@lazyingart/agintiflow 0.11.0 → 0.12.0
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 +4 -0
- 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-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 +27 -1
- 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
|
@@ -73,6 +73,10 @@ For code edits, AgInTiFlow routes patch/refactor/database-style tasks to DeepSee
|
|
|
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" />
|
|
@@ -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
|
@@ -37,6 +37,8 @@ const SLASH_COMMANDS = [
|
|
|
37
37
|
"/resume",
|
|
38
38
|
"/sessions",
|
|
39
39
|
"/profile",
|
|
40
|
+
"/web-search",
|
|
41
|
+
"/scouts",
|
|
40
42
|
"/routing",
|
|
41
43
|
"/provider",
|
|
42
44
|
"/model",
|
|
@@ -217,6 +219,8 @@ function printHelp() {
|
|
|
217
219
|
" /resume <session-id> Continue a saved session.",
|
|
218
220
|
" /sessions List recent sessions in this project.",
|
|
219
221
|
" /profile <name> Set task profile, e.g. code, website, latex, maintenance.",
|
|
222
|
+
" /web-search on|off Enable or disable the web_search tool.",
|
|
223
|
+
" /scouts on|off|<1-4> Enable parallel DeepSeek scouts and set scout count.",
|
|
220
224
|
" /routing <mode> Set routing: smart, fast, complex, manual.",
|
|
221
225
|
" /provider <name> Set provider: deepseek, openai, mock.",
|
|
222
226
|
" /model <name> Set an explicit model, or /model auto.",
|
|
@@ -350,7 +354,7 @@ function printStatus(state) {
|
|
|
350
354
|
printSystemLine(`provider=${state.provider || "auto"} routing=${state.routingMode} model=${state.model || "auto"}`);
|
|
351
355
|
printSystemLine(`profile=${state.taskProfile} maxSteps=${state.maxSteps}`);
|
|
352
356
|
printSystemLine(
|
|
353
|
-
`shell=${state.allowShellTool} files=${state.allowFileTools} auxiliary=${state.allowAuxiliaryTools} sandbox=${state.sandboxMode} installs=${state.packageInstallPolicy}`
|
|
357
|
+
`shell=${state.allowShellTool} files=${state.allowFileTools} webSearch=${state.allowWebSearch} scouts=${state.allowParallelScouts}:${state.parallelScoutCount} auxiliary=${state.allowAuxiliaryTools} sandbox=${state.sandboxMode} installs=${state.packageInstallPolicy}`
|
|
354
358
|
);
|
|
355
359
|
if (state.sandboxMode !== "host") {
|
|
356
360
|
printSystemLine(`dockerWorkspace=/workspace -> ${state.commandCwd || process.cwd()}`);
|
|
@@ -435,6 +439,9 @@ function createState(args = {}) {
|
|
|
435
439
|
allowShellTool: args.allowShellTool ?? true,
|
|
436
440
|
allowFileTools: args.allowFileTools ?? true,
|
|
437
441
|
allowAuxiliaryTools: args.allowAuxiliaryTools ?? true,
|
|
442
|
+
allowWebSearch: args.allowWebSearch ?? true,
|
|
443
|
+
allowParallelScouts: args.allowParallelScouts ?? true,
|
|
444
|
+
parallelScoutCount: args.parallelScoutCount || 3,
|
|
438
445
|
allowWrapperTools: args.allowWrapperTools ?? false,
|
|
439
446
|
allowDestructive: args.allowDestructive ?? false,
|
|
440
447
|
preferredWrapper: args.preferredWrapper || "codex",
|
|
@@ -597,6 +604,22 @@ async function handleCommand(line, state, packageDir) {
|
|
|
597
604
|
printSystemLine(`profile=${state.taskProfile}`);
|
|
598
605
|
return true;
|
|
599
606
|
}
|
|
607
|
+
if (command === "web-search") {
|
|
608
|
+
state.allowWebSearch = value !== "off";
|
|
609
|
+
printSystemLine(`webSearch=${state.allowWebSearch ? "on" : "off"}`);
|
|
610
|
+
return true;
|
|
611
|
+
}
|
|
612
|
+
if (command === "scouts") {
|
|
613
|
+
if (value === "off") {
|
|
614
|
+
state.allowParallelScouts = false;
|
|
615
|
+
} else {
|
|
616
|
+
state.allowParallelScouts = true;
|
|
617
|
+
const count = Number(value);
|
|
618
|
+
if (Number.isFinite(count) && count > 0) state.parallelScoutCount = Math.min(Math.max(count, 1), 4);
|
|
619
|
+
}
|
|
620
|
+
printSystemLine(`parallelScouts=${state.allowParallelScouts ? "on" : "off"} count=${state.parallelScoutCount}`);
|
|
621
|
+
return true;
|
|
622
|
+
}
|
|
600
623
|
if (command === "routing") {
|
|
601
624
|
state.routingMode = value || "smart";
|
|
602
625
|
printSystemLine(`routing=${state.routingMode}`);
|
|
@@ -692,6 +715,9 @@ async function runPrompt(prompt, state, packageDir) {
|
|
|
692
715
|
allowShellTool: state.allowShellTool,
|
|
693
716
|
allowFileTools: state.allowFileTools,
|
|
694
717
|
allowAuxiliaryTools: state.allowAuxiliaryTools,
|
|
718
|
+
allowWebSearch: state.allowWebSearch,
|
|
719
|
+
allowParallelScouts: state.allowParallelScouts,
|
|
720
|
+
parallelScoutCount: state.parallelScoutCount,
|
|
695
721
|
allowWrapperTools: state.allowWrapperTools,
|
|
696
722
|
allowDestructive: state.allowDestructive,
|
|
697
723
|
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,
|