@adhdev/daemon-core 0.9.82-rc.34 → 0.9.82-rc.341

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 (318) hide show
  1. package/dist/boot/daemon-lifecycle.d.ts +8 -0
  2. package/dist/boot/process-hardening.d.ts +50 -0
  3. package/dist/build-info.d.ts +37 -0
  4. package/dist/chat/source-machine.d.ts +166 -0
  5. package/dist/chat/source-resolver.d.ts +104 -0
  6. package/dist/chat/subscription-updates.d.ts +1 -0
  7. package/dist/cli-adapter-types.d.ts +46 -2
  8. package/dist/cli-adapters/cli-script-runner.d.ts +117 -0
  9. package/dist/cli-adapters/cli-state-engine.d.ts +198 -0
  10. package/dist/cli-adapters/provider-cli-adapter.d.ts +116 -64
  11. package/dist/cli-adapters/provider-cli-parse.d.ts +4 -0
  12. package/dist/cli-adapters/provider-cli-runtime.d.ts +34 -0
  13. package/dist/cli-adapters/provider-cli-shared.d.ts +53 -0
  14. package/dist/cli-adapters/raw-terminal-io.d.ts +37 -0
  15. package/dist/cli-adapters/resolve-executable.d.ts +14 -0
  16. package/dist/cli-adapters/terminal-backends/ghostty-vt-backend.d.ts +6 -3
  17. package/dist/cli-adapters/terminal-backends/types.d.ts +10 -2
  18. package/dist/cli-adapters/terminal-screen.d.ts +4 -11
  19. package/dist/commands/cli-manager.d.ts +2 -1
  20. package/dist/commands/handler.d.ts +110 -0
  21. package/dist/commands/mesh-coordinator.d.ts +85 -1
  22. package/dist/commands/router.d.ts +308 -1
  23. package/dist/commands/upgrade-helper.d.ts +12 -0
  24. package/dist/config/chat-history.d.ts +9 -0
  25. package/dist/config/config.d.ts +5 -0
  26. package/dist/config/mesh-config.d.ts +77 -1
  27. package/dist/detection/ide-detector.d.ts +13 -0
  28. package/dist/detection/win32-ide-version.d.ts +37 -0
  29. package/dist/git/change-impact-config.d.ts +159 -0
  30. package/dist/git/git-commands.d.ts +11 -1
  31. package/dist/git/git-diff.d.ts +6 -0
  32. package/dist/git/git-executor.d.ts +11 -0
  33. package/dist/git/git-status.d.ts +23 -0
  34. package/dist/git/git-types.d.ts +2 -50
  35. package/dist/git/index.d.ts +2 -0
  36. package/dist/index.d.ts +52 -13
  37. package/dist/index.js +42012 -17098
  38. package/dist/index.js.map +1 -1
  39. package/dist/index.mjs +41683 -16890
  40. package/dist/index.mjs.map +1 -1
  41. package/dist/installer.d.ts +1 -4
  42. package/dist/ipc/local-ipc-server.d.ts +91 -0
  43. package/dist/launch.d.ts +1 -1
  44. package/dist/logging/async-batch-writer.d.ts +10 -0
  45. package/dist/logging/log-redactor.d.ts +24 -0
  46. package/dist/logging/log-tail-reader.d.ts +46 -0
  47. package/dist/mesh/contracts.d.ts +164 -0
  48. package/dist/mesh/coordinator-prompt.d.ts +36 -0
  49. package/dist/mesh/coordinator-registry.d.ts +59 -0
  50. package/dist/mesh/mesh-active-work.d.ts +174 -0
  51. package/dist/mesh/mesh-delivery-policy.d.ts +131 -0
  52. package/dist/mesh/mesh-events-coordinator.d.ts +169 -0
  53. package/dist/mesh/mesh-events-pending.d.ts +45 -0
  54. package/dist/mesh/mesh-events-stale.d.ts +40 -0
  55. package/dist/mesh/mesh-events-utils.d.ts +66 -0
  56. package/dist/mesh/mesh-events.d.ts +6 -49
  57. package/dist/mesh/mesh-fast-forward.d.ts +81 -0
  58. package/dist/mesh/mesh-host-ownership.d.ts +9 -0
  59. package/dist/mesh/mesh-init.d.ts +86 -0
  60. package/dist/mesh/mesh-ledger-reconciliation.d.ts +23 -3
  61. package/dist/mesh/mesh-ledger.d.ts +77 -1
  62. package/dist/mesh/mesh-missions.d.ts +116 -0
  63. package/dist/mesh/mesh-reconcile-loop.d.ts +7 -0
  64. package/dist/mesh/mesh-refine-batch.d.ts +68 -0
  65. package/dist/mesh/mesh-refine-status.d.ts +62 -0
  66. package/dist/mesh/mesh-review-inbox.d.ts +90 -0
  67. package/dist/mesh/mesh-routing.d.ts +70 -0
  68. package/dist/mesh/mesh-runtime-store.d.ts +418 -0
  69. package/dist/mesh/mesh-task-stats.d.ts +49 -0
  70. package/dist/mesh/mesh-unresolved-forward-outbox.d.ts +30 -0
  71. package/dist/mesh/mesh-work-queue.d.ts +208 -5
  72. package/dist/mesh/preview-freshness.d.ts +18 -0
  73. package/dist/mesh/refine-config.d.ts +216 -0
  74. package/dist/mesh/worktree-bootstrap-config.d.ts +135 -0
  75. package/dist/providers/acp-provider-instance.d.ts +2 -0
  76. package/dist/providers/approval-utils.d.ts +13 -0
  77. package/dist/providers/chat-message-normalization.d.ts +12 -0
  78. package/dist/providers/cli-provider-instance.d.ts +76 -3
  79. package/dist/providers/contracts.d.ts +139 -6
  80. package/dist/providers/external-sources.d.ts +71 -0
  81. package/dist/providers/native-history/antigravity-cli-transcript.d.ts +100 -0
  82. package/dist/providers/native-history/claude-cli-transcript.d.ts +70 -0
  83. package/dist/providers/native-history/codex-cli-transcript.d.ts +73 -0
  84. package/dist/providers/native-history/dispatcher.d.ts +27 -0
  85. package/dist/providers/native-history/hermes-cli-transcript.d.ts +30 -0
  86. package/dist/providers/native-history/index.d.ts +13 -0
  87. package/dist/providers/provider-instance-manager.d.ts +13 -0
  88. package/dist/providers/provider-instance.d.ts +13 -1
  89. package/dist/providers/provider-loader.d.ts +26 -4
  90. package/dist/providers/provider-trust.d.ts +31 -0
  91. package/dist/providers/read-chat-contract.d.ts +29 -0
  92. package/dist/providers/sdk/v1/builders/acp/detect-status.d.ts +68 -0
  93. package/dist/providers/sdk/v1/builders/cli/detect-status.d.ts +85 -0
  94. package/dist/providers/sdk/v1/builders/cli/parse-approval-squash.d.ts +59 -0
  95. package/dist/providers/sdk/v1/builders/cli/parse-approval.d.ts +65 -0
  96. package/dist/providers/sdk/v1/builders/cli/parse-session.d.ts +91 -0
  97. package/dist/providers/sdk/v1/builders/cli/visible-region.d.ts +42 -0
  98. package/dist/providers/sdk/v1/fixture-tooling/format.d.ts +126 -0
  99. package/dist/providers/sdk/v1/fixture-tooling/index.d.ts +8 -0
  100. package/dist/providers/sdk/v1/fixture-tooling/replay.d.ts +38 -0
  101. package/dist/providers/sdk/v1/index.d.ts +30 -0
  102. package/dist/providers/sdk/v1/sandbox/README-design.d.ts +193 -0
  103. package/dist/providers/sdk/v1/sandbox/require-whitelist.d.ts +74 -0
  104. package/dist/providers/sdk/v1/sandbox/script-runner.d.ts +98 -0
  105. package/dist/providers/sdk/v1/types/cli/index.d.ts +277 -0
  106. package/dist/providers/sdk/v1/types/common/index.d.ts +203 -0
  107. package/dist/providers/sdk/v1/validators/index.d.ts +5 -0
  108. package/dist/providers/sdk/v1/validators/manifest.d.ts +40 -0
  109. package/dist/providers/sdk/v1/validators/taint.d.ts +52 -0
  110. package/dist/providers/spec/adapter.d.ts +69 -0
  111. package/dist/providers/spec/cli-adapter.d.ts +217 -0
  112. package/dist/providers/spec/evaluator.d.ts +23 -0
  113. package/dist/providers/spec/fsm-driver.d.ts +306 -0
  114. package/dist/providers/spec/fsm-evaluator.d.ts +72 -0
  115. package/dist/providers/spec/fsm-loader.d.ts +14 -0
  116. package/dist/providers/spec/fsm-types.d.ts +144 -0
  117. package/dist/providers/spec/native-history-executor.d.ts +41 -0
  118. package/dist/providers/spec/pre-launch-trust.d.ts +16 -0
  119. package/dist/providers/spec/route.d.ts +4 -0
  120. package/dist/providers/spec/types.d.ts +180 -0
  121. package/dist/providers/status-monitor.d.ts +7 -7
  122. package/dist/providers/transcript-v2.d.ts +176 -0
  123. package/dist/providers/types/interactive-prompt.d.ts +84 -0
  124. package/dist/providers/working-dir.d.ts +17 -0
  125. package/dist/repo-mesh-types.d.ts +295 -13
  126. package/dist/sessions/registry.d.ts +6 -0
  127. package/dist/shared-types-extra.d.ts +2 -4
  128. package/dist/shared-types.d.ts +59 -2
  129. package/dist/status/normalize.d.ts +1 -1
  130. package/dist/status/normalize.js +1 -0
  131. package/dist/status/normalize.js.map +1 -1
  132. package/dist/status/normalize.mjs +1 -0
  133. package/dist/status/normalize.mjs.map +1 -1
  134. package/dist/status/reporter.d.ts +2 -0
  135. package/dist/status/snapshot.d.ts +26 -0
  136. package/dist/system/load-better-sqlite3.d.ts +21 -0
  137. package/dist/types.d.ts +5 -0
  138. package/package.json +7 -3
  139. package/src/agent-stream/poller.ts +2 -3
  140. package/src/agent-stream/provider-adapter.ts +1 -1
  141. package/src/boot/daemon-lifecycle.ts +55 -12
  142. package/src/boot/process-hardening.ts +89 -0
  143. package/src/build-info.ts +73 -0
  144. package/src/chat/source-machine.ts +534 -0
  145. package/src/chat/source-resolver.ts +0 -0
  146. package/src/chat/subscription-updates.ts +20 -1
  147. package/src/cli-adapter-types.d.ts +1 -0
  148. package/src/cli-adapter-types.ts +47 -2
  149. package/src/cli-adapters/cli-script-runner.ts +421 -0
  150. package/src/cli-adapters/cli-state-engine.ts +1215 -0
  151. package/src/cli-adapters/provider-cli-adapter.d.ts +1 -1
  152. package/src/cli-adapters/provider-cli-adapter.ts +774 -1141
  153. package/src/cli-adapters/provider-cli-parse.d.ts +1 -0
  154. package/src/cli-adapters/provider-cli-parse.ts +13 -0
  155. package/src/cli-adapters/provider-cli-runtime.ts +56 -7
  156. package/src/cli-adapters/provider-cli-shared.d.ts +4 -0
  157. package/src/cli-adapters/provider-cli-shared.ts +100 -11
  158. package/src/cli-adapters/pty-transport.ts +2 -1
  159. package/src/cli-adapters/raw-terminal-io.ts +252 -0
  160. package/src/cli-adapters/resolve-executable.ts +90 -0
  161. package/src/cli-adapters/session-host-transport.ts +2 -1
  162. package/src/cli-adapters/terminal-backends/ghostty-vt-backend.ts +53 -29
  163. package/src/cli-adapters/terminal-backends/types.ts +10 -3
  164. package/src/cli-adapters/terminal-screen.ts +16 -81
  165. package/src/commands/chat-commands.ts +1823 -62
  166. package/src/commands/cli-manager.ts +286 -14
  167. package/src/commands/handler.ts +809 -2
  168. package/src/commands/mesh-coordinator.ts +332 -122
  169. package/src/commands/router.ts +7433 -1317
  170. package/src/commands/stream-commands.ts +8 -0
  171. package/src/commands/upgrade-helper.ts +81 -30
  172. package/src/config/chat-history.ts +483 -24
  173. package/src/config/config.ts +12 -0
  174. package/src/config/mesh-config.ts +388 -6
  175. package/src/config/recent-activity.ts +8 -2
  176. package/src/daemon/dev-auto-implement.ts +3 -2
  177. package/src/daemon/dev-cli-debug.ts +10 -1
  178. package/src/detection/cli-detector.ts +28 -9
  179. package/src/detection/ide-detector.ts +55 -16
  180. package/src/detection/win32-ide-version.ts +106 -0
  181. package/src/git/change-impact-config.ts +354 -0
  182. package/src/git/git-commands.ts +55 -14
  183. package/src/git/git-diff.ts +53 -0
  184. package/src/git/git-executor.ts +12 -0
  185. package/src/git/git-status.ts +574 -43
  186. package/src/git/git-types.ts +14 -62
  187. package/src/git/git-worktree.ts +8 -1
  188. package/src/git/index.ts +16 -0
  189. package/src/index.ts +146 -12
  190. package/src/installer.d.ts +1 -1
  191. package/src/installer.ts +8 -6
  192. package/src/ipc/local-ipc-server.ts +278 -0
  193. package/src/launch.d.ts +1 -1
  194. package/src/launch.ts +37 -28
  195. package/src/logging/async-batch-writer.ts +55 -0
  196. package/src/logging/log-redactor.ts +100 -0
  197. package/src/logging/log-tail-reader.ts +220 -0
  198. package/src/logging/logger.ts +2 -1
  199. package/src/mesh/contracts.ts +329 -0
  200. package/src/mesh/coordinator-prompt.ts +234 -31
  201. package/src/mesh/coordinator-registry.ts +121 -0
  202. package/src/mesh/mesh-active-work.ts +623 -0
  203. package/src/mesh/mesh-delivery-policy.ts +315 -0
  204. package/src/mesh/mesh-events-coordinator.ts +2144 -0
  205. package/src/mesh/mesh-events-pending.ts +508 -0
  206. package/src/mesh/mesh-events-stale.ts +290 -0
  207. package/src/mesh/mesh-events-utils.ts +300 -0
  208. package/src/mesh/mesh-events.ts +30 -1035
  209. package/src/mesh/mesh-fast-forward.ts +839 -0
  210. package/src/mesh/mesh-host-ownership.ts +73 -0
  211. package/src/mesh/mesh-init.ts +260 -0
  212. package/src/mesh/mesh-ledger-reconciliation.ts +12 -5
  213. package/src/mesh/mesh-ledger.ts +589 -102
  214. package/src/mesh/mesh-missions.ts +248 -0
  215. package/src/mesh/mesh-reconcile-loop.ts +822 -0
  216. package/src/mesh/mesh-refine-batch.ts +197 -0
  217. package/src/mesh/mesh-refine-status.ts +231 -0
  218. package/src/mesh/mesh-review-inbox.ts +307 -0
  219. package/src/mesh/mesh-routing.ts +291 -0
  220. package/src/mesh/mesh-runtime-store.ts +1626 -0
  221. package/src/mesh/mesh-task-stats.ts +154 -0
  222. package/src/mesh/mesh-unresolved-forward-outbox.ts +185 -0
  223. package/src/mesh/mesh-work-queue.ts +888 -141
  224. package/src/mesh/preview-freshness.ts +118 -0
  225. package/src/mesh/refine-config.ts +423 -0
  226. package/src/mesh/worktree-bootstrap-config.ts +331 -0
  227. package/src/providers/acp-provider-instance.ts +25 -9
  228. package/src/providers/approval-utils.d.ts +4 -0
  229. package/src/providers/approval-utils.ts +47 -5
  230. package/src/providers/chat-message-normalization.ts +45 -5
  231. package/src/providers/cli-provider-instance.ts +1038 -99
  232. package/src/providers/contracts.d.ts +55 -0
  233. package/src/providers/contracts.ts +150 -6
  234. package/src/providers/extension-provider-instance.ts +12 -7
  235. package/src/providers/external-sources.ts +218 -0
  236. package/src/providers/ide-provider-instance.ts +35 -12
  237. package/src/providers/native-history/antigravity-cli-transcript.ts +643 -0
  238. package/src/providers/native-history/claude-cli-transcript.ts +396 -0
  239. package/src/providers/native-history/codex-cli-transcript.ts +479 -0
  240. package/src/providers/native-history/dispatcher.ts +340 -0
  241. package/src/providers/native-history/hermes-cli-transcript.ts +230 -0
  242. package/src/providers/native-history/index.ts +30 -0
  243. package/src/providers/provider-instance-manager.ts +30 -0
  244. package/src/providers/provider-instance.ts +10 -1
  245. package/src/providers/provider-loader.ts +670 -50
  246. package/src/providers/provider-schema.ts +87 -14
  247. package/src/providers/provider-trust.ts +114 -0
  248. package/src/providers/read-chat-contract.ts +76 -16
  249. package/src/providers/sdk/README.md +49 -0
  250. package/src/providers/sdk/v1/builders/acp/detect-status.ts +144 -0
  251. package/src/providers/sdk/v1/builders/cli/detect-status.ts +262 -0
  252. package/src/providers/sdk/v1/builders/cli/parse-approval-squash.ts +158 -0
  253. package/src/providers/sdk/v1/builders/cli/parse-approval.ts +250 -0
  254. package/src/providers/sdk/v1/builders/cli/parse-session.ts +276 -0
  255. package/src/providers/sdk/v1/builders/cli/visible-region.ts +143 -0
  256. package/src/providers/sdk/v1/fixture-tooling/format.ts +130 -0
  257. package/src/providers/sdk/v1/fixture-tooling/index.ts +22 -0
  258. package/src/providers/sdk/v1/fixture-tooling/replay.ts +352 -0
  259. package/src/providers/sdk/v1/index.ts +152 -0
  260. package/src/providers/sdk/v1/sandbox/README-design.ts +195 -0
  261. package/src/providers/sdk/v1/sandbox/require-whitelist.ts +472 -0
  262. package/src/providers/sdk/v1/sandbox/script-runner.ts +150 -0
  263. package/src/providers/sdk/v1/schemas/cli/provider.schema.json +504 -0
  264. package/src/providers/sdk/v1/schemas/primitives/acp-session-protocol-v1.json +131 -0
  265. package/src/providers/sdk/v1/schemas/primitives/native-history-codex-rollout-v1.json +66 -0
  266. package/src/providers/sdk/v1/schemas/primitives/tui-approval-squash-v1.json +91 -0
  267. package/src/providers/sdk/v1/schemas/primitives/tui-assistant-block-v1.json +91 -0
  268. package/src/providers/sdk/v1/schemas/primitives/tui-cue-ordering-v1.json +47 -0
  269. package/src/providers/sdk/v1/schemas/primitives/tui-dispatch-order-v1.json +32 -0
  270. package/src/providers/sdk/v1/schemas/primitives/tui-footer-chrome-v1.json +42 -0
  271. package/src/providers/sdk/v1/schemas/primitives/tui-index-finder-v1.json +27 -0
  272. package/src/providers/sdk/v1/schemas/primitives/tui-modal-v1.json +125 -0
  273. package/src/providers/sdk/v1/schemas/primitives/tui-prompt-marker-v1.json +45 -0
  274. package/src/providers/sdk/v1/schemas/primitives/tui-session-id-extraction-v1.json +46 -0
  275. package/src/providers/sdk/v1/schemas/primitives/tui-settled-prompt-v1.json +71 -0
  276. package/src/providers/sdk/v1/schemas/primitives/tui-spinner-v1.json +83 -0
  277. package/src/providers/sdk/v1/schemas/primitives/tui-transcript-pty-v1.json +83 -0
  278. package/src/providers/sdk/v1/schemas/primitives/tui-visible-region-v1.json +57 -0
  279. package/src/providers/sdk/v1/schemas/primitives/tui-welcome-screen-v1.json +35 -0
  280. package/src/providers/sdk/v1/types/cli/index.ts +365 -0
  281. package/src/providers/sdk/v1/types/common/index.ts +229 -0
  282. package/src/providers/sdk/v1/validators/index.ts +19 -0
  283. package/src/providers/sdk/v1/validators/manifest.ts +110 -0
  284. package/src/providers/sdk/v1/validators/taint.ts +309 -0
  285. package/src/providers/spec/adapter.ts +168 -0
  286. package/src/providers/spec/cli-adapter.ts +986 -0
  287. package/src/providers/spec/evaluator.ts +345 -0
  288. package/src/providers/spec/fsm-driver.ts +1015 -0
  289. package/src/providers/spec/fsm-evaluator.ts +255 -0
  290. package/src/providers/spec/fsm-loader.ts +120 -0
  291. package/src/providers/spec/fsm-types.ts +238 -0
  292. package/src/providers/spec/native-history-executor.ts +1136 -0
  293. package/src/providers/spec/pre-launch-trust.ts +104 -0
  294. package/src/providers/spec/route.ts +51 -0
  295. package/src/providers/spec/types.ts +226 -0
  296. package/src/providers/status-monitor.d.ts +7 -7
  297. package/src/providers/status-monitor.ts +37 -22
  298. package/src/providers/transcript-v2.ts +567 -0
  299. package/src/providers/types/interactive-prompt.ts +536 -0
  300. package/src/providers/version-archive.ts +64 -24
  301. package/src/providers/working-dir.ts +23 -0
  302. package/src/repo-mesh-types.ts +378 -14
  303. package/src/sessions/registry.ts +6 -0
  304. package/src/shared-types-extra.ts +2 -4
  305. package/src/shared-types.d.ts +8 -0
  306. package/src/shared-types.ts +64 -1
  307. package/src/status/builders.ts +26 -6
  308. package/src/status/normalize.ts +2 -0
  309. package/src/status/reporter.ts +19 -1
  310. package/src/status/snapshot.ts +95 -26
  311. package/src/system/host-memory.ts +29 -12
  312. package/src/system/load-better-sqlite3.ts +68 -0
  313. package/src/types.ts +5 -0
  314. package/dist/cli-adapters/terminal-backends/xterm-backend.d.ts +0 -17
  315. package/dist/mesh/mesh-sync.d.ts +0 -53
  316. package/src/cli-adapters/terminal-backends/ghostty-vt-backend.d.ts +0 -16
  317. package/src/cli-adapters/terminal-backends/xterm-backend.ts +0 -97
  318. package/src/mesh/mesh-sync.ts +0 -111
@@ -0,0 +1,508 @@
1
+ import { appendFileSync, existsSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync } from 'fs';
2
+ import { join } from 'path';
3
+ import { randomUUID } from 'crypto';
4
+ import { LOG } from '../logging/logger.js';
5
+ import { getLedgerDir, readLedgerEntries } from './mesh-ledger.js';
6
+ import { MeshRuntimeStore } from './mesh-runtime-store.js';
7
+ import { buildMeshSystemMessage, readNonEmptyString, readRecord, resolveEventSessionId } from './mesh-events-utils.js';
8
+
9
+ // ---------------------------------------------------------------------------
10
+ // MCP coordinator pending-event queue — FILE-BASED PERSISTENCE
11
+ // ---------------------------------------------------------------------------
12
+ // When a mesh event fires but no CLI coordinator session is registered (e.g.
13
+ // the coordinator is Claude Code running via MCP), we persist the event to a
14
+ // per-mesh JSONL file so it survives daemon restarts. The 50-entry hard cap
15
+ // is removed; the file is drained atomically on each get_pending_mesh_events
16
+ // call and limited to 100 KB to prevent runaway growth.
17
+ //
18
+ // File: <ledgerDir>/<meshId>.pending-events.jsonl
19
+ // ---------------------------------------------------------------------------
20
+
21
+ export interface PendingMeshCoordinatorEvent {
22
+ event: string;
23
+ meshId: string;
24
+ nodeLabel: string;
25
+ nodeId?: string;
26
+ workspace?: string;
27
+ metadataEvent: Record<string, unknown>;
28
+ coordinatorMessage?: string;
29
+ queuedAt: number;
30
+ /**
31
+ * When set, this event is intended for a specific coordinator daemon.
32
+ * Coordinators on other daemons should ignore it during drain.
33
+ * Absent on legacy events — treated as broadcast to any coordinator.
34
+ */
35
+ targetCoordinatorDaemonId?: string;
36
+ }
37
+
38
+ const REFINE_TERMINAL_EVENTS = new Set(['refine:completed', 'refine:failed']);
39
+
40
+ /** Normalise a coordinator-daemon-id argument (single id, list, or undefined) into a
41
+ * de-duplicated list of non-empty strings. The first entry is treated as primary for
42
+ * per-daemon JSONL file naming; all entries are accepted by drain/peek targeting. */
43
+ function normalizeCoordinatorDaemonIds(
44
+ coordinatorDaemonId?: string | null | ReadonlyArray<string>,
45
+ ): string[] {
46
+ const raw = Array.isArray(coordinatorDaemonId)
47
+ ? coordinatorDaemonId
48
+ : coordinatorDaemonId != null ? [coordinatorDaemonId] : [];
49
+ const seen = new Set<string>();
50
+ const out: string[] = [];
51
+ for (const id of raw) {
52
+ if (typeof id !== 'string') continue;
53
+ const trimmed = id.trim();
54
+ if (!trimmed || seen.has(trimmed)) continue;
55
+ seen.add(trimmed);
56
+ out.push(trimmed);
57
+ }
58
+ return out;
59
+ }
60
+
61
+ export function readRefineJobId(event: { metadataEvent?: Record<string, unknown> } | Record<string, unknown>): string {
62
+ const metadata = readRecord((event as any).metadataEvent) || event as Record<string, unknown>;
63
+ const result = readRecord(metadata.result);
64
+ const refineJob = readRecord(result?.refineJob);
65
+ return readNonEmptyString(metadata.jobId) || readNonEmptyString(refineJob?.jobId);
66
+ }
67
+
68
+ function buildRefineTerminalEventFingerprint(meshId: string, eventName: string, metadataEvent: Record<string, unknown>): string {
69
+ const jobId = readRefineJobId({ metadataEvent });
70
+ return jobId && REFINE_TERMINAL_EVENTS.has(eventName) ? `${meshId}::${eventName}::${jobId}` : '';
71
+ }
72
+
73
+ function hasPendingRefineTerminalEventDuplicate(event: PendingMeshCoordinatorEvent): boolean {
74
+ if (!REFINE_TERMINAL_EVENTS.has(event.event)) return false;
75
+ const jobId = readRefineJobId(event);
76
+ if (!jobId) return false;
77
+ return readPendingMeshCoordinatorEventsFromDisk(event.meshId).some((pending) =>
78
+ pending.event === event.event && readRefineJobId(pending) === jobId,
79
+ );
80
+ }
81
+
82
+ export function buildPendingEventFingerprint(event: PendingMeshCoordinatorEvent): string {
83
+ const metadata = readRecord(event.metadataEvent) || {};
84
+ // Bootstrap events are node-scoped: dedup by meshId+event+nodeId only.
85
+ // They carry no sessionId/taskId/timestamp — using those fields would produce
86
+ // an empty fingerprint that defeats dedup entirely.
87
+ if (event.event === 'worktree_bootstrap_complete' || event.event === 'worktree_bootstrap_failed') {
88
+ return [event.meshId, event.event, event.nodeId || ''].join('::');
89
+ }
90
+ const sessionId = resolveEventSessionId(metadata);
91
+ const providerSessionId = readNonEmptyString(metadata.providerSessionId);
92
+ const taskId = readNonEmptyString(metadata.taskId) || readNonEmptyString(readRecord(metadata.payload)?.taskId);
93
+ const jobId = readRefineJobId(event);
94
+ const timestamp = metadata.timestamp !== undefined && metadata.timestamp !== null ? String(metadata.timestamp) : '';
95
+ return [
96
+ event.meshId,
97
+ event.event,
98
+ event.nodeId || '',
99
+ sessionId || '',
100
+ providerSessionId || '',
101
+ taskId || '',
102
+ jobId || '',
103
+ timestamp || '',
104
+ ].join('::');
105
+ }
106
+
107
+ // NOTE: the former R3 "direct-delivered" marker (markMeshCoordinatorEventDirectDelivered /
108
+ // wasDirectDeliveredToCoordinator) was removed when spontaneous PTY direct-inject was retired.
109
+ // Delivery is now queue-drain-only: an event is consumed by exactly one drainer via the atomic
110
+ // SQLite drained=1 marking, so there is no PTY-vs-poll double-delivery left to dedup against.
111
+ // The dormant mesh_direct_delivered_events table / store helpers remain (harmless) but unused.
112
+
113
+ export function hasPendingCoordinatorEventDuplicate(event: PendingMeshCoordinatorEvent): boolean {
114
+ const fingerprint = buildPendingEventFingerprint(event);
115
+ if (!fingerprint.trim()) return false;
116
+ // Check SQLite inbox first (G3 primary path)
117
+ try {
118
+ if (MeshRuntimeStore.getInstance().hasPendingEventFingerprint(event.meshId, fingerprint)) return true;
119
+ } catch { /* fall through to JSONL check */ }
120
+ return readPendingMeshCoordinatorEventsFromDisk(event.meshId).some((pending) => buildPendingEventFingerprint(pending) === fingerprint);
121
+ }
122
+
123
+ function getPendingEventsPath(meshId: string, coordinatorDaemonId?: string): string {
124
+ const safe = meshId.replace(/[^a-zA-Z0-9_-]/g, '_');
125
+ if (coordinatorDaemonId) {
126
+ const safeDaemon = coordinatorDaemonId.replace(/[^a-zA-Z0-9_-]/g, '_');
127
+ return join(getLedgerDir(), `${safe}-${safeDaemon}.pending-events.jsonl`);
128
+ }
129
+ return join(getLedgerDir(), `${safe}.pending-events.jsonl`);
130
+ }
131
+
132
+ function readPendingMeshCoordinatorEventsFromDisk(meshId?: string, coordinatorDaemonId?: string | ReadonlyArray<string>): PendingMeshCoordinatorEvent[] {
133
+ if (!meshId) return [];
134
+ const daemonIds = normalizeCoordinatorDaemonIds(coordinatorDaemonId);
135
+ const primaryDaemonId = daemonIds[0];
136
+ // Read coordinator-scoped file first; fall back to legacy shared file.
137
+ const paths = primaryDaemonId
138
+ ? [getPendingEventsPath(meshId, primaryDaemonId), getPendingEventsPath(meshId)]
139
+ : [getPendingEventsPath(meshId)];
140
+ const events: PendingMeshCoordinatorEvent[] = [];
141
+ for (const path of paths) {
142
+ if (!existsSync(path)) continue;
143
+ try {
144
+ const raw = readFileSync(path, 'utf-8');
145
+ const parsed = raw.split('\n').filter(Boolean).flatMap(line => {
146
+ try { return [JSON.parse(line) as PendingMeshCoordinatorEvent]; } catch { return []; }
147
+ });
148
+ // If reading the shared file, filter to events that target this coordinator or are unscoped.
149
+ const filtered = (primaryDaemonId && path === getPendingEventsPath(meshId))
150
+ ? parsed.filter(e => !e.targetCoordinatorDaemonId || daemonIds.includes(e.targetCoordinatorDaemonId))
151
+ : parsed;
152
+ events.push(...filtered);
153
+ } catch { /* skip unreadable files */ }
154
+ }
155
+ return events;
156
+ }
157
+
158
+ function refineTerminalEventFromLedger(meshId: string, pending: readonly PendingMeshCoordinatorEvent[]): PendingMeshCoordinatorEvent[] {
159
+ const acceptedJobIds = new Set(
160
+ pending
161
+ .filter(event => event.event === 'refine:accepted')
162
+ .map(event => readRefineJobId(event))
163
+ .filter(Boolean),
164
+ );
165
+ if (acceptedJobIds.size === 0) return [];
166
+ const existingTerminalJobIds = new Set(
167
+ pending
168
+ .filter(event => REFINE_TERMINAL_EVENTS.has(event.event))
169
+ .map(event => `${event.event}:${readRefineJobId(event)}`)
170
+ .filter(value => !value.endsWith(':')),
171
+ );
172
+ const backfilled: PendingMeshCoordinatorEvent[] = [];
173
+ const entries = readLedgerEntries(meshId, { tail: 200 });
174
+ for (let i = entries.length - 1; i >= 0; i--) {
175
+ const entry = entries[i];
176
+ if (entry.kind !== 'task_completed' && entry.kind !== 'task_failed') continue;
177
+ const payload = readRecord(entry.payload);
178
+ if (payload?.source !== 'refine_mesh_node_async_job') continue;
179
+ const refineJob = readRecord(payload.refineJob);
180
+ const jobId = readNonEmptyString(refineJob?.jobId);
181
+ if (!jobId || !acceptedJobIds.has(jobId)) continue;
182
+ const eventName = entry.kind === 'task_completed' ? 'refine:completed' : 'refine:failed';
183
+ if (existingTerminalJobIds.has(`${eventName}:${jobId}`)) continue;
184
+ existingTerminalJobIds.add(`${eventName}:${jobId}`);
185
+ const result = readRecord(payload.result);
186
+ const metadataEvent = {
187
+ source: 'refine_mesh_node_async_job',
188
+ jobId,
189
+ interactionId: readNonEmptyString(refineJob?.interactionId),
190
+ meshId,
191
+ nodeId: readNonEmptyString(refineJob?.nodeId) || entry.nodeId,
192
+ targetDaemonId: readNonEmptyString(refineJob?.targetDaemonId),
193
+ workspace: readNonEmptyString(refineJob?.workspace),
194
+ status: eventName === 'refine:completed' ? 'completed' : 'failed',
195
+ startedAt: readNonEmptyString(refineJob?.startedAt),
196
+ completedAt: readNonEmptyString(refineJob?.completedAt) || entry.timestamp,
197
+ retryOfJobId: readNonEmptyString(refineJob?.retryOfJobId) || readNonEmptyString(payload.retryOfJobId),
198
+ ...(result ? { result } : {}),
199
+ };
200
+ const nodeLabel = readNonEmptyString(refineJob?.nodeId) || entry.nodeId || 'refine job';
201
+ backfilled.push({
202
+ event: eventName,
203
+ meshId,
204
+ nodeLabel,
205
+ nodeId: readNonEmptyString(refineJob?.nodeId) || entry.nodeId,
206
+ workspace: readNonEmptyString(refineJob?.workspace),
207
+ metadataEvent,
208
+ coordinatorMessage: buildMeshSystemMessage({ event: eventName, nodeLabel, metadataEvent }),
209
+ queuedAt: Date.now(),
210
+ });
211
+ }
212
+ return backfilled.reverse();
213
+ }
214
+
215
+ function reconcilePendingMeshCoordinatorEvents(meshId: string, events: PendingMeshCoordinatorEvent[]): PendingMeshCoordinatorEvent[] {
216
+ const backfilled = refineTerminalEventFromLedger(meshId, events);
217
+ // A refine:accepted event is a provisional "job accepted, result to follow" signal.
218
+ // Once its terminal (completed/failed) counterpart for the same jobId exists — whether
219
+ // already direct-queued into the pending store OR backfilled from the ledger here — the
220
+ // accepted is superseded and is dropped so the coordinator isn't shown stale duplicate
221
+ // noise alongside the terminal outcome.
222
+ const terminalJobIds = new Set(
223
+ [...events.filter(event => REFINE_TERMINAL_EVENTS.has(event.event)), ...backfilled]
224
+ .map(event => readRefineJobId(event))
225
+ .filter(Boolean),
226
+ );
227
+ const reconciled = terminalJobIds.size === 0
228
+ ? events
229
+ : events.filter(event => !(event.event === 'refine:accepted' && terminalJobIds.has(readRefineJobId(event))));
230
+ return backfilled.length === 0 ? reconciled : [...reconciled, ...backfilled];
231
+ }
232
+
233
+ const MAX_PENDING_EVENTS_BYTES = 100 * 1024; // 100 KB — keep the pending file small
234
+ const MAX_PENDING_EVENTS_KEEP = 50; // keep the last 50 events when trimming
235
+
236
+ function trimPendingEventsIfNeeded(path: string): void {
237
+ try {
238
+ if (!existsSync(path)) return;
239
+ if (statSync(path).size <= MAX_PENDING_EVENTS_BYTES) return;
240
+ const lines = readFileSync(path, 'utf-8').split('\n').filter(Boolean);
241
+ if (lines.length <= MAX_PENDING_EVENTS_KEEP) return;
242
+ writeFileSync(path, lines.slice(-MAX_PENDING_EVENTS_KEEP).join('\n') + '\n', 'utf-8');
243
+ } catch { /* best-effort; if trim fails, append still proceeds */ }
244
+ }
245
+
246
+ export function queuePendingMeshCoordinatorEvent(event: PendingMeshCoordinatorEvent): boolean {
247
+ try {
248
+ if (hasPendingRefineTerminalEventDuplicate(event)) {
249
+ LOG.info('MeshEvents', `Suppressed duplicate pending ${event.event} for refine job ${readRefineJobId(event)}`);
250
+ return true;
251
+ }
252
+ if (hasPendingCoordinatorEventDuplicate(event)) {
253
+ LOG.info('MeshEvents', `Suppressed duplicate pending ${event.event} for mesh ${event.meshId}`);
254
+ return true;
255
+ }
256
+
257
+ const fingerprint = buildPendingEventFingerprint(event);
258
+
259
+ // G3: Write to SQLite inbox (primary path going forward)
260
+ try {
261
+ MeshRuntimeStore.getInstance().insertPendingEvent({
262
+ id: randomUUID(),
263
+ meshId: event.meshId,
264
+ coordinatorDaemonId: event.targetCoordinatorDaemonId ?? null,
265
+ event: event.event,
266
+ payload: event,
267
+ fingerprint: fingerprint || null,
268
+ queuedAt: event.queuedAt,
269
+ });
270
+ } catch {
271
+ // SQLite write failure is non-fatal; JSONL fallback below still works.
272
+ }
273
+
274
+ // Also write to JSONL (retained as legacy/export artifact)
275
+ const path = getPendingEventsPath(event.meshId, event.targetCoordinatorDaemonId);
276
+ trimPendingEventsIfNeeded(path);
277
+ appendFileSync(path, JSON.stringify(event) + '\n', 'utf-8');
278
+ return true;
279
+ } catch (e: any) {
280
+ LOG.warn('MeshEvents', `Failed to persist pending coordinator event: ${e?.message || e}`);
281
+ return false;
282
+ }
283
+ }
284
+
285
+ // Atomically rename the file before reading so concurrent drains can't both consume
286
+ // the same events. renameSync is atomic on POSIX (same filesystem); only one caller
287
+ // wins the rename — the other gets ENOENT and returns null, preventing duplicate delivery.
288
+ function atomicDrainFile(path: string): string | null {
289
+ const tmpPath = `${path}.draining`;
290
+ try {
291
+ renameSync(path, tmpPath);
292
+ } catch {
293
+ return null; // another drain already renamed it, or file doesn't exist
294
+ }
295
+ try {
296
+ const content = readFileSync(tmpPath, 'utf-8');
297
+ try { unlinkSync(tmpPath); } catch { /* already cleaned up */ }
298
+ return content;
299
+ } catch {
300
+ try { unlinkSync(tmpPath); } catch { /* best-effort cleanup */ }
301
+ return null;
302
+ }
303
+ }
304
+
305
+ // Selectively drain a JSONL pending-events file: atomically claim it (rename), then
306
+ // consume only the lines whose parsed event matches `predicate` and rewrite the
307
+ // remaining (kept) lines back to the original path. Unparseable lines are kept
308
+ // untouched. Returns the consumed events. The rename makes claiming exclusive —
309
+ // only one concurrent caller wins, so there is no double-consume of the same lines.
310
+ function selectiveDrainFile(
311
+ path: string,
312
+ predicate: (event: PendingMeshCoordinatorEvent) => boolean,
313
+ ): PendingMeshCoordinatorEvent[] {
314
+ const tmpPath = `${path}.draining`;
315
+ try {
316
+ renameSync(path, tmpPath);
317
+ } catch {
318
+ return []; // another drain claimed it, or the file doesn't exist
319
+ }
320
+ let content: string;
321
+ try {
322
+ content = readFileSync(tmpPath, 'utf-8');
323
+ } catch {
324
+ try { unlinkSync(tmpPath); } catch { /* best-effort */ }
325
+ return [];
326
+ }
327
+
328
+ const consumed: PendingMeshCoordinatorEvent[] = [];
329
+ const keptLines: string[] = [];
330
+ for (const line of content.split('\n')) {
331
+ if (!line) continue;
332
+ let parsed: PendingMeshCoordinatorEvent | undefined;
333
+ try { parsed = JSON.parse(line) as PendingMeshCoordinatorEvent; } catch { parsed = undefined; }
334
+ if (parsed && predicate(parsed)) {
335
+ consumed.push(parsed);
336
+ } else {
337
+ keptLines.push(line); // non-matching or unparseable → leave queued
338
+ }
339
+ }
340
+
341
+ try {
342
+ if (keptLines.length > 0) {
343
+ writeFileSync(path, keptLines.join('\n') + '\n', 'utf-8');
344
+ }
345
+ unlinkSync(tmpPath);
346
+ } catch {
347
+ // If the rewrite/cleanup fails, restore the claimed file so no events are
348
+ // lost — the next drain retries the whole file.
349
+ try { if (existsSync(tmpPath) && !existsSync(path)) renameSync(tmpPath, path); } catch { /* best-effort */ }
350
+ return [];
351
+ }
352
+ return consumed;
353
+ }
354
+
355
+ /**
356
+ * Drain and return pending coordinator events for meshId, removing the drained
357
+ * ones from both the SQLite inbox and the JSONL legacy file.
358
+ *
359
+ * When `opts.onlyEvents` is supplied, ONLY events whose name is in that set are
360
+ * drained; every other event stays queued (undrained in SQLite, rewritten back to
361
+ * the JSONL file). The reconcile loop uses this to force-drain terminal/force-inject
362
+ * events into a *generating* coordinator while leaving non-force progress events for
363
+ * the coordinator's next idle transition. The atomic SQLite drained=1 marking and the
364
+ * atomic JSONL rename keep force-drain and a concurrent full drain from double-consuming.
365
+ */
366
+ export function drainPendingMeshCoordinatorEvents(
367
+ meshId?: string,
368
+ coordinatorDaemonId?: string | ReadonlyArray<string>,
369
+ opts?: { onlyEvents?: ReadonlySet<string> },
370
+ ): PendingMeshCoordinatorEvent[] {
371
+ if (!meshId) return [];
372
+
373
+ // A daemon may answer to more than one coordinator-id form (its canonical
374
+ // status id like `standalone_<machineId>` AND the bare machineId). Normalise
375
+ // to a list so both the SQLite IN-filter and the JSONL targeting predicate
376
+ // accept any of them.
377
+ const daemonIds = normalizeCoordinatorDaemonIds(coordinatorDaemonId);
378
+ const primaryDaemonId = daemonIds[0];
379
+
380
+ const onlyEvents = opts?.onlyEvents;
381
+ const matchesFilter = (eventName: string): boolean => !onlyEvents || onlyEvents.has(eventName);
382
+
383
+ // Dual-write means SQLite and JSONL hold the same events. Both stores must be
384
+ // emptied in one drain call — draining only one leaves the other to re-deliver
385
+ // the same events on the next call. Merge with fingerprint dedup.
386
+ const merged: PendingMeshCoordinatorEvent[] = [];
387
+ const seenFingerprints = new Set<string>();
388
+ const pushUnique = (event: PendingMeshCoordinatorEvent) => {
389
+ const fingerprint = buildPendingEventFingerprint(event);
390
+ if (fingerprint.trim()) {
391
+ if (seenFingerprints.has(fingerprint)) return;
392
+ seenFingerprints.add(fingerprint);
393
+ }
394
+ merged.push(event);
395
+ };
396
+
397
+ // G3: SQLite inbox
398
+ try {
399
+ const store = MeshRuntimeStore.getInstance();
400
+ if (store.pendingEventCount(meshId) > 0) {
401
+ for (const row of store.drainPendingEvents(meshId, daemonIds.length > 0 ? daemonIds : undefined, onlyEvents ? { onlyEvents } : undefined)) {
402
+ const event = row.payload as PendingMeshCoordinatorEvent;
403
+ if (event) pushUnique(event);
404
+ }
405
+ }
406
+ } catch {
407
+ // SQLite drain failed — JSONL below still drains
408
+ }
409
+
410
+ // JSONL (legacy / migration path) — always drained alongside SQLite.
411
+ // The scoped per-daemon file is keyed by a single id; use the primary. The
412
+ // shared (unscoped) file's targeting predicate accepts ANY of this daemon's ids.
413
+ const paths = primaryDaemonId
414
+ ? [getPendingEventsPath(meshId, primaryDaemonId), getPendingEventsPath(meshId)]
415
+ : [getPendingEventsPath(meshId)];
416
+ for (const path of paths) {
417
+ const isSharedFile = !!primaryDaemonId && path === getPendingEventsPath(meshId);
418
+ // Targeting predicate for the shared (unscoped) file: only this coordinator's
419
+ // events (or legacy untargeted ones) are eligible.
420
+ const targets = (e: PendingMeshCoordinatorEvent): boolean =>
421
+ !isSharedFile || !e.targetCoordinatorDaemonId || daemonIds.includes(e.targetCoordinatorDaemonId);
422
+
423
+ if (onlyEvents) {
424
+ // Selective JSONL drain: consume only matching events, rewrite the rest back.
425
+ for (const event of selectiveDrainFile(path, e => targets(e) && matchesFilter(e.event))) {
426
+ pushUnique(event);
427
+ }
428
+ continue;
429
+ }
430
+ const content = atomicDrainFile(path);
431
+ if (!content) continue;
432
+ const parsed = content.split('\n').filter(Boolean).flatMap(line => {
433
+ try { return [JSON.parse(line) as PendingMeshCoordinatorEvent]; } catch { return []; }
434
+ });
435
+ // If reading the shared file, filter to events that target this coordinator or are unscoped.
436
+ const filtered = isSharedFile ? parsed.filter(targets) : parsed;
437
+ for (const event of filtered) pushUnique(event);
438
+ }
439
+ if (merged.length === 0) return [];
440
+ // (Former R3 direct-delivered dedup removed.) Spontaneous PTY direct-inject no
441
+ // longer exists — delivery is now queue-drain-only (reconcile loop or MCP pull),
442
+ // so an event is consumed by exactly one drainer via the atomic SQLite drained=1
443
+ // marking. There is no PTY-vs-poll double path left to dedup against.
444
+ return reconcilePendingMeshCoordinatorEvents(meshId, merged);
445
+ }
446
+
447
+ /** Peek at pending coordinator events without draining (non-destructive). */
448
+ export function getPendingMeshCoordinatorEvents(meshId?: string, coordinatorDaemonId?: string | ReadonlyArray<string>): readonly PendingMeshCoordinatorEvent[] {
449
+ if (!meshId) return [];
450
+ const daemonIds = normalizeCoordinatorDaemonIds(coordinatorDaemonId);
451
+
452
+ // Merge SQLite (primary) + JSONL (legacy) with fingerprint dedup.
453
+ const merged: PendingMeshCoordinatorEvent[] = [];
454
+ const seenFingerprints = new Set<string>();
455
+ const pushUnique = (event: PendingMeshCoordinatorEvent) => {
456
+ const fingerprint = buildPendingEventFingerprint(event);
457
+ if (fingerprint.trim()) {
458
+ if (seenFingerprints.has(fingerprint)) return;
459
+ seenFingerprints.add(fingerprint);
460
+ }
461
+ merged.push(event);
462
+ };
463
+
464
+ // G3: SQLite inbox (non-destructive peek at undrained rows)
465
+ try {
466
+ const store = MeshRuntimeStore.getInstance();
467
+ if (store.pendingEventCount(meshId) > 0) {
468
+ for (const row of store.peekPendingEvents(meshId, daemonIds.length > 0 ? daemonIds : undefined)) {
469
+ const event = row.payload as PendingMeshCoordinatorEvent;
470
+ if (event) pushUnique(event);
471
+ }
472
+ }
473
+ } catch { /* SQLite unavailable — JSONL fallback below */ }
474
+
475
+ // JSONL (legacy)
476
+ for (const event of readPendingMeshCoordinatorEventsFromDisk(meshId, daemonIds)) {
477
+ pushUnique(event);
478
+ }
479
+
480
+ // (Former R3 direct-delivered filter removed — no PTY direct-inject path exists
481
+ // anymore, so a peeked pending event has genuinely not yet been consumed.)
482
+ return reconcilePendingMeshCoordinatorEvents(meshId, merged);
483
+ }
484
+
485
+ /**
486
+ * Test helper: purge all pending-event state for a mesh — SQLite rows
487
+ * (including drained fingerprint history) and JSONL files.
488
+ */
489
+ export function __clearMeshPendingEventsForTests(meshId: string): void {
490
+ try {
491
+ MeshRuntimeStore.getInstance().clearPendingEventsForMesh(meshId);
492
+ } catch { /* store unavailable — nothing to clear */ }
493
+ clearPendingMeshCoordinatorEvents(meshId);
494
+ }
495
+
496
+ /** Explicitly clear all pending coordinator events for a mesh (and coordinator if scoped). */
497
+ export function clearPendingMeshCoordinatorEvents(meshId?: string, coordinatorDaemonId?: string): void {
498
+ if (!meshId) return;
499
+ // Clear SQLite rows
500
+ try { MeshRuntimeStore.getInstance().clearPendingEventsForMesh(meshId); } catch { /* store unavailable */ }
501
+ // Clear JSONL files
502
+ const paths = coordinatorDaemonId
503
+ ? [getPendingEventsPath(meshId, coordinatorDaemonId), getPendingEventsPath(meshId)]
504
+ : [getPendingEventsPath(meshId)];
505
+ for (const path of paths) {
506
+ if (existsSync(path)) try { unlinkSync(path); } catch { /* already removed */ }
507
+ }
508
+ }