pi-subagents-lite 1.4.6 → 1.4.8
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
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
type AgentStatus,
|
|
15
15
|
type CompactionInfo,
|
|
16
16
|
type RunCallbacks,
|
|
17
|
+
type StopInitiator,
|
|
17
18
|
SHORT_ID_LENGTH,
|
|
18
19
|
type SpawnConfig,
|
|
19
20
|
type ToolActivity,
|
|
@@ -267,7 +268,7 @@ export class AgentManager {
|
|
|
267
268
|
|
|
268
269
|
// Wire parent abort signal to stop the subagent when the parent is interrupted
|
|
269
270
|
if (options.signal) {
|
|
270
|
-
options.signal.addEventListener("abort", () => this.abort(id), { once: true });
|
|
271
|
+
options.signal.addEventListener("abort", () => this.abort(id, "agent"), { once: true });
|
|
271
272
|
}
|
|
272
273
|
|
|
273
274
|
const promise = runAgent(ctx, type, prompt, {
|
|
@@ -465,18 +466,18 @@ export class AgentManager {
|
|
|
465
466
|
);
|
|
466
467
|
}
|
|
467
468
|
|
|
468
|
-
abort(id: string): boolean {
|
|
469
|
+
abort(id: string, stoppedBy?: StopInitiator): boolean {
|
|
469
470
|
const record = this.agents.get(id);
|
|
470
471
|
if (!record) return false;
|
|
471
472
|
|
|
472
|
-
return this.stopAgent(record);
|
|
473
|
+
return this.stopAgent(record, stoppedBy);
|
|
473
474
|
}
|
|
474
475
|
|
|
475
476
|
/**
|
|
476
477
|
* Stop an agent by aborting its session or removing it from the queue.
|
|
477
478
|
* Returns true if the agent was stopped, false if it wasn't running/queued.
|
|
478
479
|
*/
|
|
479
|
-
private stopAgent(record: AgentRecord): boolean {
|
|
480
|
+
private stopAgent(record: AgentRecord, stoppedBy?: StopInitiator): boolean {
|
|
480
481
|
if (record.lifecycle.status === "queued") {
|
|
481
482
|
this.queue = this.queue.filter(q => q.id !== record.id);
|
|
482
483
|
} else if (record.lifecycle.status !== "running") {
|
|
@@ -485,6 +486,7 @@ export class AgentManager {
|
|
|
485
486
|
record.execution.abortController?.abort();
|
|
486
487
|
}
|
|
487
488
|
record.lifecycle.status = "stopped";
|
|
489
|
+
record.lifecycle.stoppedBy = stoppedBy;
|
|
488
490
|
record.lifecycle.completedAt = Date.now();
|
|
489
491
|
return true;
|
|
490
492
|
}
|
|
@@ -501,6 +503,10 @@ export class AgentManager {
|
|
|
501
503
|
for (const [id, record] of this.agents) {
|
|
502
504
|
if (!isTerminalStatus(record.lifecycle.status)) continue;
|
|
503
505
|
if ((record.lifecycle.completedAt ?? 0) >= cutoff) continue;
|
|
506
|
+
// Keep the record until the LLM has read the result (foreground return or
|
|
507
|
+
// background nudge). Otherwise a completed background agent can be wiped
|
|
508
|
+
// before its nudge is emitted.
|
|
509
|
+
if (!record.lifecycle.resultConsumed) continue;
|
|
504
510
|
this.removeRecord(id, record);
|
|
505
511
|
}
|
|
506
512
|
}
|
|
@@ -88,6 +88,17 @@ export function buildAgentDetails(
|
|
|
88
88
|
return details;
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
+
/**
|
|
92
|
+
* Result text plus status note, for display.
|
|
93
|
+
*
|
|
94
|
+
* Shared by the foreground tool result and the subagent-result nudge so both
|
|
95
|
+
* callers stay in sync on the nullish default and separator handling — they
|
|
96
|
+
* have diverged before. getStatusNote owns the leading separator.
|
|
97
|
+
*/
|
|
98
|
+
export function formatResultContent(record: AgentRecord): string {
|
|
99
|
+
return (record.result ?? "") + getStatusNote(record.lifecycle);
|
|
100
|
+
}
|
|
101
|
+
|
|
91
102
|
// ============================================================================
|
|
92
103
|
// Tool execute handlers
|
|
93
104
|
// ============================================================================
|
|
@@ -186,8 +197,7 @@ export async function executeAgentTool(
|
|
|
186
197
|
return errorResult(`Agent failed: ${record.error || "unknown error"}`, details);
|
|
187
198
|
}
|
|
188
199
|
|
|
189
|
-
|
|
190
|
-
return successResult((record.result ?? "") + statusNote, details);
|
|
200
|
+
return successResult(formatResultContent(record), details);
|
|
191
201
|
}
|
|
192
202
|
|
|
193
203
|
// ============================================================================
|
|
@@ -244,7 +254,7 @@ export async function executeStopAgentTool(
|
|
|
244
254
|
}
|
|
245
255
|
|
|
246
256
|
// Attempt to stop the running/queued agent
|
|
247
|
-
if (getManager()!.abort(agentId)) {
|
|
257
|
+
if (getManager()!.abort(agentId, "agent")) {
|
|
248
258
|
return successResult(`Stopped agent ${agentId.slice(0, SHORT_ID_LENGTH)}`);
|
|
249
259
|
}
|
|
250
260
|
|
|
@@ -1,5 +1,11 @@
|
|
|
1
|
-
import { getStatusNote } from "../status-note.js";
|
|
2
1
|
import { getPiInstance, getSessionCtx, getWidget } from "../shell.js";
|
|
2
|
+
import { SHORT_ID_LENGTH } from "../types.js";
|
|
3
|
+
|
|
4
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
5
|
+
import type { AgentRecord, SpawnConfig, ToolActivity } from "../types.js";
|
|
6
|
+
import type { AgentManager, SpawnOptions } from "../agents/agent-manager.js";
|
|
7
|
+
import { buildAgentDetails, formatResultContent } from "../agents/tool-execution.js";
|
|
8
|
+
|
|
3
9
|
/**
|
|
4
10
|
* spawn-coordinator.ts — Spawn-and-track coordination for subagents.
|
|
5
11
|
*
|
|
@@ -11,11 +17,6 @@ import { getPiInstance, getSessionCtx, getWidget } from "../shell.js";
|
|
|
11
17
|
* D6 (Nudge owned here), D2 (peers with AgentManager).
|
|
12
18
|
*/
|
|
13
19
|
|
|
14
|
-
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
15
|
-
import type { AgentRecord, SpawnConfig, ToolActivity } from "../types.js";
|
|
16
|
-
import type { AgentManager, SpawnOptions } from "../agents/agent-manager.js";
|
|
17
|
-
import { buildAgentDetails } from "../agents/tool-execution.js";
|
|
18
|
-
|
|
19
20
|
// ============================================================================
|
|
20
21
|
// Types
|
|
21
22
|
// ============================================================================
|
|
@@ -121,6 +122,10 @@ export class SpawnCoordinator {
|
|
|
121
122
|
// Foreground: await completion
|
|
122
123
|
await record.execution.promise;
|
|
123
124
|
|
|
125
|
+
// Foreground tool handler reads the result inline on return — mark it
|
|
126
|
+
// consumed so the cleanup timer may evict the record once it ages out.
|
|
127
|
+
record.lifecycle.resultConsumed = true;
|
|
128
|
+
|
|
124
129
|
// Clean up live view (foreground completion handled inline)
|
|
125
130
|
this.liveViews.delete(agentId);
|
|
126
131
|
}
|
|
@@ -234,7 +239,7 @@ export class SpawnCoordinator {
|
|
|
234
239
|
pi.sendMessage(
|
|
235
240
|
{
|
|
236
241
|
customType: "subagent-result",
|
|
237
|
-
content: `[Subagent "${record.display.type}" ${record.lifecycle.status}]\n\n${
|
|
242
|
+
content: `[Subagent "${record.display.type}" ${record.id.slice(0, SHORT_ID_LENGTH)} ${record.lifecycle.status}]\n\n${formatResultContent(record)}`,
|
|
238
243
|
details,
|
|
239
244
|
display: true,
|
|
240
245
|
},
|
|
@@ -243,6 +248,10 @@ export class SpawnCoordinator {
|
|
|
243
248
|
triggerTurn: true,
|
|
244
249
|
},
|
|
245
250
|
);
|
|
251
|
+
|
|
252
|
+
// Full result delivered to the LLM — record is now safe for the cleanup
|
|
253
|
+
// timer to evict once it ages out.
|
|
254
|
+
record.lifecycle.resultConsumed = true;
|
|
246
255
|
} catch (error) {
|
|
247
256
|
// sendMessage failed (shared runtime overwritten by subagent bindCore).
|
|
248
257
|
// Fall back to UI notification using the captured spawning-session context.
|
package/src/status-note.ts
CHANGED
|
@@ -1,10 +1,20 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import type { AgentLifecycle, AgentStatus, StopInitiator } from "./types.js";
|
|
2
|
+
|
|
3
|
+
const STATUS_NOTES: Partial<Record<AgentStatus, string>> = {
|
|
3
4
|
aborted: "hit the turn limit before completion; output may be incomplete",
|
|
4
5
|
turn_limited: "wrapped up at the turn limit — output may be partial",
|
|
5
6
|
};
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
const STOP_NOTES: Record<StopInitiator, string> = {
|
|
9
|
+
user: "STOPPED BY THE USER before completion — output is partial; the task was NOT finished",
|
|
10
|
+
agent: "stopped before completion — output is partial; the task was NOT finished",
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export function getStatusNote(lifecycle: AgentLifecycle): string {
|
|
14
|
+
const note =
|
|
15
|
+
lifecycle.status === "stopped"
|
|
16
|
+
// A stopped agent with no recorded initiator reads as an agent stop.
|
|
17
|
+
? STOP_NOTES[lifecycle.stoppedBy ?? "agent"]
|
|
18
|
+
: STATUS_NOTES[lifecycle.status];
|
|
9
19
|
return note ? ` (${note})` : "";
|
|
10
20
|
}
|
package/src/types.ts
CHANGED
|
@@ -95,6 +95,9 @@ export interface CompactionInfo {
|
|
|
95
95
|
/** Possible agent lifecycle statuses. */
|
|
96
96
|
export type AgentStatus = "queued" | "running" | "completed" | "turn_limited" | "aborted" | "stopped" | "error";
|
|
97
97
|
|
|
98
|
+
/** Who initiated an agent stop: "user" via UI menu, or "agent" via StopAgent tool. */
|
|
99
|
+
export type StopInitiator = "user" | "agent";
|
|
100
|
+
|
|
98
101
|
/**
|
|
99
102
|
* Lifecycle state: when the agent started, completed, and its current status.
|
|
100
103
|
* Used by agent-manager (lifecycle control), menus (status display), widget (linger logic).
|
|
@@ -103,6 +106,13 @@ export interface AgentLifecycle {
|
|
|
103
106
|
status: AgentStatus;
|
|
104
107
|
startedAt: number;
|
|
105
108
|
completedAt?: number;
|
|
109
|
+
stoppedBy?: StopInitiator;
|
|
110
|
+
/**
|
|
111
|
+
* Whether the result has been read by the LLM (foreground return or background nudge).
|
|
112
|
+
* cleanup() preserves terminal records until this is set, so a completed background
|
|
113
|
+
* agent whose nudge hasn't fired yet isn't evicted before the LLM reads the result.
|
|
114
|
+
*/
|
|
115
|
+
resultConsumed?: boolean;
|
|
106
116
|
}
|
|
107
117
|
|
|
108
118
|
/**
|
|
@@ -134,7 +134,7 @@ export function buildAgentActionsList(
|
|
|
134
134
|
input.onEscape = () => setActive(list);
|
|
135
135
|
setActive(input);
|
|
136
136
|
} else if (item.value === "stop") {
|
|
137
|
-
getManager()?.abort(record.id);
|
|
137
|
+
getManager()?.abort(record.id, "user");
|
|
138
138
|
ctx.ui.notify(`Stopped ${shortId}`, "info");
|
|
139
139
|
onClose();
|
|
140
140
|
}
|
|
@@ -186,7 +186,7 @@ export async function showRunningAgentsMenu(
|
|
|
186
186
|
agentList.onSelect = async (item) => {
|
|
187
187
|
if (item.value === "__stop-all") {
|
|
188
188
|
for (const r of running) {
|
|
189
|
-
getManager()?.abort(r.id);
|
|
189
|
+
getManager()?.abort(r.id, "user");
|
|
190
190
|
}
|
|
191
191
|
ctx.ui.notify(`Stopped ${running.length} agent(s)`, "info");
|
|
192
192
|
done(undefined);
|