@iinm/plain-agent 1.8.3 → 1.8.4
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/README.md +2 -2
- package/bin/plain +1 -1
- package/config/config.predefined.json +1 -1
- package/config/prompts.predefined/shortcuts/configure.md +1 -1
- package/dist/main.mjs +473 -0
- package/dist/main.mjs.map +7 -0
- package/package.json +5 -7
- package/src/agent.d.ts +0 -52
- package/src/agent.mjs +0 -204
- package/src/agentLoop.mjs +0 -419
- package/src/agentState.mjs +0 -41
- package/src/claudeCodePlugin.mjs +0 -164
- package/src/cliArgs.mjs +0 -175
- package/src/cliBatch.mjs +0 -147
- package/src/cliCommands.mjs +0 -283
- package/src/cliCompleter.mjs +0 -227
- package/src/cliCost.mjs +0 -309
- package/src/cliFormatter.mjs +0 -413
- package/src/cliInteractive.mjs +0 -529
- package/src/cliInterruptTransform.mjs +0 -51
- package/src/cliMuteTransform.mjs +0 -26
- package/src/cliPasteTransform.mjs +0 -183
- package/src/config.d.ts +0 -36
- package/src/config.mjs +0 -197
- package/src/context/loadAgentRoles.mjs +0 -283
- package/src/context/loadPrompts.mjs +0 -324
- package/src/context/loadUserMessageContext.mjs +0 -147
- package/src/costTracker.mjs +0 -210
- package/src/env.mjs +0 -44
- package/src/main.mjs +0 -279
- package/src/mcpClient.mjs +0 -351
- package/src/mcpIntegration.mjs +0 -160
- package/src/model.d.ts +0 -109
- package/src/modelCaller.mjs +0 -32
- package/src/modelDefinition.d.ts +0 -92
- package/src/prompt.mjs +0 -138
- package/src/providers/anthropic.d.ts +0 -248
- package/src/providers/anthropic.mjs +0 -587
- package/src/providers/bedrock.d.ts +0 -249
- package/src/providers/bedrock.mjs +0 -700
- package/src/providers/gemini.d.ts +0 -208
- package/src/providers/gemini.mjs +0 -754
- package/src/providers/openai.d.ts +0 -281
- package/src/providers/openai.mjs +0 -544
- package/src/providers/openaiCompatible.d.ts +0 -147
- package/src/providers/openaiCompatible.mjs +0 -652
- package/src/providers/platform/awsSigV4.mjs +0 -184
- package/src/providers/platform/azure.mjs +0 -42
- package/src/providers/platform/bedrock.mjs +0 -78
- package/src/providers/platform/googleCloud.mjs +0 -34
- package/src/subagent.mjs +0 -265
- package/src/tmpfile.mjs +0 -27
- package/src/tool.d.ts +0 -74
- package/src/toolExecutor.mjs +0 -236
- package/src/toolInputValidator.mjs +0 -183
- package/src/toolUseApprover.mjs +0 -99
- package/src/tools/askURL.mjs +0 -209
- package/src/tools/askWeb.mjs +0 -208
- package/src/tools/compactContext.d.ts +0 -4
- package/src/tools/compactContext.mjs +0 -87
- package/src/tools/delegateToSubagent.d.ts +0 -4
- package/src/tools/delegateToSubagent.mjs +0 -48
- package/src/tools/execCommand.d.ts +0 -22
- package/src/tools/execCommand.mjs +0 -200
- package/src/tools/patchFile.d.ts +0 -4
- package/src/tools/patchFile.mjs +0 -133
- package/src/tools/reportAsSubagent.d.ts +0 -3
- package/src/tools/reportAsSubagent.mjs +0 -44
- package/src/tools/tmuxCommand.d.ts +0 -14
- package/src/tools/tmuxCommand.mjs +0 -194
- package/src/tools/writeFile.d.ts +0 -4
- package/src/tools/writeFile.mjs +0 -56
- package/src/usageStore.mjs +0 -167
- package/src/utils/evalJSONConfig.mjs +0 -72
- package/src/utils/matchValue.d.ts +0 -6
- package/src/utils/matchValue.mjs +0 -40
- package/src/utils/noThrow.mjs +0 -31
- package/src/utils/notify.mjs +0 -29
- package/src/utils/parseFileRange.mjs +0 -18
- package/src/utils/readFileRange.mjs +0 -33
- package/src/utils/retryOnError.mjs +0 -41
- package/src/voiceInput.mjs +0 -61
- package/src/voiceInputGemini.mjs +0 -105
- package/src/voiceInputOpenAI.mjs +0 -104
- package/src/voiceInputSession.mjs +0 -543
- package/src/voiceToggleKey.mjs +0 -62
package/src/toolExecutor.mjs
DELETED
|
@@ -1,236 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @import { MessageContentToolResult, MessageContentToolUse } from "./model"
|
|
3
|
-
* @import { Tool } from "./tool"
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* @typedef {ReturnType<typeof createToolExecutor>} ToolExecutor
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* @typedef {Object} ToolExecutorOptions
|
|
12
|
-
* @property {string[]} [exclusiveToolNames] - Tool names that must be called exclusively
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Create a tool executor that handles tool validation, execution, and error handling
|
|
17
|
-
* @param {Map<string, Tool>} toolByName - Map of tool names to tool implementations
|
|
18
|
-
* @param {ToolExecutorOptions} [options] - Configuration options
|
|
19
|
-
*/
|
|
20
|
-
export function createToolExecutor(toolByName, options = {}) {
|
|
21
|
-
const { exclusiveToolNames = [] } = options;
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* @typedef {ValidationSuccess | ValidationFailure} ValidationResult
|
|
25
|
-
*/
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* @typedef {Object} ValidationSuccess
|
|
29
|
-
* @property {true} isValid
|
|
30
|
-
*/
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* @typedef {Object} ValidationFailure
|
|
34
|
-
* @property {false} isValid
|
|
35
|
-
* @property {string} errorMessage
|
|
36
|
-
* @property {MessageContentToolResult[]} toolResults
|
|
37
|
-
*/
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Validate all tool uses (tool existence, input validation, exclusive tool check)
|
|
41
|
-
* @param {MessageContentToolUse[]} toolUseParts - Tool uses to validate
|
|
42
|
-
* @returns {ValidationResult}
|
|
43
|
-
*/
|
|
44
|
-
function validateBatch(toolUseParts) {
|
|
45
|
-
// Tool existence + Input validation
|
|
46
|
-
/** @type {{index: number, message: string}[]} */
|
|
47
|
-
const errors = [];
|
|
48
|
-
|
|
49
|
-
for (let i = 0; i < toolUseParts.length; i++) {
|
|
50
|
-
const toolUse = toolUseParts[i];
|
|
51
|
-
const tool = toolByName.get(toolUse.toolName);
|
|
52
|
-
if (!tool) {
|
|
53
|
-
errors.push({
|
|
54
|
-
index: i,
|
|
55
|
-
message: `Tool not found: ${toolUse.toolName}`,
|
|
56
|
-
});
|
|
57
|
-
continue;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if (tool.validateInput) {
|
|
61
|
-
const result = tool.validateInput(toolUse.input);
|
|
62
|
-
if (result instanceof Error) {
|
|
63
|
-
errors.push({ index: i, message: result.message });
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
if (errors.length > 0) {
|
|
69
|
-
return {
|
|
70
|
-
isValid: false,
|
|
71
|
-
errorMessage: errors.map((e) => e.message).join("; "),
|
|
72
|
-
toolResults: toolUseParts.map((toolUse, index) => {
|
|
73
|
-
const error = errors.find((e) => e.index === index);
|
|
74
|
-
return {
|
|
75
|
-
type: "tool_result",
|
|
76
|
-
toolUseId: toolUse.toolUseId,
|
|
77
|
-
toolName: toolUse.toolName,
|
|
78
|
-
content: [
|
|
79
|
-
{
|
|
80
|
-
type: "text",
|
|
81
|
-
text: error
|
|
82
|
-
? error.message
|
|
83
|
-
: "Tool call rejected due to other tool validation error",
|
|
84
|
-
},
|
|
85
|
-
],
|
|
86
|
-
isError: true,
|
|
87
|
-
};
|
|
88
|
-
}),
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Exclusive tool validation
|
|
93
|
-
const exclusiveResult = validateExclusiveTools(toolUseParts);
|
|
94
|
-
if (!exclusiveResult.isValid) {
|
|
95
|
-
return {
|
|
96
|
-
isValid: false,
|
|
97
|
-
errorMessage: exclusiveResult.errorMessage,
|
|
98
|
-
toolResults: toolUseParts.map((t) => ({
|
|
99
|
-
type: "tool_result",
|
|
100
|
-
toolUseId: t.toolUseId,
|
|
101
|
-
toolName: t.toolName,
|
|
102
|
-
content: [{ type: "text", text: exclusiveResult.errorMessage }],
|
|
103
|
-
isError: true,
|
|
104
|
-
})),
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
return { isValid: true };
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* @typedef {ExecuteBatchSuccess | ExecuteBatchFailure} ExecuteBatchResult
|
|
113
|
-
*/
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* @typedef {Object} ExecuteBatchSuccess
|
|
117
|
-
* @property {true} success - Execution succeeded
|
|
118
|
-
* @property {MessageContentToolResult[]} results - Tool results on success
|
|
119
|
-
*/
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* @typedef {Object} ExecuteBatchFailure
|
|
123
|
-
* @property {false} success - Execution failed
|
|
124
|
-
* @property {MessageContentToolResult[]} errors - Error tool results on validation failure
|
|
125
|
-
* @property {string} errorMessage - Error message on validation failure
|
|
126
|
-
*/
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Validate and execute multiple tools
|
|
130
|
-
* @param {MessageContentToolUse[]} toolUseParts
|
|
131
|
-
* @returns {Promise<ExecuteBatchResult>}
|
|
132
|
-
*/
|
|
133
|
-
async function executeBatch(toolUseParts) {
|
|
134
|
-
const validation = validateBatch(toolUseParts);
|
|
135
|
-
|
|
136
|
-
if (!validation.isValid) {
|
|
137
|
-
return {
|
|
138
|
-
success: false,
|
|
139
|
-
errors: /** @type {MessageContentToolResult[]} */ (
|
|
140
|
-
validation.toolResults
|
|
141
|
-
),
|
|
142
|
-
errorMessage: /** @type {string} */ (validation.errorMessage),
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const results = [];
|
|
147
|
-
for (const toolUse of toolUseParts) {
|
|
148
|
-
results.push(await execute(toolUse));
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
return {
|
|
152
|
-
success: true,
|
|
153
|
-
results,
|
|
154
|
-
};
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Validate exclusive tool constraints
|
|
159
|
-
* @param {MessageContentToolUse[]} toolUseParts
|
|
160
|
-
* @returns {{isValid: true} | {isValid: false, errorMessage: string}}
|
|
161
|
-
*/
|
|
162
|
-
function validateExclusiveTools(toolUseParts) {
|
|
163
|
-
const exclusiveTools = toolUseParts.filter((t) =>
|
|
164
|
-
exclusiveToolNames.includes(t.toolName),
|
|
165
|
-
);
|
|
166
|
-
|
|
167
|
-
if (exclusiveTools.length > 1) {
|
|
168
|
-
const toolNames = exclusiveTools.map((t) => t.toolName).join(", ");
|
|
169
|
-
return {
|
|
170
|
-
isValid: false,
|
|
171
|
-
errorMessage: `System: ${toolNames} cannot be called together. Only one of these tools can be called at a time.`,
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
if (exclusiveTools.length === 1 && toolUseParts.length > 1) {
|
|
176
|
-
return {
|
|
177
|
-
isValid: false,
|
|
178
|
-
errorMessage: `System: ${exclusiveTools[0].toolName} cannot be called with other tools. It must be called alone.`,
|
|
179
|
-
};
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
return { isValid: true };
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
* Execute a tool use and return the result
|
|
187
|
-
* @param {MessageContentToolUse} toolUse
|
|
188
|
-
* @returns {Promise<MessageContentToolResult>}
|
|
189
|
-
*/
|
|
190
|
-
async function execute(toolUse) {
|
|
191
|
-
const tool = toolByName.get(toolUse.toolName);
|
|
192
|
-
if (!tool) {
|
|
193
|
-
return {
|
|
194
|
-
type: "tool_result",
|
|
195
|
-
toolUseId: toolUse.toolUseId,
|
|
196
|
-
toolName: toolUse.toolName,
|
|
197
|
-
content: [
|
|
198
|
-
{ type: "text", text: `Tool not found: ${toolUse.toolName}` },
|
|
199
|
-
],
|
|
200
|
-
isError: true,
|
|
201
|
-
};
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
const result = await tool.impl(toolUse.input);
|
|
205
|
-
if (result instanceof Error) {
|
|
206
|
-
return {
|
|
207
|
-
type: "tool_result",
|
|
208
|
-
toolUseId: toolUse.toolUseId,
|
|
209
|
-
toolName: toolUse.toolName,
|
|
210
|
-
content: [{ type: "text", text: result.message }],
|
|
211
|
-
isError: true,
|
|
212
|
-
};
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
if (typeof result === "string") {
|
|
216
|
-
return {
|
|
217
|
-
type: "tool_result",
|
|
218
|
-
toolUseId: toolUse.toolUseId,
|
|
219
|
-
toolName: toolUse.toolName,
|
|
220
|
-
content: [{ type: "text", text: result }],
|
|
221
|
-
};
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
return {
|
|
225
|
-
type: "tool_result",
|
|
226
|
-
toolUseId: toolUse.toolUseId,
|
|
227
|
-
toolName: toolUse.toolName,
|
|
228
|
-
content: result,
|
|
229
|
-
};
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
return {
|
|
233
|
-
executeBatch,
|
|
234
|
-
validateBatch,
|
|
235
|
-
};
|
|
236
|
-
}
|
|
@@ -1,183 +0,0 @@
|
|
|
1
|
-
import { execFileSync } from "node:child_process";
|
|
2
|
-
import fs from "node:fs";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import {
|
|
5
|
-
AGENT_MEMORY_DIR,
|
|
6
|
-
AGENT_TMP_DIR,
|
|
7
|
-
CLAUDE_CODE_PLUGIN_DIR,
|
|
8
|
-
} from "./env.mjs";
|
|
9
|
-
import { noThrowSync } from "./utils/noThrow.mjs";
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* @param {unknown} input
|
|
13
|
-
* @returns {boolean}
|
|
14
|
-
*/
|
|
15
|
-
export function isSafeToolInput(input) {
|
|
16
|
-
if (["number", "boolean", "undefined"].includes(typeof input)) {
|
|
17
|
-
return true;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
if (typeof input === "string") {
|
|
21
|
-
return isSafeToolInputItem(input);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
if (Array.isArray(input)) {
|
|
25
|
-
return input.every((item) => isSafeToolInput(item));
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
if (typeof input === "object") {
|
|
29
|
-
if (input === null) {
|
|
30
|
-
return true;
|
|
31
|
-
}
|
|
32
|
-
return Object.values(input).every((value) => isSafeToolInput(value));
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
return false;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* @param {string} arg
|
|
40
|
-
* @returns {boolean}
|
|
41
|
-
*/
|
|
42
|
-
export function isSafeToolInputItem(arg) {
|
|
43
|
-
const workingDir = process.cwd();
|
|
44
|
-
|
|
45
|
-
// Note: An argument can be a command option (e.g., '-l').
|
|
46
|
-
// It will then create an absolute path like `/path/to/project/-l`.
|
|
47
|
-
const absPath = path.resolve(arg);
|
|
48
|
-
|
|
49
|
-
const realPath = resolveRealPath(absPath, workingDir);
|
|
50
|
-
if (!realPath) {
|
|
51
|
-
return false;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Disallow paths outside the working directory (WITHOUT EXCEPTION)
|
|
55
|
-
if (!isInsideWorkingDirectory(realPath, workingDir)) {
|
|
56
|
-
return false;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Disallow any input that contains ".." as a path segment (directory traversal)
|
|
60
|
-
// Example:
|
|
61
|
-
// - When write_file is allowed for ^safe-dir/.+
|
|
62
|
-
// - "safe-dir/../unsafe-path" should be disallowed
|
|
63
|
-
if (arg.split(path.sep).includes("..")) {
|
|
64
|
-
return false;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Allow safe path even if git-ignored.
|
|
68
|
-
if (isSafePath(realPath)) {
|
|
69
|
-
return true;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Deny git ignored files (which may contain sensitive information or should not be accessed)
|
|
73
|
-
return !isGitIgnored(realPath);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* @param {string} absPath
|
|
78
|
-
* @param {string} workingDir
|
|
79
|
-
* @returns {string | null}
|
|
80
|
-
*/
|
|
81
|
-
function resolveRealPath(absPath, workingDir) {
|
|
82
|
-
const realPathResult = noThrowSync(() => fs.realpathSync(absPath));
|
|
83
|
-
if (!(realPathResult instanceof Error)) {
|
|
84
|
-
return realPathResult;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// realpathSync can fail if the path (or its target) doesn't exist.
|
|
88
|
-
// Manually follow symlink chain for broken links to ensure they don't point outside.
|
|
89
|
-
let currentPath = absPath;
|
|
90
|
-
const seen = new Set();
|
|
91
|
-
const MAX_SYMLINK_DEPTH = 10;
|
|
92
|
-
|
|
93
|
-
for (let depth = 0; depth < MAX_SYMLINK_DEPTH; depth++) {
|
|
94
|
-
if (seen.has(currentPath)) {
|
|
95
|
-
return null; // Circular link
|
|
96
|
-
}
|
|
97
|
-
seen.add(currentPath);
|
|
98
|
-
|
|
99
|
-
// Check if the current path is a symbolic link.
|
|
100
|
-
const lstats = noThrowSync(() => fs.lstatSync(currentPath));
|
|
101
|
-
if (lstats instanceof Error || !lstats.isSymbolicLink()) {
|
|
102
|
-
break; // Not a symlink or doesn't exist; stop traversal.
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Read the target path the symlink points to.
|
|
106
|
-
const target = noThrowSync(() => fs.readlinkSync(currentPath));
|
|
107
|
-
if (typeof target !== "string") {
|
|
108
|
-
break; // Failed to read the link; stop traversal.
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
currentPath = path.resolve(path.dirname(currentPath), target);
|
|
112
|
-
|
|
113
|
-
// If at any point it goes outside, we stop and use this path for the check.
|
|
114
|
-
if (!isInsideWorkingDirectory(currentPath, workingDir)) {
|
|
115
|
-
return currentPath;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
if (seen.size >= MAX_SYMLINK_DEPTH) {
|
|
120
|
-
return null; // Too deep
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
return currentPath;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* @param {string} targetPath
|
|
128
|
-
* @param {string} workingDir
|
|
129
|
-
* @returns {boolean}
|
|
130
|
-
*/
|
|
131
|
-
function isInsideWorkingDirectory(targetPath, workingDir) {
|
|
132
|
-
return (
|
|
133
|
-
targetPath === workingDir ||
|
|
134
|
-
targetPath.startsWith(`${workingDir}${path.sep}`)
|
|
135
|
-
);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* @param {string} targetPath
|
|
140
|
-
* @returns {boolean}
|
|
141
|
-
*/
|
|
142
|
-
function isSafePath(targetPath) {
|
|
143
|
-
const safePaths = [AGENT_MEMORY_DIR, AGENT_TMP_DIR, CLAUDE_CODE_PLUGIN_DIR];
|
|
144
|
-
|
|
145
|
-
for (const safePath of safePaths) {
|
|
146
|
-
const safeAbsPath = path.resolve(safePath);
|
|
147
|
-
if (
|
|
148
|
-
targetPath === safeAbsPath ||
|
|
149
|
-
targetPath.startsWith(`${safeAbsPath}${path.sep}`)
|
|
150
|
-
) {
|
|
151
|
-
return true;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
return false;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* @param {string} absPath
|
|
160
|
-
* @returns {boolean}
|
|
161
|
-
*/
|
|
162
|
-
function isGitIgnored(absPath) {
|
|
163
|
-
try {
|
|
164
|
-
execFileSync("git", ["check-ignore", "--no-index", "-q", absPath], {
|
|
165
|
-
stdio: ["ignore", "ignore", "ignore"],
|
|
166
|
-
});
|
|
167
|
-
// The path is ignored (exit code 0)
|
|
168
|
-
return true;
|
|
169
|
-
} catch (error) {
|
|
170
|
-
if (
|
|
171
|
-
error instanceof Error &&
|
|
172
|
-
"status" in error &&
|
|
173
|
-
typeof error.status === "number" &&
|
|
174
|
-
error.status === 1
|
|
175
|
-
) {
|
|
176
|
-
// Path is not ignored
|
|
177
|
-
return false;
|
|
178
|
-
}
|
|
179
|
-
// Other errors (e.g., status 128 if not a git repo or other git error)
|
|
180
|
-
// We treat this as "effectively ignored" to be safe.
|
|
181
|
-
return true;
|
|
182
|
-
}
|
|
183
|
-
}
|
package/src/toolUseApprover.mjs
DELETED
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @import { ToolUseApprover, ToolUseApproverConfig, ToolUseDecision, ToolUsePattern } from './tool'
|
|
3
|
-
* @import { MessageContentToolUse } from './model'
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { isSafeToolInput } from "./toolInputValidator.mjs";
|
|
7
|
-
import { matchValue } from "./utils/matchValue.mjs";
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* @param {ToolUseApproverConfig} config
|
|
11
|
-
* @returns {ToolUseApprover}
|
|
12
|
-
*/
|
|
13
|
-
export function createToolUseApprover({
|
|
14
|
-
patterns,
|
|
15
|
-
maxApprovals: max,
|
|
16
|
-
defaultAction,
|
|
17
|
-
maskApprovalInput,
|
|
18
|
-
}) {
|
|
19
|
-
const state = {
|
|
20
|
-
approvalCount: 0,
|
|
21
|
-
/** @type {ToolUsePattern[]} */
|
|
22
|
-
allowedToolUseInSession: [],
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
/** @returns {void} */
|
|
26
|
-
function resetApprovalCount() {
|
|
27
|
-
state.approvalCount = 0;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* @param {MessageContentToolUse} toolUse
|
|
32
|
-
* @returns {ToolUseDecision}
|
|
33
|
-
*/
|
|
34
|
-
function isAllowedToolUse(toolUse) {
|
|
35
|
-
const toolUseToMatch = {
|
|
36
|
-
toolName: toolUse.toolName,
|
|
37
|
-
input: toolUse.input,
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
for (const pattern of [...patterns, ...state.allowedToolUseInSession]) {
|
|
41
|
-
const patternToMatch = {
|
|
42
|
-
toolName: pattern.toolName,
|
|
43
|
-
...(pattern.input !== undefined && { input: pattern.input }),
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
if (!matchValue(toolUseToMatch, patternToMatch)) {
|
|
47
|
-
continue;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const action = pattern.action ?? defaultAction;
|
|
51
|
-
|
|
52
|
-
if (!["allow", "deny", "ask"].includes(action)) {
|
|
53
|
-
return {
|
|
54
|
-
action: "ask",
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
if (action === "deny") {
|
|
59
|
-
return {
|
|
60
|
-
action: "deny",
|
|
61
|
-
reason: pattern.reason,
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
if (action === "allow") {
|
|
66
|
-
const maskedInput = maskApprovalInput(toolUse.toolName, toolUse.input);
|
|
67
|
-
if (isSafeToolInput(maskedInput)) {
|
|
68
|
-
state.approvalCount += 1;
|
|
69
|
-
return state.approvalCount <= max
|
|
70
|
-
? { action: "allow" }
|
|
71
|
-
: { action: "ask" };
|
|
72
|
-
}
|
|
73
|
-
return { action: defaultAction };
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
return { action };
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
return { action: defaultAction };
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* @param {MessageContentToolUse} toolUse
|
|
84
|
-
* @returns {void}
|
|
85
|
-
*/
|
|
86
|
-
function allowToolUse(toolUse) {
|
|
87
|
-
state.allowedToolUseInSession.push({
|
|
88
|
-
toolName: toolUse.toolName,
|
|
89
|
-
input: maskApprovalInput(toolUse.toolName, toolUse.input),
|
|
90
|
-
action: "allow",
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
return {
|
|
95
|
-
isAllowedToolUse,
|
|
96
|
-
allowToolUse,
|
|
97
|
-
resetApprovalCount,
|
|
98
|
-
};
|
|
99
|
-
}
|