claude-code-acp-ts 0.16.1 → 0.24.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.
package/dist/index.js CHANGED
@@ -1,20 +1,26 @@
1
1
  #!/usr/bin/env node
2
2
  // Load managed settings and apply environment variables
3
3
  import { loadManagedSettings, applyEnvironmentSettings } from "./utils.js";
4
- const managedSettings = loadManagedSettings();
5
- if (managedSettings) {
6
- applyEnvironmentSettings(managedSettings);
4
+ import { claudeCliPath, runAcp } from "./acp-agent.js";
5
+ if (process.argv.includes("--cli")) {
6
+ process.argv = process.argv.filter((arg) => arg !== "--cli");
7
+ await import(await claudeCliPath());
8
+ }
9
+ else {
10
+ const managedSettings = loadManagedSettings();
11
+ if (managedSettings) {
12
+ applyEnvironmentSettings(managedSettings);
13
+ }
14
+ // stdout is used to send messages to the client
15
+ // we redirect everything else to stderr to make sure it doesn't interfere with ACP
16
+ console.log = console.error;
17
+ console.info = console.error;
18
+ console.warn = console.error;
19
+ console.debug = console.error;
20
+ process.on("unhandledRejection", (reason, promise) => {
21
+ console.error("Unhandled Rejection at:", promise, "reason:", reason);
22
+ });
23
+ runAcp();
24
+ // Keep process alive
25
+ process.stdin.resume();
7
26
  }
8
- // stdout is used to send messages to the client
9
- // we redirect everything else to stderr to make sure it doesn't interfere with ACP
10
- console.log = console.error;
11
- console.info = console.error;
12
- console.warn = console.error;
13
- console.debug = console.error;
14
- process.on("unhandledRejection", (reason, promise) => {
15
- console.error("Unhandled Rejection at:", promise, "reason:", reason);
16
- });
17
- import { runAcp } from "./acp-agent.js";
18
- runAcp();
19
- // Keep process alive
20
- process.stdin.resume();
package/dist/lib.d.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  export { ClaudeAcpAgent, runAcp, toAcpNotifications, streamEventToAcpNotifications, type ToolUpdateMeta, type NewSessionMeta, } from "./acp-agent.js";
2
2
  export { loadManagedSettings, applyEnvironmentSettings, nodeToWebReadable, nodeToWebWritable, Pushable, unreachable, } from "./utils.js";
3
- export { createMcpServer } from "./mcp-server.js";
4
- export { toolInfoFromToolUse, planEntries, toolUpdateFromToolResult, createPreToolUseHook, acpToolNames as toolNames, } from "./tools.js";
5
- export { SettingsManager, type ClaudeCodeSettings, type PermissionSettings, type PermissionDecision, type PermissionCheckResult, type SettingsManagerOptions, } from "./settings.js";
3
+ export { toolInfoFromToolUse, toDisplayPath, planEntries, toolUpdateFromToolResult, } from "./tools.js";
4
+ export { SettingsManager, type ClaudeCodeSettings, type SettingsManagerOptions, } from "./settings.js";
6
5
  export type { ClaudePlanEntry } from "./tools.js";
7
6
  //# sourceMappingURL=lib.d.ts.map
package/dist/lib.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"lib.d.ts","sourceRoot":"","sources":["../src/lib.ts"],"names":[],"mappings":"AACA,OAAO,EACL,cAAc,EACd,MAAM,EACN,kBAAkB,EAClB,6BAA6B,EAC7B,KAAK,cAAc,EACnB,KAAK,cAAc,GACpB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,mBAAmB,EACnB,wBAAwB,EACxB,iBAAiB,EACjB,iBAAiB,EACjB,QAAQ,EACR,WAAW,GACZ,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EACL,mBAAmB,EACnB,WAAW,EACX,wBAAwB,EACxB,oBAAoB,EACpB,YAAY,IAAI,SAAS,GAC1B,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,eAAe,EACf,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,GAC5B,MAAM,eAAe,CAAC;AAGvB,YAAY,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"lib.d.ts","sourceRoot":"","sources":["../src/lib.ts"],"names":[],"mappings":"AACA,OAAO,EACL,cAAc,EACd,MAAM,EACN,kBAAkB,EAClB,6BAA6B,EAC7B,KAAK,cAAc,EACnB,KAAK,cAAc,GACpB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,mBAAmB,EACnB,wBAAwB,EACxB,iBAAiB,EACjB,iBAAiB,EACjB,QAAQ,EACR,WAAW,GACZ,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,mBAAmB,EACnB,aAAa,EACb,WAAW,EACX,wBAAwB,GACzB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,eAAe,EACf,KAAK,kBAAkB,EACvB,KAAK,sBAAsB,GAC5B,MAAM,eAAe,CAAC;AAGvB,YAAY,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC"}
package/dist/lib.js CHANGED
@@ -1,6 +1,5 @@
1
1
  // Export the main agent class and utilities for library usage
2
2
  export { ClaudeAcpAgent, runAcp, toAcpNotifications, streamEventToAcpNotifications, } from "./acp-agent.js";
3
3
  export { loadManagedSettings, applyEnvironmentSettings, nodeToWebReadable, nodeToWebWritable, Pushable, unreachable, } from "./utils.js";
4
- export { createMcpServer } from "./mcp-server.js";
5
- export { toolInfoFromToolUse, planEntries, toolUpdateFromToolResult, createPreToolUseHook, acpToolNames as toolNames, } from "./tools.js";
4
+ export { toolInfoFromToolUse, toDisplayPath, planEntries, toolUpdateFromToolResult, } from "./tools.js";
6
5
  export { SettingsManager, } from "./settings.js";
@@ -10,10 +10,6 @@
10
10
  * Docs: https://code.claude.com/docs/en/iam#tool-specific-permission-rules
11
11
  */
12
12
  export interface PermissionSettings {
13
- allow?: string[];
14
- deny?: string[];
15
- ask?: string[];
16
- additionalDirectories?: string[];
17
13
  defaultMode?: string;
18
14
  }
19
15
  export interface ClaudeCodeSettings {
@@ -21,12 +17,6 @@ export interface ClaudeCodeSettings {
21
17
  env?: Record<string, string>;
22
18
  model?: string;
23
19
  }
24
- export type PermissionDecision = "allow" | "deny" | "ask";
25
- export interface PermissionCheckResult {
26
- decision: PermissionDecision;
27
- rule?: string;
28
- source?: "allow" | "deny" | "ask";
29
- }
30
20
  /**
31
21
  * Gets the enterprise settings path based on the current platform
32
22
  */
@@ -60,7 +50,9 @@ export declare class SettingsManager {
60
50
  private onChange?;
61
51
  private logger;
62
52
  private initialized;
53
+ private disposed;
63
54
  private debounceTimer;
55
+ private initPromise;
64
56
  constructor(cwd: string, options?: SettingsManagerOptions);
65
57
  /**
66
58
  * Initialize the settings manager by loading all settings and setting up file watchers
@@ -96,14 +88,6 @@ export declare class SettingsManager {
96
88
  * Handles settings file changes with debouncing to avoid rapid reloads
97
89
  */
98
90
  private handleSettingsChange;
99
- /**
100
- * Checks if a tool invocation is allowed based on the loaded settings.
101
- *
102
- * @param toolName - The tool name (can be ACP-prefixed like mcp__acp__Read or plain like Read)
103
- * @param toolInput - The tool input object
104
- * @returns The permission decision and matching rule info
105
- */
106
- checkPermission(toolName: string, toolInput: unknown): PermissionCheckResult;
107
91
  /**
108
92
  * Returns the current merged settings
109
93
  */
@@ -1 +1 @@
1
- {"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../src/settings.ts"],"names":[],"mappings":"AAOA;;;;;;;;;;GAUG;AAEH,MAAM,WAAW,kBAAkB;IACjC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC;IACf,qBAAqB,CAAC,EAAE,MAAM,EAAE,CAAC;IACjC,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,kBAAkB;IACjC,WAAW,CAAC,EAAE,kBAAkB,CAAC;IACjC,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,MAAM,kBAAkB,GAAG,OAAO,GAAG,MAAM,GAAG,KAAK,CAAC;AAE1D,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,KAAK,CAAC;CACnC;AAwLD;;GAEG;AACH,wBAAgB,sBAAsB,IAAI,MAAM,CAW/C;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,MAAM,CAAC,EAAE;QAAE,GAAG,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAC;QAAC,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAA;KAAE,CAAC;CAC7E;AAED;;;;;;;;;;GAUG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,GAAG,CAAS;IACpB,OAAO,CAAC,YAAY,CAA0B;IAC9C,OAAO,CAAC,eAAe,CAA0B;IACjD,OAAO,CAAC,aAAa,CAA0B;IAC/C,OAAO,CAAC,kBAAkB,CAA0B;IACpD,OAAO,CAAC,cAAc,CAA0B;IAChD,OAAO,CAAC,QAAQ,CAAsB;IACtC,OAAO,CAAC,QAAQ,CAAC,CAAa;IAC9B,OAAO,CAAC,MAAM,CAAqE;IACnF,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,aAAa,CAA8C;gBAEvD,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,sBAAsB;IAMzD;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAUjC;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAI3B;;OAEG;IACH,OAAO,CAAC,sBAAsB;IAI9B;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAI5B;;OAEG;YACW,eAAe;IAgB7B;;;;OAIG;IACH,OAAO,CAAC,aAAa;IAkDrB;;OAEG;IACH,OAAO,CAAC,aAAa;IAkCrB;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAgB5B;;;;;;OAMG;IACH,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,GAAG,qBAAqB;IAuC5E;;OAEG;IACH,WAAW,IAAI,kBAAkB;IAIjC;;OAEG;IACH,MAAM,IAAI,MAAM;IAIhB;;OAEG;IACG,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAWxC;;OAEG;IACH,OAAO,IAAI,IAAI;CAYhB"}
1
+ {"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../src/settings.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;GAUG;AAEH,MAAM,WAAW,kBAAkB;IACjC,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,kBAAkB;IACjC,WAAW,CAAC,EAAE,kBAAkB,CAAC;IACjC,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AA2BD;;GAEG;AACH,wBAAgB,sBAAsB,IAAI,MAAM,CAW/C;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,MAAM,CAAC,EAAE;QAAE,GAAG,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAC;QAAC,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAA;KAAE,CAAC;CAC7E;AAED;;;;;;;;;;GAUG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,GAAG,CAAS;IACpB,OAAO,CAAC,YAAY,CAA0B;IAC9C,OAAO,CAAC,eAAe,CAA0B;IACjD,OAAO,CAAC,aAAa,CAA0B;IAC/C,OAAO,CAAC,kBAAkB,CAA0B;IACpD,OAAO,CAAC,cAAc,CAA0B;IAChD,OAAO,CAAC,QAAQ,CAAsB;IACtC,OAAO,CAAC,QAAQ,CAAC,CAAa;IAC9B,OAAO,CAAC,MAAM,CAAqE;IACnF,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,aAAa,CAA8C;IACnE,OAAO,CAAC,WAAW,CAA8B;gBAErC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,sBAAsB;IAMzD;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAmBjC;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAI3B;;OAEG;IACH,OAAO,CAAC,sBAAsB;IAI9B;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAI5B;;OAEG;YACW,eAAe;IAgB7B;;;;OAIG;IACH,OAAO,CAAC,aAAa;IA8BrB;;OAEG;IACH,OAAO,CAAC,aAAa;IAkCrB;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAqB5B;;OAEG;IACH,WAAW,IAAI,kBAAkB;IAIjC;;OAEG;IACH,MAAM,IAAI,MAAM;IAIhB;;OAEG;IACG,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAUxC;;OAEG;IACH,OAAO,IAAI,IAAI;CAehB"}
package/dist/settings.js CHANGED
@@ -1,155 +1,12 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
- import * as os from "node:os";
4
- import { minimatch } from "minimatch";
5
- import { ACP_TOOL_NAME_PREFIX, acpToolNames } from "./tools.js";
6
3
  import { CLAUDE_CONFIG_DIR } from "./acp-agent.js";
7
4
  /**
8
- * Shell operators that can be used for command chaining/injection
9
- * These should cause a prefix match to fail to prevent bypasses like:
10
- * - "safe-cmd && malicious-cmd"
11
- * - "safe-cmd; malicious-cmd"
12
- * - "safe-cmd | malicious-cmd"
13
- * - "safe-cmd || malicious-cmd"
14
- * - "$(malicious-cmd)"
15
- * - "`malicious-cmd`"
5
+ * Reads and parses a JSON settings file, returning an empty object if not found or invalid.
6
+ * Silently ignores missing files (ENOENT) but logs warnings for other errors
7
+ * (malformed JSON, permission errors, etc.) to aid debugging.
16
8
  */
17
- const SHELL_OPERATORS = ["&&", "||", ";", "|", "$(", "`", "\n"];
18
- /**
19
- * Checks if a string contains shell operators that could allow command chaining
20
- */
21
- function containsShellOperator(str) {
22
- return SHELL_OPERATORS.some((op) => str.includes(op));
23
- }
24
- /*
25
- * Tools that modify files. Per Claude Code docs:
26
- * "Edit rules apply to all built-in tools that edit files."
27
- * This means an Edit(...) rule should match Write, MultiEdit, etc.
28
- */
29
- const FILE_EDITING_TOOLS = [acpToolNames.edit, acpToolNames.write];
30
- /**
31
- * Tools that read files. Per Claude Code docs:
32
- * "Claude will make a best-effort attempt to apply Read rules to all built-in tools
33
- * that read files like Grep and Glob."
34
- * This means a Read(...) rule should match Grep, Glob, etc.
35
- */
36
- const FILE_READING_TOOLS = [acpToolNames.read];
37
- /**
38
- * Functions to extract the relevant argument from tool input for permission matching
39
- */
40
- const TOOL_ARG_ACCESSORS = {
41
- mcp__acp__Read: (input) => input?.file_path,
42
- mcp__acp__Edit: (input) => input?.file_path,
43
- mcp__acp__Write: (input) => input?.file_path,
44
- mcp__acp__Bash: (input) => input?.command,
45
- };
46
- /**
47
- * Parses a permission rule string into its components
48
- * Examples:
49
- * "Read" -> { toolName: "Read" }
50
- * "Read(./.env)" -> { toolName: "Read", argument: "./.env" }
51
- * "Bash(npm run:*)" -> { toolName: "Bash", argument: "npm run", isWildcard: true }
52
- */
53
- function parseRule(rule) {
54
- const match = rule.match(/^(\w+)(?:\((.+)\))?$/);
55
- if (!match) {
56
- return { toolName: rule };
57
- }
58
- const [, toolName, argument] = match;
59
- if (argument && argument.endsWith(":*")) {
60
- return {
61
- toolName,
62
- argument: argument.slice(0, -2),
63
- isWildcard: true,
64
- };
65
- }
66
- return { toolName, argument };
67
- }
68
- /**
69
- * Normalizes a path for comparison:
70
- * - Expands ~ to home directory
71
- * - Resolves relative paths against cwd
72
- * - Normalizes path separators
73
- */
74
- function normalizePath(filePath, cwd) {
75
- if (filePath.startsWith("~/")) {
76
- filePath = path.join(os.homedir(), filePath.slice(2));
77
- }
78
- else if (filePath.startsWith("./")) {
79
- filePath = path.join(cwd, filePath.slice(2));
80
- }
81
- else if (!path.isAbsolute(filePath)) {
82
- filePath = path.join(cwd, filePath);
83
- }
84
- // Convert backslashes to forward slashes for minimatch compatibility on Windows
85
- return path.normalize(filePath).replace(/\\/g, "/");
86
- }
87
- /**
88
- * Checks if a file path matches a glob pattern
89
- */
90
- function matchesGlob(pattern, filePath, cwd) {
91
- const normalizedPattern = normalizePath(pattern, cwd);
92
- const normalizedPath = normalizePath(filePath, cwd);
93
- return minimatch(normalizedPath, normalizedPattern, {
94
- dot: true,
95
- matchBase: false,
96
- nocase: process.platform === "win32",
97
- });
98
- }
99
- /**
100
- * Checks if a tool invocation matches a parsed permission rule
101
- */
102
- function matchesRule(rule, toolName, toolInput, cwd) {
103
- // Per Claude Code docs:
104
- // - "Edit rules apply to all built-in tools that edit files."
105
- // - "Claude will make a best-effort attempt to apply Read rules to all built-in tools
106
- // that read files like Grep, Glob, and LS."
107
- const ruleAppliesToTool = (rule.toolName === "Bash" && toolName === acpToolNames.bash) ||
108
- (rule.toolName === "Edit" && FILE_EDITING_TOOLS.includes(toolName)) ||
109
- (rule.toolName === "Read" && FILE_READING_TOOLS.includes(toolName));
110
- if (!ruleAppliesToTool) {
111
- return false;
112
- }
113
- if (!rule.argument) {
114
- return true;
115
- }
116
- const argAccessor = TOOL_ARG_ACCESSORS[toolName];
117
- if (!argAccessor) {
118
- return true;
119
- }
120
- const actualArg = argAccessor(toolInput);
121
- if (!actualArg) {
122
- return false;
123
- }
124
- if (toolName === acpToolNames.bash) {
125
- // Per Claude Code docs: https://code.claude.com/docs/en/iam#tool-specific-permission-rules
126
- // - Bash(npm run build) matches the EXACT command "npm run build"
127
- // - Bash(npm run test:*) matches commands STARTING WITH "npm run test"
128
- // The :* suffix enables prefix matching, without it the match is exact
129
- //
130
- // Also from docs: "Claude Code is aware of shell operators (like &&) so a prefix match
131
- // rule like Bash(safe-cmd:*) won't give it permission to run the command safe-cmd && other-cmd"
132
- if (rule.isWildcard) {
133
- if (!actualArg.startsWith(rule.argument)) {
134
- return false;
135
- }
136
- // Check that the matched prefix isn't followed by shell operators that could
137
- // allow command chaining/injection
138
- const remainder = actualArg.slice(rule.argument.length);
139
- if (containsShellOperator(remainder)) {
140
- return false;
141
- }
142
- return true;
143
- }
144
- return actualArg === rule.argument;
145
- }
146
- // For file-based tools (Read, Edit, Write), use glob matching
147
- return matchesGlob(rule.argument, actualArg, cwd);
148
- }
149
- /**
150
- * Reads and parses a JSON settings file, returning an empty object if not found or invalid
151
- */
152
- async function loadSettingsFile(filePath) {
9
+ async function loadSettingsFile(filePath, logger) {
153
10
  if (!filePath) {
154
11
  return {};
155
12
  }
@@ -157,7 +14,11 @@ async function loadSettingsFile(filePath) {
157
14
  const content = await fs.promises.readFile(filePath, "utf-8");
158
15
  return JSON.parse(content);
159
16
  }
160
- catch {
17
+ catch (error) {
18
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
19
+ return {};
20
+ }
21
+ logger?.error(`Failed to load settings from ${filePath}:`, error);
161
22
  return {};
162
23
  }
163
24
  }
@@ -196,7 +57,9 @@ export class SettingsManager {
196
57
  this.mergedSettings = {};
197
58
  this.watchers = [];
198
59
  this.initialized = false;
60
+ this.disposed = false;
199
61
  this.debounceTimer = null;
62
+ this.initPromise = null;
200
63
  this.cwd = cwd;
201
64
  this.onChange = options?.onChange;
202
65
  this.logger = options?.logger ?? console;
@@ -208,9 +71,18 @@ export class SettingsManager {
208
71
  if (this.initialized) {
209
72
  return;
210
73
  }
211
- await this.loadAllSettings();
212
- this.setupWatchers();
213
- this.initialized = true;
74
+ if (this.initPromise) {
75
+ return this.initPromise;
76
+ }
77
+ this.disposed = false;
78
+ this.initPromise = this.loadAllSettings().then(() => {
79
+ if (!this.disposed) {
80
+ this.setupWatchers();
81
+ this.initialized = true;
82
+ }
83
+ this.initPromise = null;
84
+ });
85
+ return this.initPromise;
214
86
  }
215
87
  /**
216
88
  * Returns the path to the user settings file
@@ -235,10 +107,10 @@ export class SettingsManager {
235
107
  */
236
108
  async loadAllSettings() {
237
109
  const [userSettings, projectSettings, localSettings, enterpriseSettings] = await Promise.all([
238
- loadSettingsFile(this.getUserSettingsPath()),
239
- loadSettingsFile(this.getProjectSettingsPath()),
240
- loadSettingsFile(this.getLocalSettingsPath()),
241
- loadSettingsFile(getManagedSettingsPath()),
110
+ loadSettingsFile(this.getUserSettingsPath(), this.logger),
111
+ loadSettingsFile(this.getProjectSettingsPath(), this.logger),
112
+ loadSettingsFile(this.getLocalSettingsPath(), this.logger),
113
+ loadSettingsFile(getManagedSettingsPath(), this.logger),
242
114
  ]);
243
115
  this.userSettings = userSettings;
244
116
  this.projectSettings = projectSettings;
@@ -258,40 +130,20 @@ export class SettingsManager {
258
130
  this.localSettings,
259
131
  this.enterpriseSettings,
260
132
  ];
261
- const merged = {
262
- permissions: {
263
- allow: [],
264
- deny: [],
265
- ask: [],
266
- },
267
- };
133
+ const merged = {};
268
134
  for (const settings of allSettings) {
269
- if (settings.permissions) {
270
- if (settings.permissions.allow) {
271
- merged.permissions.allow.push(...settings.permissions.allow);
272
- }
273
- if (settings.permissions.deny) {
274
- merged.permissions.deny.push(...settings.permissions.deny);
275
- }
276
- if (settings.permissions.ask) {
277
- merged.permissions.ask.push(...settings.permissions.ask);
278
- }
279
- if (settings.permissions.additionalDirectories) {
280
- merged.permissions.additionalDirectories = [
281
- ...(merged.permissions.additionalDirectories || []),
282
- ...settings.permissions.additionalDirectories,
283
- ];
284
- }
285
- if (settings.permissions.defaultMode) {
286
- merged.permissions.defaultMode = settings.permissions.defaultMode;
287
- }
288
- }
289
135
  if (settings.env) {
290
136
  merged.env = { ...merged.env, ...settings.env };
291
137
  }
292
138
  if (settings.model) {
293
139
  merged.model = settings.model;
294
140
  }
141
+ if (settings.permissions?.defaultMode !== undefined) {
142
+ merged.permissions = {
143
+ ...merged.permissions,
144
+ defaultMode: settings.permissions.defaultMode,
145
+ };
146
+ }
295
147
  }
296
148
  this.mergedSettings = merged;
297
149
  }
@@ -337,54 +189,20 @@ export class SettingsManager {
337
189
  }
338
190
  this.debounceTimer = setTimeout(async () => {
339
191
  this.debounceTimer = null;
192
+ if (this.disposed) {
193
+ return;
194
+ }
340
195
  try {
341
196
  await this.loadAllSettings();
342
- this.onChange?.();
197
+ if (!this.disposed) {
198
+ this.onChange?.();
199
+ }
343
200
  }
344
201
  catch (error) {
345
202
  this.logger.error("Failed to reload settings:", error);
346
203
  }
347
204
  }, 100);
348
205
  }
349
- /**
350
- * Checks if a tool invocation is allowed based on the loaded settings.
351
- *
352
- * @param toolName - The tool name (can be ACP-prefixed like mcp__acp__Read or plain like Read)
353
- * @param toolInput - The tool input object
354
- * @returns The permission decision and matching rule info
355
- */
356
- checkPermission(toolName, toolInput) {
357
- if (!toolName.startsWith(ACP_TOOL_NAME_PREFIX)) {
358
- return { decision: "ask" };
359
- }
360
- const permissions = this.mergedSettings.permissions;
361
- if (!permissions) {
362
- return { decision: "ask" };
363
- }
364
- // Check deny rules first (highest priority)
365
- for (const rule of permissions.deny || []) {
366
- const parsed = parseRule(rule);
367
- if (matchesRule(parsed, toolName, toolInput, this.cwd)) {
368
- return { decision: "deny", rule, source: "deny" };
369
- }
370
- }
371
- // Check allow rules
372
- for (const rule of permissions.allow || []) {
373
- const parsed = parseRule(rule);
374
- if (matchesRule(parsed, toolName, toolInput, this.cwd)) {
375
- return { decision: "allow", rule, source: "allow" };
376
- }
377
- }
378
- // Check ask rules
379
- for (const rule of permissions.ask || []) {
380
- const parsed = parseRule(rule);
381
- if (matchesRule(parsed, toolName, toolInput, this.cwd)) {
382
- return { decision: "ask", rule, source: "ask" };
383
- }
384
- }
385
- // No matching rule - default to ask
386
- return { decision: "ask" };
387
- }
388
206
  /**
389
207
  * Returns the current merged settings
390
208
  */
@@ -406,13 +224,15 @@ export class SettingsManager {
406
224
  }
407
225
  this.dispose();
408
226
  this.cwd = cwd;
409
- this.initialized = false;
410
227
  await this.initialize();
411
228
  }
412
229
  /**
413
230
  * Disposes of file watchers and cleans up resources
414
231
  */
415
232
  dispose() {
233
+ this.disposed = true;
234
+ this.initialized = false;
235
+ this.initPromise = null;
416
236
  if (this.debounceTimer) {
417
237
  clearTimeout(this.debounceTimer);
418
238
  this.debounceTimer = null;
@@ -421,6 +241,5 @@ export class SettingsManager {
421
241
  watcher.close();
422
242
  }
423
243
  this.watchers = [];
424
- this.initialized = false;
425
244
  }
426
245
  }
package/dist/tools.d.ts CHANGED
@@ -1,19 +1,8 @@
1
1
  import { PlanEntry, ToolCallContent, ToolCallLocation, ToolKind } from "@agentclientprotocol/sdk";
2
+ import { HookCallback } from "@anthropic-ai/claude-agent-sdk";
2
3
  import { ToolResultBlockParam, WebSearchToolResultBlockParam } from "@anthropic-ai/sdk/resources";
3
4
  import { BetaBashCodeExecutionToolResultBlockParam, BetaCodeExecutionToolResultBlockParam, BetaRequestMCPToolResultBlockParam, BetaTextEditorCodeExecutionToolResultBlockParam, BetaToolResultBlockParam, BetaToolSearchToolResultBlockParam, BetaWebFetchToolResultBlockParam, BetaWebSearchToolResultBlockParam } from "@anthropic-ai/sdk/resources/beta.mjs";
4
- export declare const ACP_TOOL_NAME_PREFIX = "mcp__acp__";
5
- export declare const acpToolNames: {
6
- read: string;
7
- edit: string;
8
- write: string;
9
- bash: string;
10
- killShell: string;
11
- bashOutput: string;
12
- };
13
- export declare const EDIT_TOOL_NAMES: string[];
14
- import { HookCallback } from "@anthropic-ai/claude-agent-sdk";
15
5
  import { Logger } from "./acp-agent.js";
16
- import { SettingsManager } from "./settings.js";
17
6
  interface ToolInfo {
18
7
  title: string;
19
8
  kind: ToolKind;
@@ -24,9 +13,28 @@ interface ToolUpdate {
24
13
  title?: string;
25
14
  content?: ToolCallContent[];
26
15
  locations?: ToolCallLocation[];
16
+ _meta?: {
17
+ terminal_info?: {
18
+ terminal_id: string;
19
+ };
20
+ terminal_output?: {
21
+ terminal_id: string;
22
+ data: string;
23
+ };
24
+ terminal_exit?: {
25
+ terminal_id: string;
26
+ exit_code: number;
27
+ signal: string | null;
28
+ };
29
+ };
27
30
  }
28
- export declare function toolInfoFromToolUse(toolUse: any): ToolInfo;
29
- export declare function toolUpdateFromToolResult(toolResult: ToolResultBlockParam | BetaToolResultBlockParam | BetaWebSearchToolResultBlockParam | BetaWebFetchToolResultBlockParam | WebSearchToolResultBlockParam | BetaCodeExecutionToolResultBlockParam | BetaBashCodeExecutionToolResultBlockParam | BetaTextEditorCodeExecutionToolResultBlockParam | BetaRequestMCPToolResultBlockParam | BetaToolSearchToolResultBlockParam, toolUse: any | undefined): ToolUpdate;
31
+ /**
32
+ * Convert an absolute file path to a project-relative path for display.
33
+ * Returns the original path if it's outside the project directory or if no cwd is provided.
34
+ */
35
+ export declare function toDisplayPath(filePath: string, cwd?: string): string;
36
+ export declare function toolInfoFromToolUse(toolUse: any, supportsTerminalOutput?: boolean, cwd?: string): ToolInfo;
37
+ export declare function toolUpdateFromToolResult(toolResult: ToolResultBlockParam | BetaToolResultBlockParam | BetaWebSearchToolResultBlockParam | BetaWebFetchToolResultBlockParam | WebSearchToolResultBlockParam | BetaCodeExecutionToolResultBlockParam | BetaBashCodeExecutionToolResultBlockParam | BetaTextEditorCodeExecutionToolResultBlockParam | BetaRequestMCPToolResultBlockParam | BetaToolSearchToolResultBlockParam, toolUse: any | undefined, supportsTerminalOutput?: boolean): ToolUpdate;
30
38
  export type ClaudePlanEntry = {
31
39
  content: string;
32
40
  status: "pending" | "in_progress" | "completed";
@@ -36,17 +44,21 @@ export declare function planEntries(input: {
36
44
  todos: ClaudePlanEntry[];
37
45
  }): PlanEntry[];
38
46
  export declare function markdownEscape(text: string): string;
47
+ /**
48
+ * Builds diff ToolUpdate content from the structured Edit toolResponse provided
49
+ * by the PostToolUse hook. Unlike parsing the plain unified diff string, this uses
50
+ * the pre-parsed structuredPatch which supports multiple replacement sites (replaceAll)
51
+ * and always includes context lines for better readability.
52
+ */
53
+ export declare function toolUpdateFromEditToolResponse(toolResponse: unknown): {
54
+ content?: ToolCallContent[];
55
+ locations?: ToolCallLocation[];
56
+ };
39
57
  export declare const registerHookCallback: (toolUseID: string, { onPostToolUseHook, }: {
40
58
  onPostToolUseHook?: (toolUseID: string, toolInput: unknown, toolResponse: unknown) => Promise<void>;
41
59
  }) => void;
42
60
  export declare const createPostToolUseHook: (logger?: Logger, options?: {
43
61
  onEnterPlanMode?: () => Promise<void>;
44
62
  }) => HookCallback;
45
- /**
46
- * Creates a PreToolUse hook that checks permissions using the SettingsManager.
47
- * This runs before the SDK's built-in permission rules, allowing us to enforce
48
- * our own permission settings for ACP-prefixed tools.
49
- */
50
- export declare const createPreToolUseHook: (settingsManager: SettingsManager, logger?: Logger) => HookCallback;
51
63
  export {};
52
64
  //# sourceMappingURL=tools.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,SAAS,EACT,eAAe,EACf,gBAAgB,EAChB,QAAQ,EACT,MAAM,0BAA0B,CAAC;AAGlC,OAAO,EAGL,oBAAoB,EAEpB,6BAA6B,EAE9B,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACL,yCAAyC,EAGzC,qCAAqC,EAGrC,kCAAkC,EAClC,+CAA+C,EAK/C,wBAAwB,EACxB,kCAAkC,EAIlC,gCAAgC,EAGhC,iCAAiC,EAElC,MAAM,sCAAsC,CAAC;AAW9C,eAAO,MAAM,oBAAoB,eAAe,CAAC;AACjD,eAAO,MAAM,YAAY;;;;;;;CAOxB,CAAC;AAEF,eAAO,MAAM,eAAe,UAA0C,CAAC;AAyBvE,OAAO,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAC9D,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AACxC,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAEhD,UAAU,QAAQ;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,QAAQ,CAAC;IACf,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B,SAAS,CAAC,EAAE,gBAAgB,EAAE,CAAC;CAChC;AAED,UAAU,UAAU;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,eAAe,EAAE,CAAC;IAC5B,SAAS,CAAC,EAAE,gBAAgB,EAAE,CAAC;CAChC;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,GAAG,GAAG,QAAQ,CA+V1D;AAED,wBAAgB,wBAAwB,CACtC,UAAU,EACN,oBAAoB,GACpB,wBAAwB,GACxB,iCAAiC,GACjC,gCAAgC,GAChC,6BAA6B,GAC7B,qCAAqC,GACrC,yCAAyC,GACzC,+CAA+C,GAC/C,kCAAkC,GAClC,kCAAkC,EACtC,OAAO,EAAE,GAAG,GAAG,SAAS,GACvB,UAAU,CA4HZ;AA0GD,MAAM,MAAM,eAAe,GAAG;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,SAAS,GAAG,aAAa,GAAG,WAAW,CAAC;IAChD,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,wBAAgB,WAAW,CAAC,KAAK,EAAE;IAAE,KAAK,EAAE,eAAe,EAAE,CAAA;CAAE,GAAG,SAAS,EAAE,CAM5E;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAQnD;AAcD,eAAO,MAAM,oBAAoB,GAC/B,WAAW,MAAM,EACjB,wBAEG;IACD,iBAAiB,CAAC,EAAE,CAClB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,OAAO,EAClB,YAAY,EAAE,OAAO,KAClB,OAAO,CAAC,IAAI,CAAC,CAAC;CACpB,SAKF,CAAC;AAGF,eAAO,MAAM,qBAAqB,GAE9B,SAAQ,MAAgB,EACxB,UAAU;IACR,eAAe,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACvC,KACA,YAoBF,CAAC;AAEJ;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,GAC9B,iBAAiB,eAAe,EAAE,SAAQ,MAAgB,KAAG,YA2C7D,CAAC"}
1
+ {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"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"}