@melihmucuk/pi-crew 1.0.11 → 1.0.13

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.
package/README.md CHANGED
@@ -58,7 +58,7 @@ Supported modes:
58
58
  "abort all active subagents"
59
59
  ```
60
60
 
61
- Tool-triggered aborts are reported back as steering messages with the reason `Aborted by tool request`.
61
+ Tool-triggered aborts are reported back as steering messages with the reason `Aborted by tool request`. User-command aborts and shutdown-triggered aborts use distinct reasons.
62
62
 
63
63
  ### `crew_respond`
64
64
 
@@ -195,6 +195,8 @@ Override values replace the matching frontmatter fields for the named subagent a
195
195
 
196
196
  When the current session owns active subagents, a live status widget appears in the TUI for that session, showing each subagent's ID, model, turn count, and context token usage.
197
197
 
198
+ On session replacement paths such as `/new`, `/resume`, `/fork`, and `/reload`, subagents keep running and reconnect to the owner session when it becomes active again. On real quit, pi-crew aborts running subagents during shutdown.
199
+
198
200
  ```
199
201
  ⠹ scout-a1b2 (claude-haiku-4-5) · turn 3 · 12.5k ctx
200
202
  ⠸ worker-c3d4 (claude-sonnet-4-6) · turn 7 · 45.2k ctx
@@ -4,6 +4,7 @@ import type { AgentConfig } from "./agent-discovery.js";
4
4
  export interface BootstrapContext {
5
5
  model: Model<Api> | undefined;
6
6
  modelRegistry: ModelRegistry;
7
+ agentDir: string;
7
8
  parentSessionFile?: string;
8
9
  }
9
10
  interface BootstrapOptions {
@@ -1,7 +1,7 @@
1
1
  import { createAgentSession, DefaultResourceLoader, SessionManager, SettingsManager, } from "@mariozechner/pi-coding-agent";
2
- import { createSupportedTools, SUPPORTED_TOOL_NAMES } from "./tool-registry.js";
3
- function resolveTools(agentConfig, cwd) {
4
- return createSupportedTools(agentConfig.tools ?? SUPPORTED_TOOL_NAMES, cwd);
2
+ import { SUPPORTED_TOOL_NAMES } from "./tool-registry.js";
3
+ function resolveTools(agentConfig) {
4
+ return [...(agentConfig.tools ?? SUPPORTED_TOOL_NAMES)];
5
5
  }
6
6
  function resolveModel(agentConfig, ctx) {
7
7
  const warnings = [];
@@ -33,9 +33,10 @@ export async function bootstrapSession(opts) {
33
33
  const modelRegistry = ctx.modelRegistry;
34
34
  const { model, warnings: modelWarnings } = resolveModel(agentConfig, ctx);
35
35
  warnings.push(...modelWarnings);
36
- const tools = resolveTools(agentConfig, cwd);
36
+ const tools = resolveTools(agentConfig);
37
37
  const resourceLoader = new DefaultResourceLoader({
38
38
  cwd,
39
+ agentDir: ctx.agentDir,
39
40
  extensionsOverride: (base) => ({
40
41
  ...base,
41
42
  extensions: base.extensions.filter((ext) => !ext.resolvedPath.startsWith(extensionResolvedPath)),
@@ -59,6 +60,7 @@ export async function bootstrapSession(opts) {
59
60
  sessionManager.newSession({ parentSession: ctx.parentSessionFile });
60
61
  const result = await createAgentSession({
61
62
  cwd,
63
+ agentDir: ctx.agentDir,
62
64
  model,
63
65
  thinkingLevel: agentConfig.thinking,
64
66
  tools,
package/dist/index.js CHANGED
@@ -10,13 +10,10 @@ function setupProcessHooks() {
10
10
  if (processHooksSetup)
11
11
  return;
12
12
  processHooksSetup = true;
13
- const abortAndExit = (signal) => {
13
+ process.once('SIGINT', () => {
14
14
  crewRuntime.abortAll();
15
- // Re-raise to restore default Node termination behavior
16
- process.exit(128 + (signal === 'SIGINT' ? 2 : 15));
17
- };
18
- process.once('SIGINT', () => abortAndExit('SIGINT'));
19
- process.once('SIGTERM', () => abortAndExit('SIGTERM'));
15
+ process.exit(130);
16
+ });
20
17
  process.on('beforeExit', () => crewRuntime.abortAll());
21
18
  }
22
19
  export default function (pi) {
@@ -38,20 +35,12 @@ export default function (pi) {
38
35
  pi.on("session_start", (_event, ctx) => {
39
36
  activateSession(ctx);
40
37
  });
41
- pi.on("session_before_switch", () => {
42
- // Session is about to switch - no action needed here.
43
- // Subagent cleanup is handled by process hooks, not session_shutdown.
44
- });
45
- pi.on("session_before_fork", () => {
46
- // Session is about to fork - no action needed here.
47
- // Subagent cleanup is handled by process hooks, not session_shutdown.
48
- });
49
- pi.on("session_shutdown", (_event, ctx) => {
38
+ pi.on("session_shutdown", (event, ctx) => {
50
39
  const sessionId = ctx.sessionManager.getSessionId();
51
- // Deactivate delivery to this session, but don't abort subagents.
52
- // Subagents continue running and will complete normally.
53
- // Real cleanup happens in process exit hooks.
54
40
  crewRuntime.deactivateSession(sessionId);
41
+ if (event.reason === "quit") {
42
+ crewRuntime.abortAll();
43
+ }
55
44
  });
56
45
  registerCrewIntegration(pi, crewRuntime, extensionDir);
57
46
  }
@@ -15,6 +15,11 @@ function getStatusColor(status) {
15
15
  return "muted";
16
16
  }
17
17
  }
18
+ function renderWarningMessage(content, theme) {
19
+ const box = new Box(1, 1, (text) => theme.bg("customMessageBg", text));
20
+ box.addChild(new Text(theme.fg("warning", String(content ?? "")), 0, 0));
21
+ return box;
22
+ }
18
23
  export function registerCrewMessageRenderers(pi) {
19
24
  pi.registerMessageRenderer("crew-result", (message, { expanded }, theme) => {
20
25
  const details = message.details;
@@ -46,8 +51,9 @@ export function registerCrewMessageRenderers(pi) {
46
51
  return box;
47
52
  });
48
53
  pi.registerMessageRenderer("crew-remaining", (message, _options, theme) => {
49
- const box = new Box(1, 1, (text) => theme.bg("customMessageBg", text));
50
- box.addChild(new Text(theme.fg("warning", String(message.content ?? "")), 0, 0));
51
- return box;
54
+ return renderWarningMessage(message.content, theme);
55
+ });
56
+ pi.registerMessageRenderer("crew-list-warning", (message, _options, theme) => {
57
+ return renderWarningMessage(message.content, theme);
52
58
  });
53
59
  }
@@ -1,13 +1,8 @@
1
+ import type { AgentToolResult } from "@mariozechner/pi-agent-core";
1
2
  import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
2
3
  import { Box, Text } from "@mariozechner/pi-tui";
3
4
  export type ToolTheme = Parameters<Exclude<Parameters<ExtensionAPI["registerTool"]>[0]["renderCall"], undefined>>[1];
4
- export type ToolResult = {
5
- content: {
6
- type: string;
7
- text?: string;
8
- }[];
9
- details: unknown;
10
- };
5
+ export type ToolResult = AgentToolResult<unknown>;
11
6
  export declare function toolError(text: string): {
12
7
  content: {
13
8
  type: "text";
@@ -18,7 +13,10 @@ export declare function toolError(text: string): {
18
13
  error: boolean;
19
14
  };
20
15
  };
21
- export declare function toolSuccess(text: string, details?: Record<string, unknown>): {
16
+ export declare function toolSuccess(text: string, details?: Record<string, unknown>, options?: {
17
+ terminate?: boolean;
18
+ }): {
19
+ terminate?: boolean | undefined;
22
20
  content: {
23
21
  type: "text";
24
22
  text: string;
@@ -6,10 +6,11 @@ export function toolError(text) {
6
6
  details: { error: true },
7
7
  };
8
8
  }
9
- export function toolSuccess(text, details = {}) {
9
+ export function toolSuccess(text, details = {}, options = {}) {
10
10
  return {
11
11
  content: [{ type: "text", text }],
12
12
  details,
13
+ ...(options.terminate ? { terminate: true } : {}),
13
14
  };
14
15
  }
15
16
  export function renderCrewCall(theme, name, id, preview) {
@@ -1,4 +1,4 @@
1
- import { Type } from "@sinclair/typebox";
1
+ import { Type } from "typebox";
2
2
  import { renderCrewCall, renderCrewResult, toolError, toolSuccess, } from "../tool-presentation.js";
3
3
  function formatAbortToolMessage(result) {
4
4
  const parts = [];
@@ -44,7 +44,7 @@ export function registerCrewAbortTool({ pi, crew }) {
44
44
  if (abortedIds.length === 0) {
45
45
  return toolError("No active subagents in the current session.");
46
46
  }
47
- return toolSuccess(`Aborted ${abortedIds.length} subagent(s): ${abortedIds.join(", ")}`, { ids: abortedIds });
47
+ return toolSuccess(`Aborted ${abortedIds.length} subagent(s): ${abortedIds.join(", ")}`, { ids: abortedIds }, { terminate: true });
48
48
  }
49
49
  const ids = params.subagent_id
50
50
  ? [params.subagent_id]
@@ -60,7 +60,7 @@ export function registerCrewAbortTool({ pi, crew }) {
60
60
  ids: result.abortedIds,
61
61
  missing_ids: result.missingIds,
62
62
  foreign_ids: result.foreignIds,
63
- });
63
+ }, { terminate: true });
64
64
  },
65
65
  renderCall(args, theme, _context) {
66
66
  if (args.all) {
@@ -1,4 +1,4 @@
1
- import { Type } from "@sinclair/typebox";
1
+ import { Type } from "typebox";
2
2
  import { renderCrewCall, renderCrewResult, toolError, toolSuccess, } from "../tool-presentation.js";
3
3
  export function registerCrewDoneTool({ pi, crew }) {
4
4
  pi.registerTool({
@@ -1,7 +1,7 @@
1
1
  import { Text } from "@mariozechner/pi-tui";
2
- import { Type } from "@sinclair/typebox";
2
+ import { Type } from "typebox";
3
3
  import { discoverAgents } from "../../agent-discovery.js";
4
- import { STATUS_ICON } from "../../subagent-messages.js";
4
+ import { STATUS_ICON, sendCrewListActiveWarning } from "../../subagent-messages.js";
5
5
  export function registerCrewListTool({ pi, crew, notifyDiscoveryWarnings, }) {
6
6
  pi.registerTool({
7
7
  name: "crew_list",
@@ -19,10 +19,6 @@ export function registerCrewListTool({ pi, crew, notifyDiscoveryWarnings, }) {
19
19
  const callerSessionId = ctx.sessionManager.getSessionId();
20
20
  const running = crew.getActiveSummariesForOwner(callerSessionId);
21
21
  const lines = [];
22
- if (running.length > 0) {
23
- lines.push("⚠ Active subagents detected. Do not poll crew_list for completion — results arrive as steering messages. Continue with unrelated work or end your turn and wait for the steering messages.");
24
- lines.push("");
25
- }
26
22
  lines.push("## Available Subagents");
27
23
  if (agents.length === 0) {
28
24
  lines.push("No valid subagent definitions found. Add `.md` files to `<cwd>/.pi/agents/` or `~/.pi/agent/agents/`.");
@@ -54,11 +50,17 @@ export function registerCrewListTool({ pi, crew, notifyDiscoveryWarnings, }) {
54
50
  lines.push(`id: ${agent.id}`);
55
51
  lines.push(`name: ${agent.agentName}`);
56
52
  lines.push(`status: ${icon} ${agent.status}`);
57
- lines.push(`task: ${agent.taskPreview}`);
58
- lines.push(`turns: ${agent.turns}`);
59
53
  }
60
54
  }
61
55
  const text = lines.join("\n");
56
+ if (running.length > 0) {
57
+ Promise.resolve().then(() => {
58
+ sendCrewListActiveWarning(pi.sendMessage.bind(pi), {
59
+ isIdle: ctx.isIdle(),
60
+ triggerTurn: true,
61
+ });
62
+ });
63
+ }
62
64
  return { content: [{ type: "text", text }], details: {} };
63
65
  },
64
66
  renderCall(_args, theme, _context) {
@@ -1,4 +1,4 @@
1
- import { Type } from "@sinclair/typebox";
1
+ import { Type } from "typebox";
2
2
  import { renderCrewCall, renderCrewResult, toolError, toolSuccess, } from "../tool-presentation.js";
3
3
  export function registerCrewRespondTool({ pi, crew }) {
4
4
  pi.registerTool({
@@ -1,4 +1,5 @@
1
- import { Type } from "@sinclair/typebox";
1
+ import { getAgentDir } from "@mariozechner/pi-coding-agent";
2
+ import { Type } from "typebox";
2
3
  import { discoverAgents } from "../../agent-discovery.js";
3
4
  import { renderCrewCall, renderCrewResult, toolError, toolSuccess, } from "../tool-presentation.js";
4
5
  export function registerCrewSpawnTool({ pi, crew, extensionDir, notifyDiscoveryWarnings, }) {
@@ -31,6 +32,7 @@ export function registerCrewSpawnTool({ pi, crew, extensionDir, notifyDiscoveryW
31
32
  const id = crew.spawn(subagent, params.task, ctx.cwd, ownerSessionId, {
32
33
  model: ctx.model,
33
34
  modelRegistry: ctx.modelRegistry,
35
+ agentDir: getAgentDir(),
34
36
  parentSessionFile: ctx.sessionManager.getSessionFile(),
35
37
  onWarning: (msg) => ctx.ui.notify(msg, "warning"),
36
38
  }, extensionDir);
@@ -15,6 +15,7 @@ interface AbortOptions {
15
15
  export interface SpawnContext {
16
16
  model: Model<Api> | undefined;
17
17
  modelRegistry: ModelRegistry;
18
+ agentDir: string;
18
19
  parentSessionFile?: string;
19
20
  onWarning?: (message: string) => void;
20
21
  }
@@ -50,8 +51,8 @@ declare class CrewRuntime {
50
51
  abortOwned(ids: string[], callerSessionId: string, opts: AbortOptions): AbortOwnedResult;
51
52
  abortAllOwned(callerSessionId: string, opts: AbortOptions): string[];
52
53
  /**
53
- * Abort all running subagents (process-level cleanup).
54
- * Called from process exit hooks.
54
+ * Abort all running subagents during shutdown cleanup.
55
+ * Called from SIGINT, session_shutdown(reason="quit"), and beforeExit fallback paths.
55
56
  */
56
57
  abortAll(): void;
57
58
  getAbortableAgents(): AbortableAgentSummary[];
@@ -7,6 +7,7 @@ function toBootstrapContext(ctx) {
7
7
  return {
8
8
  model: ctx.model,
9
9
  modelRegistry: ctx.modelRegistry,
10
+ agentDir: ctx.agentDir,
10
11
  parentSessionFile: ctx.parentSessionFile,
11
12
  };
12
13
  }
@@ -265,13 +266,13 @@ class CrewRuntime {
265
266
  return ids;
266
267
  }
267
268
  /**
268
- * Abort all running subagents (process-level cleanup).
269
- * Called from process exit hooks.
269
+ * Abort all running subagents during shutdown cleanup.
270
+ * Called from SIGINT, session_shutdown(reason="quit"), and beforeExit fallback paths.
270
271
  */
271
272
  abortAll() {
272
273
  const allAgents = this.registry.getAllRunning();
273
274
  for (const state of allAgents) {
274
- this.abort(state.id, { reason: "Aborted on process exit" });
275
+ this.abort(state.id, { reason: "Aborted during shutdown" });
275
276
  }
276
277
  }
277
278
  getAbortableAgents() {
@@ -20,7 +20,6 @@ export interface ActiveAgentSummary {
20
20
  id: string;
21
21
  agentName: string;
22
22
  status: SubagentStatus;
23
- taskPreview: string;
24
23
  turns: number;
25
24
  contextTokens: number;
26
25
  model: string | undefined;
@@ -15,12 +15,10 @@ export function isAbortableStatus(status) {
15
15
  return status === "running" || status === "waiting";
16
16
  }
17
17
  export function buildActiveAgentSummary(state) {
18
- const taskPreview = state.task.length > 80 ? `${state.task.slice(0, 80)}...` : state.task;
19
18
  return {
20
19
  id: state.id,
21
20
  agentName: state.agentConfig.name,
22
21
  status: state.status,
23
- taskPreview,
24
22
  turns: state.turns,
25
23
  contextTokens: state.contextTokens,
26
24
  model: state.model,
@@ -31,3 +31,7 @@ export declare function sendRemainingNote(remainingCount: number, sendMessage: S
31
31
  isIdle: boolean;
32
32
  triggerTurn: boolean;
33
33
  }): void;
34
+ export declare function sendCrewListActiveWarning(sendMessage: SendMessageFn, opts: {
35
+ isIdle: boolean;
36
+ triggerTurn: boolean;
37
+ }): void;
@@ -57,3 +57,12 @@ export function sendRemainingNote(remainingCount, sendMessage, opts) {
57
57
  ? { triggerTurn: opts.triggerTurn }
58
58
  : { deliverAs: "steer", triggerTurn: opts.triggerTurn });
59
59
  }
60
+ export function sendCrewListActiveWarning(sendMessage, opts) {
61
+ sendMessage({
62
+ customType: "crew-list-warning",
63
+ content: "⚠ Active subagents detected. Do not poll crew_list for completion — results arrive as steering messages. Continue with unrelated work or end your turn and wait for the steering messages.",
64
+ display: true,
65
+ }, opts.isIdle
66
+ ? { triggerTurn: opts.triggerTurn }
67
+ : { deliverAs: "steer", triggerTurn: opts.triggerTurn });
68
+ }
@@ -1,76 +1,5 @@
1
- declare const TOOL_FACTORIES: {
2
- read: (cwd: string) => import("@mariozechner/pi-agent-core").AgentTool<import("@sinclair/typebox").TObject<{
3
- path: import("@sinclair/typebox").TString;
4
- offset: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
5
- limit: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
6
- }>, any>;
7
- bash: (cwd: string) => import("@mariozechner/pi-agent-core").AgentTool<import("@sinclair/typebox").TObject<{
8
- command: import("@sinclair/typebox").TString;
9
- timeout: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
10
- }>, any>;
11
- edit: (cwd: string) => import("@mariozechner/pi-agent-core").AgentTool<import("@sinclair/typebox").TObject<{
12
- path: import("@sinclair/typebox").TString;
13
- edits: import("@sinclair/typebox").TArray<import("@sinclair/typebox").TObject<{
14
- oldText: import("@sinclair/typebox").TString;
15
- newText: import("@sinclair/typebox").TString;
16
- }>>;
17
- }>, any>;
18
- write: (cwd: string) => import("@mariozechner/pi-agent-core").AgentTool<import("@sinclair/typebox").TObject<{
19
- path: import("@sinclair/typebox").TString;
20
- content: import("@sinclair/typebox").TString;
21
- }>, any>;
22
- grep: (cwd: string) => import("@mariozechner/pi-agent-core").AgentTool<import("@sinclair/typebox").TObject<{
23
- pattern: import("@sinclair/typebox").TString;
24
- path: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
25
- glob: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
26
- ignoreCase: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
27
- literal: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
28
- context: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
29
- limit: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
30
- }>, any>;
31
- find: (cwd: string) => import("@mariozechner/pi-agent-core").AgentTool<import("@sinclair/typebox").TObject<{
32
- pattern: import("@sinclair/typebox").TString;
33
- path: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
34
- limit: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
35
- }>, any>;
36
- ls: (cwd: string) => import("@mariozechner/pi-agent-core").AgentTool<import("@sinclair/typebox").TObject<{
37
- path: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
38
- limit: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
39
- }>, any>;
40
- };
41
- export type SupportedToolName = keyof typeof TOOL_FACTORIES;
1
+ declare const SUPPORTED_TOOL_NAMES_LITERAL: readonly ["read", "bash", "edit", "write", "grep", "find", "ls"];
2
+ export type SupportedToolName = (typeof SUPPORTED_TOOL_NAMES_LITERAL)[number];
42
3
  export declare const SUPPORTED_TOOL_NAMES: readonly ("read" | "bash" | "edit" | "write" | "grep" | "find" | "ls")[];
43
4
  export declare function isSupportedToolName(name: string): name is SupportedToolName;
44
- export declare function createSupportedTools(toolNames: readonly SupportedToolName[], cwd: string): (import("@mariozechner/pi-agent-core").AgentTool<import("@sinclair/typebox").TObject<{
45
- path: import("@sinclair/typebox").TString;
46
- offset: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
47
- limit: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
48
- }>, any> | import("@mariozechner/pi-agent-core").AgentTool<import("@sinclair/typebox").TObject<{
49
- command: import("@sinclair/typebox").TString;
50
- timeout: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
51
- }>, any> | import("@mariozechner/pi-agent-core").AgentTool<import("@sinclair/typebox").TObject<{
52
- path: import("@sinclair/typebox").TString;
53
- edits: import("@sinclair/typebox").TArray<import("@sinclair/typebox").TObject<{
54
- oldText: import("@sinclair/typebox").TString;
55
- newText: import("@sinclair/typebox").TString;
56
- }>>;
57
- }>, any> | import("@mariozechner/pi-agent-core").AgentTool<import("@sinclair/typebox").TObject<{
58
- path: import("@sinclair/typebox").TString;
59
- content: import("@sinclair/typebox").TString;
60
- }>, any> | import("@mariozechner/pi-agent-core").AgentTool<import("@sinclair/typebox").TObject<{
61
- pattern: import("@sinclair/typebox").TString;
62
- path: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
63
- glob: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
64
- ignoreCase: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
65
- literal: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
66
- context: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
67
- limit: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
68
- }>, any> | import("@mariozechner/pi-agent-core").AgentTool<import("@sinclair/typebox").TObject<{
69
- pattern: import("@sinclair/typebox").TString;
70
- path: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
71
- limit: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
72
- }>, any> | import("@mariozechner/pi-agent-core").AgentTool<import("@sinclair/typebox").TObject<{
73
- path: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
74
- limit: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
75
- }>, any>)[];
76
5
  export {};
@@ -1,17 +1,13 @@
1
- import { createBashTool, createEditTool, createFindTool, createGrepTool, createLsTool, createReadTool, createWriteTool, } from "@mariozechner/pi-coding-agent";
2
- const TOOL_FACTORIES = {
3
- read: (cwd) => createReadTool(cwd),
4
- bash: (cwd) => createBashTool(cwd),
5
- edit: (cwd) => createEditTool(cwd),
6
- write: (cwd) => createWriteTool(cwd),
7
- grep: (cwd) => createGrepTool(cwd),
8
- find: (cwd) => createFindTool(cwd),
9
- ls: (cwd) => createLsTool(cwd),
10
- };
11
- export const SUPPORTED_TOOL_NAMES = Object.freeze(Object.keys(TOOL_FACTORIES));
1
+ const SUPPORTED_TOOL_NAMES_LITERAL = [
2
+ "read",
3
+ "bash",
4
+ "edit",
5
+ "write",
6
+ "grep",
7
+ "find",
8
+ "ls",
9
+ ];
10
+ export const SUPPORTED_TOOL_NAMES = Object.freeze([...SUPPORTED_TOOL_NAMES_LITERAL]);
12
11
  export function isSupportedToolName(name) {
13
- return name in TOOL_FACTORIES;
14
- }
15
- export function createSupportedTools(toolNames, cwd) {
16
- return toolNames.map((toolName) => TOOL_FACTORIES[toolName](cwd));
12
+ return SUPPORTED_TOOL_NAMES.includes(name);
17
13
  }
@@ -13,6 +13,7 @@ Primary components:
13
13
  - `extension/runtime/crew-runtime.ts` - Process-level singleton owning all subagent state
14
14
  - `extension/runtime/subagent-registry.ts` - In-memory subagent registry
15
15
  - `extension/runtime/delivery-coordinator.ts` - Owner-based result routing
16
+ - `extension/runtime/overflow-recovery.ts` - Context overflow retry tracking for subagent prompts
16
17
  - `extension/bootstrap-session.ts` - Subagent session construction with extension filtering
17
18
  - `extension/agent-discovery.ts` - Subagent definition discovery and validation
18
19
 
@@ -20,12 +21,13 @@ Primary components:
20
21
 
21
22
  ### 2.1 CrewRuntime singleton
22
23
 
23
- `CrewRuntime` is a process-level singleton that survives pi runtime replacement (`/resume`, `/new`, `/fork`). When pi discards an old extension instance and creates a new one, the new instance reconnects to the same `crewRuntime` and picks up existing subagent state.
24
+ `CrewRuntime` is a process-level singleton that survives pi runtime replacement (`/resume`, `/new`, `/fork`, `/reload`). When pi discards an old extension instance and creates a new one, the new instance reconnects to the same `crewRuntime` and picks up existing subagent state.
24
25
 
25
26
  Responsibilities:
26
27
 
27
28
  - Create subagent state records
28
29
  - Bootstrap isolated subagent sessions
30
+ - Run subagent prompt cycles with overflow recovery
29
31
  - Transition subagents between states
30
32
  - Deliver results to owner sessions
31
33
 
@@ -35,12 +37,24 @@ Routes subagent results to the correct session at the correct time. Key behavior
35
37
 
36
38
  - Tracks active session via `ActiveRuntimeBinding` (set on `session_start`, cleared on `session_shutdown`)
37
39
  - Queues results when owner session is inactive
38
- - Flushes queued results when owner session activates (`session_start` with `reason: "resume"` or `"fork"`)
40
+ - Flushes queued results when owner session activates on any `session_start`; resume/fork are the important replacement paths because subagents survive runtime replacement within the same process
39
41
  - Uses `triggerTurn: false/true` split to preserve ordering between `crew-result` and `crew-remaining`
40
42
 
41
43
  Underlying delivery: see pi's `sendMessage({ deliverAs, triggerTurn })` in extensions.md.
42
44
 
43
- ### 2.3 Subagent registry
45
+ `crew_list` uses the same idle/streaming delivery rules for its `crew-list-warning` custom message when active subagents exist. The warning is separate from tool output so the list remains a one-time snapshot while anti-polling guidance is delivered as a visible message.
46
+
47
+ ### 2.3 Overflow recovery
48
+
49
+ Subagent prompt cycles are wrapped by overflow recovery tracking. The tracker observes `agent_end`, `compaction_start`, `compaction_end`, `auto_retry_start`, and `auto_retry_end` events to distinguish normal completion from context-overflow compaction and retry.
50
+
51
+ Outcomes:
52
+
53
+ - No overflow observed → prompt outcome is based on the final assistant message.
54
+ - Overflow compaction completes with retry and the retry reaches a terminal `agent_end` → recovered; prompt outcome is based on the final assistant message.
55
+ - Overflow handling times out, is cancelled, or compaction does not retry → failed; the subagent settles as `error` unless the final assistant message already reported an error.
56
+
57
+ ### 2.4 Subagent registry
44
58
 
45
59
  In-memory, process-scoped: `Map<subagentId, SubagentState>`
46
60
 
@@ -80,7 +94,7 @@ Critical: `deliverAs: "steer"` to an idle session leaves the message unprocessed
80
94
 
81
95
  ### 4.3 Deferred flush
82
96
 
83
- Pending message flush after `session_start` is deferred to next macrotask. Synchronous delivery loses custom message persistence (pi-core emits `session_start` before reconnecting agent listener during resume).
97
+ Pending message flush after `session_start` is deferred to next macrotask. Synchronous delivery loses custom message persistence (pi-core emits `session_start` before reconnecting agent listener during resume). While a flush is scheduled, new deliveries for that owner are queued so ordering is preserved.
84
98
 
85
99
  ### 4.4 TTL cleanup
86
100
 
@@ -109,13 +123,17 @@ After prompt cycle completion, inspect assistant stop reason:
109
123
 
110
124
  `interactive: true` subagents enter `waiting` after each response. They accept follow-up messages via `crew_respond` until explicitly closed with `crew_done`. Closing does NOT emit a duplicate `crew-result`.
111
125
 
126
+ ### 5.4 Tool completion behavior
127
+
128
+ `crew_respond` returns immediately and delivers the subagent response asynchronously. Successful `crew_abort` results terminate the current tool turn after aborting owned subagents.
129
+
112
130
  ## 6. Ownership and isolation
113
131
 
114
132
  Invariants:
115
133
 
116
134
  1. `crew_list`, `crew_abort`, `crew_respond`, `crew_done`, status widget: session-scoped. Only owner sees/controls.
117
135
  2. `/pi-crew-abort`: cross-session emergency escape hatch.
118
- 3. `session_shutdown` deactivates delivery binding only; subagents continue running. Abort happens on process exit.
136
+ 3. `session_shutdown` always deactivates delivery binding. On replacement paths (`reload`, `new`, `resume`, `fork`), subagents continue running. On `quit`, the extension aborts all running subagents. `SIGINT` also aborts via a process hook, and `beforeExit` remains a fallback.
119
137
 
120
138
  ## 7. Subagent definition model
121
139
 
@@ -145,8 +163,10 @@ JSON overrides: `~/.pi/agent/pi-crew.json` (global), `<cwd>/.pi/pi-crew.json` (p
145
163
  5. `crew_done` cleans up only; no duplicate result message.
146
164
  6. Queued results flush when owner session becomes active.
147
165
  7. `crew-result` messages appear before `crew-remaining` notes (ordering via `triggerTurn` split).
148
- 8. Pending messages preserved for inactive sessions; TTL (24h) prevents memory leak.
149
- 9. Active subagent state survives runtime replacement within same process.
166
+ 8. `crew-list-warning` is delivered as a separate custom message when `crew_list` is called while the owner has active subagents.
167
+ 9. Pending messages preserved for inactive sessions; TTL (24h) prevents memory leak.
168
+ 10. Active subagent state survives runtime replacement within same process.
169
+ 11. Graceful quit aborts subagents through `session_shutdown.reason === "quit"`; replacement paths do not.
150
170
 
151
171
  ## 9. Reading guide
152
172
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@melihmucuk/pi-crew",
3
- "version": "1.0.11",
3
+ "version": "1.0.13",
4
4
  "type": "module",
5
5
  "description": "Non-blocking subagent orchestration for pi coding agent",
6
6
  "files": [
@@ -39,15 +39,15 @@
39
39
  "@mariozechner/pi-ai": "*",
40
40
  "@mariozechner/pi-coding-agent": "*",
41
41
  "@mariozechner/pi-tui": "*",
42
- "@sinclair/typebox": "*"
42
+ "typebox": "*"
43
43
  },
44
44
  "devDependencies": {
45
- "@mariozechner/pi-agent-core": "^0.65.0",
46
- "@mariozechner/pi-ai": "^0.65.0",
47
- "@mariozechner/pi-coding-agent": "^0.65.0",
48
- "@mariozechner/pi-tui": "^0.65.0",
49
- "@sinclair/typebox": "^0.34.49",
45
+ "@mariozechner/pi-agent-core": "^0.70.0",
46
+ "@mariozechner/pi-ai": "^0.70.0",
47
+ "@mariozechner/pi-coding-agent": "^0.70.0",
48
+ "@mariozechner/pi-tui": "^0.70.0",
50
49
  "@types/node": "^22.19.17",
50
+ "typebox": "^1.1.33",
51
51
  "typescript": "^5.9.3"
52
52
  }
53
- }
53
+ }