pi-subagents-lite 1.4.6 → 1.4.7

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-subagents-lite",
3
- "version": "1.4.6",
3
+ "version": "1.4.7",
4
4
  "description": "Lightweight sub-agents for pi — spawn specialized agents with isolated sessions, tools, and models.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -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
  }
@@ -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
- const statusNote = getStatusNote(record.lifecycle.status);
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
  // ============================================================================
@@ -234,7 +235,7 @@ export class SpawnCoordinator {
234
235
  pi.sendMessage(
235
236
  {
236
237
  customType: "subagent-result",
237
- content: `[Subagent "${record.display.type}" ${record.lifecycle.status}]\n\n${record.result ?? ""} ${getStatusNote(record.lifecycle.status)}`,
238
+ content: `[Subagent "${record.display.type}" ${record.id.slice(0, SHORT_ID_LENGTH)} ${record.lifecycle.status}]\n\n${formatResultContent(record)}`,
238
239
  details,
239
240
  display: true,
240
241
  },
@@ -1,10 +1,20 @@
1
- const NOTES: Record<string, string> = {
2
- stopped: "STOPPED BY THE USER before completion — output is partial; the task was NOT finished",
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
- export function getStatusNote(status: string): string {
8
- const note = NOTES[status];
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,7 @@ export interface AgentLifecycle {
103
106
  status: AgentStatus;
104
107
  startedAt: number;
105
108
  completedAt?: number;
109
+ stoppedBy?: StopInitiator;
106
110
  }
107
111
 
108
112
  /**
@@ -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);