@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,10 +1,22 @@
1
1
  import { z } from 'zod';
2
- import { ReticleCommand, SnapshotMode } from '@reticlehq/core';
2
+ import { QueryBy, ReticleCommand, SnapshotMode } from '@reticlehq/core';
3
3
  import { ReticleTool } from './tool-names.js';
4
4
  import { withSizeCost } from '../session/output-budget.js';
5
5
  import { applySnapshotDelta, SnapshotCache } from './snapshot-delta.js';
6
6
  import { asString, asNumber } from './tools-helpers.js';
7
+ import { normalizeQueryArgs } from './query-shape.js';
7
8
  import { paginateQueryResult } from './query-paginate.js';
9
+ /**
10
+ * The query strategies, derived from the enum in core — never retyped.
11
+ *
12
+ * The description used to hand-list them and had already drifted: it named six and omitted
13
+ * `component`, which is real and works. Deriving both the schema and the prose from one source is
14
+ * the repo's own rule, and here it is also the difference between refusing `by:'css'` and silently
15
+ * answering "0 matches" to it.
16
+ */
17
+ const QUERY_BY_VALUES = Object.values(QueryBy);
18
+ const QUERY_BY_LIST = QUERY_BY_VALUES.join(' | ');
19
+ const queryByEnum = z.enum(QUERY_BY_VALUES);
8
20
  import { CONTRACT_TOOLS } from './contract-tools.js';
9
21
  import { DOMAIN_TOOLS } from '../domain/domain-tools.js';
10
22
  import { BROWSER_TOOLS } from './browser-tools.js';
@@ -49,6 +61,10 @@ const RAW_TOOLS = [
49
61
  title: z.string().optional(),
50
62
  adapters: z.array(z.string()),
51
63
  hasCapabilities: z.boolean(),
64
+ versionSkew: z
65
+ .string()
66
+ .optional()
67
+ .describe("Present only when this page's SDK version differs from the daemon's. A skewed pair connects and then disagrees about tool behaviour — usually surfacing as a bare -32000. Fix it before trusting any verdict from this session."),
52
68
  lastSeenMs: z.number(),
53
69
  throttled: z.boolean(),
54
70
  focused: z.boolean(),
@@ -151,19 +167,24 @@ const RAW_TOOLS = [
151
167
  sessionId: resolved.id,
152
168
  scope: asString(args['scope']) ?? '',
153
169
  mode,
154
- diff: args['diff'] === true,
170
+ diff: true === args['diff'],
155
171
  }, SNAPSHOT_CACHE)));
156
172
  },
157
173
  },
158
174
  {
159
175
  name: ReticleTool.QUERY,
160
176
  example: { by: 'testid', value: 'todo-list' },
161
- description: 'Find elements by Testing-Library semantics, INCLUDING open shadow roots — `count_only:true` gives just the count (~30x smaller); `limit` caps descriptors. Pass `by` (role|text|label|placeholder|testid|alt) and `value` (the query string). Returns matching refs + descriptors + visibility. Pass `attrs:["href"]` to project attributes (link/image URLs) onto each match. Pass `limit` to cap descriptors (broad role queries can be large) or `count_only:true` for just the match count — both cut tokens. On zero matches, also returns hint:{ route, presentRegions[], knownEmptyState } so you can distinguish an empty state from a missing element WITHOUT taking a snapshot.',
177
+ description: `Find elements by Testing-Library semantics, INCLUDING open shadow roots — \`count_only:true\` gives just the count (~30x smaller); \`limit\` caps descriptors. Pass \`by\` (${QUERY_BY_LIST}) and \`value\` (the query string). Returns matching refs + descriptors + visibility. Pass \`attrs:["href"]\` to project attributes (link/image URLs) onto each match. Pass \`limit\` to cap descriptors (broad role queries can be large) or \`count_only:true\` for just the match count — both cut tokens. On zero matches, also returns hint:{ route, presentRegions[], knownEmptyState } so you can distinguish an empty state from a missing element WITHOUT taking a snapshot.`,
162
178
  inputSchema: {
163
- by: z.string().describe('Query strategy: role | text | label | placeholder | testid | alt'),
179
+ // Constrained to the enum, NOT z.string(). A free string let `by:'css'` through to the
180
+ // browser's `default: return []`, so an unsupported strategy answered "0 matches" — which
181
+ // reads as "the element is not on the page". Measured on a live page: by:'css' value:'body'
182
+ // returned count 0. A false negative is the one answer this product must never invent.
183
+ by: queryByEnum.optional().describe(`Query strategy: ${QUERY_BY_LIST}`),
164
184
  value: z
165
185
  .string()
166
- .describe('Query value for the selected strategy (e.g. by=role value=button, or by=testid value=submit-btn).'),
186
+ .optional()
187
+ .describe('Query value for the selected strategy (e.g. by=role value=button, or by=testid value=submit-btn). Or use the predicate spelling directly: { testid: "submit" }, { text: "Deploy" }.'),
167
188
  name: z
168
189
  .string()
169
190
  .optional()
@@ -185,6 +206,14 @@ const RAW_TOOLS = [
185
206
  .boolean()
186
207
  .optional()
187
208
  .describe('Return just { count } (no element descriptors) — use when you only need "how many match?" and not their refs.'),
209
+ // The predicate's spelling, accepted as an alias for by/value so the shape an agent learns from
210
+ // act_and_wait/assert also works here. See query-shape.ts.
211
+ testid: z.string().optional().describe('Alias for by="testid" value=… (predicate spelling).'),
212
+ text: z.string().optional().describe('Alias for by="text" value=… (predicate spelling).'),
213
+ role: z.string().optional().describe('Alias for by="role" value=… (predicate spelling).'),
214
+ label: z.string().optional().describe('Alias for by="label" value=… (predicate spelling).'),
215
+ placeholder: z.string().optional().describe('Alias for by="placeholder" value=… .'),
216
+ alt: z.string().optional().describe('Alias for by="alt" value=… (predicate spelling).'),
188
217
  ...sessionIdShape,
189
218
  },
190
219
  outputSchema: {
@@ -257,16 +286,20 @@ const RAW_TOOLS = [
257
286
  .optional()
258
287
  .describe('True when a `scope` was given but resolved to nothing — zero matches then means the scope is gone, NOT that the element is absent. The search did not widen to the whole page. Re-check the scope before concluding anything.'),
259
288
  },
260
- handler: (deps, args) => commandOrThrow(deps, asString(args['sessionId']), ReticleCommand.QUERY, {
261
- by: args['by'],
262
- value: args['value'],
263
- name: args['name'],
264
- scope: args['scope'],
265
- // The handler forwards an explicit allowlist, so a new input is silently dropped unless it is
266
- // added here — the browser saw no `attrs` and returned undefined while the unit tests, which
267
- // call matchQuery directly, passed. Schema plus implementation is not the whole wire.
268
- attrs: args['attrs'],
269
- }).then((result) => withSizeCost(paginateQueryResult(result, asNumber(args['limit']), args['count_only'] === true))),
289
+ handler: (deps, rawArgs) => {
290
+ // Accept the predicate's named-field spelling too — see query-shape.ts.
291
+ const args = normalizeQueryArgs(rawArgs);
292
+ return commandOrThrow(deps, asString(args['sessionId']), ReticleCommand.QUERY, {
293
+ by: args['by'],
294
+ value: args['value'],
295
+ name: args['name'],
296
+ scope: args['scope'],
297
+ // The handler forwards an explicit allowlist, so a new input is silently dropped unless it is
298
+ // added here — the browser saw no `attrs` and returned undefined while the unit tests, which
299
+ // call matchQuery directly, passed. Schema plus implementation is not the whole wire.
300
+ attrs: args['attrs'],
301
+ }).then((result) => withSizeCost(paginateQueryResult(result, asNumber(args['limit']), true === args['count_only'])));
302
+ },
270
303
  },
271
304
  {
272
305
  name: ReticleTool.INSPECT,
@@ -383,7 +416,7 @@ const RAW_TOOLS = [
383
416
  * Guardrail (from the plan): the 12-tool core hot-set keeps its EXACT names and shapes and is never
384
417
  * merged — sessions/navigate/snapshot/query/act/act_and_wait/observe/network/console/wait_for/assert/state.
385
418
  */
386
- const MERGE_PLANS = [
419
+ export const MERGE_PLANS = [
387
420
  {
388
421
  name: ReticleTool.BASELINE,
389
422
  description: 'Semantic-state baselines: { action: "save" } snapshots the current state under a name, "list" returns saved names, "diff" compares the live page against a saved baseline (REMOVED/ADDED elements + console-error count).',
@@ -419,6 +452,9 @@ const MERGE_PLANS = [
419
452
  review: ReticleTool.REVIEW,
420
453
  narrate: ReticleTool.NARRATE,
421
454
  },
455
+ // The handback the lease block orders on every session — the one call an agent MUST make, so it
456
+ // is the one that must not need a schema lookup first.
457
+ example: { action: 'yield', mode: 'waiting' },
422
458
  },
423
459
  {
424
460
  name: ReticleTool.LEASE,
@@ -430,7 +466,7 @@ const MERGE_PLANS = [
430
466
  * Retired from the MCP surface entirely (capability preserved elsewhere, per the plan):
431
467
  * - run_record: auto-recording on flow_replay already persists run outcomes.
432
468
  */
433
- const RETIRED_FROM_SURFACE = [
469
+ export const RETIRED_FROM_SURFACE = [
434
470
  ReticleTool.RUN_RECORD, // auto-recording on flow_replay already persists run outcomes
435
471
  ReticleTool.REFRESH, // absorbed into reticle_navigate { reload: true }
436
472
  ReticleTool.WAIT_READY, // server-internal: the first live call already blocks for the session
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Guidance for `name`, or undefined when there is nothing useful to add — the tool IS advertised (so
3
+ * any error belongs to the call itself), or the name is not ours to explain.
4
+ */
5
+ export declare function unadvertisedToolHelp(name: string, advertised: ReadonlySet<string>, known: ReadonlySet<string>): string | undefined;
@@ -0,0 +1,39 @@
1
+ /**
2
+ * What to say when an agent calls a real Reticle tool that this profile does not advertise.
3
+ *
4
+ * The default profile advertises 16 of 46 tools. That is deliberate — every advertised tool's schema
5
+ * is re-sent on every turn, and the trim is most of why `hybrid` is cheap — and the other 30 stay
6
+ * fully callable through `reticle_run`, which `profile-reachability.test.ts` guards.
7
+ *
8
+ * The gap was in the ERROR. The MCP SDK answers `Tool <name> not found`, which is indistinguishable
9
+ * from "this tool does not exist", so an agent that trusts it stops trying. Reported from a real
10
+ * sweep of a Next app-router project: the first pass scored 25 failures that were nothing of the
11
+ * kind — every one of those tools worked through `reticle_run` seconds later.
12
+ *
13
+ * So: a name Reticle owns never comes back as "not found". It comes back with the call that works
14
+ * and the switch that makes it stop being necessary.
15
+ */
16
+ import { ReticleTool } from './tool-names.js';
17
+ import { TOOL_SURFACE, TOOL_PROFILE_ENV } from './tool-surface.js';
18
+ import { mergedNameRedirect, mergedNameMessage } from './merged-name-redirect.js';
19
+ /**
20
+ * Guidance for `name`, or undefined when there is nothing useful to add — the tool IS advertised (so
21
+ * any error belongs to the call itself), or the name is not ours to explain.
22
+ */
23
+ export function unadvertisedToolHelp(name, advertised, known) {
24
+ if (advertised.has(name))
25
+ return undefined;
26
+ // A name that MOVED gets the move, not a profile lecture — it is not un-advertised, it is gone.
27
+ const moved = mergedNameRedirect(name);
28
+ if (moved !== undefined)
29
+ return mergedNameMessage(name, moved);
30
+ if (!known.has(name))
31
+ return undefined;
32
+ return (`${name} exists and works, but is not advertised under this tool profile — the schemas for all ` +
33
+ `tools are re-sent every turn, so the default advertises a subset and keeps the rest one call ` +
34
+ `away. It is NOT missing: invoke it with ` +
35
+ `${ReticleTool.RUN} { tool: "${name}", args: { ... } }. ` +
36
+ `Call ${ReticleTool.TOOLS} { names: ["${name}"] } for its parameters. ` +
37
+ `If you need it repeatedly, set ${TOOL_PROFILE_ENV}=${TOOL_SURFACE.ALL} to advertise ` +
38
+ `every tool directly.`);
39
+ }
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Verbose internal flow tracing, for people working ON Reticle.
3
+ *
4
+ * The gap it fills: the journal records what the AGENT did to the app, and telemetry records
5
+ * aggregates. Neither says which of OUR code a tool call went through, in what order, or where the
6
+ * time went — so every "why is this flow slow" and "which path produced this verdict" was
7
+ * reconstructed by hand from reading source. One line per stage answers both.
8
+ *
9
+ * Design, in order of what it cost to get wrong elsewhere in this repo:
10
+ *
11
+ * - OFF by default, and ~126ns per site when off (measured) — `span` calls the function and
12
+ * returns. A trace on every tool call is a real cost on the hot path, and the hot path is a
13
+ * verification loop of 50–200 calls.
14
+ * - ONE line per stage, emitted when it ENDS, carrying its own duration. A start line as well
15
+ * would double the volume to say something the end line already implies.
16
+ * - Correlated by a call id and a depth, carried in AsyncLocalStorage rather than threaded through
17
+ * every signature. Several agents are inside `runTool` at once — interleaving is the normal case
18
+ * — so without an id the output is a pile of unrelated lines, and instrumenting anything would
19
+ * mean changing its parameters, which is how instrumentation stops being added.
20
+ * - Through the existing `log()`, so it lands in the same stderr stream the daemon already
21
+ * redirects to `~/.reticle/daemon-<port>.log`. No new sink, no new file, nothing else to tail.
22
+ */
23
+ /**
24
+ * Whether tracing is on.
25
+ *
26
+ * Read per call, not cached at import, and the reason is `loadDotEnv`: the CLI populates
27
+ * `process.env` from the project's .env AFTER every module has been imported, so a value captured at
28
+ * import time would miss a `RETICLE_TRACE` set there — silently, which is the worst way for a
29
+ * diagnostic switch to fail. (The reason originally given here — "so it can be turned on while the
30
+ * daemon runs" — was wrong: nothing outside a process can change its environment.)
31
+ *
32
+ * Measured cost of reading it per call: 126ns per traced site when tracing is OFF, against a ~9ns
33
+ * bare call. With a handful of spans per tool call that is under a microsecond on a call that takes
34
+ * milliseconds — cheap enough to be worth the correctness, and not the literal zero this comment
35
+ * used to imply.
36
+ */
37
+ export declare function traceEnabled(): boolean;
38
+ /**
39
+ * Bind a callback to the call that is running NOW, so it reports inside that call when it fires later.
40
+ *
41
+ * `waitForPredicate` re-checks its predicate from a WebSocket event listener and an interval. Neither
42
+ * is in the awaited chain, so AsyncLocalStorage does not reach them and every re-check used to open a
43
+ * brand new call at depth 0. Measured on one healthy run: 165 spans across 60 callIds, 25 of them a
44
+ * `browser.command` with no matching `tool.handler`.
45
+ *
46
+ * That is not cosmetic. The documented signature of a HUNG tool call is exactly "a browser.command
47
+ * with no tool.handler for that callId, ever" — the invariant that located a real hang. A clean run
48
+ * emitting 23 of them makes the invariant unusable and hides the next hang in its own noise.
49
+ *
50
+ * Returns `fn` unchanged when there is no call to bind to, so a caller outside a span is not given a
51
+ * fabricated parent.
52
+ */
53
+ export declare function bindSpanContext<A extends unknown[]>(fn: (...args: A) => void): (...args: A) => void;
54
+ /**
55
+ * Run `fn` as a named stage, recording how long it took and how it sat inside the call around it.
56
+ *
57
+ * Wrap a stage worth a line in a bottleneck hunt — a session resolve, a browser round-trip, a
58
+ * predicate evaluation — not every function. The value of the trace is that reading it is faster
59
+ * than reading the code, and that stops being true somewhere around one line per statement.
60
+ */
61
+ export declare function span<T>(name: string, fields: Record<string, unknown>, fn: () => Promise<T> | T): Promise<T>;
62
+ /**
63
+ * The synchronous form. Same line, same tree, same cost when off.
64
+ *
65
+ * It exists because `reticle init` — the flow every user hits before anything else, and the one
66
+ * whose slowness they experience personally — is synchronous end to end. An async-only span left
67
+ * exactly that flow untraceable.
68
+ */
69
+ export declare function spanSync<T>(name: string, fields: Record<string, unknown>, fn: () => T): T;
package/dist/trace.js ADDED
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Verbose internal flow tracing, for people working ON Reticle.
3
+ *
4
+ * The gap it fills: the journal records what the AGENT did to the app, and telemetry records
5
+ * aggregates. Neither says which of OUR code a tool call went through, in what order, or where the
6
+ * time went — so every "why is this flow slow" and "which path produced this verdict" was
7
+ * reconstructed by hand from reading source. One line per stage answers both.
8
+ *
9
+ * Design, in order of what it cost to get wrong elsewhere in this repo:
10
+ *
11
+ * - OFF by default, and ~126ns per site when off (measured) — `span` calls the function and
12
+ * returns. A trace on every tool call is a real cost on the hot path, and the hot path is a
13
+ * verification loop of 50–200 calls.
14
+ * - ONE line per stage, emitted when it ENDS, carrying its own duration. A start line as well
15
+ * would double the volume to say something the end line already implies.
16
+ * - Correlated by a call id and a depth, carried in AsyncLocalStorage rather than threaded through
17
+ * every signature. Several agents are inside `runTool` at once — interleaving is the normal case
18
+ * — so without an id the output is a pile of unrelated lines, and instrumenting anything would
19
+ * mean changing its parameters, which is how instrumentation stops being added.
20
+ * - Through the existing `log()`, so it lands in the same stderr stream the daemon already
21
+ * redirects to `~/.reticle/daemon-<port>.log`. No new sink, no new file, nothing else to tail.
22
+ */
23
+ import { AsyncLocalStorage } from 'node:async_hooks';
24
+ import { ReticleEnv } from '@reticlehq/core';
25
+ import { log } from './log.js';
26
+ /** The `event` field every trace line carries, so one `grep` separates it from milestone logs. */
27
+ const TRACE_EVENT = 'trace';
28
+ const ON_VALUES = new Set(['1', 'true', 'on', 'yes']);
29
+ const context = new AsyncLocalStorage();
30
+ /**
31
+ * Monotonic within a process, and PID-qualified so it stays unique across them.
32
+ *
33
+ * A bare counter was not enough: the e2e battery runs many daemons, and a restarted daemon starts
34
+ * over at 1, so `c7` named a different call in every process. Aggregating that trace silently
35
+ * merged unrelated calls into one — measured on the first real trace collected, before it had a
36
+ * chance to mislead anything.
37
+ */
38
+ let nextCallId = 0;
39
+ const CALL_ID_PREFIX = `p${String(process.pid)}-c`;
40
+ /**
41
+ * Whether tracing is on.
42
+ *
43
+ * Read per call, not cached at import, and the reason is `loadDotEnv`: the CLI populates
44
+ * `process.env` from the project's .env AFTER every module has been imported, so a value captured at
45
+ * import time would miss a `RETICLE_TRACE` set there — silently, which is the worst way for a
46
+ * diagnostic switch to fail. (The reason originally given here — "so it can be turned on while the
47
+ * daemon runs" — was wrong: nothing outside a process can change its environment.)
48
+ *
49
+ * Measured cost of reading it per call: 126ns per traced site when tracing is OFF, against a ~9ns
50
+ * bare call. With a handful of spans per tool call that is under a microsecond on a call that takes
51
+ * milliseconds — cheap enough to be worth the correctness, and not the literal zero this comment
52
+ * used to imply.
53
+ */
54
+ export function traceEnabled() {
55
+ const raw = process.env[ReticleEnv.TRACE];
56
+ return raw !== undefined && ON_VALUES.has(raw.trim().toLowerCase());
57
+ }
58
+ /** The scope a stage runs in, plus how to report it. Shared by the async and sync forms. */
59
+ function openSpan(name, fields) {
60
+ const parent = context.getStore();
61
+ const scope = parent === undefined
62
+ ? { callId: `${CALL_ID_PREFIX}${String((nextCallId += 1))}`, depth: 0 }
63
+ : parent;
64
+ const depth = parent === undefined ? 0 : parent.depth;
65
+ const startedAt = Date.now();
66
+ return {
67
+ // The child scope is what nested spans see: same call, one level deeper.
68
+ child: { callId: scope.callId, depth: depth + 1 },
69
+ emit: (ok, extra) => {
70
+ log(TRACE_EVENT, {
71
+ span: name,
72
+ ms: Date.now() - startedAt,
73
+ depth,
74
+ callId: scope.callId,
75
+ ok,
76
+ ...fields,
77
+ ...extra,
78
+ });
79
+ },
80
+ };
81
+ }
82
+ /**
83
+ * Bind a callback to the call that is running NOW, so it reports inside that call when it fires later.
84
+ *
85
+ * `waitForPredicate` re-checks its predicate from a WebSocket event listener and an interval. Neither
86
+ * is in the awaited chain, so AsyncLocalStorage does not reach them and every re-check used to open a
87
+ * brand new call at depth 0. Measured on one healthy run: 165 spans across 60 callIds, 25 of them a
88
+ * `browser.command` with no matching `tool.handler`.
89
+ *
90
+ * That is not cosmetic. The documented signature of a HUNG tool call is exactly "a browser.command
91
+ * with no tool.handler for that callId, ever" — the invariant that located a real hang. A clean run
92
+ * emitting 23 of them makes the invariant unusable and hides the next hang in its own noise.
93
+ *
94
+ * Returns `fn` unchanged when there is no call to bind to, so a caller outside a span is not given a
95
+ * fabricated parent.
96
+ */
97
+ export function bindSpanContext(fn) {
98
+ const captured = context.getStore();
99
+ if (captured === undefined)
100
+ return fn;
101
+ return (...args) => {
102
+ context.run(captured, () => fn(...args));
103
+ };
104
+ }
105
+ /** How much of a thrown value to keep. Enough to identify it, not enough to paste a stack into a log. */
106
+ const ERROR_CHARS = 300;
107
+ /**
108
+ * Run `fn` as a named stage, recording how long it took and how it sat inside the call around it.
109
+ *
110
+ * Wrap a stage worth a line in a bottleneck hunt — a session resolve, a browser round-trip, a
111
+ * predicate evaluation — not every function. The value of the trace is that reading it is faster
112
+ * than reading the code, and that stops being true somewhere around one line per statement.
113
+ */
114
+ export async function span(name, fields, fn) {
115
+ if (!traceEnabled())
116
+ return fn();
117
+ const { child, emit } = openSpan(name, fields);
118
+ try {
119
+ const value = await context.run(child, async () => fn());
120
+ emit(true, {});
121
+ return value;
122
+ }
123
+ catch (err) {
124
+ // A stage that THREW is the one most worth having in the trace: without this line the trace
125
+ // shows a call that entered a stage and never left it, which reads as a hang.
126
+ emit(false, { error: String(err).slice(0, ERROR_CHARS) });
127
+ throw err;
128
+ }
129
+ }
130
+ /**
131
+ * The synchronous form. Same line, same tree, same cost when off.
132
+ *
133
+ * It exists because `reticle init` — the flow every user hits before anything else, and the one
134
+ * whose slowness they experience personally — is synchronous end to end. An async-only span left
135
+ * exactly that flow untraceable.
136
+ */
137
+ export function spanSync(name, fields, fn) {
138
+ if (!traceEnabled())
139
+ return fn();
140
+ const { child, emit } = openSpan(name, fields);
141
+ try {
142
+ const value = context.run(child, fn);
143
+ emit(true, {});
144
+ return value;
145
+ }
146
+ catch (err) {
147
+ emit(false, { error: String(err).slice(0, ERROR_CHARS) });
148
+ throw err;
149
+ }
150
+ }
@@ -0,0 +1,4 @@
1
+ /** Record that an agent was told about `version`. Called when the nudge is actually delivered. */
2
+ export declare function creditNudge(version: string, dir?: string, now?: number): void;
3
+ /** True when this exact version was offered to an agent recently enough to have caused the update. */
4
+ export declare function wasNudged(version: string, dir?: string, now?: number): boolean;
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Did the update nudge cause the update?
3
+ *
4
+ * `version_changed` says a version moved. It has never said why, so the only question worth asking
5
+ * about the nudge — does telling agents about a release make releases get installed — was
6
+ * unanswerable. That is not academic: 2.4.0 carried a fix for a connect defect affecting every Vite
7
+ * app and reached zero users, and nothing in the data could distinguish "the nudge never fired" from
8
+ * "the nudge fired and nobody acted on it". Those need opposite responses.
9
+ *
10
+ * The nudge is delivered by a DAEMON; `reticle update` runs in a different process. They cannot see
11
+ * each other in memory, so a marker file is the join. It holds the version that was offered and
12
+ * when — no identity, nothing about the machine, and it is overwritten rather than appended.
13
+ *
14
+ * Every function here swallows its own errors. This runs inside `reticle update`, which must finish
15
+ * whatever the telemetry believes.
16
+ */
17
+ import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
18
+ import { homedir } from 'node:os';
19
+ import { join } from 'node:path';
20
+ const DEFAULT_DIR = join(homedir(), '.reticle');
21
+ const FILE = 'update-nudge.json';
22
+ /**
23
+ * How long a nudge can plausibly have caused an update. A week is generous for "the agent mentioned
24
+ * it and the human got round to it"; beyond that the two are unrelated events that happen to share a
25
+ * version number.
26
+ */
27
+ const CREDIT_WINDOW_MS = 7 * 24 * 60 * 60 * 1000;
28
+ /** Record that an agent was told about `version`. Called when the nudge is actually delivered. */
29
+ export function creditNudge(version, dir = DEFAULT_DIR, now = Date.now()) {
30
+ try {
31
+ mkdirSync(dir, { recursive: true });
32
+ const record = { offered: version, at: now };
33
+ writeFileSync(join(dir, FILE), JSON.stringify(record), 'utf8');
34
+ }
35
+ catch {
36
+ /* a metric must never break the nudge */
37
+ }
38
+ }
39
+ /** True when this exact version was offered to an agent recently enough to have caused the update. */
40
+ export function wasNudged(version, dir = DEFAULT_DIR, now = Date.now()) {
41
+ try {
42
+ const parsed = JSON.parse(readFileSync(join(dir, FILE), 'utf8'));
43
+ if (typeof parsed !== 'object' || null === parsed)
44
+ return false;
45
+ const record = parsed;
46
+ if (record.offered !== version || typeof record.at !== 'number')
47
+ return false;
48
+ return now - record.at <= CREDIT_WINDOW_MS;
49
+ }
50
+ catch {
51
+ return false;
52
+ }
53
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * The half of `reticle update` that was missing: bringing the APP's SDK to the same version as the
3
+ * daemon being installed.
4
+ *
5
+ * `reticle update` swapped the CLI and stopped. The app kept whatever `@reticlehq/react` it had, so
6
+ * the command whose job is to keep an install current was itself a way to create a version-skewed
7
+ * pair — the failure the HELLO skew check, the nudge and the pin all exist to prevent. The skew
8
+ * message even told people to run `reticle update` to fix an outdated SDK, which could not work.
9
+ *
10
+ * Pinned to an exact version for the same reason `init` pins: an unpinned `add` can resolve out of a
11
+ * stale registry cache and reinstall the skew being fixed. Measured once as pnpm taking 2.2.1 while
12
+ * npm took 2.3.0 in the next project over.
13
+ */
14
+ import { type PackageManager } from '../init/detect.js';
15
+ /** The Reticle packages this project declares, from either dependency section. */
16
+ export declare function reticleDepsOf(pkgJson: unknown): string[];
17
+ /** The install to run, or null when this project has nothing of ours to sync. */
18
+ export declare function sdkSyncCommand(pm: PackageManager, packages: readonly string[], version: string): {
19
+ command: string;
20
+ args: string[];
21
+ } | null;
@@ -0,0 +1,42 @@
1
+ /**
2
+ * The half of `reticle update` that was missing: bringing the APP's SDK to the same version as the
3
+ * daemon being installed.
4
+ *
5
+ * `reticle update` swapped the CLI and stopped. The app kept whatever `@reticlehq/react` it had, so
6
+ * the command whose job is to keep an install current was itself a way to create a version-skewed
7
+ * pair — the failure the HELLO skew check, the nudge and the pin all exist to prevent. The skew
8
+ * message even told people to run `reticle update` to fix an outdated SDK, which could not work.
9
+ *
10
+ * Pinned to an exact version for the same reason `init` pins: an unpinned `add` can resolve out of a
11
+ * stale registry cache and reinstall the skew being fixed. Measured once as pnpm taking 2.2.1 while
12
+ * npm took 2.3.0 in the next project over.
13
+ */
14
+ import { installCommandParts } from '../init/detect.js';
15
+ /** Everything we publish is scoped here. */
16
+ const RETICLE_SCOPE = '@reticlehq/';
17
+ /**
18
+ * The CLI. Never an app dependency: it carries the Node MCP server and `ws`, and putting it in a
19
+ * browser app's tree is what the retired `@reticlehq/core` umbrella got wrong. The other half of
20
+ * `reticle update` owns this one.
21
+ */
22
+ const SERVER_PACKAGE = '@reticlehq/server';
23
+ function namesIn(section) {
24
+ if ('object' !== typeof section || null === section)
25
+ return [];
26
+ return Object.keys(section);
27
+ }
28
+ /** The Reticle packages this project declares, from either dependency section. */
29
+ export function reticleDepsOf(pkgJson) {
30
+ if ('object' !== typeof pkgJson || null === pkgJson)
31
+ return [];
32
+ const manifest = pkgJson;
33
+ const declared = [...namesIn(manifest['dependencies']), ...namesIn(manifest['devDependencies'])];
34
+ return [...new Set(declared)].filter((name) => name.startsWith(RETICLE_SCOPE) && name !== SERVER_PACKAGE);
35
+ }
36
+ /** The install to run, or null when this project has nothing of ours to sync. */
37
+ export function sdkSyncCommand(pm, packages, version) {
38
+ if (0 === packages.length)
39
+ return null;
40
+ const { command, args } = installCommandParts(pm, packages.map((p) => `${p}@${version}`));
41
+ return { command, args: [...args] };
42
+ }
@@ -3,7 +3,7 @@ import * as https from 'node:https';
3
3
  import { join } from 'node:path';
4
4
  import { homedir } from 'node:os';
5
5
  import { UpdateCheckIntervalMs } from '@reticlehq/core';
6
- import { RETICLE_NPM_PACKAGE } from '../server-version.js';
6
+ import { RETICLE_NPM_PACKAGE } from '../version/server-version.js';
7
7
  import { log } from '../log.js';
8
8
  const RETICLE_HOME = join(homedir(), '.reticle');
9
9
  const MANIFEST_PATH = join(RETICLE_HOME, 'update-manifest.json');
@@ -20,12 +20,12 @@ function validateNpmInfo(info) {
20
20
  if (typeof info.version !== 'string' || !VERSION_RE.test(info.version)) {
21
21
  throw new Error('npm registry returned an implausible version');
22
22
  }
23
- const changelog = typeof info.reticle?.changelog === 'string'
23
+ const changelog = 'string' === typeof info.reticle?.changelog
24
24
  ? info.reticle.changelog.slice(0, MAX_MANIFEST_TEXT)
25
25
  : undefined;
26
26
  const breakingChanges = Array.isArray(info.reticle?.breakingChanges)
27
27
  ? info.reticle.breakingChanges
28
- .filter((x) => typeof x === 'string')
28
+ .filter((x) => 'string' === typeof x)
29
29
  .slice(0, 100)
30
30
  .map((s) => s.slice(0, MAX_MANIFEST_TEXT))
31
31
  : undefined;
@@ -1,4 +1,14 @@
1
- export interface UpdateNudge {
1
+ /**
2
+ * Is `candidate` a strictly NEWER release than `current`?
3
+ *
4
+ * Replaces a plain `!==`, which told anyone on a version newer than the published one — a
5
+ * prerelease, a local build, a rollback in progress — to "update" to something older. A nudge that
6
+ * prompts a downgrade is one people learn to ignore, and this is the mechanism the entire adoption
7
+ * story rests on. Numeric per segment, so 2.10.0 correctly beats 2.9.0 (string order does not), and
8
+ * a bare release beats its own prerelease.
9
+ */
10
+ export declare function isNewerVersion(candidate: string, current: string): boolean;
11
+ interface UpdateNudge {
2
12
  /** The version available, so the agent can tell the human what they would be moving to. */
3
13
  latestVersion: string;
4
14
  currentVersion: string;
@@ -7,14 +17,61 @@ export interface UpdateNudge {
7
17
  action: string;
8
18
  }
9
19
  /**
10
- * Kick off a background check. Non-blocking and best-effort: `checkForUpdate` never throws, caches
11
- * for 24h on disk, and falls back to the cached manifest when the registry is unreachable — so this
12
- * costs one npm registry request per day per machine, and nothing at all when offline.
20
+ * Describe the release, not just its number.
21
+ *
22
+ * `packages/server/package.json` carries `reticle.changelog` and `reticle.breakingChanges`, and
23
+ * `update-checker` parses both into the manifest — where they stopped. This function named two
24
+ * versions and nothing else, so both fields were written, shipped, parsed and discarded. Latent
25
+ * metadata nobody ever printed.
26
+ *
27
+ * Survivable while releases are additive; actively harmful for one that is not. An agent told only
28
+ * "2.4.1 → 2.5.0" runs `reticle update` mid-task and discovers a retired environment variable and
29
+ * six newly-strict parameters by breaking on them.
30
+ *
31
+ * Bounded on purpose: this rides on a tool result every turn until it is delivered, so a long
32
+ * changelog is a per-turn tax. The count of anything elided is stated rather than silently dropped.
13
33
  */
34
+ export declare function buildNudge(latestVersion: string, currentVersion: string, release?: {
35
+ changelog?: string;
36
+ breakingChanges?: string[];
37
+ }): UpdateNudge;
14
38
  export declare function startUpdateCheck(now?: () => number): void;
39
+ /**
40
+ * What `reticle update` should install, or undefined when it should do nothing.
41
+ *
42
+ * `handleUpdate` gated on `manifest.updateAvailable`, a plain `latest !== current`, so whenever the
43
+ * registry's latest was OLDER than the running build — a prerelease, a local build, a rollback in
44
+ * progress, a stale npx cache — it announced the move and then installed the downgrade. Reported by
45
+ * a real user on 2026-08-06: "both the `update_available` banner and `reticle update` report the
46
+ * current and target versions swapped, so an upgrade is described as a downgrade."
47
+ */
48
+ export declare function updateTarget(manifest: {
49
+ updateAvailable?: boolean;
50
+ latestVersion?: string;
51
+ }, currentVersion?: string): string | undefined;
52
+ /**
53
+ * Arm from an already-cached answer, synchronously, at daemon boot.
54
+ *
55
+ * The network check fires 8s after boot and the nudge is delivered by riding a tool result — but
56
+ * half the sessions that use Reticle at all make exactly ONE tool call, usually in the first
57
+ * seconds. The check had not finished, so the single chance to tell them was gone, and the
58
+ * population most in need of the update is precisely the one that never heard about it. Yesterday's
59
+ * answer is already on disk; use it now and let the network refresh behind it.
60
+ */
61
+ export declare function armUpdateNudgeFrom(cached: {
62
+ latestVersion?: string;
63
+ updateAvailable?: boolean;
64
+ changelog?: string;
65
+ breakingChanges?: string[];
66
+ } | null, currentVersion?: string): void;
15
67
  /** The nudge, once. Returns undefined afterwards so a long session is told exactly one time. */
16
68
  export declare function takeUpdateNudge(): UpdateNudge | undefined;
69
+ /**
70
+ * The newer version, if the cached answer says there is one — WITHOUT consuming the agent's
71
+ * one-shot nudge. `reticle status` is a human reading a terminal; the two channels are separate
72
+ * audiences and must not steal each other's message.
73
+ */
74
+ export declare function availableUpdate(currentVersion?: string): string | undefined;
17
75
  /** Tests only — drop the module state so each case starts clean. */
18
76
  export declare function resetUpdateNudge(): void;
19
- /** Tests only — seed a pending nudge without going near the network. */
20
- export declare function setPendingUpdate(nudge: UpdateNudge | undefined): void;
77
+ export {};