@melihmucuk/pi-crew 1.0.16 → 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 (35) hide show
  1. package/README.md +8 -8
  2. package/agents/code-reviewer.md +2 -2
  3. package/agents/oracle.md +1 -1
  4. package/agents/planner.md +5 -1
  5. package/agents/quality-reviewer.md +2 -2
  6. package/agents/scout.md +2 -2
  7. package/agents/worker.md +3 -3
  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/index.ts +4 -2
  12. package/extension/integration/crew-tool-actions.ts +306 -0
  13. package/extension/integration/crew-tool-executor.ts +109 -0
  14. package/extension/integration/register-tools.ts +10 -2
  15. package/extension/integration/tool-presentation.ts +0 -20
  16. package/extension/integration/tools/crew-abort.ts +14 -84
  17. package/extension/integration/tools/crew-done.ts +7 -26
  18. package/extension/integration/tools/crew-list.ts +4 -60
  19. package/extension/integration/tools/crew-respond.ts +8 -29
  20. package/extension/integration/tools/crew-spawn.ts +15 -56
  21. package/extension/message-delivery-policy.ts +22 -0
  22. package/extension/runtime/crew-runtime.ts +60 -223
  23. package/extension/runtime/{delivery-coordinator.ts → owner-session-coordinator.ts} +44 -37
  24. package/extension/runtime/subagent-lifecycle.ts +203 -0
  25. package/extension/runtime/subagent-registry.ts +50 -6
  26. package/extension/runtime/subagent-transitions.ts +100 -0
  27. package/extension/subagent-messages.ts +9 -17
  28. package/package.json +8 -6
  29. package/prompts/pi-crew-plan.md +14 -13
  30. package/prompts/pi-crew-review.md +20 -16
  31. package/skills/pi-crew/REFERENCE.md +32 -20
  32. package/skills/pi-crew/SKILL.md +13 -10
  33. package/extension/integration/tools/tool-deps.ts +0 -16
  34. package/extension/integration.ts +0 -13
  35. package/extension/runtime/subagent-state.ts +0 -59
@@ -2,7 +2,8 @@ import { dirname } from "node:path";
2
2
  import { fileURLToPath } from "node:url";
3
3
  import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
4
4
  import { crewRuntime } from "./runtime/crew-runtime.js";
5
- import { registerCrewIntegration } from "./integration.js";
5
+ import { registerCrewMessageRenderers } from "./integration/register-renderers.js";
6
+ import { registerCrewTools } from "./integration/register-tools.js";
6
7
  import { updateWidget } from "./status-widget.js";
7
8
 
8
9
  const extensionDir = dirname(fileURLToPath(import.meta.url));
@@ -59,5 +60,6 @@ export default function (pi: ExtensionAPI) {
59
60
  }
60
61
  });
61
62
 
62
- registerCrewIntegration(pi, crewRuntime, extensionDir);
63
+ registerCrewTools(pi, crewRuntime, extensionDir);
64
+ registerCrewMessageRenderers(pi);
63
65
  }
@@ -0,0 +1,306 @@
1
+ import type { AgentToolResult } from "@earendil-works/pi-agent-core";
2
+ import type { Api, Model } from "@earendil-works/pi-ai";
3
+ import type { ModelRegistry } from "@earendil-works/pi-coding-agent";
4
+ import type {
5
+ AgentConfig,
6
+ AgentDiscoveryResult,
7
+ AgentDiscoveryWarning,
8
+ } from "../agent-discovery.js";
9
+ import type {
10
+ AbortOwnedResult,
11
+ ActiveAgentSummary,
12
+ CrewRuntime,
13
+ } from "../runtime/crew-runtime.js";
14
+ import { STATUS_ICON } from "../subagent-messages.js";
15
+
16
+ export type CrewToolResult = AgentToolResult<unknown> & {
17
+ isError?: boolean;
18
+ terminate?: boolean;
19
+ };
20
+
21
+ export type CrewToolActionSideEffect =
22
+ | { type: "discovery-warnings"; warnings: AgentDiscoveryWarning[] }
23
+ | { type: "active-list-warning" };
24
+
25
+ export interface CrewToolActionResponse {
26
+ result: CrewToolResult;
27
+ sideEffects: CrewToolActionSideEffect[];
28
+ }
29
+
30
+ export interface CrewToolActionContext {
31
+ cwd: string;
32
+ callerSessionId: string;
33
+ }
34
+
35
+ export interface CrewSpawnActionContext extends CrewToolActionContext {
36
+ model: Model<Api> | undefined;
37
+ modelRegistry: ModelRegistry;
38
+ agentDir: string;
39
+ parentSessionFile?: string;
40
+ onWarning?: (message: string) => void;
41
+ }
42
+
43
+ interface CrewToolRuntime {
44
+ spawn: CrewRuntime["spawn"];
45
+ abortAllOwned: CrewRuntime["abortAllOwned"];
46
+ abortOwned: CrewRuntime["abortOwned"];
47
+ respond: CrewRuntime["respond"];
48
+ done: CrewRuntime["done"];
49
+ getActiveSummariesForOwner: CrewRuntime["getActiveSummariesForOwner"];
50
+ }
51
+
52
+ interface CreateCrewToolActionsDeps {
53
+ crew: CrewToolRuntime;
54
+ discoverAgents: (cwd: string) => AgentDiscoveryResult;
55
+ extensionDir: string;
56
+ }
57
+
58
+ function toolError(text: string): CrewToolResult {
59
+ return {
60
+ content: [{ type: "text", text }],
61
+ isError: true,
62
+ details: { error: true },
63
+ };
64
+ }
65
+
66
+ function toolSuccess(
67
+ text: string,
68
+ details: Record<string, unknown> = {},
69
+ options: { terminate?: boolean } = {},
70
+ ): CrewToolResult {
71
+ return {
72
+ content: [{ type: "text", text }],
73
+ details,
74
+ ...(options.terminate ? { terminate: true } : {}),
75
+ };
76
+ }
77
+
78
+ function response(
79
+ result: CrewToolResult,
80
+ sideEffects: CrewToolActionSideEffect[] = [],
81
+ ): CrewToolActionResponse {
82
+ return { result, sideEffects };
83
+ }
84
+
85
+ function discoveryWarningSideEffect(
86
+ warnings: AgentDiscoveryWarning[],
87
+ ): CrewToolActionSideEffect[] {
88
+ return warnings.length > 0 ? [{ type: "discovery-warnings", warnings }] : [];
89
+ }
90
+
91
+ function formatAbortToolMessage(result: AbortOwnedResult): string {
92
+ const parts: string[] = [];
93
+
94
+ if (result.abortedIds.length > 0) {
95
+ parts.push(`Aborted ${result.abortedIds.length} subagent(s): ${result.abortedIds.join(", ")}`);
96
+ }
97
+ if (result.missingIds.length > 0) {
98
+ parts.push(`Not found or already finished: ${result.missingIds.join(", ")}`);
99
+ }
100
+ if (result.foreignIds.length > 0) {
101
+ parts.push(`Belong to a different session: ${result.foreignIds.join(", ")}`);
102
+ }
103
+
104
+ return parts.join("\n");
105
+ }
106
+
107
+ function formatAvailableAgents(agents: AgentConfig[]): string[] {
108
+ if (agents.length === 0) {
109
+ return [
110
+ "No valid subagent definitions found. Add `.md` files to `<cwd>/.pi/agents/` or `~/.pi/agent/agents/`.",
111
+ ];
112
+ }
113
+
114
+ return agents.flatMap((agent) => [
115
+ "",
116
+ `name: ${agent.name}`,
117
+ `description: ${agent.description}`,
118
+ `interactive: ${agent.interactive ? "true" : "false"}`,
119
+ ]);
120
+ }
121
+
122
+ function formatWarnings(warnings: AgentDiscoveryWarning[]): string[] {
123
+ if (warnings.length === 0) return [];
124
+
125
+ return [
126
+ "",
127
+ "## Ignored subagent definitions",
128
+ ...warnings.map((warning) => `- ${warning.message} (${warning.filePath})`),
129
+ ];
130
+ }
131
+
132
+ function formatActiveAgents(running: ActiveAgentSummary[]): string[] {
133
+ if (running.length === 0) return ["No subagents currently active."];
134
+
135
+ return running.flatMap((agent) => {
136
+ const icon = STATUS_ICON[agent.status] ?? "❓";
137
+ return [
138
+ "",
139
+ `id: ${agent.id}`,
140
+ `name: ${agent.agentName}`,
141
+ `status: ${icon} ${agent.status}`,
142
+ ];
143
+ });
144
+ }
145
+
146
+ export type CrewToolActions = ReturnType<typeof createCrewToolActions>;
147
+
148
+ export function createCrewToolActions({
149
+ crew,
150
+ discoverAgents,
151
+ extensionDir,
152
+ }: CreateCrewToolActionsDeps) {
153
+ return {
154
+ list(ctx: CrewToolActionContext): CrewToolActionResponse {
155
+ const { agents, warnings } = discoverAgents(ctx.cwd);
156
+ const running = crew.getActiveSummariesForOwner(ctx.callerSessionId);
157
+ const lines = [
158
+ "## Available Subagents",
159
+ ...formatAvailableAgents(agents),
160
+ ...formatWarnings(warnings),
161
+ "",
162
+ "## Active Subagents",
163
+ ...formatActiveAgents(running),
164
+ ];
165
+
166
+ return response(
167
+ { content: [{ type: "text", text: lines.join("\n") }], details: {} },
168
+ [
169
+ ...discoveryWarningSideEffect(warnings),
170
+ ...(running.length > 0 ? [{ type: "active-list-warning" } as const] : []),
171
+ ],
172
+ );
173
+ },
174
+
175
+ spawn(
176
+ params: { subagent: string; task: string },
177
+ ctx: CrewSpawnActionContext,
178
+ ): CrewToolActionResponse {
179
+ const { agents, warnings } = discoverAgents(ctx.cwd);
180
+ const sideEffects = discoveryWarningSideEffect(warnings);
181
+ const subagent = agents.find(
182
+ (candidate) => candidate.name === params.subagent,
183
+ );
184
+
185
+ if (!subagent) {
186
+ const available =
187
+ agents.map((candidate) => candidate.name).join(", ") || "none";
188
+ return response(
189
+ toolError(
190
+ `Unknown subagent: "${params.subagent}". Available: ${available}`,
191
+ ),
192
+ sideEffects,
193
+ );
194
+ }
195
+
196
+ const id = crew.spawn(
197
+ subagent,
198
+ params.task,
199
+ ctx.cwd,
200
+ ctx.callerSessionId,
201
+ {
202
+ model: ctx.model,
203
+ modelRegistry: ctx.modelRegistry,
204
+ agentDir: ctx.agentDir,
205
+ parentSessionFile: ctx.parentSessionFile,
206
+ onWarning: ctx.onWarning,
207
+ },
208
+ extensionDir,
209
+ );
210
+
211
+ return response(
212
+ toolSuccess(
213
+ `Subagent '${subagent.name}' spawned as ${id}. Result will be delivered as a steering message when done.`,
214
+ { id, agentName: subagent.name, task: params.task },
215
+ ),
216
+ sideEffects,
217
+ );
218
+ },
219
+
220
+ abort(
221
+ params: {
222
+ subagent_id?: string;
223
+ subagent_ids?: string[];
224
+ all?: boolean;
225
+ },
226
+ ctx: CrewToolActionContext,
227
+ ): CrewToolActionResponse {
228
+ const modeCount = Number(Boolean(params.subagent_id))
229
+ + Number(Boolean(params.subagent_ids?.length))
230
+ + Number(params.all === true);
231
+
232
+ if (modeCount !== 1) {
233
+ return response(toolError(
234
+ "Provide exactly one of: subagent_id, subagent_ids, or all=true.",
235
+ ));
236
+ }
237
+
238
+ if (params.all) {
239
+ const abortedIds = crew.abortAllOwned(ctx.callerSessionId, {
240
+ reason: "Aborted by tool request",
241
+ });
242
+ if (abortedIds.length === 0) {
243
+ return response(toolError("No active subagents in the current session."));
244
+ }
245
+
246
+ return response(toolSuccess(
247
+ `Aborted ${abortedIds.length} subagent(s): ${abortedIds.join(", ")}`,
248
+ { ids: abortedIds },
249
+ { terminate: true },
250
+ ));
251
+ }
252
+
253
+ const ids = params.subagent_id
254
+ ? [params.subagent_id]
255
+ : (params.subagent_ids ?? []);
256
+ const result = crew.abortOwned(ids, ctx.callerSessionId, {
257
+ reason: "Aborted by tool request",
258
+ });
259
+ const message = formatAbortToolMessage(result);
260
+
261
+ if (result.abortedIds.length === 0) {
262
+ return response(toolError(message || "No subagents were aborted."));
263
+ }
264
+
265
+ return response(toolSuccess(
266
+ message,
267
+ {
268
+ ids: result.abortedIds,
269
+ missing_ids: result.missingIds,
270
+ foreign_ids: result.foreignIds,
271
+ },
272
+ { terminate: true },
273
+ ));
274
+ },
275
+
276
+ respond(
277
+ params: { subagent_id: string; message: string },
278
+ ctx: CrewToolActionContext,
279
+ ): CrewToolActionResponse {
280
+ const { error } = crew.respond(
281
+ params.subagent_id,
282
+ params.message,
283
+ ctx.callerSessionId,
284
+ );
285
+ if (error) return response(toolError(error));
286
+
287
+ return response(toolSuccess(
288
+ `Message sent to subagent ${params.subagent_id}. Response will be delivered as a steering message.`,
289
+ { id: params.subagent_id, message: params.message },
290
+ ));
291
+ },
292
+
293
+ done(
294
+ params: { subagent_id: string },
295
+ ctx: CrewToolActionContext,
296
+ ): CrewToolActionResponse {
297
+ const { error } = crew.done(params.subagent_id, ctx.callerSessionId);
298
+ if (error) return response(toolError(error));
299
+
300
+ return response(toolSuccess(
301
+ `Subagent ${params.subagent_id} closed.`,
302
+ { id: params.subagent_id },
303
+ ));
304
+ },
305
+ };
306
+ }
@@ -0,0 +1,109 @@
1
+ import type {
2
+ ExtensionAPI,
3
+ ExtensionContext,
4
+ } from "@earendil-works/pi-coding-agent";
5
+ import type { AgentDiscoveryWarning } from "../agent-discovery.js";
6
+ import { sendCrewListActiveWarning } from "../subagent-messages.js";
7
+ import { renderCrewResult } from "./tool-presentation.js";
8
+ import type {
9
+ CrewToolActionContext,
10
+ CrewToolActionResponse,
11
+ CrewToolActionSideEffect,
12
+ CrewToolActions,
13
+ CrewToolResult,
14
+ } from "./crew-tool-actions.js";
15
+
16
+ interface CrewToolExecutorDeps {
17
+ pi: ExtensionAPI;
18
+ notifyDiscoveryWarnings: (
19
+ ctx: ExtensionContext,
20
+ warnings: AgentDiscoveryWarning[],
21
+ ) => void;
22
+ }
23
+
24
+ export interface CrewToolDeps {
25
+ pi: ExtensionAPI;
26
+ actions: CrewToolActions;
27
+ executor: CrewToolExecutor;
28
+ }
29
+
30
+ function getBaseActionContext(ctx: ExtensionContext): CrewToolActionContext {
31
+ return {
32
+ cwd: ctx.cwd,
33
+ callerSessionId: ctx.sessionManager.getSessionId(),
34
+ };
35
+ }
36
+
37
+ function runSideEffects(
38
+ deps: CrewToolExecutorDeps,
39
+ ctx: ExtensionContext,
40
+ sideEffects: CrewToolActionSideEffect[],
41
+ ): void {
42
+ for (const sideEffect of sideEffects) {
43
+ switch (sideEffect.type) {
44
+ case "discovery-warnings":
45
+ deps.notifyDiscoveryWarnings(ctx, sideEffect.warnings);
46
+ break;
47
+ case "active-list-warning":
48
+ Promise.resolve().then(() => {
49
+ sendCrewListActiveWarning(deps.pi.sendMessage.bind(deps.pi), {
50
+ isIdle: ctx.isIdle(),
51
+ triggerTurn: true,
52
+ });
53
+ });
54
+ break;
55
+ }
56
+ }
57
+ }
58
+
59
+ export class CrewToolExecutor {
60
+ constructor(private readonly deps: CrewToolExecutorDeps) {}
61
+
62
+ execute(
63
+ ctx: ExtensionContext,
64
+ runAction: (actionCtx: CrewToolActionContext) => CrewToolActionResponse,
65
+ ): CrewToolResult {
66
+ const response = runAction(getBaseActionContext(ctx));
67
+ runSideEffects(this.deps, ctx, response.sideEffects);
68
+ return response.result;
69
+ }
70
+ }
71
+
72
+ type RegisteredTool = Parameters<ExtensionAPI["registerTool"]>[0];
73
+ type ToolRenderCall = Exclude<RegisteredTool["renderCall"], undefined>;
74
+
75
+ export function registerCrewActionTool<Params extends object>(
76
+ { pi, executor }: CrewToolDeps,
77
+ options: Omit<RegisteredTool, "execute" | "renderResult" | "renderCall"> & {
78
+ action: (
79
+ params: Params,
80
+ actionCtx: CrewToolActionContext,
81
+ ctx: ExtensionContext,
82
+ ) => CrewToolActionResponse;
83
+ renderCall?: (
84
+ args: Partial<Params>,
85
+ theme: Parameters<ToolRenderCall>[1],
86
+ context: Parameters<ToolRenderCall>[2],
87
+ ) => ReturnType<ToolRenderCall>;
88
+ },
89
+ ): void {
90
+ const { action, renderCall, ...tool } = options;
91
+
92
+ pi.registerTool({
93
+ ...tool,
94
+ ...(renderCall
95
+ ? {
96
+ renderCall: (args, theme, context) =>
97
+ renderCall(args as Partial<Params>, theme, context),
98
+ }
99
+ : {}),
100
+ async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
101
+ return executor.execute(ctx, (actionCtx) =>
102
+ action(params as Params, actionCtx, ctx),
103
+ );
104
+ },
105
+ renderResult(result, _options, theme, _context) {
106
+ return renderCrewResult(result, theme);
107
+ },
108
+ });
109
+ }
@@ -2,8 +2,10 @@ import type {
2
2
  ExtensionAPI,
3
3
  ExtensionContext,
4
4
  } from "@earendil-works/pi-coding-agent";
5
- import { type AgentDiscoveryWarning } from "../agent-discovery.js";
5
+ import { discoverAgents, type AgentDiscoveryWarning } from "../agent-discovery.js";
6
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";
7
9
  import { registerCrewAbortTool } from "./tools/crew-abort.js";
8
10
  import { registerCrewDoneTool } from "./tools/crew-done.js";
9
11
  import { registerCrewListTool } from "./tools/crew-list.js";
@@ -30,7 +32,13 @@ export function registerCrewTools(
30
32
  }
31
33
  };
32
34
 
33
- const deps = { pi, crew, extensionDir, notifyDiscoveryWarnings };
35
+ const actions = createCrewToolActions({
36
+ crew,
37
+ discoverAgents,
38
+ extensionDir,
39
+ });
40
+ const executor = new CrewToolExecutor({ pi, notifyDiscoveryWarnings });
41
+ const deps = { pi, actions, executor };
34
42
  registerCrewListTool(deps);
35
43
  registerCrewSpawnTool(deps);
36
44
  registerCrewAbortTool(deps);
@@ -5,26 +5,6 @@ import { Box, Text } from "@earendil-works/pi-tui";
5
5
  export type ToolTheme = Parameters<Exclude<Parameters<ExtensionAPI["registerTool"]>[0]["renderCall"], undefined>>[1];
6
6
  export type ToolResult = AgentToolResult<unknown>;
7
7
 
8
- export function toolError(text: string) {
9
- return {
10
- content: [{ type: "text" as const, text }],
11
- isError: true,
12
- details: { error: true },
13
- };
14
- }
15
-
16
- export function toolSuccess(
17
- text: string,
18
- details: Record<string, unknown> = {},
19
- options: { terminate?: boolean } = {},
20
- ) {
21
- return {
22
- content: [{ type: "text" as const, text }],
23
- details,
24
- ...(options.terminate ? { terminate: true } : {}),
25
- };
26
- }
27
-
28
8
  export function renderCrewCall(
29
9
  theme: ToolTheme,
30
10
  name: string,
@@ -1,34 +1,18 @@
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";
9
-
10
- function formatAbortToolMessage(result: {
11
- abortedIds: string[];
12
- missingIds: string[];
13
- foreignIds: string[];
14
- }): string {
15
- const parts: string[] = [];
16
-
17
- if (result.abortedIds.length > 0) {
18
- parts.push(`Aborted ${result.abortedIds.length} subagent(s): ${result.abortedIds.join(", ")}`);
19
- }
20
- if (result.missingIds.length > 0) {
21
- parts.push(`Not found or already finished: ${result.missingIds.join(", ")}`);
22
- }
23
- if (result.foreignIds.length > 0) {
24
- parts.push(`Belong to a different session: ${result.foreignIds.join(", ")}`);
25
- }
26
-
27
- return parts.join("\n");
28
- }
29
-
30
- export function registerCrewAbortTool({ pi, crew }: CrewToolDeps): void {
31
- pi.registerTool({
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, {
32
16
  name: "crew_abort",
33
17
  label: "Abort Crew",
34
18
  description:
@@ -55,57 +39,7 @@ export function registerCrewAbortTool({ pi, crew }: CrewToolDeps): void {
55
39
  "crew_abort: Provide exactly one mode: subagent_id, subagent_ids, or all=true.",
56
40
  "crew_abort: Use only when delegated work is obsolete, wrong, or explicitly cancelled.",
57
41
  ],
58
-
59
- async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
60
- const callerSessionId = ctx.sessionManager.getSessionId();
61
- const modeCount = Number(Boolean(params.subagent_id))
62
- + Number(Boolean(params.subagent_ids?.length))
63
- + Number(params.all === true);
64
-
65
- if (modeCount !== 1) {
66
- return toolError(
67
- "Provide exactly one of: subagent_id, subagent_ids, or all=true.",
68
- );
69
- }
70
-
71
- if (params.all) {
72
- const abortedIds = crew.abortAllOwned(callerSessionId, {
73
- reason: "Aborted by tool request",
74
- });
75
- if (abortedIds.length === 0) {
76
- return toolError("No active subagents in the current session.");
77
- }
78
-
79
- return toolSuccess(
80
- `Aborted ${abortedIds.length} subagent(s): ${abortedIds.join(", ")}`,
81
- { ids: abortedIds },
82
- { terminate: true },
83
- );
84
- }
85
-
86
- const ids = params.subagent_id
87
- ? [params.subagent_id]
88
- : (params.subagent_ids ?? []);
89
- const result = crew.abortOwned(ids, callerSessionId, {
90
- reason: "Aborted by tool request",
91
- });
92
- const message = formatAbortToolMessage(result);
93
-
94
- if (result.abortedIds.length === 0) {
95
- return toolError(message || "No subagents were aborted.");
96
- }
97
-
98
- return toolSuccess(
99
- message,
100
- {
101
- ids: result.abortedIds,
102
- missing_ids: result.missingIds,
103
- foreign_ids: result.foreignIds,
104
- },
105
- { terminate: true },
106
- );
107
- },
108
-
42
+ action: (params, actionCtx) => deps.actions.abort(params, actionCtx),
109
43
  renderCall(args, theme, _context) {
110
44
  if (args.all) {
111
45
  return renderCrewCall(theme, "crew_abort", "all");
@@ -118,9 +52,5 @@ export function registerCrewAbortTool({ pi, crew }: CrewToolDeps): void {
118
52
  const count = Array.isArray(args.subagent_ids) ? args.subagent_ids.length : 0;
119
53
  return renderCrewCall(theme, "crew_abort", `${count} ids`);
120
54
  },
121
-
122
- renderResult(result, _options, theme, _context) {
123
- return renderCrewResult(result, theme);
124
- },
125
55
  });
126
56
  }
@@ -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 registerCrewDoneTool({ pi, crew }: CrewToolDeps): void {
11
- pi.registerTool({
8
+ export function registerCrewDoneTool(deps: CrewToolDeps): void {
9
+ registerCrewActionTool<{ subagent_id: string }>(deps, {
12
10
  name: "crew_done",
13
11
  label: "Done with Crew",
14
12
  description:
@@ -21,26 +19,9 @@ export function registerCrewDoneTool({ pi, crew }: CrewToolDeps): void {
21
19
  "crew_done: Close a waiting interactive subagent owned by this session.",
22
20
  "crew_done: Use only when no further follow-up is needed; otherwise use crew_respond.",
23
21
  ],
24
-
25
- async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
26
- const callerSessionId = ctx.sessionManager.getSessionId();
27
- const { error } = crew.done(params.subagent_id, callerSessionId);
28
- if (error) return toolError(error);
29
-
30
- return toolSuccess(
31
- `Subagent ${params.subagent_id} closed.`,
32
- {
33
- id: params.subagent_id,
34
- },
35
- );
36
- },
37
-
22
+ action: (params, actionCtx) => deps.actions.done(params, actionCtx),
38
23
  renderCall(args, theme, _context) {
39
24
  return renderCrewCall(theme, "crew_done", args.subagent_id || "...");
40
25
  },
41
-
42
- renderResult(result, _options, theme, _context) {
43
- return renderCrewResult(result, theme);
44
- },
45
26
  });
46
27
  }