@iinm/plain-agent 1.9.4 → 1.10.1
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 +25 -67
- package/package.json +1 -1
- package/src/cliCost.mjs +21 -1
- package/src/cliFormatter.mjs +19 -12
- package/src/cliInteractive.mjs +19 -8
- package/src/config.d.ts +64 -4
- package/src/config.mjs +8 -8
- package/src/main.mjs +79 -32
- package/src/tools/webFetch.mjs +442 -0
- package/src/tools/webSearch.mjs +503 -0
- package/src/utils/createSequentialExecutor.mjs +28 -0
- package/src/tools/askURL.mjs +0 -209
- package/src/tools/askWeb.mjs +0 -208
package/README.md
CHANGED
|
@@ -2,62 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
A lightweight, capable coding agent for the terminal.
|
|
4
4
|
|
|
5
|
-
- **Multi-provider** — Use Claude, GPT, Gemini, or any OpenAI-compatible model.
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
# Model+Variant (Platform+Variant)
|
|
11
|
-
claude-haiku-4-5+thinking-16k (platform: anthropic+default)
|
|
12
|
-
claude-haiku-4-5+thinking-32k (platform: anthropic+default)
|
|
13
|
-
claude-sonnet-4-6+thinking-high (platform: anthropic+default)
|
|
14
|
-
claude-sonnet-4-6+thinking-max (platform: anthropic+default)
|
|
15
|
-
claude-opus-4-7+thinking-high (platform: anthropic+default)
|
|
16
|
-
claude-opus-4-7+thinking-max (platform: anthropic+default)
|
|
17
|
-
claude-haiku-4-5+thinking-16k-bedrock (platform: bedrock+default)
|
|
18
|
-
claude-haiku-4-5+thinking-32k-bedrock (platform: bedrock+default)
|
|
19
|
-
claude-sonnet-4-6+thinking-high-bedrock (platform: bedrock+default)
|
|
20
|
-
claude-sonnet-4-6+thinking-max-bedrock (platform: bedrock+default)
|
|
21
|
-
claude-opus-4-7+thinking-high-bedrock (platform: bedrock+default)
|
|
22
|
-
claude-opus-4-7+thinking-max-bedrock (platform: bedrock+default)
|
|
23
|
-
gemini-3-flash-preview+thinking-medium (platform: gemini+default)
|
|
24
|
-
gemini-3-flash-preview+thinking-high (platform: gemini+default)
|
|
25
|
-
gemini-3.1-pro-preview+thinking-medium (platform: gemini+default)
|
|
26
|
-
gemini-3.1-pro-preview+thinking-high (platform: gemini+default)
|
|
27
|
-
gemini-3-flash-preview+thinking-medium-vertex-ai (platform: vertex-ai+default)
|
|
28
|
-
gemini-3-flash-preview+thinking-high-vertex-ai (platform: vertex-ai+default)
|
|
29
|
-
gemini-3.1-pro-preview+thinking-medium-vertex-ai (platform: vertex-ai+default)
|
|
30
|
-
gemini-3.1-pro-preview+thinking-high-vertex-ai (platform: vertex-ai+default)
|
|
31
|
-
gpt-5.4-mini+thinking-medium (platform: openai+default)
|
|
32
|
-
gpt-5.4-mini+thinking-high (platform: openai+default)
|
|
33
|
-
gpt-5.4-mini+thinking-xhigh (platform: openai+default)
|
|
34
|
-
gpt-5.5+thinking-medium (platform: openai+default)
|
|
35
|
-
gpt-5.5+thinking-high (platform: openai+default)
|
|
36
|
-
gpt-5.5+thinking-xhigh (platform: openai+default)
|
|
37
|
-
gpt-5.2-chat+thinking-medium-azure (platform: azure+openai)
|
|
38
|
-
gpt-oss-120b+fireworks (platform: openai-compatible+fireworks)
|
|
39
|
-
glm-5+vertex-ai (platform: vertex-ai+default)
|
|
40
|
-
glm-5.1+fireworks (platform: openai-compatible+fireworks)
|
|
41
|
-
glm-5.1+novita (platform: openai-compatible+novita)
|
|
42
|
-
kimi-k2.6+fireworks (platform: openai-compatible+fireworks)
|
|
43
|
-
kimi-k2.6+novita (platform: openai-compatible+novita)
|
|
44
|
-
deepseek-v4-pro+novita (platform: openai-compatible+novita)
|
|
45
|
-
deepseek-v4-pro+fireworks (platform: openai-compatible+fireworks)
|
|
46
|
-
minimax-m2.7+fireworks (platform: openai-compatible+fireworks)
|
|
47
|
-
minimax-m2.7+novita (platform: openai-compatible+novita)
|
|
48
|
-
qwen3.6-plus+fireworks (platform: openai-compatible+fireworks)
|
|
49
|
-
qwen3.6-27b+novita (platform: openai-compatible+novita)
|
|
50
|
-
nova-2-lite+bedrock (platform: bedrock+default)
|
|
51
|
-
claude-haiku-4-5+thinking-16k-bedrock-converse (platform: bedrock+default)
|
|
52
|
-
```
|
|
53
|
-
</details>
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
- **Approval rules & path validation** — Auto-approve tool uses by name and arguments using regex patterns ([config.predefined.json#autoApproval](https://github.com/iinm/plain-agent/blob/main/config/config.predefined.json)); restrict file access to the working directory — git-ignored files require explicit approval ([src/toolInputValidator.mjs](https://github.com/iinm/plain-agent/blob/main/src/toolInputValidator.mjs)).
|
|
57
|
-
- **Sandboxed execution** — Run agent commands in a Docker container with a read-only project root and no network; writable mode and allowlisted network destinations can be enabled as needed.
|
|
58
|
-
- **Plain-text memory** — Task state is saved as Markdown files under `.plain-agent/memory/` for easy review.
|
|
59
|
-
- **Extensible** — Define prompts and subagents in Markdown. Connect MCP servers.
|
|
60
|
-
Supports Claude Code plugins and `.claude/` commands, subagents, and skills.
|
|
5
|
+
- **Multi-provider** — Use Claude, GPT, Gemini, or any OpenAI-compatible model via Bedrock, Vertex AI, or direct APIs.
|
|
6
|
+
- **Fine-grained auto-approval** — Auto-approve tool calls by name and arguments using regex patterns.
|
|
7
|
+
- **Sandboxed execution** — Run commands in a Docker container with filesystem and network isolation.
|
|
8
|
+
- **Claude Code compatible** — Use Claude Code plugins, commands, subagents, and skills from `.claude/`.
|
|
9
|
+
- **Zero external dependencies** — Built with Node.js standard libraries only.
|
|
61
10
|
|
|
62
11
|
## Limitations
|
|
63
12
|
|
|
@@ -114,25 +63,35 @@ Create the configuration.
|
|
|
114
63
|
}
|
|
115
64
|
],
|
|
116
65
|
|
|
117
|
-
// (Optional) Enable web
|
|
66
|
+
// (Optional) Enable web tools
|
|
118
67
|
"tools": {
|
|
119
|
-
|
|
120
|
-
"askWeb": {
|
|
68
|
+
"webSearch": {
|
|
121
69
|
"provider": "gemini",
|
|
122
70
|
"apiKey": "<GEMINI_API_KEY>",
|
|
123
71
|
"model": "gemini-3-flash-preview"
|
|
72
|
+
|
|
124
73
|
// Or use Vertex AI (Requires gcloud CLI to get authentication token)
|
|
125
74
|
// "provider": "gemini-vertex-ai",
|
|
126
75
|
// "baseURL": "https://aiplatform.googleapis.com/v1beta1/projects/<project_id>/locations/<location>",
|
|
127
76
|
// "model": "gemini-3-flash-preview"
|
|
77
|
+
|
|
78
|
+
// Or use a custom command
|
|
79
|
+
// "provider": "command",
|
|
80
|
+
// "command": "bash",
|
|
81
|
+
// "args": ["-c", "w3m -dump -o display_link_number=1 \"https://lite.duckduckgo.com/lite?q=$*\"", "-"]
|
|
128
82
|
},
|
|
129
83
|
|
|
130
|
-
|
|
131
|
-
"askURL": {
|
|
84
|
+
"webFetch": {
|
|
132
85
|
"provider": "gemini",
|
|
133
86
|
"apiKey": "<GEMINI_API_KEY>",
|
|
134
87
|
"model": "gemini-3-flash-preview"
|
|
88
|
+
|
|
135
89
|
// Or use Vertex AI (Requires gcloud CLI to get authentication token)
|
|
90
|
+
|
|
91
|
+
// Or use a custom command
|
|
92
|
+
// "provider": "command",
|
|
93
|
+
// "command": "w3m",
|
|
94
|
+
// "args": ["-dump", "-o", "display_link_number=1"]
|
|
136
95
|
}
|
|
137
96
|
}
|
|
138
97
|
}
|
|
@@ -378,8 +337,7 @@ Files are loaded in the following order. Settings in later files override earlie
|
|
|
378
337
|
├── (3) config.json # Project-specific configuration
|
|
379
338
|
├── (4) config.local.json # Project-specific local configuration (including secrets)
|
|
380
339
|
├── prompts/ # Project-specific prompts
|
|
381
|
-
|
|
382
|
-
└── sandbox/ # Sandbox runner scripts
|
|
340
|
+
└── agents/ # Project-specific agent roles
|
|
383
341
|
```
|
|
384
342
|
|
|
385
343
|
### Example
|
|
@@ -402,7 +360,7 @@ Files are loaded in the following order. Settings in later files override earlie
|
|
|
402
360
|
"action": "allow"
|
|
403
361
|
},
|
|
404
362
|
{
|
|
405
|
-
"toolName": { "$regex": "^(
|
|
363
|
+
"toolName": { "$regex": "^(web_search|web_fetch)$" },
|
|
406
364
|
"action": "allow"
|
|
407
365
|
}
|
|
408
366
|
// ⚠️ Never do this. mcp run outside the sandbox, so they can send anything externally.
|
|
@@ -461,7 +419,7 @@ Files are loaded in the following order. Settings in later files override earlie
|
|
|
461
419
|
},
|
|
462
420
|
|
|
463
421
|
{
|
|
464
|
-
"toolName": { "$regex": "^(
|
|
422
|
+
"toolName": { "$regex": "^(web_search|web_fetch)$" },
|
|
465
423
|
"action": "allow"
|
|
466
424
|
},
|
|
467
425
|
|
|
@@ -553,8 +511,8 @@ The agent can use the following tools to assist with tasks:
|
|
|
553
511
|
- **patch_file**: Patch a file.
|
|
554
512
|
- **exec_command**: Run a command without shell interpretation.
|
|
555
513
|
- **tmux_command**: Run a tmux command.
|
|
556
|
-
- **
|
|
557
|
-
- **
|
|
514
|
+
- **web_search**: Search the web with one or more keyword sets and answer a question based on the combined results (requires Google API key, Vertex AI configuration, or the `command` provider with a local search command).
|
|
515
|
+
- **web_fetch**: Fetch the contents of a single URL and answer a question based on it (requires Google API key, Vertex AI configuration, or the `command` provider with a local fetch command such as `w3m`, `curl`, or `lynx`).
|
|
558
516
|
- **switch_to_subagent**: Switch to a subagent role within the same conversation, focusing on the specified goal.
|
|
559
517
|
- **switch_to_main_agent**: Switch back to the main agent role and report the result. After reporting, the subagent's conversation history is removed from the context.
|
|
560
518
|
- **compact_context**: Compact the conversation context by discarding prior messages and reloading task state from a memory file. Use when the context has grown large but the task is not yet complete. Can also be invoked via the `/compact` slash command.
|
package/package.json
CHANGED
package/src/cliCost.mjs
CHANGED
|
@@ -87,6 +87,24 @@ export function parseDateOnly(value) {
|
|
|
87
87
|
return date;
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
+
/**
|
|
91
|
+
* Deduplicate usage records by sessionId, keeping only the last record for each session.
|
|
92
|
+
* This prevents double-counting when a session is resumed and exited multiple times.
|
|
93
|
+
*
|
|
94
|
+
* @param {UsageRecord[]} records - Records in chronological order (oldest first)
|
|
95
|
+
* @returns {UsageRecord[]} Deduplicated records (one per sessionId)
|
|
96
|
+
*/
|
|
97
|
+
function deduplicateBySessionId(records) {
|
|
98
|
+
/** @type {Map<string, UsageRecord>} */
|
|
99
|
+
const bySessionId = new Map();
|
|
100
|
+
for (const record of records) {
|
|
101
|
+
if (record.sessionId) {
|
|
102
|
+
bySessionId.set(record.sessionId, record);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return Array.from(bySessionId.values());
|
|
106
|
+
}
|
|
107
|
+
|
|
90
108
|
/**
|
|
91
109
|
* Aggregate usage records into a cost report.
|
|
92
110
|
*
|
|
@@ -103,12 +121,14 @@ export function aggregateUsage(records, period) {
|
|
|
103
121
|
);
|
|
104
122
|
}
|
|
105
123
|
|
|
124
|
+
const deduplicated = deduplicateBySessionId(records);
|
|
125
|
+
|
|
106
126
|
/** @type {Map<string, Map<string, DailyEntry>>} */
|
|
107
127
|
const byCurrency = new Map();
|
|
108
128
|
let noPricingSessionCount = 0;
|
|
109
129
|
let excludedOutOfRange = 0;
|
|
110
130
|
|
|
111
|
-
for (const record of
|
|
131
|
+
for (const record of deduplicated) {
|
|
112
132
|
if (record.timestamp == null) {
|
|
113
133
|
excludedOutOfRange++;
|
|
114
134
|
continue;
|
package/src/cliFormatter.mjs
CHANGED
|
@@ -152,20 +152,27 @@ export async function formatToolUse(toolUse) {
|
|
|
152
152
|
].join("\n");
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
-
if (toolName === "
|
|
156
|
-
/** @type {Partial<import("./tools/
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
"
|
|
160
|
-
|
|
155
|
+
if (toolName === "web_search") {
|
|
156
|
+
/** @type {Partial<import("./tools/webSearch.mjs").WebSearchInput>} */
|
|
157
|
+
const webSearchInput = input;
|
|
158
|
+
const searchesLine = webSearchInput.searches
|
|
159
|
+
? webSearchInput.searches.map((s) => s.keywords.join(" ")).join(" | ")
|
|
160
|
+
: "";
|
|
161
|
+
return [
|
|
162
|
+
`tool: ${toolName}`,
|
|
163
|
+
`searches: ${searchesLine}`,
|
|
164
|
+
`question: ${webSearchInput.question}`,
|
|
165
|
+
].join("\n");
|
|
161
166
|
}
|
|
162
167
|
|
|
163
|
-
if (toolName === "
|
|
164
|
-
/** @type {Partial<import("./tools/
|
|
165
|
-
const
|
|
166
|
-
return [
|
|
167
|
-
|
|
168
|
-
|
|
168
|
+
if (toolName === "web_fetch") {
|
|
169
|
+
/** @type {Partial<import("./tools/webFetch.mjs").WebFetchInput>} */
|
|
170
|
+
const webFetchInput = input;
|
|
171
|
+
return [
|
|
172
|
+
`tool: ${toolName}`,
|
|
173
|
+
`url: ${webFetchInput.url}`,
|
|
174
|
+
`question: ${webFetchInput.question}`,
|
|
175
|
+
].join("\n");
|
|
169
176
|
}
|
|
170
177
|
|
|
171
178
|
const { provider: _, ...filteredToolUse } = toolUse;
|
package/src/cliInteractive.mjs
CHANGED
|
@@ -17,6 +17,7 @@ import { createInterruptTransform } from "./cliInterruptTransform.mjs";
|
|
|
17
17
|
import { createMuteTransform } from "./cliMuteTransform.mjs";
|
|
18
18
|
import { createPasteHandler } from "./cliPasteTransform.mjs";
|
|
19
19
|
import { appendUsageRecord, buildUsageRecord } from "./usageStore.mjs";
|
|
20
|
+
import { createSequentialExecutor } from "./utils/createSequentialExecutor.mjs";
|
|
20
21
|
import { notify } from "./utils/notify.mjs";
|
|
21
22
|
import { parseVoiceToggleKey, startVoiceSession } from "./voiceInput.mjs";
|
|
22
23
|
|
|
@@ -473,12 +474,16 @@ export function startInteractiveSession({
|
|
|
473
474
|
}
|
|
474
475
|
});
|
|
475
476
|
|
|
477
|
+
const enqueueOutput = createSequentialExecutor();
|
|
478
|
+
|
|
476
479
|
agentEventEmitter.on("message", (message) => {
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
480
|
+
enqueueOutput(() =>
|
|
481
|
+
printMessage(message).catch((err) => {
|
|
482
|
+
console.error(
|
|
483
|
+
styleText("red", `Error rendering message: ${err.message}`),
|
|
484
|
+
);
|
|
485
|
+
}),
|
|
486
|
+
);
|
|
482
487
|
});
|
|
483
488
|
|
|
484
489
|
agentEventEmitter.on("toolUseRequest", () => {
|
|
@@ -500,7 +505,9 @@ export function startInteractiveSession({
|
|
|
500
505
|
});
|
|
501
506
|
|
|
502
507
|
agentEventEmitter.on("providerTokenUsage", (usage) => {
|
|
503
|
-
|
|
508
|
+
enqueueOutput(() => {
|
|
509
|
+
console.log(formatProviderTokenUsage(usage));
|
|
510
|
+
});
|
|
504
511
|
});
|
|
505
512
|
|
|
506
513
|
agentEventEmitter.on("error", (error) => {
|
|
@@ -519,8 +526,12 @@ export function startInteractiveSession({
|
|
|
519
526
|
styleText("yellow", `\nNotification error: ${err.message}`),
|
|
520
527
|
);
|
|
521
528
|
}
|
|
522
|
-
|
|
523
|
-
|
|
529
|
+
|
|
530
|
+
// Wait for all output operations to complete
|
|
531
|
+
await enqueueOutput(() => {});
|
|
532
|
+
|
|
533
|
+
// Defer prompt rendering to ensure terminal output is visible
|
|
534
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
524
535
|
|
|
525
536
|
state.turn = true;
|
|
526
537
|
cli.prompt();
|
package/src/config.d.ts
CHANGED
|
@@ -1,11 +1,71 @@
|
|
|
1
1
|
import { ClaudeCodePluginRepo } from "./claudeCodePlugin.mjs";
|
|
2
2
|
import { ModelDefinition, PlatformConfig } from "./modelDefinition";
|
|
3
3
|
import { ToolUsePattern } from "./tool";
|
|
4
|
-
import { AskURLToolOptions } from "./tools/askURL.mjs";
|
|
5
|
-
import { AskWebToolOptions } from "./tools/askWeb.mjs";
|
|
6
4
|
import { ExecCommandSanboxConfig } from "./tools/execCommand";
|
|
5
|
+
import {
|
|
6
|
+
WebFetchToolGeminiOptions,
|
|
7
|
+
WebFetchToolGeminiVertexAIOptions,
|
|
8
|
+
} from "./tools/webFetch.mjs";
|
|
9
|
+
import {
|
|
10
|
+
WebSearchToolGeminiOptions,
|
|
11
|
+
WebSearchToolGeminiVertexAIOptions,
|
|
12
|
+
} from "./tools/webSearch.mjs";
|
|
7
13
|
import { VoiceInputConfig } from "./voiceInput.mjs";
|
|
8
14
|
|
|
15
|
+
/**
|
|
16
|
+
* JSON-serializable webFetch configuration.
|
|
17
|
+
*
|
|
18
|
+
* The `command` provider runs an arbitrary local command per fetch to
|
|
19
|
+
* download a URL's content; the agent's main model is then used to answer
|
|
20
|
+
* based on the dumped output. The runtime tool factory receives a resolved
|
|
21
|
+
* `modelCaller` instead — see `WebFetchToolOptions` in `tools/webFetch.mjs`.
|
|
22
|
+
*/
|
|
23
|
+
export type WebFetchToolConfig =
|
|
24
|
+
| WebFetchToolGeminiOptions
|
|
25
|
+
| WebFetchToolGeminiVertexAIOptions
|
|
26
|
+
| WebFetchToolCommandJsonConfig;
|
|
27
|
+
|
|
28
|
+
export type WebFetchToolCommandJsonConfig = {
|
|
29
|
+
provider: "command";
|
|
30
|
+
/** Executable used to fetch the URL (e.g., `"w3m"`, `"curl"`). */
|
|
31
|
+
command: string;
|
|
32
|
+
/** Arguments passed before the URL (e.g., `["-dump"]`). The URL is appended automatically. */
|
|
33
|
+
args: string[];
|
|
34
|
+
/** Per-call timeout in milliseconds (default 30000). */
|
|
35
|
+
timeoutMs?: number;
|
|
36
|
+
/** Extra environment variables, merged on top of PATH / HOME / LANG. */
|
|
37
|
+
env?: Record<string, string>;
|
|
38
|
+
maxLength?: number;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* JSON-serializable webSearch configuration.
|
|
43
|
+
*
|
|
44
|
+
* The `command` provider runs an arbitrary local command per keyword set
|
|
45
|
+
* to perform a search; the agent's main model is then used to filter the
|
|
46
|
+
* combined results down to entries relevant to the question. The runtime
|
|
47
|
+
* tool factory receives a resolved `modelCaller` instead — see
|
|
48
|
+
* `WebSearchToolOptions` in `tools/webSearch.mjs`.
|
|
49
|
+
*/
|
|
50
|
+
export type WebSearchToolConfig =
|
|
51
|
+
| WebSearchToolGeminiOptions
|
|
52
|
+
| WebSearchToolGeminiVertexAIOptions
|
|
53
|
+
| WebSearchToolCommandJsonConfig;
|
|
54
|
+
|
|
55
|
+
export type WebSearchToolCommandJsonConfig = {
|
|
56
|
+
provider: "command";
|
|
57
|
+
/** Executable used to perform each search (e.g., a wrapper around a search API). */
|
|
58
|
+
command: string;
|
|
59
|
+
/** Arguments passed before each keyword set (e.g., `["-n", "5"]`). Keywords are appended automatically. */
|
|
60
|
+
args: string[];
|
|
61
|
+
/** Per-search timeout in milliseconds (default 30000). */
|
|
62
|
+
timeoutMs?: number;
|
|
63
|
+
/** Extra environment variables, merged on top of PATH / HOME / LANG. */
|
|
64
|
+
env?: Record<string, string>;
|
|
65
|
+
maxLengthPerSearch?: number;
|
|
66
|
+
maxTotalLength?: number;
|
|
67
|
+
};
|
|
68
|
+
|
|
9
69
|
export type AppConfig = {
|
|
10
70
|
model?: string;
|
|
11
71
|
models?: ModelDefinition[];
|
|
@@ -17,8 +77,8 @@ export type AppConfig = {
|
|
|
17
77
|
};
|
|
18
78
|
sandbox?: ExecCommandSanboxConfig;
|
|
19
79
|
tools?: {
|
|
20
|
-
|
|
21
|
-
|
|
80
|
+
webSearch?: WebSearchToolConfig;
|
|
81
|
+
webFetch?: WebFetchToolConfig;
|
|
22
82
|
};
|
|
23
83
|
mcpServers?: Record<string, MCPServerConfig>;
|
|
24
84
|
notifyCmd?: { command: string; args?: string[] };
|
package/src/config.mjs
CHANGED
|
@@ -76,18 +76,18 @@ export async function loadAppConfig(options = {}) {
|
|
|
76
76
|
},
|
|
77
77
|
sandbox: config.sandbox ?? merged.sandbox,
|
|
78
78
|
tools: {
|
|
79
|
-
|
|
79
|
+
webSearch: config.tools?.webSearch
|
|
80
80
|
? {
|
|
81
|
-
...(merged.tools?.
|
|
82
|
-
...config.tools.
|
|
81
|
+
...(merged.tools?.webSearch ?? {}),
|
|
82
|
+
...config.tools.webSearch,
|
|
83
83
|
}
|
|
84
|
-
: merged.tools?.
|
|
85
|
-
|
|
84
|
+
: merged.tools?.webSearch,
|
|
85
|
+
webFetch: config.tools?.webFetch
|
|
86
86
|
? {
|
|
87
|
-
...(merged.tools?.
|
|
88
|
-
...config.tools.
|
|
87
|
+
...(merged.tools?.webFetch ?? {}),
|
|
88
|
+
...config.tools.webFetch,
|
|
89
89
|
}
|
|
90
|
-
: merged.tools?.
|
|
90
|
+
: merged.tools?.webFetch,
|
|
91
91
|
},
|
|
92
92
|
mcpServers: {
|
|
93
93
|
...(merged.mcpServers ?? {}),
|
package/src/main.mjs
CHANGED
|
@@ -22,8 +22,6 @@ import { setupMCPServer } from "./mcpIntegration.mjs";
|
|
|
22
22
|
import { createModelCaller } from "./modelCaller.mjs";
|
|
23
23
|
import { createPrompt } from "./prompt.mjs";
|
|
24
24
|
import { listSessions, loadSession } from "./sessionStore.mjs";
|
|
25
|
-
import { createAskURLTool } from "./tools/askURL.mjs";
|
|
26
|
-
import { createAskWebTool } from "./tools/askWeb.mjs";
|
|
27
25
|
import { createCompactContextTool } from "./tools/compactContext.mjs";
|
|
28
26
|
import { createExecCommandTool } from "./tools/execCommand.mjs";
|
|
29
27
|
import { createPatchFileTool } from "./tools/patchFile.mjs";
|
|
@@ -31,6 +29,8 @@ import { readFileTool } from "./tools/readFile.mjs";
|
|
|
31
29
|
import { createSwitchToMainAgentTool } from "./tools/switchToMainAgent.mjs";
|
|
32
30
|
import { createSwitchToSubagentTool } from "./tools/switchToSubagent.mjs";
|
|
33
31
|
import { createTmuxCommandTool } from "./tools/tmuxCommand.mjs";
|
|
32
|
+
import { createWebFetchTool } from "./tools/webFetch.mjs";
|
|
33
|
+
import { createWebSearchTool } from "./tools/webSearch.mjs";
|
|
34
34
|
import { writeFileTool } from "./tools/writeFile.mjs";
|
|
35
35
|
import { createToolUseApprover } from "./toolUseApprover.mjs";
|
|
36
36
|
|
|
@@ -179,7 +179,7 @@ if (cliArgs.subcommand.type === "resume" && cliArgs.subcommand.list) {
|
|
|
179
179
|
console.log(
|
|
180
180
|
styleText(
|
|
181
181
|
"yellow",
|
|
182
|
-
`
|
|
182
|
+
` ⚠️ workingDir differs (saved: ${resumedState.workingDir}, current: ${process.cwd()})`,
|
|
183
183
|
),
|
|
184
184
|
);
|
|
185
185
|
}
|
|
@@ -285,28 +285,6 @@ if (cliArgs.subcommand.type === "resume" && cliArgs.subcommand.list) {
|
|
|
285
285
|
createSwitchToMainAgentTool(),
|
|
286
286
|
];
|
|
287
287
|
|
|
288
|
-
if (appConfig.tools?.askWeb) {
|
|
289
|
-
builtinTools.push(createAskWebTool(appConfig.tools.askWeb));
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
if (appConfig.tools?.askURL) {
|
|
293
|
-
builtinTools.push(createAskURLTool(appConfig.tools.askURL));
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
const toolUseApprover = createToolUseApprover({
|
|
297
|
-
maxApprovals: appConfig.autoApproval?.maxApprovals || 50,
|
|
298
|
-
defaultAction: appConfig.autoApproval?.defaultAction || "ask",
|
|
299
|
-
patterns: appConfig.autoApproval?.patterns || [],
|
|
300
|
-
maskApprovalInput: (toolName, input) => {
|
|
301
|
-
for (const tool of builtinTools) {
|
|
302
|
-
if (tool.def.name === toolName && tool.maskApprovalInput) {
|
|
303
|
-
return tool.maskApprovalInput(input);
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
return input;
|
|
307
|
-
},
|
|
308
|
-
});
|
|
309
|
-
|
|
310
288
|
const [modelName, modelVariant] = modelNameWithVariant.split("+");
|
|
311
289
|
const modelDef = (appConfig.models ?? []).find(
|
|
312
290
|
(entry) => entry.name === modelName && entry.variant === modelVariant,
|
|
@@ -328,14 +306,83 @@ if (cliArgs.subcommand.type === "resume" && cliArgs.subcommand.list) {
|
|
|
328
306
|
);
|
|
329
307
|
}
|
|
330
308
|
|
|
309
|
+
if (appConfig.tools?.webSearch) {
|
|
310
|
+
const webSearchConfig = appConfig.tools.webSearch;
|
|
311
|
+
if (webSearchConfig.provider === "command") {
|
|
312
|
+
const webSearchCallModel = createModelCaller({
|
|
313
|
+
...modelDef,
|
|
314
|
+
platform: {
|
|
315
|
+
...modelDef.platform,
|
|
316
|
+
...platform,
|
|
317
|
+
},
|
|
318
|
+
});
|
|
319
|
+
builtinTools.push(
|
|
320
|
+
createWebSearchTool({
|
|
321
|
+
provider: "command",
|
|
322
|
+
command: webSearchConfig.command,
|
|
323
|
+
args: webSearchConfig.args,
|
|
324
|
+
timeoutMs: webSearchConfig.timeoutMs,
|
|
325
|
+
env: webSearchConfig.env,
|
|
326
|
+
modelCaller: webSearchCallModel,
|
|
327
|
+
maxLengthPerSearch: webSearchConfig.maxLengthPerSearch,
|
|
328
|
+
maxTotalLength: webSearchConfig.maxTotalLength,
|
|
329
|
+
}),
|
|
330
|
+
);
|
|
331
|
+
} else {
|
|
332
|
+
builtinTools.push(createWebSearchTool(webSearchConfig));
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (appConfig.tools?.webFetch) {
|
|
337
|
+
const webFetchConfig = appConfig.tools.webFetch;
|
|
338
|
+
if (webFetchConfig.provider === "command") {
|
|
339
|
+
const webFetchCallModel = createModelCaller({
|
|
340
|
+
...modelDef,
|
|
341
|
+
platform: {
|
|
342
|
+
...modelDef.platform,
|
|
343
|
+
...platform,
|
|
344
|
+
},
|
|
345
|
+
});
|
|
346
|
+
builtinTools.push(
|
|
347
|
+
createWebFetchTool({
|
|
348
|
+
provider: "command",
|
|
349
|
+
command: webFetchConfig.command,
|
|
350
|
+
args: webFetchConfig.args,
|
|
351
|
+
timeoutMs: webFetchConfig.timeoutMs,
|
|
352
|
+
env: webFetchConfig.env,
|
|
353
|
+
modelCaller: webFetchCallModel,
|
|
354
|
+
maxLength: webFetchConfig.maxLength,
|
|
355
|
+
}),
|
|
356
|
+
);
|
|
357
|
+
} else {
|
|
358
|
+
builtinTools.push(createWebFetchTool(webFetchConfig));
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const toolUseApprover = createToolUseApprover({
|
|
363
|
+
maxApprovals: appConfig.autoApproval?.maxApprovals || 50,
|
|
364
|
+
defaultAction: appConfig.autoApproval?.defaultAction || "ask",
|
|
365
|
+
patterns: appConfig.autoApproval?.patterns || [],
|
|
366
|
+
maskApprovalInput: (toolName, input) => {
|
|
367
|
+
for (const tool of builtinTools) {
|
|
368
|
+
if (tool.def.name === toolName && tool.maskApprovalInput) {
|
|
369
|
+
return tool.maskApprovalInput(input);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
return input;
|
|
373
|
+
},
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
const agentCallModel = createModelCaller({
|
|
377
|
+
...modelDef,
|
|
378
|
+
platform: {
|
|
379
|
+
...modelDef.platform,
|
|
380
|
+
...platform,
|
|
381
|
+
},
|
|
382
|
+
});
|
|
383
|
+
|
|
331
384
|
const { userEventEmitter, agentEventEmitter, agentCommands } = createAgent({
|
|
332
|
-
callModel:
|
|
333
|
-
...modelDef,
|
|
334
|
-
platform: {
|
|
335
|
-
...modelDef.platform,
|
|
336
|
-
...platform,
|
|
337
|
-
},
|
|
338
|
-
}),
|
|
385
|
+
callModel: agentCallModel,
|
|
339
386
|
prompt,
|
|
340
387
|
tools: [...builtinTools, ...mcpTools],
|
|
341
388
|
toolUseApprover,
|