@adhdev/daemon-core 0.9.82-rc.385 → 0.9.82-rc.387
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-adapter-types.d.ts +12 -0
- package/dist/cli-adapters/provider-cli-shared.d.ts +11 -0
- package/dist/git/git-worktree.d.ts +22 -0
- package/dist/index.js +193 -8
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +193 -8
- package/dist/index.mjs.map +1 -1
- package/dist/providers/cli-provider-instance.d.ts +8 -0
- package/dist/providers/spec/fsm-driver.d.ts +10 -0
- package/package.json +2 -2
- package/src/cli-adapter-types.ts +12 -0
- package/src/cli-adapters/provider-cli-shared.ts +11 -0
- package/src/commands/med-family/mesh-crud.ts +11 -0
- package/src/commands/router.ts +49 -2
- package/src/git/git-worktree.ts +68 -0
- package/src/mesh/mesh-work-queue.ts +110 -2
- package/src/providers/cli-provider-instance.ts +43 -1
- package/src/providers/spec/cli-adapter.ts +6 -1
- package/src/providers/spec/fsm-driver.ts +12 -0
|
@@ -26,6 +26,18 @@ export interface CliAdapterStatus {
|
|
|
26
26
|
providerSessionId?: string;
|
|
27
27
|
errorMessage?: string;
|
|
28
28
|
errorReason?: string;
|
|
29
|
+
/**
|
|
30
|
+
* FSM-spec adapters only: true once the driver has observed its first
|
|
31
|
+
* non-initial idle state (the prompt is genuinely drawn — see
|
|
32
|
+
* FsmDriver.maybeMarkReady / readySeenOnce). Used by CliProviderInstance to
|
|
33
|
+
* re-arm the queue-claim `agent:ready` event on the first genuine ready,
|
|
34
|
+
* independent of the boot-time starting→idle one-shot. That one-shot is
|
|
35
|
+
* consumed too early for providers whose INITIAL FSM state already reports
|
|
36
|
+
* status 'idle' (e.g. antigravity-cli), so without this re-arm the worker
|
|
37
|
+
* never claims its queued task and the coordinator relaunch-loops. Absent
|
|
38
|
+
* (undefined) for non-FSM adapters — they keep the boot one-shot behavior.
|
|
39
|
+
*/
|
|
40
|
+
fsmReadySeen?: boolean;
|
|
29
41
|
}
|
|
30
42
|
export interface AcpAdapterHandle {
|
|
31
43
|
onEvent(event: string, data?: unknown): void;
|
|
@@ -58,6 +58,17 @@ export interface CliSessionStatus {
|
|
|
58
58
|
errorMessage?: string;
|
|
59
59
|
errorReason?: string;
|
|
60
60
|
providerSessionId?: string;
|
|
61
|
+
/**
|
|
62
|
+
* Spec/FSM adapters only (SpecCliAdapter): true once the driver has observed
|
|
63
|
+
* its first non-initial idle state — the prompt is genuinely drawn. The
|
|
64
|
+
* legacy ProviderCliAdapter never sets it (undefined). CliProviderInstance
|
|
65
|
+
* uses it to re-arm the queue-claim agent:ready on the first genuine ready,
|
|
66
|
+
* independent of the boot-time starting→idle one-shot (which is consumed too
|
|
67
|
+
* early for specs whose initial state already reports status 'idle', e.g.
|
|
68
|
+
* antigravity-cli — without the re-arm the worker never claims its queued
|
|
69
|
+
* task and the coordinator relaunch-loops).
|
|
70
|
+
*/
|
|
71
|
+
fsmReadySeen?: boolean;
|
|
61
72
|
/**
|
|
62
73
|
* Timestamp (ms) of the most recent raw PTY output chunk. Advances on every
|
|
63
74
|
* byte the process emits, including tool/build output that produces no
|
|
@@ -75,3 +75,25 @@ export declare function listWorktrees(repoRoot: string): Promise<WorktreeEntry[]
|
|
|
75
75
|
* Parse `git worktree list --porcelain` output into structured entries.
|
|
76
76
|
*/
|
|
77
77
|
export declare function parseWorktreeListOutput(output: string): WorktreeEntry[];
|
|
78
|
+
export interface BranchRefDeleteResult {
|
|
79
|
+
/** True if the branch ref no longer exists after this call. */
|
|
80
|
+
deleted: boolean;
|
|
81
|
+
/** Why it was (not) deleted, for surfacing in the cleanup result. */
|
|
82
|
+
reason: string;
|
|
83
|
+
/** True when a forced delete (`-D`) was needed (e.g. squash/patch-equivalent merge). */
|
|
84
|
+
forced?: boolean;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Delete a local branch ref after its worktree was removed.
|
|
88
|
+
*
|
|
89
|
+
* SAFETY: this is only meant to be called once the caller has independently
|
|
90
|
+
* verified that the branch is fully merged / its content is contained in the
|
|
91
|
+
* default ref (no work loss). It first tries the safe `git branch -d`, which
|
|
92
|
+
* refuses to delete a branch git itself does not consider merged. If
|
|
93
|
+
* `safeDeleteOnly` is false (the caller proved containment by patch-equivalence,
|
|
94
|
+
* which `-d` cannot see), it falls back to `git branch -D`. When the branch is
|
|
95
|
+
* already gone, this reports `deleted: true` idempotently.
|
|
96
|
+
*/
|
|
97
|
+
export declare function deleteBranchRef(repoRoot: string, branch: string, opts?: {
|
|
98
|
+
safeDeleteOnly?: boolean;
|
|
99
|
+
}): Promise<BranchRefDeleteResult>;
|
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 ? "057d5def5d55af124dfe910ee9accdf244576c14" : void 0) ?? "unknown";
|
|
387
|
+
const commitShort = readInjected(true ? "057d5def" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
|
|
388
|
+
const version = readInjected(true ? "0.9.82-rc.387" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
|
|
389
|
+
const builtAt = readInjected(true ? "2026-06-26T02:21:27.575Z" : void 0);
|
|
390
390
|
cached = builtAt ? { commit, commitShort, version, builtAt } : { commit, commitShort, version };
|
|
391
391
|
return cached;
|
|
392
392
|
}
|
|
@@ -1413,6 +1413,7 @@ var init_git_diff = __esm({
|
|
|
1413
1413
|
var git_worktree_exports = {};
|
|
1414
1414
|
__export(git_worktree_exports, {
|
|
1415
1415
|
createWorktree: () => createWorktree,
|
|
1416
|
+
deleteBranchRef: () => deleteBranchRef,
|
|
1416
1417
|
listWorktrees: () => listWorktrees,
|
|
1417
1418
|
parseWorktreeListOutput: () => parseWorktreeListOutput,
|
|
1418
1419
|
removeWorktree: () => removeWorktree,
|
|
@@ -1555,6 +1556,53 @@ function parseWorktreeListOutput(output) {
|
|
|
1555
1556
|
}
|
|
1556
1557
|
return entries;
|
|
1557
1558
|
}
|
|
1559
|
+
async function deleteBranchRef(repoRoot, branch, opts = {}) {
|
|
1560
|
+
const name = (branch || "").trim();
|
|
1561
|
+
if (!name) return { deleted: false, reason: "empty_branch_name" };
|
|
1562
|
+
try {
|
|
1563
|
+
await execFileAsync2("git", ["rev-parse", "--verify", "--quiet", `refs/heads/${name}`], {
|
|
1564
|
+
cwd: repoRoot,
|
|
1565
|
+
encoding: "utf8",
|
|
1566
|
+
timeout: GIT_TIMEOUT_MS,
|
|
1567
|
+
maxBuffer: GIT_MAX_BUFFER,
|
|
1568
|
+
windowsHide: true
|
|
1569
|
+
});
|
|
1570
|
+
} catch {
|
|
1571
|
+
return { deleted: true, reason: "branch_ref_absent" };
|
|
1572
|
+
}
|
|
1573
|
+
try {
|
|
1574
|
+
await execFileAsync2("git", ["branch", "-d", name], {
|
|
1575
|
+
cwd: repoRoot,
|
|
1576
|
+
encoding: "utf8",
|
|
1577
|
+
timeout: GIT_TIMEOUT_MS,
|
|
1578
|
+
maxBuffer: GIT_MAX_BUFFER,
|
|
1579
|
+
windowsHide: true
|
|
1580
|
+
});
|
|
1581
|
+
return { deleted: true, reason: "safe_deleted_merged_branch" };
|
|
1582
|
+
} catch (error) {
|
|
1583
|
+
const stderr = typeof error?.stderr === "string" ? error.stderr : "";
|
|
1584
|
+
const notMerged = /not fully merged/i.test(stderr) || /not fully merged/i.test(String(error?.message || ""));
|
|
1585
|
+
if (!notMerged) {
|
|
1586
|
+
return { deleted: false, reason: `branch_delete_failed: ${stderr.trim() || error?.message || "unknown error"}` };
|
|
1587
|
+
}
|
|
1588
|
+
if (opts.safeDeleteOnly) {
|
|
1589
|
+
return { deleted: false, reason: "branch_not_merged_per_git_safe_delete_only" };
|
|
1590
|
+
}
|
|
1591
|
+
try {
|
|
1592
|
+
await execFileAsync2("git", ["branch", "-D", name], {
|
|
1593
|
+
cwd: repoRoot,
|
|
1594
|
+
encoding: "utf8",
|
|
1595
|
+
timeout: GIT_TIMEOUT_MS,
|
|
1596
|
+
maxBuffer: GIT_MAX_BUFFER,
|
|
1597
|
+
windowsHide: true
|
|
1598
|
+
});
|
|
1599
|
+
return { deleted: true, reason: "force_deleted_patch_equivalent_branch", forced: true };
|
|
1600
|
+
} catch (forceError) {
|
|
1601
|
+
const fErr = typeof forceError?.stderr === "string" ? forceError.stderr : forceError?.message;
|
|
1602
|
+
return { deleted: false, reason: `branch_force_delete_failed: ${String(fErr || "unknown error").trim()}` };
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1558
1606
|
async function pruneWorktrees(repoRoot) {
|
|
1559
1607
|
try {
|
|
1560
1608
|
await execFileAsync2("git", ["worktree", "prune"], {
|
|
@@ -4421,6 +4469,12 @@ function isMutationKeywordInCommandContext(text, matchStart, matchEnd) {
|
|
|
4421
4469
|
const linePrefix = text.slice(lineStart, matchStart);
|
|
4422
4470
|
if (/^\s*[$>]\s/.test(text.slice(lineStart))) return true;
|
|
4423
4471
|
if (/^\s*$/.test(linePrefix)) return true;
|
|
4472
|
+
let tokStart = matchStart;
|
|
4473
|
+
while (tokStart > lineStart && !/\s/.test(text[tokStart - 1])) tokStart--;
|
|
4474
|
+
const beforeToken = text.slice(lineStart, tokStart);
|
|
4475
|
+
const tokenLead = text.slice(tokStart, matchStart);
|
|
4476
|
+
const atCmdPos = /^\s*$/.test(beforeToken) || /(?:&&|\|\||\||;)\s*$/.test(beforeToken);
|
|
4477
|
+
if (atCmdPos && /^(?:\.\/|\.\.\/|~\/|\/)/.test(tokenLead)) return true;
|
|
4424
4478
|
if (/(?:&&|\|\||\||;)\s*$/.test(linePrefix)) return true;
|
|
4425
4479
|
if (/(?:^|[\s,])(?:then|run|first)\s+$/i.test(linePrefix)) return true;
|
|
4426
4480
|
if (/,\s*$/.test(linePrefix)) return true;
|
|
@@ -4447,9 +4501,65 @@ function isInsideBackticksOrFence(text, matchStart, matchEnd) {
|
|
|
4447
4501
|
}
|
|
4448
4502
|
return inlineCount % 2 === 1;
|
|
4449
4503
|
}
|
|
4504
|
+
function isInsidePathSegment(text, matchStart, matchEnd) {
|
|
4505
|
+
const prev = matchStart > 0 ? text[matchStart - 1] : "";
|
|
4506
|
+
const next = matchEnd < text.length ? text[matchEnd] : "";
|
|
4507
|
+
const isSep = (c) => c === "/" || c === "\\";
|
|
4508
|
+
const segChar = (c) => /[A-Za-z0-9._~-]/.test(c);
|
|
4509
|
+
const inPath = isSep(prev) && (next === "" || isSep(next) || segChar(next) || /\s/.test(next)) || isSep(next) && (prev === "" || isSep(prev) || segChar(prev) || /\s/.test(prev));
|
|
4510
|
+
if (!inPath) return false;
|
|
4511
|
+
const lineStart = text.lastIndexOf("\n", matchStart - 1) + 1;
|
|
4512
|
+
let tokStart = matchStart;
|
|
4513
|
+
while (tokStart > lineStart && !/\s/.test(text[tokStart - 1])) tokStart--;
|
|
4514
|
+
const linePrefixBeforeToken = text.slice(lineStart, tokStart);
|
|
4515
|
+
const tokenPrefix = text.slice(tokStart, matchStart);
|
|
4516
|
+
const atLineStart = /^\s*$/.test(linePrefixBeforeToken);
|
|
4517
|
+
const afterConnective = /(?:&&|\|\||\||;)\s*$/.test(linePrefixBeforeToken);
|
|
4518
|
+
const isRunPrefixPath = /^(?:\.\/|\.\.\/|~\/|\/)/.test(tokenPrefix);
|
|
4519
|
+
if ((atLineStart || afterConnective) && isRunPrefixPath) return false;
|
|
4520
|
+
return true;
|
|
4521
|
+
}
|
|
4522
|
+
function isInsideQuotedSpan(text, matchStart, matchEnd) {
|
|
4523
|
+
const pairs = [
|
|
4524
|
+
["\u201C", "\u201D"],
|
|
4525
|
+
["\u2018", "\u2019"],
|
|
4526
|
+
["\u300C", "\u300D"],
|
|
4527
|
+
["\u300E", "\u300F"],
|
|
4528
|
+
["\u300A", "\u300B"]
|
|
4529
|
+
];
|
|
4530
|
+
for (const [open, close] of pairs) {
|
|
4531
|
+
const openIdx = text.lastIndexOf(open, matchStart - 1);
|
|
4532
|
+
if (openIdx < 0) continue;
|
|
4533
|
+
if (text.indexOf(close, openIdx + open.length) >= matchEnd) return true;
|
|
4534
|
+
}
|
|
4535
|
+
const lineStart = text.lastIndexOf("\n", matchStart - 1) + 1;
|
|
4536
|
+
const lineEnd = (() => {
|
|
4537
|
+
const i = text.indexOf("\n", matchEnd);
|
|
4538
|
+
return i < 0 ? text.length : i;
|
|
4539
|
+
})();
|
|
4540
|
+
for (const q of ['"', "'"]) {
|
|
4541
|
+
let count = 0;
|
|
4542
|
+
for (let i = lineStart; i < matchStart; i++) if (text[i] === q) count++;
|
|
4543
|
+
if (count % 2 === 1 && text.indexOf(q, matchEnd) >= 0 && text.indexOf(q, matchEnd) < lineEnd) {
|
|
4544
|
+
if (q === '"') return true;
|
|
4545
|
+
let openPos = -1, c = 0;
|
|
4546
|
+
for (let i = lineStart; i < matchStart; i++) {
|
|
4547
|
+
if (text[i] === q) {
|
|
4548
|
+
c++;
|
|
4549
|
+
if (c % 2 === 1) openPos = i;
|
|
4550
|
+
}
|
|
4551
|
+
}
|
|
4552
|
+
const beforeOpen = openPos > lineStart ? text[openPos - 1] : " ";
|
|
4553
|
+
if (/[\s(\[{>]/.test(beforeOpen) || openPos === lineStart) return true;
|
|
4554
|
+
}
|
|
4555
|
+
}
|
|
4556
|
+
return false;
|
|
4557
|
+
}
|
|
4450
4558
|
function isRealMutationMatch(text, matchStart, matchEnd) {
|
|
4451
4559
|
if (hasNegationBefore(text, matchStart)) return false;
|
|
4452
4560
|
if (hasTrailingNegation(text, matchEnd)) return false;
|
|
4561
|
+
if (isInsidePathSegment(text, matchStart, matchEnd)) return false;
|
|
4562
|
+
if (isInsideQuotedSpan(text, matchStart, matchEnd)) return false;
|
|
4453
4563
|
return isMutationKeywordInCommandContext(text, matchStart, matchEnd);
|
|
4454
4564
|
}
|
|
4455
4565
|
function patternHasRealMutation(pattern, text) {
|
|
@@ -36214,6 +36324,17 @@ var FsmDriver = class {
|
|
|
36214
36324
|
hasIdleHoldPending() {
|
|
36215
36325
|
return (this.lastFsmEval?.transitions ?? []).some((t) => !t.holdSatisfied && t.condResult);
|
|
36216
36326
|
}
|
|
36327
|
+
/**
|
|
36328
|
+
* True once the machine has reached its first non-initial idle state (the
|
|
36329
|
+
* prompt is genuinely drawn — see maybeMarkReady). The cli-adapter surfaces
|
|
36330
|
+
* this on its idle status so CliProviderInstance can re-arm the queue-claim
|
|
36331
|
+
* agent:ready on the first genuine ready, independent of the boot-time
|
|
36332
|
+
* starting→idle one-shot (which is consumed too early for specs whose
|
|
36333
|
+
* initial state already reports idle).
|
|
36334
|
+
*/
|
|
36335
|
+
hasSeenReady() {
|
|
36336
|
+
return this.readySeenOnce;
|
|
36337
|
+
}
|
|
36217
36338
|
getCompletionIdleDebounceState() {
|
|
36218
36339
|
const out = outgoingTransitions(this.spec, this.currentStateId);
|
|
36219
36340
|
const toReady = this.lastFsmEval?.transitions.find((t, i) => {
|
|
@@ -37880,7 +38001,7 @@ var SpecCliAdapter = class _SpecCliAdapter {
|
|
|
37880
38001
|
if (state.status === "generating") {
|
|
37881
38002
|
return { status: "generating", messages: [], activeModal: null, activeInteractivePrompt: this.activeInteractivePrompt, ...sessionFields };
|
|
37882
38003
|
}
|
|
37883
|
-
return { status: "idle", messages: [], activeModal: null, activeInteractivePrompt: this.activeInteractivePrompt, ...sessionFields };
|
|
38004
|
+
return { status: "idle", messages: [], activeModal: null, activeInteractivePrompt: this.activeInteractivePrompt, fsmReadySeen: this.driver.hasSeenReady?.() ?? false, ...sessionFields };
|
|
37884
38005
|
}
|
|
37885
38006
|
maybeRefreshNativeHistory() {
|
|
37886
38007
|
}
|
|
@@ -38925,6 +39046,14 @@ var CliProviderInstance = class _CliProviderInstance {
|
|
|
38925
39046
|
context = null;
|
|
38926
39047
|
events = [];
|
|
38927
39048
|
lastStatus = "starting";
|
|
39049
|
+
// Idempotency guard for the queue-claim agent:ready event. agent:ready is the
|
|
39050
|
+
// sole signal the mesh coordinator's tryAssignQueueTask waits on to hand a
|
|
39051
|
+
// queued task to this worker. It is emitted in two places: the boot-time
|
|
39052
|
+
// starting→idle one-shot, and the readySeen re-arm below. This flag makes the
|
|
39053
|
+
// event fire AT MOST ONCE per session so a worker is never claimed twice and a
|
|
39054
|
+
// queued task is never double-dispatched/double-injected. Whichever path fires
|
|
39055
|
+
// first sets it; the other becomes a no-op.
|
|
39056
|
+
agentReadyEmitted = false;
|
|
38928
39057
|
generatingStartedAt = 0;
|
|
38929
39058
|
settings = {};
|
|
38930
39059
|
monitor;
|
|
@@ -40005,6 +40134,17 @@ var CliProviderInstance = class _CliProviderInstance {
|
|
|
40005
40134
|
} catch {
|
|
40006
40135
|
}
|
|
40007
40136
|
}
|
|
40137
|
+
/**
|
|
40138
|
+
* Emit the queue-claim agent:ready event at most once per session. Both the
|
|
40139
|
+
* boot-time starting→idle one-shot and the fsmReadySeen re-arm call this; the
|
|
40140
|
+
* agentReadyEmitted guard ensures the second caller is a no-op so a worker is
|
|
40141
|
+
* never claimed twice and a queued task is never double-dispatched.
|
|
40142
|
+
*/
|
|
40143
|
+
emitAgentReadyOnce(chatTitle, now) {
|
|
40144
|
+
if (this.agentReadyEmitted) return;
|
|
40145
|
+
this.agentReadyEmitted = true;
|
|
40146
|
+
this.pushEvent({ event: "agent:ready", chatTitle, timestamp: now });
|
|
40147
|
+
}
|
|
40008
40148
|
detectStatusTransition() {
|
|
40009
40149
|
const now = Date.now();
|
|
40010
40150
|
const adapterStatus = this.adapter.getStatus({ allowParse: false });
|
|
@@ -40157,7 +40297,7 @@ var CliProviderInstance = class _CliProviderInstance {
|
|
|
40157
40297
|
this.scheduleCompletedDebounceFlush(flushDelay);
|
|
40158
40298
|
}
|
|
40159
40299
|
} else if (newStatus === "idle" && this.lastStatus === "starting") {
|
|
40160
|
-
this.
|
|
40300
|
+
this.emitAgentReadyOnce(chatTitle, now);
|
|
40161
40301
|
} else if (newStatus === "error") {
|
|
40162
40302
|
if (this.generatingDebounceTimer) {
|
|
40163
40303
|
clearTimeout(this.generatingDebounceTimer);
|
|
@@ -40196,6 +40336,9 @@ var CliProviderInstance = class _CliProviderInstance {
|
|
|
40196
40336
|
}
|
|
40197
40337
|
this.lastStatus = newStatus;
|
|
40198
40338
|
}
|
|
40339
|
+
if (newStatus === "idle" && adapterStatus.fsmReadySeen === true && !this.agentReadyEmitted) {
|
|
40340
|
+
this.emitAgentReadyOnce(chatTitle, now);
|
|
40341
|
+
}
|
|
40199
40342
|
this.applyProviderResponse(parsedStatus, {
|
|
40200
40343
|
phase: newStatus === "idle" && (previousStatus === "generating" || previousStatus === "waiting_approval") ? "turn_completed" : "immediate"
|
|
40201
40344
|
});
|
|
@@ -47564,13 +47707,16 @@ var meshCrudHandlers = {
|
|
|
47564
47707
|
worktreeBranch: typeof node?.worktreeBranch === "string" ? node.worktreeBranch : void 0,
|
|
47565
47708
|
worktreeCleanupFallback: typeof worktreeCleanup?.fallback === "string" ? worktreeCleanup.fallback : void 0,
|
|
47566
47709
|
forced: worktreeCleanup?.forced === true ? true : void 0,
|
|
47567
|
-
forceFallbackReason: typeof worktreeCleanup?.reason === "string" ? worktreeCleanup.reason : void 0
|
|
47710
|
+
forceFallbackReason: typeof worktreeCleanup?.reason === "string" ? worktreeCleanup.reason : void 0,
|
|
47711
|
+
branchRefDeleted: typeof worktreeCleanup?.branchRefDeleted === "boolean" ? worktreeCleanup.branchRefDeleted : void 0,
|
|
47712
|
+
branchRefReason: typeof worktreeCleanup?.branchRefReason === "string" ? worktreeCleanup.branchRefReason : void 0
|
|
47568
47713
|
}
|
|
47569
47714
|
});
|
|
47570
47715
|
} catch {
|
|
47571
47716
|
}
|
|
47572
47717
|
}
|
|
47573
47718
|
const residueWarning = worktreeCleanup?.residue === true && typeof worktreeCleanup?.residueWarning === "string" ? worktreeCleanup.residueWarning : void 0;
|
|
47719
|
+
const branchRefWarning = typeof worktreeCleanup?.branchRefWarning === "string" ? worktreeCleanup.branchRefWarning : void 0;
|
|
47574
47720
|
const skippedLiveSessionIds = Array.isArray(sessionCleanup?.skippedLiveSessionIds) ? sessionCleanup.skippedLiveSessionIds.filter((v) => typeof v === "string") : [];
|
|
47575
47721
|
const orphanedSessionsRemaining = skippedLiveSessionIds.length > 0;
|
|
47576
47722
|
const orphanNextAction = orphanedSessionsRemaining ? `Live session(s) [${skippedLiveSessionIds.join(", ")}] were skipped and still survive this node removal. Run mesh_cleanup_sessions with mode:'stop_and_delete' and sessionIds:[${skippedLiveSessionIds.map((id) => `'${id}'`).join(", ")}] to release them.` : void 0;
|
|
@@ -47578,6 +47724,7 @@ var meshCrudHandlers = {
|
|
|
47578
47724
|
success: true,
|
|
47579
47725
|
removed,
|
|
47580
47726
|
...residueWarning ? { residueWarning } : {},
|
|
47727
|
+
...branchRefWarning ? { branchRefWarning } : {},
|
|
47581
47728
|
...sessionCleanup ? { sessionCleanup } : {},
|
|
47582
47729
|
...worktreeCleanup ? { worktreeCleanup } : {},
|
|
47583
47730
|
...orphanedSessionsRemaining ? { orphanedSessionsRemaining: true, nextAction: orphanNextAction } : {}
|
|
@@ -52847,16 +52994,50 @@ var DaemonCommandRouter = class {
|
|
|
52847
52994
|
recoveryHint: "Inspect the worktree branch and mesh metadata before retrying cleanup."
|
|
52848
52995
|
};
|
|
52849
52996
|
}
|
|
52850
|
-
const
|
|
52997
|
+
const mergeConvergence = await this.getWorktreeForceCleanupConvergence({ repoRoot, workspace, node: args.node });
|
|
52998
|
+
const forceFallbackConvergence = args.force ? { allow: true, status: "force_override", source: "caller_force_flag" } : mergeConvergence;
|
|
52999
|
+
const deleteBranchIfMerged = async () => {
|
|
53000
|
+
const branch = String(args.node.worktreeBranch).trim();
|
|
53001
|
+
const status = mergeConvergence.allow ? mergeConvergence.status || "" : "";
|
|
53002
|
+
const MERGED_STATUSES = /* @__PURE__ */ new Set([
|
|
53003
|
+
"merged_to_main",
|
|
53004
|
+
"merged_pushed",
|
|
53005
|
+
"merged_to_default_ref",
|
|
53006
|
+
"cleanup_candidate"
|
|
53007
|
+
]);
|
|
53008
|
+
const PATCH_EQUIV_STATUS = "patch_equivalent_to_default_ref";
|
|
53009
|
+
if (!branch) {
|
|
53010
|
+
return { branchRefDeleted: false, branchRefReason: "empty_branch_name" };
|
|
53011
|
+
}
|
|
53012
|
+
if (!mergeConvergence.allow || !MERGED_STATUSES.has(status) && status !== PATCH_EQUIV_STATUS) {
|
|
53013
|
+
return {
|
|
53014
|
+
branchRefDeleted: false,
|
|
53015
|
+
branchRefReason: `branch_not_merged_preserved: ${mergeConvergence.error || mergeConvergence.status || "convergence_unverified"}`,
|
|
53016
|
+
branchRefWarning: `Branch ref '${branch}' was preserved (not deleted) because it is not confirmed merged into the default ref \u2014 no work was lost. Merge it (or pass a verified branchConvergence final state) and re-run cleanup, or delete it manually after confirming.`
|
|
53017
|
+
};
|
|
53018
|
+
}
|
|
53019
|
+
const { deleteBranchRef: deleteBranchRef2 } = await Promise.resolve().then(() => (init_git_worktree(), git_worktree_exports));
|
|
53020
|
+
const res = await deleteBranchRef2(repoRoot, branch, { safeDeleteOnly: status !== PATCH_EQUIV_STATUS });
|
|
53021
|
+
return {
|
|
53022
|
+
branchRefDeleted: res.deleted,
|
|
53023
|
+
branchRefReason: res.reason,
|
|
53024
|
+
...res.forced ? { branchRefForced: true } : {},
|
|
53025
|
+
...res.deleted ? {} : {
|
|
53026
|
+
branchRefWarning: `Branch ref '${branch}' could not be deleted (${res.reason}); it was preserved so no work is lost.`
|
|
53027
|
+
}
|
|
53028
|
+
};
|
|
53029
|
+
};
|
|
52851
53030
|
try {
|
|
52852
53031
|
const result = await removeWorktree2(repoRoot, workspace, {
|
|
52853
53032
|
requireClean: !args.force,
|
|
52854
53033
|
allowSubmoduleForceFallback: forceFallbackConvergence.allow
|
|
52855
53034
|
});
|
|
53035
|
+
const branchOutcome = await deleteBranchIfMerged();
|
|
52856
53036
|
return {
|
|
52857
53037
|
success: true,
|
|
52858
53038
|
removedPath: result.removedPath,
|
|
52859
53039
|
repoRoot,
|
|
53040
|
+
...branchOutcome,
|
|
52860
53041
|
...result.fallback ? {
|
|
52861
53042
|
fallback: result.fallback,
|
|
52862
53043
|
forced: result.forced,
|
|
@@ -52889,10 +53070,12 @@ var DaemonCommandRouter = class {
|
|
|
52889
53070
|
maxBuffer: GIT_MAX_BUFFER_CLEANUP,
|
|
52890
53071
|
windowsHide: true
|
|
52891
53072
|
});
|
|
53073
|
+
const branchOutcome = await deleteBranchIfMerged();
|
|
52892
53074
|
return {
|
|
52893
53075
|
success: true,
|
|
52894
53076
|
removedPath: workspace,
|
|
52895
53077
|
repoRoot,
|
|
53078
|
+
...branchOutcome,
|
|
52896
53079
|
fallback: "git_worktree_remove_submodule_deinit",
|
|
52897
53080
|
forced: true,
|
|
52898
53081
|
reason: "working_trees_containing_submodules",
|
|
@@ -52910,10 +53093,12 @@ var DaemonCommandRouter = class {
|
|
|
52910
53093
|
});
|
|
52911
53094
|
} catch {
|
|
52912
53095
|
}
|
|
53096
|
+
const branchOutcome = await deleteBranchIfMerged();
|
|
52913
53097
|
return {
|
|
52914
53098
|
success: true,
|
|
52915
53099
|
removedPath: workspace,
|
|
52916
53100
|
repoRoot,
|
|
53101
|
+
...branchOutcome,
|
|
52917
53102
|
fallback: "fs_rm_worktree_prune",
|
|
52918
53103
|
forced: true,
|
|
52919
53104
|
reason: "working_trees_containing_submodules",
|