@adhdev/daemon-core 0.9.82-rc.42 → 0.9.82-rc.421

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 (425) hide show
  1. package/dist/boot/daemon-lifecycle.d.ts +9 -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 +68 -2
  8. package/dist/cli-adapters/cli-script-runner.d.ts +117 -0
  9. package/dist/cli-adapters/cli-state-engine.d.ts +200 -0
  10. package/dist/cli-adapters/provider-cli-adapter.d.ts +126 -64
  11. package/dist/cli-adapters/provider-cli-parse.d.ts +5 -0
  12. package/dist/cli-adapters/provider-cli-runtime.d.ts +34 -0
  13. package/dist/cli-adapters/provider-cli-shared.d.ts +74 -0
  14. package/dist/cli-adapters/pty-write-chunking.d.ts +34 -0
  15. package/dist/cli-adapters/raw-terminal-io.d.ts +37 -0
  16. package/dist/cli-adapters/resolve-executable.d.ts +69 -0
  17. package/dist/cli-adapters/terminal-backends/ghostty-vt-backend.d.ts +6 -3
  18. package/dist/cli-adapters/terminal-backends/types.d.ts +10 -2
  19. package/dist/cli-adapters/terminal-screen.d.ts +4 -11
  20. package/dist/commands/chat-commands-debug-bundle.d.ts +14 -0
  21. package/dist/commands/chat-commands-read.d.ts +7 -0
  22. package/dist/commands/chat-commands-scope.d.ts +39 -0
  23. package/dist/commands/chat-commands-shared.d.ts +33 -0
  24. package/dist/commands/chat-commands-write.d.ts +14 -0
  25. package/dist/commands/chat-commands.d.ts +10 -23
  26. package/dist/commands/cli-manager.d.ts +15 -1
  27. package/dist/commands/handler.d.ts +125 -0
  28. package/dist/commands/high-family/index.d.ts +3 -0
  29. package/dist/commands/high-family/mesh-coordinator-launch.d.ts +2 -0
  30. package/dist/commands/high-family/mesh-events.d.ts +2 -0
  31. package/dist/commands/high-family/mesh-status.d.ts +2 -0
  32. package/dist/commands/high-family/types.d.ts +60 -0
  33. package/dist/commands/low-family/coordinator-prompt.d.ts +9 -0
  34. package/dist/commands/low-family/daemon-lifecycle.d.ts +2 -0
  35. package/dist/commands/low-family/diagnostics.d.ts +2 -0
  36. package/dist/commands/low-family/index.d.ts +3 -0
  37. package/dist/commands/low-family/mesh-ledger.d.ts +10 -0
  38. package/dist/commands/low-family/mesh-node-logs.d.ts +2 -0
  39. package/dist/commands/low-family/notification.d.ts +2 -0
  40. package/dist/commands/low-family/refine-config.d.ts +2 -0
  41. package/dist/commands/low-family/session-host.d.ts +2 -0
  42. package/dist/commands/low-family/spec-providerdev.d.ts +11 -0
  43. package/dist/commands/low-family/status-meta.d.ts +2 -0
  44. package/dist/commands/low-family/types.d.ts +33 -0
  45. package/dist/commands/med-family/cli-agent.d.ts +2 -0
  46. package/dist/commands/med-family/fast-forward.d.ts +2 -0
  47. package/dist/commands/med-family/ide.d.ts +10 -0
  48. package/dist/commands/med-family/index.d.ts +3 -0
  49. package/dist/commands/med-family/mesh-crud.d.ts +29 -0
  50. package/dist/commands/med-family/mesh-host-pairing.d.ts +2 -0
  51. package/dist/commands/med-family/mesh-queue.d.ts +2 -0
  52. package/dist/commands/med-family/mesh-restart.d.ts +2 -0
  53. package/dist/commands/med-family/types.d.ts +141 -0
  54. package/dist/commands/mesh-coordinator.d.ts +85 -1
  55. package/dist/commands/router.d.ts +276 -2
  56. package/dist/commands/upgrade-helper.d.ts +41 -1
  57. package/dist/config/chat-history.d.ts +9 -0
  58. package/dist/config/config.d.ts +5 -0
  59. package/dist/config/mesh-config.d.ts +100 -1
  60. package/dist/config/mesh-json-config.d.ts +199 -0
  61. package/dist/config/repo-settings.d.ts +77 -0
  62. package/dist/daemon/dev-server.d.ts +0 -2
  63. package/dist/detection/ide-detector.d.ts +13 -0
  64. package/dist/detection/win32-ide-version.d.ts +37 -0
  65. package/dist/git/change-impact-config.d.ts +159 -0
  66. package/dist/git/git-commands.d.ts +11 -1
  67. package/dist/git/git-diff.d.ts +6 -0
  68. package/dist/git/git-executor.d.ts +11 -0
  69. package/dist/git/git-status.d.ts +57 -0
  70. package/dist/git/git-types.d.ts +2 -50
  71. package/dist/git/git-worktree.d.ts +71 -1
  72. package/dist/git/index.d.ts +3 -1
  73. package/dist/index.d.ts +63 -14
  74. package/dist/index.js +50038 -20126
  75. package/dist/index.js.map +1 -1
  76. package/dist/index.mjs +49631 -19875
  77. package/dist/index.mjs.map +1 -1
  78. package/dist/installer.d.ts +1 -4
  79. package/dist/ipc/local-ipc-server.d.ts +91 -0
  80. package/dist/launch.d.ts +1 -1
  81. package/dist/logging/async-batch-writer.d.ts +10 -0
  82. package/dist/logging/log-redactor.d.ts +24 -0
  83. package/dist/logging/log-tail-reader.d.ts +81 -0
  84. package/dist/logging/logger.d.ts +1 -1
  85. package/dist/mesh/contracts.d.ts +164 -0
  86. package/dist/mesh/coordinator-prompt.d.ts +89 -0
  87. package/dist/mesh/coordinator-registry.d.ts +59 -0
  88. package/dist/mesh/mesh-active-work.d.ts +174 -0
  89. package/dist/mesh/mesh-clone-grace.d.ts +8 -0
  90. package/dist/mesh/mesh-coordinator-config.d.ts +21 -0
  91. package/dist/mesh/mesh-delivery-policy.d.ts +131 -0
  92. package/dist/mesh/mesh-event-classify.d.ts +5 -0
  93. package/dist/mesh/mesh-event-forwarding.d.ts +41 -0
  94. package/dist/mesh/mesh-event-trace.d.ts +21 -0
  95. package/dist/mesh/mesh-events-coordinator.d.ts +4 -0
  96. package/dist/mesh/mesh-events-pending.d.ts +69 -0
  97. package/dist/mesh/mesh-events-stale.d.ts +61 -0
  98. package/dist/mesh/mesh-events-utils.d.ts +75 -0
  99. package/dist/mesh/mesh-events.d.ts +6 -49
  100. package/dist/mesh/mesh-fast-forward.d.ts +81 -0
  101. package/dist/mesh/mesh-host-ownership.d.ts +9 -0
  102. package/dist/mesh/mesh-init.d.ts +86 -0
  103. package/dist/mesh/mesh-ledger-reconciliation.d.ts +23 -4
  104. package/dist/mesh/mesh-ledger.d.ts +77 -1
  105. package/dist/mesh/mesh-magi-status.d.ts +63 -0
  106. package/dist/mesh/mesh-missions.d.ts +164 -0
  107. package/dist/mesh/mesh-node-identity.d.ts +289 -0
  108. package/dist/mesh/mesh-queue-assignment.d.ts +113 -0
  109. package/dist/mesh/mesh-reconcile-loop.d.ts +65 -0
  110. package/dist/mesh/mesh-refine-batch.d.ts +68 -0
  111. package/dist/mesh/mesh-refine-gates.d.ts +428 -0
  112. package/dist/mesh/mesh-refine-status.d.ts +62 -0
  113. package/dist/mesh/mesh-review-inbox.d.ts +90 -0
  114. package/dist/mesh/mesh-routing.d.ts +70 -0
  115. package/dist/mesh/mesh-runtime-store.d.ts +447 -0
  116. package/dist/mesh/mesh-scheduling-runtime.d.ts +78 -0
  117. package/dist/mesh/mesh-task-inflight.d.ts +46 -0
  118. package/dist/mesh/mesh-task-stats.d.ts +49 -0
  119. package/dist/mesh/mesh-unresolved-forward-outbox.d.ts +30 -0
  120. package/dist/mesh/mesh-warmup-deadline.d.ts +68 -0
  121. package/dist/mesh/mesh-work-queue.d.ts +290 -5
  122. package/dist/mesh/preview-freshness.d.ts +18 -0
  123. package/dist/mesh/refine-config.d.ts +216 -0
  124. package/dist/mesh/worktree-bootstrap-config.d.ts +145 -0
  125. package/dist/providers/acp-provider-instance.d.ts +5 -0
  126. package/dist/providers/approval-utils.d.ts +20 -0
  127. package/dist/providers/chat-message-normalization.d.ts +31 -1
  128. package/dist/providers/cli-provider-instance.d.ts +208 -3
  129. package/dist/providers/contracts.d.ts +139 -6
  130. package/dist/providers/external-sources.d.ts +71 -0
  131. package/dist/providers/manual-attendance.d.ts +63 -0
  132. package/dist/providers/native-history/antigravity-cli-transcript.d.ts +100 -0
  133. package/dist/providers/native-history/claude-cli-transcript.d.ts +70 -0
  134. package/dist/providers/native-history/codex-cli-transcript.d.ts +73 -0
  135. package/dist/providers/native-history/constants.d.ts +12 -0
  136. package/dist/providers/native-history/dispatcher.d.ts +27 -0
  137. package/dist/providers/native-history/hermes-cli-transcript.d.ts +30 -0
  138. package/dist/providers/native-history/index.d.ts +13 -0
  139. package/dist/providers/provider-instance-manager.d.ts +14 -0
  140. package/dist/providers/provider-instance.d.ts +23 -1
  141. package/dist/providers/provider-loader.d.ts +26 -4
  142. package/dist/providers/provider-trust.d.ts +31 -0
  143. package/dist/providers/read-chat-contract.d.ts +29 -0
  144. package/dist/providers/sdk/v1/builders/acp/detect-status.d.ts +68 -0
  145. package/dist/providers/sdk/v1/builders/cli/detect-status.d.ts +86 -0
  146. package/dist/providers/sdk/v1/builders/cli/parse-approval-squash.d.ts +59 -0
  147. package/dist/providers/sdk/v1/builders/cli/parse-approval.d.ts +65 -0
  148. package/dist/providers/sdk/v1/builders/cli/parse-session.d.ts +91 -0
  149. package/dist/providers/sdk/v1/builders/cli/visible-region.d.ts +42 -0
  150. package/dist/providers/sdk/v1/fixture-tooling/format.d.ts +126 -0
  151. package/dist/providers/sdk/v1/fixture-tooling/index.d.ts +8 -0
  152. package/dist/providers/sdk/v1/fixture-tooling/replay.d.ts +38 -0
  153. package/dist/providers/sdk/v1/index.d.ts +30 -0
  154. package/dist/providers/sdk/v1/sandbox/README-design.d.ts +193 -0
  155. package/dist/providers/sdk/v1/sandbox/require-whitelist.d.ts +74 -0
  156. package/dist/providers/sdk/v1/sandbox/script-runner.d.ts +98 -0
  157. package/dist/providers/sdk/v1/types/cli/index.d.ts +277 -0
  158. package/dist/providers/sdk/v1/types/common/index.d.ts +203 -0
  159. package/dist/providers/sdk/v1/validators/index.d.ts +5 -0
  160. package/dist/providers/sdk/v1/validators/manifest.d.ts +40 -0
  161. package/dist/providers/sdk/v1/validators/taint.d.ts +52 -0
  162. package/dist/providers/spec/adapter.d.ts +91 -0
  163. package/dist/providers/spec/cli-adapter.d.ts +223 -0
  164. package/dist/providers/spec/evaluator.d.ts +45 -0
  165. package/dist/providers/spec/fsm-driver.d.ts +422 -0
  166. package/dist/providers/spec/fsm-evaluator.d.ts +76 -0
  167. package/dist/providers/spec/fsm-loader.d.ts +14 -0
  168. package/dist/providers/spec/fsm-types.d.ts +174 -0
  169. package/dist/providers/spec/native-history-executor.d.ts +41 -0
  170. package/dist/providers/spec/pre-launch-trust.d.ts +16 -0
  171. package/dist/providers/spec/route.d.ts +4 -0
  172. package/dist/providers/spec/types.d.ts +222 -0
  173. package/dist/providers/status-monitor.d.ts +7 -7
  174. package/dist/providers/transcript-v2.d.ts +176 -0
  175. package/dist/providers/types/interactive-prompt.d.ts +84 -0
  176. package/dist/providers/working-dir.d.ts +17 -0
  177. package/dist/repo-mesh-types.d.ts +428 -13
  178. package/dist/runtime-defaults.d.ts +2 -0
  179. package/dist/sessions/registry.d.ts +6 -0
  180. package/dist/shared-types-extra.d.ts +2 -4
  181. package/dist/shared-types.d.ts +59 -2
  182. package/dist/status/normalize.d.ts +1 -1
  183. package/dist/status/normalize.js +1 -0
  184. package/dist/status/normalize.js.map +1 -1
  185. package/dist/status/normalize.mjs +1 -0
  186. package/dist/status/normalize.mjs.map +1 -1
  187. package/dist/status/reporter.d.ts +2 -0
  188. package/dist/status/snapshot.d.ts +26 -0
  189. package/dist/system/hash.d.ts +8 -0
  190. package/dist/system/load-better-sqlite3.d.ts +21 -0
  191. package/dist/types.d.ts +5 -0
  192. package/package.json +7 -3
  193. package/src/agent-stream/poller.ts +2 -3
  194. package/src/agent-stream/provider-adapter.ts +1 -1
  195. package/src/boot/daemon-lifecycle.ts +63 -12
  196. package/src/boot/process-hardening.ts +89 -0
  197. package/src/build-info.ts +73 -0
  198. package/src/chat/source-machine.ts +534 -0
  199. package/src/chat/source-resolver.ts +0 -0
  200. package/src/chat/subscription-updates.ts +20 -1
  201. package/src/cli-adapter-types.d.ts +3 -1
  202. package/src/cli-adapter-types.ts +68 -2
  203. package/src/cli-adapters/cli-script-runner.ts +421 -0
  204. package/src/cli-adapters/cli-state-engine.ts +1246 -0
  205. package/src/cli-adapters/provider-cli-adapter.d.ts +1 -1
  206. package/src/cli-adapters/provider-cli-adapter.ts +914 -1144
  207. package/src/cli-adapters/provider-cli-parse.d.ts +2 -0
  208. package/src/cli-adapters/provider-cli-parse.ts +19 -0
  209. package/src/cli-adapters/provider-cli-runtime.ts +56 -7
  210. package/src/cli-adapters/provider-cli-shared.d.ts +4 -0
  211. package/src/cli-adapters/provider-cli-shared.ts +121 -11
  212. package/src/cli-adapters/pty-transport.ts +2 -1
  213. package/src/cli-adapters/pty-write-chunking.ts +106 -0
  214. package/src/cli-adapters/raw-terminal-io.ts +252 -0
  215. package/src/cli-adapters/resolve-executable.ts +204 -0
  216. package/src/cli-adapters/session-host-transport.ts +2 -1
  217. package/src/cli-adapters/terminal-backends/ghostty-vt-backend.ts +63 -29
  218. package/src/cli-adapters/terminal-backends/types.ts +10 -3
  219. package/src/cli-adapters/terminal-screen.ts +16 -81
  220. package/src/commands/WINDOWS-UPGRADE-LOCK-FAILURE.md +198 -0
  221. package/src/commands/chat-commands-debug-bundle.ts +398 -0
  222. package/src/commands/chat-commands-read.ts +2327 -0
  223. package/src/commands/chat-commands-scope.ts +54 -0
  224. package/src/commands/chat-commands-shared.ts +114 -0
  225. package/src/commands/chat-commands-write.ts +891 -0
  226. package/src/commands/chat-commands.ts +19 -1841
  227. package/src/commands/cli-manager.ts +527 -26
  228. package/src/commands/handler.ts +841 -2
  229. package/src/commands/high-family/index.ts +28 -0
  230. package/src/commands/high-family/mesh-coordinator-launch.ts +678 -0
  231. package/src/commands/high-family/mesh-events.ts +80 -0
  232. package/src/commands/high-family/mesh-status.ts +678 -0
  233. package/src/commands/high-family/types.ts +76 -0
  234. package/src/commands/low-family/coordinator-prompt.ts +72 -0
  235. package/src/commands/low-family/daemon-lifecycle.ts +107 -0
  236. package/src/commands/low-family/diagnostics.ts +57 -0
  237. package/src/commands/low-family/index.ts +37 -0
  238. package/src/commands/low-family/mesh-ledger.ts +62 -0
  239. package/src/commands/low-family/mesh-node-logs.ts +87 -0
  240. package/src/commands/low-family/notification.ts +116 -0
  241. package/src/commands/low-family/refine-config.ts +106 -0
  242. package/src/commands/low-family/session-host.ts +274 -0
  243. package/src/commands/low-family/spec-providerdev.ts +217 -0
  244. package/src/commands/low-family/status-meta.ts +112 -0
  245. package/src/commands/low-family/types.ts +39 -0
  246. package/src/commands/med-family/cli-agent.ts +238 -0
  247. package/src/commands/med-family/fast-forward.ts +230 -0
  248. package/src/commands/med-family/ide.ts +163 -0
  249. package/src/commands/med-family/index.ts +37 -0
  250. package/src/commands/med-family/mesh-crud.ts +998 -0
  251. package/src/commands/med-family/mesh-host-pairing.ts +234 -0
  252. package/src/commands/med-family/mesh-queue.ts +167 -0
  253. package/src/commands/med-family/mesh-restart.ts +92 -0
  254. package/src/commands/med-family/types.ts +142 -0
  255. package/src/commands/mesh-coordinator.ts +334 -124
  256. package/src/commands/router.ts +2669 -3246
  257. package/src/commands/stream-commands.ts +8 -0
  258. package/src/commands/upgrade-helper.ts +310 -45
  259. package/src/config/chat-history.ts +483 -24
  260. package/src/config/config.ts +12 -0
  261. package/src/config/mesh-config.ts +469 -22
  262. package/src/config/mesh-json-config.ts +376 -0
  263. package/src/config/recent-activity.ts +8 -2
  264. package/src/config/repo-settings.ts +111 -0
  265. package/src/daemon/dev-auto-implement.ts +3 -2
  266. package/src/daemon/dev-cli-debug.ts +10 -1
  267. package/src/daemon/dev-server.ts +0 -541
  268. package/src/detection/cli-detector.ts +28 -9
  269. package/src/detection/ide-detector.ts +55 -16
  270. package/src/detection/win32-ide-version.ts +106 -0
  271. package/src/git/change-impact-config.ts +354 -0
  272. package/src/git/git-commands.ts +59 -15
  273. package/src/git/git-diff.ts +81 -11
  274. package/src/git/git-executor.ts +12 -0
  275. package/src/git/git-status.ts +767 -48
  276. package/src/git/git-types.ts +14 -62
  277. package/src/git/git-worktree.ts +261 -4
  278. package/src/git/index.ts +17 -0
  279. package/src/index.ts +188 -12
  280. package/src/installer.d.ts +1 -1
  281. package/src/installer.ts +8 -6
  282. package/src/ipc/local-ipc-server.ts +278 -0
  283. package/src/launch.d.ts +1 -1
  284. package/src/launch.ts +37 -28
  285. package/src/logging/async-batch-writer.ts +55 -0
  286. package/src/logging/command-log.ts +7 -5
  287. package/src/logging/log-redactor.ts +100 -0
  288. package/src/logging/log-tail-reader.ts +341 -0
  289. package/src/logging/logger.ts +14 -7
  290. package/src/mesh/contracts.ts +338 -0
  291. package/src/mesh/coordinator-prompt.ts +381 -33
  292. package/src/mesh/coordinator-registry.ts +121 -0
  293. package/src/mesh/mesh-active-work.ts +645 -0
  294. package/src/mesh/mesh-clone-grace.ts +68 -0
  295. package/src/mesh/mesh-coordinator-config.ts +97 -0
  296. package/src/mesh/mesh-delivery-policy.ts +315 -0
  297. package/src/mesh/mesh-event-classify.ts +51 -0
  298. package/src/mesh/mesh-event-forwarding.ts +1883 -0
  299. package/src/mesh/mesh-event-trace.ts +67 -0
  300. package/src/mesh/mesh-events-coordinator.ts +32 -0
  301. package/src/mesh/mesh-events-pending.ts +656 -0
  302. package/src/mesh/mesh-events-stale.ts +389 -0
  303. package/src/mesh/mesh-events-utils.ts +443 -0
  304. package/src/mesh/mesh-events.ts +33 -1035
  305. package/src/mesh/mesh-fast-forward.ts +843 -0
  306. package/src/mesh/mesh-host-ownership.ts +73 -0
  307. package/src/mesh/mesh-init.ts +260 -0
  308. package/src/mesh/mesh-ledger-reconciliation.ts +12 -7
  309. package/src/mesh/mesh-ledger.ts +613 -102
  310. package/src/mesh/mesh-magi-status.ts +223 -0
  311. package/src/mesh/mesh-missions.ts +407 -0
  312. package/src/mesh/mesh-node-identity.ts +1887 -0
  313. package/src/mesh/mesh-queue-assignment.ts +1863 -0
  314. package/src/mesh/mesh-reconcile-loop.ts +1734 -0
  315. package/src/mesh/mesh-refine-batch.ts +205 -0
  316. package/src/mesh/mesh-refine-gates.ts +1673 -0
  317. package/src/mesh/mesh-refine-status.ts +231 -0
  318. package/src/mesh/mesh-review-inbox.ts +307 -0
  319. package/src/mesh/mesh-routing.ts +291 -0
  320. package/src/mesh/mesh-runtime-store.ts +1836 -0
  321. package/src/mesh/mesh-scheduling-runtime.ts +198 -0
  322. package/src/mesh/mesh-task-inflight.ts +70 -0
  323. package/src/mesh/mesh-task-stats.ts +161 -0
  324. package/src/mesh/mesh-unresolved-forward-outbox.ts +185 -0
  325. package/src/mesh/mesh-warmup-deadline.ts +152 -0
  326. package/src/mesh/mesh-work-queue.ts +1243 -141
  327. package/src/mesh/preview-freshness.ts +118 -0
  328. package/src/mesh/refine-config.ts +423 -0
  329. package/src/mesh/worktree-bootstrap-config.ts +450 -0
  330. package/src/providers/acp-provider-instance.ts +43 -10
  331. package/src/providers/approval-utils.d.ts +5 -0
  332. package/src/providers/approval-utils.ts +57 -5
  333. package/src/providers/chat-message-normalization.ts +92 -4
  334. package/src/providers/cli-provider-instance.ts +1898 -104
  335. package/src/providers/contracts.d.ts +55 -0
  336. package/src/providers/contracts.ts +150 -6
  337. package/src/providers/extension-provider-instance.ts +12 -7
  338. package/src/providers/external-sources.ts +218 -0
  339. package/src/providers/ide-provider-instance.ts +35 -12
  340. package/src/providers/manual-attendance.ts +85 -0
  341. package/src/providers/native-history/antigravity-cli-transcript.ts +643 -0
  342. package/src/providers/native-history/claude-cli-transcript.ts +396 -0
  343. package/src/providers/native-history/codex-cli-transcript.ts +479 -0
  344. package/src/providers/native-history/constants.ts +19 -0
  345. package/src/providers/native-history/dispatcher.ts +339 -0
  346. package/src/providers/native-history/hermes-cli-transcript.ts +230 -0
  347. package/src/providers/native-history/index.ts +30 -0
  348. package/src/providers/provider-instance-manager.ts +30 -0
  349. package/src/providers/provider-instance.ts +19 -1
  350. package/src/providers/provider-loader.ts +668 -50
  351. package/src/providers/provider-schema.ts +87 -14
  352. package/src/providers/provider-trust.ts +114 -0
  353. package/src/providers/read-chat-contract.ts +76 -16
  354. package/src/providers/sdk/README.md +49 -0
  355. package/src/providers/sdk/v1/builders/acp/detect-status.ts +144 -0
  356. package/src/providers/sdk/v1/builders/cli/detect-status.ts +313 -0
  357. package/src/providers/sdk/v1/builders/cli/parse-approval-squash.ts +158 -0
  358. package/src/providers/sdk/v1/builders/cli/parse-approval.ts +250 -0
  359. package/src/providers/sdk/v1/builders/cli/parse-session.ts +276 -0
  360. package/src/providers/sdk/v1/builders/cli/visible-region.ts +143 -0
  361. package/src/providers/sdk/v1/fixture-tooling/format.ts +130 -0
  362. package/src/providers/sdk/v1/fixture-tooling/index.ts +22 -0
  363. package/src/providers/sdk/v1/fixture-tooling/replay.ts +352 -0
  364. package/src/providers/sdk/v1/index.ts +152 -0
  365. package/src/providers/sdk/v1/sandbox/README-design.ts +195 -0
  366. package/src/providers/sdk/v1/sandbox/require-whitelist.ts +472 -0
  367. package/src/providers/sdk/v1/sandbox/script-runner.ts +150 -0
  368. package/src/providers/sdk/v1/schemas/cli/provider.schema.json +504 -0
  369. package/src/providers/sdk/v1/schemas/primitives/acp-session-protocol-v1.json +131 -0
  370. package/src/providers/sdk/v1/schemas/primitives/native-history-codex-rollout-v1.json +66 -0
  371. package/src/providers/sdk/v1/schemas/primitives/tui-approval-squash-v1.json +91 -0
  372. package/src/providers/sdk/v1/schemas/primitives/tui-assistant-block-v1.json +91 -0
  373. package/src/providers/sdk/v1/schemas/primitives/tui-cue-ordering-v1.json +47 -0
  374. package/src/providers/sdk/v1/schemas/primitives/tui-dispatch-order-v1.json +32 -0
  375. package/src/providers/sdk/v1/schemas/primitives/tui-footer-chrome-v1.json +42 -0
  376. package/src/providers/sdk/v1/schemas/primitives/tui-index-finder-v1.json +27 -0
  377. package/src/providers/sdk/v1/schemas/primitives/tui-modal-v1.json +125 -0
  378. package/src/providers/sdk/v1/schemas/primitives/tui-prompt-marker-v1.json +45 -0
  379. package/src/providers/sdk/v1/schemas/primitives/tui-session-id-extraction-v1.json +46 -0
  380. package/src/providers/sdk/v1/schemas/primitives/tui-settled-prompt-v1.json +71 -0
  381. package/src/providers/sdk/v1/schemas/primitives/tui-spinner-v1.json +83 -0
  382. package/src/providers/sdk/v1/schemas/primitives/tui-transcript-pty-v1.json +83 -0
  383. package/src/providers/sdk/v1/schemas/primitives/tui-visible-region-v1.json +57 -0
  384. package/src/providers/sdk/v1/schemas/primitives/tui-welcome-screen-v1.json +35 -0
  385. package/src/providers/sdk/v1/types/cli/index.ts +365 -0
  386. package/src/providers/sdk/v1/types/common/index.ts +229 -0
  387. package/src/providers/sdk/v1/validators/index.ts +19 -0
  388. package/src/providers/sdk/v1/validators/manifest.ts +110 -0
  389. package/src/providers/sdk/v1/validators/taint.ts +309 -0
  390. package/src/providers/spec/adapter.ts +235 -0
  391. package/src/providers/spec/cli-adapter.ts +1056 -0
  392. package/src/providers/spec/evaluator.ts +407 -0
  393. package/src/providers/spec/fsm-driver.ts +1410 -0
  394. package/src/providers/spec/fsm-evaluator.ts +272 -0
  395. package/src/providers/spec/fsm-loader.ts +120 -0
  396. package/src/providers/spec/fsm-types.ts +273 -0
  397. package/src/providers/spec/native-history-executor.ts +1128 -0
  398. package/src/providers/spec/pre-launch-trust.ts +104 -0
  399. package/src/providers/spec/route.ts +51 -0
  400. package/src/providers/spec/types.ts +262 -0
  401. package/src/providers/status-monitor.d.ts +7 -7
  402. package/src/providers/status-monitor.ts +37 -22
  403. package/src/providers/transcript-v2.ts +567 -0
  404. package/src/providers/types/interactive-prompt.ts +536 -0
  405. package/src/providers/version-archive.ts +64 -24
  406. package/src/providers/working-dir.ts +23 -0
  407. package/src/repo-mesh-types.ts +639 -14
  408. package/src/runtime-defaults.ts +39 -0
  409. package/src/sessions/registry.ts +6 -0
  410. package/src/shared-types-extra.ts +2 -4
  411. package/src/shared-types.d.ts +8 -0
  412. package/src/shared-types.ts +64 -1
  413. package/src/status/builders.ts +26 -6
  414. package/src/status/normalize.ts +2 -0
  415. package/src/status/reporter.ts +19 -1
  416. package/src/status/snapshot.ts +95 -26
  417. package/src/system/hash.ts +23 -0
  418. package/src/system/host-memory.ts +29 -12
  419. package/src/system/load-better-sqlite3.ts +68 -0
  420. package/src/types.ts +5 -0
  421. package/dist/cli-adapters/terminal-backends/xterm-backend.d.ts +0 -17
  422. package/dist/mesh/mesh-sync.d.ts +0 -53
  423. package/src/cli-adapters/terminal-backends/ghostty-vt-backend.d.ts +0 -16
  424. package/src/cli-adapters/terminal-backends/xterm-backend.ts +0 -97
  425. package/src/mesh/mesh-sync.ts +0 -111
@@ -13,11 +13,12 @@
13
13
  * Safety: mode 0o600, atomic append via appendFileSync
14
14
  */
15
15
 
16
- import { existsSync, mkdirSync, readFileSync, appendFileSync, statSync, renameSync } from 'fs';
16
+ import { appendFileSync, existsSync, mkdirSync, readFileSync, statSync, renameSync, writeFileSync } from 'fs';
17
17
  import { join } from 'path';
18
18
  import { randomUUID } from 'crypto';
19
19
  import { getConfigDir } from '../config/config.js';
20
20
  import { EventEmitter } from 'events';
21
+ import { MeshRuntimeStore } from './mesh-runtime-store.js';
21
22
  // ─── Types ──────────────────────────────────────
22
23
 
23
24
  export type MeshLedgerKind =
@@ -26,16 +27,45 @@ export type MeshLedgerKind =
26
27
  | 'task_failed'
27
28
  | 'task_stalled'
28
29
  | 'task_approval_needed'
30
+ | 'p2p_dispatch_failed'
29
31
  | 'session_launched'
30
32
  | 'session_auto_launch'
31
33
  | 'session_stopped'
32
34
  | 'checkpoint_created'
33
35
  | 'node_cloned'
36
+ | 'node_joined'
34
37
  | 'node_removed'
35
38
  | 'coordinator_started'
36
39
  | 'recovery_attempted'
37
40
  | 'ledger_replicated'
38
41
  | 'ledger_reconciled'
42
+ | 'direct_fast_forward'
43
+ | 'delivery_unroutable'
44
+ | 'direct_dispatch_pruned'
45
+ | 'event_held'
46
+ | 'task_reclaimed'
47
+ // Gap2-A: a coordinator-recorded operating note — a runtime-accumulated
48
+ // lesson (provider quirk, pattern to avoid, recovery lesson) persisted in
49
+ // the ledger so it survives coordinator restarts and is provider-neutral.
50
+ // payload: { text, category?, createdAt?, sourceCoordinator? }
51
+ | 'coordinator_operating_note'
52
+ // Mission audit trail: mission record mutations (mesh_mission_upsert) so the
53
+ // ledger captures mission lifecycle, not just task events. Without these a
54
+ // mission create / goal rewrite / status transition left no ledger trace,
55
+ // breaking audit continuity and post-restart recovery.
56
+ // mission_created payload: { missionId, title, goalSummary, goalLength, goalTruncated, status }
57
+ // mission_status_changed payload: { missionId, title, fromStatus, toStatus }
58
+ // mission_goal_updated payload: { missionId, title, prevGoalSummary, nextGoalSummary, prevGoalLength, nextGoalLength, goalTruncated }
59
+ | 'mission_created'
60
+ | 'mission_status_changed'
61
+ | 'mission_goal_updated'
62
+ // MAGI (Multi-Agent Ground-truth Insight) cross-verification activity. Persisted
63
+ // so a wait=false fan-out and its later synthesis survive coordinator restarts and
64
+ // are foldable into mesh_status (keyed by consensusGroupId).
65
+ // magi_dispatched payload: { source:'magi', consensusGroupId, missionId?, panel?, question?, replicaCount }
66
+ // magi_synthesis payload: { source:'magi', consensusGroupId, missionId?, panel?, question?, synthesis }
67
+ | 'magi_dispatched'
68
+ | 'magi_synthesis'
39
69
  ;
40
70
 
41
71
  export interface MeshLedgerEntry {
@@ -61,6 +91,44 @@ export function isIntentionalCleanupStopEntry(entry: Pick<MeshLedgerEntry, 'kind
61
91
  || payload.source === 'mesh_remove_node');
62
92
  }
63
93
 
94
+ export type MeshWorkerResultStatus = 'completed' | 'failed' | 'blocked' | 'partial' | 'unknown';
95
+ export type MeshProcessArtifactKind = 'process' | 'log' | 'port' | 'window' | 'session' | 'file' | 'url' | 'other';
96
+
97
+ export interface MeshValidationResultArtifact {
98
+ command?: string;
99
+ status: 'passed' | 'failed' | 'skipped' | 'unknown';
100
+ durationMs?: number;
101
+ outputPath?: string;
102
+ summary?: string;
103
+ }
104
+
105
+ export interface MeshProcessArtifact {
106
+ kind: MeshProcessArtifactKind;
107
+ id?: string;
108
+ label?: string;
109
+ locator?: string;
110
+ pid?: number;
111
+ port?: number;
112
+ url?: string;
113
+ path?: string;
114
+ sessionId?: string;
115
+ keepRunning?: boolean;
116
+ metadata?: Record<string, unknown>;
117
+ }
118
+
119
+ export interface MeshWorkerResultArtifact {
120
+ status: MeshWorkerResultStatus;
121
+ classification?: string;
122
+ changedFiles: string[];
123
+ validationResults: MeshValidationResultArtifact[];
124
+ gitStatus?: Record<string, unknown>;
125
+ processArtifacts: MeshProcessArtifact[];
126
+ errors: string[];
127
+ nextAction?: string;
128
+ requiresUserAction: boolean;
129
+ source: 'explicit_metadata' | 'final_summary_json' | 'default';
130
+ }
131
+
64
132
  export interface MeshTaskCompletionEvidence {
65
133
  source: 'agent_status_event';
66
134
  event: 'agent:generating_completed' | 'agent:ready';
@@ -74,6 +142,7 @@ export interface MeshTaskCompletionEvidence {
74
142
  providerSessionId?: string;
75
143
  finalSummaryAvailable: boolean;
76
144
  };
145
+ workerResult: MeshWorkerResultArtifact;
77
146
  git: {
78
147
  status: 'deferred';
79
148
  reason: string;
@@ -96,6 +165,7 @@ export interface BuildTaskCompletionEvidenceOptions {
96
165
  providerType?: string;
97
166
  providerSessionId?: string;
98
167
  finalSummary?: string;
168
+ workerResult?: Record<string, unknown>;
99
169
  completedAt?: string;
100
170
  }
101
171
 
@@ -160,8 +230,19 @@ export interface AppendRemoteLedgerResult {
160
230
  // ─── Constants ──────────────────────────────────
161
231
 
162
232
  const LEDGER_DIR_NAME = 'mesh-ledger';
163
- const MAX_FILE_SIZE_BYTES = 10 * 1024 * 1024; // 10 MB
233
+ const MAX_FILE_SIZE_BYTES = 10 * 1024 * 1024; // 10 MB — full rotation threshold
234
+ const COMPACT_THRESHOLD_BYTES = 2 * 1024 * 1024; // 2 MB — compaction threshold
235
+ const ARCHIVE_TERMINAL_OLDER_THAN_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
164
236
  const RECENT_FAILURE_WINDOW_MS = 30 * 60 * 1000; // 30 minutes
237
+
238
+ // Kinds that accumulate indefinitely and are safe to archive after ARCHIVE_TERMINAL_OLDER_THAN_MS.
239
+ // Non-terminal kinds (dispatched, sessions, nodes, checkpoints) are always kept in the active file.
240
+ const ARCHIVABLE_KINDS: ReadonlySet<MeshLedgerKind> = new Set([
241
+ 'task_completed',
242
+ 'task_failed',
243
+ 'task_stalled',
244
+ 'recovery_attempted',
245
+ ] as MeshLedgerKind[]);
165
246
  const DEFAULT_LEDGER_SLICE_LIMIT = 100;
166
247
  export const MAX_LEDGER_SLICE_LIMIT = 500;
167
248
 
@@ -186,8 +267,258 @@ function getRotatedPath(meshId: string, index: number): string {
186
267
  return join(getLedgerDir(), `${safe}.${index}.jsonl`);
187
268
  }
188
269
 
270
+ function getArchivePath(meshId: string): string {
271
+ const safe = meshId.replace(/[^a-zA-Z0-9_-]/g, '_');
272
+ return join(getLedgerDir(), `${safe}.archive.jsonl`);
273
+ }
274
+
275
+ function getRotatedArchivePath(meshId: string, index: number): string {
276
+ const safe = meshId.replace(/[^a-zA-Z0-9_-]/g, '_');
277
+ return join(getLedgerDir(), `${safe}.archive.${index}.jsonl`);
278
+ }
279
+
280
+ function getArchivedCountsPath(meshId: string): string {
281
+ const safe = meshId.replace(/[^a-zA-Z0-9_-]/g, '_');
282
+ return join(getLedgerDir(), `${safe}.archived-counts.json`);
283
+ }
284
+
285
+ function rotateArchiveFile(meshId: string, archivePath: string): void {
286
+ let index = 1;
287
+ while (existsSync(getRotatedArchivePath(meshId, index))) {
288
+ index++;
289
+ if (index > 5) break;
290
+ }
291
+ if (index > 5) index = 5;
292
+ try {
293
+ renameSync(archivePath, getRotatedArchivePath(meshId, index));
294
+ } catch (e: any) {
295
+ process.stderr.write(`[adhdev-mesh] Archive rotation failed for mesh ${meshId}: ${e?.message || e}\n`);
296
+ }
297
+ }
298
+
299
+ interface LedgerArchivedCounts {
300
+ taskCompleted: number;
301
+ taskFailed: number;
302
+ taskStalled: number;
303
+ recoveryAttempted: number;
304
+ totalArchived: number;
305
+ lastArchivedAt: string;
306
+ }
307
+
308
+ function readArchivedCounts(meshId: string): LedgerArchivedCounts {
309
+ const path = getArchivedCountsPath(meshId);
310
+ if (!existsSync(path)) return { taskCompleted: 0, taskFailed: 0, taskStalled: 0, recoveryAttempted: 0, totalArchived: 0, lastArchivedAt: '' };
311
+ try { return JSON.parse(readFileSync(path, 'utf-8')) as LedgerArchivedCounts; } catch { return { taskCompleted: 0, taskFailed: 0, taskStalled: 0, recoveryAttempted: 0, totalArchived: 0, lastArchivedAt: '' }; }
312
+ }
313
+
314
+ function updateArchivedCounts(meshId: string, archived: MeshLedgerEntry[]): void {
315
+ const counts = readArchivedCounts(meshId);
316
+ for (const e of archived) {
317
+ if (e.kind === 'task_completed') counts.taskCompleted++;
318
+ else if (e.kind === 'task_failed') counts.taskFailed++;
319
+ else if (e.kind === 'task_stalled') counts.taskStalled++;
320
+ else if (e.kind === 'recovery_attempted') counts.recoveryAttempted++;
321
+ }
322
+ counts.totalArchived += archived.length;
323
+ counts.lastArchivedAt = new Date().toISOString();
324
+ try { writeFileSync(getArchivedCountsPath(meshId), JSON.stringify(counts), { encoding: 'utf-8', mode: 0o600 }); } catch { /* best-effort */ }
325
+ }
326
+
327
+ // ─── Worker Result Footer ───────────────────────
328
+
329
+ /**
330
+ * Footer to append to worker task messages so workers output structured results
331
+ * that the daemon parses via extractJsonObjectFromSummary / normalizeMeshWorkerResult.
332
+ *
333
+ * Usage: append buildWorkerTaskFooter() to the task message in mesh_send_task /
334
+ * mesh_enqueue_task. The coordinator prompt rules instruct coordinators to do this.
335
+ */
336
+ export function buildWorkerTaskFooter(): string {
337
+ return `
338
+
339
+ ---
340
+ When your task is done, end your final response with a JSON code block in this exact format (omit fields that don't apply):
341
+ \`\`\`json
342
+ {
343
+ "status": "completed",
344
+ "changedFiles": ["src/foo.ts", "tests/foo.test.ts"],
345
+ "gitStatus": { "branch": "feat/your-branch", "committed": true, "pushed": false },
346
+ "validationResults": [{ "command": "npm test", "status": "passed" }],
347
+ "errors": [],
348
+ "nextAction": "optional guidance for the coordinator"
349
+ }
350
+ \`\`\`
351
+ Valid status values: \`completed\` | \`failed\` | \`blocked\` | \`partial\`.`;
352
+ }
353
+
354
+ // ─── Ledger Compaction ──────────────────────────
355
+
356
+ /**
357
+ * Compact the active ledger file for a mesh by moving old terminal entries
358
+ * (task_completed, task_failed, task_stalled, recovery_attempted older than 7 days)
359
+ * to <meshId>.archive.jsonl, keeping the active file lean.
360
+ *
361
+ * Non-terminal entries (dispatch, sessions, node lifecycle) are always retained.
362
+ * Called automatically from appendLedgerEntry when the file exceeds COMPACT_THRESHOLD_BYTES.
363
+ */
364
+ export function compactLedger(meshId: string): { archivedCount: number; retainedCount: number } {
365
+ const filePath = getLedgerPath(meshId);
366
+ if (!existsSync(filePath)) return { archivedCount: 0, retainedCount: 0 };
367
+
368
+ const cutoff = Date.now() - ARCHIVE_TERMINAL_OLDER_THAN_MS;
369
+ const entries = readLedgerEntries(meshId);
370
+
371
+ const keep: MeshLedgerEntry[] = [];
372
+ const archive: MeshLedgerEntry[] = [];
373
+ for (const entry of entries) {
374
+ if (ARCHIVABLE_KINDS.has(entry.kind) && new Date(entry.timestamp).getTime() < cutoff) {
375
+ archive.push(entry);
376
+ } else {
377
+ keep.push(entry);
378
+ }
379
+ }
380
+
381
+ if (archive.length === 0) return { archivedCount: 0, retainedCount: keep.length };
382
+
383
+ // Append archived entries to the archive file, rotate if it exceeds 50MB
384
+ const archivePath = getArchivePath(meshId);
385
+ try {
386
+ if (existsSync(archivePath) && statSync(archivePath).size > 50 * 1024 * 1024) {
387
+ rotateArchiveFile(meshId, archivePath);
388
+ }
389
+ const archiveLines = archive.map(e => JSON.stringify(e)).join('\n') + '\n';
390
+ appendFileSync(archivePath, archiveLines, { encoding: 'utf-8', mode: 0o600 });
391
+ updateArchivedCounts(meshId, archive);
392
+ } catch (e: any) {
393
+ process.stderr.write(`[adhdev-mesh] Ledger archive write failed for mesh ${meshId}: ${e?.message || e}\n`);
394
+ return { archivedCount: 0, retainedCount: entries.length };
395
+ }
396
+
397
+ // Rewrite active file with retained entries only
398
+ try {
399
+ const keepLines = keep.length ? keep.map(e => JSON.stringify(e)).join('\n') + '\n' : '';
400
+ writeFileSync(filePath, keepLines, { encoding: 'utf-8', mode: 0o600 });
401
+ invalidateLedgerCache(meshId);
402
+ } catch (e: any) {
403
+ process.stderr.write(`[adhdev-mesh] Ledger compaction rewrite failed for mesh ${meshId}: ${e?.message || e}\n`);
404
+ return { archivedCount: archive.length, retainedCount: keep.length };
405
+ }
406
+
407
+ // G2: mirror the compaction in SQLite so the runtime store matches the active
408
+ // ledger set. Archived entries live on in the JSONL archive files (export/debug).
409
+ try {
410
+ MeshRuntimeStore.getInstance().deleteLedgerEntries(meshId, archive.map(e => e.id));
411
+ invalidateLedgerCache(meshId);
412
+ } catch { /* best-effort; summary counts absorb the difference via archived counts */ }
413
+
414
+ return { archivedCount: archive.length, retainedCount: keep.length };
415
+ }
416
+
189
417
  // ─── Core API ───────────────────────────────────
190
418
 
419
+ function readNonEmptyString(value: unknown): string | undefined {
420
+ return typeof value === 'string' && value.trim() ? value.trim() : undefined;
421
+ }
422
+
423
+ function readStringArray(value: unknown): string[] {
424
+ if (!Array.isArray(value)) return [];
425
+ return value.map(item => readNonEmptyString(item)).filter(Boolean) as string[];
426
+ }
427
+
428
+ function extractJsonObjectFromSummary(summary?: string): Record<string, unknown> | undefined {
429
+ const text = readNonEmptyString(summary);
430
+ if (!text) return undefined;
431
+ const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/i);
432
+ const candidates = [fenced?.[1], text].filter(Boolean) as string[];
433
+ for (const candidate of candidates) {
434
+ const trimmed = candidate.trim();
435
+ if (!trimmed.startsWith('{') || !trimmed.endsWith('}')) continue;
436
+ try {
437
+ const parsed = JSON.parse(trimmed);
438
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
439
+ // Require at least one mesh worker result field to avoid false positives
440
+ // (e.g. JSON from tool call outputs or log lines in the final summary).
441
+ const hasWorkerShape = 'status' in parsed && (
442
+ 'changedFiles' in parsed || 'errors' in parsed
443
+ || 'gitStatus' in parsed || 'nextAction' in parsed
444
+ || 'validationResults' in parsed
445
+ );
446
+ if (hasWorkerShape) return parsed;
447
+ }
448
+ } catch { /* try next candidate */ }
449
+ }
450
+ return undefined;
451
+ }
452
+
453
+ function normalizeValidationResults(value: unknown): MeshValidationResultArtifact[] {
454
+ if (!Array.isArray(value)) return [];
455
+ return value
456
+ .filter(item => item && typeof item === 'object' && !Array.isArray(item))
457
+ .map((item: any) => {
458
+ const status = ['passed', 'failed', 'skipped', 'unknown'].includes(item.status) ? item.status : 'unknown';
459
+ return {
460
+ ...(readNonEmptyString(item.command) ? { command: readNonEmptyString(item.command) } : {}),
461
+ status,
462
+ ...(Number.isFinite(Number(item.durationMs)) ? { durationMs: Number(item.durationMs) } : {}),
463
+ ...(readNonEmptyString(item.outputPath) ? { outputPath: readNonEmptyString(item.outputPath) } : {}),
464
+ ...(readNonEmptyString(item.summary) ? { summary: readNonEmptyString(item.summary) } : {}),
465
+ };
466
+ });
467
+ }
468
+
469
+ function normalizeProcessArtifacts(value: unknown): MeshProcessArtifact[] {
470
+ if (!Array.isArray(value)) return [];
471
+ const kinds = new Set(['process', 'log', 'port', 'window', 'session', 'file', 'url', 'other']);
472
+ return value
473
+ .filter(item => item && typeof item === 'object' && !Array.isArray(item))
474
+ .map((item: any) => ({
475
+ kind: kinds.has(item.kind) ? item.kind : 'other',
476
+ ...(readNonEmptyString(item.id) ? { id: readNonEmptyString(item.id) } : {}),
477
+ ...(readNonEmptyString(item.label) ? { label: readNonEmptyString(item.label) } : {}),
478
+ ...(readNonEmptyString(item.locator) ? { locator: readNonEmptyString(item.locator) } : {}),
479
+ ...(Number.isFinite(Number(item.pid)) ? { pid: Number(item.pid) } : {}),
480
+ ...(Number.isFinite(Number(item.port)) ? { port: Number(item.port) } : {}),
481
+ ...(readNonEmptyString(item.url) ? { url: readNonEmptyString(item.url) } : {}),
482
+ ...(readNonEmptyString(item.path) ? { path: readNonEmptyString(item.path) } : {}),
483
+ ...(readNonEmptyString(item.sessionId) ? { sessionId: readNonEmptyString(item.sessionId) } : {}),
484
+ ...(typeof item.keepRunning === 'boolean' ? { keepRunning: item.keepRunning } : {}),
485
+ ...(item.metadata && typeof item.metadata === 'object' && !Array.isArray(item.metadata) ? { metadata: item.metadata as Record<string, unknown> } : {}),
486
+ }));
487
+ }
488
+
489
+ export function normalizeMeshWorkerResult(input?: Record<string, unknown>, source: MeshWorkerResultArtifact['source'] = 'explicit_metadata'): MeshWorkerResultArtifact {
490
+ const raw = input && typeof input === 'object' ? input : {};
491
+ const status = ['completed', 'failed', 'blocked', 'partial', 'unknown'].includes(String(raw.status))
492
+ ? raw.status as MeshWorkerResultStatus
493
+ : 'unknown';
494
+ const gitStatus = raw.gitStatus && typeof raw.gitStatus === 'object' && !Array.isArray(raw.gitStatus)
495
+ ? raw.gitStatus as Record<string, unknown>
496
+ : undefined;
497
+ return {
498
+ status,
499
+ ...(readNonEmptyString(raw.classification) ? { classification: readNonEmptyString(raw.classification) } : {}),
500
+ changedFiles: readStringArray(raw.changedFiles),
501
+ validationResults: normalizeValidationResults(raw.validationResults),
502
+ ...(gitStatus ? { gitStatus } : {}),
503
+ processArtifacts: normalizeProcessArtifacts(raw.processArtifacts),
504
+ errors: readStringArray(raw.errors),
505
+ ...(readNonEmptyString(raw.nextAction) ? { nextAction: readNonEmptyString(raw.nextAction) } : {}),
506
+ requiresUserAction: raw.requiresUserAction === true,
507
+ source,
508
+ };
509
+ }
510
+
511
+ function resolveWorkerResult(opts: BuildTaskCompletionEvidenceOptions): MeshWorkerResultArtifact {
512
+ if (opts.workerResult && typeof opts.workerResult === 'object') {
513
+ return normalizeMeshWorkerResult(opts.workerResult, 'explicit_metadata');
514
+ }
515
+ const parsed = extractJsonObjectFromSummary(opts.finalSummary);
516
+ if (parsed) {
517
+ return normalizeMeshWorkerResult(parsed, 'final_summary_json');
518
+ }
519
+ return normalizeMeshWorkerResult(undefined, 'default');
520
+ }
521
+
191
522
  export function buildTaskCompletionEvidence(opts: BuildTaskCompletionEvidenceOptions): MeshTaskCompletionEvidence {
192
523
  const providerSessionId = opts.providerSessionId?.trim() || undefined;
193
524
  const providerType = opts.providerType?.trim() || undefined;
@@ -204,6 +535,7 @@ export function buildTaskCompletionEvidence(opts: BuildTaskCompletionEvidenceOpt
204
535
  providerSessionId,
205
536
  finalSummaryAvailable: typeof opts.finalSummary === 'string' && opts.finalSummary.trim().length > 0,
206
537
  },
538
+ workerResult: resolveWorkerResult(opts),
207
539
  git: {
208
540
  status: 'deferred',
209
541
  reason: 'ordinary_completion_git_status_not_checked',
@@ -239,21 +571,44 @@ export function appendLedgerEntry(
239
571
 
240
572
  const filePath = getLedgerPath(meshId);
241
573
 
242
- // Rotate if file exceeds max size
574
+ // Compact or rotate based on file size
243
575
  if (existsSync(filePath)) {
244
576
  try {
245
577
  const stat = statSync(filePath);
246
578
  if (stat.size >= MAX_FILE_SIZE_BYTES) {
247
579
  rotateLedgerFile(meshId, filePath);
580
+ } else if (stat.size >= COMPACT_THRESHOLD_BYTES) {
581
+ compactLedger(meshId);
248
582
  }
249
583
  } catch {
250
584
  // stat failed — proceed with append anyway
251
585
  }
252
586
  }
253
587
 
588
+ // Write to SQLite (G2: primary runtime read/write path)
589
+ try {
590
+ MeshRuntimeStore.getInstance().appendLedgerEntry({
591
+ id: entry.id,
592
+ meshId: entry.meshId,
593
+ timestamp: entry.timestamp,
594
+ kind: entry.kind,
595
+ nodeId: entry.nodeId ?? null,
596
+ sessionId: entry.sessionId ?? null,
597
+ providerType: entry.providerType ?? null,
598
+ payload: entry.payload,
599
+ });
600
+ } catch {
601
+ // SQLite write failed but the JSONL append below still records the entry.
602
+ // Reset the one-time import flag so the next read re-imports from JSONL
603
+ // and the store self-heals instead of silently missing this entry.
604
+ ledgerImportDone.delete(meshId);
605
+ }
606
+
607
+ // Also write to JSONL (retained as export/import/debug/legacy artifact)
254
608
  try {
255
609
  const line = JSON.stringify(entry) + '\n';
256
610
  appendFileSync(filePath, line, { encoding: 'utf-8', mode: 0o600 });
611
+ invalidateLedgerCache(meshId);
257
612
  meshLedgerEvents.emit('append', meshId, entry);
258
613
  return entry;
259
614
  } catch (e: any) {
@@ -285,8 +640,9 @@ export function appendRemoteLedgerEntries(meshId: string, entries: MeshLedgerEnt
285
640
  if (entries.length === 0) return { accepted: 0, skippedDuplicate: 0, rejectedInvalid: 0, entries: [] };
286
641
  const ledgerPath = getLedgerPath(meshId);
287
642
 
288
- // Read existing to deduplicate by ID
289
- const existing = new Set(readLedgerEntries(meshId).map(e => e.id));
643
+ // Dedup against recent entries only — P2P replication is incremental (cursor-based),
644
+ // so duplicates appear in the recent tail, not deep history.
645
+ const existing = new Set(readLedgerEntries(meshId, { tail: 1000 }).map(e => e.id));
290
646
  const validEntries: MeshLedgerEntry[] = [];
291
647
  let rejectedInvalid = 0;
292
648
  let skippedDuplicate = 0;
@@ -307,9 +663,24 @@ export function appendRemoteLedgerEntries(meshId: string, entries: MeshLedgerEnt
307
663
  return { accepted: 0, skippedDuplicate, rejectedInvalid, entries: [] };
308
664
  }
309
665
 
666
+ // G2: write to SQLite (primary runtime store); INSERT OR IGNORE dedups by id.
667
+ try {
668
+ MeshRuntimeStore.getInstance().importLedgerEntries(validEntries.map(e => ({
669
+ id: e.id,
670
+ meshId: e.meshId,
671
+ timestamp: e.timestamp,
672
+ kind: e.kind,
673
+ nodeId: e.nodeId ?? null,
674
+ sessionId: e.sessionId ?? null,
675
+ providerType: e.providerType ?? null,
676
+ payload: e.payload ?? {},
677
+ })));
678
+ } catch { /* best-effort; JSONL append below still records the entries */ }
679
+
310
680
  try {
311
681
  const lines = validEntries.map(e => JSON.stringify(e)).join('\n') + '\n';
312
682
  appendFileSync(ledgerPath, lines, { encoding: 'utf-8', mode: 0o600 });
683
+ invalidateLedgerCache(meshId);
313
684
  for (const entry of validEntries) {
314
685
  meshLedgerEvents.emit('append', meshId, entry);
315
686
  }
@@ -319,103 +690,151 @@ export function appendRemoteLedgerEntries(meshId: string, entries: MeshLedgerEnt
319
690
  }
320
691
  }
321
692
 
322
- /**
323
- * Read ledger entries with optional filtering.
324
- */
325
- export function readLedgerEntries(meshId: string, opts?: ReadLedgerOptions): MeshLedgerEntry[] {
693
+ // ─── Ledger Read Cache ─────────────────────────
694
+ // Absorbs repeated reads within a single event-processing burst (e.g. agent:stopped
695
+ // triggers shouldSuppressIntentionalCleanupStop, findRecentTerminalLedgerEvidence,
696
+ // hasDispatchAfterTerminal, and getSessionRecoveryContext all reading the same store).
697
+ // TTL is 100ms: short enough to stay current, long enough to cover one event cycle.
698
+ // Cache is invalidated on every write (append, remote import, compaction).
699
+
700
+ const ledgerReadCache = new Map<string, { entries: MeshLedgerEntry[]; cachedAt: number }>();
701
+ const LEDGER_CACHE_TTL_MS = 100;
702
+
703
+ function readLedgerFile(meshId: string): MeshLedgerEntry[] {
326
704
  const filePath = getLedgerPath(meshId);
327
705
  if (!existsSync(filePath)) return [];
328
-
329
706
  let content: string;
707
+ try { content = readFileSync(filePath, 'utf-8'); } catch { return []; }
708
+ const entries: MeshLedgerEntry[] = [];
709
+ for (const line of content.split('\n')) {
710
+ if (!line.trim()) continue;
711
+ try {
712
+ const entry = JSON.parse(line) as MeshLedgerEntry;
713
+ if (entry.id && entry.kind) entries.push(entry);
714
+ } catch { /* skip malformed lines */ }
715
+ }
716
+ return entries;
717
+ }
718
+
719
+ // ─── G2: One-Time JSONL → SQLite Import ─────────
720
+ // On the first SQLite read for a mesh (per store instance), import any legacy
721
+ // JSONL entries into mesh_event_ledger. INSERT OR IGNORE makes this idempotent:
722
+ // dual-written entries are skipped, only pre-cutover legacy entries are added.
723
+ // Keyed by the store instance so MeshRuntimeStore.resetForTests() (fresh DB)
724
+ // naturally re-imports.
725
+
726
+ let ledgerImportStoreRef: MeshRuntimeStore | undefined;
727
+ const ledgerImportDone = new Set<string>();
728
+
729
+ function ensureLedgerImported(store: MeshRuntimeStore, meshId: string): void {
730
+ if (ledgerImportStoreRef !== store) {
731
+ ledgerImportDone.clear();
732
+ ledgerImportStoreRef = store;
733
+ }
734
+ if (ledgerImportDone.has(meshId)) return;
735
+ ledgerImportDone.add(meshId);
736
+ const fileEntries = readLedgerFile(meshId);
737
+ if (fileEntries.length === 0) return;
330
738
  try {
331
- content = readFileSync(filePath, 'utf-8');
739
+ store.importLedgerEntries(fileEntries.map(e => ({
740
+ id: e.id,
741
+ meshId: e.meshId,
742
+ timestamp: e.timestamp,
743
+ kind: e.kind,
744
+ nodeId: e.nodeId ?? null,
745
+ sessionId: e.sessionId ?? null,
746
+ providerType: e.providerType ?? null,
747
+ payload: e.payload ?? {},
748
+ })));
749
+ } catch { /* import is best-effort; reads fall back to JSONL on store failure */ }
750
+ }
751
+
752
+ function readLedgerFromStore(meshId: string): MeshLedgerEntry[] {
753
+ const store = MeshRuntimeStore.getInstance();
754
+ ensureLedgerImported(store, meshId);
755
+ return store.readLedgerEntriesOrdered(meshId).map(r => ({
756
+ id: r.id,
757
+ meshId: r.meshId,
758
+ timestamp: r.timestamp,
759
+ kind: r.kind as MeshLedgerKind,
760
+ ...(r.nodeId ? { nodeId: r.nodeId } : {}),
761
+ ...(r.sessionId ? { sessionId: r.sessionId } : {}),
762
+ ...(r.providerType ? { providerType: r.providerType } : {}),
763
+ payload: (r.payload && typeof r.payload === 'object' ? r.payload : {}) as Record<string, unknown>,
764
+ }));
765
+ }
766
+
767
+ function getCachedRawEntries(meshId: string): MeshLedgerEntry[] {
768
+ const now = Date.now();
769
+ const cached = ledgerReadCache.get(meshId);
770
+ if (cached && now - cached.cachedAt < LEDGER_CACHE_TTL_MS) return cached.entries;
771
+ let entries: MeshLedgerEntry[];
772
+ try {
773
+ // G2: SQLite mesh_event_ledger is the primary runtime read path.
774
+ entries = readLedgerFromStore(meshId);
332
775
  } catch {
333
- return [];
776
+ // Store unavailable — fall back to the JSONL export artifact.
777
+ entries = readLedgerFile(meshId);
334
778
  }
779
+ ledgerReadCache.set(meshId, { entries, cachedAt: now });
780
+ return entries;
781
+ }
335
782
 
336
- const lines = content.split('\n').filter(line => line.trim());
337
- let entries: MeshLedgerEntry[] = [];
783
+ function invalidateLedgerCache(meshId: string): void {
784
+ ledgerReadCache.delete(meshId);
785
+ }
338
786
 
339
- for (const line of lines) {
340
- try {
341
- const entry = JSON.parse(line) as MeshLedgerEntry;
342
- if (!entry.id || !entry.kind) continue;
343
- entries.push(entry);
344
- } catch {
345
- // Skip malformed lines
346
- }
347
- }
787
+ /**
788
+ * Test helper: clear all runtime ledger state for a mesh — SQLite rows, read
789
+ * cache, and the one-time import flag. JSONL files are the caller's concern.
790
+ */
791
+ export function __clearMeshLedgerForTests(meshId: string): void {
792
+ try {
793
+ MeshRuntimeStore.getInstance().clearLedgerForMesh(meshId);
794
+ } catch { /* store unavailable — nothing to clear */ }
795
+ ledgerReadCache.delete(meshId);
796
+ ledgerImportDone.delete(meshId);
797
+ }
798
+
799
+ /**
800
+ * Read ledger entries with optional filtering.
801
+ * G2: SQLite (mesh_event_ledger) is the primary read path; legacy JSONL is
802
+ * imported once per store instance and otherwise retained as an
803
+ * export/import/debug artifact only.
804
+ */
805
+ export function readLedgerEntries(meshId: string, opts?: ReadLedgerOptions): MeshLedgerEntry[] {
806
+ let entries = getCachedRawEntries(meshId);
348
807
 
349
- // Apply filters
350
808
  if (opts?.since) {
351
809
  const sinceDate = new Date(opts.since).getTime();
352
- if (!isNaN(sinceDate)) {
353
- entries = entries.filter(e => new Date(e.timestamp).getTime() >= sinceDate);
354
- }
810
+ if (!isNaN(sinceDate)) entries = entries.filter(e => new Date(e.timestamp).getTime() >= sinceDate);
355
811
  }
356
-
357
812
  if (opts?.kind?.length) {
358
813
  const kindSet = new Set(opts.kind);
359
814
  entries = entries.filter(e => kindSet.has(e.kind));
360
815
  }
361
-
362
- // Apply tail (return last N entries)
363
816
  if (opts?.tail && opts.tail > 0 && entries.length > opts.tail) {
364
817
  entries = entries.slice(-opts.tail);
365
818
  }
366
-
367
819
  return entries;
368
820
  }
369
821
 
370
822
  /**
371
- * Read a bounded, cursor-addressable ledger slice for local-first/P2P replication.
372
- * The result is intentionally small and self-describing so coordinators can query
373
- * remote daemons on demand without Cloud/D1 becoming a ledger data-plane.
374
- */
375
- export function readLedgerSlice(meshId: string, opts?: ReadLedgerSliceOptions): MeshLedgerSlice {
376
- const limit = clampLedgerSliceLimit(opts?.limit);
377
- let entries = readLedgerEntries(meshId, { since: opts?.since, kind: opts?.kind });
378
- const afterId = typeof opts?.afterId === 'string' && opts.afterId.trim() ? opts.afterId.trim() : null;
379
- if (afterId) {
380
- const index = entries.findIndex(entry => entry.id === afterId);
381
- entries = index >= 0 ? entries.slice(index + 1) : entries;
382
- }
383
- const bounded = entries.slice(0, limit);
384
- return {
385
- protocol: 'adhdev.mesh.ledger.slice.v1',
386
- meshId,
387
- entries: bounded,
388
- cursor: {
389
- afterId,
390
- nextAfterId: bounded.length ? bounded[bounded.length - 1].id : afterId,
391
- limit,
392
- hasMore: entries.length > bounded.length,
393
- },
394
- summary: getLedgerSummary(meshId),
395
- sourceOfTruth: {
396
- kind: 'local_jsonl',
397
- path: getLedgerPath(meshId),
398
- bounded: true,
399
- maxLimit: MAX_LEDGER_SLICE_LIMIT,
400
- },
401
- };
402
- }
403
-
404
- /**
405
- * Get a summary of mesh activity from the ledger.
823
+ * Build a ledger summary from pre-loaded entries. Used by both getLedgerSummary
824
+ * and readLedgerSlice so they share a single getCachedRawEntries() call.
406
825
  */
407
- export function getLedgerSummary(meshId: string): MeshLedgerSummary {
408
- const entries = readLedgerEntries(meshId);
826
+ function buildLedgerSummary(meshId: string, entries: MeshLedgerEntry[]): MeshLedgerSummary {
827
+ const archived = readArchivedCounts(meshId);
409
828
  const now = Date.now();
410
829
  const recentFailureCutoff = now - RECENT_FAILURE_WINDOW_MS;
411
830
 
412
831
  const summary: MeshLedgerSummary = {
413
832
  meshId,
414
- totalEntries: entries.length,
833
+ totalEntries: entries.length + archived.totalArchived,
415
834
  taskDispatched: 0,
416
- taskCompleted: 0,
417
- taskFailed: 0,
418
- taskStalled: 0,
835
+ taskCompleted: archived.taskCompleted,
836
+ taskFailed: archived.taskFailed,
837
+ taskStalled: archived.taskStalled,
419
838
  sessionLaunched: 0,
420
839
  checkpointCreated: 0,
421
840
  lastActivityAt: null,
@@ -450,6 +869,79 @@ export function getLedgerSummary(meshId: string): MeshLedgerSummary {
450
869
  return summary;
451
870
  }
452
871
 
872
+ /**
873
+ * Read a bounded, cursor-addressable ledger slice for local-first/P2P replication.
874
+ * The result is intentionally small and self-describing so coordinators can query
875
+ * remote daemons on demand without Cloud/D1 becoming a ledger data-plane.
876
+ */
877
+ export function readLedgerSlice(meshId: string, opts?: ReadLedgerSliceOptions): MeshLedgerSlice {
878
+ const limit = clampLedgerSliceLimit(opts?.limit);
879
+ // Load raw entries once and share between filtering, pagination, and summary.
880
+ const rawEntries = getCachedRawEntries(meshId);
881
+
882
+ let entries: MeshLedgerEntry[] = rawEntries;
883
+ if (opts?.since) {
884
+ const sinceDate = new Date(opts.since).getTime();
885
+ if (!isNaN(sinceDate)) entries = entries.filter(e => new Date(e.timestamp).getTime() >= sinceDate);
886
+ }
887
+ if (opts?.kind?.length) {
888
+ const kindSet = new Set(opts.kind);
889
+ entries = entries.filter(e => kindSet.has(e.kind));
890
+ }
891
+
892
+ const afterId = typeof opts?.afterId === 'string' && opts.afterId.trim() ? opts.afterId.trim() : null;
893
+ if (afterId) {
894
+ const index = entries.findIndex(entry => entry.id === afterId);
895
+ entries = index >= 0 ? entries.slice(index + 1) : entries;
896
+ }
897
+ const bounded = entries.slice(0, limit);
898
+ return {
899
+ protocol: 'adhdev.mesh.ledger.slice.v1',
900
+ meshId,
901
+ entries: bounded,
902
+ cursor: {
903
+ afterId,
904
+ nextAfterId: bounded.length ? bounded[bounded.length - 1].id : afterId,
905
+ limit,
906
+ hasMore: entries.length > bounded.length,
907
+ },
908
+ summary: buildLedgerSummary(meshId, rawEntries),
909
+ sourceOfTruth: {
910
+ kind: 'local_jsonl',
911
+ path: getLedgerPath(meshId),
912
+ bounded: true,
913
+ maxLimit: MAX_LEDGER_SLICE_LIMIT,
914
+ },
915
+ };
916
+ }
917
+
918
+ /**
919
+ * G4: Read a bounded ledger slice from the SQLite mesh_event_ledger table.
920
+ * This is the preferred P2P reconcile read path; JSONL files are retained as
921
+ * export/import/debug/legacy artifacts only.
922
+ *
923
+ * Returns a shape structurally compatible with MeshLedgerSlice (minus the
924
+ * JSONL-specific `summary` and `sourceOfTruth.path` fields) so callers can
925
+ * pass it to buildMeshLedgerReplicaEvidence without modification.
926
+ */
927
+ export function readLedgerSliceFromStore(meshId: string, opts?: ReadLedgerSliceOptions): ReturnType<typeof MeshRuntimeStore.prototype.readLedgerSlice> {
928
+ return MeshRuntimeStore.getInstance().readLedgerSlice(meshId, {
929
+ afterId: opts?.afterId,
930
+ since: opts?.since,
931
+ // ReadLedgerSliceOptions allows kind as array; SQLite path takes a single kind string.
932
+ // Pass first kind value if provided; callers needing multi-kind filtering should use readLedgerSlice (JSONL).
933
+ kind: opts?.kind?.length ? opts.kind[0] : undefined,
934
+ limit: opts?.limit,
935
+ });
936
+ }
937
+
938
+ /**
939
+ * Get a summary of mesh activity from the ledger.
940
+ */
941
+ export function getLedgerSummary(meshId: string): MeshLedgerSummary {
942
+ return buildLedgerSummary(meshId, getCachedRawEntries(meshId));
943
+ }
944
+
453
945
  // ─── Recovery Context ───────────────────────────
454
946
 
455
947
  export interface SessionRecoveryContext {
@@ -484,46 +976,64 @@ export function getSessionRecoveryContext(
484
976
  },
485
977
  ): SessionRecoveryContext {
486
978
  const maxRetries = opts.maxRetries ?? 1;
487
- const entries = readLedgerEntries(meshId);
979
+ // tail:500 is sufficient — task_dispatched is never archived (only terminal kinds are),
980
+ // so dispatch history is always present. The 30-min failure window means we never need
981
+ // more than a few dozen recent entries for consecutiveNodeFailures. Bounding to 500
982
+ // avoids a full O(n) scan for meshes with many historical entries.
983
+ const entries = readLedgerEntries(meshId, { tail: 500 });
488
984
 
489
- // Find the last task_dispatched for this session or node
985
+ // Single backward pass: find last task_dispatched AND count consecutive recent failures.
986
+ const now = Date.now();
987
+ const recentWindow = now - RECENT_FAILURE_WINDOW_MS;
490
988
  let lastDispatch: MeshLedgerEntry | null = null;
989
+ let consecutiveNodeFailures = 0;
990
+ let failureCountDone = false;
491
991
  for (let i = entries.length - 1; i >= 0; i--) {
492
992
  const e = entries[i];
493
- if (e.kind !== 'task_dispatched') continue;
494
- if (opts.sessionId && e.sessionId === opts.sessionId) { lastDispatch = e; break; }
495
- if (opts.nodeId && e.nodeId === opts.nodeId) { lastDispatch = e; break; }
993
+ const ts = new Date(e.timestamp).getTime();
994
+
995
+ // Failure counting: scan until we exit the recent window or hit a chain-breaker
996
+ if (!failureCountDone) {
997
+ if (ts < recentWindow) {
998
+ failureCountDone = true;
999
+ } else if (opts.nodeId && e.nodeId !== opts.nodeId) {
1000
+ // Entry for a different node — skip for failure counting but continue scanning for dispatch
1001
+ } else if (e.kind === 'task_failed') {
1002
+ if (!isIntentionalCleanupStopEntry(e)) consecutiveNodeFailures++;
1003
+ } else if (e.kind === 'task_completed' || e.kind === 'task_dispatched') {
1004
+ // A completion or new dispatch breaks the consecutive failure chain
1005
+ failureCountDone = true;
1006
+ }
1007
+ }
1008
+
1009
+ // Dispatch search: find the last dispatch matching this session or node
1010
+ if (lastDispatch === null && e.kind === 'task_dispatched') {
1011
+ if (opts.sessionId && e.sessionId === opts.sessionId) { lastDispatch = e; }
1012
+ else if (!opts.sessionId && opts.nodeId && e.nodeId === opts.nodeId) { lastDispatch = e; }
1013
+ }
1014
+
1015
+ // Stop once both tasks are done
1016
+ if (lastDispatch !== null && failureCountDone) break;
496
1017
  }
497
1018
 
498
1019
  const lastTaskMessage = typeof lastDispatch?.payload?.message === 'string'
499
1020
  ? lastDispatch.payload.message
500
1021
  : null;
501
1022
 
502
- // Count consecutive recent failures for this node (within 30 min window)
503
- const now = Date.now();
504
- const recentWindow = now - RECENT_FAILURE_WINDOW_MS;
505
- let consecutiveNodeFailures = 0;
506
- for (let i = entries.length - 1; i >= 0; i--) {
507
- const e = entries[i];
508
- if (new Date(e.timestamp).getTime() < recentWindow) break;
509
- if (opts.nodeId && e.nodeId !== opts.nodeId) continue;
510
- if (e.kind === 'task_failed') {
511
- if (isIntentionalCleanupStopEntry(e)) continue;
512
- consecutiveNodeFailures++;
513
- } else if (e.kind === 'task_completed' || e.kind === 'task_dispatched') {
514
- // A completion or new dispatch breaks the consecutive failure chain
515
- break;
516
- }
517
- }
518
-
519
- // Count how many times the same task was attempted (match by message prefix)
1023
+ // Count how many times the same task was attempted.
1024
+ // Prefer exact taskId match (payload.taskId) to avoid 200-char prefix collisions.
520
1025
  let taskAttemptCount = 0;
521
- if (lastTaskMessage) {
522
- const prefix = lastTaskMessage.slice(0, 200);
523
- for (const e of entries) {
524
- if (e.kind === 'task_dispatched' && typeof e.payload?.message === 'string') {
525
- if (e.payload.message.startsWith(prefix)) {
526
- taskAttemptCount++;
1026
+ if (lastDispatch) {
1027
+ const taskId = typeof lastDispatch.payload?.taskId === 'string' ? lastDispatch.payload.taskId : null;
1028
+ if (taskId) {
1029
+ for (const e of entries) {
1030
+ if (e.kind === 'task_dispatched' && e.payload?.taskId === taskId) taskAttemptCount++;
1031
+ }
1032
+ } else if (lastTaskMessage) {
1033
+ const prefix = lastTaskMessage.slice(0, 200);
1034
+ for (const e of entries) {
1035
+ if (e.kind === 'task_dispatched' && typeof e.payload?.message === 'string') {
1036
+ if (e.payload.message.startsWith(prefix)) taskAttemptCount++;
527
1037
  }
528
1038
  }
529
1039
  }
@@ -573,7 +1083,8 @@ function rotateLedgerFile(meshId: string, currentPath: string): void {
573
1083
 
574
1084
  try {
575
1085
  renameSync(currentPath, getRotatedPath(meshId, index));
576
- } catch {
1086
+ } catch (e: any) {
577
1087
  // Rotation failed — the next append will just grow the file
1088
+ process.stderr.write(`[adhdev-mesh] Ledger rotation failed for mesh ${meshId}: ${e?.message || e}. File will continue to grow.\n`);
578
1089
  }
579
1090
  }