@melihmucuk/pi-crew 1.0.17 → 1.0.18

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 (31) hide show
  1. package/extension/catalog.ts +543 -0
  2. package/extension/crew.ts +383 -0
  3. package/extension/index.ts +5 -6
  4. package/extension/subagent-session.ts +270 -0
  5. package/extension/tools.ts +323 -0
  6. package/extension/ui.ts +309 -0
  7. package/package.json +1 -1
  8. package/extension/agent-catalog.ts +0 -369
  9. package/extension/agent-config-fields.ts +0 -359
  10. package/extension/agent-discovery.ts +0 -123
  11. package/extension/bootstrap-session.ts +0 -131
  12. package/extension/integration/crew-tool-actions.ts +0 -306
  13. package/extension/integration/crew-tool-executor.ts +0 -109
  14. package/extension/integration/register-renderers.ts +0 -77
  15. package/extension/integration/register-tools.ts +0 -47
  16. package/extension/integration/tool-presentation.ts +0 -30
  17. package/extension/integration/tools/crew-abort.ts +0 -56
  18. package/extension/integration/tools/crew-done.ts +0 -27
  19. package/extension/integration/tools/crew-list.ts +0 -36
  20. package/extension/integration/tools/crew-respond.ts +0 -38
  21. package/extension/integration/tools/crew-spawn.ts +0 -46
  22. package/extension/message-delivery-policy.ts +0 -22
  23. package/extension/runtime/crew-runtime.ts +0 -263
  24. package/extension/runtime/owner-session-coordinator.ts +0 -138
  25. package/extension/runtime/subagent-lifecycle.ts +0 -203
  26. package/extension/runtime/subagent-registry.ts +0 -122
  27. package/extension/runtime/subagent-transitions.ts +0 -100
  28. package/extension/status-widget.ts +0 -107
  29. package/extension/subagent-messages.ts +0 -116
  30. package/extension/tool-registry.ts +0 -19
  31. /package/extension/{runtime/overflow-recovery.ts → overflow-recovery.ts} +0 -0
@@ -1,77 +0,0 @@
1
- import {
2
- type ExtensionAPI,
3
- getMarkdownTheme,
4
- } from "@earendil-works/pi-coding-agent";
5
- import { Box, Markdown, Text } from "@earendil-works/pi-tui";
6
- import {
7
- type CrewResultMessageDetails,
8
- STATUS_ICON,
9
- getCrewResultTitle,
10
- } from "../subagent-messages.js";
11
-
12
- type MessageRenderer = Parameters<ExtensionAPI["registerMessageRenderer"]>[1];
13
- type MessageRendererTheme = Parameters<MessageRenderer>[2];
14
-
15
- function getStatusColor(status: CrewResultMessageDetails["status"]): "success" | "error" | "warning" | "muted" {
16
- switch (status) {
17
- case "done":
18
- return "success";
19
- case "error":
20
- case "aborted":
21
- return "error";
22
- case "running":
23
- case "waiting":
24
- return "warning";
25
- default:
26
- return "muted";
27
- }
28
- }
29
-
30
- function renderWarningMessage(content: unknown, theme: MessageRendererTheme): Box {
31
- const box = new Box(1, 1, (text) => theme.bg("customMessageBg", text));
32
- box.addChild(new Text(theme.fg("warning", String(content ?? "")), 0, 0));
33
- return box;
34
- }
35
-
36
- export function registerCrewMessageRenderers(pi: ExtensionAPI): void {
37
- pi.registerMessageRenderer("crew-result", (message, { expanded }, theme) => {
38
- const details = message.details as CrewResultMessageDetails | undefined;
39
- const title = details ? getCrewResultTitle(details) : "Subagent update";
40
- const icon = details
41
- ? theme.fg(getStatusColor(details.status), STATUS_ICON[details.status])
42
- : theme.fg("muted", "ℹ");
43
- const header = `${icon} ${theme.fg("toolTitle", theme.bold(title))}`;
44
- const body = details?.body ?? (!details && message.content ? String(message.content) : undefined);
45
-
46
- const box = new Box(1, 1, (text) => theme.bg("customMessageBg", text));
47
- box.addChild(new Text(header, 0, 0));
48
-
49
- if (details?.sessionFile) {
50
- box.addChild(new Text(theme.fg("muted", `📁 ${details.sessionFile}`), 0, 0));
51
- }
52
-
53
- if (body) {
54
- if (expanded) {
55
- box.addChild(new Text("", 0, 0));
56
- box.addChild(new Markdown(body, 0, 0, getMarkdownTheme()));
57
- } else {
58
- const lines = body.split("\n");
59
- const preview = lines.slice(0, 5).join("\n");
60
- box.addChild(new Text(theme.fg("dim", preview), 0, 0));
61
- if (lines.length > 5) {
62
- box.addChild(new Text(theme.fg("muted", "(Ctrl+O to expand)"), 0, 0));
63
- }
64
- }
65
- }
66
-
67
- return box;
68
- });
69
-
70
- pi.registerMessageRenderer("crew-remaining", (message, _options, theme) => {
71
- return renderWarningMessage(message.content, theme);
72
- });
73
-
74
- pi.registerMessageRenderer("crew-list-warning", (message, _options, theme) => {
75
- return renderWarningMessage(message.content, theme);
76
- });
77
- }
@@ -1,47 +0,0 @@
1
- import type {
2
- ExtensionAPI,
3
- ExtensionContext,
4
- } from "@earendil-works/pi-coding-agent";
5
- import { discoverAgents, type AgentDiscoveryWarning } from "../agent-discovery.js";
6
- import type { CrewRuntime } from "../runtime/crew-runtime.js";
7
- import { createCrewToolActions } from "./crew-tool-actions.js";
8
- import { CrewToolExecutor } from "./crew-tool-executor.js";
9
- import { registerCrewAbortTool } from "./tools/crew-abort.js";
10
- import { registerCrewDoneTool } from "./tools/crew-done.js";
11
- import { registerCrewListTool } from "./tools/crew-list.js";
12
- import { registerCrewRespondTool } from "./tools/crew-respond.js";
13
- import { registerCrewSpawnTool } from "./tools/crew-spawn.js";
14
-
15
- export function registerCrewTools(
16
- pi: ExtensionAPI,
17
- crew: CrewRuntime,
18
- extensionDir: string,
19
- ): void {
20
- const shownDiscoveryWarnings = new Set<string>();
21
-
22
- const notifyDiscoveryWarnings = (
23
- ctx: ExtensionContext,
24
- warnings: AgentDiscoveryWarning[],
25
- ) => {
26
- if (!ctx.hasUI) return;
27
- for (const warning of warnings) {
28
- const key = `${warning.filePath}:${warning.message}`;
29
- if (shownDiscoveryWarnings.has(key)) continue;
30
- shownDiscoveryWarnings.add(key);
31
- ctx.ui.notify(`${warning.message} (${warning.filePath})`, "error");
32
- }
33
- };
34
-
35
- const actions = createCrewToolActions({
36
- crew,
37
- discoverAgents,
38
- extensionDir,
39
- });
40
- const executor = new CrewToolExecutor({ pi, notifyDiscoveryWarnings });
41
- const deps = { pi, actions, executor };
42
- registerCrewListTool(deps);
43
- registerCrewSpawnTool(deps);
44
- registerCrewAbortTool(deps);
45
- registerCrewRespondTool(deps);
46
- registerCrewDoneTool(deps);
47
- }
@@ -1,30 +0,0 @@
1
- import type { AgentToolResult } from "@earendil-works/pi-agent-core";
2
- import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
3
- import { Box, Text } from "@earendil-works/pi-tui";
4
-
5
- export type ToolTheme = Parameters<Exclude<Parameters<ExtensionAPI["registerTool"]>[0]["renderCall"], undefined>>[1];
6
- export type ToolResult = AgentToolResult<unknown>;
7
-
8
- export function renderCrewCall(
9
- theme: ToolTheme,
10
- name: string,
11
- id: string,
12
- preview?: string,
13
- ): Box {
14
- const box = new Box(1, 1);
15
- box.addChild(new Text(theme.fg("toolTitle", theme.bold(`${name} `)) + theme.fg("accent", id), 0, 0));
16
- if (preview) {
17
- box.addChild(new Text(theme.fg("dim", preview), 0, 0));
18
- }
19
- return box;
20
- }
21
-
22
- export function renderCrewResult(
23
- result: ToolResult,
24
- theme: ToolTheme,
25
- ): Text {
26
- const text = result.content[0];
27
- const details = result.details as { error?: boolean } | undefined;
28
- const content = text?.type === "text" && text.text ? text.text : "(no output)";
29
- return new Text(details?.error ? theme.fg("error", content) : theme.fg("success", content), 0, 0);
30
- }
@@ -1,56 +0,0 @@
1
- import { Type } from "typebox";
2
- import { renderCrewCall } from "../tool-presentation.js";
3
- import {
4
- registerCrewActionTool,
5
- type CrewToolDeps,
6
- } from "../crew-tool-executor.js";
7
-
8
- type CrewAbortParams = {
9
- subagent_id?: string;
10
- subagent_ids?: string[];
11
- all?: boolean;
12
- };
13
-
14
- export function registerCrewAbortTool(deps: CrewToolDeps): void {
15
- registerCrewActionTool<CrewAbortParams>(deps, {
16
- name: "crew_abort",
17
- label: "Abort Crew",
18
- description:
19
- "Abort one, many, or all active subagents owned by the current session.",
20
- parameters: Type.Object({
21
- subagent_id: Type.Optional(
22
- Type.String({ description: "Single subagent ID to abort" }),
23
- ),
24
- subagent_ids: Type.Optional(
25
- Type.Array(Type.String(), {
26
- minItems: 1,
27
- description: "Multiple subagent IDs to abort",
28
- }),
29
- ),
30
- all: Type.Optional(
31
- Type.Boolean({
32
- description: "Abort all active subagents owned by the current session",
33
- }),
34
- ),
35
- }),
36
- promptSnippet: "Abort one, many, or all active subagents from this session.",
37
- promptGuidelines: [
38
- "crew_abort: Abort one, many, or all active subagents owned by this session.",
39
- "crew_abort: Provide exactly one mode: subagent_id, subagent_ids, or all=true.",
40
- "crew_abort: Use only when delegated work is obsolete, wrong, or explicitly cancelled.",
41
- ],
42
- action: (params, actionCtx) => deps.actions.abort(params, actionCtx),
43
- renderCall(args, theme, _context) {
44
- if (args.all) {
45
- return renderCrewCall(theme, "crew_abort", "all");
46
- }
47
-
48
- if (args.subagent_id) {
49
- return renderCrewCall(theme, "crew_abort", args.subagent_id);
50
- }
51
-
52
- const count = Array.isArray(args.subagent_ids) ? args.subagent_ids.length : 0;
53
- return renderCrewCall(theme, "crew_abort", `${count} ids`);
54
- },
55
- });
56
- }
@@ -1,27 +0,0 @@
1
- import { Type } from "typebox";
2
- import { renderCrewCall } from "../tool-presentation.js";
3
- import {
4
- registerCrewActionTool,
5
- type CrewToolDeps,
6
- } from "../crew-tool-executor.js";
7
-
8
- export function registerCrewDoneTool(deps: CrewToolDeps): void {
9
- registerCrewActionTool<{ subagent_id: string }>(deps, {
10
- name: "crew_done",
11
- label: "Done with Crew",
12
- description:
13
- "Close an interactive subagent session. Use when you no longer need to interact with the subagent.",
14
- parameters: Type.Object({
15
- subagent_id: Type.String({ description: "ID of the subagent to close" }),
16
- }),
17
- promptSnippet: "Close an interactive subagent session when done.",
18
- promptGuidelines: [
19
- "crew_done: Close a waiting interactive subagent owned by this session.",
20
- "crew_done: Use only when no further follow-up is needed; otherwise use crew_respond.",
21
- ],
22
- action: (params, actionCtx) => deps.actions.done(params, actionCtx),
23
- renderCall(args, theme, _context) {
24
- return renderCrewCall(theme, "crew_done", args.subagent_id || "...");
25
- },
26
- });
27
- }
@@ -1,36 +0,0 @@
1
- import { Text } from "@earendil-works/pi-tui";
2
- import { Type } from "typebox";
3
- import type { CrewToolDeps } from "../crew-tool-executor.js";
4
-
5
- export function registerCrewListTool({
6
- pi,
7
- actions,
8
- executor,
9
- }: CrewToolDeps): void {
10
- pi.registerTool({
11
- name: "crew_list",
12
- label: "List Crew",
13
- description:
14
- "List available subagent definitions and currently running subagents with their status. Use only to discover which subagents exist or to get a one-time status snapshot. Do NOT call this repeatedly to check if a subagent has finished — results are delivered automatically as steering messages.",
15
- parameters: Type.Object({}),
16
- promptSnippet: "List subagent definitions and active subagents",
17
- promptGuidelines: [
18
- "crew_list: List available subagents and active subagents owned by this session.",
19
- "crew_list: Use before crew_spawn to discover names, descriptions, and interactive status.",
20
- "crew_list: Use only for discovery or a requested status snapshot; do not poll for completion.",
21
- ],
22
-
23
- async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
24
- return executor.execute(ctx, (actionCtx) => actions.list(actionCtx));
25
- },
26
-
27
- renderCall(_args, theme, _context) {
28
- return new Text(theme.fg("toolTitle", theme.bold("crew_list")), 0, 0);
29
- },
30
-
31
- renderResult(result, _options, _theme, _context) {
32
- const text = result.content[0];
33
- return new Text(text?.type === "text" ? text.text : "(no output)", 0, 0);
34
- },
35
- });
36
- }
@@ -1,38 +0,0 @@
1
- import { Type } from "typebox";
2
- import { renderCrewCall } from "../tool-presentation.js";
3
- import {
4
- registerCrewActionTool,
5
- type CrewToolDeps,
6
- } from "../crew-tool-executor.js";
7
-
8
- export function registerCrewRespondTool(deps: CrewToolDeps): void {
9
- registerCrewActionTool<{ subagent_id: string; message: string }>(deps, {
10
- name: "crew_respond",
11
- label: "Respond to Crew",
12
- description:
13
- "Send a follow-up message to an interactive subagent that is waiting for a response.",
14
- parameters: Type.Object({
15
- subagent_id: Type.String({
16
- description:
17
- "ID of the waiting subagent (from crew_list or crew_spawn result)",
18
- }),
19
- message: Type.String({ description: "Message to send to the subagent" }),
20
- }),
21
- promptSnippet:
22
- "Send a follow-up message to a waiting interactive subagent.",
23
- promptGuidelines: [
24
- "crew_respond: Send a complete follow-up message to a waiting interactive subagent.",
25
- "crew_respond: Use the waiting subagent ID from crew_spawn results or crew_list.",
26
- "crew_respond: The response arrives as a steering message; do not poll crew_list.",
27
- ],
28
- action: (params, actionCtx) => deps.actions.respond(params, actionCtx),
29
- renderCall(args, theme, _context) {
30
- return renderCrewCall(
31
- theme,
32
- "crew_respond",
33
- args.subagent_id || "...",
34
- args.message,
35
- );
36
- },
37
- });
38
- }
@@ -1,46 +0,0 @@
1
- import { getAgentDir } from "@earendil-works/pi-coding-agent";
2
- import { Type } from "typebox";
3
- import { renderCrewCall } from "../tool-presentation.js";
4
- import {
5
- registerCrewActionTool,
6
- type CrewToolDeps,
7
- } from "../crew-tool-executor.js";
8
-
9
- export function registerCrewSpawnTool(deps: CrewToolDeps): void {
10
- registerCrewActionTool<{ subagent: string; task: string }>(deps, {
11
- name: "crew_spawn",
12
- label: "Spawn Crew",
13
- description:
14
- "Spawn a non-blocking subagent that runs in an isolated session. The subagent works independently while your session stays interactive. Results are delivered back to your session as steering messages.",
15
- parameters: Type.Object({
16
- subagent: Type.String({ description: "Subagent name from crew_list" }),
17
- task: Type.String({ description: "Task to delegate to the subagent" }),
18
- }),
19
- promptSnippet:
20
- "Spawn a non-blocking subagent. Use crew_list first to see available subagents.",
21
- promptGuidelines: [
22
- "crew_spawn: Spawn a discovered subagent for one clearly delegated, self-contained task.",
23
- "crew_spawn: Include only needed context: constraints, relevant files, acceptance criteria, and expected output.",
24
- "crew_spawn: After spawning, ownership transfers to the subagent; do not work on that task yourself.",
25
- "crew_spawn: Results arrive as steering messages; do not poll crew_list or fabricate results.",
26
- "crew_spawn: Use the bundled pi-crew skill for detailed delegation patterns.",
27
- ],
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
- }),
37
- renderCall(args, theme, _context) {
38
- return renderCrewCall(
39
- theme,
40
- "crew_spawn",
41
- args.subagent || "...",
42
- args.task,
43
- );
44
- },
45
- });
46
- }
@@ -1,22 +0,0 @@
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,263 +0,0 @@
1
- import type { Api, Model } from "@earendil-works/pi-ai";
2
- import type { ModelRegistry } from "@earendil-works/pi-coding-agent";
3
- import type { AgentConfig } from "../agent-discovery.js";
4
- import type { BootstrapContext } from "../bootstrap-session.js";
5
- import { type ActiveRuntimeBinding, OwnerSessionCoordinator } from "./owner-session-coordinator.js";
6
- import {
7
- type ActiveAgentSummary,
8
- SubagentRegistry,
9
- type SubagentState,
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";
19
-
20
- export type {
21
- ActiveAgentSummary,
22
- } from "./subagent-registry.js";
23
-
24
- export interface AbortOwnedResult {
25
- abortedIds: string[];
26
- missingIds: string[];
27
- foreignIds: string[];
28
- }
29
-
30
- interface AbortOptions {
31
- reason: string;
32
- }
33
-
34
- export interface SpawnContext {
35
- model: Model<Api> | undefined;
36
- modelRegistry: ModelRegistry;
37
- agentDir: string;
38
- parentSessionFile?: string;
39
- onWarning?: (message: string) => void;
40
- }
41
-
42
- function toBootstrapContext(ctx: SpawnContext): BootstrapContext {
43
- return {
44
- model: ctx.model,
45
- modelRegistry: ctx.modelRegistry,
46
- agentDir: ctx.agentDir,
47
- parentSessionFile: ctx.parentSessionFile,
48
- };
49
- }
50
-
51
- /**
52
- * Process-level singleton that owns all durable subagent state.
53
- *
54
- * This survives extension instance replacement caused by runtime
55
- * teardown/recreation on /resume, /new, /fork (pi 0.65.0+).
56
- * Each new extension instance rebinds delivery and widget hooks
57
- * via activateSession/deactivateSession.
58
- */
59
- class CrewRuntime {
60
- private readonly registry = new SubagentRegistry();
61
- private readonly ownerSessions: OwnerSessionCoordinator;
62
- private readonly lifecycle: SubagentLifecycle;
63
-
64
- // Per-session refresh callbacks, keyed by ownerSessionId
65
- private readonly refreshCallbacks = new Map<string, () => void>();
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
-
81
- private refreshWidgetFor(sessionId: string): void {
82
- this.refreshCallbacks.get(sessionId)?.();
83
- }
84
-
85
- activateSession(
86
- binding: ActiveRuntimeBinding,
87
- refreshWidget?: () => void,
88
- ): void {
89
- if (refreshWidget) {
90
- this.refreshCallbacks.set(binding.sessionId, refreshWidget);
91
- }
92
- this.ownerSessions.activateSession(binding);
93
- refreshWidget?.();
94
- }
95
-
96
- deactivateSession(sessionId: string): void {
97
- this.ownerSessions.deactivateSession(sessionId);
98
- this.refreshCallbacks.delete(sessionId);
99
- }
100
-
101
- spawn(
102
- agentConfig: AgentConfig,
103
- task: string,
104
- cwd: string,
105
- ownerSessionId: string,
106
- ctx: SpawnContext,
107
- extensionResolvedPath: string,
108
- ): string {
109
- const state = this.registry.create(agentConfig, task, ownerSessionId);
110
- this.ownerSessions.refresh(ownerSessionId);
111
- this.lifecycle.start(state, {
112
- cwd,
113
- ctx: toBootstrapContext(ctx),
114
- extensionResolvedPath,
115
- onWarning: ctx.onWarning,
116
- });
117
- return state.id;
118
- }
119
-
120
- private settleAgent(
121
- state: SubagentState,
122
- nextStatus: SettledSubagentStatus,
123
- opts: { result?: string; error?: string },
124
- ): void {
125
- settleSubagent(state, nextStatus, opts);
126
-
127
- this.ownerSessions.deliver(
128
- state.ownerSessionId,
129
- {
130
- id: state.id,
131
- agentName: state.agentConfig.name,
132
- sessionFile: state.session?.sessionFile,
133
- status: state.status,
134
- result: state.result,
135
- error: state.error,
136
- },
137
- );
138
-
139
- if (state.status !== "waiting") {
140
- this.disposeAgent(state);
141
- } else {
142
- this.ownerSessions.refresh(state.ownerSessionId);
143
- }
144
- }
145
-
146
- private disposeAgent(state: SubagentState): void {
147
- state.unsubscribe?.();
148
- state.promptAbortController = undefined;
149
- state.session?.dispose();
150
- this.registry.delete(state.id);
151
- this.ownerSessions.refresh(state.ownerSessionId);
152
- }
153
-
154
-
155
- respond(
156
- id: string,
157
- message: string,
158
- callerSessionId: string,
159
- ): { error?: string } {
160
- const transition = startSubagentResponse(
161
- this.registry.get(id),
162
- id,
163
- callerSessionId,
164
- );
165
- if (!transition.ok) return { error: transition.error };
166
-
167
- this.ownerSessions.refresh(transition.state.ownerSessionId);
168
- this.lifecycle.respond(transition.state, message);
169
- return {};
170
- }
171
-
172
- done(id: string, callerSessionId: string): { error?: string } {
173
- const transition = validateSubagentDone(
174
- this.registry.get(id),
175
- id,
176
- callerSessionId,
177
- );
178
- if (!transition.ok) return { error: transition.error };
179
-
180
- this.disposeAgent(transition.state);
181
- return {};
182
- }
183
-
184
- abort(id: string, opts: AbortOptions): boolean {
185
- const state = this.registry.get(id);
186
- if (!canAbortSubagent(state)) return false;
187
-
188
- this.lifecycle.abortPrompt(state);
189
- this.settleAgent(state, "aborted", { error: opts.reason });
190
- return true;
191
- }
192
-
193
- abortOwned(
194
- ids: string[],
195
- callerSessionId: string,
196
- opts: AbortOptions,
197
- ): AbortOwnedResult {
198
- const uniqueIds = Array.from(
199
- new Set(ids.map((id) => id.trim()).filter(Boolean)),
200
- );
201
- const result: AbortOwnedResult = {
202
- abortedIds: [],
203
- missingIds: [],
204
- foreignIds: [],
205
- };
206
-
207
- for (const id of uniqueIds) {
208
- const state = this.registry.get(id);
209
- if (!canAbortSubagent(state)) {
210
- result.missingIds.push(id);
211
- continue;
212
- }
213
- if (state.ownerSessionId !== callerSessionId) {
214
- result.foreignIds.push(id);
215
- continue;
216
- }
217
- if (this.abort(id, opts)) {
218
- result.abortedIds.push(id);
219
- } else {
220
- result.missingIds.push(id);
221
- }
222
- }
223
-
224
- return result;
225
- }
226
-
227
- abortAllOwned(
228
- callerSessionId: string,
229
- opts: AbortOptions,
230
- ): string[] {
231
- const ids = this.registry.getOwnedAbortableIds(callerSessionId);
232
-
233
- for (const id of ids) {
234
- this.abort(id, opts);
235
- }
236
-
237
- return ids;
238
- }
239
-
240
- /**
241
- * Abort all abortable subagents during shutdown cleanup.
242
- * Called from SIGINT, session_shutdown(reason="quit"), and beforeExit fallback paths.
243
- */
244
- abortAll(): void {
245
- const allAgents = this.registry.getAllAbortable();
246
- for (const state of allAgents) {
247
- this.abort(state.id, { reason: "Aborted during shutdown" });
248
- }
249
- }
250
-
251
- getActiveSummariesForOwner(ownerSessionId: string): ActiveAgentSummary[] {
252
- return this.registry.getActiveSummariesForOwner(ownerSessionId);
253
- }
254
- }
255
-
256
- const crewRuntimeKey = Symbol.for("pi-crew.runtime");
257
- const globalWithCrewRuntime = globalThis as typeof globalThis & Record<
258
- symbol,
259
- CrewRuntime | undefined
260
- >;
261
-
262
- export const crewRuntime = globalWithCrewRuntime[crewRuntimeKey] ??= new CrewRuntime();
263
- export type { CrewRuntime };