@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.
Files changed (86) hide show
  1. package/README.md +2 -2
  2. package/bin/plain +1 -1
  3. package/config/config.predefined.json +1 -1
  4. package/config/prompts.predefined/shortcuts/configure.md +1 -1
  5. package/dist/main.mjs +473 -0
  6. package/dist/main.mjs.map +7 -0
  7. package/package.json +5 -7
  8. package/src/agent.d.ts +0 -52
  9. package/src/agent.mjs +0 -204
  10. package/src/agentLoop.mjs +0 -419
  11. package/src/agentState.mjs +0 -41
  12. package/src/claudeCodePlugin.mjs +0 -164
  13. package/src/cliArgs.mjs +0 -175
  14. package/src/cliBatch.mjs +0 -147
  15. package/src/cliCommands.mjs +0 -283
  16. package/src/cliCompleter.mjs +0 -227
  17. package/src/cliCost.mjs +0 -309
  18. package/src/cliFormatter.mjs +0 -413
  19. package/src/cliInteractive.mjs +0 -529
  20. package/src/cliInterruptTransform.mjs +0 -51
  21. package/src/cliMuteTransform.mjs +0 -26
  22. package/src/cliPasteTransform.mjs +0 -183
  23. package/src/config.d.ts +0 -36
  24. package/src/config.mjs +0 -197
  25. package/src/context/loadAgentRoles.mjs +0 -283
  26. package/src/context/loadPrompts.mjs +0 -324
  27. package/src/context/loadUserMessageContext.mjs +0 -147
  28. package/src/costTracker.mjs +0 -210
  29. package/src/env.mjs +0 -44
  30. package/src/main.mjs +0 -279
  31. package/src/mcpClient.mjs +0 -351
  32. package/src/mcpIntegration.mjs +0 -160
  33. package/src/model.d.ts +0 -109
  34. package/src/modelCaller.mjs +0 -32
  35. package/src/modelDefinition.d.ts +0 -92
  36. package/src/prompt.mjs +0 -138
  37. package/src/providers/anthropic.d.ts +0 -248
  38. package/src/providers/anthropic.mjs +0 -587
  39. package/src/providers/bedrock.d.ts +0 -249
  40. package/src/providers/bedrock.mjs +0 -700
  41. package/src/providers/gemini.d.ts +0 -208
  42. package/src/providers/gemini.mjs +0 -754
  43. package/src/providers/openai.d.ts +0 -281
  44. package/src/providers/openai.mjs +0 -544
  45. package/src/providers/openaiCompatible.d.ts +0 -147
  46. package/src/providers/openaiCompatible.mjs +0 -652
  47. package/src/providers/platform/awsSigV4.mjs +0 -184
  48. package/src/providers/platform/azure.mjs +0 -42
  49. package/src/providers/platform/bedrock.mjs +0 -78
  50. package/src/providers/platform/googleCloud.mjs +0 -34
  51. package/src/subagent.mjs +0 -265
  52. package/src/tmpfile.mjs +0 -27
  53. package/src/tool.d.ts +0 -74
  54. package/src/toolExecutor.mjs +0 -236
  55. package/src/toolInputValidator.mjs +0 -183
  56. package/src/toolUseApprover.mjs +0 -99
  57. package/src/tools/askURL.mjs +0 -209
  58. package/src/tools/askWeb.mjs +0 -208
  59. package/src/tools/compactContext.d.ts +0 -4
  60. package/src/tools/compactContext.mjs +0 -87
  61. package/src/tools/delegateToSubagent.d.ts +0 -4
  62. package/src/tools/delegateToSubagent.mjs +0 -48
  63. package/src/tools/execCommand.d.ts +0 -22
  64. package/src/tools/execCommand.mjs +0 -200
  65. package/src/tools/patchFile.d.ts +0 -4
  66. package/src/tools/patchFile.mjs +0 -133
  67. package/src/tools/reportAsSubagent.d.ts +0 -3
  68. package/src/tools/reportAsSubagent.mjs +0 -44
  69. package/src/tools/tmuxCommand.d.ts +0 -14
  70. package/src/tools/tmuxCommand.mjs +0 -194
  71. package/src/tools/writeFile.d.ts +0 -4
  72. package/src/tools/writeFile.mjs +0 -56
  73. package/src/usageStore.mjs +0 -167
  74. package/src/utils/evalJSONConfig.mjs +0 -72
  75. package/src/utils/matchValue.d.ts +0 -6
  76. package/src/utils/matchValue.mjs +0 -40
  77. package/src/utils/noThrow.mjs +0 -31
  78. package/src/utils/notify.mjs +0 -29
  79. package/src/utils/parseFileRange.mjs +0 -18
  80. package/src/utils/readFileRange.mjs +0 -33
  81. package/src/utils/retryOnError.mjs +0 -41
  82. package/src/voiceInput.mjs +0 -61
  83. package/src/voiceInputGemini.mjs +0 -105
  84. package/src/voiceInputOpenAI.mjs +0 -104
  85. package/src/voiceInputSession.mjs +0 -543
  86. package/src/voiceToggleKey.mjs +0 -62
@@ -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
- }
@@ -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
- }