@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.
- package/dist/cli-adapters/provider-cli-adapter.d.ts +2 -0
- package/dist/commands/router.d.ts +23 -0
- package/dist/index.js +129 -14
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +129 -14
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/cli-adapters/provider-cli-adapter.ts +52 -3
- package/src/commands/router.ts +73 -0
- package/src/mesh/mesh-event-forwarding.ts +23 -0
- package/src/mesh/mesh-queue-assignment.ts +5 -2
- package/src/mesh/mesh-runtime-store.ts +25 -3
package/dist/index.mjs
CHANGED
|
@@ -378,10 +378,10 @@ function readInjected(value) {
|
|
|
378
378
|
}
|
|
379
379
|
function getDaemonBuildInfo() {
|
|
380
380
|
if (cached) return cached;
|
|
381
|
-
const commit = readInjected(true ? "
|
|
382
|
-
const commitShort = readInjected(true ? "
|
|
383
|
-
const version = readInjected(true ? "0.9.82-rc.
|
|
384
|
-
const builtAt = readInjected(true ? "2026-06-
|
|
381
|
+
const commit = readInjected(true ? "a572a9e7fe0e92e33a309d89e35299ad42aa407d" : void 0) ?? "unknown";
|
|
382
|
+
const commitShort = readInjected(true ? "a572a9e7" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
|
|
383
|
+
const version = readInjected(true ? "0.9.82-rc.398" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
|
|
384
|
+
const builtAt = readInjected(true ? "2026-06-27T09:05:08.687Z" : void 0);
|
|
385
385
|
cached = builtAt ? { commit, commitShort, version, builtAt } : { commit, commitShort, version };
|
|
386
386
|
return cached;
|
|
387
387
|
}
|
|
@@ -5190,6 +5190,7 @@ var init_mesh_runtime_store = __esm({
|
|
|
5190
5190
|
init_load_better_sqlite3();
|
|
5191
5191
|
init_mesh_ledger();
|
|
5192
5192
|
init_mesh_work_queue();
|
|
5193
|
+
init_dist();
|
|
5193
5194
|
loggedMigrationFailure = false;
|
|
5194
5195
|
MeshRuntimeStore = class _MeshRuntimeStore {
|
|
5195
5196
|
static instance;
|
|
@@ -5722,6 +5723,8 @@ var init_mesh_runtime_store = __esm({
|
|
|
5722
5723
|
if (providerType && typeof providerMaxParallel === "number" && Number.isFinite(providerMaxParallel) && providerMaxParallel >= 0 && this.activeProviderAssignmentCount(meshId, nodeId, providerType) >= providerMaxParallel) {
|
|
5723
5724
|
return null;
|
|
5724
5725
|
}
|
|
5726
|
+
const nodeIdForms = expandDaemonIdForms(nodeId);
|
|
5727
|
+
const nodePinnedPlaceholders = nodeIdForms.map(() => "?").join(", ");
|
|
5725
5728
|
const rows = [
|
|
5726
5729
|
...this.db.prepare(`
|
|
5727
5730
|
SELECT payload FROM mesh_queue
|
|
@@ -5730,9 +5733,9 @@ var init_mesh_runtime_store = __esm({
|
|
|
5730
5733
|
`).all(meshId, sessionId),
|
|
5731
5734
|
...this.db.prepare(`
|
|
5732
5735
|
SELECT payload FROM mesh_queue
|
|
5733
|
-
WHERE mesh_id = ? AND status = 'pending' AND target_node_id
|
|
5736
|
+
WHERE mesh_id = ? AND status = 'pending' AND target_node_id IN (${nodePinnedPlaceholders}) AND target_session_id IS NULL
|
|
5734
5737
|
ORDER BY created_at ASC
|
|
5735
|
-
`).all(meshId,
|
|
5738
|
+
`).all(meshId, ...nodeIdForms),
|
|
5736
5739
|
...this.db.prepare(`
|
|
5737
5740
|
SELECT payload FROM mesh_queue
|
|
5738
5741
|
WHERE mesh_id = ? AND status = 'pending' AND target_node_id IS NULL AND target_session_id IS NULL
|
|
@@ -5762,7 +5765,9 @@ var init_mesh_runtime_store = __esm({
|
|
|
5762
5765
|
const convergenceAllows = (candidate) => candidate.taskMode !== "convergence" || !nodeIsWorktree;
|
|
5763
5766
|
const targetMatches = (candidate) => {
|
|
5764
5767
|
if (candidate.targetSessionId && candidate.targetSessionId !== sessionId) return false;
|
|
5765
|
-
if (candidate.targetNodeId && candidate.targetNodeId
|
|
5768
|
+
if (candidate.targetNodeId && !daemonIdsEquivalent(candidate.targetNodeId, nodeId) && !meshNodeIdMatches({ id: candidate.targetNodeId }, nodeId)) {
|
|
5769
|
+
return false;
|
|
5770
|
+
}
|
|
5766
5771
|
return true;
|
|
5767
5772
|
};
|
|
5768
5773
|
const entry = candidates.find((candidate) => nodeSatisfiesRequiredTags(candidate.requiredTags, capabilityTags) && dependenciesSatisfied(candidate) && convergenceAllows(candidate) && targetMatches(candidate) && nodeConflictAllows(candidate));
|
|
@@ -10779,7 +10784,7 @@ function deliverTaskToSession(dispatchThunk, ctx, warmup) {
|
|
|
10779
10784
|
}
|
|
10780
10785
|
function tryAssignQueueTask(components, meshId, nodeId, sessionId, providerType) {
|
|
10781
10786
|
const mesh = getMeshWithCache(components, meshId);
|
|
10782
|
-
const node = mesh?.nodes.find((n) =>
|
|
10787
|
+
const node = mesh?.nodes.find((n) => meshNodeIdMatches(n, nodeId));
|
|
10783
10788
|
const gateNode = mesh?.nodes?.find((n) => meshNodeIdMatches(n, nodeId));
|
|
10784
10789
|
if (gateNode?.worktreeBootstrap?.status === "running") {
|
|
10785
10790
|
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)`);
|
|
@@ -11388,7 +11393,7 @@ async function triggerMeshQueue(components, meshId) {
|
|
|
11388
11393
|
const providerType = state.type || readNonEmptyString2(settings.providerType);
|
|
11389
11394
|
if (providerType) {
|
|
11390
11395
|
localIdleSessionsChecked += 1;
|
|
11391
|
-
localCandidates.push({ nodeId, sessionId, providerType, origin: "local", node: mesh.nodes.find((n) =>
|
|
11396
|
+
localCandidates.push({ nodeId, sessionId, providerType, origin: "local", node: mesh.nodes.find((n) => meshNodeIdMatches(n, nodeId)) });
|
|
11392
11397
|
} else {
|
|
11393
11398
|
skippedSessions.push({
|
|
11394
11399
|
nodeId,
|
|
@@ -14662,6 +14667,18 @@ function injectMeshSystemMessage(components, args) {
|
|
|
14662
14667
|
completedTaskForLedger = markSessionTerminal(sessionId, "failed");
|
|
14663
14668
|
}
|
|
14664
14669
|
} else if (args.event === "worktree_bootstrap_complete" || args.event === "worktree_bootstrap_failed") {
|
|
14670
|
+
const bootstrapNodeId = readNonEmptyString2(args.nodeId) || readNonEmptyString2(args.metadataEvent.meshNodeId);
|
|
14671
|
+
if (bootstrapNodeId) {
|
|
14672
|
+
try {
|
|
14673
|
+
components.router?.markWorktreeBootstrapTerminalState?.(
|
|
14674
|
+
args.meshId,
|
|
14675
|
+
bootstrapNodeId,
|
|
14676
|
+
args.event === "worktree_bootstrap_failed" ? "failed" : "complete"
|
|
14677
|
+
);
|
|
14678
|
+
} catch (e) {
|
|
14679
|
+
LOG.warn("MeshQueue", `Failed to stamp terminal bootstrap state for ${bootstrapNodeId} (mesh ${args.meshId}): ${e?.message || e}`);
|
|
14680
|
+
}
|
|
14681
|
+
}
|
|
14665
14682
|
setImmediate(() => {
|
|
14666
14683
|
triggerMeshQueue(components, args.meshId).catch((e) => {
|
|
14667
14684
|
LOG.warn("MeshQueue", `Queue re-fire after ${args.event} failed (mesh ${args.meshId}): ${e?.message || e}`);
|
|
@@ -19589,6 +19606,16 @@ var init_provider_cli_adapter = __esm({
|
|
|
19589
19606
|
providerSessionId = null;
|
|
19590
19607
|
responseTimeout = null;
|
|
19591
19608
|
ready = false;
|
|
19609
|
+
// WIN32-READY-HOLD: the ready barrier can release on screen/spec-FSM grace
|
|
19610
|
+
// before win32 ConPTY's input layer is live. The first split write (text, then a
|
|
19611
|
+
// separate trailing CR via waitForEchoAndSubmit) then has its submit CR swallowed,
|
|
19612
|
+
// and every CR-only retry re-sends a bare CR the input layer keeps dropping — the
|
|
19613
|
+
// first message is typed-but-never-submitted and lost. Routing only the FIRST turn
|
|
19614
|
+
// through the atomic content+sendKey single write (submitImmediatePrompt) keeps the
|
|
19615
|
+
// Enter in the same PTY write unit as the text, the invariant win32 ConPTY needs to
|
|
19616
|
+
// recognize a submit, so the swallow is bypassed. Subsequent turns (input layer now
|
|
19617
|
+
// proven live) keep the normal echo-gated path. Flips true on first committed turn.
|
|
19618
|
+
firstTurnSent = false;
|
|
19592
19619
|
startupBuffer = "";
|
|
19593
19620
|
startupParseGate = false;
|
|
19594
19621
|
startupSettleTimer = null;
|
|
@@ -19875,6 +19902,7 @@ ${lastSnapshot}`;
|
|
|
19875
19902
|
this.resetTerminalScreen(DEFAULT_SESSION_HOST_ROWS4, DEFAULT_SESSION_HOST_COLS4);
|
|
19876
19903
|
this.pendingTerminalQueryTail = "";
|
|
19877
19904
|
this.ready = false;
|
|
19905
|
+
this.firstTurnSent = false;
|
|
19878
19906
|
await this.ptyProcess.ready;
|
|
19879
19907
|
this.engine.onSpawnReady();
|
|
19880
19908
|
this.scheduleStartupSettleCheck();
|
|
@@ -20339,6 +20367,7 @@ ${lastSnapshot}`;
|
|
|
20339
20367
|
commitSendUserTurn(state) {
|
|
20340
20368
|
if (state.didCommitUserTurn) return;
|
|
20341
20369
|
state.didCommitUserTurn = true;
|
|
20370
|
+
this.firstTurnSent = true;
|
|
20342
20371
|
}
|
|
20343
20372
|
armResponseTimeout() {
|
|
20344
20373
|
if (this.responseTimeout) clearTimeout(this.responseTimeout);
|
|
@@ -20361,12 +20390,30 @@ ${lastSnapshot}`;
|
|
|
20361
20390
|
LOG.warn("CLI", `[${this.cliType}] ${mode} write failed: ${error?.message || error}`);
|
|
20362
20391
|
});
|
|
20363
20392
|
}
|
|
20393
|
+
// WIN32-READY-HOLD: choose the retry write for a stuck prompt. When the FIRST turn
|
|
20394
|
+
// is stuck on win32 — the premature-ready swallow window — the prompt text itself
|
|
20395
|
+
// may have been partially eaten by a not-yet-live ConPTY input layer, so re-sending
|
|
20396
|
+
// a bare CR keeps hitting nothing. Re-type the whole `text + sendKey` atomically
|
|
20397
|
+
// once so the input layer (now live) receives a self-contained, submit-coupled
|
|
20398
|
+
// write. All other cases keep the cheap bare-CR retry (the prompt is fully echoed
|
|
20399
|
+
// and only the Enter is missing).
|
|
20400
|
+
writeStuckRetry(state, mode) {
|
|
20401
|
+
const retypeFirstTurn = process.platform === "win32" && state.isFirstTurn;
|
|
20402
|
+
if (retypeFirstTurn) {
|
|
20403
|
+
LOG.info("CLI", `[${this.cliType}] ${mode}: re-typing full prompt atomically (win32 first-turn swallow recovery)`);
|
|
20404
|
+
void this.writeToPty(state.text + this.sendKey).catch((error) => {
|
|
20405
|
+
LOG.warn("CLI", `[${this.cliType}] ${mode} re-type write failed: ${error?.message || error}`);
|
|
20406
|
+
});
|
|
20407
|
+
return;
|
|
20408
|
+
}
|
|
20409
|
+
this.writeSubmitKeyForRetry(mode);
|
|
20410
|
+
}
|
|
20364
20411
|
retrySubmitIfStuck(state, attempt) {
|
|
20365
20412
|
this.submitRetryTimer = null;
|
|
20366
20413
|
if (!this.isSubmitStuck(state.normalizedPromptSnippet)) return;
|
|
20367
20414
|
this.engine.responseSettleIgnoreUntil = Date.now() + this.timeouts.outputSettle + 400;
|
|
20368
20415
|
LOG.info("CLI", `[${this.cliType}] Retrying submit key for stuck prompt (attempt ${attempt})`);
|
|
20369
|
-
this.
|
|
20416
|
+
this.writeStuckRetry(state, "submit_retry");
|
|
20370
20417
|
if (attempt >= 3) {
|
|
20371
20418
|
this.engine.submitRetryUsed = true;
|
|
20372
20419
|
return;
|
|
@@ -20378,7 +20425,7 @@ ${lastSnapshot}`;
|
|
|
20378
20425
|
if (!this.isSubmitStuck(state.normalizedPromptSnippet)) return;
|
|
20379
20426
|
this.engine.responseSettleIgnoreUntil = Date.now() + this.timeouts.outputSettle + 400;
|
|
20380
20427
|
LOG.info("CLI", `[${this.cliType}] Retrying submit key for stuck prompt (attempt 1)`);
|
|
20381
|
-
this.
|
|
20428
|
+
this.writeStuckRetry(state, "immediate_retry");
|
|
20382
20429
|
this.engine.submitRetryUsed = true;
|
|
20383
20430
|
}
|
|
20384
20431
|
submitSendKey(state, completion) {
|
|
@@ -20631,7 +20678,9 @@ ${lastSnapshot}`;
|
|
|
20631
20678
|
submitDelayMs,
|
|
20632
20679
|
maxEchoWaitMs,
|
|
20633
20680
|
retryDelayMs,
|
|
20634
|
-
didCommitUserTurn: false
|
|
20681
|
+
didCommitUserTurn: false,
|
|
20682
|
+
// Capture BEFORE the send commits — commitSendUserTurn flips firstTurnSent.
|
|
20683
|
+
isFirstTurn: !this.firstTurnSent
|
|
20635
20684
|
};
|
|
20636
20685
|
this.engine.responseSettleIgnoreUntil = Date.now() + submitDelayMs + this.timeouts.outputSettle + 250;
|
|
20637
20686
|
await new Promise((resolve24, reject) => {
|
|
@@ -20649,7 +20698,8 @@ ${lastSnapshot}`;
|
|
|
20649
20698
|
reject(error);
|
|
20650
20699
|
}
|
|
20651
20700
|
};
|
|
20652
|
-
|
|
20701
|
+
const useAtomicFirstTurn = this.submitStrategy === "immediate" || process.platform === "win32" && sendState.isFirstTurn;
|
|
20702
|
+
if (useAtomicFirstTurn) {
|
|
20653
20703
|
this.submitImmediatePrompt(sendState, completion);
|
|
20654
20704
|
return;
|
|
20655
20705
|
}
|
|
@@ -52647,6 +52697,64 @@ var DaemonCommandRouter = class {
|
|
|
52647
52697
|
this.invalidateAggregateMeshStatus(meshId);
|
|
52648
52698
|
return true;
|
|
52649
52699
|
}
|
|
52700
|
+
/**
|
|
52701
|
+
* WORKTREE-BOOTSTRAP-COORD-STATE: mark a worktree node's bootstrap as reaching a
|
|
52702
|
+
* terminal state (complete / failed) in THIS daemon's mesh view.
|
|
52703
|
+
*
|
|
52704
|
+
* Root cause this fixes: clone_mesh_node forwards the clone+bootstrap to the
|
|
52705
|
+
* source node's daemon (the worktree's machine). persistWorktreeSetupState
|
|
52706
|
+
* therefore flips worktreeBootstrap.status to 'complete' on the WORKER daemon's
|
|
52707
|
+
* mesh object — never on the coordinator's. The coordinator only ever holds the
|
|
52708
|
+
* 'running' state it stamped from the forwarded clone reply. The claim path's
|
|
52709
|
+
* bootstrap gate (mesh-event-forwarding agent:ready / mesh-queue-assignment)
|
|
52710
|
+
* reads the coordinator's mesh via getMeshWithCache, sees status==='running'
|
|
52711
|
+
* forever, and DEFERS every claim — so the worktree_bootstrap_complete re-fire
|
|
52712
|
+
* (triggerMeshQueue) loops against a gate that never opens: claim never lands,
|
|
52713
|
+
* the idle session is re-registered each tick, and auto-launch keeps spawning
|
|
52714
|
+
* fresh sessions (runaway worktree-session multiplication).
|
|
52715
|
+
*
|
|
52716
|
+
* Called from the worktree_bootstrap_complete/_failed event handler BEFORE the
|
|
52717
|
+
* queue re-fire so the gate sees the terminal state and the deferred claim can
|
|
52718
|
+
* finally land. Updates the inline cache (clone worktree nodes are inline-only)
|
|
52719
|
+
* and, when the node also exists in local config, persists there too; both paths
|
|
52720
|
+
* invalidate the aggregate status cache. Best-effort and idempotent.
|
|
52721
|
+
*/
|
|
52722
|
+
markWorktreeBootstrapTerminalState(meshId, nodeId, status) {
|
|
52723
|
+
if (!meshId || !nodeId) return;
|
|
52724
|
+
const stamp = (mesh) => {
|
|
52725
|
+
if (!mesh || !Array.isArray(mesh.nodes)) return false;
|
|
52726
|
+
const node = mesh.nodes.find((entry) => meshNodeIdMatches(entry, nodeId));
|
|
52727
|
+
if (!node) return false;
|
|
52728
|
+
const prev = node.worktreeBootstrap && typeof node.worktreeBootstrap === "object" ? node.worktreeBootstrap : {};
|
|
52729
|
+
if (prev.status === status) return false;
|
|
52730
|
+
node.worktreeBootstrap = {
|
|
52731
|
+
...prev,
|
|
52732
|
+
status,
|
|
52733
|
+
completedAt: prev.completedAt ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
52734
|
+
};
|
|
52735
|
+
return true;
|
|
52736
|
+
};
|
|
52737
|
+
let changed = false;
|
|
52738
|
+
try {
|
|
52739
|
+
const cached3 = this.getCachedInlineMesh(meshId);
|
|
52740
|
+
if (cached3 && stamp(cached3)) {
|
|
52741
|
+
cached3.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
52742
|
+
this.inlineMeshCache.set(meshId, cached3);
|
|
52743
|
+
changed = true;
|
|
52744
|
+
}
|
|
52745
|
+
} catch {
|
|
52746
|
+
}
|
|
52747
|
+
if (changed) this.invalidateAggregateMeshStatus(meshId);
|
|
52748
|
+
void Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports)).then(({ getMesh: getMesh2, updateNode: updateNode2 }) => {
|
|
52749
|
+
const local = getMesh2(meshId);
|
|
52750
|
+
if (local && stamp(local)) {
|
|
52751
|
+
const node = local.nodes.find((entry) => meshNodeIdMatches(entry, nodeId));
|
|
52752
|
+
if (node) updateNode2(meshId, node.id, { worktreeBootstrap: node.worktreeBootstrap });
|
|
52753
|
+
this.invalidateAggregateMeshStatus(meshId);
|
|
52754
|
+
}
|
|
52755
|
+
}).catch(() => {
|
|
52756
|
+
});
|
|
52757
|
+
}
|
|
52650
52758
|
tombstoneRemovedInlineMeshNode(meshId, nodeId) {
|
|
52651
52759
|
if (!nodeId) return;
|
|
52652
52760
|
let set = this.removedInlineMeshNodeIds.get(meshId);
|
|
@@ -54030,7 +54138,14 @@ ${hintLines.join("\n")}` : "",
|
|
|
54030
54138
|
nodeId,
|
|
54031
54139
|
sessionCleanupMode: refineSessionCleanupMode,
|
|
54032
54140
|
...refineSessionIds && refineSessionIds.length > 0 ? { sessionIds: refineSessionIds } : {},
|
|
54033
|
-
inlineMesh: args?.inlineMesh
|
|
54141
|
+
inlineMesh: args?.inlineMesh,
|
|
54142
|
+
// REFINE-CLEANUP: refine reaches cleanup only AFTER a verified merge
|
|
54143
|
+
// convergence, so any residual worktree dirtiness here is incidental
|
|
54144
|
+
// (e.g. a bootstrap lockfile rewrite) — never unmerged work. `force`
|
|
54145
|
+
// sets requireClean=false so a plain-dirty worktree no longer aborts
|
|
54146
|
+
// removal with merged_cleanup_failed. Branch-ref deletion still keys off
|
|
54147
|
+
// mergeConvergence (NOT the force flag), so no merged work can be lost.
|
|
54148
|
+
force: true
|
|
54034
54149
|
});
|
|
54035
54150
|
recordMeshRefineStage(refineStages, "cleanup", removeResult?.success === false ? "failed" : "passed", cleanupStarted, {
|
|
54036
54151
|
removed: removeResult?.removed,
|