@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
|
@@ -114,6 +114,7 @@ export declare class ProviderCliAdapter implements CliAdapter {
|
|
|
114
114
|
private getParseScreenText;
|
|
115
115
|
private shouldReadTerminalScreenSnapshot;
|
|
116
116
|
private resetTerminalScreen;
|
|
117
|
+
private getAccumulatedRawBufferCacheKey;
|
|
117
118
|
private getFreshParsedStatusCache;
|
|
118
119
|
private providerOwnsTranscript;
|
|
119
120
|
private shouldUseFullProviderTranscriptContext;
|
|
@@ -90,6 +90,7 @@ export declare class DaemonCommandRouter {
|
|
|
90
90
|
private removeInlineMeshNode;
|
|
91
91
|
private normalizeMeshSessionCleanupMode;
|
|
92
92
|
private sessionMatchesMeshNode;
|
|
93
|
+
private cleanupLocalWorktreeNode;
|
|
93
94
|
private isCompletedHostedSession;
|
|
94
95
|
private cleanupMeshSessions;
|
|
95
96
|
private traceSessionHostAction;
|
|
@@ -31,6 +31,10 @@ export interface WorktreeEntry {
|
|
|
31
31
|
branch: string | null;
|
|
32
32
|
bare: boolean;
|
|
33
33
|
}
|
|
34
|
+
export interface WorktreeRemoveOptions {
|
|
35
|
+
/** Refuse to remove a worktree with uncommitted or untracked changes. */
|
|
36
|
+
requireClean?: boolean;
|
|
37
|
+
}
|
|
34
38
|
export interface WorktreeRemoveResult {
|
|
35
39
|
success: true;
|
|
36
40
|
removedPath: string;
|
|
@@ -49,9 +53,9 @@ export declare function createWorktree(opts: WorktreeCreateOptions): Promise<Wor
|
|
|
49
53
|
/**
|
|
50
54
|
* Remove a git worktree and clean up the directory.
|
|
51
55
|
*
|
|
52
|
-
* Runs: git worktree remove <worktreePath>
|
|
56
|
+
* Runs: git worktree remove <worktreePath>
|
|
53
57
|
*/
|
|
54
|
-
export declare function removeWorktree(repoRoot: string, worktreePath: string): Promise<WorktreeRemoveResult>;
|
|
58
|
+
export declare function removeWorktree(repoRoot: string, worktreePath: string, opts?: WorktreeRemoveOptions): Promise<WorktreeRemoveResult>;
|
|
55
59
|
/**
|
|
56
60
|
* List all worktrees for a repository.
|
|
57
61
|
*
|
package/dist/index.js
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 (!(0, import_node_fs2.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,
|
|
@@ -685,6 +697,7 @@ function buildRulesSection(coordinatorCliType) {
|
|
|
685
697
|
- **Respect node capabilities.** Don't send build tasks to read-only nodes. Don't push from nodes that aren't allowed to.
|
|
686
698
|
- **Never fabricate tool results.** Always call the actual tool; never pretend you did.
|
|
687
699
|
- **Clean up worktree nodes.** After a worktree task completes and its changes are merged or checkpointed, call \`mesh_remove_node\` to free resources.
|
|
700
|
+
- **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.
|
|
688
701
|
- **Name worktree branches meaningfully.** Use descriptive names like \`feat/auth-refactor\` or \`fix/build-123\`.${coordinatorNote}`;
|
|
689
702
|
}
|
|
690
703
|
var TOOLS_SECTION, TOOL_EXPOSURE_PREFLIGHT_SECTION, WORKFLOW_SECTION;
|
|
@@ -706,6 +719,7 @@ var init_coordinator_prompt = __esm({
|
|
|
706
719
|
| \`mesh_checkpoint\` | Create a git checkpoint on a node |
|
|
707
720
|
| \`mesh_approve\` | Approve/reject a pending agent action |
|
|
708
721
|
| \`mesh_clone_node\` | Create a worktree node for isolated parallel branch work |
|
|
722
|
+
| \`mesh_refine_node\` | Validate and merge a completed worktree node back into its base branch |
|
|
709
723
|
| \`mesh_remove_node\` | Remove a node (cleans up worktree if applicable) |`;
|
|
710
724
|
TOOL_EXPOSURE_PREFLIGHT_SECTION = `## Tool Exposure Preflight
|
|
711
725
|
|
|
@@ -722,8 +736,9 @@ Before doing any coordinator work, confirm that the actual callable tool list in
|
|
|
722
736
|
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\`.
|
|
723
737
|
5. **Verify** \u2014 When a task reports completion or git work is visible, call \`mesh_git_status\` to verify changes were made.
|
|
724
738
|
6. **Checkpoint** \u2014 Call \`mesh_checkpoint\` to save the work.
|
|
725
|
-
7. **
|
|
726
|
-
8. **
|
|
739
|
+
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.
|
|
740
|
+
8. **Clean up** \u2014 Remove worktree nodes via \`mesh_remove_node\` after their work is merged or no longer needed.
|
|
741
|
+
9. **Report** \u2014 Summarize what was done, what changed, any issues, and the branch convergence state.
|
|
727
742
|
|
|
728
743
|
## Failure Recovery
|
|
729
744
|
|
|
@@ -2770,6 +2785,7 @@ var init_provider_cli_adapter = __esm({
|
|
|
2770
2785
|
this.requirePromptEchoBeforeSubmit = resolvedConfig.requirePromptEchoBeforeSubmit;
|
|
2771
2786
|
this.providerResolutionMeta = resolvedConfig.providerResolutionMeta;
|
|
2772
2787
|
this.cliScripts = provider.scripts || {};
|
|
2788
|
+
this.scriptState = typeof this.cliScripts.createState === "function" ? this.cliScripts.createState() ?? null : null;
|
|
2773
2789
|
const scriptNames = listCliScriptNames(this.cliScripts);
|
|
2774
2790
|
if (scriptNames.length > 0) {
|
|
2775
2791
|
LOG.info("CLI", `[${this.cliType}] CLI scripts: [${scriptNames.join(", ")}]`);
|
|
@@ -2930,9 +2946,13 @@ ${lastSnapshot}`;
|
|
|
2930
2946
|
this.lastScreenChangeAt = 0;
|
|
2931
2947
|
this.lastScreenSnapshotReadAt = Number.NEGATIVE_INFINITY;
|
|
2932
2948
|
}
|
|
2949
|
+
getAccumulatedRawBufferCacheKey() {
|
|
2950
|
+
return this.accumulatedRawBuffer.replace(/\x1B\][^\x07]*(?:\x07|\x1B\\)/g, "").replace(/\x1B[P^_X][\s\S]*?(?:\x07|\x1B\\)/g, "").replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, "");
|
|
2951
|
+
}
|
|
2933
2952
|
getFreshParsedStatusCache() {
|
|
2934
2953
|
const cached = this.parsedStatusCache;
|
|
2935
|
-
|
|
2954
|
+
const accumulatedRawBufferKey = this.getAccumulatedRawBufferCacheKey();
|
|
2955
|
+
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) {
|
|
2936
2956
|
return cached.result;
|
|
2937
2957
|
}
|
|
2938
2958
|
return null;
|
|
@@ -3035,7 +3055,7 @@ ${lastSnapshot}`;
|
|
|
3035
3055
|
this.cliScripts = scripts;
|
|
3036
3056
|
this.parsedStatusCache = null;
|
|
3037
3057
|
this.parseErrorMessage = null;
|
|
3038
|
-
this.scriptState = typeof scripts.createState === "function" ? scripts.createState() : null;
|
|
3058
|
+
this.scriptState = typeof scripts.createState === "function" ? scripts.createState() ?? null : null;
|
|
3039
3059
|
const scriptNames = listCliScriptNames(scripts);
|
|
3040
3060
|
LOG.info("CLI", `[${this.cliType}] CLI scripts injected: [${scriptNames.join(", ")}]`);
|
|
3041
3061
|
}
|
|
@@ -4024,7 +4044,8 @@ ${lastSnapshot}`;
|
|
|
4024
4044
|
const screenText = this.readTerminalScreenText();
|
|
4025
4045
|
const parseScreenText = this.getParseScreenText(screenText);
|
|
4026
4046
|
const cached = this.parsedStatusCache;
|
|
4027
|
-
|
|
4047
|
+
const accumulatedRawBufferKey = this.getAccumulatedRawBufferCacheKey();
|
|
4048
|
+
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) {
|
|
4028
4049
|
return cached.result;
|
|
4029
4050
|
}
|
|
4030
4051
|
const parsed = this.runParseSession();
|
|
@@ -4052,7 +4073,7 @@ ${lastSnapshot}`;
|
|
|
4052
4073
|
currentTurnScope: this.currentTurnScope,
|
|
4053
4074
|
recentOutputBuffer: this.recentOutputBuffer,
|
|
4054
4075
|
accumulatedBuffer: this.accumulatedBuffer,
|
|
4055
|
-
|
|
4076
|
+
accumulatedRawBufferKey,
|
|
4056
4077
|
screenText: parseScreenText,
|
|
4057
4078
|
currentStatus: this.currentStatus,
|
|
4058
4079
|
activeModal: this.activeModal,
|
|
@@ -4077,7 +4098,7 @@ ${lastSnapshot}`;
|
|
|
4077
4098
|
scope: this.currentTurnScope,
|
|
4078
4099
|
runtimeSettings: this.runtimeSettings
|
|
4079
4100
|
});
|
|
4080
|
-
return await Promise.resolve(
|
|
4101
|
+
return await Promise.resolve(this.invokeCliScript(fn, {
|
|
4081
4102
|
...input,
|
|
4082
4103
|
args: args && typeof args === "object" ? { ...args } : {}
|
|
4083
4104
|
}));
|
|
@@ -16369,6 +16390,8 @@ function normalizeProviderSessionId(provider, providerSessionId) {
|
|
|
16369
16390
|
}
|
|
16370
16391
|
|
|
16371
16392
|
// src/providers/cli-provider-instance.ts
|
|
16393
|
+
var COMPLETED_FINALIZATION_RETRY_MS = 1e3;
|
|
16394
|
+
var COMPLETED_FINALIZATION_MAX_WAIT_MS = 3e4;
|
|
16372
16395
|
var IMAGE_MIME_EXTENSIONS = {
|
|
16373
16396
|
"image/png": ".png",
|
|
16374
16397
|
"image/jpeg": ".jpg",
|
|
@@ -16432,6 +16455,13 @@ function cleanupStaleMaterializedImages(dir) {
|
|
|
16432
16455
|
} catch {
|
|
16433
16456
|
}
|
|
16434
16457
|
}
|
|
16458
|
+
function hasNonEmptyCliModalButtons(activeModal) {
|
|
16459
|
+
const buttons = activeModal?.buttons;
|
|
16460
|
+
return Array.isArray(buttons) && buttons.some((button) => String(button || "").trim().length > 0);
|
|
16461
|
+
}
|
|
16462
|
+
function isCliGeneratingLikeStatus(status) {
|
|
16463
|
+
return status === "generating" || status === "streaming" || status === "long_generating" || status === "starting";
|
|
16464
|
+
}
|
|
16435
16465
|
function buildCliStructuredInputPrompt(input, options = {}) {
|
|
16436
16466
|
const promptParts = [];
|
|
16437
16467
|
const imageRefs = [];
|
|
@@ -16716,10 +16746,12 @@ var CliProviderInstance = class {
|
|
|
16716
16746
|
const mergedMessages = this.mergeConversationMessages(parsedMessages);
|
|
16717
16747
|
const canonicalBackedHistory = this.syncCanonicalSavedHistoryIfNeeded();
|
|
16718
16748
|
const dirName = this.workingDir.split("/").filter(Boolean).pop() || "session";
|
|
16749
|
+
const parsedChatStatus = typeof parsedStatus?.status === "string" && parsedStatus.status.trim() ? parsedStatus.status.trim() : void 0;
|
|
16750
|
+
const suppressStaleParsedBusyStatus = this.shouldSuppressStaleParsedBusyStatus(parsedStatus, adapterStatus);
|
|
16719
16751
|
if (parsedMessages.length > 0) {
|
|
16720
16752
|
const shouldSkipReplayPersist = this.suppressIdleHistoryReplay && adapterStatus.status === "idle" && parsedStatus?.status === "idle";
|
|
16721
16753
|
let messagesToSave = parsedMessages;
|
|
16722
|
-
if (
|
|
16754
|
+
if (!suppressStaleParsedBusyStatus && (parsedChatStatus === "generating" || parsedChatStatus === "long_generating")) {
|
|
16723
16755
|
const lastIdx = messagesToSave.length - 1;
|
|
16724
16756
|
if (lastIdx >= 0 && messagesToSave[lastIdx]?.role === "assistant") {
|
|
16725
16757
|
messagesToSave = messagesToSave.slice(0, lastIdx);
|
|
@@ -16753,6 +16785,7 @@ var CliProviderInstance = class {
|
|
|
16753
16785
|
summaryMetadata: this.summaryMetadata,
|
|
16754
16786
|
controlValues: this.controlValues
|
|
16755
16787
|
});
|
|
16788
|
+
const activeChatStatus = parseErrorMessage ? "error" : autoApproveActive && parsedStatus?.status === "waiting_approval" ? "generating" : adapterStatus.status !== "idle" ? visibleStatus : suppressStaleParsedBusyStatus ? visibleStatus : parsedChatStatus || visibleStatus;
|
|
16756
16789
|
return {
|
|
16757
16790
|
type: this.type,
|
|
16758
16791
|
name: this.provider.name,
|
|
@@ -16762,7 +16795,7 @@ var CliProviderInstance = class {
|
|
|
16762
16795
|
activeChat: {
|
|
16763
16796
|
id: `${this.type}_${this.workingDir}`,
|
|
16764
16797
|
title: parsedStatus?.title || dirName,
|
|
16765
|
-
status:
|
|
16798
|
+
status: activeChatStatus,
|
|
16766
16799
|
messages: mergedMessages,
|
|
16767
16800
|
activeModal: autoApproveActive ? null : parsedStatus?.activeModal ?? adapterStatus.activeModal,
|
|
16768
16801
|
inputContent: ""
|
|
@@ -16892,6 +16925,102 @@ var CliProviderInstance = class {
|
|
|
16892
16925
|
}
|
|
16893
16926
|
this.applyProviderResponse(parsed.payload, { phase: "immediate" });
|
|
16894
16927
|
}
|
|
16928
|
+
completionHasFinalAssistantMessage(messages) {
|
|
16929
|
+
const visibleMessages = (Array.isArray(messages) ? messages : []).filter((message) => isUserFacingChatMessage(message));
|
|
16930
|
+
const lastVisible = visibleMessages[visibleMessages.length - 1];
|
|
16931
|
+
const role = typeof lastVisible?.role === "string" ? lastVisible.role.trim().toLowerCase() : "";
|
|
16932
|
+
const content = lastVisible ? flattenContent(lastVisible.content).trim() : "";
|
|
16933
|
+
return role === "assistant" && !!content;
|
|
16934
|
+
}
|
|
16935
|
+
hasAdapterPendingResponse() {
|
|
16936
|
+
const adapterAny = this.adapter;
|
|
16937
|
+
if (adapterAny?.isWaitingForResponse === true) return true;
|
|
16938
|
+
if (adapterAny?.currentTurnScope) return true;
|
|
16939
|
+
try {
|
|
16940
|
+
if (typeof this.adapter.isProcessing === "function" && this.adapter.isProcessing()) return true;
|
|
16941
|
+
} catch {
|
|
16942
|
+
}
|
|
16943
|
+
try {
|
|
16944
|
+
const partial = typeof this.adapter.getPartialResponse === "function" ? this.adapter.getPartialResponse() : "";
|
|
16945
|
+
if (typeof partial === "string" && partial.trim()) return true;
|
|
16946
|
+
} catch {
|
|
16947
|
+
}
|
|
16948
|
+
return false;
|
|
16949
|
+
}
|
|
16950
|
+
shouldSuppressStaleParsedBusyStatus(parsedStatus, adapterStatus) {
|
|
16951
|
+
const parsedRawStatus = typeof parsedStatus?.status === "string" ? parsedStatus.status.trim() : "";
|
|
16952
|
+
const adapterRawStatus = typeof adapterStatus?.status === "string" ? adapterStatus.status.trim() : "";
|
|
16953
|
+
if (!isCliGeneratingLikeStatus(parsedRawStatus)) return false;
|
|
16954
|
+
if (adapterRawStatus !== "idle") return false;
|
|
16955
|
+
if (hasNonEmptyCliModalButtons(parsedStatus?.activeModal ?? parsedStatus?.modal)) return false;
|
|
16956
|
+
return !this.hasAdapterPendingResponse();
|
|
16957
|
+
}
|
|
16958
|
+
getCompletedFinalizationBlockReason(latestVisibleStatus) {
|
|
16959
|
+
if (latestVisibleStatus !== "idle") return `status:${latestVisibleStatus}`;
|
|
16960
|
+
const adapterAny = this.adapter;
|
|
16961
|
+
if (adapterAny?.isWaitingForResponse === true) return "adapter_waiting_for_response";
|
|
16962
|
+
if (adapterAny?.currentTurnScope) return "adapter_turn_scope_active";
|
|
16963
|
+
const partial = typeof this.adapter.getPartialResponse === "function" ? this.adapter.getPartialResponse() : "";
|
|
16964
|
+
if (typeof partial === "string" && partial.trim()) return "partial_response_pending";
|
|
16965
|
+
let parsed;
|
|
16966
|
+
try {
|
|
16967
|
+
parsed = this.adapter.getScriptParsedStatus();
|
|
16968
|
+
} catch (error) {
|
|
16969
|
+
return `parse_error:${error?.message || String(error)}`;
|
|
16970
|
+
}
|
|
16971
|
+
const parsedStatus = typeof parsed?.status === "string" ? parsed.status : "unknown";
|
|
16972
|
+
if (parsedStatus !== "idle") return `parsed_status:${parsedStatus}`;
|
|
16973
|
+
if (parsed?.activeModal || parsed?.modal) return "parsed_modal_active";
|
|
16974
|
+
if (!this.completionHasFinalAssistantMessage(parsed?.messages)) return "missing_final_assistant";
|
|
16975
|
+
return null;
|
|
16976
|
+
}
|
|
16977
|
+
scheduleCompletedDebounceFlush(delayMs) {
|
|
16978
|
+
if (this.completedDebounceTimer) clearTimeout(this.completedDebounceTimer);
|
|
16979
|
+
this.completedDebounceTimer = setTimeout(() => this.flushCompletedDebounceIfFinalized(), delayMs);
|
|
16980
|
+
}
|
|
16981
|
+
flushCompletedDebounceIfFinalized() {
|
|
16982
|
+
const pending = this.completedDebouncePending;
|
|
16983
|
+
if (!pending) {
|
|
16984
|
+
this.completedDebounceTimer = null;
|
|
16985
|
+
return;
|
|
16986
|
+
}
|
|
16987
|
+
const latestStatus = this.adapter.getStatus({ allowParse: false });
|
|
16988
|
+
const latestAutoApproveActive = latestStatus.status === "waiting_approval" && this.shouldAutoApprove();
|
|
16989
|
+
const latestVisibleStatus = latestAutoApproveActive ? "generating" : latestStatus.status;
|
|
16990
|
+
if (latestVisibleStatus !== "idle") {
|
|
16991
|
+
LOG.info("CLI", `[${this.type}] cancelled pending completed (resumed ${latestVisibleStatus})`);
|
|
16992
|
+
this.completedDebouncePending = null;
|
|
16993
|
+
this.completedDebounceTimer = null;
|
|
16994
|
+
return;
|
|
16995
|
+
}
|
|
16996
|
+
const blockReason = this.getCompletedFinalizationBlockReason(latestVisibleStatus);
|
|
16997
|
+
if (blockReason) {
|
|
16998
|
+
const waitedMs = Date.now() - pending.firstObservedAt;
|
|
16999
|
+
if (waitedMs < COMPLETED_FINALIZATION_MAX_WAIT_MS) {
|
|
17000
|
+
if (pending.loggedBlockReason !== blockReason) {
|
|
17001
|
+
LOG.info("CLI", `[${this.type}] waiting to emit completed until transcript finalizes (${blockReason})`);
|
|
17002
|
+
pending.loggedBlockReason = blockReason;
|
|
17003
|
+
}
|
|
17004
|
+
this.scheduleCompletedDebounceFlush(COMPLETED_FINALIZATION_RETRY_MS);
|
|
17005
|
+
return;
|
|
17006
|
+
}
|
|
17007
|
+
LOG.warn("CLI", `[${this.type}] suppressed completed event after ${waitedMs}ms without finalized assistant turn (${blockReason})`);
|
|
17008
|
+
this.completedDebouncePending = null;
|
|
17009
|
+
this.completedDebounceTimer = null;
|
|
17010
|
+
this.generatingStartedAt = 0;
|
|
17011
|
+
return;
|
|
17012
|
+
}
|
|
17013
|
+
LOG.info("CLI", `[${this.type}] completed in ${pending.duration}s`);
|
|
17014
|
+
this.pushEvent({
|
|
17015
|
+
event: "agent:generating_completed",
|
|
17016
|
+
chatTitle: pending.chatTitle,
|
|
17017
|
+
duration: pending.duration,
|
|
17018
|
+
timestamp: pending.timestamp
|
|
17019
|
+
});
|
|
17020
|
+
this.completedDebouncePending = null;
|
|
17021
|
+
this.completedDebounceTimer = null;
|
|
17022
|
+
this.generatingStartedAt = 0;
|
|
17023
|
+
}
|
|
16895
17024
|
maybeAutoApproveStatus(adapterStatus, now = Date.now()) {
|
|
16896
17025
|
const autoApproveActive = adapterStatus?.status === "waiting_approval" && this.shouldAutoApprove();
|
|
16897
17026
|
if (autoApproveActive && !this.autoApproveBusy) {
|
|
@@ -16989,26 +17118,8 @@ var CliProviderInstance = class {
|
|
|
16989
17118
|
this.generatingDebouncePending = null;
|
|
16990
17119
|
this.generatingStartedAt = 0;
|
|
16991
17120
|
} else {
|
|
16992
|
-
|
|
16993
|
-
this.
|
|
16994
|
-
this.completedDebounceTimer = setTimeout(() => {
|
|
16995
|
-
if (this.completedDebouncePending) {
|
|
16996
|
-
const latestStatus = this.adapter.getStatus({ allowParse: false });
|
|
16997
|
-
const latestAutoApproveActive = latestStatus.status === "waiting_approval" && this.shouldAutoApprove();
|
|
16998
|
-
const latestVisibleStatus = latestAutoApproveActive ? "generating" : latestStatus.status;
|
|
16999
|
-
if (latestVisibleStatus !== "idle") {
|
|
17000
|
-
LOG.info("CLI", `[${this.type}] cancelled pending completed (resumed ${latestVisibleStatus})`);
|
|
17001
|
-
this.completedDebouncePending = null;
|
|
17002
|
-
this.completedDebounceTimer = null;
|
|
17003
|
-
return;
|
|
17004
|
-
}
|
|
17005
|
-
LOG.info("CLI", `[${this.type}] completed in ${this.completedDebouncePending.duration}s`);
|
|
17006
|
-
this.pushEvent({ event: "agent:generating_completed", ...this.completedDebouncePending });
|
|
17007
|
-
this.completedDebouncePending = null;
|
|
17008
|
-
this.generatingStartedAt = 0;
|
|
17009
|
-
}
|
|
17010
|
-
this.completedDebounceTimer = null;
|
|
17011
|
-
}, 3e3);
|
|
17121
|
+
this.completedDebouncePending = { chatTitle, duration, timestamp: now, firstObservedAt: now };
|
|
17122
|
+
this.scheduleCompletedDebounceFlush(3e3);
|
|
17012
17123
|
}
|
|
17013
17124
|
} else if (newStatus === "idle" && this.lastStatus === "starting") {
|
|
17014
17125
|
this.pushEvent({ event: "agent:ready", chatTitle, timestamp: now });
|
|
@@ -23006,6 +23117,34 @@ function loadHermesCoordinatorBaseConfig(targetConfigPath) {
|
|
|
23006
23117
|
const { mcp_servers: _mcpServers, ...baseConfig } = parsed;
|
|
23007
23118
|
return { config: baseConfig, sourceHome, sourceConfigPath };
|
|
23008
23119
|
}
|
|
23120
|
+
function stripHermesCoordinatorTempModelProviderOverrides(config) {
|
|
23121
|
+
const {
|
|
23122
|
+
model: _model,
|
|
23123
|
+
provider: _provider,
|
|
23124
|
+
default_model: _defaultModel,
|
|
23125
|
+
defaultProvider: _defaultProvider,
|
|
23126
|
+
default_provider: _defaultProviderSnake,
|
|
23127
|
+
modelProvider: _modelProvider,
|
|
23128
|
+
model_provider: _modelProviderSnake,
|
|
23129
|
+
...sanitized
|
|
23130
|
+
} = config;
|
|
23131
|
+
const delegation = sanitized.delegation;
|
|
23132
|
+
if (delegation && typeof delegation === "object" && !Array.isArray(delegation)) {
|
|
23133
|
+
const {
|
|
23134
|
+
model: _delegationModel,
|
|
23135
|
+
provider: _delegationProvider,
|
|
23136
|
+
modelProvider: _delegationModelProvider,
|
|
23137
|
+
model_provider: _delegationModelProviderSnake,
|
|
23138
|
+
...delegationRest
|
|
23139
|
+
} = delegation;
|
|
23140
|
+
if (Object.keys(delegationRest).length > 0) {
|
|
23141
|
+
sanitized.delegation = delegationRest;
|
|
23142
|
+
} else {
|
|
23143
|
+
delete sanitized.delegation;
|
|
23144
|
+
}
|
|
23145
|
+
}
|
|
23146
|
+
return sanitized;
|
|
23147
|
+
}
|
|
23009
23148
|
function copyHermesCoordinatorCredentialFiles(sourceHome, targetHome) {
|
|
23010
23149
|
if ((0, import_path6.resolve)(sourceHome) === (0, import_path6.resolve)(targetHome)) return;
|
|
23011
23150
|
for (const fileName of [".env", "auth.json"]) {
|
|
@@ -23163,6 +23302,89 @@ var DaemonCommandRouter = class {
|
|
|
23163
23302
|
if (record?.meta?.meshNodeId === nodeId) return true;
|
|
23164
23303
|
return false;
|
|
23165
23304
|
}
|
|
23305
|
+
async cleanupLocalWorktreeNode(args) {
|
|
23306
|
+
const workspace = typeof args.node?.workspace === "string" ? args.node.workspace.trim() : "";
|
|
23307
|
+
if (!workspace) {
|
|
23308
|
+
return {
|
|
23309
|
+
success: false,
|
|
23310
|
+
code: "mesh_worktree_cleanup_missing_workspace",
|
|
23311
|
+
error: `Worktree node '${args.nodeId}' is missing workspace metadata`,
|
|
23312
|
+
recoveryHint: "Inspect the mesh node record before removing it, or remove stale metadata manually only after confirming no managed worktree remains."
|
|
23313
|
+
};
|
|
23314
|
+
}
|
|
23315
|
+
const worktreeExists = fs10.existsSync(workspace);
|
|
23316
|
+
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);
|
|
23317
|
+
const repoRoot = typeof sourceNode?.repoRoot === "string" && sourceNode.repoRoot.trim() ? sourceNode.repoRoot.trim() : typeof sourceNode?.workspace === "string" && sourceNode.workspace.trim() ? sourceNode.workspace.trim() : "";
|
|
23318
|
+
if (!worktreeExists) {
|
|
23319
|
+
return { success: true, skipped: true, removedPath: workspace, repoRoot: repoRoot || void 0, reason: "worktree_path_missing" };
|
|
23320
|
+
}
|
|
23321
|
+
if (!repoRoot || !fs10.existsSync(repoRoot)) {
|
|
23322
|
+
return {
|
|
23323
|
+
success: false,
|
|
23324
|
+
code: "mesh_worktree_cleanup_missing_source_repo",
|
|
23325
|
+
error: `Refusing to remove worktree '${workspace}' because the source repo root is unavailable`,
|
|
23326
|
+
recoveryHint: "Run mesh_remove_node from the machine that owns the source repo, or verify the source node metadata before retrying."
|
|
23327
|
+
};
|
|
23328
|
+
}
|
|
23329
|
+
if (typeof args.node?.worktreeBranch !== "string" || !args.node.worktreeBranch.trim()) {
|
|
23330
|
+
return {
|
|
23331
|
+
success: false,
|
|
23332
|
+
code: "mesh_worktree_cleanup_missing_branch",
|
|
23333
|
+
error: `Refusing to remove worktree '${workspace}' because worktreeBranch metadata is missing`,
|
|
23334
|
+
recoveryHint: "Confirm this is an ADHDev-managed worktree before removing it manually; managed worktree nodes include worktreeBranch metadata."
|
|
23335
|
+
};
|
|
23336
|
+
}
|
|
23337
|
+
const { resolveWorktreePath: resolveWorktreePath2, listWorktrees: listWorktrees2, removeWorktree: removeWorktree2 } = await Promise.resolve().then(() => (init_git_worktree(), git_worktree_exports));
|
|
23338
|
+
const normalizePath = (value) => {
|
|
23339
|
+
const resolved = (0, import_path6.resolve)(value);
|
|
23340
|
+
try {
|
|
23341
|
+
return fs10.realpathSync(resolved);
|
|
23342
|
+
} catch {
|
|
23343
|
+
return resolved;
|
|
23344
|
+
}
|
|
23345
|
+
};
|
|
23346
|
+
const expectedPath = normalizePath(resolveWorktreePath2(repoRoot, String(args.mesh?.name || args.mesh?.id || "mesh"), args.node.worktreeBranch));
|
|
23347
|
+
const actualPath = normalizePath(workspace);
|
|
23348
|
+
if (actualPath !== expectedPath) {
|
|
23349
|
+
return {
|
|
23350
|
+
success: false,
|
|
23351
|
+
code: "mesh_worktree_cleanup_unexpected_path",
|
|
23352
|
+
error: `Refusing to remove worktree '${workspace}' because it is not at the expected managed path '${expectedPath}'`,
|
|
23353
|
+
recoveryHint: "Use git worktree list/status to inspect the path. Retry only after confirming the mesh node metadata points to an ADHDev-managed worktree."
|
|
23354
|
+
};
|
|
23355
|
+
}
|
|
23356
|
+
const entries = await listWorktrees2(repoRoot);
|
|
23357
|
+
const managedEntry = entries.find((entry) => normalizePath(entry.path) === actualPath);
|
|
23358
|
+
if (!managedEntry) {
|
|
23359
|
+
return {
|
|
23360
|
+
success: false,
|
|
23361
|
+
code: "mesh_worktree_cleanup_not_registered",
|
|
23362
|
+
error: `Refusing to remove '${workspace}' because it is not registered in git worktree list for '${repoRoot}'`,
|
|
23363
|
+
recoveryHint: "Inspect git worktree list --porcelain from the source repo. If the path was already removed, prune git worktrees before retrying."
|
|
23364
|
+
};
|
|
23365
|
+
}
|
|
23366
|
+
if (managedEntry.branch && managedEntry.branch !== args.node.worktreeBranch) {
|
|
23367
|
+
return {
|
|
23368
|
+
success: false,
|
|
23369
|
+
code: "mesh_worktree_cleanup_branch_mismatch",
|
|
23370
|
+
error: `Refusing to remove '${workspace}' because git reports branch '${managedEntry.branch}', expected '${args.node.worktreeBranch}'`,
|
|
23371
|
+
recoveryHint: "Inspect the worktree branch and mesh metadata before retrying cleanup."
|
|
23372
|
+
};
|
|
23373
|
+
}
|
|
23374
|
+
try {
|
|
23375
|
+
const result = await removeWorktree2(repoRoot, workspace, { requireClean: true });
|
|
23376
|
+
return { success: true, removedPath: result.removedPath, repoRoot };
|
|
23377
|
+
} catch (e) {
|
|
23378
|
+
const message = String(e?.message || e || "worktree cleanup failed");
|
|
23379
|
+
const dirty = message.includes("dirty worktree") || message.includes("local changes");
|
|
23380
|
+
return {
|
|
23381
|
+
success: false,
|
|
23382
|
+
code: dirty ? "mesh_worktree_cleanup_dirty" : "mesh_worktree_cleanup_failed",
|
|
23383
|
+
error: message,
|
|
23384
|
+
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."
|
|
23385
|
+
};
|
|
23386
|
+
}
|
|
23387
|
+
}
|
|
23166
23388
|
isCompletedHostedSession(record) {
|
|
23167
23389
|
return record?.lifecycle === "stopped" || record?.lifecycle === "failed" || record?.lifecycle === "interrupted";
|
|
23168
23390
|
}
|
|
@@ -24163,17 +24385,21 @@ var DaemonCommandRouter = class {
|
|
|
24163
24385
|
sessionCleanup = await this.cleanupMeshSessions({ meshId, nodeId, node, mode: sessionCleanupMode });
|
|
24164
24386
|
if (sessionCleanup.success === false) return { success: false, removed: false, sessionCleanup };
|
|
24165
24387
|
}
|
|
24166
|
-
|
|
24167
|
-
|
|
24168
|
-
|
|
24169
|
-
|
|
24170
|
-
|
|
24171
|
-
|
|
24172
|
-
|
|
24173
|
-
|
|
24174
|
-
|
|
24175
|
-
|
|
24388
|
+
let worktreeCleanup;
|
|
24389
|
+
if (node?.isLocalWorktree) {
|
|
24390
|
+
const cleanupResult = await this.cleanupLocalWorktreeNode({ mesh, node, nodeId });
|
|
24391
|
+
if (cleanupResult.success === false) {
|
|
24392
|
+
return {
|
|
24393
|
+
success: false,
|
|
24394
|
+
removed: false,
|
|
24395
|
+
code: cleanupResult.code,
|
|
24396
|
+
error: cleanupResult.error,
|
|
24397
|
+
recoveryHint: cleanupResult.recoveryHint,
|
|
24398
|
+
...sessionCleanup ? { sessionCleanup } : {},
|
|
24399
|
+
worktreeCleanup: cleanupResult
|
|
24400
|
+
};
|
|
24176
24401
|
}
|
|
24402
|
+
worktreeCleanup = cleanupResult;
|
|
24177
24403
|
}
|
|
24178
24404
|
let removed = false;
|
|
24179
24405
|
if (meshRecord?.inline) {
|
|
@@ -24199,7 +24425,7 @@ var DaemonCommandRouter = class {
|
|
|
24199
24425
|
} catch {
|
|
24200
24426
|
}
|
|
24201
24427
|
}
|
|
24202
|
-
return { success: true, removed, ...sessionCleanup ? { sessionCleanup } : {} };
|
|
24428
|
+
return { success: true, removed, ...sessionCleanup ? { sessionCleanup } : {}, ...worktreeCleanup ? { worktreeCleanup } : {} };
|
|
24203
24429
|
} catch (e) {
|
|
24204
24430
|
return { success: false, error: e.message };
|
|
24205
24431
|
}
|
|
@@ -24529,7 +24755,8 @@ ${block}`);
|
|
|
24529
24755
|
if (hadExistingMcpConfig) {
|
|
24530
24756
|
try {
|
|
24531
24757
|
const parsedExistingMcpConfig = parseMeshCoordinatorMcpConfig(readFileSync17(mcpConfigPath, "utf-8"), configFormat);
|
|
24532
|
-
|
|
24758
|
+
const existingCoordinatorConfig = hermesManualFallback ? stripHermesCoordinatorTempModelProviderOverrides(parsedExistingMcpConfig) : parsedExistingMcpConfig;
|
|
24759
|
+
existingMcpConfig = { ...existingMcpConfig, ...existingCoordinatorConfig };
|
|
24533
24760
|
copyFileSync4(mcpConfigPath, mcpConfigPath + ".backup");
|
|
24534
24761
|
} catch (error) {
|
|
24535
24762
|
LOG.error("MeshCoordinator", `Failed to parse existing MCP config ${mcpConfigPath}: ${error?.message || error}`);
|