@adhdev/daemon-core 0.9.82-rc.396 → 0.9.82-rc.398

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.
@@ -39,6 +39,7 @@ export declare class ProviderCliAdapter implements CliAdapter {
39
39
  private providerSessionId;
40
40
  private responseTimeout;
41
41
  private ready;
42
+ private firstTurnSent;
42
43
  private startupBuffer;
43
44
  private startupParseGate;
44
45
  private startupSettleTimer;
@@ -178,6 +179,7 @@ export declare class ProviderCliAdapter implements CliAdapter {
178
179
  private commitSendUserTurn;
179
180
  private armResponseTimeout;
180
181
  private writeSubmitKeyForRetry;
182
+ private writeStuckRetry;
181
183
  private retrySubmitIfStuck;
182
184
  private retryImmediateSubmitIfStuck;
183
185
  private submitSendKey;
@@ -203,6 +203,29 @@ export declare class DaemonCommandRouter {
203
203
  private requireMeshHostMutationOwner;
204
204
  private updateInlineMeshNode;
205
205
  private removeInlineMeshNode;
206
+ /**
207
+ * WORKTREE-BOOTSTRAP-COORD-STATE: mark a worktree node's bootstrap as reaching a
208
+ * terminal state (complete / failed) in THIS daemon's mesh view.
209
+ *
210
+ * Root cause this fixes: clone_mesh_node forwards the clone+bootstrap to the
211
+ * source node's daemon (the worktree's machine). persistWorktreeSetupState
212
+ * therefore flips worktreeBootstrap.status to 'complete' on the WORKER daemon's
213
+ * mesh object — never on the coordinator's. The coordinator only ever holds the
214
+ * 'running' state it stamped from the forwarded clone reply. The claim path's
215
+ * bootstrap gate (mesh-event-forwarding agent:ready / mesh-queue-assignment)
216
+ * reads the coordinator's mesh via getMeshWithCache, sees status==='running'
217
+ * forever, and DEFERS every claim — so the worktree_bootstrap_complete re-fire
218
+ * (triggerMeshQueue) loops against a gate that never opens: claim never lands,
219
+ * the idle session is re-registered each tick, and auto-launch keeps spawning
220
+ * fresh sessions (runaway worktree-session multiplication).
221
+ *
222
+ * Called from the worktree_bootstrap_complete/_failed event handler BEFORE the
223
+ * queue re-fire so the gate sees the terminal state and the deferred claim can
224
+ * finally land. Updates the inline cache (clone worktree nodes are inline-only)
225
+ * and, when the node also exists in local config, persists there too; both paths
226
+ * invalidate the aggregate status cache. Best-effort and idempotent.
227
+ */
228
+ markWorktreeBootstrapTerminalState(meshId: string, nodeId: string, status: 'complete' | 'failed'): void;
206
229
  private tombstoneRemovedInlineMeshNode;
207
230
  /** Filter an incoming inline mesh against this mesh's tombstones before it is
208
231
  * reconciled into the cache. A tombstoned node is dropped only while its
package/dist/index.js CHANGED
@@ -383,10 +383,10 @@ function readInjected(value) {
383
383
  }
384
384
  function getDaemonBuildInfo() {
385
385
  if (cached) return cached;
386
- const commit = readInjected(true ? "109c5230408ea2b33e9083395b42b1c3e894aed2" : void 0) ?? "unknown";
387
- const commitShort = readInjected(true ? "109c5230" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
388
- const version = readInjected(true ? "0.9.82-rc.396" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
389
- const builtAt = readInjected(true ? "2026-06-27T07:15:39.648Z" : void 0);
386
+ const commit = readInjected(true ? "a572a9e7fe0e92e33a309d89e35299ad42aa407d" : void 0) ?? "unknown";
387
+ const commitShort = readInjected(true ? "a572a9e7" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
388
+ const version = readInjected(true ? "0.9.82-rc.398" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
389
+ const builtAt = readInjected(true ? "2026-06-27T09:05:08.687Z" : void 0);
390
390
  cached = builtAt ? { commit, commitShort, version, builtAt } : { commit, commitShort, version };
391
391
  return cached;
392
392
  }
@@ -5197,6 +5197,7 @@ var init_mesh_runtime_store = __esm({
5197
5197
  init_load_better_sqlite3();
5198
5198
  init_mesh_ledger();
5199
5199
  init_mesh_work_queue();
5200
+ init_dist();
5200
5201
  loggedMigrationFailure = false;
5201
5202
  MeshRuntimeStore = class _MeshRuntimeStore {
5202
5203
  static instance;
@@ -5729,6 +5730,8 @@ var init_mesh_runtime_store = __esm({
5729
5730
  if (providerType && typeof providerMaxParallel === "number" && Number.isFinite(providerMaxParallel) && providerMaxParallel >= 0 && this.activeProviderAssignmentCount(meshId, nodeId, providerType) >= providerMaxParallel) {
5730
5731
  return null;
5731
5732
  }
5733
+ const nodeIdForms = expandDaemonIdForms(nodeId);
5734
+ const nodePinnedPlaceholders = nodeIdForms.map(() => "?").join(", ");
5732
5735
  const rows = [
5733
5736
  ...this.db.prepare(`
5734
5737
  SELECT payload FROM mesh_queue
@@ -5737,9 +5740,9 @@ var init_mesh_runtime_store = __esm({
5737
5740
  `).all(meshId, sessionId),
5738
5741
  ...this.db.prepare(`
5739
5742
  SELECT payload FROM mesh_queue
5740
- WHERE mesh_id = ? AND status = 'pending' AND target_node_id = ? AND target_session_id IS NULL
5743
+ WHERE mesh_id = ? AND status = 'pending' AND target_node_id IN (${nodePinnedPlaceholders}) AND target_session_id IS NULL
5741
5744
  ORDER BY created_at ASC
5742
- `).all(meshId, nodeId),
5745
+ `).all(meshId, ...nodeIdForms),
5743
5746
  ...this.db.prepare(`
5744
5747
  SELECT payload FROM mesh_queue
5745
5748
  WHERE mesh_id = ? AND status = 'pending' AND target_node_id IS NULL AND target_session_id IS NULL
@@ -5769,7 +5772,9 @@ var init_mesh_runtime_store = __esm({
5769
5772
  const convergenceAllows = (candidate) => candidate.taskMode !== "convergence" || !nodeIsWorktree;
5770
5773
  const targetMatches = (candidate) => {
5771
5774
  if (candidate.targetSessionId && candidate.targetSessionId !== sessionId) return false;
5772
- if (candidate.targetNodeId && candidate.targetNodeId !== nodeId) return false;
5775
+ if (candidate.targetNodeId && !daemonIdsEquivalent(candidate.targetNodeId, nodeId) && !meshNodeIdMatches({ id: candidate.targetNodeId }, nodeId)) {
5776
+ return false;
5777
+ }
5773
5778
  return true;
5774
5779
  };
5775
5780
  const entry = candidates.find((candidate) => nodeSatisfiesRequiredTags(candidate.requiredTags, capabilityTags) && dependenciesSatisfied(candidate) && convergenceAllows(candidate) && targetMatches(candidate) && nodeConflictAllows(candidate));
@@ -10783,7 +10788,7 @@ function deliverTaskToSession(dispatchThunk, ctx, warmup) {
10783
10788
  }
10784
10789
  function tryAssignQueueTask(components, meshId, nodeId, sessionId, providerType) {
10785
10790
  const mesh = getMeshWithCache(components, meshId);
10786
- const node = mesh?.nodes.find((n) => readMeshNodeId(n) === nodeId);
10791
+ const node = mesh?.nodes.find((n) => meshNodeIdMatches(n, nodeId));
10787
10792
  const gateNode = mesh?.nodes?.find((n) => meshNodeIdMatches(n, nodeId));
10788
10793
  if (gateNode?.worktreeBootstrap?.status === "running") {
10789
10794
  LOG.info("MeshQueue", `Gating queue claim for worktree node ${nodeId} (${sessionId}): worktree bootstrap still running \u2014 task left pending; claim re-fires once bootstrap reaches a terminal state (guards against dispatching into a half-built worktree \u2192 empty session)`);
@@ -11392,7 +11397,7 @@ async function triggerMeshQueue(components, meshId) {
11392
11397
  const providerType = state.type || readNonEmptyString2(settings.providerType);
11393
11398
  if (providerType) {
11394
11399
  localIdleSessionsChecked += 1;
11395
- localCandidates.push({ nodeId, sessionId, providerType, origin: "local", node: mesh.nodes.find((n) => readMeshNodeId(n) === nodeId) });
11400
+ localCandidates.push({ nodeId, sessionId, providerType, origin: "local", node: mesh.nodes.find((n) => meshNodeIdMatches(n, nodeId)) });
11396
11401
  } else {
11397
11402
  skippedSessions.push({
11398
11403
  nodeId,
@@ -14667,6 +14672,18 @@ function injectMeshSystemMessage(components, args) {
14667
14672
  completedTaskForLedger = markSessionTerminal(sessionId, "failed");
14668
14673
  }
14669
14674
  } else if (args.event === "worktree_bootstrap_complete" || args.event === "worktree_bootstrap_failed") {
14675
+ const bootstrapNodeId = readNonEmptyString2(args.nodeId) || readNonEmptyString2(args.metadataEvent.meshNodeId);
14676
+ if (bootstrapNodeId) {
14677
+ try {
14678
+ components.router?.markWorktreeBootstrapTerminalState?.(
14679
+ args.meshId,
14680
+ bootstrapNodeId,
14681
+ args.event === "worktree_bootstrap_failed" ? "failed" : "complete"
14682
+ );
14683
+ } catch (e) {
14684
+ LOG.warn("MeshQueue", `Failed to stamp terminal bootstrap state for ${bootstrapNodeId} (mesh ${args.meshId}): ${e?.message || e}`);
14685
+ }
14686
+ }
14670
14687
  setImmediate(() => {
14671
14688
  triggerMeshQueue(components, args.meshId).catch((e) => {
14672
14689
  LOG.warn("MeshQueue", `Queue re-fire after ${args.event} failed (mesh ${args.meshId}): ${e?.message || e}`);
@@ -19596,6 +19613,16 @@ var init_provider_cli_adapter = __esm({
19596
19613
  providerSessionId = null;
19597
19614
  responseTimeout = null;
19598
19615
  ready = false;
19616
+ // WIN32-READY-HOLD: the ready barrier can release on screen/spec-FSM grace
19617
+ // before win32 ConPTY's input layer is live. The first split write (text, then a
19618
+ // separate trailing CR via waitForEchoAndSubmit) then has its submit CR swallowed,
19619
+ // and every CR-only retry re-sends a bare CR the input layer keeps dropping — the
19620
+ // first message is typed-but-never-submitted and lost. Routing only the FIRST turn
19621
+ // through the atomic content+sendKey single write (submitImmediatePrompt) keeps the
19622
+ // Enter in the same PTY write unit as the text, the invariant win32 ConPTY needs to
19623
+ // recognize a submit, so the swallow is bypassed. Subsequent turns (input layer now
19624
+ // proven live) keep the normal echo-gated path. Flips true on first committed turn.
19625
+ firstTurnSent = false;
19599
19626
  startupBuffer = "";
19600
19627
  startupParseGate = false;
19601
19628
  startupSettleTimer = null;
@@ -19882,6 +19909,7 @@ ${lastSnapshot}`;
19882
19909
  this.resetTerminalScreen(import_session_host_core5.DEFAULT_SESSION_HOST_ROWS, import_session_host_core5.DEFAULT_SESSION_HOST_COLS);
19883
19910
  this.pendingTerminalQueryTail = "";
19884
19911
  this.ready = false;
19912
+ this.firstTurnSent = false;
19885
19913
  await this.ptyProcess.ready;
19886
19914
  this.engine.onSpawnReady();
19887
19915
  this.scheduleStartupSettleCheck();
@@ -20346,6 +20374,7 @@ ${lastSnapshot}`;
20346
20374
  commitSendUserTurn(state) {
20347
20375
  if (state.didCommitUserTurn) return;
20348
20376
  state.didCommitUserTurn = true;
20377
+ this.firstTurnSent = true;
20349
20378
  }
20350
20379
  armResponseTimeout() {
20351
20380
  if (this.responseTimeout) clearTimeout(this.responseTimeout);
@@ -20368,12 +20397,30 @@ ${lastSnapshot}`;
20368
20397
  LOG.warn("CLI", `[${this.cliType}] ${mode} write failed: ${error?.message || error}`);
20369
20398
  });
20370
20399
  }
20400
+ // WIN32-READY-HOLD: choose the retry write for a stuck prompt. When the FIRST turn
20401
+ // is stuck on win32 — the premature-ready swallow window — the prompt text itself
20402
+ // may have been partially eaten by a not-yet-live ConPTY input layer, so re-sending
20403
+ // a bare CR keeps hitting nothing. Re-type the whole `text + sendKey` atomically
20404
+ // once so the input layer (now live) receives a self-contained, submit-coupled
20405
+ // write. All other cases keep the cheap bare-CR retry (the prompt is fully echoed
20406
+ // and only the Enter is missing).
20407
+ writeStuckRetry(state, mode) {
20408
+ const retypeFirstTurn = process.platform === "win32" && state.isFirstTurn;
20409
+ if (retypeFirstTurn) {
20410
+ LOG.info("CLI", `[${this.cliType}] ${mode}: re-typing full prompt atomically (win32 first-turn swallow recovery)`);
20411
+ void this.writeToPty(state.text + this.sendKey).catch((error) => {
20412
+ LOG.warn("CLI", `[${this.cliType}] ${mode} re-type write failed: ${error?.message || error}`);
20413
+ });
20414
+ return;
20415
+ }
20416
+ this.writeSubmitKeyForRetry(mode);
20417
+ }
20371
20418
  retrySubmitIfStuck(state, attempt) {
20372
20419
  this.submitRetryTimer = null;
20373
20420
  if (!this.isSubmitStuck(state.normalizedPromptSnippet)) return;
20374
20421
  this.engine.responseSettleIgnoreUntil = Date.now() + this.timeouts.outputSettle + 400;
20375
20422
  LOG.info("CLI", `[${this.cliType}] Retrying submit key for stuck prompt (attempt ${attempt})`);
20376
- this.writeSubmitKeyForRetry("submit_retry");
20423
+ this.writeStuckRetry(state, "submit_retry");
20377
20424
  if (attempt >= 3) {
20378
20425
  this.engine.submitRetryUsed = true;
20379
20426
  return;
@@ -20385,7 +20432,7 @@ ${lastSnapshot}`;
20385
20432
  if (!this.isSubmitStuck(state.normalizedPromptSnippet)) return;
20386
20433
  this.engine.responseSettleIgnoreUntil = Date.now() + this.timeouts.outputSettle + 400;
20387
20434
  LOG.info("CLI", `[${this.cliType}] Retrying submit key for stuck prompt (attempt 1)`);
20388
- this.writeSubmitKeyForRetry("immediate_retry");
20435
+ this.writeStuckRetry(state, "immediate_retry");
20389
20436
  this.engine.submitRetryUsed = true;
20390
20437
  }
20391
20438
  submitSendKey(state, completion) {
@@ -20638,7 +20685,9 @@ ${lastSnapshot}`;
20638
20685
  submitDelayMs,
20639
20686
  maxEchoWaitMs,
20640
20687
  retryDelayMs,
20641
- didCommitUserTurn: false
20688
+ didCommitUserTurn: false,
20689
+ // Capture BEFORE the send commits — commitSendUserTurn flips firstTurnSent.
20690
+ isFirstTurn: !this.firstTurnSent
20642
20691
  };
20643
20692
  this.engine.responseSettleIgnoreUntil = Date.now() + submitDelayMs + this.timeouts.outputSettle + 250;
20644
20693
  await new Promise((resolve24, reject) => {
@@ -20656,7 +20705,8 @@ ${lastSnapshot}`;
20656
20705
  reject(error);
20657
20706
  }
20658
20707
  };
20659
- if (this.submitStrategy === "immediate") {
20708
+ const useAtomicFirstTurn = this.submitStrategy === "immediate" || process.platform === "win32" && sendState.isFirstTurn;
20709
+ if (useAtomicFirstTurn) {
20660
20710
  this.submitImmediatePrompt(sendState, completion);
20661
20711
  return;
20662
20712
  }
@@ -53023,6 +53073,64 @@ var DaemonCommandRouter = class {
53023
53073
  this.invalidateAggregateMeshStatus(meshId);
53024
53074
  return true;
53025
53075
  }
53076
+ /**
53077
+ * WORKTREE-BOOTSTRAP-COORD-STATE: mark a worktree node's bootstrap as reaching a
53078
+ * terminal state (complete / failed) in THIS daemon's mesh view.
53079
+ *
53080
+ * Root cause this fixes: clone_mesh_node forwards the clone+bootstrap to the
53081
+ * source node's daemon (the worktree's machine). persistWorktreeSetupState
53082
+ * therefore flips worktreeBootstrap.status to 'complete' on the WORKER daemon's
53083
+ * mesh object — never on the coordinator's. The coordinator only ever holds the
53084
+ * 'running' state it stamped from the forwarded clone reply. The claim path's
53085
+ * bootstrap gate (mesh-event-forwarding agent:ready / mesh-queue-assignment)
53086
+ * reads the coordinator's mesh via getMeshWithCache, sees status==='running'
53087
+ * forever, and DEFERS every claim — so the worktree_bootstrap_complete re-fire
53088
+ * (triggerMeshQueue) loops against a gate that never opens: claim never lands,
53089
+ * the idle session is re-registered each tick, and auto-launch keeps spawning
53090
+ * fresh sessions (runaway worktree-session multiplication).
53091
+ *
53092
+ * Called from the worktree_bootstrap_complete/_failed event handler BEFORE the
53093
+ * queue re-fire so the gate sees the terminal state and the deferred claim can
53094
+ * finally land. Updates the inline cache (clone worktree nodes are inline-only)
53095
+ * and, when the node also exists in local config, persists there too; both paths
53096
+ * invalidate the aggregate status cache. Best-effort and idempotent.
53097
+ */
53098
+ markWorktreeBootstrapTerminalState(meshId, nodeId, status) {
53099
+ if (!meshId || !nodeId) return;
53100
+ const stamp = (mesh) => {
53101
+ if (!mesh || !Array.isArray(mesh.nodes)) return false;
53102
+ const node = mesh.nodes.find((entry) => meshNodeIdMatches(entry, nodeId));
53103
+ if (!node) return false;
53104
+ const prev = node.worktreeBootstrap && typeof node.worktreeBootstrap === "object" ? node.worktreeBootstrap : {};
53105
+ if (prev.status === status) return false;
53106
+ node.worktreeBootstrap = {
53107
+ ...prev,
53108
+ status,
53109
+ completedAt: prev.completedAt ?? (/* @__PURE__ */ new Date()).toISOString()
53110
+ };
53111
+ return true;
53112
+ };
53113
+ let changed = false;
53114
+ try {
53115
+ const cached3 = this.getCachedInlineMesh(meshId);
53116
+ if (cached3 && stamp(cached3)) {
53117
+ cached3.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
53118
+ this.inlineMeshCache.set(meshId, cached3);
53119
+ changed = true;
53120
+ }
53121
+ } catch {
53122
+ }
53123
+ if (changed) this.invalidateAggregateMeshStatus(meshId);
53124
+ void Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports)).then(({ getMesh: getMesh2, updateNode: updateNode2 }) => {
53125
+ const local = getMesh2(meshId);
53126
+ if (local && stamp(local)) {
53127
+ const node = local.nodes.find((entry) => meshNodeIdMatches(entry, nodeId));
53128
+ if (node) updateNode2(meshId, node.id, { worktreeBootstrap: node.worktreeBootstrap });
53129
+ this.invalidateAggregateMeshStatus(meshId);
53130
+ }
53131
+ }).catch(() => {
53132
+ });
53133
+ }
53026
53134
  tombstoneRemovedInlineMeshNode(meshId, nodeId) {
53027
53135
  if (!nodeId) return;
53028
53136
  let set = this.removedInlineMeshNodeIds.get(meshId);
@@ -54406,7 +54514,14 @@ ${hintLines.join("\n")}` : "",
54406
54514
  nodeId,
54407
54515
  sessionCleanupMode: refineSessionCleanupMode,
54408
54516
  ...refineSessionIds && refineSessionIds.length > 0 ? { sessionIds: refineSessionIds } : {},
54409
- inlineMesh: args?.inlineMesh
54517
+ inlineMesh: args?.inlineMesh,
54518
+ // REFINE-CLEANUP: refine reaches cleanup only AFTER a verified merge
54519
+ // convergence, so any residual worktree dirtiness here is incidental
54520
+ // (e.g. a bootstrap lockfile rewrite) — never unmerged work. `force`
54521
+ // sets requireClean=false so a plain-dirty worktree no longer aborts
54522
+ // removal with merged_cleanup_failed. Branch-ref deletion still keys off
54523
+ // mergeConvergence (NOT the force flag), so no merged work can be lost.
54524
+ force: true
54410
54525
  });
54411
54526
  recordMeshRefineStage(refineStages, "cleanup", removeResult?.success === false ? "failed" : "passed", cleanupStarted, {
54412
54527
  removed: removeResult?.removed,