@reticlehq/server 3.2.0 → 3.4.0

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 (437) hide show
  1. package/LICENSE-ENTERPRISE +1 -1
  2. package/README.md +3 -3
  3. package/SKILL.md +23 -22
  4. package/dist/chromium-launch-options.d.ts +4 -3
  5. package/dist/chromium-launch-options.js +6 -2
  6. package/dist/command/cli/adhoc-suite.d.ts +41 -0
  7. package/dist/command/cli/adhoc-suite.js +159 -0
  8. package/dist/command/cli/adhoc-verdict.d.ts +58 -2
  9. package/dist/command/cli/adhoc-verdict.js +108 -13
  10. package/dist/command/cli/answers/gate-hook-message.d.ts +6 -0
  11. package/dist/command/cli/answers/gate-hook-message.js +6 -0
  12. package/dist/command/cli/auth/cloud-keystore.d.ts +0 -2
  13. package/dist/command/cli/auth/cloud-keystore.js +0 -3
  14. package/dist/command/cli/auth/cloud-session.d.ts +4 -0
  15. package/dist/command/cli/auth/cloud-session.js +2 -0
  16. package/dist/command/cli/cli-doctor.js +30 -8
  17. package/dist/command/cli/cli-flow-commands.d.ts +3 -6
  18. package/dist/command/cli/cli-flow-commands.js +67 -4
  19. package/dist/command/cli/cli-parse-grammar.d.ts +7 -0
  20. package/dist/command/cli/cli-parse-grammar.js +7 -0
  21. package/dist/command/cli/cli-parse-verify.d.ts +2 -5
  22. package/dist/command/cli/cli-parse-verify.js +86 -13
  23. package/dist/command/cli/cli-parse.d.ts +14 -1
  24. package/dist/command/cli/cli-parse.js +54 -8
  25. package/dist/command/cli/cli-update-commands.js +8 -1
  26. package/dist/command/cli/cli-verify.d.ts +80 -0
  27. package/dist/command/cli/cli-verify.js +181 -26
  28. package/dist/command/cli/cloud-cli.js +5 -2
  29. package/dist/command/cli/cloud-kit.d.ts +0 -2
  30. package/dist/command/cli/cloud-kit.js +3 -5
  31. package/dist/command/cli/cloud-login.js +21 -3
  32. package/dist/command/cli/demo/demo-page.js +6 -3
  33. package/dist/command/cli/doctor/browser/chromium-hint.d.ts +5 -0
  34. package/dist/command/cli/doctor/browser/chromium-hint.js +5 -0
  35. package/dist/command/cli/doctor/doctor-sessions-line.d.ts +2 -1
  36. package/dist/command/cli/launch/cli-launch.d.ts +9 -29
  37. package/dist/command/cli/launch/cli-launch.js +27 -49
  38. package/dist/command/cli/launch/sdk-version-match.d.ts +84 -0
  39. package/dist/command/cli/launch/sdk-version-match.js +206 -0
  40. package/dist/command/cli/lifecycle/daemon-lifecycle.js +2 -16
  41. package/dist/command/cli/ports/resolve/cli-port.d.ts +38 -0
  42. package/dist/command/cli/ports/resolve/cli-port.js +169 -15
  43. package/dist/command/cli/report-command.d.ts +16 -0
  44. package/dist/command/cli/report-command.js +97 -0
  45. package/dist/command/cli/setup-mcp-cli.js +13 -13
  46. package/dist/command/cli/tutorial.d.ts +2 -2
  47. package/dist/command/cli/tutorial.js +21 -7
  48. package/dist/command/cli.js +67 -10
  49. package/dist/command/daemon/binding/daemon-status-probe.d.ts +44 -2
  50. package/dist/command/daemon/binding/daemon-status-probe.js +79 -6
  51. package/dist/command/daemon/binding/port-presence.d.ts +1 -1
  52. package/dist/command/daemon/daemon-resolve.d.ts +9 -0
  53. package/dist/command/daemon/daemon-resolve.js +1 -1
  54. package/dist/command/daemon/idle-shutdown.d.ts +2 -1
  55. package/dist/command/daemon/lifetime/daemon-usefulness.d.ts +7 -1
  56. package/dist/command/daemon/lifetime/daemon-usefulness.js +30 -7
  57. package/dist/command/dev/stale-issue-guard.d.ts +3 -4
  58. package/dist/command/dev/stale-issue-guard.js +8 -8
  59. package/dist/command/setup/bringup/connect-lease.d.ts +17 -0
  60. package/dist/command/setup/bringup/connect-lease.js +38 -0
  61. package/dist/command/setup/bringup/dev-server-files.d.ts +68 -0
  62. package/dist/command/setup/bringup/dev-server-files.js +131 -0
  63. package/dist/command/setup/bringup/dev-server-log-cap.d.ts +35 -0
  64. package/dist/command/setup/bringup/dev-server-log-cap.js +121 -0
  65. package/dist/command/setup/bringup/ensure-daemon.d.ts +65 -4
  66. package/dist/command/setup/bringup/ensure-daemon.js +135 -16
  67. package/dist/command/setup/bringup/static-page-server.d.ts +35 -0
  68. package/dist/command/setup/bringup/static-page-server.js +151 -0
  69. package/dist/command/setup/desktop-shape.d.ts +4 -0
  70. package/dist/command/setup/desktop-shape.js +18 -2
  71. package/dist/command/setup/init/init-host.js +8 -0
  72. package/dist/command/setup/init/init-port.d.ts +15 -0
  73. package/dist/command/setup/init/init-port.js +44 -0
  74. package/dist/command/setup/init/init-runtime.d.ts +1 -1
  75. package/dist/command/setup/init/init-runtime.js +39 -2
  76. package/dist/command/setup/node-effects.d.ts +27 -14
  77. package/dist/command/setup/node-effects.js +80 -44
  78. package/dist/command/setup/probe/loopback-probe-urls.d.ts +7 -0
  79. package/dist/command/setup/probe/loopback-probe-urls.js +40 -0
  80. package/dist/command/setup/probe/page-probe.d.ts +13 -2
  81. package/dist/command/setup/probe/page-probe.js +18 -3
  82. package/dist/command/setup/run-setup.d.ts +26 -1
  83. package/dist/command/setup/run-setup.js +96 -13
  84. package/dist/command/setup/session-pick.d.ts +13 -0
  85. package/dist/command/setup/session-pick.js +20 -0
  86. package/dist/command/setup/setup-command.d.ts +2 -14
  87. package/dist/command/setup/setup-command.js +105 -31
  88. package/dist/command/setup/setup-mcp.js +3 -3
  89. package/dist/command/setup/terminal/wait-progress.d.ts +8 -0
  90. package/dist/command/setup/terminal/wait-progress.js +17 -0
  91. package/dist/features/crawl/crawl-empty.js +1 -1
  92. package/dist/features/crawl/crawl-tools.d.ts +6 -0
  93. package/dist/features/crawl/crawl-tools.js +57 -0
  94. package/dist/features/crawl/crawl.d.ts +2 -0
  95. package/dist/features/crawl/crawl.js +171 -156
  96. package/dist/features/exhaust/app-coverage.d.ts +33 -0
  97. package/dist/features/exhaust/app-coverage.js +31 -0
  98. package/dist/features/exhaust/code-coverage.d.ts +47 -0
  99. package/dist/features/exhaust/code-coverage.js +77 -0
  100. package/dist/features/exhaust/exhaust-run.d.ts +33 -0
  101. package/dist/features/exhaust/exhaust-run.js +82 -0
  102. package/dist/features/exhaust/explorer.d.ts +103 -0
  103. package/dist/features/exhaust/explorer.js +195 -0
  104. package/dist/features/exhaust/failure-branches.d.ts +33 -0
  105. package/dist/features/exhaust/failure-branches.js +92 -0
  106. package/dist/features/exhaust/ledger.d.ts +202 -0
  107. package/dist/features/exhaust/ledger.js +252 -0
  108. package/dist/features/exhaust/session-fold.d.ts +31 -0
  109. package/dist/features/exhaust/session-fold.js +79 -0
  110. package/dist/features/exhaust/session-port.d.ts +36 -0
  111. package/dist/features/exhaust/session-port.js +102 -0
  112. package/dist/features/harness/drive-report.d.ts +0 -9
  113. package/dist/features/harness/drive-report.js +18 -0
  114. package/dist/features/harness/driver.js +4 -7
  115. package/dist/features/harness/fill-values.d.ts +6 -0
  116. package/dist/features/harness/fill-values.js +3 -0
  117. package/dist/features/harness/jev-driver.js +36 -22
  118. package/dist/features/harness/openai-driver.js +4 -7
  119. package/dist/features/harness/platform-config.d.ts +10 -0
  120. package/dist/features/harness/platform-config.js +13 -6
  121. package/dist/index.d.ts +10 -1
  122. package/dist/index.js +78 -104
  123. package/dist/judgement/capsule/capsule-store.d.ts +31 -2
  124. package/dist/judgement/capsule/capsule-store.js +58 -6
  125. package/dist/judgement/capsule/capsule.js +5 -0
  126. package/dist/judgement/capsule/causal-summary.d.ts +13 -1
  127. package/dist/judgement/capsule/causal-summary.js +7 -0
  128. package/dist/judgement/domain/domain-model.js +40 -9
  129. package/dist/judgement/oracles/self-instrument.js +4 -2
  130. package/dist/judgement/outcome/assertion-integrity.d.ts +3 -3
  131. package/dist/judgement/runs/artifact/gap-report.d.ts +17 -0
  132. package/dist/judgement/runs/artifact/gap-report.js +46 -0
  133. package/dist/judgement/runs/artifact/gap-summary.d.ts +58 -0
  134. package/dist/judgement/runs/artifact/gap-summary.js +85 -0
  135. package/dist/judgement/runs/artifact/render-report.js +4 -1
  136. package/dist/judgement/runs/artifact/replay-mapping.d.ts +2 -2
  137. package/dist/judgement/runs/artifact/replay-mapping.js +44 -9
  138. package/dist/judgement/runs/artifact/to-artifact.d.ts +7 -2
  139. package/dist/judgement/runs/artifact/to-artifact.js +1 -1
  140. package/dist/judgement/runs/context-tools.js +21 -3
  141. package/dist/judgement/runs/drive-run.js +7 -2
  142. package/dist/judgement/runs/verification-sync.d.ts +3 -1
  143. package/dist/judgement/runs/verification-sync.js +1 -1
  144. package/dist/language/flows/annotate-notes/annotate.d.ts +0 -15
  145. package/dist/language/flows/annotate-notes/annotate.js +66 -18
  146. package/dist/language/flows/change/affected.d.ts +9 -0
  147. package/dist/language/flows/change/affected.js +35 -0
  148. package/dist/language/flows/change/gate.d.ts +20 -0
  149. package/dist/language/flows/change/gate.js +12 -1
  150. package/dist/language/flows/flow-anchor.d.ts +12 -0
  151. package/dist/language/flows/flow-anchor.js +20 -0
  152. package/dist/language/flows/flow-author.d.ts +7 -0
  153. package/dist/language/flows/flow-author.js +19 -0
  154. package/dist/language/flows/flow-expect-grammar.d.ts +2 -2
  155. package/dist/language/flows/flow-expect-grammar.js +92 -11
  156. package/dist/language/flows/flow-intent.js +1 -1
  157. package/dist/language/flows/flow-journey.d.ts +54 -0
  158. package/dist/language/flows/flow-journey.js +119 -0
  159. package/dist/language/flows/flow-learning.d.ts +3 -0
  160. package/dist/language/flows/flow-learning.js +39 -8
  161. package/dist/language/flows/flow-replay-run.d.ts +33 -12
  162. package/dist/language/flows/flow-replay-run.js +131 -52
  163. package/dist/language/flows/flow-replay-types.d.ts +9 -0
  164. package/dist/language/flows/flow-replay.d.ts +27 -12
  165. package/dist/language/flows/flow-replay.js +127 -111
  166. package/dist/language/flows/flow-result.d.ts +2 -0
  167. package/dist/language/flows/flow-result.js +25 -0
  168. package/dist/language/flows/flow-step-runners.d.ts +26 -10
  169. package/dist/language/flows/flow-step-runners.js +20 -4
  170. package/dist/language/flows/flow-success.d.ts +21 -6
  171. package/dist/language/flows/flow-success.js +50 -138
  172. package/dist/language/flows/flow-tools.js +17 -5
  173. package/dist/language/flows/flows.d.ts +18 -5
  174. package/dist/language/flows/flows.js +101 -16
  175. package/dist/language/flows/heal.d.ts +9 -1
  176. package/dist/language/flows/heal.js +24 -2
  177. package/dist/language/flows/learned-sources.d.ts +21 -0
  178. package/dist/language/flows/learned-sources.js +84 -0
  179. package/dist/language/flows/recording/recording-backtrack.d.ts +0 -7
  180. package/dist/language/flows/recording/recording-backtrack.js +1 -1
  181. package/dist/language/flows/recording/replay-halt.d.ts +2 -1
  182. package/dist/language/flows/recording/replay-halt.js +3 -1
  183. package/dist/language/flows/recording/tape/recordings.d.ts +31 -2
  184. package/dist/language/flows/recording/tape/recordings.js +55 -1
  185. package/dist/language/flows/replay.d.ts +19 -0
  186. package/dist/language/flows/replay.js +53 -13
  187. package/dist/language/flows/stores/annotation-store.d.ts +5 -5
  188. package/dist/language/flows/stores/assertion-tiers-store.js +2 -3
  189. package/dist/language/flows/stores/flake-store.js +2 -3
  190. package/dist/language/flows/suite/coverage.d.ts +1 -1
  191. package/dist/language/flows/suite/parallel-suite.d.ts +2 -1
  192. package/dist/language/flows/verify-change-tools.js +19 -2
  193. package/dist/launch-chromium.d.ts +80 -0
  194. package/dist/launch-chromium.js +218 -0
  195. package/dist/memory/cloud/cloud-config.d.ts +11 -2
  196. package/dist/memory/cloud/cloud-config.js +47 -7
  197. package/dist/memory/cloud/cloud-sync.d.ts +4 -3
  198. package/dist/memory/cloud/cloud-sync.js +7 -10
  199. package/dist/memory/cloud/harness-config.d.ts +2 -0
  200. package/dist/memory/cloud/harness-offer.js +5 -6
  201. package/dist/memory/cloud/harness-switch.js +5 -6
  202. package/dist/memory/cloud/sync-cycle.d.ts +78 -1
  203. package/dist/memory/cloud/sync-cycle.js +321 -53
  204. package/dist/memory/cloud/sync-daemon.d.ts +13 -0
  205. package/dist/memory/cloud/sync-daemon.js +45 -20
  206. package/dist/memory/cloud/sync-disk.js +27 -8
  207. package/dist/memory/impact/impact-store.js +6 -0
  208. package/dist/memory/intent/inline-intent.js +8 -3
  209. package/dist/memory/intent/intent-shard-store.d.ts +33 -47
  210. package/dist/memory/intent/intent-shard-store.js +252 -140
  211. package/dist/memory/intent/intent-shard.d.ts +0 -23
  212. package/dist/memory/intent/intent-shard.js +1 -11
  213. package/dist/memory/intent/intent-store.d.ts +13 -8
  214. package/dist/memory/intent/intent-store.js +46 -90
  215. package/dist/memory/intent/intent-tools.js +34 -23
  216. package/dist/memory/journal/ambient-store.js +2 -3
  217. package/dist/memory/journal/deviation-report.js +4 -1
  218. package/dist/memory/journal/deviation-service.js +10 -2
  219. package/dist/memory/journal/drive-flow.d.ts +19 -14
  220. package/dist/memory/journal/drive-flow.js +65 -48
  221. package/dist/memory/journal/envelope-store.js +2 -3
  222. package/dist/memory/journal/journal-recorder.d.ts +21 -0
  223. package/dist/memory/journal/on-disk/retain-policy.d.ts +34 -0
  224. package/dist/memory/journal/on-disk/retain-policy.js +44 -0
  225. package/dist/memory/journal/on-disk/retention.d.ts +14 -10
  226. package/dist/memory/journal/on-disk/retention.js +13 -12
  227. package/dist/memory/journal/on-disk/startup-maintenance.d.ts +42 -0
  228. package/dist/memory/journal/on-disk/startup-maintenance.js +14 -0
  229. package/dist/memory/journal/on-disk/workspace-gitignore.d.ts +0 -11
  230. package/dist/memory/journal/on-disk/workspace-gitignore.js +6 -45
  231. package/dist/memory/journal/on-disk/workspace-tiers.d.ts +52 -0
  232. package/dist/memory/journal/on-disk/workspace-tiers.js +90 -0
  233. package/dist/memory/journal/route-template.d.ts +22 -0
  234. package/dist/memory/journal/route-template.js +44 -0
  235. package/dist/memory/journal/session-end.d.ts +13 -2
  236. package/dist/memory/journal/session-end.js +88 -18
  237. package/dist/memory/journal/session-journal.d.ts +8 -1
  238. package/dist/memory/journal/session-journal.js +10 -0
  239. package/dist/memory/project/baselines.d.ts +1 -1
  240. package/dist/memory/project/dir/reticle-dir.d.ts +3 -1
  241. package/dist/memory/project/dir/reticle-dir.js +1 -0
  242. package/dist/memory/project/fs/fs-port.js +44 -5
  243. package/dist/memory/project/fs/write-atomic.d.ts +26 -0
  244. package/dist/memory/project/fs/write-atomic.js +48 -0
  245. package/dist/memory/project/project-tools.js +3 -1
  246. package/dist/memory/recall/prior/connection-memory.d.ts +6 -0
  247. package/dist/memory/recall/prior/connection-memory.js +8 -0
  248. package/dist/memory/recall/registered-projects.d.ts +2 -1
  249. package/dist/portal/bridge/auth-failure-reason.d.ts +1 -1
  250. package/dist/portal/bridge/auth-failure-reason.js +9 -4
  251. package/dist/portal/bridge/bridge.d.ts +8 -0
  252. package/dist/portal/bridge/bridge.js +43 -2
  253. package/dist/portal/input/js-coverage.d.ts +25 -0
  254. package/dist/portal/input/js-coverage.js +30 -0
  255. package/dist/portal/input/real-input.d.ts +11 -0
  256. package/dist/portal/input/real-input.js +31 -5
  257. package/dist/portal/pool/browser-pool.d.ts +10 -0
  258. package/dist/portal/pool/browser-pool.js +10 -0
  259. package/dist/portal/pool/playwright-launcher.d.ts +6 -0
  260. package/dist/portal/pool/playwright-launcher.js +21 -13
  261. package/dist/portal/pool/pool-contract.d.ts +2 -0
  262. package/dist/portal/pool/storage-seed.d.ts +2 -1
  263. package/dist/portal/pool/zero-install.d.ts +27 -0
  264. package/dist/portal/pool/zero-install.js +54 -0
  265. package/dist/portal/realm/web-realm.js +6 -0
  266. package/dist/portal/session/dev-server/dev-command.d.ts +2 -2
  267. package/dist/portal/session/dev-server/dev-command.js +30 -12
  268. package/dist/portal/session/dev-server/dev-server-probe.d.ts +2 -1
  269. package/dist/portal/session/dev-server/dev-server-probe.js +17 -1
  270. package/dist/portal/session/dev-server/route-status-probe.d.ts +24 -0
  271. package/dist/portal/session/dev-server/route-status-probe.js +57 -0
  272. package/dist/portal/session/dev-server/served-document.d.ts +21 -0
  273. package/dist/portal/session/dev-server/served-document.js +57 -0
  274. package/dist/portal/session/facts/background-traffic.d.ts +9 -0
  275. package/dist/portal/session/facts/background-traffic.js +25 -0
  276. package/dist/portal/session/facts/observed-state.d.ts +11 -0
  277. package/dist/portal/session/facts/observed-state.js +34 -0
  278. package/dist/portal/session/live-control-tools.js +48 -3
  279. package/dist/portal/session/no-session-diagnosis.d.ts +29 -0
  280. package/dist/portal/session/no-session-diagnosis.js +62 -5
  281. package/dist/portal/session/no-session-next-action.d.ts +13 -0
  282. package/dist/portal/session/no-session-next-action.js +48 -9
  283. package/dist/portal/session/no-session-watch.d.ts +18 -0
  284. package/dist/portal/session/no-session-watch.js +88 -2
  285. package/dist/portal/session/presence/attachment-history.d.ts +13 -2
  286. package/dist/portal/session/session-health.d.ts +61 -0
  287. package/dist/portal/session/session-health.js +43 -3
  288. package/dist/portal/session/session-info.d.ts +6 -0
  289. package/dist/portal/session/session-manager.d.ts +29 -6
  290. package/dist/portal/session/session-manager.js +42 -5
  291. package/dist/portal/session/session-reaper.d.ts +28 -0
  292. package/dist/portal/session/session-reaper.js +49 -1
  293. package/dist/portal/session/session-successor.d.ts +11 -0
  294. package/dist/portal/session/session-successor.js +14 -1
  295. package/dist/portal/session/session-verdict-facts.d.ts +28 -0
  296. package/dist/portal/session/session-verdict-facts.js +15 -0
  297. package/dist/portal/session/session.d.ts +10 -0
  298. package/dist/portal/session/session.js +34 -26
  299. package/dist/surface/http-server.d.ts +2 -1
  300. package/dist/surface/http-server.js +4 -2
  301. package/dist/surface/mcp/mcp-proxy.d.ts +2 -2
  302. package/dist/surface/mcp/mcp-proxy.js +5 -5
  303. package/dist/surface/mcp/mcp.js +4 -2
  304. package/dist/surface/mcp/proxy/proxy-daemon-probe.d.ts +13 -1
  305. package/dist/surface/mcp/proxy/proxy-daemon-probe.js +27 -2
  306. package/dist/surface/mcp/proxy-idle-exit.d.ts +2 -1
  307. package/dist/surface/mcp/server-instructions.js +21 -8
  308. package/dist/surface/tools/act/after-match.d.ts +17 -0
  309. package/dist/surface/tools/act/after-match.js +30 -0
  310. package/dist/surface/tools/act/already-true.d.ts +40 -0
  311. package/dist/surface/tools/act/already-true.js +36 -0
  312. package/dist/surface/tools/act/durable.d.ts +69 -0
  313. package/dist/surface/tools/act/durable.js +139 -0
  314. package/dist/surface/tools/act/navigation/navigate-arrival.d.ts +19 -6
  315. package/dist/surface/tools/act/navigation/navigate-arrival.js +60 -10
  316. package/dist/surface/tools/act/navigation/navigate-result.d.ts +1 -12
  317. package/dist/surface/tools/act/navigation/navigate-result.js +21 -3
  318. package/dist/surface/tools/act/proved-controls.d.ts +9 -0
  319. package/dist/surface/tools/act/proved-controls.js +11 -0
  320. package/dist/surface/tools/act/sequence-grade.d.ts +5 -0
  321. package/dist/surface/tools/act/sequence-grade.js +32 -1
  322. package/dist/surface/tools/act/settle-in-flight.d.ts +6 -2
  323. package/dist/surface/tools/act/settle-in-flight.js +29 -6
  324. package/dist/surface/tools/act-capsule.d.ts +7 -0
  325. package/dist/surface/tools/act-capsule.js +68 -8
  326. package/dist/surface/tools/act-sequence-tool.js +25 -8
  327. package/dist/surface/tools/act-tools.js +51 -74
  328. package/dist/surface/tools/args/numeric-bounds.d.ts +15 -2
  329. package/dist/surface/tools/args/numeric-bounds.js +30 -3
  330. package/dist/surface/tools/assert/assert-grade.js +26 -3
  331. package/dist/surface/tools/assert/assert-verdict.js +32 -6
  332. package/dist/surface/tools/browser-tools.js +17 -4
  333. package/dist/surface/tools/coverage-tools.js +27 -1
  334. package/dist/surface/tools/dynamic-tools.js +6 -0
  335. package/dist/surface/tools/error-recovery.js +4 -4
  336. package/dist/surface/tools/feedback-invite.js +6 -4
  337. package/dist/surface/tools/feedback-tools.d.ts +2 -1
  338. package/dist/surface/tools/feedback-tools.js +1 -1
  339. package/dist/surface/tools/harness-explore.d.ts +3 -2
  340. package/dist/surface/tools/harness-explore.js +7 -6
  341. package/dist/surface/tools/invoke-tool.js +17 -4
  342. package/dist/surface/tools/lease-hint.d.ts +8 -0
  343. package/dist/surface/tools/lease-hint.js +10 -0
  344. package/dist/surface/tools/lease-tools.d.ts +16 -28
  345. package/dist/surface/tools/lease-tools.js +68 -11
  346. package/dist/surface/tools/lineage-tools.js +1 -1
  347. package/dist/surface/tools/live-call-text.js +4 -1
  348. package/dist/surface/tools/observe-tools.js +23 -7
  349. package/dist/surface/tools/real-input-attempt.d.ts +0 -5
  350. package/dist/surface/tools/real-input-attempt.js +1 -1
  351. package/dist/surface/tools/tool-kit.d.ts +5 -0
  352. package/dist/surface/tools/tool-kit.js +6 -1
  353. package/dist/surface/tools/tool-surface.js +17 -5
  354. package/dist/surface/tools/tools.js +68 -29
  355. package/dist/telemetry/argument-shape.d.ts +0 -2
  356. package/dist/telemetry/argument-shape.js +0 -4
  357. package/dist/telemetry/error-class.d.ts +2 -3
  358. package/dist/telemetry/error-class.js +2 -3
  359. package/dist/telemetry/feedback-local.d.ts +1 -0
  360. package/dist/telemetry/feedback-local.js +1 -3
  361. package/dist/telemetry/feedback.d.ts +1 -1
  362. package/dist/telemetry/harness-drive.d.ts +0 -2
  363. package/dist/telemetry/harness-drive.js +0 -4
  364. package/dist/telemetry/hud-metrics.d.ts +32 -0
  365. package/dist/telemetry/hud-metrics.js +95 -0
  366. package/dist/telemetry/session-metrics.d.ts +5 -0
  367. package/dist/telemetry/session-metrics.js +15 -1
  368. package/dist/wire-journal.d.ts +34 -0
  369. package/dist/wire-journal.js +104 -0
  370. package/dist/workspace-packages.d.ts +0 -2
  371. package/dist/workspace-packages.js +1 -9
  372. package/docs/README.md +5 -2
  373. package/docs/actions.mdx +3 -3
  374. package/docs/agent-cheatsheet.md +4 -4
  375. package/docs/architecture.md +6 -6
  376. package/docs/best-practices.mdx +1 -1
  377. package/docs/capabilities.mdx +32 -1
  378. package/docs/cli/cloud.mdx +21 -9
  379. package/docs/cli/doctor.mdx +4 -0
  380. package/docs/cli/feedback.mdx +2 -2
  381. package/docs/cli/gate.mdx +9 -4
  382. package/docs/cli/init.mdx +1 -0
  383. package/docs/cli/report.mdx +44 -0
  384. package/docs/cli/update.mdx +10 -0
  385. package/docs/cli.mdx +9 -0
  386. package/docs/desktop.mdx +14 -10
  387. package/docs/environment.mdx +87 -0
  388. package/docs/faq.mdx +18 -16
  389. package/docs/first-drive.md +5 -13
  390. package/docs/flows.md +32 -2
  391. package/docs/frameworks.mdx +140 -9
  392. package/docs/getting-started.md +3 -3
  393. package/docs/http-transport.md +6 -3
  394. package/docs/human-control.md +2 -2
  395. package/docs/index.mdx +16 -6
  396. package/docs/install-agentic.mdx +37 -1
  397. package/docs/install-manual.mdx +1 -1
  398. package/docs/integration-patterns.md +3 -3
  399. package/docs/multi-agent-testing.md +1 -1
  400. package/docs/packages/babel-plugin.mdx +1 -1
  401. package/docs/packages/browser.mdx +2 -2
  402. package/docs/packages/react.mdx +2 -2
  403. package/docs/packages/tauri.mdx +1 -1
  404. package/docs/packages/vite-plugin.mdx +2 -2
  405. package/docs/packages.mdx +9 -9
  406. package/docs/predicates.mdx +176 -7
  407. package/docs/quickstart.mdx +2 -2
  408. package/docs/recipes.mdx +2 -2
  409. package/docs/state-management.mdx +2 -2
  410. package/docs/system-map.md +1 -1
  411. package/docs/telemetry.md +1 -1
  412. package/docs/token-efficiency.md +3 -3
  413. package/docs/tools/act-and-wait.mdx +1 -0
  414. package/docs/tools/act-sequence.mdx +1 -1
  415. package/docs/tools/console.mdx +1 -1
  416. package/docs/tools/inspect.mdx +1 -1
  417. package/docs/tools/network.mdx +1 -1
  418. package/docs/tools/query.mdx +11 -11
  419. package/docs/tools/sessions.mdx +1 -1
  420. package/docs/tools/snapshot.mdx +1 -1
  421. package/docs/tools/state.mdx +1 -1
  422. package/docs/tools/wait-for.mdx +1 -1
  423. package/docs/usage.md +8 -7
  424. package/docs/use-cases.mdx +157 -0
  425. package/docs/vs-screenshots.mdx +3 -3
  426. package/docs/what-is-recorded.md +62 -0
  427. package/docs/what-is-reticle.mdx +17 -20
  428. package/docs/why-reticle.mdx +14 -21
  429. package/package.json +11 -10
  430. package/dist/judgement/capsule/minimize.d.ts +0 -12
  431. package/dist/judgement/capsule/minimize.js +0 -22
  432. package/dist/judgement/outcome/predicate-to-expect.d.ts +0 -44
  433. package/dist/judgement/outcome/predicate-to-expect.js +0 -148
  434. package/dist/judgement/phenomena/phenomena.d.ts +0 -26
  435. package/dist/judgement/phenomena/phenomena.js +0 -101
  436. package/dist/language/flows/flow-report.d.ts +0 -22
  437. package/dist/language/flows/flow-report.js +0 -106
package/dist/index.js CHANGED
@@ -4,14 +4,14 @@ import { fetchPlatformConfig } from './features/harness/platform-config.js';
4
4
  import { join } from 'node:path';
5
5
  import { existsSync, readFileSync } from 'node:fs';
6
6
  import { homedir } from 'node:os';
7
- import { linkedCloudPort } from './memory/cloud/cloud-config.js';
7
+ import { linkedCloudPort, platformEnvPort } from './memory/cloud/cloud-config.js';
8
8
  import { attachCloudSync } from './memory/cloud/sync-daemon.js';
9
9
  import { wireHooks } from './hooks/hook-commands.js';
10
10
  import { PROJECT_REGISTRY_FILE, parseProjectRegistry, projectCandidates, } from '@reticlehq/core/artifacts';
11
11
  import { discoverProjectConfigs } from './command/cli/config/config-discovery.js';
12
12
  import { artifactRootResolver } from './memory/project/artifact-root-resolver.js';
13
13
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
14
- import { AGENT_STOPPED_NOTICE, RETICLE_DEFAULT_PORT, ReticleCommand, ReticleDir, ReticleEnv, LOOPBACK_HOST, ReplayStatus, EventType, } from '@reticlehq/core';
14
+ import { AGENT_STOPPED_NOTICE, RETICLE_DEFAULT_PORT, bridgeWsUrl, ReticleCommand, ReticleDir, ReticleEnv, LOOPBACK_HOST, ReplayStatus, EventType, } from '@reticlehq/core';
15
15
  import { originOf } from './portal/session/session-manager.js';
16
16
  import { setBrowserMode, BrowserMode } from './telemetry/browser-mode.js';
17
17
  import { replayAndLearn } from './language/flows/flow-learning.js';
@@ -25,6 +25,7 @@ import { SERVER_VERSION } from './command/version/identity/server-version.js';
25
25
  import { BaselineStore } from './memory/project/baselines.js';
26
26
  import { RecordingStore } from './language/flows/recording/tape/recordings.js';
27
27
  import { initImpact } from './memory/impact/impact-recorder.js';
28
+ import { flowAuthor } from './language/flows/flow-author.js';
28
29
  import { FlowStore } from './language/flows/flows.js';
29
30
  import { buildFlowChips } from './language/flows/flow-scope.js';
30
31
  import { ProjectStore } from './memory/project/project-store.js';
@@ -42,7 +43,7 @@ import { LEASE_ACQUIRE_TOOL } from './surface/tools/lease-tools.js';
42
43
  import { runTool } from './surface/tools/invoke-tool.js';
43
44
  import { SessionReaper, endAllSessions, MCP_DISCONNECT_SUMMARY, } from './portal/session/session-reaper.js';
44
45
  import { wireSessionScope } from './portal/session/no-session-watch.js';
45
- import { buildIdlePredicate } from './command/daemon/lifetime/daemon-usefulness.js';
46
+ import { buildIdlePredicate, noteSetupHold } from './command/daemon/lifetime/daemon-usefulness.js';
46
47
  import { resolveToolSurface } from './surface/tools/tool-surface.js';
47
48
  import { statusPayload } from './status-payload.js';
48
49
  import { CdpRealInputProvider, LaunchedRealInputProvider } from './portal/input/real-input.js';
@@ -51,19 +52,16 @@ import { BrowserPool } from './portal/pool/browser-pool.js';
51
52
  import { AGENT_ALREADY_DRIVING_ELSEWHERE, shouldGreetWithLeaseNotice, } from './portal/session/lease-visibility.js';
52
53
  import { playwrightLauncher, resolveMaxContexts } from './portal/pool/playwright-launcher.js';
53
54
  import { LeaseReaper } from './portal/pool/lease-reaper.js';
54
- import { readJournalEnabled, readProjectId } from './command/cli/ports/resolve/cli-port.js';
55
- import { hasProjectConnectedBefore } from './memory/recall/prior/connection-memory.js';
55
+ import { findProjectConfig, projectIdsAt, readJournalEnabled, readProjectId, } from './command/cli/ports/resolve/cli-port.js';
56
+ import { hasAnyProjectConnectedBefore } from './memory/recall/prior/connection-memory.js';
56
57
  import { reticleStateHome } from './command/daemon/daemon.js';
57
- import { probeChromium } from './command/cli/doctor/browser/chromium-hint.js';
58
- import { makeJournalAttach } from './memory/journal/attach-journal.js';
59
- import { makeSessionEnd, recordDriveRun } from './memory/journal/session-end.js';
60
- import { attachDriveRunFlush } from './memory/journal/drive-run-flush.js';
58
+ import { probeLaunchableChromium } from './launch-chromium.js';
59
+ import { attachJournal } from './wire-journal.js';
61
60
  import { reportOnboardingStep } from './telemetry/onboarding-funnel.js';
62
61
  import { AMBIENT_RECORDING } from './language/flows/recording/tape/recordings.js';
63
- import { AmbientStore } from './memory/journal/ambient-store.js';
64
- import { ensureWorkspaceGitignore } from './memory/journal/on-disk/workspace-gitignore.js';
65
- import { pruneFeedback, pruneSessions, pruneVisualDiffs, } from './memory/journal/on-disk/retention.js';
62
+ import { readRetainPolicy } from './memory/journal/on-disk/retain-policy.js';
66
63
  import { log } from './log.js';
64
+ import { readReaderBundle, zeroInstallScript as zeroInstallScriptFor, } from './portal/pool/zero-install.js';
67
65
  /** A human-facing one-liner for a panel replay verdict — ✓ passed / ⚠ drifted / ✗ errored. */
68
66
  function replayVerdictLine(result) {
69
67
  if (result.status === ReplayStatus.OK)
@@ -83,10 +81,19 @@ export { buildSessionRecommendation } from './portal/session/presence/session-re
83
81
  export { TOOLS } from './surface/tools/tools.js';
84
82
  export { createToolInvoker, UNKNOWN_TOOL_ERROR } from './surface/tools/tool-invoker.js';
85
83
  export { runTool, SESSION_BOUND_TOOLS, SESSION_EXEMPT_TOOLS } from './surface/tools/invoke-tool.js';
84
+ /**
85
+ * The port-presence check the CLI commands (`drive`, `verify`, `status`, `doctor`, `kill`) all share,
86
+ * re-exported so a consumer embedding `start()` in its own process can refuse a taken port the same
87
+ * honest way instead of letting a raw `EADDRINUSE` stack out — the gap `@reticlehq/test`'s
88
+ * `bootSession()` had (reticlehq/reticle#1141): it called `start()` directly with no pre-flight probe.
89
+ */
90
+ export { probePresence, describePresence, PortPresence, } from './command/daemon/binding/port-presence.js';
91
+ export { probeDaemon } from './surface/mcp/proxy/proxy-daemon-probe.js';
92
+ export { fetchStatus } from './command/daemon/binding/daemon-status-probe.js';
86
93
  export { BaselineStore, normalizeLines, diffLines } from './memory/project/baselines.js';
87
94
  export { RecordingStore } from './language/flows/recording/tape/recordings.js';
88
95
  export { FlowStore, recordedStepToFlowStep } from './language/flows/flows.js';
89
- export { assertSuccess, successToPredicate, dynamicTestids, successLabel, } from './language/flows/flow-success.js';
96
+ export { assertSuccess, dynamicTestids, successLabel, successToPredicate, } from './language/flows/flow-success.js';
90
97
  export { classifyFlowAssertions, FlowAssertionGrade } from './language/flows/flow-classify.js';
91
98
  export { buildDomainModel } from './judgement/domain/domain-model.js';
92
99
  export { ProjectStore } from './memory/project/project-store.js';
@@ -108,12 +115,29 @@ export { resolveBridgeSecurity } from './portal/bridge/bridge-security.js';
108
115
  * Build the shared browser pool (one headless Chromium, N capped isolated leased contexts). Lazy —
109
116
  * no Chromium launches until the first lease — so creating it is free even when never used.
110
117
  */
111
- function createBrowserPool(headless) {
118
+ /** Where the zero-install reader connects: this daemon's own bridge, with its pairing token. */
119
+ function readerConnect(port, token) {
120
+ return { url: bridgeWsUrl(port), ...(token === undefined ? {} : { token }) };
121
+ }
122
+ function createBrowserPool(headless, reader) {
112
123
  const maxContexts = resolveMaxContexts(process.env[ReticleEnv.MAX_CONTEXTS], cpus().length);
113
124
  const genSessionId = () => `lease-${'function' === typeof globalThis.crypto?.randomUUID
114
125
  ? globalThis.crypto.randomUUID()
115
126
  : String(Date.now())}`;
116
- return new BrowserPool(playwrightLauncher({ headless }), { maxContexts, genSessionId });
127
+ // Built on first use and kept: the page that needs it is one whose app never connected.
128
+ let script;
129
+ const zeroInstallScript = () => {
130
+ if (script === undefined) {
131
+ const bundle = readReaderBundle();
132
+ script = bundle === undefined ? null : zeroInstallScriptFor(bundle, reader);
133
+ }
134
+ return script ?? undefined;
135
+ };
136
+ return new BrowserPool(playwrightLauncher({ headless }), {
137
+ maxContexts,
138
+ genSessionId,
139
+ zeroInstallScript,
140
+ });
117
141
  }
118
142
  /**
119
143
  * Route CDP-authoritative network detail onto the driven session's journal.
@@ -153,82 +177,6 @@ function makeNetworkDetailRouter(bridge, driveUrl) {
153
177
  }
154
178
  };
155
179
  }
156
- /**
157
- * Wire journal capture, ambient seeding and the journal-tail flush onto a bridge.
158
- *
159
- * Both entry points need all three, and both used to hand-roll them. `startDaemon` only ever wired the
160
- * first, so on the path every user actually takes (`reticle serve` / `reticle mcp`) the journal tail was
161
- * dropped at session end and the learned ambient map was never persisted OR seeded — meaning ambient
162
- * learning could not converge across sessions and the last events of every session were lost. The two
163
- * call sites had already drifted once before, which is why this is one function rather than a
164
- * copy-paste both are asked to keep in step.
165
- */
166
- function attachJournal(bridge, deps) {
167
- const journalAttach = makeJournalAttach(deps);
168
- const ambientStore = new AmbientStore(deps.fs, deps.reticleRoot);
169
- // Built once, not per session: the resolver walks config discovery and the user-level registry,
170
- // and neither changes between two tabs connecting a second apart.
171
- const resolveArtifactRoot = artifactRootResolver(deps.reticleRoot);
172
- bridge.attachSessionCreate((session) => {
173
- // Stamp the project's own `.reticle` before ANY counter fires for this session. Without it every
174
- // verdict is recorded against wherever the daemon was started, which is how one app's evidence
175
- // reached a different account's production dashboard.
176
- // The origin is passed for the case where the page never stamped a project id: it is the only
177
- // distinguishing fact left, and without it every such app shares one bucket.
178
- session.artifactRoot = resolveArtifactRoot(session.projectId, originOf(session.url)).root;
179
- // Here rather than on the start path, which was neither the moment we were about to write into a
180
- // repository nor the root we were about to write into: it created `.reticle/` — holding nothing
181
- // but the ignore file — wherever the daemon was launched, coming back every boot after the user
182
- // deleted it, while the journals this ignore protects landed in another tree, uncovered.
183
- if (deps.enabled)
184
- void ensureWorkspaceGitignore(deps.fs, session.artifactRoot);
185
- journalAttach(session);
186
- // Seed the learned ambient map so a fresh session starts knowing which regions churn, instead of
187
- // re-learning from zero. Best-effort + async: a late seed still helps, a failure is silent.
188
- if (deps.enabled) {
189
- void ambientStore
190
- .load()
191
- .then((counts) => session.seedAmbient(counts))
192
- .catch(() => undefined);
193
- }
194
- });
195
- // Teardown: flush the journal tail to disk + persist what this session learned.
196
- bridge.attachSessionEnd(makeSessionEnd({
197
- ...deps,
198
- // Retention runs from teardown, and it must not delete the journal of a session that is still
199
- // being written. The registry is the only thing that knows which those are.
200
- liveSessionIds: () => new Set(bridge.sessions.all().map((s) => s.id)),
201
- }));
202
- /*
203
- * And the same write, DURING the session rather than only at the end of it.
204
- *
205
- * Teardown was the only writer of a run artifact, so every verdict produced while a tab stayed
206
- * open was invisible to cloud sync — which can only push artifacts that exist. Reported as "sync
207
- * is not happening" and diagnosed as the agent forgetting to sync; there is no sync command to
208
- * forget. The evidence simply was not on disk yet.
209
- *
210
- * Safe to call repeatedly because the run id is derived from the session, so this rewrites one
211
- * artifact rather than accumulating them — a property `recordDriveRun` already had, for the
212
- * unrelated reason that a reconnecting tab must not publish two overlapping rows.
213
- */
214
- if (deps.enabled) {
215
- attachDriveRunFlush({
216
- resolve: (sessionId) => bridge.sessions.get(sessionId),
217
- write: (session) => recordDriveRun(deps, session),
218
- });
219
- }
220
- if (deps.enabled) {
221
- // Empty in the ordinary case (nothing has connected yet), but this path also runs on a daemon
222
- // that is already serving sessions.
223
- void pruneSessions(deps.fs, deps.reticleRoot, {
224
- live: new Set(bridge.sessions.all().map((s) => s.id)),
225
- });
226
- // The largest thing in the workspace, and until now the only one with no delete path at all.
227
- void pruneVisualDiffs(deps.fs, deps.reticleRoot);
228
- // Write-only local copies of reports the outbox already carries.
229
- void pruneFeedback(deps.fs, deps.reticleRoot);
230
- }
231
- }
232
180
  /**
233
181
  * Resolve the drive/real-input provider from options — shared by start and startDaemon so the
234
182
  * precedence (driveUrl launch+own → CDP attach → none) and the storageState/injectConnect plumbing
@@ -325,6 +273,8 @@ function knownProjectRoots() {
325
273
  }
326
274
  return [...roots];
327
275
  }
276
+ /** This project's platform credential (stored, else the env key), as the env the platform readers take. */
277
+ const platformEnvFor = (root) => platformEnvPort(createNodeFileSystem(), root ?? join(process.cwd(), ReticleDir.ROOT), homedir(), process.env);
328
278
  export async function start(options = {}) {
329
279
  const port = options.port ?? RETICLE_DEFAULT_PORT;
330
280
  // Open the user's impact record before anything can connect. Not inside the MCP branch: a daemon
@@ -335,7 +285,7 @@ export async function start(options = {}) {
335
285
  // The daemon owns both sides of this seam, so it is the layer that may join them: the cache
336
286
  // lives in cloud memory, the platform read lives in the harness feature, and neither is allowed
337
287
  // to reach for the other.
338
- config: harnessConfigSource(() => fetchPlatformConfig(process.env)),
288
+ config: harnessConfigSource(async () => fetchPlatformConfig(await platformEnvFor(options.reticleRoot)())),
339
289
  });
340
290
  const uninstallHooks = wireHooks(options.reticleRoot ?? join(process.cwd(), ReticleDir.ROOT), readProjectId(process.cwd()));
341
291
  const security = await resolveBridgeSecurityWithAutoToken(options);
@@ -364,21 +314,26 @@ export async function start(options = {}) {
364
314
  const fs = createNodeFileSystem();
365
315
  const reticleRoot = options.reticleRoot ?? join(process.cwd(), ReticleDir.ROOT);
366
316
  const now = options.now ?? (() => Date.now());
367
- const journalEnabled = readJournalEnabled(process.cwd(), process.env[ReticleEnv.JOURNAL]);
317
+ const retain = readRetainPolicy(findProjectConfig(process.cwd()));
318
+ // `retain.sessions: 0` says keep no journals. Honoured at CAPTURE as well as at the sweep —
319
+ // writing a directory in order to delete it on the way out is work nobody asked for.
320
+ const journalEnabled = readJournalEnabled(process.cwd(), process.env[ReticleEnv.JOURNAL]) && 0 < retain.sessions;
368
321
  const flows = new FlowStore(fs, reticleRoot, { now });
369
322
  attachJournal(bridge, {
370
323
  fs,
371
324
  reticleRoot,
372
325
  enabled: journalEnabled,
326
+ retain,
373
327
  // Bound HERE, where the recorder is already in hand: teardown gets the tape, not the recorder.
374
328
  takeAmbientTape: () => recordings.stop(AMBIENT_RECORDING),
375
329
  reportStep: reportOnboardingStep,
376
330
  flows,
331
+ author: flowAuthor,
377
332
  });
378
333
  const project = new ProjectStore(fs, reticleRoot, { now });
379
334
  attachRouteLearning(bridge, projectStoreResolver(fs, project, reticleRoot, now));
380
335
  const annotations = new AnnotationStore();
381
- pool = createBrowserPool(options.headless ?? true);
336
+ pool = createBrowserPool(options.headless ?? true, readerConnect(port, security.token));
382
337
  leaseReaper = new LeaseReaper(pool);
383
338
  leaseReaper.start();
384
339
  /*
@@ -410,16 +365,23 @@ export async function start(options = {}) {
410
365
  project,
411
366
  fs,
412
367
  reticleRoot,
368
+ linkedCloud: linkedCloudPort(fs, reticleRoot, homedir(), process.env),
413
369
  artifactRootFor: artifactRootResolver(reticleRoot),
414
370
  now,
415
371
  bridgePort: port,
416
- browserProbe: probeChromium,
372
+ browserProbe: probeLaunchableChromium,
417
373
  // The daemon's OWN project, so a tool can tell "this session is mine" from "this session
418
374
  // belongs to a sibling app under the same daemon". contract_save refuses on the second.
419
375
  ...(activeProjectId === undefined ? {} : { projectId: activeProjectId }),
420
376
  };
421
377
  const profile = resolveToolSurface(options.toolProfile);
422
- const server = createMcpServer(realInput !== undefined ? { ...deps, realInput } : deps, profile, hasProjectConnectedBefore(reticleStateHome(), port, activeProjectId));
378
+ const server = createMcpServer(realInput !== undefined ? { ...deps, realInput } : deps, profile,
379
+ // A session live RIGHT NOW is stronger evidence than the durable memory, which can be empty or
380
+ // stale for a project the plugin wired without writing `.reticle.json` (see connection-memory.ts's
381
+ // KNOWN LIMIT). Without this OR, that project's `initialize` led with the first-install steps
382
+ // while a real session was already connected — see #1138.
383
+ bridge.sessions.count() > 0 ||
384
+ hasAnyProjectConnectedBefore(reticleStateHome(), port, projectIdsAt(process.cwd())));
423
385
  // When the agent (the MCP client) disconnects cleanly, end every active session at once so the
424
386
  // HUD doesn't linger. (If the agent instead KILLS this process, the WS dies and the browser
425
387
  // self-ends via SESSION_LIFECYCLE.BRIDGE_LOST_MS — see transport.ts.)
@@ -462,7 +424,7 @@ export async function startDaemon(options = {}) {
462
424
  // The daemon owns both sides of this seam, so it is the layer that may join them: the cache
463
425
  // lives in cloud memory, the platform read lives in the harness feature, and neither is allowed
464
426
  // to reach for the other.
465
- config: harnessConfigSource(() => fetchPlatformConfig(process.env)),
427
+ config: harnessConfigSource(async () => fetchPlatformConfig(await platformEnvFor(options.reticleRoot)())),
466
428
  });
467
429
  const security = await resolveBridgeSecurityWithAutoToken(options);
468
430
  const shared = createSharedServer(security.token === undefined ? {} : { token: security.token });
@@ -478,7 +440,12 @@ export async function startDaemon(options = {}) {
478
440
  // answers WHY rather than just "sessionCount: 0".
479
441
  // `verifyPort` rides along so a later `serve --http` can tell whether this daemon already honours
480
442
  // the requested `--http-port` instead of silently ignoring the flag (#687).
481
- shared.attachStatus(() => statusPayload(bridge.sessions.count(), bridge.sessions.list(), bridge.sessions.noSessionHint(), verifyHttp?.port, bridge.sessions.noSessionLead()));
443
+ shared.attachStatus((held) => {
444
+ // `init` holds the daemon it is waiting on, so the idle rule cannot shut it down mid-wait.
445
+ if (held)
446
+ noteSetupHold(Date.now());
447
+ return statusPayload(bridge.sessions.count(), bridge.sessions.list(), bridge.sessions.noSessionHint(), verifyHttp?.port, bridge.sessions.noSessionLead());
448
+ });
482
449
  // Agent-independent presence: the daemon outlives any single agent, so when the LAST agent's MCP
483
450
  // connection drops (it stopped, or is waiting on the human), end every session and push a clear
484
451
  // "go to your terminal" notice to the panel — the human is on the browser and must not lose a typed
@@ -497,7 +464,9 @@ export async function startDaemon(options = {}) {
497
464
  const fs = createNodeFileSystem();
498
465
  const reticleRoot = options.reticleRoot ?? join(process.cwd(), ReticleDir.ROOT);
499
466
  const now = options.now ?? (() => Date.now());
500
- const journalEnabled = readJournalEnabled(process.cwd(), process.env[ReticleEnv.JOURNAL]);
467
+ const retain = readRetainPolicy(findProjectConfig(process.cwd()));
468
+ // See the sibling path: `retain.sessions: 0` stops the capture too, not only the sweep.
469
+ const journalEnabled = readJournalEnabled(process.cwd(), process.env[ReticleEnv.JOURNAL]) && 0 < retain.sessions;
501
470
  const flows = new FlowStore(fs, reticleRoot, { now });
502
471
  // Built here rather than inside `deps` below, so teardown can save what a drive recorded. Both
503
472
  // paths pass the same pair — `daemon-parity.test.ts` is what keeps them from drifting apart.
@@ -514,15 +483,17 @@ export async function startDaemon(options = {}) {
514
483
  fs,
515
484
  reticleRoot,
516
485
  enabled: journalEnabled,
486
+ retain,
517
487
  takeAmbientTape: () => recordings.stop(AMBIENT_RECORDING),
518
488
  reportStep: reportOnboardingStep,
519
489
  flows,
490
+ author: flowAuthor,
520
491
  onRunPersisted: () => syncNudge.run?.(),
521
492
  });
522
493
  const project = new ProjectStore(fs, reticleRoot, { now });
523
494
  attachRouteLearning(bridge, projectStoreResolver(fs, project, reticleRoot, now));
524
495
  const annotations = new AnnotationStore();
525
- const pool = createBrowserPool(options.headless ?? true);
496
+ const pool = createBrowserPool(options.headless ?? true, readerConnect(port, security.token));
526
497
  const leaseReaper = new LeaseReaper(pool);
527
498
  leaseReaper.start();
528
499
  /*
@@ -556,7 +527,7 @@ export async function startDaemon(options = {}) {
556
527
  // console and the panel cannot disagree about a setting they both offer. Nothing is awaited and a
557
528
  // failure is not surfaced: the next snapshot re-reads the platform, so a lost write shows up as
558
529
  // the switch springing back, which is the truthful outcome.
559
- bridge.attachHarnessRequest((enabled) => void writeHarnessSwitch(process.env, enabled));
530
+ bridge.attachHarnessRequest((enabled) => void platformEnvFor(reticleRoot)().then((env) => writeHarnessSwitch(env, enabled)));
560
531
  // Scope auto-selection to the active project (from .reticle.json) so a stray tab from another app is
561
532
  // never picked when the agent omits a sessionId. Explicit per-call scope/sessionId still overrides.
562
533
  // Scope + the no-session diagnosis: "no browser session connected" is the error that ends most
@@ -588,7 +559,7 @@ export async function startDaemon(options = {}) {
588
559
  artifactRootFor: artifactRootResolver(reticleRoot),
589
560
  now,
590
561
  bridgePort: port,
591
- browserProbe: probeChromium,
562
+ browserProbe: probeLaunchableChromium,
592
563
  // A finished verification should not sit behind a one-minute timer — see ToolDeps.onRunPersisted.
593
564
  onRunPersisted: () => cloudSync.nudge(),
594
565
  };
@@ -596,7 +567,10 @@ export async function startDaemon(options = {}) {
596
567
  const effectiveDeps = realInput !== undefined ? { ...deps, realInput } : deps;
597
568
  // Read per attach, not once: a project that gets wired while this daemon is alive should stop
598
569
  // being told to wire itself on the next agent that connects.
599
- shared.attachMcp(() => createMcpServer(effectiveDeps, profile, hasProjectConnectedBefore(reticleStateHome(), port, readProjectId(process.cwd()))));
570
+ shared.attachMcp(() => createMcpServer(effectiveDeps, profile,
571
+ // See the sibling call in `start`: a live session outweighs empty/stale durable memory (#1138).
572
+ bridge.sessions.count() > 0 ||
573
+ hasAnyProjectConnectedBefore(reticleStateHome(), port, projectIdsAt(process.cwd()))));
600
574
  // `reticle drive <url>` when this daemon already owns the port: it asks HERE instead of trying to
601
575
  // bind a port we are holding, and gets the same pooled context an agent's reticle_lease returns —
602
576
  // through runTool, so it is counted and reported like any other call rather than being a second,
@@ -698,7 +672,7 @@ export async function startDaemon(options = {}) {
698
672
  if (vh !== undefined)
699
673
  await new Promise((resolve) => vh.server.close(() => resolve()));
700
674
  leaseReaper.stop();
701
- cloudSync.stop();
675
+ await cloudSync.flush(); // not stop(): the last run written is the one nobody has yet
702
676
  await loopbackAlias.close?.();
703
677
  await pool.shutdown();
704
678
  await owned?.dispose();
@@ -29,6 +29,18 @@ declare const CapsuleSchema: z.ZodObject<{
29
29
  observed: z.ZodString;
30
30
  /** Minimal steps to reproduce (prefix-trimmed). */
31
31
  steps: z.ZodArray<z.ZodType<FlowStep, z.ZodTypeDef, FlowStep>, "many">;
32
+ /**
33
+ * How many times this exact failure has been captured, and when it last was.
34
+ *
35
+ * A repeat is not a new capsule -- it is the same one, again. "This failed 37 times, most
36
+ * recently at T" is strictly MORE than 37 files each holding one timestamp, so folding the
37
+ * duplicates in adds information rather than discarding it.
38
+ *
39
+ * Optional, because a capsule written before these existed has neither and must not be read as
40
+ * though it were seen once when nobody counted.
41
+ */
42
+ seen: z.ZodOptional<z.ZodNumber>;
43
+ lastSeenAt: z.ZodOptional<z.ZodNumber>;
32
44
  }, "strip", z.ZodTypeAny, {
33
45
  expected: string;
34
46
  origin: string;
@@ -38,6 +50,8 @@ declare const CapsuleSchema: z.ZodObject<{
38
50
  createdAt: number;
39
51
  observed: string;
40
52
  flow?: string | undefined;
53
+ seen?: number | undefined;
54
+ lastSeenAt?: number | undefined;
41
55
  }, {
42
56
  expected: string;
43
57
  origin: string;
@@ -47,6 +61,8 @@ declare const CapsuleSchema: z.ZodObject<{
47
61
  createdAt: number;
48
62
  observed: string;
49
63
  flow?: string | undefined;
64
+ seen?: number | undefined;
65
+ lastSeenAt?: number | undefined;
50
66
  }>;
51
67
  export type Capsule = z.infer<typeof CapsuleSchema>;
52
68
  /** Capsule ids are filenames — refuse anything that could escape the capsules directory. */
@@ -63,8 +79,21 @@ export declare class CapsuleStore {
63
79
  /** Every readable capsule, newest-first — what `reticle capsules` prints. */
64
80
  all(): Promise<Capsule[]>;
65
81
  }
66
- /** Deterministic, path-safe capsule id: time-ordered so `list` sorts newest-first for free. */
67
- export declare function capsuleId(now: number, label: string): string;
82
+ /**
83
+ * What makes two captures the SAME failure.
84
+ *
85
+ * Everything that describes the failure, and nothing that describes the capture: `createdAt` is
86
+ * excluded because it is precisely what turned every retry of a broken step into a new file, and
87
+ * `id` because it is derived from this. `flow` is included -- the same assertion failing in two
88
+ * different journeys is two findings, not one.
89
+ */
90
+ export declare function capsuleFingerprint(capsule: Omit<Capsule, 'id' | 'createdAt' | 'seen' | 'lastSeenAt'>): string;
91
+ /**
92
+ * Deterministic, path-safe capsule id: time-ordered so `list` sorts newest-first for free, with the
93
+ * fingerprint on the END so a duplicate can be found by listing the directory rather than reading
94
+ * every file in it.
95
+ */
96
+ export declare function capsuleId(now: number, label: string, fingerprint: string): string;
68
97
  /** The minimal reproduction: the steps up to and including the one that failed (prefix trim). */
69
98
  export declare function minimalSteps(steps: readonly FlowStep[], failedIndex: number): FlowStep[];
70
99
  export {};
@@ -1,4 +1,6 @@
1
1
  import { join } from 'node:path';
2
+ import { fnv1a } from '@reticlehq/core';
3
+ import { writeFileAtomic } from '../../memory/project/fs/write-atomic.js';
2
4
  import { z } from 'zod';
3
5
  import { FlowStepSchema } from '@reticlehq/core';
4
6
  import { reticleDirPaths } from '../../memory/project/dir/reticle-dir.js';
@@ -30,6 +32,18 @@ const CapsuleSchema = z.object({
30
32
  observed: z.string(),
31
33
  /** Minimal steps to reproduce (prefix-trimmed). */
32
34
  steps: z.array(FlowStepSchema),
35
+ /**
36
+ * How many times this exact failure has been captured, and when it last was.
37
+ *
38
+ * A repeat is not a new capsule -- it is the same one, again. "This failed 37 times, most
39
+ * recently at T" is strictly MORE than 37 files each holding one timestamp, so folding the
40
+ * duplicates in adds information rather than discarding it.
41
+ *
42
+ * Optional, because a capsule written before these existed has neither and must not be read as
43
+ * though it were seen once when nobody counted.
44
+ */
45
+ seen: z.number().int().positive().optional(),
46
+ lastSeenAt: z.number().optional(),
33
47
  });
34
48
  /** Capsule ids are filenames — refuse anything that could escape the capsules directory. */
35
49
  export function isValidCapsuleId(id) {
@@ -51,9 +65,26 @@ export class CapsuleStore {
51
65
  return false;
52
66
  try {
53
67
  await this.#fs.mkdir(this.#dir);
54
- const tmp = `${this.#pathFor(capsule.id)}.tmp`;
55
- await this.#fs.writeFile(tmp, `${JSON.stringify(capsule, null, 2)}\n`);
56
- await this.#fs.rename(tmp, this.#pathFor(capsule.id));
68
+ /*
69
+ * A capture of a failure already on disk folds into it rather than adding a file. Found by
70
+ * LISTING: the fingerprint is the id's suffix, so this costs one directory read instead of
71
+ * one read per capsule. An id written before fingerprints existed simply never matches, which
72
+ * is the correct outcome -- it is left exactly as it was.
73
+ *
74
+ * `createdAt` keeps the FIRST sighting. A repeat is the same failure happening again, not a
75
+ * new one, and the interesting pair is when it started and when it last happened.
76
+ */
77
+ const fingerprint = capsuleFingerprint(capsule);
78
+ const existingId = (await this.list()).find((id) => id.endsWith(`-${fingerprint}`));
79
+ const previous = existingId === undefined ? undefined : await this.read(existingId);
80
+ const folded = previous === undefined
81
+ ? { ...capsule, seen: 1, lastSeenAt: capsule.createdAt }
82
+ : {
83
+ ...previous,
84
+ seen: (previous.seen ?? 1) + 1,
85
+ lastSeenAt: capsule.createdAt,
86
+ };
87
+ await writeFileAtomic(this.#fs, this.#pathFor(folded.id), `${JSON.stringify(folded, null, 2)}\n`);
57
88
  return true;
58
89
  }
59
90
  catch {
@@ -100,10 +131,31 @@ export class CapsuleStore {
100
131
  return out;
101
132
  }
102
133
  }
103
- /** Deterministic, path-safe capsule id: time-ordered so `list` sorts newest-first for free. */
104
- export function capsuleId(now, label) {
134
+ /**
135
+ * What makes two captures the SAME failure.
136
+ *
137
+ * Everything that describes the failure, and nothing that describes the capture: `createdAt` is
138
+ * excluded because it is precisely what turned every retry of a broken step into a new file, and
139
+ * `id` because it is derived from this. `flow` is included -- the same assertion failing in two
140
+ * different journeys is two findings, not one.
141
+ */
142
+ export function capsuleFingerprint(capsule) {
143
+ return fnv1a(JSON.stringify([
144
+ capsule.flow ?? '',
145
+ capsule.origin,
146
+ capsule.expected,
147
+ capsule.observed,
148
+ capsule.steps,
149
+ ]));
150
+ }
151
+ /**
152
+ * Deterministic, path-safe capsule id: time-ordered so `list` sorts newest-first for free, with the
153
+ * fingerprint on the END so a duplicate can be found by listing the directory rather than reading
154
+ * every file in it.
155
+ */
156
+ export function capsuleId(now, label, fingerprint) {
105
157
  const safe = label.replace(/[^a-zA-Z0-9._-]/g, '-').slice(0, 40);
106
- return `${String(now)}-${safe.length > 0 ? safe : 'capsule'}`;
158
+ return `${String(now)}-${safe.length > 0 ? safe : 'capsule'}-${fingerprint}`;
107
159
  }
108
160
  /** The minimal reproduction: the steps up to and including the one that failed (prefix trim). */
109
161
  export function minimalSteps(steps, failedIndex) {
@@ -1,4 +1,5 @@
1
1
  import { ConsequenceKind, EventType } from '@reticlehq/core';
2
+ import { isDocumentInitiated } from '@reticlehq/engine/question/predicate/predicate-eval.js';
2
3
  import { causalSummary } from './causal-summary.js';
3
4
  import { firstDivergence } from './divergence.js';
4
5
  /**
@@ -34,6 +35,10 @@ export function blastRadius(expected, observed) {
34
35
  continue;
35
36
  if (declaredUrls.some((fragment) => url.includes(fragment)))
36
37
  continue;
38
+ // A subresource the document fetched is the page loading, not a request the action sent: one
39
+ // navigation is a couple of hundred of them. IPC and beacons carry an initiator too, and stay.
40
+ if (isDocumentInitiated(event))
41
+ continue;
37
42
  const method = event.data['method'];
38
43
  add(`net ${'string' === typeof method ? method : 'request'} ${url}`);
39
44
  continue;
@@ -63,6 +63,18 @@ export interface CausalSummary {
63
63
  elided?: Partial<Record<CappedList, number>>;
64
64
  route?: string;
65
65
  signals: string[];
66
+ /**
67
+ * Form fields whose value moved in this window, by NAME.
68
+ *
69
+ * Names only, never values: a form is where somebody types their password, and the observer that
70
+ * reports this already refuses to carry one. The names are what an assertion is written against,
71
+ * and a field moving when nothing should have touched it (or not moving when something should)
72
+ * is the silently-cleared-field class — which until this channel existed left no trace at all,
73
+ * because React sets the input PROPERTY and MutationObserver watches attributes.
74
+ *
75
+ * Present only when a field moved, so a page with no form pays nothing.
76
+ */
77
+ fieldsChanged?: string[];
66
78
  layoutShift?: number;
67
79
  longTasks: number;
68
80
  /**
@@ -114,6 +126,6 @@ interface SummaryContext {
114
126
  */
115
127
  export declare const MAX_SUMMARY_ENTRIES = 20;
116
128
  /** The lists that are capped, named so `elided` can say which one lost entries. */
117
- type CappedList = 'statePathsChanged' | 'storageKeysChanged' | 'stateDiffs' | 'storageDiffs' | 'signals';
129
+ type CappedList = 'statePathsChanged' | 'storageKeysChanged' | 'stateDiffs' | 'storageDiffs' | 'signals' | 'fieldsChanged';
118
130
  export declare function causalSummary(events: readonly ReticleEvent[], context?: SummaryContext): CausalSummary;
119
131
  export {};
@@ -69,6 +69,7 @@ export function causalSummary(events, context = {}) {
69
69
  const storageDiffs = [];
70
70
  let route;
71
71
  const signals = [];
72
+ const fieldsChanged = [];
72
73
  let layoutShift;
73
74
  let longTasks = 0;
74
75
  for (const event of events) {
@@ -131,6 +132,9 @@ export function causalSummary(events, context = {}) {
131
132
  case EventType.SIGNAL:
132
133
  pushUnique(signals, data['name']);
133
134
  break;
135
+ case EventType.FIELD_CHANGE:
136
+ pushUnique(fieldsChanged, data['field']);
137
+ break;
134
138
  case EventType.PERF:
135
139
  if (data['metric'] === PerfMetric.CLS && 'number' === typeof data['value']) {
136
140
  layoutShift = Math.max(layoutShift ?? 0, data['value']);
@@ -163,6 +167,9 @@ export function causalSummary(events, context = {}) {
163
167
  storageDiffs: cap(storageDiffs, 'storageDiffs', elided),
164
168
  ...(route === undefined ? {} : { route }),
165
169
  signals: cap(signals, 'signals', elided),
170
+ ...(0 === fieldsChanged.length
171
+ ? {}
172
+ : { fieldsChanged: cap(fieldsChanged, 'fieldsChanged', elided) }),
166
173
  ...(0 === Object.keys(elided).length ? {} : { elided }),
167
174
  ...(layoutShift === undefined ? {} : { layoutShift }),
168
175
  longTasks,