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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/dist/boot/daemon-lifecycle.d.ts +3 -0
  2. package/dist/cli-adapters/provider-cli-adapter.d.ts +4 -0
  3. package/dist/cli-adapters/provider-cli-shared.d.ts +14 -4
  4. package/dist/commands/mesh-coordinator.d.ts +10 -0
  5. package/dist/commands/router.d.ts +4 -1
  6. package/dist/config/mesh-config.d.ts +1 -0
  7. package/dist/git/git-worktree.d.ts +15 -2
  8. package/dist/index.d.ts +10 -6
  9. package/dist/index.js +2076 -299
  10. package/dist/index.js.map +1 -1
  11. package/dist/index.mjs +2061 -299
  12. package/dist/index.mjs.map +1 -1
  13. package/dist/mesh/mesh-events.d.ts +14 -7
  14. package/dist/mesh/mesh-ledger-reconciliation.d.ts +55 -0
  15. package/dist/mesh/mesh-ledger.d.ts +84 -4
  16. package/dist/mesh/mesh-sync.d.ts +4 -12
  17. package/dist/mesh/mesh-visualization.d.ts +70 -0
  18. package/dist/mesh/mesh-work-queue.d.ts +58 -1
  19. package/dist/mesh/p2p-relay-failure.d.ts +35 -0
  20. package/dist/providers/chat-message-normalization.d.ts +1 -0
  21. package/dist/providers/cli-provider-instance.d.ts +6 -0
  22. package/dist/repo-mesh-types.d.ts +2 -0
  23. package/dist/shared-types.d.ts +38 -0
  24. package/package.json +1 -1
  25. package/src/boot/daemon-lifecycle.ts +5 -0
  26. package/src/cli-adapters/provider-cli-adapter.ts +35 -4
  27. package/src/cli-adapters/provider-cli-shared.ts +14 -4
  28. package/src/commands/cli-manager.ts +0 -4
  29. package/src/commands/mesh-coordinator.ts +55 -7
  30. package/src/commands/router.ts +915 -26
  31. package/src/commands/stream-commands.ts +8 -1
  32. package/src/config/config.ts +2 -1
  33. package/src/config/mesh-config.ts +2 -0
  34. package/src/config/workspaces.ts +1 -1
  35. package/src/git/git-worktree.ts +56 -4
  36. package/src/index.d.ts +3 -0
  37. package/src/index.ts +29 -6
  38. package/src/mesh/coordinator-prompt.ts +21 -10
  39. package/src/mesh/mesh-events.ts +532 -22
  40. package/src/mesh/mesh-ledger-reconciliation.ts +115 -0
  41. package/src/mesh/mesh-ledger.ts +209 -8
  42. package/src/mesh/mesh-sync.ts +4 -34
  43. package/src/mesh/mesh-visualization.ts +341 -0
  44. package/src/mesh/mesh-work-queue.ts +183 -17
  45. package/src/mesh/p2p-relay-failure.ts +152 -0
  46. package/src/providers/acp-provider-instance.ts +2 -1
  47. package/src/providers/chat-message-normalization.ts +32 -0
  48. package/src/providers/cli-provider-instance.ts +155 -31
  49. package/src/providers/extension-provider-instance.ts +2 -1
  50. package/src/providers/ide-provider-instance.ts +2 -2
  51. package/src/repo-mesh-types.ts +2 -0
  52. package/src/shared-types.ts +38 -0
package/dist/index.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(", ")}]`);
@@ -2695,6 +3492,8 @@ var init_provider_cli_adapter = __esm({
2695
3492
  statusHistory = [];
2696
3493
  // ─── CLI Scripts (script-based parsing) ───
2697
3494
  cliScripts;
3495
+ /** Per-session opaque state object created by cliScripts.createState(), reset on stop. */
3496
+ scriptState = null;
2698
3497
  runtimeSettings = {};
2699
3498
  /** Full accumulated rendered PTY transcript for parser/readback use */
2700
3499
  accumulatedBuffer = "";
@@ -2771,9 +3570,13 @@ ${lastSnapshot}`;
2771
3570
  this.lastScreenChangeAt = 0;
2772
3571
  this.lastScreenSnapshotReadAt = Number.NEGATIVE_INFINITY;
2773
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
+ }
2774
3576
  getFreshParsedStatusCache() {
2775
3577
  const cached = this.parsedStatusCache;
2776
- 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) {
2777
3580
  return cached.result;
2778
3581
  }
2779
3582
  return null;
@@ -2876,6 +3679,7 @@ ${lastSnapshot}`;
2876
3679
  this.cliScripts = scripts;
2877
3680
  this.parsedStatusCache = null;
2878
3681
  this.parseErrorMessage = null;
3682
+ this.scriptState = typeof scripts.createState === "function" ? scripts.createState() ?? null : null;
2879
3683
  const scriptNames = listCliScriptNames(scripts);
2880
3684
  LOG.info("CLI", `[${this.cliType}] CLI scripts injected: [${scriptNames.join(", ")}]`);
2881
3685
  }
@@ -2993,6 +3797,7 @@ ${lastSnapshot}`;
2993
3797
  this.ready = false;
2994
3798
  this.startupParseGate = false;
2995
3799
  this.spawnAt = 0;
3800
+ this.scriptState = null;
2996
3801
  this.onStatusChange?.();
2997
3802
  });
2998
3803
  this.spawnAt = Date.now();
@@ -3746,6 +4551,11 @@ ${lastSnapshot}`;
3746
4551
  };
3747
4552
  }
3748
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
+ }
3749
4559
  runParseSession() {
3750
4560
  if (typeof this.cliScripts?.parseSession !== "function") {
3751
4561
  this.parseErrorMessage = `${this.cliType} parseSession unavailable`;
@@ -3766,7 +4576,10 @@ ${lastSnapshot}`;
3766
4576
  scope: this.currentTurnScope,
3767
4577
  runtimeSettings: this.runtimeSettings
3768
4578
  });
3769
- const session = this.cliScripts.parseSession({ ...input, tail, tailScreen: buildCliScreenSnapshot(tail) });
4579
+ const session = this.invokeCliScript(
4580
+ this.cliScripts.parseSession,
4581
+ { ...input, tail, tailScreen: buildCliScreenSnapshot(tail) }
4582
+ );
3770
4583
  this.parseErrorMessage = null;
3771
4584
  return session && typeof session === "object" ? session : null;
3772
4585
  } catch (e) {
@@ -3780,7 +4593,7 @@ ${lastSnapshot}`;
3780
4593
  if (!this.cliScripts?.detectStatus) return null;
3781
4594
  try {
3782
4595
  const screenText = this.terminalScreen.getText();
3783
- const status = this.cliScripts.detectStatus({
4596
+ const status = this.invokeCliScript(this.cliScripts.detectStatus, {
3784
4597
  tail: text.slice(-500),
3785
4598
  screenText,
3786
4599
  rawBuffer: this.accumulatedRawBuffer,
@@ -3799,7 +4612,7 @@ ${lastSnapshot}`;
3799
4612
  try {
3800
4613
  const screenText = this.terminalScreen.getText();
3801
4614
  const buffer = screenText || this.accumulatedBuffer;
3802
- return this.cliScripts.parseApproval({
4615
+ return this.invokeCliScript(this.cliScripts.parseApproval, {
3803
4616
  buffer,
3804
4617
  screenText,
3805
4618
  rawBuffer: this.accumulatedRawBuffer,
@@ -3855,7 +4668,8 @@ ${lastSnapshot}`;
3855
4668
  const screenText = this.readTerminalScreenText();
3856
4669
  const parseScreenText = this.getParseScreenText(screenText);
3857
4670
  const cached = this.parsedStatusCache;
3858
- 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) {
3859
4673
  return cached.result;
3860
4674
  }
3861
4675
  const parsed = this.runParseSession();
@@ -3883,6 +4697,7 @@ ${lastSnapshot}`;
3883
4697
  currentTurnScope: this.currentTurnScope,
3884
4698
  recentOutputBuffer: this.recentOutputBuffer,
3885
4699
  accumulatedBuffer: this.accumulatedBuffer,
4700
+ accumulatedRawBufferKey,
3886
4701
  screenText: parseScreenText,
3887
4702
  currentStatus: this.currentStatus,
3888
4703
  activeModal: this.activeModal,
@@ -3907,7 +4722,7 @@ ${lastSnapshot}`;
3907
4722
  scope: this.currentTurnScope,
3908
4723
  runtimeSettings: this.runtimeSettings
3909
4724
  });
3910
- return await Promise.resolve(fn({
4725
+ return await Promise.resolve(this.invokeCliScript(fn, {
3911
4726
  ...input,
3912
4727
  args: args && typeof args === "object" ? { ...args } : {}
3913
4728
  }));
@@ -6124,7 +6939,7 @@ function addWorkspaceEntry(config, rawPath, label, options) {
6124
6939
  }
6125
6940
  }
6126
6941
  const v = validateWorkspacePath(abs);
6127
- if (!v.ok) return { error: v.error };
6942
+ if (v.ok !== true) return { error: v.error };
6128
6943
  const list = [...config.workspaces || []];
6129
6944
  if (list.some((w) => path5.resolve(w.path) === abs)) {
6130
6945
  return { error: "Workspace already in list" };
@@ -6507,36 +7322,188 @@ async function syncMeshes(transport) {
6507
7322
  }
6508
7323
  }
6509
7324
  }
6510
- if (transport.syncMeshLedger) {
6511
- for (const local of localMeshes) {
6512
- try {
6513
- await syncMeshLedger(local.id, transport);
6514
- } catch (e) {
6515
- result.errors.push(`Ledger sync failed for "${local.name}": ${e.message}`);
6516
- }
6517
- }
6518
- }
6519
7325
  return result;
6520
7326
  }
6521
- async function syncMeshLedger(meshId, transport) {
6522
- if (!transport.syncMeshLedger) return;
6523
- const { readLedgerEntries: readLedgerEntries2, appendRemoteLedgerEntries: appendRemoteLedgerEntries2 } = await Promise.resolve().then(() => (init_mesh_ledger(), mesh_ledger_exports));
6524
- const localEntries = readLedgerEntries2(meshId);
6525
- const res = await transport.syncMeshLedger(meshId, { newEntries: localEntries });
6526
- if (res.missingEntries && res.missingEntries.length > 0) {
6527
- appendRemoteLedgerEntries2(meshId, res.missingEntries);
6528
- }
6529
- }
6530
7327
 
6531
7328
  // src/index.ts
6532
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
6533
7390
  init_mesh_work_queue();
6534
7391
  init_mesh_events();
6535
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
+
6536
7503
  // src/config/state-store.ts
6537
7504
  init_config();
6538
- import { existsSync as existsSync8, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
6539
- 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";
6540
7507
  var DEFAULT_STATE = {
6541
7508
  recentActivity: [],
6542
7509
  savedProviderSessions: [],
@@ -6549,7 +7516,7 @@ function isPlainObject2(value) {
6549
7516
  return !!value && typeof value === "object" && !Array.isArray(value);
6550
7517
  }
6551
7518
  function getStatePath() {
6552
- return join8(getConfigDir(), "state.json");
7519
+ return join9(getConfigDir(), "state.json");
6553
7520
  }
6554
7521
  function normalizeState(raw) {
6555
7522
  const parsed = isPlainObject2(raw) ? raw : {};
@@ -6585,7 +7552,7 @@ function normalizeState(raw) {
6585
7552
  }
6586
7553
  function loadState() {
6587
7554
  const statePath = getStatePath();
6588
- if (!existsSync8(statePath)) {
7555
+ if (!existsSync9(statePath)) {
6589
7556
  return { ...DEFAULT_STATE };
6590
7557
  }
6591
7558
  try {
@@ -6606,9 +7573,9 @@ function resetState() {
6606
7573
 
6607
7574
  // src/detection/ide-detector.ts
6608
7575
  import { execSync } from "child_process";
6609
- import { existsSync as existsSync9 } from "fs";
6610
- import { platform, homedir as homedir4 } from "os";
6611
- 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";
6612
7579
  var BUILTIN_IDE_DEFINITIONS = [];
6613
7580
  var registeredIDEs = /* @__PURE__ */ new Map();
6614
7581
  function registerIDEDefinition(def) {
@@ -6627,14 +7594,14 @@ function getMergedDefinitions() {
6627
7594
  function findCliCommand(command) {
6628
7595
  const trimmed = String(command || "").trim();
6629
7596
  if (!trimmed) return null;
6630
- if (path9.isAbsolute(trimmed) || trimmed.includes("/") || trimmed.includes("\\") || trimmed.startsWith("~")) {
6631
- const candidate = trimmed.startsWith("~") ? path9.join(homedir4(), trimmed.slice(1)) : trimmed;
6632
- const resolved = path9.isAbsolute(candidate) ? candidate : path9.resolve(candidate);
6633
- 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;
6634
7601
  }
6635
7602
  try {
6636
7603
  const result = execSync(
6637
- platform() === "win32" ? `where ${trimmed}` : `which ${trimmed}`,
7604
+ platform2() === "win32" ? `where ${trimmed}` : `which ${trimmed}`,
6638
7605
  { encoding: "utf-8", timeout: 5e3, stdio: ["pipe", "pipe", "pipe"] }
6639
7606
  ).trim();
6640
7607
  return result.split("\n")[0] || null;
@@ -6655,21 +7622,21 @@ function getIdeVersion(cliCommand) {
6655
7622
  }
6656
7623
  }
6657
7624
  function checkPathExists(paths) {
6658
- const home = homedir4();
7625
+ const home = homedir5();
6659
7626
  for (const p of paths) {
6660
- const normalized = p.startsWith("~") ? path9.join(home, p.slice(1)) : p;
7627
+ const normalized = p.startsWith("~") ? path10.join(home, p.slice(1)) : p;
6661
7628
  if (normalized.includes("*")) {
6662
7629
  const username = home.split(/[\\/]/).pop() || "";
6663
7630
  const resolved = normalized.replace("*", username);
6664
- if (existsSync9(resolved)) return resolved;
7631
+ if (existsSync10(resolved)) return resolved;
6665
7632
  } else {
6666
- if (existsSync9(normalized)) return normalized;
7633
+ if (existsSync10(normalized)) return normalized;
6667
7634
  }
6668
7635
  }
6669
7636
  return null;
6670
7637
  }
6671
7638
  async function detectIDEs(providerLoader) {
6672
- const os22 = platform();
7639
+ const os22 = platform2();
6673
7640
  const results = [];
6674
7641
  for (const def of getMergedDefinitions()) {
6675
7642
  const cliPath = findCliCommand(providerLoader?.getIdeCliCommand(def.id, def.cli) || def.cli);
@@ -6677,7 +7644,7 @@ async function detectIDEs(providerLoader) {
6677
7644
  let resolvedCli = cliPath;
6678
7645
  if (!resolvedCli && appPath && os22 === "darwin") {
6679
7646
  const bundledCli = `${appPath}/Contents/Resources/app/bin/${def.cli}`;
6680
- if (existsSync9(bundledCli)) resolvedCli = bundledCli;
7647
+ if (existsSync10(bundledCli)) resolvedCli = bundledCli;
6681
7648
  }
6682
7649
  if (!resolvedCli && appPath && os22 === "win32") {
6683
7650
  const { dirname: dirname9 } = await import("path");
@@ -6690,7 +7657,7 @@ async function detectIDEs(providerLoader) {
6690
7657
  `${appDir}\\\\resources\\\\app\\\\bin\\\\${def.cli}.cmd`
6691
7658
  ];
6692
7659
  for (const c of candidates) {
6693
- if (existsSync9(c)) {
7660
+ if (existsSync10(c)) {
6694
7661
  resolvedCli = c;
6695
7662
  break;
6696
7663
  }
@@ -6712,134 +7679,8 @@ async function detectIDEs(providerLoader) {
6712
7679
  return results;
6713
7680
  }
6714
7681
 
6715
- // src/detection/cli-detector.ts
6716
- import { exec } from "child_process";
6717
- import * as os3 from "os";
6718
- import * as path10 from "path";
6719
- import { existsSync as existsSync10 } from "fs";
6720
- function parseVersion(raw) {
6721
- const match = raw.match(/v?(\d+\.\d+(?:\.\d+)?(?:-[a-zA-Z0-9.]+)?)/);
6722
- return match ? match[1] : raw.split("\n")[0].slice(0, 100);
6723
- }
6724
- function shellQuote(value) {
6725
- if (/^[a-zA-Z0-9_./:@%+=,-]+$/.test(value)) return value;
6726
- return `"${value.replace(/(["\\$`])/g, "\\$1")}"`;
6727
- }
6728
- function expandHome(value) {
6729
- const trimmed = value.trim();
6730
- if (!trimmed.startsWith("~")) return trimmed;
6731
- return path10.join(os3.homedir(), trimmed.slice(1));
6732
- }
6733
- function isExplicitCommandPath(command) {
6734
- const trimmed = command.trim();
6735
- return path10.isAbsolute(trimmed) || trimmed.includes("/") || trimmed.includes("\\") || trimmed.startsWith("~");
6736
- }
6737
- function resolveCommandPath(command) {
6738
- const trimmed = command.trim();
6739
- if (!trimmed) return null;
6740
- if (isExplicitCommandPath(trimmed)) {
6741
- const expanded = expandHome(trimmed);
6742
- const candidate = path10.isAbsolute(expanded) ? expanded : path10.resolve(expanded);
6743
- return existsSync10(candidate) ? candidate : null;
6744
- }
6745
- return null;
6746
- }
6747
- function execAsync(cmd, timeoutMs = 5e3) {
6748
- return new Promise((resolve16) => {
6749
- const child = exec(cmd, {
6750
- encoding: "utf-8",
6751
- timeout: timeoutMs,
6752
- ...process.platform === "win32" ? { windowsHide: true } : {}
6753
- }, (err, stdout) => {
6754
- if (err || !stdout?.trim()) {
6755
- resolve16(null);
6756
- } else {
6757
- resolve16(stdout.trim());
6758
- }
6759
- });
6760
- child.on("error", () => resolve16(null));
6761
- });
6762
- }
6763
- async function detectCLIs(providerLoader, options) {
6764
- const platform10 = os3.platform();
6765
- const whichCmd = platform10 === "win32" ? "where" : "which";
6766
- const includeVersion = options?.includeVersion !== false;
6767
- const cliList = providerLoader ? providerLoader.getCliDetectionList() : [];
6768
- const results = await Promise.all(
6769
- cliList.map(async (cli) => {
6770
- try {
6771
- const explicitPath = resolveCommandPath(cli.command);
6772
- const pathResult = explicitPath || await execAsync(`${whichCmd} ${shellQuote(cli.command)}`);
6773
- if (!pathResult) return { ...cli, installed: false };
6774
- const firstPath = explicitPath || pathResult.split("\n")[0];
6775
- let version;
6776
- if (includeVersion) {
6777
- const versionCommands = [
6778
- `"${firstPath}" --version`,
6779
- `"${firstPath}" -V`,
6780
- `"${firstPath}" -v`,
6781
- cli.versionCommand
6782
- ].filter((v) => !!v);
6783
- try {
6784
- for (const versionCommand of versionCommands) {
6785
- const versionResult = await execAsync(versionCommand, 3e3);
6786
- if (versionResult) {
6787
- version = parseVersion(versionResult);
6788
- break;
6789
- }
6790
- }
6791
- } catch {
6792
- }
6793
- }
6794
- return { ...cli, installed: true, version, path: firstPath };
6795
- } catch {
6796
- return { ...cli, installed: false };
6797
- }
6798
- })
6799
- );
6800
- return results;
6801
- }
6802
- async function detectCLI(cliId, providerLoader, options) {
6803
- const resolvedId = providerLoader ? providerLoader.resolveAlias(cliId) : cliId;
6804
- if (providerLoader) {
6805
- const cliList = providerLoader.getCliDetectionList();
6806
- const target = cliList.find((c) => c.id === resolvedId);
6807
- if (target) {
6808
- const platform10 = os3.platform();
6809
- const whichCmd = platform10 === "win32" ? "where" : "which";
6810
- try {
6811
- const explicitPath = resolveCommandPath(target.command);
6812
- const pathResult = explicitPath || await execAsync(`${whichCmd} ${shellQuote(target.command)}`);
6813
- if (!pathResult) return null;
6814
- const firstPath = explicitPath || pathResult.split("\n")[0];
6815
- let version;
6816
- if (options?.includeVersion !== false) {
6817
- const versionCommands = [
6818
- `"${firstPath}" --version`,
6819
- `"${firstPath}" -V`,
6820
- `"${firstPath}" -v`,
6821
- target.versionCommand
6822
- ].filter((v) => !!v);
6823
- try {
6824
- for (const versionCommand of versionCommands) {
6825
- const versionResult = await execAsync(versionCommand, 3e3);
6826
- if (versionResult) {
6827
- version = parseVersion(versionResult);
6828
- break;
6829
- }
6830
- }
6831
- } catch {
6832
- }
6833
- }
6834
- return { ...target, installed: true, version, path: firstPath };
6835
- } catch {
6836
- return null;
6837
- }
6838
- }
6839
- }
6840
- const all = await detectCLIs(providerLoader, options);
6841
- return all.find((c) => c.id === resolvedId && c.installed) || null;
6842
- }
7682
+ // src/index.ts
7683
+ init_cli_detector();
6843
7684
 
6844
7685
  // src/system/host-memory.ts
6845
7686
  import * as os4 from "os";
@@ -8708,6 +9549,28 @@ var StatusMonitor = class {
8708
9549
  };
8709
9550
 
8710
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
+ }
8711
9574
  var BUILTIN_CHAT_MESSAGE_KINDS = ["standard", "thought", "tool", "terminal", "system"];
8712
9575
  var CHAT_MESSAGE_VISIBILITIES = ["user", "debug", "internal", "hidden"];
8713
9576
  var CHAT_MESSAGE_TRANSCRIPT_VISIBILITIES = ["visible", "chat", "user", "debug", "internal", "hidden"];
@@ -10719,7 +11582,8 @@ var ExtensionProviderInstance = class {
10719
11582
  ideType: this.ideType || this.type,
10720
11583
  agentType: this.type,
10721
11584
  agentName: this.agentName || this.provider.name,
10722
- extensionId: this.extensionId || this.type
11585
+ extensionId: this.extensionId || this.type,
11586
+ finalSummary: extractFinalSummaryFromMessages(data?.messages)
10723
11587
  });
10724
11588
  this.generatingStartedAt = 0;
10725
11589
  }
@@ -11487,7 +12351,7 @@ var IdeProviderInstance = class {
11487
12351
  } else if (agentStatus === "idle" && (lastStatus === "generating" || lastStatus === "waiting_approval")) {
11488
12352
  const startedAt = this.generatingStartedAt.get(agentKey);
11489
12353
  const duration = startedAt ? Math.round((now - startedAt) / 1e3) : 0;
11490
- 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) });
11491
12355
  this.generatingStartedAt.delete(agentKey);
11492
12356
  }
11493
12357
  this.lastAgentStatuses.set(agentKey, agentStatus);
@@ -14983,11 +15847,13 @@ async function handleOpenPanel(h, args) {
14983
15847
  async function handlePtyInput(h, args) {
14984
15848
  const { cliType, data, targetSessionId } = args || {};
14985
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 };
14986
15852
  const adapter = h.getCliAdapter(targetSessionId || cliType);
14987
15853
  if (!adapter || typeof adapter.writeRaw !== "function") {
14988
15854
  return { success: false, error: `CLI adapter not found: ${targetSessionId || cliType || "unknown"}` };
14989
15855
  }
14990
- await adapter.writeRaw(data);
15856
+ await adapter.writeRaw(cleanData);
14991
15857
  return { success: true };
14992
15858
  }
14993
15859
  function handlePtyResize(_h, args) {
@@ -15941,13 +16807,14 @@ var DaemonCommandHandler = class {
15941
16807
 
15942
16808
  // src/commands/cli-manager.ts
15943
16809
  init_provider_cli_adapter();
16810
+ init_cli_detector();
16811
+ init_config();
15944
16812
  import * as os13 from "os";
15945
16813
  import * as path18 from "path";
15946
16814
  import * as crypto4 from "crypto";
15947
16815
  import { existsSync as existsSync14, mkdirSync as mkdirSync9, writeFileSync as writeFileSync9 } from "fs";
15948
16816
  import { execFileSync } from "child_process";
15949
16817
  import chalk from "chalk";
15950
- init_config();
15951
16818
 
15952
16819
  // src/providers/cli-provider-instance.ts
15953
16820
  import * as os12 from "os";
@@ -15976,6 +16843,8 @@ function normalizeProviderSessionId(provider, providerSessionId) {
15976
16843
  }
15977
16844
 
15978
16845
  // src/providers/cli-provider-instance.ts
16846
+ var COMPLETED_FINALIZATION_RETRY_MS = 1e3;
16847
+ var COMPLETED_FINALIZATION_MAX_WAIT_MS = 3e4;
15979
16848
  var IMAGE_MIME_EXTENSIONS = {
15980
16849
  "image/png": ".png",
15981
16850
  "image/jpeg": ".jpg",
@@ -16039,6 +16908,13 @@ function cleanupStaleMaterializedImages(dir) {
16039
16908
  } catch {
16040
16909
  }
16041
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
+ }
16042
16918
  function buildCliStructuredInputPrompt(input, options = {}) {
16043
16919
  const promptParts = [];
16044
16920
  const imageRefs = [];
@@ -16323,10 +17199,12 @@ var CliProviderInstance = class {
16323
17199
  const mergedMessages = this.mergeConversationMessages(parsedMessages);
16324
17200
  const canonicalBackedHistory = this.syncCanonicalSavedHistoryIfNeeded();
16325
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);
16326
17204
  if (parsedMessages.length > 0) {
16327
17205
  const shouldSkipReplayPersist = this.suppressIdleHistoryReplay && adapterStatus.status === "idle" && parsedStatus?.status === "idle";
16328
17206
  let messagesToSave = parsedMessages;
16329
- if (parsedStatus?.status === "generating" || parsedStatus?.status === "long_generating") {
17207
+ if (!suppressStaleParsedBusyStatus && (parsedChatStatus === "generating" || parsedChatStatus === "long_generating")) {
16330
17208
  const lastIdx = messagesToSave.length - 1;
16331
17209
  if (lastIdx >= 0 && messagesToSave[lastIdx]?.role === "assistant") {
16332
17210
  messagesToSave = messagesToSave.slice(0, lastIdx);
@@ -16360,6 +17238,7 @@ var CliProviderInstance = class {
16360
17238
  summaryMetadata: this.summaryMetadata,
16361
17239
  controlValues: this.controlValues
16362
17240
  });
17241
+ const activeChatStatus = parseErrorMessage ? "error" : autoApproveActive && parsedStatus?.status === "waiting_approval" ? "generating" : adapterStatus.status !== "idle" ? visibleStatus : suppressStaleParsedBusyStatus ? visibleStatus : parsedChatStatus || visibleStatus;
16363
17242
  return {
16364
17243
  type: this.type,
16365
17244
  name: this.provider.name,
@@ -16369,7 +17248,7 @@ var CliProviderInstance = class {
16369
17248
  activeChat: {
16370
17249
  id: `${this.type}_${this.workingDir}`,
16371
17250
  title: parsedStatus?.title || dirName,
16372
- status: parseErrorMessage ? "error" : autoApproveActive && parsedStatus?.status === "waiting_approval" ? "generating" : adapterStatus.status !== "idle" ? visibleStatus : parsedStatus?.status || visibleStatus,
17251
+ status: activeChatStatus,
16373
17252
  messages: mergedMessages,
16374
17253
  activeModal: autoApproveActive ? null : parsedStatus?.activeModal ?? adapterStatus.activeModal,
16375
17254
  inputContent: ""
@@ -16499,6 +17378,103 @@ var CliProviderInstance = class {
16499
17378
  }
16500
17379
  this.applyProviderResponse(parsed.payload, { phase: "immediate" });
16501
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
+ }
16502
17478
  maybeAutoApproveStatus(adapterStatus, now = Date.now()) {
16503
17479
  const autoApproveActive = adapterStatus?.status === "waiting_approval" && this.shouldAutoApprove();
16504
17480
  if (autoApproveActive && !this.autoApproveBusy) {
@@ -16596,27 +17572,11 @@ var CliProviderInstance = class {
16596
17572
  this.generatingDebouncePending = null;
16597
17573
  this.generatingStartedAt = 0;
16598
17574
  } else {
16599
- if (this.completedDebounceTimer) clearTimeout(this.completedDebounceTimer);
16600
- this.completedDebouncePending = { chatTitle, duration, timestamp: now };
16601
- this.completedDebounceTimer = setTimeout(() => {
16602
- if (this.completedDebouncePending) {
16603
- const latestStatus = this.adapter.getStatus({ allowParse: false });
16604
- const latestAutoApproveActive = latestStatus.status === "waiting_approval" && this.shouldAutoApprove();
16605
- const latestVisibleStatus = latestAutoApproveActive ? "generating" : latestStatus.status;
16606
- if (latestVisibleStatus !== "idle") {
16607
- LOG.info("CLI", `[${this.type}] cancelled pending completed (resumed ${latestVisibleStatus})`);
16608
- this.completedDebouncePending = null;
16609
- this.completedDebounceTimer = null;
16610
- return;
16611
- }
16612
- LOG.info("CLI", `[${this.type}] completed in ${this.completedDebouncePending.duration}s`);
16613
- this.pushEvent({ event: "agent:generating_completed", ...this.completedDebouncePending });
16614
- this.completedDebouncePending = null;
16615
- this.generatingStartedAt = 0;
16616
- }
16617
- this.completedDebounceTimer = null;
16618
- }, 3e3);
17575
+ this.completedDebouncePending = { chatTitle, duration, timestamp: now, firstObservedAt: now };
17576
+ this.scheduleCompletedDebounceFlush(3e3);
16619
17577
  }
17578
+ } else if (newStatus === "idle" && this.lastStatus === "starting") {
17579
+ this.pushEvent({ event: "agent:ready", chatTitle, timestamp: now });
16620
17580
  } else if (newStatus === "stopped") {
16621
17581
  if (this.generatingDebounceTimer) {
16622
17582
  clearTimeout(this.generatingDebounceTimer);
@@ -18257,7 +19217,7 @@ ${rawInput}` : rawInput;
18257
19217
  });
18258
19218
  } else if (newStatus === "idle" && (this.lastStatus === "generating" || this.lastStatus === "waiting_approval")) {
18259
19219
  const duration = this.generatingStartedAt ? Math.round((now - this.generatingStartedAt) / 1e3) : 0;
18260
- 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) });
18261
19221
  this.generatingStartedAt = 0;
18262
19222
  } else if (newStatus === "stopped") {
18263
19223
  this.pushEvent({ event: "agent:stopped", chatTitle, timestamp: now });
@@ -18365,9 +19325,6 @@ function buildCoordinatorDelegatedCliLaunchOptions(input) {
18365
19325
  const cliType = String(input.cliType || "").trim();
18366
19326
  const cliArgs = Array.isArray(input.cliArgs) ? [...input.cliArgs] : [];
18367
19327
  const env = { ...input.env || {}, ...COORDINATOR_DELEGATED_ENV_UNSETS };
18368
- if (cliType === "hermes-cli" && !hasCliArg(cliArgs, "--ignore-user-config")) {
18369
- cliArgs.unshift("--ignore-user-config");
18370
- }
18371
19328
  if (cliType === "claude-cli" && !hasCliArg(cliArgs, "--mcp-config")) {
18372
19329
  cliArgs.unshift("--mcp-config", ensureEmptyDelegatedMcpConfig(input.workspace));
18373
19330
  }
@@ -21444,6 +22401,7 @@ function getAvailableIdeIds() {
21444
22401
 
21445
22402
  // src/commands/router.ts
21446
22403
  init_config();
22404
+ init_cli_detector();
21447
22405
  init_logger();
21448
22406
 
21449
22407
  // src/logging/command-log.ts
@@ -21613,7 +22571,9 @@ function resolveHermesMeshCoordinatorSetup(options) {
21613
22571
  const mcpServer = resolveAdhdevMcpServerLaunch({
21614
22572
  meshId: options.meshId,
21615
22573
  nodeExecutable: options.nodeExecutable,
21616
- adhdevMcpEntryPath: options.adhdevMcpEntryPath
22574
+ adhdevMcpEntryPath: options.adhdevMcpEntryPath,
22575
+ adhdevMcpTransport: options.adhdevMcpTransport,
22576
+ adhdevMcpPort: options.adhdevMcpPort
21617
22577
  });
21618
22578
  if (!mcpServer) {
21619
22579
  return {
@@ -21680,7 +22640,9 @@ function resolveMeshCoordinatorSetup(options) {
21680
22640
  const mcpServer = resolveAdhdevMcpServerLaunch({
21681
22641
  meshId,
21682
22642
  nodeExecutable: options.nodeExecutable,
21683
- adhdevMcpEntryPath: options.adhdevMcpEntryPath
22643
+ adhdevMcpEntryPath: options.adhdevMcpEntryPath,
22644
+ adhdevMcpTransport: options.adhdevMcpTransport,
22645
+ adhdevMcpPort: options.adhdevMcpPort
21684
22646
  });
21685
22647
  if (!mcpServer) {
21686
22648
  return {
@@ -21702,19 +22664,30 @@ function resolveMeshCoordinatorSetup(options) {
21702
22664
  if (!instructions || !template?.trim()) {
21703
22665
  return { kind: "unsupported", reason: "Provider manual MCP setup is missing instructions or template" };
21704
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
+ }
21705
22683
  return {
21706
22684
  kind: "manual",
21707
22685
  serverName,
21708
22686
  configFormat: mcpConfig.format,
21709
22687
  configPathCommand: mcpConfig.configPathCommand,
21710
22688
  requiresRestart: mcpConfig.requiresRestart === true,
21711
- instructions,
21712
- template: renderMeshCoordinatorTemplate(template, {
21713
- meshId,
21714
- workspace,
21715
- serverName,
21716
- adhdevMcpCommand: options.adhdevMcpCommand || DEFAULT_ADHDEV_MCP_COMMAND
21717
- })
22689
+ instructions,
22690
+ template: renderedTemplate
21718
22691
  };
21719
22692
  }
21720
22693
  return {
@@ -21743,11 +22716,27 @@ function resolveAdhdevMcpServerLaunch(options) {
21743
22716
  if (!entryPath) return null;
21744
22717
  const nodeExecutable = resolveMcpNodeExecutable(options.nodeExecutable);
21745
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));
21746
22723
  return {
21747
22724
  command: nodeExecutable,
21748
- args: [entryPath, "--mode", "ipc", "--repo-mesh", options.meshId]
22725
+ args
21749
22726
  };
21750
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
+ }
21751
22740
  function resolveMcpNodeExecutable(explicitExecutable) {
21752
22741
  const explicit = explicitExecutable?.trim();
21753
22742
  if (explicit) return explicit;
@@ -22559,6 +23548,209 @@ async function resolveProviderTypeFromPriority(args) {
22559
23548
  }
22560
23549
  return { error: `No usable provider detected for node '${args.nodeId}' from providerPriority: ${failed.join("; ")}` };
22561
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
+ }
22562
23754
  function loadYamlModule() {
22563
23755
  return yaml;
22564
23756
  }
@@ -22588,6 +23780,34 @@ function loadHermesCoordinatorBaseConfig(targetConfigPath) {
22588
23780
  const { mcp_servers: _mcpServers, ...baseConfig } = parsed;
22589
23781
  return { config: baseConfig, sourceHome, sourceConfigPath };
22590
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
+ }
22591
23811
  function copyHermesCoordinatorCredentialFiles(sourceHome, targetHome) {
22592
23812
  if (pathResolve(sourceHome) === pathResolve(targetHome)) return;
22593
23813
  for (const fileName of [".env", "auth.json"]) {
@@ -22745,9 +23965,191 @@ var DaemonCommandRouter = class {
22745
23965
  if (record?.meta?.meshNodeId === nodeId) return true;
22746
23966
  return false;
22747
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
+ }
22748
24129
  isCompletedHostedSession(record) {
22749
24130
  return record?.lifecycle === "stopped" || record?.lifecycle === "failed" || record?.lifecycle === "interrupted";
22750
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
+ }
22751
24153
  async cleanupMeshSessions(args) {
22752
24154
  if (args.mode === "preserve") {
22753
24155
  return { success: true, mode: "preserve", matchedCount: 0, stoppedSessionIds: [], deletedSessionIds: [], skippedSessionIds: [] };
@@ -22764,6 +24166,21 @@ var DaemonCommandRouter = class {
22764
24166
  const deleteUnsupportedSessionIds = [];
22765
24167
  const recordsRemainSessionIds = [];
22766
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
+ };
22767
24184
  const matchedBySurfaceKind = {
22768
24185
  live_runtime: 0,
22769
24186
  recovery_snapshot: 0,
@@ -22786,7 +24203,10 @@ var DaemonCommandRouter = class {
22786
24203
  try {
22787
24204
  if (args.mode === "stop") {
22788
24205
  if (!completed) {
22789
- 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
+ }
22790
24210
  stoppedSessionIds.push(sessionId);
22791
24211
  } else {
22792
24212
  skippedSessionIds.push(sessionId);
@@ -22803,6 +24223,7 @@ var DaemonCommandRouter = class {
22803
24223
  continue;
22804
24224
  }
22805
24225
  if (args.mode === "stop_and_delete") {
24226
+ if (!completed) await markIntentionalStop(sessionId, "delete_session_force");
22806
24227
  if (!args.dryRun) await this.deps.sessionHostControl.deleteSession(sessionId, { force: true });
22807
24228
  deletedSessionIds.push(sessionId);
22808
24229
  continue;
@@ -22814,6 +24235,7 @@ var DaemonCommandRouter = class {
22814
24235
  recordsRemainSessionIds.push(sessionId);
22815
24236
  if (args.mode === "stop_and_delete" && !completed) {
22816
24237
  try {
24238
+ await markIntentionalStop(sessionId, "stop_session");
22817
24239
  await this.deps.sessionHostControl.stopSession(sessionId);
22818
24240
  stoppedSessionIds.push(sessionId);
22819
24241
  } catch (stopError) {
@@ -23568,6 +24990,91 @@ var DaemonCommandRouter = class {
23568
24990
  return { success: false, error: e.message };
23569
24991
  }
23570
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
+ }
23571
25078
  case "add_mesh_node": {
23572
25079
  const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
23573
25080
  const workspace = typeof args?.workspace === "string" ? args.workspace.trim() : "";
@@ -23629,7 +25136,8 @@ var DaemonCommandRouter = class {
23629
25136
  node,
23630
25137
  mode,
23631
25138
  sessionIds,
23632
- dryRun: args?.dryRun === true
25139
+ dryRun: args?.dryRun === true,
25140
+ source: "mesh_cleanup_sessions"
23633
25141
  });
23634
25142
  return result;
23635
25143
  } catch (e) {
@@ -23659,10 +25167,61 @@ var DaemonCommandRouter = class {
23659
25167
  if (!branch) return { success: false, error: "Could not determine branch of the worktree node" };
23660
25168
  const { stdout: baseBranchStdout } = await execFileAsync3("git", ["branch", "--show-current"], { cwd: repoRoot, encoding: "utf8" });
23661
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
+ }
23662
25209
  try {
23663
25210
  await execFileAsync3("git", ["merge", "--no-ff", branch, "-m", `Auto-merge branch '${branch}' via Refinery`], { cwd: repoRoot, encoding: "utf8" });
23664
25211
  } catch (e) {
23665
- 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
+ };
23666
25225
  }
23667
25226
  const removeResult = await this.execute("remove_mesh_node", {
23668
25227
  meshId,
@@ -23675,11 +25234,27 @@ var DaemonCommandRouter = class {
23675
25234
  appendLedgerEntry2(meshId, {
23676
25235
  kind: "node_removed",
23677
25236
  nodeId,
23678
- payload: { refined: true, mergedBranch: branch, into: baseBranch }
25237
+ payload: { refined: true, mergedBranch: branch, into: baseBranch, validationSummary }
23679
25238
  });
23680
25239
  } catch {
23681
25240
  }
23682
- 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
+ };
23683
25258
  } catch (e) {
23684
25259
  return { success: false, error: e.message };
23685
25260
  }
@@ -23697,20 +25272,24 @@ var DaemonCommandRouter = class {
23697
25272
  );
23698
25273
  let sessionCleanup;
23699
25274
  if (node && sessionCleanupMode !== "preserve") {
23700
- sessionCleanup = await this.cleanupMeshSessions({ meshId, nodeId, node, mode: sessionCleanupMode });
25275
+ sessionCleanup = await this.cleanupMeshSessions({ meshId, nodeId, node, mode: sessionCleanupMode, source: "mesh_remove_node" });
23701
25276
  if (sessionCleanup.success === false) return { success: false, removed: false, sessionCleanup };
23702
25277
  }
23703
- if (node?.isLocalWorktree && node.workspace) {
23704
- try {
23705
- const sourceNode = node.clonedFromNodeId ? mesh?.nodes.find((n) => n.id === node.clonedFromNodeId || n.nodeId === node.clonedFromNodeId) : mesh?.nodes.find((n) => !n.isLocalWorktree);
23706
- const repoRoot = sourceNode?.repoRoot || sourceNode?.workspace;
23707
- if (repoRoot) {
23708
- const { removeWorktree: removeWorktree2 } = await Promise.resolve().then(() => (init_git_worktree(), git_worktree_exports));
23709
- await removeWorktree2(repoRoot, node.workspace);
23710
- }
23711
- } catch (e) {
23712
- 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
+ };
23713
25291
  }
25292
+ worktreeCleanup = cleanupResult;
23714
25293
  }
23715
25294
  let removed = false;
23716
25295
  if (meshRecord?.inline) {
@@ -23725,12 +25304,21 @@ var DaemonCommandRouter = class {
23725
25304
  appendLedgerEntry2(meshId, {
23726
25305
  kind: "node_removed",
23727
25306
  nodeId,
23728
- 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
+ }
23729
25317
  });
23730
25318
  } catch {
23731
25319
  }
23732
25320
  }
23733
- return { success: true, removed, ...sessionCleanup ? { sessionCleanup } : {} };
25321
+ return { success: true, removed, ...sessionCleanup ? { sessionCleanup } : {}, ...worktreeCleanup ? { worktreeCleanup } : {} };
23734
25322
  } catch (e) {
23735
25323
  return { success: false, error: e.message };
23736
25324
  }
@@ -23765,6 +25353,7 @@ var DaemonCommandRouter = class {
23765
25353
  workspace: result.worktreePath,
23766
25354
  repoRoot: result.worktreePath,
23767
25355
  daemonId: sourceNode.daemonId,
25356
+ machineId: sourceNode.machineId ?? sourceNode.machine_id,
23768
25357
  userOverrides: { ...sourceNode.userOverrides || {} },
23769
25358
  policy: { ...sourceNode.policy || {} },
23770
25359
  isLocalWorktree: true,
@@ -23778,6 +25367,7 @@ var DaemonCommandRouter = class {
23778
25367
  workspace: result.worktreePath,
23779
25368
  repoRoot: result.worktreePath,
23780
25369
  daemonId: sourceNode.daemonId,
25370
+ machineId: sourceNode.machineId ?? sourceNode.machine_id,
23781
25371
  userOverrides: { ...sourceNode.userOverrides || {} },
23782
25372
  isLocalWorktree: true,
23783
25373
  worktreeBranch: result.branch,
@@ -23896,6 +25486,93 @@ var DaemonCommandRouter = class {
23896
25486
  meshCoordinatorSetup: coordinatorSetup
23897
25487
  };
23898
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
+ }
23899
25576
  const configFormat = coordinatorSetup.configFormat;
23900
25577
  if (configFormat !== "claude_mcp_json" && configFormat !== "hermes_config_yaml") {
23901
25578
  return {
@@ -23950,9 +25627,11 @@ var DaemonCommandRouter = class {
23950
25627
  args: coordinatorSetup.mcpServer.args
23951
25628
  };
23952
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";
23953
25632
  mcpServerEntry.env = {
23954
25633
  ADHDEV_INLINE_MESH: JSON.stringify(mesh),
23955
- ADHDEV_MCP_TRANSPORT: "ipc"
25634
+ ADHDEV_MCP_TRANSPORT: mcpTransport === "local" ? "local" : "ipc"
23956
25635
  };
23957
25636
  }
23958
25637
  try {
@@ -23971,7 +25650,8 @@ var DaemonCommandRouter = class {
23971
25650
  if (hadExistingMcpConfig) {
23972
25651
  try {
23973
25652
  const parsedExistingMcpConfig = parseMeshCoordinatorMcpConfig(readFileSync17(mcpConfigPath, "utf-8"), configFormat);
23974
- existingMcpConfig = { ...existingMcpConfig, ...parsedExistingMcpConfig };
25653
+ const existingCoordinatorConfig = hermesManualFallback ? stripHermesCoordinatorTempModelProviderOverrides(parsedExistingMcpConfig) : parsedExistingMcpConfig;
25654
+ existingMcpConfig = { ...existingMcpConfig, ...existingCoordinatorConfig };
23975
25655
  copyFileSync4(mcpConfigPath, mcpConfigPath + ".backup");
23976
25656
  } catch (error) {
23977
25657
  LOG.error("MeshCoordinator", `Failed to parse existing MCP config ${mcpConfigPath}: ${error?.message || error}`);
@@ -24052,6 +25732,69 @@ var DaemonCommandRouter = class {
24052
25732
  return { success: false, error: e.message };
24053
25733
  }
24054
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
+ workspace: node.workspace,
25753
+ repoRoot: node.repoRoot,
25754
+ isLocalWorktree: node.isLocalWorktree,
25755
+ worktreeBranch: node.worktreeBranch,
25756
+ daemonId: node.daemonId,
25757
+ machineId: node.machineId,
25758
+ health: "unknown"
25759
+ };
25760
+ if (node.workspace && typeof node.workspace === "string") {
25761
+ try {
25762
+ const { execFile: execFile3 } = await import("child_process");
25763
+ const { promisify: promisify3 } = await import("util");
25764
+ const execFileAsync3 = promisify3(execFile3);
25765
+ const branch = await execFileAsync3("git", ["-C", node.workspace, "branch", "--show-current"], {
25766
+ encoding: "utf8",
25767
+ timeout: 1e4
25768
+ }).then((r) => r.stdout.trim()).catch(() => "");
25769
+ const porc = await execFileAsync3("git", ["-C", node.workspace, "status", "--porcelain"], {
25770
+ encoding: "utf8",
25771
+ timeout: 1e4
25772
+ }).then((r) => r.stdout.trim()).catch(() => "");
25773
+ const dirty = porc.length > 0;
25774
+ status.branch = branch;
25775
+ status.isDirty = dirty;
25776
+ status.uncommittedChanges = porc ? porc.split("\n").filter(Boolean).length : 0;
25777
+ status.health = branch ? dirty ? "dirty" : "online" : "degraded";
25778
+ } catch {
25779
+ status.health = "degraded";
25780
+ }
25781
+ }
25782
+ nodeStatuses.push(status);
25783
+ }
25784
+ return {
25785
+ success: true,
25786
+ meshId: mesh.id,
25787
+ meshName: mesh.name,
25788
+ repoIdentity: mesh.repoIdentity,
25789
+ defaultBranch: mesh.defaultBranch,
25790
+ nodes: nodeStatuses,
25791
+ queue: { tasks: queue, summary: queueSummary },
25792
+ ledger: { entries: ledgerEntries, summary: ledgerSummary }
25793
+ };
25794
+ } catch (e) {
25795
+ return { success: false, error: e.message };
25796
+ }
25797
+ }
24055
25798
  default:
24056
25799
  break;
24057
25800
  }
@@ -31769,6 +33512,9 @@ function launchIDE(ide, workspacePath) {
31769
33512
  }
31770
33513
  }
31771
33514
 
33515
+ // src/boot/daemon-lifecycle.ts
33516
+ init_cli_detector();
33517
+
31772
33518
  // src/sessions/registry.ts
31773
33519
  var SessionRegistry = class {
31774
33520
  bySessionId = /* @__PURE__ */ new Map();
@@ -31999,7 +33745,8 @@ async function initDaemonComponents(config) {
31999
33745
  cdpManagers,
32000
33746
  sessionRegistry,
32001
33747
  detectedIdes: detectedIdesRef,
32002
- refreshProviderAvailability
33748
+ refreshProviderAvailability,
33749
+ dispatchMeshCommand: config.dispatchMeshCommand
32003
33750
  };
32004
33751
  setupMeshEventForwarding(components);
32005
33752
  return components;
@@ -32100,10 +33847,12 @@ export {
32100
33847
  IdeProviderInstance,
32101
33848
  InMemoryGitSnapshotStore,
32102
33849
  LOG,
33850
+ MAX_LEDGER_SLICE_LIMIT,
32103
33851
  MIN_GIT_WORKSPACE_POLL_INTERVAL_MS,
32104
33852
  MIN_MACHINE_RUNTIME_SUBSCRIPTION_INTERVAL_MS,
32105
33853
  MIN_SESSION_HOST_DIAGNOSTICS_SUBSCRIPTION_INTERVAL_MS,
32106
33854
  NodePtyTransportFactory,
33855
+ P2pRelayFailureError,
32107
33856
  ProviderCliAdapter,
32108
33857
  ProviderInstanceManager,
32109
33858
  ProviderLoader,
@@ -32114,12 +33863,16 @@ export {
32114
33863
  addNode,
32115
33864
  appendLedgerEntry,
32116
33865
  appendRecentActivity,
33866
+ appendRemoteLedgerEntries,
32117
33867
  buildAssistantChatMessage,
32118
33868
  buildChatMessage,
32119
33869
  buildChatMessageSignature,
32120
33870
  buildChatTailDeliverySignature,
32121
33871
  buildCoordinatorSystemPrompt,
32122
33872
  buildMachineInfo,
33873
+ buildMeshLedgerReconciliationEvidence,
33874
+ buildMeshLedgerReplicaEvidence,
33875
+ buildP2pRelayFailurePayload,
32123
33876
  buildPinnedGlobalInstallCommand,
32124
33877
  buildRuntimeSystemChatMessage,
32125
33878
  buildSessionEntries,
@@ -32130,10 +33883,13 @@ export {
32130
33883
  buildThoughtChatMessage,
32131
33884
  buildToolChatMessage,
32132
33885
  buildUserChatMessage,
33886
+ cancelTask,
32133
33887
  claimNextTask,
32134
33888
  classifyChatMessageVisibility,
32135
33889
  classifyHotChatSessionsForSubscriptionFlush,
33890
+ classifyP2pRelayFailure,
32136
33891
  clearDebugTrace,
33892
+ clearPendingMeshCoordinatorEvents,
32137
33893
  compareGitSnapshots,
32138
33894
  configureDebugTraceStore,
32139
33895
  connectCdpManager,
@@ -32149,6 +33905,7 @@ export {
32149
33905
  detectAllVersions,
32150
33906
  detectCLIs,
32151
33907
  detectIDEs,
33908
+ drainPendingMeshCoordinatorEvents,
32152
33909
  enqueueTask,
32153
33910
  ensureSessionHostReady,
32154
33911
  execNpmCommandSync,
@@ -32173,7 +33930,9 @@ export {
32173
33930
  getLogLevel,
32174
33931
  getMesh,
32175
33932
  getMeshByRepo,
33933
+ getMeshQueueStats,
32176
33934
  getNpmExecOptions,
33935
+ getPendingMeshCoordinatorEvents,
32177
33936
  getQueue,
32178
33937
  getRecentActivity,
32179
33938
  getRecentCommands,
@@ -32199,6 +33958,7 @@ export {
32199
33958
  isInternalChatMessage,
32200
33959
  isManagedStatusWaiting,
32201
33960
  isManagedStatusWorking,
33961
+ isP2pRelayTransportFailure,
32202
33962
  isPathInside,
32203
33963
  isSessionHostLiveRuntime,
32204
33964
  isSessionHostRecoverySnapshot,
@@ -32237,10 +33997,12 @@ export {
32237
33997
  probeCdpPort,
32238
33998
  readChatHistory,
32239
33999
  readLedgerEntries,
34000
+ readLedgerSlice,
32240
34001
  recordDebugTrace,
32241
34002
  registerExtensionProviders,
32242
34003
  removeNode,
32243
34004
  removeWorktree,
34005
+ requeueTask,
32244
34006
  resetConfig,
32245
34007
  resetDebugRuntimeConfig,
32246
34008
  resetState,