@yuandc/aica 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +9 -0
  2. package/dist/acp/agent.js +54 -0
  3. package/dist/acp/client/acp-client.js +102 -0
  4. package/dist/acp/client/acp-content.js +13 -0
  5. package/dist/acp/client/acp-events.js +106 -0
  6. package/dist/acp/client/acp-process.js +34 -0
  7. package/dist/acp/client/acp-runtime-pool.js +248 -0
  8. package/dist/acp/client/context-usage.js +29 -0
  9. package/dist/acp/client/json-rpc.js +128 -0
  10. package/dist/acp/provider-types.js +1 -0
  11. package/dist/acp/providers/codex/codex-process.js +51 -0
  12. package/dist/acp/providers/codex/events.js +1473 -0
  13. package/dist/acp/providers/codex/permissions.js +49 -0
  14. package/dist/acp/providers/codex/provider.js +376 -0
  15. package/dist/acp/providers/codex-acp/adapter.js +947 -0
  16. package/dist/acp/providers/codex-acp/context-maintenance.js +148 -0
  17. package/dist/acp/providers/codex-acp/launch.js +35 -0
  18. package/dist/acp/providers/codex-acp/provider.js +486 -0
  19. package/dist/acp/providers/mimo/provider.js +448 -0
  20. package/dist/acp/providers/opencode/provider.js +489 -0
  21. package/dist/acp/providers/registry.js +23 -0
  22. package/dist/acp/standard-events.js +167 -0
  23. package/dist/commands/start.js +137 -0
  24. package/dist/commands/worker-auth.js +100 -0
  25. package/dist/commands/worker-project.js +57 -0
  26. package/dist/core/aca-config.js +74 -0
  27. package/dist/core/aca-server-client.js +57 -0
  28. package/dist/core/acp-event-coalescer.js +108 -0
  29. package/dist/core/acp-event-upload-filter.js +16 -0
  30. package/dist/core/acp-orphan-cleanup.js +91 -0
  31. package/dist/core/affected-files.js +268 -0
  32. package/dist/core/auth.js +36 -0
  33. package/dist/core/file-transfer-worker.js +169 -0
  34. package/dist/core/fs.js +28 -0
  35. package/dist/core/heartbeat.js +578 -0
  36. package/dist/core/job-permission-policy.js +42 -0
  37. package/dist/core/job-worker.js +749 -0
  38. package/dist/core/logger.js +42 -0
  39. package/dist/core/long-poll-worker.js +26 -0
  40. package/dist/core/machine-filesystem-worker.js +352 -0
  41. package/dist/core/paths.js +26 -0
  42. package/dist/core/process-identity.js +34 -0
  43. package/dist/core/process.js +33 -0
  44. package/dist/core/provider-health.js +54 -0
  45. package/dist/core/runtime-options.js +38 -0
  46. package/dist/core/worktree.js +95 -0
  47. package/dist/worker-cli.js +27 -0
  48. package/dist/worker-single-cli.js +17 -0
  49. package/package.json +35 -0
@@ -0,0 +1,1473 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { applyPatch, parsePatch, reversePatch } from "diff";
3
+ const activeFuzzyFileSearchSessions = new Set();
4
+ const activeGuardianApprovalReviews = new Set();
5
+ export function codexAcpSessionUpdatesFromNotification(method, params, options = {}) {
6
+ switch (method) {
7
+ case "item/agentMessage/delta": {
8
+ const delta = stringField(params, "delta");
9
+ const itemId = stringField(params, "itemId");
10
+ const phase = itemId ? options.agentMessagePhases?.get(itemId) : "";
11
+ if (!delta)
12
+ return [];
13
+ if (phase === "commentary")
14
+ return [createTextChunkUpdate("agent_progress_chunk", delta)];
15
+ if (phase && phase !== "final_answer")
16
+ return [];
17
+ return [createTextChunkUpdate("agent_message_chunk", delta)];
18
+ }
19
+ case "item/reasoning/summaryTextDelta":
20
+ case "item/reasoning/textDelta": {
21
+ const delta = stringField(params, "delta");
22
+ return delta ? [createTextChunkUpdate("agent_thought_chunk", delta)] : [];
23
+ }
24
+ case "turn/plan/updated":
25
+ case "item/plan/delta":
26
+ return [{
27
+ sessionUpdate: method === "item/plan/delta" ? "plan" : "plan_update",
28
+ content: {
29
+ type: "text",
30
+ text: planText(params)
31
+ }
32
+ }];
33
+ case "item/started":
34
+ case "item/completed": {
35
+ const item = objectField(params, "item");
36
+ const type = stringField(item, "type");
37
+ if (!item || !type || type === "userMessage" || type === "agentMessage" || type === "reasoning")
38
+ return [];
39
+ const normalizedToolUpdate = createNormalizedToolUpdate(method, item);
40
+ if (normalizedToolUpdate)
41
+ return [normalizedToolUpdate];
42
+ return [
43
+ method === "item/started"
44
+ ? createGenericExecuteStartUpdate(item)
45
+ : createGenericExecuteCompleteUpdate(item)
46
+ ];
47
+ }
48
+ case "item/commandExecution/outputDelta": {
49
+ const itemId = stringField(params, "itemId") || stringField(params, "id") || stringField(params, "toolCallId");
50
+ const delta = firstToolString(params, ["delta", "data", "output", "text"]);
51
+ if (!itemId && !delta)
52
+ return [];
53
+ return [{
54
+ sessionUpdate: "tool_call_update",
55
+ toolCallId: itemId,
56
+ kind: "execute",
57
+ status: "in_progress",
58
+ _meta: createTerminalOutputMeta(itemId, delta)
59
+ }];
60
+ }
61
+ case "item/commandExecution/terminalInteraction": {
62
+ const itemId = stringField(params, "itemId") || stringField(params, "id") || stringField(params, "toolCallId");
63
+ const stdin = stringField(params, "stdin");
64
+ if (!itemId)
65
+ return [];
66
+ return [{
67
+ sessionUpdate: "tool_call_update",
68
+ toolCallId: itemId,
69
+ kind: "execute",
70
+ status: "in_progress",
71
+ _meta: createTerminalOutputMeta(itemId, `\n${stdin}\n`)
72
+ }];
73
+ }
74
+ case "item/mcpToolCall/progress": {
75
+ const itemId = stringField(params, "itemId") || stringField(params, "id") || stringField(params, "toolCallId");
76
+ const message = stringField(params, "message").trim();
77
+ if (!itemId || !message)
78
+ return [];
79
+ return [{
80
+ sessionUpdate: "tool_call_update",
81
+ toolCallId: itemId,
82
+ kind: "execute",
83
+ status: "in_progress",
84
+ _meta: {
85
+ mcp_output_delta: {
86
+ data: message,
87
+ terminal_id: itemId
88
+ }
89
+ }
90
+ }];
91
+ }
92
+ case "turn/diff/updated": {
93
+ const diff = stringField(params, "diff");
94
+ if (!diff)
95
+ return [];
96
+ return [{
97
+ sessionUpdate: "tool_call_update",
98
+ toolCallId: stringField(params, "turnId") || "codex.diff",
99
+ kind: "edit",
100
+ title: "Diff updated",
101
+ status: "completed",
102
+ content: [{
103
+ type: "diff",
104
+ oldText: null,
105
+ newText: diff
106
+ }],
107
+ rawOutput: { diff }
108
+ }];
109
+ }
110
+ case "thread/tokenUsage/updated":
111
+ return [{ sessionUpdate: "usage_update", _meta: { codex: params } }];
112
+ case "thread/name/updated":
113
+ return [{
114
+ sessionUpdate: "session_info_update",
115
+ title: stringField(params, "threadName") || null
116
+ }];
117
+ case "thread/status/changed":
118
+ return [codexSessionInfoUpdate({ threadStatus: stringField(params, "status") })];
119
+ case "thread/archived":
120
+ return [codexSessionInfoUpdate({ archived: true })];
121
+ case "thread/unarchived":
122
+ return [codexSessionInfoUpdate({ archived: false })];
123
+ case "thread/closed":
124
+ return [codexSessionInfoUpdate({ closed: true })];
125
+ case "account/rateLimits/updated":
126
+ return [createRateLimitsSessionInfoUpdate(params)];
127
+ case "account/updated":
128
+ return [codexSessionInfoUpdate({ account: normalizeObject(params) ?? params })];
129
+ case "configWarning": {
130
+ const summary = stringField(params, "summary");
131
+ const details = stringField(params, "details");
132
+ return [createTextChunkUpdate("agent_message_chunk", `Config warning: ${summary}${details ? `\n\n${details}` : ""}\n\n`)];
133
+ }
134
+ case "warning":
135
+ return [createTextChunkUpdate("agent_message_chunk", `Warning: ${stringField(params, "message")}\n\n`)];
136
+ case "model/rerouted":
137
+ return [createTextChunkUpdate("agent_thought_chunk", `Model rerouted from ${stringField(params, "fromModel")} to ${stringField(params, "toModel")} (${stringField(params, "reason")}).\n\n`)];
138
+ case "thread/compacted":
139
+ return [createTextChunkUpdate("agent_message_chunk", "*Context compacted to fit the model's context window.*\n\n")];
140
+ case "fuzzyFileSearch/sessionUpdated":
141
+ return [createFuzzyFileSearchStartOrUpdate(params)];
142
+ case "fuzzyFileSearch/sessionCompleted":
143
+ return [createFuzzyFileSearchComplete(params)];
144
+ case "item/autoApprovalReview/started":
145
+ return [createGuardianApprovalReviewUpdate(params, "started")];
146
+ case "item/autoApprovalReview/completed":
147
+ return [createGuardianApprovalReviewUpdate(params, "completed")];
148
+ case "error":
149
+ return [{
150
+ sessionUpdate: "tool_call_update",
151
+ toolCallId: "codex.error",
152
+ kind: "other",
153
+ status: "failed",
154
+ title: "Codex error",
155
+ rawOutput: params
156
+ }];
157
+ default:
158
+ return [];
159
+ }
160
+ }
161
+ export function mapCodexNotification(method, params, sink, options = {}) {
162
+ switch (method) {
163
+ case "item/agentMessage/delta": {
164
+ const delta = stringField(params, "delta");
165
+ const itemId = stringField(params, "itemId");
166
+ const phase = itemId ? options.agentMessagePhases?.get(itemId) : "";
167
+ if (!delta || (phase && phase !== "commentary" && phase !== "final_answer"))
168
+ break;
169
+ const eventType = phase === "commentary" ? "agent_progress_chunk" : "agent_message_chunk";
170
+ const label = phase === "commentary" ? "progress" : "message";
171
+ sink.pushStatus("thinking", phase === "commentary" ? "处理中" : "生成回复中", textPreview(delta), method);
172
+ sink.pushUpdate({
173
+ type: eventType,
174
+ label,
175
+ text: delta,
176
+ raw: { method, params, update: createTextChunkUpdate(eventType, delta) },
177
+ atMs: Date.now()
178
+ });
179
+ break;
180
+ }
181
+ case "item/reasoning/summaryTextDelta":
182
+ case "item/reasoning/textDelta": {
183
+ const delta = stringField(params, "delta");
184
+ sink.pushStatus("thinking", "思考中", textPreview(delta), method);
185
+ sink.pushUpdate({
186
+ type: "agent_thought_chunk",
187
+ label: "thought",
188
+ ...(delta ? { text: delta } : {}),
189
+ raw: { method, params },
190
+ atMs: Date.now()
191
+ });
192
+ break;
193
+ }
194
+ case "turn/plan/updated":
195
+ case "item/plan/delta": {
196
+ sink.pushStatus("thinking", "规划中", undefined, method);
197
+ sink.pushUpdate({
198
+ type: method === "item/plan/delta" ? "plan" : "plan_update",
199
+ label: "plan",
200
+ text: planText(params),
201
+ raw: { method, params },
202
+ atMs: Date.now()
203
+ });
204
+ break;
205
+ }
206
+ case "item/started":
207
+ case "item/completed": {
208
+ const item = objectField(params, "item");
209
+ const type = stringField(item, "type");
210
+ const update = createNormalizedToolUpdate(method, item);
211
+ if (!update) {
212
+ const label = labelForItem(type);
213
+ if (type === "reasoning" || type === "agentMessage")
214
+ sink.pushStatus(phaseForItem(type), label, itemDetail(item), method);
215
+ break;
216
+ }
217
+ const tool = normalizeToolItem(method, params, item);
218
+ const label = conciseToolEventLabel(type, update);
219
+ sink.pushStatus(phaseForItem(type), liveActivityLabelForItem(type), undefined, method);
220
+ sink.pushUpdate({
221
+ type: method === "item/started" ? "tool_call" : "tool_call_update",
222
+ label,
223
+ status: firstToolString(update, ["status"]) || (method === "item/started" ? "in_progress" : "completed"),
224
+ toolCallId: stringField(item, "id"),
225
+ raw: { method, params, tool, update },
226
+ atMs: Date.now()
227
+ });
228
+ break;
229
+ }
230
+ case "item/commandExecution/outputDelta": {
231
+ const itemId = stringField(params, "itemId") || stringField(params, "id") || stringField(params, "toolCallId");
232
+ const delta = firstToolString(params, ["delta", "data", "output", "text"]);
233
+ if (!itemId && !delta)
234
+ break;
235
+ sink.pushStatus("exploring", "执行命令", undefined, method);
236
+ sink.pushUpdate({
237
+ type: "tool_call_update",
238
+ label: "tool update",
239
+ toolCallId: itemId,
240
+ raw: {
241
+ method,
242
+ params,
243
+ update: {
244
+ sessionUpdate: "tool_call_update",
245
+ toolCallId: itemId,
246
+ _meta: createTerminalOutputMeta(itemId, delta)
247
+ }
248
+ },
249
+ atMs: Date.now()
250
+ });
251
+ break;
252
+ }
253
+ case "item/commandExecution/terminalInteraction": {
254
+ const itemId = stringField(params, "itemId") || stringField(params, "id") || stringField(params, "toolCallId");
255
+ const stdin = stringField(params, "stdin");
256
+ const data = `\n${stdin}\n`;
257
+ if (!itemId)
258
+ break;
259
+ sink.pushStatus("exploring", "执行命令", undefined, method);
260
+ sink.pushUpdate({
261
+ type: "tool_call_update",
262
+ label: "tool update",
263
+ toolCallId: itemId,
264
+ raw: {
265
+ method,
266
+ params,
267
+ update: {
268
+ sessionUpdate: "tool_call_update",
269
+ toolCallId: itemId,
270
+ _meta: createTerminalOutputMeta(itemId, data)
271
+ }
272
+ },
273
+ atMs: Date.now()
274
+ });
275
+ break;
276
+ }
277
+ case "item/mcpToolCall/progress": {
278
+ const itemId = stringField(params, "itemId") || stringField(params, "id") || stringField(params, "toolCallId");
279
+ const message = stringField(params, "message").trim();
280
+ if (!itemId || !message)
281
+ break;
282
+ sink.pushStatus("exploring", "调用工具", undefined, method);
283
+ sink.pushUpdate({
284
+ type: "tool_call_update",
285
+ label: "tool update",
286
+ toolCallId: itemId,
287
+ raw: {
288
+ method,
289
+ params,
290
+ update: {
291
+ sessionUpdate: "tool_call_update",
292
+ toolCallId: itemId,
293
+ _meta: {
294
+ mcp_output_delta: {
295
+ data: message,
296
+ terminal_id: itemId
297
+ }
298
+ }
299
+ }
300
+ },
301
+ atMs: Date.now()
302
+ });
303
+ break;
304
+ }
305
+ case "turn/diff/updated":
306
+ sink.pushStatus("exploring", "编辑文件中", "diff", method);
307
+ sink.pushUpdate({
308
+ type: "diff",
309
+ label: "diff",
310
+ text: stringField(params, "diff"),
311
+ raw: { method, params },
312
+ atMs: Date.now()
313
+ });
314
+ break;
315
+ case "thread/tokenUsage/updated":
316
+ sink.pushUpdate({
317
+ type: "usage_update",
318
+ label: "usage",
319
+ raw: { method, params },
320
+ atMs: Date.now()
321
+ });
322
+ break;
323
+ case "thread/name/updated":
324
+ pushNormalizedUpdate(sink, {
325
+ method,
326
+ params,
327
+ type: "session_info_update",
328
+ label: "session_info",
329
+ update: {
330
+ sessionUpdate: "session_info_update",
331
+ title: stringField(params, "threadName") || null
332
+ }
333
+ });
334
+ break;
335
+ case "thread/status/changed":
336
+ pushCodexSessionInfoUpdate(sink, method, params, { threadStatus: stringField(params, "status") });
337
+ break;
338
+ case "thread/archived":
339
+ pushCodexSessionInfoUpdate(sink, method, params, { archived: true });
340
+ break;
341
+ case "thread/unarchived":
342
+ pushCodexSessionInfoUpdate(sink, method, params, { archived: false });
343
+ break;
344
+ case "thread/closed":
345
+ pushCodexSessionInfoUpdate(sink, method, params, { closed: true });
346
+ break;
347
+ case "account/rateLimits/updated":
348
+ pushNormalizedUpdate(sink, {
349
+ method,
350
+ params,
351
+ type: "session_info_update",
352
+ label: "rate_limit",
353
+ update: createRateLimitsSessionInfoUpdate(params)
354
+ });
355
+ break;
356
+ case "configWarning": {
357
+ const summary = stringField(params, "summary");
358
+ const details = stringField(params, "details");
359
+ const text = `Config warning: ${summary}${details ? `\n\n${details}` : ""}\n\n`;
360
+ sink.pushUpdate({
361
+ type: "agent_message_chunk",
362
+ label: "message",
363
+ text,
364
+ raw: { method, params, update: createTextChunkUpdate("agent_message_chunk", text) },
365
+ atMs: Date.now()
366
+ });
367
+ break;
368
+ }
369
+ case "warning": {
370
+ const text = `Warning: ${stringField(params, "message")}\n\n`;
371
+ sink.pushUpdate({
372
+ type: "agent_message_chunk",
373
+ label: "message",
374
+ text,
375
+ raw: { method, params, update: createTextChunkUpdate("agent_message_chunk", text) },
376
+ atMs: Date.now()
377
+ });
378
+ break;
379
+ }
380
+ case "model/rerouted": {
381
+ const text = `Model rerouted from ${stringField(params, "fromModel")} to ${stringField(params, "toModel")} (${stringField(params, "reason")}).\n\n`;
382
+ sink.pushStatus("thinking", "模型已切换", textPreview(text), method);
383
+ sink.pushUpdate({
384
+ type: "agent_thought_chunk",
385
+ label: "thought",
386
+ text,
387
+ raw: { method, params, update: createTextChunkUpdate("agent_thought_chunk", text) },
388
+ atMs: Date.now()
389
+ });
390
+ break;
391
+ }
392
+ case "thread/compacted": {
393
+ const text = "*Context compacted to fit the model's context window.*\n\n";
394
+ sink.pushStatus("thinking", "上下文已压缩", undefined, method);
395
+ sink.pushUpdate({
396
+ type: "agent_message_chunk",
397
+ label: "message",
398
+ text,
399
+ raw: { method, params, update: createTextChunkUpdate("agent_message_chunk", text) },
400
+ atMs: Date.now()
401
+ });
402
+ break;
403
+ }
404
+ case "fuzzyFileSearch/sessionUpdated": {
405
+ const update = createFuzzyFileSearchStartOrUpdate(params);
406
+ sink.pushStatus("exploring", stringField(update, "title") || "Search", fuzzySearchDetail(params), method);
407
+ sink.pushUpdate({
408
+ type: update.sessionUpdate === "tool_call" ? "tool_call" : "tool_call_update",
409
+ label: stringField(update, "title") || "Search",
410
+ status: stringField(update, "status"),
411
+ toolCallId: stringField(update, "toolCallId"),
412
+ raw: { method, params, update },
413
+ atMs: Date.now()
414
+ });
415
+ break;
416
+ }
417
+ case "fuzzyFileSearch/sessionCompleted": {
418
+ const update = createFuzzyFileSearchComplete(params);
419
+ sink.pushUpdate({
420
+ type: "tool_call_update",
421
+ label: "Search",
422
+ status: stringField(update, "status"),
423
+ toolCallId: stringField(update, "toolCallId"),
424
+ raw: { method, params, update },
425
+ atMs: Date.now()
426
+ });
427
+ break;
428
+ }
429
+ case "item/autoApprovalReview/started": {
430
+ const update = createGuardianApprovalReviewUpdate(params, "started");
431
+ sink.pushStatus("exploring", "Guardian Review", guardianReviewDetail(params), method);
432
+ sink.pushUpdate({
433
+ type: update.sessionUpdate === "tool_call" ? "tool_call" : "tool_call_update",
434
+ label: "Guardian Review",
435
+ status: stringField(update, "status"),
436
+ toolCallId: stringField(update, "toolCallId"),
437
+ raw: { method, params, update },
438
+ atMs: Date.now()
439
+ });
440
+ break;
441
+ }
442
+ case "item/autoApprovalReview/completed": {
443
+ const update = createGuardianApprovalReviewUpdate(params, "completed");
444
+ sink.pushUpdate({
445
+ type: update.sessionUpdate === "tool_call" ? "tool_call" : "tool_call_update",
446
+ label: "Guardian Review",
447
+ status: stringField(update, "status"),
448
+ toolCallId: stringField(update, "toolCallId"),
449
+ raw: { method, params, update },
450
+ atMs: Date.now()
451
+ });
452
+ break;
453
+ }
454
+ case "account/updated":
455
+ pushCodexSessionInfoUpdate(sink, method, params, { account: normalizeObject(params) ?? params });
456
+ break;
457
+ case "turn/started":
458
+ sink.pushStatus("thinking", "思考中", undefined, method);
459
+ break;
460
+ case "turn/completed":
461
+ sink.pushStatus("completed", "已完成", undefined, method);
462
+ break;
463
+ case "error":
464
+ sink.pushStatus("failed", "执行失败", textPreview(JSON.stringify(params)), method);
465
+ sink.pushUpdate({
466
+ type: "error",
467
+ label: "error",
468
+ text: textPreview(JSON.stringify(params)),
469
+ raw: { method, params },
470
+ atMs: Date.now()
471
+ });
472
+ break;
473
+ default:
474
+ break;
475
+ }
476
+ }
477
+ export function extractAssistantTextFromTurn(turn) {
478
+ const items = Array.isArray(turn?.items) ? turn.items : [];
479
+ return items
480
+ .map((item) => {
481
+ if (stringField(item, "type") !== "agentMessage")
482
+ return "";
483
+ return stringField(item, "text");
484
+ })
485
+ .filter(Boolean)
486
+ .join("\n")
487
+ .trim();
488
+ }
489
+ function labelForItem(type) {
490
+ switch (type) {
491
+ case "commandExecution":
492
+ return "执行命令中";
493
+ case "fileChange":
494
+ return "编辑文件中";
495
+ case "mcpToolCall":
496
+ case "dynamicToolCall":
497
+ return "调用工具中";
498
+ case "webSearch":
499
+ return "搜索中";
500
+ case "imageGeneration":
501
+ return "生成图片中";
502
+ case "reasoning":
503
+ return "思考中";
504
+ case "agentMessage":
505
+ return "生成回复中";
506
+ default:
507
+ return "处理中";
508
+ }
509
+ }
510
+ function liveActivityLabelForItem(type) {
511
+ switch (type) {
512
+ case "commandExecution":
513
+ return "执行命令";
514
+ case "fileChange":
515
+ return "编辑文件";
516
+ case "mcpToolCall":
517
+ case "dynamicToolCall":
518
+ return "调用工具";
519
+ case "webSearch":
520
+ case "fuzzyFileSearch":
521
+ return "搜索";
522
+ case "imageView":
523
+ return "查看图片";
524
+ case "imageGeneration":
525
+ return "生成图片";
526
+ default:
527
+ return labelForItem(type);
528
+ }
529
+ }
530
+ function conciseToolEventLabel(type, update) {
531
+ const kind = firstToolString(update, ["kind"]);
532
+ if (kind === "read")
533
+ return firstToolString(update, ["title"]) || "读取文件";
534
+ if (kind === "search")
535
+ return firstToolString(update, ["title"]) || "搜索";
536
+ if (kind === "edit")
537
+ return "编辑文件";
538
+ if (kind === "execute")
539
+ return "执行命令";
540
+ if (kind === "think")
541
+ return "思考中";
542
+ if (kind === "other")
543
+ return firstToolString(update, ["title"]) || liveActivityLabelForItem(type);
544
+ return liveActivityLabelForItem(type);
545
+ }
546
+ function phaseForItem(type) {
547
+ if (type === "imageGeneration")
548
+ return "image_generation";
549
+ if (type === "reasoning" || type === "agentMessage" || type === "plan")
550
+ return "thinking";
551
+ return "exploring";
552
+ }
553
+ function itemDetail(item) {
554
+ if (!item)
555
+ return undefined;
556
+ for (const key of ["command", "path", "tool", "server", "query", "text"]) {
557
+ const value = stringField(item, key);
558
+ if (value)
559
+ return textPreview(value);
560
+ }
561
+ return undefined;
562
+ }
563
+ function normalizeToolItem(method, params, item) {
564
+ const type = stringField(item, "type");
565
+ const id = stringField(item, "id") || stringField(params, "itemId");
566
+ const rawInput = firstToolString(item, ["rawInput", "raw_input", "command", "query", "path", "text", "input"]);
567
+ const command = type === "commandExecution" ? firstToolString(item, ["command", "rawInput", "raw_input"]) : "";
568
+ const fileChange = firstFileChange(item);
569
+ const filePath = firstToolString(item, ["path", "filePath", "file_path", "relativePath", "absolutePath"]) || fileChange.path;
570
+ const aggregatedOutput = firstToolString(item, ["aggregatedOutput", "aggregated_output", "formattedOutput", "formatted_output", "output", "stdout", "stderr"]);
571
+ const commandActions = arrayField(item, "commandActions") || arrayField(item, "command_actions") || arrayField(item, "actions") || fileChangeActions(item);
572
+ const formattedOutput = formatToolOutput(type, aggregatedOutput, item);
573
+ const displayName = toolDisplayName(type, { command, path: filePath, rawInput, item });
574
+ const summary = textPreview(firstNonEmpty(command, filePath, rawInput, aggregatedOutput));
575
+ return removeEmpty({
576
+ id,
577
+ type,
578
+ method,
579
+ displayName,
580
+ rawInput,
581
+ raw_input: rawInput,
582
+ command,
583
+ path: filePath,
584
+ action: firstToolString(item, ["action", "changeType", "operation"]) || fileChange.action,
585
+ commandActions,
586
+ command_actions: commandActions.length > 0 ? commandActions : undefined,
587
+ aggregatedOutput,
588
+ aggregated_output: aggregatedOutput,
589
+ formatted_output: formattedOutput,
590
+ summary
591
+ });
592
+ }
593
+ function createNormalizedToolUpdate(method, item) {
594
+ if (!item)
595
+ return null;
596
+ const type = stringField(item, "type");
597
+ switch (type) {
598
+ case "commandExecution":
599
+ return method === "item/started" ? createCommandExecutionStartUpdate(item) : createCommandExecutionCompleteUpdate(item);
600
+ case "fileChange":
601
+ return method === "item/started" ? createFileChangeStartUpdate(item) : createFileChangeCompleteUpdate(item);
602
+ case "mcpToolCall":
603
+ return method === "item/started" ? createMcpToolCallStartUpdate(item) : createMcpToolCallCompleteUpdate(item);
604
+ case "dynamicToolCall":
605
+ return method === "item/started" ? createDynamicToolCallStartUpdate(item) : createDynamicToolCallCompleteUpdate(item);
606
+ case "webSearch":
607
+ return method === "item/started" ? createWebSearchStartUpdate(item) : createWebSearchCompleteUpdate(item);
608
+ case "imageView":
609
+ return createImageViewUpdate(item);
610
+ case "imageGeneration":
611
+ return method === "item/started" ? createImageGenerationStartUpdate(item) : createImageGenerationCompleteUpdate(item);
612
+ default:
613
+ return null;
614
+ }
615
+ }
616
+ function pushNormalizedUpdate(sink, input) {
617
+ sink.pushUpdate({
618
+ type: input.type,
619
+ label: input.label,
620
+ raw: { method: input.method, params: input.params, update: input.update },
621
+ atMs: Date.now()
622
+ });
623
+ }
624
+ function pushCodexSessionInfoUpdate(sink, method, params, codexMetadata) {
625
+ pushNormalizedUpdate(sink, {
626
+ method,
627
+ params,
628
+ type: "session_info_update",
629
+ label: "session_info",
630
+ update: {
631
+ sessionUpdate: "session_info_update",
632
+ _meta: {
633
+ codex: codexMetadata
634
+ }
635
+ }
636
+ });
637
+ }
638
+ function codexSessionInfoUpdate(codexMetadata) {
639
+ return {
640
+ sessionUpdate: "session_info_update",
641
+ _meta: {
642
+ codex: codexMetadata
643
+ }
644
+ };
645
+ }
646
+ function createTextChunkUpdate(sessionUpdate, text) {
647
+ return {
648
+ sessionUpdate,
649
+ content: {
650
+ type: "text",
651
+ text
652
+ }
653
+ };
654
+ }
655
+ function fuzzyFileSearchToolCallId(sessionId) {
656
+ return `fuzzyFileSearch.${sessionId}`;
657
+ }
658
+ function createFuzzyFileSearchStartOrUpdate(params) {
659
+ const sessionId = stringField(params, "sessionId");
660
+ const toolCallId = fuzzyFileSearchToolCallId(sessionId);
661
+ const started = !activeFuzzyFileSearchSessions.has(toolCallId);
662
+ activeFuzzyFileSearchSessions.add(toolCallId);
663
+ const query = stringField(params, "query");
664
+ const title = searchTitle(query, "");
665
+ const locations = fuzzySearchLocations(params);
666
+ if (started) {
667
+ return removeEmpty({
668
+ sessionUpdate: "tool_call",
669
+ toolCallId,
670
+ kind: "search",
671
+ title,
672
+ status: "in_progress",
673
+ locations,
674
+ rawInput: { query }
675
+ });
676
+ }
677
+ return removeEmpty({
678
+ sessionUpdate: "tool_call_update",
679
+ toolCallId,
680
+ title,
681
+ status: "in_progress",
682
+ locations
683
+ });
684
+ }
685
+ function createFuzzyFileSearchComplete(params) {
686
+ const sessionId = stringField(params, "sessionId");
687
+ const toolCallId = fuzzyFileSearchToolCallId(sessionId);
688
+ activeFuzzyFileSearchSessions.delete(toolCallId);
689
+ return {
690
+ sessionUpdate: "tool_call_update",
691
+ toolCallId,
692
+ status: "completed"
693
+ };
694
+ }
695
+ function fuzzySearchLocations(params) {
696
+ const files = arrayField(normalizeObject(params), "files") ?? [];
697
+ const locations = files
698
+ .map((file) => {
699
+ const record = normalizeObject(file);
700
+ if (!record)
701
+ return "";
702
+ const filePath = stringField(record, "path");
703
+ const root = stringField(record, "root");
704
+ if (!filePath)
705
+ return "";
706
+ if (filePath.startsWith("/") || !root)
707
+ return filePath;
708
+ return `${root.replace(/\/+$/, "")}/${filePath.replace(/^\/+/, "")}`;
709
+ })
710
+ .filter(Boolean)
711
+ .map((path) => ({ path }));
712
+ return locations.length > 0 ? locations : undefined;
713
+ }
714
+ function fuzzySearchDetail(params) {
715
+ const query = stringField(params, "query");
716
+ const files = arrayField(normalizeObject(params), "files") ?? [];
717
+ const count = files.length;
718
+ if (query && count)
719
+ return `${query} · ${count} files`;
720
+ if (query)
721
+ return query;
722
+ if (count)
723
+ return `${count} files`;
724
+ return undefined;
725
+ }
726
+ function guardianApprovalReviewToolCallId(reviewId) {
727
+ return `guardian_assessment:${reviewId}`;
728
+ }
729
+ function createGuardianApprovalReviewUpdate(params, phase) {
730
+ const reviewId = stringField(params, "reviewId");
731
+ const toolCallId = guardianApprovalReviewToolCallId(reviewId);
732
+ const started = phase === "started" && !activeGuardianApprovalReviews.has(reviewId);
733
+ if (phase === "started")
734
+ activeGuardianApprovalReviews.add(reviewId);
735
+ if (phase === "completed")
736
+ activeGuardianApprovalReviews.delete(reviewId);
737
+ const review = objectField(params, "review") ?? {};
738
+ const update = {
739
+ sessionUpdate: started ? "tool_call" : "tool_call_update",
740
+ toolCallId,
741
+ status: toAcpGuardianApprovalReviewStatus(stringField(review, "status")),
742
+ content: createGuardianApprovalReviewContent(review, objectField(params, "action")),
743
+ ...(started ? { kind: "think", title: "Guardian Review", rawInput: params } : { rawOutput: params })
744
+ };
745
+ return removeEmpty(update);
746
+ }
747
+ function toAcpGuardianApprovalReviewStatus(status) {
748
+ switch (status) {
749
+ case "inProgress":
750
+ return "in_progress";
751
+ case "approved":
752
+ return "completed";
753
+ case "denied":
754
+ case "aborted":
755
+ case "timedOut":
756
+ return "failed";
757
+ default:
758
+ return status || "in_progress";
759
+ }
760
+ }
761
+ function createGuardianApprovalReviewContent(review, action) {
762
+ const lines = [`Status: ${formatGuardianApprovalReviewStatus(stringField(review, "status"))}`];
763
+ const actionSummary = createGuardianApprovalReviewActionSummary(action);
764
+ if (actionSummary)
765
+ lines.push(`Action: ${actionSummary}`);
766
+ const riskLevel = stringField(review, "riskLevel");
767
+ if (riskLevel)
768
+ lines.push(`Risk: ${riskLevel}`);
769
+ const userAuthorization = stringField(review, "userAuthorization");
770
+ if (userAuthorization)
771
+ lines.push(`Authorization: ${userAuthorization}`);
772
+ const rationale = stringField(review, "rationale").trim();
773
+ if (rationale)
774
+ lines.push(`Rationale: ${rationale}`);
775
+ return [{
776
+ type: "content",
777
+ content: {
778
+ type: "text",
779
+ text: lines.join("\n")
780
+ }
781
+ }];
782
+ }
783
+ function formatGuardianApprovalReviewStatus(status) {
784
+ switch (status) {
785
+ case "inProgress":
786
+ return "In progress";
787
+ case "approved":
788
+ return "Approved";
789
+ case "denied":
790
+ return "Denied";
791
+ case "aborted":
792
+ return "Aborted";
793
+ case "timedOut":
794
+ return "Timed out";
795
+ default:
796
+ return status || "Unknown";
797
+ }
798
+ }
799
+ function createGuardianApprovalReviewActionSummary(action) {
800
+ if (!action)
801
+ return "";
802
+ const type = stringField(action, "type");
803
+ if (type === "command")
804
+ return [guardianCommandSourceLabel(stringField(action, "source")), stringField(action, "command")].filter(Boolean).join(" ");
805
+ if (type === "execve") {
806
+ const argv = arrayField(action, "argv")?.map((item) => stringifyToolValue(item)).filter(Boolean) ?? [];
807
+ const program = stringField(action, "program");
808
+ const command = argv.length > 0 ? argv : [program].filter(Boolean);
809
+ return [guardianCommandSourceLabel(stringField(action, "source")), shellJoin(command)].filter(Boolean).join(" ");
810
+ }
811
+ if (type === "applyPatch") {
812
+ const files = arrayField(action, "files")?.map((item) => stringifyToolValue(item)).filter(Boolean) ?? [];
813
+ return files.length === 1 ? `apply_patch touching ${files[0]}` : `apply_patch touching ${files.length} files`;
814
+ }
815
+ if (type === "networkAccess") {
816
+ const label = stringField(action, "target") || stringField(action, "host");
817
+ return label ? `network access to ${label}` : "network access";
818
+ }
819
+ if (type === "mcpToolCall") {
820
+ const label = stringField(action, "connectorName") || stringField(action, "server");
821
+ const toolName = stringField(action, "toolName");
822
+ return `MCP ${toolName} on ${label}`.trim();
823
+ }
824
+ if (type === "requestPermissions")
825
+ return stringField(action, "reason") || "request additional permissions";
826
+ return type;
827
+ }
828
+ function guardianCommandSourceLabel(source) {
829
+ if (source === "shell")
830
+ return "shell";
831
+ if (source === "unifiedExec")
832
+ return "exec";
833
+ return source;
834
+ }
835
+ function shellJoin(args) {
836
+ return args.map(shellQuote).join(" ");
837
+ }
838
+ function shellQuote(arg) {
839
+ if (arg.length === 0)
840
+ return "''";
841
+ if (/^[A-Za-z0-9_/:=+.,@%-]+$/.test(arg))
842
+ return arg;
843
+ return `'${arg.replace(/'/g, `'\\''`)}'`;
844
+ }
845
+ function guardianReviewDetail(params) {
846
+ const review = objectField(params, "review");
847
+ const status = review ? formatGuardianApprovalReviewStatus(stringField(review, "status")) : "";
848
+ const action = createGuardianApprovalReviewActionSummary(objectField(params, "action"));
849
+ return [status, action].filter(Boolean).join(" · ") || undefined;
850
+ }
851
+ function createRateLimitsSessionInfoUpdate(params) {
852
+ const rateLimits = objectField(params, "rateLimits") ?? {};
853
+ const primary = objectField(rateLimits, "primary");
854
+ const secondary = objectField(rateLimits, "secondary");
855
+ return removeEmpty({
856
+ sessionUpdate: "session_info_update",
857
+ _meta: {
858
+ rateLimits: removeEmpty({
859
+ planName: stringField(rateLimits, "planType"),
860
+ limitName: stringField(rateLimits, "limitName"),
861
+ limitId: stringField(rateLimits, "limitId"),
862
+ fiveHour: numberField(primary, "usedPercent") ?? null,
863
+ sevenDay: numberField(secondary, "usedPercent") ?? null,
864
+ fiveHourResetAt: stringField(primary, "resetsAt") || null,
865
+ sevenDayResetAt: stringField(secondary, "resetsAt") || null,
866
+ raw: rateLimits
867
+ })
868
+ }
869
+ });
870
+ }
871
+ function createCommandExecutionStartUpdate(item) {
872
+ const id = stringField(item, "id");
873
+ const cwd = stringField(item, "cwd");
874
+ const command = stringField(item, "command");
875
+ const actions = commandActions(item);
876
+ const action = actions.length === 1 ? normalizeObject(actions[0]) : null;
877
+ if (action) {
878
+ const actionType = stringField(action, "type");
879
+ if (actionType === "read") {
880
+ const path = stringField(action, "path") || stringField(action, "name");
881
+ return removeEmpty({
882
+ sessionUpdate: "tool_call",
883
+ toolCallId: id,
884
+ status: toAcpStatus(stringField(item, "status")),
885
+ kind: "read",
886
+ title: `Read file '${path}'`,
887
+ locations: path ? [{ path }] : undefined
888
+ });
889
+ }
890
+ if (actionType === "search") {
891
+ const query = stringField(action, "query");
892
+ const path = stringField(action, "path");
893
+ return removeEmpty({
894
+ sessionUpdate: "tool_call",
895
+ toolCallId: id,
896
+ status: toAcpStatus(stringField(item, "status")),
897
+ kind: "search",
898
+ title: searchTitle(query, path)
899
+ });
900
+ }
901
+ if (actionType === "listFiles") {
902
+ const path = stringField(action, "path");
903
+ return removeEmpty({
904
+ sessionUpdate: "tool_call",
905
+ toolCallId: id,
906
+ status: toAcpStatus(stringField(item, "status")),
907
+ kind: "read",
908
+ title: path ? `List files in '${path}'` : "List files"
909
+ });
910
+ }
911
+ }
912
+ return createTerminalCommandUpdate({
913
+ sessionUpdate: "tool_call",
914
+ toolCallId: id,
915
+ status: toAcpStatus(stringField(item, "status")),
916
+ kind: "execute",
917
+ title: stripShellPrefix(command),
918
+ rawInput: removeEmpty({ command, cwd })
919
+ }, id, cwd);
920
+ }
921
+ function createCommandExecutionCompleteUpdate(item) {
922
+ const id = stringField(item, "id");
923
+ const output = firstToolString(item, ["aggregatedOutput", "aggregated_output", "formattedOutput", "formatted_output", "output", "stdout", "stderr"]);
924
+ const exitCode = numberField(item, "exitCode");
925
+ const update = {
926
+ sessionUpdate: "tool_call_update",
927
+ toolCallId: id,
928
+ status: toAcpStatus(stringField(item, "status")),
929
+ rawOutput: removeEmpty({
930
+ formatted_output: output,
931
+ exit_code: exitCode
932
+ })
933
+ };
934
+ if (commandExecutionUsesTerminalOutput(item)) {
935
+ update._meta = {
936
+ ...(output ? createTerminalOutputMeta(id, output) : {}),
937
+ terminal_exit: {
938
+ exit_code: exitCode,
939
+ signal: null,
940
+ terminal_id: id
941
+ }
942
+ };
943
+ }
944
+ return removeEmpty(update);
945
+ }
946
+ function createFileChangeStartUpdate(item) {
947
+ const id = stringField(item, "id");
948
+ const content = fileChangeContent(item);
949
+ return removeEmpty({
950
+ sessionUpdate: "tool_call",
951
+ toolCallId: id,
952
+ title: "Editing files",
953
+ kind: "edit",
954
+ status: toAcpStatus(stringField(item, "status")),
955
+ content,
956
+ locations: fileChangeLocations(item),
957
+ rawInput: { changes: fileChangeActions(item) }
958
+ });
959
+ }
960
+ function createFileChangeCompleteUpdate(item) {
961
+ return removeEmpty({
962
+ sessionUpdate: "tool_call_update",
963
+ toolCallId: stringField(item, "id"),
964
+ status: toAcpStatus(stringField(item, "status")),
965
+ content: fileChangeContent(item)
966
+ });
967
+ }
968
+ function createGenericExecuteStartUpdate(item) {
969
+ // `id` is an opaque Codex tool-call identifier, never a user-facing title.
970
+ // The specialised mappings above cover the tool types whose title can be
971
+ // derived reliably; an unknown item without metadata should remain generic.
972
+ const title = firstToolString(item, ["tool", "name", "server"]);
973
+ return removeEmpty({
974
+ sessionUpdate: "tool_call",
975
+ toolCallId: stringField(item, "id"),
976
+ kind: "execute",
977
+ title,
978
+ status: toAcpStatus(stringField(item, "status")),
979
+ rawInput: normalizeObject(item)
980
+ });
981
+ }
982
+ function createGenericExecuteCompleteUpdate(item) {
983
+ return removeEmpty({
984
+ sessionUpdate: "tool_call_update",
985
+ toolCallId: stringField(item, "id"),
986
+ status: toAcpStatus(stringField(item, "status")),
987
+ rawOutput: normalizeObject(item)
988
+ });
989
+ }
990
+ function createMcpToolCallStartUpdate(item) {
991
+ const server = stringField(item, "server");
992
+ const tool = stringField(item, "tool");
993
+ return removeEmpty({
994
+ ...createExecuteToolCallUpdate(item, `mcp.${server}.${tool}`, createMcpRawInput(server, tool, item.arguments), createMcpRawOutput(item.result, item.error)),
995
+ _meta: { is_mcp_tool_call: true }
996
+ });
997
+ }
998
+ function createMcpToolCallCompleteUpdate(item) {
999
+ const server = stringField(item, "server");
1000
+ const tool = stringField(item, "tool");
1001
+ return removeEmpty({
1002
+ sessionUpdate: "tool_call_update",
1003
+ toolCallId: stringField(item, "id"),
1004
+ status: toAcpStatus(stringField(item, "status")),
1005
+ rawInput: createMcpRawInput(server, tool, item.arguments),
1006
+ rawOutput: createMcpRawOutput(item.result, item.error)
1007
+ });
1008
+ }
1009
+ function createDynamicToolCallStartUpdate(item) {
1010
+ return createExecuteToolCallUpdate(item, stringField(item, "tool"), { arguments: item.arguments }, undefined);
1011
+ }
1012
+ function createDynamicToolCallCompleteUpdate(item) {
1013
+ return removeEmpty({
1014
+ sessionUpdate: "tool_call_update",
1015
+ toolCallId: stringField(item, "id"),
1016
+ status: toAcpStatus(stringField(item, "status"))
1017
+ });
1018
+ }
1019
+ function createExecuteToolCallUpdate(item, title, rawInput, rawOutput) {
1020
+ return removeEmpty({
1021
+ sessionUpdate: "tool_call",
1022
+ toolCallId: stringField(item, "id"),
1023
+ kind: "execute",
1024
+ title,
1025
+ status: toAcpStatus(stringField(item, "status")),
1026
+ rawInput,
1027
+ rawOutput
1028
+ });
1029
+ }
1030
+ function createMcpRawInput(server, tool, argumentsValue) {
1031
+ return {
1032
+ server,
1033
+ tool,
1034
+ arguments: argumentsValue
1035
+ };
1036
+ }
1037
+ function createMcpRawOutput(result, error) {
1038
+ if (result === null && error === null)
1039
+ return undefined;
1040
+ if (result === undefined && error === undefined)
1041
+ return undefined;
1042
+ return { result, error };
1043
+ }
1044
+ function createWebSearchStartUpdate(item) {
1045
+ return removeEmpty({
1046
+ sessionUpdate: "tool_call",
1047
+ toolCallId: stringField(item, "id"),
1048
+ kind: "search",
1049
+ title: webSearchTitle(item),
1050
+ status: toAcpStatus(stringField(item, "status")),
1051
+ rawInput: normalizeObject(item)
1052
+ });
1053
+ }
1054
+ function createWebSearchCompleteUpdate(item) {
1055
+ return removeEmpty({
1056
+ sessionUpdate: "tool_call_update",
1057
+ toolCallId: stringField(item, "id"),
1058
+ title: webSearchTitle(item),
1059
+ status: toAcpStatus(stringField(item, "status")),
1060
+ rawInput: normalizeObject(item)
1061
+ });
1062
+ }
1063
+ function createImageGenerationStartUpdate(item) {
1064
+ return removeEmpty({
1065
+ sessionUpdate: "tool_call",
1066
+ toolCallId: stringField(item, "id"),
1067
+ kind: "other",
1068
+ title: "Image generation",
1069
+ status: "in_progress",
1070
+ rawInput: {
1071
+ id: stringField(item, "id")
1072
+ }
1073
+ });
1074
+ }
1075
+ function createImageGenerationCompleteUpdate(item) {
1076
+ return removeEmpty({
1077
+ sessionUpdate: "tool_call_update",
1078
+ toolCallId: stringField(item, "id"),
1079
+ status: imageGenerationToolStatus(stringField(item, "status")),
1080
+ content: imageGenerationContent(item),
1081
+ rawOutput: imageGenerationRawOutput(item)
1082
+ });
1083
+ }
1084
+ function createImageViewUpdate(item) {
1085
+ const displayPath = stringField(item, "path");
1086
+ return removeEmpty({
1087
+ sessionUpdate: "tool_call",
1088
+ toolCallId: stringField(item, "id"),
1089
+ kind: "read",
1090
+ title: `View Image ${displayPath}`,
1091
+ status: "completed",
1092
+ content: [{
1093
+ type: "content",
1094
+ content: {
1095
+ type: "resource_link",
1096
+ name: displayPath,
1097
+ uri: displayPath
1098
+ }
1099
+ }],
1100
+ locations: [{ path: displayPath }],
1101
+ rawInput: { path: displayPath }
1102
+ });
1103
+ }
1104
+ function imageGenerationToolStatus(status) {
1105
+ if (status === "completed")
1106
+ return "completed";
1107
+ if (status === "failed")
1108
+ return "failed";
1109
+ return "in_progress";
1110
+ }
1111
+ function imageGenerationContent(item) {
1112
+ const result = normalizeObject(item.result);
1113
+ const url = firstToolString(result, ["url", "uri", "path"]);
1114
+ if (!url)
1115
+ return undefined;
1116
+ return [{
1117
+ type: "content",
1118
+ content: {
1119
+ type: "resource_link",
1120
+ name: url,
1121
+ uri: url
1122
+ }
1123
+ }];
1124
+ }
1125
+ function imageGenerationRawOutput(item) {
1126
+ const output = {
1127
+ status: item.status,
1128
+ revisedPrompt: item.revisedPrompt,
1129
+ result: item.result
1130
+ };
1131
+ if ("savedPath" in item)
1132
+ output.savedPath = item.savedPath ?? null;
1133
+ return output;
1134
+ }
1135
+ function createTerminalCommandUpdate(update, terminalId, cwd) {
1136
+ return removeEmpty({
1137
+ ...update,
1138
+ content: [{ type: "terminal", terminalId }],
1139
+ _meta: {
1140
+ terminal_info: {
1141
+ cwd,
1142
+ terminal_id: terminalId
1143
+ }
1144
+ }
1145
+ });
1146
+ }
1147
+ function commandExecutionUsesTerminalOutput(item) {
1148
+ const actions = commandActions(item);
1149
+ const action = actions.length === 1 ? normalizeObject(actions[0]) : null;
1150
+ return !action || stringField(action, "type") === "unknown";
1151
+ }
1152
+ function commandActions(item) {
1153
+ return arrayField(item, "commandActions") || arrayField(item, "command_actions") || arrayField(item, "actions") || [];
1154
+ }
1155
+ function toAcpStatus(status) {
1156
+ switch (status) {
1157
+ case "inProgress":
1158
+ case "in_progress":
1159
+ case "started":
1160
+ case "":
1161
+ return "in_progress";
1162
+ case "completed":
1163
+ return "completed";
1164
+ case "failed":
1165
+ case "declined":
1166
+ case "cancelled":
1167
+ return "failed";
1168
+ default:
1169
+ return status;
1170
+ }
1171
+ }
1172
+ function stripShellPrefix(command) {
1173
+ const withoutShell = command.replace(/^(?:\/bin\/)?(?:bash|zsh|sh)\s+(?:-[lc]+\s+)?/, "");
1174
+ if (withoutShell.startsWith("'") && withoutShell.endsWith("'")) {
1175
+ return withoutShell.slice(1, -1);
1176
+ }
1177
+ return withoutShell;
1178
+ }
1179
+ function createTerminalOutputMeta(terminalId, data) {
1180
+ return {
1181
+ terminal_output_delta: {
1182
+ data,
1183
+ terminal_id: terminalId
1184
+ }
1185
+ };
1186
+ }
1187
+ function searchTitle(query, path) {
1188
+ if (query && path)
1189
+ return `Search for '${query}' in ${path}`;
1190
+ if (query)
1191
+ return `Search for '${query}'`;
1192
+ if (path)
1193
+ return `Search in '${path}'`;
1194
+ return "Search";
1195
+ }
1196
+ function webSearchTitle(item) {
1197
+ const action = normalizeObject(item.action);
1198
+ if (!action) {
1199
+ const query = stringField(item, "query");
1200
+ return query ? `Web search: ${query}` : "Web search";
1201
+ }
1202
+ const actionType = stringField(action, "type");
1203
+ if (actionType === "search") {
1204
+ const queries = arrayField(action, "queries")?.map((query) => stringifyToolValue(query)).filter(Boolean) ?? [];
1205
+ const query = stringField(action, "query") || (queries.length > 0 ? queries.join(", ") : "") || stringField(item, "query");
1206
+ return query ? `Web search: ${query}` : "Web search";
1207
+ }
1208
+ if (actionType === "openPage") {
1209
+ const url = stringField(action, "url");
1210
+ return url ? `Open page: ${url}` : "Open page";
1211
+ }
1212
+ if (actionType === "findInPage") {
1213
+ const pattern = stringField(action, "pattern") ? ` for '${stringField(action, "pattern")}'` : "";
1214
+ const url = stringField(action, "url") ? ` in ${stringField(action, "url")}` : "";
1215
+ return `Find in page${pattern}${url}`.trim();
1216
+ }
1217
+ if (actionType === "other")
1218
+ return "Web search";
1219
+ return "Web search";
1220
+ }
1221
+ function fileChangeLocations(item) {
1222
+ const locations = fileChangeActions(item)
1223
+ .map((action) => normalizeObject(action))
1224
+ .map((action) => stringField(action, "path"))
1225
+ .filter(Boolean)
1226
+ .map((path) => ({ path }));
1227
+ return locations.length > 0 ? locations : undefined;
1228
+ }
1229
+ function fileChangeContent(item) {
1230
+ const changes = arrayField(item, "changes") ?? [];
1231
+ const content = changes
1232
+ .map((change) => {
1233
+ const record = normalizeObject(change);
1234
+ if (!record)
1235
+ return null;
1236
+ const path = stringifyToolValue(record.path);
1237
+ const kind = record.kind && typeof record.kind === "object" && !Array.isArray(record.kind)
1238
+ ? stringifyToolValue(record.kind.type)
1239
+ : stringifyToolValue(record.kind);
1240
+ const diff = stringifyToolValue(record.diff);
1241
+ if (kind === "add") {
1242
+ return removeEmpty({
1243
+ type: "diff",
1244
+ oldText: null,
1245
+ newText: diff,
1246
+ path,
1247
+ _meta: { kind: "add" }
1248
+ });
1249
+ }
1250
+ if (kind === "delete") {
1251
+ return removeEmpty({
1252
+ type: "diff",
1253
+ oldText: diff,
1254
+ newText: "",
1255
+ path,
1256
+ _meta: { kind: "delete" }
1257
+ });
1258
+ }
1259
+ return createUpdateFileContent(record, path, diff, kind);
1260
+ })
1261
+ .filter(Boolean);
1262
+ return content.length > 0 ? content : undefined;
1263
+ }
1264
+ function createUpdateFileContent(change, path, diff, kind) {
1265
+ const movePath = change.kind && typeof change.kind === "object" && !Array.isArray(change.kind)
1266
+ ? stringifyToolValue(change.kind.move_path)
1267
+ : "";
1268
+ const unifiedDiff = recoverCorruptedDiff(diff);
1269
+ const oldContent = readTextFile(path);
1270
+ if (oldContent !== null) {
1271
+ const patchedContent = safeApplyPatch(oldContent, unifiedDiff);
1272
+ if (patchedContent !== false) {
1273
+ return createUpdateDiffContent(movePath || path, oldContent, patchedContent, kind || "update");
1274
+ }
1275
+ const revertedPatch = revertPatch(unifiedDiff);
1276
+ if (revertedPatch) {
1277
+ const revertedContent = safeApplyPatch(oldContent, revertedPatch);
1278
+ if (revertedContent !== false) {
1279
+ return createUpdateDiffContent(path, revertedContent, oldContent, kind || "update");
1280
+ }
1281
+ }
1282
+ }
1283
+ if (movePath) {
1284
+ const newContent = readTextFile(movePath);
1285
+ const revertedPatch = revertPatch(unifiedDiff);
1286
+ if (newContent !== null && revertedPatch) {
1287
+ const revertedContent = safeApplyPatch(newContent, revertedPatch);
1288
+ if (revertedContent !== false) {
1289
+ return createUpdateDiffContent(movePath, revertedContent, newContent, kind || "update");
1290
+ }
1291
+ }
1292
+ }
1293
+ return createUpdateDiffContent(movePath || path, null, unifiedDiff, kind || "update");
1294
+ }
1295
+ function createUpdateDiffContent(path, oldText, newText, kind) {
1296
+ return removeEmpty({
1297
+ type: "diff",
1298
+ oldText,
1299
+ newText,
1300
+ path,
1301
+ _meta: { kind }
1302
+ });
1303
+ }
1304
+ function readTextFile(filePath) {
1305
+ try {
1306
+ return readFileSync(filePath, "utf8");
1307
+ }
1308
+ catch {
1309
+ return null;
1310
+ }
1311
+ }
1312
+ function recoverCorruptedDiff(diff) {
1313
+ return diff.replace(/\n\nMoved to: .*$/, "");
1314
+ }
1315
+ function revertPatch(unifiedDiff) {
1316
+ try {
1317
+ const [patch] = parsePatch(unifiedDiff);
1318
+ if (!patch)
1319
+ return null;
1320
+ return reversePatch(patch);
1321
+ }
1322
+ catch {
1323
+ return null;
1324
+ }
1325
+ }
1326
+ function safeApplyPatch(source, patch) {
1327
+ try {
1328
+ return applyPatch(source, patch);
1329
+ }
1330
+ catch {
1331
+ return false;
1332
+ }
1333
+ }
1334
+ function toolDisplayName(type, context) {
1335
+ if (type === "commandExecution")
1336
+ return "执行命令";
1337
+ if (type === "fileChange")
1338
+ return context.path ? `编辑文件: ${shortPath(context.path)}` : "编辑文件";
1339
+ if (type === "webSearch")
1340
+ return context.rawInput ? `搜索: ${textPreview(context.rawInput)}` : "搜索";
1341
+ if (type === "mcpToolCall" || type === "dynamicToolCall") {
1342
+ const tool = firstToolString(context.item, ["tool", "name", "server"]);
1343
+ return tool ? `调用工具: ${tool}` : "调用工具";
1344
+ }
1345
+ return labelForItem(type);
1346
+ }
1347
+ function formatToolOutput(type, aggregatedOutput, item) {
1348
+ if (aggregatedOutput)
1349
+ return aggregatedOutput;
1350
+ if (type === "fileChange") {
1351
+ const fileChange = firstFileChange(item);
1352
+ const filePath = firstToolString(item, ["path", "filePath", "file_path", "relativePath", "absolutePath"]) || fileChange.path;
1353
+ const action = firstToolString(item, ["action", "changeType", "operation"]) || fileChange.action;
1354
+ return [action, filePath].filter(Boolean).join(" ");
1355
+ }
1356
+ return "";
1357
+ }
1358
+ function firstFileChange(item) {
1359
+ const changes = arrayField(item, "changes") ?? [];
1360
+ for (const change of changes) {
1361
+ if (!change || typeof change !== "object" || Array.isArray(change))
1362
+ continue;
1363
+ const record = change;
1364
+ const path = stringifyToolValue(record.path);
1365
+ const kind = record.kind && typeof record.kind === "object" && !Array.isArray(record.kind)
1366
+ ? stringifyToolValue(record.kind.type)
1367
+ : stringifyToolValue(record.kind);
1368
+ if (path || kind)
1369
+ return { path, action: kind };
1370
+ }
1371
+ return { path: "", action: "" };
1372
+ }
1373
+ function fileChangeActions(item) {
1374
+ const changes = arrayField(item, "changes") ?? [];
1375
+ return changes
1376
+ .map((change) => {
1377
+ if (!change || typeof change !== "object" || Array.isArray(change))
1378
+ return null;
1379
+ const record = change;
1380
+ const path = stringifyToolValue(record.path);
1381
+ const kind = record.kind && typeof record.kind === "object" && !Array.isArray(record.kind)
1382
+ ? stringifyToolValue(record.kind.type)
1383
+ : stringifyToolValue(record.kind);
1384
+ if (!path && !kind)
1385
+ return null;
1386
+ return removeEmpty({ type: kind || "edit", path });
1387
+ })
1388
+ .filter(Boolean)
1389
+ .slice(0, 50);
1390
+ }
1391
+ function firstToolString(item, keys) {
1392
+ if (!item)
1393
+ return "";
1394
+ for (const key of keys) {
1395
+ const value = stringifyToolValue(item[key]);
1396
+ if (value)
1397
+ return value;
1398
+ }
1399
+ return "";
1400
+ }
1401
+ function stringifyToolValue(value) {
1402
+ if (typeof value === "string" && value.trim())
1403
+ return value.trim();
1404
+ if (typeof value === "number" || typeof value === "boolean")
1405
+ return String(value);
1406
+ if (value && typeof value === "object") {
1407
+ try {
1408
+ const json = JSON.stringify(value);
1409
+ return json.length > 2 ? json : "";
1410
+ }
1411
+ catch {
1412
+ return "";
1413
+ }
1414
+ }
1415
+ return "";
1416
+ }
1417
+ function arrayField(value, key) {
1418
+ const field = value?.[key];
1419
+ return Array.isArray(field) ? field.slice(0, 50) : null;
1420
+ }
1421
+ function firstNonEmpty(...values) {
1422
+ return values.find((value) => value.trim()) || "";
1423
+ }
1424
+ function shortPath(value) {
1425
+ return value.replace(/\\/g, "/").split("/").filter(Boolean).pop() || value;
1426
+ }
1427
+ function removeEmpty(value) {
1428
+ for (const key of Object.keys(value)) {
1429
+ const item = value[key];
1430
+ if (item === undefined || item === "" || (Array.isArray(item) && item.length === 0))
1431
+ delete value[key];
1432
+ }
1433
+ return value;
1434
+ }
1435
+ function planText(params) {
1436
+ const explanation = stringField(params, "explanation");
1437
+ const plan = Array.isArray(params?.plan) ? params.plan : [];
1438
+ const steps = plan.map((item) => stringField(item, "text")).filter(Boolean);
1439
+ return [explanation, ...steps].filter(Boolean).join("\n") || undefined;
1440
+ }
1441
+ function objectField(value, key) {
1442
+ if (!value || typeof value !== "object" || Array.isArray(value))
1443
+ return null;
1444
+ const nested = value[key];
1445
+ if (!nested || typeof nested !== "object" || Array.isArray(nested))
1446
+ return null;
1447
+ return nested;
1448
+ }
1449
+ function normalizeObject(value) {
1450
+ if (!value || typeof value !== "object" || Array.isArray(value))
1451
+ return null;
1452
+ return value;
1453
+ }
1454
+ function stringField(value, key) {
1455
+ if (!value || typeof value !== "object" || Array.isArray(value))
1456
+ return "";
1457
+ const field = value[key];
1458
+ return typeof field === "string" ? field : "";
1459
+ }
1460
+ function numberField(value, key) {
1461
+ if (!value || typeof value !== "object" || Array.isArray(value))
1462
+ return undefined;
1463
+ const field = value[key];
1464
+ return typeof field === "number" && Number.isFinite(field) ? field : undefined;
1465
+ }
1466
+ function textPreview(value) {
1467
+ if (typeof value !== "string")
1468
+ return undefined;
1469
+ const normalized = value.replace(/\s+/g, " ").trim();
1470
+ if (!normalized)
1471
+ return undefined;
1472
+ return normalized.length > 120 ? `${normalized.slice(0, 117)}...` : normalized;
1473
+ }