pi-squad 0.17.2 → 0.19.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/CHANGELOG.md +89 -0
- package/package.json +3 -1
- package/src/commands.ts +585 -0
- package/src/index.ts +14 -2013
- package/src/inline-input.ts +139 -0
- package/src/lifecycle.ts +164 -0
- package/src/panel-runtime.ts +157 -0
- package/src/planner.ts +2 -3
- package/src/protocol.ts +3 -47
- package/src/report.ts +40 -2
- package/src/runtime.ts +196 -0
- package/src/scheduler-runtime.ts +117 -0
- package/src/scheduler.ts +149 -135
- package/src/skills/squad-plan/SKILL.md +103 -0
- package/src/skills/squad-plan/validate-spec.mjs +65 -0
- package/src/skills/squad-supervisor/SKILL.md +1 -0
- package/src/start-squad.ts +176 -0
- package/src/store.ts +0 -43
- package/src/tools-registration.ts +626 -0
- package/src/types.ts +0 -26
- package/src/supervisor.ts +0 -142
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: squad-plan
|
|
3
|
+
description: Author valid pi-squad plans — inline task arrays and strict v1 file specifications. Use when creating a squad plan, writing a squad spec JSON, choosing between inline tasks and specFile, computing specSha256, or debugging squad validation errors (must be array, SPEC_MALFORMED, SPEC_HASH_MISMATCH, Plan rejected).
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Squad Plan Authoring
|
|
7
|
+
|
|
8
|
+
Two ways to start a squad. Pick one:
|
|
9
|
+
|
|
10
|
+
| Form | When |
|
|
11
|
+
|---|---|
|
|
12
|
+
| **Inline** `squad({ goal, tasks?, agents?, config? })` | Normal plans. Goal + tasks fit comfortably in a tool call. |
|
|
13
|
+
| **File spec** `squad({ specFile, specSha256 })` | Large contracts, many tasks, exact-bytes requirements, or when your transport keeps mangling inline arguments. Children mechanically attest full spec reading. |
|
|
14
|
+
|
|
15
|
+
## Planner rules (both forms)
|
|
16
|
+
|
|
17
|
+
- 3–7 tasks is usually right (hard limit 128 in file specs; >9 warns).
|
|
18
|
+
- First task(s) have empty `depends`. No cycles; every dependency must name an existing task id.
|
|
19
|
+
- When tasks share an interface (API, schema, format), add a design/contract task first and make consumers depend on it.
|
|
20
|
+
- Add a final QA/verification task for user-facing changes.
|
|
21
|
+
- Task ids: short kebab-case (`setup-db`, `auth-middleware`). File specs enforce `^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$`.
|
|
22
|
+
- Structure descriptions as: Goal (outcome first), Context (files to read), Output (deliverable), Boundaries (what must not change), Verify (proving command).
|
|
23
|
+
- Every task's `agent` must name an existing agent definition (defaults: architect, backend, debugger, devops, docs, frontend, fullstack, qa, researcher, reviewer, security).
|
|
24
|
+
- **Model/thinking come from configuration.** Omit inline `agents` overrides and keep file-spec entries at `{ "model": null, "thinking": null }` unless the user explicitly asked for a specific model or thinking level. Configured values resolve as: squad-plan override (only if set) → agent definition (`~/.pi/squad/agents/*.json` or project `.pi/squad/agents/`) → `/squad defaults` policy in `~/.pi/squad/settings.json`.
|
|
25
|
+
|
|
26
|
+
## Inline form
|
|
27
|
+
|
|
28
|
+
```json
|
|
29
|
+
{
|
|
30
|
+
"goal": "Complete user outcome/acceptance contract, preserved for review",
|
|
31
|
+
"tasks": [
|
|
32
|
+
{ "id": "api-contract", "title": "Define API contract", "agent": "architect", "depends": [] },
|
|
33
|
+
{ "id": "build-api", "title": "Implement API", "agent": "backend", "depends": ["api-contract"],
|
|
34
|
+
"description": "Goal: ... Context: ... Output: ... Boundaries: ... Verify: ..." },
|
|
35
|
+
{ "id": "qa", "title": "Verify end to end", "agent": "qa", "depends": ["build-api"] }
|
|
36
|
+
],
|
|
37
|
+
"config": { "maxConcurrency": 2 }
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
- `tasks`, `agents`, and `config` must be real JSON structures. If your transport stringifies them, pi-squad also accepts JSON-encoded strings for these three fields and decodes them — but prefer real structures.
|
|
42
|
+
- Omitting `tasks` runs the planner agent on `goal`.
|
|
43
|
+
- `Plan rejected:` errors list exact structural problems (duplicate ids, unknown dependency, cycle, no entry task). Fix and resubmit; warnings (`⚠️`) don't block.
|
|
44
|
+
|
|
45
|
+
## File-spec form (strict v1)
|
|
46
|
+
|
|
47
|
+
Validation is strict and fail-closed: unknown keys, missing keys, duplicate JSON keys, a UTF-8 BOM, invalid UTF-8, or a wrong hash all reject before anything is scheduled. **Every field below is required — including empty ones.**
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
{
|
|
51
|
+
"schemaVersion": 1,
|
|
52
|
+
"goal": "Complete acceptance contract...",
|
|
53
|
+
"tasks": [
|
|
54
|
+
{
|
|
55
|
+
"id": "api-contract",
|
|
56
|
+
"title": "Define API contract",
|
|
57
|
+
"description": "Goal: ... Verify: ...",
|
|
58
|
+
"agent": "architect",
|
|
59
|
+
"depends": [],
|
|
60
|
+
"inheritContext": false,
|
|
61
|
+
"artifactRefs": []
|
|
62
|
+
}
|
|
63
|
+
],
|
|
64
|
+
"agents": {
|
|
65
|
+
"architect": { "model": null, "thinking": null }
|
|
66
|
+
},
|
|
67
|
+
"config": { "maxConcurrency": 2, "autoUnblock": true, "maxRetries": 2 },
|
|
68
|
+
"artifacts": []
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Rules that trip people up:
|
|
73
|
+
|
|
74
|
+
- Every agent used by a task must appear in `agents`, each entry exactly `{ "model": string|null, "thinking": null|"off"|"minimal"|"low"|"medium"|"high"|"xhigh"|"max" }`. Use `null`/`null` (configured defaults) unless the user explicitly requested a specific model or thinking level.
|
|
75
|
+
- `config` needs all three keys: `maxConcurrency` (1–64), `autoUnblock` (boolean), `maxRetries` (0–20).
|
|
76
|
+
- Each task needs all seven keys, even when `description` is `""` and `artifactRefs` is `[]`.
|
|
77
|
+
- No Base64 blobs or oversized embedded content (spec ≤ 1 MiB; embedded text budgets enforced). Reference big content via `artifacts`: absolute `path`, exact lowercase `sha256`, exact `bytes`, nonempty `purpose`.
|
|
78
|
+
- `specSha256` is the SHA-256 of the exact file bytes, 64 lowercase hex chars. Write the file first, hash second, never edit afterward.
|
|
79
|
+
|
|
80
|
+
## Workflow: write → validate → call
|
|
81
|
+
|
|
82
|
+
1. Write the spec to a file (UTF-8, no BOM). Generate it with a script (`JSON.stringify`) rather than by hand to avoid duplicate keys and escaping mistakes.
|
|
83
|
+
2. Validate with the bundled validator — it runs the exact same code the squad tool runs and prints the ready-to-use hash:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
node <skill-dir>/validate-spec.mjs /path/to/spec.v1.json
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
On success it prints `VALID`, the `specSha256`, and the exact `squad(...)` call. On failure it prints the same `SPEC_MALFORMED`/`SPEC_TOO_LARGE`/`ARTIFACT_*` error the tool would return — fix and re-run.
|
|
90
|
+
|
|
91
|
+
3. Call `squad({ specFile, specSha256 })` with the printed values. Do not restate the contract inline.
|
|
92
|
+
|
|
93
|
+
## Error → fix map
|
|
94
|
+
|
|
95
|
+
| Error | Fix |
|
|
96
|
+
|---|---|
|
|
97
|
+
| `tasks: must be array` (tool validation) | Transport stringified your array — send a real array, or pass the JSON string (accepted), or use a file spec. |
|
|
98
|
+
| `SPEC_HASH_MISMATCH` | File changed after hashing, or hash uppercase/wrong. Re-run the validator; use its printed hash. |
|
|
99
|
+
| `SPEC_MALFORMED: unknown/requires ...` | Add every required key exactly; remove extras. See the full example above. |
|
|
100
|
+
| `SPEC_MALFORMED: duplicate task id / invalid dependency / cycle` | Fix the task graph; dependencies reference ids in this spec only. |
|
|
101
|
+
| `SPEC_MALFORMED: task X uses undeclared agent Y` | Add Y to `agents` with `{ "model": null, "thinking": null }`. |
|
|
102
|
+
| `SPEC_TOO_LARGE` | Move large content to `artifacts` (path + sha256 + bytes) and reference via `artifactRefs`. |
|
|
103
|
+
| `Plan rejected:` (inline) | Structural plan errors listed verbatim — apply each fix. |
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* validate-spec.mjs — validate a pi-squad strict v1 file spec and print the
|
|
4
|
+
* exact specSha256 to pass to the squad tool.
|
|
5
|
+
*
|
|
6
|
+
* Usage: node validate-spec.mjs <spec.v1.json>
|
|
7
|
+
*
|
|
8
|
+
* This runs the SAME validator the squad tool uses (src/file-spec.ts), so a
|
|
9
|
+
* VALID result here is exactly what squad({ specFile, specSha256 }) accepts.
|
|
10
|
+
*/
|
|
11
|
+
import { registerHooks } from "node:module";
|
|
12
|
+
import { createHash } from "node:crypto";
|
|
13
|
+
import { readFileSync } from "node:fs";
|
|
14
|
+
import { resolve } from "node:path";
|
|
15
|
+
|
|
16
|
+
if (!process.features.typescript) {
|
|
17
|
+
console.error(
|
|
18
|
+
"This validator needs Node.js with type stripping: Node >= 23.6, or Node >= 22.6 run as\n" +
|
|
19
|
+
" node --experimental-strip-types validate-spec.mjs <spec.v1.json>",
|
|
20
|
+
);
|
|
21
|
+
process.exit(2);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// The extension sources use ".js" specifiers resolved by Pi; map them to the
|
|
25
|
+
// on-disk ".ts" files when loading the real validator outside Pi.
|
|
26
|
+
registerHooks({
|
|
27
|
+
resolve(specifier, context, nextResolve) {
|
|
28
|
+
if (specifier.startsWith(".") && specifier.endsWith(".js")) {
|
|
29
|
+
try {
|
|
30
|
+
return nextResolve(specifier, context);
|
|
31
|
+
} catch {
|
|
32
|
+
return nextResolve(specifier.replace(/\.js$/, ".ts"), context);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return nextResolve(specifier, context);
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const specArg = process.argv[2];
|
|
40
|
+
if (!specArg) {
|
|
41
|
+
console.error("Usage: node validate-spec.mjs <spec.v1.json>");
|
|
42
|
+
process.exit(2);
|
|
43
|
+
}
|
|
44
|
+
const specPath = resolve(process.cwd(), specArg);
|
|
45
|
+
|
|
46
|
+
const { prepareSpec } = await import(new URL("../../file-spec.ts", import.meta.url).href);
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
const raw = readFileSync(specPath);
|
|
50
|
+
const sha256 = createHash("sha256").update(raw).digest("hex");
|
|
51
|
+
// Validation with the self-computed hash: the hash gate passes trivially and
|
|
52
|
+
// every structural/strictness rule still runs.
|
|
53
|
+
const prepared = prepareSpec(specPath, sha256, process.cwd());
|
|
54
|
+
console.log("VALID");
|
|
55
|
+
console.log(`specFile: ${specPath}`);
|
|
56
|
+
console.log(`specSha256: ${prepared.sha256}`);
|
|
57
|
+
console.log(`bytes: ${prepared.raw.length}`);
|
|
58
|
+
console.log(`tasks: ${prepared.spec.tasks.length}`);
|
|
59
|
+
console.log(`agents: ${Object.keys(prepared.spec.agents).join(", ")}`);
|
|
60
|
+
console.log("");
|
|
61
|
+
console.log(`squad({ specFile: ${JSON.stringify(specPath)}, specSha256: ${JSON.stringify(prepared.sha256)} })`);
|
|
62
|
+
} catch (error) {
|
|
63
|
+
console.error(`INVALID: ${error.message}`);
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
@@ -29,6 +29,7 @@ Providing `tasks` yourself skips the planner agent — so you must apply its rul
|
|
|
29
29
|
- When tasks share an interface (API endpoints, schema, data formats), create a design/contract task FIRST and make consumers depend on it
|
|
30
30
|
- Include a final QA/verification task if there are user-facing changes
|
|
31
31
|
- Required work only — no optional polish
|
|
32
|
+
- Do NOT set agent `model`/`thinking` overrides unless the user explicitly asked for them — configured agent definitions and `/squad defaults` apply otherwise
|
|
32
33
|
|
|
33
34
|
Plans are validated on submission: structural errors (unknown deps, cycles, duplicate IDs, no entry task) are rejected; rule violations come back as ⚠️ warnings in the tool response. **Act on the warnings** — fix them with `squad_modify` `add_task`, or note them for review time. Don't silently ignore them.
|
|
34
35
|
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import { chunkRanges, type PreparedSpec } from "./file-spec.js";
|
|
4
|
+
import { logError } from "./logger.js";
|
|
5
|
+
import { validatePlan } from "./plan-rules.js";
|
|
6
|
+
import { runPlanner } from "./planner.js";
|
|
7
|
+
import { Scheduler } from "./scheduler.js";
|
|
8
|
+
import { wireSchedulerEvents } from "./scheduler-runtime.js";
|
|
9
|
+
import * as store from "./store.js";
|
|
10
|
+
import type { PlannerOutput, Squad, SquadAgentEntry, SquadConfig, Task } from "./types.js";
|
|
11
|
+
import { DEFAULT_SQUAD_CONFIG } from "./types.js";
|
|
12
|
+
import { disabledToolResult, focusSquad, resolveSquadDefaults, runtime, schedulerSpawnContext, type InlineSquadStart } from "./runtime.js";
|
|
13
|
+
|
|
14
|
+
// ============================================================================
|
|
15
|
+
// Start Squad
|
|
16
|
+
// ============================================================================
|
|
17
|
+
|
|
18
|
+
export async function startSquad(
|
|
19
|
+
squadId: string,
|
|
20
|
+
params: InlineSquadStart,
|
|
21
|
+
cwd: string,
|
|
22
|
+
skillPaths: string[],
|
|
23
|
+
pi: ExtensionAPI,
|
|
24
|
+
sessionFile: string | null = null,
|
|
25
|
+
preparedSpec?: PreparedSpec,
|
|
26
|
+
) {
|
|
27
|
+
let plan: PlannerOutput;
|
|
28
|
+
|
|
29
|
+
if (params.tasks && params.tasks.length > 0) {
|
|
30
|
+
// User provided a plan — use it directly
|
|
31
|
+
plan = {
|
|
32
|
+
agents: params.agents || {},
|
|
33
|
+
tasks: params.tasks.map((t) => ({
|
|
34
|
+
...t,
|
|
35
|
+
description: t.description || "",
|
|
36
|
+
depends: t.depends || [],
|
|
37
|
+
})),
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// Validate agent names — remap unknown agents to fullstack
|
|
41
|
+
for (const task of plan.tasks) {
|
|
42
|
+
const agentDef = store.loadAgentDef(task.agent, cwd);
|
|
43
|
+
if (!agentDef || agentDef.disabled) {
|
|
44
|
+
if (preparedSpec) throw new Error(`SPEC_MALFORMED: assigned agent '${task.agent}' is missing or disabled`);
|
|
45
|
+
const original = task.agent;
|
|
46
|
+
task.agent = "fullstack";
|
|
47
|
+
task.description = `[Note: agent "${original}" not found, remapped to fullstack]\n\n${task.description}`;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
} else {
|
|
51
|
+
// Run planner to generate task breakdown (squad default policy as fallback)
|
|
52
|
+
try {
|
|
53
|
+
const defaults = resolveSquadDefaults();
|
|
54
|
+
plan = await runPlanner({ goal: params.goal, cwd, fallbackModel: defaults.model, fallbackThinking: defaults.thinking });
|
|
55
|
+
} catch (error) {
|
|
56
|
+
// Throwing marks the tool result as an error for the LLM (returning isError is ignored in current pi)
|
|
57
|
+
throw new Error(`Failed to plan: ${(error as Error).message}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// A planner may take long enough for /squad disable to run concurrently.
|
|
62
|
+
// Re-check before publishing any squad or constructing a scheduler.
|
|
63
|
+
if (!runtime.squadEnabled) return disabledToolResult();
|
|
64
|
+
|
|
65
|
+
// Merge agent roster
|
|
66
|
+
const agents: Record<string, SquadAgentEntry> = { ...plan.agents };
|
|
67
|
+
if (params.agents) {
|
|
68
|
+
for (const [name, entry] of Object.entries(params.agents)) {
|
|
69
|
+
agents[name] = { ...agents[name], ...entry };
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Validate the plan — same enforcement for main-session and planner plans.
|
|
74
|
+
// Errors block squad creation; warnings are reported back to the plan author.
|
|
75
|
+
const validation = validatePlan(plan.tasks);
|
|
76
|
+
if (validation.errors.length > 0) {
|
|
77
|
+
throw new Error(
|
|
78
|
+
`Plan rejected:\n- ${validation.errors.join("\n- ")}\n\nFix the task list and call squad again.`,
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Create squad
|
|
83
|
+
const config: SquadConfig = {
|
|
84
|
+
...DEFAULT_SQUAD_CONFIG,
|
|
85
|
+
...(params.config?.maxConcurrency ? { maxConcurrency: params.config.maxConcurrency } : {}),
|
|
86
|
+
...(typeof params.config?.autoUnblock === "boolean" ? { autoUnblock: params.config.autoUnblock } : {}),
|
|
87
|
+
...(typeof params.config?.maxRetries === "number" ? { maxRetries: params.config.maxRetries } : {}),
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const squad: Squad = {
|
|
91
|
+
id: squadId,
|
|
92
|
+
goal: params.goal,
|
|
93
|
+
status: "running",
|
|
94
|
+
created: store.now(),
|
|
95
|
+
cwd,
|
|
96
|
+
sessionFile,
|
|
97
|
+
agents,
|
|
98
|
+
config,
|
|
99
|
+
...(preparedSpec ? { spec: {
|
|
100
|
+
schemaVersion: 1 as const,
|
|
101
|
+
sha256: preparedSpec.sha256,
|
|
102
|
+
bytes: preparedSpec.raw.length,
|
|
103
|
+
path: path.join(store.getSquadDir(squadId), "spec", "spec.v1.json"),
|
|
104
|
+
chunkBytes: 32768 as const,
|
|
105
|
+
chunkCount: chunkRanges(preparedSpec.raw).length,
|
|
106
|
+
} } : {}),
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
// Materialize task state in memory so file squads can publish spec+squad+tasks atomically.
|
|
110
|
+
const initialTasks: Task[] = plan.tasks.map((taskDef) => {
|
|
111
|
+
const task: Task = {
|
|
112
|
+
id: taskDef.id,
|
|
113
|
+
title: taskDef.title,
|
|
114
|
+
description: taskDef.description,
|
|
115
|
+
agent: taskDef.agent,
|
|
116
|
+
status: taskDef.depends.length === 0 ? "pending" : "blocked",
|
|
117
|
+
depends: taskDef.depends,
|
|
118
|
+
...(taskDef.inheritContext ? { inheritContext: true } : {}),
|
|
119
|
+
created: store.now(),
|
|
120
|
+
started: null,
|
|
121
|
+
completed: null,
|
|
122
|
+
output: null,
|
|
123
|
+
error: null,
|
|
124
|
+
usage: { inputTokens: 0, outputTokens: 0, cost: 0, turns: 0 },
|
|
125
|
+
};
|
|
126
|
+
return task;
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
if (preparedSpec) store.publishFileSquad(squad, initialTasks, preparedSpec.raw);
|
|
130
|
+
else {
|
|
131
|
+
store.saveSquad(squad);
|
|
132
|
+
for (const task of initialTasks) store.createTask(squadId, task);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Start scheduler
|
|
136
|
+
const scheduler = new Scheduler(squadId, skillPaths, schedulerSpawnContext);
|
|
137
|
+
runtime.schedulers.set(squadId, scheduler);
|
|
138
|
+
// Activate panel/widget/tool focus as one invariant.
|
|
139
|
+
focusSquad(squadId);
|
|
140
|
+
|
|
141
|
+
// Wire up completion/escalation notifications to main agent.
|
|
142
|
+
wireSchedulerEvents(pi, scheduler, squadId);
|
|
143
|
+
|
|
144
|
+
// Start scheduling — fire and forget, don't block the tool call.
|
|
145
|
+
// scheduler.start() spawns agents which can take seconds per agent.
|
|
146
|
+
// We must return immediately so the main agent's turn completes
|
|
147
|
+
// and the user regains interactive control.
|
|
148
|
+
scheduler.start().catch((err) => {
|
|
149
|
+
logError("squad", `Scheduler start error: ${(err as Error).message}`);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// Build response. File mode returns only the bounded descriptor; the canonical
|
|
153
|
+
// contract is never reflected back into the main model's transport.
|
|
154
|
+
const taskSummary = preparedSpec
|
|
155
|
+
? `Canonical spec: ${squad.spec!.path}\nSHA-256: ${squad.spec!.sha256}\nBytes: ${squad.spec!.bytes}\nTasks: ${plan.tasks.length}`
|
|
156
|
+
: plan.tasks
|
|
157
|
+
.map((t) => {
|
|
158
|
+
const deps = t.depends.length > 0 ? ` (depends: ${t.depends.join(", ")})` : "";
|
|
159
|
+
return `${t.id} → ${t.agent}: ${t.title}${deps}`;
|
|
160
|
+
})
|
|
161
|
+
.join("\n");
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
content: [
|
|
165
|
+
{
|
|
166
|
+
type: "text" as const,
|
|
167
|
+
text: `Squad "${squadId}" started with ${plan.tasks.length} tasks.\n\n${taskSummary}${
|
|
168
|
+
validation.warnings.length > 0
|
|
169
|
+
? `\n\n⚠️ Plan warnings (fix with squad_modify, or address at review):\n- ${validation.warnings.join("\n- ")}`
|
|
170
|
+
: ""
|
|
171
|
+
}\n\nAgents work in the background — you will be woken automatically when the squad completes, fails, or needs help. Report this plan to the user and END YOUR TURN now. Do NOT poll squad_status, do NOT sleep-wait, do NOT loop.`,
|
|
172
|
+
},
|
|
173
|
+
],
|
|
174
|
+
details: undefined,
|
|
175
|
+
};
|
|
176
|
+
}
|
package/src/store.ts
CHANGED
|
@@ -12,7 +12,6 @@ import * as os from "node:os";
|
|
|
12
12
|
import { createHash, randomUUID } from "node:crypto";
|
|
13
13
|
import type {
|
|
14
14
|
AgentDef,
|
|
15
|
-
KnowledgeEntry,
|
|
16
15
|
Squad,
|
|
17
16
|
SquadContext,
|
|
18
17
|
Task,
|
|
@@ -103,14 +102,6 @@ export function getContextFilePath(squadId: string): string {
|
|
|
103
102
|
return path.join(getSquadDir(squadId), "context.json");
|
|
104
103
|
}
|
|
105
104
|
|
|
106
|
-
export function getKnowledgeDir(squadId: string): string {
|
|
107
|
-
return path.join(getSquadDir(squadId), "knowledge");
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
export function getMemoryFilePath(): string {
|
|
111
|
-
return path.join(getSquadRoot(), "memory.jsonl");
|
|
112
|
-
}
|
|
113
|
-
|
|
114
105
|
/** Resolve task dir, supporting nested subtasks via parentPath */
|
|
115
106
|
export function getTaskDir(squadId: string, taskId: string, parentPath?: string): string {
|
|
116
107
|
const base = parentPath
|
|
@@ -649,28 +640,6 @@ export function saveContext(squadId: string, context: SquadContext): void {
|
|
|
649
640
|
writeJsonAtomic(getContextFilePath(squadId), context);
|
|
650
641
|
}
|
|
651
642
|
|
|
652
|
-
// ============================================================================
|
|
653
|
-
// Knowledge
|
|
654
|
-
// ============================================================================
|
|
655
|
-
|
|
656
|
-
export function appendKnowledge(squadId: string, type: KnowledgeEntry["type"], entry: KnowledgeEntry): void {
|
|
657
|
-
const file = path.join(getKnowledgeDir(squadId), `${type}s.jsonl`);
|
|
658
|
-
appendJsonl(file, entry);
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
export function loadKnowledge(squadId: string, type: KnowledgeEntry["type"]): KnowledgeEntry[] {
|
|
662
|
-
const file = path.join(getKnowledgeDir(squadId), `${type}s.jsonl`);
|
|
663
|
-
return readJsonl<KnowledgeEntry>(file);
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
export function loadAllKnowledge(squadId: string): KnowledgeEntry[] {
|
|
667
|
-
return [
|
|
668
|
-
...loadKnowledge(squadId, "decision"),
|
|
669
|
-
...loadKnowledge(squadId, "convention"),
|
|
670
|
-
...loadKnowledge(squadId, "finding"),
|
|
671
|
-
].sort((a, b) => a.ts.localeCompare(b.ts));
|
|
672
|
-
}
|
|
673
|
-
|
|
674
643
|
// ============================================================================
|
|
675
644
|
// Rework Helpers
|
|
676
645
|
// ============================================================================
|
|
@@ -688,18 +657,6 @@ export function getRetryCount(squadId: string, taskId: string): number {
|
|
|
688
657
|
return findRetries(squadId, taskId).length;
|
|
689
658
|
}
|
|
690
659
|
|
|
691
|
-
// ============================================================================
|
|
692
|
-
// Memory (cross-squad)
|
|
693
|
-
// ============================================================================
|
|
694
|
-
|
|
695
|
-
export function appendMemory(entry: KnowledgeEntry): void {
|
|
696
|
-
appendJsonl(getMemoryFilePath(), entry);
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
export function loadMemory(): KnowledgeEntry[] {
|
|
700
|
-
return readJsonl<KnowledgeEntry>(getMemoryFilePath());
|
|
701
|
-
}
|
|
702
|
-
|
|
703
660
|
// ============================================================================
|
|
704
661
|
// Bootstrap — first-run agent initialization
|
|
705
662
|
// ============================================================================
|