claude-code-acp-ts 0.16.1 → 0.24.3
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/LICENSE +15 -46
- package/README.md +3 -3
- package/dist/acp-agent.d.ts +69 -12
- package/dist/acp-agent.d.ts.map +1 -1
- package/dist/acp-agent.js +1031 -483
- package/dist/index.js +22 -16
- package/dist/lib.d.ts +2 -3
- package/dist/lib.d.ts.map +1 -1
- package/dist/lib.js +1 -2
- package/dist/settings.d.ts +2 -18
- package/dist/settings.d.ts.map +1 -1
- package/dist/settings.js +43 -224
- package/dist/tools.d.ts +32 -20
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +191 -244
- package/dist/utils.d.ts +0 -14
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +0 -63
- package/package.json +12 -13
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
|
-
|
|
5
|
-
if (
|
|
6
|
-
|
|
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 {
|
|
4
|
-
export {
|
|
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,
|
|
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 {
|
|
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";
|
package/dist/settings.d.ts
CHANGED
|
@@ -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
|
*/
|
package/dist/settings.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../src/settings.ts"],"names":[],"mappings":"
|
|
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
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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.
|
|
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
|
-
|
|
29
|
-
|
|
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
|
package/dist/tools.d.ts.map
CHANGED
|
@@ -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;
|
|
1
|
+
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"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"}
|