@adhdev/daemon-core 0.9.77-rc.5 → 0.9.77-rc.51
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/boot/daemon-lifecycle.d.ts +3 -0
- package/dist/cli-adapters/provider-cli-adapter.d.ts +4 -0
- package/dist/cli-adapters/provider-cli-shared.d.ts +14 -4
- package/dist/commands/mesh-coordinator.d.ts +10 -0
- package/dist/commands/router.d.ts +4 -1
- package/dist/config/mesh-config.d.ts +1 -0
- package/dist/git/git-worktree.d.ts +15 -2
- package/dist/index.d.ts +10 -5
- package/dist/index.js +2013 -299
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1998 -299
- package/dist/index.mjs.map +1 -1
- package/dist/mesh/mesh-events.d.ts +14 -7
- package/dist/mesh/mesh-ledger-reconciliation.d.ts +55 -0
- package/dist/mesh/mesh-ledger.d.ts +84 -4
- package/dist/mesh/mesh-sync.d.ts +4 -12
- package/dist/mesh/mesh-work-queue.d.ts +58 -1
- package/dist/mesh/p2p-relay-failure.d.ts +35 -0
- package/dist/providers/chat-message-normalization.d.ts +1 -0
- package/dist/providers/cli-provider-instance.d.ts +6 -0
- package/dist/repo-mesh-types.d.ts +2 -0
- package/dist/shared-types.d.ts +38 -0
- package/package.json +1 -1
- package/src/boot/daemon-lifecycle.ts +5 -0
- package/src/cli-adapters/provider-cli-adapter.ts +35 -4
- package/src/cli-adapters/provider-cli-shared.ts +14 -4
- package/src/commands/cli-manager.ts +0 -4
- package/src/commands/mesh-coordinator.ts +55 -7
- package/src/commands/router.ts +847 -26
- package/src/commands/stream-commands.ts +8 -1
- package/src/config/config.ts +2 -1
- package/src/config/mesh-config.ts +2 -0
- package/src/config/workspaces.ts +1 -1
- package/src/git/git-worktree.ts +56 -4
- package/src/index.d.ts +3 -0
- package/src/index.ts +22 -5
- package/src/mesh/coordinator-prompt.ts +21 -10
- package/src/mesh/mesh-events.ts +532 -22
- package/src/mesh/mesh-ledger-reconciliation.ts +115 -0
- package/src/mesh/mesh-ledger.ts +209 -8
- package/src/mesh/mesh-sync.ts +4 -34
- package/src/mesh/mesh-work-queue.ts +183 -17
- package/src/mesh/p2p-relay-failure.ts +152 -0
- package/src/providers/acp-provider-instance.ts +2 -1
- package/src/providers/chat-message-normalization.ts +32 -0
- package/src/providers/cli-provider-instance.ts +155 -31
- package/src/providers/extension-provider-instance.ts +2 -1
- package/src/providers/ide-provider-instance.ts +2 -2
- package/src/repo-mesh-types.ts +2 -0
- package/src/shared-types.ts +38 -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,
|
|
@@ -111,7 +123,33 @@ async function removeWorktree(repoRoot, worktreePath) {
|
|
|
111
123
|
});
|
|
112
124
|
} catch (error) {
|
|
113
125
|
const stderr = typeof error.stderr === "string" ? error.stderr : "";
|
|
114
|
-
|
|
126
|
+
const stdout = typeof error.stdout === "string" ? error.stdout : "";
|
|
127
|
+
const detail = `${stderr}
|
|
128
|
+
${stdout}
|
|
129
|
+
${error.message || ""}`;
|
|
130
|
+
if (opts.allowSubmoduleForceFallback && SUBMODULE_WORKTREE_REMOVE_RE.test(detail)) {
|
|
131
|
+
try {
|
|
132
|
+
await execFileAsync2("git", ["worktree", "remove", "--force", worktreePath], {
|
|
133
|
+
cwd: repoRoot,
|
|
134
|
+
encoding: "utf8",
|
|
135
|
+
timeout: GIT_TIMEOUT_MS,
|
|
136
|
+
maxBuffer: GIT_MAX_BUFFER,
|
|
137
|
+
windowsHide: true
|
|
138
|
+
});
|
|
139
|
+
} catch (forceError) {
|
|
140
|
+
const forceStderr = typeof forceError.stderr === "string" ? forceError.stderr : "";
|
|
141
|
+
const forceStdout = typeof forceError.stdout === "string" ? forceError.stdout : "";
|
|
142
|
+
throw new Error(`git worktree remove --force fallback failed: ${forceStderr.trim() || forceStdout.trim() || forceError.message}`);
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
success: true,
|
|
146
|
+
removedPath: worktreePath,
|
|
147
|
+
fallback: "git_worktree_remove_force_submodule",
|
|
148
|
+
forced: true,
|
|
149
|
+
reason: "working_trees_containing_submodules"
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
throw new Error(`git worktree remove failed: ${stderr.trim() || stdout.trim() || error.message}`);
|
|
115
153
|
}
|
|
116
154
|
return { success: true, removedPath: worktreePath };
|
|
117
155
|
}
|
|
@@ -161,7 +199,7 @@ async function pruneWorktrees(repoRoot) {
|
|
|
161
199
|
} catch {
|
|
162
200
|
}
|
|
163
201
|
}
|
|
164
|
-
var execFileAsync2, WORKTREE_DIR_NAME, GIT_TIMEOUT_MS, GIT_MAX_BUFFER;
|
|
202
|
+
var execFileAsync2, WORKTREE_DIR_NAME, GIT_TIMEOUT_MS, GIT_MAX_BUFFER, SUBMODULE_WORKTREE_REMOVE_RE;
|
|
165
203
|
var init_git_worktree = __esm({
|
|
166
204
|
"src/git/git-worktree.ts"() {
|
|
167
205
|
"use strict";
|
|
@@ -169,6 +207,7 @@ var init_git_worktree = __esm({
|
|
|
169
207
|
WORKTREE_DIR_NAME = ".adhdev-worktrees";
|
|
170
208
|
GIT_TIMEOUT_MS = 3e4;
|
|
171
209
|
GIT_MAX_BUFFER = 4 * 1024 * 1024;
|
|
210
|
+
SUBMODULE_WORKTREE_REMOVE_RE = /working trees containing submodules cannot be moved or removed/i;
|
|
172
211
|
}
|
|
173
212
|
});
|
|
174
213
|
|
|
@@ -282,7 +321,8 @@ function ensureMachineId(config) {
|
|
|
282
321
|
};
|
|
283
322
|
}
|
|
284
323
|
function getConfigDir() {
|
|
285
|
-
const
|
|
324
|
+
const override = process.env.ADHDEV_CONFIG_DIR;
|
|
325
|
+
const dir = override && override.trim() ? override.trim() : join2(homedir(), ".adhdev");
|
|
286
326
|
if (!existsSync2(dir)) {
|
|
287
327
|
mkdirSync(dir, { recursive: true });
|
|
288
328
|
}
|
|
@@ -544,6 +584,7 @@ function addNode(meshId, opts) {
|
|
|
544
584
|
workspace: opts.workspace.trim(),
|
|
545
585
|
repoRoot: opts.repoRoot,
|
|
546
586
|
daemonId: opts.daemonId,
|
|
587
|
+
machineId: opts.machineId,
|
|
547
588
|
userOverrides: opts.userOverrides || {},
|
|
548
589
|
policy: opts.policy || {},
|
|
549
590
|
isLocalWorktree: opts.isLocalWorktree,
|
|
@@ -671,7 +712,8 @@ function buildRulesSection(coordinatorCliType) {
|
|
|
671
712
|
- **Minimize coordinator context.** The coordinator's job is routing, not implementing. Do not read source files, run commands, or analyze code directly \u2014 delegate all of that to node agents. Your context should stay lean.
|
|
672
713
|
- **Delegate analysis too.** If you need to understand a bug or explore the codebase, send that investigation as a task to the queue or a node. Do not do it yourself.
|
|
673
714
|
- **Respect explicit provider requests.** If the user names an agent/provider, pass the matching provider type to \`mesh_launch_session\`: Hermes \u2192 \`hermes-cli\`, Claude Code/Claude \u2192 \`claude-cli\`, Codex \u2192 \`codex-cli\`, Gemini \u2192 \`gemini-cli\`. Never substitute \`claude-cli\` just because the coordinator itself is Claude Code.
|
|
674
|
-
- **Front-load
|
|
715
|
+
- **Front-load new task messages.** When calling \`mesh_enqueue_task\` or \`mesh_send_task\` for a new task, include everything the agent needs: what files to touch, what the problem is, what the fix should look like. The agent won't ask follow-up questions.
|
|
716
|
+
- **Avoid context-wasting restarts.** For follow-up, retry, commit/push, preview, or cleanup work on the same issue, prefer the existing idle session and send only the delta from its last verified state. Start a fresh chat/session only for genuinely independent work, explicit provider/user request, unsafe transcript contamination, or required branch/worktree isolation.
|
|
675
717
|
- **Don't inspect code.** Treat delegated agent summaries as self-reports, not verification. Verify side effects via \`mesh_git_status\` (including related repo freshness when configured), not by reading source files.
|
|
676
718
|
- **Don't over-parallelize.** Start with 1-2 concurrent tasks. Scale up if they succeed. Never launch a duplicate session or second worker solely because \`mesh_read_chat\` has no final assistant message while the delegated session is still showing tool/terminal activity.
|
|
677
719
|
- **Handle failures with context.** If a task fails, check \`mesh_task_history\` first to see if this task was attempted before and how it failed. Read the chat to understand why, then decide: retry on the same node, reassign to a different node, or escalate to the user.
|
|
@@ -680,6 +722,7 @@ function buildRulesSection(coordinatorCliType) {
|
|
|
680
722
|
- **Respect node capabilities.** Don't send build tasks to read-only nodes. Don't push from nodes that aren't allowed to.
|
|
681
723
|
- **Never fabricate tool results.** Always call the actual tool; never pretend you did.
|
|
682
724
|
- **Clean up worktree nodes.** After a worktree task completes and its changes are merged or checkpointed, call \`mesh_remove_node\` to free resources.
|
|
725
|
+
- **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
726
|
- **Name worktree branches meaningfully.** Use descriptive names like \`feat/auth-refactor\` or \`fix/build-123\`.${coordinatorNote}`;
|
|
684
727
|
}
|
|
685
728
|
var TOOLS_SECTION, TOOL_EXPOSURE_PREFLIGHT_SECTION, WORKFLOW_SECTION;
|
|
@@ -691,17 +734,24 @@ var init_coordinator_prompt = __esm({
|
|
|
691
734
|
|
|
692
735
|
| Tool | Purpose |
|
|
693
736
|
|------|---------|
|
|
694
|
-
| \`mesh_status\` | Check all nodes' health, git state,
|
|
737
|
+
| \`mesh_status\` | Check all nodes' health, git state, active sessions, and branch convergence |
|
|
695
738
|
| \`mesh_list_nodes\` | List nodes with workspace paths |
|
|
739
|
+
| \`mesh_enqueue_task\` | Add a task to the pull-based work queue; idle nodes auto-claim |
|
|
740
|
+
| \`mesh_view_queue\` | View queue status \u2014 pending, assigned, completed, failed, cancelled tasks |
|
|
741
|
+
| \`mesh_queue_cancel\` | Cancel a queue task without deleting audit history |
|
|
742
|
+
| \`mesh_queue_requeue\` | Return a task to pending for retry; clears stale session targets |
|
|
743
|
+
| \`mesh_send_task\` | Legacy push: enqueue a task targeted at a specific node |
|
|
696
744
|
| \`mesh_launch_session\` | Start a new agent session on a node |
|
|
697
|
-
| \`
|
|
698
|
-
| \`
|
|
745
|
+
| \`mesh_read_chat\` | Read recent chat messages from a delegated agent session |
|
|
746
|
+
| \`mesh_read_debug\` | Collect a daemon-side chat/parser debug bundle for a session |
|
|
699
747
|
| \`mesh_task_history\` | Read the task ledger \u2014 dispatches, completions, failures. Use to understand what has been done before deciding next steps |
|
|
700
748
|
| \`mesh_git_status\` | Check git status on a specific node |
|
|
701
749
|
| \`mesh_checkpoint\` | Create a git checkpoint on a node |
|
|
702
750
|
| \`mesh_approve\` | Approve/reject a pending agent action |
|
|
703
751
|
| \`mesh_clone_node\` | Create a worktree node for isolated parallel branch work |
|
|
704
|
-
| \`
|
|
752
|
+
| \`mesh_refine_node\` | Validate and merge a completed worktree node back into its base branch |
|
|
753
|
+
| \`mesh_remove_node\` | Remove a node (cleans up worktree if applicable) |
|
|
754
|
+
| \`mesh_cleanup_sessions\` | Manually clean up delegated session records for a node |`;
|
|
705
755
|
TOOL_EXPOSURE_PREFLIGHT_SECTION = `## Tool Exposure Preflight
|
|
706
756
|
|
|
707
757
|
Before doing any coordinator work, confirm that the actual callable tool list includes \`mesh_status\` and the other \`mesh_*\` tools from the table above. If this Repo Mesh coordinator prompt is present but the callable \`mesh_*\` tools are missing, the MCP server/tool manifest is stale or not injected yet. Do not substitute terminal/file/git tools, do not inspect or edit the repository directly, and do not continue as a non-mesh local coding agent. Stop immediately and tell the user to run \`/reload-mcp\` or start a fresh coordinator session so ADHDev can reconnect \`adhdev-mesh\`.`;
|
|
@@ -711,14 +761,16 @@ Before doing any coordinator work, confirm that the actual callable tool list in
|
|
|
711
761
|
2. **Plan** \u2014 Decompose the user's request into independent tasks for parallel execution, or sequential tasks when dependencies exist. If \`mesh_task_history\` shows a recent failure for a task, decide whether to retry or reassign.
|
|
712
762
|
3. **Queue / Delegate** \u2014 The Mesh uses an autonomous pull-based Work Queue:
|
|
713
763
|
a. **General Tasks**: Enqueue tasks using \`mesh_enqueue_task\`. Idle node agents will automatically pull tasks from the queue and begin working.
|
|
714
|
-
b. **Node Preparation**: Call \`mesh_launch_session\`
|
|
764
|
+
b. **Node Preparation**: Reuse an existing idle session on the correct node/provider before launching a new chat/session. Call \`mesh_launch_session\` only when no suitable session exists, when the user explicitly asks for a fresh provider/session, or when branch/worktree isolation requires it. If you need branch isolation for parallel work, call \`mesh_clone_node\` to create a worktree node first.
|
|
715
765
|
c. **Targeted Tasks**: Use \`mesh_send_task\` only when you need to bypass the queue and force a specific node to execute a task immediately.
|
|
716
|
-
d.
|
|
766
|
+
d. For the first dispatch of a new task, provide a **complete, self-contained** instruction that includes all context the agent needs (file paths, line numbers, what to change, why). Do not send partial instructions expecting future follow-up.
|
|
767
|
+
e. For a continuation of the same issue in an existing session, send a concise **delta instruction**: current verified state, the exact failed/blocked step, the newly approved action, and final reporting requirements. Do not resend the full original task or open a new chat solely to continue the same work; that wastes coordinator and worker context.
|
|
717
768
|
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
769
|
5. **Verify** \u2014 When a task reports completion or git work is visible, call \`mesh_git_status\` to verify changes were made.
|
|
719
770
|
6. **Checkpoint** \u2014 Call \`mesh_checkpoint\` to save the work.
|
|
720
|
-
7. **
|
|
721
|
-
8. **
|
|
771
|
+
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.
|
|
772
|
+
8. **Clean up** \u2014 Remove worktree nodes via \`mesh_remove_node\` after their work is merged or no longer needed.
|
|
773
|
+
9. **Report** \u2014 Summarize what was done, what changed, any issues, and the branch convergence state.
|
|
722
774
|
|
|
723
775
|
## Failure Recovery
|
|
724
776
|
|
|
@@ -728,7 +780,7 @@ When a node agent stops unexpectedly, the daemon automatically enriches the syst
|
|
|
728
780
|
- A recommendation: **retry**, **reassign**, or **escalate**
|
|
729
781
|
|
|
730
782
|
Follow these recovery rules:
|
|
731
|
-
1. **If "Retry recommended"**:
|
|
783
|
+
1. **If "Retry recommended"**: Check \`mesh_view_queue\` first \u2014 the daemon may have auto-requeued. If not, re-launch the session on the same node (\`mesh_launch_session\`), then resend the original task (\`mesh_send_task\`). The system message includes the original task text.
|
|
732
784
|
2. **If "Max retries exceeded"**: Do NOT retry on the same node. Either reassign the task to a different node, or inform the user that the task requires manual intervention.
|
|
733
785
|
3. **If no recovery context**: The stop may be intentional (normal completion). Use \`mesh_read_chat\` once to verify, then move on.
|
|
734
786
|
4. **Always record what happened**: After handling a failure, briefly note the outcome in your report to the user.`;
|
|
@@ -738,18 +790,27 @@ Follow these recovery rules:
|
|
|
738
790
|
// src/mesh/mesh-ledger.ts
|
|
739
791
|
var mesh_ledger_exports = {};
|
|
740
792
|
__export(mesh_ledger_exports, {
|
|
793
|
+
MAX_LEDGER_SLICE_LIMIT: () => MAX_LEDGER_SLICE_LIMIT,
|
|
741
794
|
appendLedgerEntry: () => appendLedgerEntry,
|
|
742
795
|
appendRemoteLedgerEntries: () => appendRemoteLedgerEntries,
|
|
796
|
+
buildTaskCompletionEvidence: () => buildTaskCompletionEvidence,
|
|
743
797
|
getLedgerDir: () => getLedgerDir,
|
|
744
798
|
getLedgerSummary: () => getLedgerSummary,
|
|
745
799
|
getSessionRecoveryContext: () => getSessionRecoveryContext,
|
|
800
|
+
isIntentionalCleanupStopEntry: () => isIntentionalCleanupStopEntry,
|
|
746
801
|
meshLedgerEvents: () => meshLedgerEvents,
|
|
747
|
-
readLedgerEntries: () => readLedgerEntries
|
|
802
|
+
readLedgerEntries: () => readLedgerEntries,
|
|
803
|
+
readLedgerSlice: () => readLedgerSlice
|
|
748
804
|
});
|
|
749
805
|
import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync3, appendFileSync, statSync as statSync2, renameSync } from "fs";
|
|
750
806
|
import { join as join5 } from "path";
|
|
751
807
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
752
808
|
import { EventEmitter } from "events";
|
|
809
|
+
function isIntentionalCleanupStopEntry(entry) {
|
|
810
|
+
if (entry.kind !== "session_stopped" && entry.kind !== "task_failed" && entry.kind !== "task_stalled") return false;
|
|
811
|
+
const payload = entry.payload && typeof entry.payload === "object" && !Array.isArray(entry.payload) ? entry.payload : {};
|
|
812
|
+
return payload.intentional === true && (payload.reason === "operator_cleanup" || payload.intentionalStopReason === "operator_cleanup" || payload.source === "mesh_cleanup_sessions" || payload.source === "mesh_remove_node");
|
|
813
|
+
}
|
|
753
814
|
function getLedgerDir() {
|
|
754
815
|
const dir = join5(getConfigDir(), LEDGER_DIR_NAME);
|
|
755
816
|
if (!existsSync5(dir)) {
|
|
@@ -765,6 +826,37 @@ function getRotatedPath(meshId, index) {
|
|
|
765
826
|
const safe = meshId.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
766
827
|
return join5(getLedgerDir(), `${safe}.${index}.jsonl`);
|
|
767
828
|
}
|
|
829
|
+
function buildTaskCompletionEvidence(opts) {
|
|
830
|
+
const providerSessionId = opts.providerSessionId?.trim() || void 0;
|
|
831
|
+
const providerType = opts.providerType?.trim() || void 0;
|
|
832
|
+
return {
|
|
833
|
+
source: "agent_status_event",
|
|
834
|
+
event: opts.event,
|
|
835
|
+
nodeId: opts.nodeId,
|
|
836
|
+
sessionId: opts.sessionId,
|
|
837
|
+
providerType,
|
|
838
|
+
completedAt: opts.completedAt || (/* @__PURE__ */ new Date()).toISOString(),
|
|
839
|
+
transcriptHandle: {
|
|
840
|
+
kind: providerSessionId ? "provider_session" : "runtime_session",
|
|
841
|
+
sessionId: opts.sessionId,
|
|
842
|
+
providerSessionId,
|
|
843
|
+
finalSummaryAvailable: typeof opts.finalSummary === "string" && opts.finalSummary.trim().length > 0
|
|
844
|
+
},
|
|
845
|
+
git: {
|
|
846
|
+
status: "deferred",
|
|
847
|
+
reason: "ordinary_completion_git_status_not_checked"
|
|
848
|
+
},
|
|
849
|
+
validation: {
|
|
850
|
+
status: "deferred",
|
|
851
|
+
commandsRun: [],
|
|
852
|
+
reason: "ordinary_completion_validation_not_run"
|
|
853
|
+
},
|
|
854
|
+
checkpoint: {
|
|
855
|
+
attempted: false,
|
|
856
|
+
reason: "not_attempted_for_ordinary_completion"
|
|
857
|
+
}
|
|
858
|
+
};
|
|
859
|
+
}
|
|
768
860
|
function appendLedgerEntry(meshId, partial) {
|
|
769
861
|
const entry = {
|
|
770
862
|
id: randomUUID4(),
|
|
@@ -791,15 +883,49 @@ function appendLedgerEntry(meshId, partial) {
|
|
|
791
883
|
throw new Error(`Failed to append to ledger for mesh ${meshId}: ${e.message}`);
|
|
792
884
|
}
|
|
793
885
|
}
|
|
886
|
+
function clampLedgerSliceLimit(limit) {
|
|
887
|
+
if (typeof limit !== "number" || !Number.isFinite(limit)) return DEFAULT_LEDGER_SLICE_LIMIT;
|
|
888
|
+
return Math.max(1, Math.min(MAX_LEDGER_SLICE_LIMIT, Math.floor(limit)));
|
|
889
|
+
}
|
|
890
|
+
function isValidRemoteLedgerEntry(meshId, value) {
|
|
891
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
892
|
+
const entry = value;
|
|
893
|
+
if (typeof entry.id !== "string" || !entry.id.trim()) return false;
|
|
894
|
+
if (entry.meshId !== meshId) return false;
|
|
895
|
+
if (typeof entry.timestamp !== "string" || Number.isNaN(new Date(entry.timestamp).getTime())) return false;
|
|
896
|
+
if (typeof entry.kind !== "string" || !entry.kind.trim()) return false;
|
|
897
|
+
if (!entry.payload || typeof entry.payload !== "object" || Array.isArray(entry.payload)) return false;
|
|
898
|
+
return true;
|
|
899
|
+
}
|
|
794
900
|
function appendRemoteLedgerEntries(meshId, entries) {
|
|
795
|
-
if (entries.length === 0) return;
|
|
901
|
+
if (entries.length === 0) return { accepted: 0, skippedDuplicate: 0, rejectedInvalid: 0, entries: [] };
|
|
796
902
|
const ledgerPath = getLedgerPath(meshId);
|
|
797
903
|
const existing = new Set(readLedgerEntries(meshId).map((e) => e.id));
|
|
798
|
-
const
|
|
799
|
-
|
|
904
|
+
const validEntries = [];
|
|
905
|
+
let rejectedInvalid = 0;
|
|
906
|
+
let skippedDuplicate = 0;
|
|
907
|
+
for (const entry of entries) {
|
|
908
|
+
if (!isValidRemoteLedgerEntry(meshId, entry)) {
|
|
909
|
+
rejectedInvalid++;
|
|
910
|
+
continue;
|
|
911
|
+
}
|
|
912
|
+
if (existing.has(entry.id)) {
|
|
913
|
+
skippedDuplicate++;
|
|
914
|
+
continue;
|
|
915
|
+
}
|
|
916
|
+
existing.add(entry.id);
|
|
917
|
+
validEntries.push(entry);
|
|
918
|
+
}
|
|
919
|
+
if (validEntries.length === 0) {
|
|
920
|
+
return { accepted: 0, skippedDuplicate, rejectedInvalid, entries: [] };
|
|
921
|
+
}
|
|
800
922
|
try {
|
|
801
|
-
const lines =
|
|
923
|
+
const lines = validEntries.map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
802
924
|
appendFileSync(ledgerPath, lines, { encoding: "utf-8", mode: 384 });
|
|
925
|
+
for (const entry of validEntries) {
|
|
926
|
+
meshLedgerEvents.emit("append", meshId, entry);
|
|
927
|
+
}
|
|
928
|
+
return { accepted: validEntries.length, skippedDuplicate, rejectedInvalid, entries: validEntries };
|
|
803
929
|
} catch (e) {
|
|
804
930
|
throw new Error(`Failed to append remote ledger entries for mesh ${meshId}: ${e.message}`);
|
|
805
931
|
}
|
|
@@ -838,6 +964,34 @@ function readLedgerEntries(meshId, opts) {
|
|
|
838
964
|
}
|
|
839
965
|
return entries;
|
|
840
966
|
}
|
|
967
|
+
function readLedgerSlice(meshId, opts) {
|
|
968
|
+
const limit = clampLedgerSliceLimit(opts?.limit);
|
|
969
|
+
let entries = readLedgerEntries(meshId, { since: opts?.since, kind: opts?.kind });
|
|
970
|
+
const afterId = typeof opts?.afterId === "string" && opts.afterId.trim() ? opts.afterId.trim() : null;
|
|
971
|
+
if (afterId) {
|
|
972
|
+
const index = entries.findIndex((entry) => entry.id === afterId);
|
|
973
|
+
entries = index >= 0 ? entries.slice(index + 1) : entries;
|
|
974
|
+
}
|
|
975
|
+
const bounded = entries.slice(0, limit);
|
|
976
|
+
return {
|
|
977
|
+
protocol: "adhdev.mesh.ledger.slice.v1",
|
|
978
|
+
meshId,
|
|
979
|
+
entries: bounded,
|
|
980
|
+
cursor: {
|
|
981
|
+
afterId,
|
|
982
|
+
nextAfterId: bounded.length ? bounded[bounded.length - 1].id : afterId,
|
|
983
|
+
limit,
|
|
984
|
+
hasMore: entries.length > bounded.length
|
|
985
|
+
},
|
|
986
|
+
summary: getLedgerSummary(meshId),
|
|
987
|
+
sourceOfTruth: {
|
|
988
|
+
kind: "local_jsonl",
|
|
989
|
+
path: getLedgerPath(meshId),
|
|
990
|
+
bounded: true,
|
|
991
|
+
maxLimit: MAX_LEDGER_SLICE_LIMIT
|
|
992
|
+
}
|
|
993
|
+
};
|
|
994
|
+
}
|
|
841
995
|
function getLedgerSummary(meshId) {
|
|
842
996
|
const entries = readLedgerEntries(meshId);
|
|
843
997
|
const now = Date.now();
|
|
@@ -863,15 +1017,17 @@ function getLedgerSummary(meshId) {
|
|
|
863
1017
|
summary.taskCompleted++;
|
|
864
1018
|
break;
|
|
865
1019
|
case "task_failed": {
|
|
1020
|
+
if (isIntentionalCleanupStopEntry(entry)) break;
|
|
866
1021
|
summary.taskFailed++;
|
|
867
1022
|
if (new Date(entry.timestamp).getTime() >= recentFailureCutoff) {
|
|
868
1023
|
summary.recentFailures++;
|
|
869
1024
|
}
|
|
870
1025
|
break;
|
|
871
1026
|
}
|
|
872
|
-
case "task_stalled":
|
|
873
|
-
summary.taskStalled++;
|
|
1027
|
+
case "task_stalled": {
|
|
1028
|
+
if (!isIntentionalCleanupStopEntry(entry)) summary.taskStalled++;
|
|
874
1029
|
break;
|
|
1030
|
+
}
|
|
875
1031
|
case "session_launched":
|
|
876
1032
|
summary.sessionLaunched++;
|
|
877
1033
|
break;
|
|
@@ -910,6 +1066,7 @@ function getSessionRecoveryContext(meshId, opts) {
|
|
|
910
1066
|
if (new Date(e.timestamp).getTime() < recentWindow) break;
|
|
911
1067
|
if (opts.nodeId && e.nodeId !== opts.nodeId) continue;
|
|
912
1068
|
if (e.kind === "task_failed") {
|
|
1069
|
+
if (isIntentionalCleanupStopEntry(e)) continue;
|
|
913
1070
|
consecutiveNodeFailures++;
|
|
914
1071
|
} else if (e.kind === "task_completed" || e.kind === "task_dispatched") {
|
|
915
1072
|
break;
|
|
@@ -960,7 +1117,7 @@ function rotateLedgerFile(meshId, currentPath) {
|
|
|
960
1117
|
} catch {
|
|
961
1118
|
}
|
|
962
1119
|
}
|
|
963
|
-
var LEDGER_DIR_NAME, MAX_FILE_SIZE_BYTES, RECENT_FAILURE_WINDOW_MS, meshLedgerEvents;
|
|
1120
|
+
var LEDGER_DIR_NAME, MAX_FILE_SIZE_BYTES, RECENT_FAILURE_WINDOW_MS, DEFAULT_LEDGER_SLICE_LIMIT, MAX_LEDGER_SLICE_LIMIT, meshLedgerEvents;
|
|
964
1121
|
var init_mesh_ledger = __esm({
|
|
965
1122
|
"src/mesh/mesh-ledger.ts"() {
|
|
966
1123
|
"use strict";
|
|
@@ -968,11 +1125,27 @@ var init_mesh_ledger = __esm({
|
|
|
968
1125
|
LEDGER_DIR_NAME = "mesh-ledger";
|
|
969
1126
|
MAX_FILE_SIZE_BYTES = 10 * 1024 * 1024;
|
|
970
1127
|
RECENT_FAILURE_WINDOW_MS = 30 * 60 * 1e3;
|
|
1128
|
+
DEFAULT_LEDGER_SLICE_LIMIT = 100;
|
|
1129
|
+
MAX_LEDGER_SLICE_LIMIT = 500;
|
|
971
1130
|
meshLedgerEvents = new EventEmitter();
|
|
972
1131
|
}
|
|
973
1132
|
});
|
|
974
1133
|
|
|
975
1134
|
// src/mesh/mesh-work-queue.ts
|
|
1135
|
+
var mesh_work_queue_exports = {};
|
|
1136
|
+
__export(mesh_work_queue_exports, {
|
|
1137
|
+
ACTIVE_MESH_QUEUE_STATUSES: () => ACTIVE_MESH_QUEUE_STATUSES,
|
|
1138
|
+
HISTORICAL_MESH_QUEUE_STATUSES: () => HISTORICAL_MESH_QUEUE_STATUSES,
|
|
1139
|
+
cancelTask: () => cancelTask,
|
|
1140
|
+
claimNextTask: () => claimNextTask,
|
|
1141
|
+
enqueueTask: () => enqueueTask,
|
|
1142
|
+
getMeshQueueStats: () => getMeshQueueStats,
|
|
1143
|
+
getQueue: () => getQueue,
|
|
1144
|
+
recordTaskAutoLaunch: () => recordTaskAutoLaunch,
|
|
1145
|
+
requeueTask: () => requeueTask,
|
|
1146
|
+
updateSessionTaskStatus: () => updateSessionTaskStatus,
|
|
1147
|
+
updateTaskStatus: () => updateTaskStatus
|
|
1148
|
+
});
|
|
976
1149
|
import { existsSync as existsSync6, writeFileSync as writeFileSync3, readFileSync as readFileSync4 } from "fs";
|
|
977
1150
|
import { join as join6 } from "path";
|
|
978
1151
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
@@ -1002,6 +1175,7 @@ function enqueueTask(meshId, message, opts) {
|
|
|
1002
1175
|
message,
|
|
1003
1176
|
status: "pending",
|
|
1004
1177
|
targetNodeId: opts?.targetNodeId,
|
|
1178
|
+
targetSessionId: opts?.targetSessionId,
|
|
1005
1179
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1006
1180
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1007
1181
|
};
|
|
@@ -1019,15 +1193,21 @@ function getQueue(meshId, opts) {
|
|
|
1019
1193
|
}
|
|
1020
1194
|
function claimNextTask(meshId, nodeId, sessionId) {
|
|
1021
1195
|
const queue = readQueue(meshId);
|
|
1022
|
-
|
|
1196
|
+
const hasActiveAssignment = queue.some((q) => q.status === "assigned" && (q.assignedSessionId === sessionId || q.assignedNodeId === nodeId));
|
|
1197
|
+
if (hasActiveAssignment) return null;
|
|
1198
|
+
let targetIdx = queue.findIndex((q) => q.status === "pending" && q.targetSessionId === sessionId);
|
|
1023
1199
|
if (targetIdx === -1) {
|
|
1024
|
-
targetIdx = queue.findIndex((q) => q.status === "pending" && !q.
|
|
1200
|
+
targetIdx = queue.findIndex((q) => q.status === "pending" && q.targetNodeId === nodeId && !q.targetSessionId);
|
|
1201
|
+
}
|
|
1202
|
+
if (targetIdx === -1) {
|
|
1203
|
+
targetIdx = queue.findIndex((q) => q.status === "pending" && !q.targetNodeId && !q.targetSessionId);
|
|
1025
1204
|
}
|
|
1026
1205
|
if (targetIdx === -1) return null;
|
|
1027
1206
|
const entry = queue[targetIdx];
|
|
1028
1207
|
entry.status = "assigned";
|
|
1029
1208
|
entry.assignedNodeId = nodeId;
|
|
1030
1209
|
entry.assignedSessionId = sessionId;
|
|
1210
|
+
entry.dispatchTimestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
1031
1211
|
entry.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1032
1212
|
writeQueue(meshId, queue);
|
|
1033
1213
|
return entry;
|
|
@@ -1041,38 +1221,253 @@ function updateTaskStatus(meshId, taskId, status) {
|
|
|
1041
1221
|
writeQueue(meshId, queue);
|
|
1042
1222
|
return queue[idx];
|
|
1043
1223
|
}
|
|
1224
|
+
function recordTaskAutoLaunch(meshId, taskId, autoLaunch) {
|
|
1225
|
+
const queue = readQueue(meshId);
|
|
1226
|
+
const idx = queue.findIndex((q) => q.id === taskId);
|
|
1227
|
+
if (idx === -1) return null;
|
|
1228
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1229
|
+
queue[idx].autoLaunch = {
|
|
1230
|
+
...autoLaunch,
|
|
1231
|
+
updatedAt: now
|
|
1232
|
+
};
|
|
1233
|
+
queue[idx].updatedAt = now;
|
|
1234
|
+
writeQueue(meshId, queue);
|
|
1235
|
+
return queue[idx];
|
|
1236
|
+
}
|
|
1237
|
+
function cancelTask(meshId, taskId, opts) {
|
|
1238
|
+
const queue = readQueue(meshId);
|
|
1239
|
+
const idx = queue.findIndex((q) => q.id === taskId);
|
|
1240
|
+
if (idx === -1) return null;
|
|
1241
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1242
|
+
queue[idx].status = "cancelled";
|
|
1243
|
+
queue[idx].updatedAt = now;
|
|
1244
|
+
queue[idx].cancelledAt = now;
|
|
1245
|
+
if (opts?.reason) queue[idx].cancelReason = opts.reason;
|
|
1246
|
+
writeQueue(meshId, queue);
|
|
1247
|
+
return queue[idx];
|
|
1248
|
+
}
|
|
1249
|
+
function requeueTask(meshId, taskId, opts) {
|
|
1250
|
+
const queue = readQueue(meshId);
|
|
1251
|
+
const idx = queue.findIndex((q) => q.id === taskId);
|
|
1252
|
+
if (idx === -1) return null;
|
|
1253
|
+
const entry = queue[idx];
|
|
1254
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1255
|
+
entry.status = "pending";
|
|
1256
|
+
delete entry.assignedNodeId;
|
|
1257
|
+
delete entry.assignedSessionId;
|
|
1258
|
+
delete entry.cancelledAt;
|
|
1259
|
+
delete entry.cancelReason;
|
|
1260
|
+
if (opts?.clearTargetNode) delete entry.targetNodeId;
|
|
1261
|
+
if (typeof opts?.targetNodeId === "string") entry.targetNodeId = opts.targetNodeId;
|
|
1262
|
+
if (opts?.clearTargetSession !== false) delete entry.targetSessionId;
|
|
1263
|
+
if (typeof opts?.targetSessionId === "string") entry.targetSessionId = opts.targetSessionId;
|
|
1264
|
+
entry.updatedAt = now;
|
|
1265
|
+
entry.requeuedAt = now;
|
|
1266
|
+
entry.requeueCount = (entry.requeueCount || 0) + 1;
|
|
1267
|
+
if (opts?.reason) entry.requeueReason = opts.reason;
|
|
1268
|
+
writeQueue(meshId, queue);
|
|
1269
|
+
return entry;
|
|
1270
|
+
}
|
|
1044
1271
|
function updateSessionTaskStatus(meshId, sessionId, status) {
|
|
1045
1272
|
const queue = readQueue(meshId);
|
|
1273
|
+
let bestIdx = -1;
|
|
1274
|
+
let bestTime = 0;
|
|
1046
1275
|
for (let i = queue.length - 1; i >= 0; i--) {
|
|
1047
1276
|
if (queue[i].assignedSessionId === sessionId && queue[i].status === "assigned") {
|
|
1048
|
-
queue[i].
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1277
|
+
const time = new Date(queue[i].dispatchTimestamp || queue[i].updatedAt).getTime();
|
|
1278
|
+
if (time > bestTime) {
|
|
1279
|
+
bestTime = time;
|
|
1280
|
+
bestIdx = i;
|
|
1281
|
+
}
|
|
1052
1282
|
}
|
|
1053
1283
|
}
|
|
1054
|
-
return null;
|
|
1284
|
+
if (bestIdx === -1) return null;
|
|
1285
|
+
queue[bestIdx].status = status;
|
|
1286
|
+
queue[bestIdx].updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1287
|
+
writeQueue(meshId, queue);
|
|
1288
|
+
return queue[bestIdx];
|
|
1055
1289
|
}
|
|
1056
1290
|
function getMeshQueueStats(meshId) {
|
|
1057
1291
|
const queue = readQueue(meshId);
|
|
1292
|
+
const pending = queue.filter((q) => q.status === "pending").length;
|
|
1293
|
+
const assigned = queue.filter((q) => q.status === "assigned").length;
|
|
1294
|
+
const completed = queue.filter((q) => q.status === "completed").length;
|
|
1295
|
+
const failed = queue.filter((q) => q.status === "failed").length;
|
|
1296
|
+
const cancelled = queue.filter((q) => q.status === "cancelled").length;
|
|
1058
1297
|
return {
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1298
|
+
total: queue.length,
|
|
1299
|
+
active: pending + assigned,
|
|
1300
|
+
historical: completed + failed + cancelled,
|
|
1301
|
+
pending,
|
|
1302
|
+
assigned,
|
|
1303
|
+
completed,
|
|
1304
|
+
failed,
|
|
1305
|
+
cancelled,
|
|
1306
|
+
activeCounts: {
|
|
1307
|
+
pending,
|
|
1308
|
+
assigned
|
|
1309
|
+
},
|
|
1310
|
+
historicalCounts: {
|
|
1311
|
+
completed,
|
|
1312
|
+
failed,
|
|
1313
|
+
cancelled
|
|
1314
|
+
},
|
|
1315
|
+
activeAssignments: queue.filter((q) => q.status === "assigned").map((q) => ({
|
|
1316
|
+
id: q.id,
|
|
1317
|
+
nodeId: q.assignedNodeId,
|
|
1318
|
+
sessionId: q.assignedSessionId,
|
|
1319
|
+
message: q.message
|
|
1320
|
+
}))
|
|
1063
1321
|
};
|
|
1064
1322
|
}
|
|
1323
|
+
var ACTIVE_MESH_QUEUE_STATUSES, HISTORICAL_MESH_QUEUE_STATUSES;
|
|
1065
1324
|
var init_mesh_work_queue = __esm({
|
|
1066
1325
|
"src/mesh/mesh-work-queue.ts"() {
|
|
1067
1326
|
"use strict";
|
|
1068
1327
|
init_mesh_ledger();
|
|
1328
|
+
ACTIVE_MESH_QUEUE_STATUSES = ["pending", "assigned"];
|
|
1329
|
+
HISTORICAL_MESH_QUEUE_STATUSES = ["completed", "failed", "cancelled"];
|
|
1330
|
+
}
|
|
1331
|
+
});
|
|
1332
|
+
|
|
1333
|
+
// src/detection/cli-detector.ts
|
|
1334
|
+
import { exec } from "child_process";
|
|
1335
|
+
import * as os2 from "os";
|
|
1336
|
+
import * as path8 from "path";
|
|
1337
|
+
import { existsSync as existsSync7 } from "fs";
|
|
1338
|
+
function parseVersion(raw) {
|
|
1339
|
+
const match = raw.match(/v?(\d+\.\d+(?:\.\d+)?(?:-[a-zA-Z0-9.]+)?)/);
|
|
1340
|
+
return match ? match[1] : raw.split("\n")[0].slice(0, 100);
|
|
1341
|
+
}
|
|
1342
|
+
function shellQuote(value) {
|
|
1343
|
+
if (/^[a-zA-Z0-9_./:@%+=,-]+$/.test(value)) return value;
|
|
1344
|
+
return `"${value.replace(/(["\\$`])/g, "\\$1")}"`;
|
|
1345
|
+
}
|
|
1346
|
+
function expandHome(value) {
|
|
1347
|
+
const trimmed = value.trim();
|
|
1348
|
+
if (!trimmed.startsWith("~")) return trimmed;
|
|
1349
|
+
return path8.join(os2.homedir(), trimmed.slice(1));
|
|
1350
|
+
}
|
|
1351
|
+
function isExplicitCommandPath(command) {
|
|
1352
|
+
const trimmed = command.trim();
|
|
1353
|
+
return path8.isAbsolute(trimmed) || trimmed.includes("/") || trimmed.includes("\\") || trimmed.startsWith("~");
|
|
1354
|
+
}
|
|
1355
|
+
function resolveCommandPath(command) {
|
|
1356
|
+
const trimmed = command.trim();
|
|
1357
|
+
if (!trimmed) return null;
|
|
1358
|
+
if (isExplicitCommandPath(trimmed)) {
|
|
1359
|
+
const expanded = expandHome(trimmed);
|
|
1360
|
+
const candidate = path8.isAbsolute(expanded) ? expanded : path8.resolve(expanded);
|
|
1361
|
+
return existsSync7(candidate) ? candidate : null;
|
|
1362
|
+
}
|
|
1363
|
+
return null;
|
|
1364
|
+
}
|
|
1365
|
+
function execAsync(cmd, timeoutMs = 5e3) {
|
|
1366
|
+
return new Promise((resolve16) => {
|
|
1367
|
+
const child = exec(cmd, {
|
|
1368
|
+
encoding: "utf-8",
|
|
1369
|
+
timeout: timeoutMs,
|
|
1370
|
+
...process.platform === "win32" ? { windowsHide: true } : {}
|
|
1371
|
+
}, (err, stdout) => {
|
|
1372
|
+
if (err || !stdout?.trim()) {
|
|
1373
|
+
resolve16(null);
|
|
1374
|
+
} else {
|
|
1375
|
+
resolve16(stdout.trim());
|
|
1376
|
+
}
|
|
1377
|
+
});
|
|
1378
|
+
child.on("error", () => resolve16(null));
|
|
1379
|
+
});
|
|
1380
|
+
}
|
|
1381
|
+
async function detectCLIs(providerLoader, options) {
|
|
1382
|
+
const platform10 = os2.platform();
|
|
1383
|
+
const whichCmd = platform10 === "win32" ? "where" : "which";
|
|
1384
|
+
const includeVersion = options?.includeVersion !== false;
|
|
1385
|
+
const cliList = providerLoader ? providerLoader.getCliDetectionList() : [];
|
|
1386
|
+
const results = await Promise.all(
|
|
1387
|
+
cliList.map(async (cli) => {
|
|
1388
|
+
try {
|
|
1389
|
+
const explicitPath = resolveCommandPath(cli.command);
|
|
1390
|
+
const pathResult = explicitPath || await execAsync(`${whichCmd} ${shellQuote(cli.command)}`);
|
|
1391
|
+
if (!pathResult) return { ...cli, installed: false };
|
|
1392
|
+
const firstPath = explicitPath || pathResult.split("\n")[0];
|
|
1393
|
+
let version;
|
|
1394
|
+
if (includeVersion) {
|
|
1395
|
+
const versionCommands = [
|
|
1396
|
+
`"${firstPath}" --version`,
|
|
1397
|
+
`"${firstPath}" -V`,
|
|
1398
|
+
`"${firstPath}" -v`,
|
|
1399
|
+
cli.versionCommand
|
|
1400
|
+
].filter((v) => !!v);
|
|
1401
|
+
try {
|
|
1402
|
+
for (const versionCommand of versionCommands) {
|
|
1403
|
+
const versionResult = await execAsync(versionCommand, 3e3);
|
|
1404
|
+
if (versionResult) {
|
|
1405
|
+
version = parseVersion(versionResult);
|
|
1406
|
+
break;
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
} catch {
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
return { ...cli, installed: true, version, path: firstPath };
|
|
1413
|
+
} catch {
|
|
1414
|
+
return { ...cli, installed: false };
|
|
1415
|
+
}
|
|
1416
|
+
})
|
|
1417
|
+
);
|
|
1418
|
+
return results;
|
|
1419
|
+
}
|
|
1420
|
+
async function detectCLI(cliId, providerLoader, options) {
|
|
1421
|
+
const resolvedId = providerLoader ? providerLoader.resolveAlias(cliId) : cliId;
|
|
1422
|
+
if (providerLoader) {
|
|
1423
|
+
const cliList = providerLoader.getCliDetectionList();
|
|
1424
|
+
const target = cliList.find((c) => c.id === resolvedId);
|
|
1425
|
+
if (target) {
|
|
1426
|
+
const platform10 = os2.platform();
|
|
1427
|
+
const whichCmd = platform10 === "win32" ? "where" : "which";
|
|
1428
|
+
try {
|
|
1429
|
+
const explicitPath = resolveCommandPath(target.command);
|
|
1430
|
+
const pathResult = explicitPath || await execAsync(`${whichCmd} ${shellQuote(target.command)}`);
|
|
1431
|
+
if (!pathResult) return null;
|
|
1432
|
+
const firstPath = explicitPath || pathResult.split("\n")[0];
|
|
1433
|
+
let version;
|
|
1434
|
+
if (options?.includeVersion !== false) {
|
|
1435
|
+
const versionCommands = [
|
|
1436
|
+
`"${firstPath}" --version`,
|
|
1437
|
+
`"${firstPath}" -V`,
|
|
1438
|
+
`"${firstPath}" -v`,
|
|
1439
|
+
target.versionCommand
|
|
1440
|
+
].filter((v) => !!v);
|
|
1441
|
+
try {
|
|
1442
|
+
for (const versionCommand of versionCommands) {
|
|
1443
|
+
const versionResult = await execAsync(versionCommand, 3e3);
|
|
1444
|
+
if (versionResult) {
|
|
1445
|
+
version = parseVersion(versionResult);
|
|
1446
|
+
break;
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
} catch {
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
return { ...target, installed: true, version, path: firstPath };
|
|
1453
|
+
} catch {
|
|
1454
|
+
return null;
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
const all = await detectCLIs(providerLoader, options);
|
|
1459
|
+
return all.find((c) => c.id === resolvedId && c.installed) || null;
|
|
1460
|
+
}
|
|
1461
|
+
var init_cli_detector = __esm({
|
|
1462
|
+
"src/detection/cli-detector.ts"() {
|
|
1463
|
+
"use strict";
|
|
1069
1464
|
}
|
|
1070
1465
|
});
|
|
1071
1466
|
|
|
1072
1467
|
// src/logging/logger.ts
|
|
1073
1468
|
import * as fs2 from "fs";
|
|
1074
|
-
import * as
|
|
1075
|
-
import * as
|
|
1469
|
+
import * as path9 from "path";
|
|
1470
|
+
import * as os3 from "os";
|
|
1076
1471
|
function setLogLevel(level) {
|
|
1077
1472
|
currentLevel = level;
|
|
1078
1473
|
daemonLog("Logger", `Log level set to: ${level}`, "info");
|
|
@@ -1087,13 +1482,13 @@ function getDaemonLogDir() {
|
|
|
1087
1482
|
return LOG_DIR;
|
|
1088
1483
|
}
|
|
1089
1484
|
function getCurrentDaemonLogPath(date = /* @__PURE__ */ new Date()) {
|
|
1090
|
-
return
|
|
1485
|
+
return path9.join(LOG_DIR, `daemon-${date.toISOString().slice(0, 10)}.log`);
|
|
1091
1486
|
}
|
|
1092
1487
|
function checkDateRotation() {
|
|
1093
1488
|
const today = getDateStr();
|
|
1094
1489
|
if (today !== currentDate) {
|
|
1095
1490
|
currentDate = today;
|
|
1096
|
-
currentLogFile =
|
|
1491
|
+
currentLogFile = path9.join(LOG_DIR, `daemon-${currentDate}.log`);
|
|
1097
1492
|
cleanOldLogs();
|
|
1098
1493
|
}
|
|
1099
1494
|
}
|
|
@@ -1107,7 +1502,7 @@ function cleanOldLogs() {
|
|
|
1107
1502
|
const dateMatch = file.match(/daemon-(\d{4}-\d{2}-\d{2})/);
|
|
1108
1503
|
if (dateMatch && dateMatch[1] < cutoffStr) {
|
|
1109
1504
|
try {
|
|
1110
|
-
fs2.unlinkSync(
|
|
1505
|
+
fs2.unlinkSync(path9.join(LOG_DIR, file));
|
|
1111
1506
|
} catch {
|
|
1112
1507
|
}
|
|
1113
1508
|
}
|
|
@@ -1230,7 +1625,7 @@ var init_logger = __esm({
|
|
|
1230
1625
|
LEVEL_NUM = { debug: 0, info: 1, warn: 2, error: 3 };
|
|
1231
1626
|
LEVEL_LABEL = { debug: "DBG", info: "INF", warn: "WRN", error: "ERR" };
|
|
1232
1627
|
currentLevel = "info";
|
|
1233
|
-
LOG_DIR = process.platform === "win32" ?
|
|
1628
|
+
LOG_DIR = process.platform === "win32" ? path9.join(process.env.LOCALAPPDATA || process.env.APPDATA || path9.join(os3.homedir(), "AppData", "Local"), "adhdev", "logs") : process.platform === "darwin" ? path9.join(os3.homedir(), "Library", "Logs", "adhdev") : path9.join(os3.homedir(), ".local", "share", "adhdev", "logs");
|
|
1234
1629
|
MAX_LOG_SIZE = 5 * 1024 * 1024;
|
|
1235
1630
|
MAX_LOG_DAYS = 7;
|
|
1236
1631
|
try {
|
|
@@ -1238,16 +1633,16 @@ var init_logger = __esm({
|
|
|
1238
1633
|
} catch {
|
|
1239
1634
|
}
|
|
1240
1635
|
currentDate = getDateStr();
|
|
1241
|
-
currentLogFile =
|
|
1636
|
+
currentLogFile = path9.join(LOG_DIR, `daemon-${currentDate}.log`);
|
|
1242
1637
|
cleanOldLogs();
|
|
1243
1638
|
try {
|
|
1244
|
-
const oldLog =
|
|
1639
|
+
const oldLog = path9.join(LOG_DIR, "daemon.log");
|
|
1245
1640
|
if (fs2.existsSync(oldLog)) {
|
|
1246
1641
|
const stat2 = fs2.statSync(oldLog);
|
|
1247
1642
|
const oldDate = stat2.mtime.toISOString().slice(0, 10);
|
|
1248
|
-
fs2.renameSync(oldLog,
|
|
1643
|
+
fs2.renameSync(oldLog, path9.join(LOG_DIR, `daemon-${oldDate}.log`));
|
|
1249
1644
|
}
|
|
1250
|
-
const oldLogBackup =
|
|
1645
|
+
const oldLogBackup = path9.join(LOG_DIR, "daemon.log.old");
|
|
1251
1646
|
if (fs2.existsSync(oldLogBackup)) {
|
|
1252
1647
|
fs2.unlinkSync(oldLogBackup);
|
|
1253
1648
|
}
|
|
@@ -1279,14 +1674,16 @@ var init_logger = __esm({
|
|
|
1279
1674
|
}
|
|
1280
1675
|
};
|
|
1281
1676
|
interceptorInstalled = false;
|
|
1282
|
-
LOG_PATH =
|
|
1677
|
+
LOG_PATH = path9.join(LOG_DIR, `daemon-${getDateStr()}.log`);
|
|
1283
1678
|
}
|
|
1284
1679
|
});
|
|
1285
1680
|
|
|
1286
1681
|
// src/mesh/mesh-events.ts
|
|
1287
1682
|
var mesh_events_exports = {};
|
|
1288
1683
|
__export(mesh_events_exports, {
|
|
1684
|
+
clearPendingMeshCoordinatorEvents: () => clearPendingMeshCoordinatorEvents,
|
|
1289
1685
|
drainPendingMeshCoordinatorEvents: () => drainPendingMeshCoordinatorEvents,
|
|
1686
|
+
getPendingMeshCoordinatorEvents: () => getPendingMeshCoordinatorEvents,
|
|
1290
1687
|
handleMeshForwardEvent: () => handleMeshForwardEvent,
|
|
1291
1688
|
setupMeshEventForwarding: () => setupMeshEventForwarding,
|
|
1292
1689
|
triggerMeshQueue: () => triggerMeshQueue,
|
|
@@ -1295,9 +1692,18 @@ __export(mesh_events_exports, {
|
|
|
1295
1692
|
function drainPendingMeshCoordinatorEvents() {
|
|
1296
1693
|
return pendingMeshCoordinatorEvents.splice(0);
|
|
1297
1694
|
}
|
|
1695
|
+
function getPendingMeshCoordinatorEvents() {
|
|
1696
|
+
return pendingMeshCoordinatorEvents.slice();
|
|
1697
|
+
}
|
|
1698
|
+
function clearPendingMeshCoordinatorEvents() {
|
|
1699
|
+
pendingMeshCoordinatorEvents.splice(0);
|
|
1700
|
+
}
|
|
1298
1701
|
function readNonEmptyString(value) {
|
|
1299
1702
|
return typeof value === "string" && value.trim() ? value.trim() : "";
|
|
1300
1703
|
}
|
|
1704
|
+
function resolveEventSessionId(event, fallback) {
|
|
1705
|
+
return readNonEmptyString(event.targetSessionId) || readNonEmptyString(event.sessionId) || readNonEmptyString(event.instanceId) || readNonEmptyString(fallback);
|
|
1706
|
+
}
|
|
1301
1707
|
function isMeshCoordinatorEvent(eventName) {
|
|
1302
1708
|
return typeof eventName === "string" && MESH_COORDINATOR_EVENTS.has(eventName);
|
|
1303
1709
|
}
|
|
@@ -1309,38 +1715,323 @@ function formatCompletionMetadata(event) {
|
|
|
1309
1715
|
].filter(Boolean);
|
|
1310
1716
|
return parts.length > 0 ? ` (${parts.join("; ")})` : "";
|
|
1311
1717
|
}
|
|
1718
|
+
function getMeshWithCache(components, meshId) {
|
|
1719
|
+
const localMesh = getMesh(meshId);
|
|
1720
|
+
if (localMesh) return localMesh;
|
|
1721
|
+
return components.router?.getCachedInlineMesh(meshId);
|
|
1722
|
+
}
|
|
1723
|
+
function isIntentionalCleanupStopMetadata(event) {
|
|
1724
|
+
return event.intentional === true || event.intentionalStop === true || event.operatorCleanup === true || event.reason === "operator_cleanup" || event.stopReason === "operator_cleanup" || event.cleanupReason === "operator_cleanup" || event.source === "mesh_cleanup_sessions" || event.source === "mesh_remove_node";
|
|
1725
|
+
}
|
|
1726
|
+
function hasRecentIntentionalCleanupStop(meshId, sessionId, nodeId) {
|
|
1727
|
+
if (!sessionId && !nodeId) return false;
|
|
1728
|
+
const cutoff = Date.now() - INTENTIONAL_CLEANUP_STOP_SUPPRESSION_MS;
|
|
1729
|
+
const entries = readLedgerEntries(meshId);
|
|
1730
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
1731
|
+
const entry = entries[i];
|
|
1732
|
+
const timestamp = new Date(entry.timestamp).getTime();
|
|
1733
|
+
if (!Number.isNaN(timestamp) && timestamp < cutoff) break;
|
|
1734
|
+
if (!isIntentionalCleanupStopEntry(entry)) continue;
|
|
1735
|
+
if (sessionId && entry.sessionId === sessionId) return true;
|
|
1736
|
+
if (!sessionId && nodeId && entry.nodeId === nodeId) return true;
|
|
1737
|
+
}
|
|
1738
|
+
return false;
|
|
1739
|
+
}
|
|
1740
|
+
function shouldSuppressIntentionalCleanupStop(args) {
|
|
1741
|
+
if (args.event !== "agent:stopped" && args.event !== "monitor:long_generating") return false;
|
|
1742
|
+
if (isIntentionalCleanupStopMetadata(args.metadataEvent)) return true;
|
|
1743
|
+
return hasRecentIntentionalCleanupStop(args.meshId, args.sessionId, args.nodeId);
|
|
1744
|
+
}
|
|
1312
1745
|
function tryAssignQueueTask(components, meshId, nodeId, sessionId, providerType) {
|
|
1313
1746
|
const task = claimNextTask(meshId, nodeId, sessionId);
|
|
1314
|
-
if (!task)
|
|
1747
|
+
if (!task) {
|
|
1748
|
+
return false;
|
|
1749
|
+
}
|
|
1315
1750
|
LOG.info("MeshQueue", `Node ${nodeId} (${sessionId}) pulled task ${task.id}`);
|
|
1751
|
+
const mesh = getMeshWithCache(components, meshId);
|
|
1752
|
+
const node = mesh?.nodes.find((n) => n.id === nodeId);
|
|
1753
|
+
if (node?.daemonId && components.dispatchMeshCommand) {
|
|
1754
|
+
const isLocalNode = components.cliManager.adapters.has(sessionId);
|
|
1755
|
+
if (!isLocalNode) {
|
|
1756
|
+
components.dispatchMeshCommand(node.daemonId, "agent_command", {
|
|
1757
|
+
targetSessionId: sessionId,
|
|
1758
|
+
cliType: providerType,
|
|
1759
|
+
action: "send_chat",
|
|
1760
|
+
message: task.message
|
|
1761
|
+
}).catch((e) => {
|
|
1762
|
+
LOG.error("MeshQueue", `Failed to dispatch task via P2P to remote node ${nodeId}: ${e?.message}`);
|
|
1763
|
+
updateTaskStatus(meshId, task.id, "failed");
|
|
1764
|
+
});
|
|
1765
|
+
return true;
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1316
1768
|
components.cliManager.handleCliCommand("agent_command", {
|
|
1317
1769
|
targetSessionId: sessionId,
|
|
1318
1770
|
cliType: providerType,
|
|
1319
1771
|
action: "send_chat",
|
|
1320
|
-
|
|
1772
|
+
message: task.message
|
|
1321
1773
|
}).catch((e) => {
|
|
1322
|
-
LOG.error("MeshQueue", `Failed to dispatch task to node ${nodeId}: ${e?.message}`);
|
|
1774
|
+
LOG.error("MeshQueue", `Failed to dispatch task locally to node ${nodeId}: ${e?.message}`);
|
|
1775
|
+
updateTaskStatus(meshId, task.id, "failed");
|
|
1323
1776
|
});
|
|
1324
1777
|
return true;
|
|
1325
1778
|
}
|
|
1326
|
-
function
|
|
1327
|
-
const
|
|
1779
|
+
function normalizeProviderPriority(policy) {
|
|
1780
|
+
const raw = policy && typeof policy === "object" && !Array.isArray(policy) ? policy.providerPriority : void 0;
|
|
1781
|
+
if (!Array.isArray(raw)) return [];
|
|
1782
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1783
|
+
return raw.map((type) => typeof type === "string" ? type.trim() : "").filter(Boolean).filter((type) => {
|
|
1784
|
+
if (seen.has(type)) return false;
|
|
1785
|
+
seen.add(type);
|
|
1786
|
+
return true;
|
|
1787
|
+
});
|
|
1788
|
+
}
|
|
1789
|
+
function isTerminalSessionStatus(status) {
|
|
1790
|
+
return ["stopped", "failed", "terminated", "exited", "closed"].includes(status);
|
|
1791
|
+
}
|
|
1792
|
+
function isIdleSessionState(state) {
|
|
1793
|
+
const status = readNonEmptyString(state?.status).toLowerCase();
|
|
1794
|
+
if (isTerminalSessionStatus(status)) return false;
|
|
1795
|
+
return status === "idle" || state?.activeChat?.status === "waiting_input";
|
|
1796
|
+
}
|
|
1797
|
+
function isDirtyNode(node) {
|
|
1798
|
+
return node?.health === "dirty" || node?.git?.dirty === true;
|
|
1799
|
+
}
|
|
1800
|
+
function isLaunchableNode(node) {
|
|
1801
|
+
if (!node || node.status === "disabled" || node.status === "removed") return false;
|
|
1802
|
+
const health = readNonEmptyString(node.health).toLowerCase();
|
|
1803
|
+
if (!health) return true;
|
|
1804
|
+
return health === "online" || health === "unknown";
|
|
1805
|
+
}
|
|
1806
|
+
function localAutoLaunchSkipReason(node) {
|
|
1807
|
+
const daemonId = readNonEmptyString(node?.daemonId);
|
|
1808
|
+
const machineId = readNonEmptyString(node?.machineId);
|
|
1809
|
+
const appConfig = loadConfig();
|
|
1810
|
+
const localMachineId = readNonEmptyString(appConfig.machineId) || readNonEmptyString(appConfig.registeredMachineId);
|
|
1811
|
+
const cloudDaemonId = localMachineId ? `daemon_${localMachineId}` : "";
|
|
1812
|
+
const standaloneDaemonId = localMachineId ? `standalone_${localMachineId}` : "";
|
|
1813
|
+
const daemonMatchesLocal = !daemonId || daemonId === cloudDaemonId || daemonId === standaloneDaemonId;
|
|
1814
|
+
const machineMatchesLocal = !machineId || localMachineId && machineId === localMachineId;
|
|
1815
|
+
if (node?.isLocalWorktree === true) {
|
|
1816
|
+
return daemonMatchesLocal && machineMatchesLocal ? null : "remote_auto_launch_unsupported";
|
|
1817
|
+
}
|
|
1818
|
+
if (daemonId || machineId) {
|
|
1819
|
+
return daemonMatchesLocal && machineMatchesLocal ? null : "remote_auto_launch_unsupported";
|
|
1820
|
+
}
|
|
1821
|
+
return null;
|
|
1822
|
+
}
|
|
1823
|
+
function activeAssignedCount(meshId) {
|
|
1824
|
+
return getQueue(meshId, { status: ["assigned"] }).length;
|
|
1825
|
+
}
|
|
1826
|
+
function nodeHasActiveAssignment(meshId, nodeId) {
|
|
1827
|
+
return getQueue(meshId, { status: ["assigned"] }).some((task) => task.assignedNodeId === nodeId);
|
|
1828
|
+
}
|
|
1829
|
+
function liveSessionCountForNode(components, meshId, nodeId) {
|
|
1830
|
+
return components.instanceManager.getByCategory("cli").filter((inst) => {
|
|
1831
|
+
const state = inst.getState();
|
|
1832
|
+
const settings = state.settings || {};
|
|
1833
|
+
if (readNonEmptyString(settings.meshNodeFor) !== meshId) return false;
|
|
1834
|
+
const instNodeId = readNonEmptyString(settings.meshNodeId) || readNonEmptyString(settings.nodeId);
|
|
1835
|
+
if (instNodeId !== nodeId) return false;
|
|
1836
|
+
const status = readNonEmptyString(state.status).toLowerCase();
|
|
1837
|
+
return !isTerminalSessionStatus(status);
|
|
1838
|
+
}).length;
|
|
1839
|
+
}
|
|
1840
|
+
function recordAutoLaunchEvent(meshId, args) {
|
|
1841
|
+
try {
|
|
1842
|
+
appendLedgerEntry(meshId, {
|
|
1843
|
+
kind: "session_auto_launch",
|
|
1844
|
+
nodeId: args.nodeId,
|
|
1845
|
+
sessionId: args.sessionId,
|
|
1846
|
+
providerType: args.providerType,
|
|
1847
|
+
payload: {
|
|
1848
|
+
phase: args.phase,
|
|
1849
|
+
taskId: args.taskId,
|
|
1850
|
+
reason: args.reason,
|
|
1851
|
+
error: args.error
|
|
1852
|
+
}
|
|
1853
|
+
});
|
|
1854
|
+
} catch (e) {
|
|
1855
|
+
LOG.warn("MeshQueue", `Failed to record auto-launch ledger event: ${e?.message || e}`);
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1858
|
+
function markAutoLaunch(meshId, taskId, args) {
|
|
1859
|
+
recordTaskAutoLaunch(meshId, taskId, {
|
|
1860
|
+
status: args.status,
|
|
1861
|
+
reason: args.reason || args.error,
|
|
1862
|
+
nodeId: args.nodeId,
|
|
1863
|
+
providerType: args.providerType,
|
|
1864
|
+
sessionId: args.sessionId
|
|
1865
|
+
});
|
|
1866
|
+
recordAutoLaunchEvent(meshId, {
|
|
1867
|
+
phase: args.status,
|
|
1868
|
+
taskId,
|
|
1869
|
+
nodeId: args.nodeId,
|
|
1870
|
+
providerType: args.providerType,
|
|
1871
|
+
sessionId: args.sessionId,
|
|
1872
|
+
reason: args.reason,
|
|
1873
|
+
error: args.error
|
|
1874
|
+
});
|
|
1875
|
+
}
|
|
1876
|
+
async function resolveUsableProvider(components, nodeId, node) {
|
|
1877
|
+
const providerPriority = normalizeProviderPriority(node?.policy);
|
|
1878
|
+
if (!providerPriority.length) return { reason: "missing_provider_priority" };
|
|
1879
|
+
const providerLoader = components.providerLoader;
|
|
1880
|
+
if (!providerLoader) return { reason: "provider_loader_unavailable" };
|
|
1881
|
+
const failed = [];
|
|
1882
|
+
for (const requestedType of providerPriority) {
|
|
1883
|
+
const normalizedType = typeof providerLoader.resolveAlias === "function" ? providerLoader.resolveAlias(requestedType) : requestedType;
|
|
1884
|
+
if (typeof providerLoader.isMachineProviderEnabled === "function" && !providerLoader.isMachineProviderEnabled(normalizedType)) {
|
|
1885
|
+
failed.push(`${requestedType}: disabled`);
|
|
1886
|
+
continue;
|
|
1887
|
+
}
|
|
1888
|
+
let detected;
|
|
1889
|
+
try {
|
|
1890
|
+
detected = await detectCLI(normalizedType, providerLoader, { includeVersion: false });
|
|
1891
|
+
} catch (e) {
|
|
1892
|
+
failed.push(`${requestedType}: detect failed: ${e?.message || e}`);
|
|
1893
|
+
continue;
|
|
1894
|
+
}
|
|
1895
|
+
if (typeof providerLoader.setCliDetectionResults === "function") {
|
|
1896
|
+
providerLoader.setCliDetectionResults([{
|
|
1897
|
+
id: normalizedType,
|
|
1898
|
+
installed: !!detected,
|
|
1899
|
+
path: detected?.path
|
|
1900
|
+
}], false);
|
|
1901
|
+
}
|
|
1902
|
+
components.onStatusChange?.();
|
|
1903
|
+
if (detected) return { providerType: normalizedType };
|
|
1904
|
+
failed.push(`${requestedType}: not detected`);
|
|
1905
|
+
}
|
|
1906
|
+
return { reason: `provider_priority_unusable: ${failed.join("; ") || nodeId}` };
|
|
1907
|
+
}
|
|
1908
|
+
async function maybeAutoLaunchOneQueueSession(components, meshId, mesh) {
|
|
1909
|
+
const queue = getQueue(meshId);
|
|
1910
|
+
const pending = queue.filter((task) => task.status === "pending");
|
|
1911
|
+
if (!pending.length) return false;
|
|
1912
|
+
const maxParallelTasks = Math.max(1, Math.floor(Number(mesh?.policy?.maxParallelTasks) || 2));
|
|
1913
|
+
for (const task of pending) {
|
|
1914
|
+
if (activeAssignedCount(meshId) >= maxParallelTasks) {
|
|
1915
|
+
markAutoLaunch(meshId, task.id, { status: "skipped", reason: "max_parallel_tasks_reached" });
|
|
1916
|
+
return false;
|
|
1917
|
+
}
|
|
1918
|
+
if (task.targetSessionId) {
|
|
1919
|
+
markAutoLaunch(meshId, task.id, { status: "skipped", reason: "target_session_constraint" });
|
|
1920
|
+
continue;
|
|
1921
|
+
}
|
|
1922
|
+
const candidateNodes = Array.isArray(mesh?.nodes) ? mesh.nodes.filter((node) => task.targetNodeId ? node?.id === task.targetNodeId : true) : [];
|
|
1923
|
+
if (!candidateNodes.length) {
|
|
1924
|
+
markAutoLaunch(meshId, task.id, { status: "skipped", reason: "no_matching_node", nodeId: task.targetNodeId });
|
|
1925
|
+
continue;
|
|
1926
|
+
}
|
|
1927
|
+
for (const node of candidateNodes) {
|
|
1928
|
+
const nodeId = readNonEmptyString(node?.id);
|
|
1929
|
+
if (!nodeId) continue;
|
|
1930
|
+
const launchKey = `${meshId}:${nodeId}`;
|
|
1931
|
+
const cooldownUntil = autoLaunchCooldownUntil.get(launchKey) || 0;
|
|
1932
|
+
if (autoLaunchInProgress.has(launchKey)) {
|
|
1933
|
+
markAutoLaunch(meshId, task.id, { status: "skipped", reason: "auto_launch_in_progress", nodeId });
|
|
1934
|
+
continue;
|
|
1935
|
+
}
|
|
1936
|
+
if (Date.now() < cooldownUntil) {
|
|
1937
|
+
markAutoLaunch(meshId, task.id, { status: "skipped", reason: "auto_launch_cooldown", nodeId });
|
|
1938
|
+
continue;
|
|
1939
|
+
}
|
|
1940
|
+
if (isDirtyNode(node)) {
|
|
1941
|
+
markAutoLaunch(meshId, task.id, { status: "skipped", reason: "dirty_workspace", nodeId });
|
|
1942
|
+
continue;
|
|
1943
|
+
}
|
|
1944
|
+
if (!isLaunchableNode(node)) {
|
|
1945
|
+
markAutoLaunch(meshId, task.id, { status: "skipped", reason: "node_not_launch_ready", nodeId });
|
|
1946
|
+
continue;
|
|
1947
|
+
}
|
|
1948
|
+
const localSkipReason = localAutoLaunchSkipReason(node);
|
|
1949
|
+
if (localSkipReason) {
|
|
1950
|
+
markAutoLaunch(meshId, task.id, { status: "skipped", reason: localSkipReason, nodeId });
|
|
1951
|
+
continue;
|
|
1952
|
+
}
|
|
1953
|
+
if (nodeHasActiveAssignment(meshId, nodeId)) {
|
|
1954
|
+
markAutoLaunch(meshId, task.id, { status: "skipped", reason: "node_has_active_assignment", nodeId });
|
|
1955
|
+
continue;
|
|
1956
|
+
}
|
|
1957
|
+
const maxConcurrentSessions = Number(node?.policy?.maxConcurrentSessions);
|
|
1958
|
+
if (Number.isFinite(maxConcurrentSessions) && maxConcurrentSessions >= 0 && liveSessionCountForNode(components, meshId, nodeId) >= maxConcurrentSessions) {
|
|
1959
|
+
markAutoLaunch(meshId, task.id, { status: "skipped", reason: "max_concurrent_sessions_reached", nodeId });
|
|
1960
|
+
continue;
|
|
1961
|
+
}
|
|
1962
|
+
autoLaunchInProgress.add(launchKey);
|
|
1963
|
+
try {
|
|
1964
|
+
const resolved = await resolveUsableProvider(components, nodeId, node);
|
|
1965
|
+
if (!resolved.providerType) {
|
|
1966
|
+
markAutoLaunch(meshId, task.id, { status: "skipped", reason: resolved.reason || "provider_unusable", nodeId });
|
|
1967
|
+
continue;
|
|
1968
|
+
}
|
|
1969
|
+
markAutoLaunch(meshId, task.id, { status: "started", nodeId, providerType: resolved.providerType });
|
|
1970
|
+
const launchResult = await components.cliManager.handleCliCommand("launch_cli", {
|
|
1971
|
+
cliType: resolved.providerType,
|
|
1972
|
+
dir: node.workspace,
|
|
1973
|
+
settings: {
|
|
1974
|
+
meshNodeFor: meshId,
|
|
1975
|
+
meshNodeId: nodeId,
|
|
1976
|
+
spawnedSessionVisibility: mesh?.policy?.spawnedSessionVisibility || "hidden",
|
|
1977
|
+
launchedByCoordinator: true,
|
|
1978
|
+
autoLaunchedForQueueTaskId: task.id
|
|
1979
|
+
}
|
|
1980
|
+
});
|
|
1981
|
+
if (!launchResult?.success) {
|
|
1982
|
+
const reason = launchResult?.error || "launch_cli_failed";
|
|
1983
|
+
markAutoLaunch(meshId, task.id, { status: "failed", reason, nodeId, providerType: resolved.providerType });
|
|
1984
|
+
autoLaunchCooldownUntil.set(launchKey, Date.now() + AUTO_LAUNCH_COOLDOWN_MS);
|
|
1985
|
+
return false;
|
|
1986
|
+
}
|
|
1987
|
+
const sessionId = readNonEmptyString(launchResult.sessionId) || readNonEmptyString(launchResult.id) || readNonEmptyString(launchResult.runtimeSessionId);
|
|
1988
|
+
if (!sessionId) {
|
|
1989
|
+
markAutoLaunch(meshId, task.id, { status: "failed", reason: "launch_missing_session_id", nodeId, providerType: resolved.providerType });
|
|
1990
|
+
autoLaunchCooldownUntil.set(launchKey, Date.now() + AUTO_LAUNCH_COOLDOWN_MS);
|
|
1991
|
+
return false;
|
|
1992
|
+
}
|
|
1993
|
+
markAutoLaunch(meshId, task.id, { status: "completed", nodeId, providerType: resolved.providerType, sessionId });
|
|
1994
|
+
tryAssignQueueTask(components, meshId, nodeId, sessionId, resolved.providerType);
|
|
1995
|
+
return true;
|
|
1996
|
+
} catch (e) {
|
|
1997
|
+
markAutoLaunch(meshId, task.id, { status: "failed", error: e?.message || String(e), nodeId });
|
|
1998
|
+
autoLaunchCooldownUntil.set(launchKey, Date.now() + AUTO_LAUNCH_COOLDOWN_MS);
|
|
1999
|
+
return false;
|
|
2000
|
+
} finally {
|
|
2001
|
+
autoLaunchInProgress.delete(launchKey);
|
|
2002
|
+
}
|
|
2003
|
+
}
|
|
2004
|
+
}
|
|
2005
|
+
return false;
|
|
2006
|
+
}
|
|
2007
|
+
async function triggerMeshQueue(components, meshId) {
|
|
2008
|
+
const mesh = getMeshWithCache(components, meshId);
|
|
1328
2009
|
if (!mesh) return;
|
|
1329
2010
|
const cliInstances = components.instanceManager.getByCategory("cli");
|
|
1330
2011
|
for (const inst of cliInstances) {
|
|
1331
2012
|
const state = inst.getState();
|
|
1332
2013
|
const settings = state.settings || {};
|
|
1333
2014
|
const instMeshId = readNonEmptyString(settings.meshNodeFor);
|
|
1334
|
-
if (instMeshId !== meshId
|
|
2015
|
+
if (instMeshId !== meshId) continue;
|
|
1335
2016
|
const nodeId = readNonEmptyString(settings.meshNodeId) || readNonEmptyString(settings.nodeId);
|
|
1336
2017
|
if (!nodeId) continue;
|
|
1337
|
-
if (state
|
|
2018
|
+
if (!isIdleSessionState(state)) continue;
|
|
1338
2019
|
const sessionId = state.instanceId;
|
|
1339
2020
|
const providerType = state.type || readNonEmptyString(settings.providerType);
|
|
1340
2021
|
if (providerType) {
|
|
1341
2022
|
tryAssignQueueTask(components, meshId, nodeId, sessionId, providerType);
|
|
1342
2023
|
}
|
|
1343
2024
|
}
|
|
2025
|
+
for (const [key, idle] of remoteIdleSessions.entries()) {
|
|
2026
|
+
const node = mesh.nodes.find((n) => n.id === idle.nodeId);
|
|
2027
|
+
if (node) {
|
|
2028
|
+
const assigned = tryAssignQueueTask(components, meshId, idle.nodeId, idle.sessionId, idle.providerType);
|
|
2029
|
+
if (assigned) {
|
|
2030
|
+
remoteIdleSessions.delete(key);
|
|
2031
|
+
}
|
|
2032
|
+
}
|
|
2033
|
+
}
|
|
2034
|
+
await maybeAutoLaunchOneQueueSession(components, meshId, mesh);
|
|
1344
2035
|
}
|
|
1345
2036
|
function buildMeshSystemMessage(args) {
|
|
1346
2037
|
const metadata = formatCompletionMetadata(args.metadataEvent);
|
|
@@ -1387,20 +2078,91 @@ Do NOT retry on this node. Consider reassigning to a different node or asking th
|
|
|
1387
2078
|
return "";
|
|
1388
2079
|
}
|
|
1389
2080
|
function injectMeshSystemMessage(components, args) {
|
|
2081
|
+
const eventSessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId);
|
|
2082
|
+
const eventNodeId = readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId);
|
|
2083
|
+
const intentionalCleanupStop = shouldSuppressIntentionalCleanupStop({
|
|
2084
|
+
event: args.event,
|
|
2085
|
+
meshId: args.meshId,
|
|
2086
|
+
metadataEvent: args.metadataEvent,
|
|
2087
|
+
sessionId: eventSessionId || void 0,
|
|
2088
|
+
nodeId: eventNodeId || void 0
|
|
2089
|
+
});
|
|
2090
|
+
if (intentionalCleanupStop) {
|
|
2091
|
+
if (eventSessionId && eventNodeId) {
|
|
2092
|
+
remoteIdleSessions.delete(`${eventNodeId}:${eventSessionId}`);
|
|
2093
|
+
}
|
|
2094
|
+
LOG.info("MeshEvents", `Suppressed ${args.event} for intentionally cleanup-stopped session ${eventSessionId || "(unknown session)"}`);
|
|
2095
|
+
return { success: true, forwarded: 0, suppressed: true, intentionalCleanupStop: true };
|
|
2096
|
+
}
|
|
2097
|
+
let completedTaskForLedger = null;
|
|
1390
2098
|
if (args.event === "agent:generating_completed") {
|
|
1391
|
-
const sessionId =
|
|
1392
|
-
const nodeId = readNonEmptyString(args.
|
|
2099
|
+
const sessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId);
|
|
2100
|
+
const nodeId = readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId);
|
|
1393
2101
|
const providerType = readNonEmptyString(args.metadataEvent.providerType);
|
|
1394
2102
|
if (sessionId) {
|
|
1395
|
-
updateSessionTaskStatus(args.meshId, sessionId, "completed");
|
|
2103
|
+
const completedTask = updateSessionTaskStatus(args.meshId, sessionId, "completed");
|
|
2104
|
+
completedTaskForLedger = completedTask ? { id: completedTask.id } : null;
|
|
1396
2105
|
if (nodeId && providerType) {
|
|
1397
2106
|
setTimeout(() => {
|
|
1398
2107
|
tryAssignQueueTask(components, args.meshId, nodeId, sessionId, providerType);
|
|
1399
2108
|
}, 500);
|
|
1400
2109
|
}
|
|
1401
2110
|
}
|
|
2111
|
+
} else if (args.event === "agent:ready") {
|
|
2112
|
+
const sessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId);
|
|
2113
|
+
const nodeId = readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId);
|
|
2114
|
+
const providerType = readNonEmptyString(args.metadataEvent.providerType);
|
|
2115
|
+
const completedTask = sessionId ? updateSessionTaskStatus(args.meshId, sessionId, "completed") : null;
|
|
2116
|
+
if (completedTask) {
|
|
2117
|
+
completedTaskForLedger = { id: completedTask.id };
|
|
2118
|
+
try {
|
|
2119
|
+
appendLedgerEntry(args.meshId, {
|
|
2120
|
+
kind: "task_completed",
|
|
2121
|
+
nodeId: nodeId || void 0,
|
|
2122
|
+
sessionId,
|
|
2123
|
+
providerType: providerType || void 0,
|
|
2124
|
+
payload: {
|
|
2125
|
+
event: args.event,
|
|
2126
|
+
nodeLabel: args.nodeLabel,
|
|
2127
|
+
taskId: completedTask.id,
|
|
2128
|
+
completedViaReady: true,
|
|
2129
|
+
providerSessionId: readNonEmptyString(args.metadataEvent.providerSessionId) || void 0,
|
|
2130
|
+
finalSummary: readNonEmptyString(args.metadataEvent.finalSummary) || void 0,
|
|
2131
|
+
evidence: buildTaskCompletionEvidence({
|
|
2132
|
+
event: "agent:ready",
|
|
2133
|
+
nodeId,
|
|
2134
|
+
sessionId,
|
|
2135
|
+
providerType: providerType || void 0,
|
|
2136
|
+
providerSessionId: readNonEmptyString(args.metadataEvent.providerSessionId) || void 0,
|
|
2137
|
+
finalSummary: readNonEmptyString(args.metadataEvent.finalSummary) || void 0
|
|
2138
|
+
})
|
|
2139
|
+
}
|
|
2140
|
+
});
|
|
2141
|
+
} catch (e) {
|
|
2142
|
+
LOG.warn("MeshLedger", `Failed to record task_completed from ready: ${e?.message || e}`);
|
|
2143
|
+
}
|
|
2144
|
+
}
|
|
2145
|
+
if (sessionId && nodeId && providerType) {
|
|
2146
|
+
remoteIdleSessions.set(`${nodeId}:${sessionId}`, { nodeId, sessionId, providerType });
|
|
2147
|
+
setTimeout(() => {
|
|
2148
|
+
const assigned = tryAssignQueueTask(components, args.meshId, nodeId, sessionId, providerType);
|
|
2149
|
+
if (assigned) {
|
|
2150
|
+
remoteIdleSessions.delete(`${nodeId}:${sessionId}`);
|
|
2151
|
+
}
|
|
2152
|
+
}, 500);
|
|
2153
|
+
}
|
|
2154
|
+
} else if (args.event === "agent:generating_started") {
|
|
2155
|
+
const sessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId);
|
|
2156
|
+
const nodeId = readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId);
|
|
2157
|
+
if (sessionId && nodeId) {
|
|
2158
|
+
remoteIdleSessions.delete(`${nodeId}:${sessionId}`);
|
|
2159
|
+
}
|
|
1402
2160
|
} else if (args.event === "agent:stopped") {
|
|
1403
|
-
const sessionId =
|
|
2161
|
+
const sessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId);
|
|
2162
|
+
const nodeId = readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId);
|
|
2163
|
+
if (sessionId && nodeId) {
|
|
2164
|
+
remoteIdleSessions.delete(`${nodeId}:${sessionId}`);
|
|
2165
|
+
}
|
|
1404
2166
|
if (sessionId) {
|
|
1405
2167
|
updateSessionTaskStatus(args.meshId, sessionId, "failed");
|
|
1406
2168
|
}
|
|
@@ -1408,15 +2170,29 @@ function injectMeshSystemMessage(components, args) {
|
|
|
1408
2170
|
const ledgerKind = EVENT_TO_LEDGER_KIND[args.event];
|
|
1409
2171
|
if (ledgerKind) {
|
|
1410
2172
|
try {
|
|
2173
|
+
const ledgerNodeId = readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId) || void 0;
|
|
2174
|
+
const ledgerSessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId) || void 0;
|
|
2175
|
+
const ledgerProviderType = readNonEmptyString(args.metadataEvent.providerType) || void 0;
|
|
2176
|
+
const completionEvidence = ledgerKind === "task_completed" && ledgerNodeId && ledgerSessionId ? buildTaskCompletionEvidence({
|
|
2177
|
+
event: "agent:generating_completed",
|
|
2178
|
+
nodeId: ledgerNodeId,
|
|
2179
|
+
sessionId: ledgerSessionId,
|
|
2180
|
+
providerType: ledgerProviderType,
|
|
2181
|
+
providerSessionId: readNonEmptyString(args.metadataEvent.providerSessionId) || void 0,
|
|
2182
|
+
finalSummary: readNonEmptyString(args.metadataEvent.finalSummary) || void 0
|
|
2183
|
+
}) : void 0;
|
|
1411
2184
|
appendLedgerEntry(args.meshId, {
|
|
1412
2185
|
kind: ledgerKind,
|
|
1413
|
-
nodeId:
|
|
1414
|
-
sessionId:
|
|
1415
|
-
providerType:
|
|
2186
|
+
nodeId: ledgerNodeId,
|
|
2187
|
+
sessionId: ledgerSessionId,
|
|
2188
|
+
providerType: ledgerProviderType,
|
|
1416
2189
|
payload: {
|
|
1417
2190
|
event: args.event,
|
|
1418
2191
|
nodeLabel: args.nodeLabel,
|
|
1419
|
-
|
|
2192
|
+
taskId: completedTaskForLedger?.id || void 0,
|
|
2193
|
+
providerSessionId: readNonEmptyString(args.metadataEvent.providerSessionId) || void 0,
|
|
2194
|
+
finalSummary: readNonEmptyString(args.metadataEvent.finalSummary) || void 0,
|
|
2195
|
+
evidence: completionEvidence
|
|
1420
2196
|
}
|
|
1421
2197
|
});
|
|
1422
2198
|
} catch (e) {
|
|
@@ -1429,8 +2205,8 @@ function injectMeshSystemMessage(components, args) {
|
|
|
1429
2205
|
const mesh = getMesh(args.meshId);
|
|
1430
2206
|
const maxRetries = mesh?.policy?.maxTaskRetries ?? 1;
|
|
1431
2207
|
recoveryContext = getSessionRecoveryContext(args.meshId, {
|
|
1432
|
-
sessionId:
|
|
1433
|
-
nodeId: readNonEmptyString(args.
|
|
2208
|
+
sessionId: resolveEventSessionId(args.metadataEvent, args.sourceInstanceId) || void 0,
|
|
2209
|
+
nodeId: readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId) || void 0,
|
|
1434
2210
|
maxRetries
|
|
1435
2211
|
});
|
|
1436
2212
|
recoveryContext.failedProviderType = readNonEmptyString(args.metadataEvent.providerType) || null;
|
|
@@ -1525,12 +2301,21 @@ function handleMeshForwardEvent(components, payload) {
|
|
|
1525
2301
|
const nodeLabel = nodeId ? `Node '${nodeId}'` : workspace ? `Agent at ${workspace}` : "Remote agent";
|
|
1526
2302
|
return injectMeshSystemMessage(components, {
|
|
1527
2303
|
meshId,
|
|
2304
|
+
nodeId,
|
|
1528
2305
|
nodeLabel,
|
|
1529
2306
|
event: eventName,
|
|
1530
2307
|
metadataEvent: {
|
|
1531
|
-
targetSessionId: readNonEmptyString(payload.targetSessionId) || readNonEmptyString(payload.sessionId),
|
|
2308
|
+
targetSessionId: readNonEmptyString(payload.targetSessionId) || readNonEmptyString(payload.sessionId) || readNonEmptyString(payload.instanceId),
|
|
1532
2309
|
providerType: readNonEmptyString(payload.providerType),
|
|
1533
|
-
providerSessionId: readNonEmptyString(payload.providerSessionId)
|
|
2310
|
+
providerSessionId: readNonEmptyString(payload.providerSessionId),
|
|
2311
|
+
finalSummary: readNonEmptyString(payload.finalSummary) || readNonEmptyString(payload.summary),
|
|
2312
|
+
intentional: payload.intentional === true,
|
|
2313
|
+
intentionalStop: payload.intentionalStop === true,
|
|
2314
|
+
operatorCleanup: payload.operatorCleanup === true,
|
|
2315
|
+
reason: readNonEmptyString(payload.reason),
|
|
2316
|
+
stopReason: readNonEmptyString(payload.stopReason),
|
|
2317
|
+
cleanupReason: readNonEmptyString(payload.cleanupReason),
|
|
2318
|
+
source: readNonEmptyString(payload.source)
|
|
1534
2319
|
}
|
|
1535
2320
|
});
|
|
1536
2321
|
}
|
|
@@ -1549,35 +2334,42 @@ function setupMeshEventForwarding(components) {
|
|
|
1549
2334
|
const meshIdFromRuntime = readNonEmptyString(settings.meshNodeFor);
|
|
1550
2335
|
const isMeshDelegate = Boolean(meshIdFromRuntime || settings.launchedByCoordinator);
|
|
1551
2336
|
if (!isMeshDelegate) return;
|
|
1552
|
-
const mesh = meshIdFromRuntime ?
|
|
2337
|
+
const mesh = meshIdFromRuntime ? getMeshWithCache(components, meshIdFromRuntime) : getMeshByRepo(workspace);
|
|
1553
2338
|
const meshId = meshIdFromRuntime || readNonEmptyString(mesh?.id);
|
|
1554
2339
|
if (!meshId) return;
|
|
1555
2340
|
const targetNode = mesh?.nodes?.find((n) => n.workspace === workspace);
|
|
1556
2341
|
const runtimeNodeId = readNonEmptyString(settings.meshNodeId);
|
|
2342
|
+
const resolvedNodeId = targetNode?.id || runtimeNodeId;
|
|
1557
2343
|
const nodeLabel = targetNode ? `Node '${targetNode.id}'` : runtimeNodeId ? `Node '${runtimeNodeId}'` : `Agent at ${workspace}`;
|
|
1558
2344
|
injectMeshSystemMessage(components, {
|
|
1559
2345
|
meshId,
|
|
1560
2346
|
sourceInstanceId: instanceId,
|
|
2347
|
+
nodeId: resolvedNodeId,
|
|
1561
2348
|
nodeLabel,
|
|
1562
2349
|
event: event.event,
|
|
1563
2350
|
metadataEvent: event
|
|
1564
2351
|
});
|
|
1565
2352
|
});
|
|
1566
2353
|
}
|
|
1567
|
-
var MAX_PENDING_EVENTS, pendingMeshCoordinatorEvents, MESH_COORDINATOR_EVENTS, EVENT_TO_LEDGER_KIND;
|
|
2354
|
+
var remoteIdleSessions, MAX_PENDING_EVENTS, pendingMeshCoordinatorEvents, MESH_COORDINATOR_EVENTS, EVENT_TO_LEDGER_KIND, INTENTIONAL_CLEANUP_STOP_SUPPRESSION_MS, autoLaunchInProgress, autoLaunchCooldownUntil, AUTO_LAUNCH_COOLDOWN_MS;
|
|
1568
2355
|
var init_mesh_events = __esm({
|
|
1569
2356
|
"src/mesh/mesh-events.ts"() {
|
|
1570
2357
|
"use strict";
|
|
2358
|
+
init_config();
|
|
1571
2359
|
init_mesh_config();
|
|
2360
|
+
init_cli_detector();
|
|
1572
2361
|
init_logger();
|
|
1573
2362
|
init_mesh_ledger();
|
|
1574
2363
|
init_mesh_work_queue();
|
|
2364
|
+
remoteIdleSessions = /* @__PURE__ */ new Map();
|
|
1575
2365
|
MAX_PENDING_EVENTS = 50;
|
|
1576
2366
|
pendingMeshCoordinatorEvents = [];
|
|
1577
2367
|
MESH_COORDINATOR_EVENTS = /* @__PURE__ */ new Set([
|
|
2368
|
+
"agent:generating_started",
|
|
1578
2369
|
"agent:generating_completed",
|
|
1579
2370
|
"agent:waiting_approval",
|
|
1580
2371
|
"agent:stopped",
|
|
2372
|
+
"agent:ready",
|
|
1581
2373
|
"monitor:long_generating"
|
|
1582
2374
|
]);
|
|
1583
2375
|
EVENT_TO_LEDGER_KIND = {
|
|
@@ -1586,6 +2378,10 @@ var init_mesh_events = __esm({
|
|
|
1586
2378
|
"agent:stopped": "task_failed",
|
|
1587
2379
|
"monitor:long_generating": "task_stalled"
|
|
1588
2380
|
};
|
|
2381
|
+
INTENTIONAL_CLEANUP_STOP_SUPPRESSION_MS = 30 * 60 * 1e3;
|
|
2382
|
+
autoLaunchInProgress = /* @__PURE__ */ new Set();
|
|
2383
|
+
autoLaunchCooldownUntil = /* @__PURE__ */ new Map();
|
|
2384
|
+
AUTO_LAUNCH_COOLDOWN_MS = 5e3;
|
|
1589
2385
|
}
|
|
1590
2386
|
});
|
|
1591
2387
|
|
|
@@ -2613,6 +3409,7 @@ var init_provider_cli_adapter = __esm({
|
|
|
2613
3409
|
this.requirePromptEchoBeforeSubmit = resolvedConfig.requirePromptEchoBeforeSubmit;
|
|
2614
3410
|
this.providerResolutionMeta = resolvedConfig.providerResolutionMeta;
|
|
2615
3411
|
this.cliScripts = provider.scripts || {};
|
|
3412
|
+
this.scriptState = typeof this.cliScripts.createState === "function" ? this.cliScripts.createState() ?? null : null;
|
|
2616
3413
|
const scriptNames = listCliScriptNames(this.cliScripts);
|
|
2617
3414
|
if (scriptNames.length > 0) {
|
|
2618
3415
|
LOG.info("CLI", `[${this.cliType}] CLI scripts: [${scriptNames.join(", ")}]`);
|
|
@@ -2695,6 +3492,8 @@ var init_provider_cli_adapter = __esm({
|
|
|
2695
3492
|
statusHistory = [];
|
|
2696
3493
|
// ─── CLI Scripts (script-based parsing) ───
|
|
2697
3494
|
cliScripts;
|
|
3495
|
+
/** Per-session opaque state object created by cliScripts.createState(), reset on stop. */
|
|
3496
|
+
scriptState = null;
|
|
2698
3497
|
runtimeSettings = {};
|
|
2699
3498
|
/** Full accumulated rendered PTY transcript for parser/readback use */
|
|
2700
3499
|
accumulatedBuffer = "";
|
|
@@ -2771,9 +3570,13 @@ ${lastSnapshot}`;
|
|
|
2771
3570
|
this.lastScreenChangeAt = 0;
|
|
2772
3571
|
this.lastScreenSnapshotReadAt = Number.NEGATIVE_INFINITY;
|
|
2773
3572
|
}
|
|
3573
|
+
getAccumulatedRawBufferCacheKey() {
|
|
3574
|
+
return this.accumulatedRawBuffer.replace(/\x1B\][^\x07]*(?:\x07|\x1B\\)/g, "").replace(/\x1B[P^_X][\s\S]*?(?:\x07|\x1B\\)/g, "").replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, "");
|
|
3575
|
+
}
|
|
2774
3576
|
getFreshParsedStatusCache() {
|
|
2775
3577
|
const cached = this.parsedStatusCache;
|
|
2776
|
-
|
|
3578
|
+
const accumulatedRawBufferKey = this.getAccumulatedRawBufferCacheKey();
|
|
3579
|
+
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) {
|
|
2777
3580
|
return cached.result;
|
|
2778
3581
|
}
|
|
2779
3582
|
return null;
|
|
@@ -2876,6 +3679,7 @@ ${lastSnapshot}`;
|
|
|
2876
3679
|
this.cliScripts = scripts;
|
|
2877
3680
|
this.parsedStatusCache = null;
|
|
2878
3681
|
this.parseErrorMessage = null;
|
|
3682
|
+
this.scriptState = typeof scripts.createState === "function" ? scripts.createState() ?? null : null;
|
|
2879
3683
|
const scriptNames = listCliScriptNames(scripts);
|
|
2880
3684
|
LOG.info("CLI", `[${this.cliType}] CLI scripts injected: [${scriptNames.join(", ")}]`);
|
|
2881
3685
|
}
|
|
@@ -2993,6 +3797,7 @@ ${lastSnapshot}`;
|
|
|
2993
3797
|
this.ready = false;
|
|
2994
3798
|
this.startupParseGate = false;
|
|
2995
3799
|
this.spawnAt = 0;
|
|
3800
|
+
this.scriptState = null;
|
|
2996
3801
|
this.onStatusChange?.();
|
|
2997
3802
|
});
|
|
2998
3803
|
this.spawnAt = Date.now();
|
|
@@ -3746,6 +4551,11 @@ ${lastSnapshot}`;
|
|
|
3746
4551
|
};
|
|
3747
4552
|
}
|
|
3748
4553
|
// ─── Script Execution ──────────────────────────
|
|
4554
|
+
invokeCliScript(script, input) {
|
|
4555
|
+
const hasStateFactory = typeof this.cliScripts?.createState === "function";
|
|
4556
|
+
const expectsStateArgument = hasStateFactory || this.scriptState !== null || script.length >= 2;
|
|
4557
|
+
return expectsStateArgument ? script(this.scriptState, input) : script(input);
|
|
4558
|
+
}
|
|
3749
4559
|
runParseSession() {
|
|
3750
4560
|
if (typeof this.cliScripts?.parseSession !== "function") {
|
|
3751
4561
|
this.parseErrorMessage = `${this.cliType} parseSession unavailable`;
|
|
@@ -3766,7 +4576,10 @@ ${lastSnapshot}`;
|
|
|
3766
4576
|
scope: this.currentTurnScope,
|
|
3767
4577
|
runtimeSettings: this.runtimeSettings
|
|
3768
4578
|
});
|
|
3769
|
-
const session = this.
|
|
4579
|
+
const session = this.invokeCliScript(
|
|
4580
|
+
this.cliScripts.parseSession,
|
|
4581
|
+
{ ...input, tail, tailScreen: buildCliScreenSnapshot(tail) }
|
|
4582
|
+
);
|
|
3770
4583
|
this.parseErrorMessage = null;
|
|
3771
4584
|
return session && typeof session === "object" ? session : null;
|
|
3772
4585
|
} catch (e) {
|
|
@@ -3780,7 +4593,7 @@ ${lastSnapshot}`;
|
|
|
3780
4593
|
if (!this.cliScripts?.detectStatus) return null;
|
|
3781
4594
|
try {
|
|
3782
4595
|
const screenText = this.terminalScreen.getText();
|
|
3783
|
-
const status = this.cliScripts.detectStatus
|
|
4596
|
+
const status = this.invokeCliScript(this.cliScripts.detectStatus, {
|
|
3784
4597
|
tail: text.slice(-500),
|
|
3785
4598
|
screenText,
|
|
3786
4599
|
rawBuffer: this.accumulatedRawBuffer,
|
|
@@ -3799,7 +4612,7 @@ ${lastSnapshot}`;
|
|
|
3799
4612
|
try {
|
|
3800
4613
|
const screenText = this.terminalScreen.getText();
|
|
3801
4614
|
const buffer = screenText || this.accumulatedBuffer;
|
|
3802
|
-
return this.cliScripts.parseApproval
|
|
4615
|
+
return this.invokeCliScript(this.cliScripts.parseApproval, {
|
|
3803
4616
|
buffer,
|
|
3804
4617
|
screenText,
|
|
3805
4618
|
rawBuffer: this.accumulatedRawBuffer,
|
|
@@ -3855,7 +4668,8 @@ ${lastSnapshot}`;
|
|
|
3855
4668
|
const screenText = this.readTerminalScreenText();
|
|
3856
4669
|
const parseScreenText = this.getParseScreenText(screenText);
|
|
3857
4670
|
const cached = this.parsedStatusCache;
|
|
3858
|
-
|
|
4671
|
+
const accumulatedRawBufferKey = this.getAccumulatedRawBufferCacheKey();
|
|
4672
|
+
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) {
|
|
3859
4673
|
return cached.result;
|
|
3860
4674
|
}
|
|
3861
4675
|
const parsed = this.runParseSession();
|
|
@@ -3883,6 +4697,7 @@ ${lastSnapshot}`;
|
|
|
3883
4697
|
currentTurnScope: this.currentTurnScope,
|
|
3884
4698
|
recentOutputBuffer: this.recentOutputBuffer,
|
|
3885
4699
|
accumulatedBuffer: this.accumulatedBuffer,
|
|
4700
|
+
accumulatedRawBufferKey,
|
|
3886
4701
|
screenText: parseScreenText,
|
|
3887
4702
|
currentStatus: this.currentStatus,
|
|
3888
4703
|
activeModal: this.activeModal,
|
|
@@ -3907,7 +4722,7 @@ ${lastSnapshot}`;
|
|
|
3907
4722
|
scope: this.currentTurnScope,
|
|
3908
4723
|
runtimeSettings: this.runtimeSettings
|
|
3909
4724
|
});
|
|
3910
|
-
return await Promise.resolve(fn
|
|
4725
|
+
return await Promise.resolve(this.invokeCliScript(fn, {
|
|
3911
4726
|
...input,
|
|
3912
4727
|
args: args && typeof args === "object" ? { ...args } : {}
|
|
3913
4728
|
}));
|
|
@@ -6124,7 +6939,7 @@ function addWorkspaceEntry(config, rawPath, label, options) {
|
|
|
6124
6939
|
}
|
|
6125
6940
|
}
|
|
6126
6941
|
const v = validateWorkspacePath(abs);
|
|
6127
|
-
if (
|
|
6942
|
+
if (v.ok !== true) return { error: v.error };
|
|
6128
6943
|
const list = [...config.workspaces || []];
|
|
6129
6944
|
if (list.some((w) => path5.resolve(w.path) === abs)) {
|
|
6130
6945
|
return { error: "Workspace already in list" };
|
|
@@ -6507,36 +7322,188 @@ async function syncMeshes(transport) {
|
|
|
6507
7322
|
}
|
|
6508
7323
|
}
|
|
6509
7324
|
}
|
|
6510
|
-
if (transport.syncMeshLedger) {
|
|
6511
|
-
for (const local of localMeshes) {
|
|
6512
|
-
try {
|
|
6513
|
-
await syncMeshLedger(local.id, transport);
|
|
6514
|
-
} catch (e) {
|
|
6515
|
-
result.errors.push(`Ledger sync failed for "${local.name}": ${e.message}`);
|
|
6516
|
-
}
|
|
6517
|
-
}
|
|
6518
|
-
}
|
|
6519
7325
|
return result;
|
|
6520
7326
|
}
|
|
6521
|
-
async function syncMeshLedger(meshId, transport) {
|
|
6522
|
-
if (!transport.syncMeshLedger) return;
|
|
6523
|
-
const { readLedgerEntries: readLedgerEntries2, appendRemoteLedgerEntries: appendRemoteLedgerEntries2 } = await Promise.resolve().then(() => (init_mesh_ledger(), mesh_ledger_exports));
|
|
6524
|
-
const localEntries = readLedgerEntries2(meshId);
|
|
6525
|
-
const res = await transport.syncMeshLedger(meshId, { newEntries: localEntries });
|
|
6526
|
-
if (res.missingEntries && res.missingEntries.length > 0) {
|
|
6527
|
-
appendRemoteLedgerEntries2(meshId, res.missingEntries);
|
|
6528
|
-
}
|
|
6529
|
-
}
|
|
6530
7327
|
|
|
6531
7328
|
// src/index.ts
|
|
6532
7329
|
init_mesh_ledger();
|
|
7330
|
+
|
|
7331
|
+
// src/mesh/mesh-ledger-reconciliation.ts
|
|
7332
|
+
function lastTimestamp(slice) {
|
|
7333
|
+
const entries = Array.isArray(slice?.entries) ? slice.entries : [];
|
|
7334
|
+
return entries.length ? entries[entries.length - 1].timestamp : null;
|
|
7335
|
+
}
|
|
7336
|
+
function buildMeshLedgerReplicaEvidence(args) {
|
|
7337
|
+
const entriesReceived = Array.isArray(args.slice?.entries) ? args.slice.entries.length : 0;
|
|
7338
|
+
return {
|
|
7339
|
+
nodeId: args.nodeId,
|
|
7340
|
+
...args.daemonId ? { daemonId: args.daemonId } : {},
|
|
7341
|
+
status: args.status ?? (args.importResult && args.importResult.accepted > 0 ? "imported" : "queried"),
|
|
7342
|
+
transport: args.transport,
|
|
7343
|
+
protocol: "adhdev.mesh.ledger.slice.v1",
|
|
7344
|
+
entriesReceived,
|
|
7345
|
+
entriesImported: args.importResult?.accepted ?? 0,
|
|
7346
|
+
skippedDuplicate: args.importResult?.skippedDuplicate ?? 0,
|
|
7347
|
+
rejectedInvalid: args.importResult?.rejectedInvalid ?? 0,
|
|
7348
|
+
hasMore: args.slice?.cursor?.hasMore === true,
|
|
7349
|
+
nextAfterId: args.slice?.cursor?.nextAfterId ?? null,
|
|
7350
|
+
lastTimestamp: lastTimestamp(args.slice),
|
|
7351
|
+
...args.slice?.summary ? { summary: args.slice.summary } : {},
|
|
7352
|
+
...args.error ? {
|
|
7353
|
+
error: args.error,
|
|
7354
|
+
noFallbackReason: "Ledger reconciliation is P2P/local-first only; Cloud/D1 ledger sync is intentionally disabled."
|
|
7355
|
+
} : {}
|
|
7356
|
+
};
|
|
7357
|
+
}
|
|
7358
|
+
function buildMeshLedgerReconciliationEvidence(meshId, replicas) {
|
|
7359
|
+
const failedNodes = replicas.filter((replica) => replica.status === "failed").map((replica) => replica.nodeId);
|
|
7360
|
+
const pendingNodes = replicas.filter((replica) => replica.hasMore && replica.status !== "failed").map((replica) => replica.nodeId);
|
|
7361
|
+
return {
|
|
7362
|
+
protocol: "adhdev.mesh.ledger.reconciliation.v1",
|
|
7363
|
+
meshId,
|
|
7364
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7365
|
+
sourceOfTruth: {
|
|
7366
|
+
kind: "coordinator_local_jsonl",
|
|
7367
|
+
p2pOnly: true,
|
|
7368
|
+
cloudD1LedgerSync: false,
|
|
7369
|
+
notes: "Coordinator reconciles bounded slices from daemon-local JSONL ledgers over P2P DataChannel; Cloud/D1 is not a ledger source of truth."
|
|
7370
|
+
},
|
|
7371
|
+
replicas,
|
|
7372
|
+
totals: {
|
|
7373
|
+
replicas: replicas.length,
|
|
7374
|
+
queried: replicas.filter((replica) => replica.status !== "failed").length,
|
|
7375
|
+
failed: failedNodes.length,
|
|
7376
|
+
entriesReceived: replicas.reduce((sum, replica) => sum + replica.entriesReceived, 0),
|
|
7377
|
+
entriesImported: replicas.reduce((sum, replica) => sum + replica.entriesImported, 0),
|
|
7378
|
+
skippedDuplicate: replicas.reduce((sum, replica) => sum + replica.skippedDuplicate, 0),
|
|
7379
|
+
rejectedInvalid: replicas.reduce((sum, replica) => sum + replica.rejectedInvalid, 0)
|
|
7380
|
+
},
|
|
7381
|
+
convergence: {
|
|
7382
|
+
complete: failedNodes.length === 0 && pendingNodes.length === 0,
|
|
7383
|
+
pendingNodes,
|
|
7384
|
+
failedNodes
|
|
7385
|
+
}
|
|
7386
|
+
};
|
|
7387
|
+
}
|
|
7388
|
+
|
|
7389
|
+
// src/index.ts
|
|
6533
7390
|
init_mesh_work_queue();
|
|
6534
7391
|
init_mesh_events();
|
|
6535
7392
|
|
|
7393
|
+
// src/mesh/p2p-relay-failure.ts
|
|
7394
|
+
var NO_FALLBACK_REASON = "Repo Mesh command/data-plane is P2P-only; WS/REST command fallback is intentionally disabled to preserve the transport boundary.";
|
|
7395
|
+
var P2P_NEXT_ACTION = "Check daemon/P2P health, wait briefly for connection establishment, then do one bounded retry or requeue the mesh task after clearing stale target session metadata.";
|
|
7396
|
+
var NON_P2P_NEXT_ACTION = "Inspect the provider/command error and fix the underlying logic or configuration before retrying.";
|
|
7397
|
+
function messageFromError(error) {
|
|
7398
|
+
if (error instanceof Error) return error.message;
|
|
7399
|
+
if (typeof error === "string") return error;
|
|
7400
|
+
if (error && typeof error === "object") {
|
|
7401
|
+
const candidate = error.error ?? error.message ?? error.reason;
|
|
7402
|
+
if (typeof candidate === "string") return candidate;
|
|
7403
|
+
}
|
|
7404
|
+
return String(error || "mesh relay command failed");
|
|
7405
|
+
}
|
|
7406
|
+
function classifyP2pRelayFailure(error, _context = {}) {
|
|
7407
|
+
const message = messageFromError(error);
|
|
7408
|
+
const lower = message.toLowerCase();
|
|
7409
|
+
const hasP2pSignal = /p2p|datachannel|node-datachannel|webrtc|ice|mesh_relay_command|daemon_mesh_p2p_transport/i.test(message);
|
|
7410
|
+
const hasFailureSignal = /unavailable|missing|failed|failure|timeout|timed out|not connected|closed|disconnected|offline|no route|route unavailable|cannot send|cannot establish/i.test(message);
|
|
7411
|
+
if (/requires targetdaemonid and command|providerpriority|no inference provider|permission denied|read-only|not a member/i.test(message)) {
|
|
7412
|
+
return {
|
|
7413
|
+
code: "mesh_logic_or_provider_failure",
|
|
7414
|
+
reason: "mesh_logic_or_provider_failure",
|
|
7415
|
+
transport: "unknown",
|
|
7416
|
+
recoverable: false,
|
|
7417
|
+
retryRecommended: false,
|
|
7418
|
+
nextAction: NON_P2P_NEXT_ACTION,
|
|
7419
|
+
noFallbackReason: NO_FALLBACK_REASON
|
|
7420
|
+
};
|
|
7421
|
+
}
|
|
7422
|
+
let code = null;
|
|
7423
|
+
let reason = "";
|
|
7424
|
+
if (/timeout|timed out/i.test(message) && (hasP2pSignal || /mesh transport/i.test(message))) {
|
|
7425
|
+
code = "p2p_timeout";
|
|
7426
|
+
reason = "daemon_mesh_p2p_timeout";
|
|
7427
|
+
} else if (/no route|route unavailable/i.test(message)) {
|
|
7428
|
+
code = "p2p_no_route";
|
|
7429
|
+
reason = "daemon_mesh_p2p_no_route";
|
|
7430
|
+
} else if (/offline|not owned|not found|not connected to server/i.test(message) && /daemon|peer|target/i.test(message)) {
|
|
7431
|
+
code = "p2p_daemon_offline";
|
|
7432
|
+
reason = "daemon_mesh_target_offline";
|
|
7433
|
+
} else if (/closed|disconnected/i.test(message) && (hasP2pSignal || /state changed/i.test(message))) {
|
|
7434
|
+
code = "p2p_datachannel_closed";
|
|
7435
|
+
reason = "daemon_mesh_p2p_datachannel_closed";
|
|
7436
|
+
} else if (/not connected|cannot send|cannot establish/i.test(message) && hasP2pSignal) {
|
|
7437
|
+
code = "p2p_not_connected";
|
|
7438
|
+
reason = "daemon_mesh_p2p_not_connected";
|
|
7439
|
+
} else if (hasP2pSignal && hasFailureSignal) {
|
|
7440
|
+
code = "p2p_unavailable";
|
|
7441
|
+
reason = "daemon_mesh_p2p_transport_unavailable";
|
|
7442
|
+
}
|
|
7443
|
+
if (!code) {
|
|
7444
|
+
return {
|
|
7445
|
+
code: "mesh_logic_or_provider_failure",
|
|
7446
|
+
reason: "mesh_logic_or_provider_failure",
|
|
7447
|
+
transport: "unknown",
|
|
7448
|
+
recoverable: false,
|
|
7449
|
+
retryRecommended: false,
|
|
7450
|
+
nextAction: NON_P2P_NEXT_ACTION,
|
|
7451
|
+
noFallbackReason: NO_FALLBACK_REASON
|
|
7452
|
+
};
|
|
7453
|
+
}
|
|
7454
|
+
return {
|
|
7455
|
+
code,
|
|
7456
|
+
reason,
|
|
7457
|
+
transport: "p2p",
|
|
7458
|
+
recoverable: true,
|
|
7459
|
+
retryRecommended: true,
|
|
7460
|
+
nextAction: P2P_NEXT_ACTION,
|
|
7461
|
+
noFallbackReason: NO_FALLBACK_REASON
|
|
7462
|
+
};
|
|
7463
|
+
}
|
|
7464
|
+
function isP2pRelayTransportFailure(error) {
|
|
7465
|
+
return classifyP2pRelayFailure(error).recoverable === true;
|
|
7466
|
+
}
|
|
7467
|
+
function buildP2pRelayFailurePayload(error, context = {}) {
|
|
7468
|
+
const classification = classifyP2pRelayFailure(error, context);
|
|
7469
|
+
return {
|
|
7470
|
+
success: false,
|
|
7471
|
+
...classification,
|
|
7472
|
+
error: messageFromError(error),
|
|
7473
|
+
...context.command ? { command: context.command } : {},
|
|
7474
|
+
...context.targetDaemonId ? { targetDaemonId: context.targetDaemonId } : {}
|
|
7475
|
+
};
|
|
7476
|
+
}
|
|
7477
|
+
var P2pRelayFailureError = class extends Error {
|
|
7478
|
+
code;
|
|
7479
|
+
reason;
|
|
7480
|
+
transport;
|
|
7481
|
+
recoverable;
|
|
7482
|
+
retryRecommended;
|
|
7483
|
+
nextAction;
|
|
7484
|
+
noFallbackReason;
|
|
7485
|
+
command;
|
|
7486
|
+
targetDaemonId;
|
|
7487
|
+
constructor(message, context = {}) {
|
|
7488
|
+
super(message);
|
|
7489
|
+
this.name = "P2pRelayFailureError";
|
|
7490
|
+
const payload = buildP2pRelayFailurePayload(message, context);
|
|
7491
|
+
this.code = payload.code;
|
|
7492
|
+
this.reason = payload.reason;
|
|
7493
|
+
this.transport = payload.transport;
|
|
7494
|
+
this.recoverable = payload.recoverable;
|
|
7495
|
+
this.retryRecommended = payload.retryRecommended;
|
|
7496
|
+
this.nextAction = payload.nextAction;
|
|
7497
|
+
this.noFallbackReason = payload.noFallbackReason;
|
|
7498
|
+
this.command = context.command;
|
|
7499
|
+
this.targetDaemonId = context.targetDaemonId;
|
|
7500
|
+
}
|
|
7501
|
+
};
|
|
7502
|
+
|
|
6536
7503
|
// src/config/state-store.ts
|
|
6537
7504
|
init_config();
|
|
6538
|
-
import { existsSync as
|
|
6539
|
-
import { join as
|
|
7505
|
+
import { existsSync as existsSync9, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
7506
|
+
import { join as join9 } from "path";
|
|
6540
7507
|
var DEFAULT_STATE = {
|
|
6541
7508
|
recentActivity: [],
|
|
6542
7509
|
savedProviderSessions: [],
|
|
@@ -6549,7 +7516,7 @@ function isPlainObject2(value) {
|
|
|
6549
7516
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
6550
7517
|
}
|
|
6551
7518
|
function getStatePath() {
|
|
6552
|
-
return
|
|
7519
|
+
return join9(getConfigDir(), "state.json");
|
|
6553
7520
|
}
|
|
6554
7521
|
function normalizeState(raw) {
|
|
6555
7522
|
const parsed = isPlainObject2(raw) ? raw : {};
|
|
@@ -6585,7 +7552,7 @@ function normalizeState(raw) {
|
|
|
6585
7552
|
}
|
|
6586
7553
|
function loadState() {
|
|
6587
7554
|
const statePath = getStatePath();
|
|
6588
|
-
if (!
|
|
7555
|
+
if (!existsSync9(statePath)) {
|
|
6589
7556
|
return { ...DEFAULT_STATE };
|
|
6590
7557
|
}
|
|
6591
7558
|
try {
|
|
@@ -6606,9 +7573,9 @@ function resetState() {
|
|
|
6606
7573
|
|
|
6607
7574
|
// src/detection/ide-detector.ts
|
|
6608
7575
|
import { execSync } from "child_process";
|
|
6609
|
-
import { existsSync as
|
|
6610
|
-
import { platform, homedir as
|
|
6611
|
-
import * as
|
|
7576
|
+
import { existsSync as existsSync10 } from "fs";
|
|
7577
|
+
import { platform as platform2, homedir as homedir5 } from "os";
|
|
7578
|
+
import * as path10 from "path";
|
|
6612
7579
|
var BUILTIN_IDE_DEFINITIONS = [];
|
|
6613
7580
|
var registeredIDEs = /* @__PURE__ */ new Map();
|
|
6614
7581
|
function registerIDEDefinition(def) {
|
|
@@ -6627,14 +7594,14 @@ function getMergedDefinitions() {
|
|
|
6627
7594
|
function findCliCommand(command) {
|
|
6628
7595
|
const trimmed = String(command || "").trim();
|
|
6629
7596
|
if (!trimmed) return null;
|
|
6630
|
-
if (
|
|
6631
|
-
const candidate = trimmed.startsWith("~") ?
|
|
6632
|
-
const resolved =
|
|
6633
|
-
return
|
|
7597
|
+
if (path10.isAbsolute(trimmed) || trimmed.includes("/") || trimmed.includes("\\") || trimmed.startsWith("~")) {
|
|
7598
|
+
const candidate = trimmed.startsWith("~") ? path10.join(homedir5(), trimmed.slice(1)) : trimmed;
|
|
7599
|
+
const resolved = path10.isAbsolute(candidate) ? candidate : path10.resolve(candidate);
|
|
7600
|
+
return existsSync10(resolved) ? resolved : null;
|
|
6634
7601
|
}
|
|
6635
7602
|
try {
|
|
6636
7603
|
const result = execSync(
|
|
6637
|
-
|
|
7604
|
+
platform2() === "win32" ? `where ${trimmed}` : `which ${trimmed}`,
|
|
6638
7605
|
{ encoding: "utf-8", timeout: 5e3, stdio: ["pipe", "pipe", "pipe"] }
|
|
6639
7606
|
).trim();
|
|
6640
7607
|
return result.split("\n")[0] || null;
|
|
@@ -6655,21 +7622,21 @@ function getIdeVersion(cliCommand) {
|
|
|
6655
7622
|
}
|
|
6656
7623
|
}
|
|
6657
7624
|
function checkPathExists(paths) {
|
|
6658
|
-
const home =
|
|
7625
|
+
const home = homedir5();
|
|
6659
7626
|
for (const p of paths) {
|
|
6660
|
-
const normalized = p.startsWith("~") ?
|
|
7627
|
+
const normalized = p.startsWith("~") ? path10.join(home, p.slice(1)) : p;
|
|
6661
7628
|
if (normalized.includes("*")) {
|
|
6662
7629
|
const username = home.split(/[\\/]/).pop() || "";
|
|
6663
7630
|
const resolved = normalized.replace("*", username);
|
|
6664
|
-
if (
|
|
7631
|
+
if (existsSync10(resolved)) return resolved;
|
|
6665
7632
|
} else {
|
|
6666
|
-
if (
|
|
7633
|
+
if (existsSync10(normalized)) return normalized;
|
|
6667
7634
|
}
|
|
6668
7635
|
}
|
|
6669
7636
|
return null;
|
|
6670
7637
|
}
|
|
6671
7638
|
async function detectIDEs(providerLoader) {
|
|
6672
|
-
const os22 =
|
|
7639
|
+
const os22 = platform2();
|
|
6673
7640
|
const results = [];
|
|
6674
7641
|
for (const def of getMergedDefinitions()) {
|
|
6675
7642
|
const cliPath = findCliCommand(providerLoader?.getIdeCliCommand(def.id, def.cli) || def.cli);
|
|
@@ -6677,7 +7644,7 @@ async function detectIDEs(providerLoader) {
|
|
|
6677
7644
|
let resolvedCli = cliPath;
|
|
6678
7645
|
if (!resolvedCli && appPath && os22 === "darwin") {
|
|
6679
7646
|
const bundledCli = `${appPath}/Contents/Resources/app/bin/${def.cli}`;
|
|
6680
|
-
if (
|
|
7647
|
+
if (existsSync10(bundledCli)) resolvedCli = bundledCli;
|
|
6681
7648
|
}
|
|
6682
7649
|
if (!resolvedCli && appPath && os22 === "win32") {
|
|
6683
7650
|
const { dirname: dirname9 } = await import("path");
|
|
@@ -6690,7 +7657,7 @@ async function detectIDEs(providerLoader) {
|
|
|
6690
7657
|
`${appDir}\\\\resources\\\\app\\\\bin\\\\${def.cli}.cmd`
|
|
6691
7658
|
];
|
|
6692
7659
|
for (const c of candidates) {
|
|
6693
|
-
if (
|
|
7660
|
+
if (existsSync10(c)) {
|
|
6694
7661
|
resolvedCli = c;
|
|
6695
7662
|
break;
|
|
6696
7663
|
}
|
|
@@ -6712,134 +7679,8 @@ async function detectIDEs(providerLoader) {
|
|
|
6712
7679
|
return results;
|
|
6713
7680
|
}
|
|
6714
7681
|
|
|
6715
|
-
// src/
|
|
6716
|
-
|
|
6717
|
-
import * as os3 from "os";
|
|
6718
|
-
import * as path10 from "path";
|
|
6719
|
-
import { existsSync as existsSync10 } from "fs";
|
|
6720
|
-
function parseVersion(raw) {
|
|
6721
|
-
const match = raw.match(/v?(\d+\.\d+(?:\.\d+)?(?:-[a-zA-Z0-9.]+)?)/);
|
|
6722
|
-
return match ? match[1] : raw.split("\n")[0].slice(0, 100);
|
|
6723
|
-
}
|
|
6724
|
-
function shellQuote(value) {
|
|
6725
|
-
if (/^[a-zA-Z0-9_./:@%+=,-]+$/.test(value)) return value;
|
|
6726
|
-
return `"${value.replace(/(["\\$`])/g, "\\$1")}"`;
|
|
6727
|
-
}
|
|
6728
|
-
function expandHome(value) {
|
|
6729
|
-
const trimmed = value.trim();
|
|
6730
|
-
if (!trimmed.startsWith("~")) return trimmed;
|
|
6731
|
-
return path10.join(os3.homedir(), trimmed.slice(1));
|
|
6732
|
-
}
|
|
6733
|
-
function isExplicitCommandPath(command) {
|
|
6734
|
-
const trimmed = command.trim();
|
|
6735
|
-
return path10.isAbsolute(trimmed) || trimmed.includes("/") || trimmed.includes("\\") || trimmed.startsWith("~");
|
|
6736
|
-
}
|
|
6737
|
-
function resolveCommandPath(command) {
|
|
6738
|
-
const trimmed = command.trim();
|
|
6739
|
-
if (!trimmed) return null;
|
|
6740
|
-
if (isExplicitCommandPath(trimmed)) {
|
|
6741
|
-
const expanded = expandHome(trimmed);
|
|
6742
|
-
const candidate = path10.isAbsolute(expanded) ? expanded : path10.resolve(expanded);
|
|
6743
|
-
return existsSync10(candidate) ? candidate : null;
|
|
6744
|
-
}
|
|
6745
|
-
return null;
|
|
6746
|
-
}
|
|
6747
|
-
function execAsync(cmd, timeoutMs = 5e3) {
|
|
6748
|
-
return new Promise((resolve16) => {
|
|
6749
|
-
const child = exec(cmd, {
|
|
6750
|
-
encoding: "utf-8",
|
|
6751
|
-
timeout: timeoutMs,
|
|
6752
|
-
...process.platform === "win32" ? { windowsHide: true } : {}
|
|
6753
|
-
}, (err, stdout) => {
|
|
6754
|
-
if (err || !stdout?.trim()) {
|
|
6755
|
-
resolve16(null);
|
|
6756
|
-
} else {
|
|
6757
|
-
resolve16(stdout.trim());
|
|
6758
|
-
}
|
|
6759
|
-
});
|
|
6760
|
-
child.on("error", () => resolve16(null));
|
|
6761
|
-
});
|
|
6762
|
-
}
|
|
6763
|
-
async function detectCLIs(providerLoader, options) {
|
|
6764
|
-
const platform10 = os3.platform();
|
|
6765
|
-
const whichCmd = platform10 === "win32" ? "where" : "which";
|
|
6766
|
-
const includeVersion = options?.includeVersion !== false;
|
|
6767
|
-
const cliList = providerLoader ? providerLoader.getCliDetectionList() : [];
|
|
6768
|
-
const results = await Promise.all(
|
|
6769
|
-
cliList.map(async (cli) => {
|
|
6770
|
-
try {
|
|
6771
|
-
const explicitPath = resolveCommandPath(cli.command);
|
|
6772
|
-
const pathResult = explicitPath || await execAsync(`${whichCmd} ${shellQuote(cli.command)}`);
|
|
6773
|
-
if (!pathResult) return { ...cli, installed: false };
|
|
6774
|
-
const firstPath = explicitPath || pathResult.split("\n")[0];
|
|
6775
|
-
let version;
|
|
6776
|
-
if (includeVersion) {
|
|
6777
|
-
const versionCommands = [
|
|
6778
|
-
`"${firstPath}" --version`,
|
|
6779
|
-
`"${firstPath}" -V`,
|
|
6780
|
-
`"${firstPath}" -v`,
|
|
6781
|
-
cli.versionCommand
|
|
6782
|
-
].filter((v) => !!v);
|
|
6783
|
-
try {
|
|
6784
|
-
for (const versionCommand of versionCommands) {
|
|
6785
|
-
const versionResult = await execAsync(versionCommand, 3e3);
|
|
6786
|
-
if (versionResult) {
|
|
6787
|
-
version = parseVersion(versionResult);
|
|
6788
|
-
break;
|
|
6789
|
-
}
|
|
6790
|
-
}
|
|
6791
|
-
} catch {
|
|
6792
|
-
}
|
|
6793
|
-
}
|
|
6794
|
-
return { ...cli, installed: true, version, path: firstPath };
|
|
6795
|
-
} catch {
|
|
6796
|
-
return { ...cli, installed: false };
|
|
6797
|
-
}
|
|
6798
|
-
})
|
|
6799
|
-
);
|
|
6800
|
-
return results;
|
|
6801
|
-
}
|
|
6802
|
-
async function detectCLI(cliId, providerLoader, options) {
|
|
6803
|
-
const resolvedId = providerLoader ? providerLoader.resolveAlias(cliId) : cliId;
|
|
6804
|
-
if (providerLoader) {
|
|
6805
|
-
const cliList = providerLoader.getCliDetectionList();
|
|
6806
|
-
const target = cliList.find((c) => c.id === resolvedId);
|
|
6807
|
-
if (target) {
|
|
6808
|
-
const platform10 = os3.platform();
|
|
6809
|
-
const whichCmd = platform10 === "win32" ? "where" : "which";
|
|
6810
|
-
try {
|
|
6811
|
-
const explicitPath = resolveCommandPath(target.command);
|
|
6812
|
-
const pathResult = explicitPath || await execAsync(`${whichCmd} ${shellQuote(target.command)}`);
|
|
6813
|
-
if (!pathResult) return null;
|
|
6814
|
-
const firstPath = explicitPath || pathResult.split("\n")[0];
|
|
6815
|
-
let version;
|
|
6816
|
-
if (options?.includeVersion !== false) {
|
|
6817
|
-
const versionCommands = [
|
|
6818
|
-
`"${firstPath}" --version`,
|
|
6819
|
-
`"${firstPath}" -V`,
|
|
6820
|
-
`"${firstPath}" -v`,
|
|
6821
|
-
target.versionCommand
|
|
6822
|
-
].filter((v) => !!v);
|
|
6823
|
-
try {
|
|
6824
|
-
for (const versionCommand of versionCommands) {
|
|
6825
|
-
const versionResult = await execAsync(versionCommand, 3e3);
|
|
6826
|
-
if (versionResult) {
|
|
6827
|
-
version = parseVersion(versionResult);
|
|
6828
|
-
break;
|
|
6829
|
-
}
|
|
6830
|
-
}
|
|
6831
|
-
} catch {
|
|
6832
|
-
}
|
|
6833
|
-
}
|
|
6834
|
-
return { ...target, installed: true, version, path: firstPath };
|
|
6835
|
-
} catch {
|
|
6836
|
-
return null;
|
|
6837
|
-
}
|
|
6838
|
-
}
|
|
6839
|
-
}
|
|
6840
|
-
const all = await detectCLIs(providerLoader, options);
|
|
6841
|
-
return all.find((c) => c.id === resolvedId && c.installed) || null;
|
|
6842
|
-
}
|
|
7682
|
+
// src/index.ts
|
|
7683
|
+
init_cli_detector();
|
|
6843
7684
|
|
|
6844
7685
|
// src/system/host-memory.ts
|
|
6845
7686
|
import * as os4 from "os";
|
|
@@ -8708,6 +9549,28 @@ var StatusMonitor = class {
|
|
|
8708
9549
|
};
|
|
8709
9550
|
|
|
8710
9551
|
// src/providers/chat-message-normalization.ts
|
|
9552
|
+
function extractFinalSummaryFromMessages(messages, maxChars = 500) {
|
|
9553
|
+
if (!Array.isArray(messages) || messages.length === 0) return "";
|
|
9554
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
9555
|
+
const msg = messages[i];
|
|
9556
|
+
if (!msg) continue;
|
|
9557
|
+
const classification = classifyChatMessageVisibility(msg);
|
|
9558
|
+
if (classification.isUserFacing && (msg.role === "assistant" || msg.role === "model")) {
|
|
9559
|
+
const text = flattenContent(msg.content).trim();
|
|
9560
|
+
if (text) return text.slice(0, maxChars);
|
|
9561
|
+
}
|
|
9562
|
+
}
|
|
9563
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
9564
|
+
const msg = messages[i];
|
|
9565
|
+
if (!msg) continue;
|
|
9566
|
+
const classification = classifyChatMessageVisibility(msg);
|
|
9567
|
+
if (classification.isUserFacing) {
|
|
9568
|
+
const text = flattenContent(msg.content).trim();
|
|
9569
|
+
if (text) return text.slice(0, maxChars);
|
|
9570
|
+
}
|
|
9571
|
+
}
|
|
9572
|
+
return "";
|
|
9573
|
+
}
|
|
8711
9574
|
var BUILTIN_CHAT_MESSAGE_KINDS = ["standard", "thought", "tool", "terminal", "system"];
|
|
8712
9575
|
var CHAT_MESSAGE_VISIBILITIES = ["user", "debug", "internal", "hidden"];
|
|
8713
9576
|
var CHAT_MESSAGE_TRANSCRIPT_VISIBILITIES = ["visible", "chat", "user", "debug", "internal", "hidden"];
|
|
@@ -10719,7 +11582,8 @@ var ExtensionProviderInstance = class {
|
|
|
10719
11582
|
ideType: this.ideType || this.type,
|
|
10720
11583
|
agentType: this.type,
|
|
10721
11584
|
agentName: this.agentName || this.provider.name,
|
|
10722
|
-
extensionId: this.extensionId || this.type
|
|
11585
|
+
extensionId: this.extensionId || this.type,
|
|
11586
|
+
finalSummary: extractFinalSummaryFromMessages(data?.messages)
|
|
10723
11587
|
});
|
|
10724
11588
|
this.generatingStartedAt = 0;
|
|
10725
11589
|
}
|
|
@@ -11487,7 +12351,7 @@ var IdeProviderInstance = class {
|
|
|
11487
12351
|
} else if (agentStatus === "idle" && (lastStatus === "generating" || lastStatus === "waiting_approval")) {
|
|
11488
12352
|
const startedAt = this.generatingStartedAt.get(agentKey);
|
|
11489
12353
|
const duration = startedAt ? Math.round((now - startedAt) / 1e3) : 0;
|
|
11490
|
-
this.pushEvent({ event: "agent:generating_completed", chatTitle, duration, timestamp: now, ideType: this.type });
|
|
12354
|
+
this.pushEvent({ event: "agent:generating_completed", chatTitle, duration, timestamp: now, ideType: this.type, finalSummary: extractFinalSummaryFromMessages(chatData?.messages) });
|
|
11491
12355
|
this.generatingStartedAt.delete(agentKey);
|
|
11492
12356
|
}
|
|
11493
12357
|
this.lastAgentStatuses.set(agentKey, agentStatus);
|
|
@@ -14983,11 +15847,13 @@ async function handleOpenPanel(h, args) {
|
|
|
14983
15847
|
async function handlePtyInput(h, args) {
|
|
14984
15848
|
const { cliType, data, targetSessionId } = args || {};
|
|
14985
15849
|
if (!data) return { success: false, error: "data required" };
|
|
15850
|
+
const cleanData = typeof data === "string" ? data.replace(/\x1b\[[?>][0-9;]*c/g, "") : data;
|
|
15851
|
+
if (!cleanData) return { success: true };
|
|
14986
15852
|
const adapter = h.getCliAdapter(targetSessionId || cliType);
|
|
14987
15853
|
if (!adapter || typeof adapter.writeRaw !== "function") {
|
|
14988
15854
|
return { success: false, error: `CLI adapter not found: ${targetSessionId || cliType || "unknown"}` };
|
|
14989
15855
|
}
|
|
14990
|
-
await adapter.writeRaw(
|
|
15856
|
+
await adapter.writeRaw(cleanData);
|
|
14991
15857
|
return { success: true };
|
|
14992
15858
|
}
|
|
14993
15859
|
function handlePtyResize(_h, args) {
|
|
@@ -15941,13 +16807,14 @@ var DaemonCommandHandler = class {
|
|
|
15941
16807
|
|
|
15942
16808
|
// src/commands/cli-manager.ts
|
|
15943
16809
|
init_provider_cli_adapter();
|
|
16810
|
+
init_cli_detector();
|
|
16811
|
+
init_config();
|
|
15944
16812
|
import * as os13 from "os";
|
|
15945
16813
|
import * as path18 from "path";
|
|
15946
16814
|
import * as crypto4 from "crypto";
|
|
15947
16815
|
import { existsSync as existsSync14, mkdirSync as mkdirSync9, writeFileSync as writeFileSync9 } from "fs";
|
|
15948
16816
|
import { execFileSync } from "child_process";
|
|
15949
16817
|
import chalk from "chalk";
|
|
15950
|
-
init_config();
|
|
15951
16818
|
|
|
15952
16819
|
// src/providers/cli-provider-instance.ts
|
|
15953
16820
|
import * as os12 from "os";
|
|
@@ -15976,6 +16843,8 @@ function normalizeProviderSessionId(provider, providerSessionId) {
|
|
|
15976
16843
|
}
|
|
15977
16844
|
|
|
15978
16845
|
// src/providers/cli-provider-instance.ts
|
|
16846
|
+
var COMPLETED_FINALIZATION_RETRY_MS = 1e3;
|
|
16847
|
+
var COMPLETED_FINALIZATION_MAX_WAIT_MS = 3e4;
|
|
15979
16848
|
var IMAGE_MIME_EXTENSIONS = {
|
|
15980
16849
|
"image/png": ".png",
|
|
15981
16850
|
"image/jpeg": ".jpg",
|
|
@@ -16039,6 +16908,13 @@ function cleanupStaleMaterializedImages(dir) {
|
|
|
16039
16908
|
} catch {
|
|
16040
16909
|
}
|
|
16041
16910
|
}
|
|
16911
|
+
function hasNonEmptyCliModalButtons(activeModal) {
|
|
16912
|
+
const buttons = activeModal?.buttons;
|
|
16913
|
+
return Array.isArray(buttons) && buttons.some((button) => String(button || "").trim().length > 0);
|
|
16914
|
+
}
|
|
16915
|
+
function isCliGeneratingLikeStatus(status) {
|
|
16916
|
+
return status === "generating" || status === "streaming" || status === "long_generating" || status === "starting";
|
|
16917
|
+
}
|
|
16042
16918
|
function buildCliStructuredInputPrompt(input, options = {}) {
|
|
16043
16919
|
const promptParts = [];
|
|
16044
16920
|
const imageRefs = [];
|
|
@@ -16323,10 +17199,12 @@ var CliProviderInstance = class {
|
|
|
16323
17199
|
const mergedMessages = this.mergeConversationMessages(parsedMessages);
|
|
16324
17200
|
const canonicalBackedHistory = this.syncCanonicalSavedHistoryIfNeeded();
|
|
16325
17201
|
const dirName = this.workingDir.split("/").filter(Boolean).pop() || "session";
|
|
17202
|
+
const parsedChatStatus = typeof parsedStatus?.status === "string" && parsedStatus.status.trim() ? parsedStatus.status.trim() : void 0;
|
|
17203
|
+
const suppressStaleParsedBusyStatus = this.shouldSuppressStaleParsedBusyStatus(parsedStatus, adapterStatus);
|
|
16326
17204
|
if (parsedMessages.length > 0) {
|
|
16327
17205
|
const shouldSkipReplayPersist = this.suppressIdleHistoryReplay && adapterStatus.status === "idle" && parsedStatus?.status === "idle";
|
|
16328
17206
|
let messagesToSave = parsedMessages;
|
|
16329
|
-
if (
|
|
17207
|
+
if (!suppressStaleParsedBusyStatus && (parsedChatStatus === "generating" || parsedChatStatus === "long_generating")) {
|
|
16330
17208
|
const lastIdx = messagesToSave.length - 1;
|
|
16331
17209
|
if (lastIdx >= 0 && messagesToSave[lastIdx]?.role === "assistant") {
|
|
16332
17210
|
messagesToSave = messagesToSave.slice(0, lastIdx);
|
|
@@ -16360,6 +17238,7 @@ var CliProviderInstance = class {
|
|
|
16360
17238
|
summaryMetadata: this.summaryMetadata,
|
|
16361
17239
|
controlValues: this.controlValues
|
|
16362
17240
|
});
|
|
17241
|
+
const activeChatStatus = parseErrorMessage ? "error" : autoApproveActive && parsedStatus?.status === "waiting_approval" ? "generating" : adapterStatus.status !== "idle" ? visibleStatus : suppressStaleParsedBusyStatus ? visibleStatus : parsedChatStatus || visibleStatus;
|
|
16363
17242
|
return {
|
|
16364
17243
|
type: this.type,
|
|
16365
17244
|
name: this.provider.name,
|
|
@@ -16369,7 +17248,7 @@ var CliProviderInstance = class {
|
|
|
16369
17248
|
activeChat: {
|
|
16370
17249
|
id: `${this.type}_${this.workingDir}`,
|
|
16371
17250
|
title: parsedStatus?.title || dirName,
|
|
16372
|
-
status:
|
|
17251
|
+
status: activeChatStatus,
|
|
16373
17252
|
messages: mergedMessages,
|
|
16374
17253
|
activeModal: autoApproveActive ? null : parsedStatus?.activeModal ?? adapterStatus.activeModal,
|
|
16375
17254
|
inputContent: ""
|
|
@@ -16497,7 +17376,104 @@ var CliProviderInstance = class {
|
|
|
16497
17376
|
await this.adapter.writeRaw("\r");
|
|
16498
17377
|
}
|
|
16499
17378
|
}
|
|
16500
|
-
this.applyProviderResponse(parsed.payload, { phase: "immediate" });
|
|
17379
|
+
this.applyProviderResponse(parsed.payload, { phase: "immediate" });
|
|
17380
|
+
}
|
|
17381
|
+
completionHasFinalAssistantMessage(messages) {
|
|
17382
|
+
const visibleMessages = (Array.isArray(messages) ? messages : []).filter((message) => isUserFacingChatMessage(message));
|
|
17383
|
+
const lastVisible = visibleMessages[visibleMessages.length - 1];
|
|
17384
|
+
const role = typeof lastVisible?.role === "string" ? lastVisible.role.trim().toLowerCase() : "";
|
|
17385
|
+
const content = lastVisible ? flattenContent(lastVisible.content).trim() : "";
|
|
17386
|
+
return role === "assistant" && !!content;
|
|
17387
|
+
}
|
|
17388
|
+
hasAdapterPendingResponse() {
|
|
17389
|
+
const adapterAny = this.adapter;
|
|
17390
|
+
if (adapterAny?.isWaitingForResponse === true) return true;
|
|
17391
|
+
if (adapterAny?.currentTurnScope) return true;
|
|
17392
|
+
try {
|
|
17393
|
+
if (typeof this.adapter.isProcessing === "function" && this.adapter.isProcessing()) return true;
|
|
17394
|
+
} catch {
|
|
17395
|
+
}
|
|
17396
|
+
try {
|
|
17397
|
+
const partial = typeof this.adapter.getPartialResponse === "function" ? this.adapter.getPartialResponse() : "";
|
|
17398
|
+
if (typeof partial === "string" && partial.trim()) return true;
|
|
17399
|
+
} catch {
|
|
17400
|
+
}
|
|
17401
|
+
return false;
|
|
17402
|
+
}
|
|
17403
|
+
shouldSuppressStaleParsedBusyStatus(parsedStatus, adapterStatus) {
|
|
17404
|
+
const parsedRawStatus = typeof parsedStatus?.status === "string" ? parsedStatus.status.trim() : "";
|
|
17405
|
+
const adapterRawStatus = typeof adapterStatus?.status === "string" ? adapterStatus.status.trim() : "";
|
|
17406
|
+
if (!isCliGeneratingLikeStatus(parsedRawStatus)) return false;
|
|
17407
|
+
if (adapterRawStatus !== "idle") return false;
|
|
17408
|
+
if (hasNonEmptyCliModalButtons(parsedStatus?.activeModal ?? parsedStatus?.modal)) return false;
|
|
17409
|
+
return !this.hasAdapterPendingResponse();
|
|
17410
|
+
}
|
|
17411
|
+
getCompletedFinalizationBlockReason(latestVisibleStatus) {
|
|
17412
|
+
if (latestVisibleStatus !== "idle") return `status:${latestVisibleStatus}`;
|
|
17413
|
+
const adapterAny = this.adapter;
|
|
17414
|
+
if (adapterAny?.isWaitingForResponse === true) return "adapter_waiting_for_response";
|
|
17415
|
+
if (adapterAny?.currentTurnScope) return "adapter_turn_scope_active";
|
|
17416
|
+
const partial = typeof this.adapter.getPartialResponse === "function" ? this.adapter.getPartialResponse() : "";
|
|
17417
|
+
if (typeof partial === "string" && partial.trim()) return "partial_response_pending";
|
|
17418
|
+
let parsed;
|
|
17419
|
+
try {
|
|
17420
|
+
parsed = this.adapter.getScriptParsedStatus();
|
|
17421
|
+
} catch (error) {
|
|
17422
|
+
return `parse_error:${error?.message || String(error)}`;
|
|
17423
|
+
}
|
|
17424
|
+
const parsedStatus = typeof parsed?.status === "string" ? parsed.status : "unknown";
|
|
17425
|
+
if (parsedStatus !== "idle") return `parsed_status:${parsedStatus}`;
|
|
17426
|
+
if (parsed?.activeModal || parsed?.modal) return "parsed_modal_active";
|
|
17427
|
+
if (!this.completionHasFinalAssistantMessage(parsed?.messages)) return "missing_final_assistant";
|
|
17428
|
+
return null;
|
|
17429
|
+
}
|
|
17430
|
+
scheduleCompletedDebounceFlush(delayMs) {
|
|
17431
|
+
if (this.completedDebounceTimer) clearTimeout(this.completedDebounceTimer);
|
|
17432
|
+
this.completedDebounceTimer = setTimeout(() => this.flushCompletedDebounceIfFinalized(), delayMs);
|
|
17433
|
+
}
|
|
17434
|
+
flushCompletedDebounceIfFinalized() {
|
|
17435
|
+
const pending = this.completedDebouncePending;
|
|
17436
|
+
if (!pending) {
|
|
17437
|
+
this.completedDebounceTimer = null;
|
|
17438
|
+
return;
|
|
17439
|
+
}
|
|
17440
|
+
const latestStatus = this.adapter.getStatus({ allowParse: false });
|
|
17441
|
+
const latestAutoApproveActive = latestStatus.status === "waiting_approval" && this.shouldAutoApprove();
|
|
17442
|
+
const latestVisibleStatus = latestAutoApproveActive ? "generating" : latestStatus.status;
|
|
17443
|
+
if (latestVisibleStatus !== "idle") {
|
|
17444
|
+
LOG.info("CLI", `[${this.type}] cancelled pending completed (resumed ${latestVisibleStatus})`);
|
|
17445
|
+
this.completedDebouncePending = null;
|
|
17446
|
+
this.completedDebounceTimer = null;
|
|
17447
|
+
return;
|
|
17448
|
+
}
|
|
17449
|
+
const blockReason = this.getCompletedFinalizationBlockReason(latestVisibleStatus);
|
|
17450
|
+
if (blockReason) {
|
|
17451
|
+
const waitedMs = Date.now() - pending.firstObservedAt;
|
|
17452
|
+
if (waitedMs < COMPLETED_FINALIZATION_MAX_WAIT_MS) {
|
|
17453
|
+
if (pending.loggedBlockReason !== blockReason) {
|
|
17454
|
+
LOG.info("CLI", `[${this.type}] waiting to emit completed until transcript finalizes (${blockReason})`);
|
|
17455
|
+
pending.loggedBlockReason = blockReason;
|
|
17456
|
+
}
|
|
17457
|
+
this.scheduleCompletedDebounceFlush(COMPLETED_FINALIZATION_RETRY_MS);
|
|
17458
|
+
return;
|
|
17459
|
+
}
|
|
17460
|
+
LOG.warn("CLI", `[${this.type}] suppressed completed event after ${waitedMs}ms without finalized assistant turn (${blockReason})`);
|
|
17461
|
+
this.completedDebouncePending = null;
|
|
17462
|
+
this.completedDebounceTimer = null;
|
|
17463
|
+
this.generatingStartedAt = 0;
|
|
17464
|
+
return;
|
|
17465
|
+
}
|
|
17466
|
+
LOG.info("CLI", `[${this.type}] completed in ${pending.duration}s`);
|
|
17467
|
+
this.pushEvent({
|
|
17468
|
+
event: "agent:generating_completed",
|
|
17469
|
+
chatTitle: pending.chatTitle,
|
|
17470
|
+
duration: pending.duration,
|
|
17471
|
+
timestamp: pending.timestamp,
|
|
17472
|
+
finalSummary: extractFinalSummaryFromMessages(this.adapter?.getScriptParsedStatus()?.messages)
|
|
17473
|
+
});
|
|
17474
|
+
this.completedDebouncePending = null;
|
|
17475
|
+
this.completedDebounceTimer = null;
|
|
17476
|
+
this.generatingStartedAt = 0;
|
|
16501
17477
|
}
|
|
16502
17478
|
maybeAutoApproveStatus(adapterStatus, now = Date.now()) {
|
|
16503
17479
|
const autoApproveActive = adapterStatus?.status === "waiting_approval" && this.shouldAutoApprove();
|
|
@@ -16596,27 +17572,11 @@ var CliProviderInstance = class {
|
|
|
16596
17572
|
this.generatingDebouncePending = null;
|
|
16597
17573
|
this.generatingStartedAt = 0;
|
|
16598
17574
|
} else {
|
|
16599
|
-
|
|
16600
|
-
this.
|
|
16601
|
-
this.completedDebounceTimer = setTimeout(() => {
|
|
16602
|
-
if (this.completedDebouncePending) {
|
|
16603
|
-
const latestStatus = this.adapter.getStatus({ allowParse: false });
|
|
16604
|
-
const latestAutoApproveActive = latestStatus.status === "waiting_approval" && this.shouldAutoApprove();
|
|
16605
|
-
const latestVisibleStatus = latestAutoApproveActive ? "generating" : latestStatus.status;
|
|
16606
|
-
if (latestVisibleStatus !== "idle") {
|
|
16607
|
-
LOG.info("CLI", `[${this.type}] cancelled pending completed (resumed ${latestVisibleStatus})`);
|
|
16608
|
-
this.completedDebouncePending = null;
|
|
16609
|
-
this.completedDebounceTimer = null;
|
|
16610
|
-
return;
|
|
16611
|
-
}
|
|
16612
|
-
LOG.info("CLI", `[${this.type}] completed in ${this.completedDebouncePending.duration}s`);
|
|
16613
|
-
this.pushEvent({ event: "agent:generating_completed", ...this.completedDebouncePending });
|
|
16614
|
-
this.completedDebouncePending = null;
|
|
16615
|
-
this.generatingStartedAt = 0;
|
|
16616
|
-
}
|
|
16617
|
-
this.completedDebounceTimer = null;
|
|
16618
|
-
}, 3e3);
|
|
17575
|
+
this.completedDebouncePending = { chatTitle, duration, timestamp: now, firstObservedAt: now };
|
|
17576
|
+
this.scheduleCompletedDebounceFlush(3e3);
|
|
16619
17577
|
}
|
|
17578
|
+
} else if (newStatus === "idle" && this.lastStatus === "starting") {
|
|
17579
|
+
this.pushEvent({ event: "agent:ready", chatTitle, timestamp: now });
|
|
16620
17580
|
} else if (newStatus === "stopped") {
|
|
16621
17581
|
if (this.generatingDebounceTimer) {
|
|
16622
17582
|
clearTimeout(this.generatingDebounceTimer);
|
|
@@ -18257,7 +19217,7 @@ ${rawInput}` : rawInput;
|
|
|
18257
19217
|
});
|
|
18258
19218
|
} else if (newStatus === "idle" && (this.lastStatus === "generating" || this.lastStatus === "waiting_approval")) {
|
|
18259
19219
|
const duration = this.generatingStartedAt ? Math.round((now - this.generatingStartedAt) / 1e3) : 0;
|
|
18260
|
-
this.pushEvent({ event: "agent:generating_completed", chatTitle, duration, timestamp: now });
|
|
19220
|
+
this.pushEvent({ event: "agent:generating_completed", chatTitle, duration, timestamp: now, finalSummary: extractFinalSummaryFromMessages(this.messages) });
|
|
18261
19221
|
this.generatingStartedAt = 0;
|
|
18262
19222
|
} else if (newStatus === "stopped") {
|
|
18263
19223
|
this.pushEvent({ event: "agent:stopped", chatTitle, timestamp: now });
|
|
@@ -18365,9 +19325,6 @@ function buildCoordinatorDelegatedCliLaunchOptions(input) {
|
|
|
18365
19325
|
const cliType = String(input.cliType || "").trim();
|
|
18366
19326
|
const cliArgs = Array.isArray(input.cliArgs) ? [...input.cliArgs] : [];
|
|
18367
19327
|
const env = { ...input.env || {}, ...COORDINATOR_DELEGATED_ENV_UNSETS };
|
|
18368
|
-
if (cliType === "hermes-cli" && !hasCliArg(cliArgs, "--ignore-user-config")) {
|
|
18369
|
-
cliArgs.unshift("--ignore-user-config");
|
|
18370
|
-
}
|
|
18371
19328
|
if (cliType === "claude-cli" && !hasCliArg(cliArgs, "--mcp-config")) {
|
|
18372
19329
|
cliArgs.unshift("--mcp-config", ensureEmptyDelegatedMcpConfig(input.workspace));
|
|
18373
19330
|
}
|
|
@@ -21444,6 +22401,7 @@ function getAvailableIdeIds() {
|
|
|
21444
22401
|
|
|
21445
22402
|
// src/commands/router.ts
|
|
21446
22403
|
init_config();
|
|
22404
|
+
init_cli_detector();
|
|
21447
22405
|
init_logger();
|
|
21448
22406
|
|
|
21449
22407
|
// src/logging/command-log.ts
|
|
@@ -21613,7 +22571,9 @@ function resolveHermesMeshCoordinatorSetup(options) {
|
|
|
21613
22571
|
const mcpServer = resolveAdhdevMcpServerLaunch({
|
|
21614
22572
|
meshId: options.meshId,
|
|
21615
22573
|
nodeExecutable: options.nodeExecutable,
|
|
21616
|
-
adhdevMcpEntryPath: options.adhdevMcpEntryPath
|
|
22574
|
+
adhdevMcpEntryPath: options.adhdevMcpEntryPath,
|
|
22575
|
+
adhdevMcpTransport: options.adhdevMcpTransport,
|
|
22576
|
+
adhdevMcpPort: options.adhdevMcpPort
|
|
21617
22577
|
});
|
|
21618
22578
|
if (!mcpServer) {
|
|
21619
22579
|
return {
|
|
@@ -21680,7 +22640,9 @@ function resolveMeshCoordinatorSetup(options) {
|
|
|
21680
22640
|
const mcpServer = resolveAdhdevMcpServerLaunch({
|
|
21681
22641
|
meshId,
|
|
21682
22642
|
nodeExecutable: options.nodeExecutable,
|
|
21683
|
-
adhdevMcpEntryPath: options.adhdevMcpEntryPath
|
|
22643
|
+
adhdevMcpEntryPath: options.adhdevMcpEntryPath,
|
|
22644
|
+
adhdevMcpTransport: options.adhdevMcpTransport,
|
|
22645
|
+
adhdevMcpPort: options.adhdevMcpPort
|
|
21684
22646
|
});
|
|
21685
22647
|
if (!mcpServer) {
|
|
21686
22648
|
return {
|
|
@@ -21702,6 +22664,22 @@ function resolveMeshCoordinatorSetup(options) {
|
|
|
21702
22664
|
if (!instructions || !template?.trim()) {
|
|
21703
22665
|
return { kind: "unsupported", reason: "Provider manual MCP setup is missing instructions or template" };
|
|
21704
22666
|
}
|
|
22667
|
+
const renderedTemplate = renderMeshCoordinatorTemplate(template, {
|
|
22668
|
+
meshId,
|
|
22669
|
+
workspace,
|
|
22670
|
+
serverName,
|
|
22671
|
+
adhdevMcpCommand: options.adhdevMcpCommand || DEFAULT_ADHDEV_MCP_COMMAND
|
|
22672
|
+
});
|
|
22673
|
+
const isCliCommand = !renderedTemplate.trim().includes("\n") && !renderedTemplate.trim().startsWith("{");
|
|
22674
|
+
if (isCliCommand) {
|
|
22675
|
+
return {
|
|
22676
|
+
kind: "cli_command",
|
|
22677
|
+
serverName,
|
|
22678
|
+
command: renderedTemplate.trim(),
|
|
22679
|
+
requiresRestart: mcpConfig.requiresRestart === true,
|
|
22680
|
+
instructions
|
|
22681
|
+
};
|
|
22682
|
+
}
|
|
21705
22683
|
return {
|
|
21706
22684
|
kind: "manual",
|
|
21707
22685
|
serverName,
|
|
@@ -21709,12 +22687,7 @@ function resolveMeshCoordinatorSetup(options) {
|
|
|
21709
22687
|
configPathCommand: mcpConfig.configPathCommand,
|
|
21710
22688
|
requiresRestart: mcpConfig.requiresRestart === true,
|
|
21711
22689
|
instructions,
|
|
21712
|
-
template:
|
|
21713
|
-
meshId,
|
|
21714
|
-
workspace,
|
|
21715
|
-
serverName,
|
|
21716
|
-
adhdevMcpCommand: options.adhdevMcpCommand || DEFAULT_ADHDEV_MCP_COMMAND
|
|
21717
|
-
})
|
|
22690
|
+
template: renderedTemplate
|
|
21718
22691
|
};
|
|
21719
22692
|
}
|
|
21720
22693
|
return {
|
|
@@ -21743,11 +22716,27 @@ function resolveAdhdevMcpServerLaunch(options) {
|
|
|
21743
22716
|
if (!entryPath) return null;
|
|
21744
22717
|
const nodeExecutable = resolveMcpNodeExecutable(options.nodeExecutable);
|
|
21745
22718
|
if (!nodeExecutable) return null;
|
|
22719
|
+
const transport = resolveMcpTransport(options.adhdevMcpTransport);
|
|
22720
|
+
const args = [entryPath, "--mode", transport, "--repo-mesh", options.meshId];
|
|
22721
|
+
const port = resolveMcpPort(options.adhdevMcpPort);
|
|
22722
|
+
if (port !== void 0) args.push("--port", String(port));
|
|
21746
22723
|
return {
|
|
21747
22724
|
command: nodeExecutable,
|
|
21748
|
-
args
|
|
22725
|
+
args
|
|
21749
22726
|
};
|
|
21750
22727
|
}
|
|
22728
|
+
function resolveMcpTransport(explicitTransport) {
|
|
22729
|
+
if (explicitTransport === "local" || explicitTransport === "ipc") return explicitTransport;
|
|
22730
|
+
const envTransport = process.env.ADHDEV_COORDINATOR_MCP_TRANSPORT?.trim();
|
|
22731
|
+
return envTransport === "local" ? "local" : "ipc";
|
|
22732
|
+
}
|
|
22733
|
+
function resolveMcpPort(explicitPort) {
|
|
22734
|
+
if (typeof explicitPort === "number" && Number.isInteger(explicitPort) && explicitPort > 0) return explicitPort;
|
|
22735
|
+
const raw = process.env.ADHDEV_COORDINATOR_MCP_PORT?.trim();
|
|
22736
|
+
if (!raw) return void 0;
|
|
22737
|
+
const parsed = Number(raw);
|
|
22738
|
+
return Number.isInteger(parsed) && parsed > 0 ? parsed : void 0;
|
|
22739
|
+
}
|
|
21751
22740
|
function resolveMcpNodeExecutable(explicitExecutable) {
|
|
21752
22741
|
const explicit = explicitExecutable?.trim();
|
|
21753
22742
|
if (explicit) return explicit;
|
|
@@ -22559,6 +23548,209 @@ async function resolveProviderTypeFromPriority(args) {
|
|
|
22559
23548
|
}
|
|
22560
23549
|
return { error: `No usable provider detected for node '${args.nodeId}' from providerPriority: ${failed.join("; ")}` };
|
|
22561
23550
|
}
|
|
23551
|
+
var REFINE_VALIDATION_CATEGORIES = ["typecheck", "test", "lint", "build"];
|
|
23552
|
+
var REFINE_VALIDATION_TIMEOUT_MS = 12e4;
|
|
23553
|
+
var REFINE_VALIDATION_OUTPUT_LIMIT_BYTES = 128 * 1024;
|
|
23554
|
+
var REFINE_VALIDATION_SUMMARY_CHARS = 2e3;
|
|
23555
|
+
var REFINE_VALIDATION_MAX_COMMANDS = 4;
|
|
23556
|
+
function truncateValidationOutput(value) {
|
|
23557
|
+
const text = typeof value === "string" ? value : value == null ? "" : String(value);
|
|
23558
|
+
if (text.length <= REFINE_VALIDATION_SUMMARY_CHARS) return text;
|
|
23559
|
+
return `${text.slice(0, REFINE_VALIDATION_SUMMARY_CHARS)}
|
|
23560
|
+
[truncated ${text.length - REFINE_VALIDATION_SUMMARY_CHARS} chars]`;
|
|
23561
|
+
}
|
|
23562
|
+
function readPackageScripts(workspace) {
|
|
23563
|
+
try {
|
|
23564
|
+
const packageJsonPath = pathJoin(workspace, "package.json");
|
|
23565
|
+
const parsed = JSON.parse(fs10.readFileSync(packageJsonPath, "utf-8"));
|
|
23566
|
+
return parsed?.scripts && typeof parsed.scripts === "object" && !Array.isArray(parsed.scripts) ? parsed.scripts : {};
|
|
23567
|
+
} catch {
|
|
23568
|
+
return {};
|
|
23569
|
+
}
|
|
23570
|
+
}
|
|
23571
|
+
function tokenizeValidationCommand(command) {
|
|
23572
|
+
const trimmed = command.trim();
|
|
23573
|
+
if (!trimmed) return null;
|
|
23574
|
+
if (/[;&|<>`$\\\n\r'\"]/.test(trimmed)) return null;
|
|
23575
|
+
const tokens = trimmed.split(/\s+/).filter(Boolean);
|
|
23576
|
+
if (!tokens.length) return null;
|
|
23577
|
+
if (tokens.some((token) => !/^[A-Za-z0-9_@./:=+-]+$/.test(token))) return null;
|
|
23578
|
+
return tokens;
|
|
23579
|
+
}
|
|
23580
|
+
function scriptMatchesValidationCategory(scriptName, category) {
|
|
23581
|
+
return scriptName === category || scriptName.startsWith(`${category}:`);
|
|
23582
|
+
}
|
|
23583
|
+
function parsePackageManagerValidationCommand(rawCommand, category, scripts, source) {
|
|
23584
|
+
const tokens = tokenizeValidationCommand(rawCommand);
|
|
23585
|
+
if (!tokens) {
|
|
23586
|
+
return { rejected: { command: rawCommand, category, source, reason: "unsafe command string is not allowlisted" } };
|
|
23587
|
+
}
|
|
23588
|
+
const [binary, second, third, ...rest] = tokens;
|
|
23589
|
+
let scriptName = "";
|
|
23590
|
+
let command = binary;
|
|
23591
|
+
let args = [];
|
|
23592
|
+
if ((binary === "npm" || binary === "pnpm" || binary === "bun") && second === "run" && third) {
|
|
23593
|
+
scriptName = third;
|
|
23594
|
+
args = ["run", scriptName, ...rest];
|
|
23595
|
+
} else if (binary === "npm" && second === "test" && !third) {
|
|
23596
|
+
scriptName = "test";
|
|
23597
|
+
args = ["test"];
|
|
23598
|
+
} else if (binary === "yarn" && second === "run" && third) {
|
|
23599
|
+
scriptName = third;
|
|
23600
|
+
args = ["run", scriptName, ...rest];
|
|
23601
|
+
} else if (binary === "yarn" && second && !third) {
|
|
23602
|
+
scriptName = second;
|
|
23603
|
+
args = [scriptName];
|
|
23604
|
+
} else {
|
|
23605
|
+
return { rejected: { command: rawCommand, category, source, reason: "command is not a supported package-manager script invocation" } };
|
|
23606
|
+
}
|
|
23607
|
+
if (!scriptName || !Object.prototype.hasOwnProperty.call(scripts, scriptName)) {
|
|
23608
|
+
return { rejected: { command: rawCommand, category, source, script: scriptName, reason: "script is not declared in package.json" } };
|
|
23609
|
+
}
|
|
23610
|
+
if (!scriptMatchesValidationCategory(scriptName, category)) {
|
|
23611
|
+
return { rejected: { command: rawCommand, category, source, script: scriptName, reason: "script name is outside the validation category allowlist" } };
|
|
23612
|
+
}
|
|
23613
|
+
return {
|
|
23614
|
+
command: {
|
|
23615
|
+
command,
|
|
23616
|
+
args,
|
|
23617
|
+
displayCommand: [command, ...args].join(" "),
|
|
23618
|
+
category,
|
|
23619
|
+
source
|
|
23620
|
+
}
|
|
23621
|
+
};
|
|
23622
|
+
}
|
|
23623
|
+
function collectProjectContextValidationCandidates(mesh) {
|
|
23624
|
+
const commands = mesh?.projectContext?.commands;
|
|
23625
|
+
if (!commands || typeof commands !== "object" || Array.isArray(commands)) return [];
|
|
23626
|
+
const candidates = [];
|
|
23627
|
+
for (const category of REFINE_VALIDATION_CATEGORIES) {
|
|
23628
|
+
const entries = Array.isArray(commands[category]) ? commands[category] : [];
|
|
23629
|
+
for (const entry of entries) {
|
|
23630
|
+
if (typeof entry?.command !== "string") continue;
|
|
23631
|
+
candidates.push({
|
|
23632
|
+
command: entry.command,
|
|
23633
|
+
category,
|
|
23634
|
+
source: typeof entry.sourcePath === "string" ? entry.sourcePath : "projectContext.commands",
|
|
23635
|
+
confidence: typeof entry.confidence === "string" ? entry.confidence : void 0
|
|
23636
|
+
});
|
|
23637
|
+
}
|
|
23638
|
+
}
|
|
23639
|
+
return candidates.sort((a, b) => {
|
|
23640
|
+
const rank = (value) => value === "high" ? 0 : value === "medium" ? 1 : 2;
|
|
23641
|
+
return rank(a.confidence) - rank(b.confidence);
|
|
23642
|
+
});
|
|
23643
|
+
}
|
|
23644
|
+
function collectPolicyValidationCandidates(mesh) {
|
|
23645
|
+
const policy = mesh?.policy && typeof mesh.policy === "object" && !Array.isArray(mesh.policy) ? mesh.policy : {};
|
|
23646
|
+
const configured = Array.isArray(policy.validationCommands) ? policy.validationCommands : Array.isArray(policy.validationGate?.commands) ? policy.validationGate.commands : [];
|
|
23647
|
+
return configured.map((entry) => typeof entry === "string" ? { command: entry, category: "", source: "mesh.policy.validationCommands" } : entry).filter((entry) => entry && typeof entry.command === "string").map((entry) => {
|
|
23648
|
+
const commandText = entry.command.trim();
|
|
23649
|
+
const category = REFINE_VALIDATION_CATEGORIES.find((cat) => commandText.includes(` ${cat}`)) ?? "";
|
|
23650
|
+
return { command: commandText, category, source: "mesh.policy.validationCommands" };
|
|
23651
|
+
}).filter((entry) => !!entry.category);
|
|
23652
|
+
}
|
|
23653
|
+
function selectMeshRefineValidationCommands(mesh, workspace) {
|
|
23654
|
+
const scripts = readPackageScripts(workspace);
|
|
23655
|
+
const rejectedCommands = [];
|
|
23656
|
+
const selected = [];
|
|
23657
|
+
const seen = /* @__PURE__ */ new Set();
|
|
23658
|
+
const candidates = [
|
|
23659
|
+
...collectPolicyValidationCandidates(mesh),
|
|
23660
|
+
...collectProjectContextValidationCandidates(mesh)
|
|
23661
|
+
];
|
|
23662
|
+
for (const candidate of candidates) {
|
|
23663
|
+
const parsed = parsePackageManagerValidationCommand(candidate.command, candidate.category, scripts, candidate.source);
|
|
23664
|
+
if (parsed.rejected) {
|
|
23665
|
+
rejectedCommands.push(parsed.rejected);
|
|
23666
|
+
continue;
|
|
23667
|
+
}
|
|
23668
|
+
if (!parsed.command || seen.has(parsed.command.displayCommand)) continue;
|
|
23669
|
+
selected.push(parsed.command);
|
|
23670
|
+
seen.add(parsed.command.displayCommand);
|
|
23671
|
+
if (selected.length >= REFINE_VALIDATION_MAX_COMMANDS) break;
|
|
23672
|
+
}
|
|
23673
|
+
if (!selected.length && candidates.length === 0) {
|
|
23674
|
+
for (const category of REFINE_VALIDATION_CATEGORIES) {
|
|
23675
|
+
if (!Object.prototype.hasOwnProperty.call(scripts, category)) continue;
|
|
23676
|
+
const fallback = parsePackageManagerValidationCommand(`npm run ${category}`, category, scripts, "package.json:scripts");
|
|
23677
|
+
if (fallback.command && !seen.has(fallback.command.displayCommand)) {
|
|
23678
|
+
selected.push(fallback.command);
|
|
23679
|
+
seen.add(fallback.command.displayCommand);
|
|
23680
|
+
} else if (fallback.rejected) {
|
|
23681
|
+
rejectedCommands.push(fallback.rejected);
|
|
23682
|
+
}
|
|
23683
|
+
if (selected.length >= 2) break;
|
|
23684
|
+
}
|
|
23685
|
+
}
|
|
23686
|
+
return {
|
|
23687
|
+
commands: selected,
|
|
23688
|
+
rejectedCommands,
|
|
23689
|
+
source: selected.some((command) => command.source === "mesh.policy.validationCommands") ? "mesh_policy" : selected.some((command) => command.source !== "package.json:scripts") ? "project_context" : selected.length ? "package_json_scripts" : "unavailable"
|
|
23690
|
+
};
|
|
23691
|
+
}
|
|
23692
|
+
async function runMeshRefineValidationGate(mesh, workspace) {
|
|
23693
|
+
const { execFile: execFile3 } = await import("child_process");
|
|
23694
|
+
const { promisify: promisify3 } = await import("util");
|
|
23695
|
+
const execFileAsync3 = promisify3(execFile3);
|
|
23696
|
+
const selection = selectMeshRefineValidationCommands(mesh, workspace);
|
|
23697
|
+
const summary = {
|
|
23698
|
+
status: "skipped",
|
|
23699
|
+
required: true,
|
|
23700
|
+
commandsRun: [],
|
|
23701
|
+
rejectedCommands: selection.rejectedCommands,
|
|
23702
|
+
skippedReason: void 0,
|
|
23703
|
+
timeoutMs: REFINE_VALIDATION_TIMEOUT_MS,
|
|
23704
|
+
outputLimitBytes: REFINE_VALIDATION_OUTPUT_LIMIT_BYTES
|
|
23705
|
+
};
|
|
23706
|
+
if (!selection.commands.length) {
|
|
23707
|
+
summary.skippedReason = "validation_unavailable: no allowlisted projectContext, mesh policy, or package.json build/test/typecheck/lint command was available";
|
|
23708
|
+
return summary;
|
|
23709
|
+
}
|
|
23710
|
+
for (const candidate of selection.commands) {
|
|
23711
|
+
const startedAt = Date.now();
|
|
23712
|
+
try {
|
|
23713
|
+
const result = await execFileAsync3(candidate.command, candidate.args, {
|
|
23714
|
+
cwd: workspace,
|
|
23715
|
+
encoding: "utf8",
|
|
23716
|
+
timeout: REFINE_VALIDATION_TIMEOUT_MS,
|
|
23717
|
+
maxBuffer: REFINE_VALIDATION_OUTPUT_LIMIT_BYTES,
|
|
23718
|
+
env: { ...process.env, CI: process.env.CI || "1" }
|
|
23719
|
+
});
|
|
23720
|
+
summary.commandsRun.push({
|
|
23721
|
+
command: candidate.command,
|
|
23722
|
+
args: candidate.args,
|
|
23723
|
+
displayCommand: candidate.displayCommand,
|
|
23724
|
+
category: candidate.category,
|
|
23725
|
+
source: candidate.source,
|
|
23726
|
+
passed: true,
|
|
23727
|
+
exitCode: 0,
|
|
23728
|
+
durationMs: Date.now() - startedAt,
|
|
23729
|
+
stdout: truncateValidationOutput(result.stdout),
|
|
23730
|
+
stderr: truncateValidationOutput(result.stderr)
|
|
23731
|
+
});
|
|
23732
|
+
} catch (error) {
|
|
23733
|
+
summary.commandsRun.push({
|
|
23734
|
+
command: candidate.command,
|
|
23735
|
+
args: candidate.args,
|
|
23736
|
+
displayCommand: candidate.displayCommand,
|
|
23737
|
+
category: candidate.category,
|
|
23738
|
+
source: candidate.source,
|
|
23739
|
+
passed: false,
|
|
23740
|
+
exitCode: typeof error?.code === "number" ? error.code : null,
|
|
23741
|
+
signal: typeof error?.signal === "string" ? error.signal : null,
|
|
23742
|
+
timedOut: error?.killed === true || /timed out/i.test(String(error?.message || "")),
|
|
23743
|
+
durationMs: Date.now() - startedAt,
|
|
23744
|
+
stdout: truncateValidationOutput(error?.stdout),
|
|
23745
|
+
stderr: truncateValidationOutput(error?.stderr || error?.message)
|
|
23746
|
+
});
|
|
23747
|
+
summary.status = "failed";
|
|
23748
|
+
return summary;
|
|
23749
|
+
}
|
|
23750
|
+
}
|
|
23751
|
+
summary.status = "passed";
|
|
23752
|
+
return summary;
|
|
23753
|
+
}
|
|
22562
23754
|
function loadYamlModule() {
|
|
22563
23755
|
return yaml;
|
|
22564
23756
|
}
|
|
@@ -22588,6 +23780,34 @@ function loadHermesCoordinatorBaseConfig(targetConfigPath) {
|
|
|
22588
23780
|
const { mcp_servers: _mcpServers, ...baseConfig } = parsed;
|
|
22589
23781
|
return { config: baseConfig, sourceHome, sourceConfigPath };
|
|
22590
23782
|
}
|
|
23783
|
+
function stripHermesCoordinatorTempModelProviderOverrides(config) {
|
|
23784
|
+
const {
|
|
23785
|
+
model: _model,
|
|
23786
|
+
provider: _provider,
|
|
23787
|
+
default_model: _defaultModel,
|
|
23788
|
+
defaultProvider: _defaultProvider,
|
|
23789
|
+
default_provider: _defaultProviderSnake,
|
|
23790
|
+
modelProvider: _modelProvider,
|
|
23791
|
+
model_provider: _modelProviderSnake,
|
|
23792
|
+
...sanitized
|
|
23793
|
+
} = config;
|
|
23794
|
+
const delegation = sanitized.delegation;
|
|
23795
|
+
if (delegation && typeof delegation === "object" && !Array.isArray(delegation)) {
|
|
23796
|
+
const {
|
|
23797
|
+
model: _delegationModel,
|
|
23798
|
+
provider: _delegationProvider,
|
|
23799
|
+
modelProvider: _delegationModelProvider,
|
|
23800
|
+
model_provider: _delegationModelProviderSnake,
|
|
23801
|
+
...delegationRest
|
|
23802
|
+
} = delegation;
|
|
23803
|
+
if (Object.keys(delegationRest).length > 0) {
|
|
23804
|
+
sanitized.delegation = delegationRest;
|
|
23805
|
+
} else {
|
|
23806
|
+
delete sanitized.delegation;
|
|
23807
|
+
}
|
|
23808
|
+
}
|
|
23809
|
+
return sanitized;
|
|
23810
|
+
}
|
|
22591
23811
|
function copyHermesCoordinatorCredentialFiles(sourceHome, targetHome) {
|
|
22592
23812
|
if (pathResolve(sourceHome) === pathResolve(targetHome)) return;
|
|
22593
23813
|
for (const fileName of [".env", "auth.json"]) {
|
|
@@ -22745,9 +23965,191 @@ var DaemonCommandRouter = class {
|
|
|
22745
23965
|
if (record?.meta?.meshNodeId === nodeId) return true;
|
|
22746
23966
|
return false;
|
|
22747
23967
|
}
|
|
23968
|
+
async cleanupLocalWorktreeNode(args) {
|
|
23969
|
+
const workspace = typeof args.node?.workspace === "string" ? args.node.workspace.trim() : "";
|
|
23970
|
+
if (!workspace) {
|
|
23971
|
+
return {
|
|
23972
|
+
success: false,
|
|
23973
|
+
code: "mesh_worktree_cleanup_missing_workspace",
|
|
23974
|
+
error: `Worktree node '${args.nodeId}' is missing workspace metadata`,
|
|
23975
|
+
recoveryHint: "Inspect the mesh node record before removing it, or remove stale metadata manually only after confirming no managed worktree remains."
|
|
23976
|
+
};
|
|
23977
|
+
}
|
|
23978
|
+
const worktreeExists = fs10.existsSync(workspace);
|
|
23979
|
+
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);
|
|
23980
|
+
const repoRoot = typeof sourceNode?.repoRoot === "string" && sourceNode.repoRoot.trim() ? sourceNode.repoRoot.trim() : typeof sourceNode?.workspace === "string" && sourceNode.workspace.trim() ? sourceNode.workspace.trim() : "";
|
|
23981
|
+
if (!worktreeExists) {
|
|
23982
|
+
return { success: true, skipped: true, removedPath: workspace, repoRoot: repoRoot || void 0, reason: "worktree_path_missing" };
|
|
23983
|
+
}
|
|
23984
|
+
if (!repoRoot || !fs10.existsSync(repoRoot)) {
|
|
23985
|
+
return {
|
|
23986
|
+
success: false,
|
|
23987
|
+
code: "mesh_worktree_cleanup_missing_source_repo",
|
|
23988
|
+
error: `Refusing to remove worktree '${workspace}' because the source repo root is unavailable`,
|
|
23989
|
+
recoveryHint: "Run mesh_remove_node from the machine that owns the source repo, or verify the source node metadata before retrying."
|
|
23990
|
+
};
|
|
23991
|
+
}
|
|
23992
|
+
if (typeof args.node?.worktreeBranch !== "string" || !args.node.worktreeBranch.trim()) {
|
|
23993
|
+
return {
|
|
23994
|
+
success: false,
|
|
23995
|
+
code: "mesh_worktree_cleanup_missing_branch",
|
|
23996
|
+
error: `Refusing to remove worktree '${workspace}' because worktreeBranch metadata is missing`,
|
|
23997
|
+
recoveryHint: "Confirm this is an ADHDev-managed worktree before removing it manually; managed worktree nodes include worktreeBranch metadata."
|
|
23998
|
+
};
|
|
23999
|
+
}
|
|
24000
|
+
const { resolveWorktreePath: resolveWorktreePath2, listWorktrees: listWorktrees2, removeWorktree: removeWorktree2 } = await Promise.resolve().then(() => (init_git_worktree(), git_worktree_exports));
|
|
24001
|
+
const normalizePath = (value) => {
|
|
24002
|
+
const resolved = pathResolve(value);
|
|
24003
|
+
try {
|
|
24004
|
+
return fs10.realpathSync(resolved);
|
|
24005
|
+
} catch {
|
|
24006
|
+
return resolved;
|
|
24007
|
+
}
|
|
24008
|
+
};
|
|
24009
|
+
const expectedPath = normalizePath(resolveWorktreePath2(repoRoot, String(args.mesh?.name || args.mesh?.id || "mesh"), args.node.worktreeBranch));
|
|
24010
|
+
const actualPath = normalizePath(workspace);
|
|
24011
|
+
if (actualPath !== expectedPath) {
|
|
24012
|
+
return {
|
|
24013
|
+
success: false,
|
|
24014
|
+
code: "mesh_worktree_cleanup_unexpected_path",
|
|
24015
|
+
error: `Refusing to remove worktree '${workspace}' because it is not at the expected managed path '${expectedPath}'`,
|
|
24016
|
+
recoveryHint: "Use git worktree list/status to inspect the path. Retry only after confirming the mesh node metadata points to an ADHDev-managed worktree."
|
|
24017
|
+
};
|
|
24018
|
+
}
|
|
24019
|
+
const entries = await listWorktrees2(repoRoot);
|
|
24020
|
+
const managedEntry = entries.find((entry) => normalizePath(entry.path) === actualPath);
|
|
24021
|
+
if (!managedEntry) {
|
|
24022
|
+
return {
|
|
24023
|
+
success: false,
|
|
24024
|
+
code: "mesh_worktree_cleanup_not_registered",
|
|
24025
|
+
error: `Refusing to remove '${workspace}' because it is not registered in git worktree list for '${repoRoot}'`,
|
|
24026
|
+
recoveryHint: "Inspect git worktree list --porcelain from the source repo. If the path was already removed, prune git worktrees before retrying."
|
|
24027
|
+
};
|
|
24028
|
+
}
|
|
24029
|
+
if (managedEntry.branch && managedEntry.branch !== args.node.worktreeBranch) {
|
|
24030
|
+
return {
|
|
24031
|
+
success: false,
|
|
24032
|
+
code: "mesh_worktree_cleanup_branch_mismatch",
|
|
24033
|
+
error: `Refusing to remove '${workspace}' because git reports branch '${managedEntry.branch}', expected '${args.node.worktreeBranch}'`,
|
|
24034
|
+
recoveryHint: "Inspect the worktree branch and mesh metadata before retrying cleanup."
|
|
24035
|
+
};
|
|
24036
|
+
}
|
|
24037
|
+
const forceFallbackConvergence = await this.getWorktreeForceCleanupConvergence({
|
|
24038
|
+
repoRoot,
|
|
24039
|
+
workspace,
|
|
24040
|
+
node: args.node
|
|
24041
|
+
});
|
|
24042
|
+
try {
|
|
24043
|
+
const result = await removeWorktree2(repoRoot, workspace, {
|
|
24044
|
+
requireClean: true,
|
|
24045
|
+
allowSubmoduleForceFallback: forceFallbackConvergence.allow
|
|
24046
|
+
});
|
|
24047
|
+
return {
|
|
24048
|
+
success: true,
|
|
24049
|
+
removedPath: result.removedPath,
|
|
24050
|
+
repoRoot,
|
|
24051
|
+
...result.fallback ? {
|
|
24052
|
+
fallback: result.fallback,
|
|
24053
|
+
forced: result.forced,
|
|
24054
|
+
reason: result.reason,
|
|
24055
|
+
convergence: forceFallbackConvergence
|
|
24056
|
+
} : {}
|
|
24057
|
+
};
|
|
24058
|
+
} catch (e) {
|
|
24059
|
+
const message = String(e?.message || e || "worktree cleanup failed");
|
|
24060
|
+
const dirty = message.includes("dirty worktree") || message.includes("local changes");
|
|
24061
|
+
const submoduleForceBlocked = /working trees containing submodules cannot be moved or removed/i.test(message) && !forceFallbackConvergence.allow;
|
|
24062
|
+
return {
|
|
24063
|
+
success: false,
|
|
24064
|
+
code: dirty ? "mesh_worktree_cleanup_dirty" : submoduleForceBlocked ? "mesh_worktree_cleanup_force_fallback_blocked" : "mesh_worktree_cleanup_failed",
|
|
24065
|
+
error: submoduleForceBlocked ? `${message}; refusing --force fallback because convergence could not be verified: ${forceFallbackConvergence.error || "unknown convergence state"}` : message,
|
|
24066
|
+
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." : submoduleForceBlocked ? "Verify the worktree branch is merged/contained in the source default branch (for example origin/main) or mark the node with a safe branchConvergence final state before retrying. The mesh registry entry is preserved." : "Inspect git worktree status/list from the source repo and retry after resolving the reported cleanup failure.",
|
|
24067
|
+
...submoduleForceBlocked ? { convergence: forceFallbackConvergence } : {}
|
|
24068
|
+
};
|
|
24069
|
+
}
|
|
24070
|
+
}
|
|
24071
|
+
async getWorktreeForceCleanupConvergence(args) {
|
|
24072
|
+
const metadataStatus = typeof args.node?.branchConvergence?.status === "string" ? args.node.branchConvergence.status : "";
|
|
24073
|
+
if (metadataStatus === "merged_to_main" || metadataStatus === "cleanup_candidate") {
|
|
24074
|
+
return { allow: true, status: metadataStatus, source: "node_branch_convergence" };
|
|
24075
|
+
}
|
|
24076
|
+
const { execFile: execFile3 } = await import("child_process");
|
|
24077
|
+
const { promisify: promisify3 } = await import("util");
|
|
24078
|
+
const execFileAsync3 = promisify3(execFile3);
|
|
24079
|
+
const runGit2 = async (gitArgs, cwd) => {
|
|
24080
|
+
const { stdout } = await execFileAsync3("git", gitArgs, {
|
|
24081
|
+
cwd,
|
|
24082
|
+
encoding: "utf8",
|
|
24083
|
+
timeout: 3e4,
|
|
24084
|
+
maxBuffer: 4 * 1024 * 1024,
|
|
24085
|
+
windowsHide: true
|
|
24086
|
+
});
|
|
24087
|
+
return String(stdout || "").trim();
|
|
24088
|
+
};
|
|
24089
|
+
let head = "";
|
|
24090
|
+
try {
|
|
24091
|
+
head = await runGit2(["rev-parse", "HEAD"], args.workspace);
|
|
24092
|
+
} catch (e) {
|
|
24093
|
+
return { allow: false, error: `could not resolve worktree HEAD: ${e?.message || e}` };
|
|
24094
|
+
}
|
|
24095
|
+
if (!head) return { allow: false, error: "worktree HEAD is empty" };
|
|
24096
|
+
const candidateRefs = [];
|
|
24097
|
+
try {
|
|
24098
|
+
const defaultBranch = await runGit2(["branch", "--show-current"], args.repoRoot);
|
|
24099
|
+
if (defaultBranch) {
|
|
24100
|
+
candidateRefs.push(defaultBranch, `origin/${defaultBranch}`);
|
|
24101
|
+
}
|
|
24102
|
+
} catch {
|
|
24103
|
+
}
|
|
24104
|
+
candidateRefs.push("origin/main", "origin/master", "main", "master");
|
|
24105
|
+
const seen = /* @__PURE__ */ new Set();
|
|
24106
|
+
const checkedRefs = [];
|
|
24107
|
+
for (const ref of candidateRefs) {
|
|
24108
|
+
if (!ref || seen.has(ref)) continue;
|
|
24109
|
+
seen.add(ref);
|
|
24110
|
+
let commit = "";
|
|
24111
|
+
try {
|
|
24112
|
+
commit = await runGit2(["rev-parse", "--verify", `${ref}^{commit}`], args.repoRoot);
|
|
24113
|
+
} catch {
|
|
24114
|
+
continue;
|
|
24115
|
+
}
|
|
24116
|
+
checkedRefs.push(ref);
|
|
24117
|
+
try {
|
|
24118
|
+
await runGit2(["merge-base", "--is-ancestor", head, commit], args.repoRoot);
|
|
24119
|
+
return { allow: true, status: "merged_to_default_ref", source: "git_merge_base", ref };
|
|
24120
|
+
} catch {
|
|
24121
|
+
}
|
|
24122
|
+
}
|
|
24123
|
+
return {
|
|
24124
|
+
allow: false,
|
|
24125
|
+
status: metadataStatus || void 0,
|
|
24126
|
+
error: checkedRefs.length ? `worktree HEAD is not contained in checked refs: ${checkedRefs.join(", ")}` : "no default/main refs were available for convergence verification"
|
|
24127
|
+
};
|
|
24128
|
+
}
|
|
22748
24129
|
isCompletedHostedSession(record) {
|
|
22749
24130
|
return record?.lifecycle === "stopped" || record?.lifecycle === "failed" || record?.lifecycle === "interrupted";
|
|
22750
24131
|
}
|
|
24132
|
+
async recordIntentionalMeshSessionStop(args) {
|
|
24133
|
+
try {
|
|
24134
|
+
const { appendLedgerEntry: appendLedgerEntry2 } = await Promise.resolve().then(() => (init_mesh_ledger(), mesh_ledger_exports));
|
|
24135
|
+
appendLedgerEntry2(args.meshId, {
|
|
24136
|
+
kind: "session_stopped",
|
|
24137
|
+
nodeId: args.nodeId,
|
|
24138
|
+
sessionId: args.sessionId,
|
|
24139
|
+
payload: {
|
|
24140
|
+
intentional: true,
|
|
24141
|
+
reason: "operator_cleanup",
|
|
24142
|
+
intentionalStopReason: "operator_cleanup",
|
|
24143
|
+
source: args.source,
|
|
24144
|
+
cleanupMode: args.mode,
|
|
24145
|
+
action: args.action,
|
|
24146
|
+
workspace: typeof args.node?.workspace === "string" ? args.node.workspace : void 0
|
|
24147
|
+
}
|
|
24148
|
+
});
|
|
24149
|
+
} catch (e) {
|
|
24150
|
+
LOG.warn("MeshCleanup", `Failed to record intentional cleanup stop for ${args.sessionId}: ${e?.message || e}`);
|
|
24151
|
+
}
|
|
24152
|
+
}
|
|
22751
24153
|
async cleanupMeshSessions(args) {
|
|
22752
24154
|
if (args.mode === "preserve") {
|
|
22753
24155
|
return { success: true, mode: "preserve", matchedCount: 0, stoppedSessionIds: [], deletedSessionIds: [], skippedSessionIds: [] };
|
|
@@ -22764,6 +24166,21 @@ var DaemonCommandRouter = class {
|
|
|
22764
24166
|
const deleteUnsupportedSessionIds = [];
|
|
22765
24167
|
const recordsRemainSessionIds = [];
|
|
22766
24168
|
const errors = [];
|
|
24169
|
+
const cleanupSource = args.source || "mesh_cleanup_sessions";
|
|
24170
|
+
const markedIntentionalStopSessionIds = /* @__PURE__ */ new Set();
|
|
24171
|
+
const markIntentionalStop = async (sessionId, action) => {
|
|
24172
|
+
if (args.dryRun || markedIntentionalStopSessionIds.has(sessionId)) return;
|
|
24173
|
+
markedIntentionalStopSessionIds.add(sessionId);
|
|
24174
|
+
await this.recordIntentionalMeshSessionStop({
|
|
24175
|
+
meshId: args.meshId,
|
|
24176
|
+
nodeId: args.nodeId,
|
|
24177
|
+
node: args.node,
|
|
24178
|
+
sessionId,
|
|
24179
|
+
mode: args.mode,
|
|
24180
|
+
source: cleanupSource,
|
|
24181
|
+
action
|
|
24182
|
+
});
|
|
24183
|
+
};
|
|
22767
24184
|
const matchedBySurfaceKind = {
|
|
22768
24185
|
live_runtime: 0,
|
|
22769
24186
|
recovery_snapshot: 0,
|
|
@@ -22786,7 +24203,10 @@ var DaemonCommandRouter = class {
|
|
|
22786
24203
|
try {
|
|
22787
24204
|
if (args.mode === "stop") {
|
|
22788
24205
|
if (!completed) {
|
|
22789
|
-
if (!args.dryRun)
|
|
24206
|
+
if (!args.dryRun) {
|
|
24207
|
+
await markIntentionalStop(sessionId, "stop_session");
|
|
24208
|
+
await this.deps.sessionHostControl.stopSession(sessionId);
|
|
24209
|
+
}
|
|
22790
24210
|
stoppedSessionIds.push(sessionId);
|
|
22791
24211
|
} else {
|
|
22792
24212
|
skippedSessionIds.push(sessionId);
|
|
@@ -22803,6 +24223,7 @@ var DaemonCommandRouter = class {
|
|
|
22803
24223
|
continue;
|
|
22804
24224
|
}
|
|
22805
24225
|
if (args.mode === "stop_and_delete") {
|
|
24226
|
+
if (!completed) await markIntentionalStop(sessionId, "delete_session_force");
|
|
22806
24227
|
if (!args.dryRun) await this.deps.sessionHostControl.deleteSession(sessionId, { force: true });
|
|
22807
24228
|
deletedSessionIds.push(sessionId);
|
|
22808
24229
|
continue;
|
|
@@ -22814,6 +24235,7 @@ var DaemonCommandRouter = class {
|
|
|
22814
24235
|
recordsRemainSessionIds.push(sessionId);
|
|
22815
24236
|
if (args.mode === "stop_and_delete" && !completed) {
|
|
22816
24237
|
try {
|
|
24238
|
+
await markIntentionalStop(sessionId, "stop_session");
|
|
22817
24239
|
await this.deps.sessionHostControl.stopSession(sessionId);
|
|
22818
24240
|
stoppedSessionIds.push(sessionId);
|
|
22819
24241
|
} catch (stopError) {
|
|
@@ -23568,6 +24990,91 @@ var DaemonCommandRouter = class {
|
|
|
23568
24990
|
return { success: false, error: e.message };
|
|
23569
24991
|
}
|
|
23570
24992
|
}
|
|
24993
|
+
case "get_mesh_ledger_slice": {
|
|
24994
|
+
const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
|
|
24995
|
+
if (!meshId) return { success: false, error: "meshId required" };
|
|
24996
|
+
try {
|
|
24997
|
+
const { readLedgerSlice: readLedgerSlice2 } = await Promise.resolve().then(() => (init_mesh_ledger(), mesh_ledger_exports));
|
|
24998
|
+
const kind = Array.isArray(args?.kind) ? args.kind.filter((k) => typeof k === "string") : void 0;
|
|
24999
|
+
const slice = readLedgerSlice2(meshId, {
|
|
25000
|
+
afterId: typeof args?.afterId === "string" ? args.afterId : void 0,
|
|
25001
|
+
since: typeof args?.since === "string" ? args.since : void 0,
|
|
25002
|
+
kind,
|
|
25003
|
+
limit: typeof args?.limit === "number" ? args.limit : void 0
|
|
25004
|
+
});
|
|
25005
|
+
return { success: true, slice };
|
|
25006
|
+
} catch (e) {
|
|
25007
|
+
return { success: false, error: e.message };
|
|
25008
|
+
}
|
|
25009
|
+
}
|
|
25010
|
+
case "import_mesh_ledger_slice": {
|
|
25011
|
+
const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
|
|
25012
|
+
if (!meshId) return { success: false, error: "meshId required" };
|
|
25013
|
+
try {
|
|
25014
|
+
const { appendRemoteLedgerEntries: appendRemoteLedgerEntries2, getLedgerSummary: getLedgerSummary2 } = await Promise.resolve().then(() => (init_mesh_ledger(), mesh_ledger_exports));
|
|
25015
|
+
const entries = Array.isArray(args?.entries) ? args.entries : Array.isArray(args?.slice?.entries) ? args.slice.entries : [];
|
|
25016
|
+
const result = appendRemoteLedgerEntries2(meshId, entries);
|
|
25017
|
+
return { success: true, result, summary: getLedgerSummary2(meshId) };
|
|
25018
|
+
} catch (e) {
|
|
25019
|
+
return { success: false, error: e.message };
|
|
25020
|
+
}
|
|
25021
|
+
}
|
|
25022
|
+
case "get_mesh_queue": {
|
|
25023
|
+
const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
|
|
25024
|
+
if (!meshId) return { success: false, error: "meshId required" };
|
|
25025
|
+
try {
|
|
25026
|
+
const { getMeshQueueStats: getMeshQueueStats2, getQueue: getQueue2 } = await Promise.resolve().then(() => (init_mesh_work_queue(), mesh_work_queue_exports));
|
|
25027
|
+
const status = Array.isArray(args?.status) ? args.status.map((s) => typeof s === "string" ? s.trim() : "").filter(Boolean) : void 0;
|
|
25028
|
+
const queue = getQueue2(meshId, { status });
|
|
25029
|
+
const summary = getMeshQueueStats2(meshId);
|
|
25030
|
+
return {
|
|
25031
|
+
success: true,
|
|
25032
|
+
queue,
|
|
25033
|
+
summary,
|
|
25034
|
+
sourceOfTruth: {
|
|
25035
|
+
kind: "mesh_work_queue_file",
|
|
25036
|
+
activeStatuses: ["pending", "assigned"],
|
|
25037
|
+
historicalStatuses: ["completed", "failed", "cancelled"],
|
|
25038
|
+
notes: "pending/assigned are active work; completed/failed/cancelled are historical records."
|
|
25039
|
+
}
|
|
25040
|
+
};
|
|
25041
|
+
} catch (e) {
|
|
25042
|
+
return { success: false, error: e.message };
|
|
25043
|
+
}
|
|
25044
|
+
}
|
|
25045
|
+
case "cancel_mesh_queue_task": {
|
|
25046
|
+
const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
|
|
25047
|
+
const taskId = typeof args?.taskId === "string" ? args.taskId.trim() : "";
|
|
25048
|
+
if (!meshId || !taskId) return { success: false, error: "meshId and taskId required" };
|
|
25049
|
+
try {
|
|
25050
|
+
const { cancelTask: cancelTask2 } = await Promise.resolve().then(() => (init_mesh_work_queue(), mesh_work_queue_exports));
|
|
25051
|
+
const reason = typeof args?.reason === "string" ? args.reason : void 0;
|
|
25052
|
+
const task = cancelTask2(meshId, taskId, { reason });
|
|
25053
|
+
if (!task) return { success: false, error: `Queue task '${taskId}' not found` };
|
|
25054
|
+
return { success: true, task };
|
|
25055
|
+
} catch (e) {
|
|
25056
|
+
return { success: false, error: e.message };
|
|
25057
|
+
}
|
|
25058
|
+
}
|
|
25059
|
+
case "requeue_mesh_queue_task": {
|
|
25060
|
+
const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
|
|
25061
|
+
const taskId = typeof args?.taskId === "string" ? args.taskId.trim() : "";
|
|
25062
|
+
if (!meshId || !taskId) return { success: false, error: "meshId and taskId required" };
|
|
25063
|
+
try {
|
|
25064
|
+
const { requeueTask: requeueTask2 } = await Promise.resolve().then(() => (init_mesh_work_queue(), mesh_work_queue_exports));
|
|
25065
|
+
const task = requeueTask2(meshId, taskId, {
|
|
25066
|
+
reason: typeof args?.reason === "string" ? args.reason : void 0,
|
|
25067
|
+
targetNodeId: typeof args?.targetNodeId === "string" ? args.targetNodeId.trim() : void 0,
|
|
25068
|
+
targetSessionId: typeof args?.targetSessionId === "string" ? args.targetSessionId.trim() : void 0,
|
|
25069
|
+
clearTargetNode: args?.clearTargetNode === true,
|
|
25070
|
+
clearTargetSession: args?.clearTargetSession !== false
|
|
25071
|
+
});
|
|
25072
|
+
if (!task) return { success: false, error: `Queue task '${taskId}' not found` };
|
|
25073
|
+
return { success: true, task };
|
|
25074
|
+
} catch (e) {
|
|
25075
|
+
return { success: false, error: e.message };
|
|
25076
|
+
}
|
|
25077
|
+
}
|
|
23571
25078
|
case "add_mesh_node": {
|
|
23572
25079
|
const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
|
|
23573
25080
|
const workspace = typeof args?.workspace === "string" ? args.workspace.trim() : "";
|
|
@@ -23629,7 +25136,8 @@ var DaemonCommandRouter = class {
|
|
|
23629
25136
|
node,
|
|
23630
25137
|
mode,
|
|
23631
25138
|
sessionIds,
|
|
23632
|
-
dryRun: args?.dryRun === true
|
|
25139
|
+
dryRun: args?.dryRun === true,
|
|
25140
|
+
source: "mesh_cleanup_sessions"
|
|
23633
25141
|
});
|
|
23634
25142
|
return result;
|
|
23635
25143
|
} catch (e) {
|
|
@@ -23659,10 +25167,61 @@ var DaemonCommandRouter = class {
|
|
|
23659
25167
|
if (!branch) return { success: false, error: "Could not determine branch of the worktree node" };
|
|
23660
25168
|
const { stdout: baseBranchStdout } = await execFileAsync3("git", ["branch", "--show-current"], { cwd: repoRoot, encoding: "utf8" });
|
|
23661
25169
|
const baseBranch = baseBranchStdout.trim();
|
|
25170
|
+
const validationSummary = await runMeshRefineValidationGate(mesh, node.workspace);
|
|
25171
|
+
if (validationSummary.status === "failed") {
|
|
25172
|
+
return {
|
|
25173
|
+
success: false,
|
|
25174
|
+
code: "validation_failed",
|
|
25175
|
+
convergenceStatus: "blocked_review",
|
|
25176
|
+
error: "Refinery validation gate failed; merge/refine was not attempted.",
|
|
25177
|
+
branch,
|
|
25178
|
+
into: baseBranch,
|
|
25179
|
+
validationSummary,
|
|
25180
|
+
finalBranchConvergenceState: {
|
|
25181
|
+
branch,
|
|
25182
|
+
baseBranch,
|
|
25183
|
+
merged: false,
|
|
25184
|
+
removed: false,
|
|
25185
|
+
validation: "failed",
|
|
25186
|
+
status: "blocked_review"
|
|
25187
|
+
}
|
|
25188
|
+
};
|
|
25189
|
+
}
|
|
25190
|
+
if (validationSummary.status === "skipped") {
|
|
25191
|
+
return {
|
|
25192
|
+
success: false,
|
|
25193
|
+
code: "validation_unavailable",
|
|
25194
|
+
convergenceStatus: "blocked_review",
|
|
25195
|
+
error: "Refinery validation gate is required but no allowlisted validation command was available; merge/refine was not attempted.",
|
|
25196
|
+
branch,
|
|
25197
|
+
into: baseBranch,
|
|
25198
|
+
validationSummary,
|
|
25199
|
+
finalBranchConvergenceState: {
|
|
25200
|
+
branch,
|
|
25201
|
+
baseBranch,
|
|
25202
|
+
merged: false,
|
|
25203
|
+
removed: false,
|
|
25204
|
+
validation: "unavailable",
|
|
25205
|
+
status: "blocked_review"
|
|
25206
|
+
}
|
|
25207
|
+
};
|
|
25208
|
+
}
|
|
23662
25209
|
try {
|
|
23663
25210
|
await execFileAsync3("git", ["merge", "--no-ff", branch, "-m", `Auto-merge branch '${branch}' via Refinery`], { cwd: repoRoot, encoding: "utf8" });
|
|
23664
25211
|
} catch (e) {
|
|
23665
|
-
return {
|
|
25212
|
+
return {
|
|
25213
|
+
success: false,
|
|
25214
|
+
error: `Merge failed (conflicts?): ${e.message}`,
|
|
25215
|
+
validationSummary,
|
|
25216
|
+
finalBranchConvergenceState: {
|
|
25217
|
+
branch,
|
|
25218
|
+
baseBranch,
|
|
25219
|
+
merged: false,
|
|
25220
|
+
removed: false,
|
|
25221
|
+
validation: "passed",
|
|
25222
|
+
status: "not_mergeable"
|
|
25223
|
+
}
|
|
25224
|
+
};
|
|
23666
25225
|
}
|
|
23667
25226
|
const removeResult = await this.execute("remove_mesh_node", {
|
|
23668
25227
|
meshId,
|
|
@@ -23675,11 +25234,27 @@ var DaemonCommandRouter = class {
|
|
|
23675
25234
|
appendLedgerEntry2(meshId, {
|
|
23676
25235
|
kind: "node_removed",
|
|
23677
25236
|
nodeId,
|
|
23678
|
-
payload: { refined: true, mergedBranch: branch, into: baseBranch }
|
|
25237
|
+
payload: { refined: true, mergedBranch: branch, into: baseBranch, validationSummary }
|
|
23679
25238
|
});
|
|
23680
25239
|
} catch {
|
|
23681
25240
|
}
|
|
23682
|
-
return {
|
|
25241
|
+
return {
|
|
25242
|
+
success: true,
|
|
25243
|
+
merged: true,
|
|
25244
|
+
branch,
|
|
25245
|
+
into: baseBranch,
|
|
25246
|
+
removeResult,
|
|
25247
|
+
validationSummary,
|
|
25248
|
+
finalBranchConvergenceState: {
|
|
25249
|
+
branch: baseBranch,
|
|
25250
|
+
mergedBranch: branch,
|
|
25251
|
+
baseBranch,
|
|
25252
|
+
merged: true,
|
|
25253
|
+
removed: removeResult?.success !== false,
|
|
25254
|
+
validation: "passed",
|
|
25255
|
+
status: removeResult?.success === false ? "merged_cleanup_failed" : "merged"
|
|
25256
|
+
}
|
|
25257
|
+
};
|
|
23683
25258
|
} catch (e) {
|
|
23684
25259
|
return { success: false, error: e.message };
|
|
23685
25260
|
}
|
|
@@ -23697,20 +25272,24 @@ var DaemonCommandRouter = class {
|
|
|
23697
25272
|
);
|
|
23698
25273
|
let sessionCleanup;
|
|
23699
25274
|
if (node && sessionCleanupMode !== "preserve") {
|
|
23700
|
-
sessionCleanup = await this.cleanupMeshSessions({ meshId, nodeId, node, mode: sessionCleanupMode });
|
|
25275
|
+
sessionCleanup = await this.cleanupMeshSessions({ meshId, nodeId, node, mode: sessionCleanupMode, source: "mesh_remove_node" });
|
|
23701
25276
|
if (sessionCleanup.success === false) return { success: false, removed: false, sessionCleanup };
|
|
23702
25277
|
}
|
|
23703
|
-
|
|
23704
|
-
|
|
23705
|
-
|
|
23706
|
-
|
|
23707
|
-
|
|
23708
|
-
|
|
23709
|
-
|
|
23710
|
-
|
|
23711
|
-
|
|
23712
|
-
|
|
25278
|
+
let worktreeCleanup;
|
|
25279
|
+
if (node?.isLocalWorktree) {
|
|
25280
|
+
const cleanupResult = await this.cleanupLocalWorktreeNode({ mesh, node, nodeId });
|
|
25281
|
+
if (cleanupResult.success === false) {
|
|
25282
|
+
return {
|
|
25283
|
+
success: false,
|
|
25284
|
+
removed: false,
|
|
25285
|
+
code: cleanupResult.code,
|
|
25286
|
+
error: cleanupResult.error,
|
|
25287
|
+
recoveryHint: cleanupResult.recoveryHint,
|
|
25288
|
+
...sessionCleanup ? { sessionCleanup } : {},
|
|
25289
|
+
worktreeCleanup: cleanupResult
|
|
25290
|
+
};
|
|
23713
25291
|
}
|
|
25292
|
+
worktreeCleanup = cleanupResult;
|
|
23714
25293
|
}
|
|
23715
25294
|
let removed = false;
|
|
23716
25295
|
if (meshRecord?.inline) {
|
|
@@ -23725,12 +25304,21 @@ var DaemonCommandRouter = class {
|
|
|
23725
25304
|
appendLedgerEntry2(meshId, {
|
|
23726
25305
|
kind: "node_removed",
|
|
23727
25306
|
nodeId,
|
|
23728
|
-
payload: {
|
|
25307
|
+
payload: {
|
|
25308
|
+
worktree: !!node?.isLocalWorktree,
|
|
25309
|
+
sessionCleanupMode,
|
|
25310
|
+
workspace: typeof node?.workspace === "string" ? node.workspace : void 0,
|
|
25311
|
+
daemonId: typeof node?.daemonId === "string" ? node.daemonId : void 0,
|
|
25312
|
+
worktreeBranch: typeof node?.worktreeBranch === "string" ? node.worktreeBranch : void 0,
|
|
25313
|
+
worktreeCleanupFallback: typeof worktreeCleanup?.fallback === "string" ? worktreeCleanup.fallback : void 0,
|
|
25314
|
+
forced: worktreeCleanup?.forced === true ? true : void 0,
|
|
25315
|
+
forceFallbackReason: typeof worktreeCleanup?.reason === "string" ? worktreeCleanup.reason : void 0
|
|
25316
|
+
}
|
|
23729
25317
|
});
|
|
23730
25318
|
} catch {
|
|
23731
25319
|
}
|
|
23732
25320
|
}
|
|
23733
|
-
return { success: true, removed, ...sessionCleanup ? { sessionCleanup } : {} };
|
|
25321
|
+
return { success: true, removed, ...sessionCleanup ? { sessionCleanup } : {}, ...worktreeCleanup ? { worktreeCleanup } : {} };
|
|
23734
25322
|
} catch (e) {
|
|
23735
25323
|
return { success: false, error: e.message };
|
|
23736
25324
|
}
|
|
@@ -23765,6 +25353,7 @@ var DaemonCommandRouter = class {
|
|
|
23765
25353
|
workspace: result.worktreePath,
|
|
23766
25354
|
repoRoot: result.worktreePath,
|
|
23767
25355
|
daemonId: sourceNode.daemonId,
|
|
25356
|
+
machineId: sourceNode.machineId ?? sourceNode.machine_id,
|
|
23768
25357
|
userOverrides: { ...sourceNode.userOverrides || {} },
|
|
23769
25358
|
policy: { ...sourceNode.policy || {} },
|
|
23770
25359
|
isLocalWorktree: true,
|
|
@@ -23778,6 +25367,7 @@ var DaemonCommandRouter = class {
|
|
|
23778
25367
|
workspace: result.worktreePath,
|
|
23779
25368
|
repoRoot: result.worktreePath,
|
|
23780
25369
|
daemonId: sourceNode.daemonId,
|
|
25370
|
+
machineId: sourceNode.machineId ?? sourceNode.machine_id,
|
|
23781
25371
|
userOverrides: { ...sourceNode.userOverrides || {} },
|
|
23782
25372
|
isLocalWorktree: true,
|
|
23783
25373
|
worktreeBranch: result.branch,
|
|
@@ -23896,6 +25486,93 @@ var DaemonCommandRouter = class {
|
|
|
23896
25486
|
meshCoordinatorSetup: coordinatorSetup
|
|
23897
25487
|
};
|
|
23898
25488
|
}
|
|
25489
|
+
if (coordinatorSetup.kind === "cli_command") {
|
|
25490
|
+
let cliCmdSystemPrompt = "";
|
|
25491
|
+
try {
|
|
25492
|
+
cliCmdSystemPrompt = buildCoordinatorSystemPrompt2({ mesh, coordinatorCliType: cliType });
|
|
25493
|
+
} catch (error) {
|
|
25494
|
+
const message = error?.message || String(error);
|
|
25495
|
+
LOG.error("MeshCoordinator", `Failed to build coordinator prompt: ${message}`);
|
|
25496
|
+
return {
|
|
25497
|
+
success: false,
|
|
25498
|
+
code: "mesh_coordinator_prompt_failed",
|
|
25499
|
+
error: `Failed to build Repo Mesh coordinator prompt: ${message}`,
|
|
25500
|
+
meshId,
|
|
25501
|
+
cliType,
|
|
25502
|
+
workspace
|
|
25503
|
+
};
|
|
25504
|
+
}
|
|
25505
|
+
try {
|
|
25506
|
+
const { execFileSync: execCmdSync } = await import("child_process");
|
|
25507
|
+
const cmdParts = coordinatorSetup.command.trim().split(/\s+/);
|
|
25508
|
+
const [regCmd, ...regArgs] = cmdParts;
|
|
25509
|
+
LOG.info("MeshCoordinator", `Running MCP registration: ${coordinatorSetup.command}`);
|
|
25510
|
+
execCmdSync(regCmd, regArgs, { stdio: "pipe", timeout: 15e3 });
|
|
25511
|
+
} catch (error) {
|
|
25512
|
+
LOG.warn("MeshCoordinator", `MCP registration command failed (may be pre-registered): ${error?.message || error}`);
|
|
25513
|
+
}
|
|
25514
|
+
const cliCmdArgs = [];
|
|
25515
|
+
const cliCmdEnv = {};
|
|
25516
|
+
if (cliCmdSystemPrompt) {
|
|
25517
|
+
if (cliType === "codex-cli") {
|
|
25518
|
+
cliCmdArgs.push("-c", `developer_instructions=${JSON.stringify(cliCmdSystemPrompt)}`);
|
|
25519
|
+
} else if (cliType === "gemini-cli") {
|
|
25520
|
+
try {
|
|
25521
|
+
const { writeFileSync: wfs, existsSync: efs, readFileSync: rfs } = await import("fs");
|
|
25522
|
+
const geminiMdPath = `${workspace}/GEMINI.md`;
|
|
25523
|
+
const marker = "<!-- adhdev-mesh-coordinator-prompt -->";
|
|
25524
|
+
const markerEnd = "<!-- /adhdev-mesh-coordinator-prompt -->";
|
|
25525
|
+
const block = `${marker}
|
|
25526
|
+
${cliCmdSystemPrompt}
|
|
25527
|
+
${markerEnd}`;
|
|
25528
|
+
if (efs(geminiMdPath)) {
|
|
25529
|
+
const existing = rfs(geminiMdPath, "utf-8");
|
|
25530
|
+
const replaced = existing.replace(
|
|
25531
|
+
new RegExp(`${marker}[\\s\\S]*?${markerEnd}`, "g"),
|
|
25532
|
+
block
|
|
25533
|
+
);
|
|
25534
|
+
wfs(geminiMdPath, replaced.includes(marker) ? replaced : `${existing}
|
|
25535
|
+
|
|
25536
|
+
${block}`);
|
|
25537
|
+
} else {
|
|
25538
|
+
wfs(geminiMdPath, block);
|
|
25539
|
+
}
|
|
25540
|
+
LOG.info("MeshCoordinator", `Wrote coordinator prompt to ${workspace}/GEMINI.md`);
|
|
25541
|
+
} catch (e) {
|
|
25542
|
+
LOG.warn("MeshCoordinator", `Could not write GEMINI.md: ${e?.message || e}`);
|
|
25543
|
+
}
|
|
25544
|
+
}
|
|
25545
|
+
}
|
|
25546
|
+
const cliCmdLaunch = await this.deps.cliManager.handleCliCommand("launch_cli", {
|
|
25547
|
+
cliType,
|
|
25548
|
+
dir: workspace,
|
|
25549
|
+
cliArgs: cliCmdArgs.length > 0 ? cliCmdArgs : void 0,
|
|
25550
|
+
env: Object.keys(cliCmdEnv).length > 0 ? cliCmdEnv : void 0,
|
|
25551
|
+
settings: { meshCoordinatorFor: meshId }
|
|
25552
|
+
});
|
|
25553
|
+
if (!cliCmdLaunch?.success) {
|
|
25554
|
+
return { success: false, error: cliCmdLaunch?.error || "Failed to launch CLI session" };
|
|
25555
|
+
}
|
|
25556
|
+
LOG.info("MeshCoordinator", `Launched ${cliType} coordinator (cli_command) for mesh ${meshId}`);
|
|
25557
|
+
try {
|
|
25558
|
+
const { appendLedgerEntry: appendLedgerEntry2 } = await Promise.resolve().then(() => (init_mesh_ledger(), mesh_ledger_exports));
|
|
25559
|
+
appendLedgerEntry2(meshId, {
|
|
25560
|
+
kind: "coordinator_started",
|
|
25561
|
+
sessionId: cliCmdLaunch.sessionId || cliCmdLaunch.id,
|
|
25562
|
+
providerType: cliType,
|
|
25563
|
+
payload: { workspace }
|
|
25564
|
+
});
|
|
25565
|
+
} catch {
|
|
25566
|
+
}
|
|
25567
|
+
return {
|
|
25568
|
+
success: true,
|
|
25569
|
+
meshId,
|
|
25570
|
+
cliType,
|
|
25571
|
+
workspace,
|
|
25572
|
+
sessionId: cliCmdLaunch.sessionId || cliCmdLaunch.id,
|
|
25573
|
+
mcpRegistered: true
|
|
25574
|
+
};
|
|
25575
|
+
}
|
|
23899
25576
|
const configFormat = coordinatorSetup.configFormat;
|
|
23900
25577
|
if (configFormat !== "claude_mcp_json" && configFormat !== "hermes_config_yaml") {
|
|
23901
25578
|
return {
|
|
@@ -23950,9 +25627,11 @@ var DaemonCommandRouter = class {
|
|
|
23950
25627
|
args: coordinatorSetup.mcpServer.args
|
|
23951
25628
|
};
|
|
23952
25629
|
if (args?.inlineMesh) {
|
|
25630
|
+
const modeArgIndex = coordinatorSetup.mcpServer.args.findIndex((value) => value === "--mode");
|
|
25631
|
+
const mcpTransport = modeArgIndex >= 0 ? coordinatorSetup.mcpServer.args[modeArgIndex + 1] : "ipc";
|
|
23953
25632
|
mcpServerEntry.env = {
|
|
23954
25633
|
ADHDEV_INLINE_MESH: JSON.stringify(mesh),
|
|
23955
|
-
ADHDEV_MCP_TRANSPORT: "ipc"
|
|
25634
|
+
ADHDEV_MCP_TRANSPORT: mcpTransport === "local" ? "local" : "ipc"
|
|
23956
25635
|
};
|
|
23957
25636
|
}
|
|
23958
25637
|
try {
|
|
@@ -23971,7 +25650,8 @@ var DaemonCommandRouter = class {
|
|
|
23971
25650
|
if (hadExistingMcpConfig) {
|
|
23972
25651
|
try {
|
|
23973
25652
|
const parsedExistingMcpConfig = parseMeshCoordinatorMcpConfig(readFileSync17(mcpConfigPath, "utf-8"), configFormat);
|
|
23974
|
-
|
|
25653
|
+
const existingCoordinatorConfig = hermesManualFallback ? stripHermesCoordinatorTempModelProviderOverrides(parsedExistingMcpConfig) : parsedExistingMcpConfig;
|
|
25654
|
+
existingMcpConfig = { ...existingMcpConfig, ...existingCoordinatorConfig };
|
|
23975
25655
|
copyFileSync4(mcpConfigPath, mcpConfigPath + ".backup");
|
|
23976
25656
|
} catch (error) {
|
|
23977
25657
|
LOG.error("MeshCoordinator", `Failed to parse existing MCP config ${mcpConfigPath}: ${error?.message || error}`);
|
|
@@ -31769,6 +33449,9 @@ function launchIDE(ide, workspacePath) {
|
|
|
31769
33449
|
}
|
|
31770
33450
|
}
|
|
31771
33451
|
|
|
33452
|
+
// src/boot/daemon-lifecycle.ts
|
|
33453
|
+
init_cli_detector();
|
|
33454
|
+
|
|
31772
33455
|
// src/sessions/registry.ts
|
|
31773
33456
|
var SessionRegistry = class {
|
|
31774
33457
|
bySessionId = /* @__PURE__ */ new Map();
|
|
@@ -31999,7 +33682,8 @@ async function initDaemonComponents(config) {
|
|
|
31999
33682
|
cdpManagers,
|
|
32000
33683
|
sessionRegistry,
|
|
32001
33684
|
detectedIdes: detectedIdesRef,
|
|
32002
|
-
refreshProviderAvailability
|
|
33685
|
+
refreshProviderAvailability,
|
|
33686
|
+
dispatchMeshCommand: config.dispatchMeshCommand
|
|
32003
33687
|
};
|
|
32004
33688
|
setupMeshEventForwarding(components);
|
|
32005
33689
|
return components;
|
|
@@ -32100,10 +33784,12 @@ export {
|
|
|
32100
33784
|
IdeProviderInstance,
|
|
32101
33785
|
InMemoryGitSnapshotStore,
|
|
32102
33786
|
LOG,
|
|
33787
|
+
MAX_LEDGER_SLICE_LIMIT,
|
|
32103
33788
|
MIN_GIT_WORKSPACE_POLL_INTERVAL_MS,
|
|
32104
33789
|
MIN_MACHINE_RUNTIME_SUBSCRIPTION_INTERVAL_MS,
|
|
32105
33790
|
MIN_SESSION_HOST_DIAGNOSTICS_SUBSCRIPTION_INTERVAL_MS,
|
|
32106
33791
|
NodePtyTransportFactory,
|
|
33792
|
+
P2pRelayFailureError,
|
|
32107
33793
|
ProviderCliAdapter,
|
|
32108
33794
|
ProviderInstanceManager,
|
|
32109
33795
|
ProviderLoader,
|
|
@@ -32114,12 +33800,16 @@ export {
|
|
|
32114
33800
|
addNode,
|
|
32115
33801
|
appendLedgerEntry,
|
|
32116
33802
|
appendRecentActivity,
|
|
33803
|
+
appendRemoteLedgerEntries,
|
|
32117
33804
|
buildAssistantChatMessage,
|
|
32118
33805
|
buildChatMessage,
|
|
32119
33806
|
buildChatMessageSignature,
|
|
32120
33807
|
buildChatTailDeliverySignature,
|
|
32121
33808
|
buildCoordinatorSystemPrompt,
|
|
32122
33809
|
buildMachineInfo,
|
|
33810
|
+
buildMeshLedgerReconciliationEvidence,
|
|
33811
|
+
buildMeshLedgerReplicaEvidence,
|
|
33812
|
+
buildP2pRelayFailurePayload,
|
|
32123
33813
|
buildPinnedGlobalInstallCommand,
|
|
32124
33814
|
buildRuntimeSystemChatMessage,
|
|
32125
33815
|
buildSessionEntries,
|
|
@@ -32130,10 +33820,13 @@ export {
|
|
|
32130
33820
|
buildThoughtChatMessage,
|
|
32131
33821
|
buildToolChatMessage,
|
|
32132
33822
|
buildUserChatMessage,
|
|
33823
|
+
cancelTask,
|
|
32133
33824
|
claimNextTask,
|
|
32134
33825
|
classifyChatMessageVisibility,
|
|
32135
33826
|
classifyHotChatSessionsForSubscriptionFlush,
|
|
33827
|
+
classifyP2pRelayFailure,
|
|
32136
33828
|
clearDebugTrace,
|
|
33829
|
+
clearPendingMeshCoordinatorEvents,
|
|
32137
33830
|
compareGitSnapshots,
|
|
32138
33831
|
configureDebugTraceStore,
|
|
32139
33832
|
connectCdpManager,
|
|
@@ -32149,6 +33842,7 @@ export {
|
|
|
32149
33842
|
detectAllVersions,
|
|
32150
33843
|
detectCLIs,
|
|
32151
33844
|
detectIDEs,
|
|
33845
|
+
drainPendingMeshCoordinatorEvents,
|
|
32152
33846
|
enqueueTask,
|
|
32153
33847
|
ensureSessionHostReady,
|
|
32154
33848
|
execNpmCommandSync,
|
|
@@ -32173,7 +33867,9 @@ export {
|
|
|
32173
33867
|
getLogLevel,
|
|
32174
33868
|
getMesh,
|
|
32175
33869
|
getMeshByRepo,
|
|
33870
|
+
getMeshQueueStats,
|
|
32176
33871
|
getNpmExecOptions,
|
|
33872
|
+
getPendingMeshCoordinatorEvents,
|
|
32177
33873
|
getQueue,
|
|
32178
33874
|
getRecentActivity,
|
|
32179
33875
|
getRecentCommands,
|
|
@@ -32199,6 +33895,7 @@ export {
|
|
|
32199
33895
|
isInternalChatMessage,
|
|
32200
33896
|
isManagedStatusWaiting,
|
|
32201
33897
|
isManagedStatusWorking,
|
|
33898
|
+
isP2pRelayTransportFailure,
|
|
32202
33899
|
isPathInside,
|
|
32203
33900
|
isSessionHostLiveRuntime,
|
|
32204
33901
|
isSessionHostRecoverySnapshot,
|
|
@@ -32237,10 +33934,12 @@ export {
|
|
|
32237
33934
|
probeCdpPort,
|
|
32238
33935
|
readChatHistory,
|
|
32239
33936
|
readLedgerEntries,
|
|
33937
|
+
readLedgerSlice,
|
|
32240
33938
|
recordDebugTrace,
|
|
32241
33939
|
registerExtensionProviders,
|
|
32242
33940
|
removeNode,
|
|
32243
33941
|
removeWorktree,
|
|
33942
|
+
requeueTask,
|
|
32244
33943
|
resetConfig,
|
|
32245
33944
|
resetDebugRuntimeConfig,
|
|
32246
33945
|
resetState,
|