lark-remote 0.5.19 → 0.5.21

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 (136) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/dist/bridge/approval-actions.d.ts +1 -1
  3. package/dist/bridge/approval-actions.js +1 -1
  4. package/dist/bridge/bash-run.d.ts +71 -0
  5. package/dist/bridge/bash-run.js +217 -0
  6. package/dist/bridge/compact.d.ts +155 -0
  7. package/dist/bridge/compact.js +450 -0
  8. package/dist/bridge/final-usage.js +4 -4
  9. package/dist/bridge/inbound-media.js +72 -46
  10. package/dist/bridge/index.d.ts +106 -153
  11. package/dist/bridge/index.js +400 -1000
  12. package/dist/bridge/job-watch.d.ts +68 -0
  13. package/dist/bridge/job-watch.js +146 -0
  14. package/dist/bridge/queue-manager.d.ts +3 -3
  15. package/dist/bridge/queue-manager.js +10 -29
  16. package/dist/card/approval-render.d.ts +2 -2
  17. package/dist/card/approval-render.js +141 -130
  18. package/dist/card/card-budget.js +123 -98
  19. package/dist/card/card-helpers.d.ts +3 -3
  20. package/dist/card/card-helpers.js +6 -6
  21. package/dist/card/final-answer-file.js +1 -1
  22. package/dist/card/run-renderer.d.ts +3 -3
  23. package/dist/card/run-renderer.js +114 -159
  24. package/dist/card/run-state.d.ts +1 -15
  25. package/dist/card/run-state.js +358 -292
  26. package/dist/card/tool-render.d.ts +1 -1
  27. package/dist/card/tool-render.js +21 -17
  28. package/dist/card/usage-format.d.ts +22 -7
  29. package/dist/card/usage-format.js +119 -100
  30. package/dist/cli.js +15 -3
  31. package/dist/clone.js +2 -2
  32. package/dist/common/text-width.js +36 -24
  33. package/dist/common/tool-summary.js +132 -90
  34. package/dist/config/codex-config.js +130 -106
  35. package/dist/config/index.d.ts +16 -2
  36. package/dist/config/index.js +23 -8
  37. package/dist/connector/index.js +6 -6
  38. package/dist/error-classification.js +3 -2
  39. package/dist/inbound/placeholder.d.ts +1 -1
  40. package/dist/inbound/placeholder.js +1 -1
  41. package/dist/index.js +387 -281
  42. package/dist/logger/index.js +6 -6
  43. package/dist/message-handlers.d.ts +0 -5
  44. package/dist/message-handlers.js +254 -237
  45. package/dist/model-catalog.d.ts +2 -2
  46. package/dist/model-catalog.js +3 -8
  47. package/dist/platform/identity.js +1 -1
  48. package/dist/platform/path.d.ts +1 -1
  49. package/dist/platform/path.js +1 -1
  50. package/dist/platform/sleep-blocker.js +1 -1
  51. package/dist/platform/terminator-win32.js +2 -1
  52. package/dist/router/commands/ls.d.ts +3 -0
  53. package/dist/router/commands/ls.js +256 -175
  54. package/dist/router/commands/order.js +184 -159
  55. package/dist/router/commands/resume.js +190 -152
  56. package/dist/router/commands/ws.js +219 -174
  57. package/dist/router/config/dsh.d.ts +4 -8
  58. package/dist/router/config/dsh.js +3 -10
  59. package/dist/router/config/kimi.js +1 -1
  60. package/dist/router/config/types.d.ts +1 -1
  61. package/dist/router/config-diff.js +1 -1
  62. package/dist/router/index.d.ts +56 -11
  63. package/dist/router/index.js +401 -391
  64. package/dist/runner/bash/runner.js +2 -4
  65. package/dist/runner/claude/session.d.ts +1 -1
  66. package/dist/runner/claude/session.js +6 -4
  67. package/dist/runner/codex/app-server/runner.d.ts +9 -2
  68. package/dist/runner/codex/app-server/runner.js +30 -25
  69. package/dist/runner/codex/app-server/translator.d.ts +2 -5
  70. package/dist/runner/codex/app-server/translator.js +291 -238
  71. package/dist/runner/common/acp/acp-approval.d.ts +1 -1
  72. package/dist/runner/common/acp/acp-approval.js +1 -1
  73. package/dist/runner/common/acp/base-acp-runner.d.ts +5 -2
  74. package/dist/runner/common/acp/base-acp-runner.js +26 -65
  75. package/dist/runner/common/acp/base-translator.d.ts +12 -2
  76. package/dist/runner/common/acp/base-translator.js +50 -26
  77. package/dist/runner/common/acp/protocol-types.d.ts +1 -1
  78. package/dist/runner/common/acp/protocol-types.js +2 -2
  79. package/dist/runner/common/connection-based-runner.d.ts +32 -1
  80. package/dist/runner/common/connection-based-runner.js +32 -2
  81. package/dist/runner/common/jsonl-stream.js +154 -187
  82. package/dist/runner/common/jsonrpc/client.d.ts +29 -0
  83. package/dist/runner/common/jsonrpc/client.js +43 -12
  84. package/dist/runner/common/jsonrpc/connection-manager.d.ts +2 -2
  85. package/dist/runner/common/jsonrpc/connection-manager.js +4 -4
  86. package/dist/runner/common/spawning-runner.d.ts +6 -1
  87. package/dist/runner/common/spawning-runner.js +9 -4
  88. package/dist/runner/common/turn-started.d.ts +8 -0
  89. package/dist/runner/common/turn-started.js +11 -0
  90. package/dist/runner/dsh/approval.js +84 -51
  91. package/dist/runner/dsh/catalog-probe.d.ts +17 -0
  92. package/dist/runner/dsh/catalog-probe.js +10 -0
  93. package/dist/runner/dsh/client.d.ts +7 -11
  94. package/dist/runner/dsh/client.js +356 -289
  95. package/dist/runner/dsh/exit-cancel.d.ts +2 -2
  96. package/dist/runner/dsh/exit-cancel.js +2 -2
  97. package/dist/runner/dsh/inflight.d.ts +1 -1
  98. package/dist/runner/dsh/inflight.js +1 -1
  99. package/dist/runner/dsh/mux-stream.js +28 -8
  100. package/dist/runner/dsh/runner.d.ts +44 -4
  101. package/dist/runner/dsh/runner.js +296 -199
  102. package/dist/runner/dsh/translator.d.ts +1 -31
  103. package/dist/runner/dsh/translator.js +180 -178
  104. package/dist/runner/index.d.ts +1 -1
  105. package/dist/runner/kimi/acp/runner.d.ts +1 -2
  106. package/dist/runner/kimi/acp/runner.js +82 -46
  107. package/dist/runner/kimi/acp/translator.d.ts +1 -1
  108. package/dist/runner/kimi/acp/translator.js +3 -4
  109. package/dist/runner/opencode/acp/error-monitor.js +5 -5
  110. package/dist/runner/opencode/acp/runner.d.ts +2 -2
  111. package/dist/runner/opencode/acp/runner.js +4 -4
  112. package/dist/runner/pi/rpc/client.js +4 -13
  113. package/dist/runner/pi/rpc/protocol-types.d.ts +1 -1
  114. package/dist/runner/pi/rpc/protocol-types.js +1 -1
  115. package/dist/runner/pi/rpc/runner.d.ts +3 -0
  116. package/dist/runner/pi/rpc/runner.js +27 -42
  117. package/dist/runner/pi/rpc/translator.js +61 -60
  118. package/dist/runner/types.d.ts +2 -2
  119. package/dist/runner/types.js +1 -1
  120. package/dist/session/claude/sessions.d.ts +45 -0
  121. package/dist/session/claude/sessions.js +160 -122
  122. package/dist/session/codex/rollout-reader.js +181 -182
  123. package/dist/session/common/throw-session-list-error.d.ts +10 -0
  124. package/dist/session/common/throw-session-list-error.js +16 -0
  125. package/dist/session/dsh/sessions.js +291 -235
  126. package/dist/session/kimi/sessions.d.ts +40 -8
  127. package/dist/session/kimi/sessions.js +373 -301
  128. package/dist/session/opencode/sessions.d.ts +142 -1
  129. package/dist/session/opencode/sessions.js +378 -321
  130. package/dist/session/pi/sessions.js +4 -5
  131. package/dist/session/session-store.d.ts +9 -1
  132. package/dist/session/session-store.js +99 -99
  133. package/dist/update/version-check.js +2 -1
  134. package/dist/workspace/index.d.ts +1 -1
  135. package/dist/workspace/index.js +1 -1
  136. package/package.json +1 -1
@@ -8,14 +8,268 @@
8
8
  * app-server v2 protocol (see protocol-types.ts).
9
9
  */
10
10
  import { makeQuestionApprovalEvent } from '../../question-common.js';
11
+ import { renderPlanMarkdown } from '../../common/acp/base-translator.js';
11
12
  import { NotificationMethod, ServerRequestMethod, UNSUPPORTED_SERVER_REQUEST_METHODS, } from './protocol-types.js';
12
- // =============================================================================
13
- // Translator
14
- // =============================================================================
13
+ /** 权限 profile → 去重后的权限项列表(entries 优先,legacy read/write 兜底)。 */
14
+ function pushFsEntries(items, profile) {
15
+ // entries 是结构化形式([{ path, access }]),read/write 是 legacy 字符串数组。
16
+ for (const entry of profile.fileSystem?.entries ?? []) {
17
+ if (entry.access === 'read' || entry.access === 'write') {
18
+ items.push({
19
+ id: `fs-${entry.access}:${entry.path}`,
20
+ label: `${entry.access === 'read' ? 'Read' : 'Write'}: ${entry.path}`,
21
+ target: { kind: entry.access === 'read' ? 'fsRead' : 'fsWrite', path: entry.path },
22
+ selected: false,
23
+ });
24
+ }
25
+ }
26
+ }
27
+ /** legacy read/write 字符串数组 → 权限项(按 id 去重,entries 优先)。 */
28
+ function pushLegacyPaths(items, profile, access) {
29
+ const paths = access === 'read' ? profile.fileSystem?.read : profile.fileSystem?.write;
30
+ for (const path of paths ?? []) {
31
+ const id = `fs-${access}:${path}`;
32
+ if (!items.some((i) => i.id === id)) {
33
+ items.push({
34
+ id,
35
+ label: `${access === 'read' ? 'Read' : 'Write'}: ${path}`,
36
+ target: { kind: access === 'read' ? 'fsRead' : 'fsWrite', path },
37
+ selected: false,
38
+ });
39
+ }
40
+ }
41
+ }
42
+ /** network.enabled 整体开关 → 单条权限项。 */
43
+ function pushNetworkItem(items, profile) {
44
+ if (profile.network?.enabled) {
45
+ // 真实 AdditionalNetworkPermissions 只有 enabled 布尔开关(整体网络访问)。
46
+ items.push({
47
+ id: 'net:all',
48
+ label: '网络访问',
49
+ target: { kind: 'network' },
50
+ selected: false,
51
+ });
52
+ }
53
+ }
54
+ /** 权限 profile → 去重后的权限项列表(entries 优先,legacy read/write 兜底)。 */
55
+ function permissionItemsOf(profile) {
56
+ const items = [];
57
+ pushFsEntries(items, profile);
58
+ pushLegacyPaths(items, profile, 'read');
59
+ pushLegacyPaths(items, profile, 'write');
60
+ pushNetworkItem(items, profile);
61
+ return items;
62
+ }
63
+ /** agentMessage 权威全文与已流式内容不一致 → 补发快照。 */
64
+ function agentMessageFallback(state, item, ids, events) {
65
+ const text = item.text ?? '';
66
+ if (text && state.textByItem.get(item.id) !== text) {
67
+ state.textByItem.set(item.id, text);
68
+ const final = item.phase === 'final_answer';
69
+ events.push(final
70
+ ? {
71
+ type: 'final_answer',
72
+ text,
73
+ itemId: item.id,
74
+ complete: true,
75
+ timestamp: state.now(),
76
+ }
77
+ : state.turnDiff(ids, { text, itemId: item.id, complete: true }));
78
+ }
79
+ }
80
+ /** plan 权威文本与已流式内容不一致 → 补发快照。 */
81
+ function planFallback(state, item, ids, events) {
82
+ const planText = item.text ?? '';
83
+ if (planText && state.planByItem.get(item.id) !== planText) {
84
+ state.planByItem.set(item.id, planText);
85
+ events.push(state.turnDiff(ids, { plan: planText, itemId: item.id, complete: true }));
86
+ }
87
+ }
88
+ const TURN_COMPLETED_FALLBACK_HANDLERS = new Map([
89
+ ['agentMessage', agentMessageFallback],
90
+ ['plan', planFallback],
91
+ ]);
92
+ /** turn.completed 的权威 items 与已流式内容不一致时补发 item 级快照。 */
93
+ function turnCompletedFallbackEvents(state, params) {
94
+ const events = [];
95
+ const turn = params?.turn;
96
+ const ids = {
97
+ threadId: params?.threadId ?? state.threadId,
98
+ turnId: params?.turn?.id ?? state.turnId,
99
+ };
100
+ for (const item of turn?.items ?? []) {
101
+ TURN_COMPLETED_FALLBACK_HANDLERS.get(item.type)?.(state, item, ids, events);
102
+ }
103
+ return events;
104
+ }
105
+ /** 终态 ResultEvent:interrupted / failed / success 保持各自 subtype。 */
106
+ function resultEventOf(state, params) {
107
+ const turn = params?.turn;
108
+ const usage = {
109
+ input_tokens: state.usage.inputTokens,
110
+ output_tokens: state.usage.outputTokens,
111
+ cache_read_tokens: state.usage.cachedInputTokens,
112
+ total_tokens: state.usage.totalTokens,
113
+ context_limit: state.usage.contextLimit,
114
+ };
115
+ const subtype = turn?.status === 'failed'
116
+ ? 'error'
117
+ : turn?.status === 'interrupted'
118
+ ? 'interrupted'
119
+ : 'success';
120
+ return {
121
+ type: 'result',
122
+ subtype,
123
+ session_id: state.threadId,
124
+ usage,
125
+ errorMessage: turn?.error?.message,
126
+ };
127
+ }
128
+ function fileChangeCompleted(state, params) {
129
+ // 查表已按 type 分派;此处显式断言还原原分支内的类型收窄。
130
+ const item = params.item;
131
+ if (!Array.isArray(item.changes))
132
+ return { events: [], stop: false };
133
+ // 权威文件变更(含聚合后的 diff);以快照语义替换本 item 的变更列表。
134
+ state.fileChangeItems.set(item.id, item.changes);
135
+ return {
136
+ events: [
137
+ state.turnDiff(params, {
138
+ fileChanges: normalizeFileChanges(item.changes),
139
+ itemId: item.id,
140
+ complete: true,
141
+ }),
142
+ ],
143
+ stop: true,
144
+ };
145
+ }
146
+ function reasoningCompleted(state, params) {
147
+ // 查表已按 type 分派;此处显式断言还原原分支内的类型收窄。
148
+ const item = params.item;
149
+ const content = Array.isArray(item.content) ? item.content : [];
150
+ const summary = Array.isArray(item.summary) ? item.summary : [];
151
+ if (content.length > 0 || summary.length > 0) {
152
+ // 权威内容:summary 优先展示,content 段落保留(per-item 替换)。
153
+ const entry = state.reasoningEntry(item.id);
154
+ entry.content = new Map(content.map((c, i) => [i, c]));
155
+ entry.summary = summary.join('\n');
156
+ return {
157
+ events: [
158
+ state.turnDiff(params, {
159
+ reasoning: state.reasoningSnapshotFor(entry),
160
+ itemId: item.id,
161
+ complete: true,
162
+ }),
163
+ ],
164
+ stop: true,
165
+ };
166
+ }
167
+ return { events: [], stop: false };
168
+ }
169
+ function commandExecutionCompleted(state, params) {
170
+ const item = params.item;
171
+ // 无 outputDelta 流时(短命令),用 item/completed 的聚合输出兜底;
172
+ // 已流式输出则以累积快照收尾(complete 语义标记工具完成)。
173
+ const accumulated = state.toolOutputByItem.get(item.id) ?? '';
174
+ const aggregated = item.aggregatedOutput;
175
+ const authoritative = aggregated != null ? String(aggregated) : accumulated;
176
+ // 从未 item/started 且无任何输出(如审批被拒的空命令)→ 不产生工具块,
177
+ // 避免完成时刻凭空冒出一个空命令块。
178
+ if (!authoritative && !state.startedToolItems.has(item.id)) {
179
+ return { events: [], stop: true };
180
+ }
181
+ state.toolOutputByItem.set(item.id, authoritative);
182
+ // 信息保真 完成事件同样携带身份,并在发出后释放 meta。
183
+ const meta = state.toolItemMeta.get(item.id);
184
+ state.toolItemMeta.delete(item.id);
185
+ return {
186
+ events: [
187
+ state.turnDiff(params, {
188
+ toolOutput: authoritative,
189
+ ...meta,
190
+ itemId: item.id,
191
+ complete: true,
192
+ toolStatus: commandExecutionStatusToToolStatus(item.status),
193
+ }),
194
+ ],
195
+ stop: true,
196
+ };
197
+ }
198
+ function agentMessageCompleted(state, params) {
199
+ const item = params.item;
200
+ // item/completed 的 agentMessage 带权威全文(text 字段);以 item 级
201
+ // 快照收尾,run-state 按 itemId 原地替换,保证内容收敛到权威值。
202
+ const text = item.text ?? '';
203
+ const accumulated = state.textByItem.get(item.id) ?? '';
204
+ // 空最终文本但已有流式内容(异常边界)→ 用累积内容收尾,补齐 completedAt;
205
+ // 完全无内容则没有块可完成,不发事件。
206
+ if (text || accumulated) {
207
+ const finalText = text || accumulated;
208
+ state.textByItem.set(item.id, finalText);
209
+ if (item.phase === 'final_answer' ||
210
+ state.finalTextItems.has(item.id)) {
211
+ state.finalTextItems.delete(item.id);
212
+ return {
213
+ events: [
214
+ {
215
+ type: 'final_answer',
216
+ text: finalText,
217
+ itemId: item.id,
218
+ complete: true,
219
+ timestamp: state.now(),
220
+ },
221
+ ],
222
+ stop: true,
223
+ };
224
+ }
225
+ return {
226
+ events: [state.turnDiff(params, { text: finalText, itemId: item.id, complete: true })],
227
+ stop: true,
228
+ };
229
+ }
230
+ return { events: [], stop: false };
231
+ }
232
+ function planCompleted(state, params) {
233
+ const item = params.item;
234
+ const text = item.text ?? '';
235
+ if (text) {
236
+ state.planByItem.set(item.id, text);
237
+ return {
238
+ events: [state.turnDiff(params, { plan: text, itemId: item.id, complete: true })],
239
+ stop: true,
240
+ };
241
+ }
242
+ return { events: [], stop: false };
243
+ }
244
+ /** 终态分支查表:命中且定案则直接返回,否则落到通用工具收尾。 */
245
+ const ITEM_COMPLETED_HANDLERS = new Map([
246
+ ['fileChange', fileChangeCompleted],
247
+ ['reasoning', reasoningCompleted],
248
+ ['commandExecution', commandExecutionCompleted],
249
+ ['agentMessage', agentMessageCompleted],
250
+ ['plan', planCompleted],
251
+ ]);
252
+ /** 无专属 completed 分支的工具 item 也必须收尾(避免工具块永挂 running)。 */
253
+ function finalizeUnhandledToolItem(state, params) {
254
+ const item = params.item;
255
+ if (state.startedToolItems.has(item.id)) {
256
+ const meta = state.toolItemMeta.get(item.id);
257
+ state.toolItemMeta.delete(item.id);
258
+ state.startedToolItems.delete(item.id);
259
+ return [
260
+ state.turnDiff(params, {
261
+ toolOutput: state.toolOutputByItem.get(item.id) ?? '',
262
+ ...meta,
263
+ itemId: item.id,
264
+ complete: true,
265
+ }),
266
+ ];
267
+ }
268
+ return [];
269
+ }
15
270
  export class CodexAppServerTranslator {
16
271
  turnId = '';
17
272
  threadId = '';
18
- turnStatus = 'pending';
19
273
  /** Full agentMessage text per item id (item-scoped snapshot). */
20
274
  textByItem = new Map();
21
275
  /** agentMessage item ids whose protocol phase is `final_answer`. */
@@ -132,67 +386,9 @@ export class CodexAppServerTranslator {
132
386
  * Called when the turn completes.
133
387
  */
134
388
  handleTurnCompleted(params) {
135
- const turn = params?.turn;
136
- this.turnStatus = turn?.status ?? 'completed';
137
- const events = [];
138
- // 兜底:turn.items 里权威 agentMessage / plan item 与已流式内容不一致或
139
- // 未发过(如 itemsView 摘要只含部分 item)时,补发 item 级快照。已一致则
140
- // 跳过,避免冗余事件(run-state 按 itemId 原地替换、幂等)。
141
- for (const item of turn?.items ?? []) {
142
- if (item.type === 'agentMessage') {
143
- const text = item.text ?? '';
144
- if (text && this.textByItem.get(item.id) !== text) {
145
- this.textByItem.set(item.id, text);
146
- const final = item.phase === 'final_answer';
147
- events.push(final
148
- ? {
149
- type: 'final_answer',
150
- text,
151
- itemId: item.id,
152
- complete: true,
153
- timestamp: this.now(),
154
- }
155
- : this.turnDiffEvent({
156
- threadId: params?.threadId ?? this.threadId,
157
- turnId: params?.turn?.id ?? this.turnId,
158
- }, { text, itemId: item.id, complete: true }));
159
- }
160
- }
161
- else if (item.type === 'plan') {
162
- // plan 是 EXPERIMENTAL 文本增量,不保证与最终 plan item 一致;
163
- // turn/completed 的权威 plan item 以此兜底校正。
164
- const planText = item.text ?? '';
165
- if (planText && this.planByItem.get(item.id) !== planText) {
166
- this.planByItem.set(item.id, planText);
167
- events.push(this.turnDiffEvent({
168
- threadId: params?.threadId ?? this.threadId,
169
- turnId: params?.turn?.id ?? this.turnId,
170
- }, { plan: planText, itemId: item.id, complete: true }));
171
- }
172
- }
173
- }
174
- const usage = {
175
- input_tokens: this.usage.inputTokens,
176
- output_tokens: this.usage.outputTokens,
177
- cache_read_tokens: this.usage.cachedInputTokens,
178
- total_tokens: this.usage.totalTokens,
179
- context_limit: this.usage.contextLimit,
180
- };
181
- // 语义保真:interrupted(审批超时/取消、/stop)与 failed(Agent 真失败)
182
- // 是性质不同的终态。interrupted 走独立 subtype,卡片不再误报「Agent 返回
183
- // 错误结果」;failed 保持 error。
184
- const subtype = turn?.status === 'failed'
185
- ? 'error'
186
- : turn?.status === 'interrupted'
187
- ? 'interrupted'
188
- : 'success';
189
- events.push({
190
- type: 'result',
191
- subtype,
192
- session_id: this.threadId,
193
- usage,
194
- errorMessage: turn?.error?.message,
195
- });
389
+ const state = this.translatorState();
390
+ const events = turnCompletedFallbackEvents(state, params);
391
+ events.push(resultEventOf(state, params));
196
392
  return events;
197
393
  }
198
394
  /**
@@ -204,12 +400,6 @@ export class CodexAppServerTranslator {
204
400
  outputTokens: this.usage.outputTokens,
205
401
  };
206
402
  }
207
- /**
208
- * Get the current turn status.
209
- */
210
- getTurnStatus() {
211
- return this.turnStatus;
212
- }
213
403
  /**
214
404
  * Set the operation kind (exec vs compact).
215
405
  */
@@ -222,7 +412,6 @@ export class CodexAppServerTranslator {
222
412
  handleTurnStarted(params) {
223
413
  this.threadId = params.threadId;
224
414
  this.turnId = params.turn.id;
225
- this.turnStatus = params.turn.status ?? 'inProgress';
226
415
  // 单 turn 用量从零开始累计:turn 内每次采样都会发一条
227
416
  // `thread/tokenUsage/updated`,本 run 的用量是这些增量之和(不是最后一条)。
228
417
  // resume 后服务端重放的上一 turn 用量若在此之后到达,由 handleTokenUsageUpdated
@@ -282,122 +471,12 @@ export class CodexAppServerTranslator {
282
471
  return [];
283
472
  }
284
473
  handleItemCompleted(params) {
285
- const item = params.item;
286
- if (item.type === 'fileChange' && Array.isArray(item.changes)) {
287
- // 权威文件变更(含聚合后的 diff);以快照语义替换本 item 的变更列表。
288
- this.fileChangeItems.set(item.id, item.changes);
289
- return [
290
- this.turnDiffEvent(params, {
291
- fileChanges: normalizeFileChanges(item.changes),
292
- itemId: item.id,
293
- complete: true,
294
- }),
295
- ];
296
- }
297
- if (item.type === 'reasoning') {
298
- const content = Array.isArray(item.content) ? item.content : [];
299
- const summary = Array.isArray(item.summary) ? item.summary : [];
300
- if (content.length > 0 || summary.length > 0) {
301
- // 权威内容:summary 优先展示,content 段落保留(per-item 替换)。
302
- const entry = this.reasoningEntry(item.id);
303
- entry.content = new Map(content.map((c, i) => [i, c]));
304
- entry.summary = summary.join('\n');
305
- return [
306
- this.turnDiffEvent(params, {
307
- reasoning: this.reasoningSnapshotFor(entry),
308
- itemId: item.id,
309
- complete: true,
310
- }),
311
- ];
312
- }
313
- }
314
- if (item.type === 'commandExecution') {
315
- // 无 outputDelta 流时(短命令),用 item/completed 的聚合输出兜底;
316
- // 已流式输出则以累积快照收尾(complete 语义标记工具完成)。
317
- const accumulated = this.toolOutputByItem.get(item.id) ?? '';
318
- const aggregated = item.aggregatedOutput;
319
- const authoritative = aggregated != null ? String(aggregated) : accumulated;
320
- // 从未 item/started 且无任何输出(如审批被拒的空命令)→ 不产生工具块,
321
- // 避免完成时刻凭空冒出一个空命令块。
322
- if (!authoritative && !this.startedToolItems.has(item.id)) {
323
- return [];
324
- }
325
- this.toolOutputByItem.set(item.id, authoritative);
326
- // 信息保真 完成事件同样携带身份,并在发出后释放 meta。
327
- const meta = this.toolItemMeta.get(item.id);
328
- this.toolItemMeta.delete(item.id);
329
- return [
330
- this.turnDiffEvent(params, {
331
- toolOutput: authoritative,
332
- ...meta,
333
- itemId: item.id,
334
- complete: true,
335
- toolStatus: commandExecutionStatusToToolStatus(item.status),
336
- }),
337
- ];
338
- }
339
- if (item.type === 'agentMessage') {
340
- // item/completed 的 agentMessage 带权威全文(text 字段);以 item 级
341
- // 快照收尾,run-state 按 itemId 原地替换,保证内容收敛到权威值。
342
- const text = item.text ?? '';
343
- const accumulated = this.textByItem.get(item.id) ?? '';
344
- // 空最终文本但已有流式内容(异常边界)→ 用累积内容收尾,补齐 completedAt;
345
- // 完全无内容则没有块可完成,不发事件。
346
- if (text || accumulated) {
347
- const finalText = text || accumulated;
348
- this.textByItem.set(item.id, finalText);
349
- if (item.phase === 'final_answer' ||
350
- this.finalTextItems.has(item.id)) {
351
- this.finalTextItems.delete(item.id);
352
- return [
353
- {
354
- type: 'final_answer',
355
- text: finalText,
356
- itemId: item.id,
357
- complete: true,
358
- timestamp: this.now(),
359
- },
360
- ];
361
- }
362
- return [
363
- this.turnDiffEvent(params, {
364
- text: finalText,
365
- itemId: item.id,
366
- complete: true,
367
- }),
368
- ];
369
- }
370
- }
371
- if (item.type === 'plan') {
372
- const text = item.text ?? '';
373
- if (text) {
374
- this.planByItem.set(item.id, text);
375
- return [
376
- this.turnDiffEvent(params, {
377
- plan: text,
378
- itemId: item.id,
379
- complete: true,
380
- }),
381
- ];
382
- }
383
- }
384
- // 信息保真 webSearch/mcpToolCall/dynamicToolCall 等无专属 completed 分支的
385
- // 工具 item 也必须收尾——否则 run-state 侧工具块永挂 running(无 completedAt/终态),
386
- // toolItemMeta 泄漏到下个 turn 的 clear() 才被释放。
387
- if (this.startedToolItems.has(item.id)) {
388
- const meta = this.toolItemMeta.get(item.id);
389
- this.toolItemMeta.delete(item.id);
390
- this.startedToolItems.delete(item.id);
391
- return [
392
- this.turnDiffEvent(params, {
393
- toolOutput: this.toolOutputByItem.get(item.id) ?? '',
394
- ...meta,
395
- itemId: item.id,
396
- complete: true,
397
- }),
398
- ];
399
- }
400
- return [];
474
+ const state = this.translatorState();
475
+ const handler = ITEM_COMPLETED_HANDLERS.get(params.item.type);
476
+ const result = handler?.(state, params);
477
+ if (result?.stop)
478
+ return result.events;
479
+ return finalizeUnhandledToolItem(state, params);
401
480
  }
402
481
  handleReasoningTextDelta(params) {
403
482
  const index = params.contentIndex ?? 0;
@@ -497,14 +576,9 @@ export class CodexAppServerTranslator {
497
576
  * status 缺省或未知一律按 pending 处理(防御 wire 枚举漂移)。
498
577
  */
499
578
  handleTurnPlanUpdated(params) {
500
- const ICON = { completed: '✅', in_progress: '🔄', pending: '⬜', error: '❌' };
501
- const markdown = params.plan
502
- .map((s) => {
503
- const status = s.status && s.status in ICON ? s.status : 'pending';
504
- return `${ICON[status]} ${s.title}${s.subtitle ? ` — ${s.subtitle}` : ''}`;
505
- })
506
- .join('\n');
507
- return [{ type: 'plan', plan: markdown, timestamp: this.now() }];
579
+ // 图标表 + status 归一与 ACP handlePlan 共用 renderPlanMarkdown
580
+ // (未知/缺省 status 一律按 pending,防 wire 枚举漂移)。
581
+ return [{ type: 'plan', plan: renderPlanMarkdown(params.plan), timestamp: this.now() }];
508
582
  }
509
583
  handleError(params) {
510
584
  if (params.willRetry) {
@@ -743,48 +817,7 @@ export class CodexAppServerTranslator {
743
817
  ];
744
818
  }
745
819
  buildPermissionItems(profile) {
746
- const items = [];
747
- // entries 是结构化形式([{ path, access }]),read/write 是 legacy 字符串数组。
748
- for (const entry of profile.fileSystem?.entries ?? []) {
749
- if (entry.access === 'read' || entry.access === 'write') {
750
- items.push({
751
- id: `fs-${entry.access}:${entry.path}`,
752
- label: `${entry.access === 'read' ? 'Read' : 'Write'}: ${entry.path}`,
753
- target: { kind: entry.access === 'read' ? 'fsRead' : 'fsWrite', path: entry.path },
754
- selected: false,
755
- });
756
- }
757
- }
758
- for (const path of profile.fileSystem?.read ?? []) {
759
- if (!items.some((i) => i.id === `fs-read:${path}`)) {
760
- items.push({
761
- id: `fs-read:${path}`,
762
- label: `Read: ${path}`,
763
- target: { kind: 'fsRead', path },
764
- selected: false,
765
- });
766
- }
767
- }
768
- for (const path of profile.fileSystem?.write ?? []) {
769
- if (!items.some((i) => i.id === `fs-write:${path}`)) {
770
- items.push({
771
- id: `fs-write:${path}`,
772
- label: `Write: ${path}`,
773
- target: { kind: 'fsWrite', path },
774
- selected: false,
775
- });
776
- }
777
- }
778
- if (profile.network?.enabled) {
779
- // 真实 AdditionalNetworkPermissions 只有 enabled 布尔开关(整体网络访问)。
780
- items.push({
781
- id: 'net:all',
782
- label: '网络访问',
783
- target: { kind: 'network' },
784
- selected: false,
785
- });
786
- }
787
- return items;
820
+ return permissionItemsOf(profile);
788
821
  }
789
822
  /** Get (or lazily create) the reasoning accumulator for one item. */
790
823
  reasoningEntry(itemId) {
@@ -807,6 +840,26 @@ export class CodexAppServerTranslator {
807
840
  now() {
808
841
  return new Date().toISOString();
809
842
  }
843
+ /** 把实例状态打包成模块级 handler 可用的显式快照(.bind 保留 this 绑定)。 */
844
+ translatorState() {
845
+ return {
846
+ fileChangeItems: this.fileChangeItems,
847
+ reasoningByItem: this.reasoningByItem,
848
+ toolOutputByItem: this.toolOutputByItem,
849
+ toolItemMeta: this.toolItemMeta,
850
+ startedToolItems: this.startedToolItems,
851
+ textByItem: this.textByItem,
852
+ finalTextItems: this.finalTextItems,
853
+ planByItem: this.planByItem,
854
+ usage: this.usage,
855
+ threadId: this.threadId,
856
+ turnId: this.turnId,
857
+ turnDiff: this.turnDiffEvent.bind(this),
858
+ now: this.now.bind(this),
859
+ reasoningEntry: this.reasoningEntry.bind(this),
860
+ reasoningSnapshotFor: this.reasoningSnapshotFor.bind(this),
861
+ };
862
+ }
810
863
  }
811
864
  /** Normalize protocol change kind to the ApprovalView kind vocabulary. */
812
865
  function normalizeChangeKind(kind) {
@@ -13,7 +13,7 @@ export interface AcpApprovalKinds {
13
13
  acceptForSession: string[];
14
14
  decline: string[];
15
15
  }
16
- /** kimi 观测 kind(approval.ts:28-29 + 2026-08-15 live:approve_once/approve_always/reject)。 */
16
+ /** kimi 观测 kind(approval.ts:28-29:approve_once/approve_always/reject)。 */
17
17
  export declare const KIMI_APPROVAL_KINDS: AcpApprovalKinds;
18
18
  /** opencode kind(permission.ts:219-223:once/always → approve,其余 → reject)。 */
19
19
  export declare const OPENCODE_APPROVAL_KINDS: AcpApprovalKinds;
@@ -20,7 +20,7 @@ function findOptionIdByKind(options, candidateKinds) {
20
20
  }
21
21
  return undefined;
22
22
  }
23
- /** kimi 观测 kind(approval.ts:28-29 + 2026-08-15 live:approve_once/approve_always/reject)。 */
23
+ /** kimi 观测 kind(approval.ts:28-29:approve_once/approve_always/reject)。 */
24
24
  export const KIMI_APPROVAL_KINDS = {
25
25
  accept: ['approve_once', 'approve_always', 'allow_once'],
26
26
  acceptForSession: ['approve_always', 'allow_always'],
@@ -6,7 +6,7 @@
6
6
  * prompt orchestration, compact orchestration, approval response dispatch,
7
7
  * cancel/clear/release plumbing and the pendingApprovals registry were
8
8
  * line-for-line duplicated with only constants/field names differing
9
- * (2026-09 round-2 simplification). This base absorbs them; what stays in
9
+ * (round-2 simplification). This base absorbs them; what stays in
10
10
  * subclasses:
11
11
  * - applyTurnSettings: mode/model下发 semantics differ (kimi unconditional,
12
12
  * opencode conditional on cached values)
@@ -80,7 +80,7 @@ export declare abstract class BaseAcpRunner<TTranslator extends BaseAcpTranslato
80
80
  * 自身半死:opencode v2 的 location 在每轮拉起的 `opencode acp` 子进程里 boot,
81
81
  * boot 期间后台服务不可用会让它长期停在未激活,后续轮次复用同一连接只会重复
82
82
  * 失败——用户重启 opencode service 也治不好,因为连接缓存在本进程里
83
- * (2026-10-30 现场:4 次重试全失败,重启服务与 lark-remote 都无效)。丢掉连接
83
+ * (4 次重试全失败,重启服务与 lark-remote 都无效)。丢掉连接
84
84
  * 后下一轮重新 spawn,拿到的是新进程的新状态。
85
85
  *
86
86
  * 只对声明为可重试的错误生效:kimi 等沿用默认 `isRetryableSessionSetupError`
@@ -94,6 +94,9 @@ export declare abstract class BaseAcpRunner<TTranslator extends BaseAcpTranslato
94
94
  protected clearTurnState(): void;
95
95
  protected releaseConnection(cwd: string): Promise<void>;
96
96
  protected notifyIdle(cwd: string): void;
97
+ protected notifyConnectionActivity(cwd: string): void;
98
+ /** Wire the acquired client's hooks into the turn dispatcher (anchor-guarded). */
99
+ private installTurnHooks;
97
100
  protected disposeConnections(): Promise<void>;
98
101
  /**
99
102
  * Respond to an approval server request. `response` is the bridge