@reticlehq/server 2.3.0 → 2.5.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 (424) hide show
  1. package/SKILL.md +702 -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} +1 -1
  7. package/dist/{bridge.js → bridge/bridge.js} +105 -15
  8. package/dist/{bridge.test-harness.d.ts → bridge/bridge.test-harness.d.ts} +6 -11
  9. package/dist/{bridge.test-harness.js → bridge/bridge.test-harness.js} +21 -9
  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-doctor.js → cli/cli-doctor.js} +9 -2
  20. package/dist/{cli-flow-commands.d.ts → cli/cli-flow-commands.d.ts} +2 -2
  21. package/dist/{cli-flow-commands.js → cli/cli-flow-commands.js} +15 -15
  22. package/dist/{cli-launch.d.ts → cli/cli-launch.d.ts} +32 -3
  23. package/dist/cli/cli-launch.js +171 -0
  24. package/dist/cli/cli-parse.d.ts +143 -0
  25. package/dist/{cli-parse.js → cli/cli-parse.js} +146 -65
  26. package/dist/{cli-port.d.ts → cli/cli-port.d.ts} +15 -0
  27. package/dist/{cli-port.js → cli/cli-port.js} +29 -8
  28. package/dist/cli/cli-update-commands.d.ts +4 -0
  29. package/dist/cli/cli-update-commands.js +93 -0
  30. package/dist/{cli-verify.js → cli/cli-verify.js} +14 -14
  31. package/dist/{cloud-cli.js → cli/cloud-cli.js} +29 -24
  32. package/dist/cli.d.ts +2 -2
  33. package/dist/cli.js +182 -93
  34. package/dist/cloud/cloud-config.d.ts +0 -1
  35. package/dist/cloud/cloud-config.js +9 -9
  36. package/dist/cloud/cloud-sync.d.ts +1 -7
  37. package/dist/cloud/cloud-sync.js +12 -12
  38. package/dist/crawl/crawl-empty.d.ts +16 -0
  39. package/dist/crawl/crawl-empty.js +24 -0
  40. package/dist/crawl/crawl-tools.js +4 -1
  41. package/dist/crawl/crawl.js +26 -7
  42. package/dist/daemon/daemon-resilience.d.ts +49 -0
  43. package/dist/daemon/daemon-resilience.js +191 -0
  44. package/dist/daemon/daemon-usefulness.d.ts +61 -0
  45. package/dist/daemon/daemon-usefulness.js +62 -0
  46. package/dist/{daemon.d.ts → daemon/daemon.d.ts} +33 -8
  47. package/dist/{daemon.js → daemon/daemon.js} +80 -48
  48. package/dist/daemon/idle-grace.d.ts +32 -0
  49. package/dist/daemon/idle-grace.js +41 -0
  50. package/dist/{idle-shutdown.d.ts → daemon/idle-shutdown.d.ts} +13 -0
  51. package/dist/{idle-shutdown.js → daemon/idle-shutdown.js} +27 -4
  52. package/dist/domain/domain-model.js +2 -2
  53. package/dist/events/body-failures.js +8 -8
  54. package/dist/events/contradictions.d.ts +1 -1
  55. package/dist/events/contradictions.js +60 -10
  56. package/dist/events/echo-mismatch.js +10 -10
  57. package/dist/events/event-filters.js +6 -6
  58. package/dist/events/json-salvage.d.ts +2 -1
  59. package/dist/events/json-salvage.js +6 -6
  60. package/dist/events/observed-in-window.d.ts +12 -0
  61. package/dist/events/observed-in-window.js +21 -0
  62. package/dist/events/predicate-eval.d.ts +43 -20
  63. package/dist/events/predicate-eval.js +159 -55
  64. package/dist/events/predicate.d.ts +2 -2
  65. package/dist/events/predicate.js +112 -35
  66. package/dist/events/reconcile.js +5 -5
  67. package/dist/events/ring-buffer.js +1 -1
  68. package/dist/events/stale-response.js +6 -6
  69. package/dist/events/testid-near-miss.d.ts +22 -0
  70. package/dist/events/testid-near-miss.js +33 -0
  71. package/dist/events/unit-mismatch.js +8 -8
  72. package/dist/flows/affected-tools.js +19 -4
  73. package/dist/flows/affected.js +1 -1
  74. package/dist/flows/annotate-target.d.ts +1 -0
  75. package/dist/flows/annotate-target.js +20 -0
  76. package/dist/flows/annotate-tools.js +25 -3
  77. package/dist/flows/annotate.js +17 -4
  78. package/dist/flows/assertion-integrity.d.ts +2 -1
  79. package/dist/flows/assertion-tiers-store.d.ts +3 -66
  80. package/dist/flows/assertion-tiers-store.js +2 -2
  81. package/dist/flows/attributed-failure.d.ts +17 -0
  82. package/dist/flows/attributed-failure.js +20 -0
  83. package/dist/flows/buddy-status.d.ts +2 -1
  84. package/dist/flows/buddy-status.js +2 -2
  85. package/dist/flows/coverage.d.ts +3 -2
  86. package/dist/flows/coverage.js +2 -2
  87. package/dist/flows/decision.d.ts +0 -6
  88. package/dist/flows/decision.js +64 -8
  89. package/dist/flows/empty-flow.d.ts +6 -0
  90. package/dist/flows/empty-flow.js +24 -0
  91. package/dist/flows/flake-store.d.ts +0 -1
  92. package/dist/flows/flake-store.js +1 -1
  93. package/dist/flows/flake.d.ts +2 -1
  94. package/dist/flows/flake.js +2 -2
  95. package/dist/flows/flow-classify.js +12 -0
  96. package/dist/flows/flow-replay-run.js +4 -4
  97. package/dist/flows/flow-replay.d.ts +5 -7
  98. package/dist/flows/flow-replay.js +114 -47
  99. package/dist/flows/flow-report.js +1 -1
  100. package/dist/flows/flow-scope.d.ts +1 -6
  101. package/dist/flows/flow-step-runners.d.ts +26 -6
  102. package/dist/flows/flow-step-runners.js +157 -6
  103. package/dist/flows/flow-success.js +20 -15
  104. package/dist/flows/flow-tools.js +67 -13
  105. package/dist/flows/flows.js +15 -11
  106. package/dist/flows/gate.d.ts +4 -3
  107. package/dist/flows/gate.js +1 -1
  108. package/dist/flows/heal-run.d.ts +0 -8
  109. package/dist/flows/heal-run.js +5 -5
  110. package/dist/flows/heal.js +3 -3
  111. package/dist/flows/predicate-to-expect.d.ts +38 -0
  112. package/dist/flows/predicate-to-expect.js +95 -0
  113. package/dist/flows/replay.d.ts +16 -1
  114. package/dist/flows/replay.js +58 -22
  115. package/dist/flows/role-anchor-nearest.d.ts +6 -0
  116. package/dist/flows/role-anchor-nearest.js +45 -0
  117. package/dist/flows/server-verify.d.ts +1 -1
  118. package/dist/flows/server-verify.js +5 -5
  119. package/dist/flows/verify-change-tools.js +32 -10
  120. package/dist/flows/verify-change-verdict.d.ts +15 -0
  121. package/dist/flows/verify-change-verdict.js +25 -0
  122. package/dist/flows/watch-batcher.d.ts +3 -2
  123. package/dist/honesty/already-true.d.ts +2 -0
  124. package/dist/honesty/already-true.js +31 -0
  125. package/dist/honesty/blind-spots.d.ts +16 -3
  126. package/dist/honesty/blind-spots.js +24 -12
  127. package/dist/honesty/honesty.d.ts +3 -2
  128. package/dist/honesty/honesty.js +4 -4
  129. package/dist/honesty/uncaptured-bodies.js +1 -1
  130. package/dist/honesty/unread-outcome.js +2 -2
  131. package/dist/honesty/verified.d.ts +22 -3
  132. package/dist/honesty/verified.js +48 -8
  133. package/dist/http-server.js +14 -7
  134. package/dist/hunt/hunt-report.d.ts +2 -1
  135. package/dist/hunt/hunt-report.js +2 -2
  136. package/dist/index.d.ts +8 -6
  137. package/dist/index.js +32 -25
  138. package/dist/init/agent-rules.d.ts +2 -1
  139. package/dist/init/agent-rules.js +45 -7
  140. package/dist/init/app-choice.d.ts +20 -0
  141. package/dist/init/app-choice.js +25 -0
  142. package/dist/init/astro-host.d.ts +21 -0
  143. package/dist/init/astro-host.js +27 -0
  144. package/dist/init/astro-patch.d.ts +16 -0
  145. package/dist/init/astro-patch.js +240 -0
  146. package/dist/init/capabilities.d.ts +17 -0
  147. package/dist/init/capabilities.js +54 -0
  148. package/dist/init/cra.d.ts +37 -0
  149. package/dist/init/cra.js +90 -0
  150. package/dist/init/cursor.js +39 -4
  151. package/dist/init/desktop-doctor.d.ts +3 -2
  152. package/dist/init/desktop-doctor.js +4 -4
  153. package/dist/init/detect.d.ts +26 -0
  154. package/dist/init/detect.js +78 -5
  155. package/dist/init/mcp-registered.d.ts +12 -0
  156. package/dist/init/mcp-registered.js +14 -0
  157. package/dist/init/mcp.d.ts +27 -2
  158. package/dist/init/mcp.js +40 -4
  159. package/dist/init/next-patch.d.ts +39 -0
  160. package/dist/init/next-patch.js +196 -0
  161. package/dist/init/node-io.js +48 -6
  162. package/dist/init/patch-kind.d.ts +20 -0
  163. package/dist/init/patch-kind.js +10 -0
  164. package/dist/init/plan-framework.d.ts +50 -0
  165. package/dist/init/plan-framework.js +341 -0
  166. package/dist/init/plan.d.ts +119 -6
  167. package/dist/init/plan.js +230 -131
  168. package/dist/init/project-id.d.ts +5 -9
  169. package/dist/init/project-id.js +10 -22
  170. package/dist/init/run.d.ts +22 -0
  171. package/dist/init/run.js +393 -28
  172. package/dist/init/slash-command.d.ts +27 -0
  173. package/dist/init/slash-command.js +61 -0
  174. package/dist/init/snippets.d.ts +41 -3
  175. package/dist/init/snippets.js +185 -10
  176. package/dist/init/vite-config.d.ts +4 -10
  177. package/dist/init/vite-config.js +15 -7
  178. package/dist/init/workspace-apps.d.ts +23 -0
  179. package/dist/init/workspace-apps.js +68 -0
  180. package/dist/input/driven-redaction.d.ts +15 -0
  181. package/dist/input/driven-redaction.js +76 -0
  182. package/dist/input/network-detail.d.ts +9 -3
  183. package/dist/input/network-detail.js +43 -23
  184. package/dist/input/network-mock-tools.js +2 -2
  185. package/dist/input/network-mock.js +4 -4
  186. package/dist/input/real-input.js +8 -3
  187. package/dist/input/scroll-find.js +2 -2
  188. package/dist/input/scroll-tools.d.ts +0 -5
  189. package/dist/input/scroll-tools.js +11 -4
  190. package/dist/input/viewport-tools.js +2 -2
  191. package/dist/journal/ambient-store.d.ts +0 -2
  192. package/dist/journal/ambient-store.js +1 -1
  193. package/dist/journal/ambient.js +1 -1
  194. package/dist/journal/attach-journal.d.ts +2 -1
  195. package/dist/journal/deviation-report.js +6 -6
  196. package/dist/journal/deviation-service.js +1 -1
  197. package/dist/journal/envelope-store.d.ts +0 -2
  198. package/dist/journal/envelope-store.js +1 -1
  199. package/dist/journal/envelope.d.ts +2 -3
  200. package/dist/journal/envelope.js +3 -3
  201. package/dist/journal/journal-query.js +1 -1
  202. package/dist/journal/journal-recorder.d.ts +2 -1
  203. package/dist/journal/journal-recorder.js +2 -2
  204. package/dist/journal/rollups.js +9 -9
  205. package/dist/journal/session-end.d.ts +2 -1
  206. package/dist/journal/session-journal.js +4 -4
  207. package/dist/license/license.d.ts +0 -14
  208. package/dist/license/license.js +8 -8
  209. package/dist/log.d.ts +10 -0
  210. package/dist/log.js +11 -1
  211. package/dist/mcp/mcp-is-error.d.ts +13 -0
  212. package/dist/mcp/mcp-is-error.js +18 -0
  213. package/dist/mcp/mcp-outage.d.ts +31 -0
  214. package/dist/mcp/mcp-outage.js +48 -0
  215. package/dist/mcp/mcp-proxy.d.ts +124 -0
  216. package/dist/mcp/mcp-proxy.js +697 -0
  217. package/dist/{mcp.d.ts → mcp/mcp.d.ts} +13 -10
  218. package/dist/{mcp.js → mcp/mcp.js} +110 -35
  219. package/dist/mcp/proxy-handshake.d.ts +3 -0
  220. package/dist/mcp/proxy-handshake.js +54 -0
  221. package/dist/mcp/proxy-lifecycle.d.ts +64 -0
  222. package/dist/mcp/proxy-lifecycle.js +70 -0
  223. package/dist/mcp/tool-catalog-cache.d.ts +29 -0
  224. package/dist/mcp/tool-catalog-cache.js +55 -0
  225. package/dist/oracles/propose-consequences.d.ts +2 -1
  226. package/dist/oracles/propose-consequences.js +16 -11
  227. package/dist/oracles/self-instrument.d.ts +2 -1
  228. package/dist/oracles/self-instrument.js +1 -1
  229. package/dist/phenomena/phenomena.js +3 -3
  230. package/dist/platform.d.ts +16 -0
  231. package/dist/platform.js +16 -0
  232. package/dist/pool/playwright-launcher.d.ts +15 -2
  233. package/dist/pool/playwright-launcher.js +21 -2
  234. package/dist/project/fs-port.js +1 -1
  235. package/dist/project/project-tools.js +2 -2
  236. package/dist/runs/build-verification-run.js +1 -1
  237. package/dist/runs/repair-prompt.d.ts +0 -6
  238. package/dist/runs/repair-prompt.js +3 -3
  239. package/dist/runs/risk-classify.js +1 -1
  240. package/dist/runs/run-diff.d.ts +0 -2
  241. package/dist/runs/run-diff.js +3 -3
  242. package/dist/runs/run-tools.js +2 -2
  243. package/dist/runs/verification-sync.js +2 -2
  244. package/dist/runs/verify-http.js +1 -1
  245. package/dist/runs/verify-server.js +1 -1
  246. package/dist/session/command-timeout.d.ts +11 -3
  247. package/dist/session/command-timeout.js +2 -2
  248. package/dist/session/control-envelope.d.ts +6 -0
  249. package/dist/session/control-envelope.js +7 -1
  250. package/dist/session/dev-server-probe.d.ts +13 -0
  251. package/dist/session/dev-server-probe.js +52 -0
  252. package/dist/session/last-act.d.ts +17 -3
  253. package/dist/session/last-act.js +16 -4
  254. package/dist/session/live-control-tools.js +7 -2
  255. package/dist/session/live-control.js +1 -1
  256. package/dist/session/looks-like-dev-server.d.ts +14 -0
  257. package/dist/session/looks-like-dev-server.js +25 -0
  258. package/dist/session/no-session-diagnosis.d.ts +38 -0
  259. package/dist/session/no-session-diagnosis.js +70 -0
  260. package/dist/session/no-session-watch.d.ts +19 -0
  261. package/dist/session/no-session-watch.js +61 -0
  262. package/dist/session/observed-state.d.ts +4 -0
  263. package/dist/session/observed-state.js +19 -1
  264. package/dist/session/output-budget.js +1 -1
  265. package/dist/session/session-health.d.ts +8 -1
  266. package/dist/session/session-health.js +8 -7
  267. package/dist/session/session-info.d.ts +26 -7
  268. package/dist/session/session-info.js +42 -1
  269. package/dist/session/session-lease.d.ts +0 -11
  270. package/dist/session/session-lease.js +1 -1
  271. package/dist/session/session-manager.d.ts +3 -0
  272. package/dist/session/session-manager.js +58 -9
  273. package/dist/session/session-readiness.d.ts +1 -1
  274. package/dist/session/session-readiness.js +6 -1
  275. package/dist/session/session-reaper.js +1 -1
  276. package/dist/session/session-reconnect.d.ts +32 -0
  277. package/dist/session/session-reconnect.js +42 -0
  278. package/dist/session/session.d.ts +20 -2
  279. package/dist/session/session.js +76 -26
  280. package/dist/session/state-select.d.ts +1 -1
  281. package/dist/session/state-select.js +1 -1
  282. package/dist/status-payload.d.ts +19 -0
  283. package/dist/status-payload.js +11 -0
  284. package/dist/telemetry/argument-shape.js +10 -1
  285. package/dist/telemetry/browser-mode.d.ts +27 -0
  286. package/dist/telemetry/browser-mode.js +33 -0
  287. package/dist/telemetry/bug-found.d.ts +17 -21
  288. package/dist/telemetry/bug-found.js +120 -8
  289. package/dist/telemetry/cli-telemetry.d.ts +2 -12
  290. package/dist/telemetry/cli-telemetry.js +27 -4
  291. package/dist/telemetry/daemon-telemetry.d.ts +19 -0
  292. package/dist/telemetry/daemon-telemetry.js +12 -2
  293. package/dist/telemetry/dev-repo.js +4 -4
  294. package/dist/telemetry/error-fingerprint.js +21 -2
  295. package/dist/telemetry/feedback-cli.d.ts +1 -1
  296. package/dist/telemetry/feedback-cli.js +4 -4
  297. package/dist/telemetry/feedback-context.d.ts +22 -0
  298. package/dist/telemetry/feedback-context.js +41 -1
  299. package/dist/telemetry/feedback-delivery.d.ts +20 -0
  300. package/dist/telemetry/feedback-delivery.js +30 -0
  301. package/dist/telemetry/feedback-outbox.d.ts +22 -0
  302. package/dist/telemetry/feedback-outbox.js +87 -0
  303. package/dist/telemetry/feedback.d.ts +22 -1
  304. package/dist/telemetry/feedback.js +67 -8
  305. package/dist/telemetry/git-facts.js +1 -1
  306. package/dist/telemetry/session-metrics.d.ts +2 -0
  307. package/dist/telemetry/session-metrics.js +68 -6
  308. package/dist/telemetry/telemetry.d.ts +27 -2
  309. package/dist/telemetry/telemetry.js +119 -29
  310. package/dist/telemetry/verification-of.d.ts +22 -0
  311. package/dist/telemetry/verification-of.js +84 -0
  312. package/dist/tools/act-capsule.js +2 -2
  313. package/dist/tools/act-danger.d.ts +12 -0
  314. package/dist/tools/act-danger.js +20 -3
  315. package/dist/tools/act-helpers.js +4 -4
  316. package/dist/tools/act-tools.d.ts +0 -8
  317. package/dist/tools/act-tools.js +84 -115
  318. package/dist/tools/act-view.js +7 -7
  319. package/dist/tools/alias-args.d.ts +17 -0
  320. package/dist/tools/alias-args.js +25 -0
  321. package/dist/tools/assert-grade.js +9 -9
  322. package/dist/tools/assert-verdict.d.ts +3 -1
  323. package/dist/tools/assert-verdict.js +8 -5
  324. package/dist/tools/browser-tools.js +30 -11
  325. package/dist/tools/contract-tools.js +1 -1
  326. package/dist/tools/coverage-identity.d.ts +34 -0
  327. package/dist/tools/coverage-identity.js +46 -0
  328. package/dist/tools/coverage-tools.js +10 -14
  329. package/dist/tools/dynamic-tools.d.ts +2 -1
  330. package/dist/tools/dynamic-tools.js +90 -18
  331. package/dist/tools/error-recovery.d.ts +20 -2
  332. package/dist/tools/error-recovery.js +179 -4
  333. package/dist/tools/feedback-tools.d.ts +12 -1
  334. package/dist/tools/feedback-tools.js +28 -5
  335. package/dist/tools/invoke-tool.d.ts +0 -6
  336. package/dist/tools/invoke-tool.js +57 -32
  337. package/dist/tools/lease-hint.d.ts +17 -0
  338. package/dist/tools/lease-hint.js +24 -0
  339. package/dist/tools/lease-tools.js +6 -5
  340. package/dist/tools/merge-tools.d.ts +10 -1
  341. package/dist/tools/merge-tools.js +9 -3
  342. package/dist/tools/merged-name-redirect.d.ts +13 -0
  343. package/dist/tools/merged-name-redirect.js +55 -0
  344. package/dist/tools/navigate-result.d.ts +17 -0
  345. package/dist/tools/navigate-result.js +31 -0
  346. package/dist/tools/observe-tools.js +54 -20
  347. package/dist/tools/observed-nothing.d.ts +25 -0
  348. package/dist/tools/observed-nothing.js +33 -0
  349. package/dist/tools/query-paginate.js +2 -2
  350. package/dist/tools/query-shape.d.ts +18 -0
  351. package/dist/tools/query-shape.js +39 -0
  352. package/dist/tools/read-tools.js +85 -20
  353. package/dist/tools/real-input-attempt.d.ts +28 -0
  354. package/dist/tools/real-input-attempt.js +88 -0
  355. package/dist/tools/reconcile-tools.js +7 -7
  356. package/dist/tools/reload-result.d.ts +14 -0
  357. package/dist/tools/reload-result.js +29 -0
  358. package/dist/tools/snapshot-delta.d.ts +4 -5
  359. package/dist/tools/snapshot-delta.js +0 -0
  360. package/dist/tools/tool-kit.d.ts +8 -0
  361. package/dist/tools/tool-surface.d.ts +83 -0
  362. package/dist/tools/tool-surface.js +204 -0
  363. package/dist/tools/tools-helpers.js +6 -6
  364. package/dist/tools/tools.d.ts +15 -0
  365. package/dist/tools/tools.js +53 -17
  366. package/dist/tools/unadvertised-help.d.ts +5 -0
  367. package/dist/tools/unadvertised-help.js +39 -0
  368. package/dist/trace.d.ts +69 -0
  369. package/dist/trace.js +150 -0
  370. package/dist/update/nudge-credit.d.ts +4 -0
  371. package/dist/update/nudge-credit.js +53 -0
  372. package/dist/update/sdk-sync.d.ts +21 -0
  373. package/dist/update/sdk-sync.js +42 -0
  374. package/dist/update/update-checker.js +3 -3
  375. package/dist/update/update-nudge.d.ts +63 -6
  376. package/dist/update/update-nudge.js +128 -15
  377. package/dist/update/updater.d.ts +1 -9
  378. package/dist/update/updater.js +13 -8
  379. package/dist/version/peer-announce.d.ts +4 -0
  380. package/dist/version/peer-announce.js +30 -0
  381. package/dist/{server-version.js → version/server-version.js} +5 -1
  382. package/dist/version/version-nudge.d.ts +28 -0
  383. package/dist/version/version-nudge.js +40 -0
  384. package/dist/version/version-skew.d.ts +66 -0
  385. package/dist/version/version-skew.js +71 -0
  386. package/dist/visual/visual-diff.js +2 -2
  387. package/dist/visual/visual-tools.js +4 -4
  388. package/docs/agent-cheatsheet.md +85 -0
  389. package/docs/architecture.md +113 -0
  390. package/docs/benchmarks.md +255 -0
  391. package/docs/debugging.md +102 -0
  392. package/docs/deploy-checks.md +69 -0
  393. package/docs/desktop-apps.md +256 -0
  394. package/docs/enterprise.md +62 -0
  395. package/docs/fixtures.md +28 -0
  396. package/docs/flows.md +166 -0
  397. package/docs/getting-started.md +469 -0
  398. package/docs/human-control.md +63 -0
  399. package/docs/integration-patterns.md +171 -0
  400. package/docs/local-registry.md +126 -0
  401. package/docs/mint.json +61 -0
  402. package/docs/multi-agent-testing.md +55 -0
  403. package/docs/platform-integration.md +122 -0
  404. package/docs/telemetry-contract.md +214 -0
  405. package/docs/telemetry.md +184 -0
  406. package/docs/testing.md +82 -0
  407. package/docs/token-efficiency.md +80 -0
  408. package/docs/usage.md +1046 -0
  409. package/package.json +8 -5
  410. package/dist/cli-launch.js +0 -112
  411. package/dist/cli-parse.d.ts +0 -140
  412. package/dist/daemon-resilience.d.ts +0 -22
  413. package/dist/daemon-resilience.js +0 -85
  414. package/dist/mcp-proxy.d.ts +0 -35
  415. package/dist/mcp-proxy.js +0 -190
  416. package/dist/tools/profiles.d.ts +0 -37
  417. package/dist/tools/profiles.js +0 -139
  418. /package/dist/{pairing-token.d.ts → bridge/pairing-token.d.ts} +0 -0
  419. /package/dist/{token-auth.d.ts → bridge/token-auth.d.ts} +0 -0
  420. /package/dist/{token-auth.js → bridge/token-auth.js} +0 -0
  421. /package/dist/{cli-doctor.d.ts → cli/cli-doctor.d.ts} +0 -0
  422. /package/dist/{cli-verify.d.ts → cli/cli-verify.d.ts} +0 -0
  423. /package/dist/{cloud-cli.d.ts → cli/cloud-cli.d.ts} +0 -0
  424. /package/dist/{server-version.d.ts → version/server-version.d.ts} +0 -0
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Turning "no browser session connected" from a dead end into a next action.
3
+ *
4
+ * This is the most consequential sentence in the product. Measured over a day of telemetry: 74% of
5
+ * sessions never call a Reticle tool, and of the ones that do, half make exactly ONE call — usually
6
+ * `reticle_sessions` — and stop. Ten of those thirteen never touched a browser, and every recorded
7
+ * session error is this one. The agent asks whether anything is connected, is told no, and leaves.
8
+ *
9
+ * The old message asked the agent to check two things it cannot see from where it stands ("is your
10
+ * app running with the SDK enabled?", "does it point at this port?"). The daemon can actually tell
11
+ * the three cases apart, and each has a different, concrete fix:
12
+ *
13
+ * - a session was here and went away -> the tab closed or reloaded; reopen it
14
+ * - something is listening, never dialled, project not wired -> run `reticle init` in THAT app
15
+ * - something is listening, project wired -> port mismatch or a stale build; restart the dev server
16
+ * - nothing is listening anywhere -> there is no app running; start it
17
+ *
18
+ * Pure: everything it needs is passed in, so the probe that discovers listening ports stays out of
19
+ * the hot resolve() path and this stays unit-testable.
20
+ */
21
+ interface NoSessionFacts {
22
+ /** Whether ANY session has connected to this daemon since it booted. */
23
+ everConnected: boolean;
24
+ /** Whether this project has been through `reticle init` (a .reticle.json / projectId is present). */
25
+ initialized: boolean;
26
+ /** Localhost ports with something listening that looks like a dev server. */
27
+ listening: readonly number[];
28
+ /** The port this daemon is on — half of the mismatch the old message asked about. */
29
+ port: number;
30
+ }
31
+ /**
32
+ * Every branch ends with this, and `recoveryFor` keys on it to suppress the generic no-session
33
+ * recovery: a message that already carries its own next action must not be handed a second, more
34
+ * generic one that contradicts it.
35
+ */
36
+ export declare const SELF_RECOVERING_MARKER = "Then call reticle_sessions again \u2014 it will appear within a second of the page loading.";
37
+ export declare function diagnoseNoSession(facts: NoSessionFacts): string;
38
+ export {};
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Turning "no browser session connected" from a dead end into a next action.
3
+ *
4
+ * This is the most consequential sentence in the product. Measured over a day of telemetry: 74% of
5
+ * sessions never call a Reticle tool, and of the ones that do, half make exactly ONE call — usually
6
+ * `reticle_sessions` — and stop. Ten of those thirteen never touched a browser, and every recorded
7
+ * session error is this one. The agent asks whether anything is connected, is told no, and leaves.
8
+ *
9
+ * The old message asked the agent to check two things it cannot see from where it stands ("is your
10
+ * app running with the SDK enabled?", "does it point at this port?"). The daemon can actually tell
11
+ * the three cases apart, and each has a different, concrete fix:
12
+ *
13
+ * - a session was here and went away -> the tab closed or reloaded; reopen it
14
+ * - something is listening, never dialled, project not wired -> run `reticle init` in THAT app
15
+ * - something is listening, project wired -> port mismatch or a stale build; restart the dev server
16
+ * - nothing is listening anywhere -> there is no app running; start it
17
+ *
18
+ * Pure: everything it needs is passed in, so the probe that discovers listening ports stays out of
19
+ * the hot resolve() path and this stays unit-testable.
20
+ */
21
+ /**
22
+ * Every branch ends with this, and `recoveryFor` keys on it to suppress the generic no-session
23
+ * recovery: a message that already carries its own next action must not be handed a second, more
24
+ * generic one that contradicts it.
25
+ */
26
+ export const SELF_RECOVERING_MARKER = 'Then call reticle_sessions again — it will appear within a second of the page loading.';
27
+ const RETRY = SELF_RECOVERING_MARKER;
28
+ /**
29
+ * The way out that needs no human at all.
30
+ *
31
+ * `reticle_lease` opens a browser Reticle drives itself, instead of waiting for somebody's tab to
32
+ * dial in. Measured over a day of telemetry it is the single strongest predictor of a session that
33
+ * works: the 5 sessions that used it had a MEDIAN of 30 tool calls and produced 46% of every bug
34
+ * found, against a median of 1 call for the 20 active sessions that did not — and not one
35
+ * single-call bounce used one. It is also advertised on no profile except `full`, so an agent only
36
+ * ever finds it if it already knew it existed. Naming it HERE puts it in front of the agent at the
37
+ * one moment it is the answer, and costs nothing on the turns when it is not.
38
+ *
39
+ * Only offered when the app is known to carry the SDK: leasing an uninstrumented app just burns a
40
+ * browser and comes back `ready:false`.
41
+ */
42
+ const SELF_SERVE = 'You do not have to wait for the human: reticle_lease {action:"acquire", url} opens a browser ' +
43
+ 'Reticle drives itself, and returns a sessionId you can use immediately (reach it with ' +
44
+ 'reticle_run {tool:"reticle_lease"} if it is not advertised directly; release it when you finish).';
45
+ export function diagnoseNoSession(facts) {
46
+ const { everConnected, initialized, listening, port } = facts;
47
+ const ports = listening.join(', ');
48
+ if (everConnected) {
49
+ return ('no browser session connected — but one WAS connected to this daemon earlier, so the wiring ' +
50
+ 'is correct. The tab was closed, navigated away, or hard-reloaded. Ask the human to reopen ' +
51
+ `the app (or run \`reticle open\`), or reload the tab. ${SELF_SERVE} ${RETRY}`);
52
+ }
53
+ if (0 === listening.length) {
54
+ return ('no browser session connected, and nothing is listening on any of the usual dev-server ports ' +
55
+ '— so the app is almost certainly not running. This is not a Reticle wiring problem: ask the ' +
56
+ `human to start their dev server (\`npm run dev\`), then open the app in a browser. ${RETRY}`);
57
+ }
58
+ if (!initialized) {
59
+ return (`no browser session connected, but something IS listening on port ${ports} — so a server is ` +
60
+ 'up and has never dialled this daemon. This project has not been through `reticle init`, ' +
61
+ 'which is what installs the SDK and wires it into the build, so the likeliest cause is that ' +
62
+ "the app carries no Reticle SDK. Ask the human to run `reticle init` in the app's directory " +
63
+ `and restart the dev server. ${RETRY}`);
64
+ }
65
+ return (`no browser session connected, but something IS listening on port ${ports} and this project is ` +
66
+ `wired for Reticle — so the app is either serving a build made before the wiring landed, or ` +
67
+ `dialling a different daemon than this one (this daemon is on ${String(port)}). Ask the human ` +
68
+ 'to restart the dev server and hard-reload the page; if it still does not appear, check that ' +
69
+ `the app's reticle port matches ${String(port)}. ${SELF_SERVE} ${RETRY}`);
70
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Keeps the "why is nothing connected" diagnosis fresh, without putting a probe on the hot path.
3
+ *
4
+ * `SessionManager.resolve` is synchronous and runs on every tool call, so it cannot await a port
5
+ * scan. This refreshes the answer in the background and hands the manager a closure that reads the
6
+ * cached result — a hint in an error message is exactly the sort of thing that may be a few seconds
7
+ * stale.
8
+ *
9
+ * It only probes while NOTHING is connected. Once a session is live the question is moot, and a
10
+ * daemon that outlives the agent by hours has no business scanning ports it does not need.
11
+ */
12
+ import type { SessionManager } from './session-manager.js';
13
+ /**
14
+ * The daemon's whole session-scoping decision in one call: scope auto-selection to the active
15
+ * project, and keep the no-session diagnosis fresh. Both derive from the same one fact — whether
16
+ * this directory has been through `reticle init` — so they belong together rather than as two
17
+ * adjacent blocks in the bootstrap.
18
+ */
19
+ export declare function wireSessionScope(sessions: SessionManager, activeProjectId: string | undefined, port: number): () => void;
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Keeps the "why is nothing connected" diagnosis fresh, without putting a probe on the hot path.
3
+ *
4
+ * `SessionManager.resolve` is synchronous and runs on every tool call, so it cannot await a port
5
+ * scan. This refreshes the answer in the background and hands the manager a closure that reads the
6
+ * cached result — a hint in an error message is exactly the sort of thing that may be a few seconds
7
+ * stale.
8
+ *
9
+ * It only probes while NOTHING is connected. Once a session is live the question is moot, and a
10
+ * daemon that outlives the agent by hours has no business scanning ports it does not need.
11
+ */
12
+ import { probeDevServers } from './dev-server-probe.js';
13
+ import { diagnoseNoSession } from './no-session-diagnosis.js';
14
+ /** Slow enough to be free, fast enough that a dev server started 15s ago is already reflected. */
15
+ const REFRESH_MS = 15_000;
16
+ /** Start the watch. Returns a stop function; the timer is unref'd so it never holds the daemon up. */
17
+ function startNoSessionWatch(options) {
18
+ const probe = options.probe ?? (() => probeDevServers());
19
+ let listening = [];
20
+ let running = false;
21
+ const refresh = () => {
22
+ // Nothing to diagnose while a session is live, and no reason to scan.
23
+ if (running || options.sessions.count() > 0)
24
+ return;
25
+ running = true;
26
+ void probe()
27
+ .then((ports) => {
28
+ listening = ports;
29
+ })
30
+ .catch(() => {
31
+ /* a diagnostic hint must never take the daemon down */
32
+ })
33
+ .finally(() => {
34
+ running = false;
35
+ });
36
+ };
37
+ refresh();
38
+ const timer = setInterval(refresh, REFRESH_MS);
39
+ timer.unref();
40
+ options.sessions.setNoSessionHint(() => diagnoseNoSession({
41
+ everConnected: options.sessions.everConnected(),
42
+ initialized: options.initialized,
43
+ listening,
44
+ port: options.port,
45
+ }));
46
+ return () => {
47
+ clearInterval(timer);
48
+ options.sessions.setNoSessionHint(undefined);
49
+ };
50
+ }
51
+ /**
52
+ * The daemon's whole session-scoping decision in one call: scope auto-selection to the active
53
+ * project, and keep the no-session diagnosis fresh. Both derive from the same one fact — whether
54
+ * this directory has been through `reticle init` — so they belong together rather than as two
55
+ * adjacent blocks in the bootstrap.
56
+ */
57
+ export function wireSessionScope(sessions, activeProjectId, port) {
58
+ if (activeProjectId !== undefined)
59
+ sessions.setDefaultScope({ projectId: activeProjectId });
60
+ return startNoSessionWatch({ sessions, port, initialized: activeProjectId !== undefined });
61
+ }
@@ -42,8 +42,12 @@ export declare class ObservedState {
42
42
  seedAmbient(counts: AmbientCounts): void;
43
43
  /** Record a driven ref. Idempotent. */
44
44
  recordActedRef(ref: string): void;
45
+ /** Record a driven control's re-render-surviving label. Idempotent; empty labels are not identities. */
46
+ recordActedLabel(label: string): void;
45
47
  /** Every ref driven so far this session. */
46
48
  actedRefs(): ReadonlySet<string>;
49
+ /** Every driven control's label, for matching across re-renders. */
50
+ actedLabels(): ReadonlySet<string>;
47
51
  /**
48
52
  * Record bridge-side sampling as a blind spot.
49
53
  *
@@ -34,7 +34,7 @@ export class ObservedState {
34
34
  if (event.type === EventType.BLIND_SPOT) {
35
35
  const kind = event.data['kind'];
36
36
  const count = event.data['count'];
37
- if (typeof kind === 'string' && typeof count === 'number')
37
+ if ('string' === typeof kind && 'number' === typeof count)
38
38
  this.#blindSpots[kind] = count;
39
39
  }
40
40
  }
@@ -78,15 +78,33 @@ export class ObservedState {
78
78
  * "recently untouched" — a number that reads as thorough while drifting toward the opposite.
79
79
  */
80
80
  #actedRefs = new Set();
81
+ /**
82
+ * The same drives, keyed by something that SURVIVES a re-render.
83
+ *
84
+ * A ref is invalidated whenever the DOM changes, so on a framework that replaces nodes rather than
85
+ * reconciling in place (Next's app router, for one) every driven ref is stale by the time coverage
86
+ * is asked and the number reads 0 forever. The snapshot label — `button "Deploy"` — names the same
87
+ * control on both sides of a re-render.
88
+ */
89
+ #actedLabels = new Set();
81
90
  /** Record a driven ref. Idempotent. */
82
91
  recordActedRef(ref) {
83
92
  if (ref.length > 0)
84
93
  this.#actedRefs.add(ref);
85
94
  }
95
+ /** Record a driven control's re-render-surviving label. Idempotent; empty labels are not identities. */
96
+ recordActedLabel(label) {
97
+ if (label.length > 0)
98
+ this.#actedLabels.add(label);
99
+ }
86
100
  /** Every ref driven so far this session. */
87
101
  actedRefs() {
88
102
  return this.#actedRefs;
89
103
  }
104
+ /** Every driven control's label, for matching across re-renders. */
105
+ actedLabels() {
106
+ return this.#actedLabels;
107
+ }
90
108
  /**
91
109
  * Record bridge-side sampling as a blind spot.
92
110
  *
@@ -58,7 +58,7 @@ export function sizeCost(payload) {
58
58
  * Non-object results (e.g. a thrown-error envelope) pass through unchanged.
59
59
  */
60
60
  export function withSizeCost(result) {
61
- if (typeof result !== 'object' || result === null)
61
+ if (typeof result !== 'object' || null === result)
62
62
  return result;
63
63
  return { ...result, cost: sizeCost(result) };
64
64
  }
@@ -1,3 +1,4 @@
1
+ import { BrowserBrand } from '@reticlehq/core';
1
2
  import type { Session } from './session.js';
2
3
  /**
3
4
  * The health block spliced onto act/assert results. Defined here rather than in session.ts: this is
@@ -45,12 +46,18 @@ export declare function healthEnvelope(session: Session): HealthEnvelope;
45
46
  */
46
47
  export declare function refuseIfThrottled(session: Session, refuse: unknown): void;
47
48
  /** The page's own visibility/runtime report, narrowed out of an untrusted PAGE_HEALTH payload. */
48
- export interface HealthReport {
49
+ interface HealthReport {
49
50
  hidden: boolean | undefined;
50
51
  focused: boolean | undefined;
51
52
  runtime: string | undefined;
52
53
  /** Coarse rendering engine (blink/gecko/webkit) — context for a feedback report, not a health input. */
53
54
  engine: string | undefined;
55
+ /**
56
+ * Which browser the page is. Narrowed to `BrowserBrand` HERE rather than trusted: the SDK already
57
+ * normalises, so anything else is a stale or hand-forged payload and is dropped — the point of the
58
+ * closed list is that an unbounded string can never reach telemetry through it.
59
+ */
60
+ brand: BrowserBrand | undefined;
54
61
  }
55
62
  /**
56
63
  * Narrow a PAGE_HEALTH payload. Every field is optional on the wire — an older SDK does not report
@@ -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,10 +1,4 @@
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;
@@ -13,6 +7,8 @@ export interface SessionInfo {
13
7
  title: string;
14
8
  adapters: string[];
15
9
  hasCapabilities: boolean;
10
+ /** Present only when the page's SDK version differs from the daemon's — see version-skew.ts. */
11
+ versionSkew?: string;
16
12
  /** ms since the SDK last reported anything (silence ⇒ likely throttled). */
17
13
  lastSeenMs: number;
18
14
  hidden: boolean;
@@ -27,3 +23,26 @@ export interface SessionInfo {
27
23
  /** present with pendingMarks — nudges the agent to drain them with reticle_review. */
28
24
  review_suggestion?: string;
29
25
  }
26
+ /** The read-only slice of a Session that the projection needs. Keeps this module class-free. */
27
+ interface SessionView {
28
+ id: string;
29
+ url: string;
30
+ projectId: string | undefined;
31
+ title: string;
32
+ adapters: string[];
33
+ hasCapabilities: boolean;
34
+ versionSkew: string | undefined;
35
+ hidden: boolean;
36
+ health: () => SessionHealth;
37
+ staleMs: () => number;
38
+ pendingMarkCount: () => number;
39
+ }
40
+ /**
41
+ * Project a live Session into the shape the tools return.
42
+ *
43
+ * Lives beside the SessionInfo type rather than on the class: it is a pure read of already-computed
44
+ * state, it is where every "what does the agent see about this tab" decision belongs, and Session is
45
+ * a stateful class already at its size cap — every field added to a listing was costing a line there.
46
+ */
47
+ export declare function buildSessionInfo(session: SessionView): SessionInfo;
48
+ export {};
@@ -1 +1,42 @@
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
+ title: session.title,
22
+ adapters: session.adapters,
23
+ hasCapabilities: session.hasCapabilities,
24
+ // On every listing, not buried in a log — skew explains failures that read as app bugs.
25
+ ...(session.versionSkew === undefined ? {} : { versionSkew: session.versionSkew }),
26
+ hidden: session.hidden,
27
+ ...session.health(),
28
+ };
29
+ if (session.staleMs() > SESSION_LEASE.STALE_AFTER_MS) {
30
+ base.stale = true;
31
+ base.cleanup_suggestion =
32
+ 'Call reticle_session{action:"end"} to free this session before starting new work.';
33
+ }
34
+ // Surface human bug reports in reticle_sessions (only when > 0, so a clean session adds nothing).
35
+ const marks = session.pendingMarkCount();
36
+ if (marks > 0) {
37
+ base.pendingMarks = marks;
38
+ const s = 1 === marks ? '' : 's';
39
+ 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'}.`;
40
+ }
41
+ return base;
42
+ }
@@ -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
  }
@@ -21,6 +21,9 @@ export declare class SessionManager {
21
21
  /** Every connected session — used by the liveness reaper to sweep for idle/disconnected ones. */
22
22
  all(): Session[];
23
23
  count(): number;
24
+ /** Wire the diagnosis provider (daemon boot). Absent ⇒ the plain, static message. */
25
+ setNoSessionHint(hint: (() => string | undefined) | undefined): void;
26
+ everConnected(): boolean;
24
27
  /** Record a bridge-initiated close so `resolve` can explain a session that vanished. */
25
28
  noteClosure(reason: string, at: number): void;
26
29
  /** The most recent bridge-initiated close, if any. */
@@ -1,4 +1,5 @@
1
1
  import { NO_SESSION_CONNECTED_ERROR } from '@reticlehq/core';
2
+ import { declareDrivenRedactionKeys, forgetDrivenRedactionKeys, } from '../input/driven-redaction.js';
2
3
  import { Session } from './session.js';
3
4
  /** The scheme://host:port of a URL, or undefined if it can't be parsed. Used to compare origins. */
4
5
  export function originOf(url) {
@@ -28,14 +29,29 @@ function scopeSessions(sessions, scope) {
28
29
  }
29
30
  return sessions;
30
31
  }
31
- /** Honest, scoped error when sessions exist but none match the agent's project. */
32
- function scopeMissError(scope) {
32
+ /**
33
+ * Honest, scoped error when sessions exist but none match the agent's project.
34
+ *
35
+ * The daemon takes its default scope from the `.reticle.json` of whichever directory started it, so
36
+ * attaching from a second project — or another worktree of the SAME project — makes every tool
37
+ * refuse while `reticle_sessions` shows the tab sitting right there. The old message asked "is that
38
+ * app running with @reticlehq/core enabled?", which is the one thing that is definitely true, so the
39
+ * reader goes looking at their app instead of at the scope. Name what IS connected and how to
40
+ * target it: both facts are already in hand here.
41
+ */
42
+ function scopeMissError(connected, scope) {
33
43
  const who = scope?.projectId !== undefined
34
44
  ? `project '${scope.projectId}'`
35
45
  : scope?.url !== undefined
36
46
  ? `your app at ${scope.url}`
37
47
  : 'the active project';
38
- 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)`;
48
+ const listed = connected
49
+ .map((s) => `'${s.projectId ?? 'untagged'}' (${s.url}, sessionId '${s.id}')`)
50
+ .join(', ');
51
+ return (`no browser session for ${who}, but ${String(connected.length)} session(s) ARE connected under a ` +
52
+ `different project: ${listed}. The daemon scopes to the .reticle.json of the directory it was ` +
53
+ `started in, so this is a scope mismatch, not a dead app. Pass the sessionId above to target one, ` +
54
+ `or restart the daemon from that app's directory.`);
39
55
  }
40
56
  /**
41
57
  * Owns the set of connected browser sessions and the smart auto-selection that resolves which one a
@@ -57,14 +73,20 @@ export class SessionManager {
57
73
  this.#defaultScope = scope;
58
74
  }
59
75
  add(session) {
76
+ this.#everConnected = true;
60
77
  const previous = this.#sessions.get(session.id);
61
78
  this.#sessions.set(session.id, session);
79
+ // Publish what this app declared sensitive to the driven-path rule. Here rather than in the
80
+ // bridge because EVERY path that registers a session goes through this method, and a declaration
81
+ // that silently fails to register is a leak nothing would report.
82
+ declareDrivenRedactionKeys(session.id, session.redactKeys);
62
83
  return previous;
63
84
  }
64
85
  remove(session) {
65
86
  if (this.#sessions.get(session.id) !== session)
66
87
  return false;
67
88
  session.rejectAll('session disconnected');
89
+ forgetDrivenRedactionKeys(session.id);
68
90
  return this.#sessions.delete(session.id);
69
91
  }
70
92
  get(sessionId) {
@@ -106,6 +128,25 @@ export class SessionManager {
106
128
  * was hung up on. Keeping the last few reasons turns an unexplained disappearance into a fact.
107
129
  */
108
130
  #recentClosures = [];
131
+ /**
132
+ * What the daemon knows about WHY nothing is connected, refreshed in the background.
133
+ *
134
+ * Measured: of the sessions that call any tool, half make exactly one call — usually
135
+ * reticle_sessions — and stop, because the answer they get names two things they cannot check and
136
+ * nothing they can do. The daemon can tell "no app is running" from "an app is running and never
137
+ * dialled us" from "one was connected and left", and each has a different fix. Optional so a
138
+ * bridge constructed without a daemon (every unit test) keeps the plain message.
139
+ */
140
+ #noSessionHint;
141
+ /** Wire the diagnosis provider (daemon boot). Absent ⇒ the plain, static message. */
142
+ setNoSessionHint(hint) {
143
+ this.#noSessionHint = hint;
144
+ }
145
+ /** Whether any session has connected since this daemon booted — half the diagnosis. */
146
+ #everConnected = false;
147
+ everConnected() {
148
+ return this.#everConnected;
149
+ }
109
150
  /** Record a bridge-initiated close so `resolve` can explain a session that vanished. */
110
151
  noteClosure(reason, at) {
111
152
  this.#recentClosures.push({ at, reason });
@@ -125,8 +166,12 @@ export class SessionManager {
125
166
  found.markAgentActivity(); // liveness — a targeted tool keeps the session alive / revives it
126
167
  return found;
127
168
  }
128
- if (this.#sessions.size === 0) {
169
+ if (0 === this.#sessions.size) {
129
170
  const closure = this.lastClosure();
171
+ // A diagnosis beats a checklist: it names which of the three causes this actually is.
172
+ const hint = this.#noSessionHint?.();
173
+ if (hint !== undefined && closure === undefined)
174
+ throw new Error(hint);
130
175
  throw new Error(closure === undefined
131
176
  ? NO_SESSION_CONNECTED_ERROR
132
177
  : `${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.`);
@@ -136,12 +181,16 @@ export class SessionManager {
136
181
  // candidate set, no matter how recently it was heard from. This is the anti-cross-talk guard.
137
182
  // An explicit per-call scope wins; otherwise the daemon's active-project default applies.
138
183
  const effectiveScope = scope ?? this.#defaultScope;
139
- const all = scopeSessions([...this.#sessions.values()], effectiveScope);
140
- if (all.length === 0) {
184
+ const connected = [...this.#sessions.values()];
185
+ const all = scopeSessions(connected, effectiveScope);
186
+ if (0 === all.length) {
141
187
  // Sessions exist, but none belong to the scoped project — never fall back to a foreign tab.
142
- throw new Error(scopeMissError(effectiveScope));
188
+ // ponytail: still a refusal, deliberately. Auto-targeting the only connected tab would be the
189
+ // friendlier 90% case and would also silently defeat the anti-cross-talk guard this scope
190
+ // exists to be. Naming the tab and its sessionId costs the agent one extra argument.
191
+ throw new Error(scopeMissError(connected, effectiveScope));
143
192
  }
144
- if (all.length === 1) {
193
+ if (1 === all.length) {
145
194
  const [only] = all;
146
195
  if (only === undefined)
147
196
  throw new Error('session lookup failed');
@@ -167,7 +216,7 @@ export class SessionManager {
167
216
  // desktop), the gap requirement is dropped: every session is already in "background" mode
168
217
  // so we just pick the one with the freshest heartbeat and let the agent proceed. Requiring
169
218
  // a gap here only produces spurious "ambiguous" errors while the user works elsewhere.
170
- const allThrottled = bestScore === 1;
219
+ const allThrottled = 1 === bestScore;
171
220
  const RECENCY_GAP_MS = allThrottled ? 0 : 1_000;
172
221
  const clearWinner = runnerUp === undefined || best.ms + RECENCY_GAP_MS < runnerUp.ms;
173
222
  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 {};