@adhdev/daemon-core 0.9.77-rc.40 → 0.9.77-rc.42
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 +1 -0
- package/dist/commands/router.d.ts +1 -0
- package/dist/git/git-worktree.d.ts +6 -2
- package/dist/index.js +270 -43
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +270 -43
- package/dist/index.mjs.map +1 -1
- package/dist/providers/cli-provider-instance.d.ts +6 -0
- package/package.json +1 -1
- package/src/cli-adapters/provider-cli-adapter.ts +16 -6
- package/src/commands/router.ts +145 -16
- 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/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,
|
|
@@ -680,6 +692,7 @@ function buildRulesSection(coordinatorCliType) {
|
|
|
680
692
|
- **Respect node capabilities.** Don't send build tasks to read-only nodes. Don't push from nodes that aren't allowed to.
|
|
681
693
|
- **Never fabricate tool results.** Always call the actual tool; never pretend you did.
|
|
682
694
|
- **Clean up worktree nodes.** After a worktree task completes and its changes are merged or checkpointed, call \`mesh_remove_node\` to free resources.
|
|
695
|
+
- **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
696
|
- **Name worktree branches meaningfully.** Use descriptive names like \`feat/auth-refactor\` or \`fix/build-123\`.${coordinatorNote}`;
|
|
684
697
|
}
|
|
685
698
|
var TOOLS_SECTION, TOOL_EXPOSURE_PREFLIGHT_SECTION, WORKFLOW_SECTION;
|
|
@@ -701,6 +714,7 @@ var init_coordinator_prompt = __esm({
|
|
|
701
714
|
| \`mesh_checkpoint\` | Create a git checkpoint on a node |
|
|
702
715
|
| \`mesh_approve\` | Approve/reject a pending agent action |
|
|
703
716
|
| \`mesh_clone_node\` | Create a worktree node for isolated parallel branch work |
|
|
717
|
+
| \`mesh_refine_node\` | Validate and merge a completed worktree node back into its base branch |
|
|
704
718
|
| \`mesh_remove_node\` | Remove a node (cleans up worktree if applicable) |`;
|
|
705
719
|
TOOL_EXPOSURE_PREFLIGHT_SECTION = `## Tool Exposure Preflight
|
|
706
720
|
|
|
@@ -717,8 +731,9 @@ Before doing any coordinator work, confirm that the actual callable tool list in
|
|
|
717
731
|
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
732
|
5. **Verify** \u2014 When a task reports completion or git work is visible, call \`mesh_git_status\` to verify changes were made.
|
|
719
733
|
6. **Checkpoint** \u2014 Call \`mesh_checkpoint\` to save the work.
|
|
720
|
-
7. **
|
|
721
|
-
8. **
|
|
734
|
+
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.
|
|
735
|
+
8. **Clean up** \u2014 Remove worktree nodes via \`mesh_remove_node\` after their work is merged or no longer needed.
|
|
736
|
+
9. **Report** \u2014 Summarize what was done, what changed, any issues, and the branch convergence state.
|
|
722
737
|
|
|
723
738
|
## Failure Recovery
|
|
724
739
|
|
|
@@ -2766,6 +2781,7 @@ var init_provider_cli_adapter = __esm({
|
|
|
2766
2781
|
this.requirePromptEchoBeforeSubmit = resolvedConfig.requirePromptEchoBeforeSubmit;
|
|
2767
2782
|
this.providerResolutionMeta = resolvedConfig.providerResolutionMeta;
|
|
2768
2783
|
this.cliScripts = provider.scripts || {};
|
|
2784
|
+
this.scriptState = typeof this.cliScripts.createState === "function" ? this.cliScripts.createState() ?? null : null;
|
|
2769
2785
|
const scriptNames = listCliScriptNames(this.cliScripts);
|
|
2770
2786
|
if (scriptNames.length > 0) {
|
|
2771
2787
|
LOG.info("CLI", `[${this.cliType}] CLI scripts: [${scriptNames.join(", ")}]`);
|
|
@@ -2926,9 +2942,13 @@ ${lastSnapshot}`;
|
|
|
2926
2942
|
this.lastScreenChangeAt = 0;
|
|
2927
2943
|
this.lastScreenSnapshotReadAt = Number.NEGATIVE_INFINITY;
|
|
2928
2944
|
}
|
|
2945
|
+
getAccumulatedRawBufferCacheKey() {
|
|
2946
|
+
return this.accumulatedRawBuffer.replace(/\x1B\][^\x07]*(?:\x07|\x1B\\)/g, "").replace(/\x1B[P^_X][\s\S]*?(?:\x07|\x1B\\)/g, "").replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, "");
|
|
2947
|
+
}
|
|
2929
2948
|
getFreshParsedStatusCache() {
|
|
2930
2949
|
const cached = this.parsedStatusCache;
|
|
2931
|
-
|
|
2950
|
+
const accumulatedRawBufferKey = this.getAccumulatedRawBufferCacheKey();
|
|
2951
|
+
if (cached && cached.responseBuffer === this.responseBuffer && cached.currentTurnScope === this.currentTurnScope && cached.recentOutputBuffer === this.recentOutputBuffer && cached.accumulatedBuffer === this.accumulatedBuffer && cached.accumulatedRawBufferKey === accumulatedRawBufferKey && cached.screenText === this.lastScreenText && cached.currentStatus === this.currentStatus && cached.activeModal === this.activeModal && cached.cliName === this.cliName) {
|
|
2932
2952
|
return cached.result;
|
|
2933
2953
|
}
|
|
2934
2954
|
return null;
|
|
@@ -3031,7 +3051,7 @@ ${lastSnapshot}`;
|
|
|
3031
3051
|
this.cliScripts = scripts;
|
|
3032
3052
|
this.parsedStatusCache = null;
|
|
3033
3053
|
this.parseErrorMessage = null;
|
|
3034
|
-
this.scriptState = typeof scripts.createState === "function" ? scripts.createState() : null;
|
|
3054
|
+
this.scriptState = typeof scripts.createState === "function" ? scripts.createState() ?? null : null;
|
|
3035
3055
|
const scriptNames = listCliScriptNames(scripts);
|
|
3036
3056
|
LOG.info("CLI", `[${this.cliType}] CLI scripts injected: [${scriptNames.join(", ")}]`);
|
|
3037
3057
|
}
|
|
@@ -4020,7 +4040,8 @@ ${lastSnapshot}`;
|
|
|
4020
4040
|
const screenText = this.readTerminalScreenText();
|
|
4021
4041
|
const parseScreenText = this.getParseScreenText(screenText);
|
|
4022
4042
|
const cached = this.parsedStatusCache;
|
|
4023
|
-
|
|
4043
|
+
const accumulatedRawBufferKey = this.getAccumulatedRawBufferCacheKey();
|
|
4044
|
+
if (cached && cached.responseBuffer === this.responseBuffer && cached.currentTurnScope === this.currentTurnScope && cached.recentOutputBuffer === this.recentOutputBuffer && cached.accumulatedBuffer === this.accumulatedBuffer && cached.accumulatedRawBufferKey === accumulatedRawBufferKey && cached.screenText === parseScreenText && cached.currentStatus === this.currentStatus && cached.activeModal === this.activeModal && cached.cliName === this.cliName) {
|
|
4024
4045
|
return cached.result;
|
|
4025
4046
|
}
|
|
4026
4047
|
const parsed = this.runParseSession();
|
|
@@ -4048,7 +4069,7 @@ ${lastSnapshot}`;
|
|
|
4048
4069
|
currentTurnScope: this.currentTurnScope,
|
|
4049
4070
|
recentOutputBuffer: this.recentOutputBuffer,
|
|
4050
4071
|
accumulatedBuffer: this.accumulatedBuffer,
|
|
4051
|
-
|
|
4072
|
+
accumulatedRawBufferKey,
|
|
4052
4073
|
screenText: parseScreenText,
|
|
4053
4074
|
currentStatus: this.currentStatus,
|
|
4054
4075
|
activeModal: this.activeModal,
|
|
@@ -4073,7 +4094,7 @@ ${lastSnapshot}`;
|
|
|
4073
4094
|
scope: this.currentTurnScope,
|
|
4074
4095
|
runtimeSettings: this.runtimeSettings
|
|
4075
4096
|
});
|
|
4076
|
-
return await Promise.resolve(
|
|
4097
|
+
return await Promise.resolve(this.invokeCliScript(fn, {
|
|
4077
4098
|
...input,
|
|
4078
4099
|
args: args && typeof args === "object" ? { ...args } : {}
|
|
4079
4100
|
}));
|
|
@@ -16144,6 +16165,8 @@ function normalizeProviderSessionId(provider, providerSessionId) {
|
|
|
16144
16165
|
}
|
|
16145
16166
|
|
|
16146
16167
|
// src/providers/cli-provider-instance.ts
|
|
16168
|
+
var COMPLETED_FINALIZATION_RETRY_MS = 1e3;
|
|
16169
|
+
var COMPLETED_FINALIZATION_MAX_WAIT_MS = 3e4;
|
|
16147
16170
|
var IMAGE_MIME_EXTENSIONS = {
|
|
16148
16171
|
"image/png": ".png",
|
|
16149
16172
|
"image/jpeg": ".jpg",
|
|
@@ -16207,6 +16230,13 @@ function cleanupStaleMaterializedImages(dir) {
|
|
|
16207
16230
|
} catch {
|
|
16208
16231
|
}
|
|
16209
16232
|
}
|
|
16233
|
+
function hasNonEmptyCliModalButtons(activeModal) {
|
|
16234
|
+
const buttons = activeModal?.buttons;
|
|
16235
|
+
return Array.isArray(buttons) && buttons.some((button) => String(button || "").trim().length > 0);
|
|
16236
|
+
}
|
|
16237
|
+
function isCliGeneratingLikeStatus(status) {
|
|
16238
|
+
return status === "generating" || status === "streaming" || status === "long_generating" || status === "starting";
|
|
16239
|
+
}
|
|
16210
16240
|
function buildCliStructuredInputPrompt(input, options = {}) {
|
|
16211
16241
|
const promptParts = [];
|
|
16212
16242
|
const imageRefs = [];
|
|
@@ -16491,10 +16521,12 @@ var CliProviderInstance = class {
|
|
|
16491
16521
|
const mergedMessages = this.mergeConversationMessages(parsedMessages);
|
|
16492
16522
|
const canonicalBackedHistory = this.syncCanonicalSavedHistoryIfNeeded();
|
|
16493
16523
|
const dirName = this.workingDir.split("/").filter(Boolean).pop() || "session";
|
|
16524
|
+
const parsedChatStatus = typeof parsedStatus?.status === "string" && parsedStatus.status.trim() ? parsedStatus.status.trim() : void 0;
|
|
16525
|
+
const suppressStaleParsedBusyStatus = this.shouldSuppressStaleParsedBusyStatus(parsedStatus, adapterStatus);
|
|
16494
16526
|
if (parsedMessages.length > 0) {
|
|
16495
16527
|
const shouldSkipReplayPersist = this.suppressIdleHistoryReplay && adapterStatus.status === "idle" && parsedStatus?.status === "idle";
|
|
16496
16528
|
let messagesToSave = parsedMessages;
|
|
16497
|
-
if (
|
|
16529
|
+
if (!suppressStaleParsedBusyStatus && (parsedChatStatus === "generating" || parsedChatStatus === "long_generating")) {
|
|
16498
16530
|
const lastIdx = messagesToSave.length - 1;
|
|
16499
16531
|
if (lastIdx >= 0 && messagesToSave[lastIdx]?.role === "assistant") {
|
|
16500
16532
|
messagesToSave = messagesToSave.slice(0, lastIdx);
|
|
@@ -16528,6 +16560,7 @@ var CliProviderInstance = class {
|
|
|
16528
16560
|
summaryMetadata: this.summaryMetadata,
|
|
16529
16561
|
controlValues: this.controlValues
|
|
16530
16562
|
});
|
|
16563
|
+
const activeChatStatus = parseErrorMessage ? "error" : autoApproveActive && parsedStatus?.status === "waiting_approval" ? "generating" : adapterStatus.status !== "idle" ? visibleStatus : suppressStaleParsedBusyStatus ? visibleStatus : parsedChatStatus || visibleStatus;
|
|
16531
16564
|
return {
|
|
16532
16565
|
type: this.type,
|
|
16533
16566
|
name: this.provider.name,
|
|
@@ -16537,7 +16570,7 @@ var CliProviderInstance = class {
|
|
|
16537
16570
|
activeChat: {
|
|
16538
16571
|
id: `${this.type}_${this.workingDir}`,
|
|
16539
16572
|
title: parsedStatus?.title || dirName,
|
|
16540
|
-
status:
|
|
16573
|
+
status: activeChatStatus,
|
|
16541
16574
|
messages: mergedMessages,
|
|
16542
16575
|
activeModal: autoApproveActive ? null : parsedStatus?.activeModal ?? adapterStatus.activeModal,
|
|
16543
16576
|
inputContent: ""
|
|
@@ -16667,6 +16700,102 @@ var CliProviderInstance = class {
|
|
|
16667
16700
|
}
|
|
16668
16701
|
this.applyProviderResponse(parsed.payload, { phase: "immediate" });
|
|
16669
16702
|
}
|
|
16703
|
+
completionHasFinalAssistantMessage(messages) {
|
|
16704
|
+
const visibleMessages = (Array.isArray(messages) ? messages : []).filter((message) => isUserFacingChatMessage(message));
|
|
16705
|
+
const lastVisible = visibleMessages[visibleMessages.length - 1];
|
|
16706
|
+
const role = typeof lastVisible?.role === "string" ? lastVisible.role.trim().toLowerCase() : "";
|
|
16707
|
+
const content = lastVisible ? flattenContent(lastVisible.content).trim() : "";
|
|
16708
|
+
return role === "assistant" && !!content;
|
|
16709
|
+
}
|
|
16710
|
+
hasAdapterPendingResponse() {
|
|
16711
|
+
const adapterAny = this.adapter;
|
|
16712
|
+
if (adapterAny?.isWaitingForResponse === true) return true;
|
|
16713
|
+
if (adapterAny?.currentTurnScope) return true;
|
|
16714
|
+
try {
|
|
16715
|
+
if (typeof this.adapter.isProcessing === "function" && this.adapter.isProcessing()) return true;
|
|
16716
|
+
} catch {
|
|
16717
|
+
}
|
|
16718
|
+
try {
|
|
16719
|
+
const partial = typeof this.adapter.getPartialResponse === "function" ? this.adapter.getPartialResponse() : "";
|
|
16720
|
+
if (typeof partial === "string" && partial.trim()) return true;
|
|
16721
|
+
} catch {
|
|
16722
|
+
}
|
|
16723
|
+
return false;
|
|
16724
|
+
}
|
|
16725
|
+
shouldSuppressStaleParsedBusyStatus(parsedStatus, adapterStatus) {
|
|
16726
|
+
const parsedRawStatus = typeof parsedStatus?.status === "string" ? parsedStatus.status.trim() : "";
|
|
16727
|
+
const adapterRawStatus = typeof adapterStatus?.status === "string" ? adapterStatus.status.trim() : "";
|
|
16728
|
+
if (!isCliGeneratingLikeStatus(parsedRawStatus)) return false;
|
|
16729
|
+
if (adapterRawStatus !== "idle") return false;
|
|
16730
|
+
if (hasNonEmptyCliModalButtons(parsedStatus?.activeModal ?? parsedStatus?.modal)) return false;
|
|
16731
|
+
return !this.hasAdapterPendingResponse();
|
|
16732
|
+
}
|
|
16733
|
+
getCompletedFinalizationBlockReason(latestVisibleStatus) {
|
|
16734
|
+
if (latestVisibleStatus !== "idle") return `status:${latestVisibleStatus}`;
|
|
16735
|
+
const adapterAny = this.adapter;
|
|
16736
|
+
if (adapterAny?.isWaitingForResponse === true) return "adapter_waiting_for_response";
|
|
16737
|
+
if (adapterAny?.currentTurnScope) return "adapter_turn_scope_active";
|
|
16738
|
+
const partial = typeof this.adapter.getPartialResponse === "function" ? this.adapter.getPartialResponse() : "";
|
|
16739
|
+
if (typeof partial === "string" && partial.trim()) return "partial_response_pending";
|
|
16740
|
+
let parsed;
|
|
16741
|
+
try {
|
|
16742
|
+
parsed = this.adapter.getScriptParsedStatus();
|
|
16743
|
+
} catch (error) {
|
|
16744
|
+
return `parse_error:${error?.message || String(error)}`;
|
|
16745
|
+
}
|
|
16746
|
+
const parsedStatus = typeof parsed?.status === "string" ? parsed.status : "unknown";
|
|
16747
|
+
if (parsedStatus !== "idle") return `parsed_status:${parsedStatus}`;
|
|
16748
|
+
if (parsed?.activeModal || parsed?.modal) return "parsed_modal_active";
|
|
16749
|
+
if (!this.completionHasFinalAssistantMessage(parsed?.messages)) return "missing_final_assistant";
|
|
16750
|
+
return null;
|
|
16751
|
+
}
|
|
16752
|
+
scheduleCompletedDebounceFlush(delayMs) {
|
|
16753
|
+
if (this.completedDebounceTimer) clearTimeout(this.completedDebounceTimer);
|
|
16754
|
+
this.completedDebounceTimer = setTimeout(() => this.flushCompletedDebounceIfFinalized(), delayMs);
|
|
16755
|
+
}
|
|
16756
|
+
flushCompletedDebounceIfFinalized() {
|
|
16757
|
+
const pending = this.completedDebouncePending;
|
|
16758
|
+
if (!pending) {
|
|
16759
|
+
this.completedDebounceTimer = null;
|
|
16760
|
+
return;
|
|
16761
|
+
}
|
|
16762
|
+
const latestStatus = this.adapter.getStatus({ allowParse: false });
|
|
16763
|
+
const latestAutoApproveActive = latestStatus.status === "waiting_approval" && this.shouldAutoApprove();
|
|
16764
|
+
const latestVisibleStatus = latestAutoApproveActive ? "generating" : latestStatus.status;
|
|
16765
|
+
if (latestVisibleStatus !== "idle") {
|
|
16766
|
+
LOG.info("CLI", `[${this.type}] cancelled pending completed (resumed ${latestVisibleStatus})`);
|
|
16767
|
+
this.completedDebouncePending = null;
|
|
16768
|
+
this.completedDebounceTimer = null;
|
|
16769
|
+
return;
|
|
16770
|
+
}
|
|
16771
|
+
const blockReason = this.getCompletedFinalizationBlockReason(latestVisibleStatus);
|
|
16772
|
+
if (blockReason) {
|
|
16773
|
+
const waitedMs = Date.now() - pending.firstObservedAt;
|
|
16774
|
+
if (waitedMs < COMPLETED_FINALIZATION_MAX_WAIT_MS) {
|
|
16775
|
+
if (pending.loggedBlockReason !== blockReason) {
|
|
16776
|
+
LOG.info("CLI", `[${this.type}] waiting to emit completed until transcript finalizes (${blockReason})`);
|
|
16777
|
+
pending.loggedBlockReason = blockReason;
|
|
16778
|
+
}
|
|
16779
|
+
this.scheduleCompletedDebounceFlush(COMPLETED_FINALIZATION_RETRY_MS);
|
|
16780
|
+
return;
|
|
16781
|
+
}
|
|
16782
|
+
LOG.warn("CLI", `[${this.type}] suppressed completed event after ${waitedMs}ms without finalized assistant turn (${blockReason})`);
|
|
16783
|
+
this.completedDebouncePending = null;
|
|
16784
|
+
this.completedDebounceTimer = null;
|
|
16785
|
+
this.generatingStartedAt = 0;
|
|
16786
|
+
return;
|
|
16787
|
+
}
|
|
16788
|
+
LOG.info("CLI", `[${this.type}] completed in ${pending.duration}s`);
|
|
16789
|
+
this.pushEvent({
|
|
16790
|
+
event: "agent:generating_completed",
|
|
16791
|
+
chatTitle: pending.chatTitle,
|
|
16792
|
+
duration: pending.duration,
|
|
16793
|
+
timestamp: pending.timestamp
|
|
16794
|
+
});
|
|
16795
|
+
this.completedDebouncePending = null;
|
|
16796
|
+
this.completedDebounceTimer = null;
|
|
16797
|
+
this.generatingStartedAt = 0;
|
|
16798
|
+
}
|
|
16670
16799
|
maybeAutoApproveStatus(adapterStatus, now = Date.now()) {
|
|
16671
16800
|
const autoApproveActive = adapterStatus?.status === "waiting_approval" && this.shouldAutoApprove();
|
|
16672
16801
|
if (autoApproveActive && !this.autoApproveBusy) {
|
|
@@ -16764,26 +16893,8 @@ var CliProviderInstance = class {
|
|
|
16764
16893
|
this.generatingDebouncePending = null;
|
|
16765
16894
|
this.generatingStartedAt = 0;
|
|
16766
16895
|
} else {
|
|
16767
|
-
|
|
16768
|
-
this.
|
|
16769
|
-
this.completedDebounceTimer = setTimeout(() => {
|
|
16770
|
-
if (this.completedDebouncePending) {
|
|
16771
|
-
const latestStatus = this.adapter.getStatus({ allowParse: false });
|
|
16772
|
-
const latestAutoApproveActive = latestStatus.status === "waiting_approval" && this.shouldAutoApprove();
|
|
16773
|
-
const latestVisibleStatus = latestAutoApproveActive ? "generating" : latestStatus.status;
|
|
16774
|
-
if (latestVisibleStatus !== "idle") {
|
|
16775
|
-
LOG.info("CLI", `[${this.type}] cancelled pending completed (resumed ${latestVisibleStatus})`);
|
|
16776
|
-
this.completedDebouncePending = null;
|
|
16777
|
-
this.completedDebounceTimer = null;
|
|
16778
|
-
return;
|
|
16779
|
-
}
|
|
16780
|
-
LOG.info("CLI", `[${this.type}] completed in ${this.completedDebouncePending.duration}s`);
|
|
16781
|
-
this.pushEvent({ event: "agent:generating_completed", ...this.completedDebouncePending });
|
|
16782
|
-
this.completedDebouncePending = null;
|
|
16783
|
-
this.generatingStartedAt = 0;
|
|
16784
|
-
}
|
|
16785
|
-
this.completedDebounceTimer = null;
|
|
16786
|
-
}, 3e3);
|
|
16896
|
+
this.completedDebouncePending = { chatTitle, duration, timestamp: now, firstObservedAt: now };
|
|
16897
|
+
this.scheduleCompletedDebounceFlush(3e3);
|
|
16787
16898
|
}
|
|
16788
16899
|
} else if (newStatus === "idle" && this.lastStatus === "starting") {
|
|
16789
16900
|
this.pushEvent({ event: "agent:ready", chatTitle, timestamp: now });
|
|
@@ -22786,6 +22897,34 @@ function loadHermesCoordinatorBaseConfig(targetConfigPath) {
|
|
|
22786
22897
|
const { mcp_servers: _mcpServers, ...baseConfig } = parsed;
|
|
22787
22898
|
return { config: baseConfig, sourceHome, sourceConfigPath };
|
|
22788
22899
|
}
|
|
22900
|
+
function stripHermesCoordinatorTempModelProviderOverrides(config) {
|
|
22901
|
+
const {
|
|
22902
|
+
model: _model,
|
|
22903
|
+
provider: _provider,
|
|
22904
|
+
default_model: _defaultModel,
|
|
22905
|
+
defaultProvider: _defaultProvider,
|
|
22906
|
+
default_provider: _defaultProviderSnake,
|
|
22907
|
+
modelProvider: _modelProvider,
|
|
22908
|
+
model_provider: _modelProviderSnake,
|
|
22909
|
+
...sanitized
|
|
22910
|
+
} = config;
|
|
22911
|
+
const delegation = sanitized.delegation;
|
|
22912
|
+
if (delegation && typeof delegation === "object" && !Array.isArray(delegation)) {
|
|
22913
|
+
const {
|
|
22914
|
+
model: _delegationModel,
|
|
22915
|
+
provider: _delegationProvider,
|
|
22916
|
+
modelProvider: _delegationModelProvider,
|
|
22917
|
+
model_provider: _delegationModelProviderSnake,
|
|
22918
|
+
...delegationRest
|
|
22919
|
+
} = delegation;
|
|
22920
|
+
if (Object.keys(delegationRest).length > 0) {
|
|
22921
|
+
sanitized.delegation = delegationRest;
|
|
22922
|
+
} else {
|
|
22923
|
+
delete sanitized.delegation;
|
|
22924
|
+
}
|
|
22925
|
+
}
|
|
22926
|
+
return sanitized;
|
|
22927
|
+
}
|
|
22789
22928
|
function copyHermesCoordinatorCredentialFiles(sourceHome, targetHome) {
|
|
22790
22929
|
if (pathResolve(sourceHome) === pathResolve(targetHome)) return;
|
|
22791
22930
|
for (const fileName of [".env", "auth.json"]) {
|
|
@@ -22943,6 +23082,89 @@ var DaemonCommandRouter = class {
|
|
|
22943
23082
|
if (record?.meta?.meshNodeId === nodeId) return true;
|
|
22944
23083
|
return false;
|
|
22945
23084
|
}
|
|
23085
|
+
async cleanupLocalWorktreeNode(args) {
|
|
23086
|
+
const workspace = typeof args.node?.workspace === "string" ? args.node.workspace.trim() : "";
|
|
23087
|
+
if (!workspace) {
|
|
23088
|
+
return {
|
|
23089
|
+
success: false,
|
|
23090
|
+
code: "mesh_worktree_cleanup_missing_workspace",
|
|
23091
|
+
error: `Worktree node '${args.nodeId}' is missing workspace metadata`,
|
|
23092
|
+
recoveryHint: "Inspect the mesh node record before removing it, or remove stale metadata manually only after confirming no managed worktree remains."
|
|
23093
|
+
};
|
|
23094
|
+
}
|
|
23095
|
+
const worktreeExists = fs10.existsSync(workspace);
|
|
23096
|
+
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);
|
|
23097
|
+
const repoRoot = typeof sourceNode?.repoRoot === "string" && sourceNode.repoRoot.trim() ? sourceNode.repoRoot.trim() : typeof sourceNode?.workspace === "string" && sourceNode.workspace.trim() ? sourceNode.workspace.trim() : "";
|
|
23098
|
+
if (!worktreeExists) {
|
|
23099
|
+
return { success: true, skipped: true, removedPath: workspace, repoRoot: repoRoot || void 0, reason: "worktree_path_missing" };
|
|
23100
|
+
}
|
|
23101
|
+
if (!repoRoot || !fs10.existsSync(repoRoot)) {
|
|
23102
|
+
return {
|
|
23103
|
+
success: false,
|
|
23104
|
+
code: "mesh_worktree_cleanup_missing_source_repo",
|
|
23105
|
+
error: `Refusing to remove worktree '${workspace}' because the source repo root is unavailable`,
|
|
23106
|
+
recoveryHint: "Run mesh_remove_node from the machine that owns the source repo, or verify the source node metadata before retrying."
|
|
23107
|
+
};
|
|
23108
|
+
}
|
|
23109
|
+
if (typeof args.node?.worktreeBranch !== "string" || !args.node.worktreeBranch.trim()) {
|
|
23110
|
+
return {
|
|
23111
|
+
success: false,
|
|
23112
|
+
code: "mesh_worktree_cleanup_missing_branch",
|
|
23113
|
+
error: `Refusing to remove worktree '${workspace}' because worktreeBranch metadata is missing`,
|
|
23114
|
+
recoveryHint: "Confirm this is an ADHDev-managed worktree before removing it manually; managed worktree nodes include worktreeBranch metadata."
|
|
23115
|
+
};
|
|
23116
|
+
}
|
|
23117
|
+
const { resolveWorktreePath: resolveWorktreePath2, listWorktrees: listWorktrees2, removeWorktree: removeWorktree2 } = await Promise.resolve().then(() => (init_git_worktree(), git_worktree_exports));
|
|
23118
|
+
const normalizePath = (value) => {
|
|
23119
|
+
const resolved = pathResolve(value);
|
|
23120
|
+
try {
|
|
23121
|
+
return fs10.realpathSync(resolved);
|
|
23122
|
+
} catch {
|
|
23123
|
+
return resolved;
|
|
23124
|
+
}
|
|
23125
|
+
};
|
|
23126
|
+
const expectedPath = normalizePath(resolveWorktreePath2(repoRoot, String(args.mesh?.name || args.mesh?.id || "mesh"), args.node.worktreeBranch));
|
|
23127
|
+
const actualPath = normalizePath(workspace);
|
|
23128
|
+
if (actualPath !== expectedPath) {
|
|
23129
|
+
return {
|
|
23130
|
+
success: false,
|
|
23131
|
+
code: "mesh_worktree_cleanup_unexpected_path",
|
|
23132
|
+
error: `Refusing to remove worktree '${workspace}' because it is not at the expected managed path '${expectedPath}'`,
|
|
23133
|
+
recoveryHint: "Use git worktree list/status to inspect the path. Retry only after confirming the mesh node metadata points to an ADHDev-managed worktree."
|
|
23134
|
+
};
|
|
23135
|
+
}
|
|
23136
|
+
const entries = await listWorktrees2(repoRoot);
|
|
23137
|
+
const managedEntry = entries.find((entry) => normalizePath(entry.path) === actualPath);
|
|
23138
|
+
if (!managedEntry) {
|
|
23139
|
+
return {
|
|
23140
|
+
success: false,
|
|
23141
|
+
code: "mesh_worktree_cleanup_not_registered",
|
|
23142
|
+
error: `Refusing to remove '${workspace}' because it is not registered in git worktree list for '${repoRoot}'`,
|
|
23143
|
+
recoveryHint: "Inspect git worktree list --porcelain from the source repo. If the path was already removed, prune git worktrees before retrying."
|
|
23144
|
+
};
|
|
23145
|
+
}
|
|
23146
|
+
if (managedEntry.branch && managedEntry.branch !== args.node.worktreeBranch) {
|
|
23147
|
+
return {
|
|
23148
|
+
success: false,
|
|
23149
|
+
code: "mesh_worktree_cleanup_branch_mismatch",
|
|
23150
|
+
error: `Refusing to remove '${workspace}' because git reports branch '${managedEntry.branch}', expected '${args.node.worktreeBranch}'`,
|
|
23151
|
+
recoveryHint: "Inspect the worktree branch and mesh metadata before retrying cleanup."
|
|
23152
|
+
};
|
|
23153
|
+
}
|
|
23154
|
+
try {
|
|
23155
|
+
const result = await removeWorktree2(repoRoot, workspace, { requireClean: true });
|
|
23156
|
+
return { success: true, removedPath: result.removedPath, repoRoot };
|
|
23157
|
+
} catch (e) {
|
|
23158
|
+
const message = String(e?.message || e || "worktree cleanup failed");
|
|
23159
|
+
const dirty = message.includes("dirty worktree") || message.includes("local changes");
|
|
23160
|
+
return {
|
|
23161
|
+
success: false,
|
|
23162
|
+
code: dirty ? "mesh_worktree_cleanup_dirty" : "mesh_worktree_cleanup_failed",
|
|
23163
|
+
error: message,
|
|
23164
|
+
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."
|
|
23165
|
+
};
|
|
23166
|
+
}
|
|
23167
|
+
}
|
|
22946
23168
|
isCompletedHostedSession(record) {
|
|
22947
23169
|
return record?.lifecycle === "stopped" || record?.lifecycle === "failed" || record?.lifecycle === "interrupted";
|
|
22948
23170
|
}
|
|
@@ -23943,17 +24165,21 @@ var DaemonCommandRouter = class {
|
|
|
23943
24165
|
sessionCleanup = await this.cleanupMeshSessions({ meshId, nodeId, node, mode: sessionCleanupMode });
|
|
23944
24166
|
if (sessionCleanup.success === false) return { success: false, removed: false, sessionCleanup };
|
|
23945
24167
|
}
|
|
23946
|
-
|
|
23947
|
-
|
|
23948
|
-
|
|
23949
|
-
|
|
23950
|
-
|
|
23951
|
-
|
|
23952
|
-
|
|
23953
|
-
|
|
23954
|
-
|
|
23955
|
-
|
|
24168
|
+
let worktreeCleanup;
|
|
24169
|
+
if (node?.isLocalWorktree) {
|
|
24170
|
+
const cleanupResult = await this.cleanupLocalWorktreeNode({ mesh, node, nodeId });
|
|
24171
|
+
if (cleanupResult.success === false) {
|
|
24172
|
+
return {
|
|
24173
|
+
success: false,
|
|
24174
|
+
removed: false,
|
|
24175
|
+
code: cleanupResult.code,
|
|
24176
|
+
error: cleanupResult.error,
|
|
24177
|
+
recoveryHint: cleanupResult.recoveryHint,
|
|
24178
|
+
...sessionCleanup ? { sessionCleanup } : {},
|
|
24179
|
+
worktreeCleanup: cleanupResult
|
|
24180
|
+
};
|
|
23956
24181
|
}
|
|
24182
|
+
worktreeCleanup = cleanupResult;
|
|
23957
24183
|
}
|
|
23958
24184
|
let removed = false;
|
|
23959
24185
|
if (meshRecord?.inline) {
|
|
@@ -23979,7 +24205,7 @@ var DaemonCommandRouter = class {
|
|
|
23979
24205
|
} catch {
|
|
23980
24206
|
}
|
|
23981
24207
|
}
|
|
23982
|
-
return { success: true, removed, ...sessionCleanup ? { sessionCleanup } : {} };
|
|
24208
|
+
return { success: true, removed, ...sessionCleanup ? { sessionCleanup } : {}, ...worktreeCleanup ? { worktreeCleanup } : {} };
|
|
23983
24209
|
} catch (e) {
|
|
23984
24210
|
return { success: false, error: e.message };
|
|
23985
24211
|
}
|
|
@@ -24309,7 +24535,8 @@ ${block}`);
|
|
|
24309
24535
|
if (hadExistingMcpConfig) {
|
|
24310
24536
|
try {
|
|
24311
24537
|
const parsedExistingMcpConfig = parseMeshCoordinatorMcpConfig(readFileSync17(mcpConfigPath, "utf-8"), configFormat);
|
|
24312
|
-
|
|
24538
|
+
const existingCoordinatorConfig = hermesManualFallback ? stripHermesCoordinatorTempModelProviderOverrides(parsedExistingMcpConfig) : parsedExistingMcpConfig;
|
|
24539
|
+
existingMcpConfig = { ...existingMcpConfig, ...existingCoordinatorConfig };
|
|
24313
24540
|
copyFileSync4(mcpConfigPath, mcpConfigPath + ".backup");
|
|
24314
24541
|
} catch (error) {
|
|
24315
24542
|
LOG.error("MeshCoordinator", `Failed to parse existing MCP config ${mcpConfigPath}: ${error?.message || error}`);
|