@lucascouts/claude-agent-acp-plus 0.8.1 → 0.9.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 (62) hide show
  1. package/dist/acp-agent.d.ts +107 -95
  2. package/dist/acp-agent.d.ts.map +1 -1
  3. package/dist/acp-agent.js +1050 -817
  4. package/dist/clear-context-coordinator.d.ts +58 -0
  5. package/dist/clear-context-coordinator.d.ts.map +1 -0
  6. package/dist/clear-context-coordinator.js +79 -0
  7. package/dist/exit-plan.d.ts +42 -0
  8. package/dist/exit-plan.d.ts.map +1 -0
  9. package/dist/exit-plan.js +154 -0
  10. package/dist/file-change-audit.d.ts +56 -0
  11. package/dist/file-change-audit.d.ts.map +1 -0
  12. package/dist/file-change-audit.js +379 -0
  13. package/dist/index.js +24 -1
  14. package/dist/permissions/effects.d.ts +19 -0
  15. package/dist/permissions/effects.d.ts.map +1 -0
  16. package/dist/permissions/effects.js +166 -0
  17. package/dist/permissions/modes.d.ts +8 -0
  18. package/dist/permissions/modes.d.ts.map +1 -0
  19. package/dist/permissions/modes.js +41 -0
  20. package/dist/permissions/normalization.d.ts +6 -0
  21. package/dist/permissions/normalization.d.ts.map +1 -0
  22. package/dist/permissions/normalization.js +100 -0
  23. package/dist/permissions/options/filesystem.d.ts +9 -0
  24. package/dist/permissions/options/filesystem.d.ts.map +1 -0
  25. package/dist/permissions/options/filesystem.js +124 -0
  26. package/dist/permissions/options/shared.d.ts +39 -0
  27. package/dist/permissions/options/shared.d.ts.map +1 -0
  28. package/dist/permissions/options/shared.js +64 -0
  29. package/dist/permissions/options/shell.d.ts +5 -0
  30. package/dist/permissions/options/shell.d.ts.map +1 -0
  31. package/dist/permissions/options/shell.js +100 -0
  32. package/dist/permissions/options/tools.d.ts +11 -0
  33. package/dist/permissions/options/tools.d.ts.map +1 -0
  34. package/dist/permissions/options/tools.js +135 -0
  35. package/dist/permissions/options.d.ts +5 -0
  36. package/dist/permissions/options.d.ts.map +1 -0
  37. package/dist/permissions/options.js +60 -0
  38. package/dist/permissions/presentation.d.ts +15 -0
  39. package/dist/permissions/presentation.d.ts.map +1 -0
  40. package/dist/permissions/presentation.js +83 -0
  41. package/dist/permissions/response.d.ts +11 -0
  42. package/dist/permissions/response.d.ts.map +1 -0
  43. package/dist/permissions/response.js +19 -0
  44. package/dist/session-config-ids.d.ts +5 -0
  45. package/dist/session-config-ids.d.ts.map +1 -0
  46. package/dist/session-config-ids.js +4 -0
  47. package/dist/session-failure-extension.d.ts +104 -0
  48. package/dist/session-failure-extension.d.ts.map +1 -0
  49. package/dist/session-failure-extension.js +352 -0
  50. package/dist/session-mode.d.ts +66 -0
  51. package/dist/session-mode.d.ts.map +1 -0
  52. package/dist/session-mode.js +246 -0
  53. package/dist/session-titles.d.ts +77 -0
  54. package/dist/session-titles.d.ts.map +1 -0
  55. package/dist/session-titles.js +199 -0
  56. package/dist/tool-result-meta.d.ts +8 -0
  57. package/dist/tool-result-meta.d.ts.map +1 -0
  58. package/dist/tool-result-meta.js +19 -0
  59. package/dist/tools.d.ts +4 -6
  60. package/dist/tools.d.ts.map +1 -1
  61. package/dist/tools.js +129 -24
  62. package/package.json +3 -3
@@ -0,0 +1,77 @@
1
+ /**
2
+ * AI-generated session titles.
3
+ *
4
+ * Claude Code's own auto-title generation never runs under the Agent SDK — the
5
+ * latch that arms it is pre-set in the headless path — so `SDKSessionInfo.summary`
6
+ * degrades to the raw first prompt. This module asks the CLI for a real title
7
+ * instead, via the `generate_session_title` control request, and publishes it
8
+ * over ACP as a `session_info_update`.
9
+ *
10
+ * {@link SessionTitles} holds the per-session state and is owned by `Session`.
11
+ * `acp-agent.ts` drives it from four places: {@link SessionTitles.onPrompt} and
12
+ * {@link SessionTitles.onAssistantText} to collect text,
13
+ * {@link SessionTitles.onTurnEnd} at `session_state_changed: idle`, and
14
+ * {@link SessionTitles.reset} on `conversation_reset`.
15
+ */
16
+ import type { ContentBlock, PromptRequest } from "@agentclientprotocol/sdk";
17
+ import type { ClaudeAcpAgent, Session } from "./acp-agent.js";
18
+ export declare function sanitizeTitle(text: string): string;
19
+ /** Rolling text a title is generated from: `previous` plus `text`, keeping only
20
+ * the trailing {@link MAX_TITLE_CONTEXT_LENGTH} characters. */
21
+ export declare function appendTitleContext(previous: string | undefined, text: string): string;
22
+ /** One session's title: the text a generated title is derived from, whether a
23
+ * title has been settled on, and the last one published. Created with the
24
+ * session and reachable as `session.titles`. */
25
+ export declare class SessionTitles {
26
+ private readonly agent;
27
+ private readonly sessionId;
28
+ /** Last title pushed to the client via `session_info_update`, so an unchanged
29
+ * title is never re-notified. Undefined until the first push. */
30
+ private lastTitle?;
31
+ /** Rolling tail of this session's own user + assistant text, capped at
32
+ * {@link MAX_TITLE_CONTEXT_LENGTH}. Dropped once a title exists. */
33
+ private context?;
34
+ /** Set once this session has a title or has asked for one. A title is
35
+ * generated at most once per session: the SDK reports a generated title in
36
+ * the same field as a user `/rename`, so re-titling could silently overwrite
37
+ * one. Released by {@link reset}, and when generation yields nothing. */
38
+ private settled;
39
+ constructor(agent: ClaudeAcpAgent, sessionId: string);
40
+ /** Collect a prompt's own text for the title, skipping openers not worth
41
+ * titling after. No-op once the title is settled. */
42
+ onPrompt(prompt: PromptRequest["prompt"]): void;
43
+ /** Collect the assistant's own answer for the title. Chunks are appended
44
+ * verbatim so streamed text reassembles. No-op once the title is settled. */
45
+ onAssistantText(content: ContentBlock): void;
46
+ /** Drop the title state so the next turn re-evaluates. Used on
47
+ * `conversation_reset`, which mounts a fresh transcript. */
48
+ reset(): void;
49
+ /** Turn-end title handling. `idle` is the SDK's turn-over signal, so it is
50
+ * when a title may have landed or become generatable.
51
+ *
52
+ * `info.customTitle` is non-empty only once the session file holds a real
53
+ * title — a user `/rename` or one generated earlier; the SDK folds both into
54
+ * that one field. Adopt it and latch, so neither is ever titled over.
55
+ *
56
+ * With no title yet, ask the SDK to generate one in the background: it is a
57
+ * ~2s small-model call and turn-end must not wait on it. Only when that is
58
+ * not possible do we fall back to `summary`, which for an SDK-driven session
59
+ * is just the raw first prompt. */
60
+ onTurnEnd(session: Session): Promise<void>;
61
+ /** Read the SDK's stored info for this session. A missing session file or read
62
+ * error is non-fatal: the title is best-effort and another turn will retry. */
63
+ private readSessionInfo;
64
+ /** Notify the client of a title, unless it is the one we last sent. */
65
+ private publish;
66
+ /** Whether to ask the SDK for a generated title now: once per session, on a
67
+ * live query, with enough collected text for the generator to work with. */
68
+ private canRequest;
69
+ /** Generate a title from the session's own text and publish it. `persist`
70
+ * writes it to the session file, which is what carries it into
71
+ * `session/list` and `session/load`. A null or failed generation releases the
72
+ * latch so a later turn retries, and publishes `fallback` (the stored
73
+ * summary) so a backend that can't generate titles is no worse off than
74
+ * before. */
75
+ private requestGenerateTitle;
76
+ }
77
+ //# sourceMappingURL=session-titles.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-titles.d.ts","sourceRoot":"","sources":["../src/session-titles.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAE5E,OAAO,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AA0B9D,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAUlD;AAED;gEACgE;AAChE,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAGrF;AAOD;;iDAEiD;AACjD,qBAAa,aAAa;IAgBtB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAhB5B;sEACkE;IAClE,OAAO,CAAC,SAAS,CAAC,CAAS;IAE3B;yEACqE;IACrE,OAAO,CAAC,OAAO,CAAC,CAAS;IAEzB;;;8EAG0E;IAC1E,OAAO,CAAC,OAAO,CAAS;gBAGL,KAAK,EAAE,cAAc,EACrB,SAAS,EAAE,MAAM;IAGpC;0DACsD;IACtD,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC,QAAQ,CAAC,GAAG,IAAI;IAW/C;kFAC8E;IAC9E,eAAe,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI;IAO5C;iEAC6D;IAC7D,KAAK,IAAI,IAAI;IAMb;;;;;;;;;;wCAUoC;IAC9B,SAAS,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IA+BhD;oFACgF;YAClE,eAAe;IAS7B,uEAAuE;YACzD,OAAO;IAgBrB;iFAC6E;IAC7E,OAAO,CAAC,UAAU;IAUlB;;;;;kBAKc;YACA,oBAAoB;CAoCnC"}
@@ -0,0 +1,199 @@
1
+ /**
2
+ * AI-generated session titles.
3
+ *
4
+ * Claude Code's own auto-title generation never runs under the Agent SDK — the
5
+ * latch that arms it is pre-set in the headless path — so `SDKSessionInfo.summary`
6
+ * degrades to the raw first prompt. This module asks the CLI for a real title
7
+ * instead, via the `generate_session_title` control request, and publishes it
8
+ * over ACP as a `session_info_update`.
9
+ *
10
+ * {@link SessionTitles} holds the per-session state and is owned by `Session`.
11
+ * `acp-agent.ts` drives it from four places: {@link SessionTitles.onPrompt} and
12
+ * {@link SessionTitles.onAssistantText} to collect text,
13
+ * {@link SessionTitles.onTurnEnd} at `session_state_changed: idle`, and
14
+ * {@link SessionTitles.reset} on `conversation_reset`.
15
+ */
16
+ import { getSessionInfo } from "@anthropic-ai/claude-agent-sdk";
17
+ const MAX_TITLE_LENGTH = 256;
18
+ /** How much session text the title is generated from. The CLI caps its own
19
+ * title input at the same 1000 trailing characters. */
20
+ const MAX_TITLE_CONTEXT_LENGTH = 1000;
21
+ /** Below this the generator returns null without calling the model, so there is
22
+ * nothing to gain by asking yet. */
23
+ const MIN_TITLE_CONTEXT_LENGTH = 10;
24
+ export function sanitizeTitle(text) {
25
+ // Replace newlines and collapse whitespace
26
+ const sanitized = text
27
+ .replace(/[\r\n]+/g, " ")
28
+ .replace(/\s+/g, " ")
29
+ .trim();
30
+ if (sanitized.length <= MAX_TITLE_LENGTH) {
31
+ return sanitized;
32
+ }
33
+ return sanitized.slice(0, MAX_TITLE_LENGTH - 1) + "…";
34
+ }
35
+ /** Rolling text a title is generated from: `previous` plus `text`, keeping only
36
+ * the trailing {@link MAX_TITLE_CONTEXT_LENGTH} characters. */
37
+ export function appendTitleContext(previous, text) {
38
+ const next = (previous ?? "") + text;
39
+ return next.length > MAX_TITLE_CONTEXT_LENGTH ? next.slice(-MAX_TITLE_CONTEXT_LENGTH) : next;
40
+ }
41
+ /** Whether this SDK still offers on-demand title generation. */
42
+ function supportsTitleGeneration(query) {
43
+ return typeof query.generateSessionTitle === "function";
44
+ }
45
+ /** One session's title: the text a generated title is derived from, whether a
46
+ * title has been settled on, and the last one published. Created with the
47
+ * session and reachable as `session.titles`. */
48
+ export class SessionTitles {
49
+ agent;
50
+ sessionId;
51
+ /** Last title pushed to the client via `session_info_update`, so an unchanged
52
+ * title is never re-notified. Undefined until the first push. */
53
+ lastTitle;
54
+ /** Rolling tail of this session's own user + assistant text, capped at
55
+ * {@link MAX_TITLE_CONTEXT_LENGTH}. Dropped once a title exists. */
56
+ context;
57
+ /** Set once this session has a title or has asked for one. A title is
58
+ * generated at most once per session: the SDK reports a generated title in
59
+ * the same field as a user `/rename`, so re-titling could silently overwrite
60
+ * one. Released by {@link reset}, and when generation yields nothing. */
61
+ settled = false;
62
+ constructor(agent, sessionId) {
63
+ this.agent = agent;
64
+ this.sessionId = sessionId;
65
+ }
66
+ /** Collect a prompt's own text for the title, skipping openers not worth
67
+ * titling after. No-op once the title is settled. */
68
+ onPrompt(prompt) {
69
+ if (this.settled) {
70
+ return;
71
+ }
72
+ const promptText = prompt
73
+ .flatMap((chunk) => (chunk.type === "text" ? [chunk.text] : []))
74
+ .join("\n");
75
+ this.context = appendTitleContext(this.context, `\n${promptText}\n`);
76
+ }
77
+ /** Collect the assistant's own answer for the title. Chunks are appended
78
+ * verbatim so streamed text reassembles. No-op once the title is settled. */
79
+ onAssistantText(content) {
80
+ if (this.settled || content.type !== "text") {
81
+ return;
82
+ }
83
+ this.context = appendTitleContext(this.context, content.text);
84
+ }
85
+ /** Drop the title state so the next turn re-evaluates. Used on
86
+ * `conversation_reset`, which mounts a fresh transcript. */
87
+ reset() {
88
+ this.settled = false;
89
+ this.context = undefined;
90
+ this.lastTitle = undefined;
91
+ }
92
+ /** Turn-end title handling. `idle` is the SDK's turn-over signal, so it is
93
+ * when a title may have landed or become generatable.
94
+ *
95
+ * `info.customTitle` is non-empty only once the session file holds a real
96
+ * title — a user `/rename` or one generated earlier; the SDK folds both into
97
+ * that one field. Adopt it and latch, so neither is ever titled over.
98
+ *
99
+ * With no title yet, ask the SDK to generate one in the background: it is a
100
+ * ~2s small-model call and turn-end must not wait on it. Only when that is
101
+ * not possible do we fall back to `summary`, which for an SDK-driven session
102
+ * is just the raw first prompt. */
103
+ async onTurnEnd(session) {
104
+ const info = await this.readSessionInfo(session);
105
+ if (info?.customTitle) {
106
+ this.settled = true;
107
+ this.context = undefined;
108
+ await this.publish(info.customTitle, info.lastModified);
109
+ return;
110
+ }
111
+ const fallback = info?.summary
112
+ ? { title: info.summary, lastModified: info.lastModified }
113
+ : undefined;
114
+ if (this.canRequest(session)) {
115
+ this.settled = true;
116
+ void this.requestGenerateTitle(session, fallback).catch((error) => {
117
+ this.agent.logger.error(`Session ${this.sessionId}: session title update failed: ${error}`);
118
+ });
119
+ return;
120
+ }
121
+ // Only while this session has no title of its own: `summary` degrades to the
122
+ // raw first prompt, which must never overwrite a generated title if the
123
+ // persisted one is slow to show up in `info`.
124
+ if (fallback && !this.settled) {
125
+ await this.publish(fallback.title, fallback.lastModified);
126
+ }
127
+ }
128
+ /** Read the SDK's stored info for this session. A missing session file or read
129
+ * error is non-fatal: the title is best-effort and another turn will retry. */
130
+ async readSessionInfo(session) {
131
+ try {
132
+ return await getSessionInfo(this.sessionId, { dir: session.cwd });
133
+ }
134
+ catch (error) {
135
+ this.agent.logger.error(`Session ${this.sessionId}: failed to read session info: ${error}`);
136
+ return undefined;
137
+ }
138
+ }
139
+ /** Notify the client of a title, unless it is the one we last sent. */
140
+ async publish(rawTitle, lastModified) {
141
+ const title = sanitizeTitle(rawTitle);
142
+ if (title === this.lastTitle) {
143
+ return;
144
+ }
145
+ this.lastTitle = title;
146
+ await this.agent.client.sessionUpdate({
147
+ sessionId: this.sessionId,
148
+ update: {
149
+ sessionUpdate: "session_info_update",
150
+ title,
151
+ updatedAt: new Date(lastModified).toISOString(),
152
+ },
153
+ });
154
+ }
155
+ /** Whether to ask the SDK for a generated title now: once per session, on a
156
+ * live query, with enough collected text for the generator to work with. */
157
+ canRequest(session) {
158
+ if (this.settled || session.queryClosed || session.cancelled) {
159
+ return false;
160
+ }
161
+ if (!supportsTitleGeneration(session.query)) {
162
+ return false;
163
+ }
164
+ return (this.context?.trim().length ?? 0) >= MIN_TITLE_CONTEXT_LENGTH;
165
+ }
166
+ /** Generate a title from the session's own text and publish it. `persist`
167
+ * writes it to the session file, which is what carries it into
168
+ * `session/list` and `session/load`. A null or failed generation releases the
169
+ * latch so a later turn retries, and publishes `fallback` (the stored
170
+ * summary) so a backend that can't generate titles is no worse off than
171
+ * before. */
172
+ async requestGenerateTitle(session, fallback) {
173
+ const description = this.context?.trim() ?? "";
174
+ let title = null;
175
+ try {
176
+ title =
177
+ (await session.query.generateSessionTitle?.(description, {
178
+ persist: true,
179
+ })) ?? null;
180
+ }
181
+ catch (error) {
182
+ this.agent.logger.error(`Session ${this.sessionId}: failed to generate a session title: ${error}`);
183
+ }
184
+ // A session torn down or replaced while the title was in flight must not
185
+ // adopt it.
186
+ if (this.agent.sessions[this.sessionId] !== session) {
187
+ return;
188
+ }
189
+ if (!title) {
190
+ this.settled = false;
191
+ if (fallback) {
192
+ await this.publish(fallback.title, fallback.lastModified);
193
+ }
194
+ return;
195
+ }
196
+ this.context = undefined;
197
+ await this.publish(title, Date.now());
198
+ }
199
+ }
@@ -0,0 +1,8 @@
1
+ export type ToolResultMeta = {
2
+ nonExecutionKind: string;
3
+ userFeedback?: string;
4
+ };
5
+ /** Validate the SDK's currently untyped tool_result_meta sidecar. Unknown
6
+ * non-execution kinds are preserved so newer CLIs remain forward-compatible. */
7
+ export declare function parseToolResultMeta(raw: unknown): Map<string, ToolResultMeta> | undefined;
8
+ //# sourceMappingURL=tool-result-meta.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-result-meta.d.ts","sourceRoot":"","sources":["../src/tool-result-meta.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,cAAc,GAAG;IAC3B,gBAAgB,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF;gFACgF;AAChF,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,GAAG,SAAS,CAczF"}
@@ -0,0 +1,19 @@
1
+ /** Validate the SDK's currently untyped tool_result_meta sidecar. Unknown
2
+ * non-execution kinds are preserved so newer CLIs remain forward-compatible. */
3
+ export function parseToolResultMeta(raw) {
4
+ if (!Array.isArray(raw))
5
+ return undefined;
6
+ let byToolUseId;
7
+ for (const entry of raw) {
8
+ if (typeof entry !== "object" || entry === null)
9
+ continue;
10
+ const { id, non_execution_kind, user_feedback } = entry;
11
+ if (typeof id !== "string" || typeof non_execution_kind !== "string")
12
+ continue;
13
+ (byToolUseId ??= new Map()).set(id, {
14
+ nonExecutionKind: non_execution_kind,
15
+ ...(typeof user_feedback === "string" ? { userFeedback: user_feedback } : {}),
16
+ });
17
+ }
18
+ return byToolUseId;
19
+ }
package/dist/tools.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { PlanEntry, ToolCallContent, ToolCallLocation, ToolKind } from "@agentclientprotocol/sdk";
2
2
  import { HookCallback } from "@anthropic-ai/claude-agent-sdk";
3
- import { TaskCreateInput, TaskCreateOutput, TaskUpdateInput } from "@anthropic-ai/claude-agent-sdk/sdk-tools.js";
3
+ import { TaskCreateInput, TaskCreateOutput, TaskListOutput, TaskUpdateInput, TaskUpdateOutput } from "@anthropic-ai/claude-agent-sdk/sdk-tools.js";
4
4
  import { ToolResultBlockParam, WebSearchToolResultBlockParam } from "@anthropic-ai/sdk/resources";
5
5
  import { BetaBashCodeExecutionToolResultBlockParam, BetaCodeExecutionToolResultBlockParam, BetaRequestMCPToolResultBlockParam, BetaTextEditorCodeExecutionToolResultBlockParam, BetaToolResultBlockParam, BetaToolSearchToolResultBlockParam, BetaWebFetchToolResultBlockParam, BetaWebSearchToolResultBlockParam } from "@anthropic-ai/sdk/resources/beta.mjs";
6
6
  interface ToolInfo {
@@ -57,14 +57,12 @@ export type TaskEntry = {
57
57
  description?: string;
58
58
  };
59
59
  export type TaskState = Map<string, TaskEntry>;
60
- /**
61
- * Best-effort parse of a TaskCreate tool_result content into the structured
62
- * TaskCreateOutput. The SDK delivers tool outputs either as a string or as
63
- * an array of TextBlockParam-like blocks containing JSON text; try both.
64
- */
65
60
  export declare function parseTaskCreateOutput(content: unknown): TaskCreateOutput | undefined;
61
+ export declare function parseTaskListOutput(content: unknown): TaskListOutput | undefined;
62
+ export declare function parseTaskUpdateOutput(content: unknown, expectedTaskId?: string): TaskUpdateOutput | undefined;
66
63
  export declare function applyTaskCreate(state: TaskState, input: TaskCreateInput | undefined, output: TaskCreateOutput | undefined): void;
67
64
  export declare function applyTaskUpdate(state: TaskState, input: TaskUpdateInput | undefined): void;
65
+ export declare function applyTaskList(state: TaskState, output: TaskListOutput): void;
68
66
  export declare function taskStateToPlanEntries(state: TaskState): PlanEntry[];
69
67
  export declare function markdownEscape(text: string): string;
70
68
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"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"}
package/dist/tools.js CHANGED
@@ -96,7 +96,7 @@ export function toolInfoFromToolUse(toolUse, supportsTerminalOutput = false, cwd
96
96
  }
97
97
  const displayPath = input?.file_path ? toDisplayPath(input.file_path, cwd) : undefined;
98
98
  return {
99
- title: displayPath ? `Write ${displayPath}` : "Write",
99
+ title: displayPath ? `Write ${displayPath}` : "Preparing file…",
100
100
  kind: "edit",
101
101
  content,
102
102
  locations: input?.file_path ? [{ path: input.file_path }] : [],
@@ -211,15 +211,8 @@ export function toolInfoFromToolUse(toolUse, supportsTerminalOutput = false, cwd
211
211
  }
212
212
  case "WebSearch": {
213
213
  const input = toolUse.input;
214
- let label = input?.query ? `"${input.query}"` : "Web search";
215
- if (input?.allowed_domains && input.allowed_domains.length > 0) {
216
- label += ` (allowed: ${input.allowed_domains.join(", ")})`;
217
- }
218
- if (input?.blocked_domains && input.blocked_domains.length > 0) {
219
- label += ` (blocked: ${input.blocked_domains.join(", ")})`;
220
- }
221
214
  return {
222
- title: label,
215
+ title: input?.query ? `Search "${input.query}"` : "Web search",
223
216
  kind: "fetch",
224
217
  content: [],
225
218
  };
@@ -284,13 +277,22 @@ export function toolInfoFromToolUse(toolUse, supportsTerminalOutput = false, cwd
284
277
  case "ExitPlanMode": {
285
278
  const planInput = toolUse.input;
286
279
  return {
287
- title: "Ready to code?",
280
+ title: "Approve Plan",
288
281
  kind: "switch_mode",
289
282
  content: planInput?.plan
290
283
  ? [{ type: "content", content: { type: "text", text: planInput.plan } }]
291
284
  : [],
292
285
  };
293
286
  }
287
+ case "Skill": {
288
+ const input = toolUse.input;
289
+ const skillName = input?.skill;
290
+ return {
291
+ title: skillName ? `Load skill: ${skillName}` : "Load skill",
292
+ kind: "other",
293
+ content: [],
294
+ };
295
+ }
294
296
  case "AskUserQuestion": {
295
297
  const input = toolUse.input;
296
298
  const questions = Array.isArray(input?.questions) ? input.questions : [];
@@ -658,6 +660,9 @@ export function toolUpdateFromToolResult(toolResult, toolUse, supportsTerminalOu
658
660
  // renders, no worse than before.
659
661
  return toAcpContentUpdate(stripAgentTrailerFromContent(toolResult.content), "is_error" in toolResult ? toolResult.is_error : false);
660
662
  }
663
+ case "Skill": {
664
+ return {};
665
+ }
661
666
  case "Edit": // Edit is handled in hooks
662
667
  case "Write": {
663
668
  return {};
@@ -798,35 +803,32 @@ function toAcpContentBlock(content, isError) {
798
803
  }
799
804
  export function planEntries(input) {
800
805
  return (input?.todos ?? []).map((todo) => ({
801
- content: todo.content,
806
+ content: todo.status === "in_progress" && todo.activeForm ? todo.activeForm : todo.content,
802
807
  status: todo.status,
803
808
  priority: "medium",
804
809
  }));
805
810
  }
806
811
  /**
807
- * Best-effort parse of a TaskCreate tool_result content into the structured
808
- * TaskCreateOutput. The SDK delivers tool outputs either as a string or as
809
- * an array of TextBlockParam-like blocks containing JSON text; try both.
812
+ * Best-effort parse of a structured Task* tool_result. The SDK delivers tool
813
+ * outputs either as a string or as an array of TextBlockParam-like blocks
814
+ * containing JSON text; try both.
810
815
  */
811
- export function parseTaskCreateOutput(content) {
816
+ function parseJsonToolOutput(content, isExpectedOutput) {
812
817
  const tryParse = (text) => {
813
818
  try {
814
819
  const parsed = JSON.parse(text);
815
- if (parsed &&
816
- typeof parsed === "object" &&
817
- parsed.task &&
818
- typeof parsed.task.id === "string") {
819
- return parsed;
820
- }
820
+ return isExpectedOutput(parsed) ? parsed : undefined;
821
821
  }
822
822
  catch {
823
- // ignore
823
+ return undefined;
824
824
  }
825
- return undefined;
826
825
  };
827
826
  if (typeof content === "string") {
828
827
  return tryParse(content);
829
828
  }
829
+ if (content && typeof content === "object" && !Array.isArray(content)) {
830
+ return isExpectedOutput(content) ? content : undefined;
831
+ }
830
832
  if (Array.isArray(content)) {
831
833
  for (const block of content) {
832
834
  if (block && typeof block === "object" && "type" in block && block.type === "text") {
@@ -841,6 +843,96 @@ export function parseTaskCreateOutput(content) {
841
843
  }
842
844
  return undefined;
843
845
  }
846
+ function toolOutputTexts(content) {
847
+ if (typeof content === "string")
848
+ return [content];
849
+ if (!Array.isArray(content))
850
+ return [];
851
+ return content.flatMap((block) => block &&
852
+ typeof block === "object" &&
853
+ "type" in block &&
854
+ block.type === "text" &&
855
+ "text" in block &&
856
+ typeof block.text === "string"
857
+ ? [block.text]
858
+ : []);
859
+ }
860
+ export function parseTaskCreateOutput(content) {
861
+ const structured = parseJsonToolOutput(content, (parsed) => Boolean(parsed &&
862
+ typeof parsed === "object" &&
863
+ "task" in parsed &&
864
+ parsed.task &&
865
+ typeof parsed.task === "object" &&
866
+ "id" in parsed.task &&
867
+ typeof parsed.task.id === "string"));
868
+ if (structured)
869
+ return structured;
870
+ for (const text of toolOutputTexts(content)) {
871
+ const match = /^Task #(\S+) created successfully: (.+)$/.exec(text.trim());
872
+ if (match)
873
+ return { task: { id: match[1], subject: match[2] } };
874
+ }
875
+ return undefined;
876
+ }
877
+ export function parseTaskListOutput(content) {
878
+ const validStatuses = new Set(["pending", "in_progress", "completed"]);
879
+ const structured = parseJsonToolOutput(content, (parsed) => Boolean(parsed &&
880
+ typeof parsed === "object" &&
881
+ "tasks" in parsed &&
882
+ Array.isArray(parsed.tasks) &&
883
+ parsed.tasks.every((task) => task &&
884
+ typeof task === "object" &&
885
+ typeof task.id === "string" &&
886
+ typeof task.subject === "string" &&
887
+ typeof task.status === "string" &&
888
+ validStatuses.has(task.status))));
889
+ if (structured)
890
+ return structured;
891
+ for (const text of toolOutputTexts(content)) {
892
+ if (text.trim() === "No tasks found")
893
+ return { tasks: [] };
894
+ const tasks = [];
895
+ const lines = text.trim().split("\n");
896
+ for (const line of lines) {
897
+ const match = /^#(\S+) \[(pending|in_progress|completed)\] (.+?)(?: \(([^()]*)\))?(?: \[blocked by ((?:#[^,\]]+(?:, )?)+)\])?$/.exec(line);
898
+ if (!match) {
899
+ tasks.length = 0;
900
+ break;
901
+ }
902
+ tasks.push({
903
+ id: match[1],
904
+ subject: match[3],
905
+ status: match[2],
906
+ ...(match[4] ? { owner: match[4] } : {}),
907
+ blockedBy: match[5] ? match[5].split(", ").map((id) => id.slice(1)) : [],
908
+ });
909
+ }
910
+ if (tasks.length > 0)
911
+ return { tasks };
912
+ }
913
+ return undefined;
914
+ }
915
+ export function parseTaskUpdateOutput(content, expectedTaskId) {
916
+ const structured = parseJsonToolOutput(content, (parsed) => Boolean(parsed &&
917
+ typeof parsed === "object" &&
918
+ "success" in parsed &&
919
+ typeof parsed.success === "boolean" &&
920
+ "taskId" in parsed &&
921
+ typeof parsed.taskId === "string" &&
922
+ "updatedFields" in parsed &&
923
+ Array.isArray(parsed.updatedFields) &&
924
+ parsed.updatedFields.every((field) => typeof field === "string")));
925
+ if (structured)
926
+ return structured;
927
+ for (const text of toolOutputTexts(content)) {
928
+ const notFound = /^Task #(\S+) not found$/.exec(text.trim());
929
+ const taskId = notFound?.[1] ?? expectedTaskId;
930
+ if (taskId && (notFound || text.trim() === "Failed to delete task")) {
931
+ return { success: false, taskId, updatedFields: [], error: text.trim() };
932
+ }
933
+ }
934
+ return undefined;
935
+ }
844
936
  export function applyTaskCreate(state, input, output) {
845
937
  const taskId = output?.task?.id;
846
938
  if (!taskId || !input)
@@ -872,9 +964,22 @@ export function applyTaskUpdate(state, input) {
872
964
  description: input.description ?? existing?.description,
873
965
  });
874
966
  }
967
+ export function applyTaskList(state, output) {
968
+ const previous = new Map(state);
969
+ state.clear();
970
+ for (const task of output.tasks) {
971
+ const existing = previous.get(task.id);
972
+ state.set(task.id, {
973
+ subject: task.subject,
974
+ status: task.status,
975
+ activeForm: existing?.activeForm,
976
+ description: existing?.description,
977
+ });
978
+ }
979
+ }
875
980
  export function taskStateToPlanEntries(state) {
876
981
  return Array.from(state.values()).map((task) => ({
877
- content: task.subject,
982
+ content: task.status === "in_progress" && task.activeForm ? task.activeForm : task.subject,
878
983
  status: task.status,
879
984
  priority: "medium",
880
985
  }));
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.8.1",
6
+ "version": "0.9.0",
7
7
  "description": "ACP adapter for the Claude Agent SDK with VS Code-parity UX for Zed and other ACP clients — checkbox multi-select questions, refusal consent dialog, dynamic agent name. Fork of claude-agent-acp.",
8
8
  "main": "dist/lib.js",
9
9
  "types": "dist/lib.d.ts",
@@ -68,11 +68,11 @@
68
68
  },
69
69
  "dependencies": {
70
70
  "@agentclientprotocol/sdk": "1.3.0",
71
- "@anthropic-ai/claude-agent-sdk": "0.3.233",
71
+ "@anthropic-ai/claude-agent-sdk": "0.3.235",
72
72
  "zod": "^3.25.0 || ^4.0.0"
73
73
  },
74
74
  "devDependencies": {
75
- "@anthropic-ai/sdk": "0.117.1",
75
+ "@anthropic-ai/sdk": "0.120.0",
76
76
  "@eslint/js": "10.0.1",
77
77
  "@tsconfig/node24": "24.0.5",
78
78
  "@types/node": "26.2.0",