@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
|
@@ -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 ? "
|
|
387
|
-
const commitShort = readInjected(true ? "
|
|
388
|
-
const version = readInjected(true ? "0.9.82-rc.
|
|
389
|
-
const builtAt = readInjected(true ? "2026-06-
|
|
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
|
|
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,
|
|
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
|
|
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) =>
|
|
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) =>
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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,
|