@lazyingart/agintiflow 0.12.9 → 0.13.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 -2
- package/docs/large-codebase-engineering.md +8 -2
- package/docs/productive-agent-roadmap.md +35 -0
- package/package.json +1 -1
- package/public/app.js +1 -1
- package/public/index.html +1 -1
- package/scripts/smoke-cli-chat.js +4 -1
- package/scripts/smoke-coding-tools.js +73 -1
- package/src/agent-runner.js +11 -0
- package/src/capabilities.js +10 -1
- package/src/cli.js +1 -1
- package/src/codebase-map.js +151 -0
- package/src/config.js +1 -1
- package/src/interactive-cli.js +22 -12
- package/src/parallel-scouts.js +122 -9
- package/src/project.js +3 -0
- package/src/session-store.js +9 -0
package/README.md
CHANGED
|
@@ -67,7 +67,7 @@ aginti
|
|
|
67
67
|
aginti chat
|
|
68
68
|
```
|
|
69
69
|
|
|
70
|
-
Inside chat, type normal requests such as `write a small Python CLI app with tests`. The default is Docker workspace mode with approved package installs, so coding, plotting, and LaTeX tasks can set up project-local tools without touching the host. Use `/help` for commands, `/login` or `/auth` to paste a provider key, `/instructions` to inspect `AGINTI.md`, `/latex on` for PDF work, `/docker off` only when you intentionally want host mode, `/sessions` to list project runs, and `/resume latest` or `/resume <session-id>` to continue work. Type `/` then Tab for command completion. `Ctrl+J` inserts a new line in the colored input panel, Enter sends, arrow keys move through wrapped multiline input, and `Ctrl+A`/`Ctrl+E` jump to the current line start/end. During an active run, Enter sends the draft as an ASAP pipe message (`→`) and Tab queues it for after the run (`↳`); ASAP messages are consumed first, Alt+Up edits the last piped message, and Shift+Left edits the last after-finish queued message. The input panel always shows the current `cwd` footer and a single live status row, so long goals and tool updates are compacted instead of flooding the transcript. Assistant responses start on a fresh line after the `aginti>` header with a colored response gutter and render common Markdown, including headings, inline code, bold text, lists, quotes, code fences, tables, and red/green patch diff lines. Resuming a session prints
|
|
70
|
+
Inside chat, type normal requests such as `write a small Python CLI app with tests`. The default is Docker workspace mode with approved package installs, so coding, plotting, and LaTeX tasks can set up project-local tools without touching the host. Use `/help` for commands, `/login` or `/auth` to paste a provider key, `/instructions` to inspect `AGINTI.md`, `/latex on` for PDF work, `/docker off` only when you intentionally want host mode, `/sessions` to list project runs, and `/resume latest` or `/resume <session-id>` to continue work. Type `/` then Tab for command completion. `Ctrl+J` inserts a new line in the colored input panel, Enter sends, arrow keys move through wrapped multiline input, and `Ctrl+A`/`Ctrl+E` jump to the current line start/end. During an active run, Enter sends the draft as an ASAP pipe message (`→`) and Tab queues it for after the run (`↳`); ASAP messages are consumed first, Alt+Up edits the last piped message, and Shift+Left edits the last after-finish queued message. The input panel always shows the current `cwd` footer and a single live status row, so long goals and tool updates are compacted instead of flooding the transcript. Assistant responses start on a fresh line after the `aginti>` header with a colored response gutter and render common Markdown, including headings, inline code, bold text, lists, quotes, code fences, tables, and red/green patch diff lines. Resuming a session prints the full saved chat history with wrapped messages before the prompt. Esc or Ctrl+C stops the active run cleanly and prints the resume command.
|
|
71
71
|
|
|
72
72
|
`aginti init` creates `AGINTI.md` at the project root. This is the editable project-instruction file for both CLI and web runs, similar to `AGENTS.md` or project memory in other agents. Keep durable preferences, commands, and constraints there, but never secrets. You can edit it manually or ask in chat, for example: `update AGINTI.md to remember that this project uses pytest and npm run check`.
|
|
73
73
|
|
|
@@ -75,7 +75,9 @@ For code edits, AgInTiFlow routes patch/refactor/database-style tasks to DeepSee
|
|
|
75
75
|
|
|
76
76
|
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).
|
|
77
77
|
|
|
78
|
-
AgInTiFlow can also spend cheap DeepSeek calls on parallel scout notes before the main executor starts a complicated task.
|
|
78
|
+
AgInTiFlow can also spend cheap DeepSeek calls on parallel scout notes before the main executor starts a complicated task. It first writes a bounded project map to `.aginti/codebase-map.json`, then runs scouts for architecture, implementation, review, research, context mapping, tests, git workflow, integration, symbol tracing, and dependency risks. A coordinator Swarm Board is injected for the main agent and saved as `artifacts/scout-blackboard.json` in the session. The executor still does the real file/shell/browser work itself. Disable with `--no-parallel-scouts` or set `--scout-count 1..10`.
|
|
79
|
+
|
|
80
|
+
The next productive-agent roadmap is tracked in [docs/productive-agent-roadmap.md](docs/productive-agent-roadmap.md): durable codebase maps, stronger scout blackboards, long-run checkpoints, LSP/symbol tools, test triage, and release automation.
|
|
79
81
|
|
|
80
82
|
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`.
|
|
81
83
|
|
|
@@ -13,6 +13,8 @@ Local agent references informed the design:
|
|
|
13
13
|
- Claw-style `doctor` discipline: check health and environment before treating system symptoms as code bugs.
|
|
14
14
|
- Claude/Codex-style context discipline: read project instructions, manifests, entry points, and failing tests before touching broad files.
|
|
15
15
|
|
|
16
|
+
The shared pattern is not “put the whole repo in context.” It is: build a cheap map, choose the next exact evidence, edit with deterministic patches, verify, then compact what changed. AgInTiFlow uses cheap DeepSeek calls to add more independent eyes around that loop, not to replace it.
|
|
17
|
+
|
|
16
18
|
## Skill vs Tool
|
|
17
19
|
|
|
18
20
|
The `large-codebase` profile is a skill: it changes the model’s engineering behavior. It tells DeepSeek v4 pro to orient first, plan minimally, patch incrementally, and verify.
|
|
@@ -94,11 +96,15 @@ DeepSeek calls are cheap enough that complex tasks can use several short advisor
|
|
|
94
96
|
- Tester: finds the narrowest useful checks and setup blockers.
|
|
95
97
|
- Git operator: keeps status/diff/commit/pull/push workflows disciplined.
|
|
96
98
|
- Integrator: looks for cross-stream conflicts and ordering constraints.
|
|
99
|
+
- Symbol tracer: predicts names, APIs, routes, schemas, and searches that connect the change.
|
|
100
|
+
- Dependency doctor: checks package managers, Docker/toolchain setup, generated artifacts, and install risks.
|
|
101
|
+
|
|
102
|
+
Before scouts run, AgInTiFlow builds a bounded shared context pack from a durable project map at `.aginti/codebase-map.json`: manifests, top-level files, source/test directories, package scripts, language counts, git status hints, and recommended reads. This avoids the weak pattern of each scout rediscovering the repo differently or flooding the main context with an infinite tree.
|
|
97
103
|
|
|
98
|
-
Scout output is synthesized by a coordinator
|
|
104
|
+
Scout output is synthesized by a coordinator Swarm Board, persisted as `artifacts/scout-blackboard.json`, and injected as advisory context only. The board records shared context, execution order, conflicts/unknowns, must-read files/checks, and stop conditions. The main agent still owns execution and must use real tools to inspect, edit, run commands, and finish. CLI flags:
|
|
99
105
|
|
|
100
106
|
```bash
|
|
101
|
-
aginti --parallel-scouts --scout-count
|
|
107
|
+
aginti --parallel-scouts --scout-count 10 "fix this complicated repo bug"
|
|
102
108
|
aginti --no-parallel-scouts "run a cheap short task"
|
|
103
109
|
```
|
|
104
110
|
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Productive Agent Roadmap
|
|
2
|
+
|
|
3
|
+
AgInTiFlow should become a practical coding agent, not only a chat UI around a model. DeepSeek v4 pro is strong and cheap, so the advantage is to spend more inference on context, review, and verification while keeping the main executor focused.
|
|
4
|
+
|
|
5
|
+
## Lessons From Other Agents
|
|
6
|
+
|
|
7
|
+
- Codex: orient with file search, edit with deterministic patches, run focused checks, and keep git status visible.
|
|
8
|
+
- Claude Code: keep the terminal UX simple, project-local, resumable, and interruption-friendly.
|
|
9
|
+
- Gemini CLI: expose extensibility through tools, MCP-style capabilities, large-context workflows, and auth/setup discovery.
|
|
10
|
+
- Copilot SDK: make sessions, hooks, permissions, telemetry, and tool events first-class APIs.
|
|
11
|
+
- Claw Code: use doctor/preflight checks, container-first execution, parity tests, and explicit lifecycle state.
|
|
12
|
+
- LazyingArtBot: preserve workflow memory and artifacts so the agent can continue creative or production tasks later.
|
|
13
|
+
|
|
14
|
+
## Current Strengths
|
|
15
|
+
|
|
16
|
+
- Project-local CLI and web share sessions, preferences, artifacts, and `AGINTI.md`.
|
|
17
|
+
- DeepSeek flash/pro routing, Docker workspace mode, web search, image generation, canvas artifacts, and guarded file tools are already wired.
|
|
18
|
+
- `inspect_project`, `apply_patch`, command policy, git discipline, and the scout swarm give the model concrete tools instead of relying on memory.
|
|
19
|
+
|
|
20
|
+
## Missing Productive-Agent Pieces
|
|
21
|
+
|
|
22
|
+
1. Durable codebase map: cache `inspect_project`, symbol locations, test commands, package scripts, and recently changed files per project. Initial slice implemented as `.aginti/codebase-map.json` with manifests, source/test dirs, package scripts, languages, git hints, and recommended reads.
|
|
23
|
+
2. Scout blackboard: let scouts write short structured findings to a shared board, then run a coordinator pass that resolves conflicts before execution. Initial slice implemented as per-session `artifacts/scout-blackboard.json` with role lanes, findings, coordinator handoff, and codebase-map metadata.
|
|
24
|
+
3. Long-run checkpoints: save phase state after inspect, patch, test, repair, and commit so a long task can recover after interruption.
|
|
25
|
+
4. Symbol/LSP tools: add find-definition, references, diagnostics, and document-symbols for JS/TS, Python, Rust, Go, and C/C++ where available.
|
|
26
|
+
5. Test triage: parse common test failures into file, symbol, command, and likely cause so repair loops stay narrow.
|
|
27
|
+
6. Patch review loop: before finalizing large edits, run a cheap reviewer scout over diffs, risks, and missing tests.
|
|
28
|
+
7. Dependency doctor: detect toolchain gaps and propose Docker/project-local setup before touching host installs.
|
|
29
|
+
8. Release assistant: status, diff, changelog, version bump, pack, publish, push, and rollback notes as a reusable workflow.
|
|
30
|
+
|
|
31
|
+
## Swarm Design
|
|
32
|
+
|
|
33
|
+
Scouts must not become noisy subagents. Each scout gets the same bounded context pack generated from the durable codebase map and one role. The coordinator produces a Swarm Board with shared context, execution order, disagreements, must-read files, checks, and stop conditions. The main agent still owns tool use and must re-read exact files before editing.
|
|
34
|
+
|
|
35
|
+
Use 3 scouts for medium tasks, 5 for large tasks, and up to 10 for complex multi-language or system tasks. More scouts are only useful when their roles cover different failure modes.
|
package/package.json
CHANGED
package/public/app.js
CHANGED
|
@@ -1042,7 +1042,7 @@ function formPayload() {
|
|
|
1042
1042
|
allowAuxiliaryTools: allowAuxiliaryToolsField?.checked ?? true,
|
|
1043
1043
|
allowWebSearch: allowWebSearchField?.checked ?? true,
|
|
1044
1044
|
allowParallelScouts: allowParallelScoutsField?.checked ?? true,
|
|
1045
|
-
parallelScoutCount: Math.min(Math.max(Number(parallelScoutCountField?.value) || 3, 1),
|
|
1045
|
+
parallelScoutCount: Math.min(Math.max(Number(parallelScoutCountField?.value) || 3, 1), 10),
|
|
1046
1046
|
allowWrapperTools: allowWrapperToolsField.checked,
|
|
1047
1047
|
preferredWrapper: preferredWrapperField.value,
|
|
1048
1048
|
taskProfile: taskProfileField?.value || "auto",
|
package/public/index.html
CHANGED
|
@@ -205,7 +205,7 @@
|
|
|
205
205
|
|
|
206
206
|
<label>
|
|
207
207
|
<span data-i18n="parallelScoutCountLabel">Scout count</span>
|
|
208
|
-
<input id="parallelScoutCount" name="parallelScoutCount" type="number" min="1" max="
|
|
208
|
+
<input id="parallelScoutCount" name="parallelScoutCount" type="number" min="1" max="10" value="3" />
|
|
209
209
|
</label>
|
|
210
210
|
|
|
211
211
|
<div class="grid wrapper-controls">
|
|
@@ -150,13 +150,16 @@ try {
|
|
|
150
150
|
if (!latest.stdout.includes("resume history") || !latest.stdout.includes("Mock run complete")) {
|
|
151
151
|
throw new Error("bare aginti resume did not preview saved chat history");
|
|
152
152
|
}
|
|
153
|
+
if (latest.stdout.includes("showing=") || latest.stdout.includes("…")) {
|
|
154
|
+
throw new Error("resume history should render full saved messages instead of compact previews");
|
|
155
|
+
}
|
|
153
156
|
|
|
154
157
|
console.log(
|
|
155
158
|
JSON.stringify(
|
|
156
159
|
{
|
|
157
160
|
ok: true,
|
|
158
161
|
projectRoot: tempRoot,
|
|
159
|
-
checks: ["markdown-render", "markdown-table-no-duplicate", "patch-diff-render", "prompt-layout", "live-input-status-layout", "agent-response-gutter", "aginti-md", "instructions-command", "instructions-chat-edit", "interactive-chat", "mock-file-write", "run-status", "resume-latest", "resume-history-
|
|
162
|
+
checks: ["markdown-render", "markdown-table-no-duplicate", "patch-diff-render", "prompt-layout", "live-input-status-layout", "agent-response-gutter", "aginti-md", "instructions-command", "instructions-chat-edit", "interactive-chat", "mock-file-write", "run-status", "resume-latest", "resume-history-full"],
|
|
160
163
|
},
|
|
161
164
|
null,
|
|
162
165
|
2
|
|
@@ -5,9 +5,10 @@ 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 { readCodebaseMap } from "../src/codebase-map.js";
|
|
8
9
|
import { engineeringGuidanceForTask, recommendedMaxStepsForTask } from "../src/engineering-guidance.js";
|
|
9
10
|
import { selectModelRoute } from "../src/model-routing.js";
|
|
10
|
-
import { shouldRunParallelScouts } from "../src/parallel-scouts.js";
|
|
11
|
+
import { listParallelScouts, runParallelScouts, shouldRunParallelScouts } from "../src/parallel-scouts.js";
|
|
11
12
|
import { SessionStore } from "../src/session-store.js";
|
|
12
13
|
import { searchWeb } from "../src/web-search.js";
|
|
13
14
|
import { executeWorkspaceTool } from "../src/workspace-tools.js";
|
|
@@ -127,6 +128,16 @@ try {
|
|
|
127
128
|
),
|
|
128
129
|
"parallel scouts did not enable for complex auto task"
|
|
129
130
|
);
|
|
131
|
+
const scoutNames = listParallelScouts().map((scout) => scout.name);
|
|
132
|
+
assert(scoutNames.length >= 10, "parallel scout roster did not expose 10 scout roles");
|
|
133
|
+
for (const expectedScout of ["cartographer", "git-operator", "integrator", "symbol-tracer", "dependency-doctor"]) {
|
|
134
|
+
assert(scoutNames.includes(expectedScout), `parallel scout roster missing ${expectedScout}`);
|
|
135
|
+
}
|
|
136
|
+
const scoutConfig = resolveRuntimeConfig(
|
|
137
|
+
{ provider: "mock", parallelScoutCount: 99, commandCwd: workspace },
|
|
138
|
+
{ baseDir: runtimeDir, packageDir: repoRoot, provider: "mock", commandCwd: workspace }
|
|
139
|
+
);
|
|
140
|
+
assert(scoutConfig.parallelScoutCount === 10, "parallel scout count did not clamp to 10");
|
|
130
141
|
const drySearch = await searchWeb(
|
|
131
142
|
{ query: "AgInTiFlow web_search smoke", maxResults: 2 },
|
|
132
143
|
{ allowWebSearch: true, webSearchDryRun: true }
|
|
@@ -166,6 +177,62 @@ try {
|
|
|
166
177
|
assert(inspected.sourceDirs.some((item) => item.path === "src"), "inspect_project did not identify src directory");
|
|
167
178
|
assert(inspected.testFiles.some((item) => item.path === "test/index.test.js"), "inspect_project did not identify test file");
|
|
168
179
|
assert(inspected.recommendedReads.includes("package.json"), "inspect_project did not recommend package.json");
|
|
180
|
+
const fakeScoutPrompts = [];
|
|
181
|
+
const fakeScoutClient = {
|
|
182
|
+
chat: {
|
|
183
|
+
completions: {
|
|
184
|
+
create: async ({ messages }) => {
|
|
185
|
+
const systemContent = messages.find((message) => message.role === "system")?.content || "";
|
|
186
|
+
const userContent = messages.find((message) => message.role === "user")?.content || "";
|
|
187
|
+
fakeScoutPrompts.push(userContent);
|
|
188
|
+
const role = userContent.match(/Scout role: ([^\n]+)/)?.[1] || "coordinator";
|
|
189
|
+
return {
|
|
190
|
+
choices: [
|
|
191
|
+
{
|
|
192
|
+
message: {
|
|
193
|
+
content: systemContent.includes("synthesize")
|
|
194
|
+
? "Swarm Board: read package.json first, inspect src/test, patch narrowly, run npm test, stop on unrelated git changes."
|
|
195
|
+
: `Advice from ${role}: use the shared context pack and inspect package.json before editing.`,
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
],
|
|
199
|
+
};
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
};
|
|
204
|
+
const scoutRun = await runParallelScouts(
|
|
205
|
+
fakeScoutClient,
|
|
206
|
+
{
|
|
207
|
+
provider: "deepseek",
|
|
208
|
+
model: "deepseek-v4-flash",
|
|
209
|
+
commandCwd: workspace,
|
|
210
|
+
allowFileTools: true,
|
|
211
|
+
parallelScoutCount: 10,
|
|
212
|
+
goal: "fix this complicated repo test bug",
|
|
213
|
+
taskProfile: "large-codebase",
|
|
214
|
+
sandboxMode: "host",
|
|
215
|
+
packageInstallPolicy: "block",
|
|
216
|
+
},
|
|
217
|
+
{ goal: "fix this complicated repo test bug", meta: {}, plan: "Inspect and patch." }
|
|
218
|
+
);
|
|
219
|
+
assert(scoutRun.requested === 10 && scoutRun.completed === 10, "parallel scout fake run did not complete 10 scouts");
|
|
220
|
+
assert(scoutRun.contextPack.includes("package.json"), "parallel scout context pack did not include manifest evidence");
|
|
221
|
+
assert(scoutRun.summary.includes("## shared context pack"), "parallel scout summary omitted shared context pack");
|
|
222
|
+
assert(scoutRun.summary.includes("## coordinator"), "parallel scout summary omitted coordinator synthesis");
|
|
223
|
+
assert(scoutRun.codebaseMap?.fingerprint, "parallel scout run did not return durable codebase map metadata");
|
|
224
|
+
assert(scoutRun.blackboard?.lanes?.length === 10, "parallel scout blackboard did not include all scout lanes");
|
|
225
|
+
assert(scoutRun.blackboard?.coordinator.includes("Swarm Board"), "parallel scout blackboard did not retain coordinator synthesis");
|
|
226
|
+
assert(
|
|
227
|
+
fakeScoutPrompts.filter((prompt) => /Shared context pack:[\s\S]*package\.json/.test(prompt)).length >= 10,
|
|
228
|
+
"parallel scouts did not receive the shared context pack"
|
|
229
|
+
);
|
|
230
|
+
const codebaseMap = await readCodebaseMap(workspace);
|
|
231
|
+
assert(codebaseMap.ok && codebaseMap.map.fingerprint === scoutRun.codebaseMap.fingerprint, "durable codebase map was not persisted");
|
|
232
|
+
const blackboardStore = new SessionStore(runtimeDir, "blackboard-smoke");
|
|
233
|
+
const blackboardPath = await blackboardStore.saveJsonArtifact("scout-blackboard.json", scoutRun.blackboard);
|
|
234
|
+
const blackboardJson = JSON.parse(await fs.readFile(blackboardPath, "utf8"));
|
|
235
|
+
assert(blackboardJson.lanes.length === 10, "scout blackboard artifact did not persist lanes");
|
|
169
236
|
|
|
170
237
|
const inspectRun = await runMock("Inspect this large codebase and recommend next reads.", "coding-inspect");
|
|
171
238
|
assert(
|
|
@@ -306,8 +373,13 @@ try {
|
|
|
306
373
|
"auto_system_pro_route",
|
|
307
374
|
"auto_engineering_guidance",
|
|
308
375
|
"parallel_scout_trigger",
|
|
376
|
+
"parallel_scout_roster",
|
|
377
|
+
"parallel_scout_count_clamp",
|
|
309
378
|
"web_search_dry_run",
|
|
310
379
|
"inspect_project",
|
|
380
|
+
"parallel_scout_context_pack",
|
|
381
|
+
"durable_codebase_map",
|
|
382
|
+
"scout_blackboard",
|
|
311
383
|
"mock_inspect_project",
|
|
312
384
|
"write_file",
|
|
313
385
|
"duplicate_write_failed",
|
package/src/agent-runner.js
CHANGED
|
@@ -1170,11 +1170,17 @@ export async function runAgent(config) {
|
|
|
1170
1170
|
|
|
1171
1171
|
if (shouldRunParallelScouts(config, state)) {
|
|
1172
1172
|
const scouts = await runParallelScouts(client, config, state);
|
|
1173
|
+
const blackboardPath = scouts.blackboard
|
|
1174
|
+
? await store.saveJsonArtifact("scout-blackboard.json", scouts.blackboard).catch(() => "")
|
|
1175
|
+
: "";
|
|
1173
1176
|
state.meta.parallelScoutsCompleted = true;
|
|
1174
1177
|
state.meta.parallelScouts = {
|
|
1175
1178
|
model: scouts.model,
|
|
1176
1179
|
requested: scouts.requested,
|
|
1177
1180
|
completed: scouts.completed,
|
|
1181
|
+
codebaseMap: scouts.codebaseMap || null,
|
|
1182
|
+
blackboardPath,
|
|
1183
|
+
contextPack: scouts.contextPack ? scouts.contextPack.slice(0, 1200) : "",
|
|
1178
1184
|
synthesis: scouts.synthesis || "",
|
|
1179
1185
|
};
|
|
1180
1186
|
state.messages.push({
|
|
@@ -1185,7 +1191,11 @@ export async function runAgent(config) {
|
|
|
1185
1191
|
model: scouts.model,
|
|
1186
1192
|
requested: scouts.requested,
|
|
1187
1193
|
completed: scouts.completed,
|
|
1194
|
+
codebaseMap: scouts.codebaseMap || null,
|
|
1195
|
+
blackboardPath,
|
|
1196
|
+
contextPack: scouts.contextPack || "",
|
|
1188
1197
|
synthesis: scouts.synthesis || "",
|
|
1198
|
+
blackboard: scouts.blackboard || null,
|
|
1189
1199
|
scouts: scouts.scouts.map((scout) => ({
|
|
1190
1200
|
name: scout.name,
|
|
1191
1201
|
model: scout.model,
|
|
@@ -1198,6 +1208,7 @@ export async function runAgent(config) {
|
|
|
1198
1208
|
model: scouts.model,
|
|
1199
1209
|
requested: scouts.requested,
|
|
1200
1210
|
completed: scouts.completed,
|
|
1211
|
+
blackboardPath,
|
|
1201
1212
|
});
|
|
1202
1213
|
emitConsole(config, `Parallel scouts: ${scouts.completed}/${scouts.requested} completed using ${scouts.model}`, {
|
|
1203
1214
|
kind: "meta",
|
package/src/capabilities.js
CHANGED
|
@@ -8,6 +8,7 @@ import { listProjectSessions, projectPaths, providerKeyStatus, readProjectInstru
|
|
|
8
8
|
import { listTaskProfiles } from "./task-profiles.js";
|
|
9
9
|
import { listAgentWrappers } from "./tool-wrappers.js";
|
|
10
10
|
import { listAuxiliarySkills } from "./auxiliary-tools.js";
|
|
11
|
+
import { readCodebaseMap } from "./codebase-map.js";
|
|
11
12
|
|
|
12
13
|
const execFileAsync = promisify(execFile);
|
|
13
14
|
|
|
@@ -110,7 +111,7 @@ function trustedDockerPolicyChecks(config) {
|
|
|
110
111
|
export async function buildCapabilityReport(projectRoot, packageVersion, config) {
|
|
111
112
|
const paths = projectPaths(projectRoot);
|
|
112
113
|
const keyStatus = providerKeyStatus(projectRoot);
|
|
113
|
-
const [node, npm, python, conda, r, pdflatex, latexmk, dockerStatus, sessions, instructions] = await Promise.all([
|
|
114
|
+
const [node, npm, python, conda, r, pdflatex, latexmk, dockerStatus, sessions, instructions, codebaseMap] = await Promise.all([
|
|
114
115
|
commandAvailable("node", ["--version"]),
|
|
115
116
|
commandAvailable("npm", ["--version"]),
|
|
116
117
|
commandAvailable("python3", ["--version"]),
|
|
@@ -121,6 +122,7 @@ export async function buildCapabilityReport(projectRoot, packageVersion, config)
|
|
|
121
122
|
getDockerSandboxStatus(config).catch((error) => ({ ok: false, error: error.message })),
|
|
122
123
|
listProjectSessions(projectRoot, 12),
|
|
123
124
|
readProjectInstructions(projectRoot, { maxBytes: 1 }),
|
|
125
|
+
readCodebaseMap(projectRoot),
|
|
124
126
|
]);
|
|
125
127
|
|
|
126
128
|
const npmPrefixPolicy = evaluateCommandPolicy("npm --prefix round9-node-app test", config);
|
|
@@ -179,6 +181,13 @@ export async function buildCapabilityReport(projectRoot, packageVersion, config)
|
|
|
179
181
|
sessionsDir: paths.sessionsDir,
|
|
180
182
|
sessionDbPath: paths.sessionDbPath,
|
|
181
183
|
sharedSessionFolder: path.resolve(config.sessionsDir) === path.resolve(paths.sessionsDir),
|
|
184
|
+
codebaseMap: {
|
|
185
|
+
present: Boolean(codebaseMap.ok),
|
|
186
|
+
path: paths.codebaseMapPath,
|
|
187
|
+
generatedAt: codebaseMap.ok ? codebaseMap.map.generatedAt || "" : "",
|
|
188
|
+
fingerprint: codebaseMap.ok ? codebaseMap.map.fingerprint || "" : "",
|
|
189
|
+
summary: codebaseMap.ok ? codebaseMap.map.inspection?.summary || "" : "",
|
|
190
|
+
},
|
|
182
191
|
},
|
|
183
192
|
routing: {
|
|
184
193
|
active: {
|
package/src/cli.js
CHANGED
|
@@ -261,7 +261,7 @@ export function parseArgs(argv) {
|
|
|
261
261
|
|
|
262
262
|
function printUsage() {
|
|
263
263
|
console.log(
|
|
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 1..
|
|
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 1..10] [--allow-auxiliary-tools|--no-auxiliary-tools] [--allow-wrappers --wrapper codex] [--sandbox-status|--sandbox-preflight] "your task"'
|
|
265
265
|
);
|
|
266
266
|
}
|
|
267
267
|
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { projectPaths } from "./project.js";
|
|
5
|
+
import { redactSensitiveText } from "./redaction.js";
|
|
6
|
+
import { executeWorkspaceTool } from "./workspace-tools.js";
|
|
7
|
+
|
|
8
|
+
const MAP_VERSION = 1;
|
|
9
|
+
const DEFAULT_CONTEXT_PACK_CHARS = 3800;
|
|
10
|
+
|
|
11
|
+
function stableHash(value) {
|
|
12
|
+
return crypto.createHash("sha256").update(JSON.stringify(value)).digest("hex").slice(0, 16);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function truncateText(text, limit = DEFAULT_CONTEXT_PACK_CHARS) {
|
|
16
|
+
const value = String(text || "");
|
|
17
|
+
if (value.length <= limit) return value;
|
|
18
|
+
return `${value.slice(0, Math.max(limit - 80, 1))}\n... [truncated ${value.length - limit + 80} chars]`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function listValues(items, limit, mapper) {
|
|
22
|
+
const values = Array.isArray(items) ? items.slice(0, limit).map(mapper).filter(Boolean) : [];
|
|
23
|
+
if (!values.length) return "";
|
|
24
|
+
const suffix = Array.isArray(items) && items.length > limit ? `, ... +${items.length - limit}` : "";
|
|
25
|
+
return `${values.join(", ")}${suffix}`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function packageScriptValues(scripts) {
|
|
29
|
+
if (!Array.isArray(scripts) || !scripts.length) return "";
|
|
30
|
+
return listValues(scripts, 14, (item) => `${item.name}: ${item.command}`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function compactInspection(inspected) {
|
|
34
|
+
return {
|
|
35
|
+
summary: inspected.summary || "",
|
|
36
|
+
counts: inspected.counts || {},
|
|
37
|
+
truncated: Boolean(inspected.truncated),
|
|
38
|
+
topLevel: Array.isArray(inspected.topLevel) ? inspected.topLevel.slice(0, 80) : [],
|
|
39
|
+
manifestFiles: Array.isArray(inspected.manifestFiles) ? inspected.manifestFiles.slice(0, 80) : [],
|
|
40
|
+
sourceDirs: Array.isArray(inspected.sourceDirs) ? inspected.sourceDirs.slice(0, 60) : [],
|
|
41
|
+
testFiles: Array.isArray(inspected.testFiles) ? inspected.testFiles.slice(0, 80) : [],
|
|
42
|
+
packageManagers: Array.isArray(inspected.packageManagers) ? inspected.packageManagers.slice(0, 20) : [],
|
|
43
|
+
packageScripts: Array.isArray(inspected.packageScripts) ? inspected.packageScripts.slice(0, 30) : [],
|
|
44
|
+
languageCounts: Array.isArray(inspected.languageCounts) ? inspected.languageCounts.slice(0, 30) : [],
|
|
45
|
+
git: inspected.git || {},
|
|
46
|
+
recommendedReads: Array.isArray(inspected.recommendedReads) ? inspected.recommendedReads.slice(0, 40) : [],
|
|
47
|
+
engineeringHints: Array.isArray(inspected.engineeringHints) ? inspected.engineeringHints.slice(0, 20) : [],
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function codebaseMapToContextPack(map, { maxChars = DEFAULT_CONTEXT_PACK_CHARS } = {}) {
|
|
52
|
+
if (!map?.inspection) return "Context pack: no durable codebase map available.";
|
|
53
|
+
const inspected = map.inspection;
|
|
54
|
+
const sections = [
|
|
55
|
+
`Context pack for ${map.commandCwd || map.projectRoot}`,
|
|
56
|
+
`Map: version=${map.version} generatedAt=${map.generatedAt} fingerprint=${map.fingerprint}`,
|
|
57
|
+
`Summary: ${inspected.summary || "no summary"}`,
|
|
58
|
+
inspected.counts
|
|
59
|
+
? `Counts: files=${inspected.counts.files} dirs=${inspected.counts.directories} bytes=${inspected.counts.totalBytes}`
|
|
60
|
+
: "",
|
|
61
|
+
`Top level: ${listValues(inspected.topLevel, 20, (item) => `${item.type}:${item.path}`)}`,
|
|
62
|
+
`Manifests: ${listValues(inspected.manifestFiles, 18, (item) => item.path)}`,
|
|
63
|
+
`Source dirs: ${listValues(inspected.sourceDirs, 14, (item) => item.path)}`,
|
|
64
|
+
`Tests: ${listValues(inspected.testFiles, 18, (item) => item.path)}`,
|
|
65
|
+
`Package managers: ${listValues(inspected.packageManagers, 8, (item) => item.name || item.path || item)}`,
|
|
66
|
+
packageScriptValues(inspected.packageScripts) ? `Package scripts: ${packageScriptValues(inspected.packageScripts)}` : "",
|
|
67
|
+
`Languages: ${listValues(inspected.languageCounts, 10, (item) => `${item.name}:${item.count}`)}`,
|
|
68
|
+
inspected.git?.present
|
|
69
|
+
? `Git: present; start with ${inspected.git.recommendedCommands?.join(" && ") || "git status --short"}; ${inspected.git.workflow}`
|
|
70
|
+
: "Git: not detected at workspace root.",
|
|
71
|
+
`Recommended reads: ${listValues(inspected.recommendedReads, 16, (item) => item)}`,
|
|
72
|
+
`Engineering hints: ${listValues(inspected.engineeringHints, 8, (item) => item)}`,
|
|
73
|
+
].filter(Boolean);
|
|
74
|
+
return truncateText(redactSensitiveText(sections.join("\n")), maxChars);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export async function readCodebaseMap(projectRoot = process.cwd()) {
|
|
78
|
+
const paths = projectPaths(projectRoot);
|
|
79
|
+
try {
|
|
80
|
+
const raw = await fs.readFile(paths.codebaseMapPath, "utf8");
|
|
81
|
+
return {
|
|
82
|
+
ok: true,
|
|
83
|
+
path: paths.codebaseMapPath,
|
|
84
|
+
map: JSON.parse(raw),
|
|
85
|
+
};
|
|
86
|
+
} catch (error) {
|
|
87
|
+
return {
|
|
88
|
+
ok: false,
|
|
89
|
+
path: paths.codebaseMapPath,
|
|
90
|
+
reason: error?.code === "ENOENT" ? "missing" : error instanceof Error ? error.message : String(error),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export async function refreshCodebaseMap(config, options = {}) {
|
|
96
|
+
if (!config.allowFileTools) {
|
|
97
|
+
return { ok: false, reason: "workspace file tools disabled" };
|
|
98
|
+
}
|
|
99
|
+
const projectRoot = path.resolve(options.projectRoot || config.commandCwd || config.baseDir || process.cwd());
|
|
100
|
+
const paths = projectPaths(projectRoot);
|
|
101
|
+
const inspected = await executeWorkspaceTool(
|
|
102
|
+
"inspect_project",
|
|
103
|
+
{
|
|
104
|
+
path: options.path || ".",
|
|
105
|
+
maxDepth: options.maxDepth || 4,
|
|
106
|
+
limit: options.limit || 1000,
|
|
107
|
+
includeFiles: Boolean(options.includeFiles),
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
...config,
|
|
111
|
+
commandCwd: projectRoot,
|
|
112
|
+
allowFileTools: true,
|
|
113
|
+
}
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
if (!inspected?.ok) {
|
|
117
|
+
return {
|
|
118
|
+
ok: false,
|
|
119
|
+
path: paths.codebaseMapPath,
|
|
120
|
+
reason: inspected?.reason || inspected?.error || "inspect_project failed",
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const inspection = compactInspection(inspected);
|
|
125
|
+
const fingerprint = stableHash({
|
|
126
|
+
summary: inspection.summary,
|
|
127
|
+
counts: inspection.counts,
|
|
128
|
+
manifests: inspection.manifestFiles.map((item) => item.path),
|
|
129
|
+
sourceDirs: inspection.sourceDirs.map((item) => item.path),
|
|
130
|
+
tests: inspection.testFiles.map((item) => item.path),
|
|
131
|
+
packageScripts: inspection.packageScripts,
|
|
132
|
+
languages: inspection.languageCounts,
|
|
133
|
+
});
|
|
134
|
+
const map = {
|
|
135
|
+
version: MAP_VERSION,
|
|
136
|
+
generatedAt: new Date().toISOString(),
|
|
137
|
+
projectRoot: paths.root,
|
|
138
|
+
commandCwd: projectRoot,
|
|
139
|
+
fingerprint,
|
|
140
|
+
inspection,
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
await fs.mkdir(paths.controlDir, { recursive: true });
|
|
144
|
+
await fs.writeFile(paths.codebaseMapPath, `${JSON.stringify(map, null, 2)}\n`, "utf8");
|
|
145
|
+
return {
|
|
146
|
+
ok: true,
|
|
147
|
+
path: paths.codebaseMapPath,
|
|
148
|
+
map,
|
|
149
|
+
contextPack: codebaseMapToContextPack(map),
|
|
150
|
+
};
|
|
151
|
+
}
|
package/src/config.js
CHANGED
|
@@ -102,7 +102,7 @@ export function resolveRuntimeConfig(args, overrides = {}) {
|
|
|
102
102
|
parallelScoutCount: clampNumber(
|
|
103
103
|
parseNumber(overrides.parallelScoutCount ?? args.parallelScoutCount ?? process.env.AGINTI_SCOUT_COUNT, 3),
|
|
104
104
|
1,
|
|
105
|
-
|
|
105
|
+
10
|
|
106
106
|
),
|
|
107
107
|
preferredWrapper: normalizeWrapperName(
|
|
108
108
|
overrides.preferredWrapper ?? args.preferredWrapper ?? process.env.PREFERRED_WRAPPER ?? process.env.AGENT_WRAPPER
|
package/src/interactive-cli.js
CHANGED
|
@@ -351,6 +351,20 @@ function printPreviewBlock(role, text, { time = "", bg = ansi.systemBg, maxLines
|
|
|
351
351
|
}
|
|
352
352
|
}
|
|
353
353
|
|
|
354
|
+
function printHistoryBlock(role, text, { time = "", bg = ansi.systemBg } = {}) {
|
|
355
|
+
const header = [label(role, bg).trimEnd(), time ? color(time, ansi.dim) : ""].filter(Boolean).join(" ");
|
|
356
|
+
outputLine(header);
|
|
357
|
+
const width = Math.max(terminalWidth() - 8, 38);
|
|
358
|
+
const rendered = stripMarkdown(text)
|
|
359
|
+
.split(/\r?\n/)
|
|
360
|
+
.map((line) => line.trimEnd());
|
|
361
|
+
const lines = rendered.length ? rendered : ["(empty)"];
|
|
362
|
+
for (const line of lines) {
|
|
363
|
+
const wrapped = wrapTextLine(line || " ", width);
|
|
364
|
+
for (const wrappedLine of wrapped) outputLine(`${color(" | ", bg)} ${wrappedLine}`);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
354
368
|
function printSystemLine(text) {
|
|
355
369
|
if (!String(text || "").trim()) {
|
|
356
370
|
outputLine("");
|
|
@@ -468,7 +482,7 @@ function printHelp() {
|
|
|
468
482
|
" /sessions List recent sessions in this project.",
|
|
469
483
|
" /profile <name> Set task profile, e.g. code, website, latex, maintenance.",
|
|
470
484
|
" /web-search on|off Enable or disable the web_search tool.",
|
|
471
|
-
" /scouts on|off|<1-
|
|
485
|
+
" /scouts on|off|<1-10> Enable parallel DeepSeek scouts and set scout count.",
|
|
472
486
|
" /routing <mode> Set routing: smart, fast, complex, manual.",
|
|
473
487
|
" /provider <name> Set provider: deepseek, openai, mock.",
|
|
474
488
|
" /model <name> Set an explicit model, or /model auto.",
|
|
@@ -1298,20 +1312,14 @@ async function latestSession() {
|
|
|
1298
1312
|
return sessions[0] || null;
|
|
1299
1313
|
}
|
|
1300
1314
|
|
|
1301
|
-
function compactHistoryText(content = "", limit = 170) {
|
|
1302
|
-
const rendered = stripAnsi(stripMarkdown(String(content || "")));
|
|
1303
|
-
return compactLine(rendered.replace(/\s+/g, " "), limit);
|
|
1304
|
-
}
|
|
1305
|
-
|
|
1306
1315
|
function printHistoryEntry(entry) {
|
|
1307
1316
|
const role = entry.role === "assistant" ? "aginti" : entry.role === "user" ? "user" : String(entry.role || "note");
|
|
1308
1317
|
const bg = role === "aginti" ? ansi.agentBg : role === "user" ? ansi.userBg : ansi.systemBg;
|
|
1309
1318
|
const time = entry.at ? new Date(entry.at).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }) : "";
|
|
1310
|
-
|
|
1311
|
-
printPreviewBlock(role, compactHistoryText(entry.content, 520), { time, bg, maxLines });
|
|
1319
|
+
printHistoryBlock(role, entry.content, { time, bg });
|
|
1312
1320
|
}
|
|
1313
1321
|
|
|
1314
|
-
async function printResumeHistory(state, { limit =
|
|
1322
|
+
async function printResumeHistory(state, { limit = 0 } = {}) {
|
|
1315
1323
|
if (!state.sessionId) return;
|
|
1316
1324
|
const store = new SessionStore(projectPaths(process.cwd()).sessionsDir, state.sessionId);
|
|
1317
1325
|
const saved = await store.loadState().catch(() => null);
|
|
@@ -1321,8 +1329,10 @@ async function printResumeHistory(state, { limit = 8 } = {}) {
|
|
|
1321
1329
|
return;
|
|
1322
1330
|
}
|
|
1323
1331
|
|
|
1324
|
-
const shown = chat.slice(-limit);
|
|
1325
|
-
printSystemLine(
|
|
1332
|
+
const shown = limit > 0 ? chat.slice(-limit) : chat;
|
|
1333
|
+
printSystemLine(
|
|
1334
|
+
`resume history session=${state.sessionId} messages=${chat.length}${limit > 0 ? ` showing=${shown.length}/${chat.length}` : ""}`
|
|
1335
|
+
);
|
|
1326
1336
|
for (const entry of shown) printHistoryEntry(entry);
|
|
1327
1337
|
}
|
|
1328
1338
|
|
|
@@ -1596,7 +1606,7 @@ async function handleCommand(line, state, packageDir) {
|
|
|
1596
1606
|
} else {
|
|
1597
1607
|
state.allowParallelScouts = true;
|
|
1598
1608
|
const count = Number(value);
|
|
1599
|
-
if (Number.isFinite(count) && count > 0) state.parallelScoutCount = Math.min(Math.max(count, 1),
|
|
1609
|
+
if (Number.isFinite(count) && count > 0) state.parallelScoutCount = Math.min(Math.max(count, 1), 10);
|
|
1600
1610
|
}
|
|
1601
1611
|
printSystemLine(`parallelScouts=${state.allowParallelScouts ? "on" : "off"} count=${state.parallelScoutCount}`);
|
|
1602
1612
|
return true;
|
package/src/parallel-scouts.js
CHANGED
|
@@ -2,6 +2,10 @@ import { engineeringGuidanceForTask } from "./engineering-guidance.js";
|
|
|
2
2
|
import { getModelPresets } from "./model-routing.js";
|
|
3
3
|
import { getTaskProfile, normalizeTaskProfile } from "./task-profiles.js";
|
|
4
4
|
import { redactSensitiveText } from "./redaction.js";
|
|
5
|
+
import { refreshCodebaseMap } from "./codebase-map.js";
|
|
6
|
+
|
|
7
|
+
const MAX_CONTEXT_PACK_CHARS = 3800;
|
|
8
|
+
const MAX_SCOUT_CONTENT_CHARS = 1200;
|
|
5
9
|
|
|
6
10
|
const SCOUTS = [
|
|
7
11
|
{
|
|
@@ -44,8 +48,22 @@ const SCOUTS = [
|
|
|
44
48
|
prompt:
|
|
45
49
|
"Look across workstreams for conflicts, shared files, ordering constraints, and what each scout might be missing. Keep it execution-focused.",
|
|
46
50
|
},
|
|
51
|
+
{
|
|
52
|
+
name: "symbol-tracer",
|
|
53
|
+
prompt:
|
|
54
|
+
"Infer likely symbols, APIs, routes, commands, schemas, or config keys that connect the change. Suggest exact searches before editing.",
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: "dependency-doctor",
|
|
58
|
+
prompt:
|
|
59
|
+
"Identify dependency, environment, package-manager, Docker, and generated-artifact risks. Prefer project-local or sandboxed setup and focused verification.",
|
|
60
|
+
},
|
|
47
61
|
];
|
|
48
62
|
|
|
63
|
+
export function listParallelScouts() {
|
|
64
|
+
return SCOUTS.map((scout) => ({ name: scout.name, prompt: scout.prompt }));
|
|
65
|
+
}
|
|
66
|
+
|
|
49
67
|
function shouldUseComplexScouts(config, state) {
|
|
50
68
|
const profile = normalizeTaskProfile(config.taskProfile);
|
|
51
69
|
const goal = String(config.goal || state.goal || "");
|
|
@@ -70,20 +88,64 @@ function scoutModel(config) {
|
|
|
70
88
|
return config.model;
|
|
71
89
|
}
|
|
72
90
|
|
|
73
|
-
function
|
|
91
|
+
function truncateText(text, limit = MAX_CONTEXT_PACK_CHARS) {
|
|
92
|
+
const value = String(text || "");
|
|
93
|
+
if (value.length <= limit) return value;
|
|
94
|
+
return `${value.slice(0, limit - 80)}\n... [truncated ${value.length - limit + 80} chars]`;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function buildScoutContextPack(config) {
|
|
98
|
+
if (!config.allowFileTools) {
|
|
99
|
+
return {
|
|
100
|
+
text: "Context pack: workspace file tools are disabled. Scouts should recommend the minimal inspection commands/files instead of assuming repository structure.",
|
|
101
|
+
codebaseMap: null,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
const refreshed = await refreshCodebaseMap(config, { maxDepth: 4, limit: 1000, includeFiles: false });
|
|
107
|
+
if (!refreshed?.ok) {
|
|
108
|
+
return {
|
|
109
|
+
text: `Context pack: inspect_project unavailable (${refreshed?.reason || refreshed?.error || "unknown error"}).`,
|
|
110
|
+
codebaseMap: null,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
text: truncateText(redactSensitiveText(refreshed.contextPack || "")),
|
|
115
|
+
codebaseMap: {
|
|
116
|
+
path: refreshed.path,
|
|
117
|
+
generatedAt: refreshed.map.generatedAt,
|
|
118
|
+
fingerprint: refreshed.map.fingerprint,
|
|
119
|
+
summary: refreshed.map.inspection?.summary || "",
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
} catch (error) {
|
|
123
|
+
return {
|
|
124
|
+
text: `Context pack: inspect_project failed (${redactSensitiveText(error instanceof Error ? error.message : String(error))}).`,
|
|
125
|
+
codebaseMap: null,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function capScoutContent(content) {
|
|
131
|
+
return truncateText(redactSensitiveText(content || "").trim(), MAX_SCOUT_CONTENT_CHARS);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function scoutMessages(config, state, scout, contextPack) {
|
|
74
135
|
const profile = getTaskProfile(config.taskProfile);
|
|
75
136
|
const guidance = engineeringGuidanceForTask(config.goal || state.goal || "", config.taskProfile);
|
|
76
137
|
return [
|
|
77
138
|
{
|
|
78
139
|
role: "system",
|
|
79
140
|
content:
|
|
80
|
-
"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.",
|
|
141
|
+
"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. Work from the shared context pack and state dependencies on other scout roles when relevant.",
|
|
81
142
|
},
|
|
82
143
|
{
|
|
83
144
|
role: "user",
|
|
84
145
|
content: [
|
|
85
146
|
`Scout role: ${scout.name}`,
|
|
86
147
|
scout.prompt,
|
|
148
|
+
`Shared context pack:\n${contextPack}`,
|
|
87
149
|
`Goal: ${config.goal || state.goal || ""}`,
|
|
88
150
|
`Task profile: ${profile.label}. ${profile.prompt}`,
|
|
89
151
|
guidance,
|
|
@@ -100,7 +162,7 @@ function scoutMessages(config, state, scout) {
|
|
|
100
162
|
];
|
|
101
163
|
}
|
|
102
164
|
|
|
103
|
-
async function synthesizeScouts(client, config, model, scouts) {
|
|
165
|
+
async function synthesizeScouts(client, config, model, scouts, contextPack) {
|
|
104
166
|
const usable = scouts.filter((scout) => scout.content);
|
|
105
167
|
if (usable.length < 2) return "";
|
|
106
168
|
const response = await client.chat.completions.create(
|
|
@@ -111,11 +173,13 @@ async function synthesizeScouts(client, config, model, scouts) {
|
|
|
111
173
|
{
|
|
112
174
|
role: "system",
|
|
113
175
|
content:
|
|
114
|
-
"You synthesize parallel coding-agent scout notes
|
|
176
|
+
"You synthesize parallel coding-agent scout notes into a Swarm Board. Produce under 320 words with: shared context, execution order, conflicts/unknowns, must-read files/checks, and stop conditions. Prefer concrete paths/commands from the context pack. Do not claim work is done.",
|
|
115
177
|
},
|
|
116
178
|
{
|
|
117
179
|
role: "user",
|
|
118
|
-
content: usable.map((scout) => `## ${scout.name}\n${scout.content}`).join(
|
|
180
|
+
content: [`## shared context pack\n${contextPack}`, ...usable.map((scout) => `## ${scout.name}\n${scout.content}`)].join(
|
|
181
|
+
"\n\n"
|
|
182
|
+
),
|
|
119
183
|
},
|
|
120
184
|
],
|
|
121
185
|
},
|
|
@@ -124,24 +188,58 @@ async function synthesizeScouts(client, config, model, scouts) {
|
|
|
124
188
|
return redactSensitiveText(response.choices[0]?.message?.content || "").trim();
|
|
125
189
|
}
|
|
126
190
|
|
|
191
|
+
function extractFindings(content) {
|
|
192
|
+
return String(content || "")
|
|
193
|
+
.split(/\r?\n/)
|
|
194
|
+
.map((line) => line.trim().replace(/^[-*]\s+/, "").replace(/^\d+\.\s+/, ""))
|
|
195
|
+
.filter((line) => line && line.length > 8)
|
|
196
|
+
.slice(0, 6);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function buildScoutBlackboard({ config, state, model, requested, completed, scouts, contextPack, codebaseMap, synthesis }) {
|
|
200
|
+
return {
|
|
201
|
+
version: 1,
|
|
202
|
+
generatedAt: new Date().toISOString(),
|
|
203
|
+
goal: config.goal || state.goal || "",
|
|
204
|
+
model,
|
|
205
|
+
requested,
|
|
206
|
+
completed,
|
|
207
|
+
codebaseMap,
|
|
208
|
+
contextPack,
|
|
209
|
+
coordinator: synthesis || "",
|
|
210
|
+
lanes: scouts.map((scout) => ({
|
|
211
|
+
role: scout.name,
|
|
212
|
+
model: scout.model || model,
|
|
213
|
+
status: scout.content ? "completed" : "failed",
|
|
214
|
+
findings: scout.content ? extractFindings(scout.content) : [],
|
|
215
|
+
content: scout.content || "",
|
|
216
|
+
error: scout.error || "",
|
|
217
|
+
})),
|
|
218
|
+
handoff:
|
|
219
|
+
"Use this blackboard as advisory shared context. The executor must still inspect exact files, patch deterministically, run focused checks, and stop on conflicts or unrelated dirty work.",
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
127
223
|
export async function runParallelScouts(client, config, state) {
|
|
128
224
|
const count = Math.min(Math.max(Number(config.parallelScoutCount) || 3, 1), SCOUTS.length);
|
|
129
225
|
const selected = SCOUTS.slice(0, count);
|
|
130
226
|
const model = scoutModel(config);
|
|
227
|
+
const context = await buildScoutContextPack(config);
|
|
228
|
+
const contextPack = context.text;
|
|
131
229
|
const settled = await Promise.allSettled(
|
|
132
230
|
selected.map(async (scout) => {
|
|
133
231
|
const response = await client.chat.completions.create(
|
|
134
232
|
{
|
|
135
233
|
model,
|
|
136
234
|
temperature: 0,
|
|
137
|
-
messages: scoutMessages(config, state, scout),
|
|
235
|
+
messages: scoutMessages(config, state, scout, contextPack),
|
|
138
236
|
},
|
|
139
237
|
config.abortSignal ? { signal: config.abortSignal } : undefined
|
|
140
238
|
);
|
|
141
239
|
return {
|
|
142
240
|
name: scout.name,
|
|
143
241
|
model,
|
|
144
|
-
content:
|
|
242
|
+
content: capScoutContent(response.choices[0]?.message?.content || ""),
|
|
145
243
|
};
|
|
146
244
|
})
|
|
147
245
|
);
|
|
@@ -157,12 +255,13 @@ export async function runParallelScouts(client, config, state) {
|
|
|
157
255
|
const completed = scouts.filter((scout) => scout.content).length;
|
|
158
256
|
const synthesis =
|
|
159
257
|
completed > 1
|
|
160
|
-
? await synthesizeScouts(client, config, model, scouts).catch((error) =>
|
|
258
|
+
? await synthesizeScouts(client, config, model, scouts, contextPack).catch((error) =>
|
|
161
259
|
`Synthesis failed: ${redactSensitiveText(error instanceof Error ? error.message : String(error))}`
|
|
162
260
|
)
|
|
163
261
|
: "";
|
|
164
262
|
const summary = [
|
|
165
|
-
"Parallel scout notes. Treat these as advisory, not completed work.",
|
|
263
|
+
"Parallel scout swarm notes. Treat these as advisory shared context, not completed work.",
|
|
264
|
+
`\n## shared context pack\n${contextPack}`,
|
|
166
265
|
synthesis ? `\n## coordinator\n${synthesis}` : "",
|
|
167
266
|
...scouts.map((scout) =>
|
|
168
267
|
scout.content ? `\n## ${scout.name}\n${scout.content}` : `\n## ${scout.name}\nScout failed: ${scout.error || "unknown error"}`
|
|
@@ -170,6 +269,17 @@ export async function runParallelScouts(client, config, state) {
|
|
|
170
269
|
]
|
|
171
270
|
.filter(Boolean)
|
|
172
271
|
.join("\n");
|
|
272
|
+
const blackboard = buildScoutBlackboard({
|
|
273
|
+
config,
|
|
274
|
+
state,
|
|
275
|
+
model,
|
|
276
|
+
requested: selected.length,
|
|
277
|
+
completed,
|
|
278
|
+
scouts,
|
|
279
|
+
contextPack,
|
|
280
|
+
codebaseMap: context.codebaseMap,
|
|
281
|
+
synthesis,
|
|
282
|
+
});
|
|
173
283
|
|
|
174
284
|
return {
|
|
175
285
|
ok: completed > 0,
|
|
@@ -177,6 +287,9 @@ export async function runParallelScouts(client, config, state) {
|
|
|
177
287
|
requested: selected.length,
|
|
178
288
|
completed,
|
|
179
289
|
scouts,
|
|
290
|
+
contextPack,
|
|
291
|
+
codebaseMap: context.codebaseMap,
|
|
292
|
+
blackboard,
|
|
180
293
|
synthesis,
|
|
181
294
|
summary,
|
|
182
295
|
};
|
package/src/project.js
CHANGED
|
@@ -33,6 +33,7 @@ export function projectPaths(projectRoot = process.cwd()) {
|
|
|
33
33
|
rootEnvPath: path.join(root, ".env"),
|
|
34
34
|
envExamplePath: path.join(root, ".aginti", ".env.example"),
|
|
35
35
|
controlReadmePath: path.join(root, ".aginti", "README.md"),
|
|
36
|
+
codebaseMapPath: path.join(root, ".aginti", "codebase-map.json"),
|
|
36
37
|
notesDir: path.join(root, "notes"),
|
|
37
38
|
notesReadmePath: path.join(root, "notes", "README.md"),
|
|
38
39
|
sessionsDir: path.join(root, ".sessions"),
|
|
@@ -195,6 +196,7 @@ export async function initProject(projectRoot = process.cwd()) {
|
|
|
195
196
|
"- `../AGINTI.md` stores editable project instructions for CLI and web agents.",
|
|
196
197
|
"- `.env` is ignored and can hold local provider keys.",
|
|
197
198
|
"- `.env.example` documents accepted variable names.",
|
|
199
|
+
"- `codebase-map.json` is a generated, ignored project-intelligence cache.",
|
|
198
200
|
"- `.sessions/` at the project root stores CLI and web run history.",
|
|
199
201
|
"",
|
|
200
202
|
].join("\n")
|
|
@@ -227,6 +229,7 @@ export async function initProject(projectRoot = process.cwd()) {
|
|
|
227
229
|
".aginti/.env",
|
|
228
230
|
".aginti/.env.*",
|
|
229
231
|
"!.aginti/.env.example",
|
|
232
|
+
".aginti/codebase-map.json",
|
|
230
233
|
".sessions/",
|
|
231
234
|
]);
|
|
232
235
|
if (gitignore.changed) updated.push(paths.gitignorePath);
|
package/src/session-store.js
CHANGED
|
@@ -38,6 +38,15 @@ export class SessionStore {
|
|
|
38
38
|
await fs.writeFile(this.planPath, `${planText.trim()}\n`, "utf8");
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
async saveJsonArtifact(filename, data) {
|
|
42
|
+
await this.ensure();
|
|
43
|
+
const safeName = path.basename(String(filename || "artifact.json"));
|
|
44
|
+
const outputName = safeName.endsWith(".json") ? safeName : `${safeName}.json`;
|
|
45
|
+
const filePath = path.join(this.artifactsDir, outputName);
|
|
46
|
+
await fs.writeFile(filePath, `${JSON.stringify(data, null, 2)}\n`, "utf8");
|
|
47
|
+
return filePath;
|
|
48
|
+
}
|
|
49
|
+
|
|
41
50
|
async appendEvent(type, data = {}) {
|
|
42
51
|
await this.ensure();
|
|
43
52
|
const line = JSON.stringify({
|