@cjhyy/code-shell-capability-coding 0.9.44 → 0.9.47

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.
Files changed (58) hide show
  1. package/dist/capability-runtime.js +4 -1
  2. package/dist/cc-orchestrator/codex-session-history.d.ts +3 -1
  3. package/dist/cc-orchestrator/codex-session-history.js +11 -4
  4. package/dist/cc-orchestrator/external-agent-changes.js +7 -65
  5. package/dist/cc-orchestrator/external-agent-driver.js +61 -3
  6. package/dist/cc-orchestrator/session-discovery.d.ts +4 -2
  7. package/dist/cc-orchestrator/session-discovery.js +63 -6
  8. package/dist/cc-orchestrator/session-history.js +5 -0
  9. package/dist/external-runtimes/claude-code/event-translator.d.ts +7 -0
  10. package/dist/external-runtimes/claude-code/event-translator.js +58 -9
  11. package/dist/external-runtimes/claude-code/mcp-config.d.ts +8 -0
  12. package/dist/external-runtimes/claude-code/mcp-config.js +16 -3
  13. package/dist/external-runtimes/claude-code/permissions.d.ts +69 -0
  14. package/dist/external-runtimes/claude-code/permissions.js +120 -0
  15. package/dist/external-runtimes/claude-code/runtime.d.ts +43 -4
  16. package/dist/external-runtimes/claude-code/runtime.js +155 -39
  17. package/dist/external-runtimes/claude-code/text-inference.d.ts +22 -0
  18. package/dist/external-runtimes/claude-code/text-inference.js +240 -0
  19. package/dist/external-runtimes/codex/app-server-client.d.ts +15 -0
  20. package/dist/external-runtimes/codex/app-server-client.js +69 -6
  21. package/dist/external-runtimes/codex/event-translator.js +16 -2
  22. package/dist/external-runtimes/codex/runtime.d.ts +14 -0
  23. package/dist/external-runtimes/codex/runtime.js +152 -16
  24. package/dist/external-runtimes/index.d.ts +4 -0
  25. package/dist/external-runtimes/index.js +2 -0
  26. package/dist/external-runtimes/session-factory.d.ts +16 -0
  27. package/dist/external-runtimes/session-factory.js +43 -1
  28. package/dist/external-runtimes/shared/mcp-bridge.d.ts +36 -1
  29. package/dist/external-runtimes/shared/mcp-bridge.js +93 -13
  30. package/dist/git/parse-log.d.ts +6 -2
  31. package/dist/git/parse-log.js +7 -3
  32. package/dist/git/utils.d.ts +4 -0
  33. package/dist/git/utils.js +29 -10
  34. package/dist/git/worktree/crud.d.ts +2 -0
  35. package/dist/git/worktree/crud.js +29 -1
  36. package/dist/git/worktree/diff.js +57 -29
  37. package/dist/index.capability.js +14 -1
  38. package/dist/index.git.d.ts +1 -0
  39. package/dist/index.git.js +1 -0
  40. package/dist/lsp/client.d.ts +3 -0
  41. package/dist/lsp/client.js +96 -25
  42. package/dist/lsp/manager.d.ts +2 -0
  43. package/dist/lsp/manager.js +56 -15
  44. package/dist/prompt/coding.md +1 -0
  45. package/dist/quota/index.d.ts +2 -1
  46. package/dist/quota/index.js +27 -7
  47. package/dist/quota/types.d.ts +5 -2
  48. package/dist/review/review-prompt.d.ts +2 -0
  49. package/dist/review/review-prompt.js +70 -29
  50. package/dist/review/review-target.d.ts +29 -0
  51. package/dist/review/review-target.js +353 -0
  52. package/dist/tools/apply-patch/applier.js +64 -39
  53. package/dist/tools/apply-patch/backup-targets.d.ts +8 -0
  54. package/dist/tools/apply-patch/backup-targets.js +24 -0
  55. package/dist/tools/apply-patch/parser.js +4 -2
  56. package/dist/tools/check-quota.js +3 -1
  57. package/dist/tools/lsp.js +36 -5
  58. package/package.json +2 -2
@@ -0,0 +1,240 @@
1
+ import { spawn } from "node:child_process";
2
+ import { chmod, mkdtemp, rm, writeFile } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { killProcessGroup } from "@cjhyy/code-shell-core/extension";
6
+ const MAX_OUTPUT_BYTES = 2 * 1024 * 1024;
7
+ const MAX_STDERR_BYTES = 64 * 1024;
8
+ const MAX_INPUT_BYTES = 2 * 1024 * 1024;
9
+ function usageFromResult(value) {
10
+ if (!value || typeof value !== "object")
11
+ return undefined;
12
+ const raw = value;
13
+ const input = raw.input_tokens;
14
+ const output = raw.output_tokens;
15
+ const read = raw.cache_read_input_tokens ?? 0;
16
+ const write = raw.cache_creation_input_tokens ?? 0;
17
+ if (![input, output, read, write].every((count) => Number.isSafeInteger(count) && Number(count) >= 0))
18
+ return undefined;
19
+ const promptTokens = Number(input) + Number(read) + Number(write);
20
+ const totalTokens = promptTokens + Number(output);
21
+ if (!Number.isSafeInteger(totalTokens))
22
+ return undefined;
23
+ return {
24
+ promptTokens,
25
+ completionTokens: Number(output),
26
+ totalTokens,
27
+ ...(raw.cache_read_input_tokens !== undefined ? { cacheReadTokens: Number(read) } : {}),
28
+ ...(raw.cache_creation_input_tokens !== undefined
29
+ ? { cacheCreationTokens: Number(write) }
30
+ : {}),
31
+ };
32
+ }
33
+ /** Fresh CLI inference, with no conversation, Host tools or user customizations. */
34
+ export async function runClaudeTextInference(options, dependencies = {}) {
35
+ options.signal?.throwIfAborted();
36
+ if (!options.systemPrompt?.trim() || !options.userMessage?.trim())
37
+ throw new Error("Claude text inference requires a system prompt and user message");
38
+ if (Buffer.byteLength(options.systemPrompt) + Buffer.byteLength(options.userMessage) >
39
+ MAX_INPUT_BYTES)
40
+ throw new Error("Claude text inference input exceeds its byte limit");
41
+ const maxTokens = options.maxTokens ?? 3072;
42
+ const timeoutMs = options.timeoutMs ?? 120_000;
43
+ if (!Number.isSafeInteger(maxTokens) || maxTokens < 1 || maxTokens > 128_000)
44
+ throw new Error("Invalid Claude text inference output budget");
45
+ if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 3_600_000)
46
+ throw new Error("Invalid Claude text inference timeout");
47
+ const privateDir = await mkdtemp(join(tmpdir(), "codeshell-claude-text-"));
48
+ try {
49
+ await chmod(privateDir, 0o700);
50
+ const systemPromptFile = join(privateDir, "system-prompt.txt");
51
+ await writeFile(systemPromptFile, options.systemPrompt, { mode: 0o600 });
52
+ await chmod(systemPromptFile, 0o600);
53
+ options.signal?.throwIfAborted();
54
+ const args = [
55
+ "-p",
56
+ "--output-format",
57
+ "stream-json",
58
+ "--verbose",
59
+ "--include-partial-messages",
60
+ "--safe-mode",
61
+ "--tools",
62
+ "",
63
+ "--disallowedTools",
64
+ "*",
65
+ "--strict-mcp-config",
66
+ "--disable-slash-commands",
67
+ "--no-session-persistence",
68
+ "--permission-mode",
69
+ "dontAsk",
70
+ "--permission-prompts",
71
+ "none",
72
+ "--max-turns",
73
+ "1",
74
+ "--system-prompt-file",
75
+ systemPromptFile,
76
+ ...(options.model ? ["--model", options.model] : []),
77
+ ];
78
+ // Safe mode keeps the CLI's auth/provider selection, unlike --bare. Managed
79
+ // policy (including managed hooks) remains authoritative inside the CLI.
80
+ const env = {
81
+ ...(options.env ?? process.env),
82
+ CLAUDE_CODE_SAFE_MODE: "1",
83
+ CLAUDE_CODE_MAX_OUTPUT_TOKENS: String(maxTokens),
84
+ CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1",
85
+ CLAUDE_CODE_DISABLE_TERMINAL_TITLE: "1",
86
+ CLAUDE_CODE_DISABLE_FILE_MENTIONS: "1",
87
+ };
88
+ for (const name of [
89
+ "CLAUDECODE",
90
+ "CLAUDE_CODE_ENTRYPOINT",
91
+ "CLAUDE_CODE_SIMPLE",
92
+ "CODESHELL_CODEX_MCP_TOKEN",
93
+ "CODE_SHELL_HOST_MCP_SERVERS",
94
+ "CODESHELL_MODEL_TRACE_FD",
95
+ ])
96
+ delete env[name];
97
+ return await new Promise((resolve, reject) => {
98
+ const launch = dependencies.spawn ?? spawn;
99
+ const child = launch(options.command ?? "claude", args, {
100
+ cwd: options.cwd ?? privateDir,
101
+ env,
102
+ shell: false,
103
+ detached: process.platform !== "win32",
104
+ stdio: ["pipe", "pipe", "pipe"],
105
+ });
106
+ let outputBytes = 0;
107
+ let stderrBytes = 0;
108
+ let pending = "";
109
+ let terminal;
110
+ let stopReason;
111
+ let failure;
112
+ let settling = false;
113
+ let stopping;
114
+ const stop = () => {
115
+ stopping ??= child.pid ? killProcessGroup(child.pid, { graceMs: 250 }) : Promise.resolve();
116
+ return stopping;
117
+ };
118
+ const finish = (code, signal) => {
119
+ if (settling)
120
+ return;
121
+ settling = true;
122
+ clearTimeout(timer);
123
+ options.signal?.removeEventListener("abort", onAbort);
124
+ void stop().then(() => {
125
+ child.stdin.destroy();
126
+ child.stdout.destroy();
127
+ child.stderr.destroy();
128
+ if (failure)
129
+ return reject(failure);
130
+ if (code !== 0 || signal)
131
+ return reject(new Error(`Claude text inference exited without success (${signal ?? code})`));
132
+ try {
133
+ if (pending.trim())
134
+ accept(pending);
135
+ if (!terminal)
136
+ throw new Error("Claude text inference produced no terminal result");
137
+ resolve({
138
+ text: terminal.result,
139
+ usage: usageFromResult(terminal.usage),
140
+ stopReason,
141
+ });
142
+ }
143
+ catch (error) {
144
+ reject(error);
145
+ }
146
+ }, reject);
147
+ };
148
+ const fail = (error) => {
149
+ if (settling || failure)
150
+ return;
151
+ failure = error;
152
+ void stop().then(() => finish(), reject);
153
+ };
154
+ const acceptStopReason = (value) => {
155
+ if (typeof value !== "string")
156
+ return;
157
+ if (!["end_turn", "stop", "stop_sequence"].includes(value))
158
+ throw new Error(`Claude text inference stopped without complete text (${value})`);
159
+ stopReason = value;
160
+ };
161
+ const accept = (line) => {
162
+ if (!line.trim())
163
+ return;
164
+ let item;
165
+ try {
166
+ item = JSON.parse(line);
167
+ }
168
+ catch {
169
+ throw new Error("Claude text inference emitted malformed JSON");
170
+ }
171
+ if (!item || typeof item !== "object" || Array.isArray(item))
172
+ throw new Error("Claude text inference emitted a malformed event");
173
+ const content = item.message?.content;
174
+ const block = item.event?.content_block;
175
+ if (["tool_progress", "tool_use_summary"].includes(item.type) ||
176
+ (item.type === "control_request" && item.request?.subtype === "can_use_tool") ||
177
+ ["tool_use", "server_tool_use"].includes(block?.type) ||
178
+ (Array.isArray(content) &&
179
+ content.some((part) => ["tool_use", "server_tool_use", "tool_result"].includes(part?.type))) ||
180
+ (item.type === "system" && item.subtype === "init" && item.tools?.length > 0))
181
+ throw new Error("Claude text inference attempted to use tools");
182
+ if (item.type === "assistant")
183
+ acceptStopReason(item.message?.stop_reason);
184
+ if (item.type === "stream_event" && item.event?.type === "message_delta")
185
+ acceptStopReason(item.event.delta?.stop_reason);
186
+ if (item.type !== "result")
187
+ return;
188
+ if (terminal)
189
+ throw new Error("Claude text inference emitted multiple terminal results");
190
+ if (item.is_error !== false ||
191
+ item.subtype !== "success" ||
192
+ typeof item.result !== "string" ||
193
+ !item.result.trim() ||
194
+ (Array.isArray(item.errors) && item.errors.length > 0))
195
+ throw new Error("Claude text inference returned an incomplete or failed result");
196
+ acceptStopReason(item.stop_reason);
197
+ terminal = item;
198
+ };
199
+ const onAbort = () => fail(Object.assign(new Error("Claude text inference aborted"), { name: "AbortError" }));
200
+ const timer = setTimeout(() => fail(new Error(`Claude text inference timed out after ${timeoutMs}ms`)), timeoutMs);
201
+ child.stdout.setEncoding("utf8");
202
+ child.stdout.on("data", (chunk) => {
203
+ if (settling || failure)
204
+ return;
205
+ outputBytes += Buffer.byteLength(chunk);
206
+ if (outputBytes > MAX_OUTPUT_BYTES)
207
+ return fail(new Error("Claude text inference output exceeds its byte limit"));
208
+ pending += chunk;
209
+ try {
210
+ for (let newline; (newline = pending.indexOf("\n")) >= 0;) {
211
+ const line = pending.slice(0, newline);
212
+ pending = pending.slice(newline + 1);
213
+ accept(line);
214
+ }
215
+ }
216
+ catch (error) {
217
+ fail(error);
218
+ }
219
+ });
220
+ child.stderr.on("data", (chunk) => {
221
+ stderrBytes += chunk.byteLength;
222
+ if (stderrBytes > MAX_STDERR_BYTES)
223
+ fail(new Error("Claude text inference diagnostics exceed their byte limit"));
224
+ });
225
+ child.stdin.on("error", (error) => fail(error));
226
+ child.stdout.on("error", (error) => fail(error));
227
+ child.stderr.on("error", (error) => fail(error));
228
+ child.once("error", (error) => fail(error));
229
+ child.once("close", finish);
230
+ options.signal?.addEventListener("abort", onAbort, { once: true });
231
+ if (options.signal?.aborted)
232
+ onAbort();
233
+ else
234
+ child.stdin.end(options.userMessage);
235
+ });
236
+ }
237
+ finally {
238
+ await rm(privateDir, { recursive: true, force: true });
239
+ }
240
+ }
@@ -21,6 +21,14 @@ export type NotificationHandler = (method: string, params: unknown) => void;
21
21
  * waiting either way.
22
22
  */
23
23
  export type ServerRequestHandler = (method: string, params: unknown) => Promise<unknown> | unknown;
24
+ /**
25
+ * Told once when the transport is gone for good — the process exited or failed
26
+ * to spawn, the stream broke, or {@link CodexAppServerClient.close} ran. Pending
27
+ * requests are rejected by then; this is for state that is NOT a request, such
28
+ * as a turn whose RPC was already answered and now waits on notifications that
29
+ * can never arrive.
30
+ */
31
+ export type CloseHandler = (reason: string) => void;
24
32
  export declare class CodexAppServerClient {
25
33
  private readonly options;
26
34
  private child?;
@@ -29,7 +37,9 @@ export declare class CodexAppServerClient {
29
37
  private readonly pending;
30
38
  private notificationHandler?;
31
39
  private requestHandler?;
40
+ private closeHandler?;
32
41
  private closed;
42
+ private closing?;
33
43
  private closeReason?;
34
44
  /** Buffer lines that arrive before a handler exists, then drain in order. */
35
45
  private readonly preHandlerLines;
@@ -42,6 +52,7 @@ export declare class CodexAppServerClient {
42
52
  */
43
53
  onNotification(handler: NotificationHandler): void;
44
54
  onServerRequest(handler: ServerRequestHandler): void;
55
+ onClose(handler: CloseHandler): void;
45
56
  /** Spawn the app-server. Handlers must already be registered. */
46
57
  start(): void;
47
58
  private acceptLine;
@@ -59,8 +70,12 @@ export declare class CodexAppServerClient {
59
70
  request(method: string, params?: unknown, timeoutMs?: number): Promise<unknown>;
60
71
  notify(method: string, params?: unknown): void;
61
72
  private write;
73
+ private failInput;
62
74
  private failAll;
75
+ /** Why the transport closed, once it has. */
76
+ get closedReason(): string | undefined;
63
77
  get isClosed(): boolean;
64
78
  /** Stop via stdin EOF, then SIGTERM and SIGKILL if the server does not exit. */
65
79
  close(): Promise<void>;
80
+ private stop;
66
81
  }
@@ -36,7 +36,9 @@ export class CodexAppServerClient {
36
36
  pending = new Map();
37
37
  notificationHandler;
38
38
  requestHandler;
39
+ closeHandler;
39
40
  closed = false;
41
+ closing;
40
42
  closeReason;
41
43
  /** Buffer lines that arrive before a handler exists, then drain in order. */
42
44
  preHandlerLines = [];
@@ -56,8 +58,13 @@ export class CodexAppServerClient {
56
58
  onServerRequest(handler) {
57
59
  this.requestHandler = handler;
58
60
  }
61
+ onClose(handler) {
62
+ this.closeHandler = handler;
63
+ }
59
64
  /** Spawn the app-server. Handlers must already be registered. */
60
65
  start() {
66
+ if (this.closed)
67
+ throw new Error(this.closeReason ?? "app-server client is closed");
61
68
  if (this.child)
62
69
  return;
63
70
  const command = this.options.command ?? "codex";
@@ -70,6 +77,12 @@ export class CodexAppServerClient {
70
77
  stdio: ["pipe", "pipe", "pipe"],
71
78
  shell: false,
72
79
  });
80
+ this.child.stdin.on("error", (error) => {
81
+ // Failed spawns have no pid; ChildProcess.error owns their OS diagnostic.
82
+ if (this.child?.pid === undefined)
83
+ return;
84
+ this.failInput(error);
85
+ });
73
86
  this.child.stdout.setEncoding("utf8");
74
87
  this.lines = createInterface({ input: this.child.stdout, crlfDelay: Infinity });
75
88
  this.lines.on("line", (line) => this.acceptLine(line));
@@ -114,6 +127,8 @@ export class CodexAppServerClient {
114
127
  this.handleLine(line);
115
128
  }
116
129
  handleLine(line) {
130
+ if (this.closed)
131
+ return;
117
132
  if (line.length > MAX_LINE_BYTES) {
118
133
  // Oversized is a transport failure: parsing it would stall the event loop.
119
134
  this.failAll("app-server sent an oversized line");
@@ -208,7 +223,17 @@ export class CodexAppServerClient {
208
223
  }, timeoutMs);
209
224
  }
210
225
  this.pending.set(id, entry);
211
- this.write({ id, method, params: params ?? {} });
226
+ try {
227
+ this.write({ id, method, params: params ?? {} });
228
+ }
229
+ catch (error) {
230
+ // Invalid caller data was never written. Release only this request;
231
+ // serialization failures say nothing about the transport's health.
232
+ this.pending.delete(id);
233
+ if (entry.timer)
234
+ clearTimeout(entry.timer);
235
+ reject(error);
236
+ }
212
237
  });
213
238
  }
214
239
  notify(method, params) {
@@ -218,9 +243,27 @@ export class CodexAppServerClient {
218
243
  }
219
244
  write(payload) {
220
245
  const child = this.child;
221
- if (!child || child.stdin.destroyed)
246
+ if (this.closed)
222
247
  return;
223
- child.stdin.write(`${JSON.stringify(payload)}\n`);
248
+ if (child && child.pid === undefined)
249
+ return;
250
+ if (!child || child.stdin.destroyed || !child.stdin.writable) {
251
+ this.failInput();
252
+ return;
253
+ }
254
+ const line = `${JSON.stringify(payload)}\n`;
255
+ try {
256
+ child.stdin.write(line);
257
+ }
258
+ catch (error) {
259
+ this.failInput(error);
260
+ }
261
+ }
262
+ failInput(error) {
263
+ this.failAll(`app-server input pipe closed${error?.code ? ` (${diagnosticText(error.code)})` : ""}`);
264
+ // Reject outstanding RPCs now and terminate even when the process keeps
265
+ // running after closing fd 0. close() owns one bounded cleanup sequence.
266
+ void this.close();
224
267
  }
225
268
  failAll(reason, details = {}) {
226
269
  if (this.closed)
@@ -234,15 +277,35 @@ export class CodexAppServerClient {
234
277
  entry.reject(new Error(reason));
235
278
  }
236
279
  this.pending.clear();
280
+ try {
281
+ this.closeHandler?.(reason);
282
+ }
283
+ catch (error) {
284
+ this.log("appserver.close_handler_failed", {
285
+ error: error instanceof Error ? error.name : "unknown",
286
+ });
287
+ }
288
+ }
289
+ /** Why the transport closed, once it has. */
290
+ get closedReason() {
291
+ return this.closeReason;
237
292
  }
238
293
  get isClosed() {
239
294
  return this.closed;
240
295
  }
241
296
  /** Stop via stdin EOF, then SIGTERM and SIGKILL if the server does not exit. */
242
- async close() {
297
+ close() {
298
+ if (!this.closing) {
299
+ // Publish cleanup ownership before notifying callers, whose onClose may
300
+ // itself call close(). One process must have only one escalation timer.
301
+ this.closing = Promise.resolve().then(() => this.stop());
302
+ this.failAll("app-server client closed");
303
+ this.lines?.close();
304
+ }
305
+ return this.closing;
306
+ }
307
+ async stop() {
243
308
  const child = this.child;
244
- this.failAll("app-server client closed");
245
- this.lines?.close();
246
309
  // Failed spawns have no pid and never emit `exit`. A process already killed
247
310
  // by a signal likewise has no numeric exitCode and will not emit it again.
248
311
  if (!child || !child.pid || child.exitCode !== null || child.signalCode !== null)
@@ -48,6 +48,21 @@ function toolArgsOf(item) {
48
48
  }
49
49
  return args;
50
50
  }
51
+ function toolErrorOf(item, type) {
52
+ const detail = str(asRecord(item.error)?.message) ?? str(item.error);
53
+ if (detail)
54
+ return detail;
55
+ // A completed app-server item means the operation settled, not that its
56
+ // command succeeded. In particular, nonzero exits often have no error object.
57
+ if (type === "commandExecution" && Number.isInteger(item.exitCode) && item.exitCode !== 0) {
58
+ return `Command exited with code ${item.exitCode}.`;
59
+ }
60
+ if (item.status === "failed")
61
+ return `${type} failed.`;
62
+ if (item.status === "declined")
63
+ return `${type} was declined.`;
64
+ return undefined;
65
+ }
51
66
  /**
52
67
  * Map a Codex turn status onto a CodeShell `TerminalReason`.
53
68
  *
@@ -350,8 +365,7 @@ export class CodexEventTranslator {
350
365
  output = String(rawOutput);
351
366
  }
352
367
  }
353
- const error = asRecord(item.error);
354
- const errorMessage = str(error?.message) ?? str(item.error);
368
+ const errorMessage = toolErrorOf(item, type);
355
369
  const events = [];
356
370
  // Correct the arguments first when they only materialized now, so the
357
371
  // recorded call shows what actually ran before its result.
@@ -77,6 +77,7 @@ export declare class CodexRuntime {
77
77
  private readonly log;
78
78
  private translator?;
79
79
  private threadId?;
80
+ private reportedModel?;
80
81
  private started;
81
82
  private resumed;
82
83
  private firstTurn;
@@ -85,6 +86,8 @@ export declare class CodexRuntime {
85
86
  /** Codex thread id, once the thread exists. Protocol routing only — never the
86
87
  * authorization subject (§8.1). */
87
88
  get runtimeSessionId(): string | undefined;
89
+ /** CLI-reported model, falling back to the requested model until reported. */
90
+ get resolvedModel(): string | undefined;
88
91
  /**
89
92
  * Spawn the app-server, handshake, and open a thread.
90
93
  *
@@ -111,7 +114,18 @@ export declare class CodexRuntime {
111
114
  * that no further step starts (§13.3).
112
115
  */
113
116
  interrupt(): Promise<void>;
117
+ private requestInterrupt;
114
118
  close(): Promise<void>;
119
+ /** Learn the active turn's id; deliver a Stop that was waiting for it, once. */
120
+ private bindTurnId;
121
+ /**
122
+ * The app-server is gone (exited, failed, broken stream). A turn Codex already
123
+ * accepted would otherwise wait forever for a `turn/completed` that can never
124
+ * arrive, so report it terminal now as a model error. A turn whose
125
+ * `turn/start` is still unanswered is NOT reported here: that RPC rejects, and
126
+ * the rejected `send()` is its caller's terminal signal.
127
+ */
128
+ private onTransportClosed;
115
129
  private onNotification;
116
130
  private emitEvent;
117
131
  /**