@melihmucuk/pi-crew 1.0.17 → 1.0.19

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 (35) hide show
  1. package/agents/code-reviewer.md +16 -11
  2. package/agents/quality-reviewer.md +8 -17
  3. package/extension/catalog.ts +543 -0
  4. package/extension/crew.ts +377 -0
  5. package/extension/index.ts +35 -18
  6. package/extension/subagent-session.ts +257 -0
  7. package/extension/tools.ts +323 -0
  8. package/extension/ui.ts +291 -0
  9. package/package.json +6 -6
  10. package/prompts/pi-crew-review.md +25 -16
  11. package/skills/pi-crew/SKILL.md +3 -1
  12. package/extension/agent-catalog.ts +0 -369
  13. package/extension/agent-config-fields.ts +0 -359
  14. package/extension/agent-discovery.ts +0 -123
  15. package/extension/bootstrap-session.ts +0 -131
  16. package/extension/integration/crew-tool-actions.ts +0 -306
  17. package/extension/integration/crew-tool-executor.ts +0 -109
  18. package/extension/integration/register-renderers.ts +0 -77
  19. package/extension/integration/register-tools.ts +0 -47
  20. package/extension/integration/tool-presentation.ts +0 -30
  21. package/extension/integration/tools/crew-abort.ts +0 -56
  22. package/extension/integration/tools/crew-done.ts +0 -27
  23. package/extension/integration/tools/crew-list.ts +0 -36
  24. package/extension/integration/tools/crew-respond.ts +0 -38
  25. package/extension/integration/tools/crew-spawn.ts +0 -46
  26. package/extension/message-delivery-policy.ts +0 -22
  27. package/extension/runtime/crew-runtime.ts +0 -263
  28. package/extension/runtime/overflow-recovery.ts +0 -211
  29. package/extension/runtime/owner-session-coordinator.ts +0 -138
  30. package/extension/runtime/subagent-lifecycle.ts +0 -203
  31. package/extension/runtime/subagent-registry.ts +0 -122
  32. package/extension/runtime/subagent-transitions.ts +0 -100
  33. package/extension/status-widget.ts +0 -107
  34. package/extension/subagent-messages.ts +0 -116
  35. package/extension/tool-registry.ts +0 -19
@@ -1,211 +0,0 @@
1
- import type { AgentSession, AgentSessionEvent } from "@earendil-works/pi-coding-agent";
2
-
3
- const OVERFLOW_RECOVERY_TIMEOUT_MS = 120_000;
4
-
5
- /**
6
- * Short grace period for the first terminal agent_end after prompt() resolves.
7
- * If this window expires, we still wait the full recovery timeout.
8
- */
9
- const INITIAL_AGENT_END_WAIT_MS = 5_000;
10
-
11
- type PhaseWaitResult = "done" | "timeout" | "cancelled";
12
-
13
- export type OverflowRecoveryResult = "none" | "recovered" | "failed";
14
-
15
- interface DeferredPhase {
16
- promise: Promise<void>;
17
- resolve: () => void;
18
- isDone: () => boolean;
19
- }
20
-
21
- function createDeferredPhase(): DeferredPhase {
22
- let done = false;
23
- let resolveFn: (() => void) | undefined;
24
-
25
- const promise = new Promise<void>((resolve) => {
26
- resolveFn = () => {
27
- if (done) return;
28
- done = true;
29
- resolve();
30
- };
31
- });
32
-
33
- return {
34
- promise,
35
- resolve: () => resolveFn?.(),
36
- isDone: () => done,
37
- };
38
- }
39
-
40
- class OverflowRecoveryTracker {
41
- private overflowDetected = false;
42
- private compactionWillRetry = false;
43
-
44
- private autoRetryActive = false;
45
- private readonly initialAgentEnd = createDeferredPhase();
46
- private compactionEnd: DeferredPhase | undefined;
47
- private retryAgentEnd: DeferredPhase | undefined;
48
- private overflowAutoRetryEnd: DeferredPhase | undefined;
49
- private timers: ReturnType<typeof setTimeout>[] = [];
50
-
51
- handleEvent(event: AgentSessionEvent): void {
52
- switch (event.type) {
53
- case "agent_end":
54
- this.onAgentEnd();
55
- break;
56
- case "compaction_start":
57
- this.onCompactionStart(event.reason);
58
- break;
59
- case "compaction_end":
60
- this.onCompactionEnd(event.reason, event.willRetry);
61
- break;
62
- case "auto_retry_start":
63
- this.onAutoRetryStart();
64
- break;
65
- case "auto_retry_end":
66
- this.onAutoRetryEnd();
67
- break;
68
- default:
69
- break;
70
- }
71
- }
72
-
73
- async awaitCompletion(signal: AbortSignal): Promise<OverflowRecoveryResult> {
74
- const cancelPromise = new Promise<void>((resolve) => {
75
- if (signal.aborted) {
76
- resolve();
77
- return;
78
- }
79
- signal.addEventListener("abort", () => resolve(), { once: true });
80
- });
81
-
82
- try {
83
- let initialEnd = await this.waitForPhase(
84
- this.initialAgentEnd.promise,
85
- INITIAL_AGENT_END_WAIT_MS,
86
- cancelPromise,
87
- );
88
-
89
- if (initialEnd === "timeout") {
90
- initialEnd = await this.waitForPhase(
91
- this.initialAgentEnd.promise,
92
- OVERFLOW_RECOVERY_TIMEOUT_MS,
93
- cancelPromise,
94
- );
95
- }
96
-
97
- if (initialEnd !== "done") {
98
- return this.overflowDetected ? "failed" : "none";
99
- }
100
-
101
- if (!this.overflowDetected) return "none";
102
-
103
- if (this.compactionEnd) {
104
- const compactionEnd = await this.waitForPhase(
105
- this.compactionEnd.promise,
106
- OVERFLOW_RECOVERY_TIMEOUT_MS,
107
- cancelPromise,
108
- );
109
- if (compactionEnd !== "done") return "failed";
110
- }
111
-
112
- if (!this.compactionWillRetry) return "failed";
113
-
114
- if (this.retryAgentEnd) {
115
- const retryEnd = await this.waitForPhase(
116
- this.retryAgentEnd.promise,
117
- OVERFLOW_RECOVERY_TIMEOUT_MS,
118
- cancelPromise,
119
- );
120
- if (retryEnd !== "done") return "failed";
121
- }
122
-
123
- if (this.overflowAutoRetryEnd) {
124
- const autoRetryEnd = await this.waitForPhase(
125
- this.overflowAutoRetryEnd.promise,
126
- OVERFLOW_RECOVERY_TIMEOUT_MS,
127
- cancelPromise,
128
- );
129
- if (autoRetryEnd !== "done") return "failed";
130
- }
131
-
132
- return "recovered";
133
- } finally {
134
- for (const timer of this.timers) clearTimeout(timer);
135
- }
136
- }
137
-
138
- private async waitForPhase(
139
- phasePromise: Promise<void>,
140
- timeoutMs: number,
141
- cancelPromise: Promise<void>,
142
- ): Promise<PhaseWaitResult> {
143
- return Promise.race([
144
- phasePromise.then(() => "done" as const),
145
- cancelPromise.then(() => "cancelled" as const),
146
- new Promise<"timeout">((resolve) => {
147
- this.timers.push(setTimeout(() => resolve("timeout"), timeoutMs));
148
- }),
149
- ]);
150
- }
151
-
152
- // agent_end can be followed immediately by auto_retry_start in the same
153
- // _processAgentEvent tick. Resolve on microtask so we can ignore retrying
154
- // attempts and only accept terminal agent_end events.
155
- private onAgentEnd(): void {
156
- queueMicrotask(() => {
157
- if (this.autoRetryActive) return;
158
-
159
- if (!this.initialAgentEnd.isDone()) {
160
- this.initialAgentEnd.resolve();
161
- return;
162
- }
163
-
164
- this.retryAgentEnd?.resolve();
165
- });
166
- }
167
-
168
- private onCompactionStart(reason: "manual" | "threshold" | "overflow"): void {
169
- if (reason !== "overflow") return;
170
- this.overflowDetected = true;
171
- this.compactionEnd ??= createDeferredPhase();
172
- }
173
-
174
- private onCompactionEnd(reason: "manual" | "threshold" | "overflow", willRetry: boolean): void {
175
- if (reason !== "overflow") return;
176
-
177
- this.compactionWillRetry = willRetry;
178
- if (willRetry) {
179
- this.retryAgentEnd ??= createDeferredPhase();
180
- }
181
- this.compactionEnd?.resolve();
182
- }
183
-
184
- private onAutoRetryStart(): void {
185
- this.autoRetryActive = true;
186
- if (this.overflowDetected) {
187
- this.overflowAutoRetryEnd ??= createDeferredPhase();
188
- }
189
- }
190
-
191
- private onAutoRetryEnd(): void {
192
- this.autoRetryActive = false;
193
- this.overflowAutoRetryEnd?.resolve();
194
- }
195
- }
196
-
197
- export async function runPromptWithOverflowRecovery(
198
- session: AgentSession,
199
- text: string,
200
- signal: AbortSignal,
201
- ): Promise<OverflowRecoveryResult> {
202
- const tracker = new OverflowRecoveryTracker();
203
- const unsubscribe = session.subscribe((event) => tracker.handleEvent(event));
204
-
205
- try {
206
- await session.prompt(text);
207
- return await tracker.awaitCompletion(signal);
208
- } finally {
209
- unsubscribe();
210
- }
211
- }
@@ -1,138 +0,0 @@
1
- import type { SendMessageFn } from "../message-delivery-policy.js";
2
- import {
3
- type SteeringPayload,
4
- sendRemainingNote,
5
- sendSteeringMessage,
6
- } from "../subagent-messages.js";
7
-
8
- export interface ActiveRuntimeBinding {
9
- sessionId: string;
10
- isIdle: () => boolean;
11
- sendMessage: SendMessageFn;
12
- }
13
-
14
- interface PendingMessage {
15
- ownerSessionId: string;
16
- payload: SteeringPayload;
17
- queuedAt: number;
18
- }
19
-
20
- interface OwnerSessionCoordinatorDeps {
21
- countRunningForOwner: (ownerSessionId: string, excludeId: string) => number;
22
- onRefreshOwnerSession: (ownerSessionId: string) => void;
23
- now?: () => number;
24
- scheduleFlush?: (callback: () => void) => void;
25
- }
26
-
27
- const PENDING_MESSAGE_TTL_MS = 86_400_000;
28
-
29
- export class OwnerSessionCoordinator {
30
- private binding: ActiveRuntimeBinding | undefined;
31
- private pendingMessages: PendingMessage[] = [];
32
- private flushScheduled = false;
33
- private readonly countRunningForOwner: (ownerSessionId: string, excludeId: string) => number;
34
- private readonly onRefreshOwnerSession: (ownerSessionId: string) => void;
35
- private readonly now: () => number;
36
- private readonly scheduleFlush: (callback: () => void) => void;
37
-
38
- constructor(deps: OwnerSessionCoordinatorDeps) {
39
- this.countRunningForOwner = deps.countRunningForOwner;
40
- this.onRefreshOwnerSession = deps.onRefreshOwnerSession;
41
- this.now = deps.now ?? Date.now;
42
- this.scheduleFlush = deps.scheduleFlush ?? ((callback) => setTimeout(callback, 0));
43
- }
44
-
45
- activateSession(binding: ActiveRuntimeBinding): void {
46
- this.binding = binding;
47
-
48
- // Delay flush to next macrotask. session_start fires before pi-core
49
- // calls _reconnectToAgent(), so synchronous delivery would emit agent
50
- // events while the session listener is disconnected, losing JSONL persistence.
51
- if (this.pendingMessages.some((entry) => entry.ownerSessionId === binding.sessionId)) {
52
- this.flushScheduled = true;
53
- this.scheduleFlush(() => {
54
- this.flushScheduled = false;
55
- this.flushPending();
56
- });
57
- }
58
- }
59
-
60
- deactivateSession(sessionId: string): void {
61
- if (this.binding?.sessionId === sessionId) {
62
- this.binding = undefined;
63
- }
64
- }
65
-
66
- refresh(ownerSessionId: string): void {
67
- this.onRefreshOwnerSession(ownerSessionId);
68
- }
69
-
70
- deliver(ownerSessionId: string, payload: SteeringPayload): void {
71
- if (!this.binding || ownerSessionId !== this.binding.sessionId || this.flushScheduled) {
72
- this.queue(ownerSessionId, payload);
73
- return;
74
- }
75
-
76
- this.send(ownerSessionId, payload);
77
- }
78
-
79
- private queue(ownerSessionId: string, payload: SteeringPayload): void {
80
- this.pendingMessages.push({ ownerSessionId, payload, queuedAt: this.now() });
81
- }
82
-
83
- private cleanStaleMessages(): void {
84
- const cutoff = this.now() - PENDING_MESSAGE_TTL_MS;
85
- this.pendingMessages = this.pendingMessages.filter(
86
- (entry) => entry.queuedAt >= cutoff,
87
- );
88
- }
89
-
90
- private flushPending(): void {
91
- if (!this.binding) return;
92
- const targetSessionId = this.binding.sessionId;
93
-
94
- this.cleanStaleMessages();
95
-
96
- const toDeliver: PendingMessage[] = [];
97
- const remaining: PendingMessage[] = [];
98
-
99
- for (const entry of this.pendingMessages) {
100
- if (entry.ownerSessionId === targetSessionId) {
101
- toDeliver.push(entry);
102
- } else {
103
- remaining.push(entry);
104
- }
105
- }
106
-
107
- this.pendingMessages = remaining;
108
-
109
- for (const entry of toDeliver) {
110
- this.send(entry.ownerSessionId, entry.payload);
111
- }
112
- }
113
-
114
- /**
115
- * Result messages always go first. If more subagents are still running and the
116
- * owner is idle, queue the result without triggering, then queue the separate
117
- * remaining note with triggerTurn so the next turn sees both in order.
118
- */
119
- private send(ownerSessionId: string, payload: SteeringPayload): void {
120
- if (!this.binding || this.binding.sessionId !== ownerSessionId) {
121
- this.queue(ownerSessionId, payload);
122
- return;
123
- }
124
-
125
- const remaining = this.countRunningForOwner(ownerSessionId, payload.id);
126
- const isIdle = this.binding.isIdle();
127
- const triggerResultTurn = !(isIdle && remaining > 0);
128
-
129
- sendSteeringMessage(payload, this.binding.sendMessage, {
130
- isIdle,
131
- triggerTurn: triggerResultTurn,
132
- });
133
- sendRemainingNote(remaining, this.binding.sendMessage, {
134
- isIdle,
135
- triggerTurn: isIdle && remaining > 0,
136
- });
137
- }
138
- }
@@ -1,203 +0,0 @@
1
- import type { AgentMessage } from "@earendil-works/pi-agent-core";
2
- import type { AssistantMessage } from "@earendil-works/pi-ai";
3
- import type { AgentSession } from "@earendil-works/pi-coding-agent";
4
- import type { BootstrapContext } from "../bootstrap-session.js";
5
- import { bootstrapSession } from "../bootstrap-session.js";
6
- import type { SubagentStatus } from "../subagent-messages.js";
7
- import { runPromptWithOverflowRecovery } from "./overflow-recovery.js";
8
- import type { SubagentState } from "./subagent-registry.js";
9
- import { isAborted } from "./subagent-transitions.js";
10
-
11
- interface PromptOutcome {
12
- status: Extract<SubagentStatus, "done" | "waiting" | "error" | "aborted">;
13
- result?: string;
14
- error?: string;
15
- }
16
-
17
- interface StartOptions {
18
- cwd: string;
19
- ctx: BootstrapContext;
20
- extensionResolvedPath: string;
21
- onWarning?: (message: string) => void;
22
- }
23
-
24
- interface SubagentLifecycleCallbacks {
25
- isCurrent: (state: SubagentState) => boolean;
26
- onProgress: (ownerSessionId: string) => void;
27
- onSettled: (
28
- state: SubagentState,
29
- status: Extract<SubagentStatus, "done" | "waiting" | "error" | "aborted">,
30
- outcome: { result?: string; error?: string },
31
- ) => void;
32
- }
33
-
34
- function getLastAssistantMessage(
35
- messages: AgentMessage[],
36
- ): AssistantMessage | undefined {
37
- for (let i = messages.length - 1; i >= 0; i--) {
38
- const msg = messages[i];
39
- if (msg.role === "assistant") {
40
- return msg as AssistantMessage;
41
- }
42
- }
43
- return undefined;
44
- }
45
-
46
- function getAssistantText(
47
- message: AssistantMessage | undefined,
48
- ): string | undefined {
49
- if (!message) return undefined;
50
-
51
- const texts: string[] = [];
52
- for (const part of message.content) {
53
- if (part.type === "text") {
54
- texts.push(part.text);
55
- }
56
- }
57
-
58
- return texts.length > 0 ? texts.join("\n") : undefined;
59
- }
60
-
61
- function getPromptOutcome(state: SubagentState): PromptOutcome {
62
- const lastAssistant = getLastAssistantMessage(state.session!.messages);
63
- const text = getAssistantText(lastAssistant);
64
-
65
- if (lastAssistant?.stopReason === "error") {
66
- return {
67
- status: "error",
68
- error: lastAssistant.errorMessage ?? text ?? "(no output)",
69
- };
70
- }
71
-
72
- if (lastAssistant?.stopReason === "aborted") {
73
- return {
74
- status: "aborted",
75
- error: lastAssistant.errorMessage ?? text ?? "(no output)",
76
- };
77
- }
78
-
79
- return {
80
- status: state.agentConfig.interactive ? "waiting" : "done",
81
- result: text ?? "(no output)",
82
- };
83
- }
84
-
85
- export class SubagentLifecycle {
86
- constructor(private readonly callbacks: SubagentLifecycleCallbacks) {}
87
-
88
- start(state: SubagentState, opts: StartOptions): void {
89
- void this.spawnSession(state, opts);
90
- }
91
-
92
- respond(state: SubagentState, message: string): void {
93
- void this.runPromptCycle(state, message);
94
- }
95
-
96
- abortPrompt(state: SubagentState): void {
97
- state.promptAbortController?.abort();
98
- state.promptAbortController = undefined;
99
- state.session?.abortCompaction();
100
- state.session?.abortRetry();
101
- state.session?.abort().catch(() => {});
102
- }
103
-
104
- private attachSessionListeners(
105
- state: SubagentState,
106
- session: AgentSession,
107
- ): void {
108
- state.unsubscribe = session.subscribe((event) => {
109
- if (event.type !== "turn_end") return;
110
-
111
- state.turns++;
112
- const msg = event.message;
113
- if (msg.role === "assistant") {
114
- const assistantMsg = msg as AssistantMessage;
115
- state.contextTokens = assistantMsg.usage.totalTokens;
116
- state.model = assistantMsg.model;
117
- }
118
- this.callbacks.onProgress(state.ownerSessionId);
119
- });
120
- }
121
-
122
- private attachSpawnedSession(
123
- state: SubagentState,
124
- session: AgentSession,
125
- ): boolean {
126
- if (!this.callbacks.isCurrent(state)) {
127
- session.dispose();
128
- return false;
129
- }
130
-
131
- state.session = session;
132
- return true;
133
- }
134
-
135
- private async runPromptCycle(
136
- state: SubagentState,
137
- prompt: string,
138
- ): Promise<void> {
139
- if (isAborted(state)) return;
140
-
141
- const abortController = new AbortController();
142
- state.promptAbortController = abortController;
143
-
144
- try {
145
- const recovery = await runPromptWithOverflowRecovery(
146
- state.session!,
147
- prompt,
148
- abortController.signal,
149
- );
150
- if (isAborted(state)) return;
151
-
152
- const outcome = getPromptOutcome(state);
153
-
154
- if (recovery === "failed" && outcome.status !== "error") {
155
- this.callbacks.onSettled(state, "error", {
156
- error: "Context overflow recovery failed",
157
- });
158
- return;
159
- }
160
-
161
- this.callbacks.onSettled(state, outcome.status, outcome);
162
- } catch (err) {
163
- if (isAborted(state)) return;
164
-
165
- const error = err instanceof Error ? err.message : String(err);
166
- this.callbacks.onSettled(state, "error", { error });
167
- } finally {
168
- state.promptAbortController = undefined;
169
- }
170
- }
171
-
172
- private async spawnSession(
173
- state: SubagentState,
174
- opts: StartOptions,
175
- ): Promise<void> {
176
- try {
177
- if (isAborted(state)) return;
178
-
179
- const { session, warnings } = await bootstrapSession({
180
- agentConfig: state.agentConfig,
181
- cwd: opts.cwd,
182
- ctx: opts.ctx,
183
- extensionResolvedPath: opts.extensionResolvedPath,
184
- });
185
-
186
- for (const warning of warnings) {
187
- opts.onWarning?.(warning);
188
- }
189
-
190
- if (!this.attachSpawnedSession(state, session)) return;
191
-
192
- this.attachSessionListeners(state, session);
193
- await this.runPromptCycle(state, state.task);
194
- } catch (err) {
195
- if (isAborted(state)) return;
196
-
197
- if (state.status === "running") {
198
- const error = err instanceof Error ? err.message : String(err);
199
- this.callbacks.onSettled(state, "error", { error });
200
- }
201
- }
202
- }
203
- }
@@ -1,122 +0,0 @@
1
- import { randomBytes } from "node:crypto";
2
- import type { AgentSession } from "@earendil-works/pi-coding-agent";
3
- import type { AgentConfig } from "../agent-discovery.js";
4
- import type { SubagentStatus } from "../subagent-messages.js";
5
- import { isAbortableStatus } from "./subagent-transitions.js";
6
-
7
- export interface SubagentState {
8
- id: string;
9
- agentConfig: AgentConfig;
10
- task: string;
11
- status: SubagentStatus;
12
- ownerSessionId: string;
13
- session: AgentSession | null;
14
- turns: number;
15
- contextTokens: number;
16
- model: string | undefined;
17
- error?: string;
18
- result?: string;
19
- promptAbortController?: AbortController;
20
- unsubscribe?: () => void;
21
- }
22
-
23
- export interface ActiveAgentSummary {
24
- id: string;
25
- agentName: string;
26
- status: SubagentStatus;
27
- turns: number;
28
- contextTokens: number;
29
- model: string | undefined;
30
- }
31
-
32
- function generateId(name: string, existingIds: Set<string>): string {
33
- for (let i = 0; i < 10; i++) {
34
- const id = `${name}-${randomBytes(4).toString("hex")}`;
35
- if (!existingIds.has(id)) return id;
36
- }
37
- return `${name}-${randomBytes(8).toString("hex")}`;
38
- }
39
-
40
- function buildActiveAgentSummary(
41
- state: SubagentState,
42
- ): ActiveAgentSummary {
43
- return {
44
- id: state.id,
45
- agentName: state.agentConfig.name,
46
- status: state.status,
47
- turns: state.turns,
48
- contextTokens: state.contextTokens,
49
- model: state.model,
50
- };
51
- }
52
-
53
- export class SubagentRegistry {
54
- private activeAgents = new Map<string, SubagentState>();
55
-
56
- create(agentConfig: AgentConfig, task: string, ownerSessionId: string): SubagentState {
57
- const id = generateId(agentConfig.name, new Set(this.activeAgents.keys()));
58
- const state: SubagentState = {
59
- id,
60
- agentConfig,
61
- task,
62
- status: "running",
63
- ownerSessionId,
64
- session: null,
65
- turns: 0,
66
- contextTokens: 0,
67
- model: undefined,
68
- };
69
-
70
- this.activeAgents.set(id, state);
71
- return state;
72
- }
73
-
74
- get(id: string): SubagentState | undefined {
75
- return this.activeAgents.get(id);
76
- }
77
-
78
- hasState(state: SubagentState): boolean {
79
- return this.activeAgents.get(state.id) === state;
80
- }
81
-
82
- delete(id: string): void {
83
- this.activeAgents.delete(id);
84
- }
85
-
86
- countRunningForOwner(ownerSessionId: string, excludeId: string): number {
87
- let count = 0;
88
- for (const state of this.activeAgents.values()) {
89
- if (
90
- state.id !== excludeId &&
91
- state.ownerSessionId === ownerSessionId &&
92
- state.status === "running"
93
- ) {
94
- count++;
95
- }
96
- }
97
- return count;
98
- }
99
-
100
- getActiveSummariesForOwner(ownerSessionId: string): ActiveAgentSummary[] {
101
- return Array.from(this.activeAgents.values())
102
- .filter(
103
- (state) => isAbortableStatus(state.status) && state.ownerSessionId === ownerSessionId,
104
- )
105
- .map(buildActiveAgentSummary);
106
- }
107
-
108
- getOwnedAbortableIds(ownerSessionId: string): string[] {
109
- return Array.from(this.activeAgents.values())
110
- .filter(
111
- (state) =>
112
- state.ownerSessionId === ownerSessionId && isAbortableStatus(state.status),
113
- )
114
- .map((state) => state.id);
115
- }
116
-
117
- getAllAbortable(): SubagentState[] {
118
- return Array.from(this.activeAgents.values()).filter((state) =>
119
- isAbortableStatus(state.status),
120
- );
121
- }
122
- }