@agentclientprotocol/claude-agent-acp 0.81.2 → 0.81.3-preview.2

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 (99) hide show
  1. package/README.md +8 -5
  2. package/dist/acp-agent.d.ts +32 -40
  3. package/dist/acp-agent.d.ts.map +1 -1
  4. package/dist/acp-agent.js +557 -577
  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/hide-claude-auth.d.ts +2 -6
  26. package/dist/hide-claude-auth.d.ts.map +1 -1
  27. package/dist/hide-claude-auth.js +3 -21
  28. package/dist/native-subagents.d.ts +58 -0
  29. package/dist/native-subagents.d.ts.map +1 -1
  30. package/dist/native-subagents.js +186 -34
  31. package/dist/paths.d.ts +3 -0
  32. package/dist/paths.d.ts.map +1 -0
  33. package/dist/paths.js +6 -0
  34. package/dist/permissions/presentation.d.ts +13 -2
  35. package/dist/permissions/presentation.d.ts.map +1 -1
  36. package/dist/permissions/presentation.js +49 -44
  37. package/dist/resumed-session.d.ts +11 -0
  38. package/dist/resumed-session.d.ts.map +1 -1
  39. package/dist/resumed-session.js +106 -4
  40. package/dist/session-failure-extension.js +1 -1
  41. package/dist/session-mode.d.ts +2 -0
  42. package/dist/session-mode.d.ts.map +1 -1
  43. package/dist/session-mode.js +12 -5
  44. package/dist/settings.js +2 -2
  45. package/dist/subagent-history.d.ts +20 -0
  46. package/dist/subagent-history.d.ts.map +1 -0
  47. package/dist/subagent-history.js +46 -0
  48. package/dist/tool-calls/background.d.ts +20 -0
  49. package/dist/tool-calls/background.d.ts.map +1 -0
  50. package/dist/tool-calls/background.js +41 -0
  51. package/dist/tool-calls/changed-meta-filter.d.ts +27 -0
  52. package/dist/tool-calls/changed-meta-filter.d.ts.map +1 -0
  53. package/dist/tool-calls/changed-meta-filter.js +102 -0
  54. package/dist/tool-calls/client-capabilities.d.ts +51 -0
  55. package/dist/tool-calls/client-capabilities.d.ts.map +1 -0
  56. package/dist/tool-calls/client-capabilities.js +43 -0
  57. package/dist/tool-calls/content.d.ts +59 -0
  58. package/dist/tool-calls/content.d.ts.map +1 -0
  59. package/dist/tool-calls/content.js +194 -0
  60. package/dist/tool-calls/facts.d.ts +116 -0
  61. package/dist/tool-calls/facts.d.ts.map +1 -0
  62. package/dist/tool-calls/facts.js +1 -0
  63. package/dist/tool-calls/field-tracker.d.ts +58 -0
  64. package/dist/tool-calls/field-tracker.d.ts.map +1 -0
  65. package/dist/tool-calls/field-tracker.js +167 -0
  66. package/dist/tool-calls/renderer.d.ts +216 -0
  67. package/dist/tool-calls/renderer.d.ts.map +1 -0
  68. package/dist/tool-calls/renderer.js +487 -0
  69. package/dist/tool-calls/reporters/agent.d.ts +7 -0
  70. package/dist/tool-calls/reporters/agent.d.ts.map +1 -0
  71. package/dist/tool-calls/reporters/agent.js +190 -0
  72. package/dist/tool-calls/reporters/bash.d.ts +9 -0
  73. package/dist/tool-calls/reporters/bash.d.ts.map +1 -0
  74. package/dist/tool-calls/reporters/bash.js +83 -0
  75. package/dist/tool-calls/reporters/compaction.d.ts +24 -0
  76. package/dist/tool-calls/reporters/compaction.d.ts.map +1 -0
  77. package/dist/tool-calls/reporters/compaction.js +66 -0
  78. package/dist/tool-calls/reporters/file-edit.d.ts +46 -0
  79. package/dist/tool-calls/reporters/file-edit.d.ts.map +1 -0
  80. package/dist/tool-calls/reporters/file-edit.js +180 -0
  81. package/dist/tool-calls/reporters/index.d.ts +4 -0
  82. package/dist/tool-calls/reporters/index.d.ts.map +1 -0
  83. package/dist/tool-calls/reporters/index.js +40 -0
  84. package/dist/tool-calls/reporters/interaction.d.ts +71 -0
  85. package/dist/tool-calls/reporters/interaction.d.ts.map +1 -0
  86. package/dist/tool-calls/reporters/interaction.js +222 -0
  87. package/dist/tool-calls/reporters/read.d.ts +7 -0
  88. package/dist/tool-calls/reporters/read.d.ts.map +1 -0
  89. package/dist/tool-calls/reporters/read.js +64 -0
  90. package/dist/tool-calls/reporters/search.d.ts +10 -0
  91. package/dist/tool-calls/reporters/search.d.ts.map +1 -0
  92. package/dist/tool-calls/reporters/search.js +50 -0
  93. package/dist/tool-calls/reporters/web.d.ts +11 -0
  94. package/dist/tool-calls/reporters/web.d.ts.map +1 -0
  95. package/dist/tool-calls/reporters/web.js +42 -0
  96. package/dist/tools.d.ts +32 -37
  97. package/dist/tools.d.ts.map +1 -1
  98. package/dist/tools.js +48 -970
  99. package/package.json +4 -1
@@ -1,4 +1,6 @@
1
- import { toolInfoFromToolUse } from "../tools.js";
1
+ import { AIR_PERMISSION_KEY, withAirMeta } from "../air-extension.js";
2
+ import { ClientCapabilities } from "../tool-calls/client-capabilities.js";
3
+ import { AcpToolCallRenderer } from "../tool-calls/renderer.js";
2
4
  function humanText(value, maxLength, singleLine = false) {
3
5
  if (typeof value !== "string")
4
6
  return undefined;
@@ -20,36 +22,27 @@ function humanText(value, maxLength, singleLine = false) {
20
22
  function compactText(value) {
21
23
  return humanText(value, 160, true);
22
24
  }
23
- function withBlockedPath(locations, blockedPath) {
24
- const path = humanText(blockedPath, 4_096, true);
25
- if (!path)
26
- return locations;
27
- const result = [...(locations ?? [])];
28
- if (!result.some((location) => location.path === path))
29
- result.push({ path });
30
- return result;
31
- }
25
+ /**
26
+ * The permission request presentation.
27
+ *
28
+ * The request `toolCall` carries `toolCallId`, `title`, and `rawInput`. The
29
+ * adapter emits the `tool_call` before the request, so the client already
30
+ * holds the rest. The request adds only what it shows new: the exact preview
31
+ * patch, and the blocked path when the tool call has no such location.
32
+ */
32
33
  export function buildClaudePermissionPresentation(value) {
33
- const info = toolInfoFromToolUse({ id: value.toolUseID, name: value.toolName, input: value.input }, value.supportsTerminalOutput ?? false, value.cwd);
34
+ const capabilities = value.capabilities ?? new ClientCapabilities();
35
+ const renderer = new AcpToolCallRenderer(capabilities);
36
+ const toolUse = { id: value.toolUseID, name: value.toolName, input: value.input };
37
+ const facts = renderer.facts(toolUse, value.cwd);
34
38
  const host = value.toolName === "SandboxNetworkAccess" ? compactText(value.input.host) : undefined;
35
39
  const isComputerUse = value.toolName.startsWith("mcp__computer-use__");
36
40
  const subjectTitle = host ?? (isComputerUse ? compactText(value.displayName) : undefined);
37
- const subjectContent = (host || isComputerUse) && info.content.length === 0
38
- ? [
39
- {
40
- type: "content",
41
- content: {
42
- type: "text",
43
- text: `\`\`\`json\n${JSON.stringify(value.input, null, 2)}\n\`\`\``,
44
- },
45
- },
46
- ]
47
- : info.content;
48
41
  // Reuse the exact standard tool-call heading as the permission heading so
49
42
  // the approval never maintains a second, divergent name for the operation.
50
43
  // decisionReason is temporarily exposed as the permission description so
51
44
  // its actual SDK values can be inspected; it remains diagnostic policy text.
52
- const toolCallTitle = subjectTitle ?? info.title;
45
+ const toolCallTitle = subjectTitle ?? facts.title;
53
46
  const permissionTitle = value.toolName === "ExitPlanMode" ? "Ready to code?" : toolCallTitle;
54
47
  // Shell titles are executable input: compacting whitespace changes quoted
55
48
  // arguments and comment boundaries, and length limits can hide the command.
@@ -58,30 +51,42 @@ export function buildClaudePermissionPresentation(value) {
58
51
  : (humanText(permissionTitle, 4_000, true) ?? "Use tool?");
59
52
  const decisionReason = humanText(value.decisionReason, 4_000);
60
53
  const description = decisionReason ? `Reason: ${decisionReason}` : undefined;
54
+ const blockedPath = humanText(value.blockedPath, 4_096, true);
55
+ const extraLocations = blockedPath && !(facts.locations ?? []).some((location) => location.path === blockedPath)
56
+ ? [{ path: blockedPath }]
57
+ : undefined;
61
58
  return {
62
- toolCall: {
63
- toolCallId: value.toolUseID,
64
- name: value.toolName,
65
- status: "pending",
66
- rawInput: value.input,
67
- ...info,
59
+ toolCall: renderer.permissionToolCall(toolUse, {
60
+ cwd: value.cwd,
68
61
  title: toolCallTitle,
69
- content: subjectContent,
70
- locations: withBlockedPath(info.locations, value.blockedPath),
71
- },
72
- ...(title
73
- ? {
74
- _meta: {
75
- permission: {
76
- version: 1,
77
- title,
78
- ...(description ? { description } : {}),
79
- // The CLI's own hint, forwarded so a client that can pre-select
80
- // an option keeps the decline focused; the option order already
81
- // leads with the reject options when this is set.
82
- ...(value.defaultToNo === true ? { defaultToNo: true } : {}),
62
+ previewContent: value.previewContent,
63
+ extraLocations,
64
+ // The upstream request shows the input of a network or computer-use
65
+ // request that has no content of its own.
66
+ fallbackContent: host || isComputerUse
67
+ ? [
68
+ {
69
+ type: "content",
70
+ content: {
71
+ type: "text",
72
+ text: `\`\`\`json\n${JSON.stringify(value.input, null, 2)}\n\`\`\``,
73
+ },
83
74
  },
84
- },
75
+ ]
76
+ : undefined,
77
+ }),
78
+ // Only AIR gets the permission presentation.
79
+ ...(title && capabilities.air.client
80
+ ? {
81
+ _meta: withAirMeta(undefined, AIR_PERMISSION_KEY, {
82
+ version: 1,
83
+ title,
84
+ ...(description ? { description } : {}),
85
+ // The CLI's own hint, forwarded so a client that can pre-select
86
+ // an option keeps the decline focused; the option order already
87
+ // leads with the reject options when this is set.
88
+ ...(value.defaultToNo === true ? { defaultToNo: true } : {}),
89
+ }),
85
90
  }
86
91
  : {}),
87
92
  };
@@ -15,5 +15,16 @@ export declare function resumedModelFromTranscript(messages: SessionMessage[]):
15
15
  /** Read the resume model from the local transcript without starting a Claude
16
16
  * control request. This is intentionally on the load critical path. */
17
17
  export declare function readResumedSession(sessionId: string, logger?: ResumeLogger): Promise<ResumedSessionSnapshot>;
18
+ /**
19
+ * Read the resume model of a session from the end of its local transcript.
20
+ *
21
+ * A resume needs only the model, not the messages. The transcript file is
22
+ * read backwards until the last real assistant record of the main thread, so
23
+ * the cost does not grow with the length of the session. A session without a
24
+ * local transcript file falls back to {@link readResumedSession}.
25
+ */
26
+ export declare function readResumedModel(sessionId: string, logger?: ResumeLogger): Promise<string | undefined>;
27
+ /** The local transcript of a session in any project directory, as the SDK looks it up. */
28
+ export declare function findTranscript(sessionId: string): Promise<string | undefined>;
18
29
  export {};
19
30
  //# sourceMappingURL=resumed-session.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"resumed-session.d.ts","sourceRoot":"","sources":["../src/resumed-session.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAGzF,KAAK,YAAY,GAAG;IAClB,GAAG,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;IAClC,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;CACrC,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,CAAC,EAAE,cAAc,EAAE,CAAC;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF;;;iDAGiD;AACjD,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,cAAc,EAAE,GAAG,MAAM,GAAG,SAAS,CAkBzF;AAED;uEACuE;AACvE,wBAAsB,kBAAkB,CACtC,SAAS,EAAE,MAAM,EACjB,MAAM,CAAC,EAAE,YAAY,GACpB,OAAO,CAAC,sBAAsB,CAAC,CAejC"}
1
+ {"version":3,"file":"resumed-session.d.ts","sourceRoot":"","sources":["../src/resumed-session.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,gCAAgC,CAAC;AASzF,KAAK,YAAY,GAAG;IAClB,GAAG,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;IAClC,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;CACrC,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,CAAC,EAAE,cAAc,EAAE,CAAC;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF;;;iDAGiD;AACjD,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,cAAc,EAAE,GAAG,MAAM,GAAG,SAAS,CAgBzF;AASD;uEACuE;AACvE,wBAAsB,kBAAkB,CACtC,SAAS,EAAE,MAAM,EACjB,MAAM,CAAC,EAAE,YAAY,GACpB,OAAO,CAAC,sBAAsB,CAAC,CAejC;AAED;;;;;;;GAOG;AACH,wBAAsB,gBAAgB,CACpC,SAAS,EAAE,MAAM,EACjB,MAAM,CAAC,EAAE,YAAY,GACpB,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAc7B;AAED,0FAA0F;AAC1F,wBAAsB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAkBnF"}
@@ -1,5 +1,10 @@
1
1
  import { getSessionMessages } from "@anthropic-ai/claude-agent-sdk";
2
+ import { access, open, readdir } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { claudeConfigDir } from "./paths.js";
2
5
  import { SessionTiming } from "./session-timing.js";
6
+ /** The size of one backward read of a transcript. */
7
+ const TAIL_CHUNK_BYTES = 64 * 1024;
3
8
  /** Return the concrete model recorded by the last real assistant response.
4
9
  * Claude Code restores a resumed query from this same transcript field.
5
10
  * Synthetic assistant records use angle-bracket placeholders and do not
@@ -14,13 +19,19 @@ export function resumedModelFromTranscript(messages) {
14
19
  typeof entry.message !== "object") {
15
20
  continue;
16
21
  }
17
- const model = entry.message.model;
18
- if (typeof model === "string" && model.trim().length > 0 && !/^<[^>]+>$/.test(model.trim())) {
19
- return model.trim();
20
- }
22
+ const model = concreteModel(entry.message.model);
23
+ if (model)
24
+ return model;
21
25
  }
22
26
  return undefined;
23
27
  }
28
+ /** The model name, or undefined for a missing or a synthetic `<…>` model. */
29
+ function concreteModel(model) {
30
+ if (typeof model !== "string")
31
+ return undefined;
32
+ const name = model.trim();
33
+ return name.length > 0 && !/^<[^>]+>$/.test(name) ? name : undefined;
34
+ }
24
35
  /** Read the resume model from the local transcript without starting a Claude
25
36
  * control request. This is intentionally on the load critical path. */
26
37
  export async function readResumedSession(sessionId, logger) {
@@ -40,3 +51,94 @@ export async function readResumedSession(sessionId, logger) {
40
51
  return {};
41
52
  }
42
53
  }
54
+ /**
55
+ * Read the resume model of a session from the end of its local transcript.
56
+ *
57
+ * A resume needs only the model, not the messages. The transcript file is
58
+ * read backwards until the last real assistant record of the main thread, so
59
+ * the cost does not grow with the length of the session. A session without a
60
+ * local transcript file falls back to {@link readResumedSession}.
61
+ */
62
+ export async function readResumedModel(sessionId, logger) {
63
+ const timing = new SessionTiming(logger, "models", sessionId);
64
+ try {
65
+ const filePath = await findTranscript(sessionId);
66
+ if (filePath) {
67
+ const model = await lastAssistantModel(filePath);
68
+ timing.phase("read-transcript-tail", ` model=${model ?? "unknown"}`);
69
+ return model;
70
+ }
71
+ }
72
+ catch (error) {
73
+ timing.phase("read-transcript-tail", " outcome=error");
74
+ logger?.error(`Failed to read the transcript tail of resumed session ${sessionId}:`, error);
75
+ }
76
+ return (await readResumedSession(sessionId, logger)).model;
77
+ }
78
+ /** The local transcript of a session in any project directory, as the SDK looks it up. */
79
+ export async function findTranscript(sessionId) {
80
+ const projects = path.join(claudeConfigDir(), "projects");
81
+ let directories;
82
+ try {
83
+ directories = await readdir(projects);
84
+ }
85
+ catch {
86
+ return undefined;
87
+ }
88
+ for (const directory of directories) {
89
+ const candidate = path.join(projects, directory, `${sessionId}.jsonl`);
90
+ try {
91
+ await access(candidate);
92
+ return candidate;
93
+ }
94
+ catch {
95
+ // Not in this project directory.
96
+ }
97
+ }
98
+ return undefined;
99
+ }
100
+ /** The model of the last real assistant record of the main thread in a JSONL transcript. */
101
+ async function lastAssistantModel(filePath) {
102
+ const handle = await open(filePath, "r");
103
+ try {
104
+ let end = (await handle.stat()).size;
105
+ // The bytes of the line whose start is not read yet, in file order.
106
+ let pending = [];
107
+ while (end > 0) {
108
+ const start = Math.max(0, end - TAIL_CHUNK_BYTES);
109
+ let chunk = Buffer.alloc(end - start);
110
+ await handle.read(chunk, 0, chunk.length, start);
111
+ for (let at = chunk.lastIndexOf(0x0a); at >= 0; at = chunk.lastIndexOf(0x0a)) {
112
+ const model = assistantModelOfLine(Buffer.concat([chunk.subarray(at + 1), ...pending]));
113
+ if (model)
114
+ return model;
115
+ pending = [];
116
+ chunk = chunk.subarray(0, at);
117
+ }
118
+ pending.unshift(chunk);
119
+ end = start;
120
+ }
121
+ return assistantModelOfLine(Buffer.concat(pending));
122
+ }
123
+ finally {
124
+ await handle.close();
125
+ }
126
+ }
127
+ function assistantModelOfLine(line) {
128
+ // Most lines are not assistant records. The check skips their JSON parse.
129
+ if (line.indexOf('"assistant"') < 0)
130
+ return undefined;
131
+ let entry;
132
+ try {
133
+ entry = JSON.parse(line.toString("utf8"));
134
+ }
135
+ catch {
136
+ return undefined;
137
+ }
138
+ if (!entry || typeof entry !== "object")
139
+ return undefined;
140
+ const record = entry;
141
+ if (record.type !== "assistant" || record.isSidechain === true)
142
+ return undefined;
143
+ return concreteModel(record.message?.model);
144
+ }
@@ -17,7 +17,7 @@ const AIR_FAILURE_POLICY = {
17
17
  // credentials must be refreshed outside Claude. No `login` action, and not
18
18
  // sticky like `auth_required` (nothing clears at `auth_status`).
19
19
  access_denied: {
20
- category: "access",
20
+ category: "service",
21
21
  actions: ["retry"],
22
22
  fallbackTitle: "Claude could not access the account or provider credentials.",
23
23
  },
@@ -27,6 +27,8 @@ export type SessionModeManagerOptions<S extends SessionMode> = {
27
27
  * known after `initialize`. */
28
28
  supportsNotices?(): boolean;
29
29
  logError(...args: unknown[]): void;
30
+ /** Whether the client is AIR. Only AIR gets the mode kind, under `_meta.jetbrains.air`. */
31
+ airClient?(): boolean;
30
32
  };
31
33
  type ModeConfigSession = SessionMode & {
32
34
  configOptions: SessionConfigOption[];
@@ -1 +1 @@
1
- {"version":3,"file":"session-mode.d.ts","sourceRoot":"","sources":["../src/session-mode.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"session-mode.d.ts","sourceRoot":"","sources":["../src/session-mode.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  import { RequestError, } from "@agentclientprotocol/sdk";
2
+ import { AIR_KIND_KEY, airOnlyMeta } from "./air-extension.js";
2
3
  import { noticeOrTranscriptUpdate, noticeTranscriptText, sentenceCase, } from "./session-notices.js";
3
4
  export const MODE_CONFIG_ID = "mode";
4
5
  export const AUTO_MODE_FALLBACK = "acceptEdits";
@@ -201,30 +202,36 @@ export class SessionModeManager {
201
202
  }
202
203
  }
203
204
  buildAvailableModes(allowBypass) {
205
+ // Only AIR gets the mode kind.
206
+ const air = this.options.airClient?.() ?? false;
207
+ const kind = (value) => {
208
+ const meta = airOnlyMeta(air, AIR_KIND_KEY, value);
209
+ return meta ? { _meta: meta } : {};
210
+ };
204
211
  const modes = [
205
212
  {
206
213
  id: "default",
207
214
  name: "Manual",
208
215
  description: "Always ask before making changes",
209
- _meta: { kind: "standard" },
216
+ ...kind("standard"),
210
217
  },
211
218
  {
212
219
  id: "acceptEdits",
213
220
  name: "Accept edits",
214
221
  description: "Automatically accept all file edits",
215
- _meta: { kind: "standard" },
222
+ ...kind("standard"),
216
223
  },
217
224
  {
218
225
  id: "plan",
219
226
  name: "Plan",
220
227
  description: "Create a plan before making changes",
221
- _meta: { kind: "plan" },
228
+ ...kind("plan"),
222
229
  },
223
230
  {
224
231
  id: "auto",
225
232
  name: "Auto",
226
233
  description: "Claude handles permission decisions",
227
- _meta: { kind: "auto_review" },
234
+ ...kind("auto_review"),
228
235
  },
229
236
  ];
230
237
  if (allowBypass) {
@@ -232,7 +239,7 @@ export class SessionModeManager {
232
239
  id: "bypassPermissions",
233
240
  name: "Bypass permissions",
234
241
  description: "Accepts all permissions",
235
- _meta: { kind: "full_access" },
242
+ ...kind("full_access"),
236
243
  });
237
244
  }
238
245
  return modes;
package/dist/settings.js CHANGED
@@ -3,7 +3,7 @@ import * as path from "node:path";
3
3
  // resolveSettings and filterEscalatingDefaultMode are marked @alpha in the
4
4
  // SDK; API may shift in a future release.
5
5
  import { filterEscalatingDefaultMode, resolveSettings, } from "@anthropic-ai/claude-agent-sdk";
6
- import { CLAUDE_CONFIG_DIR } from "./acp-agent.js";
6
+ import { claudeConfigDir } from "./paths.js";
7
7
  /**
8
8
  * Permission rule format examples:
9
9
  * - "Read" - matches all Read tool calls
@@ -75,7 +75,7 @@ export class SettingsManager {
75
75
  */
76
76
  getWatchedPaths() {
77
77
  return [
78
- path.join(CLAUDE_CONFIG_DIR, "settings.json"),
78
+ path.join(claudeConfigDir(), "settings.json"),
79
79
  path.join(this.cwd, ".claude", "settings.json"),
80
80
  path.join(this.cwd, ".claude", "settings.local.json"),
81
81
  getManagedSettingsPath(),
@@ -0,0 +1,20 @@
1
+ /** Where the subagent transcripts of a session are. */
2
+ export interface SubagentHistory {
3
+ /** The subagent id of each Agent or Task tool use that launched a subagent. */
4
+ ids: Map<string, string>;
5
+ /**
6
+ * The project directory of the session, as the SDK takes it in `dir`. With
7
+ * it, the SDK reads a subagent transcript without a search of every project.
8
+ */
9
+ dir?: string;
10
+ }
11
+ /**
12
+ * Finds the subagent transcripts of a session.
13
+ *
14
+ * Claude keeps the history of a subagent in its own transcript,
15
+ * `<session>/subagents/agent-<id>.jsonl`, next to the session transcript.
16
+ * The `agent-<id>.meta.json` file beside it names the launching tool use.
17
+ * The ids are empty for a session without a local transcript.
18
+ */
19
+ export declare function subagentHistory(sessionId: string): Promise<SubagentHistory>;
20
+ //# sourceMappingURL=subagent-history.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subagent-history.d.ts","sourceRoot":"","sources":["../src/subagent-history.ts"],"names":[],"mappings":"AAKA,uDAAuD;AACvD,MAAM,WAAW,eAAe;IAC9B,+EAA+E;IAC/E,GAAG,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzB;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;GAOG;AACH,wBAAsB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CA6BjF"}
@@ -0,0 +1,46 @@
1
+ import { getSessionInfo } from "@anthropic-ai/claude-agent-sdk";
2
+ import { readdir, readFile } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { findTranscript } from "./resumed-session.js";
5
+ /**
6
+ * Finds the subagent transcripts of a session.
7
+ *
8
+ * Claude keeps the history of a subagent in its own transcript,
9
+ * `<session>/subagents/agent-<id>.jsonl`, next to the session transcript.
10
+ * The `agent-<id>.meta.json` file beside it names the launching tool use.
11
+ * The ids are empty for a session without a local transcript.
12
+ */
13
+ export async function subagentHistory(sessionId) {
14
+ const ids = new Map();
15
+ const transcript = await findTranscript(sessionId);
16
+ if (!transcript)
17
+ return { ids };
18
+ const directory = path.join(path.dirname(transcript), sessionId, "subagents");
19
+ let names;
20
+ try {
21
+ names = await readdir(directory);
22
+ }
23
+ catch {
24
+ return { ids };
25
+ }
26
+ await Promise.all(names.map(async (name) => {
27
+ const match = /^agent-(.+)\.meta\.json$/.exec(name);
28
+ if (!match)
29
+ return;
30
+ try {
31
+ const meta = JSON.parse(await readFile(path.join(directory, name), "utf8"));
32
+ const toolUseId = meta?.toolUseId;
33
+ if (typeof toolUseId === "string")
34
+ ids.set(toolUseId, match[1]);
35
+ }
36
+ catch {
37
+ // A damaged meta file leaves its subagent without history.
38
+ }
39
+ }));
40
+ if (ids.size === 0)
41
+ return { ids };
42
+ // The folder name encodes the project directory, and the encoding loses
43
+ // characters. The session info reads it back from the transcript.
44
+ const dir = (await getSessionInfo(sessionId).catch(() => undefined))?.cwd;
45
+ return dir ? { ids, dir } : { ids };
46
+ }
@@ -0,0 +1,20 @@
1
+ import type { SessionNotification } from "@agentclientprotocol/sdk";
2
+ import type { AsyncTaskStarted } from "../async-tasks.js";
3
+ /**
4
+ * Marks the Bash `tool_call_update` whose command detached into the background.
5
+ *
6
+ * A backgrounded Bash call returns as soon as the command is handed off, so the
7
+ * card reaches `completed` while the command itself runs on for minutes. ACP has
8
+ * no tool-call status for "still running elsewhere", so this marker is what lets
9
+ * a client render the card as backgrounded work instead of finished work. It
10
+ * rides the update the tool result already emits, so it costs no extra
11
+ * notification and cannot arrive out of order.
12
+ *
13
+ * The command's own lifecycle -- progress, completion, the stop control -- is
14
+ * published separately as an async task; this says only that the card has one.
15
+ * Hence the AIR namespace rather than `claudeCode`: to a client without the
16
+ * `asyncTasks` capability, which is never sent that lifecycle, the marker would
17
+ * promise a card state it has no way to ever resolve.
18
+ */
19
+ export declare function backgroundedBashToolCall(notification: SessionNotification, task: AsyncTaskStarted | undefined, asyncTasksSupported: boolean): SessionNotification;
20
+ //# sourceMappingURL=background.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"background.d.ts","sourceRoot":"","sources":["../../src/tool-calls/background.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAEpE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAE1D;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,wBAAwB,CACtC,YAAY,EAAE,mBAAmB,EACjC,IAAI,EAAE,gBAAgB,GAAG,SAAS,EAClC,mBAAmB,EAAE,OAAO,GAC3B,mBAAmB,CAkBrB"}
@@ -0,0 +1,41 @@
1
+ import { AIR_ASYNC_TASKS_CAPABILITY, withAirMeta } from "../air-extension.js";
2
+ /**
3
+ * Marks the Bash `tool_call_update` whose command detached into the background.
4
+ *
5
+ * A backgrounded Bash call returns as soon as the command is handed off, so the
6
+ * card reaches `completed` while the command itself runs on for minutes. ACP has
7
+ * no tool-call status for "still running elsewhere", so this marker is what lets
8
+ * a client render the card as backgrounded work instead of finished work. It
9
+ * rides the update the tool result already emits, so it costs no extra
10
+ * notification and cannot arrive out of order.
11
+ *
12
+ * The command's own lifecycle -- progress, completion, the stop control -- is
13
+ * published separately as an async task; this says only that the card has one.
14
+ * Hence the AIR namespace rather than `claudeCode`: to a client without the
15
+ * `asyncTasks` capability, which is never sent that lifecycle, the marker would
16
+ * promise a card state it has no way to ever resolve.
17
+ */
18
+ export function backgroundedBashToolCall(notification, task, asyncTasksSupported) {
19
+ const update = notification.update;
20
+ const toolCallId = task ? nonBlankTaskField(task.toolCallId ?? task.tool_use_id) : undefined;
21
+ if (!asyncTasksSupported ||
22
+ !toolCallId ||
23
+ update.sessionUpdate !== "tool_call_update" ||
24
+ update.toolCallId !== toolCallId) {
25
+ return notification;
26
+ }
27
+ return {
28
+ ...notification,
29
+ update: {
30
+ ...update,
31
+ _meta: withAirMeta(update._meta, AIR_ASYNC_TASKS_CAPABILITY, { backgrounded: true }),
32
+ },
33
+ };
34
+ }
35
+ /** The task fields arrive as `unknown` off the wire; only non-blank strings carry a link. */
36
+ function nonBlankTaskField(value) {
37
+ if (typeof value !== "string")
38
+ return undefined;
39
+ const trimmed = value.trim();
40
+ return trimmed ? trimmed : undefined;
41
+ }
@@ -0,0 +1,27 @@
1
+ import type { SessionNotification } from "@agentclientprotocol/sdk";
2
+ type SessionUpdate = SessionNotification["update"];
3
+ /**
4
+ * Removes the `_meta` keys of a `tool_call_update` that an earlier report of
5
+ * the same tool call already sent with the same value.
6
+ *
7
+ * Only an AIR client gets the filtered updates, because AIR merges these
8
+ * `_meta` keys. ACP does not define a merge for `_meta` keys, so another
9
+ * client gets every key on every update.
10
+ *
11
+ * It runs on the final notification, after the native subagent routing,
12
+ * because the routing reads `claudeCode.toolName` and
13
+ * `claudeCode.parentToolUseId` on every update. It compares the keys of
14
+ * `claudeCode`, of `jetbrains.air`, and the flags `is_mcp_tool_call` and
15
+ * `terminal_info`. It never compares `terminal_output`,
16
+ * `terminal_output_delta`, `terminal_exit`, or `mcp_output_delta`: a client
17
+ * appends their data. An update with nothing left is dropped.
18
+ */
19
+ export declare class ChangedMetaFilter {
20
+ private readonly sent;
21
+ /** Returns the update without the unchanged keys, or null when nothing is left. */
22
+ apply(update: SessionUpdate): SessionUpdate | null;
23
+ /** Stores the entry as the most recent one, and forgets the oldest one past the limit. */
24
+ private remember;
25
+ }
26
+ export {};
27
+ //# sourceMappingURL=changed-meta-filter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"changed-meta-filter.d.ts","sourceRoot":"","sources":["../../src/tool-calls/changed-meta-filter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAEpE,KAAK,aAAa,GAAG,mBAAmB,CAAC,QAAQ,CAAC,CAAC;AAWnD;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAA0C;IAE/D,mFAAmF;IACnF,KAAK,CAAC,MAAM,EAAE,aAAa,GAAG,aAAa,GAAG,IAAI;IAuBlD,0FAA0F;IAC1F,OAAO,CAAC,QAAQ;CAQjB"}