@adhdev/daemon-standalone 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.js CHANGED
@@ -30033,10 +30033,10 @@ var require_dist3 = __commonJS({
30033
30033
  }
30034
30034
  function getDaemonBuildInfo() {
30035
30035
  if (cached2) return cached2;
30036
- const commit = readInjected(true ? "9277ba79593a0feae0e17de0204856825a199ebe" : void 0) ?? "unknown";
30037
- const commitShort = readInjected(true ? "9277ba79" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
30038
- const version2 = readInjected(true ? "0.9.82-rc.329" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
30039
- const builtAt = readInjected(true ? "2026-06-19T15:57:13.250Z" : void 0);
30036
+ const commit = readInjected(true ? "ae30dde113f601dc80d6396a625c1dafb3dbaddd" : void 0) ?? "unknown";
30037
+ const commitShort = readInjected(true ? "ae30dde1" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
30038
+ const version2 = readInjected(true ? "0.9.82-rc.330" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
30039
+ const builtAt = readInjected(true ? "2026-06-19T17:21:17.821Z" : void 0);
30040
30040
  cached2 = builtAt ? { commit, commitShort, version: version2, builtAt } : { commit, commitShort, version: version2 };
30041
30041
  return cached2;
30042
30042
  }
@@ -40040,7 +40040,11 @@ Next step: ${nextStep}`;
40040
40040
  event: args.event,
40041
40041
  meshId: args.meshId,
40042
40042
  nodeId: eventNodeId || void 0,
40043
- ...args.metadataEvent
40043
+ ...args.metadataEvent,
40044
+ // Ensure a `workspace` field reaches updateMeshOwnedSession even when the
40045
+ // worker provider event only carried `workspaceName`. The merge spread of
40046
+ // metadataEvent above wins when it already has a non-empty `workspace`.
40047
+ workspace: readNonEmptyString2(args.metadataEvent.workspace) || readNonEmptyString2(args.metadataEvent.workspaceName) || void 0
40044
40048
  });
40045
40049
  } catch {
40046
40050
  }
@@ -40399,7 +40403,7 @@ Next step: ${nextStep}`;
40399
40403
  meshId: args.meshId,
40400
40404
  nodeLabel: args.nodeLabel,
40401
40405
  nodeId: args.nodeId || void 0,
40402
- workspace: readNonEmptyString2(args.metadataEvent.workspace),
40406
+ workspace: readNonEmptyString2(args.metadataEvent.workspace) || readNonEmptyString2(args.metadataEvent.workspaceName),
40403
40407
  metadataEvent: {
40404
40408
  ...args.metadataEvent,
40405
40409
  ...recoveryContext ? { recoveryContext } : {}
@@ -46951,6 +46955,13 @@ ${lastSnapshot}`;
46951
46955
  if (typeof t.key !== "string" || !t.key) errs.push("pre_launch_trust.key is required");
46952
46956
  }
46953
46957
  }
46958
+ if (spec.refocus_when_stalled_ms !== void 0) {
46959
+ if (typeof spec.refocus_when_stalled_ms !== "number" || !(spec.refocus_when_stalled_ms > 0)) {
46960
+ errs.push("refocus_when_stalled_ms must be a positive number");
46961
+ } else if (!Array.isArray(spec.send_on_spawn) || spec.send_on_spawn.length === 0) {
46962
+ errs.push("refocus_when_stalled_ms requires send_on_spawn (the wake sequence to re-inject)");
46963
+ }
46964
+ }
46954
46965
  if (!Array.isArray(spec.states) || spec.states.length === 0) {
46955
46966
  errs.push("states[] must be a non-empty array");
46956
46967
  return errs;
@@ -61493,6 +61504,14 @@ ${formatManifestValidationIssues2(validation.issues)}`,
61493
61504
  /** Timer that re-runs evaluate() when a time-condition would flip true
61494
61505
  * with no PTY frame to trigger it. */
61495
61506
  wakeTimer = null;
61507
+ /** Timer driving the focus-gated stall watchdog (refocus_when_stalled_ms).
61508
+ * Re-arms itself while the machine is generating so a re-prime can fire
61509
+ * even when the PTY has gone completely quiet. */
61510
+ stallTimer = null;
61511
+ /** Wall-clock time the last stall re-prime was injected. The cooldown gate:
61512
+ * after a re-prime we don't re-inject until the screen changes (which
61513
+ * resets the stall reference) or another full stall window lapses. */
61514
+ lastRefocusAt = 0;
61496
61515
  currentEval = null;
61497
61516
  stateHistory = [];
61498
61517
  prevStateAt = 0;
@@ -61533,11 +61552,18 @@ ${formatManifestValidationIssues2(validation.issues)}`,
61533
61552
  const seqs = this.spec.send_on_spawn;
61534
61553
  if (!Array.isArray(seqs) || seqs.length === 0) return;
61535
61554
  const delay2 = Math.max(0, this.spec.send_on_spawn_delay_ms ?? 250);
61536
- setTimeout(() => {
61537
- for (const seq of seqs) {
61538
- if (typeof seq === "string" && seq.length > 0) this.adapter.send_keys(seq);
61539
- }
61540
- }, delay2);
61555
+ setTimeout(() => this.sendSpawnPrime(), delay2);
61556
+ }
61557
+ /** Write the declared `send_on_spawn` sequences to the PTY once. Used both
61558
+ * at spawn (scheduleSpawnPrime) and, for focus-gated TUIs, by the stall
61559
+ * watchdog to re-inject the focus-in wake mid-turn. No-op when the spec
61560
+ * declares no prime, so non-focus-gated CLIs are never poked. */
61561
+ sendSpawnPrime() {
61562
+ const seqs = this.spec.send_on_spawn;
61563
+ if (!Array.isArray(seqs) || seqs.length === 0) return;
61564
+ for (const seq of seqs) {
61565
+ if (typeof seq === "string" && seq.length > 0) this.adapter.send_keys(seq);
61566
+ }
61541
61567
  }
61542
61568
  dispatch(cmd) {
61543
61569
  switch (cmd.kind) {
@@ -61598,6 +61624,10 @@ ${formatManifestValidationIssues2(validation.issues)}`,
61598
61624
  clearTimeout(this.wakeTimer);
61599
61625
  this.wakeTimer = null;
61600
61626
  }
61627
+ if (this.stallTimer) {
61628
+ clearTimeout(this.stallTimer);
61629
+ this.stallTimer = null;
61630
+ }
61601
61631
  this.specWatcher?.close();
61602
61632
  this.adapter.kill();
61603
61633
  }
@@ -61738,11 +61768,13 @@ ${formatManifestValidationIssues2(validation.issues)}`,
61738
61768
  this.commitTransition(ev.fired, now, ev);
61739
61769
  this.emitStateChanged(forceEmit);
61740
61770
  this.scheduleWakeForState();
61771
+ this.scheduleStallWatchdog();
61741
61772
  this.maybeMarkReady();
61742
61773
  return;
61743
61774
  }
61744
61775
  this.emitStateChanged(forceEmit);
61745
61776
  this.scheduleWakeForState();
61777
+ this.scheduleStallWatchdog();
61746
61778
  this.maybeMarkReady();
61747
61779
  }
61748
61780
  commitTransition(fired, now, ev) {
@@ -61890,6 +61922,67 @@ ${formatManifestValidationIssues2(validation.issues)}`,
61890
61922
  this.reevaluate();
61891
61923
  }, Math.max(soonest + 30, 50));
61892
61924
  }
61925
+ // ── Focus-gated stall watchdog (refocus_when_stalled_ms) ──────────────────
61926
+ //
61927
+ // A focus-event TUI (antigravity's `agy`) freezes its render loop the moment
61928
+ // it thinks it has lost focus mid-turn: the screen stops updating and only
61929
+ // repaints on the next keypress, which the daemon never sends. The output
61930
+ // pump is wired entirely to PTY onData (no PTY data → no reevaluate, no
61931
+ // on_screen_changed), so a normal time-wake that only re-reads the screen
61932
+ // can't help — there is nothing new to read. The fix is to re-inject the
61933
+ // focus-in wake (`send_on_spawn`) so the CLI flushes the held output itself.
61934
+ /** True when this spec opts into stall recovery (declares a positive
61935
+ * refocus window AND a wake sequence to re-inject). */
61936
+ stallRecoveryEnabled() {
61937
+ const ms = this.spec.refocus_when_stalled_ms;
61938
+ return typeof ms === "number" && ms > 0 && Array.isArray(this.spec.send_on_spawn) && this.spec.send_on_spawn.length > 0;
61939
+ }
61940
+ /** Wall-clock time the screen last changed in the current state, falling
61941
+ * back to state entry when it has not changed since (i.e. fully stalled
61942
+ * from the start of the state). */
61943
+ lastScreenChangeAt() {
61944
+ return this.regionLastChangedAt.get(-1) ?? this.stateEnteredAt;
61945
+ }
61946
+ /** Arm a timer to re-inject the focus-in prime if the screen stays frozen
61947
+ * through a `generating` state. Only active for opted-in focus-gated specs;
61948
+ * a no-op (and cleared) for every other CLI and every non-generating state.
61949
+ * Re-arms itself so it keeps watching while the PTY is quiet. */
61950
+ scheduleStallWatchdog() {
61951
+ if (this.stallTimer) {
61952
+ clearTimeout(this.stallTimer);
61953
+ this.stallTimer = null;
61954
+ }
61955
+ if (!this.stallRecoveryEnabled()) return;
61956
+ const st = stateById(this.spec, this.currentStateId);
61957
+ if (!st || statusForState(st) !== "generating") return;
61958
+ const windowMs = this.spec.refocus_when_stalled_ms;
61959
+ const since = Math.max(this.lastScreenChangeAt(), this.lastRefocusAt);
61960
+ const remaining = windowMs - (Date.now() - since);
61961
+ this.stallTimer = setTimeout(
61962
+ () => {
61963
+ this.stallTimer = null;
61964
+ this.onStallTick();
61965
+ },
61966
+ Math.max(remaining + 30, 50)
61967
+ );
61968
+ }
61969
+ /** Fire when the stall window elapses: if the screen is still frozen and we
61970
+ * are still generating, re-inject the focus-in wake once, then re-arm. */
61971
+ onStallTick() {
61972
+ if (!this.stallRecoveryEnabled()) return;
61973
+ const st = stateById(this.spec, this.currentStateId);
61974
+ if (!st || statusForState(st) !== "generating") return;
61975
+ const windowMs = this.spec.refocus_when_stalled_ms;
61976
+ const now = Date.now();
61977
+ const stalledFor = now - this.lastScreenChangeAt();
61978
+ const sinceLastRefocus = now - this.lastRefocusAt;
61979
+ if (stalledFor >= windowMs && sinceLastRefocus >= windowMs) {
61980
+ LOG2.info("FsmDriver", `[${this.specTag()}] stall detected (${stalledFor}ms quiet, generating) \u2014 re-injecting focus-in`);
61981
+ this.sendSpawnPrime();
61982
+ this.lastRefocusAt = now;
61983
+ }
61984
+ this.scheduleStallWatchdog();
61985
+ }
61893
61986
  maybeMarkReady() {
61894
61987
  if (this.readySeenOnce) return;
61895
61988
  const st = stateById(this.spec, this.currentStateId);
@@ -65120,6 +65213,12 @@ ${formatManifestValidationIssues2(validation.issues)}`,
65120
65213
  targetSessionId: typeof event.targetSessionId === "string" && event.targetSessionId.trim() ? event.targetSessionId : this.instanceId,
65121
65214
  providerType: typeof event.providerType === "string" && event.providerType.trim() ? event.providerType : this.type,
65122
65215
  workspaceName: typeof event.workspaceName === "string" && event.workspaceName.trim() ? event.workspaceName : this.workingDir,
65216
+ // Carry the workspace under BOTH `workspace` and `workspaceName` so the
65217
+ // downstream mesh forward/merge path — which reads `workspace` — can
65218
+ // propagate it to the coordinator snapshot. Without `workspace` the live
65219
+ // event path delivers an empty workspace and the dashboard falls back to
65220
+ // the generic "Terminal (Mesh Node)" title.
65221
+ workspace: typeof event.workspace === "string" && event.workspace.trim() ? event.workspace : this.workingDir,
65123
65222
  providerSessionId: typeof event.providerSessionId === "string" && event.providerSessionId.trim() ? event.providerSessionId : this.providerSessionId
65124
65223
  };
65125
65224
  if (this.context?.emitProviderEvent) {