pi-crew 0.9.50 → 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.
package/dist/index.mjs CHANGED
@@ -42584,7 +42584,7 @@ var init_tool_renderers = __esm({
42584
42584
  });
42585
42585
 
42586
42586
  // src/ui/render-scheduler.ts
42587
- var DEFAULT_EVENTS, RenderScheduler;
42587
+ var DEFAULT_EVENTS, CAP_BACKOFF_MAX_EXP, RenderScheduler;
42588
42588
  var init_render_scheduler = __esm({
42589
42589
  "src/ui/render-scheduler.ts"() {
42590
42590
  "use strict";
@@ -42599,6 +42599,7 @@ var init_render_scheduler = __esm({
42599
42599
  "crew.mailbox.updated",
42600
42600
  "crew.mailbox.message"
42601
42601
  ];
42602
+ CAP_BACKOFF_MAX_EXP = 5;
42602
42603
  RenderScheduler = class {
42603
42604
  render;
42604
42605
  onInvalidate;
@@ -42606,6 +42607,7 @@ var init_render_scheduler = __esm({
42606
42607
  fallbackProvider;
42607
42608
  fallbackMs;
42608
42609
  invalidateCoalesceMs;
42610
+ maxIdleFallbackRenders;
42609
42611
  debounceTimer;
42610
42612
  fallbackTimer;
42611
42613
  invalidateTimer;
@@ -42615,6 +42617,10 @@ var init_render_scheduler = __esm({
42615
42617
  lastEventAt = 0;
42616
42618
  rendering = false;
42617
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;
42618
42624
  unsubs = [];
42619
42625
  constructor(events, render, options = {}) {
42620
42626
  this.render = render;
@@ -42624,6 +42630,7 @@ var init_render_scheduler = __esm({
42624
42630
  this.fallbackProvider = typeof fallback2 === "function" ? fallback2 : () => fallback2;
42625
42631
  this.fallbackMs = typeof fallback2 === "number" ? fallback2 : 750;
42626
42632
  this.invalidateCoalesceMs = options.invalidateCoalesceMs ?? 50;
42633
+ this.maxIdleFallbackRenders = options.maxIdleFallbackRenders ?? 8;
42627
42634
  for (const event of options.events ?? DEFAULT_EVENTS) this.subscribe(events, event);
42628
42635
  this.fallbackTimer = setTimeout(() => this.fallbackLoop(), this.currentFallbackMs());
42629
42636
  this.fallbackTimer.unref();
@@ -42652,25 +42659,44 @@ var init_render_scheduler = __esm({
42652
42659
  if (this.disposed) return;
42653
42660
  const fallbackMs = this.currentFallbackMs();
42654
42661
  if (Date.now() - this.lastEventAt < fallbackMs) {
42655
- if (this.disposed) return;
42662
+ this.idleFallbackRenders = 0;
42656
42663
  this.fallbackTimer = setTimeout(() => this.fallbackLoop(), fallbackMs);
42657
42664
  this.fallbackTimer.unref();
42658
42665
  return;
42659
42666
  }
42660
- this.schedule();
42661
- 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;
42662
42673
  this.fallbackTimer = setTimeout(() => this.fallbackLoop(), this.currentFallbackMs());
42663
42674
  this.fallbackTimer.unref();
42664
42675
  }
42665
42676
  schedule(payload) {
42666
42677
  if (this.disposed) return;
42667
42678
  this.lastEventAt = Date.now();
42679
+ this.idleFallbackRenders = 0;
42680
+ this.consecutiveCapHits = 0;
42668
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) {
42669
42695
  if (this.debounceTimer) clearTimeout(this.debounceTimer);
42670
42696
  this.debounceTimer = setTimeout(() => {
42671
42697
  this.debounceTimer = void 0;
42672
42698
  this.flush();
42673
- }, this.debounceMs);
42699
+ }, delay);
42674
42700
  this.debounceTimer.unref();
42675
42701
  }
42676
42702
  /**
@@ -42737,7 +42763,11 @@ var init_render_scheduler = __esm({
42737
42763
  } finally {
42738
42764
  this.rendering = false;
42739
42765
  if (iterations >= 5 && this.pendingRender && !this.disposed) {
42740
- 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;
42741
42771
  }
42742
42772
  }
42743
42773
  }
@@ -42763,6 +42793,69 @@ var init_render_scheduler = __esm({
42763
42793
  }
42764
42794
  });
42765
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
+
42766
42859
  // src/runtime/stale-reconciler.ts
42767
42860
  import * as fs67 from "node:fs";
42768
42861
  import * as os14 from "node:os";
@@ -44047,15 +44140,14 @@ function stopCrewWidget(ctx, state, config) {
44047
44140
  requestRender(ctx);
44048
44141
  }
44049
44142
  }
44050
- 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;
44051
44144
  var init_widget = __esm({
44052
44145
  "src/ui/widget/index.ts"() {
44053
44146
  "use strict";
44054
44147
  init_defaults();
44055
44148
  init_visual();
44056
44149
  init_pi_ui_compat();
44057
- init_render_scheduler();
44058
- init_run_event_bus();
44150
+ init_shared_overlay_scheduler();
44059
44151
  init_spinner();
44060
44152
  init_theme_adapter();
44061
44153
  init_widget_model();
@@ -44068,18 +44160,22 @@ var init_widget = __esm({
44068
44160
  LEGACY_WIDGET_KEY = "pi-crew";
44069
44161
  WIDGET_KEY = "pi-crew-active";
44070
44162
  STATUS_KEY = "pi-crew";
44163
+ SIGNATURE_CACHE_TTL_MS = 100;
44071
44164
  resizeListenerInstalled = false;
44072
44165
  CrewWidgetComponent = class {
44073
44166
  model;
44074
44167
  theme;
44075
44168
  cacheSignature = "";
44169
+ /** C4 — invalidate-on-write cache for the buildSignature() result. */
44170
+ cachedBuildSignature = "";
44171
+ cachedBuildSignatureAt = 0;
44076
44172
  cachedWidth = 0;
44077
44173
  cachedLines = [];
44078
44174
  cachedBaseLines = [];
44079
44175
  cachedTheme;
44080
44176
  tui;
44081
44177
  unsubscribeTheme;
44082
- renderScheduler;
44178
+ schedulerHandle;
44083
44179
  constructor(model, themeLike, tui) {
44084
44180
  this.model = model;
44085
44181
  this.theme = asCrewTheme(themeLike);
@@ -44088,13 +44184,11 @@ var init_widget = __esm({
44088
44184
  activeResizeTarget = this;
44089
44185
  installResizeListener();
44090
44186
  this.unsubscribeTheme = subscribeThemeChange(themeLike, () => this.invalidate());
44091
- this.renderScheduler = new RenderScheduler(
44092
- runEventBusAsRenderScheduler(["run:state", "worker:lifecycle", "ui:invalidate"]),
44187
+ this.schedulerHandle = registerOverlayScheduler(
44093
44188
  () => this.invalidate(),
44094
- {
44095
- debounceMs: 75,
44096
- fallbackMs: 750,
44097
- events: ["run:state", "worker:lifecycle", "ui:invalidate"]
44189
+ () => {
44190
+ this.cachedBuildSignature = "";
44191
+ this.cachedBuildSignatureAt = 0;
44098
44192
  }
44099
44193
  );
44100
44194
  }
@@ -44130,6 +44224,8 @@ var init_widget = __esm({
44130
44224
  this.cacheSignature = "";
44131
44225
  this.cachedBaseLines = [];
44132
44226
  this.cachedLines = [];
44227
+ this.cachedBuildSignature = "";
44228
+ this.cachedBuildSignatureAt = 0;
44133
44229
  }
44134
44230
  /** Poke the host TUI to repaint immediately (defensive: no-op if unavailable). */
44135
44231
  requestRepaint() {
@@ -44137,12 +44233,21 @@ var init_widget = __esm({
44137
44233
  }
44138
44234
  dispose() {
44139
44235
  this.unsubscribeTheme();
44140
- this.renderScheduler.dispose();
44236
+ this.schedulerHandle.dispose();
44141
44237
  if (activeResizeTarget === this) activeResizeTarget = void 0;
44142
44238
  }
44143
44239
  render(width) {
44144
44240
  const runs = activeWidgetRuns(this.model.cwd, this.model.manifestCache, this.model.snapshotCache, this.model.preloadManifests);
44145
- 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}`;
44146
44251
  const runningGlyph = spinnerFrame("widget-header");
44147
44252
  if (this.cacheSignature !== signature || width !== this.cachedWidth || this.cachedTheme !== this.theme) {
44148
44253
  this.cachedBaseLines = buildWidgetLines(
@@ -60928,7 +61033,7 @@ function groupedRuns(runs, snapshotCache) {
60928
61033
  function selectedRunFromGrouped(runs, selected, snapshotCache) {
60929
61034
  return groupedRuns(runs, snapshotCache).filter((row) => row.run)[selected]?.run;
60930
61035
  }
60931
- 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;
60932
61037
  var init_run_dashboard = __esm({
60933
61038
  "src/ui/run-dashboard.ts"() {
60934
61039
  "use strict";
@@ -60950,8 +61055,7 @@ var init_run_dashboard = __esm({
60950
61055
  init_dynamic_border();
60951
61056
  init_keybinding_map();
60952
61057
  init_help_overlay();
60953
- init_render_scheduler();
60954
- init_run_event_bus();
61058
+ init_shared_overlay_scheduler();
60955
61059
  init_spinner();
60956
61060
  init_status_colors();
60957
61061
  init_theme_adapter();
@@ -60959,7 +61063,7 @@ var init_run_dashboard = __esm({
60959
61063
  lastActivePane = "agents";
60960
61064
  TASK_READ_TTL_MS2 = 1e3;
60961
61065
  RUN_LIST_MAX = 8;
60962
- SIGNATURE_CACHE_TTL_MS = 100;
61066
+ SIGNATURE_CACHE_TTL_MS2 = 100;
60963
61067
  STALE_SNAPSHOT_MS = 15e3;
60964
61068
  RunDashboard = class _RunDashboard {
60965
61069
  // TEMP DIAGNOSTIC (remove after verifying keybind fix on Pi 0.81.1)
@@ -60983,7 +61087,7 @@ var init_run_dashboard = __esm({
60983
61087
  cachedSignatureAt = 0;
60984
61088
  cachedSignature = "";
60985
61089
  unsubscribeTheme;
60986
- renderScheduler;
61090
+ schedulerHandle;
60987
61091
  constructor(runs, done, theme = {}, options = {}) {
60988
61092
  this._instanceId = ++_RunDashboard._instanceCounter;
60989
61093
  try {
@@ -61001,19 +61105,10 @@ var init_run_dashboard = __esm({
61001
61105
  const renderTick = () => {
61002
61106
  this.invalidateAndRender();
61003
61107
  };
61004
- this.renderScheduler = new RenderScheduler(
61005
- runEventBusAsRenderScheduler(["run:state", "worker:lifecycle", "ui:invalidate"]),
61006
- renderTick,
61007
- {
61008
- debounceMs: 75,
61009
- fallbackMs: 750,
61010
- events: ["run:state", "worker:lifecycle", "ui:invalidate"],
61011
- onInvalidate: () => {
61012
- this.cachedSignature = "";
61013
- this.cachedSignatureAt = 0;
61014
- }
61015
- }
61016
- );
61108
+ this.schedulerHandle = registerOverlayScheduler(renderTick, () => {
61109
+ this.cachedSignature = "";
61110
+ this.cachedSignatureAt = 0;
61111
+ });
61017
61112
  this.unsubscribeTheme = subscribeThemeChange(theme, () => this.scheduleRender());
61018
61113
  }
61019
61114
  /**
@@ -61027,8 +61122,8 @@ var init_run_dashboard = __esm({
61027
61122
  * (defensive — currently always created in the constructor).
61028
61123
  */
61029
61124
  scheduleRender() {
61030
- if (this.renderScheduler) {
61031
- this.renderScheduler.schedule();
61125
+ if (this.schedulerHandle) {
61126
+ this.schedulerHandle.schedule();
61032
61127
  return;
61033
61128
  }
61034
61129
  this.invalidateAndRender();
@@ -61098,7 +61193,7 @@ var init_run_dashboard = __esm({
61098
61193
  }
61099
61194
  buildSignature() {
61100
61195
  const now = Date.now();
61101
- if (this.cachedSignature && now - this.cachedSignatureAt < SIGNATURE_CACHE_TTL_MS) {
61196
+ if (this.cachedSignature && now - this.cachedSignatureAt < SIGNATURE_CACHE_TTL_MS2) {
61102
61197
  return this.cachedSignature;
61103
61198
  }
61104
61199
  let hasRunning = false;
@@ -61125,7 +61220,7 @@ var init_run_dashboard = __esm({
61125
61220
  }
61126
61221
  dispose() {
61127
61222
  this.unsubscribeTheme();
61128
- this.renderScheduler?.dispose();
61223
+ this.schedulerHandle?.dispose();
61129
61224
  }
61130
61225
  selectedRunId() {
61131
61226
  return selectedRunFromGrouped(this.runs, this.selected, this.options.snapshotCache)?.runId;
@@ -62075,6 +62170,7 @@ var init_mascot = __esm({
62075
62170
  currentArminGrid;
62076
62171
  effectState = {};
62077
62172
  effectDone = false;
62173
+ visible = true;
62078
62174
  frame = 0;
62079
62175
  effectPhase = 0;
62080
62176
  gridVersion = 0;
@@ -62170,7 +62266,7 @@ var init_mascot = __esm({
62170
62266
  this.gridVersion++;
62171
62267
  }
62172
62268
  this.invalidate();
62173
- this.requestRender?.();
62269
+ if (this.visible) this.requestRender?.();
62174
62270
  }
62175
62271
  tickArminEffect() {
62176
62272
  switch (this.effect) {
@@ -62377,6 +62473,14 @@ var init_mascot = __esm({
62377
62473
  this.close();
62378
62474
  }
62379
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
+ }
62380
62484
  dispose() {
62381
62485
  this.doneGuard.called = true;
62382
62486
  if (this.interval) clearInterval(this.interval);
@@ -64895,8 +64999,7 @@ var init_live_run_sidebar = __esm({
64895
64999
  init_usage();
64896
65000
  init_file_coalescer();
64897
65001
  init_visual();
64898
- init_render_scheduler();
64899
- init_run_event_bus();
65002
+ init_shared_overlay_scheduler();
64900
65003
  init_spinner();
64901
65004
  init_status_colors();
64902
65005
  init_theme_adapter();
@@ -64909,7 +65012,7 @@ var init_live_run_sidebar = __esm({
64909
65012
  theme;
64910
65013
  config;
64911
65014
  unsubscribeTheme;
64912
- renderScheduler;
65015
+ schedulerHandle;
64913
65016
  snapshotCache;
64914
65017
  cachedLines = [];
64915
65018
  cachedWidth = 0;
@@ -64924,15 +65027,7 @@ var init_live_run_sidebar = __esm({
64924
65027
  this.config = input.config ?? {};
64925
65028
  this.snapshotCache = input.snapshotCache;
64926
65029
  this.unsubscribeTheme = subscribeThemeChange(input.theme, () => this.invalidate());
64927
- this.renderScheduler = new RenderScheduler(
64928
- runEventBusAsRenderScheduler(["run:state", "worker:lifecycle", "ui:invalidate"]),
64929
- () => this.invalidate(),
64930
- {
64931
- debounceMs: 75,
64932
- fallbackMs: 750,
64933
- events: ["run:state", "worker:lifecycle", "ui:invalidate"]
64934
- }
64935
- );
65030
+ this.schedulerHandle = registerOverlayScheduler(() => this.invalidate());
64936
65031
  }
64937
65032
  buildSignature(manifestStatus, tasks, agents, waitingCount, snapshot) {
64938
65033
  const animation = agents.some((agent) => agent.status === "running") ? `:spin=${spinnerBucket()}` : "";
@@ -64970,7 +65065,7 @@ var init_live_run_sidebar = __esm({
64970
65065
  this.autoCloseTimeout = void 0;
64971
65066
  }
64972
65067
  this.unsubscribeTheme();
64973
- this.renderScheduler.dispose();
65068
+ this.schedulerHandle.dispose();
64974
65069
  }
64975
65070
  render(width) {
64976
65071
  const w = Math.max(36, width);