@reticlehq/server 2.4.0 → 2.6.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 (472) hide show
  1. package/SKILL.md +719 -0
  2. package/dist/bridge/auth-failure-reason.d.ts +19 -0
  3. package/dist/bridge/auth-failure-reason.js +29 -0
  4. package/dist/{bridge-security.d.ts → bridge/bridge-security.d.ts} +1 -1
  5. package/dist/{bridge-security.js → bridge/bridge-security.js} +1 -1
  6. package/dist/{bridge.d.ts → bridge/bridge.d.ts} +13 -1
  7. package/dist/{bridge.js → bridge/bridge.js} +95 -15
  8. package/dist/{bridge.test-harness.d.ts → bridge/bridge.test-harness.d.ts} +9 -11
  9. package/dist/{bridge.test-harness.js → bridge/bridge.test-harness.js} +18 -10
  10. package/dist/{pairing-token.js → bridge/pairing-token.js} +1 -1
  11. package/dist/capsule/capsule-store.d.ts +8 -3
  12. package/dist/capsule/capsule-store.js +7 -2
  13. package/dist/capsule/capsule.js +3 -3
  14. package/dist/capsule/causal-summary.d.ts +8 -0
  15. package/dist/capsule/causal-summary.js +22 -12
  16. package/dist/capsule/divergence.d.ts +9 -4
  17. package/dist/capsule/divergence.js +8 -8
  18. package/dist/capsule/predicate-to-links.js +11 -8
  19. package/dist/cli/cli-doctor.d.ts +1 -0
  20. package/dist/cli/cli-doctor.js +135 -0
  21. package/dist/{cli-flow-commands.d.ts → cli/cli-flow-commands.d.ts} +2 -2
  22. package/dist/{cli-flow-commands.js → cli/cli-flow-commands.js} +15 -15
  23. package/dist/{cli-launch.d.ts → cli/cli-launch.d.ts} +32 -3
  24. package/dist/cli/cli-launch.js +171 -0
  25. package/dist/cli/cli-parse-feedback.d.ts +31 -0
  26. package/dist/cli/cli-parse-feedback.js +56 -0
  27. package/dist/{cli-parse.d.ts → cli/cli-parse.d.ts} +6 -19
  28. package/dist/{cli-parse.js → cli/cli-parse.js} +118 -81
  29. package/dist/{cli-port.d.ts → cli/cli-port.d.ts} +12 -0
  30. package/dist/{cli-port.js → cli/cli-port.js} +9 -9
  31. package/dist/cli/cli-update-commands.d.ts +4 -0
  32. package/dist/cli/cli-update-commands.js +93 -0
  33. package/dist/{cli-verify.js → cli/cli-verify.js} +14 -14
  34. package/dist/{cloud-cli.js → cli/cloud-cli.js} +29 -24
  35. package/dist/cli/doctor-daemon-line.d.ts +37 -0
  36. package/dist/cli/doctor-daemon-line.js +35 -0
  37. package/dist/cli/port-holder.d.ts +43 -0
  38. package/dist/cli/port-holder.js +99 -0
  39. package/dist/cli.d.ts +2 -2
  40. package/dist/cli.js +302 -100
  41. package/dist/cloud/cloud-config.d.ts +0 -1
  42. package/dist/cloud/cloud-config.js +9 -9
  43. package/dist/cloud/cloud-sync.d.ts +1 -7
  44. package/dist/cloud/cloud-sync.js +12 -12
  45. package/dist/crawl/crawl-empty.d.ts +16 -0
  46. package/dist/crawl/crawl-empty.js +24 -0
  47. package/dist/crawl/crawl-tools.js +4 -1
  48. package/dist/crawl/crawl.js +26 -7
  49. package/dist/daemon/daemon-resilience.d.ts +73 -0
  50. package/dist/daemon/daemon-resilience.js +279 -0
  51. package/dist/daemon/daemon-usefulness.d.ts +61 -0
  52. package/dist/daemon/daemon-usefulness.js +62 -0
  53. package/dist/{daemon.d.ts → daemon/daemon.d.ts} +33 -8
  54. package/dist/{daemon.js → daemon/daemon.js} +105 -48
  55. package/dist/daemon/heartbeat.d.ts +115 -0
  56. package/dist/daemon/heartbeat.js +141 -0
  57. package/dist/daemon/idle-grace.d.ts +32 -0
  58. package/dist/daemon/idle-grace.js +41 -0
  59. package/dist/{idle-shutdown.d.ts → daemon/idle-shutdown.d.ts} +23 -2
  60. package/dist/{idle-shutdown.js → daemon/idle-shutdown.js} +37 -6
  61. package/dist/daemon/port-presence.d.ts +76 -0
  62. package/dist/daemon/port-presence.js +93 -0
  63. package/dist/daemon/state-dir.d.ts +7 -0
  64. package/dist/daemon/state-dir.js +59 -0
  65. package/dist/daemon/unclean-predecessor.d.ts +23 -0
  66. package/dist/daemon/unclean-predecessor.js +27 -0
  67. package/dist/domain/domain-model.js +2 -2
  68. package/dist/events/body-failures.js +8 -8
  69. package/dist/events/contradictions.d.ts +1 -1
  70. package/dist/events/contradictions.js +60 -10
  71. package/dist/events/echo-mismatch.js +10 -10
  72. package/dist/events/event-filters.js +6 -6
  73. package/dist/events/json-salvage.d.ts +2 -1
  74. package/dist/events/json-salvage.js +6 -6
  75. package/dist/events/observed-in-window.d.ts +12 -0
  76. package/dist/events/observed-in-window.js +21 -0
  77. package/dist/events/predicate-eval.d.ts +53 -20
  78. package/dist/events/predicate-eval.js +179 -55
  79. package/dist/events/predicate-parse.d.ts +23 -0
  80. package/dist/events/predicate-parse.js +54 -0
  81. package/dist/events/predicate.d.ts +7 -2
  82. package/dist/events/predicate.js +126 -35
  83. package/dist/events/reconcile.js +5 -5
  84. package/dist/events/ring-buffer.js +2 -17
  85. package/dist/events/stale-response.js +6 -6
  86. package/dist/events/testid-near-miss.d.ts +22 -0
  87. package/dist/events/testid-near-miss.js +33 -0
  88. package/dist/events/unit-mismatch.js +8 -8
  89. package/dist/flows/affected-tools.js +19 -4
  90. package/dist/flows/affected.js +1 -1
  91. package/dist/flows/annotate-target.d.ts +1 -0
  92. package/dist/flows/annotate-target.js +20 -0
  93. package/dist/flows/annotate-tools.js +25 -3
  94. package/dist/flows/annotate.js +17 -4
  95. package/dist/flows/assertion-integrity.d.ts +2 -1
  96. package/dist/flows/assertion-tiers-store.d.ts +3 -66
  97. package/dist/flows/assertion-tiers-store.js +2 -2
  98. package/dist/flows/attributed-failure.d.ts +17 -0
  99. package/dist/flows/attributed-failure.js +20 -0
  100. package/dist/flows/buddy-status.d.ts +2 -1
  101. package/dist/flows/buddy-status.js +2 -2
  102. package/dist/flows/coverage.d.ts +3 -2
  103. package/dist/flows/coverage.js +2 -2
  104. package/dist/flows/decision.js +24 -9
  105. package/dist/flows/empty-flow.d.ts +6 -0
  106. package/dist/flows/empty-flow.js +24 -0
  107. package/dist/flows/flake-store.d.ts +0 -1
  108. package/dist/flows/flake-store.js +1 -1
  109. package/dist/flows/flake.d.ts +2 -1
  110. package/dist/flows/flake.js +2 -2
  111. package/dist/flows/flow-classify.js +12 -0
  112. package/dist/flows/flow-replay-run.js +4 -4
  113. package/dist/flows/flow-replay.d.ts +6 -8
  114. package/dist/flows/flow-replay.js +132 -48
  115. package/dist/flows/flow-report.js +1 -1
  116. package/dist/flows/flow-scope.d.ts +1 -6
  117. package/dist/flows/flow-step-runners.d.ts +26 -6
  118. package/dist/flows/flow-step-runners.js +156 -6
  119. package/dist/flows/flow-success.js +20 -15
  120. package/dist/flows/flow-tools.js +63 -21
  121. package/dist/flows/flows.js +15 -11
  122. package/dist/flows/gate.d.ts +4 -3
  123. package/dist/flows/gate.js +1 -1
  124. package/dist/flows/heal-run.d.ts +0 -8
  125. package/dist/flows/heal-run.js +5 -5
  126. package/dist/flows/heal.js +3 -3
  127. package/dist/flows/predicate-to-expect.d.ts +38 -0
  128. package/dist/flows/predicate-to-expect.js +95 -0
  129. package/dist/flows/replay.d.ts +16 -1
  130. package/dist/flows/replay.js +58 -22
  131. package/dist/flows/role-anchor-nearest.d.ts +6 -0
  132. package/dist/flows/role-anchor-nearest.js +45 -0
  133. package/dist/flows/role-drift-reason.d.ts +20 -0
  134. package/dist/flows/role-drift-reason.js +39 -0
  135. package/dist/flows/server-verify.d.ts +1 -1
  136. package/dist/flows/server-verify.js +5 -5
  137. package/dist/flows/suite-flakes.d.ts +33 -0
  138. package/dist/flows/suite-flakes.js +32 -0
  139. package/dist/flows/verify-change-tools.js +32 -10
  140. package/dist/flows/verify-change-verdict.d.ts +15 -0
  141. package/dist/flows/verify-change-verdict.js +25 -0
  142. package/dist/flows/watch-batcher.d.ts +3 -2
  143. package/dist/honesty/already-true.d.ts +2 -0
  144. package/dist/honesty/already-true.js +31 -0
  145. package/dist/honesty/blind-spots.d.ts +35 -3
  146. package/dist/honesty/blind-spots.js +57 -12
  147. package/dist/honesty/honesty.d.ts +3 -2
  148. package/dist/honesty/honesty.js +4 -4
  149. package/dist/honesty/uncaptured-bodies.js +1 -1
  150. package/dist/honesty/unread-outcome.js +2 -2
  151. package/dist/honesty/verified.d.ts +31 -3
  152. package/dist/honesty/verified.js +64 -8
  153. package/dist/http-server.js +23 -7
  154. package/dist/hunt/hunt-report.d.ts +2 -1
  155. package/dist/hunt/hunt-report.js +2 -2
  156. package/dist/index.d.ts +9 -6
  157. package/dist/index.js +41 -25
  158. package/dist/init/agent-rules.d.ts +20 -1
  159. package/dist/init/agent-rules.js +64 -8
  160. package/dist/init/app-choice.d.ts +20 -0
  161. package/dist/init/app-choice.js +25 -0
  162. package/dist/init/astro-host.d.ts +21 -0
  163. package/dist/init/astro-host.js +27 -0
  164. package/dist/init/astro-patch.d.ts +16 -0
  165. package/dist/init/astro-patch.js +240 -0
  166. package/dist/init/capabilities.js +4 -1
  167. package/dist/init/cra.d.ts +50 -0
  168. package/dist/init/cra.js +106 -0
  169. package/dist/init/cursor.js +39 -4
  170. package/dist/init/desktop-doctor.d.ts +3 -2
  171. package/dist/init/desktop-doctor.js +4 -4
  172. package/dist/init/detect.d.ts +9 -2
  173. package/dist/init/detect.js +24 -5
  174. package/dist/init/mcp-clients.d.ts +81 -0
  175. package/dist/init/mcp-clients.js +248 -0
  176. package/dist/init/mcp-registered.d.ts +12 -0
  177. package/dist/init/mcp-registered.js +14 -0
  178. package/dist/init/mcp.d.ts +27 -2
  179. package/dist/init/mcp.js +40 -4
  180. package/dist/init/next-patch.d.ts +2 -4
  181. package/dist/init/next-patch.js +109 -19
  182. package/dist/init/node-io.js +29 -6
  183. package/dist/init/plan-framework.d.ts +16 -1
  184. package/dist/init/plan-framework.js +166 -8
  185. package/dist/init/plan.d.ts +85 -6
  186. package/dist/init/plan.js +196 -33
  187. package/dist/init/project-id.d.ts +5 -9
  188. package/dist/init/project-id.js +10 -22
  189. package/dist/init/run.d.ts +23 -1
  190. package/dist/init/run.js +338 -47
  191. package/dist/init/slash-command.d.ts +9 -0
  192. package/dist/init/slash-command.js +10 -1
  193. package/dist/init/snippets.d.ts +7 -10
  194. package/dist/init/snippets.js +44 -15
  195. package/dist/init/vite-config.js +1 -1
  196. package/dist/init/windows-quote.d.ts +38 -0
  197. package/dist/init/windows-quote.js +70 -0
  198. package/dist/init/workspace-apps.d.ts +23 -0
  199. package/dist/init/workspace-apps.js +68 -0
  200. package/dist/input/driven-redaction.js +1 -1
  201. package/dist/input/network-detail.js +8 -8
  202. package/dist/input/network-mock-tools.js +2 -2
  203. package/dist/input/network-mock.js +4 -4
  204. package/dist/input/real-input.js +8 -3
  205. package/dist/input/scroll-find.js +2 -2
  206. package/dist/input/scroll-tools.d.ts +0 -5
  207. package/dist/input/scroll-tools.js +11 -4
  208. package/dist/input/viewport-tools.js +2 -2
  209. package/dist/journal/ambient-store.d.ts +0 -2
  210. package/dist/journal/ambient-store.js +1 -1
  211. package/dist/journal/ambient.js +1 -1
  212. package/dist/journal/attach-journal.d.ts +2 -1
  213. package/dist/journal/deviation-report.js +6 -6
  214. package/dist/journal/deviation-service.js +1 -1
  215. package/dist/journal/envelope-store.d.ts +0 -2
  216. package/dist/journal/envelope-store.js +1 -1
  217. package/dist/journal/envelope.d.ts +2 -3
  218. package/dist/journal/envelope.js +3 -3
  219. package/dist/journal/journal-query.js +1 -1
  220. package/dist/journal/journal-recorder.d.ts +2 -1
  221. package/dist/journal/journal-recorder.js +2 -2
  222. package/dist/journal/rollups.js +9 -9
  223. package/dist/journal/session-end.d.ts +2 -1
  224. package/dist/journal/session-journal.js +4 -4
  225. package/dist/license/license.d.ts +0 -14
  226. package/dist/license/license.js +8 -8
  227. package/dist/log.d.ts +10 -0
  228. package/dist/log.js +11 -1
  229. package/dist/mcp/mcp-is-error.d.ts +13 -0
  230. package/dist/mcp/mcp-is-error.js +18 -0
  231. package/dist/mcp/mcp-outage.d.ts +32 -0
  232. package/dist/mcp/mcp-outage.js +55 -0
  233. package/dist/mcp/mcp-proxy.d.ts +134 -0
  234. package/dist/mcp/mcp-proxy.js +720 -0
  235. package/dist/{mcp.d.ts → mcp/mcp.d.ts} +13 -10
  236. package/dist/{mcp.js → mcp/mcp.js} +139 -37
  237. package/dist/mcp/proxy-handshake.d.ts +3 -0
  238. package/dist/mcp/proxy-handshake.js +54 -0
  239. package/dist/mcp/proxy-lifecycle.d.ts +64 -0
  240. package/dist/mcp/proxy-lifecycle.js +70 -0
  241. package/dist/mcp/tool-catalog-cache.d.ts +29 -0
  242. package/dist/mcp/tool-catalog-cache.js +55 -0
  243. package/dist/oracles/propose-consequences.d.ts +2 -1
  244. package/dist/oracles/propose-consequences.js +16 -11
  245. package/dist/oracles/self-instrument.d.ts +2 -1
  246. package/dist/oracles/self-instrument.js +1 -1
  247. package/dist/phenomena/phenomena.js +3 -3
  248. package/dist/platform.d.ts +16 -0
  249. package/dist/platform.js +16 -0
  250. package/dist/pool/browser-pool.d.ts +9 -0
  251. package/dist/pool/browser-pool.js +38 -9
  252. package/dist/pool/playwright-launcher.d.ts +15 -2
  253. package/dist/pool/playwright-launcher.js +21 -2
  254. package/dist/project/fs-port.js +1 -1
  255. package/dist/project/project-tools.js +2 -2
  256. package/dist/runs/build-verification-run.js +1 -1
  257. package/dist/runs/repair-prompt.d.ts +0 -6
  258. package/dist/runs/repair-prompt.js +3 -3
  259. package/dist/runs/risk-classify.js +1 -1
  260. package/dist/runs/run-diff.d.ts +0 -2
  261. package/dist/runs/run-diff.js +3 -3
  262. package/dist/runs/run-tools.js +2 -2
  263. package/dist/runs/verification-sync.js +2 -2
  264. package/dist/runs/verify-http.js +1 -1
  265. package/dist/runs/verify-server.js +1 -1
  266. package/dist/session/attachment-history.d.ts +46 -0
  267. package/dist/session/attachment-history.js +65 -0
  268. package/dist/session/command-timeout.d.ts +11 -3
  269. package/dist/session/command-timeout.js +2 -2
  270. package/dist/session/control-envelope.d.ts +6 -0
  271. package/dist/session/control-envelope.js +7 -1
  272. package/dist/session/dev-server-probe.d.ts +13 -0
  273. package/dist/session/dev-server-probe.js +52 -0
  274. package/dist/session/last-act.d.ts +17 -3
  275. package/dist/session/last-act.js +16 -4
  276. package/dist/session/live-control-tools.js +7 -2
  277. package/dist/session/live-control.js +1 -1
  278. package/dist/session/looks-like-dev-server.d.ts +14 -0
  279. package/dist/session/looks-like-dev-server.js +25 -0
  280. package/dist/session/no-session-diagnosis.d.ts +45 -0
  281. package/dist/session/no-session-diagnosis.js +120 -0
  282. package/dist/session/no-session-watch.d.ts +21 -0
  283. package/dist/session/no-session-watch.js +69 -0
  284. package/dist/session/observed-state.d.ts +4 -0
  285. package/dist/session/observed-state.js +19 -1
  286. package/dist/session/output-budget.js +1 -1
  287. package/dist/session/pending-commands.js +1 -0
  288. package/dist/session/session-health.d.ts +8 -1
  289. package/dist/session/session-health.js +8 -7
  290. package/dist/session/session-info.d.ts +43 -8
  291. package/dist/session/session-info.js +46 -1
  292. package/dist/session/session-lease.d.ts +0 -11
  293. package/dist/session/session-lease.js +1 -1
  294. package/dist/session/session-manager.d.ts +6 -0
  295. package/dist/session/session-manager.js +124 -12
  296. package/dist/session/session-readiness.d.ts +1 -1
  297. package/dist/session/session-readiness.js +6 -1
  298. package/dist/session/session-reaper.js +1 -1
  299. package/dist/session/session-reconnect.d.ts +32 -0
  300. package/dist/session/session-reconnect.js +42 -0
  301. package/dist/session/session.d.ts +15 -2
  302. package/dist/session/session.js +77 -26
  303. package/dist/session/state-select.d.ts +1 -1
  304. package/dist/session/state-select.js +1 -1
  305. package/dist/status-payload.d.ts +19 -0
  306. package/dist/status-payload.js +11 -0
  307. package/dist/telemetry/argument-shape.js +10 -1
  308. package/dist/telemetry/browser-mode.d.ts +27 -0
  309. package/dist/telemetry/browser-mode.js +33 -0
  310. package/dist/telemetry/bug-found.d.ts +17 -21
  311. package/dist/telemetry/bug-found.js +120 -8
  312. package/dist/telemetry/cli-telemetry.d.ts +2 -12
  313. package/dist/telemetry/cli-telemetry.js +27 -4
  314. package/dist/telemetry/crash-cause.d.ts +47 -0
  315. package/dist/telemetry/crash-cause.js +104 -0
  316. package/dist/telemetry/daemon-telemetry.d.ts +38 -1
  317. package/dist/telemetry/daemon-telemetry.js +37 -12
  318. package/dist/telemetry/dev-repo.js +4 -4
  319. package/dist/telemetry/error-class.d.ts +26 -0
  320. package/dist/telemetry/error-class.js +52 -0
  321. package/dist/telemetry/error-fingerprint.js +21 -2
  322. package/dist/telemetry/feedback-cli.d.ts +8 -3
  323. package/dist/telemetry/feedback-cli.js +52 -8
  324. package/dist/telemetry/feedback-context.d.ts +23 -6
  325. package/dist/telemetry/feedback-context.js +105 -5
  326. package/dist/telemetry/feedback-delivery.d.ts +20 -0
  327. package/dist/telemetry/feedback-delivery.js +30 -0
  328. package/dist/telemetry/feedback-outbox.d.ts +22 -0
  329. package/dist/telemetry/feedback-outbox.js +87 -0
  330. package/dist/telemetry/feedback.d.ts +22 -1
  331. package/dist/telemetry/feedback.js +67 -8
  332. package/dist/telemetry/git-facts.d.ts +7 -0
  333. package/dist/telemetry/git-facts.js +5 -5
  334. package/dist/telemetry/project-profile.js +2 -1
  335. package/dist/telemetry/session-metrics.d.ts +47 -3
  336. package/dist/telemetry/session-metrics.js +315 -15
  337. package/dist/telemetry/telemetry.d.ts +45 -2
  338. package/dist/telemetry/telemetry.js +150 -37
  339. package/dist/telemetry/verification-of.d.ts +22 -0
  340. package/dist/telemetry/verification-of.js +84 -0
  341. package/dist/tools/act-capsule.js +2 -2
  342. package/dist/tools/act-danger.d.ts +12 -0
  343. package/dist/tools/act-danger.js +20 -3
  344. package/dist/tools/act-helpers.js +4 -4
  345. package/dist/tools/act-tools.d.ts +0 -8
  346. package/dist/tools/act-tools.js +114 -118
  347. package/dist/tools/act-view.js +7 -7
  348. package/dist/tools/alias-args.d.ts +17 -0
  349. package/dist/tools/alias-args.js +25 -0
  350. package/dist/tools/assert-grade.js +9 -9
  351. package/dist/tools/assert-verdict.d.ts +9 -1
  352. package/dist/tools/assert-verdict.js +21 -6
  353. package/dist/tools/browser-tools.js +36 -11
  354. package/dist/tools/contract-tools.d.ts +0 -5
  355. package/dist/tools/contract-tools.js +27 -1
  356. package/dist/tools/coverage-identity.d.ts +34 -0
  357. package/dist/tools/coverage-identity.js +46 -0
  358. package/dist/tools/coverage-tools.js +10 -14
  359. package/dist/tools/dynamic-tools.d.ts +2 -1
  360. package/dist/tools/dynamic-tools.js +96 -19
  361. package/dist/tools/error-recovery.d.ts +34 -2
  362. package/dist/tools/error-recovery.js +255 -4
  363. package/dist/tools/feedback-invite.d.ts +48 -0
  364. package/dist/tools/feedback-invite.js +71 -0
  365. package/dist/tools/feedback-tools.d.ts +12 -1
  366. package/dist/tools/feedback-tools.js +28 -5
  367. package/dist/tools/invoke-tool.d.ts +0 -12
  368. package/dist/tools/invoke-tool.js +126 -33
  369. package/dist/tools/lease-hint.d.ts +17 -0
  370. package/dist/tools/lease-hint.js +24 -0
  371. package/dist/tools/lease-tools.js +6 -5
  372. package/dist/tools/merge-tools.d.ts +10 -1
  373. package/dist/tools/merge-tools.js +9 -3
  374. package/dist/tools/merged-name-redirect.d.ts +13 -0
  375. package/dist/tools/merged-name-redirect.js +55 -0
  376. package/dist/tools/navigate-arrival.d.ts +26 -0
  377. package/dist/tools/navigate-arrival.js +60 -0
  378. package/dist/tools/navigate-result.d.ts +31 -0
  379. package/dist/tools/navigate-result.js +38 -0
  380. package/dist/tools/observe-tools.js +72 -20
  381. package/dist/tools/observed-nothing.d.ts +25 -0
  382. package/dist/tools/observed-nothing.js +33 -0
  383. package/dist/tools/query-paginate.js +2 -2
  384. package/dist/tools/query-shape.d.ts +18 -0
  385. package/dist/tools/query-shape.js +39 -0
  386. package/dist/tools/read-tools.js +81 -20
  387. package/dist/tools/real-input-attempt.d.ts +28 -0
  388. package/dist/tools/real-input-attempt.js +93 -0
  389. package/dist/tools/reconcile-tools.js +7 -7
  390. package/dist/tools/reload-result.d.ts +14 -0
  391. package/dist/tools/reload-result.js +29 -0
  392. package/dist/tools/snapshot-delta.d.ts +4 -5
  393. package/dist/tools/snapshot-delta.js +0 -0
  394. package/dist/tools/tool-kit.d.ts +16 -0
  395. package/dist/tools/tool-surface.d.ts +83 -0
  396. package/dist/tools/tool-surface.js +217 -0
  397. package/dist/tools/tools-helpers.js +6 -6
  398. package/dist/tools/tools.d.ts +15 -0
  399. package/dist/tools/tools.js +53 -17
  400. package/dist/tools/unadvertised-help.d.ts +5 -0
  401. package/dist/tools/unadvertised-help.js +39 -0
  402. package/dist/trace.d.ts +69 -0
  403. package/dist/trace.js +150 -0
  404. package/dist/update/nudge-credit.d.ts +4 -0
  405. package/dist/update/nudge-credit.js +53 -0
  406. package/dist/update/sdk-sync.d.ts +21 -0
  407. package/dist/update/sdk-sync.js +42 -0
  408. package/dist/update/update-checker.js +3 -3
  409. package/dist/update/update-nudge.d.ts +63 -6
  410. package/dist/update/update-nudge.js +128 -15
  411. package/dist/update/updater.d.ts +1 -9
  412. package/dist/update/updater.js +13 -8
  413. package/dist/version/peer-announce.d.ts +4 -0
  414. package/dist/version/peer-announce.js +30 -0
  415. package/dist/{server-version.js → version/server-version.js} +5 -1
  416. package/dist/version/version-nudge.d.ts +28 -0
  417. package/dist/version/version-nudge.js +40 -0
  418. package/dist/version/version-skew.d.ts +66 -0
  419. package/dist/version/version-skew.js +71 -0
  420. package/dist/visual/visual-diff.js +2 -2
  421. package/dist/visual/visual-tools.js +4 -4
  422. package/docs/README.md +55 -0
  423. package/docs/agent-cheatsheet.md +85 -0
  424. package/docs/architecture.md +113 -0
  425. package/docs/benchmarks.md +255 -0
  426. package/docs/debugging.md +102 -0
  427. package/docs/deploy-checks.md +69 -0
  428. package/docs/desktop-apps.md +256 -0
  429. package/docs/enterprise.md +62 -0
  430. package/docs/fixtures-dispatch-receiver.yml +76 -0
  431. package/docs/fixtures.md +45 -0
  432. package/docs/flows.md +166 -0
  433. package/docs/gate-plan.md +98 -0
  434. package/docs/gates.md +117 -0
  435. package/docs/getting-started.md +469 -0
  436. package/docs/human-control.md +63 -0
  437. package/docs/integration-patterns.md +171 -0
  438. package/docs/local-registry.md +126 -0
  439. package/docs/matrix/2.5.0/claude-code-machine.json +19 -0
  440. package/docs/matrix/2.5.0/codex-machine.json +20 -0
  441. package/docs/matrix/2.5.0/cursor-machine.json +22 -0
  442. package/docs/matrix/2.5.0/gemini-machine.json +22 -0
  443. package/docs/matrix/2.5.0/opencode-machine.json +22 -0
  444. package/docs/matrix/2.5.0/vscode-machine.json +22 -0
  445. package/docs/matrix/2.5.0/windsurf-machine.json +22 -0
  446. package/docs/matrix/MATRIX.md +26 -0
  447. package/docs/matrix/README.md +59 -0
  448. package/docs/mint.json +61 -0
  449. package/docs/multi-agent-testing.md +55 -0
  450. package/docs/platform-integration.md +122 -0
  451. package/docs/system-map.md +152 -0
  452. package/docs/telemetry-contract.md +214 -0
  453. package/docs/telemetry.md +191 -0
  454. package/docs/testing.md +82 -0
  455. package/docs/token-efficiency.md +80 -0
  456. package/docs/usage.md +1046 -0
  457. package/package.json +8 -5
  458. package/dist/cli-doctor.d.ts +0 -6
  459. package/dist/cli-doctor.js +0 -60
  460. package/dist/cli-launch.js +0 -112
  461. package/dist/daemon-resilience.d.ts +0 -22
  462. package/dist/daemon-resilience.js +0 -85
  463. package/dist/mcp-proxy.d.ts +0 -67
  464. package/dist/mcp-proxy.js +0 -337
  465. package/dist/tools/profiles.d.ts +0 -37
  466. package/dist/tools/profiles.js +0 -139
  467. /package/dist/{pairing-token.d.ts → bridge/pairing-token.d.ts} +0 -0
  468. /package/dist/{token-auth.d.ts → bridge/token-auth.d.ts} +0 -0
  469. /package/dist/{token-auth.js → bridge/token-auth.js} +0 -0
  470. /package/dist/{cli-verify.d.ts → cli/cli-verify.d.ts} +0 -0
  471. /package/dist/{cloud-cli.d.ts → cli/cloud-cli.d.ts} +0 -0
  472. /package/dist/{server-version.d.ts → version/server-version.d.ts} +0 -0
@@ -1,4 +1,4 @@
1
- import { BUFFER_EVICTION_WARNING, THROTTLED_WARNING } from '@reticlehq/core';
1
+ import { BrowserBrand, BUFFER_EVICTION_WARNING, THROTTLED_WARNING } from '@reticlehq/core';
2
2
  /**
3
3
  * Buffer-honesty envelope for observe/network/console. When the ring buffer has evicted anything
4
4
  * (age/size cap), a "no such event" answer may be a false negative — so we attach the drop count and
@@ -7,7 +7,7 @@ import { BUFFER_EVICTION_WARNING, THROTTLED_WARNING } from '@reticlehq/core';
7
7
  */
8
8
  export function bufferEnvelope(session) {
9
9
  const { total, dropped } = session.bufferHealth();
10
- if (dropped === 0)
10
+ if (0 === dropped)
11
11
  return {};
12
12
  return { buffer: { held: total, dropped, note: BUFFER_EVICTION_WARNING } };
13
13
  }
@@ -31,7 +31,7 @@ export function healthEnvelope(session) {
31
31
  * warn-only so background testing never breaks.
32
32
  */
33
33
  export function refuseIfThrottled(session, refuse) {
34
- if (refuse === true && session.throttled()) {
34
+ if (true === refuse && session.throttled()) {
35
35
  throw new Error(`refusing to act: ${THROTTLED_WARNING}`);
36
36
  }
37
37
  }
@@ -42,9 +42,10 @@ export function refuseIfThrottled(session, refuse) {
42
42
  */
43
43
  export function readHealthEvent(data) {
44
44
  return {
45
- hidden: typeof data['hidden'] === 'boolean' ? data['hidden'] : undefined,
46
- focused: typeof data['focused'] === 'boolean' ? data['focused'] : undefined,
47
- runtime: typeof data['runtime'] === 'string' ? data['runtime'] : undefined,
48
- engine: typeof data['engine'] === 'string' ? data['engine'] : undefined,
45
+ hidden: 'boolean' === typeof data['hidden'] ? data['hidden'] : undefined,
46
+ focused: 'boolean' === typeof data['focused'] ? data['focused'] : undefined,
47
+ runtime: 'string' === typeof data['runtime'] ? data['runtime'] : undefined,
48
+ engine: 'string' === typeof data['engine'] ? data['engine'] : undefined,
49
+ brand: Object.values(BrowserBrand).find((known) => known === data['brand']),
49
50
  };
50
51
  }
@@ -1,18 +1,15 @@
1
- /**
2
- * The shape `reticle_sessions` returns for one connected tab.
3
- *
4
- * Split from session.ts when that file crossed the 600-line cap. A pure description of what a
5
- * session looks like from the outside has no reason to live inside the class that happens to build
6
- * it, and separating them keeps the wire-facing shape reviewable on its own.
7
- */
1
+ import type { SessionHealth } from './session-health.js';
8
2
  export interface SessionInfo {
9
3
  sessionId: string;
10
4
  url: string;
11
5
  /** Stable build-stamped project identity; absent for v1.0 SDKs that don't send it. */
12
6
  projectId?: string;
13
- title: string;
7
+ /** Absent when the page has no title — never the empty string. Fall back to `url`. */
8
+ title?: string;
14
9
  adapters: string[];
15
10
  hasCapabilities: boolean;
11
+ /** Present only when the page's SDK version differs from the daemon's — see version-skew.ts. */
12
+ versionSkew?: string;
16
13
  /** ms since the SDK last reported anything (silence ⇒ likely throttled). */
17
14
  lastSeenMs: number;
18
15
  hidden: boolean;
@@ -26,4 +23,42 @@ export interface SessionInfo {
26
23
  pendingMarks?: number;
27
24
  /** present with pendingMarks — nudges the agent to drain them with reticle_review. */
28
25
  review_suggestion?: string;
26
+ /**
27
+ * Continuity of this attachment — `{ connectedSinceMs, outages, lastOutage? }`.
28
+ *
29
+ * A tab that dropped for four seconds and came back is indistinguishable from one that never left
30
+ * (#117), and that difference decides whether a verdict over the window can be trusted. Attached
31
+ * by `SessionManager.list()`, which is the layer that sees every connect and disconnect.
32
+ */
33
+ attachment?: {
34
+ connectedSinceMs: number;
35
+ outages: number;
36
+ lastOutage?: {
37
+ startedMs: number;
38
+ durationMs: number;
39
+ };
40
+ };
29
41
  }
42
+ /** The read-only slice of a Session that the projection needs. Keeps this module class-free. */
43
+ interface SessionView {
44
+ id: string;
45
+ url: string;
46
+ projectId: string | undefined;
47
+ title: string;
48
+ adapters: string[];
49
+ hasCapabilities: boolean;
50
+ versionSkew: string | undefined;
51
+ hidden: boolean;
52
+ health: () => SessionHealth;
53
+ staleMs: () => number;
54
+ pendingMarkCount: () => number;
55
+ }
56
+ /**
57
+ * Project a live Session into the shape the tools return.
58
+ *
59
+ * Lives beside the SessionInfo type rather than on the class: it is a pure read of already-computed
60
+ * state, it is where every "what does the agent see about this tab" decision belongs, and Session is
61
+ * a stateful class already at its size cap — every field added to a listing was costing a line there.
62
+ */
63
+ export declare function buildSessionInfo(session: SessionView): SessionInfo;
64
+ export {};
@@ -1 +1,46 @@
1
- export {};
1
+ /**
2
+ * The shape `reticle_sessions` returns for one connected tab.
3
+ *
4
+ * Split from session.ts when that file crossed the 600-line cap. A pure description of what a
5
+ * session looks like from the outside has no reason to live inside the class that happens to build
6
+ * it, and separating them keeps the wire-facing shape reviewable on its own.
7
+ */
8
+ import { SESSION_LEASE } from '@reticlehq/core';
9
+ /**
10
+ * Project a live Session into the shape the tools return.
11
+ *
12
+ * Lives beside the SessionInfo type rather than on the class: it is a pure read of already-computed
13
+ * state, it is where every "what does the agent see about this tab" decision belongs, and Session is
14
+ * a stateful class already at its size cap — every field added to a listing was costing a line there.
15
+ */
16
+ export function buildSessionInfo(session) {
17
+ const base = {
18
+ sessionId: session.id,
19
+ url: session.url,
20
+ ...(session.projectId === undefined ? {} : { projectId: session.projectId }),
21
+ // Omitted when blank, never `""`. An empty title cannot be told apart from "we sampled before
22
+ // the page set one", and two such tabs make the listing unusable. Moving the sample later does
23
+ // not fix it — a page may set its title after connect, or never — so the projection refuses the
24
+ // bad answer and the reader falls back to `url`, which is right here in the same record.
25
+ ...('' === session.title.trim() ? {} : { title: session.title }),
26
+ adapters: session.adapters,
27
+ hasCapabilities: session.hasCapabilities,
28
+ // On every listing, not buried in a log — skew explains failures that read as app bugs.
29
+ ...(session.versionSkew === undefined ? {} : { versionSkew: session.versionSkew }),
30
+ hidden: session.hidden,
31
+ ...session.health(),
32
+ };
33
+ if (session.staleMs() > SESSION_LEASE.STALE_AFTER_MS) {
34
+ base.stale = true;
35
+ base.cleanup_suggestion =
36
+ 'Call reticle_session{action:"end"} to free this session before starting new work.';
37
+ }
38
+ // Surface human bug reports in reticle_sessions (only when > 0, so a clean session adds nothing).
39
+ const marks = session.pendingMarkCount();
40
+ if (marks > 0) {
41
+ base.pendingMarks = marks;
42
+ const s = 1 === marks ? '' : 's';
43
+ base.review_suggestion = `The human flagged ${String(marks)} issue${s} on this tab — call reticle_session{action:"review"} to see and fix ${1 === marks ? 'it' : 'them'}.`;
44
+ }
45
+ return base;
46
+ }
@@ -11,15 +11,4 @@ export interface SessionLease {
11
11
  opened_at: number;
12
12
  IMPORTANT: string;
13
13
  }
14
- /**
15
- * Kept SHORT on purpose, and this is the reason to read before editing it.
16
- *
17
- * This block is a fixed, identical cost paid by EVERY session. The long-form version was 433 bytes
18
- * on a `reticle_query` whose actual answer was 259 — 57% of the first call of every session spent
19
- * re-explaining something that never varies. Measured at 815 → 1058 average observation tokens, and
20
- * the benchmark's fresh-session-per-scenario shape pays it ten times over.
21
- *
22
- * Any edit here is a per-session tax on every user: say it in as few words as still work.
23
- */
24
- export declare const SESSION_LEASE_NOTICE = "When you stop driving, call reticle_session {action:\"yield\", mode:\"waiting\"|\"ask\"} \u2014 the panel reads \"live\" until you do. {action:\"end\"} when the task is done; it revives on your next action.";
25
14
  export declare function buildSessionLease(sessionId: string, openedAt: number): SessionLease;
@@ -8,7 +8,7 @@
8
8
  *
9
9
  * Any edit here is a per-session tax on every user: say it in as few words as still work.
10
10
  */
11
- export const SESSION_LEASE_NOTICE = 'When you stop driving, call reticle_session {action:"yield", mode:"waiting"|"ask"} — the panel reads "live" until you do. {action:"end"} when the task is done; it revives on your next action.';
11
+ const SESSION_LEASE_NOTICE = 'When you stop driving, call reticle_session {action:"yield", mode:"waiting"|"ask"} — the panel reads "live" until you do. {action:"end"} when the task is done; it revives on your next action.';
12
12
  export function buildSessionLease(sessionId, openedAt) {
13
13
  return { sessionId, opened_at: openedAt, IMPORTANT: SESSION_LEASE_NOTICE };
14
14
  }
@@ -1,4 +1,5 @@
1
1
  import { Session, type SessionInfo } from './session.js';
2
+ import { AttachmentHistory } from './attachment-history.js';
2
3
  /**
3
4
  * The agent's active project, used to scope auto-selection. `projectId` is the stable build-stamped
4
5
  * identity (authoritative when present); `url` is the app origin, a fallback hint for older SDKs that
@@ -18,9 +19,14 @@ export declare class SessionManager {
18
19
  remove(session: Session): boolean;
19
20
  get(sessionId: string): Session | undefined;
20
21
  list(): SessionInfo[];
22
+ /** Continuity for one session — "has this been attached the whole time I am reasoning about?" */
23
+ attachmentOf(sessionId: string): ReturnType<AttachmentHistory['of']>;
21
24
  /** Every connected session — used by the liveness reaper to sweep for idle/disconnected ones. */
22
25
  all(): Session[];
23
26
  count(): number;
27
+ /** Wire the diagnosis provider (daemon boot). Absent ⇒ the plain, static message. */
28
+ setNoSessionHint(hint: (() => string | undefined) | undefined): void;
29
+ everConnected(): boolean;
24
30
  /** Record a bridge-initiated close so `resolve` can explain a session that vanished. */
25
31
  noteClosure(reason: string, at: number): void;
26
32
  /** The most recent bridge-initiated close, if any. */
@@ -1,6 +1,7 @@
1
1
  import { NO_SESSION_CONNECTED_ERROR } from '@reticlehq/core';
2
2
  import { declareDrivenRedactionKeys, forgetDrivenRedactionKeys, } from '../input/driven-redaction.js';
3
3
  import { Session } from './session.js';
4
+ import { AttachmentHistory } from './attachment-history.js';
4
5
  /** The scheme://host:port of a URL, or undefined if it can't be parsed. Used to compare origins. */
5
6
  export function originOf(url) {
6
7
  if (url === undefined)
@@ -29,14 +30,29 @@ function scopeSessions(sessions, scope) {
29
30
  }
30
31
  return sessions;
31
32
  }
32
- /** Honest, scoped error when sessions exist but none match the agent's project. */
33
- function scopeMissError(scope) {
33
+ /**
34
+ * Honest, scoped error when sessions exist but none match the agent's project.
35
+ *
36
+ * The daemon takes its default scope from the `.reticle.json` of whichever directory started it, so
37
+ * attaching from a second project — or another worktree of the SAME project — makes every tool
38
+ * refuse while `reticle_sessions` shows the tab sitting right there. The old message asked "is that
39
+ * app running with @reticlehq/core enabled?", which is the one thing that is definitely true, so the
40
+ * reader goes looking at their app instead of at the scope. Name what IS connected and how to
41
+ * target it: both facts are already in hand here.
42
+ */
43
+ function scopeMissError(connected, scope) {
34
44
  const who = scope?.projectId !== undefined
35
45
  ? `project '${scope.projectId}'`
36
46
  : scope?.url !== undefined
37
47
  ? `your app at ${scope.url}`
38
48
  : 'the active project';
39
- return `no browser session for ${who} — is that app running with @reticlehq/core enabled? (other apps may be connected, but they won't be driven by mistake)`;
49
+ const listed = connected
50
+ .map((s) => `'${s.projectId ?? 'untagged'}' (${s.url}, sessionId '${s.id}')`)
51
+ .join(', ');
52
+ return (`no browser session for ${who}, but ${String(connected.length)} session(s) ARE connected under a ` +
53
+ `different project: ${listed}. The daemon scopes to the .reticle.json of the directory it was ` +
54
+ `started in, so this is a scope mismatch, not a dead app. Pass the sessionId above to target one, ` +
55
+ `or restart the daemon from that app's directory.`);
40
56
  }
41
57
  /**
42
58
  * Owns the set of connected browser sessions and the smart auto-selection that resolves which one a
@@ -47,6 +63,12 @@ function scopeMissError(scope) {
47
63
  const MAX_REMEMBERED_CLOSURES = 5;
48
64
  export class SessionManager {
49
65
  #sessions = new Map();
66
+ /**
67
+ * Continuity per session, so a listing can answer "was this attached the whole time".
68
+ *
69
+ * Owned here because this is the one place that sees both halves — every add and every remove.
70
+ */
71
+ #attachment = new AttachmentHistory();
50
72
  /**
51
73
  * The active project's scope, set once from the daemon's .reticle.json. When a tool resolves a session
52
74
  * without passing its own scope, this is applied — so auto-selection is project-scoped by default
@@ -58,12 +80,14 @@ export class SessionManager {
58
80
  this.#defaultScope = scope;
59
81
  }
60
82
  add(session) {
83
+ this.#everConnected = true;
61
84
  const previous = this.#sessions.get(session.id);
62
85
  this.#sessions.set(session.id, session);
63
86
  // Publish what this app declared sensitive to the driven-path rule. Here rather than in the
64
87
  // bridge because EVERY path that registers a session goes through this method, and a declaration
65
88
  // that silently fails to register is a leak nothing would report.
66
89
  declareDrivenRedactionKeys(session.id, session.redactKeys);
90
+ this.#attachment.attached(session.id);
67
91
  return previous;
68
92
  }
69
93
  remove(session) {
@@ -71,13 +95,24 @@ export class SessionManager {
71
95
  return false;
72
96
  session.rejectAll('session disconnected');
73
97
  forgetDrivenRedactionKeys(session.id);
98
+ // Recorded, not forgotten: a session that comes back needs its gap measured, and a listing after
99
+ // the reconnect is exactly where that matters.
100
+ this.#attachment.detached(session.id);
74
101
  return this.#sessions.delete(session.id);
75
102
  }
76
103
  get(sessionId) {
77
104
  return this.#sessions.get(sessionId);
78
105
  }
79
106
  list() {
80
- return [...this.#sessions.values()].map((s) => s.info());
107
+ return [...this.#sessions.values()].map((s) => {
108
+ const info = s.info();
109
+ const attachment = this.#attachment.of(s.id);
110
+ return attachment === undefined ? info : { ...info, attachment };
111
+ });
112
+ }
113
+ /** Continuity for one session — "has this been attached the whole time I am reasoning about?" */
114
+ attachmentOf(sessionId) {
115
+ return this.#attachment.of(sessionId);
81
116
  }
82
117
  /** Every connected session — used by the liveness reaper to sweep for idle/disconnected ones. */
83
118
  all() {
@@ -112,6 +147,25 @@ export class SessionManager {
112
147
  * was hung up on. Keeping the last few reasons turns an unexplained disappearance into a fact.
113
148
  */
114
149
  #recentClosures = [];
150
+ /**
151
+ * What the daemon knows about WHY nothing is connected, refreshed in the background.
152
+ *
153
+ * Measured: of the sessions that call any tool, half make exactly one call — usually
154
+ * reticle_sessions — and stop, because the answer they get names two things they cannot check and
155
+ * nothing they can do. The daemon can tell "no app is running" from "an app is running and never
156
+ * dialled us" from "one was connected and left", and each has a different fix. Optional so a
157
+ * bridge constructed without a daemon (every unit test) keeps the plain message.
158
+ */
159
+ #noSessionHint;
160
+ /** Wire the diagnosis provider (daemon boot). Absent ⇒ the plain, static message. */
161
+ setNoSessionHint(hint) {
162
+ this.#noSessionHint = hint;
163
+ }
164
+ /** Whether any session has connected since this daemon booted — half the diagnosis. */
165
+ #everConnected = false;
166
+ everConnected() {
167
+ return this.#everConnected;
168
+ }
115
169
  /** Record a bridge-initiated close so `resolve` can explain a session that vanished. */
116
170
  noteClosure(reason, at) {
117
171
  this.#recentClosures.push({ at, reason });
@@ -122,38 +176,96 @@ export class SessionManager {
122
176
  lastClosure() {
123
177
  return this.#recentClosures[this.#recentClosures.length - 1];
124
178
  }
179
+ /**
180
+ * A dead `sessionId`, answered with what the caller needs to recover — not with an errand.
181
+ *
182
+ * Telemetry, 2026-08-10: one agent called `reticle_navigate` twelve times against an id that was
183
+ * no longer connected. That single loop is **12 of the 58 tool errors recorded all day, 21%**. The
184
+ * refusal said only `no connected session with id 'x'`, and the recovery hint told it to call
185
+ * `reticle_sessions` and retry — two extra round trips to learn something this method already
186
+ * knows at the moment it refuses. An agent charged two calls to recover will often just repeat the
187
+ * one it made.
188
+ *
189
+ * So: name the live ids inline, and when there are none say that instead, because "retry with a
190
+ * valid one" is advice nobody can act on when no valid one exists.
191
+ */
192
+ #unknownSessionError(sessionId) {
193
+ const live = [...this.#sessions.values()];
194
+ if (0 === live.length) {
195
+ return (`no connected session with id '${sessionId}', and no sessions are connected at all — so ` +
196
+ 'there is no other id to retry with. The app is not dialling this daemon; call ' +
197
+ 'reticle_sessions for the diagnosis rather than retrying this call.');
198
+ }
199
+ const named = live.map((s) => `'${s.id}' (${s.url})`).join(', ');
200
+ return (`no connected session with id '${sessionId}'. Connected right now: ${named}. ` +
201
+ 'Retry with one of those, or omit sessionId entirely and let Reticle scope to your project.');
202
+ }
125
203
  resolve(sessionId, scope) {
126
204
  if (sessionId !== undefined) {
127
205
  const found = this.#sessions.get(sessionId);
128
206
  if (found === undefined) {
129
- throw new Error(`no connected session with id '${sessionId}'`);
207
+ throw new Error(this.#unknownSessionError(sessionId));
130
208
  }
131
209
  found.markAgentActivity(); // liveness — a targeted tool keeps the session alive / revives it
132
210
  return found;
133
211
  }
134
- if (this.#sessions.size === 0) {
212
+ if (0 === this.#sessions.size) {
135
213
  const closure = this.lastClosure();
214
+ // A diagnosis beats a checklist: it names which of the three causes this actually is.
215
+ const hint = this.#noSessionHint?.();
216
+ if (hint !== undefined && closure === undefined)
217
+ throw new Error(hint);
136
218
  throw new Error(closure === undefined
137
219
  ? NO_SESSION_CONNECTED_ERROR
138
- : `${NO_SESSION_CONNECTED_ERROR} NOTE: the bridge closed a session recently — "${closure.reason}". The app is probably still running; it was disconnected, and the SDK does not retry after a policy close. Reload the page to reconnect.`);
220
+ : `${NO_SESSION_CONNECTED_ERROR} NOTE: the bridge REFUSED or closed a connection recently — "${closure.reason}". The app is probably still running and trying to connect: it was turned away, and the SDK does not retry after a policy close. The reason above names the fix — do not go looking for a stopped dev server.`);
139
221
  }
140
222
  // Scope to the agent's active project FIRST, so a stray tab from another app/origin (e.g. a
141
223
  // leftover dashboard on a different port) is structurally unselectable — it never enters the
142
224
  // candidate set, no matter how recently it was heard from. This is the anti-cross-talk guard.
143
225
  // An explicit per-call scope wins; otherwise the daemon's active-project default applies.
144
226
  const effectiveScope = scope ?? this.#defaultScope;
145
- const all = scopeSessions([...this.#sessions.values()], effectiveScope);
146
- if (all.length === 0) {
227
+ const connected = [...this.#sessions.values()];
228
+ const all = scopeSessions(connected, effectiveScope);
229
+ if (0 === all.length) {
147
230
  // Sessions exist, but none belong to the scoped project — never fall back to a foreign tab.
148
- throw new Error(scopeMissError(effectiveScope));
231
+ // ponytail: still a refusal, deliberately. Auto-targeting the only connected tab would be the
232
+ // friendlier 90% case and would also silently defeat the anti-cross-talk guard this scope
233
+ // exists to be. Naming the tab and its sessionId costs the agent one extra argument.
234
+ throw new Error(scopeMissError(connected, effectiveScope));
149
235
  }
150
- if (all.length === 1) {
236
+ if (1 === all.length) {
151
237
  const [only] = all;
152
238
  if (only === undefined)
153
239
  throw new Error('session lookup failed');
154
240
  only.markAgentActivity();
155
241
  return only;
156
242
  }
243
+ // Two projects and nothing to choose between them: refuse, BEFORE any scoring.
244
+ //
245
+ // Reported from the field: a call from `apps/next-app-router` was answered by a `rowy-*`
246
+ // session, and `act_and_wait` spent 16.9s returning `verified:'no'` about an app never under
247
+ // test — a confident false verdict, with a source pointer, about code the agent never touched.
248
+ //
249
+ // The recency rule below cannot prevent it. It asks "is one of these clearly fresher", which is
250
+ // the right question for two tabs of ONE app and the wrong question for two apps: the fresher
251
+ // tab of the wrong project is still the wrong project. The reported case had a clear winner,
252
+ // so the ambiguity check passed and the wrong app was selected.
253
+ //
254
+ // This sits ahead of scoring so freshness cannot rescue it. It only fires when nothing scoped
255
+ // the call — an explicit `sessionId` returns far above, and an explicit or default scope has
256
+ // already narrowed `all` to one project by here.
257
+ const projects = new Set(all.map((s) => s.projectId).filter((p) => p !== undefined));
258
+ if (projects.size > 1) {
259
+ const listed = all
260
+ .filter((s) => s.projectId !== undefined)
261
+ .map((s) => `'${String(s.projectId)}' (${s.url}, sessionId '${s.id}')`)
262
+ .join(', ');
263
+ throw new Error(`${String(projects.size)} different projects are connected and nothing says which one this ` +
264
+ `call is about: ${listed}. Pass sessionId to target one. Reticle refuses rather than ` +
265
+ 'guessing here because picking the wrong app produces a confident verdict about code you ' +
266
+ 'never touched — the daemon scopes to the directory it was started in, so running it above ' +
267
+ 'several apps leaves it unable to tell them apart.');
268
+ }
157
269
  // Multiple sessions: score each (lower = better candidate for auto-selection).
158
270
  // 0 = non-throttled (visible + recently-heard), 1 = throttled (hidden or stale heartbeat).
159
271
  const scored = all.map((s) => ({ s, score: s.throttled() ? 1 : 0, ms: s.lastSeenMs() }));
@@ -173,7 +285,7 @@ export class SessionManager {
173
285
  // desktop), the gap requirement is dropped: every session is already in "background" mode
174
286
  // so we just pick the one with the freshest heartbeat and let the agent proceed. Requiring
175
287
  // a gap here only produces spurious "ambiguous" errors while the user works elsewhere.
176
- const allThrottled = bestScore === 1;
288
+ const allThrottled = 1 === bestScore;
177
289
  const RECENCY_GAP_MS = allThrottled ? 0 : 1_000;
178
290
  const clearWinner = runnerUp === undefined || best.ms + RECENCY_GAP_MS < runnerUp.ms;
179
291
  if (!clearWinner) {
@@ -16,7 +16,7 @@ interface WaitForReadyOptions {
16
16
  now: () => number;
17
17
  /** Injected delay between polls. */
18
18
  sleep: (ms: number) => Promise<void>;
19
- /** Poll interval (ms). Default 100. */
19
+ /** Poll interval (ms). Default 25 — see DEFAULT_POLL_MS. */
20
20
  pollMs?: number;
21
21
  }
22
22
  /**
@@ -1,4 +1,9 @@
1
- const DEFAULT_POLL_MS = 100;
1
+ /**
2
+ * Same reasoning as session-reconnect: the check is an in-memory lookup and the thing awaited is an
3
+ * event (a session registering), so a coarse grid only adds latency to the first call an agent
4
+ * makes. Four times the checks of something with no I/O costs nothing measurable.
5
+ */
6
+ const DEFAULT_POLL_MS = 25;
2
7
  /**
3
8
  * One-line orientation for a FRESH agent — the Reticle loop, returned by reticle_wait_ready (the first call)
4
9
  * so an agent learns how to drive Reticle without reading docs. Named so it is not a free string; kept
@@ -20,7 +20,7 @@ const IDLE_WAITING_SUMMARY = 'Agent idle — your turn. Continue in your termina
20
20
  * it rides the end banner so the human can copy it into their terminal. No notes → the base unchanged.
21
21
  */
22
22
  export function composeEndedNotice(base, undelivered) {
23
- if (undelivered.length === 0)
23
+ if (0 === undelivered.length)
24
24
  return base;
25
25
  const notes = undelivered.map((text) => `"${text}"`).join(', ');
26
26
  return `${base} · ${UNDELIVERED_NOTES_LABEL} ${notes}`;
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Block until a reloaded page has re-announced itself under the same session id.
3
+ *
4
+ * A reload tears the SDK down. The id survives (see session-continuity), and the daemon replaces the
5
+ * old Session with a new one when the page says HELLO again — so "is it back?" is exactly "does a
6
+ * DIFFERENT object hold this id now?". Identity, not a flag: the returning page always constructs a
7
+ * new Session, and nothing else can claim an id it already owns.
8
+ *
9
+ * Polls a live getter rather than subscribing, because there is no reconnect event to subscribe to
10
+ * and inventing one for this would be a wire change for a two-second wait.
11
+ */
12
+ interface WaitForReconnectOptions {
13
+ /** The session registered under this id right now, or undefined if none is. */
14
+ current: () => object | undefined;
15
+ /** The session object that was live when the reload was dispatched. */
16
+ previous: object;
17
+ /** Give up after this much elapsed time and let the caller report it honestly. */
18
+ timeoutMs: number;
19
+ now: () => number;
20
+ sleep: (ms: number) => Promise<void>;
21
+ pollMs?: number;
22
+ }
23
+ /**
24
+ * How long a reload is worth waiting for before answering "not confirmed".
25
+ *
26
+ * A production build is back in well under a second; a cold dev-server route rebuild is the slow
27
+ * case this budget is sized for. It is a CEILING, not a delay — the common path returns on the first
28
+ * or second poll.
29
+ */
30
+ export declare const RELOAD_RECONNECT_TIMEOUT_MS = 5000;
31
+ export declare function waitForReconnect(opts: WaitForReconnectOptions): Promise<boolean>;
32
+ export {};
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Block until a reloaded page has re-announced itself under the same session id.
3
+ *
4
+ * A reload tears the SDK down. The id survives (see session-continuity), and the daemon replaces the
5
+ * old Session with a new one when the page says HELLO again — so "is it back?" is exactly "does a
6
+ * DIFFERENT object hold this id now?". Identity, not a flag: the returning page always constructs a
7
+ * new Session, and nothing else can claim an id it already owns.
8
+ *
9
+ * Polls a live getter rather than subscribing, because there is no reconnect event to subscribe to
10
+ * and inventing one for this would be a wire change for a two-second wait.
11
+ */
12
+ /**
13
+ * How often to look for the reconnected session.
14
+ *
15
+ * 25ms, not 100. What is being polled is an in-memory map lookup — no I/O, no browser round-trip —
16
+ * while the thing being waited for is an EVENT (the reloaded page's HELLO). At 100ms the agent paid
17
+ * up to a full interval of dead time after the page was already back, on every navigate-with-reload.
18
+ * Measured across the fixture fleet, `reticle_navigate` clustered at 105 / 202-206 / 308 / 407ms —
19
+ * the poll grid, visible in the data. Four times the checks of something this cheap costs nothing
20
+ * anyone can measure.
21
+ */
22
+ const DEFAULT_POLL_MS = 25;
23
+ /**
24
+ * How long a reload is worth waiting for before answering "not confirmed".
25
+ *
26
+ * A production build is back in well under a second; a cold dev-server route rebuild is the slow
27
+ * case this budget is sized for. It is a CEILING, not a delay — the common path returns on the first
28
+ * or second poll.
29
+ */
30
+ export const RELOAD_RECONNECT_TIMEOUT_MS = 5000;
31
+ export async function waitForReconnect(opts) {
32
+ const poll = opts.pollMs ?? DEFAULT_POLL_MS;
33
+ const start = opts.now();
34
+ for (;;) {
35
+ const live = opts.current();
36
+ if (live !== undefined && live !== opts.previous)
37
+ return true;
38
+ if (opts.now() - start >= opts.timeoutMs)
39
+ return false;
40
+ await opts.sleep(poll);
41
+ }
42
+ }
@@ -3,7 +3,7 @@ import { LastAct } from './last-act.js';
3
3
  import { type PageRuntime } from './command-timeout.js';
4
4
  import { type SessionHealth } from './session-health.js';
5
5
  export type { SessionHealth };
6
- import { PresenterTone, SessionState, type CommandResult, type HelloMessage, type HumanControlData, type ReticleEvent } from '@reticlehq/core';
6
+ import { PresenterTone, SessionState, type BrowserBrand, type CommandResult, type HelloMessage, type HumanControlData, type ReticleEvent } from '@reticlehq/core';
7
7
  import type { JournalReader, JournalRecorder } from '../journal/journal-recorder.js';
8
8
  import { type EventQueryOptions } from '../journal/journal-query.js';
9
9
  import { type AmbientCounts } from '../journal/ambient.js';
@@ -27,6 +27,8 @@ export declare class Session {
27
27
  title: string;
28
28
  adapters: string[];
29
29
  hasCapabilities: boolean;
30
+ /** Set when the page's SDK version differs from the daemon's (see version-skew.ts). */
31
+ versionSkew?: string;
30
32
  /**
31
33
  * Extra key names this app declared sensitive via `connect({ redact: { keys } })`. Held so the
32
34
  * DRIVEN path can redact them too — a request body the daemon captures from the network stack
@@ -46,10 +48,12 @@ export declare class Session {
46
48
  * `runtime` is optional so an older SDK (which does not report it) still works — it simply loses
47
49
  * the desktop-specific timeout diagnosis rather than breaking.
48
50
  */
49
- applyHealth(hidden: boolean, focused: boolean, runtime?: string, engine?: string): void;
51
+ applyHealth(hidden: boolean, focused: boolean, runtime?: string, engine?: string, brand?: BrowserBrand): void;
50
52
  /** The shell (web/electron/tauri) and rendering engine the SDK reported. Undefined on an older SDK. */
51
53
  get runtime(): PageRuntime | undefined;
52
54
  get engine(): string | undefined;
55
+ /** The browser brand the SDK reported. Absent (never guessed) when the page did not say. */
56
+ get brand(): BrowserBrand | undefined;
53
57
  /** Throttled if the tab is hidden OR we have not heard from it recently. */
54
58
  throttled(): boolean;
55
59
  /** The attachable health block — single source of truth for the tools. */
@@ -60,6 +64,14 @@ export declare class Session {
60
64
  /** Re-stamp an incoming event with server-relative time, buffer it, and fan out. */
61
65
  pushEvent(event: ReticleEvent, byteSize?: number): void;
62
66
  recordActedRef(ref: string): void;
67
+ actedLabels(): ReadonlySet<string>;
68
+ /**
69
+ * Derive the snapshot-shaped label (`button "Deploy"`) from an act reply and remember it.
70
+ *
71
+ * Same shape the interactive snapshot emits, so coverage can match a control the agent drove
72
+ * against the same control after a re-render gave it a different ref.
73
+ */
74
+ private recordActedLabelFrom;
63
75
  actedRefs(): ReadonlySet<string>;
64
76
  noteRateLimited(dropped: number): void;
65
77
  blindSpots(): Readonly<Record<string, number>>;
@@ -124,6 +136,7 @@ export declare class Session {
124
136
  dropped: number;
125
137
  };
126
138
  onEvent(listener: (event: ReticleEvent) => void): () => void;
139
+ onDisconnect(listener: () => void): () => void;
127
140
  /** Send a command to the browser and await its reply (or time out). */
128
141
  command(name: string, args?: Record<string, unknown>, timeoutMs?: number): Promise<CommandResult>;
129
142
  handleResult(result: CommandResult): void;