diff options
| author | Adam Malczewski <[email protected]> | 2026-06-21 21:47:34 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-06-21 21:47:34 +0900 |
| commit | ba9149a6474d0b4cb01c325acea1c83e04f22379 (patch) | |
| tree | 1aa6fe1ab8f8895cde6a189f060ba0d8021844e1 | |
| parent | 1049c38af2466eaa4dbb698af3cf56a4826c26c6 (diff) | |
| download | dispatch-ba9149a6474d0b4cb01c325acea1c83e04f22379.tar.gz dispatch-ba9149a6474d0b4cb01c325acea1c83e04f22379.zip | |
feat(cli): add --open flag to model-name chat command
dispatch <model> --text "..." --open now starts a new conversation AND
signals the frontend to open the tab — no need for a separate
'dispatch send --open' step.
| -rw-r--r-- | frontend-conversation-open-handoff.md | 53 | ||||
| -rw-r--r-- | packages/cli/src/args.test.ts | 4 | ||||
| -rw-r--r-- | packages/cli/src/args.ts | 6 | ||||
| -rw-r--r-- | packages/cli/src/main.ts | 10 |
4 files changed, 72 insertions, 1 deletions
diff --git a/frontend-conversation-open-handoff.md b/frontend-conversation-open-handoff.md new file mode 100644 index 0000000..ae900eb --- /dev/null +++ b/frontend-conversation-open-handoff.md @@ -0,0 +1,53 @@ +# FE handoff — conversation.open (CLI --open flag) + +Courier this to `../dispatch-web`. All changes are ADDITIVE. + +## What shipped (backend) + +The CLI's `--open` flag (`dispatch send <id> --text "..." --open`) calls +`POST /conversations/:id/open`, which emits a `conversationOpened` bus event. The +transport-ws extension subscribes and broadcasts a new WS message to ALL connected +frontend clients. + +## New WS message (additive to `WsServerMessage`) + +```ts +interface ConversationOpenMessage { + readonly type: "conversation.open"; + readonly conversationId: string; +} +``` + +The `type` is `"conversation.open"` — add it to the FE's `WsServerMessage` union +handler. It arrives as a top-level WS message (not inside `chat.delta`). + +## What the FE needs to do + +1. **Handle the WS message** — when a `"conversation.open"` message arrives, open + (or focus) a tab for `conversationId`. The backend just signals; the FE decides + whether to actually open/focus or just notify. + +2. **Suggested behavior:** + - If the conversation is already open in a tab, focus that tab. + - If not, open a new tab for it (load its history via `GET /conversations/:id`). + - Do NOT auto-focus if the user is actively typing in another tab (optional — + your discretion). + +3. **No version bumps needed** — `@dispatch/transport-contract` already exports + `ConversationOpenMessage` (additive to `WsServerMessage` since `0.13.0`). The FE + just needs to add the `type: "conversation.open"` case to its WS message handler. + +## No other integration points + +- No new HTTP endpoints for the FE (the CLI calls `POST /conversations/:id/open`). +- No new surface types. +- No new `AgentEvent` types. +- The message is a global broadcast (sent to ALL connected clients), not per- + conversation. + +## Notes + +- The `--open` flag can be combined with `--queue` (enqueue + signal) or used + without `--queue` (blocking send + signal). +- Multiple clients (e.g. phone + laptop) all receive the broadcast — each decides + independently whether to open/focus. diff --git a/packages/cli/src/args.test.ts b/packages/cli/src/args.test.ts index 392d560..70a4868 100644 --- a/packages/cli/src/args.test.ts +++ b/packages/cli/src/args.test.ts @@ -51,6 +51,7 @@ describe("parseArgs", () => { conversationId: undefined, reasoningEffort: undefined, showReasoning: false, + open: false, }); }); @@ -66,6 +67,7 @@ describe("parseArgs", () => { conversationId: undefined, reasoningEffort: undefined, showReasoning: false, + open: false, }); }); @@ -100,6 +102,7 @@ describe("parseArgs", () => { conversationId: "abc", reasoningEffort: undefined, showReasoning: true, + open: false, }); }); @@ -115,6 +118,7 @@ describe("parseArgs", () => { conversationId: undefined, reasoningEffort: "high", showReasoning: false, + open: false, }); }); diff --git a/packages/cli/src/args.ts b/packages/cli/src/args.ts index b4478b0..4d6652e 100644 --- a/packages/cli/src/args.ts +++ b/packages/cli/src/args.ts @@ -25,6 +25,7 @@ export type ParsedCommand = readonly conversationId?: string | undefined; readonly reasoningEffort?: ReasoningEffort | undefined; readonly showReasoning: boolean; + readonly open: boolean; } | { readonly kind: "list"; readonly server: string; readonly query?: string } | { readonly kind: "read"; readonly server: string; readonly conversationId: string } @@ -189,6 +190,7 @@ export function parseArgs(argv: readonly string[], opts: ParseOpts): ParsedComma let conversationId: string | undefined; let reasoningEffort: ReasoningEffort | undefined; let showReasoning = false; + let open = false; let server = opts.defaultServer; for (let i = 1; i < argv.length; i++) { @@ -218,6 +220,9 @@ export function parseArgs(argv: readonly string[], opts: ParseOpts): ParsedComma case "--show-reasoning": showReasoning = true; break; + case "--open": + open = true; + break; case "--effort": if (i + 1 >= argv.length) return { @@ -256,5 +261,6 @@ export function parseArgs(argv: readonly string[], opts: ParseOpts): ParsedComma conversationId, reasoningEffort, showReasoning, + open, }; } diff --git a/packages/cli/src/main.ts b/packages/cli/src/main.ts index dd8cfa8..04e7231 100644 --- a/packages/cli/src/main.ts +++ b/packages/cli/src/main.ts @@ -25,7 +25,7 @@ const USAGE = `Usage: dispatch list [<prefix>] [--server <url>] dispatch read <conversationId> [--server <url>] dispatch send <conversationId> --text "..." [--queue] [--open] [--cwd <dir>] [--effort <level>] [--server <url>] - dispatch <modelName> --text "..." [--file <path>] [--cwd <dir>] [--conversation <id>] [--effort <level>] [--server <url>] [--show-reasoning] + dispatch <modelName> --text "..." [--file <path>] [--cwd <dir>] [--conversation <id>] [--effort <level>] [--server <url>] [--show-reasoning] [--open] dispatch --help Effort levels: low, medium, high (default), xhigh, max`; @@ -153,6 +153,14 @@ async function main(): Promise<void> { if (conversationId) { process.stdout.write(`\n[conversation] ${conversationId}\n`); + + if (parsed.open) { + await openConversation( + { fetchImpl: globalThis.fetch }, + { server: parsed.server, conversationId }, + ); + process.stdout.write(`Signaled frontend to open ${conversationId}\n`); + } } break; } |
