pi-crew 0.9.49 → 0.9.51

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 (44) hide show
  1. package/CHANGELOG.md +324 -0
  2. package/dist/build-meta.json +84 -57
  3. package/dist/index.mjs +224 -96
  4. package/dist/index.mjs.map +4 -4
  5. package/docs/decisions/2026-07-26-c6-mascot-visibility-not-wired.md +84 -0
  6. package/package.json +1 -2
  7. package/skills/distill-persona/SKILL.md +83 -145
  8. package/skills/distill-persona/references/cross-skill-differentiation.md +12 -0
  9. package/skills/distill-persona/references/description-discipline.md +6 -0
  10. package/skills/distill-persona/references/diagnostic-path.md +25 -0
  11. package/skills/distill-persona/references/fidelity-rubric.md +19 -0
  12. package/skills/distill-persona/references/field-models.md +20 -0
  13. package/skills/distill-persona/references/optional-body-sections.md +9 -0
  14. package/skills/distill-persona/references/registry-routing.md +11 -0
  15. package/skills/distill-persona/references/self-upgrade-directive.md +20 -0
  16. package/skills/distill-persona/references/taste-principles.md +8 -0
  17. package/skills/distill-persona/references/topic-variant.md +13 -0
  18. package/skills/distill-persona/references/update-mode.md +7 -0
  19. package/skills/distill-persona/scripts/validate-run.mjs +297 -0
  20. package/skills/distill-software/SKILL.md +174 -90
  21. package/skills/research/SKILL.md +1 -1
  22. package/src/extension/crew-cleanup.ts +18 -1
  23. package/src/extension/crew-vibes/index.ts +11 -2
  24. package/src/extension/register.ts +1 -1
  25. package/src/extension/registration/command-registration.ts +1 -0
  26. package/src/extension/registration/commands.ts +7 -3
  27. package/src/extension/registration/lifecycle-handlers.ts +1 -3
  28. package/src/extension/registration/ui.ts +4 -0
  29. package/src/extension/registration/viewers.ts +3 -0
  30. package/src/extension/team-tool/run.ts +7 -6
  31. package/src/runtime/chain-runner.ts +3 -2
  32. package/src/runtime/pipeline-runner.ts +8 -7
  33. package/src/ui/live-run-sidebar.ts +7 -13
  34. package/src/ui/loaders.ts +6 -176
  35. package/src/ui/mascot.ts +25 -10
  36. package/src/ui/render-coalescer.ts +9 -0
  37. package/src/ui/render-scheduler.ts +60 -6
  38. package/src/ui/run-dashboard.ts +12 -21
  39. package/src/ui/run-snapshot-cache.ts +10 -11
  40. package/src/ui/shared-overlay-scheduler.ts +96 -0
  41. package/src/ui/terminal-status.ts +5 -0
  42. package/src/ui/widget/index.ts +48 -16
  43. package/src/ui/widget/widget-types.ts +0 -1
  44. package/assets/runner-spritesheet.png +0 -0
package/dist/index.mjs CHANGED
@@ -6893,8 +6893,8 @@ function extractErrorMessage(event) {
6893
6893
  const obj = asRecord3(event);
6894
6894
  if (!obj) return void 0;
6895
6895
  const message = asRecord3(obj.message) ?? obj;
6896
- const errorMessage = message.errorMessage;
6897
- return typeof errorMessage === "string" && errorMessage.trim() ? errorMessage.trim() : void 0;
6896
+ const errorMessage2 = message.errorMessage;
6897
+ return typeof errorMessage2 === "string" && errorMessage2.trim() ? errorMessage2.trim() : void 0;
6898
6898
  }
6899
6899
  function extractPatch(event, patches) {
6900
6900
  const obj = asRecord3(event);
@@ -8078,7 +8078,8 @@ var init_child_pi = __esm({
8078
8078
  });
8079
8079
 
8080
8080
  // src/extension/crew-cleanup.ts
8081
- function registerCleanupHandler(pi) {
8081
+ function registerCleanupHandler(pi, opts) {
8082
+ terminalStatusDispose = opts?.disposeTerminalStatus;
8082
8083
  pi.on("session_shutdown", async () => {
8083
8084
  console.log("[pi-crew] Session shutdown - cleaning up resources");
8084
8085
  try {
@@ -8093,6 +8094,10 @@ function registerCleanupHandler(pi) {
8093
8094
  signalHandlersRegistered = true;
8094
8095
  const handleSignal = async (signal) => {
8095
8096
  console.log(`[pi-crew] Received ${signal} - starting cleanup`);
8097
+ try {
8098
+ terminalStatusDispose?.();
8099
+ } catch {
8100
+ }
8096
8101
  await cleanupChildProcesses();
8097
8102
  };
8098
8103
  process.on("SIGTERM", () => {
@@ -8140,7 +8145,7 @@ function registerChildProcess(pid, runId, agentId) {
8140
8145
  function unregisterChildProcess(pid) {
8141
8146
  childProcessRegistry.unregister(pid);
8142
8147
  }
8143
- var signalHandlersRegistered, ChildProcessRegistry, childProcessRegistry;
8148
+ var signalHandlersRegistered, ChildProcessRegistry, childProcessRegistry, terminalStatusDispose;
8144
8149
  var init_crew_cleanup = __esm({
8145
8150
  "src/extension/crew-cleanup.ts"() {
8146
8151
  "use strict";
@@ -40864,6 +40869,14 @@ var init_render_coalescer = __esm({
40864
40869
  this.#timerId = null;
40865
40870
  }
40866
40871
  this.#pending = false;
40872
+ const dropped = this.#dropped;
40873
+ this.#dropped = 0;
40874
+ if (dropped > 0) {
40875
+ try {
40876
+ this.#onDrop(dropped);
40877
+ } catch {
40878
+ }
40879
+ }
40867
40880
  this.#callback();
40868
40881
  }
40869
40882
  /** Check if a render is pending. */
@@ -42571,7 +42584,7 @@ var init_tool_renderers = __esm({
42571
42584
  });
42572
42585
 
42573
42586
  // src/ui/render-scheduler.ts
42574
- var DEFAULT_EVENTS, RenderScheduler;
42587
+ var DEFAULT_EVENTS, CAP_BACKOFF_MAX_EXP, RenderScheduler;
42575
42588
  var init_render_scheduler = __esm({
42576
42589
  "src/ui/render-scheduler.ts"() {
42577
42590
  "use strict";
@@ -42586,6 +42599,7 @@ var init_render_scheduler = __esm({
42586
42599
  "crew.mailbox.updated",
42587
42600
  "crew.mailbox.message"
42588
42601
  ];
42602
+ CAP_BACKOFF_MAX_EXP = 5;
42589
42603
  RenderScheduler = class {
42590
42604
  render;
42591
42605
  onInvalidate;
@@ -42593,6 +42607,7 @@ var init_render_scheduler = __esm({
42593
42607
  fallbackProvider;
42594
42608
  fallbackMs;
42595
42609
  invalidateCoalesceMs;
42610
+ maxIdleFallbackRenders;
42596
42611
  debounceTimer;
42597
42612
  fallbackTimer;
42598
42613
  invalidateTimer;
@@ -42602,6 +42617,10 @@ var init_render_scheduler = __esm({
42602
42617
  lastEventAt = 0;
42603
42618
  rendering = false;
42604
42619
  pendingRender = false;
42620
+ /** R1: consecutive idle fallback renders since the last real event. */
42621
+ idleFallbackRenders = 0;
42622
+ /** R4: consecutive re-entrancy cap-hits — drives exponential backoff. */
42623
+ consecutiveCapHits = 0;
42605
42624
  unsubs = [];
42606
42625
  constructor(events, render, options = {}) {
42607
42626
  this.render = render;
@@ -42611,6 +42630,7 @@ var init_render_scheduler = __esm({
42611
42630
  this.fallbackProvider = typeof fallback2 === "function" ? fallback2 : () => fallback2;
42612
42631
  this.fallbackMs = typeof fallback2 === "number" ? fallback2 : 750;
42613
42632
  this.invalidateCoalesceMs = options.invalidateCoalesceMs ?? 50;
42633
+ this.maxIdleFallbackRenders = options.maxIdleFallbackRenders ?? 8;
42614
42634
  for (const event of options.events ?? DEFAULT_EVENTS) this.subscribe(events, event);
42615
42635
  this.fallbackTimer = setTimeout(() => this.fallbackLoop(), this.currentFallbackMs());
42616
42636
  this.fallbackTimer.unref();
@@ -42639,25 +42659,44 @@ var init_render_scheduler = __esm({
42639
42659
  if (this.disposed) return;
42640
42660
  const fallbackMs = this.currentFallbackMs();
42641
42661
  if (Date.now() - this.lastEventAt < fallbackMs) {
42642
- if (this.disposed) return;
42662
+ this.idleFallbackRenders = 0;
42643
42663
  this.fallbackTimer = setTimeout(() => this.fallbackLoop(), fallbackMs);
42644
42664
  this.fallbackTimer.unref();
42645
42665
  return;
42646
42666
  }
42647
- this.schedule();
42648
- if (this.disposed) return;
42667
+ if (this.idleFallbackRenders >= this.maxIdleFallbackRenders) {
42668
+ this.fallbackTimer = void 0;
42669
+ return;
42670
+ }
42671
+ this.armDebouncedRender();
42672
+ this.idleFallbackRenders += 1;
42649
42673
  this.fallbackTimer = setTimeout(() => this.fallbackLoop(), this.currentFallbackMs());
42650
42674
  this.fallbackTimer.unref();
42651
42675
  }
42652
42676
  schedule(payload) {
42653
42677
  if (this.disposed) return;
42654
42678
  this.lastEventAt = Date.now();
42679
+ this.idleFallbackRenders = 0;
42680
+ this.consecutiveCapHits = 0;
42655
42681
  this.invalidate(payload);
42682
+ this.armDebouncedRender();
42683
+ if (!this.fallbackTimer) {
42684
+ this.fallbackTimer = setTimeout(() => this.fallbackLoop(), this.currentFallbackMs());
42685
+ this.fallbackTimer.unref();
42686
+ }
42687
+ }
42688
+ /**
42689
+ * Arm (or re-arm) the debounce timer that triggers a single flush. Shared by
42690
+ * real-event schedules, the fallback path, and the re-entrancy cap drain.
42691
+ * `delay` lets the cap apply exponential backoff (R4) without touching
42692
+ * `lastEventAt` or `onInvalidate` (those belong to real external events).
42693
+ */
42694
+ armDebouncedRender(delay = this.debounceMs) {
42656
42695
  if (this.debounceTimer) clearTimeout(this.debounceTimer);
42657
42696
  this.debounceTimer = setTimeout(() => {
42658
42697
  this.debounceTimer = void 0;
42659
42698
  this.flush();
42660
- }, this.debounceMs);
42699
+ }, delay);
42661
42700
  this.debounceTimer.unref();
42662
42701
  }
42663
42702
  /**
@@ -42724,7 +42763,11 @@ var init_render_scheduler = __esm({
42724
42763
  } finally {
42725
42764
  this.rendering = false;
42726
42765
  if (iterations >= 5 && this.pendingRender && !this.disposed) {
42727
- this.schedule();
42766
+ this.consecutiveCapHits += 1;
42767
+ const exp = Math.min(this.consecutiveCapHits - 1, CAP_BACKOFF_MAX_EXP);
42768
+ this.armDebouncedRender(this.debounceMs * 2 ** exp);
42769
+ } else {
42770
+ this.consecutiveCapHits = 0;
42728
42771
  }
42729
42772
  }
42730
42773
  }
@@ -42750,6 +42793,69 @@ var init_render_scheduler = __esm({
42750
42793
  }
42751
42794
  });
42752
42795
 
42796
+ // src/ui/shared-overlay-scheduler.ts
42797
+ function ensureShared() {
42798
+ if (shared) return shared;
42799
+ const renderers = /* @__PURE__ */ new Set();
42800
+ const invalidators = /* @__PURE__ */ new Set();
42801
+ const scheduler = new RenderScheduler(
42802
+ runEventBusAsRenderScheduler([...CHANNELS]),
42803
+ () => {
42804
+ for (const render of renderers) {
42805
+ try {
42806
+ render();
42807
+ } catch (error) {
42808
+ logInternalError("shared-overlay-scheduler.render", error);
42809
+ }
42810
+ }
42811
+ },
42812
+ {
42813
+ debounceMs: 75,
42814
+ fallbackMs: 750,
42815
+ events: [...CHANNELS],
42816
+ onInvalidate: (payload) => {
42817
+ for (const invalidate of invalidators) {
42818
+ try {
42819
+ invalidate(payload);
42820
+ } catch (error) {
42821
+ logInternalError("shared-overlay-scheduler.invalidate", error);
42822
+ }
42823
+ }
42824
+ }
42825
+ }
42826
+ );
42827
+ shared = { scheduler, renderers, invalidators };
42828
+ return shared;
42829
+ }
42830
+ function registerOverlayScheduler(render, onInvalidate) {
42831
+ const state = ensureShared();
42832
+ state.renderers.add(render);
42833
+ if (onInvalidate) state.invalidators.add(onInvalidate);
42834
+ return {
42835
+ schedule: () => {
42836
+ state.scheduler.schedule();
42837
+ },
42838
+ dispose: () => {
42839
+ state.renderers.delete(render);
42840
+ if (onInvalidate) state.invalidators.delete(onInvalidate);
42841
+ if (state.renderers.size === 0) {
42842
+ state.scheduler.dispose();
42843
+ if (shared === state) shared = void 0;
42844
+ }
42845
+ }
42846
+ };
42847
+ }
42848
+ var CHANNELS, shared;
42849
+ var init_shared_overlay_scheduler = __esm({
42850
+ "src/ui/shared-overlay-scheduler.ts"() {
42851
+ "use strict";
42852
+ init_internal_error();
42853
+ init_render_scheduler();
42854
+ init_run_event_bus();
42855
+ CHANNELS = ["run:state", "worker:lifecycle", "ui:invalidate"];
42856
+ }
42857
+ });
42858
+
42753
42859
  // src/runtime/stale-reconciler.ts
42754
42860
  import * as fs67 from "node:fs";
42755
42861
  import * as os14 from "node:os";
@@ -44021,8 +44127,6 @@ function updateCrewWidget(ctx, state, config, manifestCache2, snapshotCache, pre
44021
44127
  requestRender(ctx);
44022
44128
  }
44023
44129
  function stopCrewWidget(ctx, state, config) {
44024
- if (state.interval) clearInterval(state.interval);
44025
- state.interval = void 0;
44026
44130
  if (ctx?.hasUI) {
44027
44131
  const placement = config?.widgetPlacement ?? DEFAULT_UI.widgetPlacement;
44028
44132
  ctx.ui.setStatus(STATUS_KEY, void 0);
@@ -44036,15 +44140,14 @@ function stopCrewWidget(ctx, state, config) {
44036
44140
  requestRender(ctx);
44037
44141
  }
44038
44142
  }
44039
- var MAX_LINES_DEFAULT, LEGACY_WIDGET_KEY, WIDGET_KEY, STATUS_KEY, resizeListenerInstalled, activeResizeTarget, CrewWidgetComponent;
44143
+ var MAX_LINES_DEFAULT, LEGACY_WIDGET_KEY, WIDGET_KEY, STATUS_KEY, SIGNATURE_CACHE_TTL_MS, resizeListenerInstalled, activeResizeTarget, CrewWidgetComponent;
44040
44144
  var init_widget = __esm({
44041
44145
  "src/ui/widget/index.ts"() {
44042
44146
  "use strict";
44043
44147
  init_defaults();
44044
44148
  init_visual();
44045
44149
  init_pi_ui_compat();
44046
- init_render_scheduler();
44047
- init_run_event_bus();
44150
+ init_shared_overlay_scheduler();
44048
44151
  init_spinner();
44049
44152
  init_theme_adapter();
44050
44153
  init_widget_model();
@@ -44057,18 +44160,22 @@ var init_widget = __esm({
44057
44160
  LEGACY_WIDGET_KEY = "pi-crew";
44058
44161
  WIDGET_KEY = "pi-crew-active";
44059
44162
  STATUS_KEY = "pi-crew";
44163
+ SIGNATURE_CACHE_TTL_MS = 100;
44060
44164
  resizeListenerInstalled = false;
44061
44165
  CrewWidgetComponent = class {
44062
44166
  model;
44063
44167
  theme;
44064
44168
  cacheSignature = "";
44169
+ /** C4 — invalidate-on-write cache for the buildSignature() result. */
44170
+ cachedBuildSignature = "";
44171
+ cachedBuildSignatureAt = 0;
44065
44172
  cachedWidth = 0;
44066
44173
  cachedLines = [];
44067
44174
  cachedBaseLines = [];
44068
44175
  cachedTheme;
44069
44176
  tui;
44070
44177
  unsubscribeTheme;
44071
- renderScheduler;
44178
+ schedulerHandle;
44072
44179
  constructor(model, themeLike, tui) {
44073
44180
  this.model = model;
44074
44181
  this.theme = asCrewTheme(themeLike);
@@ -44077,13 +44184,11 @@ var init_widget = __esm({
44077
44184
  activeResizeTarget = this;
44078
44185
  installResizeListener();
44079
44186
  this.unsubscribeTheme = subscribeThemeChange(themeLike, () => this.invalidate());
44080
- this.renderScheduler = new RenderScheduler(
44081
- runEventBusAsRenderScheduler(["run:state", "worker:lifecycle", "ui:invalidate"]),
44187
+ this.schedulerHandle = registerOverlayScheduler(
44082
44188
  () => this.invalidate(),
44083
- {
44084
- debounceMs: 75,
44085
- fallbackMs: 750,
44086
- events: ["run:state", "worker:lifecycle", "ui:invalidate"]
44189
+ () => {
44190
+ this.cachedBuildSignature = "";
44191
+ this.cachedBuildSignatureAt = 0;
44087
44192
  }
44088
44193
  );
44089
44194
  }
@@ -44093,7 +44198,7 @@ var init_widget = __esm({
44093
44198
  ).join("|");
44094
44199
  const hasRunning = runs.some((entry) => entry.agents.some((a) => a.status === "running")) || [...listLiveAgents()].some((h) => h.status === "running");
44095
44200
  const animation = hasRunning ? `:spin=${spinnerBucket()}` : "";
44096
- return runs.map(
44201
+ const sig = runs.map(
44097
44202
  (entry) => entry.snapshot?.signature ?? `${entry.run.runId}:${entry.run.status}:${entry.run.updatedAt}:` + entry.agents.map((a) => {
44098
44203
  const recentOutput = a.progress?.recentOutput.at(-1) ?? "";
44099
44204
  const progress = [
@@ -44107,6 +44212,7 @@ var init_widget = __esm({
44107
44212
  return `${a.status}:${a.startedAt}:${a.completedAt ?? ""}:${a.toolUses ?? 0}:${progress}`;
44108
44213
  }).join(",")
44109
44214
  ).join("|") + `|live:${liveSig}${animation}`;
44215
+ return sig;
44110
44216
  }
44111
44217
  colorize(lines, width) {
44112
44218
  return renderLines(
@@ -44118,6 +44224,8 @@ var init_widget = __esm({
44118
44224
  this.cacheSignature = "";
44119
44225
  this.cachedBaseLines = [];
44120
44226
  this.cachedLines = [];
44227
+ this.cachedBuildSignature = "";
44228
+ this.cachedBuildSignatureAt = 0;
44121
44229
  }
44122
44230
  /** Poke the host TUI to repaint immediately (defensive: no-op if unavailable). */
44123
44231
  requestRepaint() {
@@ -44125,12 +44233,21 @@ var init_widget = __esm({
44125
44233
  }
44126
44234
  dispose() {
44127
44235
  this.unsubscribeTheme();
44128
- this.renderScheduler.dispose();
44236
+ this.schedulerHandle.dispose();
44129
44237
  if (activeResizeTarget === this) activeResizeTarget = void 0;
44130
44238
  }
44131
44239
  render(width) {
44132
44240
  const runs = activeWidgetRuns(this.model.cwd, this.model.manifestCache, this.model.snapshotCache, this.model.preloadManifests);
44133
- const signature = `${this.buildSignature(runs)}:${this.model.notificationCount ?? 0}`;
44241
+ const now = Date.now();
44242
+ let sigBase;
44243
+ if (this.cachedBuildSignature !== "" && now - this.cachedBuildSignatureAt < SIGNATURE_CACHE_TTL_MS) {
44244
+ sigBase = this.cachedBuildSignature;
44245
+ } else {
44246
+ sigBase = this.buildSignature(runs);
44247
+ this.cachedBuildSignature = sigBase;
44248
+ this.cachedBuildSignatureAt = now;
44249
+ }
44250
+ const signature = `${sigBase}:${this.model.notificationCount ?? 0}`;
44134
44251
  const runningGlyph = spinnerFrame("widget-header");
44135
44252
  if (this.cacheSignature !== signature || width !== this.cachedWidth || this.cachedTheme !== this.theme) {
44136
44253
  this.cachedBaseLines = buildWidgetLines(
@@ -54169,6 +54286,16 @@ var init_team_runner = __esm({
54169
54286
  }
54170
54287
  });
54171
54288
 
54289
+ // src/utils/guards.ts
54290
+ function errorMessage(err2) {
54291
+ return err2 instanceof Error ? err2.message : String(err2);
54292
+ }
54293
+ var init_guards = __esm({
54294
+ "src/utils/guards.ts"() {
54295
+ "use strict";
54296
+ }
54297
+ });
54298
+
54172
54299
  // src/state/run-metrics.ts
54173
54300
  function collectRunMetrics(cwd, runId) {
54174
54301
  assertSafePathId("runId", runId);
@@ -54809,6 +54936,7 @@ var ChainRunner;
54809
54936
  var init_chain_runner = __esm({
54810
54937
  "src/runtime/chain-runner.ts"() {
54811
54938
  "use strict";
54939
+ init_guards();
54812
54940
  init_chain_parser();
54813
54941
  ChainRunner = class _ChainRunner {
54814
54942
  /** Maximum number of chain history entries to prevent memory leaks */
@@ -54930,13 +55058,13 @@ var init_chain_runner = __esm({
54930
55058
  });
54931
55059
  }
54932
55060
  } catch (error) {
54933
- const errorMessage = error instanceof Error ? error.message : String(error);
55061
+ const errMsg = errorMessage(error);
54934
55062
  stepResults.push({
54935
55063
  step: i + 1,
54936
55064
  name: step.name,
54937
55065
  outcome: "failure",
54938
55066
  duration: Date.now() - stepStart,
54939
- error: errorMessage
55067
+ error: errMsg
54940
55068
  });
54941
55069
  if (!spec.continueOnError && !step.continueOnError) {
54942
55070
  break;
@@ -57345,7 +57473,7 @@ Use workspaceMode: 'single' or run from a git repository.`,
57345
57473
  try {
57346
57474
  await assertCleanLeaderAsync(gitRoot);
57347
57475
  } catch (err2) {
57348
- const msg = err2 instanceof Error ? err2.message : String(err2);
57476
+ const msg = errorMessage(err2);
57349
57477
  return result(
57350
57478
  `${msg}
57351
57479
  Commit or stash changes before using worktree mode, or use workspaceMode: 'single'.`,
@@ -57516,7 +57644,7 @@ Commit or stash changes before using worktree mode, or use workspaceMode: 'singl
57516
57644
  tokenBudget: params.tokenBudget ?? workflow.maxTokenBudget
57517
57645
  });
57518
57646
  } catch (runnerError) {
57519
- const failureReason = runnerError instanceof Error ? runnerError.message : String(runnerError);
57647
+ const failureReason = errorMessage(runnerError);
57520
57648
  const failedManifest = {
57521
57649
  ...dwfManifest,
57522
57650
  status: "failed",
@@ -57704,13 +57832,13 @@ ${dwfResult.manifest.summary ?? ""}`,
57704
57832
  workspaceId: ctx.sessionId ?? ctx.cwd
57705
57833
  });
57706
57834
  } catch (waitError) {
57707
- const errorMessage = waitError instanceof Error ? waitError.message : String(waitError);
57835
+ const waitErrMsg = errorMessage(waitError);
57708
57836
  return result(
57709
57837
  [
57710
57838
  `pi-crew run timed out or failed: ${updatedManifest.runId}`,
57711
57839
  `Team: ${team.name}`,
57712
57840
  `Workflow: ${workflow.name}`,
57713
- `Error: ${errorMessage}`,
57841
+ `Error: ${waitErrMsg}`,
57714
57842
  "",
57715
57843
  `Check status with: team status runId=${updatedManifest.runId}`,
57716
57844
  `State: ${updatedManifest.stateRoot}`,
@@ -57814,13 +57942,13 @@ ${dwfResult.manifest.summary ?? ""}`,
57814
57942
  workspaceId: ctx.sessionId ?? ctx.cwd
57815
57943
  });
57816
57944
  } catch (waitError) {
57817
- const errorMessage = waitError instanceof Error ? waitError.message : String(waitError);
57945
+ const waitErrMsg = errorMessage(waitError);
57818
57946
  return result(
57819
57947
  [
57820
57948
  `pi-crew run timed out or failed: ${updatedManifest.runId}`,
57821
57949
  `Team: ${team.name}`,
57822
57950
  `Workflow: ${workflow.name}`,
57823
- `Error: ${errorMessage}`,
57951
+ `Error: ${waitErrMsg}`,
57824
57952
  "",
57825
57953
  `Check status with: team status runId=${updatedManifest.runId}`,
57826
57954
  `State: ${updatedManifest.stateRoot}`
@@ -57890,6 +58018,7 @@ var init_run = __esm({
57890
58018
  init_discover_teams();
57891
58019
  init_discover_workflows();
57892
58020
  init_worktree_manager();
58021
+ init_guards();
57893
58022
  init_internal_error();
57894
58023
  init_safe_paths();
57895
58024
  init_runtime_resolver();
@@ -59994,6 +60123,9 @@ async function openLiveConversation(ctx, initialRunId, initialTaskId) {
59994
60123
  }
59995
60124
  },
59996
60125
  invalidate() {
60126
+ },
60127
+ dispose() {
60128
+ overlay.dispose();
59997
60129
  }
59998
60130
  };
59999
60131
  },
@@ -60901,7 +61033,7 @@ function groupedRuns(runs, snapshotCache) {
60901
61033
  function selectedRunFromGrouped(runs, selected, snapshotCache) {
60902
61034
  return groupedRuns(runs, snapshotCache).filter((row) => row.run)[selected]?.run;
60903
61035
  }
60904
- var lastActivePane, TASK_READ_TTL_MS2, RUN_LIST_MAX, SIGNATURE_CACHE_TTL_MS, STALE_SNAPSHOT_MS, RunDashboard;
61036
+ var lastActivePane, TASK_READ_TTL_MS2, RUN_LIST_MAX, SIGNATURE_CACHE_TTL_MS2, STALE_SNAPSHOT_MS, RunDashboard;
60905
61037
  var init_run_dashboard = __esm({
60906
61038
  "src/ui/run-dashboard.ts"() {
60907
61039
  "use strict";
@@ -60923,8 +61055,7 @@ var init_run_dashboard = __esm({
60923
61055
  init_dynamic_border();
60924
61056
  init_keybinding_map();
60925
61057
  init_help_overlay();
60926
- init_render_scheduler();
60927
- init_run_event_bus();
61058
+ init_shared_overlay_scheduler();
60928
61059
  init_spinner();
60929
61060
  init_status_colors();
60930
61061
  init_theme_adapter();
@@ -60932,7 +61063,7 @@ var init_run_dashboard = __esm({
60932
61063
  lastActivePane = "agents";
60933
61064
  TASK_READ_TTL_MS2 = 1e3;
60934
61065
  RUN_LIST_MAX = 8;
60935
- SIGNATURE_CACHE_TTL_MS = 100;
61066
+ SIGNATURE_CACHE_TTL_MS2 = 100;
60936
61067
  STALE_SNAPSHOT_MS = 15e3;
60937
61068
  RunDashboard = class _RunDashboard {
60938
61069
  // TEMP DIAGNOSTIC (remove after verifying keybind fix on Pi 0.81.1)
@@ -60956,7 +61087,7 @@ var init_run_dashboard = __esm({
60956
61087
  cachedSignatureAt = 0;
60957
61088
  cachedSignature = "";
60958
61089
  unsubscribeTheme;
60959
- renderScheduler;
61090
+ schedulerHandle;
60960
61091
  constructor(runs, done, theme = {}, options = {}) {
60961
61092
  this._instanceId = ++_RunDashboard._instanceCounter;
60962
61093
  try {
@@ -60974,19 +61105,10 @@ var init_run_dashboard = __esm({
60974
61105
  const renderTick = () => {
60975
61106
  this.invalidateAndRender();
60976
61107
  };
60977
- this.renderScheduler = new RenderScheduler(
60978
- runEventBusAsRenderScheduler(["run:state", "worker:lifecycle", "ui:invalidate"]),
60979
- renderTick,
60980
- {
60981
- debounceMs: 75,
60982
- fallbackMs: 750,
60983
- events: ["run:state", "worker:lifecycle", "ui:invalidate"],
60984
- onInvalidate: () => {
60985
- this.cachedSignature = "";
60986
- this.cachedSignatureAt = 0;
60987
- }
60988
- }
60989
- );
61108
+ this.schedulerHandle = registerOverlayScheduler(renderTick, () => {
61109
+ this.cachedSignature = "";
61110
+ this.cachedSignatureAt = 0;
61111
+ });
60990
61112
  this.unsubscribeTheme = subscribeThemeChange(theme, () => this.scheduleRender());
60991
61113
  }
60992
61114
  /**
@@ -61000,8 +61122,8 @@ var init_run_dashboard = __esm({
61000
61122
  * (defensive — currently always created in the constructor).
61001
61123
  */
61002
61124
  scheduleRender() {
61003
- if (this.renderScheduler) {
61004
- this.renderScheduler.schedule();
61125
+ if (this.schedulerHandle) {
61126
+ this.schedulerHandle.schedule();
61005
61127
  return;
61006
61128
  }
61007
61129
  this.invalidateAndRender();
@@ -61071,7 +61193,7 @@ var init_run_dashboard = __esm({
61071
61193
  }
61072
61194
  buildSignature() {
61073
61195
  const now = Date.now();
61074
- if (this.cachedSignature && now - this.cachedSignatureAt < SIGNATURE_CACHE_TTL_MS) {
61196
+ if (this.cachedSignature && now - this.cachedSignatureAt < SIGNATURE_CACHE_TTL_MS2) {
61075
61197
  return this.cachedSignature;
61076
61198
  }
61077
61199
  let hasRunning = false;
@@ -61098,7 +61220,7 @@ var init_run_dashboard = __esm({
61098
61220
  }
61099
61221
  dispose() {
61100
61222
  this.unsubscribeTheme();
61101
- this.renderScheduler?.dispose();
61223
+ this.schedulerHandle?.dispose();
61102
61224
  }
61103
61225
  selectedRunId() {
61104
61226
  return selectedRunFromGrouped(this.runs, this.selected, this.options.snapshotCache)?.runId;
@@ -62048,6 +62170,7 @@ var init_mascot = __esm({
62048
62170
  currentArminGrid;
62049
62171
  effectState = {};
62050
62172
  effectDone = false;
62173
+ visible = true;
62051
62174
  frame = 0;
62052
62175
  effectPhase = 0;
62053
62176
  gridVersion = 0;
@@ -62143,7 +62266,7 @@ var init_mascot = __esm({
62143
62266
  this.gridVersion++;
62144
62267
  }
62145
62268
  this.invalidate();
62146
- this.requestRender?.();
62269
+ if (this.visible) this.requestRender?.();
62147
62270
  }
62148
62271
  tickArminEffect() {
62149
62272
  switch (this.effect) {
@@ -62168,7 +62291,7 @@ var init_mascot = __esm({
62168
62291
  tickTypewriter() {
62169
62292
  const state = this.effectState;
62170
62293
  if (state.pos === void 0) return true;
62171
- for (let i = 0; i < 6; i++) {
62294
+ for (let i = 0; i < 18; i++) {
62172
62295
  const row = Math.floor(state.pos / ARMIN_WIDTH);
62173
62296
  const x = state.pos % ARMIN_WIDTH;
62174
62297
  if (row >= ARMIN_DISPLAY_HEIGHT) return true;
@@ -62180,9 +62303,11 @@ var init_mascot = __esm({
62180
62303
  tickScanline() {
62181
62304
  const state = this.effectState;
62182
62305
  if (state.row === void 0) return true;
62183
- if (state.row >= ARMIN_DISPLAY_HEIGHT) return true;
62184
- for (let x = 0; x < ARMIN_WIDTH; x++) this.currentArminGrid[state.row][x] = this.finalArminGrid[state.row][x];
62185
- state.row++;
62306
+ for (let step = 0; step < 3; step++) {
62307
+ if (state.row >= ARMIN_DISPLAY_HEIGHT) return true;
62308
+ for (let x = 0; x < ARMIN_WIDTH; x++) this.currentArminGrid[state.row][x] = this.finalArminGrid[state.row][x];
62309
+ state.row++;
62310
+ }
62186
62311
  return false;
62187
62312
  }
62188
62313
  tickRain() {
@@ -62204,7 +62329,7 @@ var init_mascot = __esm({
62204
62329
  break;
62205
62330
  }
62206
62331
  }
62207
- drop.y++;
62332
+ drop.y += 3;
62208
62333
  if (drop.y >= 0 && drop.y < ARMIN_DISPLAY_HEIGHT) {
62209
62334
  if (targetRow >= 0 && drop.y >= targetRow) {
62210
62335
  drop.settled = ARMIN_DISPLAY_HEIGHT - targetRow;
@@ -62219,7 +62344,7 @@ var init_mascot = __esm({
62219
62344
  tickFade() {
62220
62345
  const state = this.effectState;
62221
62346
  if (!state.positions || state.idx === void 0) return true;
62222
- for (let i = 0; i < 18; i++) {
62347
+ for (let i = 0; i < 54; i++) {
62223
62348
  if (state.idx >= state.positions.length) return true;
62224
62349
  const [row, x] = state.positions[state.idx];
62225
62350
  this.currentArminGrid[row][x] = this.finalArminGrid[row][x];
@@ -62237,7 +62362,7 @@ var init_mascot = __esm({
62237
62362
  for (let row = Math.max(0, top); row <= Math.min(ARMIN_DISPLAY_HEIGHT - 1, bottom); row++) {
62238
62363
  for (let x = 0; x < ARMIN_WIDTH; x++) this.currentArminGrid[row][x] = this.finalArminGrid[row][x];
62239
62364
  }
62240
- state.expansion++;
62365
+ state.expansion += 3;
62241
62366
  return state.expansion > ARMIN_DISPLAY_HEIGHT;
62242
62367
  }
62243
62368
  tickGlitch() {
@@ -62258,7 +62383,7 @@ var init_mascot = __esm({
62258
62383
  }
62259
62384
  }
62260
62385
  }
62261
- state.phase++;
62386
+ state.phase += 3;
62262
62387
  return false;
62263
62388
  }
62264
62389
  for (let row = 0; row < ARMIN_DISPLAY_HEIGHT; row++) {
@@ -62271,7 +62396,7 @@ var init_mascot = __esm({
62271
62396
  tickDissolve() {
62272
62397
  const state = this.effectState;
62273
62398
  if (!state.positions || state.idx === void 0) return true;
62274
- for (let i = 0; i < 22; i++) {
62399
+ for (let i = 0; i < 66; i++) {
62275
62400
  if (state.idx >= state.positions.length) return true;
62276
62401
  const [row, x] = state.positions[state.idx];
62277
62402
  this.currentArminGrid[row][x] = this.finalArminGrid[row][x];
@@ -62348,6 +62473,14 @@ var init_mascot = __esm({
62348
62473
  this.close();
62349
62474
  }
62350
62475
  }
62476
+ /**
62477
+ * Set whether the mascot is currently visible (not obscured by another
62478
+ * overlay). When invisible, tick() skips requestRender so the animation
62479
+ * does not trigger needless repaints while hidden.
62480
+ */
62481
+ setVisible(visible) {
62482
+ this.visible = visible;
62483
+ }
62351
62484
  dispose() {
62352
62485
  this.doneGuard.called = true;
62353
62486
  if (this.interval) clearInterval(this.interval);
@@ -63519,6 +63652,7 @@ async function openTeamDashboard(ctx) {
63519
63652
  if (!ctx.hasUI) return;
63520
63653
  const deps = depsRef;
63521
63654
  if (!deps) return;
63655
+ if (deps.uiState) deps.uiState.dashboardOpen = true;
63522
63656
  const cmdCtx = ctx;
63523
63657
  for (; ; ) {
63524
63658
  const sessionId = cmdCtx.sessionManager?.getSessionId?.();
@@ -63552,7 +63686,7 @@ async function openTeamDashboard(ctx) {
63552
63686
  } : { width, maxHeight: "90%", anchor: "center", margin: 2 }
63553
63687
  }
63554
63688
  );
63555
- if (!selection) return;
63689
+ if (!selection) break;
63556
63690
  if (selection.action === "reload") continue;
63557
63691
  if (selection.action === "notifications-dismiss") {
63558
63692
  deps.dismissNotifications?.();
@@ -63646,8 +63780,9 @@ async function openTeamDashboard(ctx) {
63646
63780
  )
63647
63781
  );
63648
63782
  await notifyCommandResult(cmdCtx, commandText2(result4));
63649
- return;
63783
+ break;
63650
63784
  }
63785
+ if (deps.uiState) deps.uiState.dashboardOpen = false;
63651
63786
  }
63652
63787
  function registerTeamCommands(pi, deps) {
63653
63788
  depsRef = deps;
@@ -64025,7 +64160,7 @@ function registerTeamCommands(pi, deps) {
64025
64160
  const { AnimatedMascot: AnimatedMascot2 } = await ui();
64026
64161
  await ctx.ui.custom(
64027
64162
  (tui, theme, _keybindings, done) => new AnimatedMascot2(theme, () => done(void 0), {
64028
- frameIntervalMs: style === "armin" ? 33 : 180,
64163
+ frameIntervalMs: style === "armin" ? 100 : 180,
64029
64164
  autoCloseMs: 7e3,
64030
64165
  requestRender: () => requestRenderTarget(tui),
64031
64166
  style,
@@ -64864,8 +64999,7 @@ var init_live_run_sidebar = __esm({
64864
64999
  init_usage();
64865
65000
  init_file_coalescer();
64866
65001
  init_visual();
64867
- init_render_scheduler();
64868
- init_run_event_bus();
65002
+ init_shared_overlay_scheduler();
64869
65003
  init_spinner();
64870
65004
  init_status_colors();
64871
65005
  init_theme_adapter();
@@ -64878,7 +65012,7 @@ var init_live_run_sidebar = __esm({
64878
65012
  theme;
64879
65013
  config;
64880
65014
  unsubscribeTheme;
64881
- renderScheduler;
65015
+ schedulerHandle;
64882
65016
  snapshotCache;
64883
65017
  cachedLines = [];
64884
65018
  cachedWidth = 0;
@@ -64893,15 +65027,7 @@ var init_live_run_sidebar = __esm({
64893
65027
  this.config = input.config ?? {};
64894
65028
  this.snapshotCache = input.snapshotCache;
64895
65029
  this.unsubscribeTheme = subscribeThemeChange(input.theme, () => this.invalidate());
64896
- this.renderScheduler = new RenderScheduler(
64897
- runEventBusAsRenderScheduler(["run:state", "worker:lifecycle", "ui:invalidate"]),
64898
- () => this.invalidate(),
64899
- {
64900
- debounceMs: 75,
64901
- fallbackMs: 750,
64902
- events: ["run:state", "worker:lifecycle", "ui:invalidate"]
64903
- }
64904
- );
65030
+ this.schedulerHandle = registerOverlayScheduler(() => this.invalidate());
64905
65031
  }
64906
65032
  buildSignature(manifestStatus, tasks, agents, waitingCount, snapshot) {
64907
65033
  const animation = agents.some((agent) => agent.status === "running") ? `:spin=${spinnerBucket()}` : "";
@@ -64939,7 +65065,7 @@ var init_live_run_sidebar = __esm({
64939
65065
  this.autoCloseTimeout = void 0;
64940
65066
  }
64941
65067
  this.unsubscribeTheme();
64942
- this.renderScheduler.dispose();
65068
+ this.schedulerHandle.dispose();
64943
65069
  }
64944
65070
  render(width) {
64945
65071
  const w = Math.max(36, width);
@@ -65038,10 +65164,12 @@ var init_live_run_sidebar = __esm({
65038
65164
  if (isTerminal && !hasActiveAgents && !this.hasAutoClosed) {
65039
65165
  const autoCloseMs = this.config?.autoCloseDashboardMs ?? 3e3;
65040
65166
  if (autoCloseMs > 0) {
65167
+ if (this.autoCloseTimeout) clearTimeout(this.autoCloseTimeout);
65041
65168
  this.autoCloseTimeout = setTimeout(() => {
65042
65169
  this.hasAutoClosed = true;
65043
65170
  this.done(void 0);
65044
65171
  }, autoCloseMs);
65172
+ this.autoCloseTimeout?.unref();
65045
65173
  lines.push(line3(`auto-close in ${Math.round(autoCloseMs / 1e3)}s\u2026`, w));
65046
65174
  }
65047
65175
  } else if (this.autoCloseTimeout) {
@@ -69178,7 +69306,9 @@ function registerCrewVibes(pi) {
69178
69306
  });
69179
69307
  }
69180
69308
  function refreshFooter(ctx) {
69181
- if (ctx?.hasUI) safeUiCall("refresh-footer", () => requestRender(ctx));
69309
+ safeUiCall("refresh-footer", () => {
69310
+ if (ctx?.hasUI) requestRender(ctx);
69311
+ });
69182
69312
  }
69183
69313
  function publishSpeedFooter(ctx, speed = footerAnimator.value()) {
69184
69314
  if (!config.enabled || !config.speed.enabled || !config.speed.footer) {
@@ -69527,6 +69657,7 @@ function registerPiCommands(pi, ctx) {
69527
69657
  getManifestCache: ctx.getManifestCache,
69528
69658
  getRunSnapshotCache: ctx.getRunSnapshotCache,
69529
69659
  getMetricRegistry: () => ctx.observabilityState.metricRegistry,
69660
+ uiState: ctx.uiState,
69530
69661
  dismissNotifications: () => {
69531
69662
  ctx.widgetState.notificationCount = 0;
69532
69663
  if (ctx.currentCtx) {
@@ -70839,16 +70970,15 @@ function createRunSnapshotCache(cwd, options = {}) {
70839
70970
  const scheduleRefresh = (runId) => {
70840
70971
  const existing = pendingRefreshes.get(runId);
70841
70972
  if (existing) clearTimeout(existing);
70842
- pendingRefreshes.set(
70843
- runId,
70844
- setTimeout(() => {
70845
- pendingRefreshes.delete(runId);
70846
- try {
70847
- localRefreshIfStale(runId);
70848
- } catch {
70849
- }
70850
- }, INVAL_COALESCE_MS)
70851
- );
70973
+ const timer = setTimeout(() => {
70974
+ pendingRefreshes.delete(runId);
70975
+ try {
70976
+ localRefreshIfStale(runId);
70977
+ } catch {
70978
+ }
70979
+ }, INVAL_COALESCE_MS);
70980
+ timer.unref();
70981
+ pendingRefreshes.set(runId, timer);
70852
70982
  };
70853
70983
  const unsubState = runEventBus.onChannel("run:state", (event) => {
70854
70984
  if (entries.has(event.runId)) scheduleRefresh(event.runId);
@@ -72921,8 +73051,6 @@ function installSessionStartHandler(pi, ctx) {
72921
73051
  ctx.crewAutocompleteRegistered = true;
72922
73052
  registerCrewAutocomplete(extensionCtx);
72923
73053
  }
72924
- if (ctx.widgetState.interval) clearInterval(ctx.widgetState.interval);
72925
- ctx.widgetState.interval = void 0;
72926
73054
  notifyActiveRuns(extensionCtx);
72927
73055
  const currentSessionId = extractBrokerSessionId(extensionCtx);
72928
73056
  ctx.brokerController?.setSessionId(currentSessionId);
@@ -73232,7 +73360,7 @@ function setupRenderLoop(pi, ctx, extensionCtx, loadedConfig) {
73232
73360
  const snapshotCache = lastFrameSnapshotCache ?? ctx.getRunSnapshotCache(ctx.currentCtx.cwd);
73233
73361
  const manifests = lastPreloadedManifests;
73234
73362
  if (!lastPreloadedConfig) backgroundPreload();
73235
- if (ctx.uiState.liveSidebarRunId) {
73363
+ if (ctx.uiState.liveSidebarRunId || ctx.uiState.dashboardOpen) {
73236
73364
  const placement = config?.widgetPlacement ?? DEFAULT_UI.widgetPlacement;
73237
73365
  if (ctx.widgetState.lastVisibility !== "hidden" || ctx.widgetState.lastPlacement !== placement) {
73238
73366
  setExtensionWidget(ctx.currentCtx, "pi-crew", void 0, { placement });
@@ -74652,7 +74780,7 @@ function registerPiTeams(pi) {
74652
74780
  installPiHooks(pi, ctx);
74653
74781
  installSessionLifecycleHandlers(pi, ctx);
74654
74782
  ctx.brokerController = installCrewBrokerLifecycleController(pi, ctx);
74655
- registerCleanupHandler(pi);
74783
+ registerCleanupHandler(pi, { disposeTerminalStatus: () => ctx.terminalStatus?.dispose?.() });
74656
74784
  registerCompactionGuard(pi, {
74657
74785
  foregroundControllers: ctx.foregroundControllers,
74658
74786
  foregroundTeamRunControllers: ctx.foregroundTeamRunControllers