@agentclientprotocol/claude-agent-acp 0.81.3-preview.1 → 0.82.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 (95) hide show
  1. package/README.md +8 -5
  2. package/dist/acp-agent.d.ts +31 -37
  3. package/dist/acp-agent.d.ts.map +1 -1
  4. package/dist/acp-agent.js +546 -534
  5. package/dist/acp-subagents.d.ts +6 -0
  6. package/dist/acp-subagents.d.ts.map +1 -1
  7. package/dist/air-extension.d.ts +31 -1
  8. package/dist/air-extension.d.ts.map +1 -1
  9. package/dist/air-extension.js +35 -1
  10. package/dist/async-tasks.d.ts +39 -2
  11. package/dist/async-tasks.d.ts.map +1 -1
  12. package/dist/async-tasks.js +147 -44
  13. package/dist/context-compaction-meta.d.ts +6 -5
  14. package/dist/context-compaction-meta.d.ts.map +1 -1
  15. package/dist/context-compaction-meta.js +10 -10
  16. package/dist/context-compaction.d.ts +3 -0
  17. package/dist/context-compaction.d.ts.map +1 -1
  18. package/dist/context-compaction.js +23 -57
  19. package/dist/diff.d.ts +73 -5
  20. package/dist/diff.d.ts.map +1 -1
  21. package/dist/diff.js +435 -64
  22. package/dist/elicitation.d.ts +1 -1
  23. package/dist/elicitation.d.ts.map +1 -1
  24. package/dist/elicitation.js +12 -14
  25. package/dist/native-subagents.d.ts +58 -0
  26. package/dist/native-subagents.d.ts.map +1 -1
  27. package/dist/native-subagents.js +186 -34
  28. package/dist/paths.d.ts +3 -0
  29. package/dist/paths.d.ts.map +1 -0
  30. package/dist/paths.js +6 -0
  31. package/dist/permissions/presentation.d.ts +13 -2
  32. package/dist/permissions/presentation.d.ts.map +1 -1
  33. package/dist/permissions/presentation.js +49 -44
  34. package/dist/resumed-session.d.ts +11 -0
  35. package/dist/resumed-session.d.ts.map +1 -1
  36. package/dist/resumed-session.js +106 -4
  37. package/dist/session-mode.d.ts +2 -0
  38. package/dist/session-mode.d.ts.map +1 -1
  39. package/dist/session-mode.js +12 -5
  40. package/dist/settings.js +2 -2
  41. package/dist/subagent-history.d.ts +20 -0
  42. package/dist/subagent-history.d.ts.map +1 -0
  43. package/dist/subagent-history.js +46 -0
  44. package/dist/tool-calls/background.d.ts +20 -0
  45. package/dist/tool-calls/background.d.ts.map +1 -0
  46. package/dist/tool-calls/background.js +41 -0
  47. package/dist/tool-calls/changed-meta-filter.d.ts +27 -0
  48. package/dist/tool-calls/changed-meta-filter.d.ts.map +1 -0
  49. package/dist/tool-calls/changed-meta-filter.js +102 -0
  50. package/dist/tool-calls/client-capabilities.d.ts +51 -0
  51. package/dist/tool-calls/client-capabilities.d.ts.map +1 -0
  52. package/dist/tool-calls/client-capabilities.js +43 -0
  53. package/dist/tool-calls/content.d.ts +59 -0
  54. package/dist/tool-calls/content.d.ts.map +1 -0
  55. package/dist/tool-calls/content.js +194 -0
  56. package/dist/tool-calls/facts.d.ts +116 -0
  57. package/dist/tool-calls/facts.d.ts.map +1 -0
  58. package/dist/tool-calls/facts.js +1 -0
  59. package/dist/tool-calls/field-tracker.d.ts +58 -0
  60. package/dist/tool-calls/field-tracker.d.ts.map +1 -0
  61. package/dist/tool-calls/field-tracker.js +167 -0
  62. package/dist/tool-calls/renderer.d.ts +216 -0
  63. package/dist/tool-calls/renderer.d.ts.map +1 -0
  64. package/dist/tool-calls/renderer.js +487 -0
  65. package/dist/tool-calls/reporters/agent.d.ts +7 -0
  66. package/dist/tool-calls/reporters/agent.d.ts.map +1 -0
  67. package/dist/tool-calls/reporters/agent.js +190 -0
  68. package/dist/tool-calls/reporters/bash.d.ts +9 -0
  69. package/dist/tool-calls/reporters/bash.d.ts.map +1 -0
  70. package/dist/tool-calls/reporters/bash.js +83 -0
  71. package/dist/tool-calls/reporters/compaction.d.ts +24 -0
  72. package/dist/tool-calls/reporters/compaction.d.ts.map +1 -0
  73. package/dist/tool-calls/reporters/compaction.js +66 -0
  74. package/dist/tool-calls/reporters/file-edit.d.ts +46 -0
  75. package/dist/tool-calls/reporters/file-edit.d.ts.map +1 -0
  76. package/dist/tool-calls/reporters/file-edit.js +180 -0
  77. package/dist/tool-calls/reporters/index.d.ts +4 -0
  78. package/dist/tool-calls/reporters/index.d.ts.map +1 -0
  79. package/dist/tool-calls/reporters/index.js +40 -0
  80. package/dist/tool-calls/reporters/interaction.d.ts +71 -0
  81. package/dist/tool-calls/reporters/interaction.d.ts.map +1 -0
  82. package/dist/tool-calls/reporters/interaction.js +222 -0
  83. package/dist/tool-calls/reporters/read.d.ts +7 -0
  84. package/dist/tool-calls/reporters/read.d.ts.map +1 -0
  85. package/dist/tool-calls/reporters/read.js +64 -0
  86. package/dist/tool-calls/reporters/search.d.ts +10 -0
  87. package/dist/tool-calls/reporters/search.d.ts.map +1 -0
  88. package/dist/tool-calls/reporters/search.js +50 -0
  89. package/dist/tool-calls/reporters/web.d.ts +11 -0
  90. package/dist/tool-calls/reporters/web.d.ts.map +1 -0
  91. package/dist/tool-calls/reporters/web.js +42 -0
  92. package/dist/tools.d.ts +32 -37
  93. package/dist/tools.d.ts.map +1 -1
  94. package/dist/tools.js +48 -970
  95. package/package.json +4 -1
@@ -0,0 +1,487 @@
1
+ import { AIR_COMMAND_TITLE_KEY, AIR_SKILL_KEY, AIR_SUBAGENT_KEY, withAirMeta, } from "../air-extension.js";
2
+ import { exitPlanModeRawOutput } from "../exit-plan.js";
3
+ import { ClientCapabilities } from "./client-capabilities.js";
4
+ import { resultText, textContent, toAcpContentUpdate, toolResponseMarkers } from "./content.js";
5
+ import { reporterFor } from "./reporters/index.js";
6
+ import { resolveSkillPath } from "./reporters/interaction.js";
7
+ /** The id suffix of the call that clears the plan file of an approved plan for AIR. */
8
+ const PLAN_FILE_CLEAR_SUFFIX = ":plan-file-clear";
9
+ /**
10
+ * Turns tool facts into the fields of the standard ACP tool call report, as
11
+ * `docs/air-extensions.md#tool-call-contract` defines: each fact goes in one field.
12
+ *
13
+ * The {@link ToolReporter} of the tool reads the SDK data. The renderer
14
+ * decides the fields from the facts and the {@link ClientCapabilities}. The
15
+ * {@link ToolCallFieldTracker} runs after it and drops the fields that an
16
+ * earlier report of the same tool call already sent.
17
+ */
18
+ export class AcpToolCallRenderer {
19
+ capabilities;
20
+ replay;
21
+ constructor(capabilities = new ClientCapabilities(),
22
+ /** True when the renderer reports the history of a loaded session. */
23
+ replay = false) {
24
+ this.capabilities = capabilities;
25
+ this.replay = replay;
26
+ }
27
+ static for(capabilities, replay = false) {
28
+ return new AcpToolCallRenderer(ClientCapabilities.from(capabilities), replay);
29
+ }
30
+ /** The facts of a tool use. */
31
+ facts(toolUse, cwd) {
32
+ return reporterFor(toolUse.name).toolUse(toolUse.input, {
33
+ cwd,
34
+ capabilities: this.capabilities,
35
+ replay: this.replay,
36
+ });
37
+ }
38
+ /** The standard fields of a tool use: the title, the kind, the content, and the locations. */
39
+ toolInfo(toolUse, cwd) {
40
+ const facts = this.facts(toolUse, cwd);
41
+ return {
42
+ title: facts.title,
43
+ kind: facts.kind,
44
+ content: this.toolUseContent(toolUse.id, facts),
45
+ ...(facts.locations !== undefined ? { locations: facts.locations } : {}),
46
+ };
47
+ }
48
+ /**
49
+ * The first report of a tool call. `rawInput` is left out while the input
50
+ * still streams: the consolidated message sends it once it is complete.
51
+ */
52
+ toolCall(toolUse, options = {}) {
53
+ const facts = this.facts(toolUse, options.cwd);
54
+ const isMcp = toolUse.name.startsWith("mcp__");
55
+ return {
56
+ _meta: {
57
+ ...this.toolUseMeta(toolUse, options.cwd),
58
+ ...(facts.command && this.capabilities.terminalOutput
59
+ ? { terminal_info: { terminal_id: toolUse.id } }
60
+ : {}),
61
+ // Only AIR gets the MCP flag. The upstream adapter does not send it.
62
+ ...(isMcp && this.capabilities.air.client ? { is_mcp_tool_call: true } : {}),
63
+ },
64
+ toolCallId: toolUse.id,
65
+ sessionUpdate: "tool_call",
66
+ name: toolUse.name,
67
+ // AIR gets `rawInput` once it is complete. Every other client gets the
68
+ // input as it stands, also the empty input at the stream start.
69
+ ...(options.inputComplete === false && this.capabilities.air.client
70
+ ? {}
71
+ : { rawInput: this.rawInput(facts, toolUse.input) }),
72
+ status: "pending",
73
+ title: facts.title,
74
+ kind: facts.kind,
75
+ content: options.previewContent ?? this.toolUseContent(toolUse.id, facts),
76
+ ...(facts.locations !== undefined ? { locations: facts.locations } : {}),
77
+ };
78
+ }
79
+ /** The report of a tool call whose input is complete now. */
80
+ refinement(toolUse, cwd) {
81
+ const facts = this.facts(toolUse, cwd);
82
+ return {
83
+ _meta: this.toolUseMeta(toolUse, cwd),
84
+ toolCallId: toolUse.id,
85
+ sessionUpdate: "tool_call_update",
86
+ rawInput: this.rawInput(facts, toolUse.input),
87
+ title: facts.title,
88
+ kind: facts.kind,
89
+ content: this.toolUseContent(toolUse.id, facts),
90
+ ...(facts.locations !== undefined ? { locations: facts.locations } : {}),
91
+ };
92
+ }
93
+ /**
94
+ * The report of a tool call from the complete top-level fields of its still
95
+ * streaming input. It carries no content: content built from partial input
96
+ * is misleading (an Edit without its `new_string` renders as a deletion) or
97
+ * invalid. AIR gets no `rawInput`, which goes out once it is complete. Every
98
+ * other client gets the partial input as `rawInput`.
99
+ */
100
+ partialRefinement(toolUse, input, cwd) {
101
+ const facts = this.facts({ name: toolUse.name, input }, cwd);
102
+ return {
103
+ _meta: this.toolUseMeta({ name: toolUse.name, input }, cwd),
104
+ toolCallId: toolUse.id,
105
+ sessionUpdate: "tool_call_update",
106
+ ...(this.capabilities.air.client ? {} : { rawInput: input }),
107
+ title: facts.title,
108
+ kind: facts.kind,
109
+ ...(facts.locations ? { locations: facts.locations } : {}),
110
+ };
111
+ }
112
+ /**
113
+ * The tool call of a permission request: `toolCallId`, `title`, and
114
+ * `rawInput`. The client already holds the rest. The request adds only what
115
+ * it shows new: an exact preview patch, and a location that the tool call
116
+ * does not have.
117
+ */
118
+ permissionToolCall(toolUse, options = {}) {
119
+ const facts = this.facts(toolUse, options.cwd);
120
+ if (!this.capabilities.air.client) {
121
+ // The upstream shape: the whole tool call again.
122
+ const content = this.toolUseContent(toolUse.id, facts);
123
+ const locations = [...(facts.locations ?? []), ...(options.extraLocations ?? [])];
124
+ return {
125
+ toolCallId: toolUse.id,
126
+ name: toolUse.name,
127
+ status: "pending",
128
+ rawInput: toolUse.input,
129
+ title: options.title ?? facts.title,
130
+ kind: facts.kind,
131
+ content: content.length === 0 && options.fallbackContent ? options.fallbackContent : content,
132
+ ...(facts.locations !== undefined || options.extraLocations?.length ? { locations } : {}),
133
+ ...(options.meta ? { _meta: options.meta } : {}),
134
+ };
135
+ }
136
+ return {
137
+ toolCallId: toolUse.id,
138
+ title: options.title ?? facts.title,
139
+ rawInput: this.rawInput(facts, toolUse.input),
140
+ ...(options.previewContent ? { content: options.previewContent } : {}),
141
+ ...(options.extraLocations?.length
142
+ ? { locations: [...(facts.locations ?? []), ...options.extraLocations] }
143
+ : {}),
144
+ ...(options.meta ? { _meta: options.meta } : {}),
145
+ };
146
+ }
147
+ /** The facts of a tool result. */
148
+ resultFacts(toolUse, result, structured) {
149
+ const reporter = reporterFor(toolUse.name);
150
+ const context = {
151
+ toolUse,
152
+ result,
153
+ structured,
154
+ capabilities: this.capabilities,
155
+ };
156
+ const content = result.content;
157
+ const hasErrorText = result.is_error === true &&
158
+ content !== undefined &&
159
+ content !== null &&
160
+ (typeof content === "string" || Array.isArray(content)) &&
161
+ content.length > 0;
162
+ if (hasErrorText) {
163
+ return reporter.errorResult?.(context) ?? toAcpContentUpdate(content, true);
164
+ }
165
+ return reporter.toolResult ? reporter.toolResult(context) : resultText(result);
166
+ }
167
+ /** The result fields of a tool result, before the status and the tool name. */
168
+ resultFields(toolUse, result, structured) {
169
+ const facts = this.resultFacts(toolUse, result, structured);
170
+ const { command, ...fields } = facts;
171
+ if (!command)
172
+ return fields;
173
+ // The terminal was announced under the tool_use id (see `toolCall`), so the
174
+ // output keys off that id. A non-string id is no id at all.
175
+ const idOf = (id) => (typeof id === "string" && id.length > 0 ? id : undefined);
176
+ const terminalId = idOf(toolUse.id) ?? idOf(result.tool_use_id);
177
+ // Without a terminal id nothing can match the output. A client that
178
+ // buffers output for an unknown terminal would hold it forever, so the
179
+ // output becomes a code block instead.
180
+ if (this.capabilities.terminalOutput && terminalId !== undefined) {
181
+ const output = { terminal_id: terminalId, data: command.output };
182
+ return {
183
+ ...fields,
184
+ // A client that is not AIR gets the terminal content again, like
185
+ // upstream: it replaces a denial text that an earlier update showed.
186
+ ...(this.capabilities.air.client
187
+ ? {}
188
+ : { content: [{ type: "terminal", terminalId }] }),
189
+ _meta: {
190
+ terminal_info: { terminal_id: terminalId },
191
+ ...(this.capabilities.terminalOutputDelta
192
+ ? { terminal_output_delta: output }
193
+ : { terminal_output: output }),
194
+ terminal_exit: { terminal_id: terminalId, exit_code: command.exitCode, signal: null },
195
+ },
196
+ };
197
+ }
198
+ return command.output.trim()
199
+ ? {
200
+ ...fields,
201
+ content: [
202
+ {
203
+ type: "content",
204
+ content: {
205
+ type: "text",
206
+ text: `\`\`\`console\n${command.output.trimEnd()}\n\`\`\``,
207
+ },
208
+ },
209
+ ],
210
+ }
211
+ : fields;
212
+ }
213
+ /**
214
+ * The reports of a tool result. A command sends its output as a separate
215
+ * report first, like codex-acp: the output, then the exit and the status.
216
+ * The output travels once: the raw tool_result goes to `rawOutput` only
217
+ * when no other field carries the result.
218
+ */
219
+ result(toolUse, result, options = {}) {
220
+ const { _meta: resultMeta, ...fields } = this.resultFields(toolUse, result, options.structured);
221
+ // AIR shows a read or a search that names a path as the list of viewed
222
+ // files. That view does not show the text of the result, so it stays out.
223
+ const viewedFiles = this.capabilities.air.client && result.is_error !== true && this.namesViewedFile(toolUse);
224
+ if (viewedFiles) {
225
+ const content = fields.content?.filter((item) => !(item.type === "content" && item.content.type === "text"));
226
+ if (content?.length)
227
+ fields.content = content;
228
+ else
229
+ delete fields.content;
230
+ }
231
+ const updates = [];
232
+ const terminalOutput = resultMeta?.terminal_output_delta ?? resultMeta?.terminal_output;
233
+ if (terminalOutput) {
234
+ updates.push({
235
+ _meta: resultMeta?.terminal_output_delta
236
+ ? { terminal_output_delta: terminalOutput }
237
+ : { terminal_output: terminalOutput },
238
+ toolCallId: toolUse.id,
239
+ sessionUpdate: "tool_call_update",
240
+ });
241
+ }
242
+ // AIR gets the raw tool_result only when no other field carries the
243
+ // result. Every other client gets it unless the terminal carried it.
244
+ const rawOutput = !this.capabilities.air.client
245
+ ? terminalOutput
246
+ ? undefined
247
+ : exitPlanModeRawOutput(toolUse.name, result.content)
248
+ : "rawOutput" in fields
249
+ ? fields.rawOutput
250
+ : viewedFiles || terminalOutput || fields.content !== undefined
251
+ ? undefined
252
+ : result.content;
253
+ delete fields.rawOutput;
254
+ // A plan file that the result names goes out also when the input did not
255
+ // name it. The field tracker drops a path that the client holds.
256
+ const planFilePath = fields.planFilePath;
257
+ delete fields.planFilePath;
258
+ const planFileReleased = fields.planFileReleased === true;
259
+ delete fields.planFileReleased;
260
+ updates.push({
261
+ _meta: {
262
+ claudeCode: { toolName: toolUse.name, ...(options.nonExecution ?? {}) },
263
+ ...(resultMeta?.terminal_exit ? { terminal_exit: resultMeta.terminal_exit } : {}),
264
+ },
265
+ toolCallId: toolUse.id,
266
+ sessionUpdate: "tool_call_update",
267
+ status: result.is_error === true ? "failed" : "completed",
268
+ ...(rawOutput !== undefined ? { rawOutput } : {}),
269
+ ...(planFilePath ? { rawInput: planFileInput(toolUse.input, planFilePath) } : {}),
270
+ ...fields,
271
+ });
272
+ // AIR keeps the plan file button while the last reported path names a
273
+ // file, and a blank path is its clear signal. The clear goes as a call of
274
+ // its own, so the plan card of this call keeps its file.
275
+ if (planFilePath && planFileReleased && result.is_error !== true) {
276
+ updates.push({
277
+ toolCallId: `${toolUse.id}${PLAN_FILE_CLEAR_SUFFIX}`,
278
+ sessionUpdate: "tool_call",
279
+ title: "Exited Plan Mode",
280
+ kind: "switch_mode",
281
+ status: "completed",
282
+ rawInput: { planFilePath: "" },
283
+ content: [],
284
+ });
285
+ }
286
+ return updates;
287
+ }
288
+ /**
289
+ * Whether AIR shows [toolUse] as the list of viewed files: a read or a
290
+ * search with a path in its locations or in the `path` of its input.
291
+ */
292
+ namesViewedFile(toolUse) {
293
+ const facts = this.facts(toolUse);
294
+ if (facts.kind !== "read" && facts.kind !== "search")
295
+ return false;
296
+ if (facts.locations?.some((location) => location.path))
297
+ return true;
298
+ const path = toolUse.input?.path;
299
+ return typeof path === "string" && path.length > 0;
300
+ }
301
+ /**
302
+ * The report of a PostToolUse hook: the final change of an edit tool, and
303
+ * the `status` and `isAsync` markers of the `tool_response`. The rest of the
304
+ * `tool_response` repeats output that the content already carries.
305
+ */
306
+ async hookResult(toolUse, toolResponse, cwd) {
307
+ const reporter = reporterFor(toolUse.name);
308
+ const change = reporter.hookResult
309
+ ? await reporter.hookResult(toolResponse, { cwd, capabilities: this.capabilities })
310
+ : {};
311
+ if (!this.capabilities.air.client) {
312
+ // The upstream shape: the whole tool_response, on every hook.
313
+ return {
314
+ _meta: { claudeCode: { toolResponse, toolName: toolUse.name } },
315
+ toolCallId: toolUse.id,
316
+ sessionUpdate: "tool_call_update",
317
+ ...(change.content ? { content: change.content } : {}),
318
+ ...(change.locations ? { locations: change.locations } : {}),
319
+ };
320
+ }
321
+ const markers = toolResponseMarkers(toolResponse);
322
+ if (!markers && !change.content && !change.locations)
323
+ return undefined;
324
+ return {
325
+ _meta: {
326
+ claudeCode: { ...(markers ? { toolResponse: markers } : {}), toolName: toolUse.name },
327
+ },
328
+ toolCallId: toolUse.id,
329
+ sessionUpdate: "tool_call_update",
330
+ ...(change.content ? { content: change.content } : {}),
331
+ ...(change.locations ? { locations: change.locations } : {}),
332
+ };
333
+ }
334
+ /**
335
+ * The report of a memory recall: a completed read tool call. A synthesis
336
+ * shows the recalled text, a plain recall shows the memory files.
337
+ */
338
+ memoryRecall(recall) {
339
+ const isSynthesis = recall.mode === "synthesize";
340
+ const locations = isSynthesis ? [] : recall.memories.map((memory) => ({ path: memory.path }));
341
+ const content = isSynthesis
342
+ ? recall.memories.flatMap((memory) => typeof memory.content === "string" ? [textContent(memory.content)] : [])
343
+ : [];
344
+ const count = recall.memories.length;
345
+ return {
346
+ sessionUpdate: "tool_call",
347
+ toolCallId: recall.uuid,
348
+ title: isSynthesis
349
+ ? "Recalled synthesized memory"
350
+ : `Recalled ${count} ${count === 1 ? "memory" : "memories"}`,
351
+ kind: "read",
352
+ status: "completed",
353
+ ...(locations.length > 0 && { locations }),
354
+ ...(content.length > 0 && { content }),
355
+ _meta: {
356
+ claudeCode: { toolName: "memory_recall", toolResponse: { mode: recall.mode } },
357
+ },
358
+ };
359
+ }
360
+ /**
361
+ * The report of a tool call that a rule, the classifier, or a mode denied
362
+ * before it ran. The reason is the result to show, so `toolResponse` keeps
363
+ * only the reason type, and the SDK message when it differs from the reason.
364
+ */
365
+ permissionDenied(denial) {
366
+ const reason = denial.decisionReason ?? denial.message;
367
+ // AIR gets the SDK message only when it differs from the reason, which
368
+ // the content shows. Every other client gets the upstream toolResponse.
369
+ const extraMessage = !this.capabilities.air.client
370
+ ? { decisionReason: denial.decisionReason, message: denial.message }
371
+ : denial.decisionReason !== undefined &&
372
+ denial.message !== undefined &&
373
+ denial.message !== denial.decisionReason
374
+ ? { message: denial.message }
375
+ : {};
376
+ return {
377
+ sessionUpdate: "tool_call_update",
378
+ toolCallId: denial.toolCallId,
379
+ status: "failed",
380
+ content: [textContent(`Permission denied: ${reason}`)],
381
+ _meta: {
382
+ claudeCode: {
383
+ toolName: denial.toolName,
384
+ ...(denial.parentToolUseId ? { parentToolUseId: denial.parentToolUseId } : {}),
385
+ toolResponse: { decisionReasonType: denial.decisionReasonType, ...extraMessage },
386
+ },
387
+ },
388
+ };
389
+ }
390
+ /**
391
+ * The report of a tool progress beat. The field tracker sends the
392
+ * `in_progress` status once, so a later beat carries only the progress.
393
+ */
394
+ progress(beat) {
395
+ return {
396
+ sessionUpdate: "tool_call_update",
397
+ toolCallId: beat.toolCallId,
398
+ status: "in_progress",
399
+ _meta: {
400
+ claudeCode: {
401
+ ...(beat.toolName !== undefined ? { toolName: beat.toolName } : {}),
402
+ ...(beat.parentToolUseId ? { parentToolUseId: beat.parentToolUseId } : {}),
403
+ toolResponse: {
404
+ elapsedTimeSeconds: beat.elapsedTimeSeconds,
405
+ // For Agent/Task calls: the subagent type, and the SDK retry
406
+ // counters while the subagent waits out an API rate limit, so a
407
+ // client can show why a spawn looks stalled.
408
+ ...(beat.subagentType !== undefined && { subagentType: beat.subagentType }),
409
+ ...(beat.subagentRetry !== undefined && { subagentRetry: beat.subagentRetry }),
410
+ },
411
+ },
412
+ },
413
+ };
414
+ }
415
+ /**
416
+ * `rawInput`. For AIR, without the file text that a diff holds, and with
417
+ * the path of the plan file in place of the plan text.
418
+ */
419
+ rawInput(facts, rawInput) {
420
+ if (facts.planFilePath)
421
+ return planFileInput(rawInput, facts.planFilePath);
422
+ if (!this.capabilities.air.client ||
423
+ !facts.fileTextKeys ||
424
+ !rawInput ||
425
+ typeof rawInput !== "object" ||
426
+ Array.isArray(rawInput)) {
427
+ return rawInput;
428
+ }
429
+ const input = { ...rawInput };
430
+ for (const key of facts.fileTextKeys)
431
+ delete input[key];
432
+ return input;
433
+ }
434
+ /**
435
+ * The content of a tool use. A command shows the terminal marker. An edit
436
+ * shows its change. A display copy of the input goes only to a client that
437
+ * does not render `rawInput` itself, and never next to a terminal.
438
+ */
439
+ toolUseContent(toolCallId, facts) {
440
+ const terminal = facts.command === true && this.capabilities.terminalOutput;
441
+ return [
442
+ ...(terminal ? [{ type: "terminal", terminalId: toolCallId }] : []),
443
+ ...(facts.change ?? []),
444
+ ...(!terminal && !this.capabilities.air.rawInputRendering ? (facts.display ?? []) : []),
445
+ ];
446
+ }
447
+ /**
448
+ * The `_meta` of a tool use report: the tool name, and for AIR the tool
449
+ * flags under `_meta.jetbrains.air`. A shell description stays out of the
450
+ * standard `title`, which clients use as the command preview. Every other
451
+ * client gets only the upstream tool name.
452
+ */
453
+ toolUseMeta(toolUse, cwd) {
454
+ const input = toolUse.input !== null && typeof toolUse.input === "object"
455
+ ? toolUse.input
456
+ : {};
457
+ const description = (toolUse.name === "Bash" || toolUse.name === "PowerShell") &&
458
+ typeof input.description === "string"
459
+ ? input.description
460
+ : undefined;
461
+ const skillName = toolUse.name === "Skill" && typeof input.skill === "string" ? input.skill : undefined;
462
+ const subagent = toolUse.name === "Agent" || toolUse.name === "Task";
463
+ if (!this.capabilities.air.client)
464
+ return { claudeCode: { toolName: toolUse.name } };
465
+ let meta = { claudeCode: { toolName: toolUse.name } };
466
+ if (description)
467
+ meta = withAirMeta(meta, AIR_COMMAND_TITLE_KEY, description);
468
+ if (subagent)
469
+ meta = withAirMeta(meta, AIR_SUBAGENT_KEY, true);
470
+ if (skillName) {
471
+ const skillPath = resolveSkillPath(skillName, cwd);
472
+ meta = withAirMeta(meta, AIR_SKILL_KEY, {
473
+ name: skillName,
474
+ ...(skillPath ? { path: skillPath } : {}),
475
+ });
476
+ }
477
+ return meta;
478
+ }
479
+ }
480
+ /** The input with the path of the plan file in place of the plan text. */
481
+ function planFileInput(rawInput, planFilePath) {
482
+ const input = rawInput && typeof rawInput === "object" && !Array.isArray(rawInput)
483
+ ? { ...rawInput }
484
+ : {};
485
+ delete input.plan;
486
+ return { ...input, planFilePath };
487
+ }
@@ -0,0 +1,7 @@
1
+ import type { ToolReporter, ToolResultContext, ToolResultFacts, ToolUseFacts } from "../facts.js";
2
+ /** Agent and Task: a subagent. Its prompt is input that the user reads. */
3
+ export declare class AgentReporter implements ToolReporter {
4
+ toolUse(input: unknown): ToolUseFacts;
5
+ toolResult({ result, structured }: ToolResultContext): ToolResultFacts;
6
+ }
7
+ //# sourceMappingURL=agent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../../src/tool-calls/reporters/agent.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,iBAAiB,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAElG,2EAA2E;AAC3E,qBAAa,aAAc,YAAW,YAAY;IAChD,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,YAAY;IASrC,UAAU,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,EAAE,iBAAiB,GAAG,eAAe;CA2BvE"}