zcode-acp-server 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +192 -9
- package/README.zh-CN.md +143 -8
- package/dist/backend/client.d.ts +12 -4
- package/dist/backend/client.d.ts.map +1 -1
- package/dist/backend/client.js +72 -7
- package/dist/backend/client.js.map +1 -1
- package/dist/backend/credentials.d.ts.map +1 -1
- package/dist/backend/credentials.js +2 -1
- package/dist/backend/credentials.js.map +1 -1
- package/dist/backend/listener.d.ts +12 -4
- package/dist/backend/listener.d.ts.map +1 -1
- package/dist/backend/listener.js +68 -12
- package/dist/backend/listener.js.map +1 -1
- package/dist/backend/types.d.ts +1 -1
- package/dist/backend/types.d.ts.map +1 -1
- package/dist/bin/hub.d.ts +16 -0
- package/dist/bin/hub.d.ts.map +1 -0
- package/dist/bin/hub.js +41 -0
- package/dist/bin/hub.js.map +1 -0
- package/dist/bin/quota.d.ts +54 -0
- package/dist/bin/quota.d.ts.map +1 -0
- package/dist/bin/quota.js +333 -0
- package/dist/bin/quota.js.map +1 -0
- package/dist/config/auto-compact.d.ts +23 -0
- package/dist/config/auto-compact.d.ts.map +1 -0
- package/dist/config/auto-compact.js +67 -0
- package/dist/config/auto-compact.js.map +1 -0
- package/dist/config/mcp-discovery.d.ts +34 -0
- package/dist/config/mcp-discovery.d.ts.map +1 -0
- package/dist/config/mcp-discovery.js +153 -0
- package/dist/config/mcp-discovery.js.map +1 -0
- package/dist/config/model-cache.d.ts +12 -1
- package/dist/config/model-cache.d.ts.map +1 -1
- package/dist/config/model-cache.js +30 -8
- package/dist/config/model-cache.js.map +1 -1
- package/dist/config/options.d.ts +87 -9
- package/dist/config/options.d.ts.map +1 -1
- package/dist/config/options.js +248 -45
- package/dist/config/options.js.map +1 -1
- package/dist/config/plugin-commands.d.ts +24 -0
- package/dist/config/plugin-commands.d.ts.map +1 -0
- package/dist/config/plugin-commands.js +107 -0
- package/dist/config/plugin-commands.js.map +1 -0
- package/dist/config/provider-registry.d.ts +60 -0
- package/dist/config/provider-registry.d.ts.map +1 -0
- package/dist/config/provider-registry.js +128 -0
- package/dist/config/provider-registry.js.map +1 -0
- package/dist/config/runtime-model.d.ts +54 -18
- package/dist/config/runtime-model.d.ts.map +1 -1
- package/dist/config/runtime-model.js +105 -61
- package/dist/config/runtime-model.js.map +1 -1
- package/dist/config/skill-discovery.d.ts +35 -0
- package/dist/config/skill-discovery.d.ts.map +1 -0
- package/dist/config/skill-discovery.js +188 -0
- package/dist/config/skill-discovery.js.map +1 -0
- package/dist/handlers/account.d.ts +43 -0
- package/dist/handlers/account.d.ts.map +1 -0
- package/dist/handlers/account.js +59 -0
- package/dist/handlers/account.js.map +1 -0
- package/dist/handlers/background-tasks.d.ts +72 -0
- package/dist/handlers/background-tasks.d.ts.map +1 -0
- package/dist/handlers/background-tasks.js +330 -0
- package/dist/handlers/background-tasks.js.map +1 -0
- package/dist/handlers/dispatch.d.ts.map +1 -1
- package/dist/handlers/dispatch.js +134 -22
- package/dist/handlers/dispatch.js.map +1 -1
- package/dist/handlers/extensions.d.ts +15 -0
- package/dist/handlers/extensions.d.ts.map +1 -1
- package/dist/handlers/extensions.js +43 -27
- package/dist/handlers/extensions.js.map +1 -1
- package/dist/handlers/io.d.ts +31 -3
- package/dist/handlers/io.d.ts.map +1 -1
- package/dist/handlers/io.js +108 -9
- package/dist/handlers/io.js.map +1 -1
- package/dist/handlers/replay.d.ts +79 -0
- package/dist/handlers/replay.d.ts.map +1 -0
- package/dist/handlers/replay.js +252 -0
- package/dist/handlers/replay.js.map +1 -0
- package/dist/handlers/server-requests.d.ts +20 -4
- package/dist/handlers/server-requests.d.ts.map +1 -1
- package/dist/handlers/server-requests.js +293 -63
- package/dist/handlers/server-requests.js.map +1 -1
- package/dist/handlers/session.d.ts +113 -10
- package/dist/handlers/session.d.ts.map +1 -1
- package/dist/handlers/session.js +932 -277
- package/dist/handlers/session.js.map +1 -1
- package/dist/handlers/slash.d.ts +37 -1
- package/dist/handlers/slash.d.ts.map +1 -1
- package/dist/handlers/slash.js +143 -8
- package/dist/handlers/slash.js.map +1 -1
- package/dist/index.js +76 -12
- package/dist/index.js.map +1 -1
- package/dist/interaction/adapter.d.ts +19 -33
- package/dist/interaction/adapter.d.ts.map +1 -1
- package/dist/interaction/adapter.js +80 -79
- package/dist/interaction/adapter.js.map +1 -1
- package/dist/lazy-sessions.d.ts +35 -0
- package/dist/lazy-sessions.d.ts.map +1 -0
- package/dist/lazy-sessions.js +98 -0
- package/dist/lazy-sessions.js.map +1 -0
- package/dist/quota/cache.d.ts +18 -0
- package/dist/quota/cache.d.ts.map +1 -0
- package/dist/quota/cache.js +32 -0
- package/dist/quota/cache.js.map +1 -0
- package/dist/quota/client.d.ts +30 -0
- package/dist/quota/client.d.ts.map +1 -0
- package/dist/quota/client.js +57 -0
- package/dist/quota/client.js.map +1 -0
- package/dist/quota/color.d.ts +58 -0
- package/dist/quota/color.d.ts.map +1 -0
- package/dist/quota/color.js +95 -0
- package/dist/quota/color.js.map +1 -0
- package/dist/quota/combined.d.ts +66 -0
- package/dist/quota/combined.d.ts.map +1 -0
- package/dist/quota/combined.js +179 -0
- package/dist/quota/combined.js.map +1 -0
- package/dist/quota/format.d.ts +105 -0
- package/dist/quota/format.d.ts.map +1 -0
- package/dist/quota/format.js +219 -0
- package/dist/quota/format.js.map +1 -0
- package/dist/quota/index.d.ts +23 -0
- package/dist/quota/index.d.ts.map +1 -0
- package/dist/quota/index.js +42 -0
- package/dist/quota/index.js.map +1 -0
- package/dist/quota/opencode-go/cache.d.ts +17 -0
- package/dist/quota/opencode-go/cache.d.ts.map +1 -0
- package/dist/quota/opencode-go/cache.js +31 -0
- package/dist/quota/opencode-go/cache.js.map +1 -0
- package/dist/quota/opencode-go/client.d.ts +28 -0
- package/dist/quota/opencode-go/client.d.ts.map +1 -0
- package/dist/quota/opencode-go/client.js +48 -0
- package/dist/quota/opencode-go/client.js.map +1 -0
- package/dist/quota/opencode-go/config.d.ts +37 -0
- package/dist/quota/opencode-go/config.d.ts.map +1 -0
- package/dist/quota/opencode-go/config.js +58 -0
- package/dist/quota/opencode-go/config.js.map +1 -0
- package/dist/quota/opencode-go/format.d.ts +36 -0
- package/dist/quota/opencode-go/format.d.ts.map +1 -0
- package/dist/quota/opencode-go/format.js +87 -0
- package/dist/quota/opencode-go/format.js.map +1 -0
- package/dist/quota/opencode-go/index.d.ts +30 -0
- package/dist/quota/opencode-go/index.d.ts.map +1 -0
- package/dist/quota/opencode-go/index.js +108 -0
- package/dist/quota/opencode-go/index.js.map +1 -0
- package/dist/quota/opencode-go/parse.d.ts +41 -0
- package/dist/quota/opencode-go/parse.d.ts.map +1 -0
- package/dist/quota/opencode-go/parse.js +75 -0
- package/dist/quota/opencode-go/parse.js.map +1 -0
- package/dist/quota/opencode-go/types.d.ts +48 -0
- package/dist/quota/opencode-go/types.d.ts.map +1 -0
- package/dist/quota/opencode-go/types.js +11 -0
- package/dist/quota/opencode-go/types.js.map +1 -0
- package/dist/quota/parse.d.ts +33 -0
- package/dist/quota/parse.d.ts.map +1 -0
- package/dist/quota/parse.js +200 -0
- package/dist/quota/parse.js.map +1 -0
- package/dist/quota/types.d.ts +72 -0
- package/dist/quota/types.d.ts.map +1 -0
- package/dist/quota/types.js +10 -0
- package/dist/quota/types.js.map +1 -0
- package/dist/remote/broadcast.d.ts +47 -0
- package/dist/remote/broadcast.d.ts.map +1 -0
- package/dist/remote/broadcast.js +121 -0
- package/dist/remote/broadcast.js.map +1 -0
- package/dist/remote/config.d.ts +32 -0
- package/dist/remote/config.d.ts.map +1 -0
- package/dist/remote/config.js +65 -0
- package/dist/remote/config.js.map +1 -0
- package/dist/remote/endpoint.d.ts +30 -0
- package/dist/remote/endpoint.d.ts.map +1 -0
- package/dist/remote/endpoint.js +213 -0
- package/dist/remote/endpoint.js.map +1 -0
- package/dist/remote/hub-server.d.ts +41 -0
- package/dist/remote/hub-server.d.ts.map +1 -0
- package/dist/remote/hub-server.js +346 -0
- package/dist/remote/hub-server.js.map +1 -0
- package/dist/server.d.ts +139 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +179 -4
- package/dist/server.js.map +1 -1
- package/dist/tasks-index.d.ts +14 -4
- package/dist/tasks-index.d.ts.map +1 -1
- package/dist/tasks-index.js +145 -46
- package/dist/tasks-index.js.map +1 -1
- package/dist/translators/event-translator.d.ts +33 -0
- package/dist/translators/event-translator.d.ts.map +1 -1
- package/dist/translators/event-translator.js +101 -0
- package/dist/translators/event-translator.js.map +1 -1
- package/dist/translators/index.d.ts +1 -1
- package/dist/translators/index.d.ts.map +1 -1
- package/dist/translators/index.js +1 -1
- package/dist/translators/index.js.map +1 -1
- package/dist/translators/projection-differ.d.ts +8 -0
- package/dist/translators/projection-differ.d.ts.map +1 -1
- package/dist/translators/projection-differ.js +19 -9
- package/dist/translators/projection-differ.js.map +1 -1
- package/dist/translators/tool-helpers.d.ts +28 -0
- package/dist/translators/tool-helpers.d.ts.map +1 -1
- package/dist/translators/tool-helpers.js +104 -0
- package/dist/translators/tool-helpers.js.map +1 -1
- package/dist/translators/types.d.ts +38 -1
- package/dist/translators/types.d.ts.map +1 -1
- package/dist/translators/types.js.map +1 -1
- package/dist/utils.d.ts +36 -7
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +69 -5
- package/dist/utils.js.map +1 -1
- package/docs/ARCHITECTURE.md +130 -40
- package/docs/BACKLOG.md +59 -0
- package/docs/DEVELOPMENT.md +31 -0
- package/docs/PROTOCOL.md +328 -23
- package/docs/REMOTE-CLIENTS.md +260 -0
- package/docs/REPLAY-GUIDE.md +131 -0
- package/docs/TROUBLESHOOTING.md +131 -14
- package/docs/adr/0001-bridge-lifetime-follows-primary-client.md +14 -0
- package/docs/adr/0002-stateless-hub-over-per-bridge-acp-endpoints.md +23 -0
- package/docs/adr/0003-tail-replay-meta-and-cursor-pagination.md +40 -0
- package/docs/agents/domain.md +51 -0
- package/docs/agents/issue-tracker.md +22 -0
- package/docs/agents/triage-labels.md +15 -0
- package/docs/proposals/0001-tail-session-replay.md +136 -0
- package/docs/proposals/0002-plan-quota-usage.md +81 -0
- package/package.json +10 -6
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session-scoped background-task listener.
|
|
3
|
+
*
|
|
4
|
+
* Background sub-agents (the Agent/Task tool launched with `run_in_background:
|
|
5
|
+
* true`) keep producing events AFTER `session/prompt` has returned: their
|
|
6
|
+
* internal tool calls stream as `tool.updated`, and when they finish the
|
|
7
|
+
* backend auto-triggers a notification turn (`turn.started` with
|
|
8
|
+
* `inputSource:"background_task"`) whose `text_delta` carries the result.
|
|
9
|
+
*
|
|
10
|
+
* The per-prompt turn loop (`runEventTurn`) exits at `turn.completed` and
|
|
11
|
+
* unregisters its listener, so without this module ALL of those post-turn
|
|
12
|
+
* events are silently dropped — the user sees "background task launched" and
|
|
13
|
+
* then nothing.
|
|
14
|
+
*
|
|
15
|
+
* This listener is registered once per session (lives across prompts) and
|
|
16
|
+
* forwards a curated subset to the ACP client as out-of-band `session/update`
|
|
17
|
+
* notifications:
|
|
18
|
+
*
|
|
19
|
+
* - `session.updated` carrying `taskId` → background task status change.
|
|
20
|
+
* First sighting emits a fresh `tool_call` card (`[background] …`);
|
|
21
|
+
* subsequent sightings emit `tool_call_update` with the new status.
|
|
22
|
+
* - `turn.started` with `inputSource:"background_task"` → marks the start
|
|
23
|
+
* of a notification turn; its `model.streaming text_delta` is forwarded
|
|
24
|
+
* as `agent_message_chunk` so the user sees the background result.
|
|
25
|
+
*
|
|
26
|
+
* Everything else is ignored — the turn loop handles normal turns and normal
|
|
27
|
+
* tool calls. Per the design decision, background agents' INTERNAL tool calls
|
|
28
|
+
* are NOT expanded here (only the task-level card + the final result message).
|
|
29
|
+
*
|
|
30
|
+
* All work is best-effort: failures are logged via `warn()` and never thrown
|
|
31
|
+
* into the event loop (would crash the bridge, per AGENTS.md).
|
|
32
|
+
*/
|
|
33
|
+
import type { EventListener } from "../backend/client.js";
|
|
34
|
+
import type { ZcodeEvent } from "../backend/types.js";
|
|
35
|
+
import type { ZcodeAcpServer } from "../server.js";
|
|
36
|
+
export declare class BackgroundTaskListener implements EventListener {
|
|
37
|
+
private readonly server;
|
|
38
|
+
readonly zcodeSid: string;
|
|
39
|
+
/** taskId → tracked state. */
|
|
40
|
+
private readonly tasks;
|
|
41
|
+
/**
|
|
42
|
+
* The turnId of the currently-active background notification turn, if any.
|
|
43
|
+
* While set, that turn's `model.streaming text_delta` events are forwarded
|
|
44
|
+
* to the client as `agent_message_chunk`.
|
|
45
|
+
*/
|
|
46
|
+
private activeNotifyTurnId;
|
|
47
|
+
constructor(server: ZcodeAcpServer, zcodeSid: string);
|
|
48
|
+
handleEvent(event: ZcodeEvent): void;
|
|
49
|
+
/** Handle a `session.updated` carrying a background task status. */
|
|
50
|
+
private onTaskStatus;
|
|
51
|
+
/**
|
|
52
|
+
* Push a status update for an already-tracked task. For background Bash
|
|
53
|
+
* (reusesLaunchCard), a terminal-state transition also streams the final
|
|
54
|
+
* output via terminal_output and closes the terminal UI with terminal_exit
|
|
55
|
+
* (mirroring dispatchTerminalUpdate's 2-notification split), then clears
|
|
56
|
+
* terminalSentData so the launch card is fully retired.
|
|
57
|
+
*/
|
|
58
|
+
private emitStatusUpdate;
|
|
59
|
+
/** Forward a text delta from the active background notification turn. */
|
|
60
|
+
private forwardText;
|
|
61
|
+
private firstMessageId;
|
|
62
|
+
private activeMessageId;
|
|
63
|
+
/**
|
|
64
|
+
* Mark a tracked task as cancelled (used by `session/cancelBackgroundTask`).
|
|
65
|
+
* Emits a final `failed` update with `_meta.backgroundTask.cancelled = true`
|
|
66
|
+
* so the editor card reflects the cancellation, then drops local state. For
|
|
67
|
+
* background Bash reusing a launch terminal card, also emits terminal_exit
|
|
68
|
+
* and clears terminalSentData so the terminal UI closes cleanly.
|
|
69
|
+
*/
|
|
70
|
+
markCancelled(taskId: string): Promise<void>;
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=background-tasks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"background-tasks.d.ts","sourceRoot":"","sources":["../../src/handlers/background-tasks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAIH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAEtD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAwEnD,qBAAa,sBAAuB,YAAW,aAAa;IAC1D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAiB;IACxC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,8BAA8B;IAC9B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAkC;IACxD;;;;OAIG;IACH,OAAO,CAAC,kBAAkB,CAAuB;gBAErC,MAAM,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM;IAKpD,WAAW,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI;IAmDpC,oEAAoE;YACtD,YAAY;IAkE1B;;;;;;OAMG;YACW,gBAAgB;IAiG9B,yEAAyE;YAC3D,WAAW;IAczB,OAAO,CAAC,cAAc,CAAuB;IAC7C,OAAO,CAAC,eAAe;IAIvB;;;;;;OAMG;IACG,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CA+BnD"}
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session-scoped background-task listener.
|
|
3
|
+
*
|
|
4
|
+
* Background sub-agents (the Agent/Task tool launched with `run_in_background:
|
|
5
|
+
* true`) keep producing events AFTER `session/prompt` has returned: their
|
|
6
|
+
* internal tool calls stream as `tool.updated`, and when they finish the
|
|
7
|
+
* backend auto-triggers a notification turn (`turn.started` with
|
|
8
|
+
* `inputSource:"background_task"`) whose `text_delta` carries the result.
|
|
9
|
+
*
|
|
10
|
+
* The per-prompt turn loop (`runEventTurn`) exits at `turn.completed` and
|
|
11
|
+
* unregisters its listener, so without this module ALL of those post-turn
|
|
12
|
+
* events are silently dropped — the user sees "background task launched" and
|
|
13
|
+
* then nothing.
|
|
14
|
+
*
|
|
15
|
+
* This listener is registered once per session (lives across prompts) and
|
|
16
|
+
* forwards a curated subset to the ACP client as out-of-band `session/update`
|
|
17
|
+
* notifications:
|
|
18
|
+
*
|
|
19
|
+
* - `session.updated` carrying `taskId` → background task status change.
|
|
20
|
+
* First sighting emits a fresh `tool_call` card (`[background] …`);
|
|
21
|
+
* subsequent sightings emit `tool_call_update` with the new status.
|
|
22
|
+
* - `turn.started` with `inputSource:"background_task"` → marks the start
|
|
23
|
+
* of a notification turn; its `model.streaming text_delta` is forwarded
|
|
24
|
+
* as `agent_message_chunk` so the user sees the background result.
|
|
25
|
+
*
|
|
26
|
+
* Everything else is ignored — the turn loop handles normal turns and normal
|
|
27
|
+
* tool calls. Per the design decision, background agents' INTERNAL tool calls
|
|
28
|
+
* are NOT expanded here (only the task-level card + the final result message).
|
|
29
|
+
*
|
|
30
|
+
* All work is best-effort: failures are logged via `warn()` and never thrown
|
|
31
|
+
* into the event loop (would crash the bridge, per AGENTS.md).
|
|
32
|
+
*/
|
|
33
|
+
import { randomUUID } from "node:crypto";
|
|
34
|
+
import { log, warn } from "../utils.js";
|
|
35
|
+
/** Map a zcode background task status string to an ACP ToolCallStatus. */
|
|
36
|
+
function toAcpStatus(status) {
|
|
37
|
+
if (status === "completed")
|
|
38
|
+
return "completed";
|
|
39
|
+
if (status === "failed" || status === "error" || status === "cancelled")
|
|
40
|
+
return "failed";
|
|
41
|
+
return "in_progress"; // running / pending / unknown
|
|
42
|
+
}
|
|
43
|
+
/** Build the card title for a background task. */
|
|
44
|
+
function cardTitle(description) {
|
|
45
|
+
const d = (description ?? "").trim();
|
|
46
|
+
return d ? `[background] ${d}` : "[background] task";
|
|
47
|
+
}
|
|
48
|
+
export class BackgroundTaskListener {
|
|
49
|
+
server;
|
|
50
|
+
zcodeSid;
|
|
51
|
+
/** taskId → tracked state. */
|
|
52
|
+
tasks = new Map();
|
|
53
|
+
/**
|
|
54
|
+
* The turnId of the currently-active background notification turn, if any.
|
|
55
|
+
* While set, that turn's `model.streaming text_delta` events are forwarded
|
|
56
|
+
* to the client as `agent_message_chunk`.
|
|
57
|
+
*/
|
|
58
|
+
activeNotifyTurnId = null;
|
|
59
|
+
constructor(server, zcodeSid) {
|
|
60
|
+
this.server = server;
|
|
61
|
+
this.zcodeSid = zcodeSid;
|
|
62
|
+
}
|
|
63
|
+
handleEvent(event) {
|
|
64
|
+
try {
|
|
65
|
+
if (event.type === "session.updated") {
|
|
66
|
+
const raw = event.payload;
|
|
67
|
+
if (raw && typeof raw.taskId === "string" && raw.taskId) {
|
|
68
|
+
// taskId is verified string above; cast to the full shape (other
|
|
69
|
+
// fields are optional and read defensively inside onTaskStatus).
|
|
70
|
+
void this.onTaskStatus(raw);
|
|
71
|
+
}
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (event.type === "turn.started") {
|
|
75
|
+
const inputSource = event.payload?.["inputSource"];
|
|
76
|
+
const turnId = event.payload?.["turnId"] ?? "";
|
|
77
|
+
if (inputSource === "background_task") {
|
|
78
|
+
this.activeNotifyTurnId = turnId || null;
|
|
79
|
+
if (this.activeNotifyTurnId) {
|
|
80
|
+
log(` [bg] background notification turn started (${turnId.slice(-8)})`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
// Inside an active background notification turn → forward text deltas.
|
|
86
|
+
if (this.activeNotifyTurnId) {
|
|
87
|
+
if (event.type === "model.streaming") {
|
|
88
|
+
const kind = event.payload?.["kind"];
|
|
89
|
+
if (kind === "text_delta") {
|
|
90
|
+
const delta = event.payload?.["delta"] ?? "";
|
|
91
|
+
if (delta)
|
|
92
|
+
void this.forwardText(delta);
|
|
93
|
+
}
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (event.type === "turn.completed" || event.type === "turn.failed") {
|
|
97
|
+
log(` [bg] background notification turn ended (${event.type})`);
|
|
98
|
+
this.activeNotifyTurnId = null;
|
|
99
|
+
// Reset the result messageId so the NEXT background task in this
|
|
100
|
+
// session gets its own — otherwise the editor would merge/overwrite
|
|
101
|
+
// distinct tasks' outputs under one shared messageId.
|
|
102
|
+
this.firstMessageId = null;
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
catch (e) {
|
|
108
|
+
warn(`BackgroundTaskListener: event handling failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/** Handle a `session.updated` carrying a background task status. */
|
|
112
|
+
async onTaskStatus(p) {
|
|
113
|
+
const taskId = p.taskId;
|
|
114
|
+
const status = p.status ?? "running";
|
|
115
|
+
const acpStatus = toAcpStatus(status);
|
|
116
|
+
let task = this.tasks.get(taskId);
|
|
117
|
+
if (!task) {
|
|
118
|
+
// First sighting → decide whether to reuse the launch card or mint a
|
|
119
|
+
// fresh bg_* card. Background Bash: the dispatcher seeds
|
|
120
|
+
// `terminalSentData[toolCallId]` when the launch card is created, so its
|
|
121
|
+
// presence means the client already has a terminal card for this call —
|
|
122
|
+
// route the lifecycle back to it instead of creating a duplicate. Sub-
|
|
123
|
+
// agents (Agent/Task tool) have no such launch card → fall back to bg_*.
|
|
124
|
+
const launchToolCallId = p.toolCallId;
|
|
125
|
+
const reusesLaunchCard = !!launchToolCallId && this.server.terminalSentData.has(launchToolCallId);
|
|
126
|
+
if (reusesLaunchCard) {
|
|
127
|
+
task = {
|
|
128
|
+
acpCallId: launchToolCallId,
|
|
129
|
+
sourceToolCallId: launchToolCallId,
|
|
130
|
+
reusesLaunchCard: true,
|
|
131
|
+
description: p.description ?? "",
|
|
132
|
+
lastStatus: "",
|
|
133
|
+
};
|
|
134
|
+
this.tasks.set(taskId, task);
|
|
135
|
+
// The launch card already exists (dispatch created it in the main
|
|
136
|
+
// turn). Don't emit a new tool_call — just push the first status update
|
|
137
|
+
// so the card reflects "running in background" with task metadata.
|
|
138
|
+
await this.emitStatusUpdate(task, acpStatus, p);
|
|
139
|
+
log(` [bg] reusing launch card ${launchToolCallId.slice(-12)} for task ${taskId.slice(-12)}`);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
// No launch card to reuse → emit a fresh [background] tool_call card.
|
|
143
|
+
task = {
|
|
144
|
+
acpCallId: `bg_${randomUUID().slice(0, 12)}`,
|
|
145
|
+
reusesLaunchCard: false,
|
|
146
|
+
description: p.description ?? "",
|
|
147
|
+
lastStatus: "",
|
|
148
|
+
};
|
|
149
|
+
this.tasks.set(taskId, task);
|
|
150
|
+
const meta = {
|
|
151
|
+
backgroundTask: {
|
|
152
|
+
taskId,
|
|
153
|
+
agentId: p.terminalId ?? undefined,
|
|
154
|
+
outputPath: p.outputPath ?? undefined,
|
|
155
|
+
sourceToolCallId: p.toolCallId ?? undefined,
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
const ok = await this.server.notifyByZcodeSid(this.zcodeSid, {
|
|
159
|
+
sessionUpdate: "tool_call",
|
|
160
|
+
toolCallId: task.acpCallId,
|
|
161
|
+
title: cardTitle(task.description),
|
|
162
|
+
kind: "other",
|
|
163
|
+
status: acpStatus,
|
|
164
|
+
_meta: meta,
|
|
165
|
+
});
|
|
166
|
+
task.lastStatus = acpStatus;
|
|
167
|
+
if (ok) {
|
|
168
|
+
log(` [bg] task card emitted: ${taskId.slice(-12)} status=${acpStatus}`);
|
|
169
|
+
}
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
await this.emitStatusUpdate(task, acpStatus, p);
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Push a status update for an already-tracked task. For background Bash
|
|
176
|
+
* (reusesLaunchCard), a terminal-state transition also streams the final
|
|
177
|
+
* output via terminal_output and closes the terminal UI with terminal_exit
|
|
178
|
+
* (mirroring dispatchTerminalUpdate's 2-notification split), then clears
|
|
179
|
+
* terminalSentData so the launch card is fully retired.
|
|
180
|
+
*/
|
|
181
|
+
async emitStatusUpdate(task, acpStatus, p) {
|
|
182
|
+
// Skip no-op status updates (same as last advertised).
|
|
183
|
+
if (task.lastStatus === acpStatus && !task.reusesLaunchCard)
|
|
184
|
+
return;
|
|
185
|
+
const meta = {
|
|
186
|
+
backgroundTask: { taskId: p.taskId },
|
|
187
|
+
};
|
|
188
|
+
if (p.outputPath)
|
|
189
|
+
meta.backgroundTask["outputPath"] = p.outputPath;
|
|
190
|
+
const isTerminal = acpStatus === "completed" || acpStatus === "failed";
|
|
191
|
+
// Background Bash closing a launch terminal card: stream final output +
|
|
192
|
+
// emit terminal_exit so Zed's terminal UI finalises. The terminal_output
|
|
193
|
+
// diff guard mirrors dispatchTerminalUpdate (only emit the suffix beyond
|
|
194
|
+
// what was already streamed; for a background launch the launch text was
|
|
195
|
+
// already sent, so outputTail here is the real command output).
|
|
196
|
+
if (task.reusesLaunchCard && task.sourceToolCallId) {
|
|
197
|
+
// Stream any new command output via terminal_output. The output is
|
|
198
|
+
// tracked on `task.streamedOutput` (NOT server.terminalSentData) because
|
|
199
|
+
// terminalSentData holds the launch acknowledgement text written by the
|
|
200
|
+
// dispatcher, which is unrelated to the command's actual stdout — they
|
|
201
|
+
// aren't prefix-related, so diffing against the launch text would drop
|
|
202
|
+
// the real output entirely. Each session.updated carries a CUMULATIVE
|
|
203
|
+
// outputTail snapshot, so we diff against what we last streamed and emit
|
|
204
|
+
// only the suffix (mirrors dispatchTerminalUpdate's progress diffing).
|
|
205
|
+
const finalOutput = p.outputTail ?? p.stdoutTail ?? "";
|
|
206
|
+
const lastSent = task.streamedOutput ?? "";
|
|
207
|
+
if (finalOutput) {
|
|
208
|
+
const delta = lastSent && finalOutput.startsWith(lastSent)
|
|
209
|
+
? finalOutput.slice(lastSent.length)
|
|
210
|
+
: finalOutput;
|
|
211
|
+
if (delta) {
|
|
212
|
+
task.streamedOutput = finalOutput;
|
|
213
|
+
await this.server.notifyByZcodeSid(this.zcodeSid, {
|
|
214
|
+
sessionUpdate: "tool_call_update",
|
|
215
|
+
toolCallId: task.sourceToolCallId,
|
|
216
|
+
_meta: { terminal_output: { terminal_id: task.sourceToolCallId, data: delta } },
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
// terminal_exit only fires on a terminal status. A non-terminal (running)
|
|
221
|
+
// update has streamed its progress above; also push an in_progress status
|
|
222
|
+
// update with backgroundTask metadata so the card reflects "running in
|
|
223
|
+
// background" on first sighting (the launch card was left in_progress by
|
|
224
|
+
// the dispatcher, but we still advertise the status to attach taskId).
|
|
225
|
+
if (!isTerminal) {
|
|
226
|
+
await this.server.notifyByZcodeSid(this.zcodeSid, {
|
|
227
|
+
sessionUpdate: "tool_call_update",
|
|
228
|
+
toolCallId: task.sourceToolCallId,
|
|
229
|
+
status: acpStatus,
|
|
230
|
+
_meta: meta,
|
|
231
|
+
});
|
|
232
|
+
task.lastStatus = acpStatus;
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
const exitCode = acpStatus === "failed" ? 1 : 0;
|
|
236
|
+
meta.backgroundTask["completed"] = true;
|
|
237
|
+
await this.server.notifyByZcodeSid(this.zcodeSid, {
|
|
238
|
+
sessionUpdate: "tool_call_update",
|
|
239
|
+
toolCallId: task.sourceToolCallId,
|
|
240
|
+
status: acpStatus,
|
|
241
|
+
content: [{ type: "terminal", terminalId: task.sourceToolCallId }],
|
|
242
|
+
_meta: {
|
|
243
|
+
...meta,
|
|
244
|
+
claudeCode: { toolName: "Bash" },
|
|
245
|
+
terminal_exit: {
|
|
246
|
+
terminal_id: task.sourceToolCallId,
|
|
247
|
+
exit_code: exitCode,
|
|
248
|
+
signal: null,
|
|
249
|
+
},
|
|
250
|
+
},
|
|
251
|
+
});
|
|
252
|
+
this.server.terminalSentData.delete(task.sourceToolCallId);
|
|
253
|
+
task.lastStatus = acpStatus;
|
|
254
|
+
log(` [bg] launch card ${task.sourceToolCallId.slice(-12)} → ${acpStatus} (terminal_exit)`);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
// Subsequent sighting (bg_* card) or non-terminal → status update only.
|
|
258
|
+
if (task.lastStatus === acpStatus)
|
|
259
|
+
return;
|
|
260
|
+
const ok = await this.server.notifyByZcodeSid(this.zcodeSid, {
|
|
261
|
+
sessionUpdate: "tool_call_update",
|
|
262
|
+
toolCallId: task.acpCallId,
|
|
263
|
+
status: acpStatus,
|
|
264
|
+
_meta: meta,
|
|
265
|
+
});
|
|
266
|
+
task.lastStatus = acpStatus;
|
|
267
|
+
if (ok) {
|
|
268
|
+
log(` [bg] task status update: ${p.taskId.slice(-12)} → ${acpStatus}`);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
/** Forward a text delta from the active background notification turn. */
|
|
272
|
+
async forwardText(delta) {
|
|
273
|
+
// Allocate the messageId lazily so an empty-result turn emits nothing.
|
|
274
|
+
let msgId = this.activeMessageId();
|
|
275
|
+
if (!msgId) {
|
|
276
|
+
msgId = `bg_msg_${randomUUID().slice(0, 12)}`;
|
|
277
|
+
this.firstMessageId = msgId;
|
|
278
|
+
}
|
|
279
|
+
await this.server.notifyByZcodeSid(this.zcodeSid, {
|
|
280
|
+
sessionUpdate: "agent_message_chunk",
|
|
281
|
+
content: { type: "text", text: delta },
|
|
282
|
+
messageId: msgId,
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
firstMessageId = null;
|
|
286
|
+
activeMessageId() {
|
|
287
|
+
return this.firstMessageId;
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Mark a tracked task as cancelled (used by `session/cancelBackgroundTask`).
|
|
291
|
+
* Emits a final `failed` update with `_meta.backgroundTask.cancelled = true`
|
|
292
|
+
* so the editor card reflects the cancellation, then drops local state. For
|
|
293
|
+
* background Bash reusing a launch terminal card, also emits terminal_exit
|
|
294
|
+
* and clears terminalSentData so the terminal UI closes cleanly.
|
|
295
|
+
*/
|
|
296
|
+
async markCancelled(taskId) {
|
|
297
|
+
const task = this.tasks.get(taskId);
|
|
298
|
+
if (!task)
|
|
299
|
+
return;
|
|
300
|
+
if (task.reusesLaunchCard && task.sourceToolCallId) {
|
|
301
|
+
await this.server.notifyByZcodeSid(this.zcodeSid, {
|
|
302
|
+
sessionUpdate: "tool_call_update",
|
|
303
|
+
toolCallId: task.sourceToolCallId,
|
|
304
|
+
status: "failed",
|
|
305
|
+
content: [{ type: "terminal", terminalId: task.sourceToolCallId }],
|
|
306
|
+
_meta: {
|
|
307
|
+
backgroundTask: { taskId, cancelled: true },
|
|
308
|
+
claudeCode: { toolName: "Bash" },
|
|
309
|
+
terminal_exit: {
|
|
310
|
+
terminal_id: task.sourceToolCallId,
|
|
311
|
+
exit_code: 1,
|
|
312
|
+
signal: null,
|
|
313
|
+
},
|
|
314
|
+
},
|
|
315
|
+
});
|
|
316
|
+
this.server.terminalSentData.delete(task.sourceToolCallId);
|
|
317
|
+
}
|
|
318
|
+
else {
|
|
319
|
+
await this.server.notifyByZcodeSid(this.zcodeSid, {
|
|
320
|
+
sessionUpdate: "tool_call_update",
|
|
321
|
+
toolCallId: task.acpCallId,
|
|
322
|
+
status: "failed",
|
|
323
|
+
_meta: { backgroundTask: { taskId, cancelled: true } },
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
this.tasks.delete(taskId);
|
|
327
|
+
log(` [bg] task cancelled: ${taskId.slice(-12)}`);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
//# sourceMappingURL=background-tasks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"background-tasks.js","sourceRoot":"","sources":["../../src/handlers/background-tasks.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dispatch.d.ts","sourceRoot":"","sources":["../../src/handlers/dispatch.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,KAAK,GAAG,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"dispatch.d.ts","sourceRoot":"","sources":["../../src/handlers/dispatch.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,KAAK,GAAG,MAAM,0BAA0B,CAAC;AAcrD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAInD,yEAAyE;AACzE,wBAAsB,aAAa,CACjC,MAAM,EAAE,cAAc,EACtB,EAAE,EAAE,GAAG,CAAC,YAAY,EACpB,MAAM,EAAE,MAAM,EACd,EAAE,EAAE,aAAa,EACjB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC,CAsCf"}
|
|
@@ -9,8 +9,9 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { randomUUID } from "node:crypto";
|
|
11
11
|
import { currentModelCached } from "../config/model-cache.js";
|
|
12
|
-
import { modelContextWindow } from "../config/options.js";
|
|
13
|
-
import { extractExitCode, TOOL_KIND_MAP } from "../translators/tool-helpers.js";
|
|
12
|
+
import { buildConfigOptions, formatModelValue, modelContextWindow, parseModelValue, } from "../config/options.js";
|
|
13
|
+
import { extractExitCode, parseSubagentMetadata, TOOL_KIND_MAP, } from "../translators/tool-helpers.js";
|
|
14
|
+
import { warn } from "../utils.js";
|
|
14
15
|
import { sendSessionUpdate } from "./io.js";
|
|
15
16
|
/** Dispatch one internal event to the ACP client as a session/update. */
|
|
16
17
|
export async function dispatchEvent(server, cx, acpSid, ev, chunkMsgId) {
|
|
@@ -47,6 +48,52 @@ export async function dispatchEvent(server, cx, acpSid, ev, chunkMsgId) {
|
|
|
47
48
|
case "FilesChanged":
|
|
48
49
|
await dispatchFilesChanged(cx, acpSid, ev);
|
|
49
50
|
break;
|
|
51
|
+
case "ConfigChanged":
|
|
52
|
+
await dispatchConfigChanged(server, cx, acpSid, ev);
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Session settings changed (model/mode/thoughtLevel switch). Push the rebuilt
|
|
58
|
+
* configOptions (+ current_mode_update for mode) so the editor UI follows the
|
|
59
|
+
* switch immediately — even mid-turn, without waiting for turn completion.
|
|
60
|
+
*
|
|
61
|
+
* Builds the option structures from the session's authoritative settings
|
|
62
|
+
* (`session/read` via buildConfigOptions), then overlays the values from the
|
|
63
|
+
* event patch. A state.updated patch only carries the fields that actually
|
|
64
|
+
* changed, so building from the null defaults instead would reset every
|
|
65
|
+
* untouched field — most visibly the model dropdown jumping back to the
|
|
66
|
+
* default model mid-conversation.
|
|
67
|
+
* Best-effort: failures are logged and swallowed, never thrown into the loop.
|
|
68
|
+
*/
|
|
69
|
+
async function dispatchConfigChanged(server, cx, acpSid, ev) {
|
|
70
|
+
try {
|
|
71
|
+
// Fall back to null (defaults) only if the session mapping isn't live yet —
|
|
72
|
+
// events routed through a registered turn loop always have it.
|
|
73
|
+
const zcodeSid = server.resolveSid(acpSid) ?? null;
|
|
74
|
+
const options = await buildConfigOptions(server, zcodeSid);
|
|
75
|
+
if (ev.model)
|
|
76
|
+
options[0].currentValue = formatModelValue(ev.model.providerId, ev.model.modelId);
|
|
77
|
+
if (ev.mode !== undefined)
|
|
78
|
+
options[1].currentValue = ev.mode;
|
|
79
|
+
if (ev.thought !== undefined)
|
|
80
|
+
options[2].currentValue = ev.thought;
|
|
81
|
+
await sendSessionUpdate(cx, acpSid, {
|
|
82
|
+
sessionUpdate: "config_option_update",
|
|
83
|
+
configOptions: options,
|
|
84
|
+
});
|
|
85
|
+
if (ev.mode !== undefined) {
|
|
86
|
+
// Mirror the advertised mode so turn-completion reconciliation
|
|
87
|
+
// (emitModeIfChanged) doesn't re-emit the same value.
|
|
88
|
+
server.lastMode.set(acpSid, ev.mode);
|
|
89
|
+
await sendSessionUpdate(cx, acpSid, {
|
|
90
|
+
sessionUpdate: "current_mode_update",
|
|
91
|
+
currentModeId: ev.mode,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
catch (e) {
|
|
96
|
+
warn(`dispatch: ConfigChanged failed (${e instanceof Error ? e.message : String(e)})`);
|
|
50
97
|
}
|
|
51
98
|
}
|
|
52
99
|
function dispatchToolCallNew(server, cx, acpSid, ev) {
|
|
@@ -54,6 +101,9 @@ function dispatchToolCallNew(server, cx, acpSid, ev) {
|
|
|
54
101
|
const meta = { claudeCode: { toolName: ev.tool } };
|
|
55
102
|
if (termSupported)
|
|
56
103
|
meta["terminal_info"] = { terminal_id: ev.callId };
|
|
104
|
+
// Mark sub-agent dispatch cards so editors can badge them from creation.
|
|
105
|
+
if (ev.tool === "Agent" || ev.tool === "Task")
|
|
106
|
+
meta["subagent"] = true;
|
|
57
107
|
const update = {
|
|
58
108
|
sessionUpdate: "tool_call",
|
|
59
109
|
toolCallId: ev.callId,
|
|
@@ -80,8 +130,18 @@ function dispatchToolCallUpdate(server, cx, acpSid, ev) {
|
|
|
80
130
|
const toolName = ev.tool ?? "";
|
|
81
131
|
const termSupported = server.supportsTerminalOutput() && (toolName === "Bash" || toolName === "bash");
|
|
82
132
|
if (termSupported) {
|
|
83
|
-
// Bash
|
|
84
|
-
//
|
|
133
|
+
// Background Bash: the `result` event carries the launch acknowledgement
|
|
134
|
+
// ("Command running in background with ID: exec_…"), NOT the final output.
|
|
135
|
+
// Closing the card now (terminal_exit + status:completed) would make it
|
|
136
|
+
// indistinguishable from a normal Bash call and steal the lifecycle from
|
|
137
|
+
// the BackgroundTaskListener, which owns the real completion via
|
|
138
|
+
// out-of-band `session.updated` events. So stream any launch text via
|
|
139
|
+
// terminal_output and leave the card in_progress.
|
|
140
|
+
if (ev.background) {
|
|
141
|
+
return dispatchTerminalUpdate(server, cx, acpSid, ev, toolName, {
|
|
142
|
+
skipExit: true,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
85
145
|
return dispatchTerminalUpdate(server, cx, acpSid, ev, toolName);
|
|
86
146
|
}
|
|
87
147
|
const update = {
|
|
@@ -92,6 +152,15 @@ function dispatchToolCallUpdate(server, cx, acpSid, ev) {
|
|
|
92
152
|
const meta = {};
|
|
93
153
|
if (toolName)
|
|
94
154
|
meta["claudeCode"] = { toolName };
|
|
155
|
+
// Sub-agent (Agent/Task tool) result: surface structured metadata so editors
|
|
156
|
+
// can badge the card (agentId, background flag, token/tool/time usage). The
|
|
157
|
+
// raw result text is left in `content` for the user-facing view.
|
|
158
|
+
if ((toolName === "Agent" || toolName === "Task") &&
|
|
159
|
+
(ev.status === "completed" || ev.status === "failed")) {
|
|
160
|
+
const sub = parseSubagentMetadata(ev.rawResult);
|
|
161
|
+
if (sub)
|
|
162
|
+
meta["subagent"] = sub;
|
|
163
|
+
}
|
|
95
164
|
if (ev.output !== undefined)
|
|
96
165
|
update.rawOutput = ev.output;
|
|
97
166
|
if (ev.diffContent && ev.diffContent.length > 0) {
|
|
@@ -110,30 +179,69 @@ function dispatchToolCallUpdate(server, cx, acpSid, ev) {
|
|
|
110
179
|
* Bash terminal update — the 2-notification split (matches acp-agent.ts:5061-5094
|
|
111
180
|
* and the Python bridge's _dispatch_event). Zed correlates by terminal_id, so
|
|
112
181
|
* the two notifications MUST be separate:
|
|
113
|
-
* ① terminal_output — pure data, no status/content.
|
|
114
|
-
* result states (whenever there's data) so live output streams.
|
|
182
|
+
* ① terminal_output — pure data, no status/content. Streams live output.
|
|
115
183
|
* ② terminal_exit — terminal state only: status + content[type:terminal] +
|
|
116
|
-
* _meta.terminal_exit (with exitCode)
|
|
117
|
-
* on completed/failed.
|
|
184
|
+
* _meta.terminal_exit (with exitCode). Sent ONLY on completed/failed.
|
|
118
185
|
*
|
|
119
186
|
* Merging them into one notification causes Zed to clear the content once the
|
|
120
187
|
* turn completes (the original bug); splitting keeps the output visible.
|
|
188
|
+
*
|
|
189
|
+
* OUTPUT DEDUP (two distinct hazards, both fixed here):
|
|
190
|
+
* - Progress replay: zcode's `stdoutTail` is a CUMULATIVE snapshot of the tail,
|
|
191
|
+
* but terminal_output.data is APPEND semantics. Without diffing, each progress
|
|
192
|
+
* event replays the whole tail → N× duplication. We track the last-sent
|
|
193
|
+
* snapshot per callId and emit only the suffix beyond it.
|
|
194
|
+
* - Result replay: zcode's result payload re-sends the COMPLETE output. Once
|
|
195
|
+
* progress has streamed it, emitting it again would duplicate every line.
|
|
196
|
+
* So on completed/failed we SKIP terminal_output IF any progress already
|
|
197
|
+
* streamed data for this callId; for short commands (scheduled → result, no
|
|
198
|
+
* progress) nothing was sent yet, so we emit the full output once.
|
|
121
199
|
*/
|
|
122
|
-
async function dispatchTerminalUpdate(
|
|
123
|
-
//
|
|
200
|
+
async function dispatchTerminalUpdate(server, cx, acpSid, ev, toolName, opts = {}) {
|
|
201
|
+
// Extract the textual output from whichever payload field carries it.
|
|
124
202
|
let termData = ev.rawOutput ?? ev.rawResult;
|
|
125
203
|
if (termData && typeof termData === "object" && !Array.isArray(termData)) {
|
|
126
204
|
termData = termData["content"] ?? "";
|
|
127
205
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
206
|
+
const full = termData ? String(termData) : "";
|
|
207
|
+
const lastSent = server.terminalSentData.get(ev.callId) ?? "";
|
|
208
|
+
// ① terminal_output (pure data, append semantics).
|
|
209
|
+
// - progress (in_progress): zcode's stdoutTail is a CUMULATIVE snapshot of
|
|
210
|
+
// the tail, but terminal_output.data is APPEND semantics, so we diff and
|
|
211
|
+
// emit only the suffix beyond the last-sent snapshot.
|
|
212
|
+
// - result (completed/failed): the full output was already streamed during
|
|
213
|
+
// progress, and zcode's result re-sends the complete tail → skip, UNLESS
|
|
214
|
+
// no progress ever fired (short command: scheduled → result). In that
|
|
215
|
+
// case nothing was streamed yet, so emit the full output once.
|
|
216
|
+
const isResult = ev.status === "completed" || ev.status === "failed";
|
|
217
|
+
const alreadyStreamed = server.terminalSentData.has(ev.callId);
|
|
218
|
+
if (full && !(isResult && alreadyStreamed)) {
|
|
219
|
+
const delta = lastSent && full.startsWith(lastSent) ? full.slice(lastSent.length) : full;
|
|
220
|
+
if (delta) {
|
|
221
|
+
server.terminalSentData.set(ev.callId, full);
|
|
222
|
+
await sendSessionUpdate(cx, acpSid, {
|
|
223
|
+
sessionUpdate: "tool_call_update",
|
|
224
|
+
toolCallId: ev.callId,
|
|
225
|
+
_meta: { terminal_output: { terminal_id: ev.callId, data: delta } },
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
// Background Bash launch: leave the card in_progress — BackgroundTaskListener
|
|
230
|
+
// owns the final status + terminal_exit via out-of-band session.updated. We
|
|
231
|
+
// MUST still seed terminalSentData with an empty entry (even when no launch
|
|
232
|
+
// text was streamed) so the listener can recognise this callId as a tracked
|
|
233
|
+
// launch card and route its updates back to the same card.
|
|
234
|
+
if (opts.skipExit) {
|
|
235
|
+
if (!server.terminalSentData.has(ev.callId)) {
|
|
236
|
+
server.terminalSentData.set(ev.callId, "");
|
|
237
|
+
}
|
|
238
|
+
return;
|
|
134
239
|
}
|
|
135
240
|
// ② terminal_exit (terminal state) — only on completed/failed.
|
|
136
|
-
|
|
241
|
+
// Deliberately omits rawOutput: the output was already streamed via
|
|
242
|
+
// terminal_output.data above, and Zed renders BOTH the terminal buffer and
|
|
243
|
+
// the rawOutput fallback — including rawOutput here duplicates every line.
|
|
244
|
+
if (isResult) {
|
|
137
245
|
const exitCode = extractExitCode(ev.rawResult, ev.status === "failed");
|
|
138
246
|
const exitUpdate = {
|
|
139
247
|
sessionUpdate: "tool_call_update",
|
|
@@ -145,10 +253,10 @@ async function dispatchTerminalUpdate(_server, cx, acpSid, ev, toolName) {
|
|
|
145
253
|
terminal_exit: { terminal_id: ev.callId, exit_code: exitCode, signal: null },
|
|
146
254
|
},
|
|
147
255
|
};
|
|
148
|
-
// rawOutput gives Zed a text fallback outside the terminal render path.
|
|
149
|
-
if (ev.output !== undefined)
|
|
150
|
-
exitUpdate.rawOutput = ev.output;
|
|
151
256
|
await sendSessionUpdate(cx, acpSid, exitUpdate);
|
|
257
|
+
// Terminal session ended — clear the snapshot so a future tool reusing this
|
|
258
|
+
// callId (shouldn't happen, but defensively) starts fresh.
|
|
259
|
+
server.terminalSentData.delete(ev.callId);
|
|
152
260
|
}
|
|
153
261
|
}
|
|
154
262
|
async function dispatchUsageDelta(server, cx, acpSid, ev) {
|
|
@@ -156,8 +264,12 @@ async function dispatchUsageDelta(server, cx, acpSid, ev) {
|
|
|
156
264
|
// config.json limit.context so the editor can render the context bar.
|
|
157
265
|
let size = ev.size;
|
|
158
266
|
if (!size) {
|
|
159
|
-
|
|
160
|
-
|
|
267
|
+
// Resolve to the real backend session id — `acpSid` may be a lazy
|
|
268
|
+
// session/new placeholder that the backend rejects with "Session is not
|
|
269
|
+
// active", wasting a 5s request timeout on every usage_update.
|
|
270
|
+
const zcodeSid = server.resolveSid(acpSid) ?? acpSid;
|
|
271
|
+
const { providerId, modelId } = parseModelValue(await currentModelCached(server, zcodeSid));
|
|
272
|
+
size = modelContextWindow(providerId, modelId);
|
|
161
273
|
}
|
|
162
274
|
await sendSessionUpdate(cx, acpSid, {
|
|
163
275
|
sessionUpdate: "usage_update",
|