pi-squad 0.6.3 → 0.6.5
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/package.json +1 -1
- package/src/agent-pool.ts +18 -2
- package/src/index.ts +1 -1
- package/src/scheduler.ts +17 -11
package/package.json
CHANGED
package/src/agent-pool.ts
CHANGED
|
@@ -217,6 +217,8 @@ export class AgentPool {
|
|
|
217
217
|
if (code !== 0 && code !== null) {
|
|
218
218
|
logError("squad-pool", `${agentDef.name} exited: code=${code} signal=${signal} pid=${proc.pid} stdoutLines=${stdoutLines} stderr=${stderr.slice(0, 300) || "(empty)"}`);
|
|
219
219
|
}
|
|
220
|
+
// Capture activity stats BEFORE deleting the agent
|
|
221
|
+
const finalActivity = agentProc.activity;
|
|
220
222
|
// Clean up agents map so getRunningAgents() doesn't count dead processes
|
|
221
223
|
this.agents.delete(taskId);
|
|
222
224
|
// Only emit if we haven't already emitted via RPC agent_end event
|
|
@@ -226,7 +228,13 @@ export class AgentPool {
|
|
|
226
228
|
type: "agent_end",
|
|
227
229
|
taskId,
|
|
228
230
|
agentName: agentDef.name,
|
|
229
|
-
data: {
|
|
231
|
+
data: {
|
|
232
|
+
exitCode: code,
|
|
233
|
+
stderr: stderr.slice(-2000),
|
|
234
|
+
turnCount: finalActivity.turnCount,
|
|
235
|
+
toolCallCount: finalActivity.recentToolCalls.length,
|
|
236
|
+
filesModified: finalActivity.modifiedFiles.size,
|
|
237
|
+
},
|
|
230
238
|
});
|
|
231
239
|
}
|
|
232
240
|
// Cleanup temp files — delay to avoid race with last stdout reads
|
|
@@ -382,13 +390,21 @@ export class AgentPool {
|
|
|
382
390
|
// Mark the guard to prevent double-emit from proc.on("exit")
|
|
383
391
|
const guardFn = (agent as any)._agentEndEmitted;
|
|
384
392
|
if (guardFn) guardFn();
|
|
393
|
+
// Capture activity stats BEFORE deleting
|
|
394
|
+
const endActivity = agent.activity;
|
|
385
395
|
// Remove from agents map BEFORE emitting so getRunningAgents() doesn't count it
|
|
386
396
|
this.agents.delete(agent.taskId);
|
|
387
397
|
this.emit({
|
|
388
398
|
type: "agent_end",
|
|
389
399
|
taskId: agent.taskId,
|
|
390
400
|
agentName: agent.agentName,
|
|
391
|
-
data: {
|
|
401
|
+
data: {
|
|
402
|
+
exitCode: 0,
|
|
403
|
+
stderr: "",
|
|
404
|
+
turnCount: endActivity.turnCount,
|
|
405
|
+
toolCallCount: endActivity.recentToolCalls.length,
|
|
406
|
+
filesModified: endActivity.modifiedFiles.size,
|
|
407
|
+
},
|
|
392
408
|
});
|
|
393
409
|
// Kill the RPC process since the agent's work is done
|
|
394
410
|
agent.process.kill("SIGTERM");
|
package/src/index.ts
CHANGED
|
@@ -69,7 +69,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
69
69
|
// =========================================================================
|
|
70
70
|
|
|
71
71
|
// Inject squad awareness before each LLM call
|
|
72
|
-
pi.on("before_agent_start", async (event,
|
|
72
|
+
pi.on("before_agent_start", async (event, ctx) => {
|
|
73
73
|
if (!squadEnabled) return;
|
|
74
74
|
|
|
75
75
|
// When a squad is active, inject its status
|
package/src/scheduler.ts
CHANGED
|
@@ -378,29 +378,35 @@ export class Scheduler {
|
|
|
378
378
|
break;
|
|
379
379
|
}
|
|
380
380
|
|
|
381
|
-
|
|
381
|
+
case "agent_end": {
|
|
382
382
|
const exitCode = event.data?.exitCode ?? 1;
|
|
383
|
-
const
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
383
|
+
const turnCount = event.data?.turnCount ?? 0;
|
|
384
|
+
const toolCallCount = event.data?.toolCallCount ?? 0;
|
|
385
|
+
|
|
386
|
+
// Agent must have done real work: at least 1 turn AND at least 1 tool call.
|
|
387
|
+
// An agent that exits cleanly but with 0 turns/tools did nothing —
|
|
388
|
+
// likely hit a rate limit or API error. Treat as crash, not success.
|
|
389
|
+
const hadMeaningfulWork = turnCount > 0 && toolCallCount > 0;
|
|
390
|
+
if (hadMeaningfulWork) {
|
|
388
391
|
this.handleTaskCompleted(event.taskId).then(() => this.updateContext());
|
|
389
392
|
} else {
|
|
390
|
-
// Agent
|
|
391
|
-
//
|
|
392
|
-
//
|
|
393
|
+
// Agent exited without doing real work (0 turns or 0 tool calls).
|
|
394
|
+
// Common causes: rate limit, API error, resource pressure, crash.
|
|
395
|
+
// Retry once before failing.
|
|
393
396
|
const retryKey = `spawn-retry:${event.taskId}`;
|
|
394
397
|
if (!this.spawnRetries.has(retryKey)) {
|
|
395
398
|
this.spawnRetries.add(retryKey);
|
|
396
399
|
const stderr = event.data?.stderr || "";
|
|
397
|
-
|
|
400
|
+
const reason = turnCount === 0
|
|
401
|
+
? `exited with 0 turns (likely rate limit or API error)`
|
|
402
|
+
: `exited with ${turnCount} turns but 0 tool calls (no work done)`;
|
|
403
|
+
logError("squad-scheduler", `Agent ${event.agentName} ${reason}, code=${exitCode}. Retrying in 2s... stderr: ${stderr.slice(0, 200)}`);
|
|
398
404
|
store.updateTaskStatus(this.squadId, event.taskId, "pending");
|
|
399
405
|
store.appendMessage(this.squadId, event.taskId, {
|
|
400
406
|
ts: store.now(),
|
|
401
407
|
from: "system",
|
|
402
408
|
type: "status",
|
|
403
|
-
text: `Agent
|
|
409
|
+
text: `Agent ${reason}. Retrying...`,
|
|
404
410
|
});
|
|
405
411
|
// Delay retry to let resources settle
|
|
406
412
|
setTimeout(() => {
|