pi-squad 0.16.1 → 0.16.2
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 +2 -0
- package/package.json +1 -1
- package/src/agent-pool.ts +24 -15
package/README.md
CHANGED
|
@@ -139,6 +139,8 @@ Bundled agent definitions are copied to `~/.pi/squad/agents/` on first run. Edit
|
|
|
139
139
|
|
|
140
140
|
**@mention routing**: Agents write `@frontend what token format?` in their output. The router delivers it in real-time via RPC `steer()`.
|
|
141
141
|
|
|
142
|
+
**Main-session steering**: `squad_message` resolves an agent name to its live task, durably records the full message, and writes the documented JSONL RPC command `{"type":"steer","message":"..."}` to the child. pi-squad waits for Pi's final `agent_settled` event before closing the child; low-level `agent_end` no longer kills queued steering/follow-up continuations.
|
|
143
|
+
|
|
142
144
|
### Smart Planner
|
|
143
145
|
|
|
144
146
|
The planner creates task breakdowns with proper dependency ordering:
|
package/package.json
CHANGED
package/src/agent-pool.ts
CHANGED
|
@@ -270,16 +270,14 @@ export class AgentPool {
|
|
|
270
270
|
async steer(taskId: string, message: string): Promise<boolean> {
|
|
271
271
|
const agent = this.agents.get(taskId);
|
|
272
272
|
if (!agent || agent.aborted || agent.process.exitCode !== null) return false;
|
|
273
|
-
this.sendRpcCommand(agent.process, { type: "steer", message });
|
|
274
|
-
return true;
|
|
273
|
+
return this.sendRpcCommand(agent.process, { type: "steer", message });
|
|
275
274
|
}
|
|
276
275
|
|
|
277
276
|
/** Queue a follow-up message for after the current turn */
|
|
278
277
|
async followUp(taskId: string, message: string): Promise<boolean> {
|
|
279
278
|
const agent = this.agents.get(taskId);
|
|
280
279
|
if (!agent || agent.aborted || agent.process.exitCode !== null) return false;
|
|
281
|
-
this.sendRpcCommand(agent.process, { type: "follow_up", message });
|
|
282
|
-
return true;
|
|
280
|
+
return this.sendRpcCommand(agent.process, { type: "follow_up", message });
|
|
283
281
|
}
|
|
284
282
|
|
|
285
283
|
/** Abort the current operation */
|
|
@@ -301,7 +299,7 @@ export class AgentPool {
|
|
|
301
299
|
agent.process.kill("SIGTERM");
|
|
302
300
|
// Force kill after 5s
|
|
303
301
|
const timer = setTimeout(() => {
|
|
304
|
-
if (
|
|
302
|
+
if (agent.process.exitCode === null) agent.process.kill("SIGKILL");
|
|
305
303
|
}, 5000);
|
|
306
304
|
await new Promise<void>((resolve) => {
|
|
307
305
|
agent.process.on("exit", () => {
|
|
@@ -337,9 +335,17 @@ export class AgentPool {
|
|
|
337
335
|
// Internal
|
|
338
336
|
// =========================================================================
|
|
339
337
|
|
|
340
|
-
private sendRpcCommand(proc: ChildProcess, command: Record<string, unknown>):
|
|
341
|
-
if (!proc.stdin || proc.stdin.destroyed) return;
|
|
342
|
-
|
|
338
|
+
private sendRpcCommand(proc: ChildProcess, command: Record<string, unknown>): boolean {
|
|
339
|
+
if (!proc.stdin || proc.stdin.destroyed) return false;
|
|
340
|
+
try {
|
|
341
|
+
// Writable.write(false) means backpressure, not rejection; the bytes are
|
|
342
|
+
// still buffered. Reaching write() without throwing means the JSONL
|
|
343
|
+
// command was handed to the child stream.
|
|
344
|
+
proc.stdin.write(serializeJsonLine(command));
|
|
345
|
+
return true;
|
|
346
|
+
} catch {
|
|
347
|
+
return false;
|
|
348
|
+
}
|
|
343
349
|
}
|
|
344
350
|
|
|
345
351
|
private handleRpcEvent(agent: AgentProcess, event: any): void {
|
|
@@ -394,10 +400,12 @@ export class AgentPool {
|
|
|
394
400
|
data: event,
|
|
395
401
|
});
|
|
396
402
|
} else if (event.type === "agent_end") {
|
|
397
|
-
//
|
|
398
|
-
//
|
|
399
|
-
//
|
|
400
|
-
debug("squad-pool", `agent_end from RPC: ${agent.agentName} (task: ${agent.taskId})`);
|
|
403
|
+
// agent_end is one low-level run. Pi may still process queued steer,
|
|
404
|
+
// follow-up, retry, or compaction continuations. Killing here races and
|
|
405
|
+
// drops main-session steering. agent_settled is the final lifecycle edge.
|
|
406
|
+
debug("squad-pool", `agent_end from RPC (awaiting settled): ${agent.agentName} (task: ${agent.taskId})`);
|
|
407
|
+
} else if (event.type === "agent_settled") {
|
|
408
|
+
debug("squad-pool", `agent_settled from RPC: ${agent.agentName} (task: ${agent.taskId})`);
|
|
401
409
|
// Mark the guard to prevent double-emit from proc.on("exit")
|
|
402
410
|
const guardFn = (agent as any)._agentEndEmitted;
|
|
403
411
|
if (guardFn) guardFn();
|
|
@@ -417,11 +425,12 @@ export class AgentPool {
|
|
|
417
425
|
filesModified: endActivity.modifiedFiles.size,
|
|
418
426
|
},
|
|
419
427
|
});
|
|
420
|
-
//
|
|
428
|
+
// The session is fully settled, so the RPC process can now close.
|
|
421
429
|
agent.process.kill("SIGTERM");
|
|
422
|
-
setTimeout(() => {
|
|
423
|
-
if (
|
|
430
|
+
const forceKill = setTimeout(() => {
|
|
431
|
+
if (agent.process.exitCode === null) agent.process.kill("SIGKILL");
|
|
424
432
|
}, 3000);
|
|
433
|
+
forceKill.unref();
|
|
425
434
|
} else if (event.type === "error") {
|
|
426
435
|
this.emit({
|
|
427
436
|
type: "error",
|