@reticlehq/server 3.1.0 → 3.3.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 (384) hide show
  1. package/SKILL.md +30 -28
  2. package/dist/command/cli/adhoc-suite.d.ts +19 -0
  3. package/dist/command/cli/adhoc-suite.js +88 -0
  4. package/dist/command/cli/adhoc-verdict.d.ts +23 -2
  5. package/dist/command/cli/adhoc-verdict.js +18 -10
  6. package/dist/command/cli/auth/device-flow.d.ts +2 -2
  7. package/dist/command/cli/cli-doctor.js +37 -5
  8. package/dist/command/cli/cli-flow-commands.d.ts +2 -1
  9. package/dist/command/cli/cli-kill.js +1 -1
  10. package/dist/command/cli/cli-parse-grammar.d.ts +7 -0
  11. package/dist/command/cli/cli-parse-grammar.js +7 -0
  12. package/dist/command/cli/cli-parse-verify.d.ts +2 -5
  13. package/dist/command/cli/cli-parse-verify.js +86 -13
  14. package/dist/command/cli/cli-parse.d.ts +8 -1
  15. package/dist/command/cli/cli-parse.js +38 -7
  16. package/dist/command/cli/cli-verify.d.ts +59 -0
  17. package/dist/command/cli/cli-verify.js +131 -19
  18. package/dist/command/cli/cloud-cli.js +7 -6
  19. package/dist/command/cli/cloud-kit.d.ts +0 -2
  20. package/dist/command/cli/cloud-kit.js +3 -7
  21. package/dist/command/cli/config/config-discovery.d.ts +12 -0
  22. package/dist/command/cli/config/config-discovery.js +19 -0
  23. package/dist/command/cli/doctor/doctor-rows.d.ts +12 -0
  24. package/dist/command/cli/doctor/doctor-rows.js +12 -0
  25. package/dist/command/cli/doctor/doctor-sessions-line.d.ts +2 -1
  26. package/dist/command/cli/drive/drive-command.js +1 -1
  27. package/dist/command/cli/launch/cli-launch.d.ts +3 -23
  28. package/dist/command/cli/launch/cli-launch.js +29 -70
  29. package/dist/command/cli/lifecycle/daemon-lifecycle.d.ts +33 -0
  30. package/dist/command/cli/lifecycle/daemon-lifecycle.js +198 -0
  31. package/dist/command/cli/mcp-command.js +5 -3
  32. package/dist/command/cli/ports/resolve/cli-port.d.ts +15 -1
  33. package/dist/command/cli/ports/resolve/cli-port.js +50 -2
  34. package/dist/command/cli/report-command.d.ts +16 -0
  35. package/dist/command/cli/report-command.js +97 -0
  36. package/dist/command/cli/status/status-lines.d.ts +28 -0
  37. package/dist/command/cli/status/status-lines.js +117 -0
  38. package/dist/command/cli.d.ts +1 -1
  39. package/dist/command/cli.js +29 -177
  40. package/dist/command/daemon/binding/daemon-status-probe.d.ts +32 -0
  41. package/dist/command/daemon/binding/daemon-status-probe.js +93 -0
  42. package/dist/command/daemon/binding/loopback-reach.d.ts +42 -0
  43. package/dist/command/daemon/binding/loopback-reach.js +63 -0
  44. package/dist/command/daemon/binding/port-presence.d.ts +1 -1
  45. package/dist/command/daemon/daemon-resilience.d.ts +13 -1
  46. package/dist/command/daemon/daemon-resilience.js +12 -1
  47. package/dist/command/daemon/daemon.d.ts +2 -11
  48. package/dist/command/daemon/daemon.js +8 -11
  49. package/dist/command/daemon/idle-shutdown.d.ts +2 -1
  50. package/dist/command/daemon/lifetime/heartbeat.d.ts +8 -0
  51. package/dist/command/daemon/lifetime/heartbeat.js +10 -0
  52. package/dist/command/daemon/lifetime/log-cap.d.ts +68 -0
  53. package/dist/command/daemon/lifetime/log-cap.js +115 -0
  54. package/dist/command/setup/agent-notice-memory.d.ts +16 -0
  55. package/dist/command/setup/agent-notice-memory.js +65 -0
  56. package/dist/command/setup/bringup/dev-server-wait.d.ts +1 -0
  57. package/dist/command/setup/bringup/dev-server-wait.js +16 -0
  58. package/dist/command/setup/bringup/ensure-daemon.js +1 -1
  59. package/dist/command/setup/init/init-host.js +8 -0
  60. package/dist/command/setup/init/init-runtime.js +13 -1
  61. package/dist/command/setup/node-effects.js +1 -1
  62. package/dist/command/setup/probe/page-probe.js +9 -2
  63. package/dist/command/setup/run-setup.d.ts +25 -7
  64. package/dist/command/setup/run-setup.js +51 -9
  65. package/dist/command/setup/setup-command.js +31 -3
  66. package/dist/command/setup/terminal/confirm.js +2 -1
  67. package/dist/command/update/updater.d.ts +27 -0
  68. package/dist/command/update/updater.js +35 -2
  69. package/dist/features/crawl/crawl-tools.js +3 -1
  70. package/dist/features/crawl/crawl.d.ts +2 -0
  71. package/dist/features/crawl/crawl.js +3 -1
  72. package/dist/features/harness/drive-report.d.ts +73 -0
  73. package/dist/features/harness/drive-report.js +196 -0
  74. package/dist/features/harness/driver.js +29 -8
  75. package/dist/features/harness/drivers.d.ts +19 -0
  76. package/dist/features/harness/drivers.js +19 -0
  77. package/dist/features/harness/fill-values.d.ts +67 -0
  78. package/dist/features/harness/fill-values.js +153 -0
  79. package/dist/features/harness/jev-driver.d.ts +188 -0
  80. package/dist/features/harness/jev-driver.js +661 -0
  81. package/dist/features/harness/openai-driver.d.ts +46 -0
  82. package/dist/features/harness/openai-driver.js +197 -0
  83. package/dist/features/harness/platform-config.d.ts +62 -0
  84. package/dist/features/harness/platform-config.js +97 -0
  85. package/dist/features/visual/visual-tools.js +26 -2
  86. package/dist/index.d.ts +1 -1
  87. package/dist/index.js +80 -130
  88. package/dist/judgement/capsule/capsule-store.d.ts +31 -2
  89. package/dist/judgement/capsule/capsule-store.js +58 -6
  90. package/dist/judgement/capsule/capsule.js +5 -0
  91. package/dist/judgement/capsule/causal-summary.d.ts +13 -1
  92. package/dist/judgement/capsule/causal-summary.js +7 -0
  93. package/dist/judgement/domain/domain-model.js +38 -9
  94. package/dist/judgement/domain/domain-tools.js +4 -2
  95. package/dist/judgement/outcome/assertion-integrity.d.ts +3 -3
  96. package/dist/judgement/phenomena/phenomena.d.ts +2 -1
  97. package/dist/judgement/runs/artifact/gap-report.d.ts +17 -0
  98. package/dist/judgement/runs/artifact/gap-report.js +46 -0
  99. package/dist/judgement/runs/artifact/gap-summary.d.ts +58 -0
  100. package/dist/judgement/runs/artifact/gap-summary.js +85 -0
  101. package/dist/judgement/runs/artifact/render-report.js +4 -1
  102. package/dist/judgement/runs/artifact/to-artifact.d.ts +7 -2
  103. package/dist/judgement/runs/artifact/to-artifact.js +1 -1
  104. package/dist/judgement/runs/context-tools.js +21 -3
  105. package/dist/judgement/runs/drive-run.js +7 -2
  106. package/dist/judgement/runs/verification-sync.d.ts +2 -2
  107. package/dist/judgement/runs/verification-sync.js +9 -2
  108. package/dist/language/flows/affected-tools.js +6 -3
  109. package/dist/language/flows/annotate-notes/annotate.d.ts +0 -15
  110. package/dist/language/flows/annotate-notes/annotate.js +66 -18
  111. package/dist/language/flows/flow-anchor.d.ts +12 -0
  112. package/dist/language/flows/flow-anchor.js +20 -0
  113. package/dist/language/flows/flow-expect-grammar.d.ts +2 -2
  114. package/dist/language/flows/flow-expect-grammar.js +92 -11
  115. package/dist/language/flows/flow-intent.js +1 -1
  116. package/dist/language/flows/flow-learning.d.ts +3 -0
  117. package/dist/language/flows/flow-learning.js +39 -8
  118. package/dist/language/flows/flow-replay-run.d.ts +55 -10
  119. package/dist/language/flows/flow-replay-run.js +217 -58
  120. package/dist/language/flows/flow-replay-types.d.ts +9 -0
  121. package/dist/language/flows/flow-replay.d.ts +29 -11
  122. package/dist/language/flows/flow-replay.js +221 -203
  123. package/dist/language/flows/flow-result.d.ts +18 -1
  124. package/dist/language/flows/flow-result.js +18 -2
  125. package/dist/language/flows/flow-step-runners.d.ts +26 -10
  126. package/dist/language/flows/flow-step-runners.js +20 -4
  127. package/dist/language/flows/flow-store-for-session.d.ts +2 -1
  128. package/dist/language/flows/flow-success.d.ts +21 -6
  129. package/dist/language/flows/flow-success.js +50 -138
  130. package/dist/language/flows/flow-tools.js +8 -4
  131. package/dist/language/flows/flows.d.ts +15 -10
  132. package/dist/language/flows/flows.js +17 -4
  133. package/dist/language/flows/heal.d.ts +9 -1
  134. package/dist/language/flows/heal.js +24 -2
  135. package/dist/language/flows/learned-sources.d.ts +21 -0
  136. package/dist/language/flows/learned-sources.js +84 -0
  137. package/dist/language/flows/narrow-write.d.ts +4 -4
  138. package/dist/language/flows/recording/recording-backtrack.d.ts +0 -7
  139. package/dist/language/flows/recording/recording-backtrack.js +1 -1
  140. package/dist/language/flows/recording/replay-halt.d.ts +2 -1
  141. package/dist/language/flows/recording/tape/recordings.d.ts +2 -2
  142. package/dist/language/flows/replay.js +13 -9
  143. package/dist/language/flows/stores/annotation-store.d.ts +5 -5
  144. package/dist/language/flows/stores/assertion-tiers-store.js +2 -3
  145. package/dist/language/flows/stores/flake-store.js +2 -3
  146. package/dist/language/flows/suite/coverage.d.ts +1 -1
  147. package/dist/language/flows/suite/parallel-suite.d.ts +2 -1
  148. package/dist/language/flows/suite-selection.d.ts +15 -1
  149. package/dist/language/flows/suite-selection.js +1 -1
  150. package/dist/language/flows/verify-change-tools.js +21 -4
  151. package/dist/memory/cloud/account-state.d.ts +1 -1
  152. package/dist/memory/cloud/account-state.js +5 -4
  153. package/dist/memory/cloud/cloud-config.d.ts +9 -0
  154. package/dist/memory/cloud/cloud-config.js +9 -0
  155. package/dist/memory/cloud/cloud-sync.d.ts +7 -4
  156. package/dist/memory/cloud/cloud-sync.js +11 -6
  157. package/dist/memory/cloud/harness-config.d.ts +12 -0
  158. package/dist/memory/cloud/harness-config.js +58 -0
  159. package/dist/memory/cloud/harness-offer.d.ts +44 -0
  160. package/dist/memory/cloud/harness-offer.js +146 -0
  161. package/dist/memory/cloud/harness-switch.d.ts +11 -0
  162. package/dist/memory/cloud/harness-switch.js +53 -0
  163. package/dist/memory/cloud/sync-cycle.d.ts +12 -0
  164. package/dist/memory/cloud/sync-cycle.js +29 -4
  165. package/dist/memory/cloud/sync-daemon.d.ts +35 -2
  166. package/dist/memory/cloud/sync-daemon.js +45 -0
  167. package/dist/memory/cloud/sync-disk.js +11 -6
  168. package/dist/memory/impact/impact-recorder.d.ts +8 -0
  169. package/dist/memory/impact/impact-recorder.js +2 -1
  170. package/dist/memory/impact/impact-store.d.ts +5 -0
  171. package/dist/memory/impact/impact-store.js +25 -0
  172. package/dist/memory/intent/inline-intent.js +8 -3
  173. package/dist/memory/intent/intent-shard-store.d.ts +33 -47
  174. package/dist/memory/intent/intent-shard-store.js +253 -141
  175. package/dist/memory/intent/intent-shard.d.ts +22 -23
  176. package/dist/memory/intent/intent-shard.js +1 -1
  177. package/dist/memory/intent/intent-store.d.ts +13 -8
  178. package/dist/memory/intent/intent-store.js +46 -90
  179. package/dist/memory/intent/intent-tools.js +34 -23
  180. package/dist/memory/journal/ambient-store.js +2 -3
  181. package/dist/memory/journal/attach-journal.d.ts +11 -0
  182. package/dist/memory/journal/attach-journal.js +1 -1
  183. package/dist/memory/journal/deviation-report.js +4 -1
  184. package/dist/memory/journal/deviation-service.js +10 -2
  185. package/dist/memory/journal/drive-flow.d.ts +2 -2
  186. package/dist/memory/journal/drive-flow.js +21 -12
  187. package/dist/memory/journal/envelope-store.js +2 -3
  188. package/dist/memory/journal/journal-query.d.ts +35 -1
  189. package/dist/memory/journal/journal-query.js +46 -0
  190. package/dist/memory/journal/journal-recorder.d.ts +71 -2
  191. package/dist/memory/journal/journal-recorder.js +45 -7
  192. package/dist/memory/journal/on-disk/retain-policy.d.ts +34 -0
  193. package/dist/memory/journal/on-disk/retain-policy.js +44 -0
  194. package/dist/memory/journal/on-disk/retention.d.ts +91 -2
  195. package/dist/memory/journal/on-disk/retention.js +181 -11
  196. package/dist/memory/journal/on-disk/startup-maintenance.d.ts +42 -0
  197. package/dist/memory/journal/on-disk/startup-maintenance.js +14 -0
  198. package/dist/memory/journal/on-disk/workspace-gitignore.d.ts +0 -2
  199. package/dist/memory/journal/on-disk/workspace-gitignore.js +6 -38
  200. package/dist/memory/journal/on-disk/workspace-tiers.d.ts +52 -0
  201. package/dist/memory/journal/on-disk/workspace-tiers.js +88 -0
  202. package/dist/memory/journal/route-template.d.ts +22 -0
  203. package/dist/memory/journal/route-template.js +44 -0
  204. package/dist/memory/journal/self-observation.d.ts +3 -0
  205. package/dist/memory/journal/self-observation.js +67 -0
  206. package/dist/memory/journal/session-end.d.ts +17 -4
  207. package/dist/memory/journal/session-end.js +85 -7
  208. package/dist/memory/journal/session-journal.d.ts +122 -5
  209. package/dist/memory/journal/session-journal.js +489 -32
  210. package/dist/memory/project/artifact-root-resolver.d.ts +3 -0
  211. package/dist/memory/project/artifact-root-resolver.js +89 -0
  212. package/dist/memory/project/artifact-root.d.ts +39 -2
  213. package/dist/memory/project/artifact-root.js +58 -1
  214. package/dist/memory/project/baselines.d.ts +1 -1
  215. package/dist/memory/project/dir/fill-value-store.d.ts +32 -0
  216. package/dist/memory/project/dir/fill-value-store.js +88 -0
  217. package/dist/memory/project/dir/reticle-dir.d.ts +15 -5
  218. package/dist/memory/project/dir/reticle-dir.js +11 -0
  219. package/dist/memory/project/fs/fs-port.d.ts +22 -1
  220. package/dist/memory/project/fs/fs-port.js +72 -5
  221. package/dist/memory/project/fs/write-atomic.d.ts +26 -0
  222. package/dist/memory/project/fs/write-atomic.js +48 -0
  223. package/dist/memory/project/learned-routes.d.ts +16 -2
  224. package/dist/memory/project/learned-routes.js +26 -10
  225. package/dist/memory/project/project-for-root.d.ts +30 -0
  226. package/dist/memory/project/project-for-root.js +43 -0
  227. package/dist/memory/project/project-tools.js +10 -5
  228. package/dist/memory/project/session-root.d.ts +35 -6
  229. package/dist/memory/project/session-root.js +70 -8
  230. package/dist/memory/recall/prior/connection-memory.d.ts +6 -0
  231. package/dist/memory/recall/prior/connection-memory.js +8 -0
  232. package/dist/memory/recall/registered-projects.d.ts +2 -1
  233. package/dist/portal/bridge/auth-failure-reason.d.ts +9 -0
  234. package/dist/portal/bridge/auth-failure-reason.js +20 -3
  235. package/dist/portal/bridge/bridge.d.ts +6 -1
  236. package/dist/portal/bridge/bridge.js +36 -2
  237. package/dist/portal/input/real-input.js +2 -1
  238. package/dist/portal/pool/browser-pool.d.ts +10 -0
  239. package/dist/portal/pool/browser-pool.js +10 -0
  240. package/dist/portal/pool/playwright-launcher.js +1 -0
  241. package/dist/portal/pool/pool-contract.d.ts +2 -0
  242. package/dist/portal/pool/storage-seed.d.ts +2 -1
  243. package/dist/portal/pool/zero-install.d.ts +27 -0
  244. package/dist/portal/pool/zero-install.js +54 -0
  245. package/dist/portal/realm/web-realm.js +6 -0
  246. package/dist/portal/session/dev-server/dev-server-probe.d.ts +2 -1
  247. package/dist/portal/session/dev-server/served-document.d.ts +21 -0
  248. package/dist/portal/session/dev-server/served-document.js +57 -0
  249. package/dist/portal/session/facts/background-traffic.d.ts +9 -0
  250. package/dist/portal/session/facts/background-traffic.js +25 -0
  251. package/dist/portal/session/facts/session-replaced.d.ts +19 -0
  252. package/dist/portal/session/facts/session-replaced.js +22 -0
  253. package/dist/portal/session/live-control-tools.js +48 -3
  254. package/dist/portal/session/no-session-diagnosis.d.ts +1 -0
  255. package/dist/portal/session/no-session-diagnosis.js +67 -60
  256. package/dist/portal/session/no-session-next-action.js +3 -2
  257. package/dist/portal/session/no-session-watch.js +14 -10
  258. package/dist/portal/session/presence/attachment-history.d.ts +13 -2
  259. package/dist/portal/session/rebind.d.ts +18 -0
  260. package/dist/portal/session/rebind.js +38 -0
  261. package/dist/portal/session/session-health.d.ts +61 -0
  262. package/dist/portal/session/session-health.js +40 -1
  263. package/dist/portal/session/session-info.d.ts +6 -0
  264. package/dist/portal/session/session-manager.d.ts +13 -0
  265. package/dist/portal/session/session-manager.js +42 -3
  266. package/dist/portal/session/session-successor.d.ts +11 -0
  267. package/dist/portal/session/session-successor.js +14 -1
  268. package/dist/portal/session/session-verdict-facts.d.ts +28 -0
  269. package/dist/portal/session/session-verdict-facts.js +15 -0
  270. package/dist/portal/session/session.d.ts +26 -10
  271. package/dist/portal/session/session.js +48 -49
  272. package/dist/status-payload.d.ts +12 -1
  273. package/dist/status-payload.js +4 -1
  274. package/dist/surface/http-server.js +3 -5
  275. package/dist/surface/mcp/faults/mcp-outage.d.ts +2 -1
  276. package/dist/surface/mcp/faults/mcp-outage.js +20 -4
  277. package/dist/surface/mcp/mcp-proxy.d.ts +10 -1
  278. package/dist/surface/mcp/mcp-proxy.js +23 -12
  279. package/dist/surface/mcp/mcp.d.ts +10 -1
  280. package/dist/surface/mcp/mcp.js +59 -29
  281. package/dist/surface/mcp/proxy/proxy-daemon-probe.d.ts +15 -2
  282. package/dist/surface/mcp/proxy/proxy-daemon-probe.js +22 -5
  283. package/dist/surface/mcp/proxy/proxy-log.d.ts +5 -11
  284. package/dist/surface/mcp/proxy/proxy-log.js +7 -22
  285. package/dist/surface/mcp/proxy-idle-exit.d.ts +2 -1
  286. package/dist/surface/mcp/server-instructions.js +12 -1
  287. package/dist/surface/tools/act/act-preflight.js +6 -1
  288. package/dist/surface/tools/act/already-true.d.ts +40 -0
  289. package/dist/surface/tools/act/already-true.js +36 -0
  290. package/dist/surface/tools/act/navigation/navigate-arrival.d.ts +6 -0
  291. package/dist/surface/tools/act/navigation/navigate-arrival.js +13 -0
  292. package/dist/surface/tools/act/sequence-grade.js +13 -0
  293. package/dist/surface/tools/act/settle-in-flight.d.ts +6 -2
  294. package/dist/surface/tools/act/settle-in-flight.js +29 -6
  295. package/dist/surface/tools/act-capsule.js +49 -8
  296. package/dist/surface/tools/act-tools.js +25 -24
  297. package/dist/surface/tools/args/numeric-bounds.d.ts +15 -2
  298. package/dist/surface/tools/args/numeric-bounds.js +30 -3
  299. package/dist/surface/tools/assert/assert-grade.js +14 -2
  300. package/dist/surface/tools/assert/assert-verdict.js +74 -21
  301. package/dist/surface/tools/browser-tools.js +4 -3
  302. package/dist/surface/tools/coverage-tools.js +5 -2
  303. package/dist/surface/tools/dynamic-tools.js +43 -46
  304. package/dist/surface/tools/error-recovery.d.ts +6 -0
  305. package/dist/surface/tools/error-recovery.js +15 -0
  306. package/dist/surface/tools/explore-tools.js +80 -7
  307. package/dist/surface/tools/feedback-tools.d.ts +2 -1
  308. package/dist/surface/tools/feedback-tools.js +50 -3
  309. package/dist/surface/tools/first-sentence.d.ts +24 -0
  310. package/dist/surface/tools/first-sentence.js +0 -0
  311. package/dist/surface/tools/harness-explore.d.ts +125 -1
  312. package/dist/surface/tools/harness-explore.js +401 -15
  313. package/dist/surface/tools/harness-plan.d.ts +76 -0
  314. package/dist/surface/tools/harness-plan.js +156 -0
  315. package/dist/surface/tools/harness-toolset.js +12 -1
  316. package/dist/surface/tools/invoke-tool.js +82 -9
  317. package/dist/surface/tools/lease-hint.d.ts +8 -0
  318. package/dist/surface/tools/lease-hint.js +10 -0
  319. package/dist/surface/tools/lease-tools.d.ts +16 -28
  320. package/dist/surface/tools/lease-tools.js +53 -5
  321. package/dist/surface/tools/lineage-tools.js +1 -1
  322. package/dist/surface/tools/live-call-text.d.ts +6 -0
  323. package/dist/surface/tools/live-call-text.js +36 -2
  324. package/dist/surface/tools/observe-tools.js +69 -15
  325. package/dist/surface/tools/read/query-windowed.d.ts +1 -1
  326. package/dist/surface/tools/read/query-windowed.js +1 -1
  327. package/dist/surface/tools/read-tools.js +1 -2
  328. package/dist/surface/tools/real-input-attempt.d.ts +0 -5
  329. package/dist/surface/tools/real-input-attempt.js +1 -1
  330. package/dist/surface/tools/tool-kit.d.ts +23 -3
  331. package/dist/surface/tools/tool-kit.js +6 -1
  332. package/dist/surface/tools/tool-surface.js +17 -5
  333. package/dist/surface/tools/tools-helpers.d.ts +8 -7
  334. package/dist/surface/tools/tools-helpers.js +11 -13
  335. package/dist/surface/tools/tools.js +70 -30
  336. package/dist/telemetry/bug-found.js +32 -2
  337. package/dist/telemetry/feedback-context.js +3 -3
  338. package/dist/telemetry/feedback-local.d.ts +1 -0
  339. package/dist/telemetry/feedback-local.js +1 -3
  340. package/dist/telemetry/feedback.d.ts +1 -1
  341. package/dist/telemetry/harness-drive.d.ts +6 -0
  342. package/dist/telemetry/harness-drive.js +39 -0
  343. package/dist/telemetry/hud-metrics.d.ts +32 -0
  344. package/dist/telemetry/hud-metrics.js +95 -0
  345. package/dist/telemetry/mcp-connection.d.ts +14 -1
  346. package/dist/telemetry/mcp-connection.js +17 -2
  347. package/dist/telemetry/onboarding-firsts.d.ts +30 -1
  348. package/dist/telemetry/onboarding-firsts.js +54 -12
  349. package/dist/telemetry/session-metrics.d.ts +5 -0
  350. package/dist/telemetry/session-metrics.js +15 -1
  351. package/dist/telemetry/verification-of.js +31 -3
  352. package/dist/wire-journal.d.ts +31 -0
  353. package/dist/wire-journal.js +100 -0
  354. package/docs/README.md +1 -0
  355. package/docs/agent-cheatsheet.md +7 -3
  356. package/docs/autodrive.md +42 -2
  357. package/docs/benchmarks.md +1 -1
  358. package/docs/capabilities.mdx +1 -1
  359. package/docs/cli/cloud.mdx +11 -9
  360. package/docs/cli/doctor.mdx +1 -0
  361. package/docs/cli/init.mdx +1 -0
  362. package/docs/cli/report.mdx +44 -0
  363. package/docs/cli/status.mdx +35 -20
  364. package/docs/cli.mdx +8 -18
  365. package/docs/desktop.mdx +1 -1
  366. package/docs/faq.mdx +1 -1
  367. package/docs/flows.md +11 -0
  368. package/docs/frameworks.mdx +113 -1
  369. package/docs/install-agentic.mdx +36 -0
  370. package/docs/install-manual.mdx +1 -1
  371. package/docs/packages/tauri.mdx +1 -1
  372. package/docs/packages.mdx +1 -1
  373. package/docs/predicates.mdx +144 -4
  374. package/docs/system-map.md +6 -4
  375. package/docs/telemetry.md +2 -1
  376. package/docs/token-efficiency.md +2 -2
  377. package/docs/tools/overview.mdx +1 -1
  378. package/docs/tools/query.mdx +10 -10
  379. package/docs/troubleshooting.mdx +8 -2
  380. package/docs/usage.md +2 -2
  381. package/docs/what-is-recorded.md +62 -0
  382. package/package.json +5 -5
  383. package/dist/judgement/outcome/predicate-to-expect.d.ts +0 -44
  384. package/dist/judgement/outcome/predicate-to-expect.js +0 -148
@@ -23,7 +23,7 @@ import { daemonsServingProjectElsewhere, splitBrainNote } from '../../command/da
23
23
  import { stallUptime } from './stall-clock.js';
24
24
  import { probeDaemon } from '../../surface/mcp/mcp-proxy.js';
25
25
  import { findOccupiedSiblings } from '../../command/cli/ports/sibling-ports.js';
26
- import { WS_CLOSE_REASON } from '../bridge/bridge.js';
26
+ import { isAuthRefusalReason } from '../bridge/auth-failure-reason.js';
27
27
  /** Slow enough to be free, fast enough that a dev server started 15s ago is already reflected. */
28
28
  const REFRESH_MS = 15_000;
29
29
  /** The URL an auto-attach opens. `localhost` for the same reason the probe uses it: both stacks. */
@@ -208,7 +208,7 @@ export function startNoSessionWatch(options) {
208
208
  /**
209
209
  * Is a refusal on the pairing token the CURRENT state of the bridge?
210
210
  *
211
- * The bridge records the refusal (`noteClosure(WS_CLOSE_REASON.AUTH_FAILED)`) and nothing read it
211
+ * The bridge records the refusal (the sentence it closed the socket with) and nothing read it
212
212
  * as a DIAGNOSIS. It is the one fact that proves an app is running and instrumented: only an SDK
213
213
  * dials the bridge, so a refused hello means the wiring works and this daemon would not serve it.
214
214
  *
@@ -223,7 +223,7 @@ export function startNoSessionWatch(options) {
223
223
  * recorded no refusal, which falls through to the behaviour that was there before -- the safe
224
224
  * direction for a fact whose job is to SUPPRESS advice rather than to add any.
225
225
  */
226
- const lastCloseWasAuthFailure = () => options.sessions.lastClosure?.()?.reason === WS_CLOSE_REASON.AUTH_FAILED &&
226
+ const lastCloseWasAuthFailure = () => isAuthRefusalReason(options.sessions.lastClosure?.()?.reason) &&
227
227
  true !== options.sessions.connectedSinceLastClosure?.();
228
228
  const nextAction = (scope) => {
229
229
  const split = splitBrain();
@@ -307,19 +307,23 @@ export function startNoSessionWatch(options) {
307
307
  ...(0 === siblingListeners.length ? {} : { siblingListeners }),
308
308
  });
309
309
  };
310
- options.sessions.setNoSessionHint(() => {
310
+ // The lead, then the literal command, then the differential. Both renderings consume the same
311
+ // scope facts so a discovered workspace config cannot become an `init` recommendation below it,
312
+ // and both come from ONE `explain()` call so they cannot describe different branches.
313
+ const rendered = () => {
311
314
  const scope = projectScopeFacts();
312
- return (explain().message +
313
- // Prose for the human, then the literal command for the agent. Both consume the same scope
314
- // facts so a discovered workspace config cannot become an `init` recommendation below it.
315
- ` ${renderNextAction(nextAction(scope))}` +
316
- (attachFailure ?? ''));
317
- });
315
+ const { message, detail } = explain();
316
+ const lead = `${message} ${renderNextAction(nextAction(scope))}${attachFailure ?? ''}`;
317
+ return { lead, full: undefined === detail ? lead : `${lead} ${detail}` };
318
+ };
319
+ options.sessions.setNoSessionHint(() => rendered().full);
320
+ options.sessions.setNoSessionLead(() => rendered().lead);
318
321
  options.sessions.setNoSessionReason(() => explain().reason);
319
322
  return () => {
320
323
  clearInterval(timer);
321
324
  options.sessions.setConnectionRecorder(undefined);
322
325
  options.sessions.setNoSessionHint(undefined);
326
+ options.sessions.setNoSessionLead(undefined);
323
327
  options.sessions.setNoSessionReason(undefined);
324
328
  options.sessions.setNoSessionNextAction(undefined);
325
329
  };
@@ -15,11 +15,22 @@
15
15
  * The clock is injected, per the repo rule: this is arithmetic over timestamps, and a test that
16
16
  * needs to wait four seconds to check a four-second gap is a test nobody runs.
17
17
  */
18
- /** What an agent needs to know about the continuity of one session. */
18
+ /**
19
+ * What an agent needs to know about the continuity of one session.
20
+ *
21
+ * Every field here measures the SDK-to-daemon LINK. None of them measures the app: the origin the
22
+ * page was served from can stop answering entirely without any of these moving, because the socket
23
+ * that reports them is between the daemon and a document the browser is already holding. Reported
24
+ * as a false read of app health in #938, where a tab whose dev server was dead carried `outages: 0`
25
+ * and `connectedSinceMs: 11min` and was taken for a healthy app.
26
+ */
19
27
  interface Attachment {
20
28
  /** How long THIS attachment has lasted. Resets on reconnect — it is not the session's lifetime. */
21
29
  connectedSinceMs: number;
22
- /** How many times this session dropped and came back. Zero means it never left. */
30
+ /**
31
+ * How many times this session dropped and came back. Zero means the LINK never dropped; it does
32
+ * not mean the app behind it is alive.
33
+ */
23
34
  outages: number;
24
35
  /** The most recent gap, when there has been one. */
25
36
  lastOutage?: {
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Commands that may be re-issued against the connection that replaced this session.
3
+ *
4
+ * Reads only, and the boundary is the point. Re-reading a page costs nothing and answers the same
5
+ * question the caller asked. An act cannot be replayed: it may already have been dispatched in the
6
+ * page that went away, and performing it a second time behind the caller's back is a double submit
7
+ * nobody asked for. A replaced act still errors, and the caller's own retry — with the same id,
8
+ * which is still the right one — is the safe path.
9
+ */
10
+ export declare const REBINDABLE_COMMANDS: ReadonlySet<string>;
11
+ /**
12
+ * How far to walk a chain of replacements before giving up.
13
+ *
14
+ * Generous for the real case (a page reloading a handful of times while a call is in flight) and
15
+ * small enough that a cycle costs nothing. The number is a backstop, not a policy: a chain longer
16
+ * than this means something is re-dialling in a loop, and that is a different problem.
17
+ */
18
+ export declare const MAX_SUCCESSOR_HOPS = 32;
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Whether a command in flight may be re-issued against the connection that REPLACED this session,
3
+ * and how far a chain of replacements is worth walking.
4
+ *
5
+ * One question, so one file. It left `session.ts` when that file crossed the thousand-line cap on a
6
+ * merge: two branches had each grown it by a few lines and the sum went over, which is the moment
7
+ * the rule says to split rather than to raise the number. These two constants and the reasoning
8
+ * behind them are the most self-contained thing in there — `session.ts` reads them twice and owns
9
+ * nothing else about rebinding.
10
+ */
11
+ import { ReticleCommand } from '@reticlehq/core';
12
+ /**
13
+ * Commands that may be re-issued against the connection that replaced this session.
14
+ *
15
+ * Reads only, and the boundary is the point. Re-reading a page costs nothing and answers the same
16
+ * question the caller asked. An act cannot be replayed: it may already have been dispatched in the
17
+ * page that went away, and performing it a second time behind the caller's back is a double submit
18
+ * nobody asked for. A replaced act still errors, and the caller's own retry — with the same id,
19
+ * which is still the right one — is the safe path.
20
+ */
21
+ export const REBINDABLE_COMMANDS = new Set([
22
+ ReticleCommand.SNAPSHOT,
23
+ ReticleCommand.QUERY,
24
+ ReticleCommand.MATCH,
25
+ ReticleCommand.INSPECT,
26
+ ReticleCommand.STATE_READ,
27
+ ReticleCommand.STORAGE_READ,
28
+ ReticleCommand.CAPABILITIES,
29
+ ReticleCommand.ANIMATIONS,
30
+ ]);
31
+ /**
32
+ * How far to walk a chain of replacements before giving up.
33
+ *
34
+ * Generous for the real case (a page reloading a handful of times while a call is in flight) and
35
+ * small enough that a cycle costs nothing. The number is a backstop, not a policy: a chain longer
36
+ * than this means something is re-dialling in a loop, and that is a different problem.
37
+ */
38
+ export const MAX_SUCCESSOR_HOPS = 32;
@@ -38,6 +38,22 @@ export interface SessionHealth {
38
38
  * on `reticle_sessions` or `reticle_lease` is not on the surface it reads every call.
39
39
  */
40
40
  versionSkew?: string;
41
+ /**
42
+ * The session's event ledger, once it has grown enough to be worth a reader's attention.
43
+ *
44
+ * Present only past `LEDGER_NOTICE_FRACTION` of the cap, so a healthy session costs nothing --
45
+ * the same rule `pendingNavigationMs` follows. A ledger below that is doing its job quietly.
46
+ *
47
+ * It is here because nothing reported it. A repeating uncaught error wrote one session's ledger
48
+ * until the disk was full; neither the daemon nor `doctor` nor any
49
+ * health counter said a word, and the user found out when the disk was full (#986). A cap bounds
50
+ * the damage but does not make the writing visible, and this block is the surface an agent
51
+ * already reads on every act and assert.
52
+ */
53
+ ledger?: {
54
+ bytes: number;
55
+ capBytes: number;
56
+ };
41
57
  }
42
58
  /**
43
59
  * How long a request may be in flight before it is worth reporting on session health.
@@ -84,6 +100,18 @@ interface BufferEnvelope {
84
100
  * so a clean/empty result there is trustworthy and costs zero tokens.
85
101
  */
86
102
  export declare function bufferEnvelope(session: HealthSubject): BufferEnvelope;
103
+ /**
104
+ * The session registered under `session.id` NOW, for describing a tab after a call that may have
105
+ * replaced its document. A reload or full navigation registers a new Session under the same id, and
106
+ * the object a handler resolved first is the page that unloaded; it reported itself hidden on the way
107
+ * out, so its health said "throttled" about a tab that was fine. Optional-called because test doubles
108
+ * are partial registries.
109
+ */
110
+ export declare function currentOf<S extends {
111
+ id: string;
112
+ }>(registry: {
113
+ get?: (id: string) => S | undefined;
114
+ }, session: S): S;
87
115
  /** The `session` (and optional throttled `warning`) block spliced onto act/assert results. */
88
116
  interface HealthEnvelope {
89
117
  session?: SessionHealth;
@@ -142,4 +170,37 @@ interface HealthReport {
142
170
  * keep its previous value instead of silently flipping a session to "visible" on a partial report.
143
171
  */
144
172
  export declare function readHealthEvent(data: Record<string, unknown>): HealthReport;
173
+ /**
174
+ * The ledger block for the health payload, or nothing when it is not worth saying.
175
+ *
176
+ * Absent below `LEDGER_NOTICE_FRACTION` of the cap, and absent when the size has not been measured
177
+ * yet -- a reader must be able to tell "not measured" from "empty", and a zero reported before the
178
+ * first append would be a claim rather than a reading.
179
+ */
180
+ export declare function ledgerNotice(ledger: {
181
+ bytes: number;
182
+ capBytes: number;
183
+ } | undefined): {
184
+ ledger?: {
185
+ bytes: number;
186
+ capBytes: number;
187
+ };
188
+ };
189
+ /**
190
+ * Every health field that is present only when it means something.
191
+ *
192
+ * One place answers "which optional facts belong on the health block", so a session does not have
193
+ * to know the rule for each. A healthy session gets an empty object back and its health block
194
+ * carries only the fields that are always true.
195
+ */
196
+ export declare function healthNotices(events: readonly ReticleEvent[], elapsed: number, ledger: {
197
+ bytes: number;
198
+ capBytes: number;
199
+ } | undefined): {
200
+ pendingNavigationMs?: number;
201
+ ledger?: {
202
+ bytes: number;
203
+ capBytes: number;
204
+ };
205
+ };
145
206
  export {};
@@ -1,4 +1,4 @@
1
- import { BrowserBrand, EventType, BUFFER_EVICTION_WARNING, THROTTLED_STARVED_NOTE, THROTTLED_WARNING, } from '@reticlehq/core';
1
+ import { BrowserBrand, EventType, BUFFER_EVICTION_WARNING, THROTTLED_STARVED_NOTE, SESSION_HEALTH, THROTTLED_WARNING, } from '@reticlehq/core';
2
2
  /**
3
3
  * How long a request may be in flight before it is worth reporting on session health.
4
4
  *
@@ -68,6 +68,16 @@ export function bufferEnvelope(session) {
68
68
  return {};
69
69
  return { buffer: { held: total, dropped, note: BUFFER_EVICTION_WARNING } };
70
70
  }
71
+ /**
72
+ * The session registered under `session.id` NOW, for describing a tab after a call that may have
73
+ * replaced its document. A reload or full navigation registers a new Session under the same id, and
74
+ * the object a handler resolved first is the page that unloaded; it reported itself hidden on the way
75
+ * out, so its health said "throttled" about a tab that was fine. Optional-called because test doubles
76
+ * are partial registries.
77
+ */
78
+ export function currentOf(registry, session) {
79
+ return registry.get?.(session.id) ?? session;
80
+ }
71
81
  /**
72
82
  * Build the health envelope for a tool result. When the session is **nominal** (focused, not
73
83
  * throttled, no escape-hatch recommendation) the block is OMITTED entirely — a healthy session
@@ -152,3 +162,32 @@ export function readHealthEvent(data) {
152
162
  brand: Object.values(BrowserBrand).find((known) => known === data['brand']),
153
163
  };
154
164
  }
165
+ /**
166
+ * The ledger block for the health payload, or nothing when it is not worth saying.
167
+ *
168
+ * Absent below `LEDGER_NOTICE_FRACTION` of the cap, and absent when the size has not been measured
169
+ * yet -- a reader must be able to tell "not measured" from "empty", and a zero reported before the
170
+ * first append would be a claim rather than a reading.
171
+ */
172
+ export function ledgerNotice(ledger) {
173
+ if (ledger === undefined || 0 >= ledger.capBytes)
174
+ return {};
175
+ const fraction = ledger.bytes / ledger.capBytes;
176
+ return fraction < SESSION_HEALTH.LEDGER_NOTICE_FRACTION ? {} : { ledger };
177
+ }
178
+ /**
179
+ * Every health field that is present only when it means something.
180
+ *
181
+ * One place answers "which optional facts belong on the health block", so a session does not have
182
+ * to know the rule for each. A healthy session gets an empty object back and its health block
183
+ * carries only the fields that are always true.
184
+ */
185
+ export function healthNotices(events, elapsed, ledger) {
186
+ // From event t=0, not a cursor: a wedge that began before the current action is exactly the case
187
+ // a per-window reading cannot see, and is the one both reporters hit.
188
+ const stuck = pendingNavigationMs(events, elapsed);
189
+ return {
190
+ ...(stuck === undefined ? {} : { pendingNavigationMs: stuck }),
191
+ ...ledgerNotice(ledger),
192
+ };
193
+ }
@@ -45,6 +45,12 @@ export interface SessionInfo {
45
45
  * A tab that dropped for four seconds and came back is indistinguishable from one that never left
46
46
  * (#117), and that difference decides whether a verdict over the window can be trusted. Attached
47
47
  * by `SessionManager.list()`, which is the layer that sees every connect and disconnect.
48
+ *
49
+ * Scoped to the SDK-to-daemon LINK, and the names do not say so on their own. A zombie tab whose
50
+ * dev server had died reported `outages: 0` and `connectedSinceMs: 11min`, and both were correct:
51
+ * the page's socket was still attached to the daemon. Read as app health, they said the app was
52
+ * up while nothing was listening on the port (#938). The describe() on the `reticle_sessions`
53
+ * output schema carries that caveat, because that is where an agent reads it.
48
54
  */
49
55
  attachment?: {
50
56
  connectedSinceMs: number;
@@ -22,6 +22,15 @@ export declare class SessionManager {
22
22
  add(session: Session): Session | undefined;
23
23
  remove(session: Session): boolean;
24
24
  get(sessionId: string): Session | undefined;
25
+ /**
26
+ * Did this id belong to a session that has since disconnected?
27
+ *
28
+ * `resolve` answers a departed id with its live SUCCESSOR, which is what keeps a reload working
29
+ * for everything that observes. A caller that is about to do something DESTRUCTIVE needs the
30
+ * other answer: whether the thing it named is still there. Ending a session it did not name is
31
+ * not a recoverable mistake (#983).
32
+ */
33
+ departed(sessionId: string): SessionIdentity | undefined;
25
34
  list(): SessionInfo[];
26
35
  /** Continuity for one session — "has this been attached the whole time I am reasoning about?" */
27
36
  attachmentOf(sessionId: string): ReturnType<AttachmentHistory['of']>;
@@ -30,6 +39,10 @@ export declare class SessionManager {
30
39
  count(): number;
31
40
  /** Wire the diagnosis provider (daemon boot). Absent ⇒ the plain, static message. */
32
41
  setNoSessionHint(hint: (() => string | undefined) | undefined): void;
42
+ /** Wire the short rendering. Registered from the same `explainNoSession` call as the hint. */
43
+ setNoSessionLead(lead: (() => string | undefined) | undefined): void;
44
+ /** The lead and the next action, for a surface a person reads. */
45
+ noSessionLead(): string | undefined;
33
46
  /**
34
47
  * The same diagnosis, for a caller that is ASKING about sessions rather than failing to get one.
35
48
  *
@@ -3,7 +3,8 @@ 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
+ import { SESSION_DISCONNECTED_REASON } from './facts/session-replaced.js';
7
8
  /** The scheme://host:port of a URL, or undefined if it can't be parsed. Used to compare origins. */
8
9
  export function originOf(url) {
9
10
  if (url === undefined)
@@ -86,6 +87,8 @@ export class SessionManager {
86
87
  * id forgets its tombstone — the live session is the answer then.
87
88
  */
88
89
  #tombstones = new Map();
90
+ /** The tabs already open when each tombstoned session departed: siblings, never its successor. */
91
+ #siblingsAtDeparture = new Map();
89
92
  /**
90
93
  * The active project's scope, set once from the daemon's .reticle.json. When a tool resolves a session
91
94
  * without passing its own scope, this is applied — so auto-selection is project-scoped by default
@@ -127,6 +130,7 @@ export class SessionManager {
127
130
  declareDrivenRedactionKeys(session.id, session.redactKeys);
128
131
  this.#attachment.attached(session.id);
129
132
  this.#tombstones.delete(session.id);
133
+ this.#siblingsAtDeparture.delete(session.id);
130
134
  return previous;
131
135
  }
132
136
  /**
@@ -143,7 +147,7 @@ export class SessionManager {
143
147
  remove(session) {
144
148
  if (this.#sessions.get(session.id) !== session)
145
149
  return false;
146
- session.rejectAll('session disconnected');
150
+ session.rejectAll(SESSION_DISCONNECTED_REASON);
147
151
  forgetDrivenRedactionKeys(session.id);
148
152
  // Recorded, not forgotten: a session that comes back needs its gap measured, and a listing after
149
153
  // the reconnect is exactly where that matters.
@@ -157,6 +161,9 @@ export class SessionManager {
157
161
  return;
158
162
  if (this.#tombstones.has(session.id))
159
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)));
160
167
  this.#tombstones.set(session.id, {
161
168
  id: session.id,
162
169
  url: session.url,
@@ -167,13 +174,17 @@ export class SessionManager {
167
174
  if (oldest === undefined)
168
175
  break;
169
176
  this.#tombstones.delete(oldest);
177
+ this.#siblingsAtDeparture.delete(oldest);
170
178
  }
171
179
  }
172
180
  #successorOf(sessionId) {
173
181
  const departed = this.#tombstones.get(sessionId);
174
182
  if (departed === undefined)
175
183
  return undefined;
176
- 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) => ({
177
188
  id: s.id,
178
189
  url: s.url,
179
190
  ...(s.projectId === undefined ? {} : { projectId: s.projectId }),
@@ -185,6 +196,17 @@ export class SessionManager {
185
196
  get(sessionId) {
186
197
  return this.#sessions.get(sessionId);
187
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
+ }
188
210
  list() {
189
211
  return [...this.#sessions.values()].map((s) => {
190
212
  const info = s.info();
@@ -246,10 +268,27 @@ export class SessionManager {
246
268
  * different branches. See no-session-watch.ts.
247
269
  */
248
270
  #noSessionReason;
271
+ /**
272
+ * The same diagnosis with the differential left off: the lead and the next action, nothing else.
273
+ *
274
+ * Two renderings because there are two readers, and only one of them can spend the words. An
275
+ * agent about to burn a drive on a guess wants the ranked causes, the port-scan caveat and the
276
+ * lease offer. The person watching `reticle init` fail wants to know what is wrong and what to
277
+ * type, and was instead handed roughly five hundred words in one paragraph.
278
+ */
279
+ #noSessionLead;
249
280
  /** Wire the diagnosis provider (daemon boot). Absent ⇒ the plain, static message. */
250
281
  setNoSessionHint(hint) {
251
282
  this.#noSessionHint = hint;
252
283
  }
284
+ /** Wire the short rendering. Registered from the same `explainNoSession` call as the hint. */
285
+ setNoSessionLead(lead) {
286
+ this.#noSessionLead = lead;
287
+ }
288
+ /** The lead and the next action, for a surface a person reads. */
289
+ noSessionLead() {
290
+ return this.#noSessionLead?.();
291
+ }
253
292
  /**
254
293
  * The same diagnosis, for a caller that is ASKING about sessions rather than failing to get one.
255
294
  *
@@ -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
+ }
@@ -1,5 +1,5 @@
1
1
  import type { WebSocket } from 'ws';
2
- import type { ChannelId, ImpactSnapshot } from '@reticlehq/core';
2
+ import { type ChannelId, type ImpactSnapshot, type ProjectId } from '@reticlehq/core';
3
3
  import type { HandshakeFacts } from './facts/handshake-facts.js';
4
4
  import { LastAct } from './last-act.js';
5
5
  import { GapLedger } from '@reticlehq/engine/evidence/gap-ledger.js';
@@ -7,7 +7,7 @@ import { CaptureLedger } from '../../surface/tools/feature-capture.js';
7
7
  import { type PageRuntime } from './timing/command-timeout.js';
8
8
  import { type SessionHealth } from './session-health.js';
9
9
  export type { SessionHealth };
10
- import { PresenterTone, SessionState, type BrowserBrand, type CommandResult, type JournalAction, type HelloMessage, type HumanControlData, type ReticleEvent } from '@reticlehq/core';
10
+ import { PresenterTone, SessionState, type BrowserBrand, type CommandResult, type JournalAction, type JournalWriteLoss, type HelloMessage, type HumanControlData, type ReticleEvent } from '@reticlehq/core';
11
11
  import type { JournalReader, JournalRecorder } from '../../memory/journal/journal-recorder.js';
12
12
  import { type EventQueryOptions } from '../../memory/journal/journal-query.js';
13
13
  import { type AmbientCounts } from '@reticlehq/engine/window/ambient.js';
@@ -25,8 +25,14 @@ type Clock = () => number;
25
25
  export declare class Session implements HandshakeFacts {
26
26
  #private;
27
27
  readonly id: string;
28
- /** Stable build-stamped project identity; undefined for v1.0 SDKs that omit it. */
29
- readonly projectId: string | undefined;
28
+ /**
29
+ * Stable build-stamped project identity; undefined for SDKs old enough to omit it.
30
+ *
31
+ * MINTED HERE. This is one of the two places a projectId enters the daemon with its provenance
32
+ * intact: the field is `projectId` on a zod-parsed HELLO, so the page said what it is. Every
33
+ * downstream reader takes the brand from this property rather than re-blessing a string.
34
+ */
35
+ readonly projectId: ProjectId | undefined;
30
36
  /**
31
37
  * The `.reticle` directory this session's evidence belongs in — impact counters AND capsules.
32
38
  *
@@ -38,6 +44,11 @@ export declare class Session implements HandshakeFacts {
38
44
  * root, which is what every counter did unconditionally before this existed.
39
45
  */
40
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[];
41
52
  url: string;
42
53
  title: string;
43
54
  adapters: string[];
@@ -118,12 +129,13 @@ export declare class Session implements HandshakeFacts {
118
129
  */
119
130
  setJournal(recorder: JournalRecorder, reader?: JournalReader): void;
120
131
  /**
121
- * Journal-backed event query: the ring buffer's events, merged with the durable journal **only when
122
- * the buffer has evicted** (so a healthy session pays no disk cost), then filtered by since/until/
123
- * actionId. This is how "what did action N cause" is answered after the buffer has dropped the
124
- * evidence — the substrate's whole point. The sync `eventsSince`/`window` stay for the hot path.
132
+ * Journal-backed event query — the rule itself lives in `readQueryEvents`, beside the merge and
133
+ * filter it is made of. This is how "what did action N cause" is answered after the buffer has
134
+ * dropped the evidence. The sync `eventsSince`/`window` stay for the hot path.
125
135
  */
126
136
  queryEvents(options: EventQueryOptions): Promise<ReticleEvent[]>;
137
+ /** What the durable ledger could not WRITE — see `readJournalWriteLoss` for the rule and why. */
138
+ journalWriteLoss(): Promise<JournalWriteLoss | undefined>;
127
139
  /**
128
140
  * Open an action-attribution window: events observed until finishAction attribute to the returned
129
141
  * action id. Ids are minted independently of command correlation ids so the journal is self-contained.
@@ -200,9 +212,13 @@ export declare class Session implements HandshakeFacts {
200
212
  dropped: number;
201
213
  };
202
214
  /**
203
- * Did the buffer lose scarce evidence from a window opened at `cursor`? The input to whether a
215
+ * Did the EVENT STORE lose scarce evidence from a window opened at `cursor`? The input to whether a
204
216
  * verdict's capture was clean — see `RingBuffer.lostSince`, and never the raw drop counter, which
205
- * moves for the age and churn evictions that every live page produces continuously.
217
+ * moves for the age and churn evictions that every live page produces continuously. The journal is
218
+ * the other half of that store, so a durable read that could not reach back to `cursor` lost
219
+ * exactly what an eviction would have. Both boundaries are INCLUSIVE (`t` is a millisecond many
220
+ * records share) and an absent `lostThroughT` impeaches every window rather than none — the
221
+ * conservative direction, both times.
206
222
  */
207
223
  lostSince(cursor: number): boolean;
208
224
  onEvent(listener: (event: ReticleEvent) => void): () => void;