@reticlehq/server 3.2.0 → 3.4.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 (437) hide show
  1. package/LICENSE-ENTERPRISE +1 -1
  2. package/README.md +3 -3
  3. package/SKILL.md +23 -22
  4. package/dist/chromium-launch-options.d.ts +4 -3
  5. package/dist/chromium-launch-options.js +6 -2
  6. package/dist/command/cli/adhoc-suite.d.ts +41 -0
  7. package/dist/command/cli/adhoc-suite.js +159 -0
  8. package/dist/command/cli/adhoc-verdict.d.ts +58 -2
  9. package/dist/command/cli/adhoc-verdict.js +108 -13
  10. package/dist/command/cli/answers/gate-hook-message.d.ts +6 -0
  11. package/dist/command/cli/answers/gate-hook-message.js +6 -0
  12. package/dist/command/cli/auth/cloud-keystore.d.ts +0 -2
  13. package/dist/command/cli/auth/cloud-keystore.js +0 -3
  14. package/dist/command/cli/auth/cloud-session.d.ts +4 -0
  15. package/dist/command/cli/auth/cloud-session.js +2 -0
  16. package/dist/command/cli/cli-doctor.js +30 -8
  17. package/dist/command/cli/cli-flow-commands.d.ts +3 -6
  18. package/dist/command/cli/cli-flow-commands.js +67 -4
  19. package/dist/command/cli/cli-parse-grammar.d.ts +7 -0
  20. package/dist/command/cli/cli-parse-grammar.js +7 -0
  21. package/dist/command/cli/cli-parse-verify.d.ts +2 -5
  22. package/dist/command/cli/cli-parse-verify.js +86 -13
  23. package/dist/command/cli/cli-parse.d.ts +14 -1
  24. package/dist/command/cli/cli-parse.js +54 -8
  25. package/dist/command/cli/cli-update-commands.js +8 -1
  26. package/dist/command/cli/cli-verify.d.ts +80 -0
  27. package/dist/command/cli/cli-verify.js +181 -26
  28. package/dist/command/cli/cloud-cli.js +5 -2
  29. package/dist/command/cli/cloud-kit.d.ts +0 -2
  30. package/dist/command/cli/cloud-kit.js +3 -5
  31. package/dist/command/cli/cloud-login.js +21 -3
  32. package/dist/command/cli/demo/demo-page.js +6 -3
  33. package/dist/command/cli/doctor/browser/chromium-hint.d.ts +5 -0
  34. package/dist/command/cli/doctor/browser/chromium-hint.js +5 -0
  35. package/dist/command/cli/doctor/doctor-sessions-line.d.ts +2 -1
  36. package/dist/command/cli/launch/cli-launch.d.ts +9 -29
  37. package/dist/command/cli/launch/cli-launch.js +27 -49
  38. package/dist/command/cli/launch/sdk-version-match.d.ts +84 -0
  39. package/dist/command/cli/launch/sdk-version-match.js +206 -0
  40. package/dist/command/cli/lifecycle/daemon-lifecycle.js +2 -16
  41. package/dist/command/cli/ports/resolve/cli-port.d.ts +38 -0
  42. package/dist/command/cli/ports/resolve/cli-port.js +169 -15
  43. package/dist/command/cli/report-command.d.ts +16 -0
  44. package/dist/command/cli/report-command.js +97 -0
  45. package/dist/command/cli/setup-mcp-cli.js +13 -13
  46. package/dist/command/cli/tutorial.d.ts +2 -2
  47. package/dist/command/cli/tutorial.js +21 -7
  48. package/dist/command/cli.js +67 -10
  49. package/dist/command/daemon/binding/daemon-status-probe.d.ts +44 -2
  50. package/dist/command/daemon/binding/daemon-status-probe.js +79 -6
  51. package/dist/command/daemon/binding/port-presence.d.ts +1 -1
  52. package/dist/command/daemon/daemon-resolve.d.ts +9 -0
  53. package/dist/command/daemon/daemon-resolve.js +1 -1
  54. package/dist/command/daemon/idle-shutdown.d.ts +2 -1
  55. package/dist/command/daemon/lifetime/daemon-usefulness.d.ts +7 -1
  56. package/dist/command/daemon/lifetime/daemon-usefulness.js +30 -7
  57. package/dist/command/dev/stale-issue-guard.d.ts +3 -4
  58. package/dist/command/dev/stale-issue-guard.js +8 -8
  59. package/dist/command/setup/bringup/connect-lease.d.ts +17 -0
  60. package/dist/command/setup/bringup/connect-lease.js +38 -0
  61. package/dist/command/setup/bringup/dev-server-files.d.ts +68 -0
  62. package/dist/command/setup/bringup/dev-server-files.js +131 -0
  63. package/dist/command/setup/bringup/dev-server-log-cap.d.ts +35 -0
  64. package/dist/command/setup/bringup/dev-server-log-cap.js +121 -0
  65. package/dist/command/setup/bringup/ensure-daemon.d.ts +65 -4
  66. package/dist/command/setup/bringup/ensure-daemon.js +135 -16
  67. package/dist/command/setup/bringup/static-page-server.d.ts +35 -0
  68. package/dist/command/setup/bringup/static-page-server.js +151 -0
  69. package/dist/command/setup/desktop-shape.d.ts +4 -0
  70. package/dist/command/setup/desktop-shape.js +18 -2
  71. package/dist/command/setup/init/init-host.js +8 -0
  72. package/dist/command/setup/init/init-port.d.ts +15 -0
  73. package/dist/command/setup/init/init-port.js +44 -0
  74. package/dist/command/setup/init/init-runtime.d.ts +1 -1
  75. package/dist/command/setup/init/init-runtime.js +39 -2
  76. package/dist/command/setup/node-effects.d.ts +27 -14
  77. package/dist/command/setup/node-effects.js +80 -44
  78. package/dist/command/setup/probe/loopback-probe-urls.d.ts +7 -0
  79. package/dist/command/setup/probe/loopback-probe-urls.js +40 -0
  80. package/dist/command/setup/probe/page-probe.d.ts +13 -2
  81. package/dist/command/setup/probe/page-probe.js +18 -3
  82. package/dist/command/setup/run-setup.d.ts +26 -1
  83. package/dist/command/setup/run-setup.js +96 -13
  84. package/dist/command/setup/session-pick.d.ts +13 -0
  85. package/dist/command/setup/session-pick.js +20 -0
  86. package/dist/command/setup/setup-command.d.ts +2 -14
  87. package/dist/command/setup/setup-command.js +105 -31
  88. package/dist/command/setup/setup-mcp.js +3 -3
  89. package/dist/command/setup/terminal/wait-progress.d.ts +8 -0
  90. package/dist/command/setup/terminal/wait-progress.js +17 -0
  91. package/dist/features/crawl/crawl-empty.js +1 -1
  92. package/dist/features/crawl/crawl-tools.d.ts +6 -0
  93. package/dist/features/crawl/crawl-tools.js +57 -0
  94. package/dist/features/crawl/crawl.d.ts +2 -0
  95. package/dist/features/crawl/crawl.js +171 -156
  96. package/dist/features/exhaust/app-coverage.d.ts +33 -0
  97. package/dist/features/exhaust/app-coverage.js +31 -0
  98. package/dist/features/exhaust/code-coverage.d.ts +47 -0
  99. package/dist/features/exhaust/code-coverage.js +77 -0
  100. package/dist/features/exhaust/exhaust-run.d.ts +33 -0
  101. package/dist/features/exhaust/exhaust-run.js +82 -0
  102. package/dist/features/exhaust/explorer.d.ts +103 -0
  103. package/dist/features/exhaust/explorer.js +195 -0
  104. package/dist/features/exhaust/failure-branches.d.ts +33 -0
  105. package/dist/features/exhaust/failure-branches.js +92 -0
  106. package/dist/features/exhaust/ledger.d.ts +202 -0
  107. package/dist/features/exhaust/ledger.js +252 -0
  108. package/dist/features/exhaust/session-fold.d.ts +31 -0
  109. package/dist/features/exhaust/session-fold.js +79 -0
  110. package/dist/features/exhaust/session-port.d.ts +36 -0
  111. package/dist/features/exhaust/session-port.js +102 -0
  112. package/dist/features/harness/drive-report.d.ts +0 -9
  113. package/dist/features/harness/drive-report.js +18 -0
  114. package/dist/features/harness/driver.js +4 -7
  115. package/dist/features/harness/fill-values.d.ts +6 -0
  116. package/dist/features/harness/fill-values.js +3 -0
  117. package/dist/features/harness/jev-driver.js +36 -22
  118. package/dist/features/harness/openai-driver.js +4 -7
  119. package/dist/features/harness/platform-config.d.ts +10 -0
  120. package/dist/features/harness/platform-config.js +13 -6
  121. package/dist/index.d.ts +10 -1
  122. package/dist/index.js +78 -104
  123. package/dist/judgement/capsule/capsule-store.d.ts +31 -2
  124. package/dist/judgement/capsule/capsule-store.js +58 -6
  125. package/dist/judgement/capsule/capsule.js +5 -0
  126. package/dist/judgement/capsule/causal-summary.d.ts +13 -1
  127. package/dist/judgement/capsule/causal-summary.js +7 -0
  128. package/dist/judgement/domain/domain-model.js +40 -9
  129. package/dist/judgement/oracles/self-instrument.js +4 -2
  130. package/dist/judgement/outcome/assertion-integrity.d.ts +3 -3
  131. package/dist/judgement/runs/artifact/gap-report.d.ts +17 -0
  132. package/dist/judgement/runs/artifact/gap-report.js +46 -0
  133. package/dist/judgement/runs/artifact/gap-summary.d.ts +58 -0
  134. package/dist/judgement/runs/artifact/gap-summary.js +85 -0
  135. package/dist/judgement/runs/artifact/render-report.js +4 -1
  136. package/dist/judgement/runs/artifact/replay-mapping.d.ts +2 -2
  137. package/dist/judgement/runs/artifact/replay-mapping.js +44 -9
  138. package/dist/judgement/runs/artifact/to-artifact.d.ts +7 -2
  139. package/dist/judgement/runs/artifact/to-artifact.js +1 -1
  140. package/dist/judgement/runs/context-tools.js +21 -3
  141. package/dist/judgement/runs/drive-run.js +7 -2
  142. package/dist/judgement/runs/verification-sync.d.ts +3 -1
  143. package/dist/judgement/runs/verification-sync.js +1 -1
  144. package/dist/language/flows/annotate-notes/annotate.d.ts +0 -15
  145. package/dist/language/flows/annotate-notes/annotate.js +66 -18
  146. package/dist/language/flows/change/affected.d.ts +9 -0
  147. package/dist/language/flows/change/affected.js +35 -0
  148. package/dist/language/flows/change/gate.d.ts +20 -0
  149. package/dist/language/flows/change/gate.js +12 -1
  150. package/dist/language/flows/flow-anchor.d.ts +12 -0
  151. package/dist/language/flows/flow-anchor.js +20 -0
  152. package/dist/language/flows/flow-author.d.ts +7 -0
  153. package/dist/language/flows/flow-author.js +19 -0
  154. package/dist/language/flows/flow-expect-grammar.d.ts +2 -2
  155. package/dist/language/flows/flow-expect-grammar.js +92 -11
  156. package/dist/language/flows/flow-intent.js +1 -1
  157. package/dist/language/flows/flow-journey.d.ts +54 -0
  158. package/dist/language/flows/flow-journey.js +119 -0
  159. package/dist/language/flows/flow-learning.d.ts +3 -0
  160. package/dist/language/flows/flow-learning.js +39 -8
  161. package/dist/language/flows/flow-replay-run.d.ts +33 -12
  162. package/dist/language/flows/flow-replay-run.js +131 -52
  163. package/dist/language/flows/flow-replay-types.d.ts +9 -0
  164. package/dist/language/flows/flow-replay.d.ts +27 -12
  165. package/dist/language/flows/flow-replay.js +127 -111
  166. package/dist/language/flows/flow-result.d.ts +2 -0
  167. package/dist/language/flows/flow-result.js +25 -0
  168. package/dist/language/flows/flow-step-runners.d.ts +26 -10
  169. package/dist/language/flows/flow-step-runners.js +20 -4
  170. package/dist/language/flows/flow-success.d.ts +21 -6
  171. package/dist/language/flows/flow-success.js +50 -138
  172. package/dist/language/flows/flow-tools.js +17 -5
  173. package/dist/language/flows/flows.d.ts +18 -5
  174. package/dist/language/flows/flows.js +101 -16
  175. package/dist/language/flows/heal.d.ts +9 -1
  176. package/dist/language/flows/heal.js +24 -2
  177. package/dist/language/flows/learned-sources.d.ts +21 -0
  178. package/dist/language/flows/learned-sources.js +84 -0
  179. package/dist/language/flows/recording/recording-backtrack.d.ts +0 -7
  180. package/dist/language/flows/recording/recording-backtrack.js +1 -1
  181. package/dist/language/flows/recording/replay-halt.d.ts +2 -1
  182. package/dist/language/flows/recording/replay-halt.js +3 -1
  183. package/dist/language/flows/recording/tape/recordings.d.ts +31 -2
  184. package/dist/language/flows/recording/tape/recordings.js +55 -1
  185. package/dist/language/flows/replay.d.ts +19 -0
  186. package/dist/language/flows/replay.js +53 -13
  187. package/dist/language/flows/stores/annotation-store.d.ts +5 -5
  188. package/dist/language/flows/stores/assertion-tiers-store.js +2 -3
  189. package/dist/language/flows/stores/flake-store.js +2 -3
  190. package/dist/language/flows/suite/coverage.d.ts +1 -1
  191. package/dist/language/flows/suite/parallel-suite.d.ts +2 -1
  192. package/dist/language/flows/verify-change-tools.js +19 -2
  193. package/dist/launch-chromium.d.ts +80 -0
  194. package/dist/launch-chromium.js +218 -0
  195. package/dist/memory/cloud/cloud-config.d.ts +11 -2
  196. package/dist/memory/cloud/cloud-config.js +47 -7
  197. package/dist/memory/cloud/cloud-sync.d.ts +4 -3
  198. package/dist/memory/cloud/cloud-sync.js +7 -10
  199. package/dist/memory/cloud/harness-config.d.ts +2 -0
  200. package/dist/memory/cloud/harness-offer.js +5 -6
  201. package/dist/memory/cloud/harness-switch.js +5 -6
  202. package/dist/memory/cloud/sync-cycle.d.ts +78 -1
  203. package/dist/memory/cloud/sync-cycle.js +321 -53
  204. package/dist/memory/cloud/sync-daemon.d.ts +13 -0
  205. package/dist/memory/cloud/sync-daemon.js +45 -20
  206. package/dist/memory/cloud/sync-disk.js +27 -8
  207. package/dist/memory/impact/impact-store.js +6 -0
  208. package/dist/memory/intent/inline-intent.js +8 -3
  209. package/dist/memory/intent/intent-shard-store.d.ts +33 -47
  210. package/dist/memory/intent/intent-shard-store.js +252 -140
  211. package/dist/memory/intent/intent-shard.d.ts +0 -23
  212. package/dist/memory/intent/intent-shard.js +1 -11
  213. package/dist/memory/intent/intent-store.d.ts +13 -8
  214. package/dist/memory/intent/intent-store.js +46 -90
  215. package/dist/memory/intent/intent-tools.js +34 -23
  216. package/dist/memory/journal/ambient-store.js +2 -3
  217. package/dist/memory/journal/deviation-report.js +4 -1
  218. package/dist/memory/journal/deviation-service.js +10 -2
  219. package/dist/memory/journal/drive-flow.d.ts +19 -14
  220. package/dist/memory/journal/drive-flow.js +65 -48
  221. package/dist/memory/journal/envelope-store.js +2 -3
  222. package/dist/memory/journal/journal-recorder.d.ts +21 -0
  223. package/dist/memory/journal/on-disk/retain-policy.d.ts +34 -0
  224. package/dist/memory/journal/on-disk/retain-policy.js +44 -0
  225. package/dist/memory/journal/on-disk/retention.d.ts +14 -10
  226. package/dist/memory/journal/on-disk/retention.js +13 -12
  227. package/dist/memory/journal/on-disk/startup-maintenance.d.ts +42 -0
  228. package/dist/memory/journal/on-disk/startup-maintenance.js +14 -0
  229. package/dist/memory/journal/on-disk/workspace-gitignore.d.ts +0 -11
  230. package/dist/memory/journal/on-disk/workspace-gitignore.js +6 -45
  231. package/dist/memory/journal/on-disk/workspace-tiers.d.ts +52 -0
  232. package/dist/memory/journal/on-disk/workspace-tiers.js +90 -0
  233. package/dist/memory/journal/route-template.d.ts +22 -0
  234. package/dist/memory/journal/route-template.js +44 -0
  235. package/dist/memory/journal/session-end.d.ts +13 -2
  236. package/dist/memory/journal/session-end.js +88 -18
  237. package/dist/memory/journal/session-journal.d.ts +8 -1
  238. package/dist/memory/journal/session-journal.js +10 -0
  239. package/dist/memory/project/baselines.d.ts +1 -1
  240. package/dist/memory/project/dir/reticle-dir.d.ts +3 -1
  241. package/dist/memory/project/dir/reticle-dir.js +1 -0
  242. package/dist/memory/project/fs/fs-port.js +44 -5
  243. package/dist/memory/project/fs/write-atomic.d.ts +26 -0
  244. package/dist/memory/project/fs/write-atomic.js +48 -0
  245. package/dist/memory/project/project-tools.js +3 -1
  246. package/dist/memory/recall/prior/connection-memory.d.ts +6 -0
  247. package/dist/memory/recall/prior/connection-memory.js +8 -0
  248. package/dist/memory/recall/registered-projects.d.ts +2 -1
  249. package/dist/portal/bridge/auth-failure-reason.d.ts +1 -1
  250. package/dist/portal/bridge/auth-failure-reason.js +9 -4
  251. package/dist/portal/bridge/bridge.d.ts +8 -0
  252. package/dist/portal/bridge/bridge.js +43 -2
  253. package/dist/portal/input/js-coverage.d.ts +25 -0
  254. package/dist/portal/input/js-coverage.js +30 -0
  255. package/dist/portal/input/real-input.d.ts +11 -0
  256. package/dist/portal/input/real-input.js +31 -5
  257. package/dist/portal/pool/browser-pool.d.ts +10 -0
  258. package/dist/portal/pool/browser-pool.js +10 -0
  259. package/dist/portal/pool/playwright-launcher.d.ts +6 -0
  260. package/dist/portal/pool/playwright-launcher.js +21 -13
  261. package/dist/portal/pool/pool-contract.d.ts +2 -0
  262. package/dist/portal/pool/storage-seed.d.ts +2 -1
  263. package/dist/portal/pool/zero-install.d.ts +27 -0
  264. package/dist/portal/pool/zero-install.js +54 -0
  265. package/dist/portal/realm/web-realm.js +6 -0
  266. package/dist/portal/session/dev-server/dev-command.d.ts +2 -2
  267. package/dist/portal/session/dev-server/dev-command.js +30 -12
  268. package/dist/portal/session/dev-server/dev-server-probe.d.ts +2 -1
  269. package/dist/portal/session/dev-server/dev-server-probe.js +17 -1
  270. package/dist/portal/session/dev-server/route-status-probe.d.ts +24 -0
  271. package/dist/portal/session/dev-server/route-status-probe.js +57 -0
  272. package/dist/portal/session/dev-server/served-document.d.ts +21 -0
  273. package/dist/portal/session/dev-server/served-document.js +57 -0
  274. package/dist/portal/session/facts/background-traffic.d.ts +9 -0
  275. package/dist/portal/session/facts/background-traffic.js +25 -0
  276. package/dist/portal/session/facts/observed-state.d.ts +11 -0
  277. package/dist/portal/session/facts/observed-state.js +34 -0
  278. package/dist/portal/session/live-control-tools.js +48 -3
  279. package/dist/portal/session/no-session-diagnosis.d.ts +29 -0
  280. package/dist/portal/session/no-session-diagnosis.js +62 -5
  281. package/dist/portal/session/no-session-next-action.d.ts +13 -0
  282. package/dist/portal/session/no-session-next-action.js +48 -9
  283. package/dist/portal/session/no-session-watch.d.ts +18 -0
  284. package/dist/portal/session/no-session-watch.js +88 -2
  285. package/dist/portal/session/presence/attachment-history.d.ts +13 -2
  286. package/dist/portal/session/session-health.d.ts +61 -0
  287. package/dist/portal/session/session-health.js +43 -3
  288. package/dist/portal/session/session-info.d.ts +6 -0
  289. package/dist/portal/session/session-manager.d.ts +29 -6
  290. package/dist/portal/session/session-manager.js +42 -5
  291. package/dist/portal/session/session-reaper.d.ts +28 -0
  292. package/dist/portal/session/session-reaper.js +49 -1
  293. package/dist/portal/session/session-successor.d.ts +11 -0
  294. package/dist/portal/session/session-successor.js +14 -1
  295. package/dist/portal/session/session-verdict-facts.d.ts +28 -0
  296. package/dist/portal/session/session-verdict-facts.js +15 -0
  297. package/dist/portal/session/session.d.ts +10 -0
  298. package/dist/portal/session/session.js +34 -26
  299. package/dist/surface/http-server.d.ts +2 -1
  300. package/dist/surface/http-server.js +4 -2
  301. package/dist/surface/mcp/mcp-proxy.d.ts +2 -2
  302. package/dist/surface/mcp/mcp-proxy.js +5 -5
  303. package/dist/surface/mcp/mcp.js +4 -2
  304. package/dist/surface/mcp/proxy/proxy-daemon-probe.d.ts +13 -1
  305. package/dist/surface/mcp/proxy/proxy-daemon-probe.js +27 -2
  306. package/dist/surface/mcp/proxy-idle-exit.d.ts +2 -1
  307. package/dist/surface/mcp/server-instructions.js +21 -8
  308. package/dist/surface/tools/act/after-match.d.ts +17 -0
  309. package/dist/surface/tools/act/after-match.js +30 -0
  310. package/dist/surface/tools/act/already-true.d.ts +40 -0
  311. package/dist/surface/tools/act/already-true.js +36 -0
  312. package/dist/surface/tools/act/durable.d.ts +69 -0
  313. package/dist/surface/tools/act/durable.js +139 -0
  314. package/dist/surface/tools/act/navigation/navigate-arrival.d.ts +19 -6
  315. package/dist/surface/tools/act/navigation/navigate-arrival.js +60 -10
  316. package/dist/surface/tools/act/navigation/navigate-result.d.ts +1 -12
  317. package/dist/surface/tools/act/navigation/navigate-result.js +21 -3
  318. package/dist/surface/tools/act/proved-controls.d.ts +9 -0
  319. package/dist/surface/tools/act/proved-controls.js +11 -0
  320. package/dist/surface/tools/act/sequence-grade.d.ts +5 -0
  321. package/dist/surface/tools/act/sequence-grade.js +32 -1
  322. package/dist/surface/tools/act/settle-in-flight.d.ts +6 -2
  323. package/dist/surface/tools/act/settle-in-flight.js +29 -6
  324. package/dist/surface/tools/act-capsule.d.ts +7 -0
  325. package/dist/surface/tools/act-capsule.js +68 -8
  326. package/dist/surface/tools/act-sequence-tool.js +25 -8
  327. package/dist/surface/tools/act-tools.js +51 -74
  328. package/dist/surface/tools/args/numeric-bounds.d.ts +15 -2
  329. package/dist/surface/tools/args/numeric-bounds.js +30 -3
  330. package/dist/surface/tools/assert/assert-grade.js +26 -3
  331. package/dist/surface/tools/assert/assert-verdict.js +32 -6
  332. package/dist/surface/tools/browser-tools.js +17 -4
  333. package/dist/surface/tools/coverage-tools.js +27 -1
  334. package/dist/surface/tools/dynamic-tools.js +6 -0
  335. package/dist/surface/tools/error-recovery.js +4 -4
  336. package/dist/surface/tools/feedback-invite.js +6 -4
  337. package/dist/surface/tools/feedback-tools.d.ts +2 -1
  338. package/dist/surface/tools/feedback-tools.js +1 -1
  339. package/dist/surface/tools/harness-explore.d.ts +3 -2
  340. package/dist/surface/tools/harness-explore.js +7 -6
  341. package/dist/surface/tools/invoke-tool.js +17 -4
  342. package/dist/surface/tools/lease-hint.d.ts +8 -0
  343. package/dist/surface/tools/lease-hint.js +10 -0
  344. package/dist/surface/tools/lease-tools.d.ts +16 -28
  345. package/dist/surface/tools/lease-tools.js +68 -11
  346. package/dist/surface/tools/lineage-tools.js +1 -1
  347. package/dist/surface/tools/live-call-text.js +4 -1
  348. package/dist/surface/tools/observe-tools.js +23 -7
  349. package/dist/surface/tools/real-input-attempt.d.ts +0 -5
  350. package/dist/surface/tools/real-input-attempt.js +1 -1
  351. package/dist/surface/tools/tool-kit.d.ts +5 -0
  352. package/dist/surface/tools/tool-kit.js +6 -1
  353. package/dist/surface/tools/tool-surface.js +17 -5
  354. package/dist/surface/tools/tools.js +68 -29
  355. package/dist/telemetry/argument-shape.d.ts +0 -2
  356. package/dist/telemetry/argument-shape.js +0 -4
  357. package/dist/telemetry/error-class.d.ts +2 -3
  358. package/dist/telemetry/error-class.js +2 -3
  359. package/dist/telemetry/feedback-local.d.ts +1 -0
  360. package/dist/telemetry/feedback-local.js +1 -3
  361. package/dist/telemetry/feedback.d.ts +1 -1
  362. package/dist/telemetry/harness-drive.d.ts +0 -2
  363. package/dist/telemetry/harness-drive.js +0 -4
  364. package/dist/telemetry/hud-metrics.d.ts +32 -0
  365. package/dist/telemetry/hud-metrics.js +95 -0
  366. package/dist/telemetry/session-metrics.d.ts +5 -0
  367. package/dist/telemetry/session-metrics.js +15 -1
  368. package/dist/wire-journal.d.ts +34 -0
  369. package/dist/wire-journal.js +104 -0
  370. package/dist/workspace-packages.d.ts +0 -2
  371. package/dist/workspace-packages.js +1 -9
  372. package/docs/README.md +5 -2
  373. package/docs/actions.mdx +3 -3
  374. package/docs/agent-cheatsheet.md +4 -4
  375. package/docs/architecture.md +6 -6
  376. package/docs/best-practices.mdx +1 -1
  377. package/docs/capabilities.mdx +32 -1
  378. package/docs/cli/cloud.mdx +21 -9
  379. package/docs/cli/doctor.mdx +4 -0
  380. package/docs/cli/feedback.mdx +2 -2
  381. package/docs/cli/gate.mdx +9 -4
  382. package/docs/cli/init.mdx +1 -0
  383. package/docs/cli/report.mdx +44 -0
  384. package/docs/cli/update.mdx +10 -0
  385. package/docs/cli.mdx +9 -0
  386. package/docs/desktop.mdx +14 -10
  387. package/docs/environment.mdx +87 -0
  388. package/docs/faq.mdx +18 -16
  389. package/docs/first-drive.md +5 -13
  390. package/docs/flows.md +32 -2
  391. package/docs/frameworks.mdx +140 -9
  392. package/docs/getting-started.md +3 -3
  393. package/docs/http-transport.md +6 -3
  394. package/docs/human-control.md +2 -2
  395. package/docs/index.mdx +16 -6
  396. package/docs/install-agentic.mdx +37 -1
  397. package/docs/install-manual.mdx +1 -1
  398. package/docs/integration-patterns.md +3 -3
  399. package/docs/multi-agent-testing.md +1 -1
  400. package/docs/packages/babel-plugin.mdx +1 -1
  401. package/docs/packages/browser.mdx +2 -2
  402. package/docs/packages/react.mdx +2 -2
  403. package/docs/packages/tauri.mdx +1 -1
  404. package/docs/packages/vite-plugin.mdx +2 -2
  405. package/docs/packages.mdx +9 -9
  406. package/docs/predicates.mdx +176 -7
  407. package/docs/quickstart.mdx +2 -2
  408. package/docs/recipes.mdx +2 -2
  409. package/docs/state-management.mdx +2 -2
  410. package/docs/system-map.md +1 -1
  411. package/docs/telemetry.md +1 -1
  412. package/docs/token-efficiency.md +3 -3
  413. package/docs/tools/act-and-wait.mdx +1 -0
  414. package/docs/tools/act-sequence.mdx +1 -1
  415. package/docs/tools/console.mdx +1 -1
  416. package/docs/tools/inspect.mdx +1 -1
  417. package/docs/tools/network.mdx +1 -1
  418. package/docs/tools/query.mdx +11 -11
  419. package/docs/tools/sessions.mdx +1 -1
  420. package/docs/tools/snapshot.mdx +1 -1
  421. package/docs/tools/state.mdx +1 -1
  422. package/docs/tools/wait-for.mdx +1 -1
  423. package/docs/usage.md +8 -7
  424. package/docs/use-cases.mdx +157 -0
  425. package/docs/vs-screenshots.mdx +3 -3
  426. package/docs/what-is-recorded.md +62 -0
  427. package/docs/what-is-reticle.mdx +17 -20
  428. package/docs/why-reticle.mdx +14 -21
  429. package/package.json +11 -10
  430. package/dist/judgement/capsule/minimize.d.ts +0 -12
  431. package/dist/judgement/capsule/minimize.js +0 -22
  432. package/dist/judgement/outcome/predicate-to-expect.d.ts +0 -44
  433. package/dist/judgement/outcome/predicate-to-expect.js +0 -148
  434. package/dist/judgement/phenomena/phenomena.d.ts +0 -26
  435. package/dist/judgement/phenomena/phenomena.js +0 -101
  436. package/dist/language/flows/flow-report.d.ts +0 -22
  437. package/dist/language/flows/flow-report.js +0 -106
@@ -37,10 +37,28 @@ const LoginSchema = z.object({
37
37
  * code out of a server log they may not even be tailing.
38
38
  */
39
39
  const RequestCodeSchema = z.object({ devCode: z.string().optional() });
40
+ const MeSchema = z.object({ email: z.string().min(1).nullable().optional() });
41
+ /** The signed-in address, asked of the cloud. Best-effort: a login never fails for want of it. */
42
+ const signedInEmail = async (url, token) => {
43
+ try {
44
+ return MeSchema.parse(await api('GET', `${url}/v1/me`, token)).email ?? undefined;
45
+ }
46
+ catch {
47
+ return undefined;
48
+ }
49
+ };
40
50
  /** Persist a session token under ~/.reticle and print the next step. Shared by both login paths. */
41
- const writeSession = async (url, token, orgName, orgId, project, link) => {
51
+ const writeSession = async (url, token, orgName, orgId, project, link, email) => {
42
52
  await mkdir(join(home(), SESSIONS_DIR), { recursive: true });
43
- const body = `${JSON.stringify({ url: normalizeUrl(url), token, orgName, ...(orgId === undefined ? {} : { orgId }) }, null, 2)}\n`;
53
+ // Who signed in, so a flow saved on this machine can say which person made it (FlowFile.author).
54
+ const who = email ?? (await signedInEmail(url, token));
55
+ const body = `${JSON.stringify({
56
+ url: normalizeUrl(url),
57
+ token,
58
+ orgName,
59
+ ...(orgId === undefined ? {} : { orgId }),
60
+ ...(who === undefined ? {} : { email: who }),
61
+ }, null, 2)}\n`;
44
62
  // Both, on purpose. The per-host file is what lets another environment stay logged in; the active
45
63
  // file is what a bare command with no override resolves through, and keeping it means nothing
46
64
  // about the single-environment workflow changes.
@@ -159,7 +177,7 @@ export const cmdLogin = async (argv, link) => {
159
177
  code = requested.devCode;
160
178
  }
161
179
  const parsed = LoginSchema.parse(await api('POST', `${url}/v1/auth/login`, null, { email, code }));
162
- await writeSession(url, parsed.token, parsed.org.name, parsed.org.id, f['project'], link);
180
+ await writeSession(url, parsed.token, parsed.org.name, parsed.org.id, f['project'], link, email);
163
181
  return 0;
164
182
  };
165
183
  /** `reticle logout` — forget the cached session token (per-project keys under credentials.json stay). */
@@ -67,16 +67,19 @@ const HTML_CONTENT_TYPE = 'text/html; charset=utf-8';
67
67
  function bareSpecifiersIn(dir) {
68
68
  const found = new Set();
69
69
  const walk = (at) => {
70
- for (const name of readdirSync(at)) {
70
+ for (const entry of readdirSync(at, { withFileTypes: true })) {
71
+ const name = entry.name;
71
72
  const path = join(at, name);
72
- if (statSync(path).isDirectory()) {
73
+ if (entry.isDirectory()) {
73
74
  if ('node_modules' !== name)
74
75
  walk(path);
75
76
  continue;
76
77
  }
77
78
  if (!name.endsWith('.js') || name.endsWith('.test.js'))
78
79
  continue;
79
- for (const match of readFileSync(path, 'utf8').matchAll(/from\s*'([^.'][^']*)'/g)) {
80
+ // `from` as a keyword, not the tail of a string: core's `z.discriminatedUnion('from', [...])`
81
+ // read as an import of everything up to the next quote. A specifier never holds whitespace.
82
+ for (const match of readFileSync(path, 'utf8').matchAll(/(?<![\w$.'"])from\s*'([^.'\s][^'\s]*)'/g)) {
80
83
  const spec = match[1];
81
84
  if (spec !== undefined && !NOT_SERVABLE.test(spec))
82
85
  found.add(spec);
@@ -43,6 +43,11 @@ export interface ChromiumProbe {
43
43
  * and kept reading "not installed", with no way to see that neither was ever searched.
44
44
  */
45
45
  searchedPaths?: readonly string[] | undefined;
46
+ /**
47
+ * The installed browser Reticle drives instead when its own build is absent (`Google Chrome`,
48
+ * `Microsoft Edge`) — see launch-chromium. Present only when `exists` is false and one was found.
49
+ */
50
+ fallback?: string | undefined;
46
51
  }
47
52
  /** The install command, pinned when we know what to pin to. */
48
53
  export declare function chromiumInstallCommand(playwrightVersion?: string): string;
@@ -170,6 +170,11 @@ export function chromiumHint(probe) {
170
170
  : `✓ installed (${probe.wantedRevision})`;
171
171
  }
172
172
  const command = chromiumInstallCommand(probe.playwrightVersion);
173
+ // Not a failure: every Reticle browser launches on this one. The install command stays on the
174
+ // line for a reader who wants the exact build the tests run against.
175
+ if (probe.fallback !== undefined && probe.executablePath !== undefined) {
176
+ return `✓ using the installed ${probe.fallback} (Playwright's own Chromium is not installed; ${command} adds it)`;
177
+ }
173
178
  if (probe.executablePath === undefined) {
174
179
  // Same rule as the missing-browser line below: a verdict with nowhere to check it against reads
175
180
  // as a broken check. The roots say whether this process even searched the directory the reader
@@ -1,5 +1,5 @@
1
1
  /** What this reads off `/status`. Everything is optional — an older daemon may send none of it. */
2
- export interface SessionFacts {
2
+ interface SessionFacts {
3
3
  sessionCount?: unknown;
4
4
  why?: unknown;
5
5
  }
@@ -10,3 +10,4 @@ export interface SessionsLine {
10
10
  why?: string;
11
11
  }
12
12
  export declare function sessionsLine(facts: SessionFacts): SessionsLine;
13
+ export {};
@@ -3,34 +3,7 @@
3
3
  * "show me the app" flow (`reticle open`). Split out of cli.ts so that file stays under the size cap.
4
4
  * The decision logic is pure (unit-tested); the IO (fetch, OS browser launch) is injected/isolated.
5
5
  */
6
- /** One connected tab as `reticle status` reports it — the at-a-glance health line. */
7
- interface StatusSession {
8
- sessionId: string;
9
- url: string;
10
- throttled: boolean;
11
- /**
12
- * The tab is backgrounded. Reported by the daemon on every session; `false` on one too old to say,
13
- * which reads as visible — the conservative answer, since it keeps the reuse this command has
14
- * always done rather than opening a tab on a guess.
15
- */
16
- hidden: boolean;
17
- stale: boolean;
18
- pendingMarks: number;
19
- /** The tab is attached and answering nothing — see Session.unresponsive. Absent on an older daemon. */
20
- unresponsive?: true;
21
- }
22
- /**
23
- * Reduce the daemon's /status JSON to the compact view `reticle status` prints. Pure: narrows the
24
- * untrusted wire payload (never `any`) and tolerates a missing/partial body so a malformed response
25
- * degrades to "running, 0 sessions" instead of throwing.
26
- */
27
- export declare function summarizeStatus(payload: unknown): {
28
- sessionCount: number;
29
- sessions: StatusSession[];
30
- why?: string;
31
- /** The same diagnosis without the differential, for a surface a person reads. */
32
- whyLead?: string;
33
- };
6
+ export { summarizeStatus } from '../../daemon/binding/daemon-status-probe.js';
34
7
  /**
35
8
  * Warn when the daemon already on this port is a different version than this CLI, and attach anyway.
36
9
  *
@@ -39,6 +12,13 @@ export declare function summarizeStatus(payload: unknown): {
39
12
  * another agent's session on a version bump, which is worse than a loud line.
40
13
  */
41
14
  export declare function warnOnDaemonSkew(port: number): Promise<void>;
15
+ /**
16
+ * The skew sentence for a daemon's `/status` body, or undefined when it speaks our contract.
17
+ *
18
+ * Shared with `init`, which must not ADOPT a skewed daemon: an older one refuses every hello from
19
+ * the page init just instrumented.
20
+ */
21
+ export declare function daemonSkew(status: unknown): string | undefined;
42
22
  /** What `reticle open` should do: reuse an already-connected tab, open a new one, or ask for a url. */
43
23
  type OpenDecision = {
44
24
  action: 'reuse';
@@ -75,6 +55,7 @@ export declare function decideOpen(all: {
75
55
  url: string;
76
56
  unresponsive?: boolean;
77
57
  hidden?: boolean;
58
+ lastSeenMs?: number;
78
59
  }[], url: string | undefined): OpenDecision;
79
60
  /** The OS command that opens a URL in the default browser, per platform. Pure — unit-tested. */
80
61
  export declare function openCommand(url: string, platform: NodeJS.Platform): {
@@ -97,4 +78,3 @@ export declare function openInBrowser(url: string, platform?: NodeJS.Platform, r
97
78
  * by spawning a real launcher on a developer machine.
98
79
  */
99
80
  export declare function launcherFailure(code: number | null, signal: NodeJS.Signals | null): string | null;
100
- export {};
@@ -1,53 +1,19 @@
1
1
  import { NodePlatform } from '../../../machine/platform.js';
2
2
  import { spawn } from 'node:child_process';
3
- import { isOpaqueOrigin } from '@reticlehq/core';
3
+ import { isOpaqueOrigin, SESSION_HEALTH } from '@reticlehq/core';
4
4
  import { daemonFix, describeSkew } from '../../version/version-skew.js';
5
5
  import { CONTRACT_FINGERPRINT } from '@reticlehq/core';
6
6
  import { SERVER_VERSION } from '../../version/identity/server-version.js';
7
7
  import { log } from '../../../log.js';
8
8
  import { fetchStatus } from '../../daemon/binding/daemon-status-probe.js';
9
9
  /**
10
- * Reduce the daemon's /status JSON to the compact view `reticle status` prints. Pure: narrows the
11
- * untrusted wire payload (never `any`) and tolerates a missing/partial body so a malformed response
12
- * degrades to "running, 0 sessions" instead of throwing.
10
+ * CLI launch + status helpers — the daemon-introspection (`reticle status`) and the one-command
11
+ * "show me the app" flow (`reticle open`). Split out of cli.ts so that file stays under the size cap.
12
+ * The decision logic is pure (unit-tested); the IO (fetch, OS browser launch) is injected/isolated.
13
13
  */
14
- export function summarizeStatus(payload) {
15
- if (typeof payload !== 'object' || null === payload)
16
- return { sessionCount: 0, sessions: [] };
17
- const obj = payload;
18
- // Carried through to the printed line: with no sessions this is the whole answer, and dropping it
19
- // here would silently undo the reason it is on the wire.
20
- const why = 'string' === typeof obj['why'] ? obj['why'] : undefined;
21
- // Absent on a daemon older than this field, which is why every reader falls back to `why`.
22
- const whyLead = 'string' === typeof obj['whyLead'] ? obj['whyLead'] : undefined;
23
- const raw = Array.isArray(obj['sessions']) ? obj['sessions'] : [];
24
- const sessions = raw
25
- .map((s) => {
26
- if (typeof s !== 'object' || null === s)
27
- return null;
28
- const r = s;
29
- const sessionId = 'string' === typeof r['sessionId'] ? r['sessionId'] : '';
30
- if ('' === sessionId)
31
- return null;
32
- return {
33
- sessionId,
34
- url: 'string' === typeof r['url'] ? r['url'] : '',
35
- throttled: true === r['throttled'],
36
- hidden: true === r['hidden'],
37
- stale: true === r['stale'],
38
- pendingMarks: 'number' === typeof r['pendingMarks'] ? r['pendingMarks'] : 0,
39
- ...(true === r['unresponsive'] ? { unresponsive: true } : {}),
40
- };
41
- })
42
- .filter((s) => s !== null);
43
- const sessionCount = 'number' === typeof obj['sessionCount'] ? obj['sessionCount'] : sessions.length;
44
- return {
45
- sessionCount,
46
- sessions,
47
- ...(why === undefined ? {} : { why }),
48
- ...(whyLead === undefined ? {} : { whyLead }),
49
- };
50
- }
14
+ // Parsing the daemon's /status payload lives beside the probe that fetches it; re-exported here for
15
+ // the callers that already import it from this module.
16
+ export { summarizeStatus } from '../../daemon/binding/daemon-status-probe.js';
51
17
  /** A string field off the /status body, or undefined on a daemon too old to report it. */
52
18
  function statusField(payload, key) {
53
19
  if (typeof payload !== 'object' || null === payload)
@@ -63,17 +29,25 @@ function statusField(payload, key) {
63
29
  * another agent's session on a version bump, which is worse than a loud line.
64
30
  */
65
31
  export async function warnOnDaemonSkew(port) {
66
- const status = await fetchStatus(port);
32
+ const skew = daemonSkew(await fetchStatus(port));
33
+ if (skew !== undefined)
34
+ log('reticle_daemon_skew', { port, warning: skew });
35
+ }
36
+ /**
37
+ * The skew sentence for a daemon's `/status` body, or undefined when it speaks our contract.
38
+ *
39
+ * Shared with `init`, which must not ADOPT a skewed daemon: an older one refuses every hello from
40
+ * the page init just instrumented.
41
+ */
42
+ export function daemonSkew(status) {
67
43
  const daemonVersion = statusField(status, 'version');
68
- const skew = describeSkew({
44
+ return describeSkew({
69
45
  what: 'the daemon already running on this port',
70
46
  version: daemonVersion,
71
47
  contract: statusField(status, 'contract'),
72
48
  // The daemon is the peer here and this process is the agent side.
73
49
  fix: daemonFix(daemonVersion, SERVER_VERSION),
74
50
  }, { version: SERVER_VERSION, contract: CONTRACT_FINGERPRINT });
75
- if (skew !== undefined)
76
- log('reticle_daemon_skew', { port, warning: skew });
77
51
  }
78
52
  function sameOrigin(a, b) {
79
53
  try {
@@ -116,10 +90,14 @@ export function decideOpen(all, url) {
116
90
  // three checks that could never resolve. Opening the url instead costs one foreground tab and the
117
91
  // hidden one is left exactly where it was — nothing is navigated out from under anybody.
118
92
  //
119
- // HIDDEN, not `throttled`: throttled is `hidden || stale`, and a stale-but-visible tab is usually
120
- // a quiet page that drives fine. Excluding those would pile up a duplicate tab per run, which is
121
- // the thing the origin match below exists to prevent.
122
- const sessions = all.filter((s) => true !== s.unresponsive && true !== s.hidden);
93
+ // And a tab the SDK has gone SILENT in. The page heartbeats on a native timer every few seconds
94
+ // whatever it is doing, so a quiet page is not a silent one: past the daemon's own staleness
95
+ // threshold the page is frozen (a discarded tab, a sleeping machine, a wedged main thread). This
96
+ // looked only at the url, and `verify --expect` drove a tab last heard from 105s earlier into
97
+ // "command 'match' timed out after 8000ms" instead of opening the url it had been given.
98
+ const sessions = all.filter((s) => true !== s.unresponsive &&
99
+ true !== s.hidden &&
100
+ (s.lastSeenMs ?? 0) <= SESSION_HEALTH.STALE_THRESHOLD_MS);
123
101
  if (url === undefined) {
124
102
  const first = sessions[0];
125
103
  if (first !== undefined)
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Run the CLI whose major matches the SDK the project actually installed.
3
+ *
4
+ * The agent's MCP entry and every rule file `init` writes say `npx @reticlehq/server`, deliberately
5
+ * unpinned (see `npxServerArgs` in init), which resolves the LATEST server — while the project's SDK
6
+ * stays locked at whatever `init` installed. A project wired on the previous major therefore got a
7
+ * daemon from the next one, and every verdict came back `version_skew`; a global install from an
8
+ * older release shadows npx the same way in the other direction. Nothing the project could do fixed
9
+ * it short of upgrading, and the report named the skew without resolving it.
10
+ *
11
+ * So the one entry every command passes through asks, before doing anything: is the SDK in this
12
+ * project on my major? If not, it hands the same arguments to `npx @reticlehq/server@<that version>`
13
+ * and steps aside. Only across majors — a minor apart verifies fine, and re-running for it would
14
+ * cost an npx resolve on every command for nothing.
15
+ *
16
+ * Deliberately free of `@/` and relative imports: the stdio test runs this file in a bare Node child
17
+ * to see the real file descriptors, and that child has neither the alias nor the build.
18
+ */
19
+ import { type ChildProcess } from 'node:child_process';
20
+ /** The packages an instrumented project installs; they release in lockstep, so any one will do. */
21
+ export declare const SDK_PACKAGES: readonly ["@reticlehq/react", "@reticlehq/browser", "@reticlehq/next", "@reticlehq/vite-plugin"];
22
+ export declare const VersionMatchEnv: {
23
+ /** Set to anything to keep the CLI you ran, whatever the project installed. */
24
+ readonly OPT_OUT: "RETICLE_NO_VERSION_MATCH";
25
+ /** Set on the re-run child, so it can never re-run itself again. */
26
+ readonly REEXECUTED: "RETICLE_VERSION_MATCHED";
27
+ /** What npm puts here for `npx <spec>` — how a version typed on the npx line is recognised. */
28
+ readonly NPX_PACKAGE: "npm_config_package";
29
+ };
30
+ /**
31
+ * The SDK version installed for `projectDir`, found the way Node resolves it: the nearest
32
+ * `node_modules` at or above the directory, so a package hoisted to a workspace root counts.
33
+ */
34
+ export declare function installedSdkVersion(projectDir: string, readFile: (path: string) => string | undefined): string | undefined;
35
+ /** `readFile` for the real filesystem: the text, or undefined when there is nothing readable. */
36
+ export declare function readTextFile(path: string): string | undefined;
37
+ /** The one line a person sees on stderr when the CLI hands over — stdout belongs to the protocol. */
38
+ export declare function versionMatchNote(sdkVersion: string, cliVersion: string): string;
39
+ export interface VersionMatchInput {
40
+ argv: readonly string[];
41
+ cliVersion: string;
42
+ env: Readonly<Record<string, string | undefined>>;
43
+ projectDir: string;
44
+ readFile: (path: string) => string | undefined;
45
+ }
46
+ /**
47
+ * The version to re-run at, or undefined to carry on in this process. Pure: the filesystem and the
48
+ * environment are handed in.
49
+ */
50
+ export declare function versionToMatch(input: VersionMatchInput): string | undefined;
51
+ /** How to launch `npx --yes @reticlehq/server@<version> <args>` on this platform. */
52
+ export declare function npxInvocation(version: string, args: readonly string[], on: string): {
53
+ file: string;
54
+ argv: string[];
55
+ shell: boolean;
56
+ };
57
+ /** The two things the re-run needs from its child: when it ends, and a way to pass a signal on. */
58
+ export interface ReexecChild {
59
+ /** Called once, with the exit code — or undefined when the child never started or was killed. */
60
+ onEnd(listener: (code: number | undefined, error: Error | undefined) => void): void;
61
+ kill(signal: NodeJS.Signals): void;
62
+ }
63
+ export type ReexecSpawner = (file: string, argv: string[], options: {
64
+ stdio: 'inherit';
65
+ env: NodeJS.ProcessEnv;
66
+ shell: boolean;
67
+ }) => ReexecChild;
68
+ /** A ChildProcess, seen through `ReexecChild`. */
69
+ export declare function asReexecChild(child: ChildProcess): ReexecChild;
70
+ /** The real spawner. */
71
+ export declare const nodeSpawner: ReexecSpawner;
72
+ /**
73
+ * Hand this invocation to the matching version and exit with its code.
74
+ *
75
+ * `stdio: 'inherit'` gives the child this process's own file descriptors, so an MCP client's JSON-RPC
76
+ * stream reaches it without passing through a byte of JavaScript here — nothing to buffer, reframe or
77
+ * lose. For that to hold, this must run before anything writes to stdout.
78
+ */
79
+ export declare function reexecAtVersion(version: string, args: readonly string[], env: NodeJS.ProcessEnv, deps: {
80
+ spawn: ReexecSpawner;
81
+ exit: (code: number) => void;
82
+ platform?: string;
83
+ warn?: (line: string) => void;
84
+ }): void;
@@ -0,0 +1,206 @@
1
+ /**
2
+ * Run the CLI whose major matches the SDK the project actually installed.
3
+ *
4
+ * The agent's MCP entry and every rule file `init` writes say `npx @reticlehq/server`, deliberately
5
+ * unpinned (see `npxServerArgs` in init), which resolves the LATEST server — while the project's SDK
6
+ * stays locked at whatever `init` installed. A project wired on the previous major therefore got a
7
+ * daemon from the next one, and every verdict came back `version_skew`; a global install from an
8
+ * older release shadows npx the same way in the other direction. Nothing the project could do fixed
9
+ * it short of upgrading, and the report named the skew without resolving it.
10
+ *
11
+ * So the one entry every command passes through asks, before doing anything: is the SDK in this
12
+ * project on my major? If not, it hands the same arguments to `npx @reticlehq/server@<that version>`
13
+ * and steps aside. Only across majors — a minor apart verifies fine, and re-running for it would
14
+ * cost an npx resolve on every command for nothing.
15
+ *
16
+ * Deliberately free of `@/` and relative imports: the stdio test runs this file in a bare Node child
17
+ * to see the real file descriptors, and that child has neither the alias nor the build.
18
+ */
19
+ import { spawn } from 'node:child_process';
20
+ import { readFileSync } from 'node:fs';
21
+ import { dirname, join } from 'node:path';
22
+ import { NodePlatform, windowsShellArg } from '@reticlehq/init';
23
+ /** The packages an instrumented project installs; they release in lockstep, so any one will do. */
24
+ export const SDK_PACKAGES = [
25
+ '@reticlehq/react',
26
+ '@reticlehq/browser',
27
+ '@reticlehq/next',
28
+ '@reticlehq/vite-plugin',
29
+ ];
30
+ /** The package that carries the `reticle` bin — what the re-run asks npx for. */
31
+ const SERVER_PACKAGE = '@reticlehq/server';
32
+ const NPX = 'npx';
33
+ const NPX_WINDOWS = 'npx.cmd';
34
+ const NPX_YES = '--yes';
35
+ const NODE_MODULES = 'node_modules';
36
+ const PACKAGE_JSON = 'package.json';
37
+ const LATEST_TAG = 'latest';
38
+ const MARKED = '1';
39
+ export const VersionMatchEnv = {
40
+ /** Set to anything to keep the CLI you ran, whatever the project installed. */
41
+ OPT_OUT: 'RETICLE_NO_VERSION_MATCH',
42
+ /** Set on the re-run child, so it can never re-run itself again. */
43
+ REEXECUTED: 'RETICLE_VERSION_MATCHED',
44
+ /** What npm puts here for `npx <spec>` — how a version typed on the npx line is recognised. */
45
+ NPX_PACKAGE: 'npm_config_package',
46
+ };
47
+ /**
48
+ * Commands whose job is to CHANGE the version, or to report on this binary. Re-running `init` or
49
+ * `update` at the project's old version would undo exactly what the person asked for; `version` and
50
+ * `help` describe the binary they ran. `_daemon` is spawned by a CLI that already decided.
51
+ */
52
+ const KEEP_COMMANDS = new Set([
53
+ 'init',
54
+ 'update',
55
+ 'rollback',
56
+ 'version',
57
+ 'help',
58
+ '_daemon',
59
+ ]);
60
+ const KEEP_FLAGS = new Set(['--version', '-v', '--help', '-h']);
61
+ const SETUP_COMMAND = 'setup';
62
+ const SETUP_INSTALL = 'install';
63
+ /** A published release — not `workspace:*`, a link, or a tag npx could not pin. */
64
+ const RELEASE_VERSION = /^(\d+)\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
65
+ function majorOf(version) {
66
+ return RELEASE_VERSION.exec(version)?.[1];
67
+ }
68
+ /**
69
+ * The SDK version installed for `projectDir`, found the way Node resolves it: the nearest
70
+ * `node_modules` at or above the directory, so a package hoisted to a workspace root counts.
71
+ */
72
+ export function installedSdkVersion(projectDir, readFile) {
73
+ let dir = projectDir;
74
+ for (;;) {
75
+ for (const pkg of SDK_PACKAGES) {
76
+ const raw = readFile(join(dir, NODE_MODULES, pkg, PACKAGE_JSON));
77
+ if (raw === undefined)
78
+ continue;
79
+ try {
80
+ const version = JSON.parse(raw).version;
81
+ if ('string' === typeof version)
82
+ return version;
83
+ }
84
+ catch {
85
+ // A manifest that will not parse is no evidence; try the next package.
86
+ }
87
+ }
88
+ const parent = dirname(dir);
89
+ if (parent === dir)
90
+ return undefined;
91
+ dir = parent;
92
+ }
93
+ }
94
+ /** `readFile` for the real filesystem: the text, or undefined when there is nothing readable. */
95
+ export function readTextFile(path) {
96
+ try {
97
+ return readFileSync(path, 'utf8');
98
+ }
99
+ catch {
100
+ return undefined;
101
+ }
102
+ }
103
+ /** The one line a person sees on stderr when the CLI hands over — stdout belongs to the protocol. */
104
+ export function versionMatchNote(sdkVersion, cliVersion) {
105
+ return (`This project's Reticle SDK is ${sdkVersion}; running ${SERVER_PACKAGE}@${sdkVersion} to match ` +
106
+ `(this CLI is ${cliVersion}, a different major). Set ${VersionMatchEnv.OPT_OUT}=1 to keep this one.`);
107
+ }
108
+ /** Did the person name a version on the npx line (`npx @reticlehq/server@<version> …`)? */
109
+ function pinnedOnNpxLine(spec) {
110
+ if (spec === undefined || !spec.startsWith(`${SERVER_PACKAGE}@`))
111
+ return false;
112
+ return spec.slice(SERVER_PACKAGE.length + 1) !== LATEST_TAG;
113
+ }
114
+ function keepsThisBinary(argv) {
115
+ const [command, sub] = argv;
116
+ if (command !== undefined && KEEP_COMMANDS.has(command))
117
+ return true;
118
+ if (SETUP_COMMAND === command && SETUP_INSTALL === sub)
119
+ return true;
120
+ return argv.some((arg) => KEEP_FLAGS.has(arg));
121
+ }
122
+ /**
123
+ * The version to re-run at, or undefined to carry on in this process. Pure: the filesystem and the
124
+ * environment are handed in.
125
+ */
126
+ export function versionToMatch(input) {
127
+ const { env } = input;
128
+ if (env[VersionMatchEnv.REEXECUTED] !== undefined)
129
+ return undefined;
130
+ if (env[VersionMatchEnv.OPT_OUT] !== undefined)
131
+ return undefined;
132
+ if (pinnedOnNpxLine(env[VersionMatchEnv.NPX_PACKAGE]))
133
+ return undefined;
134
+ if (keepsThisBinary(input.argv))
135
+ return undefined;
136
+ const sdk = installedSdkVersion(input.projectDir, input.readFile);
137
+ if (sdk === undefined)
138
+ return undefined;
139
+ const sdkMajor = majorOf(sdk);
140
+ if (sdkMajor === undefined)
141
+ return undefined;
142
+ return sdkMajor === majorOf(input.cliVersion) ? undefined : sdk;
143
+ }
144
+ /** How to launch `npx --yes @reticlehq/server@<version> <args>` on this platform. */
145
+ export function npxInvocation(version, args, on) {
146
+ const argv = [NPX_YES, `${SERVER_PACKAGE}@${version}`, ...args];
147
+ // A `.cmd` cannot be spawned without a shell since the CVE-2024-27980 fix, and a shell needs every
148
+ // argument quoted — the same rule `npmSpawn` in the updater follows.
149
+ if (NodePlatform.WINDOWS !== on)
150
+ return { file: NPX, argv, shell: false };
151
+ return { file: [NPX_WINDOWS, ...argv.map(windowsShellArg)].join(' '), argv: [], shell: true };
152
+ }
153
+ /** A ChildProcess, seen through `ReexecChild`. */
154
+ export function asReexecChild(child) {
155
+ return {
156
+ onEnd: (listener) => {
157
+ // Node can emit `error` AND `exit` for one child; the listener exits the process, so once.
158
+ let ended = false;
159
+ const end = (code, error) => {
160
+ if (ended)
161
+ return;
162
+ ended = true;
163
+ listener(code, error);
164
+ };
165
+ child.once('error', (error) => end(undefined, error));
166
+ child.once('exit', (code) => end(code ?? undefined, undefined));
167
+ },
168
+ kill: (signal) => {
169
+ child.kill(signal);
170
+ },
171
+ };
172
+ }
173
+ /** The real spawner. */
174
+ export const nodeSpawner = (file, argv, options) => asReexecChild(spawn(file, argv, options));
175
+ const FORWARDED_SIGNALS = ['SIGINT', 'SIGTERM', 'SIGHUP'];
176
+ const SPAWN_FAILED_EXIT = 1;
177
+ /**
178
+ * Hand this invocation to the matching version and exit with its code.
179
+ *
180
+ * `stdio: 'inherit'` gives the child this process's own file descriptors, so an MCP client's JSON-RPC
181
+ * stream reaches it without passing through a byte of JavaScript here — nothing to buffer, reframe or
182
+ * lose. For that to hold, this must run before anything writes to stdout.
183
+ */
184
+ export function reexecAtVersion(version, args, env, deps) {
185
+ const inv = npxInvocation(version, args, deps.platform ?? process.platform);
186
+ const child = deps.spawn(inv.file, inv.argv, {
187
+ stdio: 'inherit',
188
+ env: { ...env, [VersionMatchEnv.REEXECUTED]: MARKED },
189
+ shell: inv.shell,
190
+ });
191
+ // A client that stops the proxy signals THIS pid; the real server is the child.
192
+ const forward = (signal) => {
193
+ child.kill(signal);
194
+ };
195
+ for (const signal of FORWARDED_SIGNALS)
196
+ process.on(signal, forward);
197
+ child.onEnd((code, error) => {
198
+ for (const signal of FORWARDED_SIGNALS)
199
+ process.off(signal, forward);
200
+ if (error !== undefined) {
201
+ deps.warn?.(`Could not start ${SERVER_PACKAGE}@${version} to match this project's SDK (${error.message}). ` +
202
+ `Set ${VersionMatchEnv.OPT_OUT}=1 to use the installed CLI instead.`);
203
+ }
204
+ deps.exit(code ?? SPAWN_FAILED_EXIT);
205
+ });
206
+ }
@@ -17,7 +17,7 @@
17
17
  import { log } from '../../../log.js';
18
18
  import { RETICLE_VERIFY_DEFAULT_PORT } from '../../../index.js';
19
19
  import { verifyEndpointMismatch } from '../../../status-payload.js';
20
- import { probeDaemon } from '../../../surface/mcp/mcp-proxy.js';
20
+ import { probeDaemon, waitForDaemonBind } from '../../../surface/mcp/mcp-proxy.js';
21
21
  import { stateDirProblem } from '../../daemon/state-dir.js';
22
22
  import { readPid, isAlive, removePid, spawnDaemon, logPath, reticleStateHome, } from '../../daemon/daemon.js';
23
23
  import { readDaemonStartupCause } from '../../daemon/lifetime/startup-failure.js';
@@ -84,7 +84,7 @@ async function serveWithHonestExit(parsed) {
84
84
  // The child binds asynchronously and, when it cannot, exits 1 long after this process would have
85
85
  // reported success. Wait for it to ANSWER — `/status` responding is the only evidence a daemon
86
86
  // exists that does not come from the pid file the child may never have earned.
87
- const bound = await waitForPresence(parsed.port, PortPresence.DAEMON, SERVE_BIND_TIMEOUT_MS);
87
+ const bound = await waitForDaemonBind(parsed.port);
88
88
  if (!bound) {
89
89
  const settled = await probePresence(parsed.port, { tcpOpen: probeDaemon, status: fetchStatus });
90
90
  const startupCause = readDaemonStartupCause(logPath(parsed.port), startupStartedAt);
@@ -113,24 +113,10 @@ async function serveWithHonestExit(parsed) {
113
113
  }
114
114
  log('reticle_daemon_spawned', { port: parsed.port, ...(parsed.http ? { http: true } : {}) });
115
115
  }
116
- /** How long `serve` waits for the child to bind. Generous: a cold daemon start is seconds. */
117
- const SERVE_BIND_TIMEOUT_MS = 15_000;
118
- const PRESENCE_POLL_MS = 150;
119
116
  /** How long a daemon gets to shut down on its own before `stop` stops asking politely. */
120
117
  const GRACEFUL_STOP_MS = 5000;
121
118
  /** How long after SIGKILL before we accept the process is not ours to kill and say so. */
122
119
  const FORCED_STOP_MS = 2000;
123
- async function waitForPresence(port, want, timeoutMs) {
124
- const deadline = Date.now() + timeoutMs;
125
- for (;;) {
126
- const presence = await probePresence(port, { tcpOpen: probeDaemon, status: fetchStatus });
127
- if (presence === want)
128
- return true;
129
- if (Date.now() >= deadline)
130
- return false;
131
- await new Promise((resolve) => setTimeout(resolve, PRESENCE_POLL_MS));
132
- }
133
- }
134
120
  export function handleStop(port, quiet) {
135
121
  const pid = readPid(port);
136
122
  if (null === pid || !isAlive(pid)) {