pi-squad 0.15.0 → 0.15.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -1
- package/package.json +1 -1
- package/src/advisor.ts +9 -18
- package/src/agent-pool.ts +3 -2
- package/src/index.ts +10 -15
- package/src/planner.ts +2 -2
- package/src/protocol.ts +6 -9
- package/src/report.ts +20 -0
- package/src/router.ts +1 -1
- package/src/scheduler.ts +16 -16
- package/src/supervisor.ts +3 -4
package/README.md
CHANGED
|
@@ -41,9 +41,13 @@ The planner agent reads your codebase and creates a task breakdown automatically
|
|
|
41
41
|
2. A **live widget** appears above the editor showing task progress
|
|
42
42
|
3. **Specialist agents** spawn as separate pi processes, working in parallel where dependencies allow
|
|
43
43
|
4. QA agents can trigger **automatic rework loops** when they find bugs
|
|
44
|
-
5. On completion, pi receives a summary with each task's output
|
|
44
|
+
5. On completion, pi receives a summary with each task's **complete, untruncated output**
|
|
45
45
|
6. Multiple squads can run concurrently across different projects
|
|
46
46
|
|
|
47
|
+
### No-Truncation Contract
|
|
48
|
+
|
|
49
|
+
Task messages, task outputs, dependency/rework handoffs, QA feedback, advisor handoffs, completion reports, failure diagnostics, and planner errors are persisted and forwarded in full. There is no character or task-count limit on report data. TUI widgets may show width/height-limited **views** to fit the terminal, but the underlying data and agent/main-session handoffs remain complete.
|
|
50
|
+
|
|
47
51
|
## Features
|
|
48
52
|
|
|
49
53
|
### Dependency-Aware Scheduling
|
package/package.json
CHANGED
package/src/advisor.ts
CHANGED
|
@@ -78,18 +78,10 @@ Output format:
|
|
|
78
78
|
|
|
79
79
|
Keep it short. The agent reads your advice and immediately acts on it.`;
|
|
80
80
|
|
|
81
|
-
const MAX_MSG_CHARS = 400;
|
|
82
|
-
const MAX_MESSAGES = 12;
|
|
83
|
-
const MAX_TOOL_CALLS = 10;
|
|
84
|
-
|
|
85
|
-
function clamp(text: string, max: number): string {
|
|
86
|
-
const t = text.trim();
|
|
87
|
-
return t.length > max ? `${t.slice(0, max).trimEnd()}…` : t;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
81
|
/**
|
|
91
82
|
* Build the user-message digest sent to the advisor model.
|
|
92
|
-
*
|
|
83
|
+
* Preserve complete descriptions, messages, and tool history: advisor handoffs
|
|
84
|
+
* follow the same no-truncation contract as task and completion reports.
|
|
93
85
|
*/
|
|
94
86
|
export function buildAdvisorConsultText(input: AdvisorConsultInput): string {
|
|
95
87
|
const lines: string[] = [];
|
|
@@ -102,22 +94,21 @@ export function buildAdvisorConsultText(input: AdvisorConsultInput): string {
|
|
|
102
94
|
lines.push(`Progress: ${input.turnCount} turns, ~${Math.round(input.elapsedMinutes)} min elapsed`);
|
|
103
95
|
lines.push("");
|
|
104
96
|
lines.push(`## Task Description`);
|
|
105
|
-
lines.push(
|
|
97
|
+
lines.push((input.taskDescription || "(no description)").trim());
|
|
106
98
|
|
|
107
99
|
if (input.recentToolCalls.length > 0) {
|
|
108
100
|
lines.push("");
|
|
109
101
|
lines.push(`## Recent Tool Activity (newest last)`);
|
|
110
|
-
for (const call of input.recentToolCalls
|
|
111
|
-
lines.push(`- ${
|
|
102
|
+
for (const call of input.recentToolCalls) {
|
|
103
|
+
lines.push(`- ${call.trim()}`);
|
|
112
104
|
}
|
|
113
105
|
}
|
|
114
106
|
|
|
115
|
-
|
|
116
|
-
if (messages.length > 0) {
|
|
107
|
+
if (input.recentMessages.length > 0) {
|
|
117
108
|
lines.push("");
|
|
118
109
|
lines.push(`## Recent Messages (newest last)`);
|
|
119
|
-
for (const msg of
|
|
120
|
-
lines.push(`[${msg.from}/${msg.type}] ${
|
|
110
|
+
for (const msg of input.recentMessages) {
|
|
111
|
+
lines.push(`[${msg.from}/${msg.type}] ${msg.text.trim()}`);
|
|
121
112
|
}
|
|
122
113
|
}
|
|
123
114
|
|
|
@@ -141,5 +132,5 @@ export function formatAdvisorSteerMessage(advice: string, reason: string): strin
|
|
|
141
132
|
|
|
142
133
|
/** True when the advisor's verdict says a human decision is required. */
|
|
143
134
|
export function adviceNeedsHuman(advice: string): boolean {
|
|
144
|
-
return /needs human input/i.test(advice
|
|
135
|
+
return /needs human input/i.test(advice);
|
|
145
136
|
}
|
package/src/agent-pool.ts
CHANGED
|
@@ -217,7 +217,7 @@ export class AgentPool {
|
|
|
217
217
|
proc.on("exit", (code, signal) => {
|
|
218
218
|
// Log diagnostic info for debugging spawn failures
|
|
219
219
|
if (code !== 0 && code !== null) {
|
|
220
|
-
logError("squad-pool", `${agentDef.name} exited: code=${code} signal=${signal} pid=${proc.pid} stdoutLines=${stdoutLines} stderr=${stderr
|
|
220
|
+
logError("squad-pool", `${agentDef.name} exited: code=${code} signal=${signal} pid=${proc.pid} stdoutLines=${stdoutLines} stderr=${stderr || "(empty)"}`);
|
|
221
221
|
}
|
|
222
222
|
// Capture activity stats BEFORE deleting the agent
|
|
223
223
|
const finalActivity = agentProc.activity;
|
|
@@ -232,7 +232,8 @@ export class AgentPool {
|
|
|
232
232
|
agentName: agentDef.name,
|
|
233
233
|
data: {
|
|
234
234
|
exitCode: code,
|
|
235
|
-
|
|
235
|
+
// Preserve complete diagnostics for task failure reports and recovery.
|
|
236
|
+
stderr,
|
|
236
237
|
turnCount: finalActivity.turnCount,
|
|
237
238
|
toolCallCount: finalActivity.recentToolCalls.length,
|
|
238
239
|
filesModified: finalActivity.modifiedFiles.size,
|
package/src/index.ts
CHANGED
|
@@ -25,6 +25,7 @@ import { SquadPanel, type SquadPanelResult } from "./panel/squad-panel.js";
|
|
|
25
25
|
import { setupSquadWidget, type SquadWidgetState } from "./panel/squad-widget.js";
|
|
26
26
|
import * as store from "./store.js";
|
|
27
27
|
import { debug, logError } from "./logger.js";
|
|
28
|
+
import { buildCompletionSummary, buildFailureSummary } from "./report.js";
|
|
28
29
|
|
|
29
30
|
// ============================================================================
|
|
30
31
|
// State
|
|
@@ -229,8 +230,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
229
230
|
const taskLines = tasks.map((t) => {
|
|
230
231
|
const icon = t.status === "done" ? "✓" : t.status === "in_progress" ? "⏳" : t.status === "failed" ? "✗" : t.status === "blocked" ? "◻" : "·";
|
|
231
232
|
let line = ` ${icon} ${t.id} (${t.agent}) [${t.status}]`;
|
|
232
|
-
if (t.output) line += ` — ${t.output
|
|
233
|
-
if (t.error) line += ` ERROR: ${t.error
|
|
233
|
+
if (t.output) line += ` — ${t.output}`;
|
|
234
|
+
if (t.error) line += ` ERROR: ${t.error}`;
|
|
234
235
|
return line;
|
|
235
236
|
}).join("\n");
|
|
236
237
|
|
|
@@ -519,10 +520,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
519
520
|
switch (event.type) {
|
|
520
521
|
case "squad_completed": {
|
|
521
522
|
const tasks = store.loadAllTasks(squadId);
|
|
522
|
-
const summary = tasks
|
|
523
|
-
.filter((t) => t.status === "done")
|
|
524
|
-
.map((t) => `- ${t.id} (${t.agent}): ${t.output?.slice(0, 150) || "done"}`)
|
|
525
|
-
.join("\n");
|
|
523
|
+
const summary = buildCompletionSummary(tasks);
|
|
526
524
|
const totalCost = tasks.reduce((sum, t) => sum + t.usage.cost, 0);
|
|
527
525
|
const s = schedulers.get(squadId); if (s) s.updateContext();
|
|
528
526
|
// followUp + triggerTurn: wake an idle main agent to review;
|
|
@@ -542,7 +540,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
542
540
|
const done = tasks.filter((t) => t.status === "done");
|
|
543
541
|
pi.sendMessage({
|
|
544
542
|
customType: "squad-failed",
|
|
545
|
-
content: `[squad] Squad "${squadId}" has stalled. ${done.length}/${tasks.length} done, ${failed.length} failed.\nFailed: ${
|
|
543
|
+
content: `[squad] Squad "${squadId}" has stalled. ${done.length}/${tasks.length} done, ${failed.length} failed.\nFailed: ${buildFailureSummary(tasks)}`,
|
|
546
544
|
display: true,
|
|
547
545
|
}, { triggerTurn: true, deliverAs: "followUp" });
|
|
548
546
|
forceWidgetUpdate();
|
|
@@ -932,7 +930,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
932
930
|
const msgSched = getActiveScheduler();
|
|
933
931
|
if (msgSched) {
|
|
934
932
|
await msgSched.sendHumanMessage(targetTaskId, msgText);
|
|
935
|
-
ctx.ui.notify(`Sent to ${targetAgent}: "${msgText
|
|
933
|
+
ctx.ui.notify(`Sent to ${targetAgent}: "${msgText}"`, "info");
|
|
936
934
|
} else {
|
|
937
935
|
store.appendMessage(activeSquadId, targetTaskId, {
|
|
938
936
|
ts: store.now(),
|
|
@@ -1210,7 +1208,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1210
1208
|
`Tags: ${agent.tags.join(", ")}`,
|
|
1211
1209
|
``,
|
|
1212
1210
|
`Prompt:`,
|
|
1213
|
-
|
|
1211
|
+
agent.prompt,
|
|
1214
1212
|
``,
|
|
1215
1213
|
`File: ${store.getGlobalAgentsDir()}/${agent.name}.json`,
|
|
1216
1214
|
].join("\n");
|
|
@@ -1399,7 +1397,7 @@ function openPanel(
|
|
|
1399
1397
|
const panelSched = schedulers.get(squadId);
|
|
1400
1398
|
if (input && panelSched) {
|
|
1401
1399
|
await panelSched.sendHumanMessage(taskId, input);
|
|
1402
|
-
ctx.ui.notify(`Sent to ${agentName}: "${input
|
|
1400
|
+
ctx.ui.notify(`Sent to ${agentName}: "${input}"`, "info");
|
|
1403
1401
|
} else if (input) {
|
|
1404
1402
|
store.appendMessage(squadId, taskId, {
|
|
1405
1403
|
ts: store.now(),
|
|
@@ -1567,10 +1565,7 @@ async function startSquad(
|
|
|
1567
1565
|
switch (event.type) {
|
|
1568
1566
|
case "squad_completed": {
|
|
1569
1567
|
const tasks = store.loadAllTasks(squadId);
|
|
1570
|
-
const summary = tasks
|
|
1571
|
-
.filter((t) => t.status === "done")
|
|
1572
|
-
.map((t) => `- ${t.id} (${t.agent}): ${t.output?.slice(0, 150) || "done"}`)
|
|
1573
|
-
.join("\n");
|
|
1568
|
+
const summary = buildCompletionSummary(tasks);
|
|
1574
1569
|
const totalCost = tasks.reduce((sum, t) => sum + t.usage.cost, 0);
|
|
1575
1570
|
|
|
1576
1571
|
// Final context update before clearing scheduler
|
|
@@ -1605,7 +1600,7 @@ async function startSquad(
|
|
|
1605
1600
|
customType: "squad-failed",
|
|
1606
1601
|
content: `[squad] Squad "${squadId}" has stalled. ` +
|
|
1607
1602
|
`${done.length}/${tasks.length} tasks done, ${failed.length} failed.\n` +
|
|
1608
|
-
`Failed: ${
|
|
1603
|
+
`Failed: ${buildFailureSummary(tasks)}\n` +
|
|
1609
1604
|
`Use squad_status for details or squad_modify to adjust.`,
|
|
1610
1605
|
display: true,
|
|
1611
1606
|
}, { triggerTurn: true, deliverAs: "followUp" });
|
package/src/planner.ts
CHANGED
|
@@ -152,7 +152,7 @@ async function runPiJson(options: PiJsonOptions): Promise<string> {
|
|
|
152
152
|
}
|
|
153
153
|
|
|
154
154
|
if (code !== 0 && messages.length === 0) {
|
|
155
|
-
reject(new Error(`Planner failed (code ${code}): ${stderr
|
|
155
|
+
reject(new Error(`Planner failed (code ${code}): ${stderr}`));
|
|
156
156
|
return;
|
|
157
157
|
}
|
|
158
158
|
|
|
@@ -217,7 +217,7 @@ function parsePlannerOutput(text: string, validAgents?: Set<string>): PlannerOut
|
|
|
217
217
|
return parsed as PlannerOutput;
|
|
218
218
|
} catch (error) {
|
|
219
219
|
if (error instanceof SyntaxError) {
|
|
220
|
-
throw new Error(`Planner output is not valid JSON: ${text
|
|
220
|
+
throw new Error(`Planner output is not valid JSON: ${text}`);
|
|
221
221
|
}
|
|
222
222
|
throw error;
|
|
223
223
|
}
|
package/src/protocol.ts
CHANGED
|
@@ -102,7 +102,6 @@ function buildChainContext(task: Task, allTasks: Task[], squadId: string): strin
|
|
|
102
102
|
const messages = loadMessages(squadId, dep.id);
|
|
103
103
|
const lastText = messages
|
|
104
104
|
.filter((m) => m.from === dep.agent && (m.type === "text" || m.type === "done"))
|
|
105
|
-
.slice(-3)
|
|
106
105
|
.map((m) => m.text)
|
|
107
106
|
.join("\n");
|
|
108
107
|
if (lastText) {
|
|
@@ -155,12 +154,9 @@ function buildSiblingAwareness(
|
|
|
155
154
|
for (const [agent, files] of fileEntries) {
|
|
156
155
|
if (files.length > 0) {
|
|
157
156
|
lines.push(`**${agent}:**`);
|
|
158
|
-
for (const f of files
|
|
157
|
+
for (const f of files) {
|
|
159
158
|
lines.push(` - ${f}`);
|
|
160
159
|
}
|
|
161
|
-
if (files.length > 10) {
|
|
162
|
-
lines.push(` - ...and ${files.length - 10} more`);
|
|
163
|
-
}
|
|
164
160
|
}
|
|
165
161
|
}
|
|
166
162
|
lines.push(
|
|
@@ -187,7 +183,7 @@ function buildKnowledgeSection(squadId: string): string {
|
|
|
187
183
|
|
|
188
184
|
if (decisions.length > 0) {
|
|
189
185
|
lines.push("## Decisions");
|
|
190
|
-
for (const d of decisions
|
|
186
|
+
for (const d of decisions) {
|
|
191
187
|
lines.push(`- ${d.text} (${d.from})`);
|
|
192
188
|
}
|
|
193
189
|
lines.push("");
|
|
@@ -195,7 +191,7 @@ function buildKnowledgeSection(squadId: string): string {
|
|
|
195
191
|
|
|
196
192
|
if (conventions.length > 0) {
|
|
197
193
|
lines.push("## Project Conventions");
|
|
198
|
-
for (const c of conventions
|
|
194
|
+
for (const c of conventions) {
|
|
199
195
|
lines.push(`- ${c.text} (${c.from})`);
|
|
200
196
|
}
|
|
201
197
|
lines.push("");
|
|
@@ -203,7 +199,7 @@ function buildKnowledgeSection(squadId: string): string {
|
|
|
203
199
|
|
|
204
200
|
if (findings.length > 0) {
|
|
205
201
|
lines.push("## Findings");
|
|
206
|
-
for (const f of findings
|
|
202
|
+
for (const f of findings) {
|
|
207
203
|
lines.push(`- ${f.text} (${f.from})`);
|
|
208
204
|
}
|
|
209
205
|
lines.push("");
|
|
@@ -256,7 +252,8 @@ function buildReworkContext(task: Task, squadId: string): string {
|
|
|
256
252
|
|
|
257
253
|
if (originalTask?.output) {
|
|
258
254
|
lines.push("## What Was Built (Previous Attempt)");
|
|
259
|
-
|
|
255
|
+
// Rework agents need the complete prior handoff, not an arbitrary prefix.
|
|
256
|
+
lines.push(originalTask.output);
|
|
260
257
|
lines.push("");
|
|
261
258
|
}
|
|
262
259
|
|
package/src/report.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Task } from "./types.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Build the full task handoff included in squad completion notifications.
|
|
5
|
+
* This is durable report data, not a UI preview: never shorten task output.
|
|
6
|
+
*/
|
|
7
|
+
export function buildCompletionSummary(tasks: Task[]): string {
|
|
8
|
+
return tasks
|
|
9
|
+
.filter((task) => task.status === "done")
|
|
10
|
+
.map((task) => `- ${task.id} (${task.agent}): ${task.output || "done"}`)
|
|
11
|
+
.join("\n");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Build the full failure handoff without shortening diagnostics. */
|
|
15
|
+
export function buildFailureSummary(tasks: Task[]): string {
|
|
16
|
+
return tasks
|
|
17
|
+
.filter((task) => task.status === "failed")
|
|
18
|
+
.map((task) => `${task.id}: ${task.error || "unknown error"}`)
|
|
19
|
+
.join("; ");
|
|
20
|
+
}
|
package/src/router.ts
CHANGED
package/src/scheduler.ts
CHANGED
|
@@ -465,7 +465,7 @@ export class Scheduler {
|
|
|
465
465
|
agentName: task.agent,
|
|
466
466
|
agentRole: agentDef?.role || task.agent,
|
|
467
467
|
reason,
|
|
468
|
-
recentMessages: store.loadMessages(this.squadId, taskId).
|
|
468
|
+
recentMessages: store.loadMessages(this.squadId, taskId).map((m) => ({ from: m.from, type: m.type, text: m.text })),
|
|
469
469
|
recentToolCalls: activity ? [...activity.recentToolCalls] : [],
|
|
470
470
|
turnCount: activity?.turnCount || 0,
|
|
471
471
|
elapsedMinutes: activity ? (Date.now() - activity.startedAt) / 60000 : 0,
|
|
@@ -488,7 +488,7 @@ export class Scheduler {
|
|
|
488
488
|
squadId: this.squadId,
|
|
489
489
|
taskId,
|
|
490
490
|
agentName,
|
|
491
|
-
message: `${reason}\n\nAdvisor assessment:\n${advice
|
|
491
|
+
message: `${reason}\n\nAdvisor assessment:\n${advice}`,
|
|
492
492
|
});
|
|
493
493
|
return true; // escalation already emitted with richer context
|
|
494
494
|
}
|
|
@@ -563,7 +563,9 @@ export class Scheduler {
|
|
|
563
563
|
ts: store.now(),
|
|
564
564
|
from: event.agentName,
|
|
565
565
|
type: "text",
|
|
566
|
-
|
|
566
|
+
// Persist the complete handoff. Reports can be arbitrarily long;
|
|
567
|
+
// presentation layers may viewport them, but source data must never truncate.
|
|
568
|
+
text,
|
|
567
569
|
});
|
|
568
570
|
}
|
|
569
571
|
|
|
@@ -636,7 +638,7 @@ export class Scheduler {
|
|
|
636
638
|
const reason = turnCount === 0
|
|
637
639
|
? `exited with 0 turns (likely rate limit or API error)`
|
|
638
640
|
: `exited with ${turnCount} turns but 0 tool calls (no work done)`;
|
|
639
|
-
logError("squad-scheduler", `Agent ${event.agentName} ${reason}, code=${exitCode}. Retrying in 2s... stderr: ${stderr
|
|
641
|
+
logError("squad-scheduler", `Agent ${event.agentName} ${reason}, code=${exitCode}. Retrying in 2s... stderr: ${stderr}`);
|
|
640
642
|
store.updateTaskStatus(this.squadId, event.taskId, "pending");
|
|
641
643
|
store.appendMessage(this.squadId, event.taskId, {
|
|
642
644
|
ts: store.now(),
|
|
@@ -650,7 +652,7 @@ export class Scheduler {
|
|
|
650
652
|
}, 2000);
|
|
651
653
|
} else {
|
|
652
654
|
const stderr = event.data?.stderr || "";
|
|
653
|
-
this.handleTaskFailed(event.taskId, `Agent exited with code ${exitCode} (retry exhausted). ${stderr
|
|
655
|
+
this.handleTaskFailed(event.taskId, `Agent exited with code ${exitCode} (retry exhausted). ${stderr}`);
|
|
654
656
|
}
|
|
655
657
|
this.updateContext();
|
|
656
658
|
}
|
|
@@ -682,10 +684,9 @@ export class Scheduler {
|
|
|
682
684
|
|
|
683
685
|
// Extract output from last messages
|
|
684
686
|
const messages = store.loadMessages(this.squadId, taskId);
|
|
685
|
-
const
|
|
686
|
-
.filter((m) => m.from === task.agent && (m.type === "text" || m.type === "done"))
|
|
687
|
-
|
|
688
|
-
const output = lastAgentMessages.map((m) => m.text).join("\n");
|
|
687
|
+
const agentMessages = messages
|
|
688
|
+
.filter((m) => m.from === task.agent && (m.type === "text" || m.type === "done"));
|
|
689
|
+
const output = agentMessages.map((m) => m.text).join("\n");
|
|
689
690
|
|
|
690
691
|
store.updateTaskStatus(this.squadId, taskId, "done", {
|
|
691
692
|
output: output || "Task completed",
|
|
@@ -869,7 +870,7 @@ export class Scheduler {
|
|
|
869
870
|
squadId: this.squadId,
|
|
870
871
|
taskId: task.id,
|
|
871
872
|
agentName: task.agent,
|
|
872
|
-
message: `QA failed ${originalId} ${retryCount} times. Retry limit reached.\nLatest feedback:\n${feedback
|
|
873
|
+
message: `QA failed ${originalId} ${retryCount} times. Retry limit reached.\nLatest feedback:\n${feedback}`,
|
|
873
874
|
});
|
|
874
875
|
continue;
|
|
875
876
|
}
|
|
@@ -974,12 +975,11 @@ export class Scheduler {
|
|
|
974
975
|
|
|
975
976
|
// Try to extract lines containing "FAIL", "Error", "✗"
|
|
976
977
|
const failLines = output.split("\n")
|
|
977
|
-
.filter((line) => /fail|error|✗|✘|broken|bug/i.test(line))
|
|
978
|
-
.slice(0, 20);
|
|
978
|
+
.filter((line) => /fail|error|✗|✘|broken|bug/i.test(line));
|
|
979
979
|
if (failLines.length > 0) return failLines.join("\n");
|
|
980
980
|
|
|
981
|
-
//
|
|
982
|
-
return output
|
|
981
|
+
// Preserve the complete QA handoff when no structured failure section exists.
|
|
982
|
+
return output;
|
|
983
983
|
}
|
|
984
984
|
|
|
985
985
|
// =========================================================================
|
|
@@ -1047,7 +1047,7 @@ export class Scheduler {
|
|
|
1047
1047
|
status: task.status,
|
|
1048
1048
|
agent: task.agent,
|
|
1049
1049
|
title: task.title,
|
|
1050
|
-
...(task.output ? { output: task.output
|
|
1050
|
+
...(task.output ? { output: task.output } : {}),
|
|
1051
1051
|
...(task.status === "blocked"
|
|
1052
1052
|
? {
|
|
1053
1053
|
blockedBy: task.depends.filter((d) => {
|
|
@@ -1089,7 +1089,7 @@ export class Scheduler {
|
|
|
1089
1089
|
action:
|
|
1090
1090
|
msg.type === "tool"
|
|
1091
1091
|
? `→ ${msg.name} ${msg.args?.path || msg.args?.command || ""}`.trim()
|
|
1092
|
-
: msg.text
|
|
1092
|
+
: msg.text,
|
|
1093
1093
|
});
|
|
1094
1094
|
}
|
|
1095
1095
|
}
|
package/src/supervisor.ts
CHANGED
|
@@ -70,10 +70,9 @@ export async function analyzeStuckAgent(
|
|
|
70
70
|
): Promise<{ action: "retry" | "reassign" | "escalate"; reason: string; suggestion?: string }> {
|
|
71
71
|
const task = store.loadTask(squadId, taskId);
|
|
72
72
|
const messages = store.loadMessages(squadId, taskId);
|
|
73
|
-
const recentMessages = messages.slice(-10);
|
|
74
73
|
|
|
75
|
-
// Simple heuristic for now
|
|
76
|
-
const errorMessages =
|
|
74
|
+
// Simple heuristic for now — inspect the complete task history.
|
|
75
|
+
const errorMessages = messages.filter((m) => m.type === "error");
|
|
77
76
|
if (errorMessages.length >= 3) {
|
|
78
77
|
return {
|
|
79
78
|
action: "escalate",
|
|
@@ -128,7 +127,7 @@ export async function analyzeBlockRequest(
|
|
|
128
127
|
return {
|
|
129
128
|
action: "create_subtask",
|
|
130
129
|
subtask: {
|
|
131
|
-
title: `Support: ${blockReason
|
|
130
|
+
title: `Support: ${blockReason}`,
|
|
132
131
|
agent: "fullstack",
|
|
133
132
|
description: `Created from block request by ${agentName} on task ${taskId}: ${blockReason}`,
|
|
134
133
|
},
|