@melihmucuk/pi-crew 1.0.6 → 1.0.8

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/dist/agent-discovery.d.ts +0 -5
  2. package/dist/agent-discovery.js +1 -1
  3. package/dist/bootstrap-session.d.ts +13 -4
  4. package/dist/bootstrap-session.js +25 -16
  5. package/dist/index.js +37 -9
  6. package/dist/integration/register-command.d.ts +2 -2
  7. package/dist/integration/register-command.js +5 -5
  8. package/dist/integration/register-renderers.js +3 -0
  9. package/dist/integration/register-tools.d.ts +2 -2
  10. package/dist/integration/register-tools.js +2 -2
  11. package/dist/integration/tool-presentation.d.ts +2 -3
  12. package/dist/integration/tool-presentation.js +7 -8
  13. package/dist/integration/tools/crew-abort.d.ts +1 -1
  14. package/dist/integration/tools/crew-abort.js +3 -3
  15. package/dist/integration/tools/crew-done.d.ts +1 -1
  16. package/dist/integration/tools/crew-done.js +2 -2
  17. package/dist/integration/tools/crew-list.d.ts +1 -1
  18. package/dist/integration/tools/crew-list.js +3 -3
  19. package/dist/integration/tools/crew-respond.d.ts +1 -1
  20. package/dist/integration/tools/crew-respond.js +6 -7
  21. package/dist/integration/tools/crew-spawn.d.ts +1 -1
  22. package/dist/integration/tools/crew-spawn.js +17 -14
  23. package/dist/integration/tools/tool-deps.d.ts +3 -2
  24. package/dist/integration.d.ts +2 -2
  25. package/dist/integration.js +3 -3
  26. package/dist/runtime/crew-runtime.d.ts +61 -0
  27. package/dist/{crew-manager.js → runtime/crew-runtime.js} +84 -58
  28. package/dist/runtime/delivery-coordinator.d.ts +16 -7
  29. package/dist/runtime/delivery-coordinator.js +47 -21
  30. package/dist/runtime/subagent-registry.d.ts +1 -0
  31. package/dist/runtime/subagent-registry.js +3 -0
  32. package/dist/runtime/subagent-state.d.ts +2 -0
  33. package/dist/status-widget.d.ts +2 -2
  34. package/dist/status-widget.js +3 -3
  35. package/dist/subagent-messages.d.ts +5 -2
  36. package/dist/subagent-messages.js +5 -4
  37. package/docs/architecture.md +106 -847
  38. package/package.json +7 -7
  39. package/dist/crew-manager.d.ts +0 -44
@@ -25,10 +25,5 @@ interface AgentDiscoveryResult {
25
25
  agents: AgentConfig[];
26
26
  warnings: AgentDiscoveryWarning[];
27
27
  }
28
- interface ParseResult {
29
- agent: AgentConfig | null;
30
- warnings: AgentDiscoveryWarning[];
31
- }
32
- export declare function parseAgentDefinition(content: string, filePath: string): ParseResult;
33
28
  export declare function discoverAgents(cwd?: string): AgentDiscoveryResult;
34
29
  export {};
@@ -202,7 +202,7 @@ function parseSharedFields(record, options) {
202
202
  ],
203
203
  };
204
204
  }
205
- export function parseAgentDefinition(content, filePath) {
205
+ function parseAgentDefinition(content, filePath) {
206
206
  const warnings = [];
207
207
  let frontmatter;
208
208
  let body;
@@ -1,11 +1,20 @@
1
- import { type CreateAgentSessionResult, type ExtensionContext } from "@mariozechner/pi-coding-agent";
1
+ import { type AgentSession, type ModelRegistry } from "@mariozechner/pi-coding-agent";
2
+ import type { Api, Model } from "@mariozechner/pi-ai";
2
3
  import type { AgentConfig } from "./agent-discovery.js";
4
+ export interface BootstrapContext {
5
+ model: Model<Api> | undefined;
6
+ modelRegistry: ModelRegistry;
7
+ parentSessionFile?: string;
8
+ }
3
9
  interface BootstrapOptions {
4
10
  agentConfig: AgentConfig;
5
11
  cwd: string;
6
- ctx: ExtensionContext;
12
+ ctx: BootstrapContext;
7
13
  extensionResolvedPath: string;
8
- parentSessionFile?: string;
9
14
  }
10
- export declare function bootstrapSession(opts: BootstrapOptions): Promise<CreateAgentSessionResult>;
15
+ export interface BootstrapResult {
16
+ session: AgentSession;
17
+ warnings: string[];
18
+ }
19
+ export declare function bootstrapSession(opts: BootstrapOptions): Promise<BootstrapResult>;
11
20
  export {};
@@ -4,29 +4,35 @@ function resolveTools(agentConfig, cwd) {
4
4
  return createSupportedTools(agentConfig.tools ?? SUPPORTED_TOOL_NAMES, cwd);
5
5
  }
6
6
  function resolveModel(agentConfig, ctx) {
7
+ const warnings = [];
7
8
  const model = ctx.model;
8
9
  if (!agentConfig.parsedModel)
9
- return model;
10
+ return { model, warnings };
10
11
  const found = ctx.modelRegistry.find(agentConfig.parsedModel.provider, agentConfig.parsedModel.modelId);
11
12
  if (found)
12
- return found;
13
- console.warn(`[pi-crew] Subagent "${agentConfig.name}": model "${agentConfig.model}" not found in registry, using default`);
14
- return model;
13
+ return { model: found, warnings };
14
+ warnings.push(`Model "${agentConfig.model}" not found, using current session model`);
15
+ return { model, warnings };
15
16
  }
16
- function warnUnknownSkills(agentConfig, resourceLoader) {
17
+ function getSkillWarnings(agentConfig, resourceLoader) {
18
+ const warnings = [];
17
19
  if (!agentConfig.skills)
18
- return;
20
+ return warnings;
19
21
  const availableSkillNames = new Set(resourceLoader.getSkills().skills.map((skill) => skill.name));
20
- const unknownSkills = agentConfig.skills.filter((skillName) => !availableSkillNames.has(skillName));
21
- if (unknownSkills.length === 0)
22
- return;
23
- console.warn(`[pi-crew] Subagent "${agentConfig.name}": unknown skills ${unknownSkills.map((skillName) => `"${skillName}"`).join(", ")}, ignoring`);
22
+ for (const skillName of agentConfig.skills) {
23
+ if (!availableSkillNames.has(skillName)) {
24
+ warnings.push(`Unknown skill "${skillName}" in subagent config, skipping`);
25
+ }
26
+ }
27
+ return warnings;
24
28
  }
25
29
  export async function bootstrapSession(opts) {
26
- const { agentConfig, cwd, ctx, extensionResolvedPath, parentSessionFile } = opts;
30
+ const warnings = [];
31
+ const { agentConfig, cwd, ctx, extensionResolvedPath } = opts;
27
32
  const authStorage = ctx.modelRegistry.authStorage;
28
33
  const modelRegistry = ctx.modelRegistry;
29
- const model = resolveModel(agentConfig, ctx);
34
+ const { model, warnings: modelWarnings } = resolveModel(agentConfig, ctx);
35
+ warnings.push(...modelWarnings);
30
36
  const tools = resolveTools(agentConfig, cwd);
31
37
  const resourceLoader = new DefaultResourceLoader({
32
38
  cwd,
@@ -40,16 +46,18 @@ export async function bootstrapSession(opts) {
40
46
  diagnostics: base.diagnostics,
41
47
  })
42
48
  : undefined,
43
- appendSystemPromptOverride: (base) => agentConfig.systemPrompt.trim() ? [...base, agentConfig.systemPrompt] : base,
49
+ appendSystemPromptOverride: (base) => agentConfig.systemPrompt.trim()
50
+ ? [...base, agentConfig.systemPrompt]
51
+ : base,
44
52
  });
45
53
  await resourceLoader.reload();
46
- warnUnknownSkills(agentConfig, resourceLoader);
54
+ warnings.push(...getSkillWarnings(agentConfig, resourceLoader));
47
55
  const settingsManager = SettingsManager.inMemory({
48
56
  compaction: { enabled: agentConfig.compaction ?? true },
49
57
  });
50
58
  const sessionManager = SessionManager.create(cwd);
51
- sessionManager.newSession({ parentSession: parentSessionFile });
52
- return createAgentSession({
59
+ sessionManager.newSession({ parentSession: ctx.parentSessionFile });
60
+ const result = await createAgentSession({
53
61
  cwd,
54
62
  model,
55
63
  thinkingLevel: agentConfig.thinking,
@@ -60,4 +68,5 @@ export async function bootstrapSession(opts) {
60
68
  authStorage,
61
69
  modelRegistry,
62
70
  });
71
+ return { session: result.session, warnings };
63
72
  }
package/dist/index.js CHANGED
@@ -1,18 +1,33 @@
1
1
  import { dirname } from "node:path";
2
2
  import { fileURLToPath } from "node:url";
3
3
  import { discoverAgents } from "./agent-discovery.js";
4
- import { CrewManager } from "./crew-manager.js";
4
+ import { crewRuntime, } from "./runtime/crew-runtime.js";
5
5
  import { registerCrewIntegration } from "./integration.js";
6
6
  import { formatAgentsForPrompt } from "./prompt-injection.js";
7
7
  import { updateWidget } from "./status-widget.js";
8
8
  const extensionDir = dirname(fileURLToPath(import.meta.url));
9
+ // Process-level cleanup for subagents on exit
10
+ let processHooksSetup = false;
11
+ function setupProcessHooks() {
12
+ if (processHooksSetup)
13
+ return;
14
+ processHooksSetup = true;
15
+ const abortAndExit = (signal) => {
16
+ crewRuntime.abortAll();
17
+ // Re-raise to restore default Node termination behavior
18
+ process.exit(128 + (signal === 'SIGINT' ? 2 : 15));
19
+ };
20
+ process.once('SIGINT', () => abortAndExit('SIGINT'));
21
+ process.once('SIGTERM', () => abortAndExit('SIGTERM'));
22
+ process.on('beforeExit', () => crewRuntime.abortAll());
23
+ }
9
24
  export default function (pi) {
10
- const crewManager = new CrewManager(extensionDir);
11
25
  let currentCtx;
12
26
  let cachedPromptSuffix = "";
27
+ setupProcessHooks();
13
28
  const refreshWidget = () => {
14
29
  if (currentCtx)
15
- updateWidget(currentCtx, crewManager);
30
+ updateWidget(currentCtx, crewRuntime);
16
31
  };
17
32
  const rebuildPromptCache = (cwd) => {
18
33
  const { agents } = discoverAgents(cwd);
@@ -20,18 +35,31 @@ export default function (pi) {
20
35
  };
21
36
  const activateSession = (ctx) => {
22
37
  currentCtx = ctx;
23
- crewManager.activateSession(ctx.sessionManager.getSessionId(), () => ctx.isIdle(), pi);
38
+ crewRuntime.activateSession({
39
+ sessionId: ctx.sessionManager.getSessionId(),
40
+ isIdle: () => ctx.isIdle(),
41
+ sendMessage: pi.sendMessage.bind(pi),
42
+ }, refreshWidget);
24
43
  refreshWidget();
25
44
  };
26
- crewManager.onWidgetUpdate = refreshWidget;
27
45
  pi.on("session_start", (_event, ctx) => {
28
46
  rebuildPromptCache(ctx.cwd);
29
47
  activateSession(ctx);
30
48
  });
31
- pi.on("session_switch", (_event, ctx) => activateSession(ctx));
32
- pi.on("session_fork", (_event, ctx) => activateSession(ctx));
49
+ pi.on("session_before_switch", () => {
50
+ // Session is about to switch - no action needed here.
51
+ // Subagent cleanup is handled by process hooks, not session_shutdown.
52
+ });
53
+ pi.on("session_before_fork", () => {
54
+ // Session is about to fork - no action needed here.
55
+ // Subagent cleanup is handled by process hooks, not session_shutdown.
56
+ });
33
57
  pi.on("session_shutdown", (_event, ctx) => {
34
- crewManager.abortForOwner(ctx.sessionManager.getSessionId(), pi);
58
+ const sessionId = ctx.sessionManager.getSessionId();
59
+ // Deactivate delivery to this session, but don't abort subagents.
60
+ // Subagents continue running and will complete normally.
61
+ // Real cleanup happens in process exit hooks.
62
+ crewRuntime.deactivateSession(sessionId);
35
63
  });
36
64
  pi.on("before_agent_start", (event) => {
37
65
  if (!cachedPromptSuffix)
@@ -45,5 +73,5 @@ export default function (pi) {
45
73
  const after = event.systemPrompt.slice(idx);
46
74
  return { systemPrompt: before + cachedPromptSuffix + after };
47
75
  });
48
- registerCrewIntegration(pi, crewManager);
76
+ registerCrewIntegration(pi, crewRuntime, extensionDir);
49
77
  }
@@ -1,3 +1,3 @@
1
1
  import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
2
- import type { CrewManager } from "../crew-manager.js";
3
- export declare function registerCrewCommand(pi: ExtensionAPI, crewManager: CrewManager): void;
2
+ import type { CrewRuntime } from "../runtime/crew-runtime.js";
3
+ export declare function registerCrewCommand(pi: ExtensionAPI, crew: CrewRuntime): void;
@@ -1,8 +1,8 @@
1
- export function registerCrewCommand(pi, crewManager) {
1
+ export function registerCrewCommand(pi, crew) {
2
2
  pi.registerCommand("pi-crew-abort", {
3
3
  description: "Abort an active subagent",
4
4
  getArgumentCompletions(argumentPrefix) {
5
- const activeAgents = crewManager.getAbortableAgents();
5
+ const activeAgents = crew.getAbortableAgents();
6
6
  if (activeAgents.length === 0)
7
7
  return null;
8
8
  return activeAgents
@@ -15,7 +15,7 @@ export function registerCrewCommand(pi, crewManager) {
15
15
  async handler(args, ctx) {
16
16
  const trimmed = args.trim();
17
17
  if (trimmed) {
18
- const success = crewManager.abort(trimmed, pi, { reason: "Aborted by user command" });
18
+ const success = crew.abort(trimmed, { reason: "Aborted by user command" });
19
19
  if (!success) {
20
20
  ctx.ui.notify(`No active subagent with id "${trimmed}"`, "error");
21
21
  }
@@ -24,7 +24,7 @@ export function registerCrewCommand(pi, crewManager) {
24
24
  }
25
25
  return;
26
26
  }
27
- const activeAgents = crewManager.getAbortableAgents();
27
+ const activeAgents = crew.getAbortableAgents();
28
28
  if (activeAgents.length === 0) {
29
29
  ctx.ui.notify("No active subagents", "info");
30
30
  return;
@@ -39,7 +39,7 @@ export function registerCrewCommand(pi, crewManager) {
39
39
  const selectedOption = options.find((option) => option.label === selected);
40
40
  if (!selectedOption)
41
41
  return;
42
- const success = crewManager.abort(selectedOption.id, pi, { reason: "Aborted by user command" });
42
+ const success = crew.abort(selectedOption.id, { reason: "Aborted by user command" });
43
43
  if (success) {
44
44
  ctx.ui.notify(`Subagent ${selectedOption.id} aborted`, "info");
45
45
  }
@@ -26,6 +26,9 @@ export function registerCrewMessageRenderers(pi) {
26
26
  const body = details?.body ?? (!details && message.content ? String(message.content) : undefined);
27
27
  const box = new Box(1, 1, (text) => theme.bg("customMessageBg", text));
28
28
  box.addChild(new Text(header, 0, 0));
29
+ if (details?.sessionFile) {
30
+ box.addChild(new Text(theme.fg("muted", `📁 ${details.sessionFile}`), 0, 0));
31
+ }
29
32
  if (body) {
30
33
  if (expanded) {
31
34
  box.addChild(new Text("", 0, 0));
@@ -1,3 +1,3 @@
1
1
  import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
2
- import type { CrewManager } from "../crew-manager.js";
3
- export declare function registerCrewTools(pi: ExtensionAPI, crewManager: CrewManager): void;
2
+ import type { CrewRuntime } from "../runtime/crew-runtime.js";
3
+ export declare function registerCrewTools(pi: ExtensionAPI, crew: CrewRuntime, extensionDir: string): void;
@@ -3,7 +3,7 @@ import { registerCrewDoneTool } from "./tools/crew-done.js";
3
3
  import { registerCrewListTool } from "./tools/crew-list.js";
4
4
  import { registerCrewRespondTool } from "./tools/crew-respond.js";
5
5
  import { registerCrewSpawnTool } from "./tools/crew-spawn.js";
6
- export function registerCrewTools(pi, crewManager) {
6
+ export function registerCrewTools(pi, crew, extensionDir) {
7
7
  const shownDiscoveryWarnings = new Set();
8
8
  const notifyDiscoveryWarnings = (ctx, warnings) => {
9
9
  if (!ctx.hasUI)
@@ -16,7 +16,7 @@ export function registerCrewTools(pi, crewManager) {
16
16
  ctx.ui.notify(`${warning.message} (${warning.filePath})`, "error");
17
17
  }
18
18
  };
19
- const deps = { pi, crewManager, notifyDiscoveryWarnings };
19
+ const deps = { pi, crew, extensionDir, notifyDiscoveryWarnings };
20
20
  registerCrewListTool(deps);
21
21
  registerCrewSpawnTool(deps);
22
22
  registerCrewAbortTool(deps);
@@ -1,5 +1,5 @@
1
1
  import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
2
- import { Text } from "@mariozechner/pi-tui";
2
+ import { Box, Text } from "@mariozechner/pi-tui";
3
3
  export type ToolTheme = Parameters<Exclude<Parameters<ExtensionAPI["registerTool"]>[0]["renderCall"], undefined>>[1];
4
4
  export type ToolResult = {
5
5
  content: {
@@ -25,6 +25,5 @@ export declare function toolSuccess(text: string, details?: Record<string, unkno
25
25
  }[];
26
26
  details: Record<string, unknown>;
27
27
  };
28
- export declare function truncatePreview(text: string, max: number): string;
29
- export declare function renderCrewCall(theme: ToolTheme, name: string, id: string, preview?: string): Text;
28
+ export declare function renderCrewCall(theme: ToolTheme, name: string, id: string, preview?: string): Box;
30
29
  export declare function renderCrewResult(result: ToolResult, theme: ToolTheme): Text;
@@ -1,4 +1,4 @@
1
- import { Text } from "@mariozechner/pi-tui";
1
+ import { Box, Text } from "@mariozechner/pi-tui";
2
2
  export function toolError(text) {
3
3
  return {
4
4
  content: [{ type: "text", text }],
@@ -12,14 +12,13 @@ export function toolSuccess(text, details = {}) {
12
12
  details,
13
13
  };
14
14
  }
15
- export function truncatePreview(text, max) {
16
- return text.length > max ? `${text.slice(0, max)}...` : text;
17
- }
18
15
  export function renderCrewCall(theme, name, id, preview) {
19
- let text = theme.fg("toolTitle", theme.bold(`${name} `)) + theme.fg("accent", id);
20
- if (preview)
21
- text += theme.fg("dim", ` "${preview}"`);
22
- return new Text(text, 0, 0);
16
+ const box = new Box(1, 1);
17
+ box.addChild(new Text(theme.fg("toolTitle", theme.bold(`${name} `)) + theme.fg("accent", id), 0, 0));
18
+ if (preview) {
19
+ box.addChild(new Text(theme.fg("dim", preview), 0, 0));
20
+ }
21
+ return box;
23
22
  }
24
23
  export function renderCrewResult(result, theme) {
25
24
  const text = result.content[0];
@@ -1,2 +1,2 @@
1
1
  import type { CrewToolDeps } from "./tool-deps.js";
2
- export declare function registerCrewAbortTool({ pi, crewManager }: CrewToolDeps): void;
2
+ export declare function registerCrewAbortTool({ pi, crew }: CrewToolDeps): void;
@@ -13,7 +13,7 @@ function formatAbortToolMessage(result) {
13
13
  }
14
14
  return parts.join("\n");
15
15
  }
16
- export function registerCrewAbortTool({ pi, crewManager }) {
16
+ export function registerCrewAbortTool({ pi, crew }) {
17
17
  pi.registerTool({
18
18
  name: "crew_abort",
19
19
  label: "Abort Crew",
@@ -38,7 +38,7 @@ export function registerCrewAbortTool({ pi, crewManager }) {
38
38
  return toolError("Provide exactly one of: subagent_id, subagent_ids, or all=true.");
39
39
  }
40
40
  if (params.all) {
41
- const abortedIds = crewManager.abortAllOwned(callerSessionId, pi, {
41
+ const abortedIds = crew.abortAllOwned(callerSessionId, {
42
42
  reason: "Aborted by tool request",
43
43
  });
44
44
  if (abortedIds.length === 0) {
@@ -49,7 +49,7 @@ export function registerCrewAbortTool({ pi, crewManager }) {
49
49
  const ids = params.subagent_id
50
50
  ? [params.subagent_id]
51
51
  : (params.subagent_ids ?? []);
52
- const result = crewManager.abortOwned(ids, callerSessionId, pi, {
52
+ const result = crew.abortOwned(ids, callerSessionId, {
53
53
  reason: "Aborted by tool request",
54
54
  });
55
55
  const message = formatAbortToolMessage(result);
@@ -1,2 +1,2 @@
1
1
  import type { CrewToolDeps } from "./tool-deps.js";
2
- export declare function registerCrewDoneTool({ pi, crewManager }: CrewToolDeps): void;
2
+ export declare function registerCrewDoneTool({ pi, crew }: CrewToolDeps): void;
@@ -1,6 +1,6 @@
1
1
  import { Type } from "@sinclair/typebox";
2
2
  import { renderCrewCall, renderCrewResult, toolError, toolSuccess, } from "../tool-presentation.js";
3
- export function registerCrewDoneTool({ pi, crewManager }) {
3
+ export function registerCrewDoneTool({ pi, crew }) {
4
4
  pi.registerTool({
5
5
  name: "crew_done",
6
6
  label: "Done with Crew",
@@ -11,7 +11,7 @@ export function registerCrewDoneTool({ pi, crewManager }) {
11
11
  promptSnippet: "Close an interactive subagent session when done.",
12
12
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
13
13
  const callerSessionId = ctx.sessionManager.getSessionId();
14
- const { error } = crewManager.done(params.subagent_id, callerSessionId);
14
+ const { error } = crew.done(params.subagent_id, callerSessionId);
15
15
  if (error)
16
16
  return toolError(error);
17
17
  return toolSuccess(`Subagent ${params.subagent_id} closed.`, {
@@ -1,2 +1,2 @@
1
1
  import type { CrewToolDeps } from "./tool-deps.js";
2
- export declare function registerCrewListTool({ pi, crewManager, notifyDiscoveryWarnings, }: CrewToolDeps): void;
2
+ export declare function registerCrewListTool({ pi, crew, notifyDiscoveryWarnings, }: CrewToolDeps): void;
@@ -2,18 +2,18 @@ import { Text } from "@mariozechner/pi-tui";
2
2
  import { Type } from "@sinclair/typebox";
3
3
  import { discoverAgents } from "../../agent-discovery.js";
4
4
  import { STATUS_ICON } from "../../subagent-messages.js";
5
- export function registerCrewListTool({ pi, crewManager, notifyDiscoveryWarnings, }) {
5
+ export function registerCrewListTool({ pi, crew, notifyDiscoveryWarnings, }) {
6
6
  pi.registerTool({
7
7
  name: "crew_list",
8
8
  label: "List Crew",
9
- description: "List available subagent definitions (from <cwd>/.pi/agents/, ~/.pi/agent/agents/, and bundled agents, with optional global/project pi-crew.json overrides) and currently running subagents with their status.",
9
+ description: "List available subagent definitions and currently running subagents with their status.",
10
10
  parameters: Type.Object({}),
11
11
  promptSnippet: "List subagent definitions and active subagents",
12
12
  async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
13
13
  const { agents, warnings } = discoverAgents(ctx.cwd);
14
14
  notifyDiscoveryWarnings(ctx, warnings);
15
15
  const callerSessionId = ctx.sessionManager.getSessionId();
16
- const running = crewManager.getActiveSummariesForOwner(callerSessionId);
16
+ const running = crew.getActiveSummariesForOwner(callerSessionId);
17
17
  const lines = [];
18
18
  lines.push("## Available subagents");
19
19
  if (agents.length === 0) {
@@ -1,2 +1,2 @@
1
1
  import type { CrewToolDeps } from "./tool-deps.js";
2
- export declare function registerCrewRespondTool({ pi, crewManager }: CrewToolDeps): void;
2
+ export declare function registerCrewRespondTool({ pi, crew }: CrewToolDeps): void;
@@ -1,10 +1,10 @@
1
1
  import { Type } from "@sinclair/typebox";
2
- import { renderCrewCall, renderCrewResult, toolError, toolSuccess, truncatePreview, } from "../tool-presentation.js";
3
- export function registerCrewRespondTool({ pi, crewManager }) {
2
+ import { renderCrewCall, renderCrewResult, toolError, toolSuccess, } from "../tool-presentation.js";
3
+ export function registerCrewRespondTool({ pi, crew }) {
4
4
  pi.registerTool({
5
5
  name: "crew_respond",
6
6
  label: "Respond to Crew",
7
- description: "Send a follow-up message to an interactive subagent that is waiting for a response. Use crew_list to see waiting subagents.",
7
+ description: "Send a follow-up message to an interactive subagent that is waiting for a response.",
8
8
  parameters: Type.Object({
9
9
  subagent_id: Type.String({
10
10
  description: "ID of the waiting subagent (from crew_list or crew_spawn result)",
@@ -14,14 +14,13 @@ export function registerCrewRespondTool({ pi, crewManager }) {
14
14
  promptSnippet: "Send a follow-up message to a waiting interactive subagent.",
15
15
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
16
16
  const callerSessionId = ctx.sessionManager.getSessionId();
17
- const { error } = crewManager.respond(params.subagent_id, params.message, pi, callerSessionId);
17
+ const { error } = crew.respond(params.subagent_id, params.message, callerSessionId);
18
18
  if (error)
19
19
  return toolError(error);
20
- return toolSuccess(`Message sent to subagent ${params.subagent_id}. Response will be delivered as a steering message.`, { id: params.subagent_id });
20
+ return toolSuccess(`Message sent to subagent ${params.subagent_id}. Response will be delivered as a steering message.`, { id: params.subagent_id, message: params.message });
21
21
  },
22
22
  renderCall(args, theme, _context) {
23
- const preview = args.message ? truncatePreview(args.message, 60) : "...";
24
- return renderCrewCall(theme, "crew_respond", args.subagent_id || "...", preview);
23
+ return renderCrewCall(theme, "crew_respond", args.subagent_id || "...", args.message);
25
24
  },
26
25
  renderResult(result, _options, theme, _context) {
27
26
  return renderCrewResult(result, theme);
@@ -1,2 +1,2 @@
1
1
  import type { CrewToolDeps } from "./tool-deps.js";
2
- export declare function registerCrewSpawnTool({ pi, crewManager, notifyDiscoveryWarnings, }: CrewToolDeps): void;
2
+ export declare function registerCrewSpawnTool({ pi, crew, extensionDir, notifyDiscoveryWarnings, }: CrewToolDeps): void;
@@ -1,24 +1,23 @@
1
1
  import { Type } from "@sinclair/typebox";
2
2
  import { discoverAgents } from "../../agent-discovery.js";
3
- import { renderCrewCall, renderCrewResult, toolError, toolSuccess, truncatePreview, } from "../tool-presentation.js";
4
- export function registerCrewSpawnTool({ pi, crewManager, notifyDiscoveryWarnings, }) {
3
+ import { renderCrewCall, renderCrewResult, toolError, toolSuccess, } from "../tool-presentation.js";
4
+ export function registerCrewSpawnTool({ pi, crew, extensionDir, notifyDiscoveryWarnings, }) {
5
5
  pi.registerTool({
6
6
  name: "crew_spawn",
7
7
  label: "Spawn Crew",
8
- description: "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 when done. NEVER PREDICT or FABRICATE results for subagents that have not yet reported back to you. Use crew_list first to see available subagents.",
8
+ description: "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.",
9
9
  parameters: Type.Object({
10
10
  subagent: Type.String({ description: "Subagent name from crew_list" }),
11
11
  task: Type.String({ description: "Task to delegate to the subagent" }),
12
12
  }),
13
13
  promptSnippet: "Spawn a non-blocking subagent. Use crew_list first to see available subagents.",
14
14
  promptGuidelines: [
15
- "Use crew_* tools to delegate parallelizable, independent tasks to specialized subagents. For interactive multi-turn workflows, use crew_respond/crew_done. Avoid spawning for trivial, single-turn tasks.",
16
- "crew_spawn: Always call crew_list first to see which subagents are available before spawning.",
17
- "crew_spawn: The spawned subagent runs in a separate context window with no access to your session. Include all relevant context (file paths, requirements, prior findings) directly in the task parameter.",
18
- "crew_spawn: Results are delivered asynchronously as steering messages. Do not block or poll for completion. If there are other independent tasks to handle, continue with those; otherwise wait for the user's next instruction or the subagent result.",
19
- "crew_spawn: NEVER perform the same work you delegated to a subagent. Once a task is spawned, trust the subagent to do it. Do not run the same searches, reads, or analysis yourself while waiting. You may only gather context BEFORE spawning to prepare the task description. After spawning, move on to other independent work or simply wait for the result.",
20
- "crew_spawn: When multiple subagents are spawned, each result arrives as a separate steering message. NEVER PREDICT or FABRICATE results for subagents that have not yet reported back to you. Wait for ALL crew-result messages.",
21
- "crew_spawn: Interactive subagents (marked with 'interactive' in crew_list) stay alive after responding. Use crew_respond to continue the conversation and crew_done to close when finished.",
15
+ "Use crew_list first to see available subagents before spawning.",
16
+ "crew_spawn: The subagent runs in isolation with no access to your session. Include file paths, requirements, and known locations directly in the task parameter.",
17
+ "crew_spawn: DELEGATE means STOP. After spawning, either work on an UNRELATED task or end your turn. Never continue the delegated task yourself.",
18
+ "crew_spawn: To avoid duplication, gather only enough context to write a useful task (key files, entry points). Do not pre-investigate the full problem.",
19
+ "crew_spawn: Results arrive asynchronously as steering messages. Do not predict or fabricate results. Wait for all crew-result messages before acting on them.",
20
+ "crew_spawn: Interactive subagents stay alive after responding. Use crew_respond to continue and crew_done to close when finished.",
22
21
  ],
23
22
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
24
23
  const { agents, warnings } = discoverAgents(ctx.cwd);
@@ -29,12 +28,16 @@ export function registerCrewSpawnTool({ pi, crewManager, notifyDiscoveryWarnings
29
28
  return toolError(`Unknown subagent: "${params.subagent}". Available: ${available}`);
30
29
  }
31
30
  const ownerSessionId = ctx.sessionManager.getSessionId();
32
- const id = crewManager.spawn(subagent, params.task, ctx.cwd, ownerSessionId, ctx, pi);
33
- return toolSuccess(`Subagent '${subagent.name}' spawned as ${id}. Result will be delivered as a steering message when done.`, { id });
31
+ const id = crew.spawn(subagent, params.task, ctx.cwd, ownerSessionId, {
32
+ model: ctx.model,
33
+ modelRegistry: ctx.modelRegistry,
34
+ parentSessionFile: ctx.sessionManager.getSessionFile(),
35
+ onWarning: (msg) => ctx.ui.notify(msg, "warning"),
36
+ }, extensionDir);
37
+ return toolSuccess(`Subagent '${subagent.name}' spawned as ${id}. Result will be delivered as a steering message when done.`, { id, agentName: subagent.name, task: params.task });
34
38
  },
35
39
  renderCall(args, theme, _context) {
36
- const preview = args.task ? truncatePreview(args.task, 60) : "...";
37
- return renderCrewCall(theme, "crew_spawn", args.subagent || "...", preview);
40
+ return renderCrewCall(theme, "crew_spawn", args.subagent || "...", args.task);
38
41
  },
39
42
  renderResult(result, _options, theme, _context) {
40
43
  return renderCrewResult(result, theme);
@@ -1,8 +1,9 @@
1
1
  import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
2
2
  import type { AgentDiscoveryWarning } from "../../agent-discovery.js";
3
- import type { CrewManager } from "../../crew-manager.js";
3
+ import type { CrewRuntime } from "../../runtime/crew-runtime.js";
4
4
  export interface CrewToolDeps {
5
5
  pi: ExtensionAPI;
6
- crewManager: CrewManager;
6
+ crew: CrewRuntime;
7
+ extensionDir: string;
7
8
  notifyDiscoveryWarnings: (ctx: ExtensionContext, warnings: AgentDiscoveryWarning[]) => void;
8
9
  }
@@ -1,3 +1,3 @@
1
1
  import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
2
- import type { CrewManager } from "./crew-manager.js";
3
- export declare function registerCrewIntegration(pi: ExtensionAPI, crewManager: CrewManager): void;
2
+ import type { CrewRuntime } from "./runtime/crew-runtime.js";
3
+ export declare function registerCrewIntegration(pi: ExtensionAPI, crew: CrewRuntime, extensionDir: string): void;
@@ -1,8 +1,8 @@
1
1
  import { registerCrewCommand } from "./integration/register-command.js";
2
2
  import { registerCrewMessageRenderers } from "./integration/register-renderers.js";
3
3
  import { registerCrewTools } from "./integration/register-tools.js";
4
- export function registerCrewIntegration(pi, crewManager) {
5
- registerCrewTools(pi, crewManager);
6
- registerCrewCommand(pi, crewManager);
4
+ export function registerCrewIntegration(pi, crew, extensionDir) {
5
+ registerCrewTools(pi, crew, extensionDir);
6
+ registerCrewCommand(pi, crew);
7
7
  registerCrewMessageRenderers(pi);
8
8
  }
@@ -0,0 +1,61 @@
1
+ import type { Api, Model } from "@mariozechner/pi-ai";
2
+ import type { ModelRegistry } from "@mariozechner/pi-coding-agent";
3
+ import type { AgentConfig } from "../agent-discovery.js";
4
+ import { type ActiveRuntimeBinding } from "./delivery-coordinator.js";
5
+ import { type AbortableAgentSummary, type ActiveAgentSummary } from "./subagent-state.js";
6
+ export type { AbortableAgentSummary, ActiveAgentSummary, } from "./subagent-state.js";
7
+ export interface AbortOwnedResult {
8
+ abortedIds: string[];
9
+ missingIds: string[];
10
+ foreignIds: string[];
11
+ }
12
+ interface AbortOptions {
13
+ reason: string;
14
+ }
15
+ export interface SpawnContext {
16
+ model: Model<Api> | undefined;
17
+ modelRegistry: ModelRegistry;
18
+ parentSessionFile?: string;
19
+ onWarning?: (message: string) => void;
20
+ }
21
+ /**
22
+ * Process-level singleton that owns all durable subagent state.
23
+ *
24
+ * This survives extension instance replacement caused by runtime
25
+ * teardown/recreation on /resume, /new, /fork (pi 0.65.0+).
26
+ * Each new extension instance rebinds delivery and widget hooks
27
+ * via activateSession/deactivateSession.
28
+ */
29
+ declare class CrewRuntime {
30
+ private readonly registry;
31
+ private readonly delivery;
32
+ private readonly refreshCallbacks;
33
+ private refreshWidgetFor;
34
+ activateSession(binding: ActiveRuntimeBinding, refreshWidget?: () => void): void;
35
+ deactivateSession(sessionId: string): void;
36
+ spawn(agentConfig: AgentConfig, task: string, cwd: string, ownerSessionId: string, ctx: SpawnContext, extensionResolvedPath: string): string;
37
+ private attachSessionListeners;
38
+ private attachSpawnedSession;
39
+ private settleAgent;
40
+ private disposeAgent;
41
+ private runPromptCycle;
42
+ private spawnSession;
43
+ respond(id: string, message: string, callerSessionId: string): {
44
+ error?: string;
45
+ };
46
+ done(id: string, callerSessionId: string): {
47
+ error?: string;
48
+ };
49
+ abort(id: string, opts: AbortOptions): boolean;
50
+ abortOwned(ids: string[], callerSessionId: string, opts: AbortOptions): AbortOwnedResult;
51
+ abortAllOwned(callerSessionId: string, opts: AbortOptions): string[];
52
+ /**
53
+ * Abort all running subagents (process-level cleanup).
54
+ * Called from process exit hooks.
55
+ */
56
+ abortAll(): void;
57
+ getAbortableAgents(): AbortableAgentSummary[];
58
+ getActiveSummariesForOwner(ownerSessionId: string): ActiveAgentSummary[];
59
+ }
60
+ export declare const crewRuntime: CrewRuntime;
61
+ export type { CrewRuntime };