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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-squad",
3
- "version": "0.6.3",
3
+ "version": "0.6.5",
4
4
  "description": "Multi-agent collaboration extension for pi — task decomposition, dependency management, parallel execution, TUI panel",
5
5
  "type": "module",
6
6
  "pi": {
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: { exitCode: code, stderr: stderr.slice(-2000) },
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: { exitCode: 0, stderr: "" },
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, _ctx) => {
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
- case "agent_end": {
381
+ case "agent_end": {
382
382
  const exitCode = event.data?.exitCode ?? 1;
383
- const activity = this.pool.getActivity(event.taskId);
384
- // Treat as success if agent did meaningful work (had turns),
385
- // even if exit code is non-zero (common with EPIPE on parent exit)
386
- const hadMeaningfulWork = activity && activity.turnCount > 0;
387
- if (exitCode === 0 || hadMeaningfulWork) {
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 died with no work done retry once before failing.
391
- // Transient failures (resource pressure, startup race) are common
392
- // when multiple agents spawn simultaneously.
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
- logError("squad-scheduler", `Agent ${event.agentName} died instantly (code ${exitCode}). Retrying in 2s... stderr: ${stderr.slice(0, 200)}`);
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 crashed on startup (code ${exitCode}). Retrying...`,
409
+ text: `Agent ${reason}. Retrying...`,
404
410
  });
405
411
  // Delay retry to let resources settle
406
412
  setTimeout(() => {