Skip to content

[WIP] Symphony: CLI-IPC: WebSocket bridge for provider agents to trigger Maestro UI actions (#511)#1033

Open
DRAZY wants to merge 24 commits into
RunMaestro:mainfrom
DRAZY:symphony/issue-511-mpg716fi
Open

[WIP] Symphony: CLI-IPC: WebSocket bridge for provider agents to trigger Maestro UI actions (#511)#1033
DRAZY wants to merge 24 commits into
RunMaestro:mainfrom
DRAZY:symphony/issue-511-mpg716fi

Conversation

@DRAZY
Copy link
Copy Markdown

@DRAZY DRAZY commented May 22, 2026

Maestro Symphony Contribution

Closes #511

Contributed via Maestro Symphony.

Status: In Progress
Started: 2026-05-22T00:40:48.300Z


This PR will be updated automatically when the Auto Run completes.

Summary by CodeRabbit

  • New Features

    • Added CLI commands: auto-run, open-file, refresh-auto-run, refresh-files, and status for interacting with the desktop app
    • send gains --tab to focus a session; CLI can configure/save/launch Auto Run playbooks
    • Desktop app now exposes remote handlers to open files, refresh file tree/Auto Run docs, and prepopulate the batch-run modal; CLI discovery/auto-start integrated
  • Tests

    • Extensive test coverage for new CLI commands, IPC/WebSocket client, and remote desktop integrations

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 22, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 311957f0-6592-4497-b962-1cbc0b63ee3c

📥 Commits

Reviewing files that changed from the base of the PR and between f844243 and d5a098c.

📒 Files selected for processing (14)
  • src/__tests__/cli/commands/auto-run.test.ts
  • src/__tests__/cli/services/maestro-client.test.ts
  • src/__tests__/main/ipc/handlers/web.test.ts
  • src/__tests__/main/web-server/handlers/messageHandlers.test.ts
  • src/__tests__/shared/cli-server-discovery.test.ts
  • src/cli/commands/auto-run.ts
  • src/cli/commands/send.ts
  • src/cli/services/maestro-client.ts
  • src/main/ipc/handlers/web.ts
  • src/main/web-server/handlers/messageHandlers.ts
  • src/main/web-server/types.ts
  • src/main/web-server/web-server-factory.ts
  • src/renderer/App.tsx
  • src/shared/cli-server-discovery.ts
💤 Files with no reviewable changes (1)
  • src/cli/commands/send.ts

📝 Walkthrough

Walkthrough

Implements a CLI↔Electron WebSocket IPC bridge: discovery file persistence, MaestroClient, new CLI commands (auto-run, open-file, refresh-files, refresh-auto-run, status, send --tab), WebSocket message types and handlers, preload/renderer remote handlers, UI wiring, and extensive tests.

Changes

CLI‑IPC Bridge

Layer / File(s) Summary
All changes (full feature set)
src/cli/..., src/main/..., src/main/web-server/..., src/shared/cli-server-discovery.ts, src/main/preload/process.ts, src/renderer/..., src/__tests__/...
Adds a WebSocket-based MaestroClient with requestId routing and timeouts; new CLI commands (auto-run, open-file, refresh-files, refresh-auto-run, status), --tab focus for send; persistent discovery file (read/write/delete/isRunning); WebServer message handlers and CallbackRegistry extensions for open file / refresh / configure auto-run; preload API and renderer event wiring, App UI support to prepopulate BatchRunner modal; and comprehensive test coverage across these areas.

Sequence Diagram(s)

sequenceDiagram
  participant CLI
  participant MaestroClient
  participant WebServer
  participant CallbackRegistry
  participant Renderer
  CLI->>MaestroClient: connect() + sendCommand(configure_auto_run/open_file_tab/refresh...)
  MaestroClient->>WebServer: WebSocket message (requestId, type, payload)
  WebServer->>CallbackRegistry: route to handler (openFileTab/refreshFileTree/configureAutoRun)
  CallbackRegistry->>Renderer: invoke callback -> dispatch CustomEvent (maestro:*)
  Renderer->>CLI: (optional) sendRemoteConfigureAutoRunResponse via IPC channel
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

runmaestro.ai

"🐰 I hopped from CLI to window, a silent little sweep—
Commands now cross the wire, so users wake from sleep.
Refresh and open files, configure and run,
Agents whisper to the UI — and now the work gets done!"

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

@DRAZY DRAZY marked this pull request as ready for review May 22, 2026 04:07
@DRAZY
Copy link
Copy Markdown
Author

DRAZY commented May 22, 2026

Symphony Contribution Summary

This pull request was created using Maestro Symphony - connecting AI-powered contributors with open source projects.

Contribution Stats

Metric Value
Input Tokens 55,177,983
Output Tokens 188,540
Total Tokens 55,366,523
Estimated Cost $0.00
Time Spent 2h 59m 41s
Documents Processed 4
Tasks Completed 23

Powered by MaestroLearn about Symphony

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 22, 2026

Greptile Summary

This PR introduces a WebSocket-based CLI↔desktop IPC bridge, letting provider agents trigger Maestro UI actions — open a file tab, refresh the file tree, refresh Auto Run docs, configure/launch Auto Run, and focus a session — directly from the command line. The web server is now auto-started at app launch and writes a discovery file so the CLI can connect without user interaction.

  • New CLI commands (auto-run, open-file, refresh-files, refresh-auto-run, status, --tab flag on send) connect via a token-authenticated WebSocket and send typed commands to the running desktop.
  • New WebSocket message handlers dispatch four new action types through the existing CallbackRegistryWebServer → IPC → renderer pipeline.
  • BatchRunnerModal gains an initialConfig prop so a remote configure_auto_run message can pre-populate its document list, prompt, and loop settings.

Confidence Score: 3/5

The core IPC plumbing is sound, but the auto-run command's path handling silently substitutes a different file than the one the user specified, which could produce wrong results without any error.

The auto-run command resolves and validates the caller's full file path, then discards everything but the basename before sending it to the desktop. The desktop reads the document from the session's configured autoRunFolderPath, so the validated path and the file actually used can be entirely different. The rest of the bridge (discovery, token auth, callback registry, preload wiring) follows established patterns and looks correct.

src/cli/commands/auto-run.ts (path validation mismatch) and src/main/ipc/handlers/web.ts / src/main/web-server/web-server-factory.ts (CLI server lifetime tied to Live mode toggle).

Important Files Changed

Filename Overview
src/cli/commands/auto-run.ts New CLI command for configuring Auto Run; validates full file paths locally but sends only basenames to the desktop, which reads documents from autoRunFolderPath — creating a silent path mismatch.
src/cli/services/maestro-client.ts New WebSocket client for CLI-to-desktop IPC; response matching falls back to first-match-wins by response type when the server omits requestId, which could misbehave with concurrent same-type requests.
src/main/web-server/web-server-factory.ts Adds four new action callbacks; configureAutoRun uses Date.now() for IPC response channel names which is not collision-safe; stopping the web server via Live mode toggle also kills the CLI IPC server.
src/main/ipc/handlers/web.ts Extracts ensureCliServer to auto-start the web server at startup; deleteCliServerInfo is also called on explicit stop/disable, meaning the always-on CLI server is torn down when the user disables Live mode.
src/shared/cli-server-discovery.ts New shared module for writing/reading/deleting the CLI server discovery JSON file; uses atomic rename-based write and process.kill(pid, 0) liveness check — well-structured.
src/renderer/App.tsx Handles the four new remote CustomEvents in a single useEffect; duplicates ConfigureAutoRunConfig/Result types that are already canonical in web-server/types.ts.
src/main/preload/process.ts Exposes four new IPC listener APIs and sendRemoteConfigureAutoRunResponse to the renderer via the preload bridge; clean and consistent with the existing pattern.

Sequence Diagram

sequenceDiagram
    participant CLI as CLI Process
    participant Disc as cli-server.json
    participant WS as WebServer (WebSocket)
    participant Main as Main Process (ipcMain)
    participant Renderer as Renderer (React)

    Note over Main: App startup
    Main->>WS: ensureCliServer()
    WS-->>Disc: writeCliServerInfo(port, token, pid)

    CLI->>Disc: readCliServerInfo()
    Disc-->>CLI: "{port, token}"
    CLI->>WS: "WebSocket connect ws://localhost:{port}/{token}/ws"
    CLI->>WS: "{type: configure_auto_run, sessionId, documents}"
    WS->>Main: configureAutoRunCallback(sessionId, config)
    Main->>Renderer: webContents.send(remote:configureAutoRun, sessionId, config, responseChannel)
    Renderer->>Main: ipcRenderer.send(responseChannel, result)
    Main-->>WS: resolve(result)
    WS-->>CLI: "{type: configure_auto_run_result, success, playbookId}"

    Note over Main: App quit or Live mode stop
    Main->>Disc: deleteCliServerInfo()
Loading

Reviews (1): Last reviewed commit: "MAESTRO: Add auto-run CLI IPC tests" | Re-trigger Greptile

Comment on lines +60 to +66
const documentPaths = documentPathArgs.map(resolveMarkdownDocument);
const maxLoops = parseMaxLoops(options.maxLoops);
const sessionId = resolveSessionId(options);
const documents = documentPaths.map((documentPath) => ({
filename: path.basename(documentPath),
resetOnCompletion: options.resetOnCompletion || false,
}));
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 CLI path validation disconnected from desktop file resolution

resolveMarkdownDocument resolves and validates the full absolute path passed by the caller, but path.basename() strips everything except the filename before sending it to the desktop. The desktop handler in App.tsx then reads the document from session.autoRunFolderPath + "/" + filename, not from the path the user provided. If the user runs maestro auto-run /some/other/path/spec.md, the CLI validates that /some/other/path/spec.md exists, but the desktop silently reads <autoRunFolderPath>/spec.md — a potentially different file (or one that doesn't exist in the auto-run folder at all). The validated path is entirely unused after validation.

Comment thread src/main/ipc/handlers/web.ts Outdated
Comment on lines +265 to +268
try {
logger.info('Stopping web server', 'WebServer');
await webServer.stop();
deleteCliServerInfo();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Stopping Live mode also kills the always-on CLI IPC server

ensureCliServer is called at app startup to guarantee CLI IPC is always available. However, both the live:stopServer and live:disableAllSessions handlers call deleteCliServerInfo() and stop the same server instance. If a user disables the Live feature from the UI, the CLI discovery file is deleted and the server is stopped, silently breaking all CLI IPC commands until the app restarts. Since the CLI server is now a first-class always-on service, it should either be started on a separate port/instance or the stop paths should guard against removing the CLI server's discovery info.

Comment thread src/cli/services/maestro-client.ts Outdated
Comment on lines +136 to +153
} catch {
return;
}

if (message.type === 'error') {
this.rejectMatchingOrAll(message);
return;
}

for (const [requestId, pending] of this.pendingRequests) {
if (message.type !== pending.responseType) continue;
if (message.requestId && message.requestId !== requestId) continue;

clearTimeout(pending.timeout);
this.pendingRequests.delete(requestId);

if (message.success === false) {
pending.reject(new Error(this.getErrorMessage(message)));
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Response matching falls back to first-match-wins by response type

When the server sends a response that omits requestId, the loop matches the first pending entry whose responseType equals message.type, regardless of which request actually triggered it. If two sendCommand calls with the same expected response type are in-flight simultaneously, the earlier one in the map could be resolved by the response intended for the later one. In current usage withMaestroClient creates a fresh client per action, but the MaestroClient class exposes sendCommand publicly, making this a latent bug for callers who issue multiple concurrent commands on one client instance.

Comment thread src/renderer/App.tsx
Comment on lines 200 to +215
import { useTabStore } from './stores/tabStore';
import { useFileExplorerStore } from './stores/fileExplorerStore';

type RemoteConfigureAutoRunConfig = {
documents: Array<{ filename: string; resetOnCompletion?: boolean }>;
prompt?: string;
loopEnabled?: boolean;
maxLoops?: number;
saveAsPlaybook?: string;
launch?: boolean;
};

type RemoteConfigureAutoRunResult = {
success: boolean;
playbookId?: string;
error?: string;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 ConfigureAutoRunConfig/Result types duplicated across four files

Identical type shapes are declared independently in App.tsx, useRemoteIntegration.ts, renderer/global.d.ts, and main/preload/process.ts (with a canonical version already in main/web-server/types.ts). Any field addition requires four separate edits. These could be consolidated to imports from the canonical source.

Comment on lines +580 to +582
return new Promise((resolve) => {
const responseChannel = `remote:configureAutoRun:response:${Date.now()}`;
let resolved = false;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Date.now() is not guaranteed to be unique — two concurrent configure_auto_run WebSocket messages arriving within the same millisecond would produce identical channel names, causing ipcMain.once listeners to race and cross-resolve each other's responses. randomUUID() (already used elsewhere in this repo) gives collision-free channel names.

Suggested change
return new Promise((resolve) => {
const responseChannel = `remote:configureAutoRun:response:${Date.now()}`;
let resolved = false;
return new Promise((resolve) => {
const responseChannel = `remote:configureAutoRun:response:${crypto.randomUUID()}`;
let resolved = false;

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

🧹 Nitpick comments (1)
src/cli/commands/send.ts (1)

122-125: 💤 Low value

Unreachable code after process.exit(1).

The return statement on line 124 is unreachable since process.exit(1) terminates the process immediately.

♻️ Remove unreachable return
 	if (!result.success) {
 		process.exit(1);
-		return;
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/cli/commands/send.ts` around lines 122 - 125, The code checks
result.success and calls process.exit(1) on failure, but the subsequent return;
after process.exit(1) is unreachable — remove the redundant return statement in
the failure branch (the block that tests result.success) inside send.ts (where
result.success is evaluated) so the function is clean; if you intended to avoid
exiting the whole process, replace process.exit(1) with a thrown error or an
early return instead of keeping both.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/cli/commands/auto-run.ts`:
- Around line 19-24: The ConfigureAutoRunResult type currently makes success
optional which lets malformed responses be treated as success; update the
interface ConfigureAutoRunResult so success is required (remove the ? from
success) and then change the runtime check in the auto-run command (where it
currently tests success === false around the configure_auto_run response
handling) to a falsy check (e.g., if (!result.success) { ... }) so any missing
or false value is treated as a failure; update any related branches that rely on
optional success to use the required boolean instead.

In `@src/cli/services/maestro-client.ts`:
- Around line 132-158: The handleMessage currently swallows malformed JSON and
can match responses by responseType alone, risking misrouting; change it to (1)
surface JSON parse errors instead of silently returning—catch JSON.parse errors
and re-throw or pass them to the global error telemetry (so Sentry can capture
them) while preserving the function signature, and (2) match incoming messages
to pending requests by requestId first (use message.requestId to look up the
specific pending entry in pendingRequests) and only fall back to strict
responseType matching when no requestId is provided, ensuring you only
clearTimeout, delete, resolve, or reject the exact pending request; reference
handleMessage, pendingRequests, rejectMatchingOrAll, and getErrorMessage when
making the changes.

In `@src/main/ipc/handlers/web.ts`:
- Around line 55-77: The ensureCliServer function always overwrites startedAt
with Date.now() when calling writeCliServerInfo even if the server was already
running; update the logic so startedAt reflects the real server start time by
either moving the writeCliServerInfo call inside the if (!webServer.isActive())
block so it only writes when webServer.start() is executed, or by querying the
WebServer instance for its original start time (e.g., add/use a method like
webServer.getStartTime() or webServer.getStartedAt() and pass that value to
writeCliServerInfo) while keeping other fields (getPort/getSecurityToken/pid)
unchanged.
- Around line 71-76: The discovery writer writeCliServerInfo currently writes a
temp file and renames it to cli-server.json without enforcing permissions;
update writeCliServerInfo to create the temp file with mode 0o600 (use
fs.writeFile with { mode: 0o600 } or fs.open+fs.write), after rename explicitly
chmod the final cli-server.json to 0o600 (fs.chmod) and also chmod the temp file
to 0o600 before renaming to be safe; additionally check the containing config
directory permissions and, if group/world bits are set, chmod the directory to
0o700 so the token file cannot be read by other users.

In `@src/main/web-server/handlers/messageHandlers.ts`:
- Around line 771-779: The current check in messageHandlers.ts (around the
configure_auto_run handling) only ensures documents is a non-empty array but
doesn't validate each item's shape, so malformed items (e.g., missing or
non-string filename) can slip through; update the validation in the handler that
sends the 'configure_auto_run_result' response to iterate over documents and
assert each is an object with required properties (at minimum a string
'filename' and any other expected fields), returning a failure response with an
explanatory error if any item is invalid, and apply the same strict per-item
validation to the other configure_auto_run forwarding block referenced around
lines 789-796 so downstream handlers receive well-formed document entries.

In `@src/main/web-server/web-server-factory.ts`:
- Around line 581-582: The responseChannel currently uses Date.now() which can
collide for concurrent configureAutoRun requests; update the creation of
responseChannel (the variable named responseChannel in the configureAutoRun
handling code) to append a collision-safe unique id (e.g., a UUID or
crypto.randomUUID()) instead of Date.now(); ensure you add the necessary import
or use the built-in crypto API and keep the same prefix
`remote:configureAutoRun:response:` so the rest of the handler
(listeners/cleanup logic tied to responseChannel) continues to work unchanged.

In `@src/renderer/App.tsx`:
- Around line 1863-1980: handleRemoteConfigureAutoRun currently can throw and
never send a response; wrap the entire function body in a top-level try/catch so
every code path calls sendResponse and unexpected errors are handled: inside the
catch, call the Sentry utility (captureException) with the caught error,
sendResponse({ success: false, error: error instanceof Error ? error.message :
String(error) }), and then re-throw the error to preserve upstream reporting;
ensure this covers calls like window.maestro.autorun.readDoc,
startBatchRunRef.current(...), and all mapping/access code before returning or
updating state (setSessions, setAutoRunDocumentList, setRemoteBatchRunConfig,
etc.).
- Around line 1839-1841: Replace the console.error-only catch in the remote
file-open flow with explicit Sentry reporting using captureException (and
captureMessage if helpful) from src/utils/sentry.ts, include contextual metadata
(e.g., action: "openRemoteFile", file identifier/path, userId or session if
available), remove the console.error call, and then surface a recoverable result
instead of swallowing (for example set an error state / call the existing UI
notification handler or rethrow the error) so the UI can show a user-facing
failure; reference the catch block in App.tsx and use
captureException/captureMessage to report the error with context.

In `@src/shared/cli-server-discovery.ts`:
- Around line 50-57: The numeric validation for info.port, info.pid (and similar
checks at the other occurrence) is too lax; replace Number.isFinite checks with
stricter integer-and-range checks: ensure info.port is Number.isInteger and
between 1 and 65535, ensure info.pid is Number.isInteger and > 0 (no zero or
negatives), and ensure info.startedAt is a Number.isInteger and non-negative (or
a sensible timestamp lower bound); then only run process liveness checks (e.g.,
process.kill(info.pid, 0)) after these stricter validations so you never call
kill with pid 0 or an invalid value.
- Around line 72-73: The discovery file is written without explicit file
permissions, which can leave the IPC auth token world-readable; when creating
the temp file (tmpPath) before renaming to filePath, ensure it is created with
restrictive permissions (e.g., 0o600) by passing a mode option to
fs.writeFileSync or immediately calling fs.chmodSync(tmpPath, 0o600) after
writing and before fs.renameSync, so the written JSON (info) is only
readable/writable by the owner.

---

Nitpick comments:
In `@src/cli/commands/send.ts`:
- Around line 122-125: The code checks result.success and calls process.exit(1)
on failure, but the subsequent return; after process.exit(1) is unreachable —
remove the redundant return statement in the failure branch (the block that
tests result.success) inside send.ts (where result.success is evaluated) so the
function is clean; if you intended to avoid exiting the whole process, replace
process.exit(1) with a thrown error or an early return instead of keeping both.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6b2c7d2e-05dd-42a3-8a70-cafbc8106ece

📥 Commits

Reviewing files that changed from the base of the PR and between 1006e3b and f844243.

📒 Files selected for processing (40)
  • src/__tests__/cli/commands/auto-run.test.ts
  • src/__tests__/cli/commands/open-file.test.ts
  • src/__tests__/cli/commands/refresh-auto-run.test.ts
  • src/__tests__/cli/commands/refresh-files.test.ts
  • src/__tests__/cli/commands/send.test.ts
  • src/__tests__/cli/commands/status.test.ts
  • src/__tests__/cli/services/maestro-client-session.test.ts
  • src/__tests__/cli/services/maestro-client.test.ts
  • src/__tests__/main/app-lifecycle/quit-handler.test.ts
  • src/__tests__/main/ipc/handlers/web.test.ts
  • src/__tests__/main/web-server/handlers/messageHandlers.test.ts
  • src/__tests__/main/web-server/managers/CallbackRegistry.test.ts
  • src/__tests__/main/web-server/web-server-factory.test.ts
  • src/__tests__/renderer/hooks/useRemoteIntegration.test.ts
  • src/__tests__/shared/cli-server-discovery.test.ts
  • src/cli/commands/auto-run.ts
  • src/cli/commands/open-file.ts
  • src/cli/commands/refresh-auto-run.ts
  • src/cli/commands/refresh-files.ts
  • src/cli/commands/send.ts
  • src/cli/commands/status.ts
  • src/cli/index.ts
  • src/cli/services/maestro-client.ts
  • src/main/app-lifecycle/quit-handler.ts
  • src/main/index.ts
  • src/main/ipc/handlers/index.ts
  • src/main/ipc/handlers/web.ts
  • src/main/preload/process.ts
  • src/main/web-server/WebServer.ts
  • src/main/web-server/handlers/messageHandlers.ts
  • src/main/web-server/managers/CallbackRegistry.ts
  • src/main/web-server/types.ts
  • src/main/web-server/web-server-factory.ts
  • src/prompts/maestro-system-prompt.md
  • src/renderer/App.tsx
  • src/renderer/components/AppModals.tsx
  • src/renderer/components/BatchRunnerModal.tsx
  • src/renderer/global.d.ts
  • src/renderer/hooks/remote/useRemoteIntegration.ts
  • src/shared/cli-server-discovery.ts

Comment thread src/cli/commands/auto-run.ts
Comment thread src/cli/services/maestro-client.ts
Comment thread src/main/ipc/handlers/web.ts
Comment thread src/main/ipc/handlers/web.ts
Comment thread src/main/web-server/handlers/messageHandlers.ts Outdated
Comment thread src/main/web-server/web-server-factory.ts Outdated
Comment thread src/renderer/App.tsx
Comment thread src/renderer/App.tsx
Comment thread src/shared/cli-server-discovery.ts Outdated
Comment thread src/shared/cli-server-discovery.ts Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CLI-IPC: WebSocket bridge for provider agents to trigger Maestro UI actions

1 participant