@melihmucuk/pi-crew 1.0.11 → 1.0.12

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
  }
@@ -1,3 +1,4 @@
1
+ import { getAgentDir } from "@mariozechner/pi-coding-agent";
1
2
  import { Type } from "@sinclair/typebox";
2
3
  import { discoverAgents } from "../../agent-discovery.js";
3
4
  import { renderCrewCall, renderCrewResult, toolError, toolSuccess, } from "../tool-presentation.js";
@@ -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() {
@@ -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
  }
@@ -20,7 +20,7 @@ Primary components:
20
20
 
21
21
  ### 2.1 CrewRuntime singleton
22
22
 
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.
23
+ `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
24
 
25
25
  Responsibilities:
26
26
 
@@ -35,7 +35,7 @@ Routes subagent results to the correct session at the correct time. Key behavior
35
35
 
36
36
  - Tracks active session via `ActiveRuntimeBinding` (set on `session_start`, cleared on `session_shutdown`)
37
37
  - Queues results when owner session is inactive
38
- - Flushes queued results when owner session activates (`session_start` with `reason: "resume"` or `"fork"`)
38
+ - 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
39
  - Uses `triggerTurn: false/true` split to preserve ordering between `crew-result` and `crew-remaining`
40
40
 
41
41
  Underlying delivery: see pi's `sendMessage({ deliverAs, triggerTurn })` in extensions.md.
@@ -115,7 +115,7 @@ Invariants:
115
115
 
116
116
  1. `crew_list`, `crew_abort`, `crew_respond`, `crew_done`, status widget: session-scoped. Only owner sees/controls.
117
117
  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.
118
+ 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
119
 
120
120
  ## 7. Subagent definition model
121
121
 
@@ -147,6 +147,7 @@ JSON overrides: `~/.pi/agent/pi-crew.json` (global), `<cwd>/.pi/pi-crew.json` (p
147
147
  7. `crew-result` messages appear before `crew-remaining` notes (ordering via `triggerTurn` split).
148
148
  8. Pending messages preserved for inactive sessions; TTL (24h) prevents memory leak.
149
149
  9. Active subagent state survives runtime replacement within same process.
150
+ 10. Graceful quit aborts subagents through `session_shutdown.reason === "quit"`; replacement paths do not.
150
151
 
151
152
  ## 9. Reading guide
152
153
 
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.12",
4
4
  "type": "module",
5
5
  "description": "Non-blocking subagent orchestration for pi coding agent",
6
6
  "files": [
@@ -42,10 +42,10 @@
42
42
  "@sinclair/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",
45
+ "@mariozechner/pi-agent-core": "^0.68.0",
46
+ "@mariozechner/pi-ai": "^0.68.0",
47
+ "@mariozechner/pi-coding-agent": "^0.68.0",
48
+ "@mariozechner/pi-tui": "^0.68.0",
49
49
  "@sinclair/typebox": "^0.34.49",
50
50
  "@types/node": "^22.19.17",
51
51
  "typescript": "^5.9.3"