@adhdev/daemon-core 0.9.77-rc.5 → 0.9.77-rc.50
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 +8 -4
- package/dist/index.js +1959 -291
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1947 -291
- package/dist/index.mjs.map +1 -1
- package/dist/mesh/mesh-events.d.ts +10 -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 +56 -1
- package/dist/mesh/p2p-relay-failure.d.ts +35 -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 +20 -4
- package/src/mesh/coordinator-prompt.ts +21 -10
- package/src/mesh/mesh-events.ts +522 -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 +163 -10
- package/src/mesh/p2p-relay-failure.ts +152 -0
- package/src/providers/cli-provider-instance.ts +153 -30
- package/src/repo-mesh-types.ts +2 -0
- package/src/shared-types.ts +38 -0
package/dist/index.js
CHANGED
|
@@ -96,13 +96,25 @@ async function createWorktree(opts) {
|
|
|
96
96
|
branch
|
|
97
97
|
};
|
|
98
98
|
}
|
|
99
|
-
async function removeWorktree(repoRoot, worktreePath) {
|
|
99
|
+
async function removeWorktree(repoRoot, worktreePath, opts = {}) {
|
|
100
100
|
if (!(0, import_node_fs2.existsSync)(worktreePath)) {
|
|
101
101
|
await pruneWorktrees(repoRoot);
|
|
102
102
|
return { success: true, removedPath: worktreePath };
|
|
103
103
|
}
|
|
104
|
+
if (opts.requireClean) {
|
|
105
|
+
const { stdout } = await execFileAsync2("git", ["status", "--porcelain"], {
|
|
106
|
+
cwd: worktreePath,
|
|
107
|
+
encoding: "utf8",
|
|
108
|
+
timeout: GIT_TIMEOUT_MS,
|
|
109
|
+
maxBuffer: GIT_MAX_BUFFER,
|
|
110
|
+
windowsHide: true
|
|
111
|
+
});
|
|
112
|
+
if (stdout.trim()) {
|
|
113
|
+
throw new Error(`Refusing to remove dirty worktree: ${worktreePath}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
104
116
|
try {
|
|
105
|
-
await execFileAsync2("git", ["worktree", "remove", worktreePath
|
|
117
|
+
await execFileAsync2("git", ["worktree", "remove", worktreePath], {
|
|
106
118
|
cwd: repoRoot,
|
|
107
119
|
encoding: "utf8",
|
|
108
120
|
timeout: GIT_TIMEOUT_MS,
|
|
@@ -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 path4, import_promises3, import_node_fs2, import_node_child_process2, import_node_util2, execFileAsync2, WORKTREE_DIR_NAME, GIT_TIMEOUT_MS, GIT_MAX_BUFFER;
|
|
202
|
+
var path4, import_promises3, import_node_fs2, import_node_child_process2, import_node_util2, 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";
|
|
@@ -174,6 +212,7 @@ var init_git_worktree = __esm({
|
|
|
174
212
|
WORKTREE_DIR_NAME = ".adhdev-worktrees";
|
|
175
213
|
GIT_TIMEOUT_MS = 3e4;
|
|
176
214
|
GIT_MAX_BUFFER = 4 * 1024 * 1024;
|
|
215
|
+
SUBMODULE_WORKTREE_REMOVE_RE = /working trees containing submodules cannot be moved or removed/i;
|
|
177
216
|
}
|
|
178
217
|
});
|
|
179
218
|
|
|
@@ -283,7 +322,8 @@ function ensureMachineId(config) {
|
|
|
283
322
|
};
|
|
284
323
|
}
|
|
285
324
|
function getConfigDir() {
|
|
286
|
-
const
|
|
325
|
+
const override = process.env.ADHDEV_CONFIG_DIR;
|
|
326
|
+
const dir = override && override.trim() ? override.trim() : (0, import_path.join)((0, import_os.homedir)(), ".adhdev");
|
|
287
327
|
if (!(0, import_fs.existsSync)(dir)) {
|
|
288
328
|
(0, import_fs.mkdirSync)(dir, { recursive: true });
|
|
289
329
|
}
|
|
@@ -546,6 +586,7 @@ function addNode(meshId, opts) {
|
|
|
546
586
|
workspace: opts.workspace.trim(),
|
|
547
587
|
repoRoot: opts.repoRoot,
|
|
548
588
|
daemonId: opts.daemonId,
|
|
589
|
+
machineId: opts.machineId,
|
|
549
590
|
userOverrides: opts.userOverrides || {},
|
|
550
591
|
policy: opts.policy || {},
|
|
551
592
|
isLocalWorktree: opts.isLocalWorktree,
|
|
@@ -676,7 +717,8 @@ function buildRulesSection(coordinatorCliType) {
|
|
|
676
717
|
- **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.
|
|
677
718
|
- **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.
|
|
678
719
|
- **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.
|
|
679
|
-
- **Front-load
|
|
720
|
+
- **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.
|
|
721
|
+
- **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.
|
|
680
722
|
- **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.
|
|
681
723
|
- **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.
|
|
682
724
|
- **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.
|
|
@@ -685,6 +727,7 @@ function buildRulesSection(coordinatorCliType) {
|
|
|
685
727
|
- **Respect node capabilities.** Don't send build tasks to read-only nodes. Don't push from nodes that aren't allowed to.
|
|
686
728
|
- **Never fabricate tool results.** Always call the actual tool; never pretend you did.
|
|
687
729
|
- **Clean up worktree nodes.** After a worktree task completes and its changes are merged or checkpointed, call \`mesh_remove_node\` to free resources.
|
|
730
|
+
- **Do not strand completed branches.** A checkpointed or clean feature/worktree branch is not done by itself. Merge/refine it to the mesh default branch, or explicitly report one of \`pushed_feature_branch_needs_merge\`, \`blocked_review\`, \`cleanup_candidate\`, or \`not_mergeable\` with the next action.
|
|
688
731
|
- **Name worktree branches meaningfully.** Use descriptive names like \`feat/auth-refactor\` or \`fix/build-123\`.${coordinatorNote}`;
|
|
689
732
|
}
|
|
690
733
|
var TOOLS_SECTION, TOOL_EXPOSURE_PREFLIGHT_SECTION, WORKFLOW_SECTION;
|
|
@@ -696,17 +739,24 @@ var init_coordinator_prompt = __esm({
|
|
|
696
739
|
|
|
697
740
|
| Tool | Purpose |
|
|
698
741
|
|------|---------|
|
|
699
|
-
| \`mesh_status\` | Check all nodes' health, git state,
|
|
742
|
+
| \`mesh_status\` | Check all nodes' health, git state, active sessions, and branch convergence |
|
|
700
743
|
| \`mesh_list_nodes\` | List nodes with workspace paths |
|
|
744
|
+
| \`mesh_enqueue_task\` | Add a task to the pull-based work queue; idle nodes auto-claim |
|
|
745
|
+
| \`mesh_view_queue\` | View queue status \u2014 pending, assigned, completed, failed, cancelled tasks |
|
|
746
|
+
| \`mesh_queue_cancel\` | Cancel a queue task without deleting audit history |
|
|
747
|
+
| \`mesh_queue_requeue\` | Return a task to pending for retry; clears stale session targets |
|
|
748
|
+
| \`mesh_send_task\` | Legacy push: enqueue a task targeted at a specific node |
|
|
701
749
|
| \`mesh_launch_session\` | Start a new agent session on a node |
|
|
702
|
-
| \`
|
|
703
|
-
| \`
|
|
750
|
+
| \`mesh_read_chat\` | Read recent chat messages from a delegated agent session |
|
|
751
|
+
| \`mesh_read_debug\` | Collect a daemon-side chat/parser debug bundle for a session |
|
|
704
752
|
| \`mesh_task_history\` | Read the task ledger \u2014 dispatches, completions, failures. Use to understand what has been done before deciding next steps |
|
|
705
753
|
| \`mesh_git_status\` | Check git status on a specific node |
|
|
706
754
|
| \`mesh_checkpoint\` | Create a git checkpoint on a node |
|
|
707
755
|
| \`mesh_approve\` | Approve/reject a pending agent action |
|
|
708
756
|
| \`mesh_clone_node\` | Create a worktree node for isolated parallel branch work |
|
|
709
|
-
| \`
|
|
757
|
+
| \`mesh_refine_node\` | Validate and merge a completed worktree node back into its base branch |
|
|
758
|
+
| \`mesh_remove_node\` | Remove a node (cleans up worktree if applicable) |
|
|
759
|
+
| \`mesh_cleanup_sessions\` | Manually clean up delegated session records for a node |`;
|
|
710
760
|
TOOL_EXPOSURE_PREFLIGHT_SECTION = `## Tool Exposure Preflight
|
|
711
761
|
|
|
712
762
|
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\`.`;
|
|
@@ -716,14 +766,16 @@ Before doing any coordinator work, confirm that the actual callable tool list in
|
|
|
716
766
|
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.
|
|
717
767
|
3. **Queue / Delegate** \u2014 The Mesh uses an autonomous pull-based Work Queue:
|
|
718
768
|
a. **General Tasks**: Enqueue tasks using \`mesh_enqueue_task\`. Idle node agents will automatically pull tasks from the queue and begin working.
|
|
719
|
-
b. **Node Preparation**: Call \`mesh_launch_session\`
|
|
769
|
+
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.
|
|
720
770
|
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.
|
|
721
|
-
d.
|
|
771
|
+
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.
|
|
772
|
+
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.
|
|
722
773
|
4. **Monitor** \u2014 Prefer event-driven completion/status notifications. Do **not** poll \`mesh_read_chat\` repeatedly. Use \`mesh_view_queue\` to see the status of all pending, assigned, completed, and failed tasks. Do not call \`mesh_read_chat\` again within a few seconds for the same generating session. Use at most one compact \`mesh_read_chat\` check after a completion/approval signal. Handle approvals via \`mesh_approve\`.
|
|
723
774
|
5. **Verify** \u2014 When a task reports completion or git work is visible, call \`mesh_git_status\` to verify changes were made.
|
|
724
775
|
6. **Checkpoint** \u2014 Call \`mesh_checkpoint\` to save the work.
|
|
725
|
-
7. **
|
|
726
|
-
8. **
|
|
776
|
+
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.
|
|
777
|
+
8. **Clean up** \u2014 Remove worktree nodes via \`mesh_remove_node\` after their work is merged or no longer needed.
|
|
778
|
+
9. **Report** \u2014 Summarize what was done, what changed, any issues, and the branch convergence state.
|
|
727
779
|
|
|
728
780
|
## Failure Recovery
|
|
729
781
|
|
|
@@ -733,7 +785,7 @@ When a node agent stops unexpectedly, the daemon automatically enriches the syst
|
|
|
733
785
|
- A recommendation: **retry**, **reassign**, or **escalate**
|
|
734
786
|
|
|
735
787
|
Follow these recovery rules:
|
|
736
|
-
1. **If "Retry recommended"**:
|
|
788
|
+
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.
|
|
737
789
|
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.
|
|
738
790
|
3. **If no recovery context**: The stop may be intentional (normal completion). Use \`mesh_read_chat\` once to verify, then move on.
|
|
739
791
|
4. **Always record what happened**: After handling a failure, briefly note the outcome in your report to the user.`;
|
|
@@ -743,14 +795,23 @@ Follow these recovery rules:
|
|
|
743
795
|
// src/mesh/mesh-ledger.ts
|
|
744
796
|
var mesh_ledger_exports = {};
|
|
745
797
|
__export(mesh_ledger_exports, {
|
|
798
|
+
MAX_LEDGER_SLICE_LIMIT: () => MAX_LEDGER_SLICE_LIMIT,
|
|
746
799
|
appendLedgerEntry: () => appendLedgerEntry,
|
|
747
800
|
appendRemoteLedgerEntries: () => appendRemoteLedgerEntries,
|
|
801
|
+
buildTaskCompletionEvidence: () => buildTaskCompletionEvidence,
|
|
748
802
|
getLedgerDir: () => getLedgerDir,
|
|
749
803
|
getLedgerSummary: () => getLedgerSummary,
|
|
750
804
|
getSessionRecoveryContext: () => getSessionRecoveryContext,
|
|
805
|
+
isIntentionalCleanupStopEntry: () => isIntentionalCleanupStopEntry,
|
|
751
806
|
meshLedgerEvents: () => meshLedgerEvents,
|
|
752
|
-
readLedgerEntries: () => readLedgerEntries
|
|
807
|
+
readLedgerEntries: () => readLedgerEntries,
|
|
808
|
+
readLedgerSlice: () => readLedgerSlice
|
|
753
809
|
});
|
|
810
|
+
function isIntentionalCleanupStopEntry(entry) {
|
|
811
|
+
if (entry.kind !== "session_stopped" && entry.kind !== "task_failed" && entry.kind !== "task_stalled") return false;
|
|
812
|
+
const payload = entry.payload && typeof entry.payload === "object" && !Array.isArray(entry.payload) ? entry.payload : {};
|
|
813
|
+
return payload.intentional === true && (payload.reason === "operator_cleanup" || payload.intentionalStopReason === "operator_cleanup" || payload.source === "mesh_cleanup_sessions" || payload.source === "mesh_remove_node");
|
|
814
|
+
}
|
|
754
815
|
function getLedgerDir() {
|
|
755
816
|
const dir = (0, import_path3.join)(getConfigDir(), LEDGER_DIR_NAME);
|
|
756
817
|
if (!(0, import_fs3.existsSync)(dir)) {
|
|
@@ -766,6 +827,37 @@ function getRotatedPath(meshId, index) {
|
|
|
766
827
|
const safe = meshId.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
767
828
|
return (0, import_path3.join)(getLedgerDir(), `${safe}.${index}.jsonl`);
|
|
768
829
|
}
|
|
830
|
+
function buildTaskCompletionEvidence(opts) {
|
|
831
|
+
const providerSessionId = opts.providerSessionId?.trim() || void 0;
|
|
832
|
+
const providerType = opts.providerType?.trim() || void 0;
|
|
833
|
+
return {
|
|
834
|
+
source: "agent_status_event",
|
|
835
|
+
event: opts.event,
|
|
836
|
+
nodeId: opts.nodeId,
|
|
837
|
+
sessionId: opts.sessionId,
|
|
838
|
+
providerType,
|
|
839
|
+
completedAt: opts.completedAt || (/* @__PURE__ */ new Date()).toISOString(),
|
|
840
|
+
transcriptHandle: {
|
|
841
|
+
kind: providerSessionId ? "provider_session" : "runtime_session",
|
|
842
|
+
sessionId: opts.sessionId,
|
|
843
|
+
providerSessionId,
|
|
844
|
+
finalSummaryAvailable: typeof opts.finalSummary === "string" && opts.finalSummary.trim().length > 0
|
|
845
|
+
},
|
|
846
|
+
git: {
|
|
847
|
+
status: "deferred",
|
|
848
|
+
reason: "ordinary_completion_git_status_not_checked"
|
|
849
|
+
},
|
|
850
|
+
validation: {
|
|
851
|
+
status: "deferred",
|
|
852
|
+
commandsRun: [],
|
|
853
|
+
reason: "ordinary_completion_validation_not_run"
|
|
854
|
+
},
|
|
855
|
+
checkpoint: {
|
|
856
|
+
attempted: false,
|
|
857
|
+
reason: "not_attempted_for_ordinary_completion"
|
|
858
|
+
}
|
|
859
|
+
};
|
|
860
|
+
}
|
|
769
861
|
function appendLedgerEntry(meshId, partial) {
|
|
770
862
|
const entry = {
|
|
771
863
|
id: (0, import_crypto4.randomUUID)(),
|
|
@@ -792,15 +884,49 @@ function appendLedgerEntry(meshId, partial) {
|
|
|
792
884
|
throw new Error(`Failed to append to ledger for mesh ${meshId}: ${e.message}`);
|
|
793
885
|
}
|
|
794
886
|
}
|
|
887
|
+
function clampLedgerSliceLimit(limit) {
|
|
888
|
+
if (typeof limit !== "number" || !Number.isFinite(limit)) return DEFAULT_LEDGER_SLICE_LIMIT;
|
|
889
|
+
return Math.max(1, Math.min(MAX_LEDGER_SLICE_LIMIT, Math.floor(limit)));
|
|
890
|
+
}
|
|
891
|
+
function isValidRemoteLedgerEntry(meshId, value) {
|
|
892
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
893
|
+
const entry = value;
|
|
894
|
+
if (typeof entry.id !== "string" || !entry.id.trim()) return false;
|
|
895
|
+
if (entry.meshId !== meshId) return false;
|
|
896
|
+
if (typeof entry.timestamp !== "string" || Number.isNaN(new Date(entry.timestamp).getTime())) return false;
|
|
897
|
+
if (typeof entry.kind !== "string" || !entry.kind.trim()) return false;
|
|
898
|
+
if (!entry.payload || typeof entry.payload !== "object" || Array.isArray(entry.payload)) return false;
|
|
899
|
+
return true;
|
|
900
|
+
}
|
|
795
901
|
function appendRemoteLedgerEntries(meshId, entries) {
|
|
796
|
-
if (entries.length === 0) return;
|
|
902
|
+
if (entries.length === 0) return { accepted: 0, skippedDuplicate: 0, rejectedInvalid: 0, entries: [] };
|
|
797
903
|
const ledgerPath = getLedgerPath(meshId);
|
|
798
904
|
const existing = new Set(readLedgerEntries(meshId).map((e) => e.id));
|
|
799
|
-
const
|
|
800
|
-
|
|
905
|
+
const validEntries = [];
|
|
906
|
+
let rejectedInvalid = 0;
|
|
907
|
+
let skippedDuplicate = 0;
|
|
908
|
+
for (const entry of entries) {
|
|
909
|
+
if (!isValidRemoteLedgerEntry(meshId, entry)) {
|
|
910
|
+
rejectedInvalid++;
|
|
911
|
+
continue;
|
|
912
|
+
}
|
|
913
|
+
if (existing.has(entry.id)) {
|
|
914
|
+
skippedDuplicate++;
|
|
915
|
+
continue;
|
|
916
|
+
}
|
|
917
|
+
existing.add(entry.id);
|
|
918
|
+
validEntries.push(entry);
|
|
919
|
+
}
|
|
920
|
+
if (validEntries.length === 0) {
|
|
921
|
+
return { accepted: 0, skippedDuplicate, rejectedInvalid, entries: [] };
|
|
922
|
+
}
|
|
801
923
|
try {
|
|
802
|
-
const lines =
|
|
924
|
+
const lines = validEntries.map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
803
925
|
(0, import_fs3.appendFileSync)(ledgerPath, lines, { encoding: "utf-8", mode: 384 });
|
|
926
|
+
for (const entry of validEntries) {
|
|
927
|
+
meshLedgerEvents.emit("append", meshId, entry);
|
|
928
|
+
}
|
|
929
|
+
return { accepted: validEntries.length, skippedDuplicate, rejectedInvalid, entries: validEntries };
|
|
804
930
|
} catch (e) {
|
|
805
931
|
throw new Error(`Failed to append remote ledger entries for mesh ${meshId}: ${e.message}`);
|
|
806
932
|
}
|
|
@@ -839,6 +965,34 @@ function readLedgerEntries(meshId, opts) {
|
|
|
839
965
|
}
|
|
840
966
|
return entries;
|
|
841
967
|
}
|
|
968
|
+
function readLedgerSlice(meshId, opts) {
|
|
969
|
+
const limit = clampLedgerSliceLimit(opts?.limit);
|
|
970
|
+
let entries = readLedgerEntries(meshId, { since: opts?.since, kind: opts?.kind });
|
|
971
|
+
const afterId = typeof opts?.afterId === "string" && opts.afterId.trim() ? opts.afterId.trim() : null;
|
|
972
|
+
if (afterId) {
|
|
973
|
+
const index = entries.findIndex((entry) => entry.id === afterId);
|
|
974
|
+
entries = index >= 0 ? entries.slice(index + 1) : entries;
|
|
975
|
+
}
|
|
976
|
+
const bounded = entries.slice(0, limit);
|
|
977
|
+
return {
|
|
978
|
+
protocol: "adhdev.mesh.ledger.slice.v1",
|
|
979
|
+
meshId,
|
|
980
|
+
entries: bounded,
|
|
981
|
+
cursor: {
|
|
982
|
+
afterId,
|
|
983
|
+
nextAfterId: bounded.length ? bounded[bounded.length - 1].id : afterId,
|
|
984
|
+
limit,
|
|
985
|
+
hasMore: entries.length > bounded.length
|
|
986
|
+
},
|
|
987
|
+
summary: getLedgerSummary(meshId),
|
|
988
|
+
sourceOfTruth: {
|
|
989
|
+
kind: "local_jsonl",
|
|
990
|
+
path: getLedgerPath(meshId),
|
|
991
|
+
bounded: true,
|
|
992
|
+
maxLimit: MAX_LEDGER_SLICE_LIMIT
|
|
993
|
+
}
|
|
994
|
+
};
|
|
995
|
+
}
|
|
842
996
|
function getLedgerSummary(meshId) {
|
|
843
997
|
const entries = readLedgerEntries(meshId);
|
|
844
998
|
const now = Date.now();
|
|
@@ -864,15 +1018,17 @@ function getLedgerSummary(meshId) {
|
|
|
864
1018
|
summary.taskCompleted++;
|
|
865
1019
|
break;
|
|
866
1020
|
case "task_failed": {
|
|
1021
|
+
if (isIntentionalCleanupStopEntry(entry)) break;
|
|
867
1022
|
summary.taskFailed++;
|
|
868
1023
|
if (new Date(entry.timestamp).getTime() >= recentFailureCutoff) {
|
|
869
1024
|
summary.recentFailures++;
|
|
870
1025
|
}
|
|
871
1026
|
break;
|
|
872
1027
|
}
|
|
873
|
-
case "task_stalled":
|
|
874
|
-
summary.taskStalled++;
|
|
1028
|
+
case "task_stalled": {
|
|
1029
|
+
if (!isIntentionalCleanupStopEntry(entry)) summary.taskStalled++;
|
|
875
1030
|
break;
|
|
1031
|
+
}
|
|
876
1032
|
case "session_launched":
|
|
877
1033
|
summary.sessionLaunched++;
|
|
878
1034
|
break;
|
|
@@ -911,6 +1067,7 @@ function getSessionRecoveryContext(meshId, opts) {
|
|
|
911
1067
|
if (new Date(e.timestamp).getTime() < recentWindow) break;
|
|
912
1068
|
if (opts.nodeId && e.nodeId !== opts.nodeId) continue;
|
|
913
1069
|
if (e.kind === "task_failed") {
|
|
1070
|
+
if (isIntentionalCleanupStopEntry(e)) continue;
|
|
914
1071
|
consecutiveNodeFailures++;
|
|
915
1072
|
} else if (e.kind === "task_completed" || e.kind === "task_dispatched") {
|
|
916
1073
|
break;
|
|
@@ -961,7 +1118,7 @@ function rotateLedgerFile(meshId, currentPath) {
|
|
|
961
1118
|
} catch {
|
|
962
1119
|
}
|
|
963
1120
|
}
|
|
964
|
-
var import_fs3, import_path3, import_crypto4, import_events, LEDGER_DIR_NAME, MAX_FILE_SIZE_BYTES, RECENT_FAILURE_WINDOW_MS, meshLedgerEvents;
|
|
1121
|
+
var import_fs3, import_path3, import_crypto4, import_events, LEDGER_DIR_NAME, MAX_FILE_SIZE_BYTES, RECENT_FAILURE_WINDOW_MS, DEFAULT_LEDGER_SLICE_LIMIT, MAX_LEDGER_SLICE_LIMIT, meshLedgerEvents;
|
|
965
1122
|
var init_mesh_ledger = __esm({
|
|
966
1123
|
"src/mesh/mesh-ledger.ts"() {
|
|
967
1124
|
"use strict";
|
|
@@ -973,11 +1130,27 @@ var init_mesh_ledger = __esm({
|
|
|
973
1130
|
LEDGER_DIR_NAME = "mesh-ledger";
|
|
974
1131
|
MAX_FILE_SIZE_BYTES = 10 * 1024 * 1024;
|
|
975
1132
|
RECENT_FAILURE_WINDOW_MS = 30 * 60 * 1e3;
|
|
1133
|
+
DEFAULT_LEDGER_SLICE_LIMIT = 100;
|
|
1134
|
+
MAX_LEDGER_SLICE_LIMIT = 500;
|
|
976
1135
|
meshLedgerEvents = new import_events.EventEmitter();
|
|
977
1136
|
}
|
|
978
1137
|
});
|
|
979
1138
|
|
|
980
1139
|
// src/mesh/mesh-work-queue.ts
|
|
1140
|
+
var mesh_work_queue_exports = {};
|
|
1141
|
+
__export(mesh_work_queue_exports, {
|
|
1142
|
+
ACTIVE_MESH_QUEUE_STATUSES: () => ACTIVE_MESH_QUEUE_STATUSES,
|
|
1143
|
+
HISTORICAL_MESH_QUEUE_STATUSES: () => HISTORICAL_MESH_QUEUE_STATUSES,
|
|
1144
|
+
cancelTask: () => cancelTask,
|
|
1145
|
+
claimNextTask: () => claimNextTask,
|
|
1146
|
+
enqueueTask: () => enqueueTask,
|
|
1147
|
+
getMeshQueueStats: () => getMeshQueueStats,
|
|
1148
|
+
getQueue: () => getQueue,
|
|
1149
|
+
recordTaskAutoLaunch: () => recordTaskAutoLaunch,
|
|
1150
|
+
requeueTask: () => requeueTask,
|
|
1151
|
+
updateSessionTaskStatus: () => updateSessionTaskStatus,
|
|
1152
|
+
updateTaskStatus: () => updateTaskStatus
|
|
1153
|
+
});
|
|
981
1154
|
function getQueuePath(meshId) {
|
|
982
1155
|
const safe = meshId.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
983
1156
|
return (0, import_path4.join)(getLedgerDir(), `${safe}.queue.json`);
|
|
@@ -1004,6 +1177,7 @@ function enqueueTask(meshId, message, opts) {
|
|
|
1004
1177
|
message,
|
|
1005
1178
|
status: "pending",
|
|
1006
1179
|
targetNodeId: opts?.targetNodeId,
|
|
1180
|
+
targetSessionId: opts?.targetSessionId,
|
|
1007
1181
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1008
1182
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1009
1183
|
};
|
|
@@ -1021,9 +1195,14 @@ function getQueue(meshId, opts) {
|
|
|
1021
1195
|
}
|
|
1022
1196
|
function claimNextTask(meshId, nodeId, sessionId) {
|
|
1023
1197
|
const queue = readQueue(meshId);
|
|
1024
|
-
|
|
1198
|
+
const hasActiveAssignment = queue.some((q) => q.status === "assigned" && (q.assignedSessionId === sessionId || q.assignedNodeId === nodeId));
|
|
1199
|
+
if (hasActiveAssignment) return null;
|
|
1200
|
+
let targetIdx = queue.findIndex((q) => q.status === "pending" && q.targetSessionId === sessionId);
|
|
1201
|
+
if (targetIdx === -1) {
|
|
1202
|
+
targetIdx = queue.findIndex((q) => q.status === "pending" && q.targetNodeId === nodeId && !q.targetSessionId);
|
|
1203
|
+
}
|
|
1025
1204
|
if (targetIdx === -1) {
|
|
1026
|
-
targetIdx = queue.findIndex((q) => q.status === "pending" && !q.targetNodeId);
|
|
1205
|
+
targetIdx = queue.findIndex((q) => q.status === "pending" && !q.targetNodeId && !q.targetSessionId);
|
|
1027
1206
|
}
|
|
1028
1207
|
if (targetIdx === -1) return null;
|
|
1029
1208
|
const entry = queue[targetIdx];
|
|
@@ -1043,6 +1222,53 @@ function updateTaskStatus(meshId, taskId, status) {
|
|
|
1043
1222
|
writeQueue(meshId, queue);
|
|
1044
1223
|
return queue[idx];
|
|
1045
1224
|
}
|
|
1225
|
+
function recordTaskAutoLaunch(meshId, taskId, autoLaunch) {
|
|
1226
|
+
const queue = readQueue(meshId);
|
|
1227
|
+
const idx = queue.findIndex((q) => q.id === taskId);
|
|
1228
|
+
if (idx === -1) return null;
|
|
1229
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1230
|
+
queue[idx].autoLaunch = {
|
|
1231
|
+
...autoLaunch,
|
|
1232
|
+
updatedAt: now
|
|
1233
|
+
};
|
|
1234
|
+
queue[idx].updatedAt = now;
|
|
1235
|
+
writeQueue(meshId, queue);
|
|
1236
|
+
return queue[idx];
|
|
1237
|
+
}
|
|
1238
|
+
function cancelTask(meshId, taskId, opts) {
|
|
1239
|
+
const queue = readQueue(meshId);
|
|
1240
|
+
const idx = queue.findIndex((q) => q.id === taskId);
|
|
1241
|
+
if (idx === -1) return null;
|
|
1242
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1243
|
+
queue[idx].status = "cancelled";
|
|
1244
|
+
queue[idx].updatedAt = now;
|
|
1245
|
+
queue[idx].cancelledAt = now;
|
|
1246
|
+
if (opts?.reason) queue[idx].cancelReason = opts.reason;
|
|
1247
|
+
writeQueue(meshId, queue);
|
|
1248
|
+
return queue[idx];
|
|
1249
|
+
}
|
|
1250
|
+
function requeueTask(meshId, taskId, opts) {
|
|
1251
|
+
const queue = readQueue(meshId);
|
|
1252
|
+
const idx = queue.findIndex((q) => q.id === taskId);
|
|
1253
|
+
if (idx === -1) return null;
|
|
1254
|
+
const entry = queue[idx];
|
|
1255
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1256
|
+
entry.status = "pending";
|
|
1257
|
+
delete entry.assignedNodeId;
|
|
1258
|
+
delete entry.assignedSessionId;
|
|
1259
|
+
delete entry.cancelledAt;
|
|
1260
|
+
delete entry.cancelReason;
|
|
1261
|
+
if (opts?.clearTargetNode) delete entry.targetNodeId;
|
|
1262
|
+
if (typeof opts?.targetNodeId === "string") entry.targetNodeId = opts.targetNodeId;
|
|
1263
|
+
if (opts?.clearTargetSession !== false) delete entry.targetSessionId;
|
|
1264
|
+
if (typeof opts?.targetSessionId === "string") entry.targetSessionId = opts.targetSessionId;
|
|
1265
|
+
entry.updatedAt = now;
|
|
1266
|
+
entry.requeuedAt = now;
|
|
1267
|
+
entry.requeueCount = (entry.requeueCount || 0) + 1;
|
|
1268
|
+
if (opts?.reason) entry.requeueReason = opts.reason;
|
|
1269
|
+
writeQueue(meshId, queue);
|
|
1270
|
+
return entry;
|
|
1271
|
+
}
|
|
1046
1272
|
function updateSessionTaskStatus(meshId, sessionId, status) {
|
|
1047
1273
|
const queue = readQueue(meshId);
|
|
1048
1274
|
for (let i = queue.length - 1; i >= 0; i--) {
|
|
@@ -1057,14 +1283,38 @@ function updateSessionTaskStatus(meshId, sessionId, status) {
|
|
|
1057
1283
|
}
|
|
1058
1284
|
function getMeshQueueStats(meshId) {
|
|
1059
1285
|
const queue = readQueue(meshId);
|
|
1286
|
+
const pending = queue.filter((q) => q.status === "pending").length;
|
|
1287
|
+
const assigned = queue.filter((q) => q.status === "assigned").length;
|
|
1288
|
+
const completed = queue.filter((q) => q.status === "completed").length;
|
|
1289
|
+
const failed = queue.filter((q) => q.status === "failed").length;
|
|
1290
|
+
const cancelled = queue.filter((q) => q.status === "cancelled").length;
|
|
1060
1291
|
return {
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1292
|
+
total: queue.length,
|
|
1293
|
+
active: pending + assigned,
|
|
1294
|
+
historical: completed + failed + cancelled,
|
|
1295
|
+
pending,
|
|
1296
|
+
assigned,
|
|
1297
|
+
completed,
|
|
1298
|
+
failed,
|
|
1299
|
+
cancelled,
|
|
1300
|
+
activeCounts: {
|
|
1301
|
+
pending,
|
|
1302
|
+
assigned
|
|
1303
|
+
},
|
|
1304
|
+
historicalCounts: {
|
|
1305
|
+
completed,
|
|
1306
|
+
failed,
|
|
1307
|
+
cancelled
|
|
1308
|
+
},
|
|
1309
|
+
activeAssignments: queue.filter((q) => q.status === "assigned").map((q) => ({
|
|
1310
|
+
id: q.id,
|
|
1311
|
+
nodeId: q.assignedNodeId,
|
|
1312
|
+
sessionId: q.assignedSessionId,
|
|
1313
|
+
message: q.message
|
|
1314
|
+
}))
|
|
1065
1315
|
};
|
|
1066
1316
|
}
|
|
1067
|
-
var import_fs4, import_path4, import_crypto5;
|
|
1317
|
+
var import_fs4, import_path4, import_crypto5, ACTIVE_MESH_QUEUE_STATUSES, HISTORICAL_MESH_QUEUE_STATUSES;
|
|
1068
1318
|
var init_mesh_work_queue = __esm({
|
|
1069
1319
|
"src/mesh/mesh-work-queue.ts"() {
|
|
1070
1320
|
"use strict";
|
|
@@ -1072,6 +1322,143 @@ var init_mesh_work_queue = __esm({
|
|
|
1072
1322
|
import_path4 = require("path");
|
|
1073
1323
|
import_crypto5 = require("crypto");
|
|
1074
1324
|
init_mesh_ledger();
|
|
1325
|
+
ACTIVE_MESH_QUEUE_STATUSES = ["pending", "assigned"];
|
|
1326
|
+
HISTORICAL_MESH_QUEUE_STATUSES = ["completed", "failed", "cancelled"];
|
|
1327
|
+
}
|
|
1328
|
+
});
|
|
1329
|
+
|
|
1330
|
+
// src/detection/cli-detector.ts
|
|
1331
|
+
function parseVersion(raw) {
|
|
1332
|
+
const match = raw.match(/v?(\d+\.\d+(?:\.\d+)?(?:-[a-zA-Z0-9.]+)?)/);
|
|
1333
|
+
return match ? match[1] : raw.split("\n")[0].slice(0, 100);
|
|
1334
|
+
}
|
|
1335
|
+
function shellQuote(value) {
|
|
1336
|
+
if (/^[a-zA-Z0-9_./:@%+=,-]+$/.test(value)) return value;
|
|
1337
|
+
return `"${value.replace(/(["\\$`])/g, "\\$1")}"`;
|
|
1338
|
+
}
|
|
1339
|
+
function expandHome(value) {
|
|
1340
|
+
const trimmed = value.trim();
|
|
1341
|
+
if (!trimmed.startsWith("~")) return trimmed;
|
|
1342
|
+
return path8.join(os2.homedir(), trimmed.slice(1));
|
|
1343
|
+
}
|
|
1344
|
+
function isExplicitCommandPath(command) {
|
|
1345
|
+
const trimmed = command.trim();
|
|
1346
|
+
return path8.isAbsolute(trimmed) || trimmed.includes("/") || trimmed.includes("\\") || trimmed.startsWith("~");
|
|
1347
|
+
}
|
|
1348
|
+
function resolveCommandPath(command) {
|
|
1349
|
+
const trimmed = command.trim();
|
|
1350
|
+
if (!trimmed) return null;
|
|
1351
|
+
if (isExplicitCommandPath(trimmed)) {
|
|
1352
|
+
const expanded = expandHome(trimmed);
|
|
1353
|
+
const candidate = path8.isAbsolute(expanded) ? expanded : path8.resolve(expanded);
|
|
1354
|
+
return (0, import_fs5.existsSync)(candidate) ? candidate : null;
|
|
1355
|
+
}
|
|
1356
|
+
return null;
|
|
1357
|
+
}
|
|
1358
|
+
function execAsync(cmd, timeoutMs = 5e3) {
|
|
1359
|
+
return new Promise((resolve16) => {
|
|
1360
|
+
const child = (0, import_child_process.exec)(cmd, {
|
|
1361
|
+
encoding: "utf-8",
|
|
1362
|
+
timeout: timeoutMs,
|
|
1363
|
+
...process.platform === "win32" ? { windowsHide: true } : {}
|
|
1364
|
+
}, (err, stdout) => {
|
|
1365
|
+
if (err || !stdout?.trim()) {
|
|
1366
|
+
resolve16(null);
|
|
1367
|
+
} else {
|
|
1368
|
+
resolve16(stdout.trim());
|
|
1369
|
+
}
|
|
1370
|
+
});
|
|
1371
|
+
child.on("error", () => resolve16(null));
|
|
1372
|
+
});
|
|
1373
|
+
}
|
|
1374
|
+
async function detectCLIs(providerLoader, options) {
|
|
1375
|
+
const platform10 = os2.platform();
|
|
1376
|
+
const whichCmd = platform10 === "win32" ? "where" : "which";
|
|
1377
|
+
const includeVersion = options?.includeVersion !== false;
|
|
1378
|
+
const cliList = providerLoader ? providerLoader.getCliDetectionList() : [];
|
|
1379
|
+
const results = await Promise.all(
|
|
1380
|
+
cliList.map(async (cli) => {
|
|
1381
|
+
try {
|
|
1382
|
+
const explicitPath = resolveCommandPath(cli.command);
|
|
1383
|
+
const pathResult = explicitPath || await execAsync(`${whichCmd} ${shellQuote(cli.command)}`);
|
|
1384
|
+
if (!pathResult) return { ...cli, installed: false };
|
|
1385
|
+
const firstPath = explicitPath || pathResult.split("\n")[0];
|
|
1386
|
+
let version;
|
|
1387
|
+
if (includeVersion) {
|
|
1388
|
+
const versionCommands = [
|
|
1389
|
+
`"${firstPath}" --version`,
|
|
1390
|
+
`"${firstPath}" -V`,
|
|
1391
|
+
`"${firstPath}" -v`,
|
|
1392
|
+
cli.versionCommand
|
|
1393
|
+
].filter((v) => !!v);
|
|
1394
|
+
try {
|
|
1395
|
+
for (const versionCommand of versionCommands) {
|
|
1396
|
+
const versionResult = await execAsync(versionCommand, 3e3);
|
|
1397
|
+
if (versionResult) {
|
|
1398
|
+
version = parseVersion(versionResult);
|
|
1399
|
+
break;
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
} catch {
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
return { ...cli, installed: true, version, path: firstPath };
|
|
1406
|
+
} catch {
|
|
1407
|
+
return { ...cli, installed: false };
|
|
1408
|
+
}
|
|
1409
|
+
})
|
|
1410
|
+
);
|
|
1411
|
+
return results;
|
|
1412
|
+
}
|
|
1413
|
+
async function detectCLI(cliId, providerLoader, options) {
|
|
1414
|
+
const resolvedId = providerLoader ? providerLoader.resolveAlias(cliId) : cliId;
|
|
1415
|
+
if (providerLoader) {
|
|
1416
|
+
const cliList = providerLoader.getCliDetectionList();
|
|
1417
|
+
const target = cliList.find((c) => c.id === resolvedId);
|
|
1418
|
+
if (target) {
|
|
1419
|
+
const platform10 = os2.platform();
|
|
1420
|
+
const whichCmd = platform10 === "win32" ? "where" : "which";
|
|
1421
|
+
try {
|
|
1422
|
+
const explicitPath = resolveCommandPath(target.command);
|
|
1423
|
+
const pathResult = explicitPath || await execAsync(`${whichCmd} ${shellQuote(target.command)}`);
|
|
1424
|
+
if (!pathResult) return null;
|
|
1425
|
+
const firstPath = explicitPath || pathResult.split("\n")[0];
|
|
1426
|
+
let version;
|
|
1427
|
+
if (options?.includeVersion !== false) {
|
|
1428
|
+
const versionCommands = [
|
|
1429
|
+
`"${firstPath}" --version`,
|
|
1430
|
+
`"${firstPath}" -V`,
|
|
1431
|
+
`"${firstPath}" -v`,
|
|
1432
|
+
target.versionCommand
|
|
1433
|
+
].filter((v) => !!v);
|
|
1434
|
+
try {
|
|
1435
|
+
for (const versionCommand of versionCommands) {
|
|
1436
|
+
const versionResult = await execAsync(versionCommand, 3e3);
|
|
1437
|
+
if (versionResult) {
|
|
1438
|
+
version = parseVersion(versionResult);
|
|
1439
|
+
break;
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
} catch {
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
return { ...target, installed: true, version, path: firstPath };
|
|
1446
|
+
} catch {
|
|
1447
|
+
return null;
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
const all = await detectCLIs(providerLoader, options);
|
|
1452
|
+
return all.find((c) => c.id === resolvedId && c.installed) || null;
|
|
1453
|
+
}
|
|
1454
|
+
var import_child_process, os2, path8, import_fs5;
|
|
1455
|
+
var init_cli_detector = __esm({
|
|
1456
|
+
"src/detection/cli-detector.ts"() {
|
|
1457
|
+
"use strict";
|
|
1458
|
+
import_child_process = require("child_process");
|
|
1459
|
+
os2 = __toESM(require("os"));
|
|
1460
|
+
path8 = __toESM(require("path"));
|
|
1461
|
+
import_fs5 = require("fs");
|
|
1075
1462
|
}
|
|
1076
1463
|
});
|
|
1077
1464
|
|
|
@@ -1090,13 +1477,13 @@ function getDaemonLogDir() {
|
|
|
1090
1477
|
return LOG_DIR;
|
|
1091
1478
|
}
|
|
1092
1479
|
function getCurrentDaemonLogPath(date = /* @__PURE__ */ new Date()) {
|
|
1093
|
-
return
|
|
1480
|
+
return path9.join(LOG_DIR, `daemon-${date.toISOString().slice(0, 10)}.log`);
|
|
1094
1481
|
}
|
|
1095
1482
|
function checkDateRotation() {
|
|
1096
1483
|
const today = getDateStr();
|
|
1097
1484
|
if (today !== currentDate) {
|
|
1098
1485
|
currentDate = today;
|
|
1099
|
-
currentLogFile =
|
|
1486
|
+
currentLogFile = path9.join(LOG_DIR, `daemon-${currentDate}.log`);
|
|
1100
1487
|
cleanOldLogs();
|
|
1101
1488
|
}
|
|
1102
1489
|
}
|
|
@@ -1110,7 +1497,7 @@ function cleanOldLogs() {
|
|
|
1110
1497
|
const dateMatch = file.match(/daemon-(\d{4}-\d{2}-\d{2})/);
|
|
1111
1498
|
if (dateMatch && dateMatch[1] < cutoffStr) {
|
|
1112
1499
|
try {
|
|
1113
|
-
fs2.unlinkSync(
|
|
1500
|
+
fs2.unlinkSync(path9.join(LOG_DIR, file));
|
|
1114
1501
|
} catch {
|
|
1115
1502
|
}
|
|
1116
1503
|
}
|
|
@@ -1226,17 +1613,17 @@ function installGlobalInterceptor() {
|
|
|
1226
1613
|
writeToFile(`Log file: ${currentLogFile}`);
|
|
1227
1614
|
writeToFile(`Log level: ${currentLevel}`);
|
|
1228
1615
|
}
|
|
1229
|
-
var fs2,
|
|
1616
|
+
var fs2, path9, os3, LEVEL_NUM, LEVEL_LABEL, currentLevel, LOG_DIR, MAX_LOG_SIZE, MAX_LOG_DAYS, currentDate, currentLogFile, writeCount, RING_BUFFER_SIZE, ringBuffer, origConsoleLog, origConsoleError, origConsoleWarn, LOG, interceptorInstalled, LOG_PATH;
|
|
1230
1617
|
var init_logger = __esm({
|
|
1231
1618
|
"src/logging/logger.ts"() {
|
|
1232
1619
|
"use strict";
|
|
1233
1620
|
fs2 = __toESM(require("fs"));
|
|
1234
|
-
|
|
1235
|
-
|
|
1621
|
+
path9 = __toESM(require("path"));
|
|
1622
|
+
os3 = __toESM(require("os"));
|
|
1236
1623
|
LEVEL_NUM = { debug: 0, info: 1, warn: 2, error: 3 };
|
|
1237
1624
|
LEVEL_LABEL = { debug: "DBG", info: "INF", warn: "WRN", error: "ERR" };
|
|
1238
1625
|
currentLevel = "info";
|
|
1239
|
-
LOG_DIR = process.platform === "win32" ?
|
|
1626
|
+
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");
|
|
1240
1627
|
MAX_LOG_SIZE = 5 * 1024 * 1024;
|
|
1241
1628
|
MAX_LOG_DAYS = 7;
|
|
1242
1629
|
try {
|
|
@@ -1244,16 +1631,16 @@ var init_logger = __esm({
|
|
|
1244
1631
|
} catch {
|
|
1245
1632
|
}
|
|
1246
1633
|
currentDate = getDateStr();
|
|
1247
|
-
currentLogFile =
|
|
1634
|
+
currentLogFile = path9.join(LOG_DIR, `daemon-${currentDate}.log`);
|
|
1248
1635
|
cleanOldLogs();
|
|
1249
1636
|
try {
|
|
1250
|
-
const oldLog =
|
|
1637
|
+
const oldLog = path9.join(LOG_DIR, "daemon.log");
|
|
1251
1638
|
if (fs2.existsSync(oldLog)) {
|
|
1252
1639
|
const stat2 = fs2.statSync(oldLog);
|
|
1253
1640
|
const oldDate = stat2.mtime.toISOString().slice(0, 10);
|
|
1254
|
-
fs2.renameSync(oldLog,
|
|
1641
|
+
fs2.renameSync(oldLog, path9.join(LOG_DIR, `daemon-${oldDate}.log`));
|
|
1255
1642
|
}
|
|
1256
|
-
const oldLogBackup =
|
|
1643
|
+
const oldLogBackup = path9.join(LOG_DIR, "daemon.log.old");
|
|
1257
1644
|
if (fs2.existsSync(oldLogBackup)) {
|
|
1258
1645
|
fs2.unlinkSync(oldLogBackup);
|
|
1259
1646
|
}
|
|
@@ -1285,7 +1672,7 @@ var init_logger = __esm({
|
|
|
1285
1672
|
}
|
|
1286
1673
|
};
|
|
1287
1674
|
interceptorInstalled = false;
|
|
1288
|
-
LOG_PATH =
|
|
1675
|
+
LOG_PATH = path9.join(LOG_DIR, `daemon-${getDateStr()}.log`);
|
|
1289
1676
|
}
|
|
1290
1677
|
});
|
|
1291
1678
|
|
|
@@ -1304,6 +1691,9 @@ function drainPendingMeshCoordinatorEvents() {
|
|
|
1304
1691
|
function readNonEmptyString(value) {
|
|
1305
1692
|
return typeof value === "string" && value.trim() ? value.trim() : "";
|
|
1306
1693
|
}
|
|
1694
|
+
function resolveEventSessionId(event, fallback) {
|
|
1695
|
+
return readNonEmptyString(event.targetSessionId) || readNonEmptyString(event.sessionId) || readNonEmptyString(event.instanceId) || readNonEmptyString(fallback);
|
|
1696
|
+
}
|
|
1307
1697
|
function isMeshCoordinatorEvent(eventName) {
|
|
1308
1698
|
return typeof eventName === "string" && MESH_COORDINATOR_EVENTS.has(eventName);
|
|
1309
1699
|
}
|
|
@@ -1315,38 +1705,323 @@ function formatCompletionMetadata(event) {
|
|
|
1315
1705
|
].filter(Boolean);
|
|
1316
1706
|
return parts.length > 0 ? ` (${parts.join("; ")})` : "";
|
|
1317
1707
|
}
|
|
1708
|
+
function getMeshWithCache(components, meshId) {
|
|
1709
|
+
const localMesh = getMesh(meshId);
|
|
1710
|
+
if (localMesh) return localMesh;
|
|
1711
|
+
return components.router?.getCachedInlineMesh(meshId);
|
|
1712
|
+
}
|
|
1713
|
+
function isIntentionalCleanupStopMetadata(event) {
|
|
1714
|
+
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";
|
|
1715
|
+
}
|
|
1716
|
+
function hasRecentIntentionalCleanupStop(meshId, sessionId, nodeId) {
|
|
1717
|
+
if (!sessionId && !nodeId) return false;
|
|
1718
|
+
const cutoff = Date.now() - INTENTIONAL_CLEANUP_STOP_SUPPRESSION_MS;
|
|
1719
|
+
const entries = readLedgerEntries(meshId);
|
|
1720
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
1721
|
+
const entry = entries[i];
|
|
1722
|
+
const timestamp = new Date(entry.timestamp).getTime();
|
|
1723
|
+
if (!Number.isNaN(timestamp) && timestamp < cutoff) break;
|
|
1724
|
+
if (!isIntentionalCleanupStopEntry(entry)) continue;
|
|
1725
|
+
if (sessionId && entry.sessionId === sessionId) return true;
|
|
1726
|
+
if (!sessionId && nodeId && entry.nodeId === nodeId) return true;
|
|
1727
|
+
}
|
|
1728
|
+
return false;
|
|
1729
|
+
}
|
|
1730
|
+
function shouldSuppressIntentionalCleanupStop(args) {
|
|
1731
|
+
if (args.event !== "agent:stopped" && args.event !== "monitor:long_generating") return false;
|
|
1732
|
+
if (isIntentionalCleanupStopMetadata(args.metadataEvent)) return true;
|
|
1733
|
+
return hasRecentIntentionalCleanupStop(args.meshId, args.sessionId, args.nodeId);
|
|
1734
|
+
}
|
|
1318
1735
|
function tryAssignQueueTask(components, meshId, nodeId, sessionId, providerType) {
|
|
1319
1736
|
const task = claimNextTask(meshId, nodeId, sessionId);
|
|
1320
|
-
if (!task)
|
|
1737
|
+
if (!task) {
|
|
1738
|
+
return false;
|
|
1739
|
+
}
|
|
1321
1740
|
LOG.info("MeshQueue", `Node ${nodeId} (${sessionId}) pulled task ${task.id}`);
|
|
1741
|
+
const mesh = getMeshWithCache(components, meshId);
|
|
1742
|
+
const node = mesh?.nodes.find((n) => n.id === nodeId);
|
|
1743
|
+
if (node?.daemonId && components.dispatchMeshCommand) {
|
|
1744
|
+
const isLocalNode = components.cliManager.adapters.has(sessionId);
|
|
1745
|
+
if (!isLocalNode) {
|
|
1746
|
+
components.dispatchMeshCommand(node.daemonId, "agent_command", {
|
|
1747
|
+
targetSessionId: sessionId,
|
|
1748
|
+
cliType: providerType,
|
|
1749
|
+
action: "send_chat",
|
|
1750
|
+
message: task.message
|
|
1751
|
+
}).catch((e) => {
|
|
1752
|
+
LOG.error("MeshQueue", `Failed to dispatch task via P2P to remote node ${nodeId}: ${e?.message}`);
|
|
1753
|
+
updateTaskStatus(meshId, task.id, "failed");
|
|
1754
|
+
});
|
|
1755
|
+
return true;
|
|
1756
|
+
}
|
|
1757
|
+
}
|
|
1322
1758
|
components.cliManager.handleCliCommand("agent_command", {
|
|
1323
1759
|
targetSessionId: sessionId,
|
|
1324
1760
|
cliType: providerType,
|
|
1325
1761
|
action: "send_chat",
|
|
1326
|
-
|
|
1762
|
+
message: task.message
|
|
1327
1763
|
}).catch((e) => {
|
|
1328
|
-
LOG.error("MeshQueue", `Failed to dispatch task to node ${nodeId}: ${e?.message}`);
|
|
1764
|
+
LOG.error("MeshQueue", `Failed to dispatch task locally to node ${nodeId}: ${e?.message}`);
|
|
1765
|
+
updateTaskStatus(meshId, task.id, "failed");
|
|
1329
1766
|
});
|
|
1330
1767
|
return true;
|
|
1331
1768
|
}
|
|
1332
|
-
function
|
|
1333
|
-
const
|
|
1769
|
+
function normalizeProviderPriority(policy) {
|
|
1770
|
+
const raw = policy && typeof policy === "object" && !Array.isArray(policy) ? policy.providerPriority : void 0;
|
|
1771
|
+
if (!Array.isArray(raw)) return [];
|
|
1772
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1773
|
+
return raw.map((type) => typeof type === "string" ? type.trim() : "").filter(Boolean).filter((type) => {
|
|
1774
|
+
if (seen.has(type)) return false;
|
|
1775
|
+
seen.add(type);
|
|
1776
|
+
return true;
|
|
1777
|
+
});
|
|
1778
|
+
}
|
|
1779
|
+
function isTerminalSessionStatus(status) {
|
|
1780
|
+
return ["stopped", "failed", "terminated", "exited", "closed"].includes(status);
|
|
1781
|
+
}
|
|
1782
|
+
function isIdleSessionState(state) {
|
|
1783
|
+
const status = readNonEmptyString(state?.status).toLowerCase();
|
|
1784
|
+
if (isTerminalSessionStatus(status)) return false;
|
|
1785
|
+
return status === "idle" || state?.activeChat?.status === "waiting_input";
|
|
1786
|
+
}
|
|
1787
|
+
function isDirtyNode(node) {
|
|
1788
|
+
return node?.health === "dirty" || node?.git?.dirty === true;
|
|
1789
|
+
}
|
|
1790
|
+
function isLaunchableNode(node) {
|
|
1791
|
+
if (!node || node.status === "disabled" || node.status === "removed") return false;
|
|
1792
|
+
const health = readNonEmptyString(node.health).toLowerCase();
|
|
1793
|
+
if (!health) return true;
|
|
1794
|
+
return health === "online" || health === "unknown";
|
|
1795
|
+
}
|
|
1796
|
+
function localAutoLaunchSkipReason(node) {
|
|
1797
|
+
const daemonId = readNonEmptyString(node?.daemonId);
|
|
1798
|
+
const machineId = readNonEmptyString(node?.machineId);
|
|
1799
|
+
const appConfig = loadConfig();
|
|
1800
|
+
const localMachineId = readNonEmptyString(appConfig.machineId) || readNonEmptyString(appConfig.registeredMachineId);
|
|
1801
|
+
const cloudDaemonId = localMachineId ? `daemon_${localMachineId}` : "";
|
|
1802
|
+
const standaloneDaemonId = localMachineId ? `standalone_${localMachineId}` : "";
|
|
1803
|
+
const daemonMatchesLocal = !daemonId || daemonId === cloudDaemonId || daemonId === standaloneDaemonId;
|
|
1804
|
+
const machineMatchesLocal = !machineId || localMachineId && machineId === localMachineId;
|
|
1805
|
+
if (node?.isLocalWorktree === true) {
|
|
1806
|
+
return daemonMatchesLocal && machineMatchesLocal ? null : "remote_auto_launch_unsupported";
|
|
1807
|
+
}
|
|
1808
|
+
if (daemonId || machineId) {
|
|
1809
|
+
return daemonMatchesLocal && machineMatchesLocal ? null : "remote_auto_launch_unsupported";
|
|
1810
|
+
}
|
|
1811
|
+
return null;
|
|
1812
|
+
}
|
|
1813
|
+
function activeAssignedCount(meshId) {
|
|
1814
|
+
return getQueue(meshId, { status: ["assigned"] }).length;
|
|
1815
|
+
}
|
|
1816
|
+
function nodeHasActiveAssignment(meshId, nodeId) {
|
|
1817
|
+
return getQueue(meshId, { status: ["assigned"] }).some((task) => task.assignedNodeId === nodeId);
|
|
1818
|
+
}
|
|
1819
|
+
function liveSessionCountForNode(components, meshId, nodeId) {
|
|
1820
|
+
return components.instanceManager.getByCategory("cli").filter((inst) => {
|
|
1821
|
+
const state = inst.getState();
|
|
1822
|
+
const settings = state.settings || {};
|
|
1823
|
+
if (readNonEmptyString(settings.meshNodeFor) !== meshId) return false;
|
|
1824
|
+
const instNodeId = readNonEmptyString(settings.meshNodeId) || readNonEmptyString(settings.nodeId);
|
|
1825
|
+
if (instNodeId !== nodeId) return false;
|
|
1826
|
+
const status = readNonEmptyString(state.status).toLowerCase();
|
|
1827
|
+
return !isTerminalSessionStatus(status);
|
|
1828
|
+
}).length;
|
|
1829
|
+
}
|
|
1830
|
+
function recordAutoLaunchEvent(meshId, args) {
|
|
1831
|
+
try {
|
|
1832
|
+
appendLedgerEntry(meshId, {
|
|
1833
|
+
kind: "session_auto_launch",
|
|
1834
|
+
nodeId: args.nodeId,
|
|
1835
|
+
sessionId: args.sessionId,
|
|
1836
|
+
providerType: args.providerType,
|
|
1837
|
+
payload: {
|
|
1838
|
+
phase: args.phase,
|
|
1839
|
+
taskId: args.taskId,
|
|
1840
|
+
reason: args.reason,
|
|
1841
|
+
error: args.error
|
|
1842
|
+
}
|
|
1843
|
+
});
|
|
1844
|
+
} catch (e) {
|
|
1845
|
+
LOG.warn("MeshQueue", `Failed to record auto-launch ledger event: ${e?.message || e}`);
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
function markAutoLaunch(meshId, taskId, args) {
|
|
1849
|
+
recordTaskAutoLaunch(meshId, taskId, {
|
|
1850
|
+
status: args.status,
|
|
1851
|
+
reason: args.reason || args.error,
|
|
1852
|
+
nodeId: args.nodeId,
|
|
1853
|
+
providerType: args.providerType,
|
|
1854
|
+
sessionId: args.sessionId
|
|
1855
|
+
});
|
|
1856
|
+
recordAutoLaunchEvent(meshId, {
|
|
1857
|
+
phase: args.status,
|
|
1858
|
+
taskId,
|
|
1859
|
+
nodeId: args.nodeId,
|
|
1860
|
+
providerType: args.providerType,
|
|
1861
|
+
sessionId: args.sessionId,
|
|
1862
|
+
reason: args.reason,
|
|
1863
|
+
error: args.error
|
|
1864
|
+
});
|
|
1865
|
+
}
|
|
1866
|
+
async function resolveUsableProvider(components, nodeId, node) {
|
|
1867
|
+
const providerPriority = normalizeProviderPriority(node?.policy);
|
|
1868
|
+
if (!providerPriority.length) return { reason: "missing_provider_priority" };
|
|
1869
|
+
const providerLoader = components.providerLoader;
|
|
1870
|
+
if (!providerLoader) return { reason: "provider_loader_unavailable" };
|
|
1871
|
+
const failed = [];
|
|
1872
|
+
for (const requestedType of providerPriority) {
|
|
1873
|
+
const normalizedType = typeof providerLoader.resolveAlias === "function" ? providerLoader.resolveAlias(requestedType) : requestedType;
|
|
1874
|
+
if (typeof providerLoader.isMachineProviderEnabled === "function" && !providerLoader.isMachineProviderEnabled(normalizedType)) {
|
|
1875
|
+
failed.push(`${requestedType}: disabled`);
|
|
1876
|
+
continue;
|
|
1877
|
+
}
|
|
1878
|
+
let detected;
|
|
1879
|
+
try {
|
|
1880
|
+
detected = await detectCLI(normalizedType, providerLoader, { includeVersion: false });
|
|
1881
|
+
} catch (e) {
|
|
1882
|
+
failed.push(`${requestedType}: detect failed: ${e?.message || e}`);
|
|
1883
|
+
continue;
|
|
1884
|
+
}
|
|
1885
|
+
if (typeof providerLoader.setCliDetectionResults === "function") {
|
|
1886
|
+
providerLoader.setCliDetectionResults([{
|
|
1887
|
+
id: normalizedType,
|
|
1888
|
+
installed: !!detected,
|
|
1889
|
+
path: detected?.path
|
|
1890
|
+
}], false);
|
|
1891
|
+
}
|
|
1892
|
+
components.onStatusChange?.();
|
|
1893
|
+
if (detected) return { providerType: normalizedType };
|
|
1894
|
+
failed.push(`${requestedType}: not detected`);
|
|
1895
|
+
}
|
|
1896
|
+
return { reason: `provider_priority_unusable: ${failed.join("; ") || nodeId}` };
|
|
1897
|
+
}
|
|
1898
|
+
async function maybeAutoLaunchOneQueueSession(components, meshId, mesh) {
|
|
1899
|
+
const queue = getQueue(meshId);
|
|
1900
|
+
const pending = queue.filter((task) => task.status === "pending");
|
|
1901
|
+
if (!pending.length) return false;
|
|
1902
|
+
const maxParallelTasks = Math.max(1, Math.floor(Number(mesh?.policy?.maxParallelTasks) || 2));
|
|
1903
|
+
for (const task of pending) {
|
|
1904
|
+
if (activeAssignedCount(meshId) >= maxParallelTasks) {
|
|
1905
|
+
markAutoLaunch(meshId, task.id, { status: "skipped", reason: "max_parallel_tasks_reached" });
|
|
1906
|
+
return false;
|
|
1907
|
+
}
|
|
1908
|
+
if (task.targetSessionId) {
|
|
1909
|
+
markAutoLaunch(meshId, task.id, { status: "skipped", reason: "target_session_constraint" });
|
|
1910
|
+
continue;
|
|
1911
|
+
}
|
|
1912
|
+
const candidateNodes = Array.isArray(mesh?.nodes) ? mesh.nodes.filter((node) => task.targetNodeId ? node?.id === task.targetNodeId : true) : [];
|
|
1913
|
+
if (!candidateNodes.length) {
|
|
1914
|
+
markAutoLaunch(meshId, task.id, { status: "skipped", reason: "no_matching_node", nodeId: task.targetNodeId });
|
|
1915
|
+
continue;
|
|
1916
|
+
}
|
|
1917
|
+
for (const node of candidateNodes) {
|
|
1918
|
+
const nodeId = readNonEmptyString(node?.id);
|
|
1919
|
+
if (!nodeId) continue;
|
|
1920
|
+
const launchKey = `${meshId}:${nodeId}`;
|
|
1921
|
+
const cooldownUntil = autoLaunchCooldownUntil.get(launchKey) || 0;
|
|
1922
|
+
if (autoLaunchInProgress.has(launchKey)) {
|
|
1923
|
+
markAutoLaunch(meshId, task.id, { status: "skipped", reason: "auto_launch_in_progress", nodeId });
|
|
1924
|
+
continue;
|
|
1925
|
+
}
|
|
1926
|
+
if (Date.now() < cooldownUntil) {
|
|
1927
|
+
markAutoLaunch(meshId, task.id, { status: "skipped", reason: "auto_launch_cooldown", nodeId });
|
|
1928
|
+
continue;
|
|
1929
|
+
}
|
|
1930
|
+
if (isDirtyNode(node)) {
|
|
1931
|
+
markAutoLaunch(meshId, task.id, { status: "skipped", reason: "dirty_workspace", nodeId });
|
|
1932
|
+
continue;
|
|
1933
|
+
}
|
|
1934
|
+
if (!isLaunchableNode(node)) {
|
|
1935
|
+
markAutoLaunch(meshId, task.id, { status: "skipped", reason: "node_not_launch_ready", nodeId });
|
|
1936
|
+
continue;
|
|
1937
|
+
}
|
|
1938
|
+
const localSkipReason = localAutoLaunchSkipReason(node);
|
|
1939
|
+
if (localSkipReason) {
|
|
1940
|
+
markAutoLaunch(meshId, task.id, { status: "skipped", reason: localSkipReason, nodeId });
|
|
1941
|
+
continue;
|
|
1942
|
+
}
|
|
1943
|
+
if (nodeHasActiveAssignment(meshId, nodeId)) {
|
|
1944
|
+
markAutoLaunch(meshId, task.id, { status: "skipped", reason: "node_has_active_assignment", nodeId });
|
|
1945
|
+
continue;
|
|
1946
|
+
}
|
|
1947
|
+
const maxConcurrentSessions = Number(node?.policy?.maxConcurrentSessions);
|
|
1948
|
+
if (Number.isFinite(maxConcurrentSessions) && maxConcurrentSessions >= 0 && liveSessionCountForNode(components, meshId, nodeId) >= maxConcurrentSessions) {
|
|
1949
|
+
markAutoLaunch(meshId, task.id, { status: "skipped", reason: "max_concurrent_sessions_reached", nodeId });
|
|
1950
|
+
continue;
|
|
1951
|
+
}
|
|
1952
|
+
autoLaunchInProgress.add(launchKey);
|
|
1953
|
+
try {
|
|
1954
|
+
const resolved = await resolveUsableProvider(components, nodeId, node);
|
|
1955
|
+
if (!resolved.providerType) {
|
|
1956
|
+
markAutoLaunch(meshId, task.id, { status: "skipped", reason: resolved.reason || "provider_unusable", nodeId });
|
|
1957
|
+
continue;
|
|
1958
|
+
}
|
|
1959
|
+
markAutoLaunch(meshId, task.id, { status: "started", nodeId, providerType: resolved.providerType });
|
|
1960
|
+
const launchResult = await components.cliManager.handleCliCommand("launch_cli", {
|
|
1961
|
+
cliType: resolved.providerType,
|
|
1962
|
+
dir: node.workspace,
|
|
1963
|
+
settings: {
|
|
1964
|
+
meshNodeFor: meshId,
|
|
1965
|
+
meshNodeId: nodeId,
|
|
1966
|
+
spawnedSessionVisibility: mesh?.policy?.spawnedSessionVisibility || "hidden",
|
|
1967
|
+
launchedByCoordinator: true,
|
|
1968
|
+
autoLaunchedForQueueTaskId: task.id
|
|
1969
|
+
}
|
|
1970
|
+
});
|
|
1971
|
+
if (!launchResult?.success) {
|
|
1972
|
+
const reason = launchResult?.error || "launch_cli_failed";
|
|
1973
|
+
markAutoLaunch(meshId, task.id, { status: "failed", reason, nodeId, providerType: resolved.providerType });
|
|
1974
|
+
autoLaunchCooldownUntil.set(launchKey, Date.now() + AUTO_LAUNCH_COOLDOWN_MS);
|
|
1975
|
+
return false;
|
|
1976
|
+
}
|
|
1977
|
+
const sessionId = readNonEmptyString(launchResult.sessionId) || readNonEmptyString(launchResult.id) || readNonEmptyString(launchResult.runtimeSessionId);
|
|
1978
|
+
if (!sessionId) {
|
|
1979
|
+
markAutoLaunch(meshId, task.id, { status: "failed", reason: "launch_missing_session_id", nodeId, providerType: resolved.providerType });
|
|
1980
|
+
autoLaunchCooldownUntil.set(launchKey, Date.now() + AUTO_LAUNCH_COOLDOWN_MS);
|
|
1981
|
+
return false;
|
|
1982
|
+
}
|
|
1983
|
+
markAutoLaunch(meshId, task.id, { status: "completed", nodeId, providerType: resolved.providerType, sessionId });
|
|
1984
|
+
tryAssignQueueTask(components, meshId, nodeId, sessionId, resolved.providerType);
|
|
1985
|
+
return true;
|
|
1986
|
+
} catch (e) {
|
|
1987
|
+
markAutoLaunch(meshId, task.id, { status: "failed", error: e?.message || String(e), nodeId });
|
|
1988
|
+
autoLaunchCooldownUntil.set(launchKey, Date.now() + AUTO_LAUNCH_COOLDOWN_MS);
|
|
1989
|
+
return false;
|
|
1990
|
+
} finally {
|
|
1991
|
+
autoLaunchInProgress.delete(launchKey);
|
|
1992
|
+
}
|
|
1993
|
+
}
|
|
1994
|
+
}
|
|
1995
|
+
return false;
|
|
1996
|
+
}
|
|
1997
|
+
async function triggerMeshQueue(components, meshId) {
|
|
1998
|
+
const mesh = getMeshWithCache(components, meshId);
|
|
1334
1999
|
if (!mesh) return;
|
|
1335
2000
|
const cliInstances = components.instanceManager.getByCategory("cli");
|
|
1336
2001
|
for (const inst of cliInstances) {
|
|
1337
2002
|
const state = inst.getState();
|
|
1338
2003
|
const settings = state.settings || {};
|
|
1339
2004
|
const instMeshId = readNonEmptyString(settings.meshNodeFor);
|
|
1340
|
-
if (instMeshId !== meshId
|
|
2005
|
+
if (instMeshId !== meshId) continue;
|
|
1341
2006
|
const nodeId = readNonEmptyString(settings.meshNodeId) || readNonEmptyString(settings.nodeId);
|
|
1342
2007
|
if (!nodeId) continue;
|
|
1343
|
-
if (state
|
|
2008
|
+
if (!isIdleSessionState(state)) continue;
|
|
1344
2009
|
const sessionId = state.instanceId;
|
|
1345
2010
|
const providerType = state.type || readNonEmptyString(settings.providerType);
|
|
1346
2011
|
if (providerType) {
|
|
1347
2012
|
tryAssignQueueTask(components, meshId, nodeId, sessionId, providerType);
|
|
1348
2013
|
}
|
|
1349
2014
|
}
|
|
2015
|
+
for (const [key, idle] of remoteIdleSessions.entries()) {
|
|
2016
|
+
const node = mesh.nodes.find((n) => n.id === idle.nodeId);
|
|
2017
|
+
if (node) {
|
|
2018
|
+
const assigned = tryAssignQueueTask(components, meshId, idle.nodeId, idle.sessionId, idle.providerType);
|
|
2019
|
+
if (assigned) {
|
|
2020
|
+
remoteIdleSessions.delete(key);
|
|
2021
|
+
}
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
2024
|
+
await maybeAutoLaunchOneQueueSession(components, meshId, mesh);
|
|
1350
2025
|
}
|
|
1351
2026
|
function buildMeshSystemMessage(args) {
|
|
1352
2027
|
const metadata = formatCompletionMetadata(args.metadataEvent);
|
|
@@ -1393,20 +2068,91 @@ Do NOT retry on this node. Consider reassigning to a different node or asking th
|
|
|
1393
2068
|
return "";
|
|
1394
2069
|
}
|
|
1395
2070
|
function injectMeshSystemMessage(components, args) {
|
|
2071
|
+
const eventSessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId);
|
|
2072
|
+
const eventNodeId = readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId);
|
|
2073
|
+
const intentionalCleanupStop = shouldSuppressIntentionalCleanupStop({
|
|
2074
|
+
event: args.event,
|
|
2075
|
+
meshId: args.meshId,
|
|
2076
|
+
metadataEvent: args.metadataEvent,
|
|
2077
|
+
sessionId: eventSessionId || void 0,
|
|
2078
|
+
nodeId: eventNodeId || void 0
|
|
2079
|
+
});
|
|
2080
|
+
if (intentionalCleanupStop) {
|
|
2081
|
+
if (eventSessionId && eventNodeId) {
|
|
2082
|
+
remoteIdleSessions.delete(`${eventNodeId}:${eventSessionId}`);
|
|
2083
|
+
}
|
|
2084
|
+
LOG.info("MeshEvents", `Suppressed ${args.event} for intentionally cleanup-stopped session ${eventSessionId || "(unknown session)"}`);
|
|
2085
|
+
return { success: true, forwarded: 0, suppressed: true, intentionalCleanupStop: true };
|
|
2086
|
+
}
|
|
2087
|
+
let completedTaskForLedger = null;
|
|
1396
2088
|
if (args.event === "agent:generating_completed") {
|
|
1397
|
-
const sessionId =
|
|
1398
|
-
const nodeId = readNonEmptyString(args.
|
|
2089
|
+
const sessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId);
|
|
2090
|
+
const nodeId = readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId);
|
|
1399
2091
|
const providerType = readNonEmptyString(args.metadataEvent.providerType);
|
|
1400
2092
|
if (sessionId) {
|
|
1401
|
-
updateSessionTaskStatus(args.meshId, sessionId, "completed");
|
|
2093
|
+
const completedTask = updateSessionTaskStatus(args.meshId, sessionId, "completed");
|
|
2094
|
+
completedTaskForLedger = completedTask ? { id: completedTask.id } : null;
|
|
1402
2095
|
if (nodeId && providerType) {
|
|
1403
2096
|
setTimeout(() => {
|
|
1404
2097
|
tryAssignQueueTask(components, args.meshId, nodeId, sessionId, providerType);
|
|
1405
2098
|
}, 500);
|
|
1406
2099
|
}
|
|
1407
2100
|
}
|
|
2101
|
+
} else if (args.event === "agent:ready") {
|
|
2102
|
+
const sessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId);
|
|
2103
|
+
const nodeId = readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId);
|
|
2104
|
+
const providerType = readNonEmptyString(args.metadataEvent.providerType);
|
|
2105
|
+
const completedTask = sessionId ? updateSessionTaskStatus(args.meshId, sessionId, "completed") : null;
|
|
2106
|
+
if (completedTask) {
|
|
2107
|
+
completedTaskForLedger = { id: completedTask.id };
|
|
2108
|
+
try {
|
|
2109
|
+
appendLedgerEntry(args.meshId, {
|
|
2110
|
+
kind: "task_completed",
|
|
2111
|
+
nodeId: nodeId || void 0,
|
|
2112
|
+
sessionId,
|
|
2113
|
+
providerType: providerType || void 0,
|
|
2114
|
+
payload: {
|
|
2115
|
+
event: args.event,
|
|
2116
|
+
nodeLabel: args.nodeLabel,
|
|
2117
|
+
taskId: completedTask.id,
|
|
2118
|
+
completedViaReady: true,
|
|
2119
|
+
providerSessionId: readNonEmptyString(args.metadataEvent.providerSessionId) || void 0,
|
|
2120
|
+
finalSummary: readNonEmptyString(args.metadataEvent.finalSummary) || void 0,
|
|
2121
|
+
evidence: buildTaskCompletionEvidence({
|
|
2122
|
+
event: "agent:ready",
|
|
2123
|
+
nodeId,
|
|
2124
|
+
sessionId,
|
|
2125
|
+
providerType: providerType || void 0,
|
|
2126
|
+
providerSessionId: readNonEmptyString(args.metadataEvent.providerSessionId) || void 0,
|
|
2127
|
+
finalSummary: readNonEmptyString(args.metadataEvent.finalSummary) || void 0
|
|
2128
|
+
})
|
|
2129
|
+
}
|
|
2130
|
+
});
|
|
2131
|
+
} catch (e) {
|
|
2132
|
+
LOG.warn("MeshLedger", `Failed to record task_completed from ready: ${e?.message || e}`);
|
|
2133
|
+
}
|
|
2134
|
+
}
|
|
2135
|
+
if (sessionId && nodeId && providerType) {
|
|
2136
|
+
remoteIdleSessions.set(`${nodeId}:${sessionId}`, { nodeId, sessionId, providerType });
|
|
2137
|
+
setTimeout(() => {
|
|
2138
|
+
const assigned = tryAssignQueueTask(components, args.meshId, nodeId, sessionId, providerType);
|
|
2139
|
+
if (assigned) {
|
|
2140
|
+
remoteIdleSessions.delete(`${nodeId}:${sessionId}`);
|
|
2141
|
+
}
|
|
2142
|
+
}, 500);
|
|
2143
|
+
}
|
|
2144
|
+
} else if (args.event === "agent:generating_started") {
|
|
2145
|
+
const sessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId);
|
|
2146
|
+
const nodeId = readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId);
|
|
2147
|
+
if (sessionId && nodeId) {
|
|
2148
|
+
remoteIdleSessions.delete(`${nodeId}:${sessionId}`);
|
|
2149
|
+
}
|
|
1408
2150
|
} else if (args.event === "agent:stopped") {
|
|
1409
|
-
const sessionId =
|
|
2151
|
+
const sessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId);
|
|
2152
|
+
const nodeId = readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId);
|
|
2153
|
+
if (sessionId && nodeId) {
|
|
2154
|
+
remoteIdleSessions.delete(`${nodeId}:${sessionId}`);
|
|
2155
|
+
}
|
|
1410
2156
|
if (sessionId) {
|
|
1411
2157
|
updateSessionTaskStatus(args.meshId, sessionId, "failed");
|
|
1412
2158
|
}
|
|
@@ -1414,15 +2160,29 @@ function injectMeshSystemMessage(components, args) {
|
|
|
1414
2160
|
const ledgerKind = EVENT_TO_LEDGER_KIND[args.event];
|
|
1415
2161
|
if (ledgerKind) {
|
|
1416
2162
|
try {
|
|
2163
|
+
const ledgerNodeId = readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId) || void 0;
|
|
2164
|
+
const ledgerSessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId) || void 0;
|
|
2165
|
+
const ledgerProviderType = readNonEmptyString(args.metadataEvent.providerType) || void 0;
|
|
2166
|
+
const completionEvidence = ledgerKind === "task_completed" && ledgerNodeId && ledgerSessionId ? buildTaskCompletionEvidence({
|
|
2167
|
+
event: "agent:generating_completed",
|
|
2168
|
+
nodeId: ledgerNodeId,
|
|
2169
|
+
sessionId: ledgerSessionId,
|
|
2170
|
+
providerType: ledgerProviderType,
|
|
2171
|
+
providerSessionId: readNonEmptyString(args.metadataEvent.providerSessionId) || void 0,
|
|
2172
|
+
finalSummary: readNonEmptyString(args.metadataEvent.finalSummary) || void 0
|
|
2173
|
+
}) : void 0;
|
|
1417
2174
|
appendLedgerEntry(args.meshId, {
|
|
1418
2175
|
kind: ledgerKind,
|
|
1419
|
-
nodeId:
|
|
1420
|
-
sessionId:
|
|
1421
|
-
providerType:
|
|
2176
|
+
nodeId: ledgerNodeId,
|
|
2177
|
+
sessionId: ledgerSessionId,
|
|
2178
|
+
providerType: ledgerProviderType,
|
|
1422
2179
|
payload: {
|
|
1423
2180
|
event: args.event,
|
|
1424
2181
|
nodeLabel: args.nodeLabel,
|
|
1425
|
-
|
|
2182
|
+
taskId: completedTaskForLedger?.id || void 0,
|
|
2183
|
+
providerSessionId: readNonEmptyString(args.metadataEvent.providerSessionId) || void 0,
|
|
2184
|
+
finalSummary: readNonEmptyString(args.metadataEvent.finalSummary) || void 0,
|
|
2185
|
+
evidence: completionEvidence
|
|
1426
2186
|
}
|
|
1427
2187
|
});
|
|
1428
2188
|
} catch (e) {
|
|
@@ -1435,8 +2195,8 @@ function injectMeshSystemMessage(components, args) {
|
|
|
1435
2195
|
const mesh = getMesh(args.meshId);
|
|
1436
2196
|
const maxRetries = mesh?.policy?.maxTaskRetries ?? 1;
|
|
1437
2197
|
recoveryContext = getSessionRecoveryContext(args.meshId, {
|
|
1438
|
-
sessionId:
|
|
1439
|
-
nodeId: readNonEmptyString(args.
|
|
2198
|
+
sessionId: resolveEventSessionId(args.metadataEvent, args.sourceInstanceId) || void 0,
|
|
2199
|
+
nodeId: readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId) || void 0,
|
|
1440
2200
|
maxRetries
|
|
1441
2201
|
});
|
|
1442
2202
|
recoveryContext.failedProviderType = readNonEmptyString(args.metadataEvent.providerType) || null;
|
|
@@ -1531,12 +2291,21 @@ function handleMeshForwardEvent(components, payload) {
|
|
|
1531
2291
|
const nodeLabel = nodeId ? `Node '${nodeId}'` : workspace ? `Agent at ${workspace}` : "Remote agent";
|
|
1532
2292
|
return injectMeshSystemMessage(components, {
|
|
1533
2293
|
meshId,
|
|
2294
|
+
nodeId,
|
|
1534
2295
|
nodeLabel,
|
|
1535
2296
|
event: eventName,
|
|
1536
2297
|
metadataEvent: {
|
|
1537
|
-
targetSessionId: readNonEmptyString(payload.targetSessionId) || readNonEmptyString(payload.sessionId),
|
|
2298
|
+
targetSessionId: readNonEmptyString(payload.targetSessionId) || readNonEmptyString(payload.sessionId) || readNonEmptyString(payload.instanceId),
|
|
1538
2299
|
providerType: readNonEmptyString(payload.providerType),
|
|
1539
|
-
providerSessionId: readNonEmptyString(payload.providerSessionId)
|
|
2300
|
+
providerSessionId: readNonEmptyString(payload.providerSessionId),
|
|
2301
|
+
finalSummary: readNonEmptyString(payload.finalSummary) || readNonEmptyString(payload.summary),
|
|
2302
|
+
intentional: payload.intentional === true,
|
|
2303
|
+
intentionalStop: payload.intentionalStop === true,
|
|
2304
|
+
operatorCleanup: payload.operatorCleanup === true,
|
|
2305
|
+
reason: readNonEmptyString(payload.reason),
|
|
2306
|
+
stopReason: readNonEmptyString(payload.stopReason),
|
|
2307
|
+
cleanupReason: readNonEmptyString(payload.cleanupReason),
|
|
2308
|
+
source: readNonEmptyString(payload.source)
|
|
1540
2309
|
}
|
|
1541
2310
|
});
|
|
1542
2311
|
}
|
|
@@ -1555,35 +2324,42 @@ function setupMeshEventForwarding(components) {
|
|
|
1555
2324
|
const meshIdFromRuntime = readNonEmptyString(settings.meshNodeFor);
|
|
1556
2325
|
const isMeshDelegate = Boolean(meshIdFromRuntime || settings.launchedByCoordinator);
|
|
1557
2326
|
if (!isMeshDelegate) return;
|
|
1558
|
-
const mesh = meshIdFromRuntime ?
|
|
2327
|
+
const mesh = meshIdFromRuntime ? getMeshWithCache(components, meshIdFromRuntime) : getMeshByRepo(workspace);
|
|
1559
2328
|
const meshId = meshIdFromRuntime || readNonEmptyString(mesh?.id);
|
|
1560
2329
|
if (!meshId) return;
|
|
1561
2330
|
const targetNode = mesh?.nodes?.find((n) => n.workspace === workspace);
|
|
1562
2331
|
const runtimeNodeId = readNonEmptyString(settings.meshNodeId);
|
|
2332
|
+
const resolvedNodeId = targetNode?.id || runtimeNodeId;
|
|
1563
2333
|
const nodeLabel = targetNode ? `Node '${targetNode.id}'` : runtimeNodeId ? `Node '${runtimeNodeId}'` : `Agent at ${workspace}`;
|
|
1564
2334
|
injectMeshSystemMessage(components, {
|
|
1565
2335
|
meshId,
|
|
1566
2336
|
sourceInstanceId: instanceId,
|
|
2337
|
+
nodeId: resolvedNodeId,
|
|
1567
2338
|
nodeLabel,
|
|
1568
2339
|
event: event.event,
|
|
1569
2340
|
metadataEvent: event
|
|
1570
2341
|
});
|
|
1571
2342
|
});
|
|
1572
2343
|
}
|
|
1573
|
-
var MAX_PENDING_EVENTS, pendingMeshCoordinatorEvents, MESH_COORDINATOR_EVENTS, EVENT_TO_LEDGER_KIND;
|
|
2344
|
+
var remoteIdleSessions, MAX_PENDING_EVENTS, pendingMeshCoordinatorEvents, MESH_COORDINATOR_EVENTS, EVENT_TO_LEDGER_KIND, INTENTIONAL_CLEANUP_STOP_SUPPRESSION_MS, autoLaunchInProgress, autoLaunchCooldownUntil, AUTO_LAUNCH_COOLDOWN_MS;
|
|
1574
2345
|
var init_mesh_events = __esm({
|
|
1575
2346
|
"src/mesh/mesh-events.ts"() {
|
|
1576
2347
|
"use strict";
|
|
2348
|
+
init_config();
|
|
1577
2349
|
init_mesh_config();
|
|
2350
|
+
init_cli_detector();
|
|
1578
2351
|
init_logger();
|
|
1579
2352
|
init_mesh_ledger();
|
|
1580
2353
|
init_mesh_work_queue();
|
|
2354
|
+
remoteIdleSessions = /* @__PURE__ */ new Map();
|
|
1581
2355
|
MAX_PENDING_EVENTS = 50;
|
|
1582
2356
|
pendingMeshCoordinatorEvents = [];
|
|
1583
2357
|
MESH_COORDINATOR_EVENTS = /* @__PURE__ */ new Set([
|
|
2358
|
+
"agent:generating_started",
|
|
1584
2359
|
"agent:generating_completed",
|
|
1585
2360
|
"agent:waiting_approval",
|
|
1586
2361
|
"agent:stopped",
|
|
2362
|
+
"agent:ready",
|
|
1587
2363
|
"monitor:long_generating"
|
|
1588
2364
|
]);
|
|
1589
2365
|
EVENT_TO_LEDGER_KIND = {
|
|
@@ -1592,6 +2368,10 @@ var init_mesh_events = __esm({
|
|
|
1592
2368
|
"agent:stopped": "task_failed",
|
|
1593
2369
|
"monitor:long_generating": "task_stalled"
|
|
1594
2370
|
};
|
|
2371
|
+
INTENTIONAL_CLEANUP_STOP_SUPPRESSION_MS = 30 * 60 * 1e3;
|
|
2372
|
+
autoLaunchInProgress = /* @__PURE__ */ new Set();
|
|
2373
|
+
autoLaunchCooldownUntil = /* @__PURE__ */ new Map();
|
|
2374
|
+
AUTO_LAUNCH_COOLDOWN_MS = 5e3;
|
|
1595
2375
|
}
|
|
1596
2376
|
});
|
|
1597
2377
|
|
|
@@ -2617,6 +3397,7 @@ var init_provider_cli_adapter = __esm({
|
|
|
2617
3397
|
this.requirePromptEchoBeforeSubmit = resolvedConfig.requirePromptEchoBeforeSubmit;
|
|
2618
3398
|
this.providerResolutionMeta = resolvedConfig.providerResolutionMeta;
|
|
2619
3399
|
this.cliScripts = provider.scripts || {};
|
|
3400
|
+
this.scriptState = typeof this.cliScripts.createState === "function" ? this.cliScripts.createState() ?? null : null;
|
|
2620
3401
|
const scriptNames = listCliScriptNames(this.cliScripts);
|
|
2621
3402
|
if (scriptNames.length > 0) {
|
|
2622
3403
|
LOG.info("CLI", `[${this.cliType}] CLI scripts: [${scriptNames.join(", ")}]`);
|
|
@@ -2699,6 +3480,8 @@ var init_provider_cli_adapter = __esm({
|
|
|
2699
3480
|
statusHistory = [];
|
|
2700
3481
|
// ─── CLI Scripts (script-based parsing) ───
|
|
2701
3482
|
cliScripts;
|
|
3483
|
+
/** Per-session opaque state object created by cliScripts.createState(), reset on stop. */
|
|
3484
|
+
scriptState = null;
|
|
2702
3485
|
runtimeSettings = {};
|
|
2703
3486
|
/** Full accumulated rendered PTY transcript for parser/readback use */
|
|
2704
3487
|
accumulatedBuffer = "";
|
|
@@ -2775,9 +3558,13 @@ ${lastSnapshot}`;
|
|
|
2775
3558
|
this.lastScreenChangeAt = 0;
|
|
2776
3559
|
this.lastScreenSnapshotReadAt = Number.NEGATIVE_INFINITY;
|
|
2777
3560
|
}
|
|
3561
|
+
getAccumulatedRawBufferCacheKey() {
|
|
3562
|
+
return this.accumulatedRawBuffer.replace(/\x1B\][^\x07]*(?:\x07|\x1B\\)/g, "").replace(/\x1B[P^_X][\s\S]*?(?:\x07|\x1B\\)/g, "").replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, "");
|
|
3563
|
+
}
|
|
2778
3564
|
getFreshParsedStatusCache() {
|
|
2779
3565
|
const cached = this.parsedStatusCache;
|
|
2780
|
-
|
|
3566
|
+
const accumulatedRawBufferKey = this.getAccumulatedRawBufferCacheKey();
|
|
3567
|
+
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) {
|
|
2781
3568
|
return cached.result;
|
|
2782
3569
|
}
|
|
2783
3570
|
return null;
|
|
@@ -2880,6 +3667,7 @@ ${lastSnapshot}`;
|
|
|
2880
3667
|
this.cliScripts = scripts;
|
|
2881
3668
|
this.parsedStatusCache = null;
|
|
2882
3669
|
this.parseErrorMessage = null;
|
|
3670
|
+
this.scriptState = typeof scripts.createState === "function" ? scripts.createState() ?? null : null;
|
|
2883
3671
|
const scriptNames = listCliScriptNames(scripts);
|
|
2884
3672
|
LOG.info("CLI", `[${this.cliType}] CLI scripts injected: [${scriptNames.join(", ")}]`);
|
|
2885
3673
|
}
|
|
@@ -2997,6 +3785,7 @@ ${lastSnapshot}`;
|
|
|
2997
3785
|
this.ready = false;
|
|
2998
3786
|
this.startupParseGate = false;
|
|
2999
3787
|
this.spawnAt = 0;
|
|
3788
|
+
this.scriptState = null;
|
|
3000
3789
|
this.onStatusChange?.();
|
|
3001
3790
|
});
|
|
3002
3791
|
this.spawnAt = Date.now();
|
|
@@ -3750,6 +4539,11 @@ ${lastSnapshot}`;
|
|
|
3750
4539
|
};
|
|
3751
4540
|
}
|
|
3752
4541
|
// ─── Script Execution ──────────────────────────
|
|
4542
|
+
invokeCliScript(script, input) {
|
|
4543
|
+
const hasStateFactory = typeof this.cliScripts?.createState === "function";
|
|
4544
|
+
const expectsStateArgument = hasStateFactory || this.scriptState !== null || script.length >= 2;
|
|
4545
|
+
return expectsStateArgument ? script(this.scriptState, input) : script(input);
|
|
4546
|
+
}
|
|
3753
4547
|
runParseSession() {
|
|
3754
4548
|
if (typeof this.cliScripts?.parseSession !== "function") {
|
|
3755
4549
|
this.parseErrorMessage = `${this.cliType} parseSession unavailable`;
|
|
@@ -3770,7 +4564,10 @@ ${lastSnapshot}`;
|
|
|
3770
4564
|
scope: this.currentTurnScope,
|
|
3771
4565
|
runtimeSettings: this.runtimeSettings
|
|
3772
4566
|
});
|
|
3773
|
-
const session = this.
|
|
4567
|
+
const session = this.invokeCliScript(
|
|
4568
|
+
this.cliScripts.parseSession,
|
|
4569
|
+
{ ...input, tail, tailScreen: buildCliScreenSnapshot(tail) }
|
|
4570
|
+
);
|
|
3774
4571
|
this.parseErrorMessage = null;
|
|
3775
4572
|
return session && typeof session === "object" ? session : null;
|
|
3776
4573
|
} catch (e) {
|
|
@@ -3784,7 +4581,7 @@ ${lastSnapshot}`;
|
|
|
3784
4581
|
if (!this.cliScripts?.detectStatus) return null;
|
|
3785
4582
|
try {
|
|
3786
4583
|
const screenText = this.terminalScreen.getText();
|
|
3787
|
-
const status = this.cliScripts.detectStatus
|
|
4584
|
+
const status = this.invokeCliScript(this.cliScripts.detectStatus, {
|
|
3788
4585
|
tail: text.slice(-500),
|
|
3789
4586
|
screenText,
|
|
3790
4587
|
rawBuffer: this.accumulatedRawBuffer,
|
|
@@ -3803,7 +4600,7 @@ ${lastSnapshot}`;
|
|
|
3803
4600
|
try {
|
|
3804
4601
|
const screenText = this.terminalScreen.getText();
|
|
3805
4602
|
const buffer = screenText || this.accumulatedBuffer;
|
|
3806
|
-
return this.cliScripts.parseApproval
|
|
4603
|
+
return this.invokeCliScript(this.cliScripts.parseApproval, {
|
|
3807
4604
|
buffer,
|
|
3808
4605
|
screenText,
|
|
3809
4606
|
rawBuffer: this.accumulatedRawBuffer,
|
|
@@ -3859,7 +4656,8 @@ ${lastSnapshot}`;
|
|
|
3859
4656
|
const screenText = this.readTerminalScreenText();
|
|
3860
4657
|
const parseScreenText = this.getParseScreenText(screenText);
|
|
3861
4658
|
const cached = this.parsedStatusCache;
|
|
3862
|
-
|
|
4659
|
+
const accumulatedRawBufferKey = this.getAccumulatedRawBufferCacheKey();
|
|
4660
|
+
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) {
|
|
3863
4661
|
return cached.result;
|
|
3864
4662
|
}
|
|
3865
4663
|
const parsed = this.runParseSession();
|
|
@@ -3887,6 +4685,7 @@ ${lastSnapshot}`;
|
|
|
3887
4685
|
currentTurnScope: this.currentTurnScope,
|
|
3888
4686
|
recentOutputBuffer: this.recentOutputBuffer,
|
|
3889
4687
|
accumulatedBuffer: this.accumulatedBuffer,
|
|
4688
|
+
accumulatedRawBufferKey,
|
|
3890
4689
|
screenText: parseScreenText,
|
|
3891
4690
|
currentStatus: this.currentStatus,
|
|
3892
4691
|
activeModal: this.activeModal,
|
|
@@ -3911,7 +4710,7 @@ ${lastSnapshot}`;
|
|
|
3911
4710
|
scope: this.currentTurnScope,
|
|
3912
4711
|
runtimeSettings: this.runtimeSettings
|
|
3913
4712
|
});
|
|
3914
|
-
return await Promise.resolve(fn
|
|
4713
|
+
return await Promise.resolve(this.invokeCliScript(fn, {
|
|
3915
4714
|
...input,
|
|
3916
4715
|
args: args && typeof args === "object" ? { ...args } : {}
|
|
3917
4716
|
}));
|
|
@@ -4692,10 +5491,12 @@ __export(index_exports, {
|
|
|
4692
5491
|
IdeProviderInstance: () => IdeProviderInstance,
|
|
4693
5492
|
InMemoryGitSnapshotStore: () => InMemoryGitSnapshotStore,
|
|
4694
5493
|
LOG: () => LOG,
|
|
5494
|
+
MAX_LEDGER_SLICE_LIMIT: () => MAX_LEDGER_SLICE_LIMIT,
|
|
4695
5495
|
MIN_GIT_WORKSPACE_POLL_INTERVAL_MS: () => MIN_GIT_WORKSPACE_POLL_INTERVAL_MS,
|
|
4696
5496
|
MIN_MACHINE_RUNTIME_SUBSCRIPTION_INTERVAL_MS: () => MIN_MACHINE_RUNTIME_SUBSCRIPTION_INTERVAL_MS,
|
|
4697
5497
|
MIN_SESSION_HOST_DIAGNOSTICS_SUBSCRIPTION_INTERVAL_MS: () => MIN_SESSION_HOST_DIAGNOSTICS_SUBSCRIPTION_INTERVAL_MS,
|
|
4698
5498
|
NodePtyTransportFactory: () => NodePtyTransportFactory,
|
|
5499
|
+
P2pRelayFailureError: () => P2pRelayFailureError,
|
|
4699
5500
|
ProviderCliAdapter: () => ProviderCliAdapter,
|
|
4700
5501
|
ProviderInstanceManager: () => ProviderInstanceManager,
|
|
4701
5502
|
ProviderLoader: () => ProviderLoader,
|
|
@@ -4706,12 +5507,16 @@ __export(index_exports, {
|
|
|
4706
5507
|
addNode: () => addNode,
|
|
4707
5508
|
appendLedgerEntry: () => appendLedgerEntry,
|
|
4708
5509
|
appendRecentActivity: () => appendRecentActivity,
|
|
5510
|
+
appendRemoteLedgerEntries: () => appendRemoteLedgerEntries,
|
|
4709
5511
|
buildAssistantChatMessage: () => buildAssistantChatMessage,
|
|
4710
5512
|
buildChatMessage: () => buildChatMessage,
|
|
4711
5513
|
buildChatMessageSignature: () => buildChatMessageSignature,
|
|
4712
5514
|
buildChatTailDeliverySignature: () => buildChatTailDeliverySignature,
|
|
4713
5515
|
buildCoordinatorSystemPrompt: () => buildCoordinatorSystemPrompt,
|
|
4714
5516
|
buildMachineInfo: () => buildMachineInfo,
|
|
5517
|
+
buildMeshLedgerReconciliationEvidence: () => buildMeshLedgerReconciliationEvidence,
|
|
5518
|
+
buildMeshLedgerReplicaEvidence: () => buildMeshLedgerReplicaEvidence,
|
|
5519
|
+
buildP2pRelayFailurePayload: () => buildP2pRelayFailurePayload,
|
|
4715
5520
|
buildPinnedGlobalInstallCommand: () => buildPinnedGlobalInstallCommand,
|
|
4716
5521
|
buildRuntimeSystemChatMessage: () => buildRuntimeSystemChatMessage,
|
|
4717
5522
|
buildSessionEntries: () => buildSessionEntries,
|
|
@@ -4722,9 +5527,11 @@ __export(index_exports, {
|
|
|
4722
5527
|
buildThoughtChatMessage: () => buildThoughtChatMessage,
|
|
4723
5528
|
buildToolChatMessage: () => buildToolChatMessage,
|
|
4724
5529
|
buildUserChatMessage: () => buildUserChatMessage,
|
|
5530
|
+
cancelTask: () => cancelTask,
|
|
4725
5531
|
claimNextTask: () => claimNextTask,
|
|
4726
5532
|
classifyChatMessageVisibility: () => classifyChatMessageVisibility,
|
|
4727
5533
|
classifyHotChatSessionsForSubscriptionFlush: () => classifyHotChatSessionsForSubscriptionFlush,
|
|
5534
|
+
classifyP2pRelayFailure: () => classifyP2pRelayFailure,
|
|
4728
5535
|
clearDebugTrace: () => clearDebugTrace,
|
|
4729
5536
|
compareGitSnapshots: () => compareGitSnapshots,
|
|
4730
5537
|
configureDebugTraceStore: () => configureDebugTraceStore,
|
|
@@ -4765,6 +5572,7 @@ __export(index_exports, {
|
|
|
4765
5572
|
getLogLevel: () => getLogLevel,
|
|
4766
5573
|
getMesh: () => getMesh,
|
|
4767
5574
|
getMeshByRepo: () => getMeshByRepo,
|
|
5575
|
+
getMeshQueueStats: () => getMeshQueueStats,
|
|
4768
5576
|
getNpmExecOptions: () => getNpmExecOptions,
|
|
4769
5577
|
getQueue: () => getQueue,
|
|
4770
5578
|
getRecentActivity: () => getRecentActivity,
|
|
@@ -4791,6 +5599,7 @@ __export(index_exports, {
|
|
|
4791
5599
|
isInternalChatMessage: () => isInternalChatMessage,
|
|
4792
5600
|
isManagedStatusWaiting: () => isManagedStatusWaiting,
|
|
4793
5601
|
isManagedStatusWorking: () => isManagedStatusWorking,
|
|
5602
|
+
isP2pRelayTransportFailure: () => isP2pRelayTransportFailure,
|
|
4794
5603
|
isPathInside: () => isPathInside,
|
|
4795
5604
|
isSessionHostLiveRuntime: () => isSessionHostLiveRuntime,
|
|
4796
5605
|
isSessionHostRecoverySnapshot: () => isSessionHostRecoverySnapshot,
|
|
@@ -4829,10 +5638,12 @@ __export(index_exports, {
|
|
|
4829
5638
|
probeCdpPort: () => probeCdpPort,
|
|
4830
5639
|
readChatHistory: () => readChatHistory,
|
|
4831
5640
|
readLedgerEntries: () => readLedgerEntries,
|
|
5641
|
+
readLedgerSlice: () => readLedgerSlice,
|
|
4832
5642
|
recordDebugTrace: () => recordDebugTrace,
|
|
4833
5643
|
registerExtensionProviders: () => registerExtensionProviders,
|
|
4834
5644
|
removeNode: () => removeNode,
|
|
4835
5645
|
removeWorktree: () => removeWorktree,
|
|
5646
|
+
requeueTask: () => requeueTask,
|
|
4836
5647
|
resetConfig: () => resetConfig,
|
|
4837
5648
|
resetDebugRuntimeConfig: () => resetDebugRuntimeConfig,
|
|
4838
5649
|
resetState: () => resetState,
|
|
@@ -6346,7 +7157,7 @@ function addWorkspaceEntry(config, rawPath, label, options) {
|
|
|
6346
7157
|
}
|
|
6347
7158
|
}
|
|
6348
7159
|
const v = validateWorkspacePath(abs);
|
|
6349
|
-
if (
|
|
7160
|
+
if (v.ok !== true) return { error: v.error };
|
|
6350
7161
|
const list = [...config.workspaces || []];
|
|
6351
7162
|
if (list.some((w) => path5.resolve(w.path) === abs)) {
|
|
6352
7163
|
return { error: "Workspace already in list" };
|
|
@@ -6729,34 +7540,186 @@ async function syncMeshes(transport) {
|
|
|
6729
7540
|
}
|
|
6730
7541
|
}
|
|
6731
7542
|
}
|
|
6732
|
-
if (transport.syncMeshLedger) {
|
|
6733
|
-
for (const local of localMeshes) {
|
|
6734
|
-
try {
|
|
6735
|
-
await syncMeshLedger(local.id, transport);
|
|
6736
|
-
} catch (e) {
|
|
6737
|
-
result.errors.push(`Ledger sync failed for "${local.name}": ${e.message}`);
|
|
6738
|
-
}
|
|
6739
|
-
}
|
|
6740
|
-
}
|
|
6741
7543
|
return result;
|
|
6742
7544
|
}
|
|
6743
|
-
async function syncMeshLedger(meshId, transport) {
|
|
6744
|
-
if (!transport.syncMeshLedger) return;
|
|
6745
|
-
const { readLedgerEntries: readLedgerEntries2, appendRemoteLedgerEntries: appendRemoteLedgerEntries2 } = await Promise.resolve().then(() => (init_mesh_ledger(), mesh_ledger_exports));
|
|
6746
|
-
const localEntries = readLedgerEntries2(meshId);
|
|
6747
|
-
const res = await transport.syncMeshLedger(meshId, { newEntries: localEntries });
|
|
6748
|
-
if (res.missingEntries && res.missingEntries.length > 0) {
|
|
6749
|
-
appendRemoteLedgerEntries2(meshId, res.missingEntries);
|
|
6750
|
-
}
|
|
6751
|
-
}
|
|
6752
7545
|
|
|
6753
7546
|
// src/index.ts
|
|
6754
7547
|
init_mesh_ledger();
|
|
7548
|
+
|
|
7549
|
+
// src/mesh/mesh-ledger-reconciliation.ts
|
|
7550
|
+
function lastTimestamp(slice) {
|
|
7551
|
+
const entries = Array.isArray(slice?.entries) ? slice.entries : [];
|
|
7552
|
+
return entries.length ? entries[entries.length - 1].timestamp : null;
|
|
7553
|
+
}
|
|
7554
|
+
function buildMeshLedgerReplicaEvidence(args) {
|
|
7555
|
+
const entriesReceived = Array.isArray(args.slice?.entries) ? args.slice.entries.length : 0;
|
|
7556
|
+
return {
|
|
7557
|
+
nodeId: args.nodeId,
|
|
7558
|
+
...args.daemonId ? { daemonId: args.daemonId } : {},
|
|
7559
|
+
status: args.status ?? (args.importResult && args.importResult.accepted > 0 ? "imported" : "queried"),
|
|
7560
|
+
transport: args.transport,
|
|
7561
|
+
protocol: "adhdev.mesh.ledger.slice.v1",
|
|
7562
|
+
entriesReceived,
|
|
7563
|
+
entriesImported: args.importResult?.accepted ?? 0,
|
|
7564
|
+
skippedDuplicate: args.importResult?.skippedDuplicate ?? 0,
|
|
7565
|
+
rejectedInvalid: args.importResult?.rejectedInvalid ?? 0,
|
|
7566
|
+
hasMore: args.slice?.cursor?.hasMore === true,
|
|
7567
|
+
nextAfterId: args.slice?.cursor?.nextAfterId ?? null,
|
|
7568
|
+
lastTimestamp: lastTimestamp(args.slice),
|
|
7569
|
+
...args.slice?.summary ? { summary: args.slice.summary } : {},
|
|
7570
|
+
...args.error ? {
|
|
7571
|
+
error: args.error,
|
|
7572
|
+
noFallbackReason: "Ledger reconciliation is P2P/local-first only; Cloud/D1 ledger sync is intentionally disabled."
|
|
7573
|
+
} : {}
|
|
7574
|
+
};
|
|
7575
|
+
}
|
|
7576
|
+
function buildMeshLedgerReconciliationEvidence(meshId, replicas) {
|
|
7577
|
+
const failedNodes = replicas.filter((replica) => replica.status === "failed").map((replica) => replica.nodeId);
|
|
7578
|
+
const pendingNodes = replicas.filter((replica) => replica.hasMore && replica.status !== "failed").map((replica) => replica.nodeId);
|
|
7579
|
+
return {
|
|
7580
|
+
protocol: "adhdev.mesh.ledger.reconciliation.v1",
|
|
7581
|
+
meshId,
|
|
7582
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7583
|
+
sourceOfTruth: {
|
|
7584
|
+
kind: "coordinator_local_jsonl",
|
|
7585
|
+
p2pOnly: true,
|
|
7586
|
+
cloudD1LedgerSync: false,
|
|
7587
|
+
notes: "Coordinator reconciles bounded slices from daemon-local JSONL ledgers over P2P DataChannel; Cloud/D1 is not a ledger source of truth."
|
|
7588
|
+
},
|
|
7589
|
+
replicas,
|
|
7590
|
+
totals: {
|
|
7591
|
+
replicas: replicas.length,
|
|
7592
|
+
queried: replicas.filter((replica) => replica.status !== "failed").length,
|
|
7593
|
+
failed: failedNodes.length,
|
|
7594
|
+
entriesReceived: replicas.reduce((sum, replica) => sum + replica.entriesReceived, 0),
|
|
7595
|
+
entriesImported: replicas.reduce((sum, replica) => sum + replica.entriesImported, 0),
|
|
7596
|
+
skippedDuplicate: replicas.reduce((sum, replica) => sum + replica.skippedDuplicate, 0),
|
|
7597
|
+
rejectedInvalid: replicas.reduce((sum, replica) => sum + replica.rejectedInvalid, 0)
|
|
7598
|
+
},
|
|
7599
|
+
convergence: {
|
|
7600
|
+
complete: failedNodes.length === 0 && pendingNodes.length === 0,
|
|
7601
|
+
pendingNodes,
|
|
7602
|
+
failedNodes
|
|
7603
|
+
}
|
|
7604
|
+
};
|
|
7605
|
+
}
|
|
7606
|
+
|
|
7607
|
+
// src/index.ts
|
|
6755
7608
|
init_mesh_work_queue();
|
|
6756
7609
|
init_mesh_events();
|
|
6757
7610
|
|
|
7611
|
+
// src/mesh/p2p-relay-failure.ts
|
|
7612
|
+
var NO_FALLBACK_REASON = "Repo Mesh command/data-plane is P2P-only; WS/REST command fallback is intentionally disabled to preserve the transport boundary.";
|
|
7613
|
+
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.";
|
|
7614
|
+
var NON_P2P_NEXT_ACTION = "Inspect the provider/command error and fix the underlying logic or configuration before retrying.";
|
|
7615
|
+
function messageFromError(error) {
|
|
7616
|
+
if (error instanceof Error) return error.message;
|
|
7617
|
+
if (typeof error === "string") return error;
|
|
7618
|
+
if (error && typeof error === "object") {
|
|
7619
|
+
const candidate = error.error ?? error.message ?? error.reason;
|
|
7620
|
+
if (typeof candidate === "string") return candidate;
|
|
7621
|
+
}
|
|
7622
|
+
return String(error || "mesh relay command failed");
|
|
7623
|
+
}
|
|
7624
|
+
function classifyP2pRelayFailure(error, _context = {}) {
|
|
7625
|
+
const message = messageFromError(error);
|
|
7626
|
+
const lower = message.toLowerCase();
|
|
7627
|
+
const hasP2pSignal = /p2p|datachannel|node-datachannel|webrtc|ice|mesh_relay_command|daemon_mesh_p2p_transport/i.test(message);
|
|
7628
|
+
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);
|
|
7629
|
+
if (/requires targetdaemonid and command|providerpriority|no inference provider|permission denied|read-only|not a member/i.test(message)) {
|
|
7630
|
+
return {
|
|
7631
|
+
code: "mesh_logic_or_provider_failure",
|
|
7632
|
+
reason: "mesh_logic_or_provider_failure",
|
|
7633
|
+
transport: "unknown",
|
|
7634
|
+
recoverable: false,
|
|
7635
|
+
retryRecommended: false,
|
|
7636
|
+
nextAction: NON_P2P_NEXT_ACTION,
|
|
7637
|
+
noFallbackReason: NO_FALLBACK_REASON
|
|
7638
|
+
};
|
|
7639
|
+
}
|
|
7640
|
+
let code = null;
|
|
7641
|
+
let reason = "";
|
|
7642
|
+
if (/timeout|timed out/i.test(message) && (hasP2pSignal || /mesh transport/i.test(message))) {
|
|
7643
|
+
code = "p2p_timeout";
|
|
7644
|
+
reason = "daemon_mesh_p2p_timeout";
|
|
7645
|
+
} else if (/no route|route unavailable/i.test(message)) {
|
|
7646
|
+
code = "p2p_no_route";
|
|
7647
|
+
reason = "daemon_mesh_p2p_no_route";
|
|
7648
|
+
} else if (/offline|not owned|not found|not connected to server/i.test(message) && /daemon|peer|target/i.test(message)) {
|
|
7649
|
+
code = "p2p_daemon_offline";
|
|
7650
|
+
reason = "daemon_mesh_target_offline";
|
|
7651
|
+
} else if (/closed|disconnected/i.test(message) && (hasP2pSignal || /state changed/i.test(message))) {
|
|
7652
|
+
code = "p2p_datachannel_closed";
|
|
7653
|
+
reason = "daemon_mesh_p2p_datachannel_closed";
|
|
7654
|
+
} else if (/not connected|cannot send|cannot establish/i.test(message) && hasP2pSignal) {
|
|
7655
|
+
code = "p2p_not_connected";
|
|
7656
|
+
reason = "daemon_mesh_p2p_not_connected";
|
|
7657
|
+
} else if (hasP2pSignal && hasFailureSignal) {
|
|
7658
|
+
code = "p2p_unavailable";
|
|
7659
|
+
reason = "daemon_mesh_p2p_transport_unavailable";
|
|
7660
|
+
}
|
|
7661
|
+
if (!code) {
|
|
7662
|
+
return {
|
|
7663
|
+
code: "mesh_logic_or_provider_failure",
|
|
7664
|
+
reason: "mesh_logic_or_provider_failure",
|
|
7665
|
+
transport: "unknown",
|
|
7666
|
+
recoverable: false,
|
|
7667
|
+
retryRecommended: false,
|
|
7668
|
+
nextAction: NON_P2P_NEXT_ACTION,
|
|
7669
|
+
noFallbackReason: NO_FALLBACK_REASON
|
|
7670
|
+
};
|
|
7671
|
+
}
|
|
7672
|
+
return {
|
|
7673
|
+
code,
|
|
7674
|
+
reason,
|
|
7675
|
+
transport: "p2p",
|
|
7676
|
+
recoverable: true,
|
|
7677
|
+
retryRecommended: true,
|
|
7678
|
+
nextAction: P2P_NEXT_ACTION,
|
|
7679
|
+
noFallbackReason: NO_FALLBACK_REASON
|
|
7680
|
+
};
|
|
7681
|
+
}
|
|
7682
|
+
function isP2pRelayTransportFailure(error) {
|
|
7683
|
+
return classifyP2pRelayFailure(error).recoverable === true;
|
|
7684
|
+
}
|
|
7685
|
+
function buildP2pRelayFailurePayload(error, context = {}) {
|
|
7686
|
+
const classification = classifyP2pRelayFailure(error, context);
|
|
7687
|
+
return {
|
|
7688
|
+
success: false,
|
|
7689
|
+
...classification,
|
|
7690
|
+
error: messageFromError(error),
|
|
7691
|
+
...context.command ? { command: context.command } : {},
|
|
7692
|
+
...context.targetDaemonId ? { targetDaemonId: context.targetDaemonId } : {}
|
|
7693
|
+
};
|
|
7694
|
+
}
|
|
7695
|
+
var P2pRelayFailureError = class extends Error {
|
|
7696
|
+
code;
|
|
7697
|
+
reason;
|
|
7698
|
+
transport;
|
|
7699
|
+
recoverable;
|
|
7700
|
+
retryRecommended;
|
|
7701
|
+
nextAction;
|
|
7702
|
+
noFallbackReason;
|
|
7703
|
+
command;
|
|
7704
|
+
targetDaemonId;
|
|
7705
|
+
constructor(message, context = {}) {
|
|
7706
|
+
super(message);
|
|
7707
|
+
this.name = "P2pRelayFailureError";
|
|
7708
|
+
const payload = buildP2pRelayFailurePayload(message, context);
|
|
7709
|
+
this.code = payload.code;
|
|
7710
|
+
this.reason = payload.reason;
|
|
7711
|
+
this.transport = payload.transport;
|
|
7712
|
+
this.recoverable = payload.recoverable;
|
|
7713
|
+
this.retryRecommended = payload.retryRecommended;
|
|
7714
|
+
this.nextAction = payload.nextAction;
|
|
7715
|
+
this.noFallbackReason = payload.noFallbackReason;
|
|
7716
|
+
this.command = context.command;
|
|
7717
|
+
this.targetDaemonId = context.targetDaemonId;
|
|
7718
|
+
}
|
|
7719
|
+
};
|
|
7720
|
+
|
|
6758
7721
|
// src/config/state-store.ts
|
|
6759
|
-
var
|
|
7722
|
+
var import_fs6 = require("fs");
|
|
6760
7723
|
var import_path5 = require("path");
|
|
6761
7724
|
init_config();
|
|
6762
7725
|
var DEFAULT_STATE = {
|
|
@@ -6807,11 +7770,11 @@ function normalizeState(raw) {
|
|
|
6807
7770
|
}
|
|
6808
7771
|
function loadState() {
|
|
6809
7772
|
const statePath = getStatePath();
|
|
6810
|
-
if (!(0,
|
|
7773
|
+
if (!(0, import_fs6.existsSync)(statePath)) {
|
|
6811
7774
|
return { ...DEFAULT_STATE };
|
|
6812
7775
|
}
|
|
6813
7776
|
try {
|
|
6814
|
-
const raw = (0,
|
|
7777
|
+
const raw = (0, import_fs6.readFileSync)(statePath, "utf-8");
|
|
6815
7778
|
return normalizeState(JSON.parse(raw));
|
|
6816
7779
|
} catch {
|
|
6817
7780
|
return { ...DEFAULT_STATE };
|
|
@@ -6820,17 +7783,17 @@ function loadState() {
|
|
|
6820
7783
|
function saveState(state) {
|
|
6821
7784
|
const statePath = getStatePath();
|
|
6822
7785
|
const normalized = normalizeState(state);
|
|
6823
|
-
(0,
|
|
7786
|
+
(0, import_fs6.writeFileSync)(statePath, JSON.stringify(normalized, null, 2), { encoding: "utf-8", mode: 384 });
|
|
6824
7787
|
}
|
|
6825
7788
|
function resetState() {
|
|
6826
7789
|
saveState({ ...DEFAULT_STATE });
|
|
6827
7790
|
}
|
|
6828
7791
|
|
|
6829
7792
|
// src/detection/ide-detector.ts
|
|
6830
|
-
var
|
|
6831
|
-
var
|
|
7793
|
+
var import_child_process2 = require("child_process");
|
|
7794
|
+
var import_fs7 = require("fs");
|
|
6832
7795
|
var import_os2 = require("os");
|
|
6833
|
-
var
|
|
7796
|
+
var path10 = __toESM(require("path"));
|
|
6834
7797
|
var BUILTIN_IDE_DEFINITIONS = [];
|
|
6835
7798
|
var registeredIDEs = /* @__PURE__ */ new Map();
|
|
6836
7799
|
function registerIDEDefinition(def) {
|
|
@@ -6849,13 +7812,13 @@ function getMergedDefinitions() {
|
|
|
6849
7812
|
function findCliCommand(command) {
|
|
6850
7813
|
const trimmed = String(command || "").trim();
|
|
6851
7814
|
if (!trimmed) return null;
|
|
6852
|
-
if (
|
|
6853
|
-
const candidate = trimmed.startsWith("~") ?
|
|
6854
|
-
const resolved =
|
|
6855
|
-
return (0,
|
|
7815
|
+
if (path10.isAbsolute(trimmed) || trimmed.includes("/") || trimmed.includes("\\") || trimmed.startsWith("~")) {
|
|
7816
|
+
const candidate = trimmed.startsWith("~") ? path10.join((0, import_os2.homedir)(), trimmed.slice(1)) : trimmed;
|
|
7817
|
+
const resolved = path10.isAbsolute(candidate) ? candidate : path10.resolve(candidate);
|
|
7818
|
+
return (0, import_fs7.existsSync)(resolved) ? resolved : null;
|
|
6856
7819
|
}
|
|
6857
7820
|
try {
|
|
6858
|
-
const result = (0,
|
|
7821
|
+
const result = (0, import_child_process2.execSync)(
|
|
6859
7822
|
(0, import_os2.platform)() === "win32" ? `where ${trimmed}` : `which ${trimmed}`,
|
|
6860
7823
|
{ encoding: "utf-8", timeout: 5e3, stdio: ["pipe", "pipe", "pipe"] }
|
|
6861
7824
|
).trim();
|
|
@@ -6866,7 +7829,7 @@ function findCliCommand(command) {
|
|
|
6866
7829
|
}
|
|
6867
7830
|
function getIdeVersion(cliCommand) {
|
|
6868
7831
|
try {
|
|
6869
|
-
const result = (0,
|
|
7832
|
+
const result = (0, import_child_process2.execSync)(`"${cliCommand}" --version`, {
|
|
6870
7833
|
encoding: "utf-8",
|
|
6871
7834
|
timeout: 1e4,
|
|
6872
7835
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -6879,13 +7842,13 @@ function getIdeVersion(cliCommand) {
|
|
|
6879
7842
|
function checkPathExists(paths) {
|
|
6880
7843
|
const home = (0, import_os2.homedir)();
|
|
6881
7844
|
for (const p of paths) {
|
|
6882
|
-
const normalized = p.startsWith("~") ?
|
|
7845
|
+
const normalized = p.startsWith("~") ? path10.join(home, p.slice(1)) : p;
|
|
6883
7846
|
if (normalized.includes("*")) {
|
|
6884
7847
|
const username = home.split(/[\\/]/).pop() || "";
|
|
6885
7848
|
const resolved = normalized.replace("*", username);
|
|
6886
|
-
if ((0,
|
|
7849
|
+
if ((0, import_fs7.existsSync)(resolved)) return resolved;
|
|
6887
7850
|
} else {
|
|
6888
|
-
if ((0,
|
|
7851
|
+
if ((0, import_fs7.existsSync)(normalized)) return normalized;
|
|
6889
7852
|
}
|
|
6890
7853
|
}
|
|
6891
7854
|
return null;
|
|
@@ -6899,7 +7862,7 @@ async function detectIDEs(providerLoader) {
|
|
|
6899
7862
|
let resolvedCli = cliPath;
|
|
6900
7863
|
if (!resolvedCli && appPath && os22 === "darwin") {
|
|
6901
7864
|
const bundledCli = `${appPath}/Contents/Resources/app/bin/${def.cli}`;
|
|
6902
|
-
if ((0,
|
|
7865
|
+
if ((0, import_fs7.existsSync)(bundledCli)) resolvedCli = bundledCli;
|
|
6903
7866
|
}
|
|
6904
7867
|
if (!resolvedCli && appPath && os22 === "win32") {
|
|
6905
7868
|
const { dirname: dirname9 } = await import("path");
|
|
@@ -6912,7 +7875,7 @@ async function detectIDEs(providerLoader) {
|
|
|
6912
7875
|
`${appDir}\\\\resources\\\\app\\\\bin\\\\${def.cli}.cmd`
|
|
6913
7876
|
];
|
|
6914
7877
|
for (const c of candidates) {
|
|
6915
|
-
if ((0,
|
|
7878
|
+
if ((0, import_fs7.existsSync)(c)) {
|
|
6916
7879
|
resolvedCli = c;
|
|
6917
7880
|
break;
|
|
6918
7881
|
}
|
|
@@ -6934,134 +7897,8 @@ async function detectIDEs(providerLoader) {
|
|
|
6934
7897
|
return results;
|
|
6935
7898
|
}
|
|
6936
7899
|
|
|
6937
|
-
// src/
|
|
6938
|
-
|
|
6939
|
-
var os3 = __toESM(require("os"));
|
|
6940
|
-
var path10 = __toESM(require("path"));
|
|
6941
|
-
var import_fs7 = require("fs");
|
|
6942
|
-
function parseVersion(raw) {
|
|
6943
|
-
const match = raw.match(/v?(\d+\.\d+(?:\.\d+)?(?:-[a-zA-Z0-9.]+)?)/);
|
|
6944
|
-
return match ? match[1] : raw.split("\n")[0].slice(0, 100);
|
|
6945
|
-
}
|
|
6946
|
-
function shellQuote(value) {
|
|
6947
|
-
if (/^[a-zA-Z0-9_./:@%+=,-]+$/.test(value)) return value;
|
|
6948
|
-
return `"${value.replace(/(["\\$`])/g, "\\$1")}"`;
|
|
6949
|
-
}
|
|
6950
|
-
function expandHome(value) {
|
|
6951
|
-
const trimmed = value.trim();
|
|
6952
|
-
if (!trimmed.startsWith("~")) return trimmed;
|
|
6953
|
-
return path10.join(os3.homedir(), trimmed.slice(1));
|
|
6954
|
-
}
|
|
6955
|
-
function isExplicitCommandPath(command) {
|
|
6956
|
-
const trimmed = command.trim();
|
|
6957
|
-
return path10.isAbsolute(trimmed) || trimmed.includes("/") || trimmed.includes("\\") || trimmed.startsWith("~");
|
|
6958
|
-
}
|
|
6959
|
-
function resolveCommandPath(command) {
|
|
6960
|
-
const trimmed = command.trim();
|
|
6961
|
-
if (!trimmed) return null;
|
|
6962
|
-
if (isExplicitCommandPath(trimmed)) {
|
|
6963
|
-
const expanded = expandHome(trimmed);
|
|
6964
|
-
const candidate = path10.isAbsolute(expanded) ? expanded : path10.resolve(expanded);
|
|
6965
|
-
return (0, import_fs7.existsSync)(candidate) ? candidate : null;
|
|
6966
|
-
}
|
|
6967
|
-
return null;
|
|
6968
|
-
}
|
|
6969
|
-
function execAsync(cmd, timeoutMs = 5e3) {
|
|
6970
|
-
return new Promise((resolve16) => {
|
|
6971
|
-
const child = (0, import_child_process2.exec)(cmd, {
|
|
6972
|
-
encoding: "utf-8",
|
|
6973
|
-
timeout: timeoutMs,
|
|
6974
|
-
...process.platform === "win32" ? { windowsHide: true } : {}
|
|
6975
|
-
}, (err, stdout) => {
|
|
6976
|
-
if (err || !stdout?.trim()) {
|
|
6977
|
-
resolve16(null);
|
|
6978
|
-
} else {
|
|
6979
|
-
resolve16(stdout.trim());
|
|
6980
|
-
}
|
|
6981
|
-
});
|
|
6982
|
-
child.on("error", () => resolve16(null));
|
|
6983
|
-
});
|
|
6984
|
-
}
|
|
6985
|
-
async function detectCLIs(providerLoader, options) {
|
|
6986
|
-
const platform10 = os3.platform();
|
|
6987
|
-
const whichCmd = platform10 === "win32" ? "where" : "which";
|
|
6988
|
-
const includeVersion = options?.includeVersion !== false;
|
|
6989
|
-
const cliList = providerLoader ? providerLoader.getCliDetectionList() : [];
|
|
6990
|
-
const results = await Promise.all(
|
|
6991
|
-
cliList.map(async (cli) => {
|
|
6992
|
-
try {
|
|
6993
|
-
const explicitPath = resolveCommandPath(cli.command);
|
|
6994
|
-
const pathResult = explicitPath || await execAsync(`${whichCmd} ${shellQuote(cli.command)}`);
|
|
6995
|
-
if (!pathResult) return { ...cli, installed: false };
|
|
6996
|
-
const firstPath = explicitPath || pathResult.split("\n")[0];
|
|
6997
|
-
let version;
|
|
6998
|
-
if (includeVersion) {
|
|
6999
|
-
const versionCommands = [
|
|
7000
|
-
`"${firstPath}" --version`,
|
|
7001
|
-
`"${firstPath}" -V`,
|
|
7002
|
-
`"${firstPath}" -v`,
|
|
7003
|
-
cli.versionCommand
|
|
7004
|
-
].filter((v) => !!v);
|
|
7005
|
-
try {
|
|
7006
|
-
for (const versionCommand of versionCommands) {
|
|
7007
|
-
const versionResult = await execAsync(versionCommand, 3e3);
|
|
7008
|
-
if (versionResult) {
|
|
7009
|
-
version = parseVersion(versionResult);
|
|
7010
|
-
break;
|
|
7011
|
-
}
|
|
7012
|
-
}
|
|
7013
|
-
} catch {
|
|
7014
|
-
}
|
|
7015
|
-
}
|
|
7016
|
-
return { ...cli, installed: true, version, path: firstPath };
|
|
7017
|
-
} catch {
|
|
7018
|
-
return { ...cli, installed: false };
|
|
7019
|
-
}
|
|
7020
|
-
})
|
|
7021
|
-
);
|
|
7022
|
-
return results;
|
|
7023
|
-
}
|
|
7024
|
-
async function detectCLI(cliId, providerLoader, options) {
|
|
7025
|
-
const resolvedId = providerLoader ? providerLoader.resolveAlias(cliId) : cliId;
|
|
7026
|
-
if (providerLoader) {
|
|
7027
|
-
const cliList = providerLoader.getCliDetectionList();
|
|
7028
|
-
const target = cliList.find((c) => c.id === resolvedId);
|
|
7029
|
-
if (target) {
|
|
7030
|
-
const platform10 = os3.platform();
|
|
7031
|
-
const whichCmd = platform10 === "win32" ? "where" : "which";
|
|
7032
|
-
try {
|
|
7033
|
-
const explicitPath = resolveCommandPath(target.command);
|
|
7034
|
-
const pathResult = explicitPath || await execAsync(`${whichCmd} ${shellQuote(target.command)}`);
|
|
7035
|
-
if (!pathResult) return null;
|
|
7036
|
-
const firstPath = explicitPath || pathResult.split("\n")[0];
|
|
7037
|
-
let version;
|
|
7038
|
-
if (options?.includeVersion !== false) {
|
|
7039
|
-
const versionCommands = [
|
|
7040
|
-
`"${firstPath}" --version`,
|
|
7041
|
-
`"${firstPath}" -V`,
|
|
7042
|
-
`"${firstPath}" -v`,
|
|
7043
|
-
target.versionCommand
|
|
7044
|
-
].filter((v) => !!v);
|
|
7045
|
-
try {
|
|
7046
|
-
for (const versionCommand of versionCommands) {
|
|
7047
|
-
const versionResult = await execAsync(versionCommand, 3e3);
|
|
7048
|
-
if (versionResult) {
|
|
7049
|
-
version = parseVersion(versionResult);
|
|
7050
|
-
break;
|
|
7051
|
-
}
|
|
7052
|
-
}
|
|
7053
|
-
} catch {
|
|
7054
|
-
}
|
|
7055
|
-
}
|
|
7056
|
-
return { ...target, installed: true, version, path: firstPath };
|
|
7057
|
-
} catch {
|
|
7058
|
-
return null;
|
|
7059
|
-
}
|
|
7060
|
-
}
|
|
7061
|
-
}
|
|
7062
|
-
const all = await detectCLIs(providerLoader, options);
|
|
7063
|
-
return all.find((c) => c.id === resolvedId && c.installed) || null;
|
|
7064
|
-
}
|
|
7900
|
+
// src/index.ts
|
|
7901
|
+
init_cli_detector();
|
|
7065
7902
|
|
|
7066
7903
|
// src/system/host-memory.ts
|
|
7067
7904
|
var os4 = __toESM(require("os"));
|
|
@@ -15205,11 +16042,13 @@ async function handleOpenPanel(h, args) {
|
|
|
15205
16042
|
async function handlePtyInput(h, args) {
|
|
15206
16043
|
const { cliType, data, targetSessionId } = args || {};
|
|
15207
16044
|
if (!data) return { success: false, error: "data required" };
|
|
16045
|
+
const cleanData = typeof data === "string" ? data.replace(/\x1b\[[?>][0-9;]*c/g, "") : data;
|
|
16046
|
+
if (!cleanData) return { success: true };
|
|
15208
16047
|
const adapter = h.getCliAdapter(targetSessionId || cliType);
|
|
15209
16048
|
if (!adapter || typeof adapter.writeRaw !== "function") {
|
|
15210
16049
|
return { success: false, error: `CLI adapter not found: ${targetSessionId || cliType || "unknown"}` };
|
|
15211
16050
|
}
|
|
15212
|
-
await adapter.writeRaw(
|
|
16051
|
+
await adapter.writeRaw(cleanData);
|
|
15213
16052
|
return { success: true };
|
|
15214
16053
|
}
|
|
15215
16054
|
function handlePtyResize(_h, args) {
|
|
@@ -16169,6 +17008,7 @@ var import_fs8 = require("fs");
|
|
|
16169
17008
|
var import_child_process6 = require("child_process");
|
|
16170
17009
|
var import_chalk = __toESM(require("chalk"));
|
|
16171
17010
|
init_provider_cli_adapter();
|
|
17011
|
+
init_cli_detector();
|
|
16172
17012
|
init_config();
|
|
16173
17013
|
|
|
16174
17014
|
// src/providers/cli-provider-instance.ts
|
|
@@ -16198,6 +17038,8 @@ function normalizeProviderSessionId(provider, providerSessionId) {
|
|
|
16198
17038
|
}
|
|
16199
17039
|
|
|
16200
17040
|
// src/providers/cli-provider-instance.ts
|
|
17041
|
+
var COMPLETED_FINALIZATION_RETRY_MS = 1e3;
|
|
17042
|
+
var COMPLETED_FINALIZATION_MAX_WAIT_MS = 3e4;
|
|
16201
17043
|
var IMAGE_MIME_EXTENSIONS = {
|
|
16202
17044
|
"image/png": ".png",
|
|
16203
17045
|
"image/jpeg": ".jpg",
|
|
@@ -16261,6 +17103,13 @@ function cleanupStaleMaterializedImages(dir) {
|
|
|
16261
17103
|
} catch {
|
|
16262
17104
|
}
|
|
16263
17105
|
}
|
|
17106
|
+
function hasNonEmptyCliModalButtons(activeModal) {
|
|
17107
|
+
const buttons = activeModal?.buttons;
|
|
17108
|
+
return Array.isArray(buttons) && buttons.some((button) => String(button || "").trim().length > 0);
|
|
17109
|
+
}
|
|
17110
|
+
function isCliGeneratingLikeStatus(status) {
|
|
17111
|
+
return status === "generating" || status === "streaming" || status === "long_generating" || status === "starting";
|
|
17112
|
+
}
|
|
16264
17113
|
function buildCliStructuredInputPrompt(input, options = {}) {
|
|
16265
17114
|
const promptParts = [];
|
|
16266
17115
|
const imageRefs = [];
|
|
@@ -16545,10 +17394,12 @@ var CliProviderInstance = class {
|
|
|
16545
17394
|
const mergedMessages = this.mergeConversationMessages(parsedMessages);
|
|
16546
17395
|
const canonicalBackedHistory = this.syncCanonicalSavedHistoryIfNeeded();
|
|
16547
17396
|
const dirName = this.workingDir.split("/").filter(Boolean).pop() || "session";
|
|
17397
|
+
const parsedChatStatus = typeof parsedStatus?.status === "string" && parsedStatus.status.trim() ? parsedStatus.status.trim() : void 0;
|
|
17398
|
+
const suppressStaleParsedBusyStatus = this.shouldSuppressStaleParsedBusyStatus(parsedStatus, adapterStatus);
|
|
16548
17399
|
if (parsedMessages.length > 0) {
|
|
16549
17400
|
const shouldSkipReplayPersist = this.suppressIdleHistoryReplay && adapterStatus.status === "idle" && parsedStatus?.status === "idle";
|
|
16550
17401
|
let messagesToSave = parsedMessages;
|
|
16551
|
-
if (
|
|
17402
|
+
if (!suppressStaleParsedBusyStatus && (parsedChatStatus === "generating" || parsedChatStatus === "long_generating")) {
|
|
16552
17403
|
const lastIdx = messagesToSave.length - 1;
|
|
16553
17404
|
if (lastIdx >= 0 && messagesToSave[lastIdx]?.role === "assistant") {
|
|
16554
17405
|
messagesToSave = messagesToSave.slice(0, lastIdx);
|
|
@@ -16582,6 +17433,7 @@ var CliProviderInstance = class {
|
|
|
16582
17433
|
summaryMetadata: this.summaryMetadata,
|
|
16583
17434
|
controlValues: this.controlValues
|
|
16584
17435
|
});
|
|
17436
|
+
const activeChatStatus = parseErrorMessage ? "error" : autoApproveActive && parsedStatus?.status === "waiting_approval" ? "generating" : adapterStatus.status !== "idle" ? visibleStatus : suppressStaleParsedBusyStatus ? visibleStatus : parsedChatStatus || visibleStatus;
|
|
16585
17437
|
return {
|
|
16586
17438
|
type: this.type,
|
|
16587
17439
|
name: this.provider.name,
|
|
@@ -16591,7 +17443,7 @@ var CliProviderInstance = class {
|
|
|
16591
17443
|
activeChat: {
|
|
16592
17444
|
id: `${this.type}_${this.workingDir}`,
|
|
16593
17445
|
title: parsedStatus?.title || dirName,
|
|
16594
|
-
status:
|
|
17446
|
+
status: activeChatStatus,
|
|
16595
17447
|
messages: mergedMessages,
|
|
16596
17448
|
activeModal: autoApproveActive ? null : parsedStatus?.activeModal ?? adapterStatus.activeModal,
|
|
16597
17449
|
inputContent: ""
|
|
@@ -16719,7 +17571,103 @@ var CliProviderInstance = class {
|
|
|
16719
17571
|
await this.adapter.writeRaw("\r");
|
|
16720
17572
|
}
|
|
16721
17573
|
}
|
|
16722
|
-
this.applyProviderResponse(parsed.payload, { phase: "immediate" });
|
|
17574
|
+
this.applyProviderResponse(parsed.payload, { phase: "immediate" });
|
|
17575
|
+
}
|
|
17576
|
+
completionHasFinalAssistantMessage(messages) {
|
|
17577
|
+
const visibleMessages = (Array.isArray(messages) ? messages : []).filter((message) => isUserFacingChatMessage(message));
|
|
17578
|
+
const lastVisible = visibleMessages[visibleMessages.length - 1];
|
|
17579
|
+
const role = typeof lastVisible?.role === "string" ? lastVisible.role.trim().toLowerCase() : "";
|
|
17580
|
+
const content = lastVisible ? flattenContent(lastVisible.content).trim() : "";
|
|
17581
|
+
return role === "assistant" && !!content;
|
|
17582
|
+
}
|
|
17583
|
+
hasAdapterPendingResponse() {
|
|
17584
|
+
const adapterAny = this.adapter;
|
|
17585
|
+
if (adapterAny?.isWaitingForResponse === true) return true;
|
|
17586
|
+
if (adapterAny?.currentTurnScope) return true;
|
|
17587
|
+
try {
|
|
17588
|
+
if (typeof this.adapter.isProcessing === "function" && this.adapter.isProcessing()) return true;
|
|
17589
|
+
} catch {
|
|
17590
|
+
}
|
|
17591
|
+
try {
|
|
17592
|
+
const partial = typeof this.adapter.getPartialResponse === "function" ? this.adapter.getPartialResponse() : "";
|
|
17593
|
+
if (typeof partial === "string" && partial.trim()) return true;
|
|
17594
|
+
} catch {
|
|
17595
|
+
}
|
|
17596
|
+
return false;
|
|
17597
|
+
}
|
|
17598
|
+
shouldSuppressStaleParsedBusyStatus(parsedStatus, adapterStatus) {
|
|
17599
|
+
const parsedRawStatus = typeof parsedStatus?.status === "string" ? parsedStatus.status.trim() : "";
|
|
17600
|
+
const adapterRawStatus = typeof adapterStatus?.status === "string" ? adapterStatus.status.trim() : "";
|
|
17601
|
+
if (!isCliGeneratingLikeStatus(parsedRawStatus)) return false;
|
|
17602
|
+
if (adapterRawStatus !== "idle") return false;
|
|
17603
|
+
if (hasNonEmptyCliModalButtons(parsedStatus?.activeModal ?? parsedStatus?.modal)) return false;
|
|
17604
|
+
return !this.hasAdapterPendingResponse();
|
|
17605
|
+
}
|
|
17606
|
+
getCompletedFinalizationBlockReason(latestVisibleStatus) {
|
|
17607
|
+
if (latestVisibleStatus !== "idle") return `status:${latestVisibleStatus}`;
|
|
17608
|
+
const adapterAny = this.adapter;
|
|
17609
|
+
if (adapterAny?.isWaitingForResponse === true) return "adapter_waiting_for_response";
|
|
17610
|
+
if (adapterAny?.currentTurnScope) return "adapter_turn_scope_active";
|
|
17611
|
+
const partial = typeof this.adapter.getPartialResponse === "function" ? this.adapter.getPartialResponse() : "";
|
|
17612
|
+
if (typeof partial === "string" && partial.trim()) return "partial_response_pending";
|
|
17613
|
+
let parsed;
|
|
17614
|
+
try {
|
|
17615
|
+
parsed = this.adapter.getScriptParsedStatus();
|
|
17616
|
+
} catch (error) {
|
|
17617
|
+
return `parse_error:${error?.message || String(error)}`;
|
|
17618
|
+
}
|
|
17619
|
+
const parsedStatus = typeof parsed?.status === "string" ? parsed.status : "unknown";
|
|
17620
|
+
if (parsedStatus !== "idle") return `parsed_status:${parsedStatus}`;
|
|
17621
|
+
if (parsed?.activeModal || parsed?.modal) return "parsed_modal_active";
|
|
17622
|
+
if (!this.completionHasFinalAssistantMessage(parsed?.messages)) return "missing_final_assistant";
|
|
17623
|
+
return null;
|
|
17624
|
+
}
|
|
17625
|
+
scheduleCompletedDebounceFlush(delayMs) {
|
|
17626
|
+
if (this.completedDebounceTimer) clearTimeout(this.completedDebounceTimer);
|
|
17627
|
+
this.completedDebounceTimer = setTimeout(() => this.flushCompletedDebounceIfFinalized(), delayMs);
|
|
17628
|
+
}
|
|
17629
|
+
flushCompletedDebounceIfFinalized() {
|
|
17630
|
+
const pending = this.completedDebouncePending;
|
|
17631
|
+
if (!pending) {
|
|
17632
|
+
this.completedDebounceTimer = null;
|
|
17633
|
+
return;
|
|
17634
|
+
}
|
|
17635
|
+
const latestStatus = this.adapter.getStatus({ allowParse: false });
|
|
17636
|
+
const latestAutoApproveActive = latestStatus.status === "waiting_approval" && this.shouldAutoApprove();
|
|
17637
|
+
const latestVisibleStatus = latestAutoApproveActive ? "generating" : latestStatus.status;
|
|
17638
|
+
if (latestVisibleStatus !== "idle") {
|
|
17639
|
+
LOG.info("CLI", `[${this.type}] cancelled pending completed (resumed ${latestVisibleStatus})`);
|
|
17640
|
+
this.completedDebouncePending = null;
|
|
17641
|
+
this.completedDebounceTimer = null;
|
|
17642
|
+
return;
|
|
17643
|
+
}
|
|
17644
|
+
const blockReason = this.getCompletedFinalizationBlockReason(latestVisibleStatus);
|
|
17645
|
+
if (blockReason) {
|
|
17646
|
+
const waitedMs = Date.now() - pending.firstObservedAt;
|
|
17647
|
+
if (waitedMs < COMPLETED_FINALIZATION_MAX_WAIT_MS) {
|
|
17648
|
+
if (pending.loggedBlockReason !== blockReason) {
|
|
17649
|
+
LOG.info("CLI", `[${this.type}] waiting to emit completed until transcript finalizes (${blockReason})`);
|
|
17650
|
+
pending.loggedBlockReason = blockReason;
|
|
17651
|
+
}
|
|
17652
|
+
this.scheduleCompletedDebounceFlush(COMPLETED_FINALIZATION_RETRY_MS);
|
|
17653
|
+
return;
|
|
17654
|
+
}
|
|
17655
|
+
LOG.warn("CLI", `[${this.type}] suppressed completed event after ${waitedMs}ms without finalized assistant turn (${blockReason})`);
|
|
17656
|
+
this.completedDebouncePending = null;
|
|
17657
|
+
this.completedDebounceTimer = null;
|
|
17658
|
+
this.generatingStartedAt = 0;
|
|
17659
|
+
return;
|
|
17660
|
+
}
|
|
17661
|
+
LOG.info("CLI", `[${this.type}] completed in ${pending.duration}s`);
|
|
17662
|
+
this.pushEvent({
|
|
17663
|
+
event: "agent:generating_completed",
|
|
17664
|
+
chatTitle: pending.chatTitle,
|
|
17665
|
+
duration: pending.duration,
|
|
17666
|
+
timestamp: pending.timestamp
|
|
17667
|
+
});
|
|
17668
|
+
this.completedDebouncePending = null;
|
|
17669
|
+
this.completedDebounceTimer = null;
|
|
17670
|
+
this.generatingStartedAt = 0;
|
|
16723
17671
|
}
|
|
16724
17672
|
maybeAutoApproveStatus(adapterStatus, now = Date.now()) {
|
|
16725
17673
|
const autoApproveActive = adapterStatus?.status === "waiting_approval" && this.shouldAutoApprove();
|
|
@@ -16818,27 +17766,11 @@ var CliProviderInstance = class {
|
|
|
16818
17766
|
this.generatingDebouncePending = null;
|
|
16819
17767
|
this.generatingStartedAt = 0;
|
|
16820
17768
|
} else {
|
|
16821
|
-
|
|
16822
|
-
this.
|
|
16823
|
-
this.completedDebounceTimer = setTimeout(() => {
|
|
16824
|
-
if (this.completedDebouncePending) {
|
|
16825
|
-
const latestStatus = this.adapter.getStatus({ allowParse: false });
|
|
16826
|
-
const latestAutoApproveActive = latestStatus.status === "waiting_approval" && this.shouldAutoApprove();
|
|
16827
|
-
const latestVisibleStatus = latestAutoApproveActive ? "generating" : latestStatus.status;
|
|
16828
|
-
if (latestVisibleStatus !== "idle") {
|
|
16829
|
-
LOG.info("CLI", `[${this.type}] cancelled pending completed (resumed ${latestVisibleStatus})`);
|
|
16830
|
-
this.completedDebouncePending = null;
|
|
16831
|
-
this.completedDebounceTimer = null;
|
|
16832
|
-
return;
|
|
16833
|
-
}
|
|
16834
|
-
LOG.info("CLI", `[${this.type}] completed in ${this.completedDebouncePending.duration}s`);
|
|
16835
|
-
this.pushEvent({ event: "agent:generating_completed", ...this.completedDebouncePending });
|
|
16836
|
-
this.completedDebouncePending = null;
|
|
16837
|
-
this.generatingStartedAt = 0;
|
|
16838
|
-
}
|
|
16839
|
-
this.completedDebounceTimer = null;
|
|
16840
|
-
}, 3e3);
|
|
17769
|
+
this.completedDebouncePending = { chatTitle, duration, timestamp: now, firstObservedAt: now };
|
|
17770
|
+
this.scheduleCompletedDebounceFlush(3e3);
|
|
16841
17771
|
}
|
|
17772
|
+
} else if (newStatus === "idle" && this.lastStatus === "starting") {
|
|
17773
|
+
this.pushEvent({ event: "agent:ready", chatTitle, timestamp: now });
|
|
16842
17774
|
} else if (newStatus === "stopped") {
|
|
16843
17775
|
if (this.generatingDebounceTimer) {
|
|
16844
17776
|
clearTimeout(this.generatingDebounceTimer);
|
|
@@ -18582,9 +19514,6 @@ function buildCoordinatorDelegatedCliLaunchOptions(input) {
|
|
|
18582
19514
|
const cliType = String(input.cliType || "").trim();
|
|
18583
19515
|
const cliArgs = Array.isArray(input.cliArgs) ? [...input.cliArgs] : [];
|
|
18584
19516
|
const env = { ...input.env || {}, ...COORDINATOR_DELEGATED_ENV_UNSETS };
|
|
18585
|
-
if (cliType === "hermes-cli" && !hasCliArg(cliArgs, "--ignore-user-config")) {
|
|
18586
|
-
cliArgs.unshift("--ignore-user-config");
|
|
18587
|
-
}
|
|
18588
19517
|
if (cliType === "claude-cli" && !hasCliArg(cliArgs, "--mcp-config")) {
|
|
18589
19518
|
cliArgs.unshift("--mcp-config", ensureEmptyDelegatedMcpConfig(input.workspace));
|
|
18590
19519
|
}
|
|
@@ -21661,6 +22590,7 @@ function getAvailableIdeIds() {
|
|
|
21661
22590
|
|
|
21662
22591
|
// src/commands/router.ts
|
|
21663
22592
|
init_config();
|
|
22593
|
+
init_cli_detector();
|
|
21664
22594
|
init_logger();
|
|
21665
22595
|
|
|
21666
22596
|
// src/logging/command-log.ts
|
|
@@ -21830,7 +22760,9 @@ function resolveHermesMeshCoordinatorSetup(options) {
|
|
|
21830
22760
|
const mcpServer = resolveAdhdevMcpServerLaunch({
|
|
21831
22761
|
meshId: options.meshId,
|
|
21832
22762
|
nodeExecutable: options.nodeExecutable,
|
|
21833
|
-
adhdevMcpEntryPath: options.adhdevMcpEntryPath
|
|
22763
|
+
adhdevMcpEntryPath: options.adhdevMcpEntryPath,
|
|
22764
|
+
adhdevMcpTransport: options.adhdevMcpTransport,
|
|
22765
|
+
adhdevMcpPort: options.adhdevMcpPort
|
|
21834
22766
|
});
|
|
21835
22767
|
if (!mcpServer) {
|
|
21836
22768
|
return {
|
|
@@ -21897,7 +22829,9 @@ function resolveMeshCoordinatorSetup(options) {
|
|
|
21897
22829
|
const mcpServer = resolveAdhdevMcpServerLaunch({
|
|
21898
22830
|
meshId,
|
|
21899
22831
|
nodeExecutable: options.nodeExecutable,
|
|
21900
|
-
adhdevMcpEntryPath: options.adhdevMcpEntryPath
|
|
22832
|
+
adhdevMcpEntryPath: options.adhdevMcpEntryPath,
|
|
22833
|
+
adhdevMcpTransport: options.adhdevMcpTransport,
|
|
22834
|
+
adhdevMcpPort: options.adhdevMcpPort
|
|
21901
22835
|
});
|
|
21902
22836
|
if (!mcpServer) {
|
|
21903
22837
|
return {
|
|
@@ -21919,6 +22853,22 @@ function resolveMeshCoordinatorSetup(options) {
|
|
|
21919
22853
|
if (!instructions || !template?.trim()) {
|
|
21920
22854
|
return { kind: "unsupported", reason: "Provider manual MCP setup is missing instructions or template" };
|
|
21921
22855
|
}
|
|
22856
|
+
const renderedTemplate = renderMeshCoordinatorTemplate(template, {
|
|
22857
|
+
meshId,
|
|
22858
|
+
workspace,
|
|
22859
|
+
serverName,
|
|
22860
|
+
adhdevMcpCommand: options.adhdevMcpCommand || DEFAULT_ADHDEV_MCP_COMMAND
|
|
22861
|
+
});
|
|
22862
|
+
const isCliCommand = !renderedTemplate.trim().includes("\n") && !renderedTemplate.trim().startsWith("{");
|
|
22863
|
+
if (isCliCommand) {
|
|
22864
|
+
return {
|
|
22865
|
+
kind: "cli_command",
|
|
22866
|
+
serverName,
|
|
22867
|
+
command: renderedTemplate.trim(),
|
|
22868
|
+
requiresRestart: mcpConfig.requiresRestart === true,
|
|
22869
|
+
instructions
|
|
22870
|
+
};
|
|
22871
|
+
}
|
|
21922
22872
|
return {
|
|
21923
22873
|
kind: "manual",
|
|
21924
22874
|
serverName,
|
|
@@ -21926,12 +22876,7 @@ function resolveMeshCoordinatorSetup(options) {
|
|
|
21926
22876
|
configPathCommand: mcpConfig.configPathCommand,
|
|
21927
22877
|
requiresRestart: mcpConfig.requiresRestart === true,
|
|
21928
22878
|
instructions,
|
|
21929
|
-
template:
|
|
21930
|
-
meshId,
|
|
21931
|
-
workspace,
|
|
21932
|
-
serverName,
|
|
21933
|
-
adhdevMcpCommand: options.adhdevMcpCommand || DEFAULT_ADHDEV_MCP_COMMAND
|
|
21934
|
-
})
|
|
22879
|
+
template: renderedTemplate
|
|
21935
22880
|
};
|
|
21936
22881
|
}
|
|
21937
22882
|
return {
|
|
@@ -21960,11 +22905,27 @@ function resolveAdhdevMcpServerLaunch(options) {
|
|
|
21960
22905
|
if (!entryPath) return null;
|
|
21961
22906
|
const nodeExecutable = resolveMcpNodeExecutable(options.nodeExecutable);
|
|
21962
22907
|
if (!nodeExecutable) return null;
|
|
22908
|
+
const transport = resolveMcpTransport(options.adhdevMcpTransport);
|
|
22909
|
+
const args = [entryPath, "--mode", transport, "--repo-mesh", options.meshId];
|
|
22910
|
+
const port = resolveMcpPort(options.adhdevMcpPort);
|
|
22911
|
+
if (port !== void 0) args.push("--port", String(port));
|
|
21963
22912
|
return {
|
|
21964
22913
|
command: nodeExecutable,
|
|
21965
|
-
args
|
|
22914
|
+
args
|
|
21966
22915
|
};
|
|
21967
22916
|
}
|
|
22917
|
+
function resolveMcpTransport(explicitTransport) {
|
|
22918
|
+
if (explicitTransport === "local" || explicitTransport === "ipc") return explicitTransport;
|
|
22919
|
+
const envTransport = process.env.ADHDEV_COORDINATOR_MCP_TRANSPORT?.trim();
|
|
22920
|
+
return envTransport === "local" ? "local" : "ipc";
|
|
22921
|
+
}
|
|
22922
|
+
function resolveMcpPort(explicitPort) {
|
|
22923
|
+
if (typeof explicitPort === "number" && Number.isInteger(explicitPort) && explicitPort > 0) return explicitPort;
|
|
22924
|
+
const raw = process.env.ADHDEV_COORDINATOR_MCP_PORT?.trim();
|
|
22925
|
+
if (!raw) return void 0;
|
|
22926
|
+
const parsed = Number(raw);
|
|
22927
|
+
return Number.isInteger(parsed) && parsed > 0 ? parsed : void 0;
|
|
22928
|
+
}
|
|
21968
22929
|
function resolveMcpNodeExecutable(explicitExecutable) {
|
|
21969
22930
|
const explicit = explicitExecutable?.trim();
|
|
21970
22931
|
if (explicit) return explicit;
|
|
@@ -22776,6 +23737,209 @@ async function resolveProviderTypeFromPriority(args) {
|
|
|
22776
23737
|
}
|
|
22777
23738
|
return { error: `No usable provider detected for node '${args.nodeId}' from providerPriority: ${failed.join("; ")}` };
|
|
22778
23739
|
}
|
|
23740
|
+
var REFINE_VALIDATION_CATEGORIES = ["typecheck", "test", "lint", "build"];
|
|
23741
|
+
var REFINE_VALIDATION_TIMEOUT_MS = 12e4;
|
|
23742
|
+
var REFINE_VALIDATION_OUTPUT_LIMIT_BYTES = 128 * 1024;
|
|
23743
|
+
var REFINE_VALIDATION_SUMMARY_CHARS = 2e3;
|
|
23744
|
+
var REFINE_VALIDATION_MAX_COMMANDS = 4;
|
|
23745
|
+
function truncateValidationOutput(value) {
|
|
23746
|
+
const text = typeof value === "string" ? value : value == null ? "" : String(value);
|
|
23747
|
+
if (text.length <= REFINE_VALIDATION_SUMMARY_CHARS) return text;
|
|
23748
|
+
return `${text.slice(0, REFINE_VALIDATION_SUMMARY_CHARS)}
|
|
23749
|
+
[truncated ${text.length - REFINE_VALIDATION_SUMMARY_CHARS} chars]`;
|
|
23750
|
+
}
|
|
23751
|
+
function readPackageScripts(workspace) {
|
|
23752
|
+
try {
|
|
23753
|
+
const packageJsonPath = (0, import_path6.join)(workspace, "package.json");
|
|
23754
|
+
const parsed = JSON.parse(fs10.readFileSync(packageJsonPath, "utf-8"));
|
|
23755
|
+
return parsed?.scripts && typeof parsed.scripts === "object" && !Array.isArray(parsed.scripts) ? parsed.scripts : {};
|
|
23756
|
+
} catch {
|
|
23757
|
+
return {};
|
|
23758
|
+
}
|
|
23759
|
+
}
|
|
23760
|
+
function tokenizeValidationCommand(command) {
|
|
23761
|
+
const trimmed = command.trim();
|
|
23762
|
+
if (!trimmed) return null;
|
|
23763
|
+
if (/[;&|<>`$\\\n\r'\"]/.test(trimmed)) return null;
|
|
23764
|
+
const tokens = trimmed.split(/\s+/).filter(Boolean);
|
|
23765
|
+
if (!tokens.length) return null;
|
|
23766
|
+
if (tokens.some((token) => !/^[A-Za-z0-9_@./:=+-]+$/.test(token))) return null;
|
|
23767
|
+
return tokens;
|
|
23768
|
+
}
|
|
23769
|
+
function scriptMatchesValidationCategory(scriptName, category) {
|
|
23770
|
+
return scriptName === category || scriptName.startsWith(`${category}:`);
|
|
23771
|
+
}
|
|
23772
|
+
function parsePackageManagerValidationCommand(rawCommand, category, scripts, source) {
|
|
23773
|
+
const tokens = tokenizeValidationCommand(rawCommand);
|
|
23774
|
+
if (!tokens) {
|
|
23775
|
+
return { rejected: { command: rawCommand, category, source, reason: "unsafe command string is not allowlisted" } };
|
|
23776
|
+
}
|
|
23777
|
+
const [binary, second, third, ...rest] = tokens;
|
|
23778
|
+
let scriptName = "";
|
|
23779
|
+
let command = binary;
|
|
23780
|
+
let args = [];
|
|
23781
|
+
if ((binary === "npm" || binary === "pnpm" || binary === "bun") && second === "run" && third) {
|
|
23782
|
+
scriptName = third;
|
|
23783
|
+
args = ["run", scriptName, ...rest];
|
|
23784
|
+
} else if (binary === "npm" && second === "test" && !third) {
|
|
23785
|
+
scriptName = "test";
|
|
23786
|
+
args = ["test"];
|
|
23787
|
+
} else if (binary === "yarn" && second === "run" && third) {
|
|
23788
|
+
scriptName = third;
|
|
23789
|
+
args = ["run", scriptName, ...rest];
|
|
23790
|
+
} else if (binary === "yarn" && second && !third) {
|
|
23791
|
+
scriptName = second;
|
|
23792
|
+
args = [scriptName];
|
|
23793
|
+
} else {
|
|
23794
|
+
return { rejected: { command: rawCommand, category, source, reason: "command is not a supported package-manager script invocation" } };
|
|
23795
|
+
}
|
|
23796
|
+
if (!scriptName || !Object.prototype.hasOwnProperty.call(scripts, scriptName)) {
|
|
23797
|
+
return { rejected: { command: rawCommand, category, source, script: scriptName, reason: "script is not declared in package.json" } };
|
|
23798
|
+
}
|
|
23799
|
+
if (!scriptMatchesValidationCategory(scriptName, category)) {
|
|
23800
|
+
return { rejected: { command: rawCommand, category, source, script: scriptName, reason: "script name is outside the validation category allowlist" } };
|
|
23801
|
+
}
|
|
23802
|
+
return {
|
|
23803
|
+
command: {
|
|
23804
|
+
command,
|
|
23805
|
+
args,
|
|
23806
|
+
displayCommand: [command, ...args].join(" "),
|
|
23807
|
+
category,
|
|
23808
|
+
source
|
|
23809
|
+
}
|
|
23810
|
+
};
|
|
23811
|
+
}
|
|
23812
|
+
function collectProjectContextValidationCandidates(mesh) {
|
|
23813
|
+
const commands = mesh?.projectContext?.commands;
|
|
23814
|
+
if (!commands || typeof commands !== "object" || Array.isArray(commands)) return [];
|
|
23815
|
+
const candidates = [];
|
|
23816
|
+
for (const category of REFINE_VALIDATION_CATEGORIES) {
|
|
23817
|
+
const entries = Array.isArray(commands[category]) ? commands[category] : [];
|
|
23818
|
+
for (const entry of entries) {
|
|
23819
|
+
if (typeof entry?.command !== "string") continue;
|
|
23820
|
+
candidates.push({
|
|
23821
|
+
command: entry.command,
|
|
23822
|
+
category,
|
|
23823
|
+
source: typeof entry.sourcePath === "string" ? entry.sourcePath : "projectContext.commands",
|
|
23824
|
+
confidence: typeof entry.confidence === "string" ? entry.confidence : void 0
|
|
23825
|
+
});
|
|
23826
|
+
}
|
|
23827
|
+
}
|
|
23828
|
+
return candidates.sort((a, b) => {
|
|
23829
|
+
const rank = (value) => value === "high" ? 0 : value === "medium" ? 1 : 2;
|
|
23830
|
+
return rank(a.confidence) - rank(b.confidence);
|
|
23831
|
+
});
|
|
23832
|
+
}
|
|
23833
|
+
function collectPolicyValidationCandidates(mesh) {
|
|
23834
|
+
const policy = mesh?.policy && typeof mesh.policy === "object" && !Array.isArray(mesh.policy) ? mesh.policy : {};
|
|
23835
|
+
const configured = Array.isArray(policy.validationCommands) ? policy.validationCommands : Array.isArray(policy.validationGate?.commands) ? policy.validationGate.commands : [];
|
|
23836
|
+
return configured.map((entry) => typeof entry === "string" ? { command: entry, category: "", source: "mesh.policy.validationCommands" } : entry).filter((entry) => entry && typeof entry.command === "string").map((entry) => {
|
|
23837
|
+
const commandText = entry.command.trim();
|
|
23838
|
+
const category = REFINE_VALIDATION_CATEGORIES.find((cat) => commandText.includes(` ${cat}`)) ?? "";
|
|
23839
|
+
return { command: commandText, category, source: "mesh.policy.validationCommands" };
|
|
23840
|
+
}).filter((entry) => !!entry.category);
|
|
23841
|
+
}
|
|
23842
|
+
function selectMeshRefineValidationCommands(mesh, workspace) {
|
|
23843
|
+
const scripts = readPackageScripts(workspace);
|
|
23844
|
+
const rejectedCommands = [];
|
|
23845
|
+
const selected = [];
|
|
23846
|
+
const seen = /* @__PURE__ */ new Set();
|
|
23847
|
+
const candidates = [
|
|
23848
|
+
...collectPolicyValidationCandidates(mesh),
|
|
23849
|
+
...collectProjectContextValidationCandidates(mesh)
|
|
23850
|
+
];
|
|
23851
|
+
for (const candidate of candidates) {
|
|
23852
|
+
const parsed = parsePackageManagerValidationCommand(candidate.command, candidate.category, scripts, candidate.source);
|
|
23853
|
+
if (parsed.rejected) {
|
|
23854
|
+
rejectedCommands.push(parsed.rejected);
|
|
23855
|
+
continue;
|
|
23856
|
+
}
|
|
23857
|
+
if (!parsed.command || seen.has(parsed.command.displayCommand)) continue;
|
|
23858
|
+
selected.push(parsed.command);
|
|
23859
|
+
seen.add(parsed.command.displayCommand);
|
|
23860
|
+
if (selected.length >= REFINE_VALIDATION_MAX_COMMANDS) break;
|
|
23861
|
+
}
|
|
23862
|
+
if (!selected.length && candidates.length === 0) {
|
|
23863
|
+
for (const category of REFINE_VALIDATION_CATEGORIES) {
|
|
23864
|
+
if (!Object.prototype.hasOwnProperty.call(scripts, category)) continue;
|
|
23865
|
+
const fallback = parsePackageManagerValidationCommand(`npm run ${category}`, category, scripts, "package.json:scripts");
|
|
23866
|
+
if (fallback.command && !seen.has(fallback.command.displayCommand)) {
|
|
23867
|
+
selected.push(fallback.command);
|
|
23868
|
+
seen.add(fallback.command.displayCommand);
|
|
23869
|
+
} else if (fallback.rejected) {
|
|
23870
|
+
rejectedCommands.push(fallback.rejected);
|
|
23871
|
+
}
|
|
23872
|
+
if (selected.length >= 2) break;
|
|
23873
|
+
}
|
|
23874
|
+
}
|
|
23875
|
+
return {
|
|
23876
|
+
commands: selected,
|
|
23877
|
+
rejectedCommands,
|
|
23878
|
+
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"
|
|
23879
|
+
};
|
|
23880
|
+
}
|
|
23881
|
+
async function runMeshRefineValidationGate(mesh, workspace) {
|
|
23882
|
+
const { execFile: execFile3 } = await import("child_process");
|
|
23883
|
+
const { promisify: promisify3 } = await import("util");
|
|
23884
|
+
const execFileAsync3 = promisify3(execFile3);
|
|
23885
|
+
const selection = selectMeshRefineValidationCommands(mesh, workspace);
|
|
23886
|
+
const summary = {
|
|
23887
|
+
status: "skipped",
|
|
23888
|
+
required: true,
|
|
23889
|
+
commandsRun: [],
|
|
23890
|
+
rejectedCommands: selection.rejectedCommands,
|
|
23891
|
+
skippedReason: void 0,
|
|
23892
|
+
timeoutMs: REFINE_VALIDATION_TIMEOUT_MS,
|
|
23893
|
+
outputLimitBytes: REFINE_VALIDATION_OUTPUT_LIMIT_BYTES
|
|
23894
|
+
};
|
|
23895
|
+
if (!selection.commands.length) {
|
|
23896
|
+
summary.skippedReason = "validation_unavailable: no allowlisted projectContext, mesh policy, or package.json build/test/typecheck/lint command was available";
|
|
23897
|
+
return summary;
|
|
23898
|
+
}
|
|
23899
|
+
for (const candidate of selection.commands) {
|
|
23900
|
+
const startedAt = Date.now();
|
|
23901
|
+
try {
|
|
23902
|
+
const result = await execFileAsync3(candidate.command, candidate.args, {
|
|
23903
|
+
cwd: workspace,
|
|
23904
|
+
encoding: "utf8",
|
|
23905
|
+
timeout: REFINE_VALIDATION_TIMEOUT_MS,
|
|
23906
|
+
maxBuffer: REFINE_VALIDATION_OUTPUT_LIMIT_BYTES,
|
|
23907
|
+
env: { ...process.env, CI: process.env.CI || "1" }
|
|
23908
|
+
});
|
|
23909
|
+
summary.commandsRun.push({
|
|
23910
|
+
command: candidate.command,
|
|
23911
|
+
args: candidate.args,
|
|
23912
|
+
displayCommand: candidate.displayCommand,
|
|
23913
|
+
category: candidate.category,
|
|
23914
|
+
source: candidate.source,
|
|
23915
|
+
passed: true,
|
|
23916
|
+
exitCode: 0,
|
|
23917
|
+
durationMs: Date.now() - startedAt,
|
|
23918
|
+
stdout: truncateValidationOutput(result.stdout),
|
|
23919
|
+
stderr: truncateValidationOutput(result.stderr)
|
|
23920
|
+
});
|
|
23921
|
+
} catch (error) {
|
|
23922
|
+
summary.commandsRun.push({
|
|
23923
|
+
command: candidate.command,
|
|
23924
|
+
args: candidate.args,
|
|
23925
|
+
displayCommand: candidate.displayCommand,
|
|
23926
|
+
category: candidate.category,
|
|
23927
|
+
source: candidate.source,
|
|
23928
|
+
passed: false,
|
|
23929
|
+
exitCode: typeof error?.code === "number" ? error.code : null,
|
|
23930
|
+
signal: typeof error?.signal === "string" ? error.signal : null,
|
|
23931
|
+
timedOut: error?.killed === true || /timed out/i.test(String(error?.message || "")),
|
|
23932
|
+
durationMs: Date.now() - startedAt,
|
|
23933
|
+
stdout: truncateValidationOutput(error?.stdout),
|
|
23934
|
+
stderr: truncateValidationOutput(error?.stderr || error?.message)
|
|
23935
|
+
});
|
|
23936
|
+
summary.status = "failed";
|
|
23937
|
+
return summary;
|
|
23938
|
+
}
|
|
23939
|
+
}
|
|
23940
|
+
summary.status = "passed";
|
|
23941
|
+
return summary;
|
|
23942
|
+
}
|
|
22779
23943
|
function loadYamlModule() {
|
|
22780
23944
|
return yaml;
|
|
22781
23945
|
}
|
|
@@ -22805,6 +23969,34 @@ function loadHermesCoordinatorBaseConfig(targetConfigPath) {
|
|
|
22805
23969
|
const { mcp_servers: _mcpServers, ...baseConfig } = parsed;
|
|
22806
23970
|
return { config: baseConfig, sourceHome, sourceConfigPath };
|
|
22807
23971
|
}
|
|
23972
|
+
function stripHermesCoordinatorTempModelProviderOverrides(config) {
|
|
23973
|
+
const {
|
|
23974
|
+
model: _model,
|
|
23975
|
+
provider: _provider,
|
|
23976
|
+
default_model: _defaultModel,
|
|
23977
|
+
defaultProvider: _defaultProvider,
|
|
23978
|
+
default_provider: _defaultProviderSnake,
|
|
23979
|
+
modelProvider: _modelProvider,
|
|
23980
|
+
model_provider: _modelProviderSnake,
|
|
23981
|
+
...sanitized
|
|
23982
|
+
} = config;
|
|
23983
|
+
const delegation = sanitized.delegation;
|
|
23984
|
+
if (delegation && typeof delegation === "object" && !Array.isArray(delegation)) {
|
|
23985
|
+
const {
|
|
23986
|
+
model: _delegationModel,
|
|
23987
|
+
provider: _delegationProvider,
|
|
23988
|
+
modelProvider: _delegationModelProvider,
|
|
23989
|
+
model_provider: _delegationModelProviderSnake,
|
|
23990
|
+
...delegationRest
|
|
23991
|
+
} = delegation;
|
|
23992
|
+
if (Object.keys(delegationRest).length > 0) {
|
|
23993
|
+
sanitized.delegation = delegationRest;
|
|
23994
|
+
} else {
|
|
23995
|
+
delete sanitized.delegation;
|
|
23996
|
+
}
|
|
23997
|
+
}
|
|
23998
|
+
return sanitized;
|
|
23999
|
+
}
|
|
22808
24000
|
function copyHermesCoordinatorCredentialFiles(sourceHome, targetHome) {
|
|
22809
24001
|
if ((0, import_path6.resolve)(sourceHome) === (0, import_path6.resolve)(targetHome)) return;
|
|
22810
24002
|
for (const fileName of [".env", "auth.json"]) {
|
|
@@ -22962,9 +24154,191 @@ var DaemonCommandRouter = class {
|
|
|
22962
24154
|
if (record?.meta?.meshNodeId === nodeId) return true;
|
|
22963
24155
|
return false;
|
|
22964
24156
|
}
|
|
24157
|
+
async cleanupLocalWorktreeNode(args) {
|
|
24158
|
+
const workspace = typeof args.node?.workspace === "string" ? args.node.workspace.trim() : "";
|
|
24159
|
+
if (!workspace) {
|
|
24160
|
+
return {
|
|
24161
|
+
success: false,
|
|
24162
|
+
code: "mesh_worktree_cleanup_missing_workspace",
|
|
24163
|
+
error: `Worktree node '${args.nodeId}' is missing workspace metadata`,
|
|
24164
|
+
recoveryHint: "Inspect the mesh node record before removing it, or remove stale metadata manually only after confirming no managed worktree remains."
|
|
24165
|
+
};
|
|
24166
|
+
}
|
|
24167
|
+
const worktreeExists = fs10.existsSync(workspace);
|
|
24168
|
+
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);
|
|
24169
|
+
const repoRoot = typeof sourceNode?.repoRoot === "string" && sourceNode.repoRoot.trim() ? sourceNode.repoRoot.trim() : typeof sourceNode?.workspace === "string" && sourceNode.workspace.trim() ? sourceNode.workspace.trim() : "";
|
|
24170
|
+
if (!worktreeExists) {
|
|
24171
|
+
return { success: true, skipped: true, removedPath: workspace, repoRoot: repoRoot || void 0, reason: "worktree_path_missing" };
|
|
24172
|
+
}
|
|
24173
|
+
if (!repoRoot || !fs10.existsSync(repoRoot)) {
|
|
24174
|
+
return {
|
|
24175
|
+
success: false,
|
|
24176
|
+
code: "mesh_worktree_cleanup_missing_source_repo",
|
|
24177
|
+
error: `Refusing to remove worktree '${workspace}' because the source repo root is unavailable`,
|
|
24178
|
+
recoveryHint: "Run mesh_remove_node from the machine that owns the source repo, or verify the source node metadata before retrying."
|
|
24179
|
+
};
|
|
24180
|
+
}
|
|
24181
|
+
if (typeof args.node?.worktreeBranch !== "string" || !args.node.worktreeBranch.trim()) {
|
|
24182
|
+
return {
|
|
24183
|
+
success: false,
|
|
24184
|
+
code: "mesh_worktree_cleanup_missing_branch",
|
|
24185
|
+
error: `Refusing to remove worktree '${workspace}' because worktreeBranch metadata is missing`,
|
|
24186
|
+
recoveryHint: "Confirm this is an ADHDev-managed worktree before removing it manually; managed worktree nodes include worktreeBranch metadata."
|
|
24187
|
+
};
|
|
24188
|
+
}
|
|
24189
|
+
const { resolveWorktreePath: resolveWorktreePath2, listWorktrees: listWorktrees2, removeWorktree: removeWorktree2 } = await Promise.resolve().then(() => (init_git_worktree(), git_worktree_exports));
|
|
24190
|
+
const normalizePath = (value) => {
|
|
24191
|
+
const resolved = (0, import_path6.resolve)(value);
|
|
24192
|
+
try {
|
|
24193
|
+
return fs10.realpathSync(resolved);
|
|
24194
|
+
} catch {
|
|
24195
|
+
return resolved;
|
|
24196
|
+
}
|
|
24197
|
+
};
|
|
24198
|
+
const expectedPath = normalizePath(resolveWorktreePath2(repoRoot, String(args.mesh?.name || args.mesh?.id || "mesh"), args.node.worktreeBranch));
|
|
24199
|
+
const actualPath = normalizePath(workspace);
|
|
24200
|
+
if (actualPath !== expectedPath) {
|
|
24201
|
+
return {
|
|
24202
|
+
success: false,
|
|
24203
|
+
code: "mesh_worktree_cleanup_unexpected_path",
|
|
24204
|
+
error: `Refusing to remove worktree '${workspace}' because it is not at the expected managed path '${expectedPath}'`,
|
|
24205
|
+
recoveryHint: "Use git worktree list/status to inspect the path. Retry only after confirming the mesh node metadata points to an ADHDev-managed worktree."
|
|
24206
|
+
};
|
|
24207
|
+
}
|
|
24208
|
+
const entries = await listWorktrees2(repoRoot);
|
|
24209
|
+
const managedEntry = entries.find((entry) => normalizePath(entry.path) === actualPath);
|
|
24210
|
+
if (!managedEntry) {
|
|
24211
|
+
return {
|
|
24212
|
+
success: false,
|
|
24213
|
+
code: "mesh_worktree_cleanup_not_registered",
|
|
24214
|
+
error: `Refusing to remove '${workspace}' because it is not registered in git worktree list for '${repoRoot}'`,
|
|
24215
|
+
recoveryHint: "Inspect git worktree list --porcelain from the source repo. If the path was already removed, prune git worktrees before retrying."
|
|
24216
|
+
};
|
|
24217
|
+
}
|
|
24218
|
+
if (managedEntry.branch && managedEntry.branch !== args.node.worktreeBranch) {
|
|
24219
|
+
return {
|
|
24220
|
+
success: false,
|
|
24221
|
+
code: "mesh_worktree_cleanup_branch_mismatch",
|
|
24222
|
+
error: `Refusing to remove '${workspace}' because git reports branch '${managedEntry.branch}', expected '${args.node.worktreeBranch}'`,
|
|
24223
|
+
recoveryHint: "Inspect the worktree branch and mesh metadata before retrying cleanup."
|
|
24224
|
+
};
|
|
24225
|
+
}
|
|
24226
|
+
const forceFallbackConvergence = await this.getWorktreeForceCleanupConvergence({
|
|
24227
|
+
repoRoot,
|
|
24228
|
+
workspace,
|
|
24229
|
+
node: args.node
|
|
24230
|
+
});
|
|
24231
|
+
try {
|
|
24232
|
+
const result = await removeWorktree2(repoRoot, workspace, {
|
|
24233
|
+
requireClean: true,
|
|
24234
|
+
allowSubmoduleForceFallback: forceFallbackConvergence.allow
|
|
24235
|
+
});
|
|
24236
|
+
return {
|
|
24237
|
+
success: true,
|
|
24238
|
+
removedPath: result.removedPath,
|
|
24239
|
+
repoRoot,
|
|
24240
|
+
...result.fallback ? {
|
|
24241
|
+
fallback: result.fallback,
|
|
24242
|
+
forced: result.forced,
|
|
24243
|
+
reason: result.reason,
|
|
24244
|
+
convergence: forceFallbackConvergence
|
|
24245
|
+
} : {}
|
|
24246
|
+
};
|
|
24247
|
+
} catch (e) {
|
|
24248
|
+
const message = String(e?.message || e || "worktree cleanup failed");
|
|
24249
|
+
const dirty = message.includes("dirty worktree") || message.includes("local changes");
|
|
24250
|
+
const submoduleForceBlocked = /working trees containing submodules cannot be moved or removed/i.test(message) && !forceFallbackConvergence.allow;
|
|
24251
|
+
return {
|
|
24252
|
+
success: false,
|
|
24253
|
+
code: dirty ? "mesh_worktree_cleanup_dirty" : submoduleForceBlocked ? "mesh_worktree_cleanup_force_fallback_blocked" : "mesh_worktree_cleanup_failed",
|
|
24254
|
+
error: submoduleForceBlocked ? `${message}; refusing --force fallback because convergence could not be verified: ${forceFallbackConvergence.error || "unknown convergence state"}` : message,
|
|
24255
|
+
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.",
|
|
24256
|
+
...submoduleForceBlocked ? { convergence: forceFallbackConvergence } : {}
|
|
24257
|
+
};
|
|
24258
|
+
}
|
|
24259
|
+
}
|
|
24260
|
+
async getWorktreeForceCleanupConvergence(args) {
|
|
24261
|
+
const metadataStatus = typeof args.node?.branchConvergence?.status === "string" ? args.node.branchConvergence.status : "";
|
|
24262
|
+
if (metadataStatus === "merged_to_main" || metadataStatus === "cleanup_candidate") {
|
|
24263
|
+
return { allow: true, status: metadataStatus, source: "node_branch_convergence" };
|
|
24264
|
+
}
|
|
24265
|
+
const { execFile: execFile3 } = await import("child_process");
|
|
24266
|
+
const { promisify: promisify3 } = await import("util");
|
|
24267
|
+
const execFileAsync3 = promisify3(execFile3);
|
|
24268
|
+
const runGit2 = async (gitArgs, cwd) => {
|
|
24269
|
+
const { stdout } = await execFileAsync3("git", gitArgs, {
|
|
24270
|
+
cwd,
|
|
24271
|
+
encoding: "utf8",
|
|
24272
|
+
timeout: 3e4,
|
|
24273
|
+
maxBuffer: 4 * 1024 * 1024,
|
|
24274
|
+
windowsHide: true
|
|
24275
|
+
});
|
|
24276
|
+
return String(stdout || "").trim();
|
|
24277
|
+
};
|
|
24278
|
+
let head = "";
|
|
24279
|
+
try {
|
|
24280
|
+
head = await runGit2(["rev-parse", "HEAD"], args.workspace);
|
|
24281
|
+
} catch (e) {
|
|
24282
|
+
return { allow: false, error: `could not resolve worktree HEAD: ${e?.message || e}` };
|
|
24283
|
+
}
|
|
24284
|
+
if (!head) return { allow: false, error: "worktree HEAD is empty" };
|
|
24285
|
+
const candidateRefs = [];
|
|
24286
|
+
try {
|
|
24287
|
+
const defaultBranch = await runGit2(["branch", "--show-current"], args.repoRoot);
|
|
24288
|
+
if (defaultBranch) {
|
|
24289
|
+
candidateRefs.push(defaultBranch, `origin/${defaultBranch}`);
|
|
24290
|
+
}
|
|
24291
|
+
} catch {
|
|
24292
|
+
}
|
|
24293
|
+
candidateRefs.push("origin/main", "origin/master", "main", "master");
|
|
24294
|
+
const seen = /* @__PURE__ */ new Set();
|
|
24295
|
+
const checkedRefs = [];
|
|
24296
|
+
for (const ref of candidateRefs) {
|
|
24297
|
+
if (!ref || seen.has(ref)) continue;
|
|
24298
|
+
seen.add(ref);
|
|
24299
|
+
let commit = "";
|
|
24300
|
+
try {
|
|
24301
|
+
commit = await runGit2(["rev-parse", "--verify", `${ref}^{commit}`], args.repoRoot);
|
|
24302
|
+
} catch {
|
|
24303
|
+
continue;
|
|
24304
|
+
}
|
|
24305
|
+
checkedRefs.push(ref);
|
|
24306
|
+
try {
|
|
24307
|
+
await runGit2(["merge-base", "--is-ancestor", head, commit], args.repoRoot);
|
|
24308
|
+
return { allow: true, status: "merged_to_default_ref", source: "git_merge_base", ref };
|
|
24309
|
+
} catch {
|
|
24310
|
+
}
|
|
24311
|
+
}
|
|
24312
|
+
return {
|
|
24313
|
+
allow: false,
|
|
24314
|
+
status: metadataStatus || void 0,
|
|
24315
|
+
error: checkedRefs.length ? `worktree HEAD is not contained in checked refs: ${checkedRefs.join(", ")}` : "no default/main refs were available for convergence verification"
|
|
24316
|
+
};
|
|
24317
|
+
}
|
|
22965
24318
|
isCompletedHostedSession(record) {
|
|
22966
24319
|
return record?.lifecycle === "stopped" || record?.lifecycle === "failed" || record?.lifecycle === "interrupted";
|
|
22967
24320
|
}
|
|
24321
|
+
async recordIntentionalMeshSessionStop(args) {
|
|
24322
|
+
try {
|
|
24323
|
+
const { appendLedgerEntry: appendLedgerEntry2 } = await Promise.resolve().then(() => (init_mesh_ledger(), mesh_ledger_exports));
|
|
24324
|
+
appendLedgerEntry2(args.meshId, {
|
|
24325
|
+
kind: "session_stopped",
|
|
24326
|
+
nodeId: args.nodeId,
|
|
24327
|
+
sessionId: args.sessionId,
|
|
24328
|
+
payload: {
|
|
24329
|
+
intentional: true,
|
|
24330
|
+
reason: "operator_cleanup",
|
|
24331
|
+
intentionalStopReason: "operator_cleanup",
|
|
24332
|
+
source: args.source,
|
|
24333
|
+
cleanupMode: args.mode,
|
|
24334
|
+
action: args.action,
|
|
24335
|
+
workspace: typeof args.node?.workspace === "string" ? args.node.workspace : void 0
|
|
24336
|
+
}
|
|
24337
|
+
});
|
|
24338
|
+
} catch (e) {
|
|
24339
|
+
LOG.warn("MeshCleanup", `Failed to record intentional cleanup stop for ${args.sessionId}: ${e?.message || e}`);
|
|
24340
|
+
}
|
|
24341
|
+
}
|
|
22968
24342
|
async cleanupMeshSessions(args) {
|
|
22969
24343
|
if (args.mode === "preserve") {
|
|
22970
24344
|
return { success: true, mode: "preserve", matchedCount: 0, stoppedSessionIds: [], deletedSessionIds: [], skippedSessionIds: [] };
|
|
@@ -22981,6 +24355,21 @@ var DaemonCommandRouter = class {
|
|
|
22981
24355
|
const deleteUnsupportedSessionIds = [];
|
|
22982
24356
|
const recordsRemainSessionIds = [];
|
|
22983
24357
|
const errors = [];
|
|
24358
|
+
const cleanupSource = args.source || "mesh_cleanup_sessions";
|
|
24359
|
+
const markedIntentionalStopSessionIds = /* @__PURE__ */ new Set();
|
|
24360
|
+
const markIntentionalStop = async (sessionId, action) => {
|
|
24361
|
+
if (args.dryRun || markedIntentionalStopSessionIds.has(sessionId)) return;
|
|
24362
|
+
markedIntentionalStopSessionIds.add(sessionId);
|
|
24363
|
+
await this.recordIntentionalMeshSessionStop({
|
|
24364
|
+
meshId: args.meshId,
|
|
24365
|
+
nodeId: args.nodeId,
|
|
24366
|
+
node: args.node,
|
|
24367
|
+
sessionId,
|
|
24368
|
+
mode: args.mode,
|
|
24369
|
+
source: cleanupSource,
|
|
24370
|
+
action
|
|
24371
|
+
});
|
|
24372
|
+
};
|
|
22984
24373
|
const matchedBySurfaceKind = {
|
|
22985
24374
|
live_runtime: 0,
|
|
22986
24375
|
recovery_snapshot: 0,
|
|
@@ -23003,7 +24392,10 @@ var DaemonCommandRouter = class {
|
|
|
23003
24392
|
try {
|
|
23004
24393
|
if (args.mode === "stop") {
|
|
23005
24394
|
if (!completed) {
|
|
23006
|
-
if (!args.dryRun)
|
|
24395
|
+
if (!args.dryRun) {
|
|
24396
|
+
await markIntentionalStop(sessionId, "stop_session");
|
|
24397
|
+
await this.deps.sessionHostControl.stopSession(sessionId);
|
|
24398
|
+
}
|
|
23007
24399
|
stoppedSessionIds.push(sessionId);
|
|
23008
24400
|
} else {
|
|
23009
24401
|
skippedSessionIds.push(sessionId);
|
|
@@ -23020,6 +24412,7 @@ var DaemonCommandRouter = class {
|
|
|
23020
24412
|
continue;
|
|
23021
24413
|
}
|
|
23022
24414
|
if (args.mode === "stop_and_delete") {
|
|
24415
|
+
if (!completed) await markIntentionalStop(sessionId, "delete_session_force");
|
|
23023
24416
|
if (!args.dryRun) await this.deps.sessionHostControl.deleteSession(sessionId, { force: true });
|
|
23024
24417
|
deletedSessionIds.push(sessionId);
|
|
23025
24418
|
continue;
|
|
@@ -23031,6 +24424,7 @@ var DaemonCommandRouter = class {
|
|
|
23031
24424
|
recordsRemainSessionIds.push(sessionId);
|
|
23032
24425
|
if (args.mode === "stop_and_delete" && !completed) {
|
|
23033
24426
|
try {
|
|
24427
|
+
await markIntentionalStop(sessionId, "stop_session");
|
|
23034
24428
|
await this.deps.sessionHostControl.stopSession(sessionId);
|
|
23035
24429
|
stoppedSessionIds.push(sessionId);
|
|
23036
24430
|
} catch (stopError) {
|
|
@@ -23785,6 +25179,91 @@ var DaemonCommandRouter = class {
|
|
|
23785
25179
|
return { success: false, error: e.message };
|
|
23786
25180
|
}
|
|
23787
25181
|
}
|
|
25182
|
+
case "get_mesh_ledger_slice": {
|
|
25183
|
+
const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
|
|
25184
|
+
if (!meshId) return { success: false, error: "meshId required" };
|
|
25185
|
+
try {
|
|
25186
|
+
const { readLedgerSlice: readLedgerSlice2 } = await Promise.resolve().then(() => (init_mesh_ledger(), mesh_ledger_exports));
|
|
25187
|
+
const kind = Array.isArray(args?.kind) ? args.kind.filter((k) => typeof k === "string") : void 0;
|
|
25188
|
+
const slice = readLedgerSlice2(meshId, {
|
|
25189
|
+
afterId: typeof args?.afterId === "string" ? args.afterId : void 0,
|
|
25190
|
+
since: typeof args?.since === "string" ? args.since : void 0,
|
|
25191
|
+
kind,
|
|
25192
|
+
limit: typeof args?.limit === "number" ? args.limit : void 0
|
|
25193
|
+
});
|
|
25194
|
+
return { success: true, slice };
|
|
25195
|
+
} catch (e) {
|
|
25196
|
+
return { success: false, error: e.message };
|
|
25197
|
+
}
|
|
25198
|
+
}
|
|
25199
|
+
case "import_mesh_ledger_slice": {
|
|
25200
|
+
const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
|
|
25201
|
+
if (!meshId) return { success: false, error: "meshId required" };
|
|
25202
|
+
try {
|
|
25203
|
+
const { appendRemoteLedgerEntries: appendRemoteLedgerEntries2, getLedgerSummary: getLedgerSummary2 } = await Promise.resolve().then(() => (init_mesh_ledger(), mesh_ledger_exports));
|
|
25204
|
+
const entries = Array.isArray(args?.entries) ? args.entries : Array.isArray(args?.slice?.entries) ? args.slice.entries : [];
|
|
25205
|
+
const result = appendRemoteLedgerEntries2(meshId, entries);
|
|
25206
|
+
return { success: true, result, summary: getLedgerSummary2(meshId) };
|
|
25207
|
+
} catch (e) {
|
|
25208
|
+
return { success: false, error: e.message };
|
|
25209
|
+
}
|
|
25210
|
+
}
|
|
25211
|
+
case "get_mesh_queue": {
|
|
25212
|
+
const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
|
|
25213
|
+
if (!meshId) return { success: false, error: "meshId required" };
|
|
25214
|
+
try {
|
|
25215
|
+
const { getMeshQueueStats: getMeshQueueStats2, getQueue: getQueue2 } = await Promise.resolve().then(() => (init_mesh_work_queue(), mesh_work_queue_exports));
|
|
25216
|
+
const status = Array.isArray(args?.status) ? args.status.map((s) => typeof s === "string" ? s.trim() : "").filter(Boolean) : void 0;
|
|
25217
|
+
const queue = getQueue2(meshId, { status });
|
|
25218
|
+
const summary = getMeshQueueStats2(meshId);
|
|
25219
|
+
return {
|
|
25220
|
+
success: true,
|
|
25221
|
+
queue,
|
|
25222
|
+
summary,
|
|
25223
|
+
sourceOfTruth: {
|
|
25224
|
+
kind: "mesh_work_queue_file",
|
|
25225
|
+
activeStatuses: ["pending", "assigned"],
|
|
25226
|
+
historicalStatuses: ["completed", "failed", "cancelled"],
|
|
25227
|
+
notes: "pending/assigned are active work; completed/failed/cancelled are historical records."
|
|
25228
|
+
}
|
|
25229
|
+
};
|
|
25230
|
+
} catch (e) {
|
|
25231
|
+
return { success: false, error: e.message };
|
|
25232
|
+
}
|
|
25233
|
+
}
|
|
25234
|
+
case "cancel_mesh_queue_task": {
|
|
25235
|
+
const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
|
|
25236
|
+
const taskId = typeof args?.taskId === "string" ? args.taskId.trim() : "";
|
|
25237
|
+
if (!meshId || !taskId) return { success: false, error: "meshId and taskId required" };
|
|
25238
|
+
try {
|
|
25239
|
+
const { cancelTask: cancelTask2 } = await Promise.resolve().then(() => (init_mesh_work_queue(), mesh_work_queue_exports));
|
|
25240
|
+
const reason = typeof args?.reason === "string" ? args.reason : void 0;
|
|
25241
|
+
const task = cancelTask2(meshId, taskId, { reason });
|
|
25242
|
+
if (!task) return { success: false, error: `Queue task '${taskId}' not found` };
|
|
25243
|
+
return { success: true, task };
|
|
25244
|
+
} catch (e) {
|
|
25245
|
+
return { success: false, error: e.message };
|
|
25246
|
+
}
|
|
25247
|
+
}
|
|
25248
|
+
case "requeue_mesh_queue_task": {
|
|
25249
|
+
const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
|
|
25250
|
+
const taskId = typeof args?.taskId === "string" ? args.taskId.trim() : "";
|
|
25251
|
+
if (!meshId || !taskId) return { success: false, error: "meshId and taskId required" };
|
|
25252
|
+
try {
|
|
25253
|
+
const { requeueTask: requeueTask2 } = await Promise.resolve().then(() => (init_mesh_work_queue(), mesh_work_queue_exports));
|
|
25254
|
+
const task = requeueTask2(meshId, taskId, {
|
|
25255
|
+
reason: typeof args?.reason === "string" ? args.reason : void 0,
|
|
25256
|
+
targetNodeId: typeof args?.targetNodeId === "string" ? args.targetNodeId.trim() : void 0,
|
|
25257
|
+
targetSessionId: typeof args?.targetSessionId === "string" ? args.targetSessionId.trim() : void 0,
|
|
25258
|
+
clearTargetNode: args?.clearTargetNode === true,
|
|
25259
|
+
clearTargetSession: args?.clearTargetSession !== false
|
|
25260
|
+
});
|
|
25261
|
+
if (!task) return { success: false, error: `Queue task '${taskId}' not found` };
|
|
25262
|
+
return { success: true, task };
|
|
25263
|
+
} catch (e) {
|
|
25264
|
+
return { success: false, error: e.message };
|
|
25265
|
+
}
|
|
25266
|
+
}
|
|
23788
25267
|
case "add_mesh_node": {
|
|
23789
25268
|
const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
|
|
23790
25269
|
const workspace = typeof args?.workspace === "string" ? args.workspace.trim() : "";
|
|
@@ -23846,7 +25325,8 @@ var DaemonCommandRouter = class {
|
|
|
23846
25325
|
node,
|
|
23847
25326
|
mode,
|
|
23848
25327
|
sessionIds,
|
|
23849
|
-
dryRun: args?.dryRun === true
|
|
25328
|
+
dryRun: args?.dryRun === true,
|
|
25329
|
+
source: "mesh_cleanup_sessions"
|
|
23850
25330
|
});
|
|
23851
25331
|
return result;
|
|
23852
25332
|
} catch (e) {
|
|
@@ -23876,10 +25356,61 @@ var DaemonCommandRouter = class {
|
|
|
23876
25356
|
if (!branch) return { success: false, error: "Could not determine branch of the worktree node" };
|
|
23877
25357
|
const { stdout: baseBranchStdout } = await execFileAsync3("git", ["branch", "--show-current"], { cwd: repoRoot, encoding: "utf8" });
|
|
23878
25358
|
const baseBranch = baseBranchStdout.trim();
|
|
25359
|
+
const validationSummary = await runMeshRefineValidationGate(mesh, node.workspace);
|
|
25360
|
+
if (validationSummary.status === "failed") {
|
|
25361
|
+
return {
|
|
25362
|
+
success: false,
|
|
25363
|
+
code: "validation_failed",
|
|
25364
|
+
convergenceStatus: "blocked_review",
|
|
25365
|
+
error: "Refinery validation gate failed; merge/refine was not attempted.",
|
|
25366
|
+
branch,
|
|
25367
|
+
into: baseBranch,
|
|
25368
|
+
validationSummary,
|
|
25369
|
+
finalBranchConvergenceState: {
|
|
25370
|
+
branch,
|
|
25371
|
+
baseBranch,
|
|
25372
|
+
merged: false,
|
|
25373
|
+
removed: false,
|
|
25374
|
+
validation: "failed",
|
|
25375
|
+
status: "blocked_review"
|
|
25376
|
+
}
|
|
25377
|
+
};
|
|
25378
|
+
}
|
|
25379
|
+
if (validationSummary.status === "skipped") {
|
|
25380
|
+
return {
|
|
25381
|
+
success: false,
|
|
25382
|
+
code: "validation_unavailable",
|
|
25383
|
+
convergenceStatus: "blocked_review",
|
|
25384
|
+
error: "Refinery validation gate is required but no allowlisted validation command was available; merge/refine was not attempted.",
|
|
25385
|
+
branch,
|
|
25386
|
+
into: baseBranch,
|
|
25387
|
+
validationSummary,
|
|
25388
|
+
finalBranchConvergenceState: {
|
|
25389
|
+
branch,
|
|
25390
|
+
baseBranch,
|
|
25391
|
+
merged: false,
|
|
25392
|
+
removed: false,
|
|
25393
|
+
validation: "unavailable",
|
|
25394
|
+
status: "blocked_review"
|
|
25395
|
+
}
|
|
25396
|
+
};
|
|
25397
|
+
}
|
|
23879
25398
|
try {
|
|
23880
25399
|
await execFileAsync3("git", ["merge", "--no-ff", branch, "-m", `Auto-merge branch '${branch}' via Refinery`], { cwd: repoRoot, encoding: "utf8" });
|
|
23881
25400
|
} catch (e) {
|
|
23882
|
-
return {
|
|
25401
|
+
return {
|
|
25402
|
+
success: false,
|
|
25403
|
+
error: `Merge failed (conflicts?): ${e.message}`,
|
|
25404
|
+
validationSummary,
|
|
25405
|
+
finalBranchConvergenceState: {
|
|
25406
|
+
branch,
|
|
25407
|
+
baseBranch,
|
|
25408
|
+
merged: false,
|
|
25409
|
+
removed: false,
|
|
25410
|
+
validation: "passed",
|
|
25411
|
+
status: "not_mergeable"
|
|
25412
|
+
}
|
|
25413
|
+
};
|
|
23883
25414
|
}
|
|
23884
25415
|
const removeResult = await this.execute("remove_mesh_node", {
|
|
23885
25416
|
meshId,
|
|
@@ -23892,11 +25423,27 @@ var DaemonCommandRouter = class {
|
|
|
23892
25423
|
appendLedgerEntry2(meshId, {
|
|
23893
25424
|
kind: "node_removed",
|
|
23894
25425
|
nodeId,
|
|
23895
|
-
payload: { refined: true, mergedBranch: branch, into: baseBranch }
|
|
25426
|
+
payload: { refined: true, mergedBranch: branch, into: baseBranch, validationSummary }
|
|
23896
25427
|
});
|
|
23897
25428
|
} catch {
|
|
23898
25429
|
}
|
|
23899
|
-
return {
|
|
25430
|
+
return {
|
|
25431
|
+
success: true,
|
|
25432
|
+
merged: true,
|
|
25433
|
+
branch,
|
|
25434
|
+
into: baseBranch,
|
|
25435
|
+
removeResult,
|
|
25436
|
+
validationSummary,
|
|
25437
|
+
finalBranchConvergenceState: {
|
|
25438
|
+
branch: baseBranch,
|
|
25439
|
+
mergedBranch: branch,
|
|
25440
|
+
baseBranch,
|
|
25441
|
+
merged: true,
|
|
25442
|
+
removed: removeResult?.success !== false,
|
|
25443
|
+
validation: "passed",
|
|
25444
|
+
status: removeResult?.success === false ? "merged_cleanup_failed" : "merged"
|
|
25445
|
+
}
|
|
25446
|
+
};
|
|
23900
25447
|
} catch (e) {
|
|
23901
25448
|
return { success: false, error: e.message };
|
|
23902
25449
|
}
|
|
@@ -23914,20 +25461,24 @@ var DaemonCommandRouter = class {
|
|
|
23914
25461
|
);
|
|
23915
25462
|
let sessionCleanup;
|
|
23916
25463
|
if (node && sessionCleanupMode !== "preserve") {
|
|
23917
|
-
sessionCleanup = await this.cleanupMeshSessions({ meshId, nodeId, node, mode: sessionCleanupMode });
|
|
25464
|
+
sessionCleanup = await this.cleanupMeshSessions({ meshId, nodeId, node, mode: sessionCleanupMode, source: "mesh_remove_node" });
|
|
23918
25465
|
if (sessionCleanup.success === false) return { success: false, removed: false, sessionCleanup };
|
|
23919
25466
|
}
|
|
23920
|
-
|
|
23921
|
-
|
|
23922
|
-
|
|
23923
|
-
|
|
23924
|
-
|
|
23925
|
-
|
|
23926
|
-
|
|
23927
|
-
|
|
23928
|
-
|
|
23929
|
-
|
|
25467
|
+
let worktreeCleanup;
|
|
25468
|
+
if (node?.isLocalWorktree) {
|
|
25469
|
+
const cleanupResult = await this.cleanupLocalWorktreeNode({ mesh, node, nodeId });
|
|
25470
|
+
if (cleanupResult.success === false) {
|
|
25471
|
+
return {
|
|
25472
|
+
success: false,
|
|
25473
|
+
removed: false,
|
|
25474
|
+
code: cleanupResult.code,
|
|
25475
|
+
error: cleanupResult.error,
|
|
25476
|
+
recoveryHint: cleanupResult.recoveryHint,
|
|
25477
|
+
...sessionCleanup ? { sessionCleanup } : {},
|
|
25478
|
+
worktreeCleanup: cleanupResult
|
|
25479
|
+
};
|
|
23930
25480
|
}
|
|
25481
|
+
worktreeCleanup = cleanupResult;
|
|
23931
25482
|
}
|
|
23932
25483
|
let removed = false;
|
|
23933
25484
|
if (meshRecord?.inline) {
|
|
@@ -23942,12 +25493,21 @@ var DaemonCommandRouter = class {
|
|
|
23942
25493
|
appendLedgerEntry2(meshId, {
|
|
23943
25494
|
kind: "node_removed",
|
|
23944
25495
|
nodeId,
|
|
23945
|
-
payload: {
|
|
25496
|
+
payload: {
|
|
25497
|
+
worktree: !!node?.isLocalWorktree,
|
|
25498
|
+
sessionCleanupMode,
|
|
25499
|
+
workspace: typeof node?.workspace === "string" ? node.workspace : void 0,
|
|
25500
|
+
daemonId: typeof node?.daemonId === "string" ? node.daemonId : void 0,
|
|
25501
|
+
worktreeBranch: typeof node?.worktreeBranch === "string" ? node.worktreeBranch : void 0,
|
|
25502
|
+
worktreeCleanupFallback: typeof worktreeCleanup?.fallback === "string" ? worktreeCleanup.fallback : void 0,
|
|
25503
|
+
forced: worktreeCleanup?.forced === true ? true : void 0,
|
|
25504
|
+
forceFallbackReason: typeof worktreeCleanup?.reason === "string" ? worktreeCleanup.reason : void 0
|
|
25505
|
+
}
|
|
23946
25506
|
});
|
|
23947
25507
|
} catch {
|
|
23948
25508
|
}
|
|
23949
25509
|
}
|
|
23950
|
-
return { success: true, removed, ...sessionCleanup ? { sessionCleanup } : {} };
|
|
25510
|
+
return { success: true, removed, ...sessionCleanup ? { sessionCleanup } : {}, ...worktreeCleanup ? { worktreeCleanup } : {} };
|
|
23951
25511
|
} catch (e) {
|
|
23952
25512
|
return { success: false, error: e.message };
|
|
23953
25513
|
}
|
|
@@ -23982,6 +25542,7 @@ var DaemonCommandRouter = class {
|
|
|
23982
25542
|
workspace: result.worktreePath,
|
|
23983
25543
|
repoRoot: result.worktreePath,
|
|
23984
25544
|
daemonId: sourceNode.daemonId,
|
|
25545
|
+
machineId: sourceNode.machineId ?? sourceNode.machine_id,
|
|
23985
25546
|
userOverrides: { ...sourceNode.userOverrides || {} },
|
|
23986
25547
|
policy: { ...sourceNode.policy || {} },
|
|
23987
25548
|
isLocalWorktree: true,
|
|
@@ -23995,6 +25556,7 @@ var DaemonCommandRouter = class {
|
|
|
23995
25556
|
workspace: result.worktreePath,
|
|
23996
25557
|
repoRoot: result.worktreePath,
|
|
23997
25558
|
daemonId: sourceNode.daemonId,
|
|
25559
|
+
machineId: sourceNode.machineId ?? sourceNode.machine_id,
|
|
23998
25560
|
userOverrides: { ...sourceNode.userOverrides || {} },
|
|
23999
25561
|
isLocalWorktree: true,
|
|
24000
25562
|
worktreeBranch: result.branch,
|
|
@@ -24113,6 +25675,93 @@ var DaemonCommandRouter = class {
|
|
|
24113
25675
|
meshCoordinatorSetup: coordinatorSetup
|
|
24114
25676
|
};
|
|
24115
25677
|
}
|
|
25678
|
+
if (coordinatorSetup.kind === "cli_command") {
|
|
25679
|
+
let cliCmdSystemPrompt = "";
|
|
25680
|
+
try {
|
|
25681
|
+
cliCmdSystemPrompt = buildCoordinatorSystemPrompt2({ mesh, coordinatorCliType: cliType });
|
|
25682
|
+
} catch (error) {
|
|
25683
|
+
const message = error?.message || String(error);
|
|
25684
|
+
LOG.error("MeshCoordinator", `Failed to build coordinator prompt: ${message}`);
|
|
25685
|
+
return {
|
|
25686
|
+
success: false,
|
|
25687
|
+
code: "mesh_coordinator_prompt_failed",
|
|
25688
|
+
error: `Failed to build Repo Mesh coordinator prompt: ${message}`,
|
|
25689
|
+
meshId,
|
|
25690
|
+
cliType,
|
|
25691
|
+
workspace
|
|
25692
|
+
};
|
|
25693
|
+
}
|
|
25694
|
+
try {
|
|
25695
|
+
const { execFileSync: execCmdSync } = await import("child_process");
|
|
25696
|
+
const cmdParts = coordinatorSetup.command.trim().split(/\s+/);
|
|
25697
|
+
const [regCmd, ...regArgs] = cmdParts;
|
|
25698
|
+
LOG.info("MeshCoordinator", `Running MCP registration: ${coordinatorSetup.command}`);
|
|
25699
|
+
execCmdSync(regCmd, regArgs, { stdio: "pipe", timeout: 15e3 });
|
|
25700
|
+
} catch (error) {
|
|
25701
|
+
LOG.warn("MeshCoordinator", `MCP registration command failed (may be pre-registered): ${error?.message || error}`);
|
|
25702
|
+
}
|
|
25703
|
+
const cliCmdArgs = [];
|
|
25704
|
+
const cliCmdEnv = {};
|
|
25705
|
+
if (cliCmdSystemPrompt) {
|
|
25706
|
+
if (cliType === "codex-cli") {
|
|
25707
|
+
cliCmdArgs.push("-c", `developer_instructions=${JSON.stringify(cliCmdSystemPrompt)}`);
|
|
25708
|
+
} else if (cliType === "gemini-cli") {
|
|
25709
|
+
try {
|
|
25710
|
+
const { writeFileSync: wfs, existsSync: efs, readFileSync: rfs } = await import("fs");
|
|
25711
|
+
const geminiMdPath = `${workspace}/GEMINI.md`;
|
|
25712
|
+
const marker = "<!-- adhdev-mesh-coordinator-prompt -->";
|
|
25713
|
+
const markerEnd = "<!-- /adhdev-mesh-coordinator-prompt -->";
|
|
25714
|
+
const block = `${marker}
|
|
25715
|
+
${cliCmdSystemPrompt}
|
|
25716
|
+
${markerEnd}`;
|
|
25717
|
+
if (efs(geminiMdPath)) {
|
|
25718
|
+
const existing = rfs(geminiMdPath, "utf-8");
|
|
25719
|
+
const replaced = existing.replace(
|
|
25720
|
+
new RegExp(`${marker}[\\s\\S]*?${markerEnd}`, "g"),
|
|
25721
|
+
block
|
|
25722
|
+
);
|
|
25723
|
+
wfs(geminiMdPath, replaced.includes(marker) ? replaced : `${existing}
|
|
25724
|
+
|
|
25725
|
+
${block}`);
|
|
25726
|
+
} else {
|
|
25727
|
+
wfs(geminiMdPath, block);
|
|
25728
|
+
}
|
|
25729
|
+
LOG.info("MeshCoordinator", `Wrote coordinator prompt to ${workspace}/GEMINI.md`);
|
|
25730
|
+
} catch (e) {
|
|
25731
|
+
LOG.warn("MeshCoordinator", `Could not write GEMINI.md: ${e?.message || e}`);
|
|
25732
|
+
}
|
|
25733
|
+
}
|
|
25734
|
+
}
|
|
25735
|
+
const cliCmdLaunch = await this.deps.cliManager.handleCliCommand("launch_cli", {
|
|
25736
|
+
cliType,
|
|
25737
|
+
dir: workspace,
|
|
25738
|
+
cliArgs: cliCmdArgs.length > 0 ? cliCmdArgs : void 0,
|
|
25739
|
+
env: Object.keys(cliCmdEnv).length > 0 ? cliCmdEnv : void 0,
|
|
25740
|
+
settings: { meshCoordinatorFor: meshId }
|
|
25741
|
+
});
|
|
25742
|
+
if (!cliCmdLaunch?.success) {
|
|
25743
|
+
return { success: false, error: cliCmdLaunch?.error || "Failed to launch CLI session" };
|
|
25744
|
+
}
|
|
25745
|
+
LOG.info("MeshCoordinator", `Launched ${cliType} coordinator (cli_command) for mesh ${meshId}`);
|
|
25746
|
+
try {
|
|
25747
|
+
const { appendLedgerEntry: appendLedgerEntry2 } = await Promise.resolve().then(() => (init_mesh_ledger(), mesh_ledger_exports));
|
|
25748
|
+
appendLedgerEntry2(meshId, {
|
|
25749
|
+
kind: "coordinator_started",
|
|
25750
|
+
sessionId: cliCmdLaunch.sessionId || cliCmdLaunch.id,
|
|
25751
|
+
providerType: cliType,
|
|
25752
|
+
payload: { workspace }
|
|
25753
|
+
});
|
|
25754
|
+
} catch {
|
|
25755
|
+
}
|
|
25756
|
+
return {
|
|
25757
|
+
success: true,
|
|
25758
|
+
meshId,
|
|
25759
|
+
cliType,
|
|
25760
|
+
workspace,
|
|
25761
|
+
sessionId: cliCmdLaunch.sessionId || cliCmdLaunch.id,
|
|
25762
|
+
mcpRegistered: true
|
|
25763
|
+
};
|
|
25764
|
+
}
|
|
24116
25765
|
const configFormat = coordinatorSetup.configFormat;
|
|
24117
25766
|
if (configFormat !== "claude_mcp_json" && configFormat !== "hermes_config_yaml") {
|
|
24118
25767
|
return {
|
|
@@ -24167,9 +25816,11 @@ var DaemonCommandRouter = class {
|
|
|
24167
25816
|
args: coordinatorSetup.mcpServer.args
|
|
24168
25817
|
};
|
|
24169
25818
|
if (args?.inlineMesh) {
|
|
25819
|
+
const modeArgIndex = coordinatorSetup.mcpServer.args.findIndex((value) => value === "--mode");
|
|
25820
|
+
const mcpTransport = modeArgIndex >= 0 ? coordinatorSetup.mcpServer.args[modeArgIndex + 1] : "ipc";
|
|
24170
25821
|
mcpServerEntry.env = {
|
|
24171
25822
|
ADHDEV_INLINE_MESH: JSON.stringify(mesh),
|
|
24172
|
-
ADHDEV_MCP_TRANSPORT: "ipc"
|
|
25823
|
+
ADHDEV_MCP_TRANSPORT: mcpTransport === "local" ? "local" : "ipc"
|
|
24173
25824
|
};
|
|
24174
25825
|
}
|
|
24175
25826
|
try {
|
|
@@ -24188,7 +25839,8 @@ var DaemonCommandRouter = class {
|
|
|
24188
25839
|
if (hadExistingMcpConfig) {
|
|
24189
25840
|
try {
|
|
24190
25841
|
const parsedExistingMcpConfig = parseMeshCoordinatorMcpConfig(readFileSync17(mcpConfigPath, "utf-8"), configFormat);
|
|
24191
|
-
|
|
25842
|
+
const existingCoordinatorConfig = hermesManualFallback ? stripHermesCoordinatorTempModelProviderOverrides(parsedExistingMcpConfig) : parsedExistingMcpConfig;
|
|
25843
|
+
existingMcpConfig = { ...existingMcpConfig, ...existingCoordinatorConfig };
|
|
24192
25844
|
copyFileSync4(mcpConfigPath, mcpConfigPath + ".backup");
|
|
24193
25845
|
} catch (error) {
|
|
24194
25846
|
LOG.error("MeshCoordinator", `Failed to parse existing MCP config ${mcpConfigPath}: ${error?.message || error}`);
|
|
@@ -31981,6 +33633,9 @@ function launchIDE(ide, workspacePath) {
|
|
|
31981
33633
|
}
|
|
31982
33634
|
}
|
|
31983
33635
|
|
|
33636
|
+
// src/boot/daemon-lifecycle.ts
|
|
33637
|
+
init_cli_detector();
|
|
33638
|
+
|
|
31984
33639
|
// src/sessions/registry.ts
|
|
31985
33640
|
var SessionRegistry = class {
|
|
31986
33641
|
bySessionId = /* @__PURE__ */ new Map();
|
|
@@ -32211,7 +33866,8 @@ async function initDaemonComponents(config) {
|
|
|
32211
33866
|
cdpManagers,
|
|
32212
33867
|
sessionRegistry,
|
|
32213
33868
|
detectedIdes: detectedIdesRef,
|
|
32214
|
-
refreshProviderAvailability
|
|
33869
|
+
refreshProviderAvailability,
|
|
33870
|
+
dispatchMeshCommand: config.dispatchMeshCommand
|
|
32215
33871
|
};
|
|
32216
33872
|
setupMeshEventForwarding(components);
|
|
32217
33873
|
return components;
|
|
@@ -32313,10 +33969,12 @@ async function shutdownDaemonComponents(components) {
|
|
|
32313
33969
|
IdeProviderInstance,
|
|
32314
33970
|
InMemoryGitSnapshotStore,
|
|
32315
33971
|
LOG,
|
|
33972
|
+
MAX_LEDGER_SLICE_LIMIT,
|
|
32316
33973
|
MIN_GIT_WORKSPACE_POLL_INTERVAL_MS,
|
|
32317
33974
|
MIN_MACHINE_RUNTIME_SUBSCRIPTION_INTERVAL_MS,
|
|
32318
33975
|
MIN_SESSION_HOST_DIAGNOSTICS_SUBSCRIPTION_INTERVAL_MS,
|
|
32319
33976
|
NodePtyTransportFactory,
|
|
33977
|
+
P2pRelayFailureError,
|
|
32320
33978
|
ProviderCliAdapter,
|
|
32321
33979
|
ProviderInstanceManager,
|
|
32322
33980
|
ProviderLoader,
|
|
@@ -32327,12 +33985,16 @@ async function shutdownDaemonComponents(components) {
|
|
|
32327
33985
|
addNode,
|
|
32328
33986
|
appendLedgerEntry,
|
|
32329
33987
|
appendRecentActivity,
|
|
33988
|
+
appendRemoteLedgerEntries,
|
|
32330
33989
|
buildAssistantChatMessage,
|
|
32331
33990
|
buildChatMessage,
|
|
32332
33991
|
buildChatMessageSignature,
|
|
32333
33992
|
buildChatTailDeliverySignature,
|
|
32334
33993
|
buildCoordinatorSystemPrompt,
|
|
32335
33994
|
buildMachineInfo,
|
|
33995
|
+
buildMeshLedgerReconciliationEvidence,
|
|
33996
|
+
buildMeshLedgerReplicaEvidence,
|
|
33997
|
+
buildP2pRelayFailurePayload,
|
|
32336
33998
|
buildPinnedGlobalInstallCommand,
|
|
32337
33999
|
buildRuntimeSystemChatMessage,
|
|
32338
34000
|
buildSessionEntries,
|
|
@@ -32343,9 +34005,11 @@ async function shutdownDaemonComponents(components) {
|
|
|
32343
34005
|
buildThoughtChatMessage,
|
|
32344
34006
|
buildToolChatMessage,
|
|
32345
34007
|
buildUserChatMessage,
|
|
34008
|
+
cancelTask,
|
|
32346
34009
|
claimNextTask,
|
|
32347
34010
|
classifyChatMessageVisibility,
|
|
32348
34011
|
classifyHotChatSessionsForSubscriptionFlush,
|
|
34012
|
+
classifyP2pRelayFailure,
|
|
32349
34013
|
clearDebugTrace,
|
|
32350
34014
|
compareGitSnapshots,
|
|
32351
34015
|
configureDebugTraceStore,
|
|
@@ -32386,6 +34050,7 @@ async function shutdownDaemonComponents(components) {
|
|
|
32386
34050
|
getLogLevel,
|
|
32387
34051
|
getMesh,
|
|
32388
34052
|
getMeshByRepo,
|
|
34053
|
+
getMeshQueueStats,
|
|
32389
34054
|
getNpmExecOptions,
|
|
32390
34055
|
getQueue,
|
|
32391
34056
|
getRecentActivity,
|
|
@@ -32412,6 +34077,7 @@ async function shutdownDaemonComponents(components) {
|
|
|
32412
34077
|
isInternalChatMessage,
|
|
32413
34078
|
isManagedStatusWaiting,
|
|
32414
34079
|
isManagedStatusWorking,
|
|
34080
|
+
isP2pRelayTransportFailure,
|
|
32415
34081
|
isPathInside,
|
|
32416
34082
|
isSessionHostLiveRuntime,
|
|
32417
34083
|
isSessionHostRecoverySnapshot,
|
|
@@ -32450,10 +34116,12 @@ async function shutdownDaemonComponents(components) {
|
|
|
32450
34116
|
probeCdpPort,
|
|
32451
34117
|
readChatHistory,
|
|
32452
34118
|
readLedgerEntries,
|
|
34119
|
+
readLedgerSlice,
|
|
32453
34120
|
recordDebugTrace,
|
|
32454
34121
|
registerExtensionProviders,
|
|
32455
34122
|
removeNode,
|
|
32456
34123
|
removeWorktree,
|
|
34124
|
+
requeueTask,
|
|
32457
34125
|
resetConfig,
|
|
32458
34126
|
resetDebugRuntimeConfig,
|
|
32459
34127
|
resetState,
|