@adhdev/daemon-core 0.9.82-rc.419 → 0.9.82-rc.420
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/commands/med-family/types.d.ts +25 -0
- package/dist/commands/router.d.ts +20 -0
- package/dist/index.js +233 -8
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +233 -8
- package/dist/index.mjs.map +1 -1
- package/dist/mesh/mesh-clone-grace.d.ts +8 -0
- package/dist/mesh/mesh-events-pending.d.ts +14 -0
- package/package.json +2 -2
- package/src/commands/med-family/mesh-crud.ts +47 -0
- package/src/commands/med-family/types.ts +22 -0
- package/src/commands/router.ts +128 -0
- package/src/mesh/mesh-clone-grace.ts +68 -0
- package/src/mesh/mesh-events-pending.ts +51 -0
- package/src/mesh/mesh-queue-assignment.ts +97 -4
package/dist/index.mjs
CHANGED
|
@@ -384,10 +384,10 @@ function readInjected(value) {
|
|
|
384
384
|
}
|
|
385
385
|
function getDaemonBuildInfo() {
|
|
386
386
|
if (cached) return cached;
|
|
387
|
-
const commit = readInjected(true ? "
|
|
388
|
-
const commitShort = readInjected(true ? "
|
|
389
|
-
const version = readInjected(true ? "0.9.82-rc.
|
|
390
|
-
const builtAt = readInjected(true ? "2026-06-
|
|
387
|
+
const commit = readInjected(true ? "bd19cb0fbe1e16db1d8d2fcd8c2c871aae3ce33b" : void 0) ?? "unknown";
|
|
388
|
+
const commitShort = readInjected(true ? "bd19cb0f" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
|
|
389
|
+
const version = readInjected(true ? "0.9.82-rc.420" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
|
|
390
|
+
const builtAt = readInjected(true ? "2026-06-28T19:41:31.696Z" : void 0);
|
|
391
391
|
cached = builtAt ? { commit, commitShort, version, builtAt } : { commit, commitShort, version };
|
|
392
392
|
return cached;
|
|
393
393
|
}
|
|
@@ -10909,6 +10909,35 @@ function drainPendingMeshCoordinatorEvents(meshId, coordinatorDaemonId, opts) {
|
|
|
10909
10909
|
if (merged.length === 0) return [];
|
|
10910
10910
|
return reconcilePendingMeshCoordinatorEvents(meshId, merged);
|
|
10911
10911
|
}
|
|
10912
|
+
function retractPendingDispatchBlockedEvent(meshId, taskId, coordinatorDaemonId) {
|
|
10913
|
+
if (!meshId || !taskId) return 0;
|
|
10914
|
+
let removed = 0;
|
|
10915
|
+
const matchesTask = (event) => {
|
|
10916
|
+
if (!event || event.event !== "mesh:dispatch_blocked") return false;
|
|
10917
|
+
const rowTaskId = readNonEmptyString2(event.metadataEvent?.taskId);
|
|
10918
|
+
return rowTaskId === taskId;
|
|
10919
|
+
};
|
|
10920
|
+
try {
|
|
10921
|
+
const store = MeshRuntimeStore.getInstance();
|
|
10922
|
+
const ids = [];
|
|
10923
|
+
for (const row of store.peekPendingEvents(meshId)) {
|
|
10924
|
+
if (row.event !== "mesh:dispatch_blocked") continue;
|
|
10925
|
+
if (matchesTask(row.payload)) ids.push(row.id);
|
|
10926
|
+
}
|
|
10927
|
+
if (ids.length) removed += store.deletePendingEventsById(ids);
|
|
10928
|
+
} catch {
|
|
10929
|
+
}
|
|
10930
|
+
const daemonIds = normalizeCoordinatorDaemonIds(coordinatorDaemonId);
|
|
10931
|
+
const primaryDaemonId = daemonIds[0];
|
|
10932
|
+
const paths = primaryDaemonId ? [getPendingEventsPath(meshId, primaryDaemonId), getPendingEventsPath(meshId)] : [getPendingEventsPath(meshId)];
|
|
10933
|
+
for (const path43 of paths) {
|
|
10934
|
+
try {
|
|
10935
|
+
removed += selectiveDrainFile(path43, matchesTask).length;
|
|
10936
|
+
} catch {
|
|
10937
|
+
}
|
|
10938
|
+
}
|
|
10939
|
+
return removed;
|
|
10940
|
+
}
|
|
10912
10941
|
function getPendingMeshCoordinatorEvents(meshId, coordinatorDaemonId) {
|
|
10913
10942
|
if (!meshId) return [];
|
|
10914
10943
|
const daemonIds = normalizeCoordinatorDaemonIds(coordinatorDaemonId);
|
|
@@ -11960,6 +11989,41 @@ var init_mesh_warmup_deadline = __esm({
|
|
|
11960
11989
|
}
|
|
11961
11990
|
});
|
|
11962
11991
|
|
|
11992
|
+
// src/mesh/mesh-clone-grace.ts
|
|
11993
|
+
function normalizeNodeIdKey(nodeId) {
|
|
11994
|
+
return normalizeMeshNodeId({ id: nodeId ?? void 0 }) ?? "";
|
|
11995
|
+
}
|
|
11996
|
+
function noteRecentlyClonedNode(nodeId, nowMs = Date.now()) {
|
|
11997
|
+
const key2 = normalizeNodeIdKey(nodeId);
|
|
11998
|
+
if (!key2) return;
|
|
11999
|
+
recentlyClonedNodeExpiry.set(key2, nowMs + CLONE_BOOTSTRAP_GRACE_MS);
|
|
12000
|
+
if (recentlyClonedNodeExpiry.size > MAX_TRACKED_CLONED_NODES) {
|
|
12001
|
+
const oldest = recentlyClonedNodeExpiry.keys().next().value;
|
|
12002
|
+
if (oldest !== void 0) recentlyClonedNodeExpiry.delete(oldest);
|
|
12003
|
+
}
|
|
12004
|
+
}
|
|
12005
|
+
function isWithinCloneBootstrapGrace(nodeId, nowMs = Date.now()) {
|
|
12006
|
+
const key2 = normalizeNodeIdKey(nodeId);
|
|
12007
|
+
if (!key2) return false;
|
|
12008
|
+
const expiry = recentlyClonedNodeExpiry.get(key2);
|
|
12009
|
+
if (expiry === void 0) return false;
|
|
12010
|
+
if (nowMs >= expiry) {
|
|
12011
|
+
recentlyClonedNodeExpiry.delete(key2);
|
|
12012
|
+
return false;
|
|
12013
|
+
}
|
|
12014
|
+
return true;
|
|
12015
|
+
}
|
|
12016
|
+
var CLONE_BOOTSTRAP_GRACE_MS, recentlyClonedNodeExpiry, MAX_TRACKED_CLONED_NODES;
|
|
12017
|
+
var init_mesh_clone_grace = __esm({
|
|
12018
|
+
"src/mesh/mesh-clone-grace.ts"() {
|
|
12019
|
+
"use strict";
|
|
12020
|
+
init_dist();
|
|
12021
|
+
CLONE_BOOTSTRAP_GRACE_MS = 10 * 60 * 1e3;
|
|
12022
|
+
recentlyClonedNodeExpiry = /* @__PURE__ */ new Map();
|
|
12023
|
+
MAX_TRACKED_CLONED_NODES = 512;
|
|
12024
|
+
}
|
|
12025
|
+
});
|
|
12026
|
+
|
|
11963
12027
|
// src/mesh/mesh-queue-assignment.ts
|
|
11964
12028
|
import { existsSync as existsSync17 } from "fs";
|
|
11965
12029
|
function localCoordinatorDaemonId() {
|
|
@@ -12124,6 +12188,7 @@ function tryAssignQueueTask(components, meshId, nodeId, sessionId, providerType)
|
|
|
12124
12188
|
return false;
|
|
12125
12189
|
}
|
|
12126
12190
|
LOG.info("MeshQueue", `Node ${nodeId} (${sessionId}) pulled task ${task.id}`);
|
|
12191
|
+
retractActionableSkipIfPreviouslyNotified(meshId, task.id);
|
|
12127
12192
|
beginTaskDispatchInFlight(meshId, task.id);
|
|
12128
12193
|
if (node?.daemonId && components.dispatchMeshCommand) {
|
|
12129
12194
|
const isLocalNode = components.cliManager.adapters.has(sessionId);
|
|
@@ -12213,6 +12278,28 @@ function isActionableSkipReason(reason) {
|
|
|
12213
12278
|
if (!reason) return false;
|
|
12214
12279
|
return ACTIONABLE_SKIP_REASON_PREFIXES.some((prefix) => reason === prefix || reason.startsWith(prefix));
|
|
12215
12280
|
}
|
|
12281
|
+
function isTargetNodeTransientlyUnresolved(mesh, task) {
|
|
12282
|
+
const targetNodeId = readNonEmptyString2(task.targetNodeId);
|
|
12283
|
+
if (!targetNodeId) return false;
|
|
12284
|
+
const node = Array.isArray(mesh?.nodes) ? mesh.nodes.find((n) => meshNodeIdMatches(n, targetNodeId)) : void 0;
|
|
12285
|
+
if (node && node.worktreeBootstrap?.status === "running" && !isWorktreeBootstrapStaleRunning(node)) {
|
|
12286
|
+
return true;
|
|
12287
|
+
}
|
|
12288
|
+
return isWithinCloneBootstrapGrace(targetNodeId);
|
|
12289
|
+
}
|
|
12290
|
+
function retractActionableSkipIfPreviouslyNotified(meshId, taskId) {
|
|
12291
|
+
const dedupKey = `${meshId}:${taskId}`;
|
|
12292
|
+
if (!lastActionableSkipNotified.delete(dedupKey)) return;
|
|
12293
|
+
try {
|
|
12294
|
+
const coordinatorDaemonId = readNonEmptyString2(loadConfig().machineId) || void 0;
|
|
12295
|
+
const removed = retractPendingDispatchBlockedEvent(meshId, taskId, coordinatorDaemonId);
|
|
12296
|
+
if (removed > 0) {
|
|
12297
|
+
LOG.info("MeshQueue", `Retracted ${removed} stale dispatch-blocked event(s) for task ${taskId} (mesh ${meshId}) \u2014 its blocker resolved`);
|
|
12298
|
+
}
|
|
12299
|
+
} catch (e) {
|
|
12300
|
+
LOG.warn("MeshQueue", `Failed to retract stale dispatch-blocked event for task ${taskId} (mesh ${meshId}): ${e?.message || e}`);
|
|
12301
|
+
}
|
|
12302
|
+
}
|
|
12216
12303
|
function actionableSkipGuidance(reason) {
|
|
12217
12304
|
if (reason === "target_node_id_unmatched") return {
|
|
12218
12305
|
summary: "it is pinned to a target node id that matches no node in the mesh (the node may have been removed, or its id form does not resolve)",
|
|
@@ -12245,6 +12332,7 @@ function actionableSkipGuidance(reason) {
|
|
|
12245
12332
|
}
|
|
12246
12333
|
function notifyCoordinatorOfActionableSkip(meshId, taskId, reason, nodeId) {
|
|
12247
12334
|
if (!isActionableSkipReason(reason)) return;
|
|
12335
|
+
if (reason === "target_node_id_unmatched" && isWithinCloneBootstrapGrace(readNonEmptyString2(nodeId))) return;
|
|
12248
12336
|
const dedupKey = `${meshId}:${taskId}`;
|
|
12249
12337
|
if (lastActionableSkipNotified.get(dedupKey) === reason) return;
|
|
12250
12338
|
lastActionableSkipNotified.set(dedupKey, reason);
|
|
@@ -12482,9 +12570,13 @@ function markAutoLaunch(meshId, taskId, args) {
|
|
|
12482
12570
|
error: args.error
|
|
12483
12571
|
});
|
|
12484
12572
|
if (args.status === "skipped") {
|
|
12485
|
-
|
|
12573
|
+
if (isActionableSkipReason(args.reason)) {
|
|
12574
|
+
notifyCoordinatorOfActionableSkip(meshId, taskId, args.reason, args.nodeId);
|
|
12575
|
+
} else if (args.reason === TRANSIENT_TARGET_NODE_BOOTSTRAP_PENDING_REASON) {
|
|
12576
|
+
retractActionableSkipIfPreviouslyNotified(meshId, taskId);
|
|
12577
|
+
}
|
|
12486
12578
|
} else {
|
|
12487
|
-
|
|
12579
|
+
retractActionableSkipIfPreviouslyNotified(meshId, taskId);
|
|
12488
12580
|
}
|
|
12489
12581
|
}
|
|
12490
12582
|
async function resolveUsableProvider(components, nodeId, node, requiredTags) {
|
|
@@ -12572,9 +12664,10 @@ async function maybeAutoLaunchOneQueueSession(components, meshId, mesh) {
|
|
|
12572
12664
|
const matched = mesh.nodes.filter((n) => !task.targetNodeId || meshNodeIdMatches(n, task.targetNodeId));
|
|
12573
12665
|
return matched.length > 0 && matched.every((n) => n?.isLocalWorktree === true);
|
|
12574
12666
|
})();
|
|
12667
|
+
const targetTransientlyUnresolved = targetPinUnmatched && isTargetNodeTransientlyUnresolved(mesh, task);
|
|
12575
12668
|
markAutoLaunch(meshId, task.id, {
|
|
12576
12669
|
status: "skipped",
|
|
12577
|
-
reason: convergenceOntoWorktree ? "mesh_convergence_target_is_worktree" : targetPinUnmatched ? "target_node_id_unmatched" : "no_node_satisfies_required_tags",
|
|
12670
|
+
reason: convergenceOntoWorktree ? "mesh_convergence_target_is_worktree" : targetTransientlyUnresolved ? TRANSIENT_TARGET_NODE_BOOTSTRAP_PENDING_REASON : targetPinUnmatched ? "target_node_id_unmatched" : "no_node_satisfies_required_tags",
|
|
12578
12671
|
nodeId: task.targetNodeId
|
|
12579
12672
|
});
|
|
12580
12673
|
continue;
|
|
@@ -12927,7 +13020,7 @@ function runIdleMaintenanceThenAssignQueue(components, args) {
|
|
|
12927
13020
|
});
|
|
12928
13021
|
});
|
|
12929
13022
|
}
|
|
12930
|
-
var IDLE_AUTO_FAST_FORWARD_THROTTLE_MS, idleAutoFastForwardLastAttempt, DISPATCH_CONFIRM_TIMEOUT_MS, DISPATCH_CONNECT_TIMEOUT_MS, dispatchWarmupGetterMissingWarned, LOCAL_LAUNCH_READY_TIMEOUT_MS, LOCAL_LAUNCH_READY_POLL_MS, autoLaunchInProgress, autoLaunchCooldownUntil, AUTO_LAUNCH_COOLDOWN_MS, AUTO_LAUNCH_AWAIT_CLAIM_MS, lastAutoLaunchLedgerKey, AUTO_LAUNCH_LEDGER_DEDUP_MAX, ACTIONABLE_SKIP_REASON_PREFIXES, lastActionableSkipNotified;
|
|
13023
|
+
var IDLE_AUTO_FAST_FORWARD_THROTTLE_MS, idleAutoFastForwardLastAttempt, DISPATCH_CONFIRM_TIMEOUT_MS, DISPATCH_CONNECT_TIMEOUT_MS, dispatchWarmupGetterMissingWarned, LOCAL_LAUNCH_READY_TIMEOUT_MS, LOCAL_LAUNCH_READY_POLL_MS, autoLaunchInProgress, autoLaunchCooldownUntil, AUTO_LAUNCH_COOLDOWN_MS, AUTO_LAUNCH_AWAIT_CLAIM_MS, lastAutoLaunchLedgerKey, AUTO_LAUNCH_LEDGER_DEDUP_MAX, ACTIONABLE_SKIP_REASON_PREFIXES, TRANSIENT_TARGET_NODE_BOOTSTRAP_PENDING_REASON, lastActionableSkipNotified;
|
|
12931
13024
|
var init_mesh_queue_assignment = __esm({
|
|
12932
13025
|
"src/mesh/mesh-queue-assignment.ts"() {
|
|
12933
13026
|
"use strict";
|
|
@@ -12949,6 +13042,7 @@ var init_mesh_queue_assignment = __esm({
|
|
|
12949
13042
|
init_mesh_events_utils();
|
|
12950
13043
|
init_mesh_events_pending();
|
|
12951
13044
|
init_worktree_bootstrap_config();
|
|
13045
|
+
init_mesh_clone_grace();
|
|
12952
13046
|
init_mesh_task_inflight();
|
|
12953
13047
|
IDLE_AUTO_FAST_FORWARD_THROTTLE_MS = 30 * 60 * 1e3;
|
|
12954
13048
|
idleAutoFastForwardLastAttempt = /* @__PURE__ */ new Map();
|
|
@@ -12975,6 +13069,7 @@ var init_mesh_queue_assignment = __esm({
|
|
|
12975
13069
|
"provider_unusable",
|
|
12976
13070
|
"dirty_workspace"
|
|
12977
13071
|
];
|
|
13072
|
+
TRANSIENT_TARGET_NODE_BOOTSTRAP_PENDING_REASON = "target_node_bootstrap_pending";
|
|
12978
13073
|
lastActionableSkipNotified = /* @__PURE__ */ new Map();
|
|
12979
13074
|
}
|
|
12980
13075
|
});
|
|
@@ -48596,6 +48691,7 @@ init_dist();
|
|
|
48596
48691
|
init_mesh_host_ownership();
|
|
48597
48692
|
init_worktree_bootstrap_config();
|
|
48598
48693
|
init_mesh_events();
|
|
48694
|
+
init_mesh_clone_grace();
|
|
48599
48695
|
init_config();
|
|
48600
48696
|
async function decideOssCloneSync(ossCtx, worktreeOssSha, sourceSha, rg) {
|
|
48601
48697
|
if (!worktreeOssSha || !sourceSha || worktreeOssSha === sourceSha) return "noop";
|
|
@@ -48877,6 +48973,26 @@ var meshCrudHandlers = {
|
|
|
48877
48973
|
explicitCleanupMode ?? (node?.isLocalWorktree === true ? "stop_and_delete" : void 0) ?? mesh?.policy?.sessionCleanupOnNodeRemove
|
|
48878
48974
|
);
|
|
48879
48975
|
const explicitSessionIds = Array.isArray(args?.sessionIds) ? args.sessionIds.filter((v) => typeof v === "string" && v.trim().length > 0).map((v) => v.trim()) : void 0;
|
|
48976
|
+
if (node?.isLocalWorktree) {
|
|
48977
|
+
const nodeDaemonId = typeof node.daemonId === "string" ? node.daemonId.trim() : void 0;
|
|
48978
|
+
const isRemoteWorktree = nodeDaemonId && !daemonIdsEquivalent(nodeDaemonId, ctx.deps.statusInstanceId) && ctx.deps.dispatchMeshCommand && !args?._meshDirectDispatch;
|
|
48979
|
+
if (!isRemoteWorktree) {
|
|
48980
|
+
const precheck = await ctx.precheckLocalWorktreeRemovable({ mesh, node, nodeId, force: args?.force === true });
|
|
48981
|
+
if (precheck.ok === false) {
|
|
48982
|
+
return {
|
|
48983
|
+
success: false,
|
|
48984
|
+
removed: false,
|
|
48985
|
+
code: precheck.code,
|
|
48986
|
+
error: precheck.error,
|
|
48987
|
+
recoveryHint: precheck.recoveryHint,
|
|
48988
|
+
// No sessionCleanup key: the session was deliberately NOT
|
|
48989
|
+
// touched. worktreeCleanup mirrors the destructive path's
|
|
48990
|
+
// refusal shape so existing callers see the same code.
|
|
48991
|
+
worktreeCleanup: { success: false, code: precheck.code, error: precheck.error, recoveryHint: precheck.recoveryHint }
|
|
48992
|
+
};
|
|
48993
|
+
}
|
|
48994
|
+
}
|
|
48995
|
+
}
|
|
48880
48996
|
let sessionCleanup;
|
|
48881
48997
|
if (node && sessionCleanupMode !== "preserve") {
|
|
48882
48998
|
sessionCleanup = await ctx.cleanupMeshSessions({
|
|
@@ -48987,6 +49103,8 @@ var meshCrudHandlers = {
|
|
|
48987
49103
|
...typeof args === "object" && args !== null ? args : {},
|
|
48988
49104
|
_meshDirectDispatch: true
|
|
48989
49105
|
});
|
|
49106
|
+
const forwardedNodeId = forwarded?.node?.id;
|
|
49107
|
+
if (typeof forwardedNodeId === "string" && forwardedNodeId) noteRecentlyClonedNode(forwardedNodeId);
|
|
48990
49108
|
return forwarded ?? { success: false, error: "no response from remote node" };
|
|
48991
49109
|
}
|
|
48992
49110
|
const repoRoot = sourceNode.repoRoot || sourceNode.workspace;
|
|
@@ -49036,6 +49154,7 @@ var meshCrudHandlers = {
|
|
|
49036
49154
|
if (inlineForReconcile) ctx.updateInlineMeshNode(meshId, inlineForReconcile, node);
|
|
49037
49155
|
ctx.invalidateAggregateMeshStatus(meshId);
|
|
49038
49156
|
}
|
|
49157
|
+
if (typeof node?.id === "string" && node.id) noteRecentlyClonedNode(node.id);
|
|
49039
49158
|
const persistWorktreeSetupState = async (bootstrapState2) => {
|
|
49040
49159
|
node.worktreeBootstrap = bootstrapState2;
|
|
49041
49160
|
if (meshRecord.inline) {
|
|
@@ -54061,6 +54180,7 @@ var DaemonCommandRouter = class {
|
|
|
54061
54180
|
normalizeMeshSessionCleanupMode: this.normalizeMeshSessionCleanupMode.bind(this),
|
|
54062
54181
|
cleanupMeshSessions: this.cleanupMeshSessions.bind(this),
|
|
54063
54182
|
cleanupLocalWorktreeNode: this.cleanupLocalWorktreeNode.bind(this),
|
|
54183
|
+
precheckLocalWorktreeRemovable: this.precheckLocalWorktreeRemovable.bind(this),
|
|
54064
54184
|
startMeshRefineJob: this.startMeshRefineJob.bind(this),
|
|
54065
54185
|
batchRefineMeshNodes: this.batchRefineMeshNodes.bind(this),
|
|
54066
54186
|
startMeshRefineBatchJob: this.startMeshRefineBatchJob.bind(this),
|
|
@@ -54277,6 +54397,111 @@ var DaemonCommandRouter = class {
|
|
|
54277
54397
|
}
|
|
54278
54398
|
return fs32.existsSync(dir) ? { removed: false, residue: true, error: String(lastErr?.message || lastErr || "unknown rm error") } : { removed: true, residue: false };
|
|
54279
54399
|
}
|
|
54400
|
+
/**
|
|
54401
|
+
* Non-destructive precheck mirroring every REFUSAL condition in
|
|
54402
|
+
* {@link cleanupLocalWorktreeNode} — missing workspace / source-repo / branch
|
|
54403
|
+
* metadata, unexpected (non-managed) path, branch mismatch — PLUS the
|
|
54404
|
+
* dirty-worktree guard that `removeWorktree(requireClean)` enforces
|
|
54405
|
+
* (`git status --porcelain`). It performs ZERO destructive actions: no
|
|
54406
|
+
* `git worktree remove`, no `git worktree prune`, no directory deletion.
|
|
54407
|
+
*
|
|
54408
|
+
* remove_mesh_node calls this BEFORE any session cleanup so that a refusal
|
|
54409
|
+
* (the common one being a dirty worktree) does not first stop/delete the
|
|
54410
|
+
* delegated session and orphan it — the original ordering bug. Success/skip
|
|
54411
|
+
* cases that the real cleanup handles idempotently (worktree path already
|
|
54412
|
+
* gone, git-de-registered residue) are NOT refusals and return `{ ok: true }`.
|
|
54413
|
+
*
|
|
54414
|
+
* `force:true` skips the dirty guard, preserving `removeWorktree`'s
|
|
54415
|
+
* `requireClean: !force` semantics. This is a read-only superset check; the
|
|
54416
|
+
* authoritative `requireClean` guard inside `removeWorktree` is intentionally
|
|
54417
|
+
* kept as a second line of defense against a precheck→execute race.
|
|
54418
|
+
*/
|
|
54419
|
+
async precheckLocalWorktreeRemovable(args) {
|
|
54420
|
+
const sessionPreservedNote = " The delegated session was left running (not stopped) \u2014 resolve the issue and retry mesh_remove_node.";
|
|
54421
|
+
const workspace = typeof args.node?.workspace === "string" ? args.node.workspace.trim() : "";
|
|
54422
|
+
if (!workspace) {
|
|
54423
|
+
return {
|
|
54424
|
+
ok: false,
|
|
54425
|
+
code: "mesh_worktree_cleanup_missing_workspace",
|
|
54426
|
+
error: `Worktree node '${args.nodeId}' is missing workspace metadata`,
|
|
54427
|
+
recoveryHint: "Inspect the mesh node record before removing it, or remove stale metadata manually only after confirming no managed worktree remains." + sessionPreservedNote
|
|
54428
|
+
};
|
|
54429
|
+
}
|
|
54430
|
+
if (!fs32.existsSync(workspace)) return { ok: true };
|
|
54431
|
+
const sourceNode = args.node?.clonedFromNodeId ? args.mesh?.nodes?.find((n) => meshNodeIdMatches(n, args.node.clonedFromNodeId)) : args.mesh?.nodes?.find((n) => !n.isLocalWorktree);
|
|
54432
|
+
const repoRoot = typeof sourceNode?.repoRoot === "string" && sourceNode.repoRoot.trim() ? sourceNode.repoRoot.trim() : typeof sourceNode?.workspace === "string" && sourceNode.workspace.trim() ? sourceNode.workspace.trim() : "";
|
|
54433
|
+
if (!repoRoot || !fs32.existsSync(repoRoot)) {
|
|
54434
|
+
return {
|
|
54435
|
+
ok: false,
|
|
54436
|
+
code: "mesh_worktree_cleanup_missing_source_repo",
|
|
54437
|
+
error: `Refusing to remove worktree '${workspace}' because the source repo root is unavailable`,
|
|
54438
|
+
recoveryHint: "Run mesh_remove_node from the machine that owns the source repo, or verify the source node metadata before retrying." + sessionPreservedNote
|
|
54439
|
+
};
|
|
54440
|
+
}
|
|
54441
|
+
if (typeof args.node?.worktreeBranch !== "string" || !args.node.worktreeBranch.trim()) {
|
|
54442
|
+
return {
|
|
54443
|
+
ok: false,
|
|
54444
|
+
code: "mesh_worktree_cleanup_missing_branch",
|
|
54445
|
+
error: `Refusing to remove worktree '${workspace}' because worktreeBranch metadata is missing`,
|
|
54446
|
+
recoveryHint: "Confirm this is an ADHDev-managed worktree before removing it manually; managed worktree nodes include worktreeBranch metadata." + sessionPreservedNote
|
|
54447
|
+
};
|
|
54448
|
+
}
|
|
54449
|
+
const { resolveWorktreePath: resolveWorktreePath2, listWorktrees: listWorktrees2 } = await Promise.resolve().then(() => (init_git_worktree(), git_worktree_exports));
|
|
54450
|
+
const normalizePath = (value) => {
|
|
54451
|
+
const resolved = pathResolve4(value);
|
|
54452
|
+
try {
|
|
54453
|
+
return fs32.realpathSync(resolved);
|
|
54454
|
+
} catch {
|
|
54455
|
+
return resolved;
|
|
54456
|
+
}
|
|
54457
|
+
};
|
|
54458
|
+
const expectedPath = normalizePath(resolveWorktreePath2(repoRoot, String(args.mesh?.name || args.mesh?.id || "mesh"), args.node.worktreeBranch));
|
|
54459
|
+
const actualPath = normalizePath(workspace);
|
|
54460
|
+
if (actualPath !== expectedPath) {
|
|
54461
|
+
return {
|
|
54462
|
+
ok: false,
|
|
54463
|
+
code: "mesh_worktree_cleanup_unexpected_path",
|
|
54464
|
+
error: `Refusing to remove worktree '${workspace}' because it is not at the expected managed path '${expectedPath}'`,
|
|
54465
|
+
recoveryHint: "Use git worktree list/status to inspect the path. Retry only after confirming the mesh node metadata points to an ADHDev-managed worktree." + sessionPreservedNote
|
|
54466
|
+
};
|
|
54467
|
+
}
|
|
54468
|
+
const entries = await listWorktrees2(repoRoot);
|
|
54469
|
+
const managedEntry = entries.find((entry) => normalizePath(entry.path) === actualPath);
|
|
54470
|
+
if (!managedEntry) return { ok: true };
|
|
54471
|
+
if (managedEntry.branch && managedEntry.branch !== args.node.worktreeBranch) {
|
|
54472
|
+
return {
|
|
54473
|
+
ok: false,
|
|
54474
|
+
code: "mesh_worktree_cleanup_branch_mismatch",
|
|
54475
|
+
error: `Refusing to remove '${workspace}' because git reports branch '${managedEntry.branch}', expected '${args.node.worktreeBranch}'`,
|
|
54476
|
+
recoveryHint: "Inspect the worktree branch and mesh metadata before retrying cleanup." + sessionPreservedNote
|
|
54477
|
+
};
|
|
54478
|
+
}
|
|
54479
|
+
if (args.force !== true) {
|
|
54480
|
+
const { execFile: execFile5 } = await import("child_process");
|
|
54481
|
+
const { promisify: promisify8 } = await import("util");
|
|
54482
|
+
const execFileAsync4 = promisify8(execFile5);
|
|
54483
|
+
try {
|
|
54484
|
+
const { stdout } = await execFileAsync4("git", ["status", "--porcelain"], {
|
|
54485
|
+
cwd: workspace,
|
|
54486
|
+
encoding: "utf8",
|
|
54487
|
+
timeout: 3e4,
|
|
54488
|
+
maxBuffer: 4 * 1024 * 1024,
|
|
54489
|
+
windowsHide: true
|
|
54490
|
+
});
|
|
54491
|
+
if (stdout.trim()) {
|
|
54492
|
+
return {
|
|
54493
|
+
ok: false,
|
|
54494
|
+
code: "mesh_worktree_cleanup_dirty",
|
|
54495
|
+
error: `Refusing to remove dirty worktree: ${workspace}`,
|
|
54496
|
+
recoveryHint: "Commit, stash, or intentionally discard the worktree changes before retrying mesh_remove_node. The mesh registry entry is preserved until cleanup is safe." + sessionPreservedNote
|
|
54497
|
+
};
|
|
54498
|
+
}
|
|
54499
|
+
} catch {
|
|
54500
|
+
return { ok: true };
|
|
54501
|
+
}
|
|
54502
|
+
}
|
|
54503
|
+
return { ok: true };
|
|
54504
|
+
}
|
|
54280
54505
|
async cleanupLocalWorktreeNode(args) {
|
|
54281
54506
|
const workspace = typeof args.node?.workspace === "string" ? args.node.workspace.trim() : "";
|
|
54282
54507
|
if (!workspace) {
|