@lazyingart/agintiflow 0.10.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 +5 -1
- package/docs/large-codebase-engineering.md +53 -0
- package/package.json +1 -1
- package/public/app.js +28 -6
- package/public/index.html +15 -0
- package/scripts/smoke-coding-tools.js +43 -0
- package/src/agent-runner.js +62 -0
- package/src/capabilities.js +5 -0
- package/src/cli.js +36 -3
- package/src/config.js +14 -2
- package/src/engineering-guidance.js +102 -0
- package/src/guardrails.js +12 -0
- package/src/interactive-cli.js +36 -2
- package/src/model-client.js +49 -0
- package/src/model-routing.js +23 -0
- package/src/parallel-scouts.js +126 -0
- package/src/task-profiles.js +6 -6
- package/src/web-db.js +13 -1
- package/src/web-search.js +111 -0
- package/web.js +12 -0
package/README.md
CHANGED
|
@@ -71,7 +71,11 @@ Inside chat, type normal requests such as `write a small Python CLI app with tes
|
|
|
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
|
-
For larger repositories, use `--profile large-codebase` or choose **Large codebase engineering** in the web UI.
|
|
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
|
+
|
|
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`.
|
|
75
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
|
|
|
@@ -10,6 +10,8 @@ Local agent references informed the design:
|
|
|
10
10
|
- Copilot-style SDK surfaces: structured tools, session persistence, plan/history/workspace APIs, and explicit permission hooks.
|
|
11
11
|
- Claude/Claw-style safety: project-local status, container-first execution, read-only operations by default, and clear failure recovery.
|
|
12
12
|
- Gemini/Qwen-style extensibility: capability discovery through profiles and tools rather than hardcoding one model behavior.
|
|
13
|
+
- Claw-style `doctor` discipline: check health and environment before treating system symptoms as code bugs.
|
|
14
|
+
- Claude/Codex-style context discipline: read instructions, manifests, entry points, and failing tests before touching broad files.
|
|
13
15
|
|
|
14
16
|
## Skill vs Tool
|
|
15
17
|
|
|
@@ -47,3 +49,54 @@ aginti --profile large-codebase "fix the failing tests"
|
|
|
47
49
|
or choose **Large codebase engineering** in the web task-profile dropdown.
|
|
48
50
|
|
|
49
51
|
Smart routing sends this profile to DeepSeek v4 pro even when the user prompt is short.
|
|
52
|
+
|
|
53
|
+
## Auto Profile Behavior
|
|
54
|
+
|
|
55
|
+
The web app still defaults to **Auto**. Auto does not mean weak. When the prompt mentions a large repo, system bug, failing tests, setup, install, migration, or a known language stack, AgInTiFlow adds engineering guidance and raises the step budget automatically.
|
|
56
|
+
|
|
57
|
+
Examples:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
aginti "debug this Python project system bug and fix failing tests"
|
|
61
|
+
aginti "fix the Rust workspace build"
|
|
62
|
+
aginti "repair the Docker setup and run the Node tests"
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
These route to DeepSeek v4 pro when the complexity score is high enough.
|
|
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
|
+
|
|
94
|
+
## Cross-Language Playbook
|
|
95
|
+
|
|
96
|
+
AgInTiFlow gives DeepSeek stack-specific reminders without hardcoding a solution:
|
|
97
|
+
|
|
98
|
+
- JS/TS: inspect package scripts and lockfiles, then run focused `node`, `tsc`, or test commands.
|
|
99
|
+
- Python: inspect `pyproject.toml` or requirements, prefer project-local venv/conda/Docker, then run focused pytest/module checks.
|
|
100
|
+
- Rust/Go/JVM/C/C++: inspect native manifests, format only touched files when possible, and start with narrow build/test targets.
|
|
101
|
+
- R/Stan/LaTeX: keep toolchains project-local or Docker-backed, compile from the right directory, and publish useful artifacts to canvas.
|
|
102
|
+
- System tasks: diagnose first, capture versions/logs, write reversible scripts, use Docker for installs, and avoid silent host-level changes.
|
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");
|
|
@@ -775,12 +781,25 @@ function renderTaskProfiles(selected = "auto") {
|
|
|
775
781
|
taskProfileField.value = profiles.some((profile) => profile.id === selected) ? selected : "auto";
|
|
776
782
|
}
|
|
777
783
|
|
|
778
|
-
|
|
784
|
+
const COMPLEX_ENGINEERING_HINT = /\b(large|complex|complicated|monorepo|codebase|repository|repo-wide|multi[- ]file|cross[- ]file|architecture|refactor|migration|regression|root cause|failing tests?|fix build|system bug|debug|performance|security)\b/i;
|
|
785
|
+
|
|
786
|
+
function recommendedMaxStepsForProfile(profile = "auto", goal = "") {
|
|
779
787
|
if (profile === "large-codebase") return 36;
|
|
780
788
|
if (profile === "latex") return 30;
|
|
789
|
+
if (COMPLEX_ENGINEERING_HINT.test(goal || "")) return 36;
|
|
790
|
+
if (/\b(latex|tex|pdflatex|latexmk|pdf|website|app|docker|system|install|setup|debug)\b/i.test(goal || "")) return 30;
|
|
781
791
|
return 24;
|
|
782
792
|
}
|
|
783
793
|
|
|
794
|
+
function ensureRecommendedMaxStepsForCurrentTask() {
|
|
795
|
+
const maxStepsField = document.querySelector("#maxSteps");
|
|
796
|
+
const goalField = document.querySelector("#goal");
|
|
797
|
+
const recommended = recommendedMaxStepsForProfile(taskProfileField?.value || "auto", goalField?.value || "");
|
|
798
|
+
if (maxStepsField && Number(maxStepsField.value || 0) < recommended) {
|
|
799
|
+
maxStepsField.value = String(recommended);
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
|
|
784
803
|
function renderWrapperStatus(wrappers = lastWrappers) {
|
|
785
804
|
lastWrappers = wrappers || [];
|
|
786
805
|
if (lastWrappers.length === 0) {
|
|
@@ -990,6 +1009,9 @@ function formPayload() {
|
|
|
990
1009
|
allowShellTool: document.querySelector("#allowShellTool").checked,
|
|
991
1010
|
allowFileTools: document.querySelector("#allowFileTools").checked,
|
|
992
1011
|
allowAuxiliaryTools: allowAuxiliaryToolsField?.checked ?? true,
|
|
1012
|
+
allowWebSearch: allowWebSearchField?.checked ?? true,
|
|
1013
|
+
allowParallelScouts: allowParallelScoutsField?.checked ?? true,
|
|
1014
|
+
parallelScoutCount: Number(parallelScoutCountField?.value) || 3,
|
|
993
1015
|
allowWrapperTools: allowWrapperToolsField.checked,
|
|
994
1016
|
preferredWrapper: preferredWrapperField.value,
|
|
995
1017
|
taskProfile: taskProfileField?.value || "auto",
|
|
@@ -1966,11 +1988,7 @@ packageInstallPolicyField.addEventListener("change", updatePackageWarning);
|
|
|
1966
1988
|
allowWrapperToolsField.addEventListener("change", () => renderWrapperStatus());
|
|
1967
1989
|
preferredWrapperField.addEventListener("change", () => renderWrapperStatus());
|
|
1968
1990
|
taskProfileField?.addEventListener("change", () => {
|
|
1969
|
-
|
|
1970
|
-
const recommended = recommendedMaxStepsForProfile(taskProfileField.value);
|
|
1971
|
-
if (maxStepsField && Number(maxStepsField.value || 0) < recommended) {
|
|
1972
|
-
maxStepsField.value = String(recommended);
|
|
1973
|
-
}
|
|
1991
|
+
ensureRecommendedMaxStepsForCurrentTask();
|
|
1974
1992
|
schedulePreferenceSave();
|
|
1975
1993
|
});
|
|
1976
1994
|
|
|
@@ -2048,6 +2066,7 @@ form.addEventListener("submit", async (event) => {
|
|
|
2048
2066
|
setLogs(t("goalRequired"), "empty");
|
|
2049
2067
|
return;
|
|
2050
2068
|
}
|
|
2069
|
+
ensureRecommendedMaxStepsForCurrentTask();
|
|
2051
2070
|
|
|
2052
2071
|
const payload = {
|
|
2053
2072
|
...formPayload(),
|
|
@@ -2186,6 +2205,9 @@ async function loadConfig() {
|
|
|
2186
2205
|
document.querySelector("#allowShellTool").checked = prefs.allowShellTool ?? true;
|
|
2187
2206
|
document.querySelector("#allowFileTools").checked = prefs.allowFileTools ?? true;
|
|
2188
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);
|
|
2189
2211
|
allowWrapperToolsField.checked = prefs.allowWrapperTools ?? false;
|
|
2190
2212
|
preferredWrapperField.value = prefs.preferredWrapper || "codex";
|
|
2191
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" />
|
|
@@ -5,8 +5,11 @@ import path from "node:path";
|
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { repairModelMessageHistory, runAgent } from "../src/agent-runner.js";
|
|
7
7
|
import { resolveRuntimeConfig } from "../src/config.js";
|
|
8
|
+
import { engineeringGuidanceForTask, recommendedMaxStepsForTask } from "../src/engineering-guidance.js";
|
|
8
9
|
import { selectModelRoute } from "../src/model-routing.js";
|
|
10
|
+
import { shouldRunParallelScouts } from "../src/parallel-scouts.js";
|
|
9
11
|
import { SessionStore } from "../src/session-store.js";
|
|
12
|
+
import { searchWeb } from "../src/web-search.js";
|
|
10
13
|
import { executeWorkspaceTool } from "../src/workspace-tools.js";
|
|
11
14
|
|
|
12
15
|
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
@@ -93,6 +96,42 @@ try {
|
|
|
93
96
|
taskProfile: "large-codebase",
|
|
94
97
|
});
|
|
95
98
|
assert(/pro/i.test(largeProfileRoute.model), "large-codebase profile did not route to DeepSeek pro");
|
|
99
|
+
const autoSystemRoute = selectModelRoute({
|
|
100
|
+
routingMode: "smart",
|
|
101
|
+
provider: "deepseek",
|
|
102
|
+
goal: "debug this Python project system bug and fix failing tests",
|
|
103
|
+
taskProfile: "auto",
|
|
104
|
+
});
|
|
105
|
+
assert(/pro/i.test(autoSystemRoute.model), "auto system/code problem did not route to DeepSeek pro");
|
|
106
|
+
assert(
|
|
107
|
+
recommendedMaxStepsForTask({
|
|
108
|
+
goal: "debug this Python project system bug and fix failing tests",
|
|
109
|
+
taskProfile: "auto",
|
|
110
|
+
complexityScore: autoSystemRoute.complexityScore,
|
|
111
|
+
}) >= 36,
|
|
112
|
+
"auto system/code problem did not get engineering step budget"
|
|
113
|
+
);
|
|
114
|
+
const guidance = engineeringGuidanceForTask("debug this Python project system bug and fix failing tests", "auto");
|
|
115
|
+
assert(guidance.includes("Python:"), "engineering guidance did not include Python stack advice");
|
|
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");
|
|
96
135
|
|
|
97
136
|
await fs.mkdir(path.join(workspace, "src"), { recursive: true });
|
|
98
137
|
await fs.mkdir(path.join(workspace, "test"), { recursive: true });
|
|
@@ -264,6 +303,10 @@ try {
|
|
|
264
303
|
"deepseek_history_repair",
|
|
265
304
|
"deepseek_pro_patch_route",
|
|
266
305
|
"large_profile_pro_route",
|
|
306
|
+
"auto_system_pro_route",
|
|
307
|
+
"auto_engineering_guidance",
|
|
308
|
+
"parallel_scout_trigger",
|
|
309
|
+
"web_search_dry_run",
|
|
267
310
|
"inspect_project",
|
|
268
311
|
"mock_inspect_project",
|
|
269
312
|
"write_file",
|
package/src/agent-runner.js
CHANGED
|
@@ -18,6 +18,9 @@ import { executeWorkspaceTool, resolveWorkspacePath, summarizeWorkspaceTools, WO
|
|
|
18
18
|
import { normalizeCanvasPayload } from "./artifact-tunnel.js";
|
|
19
19
|
import { getTaskProfile } from "./task-profiles.js";
|
|
20
20
|
import { generateImage, listAuxiliarySkills } from "./auxiliary-tools.js";
|
|
21
|
+
import { engineeringGuidanceForTask } from "./engineering-guidance.js";
|
|
22
|
+
import { searchWeb } from "./web-search.js";
|
|
23
|
+
import { runParallelScouts, shouldRunParallelScouts } from "./parallel-scouts.js";
|
|
21
24
|
|
|
22
25
|
const exec = promisify(execCallback);
|
|
23
26
|
const BROWSER_TOOLS = new Set(["open_url", "open_workspace_file", "preview_workspace", "click", "type", "scroll", "press", "back"]);
|
|
@@ -233,6 +236,7 @@ export function repairModelMessageHistory(state, config = {}) {
|
|
|
233
236
|
function createInitialState(config, sessionId) {
|
|
234
237
|
const now = new Date().toISOString();
|
|
235
238
|
const taskProfile = getTaskProfile(config.taskProfile);
|
|
239
|
+
const engineeringGuidance = engineeringGuidanceForTask(config.goal, config.taskProfile);
|
|
236
240
|
return {
|
|
237
241
|
sessionId,
|
|
238
242
|
createdAt: now,
|
|
@@ -282,7 +286,14 @@ function createInitialState(config, sessionId) {
|
|
|
282
286
|
.map((skill) => `${skill.id} via ${skill.toolName} (${skill.available ? "key available" : `needs ${skill.keyName}`})`)
|
|
283
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.`
|
|
284
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.",
|
|
285
295
|
`Task profile: ${taskProfile.label}. ${taskProfile.prompt}`,
|
|
296
|
+
engineeringGuidance,
|
|
286
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.",
|
|
287
298
|
"For visual-output requests such as draw, plot, graph, chart, diagram, figure, image, or visualization, proactively publish a canvas artifact even when the user does not mention canvas. If workspace file tools are enabled, prefer creating a small SVG or markdown artifact and call send_to_canvas with selected=true.",
|
|
288
299
|
"Work like a practical coding agent: orient with inspect_project/search/read, patch code with apply_patch, run safe checks when they add confidence, iterate on failures, and keep outputs inside the workspace.",
|
|
@@ -320,7 +331,10 @@ function createInitialState(config, sessionId) {
|
|
|
320
331
|
.map((skill) => `${skill.id}:${skill.available ? "available" : "missing-key"}`)
|
|
321
332
|
.join(" ")}`
|
|
322
333
|
: "",
|
|
334
|
+
config.allowWebSearch ? "Web search tool: enabled." : "Web search tool: disabled.",
|
|
335
|
+
config.allowParallelScouts ? `Parallel scouts: enabled count=${config.parallelScoutCount}.` : "Parallel scouts: disabled.",
|
|
323
336
|
`Task profile: ${taskProfile.label}. ${taskProfile.prompt}`,
|
|
337
|
+
engineeringGuidance,
|
|
324
338
|
"Canvas/artifacts tunnel: available through send_to_canvas for optional frontend rendering.",
|
|
325
339
|
"Visual-output requests should produce a canvas artifact without requiring the user to ask for canvas explicitly.",
|
|
326
340
|
"Use file, shell, browser, canvas, and wrapper tools when they are useful; choose the workflow from the user's request. For complicated engineering tasks, keep a tight loop: inspect, choose minimal files, patch, run focused checks, repair, then summarize.",
|
|
@@ -410,6 +424,7 @@ function applyContinuationPrompt(state, config, observers) {
|
|
|
410
424
|
if (!config.resume || !config.goal) return;
|
|
411
425
|
|
|
412
426
|
const taskProfile = getTaskProfile(config.taskProfile);
|
|
427
|
+
const engineeringGuidance = engineeringGuidanceForTask(config.goal, config.taskProfile);
|
|
413
428
|
ensureChatState(state);
|
|
414
429
|
state.goal = config.goal;
|
|
415
430
|
state.provider = config.provider;
|
|
@@ -436,6 +451,7 @@ function applyContinuationPrompt(state, config, observers) {
|
|
|
436
451
|
? `Agent wrappers: selected=${normalizeWrapperName(config.preferredWrapper)}; ${wrapperStatusText()}`
|
|
437
452
|
: "",
|
|
438
453
|
`Task profile: ${taskProfile.label}. ${taskProfile.prompt}`,
|
|
454
|
+
engineeringGuidance,
|
|
439
455
|
]
|
|
440
456
|
.filter(Boolean)
|
|
441
457
|
.join("\n"),
|
|
@@ -740,6 +756,13 @@ async function executeTool(browserState, toolCall, snapshot, config, store, obse
|
|
|
740
756
|
case "open_url":
|
|
741
757
|
await abortable(browserState.page.goto(String(args.url), { waitUntil: "domcontentloaded" }), config.abortSignal);
|
|
742
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
|
+
}
|
|
743
766
|
case "open_workspace_file": {
|
|
744
767
|
const target = resolveWorkspacePath(config, args.path || args.file || ".");
|
|
745
768
|
const stat = await fs.stat(target.absolutePath);
|
|
@@ -1105,6 +1128,40 @@ export async function runAgent(config) {
|
|
|
1105
1128
|
observers.event("plan.created", { plan });
|
|
1106
1129
|
}
|
|
1107
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
|
+
|
|
1108
1165
|
const repair = repairModelMessageHistory(state, config);
|
|
1109
1166
|
if (repair.changed) {
|
|
1110
1167
|
await store.appendEvent("history.repaired", repair);
|
|
@@ -1123,6 +1180,9 @@ export async function runAgent(config) {
|
|
|
1123
1180
|
allowShellTool: config.allowShellTool,
|
|
1124
1181
|
allowWrapperTools: config.allowWrapperTools,
|
|
1125
1182
|
preferredWrapper: normalizeWrapperName(config.preferredWrapper),
|
|
1183
|
+
allowWebSearch: config.allowWebSearch,
|
|
1184
|
+
allowParallelScouts: config.allowParallelScouts,
|
|
1185
|
+
parallelScoutCount: config.parallelScoutCount,
|
|
1126
1186
|
wrappers: config.allowWrapperTools ? wrapperStatusText() : "",
|
|
1127
1187
|
workspaceFileTools: summarizeWorkspaceTools(config),
|
|
1128
1188
|
shellSandbox: config.useDockerSandbox ? "docker" : "host",
|
|
@@ -1192,6 +1252,8 @@ export async function runAgent(config) {
|
|
|
1192
1252
|
agentWrappersAvailable: config.allowWrapperTools,
|
|
1193
1253
|
preferredWrapper: normalizeWrapperName(config.preferredWrapper),
|
|
1194
1254
|
agentWrappers: config.allowWrapperTools ? wrapperStatusText() : "",
|
|
1255
|
+
webSearchAvailable: config.allowWebSearch !== false,
|
|
1256
|
+
parallelScouts: state.meta.parallelScouts || null,
|
|
1195
1257
|
shellSandbox: config.useDockerSandbox ? "docker" : "host",
|
|
1196
1258
|
sandboxMode: config.sandboxMode,
|
|
1197
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
|
@@ -14,7 +14,8 @@ import {
|
|
|
14
14
|
setProviderKey,
|
|
15
15
|
showProjectSession,
|
|
16
16
|
} from "./project.js";
|
|
17
|
-
import {
|
|
17
|
+
import { listTaskProfiles } from "./task-profiles.js";
|
|
18
|
+
import { recommendedMaxStepsForTask } from "./engineering-guidance.js";
|
|
18
19
|
import { promptAndSaveDeepSeekKey, promptHidden, shouldPromptForDeepSeek } from "./auth-onboarding.js";
|
|
19
20
|
import fs from "node:fs/promises";
|
|
20
21
|
import path from "node:path";
|
|
@@ -45,6 +46,9 @@ export function parseArgs(argv) {
|
|
|
45
46
|
allowFileTools: undefined,
|
|
46
47
|
allowWrapperTools: undefined,
|
|
47
48
|
allowAuxiliaryTools: undefined,
|
|
49
|
+
allowWebSearch: undefined,
|
|
50
|
+
allowParallelScouts: undefined,
|
|
51
|
+
parallelScoutCount: undefined,
|
|
48
52
|
allowDestructive: undefined,
|
|
49
53
|
preferredWrapper: "",
|
|
50
54
|
taskProfile: "",
|
|
@@ -182,6 +186,27 @@ export function parseArgs(argv) {
|
|
|
182
186
|
result.allowAuxiliaryTools = false;
|
|
183
187
|
continue;
|
|
184
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
|
+
}
|
|
185
210
|
if (arg === "--allow-wrappers") {
|
|
186
211
|
result.allowWrapperTools = true;
|
|
187
212
|
continue;
|
|
@@ -236,7 +261,7 @@ export function parseArgs(argv) {
|
|
|
236
261
|
|
|
237
262
|
function printUsage() {
|
|
238
263
|
console.log(
|
|
239
|
-
'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"'
|
|
240
265
|
);
|
|
241
266
|
}
|
|
242
267
|
|
|
@@ -253,10 +278,18 @@ function agentDefaults(args) {
|
|
|
253
278
|
allowShellTool: args.allowShellTool ?? true,
|
|
254
279
|
allowFileTools: args.allowFileTools ?? true,
|
|
255
280
|
allowAuxiliaryTools: args.allowAuxiliaryTools ?? true,
|
|
281
|
+
allowWebSearch: args.allowWebSearch ?? true,
|
|
282
|
+
allowParallelScouts: args.allowParallelScouts ?? true,
|
|
283
|
+
parallelScoutCount: args.parallelScoutCount || 3,
|
|
256
284
|
sandboxMode: args.sandboxMode || "docker-workspace",
|
|
257
285
|
packageInstallPolicy: args.packageInstallPolicy || "allow",
|
|
258
286
|
useDockerSandbox: args.useDockerSandbox ?? true,
|
|
259
|
-
maxSteps:
|
|
287
|
+
maxSteps:
|
|
288
|
+
args.maxSteps ||
|
|
289
|
+
recommendedMaxStepsForTask({
|
|
290
|
+
goal: args.goal || "",
|
|
291
|
+
taskProfile: args.taskProfile || (args.latex ? "latex" : "auto"),
|
|
292
|
+
}),
|
|
260
293
|
};
|
|
261
294
|
|
|
262
295
|
if (defaults.sandboxMode === "host") {
|
package/src/config.js
CHANGED
|
@@ -4,7 +4,8 @@ import { getProviderDefaults, normalizeRoutingMode, selectModelRoute } from "./m
|
|
|
4
4
|
import { normalizePackageInstallPolicy, normalizeSandboxMode } from "./command-policy.js";
|
|
5
5
|
import { normalizeWrapperName } from "./tool-wrappers.js";
|
|
6
6
|
import { loadProjectEnv, resolveProjectRoot } from "./project.js";
|
|
7
|
-
import {
|
|
7
|
+
import { normalizeTaskProfile } from "./task-profiles.js";
|
|
8
|
+
import { recommendedMaxStepsForTask } from "./engineering-guidance.js";
|
|
8
9
|
|
|
9
10
|
function parseBoolean(value, fallback) {
|
|
10
11
|
if (value === undefined) return fallback;
|
|
@@ -43,6 +44,11 @@ export function resolveRuntimeConfig(args, overrides = {}) {
|
|
|
43
44
|
});
|
|
44
45
|
|
|
45
46
|
const defaults = getProviderDefaults(route.provider);
|
|
47
|
+
const defaultMaxSteps = recommendedMaxStepsForTask({
|
|
48
|
+
goal: args.goal || "",
|
|
49
|
+
taskProfile,
|
|
50
|
+
complexityScore: route.complexityScore,
|
|
51
|
+
});
|
|
46
52
|
const packageDir = path.resolve(overrides.packageDir || process.env.AGINTIFLOW_PACKAGE_DIR || baseDir);
|
|
47
53
|
const dockerRequested = parseBoolean(overrides.useDockerSandbox ?? args.useDockerSandbox ?? process.env.USE_DOCKER_SANDBOX, true);
|
|
48
54
|
const requestedSandboxMode =
|
|
@@ -67,7 +73,7 @@ export function resolveRuntimeConfig(args, overrides = {}) {
|
|
|
67
73
|
apiKey: overrides.apiKey || defaults.apiKey,
|
|
68
74
|
baseURL: overrides.baseURL || defaults.baseURL,
|
|
69
75
|
model: route.model || defaults.model,
|
|
70
|
-
maxSteps: parseNumber(overrides.maxSteps ?? args.maxSteps ?? process.env.MAX_STEPS,
|
|
76
|
+
maxSteps: parseNumber(overrides.maxSteps ?? args.maxSteps ?? process.env.MAX_STEPS, defaultMaxSteps),
|
|
71
77
|
headless: parseBoolean(overrides.headless ?? args.headless ?? process.env.HEADLESS, false),
|
|
72
78
|
allowedDomains: Array.isArray(overrides.allowedDomains)
|
|
73
79
|
? overrides.allowedDomains
|
|
@@ -84,6 +90,12 @@ export function resolveRuntimeConfig(args, overrides = {}) {
|
|
|
84
90
|
overrides.allowAuxiliaryTools ?? args.allowAuxiliaryTools ?? process.env.ALLOW_AUXILIARY_TOOLS,
|
|
85
91
|
true
|
|
86
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),
|
|
87
99
|
preferredWrapper: normalizeWrapperName(
|
|
88
100
|
overrides.preferredWrapper ?? args.preferredWrapper ?? process.env.PREFERRED_WRAPPER ?? process.env.AGENT_WRAPPER
|
|
89
101
|
),
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { defaultMaxStepsForProfile, normalizeTaskProfile } from "./task-profiles.js";
|
|
2
|
+
|
|
3
|
+
const LANGUAGE_HINTS = [
|
|
4
|
+
{
|
|
5
|
+
id: "javascript-typescript",
|
|
6
|
+
pattern: /\b(node|npm|pnpm|yarn|bun|javascript|typescript|react|vue|svelte|next\.?js|vite|express)\b/i,
|
|
7
|
+
text:
|
|
8
|
+
"JS/TS: inspect package.json and lockfiles, identify package manager, use npm/pnpm/yarn scripts before inventing commands, prefer targeted node --check/tsc/test runs before broad builds.",
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
id: "python",
|
|
12
|
+
pattern: /\b(python|pytest|pip|uv|poetry|conda|venv|jupyter|fastapi|django|flask|pandas|numpy)\b/i,
|
|
13
|
+
text:
|
|
14
|
+
"Python: inspect pyproject/requirements, prefer project-local venv/uv/conda or Docker, run python -m pytest or focused module checks, avoid global package installs on host.",
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
id: "rust",
|
|
18
|
+
pattern: /\b(rust|cargo|crate|clippy|rustfmt|tokio|actix)\b/i,
|
|
19
|
+
text:
|
|
20
|
+
"Rust: inspect Cargo.toml/workspace crates, run cargo fmt/check/test on the narrowest crate first, preserve Cargo.lock discipline, avoid broad workspace runs until focused checks pass.",
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
id: "go",
|
|
24
|
+
pattern: /\b(golang|go test|go mod|goroutine|gin|grpc)\b/i,
|
|
25
|
+
text:
|
|
26
|
+
"Go: inspect go.mod, use go test ./pkg-or-target first, run gofmt on touched files, avoid changing module paths unless required.",
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
id: "java-jvm",
|
|
30
|
+
pattern: /\b(java|kotlin|gradle|maven|spring|junit|jvm)\b/i,
|
|
31
|
+
text:
|
|
32
|
+
"JVM: inspect pom.xml/build.gradle/settings.gradle, use focused Maven/Gradle test targets, keep generated build outputs out of source patches.",
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
id: "c-cpp",
|
|
36
|
+
pattern: /\b(c\+\+|cpp|cmake|makefile|gcc|clang|native|segfault|asan|valgrind)\b/i,
|
|
37
|
+
text:
|
|
38
|
+
"C/C++: inspect CMake/Make/build scripts, prefer out-of-tree builds, run compile-only or narrow tests first, use sanitizers only when available and safe.",
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
id: "shell-system",
|
|
42
|
+
pattern: /\b(shell|bash|zsh|system|systemd|docker|linux|ubuntu|debian|apt|yum|dnf|brew|service|permission denied|port|network)\b/i,
|
|
43
|
+
text:
|
|
44
|
+
"System/shell: diagnose first with read-only commands, capture versions/logs, make reversible scripts, use Docker for installs/toolchains, and only use host-level changes when policy explicitly allows them.",
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
id: "r-stats",
|
|
48
|
+
pattern: /\b(rstats|r language|cmdstanr|stan|renv|tidyverse|shiny)\b/i,
|
|
49
|
+
text:
|
|
50
|
+
"R/Stan: inspect renv/DESCRIPTION and project notes, prefer project-local libraries or Docker, validate scripts with non-interactive Rscript commands when available.",
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
id: "latex",
|
|
54
|
+
pattern: /\b(latex|tex|pdflatex|latexmk|bibtex|biber|pdf)\b/i,
|
|
55
|
+
text:
|
|
56
|
+
"LaTeX: keep source/figures together, compile from the document directory, run enough passes for refs/bibliography, publish PDF/source artifacts to canvas.",
|
|
57
|
+
},
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
const COMPLEX_ENGINEERING_PATTERN =
|
|
61
|
+
/\b(large|complex|complicated|monorepo|codebase|repository|repo-wide|multi[- ]file|cross[- ]file|architecture|refactor|migration|regression|root cause|failing tests?|fix build|system bug|debug|performance|security)\b/i;
|
|
62
|
+
|
|
63
|
+
export function engineeringGuidanceForTask(goal = "", taskProfile = "auto") {
|
|
64
|
+
const normalizedProfile = normalizeTaskProfile(taskProfile);
|
|
65
|
+
const text = String(goal || "");
|
|
66
|
+
const matched = LANGUAGE_HINTS.filter((hint) => hint.pattern.test(text));
|
|
67
|
+
const wantsComplex =
|
|
68
|
+
normalizedProfile === "large-codebase" ||
|
|
69
|
+
normalizedProfile === "maintenance" ||
|
|
70
|
+
COMPLEX_ENGINEERING_PATTERN.test(text) ||
|
|
71
|
+
text.length > 500;
|
|
72
|
+
|
|
73
|
+
if (!wantsComplex && matched.length === 0) return "";
|
|
74
|
+
|
|
75
|
+
const lines = [
|
|
76
|
+
"Engineering operating mode:",
|
|
77
|
+
"Use the proven coding-agent loop: inspect_project, read instructions/manifests, search exact symbols/errors, patch small coherent batches, run focused checks, repair failures, then summarize changed files and residual risks.",
|
|
78
|
+
"Keep CLI and web behavior equivalent: use the same workspace, sessions, profiles, file tools, shell policy, Docker mounts, and canvas artifacts.",
|
|
79
|
+
"For large repositories, preserve context by reading fewer but more relevant files; prefer deterministic tools and diffs over long model memory.",
|
|
80
|
+
"For system repair, act like a doctor: gather evidence first, avoid silent destructive host changes, prefer Docker or project-local scripts for installs, and make every stronger action explicit in logs.",
|
|
81
|
+
];
|
|
82
|
+
|
|
83
|
+
if (matched.length > 0) {
|
|
84
|
+
lines.push("Stack-specific checks:");
|
|
85
|
+
for (const hint of matched.slice(0, 5)) lines.push(`- ${hint.text}`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return lines.join("\n");
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function recommendedMaxStepsForTask({ goal = "", taskProfile = "auto", complexityScore = 0 } = {}) {
|
|
92
|
+
const normalizedProfile = normalizeTaskProfile(taskProfile);
|
|
93
|
+
const profileDefault = defaultMaxStepsForProfile(normalizedProfile);
|
|
94
|
+
const text = String(goal || "");
|
|
95
|
+
if (normalizedProfile === "large-codebase" || complexityScore >= 3 || COMPLEX_ENGINEERING_PATTERN.test(text)) {
|
|
96
|
+
return Math.max(profileDefault, 36);
|
|
97
|
+
}
|
|
98
|
+
if (/\b(latex|tex|pdflatex|latexmk|pdf|website|app|docker|system|install|setup|debug)\b/i.test(text)) {
|
|
99
|
+
return Math.max(profileDefault, 30);
|
|
100
|
+
}
|
|
101
|
+
return profileDefault;
|
|
102
|
+
}
|
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
|
@@ -6,6 +6,7 @@ import { loadConfig } from "./config.js";
|
|
|
6
6
|
import { initProject, listProjectSessions, providerKeyStatus, setProviderKey } from "./project.js";
|
|
7
7
|
import { normalizePackageInstallPolicy, normalizeSandboxMode } from "./command-policy.js";
|
|
8
8
|
import { defaultMaxStepsForProfile, normalizeTaskProfile } from "./task-profiles.js";
|
|
9
|
+
import { recommendedMaxStepsForTask } from "./engineering-guidance.js";
|
|
9
10
|
import { promptAndSaveDeepSeekKey, promptHidden, shouldPromptForDeepSeek } from "./auth-onboarding.js";
|
|
10
11
|
|
|
11
12
|
const useColor = Boolean(input.isTTY && output.isTTY && process.env.AGINTIFLOW_NO_COLOR !== "1");
|
|
@@ -36,6 +37,8 @@ const SLASH_COMMANDS = [
|
|
|
36
37
|
"/resume",
|
|
37
38
|
"/sessions",
|
|
38
39
|
"/profile",
|
|
40
|
+
"/web-search",
|
|
41
|
+
"/scouts",
|
|
39
42
|
"/routing",
|
|
40
43
|
"/provider",
|
|
41
44
|
"/model",
|
|
@@ -216,6 +219,8 @@ function printHelp() {
|
|
|
216
219
|
" /resume <session-id> Continue a saved session.",
|
|
217
220
|
" /sessions List recent sessions in this project.",
|
|
218
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.",
|
|
219
224
|
" /routing <mode> Set routing: smart, fast, complex, manual.",
|
|
220
225
|
" /provider <name> Set provider: deepseek, openai, mock.",
|
|
221
226
|
" /model <name> Set an explicit model, or /model auto.",
|
|
@@ -349,7 +354,7 @@ function printStatus(state) {
|
|
|
349
354
|
printSystemLine(`provider=${state.provider || "auto"} routing=${state.routingMode} model=${state.model || "auto"}`);
|
|
350
355
|
printSystemLine(`profile=${state.taskProfile} maxSteps=${state.maxSteps}`);
|
|
351
356
|
printSystemLine(
|
|
352
|
-
`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}`
|
|
353
358
|
);
|
|
354
359
|
if (state.sandboxMode !== "host") {
|
|
355
360
|
printSystemLine(`dockerWorkspace=/workspace -> ${state.commandCwd || process.cwd()}`);
|
|
@@ -434,6 +439,9 @@ function createState(args = {}) {
|
|
|
434
439
|
allowShellTool: args.allowShellTool ?? true,
|
|
435
440
|
allowFileTools: args.allowFileTools ?? true,
|
|
436
441
|
allowAuxiliaryTools: args.allowAuxiliaryTools ?? true,
|
|
442
|
+
allowWebSearch: args.allowWebSearch ?? true,
|
|
443
|
+
allowParallelScouts: args.allowParallelScouts ?? true,
|
|
444
|
+
parallelScoutCount: args.parallelScoutCount || 3,
|
|
437
445
|
allowWrapperTools: args.allowWrapperTools ?? false,
|
|
438
446
|
allowDestructive: args.allowDestructive ?? false,
|
|
439
447
|
preferredWrapper: args.preferredWrapper || "codex",
|
|
@@ -596,6 +604,22 @@ async function handleCommand(line, state, packageDir) {
|
|
|
596
604
|
printSystemLine(`profile=${state.taskProfile}`);
|
|
597
605
|
return true;
|
|
598
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
|
+
}
|
|
599
623
|
if (command === "routing") {
|
|
600
624
|
state.routingMode = value || "smart";
|
|
601
625
|
printSystemLine(`routing=${state.routingMode}`);
|
|
@@ -673,6 +697,13 @@ async function handleCommand(line, state, packageDir) {
|
|
|
673
697
|
|
|
674
698
|
async function runPrompt(prompt, state, packageDir) {
|
|
675
699
|
const controller = new AbortController();
|
|
700
|
+
const runMaxSteps = Math.max(
|
|
701
|
+
state.maxSteps,
|
|
702
|
+
recommendedMaxStepsForTask({
|
|
703
|
+
goal: prompt,
|
|
704
|
+
taskProfile: state.taskProfile,
|
|
705
|
+
})
|
|
706
|
+
);
|
|
676
707
|
const config = loadConfig(
|
|
677
708
|
{
|
|
678
709
|
provider: state.provider,
|
|
@@ -684,11 +715,14 @@ async function runPrompt(prompt, state, packageDir) {
|
|
|
684
715
|
allowShellTool: state.allowShellTool,
|
|
685
716
|
allowFileTools: state.allowFileTools,
|
|
686
717
|
allowAuxiliaryTools: state.allowAuxiliaryTools,
|
|
718
|
+
allowWebSearch: state.allowWebSearch,
|
|
719
|
+
allowParallelScouts: state.allowParallelScouts,
|
|
720
|
+
parallelScoutCount: state.parallelScoutCount,
|
|
687
721
|
allowWrapperTools: state.allowWrapperTools,
|
|
688
722
|
allowDestructive: state.allowDestructive,
|
|
689
723
|
preferredWrapper: state.preferredWrapper,
|
|
690
724
|
taskProfile: state.taskProfile,
|
|
691
|
-
maxSteps:
|
|
725
|
+
maxSteps: runMaxSteps,
|
|
692
726
|
headless: state.headless,
|
|
693
727
|
resume: state.sessionId,
|
|
694
728
|
goal: prompt,
|
package/src/model-client.js
CHANGED
|
@@ -2,6 +2,7 @@ import OpenAI from "openai";
|
|
|
2
2
|
import { normalizeWrapperName, wrapperStatusText } from "./tool-wrappers.js";
|
|
3
3
|
import { getTaskProfile } from "./task-profiles.js";
|
|
4
4
|
import { listAuxiliarySkills } from "./auxiliary-tools.js";
|
|
5
|
+
import { engineeringGuidanceForTask } from "./engineering-guidance.js";
|
|
5
6
|
|
|
6
7
|
export function createClient(config) {
|
|
7
8
|
if (config.provider === "mock") {
|
|
@@ -121,6 +122,16 @@ function mockWorkspaceToolForGoal(goal = "") {
|
|
|
121
122
|
return null;
|
|
122
123
|
}
|
|
123
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
|
+
|
|
124
135
|
function mockPreviewToolForGoal(goal = "") {
|
|
125
136
|
const text = String(goal).toLowerCase();
|
|
126
137
|
if (!/(open|preview|view|browser|website|web\s*site)/.test(text)) return null;
|
|
@@ -173,6 +184,7 @@ function mockChatResponse(content, toolCalls = []) {
|
|
|
173
184
|
|
|
174
185
|
export async function createPlan(client, config, state) {
|
|
175
186
|
const taskProfile = getTaskProfile(config.taskProfile);
|
|
187
|
+
const engineeringGuidance = engineeringGuidanceForTask(state.goal, config.taskProfile);
|
|
176
188
|
if (client.mock) {
|
|
177
189
|
return [
|
|
178
190
|
"1. Inspect the request and prefer the local shell when available.",
|
|
@@ -211,7 +223,14 @@ export async function createPlan(client, config, state) {
|
|
|
211
223
|
.map((skill) => `${skill.id} via ${skill.toolName} (${skill.available ? "key available" : `needs ${skill.keyName}`})`)
|
|
212
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.`
|
|
213
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.",
|
|
214
232
|
`Task profile: ${taskProfile.label}. ${taskProfile.prompt}`,
|
|
233
|
+
engineeringGuidance,
|
|
215
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.",
|
|
216
235
|
"Work like a practical coding agent: inspect when useful, edit with file tools, run safe checks when they add confidence, and keep outputs inside the workspace.",
|
|
217
236
|
"For large apps, websites, LaTeX documents, Python/C/shell projects, or system tasks, plan a coherent minimal implementation, then use tools to create files, run checks, and publish artifacts.",
|
|
@@ -359,6 +378,26 @@ export async function requestNextStep(client, config, messages) {
|
|
|
359
378
|
},
|
|
360
379
|
];
|
|
361
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
|
+
|
|
362
401
|
if (config.allowFileTools) {
|
|
363
402
|
tools.splice(
|
|
364
403
|
0,
|
|
@@ -634,6 +673,9 @@ export async function requestNextStep(client, config, messages) {
|
|
|
634
673
|
Array.isArray(toolPayload.recommendedReads) && toolPayload.recommendedReads.length
|
|
635
674
|
? `Recommended reads: ${toolPayload.recommendedReads.join(", ")}`
|
|
636
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
|
+
: "",
|
|
637
679
|
toolPayload.path ? `Path: ${toolPayload.path}` : "",
|
|
638
680
|
Array.isArray(toolPayload.changes)
|
|
639
681
|
? toolPayload.changes
|
|
@@ -674,6 +716,13 @@ export async function requestNextStep(client, config, messages) {
|
|
|
674
716
|
}
|
|
675
717
|
}
|
|
676
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
|
+
|
|
677
726
|
const canvasTool = mockCanvasToolForGoal(config.goal);
|
|
678
727
|
if (canvasTool) {
|
|
679
728
|
return mockChatResponse("Mock mode will publish a canvas artifact for the UI tunnel.", [canvasTool]);
|
package/src/model-routing.js
CHANGED
|
@@ -29,12 +29,35 @@ const COMPLEXITY_KEYWORDS = [
|
|
|
29
29
|
"docker",
|
|
30
30
|
"ci",
|
|
31
31
|
"github",
|
|
32
|
+
"system",
|
|
33
|
+
"systemd",
|
|
34
|
+
"permission denied",
|
|
35
|
+
"install",
|
|
36
|
+
"setup",
|
|
37
|
+
"toolchain",
|
|
38
|
+
"conda",
|
|
39
|
+
"venv",
|
|
40
|
+
"kubernetes",
|
|
41
|
+
"nginx",
|
|
42
|
+
"postgres",
|
|
43
|
+
"redis",
|
|
44
|
+
"segfault",
|
|
45
|
+
"typescript",
|
|
46
|
+
"python",
|
|
47
|
+
"rust",
|
|
48
|
+
"cargo",
|
|
49
|
+
"golang",
|
|
50
|
+
"cmake",
|
|
51
|
+
"gradle",
|
|
52
|
+
"maven",
|
|
32
53
|
];
|
|
33
54
|
|
|
34
55
|
const COMPLEX_ROUTE_HINTS = [
|
|
35
56
|
/\b(large|big|complex|complicated)\s+(repo|repository|codebase|project|task)\b/i,
|
|
36
57
|
/\b(multi[- ]file|cross[- ]file|repo[- ]wide|workspace[- ]wide)\b/i,
|
|
37
58
|
/\b(root cause|regression|failing tests?|fix the build|make it pass)\b/i,
|
|
59
|
+
/\b(system bug|system problem|permission denied|service failed|daemon|systemd|toolchain|install|setup)\b/i,
|
|
60
|
+
/\b(conda|venv|python|node|typescript|rust|cargo|golang|java|gradle|maven|cmake|c\+\+)\b.*\b(project|app|tests?|build|compile|fix)\b/i,
|
|
38
61
|
/\blatex\b/i,
|
|
39
62
|
/\btexlive\b/i,
|
|
40
63
|
/\bpdflatex\b/i,
|
|
@@ -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/task-profiles.js
CHANGED
|
@@ -3,15 +3,15 @@ export const TASK_PROFILES = {
|
|
|
3
3
|
id: "auto",
|
|
4
4
|
label: "Auto",
|
|
5
5
|
prompt:
|
|
6
|
-
"Infer the task type from the user request.
|
|
7
|
-
tools: ["browser", "shell", "files", "canvas"],
|
|
6
|
+
"Infer the task type from the user request. For short tasks, use the smallest safe tool sequence that completes the work. For codebase, system, debugging, migration, or multi-language tasks, switch into the engineering loop: inspect, read/search exact context, patch incrementally, run focused checks, repair failures, and summarize changed files plus residual risks.",
|
|
7
|
+
tools: ["browser", "shell", "files", "canvas", "inspect_project"],
|
|
8
8
|
},
|
|
9
9
|
code: {
|
|
10
10
|
id: "code",
|
|
11
11
|
label: "Code writing",
|
|
12
12
|
prompt:
|
|
13
|
-
"Act like a coding agent:
|
|
14
|
-
tools: ["files", "shell", "sandbox"],
|
|
13
|
+
"Act like a coding agent across languages: inspect project manifests and conventions, edit workspace files with patches, run useful focused checks, iterate on failures, and report changed files and residual risks.",
|
|
14
|
+
tools: ["inspect_project", "files", "shell", "sandbox"],
|
|
15
15
|
},
|
|
16
16
|
"large-codebase": {
|
|
17
17
|
id: "large-codebase",
|
|
@@ -87,8 +87,8 @@ export const TASK_PROFILES = {
|
|
|
87
87
|
id: "maintenance",
|
|
88
88
|
label: "System maintenance",
|
|
89
89
|
prompt:
|
|
90
|
-
"For system maintenance, diagnose first, use Docker for broad installs when available,
|
|
91
|
-
tools: ["shell", "sandbox", "files"],
|
|
90
|
+
"For system maintenance and system bugs, diagnose first with read-only evidence, use Docker for broad installs/toolchains when available, generate reversible project-local scripts, follow the configured trust/package policy for host-level changes, and stop with clear next actions if stronger permission is needed.",
|
|
91
|
+
tools: ["shell", "sandbox", "files", "inspect_project"],
|
|
92
92
|
},
|
|
93
93
|
};
|
|
94
94
|
|
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,
|
|
@@ -95,6 +99,14 @@ export class WebDatabase {
|
|
|
95
99
|
if (!Number.isFinite(Number(parsed.maxSteps)) || Number(parsed.maxSteps) < 24) {
|
|
96
100
|
preferences.maxSteps = 24;
|
|
97
101
|
}
|
|
102
|
+
if ((parsed.preferencesSchemaVersion || 1) < 5) {
|
|
103
|
+
preferences.taskProfile = "auto";
|
|
104
|
+
}
|
|
105
|
+
if ((parsed.preferencesSchemaVersion || 1) < 6) {
|
|
106
|
+
preferences.allowWebSearch = true;
|
|
107
|
+
preferences.allowParallelScouts = true;
|
|
108
|
+
preferences.parallelScoutCount = 3;
|
|
109
|
+
}
|
|
98
110
|
this.savePreferences(preferences);
|
|
99
111
|
}
|
|
100
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,
|