@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,19 +1,34 @@
1
1
  import { healthEnvelope } from '../session/session-health.js';
2
- import { TelemetryActor, TelemetryEventKind } from '@reticlehq/core';
2
+ import { TelemetryActor, TelemetryEventKind, TRANSPORT_LIMITS, } from '@reticlehq/core';
3
3
  import { getSessionMetrics } from '../telemetry/session-metrics.js';
4
4
  import { getTelemetry } from '../telemetry/telemetry.js';
5
- import { VERIFICATION_TOOLS } from './feedback-tools.js';
6
5
  import { takeUpdateNudge } from '../update/update-nudge.js';
6
+ import { takeVersionSkew } from '../version/version-nudge.js';
7
+ import { noteToolCall } from '../daemon/daemon-usefulness.js';
7
8
  import { bugsInResult } from '../telemetry/bug-found.js';
9
+ import { verificationOf } from '../telemetry/verification-of.js';
8
10
  import { asString } from './tools-helpers.js';
9
11
  import { ReticleTool } from './tool-names.js';
10
12
  import { takeFeedbackPrompt } from './feedback-tools.js';
13
+ import { takeFeedbackUndelivered } from '../telemetry/feedback-delivery.js';
14
+ import { span } from '../trace.js';
15
+ import { frictionOf, inviteFor } from './feedback-invite.js';
11
16
  /**
12
17
  * The live-session tools whose result MUST carry the
13
18
  * session-health envelope. Owned in ONE place — not retrofitted per handler — so a throttled tab
14
19
  * can never return a healthy-looking result from any of these. `runTool` is the single choke point
15
20
  * (mcp.ts + tool-invoker.ts) that splices health on; the guard test asserts the set is exhaustive.
16
21
  */
22
+ /**
23
+ * Tools that DRIVE the page. An action with no verdict after it is the signature of the loop
24
+ * breaking mid-task — see `abandonedActions`. `act_and_wait` is one of these AND a verification, so
25
+ * it settles its own action, which is exactly right: it is the tool that does both.
26
+ */
27
+ const ACTION_TOOLS = new Set([
28
+ ReticleTool.ACT,
29
+ ReticleTool.ACT_SEQUENCE,
30
+ ReticleTool.ACT_AND_WAIT,
31
+ ]);
17
32
  export const SESSION_BOUND_TOOLS = new Set([
18
33
  ReticleTool.SNAPSHOT,
19
34
  ReticleTool.QUERY,
@@ -72,44 +87,27 @@ export const SESSION_EXEMPT_TOOLS = new Set([
72
87
  ReticleTool.FEEDBACK,
73
88
  ]);
74
89
  function isPlainObject(value) {
75
- return typeof value === 'object' && value !== null && !Array.isArray(value);
90
+ return 'object' === typeof value && value !== null && !Array.isArray(value);
76
91
  }
77
92
  /**
78
93
  * Emit `verification_completed` when a verification tool produced a verdict.
79
94
  *
80
95
  * Read off the RESULT here rather than emitted from `decideVerified`, which is where the verdict is
81
96
  * actually decided: that function is pure, and the repo's rules keep clocks, IO and side effects out
82
- * of pure logic for good reason. Reading the result also means all four verification tools are covered
83
- * by one site instead of four handler edits, and a fifth is covered the moment it joins the set.
97
+ * of pure logic for good reason. Reading the result also means all the verification tools are covered
98
+ * by one site instead of N handler edits, and the next one is covered the moment it joins the set.
84
99
  *
85
- * `falseGreenCaught` is the one that matters: the assertion PASSED and Reticle still refused to call
86
- * it verified. That is the product's entire thesis reduced to a boolean, and it is the number worth
87
- * putting in front of an investor — not "tools were called" but "this many greens were caught lying".
100
+ * The RULE itself lives in verification-of.ts, because getting it wrong is silent and it feeds the
101
+ * one number shown to investors — see that file for the two ways it was wrong.
88
102
  */
89
- function recordVerification(toolName, result, durationMs) {
90
- if (!VERIFICATION_TOOLS.has(toolName))
103
+ function recordVerification(toolName, result, durationMs, brand) {
104
+ const verification = verificationOf(toolName, result, durationMs, brand);
105
+ if (verification === undefined)
91
106
  return;
92
- const verified = typeof result['verified'] === 'string' ? result['verified'] : undefined;
93
- // flow_verify reports `status: pass|fail`; assert reports a boolean `pass`. Accept either shape so
94
- // the whole family is covered without normalizing four tools' contracts for a metric's convenience.
95
- const passed = typeof result['pass'] === 'boolean'
96
- ? result['pass']
97
- : result['status'] === 'pass'
98
- ? true
99
- : undefined;
100
- if (verified === undefined && passed === undefined)
101
- return; // not a verdict-bearing result
102
- const metrics = getSessionMetrics();
103
- metrics.recordVerification();
107
+ getSessionMetrics().recordVerification();
104
108
  void getTelemetry().emit(TelemetryEventKind.VERIFICATION_COMPLETED, {
105
109
  actor: TelemetryActor.AGENT,
106
- verification: {
107
- via: toolName,
108
- verified: verified ?? (passed === true ? 'yes' : 'no'),
109
- passed: passed ?? verified === 'yes',
110
- falseGreenCaught: passed === true && verified === 'no',
111
- durationMs,
112
- },
110
+ verification,
113
111
  });
114
112
  }
115
113
  /**
@@ -121,7 +119,7 @@ function recordVerification(toolName, result, durationMs) {
121
119
  */
122
120
  function reportBugsFound(toolName, result) {
123
121
  const bugs = bugsInResult(toolName, result);
124
- if (bugs.length === 0)
122
+ if (0 === bugs.length)
125
123
  return;
126
124
  const metrics = getSessionMetrics();
127
125
  for (const bug of bugs) {
@@ -141,6 +139,27 @@ function reportBugsFound(toolName, result) {
141
139
  * object that did not already include `session` — splices the health envelope on. Idempotent
142
140
  * (handlers that already add health are left untouched) and never alters non-object results.
143
141
  */
142
+ /**
143
+ * The name of the first argument whose string value exceeds the transport bound, if any.
144
+ *
145
+ * One level of nesting is searched because that is where callers put payloads — `reticle_act`'s
146
+ * `args.value`, and the fuzz's own `huge-string` shape. Deeper recursion would cost more than it
147
+ * buys: the failure is a single huge string, not a deeply nested structure of small ones.
148
+ */
149
+ function firstOversizedArg(args) {
150
+ const tooLong = (value) => 'string' === typeof value && value.length > TRANSPORT_LIMITS.MAX_STRING_LENGTH;
151
+ for (const [key, value] of Object.entries(args)) {
152
+ if (tooLong(value))
153
+ return key;
154
+ if ('object' === typeof value && null !== value && !Array.isArray(value)) {
155
+ for (const [inner, nested] of Object.entries(value)) {
156
+ if (tooLong(nested))
157
+ return `${key}.${inner}`;
158
+ }
159
+ }
160
+ }
161
+ return undefined;
162
+ }
144
163
  export async function runTool(tool, deps, args) {
145
164
  // Both dispatch paths (MCP + programmatic) pass through here — the one place "which tool is mostly
146
165
  // used" can be counted. This used to EMIT an event per call; it now increments an in-process counter
@@ -151,6 +170,34 @@ export async function runTool(tool, deps, args) {
151
170
  // Started/settled as a pair rather than a bare counter: several agents can be inside runTool at
152
171
  // once, so a single "last start" field would attribute one tool's duration to another. The returned
153
172
  // closure carries this call's own identity, which is also what makes peak-concurrency measurable.
173
+ // A daemon that has served even one tool call is doing a job for somebody; see daemon-usefulness.
174
+ noteToolCall();
175
+ // An oversized argument is refused BEFORE the handler deserialises it.
176
+ //
177
+ // tool-fuzz failed CI on the invariant that matters most — `every tool answers every hostile call`
178
+ // — for a ~100KB string. Seen twice with different tools; the earlier one came back
179
+ // `-32001 sse_aborted` with the tool surface dropping 48→17, which is a RESTARTED daemon. The
180
+ // shape is: oversized argument → the daemon dies → the proxy answers the in-flight call with
181
+ // transport loss, and the next assertion is talking to a different daemon.
182
+ //
183
+ // `TRANSPORT_LIMITS.MAX_STRING_LENGTH` already bounds what crosses the bridge; a tool argument
184
+ // arrives over MCP stdio and never met it. Here, because this is the one place every invocation
185
+ // routes through, so a tool added later inherits the bound instead of having to remember it.
186
+ //
187
+ // A refusal is a normal answer. An UNANSWERED call is a hung agent, and that is the failure this
188
+ // prevents: the agent learns its argument was too big, which is something it can fix.
189
+ const oversized = firstOversizedArg(args);
190
+ if (oversized !== undefined) {
191
+ return {
192
+ error: `argument '${oversized}' is larger than ${String(TRANSPORT_LIMITS.MAX_STRING_LENGTH)} ` +
193
+ 'characters, which is the most Reticle moves in one call. Nothing ran. Send a smaller ' +
194
+ 'value — a selector or an id rather than a document, or a slice of the text you need.',
195
+ };
196
+ }
197
+ // An ACTION is what gets abandoned. Counted here, against verifications, so "the agent drove the
198
+ // page and then wandered off" becomes a number instead of an impression.
199
+ if (ACTION_TOOLS.has(tool.name))
200
+ getSessionMetrics().recordAction();
154
201
  const settleTiming = getSessionMetrics().startToolCall(tool.name, args);
155
202
  const startedAt = Date.now();
156
203
  const rawSessionId = asString(args['sessionId']);
@@ -175,7 +222,10 @@ export async function runTool(tool, deps, args) {
175
222
  deps.pool?.touch(leaseId);
176
223
  let raw;
177
224
  try {
178
- raw = await tool.handler(deps, args);
225
+ // The one dispatch point every tool call passes through, so it is where the trace's root span
226
+ // belongs: with RETICLE_TRACE on, every stage that runs underneath inherits this call's id and
227
+ // nests under it. Free when off — see trace.ts.
228
+ raw = await span('tool.handler', { tool: tool.name, session: session?.id }, () => tool.handler(deps, args));
179
229
  }
180
230
  finally {
181
231
  // In a `finally` so a THROWN call still settles: otherwise every failing tool would leak a
@@ -187,7 +237,10 @@ export async function runTool(tool, deps, args) {
187
237
  // session-bound early return, because two of the four verification tools (flow_verify,
188
238
  // verify_change) are session-EXEMPT and would otherwise never carry it.
189
239
  if (isPlainObject(raw)) {
190
- recordVerification(tool.name, raw, Date.now() - startedAt);
240
+ // The brand comes from the session's own PAGE_HEALTH report, so it is present for the four
241
+ // session-bound verification tools and absent for flow_verify (session-exempt) — which replays
242
+ // into a browser Reticle launched, where `browser` already says what happened.
243
+ recordVerification(tool.name, raw, Date.now() - startedAt, session?.brand);
191
244
  reportBugsFound(tool.name, raw);
192
245
  }
193
246
  const prompt = isPlainObject(raw) ? takeFeedbackPrompt(tool.name) : undefined;
@@ -195,12 +248,28 @@ export async function runTool(tool, deps, args) {
195
248
  // and ASK for is something it will never ask for. Spliced on ANY tool result, not just a
196
249
  // verification, because an out-of-date install is worth mentioning whatever the agent is doing.
197
250
  const update = isPlainObject(raw) ? takeUpdateNudge() : undefined;
198
- const result = prompt === undefined && update === undefined
251
+ // Same one-shot channel, for the same reason. Skew was only ever reported in
252
+ // reticle_sessions.versionSkew and a CLI log line — two places an agent driving a flow never
253
+ // looks — so it could work a whole session against a mismatched pair and never learn the one fact
254
+ // that explains the behaviour. It rides out here on whatever tool it happens to be calling.
255
+ const skew = isPlainObject(raw) ? takeVersionSkew() : undefined;
256
+ // A feedback report that was accepted and then failed to send. Same one-shot channel, because the
257
+ // reporter is the only person who can act on it and they are not reading the daemon log — and a
258
+ // report announced as accepted and then silently lost is the failure the awaited send existed to
259
+ // prevent. See feedback-delivery.ts.
260
+ const undelivered = isPlainObject(raw) ? takeFeedbackUndelivered() : undefined;
261
+ const result = prompt === undefined && update === undefined && skew === undefined && undelivered === undefined
199
262
  ? raw
200
263
  : {
201
264
  ...raw,
202
265
  ...(prompt !== undefined ? { feedback_prompt: prompt } : {}),
203
266
  ...(update !== undefined ? { update_available: update } : {}),
267
+ ...(skew !== undefined ? { version_skew: skew } : {}),
268
+ ...(undelivered !== undefined
269
+ ? {
270
+ feedback_undelivered: `your earlier report did NOT send: ${undelivered}. Tell the human what you found so it is not lost.`,
271
+ }
272
+ : {}),
204
273
  };
205
274
  if (!bound || !isPlainObject(result))
206
275
  return result;
@@ -221,5 +290,29 @@ export async function runTool(tool, deps, args) {
221
290
  const warning = resolved.ageWarning();
222
291
  if (warning !== undefined)
223
292
  envelope['session_age_warning'] = warning;
293
+ // Ask for a verdict when the agent has driven the page and not asked for one. 137 of the 140
294
+ // verdict-less sessions measured over 2026-08-10/11 never called a verdict-producing tool ONCE;
295
+ // the counter behind this already existed and was reported only to us. One-shot per abandoned
296
+ // run, same discipline as the pool lease — a hint on every call is noise that gets tuned out.
297
+ const unverified = getSessionMetrics().takeUnverifiedNudge();
298
+ if (unverified !== undefined)
299
+ envelope['verify_next'] = unverified;
300
+ // Invite feedback at the moment of friction, worded for what just happened. Feedback produced
301
+ // ~11 of this release's fixes from 14 reports; the handshake instruction is what got those 14,
302
+ // and this closes the gap it leaves — the agent mid-problem, not thinking about feedback tooling.
303
+ // Counted so `feedback_submitted / feedbackPrompted` can tell us whether it works at all.
304
+ const friction = frictionOf({
305
+ unknownTool: false,
306
+ verifiedUnknown: 'unknown' === result.verified,
307
+ repeatRun: getSessionMetrics().currentRun,
308
+ errored: 'error' in result,
309
+ // A verdict is progress. Without this, three successful act_and_wait calls in a row — the exact
310
+ // loop this release exists to encourage — get told they are stuck.
311
+ producedVerdict: 'verified' in result,
312
+ });
313
+ if (friction !== undefined) {
314
+ envelope['feedback_invite'] = inviteFor(friction);
315
+ getSessionMetrics().recordFeedbackPrompt();
316
+ }
224
317
  return Object.keys(envelope).length > 0 ? { ...result, ...envelope } : result;
225
318
  }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Why a leased tab did not dial in.
3
+ *
4
+ * The lease pool opens the app itself, which is what makes it the highest-value path in the product
5
+ * — it does not wait for the human's tab. Measured over a day: lease sessions had a median of 30
6
+ * tool calls and produced 46% of all bugs found, against a median of 1 call for everything else.
7
+ *
8
+ * When it fails it is almost always a PORT MISMATCH: the tab loads, and the app's SDK dials the port
9
+ * it was BUILT with, which need not be the daemon that issued the lease. Verified by driving it —
10
+ * against a matching daemon `acquire` returns ready:true and the session is driveable immediately;
11
+ * against a different one the tab connects to the OTHER daemon instead.
12
+ *
13
+ * The old hint asked "is <url> running with @reticlehq/core enabled?", which is the one thing that
14
+ * cannot be in doubt: the pool just navigated a real browser to it. Naming the true cause first is
15
+ * the difference between a two-minute fix and abandoning the best path Reticle has.
16
+ */
17
+ export declare function leaseNotConnectedHint(url: string, port: number): string;
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Why a leased tab did not dial in.
3
+ *
4
+ * The lease pool opens the app itself, which is what makes it the highest-value path in the product
5
+ * — it does not wait for the human's tab. Measured over a day: lease sessions had a median of 30
6
+ * tool calls and produced 46% of all bugs found, against a median of 1 call for everything else.
7
+ *
8
+ * When it fails it is almost always a PORT MISMATCH: the tab loads, and the app's SDK dials the port
9
+ * it was BUILT with, which need not be the daemon that issued the lease. Verified by driving it —
10
+ * against a matching daemon `acquire` returns ready:true and the session is driveable immediately;
11
+ * against a different one the tab connects to the OTHER daemon instead.
12
+ *
13
+ * The old hint asked "is <url> running with @reticlehq/core enabled?", which is the one thing that
14
+ * cannot be in doubt: the pool just navigated a real browser to it. Naming the true cause first is
15
+ * the difference between a two-minute fix and abandoning the best path Reticle has.
16
+ */
17
+ export function leaseNotConnectedHint(url, port) {
18
+ return (`the leased tab loaded ${url} but never dialled this daemon (port ${String(port)}). The usual ` +
19
+ `cause is a PORT MISMATCH: the app's SDK connects to the port it was BUILT with, so a daemon on ` +
20
+ `a different port never hears from it — check the app's reticle port (\`.reticle.json\`, ` +
21
+ `RETICLE_PORT, or the build plugin's \`port\`) matches ${String(port)}, or start the daemon on ` +
22
+ `the port the app expects. If the app carries no Reticle SDK at all, run \`reticle init\` in it ` +
23
+ `first. The tab stays leased either way — release it with reticle_lease{action:"release"}.`);
24
+ }
@@ -9,7 +9,8 @@
9
9
  * Attach-only: the pool drives a browser against an already-running dev server — it never starts one.
10
10
  */
11
11
  import { z } from 'zod';
12
- import { RETICLE_URL_PARAM } from '@reticlehq/core';
12
+ import { leaseNotConnectedHint } from './lease-hint.js';
13
+ import { RETICLE_URL_PARAM, RETICLE_DEFAULT_PORT } from '@reticlehq/core';
13
14
  import { ReticleTool } from './tool-names.js';
14
15
  import { asString } from './tools-helpers.js';
15
16
  const POOL_UNAVAILABLE = 'browser pool unavailable — the lease tools need the daemon-managed pool (start Reticle via `reticle mcp`).';
@@ -58,7 +59,7 @@ export function cleanNavError(err) {
58
59
  }
59
60
  /** A fresh, collision-resistant lease id. Uses crypto at the I/O boundary (not pure logic). */
60
61
  function newLeaseId() {
61
- const uuid = typeof globalThis.crypto?.randomUUID === 'function'
62
+ const uuid = 'function' === typeof globalThis.crypto?.randomUUID
62
63
  ? globalThis.crypto.randomUUID()
63
64
  : `${String(Date.now())}-${String(performance.now())}`;
64
65
  return `lease-${uuid}`;
@@ -119,7 +120,7 @@ export const LEASE_TOOLS = [
119
120
  if (pool === undefined)
120
121
  throw new Error(POOL_UNAVAILABLE);
121
122
  const url = asString(args['url']);
122
- if (url === undefined || url.length === 0)
123
+ if (url === undefined || 0 === url.length)
123
124
  throw new Error('reticle_lease{action:"acquire"} requires a url');
124
125
  const projectId = asString(args['projectId']);
125
126
  const sessionId = newLeaseId();
@@ -144,7 +145,7 @@ export const LEASE_TOOLS = [
144
145
  ...(ready
145
146
  ? {}
146
147
  : {
147
- hint: `leased tab did not connect — is ${url} running with @reticlehq/core enabled?`,
148
+ hint: leaseNotConnectedHint(url, deps.bridgePort ?? RETICLE_DEFAULT_PORT),
148
149
  }),
149
150
  };
150
151
  },
@@ -166,7 +167,7 @@ export const LEASE_TOOLS = [
166
167
  if (pool === undefined)
167
168
  throw new Error(POOL_UNAVAILABLE);
168
169
  const sessionId = asString(args['sessionId']);
169
- if (sessionId === undefined || sessionId.length === 0) {
170
+ if (sessionId === undefined || 0 === sessionId.length) {
170
171
  throw new Error('reticle_lease{action:"release"} requires a sessionId');
171
172
  }
172
173
  await pool.release(sessionId);
@@ -16,11 +16,13 @@ import type { ToolDef } from './tool-kit.js';
16
16
  * like flow_replay/flow_verify/flow_heal that return their own documented verdict shapes — are NOT
17
17
  * merged. Only sibling families whose value is the capability, not the contract, are consolidated.
18
18
  */
19
- export interface MergeSpec {
19
+ interface MergeSpec {
20
20
  name: string;
21
21
  description: string;
22
22
  /** action value → the original tool whose handler serves it. */
23
23
  actions: Record<string, ToolDef>;
24
+ /** A concrete call, carried through to the merged def (see ToolDef.example). */
25
+ example?: Record<string, unknown>;
24
26
  }
25
27
  /** A merge declared by member NAME, resolved against the assembled tool list. */
26
28
  export interface MergePlan {
@@ -28,6 +30,12 @@ export interface MergePlan {
28
30
  description: string;
29
31
  /** action value → the existing tool name whose handler serves it. */
30
32
  members: Record<string, string>;
33
+ /**
34
+ * A concrete call for the merged tool. Merging discards the members' own examples — the schema is
35
+ * a union and theirs are per-member — so a merged tool that reaches the core surface has no
36
+ * example at all unless the plan states one.
37
+ */
38
+ example?: Record<string, unknown>;
31
39
  }
32
40
  /**
33
41
  * Apply the consolidation to an assembled tool list: drop each plan's members and each retired name from
@@ -40,3 +48,4 @@ export interface MergePlan {
40
48
  */
41
49
  export declare function applyMerges(tools: readonly ToolDef[], plans: readonly MergePlan[], retired?: readonly string[]): ToolDef[];
42
50
  export declare function mergeTools(spec: MergeSpec): ToolDef;
51
+ export {};
@@ -33,17 +33,23 @@ export function applyMerges(tools, plans, retired = []) {
33
33
  actions[action] = member;
34
34
  consumed.add(memberName);
35
35
  }
36
- merged.push(mergeTools({ name: plan.name, description: plan.description, actions }));
36
+ merged.push(mergeTools({
37
+ name: plan.name,
38
+ description: plan.description,
39
+ actions,
40
+ ...(plan.example === undefined ? {} : { example: plan.example }),
41
+ }));
37
42
  }
38
43
  return [...tools.filter((t) => !consumed.has(t.name)), ...merged];
39
44
  }
40
45
  export function mergeTools(spec) {
41
46
  const actionNames = Object.keys(spec.actions);
42
- if (actionNames.length === 0)
47
+ if (0 === actionNames.length)
43
48
  throw new Error(`mergeTools(${spec.name}): no actions`);
44
49
  return {
45
50
  name: spec.name,
46
51
  description: spec.description,
52
+ ...(spec.example === undefined ? {} : { example: spec.example }),
47
53
  inputSchema: {
48
54
  action: z
49
55
  .enum(actionNames)
@@ -52,7 +58,7 @@ export function mergeTools(spec) {
52
58
  },
53
59
  handler: (deps, args) => {
54
60
  const action = args['action'];
55
- const chosen = typeof action === 'string' ? spec.actions[action] : undefined;
61
+ const chosen = 'string' === typeof action ? spec.actions[action] : undefined;
56
62
  if (chosen === undefined) {
57
63
  return Promise.resolve({
58
64
  error: `unknown action '${String(action)}' for ${spec.name}`,
@@ -0,0 +1,13 @@
1
+ interface MergedNameRedirect {
2
+ /** The tool to call instead. */
3
+ tool: string;
4
+ /** The action that selects the old behaviour, when the name was merged rather than retired. */
5
+ action?: string;
6
+ /** Why there is no direct replacement, for a retired name. */
7
+ note?: string;
8
+ }
9
+ /** Where `name` went, or undefined when it is a live tool or not ours. */
10
+ export declare function mergedNameRedirect(name: string): MergedNameRedirect | undefined;
11
+ /** The sentence handed to an agent that called `name`. */
12
+ export declare function mergedNameMessage(name: string, redirect: MergedNameRedirect): string;
13
+ export {};
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Where a merged or retired tool's old name went.
3
+ *
4
+ * Twenty-two names exported from `ReticleTool` are no longer tools: `reticle_record_start` became
5
+ * `reticle_record { action: "start" }`, `reticle_diff` became `reticle_baseline { action: "diff" }`,
6
+ * and three were retired outright. Confirmed live in a field sweep, they all answered `unknown tool`
7
+ * — which is the wrong answer, because the capability exists and only moved. An agent trained on an
8
+ * earlier release, or reading the merged tool's own description (which names every action), reaches
9
+ * for exactly these.
10
+ *
11
+ * DERIVED from `MERGE_PLANS` and `RETIRED_FROM_SURFACE` rather than written out, so the next merge is
12
+ * covered the day it lands instead of the day someone notices.
13
+ */
14
+ import { MERGE_PLANS, RETIRED_FROM_SURFACE } from './tools.js';
15
+ import { ReticleTool } from './tool-names.js';
16
+ /** Retired names, and where the capability actually lives now. */
17
+ const RETIRED_NOTE = {
18
+ [ReticleTool.REFRESH]: {
19
+ tool: ReticleTool.NAVIGATE,
20
+ note: 'reloading was absorbed into reticle_navigate { reload: true }',
21
+ },
22
+ [ReticleTool.RUN_RECORD]: {
23
+ tool: ReticleTool.PROJECT,
24
+ note: 'run outcomes are persisted automatically by reticle_flow_replay; read them with reticle_project',
25
+ },
26
+ [ReticleTool.WAIT_READY]: {
27
+ tool: ReticleTool.SESSIONS,
28
+ note: 'waiting is implicit — the first live call already blocks until the session is ready',
29
+ },
30
+ };
31
+ const BY_OLD_NAME = (() => {
32
+ const map = new Map();
33
+ for (const plan of MERGE_PLANS) {
34
+ for (const [action, oldName] of Object.entries(plan.members)) {
35
+ map.set(oldName, { tool: plan.name, action });
36
+ }
37
+ }
38
+ for (const name of RETIRED_FROM_SURFACE) {
39
+ const known = RETIRED_NOTE[name];
40
+ if (known !== undefined)
41
+ map.set(name, known);
42
+ }
43
+ return map;
44
+ })();
45
+ /** Where `name` went, or undefined when it is a live tool or not ours. */
46
+ export function mergedNameRedirect(name) {
47
+ return BY_OLD_NAME.get(name);
48
+ }
49
+ /** The sentence handed to an agent that called `name`. */
50
+ export function mergedNameMessage(name, redirect) {
51
+ return redirect.action === undefined
52
+ ? `${name} no longer exists — ${redirect.note ?? `use ${redirect.tool}`}.`
53
+ : `${name} was merged into ${redirect.tool}. Call ${redirect.tool} { action: "${redirect.action}", ... } — ` +
54
+ `through reticle_run if it is not advertised under this profile.`;
55
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Wait, briefly, for the SDK to reconnect after a navigation — so `confirmed` can mean something.
3
+ *
4
+ * `window.location.assign(url)` destroys the SDK that would report on the new document, so the
5
+ * navigation itself cannot be confirmed from inside the page. But the SDK reconnects TO THE DAEMON,
6
+ * which makes the daemon the only party that can see arrival happen. Before this, it did not look,
7
+ * and told the agent to poll `reticle_sessions` instead — a tool call on every navigation, on the
8
+ * least reliable tool we ship.
9
+ *
10
+ * Bounded and best-effort by construction: a navigation to a page that is not instrumented, or not
11
+ * there at all, must still return promptly with `confirmed:false` rather than hanging. The clock and
12
+ * the sleep are injected so this is testable without waiting on a real one.
13
+ */
14
+ import type { SessionManager } from '../session/session-manager.js';
15
+ import type { NavigateArrival } from './navigate-result.js';
16
+ /** Long enough for a dev server to serve a page and the SDK to dial back; short enough to not hang. */
17
+ export declare const ARRIVAL_TIMEOUT_MS = 5000;
18
+ export interface ArrivalClock {
19
+ now: () => number;
20
+ sleep: (ms: number) => Promise<void>;
21
+ }
22
+ /**
23
+ * Poll until a session is present at `target`, or the budget runs out. Returns `null` on timeout —
24
+ * never throws, because a failure to confirm is a legitimate answer, not an error.
25
+ */
26
+ export declare function awaitArrival(sessions: SessionManager, target: string, timeoutMs?: number, clock?: ArrivalClock): Promise<NavigateArrival | null>;
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Wait, briefly, for the SDK to reconnect after a navigation — so `confirmed` can mean something.
3
+ *
4
+ * `window.location.assign(url)` destroys the SDK that would report on the new document, so the
5
+ * navigation itself cannot be confirmed from inside the page. But the SDK reconnects TO THE DAEMON,
6
+ * which makes the daemon the only party that can see arrival happen. Before this, it did not look,
7
+ * and told the agent to poll `reticle_sessions` instead — a tool call on every navigation, on the
8
+ * least reliable tool we ship.
9
+ *
10
+ * Bounded and best-effort by construction: a navigation to a page that is not instrumented, or not
11
+ * there at all, must still return promptly with `confirmed:false` rather than hanging. The clock and
12
+ * the sleep are injected so this is testable without waiting on a real one.
13
+ */
14
+ /** Long enough for a dev server to serve a page and the SDK to dial back; short enough to not hang. */
15
+ export const ARRIVAL_TIMEOUT_MS = 5_000;
16
+ const POLL_MS = 100;
17
+ /**
18
+ * Compare only origin + pathname.
19
+ *
20
+ * The app is entitled to add or rewrite the query and hash on arrival — a redirect to
21
+ * `?redirect=%2F`, a router normalising a trailing slash, an auth guard appending a reason. Matching
22
+ * the whole URL string would report `confirmed:false` for a navigation that plainly succeeded, which
23
+ * is the same lie in the opposite direction.
24
+ */
25
+ function samePage(a, b) {
26
+ try {
27
+ const x = new URL(a);
28
+ const y = new URL(b);
29
+ return x.origin === y.origin && x.pathname.replace(/\/$/, '') === y.pathname.replace(/\/$/, '');
30
+ }
31
+ catch {
32
+ return false;
33
+ }
34
+ }
35
+ function findArrival(sessions, target) {
36
+ for (const s of sessions.all()) {
37
+ if (samePage(s.url, target))
38
+ return { sessionId: s.id };
39
+ }
40
+ return null;
41
+ }
42
+ const REAL_CLOCK = {
43
+ now: () => Date.now(),
44
+ sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
45
+ };
46
+ /**
47
+ * Poll until a session is present at `target`, or the budget runs out. Returns `null` on timeout —
48
+ * never throws, because a failure to confirm is a legitimate answer, not an error.
49
+ */
50
+ export async function awaitArrival(sessions, target, timeoutMs = ARRIVAL_TIMEOUT_MS, clock = REAL_CLOCK) {
51
+ const deadline = clock.now() + timeoutMs;
52
+ for (;;) {
53
+ const found = findArrival(sessions, target);
54
+ if (found !== null)
55
+ return found;
56
+ if (clock.now() >= deadline)
57
+ return null;
58
+ await clock.sleep(POLL_MS);
59
+ }
60
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * The honest envelope for a navigation nobody can confirm.
3
+ *
4
+ * `window.location.assign(url)` returns before the page moves, and the SDK that would report on the
5
+ * new document is destroyed by the navigation itself. So a successful NAVIGATE means the browser
6
+ * accepted the instruction — not that anything arrived. Driven against a dead URL it returned
7
+ * `{"ok":true}` while the session died, which is the worst combination available: the agent believes
8
+ * it navigated, is looking at nothing, and has lost Reticle.
9
+ *
10
+ * `reticle_act` already draws this line — `dispatched` (sent) versus `settled` (a frame flushed).
11
+ * This gives navigate the same honesty without changing what it does.
12
+ */
13
+ /** The session the SDK reconnected as after the page moved, when one was observed in the window. */
14
+ export interface NavigateArrival {
15
+ sessionId: string;
16
+ }
17
+ export declare function navigateResult(result: {
18
+ ok?: unknown;
19
+ url?: unknown;
20
+ reason?: unknown;
21
+ },
22
+ /**
23
+ * Arrival, if the daemon saw the SDK come back. `null` keeps the original honest envelope.
24
+ *
25
+ * Measured on three fixtures: `confirmed` was `false` on every navigation on every app, because
26
+ * nothing ever set it. A boolean that never varies teaches an agent nothing, and an agent that
27
+ * learns to ignore it will keep ignoring it when it starts meaning something. The daemon is the
28
+ * one place that CAN answer this — the SDK reconnects to it — so making it answer removes a
29
+ * `reticle_sessions` poll from every single navigation.
30
+ */
31
+ arrival?: NavigateArrival | null): Record<string, unknown>;
@@ -0,0 +1,38 @@
1
+ export function navigateResult(result,
2
+ /**
3
+ * Arrival, if the daemon saw the SDK come back. `null` keeps the original honest envelope.
4
+ *
5
+ * Measured on three fixtures: `confirmed` was `false` on every navigation on every app, because
6
+ * nothing ever set it. A boolean that never varies teaches an agent nothing, and an agent that
7
+ * learns to ignore it will keep ignoring it when it starts meaning something. The daemon is the
8
+ * one place that CAN answer this — the SDK reconnects to it — so making it answer removes a
9
+ * `reticle_sessions` poll from every single navigation.
10
+ */
11
+ arrival = null) {
12
+ const ok = true === result.ok;
13
+ const base = {
14
+ ok,
15
+ ...('string' === typeof result.url ? { url: result.url } : {}),
16
+ ...('string' === typeof result.reason ? { reason: result.reason } : {}),
17
+ };
18
+ // A refusal is conclusive: the page never moved, so there is nothing unconfirmed to report.
19
+ if (!ok)
20
+ return base;
21
+ if (arrival !== null) {
22
+ return {
23
+ ...base,
24
+ confirmed: true,
25
+ // The SDK that reconnects after a navigation is a NEW session with a new id. Returning it here
26
+ // is the difference between a confirmation and a confirmation the agent can act on.
27
+ sessionId: arrival.sessionId,
28
+ };
29
+ }
30
+ return {
31
+ ...base,
32
+ confirmed: false,
33
+ note: 'ok means the navigation was DISPATCHED, not that the page arrived — the SDK is torn down by ' +
34
+ 'the navigation itself, so nothing here can see the new document. Call reticle_sessions to ' +
35
+ 'confirm a session reconnected at the new URL before acting; if none appears, the page did ' +
36
+ 'not load or is not instrumented.',
37
+ };
38
+ }