pi-crew 0.2.4 → 0.2.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-crew",
3
- "version": "0.2.4",
3
+ "version": "0.2.6",
4
4
  "description": "Pi extension for coordinated AI teams, workflows, worktrees, and async task orchestration",
5
5
  "author": "baphuongna",
6
6
  "license": "MIT",
@@ -80,7 +80,7 @@
80
80
  "cli-highlight": "^2.1.11",
81
81
  "diff": "^5.2.0",
82
82
  "jiti": "^2.6.1",
83
- "@sinclair/typebox": "^1.1.24"
83
+ "@sinclair/typebox": "^0.34.49"
84
84
  },
85
85
  "devDependencies": {
86
86
  "@mariozechner/pi-agent-core": "^0.65.0",
@@ -319,7 +319,16 @@ export function registerPiTeams(pi: ExtensionAPI): void {
319
319
  // immediate cache invalidate via renderScheduler.schedule. Falls back to
320
320
  // poll-only behavior on systems where fs.watch errors.
321
321
  let crewWatcher: import("node:fs").FSWatcher | undefined;
322
+ // Separate map for foreground team-run AbortControllers (distinct from subagent controllers).
323
+ // P0 fix: stopSessionBoundSubagents must NOT abort foreground team runs on session switch.
324
+ // Foreground team runs run in the same process as the session; they naturally clean up
325
+ // when the session context is torn down. Only subagents need explicit abort on switch.
326
+ const foregroundTeamRunControllers = new Map<string | symbol, AbortController>();
327
+
322
328
  const stopSessionBoundSubagents = (): void => {
329
+ // Only abort subagent controllers — NOT foreground team runs.
330
+ // Foreground team runs are bound to the session lifecycle; they will be aborted
331
+ // by cleanupRuntime during session_shutdown.
323
332
  for (const controller of foregroundControllers.values()) controller.abort();
324
333
  foregroundControllers.clear();
325
334
  subagentManager.abortAll("Session switching — foreground subagents cancelled.");
@@ -361,7 +370,7 @@ export function registerPiTeams(pi: ExtensionAPI): void {
361
370
  const ownerGeneration = captureSessionGeneration();
362
371
  const controller = new AbortController();
363
372
  const key = runId ?? Symbol();
364
- foregroundControllers.set(key, controller);
373
+ foregroundTeamRunControllers.set(key, controller);
365
374
  if (ctx.hasUI) {
366
375
  setWorkingIndicator(ctx, { frames: ["⣾", "⣽", "⣻", "⢿", "⡿", "⣟", "⣯", "⣷"], intervalMs: 80 });
367
376
  ctx.ui.setWorkingMessage(runId ? `pi-crew foreground run ${runId}...` : "pi-crew foreground run...");
@@ -382,7 +391,7 @@ export function registerPiTeams(pi: ExtensionAPI): void {
382
391
  else logInternalError("register.foreground-run-failure", error, `runId=${runId} context disposed`);
383
392
  })
384
393
  .finally(() => {
385
- foregroundControllers.delete(key);
394
+ foregroundTeamRunControllers.delete(key);
386
395
  const ownerCurrent = isContextCurrent(ctx, ownerGeneration);
387
396
  if (ctx.hasUI) {
388
397
  // Always clear working message/spinner — stale spinners for completed runs are confusing.
@@ -439,11 +448,15 @@ export function registerPiTeams(pi: ExtensionAPI): void {
439
448
  if (preloadTimer) { clearTimeout(preloadTimer); preloadTimer = undefined; }
440
449
  closeWatcher(crewWatcher); crewWatcher = undefined;
441
450
  stopSessionBoundSubagents();
451
+ // P0 fix: also abort foreground team runs on session shutdown (not on session switch).
452
+ // This is the only place where foreground team run controllers should be aborted.
453
+ for (const controller of foregroundTeamRunControllers.values()) controller.abort();
454
+ foregroundTeamRunControllers.clear();
442
455
  crewScheduler?.stop();
443
456
  stopAsyncRunNotifier(notifierState);
444
457
 
445
458
  // Best-effort: kill any async background runners that are still alive.
446
- // Foreground child processes are already handled by stopSessionBoundSubagents().
459
+ // Foreground child processes (team run tasks) are handled above.
447
460
  try {
448
461
  for (const manifest of manifestCache.list(50)) {
449
462
  if (manifest.async?.pid !== undefined && checkProcessLiveness(manifest.async.pid).alive) {
@@ -815,12 +828,12 @@ export function registerPiTeams(pi: ExtensionAPI): void {
815
828
  } catch { /* older Pi without resources_discover */ }
816
829
 
817
830
  const abortForegroundRun = (runId: string): boolean => {
818
- const controller = foregroundControllers.get(runId);
831
+ const controller = foregroundTeamRunControllers.get(runId);
819
832
  if (!controller) return false;
820
833
  controller.abort();
821
834
  return true;
822
835
  };
823
- registerCompactionGuard(pi, { foregroundControllers });
836
+ registerCompactionGuard(pi, { foregroundControllers, foregroundTeamRunControllers });
824
837
 
825
838
  // Phase 1.4: Permission gate for destructive team actions.
826
839
  // AGENTS.md requires confirm=true for management deletes.
@@ -4,6 +4,7 @@ import type { ArtifactDescriptor, TeamRunManifest } from "../../state/types.ts";
4
4
 
5
5
  export interface RegisterCompactionGuardOptions {
6
6
  foregroundControllers: Map<string | symbol, AbortController>;
7
+ foregroundTeamRunControllers: Map<string | symbol, AbortController>;
7
8
  }
8
9
 
9
10
  const TRIGGER_RATIO = 0.75;
@@ -95,7 +96,7 @@ export function registerCompactionGuard(pi: ExtensionAPI, options: RegisterCompa
95
96
 
96
97
  // Phase 1.2: Defer compaction during foreground runs unless context is critically full.
97
98
  pi.on("session_before_compact", async (_event, ctx) => {
98
- if (options.foregroundControllers.size === 0) return;
99
+ if (options.foregroundControllers.size === 0 && options.foregroundTeamRunControllers.size === 0) return;
99
100
  const ratio = usageRatio(ctx);
100
101
  if (ratio !== undefined && ratio >= HARD_RATIO) {
101
102
  ctx.ui.notify("Compaction allowed despite foreground run: context is critically full", "warning");
@@ -109,14 +110,15 @@ export function registerCompactionGuard(pi: ExtensionAPI, options: RegisterCompa
109
110
  // Phase 2.1: Proactive compaction with dynamic threshold based on model context window.
110
111
  pi.on("turn_end", (_event, ctx) => {
111
112
  if (compactionInProgress) return;
112
- if (options.foregroundControllers.size === 0 && pendingCompactReason) {
113
+ const hasActiveForeground = options.foregroundControllers.size > 0 || options.foregroundTeamRunControllers.size > 0;
114
+ if (!hasActiveForeground && pendingCompactReason) {
113
115
  pendingCompactReason = null;
114
116
  startCompact(ctx, "deferred");
115
117
  return;
116
118
  }
117
119
  const ratio = usageRatio(ctx);
118
120
  if (ratio === undefined || ratio < TRIGGER_RATIO) return;
119
- if (options.foregroundControllers.size > 0) {
121
+ if (hasActiveForeground) {
120
122
  pendingCompactReason = "threshold-during-foreground-run";
121
123
  return;
122
124
  }