@adhdev/daemon-core 0.9.77-rc.8 → 0.9.77
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 +2 -0
- package/dist/commands/mesh-coordinator.d.ts +10 -0
- package/dist/commands/router.d.ts +4 -1
- package/dist/config/mesh-config.d.ts +1 -0
- package/dist/git/git-worktree.d.ts +15 -2
- package/dist/index.d.ts +10 -6
- package/dist/index.js +2116 -299
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2101 -299
- package/dist/index.mjs.map +1 -1
- package/dist/mesh/mesh-events.d.ts +14 -7
- package/dist/mesh/mesh-ledger-reconciliation.d.ts +55 -0
- package/dist/mesh/mesh-ledger.d.ts +84 -4
- package/dist/mesh/mesh-sync.d.ts +4 -12
- package/dist/mesh/mesh-visualization.d.ts +70 -0
- package/dist/mesh/mesh-work-queue.d.ts +58 -1
- package/dist/mesh/p2p-relay-failure.d.ts +35 -0
- package/dist/providers/chat-message-normalization.d.ts +1 -0
- package/dist/providers/cli-provider-instance.d.ts +6 -0
- package/dist/repo-mesh-types.d.ts +2 -0
- package/dist/shared-types.d.ts +38 -0
- package/package.json +1 -1
- package/src/boot/daemon-lifecycle.ts +5 -0
- package/src/cli-adapters/provider-cli-adapter.ts +30 -5
- package/src/commands/cli-manager.ts +0 -4
- package/src/commands/mesh-coordinator.ts +55 -7
- package/src/commands/router.ts +964 -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 +29 -6
- package/src/mesh/coordinator-prompt.ts +21 -10
- package/src/mesh/mesh-events.ts +532 -22
- package/src/mesh/mesh-ledger-reconciliation.ts +115 -0
- package/src/mesh/mesh-ledger.ts +209 -8
- package/src/mesh/mesh-sync.ts +4 -34
- package/src/mesh/mesh-visualization.ts +341 -0
- package/src/mesh/mesh-work-queue.ts +183 -17
- package/src/mesh/p2p-relay-failure.ts +152 -0
- package/src/providers/acp-provider-instance.ts +2 -1
- package/src/providers/chat-message-normalization.ts +32 -0
- package/src/providers/cli-provider-instance.ts +155 -31
- package/src/providers/extension-provider-instance.ts +2 -1
- package/src/providers/ide-provider-instance.ts +2 -2
- package/src/repo-mesh-types.ts +2 -0
- package/src/shared-types.ts +38 -0
package/dist/index.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,15 +1195,21 @@ 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];
|
|
1030
1209
|
entry.status = "assigned";
|
|
1031
1210
|
entry.assignedNodeId = nodeId;
|
|
1032
1211
|
entry.assignedSessionId = sessionId;
|
|
1212
|
+
entry.dispatchTimestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
1033
1213
|
entry.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1034
1214
|
writeQueue(meshId, queue);
|
|
1035
1215
|
return entry;
|
|
@@ -1043,28 +1223,106 @@ function updateTaskStatus(meshId, taskId, status) {
|
|
|
1043
1223
|
writeQueue(meshId, queue);
|
|
1044
1224
|
return queue[idx];
|
|
1045
1225
|
}
|
|
1226
|
+
function recordTaskAutoLaunch(meshId, taskId, autoLaunch) {
|
|
1227
|
+
const queue = readQueue(meshId);
|
|
1228
|
+
const idx = queue.findIndex((q) => q.id === taskId);
|
|
1229
|
+
if (idx === -1) return null;
|
|
1230
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1231
|
+
queue[idx].autoLaunch = {
|
|
1232
|
+
...autoLaunch,
|
|
1233
|
+
updatedAt: now
|
|
1234
|
+
};
|
|
1235
|
+
queue[idx].updatedAt = now;
|
|
1236
|
+
writeQueue(meshId, queue);
|
|
1237
|
+
return queue[idx];
|
|
1238
|
+
}
|
|
1239
|
+
function cancelTask(meshId, taskId, opts) {
|
|
1240
|
+
const queue = readQueue(meshId);
|
|
1241
|
+
const idx = queue.findIndex((q) => q.id === taskId);
|
|
1242
|
+
if (idx === -1) return null;
|
|
1243
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1244
|
+
queue[idx].status = "cancelled";
|
|
1245
|
+
queue[idx].updatedAt = now;
|
|
1246
|
+
queue[idx].cancelledAt = now;
|
|
1247
|
+
if (opts?.reason) queue[idx].cancelReason = opts.reason;
|
|
1248
|
+
writeQueue(meshId, queue);
|
|
1249
|
+
return queue[idx];
|
|
1250
|
+
}
|
|
1251
|
+
function requeueTask(meshId, taskId, opts) {
|
|
1252
|
+
const queue = readQueue(meshId);
|
|
1253
|
+
const idx = queue.findIndex((q) => q.id === taskId);
|
|
1254
|
+
if (idx === -1) return null;
|
|
1255
|
+
const entry = queue[idx];
|
|
1256
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1257
|
+
entry.status = "pending";
|
|
1258
|
+
delete entry.assignedNodeId;
|
|
1259
|
+
delete entry.assignedSessionId;
|
|
1260
|
+
delete entry.cancelledAt;
|
|
1261
|
+
delete entry.cancelReason;
|
|
1262
|
+
if (opts?.clearTargetNode) delete entry.targetNodeId;
|
|
1263
|
+
if (typeof opts?.targetNodeId === "string") entry.targetNodeId = opts.targetNodeId;
|
|
1264
|
+
if (opts?.clearTargetSession !== false) delete entry.targetSessionId;
|
|
1265
|
+
if (typeof opts?.targetSessionId === "string") entry.targetSessionId = opts.targetSessionId;
|
|
1266
|
+
entry.updatedAt = now;
|
|
1267
|
+
entry.requeuedAt = now;
|
|
1268
|
+
entry.requeueCount = (entry.requeueCount || 0) + 1;
|
|
1269
|
+
if (opts?.reason) entry.requeueReason = opts.reason;
|
|
1270
|
+
writeQueue(meshId, queue);
|
|
1271
|
+
return entry;
|
|
1272
|
+
}
|
|
1046
1273
|
function updateSessionTaskStatus(meshId, sessionId, status) {
|
|
1047
1274
|
const queue = readQueue(meshId);
|
|
1275
|
+
let bestIdx = -1;
|
|
1276
|
+
let bestTime = 0;
|
|
1048
1277
|
for (let i = queue.length - 1; i >= 0; i--) {
|
|
1049
1278
|
if (queue[i].assignedSessionId === sessionId && queue[i].status === "assigned") {
|
|
1050
|
-
queue[i].
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1279
|
+
const time = new Date(queue[i].dispatchTimestamp || queue[i].updatedAt).getTime();
|
|
1280
|
+
if (time > bestTime) {
|
|
1281
|
+
bestTime = time;
|
|
1282
|
+
bestIdx = i;
|
|
1283
|
+
}
|
|
1054
1284
|
}
|
|
1055
1285
|
}
|
|
1056
|
-
return null;
|
|
1286
|
+
if (bestIdx === -1) return null;
|
|
1287
|
+
queue[bestIdx].status = status;
|
|
1288
|
+
queue[bestIdx].updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1289
|
+
writeQueue(meshId, queue);
|
|
1290
|
+
return queue[bestIdx];
|
|
1057
1291
|
}
|
|
1058
1292
|
function getMeshQueueStats(meshId) {
|
|
1059
1293
|
const queue = readQueue(meshId);
|
|
1294
|
+
const pending = queue.filter((q) => q.status === "pending").length;
|
|
1295
|
+
const assigned = queue.filter((q) => q.status === "assigned").length;
|
|
1296
|
+
const completed = queue.filter((q) => q.status === "completed").length;
|
|
1297
|
+
const failed = queue.filter((q) => q.status === "failed").length;
|
|
1298
|
+
const cancelled = queue.filter((q) => q.status === "cancelled").length;
|
|
1060
1299
|
return {
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1300
|
+
total: queue.length,
|
|
1301
|
+
active: pending + assigned,
|
|
1302
|
+
historical: completed + failed + cancelled,
|
|
1303
|
+
pending,
|
|
1304
|
+
assigned,
|
|
1305
|
+
completed,
|
|
1306
|
+
failed,
|
|
1307
|
+
cancelled,
|
|
1308
|
+
activeCounts: {
|
|
1309
|
+
pending,
|
|
1310
|
+
assigned
|
|
1311
|
+
},
|
|
1312
|
+
historicalCounts: {
|
|
1313
|
+
completed,
|
|
1314
|
+
failed,
|
|
1315
|
+
cancelled
|
|
1316
|
+
},
|
|
1317
|
+
activeAssignments: queue.filter((q) => q.status === "assigned").map((q) => ({
|
|
1318
|
+
id: q.id,
|
|
1319
|
+
nodeId: q.assignedNodeId,
|
|
1320
|
+
sessionId: q.assignedSessionId,
|
|
1321
|
+
message: q.message
|
|
1322
|
+
}))
|
|
1065
1323
|
};
|
|
1066
1324
|
}
|
|
1067
|
-
var import_fs4, import_path4, import_crypto5;
|
|
1325
|
+
var import_fs4, import_path4, import_crypto5, ACTIVE_MESH_QUEUE_STATUSES, HISTORICAL_MESH_QUEUE_STATUSES;
|
|
1068
1326
|
var init_mesh_work_queue = __esm({
|
|
1069
1327
|
"src/mesh/mesh-work-queue.ts"() {
|
|
1070
1328
|
"use strict";
|
|
@@ -1072,6 +1330,143 @@ var init_mesh_work_queue = __esm({
|
|
|
1072
1330
|
import_path4 = require("path");
|
|
1073
1331
|
import_crypto5 = require("crypto");
|
|
1074
1332
|
init_mesh_ledger();
|
|
1333
|
+
ACTIVE_MESH_QUEUE_STATUSES = ["pending", "assigned"];
|
|
1334
|
+
HISTORICAL_MESH_QUEUE_STATUSES = ["completed", "failed", "cancelled"];
|
|
1335
|
+
}
|
|
1336
|
+
});
|
|
1337
|
+
|
|
1338
|
+
// src/detection/cli-detector.ts
|
|
1339
|
+
function parseVersion(raw) {
|
|
1340
|
+
const match = raw.match(/v?(\d+\.\d+(?:\.\d+)?(?:-[a-zA-Z0-9.]+)?)/);
|
|
1341
|
+
return match ? match[1] : raw.split("\n")[0].slice(0, 100);
|
|
1342
|
+
}
|
|
1343
|
+
function shellQuote(value) {
|
|
1344
|
+
if (/^[a-zA-Z0-9_./:@%+=,-]+$/.test(value)) return value;
|
|
1345
|
+
return `"${value.replace(/(["\\$`])/g, "\\$1")}"`;
|
|
1346
|
+
}
|
|
1347
|
+
function expandHome(value) {
|
|
1348
|
+
const trimmed = value.trim();
|
|
1349
|
+
if (!trimmed.startsWith("~")) return trimmed;
|
|
1350
|
+
return path8.join(os2.homedir(), trimmed.slice(1));
|
|
1351
|
+
}
|
|
1352
|
+
function isExplicitCommandPath(command) {
|
|
1353
|
+
const trimmed = command.trim();
|
|
1354
|
+
return path8.isAbsolute(trimmed) || trimmed.includes("/") || trimmed.includes("\\") || trimmed.startsWith("~");
|
|
1355
|
+
}
|
|
1356
|
+
function resolveCommandPath(command) {
|
|
1357
|
+
const trimmed = command.trim();
|
|
1358
|
+
if (!trimmed) return null;
|
|
1359
|
+
if (isExplicitCommandPath(trimmed)) {
|
|
1360
|
+
const expanded = expandHome(trimmed);
|
|
1361
|
+
const candidate = path8.isAbsolute(expanded) ? expanded : path8.resolve(expanded);
|
|
1362
|
+
return (0, import_fs5.existsSync)(candidate) ? candidate : null;
|
|
1363
|
+
}
|
|
1364
|
+
return null;
|
|
1365
|
+
}
|
|
1366
|
+
function execAsync(cmd, timeoutMs = 5e3) {
|
|
1367
|
+
return new Promise((resolve16) => {
|
|
1368
|
+
const child = (0, import_child_process.exec)(cmd, {
|
|
1369
|
+
encoding: "utf-8",
|
|
1370
|
+
timeout: timeoutMs,
|
|
1371
|
+
...process.platform === "win32" ? { windowsHide: true } : {}
|
|
1372
|
+
}, (err, stdout) => {
|
|
1373
|
+
if (err || !stdout?.trim()) {
|
|
1374
|
+
resolve16(null);
|
|
1375
|
+
} else {
|
|
1376
|
+
resolve16(stdout.trim());
|
|
1377
|
+
}
|
|
1378
|
+
});
|
|
1379
|
+
child.on("error", () => resolve16(null));
|
|
1380
|
+
});
|
|
1381
|
+
}
|
|
1382
|
+
async function detectCLIs(providerLoader, options) {
|
|
1383
|
+
const platform10 = os2.platform();
|
|
1384
|
+
const whichCmd = platform10 === "win32" ? "where" : "which";
|
|
1385
|
+
const includeVersion = options?.includeVersion !== false;
|
|
1386
|
+
const cliList = providerLoader ? providerLoader.getCliDetectionList() : [];
|
|
1387
|
+
const results = await Promise.all(
|
|
1388
|
+
cliList.map(async (cli) => {
|
|
1389
|
+
try {
|
|
1390
|
+
const explicitPath = resolveCommandPath(cli.command);
|
|
1391
|
+
const pathResult = explicitPath || await execAsync(`${whichCmd} ${shellQuote(cli.command)}`);
|
|
1392
|
+
if (!pathResult) return { ...cli, installed: false };
|
|
1393
|
+
const firstPath = explicitPath || pathResult.split("\n")[0];
|
|
1394
|
+
let version;
|
|
1395
|
+
if (includeVersion) {
|
|
1396
|
+
const versionCommands = [
|
|
1397
|
+
`"${firstPath}" --version`,
|
|
1398
|
+
`"${firstPath}" -V`,
|
|
1399
|
+
`"${firstPath}" -v`,
|
|
1400
|
+
cli.versionCommand
|
|
1401
|
+
].filter((v) => !!v);
|
|
1402
|
+
try {
|
|
1403
|
+
for (const versionCommand of versionCommands) {
|
|
1404
|
+
const versionResult = await execAsync(versionCommand, 3e3);
|
|
1405
|
+
if (versionResult) {
|
|
1406
|
+
version = parseVersion(versionResult);
|
|
1407
|
+
break;
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
} catch {
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
return { ...cli, installed: true, version, path: firstPath };
|
|
1414
|
+
} catch {
|
|
1415
|
+
return { ...cli, installed: false };
|
|
1416
|
+
}
|
|
1417
|
+
})
|
|
1418
|
+
);
|
|
1419
|
+
return results;
|
|
1420
|
+
}
|
|
1421
|
+
async function detectCLI(cliId, providerLoader, options) {
|
|
1422
|
+
const resolvedId = providerLoader ? providerLoader.resolveAlias(cliId) : cliId;
|
|
1423
|
+
if (providerLoader) {
|
|
1424
|
+
const cliList = providerLoader.getCliDetectionList();
|
|
1425
|
+
const target = cliList.find((c) => c.id === resolvedId);
|
|
1426
|
+
if (target) {
|
|
1427
|
+
const platform10 = os2.platform();
|
|
1428
|
+
const whichCmd = platform10 === "win32" ? "where" : "which";
|
|
1429
|
+
try {
|
|
1430
|
+
const explicitPath = resolveCommandPath(target.command);
|
|
1431
|
+
const pathResult = explicitPath || await execAsync(`${whichCmd} ${shellQuote(target.command)}`);
|
|
1432
|
+
if (!pathResult) return null;
|
|
1433
|
+
const firstPath = explicitPath || pathResult.split("\n")[0];
|
|
1434
|
+
let version;
|
|
1435
|
+
if (options?.includeVersion !== false) {
|
|
1436
|
+
const versionCommands = [
|
|
1437
|
+
`"${firstPath}" --version`,
|
|
1438
|
+
`"${firstPath}" -V`,
|
|
1439
|
+
`"${firstPath}" -v`,
|
|
1440
|
+
target.versionCommand
|
|
1441
|
+
].filter((v) => !!v);
|
|
1442
|
+
try {
|
|
1443
|
+
for (const versionCommand of versionCommands) {
|
|
1444
|
+
const versionResult = await execAsync(versionCommand, 3e3);
|
|
1445
|
+
if (versionResult) {
|
|
1446
|
+
version = parseVersion(versionResult);
|
|
1447
|
+
break;
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
} catch {
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
return { ...target, installed: true, version, path: firstPath };
|
|
1454
|
+
} catch {
|
|
1455
|
+
return null;
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
const all = await detectCLIs(providerLoader, options);
|
|
1460
|
+
return all.find((c) => c.id === resolvedId && c.installed) || null;
|
|
1461
|
+
}
|
|
1462
|
+
var import_child_process, os2, path8, import_fs5;
|
|
1463
|
+
var init_cli_detector = __esm({
|
|
1464
|
+
"src/detection/cli-detector.ts"() {
|
|
1465
|
+
"use strict";
|
|
1466
|
+
import_child_process = require("child_process");
|
|
1467
|
+
os2 = __toESM(require("os"));
|
|
1468
|
+
path8 = __toESM(require("path"));
|
|
1469
|
+
import_fs5 = require("fs");
|
|
1075
1470
|
}
|
|
1076
1471
|
});
|
|
1077
1472
|
|
|
@@ -1090,13 +1485,13 @@ function getDaemonLogDir() {
|
|
|
1090
1485
|
return LOG_DIR;
|
|
1091
1486
|
}
|
|
1092
1487
|
function getCurrentDaemonLogPath(date = /* @__PURE__ */ new Date()) {
|
|
1093
|
-
return
|
|
1488
|
+
return path9.join(LOG_DIR, `daemon-${date.toISOString().slice(0, 10)}.log`);
|
|
1094
1489
|
}
|
|
1095
1490
|
function checkDateRotation() {
|
|
1096
1491
|
const today = getDateStr();
|
|
1097
1492
|
if (today !== currentDate) {
|
|
1098
1493
|
currentDate = today;
|
|
1099
|
-
currentLogFile =
|
|
1494
|
+
currentLogFile = path9.join(LOG_DIR, `daemon-${currentDate}.log`);
|
|
1100
1495
|
cleanOldLogs();
|
|
1101
1496
|
}
|
|
1102
1497
|
}
|
|
@@ -1110,7 +1505,7 @@ function cleanOldLogs() {
|
|
|
1110
1505
|
const dateMatch = file.match(/daemon-(\d{4}-\d{2}-\d{2})/);
|
|
1111
1506
|
if (dateMatch && dateMatch[1] < cutoffStr) {
|
|
1112
1507
|
try {
|
|
1113
|
-
fs2.unlinkSync(
|
|
1508
|
+
fs2.unlinkSync(path9.join(LOG_DIR, file));
|
|
1114
1509
|
} catch {
|
|
1115
1510
|
}
|
|
1116
1511
|
}
|
|
@@ -1226,17 +1621,17 @@ function installGlobalInterceptor() {
|
|
|
1226
1621
|
writeToFile(`Log file: ${currentLogFile}`);
|
|
1227
1622
|
writeToFile(`Log level: ${currentLevel}`);
|
|
1228
1623
|
}
|
|
1229
|
-
var fs2,
|
|
1624
|
+
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
1625
|
var init_logger = __esm({
|
|
1231
1626
|
"src/logging/logger.ts"() {
|
|
1232
1627
|
"use strict";
|
|
1233
1628
|
fs2 = __toESM(require("fs"));
|
|
1234
|
-
|
|
1235
|
-
|
|
1629
|
+
path9 = __toESM(require("path"));
|
|
1630
|
+
os3 = __toESM(require("os"));
|
|
1236
1631
|
LEVEL_NUM = { debug: 0, info: 1, warn: 2, error: 3 };
|
|
1237
1632
|
LEVEL_LABEL = { debug: "DBG", info: "INF", warn: "WRN", error: "ERR" };
|
|
1238
1633
|
currentLevel = "info";
|
|
1239
|
-
LOG_DIR = process.platform === "win32" ?
|
|
1634
|
+
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
1635
|
MAX_LOG_SIZE = 5 * 1024 * 1024;
|
|
1241
1636
|
MAX_LOG_DAYS = 7;
|
|
1242
1637
|
try {
|
|
@@ -1244,16 +1639,16 @@ var init_logger = __esm({
|
|
|
1244
1639
|
} catch {
|
|
1245
1640
|
}
|
|
1246
1641
|
currentDate = getDateStr();
|
|
1247
|
-
currentLogFile =
|
|
1642
|
+
currentLogFile = path9.join(LOG_DIR, `daemon-${currentDate}.log`);
|
|
1248
1643
|
cleanOldLogs();
|
|
1249
1644
|
try {
|
|
1250
|
-
const oldLog =
|
|
1645
|
+
const oldLog = path9.join(LOG_DIR, "daemon.log");
|
|
1251
1646
|
if (fs2.existsSync(oldLog)) {
|
|
1252
1647
|
const stat2 = fs2.statSync(oldLog);
|
|
1253
1648
|
const oldDate = stat2.mtime.toISOString().slice(0, 10);
|
|
1254
|
-
fs2.renameSync(oldLog,
|
|
1649
|
+
fs2.renameSync(oldLog, path9.join(LOG_DIR, `daemon-${oldDate}.log`));
|
|
1255
1650
|
}
|
|
1256
|
-
const oldLogBackup =
|
|
1651
|
+
const oldLogBackup = path9.join(LOG_DIR, "daemon.log.old");
|
|
1257
1652
|
if (fs2.existsSync(oldLogBackup)) {
|
|
1258
1653
|
fs2.unlinkSync(oldLogBackup);
|
|
1259
1654
|
}
|
|
@@ -1285,14 +1680,16 @@ var init_logger = __esm({
|
|
|
1285
1680
|
}
|
|
1286
1681
|
};
|
|
1287
1682
|
interceptorInstalled = false;
|
|
1288
|
-
LOG_PATH =
|
|
1683
|
+
LOG_PATH = path9.join(LOG_DIR, `daemon-${getDateStr()}.log`);
|
|
1289
1684
|
}
|
|
1290
1685
|
});
|
|
1291
1686
|
|
|
1292
1687
|
// src/mesh/mesh-events.ts
|
|
1293
1688
|
var mesh_events_exports = {};
|
|
1294
1689
|
__export(mesh_events_exports, {
|
|
1690
|
+
clearPendingMeshCoordinatorEvents: () => clearPendingMeshCoordinatorEvents,
|
|
1295
1691
|
drainPendingMeshCoordinatorEvents: () => drainPendingMeshCoordinatorEvents,
|
|
1692
|
+
getPendingMeshCoordinatorEvents: () => getPendingMeshCoordinatorEvents,
|
|
1296
1693
|
handleMeshForwardEvent: () => handleMeshForwardEvent,
|
|
1297
1694
|
setupMeshEventForwarding: () => setupMeshEventForwarding,
|
|
1298
1695
|
triggerMeshQueue: () => triggerMeshQueue,
|
|
@@ -1301,9 +1698,18 @@ __export(mesh_events_exports, {
|
|
|
1301
1698
|
function drainPendingMeshCoordinatorEvents() {
|
|
1302
1699
|
return pendingMeshCoordinatorEvents.splice(0);
|
|
1303
1700
|
}
|
|
1701
|
+
function getPendingMeshCoordinatorEvents() {
|
|
1702
|
+
return pendingMeshCoordinatorEvents.slice();
|
|
1703
|
+
}
|
|
1704
|
+
function clearPendingMeshCoordinatorEvents() {
|
|
1705
|
+
pendingMeshCoordinatorEvents.splice(0);
|
|
1706
|
+
}
|
|
1304
1707
|
function readNonEmptyString(value) {
|
|
1305
1708
|
return typeof value === "string" && value.trim() ? value.trim() : "";
|
|
1306
1709
|
}
|
|
1710
|
+
function resolveEventSessionId(event, fallback) {
|
|
1711
|
+
return readNonEmptyString(event.targetSessionId) || readNonEmptyString(event.sessionId) || readNonEmptyString(event.instanceId) || readNonEmptyString(fallback);
|
|
1712
|
+
}
|
|
1307
1713
|
function isMeshCoordinatorEvent(eventName) {
|
|
1308
1714
|
return typeof eventName === "string" && MESH_COORDINATOR_EVENTS.has(eventName);
|
|
1309
1715
|
}
|
|
@@ -1315,38 +1721,323 @@ function formatCompletionMetadata(event) {
|
|
|
1315
1721
|
].filter(Boolean);
|
|
1316
1722
|
return parts.length > 0 ? ` (${parts.join("; ")})` : "";
|
|
1317
1723
|
}
|
|
1724
|
+
function getMeshWithCache(components, meshId) {
|
|
1725
|
+
const localMesh = getMesh(meshId);
|
|
1726
|
+
if (localMesh) return localMesh;
|
|
1727
|
+
return components.router?.getCachedInlineMesh(meshId);
|
|
1728
|
+
}
|
|
1729
|
+
function isIntentionalCleanupStopMetadata(event) {
|
|
1730
|
+
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";
|
|
1731
|
+
}
|
|
1732
|
+
function hasRecentIntentionalCleanupStop(meshId, sessionId, nodeId) {
|
|
1733
|
+
if (!sessionId && !nodeId) return false;
|
|
1734
|
+
const cutoff = Date.now() - INTENTIONAL_CLEANUP_STOP_SUPPRESSION_MS;
|
|
1735
|
+
const entries = readLedgerEntries(meshId);
|
|
1736
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
1737
|
+
const entry = entries[i];
|
|
1738
|
+
const timestamp = new Date(entry.timestamp).getTime();
|
|
1739
|
+
if (!Number.isNaN(timestamp) && timestamp < cutoff) break;
|
|
1740
|
+
if (!isIntentionalCleanupStopEntry(entry)) continue;
|
|
1741
|
+
if (sessionId && entry.sessionId === sessionId) return true;
|
|
1742
|
+
if (!sessionId && nodeId && entry.nodeId === nodeId) return true;
|
|
1743
|
+
}
|
|
1744
|
+
return false;
|
|
1745
|
+
}
|
|
1746
|
+
function shouldSuppressIntentionalCleanupStop(args) {
|
|
1747
|
+
if (args.event !== "agent:stopped" && args.event !== "monitor:long_generating") return false;
|
|
1748
|
+
if (isIntentionalCleanupStopMetadata(args.metadataEvent)) return true;
|
|
1749
|
+
return hasRecentIntentionalCleanupStop(args.meshId, args.sessionId, args.nodeId);
|
|
1750
|
+
}
|
|
1318
1751
|
function tryAssignQueueTask(components, meshId, nodeId, sessionId, providerType) {
|
|
1319
1752
|
const task = claimNextTask(meshId, nodeId, sessionId);
|
|
1320
|
-
if (!task)
|
|
1753
|
+
if (!task) {
|
|
1754
|
+
return false;
|
|
1755
|
+
}
|
|
1321
1756
|
LOG.info("MeshQueue", `Node ${nodeId} (${sessionId}) pulled task ${task.id}`);
|
|
1757
|
+
const mesh = getMeshWithCache(components, meshId);
|
|
1758
|
+
const node = mesh?.nodes.find((n) => n.id === nodeId);
|
|
1759
|
+
if (node?.daemonId && components.dispatchMeshCommand) {
|
|
1760
|
+
const isLocalNode = components.cliManager.adapters.has(sessionId);
|
|
1761
|
+
if (!isLocalNode) {
|
|
1762
|
+
components.dispatchMeshCommand(node.daemonId, "agent_command", {
|
|
1763
|
+
targetSessionId: sessionId,
|
|
1764
|
+
cliType: providerType,
|
|
1765
|
+
action: "send_chat",
|
|
1766
|
+
message: task.message
|
|
1767
|
+
}).catch((e) => {
|
|
1768
|
+
LOG.error("MeshQueue", `Failed to dispatch task via P2P to remote node ${nodeId}: ${e?.message}`);
|
|
1769
|
+
updateTaskStatus(meshId, task.id, "failed");
|
|
1770
|
+
});
|
|
1771
|
+
return true;
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1322
1774
|
components.cliManager.handleCliCommand("agent_command", {
|
|
1323
1775
|
targetSessionId: sessionId,
|
|
1324
1776
|
cliType: providerType,
|
|
1325
1777
|
action: "send_chat",
|
|
1326
|
-
|
|
1778
|
+
message: task.message
|
|
1327
1779
|
}).catch((e) => {
|
|
1328
|
-
LOG.error("MeshQueue", `Failed to dispatch task to node ${nodeId}: ${e?.message}`);
|
|
1780
|
+
LOG.error("MeshQueue", `Failed to dispatch task locally to node ${nodeId}: ${e?.message}`);
|
|
1781
|
+
updateTaskStatus(meshId, task.id, "failed");
|
|
1329
1782
|
});
|
|
1330
1783
|
return true;
|
|
1331
1784
|
}
|
|
1332
|
-
function
|
|
1333
|
-
const
|
|
1785
|
+
function normalizeProviderPriority(policy) {
|
|
1786
|
+
const raw = policy && typeof policy === "object" && !Array.isArray(policy) ? policy.providerPriority : void 0;
|
|
1787
|
+
if (!Array.isArray(raw)) return [];
|
|
1788
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1789
|
+
return raw.map((type) => typeof type === "string" ? type.trim() : "").filter(Boolean).filter((type) => {
|
|
1790
|
+
if (seen.has(type)) return false;
|
|
1791
|
+
seen.add(type);
|
|
1792
|
+
return true;
|
|
1793
|
+
});
|
|
1794
|
+
}
|
|
1795
|
+
function isTerminalSessionStatus(status) {
|
|
1796
|
+
return ["stopped", "failed", "terminated", "exited", "closed"].includes(status);
|
|
1797
|
+
}
|
|
1798
|
+
function isIdleSessionState(state) {
|
|
1799
|
+
const status = readNonEmptyString(state?.status).toLowerCase();
|
|
1800
|
+
if (isTerminalSessionStatus(status)) return false;
|
|
1801
|
+
return status === "idle" || state?.activeChat?.status === "waiting_input";
|
|
1802
|
+
}
|
|
1803
|
+
function isDirtyNode(node) {
|
|
1804
|
+
return node?.health === "dirty" || node?.git?.dirty === true;
|
|
1805
|
+
}
|
|
1806
|
+
function isLaunchableNode(node) {
|
|
1807
|
+
if (!node || node.status === "disabled" || node.status === "removed") return false;
|
|
1808
|
+
const health = readNonEmptyString(node.health).toLowerCase();
|
|
1809
|
+
if (!health) return true;
|
|
1810
|
+
return health === "online" || health === "unknown";
|
|
1811
|
+
}
|
|
1812
|
+
function localAutoLaunchSkipReason(node) {
|
|
1813
|
+
const daemonId = readNonEmptyString(node?.daemonId);
|
|
1814
|
+
const machineId = readNonEmptyString(node?.machineId);
|
|
1815
|
+
const appConfig = loadConfig();
|
|
1816
|
+
const localMachineId = readNonEmptyString(appConfig.machineId) || readNonEmptyString(appConfig.registeredMachineId);
|
|
1817
|
+
const cloudDaemonId = localMachineId ? `daemon_${localMachineId}` : "";
|
|
1818
|
+
const standaloneDaemonId = localMachineId ? `standalone_${localMachineId}` : "";
|
|
1819
|
+
const daemonMatchesLocal = !daemonId || daemonId === cloudDaemonId || daemonId === standaloneDaemonId;
|
|
1820
|
+
const machineMatchesLocal = !machineId || localMachineId && machineId === localMachineId;
|
|
1821
|
+
if (node?.isLocalWorktree === true) {
|
|
1822
|
+
return daemonMatchesLocal && machineMatchesLocal ? null : "remote_auto_launch_unsupported";
|
|
1823
|
+
}
|
|
1824
|
+
if (daemonId || machineId) {
|
|
1825
|
+
return daemonMatchesLocal && machineMatchesLocal ? null : "remote_auto_launch_unsupported";
|
|
1826
|
+
}
|
|
1827
|
+
return null;
|
|
1828
|
+
}
|
|
1829
|
+
function activeAssignedCount(meshId) {
|
|
1830
|
+
return getQueue(meshId, { status: ["assigned"] }).length;
|
|
1831
|
+
}
|
|
1832
|
+
function nodeHasActiveAssignment(meshId, nodeId) {
|
|
1833
|
+
return getQueue(meshId, { status: ["assigned"] }).some((task) => task.assignedNodeId === nodeId);
|
|
1834
|
+
}
|
|
1835
|
+
function liveSessionCountForNode(components, meshId, nodeId) {
|
|
1836
|
+
return components.instanceManager.getByCategory("cli").filter((inst) => {
|
|
1837
|
+
const state = inst.getState();
|
|
1838
|
+
const settings = state.settings || {};
|
|
1839
|
+
if (readNonEmptyString(settings.meshNodeFor) !== meshId) return false;
|
|
1840
|
+
const instNodeId = readNonEmptyString(settings.meshNodeId) || readNonEmptyString(settings.nodeId);
|
|
1841
|
+
if (instNodeId !== nodeId) return false;
|
|
1842
|
+
const status = readNonEmptyString(state.status).toLowerCase();
|
|
1843
|
+
return !isTerminalSessionStatus(status);
|
|
1844
|
+
}).length;
|
|
1845
|
+
}
|
|
1846
|
+
function recordAutoLaunchEvent(meshId, args) {
|
|
1847
|
+
try {
|
|
1848
|
+
appendLedgerEntry(meshId, {
|
|
1849
|
+
kind: "session_auto_launch",
|
|
1850
|
+
nodeId: args.nodeId,
|
|
1851
|
+
sessionId: args.sessionId,
|
|
1852
|
+
providerType: args.providerType,
|
|
1853
|
+
payload: {
|
|
1854
|
+
phase: args.phase,
|
|
1855
|
+
taskId: args.taskId,
|
|
1856
|
+
reason: args.reason,
|
|
1857
|
+
error: args.error
|
|
1858
|
+
}
|
|
1859
|
+
});
|
|
1860
|
+
} catch (e) {
|
|
1861
|
+
LOG.warn("MeshQueue", `Failed to record auto-launch ledger event: ${e?.message || e}`);
|
|
1862
|
+
}
|
|
1863
|
+
}
|
|
1864
|
+
function markAutoLaunch(meshId, taskId, args) {
|
|
1865
|
+
recordTaskAutoLaunch(meshId, taskId, {
|
|
1866
|
+
status: args.status,
|
|
1867
|
+
reason: args.reason || args.error,
|
|
1868
|
+
nodeId: args.nodeId,
|
|
1869
|
+
providerType: args.providerType,
|
|
1870
|
+
sessionId: args.sessionId
|
|
1871
|
+
});
|
|
1872
|
+
recordAutoLaunchEvent(meshId, {
|
|
1873
|
+
phase: args.status,
|
|
1874
|
+
taskId,
|
|
1875
|
+
nodeId: args.nodeId,
|
|
1876
|
+
providerType: args.providerType,
|
|
1877
|
+
sessionId: args.sessionId,
|
|
1878
|
+
reason: args.reason,
|
|
1879
|
+
error: args.error
|
|
1880
|
+
});
|
|
1881
|
+
}
|
|
1882
|
+
async function resolveUsableProvider(components, nodeId, node) {
|
|
1883
|
+
const providerPriority = normalizeProviderPriority(node?.policy);
|
|
1884
|
+
if (!providerPriority.length) return { reason: "missing_provider_priority" };
|
|
1885
|
+
const providerLoader = components.providerLoader;
|
|
1886
|
+
if (!providerLoader) return { reason: "provider_loader_unavailable" };
|
|
1887
|
+
const failed = [];
|
|
1888
|
+
for (const requestedType of providerPriority) {
|
|
1889
|
+
const normalizedType = typeof providerLoader.resolveAlias === "function" ? providerLoader.resolveAlias(requestedType) : requestedType;
|
|
1890
|
+
if (typeof providerLoader.isMachineProviderEnabled === "function" && !providerLoader.isMachineProviderEnabled(normalizedType)) {
|
|
1891
|
+
failed.push(`${requestedType}: disabled`);
|
|
1892
|
+
continue;
|
|
1893
|
+
}
|
|
1894
|
+
let detected;
|
|
1895
|
+
try {
|
|
1896
|
+
detected = await detectCLI(normalizedType, providerLoader, { includeVersion: false });
|
|
1897
|
+
} catch (e) {
|
|
1898
|
+
failed.push(`${requestedType}: detect failed: ${e?.message || e}`);
|
|
1899
|
+
continue;
|
|
1900
|
+
}
|
|
1901
|
+
if (typeof providerLoader.setCliDetectionResults === "function") {
|
|
1902
|
+
providerLoader.setCliDetectionResults([{
|
|
1903
|
+
id: normalizedType,
|
|
1904
|
+
installed: !!detected,
|
|
1905
|
+
path: detected?.path
|
|
1906
|
+
}], false);
|
|
1907
|
+
}
|
|
1908
|
+
components.onStatusChange?.();
|
|
1909
|
+
if (detected) return { providerType: normalizedType };
|
|
1910
|
+
failed.push(`${requestedType}: not detected`);
|
|
1911
|
+
}
|
|
1912
|
+
return { reason: `provider_priority_unusable: ${failed.join("; ") || nodeId}` };
|
|
1913
|
+
}
|
|
1914
|
+
async function maybeAutoLaunchOneQueueSession(components, meshId, mesh) {
|
|
1915
|
+
const queue = getQueue(meshId);
|
|
1916
|
+
const pending = queue.filter((task) => task.status === "pending");
|
|
1917
|
+
if (!pending.length) return false;
|
|
1918
|
+
const maxParallelTasks = Math.max(1, Math.floor(Number(mesh?.policy?.maxParallelTasks) || 2));
|
|
1919
|
+
for (const task of pending) {
|
|
1920
|
+
if (activeAssignedCount(meshId) >= maxParallelTasks) {
|
|
1921
|
+
markAutoLaunch(meshId, task.id, { status: "skipped", reason: "max_parallel_tasks_reached" });
|
|
1922
|
+
return false;
|
|
1923
|
+
}
|
|
1924
|
+
if (task.targetSessionId) {
|
|
1925
|
+
markAutoLaunch(meshId, task.id, { status: "skipped", reason: "target_session_constraint" });
|
|
1926
|
+
continue;
|
|
1927
|
+
}
|
|
1928
|
+
const candidateNodes = Array.isArray(mesh?.nodes) ? mesh.nodes.filter((node) => task.targetNodeId ? node?.id === task.targetNodeId : true) : [];
|
|
1929
|
+
if (!candidateNodes.length) {
|
|
1930
|
+
markAutoLaunch(meshId, task.id, { status: "skipped", reason: "no_matching_node", nodeId: task.targetNodeId });
|
|
1931
|
+
continue;
|
|
1932
|
+
}
|
|
1933
|
+
for (const node of candidateNodes) {
|
|
1934
|
+
const nodeId = readNonEmptyString(node?.id);
|
|
1935
|
+
if (!nodeId) continue;
|
|
1936
|
+
const launchKey = `${meshId}:${nodeId}`;
|
|
1937
|
+
const cooldownUntil = autoLaunchCooldownUntil.get(launchKey) || 0;
|
|
1938
|
+
if (autoLaunchInProgress.has(launchKey)) {
|
|
1939
|
+
markAutoLaunch(meshId, task.id, { status: "skipped", reason: "auto_launch_in_progress", nodeId });
|
|
1940
|
+
continue;
|
|
1941
|
+
}
|
|
1942
|
+
if (Date.now() < cooldownUntil) {
|
|
1943
|
+
markAutoLaunch(meshId, task.id, { status: "skipped", reason: "auto_launch_cooldown", nodeId });
|
|
1944
|
+
continue;
|
|
1945
|
+
}
|
|
1946
|
+
if (isDirtyNode(node)) {
|
|
1947
|
+
markAutoLaunch(meshId, task.id, { status: "skipped", reason: "dirty_workspace", nodeId });
|
|
1948
|
+
continue;
|
|
1949
|
+
}
|
|
1950
|
+
if (!isLaunchableNode(node)) {
|
|
1951
|
+
markAutoLaunch(meshId, task.id, { status: "skipped", reason: "node_not_launch_ready", nodeId });
|
|
1952
|
+
continue;
|
|
1953
|
+
}
|
|
1954
|
+
const localSkipReason = localAutoLaunchSkipReason(node);
|
|
1955
|
+
if (localSkipReason) {
|
|
1956
|
+
markAutoLaunch(meshId, task.id, { status: "skipped", reason: localSkipReason, nodeId });
|
|
1957
|
+
continue;
|
|
1958
|
+
}
|
|
1959
|
+
if (nodeHasActiveAssignment(meshId, nodeId)) {
|
|
1960
|
+
markAutoLaunch(meshId, task.id, { status: "skipped", reason: "node_has_active_assignment", nodeId });
|
|
1961
|
+
continue;
|
|
1962
|
+
}
|
|
1963
|
+
const maxConcurrentSessions = Number(node?.policy?.maxConcurrentSessions);
|
|
1964
|
+
if (Number.isFinite(maxConcurrentSessions) && maxConcurrentSessions >= 0 && liveSessionCountForNode(components, meshId, nodeId) >= maxConcurrentSessions) {
|
|
1965
|
+
markAutoLaunch(meshId, task.id, { status: "skipped", reason: "max_concurrent_sessions_reached", nodeId });
|
|
1966
|
+
continue;
|
|
1967
|
+
}
|
|
1968
|
+
autoLaunchInProgress.add(launchKey);
|
|
1969
|
+
try {
|
|
1970
|
+
const resolved = await resolveUsableProvider(components, nodeId, node);
|
|
1971
|
+
if (!resolved.providerType) {
|
|
1972
|
+
markAutoLaunch(meshId, task.id, { status: "skipped", reason: resolved.reason || "provider_unusable", nodeId });
|
|
1973
|
+
continue;
|
|
1974
|
+
}
|
|
1975
|
+
markAutoLaunch(meshId, task.id, { status: "started", nodeId, providerType: resolved.providerType });
|
|
1976
|
+
const launchResult = await components.cliManager.handleCliCommand("launch_cli", {
|
|
1977
|
+
cliType: resolved.providerType,
|
|
1978
|
+
dir: node.workspace,
|
|
1979
|
+
settings: {
|
|
1980
|
+
meshNodeFor: meshId,
|
|
1981
|
+
meshNodeId: nodeId,
|
|
1982
|
+
spawnedSessionVisibility: mesh?.policy?.spawnedSessionVisibility || "hidden",
|
|
1983
|
+
launchedByCoordinator: true,
|
|
1984
|
+
autoLaunchedForQueueTaskId: task.id
|
|
1985
|
+
}
|
|
1986
|
+
});
|
|
1987
|
+
if (!launchResult?.success) {
|
|
1988
|
+
const reason = launchResult?.error || "launch_cli_failed";
|
|
1989
|
+
markAutoLaunch(meshId, task.id, { status: "failed", reason, nodeId, providerType: resolved.providerType });
|
|
1990
|
+
autoLaunchCooldownUntil.set(launchKey, Date.now() + AUTO_LAUNCH_COOLDOWN_MS);
|
|
1991
|
+
return false;
|
|
1992
|
+
}
|
|
1993
|
+
const sessionId = readNonEmptyString(launchResult.sessionId) || readNonEmptyString(launchResult.id) || readNonEmptyString(launchResult.runtimeSessionId);
|
|
1994
|
+
if (!sessionId) {
|
|
1995
|
+
markAutoLaunch(meshId, task.id, { status: "failed", reason: "launch_missing_session_id", nodeId, providerType: resolved.providerType });
|
|
1996
|
+
autoLaunchCooldownUntil.set(launchKey, Date.now() + AUTO_LAUNCH_COOLDOWN_MS);
|
|
1997
|
+
return false;
|
|
1998
|
+
}
|
|
1999
|
+
markAutoLaunch(meshId, task.id, { status: "completed", nodeId, providerType: resolved.providerType, sessionId });
|
|
2000
|
+
tryAssignQueueTask(components, meshId, nodeId, sessionId, resolved.providerType);
|
|
2001
|
+
return true;
|
|
2002
|
+
} catch (e) {
|
|
2003
|
+
markAutoLaunch(meshId, task.id, { status: "failed", error: e?.message || String(e), nodeId });
|
|
2004
|
+
autoLaunchCooldownUntil.set(launchKey, Date.now() + AUTO_LAUNCH_COOLDOWN_MS);
|
|
2005
|
+
return false;
|
|
2006
|
+
} finally {
|
|
2007
|
+
autoLaunchInProgress.delete(launchKey);
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
2010
|
+
}
|
|
2011
|
+
return false;
|
|
2012
|
+
}
|
|
2013
|
+
async function triggerMeshQueue(components, meshId) {
|
|
2014
|
+
const mesh = getMeshWithCache(components, meshId);
|
|
1334
2015
|
if (!mesh) return;
|
|
1335
2016
|
const cliInstances = components.instanceManager.getByCategory("cli");
|
|
1336
2017
|
for (const inst of cliInstances) {
|
|
1337
2018
|
const state = inst.getState();
|
|
1338
2019
|
const settings = state.settings || {};
|
|
1339
2020
|
const instMeshId = readNonEmptyString(settings.meshNodeFor);
|
|
1340
|
-
if (instMeshId !== meshId
|
|
2021
|
+
if (instMeshId !== meshId) continue;
|
|
1341
2022
|
const nodeId = readNonEmptyString(settings.meshNodeId) || readNonEmptyString(settings.nodeId);
|
|
1342
2023
|
if (!nodeId) continue;
|
|
1343
|
-
if (state
|
|
2024
|
+
if (!isIdleSessionState(state)) continue;
|
|
1344
2025
|
const sessionId = state.instanceId;
|
|
1345
2026
|
const providerType = state.type || readNonEmptyString(settings.providerType);
|
|
1346
2027
|
if (providerType) {
|
|
1347
2028
|
tryAssignQueueTask(components, meshId, nodeId, sessionId, providerType);
|
|
1348
2029
|
}
|
|
1349
2030
|
}
|
|
2031
|
+
for (const [key, idle] of remoteIdleSessions.entries()) {
|
|
2032
|
+
const node = mesh.nodes.find((n) => n.id === idle.nodeId);
|
|
2033
|
+
if (node) {
|
|
2034
|
+
const assigned = tryAssignQueueTask(components, meshId, idle.nodeId, idle.sessionId, idle.providerType);
|
|
2035
|
+
if (assigned) {
|
|
2036
|
+
remoteIdleSessions.delete(key);
|
|
2037
|
+
}
|
|
2038
|
+
}
|
|
2039
|
+
}
|
|
2040
|
+
await maybeAutoLaunchOneQueueSession(components, meshId, mesh);
|
|
1350
2041
|
}
|
|
1351
2042
|
function buildMeshSystemMessage(args) {
|
|
1352
2043
|
const metadata = formatCompletionMetadata(args.metadataEvent);
|
|
@@ -1393,20 +2084,91 @@ Do NOT retry on this node. Consider reassigning to a different node or asking th
|
|
|
1393
2084
|
return "";
|
|
1394
2085
|
}
|
|
1395
2086
|
function injectMeshSystemMessage(components, args) {
|
|
2087
|
+
const eventSessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId);
|
|
2088
|
+
const eventNodeId = readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId);
|
|
2089
|
+
const intentionalCleanupStop = shouldSuppressIntentionalCleanupStop({
|
|
2090
|
+
event: args.event,
|
|
2091
|
+
meshId: args.meshId,
|
|
2092
|
+
metadataEvent: args.metadataEvent,
|
|
2093
|
+
sessionId: eventSessionId || void 0,
|
|
2094
|
+
nodeId: eventNodeId || void 0
|
|
2095
|
+
});
|
|
2096
|
+
if (intentionalCleanupStop) {
|
|
2097
|
+
if (eventSessionId && eventNodeId) {
|
|
2098
|
+
remoteIdleSessions.delete(`${eventNodeId}:${eventSessionId}`);
|
|
2099
|
+
}
|
|
2100
|
+
LOG.info("MeshEvents", `Suppressed ${args.event} for intentionally cleanup-stopped session ${eventSessionId || "(unknown session)"}`);
|
|
2101
|
+
return { success: true, forwarded: 0, suppressed: true, intentionalCleanupStop: true };
|
|
2102
|
+
}
|
|
2103
|
+
let completedTaskForLedger = null;
|
|
1396
2104
|
if (args.event === "agent:generating_completed") {
|
|
1397
|
-
const sessionId =
|
|
1398
|
-
const nodeId = readNonEmptyString(args.
|
|
2105
|
+
const sessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId);
|
|
2106
|
+
const nodeId = readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId);
|
|
1399
2107
|
const providerType = readNonEmptyString(args.metadataEvent.providerType);
|
|
1400
2108
|
if (sessionId) {
|
|
1401
|
-
updateSessionTaskStatus(args.meshId, sessionId, "completed");
|
|
2109
|
+
const completedTask = updateSessionTaskStatus(args.meshId, sessionId, "completed");
|
|
2110
|
+
completedTaskForLedger = completedTask ? { id: completedTask.id } : null;
|
|
1402
2111
|
if (nodeId && providerType) {
|
|
1403
2112
|
setTimeout(() => {
|
|
1404
2113
|
tryAssignQueueTask(components, args.meshId, nodeId, sessionId, providerType);
|
|
1405
2114
|
}, 500);
|
|
1406
2115
|
}
|
|
1407
2116
|
}
|
|
2117
|
+
} else if (args.event === "agent:ready") {
|
|
2118
|
+
const sessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId);
|
|
2119
|
+
const nodeId = readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId);
|
|
2120
|
+
const providerType = readNonEmptyString(args.metadataEvent.providerType);
|
|
2121
|
+
const completedTask = sessionId ? updateSessionTaskStatus(args.meshId, sessionId, "completed") : null;
|
|
2122
|
+
if (completedTask) {
|
|
2123
|
+
completedTaskForLedger = { id: completedTask.id };
|
|
2124
|
+
try {
|
|
2125
|
+
appendLedgerEntry(args.meshId, {
|
|
2126
|
+
kind: "task_completed",
|
|
2127
|
+
nodeId: nodeId || void 0,
|
|
2128
|
+
sessionId,
|
|
2129
|
+
providerType: providerType || void 0,
|
|
2130
|
+
payload: {
|
|
2131
|
+
event: args.event,
|
|
2132
|
+
nodeLabel: args.nodeLabel,
|
|
2133
|
+
taskId: completedTask.id,
|
|
2134
|
+
completedViaReady: true,
|
|
2135
|
+
providerSessionId: readNonEmptyString(args.metadataEvent.providerSessionId) || void 0,
|
|
2136
|
+
finalSummary: readNonEmptyString(args.metadataEvent.finalSummary) || void 0,
|
|
2137
|
+
evidence: buildTaskCompletionEvidence({
|
|
2138
|
+
event: "agent:ready",
|
|
2139
|
+
nodeId,
|
|
2140
|
+
sessionId,
|
|
2141
|
+
providerType: providerType || void 0,
|
|
2142
|
+
providerSessionId: readNonEmptyString(args.metadataEvent.providerSessionId) || void 0,
|
|
2143
|
+
finalSummary: readNonEmptyString(args.metadataEvent.finalSummary) || void 0
|
|
2144
|
+
})
|
|
2145
|
+
}
|
|
2146
|
+
});
|
|
2147
|
+
} catch (e) {
|
|
2148
|
+
LOG.warn("MeshLedger", `Failed to record task_completed from ready: ${e?.message || e}`);
|
|
2149
|
+
}
|
|
2150
|
+
}
|
|
2151
|
+
if (sessionId && nodeId && providerType) {
|
|
2152
|
+
remoteIdleSessions.set(`${nodeId}:${sessionId}`, { nodeId, sessionId, providerType });
|
|
2153
|
+
setTimeout(() => {
|
|
2154
|
+
const assigned = tryAssignQueueTask(components, args.meshId, nodeId, sessionId, providerType);
|
|
2155
|
+
if (assigned) {
|
|
2156
|
+
remoteIdleSessions.delete(`${nodeId}:${sessionId}`);
|
|
2157
|
+
}
|
|
2158
|
+
}, 500);
|
|
2159
|
+
}
|
|
2160
|
+
} else if (args.event === "agent:generating_started") {
|
|
2161
|
+
const sessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId);
|
|
2162
|
+
const nodeId = readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId);
|
|
2163
|
+
if (sessionId && nodeId) {
|
|
2164
|
+
remoteIdleSessions.delete(`${nodeId}:${sessionId}`);
|
|
2165
|
+
}
|
|
1408
2166
|
} else if (args.event === "agent:stopped") {
|
|
1409
|
-
const sessionId =
|
|
2167
|
+
const sessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId);
|
|
2168
|
+
const nodeId = readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId);
|
|
2169
|
+
if (sessionId && nodeId) {
|
|
2170
|
+
remoteIdleSessions.delete(`${nodeId}:${sessionId}`);
|
|
2171
|
+
}
|
|
1410
2172
|
if (sessionId) {
|
|
1411
2173
|
updateSessionTaskStatus(args.meshId, sessionId, "failed");
|
|
1412
2174
|
}
|
|
@@ -1414,15 +2176,29 @@ function injectMeshSystemMessage(components, args) {
|
|
|
1414
2176
|
const ledgerKind = EVENT_TO_LEDGER_KIND[args.event];
|
|
1415
2177
|
if (ledgerKind) {
|
|
1416
2178
|
try {
|
|
2179
|
+
const ledgerNodeId = readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId) || void 0;
|
|
2180
|
+
const ledgerSessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId) || void 0;
|
|
2181
|
+
const ledgerProviderType = readNonEmptyString(args.metadataEvent.providerType) || void 0;
|
|
2182
|
+
const completionEvidence = ledgerKind === "task_completed" && ledgerNodeId && ledgerSessionId ? buildTaskCompletionEvidence({
|
|
2183
|
+
event: "agent:generating_completed",
|
|
2184
|
+
nodeId: ledgerNodeId,
|
|
2185
|
+
sessionId: ledgerSessionId,
|
|
2186
|
+
providerType: ledgerProviderType,
|
|
2187
|
+
providerSessionId: readNonEmptyString(args.metadataEvent.providerSessionId) || void 0,
|
|
2188
|
+
finalSummary: readNonEmptyString(args.metadataEvent.finalSummary) || void 0
|
|
2189
|
+
}) : void 0;
|
|
1417
2190
|
appendLedgerEntry(args.meshId, {
|
|
1418
2191
|
kind: ledgerKind,
|
|
1419
|
-
nodeId:
|
|
1420
|
-
sessionId:
|
|
1421
|
-
providerType:
|
|
2192
|
+
nodeId: ledgerNodeId,
|
|
2193
|
+
sessionId: ledgerSessionId,
|
|
2194
|
+
providerType: ledgerProviderType,
|
|
1422
2195
|
payload: {
|
|
1423
2196
|
event: args.event,
|
|
1424
2197
|
nodeLabel: args.nodeLabel,
|
|
1425
|
-
|
|
2198
|
+
taskId: completedTaskForLedger?.id || void 0,
|
|
2199
|
+
providerSessionId: readNonEmptyString(args.metadataEvent.providerSessionId) || void 0,
|
|
2200
|
+
finalSummary: readNonEmptyString(args.metadataEvent.finalSummary) || void 0,
|
|
2201
|
+
evidence: completionEvidence
|
|
1426
2202
|
}
|
|
1427
2203
|
});
|
|
1428
2204
|
} catch (e) {
|
|
@@ -1435,8 +2211,8 @@ function injectMeshSystemMessage(components, args) {
|
|
|
1435
2211
|
const mesh = getMesh(args.meshId);
|
|
1436
2212
|
const maxRetries = mesh?.policy?.maxTaskRetries ?? 1;
|
|
1437
2213
|
recoveryContext = getSessionRecoveryContext(args.meshId, {
|
|
1438
|
-
sessionId:
|
|
1439
|
-
nodeId: readNonEmptyString(args.
|
|
2214
|
+
sessionId: resolveEventSessionId(args.metadataEvent, args.sourceInstanceId) || void 0,
|
|
2215
|
+
nodeId: readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId) || void 0,
|
|
1440
2216
|
maxRetries
|
|
1441
2217
|
});
|
|
1442
2218
|
recoveryContext.failedProviderType = readNonEmptyString(args.metadataEvent.providerType) || null;
|
|
@@ -1531,12 +2307,21 @@ function handleMeshForwardEvent(components, payload) {
|
|
|
1531
2307
|
const nodeLabel = nodeId ? `Node '${nodeId}'` : workspace ? `Agent at ${workspace}` : "Remote agent";
|
|
1532
2308
|
return injectMeshSystemMessage(components, {
|
|
1533
2309
|
meshId,
|
|
2310
|
+
nodeId,
|
|
1534
2311
|
nodeLabel,
|
|
1535
2312
|
event: eventName,
|
|
1536
2313
|
metadataEvent: {
|
|
1537
|
-
targetSessionId: readNonEmptyString(payload.targetSessionId) || readNonEmptyString(payload.sessionId),
|
|
2314
|
+
targetSessionId: readNonEmptyString(payload.targetSessionId) || readNonEmptyString(payload.sessionId) || readNonEmptyString(payload.instanceId),
|
|
1538
2315
|
providerType: readNonEmptyString(payload.providerType),
|
|
1539
|
-
providerSessionId: readNonEmptyString(payload.providerSessionId)
|
|
2316
|
+
providerSessionId: readNonEmptyString(payload.providerSessionId),
|
|
2317
|
+
finalSummary: readNonEmptyString(payload.finalSummary) || readNonEmptyString(payload.summary),
|
|
2318
|
+
intentional: payload.intentional === true,
|
|
2319
|
+
intentionalStop: payload.intentionalStop === true,
|
|
2320
|
+
operatorCleanup: payload.operatorCleanup === true,
|
|
2321
|
+
reason: readNonEmptyString(payload.reason),
|
|
2322
|
+
stopReason: readNonEmptyString(payload.stopReason),
|
|
2323
|
+
cleanupReason: readNonEmptyString(payload.cleanupReason),
|
|
2324
|
+
source: readNonEmptyString(payload.source)
|
|
1540
2325
|
}
|
|
1541
2326
|
});
|
|
1542
2327
|
}
|
|
@@ -1555,35 +2340,42 @@ function setupMeshEventForwarding(components) {
|
|
|
1555
2340
|
const meshIdFromRuntime = readNonEmptyString(settings.meshNodeFor);
|
|
1556
2341
|
const isMeshDelegate = Boolean(meshIdFromRuntime || settings.launchedByCoordinator);
|
|
1557
2342
|
if (!isMeshDelegate) return;
|
|
1558
|
-
const mesh = meshIdFromRuntime ?
|
|
2343
|
+
const mesh = meshIdFromRuntime ? getMeshWithCache(components, meshIdFromRuntime) : getMeshByRepo(workspace);
|
|
1559
2344
|
const meshId = meshIdFromRuntime || readNonEmptyString(mesh?.id);
|
|
1560
2345
|
if (!meshId) return;
|
|
1561
2346
|
const targetNode = mesh?.nodes?.find((n) => n.workspace === workspace);
|
|
1562
2347
|
const runtimeNodeId = readNonEmptyString(settings.meshNodeId);
|
|
2348
|
+
const resolvedNodeId = targetNode?.id || runtimeNodeId;
|
|
1563
2349
|
const nodeLabel = targetNode ? `Node '${targetNode.id}'` : runtimeNodeId ? `Node '${runtimeNodeId}'` : `Agent at ${workspace}`;
|
|
1564
2350
|
injectMeshSystemMessage(components, {
|
|
1565
2351
|
meshId,
|
|
1566
2352
|
sourceInstanceId: instanceId,
|
|
2353
|
+
nodeId: resolvedNodeId,
|
|
1567
2354
|
nodeLabel,
|
|
1568
2355
|
event: event.event,
|
|
1569
2356
|
metadataEvent: event
|
|
1570
2357
|
});
|
|
1571
2358
|
});
|
|
1572
2359
|
}
|
|
1573
|
-
var MAX_PENDING_EVENTS, pendingMeshCoordinatorEvents, MESH_COORDINATOR_EVENTS, EVENT_TO_LEDGER_KIND;
|
|
2360
|
+
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
2361
|
var init_mesh_events = __esm({
|
|
1575
2362
|
"src/mesh/mesh-events.ts"() {
|
|
1576
2363
|
"use strict";
|
|
2364
|
+
init_config();
|
|
1577
2365
|
init_mesh_config();
|
|
2366
|
+
init_cli_detector();
|
|
1578
2367
|
init_logger();
|
|
1579
2368
|
init_mesh_ledger();
|
|
1580
2369
|
init_mesh_work_queue();
|
|
2370
|
+
remoteIdleSessions = /* @__PURE__ */ new Map();
|
|
1581
2371
|
MAX_PENDING_EVENTS = 50;
|
|
1582
2372
|
pendingMeshCoordinatorEvents = [];
|
|
1583
2373
|
MESH_COORDINATOR_EVENTS = /* @__PURE__ */ new Set([
|
|
2374
|
+
"agent:generating_started",
|
|
1584
2375
|
"agent:generating_completed",
|
|
1585
2376
|
"agent:waiting_approval",
|
|
1586
2377
|
"agent:stopped",
|
|
2378
|
+
"agent:ready",
|
|
1587
2379
|
"monitor:long_generating"
|
|
1588
2380
|
]);
|
|
1589
2381
|
EVENT_TO_LEDGER_KIND = {
|
|
@@ -1592,6 +2384,10 @@ var init_mesh_events = __esm({
|
|
|
1592
2384
|
"agent:stopped": "task_failed",
|
|
1593
2385
|
"monitor:long_generating": "task_stalled"
|
|
1594
2386
|
};
|
|
2387
|
+
INTENTIONAL_CLEANUP_STOP_SUPPRESSION_MS = 30 * 60 * 1e3;
|
|
2388
|
+
autoLaunchInProgress = /* @__PURE__ */ new Set();
|
|
2389
|
+
autoLaunchCooldownUntil = /* @__PURE__ */ new Map();
|
|
2390
|
+
AUTO_LAUNCH_COOLDOWN_MS = 5e3;
|
|
1595
2391
|
}
|
|
1596
2392
|
});
|
|
1597
2393
|
|
|
@@ -2617,6 +3413,7 @@ var init_provider_cli_adapter = __esm({
|
|
|
2617
3413
|
this.requirePromptEchoBeforeSubmit = resolvedConfig.requirePromptEchoBeforeSubmit;
|
|
2618
3414
|
this.providerResolutionMeta = resolvedConfig.providerResolutionMeta;
|
|
2619
3415
|
this.cliScripts = provider.scripts || {};
|
|
3416
|
+
this.scriptState = typeof this.cliScripts.createState === "function" ? this.cliScripts.createState() ?? null : null;
|
|
2620
3417
|
const scriptNames = listCliScriptNames(this.cliScripts);
|
|
2621
3418
|
if (scriptNames.length > 0) {
|
|
2622
3419
|
LOG.info("CLI", `[${this.cliType}] CLI scripts: [${scriptNames.join(", ")}]`);
|
|
@@ -2777,9 +3574,13 @@ ${lastSnapshot}`;
|
|
|
2777
3574
|
this.lastScreenChangeAt = 0;
|
|
2778
3575
|
this.lastScreenSnapshotReadAt = Number.NEGATIVE_INFINITY;
|
|
2779
3576
|
}
|
|
3577
|
+
getAccumulatedRawBufferCacheKey() {
|
|
3578
|
+
return this.accumulatedRawBuffer.replace(/\x1B\][^\x07]*(?:\x07|\x1B\\)/g, "").replace(/\x1B[P^_X][\s\S]*?(?:\x07|\x1B\\)/g, "").replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, "");
|
|
3579
|
+
}
|
|
2780
3580
|
getFreshParsedStatusCache() {
|
|
2781
3581
|
const cached = this.parsedStatusCache;
|
|
2782
|
-
|
|
3582
|
+
const accumulatedRawBufferKey = this.getAccumulatedRawBufferCacheKey();
|
|
3583
|
+
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) {
|
|
2783
3584
|
return cached.result;
|
|
2784
3585
|
}
|
|
2785
3586
|
return null;
|
|
@@ -2882,7 +3683,7 @@ ${lastSnapshot}`;
|
|
|
2882
3683
|
this.cliScripts = scripts;
|
|
2883
3684
|
this.parsedStatusCache = null;
|
|
2884
3685
|
this.parseErrorMessage = null;
|
|
2885
|
-
this.scriptState = typeof scripts.createState === "function" ? scripts.createState() : null;
|
|
3686
|
+
this.scriptState = typeof scripts.createState === "function" ? scripts.createState() ?? null : null;
|
|
2886
3687
|
const scriptNames = listCliScriptNames(scripts);
|
|
2887
3688
|
LOG.info("CLI", `[${this.cliType}] CLI scripts injected: [${scriptNames.join(", ")}]`);
|
|
2888
3689
|
}
|
|
@@ -3754,6 +4555,11 @@ ${lastSnapshot}`;
|
|
|
3754
4555
|
};
|
|
3755
4556
|
}
|
|
3756
4557
|
// ─── Script Execution ──────────────────────────
|
|
4558
|
+
invokeCliScript(script, input) {
|
|
4559
|
+
const hasStateFactory = typeof this.cliScripts?.createState === "function";
|
|
4560
|
+
const expectsStateArgument = hasStateFactory || this.scriptState !== null || script.length >= 2;
|
|
4561
|
+
return expectsStateArgument ? script(this.scriptState, input) : script(input);
|
|
4562
|
+
}
|
|
3757
4563
|
runParseSession() {
|
|
3758
4564
|
if (typeof this.cliScripts?.parseSession !== "function") {
|
|
3759
4565
|
this.parseErrorMessage = `${this.cliType} parseSession unavailable`;
|
|
@@ -3774,7 +4580,10 @@ ${lastSnapshot}`;
|
|
|
3774
4580
|
scope: this.currentTurnScope,
|
|
3775
4581
|
runtimeSettings: this.runtimeSettings
|
|
3776
4582
|
});
|
|
3777
|
-
const session = this.
|
|
4583
|
+
const session = this.invokeCliScript(
|
|
4584
|
+
this.cliScripts.parseSession,
|
|
4585
|
+
{ ...input, tail, tailScreen: buildCliScreenSnapshot(tail) }
|
|
4586
|
+
);
|
|
3778
4587
|
this.parseErrorMessage = null;
|
|
3779
4588
|
return session && typeof session === "object" ? session : null;
|
|
3780
4589
|
} catch (e) {
|
|
@@ -3788,7 +4597,7 @@ ${lastSnapshot}`;
|
|
|
3788
4597
|
if (!this.cliScripts?.detectStatus) return null;
|
|
3789
4598
|
try {
|
|
3790
4599
|
const screenText = this.terminalScreen.getText();
|
|
3791
|
-
const status = this.cliScripts.detectStatus
|
|
4600
|
+
const status = this.invokeCliScript(this.cliScripts.detectStatus, {
|
|
3792
4601
|
tail: text.slice(-500),
|
|
3793
4602
|
screenText,
|
|
3794
4603
|
rawBuffer: this.accumulatedRawBuffer,
|
|
@@ -3807,7 +4616,7 @@ ${lastSnapshot}`;
|
|
|
3807
4616
|
try {
|
|
3808
4617
|
const screenText = this.terminalScreen.getText();
|
|
3809
4618
|
const buffer = screenText || this.accumulatedBuffer;
|
|
3810
|
-
return this.cliScripts.parseApproval
|
|
4619
|
+
return this.invokeCliScript(this.cliScripts.parseApproval, {
|
|
3811
4620
|
buffer,
|
|
3812
4621
|
screenText,
|
|
3813
4622
|
rawBuffer: this.accumulatedRawBuffer,
|
|
@@ -3863,7 +4672,8 @@ ${lastSnapshot}`;
|
|
|
3863
4672
|
const screenText = this.readTerminalScreenText();
|
|
3864
4673
|
const parseScreenText = this.getParseScreenText(screenText);
|
|
3865
4674
|
const cached = this.parsedStatusCache;
|
|
3866
|
-
|
|
4675
|
+
const accumulatedRawBufferKey = this.getAccumulatedRawBufferCacheKey();
|
|
4676
|
+
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) {
|
|
3867
4677
|
return cached.result;
|
|
3868
4678
|
}
|
|
3869
4679
|
const parsed = this.runParseSession();
|
|
@@ -3891,6 +4701,7 @@ ${lastSnapshot}`;
|
|
|
3891
4701
|
currentTurnScope: this.currentTurnScope,
|
|
3892
4702
|
recentOutputBuffer: this.recentOutputBuffer,
|
|
3893
4703
|
accumulatedBuffer: this.accumulatedBuffer,
|
|
4704
|
+
accumulatedRawBufferKey,
|
|
3894
4705
|
screenText: parseScreenText,
|
|
3895
4706
|
currentStatus: this.currentStatus,
|
|
3896
4707
|
activeModal: this.activeModal,
|
|
@@ -3915,7 +4726,7 @@ ${lastSnapshot}`;
|
|
|
3915
4726
|
scope: this.currentTurnScope,
|
|
3916
4727
|
runtimeSettings: this.runtimeSettings
|
|
3917
4728
|
});
|
|
3918
|
-
return await Promise.resolve(
|
|
4729
|
+
return await Promise.resolve(this.invokeCliScript(fn, {
|
|
3919
4730
|
...input,
|
|
3920
4731
|
args: args && typeof args === "object" ? { ...args } : {}
|
|
3921
4732
|
}));
|
|
@@ -4696,10 +5507,12 @@ __export(index_exports, {
|
|
|
4696
5507
|
IdeProviderInstance: () => IdeProviderInstance,
|
|
4697
5508
|
InMemoryGitSnapshotStore: () => InMemoryGitSnapshotStore,
|
|
4698
5509
|
LOG: () => LOG,
|
|
5510
|
+
MAX_LEDGER_SLICE_LIMIT: () => MAX_LEDGER_SLICE_LIMIT,
|
|
4699
5511
|
MIN_GIT_WORKSPACE_POLL_INTERVAL_MS: () => MIN_GIT_WORKSPACE_POLL_INTERVAL_MS,
|
|
4700
5512
|
MIN_MACHINE_RUNTIME_SUBSCRIPTION_INTERVAL_MS: () => MIN_MACHINE_RUNTIME_SUBSCRIPTION_INTERVAL_MS,
|
|
4701
5513
|
MIN_SESSION_HOST_DIAGNOSTICS_SUBSCRIPTION_INTERVAL_MS: () => MIN_SESSION_HOST_DIAGNOSTICS_SUBSCRIPTION_INTERVAL_MS,
|
|
4702
5514
|
NodePtyTransportFactory: () => NodePtyTransportFactory,
|
|
5515
|
+
P2pRelayFailureError: () => P2pRelayFailureError,
|
|
4703
5516
|
ProviderCliAdapter: () => ProviderCliAdapter,
|
|
4704
5517
|
ProviderInstanceManager: () => ProviderInstanceManager,
|
|
4705
5518
|
ProviderLoader: () => ProviderLoader,
|
|
@@ -4710,12 +5523,16 @@ __export(index_exports, {
|
|
|
4710
5523
|
addNode: () => addNode,
|
|
4711
5524
|
appendLedgerEntry: () => appendLedgerEntry,
|
|
4712
5525
|
appendRecentActivity: () => appendRecentActivity,
|
|
5526
|
+
appendRemoteLedgerEntries: () => appendRemoteLedgerEntries,
|
|
4713
5527
|
buildAssistantChatMessage: () => buildAssistantChatMessage,
|
|
4714
5528
|
buildChatMessage: () => buildChatMessage,
|
|
4715
5529
|
buildChatMessageSignature: () => buildChatMessageSignature,
|
|
4716
5530
|
buildChatTailDeliverySignature: () => buildChatTailDeliverySignature,
|
|
4717
5531
|
buildCoordinatorSystemPrompt: () => buildCoordinatorSystemPrompt,
|
|
4718
5532
|
buildMachineInfo: () => buildMachineInfo,
|
|
5533
|
+
buildMeshLedgerReconciliationEvidence: () => buildMeshLedgerReconciliationEvidence,
|
|
5534
|
+
buildMeshLedgerReplicaEvidence: () => buildMeshLedgerReplicaEvidence,
|
|
5535
|
+
buildP2pRelayFailurePayload: () => buildP2pRelayFailurePayload,
|
|
4719
5536
|
buildPinnedGlobalInstallCommand: () => buildPinnedGlobalInstallCommand,
|
|
4720
5537
|
buildRuntimeSystemChatMessage: () => buildRuntimeSystemChatMessage,
|
|
4721
5538
|
buildSessionEntries: () => buildSessionEntries,
|
|
@@ -4726,10 +5543,13 @@ __export(index_exports, {
|
|
|
4726
5543
|
buildThoughtChatMessage: () => buildThoughtChatMessage,
|
|
4727
5544
|
buildToolChatMessage: () => buildToolChatMessage,
|
|
4728
5545
|
buildUserChatMessage: () => buildUserChatMessage,
|
|
5546
|
+
cancelTask: () => cancelTask,
|
|
4729
5547
|
claimNextTask: () => claimNextTask,
|
|
4730
5548
|
classifyChatMessageVisibility: () => classifyChatMessageVisibility,
|
|
4731
5549
|
classifyHotChatSessionsForSubscriptionFlush: () => classifyHotChatSessionsForSubscriptionFlush,
|
|
5550
|
+
classifyP2pRelayFailure: () => classifyP2pRelayFailure,
|
|
4732
5551
|
clearDebugTrace: () => clearDebugTrace,
|
|
5552
|
+
clearPendingMeshCoordinatorEvents: () => clearPendingMeshCoordinatorEvents,
|
|
4733
5553
|
compareGitSnapshots: () => compareGitSnapshots,
|
|
4734
5554
|
configureDebugTraceStore: () => configureDebugTraceStore,
|
|
4735
5555
|
connectCdpManager: () => connectCdpManager,
|
|
@@ -4745,6 +5565,7 @@ __export(index_exports, {
|
|
|
4745
5565
|
detectAllVersions: () => detectAllVersions,
|
|
4746
5566
|
detectCLIs: () => detectCLIs,
|
|
4747
5567
|
detectIDEs: () => detectIDEs,
|
|
5568
|
+
drainPendingMeshCoordinatorEvents: () => drainPendingMeshCoordinatorEvents,
|
|
4748
5569
|
enqueueTask: () => enqueueTask,
|
|
4749
5570
|
ensureSessionHostReady: () => ensureSessionHostReady,
|
|
4750
5571
|
execNpmCommandSync: () => execNpmCommandSync,
|
|
@@ -4769,7 +5590,9 @@ __export(index_exports, {
|
|
|
4769
5590
|
getLogLevel: () => getLogLevel,
|
|
4770
5591
|
getMesh: () => getMesh,
|
|
4771
5592
|
getMeshByRepo: () => getMeshByRepo,
|
|
5593
|
+
getMeshQueueStats: () => getMeshQueueStats,
|
|
4772
5594
|
getNpmExecOptions: () => getNpmExecOptions,
|
|
5595
|
+
getPendingMeshCoordinatorEvents: () => getPendingMeshCoordinatorEvents,
|
|
4773
5596
|
getQueue: () => getQueue,
|
|
4774
5597
|
getRecentActivity: () => getRecentActivity,
|
|
4775
5598
|
getRecentCommands: () => getRecentCommands,
|
|
@@ -4795,6 +5618,7 @@ __export(index_exports, {
|
|
|
4795
5618
|
isInternalChatMessage: () => isInternalChatMessage,
|
|
4796
5619
|
isManagedStatusWaiting: () => isManagedStatusWaiting,
|
|
4797
5620
|
isManagedStatusWorking: () => isManagedStatusWorking,
|
|
5621
|
+
isP2pRelayTransportFailure: () => isP2pRelayTransportFailure,
|
|
4798
5622
|
isPathInside: () => isPathInside,
|
|
4799
5623
|
isSessionHostLiveRuntime: () => isSessionHostLiveRuntime,
|
|
4800
5624
|
isSessionHostRecoverySnapshot: () => isSessionHostRecoverySnapshot,
|
|
@@ -4833,10 +5657,12 @@ __export(index_exports, {
|
|
|
4833
5657
|
probeCdpPort: () => probeCdpPort,
|
|
4834
5658
|
readChatHistory: () => readChatHistory,
|
|
4835
5659
|
readLedgerEntries: () => readLedgerEntries,
|
|
5660
|
+
readLedgerSlice: () => readLedgerSlice,
|
|
4836
5661
|
recordDebugTrace: () => recordDebugTrace,
|
|
4837
5662
|
registerExtensionProviders: () => registerExtensionProviders,
|
|
4838
5663
|
removeNode: () => removeNode,
|
|
4839
5664
|
removeWorktree: () => removeWorktree,
|
|
5665
|
+
requeueTask: () => requeueTask,
|
|
4840
5666
|
resetConfig: () => resetConfig,
|
|
4841
5667
|
resetDebugRuntimeConfig: () => resetDebugRuntimeConfig,
|
|
4842
5668
|
resetState: () => resetState,
|
|
@@ -6350,7 +7176,7 @@ function addWorkspaceEntry(config, rawPath, label, options) {
|
|
|
6350
7176
|
}
|
|
6351
7177
|
}
|
|
6352
7178
|
const v = validateWorkspacePath(abs);
|
|
6353
|
-
if (
|
|
7179
|
+
if (v.ok !== true) return { error: v.error };
|
|
6354
7180
|
const list = [...config.workspaces || []];
|
|
6355
7181
|
if (list.some((w) => path5.resolve(w.path) === abs)) {
|
|
6356
7182
|
return { error: "Workspace already in list" };
|
|
@@ -6733,34 +7559,186 @@ async function syncMeshes(transport) {
|
|
|
6733
7559
|
}
|
|
6734
7560
|
}
|
|
6735
7561
|
}
|
|
6736
|
-
if (transport.syncMeshLedger) {
|
|
6737
|
-
for (const local of localMeshes) {
|
|
6738
|
-
try {
|
|
6739
|
-
await syncMeshLedger(local.id, transport);
|
|
6740
|
-
} catch (e) {
|
|
6741
|
-
result.errors.push(`Ledger sync failed for "${local.name}": ${e.message}`);
|
|
6742
|
-
}
|
|
6743
|
-
}
|
|
6744
|
-
}
|
|
6745
7562
|
return result;
|
|
6746
7563
|
}
|
|
6747
|
-
async function syncMeshLedger(meshId, transport) {
|
|
6748
|
-
if (!transport.syncMeshLedger) return;
|
|
6749
|
-
const { readLedgerEntries: readLedgerEntries2, appendRemoteLedgerEntries: appendRemoteLedgerEntries2 } = await Promise.resolve().then(() => (init_mesh_ledger(), mesh_ledger_exports));
|
|
6750
|
-
const localEntries = readLedgerEntries2(meshId);
|
|
6751
|
-
const res = await transport.syncMeshLedger(meshId, { newEntries: localEntries });
|
|
6752
|
-
if (res.missingEntries && res.missingEntries.length > 0) {
|
|
6753
|
-
appendRemoteLedgerEntries2(meshId, res.missingEntries);
|
|
6754
|
-
}
|
|
6755
|
-
}
|
|
6756
7564
|
|
|
6757
7565
|
// src/index.ts
|
|
6758
7566
|
init_mesh_ledger();
|
|
7567
|
+
|
|
7568
|
+
// src/mesh/mesh-ledger-reconciliation.ts
|
|
7569
|
+
function lastTimestamp(slice) {
|
|
7570
|
+
const entries = Array.isArray(slice?.entries) ? slice.entries : [];
|
|
7571
|
+
return entries.length ? entries[entries.length - 1].timestamp : null;
|
|
7572
|
+
}
|
|
7573
|
+
function buildMeshLedgerReplicaEvidence(args) {
|
|
7574
|
+
const entriesReceived = Array.isArray(args.slice?.entries) ? args.slice.entries.length : 0;
|
|
7575
|
+
return {
|
|
7576
|
+
nodeId: args.nodeId,
|
|
7577
|
+
...args.daemonId ? { daemonId: args.daemonId } : {},
|
|
7578
|
+
status: args.status ?? (args.importResult && args.importResult.accepted > 0 ? "imported" : "queried"),
|
|
7579
|
+
transport: args.transport,
|
|
7580
|
+
protocol: "adhdev.mesh.ledger.slice.v1",
|
|
7581
|
+
entriesReceived,
|
|
7582
|
+
entriesImported: args.importResult?.accepted ?? 0,
|
|
7583
|
+
skippedDuplicate: args.importResult?.skippedDuplicate ?? 0,
|
|
7584
|
+
rejectedInvalid: args.importResult?.rejectedInvalid ?? 0,
|
|
7585
|
+
hasMore: args.slice?.cursor?.hasMore === true,
|
|
7586
|
+
nextAfterId: args.slice?.cursor?.nextAfterId ?? null,
|
|
7587
|
+
lastTimestamp: lastTimestamp(args.slice),
|
|
7588
|
+
...args.slice?.summary ? { summary: args.slice.summary } : {},
|
|
7589
|
+
...args.error ? {
|
|
7590
|
+
error: args.error,
|
|
7591
|
+
noFallbackReason: "Ledger reconciliation is P2P/local-first only; Cloud/D1 ledger sync is intentionally disabled."
|
|
7592
|
+
} : {}
|
|
7593
|
+
};
|
|
7594
|
+
}
|
|
7595
|
+
function buildMeshLedgerReconciliationEvidence(meshId, replicas) {
|
|
7596
|
+
const failedNodes = replicas.filter((replica) => replica.status === "failed").map((replica) => replica.nodeId);
|
|
7597
|
+
const pendingNodes = replicas.filter((replica) => replica.hasMore && replica.status !== "failed").map((replica) => replica.nodeId);
|
|
7598
|
+
return {
|
|
7599
|
+
protocol: "adhdev.mesh.ledger.reconciliation.v1",
|
|
7600
|
+
meshId,
|
|
7601
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7602
|
+
sourceOfTruth: {
|
|
7603
|
+
kind: "coordinator_local_jsonl",
|
|
7604
|
+
p2pOnly: true,
|
|
7605
|
+
cloudD1LedgerSync: false,
|
|
7606
|
+
notes: "Coordinator reconciles bounded slices from daemon-local JSONL ledgers over P2P DataChannel; Cloud/D1 is not a ledger source of truth."
|
|
7607
|
+
},
|
|
7608
|
+
replicas,
|
|
7609
|
+
totals: {
|
|
7610
|
+
replicas: replicas.length,
|
|
7611
|
+
queried: replicas.filter((replica) => replica.status !== "failed").length,
|
|
7612
|
+
failed: failedNodes.length,
|
|
7613
|
+
entriesReceived: replicas.reduce((sum, replica) => sum + replica.entriesReceived, 0),
|
|
7614
|
+
entriesImported: replicas.reduce((sum, replica) => sum + replica.entriesImported, 0),
|
|
7615
|
+
skippedDuplicate: replicas.reduce((sum, replica) => sum + replica.skippedDuplicate, 0),
|
|
7616
|
+
rejectedInvalid: replicas.reduce((sum, replica) => sum + replica.rejectedInvalid, 0)
|
|
7617
|
+
},
|
|
7618
|
+
convergence: {
|
|
7619
|
+
complete: failedNodes.length === 0 && pendingNodes.length === 0,
|
|
7620
|
+
pendingNodes,
|
|
7621
|
+
failedNodes
|
|
7622
|
+
}
|
|
7623
|
+
};
|
|
7624
|
+
}
|
|
7625
|
+
|
|
7626
|
+
// src/index.ts
|
|
6759
7627
|
init_mesh_work_queue();
|
|
6760
7628
|
init_mesh_events();
|
|
6761
7629
|
|
|
7630
|
+
// src/mesh/p2p-relay-failure.ts
|
|
7631
|
+
var NO_FALLBACK_REASON = "Repo Mesh command/data-plane is P2P-only; WS/REST command fallback is intentionally disabled to preserve the transport boundary.";
|
|
7632
|
+
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.";
|
|
7633
|
+
var NON_P2P_NEXT_ACTION = "Inspect the provider/command error and fix the underlying logic or configuration before retrying.";
|
|
7634
|
+
function messageFromError(error) {
|
|
7635
|
+
if (error instanceof Error) return error.message;
|
|
7636
|
+
if (typeof error === "string") return error;
|
|
7637
|
+
if (error && typeof error === "object") {
|
|
7638
|
+
const candidate = error.error ?? error.message ?? error.reason;
|
|
7639
|
+
if (typeof candidate === "string") return candidate;
|
|
7640
|
+
}
|
|
7641
|
+
return String(error || "mesh relay command failed");
|
|
7642
|
+
}
|
|
7643
|
+
function classifyP2pRelayFailure(error, _context = {}) {
|
|
7644
|
+
const message = messageFromError(error);
|
|
7645
|
+
const lower = message.toLowerCase();
|
|
7646
|
+
const hasP2pSignal = /p2p|datachannel|node-datachannel|webrtc|ice|mesh_relay_command|daemon_mesh_p2p_transport/i.test(message);
|
|
7647
|
+
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);
|
|
7648
|
+
if (/requires targetdaemonid and command|providerpriority|no inference provider|permission denied|read-only|not a member/i.test(message)) {
|
|
7649
|
+
return {
|
|
7650
|
+
code: "mesh_logic_or_provider_failure",
|
|
7651
|
+
reason: "mesh_logic_or_provider_failure",
|
|
7652
|
+
transport: "unknown",
|
|
7653
|
+
recoverable: false,
|
|
7654
|
+
retryRecommended: false,
|
|
7655
|
+
nextAction: NON_P2P_NEXT_ACTION,
|
|
7656
|
+
noFallbackReason: NO_FALLBACK_REASON
|
|
7657
|
+
};
|
|
7658
|
+
}
|
|
7659
|
+
let code = null;
|
|
7660
|
+
let reason = "";
|
|
7661
|
+
if (/timeout|timed out/i.test(message) && (hasP2pSignal || /mesh transport/i.test(message))) {
|
|
7662
|
+
code = "p2p_timeout";
|
|
7663
|
+
reason = "daemon_mesh_p2p_timeout";
|
|
7664
|
+
} else if (/no route|route unavailable/i.test(message)) {
|
|
7665
|
+
code = "p2p_no_route";
|
|
7666
|
+
reason = "daemon_mesh_p2p_no_route";
|
|
7667
|
+
} else if (/offline|not owned|not found|not connected to server/i.test(message) && /daemon|peer|target/i.test(message)) {
|
|
7668
|
+
code = "p2p_daemon_offline";
|
|
7669
|
+
reason = "daemon_mesh_target_offline";
|
|
7670
|
+
} else if (/closed|disconnected/i.test(message) && (hasP2pSignal || /state changed/i.test(message))) {
|
|
7671
|
+
code = "p2p_datachannel_closed";
|
|
7672
|
+
reason = "daemon_mesh_p2p_datachannel_closed";
|
|
7673
|
+
} else if (/not connected|cannot send|cannot establish/i.test(message) && hasP2pSignal) {
|
|
7674
|
+
code = "p2p_not_connected";
|
|
7675
|
+
reason = "daemon_mesh_p2p_not_connected";
|
|
7676
|
+
} else if (hasP2pSignal && hasFailureSignal) {
|
|
7677
|
+
code = "p2p_unavailable";
|
|
7678
|
+
reason = "daemon_mesh_p2p_transport_unavailable";
|
|
7679
|
+
}
|
|
7680
|
+
if (!code) {
|
|
7681
|
+
return {
|
|
7682
|
+
code: "mesh_logic_or_provider_failure",
|
|
7683
|
+
reason: "mesh_logic_or_provider_failure",
|
|
7684
|
+
transport: "unknown",
|
|
7685
|
+
recoverable: false,
|
|
7686
|
+
retryRecommended: false,
|
|
7687
|
+
nextAction: NON_P2P_NEXT_ACTION,
|
|
7688
|
+
noFallbackReason: NO_FALLBACK_REASON
|
|
7689
|
+
};
|
|
7690
|
+
}
|
|
7691
|
+
return {
|
|
7692
|
+
code,
|
|
7693
|
+
reason,
|
|
7694
|
+
transport: "p2p",
|
|
7695
|
+
recoverable: true,
|
|
7696
|
+
retryRecommended: true,
|
|
7697
|
+
nextAction: P2P_NEXT_ACTION,
|
|
7698
|
+
noFallbackReason: NO_FALLBACK_REASON
|
|
7699
|
+
};
|
|
7700
|
+
}
|
|
7701
|
+
function isP2pRelayTransportFailure(error) {
|
|
7702
|
+
return classifyP2pRelayFailure(error).recoverable === true;
|
|
7703
|
+
}
|
|
7704
|
+
function buildP2pRelayFailurePayload(error, context = {}) {
|
|
7705
|
+
const classification = classifyP2pRelayFailure(error, context);
|
|
7706
|
+
return {
|
|
7707
|
+
success: false,
|
|
7708
|
+
...classification,
|
|
7709
|
+
error: messageFromError(error),
|
|
7710
|
+
...context.command ? { command: context.command } : {},
|
|
7711
|
+
...context.targetDaemonId ? { targetDaemonId: context.targetDaemonId } : {}
|
|
7712
|
+
};
|
|
7713
|
+
}
|
|
7714
|
+
var P2pRelayFailureError = class extends Error {
|
|
7715
|
+
code;
|
|
7716
|
+
reason;
|
|
7717
|
+
transport;
|
|
7718
|
+
recoverable;
|
|
7719
|
+
retryRecommended;
|
|
7720
|
+
nextAction;
|
|
7721
|
+
noFallbackReason;
|
|
7722
|
+
command;
|
|
7723
|
+
targetDaemonId;
|
|
7724
|
+
constructor(message, context = {}) {
|
|
7725
|
+
super(message);
|
|
7726
|
+
this.name = "P2pRelayFailureError";
|
|
7727
|
+
const payload = buildP2pRelayFailurePayload(message, context);
|
|
7728
|
+
this.code = payload.code;
|
|
7729
|
+
this.reason = payload.reason;
|
|
7730
|
+
this.transport = payload.transport;
|
|
7731
|
+
this.recoverable = payload.recoverable;
|
|
7732
|
+
this.retryRecommended = payload.retryRecommended;
|
|
7733
|
+
this.nextAction = payload.nextAction;
|
|
7734
|
+
this.noFallbackReason = payload.noFallbackReason;
|
|
7735
|
+
this.command = context.command;
|
|
7736
|
+
this.targetDaemonId = context.targetDaemonId;
|
|
7737
|
+
}
|
|
7738
|
+
};
|
|
7739
|
+
|
|
6762
7740
|
// src/config/state-store.ts
|
|
6763
|
-
var
|
|
7741
|
+
var import_fs6 = require("fs");
|
|
6764
7742
|
var import_path5 = require("path");
|
|
6765
7743
|
init_config();
|
|
6766
7744
|
var DEFAULT_STATE = {
|
|
@@ -6811,11 +7789,11 @@ function normalizeState(raw) {
|
|
|
6811
7789
|
}
|
|
6812
7790
|
function loadState() {
|
|
6813
7791
|
const statePath = getStatePath();
|
|
6814
|
-
if (!(0,
|
|
7792
|
+
if (!(0, import_fs6.existsSync)(statePath)) {
|
|
6815
7793
|
return { ...DEFAULT_STATE };
|
|
6816
7794
|
}
|
|
6817
7795
|
try {
|
|
6818
|
-
const raw = (0,
|
|
7796
|
+
const raw = (0, import_fs6.readFileSync)(statePath, "utf-8");
|
|
6819
7797
|
return normalizeState(JSON.parse(raw));
|
|
6820
7798
|
} catch {
|
|
6821
7799
|
return { ...DEFAULT_STATE };
|
|
@@ -6824,17 +7802,17 @@ function loadState() {
|
|
|
6824
7802
|
function saveState(state) {
|
|
6825
7803
|
const statePath = getStatePath();
|
|
6826
7804
|
const normalized = normalizeState(state);
|
|
6827
|
-
(0,
|
|
7805
|
+
(0, import_fs6.writeFileSync)(statePath, JSON.stringify(normalized, null, 2), { encoding: "utf-8", mode: 384 });
|
|
6828
7806
|
}
|
|
6829
7807
|
function resetState() {
|
|
6830
7808
|
saveState({ ...DEFAULT_STATE });
|
|
6831
7809
|
}
|
|
6832
7810
|
|
|
6833
7811
|
// src/detection/ide-detector.ts
|
|
6834
|
-
var
|
|
6835
|
-
var
|
|
7812
|
+
var import_child_process2 = require("child_process");
|
|
7813
|
+
var import_fs7 = require("fs");
|
|
6836
7814
|
var import_os2 = require("os");
|
|
6837
|
-
var
|
|
7815
|
+
var path10 = __toESM(require("path"));
|
|
6838
7816
|
var BUILTIN_IDE_DEFINITIONS = [];
|
|
6839
7817
|
var registeredIDEs = /* @__PURE__ */ new Map();
|
|
6840
7818
|
function registerIDEDefinition(def) {
|
|
@@ -6853,13 +7831,13 @@ function getMergedDefinitions() {
|
|
|
6853
7831
|
function findCliCommand(command) {
|
|
6854
7832
|
const trimmed = String(command || "").trim();
|
|
6855
7833
|
if (!trimmed) return null;
|
|
6856
|
-
if (
|
|
6857
|
-
const candidate = trimmed.startsWith("~") ?
|
|
6858
|
-
const resolved =
|
|
6859
|
-
return (0,
|
|
7834
|
+
if (path10.isAbsolute(trimmed) || trimmed.includes("/") || trimmed.includes("\\") || trimmed.startsWith("~")) {
|
|
7835
|
+
const candidate = trimmed.startsWith("~") ? path10.join((0, import_os2.homedir)(), trimmed.slice(1)) : trimmed;
|
|
7836
|
+
const resolved = path10.isAbsolute(candidate) ? candidate : path10.resolve(candidate);
|
|
7837
|
+
return (0, import_fs7.existsSync)(resolved) ? resolved : null;
|
|
6860
7838
|
}
|
|
6861
7839
|
try {
|
|
6862
|
-
const result = (0,
|
|
7840
|
+
const result = (0, import_child_process2.execSync)(
|
|
6863
7841
|
(0, import_os2.platform)() === "win32" ? `where ${trimmed}` : `which ${trimmed}`,
|
|
6864
7842
|
{ encoding: "utf-8", timeout: 5e3, stdio: ["pipe", "pipe", "pipe"] }
|
|
6865
7843
|
).trim();
|
|
@@ -6870,7 +7848,7 @@ function findCliCommand(command) {
|
|
|
6870
7848
|
}
|
|
6871
7849
|
function getIdeVersion(cliCommand) {
|
|
6872
7850
|
try {
|
|
6873
|
-
const result = (0,
|
|
7851
|
+
const result = (0, import_child_process2.execSync)(`"${cliCommand}" --version`, {
|
|
6874
7852
|
encoding: "utf-8",
|
|
6875
7853
|
timeout: 1e4,
|
|
6876
7854
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -6883,13 +7861,13 @@ function getIdeVersion(cliCommand) {
|
|
|
6883
7861
|
function checkPathExists(paths) {
|
|
6884
7862
|
const home = (0, import_os2.homedir)();
|
|
6885
7863
|
for (const p of paths) {
|
|
6886
|
-
const normalized = p.startsWith("~") ?
|
|
7864
|
+
const normalized = p.startsWith("~") ? path10.join(home, p.slice(1)) : p;
|
|
6887
7865
|
if (normalized.includes("*")) {
|
|
6888
7866
|
const username = home.split(/[\\/]/).pop() || "";
|
|
6889
7867
|
const resolved = normalized.replace("*", username);
|
|
6890
|
-
if ((0,
|
|
7868
|
+
if ((0, import_fs7.existsSync)(resolved)) return resolved;
|
|
6891
7869
|
} else {
|
|
6892
|
-
if ((0,
|
|
7870
|
+
if ((0, import_fs7.existsSync)(normalized)) return normalized;
|
|
6893
7871
|
}
|
|
6894
7872
|
}
|
|
6895
7873
|
return null;
|
|
@@ -6903,7 +7881,7 @@ async function detectIDEs(providerLoader) {
|
|
|
6903
7881
|
let resolvedCli = cliPath;
|
|
6904
7882
|
if (!resolvedCli && appPath && os22 === "darwin") {
|
|
6905
7883
|
const bundledCli = `${appPath}/Contents/Resources/app/bin/${def.cli}`;
|
|
6906
|
-
if ((0,
|
|
7884
|
+
if ((0, import_fs7.existsSync)(bundledCli)) resolvedCli = bundledCli;
|
|
6907
7885
|
}
|
|
6908
7886
|
if (!resolvedCli && appPath && os22 === "win32") {
|
|
6909
7887
|
const { dirname: dirname9 } = await import("path");
|
|
@@ -6916,7 +7894,7 @@ async function detectIDEs(providerLoader) {
|
|
|
6916
7894
|
`${appDir}\\\\resources\\\\app\\\\bin\\\\${def.cli}.cmd`
|
|
6917
7895
|
];
|
|
6918
7896
|
for (const c of candidates) {
|
|
6919
|
-
if ((0,
|
|
7897
|
+
if ((0, import_fs7.existsSync)(c)) {
|
|
6920
7898
|
resolvedCli = c;
|
|
6921
7899
|
break;
|
|
6922
7900
|
}
|
|
@@ -6938,134 +7916,8 @@ async function detectIDEs(providerLoader) {
|
|
|
6938
7916
|
return results;
|
|
6939
7917
|
}
|
|
6940
7918
|
|
|
6941
|
-
// src/
|
|
6942
|
-
|
|
6943
|
-
var os3 = __toESM(require("os"));
|
|
6944
|
-
var path10 = __toESM(require("path"));
|
|
6945
|
-
var import_fs7 = require("fs");
|
|
6946
|
-
function parseVersion(raw) {
|
|
6947
|
-
const match = raw.match(/v?(\d+\.\d+(?:\.\d+)?(?:-[a-zA-Z0-9.]+)?)/);
|
|
6948
|
-
return match ? match[1] : raw.split("\n")[0].slice(0, 100);
|
|
6949
|
-
}
|
|
6950
|
-
function shellQuote(value) {
|
|
6951
|
-
if (/^[a-zA-Z0-9_./:@%+=,-]+$/.test(value)) return value;
|
|
6952
|
-
return `"${value.replace(/(["\\$`])/g, "\\$1")}"`;
|
|
6953
|
-
}
|
|
6954
|
-
function expandHome(value) {
|
|
6955
|
-
const trimmed = value.trim();
|
|
6956
|
-
if (!trimmed.startsWith("~")) return trimmed;
|
|
6957
|
-
return path10.join(os3.homedir(), trimmed.slice(1));
|
|
6958
|
-
}
|
|
6959
|
-
function isExplicitCommandPath(command) {
|
|
6960
|
-
const trimmed = command.trim();
|
|
6961
|
-
return path10.isAbsolute(trimmed) || trimmed.includes("/") || trimmed.includes("\\") || trimmed.startsWith("~");
|
|
6962
|
-
}
|
|
6963
|
-
function resolveCommandPath(command) {
|
|
6964
|
-
const trimmed = command.trim();
|
|
6965
|
-
if (!trimmed) return null;
|
|
6966
|
-
if (isExplicitCommandPath(trimmed)) {
|
|
6967
|
-
const expanded = expandHome(trimmed);
|
|
6968
|
-
const candidate = path10.isAbsolute(expanded) ? expanded : path10.resolve(expanded);
|
|
6969
|
-
return (0, import_fs7.existsSync)(candidate) ? candidate : null;
|
|
6970
|
-
}
|
|
6971
|
-
return null;
|
|
6972
|
-
}
|
|
6973
|
-
function execAsync(cmd, timeoutMs = 5e3) {
|
|
6974
|
-
return new Promise((resolve16) => {
|
|
6975
|
-
const child = (0, import_child_process2.exec)(cmd, {
|
|
6976
|
-
encoding: "utf-8",
|
|
6977
|
-
timeout: timeoutMs,
|
|
6978
|
-
...process.platform === "win32" ? { windowsHide: true } : {}
|
|
6979
|
-
}, (err, stdout) => {
|
|
6980
|
-
if (err || !stdout?.trim()) {
|
|
6981
|
-
resolve16(null);
|
|
6982
|
-
} else {
|
|
6983
|
-
resolve16(stdout.trim());
|
|
6984
|
-
}
|
|
6985
|
-
});
|
|
6986
|
-
child.on("error", () => resolve16(null));
|
|
6987
|
-
});
|
|
6988
|
-
}
|
|
6989
|
-
async function detectCLIs(providerLoader, options) {
|
|
6990
|
-
const platform10 = os3.platform();
|
|
6991
|
-
const whichCmd = platform10 === "win32" ? "where" : "which";
|
|
6992
|
-
const includeVersion = options?.includeVersion !== false;
|
|
6993
|
-
const cliList = providerLoader ? providerLoader.getCliDetectionList() : [];
|
|
6994
|
-
const results = await Promise.all(
|
|
6995
|
-
cliList.map(async (cli) => {
|
|
6996
|
-
try {
|
|
6997
|
-
const explicitPath = resolveCommandPath(cli.command);
|
|
6998
|
-
const pathResult = explicitPath || await execAsync(`${whichCmd} ${shellQuote(cli.command)}`);
|
|
6999
|
-
if (!pathResult) return { ...cli, installed: false };
|
|
7000
|
-
const firstPath = explicitPath || pathResult.split("\n")[0];
|
|
7001
|
-
let version;
|
|
7002
|
-
if (includeVersion) {
|
|
7003
|
-
const versionCommands = [
|
|
7004
|
-
`"${firstPath}" --version`,
|
|
7005
|
-
`"${firstPath}" -V`,
|
|
7006
|
-
`"${firstPath}" -v`,
|
|
7007
|
-
cli.versionCommand
|
|
7008
|
-
].filter((v) => !!v);
|
|
7009
|
-
try {
|
|
7010
|
-
for (const versionCommand of versionCommands) {
|
|
7011
|
-
const versionResult = await execAsync(versionCommand, 3e3);
|
|
7012
|
-
if (versionResult) {
|
|
7013
|
-
version = parseVersion(versionResult);
|
|
7014
|
-
break;
|
|
7015
|
-
}
|
|
7016
|
-
}
|
|
7017
|
-
} catch {
|
|
7018
|
-
}
|
|
7019
|
-
}
|
|
7020
|
-
return { ...cli, installed: true, version, path: firstPath };
|
|
7021
|
-
} catch {
|
|
7022
|
-
return { ...cli, installed: false };
|
|
7023
|
-
}
|
|
7024
|
-
})
|
|
7025
|
-
);
|
|
7026
|
-
return results;
|
|
7027
|
-
}
|
|
7028
|
-
async function detectCLI(cliId, providerLoader, options) {
|
|
7029
|
-
const resolvedId = providerLoader ? providerLoader.resolveAlias(cliId) : cliId;
|
|
7030
|
-
if (providerLoader) {
|
|
7031
|
-
const cliList = providerLoader.getCliDetectionList();
|
|
7032
|
-
const target = cliList.find((c) => c.id === resolvedId);
|
|
7033
|
-
if (target) {
|
|
7034
|
-
const platform10 = os3.platform();
|
|
7035
|
-
const whichCmd = platform10 === "win32" ? "where" : "which";
|
|
7036
|
-
try {
|
|
7037
|
-
const explicitPath = resolveCommandPath(target.command);
|
|
7038
|
-
const pathResult = explicitPath || await execAsync(`${whichCmd} ${shellQuote(target.command)}`);
|
|
7039
|
-
if (!pathResult) return null;
|
|
7040
|
-
const firstPath = explicitPath || pathResult.split("\n")[0];
|
|
7041
|
-
let version;
|
|
7042
|
-
if (options?.includeVersion !== false) {
|
|
7043
|
-
const versionCommands = [
|
|
7044
|
-
`"${firstPath}" --version`,
|
|
7045
|
-
`"${firstPath}" -V`,
|
|
7046
|
-
`"${firstPath}" -v`,
|
|
7047
|
-
target.versionCommand
|
|
7048
|
-
].filter((v) => !!v);
|
|
7049
|
-
try {
|
|
7050
|
-
for (const versionCommand of versionCommands) {
|
|
7051
|
-
const versionResult = await execAsync(versionCommand, 3e3);
|
|
7052
|
-
if (versionResult) {
|
|
7053
|
-
version = parseVersion(versionResult);
|
|
7054
|
-
break;
|
|
7055
|
-
}
|
|
7056
|
-
}
|
|
7057
|
-
} catch {
|
|
7058
|
-
}
|
|
7059
|
-
}
|
|
7060
|
-
return { ...target, installed: true, version, path: firstPath };
|
|
7061
|
-
} catch {
|
|
7062
|
-
return null;
|
|
7063
|
-
}
|
|
7064
|
-
}
|
|
7065
|
-
}
|
|
7066
|
-
const all = await detectCLIs(providerLoader, options);
|
|
7067
|
-
return all.find((c) => c.id === resolvedId && c.installed) || null;
|
|
7068
|
-
}
|
|
7919
|
+
// src/index.ts
|
|
7920
|
+
init_cli_detector();
|
|
7069
7921
|
|
|
7070
7922
|
// src/system/host-memory.ts
|
|
7071
7923
|
var os4 = __toESM(require("os"));
|
|
@@ -8934,6 +9786,28 @@ var StatusMonitor = class {
|
|
|
8934
9786
|
};
|
|
8935
9787
|
|
|
8936
9788
|
// src/providers/chat-message-normalization.ts
|
|
9789
|
+
function extractFinalSummaryFromMessages(messages, maxChars = 500) {
|
|
9790
|
+
if (!Array.isArray(messages) || messages.length === 0) return "";
|
|
9791
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
9792
|
+
const msg = messages[i];
|
|
9793
|
+
if (!msg) continue;
|
|
9794
|
+
const classification = classifyChatMessageVisibility(msg);
|
|
9795
|
+
if (classification.isUserFacing && (msg.role === "assistant" || msg.role === "model")) {
|
|
9796
|
+
const text = flattenContent(msg.content).trim();
|
|
9797
|
+
if (text) return text.slice(0, maxChars);
|
|
9798
|
+
}
|
|
9799
|
+
}
|
|
9800
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
9801
|
+
const msg = messages[i];
|
|
9802
|
+
if (!msg) continue;
|
|
9803
|
+
const classification = classifyChatMessageVisibility(msg);
|
|
9804
|
+
if (classification.isUserFacing) {
|
|
9805
|
+
const text = flattenContent(msg.content).trim();
|
|
9806
|
+
if (text) return text.slice(0, maxChars);
|
|
9807
|
+
}
|
|
9808
|
+
}
|
|
9809
|
+
return "";
|
|
9810
|
+
}
|
|
8937
9811
|
var BUILTIN_CHAT_MESSAGE_KINDS = ["standard", "thought", "tool", "terminal", "system"];
|
|
8938
9812
|
var CHAT_MESSAGE_VISIBILITIES = ["user", "debug", "internal", "hidden"];
|
|
8939
9813
|
var CHAT_MESSAGE_TRANSCRIPT_VISIBILITIES = ["visible", "chat", "user", "debug", "internal", "hidden"];
|
|
@@ -10945,7 +11819,8 @@ var ExtensionProviderInstance = class {
|
|
|
10945
11819
|
ideType: this.ideType || this.type,
|
|
10946
11820
|
agentType: this.type,
|
|
10947
11821
|
agentName: this.agentName || this.provider.name,
|
|
10948
|
-
extensionId: this.extensionId || this.type
|
|
11822
|
+
extensionId: this.extensionId || this.type,
|
|
11823
|
+
finalSummary: extractFinalSummaryFromMessages(data?.messages)
|
|
10949
11824
|
});
|
|
10950
11825
|
this.generatingStartedAt = 0;
|
|
10951
11826
|
}
|
|
@@ -11713,7 +12588,7 @@ var IdeProviderInstance = class {
|
|
|
11713
12588
|
} else if (agentStatus === "idle" && (lastStatus === "generating" || lastStatus === "waiting_approval")) {
|
|
11714
12589
|
const startedAt = this.generatingStartedAt.get(agentKey);
|
|
11715
12590
|
const duration = startedAt ? Math.round((now - startedAt) / 1e3) : 0;
|
|
11716
|
-
this.pushEvent({ event: "agent:generating_completed", chatTitle, duration, timestamp: now, ideType: this.type });
|
|
12591
|
+
this.pushEvent({ event: "agent:generating_completed", chatTitle, duration, timestamp: now, ideType: this.type, finalSummary: extractFinalSummaryFromMessages(chatData?.messages) });
|
|
11717
12592
|
this.generatingStartedAt.delete(agentKey);
|
|
11718
12593
|
}
|
|
11719
12594
|
this.lastAgentStatuses.set(agentKey, agentStatus);
|
|
@@ -15209,11 +16084,13 @@ async function handleOpenPanel(h, args) {
|
|
|
15209
16084
|
async function handlePtyInput(h, args) {
|
|
15210
16085
|
const { cliType, data, targetSessionId } = args || {};
|
|
15211
16086
|
if (!data) return { success: false, error: "data required" };
|
|
16087
|
+
const cleanData = typeof data === "string" ? data.replace(/\x1b\[[?>][0-9;]*c/g, "") : data;
|
|
16088
|
+
if (!cleanData) return { success: true };
|
|
15212
16089
|
const adapter = h.getCliAdapter(targetSessionId || cliType);
|
|
15213
16090
|
if (!adapter || typeof adapter.writeRaw !== "function") {
|
|
15214
16091
|
return { success: false, error: `CLI adapter not found: ${targetSessionId || cliType || "unknown"}` };
|
|
15215
16092
|
}
|
|
15216
|
-
await adapter.writeRaw(
|
|
16093
|
+
await adapter.writeRaw(cleanData);
|
|
15217
16094
|
return { success: true };
|
|
15218
16095
|
}
|
|
15219
16096
|
function handlePtyResize(_h, args) {
|
|
@@ -16173,6 +17050,7 @@ var import_fs8 = require("fs");
|
|
|
16173
17050
|
var import_child_process6 = require("child_process");
|
|
16174
17051
|
var import_chalk = __toESM(require("chalk"));
|
|
16175
17052
|
init_provider_cli_adapter();
|
|
17053
|
+
init_cli_detector();
|
|
16176
17054
|
init_config();
|
|
16177
17055
|
|
|
16178
17056
|
// src/providers/cli-provider-instance.ts
|
|
@@ -16202,6 +17080,8 @@ function normalizeProviderSessionId(provider, providerSessionId) {
|
|
|
16202
17080
|
}
|
|
16203
17081
|
|
|
16204
17082
|
// src/providers/cli-provider-instance.ts
|
|
17083
|
+
var COMPLETED_FINALIZATION_RETRY_MS = 1e3;
|
|
17084
|
+
var COMPLETED_FINALIZATION_MAX_WAIT_MS = 3e4;
|
|
16205
17085
|
var IMAGE_MIME_EXTENSIONS = {
|
|
16206
17086
|
"image/png": ".png",
|
|
16207
17087
|
"image/jpeg": ".jpg",
|
|
@@ -16265,6 +17145,13 @@ function cleanupStaleMaterializedImages(dir) {
|
|
|
16265
17145
|
} catch {
|
|
16266
17146
|
}
|
|
16267
17147
|
}
|
|
17148
|
+
function hasNonEmptyCliModalButtons(activeModal) {
|
|
17149
|
+
const buttons = activeModal?.buttons;
|
|
17150
|
+
return Array.isArray(buttons) && buttons.some((button) => String(button || "").trim().length > 0);
|
|
17151
|
+
}
|
|
17152
|
+
function isCliGeneratingLikeStatus(status) {
|
|
17153
|
+
return status === "generating" || status === "streaming" || status === "long_generating" || status === "starting";
|
|
17154
|
+
}
|
|
16268
17155
|
function buildCliStructuredInputPrompt(input, options = {}) {
|
|
16269
17156
|
const promptParts = [];
|
|
16270
17157
|
const imageRefs = [];
|
|
@@ -16549,10 +17436,12 @@ var CliProviderInstance = class {
|
|
|
16549
17436
|
const mergedMessages = this.mergeConversationMessages(parsedMessages);
|
|
16550
17437
|
const canonicalBackedHistory = this.syncCanonicalSavedHistoryIfNeeded();
|
|
16551
17438
|
const dirName = this.workingDir.split("/").filter(Boolean).pop() || "session";
|
|
17439
|
+
const parsedChatStatus = typeof parsedStatus?.status === "string" && parsedStatus.status.trim() ? parsedStatus.status.trim() : void 0;
|
|
17440
|
+
const suppressStaleParsedBusyStatus = this.shouldSuppressStaleParsedBusyStatus(parsedStatus, adapterStatus);
|
|
16552
17441
|
if (parsedMessages.length > 0) {
|
|
16553
17442
|
const shouldSkipReplayPersist = this.suppressIdleHistoryReplay && adapterStatus.status === "idle" && parsedStatus?.status === "idle";
|
|
16554
17443
|
let messagesToSave = parsedMessages;
|
|
16555
|
-
if (
|
|
17444
|
+
if (!suppressStaleParsedBusyStatus && (parsedChatStatus === "generating" || parsedChatStatus === "long_generating")) {
|
|
16556
17445
|
const lastIdx = messagesToSave.length - 1;
|
|
16557
17446
|
if (lastIdx >= 0 && messagesToSave[lastIdx]?.role === "assistant") {
|
|
16558
17447
|
messagesToSave = messagesToSave.slice(0, lastIdx);
|
|
@@ -16586,6 +17475,7 @@ var CliProviderInstance = class {
|
|
|
16586
17475
|
summaryMetadata: this.summaryMetadata,
|
|
16587
17476
|
controlValues: this.controlValues
|
|
16588
17477
|
});
|
|
17478
|
+
const activeChatStatus = parseErrorMessage ? "error" : autoApproveActive && parsedStatus?.status === "waiting_approval" ? "generating" : adapterStatus.status !== "idle" ? visibleStatus : suppressStaleParsedBusyStatus ? visibleStatus : parsedChatStatus || visibleStatus;
|
|
16589
17479
|
return {
|
|
16590
17480
|
type: this.type,
|
|
16591
17481
|
name: this.provider.name,
|
|
@@ -16595,7 +17485,7 @@ var CliProviderInstance = class {
|
|
|
16595
17485
|
activeChat: {
|
|
16596
17486
|
id: `${this.type}_${this.workingDir}`,
|
|
16597
17487
|
title: parsedStatus?.title || dirName,
|
|
16598
|
-
status:
|
|
17488
|
+
status: activeChatStatus,
|
|
16599
17489
|
messages: mergedMessages,
|
|
16600
17490
|
activeModal: autoApproveActive ? null : parsedStatus?.activeModal ?? adapterStatus.activeModal,
|
|
16601
17491
|
inputContent: ""
|
|
@@ -16725,6 +17615,103 @@ var CliProviderInstance = class {
|
|
|
16725
17615
|
}
|
|
16726
17616
|
this.applyProviderResponse(parsed.payload, { phase: "immediate" });
|
|
16727
17617
|
}
|
|
17618
|
+
completionHasFinalAssistantMessage(messages) {
|
|
17619
|
+
const visibleMessages = (Array.isArray(messages) ? messages : []).filter((message) => isUserFacingChatMessage(message));
|
|
17620
|
+
const lastVisible = visibleMessages[visibleMessages.length - 1];
|
|
17621
|
+
const role = typeof lastVisible?.role === "string" ? lastVisible.role.trim().toLowerCase() : "";
|
|
17622
|
+
const content = lastVisible ? flattenContent(lastVisible.content).trim() : "";
|
|
17623
|
+
return role === "assistant" && !!content;
|
|
17624
|
+
}
|
|
17625
|
+
hasAdapterPendingResponse() {
|
|
17626
|
+
const adapterAny = this.adapter;
|
|
17627
|
+
if (adapterAny?.isWaitingForResponse === true) return true;
|
|
17628
|
+
if (adapterAny?.currentTurnScope) return true;
|
|
17629
|
+
try {
|
|
17630
|
+
if (typeof this.adapter.isProcessing === "function" && this.adapter.isProcessing()) return true;
|
|
17631
|
+
} catch {
|
|
17632
|
+
}
|
|
17633
|
+
try {
|
|
17634
|
+
const partial = typeof this.adapter.getPartialResponse === "function" ? this.adapter.getPartialResponse() : "";
|
|
17635
|
+
if (typeof partial === "string" && partial.trim()) return true;
|
|
17636
|
+
} catch {
|
|
17637
|
+
}
|
|
17638
|
+
return false;
|
|
17639
|
+
}
|
|
17640
|
+
shouldSuppressStaleParsedBusyStatus(parsedStatus, adapterStatus) {
|
|
17641
|
+
const parsedRawStatus = typeof parsedStatus?.status === "string" ? parsedStatus.status.trim() : "";
|
|
17642
|
+
const adapterRawStatus = typeof adapterStatus?.status === "string" ? adapterStatus.status.trim() : "";
|
|
17643
|
+
if (!isCliGeneratingLikeStatus(parsedRawStatus)) return false;
|
|
17644
|
+
if (adapterRawStatus !== "idle") return false;
|
|
17645
|
+
if (hasNonEmptyCliModalButtons(parsedStatus?.activeModal ?? parsedStatus?.modal)) return false;
|
|
17646
|
+
return !this.hasAdapterPendingResponse();
|
|
17647
|
+
}
|
|
17648
|
+
getCompletedFinalizationBlockReason(latestVisibleStatus) {
|
|
17649
|
+
if (latestVisibleStatus !== "idle") return `status:${latestVisibleStatus}`;
|
|
17650
|
+
const adapterAny = this.adapter;
|
|
17651
|
+
if (adapterAny?.isWaitingForResponse === true) return "adapter_waiting_for_response";
|
|
17652
|
+
if (adapterAny?.currentTurnScope) return "adapter_turn_scope_active";
|
|
17653
|
+
const partial = typeof this.adapter.getPartialResponse === "function" ? this.adapter.getPartialResponse() : "";
|
|
17654
|
+
if (typeof partial === "string" && partial.trim()) return "partial_response_pending";
|
|
17655
|
+
let parsed;
|
|
17656
|
+
try {
|
|
17657
|
+
parsed = this.adapter.getScriptParsedStatus();
|
|
17658
|
+
} catch (error) {
|
|
17659
|
+
return `parse_error:${error?.message || String(error)}`;
|
|
17660
|
+
}
|
|
17661
|
+
const parsedStatus = typeof parsed?.status === "string" ? parsed.status : "unknown";
|
|
17662
|
+
if (parsedStatus !== "idle") return `parsed_status:${parsedStatus}`;
|
|
17663
|
+
if (parsed?.activeModal || parsed?.modal) return "parsed_modal_active";
|
|
17664
|
+
if (!this.completionHasFinalAssistantMessage(parsed?.messages)) return "missing_final_assistant";
|
|
17665
|
+
return null;
|
|
17666
|
+
}
|
|
17667
|
+
scheduleCompletedDebounceFlush(delayMs) {
|
|
17668
|
+
if (this.completedDebounceTimer) clearTimeout(this.completedDebounceTimer);
|
|
17669
|
+
this.completedDebounceTimer = setTimeout(() => this.flushCompletedDebounceIfFinalized(), delayMs);
|
|
17670
|
+
}
|
|
17671
|
+
flushCompletedDebounceIfFinalized() {
|
|
17672
|
+
const pending = this.completedDebouncePending;
|
|
17673
|
+
if (!pending) {
|
|
17674
|
+
this.completedDebounceTimer = null;
|
|
17675
|
+
return;
|
|
17676
|
+
}
|
|
17677
|
+
const latestStatus = this.adapter.getStatus({ allowParse: false });
|
|
17678
|
+
const latestAutoApproveActive = latestStatus.status === "waiting_approval" && this.shouldAutoApprove();
|
|
17679
|
+
const latestVisibleStatus = latestAutoApproveActive ? "generating" : latestStatus.status;
|
|
17680
|
+
if (latestVisibleStatus !== "idle") {
|
|
17681
|
+
LOG.info("CLI", `[${this.type}] cancelled pending completed (resumed ${latestVisibleStatus})`);
|
|
17682
|
+
this.completedDebouncePending = null;
|
|
17683
|
+
this.completedDebounceTimer = null;
|
|
17684
|
+
return;
|
|
17685
|
+
}
|
|
17686
|
+
const blockReason = this.getCompletedFinalizationBlockReason(latestVisibleStatus);
|
|
17687
|
+
if (blockReason) {
|
|
17688
|
+
const waitedMs = Date.now() - pending.firstObservedAt;
|
|
17689
|
+
if (waitedMs < COMPLETED_FINALIZATION_MAX_WAIT_MS) {
|
|
17690
|
+
if (pending.loggedBlockReason !== blockReason) {
|
|
17691
|
+
LOG.info("CLI", `[${this.type}] waiting to emit completed until transcript finalizes (${blockReason})`);
|
|
17692
|
+
pending.loggedBlockReason = blockReason;
|
|
17693
|
+
}
|
|
17694
|
+
this.scheduleCompletedDebounceFlush(COMPLETED_FINALIZATION_RETRY_MS);
|
|
17695
|
+
return;
|
|
17696
|
+
}
|
|
17697
|
+
LOG.warn("CLI", `[${this.type}] suppressed completed event after ${waitedMs}ms without finalized assistant turn (${blockReason})`);
|
|
17698
|
+
this.completedDebouncePending = null;
|
|
17699
|
+
this.completedDebounceTimer = null;
|
|
17700
|
+
this.generatingStartedAt = 0;
|
|
17701
|
+
return;
|
|
17702
|
+
}
|
|
17703
|
+
LOG.info("CLI", `[${this.type}] completed in ${pending.duration}s`);
|
|
17704
|
+
this.pushEvent({
|
|
17705
|
+
event: "agent:generating_completed",
|
|
17706
|
+
chatTitle: pending.chatTitle,
|
|
17707
|
+
duration: pending.duration,
|
|
17708
|
+
timestamp: pending.timestamp,
|
|
17709
|
+
finalSummary: extractFinalSummaryFromMessages(this.adapter?.getScriptParsedStatus()?.messages)
|
|
17710
|
+
});
|
|
17711
|
+
this.completedDebouncePending = null;
|
|
17712
|
+
this.completedDebounceTimer = null;
|
|
17713
|
+
this.generatingStartedAt = 0;
|
|
17714
|
+
}
|
|
16728
17715
|
maybeAutoApproveStatus(adapterStatus, now = Date.now()) {
|
|
16729
17716
|
const autoApproveActive = adapterStatus?.status === "waiting_approval" && this.shouldAutoApprove();
|
|
16730
17717
|
if (autoApproveActive && !this.autoApproveBusy) {
|
|
@@ -16822,27 +17809,11 @@ var CliProviderInstance = class {
|
|
|
16822
17809
|
this.generatingDebouncePending = null;
|
|
16823
17810
|
this.generatingStartedAt = 0;
|
|
16824
17811
|
} else {
|
|
16825
|
-
|
|
16826
|
-
this.
|
|
16827
|
-
this.completedDebounceTimer = setTimeout(() => {
|
|
16828
|
-
if (this.completedDebouncePending) {
|
|
16829
|
-
const latestStatus = this.adapter.getStatus({ allowParse: false });
|
|
16830
|
-
const latestAutoApproveActive = latestStatus.status === "waiting_approval" && this.shouldAutoApprove();
|
|
16831
|
-
const latestVisibleStatus = latestAutoApproveActive ? "generating" : latestStatus.status;
|
|
16832
|
-
if (latestVisibleStatus !== "idle") {
|
|
16833
|
-
LOG.info("CLI", `[${this.type}] cancelled pending completed (resumed ${latestVisibleStatus})`);
|
|
16834
|
-
this.completedDebouncePending = null;
|
|
16835
|
-
this.completedDebounceTimer = null;
|
|
16836
|
-
return;
|
|
16837
|
-
}
|
|
16838
|
-
LOG.info("CLI", `[${this.type}] completed in ${this.completedDebouncePending.duration}s`);
|
|
16839
|
-
this.pushEvent({ event: "agent:generating_completed", ...this.completedDebouncePending });
|
|
16840
|
-
this.completedDebouncePending = null;
|
|
16841
|
-
this.generatingStartedAt = 0;
|
|
16842
|
-
}
|
|
16843
|
-
this.completedDebounceTimer = null;
|
|
16844
|
-
}, 3e3);
|
|
17812
|
+
this.completedDebouncePending = { chatTitle, duration, timestamp: now, firstObservedAt: now };
|
|
17813
|
+
this.scheduleCompletedDebounceFlush(3e3);
|
|
16845
17814
|
}
|
|
17815
|
+
} else if (newStatus === "idle" && this.lastStatus === "starting") {
|
|
17816
|
+
this.pushEvent({ event: "agent:ready", chatTitle, timestamp: now });
|
|
16846
17817
|
} else if (newStatus === "stopped") {
|
|
16847
17818
|
if (this.generatingDebounceTimer) {
|
|
16848
17819
|
clearTimeout(this.generatingDebounceTimer);
|
|
@@ -18478,7 +19449,7 @@ ${rawInput}` : rawInput;
|
|
|
18478
19449
|
});
|
|
18479
19450
|
} else if (newStatus === "idle" && (this.lastStatus === "generating" || this.lastStatus === "waiting_approval")) {
|
|
18480
19451
|
const duration = this.generatingStartedAt ? Math.round((now - this.generatingStartedAt) / 1e3) : 0;
|
|
18481
|
-
this.pushEvent({ event: "agent:generating_completed", chatTitle, duration, timestamp: now });
|
|
19452
|
+
this.pushEvent({ event: "agent:generating_completed", chatTitle, duration, timestamp: now, finalSummary: extractFinalSummaryFromMessages(this.messages) });
|
|
18482
19453
|
this.generatingStartedAt = 0;
|
|
18483
19454
|
} else if (newStatus === "stopped") {
|
|
18484
19455
|
this.pushEvent({ event: "agent:stopped", chatTitle, timestamp: now });
|
|
@@ -18586,9 +19557,6 @@ function buildCoordinatorDelegatedCliLaunchOptions(input) {
|
|
|
18586
19557
|
const cliType = String(input.cliType || "").trim();
|
|
18587
19558
|
const cliArgs = Array.isArray(input.cliArgs) ? [...input.cliArgs] : [];
|
|
18588
19559
|
const env = { ...input.env || {}, ...COORDINATOR_DELEGATED_ENV_UNSETS };
|
|
18589
|
-
if (cliType === "hermes-cli" && !hasCliArg(cliArgs, "--ignore-user-config")) {
|
|
18590
|
-
cliArgs.unshift("--ignore-user-config");
|
|
18591
|
-
}
|
|
18592
19560
|
if (cliType === "claude-cli" && !hasCliArg(cliArgs, "--mcp-config")) {
|
|
18593
19561
|
cliArgs.unshift("--mcp-config", ensureEmptyDelegatedMcpConfig(input.workspace));
|
|
18594
19562
|
}
|
|
@@ -21665,6 +22633,7 @@ function getAvailableIdeIds() {
|
|
|
21665
22633
|
|
|
21666
22634
|
// src/commands/router.ts
|
|
21667
22635
|
init_config();
|
|
22636
|
+
init_cli_detector();
|
|
21668
22637
|
init_logger();
|
|
21669
22638
|
|
|
21670
22639
|
// src/logging/command-log.ts
|
|
@@ -21834,7 +22803,9 @@ function resolveHermesMeshCoordinatorSetup(options) {
|
|
|
21834
22803
|
const mcpServer = resolveAdhdevMcpServerLaunch({
|
|
21835
22804
|
meshId: options.meshId,
|
|
21836
22805
|
nodeExecutable: options.nodeExecutable,
|
|
21837
|
-
adhdevMcpEntryPath: options.adhdevMcpEntryPath
|
|
22806
|
+
adhdevMcpEntryPath: options.adhdevMcpEntryPath,
|
|
22807
|
+
adhdevMcpTransport: options.adhdevMcpTransport,
|
|
22808
|
+
adhdevMcpPort: options.adhdevMcpPort
|
|
21838
22809
|
});
|
|
21839
22810
|
if (!mcpServer) {
|
|
21840
22811
|
return {
|
|
@@ -21901,7 +22872,9 @@ function resolveMeshCoordinatorSetup(options) {
|
|
|
21901
22872
|
const mcpServer = resolveAdhdevMcpServerLaunch({
|
|
21902
22873
|
meshId,
|
|
21903
22874
|
nodeExecutable: options.nodeExecutable,
|
|
21904
|
-
adhdevMcpEntryPath: options.adhdevMcpEntryPath
|
|
22875
|
+
adhdevMcpEntryPath: options.adhdevMcpEntryPath,
|
|
22876
|
+
adhdevMcpTransport: options.adhdevMcpTransport,
|
|
22877
|
+
adhdevMcpPort: options.adhdevMcpPort
|
|
21905
22878
|
});
|
|
21906
22879
|
if (!mcpServer) {
|
|
21907
22880
|
return {
|
|
@@ -21923,6 +22896,22 @@ function resolveMeshCoordinatorSetup(options) {
|
|
|
21923
22896
|
if (!instructions || !template?.trim()) {
|
|
21924
22897
|
return { kind: "unsupported", reason: "Provider manual MCP setup is missing instructions or template" };
|
|
21925
22898
|
}
|
|
22899
|
+
const renderedTemplate = renderMeshCoordinatorTemplate(template, {
|
|
22900
|
+
meshId,
|
|
22901
|
+
workspace,
|
|
22902
|
+
serverName,
|
|
22903
|
+
adhdevMcpCommand: options.adhdevMcpCommand || DEFAULT_ADHDEV_MCP_COMMAND
|
|
22904
|
+
});
|
|
22905
|
+
const isCliCommand = !renderedTemplate.trim().includes("\n") && !renderedTemplate.trim().startsWith("{");
|
|
22906
|
+
if (isCliCommand) {
|
|
22907
|
+
return {
|
|
22908
|
+
kind: "cli_command",
|
|
22909
|
+
serverName,
|
|
22910
|
+
command: renderedTemplate.trim(),
|
|
22911
|
+
requiresRestart: mcpConfig.requiresRestart === true,
|
|
22912
|
+
instructions
|
|
22913
|
+
};
|
|
22914
|
+
}
|
|
21926
22915
|
return {
|
|
21927
22916
|
kind: "manual",
|
|
21928
22917
|
serverName,
|
|
@@ -21930,12 +22919,7 @@ function resolveMeshCoordinatorSetup(options) {
|
|
|
21930
22919
|
configPathCommand: mcpConfig.configPathCommand,
|
|
21931
22920
|
requiresRestart: mcpConfig.requiresRestart === true,
|
|
21932
22921
|
instructions,
|
|
21933
|
-
template:
|
|
21934
|
-
meshId,
|
|
21935
|
-
workspace,
|
|
21936
|
-
serverName,
|
|
21937
|
-
adhdevMcpCommand: options.adhdevMcpCommand || DEFAULT_ADHDEV_MCP_COMMAND
|
|
21938
|
-
})
|
|
22922
|
+
template: renderedTemplate
|
|
21939
22923
|
};
|
|
21940
22924
|
}
|
|
21941
22925
|
return {
|
|
@@ -21964,11 +22948,27 @@ function resolveAdhdevMcpServerLaunch(options) {
|
|
|
21964
22948
|
if (!entryPath) return null;
|
|
21965
22949
|
const nodeExecutable = resolveMcpNodeExecutable(options.nodeExecutable);
|
|
21966
22950
|
if (!nodeExecutable) return null;
|
|
22951
|
+
const transport = resolveMcpTransport(options.adhdevMcpTransport);
|
|
22952
|
+
const args = [entryPath, "--mode", transport, "--repo-mesh", options.meshId];
|
|
22953
|
+
const port = resolveMcpPort(options.adhdevMcpPort);
|
|
22954
|
+
if (port !== void 0) args.push("--port", String(port));
|
|
21967
22955
|
return {
|
|
21968
22956
|
command: nodeExecutable,
|
|
21969
|
-
args
|
|
22957
|
+
args
|
|
21970
22958
|
};
|
|
21971
22959
|
}
|
|
22960
|
+
function resolveMcpTransport(explicitTransport) {
|
|
22961
|
+
if (explicitTransport === "local" || explicitTransport === "ipc") return explicitTransport;
|
|
22962
|
+
const envTransport = process.env.ADHDEV_COORDINATOR_MCP_TRANSPORT?.trim();
|
|
22963
|
+
return envTransport === "local" ? "local" : "ipc";
|
|
22964
|
+
}
|
|
22965
|
+
function resolveMcpPort(explicitPort) {
|
|
22966
|
+
if (typeof explicitPort === "number" && Number.isInteger(explicitPort) && explicitPort > 0) return explicitPort;
|
|
22967
|
+
const raw = process.env.ADHDEV_COORDINATOR_MCP_PORT?.trim();
|
|
22968
|
+
if (!raw) return void 0;
|
|
22969
|
+
const parsed = Number(raw);
|
|
22970
|
+
return Number.isInteger(parsed) && parsed > 0 ? parsed : void 0;
|
|
22971
|
+
}
|
|
21972
22972
|
function resolveMcpNodeExecutable(explicitExecutable) {
|
|
21973
22973
|
const explicit = explicitExecutable?.trim();
|
|
21974
22974
|
if (explicit) return explicit;
|
|
@@ -22780,6 +23780,209 @@ async function resolveProviderTypeFromPriority(args) {
|
|
|
22780
23780
|
}
|
|
22781
23781
|
return { error: `No usable provider detected for node '${args.nodeId}' from providerPriority: ${failed.join("; ")}` };
|
|
22782
23782
|
}
|
|
23783
|
+
var REFINE_VALIDATION_CATEGORIES = ["typecheck", "test", "lint", "build"];
|
|
23784
|
+
var REFINE_VALIDATION_TIMEOUT_MS = 12e4;
|
|
23785
|
+
var REFINE_VALIDATION_OUTPUT_LIMIT_BYTES = 128 * 1024;
|
|
23786
|
+
var REFINE_VALIDATION_SUMMARY_CHARS = 2e3;
|
|
23787
|
+
var REFINE_VALIDATION_MAX_COMMANDS = 4;
|
|
23788
|
+
function truncateValidationOutput(value) {
|
|
23789
|
+
const text = typeof value === "string" ? value : value == null ? "" : String(value);
|
|
23790
|
+
if (text.length <= REFINE_VALIDATION_SUMMARY_CHARS) return text;
|
|
23791
|
+
return `${text.slice(0, REFINE_VALIDATION_SUMMARY_CHARS)}
|
|
23792
|
+
[truncated ${text.length - REFINE_VALIDATION_SUMMARY_CHARS} chars]`;
|
|
23793
|
+
}
|
|
23794
|
+
function readPackageScripts(workspace) {
|
|
23795
|
+
try {
|
|
23796
|
+
const packageJsonPath = (0, import_path6.join)(workspace, "package.json");
|
|
23797
|
+
const parsed = JSON.parse(fs10.readFileSync(packageJsonPath, "utf-8"));
|
|
23798
|
+
return parsed?.scripts && typeof parsed.scripts === "object" && !Array.isArray(parsed.scripts) ? parsed.scripts : {};
|
|
23799
|
+
} catch {
|
|
23800
|
+
return {};
|
|
23801
|
+
}
|
|
23802
|
+
}
|
|
23803
|
+
function tokenizeValidationCommand(command) {
|
|
23804
|
+
const trimmed = command.trim();
|
|
23805
|
+
if (!trimmed) return null;
|
|
23806
|
+
if (/[;&|<>`$\\\n\r'\"]/.test(trimmed)) return null;
|
|
23807
|
+
const tokens = trimmed.split(/\s+/).filter(Boolean);
|
|
23808
|
+
if (!tokens.length) return null;
|
|
23809
|
+
if (tokens.some((token) => !/^[A-Za-z0-9_@./:=+-]+$/.test(token))) return null;
|
|
23810
|
+
return tokens;
|
|
23811
|
+
}
|
|
23812
|
+
function scriptMatchesValidationCategory(scriptName, category) {
|
|
23813
|
+
return scriptName === category || scriptName.startsWith(`${category}:`);
|
|
23814
|
+
}
|
|
23815
|
+
function parsePackageManagerValidationCommand(rawCommand, category, scripts, source) {
|
|
23816
|
+
const tokens = tokenizeValidationCommand(rawCommand);
|
|
23817
|
+
if (!tokens) {
|
|
23818
|
+
return { rejected: { command: rawCommand, category, source, reason: "unsafe command string is not allowlisted" } };
|
|
23819
|
+
}
|
|
23820
|
+
const [binary, second, third, ...rest] = tokens;
|
|
23821
|
+
let scriptName = "";
|
|
23822
|
+
let command = binary;
|
|
23823
|
+
let args = [];
|
|
23824
|
+
if ((binary === "npm" || binary === "pnpm" || binary === "bun") && second === "run" && third) {
|
|
23825
|
+
scriptName = third;
|
|
23826
|
+
args = ["run", scriptName, ...rest];
|
|
23827
|
+
} else if (binary === "npm" && second === "test" && !third) {
|
|
23828
|
+
scriptName = "test";
|
|
23829
|
+
args = ["test"];
|
|
23830
|
+
} else if (binary === "yarn" && second === "run" && third) {
|
|
23831
|
+
scriptName = third;
|
|
23832
|
+
args = ["run", scriptName, ...rest];
|
|
23833
|
+
} else if (binary === "yarn" && second && !third) {
|
|
23834
|
+
scriptName = second;
|
|
23835
|
+
args = [scriptName];
|
|
23836
|
+
} else {
|
|
23837
|
+
return { rejected: { command: rawCommand, category, source, reason: "command is not a supported package-manager script invocation" } };
|
|
23838
|
+
}
|
|
23839
|
+
if (!scriptName || !Object.prototype.hasOwnProperty.call(scripts, scriptName)) {
|
|
23840
|
+
return { rejected: { command: rawCommand, category, source, script: scriptName, reason: "script is not declared in package.json" } };
|
|
23841
|
+
}
|
|
23842
|
+
if (!scriptMatchesValidationCategory(scriptName, category)) {
|
|
23843
|
+
return { rejected: { command: rawCommand, category, source, script: scriptName, reason: "script name is outside the validation category allowlist" } };
|
|
23844
|
+
}
|
|
23845
|
+
return {
|
|
23846
|
+
command: {
|
|
23847
|
+
command,
|
|
23848
|
+
args,
|
|
23849
|
+
displayCommand: [command, ...args].join(" "),
|
|
23850
|
+
category,
|
|
23851
|
+
source
|
|
23852
|
+
}
|
|
23853
|
+
};
|
|
23854
|
+
}
|
|
23855
|
+
function collectProjectContextValidationCandidates(mesh) {
|
|
23856
|
+
const commands = mesh?.projectContext?.commands;
|
|
23857
|
+
if (!commands || typeof commands !== "object" || Array.isArray(commands)) return [];
|
|
23858
|
+
const candidates = [];
|
|
23859
|
+
for (const category of REFINE_VALIDATION_CATEGORIES) {
|
|
23860
|
+
const entries = Array.isArray(commands[category]) ? commands[category] : [];
|
|
23861
|
+
for (const entry of entries) {
|
|
23862
|
+
if (typeof entry?.command !== "string") continue;
|
|
23863
|
+
candidates.push({
|
|
23864
|
+
command: entry.command,
|
|
23865
|
+
category,
|
|
23866
|
+
source: typeof entry.sourcePath === "string" ? entry.sourcePath : "projectContext.commands",
|
|
23867
|
+
confidence: typeof entry.confidence === "string" ? entry.confidence : void 0
|
|
23868
|
+
});
|
|
23869
|
+
}
|
|
23870
|
+
}
|
|
23871
|
+
return candidates.sort((a, b) => {
|
|
23872
|
+
const rank = (value) => value === "high" ? 0 : value === "medium" ? 1 : 2;
|
|
23873
|
+
return rank(a.confidence) - rank(b.confidence);
|
|
23874
|
+
});
|
|
23875
|
+
}
|
|
23876
|
+
function collectPolicyValidationCandidates(mesh) {
|
|
23877
|
+
const policy = mesh?.policy && typeof mesh.policy === "object" && !Array.isArray(mesh.policy) ? mesh.policy : {};
|
|
23878
|
+
const configured = Array.isArray(policy.validationCommands) ? policy.validationCommands : Array.isArray(policy.validationGate?.commands) ? policy.validationGate.commands : [];
|
|
23879
|
+
return configured.map((entry) => typeof entry === "string" ? { command: entry, category: "", source: "mesh.policy.validationCommands" } : entry).filter((entry) => entry && typeof entry.command === "string").map((entry) => {
|
|
23880
|
+
const commandText = entry.command.trim();
|
|
23881
|
+
const category = REFINE_VALIDATION_CATEGORIES.find((cat) => commandText.includes(` ${cat}`)) ?? "";
|
|
23882
|
+
return { command: commandText, category, source: "mesh.policy.validationCommands" };
|
|
23883
|
+
}).filter((entry) => !!entry.category);
|
|
23884
|
+
}
|
|
23885
|
+
function selectMeshRefineValidationCommands(mesh, workspace) {
|
|
23886
|
+
const scripts = readPackageScripts(workspace);
|
|
23887
|
+
const rejectedCommands = [];
|
|
23888
|
+
const selected = [];
|
|
23889
|
+
const seen = /* @__PURE__ */ new Set();
|
|
23890
|
+
const candidates = [
|
|
23891
|
+
...collectPolicyValidationCandidates(mesh),
|
|
23892
|
+
...collectProjectContextValidationCandidates(mesh)
|
|
23893
|
+
];
|
|
23894
|
+
for (const candidate of candidates) {
|
|
23895
|
+
const parsed = parsePackageManagerValidationCommand(candidate.command, candidate.category, scripts, candidate.source);
|
|
23896
|
+
if (parsed.rejected) {
|
|
23897
|
+
rejectedCommands.push(parsed.rejected);
|
|
23898
|
+
continue;
|
|
23899
|
+
}
|
|
23900
|
+
if (!parsed.command || seen.has(parsed.command.displayCommand)) continue;
|
|
23901
|
+
selected.push(parsed.command);
|
|
23902
|
+
seen.add(parsed.command.displayCommand);
|
|
23903
|
+
if (selected.length >= REFINE_VALIDATION_MAX_COMMANDS) break;
|
|
23904
|
+
}
|
|
23905
|
+
if (!selected.length && candidates.length === 0) {
|
|
23906
|
+
for (const category of REFINE_VALIDATION_CATEGORIES) {
|
|
23907
|
+
if (!Object.prototype.hasOwnProperty.call(scripts, category)) continue;
|
|
23908
|
+
const fallback = parsePackageManagerValidationCommand(`npm run ${category}`, category, scripts, "package.json:scripts");
|
|
23909
|
+
if (fallback.command && !seen.has(fallback.command.displayCommand)) {
|
|
23910
|
+
selected.push(fallback.command);
|
|
23911
|
+
seen.add(fallback.command.displayCommand);
|
|
23912
|
+
} else if (fallback.rejected) {
|
|
23913
|
+
rejectedCommands.push(fallback.rejected);
|
|
23914
|
+
}
|
|
23915
|
+
if (selected.length >= 2) break;
|
|
23916
|
+
}
|
|
23917
|
+
}
|
|
23918
|
+
return {
|
|
23919
|
+
commands: selected,
|
|
23920
|
+
rejectedCommands,
|
|
23921
|
+
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"
|
|
23922
|
+
};
|
|
23923
|
+
}
|
|
23924
|
+
async function runMeshRefineValidationGate(mesh, workspace) {
|
|
23925
|
+
const { execFile: execFile3 } = await import("child_process");
|
|
23926
|
+
const { promisify: promisify3 } = await import("util");
|
|
23927
|
+
const execFileAsync3 = promisify3(execFile3);
|
|
23928
|
+
const selection = selectMeshRefineValidationCommands(mesh, workspace);
|
|
23929
|
+
const summary = {
|
|
23930
|
+
status: "skipped",
|
|
23931
|
+
required: true,
|
|
23932
|
+
commandsRun: [],
|
|
23933
|
+
rejectedCommands: selection.rejectedCommands,
|
|
23934
|
+
skippedReason: void 0,
|
|
23935
|
+
timeoutMs: REFINE_VALIDATION_TIMEOUT_MS,
|
|
23936
|
+
outputLimitBytes: REFINE_VALIDATION_OUTPUT_LIMIT_BYTES
|
|
23937
|
+
};
|
|
23938
|
+
if (!selection.commands.length) {
|
|
23939
|
+
summary.skippedReason = "validation_unavailable: no allowlisted projectContext, mesh policy, or package.json build/test/typecheck/lint command was available";
|
|
23940
|
+
return summary;
|
|
23941
|
+
}
|
|
23942
|
+
for (const candidate of selection.commands) {
|
|
23943
|
+
const startedAt = Date.now();
|
|
23944
|
+
try {
|
|
23945
|
+
const result = await execFileAsync3(candidate.command, candidate.args, {
|
|
23946
|
+
cwd: workspace,
|
|
23947
|
+
encoding: "utf8",
|
|
23948
|
+
timeout: REFINE_VALIDATION_TIMEOUT_MS,
|
|
23949
|
+
maxBuffer: REFINE_VALIDATION_OUTPUT_LIMIT_BYTES,
|
|
23950
|
+
env: { ...process.env, CI: process.env.CI || "1" }
|
|
23951
|
+
});
|
|
23952
|
+
summary.commandsRun.push({
|
|
23953
|
+
command: candidate.command,
|
|
23954
|
+
args: candidate.args,
|
|
23955
|
+
displayCommand: candidate.displayCommand,
|
|
23956
|
+
category: candidate.category,
|
|
23957
|
+
source: candidate.source,
|
|
23958
|
+
passed: true,
|
|
23959
|
+
exitCode: 0,
|
|
23960
|
+
durationMs: Date.now() - startedAt,
|
|
23961
|
+
stdout: truncateValidationOutput(result.stdout),
|
|
23962
|
+
stderr: truncateValidationOutput(result.stderr)
|
|
23963
|
+
});
|
|
23964
|
+
} catch (error) {
|
|
23965
|
+
summary.commandsRun.push({
|
|
23966
|
+
command: candidate.command,
|
|
23967
|
+
args: candidate.args,
|
|
23968
|
+
displayCommand: candidate.displayCommand,
|
|
23969
|
+
category: candidate.category,
|
|
23970
|
+
source: candidate.source,
|
|
23971
|
+
passed: false,
|
|
23972
|
+
exitCode: typeof error?.code === "number" ? error.code : null,
|
|
23973
|
+
signal: typeof error?.signal === "string" ? error.signal : null,
|
|
23974
|
+
timedOut: error?.killed === true || /timed out/i.test(String(error?.message || "")),
|
|
23975
|
+
durationMs: Date.now() - startedAt,
|
|
23976
|
+
stdout: truncateValidationOutput(error?.stdout),
|
|
23977
|
+
stderr: truncateValidationOutput(error?.stderr || error?.message)
|
|
23978
|
+
});
|
|
23979
|
+
summary.status = "failed";
|
|
23980
|
+
return summary;
|
|
23981
|
+
}
|
|
23982
|
+
}
|
|
23983
|
+
summary.status = "passed";
|
|
23984
|
+
return summary;
|
|
23985
|
+
}
|
|
22783
23986
|
function loadYamlModule() {
|
|
22784
23987
|
return yaml;
|
|
22785
23988
|
}
|
|
@@ -22809,6 +24012,34 @@ function loadHermesCoordinatorBaseConfig(targetConfigPath) {
|
|
|
22809
24012
|
const { mcp_servers: _mcpServers, ...baseConfig } = parsed;
|
|
22810
24013
|
return { config: baseConfig, sourceHome, sourceConfigPath };
|
|
22811
24014
|
}
|
|
24015
|
+
function stripHermesCoordinatorTempModelProviderOverrides(config) {
|
|
24016
|
+
const {
|
|
24017
|
+
model: _model,
|
|
24018
|
+
provider: _provider,
|
|
24019
|
+
default_model: _defaultModel,
|
|
24020
|
+
defaultProvider: _defaultProvider,
|
|
24021
|
+
default_provider: _defaultProviderSnake,
|
|
24022
|
+
modelProvider: _modelProvider,
|
|
24023
|
+
model_provider: _modelProviderSnake,
|
|
24024
|
+
...sanitized
|
|
24025
|
+
} = config;
|
|
24026
|
+
const delegation = sanitized.delegation;
|
|
24027
|
+
if (delegation && typeof delegation === "object" && !Array.isArray(delegation)) {
|
|
24028
|
+
const {
|
|
24029
|
+
model: _delegationModel,
|
|
24030
|
+
provider: _delegationProvider,
|
|
24031
|
+
modelProvider: _delegationModelProvider,
|
|
24032
|
+
model_provider: _delegationModelProviderSnake,
|
|
24033
|
+
...delegationRest
|
|
24034
|
+
} = delegation;
|
|
24035
|
+
if (Object.keys(delegationRest).length > 0) {
|
|
24036
|
+
sanitized.delegation = delegationRest;
|
|
24037
|
+
} else {
|
|
24038
|
+
delete sanitized.delegation;
|
|
24039
|
+
}
|
|
24040
|
+
}
|
|
24041
|
+
return sanitized;
|
|
24042
|
+
}
|
|
22812
24043
|
function copyHermesCoordinatorCredentialFiles(sourceHome, targetHome) {
|
|
22813
24044
|
if ((0, import_path6.resolve)(sourceHome) === (0, import_path6.resolve)(targetHome)) return;
|
|
22814
24045
|
for (const fileName of [".env", "auth.json"]) {
|
|
@@ -22966,9 +24197,191 @@ var DaemonCommandRouter = class {
|
|
|
22966
24197
|
if (record?.meta?.meshNodeId === nodeId) return true;
|
|
22967
24198
|
return false;
|
|
22968
24199
|
}
|
|
24200
|
+
async cleanupLocalWorktreeNode(args) {
|
|
24201
|
+
const workspace = typeof args.node?.workspace === "string" ? args.node.workspace.trim() : "";
|
|
24202
|
+
if (!workspace) {
|
|
24203
|
+
return {
|
|
24204
|
+
success: false,
|
|
24205
|
+
code: "mesh_worktree_cleanup_missing_workspace",
|
|
24206
|
+
error: `Worktree node '${args.nodeId}' is missing workspace metadata`,
|
|
24207
|
+
recoveryHint: "Inspect the mesh node record before removing it, or remove stale metadata manually only after confirming no managed worktree remains."
|
|
24208
|
+
};
|
|
24209
|
+
}
|
|
24210
|
+
const worktreeExists = fs10.existsSync(workspace);
|
|
24211
|
+
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);
|
|
24212
|
+
const repoRoot = typeof sourceNode?.repoRoot === "string" && sourceNode.repoRoot.trim() ? sourceNode.repoRoot.trim() : typeof sourceNode?.workspace === "string" && sourceNode.workspace.trim() ? sourceNode.workspace.trim() : "";
|
|
24213
|
+
if (!worktreeExists) {
|
|
24214
|
+
return { success: true, skipped: true, removedPath: workspace, repoRoot: repoRoot || void 0, reason: "worktree_path_missing" };
|
|
24215
|
+
}
|
|
24216
|
+
if (!repoRoot || !fs10.existsSync(repoRoot)) {
|
|
24217
|
+
return {
|
|
24218
|
+
success: false,
|
|
24219
|
+
code: "mesh_worktree_cleanup_missing_source_repo",
|
|
24220
|
+
error: `Refusing to remove worktree '${workspace}' because the source repo root is unavailable`,
|
|
24221
|
+
recoveryHint: "Run mesh_remove_node from the machine that owns the source repo, or verify the source node metadata before retrying."
|
|
24222
|
+
};
|
|
24223
|
+
}
|
|
24224
|
+
if (typeof args.node?.worktreeBranch !== "string" || !args.node.worktreeBranch.trim()) {
|
|
24225
|
+
return {
|
|
24226
|
+
success: false,
|
|
24227
|
+
code: "mesh_worktree_cleanup_missing_branch",
|
|
24228
|
+
error: `Refusing to remove worktree '${workspace}' because worktreeBranch metadata is missing`,
|
|
24229
|
+
recoveryHint: "Confirm this is an ADHDev-managed worktree before removing it manually; managed worktree nodes include worktreeBranch metadata."
|
|
24230
|
+
};
|
|
24231
|
+
}
|
|
24232
|
+
const { resolveWorktreePath: resolveWorktreePath2, listWorktrees: listWorktrees2, removeWorktree: removeWorktree2 } = await Promise.resolve().then(() => (init_git_worktree(), git_worktree_exports));
|
|
24233
|
+
const normalizePath = (value) => {
|
|
24234
|
+
const resolved = (0, import_path6.resolve)(value);
|
|
24235
|
+
try {
|
|
24236
|
+
return fs10.realpathSync(resolved);
|
|
24237
|
+
} catch {
|
|
24238
|
+
return resolved;
|
|
24239
|
+
}
|
|
24240
|
+
};
|
|
24241
|
+
const expectedPath = normalizePath(resolveWorktreePath2(repoRoot, String(args.mesh?.name || args.mesh?.id || "mesh"), args.node.worktreeBranch));
|
|
24242
|
+
const actualPath = normalizePath(workspace);
|
|
24243
|
+
if (actualPath !== expectedPath) {
|
|
24244
|
+
return {
|
|
24245
|
+
success: false,
|
|
24246
|
+
code: "mesh_worktree_cleanup_unexpected_path",
|
|
24247
|
+
error: `Refusing to remove worktree '${workspace}' because it is not at the expected managed path '${expectedPath}'`,
|
|
24248
|
+
recoveryHint: "Use git worktree list/status to inspect the path. Retry only after confirming the mesh node metadata points to an ADHDev-managed worktree."
|
|
24249
|
+
};
|
|
24250
|
+
}
|
|
24251
|
+
const entries = await listWorktrees2(repoRoot);
|
|
24252
|
+
const managedEntry = entries.find((entry) => normalizePath(entry.path) === actualPath);
|
|
24253
|
+
if (!managedEntry) {
|
|
24254
|
+
return {
|
|
24255
|
+
success: false,
|
|
24256
|
+
code: "mesh_worktree_cleanup_not_registered",
|
|
24257
|
+
error: `Refusing to remove '${workspace}' because it is not registered in git worktree list for '${repoRoot}'`,
|
|
24258
|
+
recoveryHint: "Inspect git worktree list --porcelain from the source repo. If the path was already removed, prune git worktrees before retrying."
|
|
24259
|
+
};
|
|
24260
|
+
}
|
|
24261
|
+
if (managedEntry.branch && managedEntry.branch !== args.node.worktreeBranch) {
|
|
24262
|
+
return {
|
|
24263
|
+
success: false,
|
|
24264
|
+
code: "mesh_worktree_cleanup_branch_mismatch",
|
|
24265
|
+
error: `Refusing to remove '${workspace}' because git reports branch '${managedEntry.branch}', expected '${args.node.worktreeBranch}'`,
|
|
24266
|
+
recoveryHint: "Inspect the worktree branch and mesh metadata before retrying cleanup."
|
|
24267
|
+
};
|
|
24268
|
+
}
|
|
24269
|
+
const forceFallbackConvergence = await this.getWorktreeForceCleanupConvergence({
|
|
24270
|
+
repoRoot,
|
|
24271
|
+
workspace,
|
|
24272
|
+
node: args.node
|
|
24273
|
+
});
|
|
24274
|
+
try {
|
|
24275
|
+
const result = await removeWorktree2(repoRoot, workspace, {
|
|
24276
|
+
requireClean: true,
|
|
24277
|
+
allowSubmoduleForceFallback: forceFallbackConvergence.allow
|
|
24278
|
+
});
|
|
24279
|
+
return {
|
|
24280
|
+
success: true,
|
|
24281
|
+
removedPath: result.removedPath,
|
|
24282
|
+
repoRoot,
|
|
24283
|
+
...result.fallback ? {
|
|
24284
|
+
fallback: result.fallback,
|
|
24285
|
+
forced: result.forced,
|
|
24286
|
+
reason: result.reason,
|
|
24287
|
+
convergence: forceFallbackConvergence
|
|
24288
|
+
} : {}
|
|
24289
|
+
};
|
|
24290
|
+
} catch (e) {
|
|
24291
|
+
const message = String(e?.message || e || "worktree cleanup failed");
|
|
24292
|
+
const dirty = message.includes("dirty worktree") || message.includes("local changes");
|
|
24293
|
+
const submoduleForceBlocked = /working trees containing submodules cannot be moved or removed/i.test(message) && !forceFallbackConvergence.allow;
|
|
24294
|
+
return {
|
|
24295
|
+
success: false,
|
|
24296
|
+
code: dirty ? "mesh_worktree_cleanup_dirty" : submoduleForceBlocked ? "mesh_worktree_cleanup_force_fallback_blocked" : "mesh_worktree_cleanup_failed",
|
|
24297
|
+
error: submoduleForceBlocked ? `${message}; refusing --force fallback because convergence could not be verified: ${forceFallbackConvergence.error || "unknown convergence state"}` : message,
|
|
24298
|
+
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.",
|
|
24299
|
+
...submoduleForceBlocked ? { convergence: forceFallbackConvergence } : {}
|
|
24300
|
+
};
|
|
24301
|
+
}
|
|
24302
|
+
}
|
|
24303
|
+
async getWorktreeForceCleanupConvergence(args) {
|
|
24304
|
+
const metadataStatus = typeof args.node?.branchConvergence?.status === "string" ? args.node.branchConvergence.status : "";
|
|
24305
|
+
if (metadataStatus === "merged_to_main" || metadataStatus === "cleanup_candidate") {
|
|
24306
|
+
return { allow: true, status: metadataStatus, source: "node_branch_convergence" };
|
|
24307
|
+
}
|
|
24308
|
+
const { execFile: execFile3 } = await import("child_process");
|
|
24309
|
+
const { promisify: promisify3 } = await import("util");
|
|
24310
|
+
const execFileAsync3 = promisify3(execFile3);
|
|
24311
|
+
const runGit2 = async (gitArgs, cwd) => {
|
|
24312
|
+
const { stdout } = await execFileAsync3("git", gitArgs, {
|
|
24313
|
+
cwd,
|
|
24314
|
+
encoding: "utf8",
|
|
24315
|
+
timeout: 3e4,
|
|
24316
|
+
maxBuffer: 4 * 1024 * 1024,
|
|
24317
|
+
windowsHide: true
|
|
24318
|
+
});
|
|
24319
|
+
return String(stdout || "").trim();
|
|
24320
|
+
};
|
|
24321
|
+
let head = "";
|
|
24322
|
+
try {
|
|
24323
|
+
head = await runGit2(["rev-parse", "HEAD"], args.workspace);
|
|
24324
|
+
} catch (e) {
|
|
24325
|
+
return { allow: false, error: `could not resolve worktree HEAD: ${e?.message || e}` };
|
|
24326
|
+
}
|
|
24327
|
+
if (!head) return { allow: false, error: "worktree HEAD is empty" };
|
|
24328
|
+
const candidateRefs = [];
|
|
24329
|
+
try {
|
|
24330
|
+
const defaultBranch = await runGit2(["branch", "--show-current"], args.repoRoot);
|
|
24331
|
+
if (defaultBranch) {
|
|
24332
|
+
candidateRefs.push(defaultBranch, `origin/${defaultBranch}`);
|
|
24333
|
+
}
|
|
24334
|
+
} catch {
|
|
24335
|
+
}
|
|
24336
|
+
candidateRefs.push("origin/main", "origin/master", "main", "master");
|
|
24337
|
+
const seen = /* @__PURE__ */ new Set();
|
|
24338
|
+
const checkedRefs = [];
|
|
24339
|
+
for (const ref of candidateRefs) {
|
|
24340
|
+
if (!ref || seen.has(ref)) continue;
|
|
24341
|
+
seen.add(ref);
|
|
24342
|
+
let commit = "";
|
|
24343
|
+
try {
|
|
24344
|
+
commit = await runGit2(["rev-parse", "--verify", `${ref}^{commit}`], args.repoRoot);
|
|
24345
|
+
} catch {
|
|
24346
|
+
continue;
|
|
24347
|
+
}
|
|
24348
|
+
checkedRefs.push(ref);
|
|
24349
|
+
try {
|
|
24350
|
+
await runGit2(["merge-base", "--is-ancestor", head, commit], args.repoRoot);
|
|
24351
|
+
return { allow: true, status: "merged_to_default_ref", source: "git_merge_base", ref };
|
|
24352
|
+
} catch {
|
|
24353
|
+
}
|
|
24354
|
+
}
|
|
24355
|
+
return {
|
|
24356
|
+
allow: false,
|
|
24357
|
+
status: metadataStatus || void 0,
|
|
24358
|
+
error: checkedRefs.length ? `worktree HEAD is not contained in checked refs: ${checkedRefs.join(", ")}` : "no default/main refs were available for convergence verification"
|
|
24359
|
+
};
|
|
24360
|
+
}
|
|
22969
24361
|
isCompletedHostedSession(record) {
|
|
22970
24362
|
return record?.lifecycle === "stopped" || record?.lifecycle === "failed" || record?.lifecycle === "interrupted";
|
|
22971
24363
|
}
|
|
24364
|
+
async recordIntentionalMeshSessionStop(args) {
|
|
24365
|
+
try {
|
|
24366
|
+
const { appendLedgerEntry: appendLedgerEntry2 } = await Promise.resolve().then(() => (init_mesh_ledger(), mesh_ledger_exports));
|
|
24367
|
+
appendLedgerEntry2(args.meshId, {
|
|
24368
|
+
kind: "session_stopped",
|
|
24369
|
+
nodeId: args.nodeId,
|
|
24370
|
+
sessionId: args.sessionId,
|
|
24371
|
+
payload: {
|
|
24372
|
+
intentional: true,
|
|
24373
|
+
reason: "operator_cleanup",
|
|
24374
|
+
intentionalStopReason: "operator_cleanup",
|
|
24375
|
+
source: args.source,
|
|
24376
|
+
cleanupMode: args.mode,
|
|
24377
|
+
action: args.action,
|
|
24378
|
+
workspace: typeof args.node?.workspace === "string" ? args.node.workspace : void 0
|
|
24379
|
+
}
|
|
24380
|
+
});
|
|
24381
|
+
} catch (e) {
|
|
24382
|
+
LOG.warn("MeshCleanup", `Failed to record intentional cleanup stop for ${args.sessionId}: ${e?.message || e}`);
|
|
24383
|
+
}
|
|
24384
|
+
}
|
|
22972
24385
|
async cleanupMeshSessions(args) {
|
|
22973
24386
|
if (args.mode === "preserve") {
|
|
22974
24387
|
return { success: true, mode: "preserve", matchedCount: 0, stoppedSessionIds: [], deletedSessionIds: [], skippedSessionIds: [] };
|
|
@@ -22985,6 +24398,21 @@ var DaemonCommandRouter = class {
|
|
|
22985
24398
|
const deleteUnsupportedSessionIds = [];
|
|
22986
24399
|
const recordsRemainSessionIds = [];
|
|
22987
24400
|
const errors = [];
|
|
24401
|
+
const cleanupSource = args.source || "mesh_cleanup_sessions";
|
|
24402
|
+
const markedIntentionalStopSessionIds = /* @__PURE__ */ new Set();
|
|
24403
|
+
const markIntentionalStop = async (sessionId, action) => {
|
|
24404
|
+
if (args.dryRun || markedIntentionalStopSessionIds.has(sessionId)) return;
|
|
24405
|
+
markedIntentionalStopSessionIds.add(sessionId);
|
|
24406
|
+
await this.recordIntentionalMeshSessionStop({
|
|
24407
|
+
meshId: args.meshId,
|
|
24408
|
+
nodeId: args.nodeId,
|
|
24409
|
+
node: args.node,
|
|
24410
|
+
sessionId,
|
|
24411
|
+
mode: args.mode,
|
|
24412
|
+
source: cleanupSource,
|
|
24413
|
+
action
|
|
24414
|
+
});
|
|
24415
|
+
};
|
|
22988
24416
|
const matchedBySurfaceKind = {
|
|
22989
24417
|
live_runtime: 0,
|
|
22990
24418
|
recovery_snapshot: 0,
|
|
@@ -23007,7 +24435,10 @@ var DaemonCommandRouter = class {
|
|
|
23007
24435
|
try {
|
|
23008
24436
|
if (args.mode === "stop") {
|
|
23009
24437
|
if (!completed) {
|
|
23010
|
-
if (!args.dryRun)
|
|
24438
|
+
if (!args.dryRun) {
|
|
24439
|
+
await markIntentionalStop(sessionId, "stop_session");
|
|
24440
|
+
await this.deps.sessionHostControl.stopSession(sessionId);
|
|
24441
|
+
}
|
|
23011
24442
|
stoppedSessionIds.push(sessionId);
|
|
23012
24443
|
} else {
|
|
23013
24444
|
skippedSessionIds.push(sessionId);
|
|
@@ -23024,6 +24455,7 @@ var DaemonCommandRouter = class {
|
|
|
23024
24455
|
continue;
|
|
23025
24456
|
}
|
|
23026
24457
|
if (args.mode === "stop_and_delete") {
|
|
24458
|
+
if (!completed) await markIntentionalStop(sessionId, "delete_session_force");
|
|
23027
24459
|
if (!args.dryRun) await this.deps.sessionHostControl.deleteSession(sessionId, { force: true });
|
|
23028
24460
|
deletedSessionIds.push(sessionId);
|
|
23029
24461
|
continue;
|
|
@@ -23035,6 +24467,7 @@ var DaemonCommandRouter = class {
|
|
|
23035
24467
|
recordsRemainSessionIds.push(sessionId);
|
|
23036
24468
|
if (args.mode === "stop_and_delete" && !completed) {
|
|
23037
24469
|
try {
|
|
24470
|
+
await markIntentionalStop(sessionId, "stop_session");
|
|
23038
24471
|
await this.deps.sessionHostControl.stopSession(sessionId);
|
|
23039
24472
|
stoppedSessionIds.push(sessionId);
|
|
23040
24473
|
} catch (stopError) {
|
|
@@ -23789,6 +25222,91 @@ var DaemonCommandRouter = class {
|
|
|
23789
25222
|
return { success: false, error: e.message };
|
|
23790
25223
|
}
|
|
23791
25224
|
}
|
|
25225
|
+
case "get_mesh_ledger_slice": {
|
|
25226
|
+
const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
|
|
25227
|
+
if (!meshId) return { success: false, error: "meshId required" };
|
|
25228
|
+
try {
|
|
25229
|
+
const { readLedgerSlice: readLedgerSlice2 } = await Promise.resolve().then(() => (init_mesh_ledger(), mesh_ledger_exports));
|
|
25230
|
+
const kind = Array.isArray(args?.kind) ? args.kind.filter((k) => typeof k === "string") : void 0;
|
|
25231
|
+
const slice = readLedgerSlice2(meshId, {
|
|
25232
|
+
afterId: typeof args?.afterId === "string" ? args.afterId : void 0,
|
|
25233
|
+
since: typeof args?.since === "string" ? args.since : void 0,
|
|
25234
|
+
kind,
|
|
25235
|
+
limit: typeof args?.limit === "number" ? args.limit : void 0
|
|
25236
|
+
});
|
|
25237
|
+
return { success: true, slice };
|
|
25238
|
+
} catch (e) {
|
|
25239
|
+
return { success: false, error: e.message };
|
|
25240
|
+
}
|
|
25241
|
+
}
|
|
25242
|
+
case "import_mesh_ledger_slice": {
|
|
25243
|
+
const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
|
|
25244
|
+
if (!meshId) return { success: false, error: "meshId required" };
|
|
25245
|
+
try {
|
|
25246
|
+
const { appendRemoteLedgerEntries: appendRemoteLedgerEntries2, getLedgerSummary: getLedgerSummary2 } = await Promise.resolve().then(() => (init_mesh_ledger(), mesh_ledger_exports));
|
|
25247
|
+
const entries = Array.isArray(args?.entries) ? args.entries : Array.isArray(args?.slice?.entries) ? args.slice.entries : [];
|
|
25248
|
+
const result = appendRemoteLedgerEntries2(meshId, entries);
|
|
25249
|
+
return { success: true, result, summary: getLedgerSummary2(meshId) };
|
|
25250
|
+
} catch (e) {
|
|
25251
|
+
return { success: false, error: e.message };
|
|
25252
|
+
}
|
|
25253
|
+
}
|
|
25254
|
+
case "get_mesh_queue": {
|
|
25255
|
+
const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
|
|
25256
|
+
if (!meshId) return { success: false, error: "meshId required" };
|
|
25257
|
+
try {
|
|
25258
|
+
const { getMeshQueueStats: getMeshQueueStats2, getQueue: getQueue2 } = await Promise.resolve().then(() => (init_mesh_work_queue(), mesh_work_queue_exports));
|
|
25259
|
+
const status = Array.isArray(args?.status) ? args.status.map((s) => typeof s === "string" ? s.trim() : "").filter(Boolean) : void 0;
|
|
25260
|
+
const queue = getQueue2(meshId, { status });
|
|
25261
|
+
const summary = getMeshQueueStats2(meshId);
|
|
25262
|
+
return {
|
|
25263
|
+
success: true,
|
|
25264
|
+
queue,
|
|
25265
|
+
summary,
|
|
25266
|
+
sourceOfTruth: {
|
|
25267
|
+
kind: "mesh_work_queue_file",
|
|
25268
|
+
activeStatuses: ["pending", "assigned"],
|
|
25269
|
+
historicalStatuses: ["completed", "failed", "cancelled"],
|
|
25270
|
+
notes: "pending/assigned are active work; completed/failed/cancelled are historical records."
|
|
25271
|
+
}
|
|
25272
|
+
};
|
|
25273
|
+
} catch (e) {
|
|
25274
|
+
return { success: false, error: e.message };
|
|
25275
|
+
}
|
|
25276
|
+
}
|
|
25277
|
+
case "cancel_mesh_queue_task": {
|
|
25278
|
+
const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
|
|
25279
|
+
const taskId = typeof args?.taskId === "string" ? args.taskId.trim() : "";
|
|
25280
|
+
if (!meshId || !taskId) return { success: false, error: "meshId and taskId required" };
|
|
25281
|
+
try {
|
|
25282
|
+
const { cancelTask: cancelTask2 } = await Promise.resolve().then(() => (init_mesh_work_queue(), mesh_work_queue_exports));
|
|
25283
|
+
const reason = typeof args?.reason === "string" ? args.reason : void 0;
|
|
25284
|
+
const task = cancelTask2(meshId, taskId, { reason });
|
|
25285
|
+
if (!task) return { success: false, error: `Queue task '${taskId}' not found` };
|
|
25286
|
+
return { success: true, task };
|
|
25287
|
+
} catch (e) {
|
|
25288
|
+
return { success: false, error: e.message };
|
|
25289
|
+
}
|
|
25290
|
+
}
|
|
25291
|
+
case "requeue_mesh_queue_task": {
|
|
25292
|
+
const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
|
|
25293
|
+
const taskId = typeof args?.taskId === "string" ? args.taskId.trim() : "";
|
|
25294
|
+
if (!meshId || !taskId) return { success: false, error: "meshId and taskId required" };
|
|
25295
|
+
try {
|
|
25296
|
+
const { requeueTask: requeueTask2 } = await Promise.resolve().then(() => (init_mesh_work_queue(), mesh_work_queue_exports));
|
|
25297
|
+
const task = requeueTask2(meshId, taskId, {
|
|
25298
|
+
reason: typeof args?.reason === "string" ? args.reason : void 0,
|
|
25299
|
+
targetNodeId: typeof args?.targetNodeId === "string" ? args.targetNodeId.trim() : void 0,
|
|
25300
|
+
targetSessionId: typeof args?.targetSessionId === "string" ? args.targetSessionId.trim() : void 0,
|
|
25301
|
+
clearTargetNode: args?.clearTargetNode === true,
|
|
25302
|
+
clearTargetSession: args?.clearTargetSession !== false
|
|
25303
|
+
});
|
|
25304
|
+
if (!task) return { success: false, error: `Queue task '${taskId}' not found` };
|
|
25305
|
+
return { success: true, task };
|
|
25306
|
+
} catch (e) {
|
|
25307
|
+
return { success: false, error: e.message };
|
|
25308
|
+
}
|
|
25309
|
+
}
|
|
23792
25310
|
case "add_mesh_node": {
|
|
23793
25311
|
const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
|
|
23794
25312
|
const workspace = typeof args?.workspace === "string" ? args.workspace.trim() : "";
|
|
@@ -23850,7 +25368,8 @@ var DaemonCommandRouter = class {
|
|
|
23850
25368
|
node,
|
|
23851
25369
|
mode,
|
|
23852
25370
|
sessionIds,
|
|
23853
|
-
dryRun: args?.dryRun === true
|
|
25371
|
+
dryRun: args?.dryRun === true,
|
|
25372
|
+
source: "mesh_cleanup_sessions"
|
|
23854
25373
|
});
|
|
23855
25374
|
return result;
|
|
23856
25375
|
} catch (e) {
|
|
@@ -23880,10 +25399,61 @@ var DaemonCommandRouter = class {
|
|
|
23880
25399
|
if (!branch) return { success: false, error: "Could not determine branch of the worktree node" };
|
|
23881
25400
|
const { stdout: baseBranchStdout } = await execFileAsync3("git", ["branch", "--show-current"], { cwd: repoRoot, encoding: "utf8" });
|
|
23882
25401
|
const baseBranch = baseBranchStdout.trim();
|
|
25402
|
+
const validationSummary = await runMeshRefineValidationGate(mesh, node.workspace);
|
|
25403
|
+
if (validationSummary.status === "failed") {
|
|
25404
|
+
return {
|
|
25405
|
+
success: false,
|
|
25406
|
+
code: "validation_failed",
|
|
25407
|
+
convergenceStatus: "blocked_review",
|
|
25408
|
+
error: "Refinery validation gate failed; merge/refine was not attempted.",
|
|
25409
|
+
branch,
|
|
25410
|
+
into: baseBranch,
|
|
25411
|
+
validationSummary,
|
|
25412
|
+
finalBranchConvergenceState: {
|
|
25413
|
+
branch,
|
|
25414
|
+
baseBranch,
|
|
25415
|
+
merged: false,
|
|
25416
|
+
removed: false,
|
|
25417
|
+
validation: "failed",
|
|
25418
|
+
status: "blocked_review"
|
|
25419
|
+
}
|
|
25420
|
+
};
|
|
25421
|
+
}
|
|
25422
|
+
if (validationSummary.status === "skipped") {
|
|
25423
|
+
return {
|
|
25424
|
+
success: false,
|
|
25425
|
+
code: "validation_unavailable",
|
|
25426
|
+
convergenceStatus: "blocked_review",
|
|
25427
|
+
error: "Refinery validation gate is required but no allowlisted validation command was available; merge/refine was not attempted.",
|
|
25428
|
+
branch,
|
|
25429
|
+
into: baseBranch,
|
|
25430
|
+
validationSummary,
|
|
25431
|
+
finalBranchConvergenceState: {
|
|
25432
|
+
branch,
|
|
25433
|
+
baseBranch,
|
|
25434
|
+
merged: false,
|
|
25435
|
+
removed: false,
|
|
25436
|
+
validation: "unavailable",
|
|
25437
|
+
status: "blocked_review"
|
|
25438
|
+
}
|
|
25439
|
+
};
|
|
25440
|
+
}
|
|
23883
25441
|
try {
|
|
23884
25442
|
await execFileAsync3("git", ["merge", "--no-ff", branch, "-m", `Auto-merge branch '${branch}' via Refinery`], { cwd: repoRoot, encoding: "utf8" });
|
|
23885
25443
|
} catch (e) {
|
|
23886
|
-
return {
|
|
25444
|
+
return {
|
|
25445
|
+
success: false,
|
|
25446
|
+
error: `Merge failed (conflicts?): ${e.message}`,
|
|
25447
|
+
validationSummary,
|
|
25448
|
+
finalBranchConvergenceState: {
|
|
25449
|
+
branch,
|
|
25450
|
+
baseBranch,
|
|
25451
|
+
merged: false,
|
|
25452
|
+
removed: false,
|
|
25453
|
+
validation: "passed",
|
|
25454
|
+
status: "not_mergeable"
|
|
25455
|
+
}
|
|
25456
|
+
};
|
|
23887
25457
|
}
|
|
23888
25458
|
const removeResult = await this.execute("remove_mesh_node", {
|
|
23889
25459
|
meshId,
|
|
@@ -23896,11 +25466,27 @@ var DaemonCommandRouter = class {
|
|
|
23896
25466
|
appendLedgerEntry2(meshId, {
|
|
23897
25467
|
kind: "node_removed",
|
|
23898
25468
|
nodeId,
|
|
23899
|
-
payload: { refined: true, mergedBranch: branch, into: baseBranch }
|
|
25469
|
+
payload: { refined: true, mergedBranch: branch, into: baseBranch, validationSummary }
|
|
23900
25470
|
});
|
|
23901
25471
|
} catch {
|
|
23902
25472
|
}
|
|
23903
|
-
return {
|
|
25473
|
+
return {
|
|
25474
|
+
success: true,
|
|
25475
|
+
merged: true,
|
|
25476
|
+
branch,
|
|
25477
|
+
into: baseBranch,
|
|
25478
|
+
removeResult,
|
|
25479
|
+
validationSummary,
|
|
25480
|
+
finalBranchConvergenceState: {
|
|
25481
|
+
branch: baseBranch,
|
|
25482
|
+
mergedBranch: branch,
|
|
25483
|
+
baseBranch,
|
|
25484
|
+
merged: true,
|
|
25485
|
+
removed: removeResult?.success !== false,
|
|
25486
|
+
validation: "passed",
|
|
25487
|
+
status: removeResult?.success === false ? "merged_cleanup_failed" : "merged"
|
|
25488
|
+
}
|
|
25489
|
+
};
|
|
23904
25490
|
} catch (e) {
|
|
23905
25491
|
return { success: false, error: e.message };
|
|
23906
25492
|
}
|
|
@@ -23918,20 +25504,24 @@ var DaemonCommandRouter = class {
|
|
|
23918
25504
|
);
|
|
23919
25505
|
let sessionCleanup;
|
|
23920
25506
|
if (node && sessionCleanupMode !== "preserve") {
|
|
23921
|
-
sessionCleanup = await this.cleanupMeshSessions({ meshId, nodeId, node, mode: sessionCleanupMode });
|
|
25507
|
+
sessionCleanup = await this.cleanupMeshSessions({ meshId, nodeId, node, mode: sessionCleanupMode, source: "mesh_remove_node" });
|
|
23922
25508
|
if (sessionCleanup.success === false) return { success: false, removed: false, sessionCleanup };
|
|
23923
25509
|
}
|
|
23924
|
-
|
|
23925
|
-
|
|
23926
|
-
|
|
23927
|
-
|
|
23928
|
-
|
|
23929
|
-
|
|
23930
|
-
|
|
23931
|
-
|
|
23932
|
-
|
|
23933
|
-
|
|
25510
|
+
let worktreeCleanup;
|
|
25511
|
+
if (node?.isLocalWorktree) {
|
|
25512
|
+
const cleanupResult = await this.cleanupLocalWorktreeNode({ mesh, node, nodeId });
|
|
25513
|
+
if (cleanupResult.success === false) {
|
|
25514
|
+
return {
|
|
25515
|
+
success: false,
|
|
25516
|
+
removed: false,
|
|
25517
|
+
code: cleanupResult.code,
|
|
25518
|
+
error: cleanupResult.error,
|
|
25519
|
+
recoveryHint: cleanupResult.recoveryHint,
|
|
25520
|
+
...sessionCleanup ? { sessionCleanup } : {},
|
|
25521
|
+
worktreeCleanup: cleanupResult
|
|
25522
|
+
};
|
|
23934
25523
|
}
|
|
25524
|
+
worktreeCleanup = cleanupResult;
|
|
23935
25525
|
}
|
|
23936
25526
|
let removed = false;
|
|
23937
25527
|
if (meshRecord?.inline) {
|
|
@@ -23946,12 +25536,21 @@ var DaemonCommandRouter = class {
|
|
|
23946
25536
|
appendLedgerEntry2(meshId, {
|
|
23947
25537
|
kind: "node_removed",
|
|
23948
25538
|
nodeId,
|
|
23949
|
-
payload: {
|
|
25539
|
+
payload: {
|
|
25540
|
+
worktree: !!node?.isLocalWorktree,
|
|
25541
|
+
sessionCleanupMode,
|
|
25542
|
+
workspace: typeof node?.workspace === "string" ? node.workspace : void 0,
|
|
25543
|
+
daemonId: typeof node?.daemonId === "string" ? node.daemonId : void 0,
|
|
25544
|
+
worktreeBranch: typeof node?.worktreeBranch === "string" ? node.worktreeBranch : void 0,
|
|
25545
|
+
worktreeCleanupFallback: typeof worktreeCleanup?.fallback === "string" ? worktreeCleanup.fallback : void 0,
|
|
25546
|
+
forced: worktreeCleanup?.forced === true ? true : void 0,
|
|
25547
|
+
forceFallbackReason: typeof worktreeCleanup?.reason === "string" ? worktreeCleanup.reason : void 0
|
|
25548
|
+
}
|
|
23950
25549
|
});
|
|
23951
25550
|
} catch {
|
|
23952
25551
|
}
|
|
23953
25552
|
}
|
|
23954
|
-
return { success: true, removed, ...sessionCleanup ? { sessionCleanup } : {} };
|
|
25553
|
+
return { success: true, removed, ...sessionCleanup ? { sessionCleanup } : {}, ...worktreeCleanup ? { worktreeCleanup } : {} };
|
|
23955
25554
|
} catch (e) {
|
|
23956
25555
|
return { success: false, error: e.message };
|
|
23957
25556
|
}
|
|
@@ -23986,6 +25585,7 @@ var DaemonCommandRouter = class {
|
|
|
23986
25585
|
workspace: result.worktreePath,
|
|
23987
25586
|
repoRoot: result.worktreePath,
|
|
23988
25587
|
daemonId: sourceNode.daemonId,
|
|
25588
|
+
machineId: sourceNode.machineId ?? sourceNode.machine_id,
|
|
23989
25589
|
userOverrides: { ...sourceNode.userOverrides || {} },
|
|
23990
25590
|
policy: { ...sourceNode.policy || {} },
|
|
23991
25591
|
isLocalWorktree: true,
|
|
@@ -23999,6 +25599,7 @@ var DaemonCommandRouter = class {
|
|
|
23999
25599
|
workspace: result.worktreePath,
|
|
24000
25600
|
repoRoot: result.worktreePath,
|
|
24001
25601
|
daemonId: sourceNode.daemonId,
|
|
25602
|
+
machineId: sourceNode.machineId ?? sourceNode.machine_id,
|
|
24002
25603
|
userOverrides: { ...sourceNode.userOverrides || {} },
|
|
24003
25604
|
isLocalWorktree: true,
|
|
24004
25605
|
worktreeBranch: result.branch,
|
|
@@ -24117,6 +25718,93 @@ var DaemonCommandRouter = class {
|
|
|
24117
25718
|
meshCoordinatorSetup: coordinatorSetup
|
|
24118
25719
|
};
|
|
24119
25720
|
}
|
|
25721
|
+
if (coordinatorSetup.kind === "cli_command") {
|
|
25722
|
+
let cliCmdSystemPrompt = "";
|
|
25723
|
+
try {
|
|
25724
|
+
cliCmdSystemPrompt = buildCoordinatorSystemPrompt2({ mesh, coordinatorCliType: cliType });
|
|
25725
|
+
} catch (error) {
|
|
25726
|
+
const message = error?.message || String(error);
|
|
25727
|
+
LOG.error("MeshCoordinator", `Failed to build coordinator prompt: ${message}`);
|
|
25728
|
+
return {
|
|
25729
|
+
success: false,
|
|
25730
|
+
code: "mesh_coordinator_prompt_failed",
|
|
25731
|
+
error: `Failed to build Repo Mesh coordinator prompt: ${message}`,
|
|
25732
|
+
meshId,
|
|
25733
|
+
cliType,
|
|
25734
|
+
workspace
|
|
25735
|
+
};
|
|
25736
|
+
}
|
|
25737
|
+
try {
|
|
25738
|
+
const { execFileSync: execCmdSync } = await import("child_process");
|
|
25739
|
+
const cmdParts = coordinatorSetup.command.trim().split(/\s+/);
|
|
25740
|
+
const [regCmd, ...regArgs] = cmdParts;
|
|
25741
|
+
LOG.info("MeshCoordinator", `Running MCP registration: ${coordinatorSetup.command}`);
|
|
25742
|
+
execCmdSync(regCmd, regArgs, { stdio: "pipe", timeout: 15e3 });
|
|
25743
|
+
} catch (error) {
|
|
25744
|
+
LOG.warn("MeshCoordinator", `MCP registration command failed (may be pre-registered): ${error?.message || error}`);
|
|
25745
|
+
}
|
|
25746
|
+
const cliCmdArgs = [];
|
|
25747
|
+
const cliCmdEnv = {};
|
|
25748
|
+
if (cliCmdSystemPrompt) {
|
|
25749
|
+
if (cliType === "codex-cli") {
|
|
25750
|
+
cliCmdArgs.push("-c", `developer_instructions=${JSON.stringify(cliCmdSystemPrompt)}`);
|
|
25751
|
+
} else if (cliType === "gemini-cli") {
|
|
25752
|
+
try {
|
|
25753
|
+
const { writeFileSync: wfs, existsSync: efs, readFileSync: rfs } = await import("fs");
|
|
25754
|
+
const geminiMdPath = `${workspace}/GEMINI.md`;
|
|
25755
|
+
const marker = "<!-- adhdev-mesh-coordinator-prompt -->";
|
|
25756
|
+
const markerEnd = "<!-- /adhdev-mesh-coordinator-prompt -->";
|
|
25757
|
+
const block = `${marker}
|
|
25758
|
+
${cliCmdSystemPrompt}
|
|
25759
|
+
${markerEnd}`;
|
|
25760
|
+
if (efs(geminiMdPath)) {
|
|
25761
|
+
const existing = rfs(geminiMdPath, "utf-8");
|
|
25762
|
+
const replaced = existing.replace(
|
|
25763
|
+
new RegExp(`${marker}[\\s\\S]*?${markerEnd}`, "g"),
|
|
25764
|
+
block
|
|
25765
|
+
);
|
|
25766
|
+
wfs(geminiMdPath, replaced.includes(marker) ? replaced : `${existing}
|
|
25767
|
+
|
|
25768
|
+
${block}`);
|
|
25769
|
+
} else {
|
|
25770
|
+
wfs(geminiMdPath, block);
|
|
25771
|
+
}
|
|
25772
|
+
LOG.info("MeshCoordinator", `Wrote coordinator prompt to ${workspace}/GEMINI.md`);
|
|
25773
|
+
} catch (e) {
|
|
25774
|
+
LOG.warn("MeshCoordinator", `Could not write GEMINI.md: ${e?.message || e}`);
|
|
25775
|
+
}
|
|
25776
|
+
}
|
|
25777
|
+
}
|
|
25778
|
+
const cliCmdLaunch = await this.deps.cliManager.handleCliCommand("launch_cli", {
|
|
25779
|
+
cliType,
|
|
25780
|
+
dir: workspace,
|
|
25781
|
+
cliArgs: cliCmdArgs.length > 0 ? cliCmdArgs : void 0,
|
|
25782
|
+
env: Object.keys(cliCmdEnv).length > 0 ? cliCmdEnv : void 0,
|
|
25783
|
+
settings: { meshCoordinatorFor: meshId }
|
|
25784
|
+
});
|
|
25785
|
+
if (!cliCmdLaunch?.success) {
|
|
25786
|
+
return { success: false, error: cliCmdLaunch?.error || "Failed to launch CLI session" };
|
|
25787
|
+
}
|
|
25788
|
+
LOG.info("MeshCoordinator", `Launched ${cliType} coordinator (cli_command) for mesh ${meshId}`);
|
|
25789
|
+
try {
|
|
25790
|
+
const { appendLedgerEntry: appendLedgerEntry2 } = await Promise.resolve().then(() => (init_mesh_ledger(), mesh_ledger_exports));
|
|
25791
|
+
appendLedgerEntry2(meshId, {
|
|
25792
|
+
kind: "coordinator_started",
|
|
25793
|
+
sessionId: cliCmdLaunch.sessionId || cliCmdLaunch.id,
|
|
25794
|
+
providerType: cliType,
|
|
25795
|
+
payload: { workspace }
|
|
25796
|
+
});
|
|
25797
|
+
} catch {
|
|
25798
|
+
}
|
|
25799
|
+
return {
|
|
25800
|
+
success: true,
|
|
25801
|
+
meshId,
|
|
25802
|
+
cliType,
|
|
25803
|
+
workspace,
|
|
25804
|
+
sessionId: cliCmdLaunch.sessionId || cliCmdLaunch.id,
|
|
25805
|
+
mcpRegistered: true
|
|
25806
|
+
};
|
|
25807
|
+
}
|
|
24120
25808
|
const configFormat = coordinatorSetup.configFormat;
|
|
24121
25809
|
if (configFormat !== "claude_mcp_json" && configFormat !== "hermes_config_yaml") {
|
|
24122
25810
|
return {
|
|
@@ -24171,9 +25859,11 @@ var DaemonCommandRouter = class {
|
|
|
24171
25859
|
args: coordinatorSetup.mcpServer.args
|
|
24172
25860
|
};
|
|
24173
25861
|
if (args?.inlineMesh) {
|
|
25862
|
+
const modeArgIndex = coordinatorSetup.mcpServer.args.findIndex((value) => value === "--mode");
|
|
25863
|
+
const mcpTransport = modeArgIndex >= 0 ? coordinatorSetup.mcpServer.args[modeArgIndex + 1] : "ipc";
|
|
24174
25864
|
mcpServerEntry.env = {
|
|
24175
25865
|
ADHDEV_INLINE_MESH: JSON.stringify(mesh),
|
|
24176
|
-
ADHDEV_MCP_TRANSPORT: "ipc"
|
|
25866
|
+
ADHDEV_MCP_TRANSPORT: mcpTransport === "local" ? "local" : "ipc"
|
|
24177
25867
|
};
|
|
24178
25868
|
}
|
|
24179
25869
|
try {
|
|
@@ -24192,7 +25882,8 @@ var DaemonCommandRouter = class {
|
|
|
24192
25882
|
if (hadExistingMcpConfig) {
|
|
24193
25883
|
try {
|
|
24194
25884
|
const parsedExistingMcpConfig = parseMeshCoordinatorMcpConfig(readFileSync17(mcpConfigPath, "utf-8"), configFormat);
|
|
24195
|
-
|
|
25885
|
+
const existingCoordinatorConfig = hermesManualFallback ? stripHermesCoordinatorTempModelProviderOverrides(parsedExistingMcpConfig) : parsedExistingMcpConfig;
|
|
25886
|
+
existingMcpConfig = { ...existingMcpConfig, ...existingCoordinatorConfig };
|
|
24196
25887
|
copyFileSync4(mcpConfigPath, mcpConfigPath + ".backup");
|
|
24197
25888
|
} catch (error) {
|
|
24198
25889
|
LOG.error("MeshCoordinator", `Failed to parse existing MCP config ${mcpConfigPath}: ${error?.message || error}`);
|
|
@@ -24273,6 +25964,113 @@ var DaemonCommandRouter = class {
|
|
|
24273
25964
|
return { success: false, error: e.message };
|
|
24274
25965
|
}
|
|
24275
25966
|
}
|
|
25967
|
+
case "mesh_status": {
|
|
25968
|
+
const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
|
|
25969
|
+
if (!meshId) return { success: false, error: "meshId required" };
|
|
25970
|
+
try {
|
|
25971
|
+
const meshRecord = await this.getMeshForCommand(meshId, args?.inlineMesh);
|
|
25972
|
+
const mesh = meshRecord?.mesh;
|
|
25973
|
+
if (!mesh) return { success: false, error: "Mesh not found" };
|
|
25974
|
+
const { getMeshQueueStats: getMeshQueueStats2, getQueue: getQueue2 } = await Promise.resolve().then(() => (init_mesh_work_queue(), mesh_work_queue_exports));
|
|
25975
|
+
const queue = getQueue2(meshId);
|
|
25976
|
+
const queueSummary = getMeshQueueStats2(meshId);
|
|
25977
|
+
const { readLedgerEntries: readLedgerEntries2, getLedgerSummary: getLedgerSummary2 } = await Promise.resolve().then(() => (init_mesh_ledger(), mesh_ledger_exports));
|
|
25978
|
+
const ledgerEntries = readLedgerEntries2(meshId, { tail: 20 });
|
|
25979
|
+
const ledgerSummary = getLedgerSummary2(meshId);
|
|
25980
|
+
const nodeStatuses = [];
|
|
25981
|
+
for (const node of mesh.nodes || []) {
|
|
25982
|
+
const status = {
|
|
25983
|
+
nodeId: node.id || node.nodeId,
|
|
25984
|
+
machineLabel: node.machineLabel || node.id || node.nodeId,
|
|
25985
|
+
workspace: node.workspace,
|
|
25986
|
+
repoRoot: node.repoRoot,
|
|
25987
|
+
isLocalWorktree: node.isLocalWorktree,
|
|
25988
|
+
worktreeBranch: node.worktreeBranch,
|
|
25989
|
+
daemonId: node.daemonId,
|
|
25990
|
+
machineId: node.machineId,
|
|
25991
|
+
health: "unknown",
|
|
25992
|
+
providers: node.providers || [],
|
|
25993
|
+
activeSessions: []
|
|
25994
|
+
};
|
|
25995
|
+
if (node.workspace && typeof node.workspace === "string") {
|
|
25996
|
+
try {
|
|
25997
|
+
const { execFile: execFile3 } = await import("child_process");
|
|
25998
|
+
const { promisify: promisify3 } = await import("util");
|
|
25999
|
+
const execFileAsync3 = promisify3(execFile3);
|
|
26000
|
+
const runGit2 = async (args2) => {
|
|
26001
|
+
const result = await execFileAsync3("git", ["-C", node.workspace, ...args2], {
|
|
26002
|
+
encoding: "utf8",
|
|
26003
|
+
timeout: 1e4
|
|
26004
|
+
});
|
|
26005
|
+
return result.stdout.trim();
|
|
26006
|
+
};
|
|
26007
|
+
const branch = await runGit2(["branch", "--show-current"]).catch(() => "");
|
|
26008
|
+
const porc = await runGit2(["status", "--porcelain"]).catch(() => "");
|
|
26009
|
+
const headCommit = await runGit2(["rev-parse", "--short", "HEAD"]).catch(() => null);
|
|
26010
|
+
const headMessage = await runGit2(["log", "-1", "--format=%s"]).catch(() => null);
|
|
26011
|
+
const upstream = await runGit2(["rev-parse", "--abbrev-ref", "@{upstream}"]).catch(() => null);
|
|
26012
|
+
const aheadBehind = await runGit2(["rev-list", "--left-right", "--count", "@{upstream}...HEAD"]).catch(() => "");
|
|
26013
|
+
const stashCount = await runGit2(["stash", "list"]).catch(() => "");
|
|
26014
|
+
let ahead = 0, behind = 0;
|
|
26015
|
+
if (aheadBehind) {
|
|
26016
|
+
const parts = aheadBehind.split(/\s+/);
|
|
26017
|
+
if (parts.length >= 2) {
|
|
26018
|
+
behind = parseInt(parts[0], 10) || 0;
|
|
26019
|
+
ahead = parseInt(parts[1], 10) || 0;
|
|
26020
|
+
}
|
|
26021
|
+
}
|
|
26022
|
+
const dirty = porc.length > 0;
|
|
26023
|
+
const lines = porc ? porc.split("\n").filter(Boolean) : [];
|
|
26024
|
+
let staged = 0, modified = 0, untracked = 0, deleted = 0, renamed = 0;
|
|
26025
|
+
for (const line of lines) {
|
|
26026
|
+
const xy = line.slice(0, 2);
|
|
26027
|
+
if (xy[0] !== " " && xy[0] !== "?") staged++;
|
|
26028
|
+
if (xy[1] === "M") modified++;
|
|
26029
|
+
if (xy[1] === "D") deleted++;
|
|
26030
|
+
if (xy[0] === "R" || xy[1] === "R") renamed++;
|
|
26031
|
+
if (xy === "??") untracked++;
|
|
26032
|
+
}
|
|
26033
|
+
status.git = {
|
|
26034
|
+
workspace: node.workspace,
|
|
26035
|
+
repoRoot: node.workspace,
|
|
26036
|
+
isGitRepo: true,
|
|
26037
|
+
branch: branch || null,
|
|
26038
|
+
headCommit,
|
|
26039
|
+
headMessage,
|
|
26040
|
+
upstream,
|
|
26041
|
+
ahead,
|
|
26042
|
+
behind,
|
|
26043
|
+
staged,
|
|
26044
|
+
modified,
|
|
26045
|
+
untracked,
|
|
26046
|
+
deleted,
|
|
26047
|
+
renamed,
|
|
26048
|
+
hasConflicts: false,
|
|
26049
|
+
conflictFiles: [],
|
|
26050
|
+
stashCount: stashCount ? stashCount.split("\n").filter(Boolean).length : 0,
|
|
26051
|
+
lastCheckedAt: Date.now()
|
|
26052
|
+
};
|
|
26053
|
+
status.health = branch ? dirty ? "dirty" : "online" : "degraded";
|
|
26054
|
+
} catch {
|
|
26055
|
+
status.health = "degraded";
|
|
26056
|
+
}
|
|
26057
|
+
}
|
|
26058
|
+
nodeStatuses.push(status);
|
|
26059
|
+
}
|
|
26060
|
+
return {
|
|
26061
|
+
success: true,
|
|
26062
|
+
meshId: mesh.id,
|
|
26063
|
+
meshName: mesh.name,
|
|
26064
|
+
repoIdentity: mesh.repoIdentity,
|
|
26065
|
+
defaultBranch: mesh.defaultBranch,
|
|
26066
|
+
nodes: nodeStatuses,
|
|
26067
|
+
queue: { tasks: queue, summary: queueSummary },
|
|
26068
|
+
ledger: { entries: ledgerEntries, summary: ledgerSummary }
|
|
26069
|
+
};
|
|
26070
|
+
} catch (e) {
|
|
26071
|
+
return { success: false, error: e.message };
|
|
26072
|
+
}
|
|
26073
|
+
}
|
|
24276
26074
|
default:
|
|
24277
26075
|
break;
|
|
24278
26076
|
}
|
|
@@ -31985,6 +33783,9 @@ function launchIDE(ide, workspacePath) {
|
|
|
31985
33783
|
}
|
|
31986
33784
|
}
|
|
31987
33785
|
|
|
33786
|
+
// src/boot/daemon-lifecycle.ts
|
|
33787
|
+
init_cli_detector();
|
|
33788
|
+
|
|
31988
33789
|
// src/sessions/registry.ts
|
|
31989
33790
|
var SessionRegistry = class {
|
|
31990
33791
|
bySessionId = /* @__PURE__ */ new Map();
|
|
@@ -32215,7 +34016,8 @@ async function initDaemonComponents(config) {
|
|
|
32215
34016
|
cdpManagers,
|
|
32216
34017
|
sessionRegistry,
|
|
32217
34018
|
detectedIdes: detectedIdesRef,
|
|
32218
|
-
refreshProviderAvailability
|
|
34019
|
+
refreshProviderAvailability,
|
|
34020
|
+
dispatchMeshCommand: config.dispatchMeshCommand
|
|
32219
34021
|
};
|
|
32220
34022
|
setupMeshEventForwarding(components);
|
|
32221
34023
|
return components;
|
|
@@ -32317,10 +34119,12 @@ async function shutdownDaemonComponents(components) {
|
|
|
32317
34119
|
IdeProviderInstance,
|
|
32318
34120
|
InMemoryGitSnapshotStore,
|
|
32319
34121
|
LOG,
|
|
34122
|
+
MAX_LEDGER_SLICE_LIMIT,
|
|
32320
34123
|
MIN_GIT_WORKSPACE_POLL_INTERVAL_MS,
|
|
32321
34124
|
MIN_MACHINE_RUNTIME_SUBSCRIPTION_INTERVAL_MS,
|
|
32322
34125
|
MIN_SESSION_HOST_DIAGNOSTICS_SUBSCRIPTION_INTERVAL_MS,
|
|
32323
34126
|
NodePtyTransportFactory,
|
|
34127
|
+
P2pRelayFailureError,
|
|
32324
34128
|
ProviderCliAdapter,
|
|
32325
34129
|
ProviderInstanceManager,
|
|
32326
34130
|
ProviderLoader,
|
|
@@ -32331,12 +34135,16 @@ async function shutdownDaemonComponents(components) {
|
|
|
32331
34135
|
addNode,
|
|
32332
34136
|
appendLedgerEntry,
|
|
32333
34137
|
appendRecentActivity,
|
|
34138
|
+
appendRemoteLedgerEntries,
|
|
32334
34139
|
buildAssistantChatMessage,
|
|
32335
34140
|
buildChatMessage,
|
|
32336
34141
|
buildChatMessageSignature,
|
|
32337
34142
|
buildChatTailDeliverySignature,
|
|
32338
34143
|
buildCoordinatorSystemPrompt,
|
|
32339
34144
|
buildMachineInfo,
|
|
34145
|
+
buildMeshLedgerReconciliationEvidence,
|
|
34146
|
+
buildMeshLedgerReplicaEvidence,
|
|
34147
|
+
buildP2pRelayFailurePayload,
|
|
32340
34148
|
buildPinnedGlobalInstallCommand,
|
|
32341
34149
|
buildRuntimeSystemChatMessage,
|
|
32342
34150
|
buildSessionEntries,
|
|
@@ -32347,10 +34155,13 @@ async function shutdownDaemonComponents(components) {
|
|
|
32347
34155
|
buildThoughtChatMessage,
|
|
32348
34156
|
buildToolChatMessage,
|
|
32349
34157
|
buildUserChatMessage,
|
|
34158
|
+
cancelTask,
|
|
32350
34159
|
claimNextTask,
|
|
32351
34160
|
classifyChatMessageVisibility,
|
|
32352
34161
|
classifyHotChatSessionsForSubscriptionFlush,
|
|
34162
|
+
classifyP2pRelayFailure,
|
|
32353
34163
|
clearDebugTrace,
|
|
34164
|
+
clearPendingMeshCoordinatorEvents,
|
|
32354
34165
|
compareGitSnapshots,
|
|
32355
34166
|
configureDebugTraceStore,
|
|
32356
34167
|
connectCdpManager,
|
|
@@ -32366,6 +34177,7 @@ async function shutdownDaemonComponents(components) {
|
|
|
32366
34177
|
detectAllVersions,
|
|
32367
34178
|
detectCLIs,
|
|
32368
34179
|
detectIDEs,
|
|
34180
|
+
drainPendingMeshCoordinatorEvents,
|
|
32369
34181
|
enqueueTask,
|
|
32370
34182
|
ensureSessionHostReady,
|
|
32371
34183
|
execNpmCommandSync,
|
|
@@ -32390,7 +34202,9 @@ async function shutdownDaemonComponents(components) {
|
|
|
32390
34202
|
getLogLevel,
|
|
32391
34203
|
getMesh,
|
|
32392
34204
|
getMeshByRepo,
|
|
34205
|
+
getMeshQueueStats,
|
|
32393
34206
|
getNpmExecOptions,
|
|
34207
|
+
getPendingMeshCoordinatorEvents,
|
|
32394
34208
|
getQueue,
|
|
32395
34209
|
getRecentActivity,
|
|
32396
34210
|
getRecentCommands,
|
|
@@ -32416,6 +34230,7 @@ async function shutdownDaemonComponents(components) {
|
|
|
32416
34230
|
isInternalChatMessage,
|
|
32417
34231
|
isManagedStatusWaiting,
|
|
32418
34232
|
isManagedStatusWorking,
|
|
34233
|
+
isP2pRelayTransportFailure,
|
|
32419
34234
|
isPathInside,
|
|
32420
34235
|
isSessionHostLiveRuntime,
|
|
32421
34236
|
isSessionHostRecoverySnapshot,
|
|
@@ -32454,10 +34269,12 @@ async function shutdownDaemonComponents(components) {
|
|
|
32454
34269
|
probeCdpPort,
|
|
32455
34270
|
readChatHistory,
|
|
32456
34271
|
readLedgerEntries,
|
|
34272
|
+
readLedgerSlice,
|
|
32457
34273
|
recordDebugTrace,
|
|
32458
34274
|
registerExtensionProviders,
|
|
32459
34275
|
removeNode,
|
|
32460
34276
|
removeWorktree,
|
|
34277
|
+
requeueTask,
|
|
32461
34278
|
resetConfig,
|
|
32462
34279
|
resetDebugRuntimeConfig,
|
|
32463
34280
|
resetState,
|