@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.js CHANGED
@@ -313,10 +313,10 @@ function readInjected(value) {
313
313
  }
314
314
  function getDaemonBuildInfo() {
315
315
  if (cached) return cached;
316
- const commit = readInjected(true ? "9277ba79593a0feae0e17de0204856825a199ebe" : void 0) ?? "unknown";
317
- const commitShort = readInjected(true ? "9277ba79" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
318
- const version = readInjected(true ? "0.9.82-rc.329" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
319
- const builtAt = readInjected(true ? "2026-06-19T15:56:44.269Z" : void 0);
316
+ const commit = readInjected(true ? "ae30dde113f601dc80d6396a625c1dafb3dbaddd" : void 0) ?? "unknown";
317
+ const commitShort = readInjected(true ? "ae30dde1" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
318
+ const version = readInjected(true ? "0.9.82-rc.330" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
319
+ const builtAt = readInjected(true ? "2026-06-19T17:20:52.196Z" : void 0);
320
320
  cached = builtAt ? { commit, commitShort, version, builtAt } : { commit, commitShort, version };
321
321
  return cached;
322
322
  }
@@ -10264,7 +10264,11 @@ function injectMeshSystemMessage(components, args) {
10264
10264
  event: args.event,
10265
10265
  meshId: args.meshId,
10266
10266
  nodeId: eventNodeId || void 0,
10267
- ...args.metadataEvent
10267
+ ...args.metadataEvent,
10268
+ // Ensure a `workspace` field reaches updateMeshOwnedSession even when the
10269
+ // worker provider event only carried `workspaceName`. The merge spread of
10270
+ // metadataEvent above wins when it already has a non-empty `workspace`.
10271
+ workspace: readNonEmptyString2(args.metadataEvent.workspace) || readNonEmptyString2(args.metadataEvent.workspaceName) || void 0
10268
10272
  });
10269
10273
  } catch {
10270
10274
  }
@@ -10623,7 +10627,7 @@ function injectMeshSystemMessage(components, args) {
10623
10627
  meshId: args.meshId,
10624
10628
  nodeLabel: args.nodeLabel,
10625
10629
  nodeId: args.nodeId || void 0,
10626
- workspace: readNonEmptyString2(args.metadataEvent.workspace),
10630
+ workspace: readNonEmptyString2(args.metadataEvent.workspace) || readNonEmptyString2(args.metadataEvent.workspaceName),
10627
10631
  metadataEvent: {
10628
10632
  ...args.metadataEvent,
10629
10633
  ...recoveryContext ? { recoveryContext } : {}
@@ -17163,6 +17167,13 @@ function validateFsmSpec(raw) {
17163
17167
  if (typeof t.key !== "string" || !t.key) errs.push("pre_launch_trust.key is required");
17164
17168
  }
17165
17169
  }
17170
+ if (spec.refocus_when_stalled_ms !== void 0) {
17171
+ if (typeof spec.refocus_when_stalled_ms !== "number" || !(spec.refocus_when_stalled_ms > 0)) {
17172
+ errs.push("refocus_when_stalled_ms must be a positive number");
17173
+ } else if (!Array.isArray(spec.send_on_spawn) || spec.send_on_spawn.length === 0) {
17174
+ errs.push("refocus_when_stalled_ms requires send_on_spawn (the wake sequence to re-inject)");
17175
+ }
17176
+ }
17166
17177
  if (!Array.isArray(spec.states) || spec.states.length === 0) {
17167
17178
  errs.push("states[] must be a non-empty array");
17168
17179
  return errs;
@@ -31854,6 +31865,14 @@ var FsmDriver = class {
31854
31865
  /** Timer that re-runs evaluate() when a time-condition would flip true
31855
31866
  * with no PTY frame to trigger it. */
31856
31867
  wakeTimer = null;
31868
+ /** Timer driving the focus-gated stall watchdog (refocus_when_stalled_ms).
31869
+ * Re-arms itself while the machine is generating so a re-prime can fire
31870
+ * even when the PTY has gone completely quiet. */
31871
+ stallTimer = null;
31872
+ /** Wall-clock time the last stall re-prime was injected. The cooldown gate:
31873
+ * after a re-prime we don't re-inject until the screen changes (which
31874
+ * resets the stall reference) or another full stall window lapses. */
31875
+ lastRefocusAt = 0;
31857
31876
  currentEval = null;
31858
31877
  stateHistory = [];
31859
31878
  prevStateAt = 0;
@@ -31894,11 +31913,18 @@ var FsmDriver = class {
31894
31913
  const seqs = this.spec.send_on_spawn;
31895
31914
  if (!Array.isArray(seqs) || seqs.length === 0) return;
31896
31915
  const delay2 = Math.max(0, this.spec.send_on_spawn_delay_ms ?? 250);
31897
- setTimeout(() => {
31898
- for (const seq of seqs) {
31899
- if (typeof seq === "string" && seq.length > 0) this.adapter.send_keys(seq);
31900
- }
31901
- }, delay2);
31916
+ setTimeout(() => this.sendSpawnPrime(), delay2);
31917
+ }
31918
+ /** Write the declared `send_on_spawn` sequences to the PTY once. Used both
31919
+ * at spawn (scheduleSpawnPrime) and, for focus-gated TUIs, by the stall
31920
+ * watchdog to re-inject the focus-in wake mid-turn. No-op when the spec
31921
+ * declares no prime, so non-focus-gated CLIs are never poked. */
31922
+ sendSpawnPrime() {
31923
+ const seqs = this.spec.send_on_spawn;
31924
+ if (!Array.isArray(seqs) || seqs.length === 0) return;
31925
+ for (const seq of seqs) {
31926
+ if (typeof seq === "string" && seq.length > 0) this.adapter.send_keys(seq);
31927
+ }
31902
31928
  }
31903
31929
  dispatch(cmd) {
31904
31930
  switch (cmd.kind) {
@@ -31959,6 +31985,10 @@ var FsmDriver = class {
31959
31985
  clearTimeout(this.wakeTimer);
31960
31986
  this.wakeTimer = null;
31961
31987
  }
31988
+ if (this.stallTimer) {
31989
+ clearTimeout(this.stallTimer);
31990
+ this.stallTimer = null;
31991
+ }
31962
31992
  this.specWatcher?.close();
31963
31993
  this.adapter.kill();
31964
31994
  }
@@ -32099,11 +32129,13 @@ var FsmDriver = class {
32099
32129
  this.commitTransition(ev.fired, now, ev);
32100
32130
  this.emitStateChanged(forceEmit);
32101
32131
  this.scheduleWakeForState();
32132
+ this.scheduleStallWatchdog();
32102
32133
  this.maybeMarkReady();
32103
32134
  return;
32104
32135
  }
32105
32136
  this.emitStateChanged(forceEmit);
32106
32137
  this.scheduleWakeForState();
32138
+ this.scheduleStallWatchdog();
32107
32139
  this.maybeMarkReady();
32108
32140
  }
32109
32141
  commitTransition(fired, now, ev) {
@@ -32251,6 +32283,67 @@ var FsmDriver = class {
32251
32283
  this.reevaluate();
32252
32284
  }, Math.max(soonest + 30, 50));
32253
32285
  }
32286
+ // ── Focus-gated stall watchdog (refocus_when_stalled_ms) ──────────────────
32287
+ //
32288
+ // A focus-event TUI (antigravity's `agy`) freezes its render loop the moment
32289
+ // it thinks it has lost focus mid-turn: the screen stops updating and only
32290
+ // repaints on the next keypress, which the daemon never sends. The output
32291
+ // pump is wired entirely to PTY onData (no PTY data → no reevaluate, no
32292
+ // on_screen_changed), so a normal time-wake that only re-reads the screen
32293
+ // can't help — there is nothing new to read. The fix is to re-inject the
32294
+ // focus-in wake (`send_on_spawn`) so the CLI flushes the held output itself.
32295
+ /** True when this spec opts into stall recovery (declares a positive
32296
+ * refocus window AND a wake sequence to re-inject). */
32297
+ stallRecoveryEnabled() {
32298
+ const ms = this.spec.refocus_when_stalled_ms;
32299
+ return typeof ms === "number" && ms > 0 && Array.isArray(this.spec.send_on_spawn) && this.spec.send_on_spawn.length > 0;
32300
+ }
32301
+ /** Wall-clock time the screen last changed in the current state, falling
32302
+ * back to state entry when it has not changed since (i.e. fully stalled
32303
+ * from the start of the state). */
32304
+ lastScreenChangeAt() {
32305
+ return this.regionLastChangedAt.get(-1) ?? this.stateEnteredAt;
32306
+ }
32307
+ /** Arm a timer to re-inject the focus-in prime if the screen stays frozen
32308
+ * through a `generating` state. Only active for opted-in focus-gated specs;
32309
+ * a no-op (and cleared) for every other CLI and every non-generating state.
32310
+ * Re-arms itself so it keeps watching while the PTY is quiet. */
32311
+ scheduleStallWatchdog() {
32312
+ if (this.stallTimer) {
32313
+ clearTimeout(this.stallTimer);
32314
+ this.stallTimer = null;
32315
+ }
32316
+ if (!this.stallRecoveryEnabled()) return;
32317
+ const st = stateById(this.spec, this.currentStateId);
32318
+ if (!st || statusForState(st) !== "generating") return;
32319
+ const windowMs = this.spec.refocus_when_stalled_ms;
32320
+ const since = Math.max(this.lastScreenChangeAt(), this.lastRefocusAt);
32321
+ const remaining = windowMs - (Date.now() - since);
32322
+ this.stallTimer = setTimeout(
32323
+ () => {
32324
+ this.stallTimer = null;
32325
+ this.onStallTick();
32326
+ },
32327
+ Math.max(remaining + 30, 50)
32328
+ );
32329
+ }
32330
+ /** Fire when the stall window elapses: if the screen is still frozen and we
32331
+ * are still generating, re-inject the focus-in wake once, then re-arm. */
32332
+ onStallTick() {
32333
+ if (!this.stallRecoveryEnabled()) return;
32334
+ const st = stateById(this.spec, this.currentStateId);
32335
+ if (!st || statusForState(st) !== "generating") return;
32336
+ const windowMs = this.spec.refocus_when_stalled_ms;
32337
+ const now = Date.now();
32338
+ const stalledFor = now - this.lastScreenChangeAt();
32339
+ const sinceLastRefocus = now - this.lastRefocusAt;
32340
+ if (stalledFor >= windowMs && sinceLastRefocus >= windowMs) {
32341
+ LOG.info("FsmDriver", `[${this.specTag()}] stall detected (${stalledFor}ms quiet, generating) \u2014 re-injecting focus-in`);
32342
+ this.sendSpawnPrime();
32343
+ this.lastRefocusAt = now;
32344
+ }
32345
+ this.scheduleStallWatchdog();
32346
+ }
32254
32347
  maybeMarkReady() {
32255
32348
  if (this.readySeenOnce) return;
32256
32349
  const st = stateById(this.spec, this.currentStateId);
@@ -35497,6 +35590,12 @@ var CliProviderInstance = class _CliProviderInstance {
35497
35590
  targetSessionId: typeof event.targetSessionId === "string" && event.targetSessionId.trim() ? event.targetSessionId : this.instanceId,
35498
35591
  providerType: typeof event.providerType === "string" && event.providerType.trim() ? event.providerType : this.type,
35499
35592
  workspaceName: typeof event.workspaceName === "string" && event.workspaceName.trim() ? event.workspaceName : this.workingDir,
35593
+ // Carry the workspace under BOTH `workspace` and `workspaceName` so the
35594
+ // downstream mesh forward/merge path — which reads `workspace` — can
35595
+ // propagate it to the coordinator snapshot. Without `workspace` the live
35596
+ // event path delivers an empty workspace and the dashboard falls back to
35597
+ // the generic "Terminal (Mesh Node)" title.
35598
+ workspace: typeof event.workspace === "string" && event.workspace.trim() ? event.workspace : this.workingDir,
35500
35599
  providerSessionId: typeof event.providerSessionId === "string" && event.providerSessionId.trim() ? event.providerSessionId : this.providerSessionId
35501
35600
  };
35502
35601
  if (this.context?.emitProviderEvent) {