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