@adhdev/daemon-core 0.9.77-rc.41 → 0.9.77-rc.43
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/router.d.ts +1 -0
- package/dist/config/mesh-config.d.ts +1 -0
- package/dist/git/git-worktree.d.ts +6 -2
- package/dist/index.js +235 -39
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +235 -39
- package/dist/index.mjs.map +1 -1
- package/dist/providers/cli-provider-instance.d.ts +6 -0
- package/dist/repo-mesh-types.d.ts +2 -0
- package/package.json +1 -1
- package/src/cli-adapters/provider-cli-adapter.ts +3 -2
- package/src/commands/router.ts +114 -15
- package/src/config/mesh-config.ts +2 -0
- package/src/git/git-worktree.ts +21 -3
- package/src/mesh/coordinator-prompt.ts +5 -2
- package/src/providers/cli-provider-instance.ts +151 -30
- package/src/repo-mesh-types.ts +2 -0
package/dist/index.mjs
CHANGED
|
@@ -96,13 +96,25 @@ async function createWorktree(opts) {
|
|
|
96
96
|
branch
|
|
97
97
|
};
|
|
98
98
|
}
|
|
99
|
-
async function removeWorktree(repoRoot, worktreePath) {
|
|
99
|
+
async function removeWorktree(repoRoot, worktreePath, opts = {}) {
|
|
100
100
|
if (!existsSync(worktreePath)) {
|
|
101
101
|
await pruneWorktrees(repoRoot);
|
|
102
102
|
return { success: true, removedPath: worktreePath };
|
|
103
103
|
}
|
|
104
|
+
if (opts.requireClean) {
|
|
105
|
+
const { stdout } = await execFileAsync2("git", ["status", "--porcelain"], {
|
|
106
|
+
cwd: worktreePath,
|
|
107
|
+
encoding: "utf8",
|
|
108
|
+
timeout: GIT_TIMEOUT_MS,
|
|
109
|
+
maxBuffer: GIT_MAX_BUFFER,
|
|
110
|
+
windowsHide: true
|
|
111
|
+
});
|
|
112
|
+
if (stdout.trim()) {
|
|
113
|
+
throw new Error(`Refusing to remove dirty worktree: ${worktreePath}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
104
116
|
try {
|
|
105
|
-
await execFileAsync2("git", ["worktree", "remove", worktreePath
|
|
117
|
+
await execFileAsync2("git", ["worktree", "remove", worktreePath], {
|
|
106
118
|
cwd: repoRoot,
|
|
107
119
|
encoding: "utf8",
|
|
108
120
|
timeout: GIT_TIMEOUT_MS,
|
|
@@ -544,6 +556,7 @@ function addNode(meshId, opts) {
|
|
|
544
556
|
workspace: opts.workspace.trim(),
|
|
545
557
|
repoRoot: opts.repoRoot,
|
|
546
558
|
daemonId: opts.daemonId,
|
|
559
|
+
machineId: opts.machineId,
|
|
547
560
|
userOverrides: opts.userOverrides || {},
|
|
548
561
|
policy: opts.policy || {},
|
|
549
562
|
isLocalWorktree: opts.isLocalWorktree,
|
|
@@ -680,6 +693,7 @@ function buildRulesSection(coordinatorCliType) {
|
|
|
680
693
|
- **Respect node capabilities.** Don't send build tasks to read-only nodes. Don't push from nodes that aren't allowed to.
|
|
681
694
|
- **Never fabricate tool results.** Always call the actual tool; never pretend you did.
|
|
682
695
|
- **Clean up worktree nodes.** After a worktree task completes and its changes are merged or checkpointed, call \`mesh_remove_node\` to free resources.
|
|
696
|
+
- **Do not strand completed branches.** A checkpointed or clean feature/worktree branch is not done by itself. Merge/refine it to the mesh default branch, or explicitly report one of \`pushed_feature_branch_needs_merge\`, \`blocked_review\`, \`cleanup_candidate\`, or \`not_mergeable\` with the next action.
|
|
683
697
|
- **Name worktree branches meaningfully.** Use descriptive names like \`feat/auth-refactor\` or \`fix/build-123\`.${coordinatorNote}`;
|
|
684
698
|
}
|
|
685
699
|
var TOOLS_SECTION, TOOL_EXPOSURE_PREFLIGHT_SECTION, WORKFLOW_SECTION;
|
|
@@ -701,6 +715,7 @@ var init_coordinator_prompt = __esm({
|
|
|
701
715
|
| \`mesh_checkpoint\` | Create a git checkpoint on a node |
|
|
702
716
|
| \`mesh_approve\` | Approve/reject a pending agent action |
|
|
703
717
|
| \`mesh_clone_node\` | Create a worktree node for isolated parallel branch work |
|
|
718
|
+
| \`mesh_refine_node\` | Validate and merge a completed worktree node back into its base branch |
|
|
704
719
|
| \`mesh_remove_node\` | Remove a node (cleans up worktree if applicable) |`;
|
|
705
720
|
TOOL_EXPOSURE_PREFLIGHT_SECTION = `## Tool Exposure Preflight
|
|
706
721
|
|
|
@@ -717,8 +732,9 @@ Before doing any coordinator work, confirm that the actual callable tool list in
|
|
|
717
732
|
4. **Monitor** \u2014 Prefer event-driven completion/status notifications. Do **not** poll \`mesh_read_chat\` repeatedly. Use \`mesh_view_queue\` to see the status of all pending, assigned, completed, and failed tasks. Do not call \`mesh_read_chat\` again within a few seconds for the same generating session. Use at most one compact \`mesh_read_chat\` check after a completion/approval signal. Handle approvals via \`mesh_approve\`.
|
|
718
733
|
5. **Verify** \u2014 When a task reports completion or git work is visible, call \`mesh_git_status\` to verify changes were made.
|
|
719
734
|
6. **Checkpoint** \u2014 Call \`mesh_checkpoint\` to save the work.
|
|
720
|
-
7. **
|
|
721
|
-
8. **
|
|
735
|
+
7. **Converge branches** \u2014 Before marking any task complete, classify every touched node/branch into exactly one final state: \`merged_to_main\`, \`pushed_feature_branch_needs_merge\`, \`blocked_review\`, \`cleanup_candidate\`, or \`not_mergeable\`. Use \`mesh_status\` branchConvergenceSummary and \`mesh_refine_node\` for clean worktree branches when safe. A task that remains on a non-main branch is not fully complete unless the final report names the follow-up state and next step.
|
|
736
|
+
8. **Clean up** \u2014 Remove worktree nodes via \`mesh_remove_node\` after their work is merged or no longer needed.
|
|
737
|
+
9. **Report** \u2014 Summarize what was done, what changed, any issues, and the branch convergence state.
|
|
722
738
|
|
|
723
739
|
## Failure Recovery
|
|
724
740
|
|
|
@@ -2766,6 +2782,7 @@ var init_provider_cli_adapter = __esm({
|
|
|
2766
2782
|
this.requirePromptEchoBeforeSubmit = resolvedConfig.requirePromptEchoBeforeSubmit;
|
|
2767
2783
|
this.providerResolutionMeta = resolvedConfig.providerResolutionMeta;
|
|
2768
2784
|
this.cliScripts = provider.scripts || {};
|
|
2785
|
+
this.scriptState = typeof this.cliScripts.createState === "function" ? this.cliScripts.createState() ?? null : null;
|
|
2769
2786
|
const scriptNames = listCliScriptNames(this.cliScripts);
|
|
2770
2787
|
if (scriptNames.length > 0) {
|
|
2771
2788
|
LOG.info("CLI", `[${this.cliType}] CLI scripts: [${scriptNames.join(", ")}]`);
|
|
@@ -3035,7 +3052,7 @@ ${lastSnapshot}`;
|
|
|
3035
3052
|
this.cliScripts = scripts;
|
|
3036
3053
|
this.parsedStatusCache = null;
|
|
3037
3054
|
this.parseErrorMessage = null;
|
|
3038
|
-
this.scriptState = typeof scripts.createState === "function" ? scripts.createState() : null;
|
|
3055
|
+
this.scriptState = typeof scripts.createState === "function" ? scripts.createState() ?? null : null;
|
|
3039
3056
|
const scriptNames = listCliScriptNames(scripts);
|
|
3040
3057
|
LOG.info("CLI", `[${this.cliType}] CLI scripts injected: [${scriptNames.join(", ")}]`);
|
|
3041
3058
|
}
|
|
@@ -4078,7 +4095,7 @@ ${lastSnapshot}`;
|
|
|
4078
4095
|
scope: this.currentTurnScope,
|
|
4079
4096
|
runtimeSettings: this.runtimeSettings
|
|
4080
4097
|
});
|
|
4081
|
-
return await Promise.resolve(
|
|
4098
|
+
return await Promise.resolve(this.invokeCliScript(fn, {
|
|
4082
4099
|
...input,
|
|
4083
4100
|
args: args && typeof args === "object" ? { ...args } : {}
|
|
4084
4101
|
}));
|
|
@@ -16149,6 +16166,8 @@ function normalizeProviderSessionId(provider, providerSessionId) {
|
|
|
16149
16166
|
}
|
|
16150
16167
|
|
|
16151
16168
|
// src/providers/cli-provider-instance.ts
|
|
16169
|
+
var COMPLETED_FINALIZATION_RETRY_MS = 1e3;
|
|
16170
|
+
var COMPLETED_FINALIZATION_MAX_WAIT_MS = 3e4;
|
|
16152
16171
|
var IMAGE_MIME_EXTENSIONS = {
|
|
16153
16172
|
"image/png": ".png",
|
|
16154
16173
|
"image/jpeg": ".jpg",
|
|
@@ -16212,6 +16231,13 @@ function cleanupStaleMaterializedImages(dir) {
|
|
|
16212
16231
|
} catch {
|
|
16213
16232
|
}
|
|
16214
16233
|
}
|
|
16234
|
+
function hasNonEmptyCliModalButtons(activeModal) {
|
|
16235
|
+
const buttons = activeModal?.buttons;
|
|
16236
|
+
return Array.isArray(buttons) && buttons.some((button) => String(button || "").trim().length > 0);
|
|
16237
|
+
}
|
|
16238
|
+
function isCliGeneratingLikeStatus(status) {
|
|
16239
|
+
return status === "generating" || status === "streaming" || status === "long_generating" || status === "starting";
|
|
16240
|
+
}
|
|
16215
16241
|
function buildCliStructuredInputPrompt(input, options = {}) {
|
|
16216
16242
|
const promptParts = [];
|
|
16217
16243
|
const imageRefs = [];
|
|
@@ -16496,10 +16522,12 @@ var CliProviderInstance = class {
|
|
|
16496
16522
|
const mergedMessages = this.mergeConversationMessages(parsedMessages);
|
|
16497
16523
|
const canonicalBackedHistory = this.syncCanonicalSavedHistoryIfNeeded();
|
|
16498
16524
|
const dirName = this.workingDir.split("/").filter(Boolean).pop() || "session";
|
|
16525
|
+
const parsedChatStatus = typeof parsedStatus?.status === "string" && parsedStatus.status.trim() ? parsedStatus.status.trim() : void 0;
|
|
16526
|
+
const suppressStaleParsedBusyStatus = this.shouldSuppressStaleParsedBusyStatus(parsedStatus, adapterStatus);
|
|
16499
16527
|
if (parsedMessages.length > 0) {
|
|
16500
16528
|
const shouldSkipReplayPersist = this.suppressIdleHistoryReplay && adapterStatus.status === "idle" && parsedStatus?.status === "idle";
|
|
16501
16529
|
let messagesToSave = parsedMessages;
|
|
16502
|
-
if (
|
|
16530
|
+
if (!suppressStaleParsedBusyStatus && (parsedChatStatus === "generating" || parsedChatStatus === "long_generating")) {
|
|
16503
16531
|
const lastIdx = messagesToSave.length - 1;
|
|
16504
16532
|
if (lastIdx >= 0 && messagesToSave[lastIdx]?.role === "assistant") {
|
|
16505
16533
|
messagesToSave = messagesToSave.slice(0, lastIdx);
|
|
@@ -16533,6 +16561,7 @@ var CliProviderInstance = class {
|
|
|
16533
16561
|
summaryMetadata: this.summaryMetadata,
|
|
16534
16562
|
controlValues: this.controlValues
|
|
16535
16563
|
});
|
|
16564
|
+
const activeChatStatus = parseErrorMessage ? "error" : autoApproveActive && parsedStatus?.status === "waiting_approval" ? "generating" : adapterStatus.status !== "idle" ? visibleStatus : suppressStaleParsedBusyStatus ? visibleStatus : parsedChatStatus || visibleStatus;
|
|
16536
16565
|
return {
|
|
16537
16566
|
type: this.type,
|
|
16538
16567
|
name: this.provider.name,
|
|
@@ -16542,7 +16571,7 @@ var CliProviderInstance = class {
|
|
|
16542
16571
|
activeChat: {
|
|
16543
16572
|
id: `${this.type}_${this.workingDir}`,
|
|
16544
16573
|
title: parsedStatus?.title || dirName,
|
|
16545
|
-
status:
|
|
16574
|
+
status: activeChatStatus,
|
|
16546
16575
|
messages: mergedMessages,
|
|
16547
16576
|
activeModal: autoApproveActive ? null : parsedStatus?.activeModal ?? adapterStatus.activeModal,
|
|
16548
16577
|
inputContent: ""
|
|
@@ -16672,6 +16701,102 @@ var CliProviderInstance = class {
|
|
|
16672
16701
|
}
|
|
16673
16702
|
this.applyProviderResponse(parsed.payload, { phase: "immediate" });
|
|
16674
16703
|
}
|
|
16704
|
+
completionHasFinalAssistantMessage(messages) {
|
|
16705
|
+
const visibleMessages = (Array.isArray(messages) ? messages : []).filter((message) => isUserFacingChatMessage(message));
|
|
16706
|
+
const lastVisible = visibleMessages[visibleMessages.length - 1];
|
|
16707
|
+
const role = typeof lastVisible?.role === "string" ? lastVisible.role.trim().toLowerCase() : "";
|
|
16708
|
+
const content = lastVisible ? flattenContent(lastVisible.content).trim() : "";
|
|
16709
|
+
return role === "assistant" && !!content;
|
|
16710
|
+
}
|
|
16711
|
+
hasAdapterPendingResponse() {
|
|
16712
|
+
const adapterAny = this.adapter;
|
|
16713
|
+
if (adapterAny?.isWaitingForResponse === true) return true;
|
|
16714
|
+
if (adapterAny?.currentTurnScope) return true;
|
|
16715
|
+
try {
|
|
16716
|
+
if (typeof this.adapter.isProcessing === "function" && this.adapter.isProcessing()) return true;
|
|
16717
|
+
} catch {
|
|
16718
|
+
}
|
|
16719
|
+
try {
|
|
16720
|
+
const partial = typeof this.adapter.getPartialResponse === "function" ? this.adapter.getPartialResponse() : "";
|
|
16721
|
+
if (typeof partial === "string" && partial.trim()) return true;
|
|
16722
|
+
} catch {
|
|
16723
|
+
}
|
|
16724
|
+
return false;
|
|
16725
|
+
}
|
|
16726
|
+
shouldSuppressStaleParsedBusyStatus(parsedStatus, adapterStatus) {
|
|
16727
|
+
const parsedRawStatus = typeof parsedStatus?.status === "string" ? parsedStatus.status.trim() : "";
|
|
16728
|
+
const adapterRawStatus = typeof adapterStatus?.status === "string" ? adapterStatus.status.trim() : "";
|
|
16729
|
+
if (!isCliGeneratingLikeStatus(parsedRawStatus)) return false;
|
|
16730
|
+
if (adapterRawStatus !== "idle") return false;
|
|
16731
|
+
if (hasNonEmptyCliModalButtons(parsedStatus?.activeModal ?? parsedStatus?.modal)) return false;
|
|
16732
|
+
return !this.hasAdapterPendingResponse();
|
|
16733
|
+
}
|
|
16734
|
+
getCompletedFinalizationBlockReason(latestVisibleStatus) {
|
|
16735
|
+
if (latestVisibleStatus !== "idle") return `status:${latestVisibleStatus}`;
|
|
16736
|
+
const adapterAny = this.adapter;
|
|
16737
|
+
if (adapterAny?.isWaitingForResponse === true) return "adapter_waiting_for_response";
|
|
16738
|
+
if (adapterAny?.currentTurnScope) return "adapter_turn_scope_active";
|
|
16739
|
+
const partial = typeof this.adapter.getPartialResponse === "function" ? this.adapter.getPartialResponse() : "";
|
|
16740
|
+
if (typeof partial === "string" && partial.trim()) return "partial_response_pending";
|
|
16741
|
+
let parsed;
|
|
16742
|
+
try {
|
|
16743
|
+
parsed = this.adapter.getScriptParsedStatus();
|
|
16744
|
+
} catch (error) {
|
|
16745
|
+
return `parse_error:${error?.message || String(error)}`;
|
|
16746
|
+
}
|
|
16747
|
+
const parsedStatus = typeof parsed?.status === "string" ? parsed.status : "unknown";
|
|
16748
|
+
if (parsedStatus !== "idle") return `parsed_status:${parsedStatus}`;
|
|
16749
|
+
if (parsed?.activeModal || parsed?.modal) return "parsed_modal_active";
|
|
16750
|
+
if (!this.completionHasFinalAssistantMessage(parsed?.messages)) return "missing_final_assistant";
|
|
16751
|
+
return null;
|
|
16752
|
+
}
|
|
16753
|
+
scheduleCompletedDebounceFlush(delayMs) {
|
|
16754
|
+
if (this.completedDebounceTimer) clearTimeout(this.completedDebounceTimer);
|
|
16755
|
+
this.completedDebounceTimer = setTimeout(() => this.flushCompletedDebounceIfFinalized(), delayMs);
|
|
16756
|
+
}
|
|
16757
|
+
flushCompletedDebounceIfFinalized() {
|
|
16758
|
+
const pending = this.completedDebouncePending;
|
|
16759
|
+
if (!pending) {
|
|
16760
|
+
this.completedDebounceTimer = null;
|
|
16761
|
+
return;
|
|
16762
|
+
}
|
|
16763
|
+
const latestStatus = this.adapter.getStatus({ allowParse: false });
|
|
16764
|
+
const latestAutoApproveActive = latestStatus.status === "waiting_approval" && this.shouldAutoApprove();
|
|
16765
|
+
const latestVisibleStatus = latestAutoApproveActive ? "generating" : latestStatus.status;
|
|
16766
|
+
if (latestVisibleStatus !== "idle") {
|
|
16767
|
+
LOG.info("CLI", `[${this.type}] cancelled pending completed (resumed ${latestVisibleStatus})`);
|
|
16768
|
+
this.completedDebouncePending = null;
|
|
16769
|
+
this.completedDebounceTimer = null;
|
|
16770
|
+
return;
|
|
16771
|
+
}
|
|
16772
|
+
const blockReason = this.getCompletedFinalizationBlockReason(latestVisibleStatus);
|
|
16773
|
+
if (blockReason) {
|
|
16774
|
+
const waitedMs = Date.now() - pending.firstObservedAt;
|
|
16775
|
+
if (waitedMs < COMPLETED_FINALIZATION_MAX_WAIT_MS) {
|
|
16776
|
+
if (pending.loggedBlockReason !== blockReason) {
|
|
16777
|
+
LOG.info("CLI", `[${this.type}] waiting to emit completed until transcript finalizes (${blockReason})`);
|
|
16778
|
+
pending.loggedBlockReason = blockReason;
|
|
16779
|
+
}
|
|
16780
|
+
this.scheduleCompletedDebounceFlush(COMPLETED_FINALIZATION_RETRY_MS);
|
|
16781
|
+
return;
|
|
16782
|
+
}
|
|
16783
|
+
LOG.warn("CLI", `[${this.type}] suppressed completed event after ${waitedMs}ms without finalized assistant turn (${blockReason})`);
|
|
16784
|
+
this.completedDebouncePending = null;
|
|
16785
|
+
this.completedDebounceTimer = null;
|
|
16786
|
+
this.generatingStartedAt = 0;
|
|
16787
|
+
return;
|
|
16788
|
+
}
|
|
16789
|
+
LOG.info("CLI", `[${this.type}] completed in ${pending.duration}s`);
|
|
16790
|
+
this.pushEvent({
|
|
16791
|
+
event: "agent:generating_completed",
|
|
16792
|
+
chatTitle: pending.chatTitle,
|
|
16793
|
+
duration: pending.duration,
|
|
16794
|
+
timestamp: pending.timestamp
|
|
16795
|
+
});
|
|
16796
|
+
this.completedDebouncePending = null;
|
|
16797
|
+
this.completedDebounceTimer = null;
|
|
16798
|
+
this.generatingStartedAt = 0;
|
|
16799
|
+
}
|
|
16675
16800
|
maybeAutoApproveStatus(adapterStatus, now = Date.now()) {
|
|
16676
16801
|
const autoApproveActive = adapterStatus?.status === "waiting_approval" && this.shouldAutoApprove();
|
|
16677
16802
|
if (autoApproveActive && !this.autoApproveBusy) {
|
|
@@ -16769,26 +16894,8 @@ var CliProviderInstance = class {
|
|
|
16769
16894
|
this.generatingDebouncePending = null;
|
|
16770
16895
|
this.generatingStartedAt = 0;
|
|
16771
16896
|
} else {
|
|
16772
|
-
|
|
16773
|
-
this.
|
|
16774
|
-
this.completedDebounceTimer = setTimeout(() => {
|
|
16775
|
-
if (this.completedDebouncePending) {
|
|
16776
|
-
const latestStatus = this.adapter.getStatus({ allowParse: false });
|
|
16777
|
-
const latestAutoApproveActive = latestStatus.status === "waiting_approval" && this.shouldAutoApprove();
|
|
16778
|
-
const latestVisibleStatus = latestAutoApproveActive ? "generating" : latestStatus.status;
|
|
16779
|
-
if (latestVisibleStatus !== "idle") {
|
|
16780
|
-
LOG.info("CLI", `[${this.type}] cancelled pending completed (resumed ${latestVisibleStatus})`);
|
|
16781
|
-
this.completedDebouncePending = null;
|
|
16782
|
-
this.completedDebounceTimer = null;
|
|
16783
|
-
return;
|
|
16784
|
-
}
|
|
16785
|
-
LOG.info("CLI", `[${this.type}] completed in ${this.completedDebouncePending.duration}s`);
|
|
16786
|
-
this.pushEvent({ event: "agent:generating_completed", ...this.completedDebouncePending });
|
|
16787
|
-
this.completedDebouncePending = null;
|
|
16788
|
-
this.generatingStartedAt = 0;
|
|
16789
|
-
}
|
|
16790
|
-
this.completedDebounceTimer = null;
|
|
16791
|
-
}, 3e3);
|
|
16897
|
+
this.completedDebouncePending = { chatTitle, duration, timestamp: now, firstObservedAt: now };
|
|
16898
|
+
this.scheduleCompletedDebounceFlush(3e3);
|
|
16792
16899
|
}
|
|
16793
16900
|
} else if (newStatus === "idle" && this.lastStatus === "starting") {
|
|
16794
16901
|
this.pushEvent({ event: "agent:ready", chatTitle, timestamp: now });
|
|
@@ -22976,6 +23083,89 @@ var DaemonCommandRouter = class {
|
|
|
22976
23083
|
if (record?.meta?.meshNodeId === nodeId) return true;
|
|
22977
23084
|
return false;
|
|
22978
23085
|
}
|
|
23086
|
+
async cleanupLocalWorktreeNode(args) {
|
|
23087
|
+
const workspace = typeof args.node?.workspace === "string" ? args.node.workspace.trim() : "";
|
|
23088
|
+
if (!workspace) {
|
|
23089
|
+
return {
|
|
23090
|
+
success: false,
|
|
23091
|
+
code: "mesh_worktree_cleanup_missing_workspace",
|
|
23092
|
+
error: `Worktree node '${args.nodeId}' is missing workspace metadata`,
|
|
23093
|
+
recoveryHint: "Inspect the mesh node record before removing it, or remove stale metadata manually only after confirming no managed worktree remains."
|
|
23094
|
+
};
|
|
23095
|
+
}
|
|
23096
|
+
const worktreeExists = fs10.existsSync(workspace);
|
|
23097
|
+
const sourceNode = args.node?.clonedFromNodeId ? args.mesh?.nodes?.find((n) => n.id === args.node.clonedFromNodeId || n.nodeId === args.node.clonedFromNodeId) : args.mesh?.nodes?.find((n) => !n.isLocalWorktree);
|
|
23098
|
+
const repoRoot = typeof sourceNode?.repoRoot === "string" && sourceNode.repoRoot.trim() ? sourceNode.repoRoot.trim() : typeof sourceNode?.workspace === "string" && sourceNode.workspace.trim() ? sourceNode.workspace.trim() : "";
|
|
23099
|
+
if (!worktreeExists) {
|
|
23100
|
+
return { success: true, skipped: true, removedPath: workspace, repoRoot: repoRoot || void 0, reason: "worktree_path_missing" };
|
|
23101
|
+
}
|
|
23102
|
+
if (!repoRoot || !fs10.existsSync(repoRoot)) {
|
|
23103
|
+
return {
|
|
23104
|
+
success: false,
|
|
23105
|
+
code: "mesh_worktree_cleanup_missing_source_repo",
|
|
23106
|
+
error: `Refusing to remove worktree '${workspace}' because the source repo root is unavailable`,
|
|
23107
|
+
recoveryHint: "Run mesh_remove_node from the machine that owns the source repo, or verify the source node metadata before retrying."
|
|
23108
|
+
};
|
|
23109
|
+
}
|
|
23110
|
+
if (typeof args.node?.worktreeBranch !== "string" || !args.node.worktreeBranch.trim()) {
|
|
23111
|
+
return {
|
|
23112
|
+
success: false,
|
|
23113
|
+
code: "mesh_worktree_cleanup_missing_branch",
|
|
23114
|
+
error: `Refusing to remove worktree '${workspace}' because worktreeBranch metadata is missing`,
|
|
23115
|
+
recoveryHint: "Confirm this is an ADHDev-managed worktree before removing it manually; managed worktree nodes include worktreeBranch metadata."
|
|
23116
|
+
};
|
|
23117
|
+
}
|
|
23118
|
+
const { resolveWorktreePath: resolveWorktreePath2, listWorktrees: listWorktrees2, removeWorktree: removeWorktree2 } = await Promise.resolve().then(() => (init_git_worktree(), git_worktree_exports));
|
|
23119
|
+
const normalizePath = (value) => {
|
|
23120
|
+
const resolved = pathResolve(value);
|
|
23121
|
+
try {
|
|
23122
|
+
return fs10.realpathSync(resolved);
|
|
23123
|
+
} catch {
|
|
23124
|
+
return resolved;
|
|
23125
|
+
}
|
|
23126
|
+
};
|
|
23127
|
+
const expectedPath = normalizePath(resolveWorktreePath2(repoRoot, String(args.mesh?.name || args.mesh?.id || "mesh"), args.node.worktreeBranch));
|
|
23128
|
+
const actualPath = normalizePath(workspace);
|
|
23129
|
+
if (actualPath !== expectedPath) {
|
|
23130
|
+
return {
|
|
23131
|
+
success: false,
|
|
23132
|
+
code: "mesh_worktree_cleanup_unexpected_path",
|
|
23133
|
+
error: `Refusing to remove worktree '${workspace}' because it is not at the expected managed path '${expectedPath}'`,
|
|
23134
|
+
recoveryHint: "Use git worktree list/status to inspect the path. Retry only after confirming the mesh node metadata points to an ADHDev-managed worktree."
|
|
23135
|
+
};
|
|
23136
|
+
}
|
|
23137
|
+
const entries = await listWorktrees2(repoRoot);
|
|
23138
|
+
const managedEntry = entries.find((entry) => normalizePath(entry.path) === actualPath);
|
|
23139
|
+
if (!managedEntry) {
|
|
23140
|
+
return {
|
|
23141
|
+
success: false,
|
|
23142
|
+
code: "mesh_worktree_cleanup_not_registered",
|
|
23143
|
+
error: `Refusing to remove '${workspace}' because it is not registered in git worktree list for '${repoRoot}'`,
|
|
23144
|
+
recoveryHint: "Inspect git worktree list --porcelain from the source repo. If the path was already removed, prune git worktrees before retrying."
|
|
23145
|
+
};
|
|
23146
|
+
}
|
|
23147
|
+
if (managedEntry.branch && managedEntry.branch !== args.node.worktreeBranch) {
|
|
23148
|
+
return {
|
|
23149
|
+
success: false,
|
|
23150
|
+
code: "mesh_worktree_cleanup_branch_mismatch",
|
|
23151
|
+
error: `Refusing to remove '${workspace}' because git reports branch '${managedEntry.branch}', expected '${args.node.worktreeBranch}'`,
|
|
23152
|
+
recoveryHint: "Inspect the worktree branch and mesh metadata before retrying cleanup."
|
|
23153
|
+
};
|
|
23154
|
+
}
|
|
23155
|
+
try {
|
|
23156
|
+
const result = await removeWorktree2(repoRoot, workspace, { requireClean: true });
|
|
23157
|
+
return { success: true, removedPath: result.removedPath, repoRoot };
|
|
23158
|
+
} catch (e) {
|
|
23159
|
+
const message = String(e?.message || e || "worktree cleanup failed");
|
|
23160
|
+
const dirty = message.includes("dirty worktree") || message.includes("local changes");
|
|
23161
|
+
return {
|
|
23162
|
+
success: false,
|
|
23163
|
+
code: dirty ? "mesh_worktree_cleanup_dirty" : "mesh_worktree_cleanup_failed",
|
|
23164
|
+
error: message,
|
|
23165
|
+
recoveryHint: dirty ? "Commit, stash, or intentionally discard the worktree changes before retrying mesh_remove_node. The mesh registry entry is preserved until cleanup is safe." : "Inspect git worktree status/list from the source repo and retry after resolving the reported cleanup failure."
|
|
23166
|
+
};
|
|
23167
|
+
}
|
|
23168
|
+
}
|
|
22979
23169
|
isCompletedHostedSession(record) {
|
|
22980
23170
|
return record?.lifecycle === "stopped" || record?.lifecycle === "failed" || record?.lifecycle === "interrupted";
|
|
22981
23171
|
}
|
|
@@ -23976,17 +24166,21 @@ var DaemonCommandRouter = class {
|
|
|
23976
24166
|
sessionCleanup = await this.cleanupMeshSessions({ meshId, nodeId, node, mode: sessionCleanupMode });
|
|
23977
24167
|
if (sessionCleanup.success === false) return { success: false, removed: false, sessionCleanup };
|
|
23978
24168
|
}
|
|
23979
|
-
|
|
23980
|
-
|
|
23981
|
-
|
|
23982
|
-
|
|
23983
|
-
|
|
23984
|
-
|
|
23985
|
-
|
|
23986
|
-
|
|
23987
|
-
|
|
23988
|
-
|
|
24169
|
+
let worktreeCleanup;
|
|
24170
|
+
if (node?.isLocalWorktree) {
|
|
24171
|
+
const cleanupResult = await this.cleanupLocalWorktreeNode({ mesh, node, nodeId });
|
|
24172
|
+
if (cleanupResult.success === false) {
|
|
24173
|
+
return {
|
|
24174
|
+
success: false,
|
|
24175
|
+
removed: false,
|
|
24176
|
+
code: cleanupResult.code,
|
|
24177
|
+
error: cleanupResult.error,
|
|
24178
|
+
recoveryHint: cleanupResult.recoveryHint,
|
|
24179
|
+
...sessionCleanup ? { sessionCleanup } : {},
|
|
24180
|
+
worktreeCleanup: cleanupResult
|
|
24181
|
+
};
|
|
23989
24182
|
}
|
|
24183
|
+
worktreeCleanup = cleanupResult;
|
|
23990
24184
|
}
|
|
23991
24185
|
let removed = false;
|
|
23992
24186
|
if (meshRecord?.inline) {
|
|
@@ -24012,7 +24206,7 @@ var DaemonCommandRouter = class {
|
|
|
24012
24206
|
} catch {
|
|
24013
24207
|
}
|
|
24014
24208
|
}
|
|
24015
|
-
return { success: true, removed, ...sessionCleanup ? { sessionCleanup } : {} };
|
|
24209
|
+
return { success: true, removed, ...sessionCleanup ? { sessionCleanup } : {}, ...worktreeCleanup ? { worktreeCleanup } : {} };
|
|
24016
24210
|
} catch (e) {
|
|
24017
24211
|
return { success: false, error: e.message };
|
|
24018
24212
|
}
|
|
@@ -24047,6 +24241,7 @@ var DaemonCommandRouter = class {
|
|
|
24047
24241
|
workspace: result.worktreePath,
|
|
24048
24242
|
repoRoot: result.worktreePath,
|
|
24049
24243
|
daemonId: sourceNode.daemonId,
|
|
24244
|
+
machineId: sourceNode.machineId ?? sourceNode.machine_id,
|
|
24050
24245
|
userOverrides: { ...sourceNode.userOverrides || {} },
|
|
24051
24246
|
policy: { ...sourceNode.policy || {} },
|
|
24052
24247
|
isLocalWorktree: true,
|
|
@@ -24060,6 +24255,7 @@ var DaemonCommandRouter = class {
|
|
|
24060
24255
|
workspace: result.worktreePath,
|
|
24061
24256
|
repoRoot: result.worktreePath,
|
|
24062
24257
|
daemonId: sourceNode.daemonId,
|
|
24258
|
+
machineId: sourceNode.machineId ?? sourceNode.machine_id,
|
|
24063
24259
|
userOverrides: { ...sourceNode.userOverrides || {} },
|
|
24064
24260
|
isLocalWorktree: true,
|
|
24065
24261
|
worktreeBranch: result.branch,
|