@melihmucuk/pi-crew 1.0.15 → 1.0.17

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.
Files changed (39) hide show
  1. package/README.md +8 -8
  2. package/agents/code-reviewer.md +32 -102
  3. package/agents/oracle.md +23 -29
  4. package/agents/planner.md +35 -116
  5. package/agents/quality-reviewer.md +39 -124
  6. package/agents/scout.md +21 -38
  7. package/agents/worker.md +27 -72
  8. package/extension/agent-catalog.ts +369 -0
  9. package/extension/agent-config-fields.ts +359 -0
  10. package/extension/agent-discovery.ts +49 -717
  11. package/extension/bootstrap-session.ts +2 -2
  12. package/extension/index.ts +5 -3
  13. package/extension/integration/crew-tool-actions.ts +306 -0
  14. package/extension/integration/crew-tool-executor.ts +109 -0
  15. package/extension/integration/register-renderers.ts +2 -2
  16. package/extension/integration/register-tools.ts +11 -3
  17. package/extension/integration/tool-presentation.ts +3 -23
  18. package/extension/integration/tools/crew-abort.ts +14 -84
  19. package/extension/integration/tools/crew-done.ts +7 -26
  20. package/extension/integration/tools/crew-list.ts +5 -61
  21. package/extension/integration/tools/crew-respond.ts +8 -29
  22. package/extension/integration/tools/crew-spawn.ts +16 -57
  23. package/extension/message-delivery-policy.ts +22 -0
  24. package/extension/runtime/crew-runtime.ts +60 -223
  25. package/extension/runtime/overflow-recovery.ts +1 -1
  26. package/extension/runtime/{delivery-coordinator.ts → owner-session-coordinator.ts} +44 -37
  27. package/extension/runtime/subagent-lifecycle.ts +203 -0
  28. package/extension/runtime/subagent-registry.ts +50 -6
  29. package/extension/runtime/subagent-transitions.ts +100 -0
  30. package/extension/status-widget.ts +2 -2
  31. package/extension/subagent-messages.ts +9 -17
  32. package/package.json +13 -11
  33. package/prompts/pi-crew-plan.md +34 -137
  34. package/prompts/pi-crew-review.md +36 -112
  35. package/skills/pi-crew/REFERENCE.md +82 -0
  36. package/skills/pi-crew/SKILL.md +33 -104
  37. package/extension/integration/tools/tool-deps.ts +0 -16
  38. package/extension/integration.ts +0 -13
  39. package/extension/runtime/subagent-state.ts +0 -59
@@ -1,13 +1,11 @@
1
- import { Text } from "@mariozechner/pi-tui";
1
+ import { Text } from "@earendil-works/pi-tui";
2
2
  import { Type } from "typebox";
3
- import { discoverAgents } from "../../agent-discovery.js";
4
- import { STATUS_ICON, sendCrewListActiveWarning } from "../../subagent-messages.js";
5
- import type { CrewToolDeps } from "./tool-deps.js";
3
+ import type { CrewToolDeps } from "../crew-tool-executor.js";
6
4
 
7
5
  export function registerCrewListTool({
8
6
  pi,
9
- crew,
10
- notifyDiscoveryWarnings,
7
+ actions,
8
+ executor,
11
9
  }: CrewToolDeps): void {
12
10
  pi.registerTool({
13
11
  name: "crew_list",
@@ -23,61 +21,7 @@ export function registerCrewListTool({
23
21
  ],
24
22
 
25
23
  async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
26
- const { agents, warnings } = discoverAgents(ctx.cwd);
27
- notifyDiscoveryWarnings(ctx, warnings);
28
- const callerSessionId = ctx.sessionManager.getSessionId();
29
- const running = crew.getActiveSummariesForOwner(callerSessionId);
30
-
31
- const lines: string[] = [];
32
-
33
- lines.push("## Available Subagents");
34
- if (agents.length === 0) {
35
- lines.push(
36
- "No valid subagent definitions found. Add `.md` files to `<cwd>/.pi/agents/` or `~/.pi/agent/agents/`.",
37
- );
38
- } else {
39
- for (const agent of agents) {
40
- lines.push("");
41
- lines.push(`name: ${agent.name}`);
42
- lines.push(`description: ${agent.description}`);
43
- lines.push(`interactive: ${agent.interactive ? "true" : "false"}`);
44
- }
45
- }
46
-
47
- if (warnings.length > 0) {
48
- lines.push("");
49
- lines.push("## Ignored subagent definitions");
50
- for (const warning of warnings) {
51
- lines.push(`- ${warning.message} (${warning.filePath})`);
52
- }
53
- }
54
-
55
- lines.push("");
56
- lines.push("## Active Subagents");
57
- if (running.length === 0) {
58
- lines.push("No subagents currently active.");
59
- } else {
60
- for (const agent of running) {
61
- const icon = STATUS_ICON[agent.status] ?? "❓";
62
- lines.push("");
63
- lines.push(`id: ${agent.id}`);
64
- lines.push(`name: ${agent.agentName}`);
65
- lines.push(`status: ${icon} ${agent.status}`);
66
- }
67
- }
68
-
69
- const text = lines.join("\n");
70
-
71
- if (running.length > 0) {
72
- Promise.resolve().then(() => {
73
- sendCrewListActiveWarning(pi.sendMessage.bind(pi), {
74
- isIdle: ctx.isIdle(),
75
- triggerTurn: true,
76
- });
77
- });
78
- }
79
-
80
- return { content: [{ type: "text", text }], details: {} };
24
+ return executor.execute(ctx, (actionCtx) => actions.list(actionCtx));
81
25
  },
82
26
 
83
27
  renderCall(_args, theme, _context) {
@@ -1,14 +1,12 @@
1
1
  import { Type } from "typebox";
2
+ import { renderCrewCall } from "../tool-presentation.js";
2
3
  import {
3
- renderCrewCall,
4
- renderCrewResult,
5
- toolError,
6
- toolSuccess,
7
- } from "../tool-presentation.js";
8
- import type { CrewToolDeps } from "./tool-deps.js";
4
+ registerCrewActionTool,
5
+ type CrewToolDeps,
6
+ } from "../crew-tool-executor.js";
9
7
 
10
- export function registerCrewRespondTool({ pi, crew }: CrewToolDeps): void {
11
- pi.registerTool({
8
+ export function registerCrewRespondTool(deps: CrewToolDeps): void {
9
+ registerCrewActionTool<{ subagent_id: string; message: string }>(deps, {
12
10
  name: "crew_respond",
13
11
  label: "Respond to Crew",
14
12
  description:
@@ -27,22 +25,7 @@ export function registerCrewRespondTool({ pi, crew }: CrewToolDeps): void {
27
25
  "crew_respond: Use the waiting subagent ID from crew_spawn results or crew_list.",
28
26
  "crew_respond: The response arrives as a steering message; do not poll crew_list.",
29
27
  ],
30
-
31
- async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
32
- const callerSessionId = ctx.sessionManager.getSessionId();
33
- const { error } = crew.respond(
34
- params.subagent_id,
35
- params.message,
36
- callerSessionId,
37
- );
38
- if (error) return toolError(error);
39
-
40
- return toolSuccess(
41
- `Message sent to subagent ${params.subagent_id}. Response will be delivered as a steering message.`,
42
- { id: params.subagent_id, message: params.message },
43
- );
44
- },
45
-
28
+ action: (params, actionCtx) => deps.actions.respond(params, actionCtx),
46
29
  renderCall(args, theme, _context) {
47
30
  return renderCrewCall(
48
31
  theme,
@@ -51,9 +34,5 @@ export function registerCrewRespondTool({ pi, crew }: CrewToolDeps): void {
51
34
  args.message,
52
35
  );
53
36
  },
54
-
55
- renderResult(result, _options, theme, _context) {
56
- return renderCrewResult(result, theme);
57
- },
58
37
  });
59
- }
38
+ }
@@ -1,21 +1,13 @@
1
- import { getAgentDir } from "@mariozechner/pi-coding-agent";
1
+ import { getAgentDir } from "@earendil-works/pi-coding-agent";
2
2
  import { Type } from "typebox";
3
- import { discoverAgents } from "../../agent-discovery.js";
3
+ import { renderCrewCall } from "../tool-presentation.js";
4
4
  import {
5
- renderCrewCall,
6
- renderCrewResult,
7
- toolError,
8
- toolSuccess,
9
- } from "../tool-presentation.js";
10
- import type { CrewToolDeps } from "./tool-deps.js";
5
+ registerCrewActionTool,
6
+ type CrewToolDeps,
7
+ } from "../crew-tool-executor.js";
11
8
 
12
- export function registerCrewSpawnTool({
13
- pi,
14
- crew,
15
- extensionDir,
16
- notifyDiscoveryWarnings,
17
- }: CrewToolDeps): void {
18
- pi.registerTool({
9
+ export function registerCrewSpawnTool(deps: CrewToolDeps): void {
10
+ registerCrewActionTool<{ subagent: string; task: string }>(deps, {
19
11
  name: "crew_spawn",
20
12
  label: "Spawn Crew",
21
13
  description:
@@ -33,44 +25,15 @@ export function registerCrewSpawnTool({
33
25
  "crew_spawn: Results arrive as steering messages; do not poll crew_list or fabricate results.",
34
26
  "crew_spawn: Use the bundled pi-crew skill for detailed delegation patterns.",
35
27
  ],
36
-
37
- async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
38
- const { agents, warnings } = discoverAgents(ctx.cwd);
39
- notifyDiscoveryWarnings(ctx, warnings);
40
- const subagent = agents.find(
41
- (candidate) => candidate.name === params.subagent,
42
- );
43
-
44
- if (!subagent) {
45
- const available =
46
- agents.map((candidate) => candidate.name).join(", ") || "none";
47
- return toolError(
48
- `Unknown subagent: "${params.subagent}". Available: ${available}`,
49
- );
50
- }
51
-
52
- const ownerSessionId = ctx.sessionManager.getSessionId();
53
- const id = crew.spawn(
54
- subagent,
55
- params.task,
56
- ctx.cwd,
57
- ownerSessionId,
58
- {
59
- model: ctx.model,
60
- modelRegistry: ctx.modelRegistry,
61
- agentDir: getAgentDir(),
62
- parentSessionFile: ctx.sessionManager.getSessionFile(),
63
- onWarning: (msg) => ctx.ui.notify(msg, "warning"),
64
- },
65
- extensionDir,
66
- );
67
-
68
- return toolSuccess(
69
- `Subagent '${subagent.name}' spawned as ${id}. Result will be delivered as a steering message when done.`,
70
- { id, agentName: subagent.name, task: params.task },
71
- );
72
- },
73
-
28
+ action: (params, actionCtx, ctx) =>
29
+ deps.actions.spawn(params, {
30
+ ...actionCtx,
31
+ model: ctx.model,
32
+ modelRegistry: ctx.modelRegistry,
33
+ agentDir: getAgentDir(),
34
+ parentSessionFile: ctx.sessionManager.getSessionFile(),
35
+ onWarning: (msg) => ctx.ui.notify(msg, "warning"),
36
+ }),
74
37
  renderCall(args, theme, _context) {
75
38
  return renderCrewCall(
76
39
  theme,
@@ -79,9 +42,5 @@ export function registerCrewSpawnTool({
79
42
  args.task,
80
43
  );
81
44
  },
82
-
83
- renderResult(result, _options, theme, _context) {
84
- return renderCrewResult(result, theme);
85
- },
86
45
  });
87
46
  }
@@ -0,0 +1,22 @@
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
+
3
+ export type SendMessageFn = ExtensionAPI["sendMessage"];
4
+ type Message = Parameters<SendMessageFn>[0];
5
+
6
+ interface DeliveryOptions {
7
+ isIdle: boolean;
8
+ triggerTurn: boolean;
9
+ }
10
+
11
+ export function sendWithDeliveryPolicy(
12
+ message: Message,
13
+ sendMessage: SendMessageFn,
14
+ opts: DeliveryOptions,
15
+ ): void {
16
+ sendMessage(
17
+ message,
18
+ opts.isIdle
19
+ ? { triggerTurn: opts.triggerTurn }
20
+ : { deliverAs: "steer", triggerTurn: opts.triggerTurn },
21
+ );
22
+ }
@@ -1,23 +1,25 @@
1
- import type { AgentMessage } from "@mariozechner/pi-agent-core";
2
- import type { Api, AssistantMessage, Model } from "@mariozechner/pi-ai";
3
- import type { AgentSession, ModelRegistry } from "@mariozechner/pi-coding-agent";
1
+ import type { Api, Model } from "@earendil-works/pi-ai";
2
+ import type { ModelRegistry } from "@earendil-works/pi-coding-agent";
4
3
  import type { AgentConfig } from "../agent-discovery.js";
5
4
  import type { BootstrapContext } from "../bootstrap-session.js";
6
- import { bootstrapSession } from "../bootstrap-session.js";
7
- import type { SubagentStatus } from "../subagent-messages.js";
8
- import { type ActiveRuntimeBinding, DeliveryCoordinator } from "./delivery-coordinator.js";
9
- import { runPromptWithOverflowRecovery } from "./overflow-recovery.js";
10
- import { SubagentRegistry } from "./subagent-registry.js";
5
+ import { type ActiveRuntimeBinding, OwnerSessionCoordinator } from "./owner-session-coordinator.js";
11
6
  import {
12
7
  type ActiveAgentSummary,
8
+ SubagentRegistry,
13
9
  type SubagentState,
14
- isAbortableStatus,
15
- isAborted,
16
- } from "./subagent-state.js";
10
+ } from "./subagent-registry.js";
11
+ import { SubagentLifecycle } from "./subagent-lifecycle.js";
12
+ import {
13
+ type SettledSubagentStatus,
14
+ canAbortSubagent,
15
+ settleSubagent,
16
+ startSubagentResponse,
17
+ validateSubagentDone,
18
+ } from "./subagent-transitions.js";
17
19
 
18
20
  export type {
19
21
  ActiveAgentSummary,
20
- } from "./subagent-state.js";
22
+ } from "./subagent-registry.js";
21
23
 
22
24
  export interface AbortOwnedResult {
23
25
  abortedIds: string[];
@@ -46,63 +48,6 @@ function toBootstrapContext(ctx: SpawnContext): BootstrapContext {
46
48
  };
47
49
  }
48
50
 
49
- interface PromptOutcome {
50
- status: Extract<SubagentStatus, "done" | "waiting" | "error" | "aborted">;
51
- result?: string;
52
- error?: string;
53
- }
54
-
55
- function getLastAssistantMessage(
56
- messages: AgentMessage[],
57
- ): AssistantMessage | undefined {
58
- for (let i = messages.length - 1; i >= 0; i--) {
59
- const msg = messages[i];
60
- if (msg.role === "assistant") {
61
- return msg as AssistantMessage;
62
- }
63
- }
64
- return undefined;
65
- }
66
-
67
- function getAssistantText(
68
- message: AssistantMessage | undefined,
69
- ): string | undefined {
70
- if (!message) return undefined;
71
-
72
- const texts: string[] = [];
73
- for (const part of message.content) {
74
- if (part.type === "text") {
75
- texts.push(part.text);
76
- }
77
- }
78
-
79
- return texts.length > 0 ? texts.join("\n") : undefined;
80
- }
81
-
82
- function getPromptOutcome(state: SubagentState): PromptOutcome {
83
- const lastAssistant = getLastAssistantMessage(state.session!.messages);
84
- const text = getAssistantText(lastAssistant);
85
-
86
- if (lastAssistant?.stopReason === "error") {
87
- return {
88
- status: "error",
89
- error: lastAssistant.errorMessage ?? text ?? "(no output)",
90
- };
91
- }
92
-
93
- if (lastAssistant?.stopReason === "aborted") {
94
- return {
95
- status: "aborted",
96
- error: lastAssistant.errorMessage ?? text ?? "(no output)",
97
- };
98
- }
99
-
100
- return {
101
- status: state.agentConfig.interactive ? "waiting" : "done",
102
- result: text ?? "(no output)",
103
- };
104
- }
105
-
106
51
  /**
107
52
  * Process-level singleton that owns all durable subagent state.
108
53
  *
@@ -113,11 +58,26 @@ function getPromptOutcome(state: SubagentState): PromptOutcome {
113
58
  */
114
59
  class CrewRuntime {
115
60
  private readonly registry = new SubagentRegistry();
116
- private readonly delivery = new DeliveryCoordinator();
61
+ private readonly ownerSessions: OwnerSessionCoordinator;
62
+ private readonly lifecycle: SubagentLifecycle;
117
63
 
118
64
  // Per-session refresh callbacks, keyed by ownerSessionId
119
65
  private readonly refreshCallbacks = new Map<string, () => void>();
120
66
 
67
+ constructor() {
68
+ this.ownerSessions = new OwnerSessionCoordinator({
69
+ countRunningForOwner: (ownerSessionId, excludeId) =>
70
+ this.registry.countRunningForOwner(ownerSessionId, excludeId),
71
+ onRefreshOwnerSession: (ownerSessionId) => this.refreshWidgetFor(ownerSessionId),
72
+ });
73
+ this.lifecycle = new SubagentLifecycle({
74
+ isCurrent: (state) => this.registry.hasState(state),
75
+ onProgress: (ownerSessionId) => this.ownerSessions.refresh(ownerSessionId),
76
+ onSettled: (state, status, outcome) =>
77
+ this.settleAgent(state, status, outcome),
78
+ });
79
+ }
80
+
121
81
  private refreshWidgetFor(sessionId: string): void {
122
82
  this.refreshCallbacks.get(sessionId)?.();
123
83
  }
@@ -129,16 +89,12 @@ class CrewRuntime {
129
89
  if (refreshWidget) {
130
90
  this.refreshCallbacks.set(binding.sessionId, refreshWidget);
131
91
  }
132
- this.delivery.activateSession(
133
- binding,
134
- (ownerSessionId, excludeId) =>
135
- this.registry.countRunningForOwner(ownerSessionId, excludeId),
136
- );
92
+ this.ownerSessions.activateSession(binding);
137
93
  refreshWidget?.();
138
94
  }
139
95
 
140
96
  deactivateSession(sessionId: string): void {
141
- this.delivery.deactivateSession(sessionId);
97
+ this.ownerSessions.deactivateSession(sessionId);
142
98
  this.refreshCallbacks.delete(sessionId);
143
99
  }
144
100
 
@@ -151,57 +107,24 @@ class CrewRuntime {
151
107
  extensionResolvedPath: string,
152
108
  ): string {
153
109
  const state = this.registry.create(agentConfig, task, ownerSessionId);
154
- this.refreshWidgetFor(ownerSessionId);
155
- void this.spawnSession(
156
- state,
110
+ this.ownerSessions.refresh(ownerSessionId);
111
+ this.lifecycle.start(state, {
157
112
  cwd,
158
- ctx,
113
+ ctx: toBootstrapContext(ctx),
159
114
  extensionResolvedPath,
160
- );
161
- return state.id;
162
- }
163
-
164
- private attachSessionListeners(
165
- state: SubagentState,
166
- session: AgentSession,
167
- ): void {
168
- state.unsubscribe = session.subscribe((event) => {
169
- if (event.type !== "turn_end") return;
170
-
171
- state.turns++;
172
- const msg = event.message;
173
- if (msg.role === "assistant") {
174
- const assistantMsg = msg as AssistantMessage;
175
- state.contextTokens = assistantMsg.usage.totalTokens;
176
- state.model = assistantMsg.model;
177
- }
178
- this.refreshWidgetFor(state.ownerSessionId);
115
+ onWarning: ctx.onWarning,
179
116
  });
180
- }
181
-
182
- private attachSpawnedSession(
183
- state: SubagentState,
184
- session: AgentSession,
185
- ): boolean {
186
- if (!this.registry.hasState(state)) {
187
- session.dispose();
188
- return false;
189
- }
190
-
191
- state.session = session;
192
- return true;
117
+ return state.id;
193
118
  }
194
119
 
195
120
  private settleAgent(
196
121
  state: SubagentState,
197
- nextStatus: SubagentStatus,
122
+ nextStatus: SettledSubagentStatus,
198
123
  opts: { result?: string; error?: string },
199
124
  ): void {
200
- state.status = nextStatus;
201
- state.result = opts.result;
202
- state.error = opts.error;
125
+ settleSubagent(state, nextStatus, opts);
203
126
 
204
- this.delivery.deliver(
127
+ this.ownerSessions.deliver(
205
128
  state.ownerSessionId,
206
129
  {
207
130
  id: state.id,
@@ -211,14 +134,12 @@ class CrewRuntime {
211
134
  result: state.result,
212
135
  error: state.error,
213
136
  },
214
- (ownerSessionId, excludeId) =>
215
- this.registry.countRunningForOwner(ownerSessionId, excludeId),
216
137
  );
217
138
 
218
139
  if (state.status !== "waiting") {
219
140
  this.disposeAgent(state);
220
141
  } else {
221
- this.refreshWidgetFor(state.ownerSessionId);
142
+ this.ownerSessions.refresh(state.ownerSessionId);
222
143
  }
223
144
  }
224
145
 
@@ -227,128 +148,44 @@ class CrewRuntime {
227
148
  state.promptAbortController = undefined;
228
149
  state.session?.dispose();
229
150
  this.registry.delete(state.id);
230
- this.refreshWidgetFor(state.ownerSessionId);
151
+ this.ownerSessions.refresh(state.ownerSessionId);
231
152
  }
232
153
 
233
- private async runPromptCycle(
234
- state: SubagentState,
235
- prompt: string,
236
- ): Promise<void> {
237
- if (isAborted(state)) return;
238
-
239
- const abortController = new AbortController();
240
- state.promptAbortController = abortController;
241
-
242
- try {
243
- const recovery = await runPromptWithOverflowRecovery(
244
- state.session!,
245
- prompt,
246
- abortController.signal,
247
- );
248
- if (isAborted(state)) return;
249
-
250
- const outcome = getPromptOutcome(state);
251
-
252
- if (recovery === "failed" && outcome.status !== "error") {
253
- this.settleAgent(state, "error", {
254
- error: "Context overflow recovery failed",
255
- });
256
- return;
257
- }
258
-
259
- this.settleAgent(state, outcome.status, outcome);
260
- } catch (err) {
261
- if (isAborted(state)) return;
262
-
263
- const error = err instanceof Error ? err.message : String(err);
264
- this.settleAgent(state, "error", { error });
265
- } finally {
266
- state.promptAbortController = undefined;
267
- }
268
- }
269
-
270
- private async spawnSession(
271
- state: SubagentState,
272
- cwd: string,
273
- ctx: SpawnContext,
274
- extensionResolvedPath: string,
275
- ): Promise<void> {
276
- try {
277
- if (isAborted(state)) return;
278
-
279
- const { session, warnings } = await bootstrapSession({
280
- agentConfig: state.agentConfig,
281
- cwd,
282
- ctx: toBootstrapContext(ctx),
283
- extensionResolvedPath,
284
- });
285
-
286
- // Emit bootstrap warnings to UI
287
- for (const warning of warnings) {
288
- ctx.onWarning?.(warning);
289
- }
290
-
291
- if (!this.attachSpawnedSession(state, session)) return;
292
-
293
- this.attachSessionListeners(state, session);
294
- await this.runPromptCycle(state, state.task);
295
- } catch (err) {
296
- if (isAborted(state)) return;
297
-
298
- if (state.status === "running") {
299
- const error = err instanceof Error ? err.message : String(err);
300
- this.settleAgent(state, "error", { error });
301
- }
302
- }
303
- }
304
154
 
305
155
  respond(
306
156
  id: string,
307
157
  message: string,
308
158
  callerSessionId: string,
309
159
  ): { error?: string } {
310
- const state = this.registry.get(id);
311
- if (!state) return { error: `No subagent with id "${id}"` };
312
- if (state.ownerSessionId !== callerSessionId) {
313
- return { error: `Subagent "${id}" belongs to a different session` };
314
- }
315
- if (state.status !== "waiting") {
316
- return {
317
- error: `Subagent "${id}" is not waiting for a response (status: ${state.status})`,
318
- };
319
- }
320
- if (!state.session)
321
- return { error: `Subagent "${id}" has no active session` };
160
+ const transition = startSubagentResponse(
161
+ this.registry.get(id),
162
+ id,
163
+ callerSessionId,
164
+ );
165
+ if (!transition.ok) return { error: transition.error };
322
166
 
323
- state.status = "running";
324
- this.refreshWidgetFor(state.ownerSessionId);
325
- void this.runPromptCycle(state, message);
167
+ this.ownerSessions.refresh(transition.state.ownerSessionId);
168
+ this.lifecycle.respond(transition.state, message);
326
169
  return {};
327
170
  }
328
171
 
329
172
  done(id: string, callerSessionId: string): { error?: string } {
330
- const state = this.registry.get(id);
331
- if (!state) return { error: `No active subagent with id "${id}"` };
332
- if (state.ownerSessionId !== callerSessionId) {
333
- return { error: `Subagent "${id}" belongs to a different session` };
334
- }
335
- if (state.status !== "waiting") {
336
- return { error: `Subagent "${id}" is not in waiting state` };
337
- }
173
+ const transition = validateSubagentDone(
174
+ this.registry.get(id),
175
+ id,
176
+ callerSessionId,
177
+ );
178
+ if (!transition.ok) return { error: transition.error };
338
179
 
339
- this.disposeAgent(state);
180
+ this.disposeAgent(transition.state);
340
181
  return {};
341
182
  }
342
183
 
343
184
  abort(id: string, opts: AbortOptions): boolean {
344
185
  const state = this.registry.get(id);
345
- if (!state || !isAbortableStatus(state.status)) return false;
186
+ if (!canAbortSubagent(state)) return false;
346
187
 
347
- state.promptAbortController?.abort();
348
- state.promptAbortController = undefined;
349
- state.session?.abortCompaction();
350
- state.session?.abortRetry();
351
- state.session?.abort().catch(() => {});
188
+ this.lifecycle.abortPrompt(state);
352
189
  this.settleAgent(state, "aborted", { error: opts.reason });
353
190
  return true;
354
191
  }
@@ -369,7 +206,7 @@ class CrewRuntime {
369
206
 
370
207
  for (const id of uniqueIds) {
371
208
  const state = this.registry.get(id);
372
- if (!state || !isAbortableStatus(state.status)) {
209
+ if (!canAbortSubagent(state)) {
373
210
  result.missingIds.push(id);
374
211
  continue;
375
212
  }
@@ -1,4 +1,4 @@
1
- import type { AgentSession, AgentSessionEvent } from "@mariozechner/pi-coding-agent";
1
+ import type { AgentSession, AgentSessionEvent } from "@earendil-works/pi-coding-agent";
2
2
 
3
3
  const OVERFLOW_RECOVERY_TIMEOUT_MS = 120_000;
4
4