@lazyingart/agintiflow 0.11.0 → 0.12.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 +5 -1
- package/docs/large-codebase-engineering.md +27 -0
- package/package.json +1 -1
- package/public/app.js +12 -0
- package/public/index.html +15 -0
- package/scripts/smoke-cli-chat.js +19 -1
- package/scripts/smoke-coding-tools.js +22 -0
- package/src/agent-runner.js +56 -0
- package/src/capabilities.js +5 -0
- package/src/cli.js +28 -1
- package/src/config.js +6 -0
- package/src/guardrails.js +12 -0
- package/src/interactive-cli.js +151 -14
- package/src/model-client.js +46 -0
- package/src/parallel-scouts.js +126 -0
- package/src/web-db.js +10 -1
- package/src/web-search.js +111 -0
- package/web.js +12 -0
package/README.md
CHANGED
|
@@ -67,12 +67,16 @@ aginti
|
|
|
67
67
|
aginti chat
|
|
68
68
|
```
|
|
69
69
|
|
|
70
|
-
Inside chat, type normal requests such as `write a small Python CLI app with tests`. The default is Docker workspace mode with approved package installs, so coding, plotting, and LaTeX tasks can set up project-local tools without touching the host. Use `/help` for commands, `/login` or `/auth` to paste a provider key, `/latex on` for PDF work, `/docker off` only when you intentionally want host mode, `/sessions` to list project runs, and `/resume latest` or `/resume <session-id>` to continue work. Type `/` then Tab for command completion. Esc or Ctrl+C stops the active run cleanly and prints the resume command.
|
|
70
|
+
Inside chat, type normal requests such as `write a small Python CLI app with tests`. The default is Docker workspace mode with approved package installs, so coding, plotting, and LaTeX tasks can set up project-local tools without touching the host. Use `/help` for commands, `/login` or `/auth` to paste a provider key, `/latex on` for PDF work, `/docker off` only when you intentionally want host mode, `/sessions` to list project runs, and `/resume latest` or `/resume <session-id>` to continue work. Type `/` then Tab for command completion. `Ctrl+J` inserts a new line in the colored input panel, while Enter sends the message. Assistant responses render common Markdown, including headings, inline code, bold text, lists, quotes, code fences, and tables. Esc or Ctrl+C stops the active run cleanly and prints the resume command.
|
|
71
71
|
|
|
72
72
|
For code edits, AgInTiFlow routes patch/refactor/database-style tasks to DeepSeek v4 pro by default and exposes `apply_patch` as a deterministic workspace tool. It supports exact replacements, Codex-style patch envelopes, and unified diffs, with preflight checks, path guardrails, hashes, and compact per-file diffs. See [docs/patch-tools.md](docs/patch-tools.md).
|
|
73
73
|
|
|
74
74
|
For larger repositories, use `--profile large-codebase` or choose **Large codebase engineering** in the web UI. The web default stays **Auto**, and Auto now escalates codebase/system/debugging prompts to the same engineering loop when needed. Complex work routes to DeepSeek v4 pro, starts with `inspect_project`, then uses search/read/patch/check loops inspired by Codex, Copilot SDK, Claude Code, Gemini CLI, Qwen, and Claw Code. See [docs/large-codebase-engineering.md](docs/large-codebase-engineering.md).
|
|
75
75
|
|
|
76
|
+
AgInTiFlow can also spend cheap DeepSeek calls on parallel scout notes before the main executor starts a complicated task. Scouts run independently for architecture, implementation, review, and research risks, then the main agent uses those notes while still doing the real file/shell/browser work itself. Disable with `--no-parallel-scouts` or set `--scout-count 1..4`.
|
|
77
|
+
|
|
78
|
+
For current docs, install errors, package/toolchain setup, and source discovery, the agent has a guarded `web_search` tool. It returns compact search results without browser search-engine loops and respects configured domain allowlists. Disable with `--no-web-search`.
|
|
79
|
+
|
|
76
80
|
For raster image work, AgInTiFlow has an optional `image_generation` skill backed by the `generate_image` tool and a local `GRSAI` key. The skill tells DeepSeek when image generation is appropriate; the tool calls GRS AI Nano Banana, saves manifests/images under `artifacts/images`, and sends the result to the canvas. See [docs/auxiliary-image-generation.md](docs/auxiliary-image-generation.md).
|
|
77
81
|
|
|
78
82
|
Launch the local web UI from an installed package:
|
|
@@ -64,6 +64,33 @@ aginti "repair the Docker setup and run the Node tests"
|
|
|
64
64
|
|
|
65
65
|
These route to DeepSeek v4 pro when the complexity score is high enough.
|
|
66
66
|
|
|
67
|
+
## Parallel Scout Mode
|
|
68
|
+
|
|
69
|
+
DeepSeek calls are cheap enough that complex tasks can use several short advisory calls before the main executor starts. When enabled, AgInTiFlow runs bounded scouts in parallel:
|
|
70
|
+
|
|
71
|
+
- Architect: decomposes the task and identifies first files/logs/commands.
|
|
72
|
+
- Implementer: predicts patch boundaries and focused checks.
|
|
73
|
+
- Reviewer: looks for missing tests, risks, and instruction-compliance failures.
|
|
74
|
+
- Researcher: suggests `web_search` queries when current information may matter.
|
|
75
|
+
|
|
76
|
+
Scout output is injected as advisory context only. The main agent still owns execution and must use real tools to inspect, edit, run commands, and finish. CLI flags:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
aginti --parallel-scouts --scout-count 4 "fix this complicated repo bug"
|
|
80
|
+
aginti --no-parallel-scouts "run a cheap short task"
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
The web app exposes the same toggle and scout count.
|
|
84
|
+
|
|
85
|
+
## Web Search
|
|
86
|
+
|
|
87
|
+
Use `web_search` for current docs, package/toolchain errors, install instructions, and source discovery. It returns compact titles, URLs, and snippets, and should be preferred over opening a search engine in the browser. Specific result pages can still be opened later with `open_url`.
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
aginti --web-search "look up the current pytest config docs and update this project"
|
|
91
|
+
aginti --no-web-search "work fully offline"
|
|
92
|
+
```
|
|
93
|
+
|
|
67
94
|
## Cross-Language Playbook
|
|
68
95
|
|
|
69
96
|
AgInTiFlow gives DeepSeek stack-specific reminders without hardcoding a solution:
|
package/package.json
CHANGED
package/public/app.js
CHANGED
|
@@ -69,6 +69,9 @@ const translations = {
|
|
|
69
69
|
shellToolLabel: "Enable shell tool",
|
|
70
70
|
fileToolLabel: "Enable file tools",
|
|
71
71
|
auxiliaryToolLabel: "Enable auxiliary skills",
|
|
72
|
+
webSearchLabel: "Enable web search",
|
|
73
|
+
parallelScoutsLabel: "Parallel DeepSeek scouts",
|
|
74
|
+
parallelScoutCountLabel: "Scout count",
|
|
72
75
|
wrapperToolLabel: "Enable agent wrappers",
|
|
73
76
|
preferredWrapperLabel: "Preferred wrapper",
|
|
74
77
|
dockerSandboxLabel: "Use Docker sandbox",
|
|
@@ -650,6 +653,9 @@ const runMetaEl = document.querySelector("#run-meta");
|
|
|
650
653
|
const stopRunButton = document.querySelector("#stop-run");
|
|
651
654
|
const keyStatusEl = document.querySelector("#key-status");
|
|
652
655
|
const allowAuxiliaryToolsField = document.querySelector("#allowAuxiliaryTools");
|
|
656
|
+
const allowWebSearchField = document.querySelector("#allowWebSearch");
|
|
657
|
+
const allowParallelScoutsField = document.querySelector("#allowParallelScouts");
|
|
658
|
+
const parallelScoutCountField = document.querySelector("#parallelScoutCount");
|
|
653
659
|
const allowWrapperToolsField = document.querySelector("#allowWrapperTools");
|
|
654
660
|
const preferredWrapperField = document.querySelector("#preferredWrapper");
|
|
655
661
|
const wrapperStatusEl = document.querySelector("#wrapper-status");
|
|
@@ -1003,6 +1009,9 @@ function formPayload() {
|
|
|
1003
1009
|
allowShellTool: document.querySelector("#allowShellTool").checked,
|
|
1004
1010
|
allowFileTools: document.querySelector("#allowFileTools").checked,
|
|
1005
1011
|
allowAuxiliaryTools: allowAuxiliaryToolsField?.checked ?? true,
|
|
1012
|
+
allowWebSearch: allowWebSearchField?.checked ?? true,
|
|
1013
|
+
allowParallelScouts: allowParallelScoutsField?.checked ?? true,
|
|
1014
|
+
parallelScoutCount: Number(parallelScoutCountField?.value) || 3,
|
|
1006
1015
|
allowWrapperTools: allowWrapperToolsField.checked,
|
|
1007
1016
|
preferredWrapper: preferredWrapperField.value,
|
|
1008
1017
|
taskProfile: taskProfileField?.value || "auto",
|
|
@@ -2196,6 +2205,9 @@ async function loadConfig() {
|
|
|
2196
2205
|
document.querySelector("#allowShellTool").checked = prefs.allowShellTool ?? true;
|
|
2197
2206
|
document.querySelector("#allowFileTools").checked = prefs.allowFileTools ?? true;
|
|
2198
2207
|
if (allowAuxiliaryToolsField) allowAuxiliaryToolsField.checked = prefs.allowAuxiliaryTools ?? true;
|
|
2208
|
+
if (allowWebSearchField) allowWebSearchField.checked = prefs.allowWebSearch ?? true;
|
|
2209
|
+
if (allowParallelScoutsField) allowParallelScoutsField.checked = prefs.allowParallelScouts ?? true;
|
|
2210
|
+
if (parallelScoutCountField) parallelScoutCountField.value = String(prefs.parallelScoutCount || 3);
|
|
2199
2211
|
allowWrapperToolsField.checked = prefs.allowWrapperTools ?? false;
|
|
2200
2212
|
preferredWrapperField.value = prefs.preferredWrapper || "codex";
|
|
2201
2213
|
document.querySelector("#dockerSandboxImage").value = prefs.dockerSandboxImage || "agintiflow-sandbox:latest";
|
package/public/index.html
CHANGED
|
@@ -181,6 +181,16 @@
|
|
|
181
181
|
<span class="switch" aria-hidden="true"></span>
|
|
182
182
|
<span data-i18n="auxiliaryToolLabel">Enable auxiliary skills</span>
|
|
183
183
|
</label>
|
|
184
|
+
<label class="switch-label">
|
|
185
|
+
<input id="allowWebSearch" name="allowWebSearch" type="checkbox" checked />
|
|
186
|
+
<span class="switch" aria-hidden="true"></span>
|
|
187
|
+
<span data-i18n="webSearchLabel">Enable web search</span>
|
|
188
|
+
</label>
|
|
189
|
+
<label class="switch-label">
|
|
190
|
+
<input id="allowParallelScouts" name="allowParallelScouts" type="checkbox" checked />
|
|
191
|
+
<span class="switch" aria-hidden="true"></span>
|
|
192
|
+
<span data-i18n="parallelScoutsLabel">Parallel DeepSeek scouts</span>
|
|
193
|
+
</label>
|
|
184
194
|
<label class="switch-label">
|
|
185
195
|
<input id="allowPasswords" name="allowPasswords" type="checkbox" />
|
|
186
196
|
<span class="switch" aria-hidden="true"></span>
|
|
@@ -193,6 +203,11 @@
|
|
|
193
203
|
</label>
|
|
194
204
|
</div>
|
|
195
205
|
|
|
206
|
+
<label>
|
|
207
|
+
<span data-i18n="parallelScoutCountLabel">Scout count</span>
|
|
208
|
+
<input id="parallelScoutCount" name="parallelScoutCount" type="number" min="1" max="4" value="3" />
|
|
209
|
+
</label>
|
|
210
|
+
|
|
196
211
|
<div class="grid wrapper-controls">
|
|
197
212
|
<label class="switch-label switch-card">
|
|
198
213
|
<input id="allowWrapperTools" name="allowWrapperTools" type="checkbox" />
|
|
@@ -4,6 +4,7 @@ import fs from "node:fs/promises";
|
|
|
4
4
|
import os from "node:os";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { stripMarkdown } from "../src/interactive-cli.js";
|
|
7
8
|
|
|
8
9
|
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
9
10
|
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "agintiflow-cli-chat-"));
|
|
@@ -52,6 +53,23 @@ function runCli(args, inputText) {
|
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
try {
|
|
56
|
+
const renderedMarkdown = stripMarkdown(
|
|
57
|
+
[
|
|
58
|
+
"**Docker status**",
|
|
59
|
+
"",
|
|
60
|
+
"| Check | Result |",
|
|
61
|
+
"| --- | --- |",
|
|
62
|
+
"| `/.dockerenv` | **Present** |",
|
|
63
|
+
"| Hostname | `abc123` |",
|
|
64
|
+
].join("\n")
|
|
65
|
+
);
|
|
66
|
+
if (renderedMarkdown.includes("**") || renderedMarkdown.includes("| --- |")) {
|
|
67
|
+
throw new Error("terminal markdown renderer left raw markdown syntax");
|
|
68
|
+
}
|
|
69
|
+
if (!renderedMarkdown.includes("Check") || !renderedMarkdown.includes("Present")) {
|
|
70
|
+
throw new Error("terminal markdown renderer dropped table content");
|
|
71
|
+
}
|
|
72
|
+
|
|
55
73
|
const result = await runChat("Create notes/interactive.md with a short CLI chat smoke message\n/exit\n");
|
|
56
74
|
const written = await fs.readFile(path.join(tempRoot, "notes/interactive.md"), "utf8");
|
|
57
75
|
if (!written.includes("Created by AgInTiFlow mock mode.")) {
|
|
@@ -74,7 +92,7 @@ try {
|
|
|
74
92
|
{
|
|
75
93
|
ok: true,
|
|
76
94
|
projectRoot: tempRoot,
|
|
77
|
-
checks: ["interactive-chat", "mock-file-write", "run-status", "resume-latest"],
|
|
95
|
+
checks: ["markdown-render", "interactive-chat", "mock-file-write", "run-status", "resume-latest"],
|
|
78
96
|
},
|
|
79
97
|
null,
|
|
80
98
|
2
|
|
@@ -7,7 +7,9 @@ import { repairModelMessageHistory, runAgent } from "../src/agent-runner.js";
|
|
|
7
7
|
import { resolveRuntimeConfig } from "../src/config.js";
|
|
8
8
|
import { engineeringGuidanceForTask, recommendedMaxStepsForTask } from "../src/engineering-guidance.js";
|
|
9
9
|
import { selectModelRoute } from "../src/model-routing.js";
|
|
10
|
+
import { shouldRunParallelScouts } from "../src/parallel-scouts.js";
|
|
10
11
|
import { SessionStore } from "../src/session-store.js";
|
|
12
|
+
import { searchWeb } from "../src/web-search.js";
|
|
11
13
|
import { executeWorkspaceTool } from "../src/workspace-tools.js";
|
|
12
14
|
|
|
13
15
|
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
@@ -112,6 +114,24 @@ try {
|
|
|
112
114
|
const guidance = engineeringGuidanceForTask("debug this Python project system bug and fix failing tests", "auto");
|
|
113
115
|
assert(guidance.includes("Python:"), "engineering guidance did not include Python stack advice");
|
|
114
116
|
assert(guidance.includes("System/shell:"), "engineering guidance did not include system stack advice");
|
|
117
|
+
assert(
|
|
118
|
+
shouldRunParallelScouts(
|
|
119
|
+
{
|
|
120
|
+
provider: "deepseek",
|
|
121
|
+
allowParallelScouts: true,
|
|
122
|
+
routeComplexityScore: autoSystemRoute.complexityScore,
|
|
123
|
+
taskProfile: "auto",
|
|
124
|
+
goal: "debug this Python project system bug and fix failing tests",
|
|
125
|
+
},
|
|
126
|
+
{ meta: {}, goal: "debug this Python project system bug and fix failing tests" }
|
|
127
|
+
),
|
|
128
|
+
"parallel scouts did not enable for complex auto task"
|
|
129
|
+
);
|
|
130
|
+
const drySearch = await searchWeb(
|
|
131
|
+
{ query: "AgInTiFlow web_search smoke", maxResults: 2 },
|
|
132
|
+
{ allowWebSearch: true, webSearchDryRun: true }
|
|
133
|
+
);
|
|
134
|
+
assert(drySearch.ok && drySearch.results.length === 1, "web_search dry-run did not return deterministic result");
|
|
115
135
|
|
|
116
136
|
await fs.mkdir(path.join(workspace, "src"), { recursive: true });
|
|
117
137
|
await fs.mkdir(path.join(workspace, "test"), { recursive: true });
|
|
@@ -285,6 +305,8 @@ try {
|
|
|
285
305
|
"large_profile_pro_route",
|
|
286
306
|
"auto_system_pro_route",
|
|
287
307
|
"auto_engineering_guidance",
|
|
308
|
+
"parallel_scout_trigger",
|
|
309
|
+
"web_search_dry_run",
|
|
288
310
|
"inspect_project",
|
|
289
311
|
"mock_inspect_project",
|
|
290
312
|
"write_file",
|
package/src/agent-runner.js
CHANGED
|
@@ -19,6 +19,8 @@ import { normalizeCanvasPayload } from "./artifact-tunnel.js";
|
|
|
19
19
|
import { getTaskProfile } from "./task-profiles.js";
|
|
20
20
|
import { generateImage, listAuxiliarySkills } from "./auxiliary-tools.js";
|
|
21
21
|
import { engineeringGuidanceForTask } from "./engineering-guidance.js";
|
|
22
|
+
import { searchWeb } from "./web-search.js";
|
|
23
|
+
import { runParallelScouts, shouldRunParallelScouts } from "./parallel-scouts.js";
|
|
22
24
|
|
|
23
25
|
const exec = promisify(execCallback);
|
|
24
26
|
const BROWSER_TOOLS = new Set(["open_url", "open_workspace_file", "preview_workspace", "click", "type", "scroll", "press", "back"]);
|
|
@@ -284,6 +286,12 @@ function createInitialState(config, sessionId) {
|
|
|
284
286
|
.map((skill) => `${skill.id} via ${skill.toolName} (${skill.available ? "key available" : `needs ${skill.keyName}`})`)
|
|
285
287
|
.join(", ")}. Use generate_image for real raster image/photo/illustration/cover/poster/logo requests when appropriate; if the key is missing, ask the user to run /auxilliary grsai or aginti login grsai.`
|
|
286
288
|
: "Auxiliary skills are disabled for this run.",
|
|
289
|
+
config.allowWebSearch
|
|
290
|
+
? "web_search is available for current information, docs, package/toolchain errors, and source discovery. Prefer web_search over browser search-engine navigation."
|
|
291
|
+
: "web_search is disabled.",
|
|
292
|
+
config.allowParallelScouts
|
|
293
|
+
? `Parallel DeepSeek scouts may run before complex execution. Scout count: ${config.parallelScoutCount}.`
|
|
294
|
+
: "Parallel scouts are disabled.",
|
|
287
295
|
`Task profile: ${taskProfile.label}. ${taskProfile.prompt}`,
|
|
288
296
|
engineeringGuidance,
|
|
289
297
|
"A frontend canvas/artifacts tunnel exists. Use send_to_canvas when important markdown, diffs, screenshots, images, or workspace files should be highlighted in the UI. It is optional and ordinary final text can still go directly to finish.",
|
|
@@ -323,6 +331,8 @@ function createInitialState(config, sessionId) {
|
|
|
323
331
|
.map((skill) => `${skill.id}:${skill.available ? "available" : "missing-key"}`)
|
|
324
332
|
.join(" ")}`
|
|
325
333
|
: "",
|
|
334
|
+
config.allowWebSearch ? "Web search tool: enabled." : "Web search tool: disabled.",
|
|
335
|
+
config.allowParallelScouts ? `Parallel scouts: enabled count=${config.parallelScoutCount}.` : "Parallel scouts: disabled.",
|
|
326
336
|
`Task profile: ${taskProfile.label}. ${taskProfile.prompt}`,
|
|
327
337
|
engineeringGuidance,
|
|
328
338
|
"Canvas/artifacts tunnel: available through send_to_canvas for optional frontend rendering.",
|
|
@@ -746,6 +756,13 @@ async function executeTool(browserState, toolCall, snapshot, config, store, obse
|
|
|
746
756
|
case "open_url":
|
|
747
757
|
await abortable(browserState.page.goto(String(args.url), { waitUntil: "domcontentloaded" }), config.abortSignal);
|
|
748
758
|
break;
|
|
759
|
+
case "web_search": {
|
|
760
|
+
const result = await searchWeb(args, config);
|
|
761
|
+
const eventResult = sanitizeToolResult(result);
|
|
762
|
+
await store.appendEvent(result.ok ? "tool.completed" : "tool.failed", eventResult);
|
|
763
|
+
observers.event(result.ok ? "tool.completed" : "tool.failed", eventResult);
|
|
764
|
+
return result;
|
|
765
|
+
}
|
|
749
766
|
case "open_workspace_file": {
|
|
750
767
|
const target = resolveWorkspacePath(config, args.path || args.file || ".");
|
|
751
768
|
const stat = await fs.stat(target.absolutePath);
|
|
@@ -1111,6 +1128,40 @@ export async function runAgent(config) {
|
|
|
1111
1128
|
observers.event("plan.created", { plan });
|
|
1112
1129
|
}
|
|
1113
1130
|
|
|
1131
|
+
if (shouldRunParallelScouts(config, state)) {
|
|
1132
|
+
const scouts = await runParallelScouts(client, config, state);
|
|
1133
|
+
state.meta.parallelScoutsCompleted = true;
|
|
1134
|
+
state.meta.parallelScouts = {
|
|
1135
|
+
model: scouts.model,
|
|
1136
|
+
requested: scouts.requested,
|
|
1137
|
+
completed: scouts.completed,
|
|
1138
|
+
};
|
|
1139
|
+
state.messages.push({
|
|
1140
|
+
role: "user",
|
|
1141
|
+
content: scouts.summary,
|
|
1142
|
+
});
|
|
1143
|
+
await store.appendEvent("parallel_scouts.completed", {
|
|
1144
|
+
model: scouts.model,
|
|
1145
|
+
requested: scouts.requested,
|
|
1146
|
+
completed: scouts.completed,
|
|
1147
|
+
scouts: scouts.scouts.map((scout) => ({
|
|
1148
|
+
name: scout.name,
|
|
1149
|
+
model: scout.model,
|
|
1150
|
+
content: scout.content || "",
|
|
1151
|
+
error: scout.error || "",
|
|
1152
|
+
})),
|
|
1153
|
+
});
|
|
1154
|
+
await store.saveState(state);
|
|
1155
|
+
observers.event("parallel_scouts.completed", {
|
|
1156
|
+
model: scouts.model,
|
|
1157
|
+
requested: scouts.requested,
|
|
1158
|
+
completed: scouts.completed,
|
|
1159
|
+
});
|
|
1160
|
+
emitConsole(config, `Parallel scouts: ${scouts.completed}/${scouts.requested} completed using ${scouts.model}`, {
|
|
1161
|
+
kind: "meta",
|
|
1162
|
+
});
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1114
1165
|
const repair = repairModelMessageHistory(state, config);
|
|
1115
1166
|
if (repair.changed) {
|
|
1116
1167
|
await store.appendEvent("history.repaired", repair);
|
|
@@ -1129,6 +1180,9 @@ export async function runAgent(config) {
|
|
|
1129
1180
|
allowShellTool: config.allowShellTool,
|
|
1130
1181
|
allowWrapperTools: config.allowWrapperTools,
|
|
1131
1182
|
preferredWrapper: normalizeWrapperName(config.preferredWrapper),
|
|
1183
|
+
allowWebSearch: config.allowWebSearch,
|
|
1184
|
+
allowParallelScouts: config.allowParallelScouts,
|
|
1185
|
+
parallelScoutCount: config.parallelScoutCount,
|
|
1132
1186
|
wrappers: config.allowWrapperTools ? wrapperStatusText() : "",
|
|
1133
1187
|
workspaceFileTools: summarizeWorkspaceTools(config),
|
|
1134
1188
|
shellSandbox: config.useDockerSandbox ? "docker" : "host",
|
|
@@ -1198,6 +1252,8 @@ export async function runAgent(config) {
|
|
|
1198
1252
|
agentWrappersAvailable: config.allowWrapperTools,
|
|
1199
1253
|
preferredWrapper: normalizeWrapperName(config.preferredWrapper),
|
|
1200
1254
|
agentWrappers: config.allowWrapperTools ? wrapperStatusText() : "",
|
|
1255
|
+
webSearchAvailable: config.allowWebSearch !== false,
|
|
1256
|
+
parallelScouts: state.meta.parallelScouts || null,
|
|
1201
1257
|
shellSandbox: config.useDockerSandbox ? "docker" : "host",
|
|
1202
1258
|
sandboxMode: config.sandboxMode,
|
|
1203
1259
|
packageInstallPolicy: config.packageInstallPolicy,
|
package/src/capabilities.js
CHANGED
|
@@ -207,6 +207,11 @@ export async function buildCapabilityReport(projectRoot, packageVersion, config)
|
|
|
207
207
|
toolName: skill.toolName,
|
|
208
208
|
available: skill.available,
|
|
209
209
|
})),
|
|
210
|
+
orchestration: {
|
|
211
|
+
webSearch: config.allowWebSearch !== false,
|
|
212
|
+
parallelScouts: config.allowParallelScouts !== false,
|
|
213
|
+
parallelScoutCount: Number(config.parallelScoutCount) || 3,
|
|
214
|
+
},
|
|
210
215
|
},
|
|
211
216
|
checks,
|
|
212
217
|
maintenancePolicy: maintenancePolicyChecks(config),
|
package/src/cli.js
CHANGED
|
@@ -46,6 +46,9 @@ export function parseArgs(argv) {
|
|
|
46
46
|
allowFileTools: undefined,
|
|
47
47
|
allowWrapperTools: undefined,
|
|
48
48
|
allowAuxiliaryTools: undefined,
|
|
49
|
+
allowWebSearch: undefined,
|
|
50
|
+
allowParallelScouts: undefined,
|
|
51
|
+
parallelScoutCount: undefined,
|
|
49
52
|
allowDestructive: undefined,
|
|
50
53
|
preferredWrapper: "",
|
|
51
54
|
taskProfile: "",
|
|
@@ -183,6 +186,27 @@ export function parseArgs(argv) {
|
|
|
183
186
|
result.allowAuxiliaryTools = false;
|
|
184
187
|
continue;
|
|
185
188
|
}
|
|
189
|
+
if (arg === "--web-search") {
|
|
190
|
+
result.allowWebSearch = true;
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
if (arg === "--no-web-search") {
|
|
194
|
+
result.allowWebSearch = false;
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
if (arg === "--parallel-scouts") {
|
|
198
|
+
result.allowParallelScouts = true;
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
if (arg === "--no-parallel-scouts") {
|
|
202
|
+
result.allowParallelScouts = false;
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
if (arg === "--scout-count") {
|
|
206
|
+
result.parallelScoutCount = Number(readOption(argv, i));
|
|
207
|
+
i += 1;
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
186
210
|
if (arg === "--allow-wrappers") {
|
|
187
211
|
result.allowWrapperTools = true;
|
|
188
212
|
continue;
|
|
@@ -237,7 +261,7 @@ export function parseArgs(argv) {
|
|
|
237
261
|
|
|
238
262
|
function printUsage() {
|
|
239
263
|
console.log(
|
|
240
|
-
'Usage: aginti [chat] OR aginti web [--port 3210] OR aginti login deepseek|openai|grsai OR aginti resume [latest|<session-id>] ["prompt"] OR aginti queue <session-id> "message" OR aginti [--image] [--latex] [--routing smart|fast|complex|manual] [--provider deepseek|openai|mock] [--sandbox-mode host|docker-readonly|docker-workspace] [--package-install-policy block|prompt|allow] [--approve-package-installs] [--allow-shell|--no-shell] [--allow-
|
|
264
|
+
'Usage: aginti [chat] OR aginti web [--port 3210] OR aginti login deepseek|openai|grsai OR aginti resume [latest|<session-id>] ["prompt"] OR aginti queue <session-id> "message" OR aginti [--image] [--latex] [--routing smart|fast|complex|manual] [--provider deepseek|openai|mock] [--sandbox-mode host|docker-readonly|docker-workspace] [--package-install-policy block|prompt|allow] [--approve-package-installs] [--allow-shell|--no-shell] [--allow-file-tools|--no-file-tools] [--web-search|--no-web-search] [--parallel-scouts|--no-parallel-scouts --scout-count 3] [--allow-auxiliary-tools|--no-auxiliary-tools] [--allow-wrappers --wrapper codex] [--sandbox-status|--sandbox-preflight] "your task"'
|
|
241
265
|
);
|
|
242
266
|
}
|
|
243
267
|
|
|
@@ -254,6 +278,9 @@ function agentDefaults(args) {
|
|
|
254
278
|
allowShellTool: args.allowShellTool ?? true,
|
|
255
279
|
allowFileTools: args.allowFileTools ?? true,
|
|
256
280
|
allowAuxiliaryTools: args.allowAuxiliaryTools ?? true,
|
|
281
|
+
allowWebSearch: args.allowWebSearch ?? true,
|
|
282
|
+
allowParallelScouts: args.allowParallelScouts ?? true,
|
|
283
|
+
parallelScoutCount: args.parallelScoutCount || 3,
|
|
257
284
|
sandboxMode: args.sandboxMode || "docker-workspace",
|
|
258
285
|
packageInstallPolicy: args.packageInstallPolicy || "allow",
|
|
259
286
|
useDockerSandbox: args.useDockerSandbox ?? true,
|
package/src/config.js
CHANGED
|
@@ -90,6 +90,12 @@ export function resolveRuntimeConfig(args, overrides = {}) {
|
|
|
90
90
|
overrides.allowAuxiliaryTools ?? args.allowAuxiliaryTools ?? process.env.ALLOW_AUXILIARY_TOOLS,
|
|
91
91
|
true
|
|
92
92
|
),
|
|
93
|
+
allowWebSearch: parseBoolean(overrides.allowWebSearch ?? args.allowWebSearch ?? process.env.ALLOW_WEB_SEARCH, true),
|
|
94
|
+
allowParallelScouts: parseBoolean(
|
|
95
|
+
overrides.allowParallelScouts ?? args.allowParallelScouts ?? process.env.AGINTI_PARALLEL_SCOUTS,
|
|
96
|
+
true
|
|
97
|
+
),
|
|
98
|
+
parallelScoutCount: parseNumber(overrides.parallelScoutCount ?? args.parallelScoutCount ?? process.env.AGINTI_SCOUT_COUNT, 3),
|
|
93
99
|
preferredWrapper: normalizeWrapperName(
|
|
94
100
|
overrides.preferredWrapper ?? args.preferredWrapper ?? process.env.PREFERRED_WRAPPER ?? process.env.AGENT_WRAPPER
|
|
95
101
|
),
|
package/src/guardrails.js
CHANGED
|
@@ -72,6 +72,18 @@ export function checkToolUse({ toolName, args, snapshot, config }) {
|
|
|
72
72
|
return { allowed: true };
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
if (toolName === "web_search") {
|
|
76
|
+
if (config.allowWebSearch === false) {
|
|
77
|
+
return { allowed: false, reason: "Web search is disabled for this run.", category: "web-search" };
|
|
78
|
+
}
|
|
79
|
+
const query = String(args.query || "").trim();
|
|
80
|
+
if (!query) return { allowed: false, reason: "Search query is required.", category: "web-search" };
|
|
81
|
+
if (Buffer.byteLength(query, "utf8") > 500) {
|
|
82
|
+
return { allowed: false, reason: "Search query is too large.", category: "web-search" };
|
|
83
|
+
}
|
|
84
|
+
return { allowed: true };
|
|
85
|
+
}
|
|
86
|
+
|
|
75
87
|
if (toolName === "open_workspace_file" || toolName === "preview_workspace") {
|
|
76
88
|
if (!config.allowFileTools) {
|
|
77
89
|
return { allowed: false, reason: "Workspace preview tools require file tools to be enabled.", category: "workspace-tools" };
|
package/src/interactive-cli.js
CHANGED
|
@@ -18,6 +18,9 @@ const ansi = {
|
|
|
18
18
|
green: "\x1b[32m",
|
|
19
19
|
yellow: "\x1b[33m",
|
|
20
20
|
red: "\x1b[31m",
|
|
21
|
+
blue: "\x1b[34m",
|
|
22
|
+
magenta: "\x1b[35m",
|
|
23
|
+
faint: "\x1b[2m",
|
|
21
24
|
clearLine: "\x1b[2K",
|
|
22
25
|
cursorHide: "\x1b[?25l",
|
|
23
26
|
cursorShow: "\x1b[?25h",
|
|
@@ -37,6 +40,8 @@ const SLASH_COMMANDS = [
|
|
|
37
40
|
"/resume",
|
|
38
41
|
"/sessions",
|
|
39
42
|
"/profile",
|
|
43
|
+
"/web-search",
|
|
44
|
+
"/scouts",
|
|
40
45
|
"/routing",
|
|
41
46
|
"/provider",
|
|
42
47
|
"/model",
|
|
@@ -62,6 +67,26 @@ function label(name, bgCode) {
|
|
|
62
67
|
return color(` ${name} `, bgCode, ansi.bold);
|
|
63
68
|
}
|
|
64
69
|
|
|
70
|
+
function terminalWidth() {
|
|
71
|
+
return Math.max(Number(output.columns) || 80, 40);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function visualLength(value) {
|
|
75
|
+
return stripAnsi(value).length;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function padVisible(value, width) {
|
|
79
|
+
const padding = Math.max(width - visualLength(value), 0);
|
|
80
|
+
return `${value}${" ".repeat(padding)}`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function panelLine(content = "", bgCode = ansi.systemBg) {
|
|
84
|
+
const width = terminalWidth();
|
|
85
|
+
if (!useColor) return padVisible(content, width);
|
|
86
|
+
const padded = padVisible(content, width).replaceAll(ansi.reset, `${ansi.reset}${bgCode}`);
|
|
87
|
+
return `${bgCode}${padded}${ansi.reset}`;
|
|
88
|
+
}
|
|
89
|
+
|
|
65
90
|
function userPrompt() {
|
|
66
91
|
return `\n${label("user>", ansi.userBg)} ${color("|", ansi.userBg)} `;
|
|
67
92
|
}
|
|
@@ -88,12 +113,13 @@ function commandSuggestions(line = "") {
|
|
|
88
113
|
return SLASH_COMMANDS.filter((command) => command.startsWith(trimmed)).slice(0, 8);
|
|
89
114
|
}
|
|
90
115
|
|
|
91
|
-
function stripMarkdown(text) {
|
|
116
|
+
export function stripMarkdown(text) {
|
|
92
117
|
const lines = String(text || "").split(/\r?\n/);
|
|
93
118
|
let inFence = false;
|
|
94
119
|
const rendered = [];
|
|
95
120
|
|
|
96
|
-
for (
|
|
121
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
122
|
+
const rawLine = lines[index];
|
|
97
123
|
let line = rawLine;
|
|
98
124
|
if (/^\s*```/.test(line)) {
|
|
99
125
|
inFence = !inFence;
|
|
@@ -117,6 +143,12 @@ function stripMarkdown(text) {
|
|
|
117
143
|
if (/^\s*\|?\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)+\|?\s*$/.test(line)) {
|
|
118
144
|
continue;
|
|
119
145
|
}
|
|
146
|
+
const table = parseMarkdownTable(lines, index);
|
|
147
|
+
if (table) {
|
|
148
|
+
rendered.push(...renderMarkdownTable(table));
|
|
149
|
+
index += table.rawLineCount - 1;
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
120
152
|
line = line.replace(/\[([^\]]+)\]\(([^)]+)\)/g, "$1 ($2)");
|
|
121
153
|
line = line.replace(/\*\*([^*]+)\*\*/g, (_, value) => color(value, ansi.bold));
|
|
122
154
|
line = line.replace(/__([^_]+)__/g, (_, value) => color(value, ansi.bold));
|
|
@@ -135,6 +167,75 @@ function stripMarkdown(text) {
|
|
|
135
167
|
return rendered.join("\n").replace(/\n{3,}/g, "\n\n").trimEnd();
|
|
136
168
|
}
|
|
137
169
|
|
|
170
|
+
function splitMarkdownTableRow(line = "") {
|
|
171
|
+
const trimmed = String(line || "").trim();
|
|
172
|
+
if (!trimmed.includes("|")) return null;
|
|
173
|
+
const normalized = trimmed.replace(/^\|/, "").replace(/\|$/, "");
|
|
174
|
+
const cells = normalized.split("|").map((cell) => cell.trim());
|
|
175
|
+
return cells.length >= 2 ? cells : null;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function isMarkdownTableSeparator(line = "") {
|
|
179
|
+
const cells = splitMarkdownTableRow(line);
|
|
180
|
+
return Boolean(cells?.length) && cells.every((cell) => /^:?-{3,}:?$/.test(cell));
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function parseMarkdownTable(lines, startIndex) {
|
|
184
|
+
const header = splitMarkdownTableRow(lines[startIndex]);
|
|
185
|
+
if (!header || !isMarkdownTableSeparator(lines[startIndex + 1] || "")) return null;
|
|
186
|
+
const rows = [];
|
|
187
|
+
let index = startIndex + 2;
|
|
188
|
+
while (index < lines.length) {
|
|
189
|
+
const row = splitMarkdownTableRow(lines[index]);
|
|
190
|
+
if (!row) break;
|
|
191
|
+
rows.push(row);
|
|
192
|
+
index += 1;
|
|
193
|
+
}
|
|
194
|
+
return {
|
|
195
|
+
header,
|
|
196
|
+
rows,
|
|
197
|
+
rawLineCount: Math.max(index - startIndex, 2),
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function renderMarkdownTable(table) {
|
|
202
|
+
const allRows = [table.header, ...table.rows];
|
|
203
|
+
const columnCount = Math.max(...allRows.map((row) => row.length));
|
|
204
|
+
const widths = Array.from({ length: columnCount }, (_unused, column) =>
|
|
205
|
+
Math.min(
|
|
206
|
+
Math.max(
|
|
207
|
+
...allRows.map((row) => visualLength(stripMarkdownInline(row[column] || ""))),
|
|
208
|
+
3
|
|
209
|
+
),
|
|
210
|
+
36
|
|
211
|
+
)
|
|
212
|
+
);
|
|
213
|
+
const formatRow = (row, header = false) =>
|
|
214
|
+
widths
|
|
215
|
+
.map((width, column) => {
|
|
216
|
+
const value = stripMarkdownInline(row[column] || "");
|
|
217
|
+
return padVisible(value, width);
|
|
218
|
+
})
|
|
219
|
+
.join(color(" │ ", ansi.dim));
|
|
220
|
+
const separator = widths.map((width) => "─".repeat(width)).join(color("──┼──", ansi.dim));
|
|
221
|
+
return [
|
|
222
|
+
color(formatRow(table.header, true), ansi.bold, ansi.cyan),
|
|
223
|
+
color(separator, ansi.dim),
|
|
224
|
+
...table.rows.map((row) => formatRow(row)),
|
|
225
|
+
];
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function stripMarkdownInline(value = "") {
|
|
229
|
+
return String(value)
|
|
230
|
+
.replace(/\[([^\]]+)\]\(([^)]+)\)/g, "$1 ($2)")
|
|
231
|
+
.replace(/\*\*([^*]+)\*\*/g, (_, text) => color(text, ansi.bold))
|
|
232
|
+
.replace(/__([^_]+)__/g, (_, text) => color(text, ansi.bold))
|
|
233
|
+
.replace(/`([^`]+)`/g, (_, text) => color(text, ansi.yellow))
|
|
234
|
+
.replace(/~~([^~]+)~~/g, "$1")
|
|
235
|
+
.replace(/\*([^*]+)\*/g, "$1")
|
|
236
|
+
.replace(/_([^_]+)_/g, "$1");
|
|
237
|
+
}
|
|
238
|
+
|
|
138
239
|
function rolePrefix(name, bgCode) {
|
|
139
240
|
return `${label(name, bgCode)} ${color("|", bgCode)} `;
|
|
140
241
|
}
|
|
@@ -181,7 +282,10 @@ async function renderLaunchHeader(packageVersion = "") {
|
|
|
181
282
|
const title = "AgInTi Flow";
|
|
182
283
|
const subtitle = "web-first agent workspace";
|
|
183
284
|
const version = packageVersion ? `v${packageVersion}` : "";
|
|
184
|
-
const
|
|
285
|
+
const width = Math.min(Math.max(terminalWidth() - 2, 58), 82);
|
|
286
|
+
const top = `╭${"─".repeat(width)}╮`;
|
|
287
|
+
const mid = `├${"─".repeat(width)}┤`;
|
|
288
|
+
const bottom = `╰${"─".repeat(width)}╯`;
|
|
185
289
|
|
|
186
290
|
if (!useColor || process.env.AGINTIFLOW_NO_ANIMATION === "1") {
|
|
187
291
|
console.log(` AgInTiFlow ${packageVersion || ""}`.trim());
|
|
@@ -196,11 +300,15 @@ async function renderLaunchHeader(packageVersion = "") {
|
|
|
196
300
|
output.write(`\r${ansi.clearLine}`);
|
|
197
301
|
output.write(ansi.cursorShow);
|
|
198
302
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
console.log(
|
|
203
|
-
console.log(color(
|
|
303
|
+
const border = "\x1b[38;5;45m";
|
|
304
|
+
const titleLine = `${shimmerText(title, 2)} ${color(version, ansi.dim)}`;
|
|
305
|
+
const tagline = "browser + shell + files + docker + web search + scouts";
|
|
306
|
+
console.log(color(top, border));
|
|
307
|
+
console.log(`${color("│", border)} ${padVisible(titleLine, width - 2)} ${color("│", border)}`);
|
|
308
|
+
console.log(`${color("│", border)} ${color(padVisible(subtitle, width - 2), ansi.dim)} ${color("│", border)}`);
|
|
309
|
+
console.log(color(mid, border));
|
|
310
|
+
console.log(`${color("│", border)} ${color(padVisible(tagline, width - 2), ansi.cyan)} ${color("│", border)}`);
|
|
311
|
+
console.log(color(bottom, border));
|
|
204
312
|
}
|
|
205
313
|
|
|
206
314
|
function printHelp() {
|
|
@@ -217,6 +325,8 @@ function printHelp() {
|
|
|
217
325
|
" /resume <session-id> Continue a saved session.",
|
|
218
326
|
" /sessions List recent sessions in this project.",
|
|
219
327
|
" /profile <name> Set task profile, e.g. code, website, latex, maintenance.",
|
|
328
|
+
" /web-search on|off Enable or disable the web_search tool.",
|
|
329
|
+
" /scouts on|off|<1-4> Enable parallel DeepSeek scouts and set scout count.",
|
|
220
330
|
" /routing <mode> Set routing: smart, fast, complex, manual.",
|
|
221
331
|
" /provider <name> Set provider: deepseek, openai, mock.",
|
|
222
332
|
" /model <name> Set an explicit model, or /model auto.",
|
|
@@ -243,12 +353,17 @@ function renderPromptBuffer(buffer, previousLineCount = 0) {
|
|
|
243
353
|
const lines = String(buffer || "").split("\n");
|
|
244
354
|
const suggestions = commandSuggestions(lines[0] || "");
|
|
245
355
|
const rendered = [];
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
356
|
+
const firstPrefix = " user ";
|
|
357
|
+
const nextPrefix = " ... ";
|
|
358
|
+
const emptyHint = color("type a request, /help, Enter to send, Ctrl+J for newline", ansi.faint);
|
|
359
|
+
const cursor = color("▌", ansi.bold);
|
|
360
|
+
rendered.push(panelLine(`${firstPrefix}${lines[0] || emptyHint}${lines.length === 1 ? cursor : ""}`, ansi.userBg));
|
|
361
|
+
for (const [index, line] of lines.slice(1).entries()) {
|
|
362
|
+
const isLast = index === lines.length - 2;
|
|
363
|
+
rendered.push(panelLine(`${nextPrefix}${line}${isLast ? cursor : ""}`, ansi.userBg));
|
|
249
364
|
}
|
|
250
365
|
if (suggestions.length > 0) {
|
|
251
|
-
rendered.push(
|
|
366
|
+
rendered.push(panelLine(` hint ${color(suggestions.join(" "), ansi.dim)}`, ansi.systemBg));
|
|
252
367
|
}
|
|
253
368
|
output.write(rendered.join("\n"));
|
|
254
369
|
return rendered.length;
|
|
@@ -292,7 +407,7 @@ function readTtyPrompt() {
|
|
|
292
407
|
reject(createAbortError());
|
|
293
408
|
return;
|
|
294
409
|
}
|
|
295
|
-
if ((key.ctrl && key.name === "j") ||
|
|
410
|
+
if ((key.ctrl && key.name === "j") || key.sequence === "\n") {
|
|
296
411
|
buffer += "\n";
|
|
297
412
|
redraw();
|
|
298
413
|
return;
|
|
@@ -350,7 +465,7 @@ function printStatus(state) {
|
|
|
350
465
|
printSystemLine(`provider=${state.provider || "auto"} routing=${state.routingMode} model=${state.model || "auto"}`);
|
|
351
466
|
printSystemLine(`profile=${state.taskProfile} maxSteps=${state.maxSteps}`);
|
|
352
467
|
printSystemLine(
|
|
353
|
-
`shell=${state.allowShellTool} files=${state.allowFileTools} auxiliary=${state.allowAuxiliaryTools} sandbox=${state.sandboxMode} installs=${state.packageInstallPolicy}`
|
|
468
|
+
`shell=${state.allowShellTool} files=${state.allowFileTools} webSearch=${state.allowWebSearch} scouts=${state.allowParallelScouts}:${state.parallelScoutCount} auxiliary=${state.allowAuxiliaryTools} sandbox=${state.sandboxMode} installs=${state.packageInstallPolicy}`
|
|
354
469
|
);
|
|
355
470
|
if (state.sandboxMode !== "host") {
|
|
356
471
|
printSystemLine(`dockerWorkspace=/workspace -> ${state.commandCwd || process.cwd()}`);
|
|
@@ -435,6 +550,9 @@ function createState(args = {}) {
|
|
|
435
550
|
allowShellTool: args.allowShellTool ?? true,
|
|
436
551
|
allowFileTools: args.allowFileTools ?? true,
|
|
437
552
|
allowAuxiliaryTools: args.allowAuxiliaryTools ?? true,
|
|
553
|
+
allowWebSearch: args.allowWebSearch ?? true,
|
|
554
|
+
allowParallelScouts: args.allowParallelScouts ?? true,
|
|
555
|
+
parallelScoutCount: args.parallelScoutCount || 3,
|
|
438
556
|
allowWrapperTools: args.allowWrapperTools ?? false,
|
|
439
557
|
allowDestructive: args.allowDestructive ?? false,
|
|
440
558
|
preferredWrapper: args.preferredWrapper || "codex",
|
|
@@ -597,6 +715,22 @@ async function handleCommand(line, state, packageDir) {
|
|
|
597
715
|
printSystemLine(`profile=${state.taskProfile}`);
|
|
598
716
|
return true;
|
|
599
717
|
}
|
|
718
|
+
if (command === "web-search") {
|
|
719
|
+
state.allowWebSearch = value !== "off";
|
|
720
|
+
printSystemLine(`webSearch=${state.allowWebSearch ? "on" : "off"}`);
|
|
721
|
+
return true;
|
|
722
|
+
}
|
|
723
|
+
if (command === "scouts") {
|
|
724
|
+
if (value === "off") {
|
|
725
|
+
state.allowParallelScouts = false;
|
|
726
|
+
} else {
|
|
727
|
+
state.allowParallelScouts = true;
|
|
728
|
+
const count = Number(value);
|
|
729
|
+
if (Number.isFinite(count) && count > 0) state.parallelScoutCount = Math.min(Math.max(count, 1), 4);
|
|
730
|
+
}
|
|
731
|
+
printSystemLine(`parallelScouts=${state.allowParallelScouts ? "on" : "off"} count=${state.parallelScoutCount}`);
|
|
732
|
+
return true;
|
|
733
|
+
}
|
|
600
734
|
if (command === "routing") {
|
|
601
735
|
state.routingMode = value || "smart";
|
|
602
736
|
printSystemLine(`routing=${state.routingMode}`);
|
|
@@ -692,6 +826,9 @@ async function runPrompt(prompt, state, packageDir) {
|
|
|
692
826
|
allowShellTool: state.allowShellTool,
|
|
693
827
|
allowFileTools: state.allowFileTools,
|
|
694
828
|
allowAuxiliaryTools: state.allowAuxiliaryTools,
|
|
829
|
+
allowWebSearch: state.allowWebSearch,
|
|
830
|
+
allowParallelScouts: state.allowParallelScouts,
|
|
831
|
+
parallelScoutCount: state.parallelScoutCount,
|
|
695
832
|
allowWrapperTools: state.allowWrapperTools,
|
|
696
833
|
allowDestructive: state.allowDestructive,
|
|
697
834
|
preferredWrapper: state.preferredWrapper,
|
package/src/model-client.js
CHANGED
|
@@ -122,6 +122,16 @@ function mockWorkspaceToolForGoal(goal = "") {
|
|
|
122
122
|
return null;
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
function mockWebSearchToolForGoal(goal = "") {
|
|
126
|
+
const text = String(goal || "");
|
|
127
|
+
if (!/\b(web search|search web|search the web|look up|current|latest|recent|docs|documentation|online)\b/i.test(text)) return null;
|
|
128
|
+
const query = text.replace(/\s+/g, " ").slice(0, 180);
|
|
129
|
+
return mockToolCall("web_search", {
|
|
130
|
+
query,
|
|
131
|
+
maxResults: 3,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
125
135
|
function mockPreviewToolForGoal(goal = "") {
|
|
126
136
|
const text = String(goal).toLowerCase();
|
|
127
137
|
if (!/(open|preview|view|browser|website|web\s*site)/.test(text)) return null;
|
|
@@ -213,6 +223,12 @@ export async function createPlan(client, config, state) {
|
|
|
213
223
|
.map((skill) => `${skill.id} via ${skill.toolName} (${skill.available ? "key available" : `needs ${skill.keyName}`})`)
|
|
214
224
|
.join(", ")}. For raster image generation requests, plan to use generate_image when a GRSAI key is available; otherwise ask the user to run /auxilliary grsai or aginti login grsai.`
|
|
215
225
|
: "Auxiliary skills are disabled for this run.",
|
|
226
|
+
config.allowWebSearch
|
|
227
|
+
? "web_search is available for current information, docs, install errors, package/toolchain questions, and source discovery. Prefer web_search over opening a search engine in the browser."
|
|
228
|
+
: "web_search is disabled for this run.",
|
|
229
|
+
config.allowParallelScouts
|
|
230
|
+
? `Parallel scout notes may be injected before execution for complex tasks. Scout count: ${config.parallelScoutCount}.`
|
|
231
|
+
: "Parallel scouts are disabled.",
|
|
216
232
|
`Task profile: ${taskProfile.label}. ${taskProfile.prompt}`,
|
|
217
233
|
engineeringGuidance,
|
|
218
234
|
"A canvas/artifacts tunnel is available through send_to_canvas. Use it when an output should be highlighted visually, such as screenshots, image files, important markdown, diffs, or generated artifact paths. It is optional for ordinary text answers.",
|
|
@@ -362,6 +378,26 @@ export async function requestNextStep(client, config, messages) {
|
|
|
362
378
|
},
|
|
363
379
|
];
|
|
364
380
|
|
|
381
|
+
if (config.allowWebSearch !== false) {
|
|
382
|
+
tools.splice(-1, 0, {
|
|
383
|
+
type: "function",
|
|
384
|
+
function: {
|
|
385
|
+
name: "web_search",
|
|
386
|
+
description:
|
|
387
|
+
"Search the public web for current information, documentation, install errors, package/toolchain guidance, and source discovery. Returns compact result titles, URLs, and snippets. Prefer this over browser search-engine navigation; open specific results only when needed.",
|
|
388
|
+
parameters: {
|
|
389
|
+
type: "object",
|
|
390
|
+
properties: {
|
|
391
|
+
query: { type: "string", description: "Search query. Do not include secrets or tokens." },
|
|
392
|
+
maxResults: { type: "integer", description: "Number of results, 1 to 10. Defaults to 5." },
|
|
393
|
+
},
|
|
394
|
+
required: ["query"],
|
|
395
|
+
additionalProperties: false,
|
|
396
|
+
},
|
|
397
|
+
},
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
|
|
365
401
|
if (config.allowFileTools) {
|
|
366
402
|
tools.splice(
|
|
367
403
|
0,
|
|
@@ -637,6 +673,9 @@ export async function requestNextStep(client, config, messages) {
|
|
|
637
673
|
Array.isArray(toolPayload.recommendedReads) && toolPayload.recommendedReads.length
|
|
638
674
|
? `Recommended reads: ${toolPayload.recommendedReads.join(", ")}`
|
|
639
675
|
: "",
|
|
676
|
+
Array.isArray(toolPayload.results) && toolPayload.results.length
|
|
677
|
+
? `Results:\n${toolPayload.results.map((item, index) => `${index + 1}. ${item.title} ${item.url}`).join("\n")}`
|
|
678
|
+
: "",
|
|
640
679
|
toolPayload.path ? `Path: ${toolPayload.path}` : "",
|
|
641
680
|
Array.isArray(toolPayload.changes)
|
|
642
681
|
? toolPayload.changes
|
|
@@ -677,6 +716,13 @@ export async function requestNextStep(client, config, messages) {
|
|
|
677
716
|
}
|
|
678
717
|
}
|
|
679
718
|
|
|
719
|
+
if (config.allowWebSearch !== false) {
|
|
720
|
+
const webSearchTool = mockWebSearchToolForGoal(config.goal);
|
|
721
|
+
if (webSearchTool) {
|
|
722
|
+
return mockChatResponse("Mock mode will exercise the web search tool.", [webSearchTool]);
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
|
|
680
726
|
const canvasTool = mockCanvasToolForGoal(config.goal);
|
|
681
727
|
if (canvasTool) {
|
|
682
728
|
return mockChatResponse("Mock mode will publish a canvas artifact for the UI tunnel.", [canvasTool]);
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { engineeringGuidanceForTask } from "./engineering-guidance.js";
|
|
2
|
+
import { getModelPresets } from "./model-routing.js";
|
|
3
|
+
import { getTaskProfile, normalizeTaskProfile } from "./task-profiles.js";
|
|
4
|
+
import { redactSensitiveText } from "./redaction.js";
|
|
5
|
+
|
|
6
|
+
const SCOUTS = [
|
|
7
|
+
{
|
|
8
|
+
name: "architect",
|
|
9
|
+
prompt:
|
|
10
|
+
"Decompose the task into independent workstreams. Identify the first files, manifests, logs, or commands the executor should inspect. Keep it actionable.",
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
name: "implementer",
|
|
14
|
+
prompt:
|
|
15
|
+
"Predict the most likely implementation path across languages/toolchains. Suggest patch boundaries and focused checks. Avoid writing full code.",
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
name: "reviewer",
|
|
19
|
+
prompt:
|
|
20
|
+
"Find instruction-compliance risks, missing tests, system/environment pitfalls, and stop conditions. Suggest how to avoid loops.",
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
name: "researcher",
|
|
24
|
+
prompt:
|
|
25
|
+
"If current external information may matter, suggest exact web_search queries and source types. Otherwise say no web search needed.",
|
|
26
|
+
},
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
function shouldUseComplexScouts(config, state) {
|
|
30
|
+
const profile = normalizeTaskProfile(config.taskProfile);
|
|
31
|
+
const goal = String(config.goal || state.goal || "");
|
|
32
|
+
return (
|
|
33
|
+
Number(config.routeComplexityScore || 0) >= 3 ||
|
|
34
|
+
["large-codebase", "maintenance", "code"].includes(profile) ||
|
|
35
|
+
/\b(large|complex|complicated|debug|bug|failing|system|install|setup|codebase|repo|multi[- ]file|architecture|refactor|migration)\b/i.test(
|
|
36
|
+
goal
|
|
37
|
+
)
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function shouldRunParallelScouts(config, state) {
|
|
42
|
+
if (config.allowParallelScouts === false) return false;
|
|
43
|
+
if (config.provider === "mock") return false;
|
|
44
|
+
if (state?.meta?.parallelScoutsCompleted) return false;
|
|
45
|
+
return shouldUseComplexScouts(config, state || {});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function scoutModel(config) {
|
|
49
|
+
if (config.provider === "deepseek") return getModelPresets().fast.model;
|
|
50
|
+
return config.model;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function scoutMessages(config, state, scout) {
|
|
54
|
+
const profile = getTaskProfile(config.taskProfile);
|
|
55
|
+
const guidance = engineeringGuidanceForTask(config.goal || state.goal || "", config.taskProfile);
|
|
56
|
+
return [
|
|
57
|
+
{
|
|
58
|
+
role: "system",
|
|
59
|
+
content:
|
|
60
|
+
"You are a parallel scout for AgInTiFlow. Your answer is advisory only: do not claim work is done, do not ask questions, do not use tools, and keep under 180 words.",
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
role: "user",
|
|
64
|
+
content: [
|
|
65
|
+
`Scout role: ${scout.name}`,
|
|
66
|
+
scout.prompt,
|
|
67
|
+
`Goal: ${config.goal || state.goal || ""}`,
|
|
68
|
+
`Task profile: ${profile.label}. ${profile.prompt}`,
|
|
69
|
+
guidance,
|
|
70
|
+
`Workspace: ${config.commandCwd}`,
|
|
71
|
+
`Sandbox: ${config.sandboxMode}; package policy: ${config.packageInstallPolicy}`,
|
|
72
|
+
`Current plan:\n${state.plan || "(none yet)"}`,
|
|
73
|
+
]
|
|
74
|
+
.filter(Boolean)
|
|
75
|
+
.join("\n"),
|
|
76
|
+
},
|
|
77
|
+
];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export async function runParallelScouts(client, config, state) {
|
|
81
|
+
const count = Math.min(Math.max(Number(config.parallelScoutCount) || 3, 1), SCOUTS.length);
|
|
82
|
+
const selected = SCOUTS.slice(0, count);
|
|
83
|
+
const model = scoutModel(config);
|
|
84
|
+
const settled = await Promise.allSettled(
|
|
85
|
+
selected.map(async (scout) => {
|
|
86
|
+
const response = await client.chat.completions.create(
|
|
87
|
+
{
|
|
88
|
+
model,
|
|
89
|
+
temperature: 0,
|
|
90
|
+
messages: scoutMessages(config, state, scout),
|
|
91
|
+
},
|
|
92
|
+
config.abortSignal ? { signal: config.abortSignal } : undefined
|
|
93
|
+
);
|
|
94
|
+
return {
|
|
95
|
+
name: scout.name,
|
|
96
|
+
model,
|
|
97
|
+
content: redactSensitiveText(response.choices[0]?.message?.content || "").trim(),
|
|
98
|
+
};
|
|
99
|
+
})
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
const scouts = settled.map((item, index) => {
|
|
103
|
+
if (item.status === "fulfilled") return item.value;
|
|
104
|
+
return {
|
|
105
|
+
name: selected[index]?.name || `scout-${index + 1}`,
|
|
106
|
+
model,
|
|
107
|
+
error: redactSensitiveText(item.reason instanceof Error ? item.reason.message : String(item.reason)),
|
|
108
|
+
};
|
|
109
|
+
});
|
|
110
|
+
const completed = scouts.filter((scout) => scout.content).length;
|
|
111
|
+
const summary = [
|
|
112
|
+
"Parallel scout notes. Treat these as advisory, not completed work.",
|
|
113
|
+
...scouts.map((scout) =>
|
|
114
|
+
scout.content ? `\n## ${scout.name}\n${scout.content}` : `\n## ${scout.name}\nScout failed: ${scout.error || "unknown error"}`
|
|
115
|
+
),
|
|
116
|
+
].join("\n");
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
ok: completed > 0,
|
|
120
|
+
model,
|
|
121
|
+
requested: selected.length,
|
|
122
|
+
completed,
|
|
123
|
+
scouts,
|
|
124
|
+
summary,
|
|
125
|
+
};
|
|
126
|
+
}
|
package/src/web-db.js
CHANGED
|
@@ -3,7 +3,7 @@ import path from "node:path";
|
|
|
3
3
|
import { DatabaseSync } from "node:sqlite";
|
|
4
4
|
import { getModelPresets } from "./model-routing.js";
|
|
5
5
|
|
|
6
|
-
const PREFERENCES_SCHEMA_VERSION =
|
|
6
|
+
const PREFERENCES_SCHEMA_VERSION = 6;
|
|
7
7
|
|
|
8
8
|
function defaultPreferences(baseDir) {
|
|
9
9
|
const presets = getModelPresets();
|
|
@@ -19,6 +19,10 @@ function defaultPreferences(baseDir) {
|
|
|
19
19
|
commandCwd: path.resolve(baseDir),
|
|
20
20
|
allowShellTool: true,
|
|
21
21
|
allowFileTools: true,
|
|
22
|
+
allowAuxiliaryTools: true,
|
|
23
|
+
allowWebSearch: true,
|
|
24
|
+
allowParallelScouts: true,
|
|
25
|
+
parallelScoutCount: 3,
|
|
22
26
|
allowWrapperTools: false,
|
|
23
27
|
preferredWrapper: "codex",
|
|
24
28
|
wrapperTimeoutMs: 120000,
|
|
@@ -98,6 +102,11 @@ export class WebDatabase {
|
|
|
98
102
|
if ((parsed.preferencesSchemaVersion || 1) < 5) {
|
|
99
103
|
preferences.taskProfile = "auto";
|
|
100
104
|
}
|
|
105
|
+
if ((parsed.preferencesSchemaVersion || 1) < 6) {
|
|
106
|
+
preferences.allowWebSearch = true;
|
|
107
|
+
preferences.allowParallelScouts = true;
|
|
108
|
+
preferences.parallelScoutCount = 3;
|
|
109
|
+
}
|
|
101
110
|
this.savePreferences(preferences);
|
|
102
111
|
}
|
|
103
112
|
return preferences;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { isDomainAllowed } from "./guardrails.js";
|
|
2
|
+
import { redactSensitiveText } from "./redaction.js";
|
|
3
|
+
|
|
4
|
+
const MAX_QUERY_BYTES = 500;
|
|
5
|
+
const MAX_RESULTS = 10;
|
|
6
|
+
|
|
7
|
+
function decodeHtml(value = "") {
|
|
8
|
+
return String(value)
|
|
9
|
+
.replace(/&/g, "&")
|
|
10
|
+
.replace(/</g, "<")
|
|
11
|
+
.replace(/>/g, ">")
|
|
12
|
+
.replace(/"/g, '"')
|
|
13
|
+
.replace(/'/g, "'")
|
|
14
|
+
.replace(/'/g, "'")
|
|
15
|
+
.replace(/<[^>]+>/g, "")
|
|
16
|
+
.replace(/\s+/g, " ")
|
|
17
|
+
.trim();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function normalizeDuckDuckGoHref(href = "") {
|
|
21
|
+
const decoded = decodeHtml(href);
|
|
22
|
+
try {
|
|
23
|
+
const parsed = new URL(decoded, "https://duckduckgo.com");
|
|
24
|
+
const uddg = parsed.searchParams.get("uddg");
|
|
25
|
+
if (uddg) return decodeURIComponent(uddg);
|
|
26
|
+
if (/^https?:\/\//i.test(decoded)) return decoded;
|
|
27
|
+
return parsed.href;
|
|
28
|
+
} catch {
|
|
29
|
+
return decoded;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function parseDuckDuckGoHtml(html, maxResults, allowedDomains = []) {
|
|
34
|
+
const results = [];
|
|
35
|
+
const blocks = String(html || "").split(/<div class="result\b/i).slice(1);
|
|
36
|
+
for (const block of blocks) {
|
|
37
|
+
const anchor = block.match(/<a[^>]+class="[^"]*result__a[^"]*"[^>]+href="([^"]+)"[^>]*>([\s\S]*?)<\/a>/i);
|
|
38
|
+
if (!anchor) continue;
|
|
39
|
+
const url = normalizeDuckDuckGoHref(anchor[1]);
|
|
40
|
+
if (!/^https?:\/\//i.test(url)) continue;
|
|
41
|
+
if (!isDomainAllowed(url, allowedDomains)) continue;
|
|
42
|
+
const snippet = block.match(/<a[^>]+class="[^"]*result__snippet[^"]*"[^>]*>([\s\S]*?)<\/a>/i) ||
|
|
43
|
+
block.match(/<div[^>]+class="[^"]*result__snippet[^"]*"[^>]*>([\s\S]*?)<\/div>/i);
|
|
44
|
+
results.push({
|
|
45
|
+
title: decodeHtml(anchor[2]).slice(0, 220),
|
|
46
|
+
url,
|
|
47
|
+
snippet: snippet ? decodeHtml(snippet[1]).slice(0, 420) : "",
|
|
48
|
+
});
|
|
49
|
+
if (results.length >= maxResults) break;
|
|
50
|
+
}
|
|
51
|
+
return results;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function searchWeb(args = {}, config = {}) {
|
|
55
|
+
const query = String(args.query || "").trim();
|
|
56
|
+
if (!query) return { ok: false, toolName: "web_search", error: "Search query is required." };
|
|
57
|
+
if (Buffer.byteLength(query, "utf8") > MAX_QUERY_BYTES) {
|
|
58
|
+
return { ok: false, toolName: "web_search", error: "Search query is too large." };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const maxResults = Math.min(Math.max(Number(args.maxResults) || 5, 1), MAX_RESULTS);
|
|
62
|
+
const searchUrl = `https://duckduckgo.com/html/?q=${encodeURIComponent(query)}`;
|
|
63
|
+
if (config.webSearchDryRun) {
|
|
64
|
+
return {
|
|
65
|
+
ok: true,
|
|
66
|
+
toolName: "web_search",
|
|
67
|
+
query,
|
|
68
|
+
provider: "duckduckgo-html",
|
|
69
|
+
searchUrl,
|
|
70
|
+
dryRun: true,
|
|
71
|
+
results: [
|
|
72
|
+
{
|
|
73
|
+
title: `Dry-run search result for ${query}`,
|
|
74
|
+
url: "https://example.com/agintiflow-web-search-smoke",
|
|
75
|
+
snippet: "Deterministic web_search dry-run result.",
|
|
76
|
+
},
|
|
77
|
+
].slice(0, maxResults),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
const response = await fetch(searchUrl, {
|
|
83
|
+
signal: config.abortSignal || AbortSignal.timeout(Number(args.timeoutMs) || 12000),
|
|
84
|
+
headers: {
|
|
85
|
+
"User-Agent": "AgInTiFlow/1.0 (+https://flow.lazying.art)",
|
|
86
|
+
Accept: "text/html,application/xhtml+xml",
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
const html = await response.text();
|
|
90
|
+
const results = parseDuckDuckGoHtml(html, maxResults, config.allowedDomains || []);
|
|
91
|
+
return {
|
|
92
|
+
ok: true,
|
|
93
|
+
toolName: "web_search",
|
|
94
|
+
query: redactSensitiveText(query),
|
|
95
|
+
provider: "duckduckgo-html",
|
|
96
|
+
status: response.status,
|
|
97
|
+
searchUrl,
|
|
98
|
+
results,
|
|
99
|
+
note: results.length ? "" : "No results parsed. The search URL is included as fallback.",
|
|
100
|
+
};
|
|
101
|
+
} catch (error) {
|
|
102
|
+
return {
|
|
103
|
+
ok: false,
|
|
104
|
+
toolName: "web_search",
|
|
105
|
+
query: redactSensitiveText(query),
|
|
106
|
+
provider: "duckduckgo-html",
|
|
107
|
+
searchUrl,
|
|
108
|
+
error: redactSensitiveText(error instanceof Error ? error.message : String(error)),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
}
|
package/web.js
CHANGED
|
@@ -118,6 +118,7 @@ function normalizePreferencePayload(body = {}, current = db.getPreferences()) {
|
|
|
118
118
|
const providerDefaults = getProviderDefaults(provider);
|
|
119
119
|
const parsedMaxSteps = Number(body.maxSteps);
|
|
120
120
|
const parsedWrapperTimeoutMs = Number(body.wrapperTimeoutMs);
|
|
121
|
+
const parsedParallelScoutCount = Number(body.parallelScoutCount);
|
|
121
122
|
const sandboxMode = normalizeSandboxMode(body.sandboxMode || current.sandboxMode || "docker-workspace");
|
|
122
123
|
|
|
123
124
|
return {
|
|
@@ -143,6 +144,14 @@ function normalizePreferencePayload(body = {}, current = db.getPreferences()) {
|
|
|
143
144
|
allowFileTools: typeof body.allowFileTools === "boolean" ? body.allowFileTools : current.allowFileTools !== false,
|
|
144
145
|
allowAuxiliaryTools:
|
|
145
146
|
typeof body.allowAuxiliaryTools === "boolean" ? body.allowAuxiliaryTools : current.allowAuxiliaryTools !== false,
|
|
147
|
+
allowWebSearch:
|
|
148
|
+
typeof body.allowWebSearch === "boolean" ? body.allowWebSearch : current.allowWebSearch !== false,
|
|
149
|
+
allowParallelScouts:
|
|
150
|
+
typeof body.allowParallelScouts === "boolean" ? body.allowParallelScouts : current.allowParallelScouts !== false,
|
|
151
|
+
parallelScoutCount:
|
|
152
|
+
Number.isFinite(parsedParallelScoutCount) && parsedParallelScoutCount > 0
|
|
153
|
+
? Math.min(Math.max(parsedParallelScoutCount, 1), 4)
|
|
154
|
+
: Number(current.parallelScoutCount) || 3,
|
|
146
155
|
allowWrapperTools:
|
|
147
156
|
typeof body.allowWrapperTools === "boolean" ? body.allowWrapperTools : Boolean(current.allowWrapperTools),
|
|
148
157
|
preferredWrapper: normalizeWrapperName(body.preferredWrapper || current.preferredWrapper || "codex"),
|
|
@@ -221,6 +230,9 @@ function buildRunConfig(body, overrides = {}) {
|
|
|
221
230
|
allowShellTool: merged.allowShellTool,
|
|
222
231
|
allowFileTools: merged.allowFileTools,
|
|
223
232
|
allowAuxiliaryTools: merged.allowAuxiliaryTools,
|
|
233
|
+
allowWebSearch: merged.allowWebSearch,
|
|
234
|
+
allowParallelScouts: merged.allowParallelScouts,
|
|
235
|
+
parallelScoutCount: merged.parallelScoutCount,
|
|
224
236
|
allowWrapperTools: merged.allowWrapperTools,
|
|
225
237
|
preferredWrapper: merged.preferredWrapper,
|
|
226
238
|
wrapperTimeoutMs: merged.wrapperTimeoutMs,
|