summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-06-21 21:47:34 +0900
committerAdam Malczewski <[email protected]>2026-06-21 21:47:34 +0900
commitba9149a6474d0b4cb01c325acea1c83e04f22379 (patch)
tree1aa6fe1ab8f8895cde6a189f060ba0d8021844e1
parent1049c38af2466eaa4dbb698af3cf56a4826c26c6 (diff)
downloaddispatch-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.md53
-rw-r--r--packages/cli/src/args.test.ts4
-rw-r--r--packages/cli/src/args.ts6
-rw-r--r--packages/cli/src/main.ts10
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;
}