@adhdev/daemon-core 0.9.77-rc.9 → 0.9.77

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