lark-remote 0.5.20 → 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 (55) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/bridge/inbound-media.js +71 -45
  3. package/dist/bridge/index.d.ts +9 -0
  4. package/dist/bridge/index.js +230 -203
  5. package/dist/card/approval-render.js +32 -33
  6. package/dist/card/card-budget.js +123 -98
  7. package/dist/card/run-renderer.js +82 -68
  8. package/dist/card/run-state.d.ts +0 -14
  9. package/dist/card/run-state.js +335 -207
  10. package/dist/card/tool-render.js +20 -16
  11. package/dist/card/usage-format.d.ts +12 -9
  12. package/dist/card/usage-format.js +111 -97
  13. package/dist/common/text-width.js +36 -24
  14. package/dist/common/tool-summary.js +132 -90
  15. package/dist/index.js +226 -190
  16. package/dist/message-handlers.js +253 -236
  17. package/dist/router/commands/ls.d.ts +3 -0
  18. package/dist/router/commands/ls.js +256 -175
  19. package/dist/router/commands/order.js +184 -159
  20. package/dist/router/commands/resume.js +189 -133
  21. package/dist/router/commands/ws.js +219 -174
  22. package/dist/router/index.js +192 -201
  23. package/dist/runner/bash/runner.js +2 -4
  24. package/dist/runner/codex/app-server/translator.d.ts +2 -0
  25. package/dist/runner/codex/app-server/translator.js +287 -221
  26. package/dist/runner/common/acp/base-translator.js +19 -17
  27. package/dist/runner/common/jsonl-stream.js +154 -187
  28. package/dist/runner/common/jsonrpc/client.d.ts +29 -0
  29. package/dist/runner/common/jsonrpc/client.js +43 -12
  30. package/dist/runner/common/spawning-runner.d.ts +5 -0
  31. package/dist/runner/common/spawning-runner.js +8 -3
  32. package/dist/runner/common/turn-started.d.ts +8 -0
  33. package/dist/runner/common/turn-started.js +11 -0
  34. package/dist/runner/dsh/approval.js +84 -51
  35. package/dist/runner/dsh/client.d.ts +7 -9
  36. package/dist/runner/dsh/client.js +356 -287
  37. package/dist/runner/dsh/mux-stream.js +28 -8
  38. package/dist/runner/dsh/runner.d.ts +4 -3
  39. package/dist/runner/dsh/runner.js +88 -64
  40. package/dist/runner/dsh/translator.d.ts +1 -31
  41. package/dist/runner/dsh/translator.js +180 -178
  42. package/dist/runner/kimi/acp/runner.js +81 -44
  43. package/dist/runner/opencode/acp/error-monitor.js +5 -5
  44. package/dist/runner/pi/rpc/client.js +4 -13
  45. package/dist/runner/pi/rpc/translator.js +61 -60
  46. package/dist/session/claude/sessions.js +113 -101
  47. package/dist/session/codex/rollout-reader.js +179 -180
  48. package/dist/session/dsh/sessions.js +281 -231
  49. package/dist/session/kimi/sessions.d.ts +0 -7
  50. package/dist/session/kimi/sessions.js +129 -109
  51. package/dist/session/opencode/sessions.d.ts +0 -11
  52. package/dist/session/opencode/sessions.js +161 -137
  53. package/dist/session/session-store.d.ts +8 -0
  54. package/dist/session/session-store.js +98 -98
  55. package/package.json +1 -1
@@ -10,9 +10,263 @@
10
10
  import { makeQuestionApprovalEvent } from '../../question-common.js';
11
11
  import { renderPlanMarkdown } from '../../common/acp/base-translator.js';
12
12
  import { NotificationMethod, ServerRequestMethod, UNSUPPORTED_SERVER_REQUEST_METHODS, } from './protocol-types.js';
13
- // =============================================================================
14
- // Translator
15
- // =============================================================================
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
+ }
16
270
  export class CodexAppServerTranslator {
17
271
  turnId = '';
18
272
  threadId = '';
@@ -132,66 +386,9 @@ export class CodexAppServerTranslator {
132
386
  * Called when the turn completes.
133
387
  */
134
388
  handleTurnCompleted(params) {
135
- const turn = params?.turn;
136
- const events = [];
137
- // 兜底:turn.items 里权威 agentMessage / plan item 与已流式内容不一致或
138
- // 未发过(如 itemsView 摘要只含部分 item)时,补发 item 级快照。已一致则
139
- // 跳过,避免冗余事件(run-state 按 itemId 原地替换、幂等)。
140
- for (const item of turn?.items ?? []) {
141
- if (item.type === 'agentMessage') {
142
- const text = item.text ?? '';
143
- if (text && this.textByItem.get(item.id) !== text) {
144
- this.textByItem.set(item.id, text);
145
- const final = item.phase === 'final_answer';
146
- events.push(final
147
- ? {
148
- type: 'final_answer',
149
- text,
150
- itemId: item.id,
151
- complete: true,
152
- timestamp: this.now(),
153
- }
154
- : this.turnDiffEvent({
155
- threadId: params?.threadId ?? this.threadId,
156
- turnId: params?.turn?.id ?? this.turnId,
157
- }, { text, itemId: item.id, complete: true }));
158
- }
159
- }
160
- else if (item.type === 'plan') {
161
- // plan 是 EXPERIMENTAL 文本增量,不保证与最终 plan item 一致;
162
- // turn/completed 的权威 plan item 以此兜底校正。
163
- const planText = item.text ?? '';
164
- if (planText && this.planByItem.get(item.id) !== planText) {
165
- this.planByItem.set(item.id, planText);
166
- events.push(this.turnDiffEvent({
167
- threadId: params?.threadId ?? this.threadId,
168
- turnId: params?.turn?.id ?? this.turnId,
169
- }, { plan: planText, itemId: item.id, complete: true }));
170
- }
171
- }
172
- }
173
- const usage = {
174
- input_tokens: this.usage.inputTokens,
175
- output_tokens: this.usage.outputTokens,
176
- cache_read_tokens: this.usage.cachedInputTokens,
177
- total_tokens: this.usage.totalTokens,
178
- context_limit: this.usage.contextLimit,
179
- };
180
- // 语义保真:interrupted(审批超时/取消、/stop)与 failed(Agent 真失败)
181
- // 是性质不同的终态。interrupted 走独立 subtype,卡片不再误报「Agent 返回
182
- // 错误结果」;failed 保持 error。
183
- const subtype = turn?.status === 'failed'
184
- ? 'error'
185
- : turn?.status === 'interrupted'
186
- ? 'interrupted'
187
- : 'success';
188
- events.push({
189
- type: 'result',
190
- subtype,
191
- session_id: this.threadId,
192
- usage,
193
- errorMessage: turn?.error?.message,
194
- });
389
+ const state = this.translatorState();
390
+ const events = turnCompletedFallbackEvents(state, params);
391
+ events.push(resultEventOf(state, params));
195
392
  return events;
196
393
  }
197
394
  /**
@@ -274,122 +471,12 @@ export class CodexAppServerTranslator {
274
471
  return [];
275
472
  }
276
473
  handleItemCompleted(params) {
277
- const item = params.item;
278
- if (item.type === 'fileChange' && Array.isArray(item.changes)) {
279
- // 权威文件变更(含聚合后的 diff);以快照语义替换本 item 的变更列表。
280
- this.fileChangeItems.set(item.id, item.changes);
281
- return [
282
- this.turnDiffEvent(params, {
283
- fileChanges: normalizeFileChanges(item.changes),
284
- itemId: item.id,
285
- complete: true,
286
- }),
287
- ];
288
- }
289
- if (item.type === 'reasoning') {
290
- const content = Array.isArray(item.content) ? item.content : [];
291
- const summary = Array.isArray(item.summary) ? item.summary : [];
292
- if (content.length > 0 || summary.length > 0) {
293
- // 权威内容:summary 优先展示,content 段落保留(per-item 替换)。
294
- const entry = this.reasoningEntry(item.id);
295
- entry.content = new Map(content.map((c, i) => [i, c]));
296
- entry.summary = summary.join('\n');
297
- return [
298
- this.turnDiffEvent(params, {
299
- reasoning: this.reasoningSnapshotFor(entry),
300
- itemId: item.id,
301
- complete: true,
302
- }),
303
- ];
304
- }
305
- }
306
- if (item.type === 'commandExecution') {
307
- // 无 outputDelta 流时(短命令),用 item/completed 的聚合输出兜底;
308
- // 已流式输出则以累积快照收尾(complete 语义标记工具完成)。
309
- const accumulated = this.toolOutputByItem.get(item.id) ?? '';
310
- const aggregated = item.aggregatedOutput;
311
- const authoritative = aggregated != null ? String(aggregated) : accumulated;
312
- // 从未 item/started 且无任何输出(如审批被拒的空命令)→ 不产生工具块,
313
- // 避免完成时刻凭空冒出一个空命令块。
314
- if (!authoritative && !this.startedToolItems.has(item.id)) {
315
- return [];
316
- }
317
- this.toolOutputByItem.set(item.id, authoritative);
318
- // 信息保真 完成事件同样携带身份,并在发出后释放 meta。
319
- const meta = this.toolItemMeta.get(item.id);
320
- this.toolItemMeta.delete(item.id);
321
- return [
322
- this.turnDiffEvent(params, {
323
- toolOutput: authoritative,
324
- ...meta,
325
- itemId: item.id,
326
- complete: true,
327
- toolStatus: commandExecutionStatusToToolStatus(item.status),
328
- }),
329
- ];
330
- }
331
- if (item.type === 'agentMessage') {
332
- // item/completed 的 agentMessage 带权威全文(text 字段);以 item 级
333
- // 快照收尾,run-state 按 itemId 原地替换,保证内容收敛到权威值。
334
- const text = item.text ?? '';
335
- const accumulated = this.textByItem.get(item.id) ?? '';
336
- // 空最终文本但已有流式内容(异常边界)→ 用累积内容收尾,补齐 completedAt;
337
- // 完全无内容则没有块可完成,不发事件。
338
- if (text || accumulated) {
339
- const finalText = text || accumulated;
340
- this.textByItem.set(item.id, finalText);
341
- if (item.phase === 'final_answer' ||
342
- this.finalTextItems.has(item.id)) {
343
- this.finalTextItems.delete(item.id);
344
- return [
345
- {
346
- type: 'final_answer',
347
- text: finalText,
348
- itemId: item.id,
349
- complete: true,
350
- timestamp: this.now(),
351
- },
352
- ];
353
- }
354
- return [
355
- this.turnDiffEvent(params, {
356
- text: finalText,
357
- itemId: item.id,
358
- complete: true,
359
- }),
360
- ];
361
- }
362
- }
363
- if (item.type === 'plan') {
364
- const text = item.text ?? '';
365
- if (text) {
366
- this.planByItem.set(item.id, text);
367
- return [
368
- this.turnDiffEvent(params, {
369
- plan: text,
370
- itemId: item.id,
371
- complete: true,
372
- }),
373
- ];
374
- }
375
- }
376
- // 信息保真 webSearch/mcpToolCall/dynamicToolCall 等无专属 completed 分支的
377
- // 工具 item 也必须收尾——否则 run-state 侧工具块永挂 running(无 completedAt/终态),
378
- // toolItemMeta 泄漏到下个 turn 的 clear() 才被释放。
379
- if (this.startedToolItems.has(item.id)) {
380
- const meta = this.toolItemMeta.get(item.id);
381
- this.toolItemMeta.delete(item.id);
382
- this.startedToolItems.delete(item.id);
383
- return [
384
- this.turnDiffEvent(params, {
385
- toolOutput: this.toolOutputByItem.get(item.id) ?? '',
386
- ...meta,
387
- itemId: item.id,
388
- complete: true,
389
- }),
390
- ];
391
- }
392
- 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);
393
480
  }
394
481
  handleReasoningTextDelta(params) {
395
482
  const index = params.contentIndex ?? 0;
@@ -730,48 +817,7 @@ export class CodexAppServerTranslator {
730
817
  ];
731
818
  }
732
819
  buildPermissionItems(profile) {
733
- const items = [];
734
- // entries 是结构化形式([{ path, access }]),read/write 是 legacy 字符串数组。
735
- for (const entry of profile.fileSystem?.entries ?? []) {
736
- if (entry.access === 'read' || entry.access === 'write') {
737
- items.push({
738
- id: `fs-${entry.access}:${entry.path}`,
739
- label: `${entry.access === 'read' ? 'Read' : 'Write'}: ${entry.path}`,
740
- target: { kind: entry.access === 'read' ? 'fsRead' : 'fsWrite', path: entry.path },
741
- selected: false,
742
- });
743
- }
744
- }
745
- for (const path of profile.fileSystem?.read ?? []) {
746
- if (!items.some((i) => i.id === `fs-read:${path}`)) {
747
- items.push({
748
- id: `fs-read:${path}`,
749
- label: `Read: ${path}`,
750
- target: { kind: 'fsRead', path },
751
- selected: false,
752
- });
753
- }
754
- }
755
- for (const path of profile.fileSystem?.write ?? []) {
756
- if (!items.some((i) => i.id === `fs-write:${path}`)) {
757
- items.push({
758
- id: `fs-write:${path}`,
759
- label: `Write: ${path}`,
760
- target: { kind: 'fsWrite', path },
761
- selected: false,
762
- });
763
- }
764
- }
765
- if (profile.network?.enabled) {
766
- // 真实 AdditionalNetworkPermissions 只有 enabled 布尔开关(整体网络访问)。
767
- items.push({
768
- id: 'net:all',
769
- label: '网络访问',
770
- target: { kind: 'network' },
771
- selected: false,
772
- });
773
- }
774
- return items;
820
+ return permissionItemsOf(profile);
775
821
  }
776
822
  /** Get (or lazily create) the reasoning accumulator for one item. */
777
823
  reasoningEntry(itemId) {
@@ -794,6 +840,26 @@ export class CodexAppServerTranslator {
794
840
  now() {
795
841
  return new Date().toISOString();
796
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
+ }
797
863
  }
798
864
  /** Normalize protocol change kind to the ApprovalView kind vocabulary. */
799
865
  function normalizeChangeKind(kind) {
@@ -1,4 +1,4 @@
1
- import { makeTurnStartedEvent } from '../turn-started.js';
1
+ import { produceTurnStartedEvent } from '../turn-started.js';
2
2
  import { NotificationMethod, ServerRequestMethod, SessionEventType, } from './protocol-types.js';
3
3
  /**
4
4
  * ACP tool kind → 常用工具名映射(信息保真,数据驱动无 agent 分支)。
@@ -68,6 +68,19 @@ function stringifyUnknown(value) {
68
68
  return String(value);
69
69
  }
70
70
  }
71
+ /**
72
+ * 记录最新值并给 map 封顶(删最老 key,近似 LRU)。
73
+ * `trackToolCallArgs` 与 `trackToolCallName` 曾是逐字相同的两段,合并到这里。
74
+ */
75
+ function trackBounded(map, key, value) {
76
+ map.delete(key);
77
+ map.set(key, value);
78
+ if (map.size > 100) {
79
+ const oldest = map.keys().next().value;
80
+ if (oldest !== undefined)
81
+ map.delete(oldest);
82
+ }
83
+ }
71
84
  // =============================================================================
72
85
  // Translator base class
73
86
  // =============================================================================
@@ -171,8 +184,9 @@ export class BaseAcpTranslator {
171
184
  * is sent (no wire notification for this — self-produced).
172
185
  */
173
186
  produceTurnStarted(sessionId, turnId) {
174
- this.currentTurnId = turnId;
175
- return makeTurnStartedEvent(sessionId, turnId, this.operationKind);
187
+ return produceTurnStartedEvent(sessionId, turnId, this.operationKind, (_sessionId, t) => {
188
+ this.currentTurnId = t;
189
+ });
176
190
  }
177
191
  approvalRequestedEvents(requestId, sessionId, view) {
178
192
  return [
@@ -406,23 +420,11 @@ export class BaseAcpTranslator {
406
420
  }
407
421
  /** Record latest args text, bounded so a long session cannot grow it forever. */
408
422
  trackToolCallArgs(toolCallId, argsText) {
409
- this.toolCallArgsByCallId.delete(toolCallId);
410
- this.toolCallArgsByCallId.set(toolCallId, argsText);
411
- if (this.toolCallArgsByCallId.size > 100) {
412
- const oldest = this.toolCallArgsByCallId.keys().next().value;
413
- if (oldest !== undefined)
414
- this.toolCallArgsByCallId.delete(oldest);
415
- }
423
+ trackBounded(this.toolCallArgsByCallId, toolCallId, argsText);
416
424
  }
417
425
  /** Record tool_use name, bounded to keep a long session from growing it. */
418
426
  trackToolCallName(toolCallId, name) {
419
- this.toolCallNameByCallId.delete(toolCallId);
420
- this.toolCallNameByCallId.set(toolCallId, name);
421
- if (this.toolCallNameByCallId.size > 100) {
422
- const oldest = this.toolCallNameByCallId.keys().next().value;
423
- if (oldest !== undefined)
424
- this.toolCallNameByCallId.delete(oldest);
425
- }
427
+ trackBounded(this.toolCallNameByCallId, toolCallId, name);
426
428
  }
427
429
  /**
428
430
  * tool_result content extraction. Default: rawOutput passthrough (kimi