zcode-acp-server 0.22.0 → 0.23.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.
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Global user configuration (~/.config/zcode-acp/config.json).
3
+ *
4
+ * The file is the AUTHORITATIVE source for user preferences that must not
5
+ * depend on how a process was launched: the hub daemon is idle-exited and
6
+ * re-spawned by whichever bridge needs it next, so its birth env rotates
7
+ * between GUI-launched editors (no shell rc vars) and interactive shells.
8
+ * Every read is live (no cache) — editing the file takes effect on the next
9
+ * use (e.g. the next remote incubation) without restarting the hub.
10
+ *
11
+ * Precedence everywhere: config file > environment variable > built-in
12
+ * default. Env vars remain fully supported as a fallback for setups without
13
+ * a file and for one-off/test overrides of unspecified fields.
14
+ *
15
+ * Per-process plumbing (ZCODE_ACP_REMOTE_ORIGIN, _PIN_CWD,
16
+ * ZCODE_ACP_RESUME_SESSION) is deliberately NOT file-configurable — those
17
+ * carry per-request/per-role state, not user preference.
18
+ */
19
+ /** Terminal incubation preferences for remote session-create (ADR-0016). */
20
+ export interface TerminalPrefs {
21
+ /** false → remote session-create stays headless (no visible window). */
22
+ enabled?: boolean;
23
+ /** Terminal app name (Terminal, iTerm, wezterm, kitty, alacritty, ghostty, …). */
24
+ app?: string;
25
+ /** Shell command template; `{script}` is replaced with the quoted script path. */
26
+ command?: string;
27
+ }
28
+ /** The `remote` section of the user config file. */
29
+ export interface RemoteUserConfig {
30
+ enabled?: boolean;
31
+ token?: string;
32
+ hubPort?: number;
33
+ hubHost?: string;
34
+ bridgePort?: number;
35
+ terminal?: TerminalPrefs;
36
+ }
37
+ export interface UserConfig {
38
+ remote?: RemoteUserConfig;
39
+ }
40
+ /** Resolve the config file path: $XDG_CONFIG_HOME/zcode-acp or ~/.config/zcode-acp. */
41
+ export declare function userConfigPath(env?: NodeJS.ProcessEnv): string;
42
+ /**
43
+ * Read and validate the user config. Best-effort: a missing file is the
44
+ * normal no-file path ({}), anything unreadable/malformed warns once and
45
+ * reads as absent so the env fallback keeps the process working.
46
+ */
47
+ export declare function loadUserConfig(env?: NodeJS.ProcessEnv): UserConfig;
48
+ //# sourceMappingURL=user-config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"user-config.d.ts","sourceRoot":"","sources":["../../src/config/user-config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAQH,4EAA4E;AAC5E,MAAM,WAAW,aAAa;IAC5B,wEAAwE;IACxE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,kFAAkF;IAClF,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,kFAAkF;IAClF,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,oDAAoD;AACpD,MAAM,WAAW,gBAAgB;IAC/B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,aAAa,CAAC;CAC1B;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,CAAC,EAAE,gBAAgB,CAAC;CAC3B;AAED,uFAAuF;AACvF,wBAAgB,cAAc,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAG3E;AAMD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,UAAU,CAqD/E"}
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Global user configuration (~/.config/zcode-acp/config.json).
3
+ *
4
+ * The file is the AUTHORITATIVE source for user preferences that must not
5
+ * depend on how a process was launched: the hub daemon is idle-exited and
6
+ * re-spawned by whichever bridge needs it next, so its birth env rotates
7
+ * between GUI-launched editors (no shell rc vars) and interactive shells.
8
+ * Every read is live (no cache) — editing the file takes effect on the next
9
+ * use (e.g. the next remote incubation) without restarting the hub.
10
+ *
11
+ * Precedence everywhere: config file > environment variable > built-in
12
+ * default. Env vars remain fully supported as a fallback for setups without
13
+ * a file and for one-off/test overrides of unspecified fields.
14
+ *
15
+ * Per-process plumbing (ZCODE_ACP_REMOTE_ORIGIN, _PIN_CWD,
16
+ * ZCODE_ACP_RESUME_SESSION) is deliberately NOT file-configurable — those
17
+ * carry per-request/per-role state, not user preference.
18
+ */
19
+ import { readFileSync } from "node:fs";
20
+ import { homedir } from "node:os";
21
+ import path from "node:path";
22
+ import { warn } from "../utils.js";
23
+ /** Resolve the config file path: $XDG_CONFIG_HOME/zcode-acp or ~/.config/zcode-acp. */
24
+ export function userConfigPath(env = process.env) {
25
+ const base = (env.XDG_CONFIG_HOME ?? "").trim() || path.join(homedir(), ".config");
26
+ return path.join(base, "zcode-acp", "config.json");
27
+ }
28
+ function isPlainObject(v) {
29
+ return typeof v === "object" && v !== null && !Array.isArray(v);
30
+ }
31
+ /**
32
+ * Read and validate the user config. Best-effort: a missing file is the
33
+ * normal no-file path ({}), anything unreadable/malformed warns once and
34
+ * reads as absent so the env fallback keeps the process working.
35
+ */
36
+ export function loadUserConfig(env = process.env) {
37
+ const file = userConfigPath(env);
38
+ let raw;
39
+ try {
40
+ raw = readFileSync(file, "utf8");
41
+ }
42
+ catch {
43
+ return {}; // missing (or unreadable) file — env fallback applies
44
+ }
45
+ let parsed;
46
+ try {
47
+ parsed = JSON.parse(raw);
48
+ }
49
+ catch (e) {
50
+ warn(`config: ${file} is not valid JSON — ignoring (${e instanceof Error ? e.message : String(e)})`);
51
+ return {};
52
+ }
53
+ if (!isPlainObject(parsed)) {
54
+ warn(`config: ${file} is not a JSON object — ignoring`);
55
+ return {};
56
+ }
57
+ const remote = parsed["remote"];
58
+ if (remote === undefined)
59
+ return {};
60
+ if (!isPlainObject(remote)) {
61
+ warn(`config: "remote" in ${file} is not an object — ignoring the section`);
62
+ return {};
63
+ }
64
+ const out = {};
65
+ if (typeof remote["enabled"] === "boolean")
66
+ out.enabled = remote["enabled"];
67
+ if (typeof remote["token"] === "string" && remote["token"].trim())
68
+ out.token = remote["token"].trim();
69
+ for (const key of ["hubPort", "bridgePort"]) {
70
+ const v = remote[key];
71
+ if (v === undefined)
72
+ continue;
73
+ if (typeof v !== "number" || !Number.isInteger(v) || v < 1 || v > 65535) {
74
+ warn(`config: remote.${key}=${JSON.stringify(v)} is not a valid port — ignoring`);
75
+ }
76
+ else {
77
+ out[key] = v;
78
+ }
79
+ }
80
+ if (typeof remote["hubHost"] === "string" && remote["hubHost"].trim()) {
81
+ out.hubHost = remote["hubHost"].trim();
82
+ }
83
+ const terminal = remote["terminal"];
84
+ if (isPlainObject(terminal)) {
85
+ const t = {};
86
+ if (typeof terminal["enabled"] === "boolean")
87
+ t.enabled = terminal["enabled"];
88
+ if (typeof terminal["app"] === "string" && terminal["app"].trim())
89
+ t.app = terminal["app"].trim();
90
+ if (typeof terminal["command"] === "string" && terminal["command"].trim()) {
91
+ t.command = terminal["command"].trim();
92
+ }
93
+ if (Object.keys(t).length > 0)
94
+ out.terminal = t;
95
+ }
96
+ else if (terminal !== undefined) {
97
+ warn(`config: remote.terminal in ${file} is not an object — ignoring`);
98
+ }
99
+ return { remote: out };
100
+ }
101
+ //# sourceMappingURL=user-config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"user-config.js","sourceRoot":"","sources":["../../src/config/user-config.ts"],"names":[],"mappings":"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"}
@@ -20,6 +20,20 @@ import type { PendingTurn, ZcodeAcpServer } from "../server.js";
20
20
  * (the TUI's /new) create fresh sessions. Whitespace-only counts as unset.
21
21
  */
22
22
  export declare function consumeBootResumeTarget(): string | null;
23
+ /**
24
+ * Banner-handshake trigger for a boot-resumed TUI (ADR-0017 follow-up).
25
+ * Martty's welcome banner paints INSTEAD of the transcript and only dives
26
+ * when text is submitted — so a boot-resumed TUI showed its replayed history
27
+ * only after the user's first message. The hub therefore also incubates the
28
+ * resume TUI with `DSH_TUI_AUTOPROMPT=<this string>`: martty auto-submits it
29
+ * at boot (banner dives immediately, the text queues until the boot bind),
30
+ * and the prompt path answers it with a one-line ack instead of a model
31
+ * turn — scoped to the booting connection (see bootResumeTriggerConnection).
32
+ * Plain text on purpose: a leading `/` would route through martty's
33
+ * slash dispatch (whose gates run before agent capabilities are known) and
34
+ * `!` would run a local shell command.
35
+ */
36
+ export declare const BOOT_RESUME_TRIGGER = "resume session";
23
37
  /**
24
38
  * `session/new` → local placeholder id. The real zcode `session/create` is
25
39
  * deferred to first use (`ensureRealSession`) so an editor startup that never
@@ -34,7 +48,7 @@ export declare function consumeBootResumeTarget(): string | null;
34
48
  * back to a fresh session so the window still lands on a usable prompt
35
49
  * (the retired in-house REPL had the same fallback).
36
50
  */
37
- export declare function newSession(server: ZcodeAcpServer, params: acp.NewSessionRequest): Promise<acp.NewSessionResponse>;
51
+ export declare function newSession(server: ZcodeAcpServer, params: acp.NewSessionRequest, client?: acp.AgentContext): Promise<acp.NewSessionResponse>;
38
52
  /**
39
53
  * Materialize a lazy `session/new` placeholder into a real backend session on
40
54
  * first use (prompt / set_config_option / extension methods). Idempotent:
@@ -68,7 +82,7 @@ export declare function loadSession(server: ZcodeAcpServer, params: acp.LoadSess
68
82
  * the continuation for real, and only the session/load replay later surfaced
69
83
  * the orphaned continuation bubble.
70
84
  */
71
- export declare function prompt(server: ZcodeAcpServer, params: acp.PromptRequest, cx: acp.AgentContext, requestId: number | string): Promise<acp.PromptResponse>;
85
+ export declare function prompt(server: ZcodeAcpServer, params: acp.PromptRequest, cx: acp.AgentContext, requestId: number | string, client?: acp.AgentContext): Promise<acp.PromptResponse>;
72
86
  /**
73
87
  * `session/set_config_option` → dispatch model/mode/thought and emit the
74
88
  * resulting config_option_update (+ current_mode_update for mode).
@@ -1 +1 @@
1
- {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/handlers/session.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAMH,OAAO,KAAK,KAAK,GAAG,MAAM,0BAA0B,CAAC;AAGrD,OAAO,EAAE,mBAAmB,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAuB1E,OAAO,EAML,gBAAgB,EACjB,MAAM,yBAAyB,CAAC;AAGjC,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAkHhE;;;;GAIG;AACH,wBAAgB,uBAAuB,IAAI,MAAM,GAAG,IAAI,CAIvD;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,UAAU,CAC9B,MAAM,EAAE,cAAc,EACtB,MAAM,EAAE,GAAG,CAAC,iBAAiB,GAC5B,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,CA0DjC;AAED;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CA2H/F;AAED,6CAA6C;AAC7C,wBAAsB,YAAY,CAChC,MAAM,EAAE,cAAc,EACtB,MAAM,EAAE,GAAG,CAAC,mBAAmB,GAC9B,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAuBnC;AAgHD,6EAA6E;AAC7E,wBAAsB,aAAa,CACjC,MAAM,EAAE,cAAc,EACtB,MAAM,EAAE,GAAG,CAAC,oBAAoB,EAChC,EAAE,EAAE,GAAG,CAAC,YAAY,GACnB,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAqFpC;AAED;;;GAGG;AACH,wBAAsB,WAAW,CAC/B,MAAM,EAAE,cAAc,EACtB,MAAM,EAAE,GAAG,CAAC,kBAAkB,EAC9B,EAAE,EAAE,GAAG,CAAC,YAAY,EACpB,IAAI,GAAE;IAAE,aAAa,CAAC,EAAE,OAAO,CAAA;CAAO,GACrC,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,CA8GlC;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,MAAM,CAC1B,MAAM,EAAE,cAAc,EACtB,MAAM,EAAE,GAAG,CAAC,aAAa,EACzB,EAAE,EAAE,GAAG,CAAC,YAAY,EACpB,SAAS,EAAE,MAAM,GAAG,MAAM,GACzB,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAwD7B;AAgZD;;;GAGG;AACH,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,cAAc,EACtB,MAAM,EAAE,GAAG,CAAC,6BAA6B,EACzC,EAAE,EAAE,GAAG,CAAC,YAAY,GACnB,OAAO,CAAC,GAAG,CAAC,8BAA8B,CAAC,CAa7C;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,MAAM,CAC1B,MAAM,EAAE,cAAc,EACtB,MAAM,EAAE,GAAG,CAAC,kBAAkB,GAC7B,OAAO,CAAC,IAAI,CAAC,CAsBf;AAyJD,qEAAqE;AACrE,UAAU,SAAS;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,WAAW,CAAC;IAClB,QAAQ,EAAE,mBAAmB,CAAC;IAC9B,OAAO,EAAE,WAAW,CAAC;IACrB,MAAM,EAAE,gBAAgB,CAAC;IACzB,EAAE,EAAE,GAAG,CAAC,YAAY,CAAC;IACrB,mEAAmE;IACnE,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAsB,uBAAuB,CAC3C,MAAM,EAAE,cAAc,EACtB,IAAI,EAAE,SAAS,GACd,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC,CA0ClC;AAyCD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,cAAc,EACtB,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,MAAM,GAAG,MAAM,GAC7B,OAAO,CAsBT;AAID;2EAC2E;AAC3E,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,GAAG,CAAC,YAAY,EAAE,GAAG,SAAS,GAAG,MAAM,CA2ChF;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,OAAO,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAYD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,GAAG,CAAC,YAAY,EAAE,GAAG,SAAS,GAAG,eAAe,EAAE,CAwC5F;AA4JD;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAG7D;AAcD;;;;;;;;GAQG;AACH,wBAAsB,YAAY,CAChC,MAAM,EAAE,cAAc,EACtB,QAAQ,EAAE,mBAAmB,EAC7B,OAAO,EAAE,WAAW,EACpB,MAAM,EAAE,gBAAgB,EACxB,EAAE,EAAE,GAAG,CAAC,YAAY,EACpB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,WAAW,EACjB,SAAS,EAAE,OAAO,GACjB,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CA4gB7B;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,iCAAiC,CAC/C,EAAE,EAAE;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,EACpB,WAAW,EAAE,OAAO,EACpB,SAAS,EAAE,OAAO,GACjB,OAAO,CAET;AAkDD;;;;;GAKG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,OAAO,EAAE,GAAG,SAAS,EAC5B,UAAU,EAAE,KAAK,CAAC;IAAE,OAAO,CAAC,EAAE,OAAO,EAAE,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,EAAE,CAAA;CAAE,CAAC,GAAG,SAAS,GACxE,OAAO,EAAE,CASX;AAkBD;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,cAAc,EACtB,EAAE,EAAE,GAAG,CAAC,YAAY,EACpB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC,CAmBf;AAiED,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,cAAc,EACtB,EAAE,EAAE,GAAG,CAAC,YAAY,EACpB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,gBAAgB,EACxB,UAAU,EAAE,MAAM,EAClB,OAAO,UAAO,GACb,OAAO,CAAC,IAAI,CAAC,CAiCf"}
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/handlers/session.ts"],"names":[],"mappings":"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"}
@@ -24,7 +24,7 @@ import { buildResumeRuntimeModel } from "../config/runtime-model.js";
24
24
  import { messages } from "../i18n.js";
25
25
  import { lookupLazySession, recordMaterializedSession, rememberLazySession, } from "../lazy-sessions.js";
26
26
  import { buildDiffContent, EventTranslator, extractLocations, formatTurnError, isTransientTurnError, ProjectionDiffer, } from "../translators/index.js";
27
- import { log, warn } from "../utils.js";
27
+ import { clientConnectionRoot, log, warn } from "../utils.js";
28
28
  import { dispatchEvent } from "./dispatch.js";
29
29
  import { sendSessionUpdate, sendTextChunk, withReplayBatch } from "./io.js";
30
30
  import { fetchMessages, fullSlice, readTailLimit, replayMessages, sliceTail } from "./replay.js";
@@ -131,6 +131,20 @@ export function consumeBootResumeTarget() {
131
131
  delete process.env[BOOT_RESUME_ENV];
132
132
  return target || null;
133
133
  }
134
+ /**
135
+ * Banner-handshake trigger for a boot-resumed TUI (ADR-0017 follow-up).
136
+ * Martty's welcome banner paints INSTEAD of the transcript and only dives
137
+ * when text is submitted — so a boot-resumed TUI showed its replayed history
138
+ * only after the user's first message. The hub therefore also incubates the
139
+ * resume TUI with `DSH_TUI_AUTOPROMPT=<this string>`: martty auto-submits it
140
+ * at boot (banner dives immediately, the text queues until the boot bind),
141
+ * and the prompt path answers it with a one-line ack instead of a model
142
+ * turn — scoped to the booting connection (see bootResumeTriggerConnection).
143
+ * Plain text on purpose: a leading `/` would route through martty's
144
+ * slash dispatch (whose gates run before agent capabilities are known) and
145
+ * `!` would run a local shell command.
146
+ */
147
+ export const BOOT_RESUME_TRIGGER = "resume session";
134
148
  /**
135
149
  * `session/new` → local placeholder id. The real zcode `session/create` is
136
150
  * deferred to first use (`ensureRealSession`) so an editor startup that never
@@ -145,16 +159,36 @@ export function consumeBootResumeTarget() {
145
159
  * back to a fresh session so the window still lands on a usable prompt
146
160
  * (the retired in-house REPL had the same fallback).
147
161
  */
148
- export async function newSession(server, params) {
162
+ export async function newSession(server, params, client) {
149
163
  const bootResume = consumeBootResumeTarget();
150
164
  if (bootResume) {
151
165
  try {
152
- // replayHistory:false — the TUI does not render agent-replayed history
153
- // (its transcript comes from its own local recording; it says so in its
154
- // status line) and waits for this response, so a full replay would only
155
- // stall the boot.
166
+ // replayHistory:false — a PRE-response replay would arrive for a session
167
+ // id the client has not adopted yet and be dropped; the tail replay is
168
+ // deferred past the response instead (below).
156
169
  const loaded = await loadSession(server, { sessionId: bootResume }, server.clients.broadcast(), { replayHistory: false });
157
170
  log(`session/new: boot-resume → ${bootResume}`);
171
+ // Deferred tail replay, same as the TUI's /resume (see resumeSession):
172
+ // once the session/new response is written the booting martty has
173
+ // adopted the returned session id, so post-response chunks can fold
174
+ // into its transcript. The chunks sit BEHIND martty's welcome banner
175
+ // until text is submitted — the DSH_TUI_AUTOPROMPT handshake armed
176
+ // below drops the banner for the user.
177
+ if (isMarttyClient(server)) {
178
+ // Scope the handshake to THIS connection: a phone app attached to the
179
+ // same bridge may prompt during the boot window and must not disarm it.
180
+ server.bootResumeTriggerConnection = clientConnectionRoot(client);
181
+ const cx = server.clients.broadcast();
182
+ const zcodeSid = server.resolveSid(bootResume);
183
+ if (zcodeSid) {
184
+ setImmediate(() => {
185
+ replayResumeHistory(server, cx, bootResume, zcodeSid).catch((e) => {
186
+ warn(`session/new boot-resume: TUI history replay failed (non-fatal): ` +
187
+ `${e instanceof Error ? e.message : String(e)}`);
188
+ });
189
+ });
190
+ }
191
+ }
158
192
  return {
159
193
  sessionId: bootResume,
160
194
  modes: loaded.modes,
@@ -431,7 +465,9 @@ async function resolveResumeTarget(server, acpSid) {
431
465
  const MARTTY_RESUME_TAIL = 200;
432
466
  /** True when the connecting client is Martty, the bundled TUI frontend. */
433
467
  function isMarttyClient(server) {
434
- return (server.clientName ?? "").toLowerCase().includes("martty");
468
+ // Sticky flag first: clientName is last-write-wins and a remote client's
469
+ // initialize can land between the TUI's initialize and its session/new.
470
+ return server.marttyClientSeen || (server.clientName ?? "").toLowerCase().includes("martty");
435
471
  }
436
472
  /**
437
473
  * Stream a resumed session's history to the TUI as chunk updates — the same
@@ -654,8 +690,8 @@ export async function loadSession(server, params, cx, opts = {}) {
654
690
  * the continuation for real, and only the session/load replay later surfaced
655
691
  * the orphaned continuation bubble.
656
692
  */
657
- export async function prompt(server, params, cx, requestId) {
658
- let result = await runPrompt(server, params, cx, requestId);
693
+ export async function prompt(server, params, cx, requestId, client) {
694
+ let result = await runPrompt(server, params, cx, requestId, false, client);
659
695
  // Sandbox-allow continuation chaining, BOUNDED: each batched restart
660
696
  // cancels the in-flight round and queues a continuation for prompt() to
661
697
  // consume. A LATER batch (the user approving a second popup seconds after
@@ -703,7 +739,7 @@ export async function prompt(server, params, cx, requestId) {
703
739
  return result;
704
740
  }
705
741
  /** One prompt round: subscribe-before-send, run the event-driven turn loop. */
706
- async function runPrompt(server, params, cx, requestId, continuationRound = false) {
742
+ async function runPrompt(server, params, cx, requestId, continuationRound = false, client) {
707
743
  // Project-level sandbox flip (ADR-0011): a .zcode/acp/sandbox.json created
708
744
  // after the backend spawned unsandboxed must arm on THIS prompt, not on the
709
745
  // next bridge restart — kill the old process; the ensureBackend() below
@@ -718,6 +754,25 @@ async function runPrompt(server, params, cx, requestId, continuationRound = fals
718
754
  // may drag in an image with no accompanying text).
719
755
  if (!text && attachments.length === 0)
720
756
  throw new Error("empty prompt");
757
+ // Boot-resume banner handshake (see BOOT_RESUME_TRIGGER): martty queues the
758
+ // auto-submitted trigger until its boot bind, so it arrives as the FIRST
759
+ // prompt OF THAT CONNECTION — ack with one chunk and end the turn without
760
+ // touching the backend. Scoped to the arming connection's identity: a
761
+ // phone app attached to the same bridge may prompt inside the boot window
762
+ // and must neither spend nor disarm the handshake (observed live: its
763
+ // prompt raced ahead and the trigger leaked to the model). The same
764
+ // connection submitting anything else first means the auto-submit was
765
+ // lost — disarm so the trigger text typed manually later stays a normal
766
+ // prompt.
767
+ if (server.bootResumeTriggerConnection !== null &&
768
+ clientConnectionRoot(client) === server.bootResumeTriggerConnection) {
769
+ server.bootResumeTriggerConnection = null;
770
+ if (text === BOOT_RESUME_TRIGGER) {
771
+ await sendTextChunk(cx, params.sessionId, messages().bootResumeAck, randomUUID());
772
+ log("session/prompt: boot-resume banner handshake acknowledged");
773
+ return { stopReason: "end_turn" };
774
+ }
775
+ }
721
776
  // Materialize a lazy session/new placeholder on first use. Placed after the
722
777
  // empty-prompt check so an invalid request doesn't create a backend session.
723
778
  const zcodeSid = await ensureRealSession(server, params.sessionId);