@adhdev/daemon-core 0.9.82-rc.329 → 0.9.82-rc.330

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
@@ -308,10 +308,10 @@ function readInjected(value) {
308
308
  }
309
309
  function getDaemonBuildInfo() {
310
310
  if (cached) return cached;
311
- const commit = readInjected(true ? "9277ba79593a0feae0e17de0204856825a199ebe" : void 0) ?? "unknown";
312
- const commitShort = readInjected(true ? "9277ba79" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
313
- const version = readInjected(true ? "0.9.82-rc.329" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
314
- const builtAt = readInjected(true ? "2026-06-19T15:56:44.269Z" : void 0);
311
+ const commit = readInjected(true ? "ae30dde113f601dc80d6396a625c1dafb3dbaddd" : void 0) ?? "unknown";
312
+ const commitShort = readInjected(true ? "ae30dde1" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
313
+ const version = readInjected(true ? "0.9.82-rc.330" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
314
+ const builtAt = readInjected(true ? "2026-06-19T17:20:52.196Z" : void 0);
315
315
  cached = builtAt ? { commit, commitShort, version, builtAt } : { commit, commitShort, version };
316
316
  return cached;
317
317
  }
@@ -10261,7 +10261,11 @@ function injectMeshSystemMessage(components, args) {
10261
10261
  event: args.event,
10262
10262
  meshId: args.meshId,
10263
10263
  nodeId: eventNodeId || void 0,
10264
- ...args.metadataEvent
10264
+ ...args.metadataEvent,
10265
+ // Ensure a `workspace` field reaches updateMeshOwnedSession even when the
10266
+ // worker provider event only carried `workspaceName`. The merge spread of
10267
+ // metadataEvent above wins when it already has a non-empty `workspace`.
10268
+ workspace: readNonEmptyString2(args.metadataEvent.workspace) || readNonEmptyString2(args.metadataEvent.workspaceName) || void 0
10265
10269
  });
10266
10270
  } catch {
10267
10271
  }
@@ -10620,7 +10624,7 @@ function injectMeshSystemMessage(components, args) {
10620
10624
  meshId: args.meshId,
10621
10625
  nodeLabel: args.nodeLabel,
10622
10626
  nodeId: args.nodeId || void 0,
10623
- workspace: readNonEmptyString2(args.metadataEvent.workspace),
10627
+ workspace: readNonEmptyString2(args.metadataEvent.workspace) || readNonEmptyString2(args.metadataEvent.workspaceName),
10624
10628
  metadataEvent: {
10625
10629
  ...args.metadataEvent,
10626
10630
  ...recoveryContext ? { recoveryContext } : {}
@@ -17159,6 +17163,13 @@ function validateFsmSpec(raw) {
17159
17163
  if (typeof t.key !== "string" || !t.key) errs.push("pre_launch_trust.key is required");
17160
17164
  }
17161
17165
  }
17166
+ if (spec.refocus_when_stalled_ms !== void 0) {
17167
+ if (typeof spec.refocus_when_stalled_ms !== "number" || !(spec.refocus_when_stalled_ms > 0)) {
17168
+ errs.push("refocus_when_stalled_ms must be a positive number");
17169
+ } else if (!Array.isArray(spec.send_on_spawn) || spec.send_on_spawn.length === 0) {
17170
+ errs.push("refocus_when_stalled_ms requires send_on_spawn (the wake sequence to re-inject)");
17171
+ }
17172
+ }
17162
17173
  if (!Array.isArray(spec.states) || spec.states.length === 0) {
17163
17174
  errs.push("states[] must be a non-empty array");
17164
17175
  return errs;
@@ -31494,6 +31505,14 @@ var FsmDriver = class {
31494
31505
  /** Timer that re-runs evaluate() when a time-condition would flip true
31495
31506
  * with no PTY frame to trigger it. */
31496
31507
  wakeTimer = null;
31508
+ /** Timer driving the focus-gated stall watchdog (refocus_when_stalled_ms).
31509
+ * Re-arms itself while the machine is generating so a re-prime can fire
31510
+ * even when the PTY has gone completely quiet. */
31511
+ stallTimer = null;
31512
+ /** Wall-clock time the last stall re-prime was injected. The cooldown gate:
31513
+ * after a re-prime we don't re-inject until the screen changes (which
31514
+ * resets the stall reference) or another full stall window lapses. */
31515
+ lastRefocusAt = 0;
31497
31516
  currentEval = null;
31498
31517
  stateHistory = [];
31499
31518
  prevStateAt = 0;
@@ -31534,11 +31553,18 @@ var FsmDriver = class {
31534
31553
  const seqs = this.spec.send_on_spawn;
31535
31554
  if (!Array.isArray(seqs) || seqs.length === 0) return;
31536
31555
  const delay2 = Math.max(0, this.spec.send_on_spawn_delay_ms ?? 250);
31537
- setTimeout(() => {
31538
- for (const seq of seqs) {
31539
- if (typeof seq === "string" && seq.length > 0) this.adapter.send_keys(seq);
31540
- }
31541
- }, delay2);
31556
+ setTimeout(() => this.sendSpawnPrime(), delay2);
31557
+ }
31558
+ /** Write the declared `send_on_spawn` sequences to the PTY once. Used both
31559
+ * at spawn (scheduleSpawnPrime) and, for focus-gated TUIs, by the stall
31560
+ * watchdog to re-inject the focus-in wake mid-turn. No-op when the spec
31561
+ * declares no prime, so non-focus-gated CLIs are never poked. */
31562
+ sendSpawnPrime() {
31563
+ const seqs = this.spec.send_on_spawn;
31564
+ if (!Array.isArray(seqs) || seqs.length === 0) return;
31565
+ for (const seq of seqs) {
31566
+ if (typeof seq === "string" && seq.length > 0) this.adapter.send_keys(seq);
31567
+ }
31542
31568
  }
31543
31569
  dispatch(cmd) {
31544
31570
  switch (cmd.kind) {
@@ -31599,6 +31625,10 @@ var FsmDriver = class {
31599
31625
  clearTimeout(this.wakeTimer);
31600
31626
  this.wakeTimer = null;
31601
31627
  }
31628
+ if (this.stallTimer) {
31629
+ clearTimeout(this.stallTimer);
31630
+ this.stallTimer = null;
31631
+ }
31602
31632
  this.specWatcher?.close();
31603
31633
  this.adapter.kill();
31604
31634
  }
@@ -31739,11 +31769,13 @@ var FsmDriver = class {
31739
31769
  this.commitTransition(ev.fired, now, ev);
31740
31770
  this.emitStateChanged(forceEmit);
31741
31771
  this.scheduleWakeForState();
31772
+ this.scheduleStallWatchdog();
31742
31773
  this.maybeMarkReady();
31743
31774
  return;
31744
31775
  }
31745
31776
  this.emitStateChanged(forceEmit);
31746
31777
  this.scheduleWakeForState();
31778
+ this.scheduleStallWatchdog();
31747
31779
  this.maybeMarkReady();
31748
31780
  }
31749
31781
  commitTransition(fired, now, ev) {
@@ -31891,6 +31923,67 @@ var FsmDriver = class {
31891
31923
  this.reevaluate();
31892
31924
  }, Math.max(soonest + 30, 50));
31893
31925
  }
31926
+ // ── Focus-gated stall watchdog (refocus_when_stalled_ms) ──────────────────
31927
+ //
31928
+ // A focus-event TUI (antigravity's `agy`) freezes its render loop the moment
31929
+ // it thinks it has lost focus mid-turn: the screen stops updating and only
31930
+ // repaints on the next keypress, which the daemon never sends. The output
31931
+ // pump is wired entirely to PTY onData (no PTY data → no reevaluate, no
31932
+ // on_screen_changed), so a normal time-wake that only re-reads the screen
31933
+ // can't help — there is nothing new to read. The fix is to re-inject the
31934
+ // focus-in wake (`send_on_spawn`) so the CLI flushes the held output itself.
31935
+ /** True when this spec opts into stall recovery (declares a positive
31936
+ * refocus window AND a wake sequence to re-inject). */
31937
+ stallRecoveryEnabled() {
31938
+ const ms = this.spec.refocus_when_stalled_ms;
31939
+ return typeof ms === "number" && ms > 0 && Array.isArray(this.spec.send_on_spawn) && this.spec.send_on_spawn.length > 0;
31940
+ }
31941
+ /** Wall-clock time the screen last changed in the current state, falling
31942
+ * back to state entry when it has not changed since (i.e. fully stalled
31943
+ * from the start of the state). */
31944
+ lastScreenChangeAt() {
31945
+ return this.regionLastChangedAt.get(-1) ?? this.stateEnteredAt;
31946
+ }
31947
+ /** Arm a timer to re-inject the focus-in prime if the screen stays frozen
31948
+ * through a `generating` state. Only active for opted-in focus-gated specs;
31949
+ * a no-op (and cleared) for every other CLI and every non-generating state.
31950
+ * Re-arms itself so it keeps watching while the PTY is quiet. */
31951
+ scheduleStallWatchdog() {
31952
+ if (this.stallTimer) {
31953
+ clearTimeout(this.stallTimer);
31954
+ this.stallTimer = null;
31955
+ }
31956
+ if (!this.stallRecoveryEnabled()) return;
31957
+ const st = stateById(this.spec, this.currentStateId);
31958
+ if (!st || statusForState(st) !== "generating") return;
31959
+ const windowMs = this.spec.refocus_when_stalled_ms;
31960
+ const since = Math.max(this.lastScreenChangeAt(), this.lastRefocusAt);
31961
+ const remaining = windowMs - (Date.now() - since);
31962
+ this.stallTimer = setTimeout(
31963
+ () => {
31964
+ this.stallTimer = null;
31965
+ this.onStallTick();
31966
+ },
31967
+ Math.max(remaining + 30, 50)
31968
+ );
31969
+ }
31970
+ /** Fire when the stall window elapses: if the screen is still frozen and we
31971
+ * are still generating, re-inject the focus-in wake once, then re-arm. */
31972
+ onStallTick() {
31973
+ if (!this.stallRecoveryEnabled()) return;
31974
+ const st = stateById(this.spec, this.currentStateId);
31975
+ if (!st || statusForState(st) !== "generating") return;
31976
+ const windowMs = this.spec.refocus_when_stalled_ms;
31977
+ const now = Date.now();
31978
+ const stalledFor = now - this.lastScreenChangeAt();
31979
+ const sinceLastRefocus = now - this.lastRefocusAt;
31980
+ if (stalledFor >= windowMs && sinceLastRefocus >= windowMs) {
31981
+ LOG.info("FsmDriver", `[${this.specTag()}] stall detected (${stalledFor}ms quiet, generating) \u2014 re-injecting focus-in`);
31982
+ this.sendSpawnPrime();
31983
+ this.lastRefocusAt = now;
31984
+ }
31985
+ this.scheduleStallWatchdog();
31986
+ }
31894
31987
  maybeMarkReady() {
31895
31988
  if (this.readySeenOnce) return;
31896
31989
  const st = stateById(this.spec, this.currentStateId);
@@ -35137,6 +35230,12 @@ var CliProviderInstance = class _CliProviderInstance {
35137
35230
  targetSessionId: typeof event.targetSessionId === "string" && event.targetSessionId.trim() ? event.targetSessionId : this.instanceId,
35138
35231
  providerType: typeof event.providerType === "string" && event.providerType.trim() ? event.providerType : this.type,
35139
35232
  workspaceName: typeof event.workspaceName === "string" && event.workspaceName.trim() ? event.workspaceName : this.workingDir,
35233
+ // Carry the workspace under BOTH `workspace` and `workspaceName` so the
35234
+ // downstream mesh forward/merge path — which reads `workspace` — can
35235
+ // propagate it to the coordinator snapshot. Without `workspace` the live
35236
+ // event path delivers an empty workspace and the dashboard falls back to
35237
+ // the generic "Terminal (Mesh Node)" title.
35238
+ workspace: typeof event.workspace === "string" && event.workspace.trim() ? event.workspace : this.workingDir,
35140
35239
  providerSessionId: typeof event.providerSessionId === "string" && event.providerSessionId.trim() ? event.providerSessionId : this.providerSessionId
35141
35240
  };
35142
35241
  if (this.context?.emitProviderEvent) {