@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
@@ -3,7 +3,7 @@ import { notePendingNoSessionReason } from '../../telemetry/tool-refused.js';
3
3
  import { declareDrivenRedactionKeys, forgetDrivenRedactionKeys, } from '../input/driven-redaction.js';
4
4
  import { Session } from './session.js';
5
5
  import { AttachmentHistory } from './presence/attachment-history.js';
6
- import { pickDocumentSuccessor } from './session-successor.js';
6
+ import { openedBeforeDeparture, pickDocumentSuccessor, } from './session-successor.js';
7
7
  import { SESSION_DISCONNECTED_REASON } from './facts/session-replaced.js';
8
8
  /** The scheme://host:port of a URL, or undefined if it can't be parsed. Used to compare origins. */
9
9
  export function originOf(url) {
@@ -87,6 +87,8 @@ export class SessionManager {
87
87
  * id forgets its tombstone — the live session is the answer then.
88
88
  */
89
89
  #tombstones = new Map();
90
+ /** The tabs already open when each tombstoned session departed: siblings, never its successor. */
91
+ #siblingsAtDeparture = new Map();
90
92
  /**
91
93
  * The active project's scope, set once from the daemon's .reticle.json. When a tool resolves a session
92
94
  * without passing its own scope, this is applied — so auto-selection is project-scoped by default
@@ -128,6 +130,7 @@ export class SessionManager {
128
130
  declareDrivenRedactionKeys(session.id, session.redactKeys);
129
131
  this.#attachment.attached(session.id);
130
132
  this.#tombstones.delete(session.id);
133
+ this.#siblingsAtDeparture.delete(session.id);
131
134
  return previous;
132
135
  }
133
136
  /**
@@ -158,6 +161,9 @@ export class SessionManager {
158
161
  return;
159
162
  if (this.#tombstones.has(session.id))
160
163
  this.#tombstones.delete(session.id);
164
+ this.#siblingsAtDeparture.set(session.id, new Set([...this.#sessions.values()]
165
+ .filter((s) => s !== session && openedBeforeDeparture(s, session))
166
+ .map((s) => s.id)));
161
167
  this.#tombstones.set(session.id, {
162
168
  id: session.id,
163
169
  url: session.url,
@@ -168,13 +174,17 @@ export class SessionManager {
168
174
  if (oldest === undefined)
169
175
  break;
170
176
  this.#tombstones.delete(oldest);
177
+ this.#siblingsAtDeparture.delete(oldest);
171
178
  }
172
179
  }
173
180
  #successorOf(sessionId) {
174
181
  const departed = this.#tombstones.get(sessionId);
175
182
  if (departed === undefined)
176
183
  return undefined;
177
- const picked = pickDocumentSuccessor(this.all().map((s) => ({
184
+ const siblings = this.#siblingsAtDeparture.get(sessionId);
185
+ const picked = pickDocumentSuccessor(this.all()
186
+ .filter((s) => true !== siblings?.has(s.id))
187
+ .map((s) => ({
178
188
  id: s.id,
179
189
  url: s.url,
180
190
  ...(s.projectId === undefined ? {} : { projectId: s.projectId }),
@@ -186,6 +196,17 @@ export class SessionManager {
186
196
  get(sessionId) {
187
197
  return this.#sessions.get(sessionId);
188
198
  }
199
+ /**
200
+ * Did this id belong to a session that has since disconnected?
201
+ *
202
+ * `resolve` answers a departed id with its live SUCCESSOR, which is what keeps a reload working
203
+ * for everything that observes. A caller that is about to do something DESTRUCTIVE needs the
204
+ * other answer: whether the thing it named is still there. Ending a session it did not name is
205
+ * not a recoverable mistake (#983).
206
+ */
207
+ departed(sessionId) {
208
+ return this.#tombstones.get(sessionId);
209
+ }
189
210
  list() {
190
211
  return [...this.#sessions.values()].map((s) => {
191
212
  const info = s.info();
@@ -304,9 +325,15 @@ export class SessionManager {
304
325
  everConnected() {
305
326
  return this.#everConnected;
306
327
  }
307
- /** Record a bridge-initiated close so `resolve` can explain a session that vanished. */
308
- noteClosure(reason, at) {
309
- this.#recentClosures.push({ at, reason });
328
+ /**
329
+ * Record a bridge-initiated close so `resolve` can explain a session that vanished.
330
+ *
331
+ * `page` is what the refused hello said about itself, when it said anything. A refusal is the one
332
+ * piece of positive evidence the no-session diagnosis has, and "a page was refused" without WHICH
333
+ * page leaves the reader unable to tell their app from another project's tab on the same port.
334
+ */
335
+ noteClosure(reason, at, page) {
336
+ this.#recentClosures.push({ at, reason, ...(page === undefined ? {} : { page }) });
310
337
  if (this.#recentClosures.length > MAX_REMEMBERED_CLOSURES)
311
338
  this.#recentClosures.shift();
312
339
  this.#connectedSinceLastClosure = false;
@@ -491,6 +518,16 @@ export class SessionManager {
491
518
  const allThrottled = bestScore >= 1;
492
519
  const RECENCY_GAP_MS = allThrottled ? 0 : 1_000;
493
520
  const clearWinner = runnerUp === undefined || best.ms + RECENCY_GAP_MS < runnerUp.ms;
521
+ // Healthy tabs heartbeat on the same cadence, so the recency gap between two of them is noise.
522
+ // When exactly one has the window focus it is the one a human is looking at: tabs piled up one
523
+ // per `init` run, and every tool called without a sessionId was refused while one of them was
524
+ // plainly the tab in front. Only among healthy ones — focus says nothing about a wedged tab.
525
+ const focused = allThrottled ? [] : candidates.filter((x) => x.s.health().focused);
526
+ const [onlyFocused, secondFocused] = focused;
527
+ if (!clearWinner && onlyFocused !== undefined && secondFocused === undefined) {
528
+ onlyFocused.s.markAgentActivity();
529
+ return onlyFocused.s;
530
+ }
494
531
  if (!clearWinner) {
495
532
  // Ambiguous: list sessions with their health so the agent can choose.
496
533
  const detail = all
@@ -22,6 +22,34 @@ export declare function composeEndedNotice(base: string, undelivered: string[]):
22
22
  * not a crash, since the agent is still connected and revives on its next action. Returns the ids.
23
23
  */
24
24
  export declare function reapIdleSessions(sessions: SessionManager): string[];
25
+ /**
26
+ * Drop sessions that have ENDED, gone quiet and lost their socket, so a listing stops offering them.
27
+ *
28
+ * `reticle_session { action: "end" }` sets state and nothing else -- it never calls
29
+ * `sessions.remove`. `SessionManager.list()` does not filter by state, and `reapIdleSessions`
30
+ * above skips ended sessions by design. The only code path in the whole server that removes a
31
+ * session is the socket `close` handler in bridge.ts. So an ended session whose socket is already
32
+ * gone has nothing left that can remove it, and the row is listed for the life of the daemon
33
+ * (#938).
34
+ *
35
+ * That is what the field report describes: `{ ended: true }` three times, the row back on the next
36
+ * `reticle_sessions` call each time, still there after every tab on the origin was closed, 17 hours
37
+ * attached. And Reticle's own advice for a stale session is
38
+ * `Call reticle_session{action:"end"} to free this session` -- which is the one thing that does not
39
+ * free it.
40
+ *
41
+ * Deliberately NOT "remove on end". The panel is client-side, so `setState` has already pushed the
42
+ * ended state to the browser and the HUD keeps it; but `end` is documented idempotent, and removing
43
+ * immediately would make a second call fail to resolve. Waiting for the session to go quiet keeps
44
+ * that, keeps a just-ended session inspectable, and still collects the case that actually hurts: a
45
+ * row nobody can get rid of.
46
+ *
47
+ * Only once the socket is no longer open. An ended session whose tab is still connected but quiet
48
+ * belongs to a page that holds a live connection; dropping the record would orphan that socket,
49
+ * leaving a connection the daemon no longer tracks. Such a session is still removed the ordinary
50
+ * way, by the `close` handler, when the tab goes.
51
+ */
52
+ export declare function reapEndedSessions(sessions: SessionManager, staleAfterMs?: number): string[];
25
53
  /** End every active session immediately (the agent / MCP client disconnected → WARN). Returns ended ids. */
26
54
  export declare function endAllSessions(sessions: SessionManager, reason: string): string[];
27
55
  /**
@@ -1,4 +1,4 @@
1
- import { PresenterTone, SESSION_LIFECYCLE, UNDELIVERED_NOTES_LABEL } from '@reticlehq/core';
1
+ import { PresenterTone, SESSION_LEASE, SESSION_LIFECYCLE, UNDELIVERED_NOTES_LABEL, } from '@reticlehq/core';
2
2
  import { log } from '../../log.js';
3
3
  /**
4
4
  * Server-authoritative session liveness. The browser idle timer is throttled in a backgrounded tab
@@ -51,6 +51,50 @@ export function reapIdleSessions(sessions) {
51
51
  log('session_reaped_idle', { sessions: ended });
52
52
  return ended;
53
53
  }
54
+ /**
55
+ * Drop sessions that have ENDED, gone quiet and lost their socket, so a listing stops offering them.
56
+ *
57
+ * `reticle_session { action: "end" }` sets state and nothing else -- it never calls
58
+ * `sessions.remove`. `SessionManager.list()` does not filter by state, and `reapIdleSessions`
59
+ * above skips ended sessions by design. The only code path in the whole server that removes a
60
+ * session is the socket `close` handler in bridge.ts. So an ended session whose socket is already
61
+ * gone has nothing left that can remove it, and the row is listed for the life of the daemon
62
+ * (#938).
63
+ *
64
+ * That is what the field report describes: `{ ended: true }` three times, the row back on the next
65
+ * `reticle_sessions` call each time, still there after every tab on the origin was closed, 17 hours
66
+ * attached. And Reticle's own advice for a stale session is
67
+ * `Call reticle_session{action:"end"} to free this session` -- which is the one thing that does not
68
+ * free it.
69
+ *
70
+ * Deliberately NOT "remove on end". The panel is client-side, so `setState` has already pushed the
71
+ * ended state to the browser and the HUD keeps it; but `end` is documented idempotent, and removing
72
+ * immediately would make a second call fail to resolve. Waiting for the session to go quiet keeps
73
+ * that, keeps a just-ended session inspectable, and still collects the case that actually hurts: a
74
+ * row nobody can get rid of.
75
+ *
76
+ * Only once the socket is no longer open. An ended session whose tab is still connected but quiet
77
+ * belongs to a page that holds a live connection; dropping the record would orphan that socket,
78
+ * leaving a connection the daemon no longer tracks. Such a session is still removed the ordinary
79
+ * way, by the `close` handler, when the tab goes.
80
+ */
81
+ export function reapEndedSessions(sessions, staleAfterMs = SESSION_LEASE.STALE_AFTER_MS) {
82
+ const dropped = [];
83
+ // Snapshot first: `remove` mutates the map this iterates.
84
+ for (const session of [...sessions.all()]) {
85
+ if (!session.isEnded())
86
+ continue;
87
+ if (session.socketOpen())
88
+ continue;
89
+ if (session.staleMs() < staleAfterMs)
90
+ continue;
91
+ if (sessions.remove(session))
92
+ dropped.push(session.id);
93
+ }
94
+ if (dropped.length > 0)
95
+ log('session_reaped_ended', { sessions: dropped });
96
+ return dropped;
97
+ }
54
98
  /** End every active session immediately (the agent / MCP client disconnected → WARN). Returns ended ids. */
55
99
  export function endAllSessions(sessions, reason) {
56
100
  const ended = [];
@@ -81,6 +125,10 @@ export class SessionReaper {
81
125
  return;
82
126
  this.#timer = setInterval(() => {
83
127
  reapIdleSessions(this.#sessions);
128
+ // Second rule, same sweep, kept as its own function: one ends a live session that went
129
+ // quiet, the other collects one that already ended. Merging them would put "end this" and
130
+ // "forget this" behind a single condition.
131
+ reapEndedSessions(this.#sessions);
84
132
  }, this.#intervalMs);
85
133
  this.#timer.unref();
86
134
  }
@@ -26,6 +26,17 @@ export interface SuccessorClock {
26
26
  * A same-id reconnect is a successor: the id survived in sessionStorage, the document did not.
27
27
  */
28
28
  export declare function pickDocumentSuccessor(live: readonly SessionIdentity[], departed: SessionIdentity): SessionIdentity | undefined;
29
+ /**
30
+ * Whether `candidate` was already connected before the agent last drove `departed`.
31
+ *
32
+ * Such a tab is a SIBLING, never the document the departed tab's navigation produced: that document
33
+ * can only have connected after the command that caused it. Without this, two tabs of one project
34
+ * were resolved as "unique at the origin" in the gap before the departed tab came back, and a verdict
35
+ * for tab A was graded on tab B (#983, reproduced by driving). Measured as ages rather than instants,
36
+ * so both sides read the same clock at the same moment and the comparison does not drift while a
37
+ * caller polls.
38
+ */
39
+ export declare function openedBeforeDeparture(candidate: Pick<Session, 'staleMs'>, departed: Pick<Session, 'agentIdleMs'>): boolean;
29
40
  export interface SuccessorRegistry {
30
41
  get(id: string): Session | undefined;
31
42
  all(): readonly Session[];
@@ -100,6 +100,19 @@ export function pickDocumentSuccessor(live, departed) {
100
100
  return undefined;
101
101
  return others[0];
102
102
  }
103
+ /**
104
+ * Whether `candidate` was already connected before the agent last drove `departed`.
105
+ *
106
+ * Such a tab is a SIBLING, never the document the departed tab's navigation produced: that document
107
+ * can only have connected after the command that caused it. Without this, two tabs of one project
108
+ * were resolved as "unique at the origin" in the gap before the departed tab came back, and a verdict
109
+ * for tab A was graded on tab B (#983, reproduced by driving). Measured as ages rather than instants,
110
+ * so both sides read the same clock at the same moment and the comparison does not drift while a
111
+ * caller polls.
112
+ */
113
+ export function openedBeforeDeparture(candidate, departed) {
114
+ return candidate.staleMs() > departed.agentIdleMs();
115
+ }
103
116
  /**
104
117
  * Poll until a successor of `departed` is live, or the budget runs out. Null on timeout — never
105
118
  * throws, because failing to follow is a legitimate `observation_lost`, not an error.
@@ -115,7 +128,7 @@ export async function awaitDocumentSuccessor(sessions, departed, timeoutMs, cloc
115
128
  // out of the pick, or same-id-first would keep returning the page that just unloaded.
116
129
  const picked = pickDocumentSuccessor(sessions
117
130
  .all()
118
- .filter((s) => s !== departed)
131
+ .filter((s) => s !== departed && !openedBeforeDeparture(s, departed))
119
132
  .map(identityOf), departedIdentity);
120
133
  if (picked !== undefined) {
121
134
  const live = sessions.get(picked.id);
@@ -0,0 +1,28 @@
1
+ /**
2
+ * The facts a SESSION contributes to a verdict, in the shape `decideVerified` takes.
3
+ *
4
+ * `decideVerified` is pure and has no session, so anything it needs about the link has to be
5
+ * threaded in. Two tools produce verdicts — `reticle_assert` and `reticle_act_and_wait` — and both
6
+ * were threading the same fields with the same conditional-spread idiom and the same comment
7
+ * beside it. A third fact (`versionSkew`) would have been a third copy.
8
+ *
9
+ * One function instead, for the ordinary reason: when a fourth fact arrives, a verdict surface that
10
+ * learns it in one place cannot half-learn it. That failure mode is not hypothetical here — the
11
+ * repo already records `net` assertions being discarded on one half of a surface because a filter
12
+ * was updated on the other.
13
+ *
14
+ * Every field is omitted rather than defaulted when absent. An SDK too old to declare sends
15
+ * nothing, and an empty value is a claim nobody made.
16
+ */
17
+ /** What a session can tell a verdict. Optional throughout: absent means nobody said. */
18
+ export interface SessionVerdictFacts {
19
+ readonly sdkVersion?: string;
20
+ readonly versionSkew?: string;
21
+ }
22
+ /** The shape read here — structural, so a test double needs no session. */
23
+ export interface VerdictRelevantSession {
24
+ readonly sdkVersion?: string | undefined;
25
+ readonly versionSkew?: string | undefined;
26
+ readonly channels?: readonly string[] | undefined;
27
+ }
28
+ export declare function sessionVerdictFacts(session: VerdictRelevantSession): SessionVerdictFacts;
@@ -0,0 +1,15 @@
1
+ export function sessionVerdictFacts(session) {
2
+ const facts = {};
3
+ // So the unread-body remedy can check it applies to THIS page.
4
+ if (session.sdkVersion !== undefined)
5
+ facts.sdkVersion = session.sdkVersion;
6
+ /*
7
+ * An empty string is NOT a skew. The bridge sets this only on a real mismatch, but a defaulted
8
+ * or cleared field reaching the verdict would cost every call a `yes` it had earned — which is a
9
+ * worse failure than the one this exists to fix.
10
+ */
11
+ if (session.versionSkew !== undefined && session.versionSkew.length > 0) {
12
+ facts.versionSkew = session.versionSkew;
13
+ }
14
+ return facts;
15
+ }
@@ -44,6 +44,11 @@ export declare class Session implements HandshakeFacts {
44
44
  * root, which is what every counter did unconditionally before this existed.
45
45
  */
46
46
  artifactRoot: string | undefined;
47
+ /**
48
+ * Same-origin endpoints this session's project declared as background (`.reticle.json`
49
+ * `background`). Stamped with `artifactRoot`, from the same project. Empty excludes nothing.
50
+ */
51
+ background: readonly string[];
47
52
  url: string;
48
53
  title: string;
49
54
  adapters: string[];
@@ -86,6 +91,8 @@ export declare class Session implements HandshakeFacts {
86
91
  * `reticle_session{action:"end"}` — a flag on the record, not on the page — could not help.
87
92
  */
88
93
  unresponsive(): boolean;
94
+ /** False once the socket is closing or closed: dropping this record then orphans nothing. */
95
+ socketOpen(): boolean;
89
96
  /** Wall-clock age of the session in milliseconds. */
90
97
  staleMs(): number;
91
98
  /** The document the most recent stamped event was observed under. See `#documentId`. */
@@ -109,6 +116,9 @@ export declare class Session implements HandshakeFacts {
109
116
  * against the same control after a re-render gave it a different ref.
110
117
  */
111
118
  private recordActedLabelFrom;
119
+ /** A control whose action PROVED its declared consequence — the coverage ledger's `proved`. */
120
+ recordProvedFrom(actPayload: unknown): void;
121
+ provedLabels(): ReadonlySet<string>;
112
122
  actedRefs(): ReadonlySet<string>;
113
123
  noteRateLimited(dropped: number): void;
114
124
  blindSpots(): Readonly<Record<string, number>>;
@@ -5,19 +5,19 @@ import { LastAct } from './last-act.js';
5
5
  import { GapLedger } from '@reticlehq/engine/evidence/gap-ledger.js';
6
6
  import { CaptureLedger } from '../../surface/tools/feature-capture.js';
7
7
  import { commandTimeoutMessage } from './timing/command-timeout.js';
8
- import { readHealthEvent, pendingNavigationMs } from './session-health.js';
8
+ import { readHealthEvent, healthNotices } from './session-health.js';
9
9
  import { MIRRORED_COMMANDS, mirroredNarration } from './presence/session-mirror.js';
10
10
  /** The HUD does not need 200 frames to watch a counter climb; the last state always lands. */
11
11
  const IMPACT_PUSH_DEBOUNCE_MS = 700;
12
12
  import { PendingCommands, CommandTimeoutError } from './page-commands/pending-commands.js';
13
13
  import { span } from '../../trace.js';
14
14
  import { keepCallerContextInDaemon, noteToDaemonLog } from './daemon-answers.js';
15
- import { isKnownRealm, EventType, HumanControlDataSchema, HumanControlKind, HumanMarkDataSchema, ReticleCommand, MessageKind, parseEventPayload, PresenterTone, SESSION_HEALTH, SESSION_LEASE, SESSION_LIFECYCLE, SessionState, } from '@reticlehq/core';
15
+ import { isKnownRealm, EventType, HumanControlDataSchema, HumanControlKind, HumanMarkDataSchema, HudUseDataSchema, ReticleCommand, MessageKind, parseEventPayload, PresenterTone, SESSION_HEALTH, SESSION_LEASE, SESSION_LIFECYCLE, SessionState, } from '@reticlehq/core';
16
16
  import { RingBuffer } from '@reticlehq/engine/window/ring-buffer.js';
17
17
  import { readJournalWriteLoss, readQueryEvents, } from '../../memory/journal/journal-query.js';
18
18
  import {} from '@reticlehq/engine/window/ambient.js';
19
- import { ObservedState } from './facts/observed-state.js';
20
- import { recordBrowserLatency, recordSdkFailure } from '../../telemetry/session-metrics.js';
19
+ import { ObservedState, controlLabelsOf } from './facts/observed-state.js';
20
+ import { recordBrowserLatency, recordHudUse, recordSdkFailure, } from '../../telemetry/session-metrics.js';
21
21
  import { LiveControl } from './human/live-control.js';
22
22
  import { ReviewStore } from './human/review-store.js';
23
23
  import { buildSessionRecommendation } from './presence/session-recommendation.js';
@@ -63,6 +63,11 @@ export class Session {
63
63
  * root, which is what every counter did unconditionally before this existed.
64
64
  */
65
65
  artifactRoot;
66
+ /**
67
+ * Same-origin endpoints this session's project declared as background (`.reticle.json`
68
+ * `background`). Stamped with `artifactRoot`, from the same project. Empty excludes nothing.
69
+ */
70
+ background = [];
66
71
  url;
67
72
  title;
68
73
  adapters;
@@ -188,17 +193,16 @@ export class Session {
188
193
  }
189
194
  /** The attachable health block — single source of truth for the tools. */
190
195
  health() {
191
- // From event t=0, not a cursor: a wedge that began before the current action is exactly the
192
- // case a per-window reading cannot see, and is the one both reporters hit.
193
- const stuck = pendingNavigationMs(this.eventsSince(0), this.elapsed());
194
196
  const base = {
195
197
  lastSeenMs: this.lastSeenMs(),
196
198
  throttled: this.throttled(),
197
199
  focused: this.#focused,
198
- ...(stuck === undefined ? {} : { pendingNavigationMs: stuck }),
199
200
  // Carried onto every act/assert result, not just reticle_sessions: skew drops actions
200
201
  // SILENTLY, and the fields it contradicts are on the act verdict.
201
202
  ...(this.versionSkew === undefined ? {} : { versionSkew: this.versionSkew }),
203
+ // The absence-gated facts: a stuck navigation and an ledger worth naming. Both are absent on
204
+ // a healthy session, and `healthNotices` owns which is which.
205
+ ...healthNotices(this.eventsSince(0), this.elapsed(), this.#journalReader?.ledger?.()),
202
206
  };
203
207
  // attach the escape-hatch hint only when un-scriptable (keeps field absent otherwise).
204
208
  const recommendation = buildSessionRecommendation({
@@ -237,6 +241,10 @@ export class Session {
237
241
  unresponsive() {
238
242
  return this.#unansweredCommands >= UNRESPONSIVE_AFTER_TIMEOUTS;
239
243
  }
244
+ /** False once the socket is closing or closed: dropping this record then orphans nothing. */
245
+ socketOpen() {
246
+ return this.#socket.readyState === WS_OPEN;
247
+ }
240
248
  /** Wall-clock age of the session in milliseconds. */
241
249
  staleMs() {
242
250
  return this.#clock() - this.#startedAt;
@@ -261,6 +269,14 @@ export class Session {
261
269
  keepCallerContext = keepCallerContextInDaemon;
262
270
  /** Re-stamp an incoming event with server-relative time, buffer it, and fan out. */
263
271
  pushEvent(event, byteSize) {
272
+ // Telemetry about the HUD, not evidence about the app: taken off here, before the buffer, so no
273
+ // verdict, settle window or agent read can ever see a person's clicks on Reticle's own panel.
274
+ if (event.type === EventType.HUD_USED) {
275
+ const use = HudUseDataSchema.safeParse(event.data);
276
+ if (use.success)
277
+ recordHudUse(use.data);
278
+ return;
279
+ }
264
280
  if (event.documentId !== undefined)
265
281
  this.#documentId = event.documentId;
266
282
  if (event.editEpoch !== undefined)
@@ -340,24 +356,16 @@ export class Session {
340
356
  * against the same control after a re-render gave it a different ref.
341
357
  */
342
358
  recordActedLabelFrom(result) {
343
- const payload = result.result;
344
- if (typeof payload !== 'object' || null === payload)
345
- return;
346
- const record = payload;
347
- // The TESTID first: it is the strongest identity a control has and it survives any re-render.
348
- // Coverage previously matched only on `role "name"`, so a control with a testid but no accessible
349
- // name — or on a stack where the act reply carried neither — was unrecognisable after a
350
- // re-render, and coverage read `exercised: 0` however much work had been done.
351
- const testid = record['testid'];
352
- if ('string' === typeof testid && testid.length > 0)
353
- this.#observed.recordActedLabel(testid);
354
- const role = record['role'];
355
- const name = record['name'];
356
- if (typeof role !== 'string' || typeof name !== 'string')
357
- return;
358
- if (0 === role.length || 0 === name.length)
359
- return;
360
- this.#observed.recordActedLabel(`${role} "${name}"`);
359
+ for (const label of controlLabelsOf(result.result))
360
+ this.#observed.recordActedLabel(label);
361
+ }
362
+ /** A control whose action PROVED its declared consequence — the coverage ledger's `proved`. */
363
+ recordProvedFrom(actPayload) {
364
+ for (const label of controlLabelsOf(actPayload))
365
+ this.#observed.recordProvedLabel(label);
366
+ }
367
+ provedLabels() {
368
+ return this.#observed.provedLabels();
361
369
  }
362
370
  actedRefs() {
363
371
  return this.#observed.actedRefs();
@@ -10,7 +10,8 @@ export interface SharedServer {
10
10
  */
11
11
  attachMcp(factory: () => McpServer): void;
12
12
  /** Register the JSON the daemon returns from GET /status (live sessions + health for `reticle status`). */
13
- attachStatus(provider: () => unknown): void;
13
+ /** `held`: the request carried STATUS_HOLD_QUERY — a setup run is waiting on this daemon. */
14
+ attachStatus(provider: (held: boolean) => unknown): void;
14
15
  /**
15
16
  * Register the handler behind POST /drive: open a driveable browser at `url` and answer with the
16
17
  * session it registered.
@@ -1,6 +1,6 @@
1
1
  import * as http from 'node:http';
2
2
  import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
3
- import { MCP_SSE_PATH, MCP_MESSAGE_PATH, MCP_SHUTDOWN_EVENT, STATUS_PATH, DRIVE_PATH, } from '@reticlehq/core';
3
+ import { MCP_SSE_PATH, MCP_MESSAGE_PATH, MCP_SHUTDOWN_EVENT, STATUS_PATH, STATUS_HOLD_QUERY, DRIVE_PATH, } from '@reticlehq/core';
4
4
  import { getSessionMetrics } from '../telemetry/session-metrics.js';
5
5
  import { noteAgentPeer, PEER_VERSION_PARAM, PEER_CONTRACT_PARAM, } from '../command/version/peer-announce.js';
6
6
  import { log } from '../log.js';
@@ -76,7 +76,9 @@ export function createSharedServer(options = {}) {
76
76
  }
77
77
  }
78
78
  if ('GET' === req.method && path === STATUS_PATH) {
79
- const body = JSON.stringify(statusProvider?.() ?? { running: true });
79
+ // A setup run waiting on this daemon says so on its polls; see daemon-usefulness.
80
+ const held = url.searchParams.has(STATUS_HOLD_QUERY);
81
+ const body = JSON.stringify(statusProvider?.(held) ?? { running: true });
80
82
  res.writeHead(200, { 'Content-Type': 'application/json' });
81
83
  res.end(body);
82
84
  return;
@@ -1,11 +1,11 @@
1
1
  export { SseFrameParser, type SseFrame } from './sse-frame-parser.js';
2
- export { probeDaemon, waitForDaemon } from './proxy/proxy-daemon-probe.js';
2
+ export { probeDaemon, waitForDaemon, waitForDaemonBind } from './proxy/proxy-daemon-probe.js';
3
3
  /**
4
4
  * Re-exported so every existing caller (and every spec) keeps importing the proxy's log from the
5
5
  * proxy. Where the code lives is a file-size decision; where it is imported from is an API.
6
6
  */
7
7
  export { accountProxyLogWrite, proxyLog, proxyLogPath, PROXY_LOG_CHECK_BYTES, recoverOversizedProxyLog, setProxyLogPort, } from './proxy/proxy-log.js';
8
- export { reconnectDelayMs, RECONNECT_BASE_MS, RECONNECT_CAP_MS } from './proxy/proxy-backoff.js';
8
+ export { reconnectDelayMs } from './proxy/proxy-backoff.js';
9
9
  export { MCP_PROXY_HTTP_AGENT_OPTIONS, postToSession, shouldRetryUnsentPost, type PostFailure, } from './proxy/mcp-post-transport.js';
10
10
  /**
11
11
  * Overridable so the survival spec can reach exhaustion in seconds instead of minutes — the same
@@ -6,13 +6,13 @@ import { toolsChangedNotification } from './proxy/proxy-handshake.js';
6
6
  import { LOOPBACK_HOST, MCP_SSE_PATH, MCP_SHUTDOWN_EVENT, ReticleEnv, CONTRACT_FINGERPRINT, } from '@reticlehq/core';
7
7
  import { SseFrameParser } from './sse-frame-parser.js';
8
8
  export { SseFrameParser } from './sse-frame-parser.js';
9
- export { probeDaemon, waitForDaemon } from './proxy/proxy-daemon-probe.js';
9
+ export { probeDaemon, waitForDaemon, waitForDaemonBind } from './proxy/proxy-daemon-probe.js';
10
10
  import { probeDaemon } from './proxy/proxy-daemon-probe.js';
11
11
  import { SERVER_VERSION } from '../../command/version/identity/server-version.js';
12
12
  import { buildServerInstructions } from './server-instructions.js';
13
- import { hasProjectConnectedBefore } from '../../memory/recall/prior/connection-memory.js';
13
+ import { hasAnyProjectConnectedBefore } from '../../memory/recall/prior/connection-memory.js';
14
14
  import { reticleStateHome } from '../../command/daemon/daemon.js';
15
- import { readProjectId } from '../../command/cli/ports/resolve/cli-port.js';
15
+ import { projectIdsAt } from '../../command/cli/ports/resolve/cli-port.js';
16
16
  import { PEER_VERSION_PARAM, PEER_CONTRACT_PARAM } from '../../command/version/peer-announce.js';
17
17
  import { onStreamDrop, onClientRequest, onReconnectBudgetSpent, onQueueExpired, OnDrop, OnRequest, } from './proxy/proxy-lifecycle.js';
18
18
  import { describePresence, probePresence } from '../../command/daemon/binding/port-presence.js';
@@ -33,7 +33,7 @@ import { OutageReason, OutageStage, reportMcpOutage } from './faults/mcp-outage.
33
33
  import { postToSession } from './proxy/mcp-post-transport.js';
34
34
  import { reconnectDelayMs } from './proxy/proxy-backoff.js';
35
35
  import { PROXY_IDLE_EXIT_EVENT, ProxyIdleExit, resolveProxyIdleExitMs } from './proxy-idle-exit.js';
36
- export { reconnectDelayMs, RECONNECT_BASE_MS, RECONNECT_CAP_MS } from './proxy/proxy-backoff.js';
36
+ export { reconnectDelayMs } from './proxy/proxy-backoff.js';
37
37
  export { MCP_PROXY_HTTP_AGENT_OPTIONS, postToSession, shouldRetryUnsentPost, } from './proxy/mcp-post-transport.js';
38
38
  /**
39
39
  * How many consecutive failed reconnects before the proxy stops RETRYING. It does not stop serving:
@@ -289,7 +289,7 @@ export function buildSessionUrl(rawData, port) {
289
289
  function proxyInstructions(port) {
290
290
  try {
291
291
  return buildServerInstructions({
292
- previouslyConnected: hasProjectConnectedBefore(reticleStateHome(), port, readProjectId(process.cwd())),
292
+ previouslyConnected: hasAnyProjectConnectedBefore(reticleStateHome(), port, projectIdsAt(process.cwd())),
293
293
  });
294
294
  }
295
295
  catch {
@@ -103,7 +103,7 @@ import { firstSentence } from '../tools/first-sentence.js';
103
103
  * making it a pointer costs nothing.
104
104
  */
105
105
  const PREDICATE_KINDS = 'Predicate object: { kind, ...fields }. kind is one of element | text | net | route | console | ' +
106
- 'animation | signal | state | settled | allOf | anyOf | not.';
106
+ 'animation | signal | state | settled | compare | allOf | anyOf | not.';
107
107
  /**
108
108
  * Said once per turn: the bug-catching options, and where to get the rest of the field grammar.
109
109
  *
@@ -118,9 +118,11 @@ const PREDICATE_KINDS = 'Predicate object: { kind, ...fields }. kind is one of e
118
118
  * paying for six.
119
119
  */
120
120
  const PREDICATE_FIELD_GRAMMAR_HINT = ' Bug-catching options: net.count (exact request count — catches double-submit), ' +
121
- 'net.bodyMatches (a shallow JSON match on what the SERVER answered — say the FIELD and its value, ' +
121
+ 'net.bodyMatches (a JSON field match on what the SERVER answered — say the FIELD (a dotted path reaches nested ones) and its value, ' +
122
122
  'because a substring of the body matches key names too), net.requestBodyMatches (the same on what ' +
123
123
  'the UI SENT — the verdict for "applying this filter actually puts it in the payload"), ' +
124
+ 'compare (page text vs server answer, no expected value: { left:{from:"text",scope}, ' +
125
+ 'right:{from:"net",urlContains,path}, as:"number" }), ' +
124
126
  'console.absent:true (action completed with a CLEAN ' +
125
127
  'console), absent:true on element/text (it should be gone). Call reticle_tools for the full field ' +
126
128
  'grammar of a kind.';
@@ -3,8 +3,12 @@ export declare function daemonPollDelayMs(attempt: number): number;
3
3
  * Returns true if something is already listening on the reticle port.
4
4
  * Uses a plain TCP probe so we don't create a side-effectful SSE session
5
5
  * inside the daemon just to check reachability.
6
+ *
7
+ * `host` defaults to loopback, which is every caller's actual target — except `bootSession`
8
+ * (reticlehq/reticle#1165 review), whose Bridge can bind a non-default host via `RETICLE_HOST`.
9
+ * Hardcoding loopback here made that preflight check the wrong address.
6
10
  */
7
- export declare function probeDaemon(port: number): Promise<boolean>;
11
+ export declare function probeDaemon(port: number, host?: string): Promise<boolean>;
8
12
  /**
9
13
  * Poll until a Reticle daemon is SERVING on `port`, or the deadline passes.
10
14
  *
@@ -20,3 +24,11 @@ export declare function waitForDaemon(port: number, probes?: {
20
24
  tcpOpen: (port: number) => Promise<boolean>;
21
25
  status: (port: number) => Promise<unknown>;
22
26
  }): Promise<void>;
27
+ /**
28
+ * Poll until `/status` on `port` answers as a Reticle daemon; false once the bound passes.
29
+ *
30
+ * One rule for the three commands that start a daemon. `init` used to borrow `waitForDaemon`'s
31
+ * shorter reconnect budget, and on a cold fresh HOME it printed "could not start the Reticle
32
+ * daemon" over a daemon that bound seconds later. Bounded polling, never a fixed sleep.
33
+ */
34
+ export declare function waitForDaemonBind(port: number): Promise<boolean>;
@@ -24,8 +24,12 @@ export function daemonPollDelayMs(attempt) {
24
24
  * Returns true if something is already listening on the reticle port.
25
25
  * Uses a plain TCP probe so we don't create a side-effectful SSE session
26
26
  * inside the daemon just to check reachability.
27
+ *
28
+ * `host` defaults to loopback, which is every caller's actual target — except `bootSession`
29
+ * (reticlehq/reticle#1165 review), whose Bridge can bind a non-default host via `RETICLE_HOST`.
30
+ * Hardcoding loopback here made that preflight check the wrong address.
27
31
  */
28
- export function probeDaemon(port) {
32
+ export function probeDaemon(port, host = LOOPBACK_HOST) {
29
33
  return new Promise((resolve) => {
30
34
  const socket = new net.Socket();
31
35
  socket.setTimeout(500);
@@ -38,7 +42,7 @@ export function probeDaemon(port) {
38
42
  socket.destroy();
39
43
  resolve(false);
40
44
  });
41
- socket.connect(port, LOOPBACK_HOST);
45
+ socket.connect(port, host);
42
46
  });
43
47
  }
44
48
  /**
@@ -69,3 +73,24 @@ export async function waitForDaemon(port, probes = { tcpOpen: probeDaemon, statu
69
73
  throw new Error(`reticle daemon did not become ready on port ${String(port)} within ` +
70
74
  `${String(DAEMON_READY_TIMEOUT_MS)}ms — ${describePresence(presence, port)}`);
71
75
  }
76
+ /** How long a spawned daemon gets to bind before `serve`, `restart` or `init` gives up on it. */
77
+ const BIND_TIMEOUT_MS = 15_000;
78
+ const BIND_POLL_MS = 150;
79
+ /**
80
+ * Poll until `/status` on `port` answers as a Reticle daemon; false once the bound passes.
81
+ *
82
+ * One rule for the three commands that start a daemon. `init` used to borrow `waitForDaemon`'s
83
+ * shorter reconnect budget, and on a cold fresh HOME it printed "could not start the Reticle
84
+ * daemon" over a daemon that bound seconds later. Bounded polling, never a fixed sleep.
85
+ */
86
+ export async function waitForDaemonBind(port) {
87
+ const deadline = Date.now() + BIND_TIMEOUT_MS;
88
+ for (;;) {
89
+ const presence = await probePresence(port, { tcpOpen: probeDaemon, status: fetchStatus });
90
+ if (presence === PortPresence.DAEMON)
91
+ return true;
92
+ if (Date.now() >= deadline)
93
+ return false;
94
+ await delay(BIND_POLL_MS);
95
+ }
96
+ }