@adhdev/daemon-core 0.9.77-rc.5 → 0.9.77-rc.50

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 (45) 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 +8 -4
  9. package/dist/index.js +1959 -291
  10. package/dist/index.js.map +1 -1
  11. package/dist/index.mjs +1947 -291
  12. package/dist/index.mjs.map +1 -1
  13. package/dist/mesh/mesh-events.d.ts +10 -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-work-queue.d.ts +56 -1
  18. package/dist/mesh/p2p-relay-failure.d.ts +35 -0
  19. package/dist/providers/cli-provider-instance.d.ts +6 -0
  20. package/dist/repo-mesh-types.d.ts +2 -0
  21. package/dist/shared-types.d.ts +38 -0
  22. package/package.json +1 -1
  23. package/src/boot/daemon-lifecycle.ts +5 -0
  24. package/src/cli-adapters/provider-cli-adapter.ts +35 -4
  25. package/src/cli-adapters/provider-cli-shared.ts +14 -4
  26. package/src/commands/cli-manager.ts +0 -4
  27. package/src/commands/mesh-coordinator.ts +55 -7
  28. package/src/commands/router.ts +847 -26
  29. package/src/commands/stream-commands.ts +8 -1
  30. package/src/config/config.ts +2 -1
  31. package/src/config/mesh-config.ts +2 -0
  32. package/src/config/workspaces.ts +1 -1
  33. package/src/git/git-worktree.ts +56 -4
  34. package/src/index.d.ts +3 -0
  35. package/src/index.ts +20 -4
  36. package/src/mesh/coordinator-prompt.ts +21 -10
  37. package/src/mesh/mesh-events.ts +522 -22
  38. package/src/mesh/mesh-ledger-reconciliation.ts +115 -0
  39. package/src/mesh/mesh-ledger.ts +209 -8
  40. package/src/mesh/mesh-sync.ts +4 -34
  41. package/src/mesh/mesh-work-queue.ts +163 -10
  42. package/src/mesh/p2p-relay-failure.ts +152 -0
  43. package/src/providers/cli-provider-instance.ts +153 -30
  44. package/src/repo-mesh-types.ts +2 -0
  45. 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,9 +1193,14 @@ 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];
@@ -1041,6 +1220,53 @@ function updateTaskStatus(meshId, taskId, status) {
1041
1220
  writeQueue(meshId, queue);
1042
1221
  return queue[idx];
1043
1222
  }
1223
+ function recordTaskAutoLaunch(meshId, taskId, autoLaunch) {
1224
+ const queue = readQueue(meshId);
1225
+ const idx = queue.findIndex((q) => q.id === taskId);
1226
+ if (idx === -1) return null;
1227
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1228
+ queue[idx].autoLaunch = {
1229
+ ...autoLaunch,
1230
+ updatedAt: now
1231
+ };
1232
+ queue[idx].updatedAt = now;
1233
+ writeQueue(meshId, queue);
1234
+ return queue[idx];
1235
+ }
1236
+ function cancelTask(meshId, taskId, opts) {
1237
+ const queue = readQueue(meshId);
1238
+ const idx = queue.findIndex((q) => q.id === taskId);
1239
+ if (idx === -1) return null;
1240
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1241
+ queue[idx].status = "cancelled";
1242
+ queue[idx].updatedAt = now;
1243
+ queue[idx].cancelledAt = now;
1244
+ if (opts?.reason) queue[idx].cancelReason = opts.reason;
1245
+ writeQueue(meshId, queue);
1246
+ return queue[idx];
1247
+ }
1248
+ function requeueTask(meshId, taskId, opts) {
1249
+ const queue = readQueue(meshId);
1250
+ const idx = queue.findIndex((q) => q.id === taskId);
1251
+ if (idx === -1) return null;
1252
+ const entry = queue[idx];
1253
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1254
+ entry.status = "pending";
1255
+ delete entry.assignedNodeId;
1256
+ delete entry.assignedSessionId;
1257
+ delete entry.cancelledAt;
1258
+ delete entry.cancelReason;
1259
+ if (opts?.clearTargetNode) delete entry.targetNodeId;
1260
+ if (typeof opts?.targetNodeId === "string") entry.targetNodeId = opts.targetNodeId;
1261
+ if (opts?.clearTargetSession !== false) delete entry.targetSessionId;
1262
+ if (typeof opts?.targetSessionId === "string") entry.targetSessionId = opts.targetSessionId;
1263
+ entry.updatedAt = now;
1264
+ entry.requeuedAt = now;
1265
+ entry.requeueCount = (entry.requeueCount || 0) + 1;
1266
+ if (opts?.reason) entry.requeueReason = opts.reason;
1267
+ writeQueue(meshId, queue);
1268
+ return entry;
1269
+ }
1044
1270
  function updateSessionTaskStatus(meshId, sessionId, status) {
1045
1271
  const queue = readQueue(meshId);
1046
1272
  for (let i = queue.length - 1; i >= 0; i--) {
@@ -1055,24 +1281,185 @@ function updateSessionTaskStatus(meshId, sessionId, status) {
1055
1281
  }
1056
1282
  function getMeshQueueStats(meshId) {
1057
1283
  const queue = readQueue(meshId);
1284
+ const pending = queue.filter((q) => q.status === "pending").length;
1285
+ const assigned = queue.filter((q) => q.status === "assigned").length;
1286
+ const completed = queue.filter((q) => q.status === "completed").length;
1287
+ const failed = queue.filter((q) => q.status === "failed").length;
1288
+ const cancelled = queue.filter((q) => q.status === "cancelled").length;
1058
1289
  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
1290
+ total: queue.length,
1291
+ active: pending + assigned,
1292
+ historical: completed + failed + cancelled,
1293
+ pending,
1294
+ assigned,
1295
+ completed,
1296
+ failed,
1297
+ cancelled,
1298
+ activeCounts: {
1299
+ pending,
1300
+ assigned
1301
+ },
1302
+ historicalCounts: {
1303
+ completed,
1304
+ failed,
1305
+ cancelled
1306
+ },
1307
+ activeAssignments: queue.filter((q) => q.status === "assigned").map((q) => ({
1308
+ id: q.id,
1309
+ nodeId: q.assignedNodeId,
1310
+ sessionId: q.assignedSessionId,
1311
+ message: q.message
1312
+ }))
1063
1313
  };
1064
1314
  }
1315
+ var ACTIVE_MESH_QUEUE_STATUSES, HISTORICAL_MESH_QUEUE_STATUSES;
1065
1316
  var init_mesh_work_queue = __esm({
1066
1317
  "src/mesh/mesh-work-queue.ts"() {
1067
1318
  "use strict";
1068
1319
  init_mesh_ledger();
1320
+ ACTIVE_MESH_QUEUE_STATUSES = ["pending", "assigned"];
1321
+ HISTORICAL_MESH_QUEUE_STATUSES = ["completed", "failed", "cancelled"];
1322
+ }
1323
+ });
1324
+
1325
+ // src/detection/cli-detector.ts
1326
+ import { exec } from "child_process";
1327
+ import * as os2 from "os";
1328
+ import * as path8 from "path";
1329
+ import { existsSync as existsSync7 } from "fs";
1330
+ function parseVersion(raw) {
1331
+ const match = raw.match(/v?(\d+\.\d+(?:\.\d+)?(?:-[a-zA-Z0-9.]+)?)/);
1332
+ return match ? match[1] : raw.split("\n")[0].slice(0, 100);
1333
+ }
1334
+ function shellQuote(value) {
1335
+ if (/^[a-zA-Z0-9_./:@%+=,-]+$/.test(value)) return value;
1336
+ return `"${value.replace(/(["\\$`])/g, "\\$1")}"`;
1337
+ }
1338
+ function expandHome(value) {
1339
+ const trimmed = value.trim();
1340
+ if (!trimmed.startsWith("~")) return trimmed;
1341
+ return path8.join(os2.homedir(), trimmed.slice(1));
1342
+ }
1343
+ function isExplicitCommandPath(command) {
1344
+ const trimmed = command.trim();
1345
+ return path8.isAbsolute(trimmed) || trimmed.includes("/") || trimmed.includes("\\") || trimmed.startsWith("~");
1346
+ }
1347
+ function resolveCommandPath(command) {
1348
+ const trimmed = command.trim();
1349
+ if (!trimmed) return null;
1350
+ if (isExplicitCommandPath(trimmed)) {
1351
+ const expanded = expandHome(trimmed);
1352
+ const candidate = path8.isAbsolute(expanded) ? expanded : path8.resolve(expanded);
1353
+ return existsSync7(candidate) ? candidate : null;
1354
+ }
1355
+ return null;
1356
+ }
1357
+ function execAsync(cmd, timeoutMs = 5e3) {
1358
+ return new Promise((resolve16) => {
1359
+ const child = exec(cmd, {
1360
+ encoding: "utf-8",
1361
+ timeout: timeoutMs,
1362
+ ...process.platform === "win32" ? { windowsHide: true } : {}
1363
+ }, (err, stdout) => {
1364
+ if (err || !stdout?.trim()) {
1365
+ resolve16(null);
1366
+ } else {
1367
+ resolve16(stdout.trim());
1368
+ }
1369
+ });
1370
+ child.on("error", () => resolve16(null));
1371
+ });
1372
+ }
1373
+ async function detectCLIs(providerLoader, options) {
1374
+ const platform10 = os2.platform();
1375
+ const whichCmd = platform10 === "win32" ? "where" : "which";
1376
+ const includeVersion = options?.includeVersion !== false;
1377
+ const cliList = providerLoader ? providerLoader.getCliDetectionList() : [];
1378
+ const results = await Promise.all(
1379
+ cliList.map(async (cli) => {
1380
+ try {
1381
+ const explicitPath = resolveCommandPath(cli.command);
1382
+ const pathResult = explicitPath || await execAsync(`${whichCmd} ${shellQuote(cli.command)}`);
1383
+ if (!pathResult) return { ...cli, installed: false };
1384
+ const firstPath = explicitPath || pathResult.split("\n")[0];
1385
+ let version;
1386
+ if (includeVersion) {
1387
+ const versionCommands = [
1388
+ `"${firstPath}" --version`,
1389
+ `"${firstPath}" -V`,
1390
+ `"${firstPath}" -v`,
1391
+ cli.versionCommand
1392
+ ].filter((v) => !!v);
1393
+ try {
1394
+ for (const versionCommand of versionCommands) {
1395
+ const versionResult = await execAsync(versionCommand, 3e3);
1396
+ if (versionResult) {
1397
+ version = parseVersion(versionResult);
1398
+ break;
1399
+ }
1400
+ }
1401
+ } catch {
1402
+ }
1403
+ }
1404
+ return { ...cli, installed: true, version, path: firstPath };
1405
+ } catch {
1406
+ return { ...cli, installed: false };
1407
+ }
1408
+ })
1409
+ );
1410
+ return results;
1411
+ }
1412
+ async function detectCLI(cliId, providerLoader, options) {
1413
+ const resolvedId = providerLoader ? providerLoader.resolveAlias(cliId) : cliId;
1414
+ if (providerLoader) {
1415
+ const cliList = providerLoader.getCliDetectionList();
1416
+ const target = cliList.find((c) => c.id === resolvedId);
1417
+ if (target) {
1418
+ const platform10 = os2.platform();
1419
+ const whichCmd = platform10 === "win32" ? "where" : "which";
1420
+ try {
1421
+ const explicitPath = resolveCommandPath(target.command);
1422
+ const pathResult = explicitPath || await execAsync(`${whichCmd} ${shellQuote(target.command)}`);
1423
+ if (!pathResult) return null;
1424
+ const firstPath = explicitPath || pathResult.split("\n")[0];
1425
+ let version;
1426
+ if (options?.includeVersion !== false) {
1427
+ const versionCommands = [
1428
+ `"${firstPath}" --version`,
1429
+ `"${firstPath}" -V`,
1430
+ `"${firstPath}" -v`,
1431
+ target.versionCommand
1432
+ ].filter((v) => !!v);
1433
+ try {
1434
+ for (const versionCommand of versionCommands) {
1435
+ const versionResult = await execAsync(versionCommand, 3e3);
1436
+ if (versionResult) {
1437
+ version = parseVersion(versionResult);
1438
+ break;
1439
+ }
1440
+ }
1441
+ } catch {
1442
+ }
1443
+ }
1444
+ return { ...target, installed: true, version, path: firstPath };
1445
+ } catch {
1446
+ return null;
1447
+ }
1448
+ }
1449
+ }
1450
+ const all = await detectCLIs(providerLoader, options);
1451
+ return all.find((c) => c.id === resolvedId && c.installed) || null;
1452
+ }
1453
+ var init_cli_detector = __esm({
1454
+ "src/detection/cli-detector.ts"() {
1455
+ "use strict";
1069
1456
  }
1070
1457
  });
1071
1458
 
1072
1459
  // src/logging/logger.ts
1073
1460
  import * as fs2 from "fs";
1074
- import * as path8 from "path";
1075
- import * as os2 from "os";
1461
+ import * as path9 from "path";
1462
+ import * as os3 from "os";
1076
1463
  function setLogLevel(level) {
1077
1464
  currentLevel = level;
1078
1465
  daemonLog("Logger", `Log level set to: ${level}`, "info");
@@ -1087,13 +1474,13 @@ function getDaemonLogDir() {
1087
1474
  return LOG_DIR;
1088
1475
  }
1089
1476
  function getCurrentDaemonLogPath(date = /* @__PURE__ */ new Date()) {
1090
- return path8.join(LOG_DIR, `daemon-${date.toISOString().slice(0, 10)}.log`);
1477
+ return path9.join(LOG_DIR, `daemon-${date.toISOString().slice(0, 10)}.log`);
1091
1478
  }
1092
1479
  function checkDateRotation() {
1093
1480
  const today = getDateStr();
1094
1481
  if (today !== currentDate) {
1095
1482
  currentDate = today;
1096
- currentLogFile = path8.join(LOG_DIR, `daemon-${currentDate}.log`);
1483
+ currentLogFile = path9.join(LOG_DIR, `daemon-${currentDate}.log`);
1097
1484
  cleanOldLogs();
1098
1485
  }
1099
1486
  }
@@ -1107,7 +1494,7 @@ function cleanOldLogs() {
1107
1494
  const dateMatch = file.match(/daemon-(\d{4}-\d{2}-\d{2})/);
1108
1495
  if (dateMatch && dateMatch[1] < cutoffStr) {
1109
1496
  try {
1110
- fs2.unlinkSync(path8.join(LOG_DIR, file));
1497
+ fs2.unlinkSync(path9.join(LOG_DIR, file));
1111
1498
  } catch {
1112
1499
  }
1113
1500
  }
@@ -1230,7 +1617,7 @@ var init_logger = __esm({
1230
1617
  LEVEL_NUM = { debug: 0, info: 1, warn: 2, error: 3 };
1231
1618
  LEVEL_LABEL = { debug: "DBG", info: "INF", warn: "WRN", error: "ERR" };
1232
1619
  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");
1620
+ 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
1621
  MAX_LOG_SIZE = 5 * 1024 * 1024;
1235
1622
  MAX_LOG_DAYS = 7;
1236
1623
  try {
@@ -1238,16 +1625,16 @@ var init_logger = __esm({
1238
1625
  } catch {
1239
1626
  }
1240
1627
  currentDate = getDateStr();
1241
- currentLogFile = path8.join(LOG_DIR, `daemon-${currentDate}.log`);
1628
+ currentLogFile = path9.join(LOG_DIR, `daemon-${currentDate}.log`);
1242
1629
  cleanOldLogs();
1243
1630
  try {
1244
- const oldLog = path8.join(LOG_DIR, "daemon.log");
1631
+ const oldLog = path9.join(LOG_DIR, "daemon.log");
1245
1632
  if (fs2.existsSync(oldLog)) {
1246
1633
  const stat2 = fs2.statSync(oldLog);
1247
1634
  const oldDate = stat2.mtime.toISOString().slice(0, 10);
1248
- fs2.renameSync(oldLog, path8.join(LOG_DIR, `daemon-${oldDate}.log`));
1635
+ fs2.renameSync(oldLog, path9.join(LOG_DIR, `daemon-${oldDate}.log`));
1249
1636
  }
1250
- const oldLogBackup = path8.join(LOG_DIR, "daemon.log.old");
1637
+ const oldLogBackup = path9.join(LOG_DIR, "daemon.log.old");
1251
1638
  if (fs2.existsSync(oldLogBackup)) {
1252
1639
  fs2.unlinkSync(oldLogBackup);
1253
1640
  }
@@ -1279,7 +1666,7 @@ var init_logger = __esm({
1279
1666
  }
1280
1667
  };
1281
1668
  interceptorInstalled = false;
1282
- LOG_PATH = path8.join(LOG_DIR, `daemon-${getDateStr()}.log`);
1669
+ LOG_PATH = path9.join(LOG_DIR, `daemon-${getDateStr()}.log`);
1283
1670
  }
1284
1671
  });
1285
1672
 
@@ -1298,6 +1685,9 @@ function drainPendingMeshCoordinatorEvents() {
1298
1685
  function readNonEmptyString(value) {
1299
1686
  return typeof value === "string" && value.trim() ? value.trim() : "";
1300
1687
  }
1688
+ function resolveEventSessionId(event, fallback) {
1689
+ return readNonEmptyString(event.targetSessionId) || readNonEmptyString(event.sessionId) || readNonEmptyString(event.instanceId) || readNonEmptyString(fallback);
1690
+ }
1301
1691
  function isMeshCoordinatorEvent(eventName) {
1302
1692
  return typeof eventName === "string" && MESH_COORDINATOR_EVENTS.has(eventName);
1303
1693
  }
@@ -1309,38 +1699,323 @@ function formatCompletionMetadata(event) {
1309
1699
  ].filter(Boolean);
1310
1700
  return parts.length > 0 ? ` (${parts.join("; ")})` : "";
1311
1701
  }
1702
+ function getMeshWithCache(components, meshId) {
1703
+ const localMesh = getMesh(meshId);
1704
+ if (localMesh) return localMesh;
1705
+ return components.router?.getCachedInlineMesh(meshId);
1706
+ }
1707
+ function isIntentionalCleanupStopMetadata(event) {
1708
+ 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";
1709
+ }
1710
+ function hasRecentIntentionalCleanupStop(meshId, sessionId, nodeId) {
1711
+ if (!sessionId && !nodeId) return false;
1712
+ const cutoff = Date.now() - INTENTIONAL_CLEANUP_STOP_SUPPRESSION_MS;
1713
+ const entries = readLedgerEntries(meshId);
1714
+ for (let i = entries.length - 1; i >= 0; i--) {
1715
+ const entry = entries[i];
1716
+ const timestamp = new Date(entry.timestamp).getTime();
1717
+ if (!Number.isNaN(timestamp) && timestamp < cutoff) break;
1718
+ if (!isIntentionalCleanupStopEntry(entry)) continue;
1719
+ if (sessionId && entry.sessionId === sessionId) return true;
1720
+ if (!sessionId && nodeId && entry.nodeId === nodeId) return true;
1721
+ }
1722
+ return false;
1723
+ }
1724
+ function shouldSuppressIntentionalCleanupStop(args) {
1725
+ if (args.event !== "agent:stopped" && args.event !== "monitor:long_generating") return false;
1726
+ if (isIntentionalCleanupStopMetadata(args.metadataEvent)) return true;
1727
+ return hasRecentIntentionalCleanupStop(args.meshId, args.sessionId, args.nodeId);
1728
+ }
1312
1729
  function tryAssignQueueTask(components, meshId, nodeId, sessionId, providerType) {
1313
1730
  const task = claimNextTask(meshId, nodeId, sessionId);
1314
- if (!task) return false;
1731
+ if (!task) {
1732
+ return false;
1733
+ }
1315
1734
  LOG.info("MeshQueue", `Node ${nodeId} (${sessionId}) pulled task ${task.id}`);
1735
+ const mesh = getMeshWithCache(components, meshId);
1736
+ const node = mesh?.nodes.find((n) => n.id === nodeId);
1737
+ if (node?.daemonId && components.dispatchMeshCommand) {
1738
+ const isLocalNode = components.cliManager.adapters.has(sessionId);
1739
+ if (!isLocalNode) {
1740
+ components.dispatchMeshCommand(node.daemonId, "agent_command", {
1741
+ targetSessionId: sessionId,
1742
+ cliType: providerType,
1743
+ action: "send_chat",
1744
+ message: task.message
1745
+ }).catch((e) => {
1746
+ LOG.error("MeshQueue", `Failed to dispatch task via P2P to remote node ${nodeId}: ${e?.message}`);
1747
+ updateTaskStatus(meshId, task.id, "failed");
1748
+ });
1749
+ return true;
1750
+ }
1751
+ }
1316
1752
  components.cliManager.handleCliCommand("agent_command", {
1317
1753
  targetSessionId: sessionId,
1318
1754
  cliType: providerType,
1319
1755
  action: "send_chat",
1320
- input: task.message
1756
+ message: task.message
1321
1757
  }).catch((e) => {
1322
- LOG.error("MeshQueue", `Failed to dispatch task to node ${nodeId}: ${e?.message}`);
1758
+ LOG.error("MeshQueue", `Failed to dispatch task locally to node ${nodeId}: ${e?.message}`);
1759
+ updateTaskStatus(meshId, task.id, "failed");
1323
1760
  });
1324
1761
  return true;
1325
1762
  }
1326
- function triggerMeshQueue(components, meshId) {
1327
- const mesh = getMesh(meshId);
1763
+ function normalizeProviderPriority(policy) {
1764
+ const raw = policy && typeof policy === "object" && !Array.isArray(policy) ? policy.providerPriority : void 0;
1765
+ if (!Array.isArray(raw)) return [];
1766
+ const seen = /* @__PURE__ */ new Set();
1767
+ return raw.map((type) => typeof type === "string" ? type.trim() : "").filter(Boolean).filter((type) => {
1768
+ if (seen.has(type)) return false;
1769
+ seen.add(type);
1770
+ return true;
1771
+ });
1772
+ }
1773
+ function isTerminalSessionStatus(status) {
1774
+ return ["stopped", "failed", "terminated", "exited", "closed"].includes(status);
1775
+ }
1776
+ function isIdleSessionState(state) {
1777
+ const status = readNonEmptyString(state?.status).toLowerCase();
1778
+ if (isTerminalSessionStatus(status)) return false;
1779
+ return status === "idle" || state?.activeChat?.status === "waiting_input";
1780
+ }
1781
+ function isDirtyNode(node) {
1782
+ return node?.health === "dirty" || node?.git?.dirty === true;
1783
+ }
1784
+ function isLaunchableNode(node) {
1785
+ if (!node || node.status === "disabled" || node.status === "removed") return false;
1786
+ const health = readNonEmptyString(node.health).toLowerCase();
1787
+ if (!health) return true;
1788
+ return health === "online" || health === "unknown";
1789
+ }
1790
+ function localAutoLaunchSkipReason(node) {
1791
+ const daemonId = readNonEmptyString(node?.daemonId);
1792
+ const machineId = readNonEmptyString(node?.machineId);
1793
+ const appConfig = loadConfig();
1794
+ const localMachineId = readNonEmptyString(appConfig.machineId) || readNonEmptyString(appConfig.registeredMachineId);
1795
+ const cloudDaemonId = localMachineId ? `daemon_${localMachineId}` : "";
1796
+ const standaloneDaemonId = localMachineId ? `standalone_${localMachineId}` : "";
1797
+ const daemonMatchesLocal = !daemonId || daemonId === cloudDaemonId || daemonId === standaloneDaemonId;
1798
+ const machineMatchesLocal = !machineId || localMachineId && machineId === localMachineId;
1799
+ if (node?.isLocalWorktree === true) {
1800
+ return daemonMatchesLocal && machineMatchesLocal ? null : "remote_auto_launch_unsupported";
1801
+ }
1802
+ if (daemonId || machineId) {
1803
+ return daemonMatchesLocal && machineMatchesLocal ? null : "remote_auto_launch_unsupported";
1804
+ }
1805
+ return null;
1806
+ }
1807
+ function activeAssignedCount(meshId) {
1808
+ return getQueue(meshId, { status: ["assigned"] }).length;
1809
+ }
1810
+ function nodeHasActiveAssignment(meshId, nodeId) {
1811
+ return getQueue(meshId, { status: ["assigned"] }).some((task) => task.assignedNodeId === nodeId);
1812
+ }
1813
+ function liveSessionCountForNode(components, meshId, nodeId) {
1814
+ return components.instanceManager.getByCategory("cli").filter((inst) => {
1815
+ const state = inst.getState();
1816
+ const settings = state.settings || {};
1817
+ if (readNonEmptyString(settings.meshNodeFor) !== meshId) return false;
1818
+ const instNodeId = readNonEmptyString(settings.meshNodeId) || readNonEmptyString(settings.nodeId);
1819
+ if (instNodeId !== nodeId) return false;
1820
+ const status = readNonEmptyString(state.status).toLowerCase();
1821
+ return !isTerminalSessionStatus(status);
1822
+ }).length;
1823
+ }
1824
+ function recordAutoLaunchEvent(meshId, args) {
1825
+ try {
1826
+ appendLedgerEntry(meshId, {
1827
+ kind: "session_auto_launch",
1828
+ nodeId: args.nodeId,
1829
+ sessionId: args.sessionId,
1830
+ providerType: args.providerType,
1831
+ payload: {
1832
+ phase: args.phase,
1833
+ taskId: args.taskId,
1834
+ reason: args.reason,
1835
+ error: args.error
1836
+ }
1837
+ });
1838
+ } catch (e) {
1839
+ LOG.warn("MeshQueue", `Failed to record auto-launch ledger event: ${e?.message || e}`);
1840
+ }
1841
+ }
1842
+ function markAutoLaunch(meshId, taskId, args) {
1843
+ recordTaskAutoLaunch(meshId, taskId, {
1844
+ status: args.status,
1845
+ reason: args.reason || args.error,
1846
+ nodeId: args.nodeId,
1847
+ providerType: args.providerType,
1848
+ sessionId: args.sessionId
1849
+ });
1850
+ recordAutoLaunchEvent(meshId, {
1851
+ phase: args.status,
1852
+ taskId,
1853
+ nodeId: args.nodeId,
1854
+ providerType: args.providerType,
1855
+ sessionId: args.sessionId,
1856
+ reason: args.reason,
1857
+ error: args.error
1858
+ });
1859
+ }
1860
+ async function resolveUsableProvider(components, nodeId, node) {
1861
+ const providerPriority = normalizeProviderPriority(node?.policy);
1862
+ if (!providerPriority.length) return { reason: "missing_provider_priority" };
1863
+ const providerLoader = components.providerLoader;
1864
+ if (!providerLoader) return { reason: "provider_loader_unavailable" };
1865
+ const failed = [];
1866
+ for (const requestedType of providerPriority) {
1867
+ const normalizedType = typeof providerLoader.resolveAlias === "function" ? providerLoader.resolveAlias(requestedType) : requestedType;
1868
+ if (typeof providerLoader.isMachineProviderEnabled === "function" && !providerLoader.isMachineProviderEnabled(normalizedType)) {
1869
+ failed.push(`${requestedType}: disabled`);
1870
+ continue;
1871
+ }
1872
+ let detected;
1873
+ try {
1874
+ detected = await detectCLI(normalizedType, providerLoader, { includeVersion: false });
1875
+ } catch (e) {
1876
+ failed.push(`${requestedType}: detect failed: ${e?.message || e}`);
1877
+ continue;
1878
+ }
1879
+ if (typeof providerLoader.setCliDetectionResults === "function") {
1880
+ providerLoader.setCliDetectionResults([{
1881
+ id: normalizedType,
1882
+ installed: !!detected,
1883
+ path: detected?.path
1884
+ }], false);
1885
+ }
1886
+ components.onStatusChange?.();
1887
+ if (detected) return { providerType: normalizedType };
1888
+ failed.push(`${requestedType}: not detected`);
1889
+ }
1890
+ return { reason: `provider_priority_unusable: ${failed.join("; ") || nodeId}` };
1891
+ }
1892
+ async function maybeAutoLaunchOneQueueSession(components, meshId, mesh) {
1893
+ const queue = getQueue(meshId);
1894
+ const pending = queue.filter((task) => task.status === "pending");
1895
+ if (!pending.length) return false;
1896
+ const maxParallelTasks = Math.max(1, Math.floor(Number(mesh?.policy?.maxParallelTasks) || 2));
1897
+ for (const task of pending) {
1898
+ if (activeAssignedCount(meshId) >= maxParallelTasks) {
1899
+ markAutoLaunch(meshId, task.id, { status: "skipped", reason: "max_parallel_tasks_reached" });
1900
+ return false;
1901
+ }
1902
+ if (task.targetSessionId) {
1903
+ markAutoLaunch(meshId, task.id, { status: "skipped", reason: "target_session_constraint" });
1904
+ continue;
1905
+ }
1906
+ const candidateNodes = Array.isArray(mesh?.nodes) ? mesh.nodes.filter((node) => task.targetNodeId ? node?.id === task.targetNodeId : true) : [];
1907
+ if (!candidateNodes.length) {
1908
+ markAutoLaunch(meshId, task.id, { status: "skipped", reason: "no_matching_node", nodeId: task.targetNodeId });
1909
+ continue;
1910
+ }
1911
+ for (const node of candidateNodes) {
1912
+ const nodeId = readNonEmptyString(node?.id);
1913
+ if (!nodeId) continue;
1914
+ const launchKey = `${meshId}:${nodeId}`;
1915
+ const cooldownUntil = autoLaunchCooldownUntil.get(launchKey) || 0;
1916
+ if (autoLaunchInProgress.has(launchKey)) {
1917
+ markAutoLaunch(meshId, task.id, { status: "skipped", reason: "auto_launch_in_progress", nodeId });
1918
+ continue;
1919
+ }
1920
+ if (Date.now() < cooldownUntil) {
1921
+ markAutoLaunch(meshId, task.id, { status: "skipped", reason: "auto_launch_cooldown", nodeId });
1922
+ continue;
1923
+ }
1924
+ if (isDirtyNode(node)) {
1925
+ markAutoLaunch(meshId, task.id, { status: "skipped", reason: "dirty_workspace", nodeId });
1926
+ continue;
1927
+ }
1928
+ if (!isLaunchableNode(node)) {
1929
+ markAutoLaunch(meshId, task.id, { status: "skipped", reason: "node_not_launch_ready", nodeId });
1930
+ continue;
1931
+ }
1932
+ const localSkipReason = localAutoLaunchSkipReason(node);
1933
+ if (localSkipReason) {
1934
+ markAutoLaunch(meshId, task.id, { status: "skipped", reason: localSkipReason, nodeId });
1935
+ continue;
1936
+ }
1937
+ if (nodeHasActiveAssignment(meshId, nodeId)) {
1938
+ markAutoLaunch(meshId, task.id, { status: "skipped", reason: "node_has_active_assignment", nodeId });
1939
+ continue;
1940
+ }
1941
+ const maxConcurrentSessions = Number(node?.policy?.maxConcurrentSessions);
1942
+ if (Number.isFinite(maxConcurrentSessions) && maxConcurrentSessions >= 0 && liveSessionCountForNode(components, meshId, nodeId) >= maxConcurrentSessions) {
1943
+ markAutoLaunch(meshId, task.id, { status: "skipped", reason: "max_concurrent_sessions_reached", nodeId });
1944
+ continue;
1945
+ }
1946
+ autoLaunchInProgress.add(launchKey);
1947
+ try {
1948
+ const resolved = await resolveUsableProvider(components, nodeId, node);
1949
+ if (!resolved.providerType) {
1950
+ markAutoLaunch(meshId, task.id, { status: "skipped", reason: resolved.reason || "provider_unusable", nodeId });
1951
+ continue;
1952
+ }
1953
+ markAutoLaunch(meshId, task.id, { status: "started", nodeId, providerType: resolved.providerType });
1954
+ const launchResult = await components.cliManager.handleCliCommand("launch_cli", {
1955
+ cliType: resolved.providerType,
1956
+ dir: node.workspace,
1957
+ settings: {
1958
+ meshNodeFor: meshId,
1959
+ meshNodeId: nodeId,
1960
+ spawnedSessionVisibility: mesh?.policy?.spawnedSessionVisibility || "hidden",
1961
+ launchedByCoordinator: true,
1962
+ autoLaunchedForQueueTaskId: task.id
1963
+ }
1964
+ });
1965
+ if (!launchResult?.success) {
1966
+ const reason = launchResult?.error || "launch_cli_failed";
1967
+ markAutoLaunch(meshId, task.id, { status: "failed", reason, nodeId, providerType: resolved.providerType });
1968
+ autoLaunchCooldownUntil.set(launchKey, Date.now() + AUTO_LAUNCH_COOLDOWN_MS);
1969
+ return false;
1970
+ }
1971
+ const sessionId = readNonEmptyString(launchResult.sessionId) || readNonEmptyString(launchResult.id) || readNonEmptyString(launchResult.runtimeSessionId);
1972
+ if (!sessionId) {
1973
+ markAutoLaunch(meshId, task.id, { status: "failed", reason: "launch_missing_session_id", nodeId, providerType: resolved.providerType });
1974
+ autoLaunchCooldownUntil.set(launchKey, Date.now() + AUTO_LAUNCH_COOLDOWN_MS);
1975
+ return false;
1976
+ }
1977
+ markAutoLaunch(meshId, task.id, { status: "completed", nodeId, providerType: resolved.providerType, sessionId });
1978
+ tryAssignQueueTask(components, meshId, nodeId, sessionId, resolved.providerType);
1979
+ return true;
1980
+ } catch (e) {
1981
+ markAutoLaunch(meshId, task.id, { status: "failed", error: e?.message || String(e), nodeId });
1982
+ autoLaunchCooldownUntil.set(launchKey, Date.now() + AUTO_LAUNCH_COOLDOWN_MS);
1983
+ return false;
1984
+ } finally {
1985
+ autoLaunchInProgress.delete(launchKey);
1986
+ }
1987
+ }
1988
+ }
1989
+ return false;
1990
+ }
1991
+ async function triggerMeshQueue(components, meshId) {
1992
+ const mesh = getMeshWithCache(components, meshId);
1328
1993
  if (!mesh) return;
1329
1994
  const cliInstances = components.instanceManager.getByCategory("cli");
1330
1995
  for (const inst of cliInstances) {
1331
1996
  const state = inst.getState();
1332
1997
  const settings = state.settings || {};
1333
1998
  const instMeshId = readNonEmptyString(settings.meshNodeFor);
1334
- if (instMeshId !== meshId && !settings.launchedByCoordinator) continue;
1999
+ if (instMeshId !== meshId) continue;
1335
2000
  const nodeId = readNonEmptyString(settings.meshNodeId) || readNonEmptyString(settings.nodeId);
1336
2001
  if (!nodeId) continue;
1337
- if (state.status !== "idle" && state.status !== "stopped" && state.activeChat?.status !== "waiting_input") continue;
2002
+ if (!isIdleSessionState(state)) continue;
1338
2003
  const sessionId = state.instanceId;
1339
2004
  const providerType = state.type || readNonEmptyString(settings.providerType);
1340
2005
  if (providerType) {
1341
2006
  tryAssignQueueTask(components, meshId, nodeId, sessionId, providerType);
1342
2007
  }
1343
2008
  }
2009
+ for (const [key, idle] of remoteIdleSessions.entries()) {
2010
+ const node = mesh.nodes.find((n) => n.id === idle.nodeId);
2011
+ if (node) {
2012
+ const assigned = tryAssignQueueTask(components, meshId, idle.nodeId, idle.sessionId, idle.providerType);
2013
+ if (assigned) {
2014
+ remoteIdleSessions.delete(key);
2015
+ }
2016
+ }
2017
+ }
2018
+ await maybeAutoLaunchOneQueueSession(components, meshId, mesh);
1344
2019
  }
1345
2020
  function buildMeshSystemMessage(args) {
1346
2021
  const metadata = formatCompletionMetadata(args.metadataEvent);
@@ -1387,20 +2062,91 @@ Do NOT retry on this node. Consider reassigning to a different node or asking th
1387
2062
  return "";
1388
2063
  }
1389
2064
  function injectMeshSystemMessage(components, args) {
2065
+ const eventSessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId);
2066
+ const eventNodeId = readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId);
2067
+ const intentionalCleanupStop = shouldSuppressIntentionalCleanupStop({
2068
+ event: args.event,
2069
+ meshId: args.meshId,
2070
+ metadataEvent: args.metadataEvent,
2071
+ sessionId: eventSessionId || void 0,
2072
+ nodeId: eventNodeId || void 0
2073
+ });
2074
+ if (intentionalCleanupStop) {
2075
+ if (eventSessionId && eventNodeId) {
2076
+ remoteIdleSessions.delete(`${eventNodeId}:${eventSessionId}`);
2077
+ }
2078
+ LOG.info("MeshEvents", `Suppressed ${args.event} for intentionally cleanup-stopped session ${eventSessionId || "(unknown session)"}`);
2079
+ return { success: true, forwarded: 0, suppressed: true, intentionalCleanupStop: true };
2080
+ }
2081
+ let completedTaskForLedger = null;
1390
2082
  if (args.event === "agent:generating_completed") {
1391
- const sessionId = readNonEmptyString(args.metadataEvent.targetSessionId);
1392
- const nodeId = readNonEmptyString(args.metadataEvent.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId);
2083
+ const sessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId);
2084
+ const nodeId = readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId);
1393
2085
  const providerType = readNonEmptyString(args.metadataEvent.providerType);
1394
2086
  if (sessionId) {
1395
- updateSessionTaskStatus(args.meshId, sessionId, "completed");
2087
+ const completedTask = updateSessionTaskStatus(args.meshId, sessionId, "completed");
2088
+ completedTaskForLedger = completedTask ? { id: completedTask.id } : null;
1396
2089
  if (nodeId && providerType) {
1397
2090
  setTimeout(() => {
1398
2091
  tryAssignQueueTask(components, args.meshId, nodeId, sessionId, providerType);
1399
2092
  }, 500);
1400
2093
  }
1401
2094
  }
2095
+ } else if (args.event === "agent:ready") {
2096
+ const sessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId);
2097
+ const nodeId = readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId);
2098
+ const providerType = readNonEmptyString(args.metadataEvent.providerType);
2099
+ const completedTask = sessionId ? updateSessionTaskStatus(args.meshId, sessionId, "completed") : null;
2100
+ if (completedTask) {
2101
+ completedTaskForLedger = { id: completedTask.id };
2102
+ try {
2103
+ appendLedgerEntry(args.meshId, {
2104
+ kind: "task_completed",
2105
+ nodeId: nodeId || void 0,
2106
+ sessionId,
2107
+ providerType: providerType || void 0,
2108
+ payload: {
2109
+ event: args.event,
2110
+ nodeLabel: args.nodeLabel,
2111
+ taskId: completedTask.id,
2112
+ completedViaReady: true,
2113
+ providerSessionId: readNonEmptyString(args.metadataEvent.providerSessionId) || void 0,
2114
+ finalSummary: readNonEmptyString(args.metadataEvent.finalSummary) || void 0,
2115
+ evidence: buildTaskCompletionEvidence({
2116
+ event: "agent:ready",
2117
+ nodeId,
2118
+ sessionId,
2119
+ providerType: providerType || void 0,
2120
+ providerSessionId: readNonEmptyString(args.metadataEvent.providerSessionId) || void 0,
2121
+ finalSummary: readNonEmptyString(args.metadataEvent.finalSummary) || void 0
2122
+ })
2123
+ }
2124
+ });
2125
+ } catch (e) {
2126
+ LOG.warn("MeshLedger", `Failed to record task_completed from ready: ${e?.message || e}`);
2127
+ }
2128
+ }
2129
+ if (sessionId && nodeId && providerType) {
2130
+ remoteIdleSessions.set(`${nodeId}:${sessionId}`, { nodeId, sessionId, providerType });
2131
+ setTimeout(() => {
2132
+ const assigned = tryAssignQueueTask(components, args.meshId, nodeId, sessionId, providerType);
2133
+ if (assigned) {
2134
+ remoteIdleSessions.delete(`${nodeId}:${sessionId}`);
2135
+ }
2136
+ }, 500);
2137
+ }
2138
+ } else if (args.event === "agent:generating_started") {
2139
+ const sessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId);
2140
+ const nodeId = readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId);
2141
+ if (sessionId && nodeId) {
2142
+ remoteIdleSessions.delete(`${nodeId}:${sessionId}`);
2143
+ }
1402
2144
  } else if (args.event === "agent:stopped") {
1403
- const sessionId = readNonEmptyString(args.metadataEvent.targetSessionId);
2145
+ const sessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId);
2146
+ const nodeId = readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId);
2147
+ if (sessionId && nodeId) {
2148
+ remoteIdleSessions.delete(`${nodeId}:${sessionId}`);
2149
+ }
1404
2150
  if (sessionId) {
1405
2151
  updateSessionTaskStatus(args.meshId, sessionId, "failed");
1406
2152
  }
@@ -1408,15 +2154,29 @@ function injectMeshSystemMessage(components, args) {
1408
2154
  const ledgerKind = EVENT_TO_LEDGER_KIND[args.event];
1409
2155
  if (ledgerKind) {
1410
2156
  try {
2157
+ const ledgerNodeId = readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId) || void 0;
2158
+ const ledgerSessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId) || void 0;
2159
+ const ledgerProviderType = readNonEmptyString(args.metadataEvent.providerType) || void 0;
2160
+ const completionEvidence = ledgerKind === "task_completed" && ledgerNodeId && ledgerSessionId ? buildTaskCompletionEvidence({
2161
+ event: "agent:generating_completed",
2162
+ nodeId: ledgerNodeId,
2163
+ sessionId: ledgerSessionId,
2164
+ providerType: ledgerProviderType,
2165
+ providerSessionId: readNonEmptyString(args.metadataEvent.providerSessionId) || void 0,
2166
+ finalSummary: readNonEmptyString(args.metadataEvent.finalSummary) || void 0
2167
+ }) : void 0;
1411
2168
  appendLedgerEntry(args.meshId, {
1412
2169
  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,
2170
+ nodeId: ledgerNodeId,
2171
+ sessionId: ledgerSessionId,
2172
+ providerType: ledgerProviderType,
1416
2173
  payload: {
1417
2174
  event: args.event,
1418
2175
  nodeLabel: args.nodeLabel,
1419
- providerSessionId: readNonEmptyString(args.metadataEvent.providerSessionId) || void 0
2176
+ taskId: completedTaskForLedger?.id || void 0,
2177
+ providerSessionId: readNonEmptyString(args.metadataEvent.providerSessionId) || void 0,
2178
+ finalSummary: readNonEmptyString(args.metadataEvent.finalSummary) || void 0,
2179
+ evidence: completionEvidence
1420
2180
  }
1421
2181
  });
1422
2182
  } catch (e) {
@@ -1429,8 +2189,8 @@ function injectMeshSystemMessage(components, args) {
1429
2189
  const mesh = getMesh(args.meshId);
1430
2190
  const maxRetries = mesh?.policy?.maxTaskRetries ?? 1;
1431
2191
  recoveryContext = getSessionRecoveryContext(args.meshId, {
1432
- sessionId: readNonEmptyString(args.metadataEvent.targetSessionId) || void 0,
1433
- nodeId: readNonEmptyString(args.metadataEvent.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId) || void 0,
2192
+ sessionId: resolveEventSessionId(args.metadataEvent, args.sourceInstanceId) || void 0,
2193
+ nodeId: readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId) || void 0,
1434
2194
  maxRetries
1435
2195
  });
1436
2196
  recoveryContext.failedProviderType = readNonEmptyString(args.metadataEvent.providerType) || null;
@@ -1525,12 +2285,21 @@ function handleMeshForwardEvent(components, payload) {
1525
2285
  const nodeLabel = nodeId ? `Node '${nodeId}'` : workspace ? `Agent at ${workspace}` : "Remote agent";
1526
2286
  return injectMeshSystemMessage(components, {
1527
2287
  meshId,
2288
+ nodeId,
1528
2289
  nodeLabel,
1529
2290
  event: eventName,
1530
2291
  metadataEvent: {
1531
- targetSessionId: readNonEmptyString(payload.targetSessionId) || readNonEmptyString(payload.sessionId),
2292
+ targetSessionId: readNonEmptyString(payload.targetSessionId) || readNonEmptyString(payload.sessionId) || readNonEmptyString(payload.instanceId),
1532
2293
  providerType: readNonEmptyString(payload.providerType),
1533
- providerSessionId: readNonEmptyString(payload.providerSessionId)
2294
+ providerSessionId: readNonEmptyString(payload.providerSessionId),
2295
+ finalSummary: readNonEmptyString(payload.finalSummary) || readNonEmptyString(payload.summary),
2296
+ intentional: payload.intentional === true,
2297
+ intentionalStop: payload.intentionalStop === true,
2298
+ operatorCleanup: payload.operatorCleanup === true,
2299
+ reason: readNonEmptyString(payload.reason),
2300
+ stopReason: readNonEmptyString(payload.stopReason),
2301
+ cleanupReason: readNonEmptyString(payload.cleanupReason),
2302
+ source: readNonEmptyString(payload.source)
1534
2303
  }
1535
2304
  });
1536
2305
  }
@@ -1549,35 +2318,42 @@ function setupMeshEventForwarding(components) {
1549
2318
  const meshIdFromRuntime = readNonEmptyString(settings.meshNodeFor);
1550
2319
  const isMeshDelegate = Boolean(meshIdFromRuntime || settings.launchedByCoordinator);
1551
2320
  if (!isMeshDelegate) return;
1552
- const mesh = meshIdFromRuntime ? getMesh(meshIdFromRuntime) : getMeshByRepo(workspace);
2321
+ const mesh = meshIdFromRuntime ? getMeshWithCache(components, meshIdFromRuntime) : getMeshByRepo(workspace);
1553
2322
  const meshId = meshIdFromRuntime || readNonEmptyString(mesh?.id);
1554
2323
  if (!meshId) return;
1555
2324
  const targetNode = mesh?.nodes?.find((n) => n.workspace === workspace);
1556
2325
  const runtimeNodeId = readNonEmptyString(settings.meshNodeId);
2326
+ const resolvedNodeId = targetNode?.id || runtimeNodeId;
1557
2327
  const nodeLabel = targetNode ? `Node '${targetNode.id}'` : runtimeNodeId ? `Node '${runtimeNodeId}'` : `Agent at ${workspace}`;
1558
2328
  injectMeshSystemMessage(components, {
1559
2329
  meshId,
1560
2330
  sourceInstanceId: instanceId,
2331
+ nodeId: resolvedNodeId,
1561
2332
  nodeLabel,
1562
2333
  event: event.event,
1563
2334
  metadataEvent: event
1564
2335
  });
1565
2336
  });
1566
2337
  }
1567
- var MAX_PENDING_EVENTS, pendingMeshCoordinatorEvents, MESH_COORDINATOR_EVENTS, EVENT_TO_LEDGER_KIND;
2338
+ 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
2339
  var init_mesh_events = __esm({
1569
2340
  "src/mesh/mesh-events.ts"() {
1570
2341
  "use strict";
2342
+ init_config();
1571
2343
  init_mesh_config();
2344
+ init_cli_detector();
1572
2345
  init_logger();
1573
2346
  init_mesh_ledger();
1574
2347
  init_mesh_work_queue();
2348
+ remoteIdleSessions = /* @__PURE__ */ new Map();
1575
2349
  MAX_PENDING_EVENTS = 50;
1576
2350
  pendingMeshCoordinatorEvents = [];
1577
2351
  MESH_COORDINATOR_EVENTS = /* @__PURE__ */ new Set([
2352
+ "agent:generating_started",
1578
2353
  "agent:generating_completed",
1579
2354
  "agent:waiting_approval",
1580
2355
  "agent:stopped",
2356
+ "agent:ready",
1581
2357
  "monitor:long_generating"
1582
2358
  ]);
1583
2359
  EVENT_TO_LEDGER_KIND = {
@@ -1586,6 +2362,10 @@ var init_mesh_events = __esm({
1586
2362
  "agent:stopped": "task_failed",
1587
2363
  "monitor:long_generating": "task_stalled"
1588
2364
  };
2365
+ INTENTIONAL_CLEANUP_STOP_SUPPRESSION_MS = 30 * 60 * 1e3;
2366
+ autoLaunchInProgress = /* @__PURE__ */ new Set();
2367
+ autoLaunchCooldownUntil = /* @__PURE__ */ new Map();
2368
+ AUTO_LAUNCH_COOLDOWN_MS = 5e3;
1589
2369
  }
1590
2370
  });
1591
2371
 
@@ -2613,6 +3393,7 @@ var init_provider_cli_adapter = __esm({
2613
3393
  this.requirePromptEchoBeforeSubmit = resolvedConfig.requirePromptEchoBeforeSubmit;
2614
3394
  this.providerResolutionMeta = resolvedConfig.providerResolutionMeta;
2615
3395
  this.cliScripts = provider.scripts || {};
3396
+ this.scriptState = typeof this.cliScripts.createState === "function" ? this.cliScripts.createState() ?? null : null;
2616
3397
  const scriptNames = listCliScriptNames(this.cliScripts);
2617
3398
  if (scriptNames.length > 0) {
2618
3399
  LOG.info("CLI", `[${this.cliType}] CLI scripts: [${scriptNames.join(", ")}]`);
@@ -2695,6 +3476,8 @@ var init_provider_cli_adapter = __esm({
2695
3476
  statusHistory = [];
2696
3477
  // ─── CLI Scripts (script-based parsing) ───
2697
3478
  cliScripts;
3479
+ /** Per-session opaque state object created by cliScripts.createState(), reset on stop. */
3480
+ scriptState = null;
2698
3481
  runtimeSettings = {};
2699
3482
  /** Full accumulated rendered PTY transcript for parser/readback use */
2700
3483
  accumulatedBuffer = "";
@@ -2771,9 +3554,13 @@ ${lastSnapshot}`;
2771
3554
  this.lastScreenChangeAt = 0;
2772
3555
  this.lastScreenSnapshotReadAt = Number.NEGATIVE_INFINITY;
2773
3556
  }
3557
+ getAccumulatedRawBufferCacheKey() {
3558
+ return this.accumulatedRawBuffer.replace(/\x1B\][^\x07]*(?:\x07|\x1B\\)/g, "").replace(/\x1B[P^_X][\s\S]*?(?:\x07|\x1B\\)/g, "").replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, "");
3559
+ }
2774
3560
  getFreshParsedStatusCache() {
2775
3561
  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) {
3562
+ const accumulatedRawBufferKey = this.getAccumulatedRawBufferCacheKey();
3563
+ 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
3564
  return cached.result;
2778
3565
  }
2779
3566
  return null;
@@ -2876,6 +3663,7 @@ ${lastSnapshot}`;
2876
3663
  this.cliScripts = scripts;
2877
3664
  this.parsedStatusCache = null;
2878
3665
  this.parseErrorMessage = null;
3666
+ this.scriptState = typeof scripts.createState === "function" ? scripts.createState() ?? null : null;
2879
3667
  const scriptNames = listCliScriptNames(scripts);
2880
3668
  LOG.info("CLI", `[${this.cliType}] CLI scripts injected: [${scriptNames.join(", ")}]`);
2881
3669
  }
@@ -2993,6 +3781,7 @@ ${lastSnapshot}`;
2993
3781
  this.ready = false;
2994
3782
  this.startupParseGate = false;
2995
3783
  this.spawnAt = 0;
3784
+ this.scriptState = null;
2996
3785
  this.onStatusChange?.();
2997
3786
  });
2998
3787
  this.spawnAt = Date.now();
@@ -3746,6 +4535,11 @@ ${lastSnapshot}`;
3746
4535
  };
3747
4536
  }
3748
4537
  // ─── Script Execution ──────────────────────────
4538
+ invokeCliScript(script, input) {
4539
+ const hasStateFactory = typeof this.cliScripts?.createState === "function";
4540
+ const expectsStateArgument = hasStateFactory || this.scriptState !== null || script.length >= 2;
4541
+ return expectsStateArgument ? script(this.scriptState, input) : script(input);
4542
+ }
3749
4543
  runParseSession() {
3750
4544
  if (typeof this.cliScripts?.parseSession !== "function") {
3751
4545
  this.parseErrorMessage = `${this.cliType} parseSession unavailable`;
@@ -3766,7 +4560,10 @@ ${lastSnapshot}`;
3766
4560
  scope: this.currentTurnScope,
3767
4561
  runtimeSettings: this.runtimeSettings
3768
4562
  });
3769
- const session = this.cliScripts.parseSession({ ...input, tail, tailScreen: buildCliScreenSnapshot(tail) });
4563
+ const session = this.invokeCliScript(
4564
+ this.cliScripts.parseSession,
4565
+ { ...input, tail, tailScreen: buildCliScreenSnapshot(tail) }
4566
+ );
3770
4567
  this.parseErrorMessage = null;
3771
4568
  return session && typeof session === "object" ? session : null;
3772
4569
  } catch (e) {
@@ -3780,7 +4577,7 @@ ${lastSnapshot}`;
3780
4577
  if (!this.cliScripts?.detectStatus) return null;
3781
4578
  try {
3782
4579
  const screenText = this.terminalScreen.getText();
3783
- const status = this.cliScripts.detectStatus({
4580
+ const status = this.invokeCliScript(this.cliScripts.detectStatus, {
3784
4581
  tail: text.slice(-500),
3785
4582
  screenText,
3786
4583
  rawBuffer: this.accumulatedRawBuffer,
@@ -3799,7 +4596,7 @@ ${lastSnapshot}`;
3799
4596
  try {
3800
4597
  const screenText = this.terminalScreen.getText();
3801
4598
  const buffer = screenText || this.accumulatedBuffer;
3802
- return this.cliScripts.parseApproval({
4599
+ return this.invokeCliScript(this.cliScripts.parseApproval, {
3803
4600
  buffer,
3804
4601
  screenText,
3805
4602
  rawBuffer: this.accumulatedRawBuffer,
@@ -3855,7 +4652,8 @@ ${lastSnapshot}`;
3855
4652
  const screenText = this.readTerminalScreenText();
3856
4653
  const parseScreenText = this.getParseScreenText(screenText);
3857
4654
  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) {
4655
+ const accumulatedRawBufferKey = this.getAccumulatedRawBufferCacheKey();
4656
+ 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
4657
  return cached.result;
3860
4658
  }
3861
4659
  const parsed = this.runParseSession();
@@ -3883,6 +4681,7 @@ ${lastSnapshot}`;
3883
4681
  currentTurnScope: this.currentTurnScope,
3884
4682
  recentOutputBuffer: this.recentOutputBuffer,
3885
4683
  accumulatedBuffer: this.accumulatedBuffer,
4684
+ accumulatedRawBufferKey,
3886
4685
  screenText: parseScreenText,
3887
4686
  currentStatus: this.currentStatus,
3888
4687
  activeModal: this.activeModal,
@@ -3907,7 +4706,7 @@ ${lastSnapshot}`;
3907
4706
  scope: this.currentTurnScope,
3908
4707
  runtimeSettings: this.runtimeSettings
3909
4708
  });
3910
- return await Promise.resolve(fn({
4709
+ return await Promise.resolve(this.invokeCliScript(fn, {
3911
4710
  ...input,
3912
4711
  args: args && typeof args === "object" ? { ...args } : {}
3913
4712
  }));
@@ -6124,7 +6923,7 @@ function addWorkspaceEntry(config, rawPath, label, options) {
6124
6923
  }
6125
6924
  }
6126
6925
  const v = validateWorkspacePath(abs);
6127
- if (!v.ok) return { error: v.error };
6926
+ if (v.ok !== true) return { error: v.error };
6128
6927
  const list = [...config.workspaces || []];
6129
6928
  if (list.some((w) => path5.resolve(w.path) === abs)) {
6130
6929
  return { error: "Workspace already in list" };
@@ -6507,36 +7306,188 @@ async function syncMeshes(transport) {
6507
7306
  }
6508
7307
  }
6509
7308
  }
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
7309
  return result;
6520
7310
  }
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
7311
 
6531
7312
  // src/index.ts
6532
7313
  init_mesh_ledger();
7314
+
7315
+ // src/mesh/mesh-ledger-reconciliation.ts
7316
+ function lastTimestamp(slice) {
7317
+ const entries = Array.isArray(slice?.entries) ? slice.entries : [];
7318
+ return entries.length ? entries[entries.length - 1].timestamp : null;
7319
+ }
7320
+ function buildMeshLedgerReplicaEvidence(args) {
7321
+ const entriesReceived = Array.isArray(args.slice?.entries) ? args.slice.entries.length : 0;
7322
+ return {
7323
+ nodeId: args.nodeId,
7324
+ ...args.daemonId ? { daemonId: args.daemonId } : {},
7325
+ status: args.status ?? (args.importResult && args.importResult.accepted > 0 ? "imported" : "queried"),
7326
+ transport: args.transport,
7327
+ protocol: "adhdev.mesh.ledger.slice.v1",
7328
+ entriesReceived,
7329
+ entriesImported: args.importResult?.accepted ?? 0,
7330
+ skippedDuplicate: args.importResult?.skippedDuplicate ?? 0,
7331
+ rejectedInvalid: args.importResult?.rejectedInvalid ?? 0,
7332
+ hasMore: args.slice?.cursor?.hasMore === true,
7333
+ nextAfterId: args.slice?.cursor?.nextAfterId ?? null,
7334
+ lastTimestamp: lastTimestamp(args.slice),
7335
+ ...args.slice?.summary ? { summary: args.slice.summary } : {},
7336
+ ...args.error ? {
7337
+ error: args.error,
7338
+ noFallbackReason: "Ledger reconciliation is P2P/local-first only; Cloud/D1 ledger sync is intentionally disabled."
7339
+ } : {}
7340
+ };
7341
+ }
7342
+ function buildMeshLedgerReconciliationEvidence(meshId, replicas) {
7343
+ const failedNodes = replicas.filter((replica) => replica.status === "failed").map((replica) => replica.nodeId);
7344
+ const pendingNodes = replicas.filter((replica) => replica.hasMore && replica.status !== "failed").map((replica) => replica.nodeId);
7345
+ return {
7346
+ protocol: "adhdev.mesh.ledger.reconciliation.v1",
7347
+ meshId,
7348
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
7349
+ sourceOfTruth: {
7350
+ kind: "coordinator_local_jsonl",
7351
+ p2pOnly: true,
7352
+ cloudD1LedgerSync: false,
7353
+ notes: "Coordinator reconciles bounded slices from daemon-local JSONL ledgers over P2P DataChannel; Cloud/D1 is not a ledger source of truth."
7354
+ },
7355
+ replicas,
7356
+ totals: {
7357
+ replicas: replicas.length,
7358
+ queried: replicas.filter((replica) => replica.status !== "failed").length,
7359
+ failed: failedNodes.length,
7360
+ entriesReceived: replicas.reduce((sum, replica) => sum + replica.entriesReceived, 0),
7361
+ entriesImported: replicas.reduce((sum, replica) => sum + replica.entriesImported, 0),
7362
+ skippedDuplicate: replicas.reduce((sum, replica) => sum + replica.skippedDuplicate, 0),
7363
+ rejectedInvalid: replicas.reduce((sum, replica) => sum + replica.rejectedInvalid, 0)
7364
+ },
7365
+ convergence: {
7366
+ complete: failedNodes.length === 0 && pendingNodes.length === 0,
7367
+ pendingNodes,
7368
+ failedNodes
7369
+ }
7370
+ };
7371
+ }
7372
+
7373
+ // src/index.ts
6533
7374
  init_mesh_work_queue();
6534
7375
  init_mesh_events();
6535
7376
 
7377
+ // src/mesh/p2p-relay-failure.ts
7378
+ var NO_FALLBACK_REASON = "Repo Mesh command/data-plane is P2P-only; WS/REST command fallback is intentionally disabled to preserve the transport boundary.";
7379
+ 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.";
7380
+ var NON_P2P_NEXT_ACTION = "Inspect the provider/command error and fix the underlying logic or configuration before retrying.";
7381
+ function messageFromError(error) {
7382
+ if (error instanceof Error) return error.message;
7383
+ if (typeof error === "string") return error;
7384
+ if (error && typeof error === "object") {
7385
+ const candidate = error.error ?? error.message ?? error.reason;
7386
+ if (typeof candidate === "string") return candidate;
7387
+ }
7388
+ return String(error || "mesh relay command failed");
7389
+ }
7390
+ function classifyP2pRelayFailure(error, _context = {}) {
7391
+ const message = messageFromError(error);
7392
+ const lower = message.toLowerCase();
7393
+ const hasP2pSignal = /p2p|datachannel|node-datachannel|webrtc|ice|mesh_relay_command|daemon_mesh_p2p_transport/i.test(message);
7394
+ 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);
7395
+ if (/requires targetdaemonid and command|providerpriority|no inference provider|permission denied|read-only|not a member/i.test(message)) {
7396
+ return {
7397
+ code: "mesh_logic_or_provider_failure",
7398
+ reason: "mesh_logic_or_provider_failure",
7399
+ transport: "unknown",
7400
+ recoverable: false,
7401
+ retryRecommended: false,
7402
+ nextAction: NON_P2P_NEXT_ACTION,
7403
+ noFallbackReason: NO_FALLBACK_REASON
7404
+ };
7405
+ }
7406
+ let code = null;
7407
+ let reason = "";
7408
+ if (/timeout|timed out/i.test(message) && (hasP2pSignal || /mesh transport/i.test(message))) {
7409
+ code = "p2p_timeout";
7410
+ reason = "daemon_mesh_p2p_timeout";
7411
+ } else if (/no route|route unavailable/i.test(message)) {
7412
+ code = "p2p_no_route";
7413
+ reason = "daemon_mesh_p2p_no_route";
7414
+ } else if (/offline|not owned|not found|not connected to server/i.test(message) && /daemon|peer|target/i.test(message)) {
7415
+ code = "p2p_daemon_offline";
7416
+ reason = "daemon_mesh_target_offline";
7417
+ } else if (/closed|disconnected/i.test(message) && (hasP2pSignal || /state changed/i.test(message))) {
7418
+ code = "p2p_datachannel_closed";
7419
+ reason = "daemon_mesh_p2p_datachannel_closed";
7420
+ } else if (/not connected|cannot send|cannot establish/i.test(message) && hasP2pSignal) {
7421
+ code = "p2p_not_connected";
7422
+ reason = "daemon_mesh_p2p_not_connected";
7423
+ } else if (hasP2pSignal && hasFailureSignal) {
7424
+ code = "p2p_unavailable";
7425
+ reason = "daemon_mesh_p2p_transport_unavailable";
7426
+ }
7427
+ if (!code) {
7428
+ return {
7429
+ code: "mesh_logic_or_provider_failure",
7430
+ reason: "mesh_logic_or_provider_failure",
7431
+ transport: "unknown",
7432
+ recoverable: false,
7433
+ retryRecommended: false,
7434
+ nextAction: NON_P2P_NEXT_ACTION,
7435
+ noFallbackReason: NO_FALLBACK_REASON
7436
+ };
7437
+ }
7438
+ return {
7439
+ code,
7440
+ reason,
7441
+ transport: "p2p",
7442
+ recoverable: true,
7443
+ retryRecommended: true,
7444
+ nextAction: P2P_NEXT_ACTION,
7445
+ noFallbackReason: NO_FALLBACK_REASON
7446
+ };
7447
+ }
7448
+ function isP2pRelayTransportFailure(error) {
7449
+ return classifyP2pRelayFailure(error).recoverable === true;
7450
+ }
7451
+ function buildP2pRelayFailurePayload(error, context = {}) {
7452
+ const classification = classifyP2pRelayFailure(error, context);
7453
+ return {
7454
+ success: false,
7455
+ ...classification,
7456
+ error: messageFromError(error),
7457
+ ...context.command ? { command: context.command } : {},
7458
+ ...context.targetDaemonId ? { targetDaemonId: context.targetDaemonId } : {}
7459
+ };
7460
+ }
7461
+ var P2pRelayFailureError = class extends Error {
7462
+ code;
7463
+ reason;
7464
+ transport;
7465
+ recoverable;
7466
+ retryRecommended;
7467
+ nextAction;
7468
+ noFallbackReason;
7469
+ command;
7470
+ targetDaemonId;
7471
+ constructor(message, context = {}) {
7472
+ super(message);
7473
+ this.name = "P2pRelayFailureError";
7474
+ const payload = buildP2pRelayFailurePayload(message, context);
7475
+ this.code = payload.code;
7476
+ this.reason = payload.reason;
7477
+ this.transport = payload.transport;
7478
+ this.recoverable = payload.recoverable;
7479
+ this.retryRecommended = payload.retryRecommended;
7480
+ this.nextAction = payload.nextAction;
7481
+ this.noFallbackReason = payload.noFallbackReason;
7482
+ this.command = context.command;
7483
+ this.targetDaemonId = context.targetDaemonId;
7484
+ }
7485
+ };
7486
+
6536
7487
  // src/config/state-store.ts
6537
7488
  init_config();
6538
- import { existsSync as existsSync8, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
6539
- import { join as join8 } from "path";
7489
+ import { existsSync as existsSync9, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
7490
+ import { join as join9 } from "path";
6540
7491
  var DEFAULT_STATE = {
6541
7492
  recentActivity: [],
6542
7493
  savedProviderSessions: [],
@@ -6549,7 +7500,7 @@ function isPlainObject2(value) {
6549
7500
  return !!value && typeof value === "object" && !Array.isArray(value);
6550
7501
  }
6551
7502
  function getStatePath() {
6552
- return join8(getConfigDir(), "state.json");
7503
+ return join9(getConfigDir(), "state.json");
6553
7504
  }
6554
7505
  function normalizeState(raw) {
6555
7506
  const parsed = isPlainObject2(raw) ? raw : {};
@@ -6585,7 +7536,7 @@ function normalizeState(raw) {
6585
7536
  }
6586
7537
  function loadState() {
6587
7538
  const statePath = getStatePath();
6588
- if (!existsSync8(statePath)) {
7539
+ if (!existsSync9(statePath)) {
6589
7540
  return { ...DEFAULT_STATE };
6590
7541
  }
6591
7542
  try {
@@ -6606,9 +7557,9 @@ function resetState() {
6606
7557
 
6607
7558
  // src/detection/ide-detector.ts
6608
7559
  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";
7560
+ import { existsSync as existsSync10 } from "fs";
7561
+ import { platform as platform2, homedir as homedir5 } from "os";
7562
+ import * as path10 from "path";
6612
7563
  var BUILTIN_IDE_DEFINITIONS = [];
6613
7564
  var registeredIDEs = /* @__PURE__ */ new Map();
6614
7565
  function registerIDEDefinition(def) {
@@ -6627,14 +7578,14 @@ function getMergedDefinitions() {
6627
7578
  function findCliCommand(command) {
6628
7579
  const trimmed = String(command || "").trim();
6629
7580
  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;
7581
+ if (path10.isAbsolute(trimmed) || trimmed.includes("/") || trimmed.includes("\\") || trimmed.startsWith("~")) {
7582
+ const candidate = trimmed.startsWith("~") ? path10.join(homedir5(), trimmed.slice(1)) : trimmed;
7583
+ const resolved = path10.isAbsolute(candidate) ? candidate : path10.resolve(candidate);
7584
+ return existsSync10(resolved) ? resolved : null;
6634
7585
  }
6635
7586
  try {
6636
7587
  const result = execSync(
6637
- platform() === "win32" ? `where ${trimmed}` : `which ${trimmed}`,
7588
+ platform2() === "win32" ? `where ${trimmed}` : `which ${trimmed}`,
6638
7589
  { encoding: "utf-8", timeout: 5e3, stdio: ["pipe", "pipe", "pipe"] }
6639
7590
  ).trim();
6640
7591
  return result.split("\n")[0] || null;
@@ -6655,21 +7606,21 @@ function getIdeVersion(cliCommand) {
6655
7606
  }
6656
7607
  }
6657
7608
  function checkPathExists(paths) {
6658
- const home = homedir4();
7609
+ const home = homedir5();
6659
7610
  for (const p of paths) {
6660
- const normalized = p.startsWith("~") ? path9.join(home, p.slice(1)) : p;
7611
+ const normalized = p.startsWith("~") ? path10.join(home, p.slice(1)) : p;
6661
7612
  if (normalized.includes("*")) {
6662
7613
  const username = home.split(/[\\/]/).pop() || "";
6663
7614
  const resolved = normalized.replace("*", username);
6664
- if (existsSync9(resolved)) return resolved;
7615
+ if (existsSync10(resolved)) return resolved;
6665
7616
  } else {
6666
- if (existsSync9(normalized)) return normalized;
7617
+ if (existsSync10(normalized)) return normalized;
6667
7618
  }
6668
7619
  }
6669
7620
  return null;
6670
7621
  }
6671
7622
  async function detectIDEs(providerLoader) {
6672
- const os22 = platform();
7623
+ const os22 = platform2();
6673
7624
  const results = [];
6674
7625
  for (const def of getMergedDefinitions()) {
6675
7626
  const cliPath = findCliCommand(providerLoader?.getIdeCliCommand(def.id, def.cli) || def.cli);
@@ -6677,7 +7628,7 @@ async function detectIDEs(providerLoader) {
6677
7628
  let resolvedCli = cliPath;
6678
7629
  if (!resolvedCli && appPath && os22 === "darwin") {
6679
7630
  const bundledCli = `${appPath}/Contents/Resources/app/bin/${def.cli}`;
6680
- if (existsSync9(bundledCli)) resolvedCli = bundledCli;
7631
+ if (existsSync10(bundledCli)) resolvedCli = bundledCli;
6681
7632
  }
6682
7633
  if (!resolvedCli && appPath && os22 === "win32") {
6683
7634
  const { dirname: dirname9 } = await import("path");
@@ -6690,7 +7641,7 @@ async function detectIDEs(providerLoader) {
6690
7641
  `${appDir}\\\\resources\\\\app\\\\bin\\\\${def.cli}.cmd`
6691
7642
  ];
6692
7643
  for (const c of candidates) {
6693
- if (existsSync9(c)) {
7644
+ if (existsSync10(c)) {
6694
7645
  resolvedCli = c;
6695
7646
  break;
6696
7647
  }
@@ -6712,134 +7663,8 @@ async function detectIDEs(providerLoader) {
6712
7663
  return results;
6713
7664
  }
6714
7665
 
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
- }
7666
+ // src/index.ts
7667
+ init_cli_detector();
6843
7668
 
6844
7669
  // src/system/host-memory.ts
6845
7670
  import * as os4 from "os";
@@ -14983,11 +15808,13 @@ async function handleOpenPanel(h, args) {
14983
15808
  async function handlePtyInput(h, args) {
14984
15809
  const { cliType, data, targetSessionId } = args || {};
14985
15810
  if (!data) return { success: false, error: "data required" };
15811
+ const cleanData = typeof data === "string" ? data.replace(/\x1b\[[?>][0-9;]*c/g, "") : data;
15812
+ if (!cleanData) return { success: true };
14986
15813
  const adapter = h.getCliAdapter(targetSessionId || cliType);
14987
15814
  if (!adapter || typeof adapter.writeRaw !== "function") {
14988
15815
  return { success: false, error: `CLI adapter not found: ${targetSessionId || cliType || "unknown"}` };
14989
15816
  }
14990
- await adapter.writeRaw(data);
15817
+ await adapter.writeRaw(cleanData);
14991
15818
  return { success: true };
14992
15819
  }
14993
15820
  function handlePtyResize(_h, args) {
@@ -15941,13 +16768,14 @@ var DaemonCommandHandler = class {
15941
16768
 
15942
16769
  // src/commands/cli-manager.ts
15943
16770
  init_provider_cli_adapter();
16771
+ init_cli_detector();
16772
+ init_config();
15944
16773
  import * as os13 from "os";
15945
16774
  import * as path18 from "path";
15946
16775
  import * as crypto4 from "crypto";
15947
16776
  import { existsSync as existsSync14, mkdirSync as mkdirSync9, writeFileSync as writeFileSync9 } from "fs";
15948
16777
  import { execFileSync } from "child_process";
15949
16778
  import chalk from "chalk";
15950
- init_config();
15951
16779
 
15952
16780
  // src/providers/cli-provider-instance.ts
15953
16781
  import * as os12 from "os";
@@ -15976,6 +16804,8 @@ function normalizeProviderSessionId(provider, providerSessionId) {
15976
16804
  }
15977
16805
 
15978
16806
  // src/providers/cli-provider-instance.ts
16807
+ var COMPLETED_FINALIZATION_RETRY_MS = 1e3;
16808
+ var COMPLETED_FINALIZATION_MAX_WAIT_MS = 3e4;
15979
16809
  var IMAGE_MIME_EXTENSIONS = {
15980
16810
  "image/png": ".png",
15981
16811
  "image/jpeg": ".jpg",
@@ -16039,6 +16869,13 @@ function cleanupStaleMaterializedImages(dir) {
16039
16869
  } catch {
16040
16870
  }
16041
16871
  }
16872
+ function hasNonEmptyCliModalButtons(activeModal) {
16873
+ const buttons = activeModal?.buttons;
16874
+ return Array.isArray(buttons) && buttons.some((button) => String(button || "").trim().length > 0);
16875
+ }
16876
+ function isCliGeneratingLikeStatus(status) {
16877
+ return status === "generating" || status === "streaming" || status === "long_generating" || status === "starting";
16878
+ }
16042
16879
  function buildCliStructuredInputPrompt(input, options = {}) {
16043
16880
  const promptParts = [];
16044
16881
  const imageRefs = [];
@@ -16323,10 +17160,12 @@ var CliProviderInstance = class {
16323
17160
  const mergedMessages = this.mergeConversationMessages(parsedMessages);
16324
17161
  const canonicalBackedHistory = this.syncCanonicalSavedHistoryIfNeeded();
16325
17162
  const dirName = this.workingDir.split("/").filter(Boolean).pop() || "session";
17163
+ const parsedChatStatus = typeof parsedStatus?.status === "string" && parsedStatus.status.trim() ? parsedStatus.status.trim() : void 0;
17164
+ const suppressStaleParsedBusyStatus = this.shouldSuppressStaleParsedBusyStatus(parsedStatus, adapterStatus);
16326
17165
  if (parsedMessages.length > 0) {
16327
17166
  const shouldSkipReplayPersist = this.suppressIdleHistoryReplay && adapterStatus.status === "idle" && parsedStatus?.status === "idle";
16328
17167
  let messagesToSave = parsedMessages;
16329
- if (parsedStatus?.status === "generating" || parsedStatus?.status === "long_generating") {
17168
+ if (!suppressStaleParsedBusyStatus && (parsedChatStatus === "generating" || parsedChatStatus === "long_generating")) {
16330
17169
  const lastIdx = messagesToSave.length - 1;
16331
17170
  if (lastIdx >= 0 && messagesToSave[lastIdx]?.role === "assistant") {
16332
17171
  messagesToSave = messagesToSave.slice(0, lastIdx);
@@ -16360,6 +17199,7 @@ var CliProviderInstance = class {
16360
17199
  summaryMetadata: this.summaryMetadata,
16361
17200
  controlValues: this.controlValues
16362
17201
  });
17202
+ const activeChatStatus = parseErrorMessage ? "error" : autoApproveActive && parsedStatus?.status === "waiting_approval" ? "generating" : adapterStatus.status !== "idle" ? visibleStatus : suppressStaleParsedBusyStatus ? visibleStatus : parsedChatStatus || visibleStatus;
16363
17203
  return {
16364
17204
  type: this.type,
16365
17205
  name: this.provider.name,
@@ -16369,7 +17209,7 @@ var CliProviderInstance = class {
16369
17209
  activeChat: {
16370
17210
  id: `${this.type}_${this.workingDir}`,
16371
17211
  title: parsedStatus?.title || dirName,
16372
- status: parseErrorMessage ? "error" : autoApproveActive && parsedStatus?.status === "waiting_approval" ? "generating" : adapterStatus.status !== "idle" ? visibleStatus : parsedStatus?.status || visibleStatus,
17212
+ status: activeChatStatus,
16373
17213
  messages: mergedMessages,
16374
17214
  activeModal: autoApproveActive ? null : parsedStatus?.activeModal ?? adapterStatus.activeModal,
16375
17215
  inputContent: ""
@@ -16497,7 +17337,103 @@ var CliProviderInstance = class {
16497
17337
  await this.adapter.writeRaw("\r");
16498
17338
  }
16499
17339
  }
16500
- this.applyProviderResponse(parsed.payload, { phase: "immediate" });
17340
+ this.applyProviderResponse(parsed.payload, { phase: "immediate" });
17341
+ }
17342
+ completionHasFinalAssistantMessage(messages) {
17343
+ const visibleMessages = (Array.isArray(messages) ? messages : []).filter((message) => isUserFacingChatMessage(message));
17344
+ const lastVisible = visibleMessages[visibleMessages.length - 1];
17345
+ const role = typeof lastVisible?.role === "string" ? lastVisible.role.trim().toLowerCase() : "";
17346
+ const content = lastVisible ? flattenContent(lastVisible.content).trim() : "";
17347
+ return role === "assistant" && !!content;
17348
+ }
17349
+ hasAdapterPendingResponse() {
17350
+ const adapterAny = this.adapter;
17351
+ if (adapterAny?.isWaitingForResponse === true) return true;
17352
+ if (adapterAny?.currentTurnScope) return true;
17353
+ try {
17354
+ if (typeof this.adapter.isProcessing === "function" && this.adapter.isProcessing()) return true;
17355
+ } catch {
17356
+ }
17357
+ try {
17358
+ const partial = typeof this.adapter.getPartialResponse === "function" ? this.adapter.getPartialResponse() : "";
17359
+ if (typeof partial === "string" && partial.trim()) return true;
17360
+ } catch {
17361
+ }
17362
+ return false;
17363
+ }
17364
+ shouldSuppressStaleParsedBusyStatus(parsedStatus, adapterStatus) {
17365
+ const parsedRawStatus = typeof parsedStatus?.status === "string" ? parsedStatus.status.trim() : "";
17366
+ const adapterRawStatus = typeof adapterStatus?.status === "string" ? adapterStatus.status.trim() : "";
17367
+ if (!isCliGeneratingLikeStatus(parsedRawStatus)) return false;
17368
+ if (adapterRawStatus !== "idle") return false;
17369
+ if (hasNonEmptyCliModalButtons(parsedStatus?.activeModal ?? parsedStatus?.modal)) return false;
17370
+ return !this.hasAdapterPendingResponse();
17371
+ }
17372
+ getCompletedFinalizationBlockReason(latestVisibleStatus) {
17373
+ if (latestVisibleStatus !== "idle") return `status:${latestVisibleStatus}`;
17374
+ const adapterAny = this.adapter;
17375
+ if (adapterAny?.isWaitingForResponse === true) return "adapter_waiting_for_response";
17376
+ if (adapterAny?.currentTurnScope) return "adapter_turn_scope_active";
17377
+ const partial = typeof this.adapter.getPartialResponse === "function" ? this.adapter.getPartialResponse() : "";
17378
+ if (typeof partial === "string" && partial.trim()) return "partial_response_pending";
17379
+ let parsed;
17380
+ try {
17381
+ parsed = this.adapter.getScriptParsedStatus();
17382
+ } catch (error) {
17383
+ return `parse_error:${error?.message || String(error)}`;
17384
+ }
17385
+ const parsedStatus = typeof parsed?.status === "string" ? parsed.status : "unknown";
17386
+ if (parsedStatus !== "idle") return `parsed_status:${parsedStatus}`;
17387
+ if (parsed?.activeModal || parsed?.modal) return "parsed_modal_active";
17388
+ if (!this.completionHasFinalAssistantMessage(parsed?.messages)) return "missing_final_assistant";
17389
+ return null;
17390
+ }
17391
+ scheduleCompletedDebounceFlush(delayMs) {
17392
+ if (this.completedDebounceTimer) clearTimeout(this.completedDebounceTimer);
17393
+ this.completedDebounceTimer = setTimeout(() => this.flushCompletedDebounceIfFinalized(), delayMs);
17394
+ }
17395
+ flushCompletedDebounceIfFinalized() {
17396
+ const pending = this.completedDebouncePending;
17397
+ if (!pending) {
17398
+ this.completedDebounceTimer = null;
17399
+ return;
17400
+ }
17401
+ const latestStatus = this.adapter.getStatus({ allowParse: false });
17402
+ const latestAutoApproveActive = latestStatus.status === "waiting_approval" && this.shouldAutoApprove();
17403
+ const latestVisibleStatus = latestAutoApproveActive ? "generating" : latestStatus.status;
17404
+ if (latestVisibleStatus !== "idle") {
17405
+ LOG.info("CLI", `[${this.type}] cancelled pending completed (resumed ${latestVisibleStatus})`);
17406
+ this.completedDebouncePending = null;
17407
+ this.completedDebounceTimer = null;
17408
+ return;
17409
+ }
17410
+ const blockReason = this.getCompletedFinalizationBlockReason(latestVisibleStatus);
17411
+ if (blockReason) {
17412
+ const waitedMs = Date.now() - pending.firstObservedAt;
17413
+ if (waitedMs < COMPLETED_FINALIZATION_MAX_WAIT_MS) {
17414
+ if (pending.loggedBlockReason !== blockReason) {
17415
+ LOG.info("CLI", `[${this.type}] waiting to emit completed until transcript finalizes (${blockReason})`);
17416
+ pending.loggedBlockReason = blockReason;
17417
+ }
17418
+ this.scheduleCompletedDebounceFlush(COMPLETED_FINALIZATION_RETRY_MS);
17419
+ return;
17420
+ }
17421
+ LOG.warn("CLI", `[${this.type}] suppressed completed event after ${waitedMs}ms without finalized assistant turn (${blockReason})`);
17422
+ this.completedDebouncePending = null;
17423
+ this.completedDebounceTimer = null;
17424
+ this.generatingStartedAt = 0;
17425
+ return;
17426
+ }
17427
+ LOG.info("CLI", `[${this.type}] completed in ${pending.duration}s`);
17428
+ this.pushEvent({
17429
+ event: "agent:generating_completed",
17430
+ chatTitle: pending.chatTitle,
17431
+ duration: pending.duration,
17432
+ timestamp: pending.timestamp
17433
+ });
17434
+ this.completedDebouncePending = null;
17435
+ this.completedDebounceTimer = null;
17436
+ this.generatingStartedAt = 0;
16501
17437
  }
16502
17438
  maybeAutoApproveStatus(adapterStatus, now = Date.now()) {
16503
17439
  const autoApproveActive = adapterStatus?.status === "waiting_approval" && this.shouldAutoApprove();
@@ -16596,27 +17532,11 @@ var CliProviderInstance = class {
16596
17532
  this.generatingDebouncePending = null;
16597
17533
  this.generatingStartedAt = 0;
16598
17534
  } 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);
17535
+ this.completedDebouncePending = { chatTitle, duration, timestamp: now, firstObservedAt: now };
17536
+ this.scheduleCompletedDebounceFlush(3e3);
16619
17537
  }
17538
+ } else if (newStatus === "idle" && this.lastStatus === "starting") {
17539
+ this.pushEvent({ event: "agent:ready", chatTitle, timestamp: now });
16620
17540
  } else if (newStatus === "stopped") {
16621
17541
  if (this.generatingDebounceTimer) {
16622
17542
  clearTimeout(this.generatingDebounceTimer);
@@ -18365,9 +19285,6 @@ function buildCoordinatorDelegatedCliLaunchOptions(input) {
18365
19285
  const cliType = String(input.cliType || "").trim();
18366
19286
  const cliArgs = Array.isArray(input.cliArgs) ? [...input.cliArgs] : [];
18367
19287
  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
19288
  if (cliType === "claude-cli" && !hasCliArg(cliArgs, "--mcp-config")) {
18372
19289
  cliArgs.unshift("--mcp-config", ensureEmptyDelegatedMcpConfig(input.workspace));
18373
19290
  }
@@ -21444,6 +22361,7 @@ function getAvailableIdeIds() {
21444
22361
 
21445
22362
  // src/commands/router.ts
21446
22363
  init_config();
22364
+ init_cli_detector();
21447
22365
  init_logger();
21448
22366
 
21449
22367
  // src/logging/command-log.ts
@@ -21613,7 +22531,9 @@ function resolveHermesMeshCoordinatorSetup(options) {
21613
22531
  const mcpServer = resolveAdhdevMcpServerLaunch({
21614
22532
  meshId: options.meshId,
21615
22533
  nodeExecutable: options.nodeExecutable,
21616
- adhdevMcpEntryPath: options.adhdevMcpEntryPath
22534
+ adhdevMcpEntryPath: options.adhdevMcpEntryPath,
22535
+ adhdevMcpTransport: options.adhdevMcpTransport,
22536
+ adhdevMcpPort: options.adhdevMcpPort
21617
22537
  });
21618
22538
  if (!mcpServer) {
21619
22539
  return {
@@ -21680,7 +22600,9 @@ function resolveMeshCoordinatorSetup(options) {
21680
22600
  const mcpServer = resolveAdhdevMcpServerLaunch({
21681
22601
  meshId,
21682
22602
  nodeExecutable: options.nodeExecutable,
21683
- adhdevMcpEntryPath: options.adhdevMcpEntryPath
22603
+ adhdevMcpEntryPath: options.adhdevMcpEntryPath,
22604
+ adhdevMcpTransport: options.adhdevMcpTransport,
22605
+ adhdevMcpPort: options.adhdevMcpPort
21684
22606
  });
21685
22607
  if (!mcpServer) {
21686
22608
  return {
@@ -21702,6 +22624,22 @@ function resolveMeshCoordinatorSetup(options) {
21702
22624
  if (!instructions || !template?.trim()) {
21703
22625
  return { kind: "unsupported", reason: "Provider manual MCP setup is missing instructions or template" };
21704
22626
  }
22627
+ const renderedTemplate = renderMeshCoordinatorTemplate(template, {
22628
+ meshId,
22629
+ workspace,
22630
+ serverName,
22631
+ adhdevMcpCommand: options.adhdevMcpCommand || DEFAULT_ADHDEV_MCP_COMMAND
22632
+ });
22633
+ const isCliCommand = !renderedTemplate.trim().includes("\n") && !renderedTemplate.trim().startsWith("{");
22634
+ if (isCliCommand) {
22635
+ return {
22636
+ kind: "cli_command",
22637
+ serverName,
22638
+ command: renderedTemplate.trim(),
22639
+ requiresRestart: mcpConfig.requiresRestart === true,
22640
+ instructions
22641
+ };
22642
+ }
21705
22643
  return {
21706
22644
  kind: "manual",
21707
22645
  serverName,
@@ -21709,12 +22647,7 @@ function resolveMeshCoordinatorSetup(options) {
21709
22647
  configPathCommand: mcpConfig.configPathCommand,
21710
22648
  requiresRestart: mcpConfig.requiresRestart === true,
21711
22649
  instructions,
21712
- template: renderMeshCoordinatorTemplate(template, {
21713
- meshId,
21714
- workspace,
21715
- serverName,
21716
- adhdevMcpCommand: options.adhdevMcpCommand || DEFAULT_ADHDEV_MCP_COMMAND
21717
- })
22650
+ template: renderedTemplate
21718
22651
  };
21719
22652
  }
21720
22653
  return {
@@ -21743,11 +22676,27 @@ function resolveAdhdevMcpServerLaunch(options) {
21743
22676
  if (!entryPath) return null;
21744
22677
  const nodeExecutable = resolveMcpNodeExecutable(options.nodeExecutable);
21745
22678
  if (!nodeExecutable) return null;
22679
+ const transport = resolveMcpTransport(options.adhdevMcpTransport);
22680
+ const args = [entryPath, "--mode", transport, "--repo-mesh", options.meshId];
22681
+ const port = resolveMcpPort(options.adhdevMcpPort);
22682
+ if (port !== void 0) args.push("--port", String(port));
21746
22683
  return {
21747
22684
  command: nodeExecutable,
21748
- args: [entryPath, "--mode", "ipc", "--repo-mesh", options.meshId]
22685
+ args
21749
22686
  };
21750
22687
  }
22688
+ function resolveMcpTransport(explicitTransport) {
22689
+ if (explicitTransport === "local" || explicitTransport === "ipc") return explicitTransport;
22690
+ const envTransport = process.env.ADHDEV_COORDINATOR_MCP_TRANSPORT?.trim();
22691
+ return envTransport === "local" ? "local" : "ipc";
22692
+ }
22693
+ function resolveMcpPort(explicitPort) {
22694
+ if (typeof explicitPort === "number" && Number.isInteger(explicitPort) && explicitPort > 0) return explicitPort;
22695
+ const raw = process.env.ADHDEV_COORDINATOR_MCP_PORT?.trim();
22696
+ if (!raw) return void 0;
22697
+ const parsed = Number(raw);
22698
+ return Number.isInteger(parsed) && parsed > 0 ? parsed : void 0;
22699
+ }
21751
22700
  function resolveMcpNodeExecutable(explicitExecutable) {
21752
22701
  const explicit = explicitExecutable?.trim();
21753
22702
  if (explicit) return explicit;
@@ -22559,6 +23508,209 @@ async function resolveProviderTypeFromPriority(args) {
22559
23508
  }
22560
23509
  return { error: `No usable provider detected for node '${args.nodeId}' from providerPriority: ${failed.join("; ")}` };
22561
23510
  }
23511
+ var REFINE_VALIDATION_CATEGORIES = ["typecheck", "test", "lint", "build"];
23512
+ var REFINE_VALIDATION_TIMEOUT_MS = 12e4;
23513
+ var REFINE_VALIDATION_OUTPUT_LIMIT_BYTES = 128 * 1024;
23514
+ var REFINE_VALIDATION_SUMMARY_CHARS = 2e3;
23515
+ var REFINE_VALIDATION_MAX_COMMANDS = 4;
23516
+ function truncateValidationOutput(value) {
23517
+ const text = typeof value === "string" ? value : value == null ? "" : String(value);
23518
+ if (text.length <= REFINE_VALIDATION_SUMMARY_CHARS) return text;
23519
+ return `${text.slice(0, REFINE_VALIDATION_SUMMARY_CHARS)}
23520
+ [truncated ${text.length - REFINE_VALIDATION_SUMMARY_CHARS} chars]`;
23521
+ }
23522
+ function readPackageScripts(workspace) {
23523
+ try {
23524
+ const packageJsonPath = pathJoin(workspace, "package.json");
23525
+ const parsed = JSON.parse(fs10.readFileSync(packageJsonPath, "utf-8"));
23526
+ return parsed?.scripts && typeof parsed.scripts === "object" && !Array.isArray(parsed.scripts) ? parsed.scripts : {};
23527
+ } catch {
23528
+ return {};
23529
+ }
23530
+ }
23531
+ function tokenizeValidationCommand(command) {
23532
+ const trimmed = command.trim();
23533
+ if (!trimmed) return null;
23534
+ if (/[;&|<>`$\\\n\r'\"]/.test(trimmed)) return null;
23535
+ const tokens = trimmed.split(/\s+/).filter(Boolean);
23536
+ if (!tokens.length) return null;
23537
+ if (tokens.some((token) => !/^[A-Za-z0-9_@./:=+-]+$/.test(token))) return null;
23538
+ return tokens;
23539
+ }
23540
+ function scriptMatchesValidationCategory(scriptName, category) {
23541
+ return scriptName === category || scriptName.startsWith(`${category}:`);
23542
+ }
23543
+ function parsePackageManagerValidationCommand(rawCommand, category, scripts, source) {
23544
+ const tokens = tokenizeValidationCommand(rawCommand);
23545
+ if (!tokens) {
23546
+ return { rejected: { command: rawCommand, category, source, reason: "unsafe command string is not allowlisted" } };
23547
+ }
23548
+ const [binary, second, third, ...rest] = tokens;
23549
+ let scriptName = "";
23550
+ let command = binary;
23551
+ let args = [];
23552
+ if ((binary === "npm" || binary === "pnpm" || binary === "bun") && second === "run" && third) {
23553
+ scriptName = third;
23554
+ args = ["run", scriptName, ...rest];
23555
+ } else if (binary === "npm" && second === "test" && !third) {
23556
+ scriptName = "test";
23557
+ args = ["test"];
23558
+ } else if (binary === "yarn" && second === "run" && third) {
23559
+ scriptName = third;
23560
+ args = ["run", scriptName, ...rest];
23561
+ } else if (binary === "yarn" && second && !third) {
23562
+ scriptName = second;
23563
+ args = [scriptName];
23564
+ } else {
23565
+ return { rejected: { command: rawCommand, category, source, reason: "command is not a supported package-manager script invocation" } };
23566
+ }
23567
+ if (!scriptName || !Object.prototype.hasOwnProperty.call(scripts, scriptName)) {
23568
+ return { rejected: { command: rawCommand, category, source, script: scriptName, reason: "script is not declared in package.json" } };
23569
+ }
23570
+ if (!scriptMatchesValidationCategory(scriptName, category)) {
23571
+ return { rejected: { command: rawCommand, category, source, script: scriptName, reason: "script name is outside the validation category allowlist" } };
23572
+ }
23573
+ return {
23574
+ command: {
23575
+ command,
23576
+ args,
23577
+ displayCommand: [command, ...args].join(" "),
23578
+ category,
23579
+ source
23580
+ }
23581
+ };
23582
+ }
23583
+ function collectProjectContextValidationCandidates(mesh) {
23584
+ const commands = mesh?.projectContext?.commands;
23585
+ if (!commands || typeof commands !== "object" || Array.isArray(commands)) return [];
23586
+ const candidates = [];
23587
+ for (const category of REFINE_VALIDATION_CATEGORIES) {
23588
+ const entries = Array.isArray(commands[category]) ? commands[category] : [];
23589
+ for (const entry of entries) {
23590
+ if (typeof entry?.command !== "string") continue;
23591
+ candidates.push({
23592
+ command: entry.command,
23593
+ category,
23594
+ source: typeof entry.sourcePath === "string" ? entry.sourcePath : "projectContext.commands",
23595
+ confidence: typeof entry.confidence === "string" ? entry.confidence : void 0
23596
+ });
23597
+ }
23598
+ }
23599
+ return candidates.sort((a, b) => {
23600
+ const rank = (value) => value === "high" ? 0 : value === "medium" ? 1 : 2;
23601
+ return rank(a.confidence) - rank(b.confidence);
23602
+ });
23603
+ }
23604
+ function collectPolicyValidationCandidates(mesh) {
23605
+ const policy = mesh?.policy && typeof mesh.policy === "object" && !Array.isArray(mesh.policy) ? mesh.policy : {};
23606
+ const configured = Array.isArray(policy.validationCommands) ? policy.validationCommands : Array.isArray(policy.validationGate?.commands) ? policy.validationGate.commands : [];
23607
+ return configured.map((entry) => typeof entry === "string" ? { command: entry, category: "", source: "mesh.policy.validationCommands" } : entry).filter((entry) => entry && typeof entry.command === "string").map((entry) => {
23608
+ const commandText = entry.command.trim();
23609
+ const category = REFINE_VALIDATION_CATEGORIES.find((cat) => commandText.includes(` ${cat}`)) ?? "";
23610
+ return { command: commandText, category, source: "mesh.policy.validationCommands" };
23611
+ }).filter((entry) => !!entry.category);
23612
+ }
23613
+ function selectMeshRefineValidationCommands(mesh, workspace) {
23614
+ const scripts = readPackageScripts(workspace);
23615
+ const rejectedCommands = [];
23616
+ const selected = [];
23617
+ const seen = /* @__PURE__ */ new Set();
23618
+ const candidates = [
23619
+ ...collectPolicyValidationCandidates(mesh),
23620
+ ...collectProjectContextValidationCandidates(mesh)
23621
+ ];
23622
+ for (const candidate of candidates) {
23623
+ const parsed = parsePackageManagerValidationCommand(candidate.command, candidate.category, scripts, candidate.source);
23624
+ if (parsed.rejected) {
23625
+ rejectedCommands.push(parsed.rejected);
23626
+ continue;
23627
+ }
23628
+ if (!parsed.command || seen.has(parsed.command.displayCommand)) continue;
23629
+ selected.push(parsed.command);
23630
+ seen.add(parsed.command.displayCommand);
23631
+ if (selected.length >= REFINE_VALIDATION_MAX_COMMANDS) break;
23632
+ }
23633
+ if (!selected.length && candidates.length === 0) {
23634
+ for (const category of REFINE_VALIDATION_CATEGORIES) {
23635
+ if (!Object.prototype.hasOwnProperty.call(scripts, category)) continue;
23636
+ const fallback = parsePackageManagerValidationCommand(`npm run ${category}`, category, scripts, "package.json:scripts");
23637
+ if (fallback.command && !seen.has(fallback.command.displayCommand)) {
23638
+ selected.push(fallback.command);
23639
+ seen.add(fallback.command.displayCommand);
23640
+ } else if (fallback.rejected) {
23641
+ rejectedCommands.push(fallback.rejected);
23642
+ }
23643
+ if (selected.length >= 2) break;
23644
+ }
23645
+ }
23646
+ return {
23647
+ commands: selected,
23648
+ rejectedCommands,
23649
+ 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"
23650
+ };
23651
+ }
23652
+ async function runMeshRefineValidationGate(mesh, workspace) {
23653
+ const { execFile: execFile3 } = await import("child_process");
23654
+ const { promisify: promisify3 } = await import("util");
23655
+ const execFileAsync3 = promisify3(execFile3);
23656
+ const selection = selectMeshRefineValidationCommands(mesh, workspace);
23657
+ const summary = {
23658
+ status: "skipped",
23659
+ required: true,
23660
+ commandsRun: [],
23661
+ rejectedCommands: selection.rejectedCommands,
23662
+ skippedReason: void 0,
23663
+ timeoutMs: REFINE_VALIDATION_TIMEOUT_MS,
23664
+ outputLimitBytes: REFINE_VALIDATION_OUTPUT_LIMIT_BYTES
23665
+ };
23666
+ if (!selection.commands.length) {
23667
+ summary.skippedReason = "validation_unavailable: no allowlisted projectContext, mesh policy, or package.json build/test/typecheck/lint command was available";
23668
+ return summary;
23669
+ }
23670
+ for (const candidate of selection.commands) {
23671
+ const startedAt = Date.now();
23672
+ try {
23673
+ const result = await execFileAsync3(candidate.command, candidate.args, {
23674
+ cwd: workspace,
23675
+ encoding: "utf8",
23676
+ timeout: REFINE_VALIDATION_TIMEOUT_MS,
23677
+ maxBuffer: REFINE_VALIDATION_OUTPUT_LIMIT_BYTES,
23678
+ env: { ...process.env, CI: process.env.CI || "1" }
23679
+ });
23680
+ summary.commandsRun.push({
23681
+ command: candidate.command,
23682
+ args: candidate.args,
23683
+ displayCommand: candidate.displayCommand,
23684
+ category: candidate.category,
23685
+ source: candidate.source,
23686
+ passed: true,
23687
+ exitCode: 0,
23688
+ durationMs: Date.now() - startedAt,
23689
+ stdout: truncateValidationOutput(result.stdout),
23690
+ stderr: truncateValidationOutput(result.stderr)
23691
+ });
23692
+ } catch (error) {
23693
+ summary.commandsRun.push({
23694
+ command: candidate.command,
23695
+ args: candidate.args,
23696
+ displayCommand: candidate.displayCommand,
23697
+ category: candidate.category,
23698
+ source: candidate.source,
23699
+ passed: false,
23700
+ exitCode: typeof error?.code === "number" ? error.code : null,
23701
+ signal: typeof error?.signal === "string" ? error.signal : null,
23702
+ timedOut: error?.killed === true || /timed out/i.test(String(error?.message || "")),
23703
+ durationMs: Date.now() - startedAt,
23704
+ stdout: truncateValidationOutput(error?.stdout),
23705
+ stderr: truncateValidationOutput(error?.stderr || error?.message)
23706
+ });
23707
+ summary.status = "failed";
23708
+ return summary;
23709
+ }
23710
+ }
23711
+ summary.status = "passed";
23712
+ return summary;
23713
+ }
22562
23714
  function loadYamlModule() {
22563
23715
  return yaml;
22564
23716
  }
@@ -22588,6 +23740,34 @@ function loadHermesCoordinatorBaseConfig(targetConfigPath) {
22588
23740
  const { mcp_servers: _mcpServers, ...baseConfig } = parsed;
22589
23741
  return { config: baseConfig, sourceHome, sourceConfigPath };
22590
23742
  }
23743
+ function stripHermesCoordinatorTempModelProviderOverrides(config) {
23744
+ const {
23745
+ model: _model,
23746
+ provider: _provider,
23747
+ default_model: _defaultModel,
23748
+ defaultProvider: _defaultProvider,
23749
+ default_provider: _defaultProviderSnake,
23750
+ modelProvider: _modelProvider,
23751
+ model_provider: _modelProviderSnake,
23752
+ ...sanitized
23753
+ } = config;
23754
+ const delegation = sanitized.delegation;
23755
+ if (delegation && typeof delegation === "object" && !Array.isArray(delegation)) {
23756
+ const {
23757
+ model: _delegationModel,
23758
+ provider: _delegationProvider,
23759
+ modelProvider: _delegationModelProvider,
23760
+ model_provider: _delegationModelProviderSnake,
23761
+ ...delegationRest
23762
+ } = delegation;
23763
+ if (Object.keys(delegationRest).length > 0) {
23764
+ sanitized.delegation = delegationRest;
23765
+ } else {
23766
+ delete sanitized.delegation;
23767
+ }
23768
+ }
23769
+ return sanitized;
23770
+ }
22591
23771
  function copyHermesCoordinatorCredentialFiles(sourceHome, targetHome) {
22592
23772
  if (pathResolve(sourceHome) === pathResolve(targetHome)) return;
22593
23773
  for (const fileName of [".env", "auth.json"]) {
@@ -22745,9 +23925,191 @@ var DaemonCommandRouter = class {
22745
23925
  if (record?.meta?.meshNodeId === nodeId) return true;
22746
23926
  return false;
22747
23927
  }
23928
+ async cleanupLocalWorktreeNode(args) {
23929
+ const workspace = typeof args.node?.workspace === "string" ? args.node.workspace.trim() : "";
23930
+ if (!workspace) {
23931
+ return {
23932
+ success: false,
23933
+ code: "mesh_worktree_cleanup_missing_workspace",
23934
+ error: `Worktree node '${args.nodeId}' is missing workspace metadata`,
23935
+ recoveryHint: "Inspect the mesh node record before removing it, or remove stale metadata manually only after confirming no managed worktree remains."
23936
+ };
23937
+ }
23938
+ const worktreeExists = fs10.existsSync(workspace);
23939
+ 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);
23940
+ const repoRoot = typeof sourceNode?.repoRoot === "string" && sourceNode.repoRoot.trim() ? sourceNode.repoRoot.trim() : typeof sourceNode?.workspace === "string" && sourceNode.workspace.trim() ? sourceNode.workspace.trim() : "";
23941
+ if (!worktreeExists) {
23942
+ return { success: true, skipped: true, removedPath: workspace, repoRoot: repoRoot || void 0, reason: "worktree_path_missing" };
23943
+ }
23944
+ if (!repoRoot || !fs10.existsSync(repoRoot)) {
23945
+ return {
23946
+ success: false,
23947
+ code: "mesh_worktree_cleanup_missing_source_repo",
23948
+ error: `Refusing to remove worktree '${workspace}' because the source repo root is unavailable`,
23949
+ recoveryHint: "Run mesh_remove_node from the machine that owns the source repo, or verify the source node metadata before retrying."
23950
+ };
23951
+ }
23952
+ if (typeof args.node?.worktreeBranch !== "string" || !args.node.worktreeBranch.trim()) {
23953
+ return {
23954
+ success: false,
23955
+ code: "mesh_worktree_cleanup_missing_branch",
23956
+ error: `Refusing to remove worktree '${workspace}' because worktreeBranch metadata is missing`,
23957
+ recoveryHint: "Confirm this is an ADHDev-managed worktree before removing it manually; managed worktree nodes include worktreeBranch metadata."
23958
+ };
23959
+ }
23960
+ const { resolveWorktreePath: resolveWorktreePath2, listWorktrees: listWorktrees2, removeWorktree: removeWorktree2 } = await Promise.resolve().then(() => (init_git_worktree(), git_worktree_exports));
23961
+ const normalizePath = (value) => {
23962
+ const resolved = pathResolve(value);
23963
+ try {
23964
+ return fs10.realpathSync(resolved);
23965
+ } catch {
23966
+ return resolved;
23967
+ }
23968
+ };
23969
+ const expectedPath = normalizePath(resolveWorktreePath2(repoRoot, String(args.mesh?.name || args.mesh?.id || "mesh"), args.node.worktreeBranch));
23970
+ const actualPath = normalizePath(workspace);
23971
+ if (actualPath !== expectedPath) {
23972
+ return {
23973
+ success: false,
23974
+ code: "mesh_worktree_cleanup_unexpected_path",
23975
+ error: `Refusing to remove worktree '${workspace}' because it is not at the expected managed path '${expectedPath}'`,
23976
+ recoveryHint: "Use git worktree list/status to inspect the path. Retry only after confirming the mesh node metadata points to an ADHDev-managed worktree."
23977
+ };
23978
+ }
23979
+ const entries = await listWorktrees2(repoRoot);
23980
+ const managedEntry = entries.find((entry) => normalizePath(entry.path) === actualPath);
23981
+ if (!managedEntry) {
23982
+ return {
23983
+ success: false,
23984
+ code: "mesh_worktree_cleanup_not_registered",
23985
+ error: `Refusing to remove '${workspace}' because it is not registered in git worktree list for '${repoRoot}'`,
23986
+ recoveryHint: "Inspect git worktree list --porcelain from the source repo. If the path was already removed, prune git worktrees before retrying."
23987
+ };
23988
+ }
23989
+ if (managedEntry.branch && managedEntry.branch !== args.node.worktreeBranch) {
23990
+ return {
23991
+ success: false,
23992
+ code: "mesh_worktree_cleanup_branch_mismatch",
23993
+ error: `Refusing to remove '${workspace}' because git reports branch '${managedEntry.branch}', expected '${args.node.worktreeBranch}'`,
23994
+ recoveryHint: "Inspect the worktree branch and mesh metadata before retrying cleanup."
23995
+ };
23996
+ }
23997
+ const forceFallbackConvergence = await this.getWorktreeForceCleanupConvergence({
23998
+ repoRoot,
23999
+ workspace,
24000
+ node: args.node
24001
+ });
24002
+ try {
24003
+ const result = await removeWorktree2(repoRoot, workspace, {
24004
+ requireClean: true,
24005
+ allowSubmoduleForceFallback: forceFallbackConvergence.allow
24006
+ });
24007
+ return {
24008
+ success: true,
24009
+ removedPath: result.removedPath,
24010
+ repoRoot,
24011
+ ...result.fallback ? {
24012
+ fallback: result.fallback,
24013
+ forced: result.forced,
24014
+ reason: result.reason,
24015
+ convergence: forceFallbackConvergence
24016
+ } : {}
24017
+ };
24018
+ } catch (e) {
24019
+ const message = String(e?.message || e || "worktree cleanup failed");
24020
+ const dirty = message.includes("dirty worktree") || message.includes("local changes");
24021
+ const submoduleForceBlocked = /working trees containing submodules cannot be moved or removed/i.test(message) && !forceFallbackConvergence.allow;
24022
+ return {
24023
+ success: false,
24024
+ code: dirty ? "mesh_worktree_cleanup_dirty" : submoduleForceBlocked ? "mesh_worktree_cleanup_force_fallback_blocked" : "mesh_worktree_cleanup_failed",
24025
+ error: submoduleForceBlocked ? `${message}; refusing --force fallback because convergence could not be verified: ${forceFallbackConvergence.error || "unknown convergence state"}` : message,
24026
+ 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.",
24027
+ ...submoduleForceBlocked ? { convergence: forceFallbackConvergence } : {}
24028
+ };
24029
+ }
24030
+ }
24031
+ async getWorktreeForceCleanupConvergence(args) {
24032
+ const metadataStatus = typeof args.node?.branchConvergence?.status === "string" ? args.node.branchConvergence.status : "";
24033
+ if (metadataStatus === "merged_to_main" || metadataStatus === "cleanup_candidate") {
24034
+ return { allow: true, status: metadataStatus, source: "node_branch_convergence" };
24035
+ }
24036
+ const { execFile: execFile3 } = await import("child_process");
24037
+ const { promisify: promisify3 } = await import("util");
24038
+ const execFileAsync3 = promisify3(execFile3);
24039
+ const runGit2 = async (gitArgs, cwd) => {
24040
+ const { stdout } = await execFileAsync3("git", gitArgs, {
24041
+ cwd,
24042
+ encoding: "utf8",
24043
+ timeout: 3e4,
24044
+ maxBuffer: 4 * 1024 * 1024,
24045
+ windowsHide: true
24046
+ });
24047
+ return String(stdout || "").trim();
24048
+ };
24049
+ let head = "";
24050
+ try {
24051
+ head = await runGit2(["rev-parse", "HEAD"], args.workspace);
24052
+ } catch (e) {
24053
+ return { allow: false, error: `could not resolve worktree HEAD: ${e?.message || e}` };
24054
+ }
24055
+ if (!head) return { allow: false, error: "worktree HEAD is empty" };
24056
+ const candidateRefs = [];
24057
+ try {
24058
+ const defaultBranch = await runGit2(["branch", "--show-current"], args.repoRoot);
24059
+ if (defaultBranch) {
24060
+ candidateRefs.push(defaultBranch, `origin/${defaultBranch}`);
24061
+ }
24062
+ } catch {
24063
+ }
24064
+ candidateRefs.push("origin/main", "origin/master", "main", "master");
24065
+ const seen = /* @__PURE__ */ new Set();
24066
+ const checkedRefs = [];
24067
+ for (const ref of candidateRefs) {
24068
+ if (!ref || seen.has(ref)) continue;
24069
+ seen.add(ref);
24070
+ let commit = "";
24071
+ try {
24072
+ commit = await runGit2(["rev-parse", "--verify", `${ref}^{commit}`], args.repoRoot);
24073
+ } catch {
24074
+ continue;
24075
+ }
24076
+ checkedRefs.push(ref);
24077
+ try {
24078
+ await runGit2(["merge-base", "--is-ancestor", head, commit], args.repoRoot);
24079
+ return { allow: true, status: "merged_to_default_ref", source: "git_merge_base", ref };
24080
+ } catch {
24081
+ }
24082
+ }
24083
+ return {
24084
+ allow: false,
24085
+ status: metadataStatus || void 0,
24086
+ error: checkedRefs.length ? `worktree HEAD is not contained in checked refs: ${checkedRefs.join(", ")}` : "no default/main refs were available for convergence verification"
24087
+ };
24088
+ }
22748
24089
  isCompletedHostedSession(record) {
22749
24090
  return record?.lifecycle === "stopped" || record?.lifecycle === "failed" || record?.lifecycle === "interrupted";
22750
24091
  }
24092
+ async recordIntentionalMeshSessionStop(args) {
24093
+ try {
24094
+ const { appendLedgerEntry: appendLedgerEntry2 } = await Promise.resolve().then(() => (init_mesh_ledger(), mesh_ledger_exports));
24095
+ appendLedgerEntry2(args.meshId, {
24096
+ kind: "session_stopped",
24097
+ nodeId: args.nodeId,
24098
+ sessionId: args.sessionId,
24099
+ payload: {
24100
+ intentional: true,
24101
+ reason: "operator_cleanup",
24102
+ intentionalStopReason: "operator_cleanup",
24103
+ source: args.source,
24104
+ cleanupMode: args.mode,
24105
+ action: args.action,
24106
+ workspace: typeof args.node?.workspace === "string" ? args.node.workspace : void 0
24107
+ }
24108
+ });
24109
+ } catch (e) {
24110
+ LOG.warn("MeshCleanup", `Failed to record intentional cleanup stop for ${args.sessionId}: ${e?.message || e}`);
24111
+ }
24112
+ }
22751
24113
  async cleanupMeshSessions(args) {
22752
24114
  if (args.mode === "preserve") {
22753
24115
  return { success: true, mode: "preserve", matchedCount: 0, stoppedSessionIds: [], deletedSessionIds: [], skippedSessionIds: [] };
@@ -22764,6 +24126,21 @@ var DaemonCommandRouter = class {
22764
24126
  const deleteUnsupportedSessionIds = [];
22765
24127
  const recordsRemainSessionIds = [];
22766
24128
  const errors = [];
24129
+ const cleanupSource = args.source || "mesh_cleanup_sessions";
24130
+ const markedIntentionalStopSessionIds = /* @__PURE__ */ new Set();
24131
+ const markIntentionalStop = async (sessionId, action) => {
24132
+ if (args.dryRun || markedIntentionalStopSessionIds.has(sessionId)) return;
24133
+ markedIntentionalStopSessionIds.add(sessionId);
24134
+ await this.recordIntentionalMeshSessionStop({
24135
+ meshId: args.meshId,
24136
+ nodeId: args.nodeId,
24137
+ node: args.node,
24138
+ sessionId,
24139
+ mode: args.mode,
24140
+ source: cleanupSource,
24141
+ action
24142
+ });
24143
+ };
22767
24144
  const matchedBySurfaceKind = {
22768
24145
  live_runtime: 0,
22769
24146
  recovery_snapshot: 0,
@@ -22786,7 +24163,10 @@ var DaemonCommandRouter = class {
22786
24163
  try {
22787
24164
  if (args.mode === "stop") {
22788
24165
  if (!completed) {
22789
- if (!args.dryRun) await this.deps.sessionHostControl.stopSession(sessionId);
24166
+ if (!args.dryRun) {
24167
+ await markIntentionalStop(sessionId, "stop_session");
24168
+ await this.deps.sessionHostControl.stopSession(sessionId);
24169
+ }
22790
24170
  stoppedSessionIds.push(sessionId);
22791
24171
  } else {
22792
24172
  skippedSessionIds.push(sessionId);
@@ -22803,6 +24183,7 @@ var DaemonCommandRouter = class {
22803
24183
  continue;
22804
24184
  }
22805
24185
  if (args.mode === "stop_and_delete") {
24186
+ if (!completed) await markIntentionalStop(sessionId, "delete_session_force");
22806
24187
  if (!args.dryRun) await this.deps.sessionHostControl.deleteSession(sessionId, { force: true });
22807
24188
  deletedSessionIds.push(sessionId);
22808
24189
  continue;
@@ -22814,6 +24195,7 @@ var DaemonCommandRouter = class {
22814
24195
  recordsRemainSessionIds.push(sessionId);
22815
24196
  if (args.mode === "stop_and_delete" && !completed) {
22816
24197
  try {
24198
+ await markIntentionalStop(sessionId, "stop_session");
22817
24199
  await this.deps.sessionHostControl.stopSession(sessionId);
22818
24200
  stoppedSessionIds.push(sessionId);
22819
24201
  } catch (stopError) {
@@ -23568,6 +24950,91 @@ var DaemonCommandRouter = class {
23568
24950
  return { success: false, error: e.message };
23569
24951
  }
23570
24952
  }
24953
+ case "get_mesh_ledger_slice": {
24954
+ const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
24955
+ if (!meshId) return { success: false, error: "meshId required" };
24956
+ try {
24957
+ const { readLedgerSlice: readLedgerSlice2 } = await Promise.resolve().then(() => (init_mesh_ledger(), mesh_ledger_exports));
24958
+ const kind = Array.isArray(args?.kind) ? args.kind.filter((k) => typeof k === "string") : void 0;
24959
+ const slice = readLedgerSlice2(meshId, {
24960
+ afterId: typeof args?.afterId === "string" ? args.afterId : void 0,
24961
+ since: typeof args?.since === "string" ? args.since : void 0,
24962
+ kind,
24963
+ limit: typeof args?.limit === "number" ? args.limit : void 0
24964
+ });
24965
+ return { success: true, slice };
24966
+ } catch (e) {
24967
+ return { success: false, error: e.message };
24968
+ }
24969
+ }
24970
+ case "import_mesh_ledger_slice": {
24971
+ const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
24972
+ if (!meshId) return { success: false, error: "meshId required" };
24973
+ try {
24974
+ const { appendRemoteLedgerEntries: appendRemoteLedgerEntries2, getLedgerSummary: getLedgerSummary2 } = await Promise.resolve().then(() => (init_mesh_ledger(), mesh_ledger_exports));
24975
+ const entries = Array.isArray(args?.entries) ? args.entries : Array.isArray(args?.slice?.entries) ? args.slice.entries : [];
24976
+ const result = appendRemoteLedgerEntries2(meshId, entries);
24977
+ return { success: true, result, summary: getLedgerSummary2(meshId) };
24978
+ } catch (e) {
24979
+ return { success: false, error: e.message };
24980
+ }
24981
+ }
24982
+ case "get_mesh_queue": {
24983
+ const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
24984
+ if (!meshId) return { success: false, error: "meshId required" };
24985
+ try {
24986
+ const { getMeshQueueStats: getMeshQueueStats2, getQueue: getQueue2 } = await Promise.resolve().then(() => (init_mesh_work_queue(), mesh_work_queue_exports));
24987
+ const status = Array.isArray(args?.status) ? args.status.map((s) => typeof s === "string" ? s.trim() : "").filter(Boolean) : void 0;
24988
+ const queue = getQueue2(meshId, { status });
24989
+ const summary = getMeshQueueStats2(meshId);
24990
+ return {
24991
+ success: true,
24992
+ queue,
24993
+ summary,
24994
+ sourceOfTruth: {
24995
+ kind: "mesh_work_queue_file",
24996
+ activeStatuses: ["pending", "assigned"],
24997
+ historicalStatuses: ["completed", "failed", "cancelled"],
24998
+ notes: "pending/assigned are active work; completed/failed/cancelled are historical records."
24999
+ }
25000
+ };
25001
+ } catch (e) {
25002
+ return { success: false, error: e.message };
25003
+ }
25004
+ }
25005
+ case "cancel_mesh_queue_task": {
25006
+ const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
25007
+ const taskId = typeof args?.taskId === "string" ? args.taskId.trim() : "";
25008
+ if (!meshId || !taskId) return { success: false, error: "meshId and taskId required" };
25009
+ try {
25010
+ const { cancelTask: cancelTask2 } = await Promise.resolve().then(() => (init_mesh_work_queue(), mesh_work_queue_exports));
25011
+ const reason = typeof args?.reason === "string" ? args.reason : void 0;
25012
+ const task = cancelTask2(meshId, taskId, { reason });
25013
+ if (!task) return { success: false, error: `Queue task '${taskId}' not found` };
25014
+ return { success: true, task };
25015
+ } catch (e) {
25016
+ return { success: false, error: e.message };
25017
+ }
25018
+ }
25019
+ case "requeue_mesh_queue_task": {
25020
+ const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
25021
+ const taskId = typeof args?.taskId === "string" ? args.taskId.trim() : "";
25022
+ if (!meshId || !taskId) return { success: false, error: "meshId and taskId required" };
25023
+ try {
25024
+ const { requeueTask: requeueTask2 } = await Promise.resolve().then(() => (init_mesh_work_queue(), mesh_work_queue_exports));
25025
+ const task = requeueTask2(meshId, taskId, {
25026
+ reason: typeof args?.reason === "string" ? args.reason : void 0,
25027
+ targetNodeId: typeof args?.targetNodeId === "string" ? args.targetNodeId.trim() : void 0,
25028
+ targetSessionId: typeof args?.targetSessionId === "string" ? args.targetSessionId.trim() : void 0,
25029
+ clearTargetNode: args?.clearTargetNode === true,
25030
+ clearTargetSession: args?.clearTargetSession !== false
25031
+ });
25032
+ if (!task) return { success: false, error: `Queue task '${taskId}' not found` };
25033
+ return { success: true, task };
25034
+ } catch (e) {
25035
+ return { success: false, error: e.message };
25036
+ }
25037
+ }
23571
25038
  case "add_mesh_node": {
23572
25039
  const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
23573
25040
  const workspace = typeof args?.workspace === "string" ? args.workspace.trim() : "";
@@ -23629,7 +25096,8 @@ var DaemonCommandRouter = class {
23629
25096
  node,
23630
25097
  mode,
23631
25098
  sessionIds,
23632
- dryRun: args?.dryRun === true
25099
+ dryRun: args?.dryRun === true,
25100
+ source: "mesh_cleanup_sessions"
23633
25101
  });
23634
25102
  return result;
23635
25103
  } catch (e) {
@@ -23659,10 +25127,61 @@ var DaemonCommandRouter = class {
23659
25127
  if (!branch) return { success: false, error: "Could not determine branch of the worktree node" };
23660
25128
  const { stdout: baseBranchStdout } = await execFileAsync3("git", ["branch", "--show-current"], { cwd: repoRoot, encoding: "utf8" });
23661
25129
  const baseBranch = baseBranchStdout.trim();
25130
+ const validationSummary = await runMeshRefineValidationGate(mesh, node.workspace);
25131
+ if (validationSummary.status === "failed") {
25132
+ return {
25133
+ success: false,
25134
+ code: "validation_failed",
25135
+ convergenceStatus: "blocked_review",
25136
+ error: "Refinery validation gate failed; merge/refine was not attempted.",
25137
+ branch,
25138
+ into: baseBranch,
25139
+ validationSummary,
25140
+ finalBranchConvergenceState: {
25141
+ branch,
25142
+ baseBranch,
25143
+ merged: false,
25144
+ removed: false,
25145
+ validation: "failed",
25146
+ status: "blocked_review"
25147
+ }
25148
+ };
25149
+ }
25150
+ if (validationSummary.status === "skipped") {
25151
+ return {
25152
+ success: false,
25153
+ code: "validation_unavailable",
25154
+ convergenceStatus: "blocked_review",
25155
+ error: "Refinery validation gate is required but no allowlisted validation command was available; merge/refine was not attempted.",
25156
+ branch,
25157
+ into: baseBranch,
25158
+ validationSummary,
25159
+ finalBranchConvergenceState: {
25160
+ branch,
25161
+ baseBranch,
25162
+ merged: false,
25163
+ removed: false,
25164
+ validation: "unavailable",
25165
+ status: "blocked_review"
25166
+ }
25167
+ };
25168
+ }
23662
25169
  try {
23663
25170
  await execFileAsync3("git", ["merge", "--no-ff", branch, "-m", `Auto-merge branch '${branch}' via Refinery`], { cwd: repoRoot, encoding: "utf8" });
23664
25171
  } catch (e) {
23665
- return { success: false, error: `Merge failed (conflicts?): ${e.message}` };
25172
+ return {
25173
+ success: false,
25174
+ error: `Merge failed (conflicts?): ${e.message}`,
25175
+ validationSummary,
25176
+ finalBranchConvergenceState: {
25177
+ branch,
25178
+ baseBranch,
25179
+ merged: false,
25180
+ removed: false,
25181
+ validation: "passed",
25182
+ status: "not_mergeable"
25183
+ }
25184
+ };
23666
25185
  }
23667
25186
  const removeResult = await this.execute("remove_mesh_node", {
23668
25187
  meshId,
@@ -23675,11 +25194,27 @@ var DaemonCommandRouter = class {
23675
25194
  appendLedgerEntry2(meshId, {
23676
25195
  kind: "node_removed",
23677
25196
  nodeId,
23678
- payload: { refined: true, mergedBranch: branch, into: baseBranch }
25197
+ payload: { refined: true, mergedBranch: branch, into: baseBranch, validationSummary }
23679
25198
  });
23680
25199
  } catch {
23681
25200
  }
23682
- return { success: true, merged: true, branch, into: baseBranch, removeResult };
25201
+ return {
25202
+ success: true,
25203
+ merged: true,
25204
+ branch,
25205
+ into: baseBranch,
25206
+ removeResult,
25207
+ validationSummary,
25208
+ finalBranchConvergenceState: {
25209
+ branch: baseBranch,
25210
+ mergedBranch: branch,
25211
+ baseBranch,
25212
+ merged: true,
25213
+ removed: removeResult?.success !== false,
25214
+ validation: "passed",
25215
+ status: removeResult?.success === false ? "merged_cleanup_failed" : "merged"
25216
+ }
25217
+ };
23683
25218
  } catch (e) {
23684
25219
  return { success: false, error: e.message };
23685
25220
  }
@@ -23697,20 +25232,24 @@ var DaemonCommandRouter = class {
23697
25232
  );
23698
25233
  let sessionCleanup;
23699
25234
  if (node && sessionCleanupMode !== "preserve") {
23700
- sessionCleanup = await this.cleanupMeshSessions({ meshId, nodeId, node, mode: sessionCleanupMode });
25235
+ sessionCleanup = await this.cleanupMeshSessions({ meshId, nodeId, node, mode: sessionCleanupMode, source: "mesh_remove_node" });
23701
25236
  if (sessionCleanup.success === false) return { success: false, removed: false, sessionCleanup };
23702
25237
  }
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}`);
25238
+ let worktreeCleanup;
25239
+ if (node?.isLocalWorktree) {
25240
+ const cleanupResult = await this.cleanupLocalWorktreeNode({ mesh, node, nodeId });
25241
+ if (cleanupResult.success === false) {
25242
+ return {
25243
+ success: false,
25244
+ removed: false,
25245
+ code: cleanupResult.code,
25246
+ error: cleanupResult.error,
25247
+ recoveryHint: cleanupResult.recoveryHint,
25248
+ ...sessionCleanup ? { sessionCleanup } : {},
25249
+ worktreeCleanup: cleanupResult
25250
+ };
23713
25251
  }
25252
+ worktreeCleanup = cleanupResult;
23714
25253
  }
23715
25254
  let removed = false;
23716
25255
  if (meshRecord?.inline) {
@@ -23725,12 +25264,21 @@ var DaemonCommandRouter = class {
23725
25264
  appendLedgerEntry2(meshId, {
23726
25265
  kind: "node_removed",
23727
25266
  nodeId,
23728
- payload: { worktree: !!node?.isLocalWorktree, sessionCleanupMode }
25267
+ payload: {
25268
+ worktree: !!node?.isLocalWorktree,
25269
+ sessionCleanupMode,
25270
+ workspace: typeof node?.workspace === "string" ? node.workspace : void 0,
25271
+ daemonId: typeof node?.daemonId === "string" ? node.daemonId : void 0,
25272
+ worktreeBranch: typeof node?.worktreeBranch === "string" ? node.worktreeBranch : void 0,
25273
+ worktreeCleanupFallback: typeof worktreeCleanup?.fallback === "string" ? worktreeCleanup.fallback : void 0,
25274
+ forced: worktreeCleanup?.forced === true ? true : void 0,
25275
+ forceFallbackReason: typeof worktreeCleanup?.reason === "string" ? worktreeCleanup.reason : void 0
25276
+ }
23729
25277
  });
23730
25278
  } catch {
23731
25279
  }
23732
25280
  }
23733
- return { success: true, removed, ...sessionCleanup ? { sessionCleanup } : {} };
25281
+ return { success: true, removed, ...sessionCleanup ? { sessionCleanup } : {}, ...worktreeCleanup ? { worktreeCleanup } : {} };
23734
25282
  } catch (e) {
23735
25283
  return { success: false, error: e.message };
23736
25284
  }
@@ -23765,6 +25313,7 @@ var DaemonCommandRouter = class {
23765
25313
  workspace: result.worktreePath,
23766
25314
  repoRoot: result.worktreePath,
23767
25315
  daemonId: sourceNode.daemonId,
25316
+ machineId: sourceNode.machineId ?? sourceNode.machine_id,
23768
25317
  userOverrides: { ...sourceNode.userOverrides || {} },
23769
25318
  policy: { ...sourceNode.policy || {} },
23770
25319
  isLocalWorktree: true,
@@ -23778,6 +25327,7 @@ var DaemonCommandRouter = class {
23778
25327
  workspace: result.worktreePath,
23779
25328
  repoRoot: result.worktreePath,
23780
25329
  daemonId: sourceNode.daemonId,
25330
+ machineId: sourceNode.machineId ?? sourceNode.machine_id,
23781
25331
  userOverrides: { ...sourceNode.userOverrides || {} },
23782
25332
  isLocalWorktree: true,
23783
25333
  worktreeBranch: result.branch,
@@ -23896,6 +25446,93 @@ var DaemonCommandRouter = class {
23896
25446
  meshCoordinatorSetup: coordinatorSetup
23897
25447
  };
23898
25448
  }
25449
+ if (coordinatorSetup.kind === "cli_command") {
25450
+ let cliCmdSystemPrompt = "";
25451
+ try {
25452
+ cliCmdSystemPrompt = buildCoordinatorSystemPrompt2({ mesh, coordinatorCliType: cliType });
25453
+ } catch (error) {
25454
+ const message = error?.message || String(error);
25455
+ LOG.error("MeshCoordinator", `Failed to build coordinator prompt: ${message}`);
25456
+ return {
25457
+ success: false,
25458
+ code: "mesh_coordinator_prompt_failed",
25459
+ error: `Failed to build Repo Mesh coordinator prompt: ${message}`,
25460
+ meshId,
25461
+ cliType,
25462
+ workspace
25463
+ };
25464
+ }
25465
+ try {
25466
+ const { execFileSync: execCmdSync } = await import("child_process");
25467
+ const cmdParts = coordinatorSetup.command.trim().split(/\s+/);
25468
+ const [regCmd, ...regArgs] = cmdParts;
25469
+ LOG.info("MeshCoordinator", `Running MCP registration: ${coordinatorSetup.command}`);
25470
+ execCmdSync(regCmd, regArgs, { stdio: "pipe", timeout: 15e3 });
25471
+ } catch (error) {
25472
+ LOG.warn("MeshCoordinator", `MCP registration command failed (may be pre-registered): ${error?.message || error}`);
25473
+ }
25474
+ const cliCmdArgs = [];
25475
+ const cliCmdEnv = {};
25476
+ if (cliCmdSystemPrompt) {
25477
+ if (cliType === "codex-cli") {
25478
+ cliCmdArgs.push("-c", `developer_instructions=${JSON.stringify(cliCmdSystemPrompt)}`);
25479
+ } else if (cliType === "gemini-cli") {
25480
+ try {
25481
+ const { writeFileSync: wfs, existsSync: efs, readFileSync: rfs } = await import("fs");
25482
+ const geminiMdPath = `${workspace}/GEMINI.md`;
25483
+ const marker = "<!-- adhdev-mesh-coordinator-prompt -->";
25484
+ const markerEnd = "<!-- /adhdev-mesh-coordinator-prompt -->";
25485
+ const block = `${marker}
25486
+ ${cliCmdSystemPrompt}
25487
+ ${markerEnd}`;
25488
+ if (efs(geminiMdPath)) {
25489
+ const existing = rfs(geminiMdPath, "utf-8");
25490
+ const replaced = existing.replace(
25491
+ new RegExp(`${marker}[\\s\\S]*?${markerEnd}`, "g"),
25492
+ block
25493
+ );
25494
+ wfs(geminiMdPath, replaced.includes(marker) ? replaced : `${existing}
25495
+
25496
+ ${block}`);
25497
+ } else {
25498
+ wfs(geminiMdPath, block);
25499
+ }
25500
+ LOG.info("MeshCoordinator", `Wrote coordinator prompt to ${workspace}/GEMINI.md`);
25501
+ } catch (e) {
25502
+ LOG.warn("MeshCoordinator", `Could not write GEMINI.md: ${e?.message || e}`);
25503
+ }
25504
+ }
25505
+ }
25506
+ const cliCmdLaunch = await this.deps.cliManager.handleCliCommand("launch_cli", {
25507
+ cliType,
25508
+ dir: workspace,
25509
+ cliArgs: cliCmdArgs.length > 0 ? cliCmdArgs : void 0,
25510
+ env: Object.keys(cliCmdEnv).length > 0 ? cliCmdEnv : void 0,
25511
+ settings: { meshCoordinatorFor: meshId }
25512
+ });
25513
+ if (!cliCmdLaunch?.success) {
25514
+ return { success: false, error: cliCmdLaunch?.error || "Failed to launch CLI session" };
25515
+ }
25516
+ LOG.info("MeshCoordinator", `Launched ${cliType} coordinator (cli_command) for mesh ${meshId}`);
25517
+ try {
25518
+ const { appendLedgerEntry: appendLedgerEntry2 } = await Promise.resolve().then(() => (init_mesh_ledger(), mesh_ledger_exports));
25519
+ appendLedgerEntry2(meshId, {
25520
+ kind: "coordinator_started",
25521
+ sessionId: cliCmdLaunch.sessionId || cliCmdLaunch.id,
25522
+ providerType: cliType,
25523
+ payload: { workspace }
25524
+ });
25525
+ } catch {
25526
+ }
25527
+ return {
25528
+ success: true,
25529
+ meshId,
25530
+ cliType,
25531
+ workspace,
25532
+ sessionId: cliCmdLaunch.sessionId || cliCmdLaunch.id,
25533
+ mcpRegistered: true
25534
+ };
25535
+ }
23899
25536
  const configFormat = coordinatorSetup.configFormat;
23900
25537
  if (configFormat !== "claude_mcp_json" && configFormat !== "hermes_config_yaml") {
23901
25538
  return {
@@ -23950,9 +25587,11 @@ var DaemonCommandRouter = class {
23950
25587
  args: coordinatorSetup.mcpServer.args
23951
25588
  };
23952
25589
  if (args?.inlineMesh) {
25590
+ const modeArgIndex = coordinatorSetup.mcpServer.args.findIndex((value) => value === "--mode");
25591
+ const mcpTransport = modeArgIndex >= 0 ? coordinatorSetup.mcpServer.args[modeArgIndex + 1] : "ipc";
23953
25592
  mcpServerEntry.env = {
23954
25593
  ADHDEV_INLINE_MESH: JSON.stringify(mesh),
23955
- ADHDEV_MCP_TRANSPORT: "ipc"
25594
+ ADHDEV_MCP_TRANSPORT: mcpTransport === "local" ? "local" : "ipc"
23956
25595
  };
23957
25596
  }
23958
25597
  try {
@@ -23971,7 +25610,8 @@ var DaemonCommandRouter = class {
23971
25610
  if (hadExistingMcpConfig) {
23972
25611
  try {
23973
25612
  const parsedExistingMcpConfig = parseMeshCoordinatorMcpConfig(readFileSync17(mcpConfigPath, "utf-8"), configFormat);
23974
- existingMcpConfig = { ...existingMcpConfig, ...parsedExistingMcpConfig };
25613
+ const existingCoordinatorConfig = hermesManualFallback ? stripHermesCoordinatorTempModelProviderOverrides(parsedExistingMcpConfig) : parsedExistingMcpConfig;
25614
+ existingMcpConfig = { ...existingMcpConfig, ...existingCoordinatorConfig };
23975
25615
  copyFileSync4(mcpConfigPath, mcpConfigPath + ".backup");
23976
25616
  } catch (error) {
23977
25617
  LOG.error("MeshCoordinator", `Failed to parse existing MCP config ${mcpConfigPath}: ${error?.message || error}`);
@@ -31769,6 +33409,9 @@ function launchIDE(ide, workspacePath) {
31769
33409
  }
31770
33410
  }
31771
33411
 
33412
+ // src/boot/daemon-lifecycle.ts
33413
+ init_cli_detector();
33414
+
31772
33415
  // src/sessions/registry.ts
31773
33416
  var SessionRegistry = class {
31774
33417
  bySessionId = /* @__PURE__ */ new Map();
@@ -31999,7 +33642,8 @@ async function initDaemonComponents(config) {
31999
33642
  cdpManagers,
32000
33643
  sessionRegistry,
32001
33644
  detectedIdes: detectedIdesRef,
32002
- refreshProviderAvailability
33645
+ refreshProviderAvailability,
33646
+ dispatchMeshCommand: config.dispatchMeshCommand
32003
33647
  };
32004
33648
  setupMeshEventForwarding(components);
32005
33649
  return components;
@@ -32100,10 +33744,12 @@ export {
32100
33744
  IdeProviderInstance,
32101
33745
  InMemoryGitSnapshotStore,
32102
33746
  LOG,
33747
+ MAX_LEDGER_SLICE_LIMIT,
32103
33748
  MIN_GIT_WORKSPACE_POLL_INTERVAL_MS,
32104
33749
  MIN_MACHINE_RUNTIME_SUBSCRIPTION_INTERVAL_MS,
32105
33750
  MIN_SESSION_HOST_DIAGNOSTICS_SUBSCRIPTION_INTERVAL_MS,
32106
33751
  NodePtyTransportFactory,
33752
+ P2pRelayFailureError,
32107
33753
  ProviderCliAdapter,
32108
33754
  ProviderInstanceManager,
32109
33755
  ProviderLoader,
@@ -32114,12 +33760,16 @@ export {
32114
33760
  addNode,
32115
33761
  appendLedgerEntry,
32116
33762
  appendRecentActivity,
33763
+ appendRemoteLedgerEntries,
32117
33764
  buildAssistantChatMessage,
32118
33765
  buildChatMessage,
32119
33766
  buildChatMessageSignature,
32120
33767
  buildChatTailDeliverySignature,
32121
33768
  buildCoordinatorSystemPrompt,
32122
33769
  buildMachineInfo,
33770
+ buildMeshLedgerReconciliationEvidence,
33771
+ buildMeshLedgerReplicaEvidence,
33772
+ buildP2pRelayFailurePayload,
32123
33773
  buildPinnedGlobalInstallCommand,
32124
33774
  buildRuntimeSystemChatMessage,
32125
33775
  buildSessionEntries,
@@ -32130,9 +33780,11 @@ export {
32130
33780
  buildThoughtChatMessage,
32131
33781
  buildToolChatMessage,
32132
33782
  buildUserChatMessage,
33783
+ cancelTask,
32133
33784
  claimNextTask,
32134
33785
  classifyChatMessageVisibility,
32135
33786
  classifyHotChatSessionsForSubscriptionFlush,
33787
+ classifyP2pRelayFailure,
32136
33788
  clearDebugTrace,
32137
33789
  compareGitSnapshots,
32138
33790
  configureDebugTraceStore,
@@ -32173,6 +33825,7 @@ export {
32173
33825
  getLogLevel,
32174
33826
  getMesh,
32175
33827
  getMeshByRepo,
33828
+ getMeshQueueStats,
32176
33829
  getNpmExecOptions,
32177
33830
  getQueue,
32178
33831
  getRecentActivity,
@@ -32199,6 +33852,7 @@ export {
32199
33852
  isInternalChatMessage,
32200
33853
  isManagedStatusWaiting,
32201
33854
  isManagedStatusWorking,
33855
+ isP2pRelayTransportFailure,
32202
33856
  isPathInside,
32203
33857
  isSessionHostLiveRuntime,
32204
33858
  isSessionHostRecoverySnapshot,
@@ -32237,10 +33891,12 @@ export {
32237
33891
  probeCdpPort,
32238
33892
  readChatHistory,
32239
33893
  readLedgerEntries,
33894
+ readLedgerSlice,
32240
33895
  recordDebugTrace,
32241
33896
  registerExtensionProviders,
32242
33897
  removeNode,
32243
33898
  removeWorktree,
33899
+ requeueTask,
32244
33900
  resetConfig,
32245
33901
  resetDebugRuntimeConfig,
32246
33902
  resetState,