@iinm/plain-agent 1.7.6 → 1.7.8
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/package.json +1 -1
- package/src/cliFormatter.mjs +26 -1
- package/src/main.mjs +2 -2
- package/src/tools/askURL.mjs +11 -3
- package/src/tools/askWeb.mjs +8 -0
- package/src/tools/patchFile.mjs +80 -68
package/package.json
CHANGED
package/src/cliFormatter.mjs
CHANGED
|
@@ -47,7 +47,7 @@ export function formatToolUse(toolUse) {
|
|
|
47
47
|
const diffs = [];
|
|
48
48
|
const matches = Array.from(
|
|
49
49
|
diff.matchAll(
|
|
50
|
-
/<<<<<<< SEARCH\n(.*?)\n
|
|
50
|
+
/<<<<<<< SEARCH [0-9a-z]{3}\n(.*?)\n======= [0-9a-z]{3}\n(.*?)\n?>>>>>>> REPLACE [0-9a-z]{3}/gs,
|
|
51
51
|
),
|
|
52
52
|
);
|
|
53
53
|
for (const match of matches) {
|
|
@@ -95,6 +95,31 @@ export function formatToolUse(toolUse) {
|
|
|
95
95
|
].join("\n");
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
if (toolName === "report_as_subagent") {
|
|
99
|
+
/** @type {Partial<import("./tools/reportAsSubagent").ReportAsSubagentInput>} */
|
|
100
|
+
const reportAsSubagentInput = input;
|
|
101
|
+
return [
|
|
102
|
+
`tool: ${toolName}`,
|
|
103
|
+
`memoryPath: ${reportAsSubagentInput.memoryPath}`,
|
|
104
|
+
].join("\n");
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (toolName === "ask_web") {
|
|
108
|
+
/** @type {Partial<import("./tools/askWeb.mjs").AskWebInput>} */
|
|
109
|
+
const askWebInput = input;
|
|
110
|
+
return [`tool: ${toolName}`, `question: ${askWebInput.question}`].join(
|
|
111
|
+
"\n",
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (toolName === "ask_url") {
|
|
116
|
+
/** @type {Partial<import("./tools/askURL.mjs").AskURLInput>} */
|
|
117
|
+
const askURLInput = input;
|
|
118
|
+
return [`tool: ${toolName}`, `question: ${askURLInput.question}`].join(
|
|
119
|
+
"\n",
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
98
123
|
const { provider: _, ...filteredToolUse } = toolUse;
|
|
99
124
|
|
|
100
125
|
return JSON.stringify(filteredToolUse, null, 2);
|
package/src/main.mjs
CHANGED
|
@@ -26,7 +26,7 @@ import { createAskURLTool } from "./tools/askURL.mjs";
|
|
|
26
26
|
import { createAskWebTool } from "./tools/askWeb.mjs";
|
|
27
27
|
import { createDelegateToSubagentTool } from "./tools/delegateToSubagent.mjs";
|
|
28
28
|
import { createExecCommandTool } from "./tools/execCommand.mjs";
|
|
29
|
-
import {
|
|
29
|
+
import { createPatchFileTool } from "./tools/patchFile.mjs";
|
|
30
30
|
import { createReportAsSubagentTool } from "./tools/reportAsSubagent.mjs";
|
|
31
31
|
import { createTmuxCommandTool } from "./tools/tmuxCommand.mjs";
|
|
32
32
|
import { writeFileTool } from "./tools/writeFile.mjs";
|
|
@@ -162,7 +162,7 @@ if (cliArgs.subcommand.type === "install-claude-code-plugins") {
|
|
|
162
162
|
const builtinTools = [
|
|
163
163
|
createExecCommandTool({ sandbox: appConfig.sandbox }),
|
|
164
164
|
writeFileTool,
|
|
165
|
-
|
|
165
|
+
createPatchFileTool(),
|
|
166
166
|
createTmuxCommandTool({ sandbox: appConfig.sandbox }),
|
|
167
167
|
createDelegateToSubagentTool(),
|
|
168
168
|
createReportAsSubagentTool(),
|
package/src/tools/askURL.mjs
CHANGED
|
@@ -25,7 +25,7 @@ import { noThrow } from "../utils/noThrow.mjs";
|
|
|
25
25
|
*/
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
|
-
* @typedef {Object}
|
|
28
|
+
* @typedef {Object} AskURLInput
|
|
29
29
|
* @property {string} question
|
|
30
30
|
*/
|
|
31
31
|
|
|
@@ -35,7 +35,7 @@ import { noThrow } from "../utils/noThrow.mjs";
|
|
|
35
35
|
*/
|
|
36
36
|
export function createAskURLTool(config) {
|
|
37
37
|
/**
|
|
38
|
-
* @param {
|
|
38
|
+
* @param {AskURLInput} input
|
|
39
39
|
* @param {number} retryCount
|
|
40
40
|
* @returns {Promise<string | Error>}
|
|
41
41
|
*/
|
|
@@ -193,9 +193,17 @@ Question: ${input.question}`,
|
|
|
193
193
|
},
|
|
194
194
|
|
|
195
195
|
/**
|
|
196
|
-
* @param {
|
|
196
|
+
* @param {AskURLInput} input
|
|
197
197
|
* @returns {Promise<string | Error>}
|
|
198
198
|
*/
|
|
199
199
|
impl: async (input) => await noThrow(async () => askURL(input, 0)),
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* @param {Record<string, unknown>} _input
|
|
203
|
+
* @returns {Record<string, unknown>}
|
|
204
|
+
*/
|
|
205
|
+
maskApprovalInput: (_input) => {
|
|
206
|
+
return {};
|
|
207
|
+
},
|
|
200
208
|
};
|
|
201
209
|
}
|
package/src/tools/askWeb.mjs
CHANGED
|
@@ -196,5 +196,13 @@ Question: ${input.question}`,
|
|
|
196
196
|
* @returns {Promise<string | Error>}
|
|
197
197
|
*/
|
|
198
198
|
impl: async (input) => await noThrow(async () => askWeb(input, 0)),
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* @param {Record<string, unknown>} _input
|
|
202
|
+
* @returns {Record<string, unknown>}
|
|
203
|
+
*/
|
|
204
|
+
maskApprovalInput: (_input) => {
|
|
205
|
+
return {};
|
|
206
|
+
},
|
|
199
207
|
};
|
|
200
208
|
}
|
package/src/tools/patchFile.mjs
CHANGED
|
@@ -6,91 +6,103 @@
|
|
|
6
6
|
import fs from "node:fs/promises";
|
|
7
7
|
import { noThrow } from "../utils/noThrow.mjs";
|
|
8
8
|
|
|
9
|
-
/**
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
9
|
+
/**
|
|
10
|
+
* @param {string} [nonce]
|
|
11
|
+
* @returns {Tool}
|
|
12
|
+
*/
|
|
13
|
+
export function createPatchFileTool(
|
|
14
|
+
nonce = Math.random().toString(36).substring(2, 5),
|
|
15
|
+
) {
|
|
16
|
+
return {
|
|
17
|
+
def: {
|
|
18
|
+
name: "patch_file",
|
|
19
|
+
description:
|
|
20
|
+
"Modify a file by replacing specific content with new content.",
|
|
21
|
+
inputSchema: {
|
|
22
|
+
type: "object",
|
|
23
|
+
properties: {
|
|
24
|
+
filePath: {
|
|
25
|
+
type: "string",
|
|
26
|
+
},
|
|
27
|
+
diff: {
|
|
28
|
+
description: `
|
|
23
29
|
- Content is searched as an exact match including indentation and line breaks.
|
|
24
30
|
- The first match found will be replaced if there are multiple matches.
|
|
25
|
-
- Use multiple SEARCH/REPLACE blocks to replace multiple contents.
|
|
31
|
+
- Use multiple SEARCH/REPLACE blocks with nonce (${nonce}) to replace multiple contents.
|
|
26
32
|
|
|
27
33
|
Format:
|
|
28
|
-
<<<<<<< SEARCH
|
|
34
|
+
<<<<<<< SEARCH ${nonce}
|
|
29
35
|
old content
|
|
30
|
-
=======
|
|
36
|
+
======= ${nonce}
|
|
31
37
|
new content
|
|
32
|
-
>>>>>>> REPLACE
|
|
38
|
+
>>>>>>> REPLACE ${nonce}
|
|
33
39
|
|
|
34
|
-
<<<<<<< SEARCH
|
|
40
|
+
<<<<<<< SEARCH ${nonce}
|
|
35
41
|
other old content
|
|
36
|
-
=======
|
|
42
|
+
======= ${nonce}
|
|
37
43
|
other new content
|
|
38
|
-
>>>>>>> REPLACE
|
|
44
|
+
>>>>>>> REPLACE ${nonce}
|
|
39
45
|
`.trim(),
|
|
40
|
-
|
|
46
|
+
type: "string",
|
|
47
|
+
},
|
|
41
48
|
},
|
|
49
|
+
required: ["filePath", "diff"],
|
|
42
50
|
},
|
|
43
|
-
required: ["filePath", "diff"],
|
|
44
51
|
},
|
|
45
|
-
},
|
|
46
52
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
53
|
+
/**
|
|
54
|
+
* @param {PatchFileInput} input
|
|
55
|
+
* @returns {Promise<string | Error>}
|
|
56
|
+
*/
|
|
57
|
+
impl: async (input) =>
|
|
58
|
+
await noThrow(async () => {
|
|
59
|
+
const { filePath, diff } = input;
|
|
54
60
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
for (const match of matches) {
|
|
66
|
-
const [_, search, replace] = match;
|
|
67
|
-
if (!newContent.includes(search)) {
|
|
61
|
+
const content = await fs.readFile(filePath, "utf8");
|
|
62
|
+
const matches = Array.from(
|
|
63
|
+
diff.matchAll(
|
|
64
|
+
new RegExp(
|
|
65
|
+
`<<<<<<< SEARCH ${nonce}\\n(.*?)\\n======= ${nonce}\\n(.*?)\\n?>>>>>>> REPLACE ${nonce}`,
|
|
66
|
+
"gs",
|
|
67
|
+
),
|
|
68
|
+
),
|
|
69
|
+
);
|
|
70
|
+
if (matches.length === 0) {
|
|
68
71
|
throw new Error(
|
|
69
|
-
|
|
72
|
+
`Invalid diff format. All markers must include the nonce, e.g., <<<<<<< SEARCH ${nonce} / ======= ${nonce} / >>>>>>> REPLACE ${nonce}`,
|
|
70
73
|
);
|
|
71
74
|
}
|
|
72
|
-
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
75
|
+
let newContent = content;
|
|
76
|
+
for (const match of matches) {
|
|
77
|
+
const [_, search, replace] = match;
|
|
78
|
+
if (!newContent.includes(search)) {
|
|
79
|
+
throw new Error(
|
|
80
|
+
JSON.stringify(`Search content not found: ${search}`),
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
// Escape $ characters in replacement string to prevent interpretation of $& $1 $$ patterns
|
|
84
|
+
const escapedReplace = replace.replace(/\$/g, "$$$$");
|
|
85
|
+
if (replace === "" && newContent.includes(`${search}\n`)) {
|
|
86
|
+
newContent = newContent.replace(`${search}\n`, "");
|
|
87
|
+
} else if (replace === "" && newContent.includes(`\n${search}`)) {
|
|
88
|
+
newContent = newContent.replace(`\n${search}`, "");
|
|
89
|
+
} else {
|
|
90
|
+
newContent = newContent.replace(search, escapedReplace);
|
|
91
|
+
}
|
|
80
92
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}),
|
|
93
|
+
await fs.writeFile(filePath, newContent);
|
|
94
|
+
return `Patched file: ${filePath}`;
|
|
95
|
+
}),
|
|
85
96
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
};
|
|
97
|
+
/**
|
|
98
|
+
* @param {Record<string, unknown>} input
|
|
99
|
+
* @returns {Record<string, unknown>}
|
|
100
|
+
*/
|
|
101
|
+
maskApprovalInput: (input) => {
|
|
102
|
+
const patchFileInput = /** @type {PatchFileInput} */ (input);
|
|
103
|
+
return {
|
|
104
|
+
filePath: patchFileInput.filePath,
|
|
105
|
+
};
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
}
|