@adhdev/daemon-core 0.9.77-rc.6 → 0.9.77-rc.60

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.
Files changed (52) hide show
  1. package/dist/boot/daemon-lifecycle.d.ts +3 -0
  2. package/dist/cli-adapters/provider-cli-adapter.d.ts +4 -0
  3. package/dist/cli-adapters/provider-cli-shared.d.ts +14 -4
  4. package/dist/commands/mesh-coordinator.d.ts +10 -0
  5. package/dist/commands/router.d.ts +4 -1
  6. package/dist/config/mesh-config.d.ts +1 -0
  7. package/dist/git/git-worktree.d.ts +15 -2
  8. package/dist/index.d.ts +10 -6
  9. package/dist/index.js +2076 -299
  10. package/dist/index.js.map +1 -1
  11. package/dist/index.mjs +2061 -299
  12. package/dist/index.mjs.map +1 -1
  13. package/dist/mesh/mesh-events.d.ts +14 -7
  14. package/dist/mesh/mesh-ledger-reconciliation.d.ts +55 -0
  15. package/dist/mesh/mesh-ledger.d.ts +84 -4
  16. package/dist/mesh/mesh-sync.d.ts +4 -12
  17. package/dist/mesh/mesh-visualization.d.ts +70 -0
  18. package/dist/mesh/mesh-work-queue.d.ts +58 -1
  19. package/dist/mesh/p2p-relay-failure.d.ts +35 -0
  20. package/dist/providers/chat-message-normalization.d.ts +1 -0
  21. package/dist/providers/cli-provider-instance.d.ts +6 -0
  22. package/dist/repo-mesh-types.d.ts +2 -0
  23. package/dist/shared-types.d.ts +38 -0
  24. package/package.json +1 -1
  25. package/src/boot/daemon-lifecycle.ts +5 -0
  26. package/src/cli-adapters/provider-cli-adapter.ts +35 -4
  27. package/src/cli-adapters/provider-cli-shared.ts +14 -4
  28. package/src/commands/cli-manager.ts +0 -4
  29. package/src/commands/mesh-coordinator.ts +55 -7
  30. package/src/commands/router.ts +915 -26
  31. package/src/commands/stream-commands.ts +8 -1
  32. package/src/config/config.ts +2 -1
  33. package/src/config/mesh-config.ts +2 -0
  34. package/src/config/workspaces.ts +1 -1
  35. package/src/git/git-worktree.ts +56 -4
  36. package/src/index.d.ts +3 -0
  37. package/src/index.ts +29 -6
  38. package/src/mesh/coordinator-prompt.ts +21 -10
  39. package/src/mesh/mesh-events.ts +532 -22
  40. package/src/mesh/mesh-ledger-reconciliation.ts +115 -0
  41. package/src/mesh/mesh-ledger.ts +209 -8
  42. package/src/mesh/mesh-sync.ts +4 -34
  43. package/src/mesh/mesh-visualization.ts +341 -0
  44. package/src/mesh/mesh-work-queue.ts +183 -17
  45. package/src/mesh/p2p-relay-failure.ts +152 -0
  46. package/src/providers/acp-provider-instance.ts +2 -1
  47. package/src/providers/chat-message-normalization.ts +32 -0
  48. package/src/providers/cli-provider-instance.ts +155 -31
  49. package/src/providers/extension-provider-instance.ts +2 -1
  50. package/src/providers/ide-provider-instance.ts +2 -2
  51. package/src/repo-mesh-types.ts +2 -0
  52. package/src/shared-types.ts +38 -0
package/dist/index.js CHANGED
@@ -96,13 +96,25 @@ async function createWorktree(opts) {
96
96
  branch
97
97
  };
98
98
  }
99
- async function removeWorktree(repoRoot, worktreePath) {
99
+ async function removeWorktree(repoRoot, worktreePath, opts = {}) {
100
100
  if (!(0, import_node_fs2.existsSync)(worktreePath)) {
101
101
  await pruneWorktrees(repoRoot);
102
102
  return { success: true, removedPath: worktreePath };
103
103
  }
104
+ if (opts.requireClean) {
105
+ const { stdout } = await execFileAsync2("git", ["status", "--porcelain"], {
106
+ cwd: worktreePath,
107
+ encoding: "utf8",
108
+ timeout: GIT_TIMEOUT_MS,
109
+ maxBuffer: GIT_MAX_BUFFER,
110
+ windowsHide: true
111
+ });
112
+ if (stdout.trim()) {
113
+ throw new Error(`Refusing to remove dirty worktree: ${worktreePath}`);
114
+ }
115
+ }
104
116
  try {
105
- await execFileAsync2("git", ["worktree", "remove", worktreePath, "--force"], {
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
- throw new Error(`git worktree remove failed: ${stderr.trim() || error.message}`);
126
+ const stdout = typeof error.stdout === "string" ? error.stdout : "";
127
+ const detail = `${stderr}
128
+ ${stdout}
129
+ ${error.message || ""}`;
130
+ if (opts.allowSubmoduleForceFallback && SUBMODULE_WORKTREE_REMOVE_RE.test(detail)) {
131
+ try {
132
+ await execFileAsync2("git", ["worktree", "remove", "--force", worktreePath], {
133
+ cwd: repoRoot,
134
+ encoding: "utf8",
135
+ timeout: GIT_TIMEOUT_MS,
136
+ maxBuffer: GIT_MAX_BUFFER,
137
+ windowsHide: true
138
+ });
139
+ } catch (forceError) {
140
+ const forceStderr = typeof forceError.stderr === "string" ? forceError.stderr : "";
141
+ const forceStdout = typeof forceError.stdout === "string" ? forceError.stdout : "";
142
+ throw new Error(`git worktree remove --force fallback failed: ${forceStderr.trim() || forceStdout.trim() || forceError.message}`);
143
+ }
144
+ return {
145
+ success: true,
146
+ removedPath: worktreePath,
147
+ fallback: "git_worktree_remove_force_submodule",
148
+ forced: true,
149
+ reason: "working_trees_containing_submodules"
150
+ };
151
+ }
152
+ throw new Error(`git worktree remove failed: ${stderr.trim() || stdout.trim() || error.message}`);
115
153
  }
116
154
  return { success: true, removedPath: worktreePath };
117
155
  }
@@ -161,7 +199,7 @@ async function pruneWorktrees(repoRoot) {
161
199
  } catch {
162
200
  }
163
201
  }
164
- var path4, import_promises3, import_node_fs2, import_node_child_process2, import_node_util2, execFileAsync2, WORKTREE_DIR_NAME, GIT_TIMEOUT_MS, GIT_MAX_BUFFER;
202
+ var path4, import_promises3, import_node_fs2, import_node_child_process2, import_node_util2, execFileAsync2, WORKTREE_DIR_NAME, GIT_TIMEOUT_MS, GIT_MAX_BUFFER, SUBMODULE_WORKTREE_REMOVE_RE;
165
203
  var init_git_worktree = __esm({
166
204
  "src/git/git-worktree.ts"() {
167
205
  "use strict";
@@ -174,6 +212,7 @@ var init_git_worktree = __esm({
174
212
  WORKTREE_DIR_NAME = ".adhdev-worktrees";
175
213
  GIT_TIMEOUT_MS = 3e4;
176
214
  GIT_MAX_BUFFER = 4 * 1024 * 1024;
215
+ SUBMODULE_WORKTREE_REMOVE_RE = /working trees containing submodules cannot be moved or removed/i;
177
216
  }
178
217
  });
179
218
 
@@ -283,7 +322,8 @@ function ensureMachineId(config) {
283
322
  };
284
323
  }
285
324
  function getConfigDir() {
286
- const dir = (0, import_path.join)((0, import_os.homedir)(), ".adhdev");
325
+ const override = process.env.ADHDEV_CONFIG_DIR;
326
+ const dir = override && override.trim() ? override.trim() : (0, import_path.join)((0, import_os.homedir)(), ".adhdev");
287
327
  if (!(0, import_fs.existsSync)(dir)) {
288
328
  (0, import_fs.mkdirSync)(dir, { recursive: true });
289
329
  }
@@ -546,6 +586,7 @@ function addNode(meshId, opts) {
546
586
  workspace: opts.workspace.trim(),
547
587
  repoRoot: opts.repoRoot,
548
588
  daemonId: opts.daemonId,
589
+ machineId: opts.machineId,
549
590
  userOverrides: opts.userOverrides || {},
550
591
  policy: opts.policy || {},
551
592
  isLocalWorktree: opts.isLocalWorktree,
@@ -676,7 +717,8 @@ function buildRulesSection(coordinatorCliType) {
676
717
  - **Minimize coordinator context.** The coordinator's job is routing, not implementing. Do not read source files, run commands, or analyze code directly \u2014 delegate all of that to node agents. Your context should stay lean.
677
718
  - **Delegate analysis too.** If you need to understand a bug or explore the codebase, send that investigation as a task to the queue or a node. Do not do it yourself.
678
719
  - **Respect explicit provider requests.** If the user names an agent/provider, pass the matching provider type to \`mesh_launch_session\`: Hermes \u2192 \`hermes-cli\`, Claude Code/Claude \u2192 \`claude-cli\`, Codex \u2192 \`codex-cli\`, Gemini \u2192 \`gemini-cli\`. Never substitute \`claude-cli\` just because the coordinator itself is Claude Code.
679
- - **Front-load the task message.** When calling \`mesh_enqueue_task\` or \`mesh_send_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.
720
+ - **Front-load new task messages.** When calling \`mesh_enqueue_task\` or \`mesh_send_task\` for a new task, include everything the agent needs: what files to touch, what the problem is, what the fix should look like. The agent won't ask follow-up questions.
721
+ - **Avoid context-wasting restarts.** For follow-up, retry, commit/push, preview, or cleanup work on the same issue, prefer the existing idle session and send only the delta from its last verified state. Start a fresh chat/session only for genuinely independent work, explicit provider/user request, unsafe transcript contamination, or required branch/worktree isolation.
680
722
  - **Don't inspect code.** Treat delegated agent summaries as self-reports, not verification. Verify side effects via \`mesh_git_status\` (including related repo freshness when configured), not by reading source files.
681
723
  - **Don't over-parallelize.** Start with 1-2 concurrent tasks. Scale up if they succeed. Never launch a duplicate session or second worker solely because \`mesh_read_chat\` has no final assistant message while the delegated session is still showing tool/terminal activity.
682
724
  - **Handle failures with context.** If a task fails, check \`mesh_task_history\` first to see if this task was attempted before and how it failed. Read the chat to understand why, then decide: retry on the same node, reassign to a different node, or escalate to the user.
@@ -685,6 +727,7 @@ function buildRulesSection(coordinatorCliType) {
685
727
  - **Respect node capabilities.** Don't send build tasks to read-only nodes. Don't push from nodes that aren't allowed to.
686
728
  - **Never fabricate tool results.** Always call the actual tool; never pretend you did.
687
729
  - **Clean up worktree nodes.** After a worktree task completes and its changes are merged or checkpointed, call \`mesh_remove_node\` to free resources.
730
+ - **Do not strand completed branches.** A checkpointed or clean feature/worktree branch is not done by itself. Merge/refine it to the mesh default branch, or explicitly report one of \`pushed_feature_branch_needs_merge\`, \`blocked_review\`, \`cleanup_candidate\`, or \`not_mergeable\` with the next action.
688
731
  - **Name worktree branches meaningfully.** Use descriptive names like \`feat/auth-refactor\` or \`fix/build-123\`.${coordinatorNote}`;
689
732
  }
690
733
  var TOOLS_SECTION, TOOL_EXPOSURE_PREFLIGHT_SECTION, WORKFLOW_SECTION;
@@ -696,17 +739,24 @@ var init_coordinator_prompt = __esm({
696
739
 
697
740
  | Tool | Purpose |
698
741
  |------|---------|
699
- | \`mesh_status\` | Check all nodes' health, git state, and active sessions |
742
+ | \`mesh_status\` | Check all nodes' health, git state, active sessions, and branch convergence |
700
743
  | \`mesh_list_nodes\` | List nodes with workspace paths |
744
+ | \`mesh_enqueue_task\` | Add a task to the pull-based work queue; idle nodes auto-claim |
745
+ | \`mesh_view_queue\` | View queue status \u2014 pending, assigned, completed, failed, cancelled tasks |
746
+ | \`mesh_queue_cancel\` | Cancel a queue task without deleting audit history |
747
+ | \`mesh_queue_requeue\` | Return a task to pending for retry; clears stale session targets |
748
+ | \`mesh_send_task\` | Legacy push: enqueue a task targeted at a specific node |
701
749
  | \`mesh_launch_session\` | Start a new agent session on a node |
702
- | \`mesh_send_task\` | Send a task (natural language) to a running agent |
703
- | \`mesh_read_chat\` | Read an agent's recent messages to check progress |
750
+ | \`mesh_read_chat\` | Read recent chat messages from a delegated agent session |
751
+ | \`mesh_read_debug\` | Collect a daemon-side chat/parser debug bundle for a session |
704
752
  | \`mesh_task_history\` | Read the task ledger \u2014 dispatches, completions, failures. Use to understand what has been done before deciding next steps |
705
753
  | \`mesh_git_status\` | Check git status on a specific node |
706
754
  | \`mesh_checkpoint\` | Create a git checkpoint on a node |
707
755
  | \`mesh_approve\` | Approve/reject a pending agent action |
708
756
  | \`mesh_clone_node\` | Create a worktree node for isolated parallel branch work |
709
- | \`mesh_remove_node\` | Remove a node (cleans up worktree if applicable) |`;
757
+ | \`mesh_refine_node\` | Validate and merge a completed worktree node back into its base branch |
758
+ | \`mesh_remove_node\` | Remove a node (cleans up worktree if applicable) |
759
+ | \`mesh_cleanup_sessions\` | Manually clean up delegated session records for a node |`;
710
760
  TOOL_EXPOSURE_PREFLIGHT_SECTION = `## Tool Exposure Preflight
711
761
 
712
762
  Before doing any coordinator work, confirm that the actual callable tool list includes \`mesh_status\` and the other \`mesh_*\` tools from the table above. If this Repo Mesh coordinator prompt is present but the callable \`mesh_*\` tools are missing, the MCP server/tool manifest is stale or not injected yet. Do not substitute terminal/file/git tools, do not inspect or edit the repository directly, and do not continue as a non-mesh local coding agent. Stop immediately and tell the user to run \`/reload-mcp\` or start a fresh coordinator session so ADHDev can reconnect \`adhdev-mesh\`.`;
@@ -716,14 +766,16 @@ Before doing any coordinator work, confirm that the actual callable tool list in
716
766
  2. **Plan** \u2014 Decompose the user's request into independent tasks for parallel execution, or sequential tasks when dependencies exist. If \`mesh_task_history\` shows a recent failure for a task, decide whether to retry or reassign.
717
767
  3. **Queue / Delegate** \u2014 The Mesh uses an autonomous pull-based Work Queue:
718
768
  a. **General Tasks**: Enqueue tasks using \`mesh_enqueue_task\`. Idle node agents will automatically pull tasks from the queue and begin working.
719
- b. **Node Preparation**: Call \`mesh_launch_session\` to ensure enough agent sessions are active to handle the queue. If you need branch isolation for parallel work, call \`mesh_clone_node\` to create a worktree node first.
769
+ b. **Node Preparation**: Reuse an existing idle session on the correct node/provider before launching a new chat/session. Call \`mesh_launch_session\` only when no suitable session exists, when the user explicitly asks for a fresh provider/session, or when branch/worktree isolation requires it. If you need branch isolation for parallel work, call \`mesh_clone_node\` to create a worktree node first.
720
770
  c. **Targeted Tasks**: Use \`mesh_send_task\` only when you need to bypass the queue and force a specific node to execute a task immediately.
721
- d. Always 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.
771
+ d. For the first dispatch of a new task, provide a **complete, self-contained** instruction that includes all context the agent needs (file paths, line numbers, what to change, why). Do not send partial instructions expecting future follow-up.
772
+ e. For a continuation of the same issue in an existing session, send a concise **delta instruction**: current verified state, the exact failed/blocked step, the newly approved action, and final reporting requirements. Do not resend the full original task or open a new chat solely to continue the same work; that wastes coordinator and worker context.
722
773
  4. **Monitor** \u2014 Prefer event-driven completion/status notifications. Do **not** poll \`mesh_read_chat\` repeatedly. Use \`mesh_view_queue\` to see the status of all pending, assigned, completed, and failed tasks. Do not call \`mesh_read_chat\` again within a few seconds for the same generating session. Use at most one compact \`mesh_read_chat\` check after a completion/approval signal. Handle approvals via \`mesh_approve\`.
723
774
  5. **Verify** \u2014 When a task reports completion or git work is visible, call \`mesh_git_status\` to verify changes were made.
724
775
  6. **Checkpoint** \u2014 Call \`mesh_checkpoint\` to save the work.
725
- 7. **Clean up** \u2014 Remove worktree nodes via \`mesh_remove_node\` after their work is merged or no longer needed.
726
- 8. **Report** \u2014 Summarize what was done, what changed, and any issues.
776
+ 7. **Converge branches** \u2014 Before marking any task complete, classify every touched node/branch into exactly one final state: \`merged_to_main\`, \`pushed_feature_branch_needs_merge\`, \`blocked_review\`, \`cleanup_candidate\`, or \`not_mergeable\`. Use \`mesh_status\` branchConvergenceSummary and \`mesh_refine_node\` for clean worktree branches when safe. A task that remains on a non-main branch is not fully complete unless the final report names the follow-up state and next step.
777
+ 8. **Clean up** \u2014 Remove worktree nodes via \`mesh_remove_node\` after their work is merged or no longer needed.
778
+ 9. **Report** \u2014 Summarize what was done, what changed, any issues, and the branch convergence state.
727
779
 
728
780
  ## Failure Recovery
729
781
 
@@ -733,7 +785,7 @@ When a node agent stops unexpectedly, the daemon automatically enriches the syst
733
785
  - A recommendation: **retry**, **reassign**, or **escalate**
734
786
 
735
787
  Follow these recovery rules:
736
- 1. **If "Retry recommended"**: 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.
788
+ 1. **If "Retry recommended"**: Check \`mesh_view_queue\` first \u2014 the daemon may have auto-requeued. If not, re-launch the session on the same node (\`mesh_launch_session\`), then resend the original task (\`mesh_send_task\`). The system message includes the original task text.
737
789
  2. **If "Max retries exceeded"**: Do NOT retry on the same node. Either reassign the task to a different node, or inform the user that the task requires manual intervention.
738
790
  3. **If no recovery context**: The stop may be intentional (normal completion). Use \`mesh_read_chat\` once to verify, then move on.
739
791
  4. **Always record what happened**: After handling a failure, briefly note the outcome in your report to the user.`;
@@ -743,14 +795,23 @@ Follow these recovery rules:
743
795
  // src/mesh/mesh-ledger.ts
744
796
  var mesh_ledger_exports = {};
745
797
  __export(mesh_ledger_exports, {
798
+ MAX_LEDGER_SLICE_LIMIT: () => MAX_LEDGER_SLICE_LIMIT,
746
799
  appendLedgerEntry: () => appendLedgerEntry,
747
800
  appendRemoteLedgerEntries: () => appendRemoteLedgerEntries,
801
+ buildTaskCompletionEvidence: () => buildTaskCompletionEvidence,
748
802
  getLedgerDir: () => getLedgerDir,
749
803
  getLedgerSummary: () => getLedgerSummary,
750
804
  getSessionRecoveryContext: () => getSessionRecoveryContext,
805
+ isIntentionalCleanupStopEntry: () => isIntentionalCleanupStopEntry,
751
806
  meshLedgerEvents: () => meshLedgerEvents,
752
- readLedgerEntries: () => readLedgerEntries
807
+ readLedgerEntries: () => readLedgerEntries,
808
+ readLedgerSlice: () => readLedgerSlice
753
809
  });
810
+ function isIntentionalCleanupStopEntry(entry) {
811
+ if (entry.kind !== "session_stopped" && entry.kind !== "task_failed" && entry.kind !== "task_stalled") return false;
812
+ const payload = entry.payload && typeof entry.payload === "object" && !Array.isArray(entry.payload) ? entry.payload : {};
813
+ return payload.intentional === true && (payload.reason === "operator_cleanup" || payload.intentionalStopReason === "operator_cleanup" || payload.source === "mesh_cleanup_sessions" || payload.source === "mesh_remove_node");
814
+ }
754
815
  function getLedgerDir() {
755
816
  const dir = (0, import_path3.join)(getConfigDir(), LEDGER_DIR_NAME);
756
817
  if (!(0, import_fs3.existsSync)(dir)) {
@@ -766,6 +827,37 @@ function getRotatedPath(meshId, index) {
766
827
  const safe = meshId.replace(/[^a-zA-Z0-9_-]/g, "_");
767
828
  return (0, import_path3.join)(getLedgerDir(), `${safe}.${index}.jsonl`);
768
829
  }
830
+ function buildTaskCompletionEvidence(opts) {
831
+ const providerSessionId = opts.providerSessionId?.trim() || void 0;
832
+ const providerType = opts.providerType?.trim() || void 0;
833
+ return {
834
+ source: "agent_status_event",
835
+ event: opts.event,
836
+ nodeId: opts.nodeId,
837
+ sessionId: opts.sessionId,
838
+ providerType,
839
+ completedAt: opts.completedAt || (/* @__PURE__ */ new Date()).toISOString(),
840
+ transcriptHandle: {
841
+ kind: providerSessionId ? "provider_session" : "runtime_session",
842
+ sessionId: opts.sessionId,
843
+ providerSessionId,
844
+ finalSummaryAvailable: typeof opts.finalSummary === "string" && opts.finalSummary.trim().length > 0
845
+ },
846
+ git: {
847
+ status: "deferred",
848
+ reason: "ordinary_completion_git_status_not_checked"
849
+ },
850
+ validation: {
851
+ status: "deferred",
852
+ commandsRun: [],
853
+ reason: "ordinary_completion_validation_not_run"
854
+ },
855
+ checkpoint: {
856
+ attempted: false,
857
+ reason: "not_attempted_for_ordinary_completion"
858
+ }
859
+ };
860
+ }
769
861
  function appendLedgerEntry(meshId, partial) {
770
862
  const entry = {
771
863
  id: (0, import_crypto4.randomUUID)(),
@@ -792,15 +884,49 @@ function appendLedgerEntry(meshId, partial) {
792
884
  throw new Error(`Failed to append to ledger for mesh ${meshId}: ${e.message}`);
793
885
  }
794
886
  }
887
+ function clampLedgerSliceLimit(limit) {
888
+ if (typeof limit !== "number" || !Number.isFinite(limit)) return DEFAULT_LEDGER_SLICE_LIMIT;
889
+ return Math.max(1, Math.min(MAX_LEDGER_SLICE_LIMIT, Math.floor(limit)));
890
+ }
891
+ function isValidRemoteLedgerEntry(meshId, value) {
892
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
893
+ const entry = value;
894
+ if (typeof entry.id !== "string" || !entry.id.trim()) return false;
895
+ if (entry.meshId !== meshId) return false;
896
+ if (typeof entry.timestamp !== "string" || Number.isNaN(new Date(entry.timestamp).getTime())) return false;
897
+ if (typeof entry.kind !== "string" || !entry.kind.trim()) return false;
898
+ if (!entry.payload || typeof entry.payload !== "object" || Array.isArray(entry.payload)) return false;
899
+ return true;
900
+ }
795
901
  function appendRemoteLedgerEntries(meshId, entries) {
796
- if (entries.length === 0) return;
902
+ if (entries.length === 0) return { accepted: 0, skippedDuplicate: 0, rejectedInvalid: 0, entries: [] };
797
903
  const ledgerPath = getLedgerPath(meshId);
798
904
  const existing = new Set(readLedgerEntries(meshId).map((e) => e.id));
799
- const newEntries = entries.filter((e) => !existing.has(e.id));
800
- if (newEntries.length === 0) return;
905
+ const validEntries = [];
906
+ let rejectedInvalid = 0;
907
+ let skippedDuplicate = 0;
908
+ for (const entry of entries) {
909
+ if (!isValidRemoteLedgerEntry(meshId, entry)) {
910
+ rejectedInvalid++;
911
+ continue;
912
+ }
913
+ if (existing.has(entry.id)) {
914
+ skippedDuplicate++;
915
+ continue;
916
+ }
917
+ existing.add(entry.id);
918
+ validEntries.push(entry);
919
+ }
920
+ if (validEntries.length === 0) {
921
+ return { accepted: 0, skippedDuplicate, rejectedInvalid, entries: [] };
922
+ }
801
923
  try {
802
- const lines = newEntries.map((e) => JSON.stringify(e)).join("\n") + "\n";
924
+ const lines = validEntries.map((e) => JSON.stringify(e)).join("\n") + "\n";
803
925
  (0, import_fs3.appendFileSync)(ledgerPath, lines, { encoding: "utf-8", mode: 384 });
926
+ for (const entry of validEntries) {
927
+ meshLedgerEvents.emit("append", meshId, entry);
928
+ }
929
+ return { accepted: validEntries.length, skippedDuplicate, rejectedInvalid, entries: validEntries };
804
930
  } catch (e) {
805
931
  throw new Error(`Failed to append remote ledger entries for mesh ${meshId}: ${e.message}`);
806
932
  }
@@ -839,6 +965,34 @@ function readLedgerEntries(meshId, opts) {
839
965
  }
840
966
  return entries;
841
967
  }
968
+ function readLedgerSlice(meshId, opts) {
969
+ const limit = clampLedgerSliceLimit(opts?.limit);
970
+ let entries = readLedgerEntries(meshId, { since: opts?.since, kind: opts?.kind });
971
+ const afterId = typeof opts?.afterId === "string" && opts.afterId.trim() ? opts.afterId.trim() : null;
972
+ if (afterId) {
973
+ const index = entries.findIndex((entry) => entry.id === afterId);
974
+ entries = index >= 0 ? entries.slice(index + 1) : entries;
975
+ }
976
+ const bounded = entries.slice(0, limit);
977
+ return {
978
+ protocol: "adhdev.mesh.ledger.slice.v1",
979
+ meshId,
980
+ entries: bounded,
981
+ cursor: {
982
+ afterId,
983
+ nextAfterId: bounded.length ? bounded[bounded.length - 1].id : afterId,
984
+ limit,
985
+ hasMore: entries.length > bounded.length
986
+ },
987
+ summary: getLedgerSummary(meshId),
988
+ sourceOfTruth: {
989
+ kind: "local_jsonl",
990
+ path: getLedgerPath(meshId),
991
+ bounded: true,
992
+ maxLimit: MAX_LEDGER_SLICE_LIMIT
993
+ }
994
+ };
995
+ }
842
996
  function getLedgerSummary(meshId) {
843
997
  const entries = readLedgerEntries(meshId);
844
998
  const now = Date.now();
@@ -864,15 +1018,17 @@ function getLedgerSummary(meshId) {
864
1018
  summary.taskCompleted++;
865
1019
  break;
866
1020
  case "task_failed": {
1021
+ if (isIntentionalCleanupStopEntry(entry)) break;
867
1022
  summary.taskFailed++;
868
1023
  if (new Date(entry.timestamp).getTime() >= recentFailureCutoff) {
869
1024
  summary.recentFailures++;
870
1025
  }
871
1026
  break;
872
1027
  }
873
- case "task_stalled":
874
- summary.taskStalled++;
1028
+ case "task_stalled": {
1029
+ if (!isIntentionalCleanupStopEntry(entry)) summary.taskStalled++;
875
1030
  break;
1031
+ }
876
1032
  case "session_launched":
877
1033
  summary.sessionLaunched++;
878
1034
  break;
@@ -911,6 +1067,7 @@ function getSessionRecoveryContext(meshId, opts) {
911
1067
  if (new Date(e.timestamp).getTime() < recentWindow) break;
912
1068
  if (opts.nodeId && e.nodeId !== opts.nodeId) continue;
913
1069
  if (e.kind === "task_failed") {
1070
+ if (isIntentionalCleanupStopEntry(e)) continue;
914
1071
  consecutiveNodeFailures++;
915
1072
  } else if (e.kind === "task_completed" || e.kind === "task_dispatched") {
916
1073
  break;
@@ -961,7 +1118,7 @@ function rotateLedgerFile(meshId, currentPath) {
961
1118
  } catch {
962
1119
  }
963
1120
  }
964
- var import_fs3, import_path3, import_crypto4, import_events, LEDGER_DIR_NAME, MAX_FILE_SIZE_BYTES, RECENT_FAILURE_WINDOW_MS, meshLedgerEvents;
1121
+ var import_fs3, import_path3, import_crypto4, import_events, LEDGER_DIR_NAME, MAX_FILE_SIZE_BYTES, RECENT_FAILURE_WINDOW_MS, DEFAULT_LEDGER_SLICE_LIMIT, MAX_LEDGER_SLICE_LIMIT, meshLedgerEvents;
965
1122
  var init_mesh_ledger = __esm({
966
1123
  "src/mesh/mesh-ledger.ts"() {
967
1124
  "use strict";
@@ -973,11 +1130,27 @@ var init_mesh_ledger = __esm({
973
1130
  LEDGER_DIR_NAME = "mesh-ledger";
974
1131
  MAX_FILE_SIZE_BYTES = 10 * 1024 * 1024;
975
1132
  RECENT_FAILURE_WINDOW_MS = 30 * 60 * 1e3;
1133
+ DEFAULT_LEDGER_SLICE_LIMIT = 100;
1134
+ MAX_LEDGER_SLICE_LIMIT = 500;
976
1135
  meshLedgerEvents = new import_events.EventEmitter();
977
1136
  }
978
1137
  });
979
1138
 
980
1139
  // src/mesh/mesh-work-queue.ts
1140
+ var mesh_work_queue_exports = {};
1141
+ __export(mesh_work_queue_exports, {
1142
+ ACTIVE_MESH_QUEUE_STATUSES: () => ACTIVE_MESH_QUEUE_STATUSES,
1143
+ HISTORICAL_MESH_QUEUE_STATUSES: () => HISTORICAL_MESH_QUEUE_STATUSES,
1144
+ cancelTask: () => cancelTask,
1145
+ claimNextTask: () => claimNextTask,
1146
+ enqueueTask: () => enqueueTask,
1147
+ getMeshQueueStats: () => getMeshQueueStats,
1148
+ getQueue: () => getQueue,
1149
+ recordTaskAutoLaunch: () => recordTaskAutoLaunch,
1150
+ requeueTask: () => requeueTask,
1151
+ updateSessionTaskStatus: () => updateSessionTaskStatus,
1152
+ updateTaskStatus: () => updateTaskStatus
1153
+ });
981
1154
  function getQueuePath(meshId) {
982
1155
  const safe = meshId.replace(/[^a-zA-Z0-9_-]/g, "_");
983
1156
  return (0, import_path4.join)(getLedgerDir(), `${safe}.queue.json`);
@@ -1004,6 +1177,7 @@ function enqueueTask(meshId, message, opts) {
1004
1177
  message,
1005
1178
  status: "pending",
1006
1179
  targetNodeId: opts?.targetNodeId,
1180
+ targetSessionId: opts?.targetSessionId,
1007
1181
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1008
1182
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1009
1183
  };
@@ -1021,15 +1195,21 @@ function getQueue(meshId, opts) {
1021
1195
  }
1022
1196
  function claimNextTask(meshId, nodeId, sessionId) {
1023
1197
  const queue = readQueue(meshId);
1024
- let targetIdx = queue.findIndex((q) => q.status === "pending" && q.targetNodeId === nodeId);
1198
+ const hasActiveAssignment = queue.some((q) => q.status === "assigned" && (q.assignedSessionId === sessionId || q.assignedNodeId === nodeId));
1199
+ if (hasActiveAssignment) return null;
1200
+ let targetIdx = queue.findIndex((q) => q.status === "pending" && q.targetSessionId === sessionId);
1201
+ if (targetIdx === -1) {
1202
+ targetIdx = queue.findIndex((q) => q.status === "pending" && q.targetNodeId === nodeId && !q.targetSessionId);
1203
+ }
1025
1204
  if (targetIdx === -1) {
1026
- targetIdx = queue.findIndex((q) => q.status === "pending" && !q.targetNodeId);
1205
+ targetIdx = queue.findIndex((q) => q.status === "pending" && !q.targetNodeId && !q.targetSessionId);
1027
1206
  }
1028
1207
  if (targetIdx === -1) return null;
1029
1208
  const entry = queue[targetIdx];
1030
1209
  entry.status = "assigned";
1031
1210
  entry.assignedNodeId = nodeId;
1032
1211
  entry.assignedSessionId = sessionId;
1212
+ entry.dispatchTimestamp = (/* @__PURE__ */ new Date()).toISOString();
1033
1213
  entry.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1034
1214
  writeQueue(meshId, queue);
1035
1215
  return entry;
@@ -1043,28 +1223,106 @@ function updateTaskStatus(meshId, taskId, status) {
1043
1223
  writeQueue(meshId, queue);
1044
1224
  return queue[idx];
1045
1225
  }
1226
+ function recordTaskAutoLaunch(meshId, taskId, autoLaunch) {
1227
+ const queue = readQueue(meshId);
1228
+ const idx = queue.findIndex((q) => q.id === taskId);
1229
+ if (idx === -1) return null;
1230
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1231
+ queue[idx].autoLaunch = {
1232
+ ...autoLaunch,
1233
+ updatedAt: now
1234
+ };
1235
+ queue[idx].updatedAt = now;
1236
+ writeQueue(meshId, queue);
1237
+ return queue[idx];
1238
+ }
1239
+ function cancelTask(meshId, taskId, opts) {
1240
+ const queue = readQueue(meshId);
1241
+ const idx = queue.findIndex((q) => q.id === taskId);
1242
+ if (idx === -1) return null;
1243
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1244
+ queue[idx].status = "cancelled";
1245
+ queue[idx].updatedAt = now;
1246
+ queue[idx].cancelledAt = now;
1247
+ if (opts?.reason) queue[idx].cancelReason = opts.reason;
1248
+ writeQueue(meshId, queue);
1249
+ return queue[idx];
1250
+ }
1251
+ function requeueTask(meshId, taskId, opts) {
1252
+ const queue = readQueue(meshId);
1253
+ const idx = queue.findIndex((q) => q.id === taskId);
1254
+ if (idx === -1) return null;
1255
+ const entry = queue[idx];
1256
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1257
+ entry.status = "pending";
1258
+ delete entry.assignedNodeId;
1259
+ delete entry.assignedSessionId;
1260
+ delete entry.cancelledAt;
1261
+ delete entry.cancelReason;
1262
+ if (opts?.clearTargetNode) delete entry.targetNodeId;
1263
+ if (typeof opts?.targetNodeId === "string") entry.targetNodeId = opts.targetNodeId;
1264
+ if (opts?.clearTargetSession !== false) delete entry.targetSessionId;
1265
+ if (typeof opts?.targetSessionId === "string") entry.targetSessionId = opts.targetSessionId;
1266
+ entry.updatedAt = now;
1267
+ entry.requeuedAt = now;
1268
+ entry.requeueCount = (entry.requeueCount || 0) + 1;
1269
+ if (opts?.reason) entry.requeueReason = opts.reason;
1270
+ writeQueue(meshId, queue);
1271
+ return entry;
1272
+ }
1046
1273
  function updateSessionTaskStatus(meshId, sessionId, status) {
1047
1274
  const queue = readQueue(meshId);
1275
+ let bestIdx = -1;
1276
+ let bestTime = 0;
1048
1277
  for (let i = queue.length - 1; i >= 0; i--) {
1049
1278
  if (queue[i].assignedSessionId === sessionId && queue[i].status === "assigned") {
1050
- queue[i].status = status;
1051
- queue[i].updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1052
- writeQueue(meshId, queue);
1053
- return queue[i];
1279
+ const time = new Date(queue[i].dispatchTimestamp || queue[i].updatedAt).getTime();
1280
+ if (time > bestTime) {
1281
+ bestTime = time;
1282
+ bestIdx = i;
1283
+ }
1054
1284
  }
1055
1285
  }
1056
- return null;
1286
+ if (bestIdx === -1) return null;
1287
+ queue[bestIdx].status = status;
1288
+ queue[bestIdx].updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1289
+ writeQueue(meshId, queue);
1290
+ return queue[bestIdx];
1057
1291
  }
1058
1292
  function getMeshQueueStats(meshId) {
1059
1293
  const queue = readQueue(meshId);
1294
+ const pending = queue.filter((q) => q.status === "pending").length;
1295
+ const assigned = queue.filter((q) => q.status === "assigned").length;
1296
+ const completed = queue.filter((q) => q.status === "completed").length;
1297
+ const failed = queue.filter((q) => q.status === "failed").length;
1298
+ const cancelled = queue.filter((q) => q.status === "cancelled").length;
1060
1299
  return {
1061
- pending: queue.filter((q) => q.status === "pending").length,
1062
- assigned: queue.filter((q) => q.status === "assigned").length,
1063
- completed: queue.filter((q) => q.status === "completed").length,
1064
- failed: queue.filter((q) => q.status === "failed").length
1300
+ total: queue.length,
1301
+ active: pending + assigned,
1302
+ historical: completed + failed + cancelled,
1303
+ pending,
1304
+ assigned,
1305
+ completed,
1306
+ failed,
1307
+ cancelled,
1308
+ activeCounts: {
1309
+ pending,
1310
+ assigned
1311
+ },
1312
+ historicalCounts: {
1313
+ completed,
1314
+ failed,
1315
+ cancelled
1316
+ },
1317
+ activeAssignments: queue.filter((q) => q.status === "assigned").map((q) => ({
1318
+ id: q.id,
1319
+ nodeId: q.assignedNodeId,
1320
+ sessionId: q.assignedSessionId,
1321
+ message: q.message
1322
+ }))
1065
1323
  };
1066
1324
  }
1067
- var import_fs4, import_path4, import_crypto5;
1325
+ var import_fs4, import_path4, import_crypto5, ACTIVE_MESH_QUEUE_STATUSES, HISTORICAL_MESH_QUEUE_STATUSES;
1068
1326
  var init_mesh_work_queue = __esm({
1069
1327
  "src/mesh/mesh-work-queue.ts"() {
1070
1328
  "use strict";
@@ -1072,6 +1330,143 @@ var init_mesh_work_queue = __esm({
1072
1330
  import_path4 = require("path");
1073
1331
  import_crypto5 = require("crypto");
1074
1332
  init_mesh_ledger();
1333
+ ACTIVE_MESH_QUEUE_STATUSES = ["pending", "assigned"];
1334
+ HISTORICAL_MESH_QUEUE_STATUSES = ["completed", "failed", "cancelled"];
1335
+ }
1336
+ });
1337
+
1338
+ // src/detection/cli-detector.ts
1339
+ function parseVersion(raw) {
1340
+ const match = raw.match(/v?(\d+\.\d+(?:\.\d+)?(?:-[a-zA-Z0-9.]+)?)/);
1341
+ return match ? match[1] : raw.split("\n")[0].slice(0, 100);
1342
+ }
1343
+ function shellQuote(value) {
1344
+ if (/^[a-zA-Z0-9_./:@%+=,-]+$/.test(value)) return value;
1345
+ return `"${value.replace(/(["\\$`])/g, "\\$1")}"`;
1346
+ }
1347
+ function expandHome(value) {
1348
+ const trimmed = value.trim();
1349
+ if (!trimmed.startsWith("~")) return trimmed;
1350
+ return path8.join(os2.homedir(), trimmed.slice(1));
1351
+ }
1352
+ function isExplicitCommandPath(command) {
1353
+ const trimmed = command.trim();
1354
+ return path8.isAbsolute(trimmed) || trimmed.includes("/") || trimmed.includes("\\") || trimmed.startsWith("~");
1355
+ }
1356
+ function resolveCommandPath(command) {
1357
+ const trimmed = command.trim();
1358
+ if (!trimmed) return null;
1359
+ if (isExplicitCommandPath(trimmed)) {
1360
+ const expanded = expandHome(trimmed);
1361
+ const candidate = path8.isAbsolute(expanded) ? expanded : path8.resolve(expanded);
1362
+ return (0, import_fs5.existsSync)(candidate) ? candidate : null;
1363
+ }
1364
+ return null;
1365
+ }
1366
+ function execAsync(cmd, timeoutMs = 5e3) {
1367
+ return new Promise((resolve16) => {
1368
+ const child = (0, import_child_process.exec)(cmd, {
1369
+ encoding: "utf-8",
1370
+ timeout: timeoutMs,
1371
+ ...process.platform === "win32" ? { windowsHide: true } : {}
1372
+ }, (err, stdout) => {
1373
+ if (err || !stdout?.trim()) {
1374
+ resolve16(null);
1375
+ } else {
1376
+ resolve16(stdout.trim());
1377
+ }
1378
+ });
1379
+ child.on("error", () => resolve16(null));
1380
+ });
1381
+ }
1382
+ async function detectCLIs(providerLoader, options) {
1383
+ const platform10 = os2.platform();
1384
+ const whichCmd = platform10 === "win32" ? "where" : "which";
1385
+ const includeVersion = options?.includeVersion !== false;
1386
+ const cliList = providerLoader ? providerLoader.getCliDetectionList() : [];
1387
+ const results = await Promise.all(
1388
+ cliList.map(async (cli) => {
1389
+ try {
1390
+ const explicitPath = resolveCommandPath(cli.command);
1391
+ const pathResult = explicitPath || await execAsync(`${whichCmd} ${shellQuote(cli.command)}`);
1392
+ if (!pathResult) return { ...cli, installed: false };
1393
+ const firstPath = explicitPath || pathResult.split("\n")[0];
1394
+ let version;
1395
+ if (includeVersion) {
1396
+ const versionCommands = [
1397
+ `"${firstPath}" --version`,
1398
+ `"${firstPath}" -V`,
1399
+ `"${firstPath}" -v`,
1400
+ cli.versionCommand
1401
+ ].filter((v) => !!v);
1402
+ try {
1403
+ for (const versionCommand of versionCommands) {
1404
+ const versionResult = await execAsync(versionCommand, 3e3);
1405
+ if (versionResult) {
1406
+ version = parseVersion(versionResult);
1407
+ break;
1408
+ }
1409
+ }
1410
+ } catch {
1411
+ }
1412
+ }
1413
+ return { ...cli, installed: true, version, path: firstPath };
1414
+ } catch {
1415
+ return { ...cli, installed: false };
1416
+ }
1417
+ })
1418
+ );
1419
+ return results;
1420
+ }
1421
+ async function detectCLI(cliId, providerLoader, options) {
1422
+ const resolvedId = providerLoader ? providerLoader.resolveAlias(cliId) : cliId;
1423
+ if (providerLoader) {
1424
+ const cliList = providerLoader.getCliDetectionList();
1425
+ const target = cliList.find((c) => c.id === resolvedId);
1426
+ if (target) {
1427
+ const platform10 = os2.platform();
1428
+ const whichCmd = platform10 === "win32" ? "where" : "which";
1429
+ try {
1430
+ const explicitPath = resolveCommandPath(target.command);
1431
+ const pathResult = explicitPath || await execAsync(`${whichCmd} ${shellQuote(target.command)}`);
1432
+ if (!pathResult) return null;
1433
+ const firstPath = explicitPath || pathResult.split("\n")[0];
1434
+ let version;
1435
+ if (options?.includeVersion !== false) {
1436
+ const versionCommands = [
1437
+ `"${firstPath}" --version`,
1438
+ `"${firstPath}" -V`,
1439
+ `"${firstPath}" -v`,
1440
+ target.versionCommand
1441
+ ].filter((v) => !!v);
1442
+ try {
1443
+ for (const versionCommand of versionCommands) {
1444
+ const versionResult = await execAsync(versionCommand, 3e3);
1445
+ if (versionResult) {
1446
+ version = parseVersion(versionResult);
1447
+ break;
1448
+ }
1449
+ }
1450
+ } catch {
1451
+ }
1452
+ }
1453
+ return { ...target, installed: true, version, path: firstPath };
1454
+ } catch {
1455
+ return null;
1456
+ }
1457
+ }
1458
+ }
1459
+ const all = await detectCLIs(providerLoader, options);
1460
+ return all.find((c) => c.id === resolvedId && c.installed) || null;
1461
+ }
1462
+ var import_child_process, os2, path8, import_fs5;
1463
+ var init_cli_detector = __esm({
1464
+ "src/detection/cli-detector.ts"() {
1465
+ "use strict";
1466
+ import_child_process = require("child_process");
1467
+ os2 = __toESM(require("os"));
1468
+ path8 = __toESM(require("path"));
1469
+ import_fs5 = require("fs");
1075
1470
  }
1076
1471
  });
1077
1472
 
@@ -1090,13 +1485,13 @@ function getDaemonLogDir() {
1090
1485
  return LOG_DIR;
1091
1486
  }
1092
1487
  function getCurrentDaemonLogPath(date = /* @__PURE__ */ new Date()) {
1093
- return path8.join(LOG_DIR, `daemon-${date.toISOString().slice(0, 10)}.log`);
1488
+ return path9.join(LOG_DIR, `daemon-${date.toISOString().slice(0, 10)}.log`);
1094
1489
  }
1095
1490
  function checkDateRotation() {
1096
1491
  const today = getDateStr();
1097
1492
  if (today !== currentDate) {
1098
1493
  currentDate = today;
1099
- currentLogFile = path8.join(LOG_DIR, `daemon-${currentDate}.log`);
1494
+ currentLogFile = path9.join(LOG_DIR, `daemon-${currentDate}.log`);
1100
1495
  cleanOldLogs();
1101
1496
  }
1102
1497
  }
@@ -1110,7 +1505,7 @@ function cleanOldLogs() {
1110
1505
  const dateMatch = file.match(/daemon-(\d{4}-\d{2}-\d{2})/);
1111
1506
  if (dateMatch && dateMatch[1] < cutoffStr) {
1112
1507
  try {
1113
- fs2.unlinkSync(path8.join(LOG_DIR, file));
1508
+ fs2.unlinkSync(path9.join(LOG_DIR, file));
1114
1509
  } catch {
1115
1510
  }
1116
1511
  }
@@ -1226,17 +1621,17 @@ function installGlobalInterceptor() {
1226
1621
  writeToFile(`Log file: ${currentLogFile}`);
1227
1622
  writeToFile(`Log level: ${currentLevel}`);
1228
1623
  }
1229
- var fs2, path8, os2, LEVEL_NUM, LEVEL_LABEL, currentLevel, LOG_DIR, MAX_LOG_SIZE, MAX_LOG_DAYS, currentDate, currentLogFile, writeCount, RING_BUFFER_SIZE, ringBuffer, origConsoleLog, origConsoleError, origConsoleWarn, LOG, interceptorInstalled, LOG_PATH;
1624
+ var fs2, path9, os3, LEVEL_NUM, LEVEL_LABEL, currentLevel, LOG_DIR, MAX_LOG_SIZE, MAX_LOG_DAYS, currentDate, currentLogFile, writeCount, RING_BUFFER_SIZE, ringBuffer, origConsoleLog, origConsoleError, origConsoleWarn, LOG, interceptorInstalled, LOG_PATH;
1230
1625
  var init_logger = __esm({
1231
1626
  "src/logging/logger.ts"() {
1232
1627
  "use strict";
1233
1628
  fs2 = __toESM(require("fs"));
1234
- path8 = __toESM(require("path"));
1235
- os2 = __toESM(require("os"));
1629
+ path9 = __toESM(require("path"));
1630
+ os3 = __toESM(require("os"));
1236
1631
  LEVEL_NUM = { debug: 0, info: 1, warn: 2, error: 3 };
1237
1632
  LEVEL_LABEL = { debug: "DBG", info: "INF", warn: "WRN", error: "ERR" };
1238
1633
  currentLevel = "info";
1239
- LOG_DIR = process.platform === "win32" ? path8.join(process.env.LOCALAPPDATA || process.env.APPDATA || path8.join(os2.homedir(), "AppData", "Local"), "adhdev", "logs") : process.platform === "darwin" ? path8.join(os2.homedir(), "Library", "Logs", "adhdev") : path8.join(os2.homedir(), ".local", "share", "adhdev", "logs");
1634
+ LOG_DIR = process.platform === "win32" ? path9.join(process.env.LOCALAPPDATA || process.env.APPDATA || path9.join(os3.homedir(), "AppData", "Local"), "adhdev", "logs") : process.platform === "darwin" ? path9.join(os3.homedir(), "Library", "Logs", "adhdev") : path9.join(os3.homedir(), ".local", "share", "adhdev", "logs");
1240
1635
  MAX_LOG_SIZE = 5 * 1024 * 1024;
1241
1636
  MAX_LOG_DAYS = 7;
1242
1637
  try {
@@ -1244,16 +1639,16 @@ var init_logger = __esm({
1244
1639
  } catch {
1245
1640
  }
1246
1641
  currentDate = getDateStr();
1247
- currentLogFile = path8.join(LOG_DIR, `daemon-${currentDate}.log`);
1642
+ currentLogFile = path9.join(LOG_DIR, `daemon-${currentDate}.log`);
1248
1643
  cleanOldLogs();
1249
1644
  try {
1250
- const oldLog = path8.join(LOG_DIR, "daemon.log");
1645
+ const oldLog = path9.join(LOG_DIR, "daemon.log");
1251
1646
  if (fs2.existsSync(oldLog)) {
1252
1647
  const stat2 = fs2.statSync(oldLog);
1253
1648
  const oldDate = stat2.mtime.toISOString().slice(0, 10);
1254
- fs2.renameSync(oldLog, path8.join(LOG_DIR, `daemon-${oldDate}.log`));
1649
+ fs2.renameSync(oldLog, path9.join(LOG_DIR, `daemon-${oldDate}.log`));
1255
1650
  }
1256
- const oldLogBackup = path8.join(LOG_DIR, "daemon.log.old");
1651
+ const oldLogBackup = path9.join(LOG_DIR, "daemon.log.old");
1257
1652
  if (fs2.existsSync(oldLogBackup)) {
1258
1653
  fs2.unlinkSync(oldLogBackup);
1259
1654
  }
@@ -1285,14 +1680,16 @@ var init_logger = __esm({
1285
1680
  }
1286
1681
  };
1287
1682
  interceptorInstalled = false;
1288
- LOG_PATH = path8.join(LOG_DIR, `daemon-${getDateStr()}.log`);
1683
+ LOG_PATH = path9.join(LOG_DIR, `daemon-${getDateStr()}.log`);
1289
1684
  }
1290
1685
  });
1291
1686
 
1292
1687
  // src/mesh/mesh-events.ts
1293
1688
  var mesh_events_exports = {};
1294
1689
  __export(mesh_events_exports, {
1690
+ clearPendingMeshCoordinatorEvents: () => clearPendingMeshCoordinatorEvents,
1295
1691
  drainPendingMeshCoordinatorEvents: () => drainPendingMeshCoordinatorEvents,
1692
+ getPendingMeshCoordinatorEvents: () => getPendingMeshCoordinatorEvents,
1296
1693
  handleMeshForwardEvent: () => handleMeshForwardEvent,
1297
1694
  setupMeshEventForwarding: () => setupMeshEventForwarding,
1298
1695
  triggerMeshQueue: () => triggerMeshQueue,
@@ -1301,9 +1698,18 @@ __export(mesh_events_exports, {
1301
1698
  function drainPendingMeshCoordinatorEvents() {
1302
1699
  return pendingMeshCoordinatorEvents.splice(0);
1303
1700
  }
1701
+ function getPendingMeshCoordinatorEvents() {
1702
+ return pendingMeshCoordinatorEvents.slice();
1703
+ }
1704
+ function clearPendingMeshCoordinatorEvents() {
1705
+ pendingMeshCoordinatorEvents.splice(0);
1706
+ }
1304
1707
  function readNonEmptyString(value) {
1305
1708
  return typeof value === "string" && value.trim() ? value.trim() : "";
1306
1709
  }
1710
+ function resolveEventSessionId(event, fallback) {
1711
+ return readNonEmptyString(event.targetSessionId) || readNonEmptyString(event.sessionId) || readNonEmptyString(event.instanceId) || readNonEmptyString(fallback);
1712
+ }
1307
1713
  function isMeshCoordinatorEvent(eventName) {
1308
1714
  return typeof eventName === "string" && MESH_COORDINATOR_EVENTS.has(eventName);
1309
1715
  }
@@ -1315,38 +1721,323 @@ function formatCompletionMetadata(event) {
1315
1721
  ].filter(Boolean);
1316
1722
  return parts.length > 0 ? ` (${parts.join("; ")})` : "";
1317
1723
  }
1724
+ function getMeshWithCache(components, meshId) {
1725
+ const localMesh = getMesh(meshId);
1726
+ if (localMesh) return localMesh;
1727
+ return components.router?.getCachedInlineMesh(meshId);
1728
+ }
1729
+ function isIntentionalCleanupStopMetadata(event) {
1730
+ return event.intentional === true || event.intentionalStop === true || event.operatorCleanup === true || event.reason === "operator_cleanup" || event.stopReason === "operator_cleanup" || event.cleanupReason === "operator_cleanup" || event.source === "mesh_cleanup_sessions" || event.source === "mesh_remove_node";
1731
+ }
1732
+ function hasRecentIntentionalCleanupStop(meshId, sessionId, nodeId) {
1733
+ if (!sessionId && !nodeId) return false;
1734
+ const cutoff = Date.now() - INTENTIONAL_CLEANUP_STOP_SUPPRESSION_MS;
1735
+ const entries = readLedgerEntries(meshId);
1736
+ for (let i = entries.length - 1; i >= 0; i--) {
1737
+ const entry = entries[i];
1738
+ const timestamp = new Date(entry.timestamp).getTime();
1739
+ if (!Number.isNaN(timestamp) && timestamp < cutoff) break;
1740
+ if (!isIntentionalCleanupStopEntry(entry)) continue;
1741
+ if (sessionId && entry.sessionId === sessionId) return true;
1742
+ if (!sessionId && nodeId && entry.nodeId === nodeId) return true;
1743
+ }
1744
+ return false;
1745
+ }
1746
+ function shouldSuppressIntentionalCleanupStop(args) {
1747
+ if (args.event !== "agent:stopped" && args.event !== "monitor:long_generating") return false;
1748
+ if (isIntentionalCleanupStopMetadata(args.metadataEvent)) return true;
1749
+ return hasRecentIntentionalCleanupStop(args.meshId, args.sessionId, args.nodeId);
1750
+ }
1318
1751
  function tryAssignQueueTask(components, meshId, nodeId, sessionId, providerType) {
1319
1752
  const task = claimNextTask(meshId, nodeId, sessionId);
1320
- if (!task) return false;
1753
+ if (!task) {
1754
+ return false;
1755
+ }
1321
1756
  LOG.info("MeshQueue", `Node ${nodeId} (${sessionId}) pulled task ${task.id}`);
1757
+ const mesh = getMeshWithCache(components, meshId);
1758
+ const node = mesh?.nodes.find((n) => n.id === nodeId);
1759
+ if (node?.daemonId && components.dispatchMeshCommand) {
1760
+ const isLocalNode = components.cliManager.adapters.has(sessionId);
1761
+ if (!isLocalNode) {
1762
+ components.dispatchMeshCommand(node.daemonId, "agent_command", {
1763
+ targetSessionId: sessionId,
1764
+ cliType: providerType,
1765
+ action: "send_chat",
1766
+ message: task.message
1767
+ }).catch((e) => {
1768
+ LOG.error("MeshQueue", `Failed to dispatch task via P2P to remote node ${nodeId}: ${e?.message}`);
1769
+ updateTaskStatus(meshId, task.id, "failed");
1770
+ });
1771
+ return true;
1772
+ }
1773
+ }
1322
1774
  components.cliManager.handleCliCommand("agent_command", {
1323
1775
  targetSessionId: sessionId,
1324
1776
  cliType: providerType,
1325
1777
  action: "send_chat",
1326
- input: task.message
1778
+ message: task.message
1327
1779
  }).catch((e) => {
1328
- LOG.error("MeshQueue", `Failed to dispatch task to node ${nodeId}: ${e?.message}`);
1780
+ LOG.error("MeshQueue", `Failed to dispatch task locally to node ${nodeId}: ${e?.message}`);
1781
+ updateTaskStatus(meshId, task.id, "failed");
1329
1782
  });
1330
1783
  return true;
1331
1784
  }
1332
- function triggerMeshQueue(components, meshId) {
1333
- const mesh = getMesh(meshId);
1785
+ function normalizeProviderPriority(policy) {
1786
+ const raw = policy && typeof policy === "object" && !Array.isArray(policy) ? policy.providerPriority : void 0;
1787
+ if (!Array.isArray(raw)) return [];
1788
+ const seen = /* @__PURE__ */ new Set();
1789
+ return raw.map((type) => typeof type === "string" ? type.trim() : "").filter(Boolean).filter((type) => {
1790
+ if (seen.has(type)) return false;
1791
+ seen.add(type);
1792
+ return true;
1793
+ });
1794
+ }
1795
+ function isTerminalSessionStatus(status) {
1796
+ return ["stopped", "failed", "terminated", "exited", "closed"].includes(status);
1797
+ }
1798
+ function isIdleSessionState(state) {
1799
+ const status = readNonEmptyString(state?.status).toLowerCase();
1800
+ if (isTerminalSessionStatus(status)) return false;
1801
+ return status === "idle" || state?.activeChat?.status === "waiting_input";
1802
+ }
1803
+ function isDirtyNode(node) {
1804
+ return node?.health === "dirty" || node?.git?.dirty === true;
1805
+ }
1806
+ function isLaunchableNode(node) {
1807
+ if (!node || node.status === "disabled" || node.status === "removed") return false;
1808
+ const health = readNonEmptyString(node.health).toLowerCase();
1809
+ if (!health) return true;
1810
+ return health === "online" || health === "unknown";
1811
+ }
1812
+ function localAutoLaunchSkipReason(node) {
1813
+ const daemonId = readNonEmptyString(node?.daemonId);
1814
+ const machineId = readNonEmptyString(node?.machineId);
1815
+ const appConfig = loadConfig();
1816
+ const localMachineId = readNonEmptyString(appConfig.machineId) || readNonEmptyString(appConfig.registeredMachineId);
1817
+ const cloudDaemonId = localMachineId ? `daemon_${localMachineId}` : "";
1818
+ const standaloneDaemonId = localMachineId ? `standalone_${localMachineId}` : "";
1819
+ const daemonMatchesLocal = !daemonId || daemonId === cloudDaemonId || daemonId === standaloneDaemonId;
1820
+ const machineMatchesLocal = !machineId || localMachineId && machineId === localMachineId;
1821
+ if (node?.isLocalWorktree === true) {
1822
+ return daemonMatchesLocal && machineMatchesLocal ? null : "remote_auto_launch_unsupported";
1823
+ }
1824
+ if (daemonId || machineId) {
1825
+ return daemonMatchesLocal && machineMatchesLocal ? null : "remote_auto_launch_unsupported";
1826
+ }
1827
+ return null;
1828
+ }
1829
+ function activeAssignedCount(meshId) {
1830
+ return getQueue(meshId, { status: ["assigned"] }).length;
1831
+ }
1832
+ function nodeHasActiveAssignment(meshId, nodeId) {
1833
+ return getQueue(meshId, { status: ["assigned"] }).some((task) => task.assignedNodeId === nodeId);
1834
+ }
1835
+ function liveSessionCountForNode(components, meshId, nodeId) {
1836
+ return components.instanceManager.getByCategory("cli").filter((inst) => {
1837
+ const state = inst.getState();
1838
+ const settings = state.settings || {};
1839
+ if (readNonEmptyString(settings.meshNodeFor) !== meshId) return false;
1840
+ const instNodeId = readNonEmptyString(settings.meshNodeId) || readNonEmptyString(settings.nodeId);
1841
+ if (instNodeId !== nodeId) return false;
1842
+ const status = readNonEmptyString(state.status).toLowerCase();
1843
+ return !isTerminalSessionStatus(status);
1844
+ }).length;
1845
+ }
1846
+ function recordAutoLaunchEvent(meshId, args) {
1847
+ try {
1848
+ appendLedgerEntry(meshId, {
1849
+ kind: "session_auto_launch",
1850
+ nodeId: args.nodeId,
1851
+ sessionId: args.sessionId,
1852
+ providerType: args.providerType,
1853
+ payload: {
1854
+ phase: args.phase,
1855
+ taskId: args.taskId,
1856
+ reason: args.reason,
1857
+ error: args.error
1858
+ }
1859
+ });
1860
+ } catch (e) {
1861
+ LOG.warn("MeshQueue", `Failed to record auto-launch ledger event: ${e?.message || e}`);
1862
+ }
1863
+ }
1864
+ function markAutoLaunch(meshId, taskId, args) {
1865
+ recordTaskAutoLaunch(meshId, taskId, {
1866
+ status: args.status,
1867
+ reason: args.reason || args.error,
1868
+ nodeId: args.nodeId,
1869
+ providerType: args.providerType,
1870
+ sessionId: args.sessionId
1871
+ });
1872
+ recordAutoLaunchEvent(meshId, {
1873
+ phase: args.status,
1874
+ taskId,
1875
+ nodeId: args.nodeId,
1876
+ providerType: args.providerType,
1877
+ sessionId: args.sessionId,
1878
+ reason: args.reason,
1879
+ error: args.error
1880
+ });
1881
+ }
1882
+ async function resolveUsableProvider(components, nodeId, node) {
1883
+ const providerPriority = normalizeProviderPriority(node?.policy);
1884
+ if (!providerPriority.length) return { reason: "missing_provider_priority" };
1885
+ const providerLoader = components.providerLoader;
1886
+ if (!providerLoader) return { reason: "provider_loader_unavailable" };
1887
+ const failed = [];
1888
+ for (const requestedType of providerPriority) {
1889
+ const normalizedType = typeof providerLoader.resolveAlias === "function" ? providerLoader.resolveAlias(requestedType) : requestedType;
1890
+ if (typeof providerLoader.isMachineProviderEnabled === "function" && !providerLoader.isMachineProviderEnabled(normalizedType)) {
1891
+ failed.push(`${requestedType}: disabled`);
1892
+ continue;
1893
+ }
1894
+ let detected;
1895
+ try {
1896
+ detected = await detectCLI(normalizedType, providerLoader, { includeVersion: false });
1897
+ } catch (e) {
1898
+ failed.push(`${requestedType}: detect failed: ${e?.message || e}`);
1899
+ continue;
1900
+ }
1901
+ if (typeof providerLoader.setCliDetectionResults === "function") {
1902
+ providerLoader.setCliDetectionResults([{
1903
+ id: normalizedType,
1904
+ installed: !!detected,
1905
+ path: detected?.path
1906
+ }], false);
1907
+ }
1908
+ components.onStatusChange?.();
1909
+ if (detected) return { providerType: normalizedType };
1910
+ failed.push(`${requestedType}: not detected`);
1911
+ }
1912
+ return { reason: `provider_priority_unusable: ${failed.join("; ") || nodeId}` };
1913
+ }
1914
+ async function maybeAutoLaunchOneQueueSession(components, meshId, mesh) {
1915
+ const queue = getQueue(meshId);
1916
+ const pending = queue.filter((task) => task.status === "pending");
1917
+ if (!pending.length) return false;
1918
+ const maxParallelTasks = Math.max(1, Math.floor(Number(mesh?.policy?.maxParallelTasks) || 2));
1919
+ for (const task of pending) {
1920
+ if (activeAssignedCount(meshId) >= maxParallelTasks) {
1921
+ markAutoLaunch(meshId, task.id, { status: "skipped", reason: "max_parallel_tasks_reached" });
1922
+ return false;
1923
+ }
1924
+ if (task.targetSessionId) {
1925
+ markAutoLaunch(meshId, task.id, { status: "skipped", reason: "target_session_constraint" });
1926
+ continue;
1927
+ }
1928
+ const candidateNodes = Array.isArray(mesh?.nodes) ? mesh.nodes.filter((node) => task.targetNodeId ? node?.id === task.targetNodeId : true) : [];
1929
+ if (!candidateNodes.length) {
1930
+ markAutoLaunch(meshId, task.id, { status: "skipped", reason: "no_matching_node", nodeId: task.targetNodeId });
1931
+ continue;
1932
+ }
1933
+ for (const node of candidateNodes) {
1934
+ const nodeId = readNonEmptyString(node?.id);
1935
+ if (!nodeId) continue;
1936
+ const launchKey = `${meshId}:${nodeId}`;
1937
+ const cooldownUntil = autoLaunchCooldownUntil.get(launchKey) || 0;
1938
+ if (autoLaunchInProgress.has(launchKey)) {
1939
+ markAutoLaunch(meshId, task.id, { status: "skipped", reason: "auto_launch_in_progress", nodeId });
1940
+ continue;
1941
+ }
1942
+ if (Date.now() < cooldownUntil) {
1943
+ markAutoLaunch(meshId, task.id, { status: "skipped", reason: "auto_launch_cooldown", nodeId });
1944
+ continue;
1945
+ }
1946
+ if (isDirtyNode(node)) {
1947
+ markAutoLaunch(meshId, task.id, { status: "skipped", reason: "dirty_workspace", nodeId });
1948
+ continue;
1949
+ }
1950
+ if (!isLaunchableNode(node)) {
1951
+ markAutoLaunch(meshId, task.id, { status: "skipped", reason: "node_not_launch_ready", nodeId });
1952
+ continue;
1953
+ }
1954
+ const localSkipReason = localAutoLaunchSkipReason(node);
1955
+ if (localSkipReason) {
1956
+ markAutoLaunch(meshId, task.id, { status: "skipped", reason: localSkipReason, nodeId });
1957
+ continue;
1958
+ }
1959
+ if (nodeHasActiveAssignment(meshId, nodeId)) {
1960
+ markAutoLaunch(meshId, task.id, { status: "skipped", reason: "node_has_active_assignment", nodeId });
1961
+ continue;
1962
+ }
1963
+ const maxConcurrentSessions = Number(node?.policy?.maxConcurrentSessions);
1964
+ if (Number.isFinite(maxConcurrentSessions) && maxConcurrentSessions >= 0 && liveSessionCountForNode(components, meshId, nodeId) >= maxConcurrentSessions) {
1965
+ markAutoLaunch(meshId, task.id, { status: "skipped", reason: "max_concurrent_sessions_reached", nodeId });
1966
+ continue;
1967
+ }
1968
+ autoLaunchInProgress.add(launchKey);
1969
+ try {
1970
+ const resolved = await resolveUsableProvider(components, nodeId, node);
1971
+ if (!resolved.providerType) {
1972
+ markAutoLaunch(meshId, task.id, { status: "skipped", reason: resolved.reason || "provider_unusable", nodeId });
1973
+ continue;
1974
+ }
1975
+ markAutoLaunch(meshId, task.id, { status: "started", nodeId, providerType: resolved.providerType });
1976
+ const launchResult = await components.cliManager.handleCliCommand("launch_cli", {
1977
+ cliType: resolved.providerType,
1978
+ dir: node.workspace,
1979
+ settings: {
1980
+ meshNodeFor: meshId,
1981
+ meshNodeId: nodeId,
1982
+ spawnedSessionVisibility: mesh?.policy?.spawnedSessionVisibility || "hidden",
1983
+ launchedByCoordinator: true,
1984
+ autoLaunchedForQueueTaskId: task.id
1985
+ }
1986
+ });
1987
+ if (!launchResult?.success) {
1988
+ const reason = launchResult?.error || "launch_cli_failed";
1989
+ markAutoLaunch(meshId, task.id, { status: "failed", reason, nodeId, providerType: resolved.providerType });
1990
+ autoLaunchCooldownUntil.set(launchKey, Date.now() + AUTO_LAUNCH_COOLDOWN_MS);
1991
+ return false;
1992
+ }
1993
+ const sessionId = readNonEmptyString(launchResult.sessionId) || readNonEmptyString(launchResult.id) || readNonEmptyString(launchResult.runtimeSessionId);
1994
+ if (!sessionId) {
1995
+ markAutoLaunch(meshId, task.id, { status: "failed", reason: "launch_missing_session_id", nodeId, providerType: resolved.providerType });
1996
+ autoLaunchCooldownUntil.set(launchKey, Date.now() + AUTO_LAUNCH_COOLDOWN_MS);
1997
+ return false;
1998
+ }
1999
+ markAutoLaunch(meshId, task.id, { status: "completed", nodeId, providerType: resolved.providerType, sessionId });
2000
+ tryAssignQueueTask(components, meshId, nodeId, sessionId, resolved.providerType);
2001
+ return true;
2002
+ } catch (e) {
2003
+ markAutoLaunch(meshId, task.id, { status: "failed", error: e?.message || String(e), nodeId });
2004
+ autoLaunchCooldownUntil.set(launchKey, Date.now() + AUTO_LAUNCH_COOLDOWN_MS);
2005
+ return false;
2006
+ } finally {
2007
+ autoLaunchInProgress.delete(launchKey);
2008
+ }
2009
+ }
2010
+ }
2011
+ return false;
2012
+ }
2013
+ async function triggerMeshQueue(components, meshId) {
2014
+ const mesh = getMeshWithCache(components, meshId);
1334
2015
  if (!mesh) return;
1335
2016
  const cliInstances = components.instanceManager.getByCategory("cli");
1336
2017
  for (const inst of cliInstances) {
1337
2018
  const state = inst.getState();
1338
2019
  const settings = state.settings || {};
1339
2020
  const instMeshId = readNonEmptyString(settings.meshNodeFor);
1340
- if (instMeshId !== meshId && !settings.launchedByCoordinator) continue;
2021
+ if (instMeshId !== meshId) continue;
1341
2022
  const nodeId = readNonEmptyString(settings.meshNodeId) || readNonEmptyString(settings.nodeId);
1342
2023
  if (!nodeId) continue;
1343
- if (state.status !== "idle" && state.status !== "stopped" && state.activeChat?.status !== "waiting_input") continue;
2024
+ if (!isIdleSessionState(state)) continue;
1344
2025
  const sessionId = state.instanceId;
1345
2026
  const providerType = state.type || readNonEmptyString(settings.providerType);
1346
2027
  if (providerType) {
1347
2028
  tryAssignQueueTask(components, meshId, nodeId, sessionId, providerType);
1348
2029
  }
1349
2030
  }
2031
+ for (const [key, idle] of remoteIdleSessions.entries()) {
2032
+ const node = mesh.nodes.find((n) => n.id === idle.nodeId);
2033
+ if (node) {
2034
+ const assigned = tryAssignQueueTask(components, meshId, idle.nodeId, idle.sessionId, idle.providerType);
2035
+ if (assigned) {
2036
+ remoteIdleSessions.delete(key);
2037
+ }
2038
+ }
2039
+ }
2040
+ await maybeAutoLaunchOneQueueSession(components, meshId, mesh);
1350
2041
  }
1351
2042
  function buildMeshSystemMessage(args) {
1352
2043
  const metadata = formatCompletionMetadata(args.metadataEvent);
@@ -1393,20 +2084,91 @@ Do NOT retry on this node. Consider reassigning to a different node or asking th
1393
2084
  return "";
1394
2085
  }
1395
2086
  function injectMeshSystemMessage(components, args) {
2087
+ const eventSessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId);
2088
+ const eventNodeId = readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId);
2089
+ const intentionalCleanupStop = shouldSuppressIntentionalCleanupStop({
2090
+ event: args.event,
2091
+ meshId: args.meshId,
2092
+ metadataEvent: args.metadataEvent,
2093
+ sessionId: eventSessionId || void 0,
2094
+ nodeId: eventNodeId || void 0
2095
+ });
2096
+ if (intentionalCleanupStop) {
2097
+ if (eventSessionId && eventNodeId) {
2098
+ remoteIdleSessions.delete(`${eventNodeId}:${eventSessionId}`);
2099
+ }
2100
+ LOG.info("MeshEvents", `Suppressed ${args.event} for intentionally cleanup-stopped session ${eventSessionId || "(unknown session)"}`);
2101
+ return { success: true, forwarded: 0, suppressed: true, intentionalCleanupStop: true };
2102
+ }
2103
+ let completedTaskForLedger = null;
1396
2104
  if (args.event === "agent:generating_completed") {
1397
- const sessionId = readNonEmptyString(args.metadataEvent.targetSessionId);
1398
- const nodeId = readNonEmptyString(args.metadataEvent.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId);
2105
+ const sessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId);
2106
+ const nodeId = readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId);
1399
2107
  const providerType = readNonEmptyString(args.metadataEvent.providerType);
1400
2108
  if (sessionId) {
1401
- updateSessionTaskStatus(args.meshId, sessionId, "completed");
2109
+ const completedTask = updateSessionTaskStatus(args.meshId, sessionId, "completed");
2110
+ completedTaskForLedger = completedTask ? { id: completedTask.id } : null;
1402
2111
  if (nodeId && providerType) {
1403
2112
  setTimeout(() => {
1404
2113
  tryAssignQueueTask(components, args.meshId, nodeId, sessionId, providerType);
1405
2114
  }, 500);
1406
2115
  }
1407
2116
  }
2117
+ } else if (args.event === "agent:ready") {
2118
+ const sessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId);
2119
+ const nodeId = readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId);
2120
+ const providerType = readNonEmptyString(args.metadataEvent.providerType);
2121
+ const completedTask = sessionId ? updateSessionTaskStatus(args.meshId, sessionId, "completed") : null;
2122
+ if (completedTask) {
2123
+ completedTaskForLedger = { id: completedTask.id };
2124
+ try {
2125
+ appendLedgerEntry(args.meshId, {
2126
+ kind: "task_completed",
2127
+ nodeId: nodeId || void 0,
2128
+ sessionId,
2129
+ providerType: providerType || void 0,
2130
+ payload: {
2131
+ event: args.event,
2132
+ nodeLabel: args.nodeLabel,
2133
+ taskId: completedTask.id,
2134
+ completedViaReady: true,
2135
+ providerSessionId: readNonEmptyString(args.metadataEvent.providerSessionId) || void 0,
2136
+ finalSummary: readNonEmptyString(args.metadataEvent.finalSummary) || void 0,
2137
+ evidence: buildTaskCompletionEvidence({
2138
+ event: "agent:ready",
2139
+ nodeId,
2140
+ sessionId,
2141
+ providerType: providerType || void 0,
2142
+ providerSessionId: readNonEmptyString(args.metadataEvent.providerSessionId) || void 0,
2143
+ finalSummary: readNonEmptyString(args.metadataEvent.finalSummary) || void 0
2144
+ })
2145
+ }
2146
+ });
2147
+ } catch (e) {
2148
+ LOG.warn("MeshLedger", `Failed to record task_completed from ready: ${e?.message || e}`);
2149
+ }
2150
+ }
2151
+ if (sessionId && nodeId && providerType) {
2152
+ remoteIdleSessions.set(`${nodeId}:${sessionId}`, { nodeId, sessionId, providerType });
2153
+ setTimeout(() => {
2154
+ const assigned = tryAssignQueueTask(components, args.meshId, nodeId, sessionId, providerType);
2155
+ if (assigned) {
2156
+ remoteIdleSessions.delete(`${nodeId}:${sessionId}`);
2157
+ }
2158
+ }, 500);
2159
+ }
2160
+ } else if (args.event === "agent:generating_started") {
2161
+ const sessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId);
2162
+ const nodeId = readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId);
2163
+ if (sessionId && nodeId) {
2164
+ remoteIdleSessions.delete(`${nodeId}:${sessionId}`);
2165
+ }
1408
2166
  } else if (args.event === "agent:stopped") {
1409
- const sessionId = readNonEmptyString(args.metadataEvent.targetSessionId);
2167
+ const sessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId);
2168
+ const nodeId = readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId);
2169
+ if (sessionId && nodeId) {
2170
+ remoteIdleSessions.delete(`${nodeId}:${sessionId}`);
2171
+ }
1410
2172
  if (sessionId) {
1411
2173
  updateSessionTaskStatus(args.meshId, sessionId, "failed");
1412
2174
  }
@@ -1414,15 +2176,29 @@ function injectMeshSystemMessage(components, args) {
1414
2176
  const ledgerKind = EVENT_TO_LEDGER_KIND[args.event];
1415
2177
  if (ledgerKind) {
1416
2178
  try {
2179
+ const ledgerNodeId = readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId) || void 0;
2180
+ const ledgerSessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId) || void 0;
2181
+ const ledgerProviderType = readNonEmptyString(args.metadataEvent.providerType) || void 0;
2182
+ const completionEvidence = ledgerKind === "task_completed" && ledgerNodeId && ledgerSessionId ? buildTaskCompletionEvidence({
2183
+ event: "agent:generating_completed",
2184
+ nodeId: ledgerNodeId,
2185
+ sessionId: ledgerSessionId,
2186
+ providerType: ledgerProviderType,
2187
+ providerSessionId: readNonEmptyString(args.metadataEvent.providerSessionId) || void 0,
2188
+ finalSummary: readNonEmptyString(args.metadataEvent.finalSummary) || void 0
2189
+ }) : void 0;
1417
2190
  appendLedgerEntry(args.meshId, {
1418
2191
  kind: ledgerKind,
1419
- nodeId: readNonEmptyString(args.metadataEvent.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId) || void 0,
1420
- sessionId: readNonEmptyString(args.metadataEvent.targetSessionId) || void 0,
1421
- providerType: readNonEmptyString(args.metadataEvent.providerType) || void 0,
2192
+ nodeId: ledgerNodeId,
2193
+ sessionId: ledgerSessionId,
2194
+ providerType: ledgerProviderType,
1422
2195
  payload: {
1423
2196
  event: args.event,
1424
2197
  nodeLabel: args.nodeLabel,
1425
- providerSessionId: readNonEmptyString(args.metadataEvent.providerSessionId) || void 0
2198
+ taskId: completedTaskForLedger?.id || void 0,
2199
+ providerSessionId: readNonEmptyString(args.metadataEvent.providerSessionId) || void 0,
2200
+ finalSummary: readNonEmptyString(args.metadataEvent.finalSummary) || void 0,
2201
+ evidence: completionEvidence
1426
2202
  }
1427
2203
  });
1428
2204
  } catch (e) {
@@ -1435,8 +2211,8 @@ function injectMeshSystemMessage(components, args) {
1435
2211
  const mesh = getMesh(args.meshId);
1436
2212
  const maxRetries = mesh?.policy?.maxTaskRetries ?? 1;
1437
2213
  recoveryContext = getSessionRecoveryContext(args.meshId, {
1438
- sessionId: readNonEmptyString(args.metadataEvent.targetSessionId) || void 0,
1439
- nodeId: readNonEmptyString(args.metadataEvent.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId) || void 0,
2214
+ sessionId: resolveEventSessionId(args.metadataEvent, args.sourceInstanceId) || void 0,
2215
+ nodeId: readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId) || void 0,
1440
2216
  maxRetries
1441
2217
  });
1442
2218
  recoveryContext.failedProviderType = readNonEmptyString(args.metadataEvent.providerType) || null;
@@ -1531,12 +2307,21 @@ function handleMeshForwardEvent(components, payload) {
1531
2307
  const nodeLabel = nodeId ? `Node '${nodeId}'` : workspace ? `Agent at ${workspace}` : "Remote agent";
1532
2308
  return injectMeshSystemMessage(components, {
1533
2309
  meshId,
2310
+ nodeId,
1534
2311
  nodeLabel,
1535
2312
  event: eventName,
1536
2313
  metadataEvent: {
1537
- targetSessionId: readNonEmptyString(payload.targetSessionId) || readNonEmptyString(payload.sessionId),
2314
+ targetSessionId: readNonEmptyString(payload.targetSessionId) || readNonEmptyString(payload.sessionId) || readNonEmptyString(payload.instanceId),
1538
2315
  providerType: readNonEmptyString(payload.providerType),
1539
- providerSessionId: readNonEmptyString(payload.providerSessionId)
2316
+ providerSessionId: readNonEmptyString(payload.providerSessionId),
2317
+ finalSummary: readNonEmptyString(payload.finalSummary) || readNonEmptyString(payload.summary),
2318
+ intentional: payload.intentional === true,
2319
+ intentionalStop: payload.intentionalStop === true,
2320
+ operatorCleanup: payload.operatorCleanup === true,
2321
+ reason: readNonEmptyString(payload.reason),
2322
+ stopReason: readNonEmptyString(payload.stopReason),
2323
+ cleanupReason: readNonEmptyString(payload.cleanupReason),
2324
+ source: readNonEmptyString(payload.source)
1540
2325
  }
1541
2326
  });
1542
2327
  }
@@ -1555,35 +2340,42 @@ function setupMeshEventForwarding(components) {
1555
2340
  const meshIdFromRuntime = readNonEmptyString(settings.meshNodeFor);
1556
2341
  const isMeshDelegate = Boolean(meshIdFromRuntime || settings.launchedByCoordinator);
1557
2342
  if (!isMeshDelegate) return;
1558
- const mesh = meshIdFromRuntime ? getMesh(meshIdFromRuntime) : getMeshByRepo(workspace);
2343
+ const mesh = meshIdFromRuntime ? getMeshWithCache(components, meshIdFromRuntime) : getMeshByRepo(workspace);
1559
2344
  const meshId = meshIdFromRuntime || readNonEmptyString(mesh?.id);
1560
2345
  if (!meshId) return;
1561
2346
  const targetNode = mesh?.nodes?.find((n) => n.workspace === workspace);
1562
2347
  const runtimeNodeId = readNonEmptyString(settings.meshNodeId);
2348
+ const resolvedNodeId = targetNode?.id || runtimeNodeId;
1563
2349
  const nodeLabel = targetNode ? `Node '${targetNode.id}'` : runtimeNodeId ? `Node '${runtimeNodeId}'` : `Agent at ${workspace}`;
1564
2350
  injectMeshSystemMessage(components, {
1565
2351
  meshId,
1566
2352
  sourceInstanceId: instanceId,
2353
+ nodeId: resolvedNodeId,
1567
2354
  nodeLabel,
1568
2355
  event: event.event,
1569
2356
  metadataEvent: event
1570
2357
  });
1571
2358
  });
1572
2359
  }
1573
- var MAX_PENDING_EVENTS, pendingMeshCoordinatorEvents, MESH_COORDINATOR_EVENTS, EVENT_TO_LEDGER_KIND;
2360
+ var remoteIdleSessions, MAX_PENDING_EVENTS, pendingMeshCoordinatorEvents, MESH_COORDINATOR_EVENTS, EVENT_TO_LEDGER_KIND, INTENTIONAL_CLEANUP_STOP_SUPPRESSION_MS, autoLaunchInProgress, autoLaunchCooldownUntil, AUTO_LAUNCH_COOLDOWN_MS;
1574
2361
  var init_mesh_events = __esm({
1575
2362
  "src/mesh/mesh-events.ts"() {
1576
2363
  "use strict";
2364
+ init_config();
1577
2365
  init_mesh_config();
2366
+ init_cli_detector();
1578
2367
  init_logger();
1579
2368
  init_mesh_ledger();
1580
2369
  init_mesh_work_queue();
2370
+ remoteIdleSessions = /* @__PURE__ */ new Map();
1581
2371
  MAX_PENDING_EVENTS = 50;
1582
2372
  pendingMeshCoordinatorEvents = [];
1583
2373
  MESH_COORDINATOR_EVENTS = /* @__PURE__ */ new Set([
2374
+ "agent:generating_started",
1584
2375
  "agent:generating_completed",
1585
2376
  "agent:waiting_approval",
1586
2377
  "agent:stopped",
2378
+ "agent:ready",
1587
2379
  "monitor:long_generating"
1588
2380
  ]);
1589
2381
  EVENT_TO_LEDGER_KIND = {
@@ -1592,6 +2384,10 @@ var init_mesh_events = __esm({
1592
2384
  "agent:stopped": "task_failed",
1593
2385
  "monitor:long_generating": "task_stalled"
1594
2386
  };
2387
+ INTENTIONAL_CLEANUP_STOP_SUPPRESSION_MS = 30 * 60 * 1e3;
2388
+ autoLaunchInProgress = /* @__PURE__ */ new Set();
2389
+ autoLaunchCooldownUntil = /* @__PURE__ */ new Map();
2390
+ AUTO_LAUNCH_COOLDOWN_MS = 5e3;
1595
2391
  }
1596
2392
  });
1597
2393
 
@@ -2617,6 +3413,7 @@ var init_provider_cli_adapter = __esm({
2617
3413
  this.requirePromptEchoBeforeSubmit = resolvedConfig.requirePromptEchoBeforeSubmit;
2618
3414
  this.providerResolutionMeta = resolvedConfig.providerResolutionMeta;
2619
3415
  this.cliScripts = provider.scripts || {};
3416
+ this.scriptState = typeof this.cliScripts.createState === "function" ? this.cliScripts.createState() ?? null : null;
2620
3417
  const scriptNames = listCliScriptNames(this.cliScripts);
2621
3418
  if (scriptNames.length > 0) {
2622
3419
  LOG.info("CLI", `[${this.cliType}] CLI scripts: [${scriptNames.join(", ")}]`);
@@ -2699,6 +3496,8 @@ var init_provider_cli_adapter = __esm({
2699
3496
  statusHistory = [];
2700
3497
  // ─── CLI Scripts (script-based parsing) ───
2701
3498
  cliScripts;
3499
+ /** Per-session opaque state object created by cliScripts.createState(), reset on stop. */
3500
+ scriptState = null;
2702
3501
  runtimeSettings = {};
2703
3502
  /** Full accumulated rendered PTY transcript for parser/readback use */
2704
3503
  accumulatedBuffer = "";
@@ -2775,9 +3574,13 @@ ${lastSnapshot}`;
2775
3574
  this.lastScreenChangeAt = 0;
2776
3575
  this.lastScreenSnapshotReadAt = Number.NEGATIVE_INFINITY;
2777
3576
  }
3577
+ getAccumulatedRawBufferCacheKey() {
3578
+ return this.accumulatedRawBuffer.replace(/\x1B\][^\x07]*(?:\x07|\x1B\\)/g, "").replace(/\x1B[P^_X][\s\S]*?(?:\x07|\x1B\\)/g, "").replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, "");
3579
+ }
2778
3580
  getFreshParsedStatusCache() {
2779
3581
  const cached = this.parsedStatusCache;
2780
- if (cached && cached.responseBuffer === this.responseBuffer && cached.currentTurnScope === this.currentTurnScope && cached.recentOutputBuffer === this.recentOutputBuffer && cached.accumulatedBuffer === this.accumulatedBuffer && cached.screenText === this.lastScreenText && cached.currentStatus === this.currentStatus && cached.activeModal === this.activeModal && cached.cliName === this.cliName) {
3582
+ const accumulatedRawBufferKey = this.getAccumulatedRawBufferCacheKey();
3583
+ if (cached && cached.responseBuffer === this.responseBuffer && cached.currentTurnScope === this.currentTurnScope && cached.recentOutputBuffer === this.recentOutputBuffer && cached.accumulatedBuffer === this.accumulatedBuffer && cached.accumulatedRawBufferKey === accumulatedRawBufferKey && cached.screenText === this.lastScreenText && cached.currentStatus === this.currentStatus && cached.activeModal === this.activeModal && cached.cliName === this.cliName) {
2781
3584
  return cached.result;
2782
3585
  }
2783
3586
  return null;
@@ -2880,6 +3683,7 @@ ${lastSnapshot}`;
2880
3683
  this.cliScripts = scripts;
2881
3684
  this.parsedStatusCache = null;
2882
3685
  this.parseErrorMessage = null;
3686
+ this.scriptState = typeof scripts.createState === "function" ? scripts.createState() ?? null : null;
2883
3687
  const scriptNames = listCliScriptNames(scripts);
2884
3688
  LOG.info("CLI", `[${this.cliType}] CLI scripts injected: [${scriptNames.join(", ")}]`);
2885
3689
  }
@@ -2997,6 +3801,7 @@ ${lastSnapshot}`;
2997
3801
  this.ready = false;
2998
3802
  this.startupParseGate = false;
2999
3803
  this.spawnAt = 0;
3804
+ this.scriptState = null;
3000
3805
  this.onStatusChange?.();
3001
3806
  });
3002
3807
  this.spawnAt = Date.now();
@@ -3750,6 +4555,11 @@ ${lastSnapshot}`;
3750
4555
  };
3751
4556
  }
3752
4557
  // ─── Script Execution ──────────────────────────
4558
+ invokeCliScript(script, input) {
4559
+ const hasStateFactory = typeof this.cliScripts?.createState === "function";
4560
+ const expectsStateArgument = hasStateFactory || this.scriptState !== null || script.length >= 2;
4561
+ return expectsStateArgument ? script(this.scriptState, input) : script(input);
4562
+ }
3753
4563
  runParseSession() {
3754
4564
  if (typeof this.cliScripts?.parseSession !== "function") {
3755
4565
  this.parseErrorMessage = `${this.cliType} parseSession unavailable`;
@@ -3770,7 +4580,10 @@ ${lastSnapshot}`;
3770
4580
  scope: this.currentTurnScope,
3771
4581
  runtimeSettings: this.runtimeSettings
3772
4582
  });
3773
- const session = this.cliScripts.parseSession({ ...input, tail, tailScreen: buildCliScreenSnapshot(tail) });
4583
+ const session = this.invokeCliScript(
4584
+ this.cliScripts.parseSession,
4585
+ { ...input, tail, tailScreen: buildCliScreenSnapshot(tail) }
4586
+ );
3774
4587
  this.parseErrorMessage = null;
3775
4588
  return session && typeof session === "object" ? session : null;
3776
4589
  } catch (e) {
@@ -3784,7 +4597,7 @@ ${lastSnapshot}`;
3784
4597
  if (!this.cliScripts?.detectStatus) return null;
3785
4598
  try {
3786
4599
  const screenText = this.terminalScreen.getText();
3787
- const status = this.cliScripts.detectStatus({
4600
+ const status = this.invokeCliScript(this.cliScripts.detectStatus, {
3788
4601
  tail: text.slice(-500),
3789
4602
  screenText,
3790
4603
  rawBuffer: this.accumulatedRawBuffer,
@@ -3803,7 +4616,7 @@ ${lastSnapshot}`;
3803
4616
  try {
3804
4617
  const screenText = this.terminalScreen.getText();
3805
4618
  const buffer = screenText || this.accumulatedBuffer;
3806
- return this.cliScripts.parseApproval({
4619
+ return this.invokeCliScript(this.cliScripts.parseApproval, {
3807
4620
  buffer,
3808
4621
  screenText,
3809
4622
  rawBuffer: this.accumulatedRawBuffer,
@@ -3859,7 +4672,8 @@ ${lastSnapshot}`;
3859
4672
  const screenText = this.readTerminalScreenText();
3860
4673
  const parseScreenText = this.getParseScreenText(screenText);
3861
4674
  const cached = this.parsedStatusCache;
3862
- if (cached && cached.responseBuffer === this.responseBuffer && cached.currentTurnScope === this.currentTurnScope && cached.recentOutputBuffer === this.recentOutputBuffer && cached.accumulatedBuffer === this.accumulatedBuffer && cached.screenText === parseScreenText && cached.currentStatus === this.currentStatus && cached.activeModal === this.activeModal && cached.cliName === this.cliName) {
4675
+ const accumulatedRawBufferKey = this.getAccumulatedRawBufferCacheKey();
4676
+ if (cached && cached.responseBuffer === this.responseBuffer && cached.currentTurnScope === this.currentTurnScope && cached.recentOutputBuffer === this.recentOutputBuffer && cached.accumulatedBuffer === this.accumulatedBuffer && cached.accumulatedRawBufferKey === accumulatedRawBufferKey && cached.screenText === parseScreenText && cached.currentStatus === this.currentStatus && cached.activeModal === this.activeModal && cached.cliName === this.cliName) {
3863
4677
  return cached.result;
3864
4678
  }
3865
4679
  const parsed = this.runParseSession();
@@ -3887,6 +4701,7 @@ ${lastSnapshot}`;
3887
4701
  currentTurnScope: this.currentTurnScope,
3888
4702
  recentOutputBuffer: this.recentOutputBuffer,
3889
4703
  accumulatedBuffer: this.accumulatedBuffer,
4704
+ accumulatedRawBufferKey,
3890
4705
  screenText: parseScreenText,
3891
4706
  currentStatus: this.currentStatus,
3892
4707
  activeModal: this.activeModal,
@@ -3911,7 +4726,7 @@ ${lastSnapshot}`;
3911
4726
  scope: this.currentTurnScope,
3912
4727
  runtimeSettings: this.runtimeSettings
3913
4728
  });
3914
- return await Promise.resolve(fn({
4729
+ return await Promise.resolve(this.invokeCliScript(fn, {
3915
4730
  ...input,
3916
4731
  args: args && typeof args === "object" ? { ...args } : {}
3917
4732
  }));
@@ -4692,10 +5507,12 @@ __export(index_exports, {
4692
5507
  IdeProviderInstance: () => IdeProviderInstance,
4693
5508
  InMemoryGitSnapshotStore: () => InMemoryGitSnapshotStore,
4694
5509
  LOG: () => LOG,
5510
+ MAX_LEDGER_SLICE_LIMIT: () => MAX_LEDGER_SLICE_LIMIT,
4695
5511
  MIN_GIT_WORKSPACE_POLL_INTERVAL_MS: () => MIN_GIT_WORKSPACE_POLL_INTERVAL_MS,
4696
5512
  MIN_MACHINE_RUNTIME_SUBSCRIPTION_INTERVAL_MS: () => MIN_MACHINE_RUNTIME_SUBSCRIPTION_INTERVAL_MS,
4697
5513
  MIN_SESSION_HOST_DIAGNOSTICS_SUBSCRIPTION_INTERVAL_MS: () => MIN_SESSION_HOST_DIAGNOSTICS_SUBSCRIPTION_INTERVAL_MS,
4698
5514
  NodePtyTransportFactory: () => NodePtyTransportFactory,
5515
+ P2pRelayFailureError: () => P2pRelayFailureError,
4699
5516
  ProviderCliAdapter: () => ProviderCliAdapter,
4700
5517
  ProviderInstanceManager: () => ProviderInstanceManager,
4701
5518
  ProviderLoader: () => ProviderLoader,
@@ -4706,12 +5523,16 @@ __export(index_exports, {
4706
5523
  addNode: () => addNode,
4707
5524
  appendLedgerEntry: () => appendLedgerEntry,
4708
5525
  appendRecentActivity: () => appendRecentActivity,
5526
+ appendRemoteLedgerEntries: () => appendRemoteLedgerEntries,
4709
5527
  buildAssistantChatMessage: () => buildAssistantChatMessage,
4710
5528
  buildChatMessage: () => buildChatMessage,
4711
5529
  buildChatMessageSignature: () => buildChatMessageSignature,
4712
5530
  buildChatTailDeliverySignature: () => buildChatTailDeliverySignature,
4713
5531
  buildCoordinatorSystemPrompt: () => buildCoordinatorSystemPrompt,
4714
5532
  buildMachineInfo: () => buildMachineInfo,
5533
+ buildMeshLedgerReconciliationEvidence: () => buildMeshLedgerReconciliationEvidence,
5534
+ buildMeshLedgerReplicaEvidence: () => buildMeshLedgerReplicaEvidence,
5535
+ buildP2pRelayFailurePayload: () => buildP2pRelayFailurePayload,
4715
5536
  buildPinnedGlobalInstallCommand: () => buildPinnedGlobalInstallCommand,
4716
5537
  buildRuntimeSystemChatMessage: () => buildRuntimeSystemChatMessage,
4717
5538
  buildSessionEntries: () => buildSessionEntries,
@@ -4722,10 +5543,13 @@ __export(index_exports, {
4722
5543
  buildThoughtChatMessage: () => buildThoughtChatMessage,
4723
5544
  buildToolChatMessage: () => buildToolChatMessage,
4724
5545
  buildUserChatMessage: () => buildUserChatMessage,
5546
+ cancelTask: () => cancelTask,
4725
5547
  claimNextTask: () => claimNextTask,
4726
5548
  classifyChatMessageVisibility: () => classifyChatMessageVisibility,
4727
5549
  classifyHotChatSessionsForSubscriptionFlush: () => classifyHotChatSessionsForSubscriptionFlush,
5550
+ classifyP2pRelayFailure: () => classifyP2pRelayFailure,
4728
5551
  clearDebugTrace: () => clearDebugTrace,
5552
+ clearPendingMeshCoordinatorEvents: () => clearPendingMeshCoordinatorEvents,
4729
5553
  compareGitSnapshots: () => compareGitSnapshots,
4730
5554
  configureDebugTraceStore: () => configureDebugTraceStore,
4731
5555
  connectCdpManager: () => connectCdpManager,
@@ -4741,6 +5565,7 @@ __export(index_exports, {
4741
5565
  detectAllVersions: () => detectAllVersions,
4742
5566
  detectCLIs: () => detectCLIs,
4743
5567
  detectIDEs: () => detectIDEs,
5568
+ drainPendingMeshCoordinatorEvents: () => drainPendingMeshCoordinatorEvents,
4744
5569
  enqueueTask: () => enqueueTask,
4745
5570
  ensureSessionHostReady: () => ensureSessionHostReady,
4746
5571
  execNpmCommandSync: () => execNpmCommandSync,
@@ -4765,7 +5590,9 @@ __export(index_exports, {
4765
5590
  getLogLevel: () => getLogLevel,
4766
5591
  getMesh: () => getMesh,
4767
5592
  getMeshByRepo: () => getMeshByRepo,
5593
+ getMeshQueueStats: () => getMeshQueueStats,
4768
5594
  getNpmExecOptions: () => getNpmExecOptions,
5595
+ getPendingMeshCoordinatorEvents: () => getPendingMeshCoordinatorEvents,
4769
5596
  getQueue: () => getQueue,
4770
5597
  getRecentActivity: () => getRecentActivity,
4771
5598
  getRecentCommands: () => getRecentCommands,
@@ -4791,6 +5618,7 @@ __export(index_exports, {
4791
5618
  isInternalChatMessage: () => isInternalChatMessage,
4792
5619
  isManagedStatusWaiting: () => isManagedStatusWaiting,
4793
5620
  isManagedStatusWorking: () => isManagedStatusWorking,
5621
+ isP2pRelayTransportFailure: () => isP2pRelayTransportFailure,
4794
5622
  isPathInside: () => isPathInside,
4795
5623
  isSessionHostLiveRuntime: () => isSessionHostLiveRuntime,
4796
5624
  isSessionHostRecoverySnapshot: () => isSessionHostRecoverySnapshot,
@@ -4829,10 +5657,12 @@ __export(index_exports, {
4829
5657
  probeCdpPort: () => probeCdpPort,
4830
5658
  readChatHistory: () => readChatHistory,
4831
5659
  readLedgerEntries: () => readLedgerEntries,
5660
+ readLedgerSlice: () => readLedgerSlice,
4832
5661
  recordDebugTrace: () => recordDebugTrace,
4833
5662
  registerExtensionProviders: () => registerExtensionProviders,
4834
5663
  removeNode: () => removeNode,
4835
5664
  removeWorktree: () => removeWorktree,
5665
+ requeueTask: () => requeueTask,
4836
5666
  resetConfig: () => resetConfig,
4837
5667
  resetDebugRuntimeConfig: () => resetDebugRuntimeConfig,
4838
5668
  resetState: () => resetState,
@@ -6346,7 +7176,7 @@ function addWorkspaceEntry(config, rawPath, label, options) {
6346
7176
  }
6347
7177
  }
6348
7178
  const v = validateWorkspacePath(abs);
6349
- if (!v.ok) return { error: v.error };
7179
+ if (v.ok !== true) return { error: v.error };
6350
7180
  const list = [...config.workspaces || []];
6351
7181
  if (list.some((w) => path5.resolve(w.path) === abs)) {
6352
7182
  return { error: "Workspace already in list" };
@@ -6729,34 +7559,186 @@ async function syncMeshes(transport) {
6729
7559
  }
6730
7560
  }
6731
7561
  }
6732
- if (transport.syncMeshLedger) {
6733
- for (const local of localMeshes) {
6734
- try {
6735
- await syncMeshLedger(local.id, transport);
6736
- } catch (e) {
6737
- result.errors.push(`Ledger sync failed for "${local.name}": ${e.message}`);
6738
- }
6739
- }
6740
- }
6741
7562
  return result;
6742
7563
  }
6743
- async function syncMeshLedger(meshId, transport) {
6744
- if (!transport.syncMeshLedger) return;
6745
- const { readLedgerEntries: readLedgerEntries2, appendRemoteLedgerEntries: appendRemoteLedgerEntries2 } = await Promise.resolve().then(() => (init_mesh_ledger(), mesh_ledger_exports));
6746
- const localEntries = readLedgerEntries2(meshId);
6747
- const res = await transport.syncMeshLedger(meshId, { newEntries: localEntries });
6748
- if (res.missingEntries && res.missingEntries.length > 0) {
6749
- appendRemoteLedgerEntries2(meshId, res.missingEntries);
6750
- }
6751
- }
6752
7564
 
6753
7565
  // src/index.ts
6754
7566
  init_mesh_ledger();
7567
+
7568
+ // src/mesh/mesh-ledger-reconciliation.ts
7569
+ function lastTimestamp(slice) {
7570
+ const entries = Array.isArray(slice?.entries) ? slice.entries : [];
7571
+ return entries.length ? entries[entries.length - 1].timestamp : null;
7572
+ }
7573
+ function buildMeshLedgerReplicaEvidence(args) {
7574
+ const entriesReceived = Array.isArray(args.slice?.entries) ? args.slice.entries.length : 0;
7575
+ return {
7576
+ nodeId: args.nodeId,
7577
+ ...args.daemonId ? { daemonId: args.daemonId } : {},
7578
+ status: args.status ?? (args.importResult && args.importResult.accepted > 0 ? "imported" : "queried"),
7579
+ transport: args.transport,
7580
+ protocol: "adhdev.mesh.ledger.slice.v1",
7581
+ entriesReceived,
7582
+ entriesImported: args.importResult?.accepted ?? 0,
7583
+ skippedDuplicate: args.importResult?.skippedDuplicate ?? 0,
7584
+ rejectedInvalid: args.importResult?.rejectedInvalid ?? 0,
7585
+ hasMore: args.slice?.cursor?.hasMore === true,
7586
+ nextAfterId: args.slice?.cursor?.nextAfterId ?? null,
7587
+ lastTimestamp: lastTimestamp(args.slice),
7588
+ ...args.slice?.summary ? { summary: args.slice.summary } : {},
7589
+ ...args.error ? {
7590
+ error: args.error,
7591
+ noFallbackReason: "Ledger reconciliation is P2P/local-first only; Cloud/D1 ledger sync is intentionally disabled."
7592
+ } : {}
7593
+ };
7594
+ }
7595
+ function buildMeshLedgerReconciliationEvidence(meshId, replicas) {
7596
+ const failedNodes = replicas.filter((replica) => replica.status === "failed").map((replica) => replica.nodeId);
7597
+ const pendingNodes = replicas.filter((replica) => replica.hasMore && replica.status !== "failed").map((replica) => replica.nodeId);
7598
+ return {
7599
+ protocol: "adhdev.mesh.ledger.reconciliation.v1",
7600
+ meshId,
7601
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
7602
+ sourceOfTruth: {
7603
+ kind: "coordinator_local_jsonl",
7604
+ p2pOnly: true,
7605
+ cloudD1LedgerSync: false,
7606
+ notes: "Coordinator reconciles bounded slices from daemon-local JSONL ledgers over P2P DataChannel; Cloud/D1 is not a ledger source of truth."
7607
+ },
7608
+ replicas,
7609
+ totals: {
7610
+ replicas: replicas.length,
7611
+ queried: replicas.filter((replica) => replica.status !== "failed").length,
7612
+ failed: failedNodes.length,
7613
+ entriesReceived: replicas.reduce((sum, replica) => sum + replica.entriesReceived, 0),
7614
+ entriesImported: replicas.reduce((sum, replica) => sum + replica.entriesImported, 0),
7615
+ skippedDuplicate: replicas.reduce((sum, replica) => sum + replica.skippedDuplicate, 0),
7616
+ rejectedInvalid: replicas.reduce((sum, replica) => sum + replica.rejectedInvalid, 0)
7617
+ },
7618
+ convergence: {
7619
+ complete: failedNodes.length === 0 && pendingNodes.length === 0,
7620
+ pendingNodes,
7621
+ failedNodes
7622
+ }
7623
+ };
7624
+ }
7625
+
7626
+ // src/index.ts
6755
7627
  init_mesh_work_queue();
6756
7628
  init_mesh_events();
6757
7629
 
7630
+ // src/mesh/p2p-relay-failure.ts
7631
+ var NO_FALLBACK_REASON = "Repo Mesh command/data-plane is P2P-only; WS/REST command fallback is intentionally disabled to preserve the transport boundary.";
7632
+ var P2P_NEXT_ACTION = "Check daemon/P2P health, wait briefly for connection establishment, then do one bounded retry or requeue the mesh task after clearing stale target session metadata.";
7633
+ var NON_P2P_NEXT_ACTION = "Inspect the provider/command error and fix the underlying logic or configuration before retrying.";
7634
+ function messageFromError(error) {
7635
+ if (error instanceof Error) return error.message;
7636
+ if (typeof error === "string") return error;
7637
+ if (error && typeof error === "object") {
7638
+ const candidate = error.error ?? error.message ?? error.reason;
7639
+ if (typeof candidate === "string") return candidate;
7640
+ }
7641
+ return String(error || "mesh relay command failed");
7642
+ }
7643
+ function classifyP2pRelayFailure(error, _context = {}) {
7644
+ const message = messageFromError(error);
7645
+ const lower = message.toLowerCase();
7646
+ const hasP2pSignal = /p2p|datachannel|node-datachannel|webrtc|ice|mesh_relay_command|daemon_mesh_p2p_transport/i.test(message);
7647
+ const hasFailureSignal = /unavailable|missing|failed|failure|timeout|timed out|not connected|closed|disconnected|offline|no route|route unavailable|cannot send|cannot establish/i.test(message);
7648
+ if (/requires targetdaemonid and command|providerpriority|no inference provider|permission denied|read-only|not a member/i.test(message)) {
7649
+ return {
7650
+ code: "mesh_logic_or_provider_failure",
7651
+ reason: "mesh_logic_or_provider_failure",
7652
+ transport: "unknown",
7653
+ recoverable: false,
7654
+ retryRecommended: false,
7655
+ nextAction: NON_P2P_NEXT_ACTION,
7656
+ noFallbackReason: NO_FALLBACK_REASON
7657
+ };
7658
+ }
7659
+ let code = null;
7660
+ let reason = "";
7661
+ if (/timeout|timed out/i.test(message) && (hasP2pSignal || /mesh transport/i.test(message))) {
7662
+ code = "p2p_timeout";
7663
+ reason = "daemon_mesh_p2p_timeout";
7664
+ } else if (/no route|route unavailable/i.test(message)) {
7665
+ code = "p2p_no_route";
7666
+ reason = "daemon_mesh_p2p_no_route";
7667
+ } else if (/offline|not owned|not found|not connected to server/i.test(message) && /daemon|peer|target/i.test(message)) {
7668
+ code = "p2p_daemon_offline";
7669
+ reason = "daemon_mesh_target_offline";
7670
+ } else if (/closed|disconnected/i.test(message) && (hasP2pSignal || /state changed/i.test(message))) {
7671
+ code = "p2p_datachannel_closed";
7672
+ reason = "daemon_mesh_p2p_datachannel_closed";
7673
+ } else if (/not connected|cannot send|cannot establish/i.test(message) && hasP2pSignal) {
7674
+ code = "p2p_not_connected";
7675
+ reason = "daemon_mesh_p2p_not_connected";
7676
+ } else if (hasP2pSignal && hasFailureSignal) {
7677
+ code = "p2p_unavailable";
7678
+ reason = "daemon_mesh_p2p_transport_unavailable";
7679
+ }
7680
+ if (!code) {
7681
+ return {
7682
+ code: "mesh_logic_or_provider_failure",
7683
+ reason: "mesh_logic_or_provider_failure",
7684
+ transport: "unknown",
7685
+ recoverable: false,
7686
+ retryRecommended: false,
7687
+ nextAction: NON_P2P_NEXT_ACTION,
7688
+ noFallbackReason: NO_FALLBACK_REASON
7689
+ };
7690
+ }
7691
+ return {
7692
+ code,
7693
+ reason,
7694
+ transport: "p2p",
7695
+ recoverable: true,
7696
+ retryRecommended: true,
7697
+ nextAction: P2P_NEXT_ACTION,
7698
+ noFallbackReason: NO_FALLBACK_REASON
7699
+ };
7700
+ }
7701
+ function isP2pRelayTransportFailure(error) {
7702
+ return classifyP2pRelayFailure(error).recoverable === true;
7703
+ }
7704
+ function buildP2pRelayFailurePayload(error, context = {}) {
7705
+ const classification = classifyP2pRelayFailure(error, context);
7706
+ return {
7707
+ success: false,
7708
+ ...classification,
7709
+ error: messageFromError(error),
7710
+ ...context.command ? { command: context.command } : {},
7711
+ ...context.targetDaemonId ? { targetDaemonId: context.targetDaemonId } : {}
7712
+ };
7713
+ }
7714
+ var P2pRelayFailureError = class extends Error {
7715
+ code;
7716
+ reason;
7717
+ transport;
7718
+ recoverable;
7719
+ retryRecommended;
7720
+ nextAction;
7721
+ noFallbackReason;
7722
+ command;
7723
+ targetDaemonId;
7724
+ constructor(message, context = {}) {
7725
+ super(message);
7726
+ this.name = "P2pRelayFailureError";
7727
+ const payload = buildP2pRelayFailurePayload(message, context);
7728
+ this.code = payload.code;
7729
+ this.reason = payload.reason;
7730
+ this.transport = payload.transport;
7731
+ this.recoverable = payload.recoverable;
7732
+ this.retryRecommended = payload.retryRecommended;
7733
+ this.nextAction = payload.nextAction;
7734
+ this.noFallbackReason = payload.noFallbackReason;
7735
+ this.command = context.command;
7736
+ this.targetDaemonId = context.targetDaemonId;
7737
+ }
7738
+ };
7739
+
6758
7740
  // src/config/state-store.ts
6759
- var import_fs5 = require("fs");
7741
+ var import_fs6 = require("fs");
6760
7742
  var import_path5 = require("path");
6761
7743
  init_config();
6762
7744
  var DEFAULT_STATE = {
@@ -6807,11 +7789,11 @@ function normalizeState(raw) {
6807
7789
  }
6808
7790
  function loadState() {
6809
7791
  const statePath = getStatePath();
6810
- if (!(0, import_fs5.existsSync)(statePath)) {
7792
+ if (!(0, import_fs6.existsSync)(statePath)) {
6811
7793
  return { ...DEFAULT_STATE };
6812
7794
  }
6813
7795
  try {
6814
- const raw = (0, import_fs5.readFileSync)(statePath, "utf-8");
7796
+ const raw = (0, import_fs6.readFileSync)(statePath, "utf-8");
6815
7797
  return normalizeState(JSON.parse(raw));
6816
7798
  } catch {
6817
7799
  return { ...DEFAULT_STATE };
@@ -6820,17 +7802,17 @@ function loadState() {
6820
7802
  function saveState(state) {
6821
7803
  const statePath = getStatePath();
6822
7804
  const normalized = normalizeState(state);
6823
- (0, import_fs5.writeFileSync)(statePath, JSON.stringify(normalized, null, 2), { encoding: "utf-8", mode: 384 });
7805
+ (0, import_fs6.writeFileSync)(statePath, JSON.stringify(normalized, null, 2), { encoding: "utf-8", mode: 384 });
6824
7806
  }
6825
7807
  function resetState() {
6826
7808
  saveState({ ...DEFAULT_STATE });
6827
7809
  }
6828
7810
 
6829
7811
  // src/detection/ide-detector.ts
6830
- var import_child_process = require("child_process");
6831
- var import_fs6 = require("fs");
7812
+ var import_child_process2 = require("child_process");
7813
+ var import_fs7 = require("fs");
6832
7814
  var import_os2 = require("os");
6833
- var path9 = __toESM(require("path"));
7815
+ var path10 = __toESM(require("path"));
6834
7816
  var BUILTIN_IDE_DEFINITIONS = [];
6835
7817
  var registeredIDEs = /* @__PURE__ */ new Map();
6836
7818
  function registerIDEDefinition(def) {
@@ -6849,13 +7831,13 @@ function getMergedDefinitions() {
6849
7831
  function findCliCommand(command) {
6850
7832
  const trimmed = String(command || "").trim();
6851
7833
  if (!trimmed) return null;
6852
- if (path9.isAbsolute(trimmed) || trimmed.includes("/") || trimmed.includes("\\") || trimmed.startsWith("~")) {
6853
- const candidate = trimmed.startsWith("~") ? path9.join((0, import_os2.homedir)(), trimmed.slice(1)) : trimmed;
6854
- const resolved = path9.isAbsolute(candidate) ? candidate : path9.resolve(candidate);
6855
- return (0, import_fs6.existsSync)(resolved) ? resolved : null;
7834
+ if (path10.isAbsolute(trimmed) || trimmed.includes("/") || trimmed.includes("\\") || trimmed.startsWith("~")) {
7835
+ const candidate = trimmed.startsWith("~") ? path10.join((0, import_os2.homedir)(), trimmed.slice(1)) : trimmed;
7836
+ const resolved = path10.isAbsolute(candidate) ? candidate : path10.resolve(candidate);
7837
+ return (0, import_fs7.existsSync)(resolved) ? resolved : null;
6856
7838
  }
6857
7839
  try {
6858
- const result = (0, import_child_process.execSync)(
7840
+ const result = (0, import_child_process2.execSync)(
6859
7841
  (0, import_os2.platform)() === "win32" ? `where ${trimmed}` : `which ${trimmed}`,
6860
7842
  { encoding: "utf-8", timeout: 5e3, stdio: ["pipe", "pipe", "pipe"] }
6861
7843
  ).trim();
@@ -6866,7 +7848,7 @@ function findCliCommand(command) {
6866
7848
  }
6867
7849
  function getIdeVersion(cliCommand) {
6868
7850
  try {
6869
- const result = (0, import_child_process.execSync)(`"${cliCommand}" --version`, {
7851
+ const result = (0, import_child_process2.execSync)(`"${cliCommand}" --version`, {
6870
7852
  encoding: "utf-8",
6871
7853
  timeout: 1e4,
6872
7854
  stdio: ["pipe", "pipe", "pipe"]
@@ -6879,13 +7861,13 @@ function getIdeVersion(cliCommand) {
6879
7861
  function checkPathExists(paths) {
6880
7862
  const home = (0, import_os2.homedir)();
6881
7863
  for (const p of paths) {
6882
- const normalized = p.startsWith("~") ? path9.join(home, p.slice(1)) : p;
7864
+ const normalized = p.startsWith("~") ? path10.join(home, p.slice(1)) : p;
6883
7865
  if (normalized.includes("*")) {
6884
7866
  const username = home.split(/[\\/]/).pop() || "";
6885
7867
  const resolved = normalized.replace("*", username);
6886
- if ((0, import_fs6.existsSync)(resolved)) return resolved;
7868
+ if ((0, import_fs7.existsSync)(resolved)) return resolved;
6887
7869
  } else {
6888
- if ((0, import_fs6.existsSync)(normalized)) return normalized;
7870
+ if ((0, import_fs7.existsSync)(normalized)) return normalized;
6889
7871
  }
6890
7872
  }
6891
7873
  return null;
@@ -6899,7 +7881,7 @@ async function detectIDEs(providerLoader) {
6899
7881
  let resolvedCli = cliPath;
6900
7882
  if (!resolvedCli && appPath && os22 === "darwin") {
6901
7883
  const bundledCli = `${appPath}/Contents/Resources/app/bin/${def.cli}`;
6902
- if ((0, import_fs6.existsSync)(bundledCli)) resolvedCli = bundledCli;
7884
+ if ((0, import_fs7.existsSync)(bundledCli)) resolvedCli = bundledCli;
6903
7885
  }
6904
7886
  if (!resolvedCli && appPath && os22 === "win32") {
6905
7887
  const { dirname: dirname9 } = await import("path");
@@ -6912,7 +7894,7 @@ async function detectIDEs(providerLoader) {
6912
7894
  `${appDir}\\\\resources\\\\app\\\\bin\\\\${def.cli}.cmd`
6913
7895
  ];
6914
7896
  for (const c of candidates) {
6915
- if ((0, import_fs6.existsSync)(c)) {
7897
+ if ((0, import_fs7.existsSync)(c)) {
6916
7898
  resolvedCli = c;
6917
7899
  break;
6918
7900
  }
@@ -6934,134 +7916,8 @@ async function detectIDEs(providerLoader) {
6934
7916
  return results;
6935
7917
  }
6936
7918
 
6937
- // src/detection/cli-detector.ts
6938
- var import_child_process2 = require("child_process");
6939
- var os3 = __toESM(require("os"));
6940
- var path10 = __toESM(require("path"));
6941
- var import_fs7 = require("fs");
6942
- function parseVersion(raw) {
6943
- const match = raw.match(/v?(\d+\.\d+(?:\.\d+)?(?:-[a-zA-Z0-9.]+)?)/);
6944
- return match ? match[1] : raw.split("\n")[0].slice(0, 100);
6945
- }
6946
- function shellQuote(value) {
6947
- if (/^[a-zA-Z0-9_./:@%+=,-]+$/.test(value)) return value;
6948
- return `"${value.replace(/(["\\$`])/g, "\\$1")}"`;
6949
- }
6950
- function expandHome(value) {
6951
- const trimmed = value.trim();
6952
- if (!trimmed.startsWith("~")) return trimmed;
6953
- return path10.join(os3.homedir(), trimmed.slice(1));
6954
- }
6955
- function isExplicitCommandPath(command) {
6956
- const trimmed = command.trim();
6957
- return path10.isAbsolute(trimmed) || trimmed.includes("/") || trimmed.includes("\\") || trimmed.startsWith("~");
6958
- }
6959
- function resolveCommandPath(command) {
6960
- const trimmed = command.trim();
6961
- if (!trimmed) return null;
6962
- if (isExplicitCommandPath(trimmed)) {
6963
- const expanded = expandHome(trimmed);
6964
- const candidate = path10.isAbsolute(expanded) ? expanded : path10.resolve(expanded);
6965
- return (0, import_fs7.existsSync)(candidate) ? candidate : null;
6966
- }
6967
- return null;
6968
- }
6969
- function execAsync(cmd, timeoutMs = 5e3) {
6970
- return new Promise((resolve16) => {
6971
- const child = (0, import_child_process2.exec)(cmd, {
6972
- encoding: "utf-8",
6973
- timeout: timeoutMs,
6974
- ...process.platform === "win32" ? { windowsHide: true } : {}
6975
- }, (err, stdout) => {
6976
- if (err || !stdout?.trim()) {
6977
- resolve16(null);
6978
- } else {
6979
- resolve16(stdout.trim());
6980
- }
6981
- });
6982
- child.on("error", () => resolve16(null));
6983
- });
6984
- }
6985
- async function detectCLIs(providerLoader, options) {
6986
- const platform10 = os3.platform();
6987
- const whichCmd = platform10 === "win32" ? "where" : "which";
6988
- const includeVersion = options?.includeVersion !== false;
6989
- const cliList = providerLoader ? providerLoader.getCliDetectionList() : [];
6990
- const results = await Promise.all(
6991
- cliList.map(async (cli) => {
6992
- try {
6993
- const explicitPath = resolveCommandPath(cli.command);
6994
- const pathResult = explicitPath || await execAsync(`${whichCmd} ${shellQuote(cli.command)}`);
6995
- if (!pathResult) return { ...cli, installed: false };
6996
- const firstPath = explicitPath || pathResult.split("\n")[0];
6997
- let version;
6998
- if (includeVersion) {
6999
- const versionCommands = [
7000
- `"${firstPath}" --version`,
7001
- `"${firstPath}" -V`,
7002
- `"${firstPath}" -v`,
7003
- cli.versionCommand
7004
- ].filter((v) => !!v);
7005
- try {
7006
- for (const versionCommand of versionCommands) {
7007
- const versionResult = await execAsync(versionCommand, 3e3);
7008
- if (versionResult) {
7009
- version = parseVersion(versionResult);
7010
- break;
7011
- }
7012
- }
7013
- } catch {
7014
- }
7015
- }
7016
- return { ...cli, installed: true, version, path: firstPath };
7017
- } catch {
7018
- return { ...cli, installed: false };
7019
- }
7020
- })
7021
- );
7022
- return results;
7023
- }
7024
- async function detectCLI(cliId, providerLoader, options) {
7025
- const resolvedId = providerLoader ? providerLoader.resolveAlias(cliId) : cliId;
7026
- if (providerLoader) {
7027
- const cliList = providerLoader.getCliDetectionList();
7028
- const target = cliList.find((c) => c.id === resolvedId);
7029
- if (target) {
7030
- const platform10 = os3.platform();
7031
- const whichCmd = platform10 === "win32" ? "where" : "which";
7032
- try {
7033
- const explicitPath = resolveCommandPath(target.command);
7034
- const pathResult = explicitPath || await execAsync(`${whichCmd} ${shellQuote(target.command)}`);
7035
- if (!pathResult) return null;
7036
- const firstPath = explicitPath || pathResult.split("\n")[0];
7037
- let version;
7038
- if (options?.includeVersion !== false) {
7039
- const versionCommands = [
7040
- `"${firstPath}" --version`,
7041
- `"${firstPath}" -V`,
7042
- `"${firstPath}" -v`,
7043
- target.versionCommand
7044
- ].filter((v) => !!v);
7045
- try {
7046
- for (const versionCommand of versionCommands) {
7047
- const versionResult = await execAsync(versionCommand, 3e3);
7048
- if (versionResult) {
7049
- version = parseVersion(versionResult);
7050
- break;
7051
- }
7052
- }
7053
- } catch {
7054
- }
7055
- }
7056
- return { ...target, installed: true, version, path: firstPath };
7057
- } catch {
7058
- return null;
7059
- }
7060
- }
7061
- }
7062
- const all = await detectCLIs(providerLoader, options);
7063
- return all.find((c) => c.id === resolvedId && c.installed) || null;
7064
- }
7919
+ // src/index.ts
7920
+ init_cli_detector();
7065
7921
 
7066
7922
  // src/system/host-memory.ts
7067
7923
  var os4 = __toESM(require("os"));
@@ -8930,6 +9786,28 @@ var StatusMonitor = class {
8930
9786
  };
8931
9787
 
8932
9788
  // src/providers/chat-message-normalization.ts
9789
+ function extractFinalSummaryFromMessages(messages, maxChars = 500) {
9790
+ if (!Array.isArray(messages) || messages.length === 0) return "";
9791
+ for (let i = messages.length - 1; i >= 0; i--) {
9792
+ const msg = messages[i];
9793
+ if (!msg) continue;
9794
+ const classification = classifyChatMessageVisibility(msg);
9795
+ if (classification.isUserFacing && (msg.role === "assistant" || msg.role === "model")) {
9796
+ const text = flattenContent(msg.content).trim();
9797
+ if (text) return text.slice(0, maxChars);
9798
+ }
9799
+ }
9800
+ for (let i = messages.length - 1; i >= 0; i--) {
9801
+ const msg = messages[i];
9802
+ if (!msg) continue;
9803
+ const classification = classifyChatMessageVisibility(msg);
9804
+ if (classification.isUserFacing) {
9805
+ const text = flattenContent(msg.content).trim();
9806
+ if (text) return text.slice(0, maxChars);
9807
+ }
9808
+ }
9809
+ return "";
9810
+ }
8933
9811
  var BUILTIN_CHAT_MESSAGE_KINDS = ["standard", "thought", "tool", "terminal", "system"];
8934
9812
  var CHAT_MESSAGE_VISIBILITIES = ["user", "debug", "internal", "hidden"];
8935
9813
  var CHAT_MESSAGE_TRANSCRIPT_VISIBILITIES = ["visible", "chat", "user", "debug", "internal", "hidden"];
@@ -10941,7 +11819,8 @@ var ExtensionProviderInstance = class {
10941
11819
  ideType: this.ideType || this.type,
10942
11820
  agentType: this.type,
10943
11821
  agentName: this.agentName || this.provider.name,
10944
- extensionId: this.extensionId || this.type
11822
+ extensionId: this.extensionId || this.type,
11823
+ finalSummary: extractFinalSummaryFromMessages(data?.messages)
10945
11824
  });
10946
11825
  this.generatingStartedAt = 0;
10947
11826
  }
@@ -11709,7 +12588,7 @@ var IdeProviderInstance = class {
11709
12588
  } else if (agentStatus === "idle" && (lastStatus === "generating" || lastStatus === "waiting_approval")) {
11710
12589
  const startedAt = this.generatingStartedAt.get(agentKey);
11711
12590
  const duration = startedAt ? Math.round((now - startedAt) / 1e3) : 0;
11712
- this.pushEvent({ event: "agent:generating_completed", chatTitle, duration, timestamp: now, ideType: this.type });
12591
+ this.pushEvent({ event: "agent:generating_completed", chatTitle, duration, timestamp: now, ideType: this.type, finalSummary: extractFinalSummaryFromMessages(chatData?.messages) });
11713
12592
  this.generatingStartedAt.delete(agentKey);
11714
12593
  }
11715
12594
  this.lastAgentStatuses.set(agentKey, agentStatus);
@@ -15205,11 +16084,13 @@ async function handleOpenPanel(h, args) {
15205
16084
  async function handlePtyInput(h, args) {
15206
16085
  const { cliType, data, targetSessionId } = args || {};
15207
16086
  if (!data) return { success: false, error: "data required" };
16087
+ const cleanData = typeof data === "string" ? data.replace(/\x1b\[[?>][0-9;]*c/g, "") : data;
16088
+ if (!cleanData) return { success: true };
15208
16089
  const adapter = h.getCliAdapter(targetSessionId || cliType);
15209
16090
  if (!adapter || typeof adapter.writeRaw !== "function") {
15210
16091
  return { success: false, error: `CLI adapter not found: ${targetSessionId || cliType || "unknown"}` };
15211
16092
  }
15212
- await adapter.writeRaw(data);
16093
+ await adapter.writeRaw(cleanData);
15213
16094
  return { success: true };
15214
16095
  }
15215
16096
  function handlePtyResize(_h, args) {
@@ -16169,6 +17050,7 @@ var import_fs8 = require("fs");
16169
17050
  var import_child_process6 = require("child_process");
16170
17051
  var import_chalk = __toESM(require("chalk"));
16171
17052
  init_provider_cli_adapter();
17053
+ init_cli_detector();
16172
17054
  init_config();
16173
17055
 
16174
17056
  // src/providers/cli-provider-instance.ts
@@ -16198,6 +17080,8 @@ function normalizeProviderSessionId(provider, providerSessionId) {
16198
17080
  }
16199
17081
 
16200
17082
  // src/providers/cli-provider-instance.ts
17083
+ var COMPLETED_FINALIZATION_RETRY_MS = 1e3;
17084
+ var COMPLETED_FINALIZATION_MAX_WAIT_MS = 3e4;
16201
17085
  var IMAGE_MIME_EXTENSIONS = {
16202
17086
  "image/png": ".png",
16203
17087
  "image/jpeg": ".jpg",
@@ -16261,6 +17145,13 @@ function cleanupStaleMaterializedImages(dir) {
16261
17145
  } catch {
16262
17146
  }
16263
17147
  }
17148
+ function hasNonEmptyCliModalButtons(activeModal) {
17149
+ const buttons = activeModal?.buttons;
17150
+ return Array.isArray(buttons) && buttons.some((button) => String(button || "").trim().length > 0);
17151
+ }
17152
+ function isCliGeneratingLikeStatus(status) {
17153
+ return status === "generating" || status === "streaming" || status === "long_generating" || status === "starting";
17154
+ }
16264
17155
  function buildCliStructuredInputPrompt(input, options = {}) {
16265
17156
  const promptParts = [];
16266
17157
  const imageRefs = [];
@@ -16545,10 +17436,12 @@ var CliProviderInstance = class {
16545
17436
  const mergedMessages = this.mergeConversationMessages(parsedMessages);
16546
17437
  const canonicalBackedHistory = this.syncCanonicalSavedHistoryIfNeeded();
16547
17438
  const dirName = this.workingDir.split("/").filter(Boolean).pop() || "session";
17439
+ const parsedChatStatus = typeof parsedStatus?.status === "string" && parsedStatus.status.trim() ? parsedStatus.status.trim() : void 0;
17440
+ const suppressStaleParsedBusyStatus = this.shouldSuppressStaleParsedBusyStatus(parsedStatus, adapterStatus);
16548
17441
  if (parsedMessages.length > 0) {
16549
17442
  const shouldSkipReplayPersist = this.suppressIdleHistoryReplay && adapterStatus.status === "idle" && parsedStatus?.status === "idle";
16550
17443
  let messagesToSave = parsedMessages;
16551
- if (parsedStatus?.status === "generating" || parsedStatus?.status === "long_generating") {
17444
+ if (!suppressStaleParsedBusyStatus && (parsedChatStatus === "generating" || parsedChatStatus === "long_generating")) {
16552
17445
  const lastIdx = messagesToSave.length - 1;
16553
17446
  if (lastIdx >= 0 && messagesToSave[lastIdx]?.role === "assistant") {
16554
17447
  messagesToSave = messagesToSave.slice(0, lastIdx);
@@ -16582,6 +17475,7 @@ var CliProviderInstance = class {
16582
17475
  summaryMetadata: this.summaryMetadata,
16583
17476
  controlValues: this.controlValues
16584
17477
  });
17478
+ const activeChatStatus = parseErrorMessage ? "error" : autoApproveActive && parsedStatus?.status === "waiting_approval" ? "generating" : adapterStatus.status !== "idle" ? visibleStatus : suppressStaleParsedBusyStatus ? visibleStatus : parsedChatStatus || visibleStatus;
16585
17479
  return {
16586
17480
  type: this.type,
16587
17481
  name: this.provider.name,
@@ -16591,7 +17485,7 @@ var CliProviderInstance = class {
16591
17485
  activeChat: {
16592
17486
  id: `${this.type}_${this.workingDir}`,
16593
17487
  title: parsedStatus?.title || dirName,
16594
- status: parseErrorMessage ? "error" : autoApproveActive && parsedStatus?.status === "waiting_approval" ? "generating" : adapterStatus.status !== "idle" ? visibleStatus : parsedStatus?.status || visibleStatus,
17488
+ status: activeChatStatus,
16595
17489
  messages: mergedMessages,
16596
17490
  activeModal: autoApproveActive ? null : parsedStatus?.activeModal ?? adapterStatus.activeModal,
16597
17491
  inputContent: ""
@@ -16721,6 +17615,103 @@ var CliProviderInstance = class {
16721
17615
  }
16722
17616
  this.applyProviderResponse(parsed.payload, { phase: "immediate" });
16723
17617
  }
17618
+ completionHasFinalAssistantMessage(messages) {
17619
+ const visibleMessages = (Array.isArray(messages) ? messages : []).filter((message) => isUserFacingChatMessage(message));
17620
+ const lastVisible = visibleMessages[visibleMessages.length - 1];
17621
+ const role = typeof lastVisible?.role === "string" ? lastVisible.role.trim().toLowerCase() : "";
17622
+ const content = lastVisible ? flattenContent(lastVisible.content).trim() : "";
17623
+ return role === "assistant" && !!content;
17624
+ }
17625
+ hasAdapterPendingResponse() {
17626
+ const adapterAny = this.adapter;
17627
+ if (adapterAny?.isWaitingForResponse === true) return true;
17628
+ if (adapterAny?.currentTurnScope) return true;
17629
+ try {
17630
+ if (typeof this.adapter.isProcessing === "function" && this.adapter.isProcessing()) return true;
17631
+ } catch {
17632
+ }
17633
+ try {
17634
+ const partial = typeof this.adapter.getPartialResponse === "function" ? this.adapter.getPartialResponse() : "";
17635
+ if (typeof partial === "string" && partial.trim()) return true;
17636
+ } catch {
17637
+ }
17638
+ return false;
17639
+ }
17640
+ shouldSuppressStaleParsedBusyStatus(parsedStatus, adapterStatus) {
17641
+ const parsedRawStatus = typeof parsedStatus?.status === "string" ? parsedStatus.status.trim() : "";
17642
+ const adapterRawStatus = typeof adapterStatus?.status === "string" ? adapterStatus.status.trim() : "";
17643
+ if (!isCliGeneratingLikeStatus(parsedRawStatus)) return false;
17644
+ if (adapterRawStatus !== "idle") return false;
17645
+ if (hasNonEmptyCliModalButtons(parsedStatus?.activeModal ?? parsedStatus?.modal)) return false;
17646
+ return !this.hasAdapterPendingResponse();
17647
+ }
17648
+ getCompletedFinalizationBlockReason(latestVisibleStatus) {
17649
+ if (latestVisibleStatus !== "idle") return `status:${latestVisibleStatus}`;
17650
+ const adapterAny = this.adapter;
17651
+ if (adapterAny?.isWaitingForResponse === true) return "adapter_waiting_for_response";
17652
+ if (adapterAny?.currentTurnScope) return "adapter_turn_scope_active";
17653
+ const partial = typeof this.adapter.getPartialResponse === "function" ? this.adapter.getPartialResponse() : "";
17654
+ if (typeof partial === "string" && partial.trim()) return "partial_response_pending";
17655
+ let parsed;
17656
+ try {
17657
+ parsed = this.adapter.getScriptParsedStatus();
17658
+ } catch (error) {
17659
+ return `parse_error:${error?.message || String(error)}`;
17660
+ }
17661
+ const parsedStatus = typeof parsed?.status === "string" ? parsed.status : "unknown";
17662
+ if (parsedStatus !== "idle") return `parsed_status:${parsedStatus}`;
17663
+ if (parsed?.activeModal || parsed?.modal) return "parsed_modal_active";
17664
+ if (!this.completionHasFinalAssistantMessage(parsed?.messages)) return "missing_final_assistant";
17665
+ return null;
17666
+ }
17667
+ scheduleCompletedDebounceFlush(delayMs) {
17668
+ if (this.completedDebounceTimer) clearTimeout(this.completedDebounceTimer);
17669
+ this.completedDebounceTimer = setTimeout(() => this.flushCompletedDebounceIfFinalized(), delayMs);
17670
+ }
17671
+ flushCompletedDebounceIfFinalized() {
17672
+ const pending = this.completedDebouncePending;
17673
+ if (!pending) {
17674
+ this.completedDebounceTimer = null;
17675
+ return;
17676
+ }
17677
+ const latestStatus = this.adapter.getStatus({ allowParse: false });
17678
+ const latestAutoApproveActive = latestStatus.status === "waiting_approval" && this.shouldAutoApprove();
17679
+ const latestVisibleStatus = latestAutoApproveActive ? "generating" : latestStatus.status;
17680
+ if (latestVisibleStatus !== "idle") {
17681
+ LOG.info("CLI", `[${this.type}] cancelled pending completed (resumed ${latestVisibleStatus})`);
17682
+ this.completedDebouncePending = null;
17683
+ this.completedDebounceTimer = null;
17684
+ return;
17685
+ }
17686
+ const blockReason = this.getCompletedFinalizationBlockReason(latestVisibleStatus);
17687
+ if (blockReason) {
17688
+ const waitedMs = Date.now() - pending.firstObservedAt;
17689
+ if (waitedMs < COMPLETED_FINALIZATION_MAX_WAIT_MS) {
17690
+ if (pending.loggedBlockReason !== blockReason) {
17691
+ LOG.info("CLI", `[${this.type}] waiting to emit completed until transcript finalizes (${blockReason})`);
17692
+ pending.loggedBlockReason = blockReason;
17693
+ }
17694
+ this.scheduleCompletedDebounceFlush(COMPLETED_FINALIZATION_RETRY_MS);
17695
+ return;
17696
+ }
17697
+ LOG.warn("CLI", `[${this.type}] suppressed completed event after ${waitedMs}ms without finalized assistant turn (${blockReason})`);
17698
+ this.completedDebouncePending = null;
17699
+ this.completedDebounceTimer = null;
17700
+ this.generatingStartedAt = 0;
17701
+ return;
17702
+ }
17703
+ LOG.info("CLI", `[${this.type}] completed in ${pending.duration}s`);
17704
+ this.pushEvent({
17705
+ event: "agent:generating_completed",
17706
+ chatTitle: pending.chatTitle,
17707
+ duration: pending.duration,
17708
+ timestamp: pending.timestamp,
17709
+ finalSummary: extractFinalSummaryFromMessages(this.adapter?.getScriptParsedStatus()?.messages)
17710
+ });
17711
+ this.completedDebouncePending = null;
17712
+ this.completedDebounceTimer = null;
17713
+ this.generatingStartedAt = 0;
17714
+ }
16724
17715
  maybeAutoApproveStatus(adapterStatus, now = Date.now()) {
16725
17716
  const autoApproveActive = adapterStatus?.status === "waiting_approval" && this.shouldAutoApprove();
16726
17717
  if (autoApproveActive && !this.autoApproveBusy) {
@@ -16818,27 +17809,11 @@ var CliProviderInstance = class {
16818
17809
  this.generatingDebouncePending = null;
16819
17810
  this.generatingStartedAt = 0;
16820
17811
  } else {
16821
- if (this.completedDebounceTimer) clearTimeout(this.completedDebounceTimer);
16822
- this.completedDebouncePending = { chatTitle, duration, timestamp: now };
16823
- this.completedDebounceTimer = setTimeout(() => {
16824
- if (this.completedDebouncePending) {
16825
- const latestStatus = this.adapter.getStatus({ allowParse: false });
16826
- const latestAutoApproveActive = latestStatus.status === "waiting_approval" && this.shouldAutoApprove();
16827
- const latestVisibleStatus = latestAutoApproveActive ? "generating" : latestStatus.status;
16828
- if (latestVisibleStatus !== "idle") {
16829
- LOG.info("CLI", `[${this.type}] cancelled pending completed (resumed ${latestVisibleStatus})`);
16830
- this.completedDebouncePending = null;
16831
- this.completedDebounceTimer = null;
16832
- return;
16833
- }
16834
- LOG.info("CLI", `[${this.type}] completed in ${this.completedDebouncePending.duration}s`);
16835
- this.pushEvent({ event: "agent:generating_completed", ...this.completedDebouncePending });
16836
- this.completedDebouncePending = null;
16837
- this.generatingStartedAt = 0;
16838
- }
16839
- this.completedDebounceTimer = null;
16840
- }, 3e3);
17812
+ this.completedDebouncePending = { chatTitle, duration, timestamp: now, firstObservedAt: now };
17813
+ this.scheduleCompletedDebounceFlush(3e3);
16841
17814
  }
17815
+ } else if (newStatus === "idle" && this.lastStatus === "starting") {
17816
+ this.pushEvent({ event: "agent:ready", chatTitle, timestamp: now });
16842
17817
  } else if (newStatus === "stopped") {
16843
17818
  if (this.generatingDebounceTimer) {
16844
17819
  clearTimeout(this.generatingDebounceTimer);
@@ -18474,7 +19449,7 @@ ${rawInput}` : rawInput;
18474
19449
  });
18475
19450
  } else if (newStatus === "idle" && (this.lastStatus === "generating" || this.lastStatus === "waiting_approval")) {
18476
19451
  const duration = this.generatingStartedAt ? Math.round((now - this.generatingStartedAt) / 1e3) : 0;
18477
- this.pushEvent({ event: "agent:generating_completed", chatTitle, duration, timestamp: now });
19452
+ this.pushEvent({ event: "agent:generating_completed", chatTitle, duration, timestamp: now, finalSummary: extractFinalSummaryFromMessages(this.messages) });
18478
19453
  this.generatingStartedAt = 0;
18479
19454
  } else if (newStatus === "stopped") {
18480
19455
  this.pushEvent({ event: "agent:stopped", chatTitle, timestamp: now });
@@ -18582,9 +19557,6 @@ function buildCoordinatorDelegatedCliLaunchOptions(input) {
18582
19557
  const cliType = String(input.cliType || "").trim();
18583
19558
  const cliArgs = Array.isArray(input.cliArgs) ? [...input.cliArgs] : [];
18584
19559
  const env = { ...input.env || {}, ...COORDINATOR_DELEGATED_ENV_UNSETS };
18585
- if (cliType === "hermes-cli" && !hasCliArg(cliArgs, "--ignore-user-config")) {
18586
- cliArgs.unshift("--ignore-user-config");
18587
- }
18588
19560
  if (cliType === "claude-cli" && !hasCliArg(cliArgs, "--mcp-config")) {
18589
19561
  cliArgs.unshift("--mcp-config", ensureEmptyDelegatedMcpConfig(input.workspace));
18590
19562
  }
@@ -21661,6 +22633,7 @@ function getAvailableIdeIds() {
21661
22633
 
21662
22634
  // src/commands/router.ts
21663
22635
  init_config();
22636
+ init_cli_detector();
21664
22637
  init_logger();
21665
22638
 
21666
22639
  // src/logging/command-log.ts
@@ -21830,7 +22803,9 @@ function resolveHermesMeshCoordinatorSetup(options) {
21830
22803
  const mcpServer = resolveAdhdevMcpServerLaunch({
21831
22804
  meshId: options.meshId,
21832
22805
  nodeExecutable: options.nodeExecutable,
21833
- adhdevMcpEntryPath: options.adhdevMcpEntryPath
22806
+ adhdevMcpEntryPath: options.adhdevMcpEntryPath,
22807
+ adhdevMcpTransport: options.adhdevMcpTransport,
22808
+ adhdevMcpPort: options.adhdevMcpPort
21834
22809
  });
21835
22810
  if (!mcpServer) {
21836
22811
  return {
@@ -21897,7 +22872,9 @@ function resolveMeshCoordinatorSetup(options) {
21897
22872
  const mcpServer = resolveAdhdevMcpServerLaunch({
21898
22873
  meshId,
21899
22874
  nodeExecutable: options.nodeExecutable,
21900
- adhdevMcpEntryPath: options.adhdevMcpEntryPath
22875
+ adhdevMcpEntryPath: options.adhdevMcpEntryPath,
22876
+ adhdevMcpTransport: options.adhdevMcpTransport,
22877
+ adhdevMcpPort: options.adhdevMcpPort
21901
22878
  });
21902
22879
  if (!mcpServer) {
21903
22880
  return {
@@ -21919,19 +22896,30 @@ function resolveMeshCoordinatorSetup(options) {
21919
22896
  if (!instructions || !template?.trim()) {
21920
22897
  return { kind: "unsupported", reason: "Provider manual MCP setup is missing instructions or template" };
21921
22898
  }
22899
+ const renderedTemplate = renderMeshCoordinatorTemplate(template, {
22900
+ meshId,
22901
+ workspace,
22902
+ serverName,
22903
+ adhdevMcpCommand: options.adhdevMcpCommand || DEFAULT_ADHDEV_MCP_COMMAND
22904
+ });
22905
+ const isCliCommand = !renderedTemplate.trim().includes("\n") && !renderedTemplate.trim().startsWith("{");
22906
+ if (isCliCommand) {
22907
+ return {
22908
+ kind: "cli_command",
22909
+ serverName,
22910
+ command: renderedTemplate.trim(),
22911
+ requiresRestart: mcpConfig.requiresRestart === true,
22912
+ instructions
22913
+ };
22914
+ }
21922
22915
  return {
21923
22916
  kind: "manual",
21924
22917
  serverName,
21925
22918
  configFormat: mcpConfig.format,
21926
22919
  configPathCommand: mcpConfig.configPathCommand,
21927
22920
  requiresRestart: mcpConfig.requiresRestart === true,
21928
- instructions,
21929
- template: renderMeshCoordinatorTemplate(template, {
21930
- meshId,
21931
- workspace,
21932
- serverName,
21933
- adhdevMcpCommand: options.adhdevMcpCommand || DEFAULT_ADHDEV_MCP_COMMAND
21934
- })
22921
+ instructions,
22922
+ template: renderedTemplate
21935
22923
  };
21936
22924
  }
21937
22925
  return {
@@ -21960,11 +22948,27 @@ function resolveAdhdevMcpServerLaunch(options) {
21960
22948
  if (!entryPath) return null;
21961
22949
  const nodeExecutable = resolveMcpNodeExecutable(options.nodeExecutable);
21962
22950
  if (!nodeExecutable) return null;
22951
+ const transport = resolveMcpTransport(options.adhdevMcpTransport);
22952
+ const args = [entryPath, "--mode", transport, "--repo-mesh", options.meshId];
22953
+ const port = resolveMcpPort(options.adhdevMcpPort);
22954
+ if (port !== void 0) args.push("--port", String(port));
21963
22955
  return {
21964
22956
  command: nodeExecutable,
21965
- args: [entryPath, "--mode", "ipc", "--repo-mesh", options.meshId]
22957
+ args
21966
22958
  };
21967
22959
  }
22960
+ function resolveMcpTransport(explicitTransport) {
22961
+ if (explicitTransport === "local" || explicitTransport === "ipc") return explicitTransport;
22962
+ const envTransport = process.env.ADHDEV_COORDINATOR_MCP_TRANSPORT?.trim();
22963
+ return envTransport === "local" ? "local" : "ipc";
22964
+ }
22965
+ function resolveMcpPort(explicitPort) {
22966
+ if (typeof explicitPort === "number" && Number.isInteger(explicitPort) && explicitPort > 0) return explicitPort;
22967
+ const raw = process.env.ADHDEV_COORDINATOR_MCP_PORT?.trim();
22968
+ if (!raw) return void 0;
22969
+ const parsed = Number(raw);
22970
+ return Number.isInteger(parsed) && parsed > 0 ? parsed : void 0;
22971
+ }
21968
22972
  function resolveMcpNodeExecutable(explicitExecutable) {
21969
22973
  const explicit = explicitExecutable?.trim();
21970
22974
  if (explicit) return explicit;
@@ -22776,6 +23780,209 @@ async function resolveProviderTypeFromPriority(args) {
22776
23780
  }
22777
23781
  return { error: `No usable provider detected for node '${args.nodeId}' from providerPriority: ${failed.join("; ")}` };
22778
23782
  }
23783
+ var REFINE_VALIDATION_CATEGORIES = ["typecheck", "test", "lint", "build"];
23784
+ var REFINE_VALIDATION_TIMEOUT_MS = 12e4;
23785
+ var REFINE_VALIDATION_OUTPUT_LIMIT_BYTES = 128 * 1024;
23786
+ var REFINE_VALIDATION_SUMMARY_CHARS = 2e3;
23787
+ var REFINE_VALIDATION_MAX_COMMANDS = 4;
23788
+ function truncateValidationOutput(value) {
23789
+ const text = typeof value === "string" ? value : value == null ? "" : String(value);
23790
+ if (text.length <= REFINE_VALIDATION_SUMMARY_CHARS) return text;
23791
+ return `${text.slice(0, REFINE_VALIDATION_SUMMARY_CHARS)}
23792
+ [truncated ${text.length - REFINE_VALIDATION_SUMMARY_CHARS} chars]`;
23793
+ }
23794
+ function readPackageScripts(workspace) {
23795
+ try {
23796
+ const packageJsonPath = (0, import_path6.join)(workspace, "package.json");
23797
+ const parsed = JSON.parse(fs10.readFileSync(packageJsonPath, "utf-8"));
23798
+ return parsed?.scripts && typeof parsed.scripts === "object" && !Array.isArray(parsed.scripts) ? parsed.scripts : {};
23799
+ } catch {
23800
+ return {};
23801
+ }
23802
+ }
23803
+ function tokenizeValidationCommand(command) {
23804
+ const trimmed = command.trim();
23805
+ if (!trimmed) return null;
23806
+ if (/[;&|<>`$\\\n\r'\"]/.test(trimmed)) return null;
23807
+ const tokens = trimmed.split(/\s+/).filter(Boolean);
23808
+ if (!tokens.length) return null;
23809
+ if (tokens.some((token) => !/^[A-Za-z0-9_@./:=+-]+$/.test(token))) return null;
23810
+ return tokens;
23811
+ }
23812
+ function scriptMatchesValidationCategory(scriptName, category) {
23813
+ return scriptName === category || scriptName.startsWith(`${category}:`);
23814
+ }
23815
+ function parsePackageManagerValidationCommand(rawCommand, category, scripts, source) {
23816
+ const tokens = tokenizeValidationCommand(rawCommand);
23817
+ if (!tokens) {
23818
+ return { rejected: { command: rawCommand, category, source, reason: "unsafe command string is not allowlisted" } };
23819
+ }
23820
+ const [binary, second, third, ...rest] = tokens;
23821
+ let scriptName = "";
23822
+ let command = binary;
23823
+ let args = [];
23824
+ if ((binary === "npm" || binary === "pnpm" || binary === "bun") && second === "run" && third) {
23825
+ scriptName = third;
23826
+ args = ["run", scriptName, ...rest];
23827
+ } else if (binary === "npm" && second === "test" && !third) {
23828
+ scriptName = "test";
23829
+ args = ["test"];
23830
+ } else if (binary === "yarn" && second === "run" && third) {
23831
+ scriptName = third;
23832
+ args = ["run", scriptName, ...rest];
23833
+ } else if (binary === "yarn" && second && !third) {
23834
+ scriptName = second;
23835
+ args = [scriptName];
23836
+ } else {
23837
+ return { rejected: { command: rawCommand, category, source, reason: "command is not a supported package-manager script invocation" } };
23838
+ }
23839
+ if (!scriptName || !Object.prototype.hasOwnProperty.call(scripts, scriptName)) {
23840
+ return { rejected: { command: rawCommand, category, source, script: scriptName, reason: "script is not declared in package.json" } };
23841
+ }
23842
+ if (!scriptMatchesValidationCategory(scriptName, category)) {
23843
+ return { rejected: { command: rawCommand, category, source, script: scriptName, reason: "script name is outside the validation category allowlist" } };
23844
+ }
23845
+ return {
23846
+ command: {
23847
+ command,
23848
+ args,
23849
+ displayCommand: [command, ...args].join(" "),
23850
+ category,
23851
+ source
23852
+ }
23853
+ };
23854
+ }
23855
+ function collectProjectContextValidationCandidates(mesh) {
23856
+ const commands = mesh?.projectContext?.commands;
23857
+ if (!commands || typeof commands !== "object" || Array.isArray(commands)) return [];
23858
+ const candidates = [];
23859
+ for (const category of REFINE_VALIDATION_CATEGORIES) {
23860
+ const entries = Array.isArray(commands[category]) ? commands[category] : [];
23861
+ for (const entry of entries) {
23862
+ if (typeof entry?.command !== "string") continue;
23863
+ candidates.push({
23864
+ command: entry.command,
23865
+ category,
23866
+ source: typeof entry.sourcePath === "string" ? entry.sourcePath : "projectContext.commands",
23867
+ confidence: typeof entry.confidence === "string" ? entry.confidence : void 0
23868
+ });
23869
+ }
23870
+ }
23871
+ return candidates.sort((a, b) => {
23872
+ const rank = (value) => value === "high" ? 0 : value === "medium" ? 1 : 2;
23873
+ return rank(a.confidence) - rank(b.confidence);
23874
+ });
23875
+ }
23876
+ function collectPolicyValidationCandidates(mesh) {
23877
+ const policy = mesh?.policy && typeof mesh.policy === "object" && !Array.isArray(mesh.policy) ? mesh.policy : {};
23878
+ const configured = Array.isArray(policy.validationCommands) ? policy.validationCommands : Array.isArray(policy.validationGate?.commands) ? policy.validationGate.commands : [];
23879
+ return configured.map((entry) => typeof entry === "string" ? { command: entry, category: "", source: "mesh.policy.validationCommands" } : entry).filter((entry) => entry && typeof entry.command === "string").map((entry) => {
23880
+ const commandText = entry.command.trim();
23881
+ const category = REFINE_VALIDATION_CATEGORIES.find((cat) => commandText.includes(` ${cat}`)) ?? "";
23882
+ return { command: commandText, category, source: "mesh.policy.validationCommands" };
23883
+ }).filter((entry) => !!entry.category);
23884
+ }
23885
+ function selectMeshRefineValidationCommands(mesh, workspace) {
23886
+ const scripts = readPackageScripts(workspace);
23887
+ const rejectedCommands = [];
23888
+ const selected = [];
23889
+ const seen = /* @__PURE__ */ new Set();
23890
+ const candidates = [
23891
+ ...collectPolicyValidationCandidates(mesh),
23892
+ ...collectProjectContextValidationCandidates(mesh)
23893
+ ];
23894
+ for (const candidate of candidates) {
23895
+ const parsed = parsePackageManagerValidationCommand(candidate.command, candidate.category, scripts, candidate.source);
23896
+ if (parsed.rejected) {
23897
+ rejectedCommands.push(parsed.rejected);
23898
+ continue;
23899
+ }
23900
+ if (!parsed.command || seen.has(parsed.command.displayCommand)) continue;
23901
+ selected.push(parsed.command);
23902
+ seen.add(parsed.command.displayCommand);
23903
+ if (selected.length >= REFINE_VALIDATION_MAX_COMMANDS) break;
23904
+ }
23905
+ if (!selected.length && candidates.length === 0) {
23906
+ for (const category of REFINE_VALIDATION_CATEGORIES) {
23907
+ if (!Object.prototype.hasOwnProperty.call(scripts, category)) continue;
23908
+ const fallback = parsePackageManagerValidationCommand(`npm run ${category}`, category, scripts, "package.json:scripts");
23909
+ if (fallback.command && !seen.has(fallback.command.displayCommand)) {
23910
+ selected.push(fallback.command);
23911
+ seen.add(fallback.command.displayCommand);
23912
+ } else if (fallback.rejected) {
23913
+ rejectedCommands.push(fallback.rejected);
23914
+ }
23915
+ if (selected.length >= 2) break;
23916
+ }
23917
+ }
23918
+ return {
23919
+ commands: selected,
23920
+ rejectedCommands,
23921
+ source: selected.some((command) => command.source === "mesh.policy.validationCommands") ? "mesh_policy" : selected.some((command) => command.source !== "package.json:scripts") ? "project_context" : selected.length ? "package_json_scripts" : "unavailable"
23922
+ };
23923
+ }
23924
+ async function runMeshRefineValidationGate(mesh, workspace) {
23925
+ const { execFile: execFile3 } = await import("child_process");
23926
+ const { promisify: promisify3 } = await import("util");
23927
+ const execFileAsync3 = promisify3(execFile3);
23928
+ const selection = selectMeshRefineValidationCommands(mesh, workspace);
23929
+ const summary = {
23930
+ status: "skipped",
23931
+ required: true,
23932
+ commandsRun: [],
23933
+ rejectedCommands: selection.rejectedCommands,
23934
+ skippedReason: void 0,
23935
+ timeoutMs: REFINE_VALIDATION_TIMEOUT_MS,
23936
+ outputLimitBytes: REFINE_VALIDATION_OUTPUT_LIMIT_BYTES
23937
+ };
23938
+ if (!selection.commands.length) {
23939
+ summary.skippedReason = "validation_unavailable: no allowlisted projectContext, mesh policy, or package.json build/test/typecheck/lint command was available";
23940
+ return summary;
23941
+ }
23942
+ for (const candidate of selection.commands) {
23943
+ const startedAt = Date.now();
23944
+ try {
23945
+ const result = await execFileAsync3(candidate.command, candidate.args, {
23946
+ cwd: workspace,
23947
+ encoding: "utf8",
23948
+ timeout: REFINE_VALIDATION_TIMEOUT_MS,
23949
+ maxBuffer: REFINE_VALIDATION_OUTPUT_LIMIT_BYTES,
23950
+ env: { ...process.env, CI: process.env.CI || "1" }
23951
+ });
23952
+ summary.commandsRun.push({
23953
+ command: candidate.command,
23954
+ args: candidate.args,
23955
+ displayCommand: candidate.displayCommand,
23956
+ category: candidate.category,
23957
+ source: candidate.source,
23958
+ passed: true,
23959
+ exitCode: 0,
23960
+ durationMs: Date.now() - startedAt,
23961
+ stdout: truncateValidationOutput(result.stdout),
23962
+ stderr: truncateValidationOutput(result.stderr)
23963
+ });
23964
+ } catch (error) {
23965
+ summary.commandsRun.push({
23966
+ command: candidate.command,
23967
+ args: candidate.args,
23968
+ displayCommand: candidate.displayCommand,
23969
+ category: candidate.category,
23970
+ source: candidate.source,
23971
+ passed: false,
23972
+ exitCode: typeof error?.code === "number" ? error.code : null,
23973
+ signal: typeof error?.signal === "string" ? error.signal : null,
23974
+ timedOut: error?.killed === true || /timed out/i.test(String(error?.message || "")),
23975
+ durationMs: Date.now() - startedAt,
23976
+ stdout: truncateValidationOutput(error?.stdout),
23977
+ stderr: truncateValidationOutput(error?.stderr || error?.message)
23978
+ });
23979
+ summary.status = "failed";
23980
+ return summary;
23981
+ }
23982
+ }
23983
+ summary.status = "passed";
23984
+ return summary;
23985
+ }
22779
23986
  function loadYamlModule() {
22780
23987
  return yaml;
22781
23988
  }
@@ -22805,6 +24012,34 @@ function loadHermesCoordinatorBaseConfig(targetConfigPath) {
22805
24012
  const { mcp_servers: _mcpServers, ...baseConfig } = parsed;
22806
24013
  return { config: baseConfig, sourceHome, sourceConfigPath };
22807
24014
  }
24015
+ function stripHermesCoordinatorTempModelProviderOverrides(config) {
24016
+ const {
24017
+ model: _model,
24018
+ provider: _provider,
24019
+ default_model: _defaultModel,
24020
+ defaultProvider: _defaultProvider,
24021
+ default_provider: _defaultProviderSnake,
24022
+ modelProvider: _modelProvider,
24023
+ model_provider: _modelProviderSnake,
24024
+ ...sanitized
24025
+ } = config;
24026
+ const delegation = sanitized.delegation;
24027
+ if (delegation && typeof delegation === "object" && !Array.isArray(delegation)) {
24028
+ const {
24029
+ model: _delegationModel,
24030
+ provider: _delegationProvider,
24031
+ modelProvider: _delegationModelProvider,
24032
+ model_provider: _delegationModelProviderSnake,
24033
+ ...delegationRest
24034
+ } = delegation;
24035
+ if (Object.keys(delegationRest).length > 0) {
24036
+ sanitized.delegation = delegationRest;
24037
+ } else {
24038
+ delete sanitized.delegation;
24039
+ }
24040
+ }
24041
+ return sanitized;
24042
+ }
22808
24043
  function copyHermesCoordinatorCredentialFiles(sourceHome, targetHome) {
22809
24044
  if ((0, import_path6.resolve)(sourceHome) === (0, import_path6.resolve)(targetHome)) return;
22810
24045
  for (const fileName of [".env", "auth.json"]) {
@@ -22962,9 +24197,191 @@ var DaemonCommandRouter = class {
22962
24197
  if (record?.meta?.meshNodeId === nodeId) return true;
22963
24198
  return false;
22964
24199
  }
24200
+ async cleanupLocalWorktreeNode(args) {
24201
+ const workspace = typeof args.node?.workspace === "string" ? args.node.workspace.trim() : "";
24202
+ if (!workspace) {
24203
+ return {
24204
+ success: false,
24205
+ code: "mesh_worktree_cleanup_missing_workspace",
24206
+ error: `Worktree node '${args.nodeId}' is missing workspace metadata`,
24207
+ recoveryHint: "Inspect the mesh node record before removing it, or remove stale metadata manually only after confirming no managed worktree remains."
24208
+ };
24209
+ }
24210
+ const worktreeExists = fs10.existsSync(workspace);
24211
+ const sourceNode = args.node?.clonedFromNodeId ? args.mesh?.nodes?.find((n) => n.id === args.node.clonedFromNodeId || n.nodeId === args.node.clonedFromNodeId) : args.mesh?.nodes?.find((n) => !n.isLocalWorktree);
24212
+ const repoRoot = typeof sourceNode?.repoRoot === "string" && sourceNode.repoRoot.trim() ? sourceNode.repoRoot.trim() : typeof sourceNode?.workspace === "string" && sourceNode.workspace.trim() ? sourceNode.workspace.trim() : "";
24213
+ if (!worktreeExists) {
24214
+ return { success: true, skipped: true, removedPath: workspace, repoRoot: repoRoot || void 0, reason: "worktree_path_missing" };
24215
+ }
24216
+ if (!repoRoot || !fs10.existsSync(repoRoot)) {
24217
+ return {
24218
+ success: false,
24219
+ code: "mesh_worktree_cleanup_missing_source_repo",
24220
+ error: `Refusing to remove worktree '${workspace}' because the source repo root is unavailable`,
24221
+ recoveryHint: "Run mesh_remove_node from the machine that owns the source repo, or verify the source node metadata before retrying."
24222
+ };
24223
+ }
24224
+ if (typeof args.node?.worktreeBranch !== "string" || !args.node.worktreeBranch.trim()) {
24225
+ return {
24226
+ success: false,
24227
+ code: "mesh_worktree_cleanup_missing_branch",
24228
+ error: `Refusing to remove worktree '${workspace}' because worktreeBranch metadata is missing`,
24229
+ recoveryHint: "Confirm this is an ADHDev-managed worktree before removing it manually; managed worktree nodes include worktreeBranch metadata."
24230
+ };
24231
+ }
24232
+ const { resolveWorktreePath: resolveWorktreePath2, listWorktrees: listWorktrees2, removeWorktree: removeWorktree2 } = await Promise.resolve().then(() => (init_git_worktree(), git_worktree_exports));
24233
+ const normalizePath = (value) => {
24234
+ const resolved = (0, import_path6.resolve)(value);
24235
+ try {
24236
+ return fs10.realpathSync(resolved);
24237
+ } catch {
24238
+ return resolved;
24239
+ }
24240
+ };
24241
+ const expectedPath = normalizePath(resolveWorktreePath2(repoRoot, String(args.mesh?.name || args.mesh?.id || "mesh"), args.node.worktreeBranch));
24242
+ const actualPath = normalizePath(workspace);
24243
+ if (actualPath !== expectedPath) {
24244
+ return {
24245
+ success: false,
24246
+ code: "mesh_worktree_cleanup_unexpected_path",
24247
+ error: `Refusing to remove worktree '${workspace}' because it is not at the expected managed path '${expectedPath}'`,
24248
+ recoveryHint: "Use git worktree list/status to inspect the path. Retry only after confirming the mesh node metadata points to an ADHDev-managed worktree."
24249
+ };
24250
+ }
24251
+ const entries = await listWorktrees2(repoRoot);
24252
+ const managedEntry = entries.find((entry) => normalizePath(entry.path) === actualPath);
24253
+ if (!managedEntry) {
24254
+ return {
24255
+ success: false,
24256
+ code: "mesh_worktree_cleanup_not_registered",
24257
+ error: `Refusing to remove '${workspace}' because it is not registered in git worktree list for '${repoRoot}'`,
24258
+ recoveryHint: "Inspect git worktree list --porcelain from the source repo. If the path was already removed, prune git worktrees before retrying."
24259
+ };
24260
+ }
24261
+ if (managedEntry.branch && managedEntry.branch !== args.node.worktreeBranch) {
24262
+ return {
24263
+ success: false,
24264
+ code: "mesh_worktree_cleanup_branch_mismatch",
24265
+ error: `Refusing to remove '${workspace}' because git reports branch '${managedEntry.branch}', expected '${args.node.worktreeBranch}'`,
24266
+ recoveryHint: "Inspect the worktree branch and mesh metadata before retrying cleanup."
24267
+ };
24268
+ }
24269
+ const forceFallbackConvergence = await this.getWorktreeForceCleanupConvergence({
24270
+ repoRoot,
24271
+ workspace,
24272
+ node: args.node
24273
+ });
24274
+ try {
24275
+ const result = await removeWorktree2(repoRoot, workspace, {
24276
+ requireClean: true,
24277
+ allowSubmoduleForceFallback: forceFallbackConvergence.allow
24278
+ });
24279
+ return {
24280
+ success: true,
24281
+ removedPath: result.removedPath,
24282
+ repoRoot,
24283
+ ...result.fallback ? {
24284
+ fallback: result.fallback,
24285
+ forced: result.forced,
24286
+ reason: result.reason,
24287
+ convergence: forceFallbackConvergence
24288
+ } : {}
24289
+ };
24290
+ } catch (e) {
24291
+ const message = String(e?.message || e || "worktree cleanup failed");
24292
+ const dirty = message.includes("dirty worktree") || message.includes("local changes");
24293
+ const submoduleForceBlocked = /working trees containing submodules cannot be moved or removed/i.test(message) && !forceFallbackConvergence.allow;
24294
+ return {
24295
+ success: false,
24296
+ code: dirty ? "mesh_worktree_cleanup_dirty" : submoduleForceBlocked ? "mesh_worktree_cleanup_force_fallback_blocked" : "mesh_worktree_cleanup_failed",
24297
+ error: submoduleForceBlocked ? `${message}; refusing --force fallback because convergence could not be verified: ${forceFallbackConvergence.error || "unknown convergence state"}` : message,
24298
+ recoveryHint: dirty ? "Commit, stash, or intentionally discard the worktree changes before retrying mesh_remove_node. The mesh registry entry is preserved until cleanup is safe." : submoduleForceBlocked ? "Verify the worktree branch is merged/contained in the source default branch (for example origin/main) or mark the node with a safe branchConvergence final state before retrying. The mesh registry entry is preserved." : "Inspect git worktree status/list from the source repo and retry after resolving the reported cleanup failure.",
24299
+ ...submoduleForceBlocked ? { convergence: forceFallbackConvergence } : {}
24300
+ };
24301
+ }
24302
+ }
24303
+ async getWorktreeForceCleanupConvergence(args) {
24304
+ const metadataStatus = typeof args.node?.branchConvergence?.status === "string" ? args.node.branchConvergence.status : "";
24305
+ if (metadataStatus === "merged_to_main" || metadataStatus === "cleanup_candidate") {
24306
+ return { allow: true, status: metadataStatus, source: "node_branch_convergence" };
24307
+ }
24308
+ const { execFile: execFile3 } = await import("child_process");
24309
+ const { promisify: promisify3 } = await import("util");
24310
+ const execFileAsync3 = promisify3(execFile3);
24311
+ const runGit2 = async (gitArgs, cwd) => {
24312
+ const { stdout } = await execFileAsync3("git", gitArgs, {
24313
+ cwd,
24314
+ encoding: "utf8",
24315
+ timeout: 3e4,
24316
+ maxBuffer: 4 * 1024 * 1024,
24317
+ windowsHide: true
24318
+ });
24319
+ return String(stdout || "").trim();
24320
+ };
24321
+ let head = "";
24322
+ try {
24323
+ head = await runGit2(["rev-parse", "HEAD"], args.workspace);
24324
+ } catch (e) {
24325
+ return { allow: false, error: `could not resolve worktree HEAD: ${e?.message || e}` };
24326
+ }
24327
+ if (!head) return { allow: false, error: "worktree HEAD is empty" };
24328
+ const candidateRefs = [];
24329
+ try {
24330
+ const defaultBranch = await runGit2(["branch", "--show-current"], args.repoRoot);
24331
+ if (defaultBranch) {
24332
+ candidateRefs.push(defaultBranch, `origin/${defaultBranch}`);
24333
+ }
24334
+ } catch {
24335
+ }
24336
+ candidateRefs.push("origin/main", "origin/master", "main", "master");
24337
+ const seen = /* @__PURE__ */ new Set();
24338
+ const checkedRefs = [];
24339
+ for (const ref of candidateRefs) {
24340
+ if (!ref || seen.has(ref)) continue;
24341
+ seen.add(ref);
24342
+ let commit = "";
24343
+ try {
24344
+ commit = await runGit2(["rev-parse", "--verify", `${ref}^{commit}`], args.repoRoot);
24345
+ } catch {
24346
+ continue;
24347
+ }
24348
+ checkedRefs.push(ref);
24349
+ try {
24350
+ await runGit2(["merge-base", "--is-ancestor", head, commit], args.repoRoot);
24351
+ return { allow: true, status: "merged_to_default_ref", source: "git_merge_base", ref };
24352
+ } catch {
24353
+ }
24354
+ }
24355
+ return {
24356
+ allow: false,
24357
+ status: metadataStatus || void 0,
24358
+ error: checkedRefs.length ? `worktree HEAD is not contained in checked refs: ${checkedRefs.join(", ")}` : "no default/main refs were available for convergence verification"
24359
+ };
24360
+ }
22965
24361
  isCompletedHostedSession(record) {
22966
24362
  return record?.lifecycle === "stopped" || record?.lifecycle === "failed" || record?.lifecycle === "interrupted";
22967
24363
  }
24364
+ async recordIntentionalMeshSessionStop(args) {
24365
+ try {
24366
+ const { appendLedgerEntry: appendLedgerEntry2 } = await Promise.resolve().then(() => (init_mesh_ledger(), mesh_ledger_exports));
24367
+ appendLedgerEntry2(args.meshId, {
24368
+ kind: "session_stopped",
24369
+ nodeId: args.nodeId,
24370
+ sessionId: args.sessionId,
24371
+ payload: {
24372
+ intentional: true,
24373
+ reason: "operator_cleanup",
24374
+ intentionalStopReason: "operator_cleanup",
24375
+ source: args.source,
24376
+ cleanupMode: args.mode,
24377
+ action: args.action,
24378
+ workspace: typeof args.node?.workspace === "string" ? args.node.workspace : void 0
24379
+ }
24380
+ });
24381
+ } catch (e) {
24382
+ LOG.warn("MeshCleanup", `Failed to record intentional cleanup stop for ${args.sessionId}: ${e?.message || e}`);
24383
+ }
24384
+ }
22968
24385
  async cleanupMeshSessions(args) {
22969
24386
  if (args.mode === "preserve") {
22970
24387
  return { success: true, mode: "preserve", matchedCount: 0, stoppedSessionIds: [], deletedSessionIds: [], skippedSessionIds: [] };
@@ -22981,6 +24398,21 @@ var DaemonCommandRouter = class {
22981
24398
  const deleteUnsupportedSessionIds = [];
22982
24399
  const recordsRemainSessionIds = [];
22983
24400
  const errors = [];
24401
+ const cleanupSource = args.source || "mesh_cleanup_sessions";
24402
+ const markedIntentionalStopSessionIds = /* @__PURE__ */ new Set();
24403
+ const markIntentionalStop = async (sessionId, action) => {
24404
+ if (args.dryRun || markedIntentionalStopSessionIds.has(sessionId)) return;
24405
+ markedIntentionalStopSessionIds.add(sessionId);
24406
+ await this.recordIntentionalMeshSessionStop({
24407
+ meshId: args.meshId,
24408
+ nodeId: args.nodeId,
24409
+ node: args.node,
24410
+ sessionId,
24411
+ mode: args.mode,
24412
+ source: cleanupSource,
24413
+ action
24414
+ });
24415
+ };
22984
24416
  const matchedBySurfaceKind = {
22985
24417
  live_runtime: 0,
22986
24418
  recovery_snapshot: 0,
@@ -23003,7 +24435,10 @@ var DaemonCommandRouter = class {
23003
24435
  try {
23004
24436
  if (args.mode === "stop") {
23005
24437
  if (!completed) {
23006
- if (!args.dryRun) await this.deps.sessionHostControl.stopSession(sessionId);
24438
+ if (!args.dryRun) {
24439
+ await markIntentionalStop(sessionId, "stop_session");
24440
+ await this.deps.sessionHostControl.stopSession(sessionId);
24441
+ }
23007
24442
  stoppedSessionIds.push(sessionId);
23008
24443
  } else {
23009
24444
  skippedSessionIds.push(sessionId);
@@ -23020,6 +24455,7 @@ var DaemonCommandRouter = class {
23020
24455
  continue;
23021
24456
  }
23022
24457
  if (args.mode === "stop_and_delete") {
24458
+ if (!completed) await markIntentionalStop(sessionId, "delete_session_force");
23023
24459
  if (!args.dryRun) await this.deps.sessionHostControl.deleteSession(sessionId, { force: true });
23024
24460
  deletedSessionIds.push(sessionId);
23025
24461
  continue;
@@ -23031,6 +24467,7 @@ var DaemonCommandRouter = class {
23031
24467
  recordsRemainSessionIds.push(sessionId);
23032
24468
  if (args.mode === "stop_and_delete" && !completed) {
23033
24469
  try {
24470
+ await markIntentionalStop(sessionId, "stop_session");
23034
24471
  await this.deps.sessionHostControl.stopSession(sessionId);
23035
24472
  stoppedSessionIds.push(sessionId);
23036
24473
  } catch (stopError) {
@@ -23785,6 +25222,91 @@ var DaemonCommandRouter = class {
23785
25222
  return { success: false, error: e.message };
23786
25223
  }
23787
25224
  }
25225
+ case "get_mesh_ledger_slice": {
25226
+ const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
25227
+ if (!meshId) return { success: false, error: "meshId required" };
25228
+ try {
25229
+ const { readLedgerSlice: readLedgerSlice2 } = await Promise.resolve().then(() => (init_mesh_ledger(), mesh_ledger_exports));
25230
+ const kind = Array.isArray(args?.kind) ? args.kind.filter((k) => typeof k === "string") : void 0;
25231
+ const slice = readLedgerSlice2(meshId, {
25232
+ afterId: typeof args?.afterId === "string" ? args.afterId : void 0,
25233
+ since: typeof args?.since === "string" ? args.since : void 0,
25234
+ kind,
25235
+ limit: typeof args?.limit === "number" ? args.limit : void 0
25236
+ });
25237
+ return { success: true, slice };
25238
+ } catch (e) {
25239
+ return { success: false, error: e.message };
25240
+ }
25241
+ }
25242
+ case "import_mesh_ledger_slice": {
25243
+ const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
25244
+ if (!meshId) return { success: false, error: "meshId required" };
25245
+ try {
25246
+ const { appendRemoteLedgerEntries: appendRemoteLedgerEntries2, getLedgerSummary: getLedgerSummary2 } = await Promise.resolve().then(() => (init_mesh_ledger(), mesh_ledger_exports));
25247
+ const entries = Array.isArray(args?.entries) ? args.entries : Array.isArray(args?.slice?.entries) ? args.slice.entries : [];
25248
+ const result = appendRemoteLedgerEntries2(meshId, entries);
25249
+ return { success: true, result, summary: getLedgerSummary2(meshId) };
25250
+ } catch (e) {
25251
+ return { success: false, error: e.message };
25252
+ }
25253
+ }
25254
+ case "get_mesh_queue": {
25255
+ const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
25256
+ if (!meshId) return { success: false, error: "meshId required" };
25257
+ try {
25258
+ const { getMeshQueueStats: getMeshQueueStats2, getQueue: getQueue2 } = await Promise.resolve().then(() => (init_mesh_work_queue(), mesh_work_queue_exports));
25259
+ const status = Array.isArray(args?.status) ? args.status.map((s) => typeof s === "string" ? s.trim() : "").filter(Boolean) : void 0;
25260
+ const queue = getQueue2(meshId, { status });
25261
+ const summary = getMeshQueueStats2(meshId);
25262
+ return {
25263
+ success: true,
25264
+ queue,
25265
+ summary,
25266
+ sourceOfTruth: {
25267
+ kind: "mesh_work_queue_file",
25268
+ activeStatuses: ["pending", "assigned"],
25269
+ historicalStatuses: ["completed", "failed", "cancelled"],
25270
+ notes: "pending/assigned are active work; completed/failed/cancelled are historical records."
25271
+ }
25272
+ };
25273
+ } catch (e) {
25274
+ return { success: false, error: e.message };
25275
+ }
25276
+ }
25277
+ case "cancel_mesh_queue_task": {
25278
+ const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
25279
+ const taskId = typeof args?.taskId === "string" ? args.taskId.trim() : "";
25280
+ if (!meshId || !taskId) return { success: false, error: "meshId and taskId required" };
25281
+ try {
25282
+ const { cancelTask: cancelTask2 } = await Promise.resolve().then(() => (init_mesh_work_queue(), mesh_work_queue_exports));
25283
+ const reason = typeof args?.reason === "string" ? args.reason : void 0;
25284
+ const task = cancelTask2(meshId, taskId, { reason });
25285
+ if (!task) return { success: false, error: `Queue task '${taskId}' not found` };
25286
+ return { success: true, task };
25287
+ } catch (e) {
25288
+ return { success: false, error: e.message };
25289
+ }
25290
+ }
25291
+ case "requeue_mesh_queue_task": {
25292
+ const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
25293
+ const taskId = typeof args?.taskId === "string" ? args.taskId.trim() : "";
25294
+ if (!meshId || !taskId) return { success: false, error: "meshId and taskId required" };
25295
+ try {
25296
+ const { requeueTask: requeueTask2 } = await Promise.resolve().then(() => (init_mesh_work_queue(), mesh_work_queue_exports));
25297
+ const task = requeueTask2(meshId, taskId, {
25298
+ reason: typeof args?.reason === "string" ? args.reason : void 0,
25299
+ targetNodeId: typeof args?.targetNodeId === "string" ? args.targetNodeId.trim() : void 0,
25300
+ targetSessionId: typeof args?.targetSessionId === "string" ? args.targetSessionId.trim() : void 0,
25301
+ clearTargetNode: args?.clearTargetNode === true,
25302
+ clearTargetSession: args?.clearTargetSession !== false
25303
+ });
25304
+ if (!task) return { success: false, error: `Queue task '${taskId}' not found` };
25305
+ return { success: true, task };
25306
+ } catch (e) {
25307
+ return { success: false, error: e.message };
25308
+ }
25309
+ }
23788
25310
  case "add_mesh_node": {
23789
25311
  const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
23790
25312
  const workspace = typeof args?.workspace === "string" ? args.workspace.trim() : "";
@@ -23846,7 +25368,8 @@ var DaemonCommandRouter = class {
23846
25368
  node,
23847
25369
  mode,
23848
25370
  sessionIds,
23849
- dryRun: args?.dryRun === true
25371
+ dryRun: args?.dryRun === true,
25372
+ source: "mesh_cleanup_sessions"
23850
25373
  });
23851
25374
  return result;
23852
25375
  } catch (e) {
@@ -23876,10 +25399,61 @@ var DaemonCommandRouter = class {
23876
25399
  if (!branch) return { success: false, error: "Could not determine branch of the worktree node" };
23877
25400
  const { stdout: baseBranchStdout } = await execFileAsync3("git", ["branch", "--show-current"], { cwd: repoRoot, encoding: "utf8" });
23878
25401
  const baseBranch = baseBranchStdout.trim();
25402
+ const validationSummary = await runMeshRefineValidationGate(mesh, node.workspace);
25403
+ if (validationSummary.status === "failed") {
25404
+ return {
25405
+ success: false,
25406
+ code: "validation_failed",
25407
+ convergenceStatus: "blocked_review",
25408
+ error: "Refinery validation gate failed; merge/refine was not attempted.",
25409
+ branch,
25410
+ into: baseBranch,
25411
+ validationSummary,
25412
+ finalBranchConvergenceState: {
25413
+ branch,
25414
+ baseBranch,
25415
+ merged: false,
25416
+ removed: false,
25417
+ validation: "failed",
25418
+ status: "blocked_review"
25419
+ }
25420
+ };
25421
+ }
25422
+ if (validationSummary.status === "skipped") {
25423
+ return {
25424
+ success: false,
25425
+ code: "validation_unavailable",
25426
+ convergenceStatus: "blocked_review",
25427
+ error: "Refinery validation gate is required but no allowlisted validation command was available; merge/refine was not attempted.",
25428
+ branch,
25429
+ into: baseBranch,
25430
+ validationSummary,
25431
+ finalBranchConvergenceState: {
25432
+ branch,
25433
+ baseBranch,
25434
+ merged: false,
25435
+ removed: false,
25436
+ validation: "unavailable",
25437
+ status: "blocked_review"
25438
+ }
25439
+ };
25440
+ }
23879
25441
  try {
23880
25442
  await execFileAsync3("git", ["merge", "--no-ff", branch, "-m", `Auto-merge branch '${branch}' via Refinery`], { cwd: repoRoot, encoding: "utf8" });
23881
25443
  } catch (e) {
23882
- return { success: false, error: `Merge failed (conflicts?): ${e.message}` };
25444
+ return {
25445
+ success: false,
25446
+ error: `Merge failed (conflicts?): ${e.message}`,
25447
+ validationSummary,
25448
+ finalBranchConvergenceState: {
25449
+ branch,
25450
+ baseBranch,
25451
+ merged: false,
25452
+ removed: false,
25453
+ validation: "passed",
25454
+ status: "not_mergeable"
25455
+ }
25456
+ };
23883
25457
  }
23884
25458
  const removeResult = await this.execute("remove_mesh_node", {
23885
25459
  meshId,
@@ -23892,11 +25466,27 @@ var DaemonCommandRouter = class {
23892
25466
  appendLedgerEntry2(meshId, {
23893
25467
  kind: "node_removed",
23894
25468
  nodeId,
23895
- payload: { refined: true, mergedBranch: branch, into: baseBranch }
25469
+ payload: { refined: true, mergedBranch: branch, into: baseBranch, validationSummary }
23896
25470
  });
23897
25471
  } catch {
23898
25472
  }
23899
- return { success: true, merged: true, branch, into: baseBranch, removeResult };
25473
+ return {
25474
+ success: true,
25475
+ merged: true,
25476
+ branch,
25477
+ into: baseBranch,
25478
+ removeResult,
25479
+ validationSummary,
25480
+ finalBranchConvergenceState: {
25481
+ branch: baseBranch,
25482
+ mergedBranch: branch,
25483
+ baseBranch,
25484
+ merged: true,
25485
+ removed: removeResult?.success !== false,
25486
+ validation: "passed",
25487
+ status: removeResult?.success === false ? "merged_cleanup_failed" : "merged"
25488
+ }
25489
+ };
23900
25490
  } catch (e) {
23901
25491
  return { success: false, error: e.message };
23902
25492
  }
@@ -23914,20 +25504,24 @@ var DaemonCommandRouter = class {
23914
25504
  );
23915
25505
  let sessionCleanup;
23916
25506
  if (node && sessionCleanupMode !== "preserve") {
23917
- sessionCleanup = await this.cleanupMeshSessions({ meshId, nodeId, node, mode: sessionCleanupMode });
25507
+ sessionCleanup = await this.cleanupMeshSessions({ meshId, nodeId, node, mode: sessionCleanupMode, source: "mesh_remove_node" });
23918
25508
  if (sessionCleanup.success === false) return { success: false, removed: false, sessionCleanup };
23919
25509
  }
23920
- if (node?.isLocalWorktree && node.workspace) {
23921
- try {
23922
- const sourceNode = node.clonedFromNodeId ? mesh?.nodes.find((n) => n.id === node.clonedFromNodeId || n.nodeId === node.clonedFromNodeId) : mesh?.nodes.find((n) => !n.isLocalWorktree);
23923
- const repoRoot = sourceNode?.repoRoot || sourceNode?.workspace;
23924
- if (repoRoot) {
23925
- const { removeWorktree: removeWorktree2 } = await Promise.resolve().then(() => (init_git_worktree(), git_worktree_exports));
23926
- await removeWorktree2(repoRoot, node.workspace);
23927
- }
23928
- } catch (e) {
23929
- LOG.warn("MeshNode", `Worktree cleanup failed for ${nodeId}: ${e.message}`);
25510
+ let worktreeCleanup;
25511
+ if (node?.isLocalWorktree) {
25512
+ const cleanupResult = await this.cleanupLocalWorktreeNode({ mesh, node, nodeId });
25513
+ if (cleanupResult.success === false) {
25514
+ return {
25515
+ success: false,
25516
+ removed: false,
25517
+ code: cleanupResult.code,
25518
+ error: cleanupResult.error,
25519
+ recoveryHint: cleanupResult.recoveryHint,
25520
+ ...sessionCleanup ? { sessionCleanup } : {},
25521
+ worktreeCleanup: cleanupResult
25522
+ };
23930
25523
  }
25524
+ worktreeCleanup = cleanupResult;
23931
25525
  }
23932
25526
  let removed = false;
23933
25527
  if (meshRecord?.inline) {
@@ -23942,12 +25536,21 @@ var DaemonCommandRouter = class {
23942
25536
  appendLedgerEntry2(meshId, {
23943
25537
  kind: "node_removed",
23944
25538
  nodeId,
23945
- payload: { worktree: !!node?.isLocalWorktree, sessionCleanupMode }
25539
+ payload: {
25540
+ worktree: !!node?.isLocalWorktree,
25541
+ sessionCleanupMode,
25542
+ workspace: typeof node?.workspace === "string" ? node.workspace : void 0,
25543
+ daemonId: typeof node?.daemonId === "string" ? node.daemonId : void 0,
25544
+ worktreeBranch: typeof node?.worktreeBranch === "string" ? node.worktreeBranch : void 0,
25545
+ worktreeCleanupFallback: typeof worktreeCleanup?.fallback === "string" ? worktreeCleanup.fallback : void 0,
25546
+ forced: worktreeCleanup?.forced === true ? true : void 0,
25547
+ forceFallbackReason: typeof worktreeCleanup?.reason === "string" ? worktreeCleanup.reason : void 0
25548
+ }
23946
25549
  });
23947
25550
  } catch {
23948
25551
  }
23949
25552
  }
23950
- return { success: true, removed, ...sessionCleanup ? { sessionCleanup } : {} };
25553
+ return { success: true, removed, ...sessionCleanup ? { sessionCleanup } : {}, ...worktreeCleanup ? { worktreeCleanup } : {} };
23951
25554
  } catch (e) {
23952
25555
  return { success: false, error: e.message };
23953
25556
  }
@@ -23982,6 +25585,7 @@ var DaemonCommandRouter = class {
23982
25585
  workspace: result.worktreePath,
23983
25586
  repoRoot: result.worktreePath,
23984
25587
  daemonId: sourceNode.daemonId,
25588
+ machineId: sourceNode.machineId ?? sourceNode.machine_id,
23985
25589
  userOverrides: { ...sourceNode.userOverrides || {} },
23986
25590
  policy: { ...sourceNode.policy || {} },
23987
25591
  isLocalWorktree: true,
@@ -23995,6 +25599,7 @@ var DaemonCommandRouter = class {
23995
25599
  workspace: result.worktreePath,
23996
25600
  repoRoot: result.worktreePath,
23997
25601
  daemonId: sourceNode.daemonId,
25602
+ machineId: sourceNode.machineId ?? sourceNode.machine_id,
23998
25603
  userOverrides: { ...sourceNode.userOverrides || {} },
23999
25604
  isLocalWorktree: true,
24000
25605
  worktreeBranch: result.branch,
@@ -24113,6 +25718,93 @@ var DaemonCommandRouter = class {
24113
25718
  meshCoordinatorSetup: coordinatorSetup
24114
25719
  };
24115
25720
  }
25721
+ if (coordinatorSetup.kind === "cli_command") {
25722
+ let cliCmdSystemPrompt = "";
25723
+ try {
25724
+ cliCmdSystemPrompt = buildCoordinatorSystemPrompt2({ mesh, coordinatorCliType: cliType });
25725
+ } catch (error) {
25726
+ const message = error?.message || String(error);
25727
+ LOG.error("MeshCoordinator", `Failed to build coordinator prompt: ${message}`);
25728
+ return {
25729
+ success: false,
25730
+ code: "mesh_coordinator_prompt_failed",
25731
+ error: `Failed to build Repo Mesh coordinator prompt: ${message}`,
25732
+ meshId,
25733
+ cliType,
25734
+ workspace
25735
+ };
25736
+ }
25737
+ try {
25738
+ const { execFileSync: execCmdSync } = await import("child_process");
25739
+ const cmdParts = coordinatorSetup.command.trim().split(/\s+/);
25740
+ const [regCmd, ...regArgs] = cmdParts;
25741
+ LOG.info("MeshCoordinator", `Running MCP registration: ${coordinatorSetup.command}`);
25742
+ execCmdSync(regCmd, regArgs, { stdio: "pipe", timeout: 15e3 });
25743
+ } catch (error) {
25744
+ LOG.warn("MeshCoordinator", `MCP registration command failed (may be pre-registered): ${error?.message || error}`);
25745
+ }
25746
+ const cliCmdArgs = [];
25747
+ const cliCmdEnv = {};
25748
+ if (cliCmdSystemPrompt) {
25749
+ if (cliType === "codex-cli") {
25750
+ cliCmdArgs.push("-c", `developer_instructions=${JSON.stringify(cliCmdSystemPrompt)}`);
25751
+ } else if (cliType === "gemini-cli") {
25752
+ try {
25753
+ const { writeFileSync: wfs, existsSync: efs, readFileSync: rfs } = await import("fs");
25754
+ const geminiMdPath = `${workspace}/GEMINI.md`;
25755
+ const marker = "<!-- adhdev-mesh-coordinator-prompt -->";
25756
+ const markerEnd = "<!-- /adhdev-mesh-coordinator-prompt -->";
25757
+ const block = `${marker}
25758
+ ${cliCmdSystemPrompt}
25759
+ ${markerEnd}`;
25760
+ if (efs(geminiMdPath)) {
25761
+ const existing = rfs(geminiMdPath, "utf-8");
25762
+ const replaced = existing.replace(
25763
+ new RegExp(`${marker}[\\s\\S]*?${markerEnd}`, "g"),
25764
+ block
25765
+ );
25766
+ wfs(geminiMdPath, replaced.includes(marker) ? replaced : `${existing}
25767
+
25768
+ ${block}`);
25769
+ } else {
25770
+ wfs(geminiMdPath, block);
25771
+ }
25772
+ LOG.info("MeshCoordinator", `Wrote coordinator prompt to ${workspace}/GEMINI.md`);
25773
+ } catch (e) {
25774
+ LOG.warn("MeshCoordinator", `Could not write GEMINI.md: ${e?.message || e}`);
25775
+ }
25776
+ }
25777
+ }
25778
+ const cliCmdLaunch = await this.deps.cliManager.handleCliCommand("launch_cli", {
25779
+ cliType,
25780
+ dir: workspace,
25781
+ cliArgs: cliCmdArgs.length > 0 ? cliCmdArgs : void 0,
25782
+ env: Object.keys(cliCmdEnv).length > 0 ? cliCmdEnv : void 0,
25783
+ settings: { meshCoordinatorFor: meshId }
25784
+ });
25785
+ if (!cliCmdLaunch?.success) {
25786
+ return { success: false, error: cliCmdLaunch?.error || "Failed to launch CLI session" };
25787
+ }
25788
+ LOG.info("MeshCoordinator", `Launched ${cliType} coordinator (cli_command) for mesh ${meshId}`);
25789
+ try {
25790
+ const { appendLedgerEntry: appendLedgerEntry2 } = await Promise.resolve().then(() => (init_mesh_ledger(), mesh_ledger_exports));
25791
+ appendLedgerEntry2(meshId, {
25792
+ kind: "coordinator_started",
25793
+ sessionId: cliCmdLaunch.sessionId || cliCmdLaunch.id,
25794
+ providerType: cliType,
25795
+ payload: { workspace }
25796
+ });
25797
+ } catch {
25798
+ }
25799
+ return {
25800
+ success: true,
25801
+ meshId,
25802
+ cliType,
25803
+ workspace,
25804
+ sessionId: cliCmdLaunch.sessionId || cliCmdLaunch.id,
25805
+ mcpRegistered: true
25806
+ };
25807
+ }
24116
25808
  const configFormat = coordinatorSetup.configFormat;
24117
25809
  if (configFormat !== "claude_mcp_json" && configFormat !== "hermes_config_yaml") {
24118
25810
  return {
@@ -24167,9 +25859,11 @@ var DaemonCommandRouter = class {
24167
25859
  args: coordinatorSetup.mcpServer.args
24168
25860
  };
24169
25861
  if (args?.inlineMesh) {
25862
+ const modeArgIndex = coordinatorSetup.mcpServer.args.findIndex((value) => value === "--mode");
25863
+ const mcpTransport = modeArgIndex >= 0 ? coordinatorSetup.mcpServer.args[modeArgIndex + 1] : "ipc";
24170
25864
  mcpServerEntry.env = {
24171
25865
  ADHDEV_INLINE_MESH: JSON.stringify(mesh),
24172
- ADHDEV_MCP_TRANSPORT: "ipc"
25866
+ ADHDEV_MCP_TRANSPORT: mcpTransport === "local" ? "local" : "ipc"
24173
25867
  };
24174
25868
  }
24175
25869
  try {
@@ -24188,7 +25882,8 @@ var DaemonCommandRouter = class {
24188
25882
  if (hadExistingMcpConfig) {
24189
25883
  try {
24190
25884
  const parsedExistingMcpConfig = parseMeshCoordinatorMcpConfig(readFileSync17(mcpConfigPath, "utf-8"), configFormat);
24191
- existingMcpConfig = { ...existingMcpConfig, ...parsedExistingMcpConfig };
25885
+ const existingCoordinatorConfig = hermesManualFallback ? stripHermesCoordinatorTempModelProviderOverrides(parsedExistingMcpConfig) : parsedExistingMcpConfig;
25886
+ existingMcpConfig = { ...existingMcpConfig, ...existingCoordinatorConfig };
24192
25887
  copyFileSync4(mcpConfigPath, mcpConfigPath + ".backup");
24193
25888
  } catch (error) {
24194
25889
  LOG.error("MeshCoordinator", `Failed to parse existing MCP config ${mcpConfigPath}: ${error?.message || error}`);
@@ -24269,6 +25964,69 @@ var DaemonCommandRouter = class {
24269
25964
  return { success: false, error: e.message };
24270
25965
  }
24271
25966
  }
25967
+ case "mesh_status": {
25968
+ const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
25969
+ if (!meshId) return { success: false, error: "meshId required" };
25970
+ try {
25971
+ const meshRecord = await this.getMeshForCommand(meshId, args?.inlineMesh);
25972
+ const mesh = meshRecord?.mesh;
25973
+ if (!mesh) return { success: false, error: "Mesh not found" };
25974
+ const { getMeshQueueStats: getMeshQueueStats2, getQueue: getQueue2 } = await Promise.resolve().then(() => (init_mesh_work_queue(), mesh_work_queue_exports));
25975
+ const queue = getQueue2(meshId);
25976
+ const queueSummary = getMeshQueueStats2(meshId);
25977
+ const { readLedgerEntries: readLedgerEntries2, getLedgerSummary: getLedgerSummary2 } = await Promise.resolve().then(() => (init_mesh_ledger(), mesh_ledger_exports));
25978
+ const ledgerEntries = readLedgerEntries2(meshId, { tail: 20 });
25979
+ const ledgerSummary = getLedgerSummary2(meshId);
25980
+ const nodeStatuses = [];
25981
+ for (const node of mesh.nodes || []) {
25982
+ const status = {
25983
+ nodeId: node.id || node.nodeId,
25984
+ workspace: node.workspace,
25985
+ repoRoot: node.repoRoot,
25986
+ isLocalWorktree: node.isLocalWorktree,
25987
+ worktreeBranch: node.worktreeBranch,
25988
+ daemonId: node.daemonId,
25989
+ machineId: node.machineId,
25990
+ health: "unknown"
25991
+ };
25992
+ if (node.workspace && typeof node.workspace === "string") {
25993
+ try {
25994
+ const { execFile: execFile3 } = await import("child_process");
25995
+ const { promisify: promisify3 } = await import("util");
25996
+ const execFileAsync3 = promisify3(execFile3);
25997
+ const branch = await execFileAsync3("git", ["-C", node.workspace, "branch", "--show-current"], {
25998
+ encoding: "utf8",
25999
+ timeout: 1e4
26000
+ }).then((r) => r.stdout.trim()).catch(() => "");
26001
+ const porc = await execFileAsync3("git", ["-C", node.workspace, "status", "--porcelain"], {
26002
+ encoding: "utf8",
26003
+ timeout: 1e4
26004
+ }).then((r) => r.stdout.trim()).catch(() => "");
26005
+ const dirty = porc.length > 0;
26006
+ status.branch = branch;
26007
+ status.isDirty = dirty;
26008
+ status.uncommittedChanges = porc ? porc.split("\n").filter(Boolean).length : 0;
26009
+ status.health = branch ? dirty ? "dirty" : "online" : "degraded";
26010
+ } catch {
26011
+ status.health = "degraded";
26012
+ }
26013
+ }
26014
+ nodeStatuses.push(status);
26015
+ }
26016
+ return {
26017
+ success: true,
26018
+ meshId: mesh.id,
26019
+ meshName: mesh.name,
26020
+ repoIdentity: mesh.repoIdentity,
26021
+ defaultBranch: mesh.defaultBranch,
26022
+ nodes: nodeStatuses,
26023
+ queue: { tasks: queue, summary: queueSummary },
26024
+ ledger: { entries: ledgerEntries, summary: ledgerSummary }
26025
+ };
26026
+ } catch (e) {
26027
+ return { success: false, error: e.message };
26028
+ }
26029
+ }
24272
26030
  default:
24273
26031
  break;
24274
26032
  }
@@ -31981,6 +33739,9 @@ function launchIDE(ide, workspacePath) {
31981
33739
  }
31982
33740
  }
31983
33741
 
33742
+ // src/boot/daemon-lifecycle.ts
33743
+ init_cli_detector();
33744
+
31984
33745
  // src/sessions/registry.ts
31985
33746
  var SessionRegistry = class {
31986
33747
  bySessionId = /* @__PURE__ */ new Map();
@@ -32211,7 +33972,8 @@ async function initDaemonComponents(config) {
32211
33972
  cdpManagers,
32212
33973
  sessionRegistry,
32213
33974
  detectedIdes: detectedIdesRef,
32214
- refreshProviderAvailability
33975
+ refreshProviderAvailability,
33976
+ dispatchMeshCommand: config.dispatchMeshCommand
32215
33977
  };
32216
33978
  setupMeshEventForwarding(components);
32217
33979
  return components;
@@ -32313,10 +34075,12 @@ async function shutdownDaemonComponents(components) {
32313
34075
  IdeProviderInstance,
32314
34076
  InMemoryGitSnapshotStore,
32315
34077
  LOG,
34078
+ MAX_LEDGER_SLICE_LIMIT,
32316
34079
  MIN_GIT_WORKSPACE_POLL_INTERVAL_MS,
32317
34080
  MIN_MACHINE_RUNTIME_SUBSCRIPTION_INTERVAL_MS,
32318
34081
  MIN_SESSION_HOST_DIAGNOSTICS_SUBSCRIPTION_INTERVAL_MS,
32319
34082
  NodePtyTransportFactory,
34083
+ P2pRelayFailureError,
32320
34084
  ProviderCliAdapter,
32321
34085
  ProviderInstanceManager,
32322
34086
  ProviderLoader,
@@ -32327,12 +34091,16 @@ async function shutdownDaemonComponents(components) {
32327
34091
  addNode,
32328
34092
  appendLedgerEntry,
32329
34093
  appendRecentActivity,
34094
+ appendRemoteLedgerEntries,
32330
34095
  buildAssistantChatMessage,
32331
34096
  buildChatMessage,
32332
34097
  buildChatMessageSignature,
32333
34098
  buildChatTailDeliverySignature,
32334
34099
  buildCoordinatorSystemPrompt,
32335
34100
  buildMachineInfo,
34101
+ buildMeshLedgerReconciliationEvidence,
34102
+ buildMeshLedgerReplicaEvidence,
34103
+ buildP2pRelayFailurePayload,
32336
34104
  buildPinnedGlobalInstallCommand,
32337
34105
  buildRuntimeSystemChatMessage,
32338
34106
  buildSessionEntries,
@@ -32343,10 +34111,13 @@ async function shutdownDaemonComponents(components) {
32343
34111
  buildThoughtChatMessage,
32344
34112
  buildToolChatMessage,
32345
34113
  buildUserChatMessage,
34114
+ cancelTask,
32346
34115
  claimNextTask,
32347
34116
  classifyChatMessageVisibility,
32348
34117
  classifyHotChatSessionsForSubscriptionFlush,
34118
+ classifyP2pRelayFailure,
32349
34119
  clearDebugTrace,
34120
+ clearPendingMeshCoordinatorEvents,
32350
34121
  compareGitSnapshots,
32351
34122
  configureDebugTraceStore,
32352
34123
  connectCdpManager,
@@ -32362,6 +34133,7 @@ async function shutdownDaemonComponents(components) {
32362
34133
  detectAllVersions,
32363
34134
  detectCLIs,
32364
34135
  detectIDEs,
34136
+ drainPendingMeshCoordinatorEvents,
32365
34137
  enqueueTask,
32366
34138
  ensureSessionHostReady,
32367
34139
  execNpmCommandSync,
@@ -32386,7 +34158,9 @@ async function shutdownDaemonComponents(components) {
32386
34158
  getLogLevel,
32387
34159
  getMesh,
32388
34160
  getMeshByRepo,
34161
+ getMeshQueueStats,
32389
34162
  getNpmExecOptions,
34163
+ getPendingMeshCoordinatorEvents,
32390
34164
  getQueue,
32391
34165
  getRecentActivity,
32392
34166
  getRecentCommands,
@@ -32412,6 +34186,7 @@ async function shutdownDaemonComponents(components) {
32412
34186
  isInternalChatMessage,
32413
34187
  isManagedStatusWaiting,
32414
34188
  isManagedStatusWorking,
34189
+ isP2pRelayTransportFailure,
32415
34190
  isPathInside,
32416
34191
  isSessionHostLiveRuntime,
32417
34192
  isSessionHostRecoverySnapshot,
@@ -32450,10 +34225,12 @@ async function shutdownDaemonComponents(components) {
32450
34225
  probeCdpPort,
32451
34226
  readChatHistory,
32452
34227
  readLedgerEntries,
34228
+ readLedgerSlice,
32453
34229
  recordDebugTrace,
32454
34230
  registerExtensionProviders,
32455
34231
  removeNode,
32456
34232
  removeWorktree,
34233
+ requeueTask,
32457
34234
  resetConfig,
32458
34235
  resetDebugRuntimeConfig,
32459
34236
  resetState,