lark-remote 0.1.13 → 0.2.1

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 (132) hide show
  1. package/CHANGELOG.md +36 -3
  2. package/README.en.md +7 -11
  3. package/README.md +7 -11
  4. package/dist/bridge/final-usage.d.ts +24 -0
  5. package/dist/bridge/final-usage.js +55 -0
  6. package/dist/bridge/index.d.ts +0 -7
  7. package/dist/bridge/index.js +22 -64
  8. package/dist/bridge/queue-manager.js +4 -3
  9. package/dist/card/approval-render.js +39 -94
  10. package/dist/card/bash-renderer.js +12 -21
  11. package/dist/card/card-shared.d.ts +13 -1
  12. package/dist/card/card-shared.js +21 -30
  13. package/dist/card/run-renderer.d.ts +0 -22
  14. package/dist/card/run-renderer.js +206 -153
  15. package/dist/card/run-state.d.ts +29 -1
  16. package/dist/card/run-state.js +172 -84
  17. package/dist/card/text-truncate.d.ts +0 -9
  18. package/dist/card/text-truncate.js +0 -9
  19. package/dist/card/tool-render.d.ts +4 -1
  20. package/dist/card/tool-render.js +76 -32
  21. package/dist/cli.js +2 -2
  22. package/dist/common/fs.d.ts +14 -0
  23. package/dist/common/fs.js +57 -3
  24. package/dist/config/codex-config.js +2 -0
  25. package/dist/config/index.d.ts +0 -5
  26. package/dist/config/index.js +0 -11
  27. package/dist/config/kimi-config.js +1 -0
  28. package/dist/config/opencode-config.js +13 -2
  29. package/dist/config/qr.d.ts +36 -0
  30. package/dist/config/qr.js +124 -0
  31. package/dist/config/wizard.js +69 -27
  32. package/dist/connector/index.d.ts +3 -2
  33. package/dist/connector/index.js +5 -4
  34. package/dist/index.js +15 -42
  35. package/dist/instance-lock.js +7 -0
  36. package/dist/persistence/atomic-write.d.ts +2 -0
  37. package/dist/persistence/atomic-write.js +20 -5
  38. package/dist/persistence/load-json-file.d.ts +1 -2
  39. package/dist/persistence/load-json-file.js +2 -3
  40. package/dist/platform/agent-stopper.d.ts +22 -0
  41. package/dist/platform/agent-stopper.js +19 -0
  42. package/dist/platform/command.d.ts +11 -0
  43. package/dist/platform/command.js +107 -0
  44. package/dist/platform/identity.d.ts +33 -0
  45. package/dist/platform/identity.js +187 -0
  46. package/dist/platform/path.d.ts +24 -0
  47. package/dist/platform/path.js +110 -0
  48. package/dist/platform/select.d.ts +9 -0
  49. package/dist/platform/select.js +12 -0
  50. package/dist/platform/shell.d.ts +51 -0
  51. package/dist/platform/shell.js +75 -0
  52. package/dist/platform/spawn.d.ts +41 -0
  53. package/dist/platform/spawn.js +77 -0
  54. package/dist/platform/terminator-posix.d.ts +38 -0
  55. package/dist/platform/terminator-posix.js +103 -0
  56. package/dist/platform/terminator-win32.d.ts +21 -0
  57. package/dist/platform/terminator-win32.js +110 -0
  58. package/dist/platform/terminator.d.ts +44 -0
  59. package/dist/platform/terminator.js +24 -0
  60. package/dist/platform/types.d.ts +23 -0
  61. package/dist/platform/types.js +5 -0
  62. package/dist/restart.js +4 -2
  63. package/dist/router/card-helpers.d.ts +33 -6
  64. package/dist/router/card-helpers.js +67 -11
  65. package/dist/router/config/common/render.d.ts +1 -1
  66. package/dist/router/config/common/render.js +1 -1
  67. package/dist/router/config-diff.d.ts +4 -0
  68. package/dist/router/config-diff.js +70 -0
  69. package/dist/router/index.d.ts +66 -6
  70. package/dist/router/index.js +451 -316
  71. package/dist/router/utils.d.ts +6 -0
  72. package/dist/router/utils.js +12 -0
  73. package/dist/runner/bash/runner.d.ts +9 -2
  74. package/dist/runner/bash/runner.js +45 -20
  75. package/dist/runner/claude/session.d.ts +4 -1
  76. package/dist/runner/claude/session.js +18 -4
  77. package/dist/runner/codex/app-server/protocol-types.d.ts +35 -1
  78. package/dist/runner/codex/app-server/translator.d.ts +18 -0
  79. package/dist/runner/codex/app-server/translator.js +146 -0
  80. package/dist/runner/common/acp/base-acp-runner.d.ts +116 -0
  81. package/dist/runner/common/acp/base-acp-runner.js +248 -0
  82. package/dist/runner/common/acp/base-translator.d.ts +141 -0
  83. package/dist/runner/common/acp/base-translator.js +271 -0
  84. package/dist/runner/common/acp/protocol-helpers.d.ts +39 -0
  85. package/dist/runner/common/acp/protocol-helpers.js +34 -0
  86. package/dist/runner/common/jsonrpc/transport.d.ts +2 -0
  87. package/dist/runner/common/jsonrpc/transport.js +25 -5
  88. package/dist/runner/common/process-stopper.d.ts +4 -26
  89. package/dist/runner/common/process-stopper.js +4 -76
  90. package/dist/runner/common/runner-utils.d.ts +0 -5
  91. package/dist/runner/common/runner-utils.js +0 -7
  92. package/dist/runner/common/spawning-runner.d.ts +42 -132
  93. package/dist/runner/common/spawning-runner.js +52 -271
  94. package/dist/runner/common/usage.d.ts +2 -0
  95. package/dist/runner/common/usage.js +2 -0
  96. package/dist/runner/index.d.ts +1 -1
  97. package/dist/runner/kimi/acp/runner.d.ts +45 -42
  98. package/dist/runner/kimi/acp/runner.js +140 -293
  99. package/dist/runner/kimi/acp/translator.d.ts +18 -100
  100. package/dist/runner/kimi/acp/translator.js +56 -242
  101. package/dist/runner/opencode/acp/runner.d.ts +22 -46
  102. package/dist/runner/opencode/acp/runner.js +73 -256
  103. package/dist/runner/opencode/acp/translator.d.ts +28 -103
  104. package/dist/runner/opencode/acp/translator.js +51 -236
  105. package/dist/runner/pi/rpc/translator.d.ts +3 -0
  106. package/dist/runner/pi/rpc/translator.js +47 -9
  107. package/dist/runner/probe.d.ts +8 -11
  108. package/dist/runner/probe.js +17 -21
  109. package/dist/runner/registry.d.ts +4 -4
  110. package/dist/runner/registry.js +4 -4
  111. package/dist/runner/types.d.ts +38 -1
  112. package/dist/session/claude/sessions.js +35 -58
  113. package/dist/session/codex/rollout-reader.d.ts +10 -0
  114. package/dist/session/codex/rollout-reader.js +123 -41
  115. package/dist/session/common/constants.d.ts +7 -0
  116. package/dist/session/common/constants.js +9 -0
  117. package/dist/session/common/two-pass.d.ts +22 -0
  118. package/dist/session/common/two-pass.js +70 -0
  119. package/dist/session/dsh/sessions.js +7 -6
  120. package/dist/session/kimi/sessions.d.ts +6 -0
  121. package/dist/session/kimi/sessions.js +50 -73
  122. package/dist/session/opencode/sessions.js +26 -8
  123. package/dist/session/pi/sessions.js +54 -73
  124. package/dist/update/index.d.ts +1 -2
  125. package/dist/update/index.js +1 -2
  126. package/dist/update/install.d.ts +2 -1
  127. package/dist/update/install.js +58 -9
  128. package/dist/update/version-check.d.ts +5 -0
  129. package/dist/update/version-check.js +9 -0
  130. package/package.json +9 -6
  131. package/dist/update/startup-hint.d.ts +0 -5
  132. package/dist/update/startup-hint.js +0 -11
@@ -45,6 +45,12 @@ export declare function formatUsageStats(usage?: {
45
45
  cumulativeCacheReadTokens?: number;
46
46
  /** Session-cumulative cache creation tokens (all runs). */
47
47
  cumulativeCacheCreationTokens?: number;
48
+ /** 实际生效模型(system.init 携带)。非空字符串才显示。 */
49
+ model?: string;
50
+ /** 本次 run 花费(USD)。存在才显示。 */
51
+ costUsd?: number;
52
+ /** 推理 token(pi 提供)。> 0 才显示。 */
53
+ reasoningTokens?: number;
48
54
  }, options?: {
49
55
  showResult?: boolean;
50
56
  result?: string;
@@ -46,6 +46,10 @@ export function formatUsageStats(usage, options) {
46
46
  const lines = [];
47
47
  // 1. Success 状态(通用格式,无具体结果值)
48
48
  lines.push('✅ 已完成');
49
+ // 1.4. 实际生效模型(非空字符串才显示)
50
+ if (usage?.model) {
51
+ lines.push(`Model - ${usage.model}`);
52
+ }
49
53
  // 1.5. 可选的结果行(showResult 为 true 时显示)
50
54
  if (options?.showResult && options?.result) {
51
55
  lines.push(`结果 - ${options.result}`);
@@ -140,6 +144,14 @@ export function formatUsageStats(usage, options) {
140
144
  lines.push(`Total token - ${formatTokenK(totalTokens)}${withCumGuard(totalTokens)}`);
141
145
  }
142
146
  }
147
+ // 8. 本次 run 花费(USD)
148
+ if (usage?.costUsd !== undefined) {
149
+ lines.push(`Cost - $${usage.costUsd.toFixed(4)}`);
150
+ }
151
+ // 9. 推理 token(> 0 才显示,格式对齐既有 token 行)
152
+ if (usage?.reasoningTokens !== undefined && usage.reasoningTokens > 0) {
153
+ lines.push(`Reasoning token - ${formatTokenK(usage.reasoningTokens)}`);
154
+ }
143
155
  return lines.join('\n');
144
156
  }
145
157
  //# sourceMappingURL=utils.js.map
@@ -1,3 +1,5 @@
1
+ import { type ShellBackend } from '../../platform/shell.js';
2
+ import { type Terminator } from '../../platform/terminator.js';
1
3
  interface BashOutputEvent {
2
4
  type: 'stdout' | 'stderr' | 'exit';
3
5
  content: string;
@@ -32,11 +34,16 @@ export declare class BashProcessRunner implements BashRunner {
32
34
  */
33
35
  private readonly queueHighWater;
34
36
  private readonly queueLowWater;
35
- private stopper;
37
+ private readonly terminator;
38
+ private readonly shell;
36
39
  constructor(opts?: {
37
40
  stopGraceMs?: number;
38
41
  queueHighWater?: number;
39
42
  queueLowWater?: number;
43
+ /** shell 后端注入(测试用);默认按平台分发 */
44
+ shell?: ShellBackend;
45
+ /** 终止器注入(测试用);默认按平台分发 */
46
+ terminator?: Terminator;
40
47
  });
41
48
  get isRunning(): boolean;
42
49
  run(command: string, opts: {
@@ -45,7 +52,7 @@ export declare class BashProcessRunner implements BashRunner {
45
52
  /**
46
53
  * P1-22: called by the process-level exit dispatcher on SIGINT/SIGTERM/exit.
47
54
  * Kills the whole process group (leader + background children) via
48
- * ProcessStopper, matching agent-runner cleanupOnExit semantics.
55
+ * Terminator, matching agent-runner cleanupOnExit semantics.
49
56
  */
50
57
  cleanupOnExit(): void;
51
58
  stop(opts?: {
@@ -1,6 +1,7 @@
1
- import { spawn } from 'node:child_process';
2
1
  import { getLogger } from '../../logger/index.js';
3
- import { ProcessStopper } from '../common/process-stopper.js';
2
+ import { createShellBackend, ShellUnavailableError, } from '../../platform/shell.js';
3
+ import { createTerminator } from '../../platform/terminator.js';
4
+ import { useDetachedProcessGroup } from '../../platform/spawn.js';
4
5
  import { registerExitCleanup, unregisterExitCleanup } from '../common/spawning-runner.js';
5
6
  /**
6
7
  * Lightweight bash executor for `!` commands.
@@ -22,7 +23,8 @@ export class BashProcessRunner {
22
23
  */
23
24
  queueHighWater;
24
25
  queueLowWater;
25
- stopper;
26
+ terminator;
27
+ shell;
26
28
  constructor(opts) {
27
29
  const stopGraceMs = opts?.stopGraceMs ?? 1000;
28
30
  this.queueHighWater = opts?.queueHighWater ?? 32;
@@ -30,7 +32,10 @@ export class BashProcessRunner {
30
32
  if (this.queueLowWater > this.queueHighWater) {
31
33
  this.queueLowWater = this.queueHighWater;
32
34
  }
33
- this.stopper = new ProcessStopper({ graceMs: stopGraceMs });
35
+ // `!` bash 无协议停止通道(§3.3):posix 走组杀(与原 ProcessStopper 同实现),
36
+ // win32 优雅段显式跳过后树杀。
37
+ this.terminator = opts?.terminator ?? createTerminator({ graceMs: stopGraceMs, agent: 'bash' });
38
+ this.shell = opts?.shell ?? createShellBackend();
34
39
  }
35
40
  get isRunning() {
36
41
  return (this.currentProcess !== null &&
@@ -43,13 +48,33 @@ export class BashProcessRunner {
43
48
  throw new Error('bash process already running');
44
49
  }
45
50
  getLogger().debug(`[bash-runner] spawning command="${command}" cwd=${opts.cwd}`);
46
- const proc = spawn('bash', ['-c', command], {
47
- cwd: opts.cwd,
48
- stdio: ['ignore', 'pipe', 'pipe'],
49
- // Spawn in new process group so we can kill the entire group
50
- detached: true,
51
- });
51
+ let proc;
52
+ try {
53
+ proc = this.shell.spawn(command, {
54
+ cwd: opts.cwd,
55
+ options: {
56
+ stdio: ['ignore', 'pipe', 'pipe'],
57
+ // posix 建新进程组(负 PID 组杀);win32 不 detached(`.cmd` 垫片丢 stdio)
58
+ detached: useDetachedProcessGroup(),
59
+ windowsHide: true,
60
+ },
61
+ });
62
+ }
63
+ catch (err) {
64
+ // win32 Git Bash 缺失(§7.2):同步抛出,转成明确错误输出而非悬死。
65
+ if (err instanceof ShellUnavailableError) {
66
+ getLogger().warn(`[bash-runner] shell unavailable: ${err.message}`);
67
+ yield { type: 'stderr', content: `${err.message}\n` };
68
+ yield { type: 'exit', content: '', exitCode: 1 };
69
+ return;
70
+ }
71
+ throw err;
72
+ }
52
73
  this.currentProcess = proc;
74
+ // stdio 固定为 ['ignore','pipe','pipe'],两条流必为 pipe(backend 返回的
75
+ // 宽化 ChildProcess 类型丢掉了这一点,这里收窄一次)
76
+ const stdout = proc.stdout;
77
+ const stderr = proc.stderr;
53
78
  getLogger().info(`[bash-runner] spawn pid=${proc.pid} command="${command.slice(0, 50)}..." cwd=${opts.cwd}`);
54
79
  // P1-22: register with the process-level exit dispatcher while the process
55
80
  // is alive — bridge exit/restart (SIGINT/SIGTERM/exit) then kills the whole
@@ -66,25 +91,25 @@ export class BashProcessRunner {
66
91
  const maybePauseStdout = () => {
67
92
  if (!stdoutPaused && stdoutQueue.length > this.queueHighWater) {
68
93
  stdoutPaused = true;
69
- proc.stdout.pause();
94
+ stdout.pause();
70
95
  }
71
96
  };
72
97
  const maybeResumeStdout = () => {
73
98
  if (stdoutPaused && stdoutQueue.length <= this.queueLowWater) {
74
99
  stdoutPaused = false;
75
- proc.stdout.resume();
100
+ stdout.resume();
76
101
  }
77
102
  };
78
103
  const maybePauseStderr = () => {
79
104
  if (!stderrPaused && stderrQueue.length > this.queueHighWater) {
80
105
  stderrPaused = true;
81
- proc.stderr.pause();
106
+ stderr.pause();
82
107
  }
83
108
  };
84
109
  const maybeResumeStderr = () => {
85
110
  if (stderrPaused && stderrQueue.length <= this.queueLowWater) {
86
111
  stderrPaused = false;
87
- proc.stderr.resume();
112
+ stderr.resume();
88
113
  }
89
114
  };
90
115
  // Resolved by a `data` event to signal "more output may be available,
@@ -98,14 +123,14 @@ export class BashProcessRunner {
98
123
  resolve();
99
124
  }
100
125
  };
101
- proc.stdout?.on('data', (chunk) => {
126
+ stdout.on('data', (chunk) => {
102
127
  const text = chunk.toString('utf-8');
103
128
  getLogger().debug(`[bash-runner] stdout: ${text.slice(0, 100)}`);
104
129
  stdoutQueue.push(text);
105
130
  maybePauseStdout();
106
131
  notifyData();
107
132
  });
108
- proc.stderr?.on('data', (chunk) => {
133
+ stderr.on('data', (chunk) => {
109
134
  const text = chunk.toString('utf-8');
110
135
  getLogger().debug(`[bash-runner] stderr: ${text.slice(0, 100)}`);
111
136
  stderrQueue.push(text);
@@ -186,14 +211,14 @@ export class BashProcessRunner {
186
211
  /**
187
212
  * P1-22: called by the process-level exit dispatcher on SIGINT/SIGTERM/exit.
188
213
  * Kills the whole process group (leader + background children) via
189
- * ProcessStopper, matching agent-runner cleanupOnExit semantics.
214
+ * Terminator, matching agent-runner cleanupOnExit semantics.
190
215
  */
191
216
  cleanupOnExit() {
192
217
  const proc = this.currentProcess;
193
218
  if (!proc || proc.exitCode !== null || proc.signalCode !== null)
194
219
  return;
195
220
  try {
196
- void this.stopper.stop(proc, { immediate: true });
221
+ this.terminator.cleanupOnExit(proc);
197
222
  }
198
223
  catch {
199
224
  // fire-and-forget: nothing else to do at process exit
@@ -205,8 +230,8 @@ export class BashProcessRunner {
205
230
  this.currentProcess = null;
206
231
  return;
207
232
  }
208
- // Delegate to ProcessStopper for unified stop semantics
209
- await this.stopper.stop(proc, { immediate: opts?.immediate });
233
+ // Delegate to Terminator for unified stop semantics
234
+ await this.terminator.stop(proc, { immediate: opts?.immediate === true });
210
235
  this.currentProcess = null;
211
236
  }
212
237
  }
@@ -151,7 +151,10 @@ export declare class ClaudeSession extends SpawningRunner {
151
151
  * 的 post-result drain 在长驻模式下无对应物)。
152
152
  */
153
153
  private consumeTurn;
154
- /** 构造进程退出且无终态 result 时的统一错误结果(对齐 SpawningRunner 语义)。 */
154
+ /**
155
+ * 构造进程退出且无终态 result 时的统一错误结果(对齐原 SpawningRunner
156
+ * buildResultEvent 语义,含 §4.4 win32 command-not-found 双条件定性)。
157
+ */
155
158
  private buildStreamEndedError;
156
159
  private wakeWaiters;
157
160
  /**
@@ -31,7 +31,6 @@ import { readFile } from 'node:fs/promises';
31
31
  import { silentlyUnlink } from '../../common/fs.js';
32
32
  import { getLogger } from '../../logger/index.js';
33
33
  import { SpawningRunner } from '../common/spawning-runner.js';
34
- import { pipeAllStdio } from '../common/runner-utils.js';
35
34
  import { authErrorEvent, syntheticInitEvent } from '../common/runner-utils.js';
36
35
  import { makeQuestionApprovalEvent } from '../question-common.js';
37
36
  /** 拒绝权限时的默认提示(进入 claude 上下文,中文用户可读)。 */
@@ -246,7 +245,8 @@ export class ClaudeSession extends SpawningRunner {
246
245
  // SpawningRunner hooks
247
246
  // =========================================================================
248
247
  getStdio() {
249
- return pipeAllStdio();
248
+ // stdin 也要 pipe:长驻会话经 stdin 写用户消息/审批响应。
249
+ return ['pipe', 'pipe', 'pipe'];
250
250
  }
251
251
  buildArgv(opts) {
252
252
  const args = [
@@ -312,6 +312,8 @@ export class ClaudeSession extends SpawningRunner {
312
312
  this.cwd = opts.cwd;
313
313
  this.autoApprove = false;
314
314
  this.pendingToolInputs.clear();
315
+ // §4.4 win32 嗅探嫌疑标记每次 spawn 重置(spawnChild 置位,收尾双条件消费)。
316
+ this.commandNotFoundSeen = false;
315
317
  // 每次 run 独立:上次 run 会话内跟踪到的计划文件不得泄漏到本次
316
318
  // (否则本次 ExitPlanMode 无 plan/planFilePath 时 resolvePlanContent
317
319
  // 会误读上一次的计划文件当当前计划)。
@@ -421,7 +423,10 @@ export class ClaudeSession extends SpawningRunner {
421
423
  }
422
424
  }
423
425
  }
424
- /** 构造进程退出且无终态 result 时的统一错误结果(对齐 SpawningRunner 语义)。 */
426
+ /**
427
+ * 构造进程退出且无终态 result 时的统一错误结果(对齐原 SpawningRunner
428
+ * buildResultEvent 语义,含 §4.4 win32 command-not-found 双条件定性)。
429
+ */
425
430
  async buildStreamEndedError() {
426
431
  // 进程已死但 proc 'close' 事件可能略晚于 stdout 关闭:等退出登记拿可靠
427
432
  // code/signal。stdout 关闭 = 进程退出(claude 独占 stdout),'close' 必然
@@ -436,7 +441,16 @@ export class ClaudeSession extends SpawningRunner {
436
441
  errorMessage = `${this.binary} killed by signal ${exited.signal}${stderrTail ? `: ${stderrTail}` : ''}`;
437
442
  }
438
443
  else if (exited.code !== null && exited.code !== 0) {
439
- errorMessage = `${this.binary} exited code=${exited.code}${stderrTail ? `: ${stderrTail}` : ''}`;
444
+ // §4.4 双条件定性:本分支已保证「用户未停 + signal 为 null + 非零退出」,
445
+ // 这里只补嗅探嫌疑标记这第二个条件。
446
+ const commandNotFound = this.commandNotFoundSeen;
447
+ if (commandNotFound) {
448
+ getLogger().error(`[${this.logTag}] command not found confirmed (win32 shim): exit=${exited.code} stderr=${this.spawnStderr.slice(-500)}`);
449
+ }
450
+ const cause = commandNotFound
451
+ ? `${this.binary} 命令未找到(win32 垫片启动失败:未安装或不在 PATH)`
452
+ : `${this.binary} exited code=${exited.code}`;
453
+ errorMessage = `${cause}${stderrTail ? `: ${stderrTail}` : ''}`;
440
454
  }
441
455
  else {
442
456
  errorMessage = `${this.binary} 输出流已结束,但未收到 result 事件${stderrTail ? `: ${stderrTail}` : ''}`;
@@ -222,7 +222,33 @@ export interface DiffItem {
222
222
  diff: string;
223
223
  timestamp: number;
224
224
  }
225
- export type ThreadItem = AgentMessageItem | ReasoningItem | CommandExecutionItem | PlanItem | FileChangeItem | ObservationItem | DiffItem | {
225
+ /** Web search item(来源:codex-rs ThreadItem::WebSearch / v2 schema)。 */
226
+ export interface WebSearchItem {
227
+ type: 'webSearch';
228
+ id: string;
229
+ query: string;
230
+ action?: unknown;
231
+ }
232
+ /** MCP tool call item(来源:codex-rs ThreadItem::McpToolCall / v2 schema)。 */
233
+ export interface McpToolCallItem {
234
+ type: 'mcpToolCall';
235
+ id: string;
236
+ server: string;
237
+ tool: string;
238
+ arguments?: Record<string, unknown>;
239
+ result?: unknown;
240
+ error?: unknown;
241
+ status?: string;
242
+ }
243
+ /** Dynamic (client-registered) tool call item(来源:ThreadItem::DynamicToolCall)。 */
244
+ export interface DynamicToolCallItem {
245
+ type: 'dynamicToolCall';
246
+ id: string;
247
+ tool: string;
248
+ arguments?: Record<string, unknown>;
249
+ status?: string;
250
+ }
251
+ export type ThreadItem = AgentMessageItem | ReasoningItem | CommandExecutionItem | PlanItem | FileChangeItem | WebSearchItem | McpToolCallItem | DynamicToolCallItem | ObservationItem | DiffItem | {
226
252
  type: 'userMessage';
227
253
  id: string;
228
254
  content: unknown[];
@@ -430,6 +456,14 @@ export interface ModelReroutedNotification {
430
456
  timestamp?: string;
431
457
  };
432
458
  }
459
+ export interface TurnPlanUpdatedNotification {
460
+ method: 'turn/plan/updated';
461
+ params: {
462
+ threadId: string;
463
+ turnId?: string;
464
+ plan: PlanStep[];
465
+ };
466
+ }
433
467
  export declare const ServerRequestMethod: {
434
468
  readonly COMMAND_EXECUTION_APPROVAL: "item/commandExecution/requestApproval";
435
469
  readonly FILE_CHANGE_APPROVAL: "item/fileChange/requestApproval";
@@ -25,6 +25,11 @@ export declare class CodexAppServerTranslator {
25
25
  private reasoningByItem;
26
26
  /** Full command output per item id (item-scoped snapshot). */
27
27
  private toolOutputByItem;
28
+ /**
29
+ * Tool identity per item id (信息保真 C2): recorded at item/started, spread
30
+ * into every toolOutput turn_diff, released at item/completed.
31
+ */
32
+ private toolItemMeta;
28
33
  /** Full plan text per item id (item-scoped snapshot). */
29
34
  private planByItem;
30
35
  /** commandExecution items that emitted item/started (tool block position anchor). */
@@ -79,6 +84,19 @@ export declare class CodexAppServerTranslator {
79
84
  private handleCommandOutputDelta;
80
85
  private handlePlanUpdated;
81
86
  private handleTokenUsageUpdated;
87
+ /**
88
+ * warning 通知 → notice 事件(信息保真 C2:运行期告警不再静默丢弃)。
89
+ */
90
+ private handleWarning;
91
+ /**
92
+ * model/rerouted 通知 → notice 事件(模型重路由必须用户可见)。
93
+ */
94
+ private handleModelRerouted;
95
+ /**
96
+ * turn/plan/updated 通知 → plan 事件(PlanStep[] 渲染为 markdown 清单)。
97
+ * status 缺省或未知一律按 pending 处理(防御 wire 枚举漂移)。
98
+ */
99
+ private handleTurnPlanUpdated;
82
100
  private handleError;
83
101
  private handleThreadCompacted;
84
102
  private handleServerRequestResolved;
@@ -26,6 +26,11 @@ export class CodexAppServerTranslator {
26
26
  reasoningByItem = new Map();
27
27
  /** Full command output per item id (item-scoped snapshot). */
28
28
  toolOutputByItem = new Map();
29
+ /**
30
+ * Tool identity per item id (信息保真 C2): recorded at item/started, spread
31
+ * into every toolOutput turn_diff, released at item/completed.
32
+ */
33
+ toolItemMeta = new Map();
29
34
  /** Full plan text per item id (item-scoped snapshot). */
30
35
  planByItem = new Map();
31
36
  /** commandExecution items that emitted item/started (tool block position anchor). */
@@ -63,6 +68,12 @@ export class CodexAppServerTranslator {
63
68
  return this.handleTokenUsageUpdated(params);
64
69
  case NotificationMethod.TURN_COMPLETED:
65
70
  return this.handleTurnCompleted(params);
71
+ case NotificationMethod.WARNING:
72
+ return this.handleWarning(params);
73
+ case NotificationMethod.MODEL_REROUTED:
74
+ return this.handleModelRerouted(params);
75
+ case NotificationMethod.TURN_PLAN_UPDATED:
76
+ return this.handleTurnPlanUpdated(params);
66
77
  case NotificationMethod.ERROR:
67
78
  return this.handleError(params);
68
79
  case NotificationMethod.THREAD_COMPACTED:
@@ -214,6 +225,7 @@ export class CodexAppServerTranslator {
214
225
  this.textByItem.clear();
215
226
  this.reasoningByItem.clear();
216
227
  this.toolOutputByItem.clear();
228
+ this.toolItemMeta.clear();
217
229
  this.planByItem.clear();
218
230
  this.startedToolItems.clear();
219
231
  this.fileChangeItems.clear();
@@ -314,10 +326,14 @@ export class CodexAppServerTranslator {
314
326
  return [];
315
327
  }
316
328
  this.toolOutputByItem.set(item.id, authoritative);
329
+ // 信息保真 C2:完成事件同样携带身份,并在发出后释放 meta。
330
+ const meta = this.toolItemMeta.get(item.id);
331
+ this.toolItemMeta.delete(item.id);
317
332
  return [
318
333
  {
319
334
  type: 'turn_diff',
320
335
  toolOutput: authoritative,
336
+ ...meta,
321
337
  itemId: item.id,
322
338
  complete: true,
323
339
  toolStatus: commandExecutionStatusToToolStatus(item.status),
@@ -367,6 +383,26 @@ export class CodexAppServerTranslator {
367
383
  ];
368
384
  }
369
385
  }
386
+ // 信息保真 C2:webSearch/mcpToolCall/dynamicToolCall 等无专属 completed 分支的
387
+ // 工具 item 也必须收尾——否则 run-state 侧工具块永挂 running(无 completedAt/终态),
388
+ // toolItemMeta 泄漏到下个 turn 的 clear() 才被释放。
389
+ if (this.startedToolItems.has(item.id)) {
390
+ const meta = this.toolItemMeta.get(item.id);
391
+ this.toolItemMeta.delete(item.id);
392
+ this.startedToolItems.delete(item.id);
393
+ return [
394
+ {
395
+ type: 'turn_diff',
396
+ toolOutput: this.toolOutputByItem.get(item.id) ?? '',
397
+ ...meta,
398
+ itemId: item.id,
399
+ complete: true,
400
+ threadId: params.threadId,
401
+ turnId: params.turnId,
402
+ timestamp: this.now(),
403
+ },
404
+ ];
405
+ }
370
406
  return [];
371
407
  }
372
408
  handleReasoningTextDelta(params) {
@@ -393,6 +429,7 @@ export class CodexAppServerTranslator {
393
429
  {
394
430
  type: 'turn_diff',
395
431
  toolOutput: next,
432
+ ...this.toolItemMeta.get(params.itemId),
396
433
  itemId: params.itemId,
397
434
  threadId: params.threadId,
398
435
  turnId: params.turnId,
@@ -428,6 +465,48 @@ export class CodexAppServerTranslator {
428
465
  };
429
466
  return [];
430
467
  }
468
+ /**
469
+ * warning 通知 → notice 事件(信息保真 C2:运行期告警不再静默丢弃)。
470
+ */
471
+ handleWarning(params) {
472
+ return [
473
+ {
474
+ type: 'notice',
475
+ level: 'warn',
476
+ code: params.code,
477
+ text: params.message,
478
+ timestamp: this.now(),
479
+ },
480
+ ];
481
+ }
482
+ /**
483
+ * model/rerouted 通知 → notice 事件(模型重路由必须用户可见)。
484
+ */
485
+ handleModelRerouted(params) {
486
+ return [
487
+ {
488
+ type: 'notice',
489
+ level: 'warn',
490
+ code: 'model/rerouted',
491
+ text: `模型已切换为 ${params.model}${params.reason ? `:${params.reason}` : ''}`,
492
+ timestamp: this.now(),
493
+ },
494
+ ];
495
+ }
496
+ /**
497
+ * turn/plan/updated 通知 → plan 事件(PlanStep[] 渲染为 markdown 清单)。
498
+ * status 缺省或未知一律按 pending 处理(防御 wire 枚举漂移)。
499
+ */
500
+ handleTurnPlanUpdated(params) {
501
+ const ICON = { completed: '✅', in_progress: '🔄', pending: '⬜', error: '❌' };
502
+ const markdown = params.plan
503
+ .map((s) => {
504
+ const status = s.status && s.status in ICON ? s.status : 'pending';
505
+ return `${ICON[status]} ${s.title}${s.subtitle ? ` — ${s.subtitle}` : ''}`;
506
+ })
507
+ .join('\n');
508
+ return [{ type: 'plan', plan: markdown, timestamp: this.now() }];
509
+ }
431
510
  handleError(params) {
432
511
  if (params.willRetry) {
433
512
  // 非终态错误:服务端会重试该 turn。提前结束 run 会造成桥与 server
@@ -574,10 +653,77 @@ export class CodexAppServerTranslator {
574
653
  // item/started 锚定工具块位置(真实时序):命令开始时即创建工具块,
575
654
  // 而不是等到首个输出 delta——否则后续 reasoning item 会插到 command 之前。
576
655
  this.startedToolItems.add(item.id);
656
+ // 信息保真 C2:记录工具身份,随 turn_diff 透传(run-state 首见时写入)。
657
+ this.toolItemMeta.set(item.id, {
658
+ toolName: 'Bash',
659
+ toolInput: { command: item.command, ...(item.cwd ? { cwd: item.cwd } : {}) },
660
+ toolHint: 'shell',
661
+ });
662
+ return [
663
+ {
664
+ type: 'turn_diff',
665
+ toolOutput: '',
666
+ ...this.toolItemMeta.get(item.id),
667
+ itemId: item.id,
668
+ threadId: params.threadId,
669
+ turnId: params.turnId,
670
+ timestamp: this.now(),
671
+ },
672
+ ];
673
+ }
674
+ if (item.type === 'webSearch') {
675
+ this.startedToolItems.add(item.id);
676
+ this.toolItemMeta.set(item.id, {
677
+ toolName: 'WebSearch',
678
+ toolInput: { query: item.query },
679
+ toolHint: 'web',
680
+ });
681
+ return [
682
+ {
683
+ type: 'turn_diff',
684
+ toolOutput: '',
685
+ ...this.toolItemMeta.get(item.id),
686
+ itemId: item.id,
687
+ threadId: params.threadId,
688
+ turnId: params.turnId,
689
+ timestamp: this.now(),
690
+ },
691
+ ];
692
+ }
693
+ if (item.type === 'mcpToolCall') {
694
+ // ThreadItem union 含 catch-all 成员,判别收窄会与泛型成员并集;
695
+ // 显式断言到具体 interface(字段名已经 v2 schema 确认)。
696
+ const mcpItem = item;
697
+ this.startedToolItems.add(item.id);
698
+ this.toolItemMeta.set(item.id, {
699
+ toolName: `mcp__${mcpItem.server}__${mcpItem.tool}`,
700
+ toolInput: mcpItem.arguments ?? {},
701
+ toolHint: 'search',
702
+ });
703
+ return [
704
+ {
705
+ type: 'turn_diff',
706
+ toolOutput: '',
707
+ ...this.toolItemMeta.get(item.id),
708
+ itemId: item.id,
709
+ threadId: params.threadId,
710
+ turnId: params.turnId,
711
+ timestamp: this.now(),
712
+ },
713
+ ];
714
+ }
715
+ if (item.type === 'dynamicToolCall') {
716
+ const dynItem = item;
717
+ this.startedToolItems.add(item.id);
718
+ this.toolItemMeta.set(item.id, {
719
+ toolName: dynItem.tool,
720
+ toolInput: dynItem.arguments ?? {},
721
+ });
577
722
  return [
578
723
  {
579
724
  type: 'turn_diff',
580
725
  toolOutput: '',
726
+ ...this.toolItemMeta.get(item.id),
581
727
  itemId: item.id,
582
728
  threadId: params.threadId,
583
729
  turnId: params.turnId,