@reticlehq/server 2.11.0 → 2.13.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 (513) hide show
  1. package/SKILL.md +93 -111
  2. package/dist/bridge/auth-failure-reason.d.ts +6 -2
  3. package/dist/bridge/auth-failure-reason.js +15 -8
  4. package/dist/bridge/bridge.d.ts +18 -14
  5. package/dist/bridge/bridge.js +248 -165
  6. package/dist/bridge/bridge.test-harness.d.ts +2 -0
  7. package/dist/bridge/bridge.test-harness.js +8 -1
  8. package/dist/bridge/pairing-token.d.ts +5 -0
  9. package/dist/bridge/pairing-token.js +28 -0
  10. package/dist/bridge/protocol-skew.d.ts +37 -0
  11. package/dist/bridge/protocol-skew.js +45 -0
  12. package/dist/capsule/capsule-store.d.ts +8 -7
  13. package/dist/capsule/capsule-store.js +1 -1
  14. package/dist/capsule/causal-summary.d.ts +2 -2
  15. package/dist/capsule/causal-summary.js +7 -2
  16. package/dist/chromium-launch-options.d.ts +28 -0
  17. package/dist/chromium-launch-options.js +29 -0
  18. package/dist/cli/cli-doctor.js +39 -1
  19. package/dist/cli/cli-flow-commands.d.ts +3 -1
  20. package/dist/cli/cli-flow-commands.js +34 -4
  21. package/dist/cli/cli-kill.d.ts +2 -1
  22. package/dist/cli/cli-launch.d.ts +11 -1
  23. package/dist/cli/cli-launch.js +59 -10
  24. package/dist/cli/cli-parse.d.ts +17 -1
  25. package/dist/cli/cli-parse.js +238 -12
  26. package/dist/cli/cli-update-commands.js +20 -1
  27. package/dist/cli/cli-verify.d.ts +2 -1
  28. package/dist/cli/cli-verify.js +5 -4
  29. package/dist/cli/cloud-cli.js +428 -253
  30. package/dist/cli/cloud-keystore.d.ts +37 -0
  31. package/dist/cli/cloud-keystore.js +97 -0
  32. package/dist/cli/cloud-kit.d.ts +37 -0
  33. package/dist/cli/cloud-kit.js +121 -0
  34. package/dist/cli/cloud-login.d.ts +17 -0
  35. package/dist/cli/cloud-login.js +183 -0
  36. package/dist/cli/cloud-session.d.ts +48 -0
  37. package/dist/cli/cloud-session.js +53 -0
  38. package/dist/cli/config-discovery.d.ts +2 -1
  39. package/dist/cli/config-discovery.js +1 -0
  40. package/dist/cli/daemon-start-options.d.ts +32 -0
  41. package/dist/cli/daemon-start-options.js +38 -0
  42. package/dist/cli/device-flow.d.ts +76 -0
  43. package/dist/cli/device-flow.js +56 -0
  44. package/dist/cli/doctor-rows.d.ts +12 -1
  45. package/dist/cli/doctor-rows.js +12 -1
  46. package/dist/cli/drive-attach.d.ts +13 -0
  47. package/dist/cli/drive-attach.js +18 -0
  48. package/dist/cli/drive-command.js +7 -3
  49. package/dist/cli/gate-exit.d.ts +21 -0
  50. package/dist/cli/gate-exit.js +21 -0
  51. package/dist/cli/gate-hook-message.d.ts +33 -0
  52. package/dist/cli/gate-hook-message.js +46 -0
  53. package/dist/cli/mcp-command.d.ts +25 -0
  54. package/dist/cli/mcp-command.js +174 -0
  55. package/dist/cli/project-name.d.ts +30 -0
  56. package/dist/cli/project-name.js +44 -0
  57. package/dist/cli/sibling-ports.d.ts +37 -0
  58. package/dist/cli/sibling-ports.js +56 -0
  59. package/dist/cli/status-next-action.d.ts +14 -0
  60. package/dist/cli/status-next-action.js +4 -4
  61. package/dist/cli.d.ts +1 -0
  62. package/dist/cli.js +167 -186
  63. package/dist/cloud/cloud-config.d.ts +24 -2
  64. package/dist/cloud/cloud-config.js +64 -5
  65. package/dist/cloud/cloud-sync.d.ts +16 -5
  66. package/dist/cloud/cloud-sync.js +64 -1
  67. package/dist/cloud/sync-cycle.d.ts +110 -0
  68. package/dist/cloud/sync-cycle.js +270 -0
  69. package/dist/cloud/sync-daemon.d.ts +69 -0
  70. package/dist/cloud/sync-daemon.js +260 -0
  71. package/dist/cloud/sync-disk.d.ts +9 -0
  72. package/dist/cloud/sync-disk.js +191 -0
  73. package/dist/cloud/sync-hash.d.ts +2 -0
  74. package/dist/cloud/sync-hash.js +35 -0
  75. package/dist/crawl/crawl-tools.js +5 -3
  76. package/dist/crawl/crawl.d.ts +12 -13
  77. package/dist/crawl/crawl.js +25 -3
  78. package/dist/daemon/daemon-resolve.d.ts +122 -0
  79. package/dist/daemon/daemon-resolve.js +187 -0
  80. package/dist/daemon/dev-servers.d.ts +15 -0
  81. package/dist/daemon/dev-servers.js +68 -0
  82. package/dist/daemon/free-port.d.ts +29 -0
  83. package/dist/daemon/free-port.js +55 -0
  84. package/dist/daemon/heartbeat.d.ts +0 -4
  85. package/dist/daemon/heartbeat.js +2 -2
  86. package/dist/daemon/wake-decision.d.ts +32 -0
  87. package/dist/daemon/wake-decision.js +38 -0
  88. package/dist/dev/stale-issue-guard.d.ts +0 -2
  89. package/dist/dev/stale-issue-guard.js +1 -1
  90. package/dist/domain/domain-tools.js +2 -2
  91. package/dist/events/contradictions.d.ts +59 -0
  92. package/dist/events/contradictions.js +253 -27
  93. package/dist/events/declared.d.ts +17 -0
  94. package/dist/events/declared.js +43 -1
  95. package/dist/events/echo-mismatch.js +65 -1
  96. package/dist/events/event-filters.d.ts +8 -0
  97. package/dist/events/event-filters.js +19 -2
  98. package/dist/events/observed-in-window.d.ts +12 -0
  99. package/dist/events/observed-in-window.js +17 -0
  100. package/dist/events/predicate-asks.d.ts +13 -0
  101. package/dist/events/predicate-asks.js +22 -0
  102. package/dist/events/predicate-eval.js +117 -27
  103. package/dist/events/predicate-parse.d.ts +0 -6
  104. package/dist/events/predicate-parse.js +43 -3
  105. package/dist/events/predicate-precheck.d.ts +8 -0
  106. package/dist/events/predicate-precheck.js +41 -0
  107. package/dist/events/predicate-route.d.ts +14 -0
  108. package/dist/events/predicate-route.js +36 -8
  109. package/dist/events/predicate-schema.d.ts +44 -0
  110. package/dist/events/predicate-schema.js +126 -23
  111. package/dist/events/predicate.d.ts +20 -0
  112. package/dist/events/predicate.js +77 -6
  113. package/dist/events/split-text-miss.d.ts +30 -0
  114. package/dist/events/split-text-miss.js +44 -0
  115. package/dist/flows/coverage.d.ts +0 -8
  116. package/dist/flows/coverage.js +1 -1
  117. package/dist/flows/decision.d.ts +5 -1
  118. package/dist/flows/decision.js +33 -7
  119. package/dist/flows/flake.d.ts +6 -8
  120. package/dist/flows/flake.js +1 -1
  121. package/dist/flows/flow-intent.d.ts +52 -0
  122. package/dist/flows/flow-intent.js +123 -0
  123. package/dist/flows/flow-memory-consult.d.ts +55 -0
  124. package/dist/flows/flow-memory-consult.js +38 -0
  125. package/dist/flows/flow-replay-run.d.ts +18 -7
  126. package/dist/flows/flow-replay-run.js +263 -23
  127. package/dist/flows/flow-replay.js +11 -2
  128. package/dist/flows/flow-step-runners.d.ts +8 -0
  129. package/dist/flows/flow-step-runners.js +8 -2
  130. package/dist/flows/flow-store-for-session.d.ts +31 -0
  131. package/dist/flows/flow-store-for-session.js +33 -0
  132. package/dist/flows/flow-success.js +15 -3
  133. package/dist/flows/flow-tools.js +87 -33
  134. package/dist/flows/flows.d.ts +14 -1
  135. package/dist/flows/flows.js +502 -24
  136. package/dist/flows/heal-run.js +5 -2
  137. package/dist/flows/predicate-to-expect.d.ts +17 -12
  138. package/dist/flows/predicate-to-expect.js +36 -15
  139. package/dist/flows/recordings.d.ts +13 -1
  140. package/dist/flows/recordings.js +17 -2
  141. package/dist/flows/replay.d.ts +22 -2
  142. package/dist/flows/replay.js +28 -2
  143. package/dist/flows/server-verify.js +16 -2
  144. package/dist/flows/verify-change-tools.js +9 -3
  145. package/dist/honesty/blind-spots.d.ts +51 -15
  146. package/dist/honesty/blind-spots.js +64 -4
  147. package/dist/honesty/feature-capture.d.ts +150 -0
  148. package/dist/honesty/feature-capture.js +233 -0
  149. package/dist/honesty/honesty.d.ts +24 -0
  150. package/dist/honesty/honesty.js +25 -1
  151. package/dist/honesty/instrumentation-gaps.d.ts +39 -6
  152. package/dist/honesty/instrumentation-gaps.js +88 -5
  153. package/dist/honesty/tool-hit-rate.d.ts +86 -0
  154. package/dist/honesty/tool-hit-rate.js +90 -0
  155. package/dist/honesty/undeclared-change.d.ts +51 -0
  156. package/dist/honesty/undeclared-change.js +56 -0
  157. package/dist/honesty/verified.d.ts +16 -0
  158. package/dist/honesty/verified.js +84 -5
  159. package/dist/http-server.d.ts +0 -1
  160. package/dist/http-server.js +13 -1
  161. package/dist/hunt/hunt-report.d.ts +0 -2
  162. package/dist/hunt/hunt-report.js +1 -1
  163. package/dist/impact/impact-recorder.d.ts +31 -8
  164. package/dist/impact/impact-recorder.js +104 -21
  165. package/dist/impact/impact-store.d.ts +13 -4
  166. package/dist/impact/impact-store.js +44 -3
  167. package/dist/index.d.ts +4 -1
  168. package/dist/index.js +113 -7
  169. package/dist/init/agent-rules.d.ts +30 -6
  170. package/dist/init/agent-rules.js +54 -13
  171. package/dist/init/astro-patch.d.ts +16 -0
  172. package/dist/init/astro-patch.js +28 -0
  173. package/dist/init/capabilities.d.ts +4 -3
  174. package/dist/init/capabilities.js +23 -3
  175. package/dist/init/closing-hint.d.ts +36 -0
  176. package/dist/init/closing-hint.js +120 -0
  177. package/dist/init/confirm.d.ts +27 -28
  178. package/dist/init/confirm.js +51 -11
  179. package/dist/init/cra.d.ts +34 -5
  180. package/dist/init/cra.js +96 -25
  181. package/dist/init/csp-check.js +17 -2
  182. package/dist/init/csp-doctor.d.ts +8 -1
  183. package/dist/init/csp-doctor.js +8 -3
  184. package/dist/init/dev-script.d.ts +50 -0
  185. package/dist/init/dev-script.js +66 -0
  186. package/dist/init/format-generated.d.ts +18 -0
  187. package/dist/init/format-generated.js +39 -0
  188. package/dist/init/install-hint.d.ts +9 -0
  189. package/dist/init/install-hint.js +29 -0
  190. package/dist/init/mcp-clients.d.ts +7 -0
  191. package/dist/init/mcp-clients.js +7 -0
  192. package/dist/init/mcp.d.ts +17 -6
  193. package/dist/init/mcp.js +27 -11
  194. package/dist/init/node-io.js +11 -1
  195. package/dist/init/plan-framework.d.ts +0 -5
  196. package/dist/init/plan-framework.js +147 -37
  197. package/dist/init/plan.d.ts +30 -37
  198. package/dist/init/plan.js +50 -60
  199. package/dist/init/preflight.d.ts +28 -0
  200. package/dist/init/preflight.js +38 -0
  201. package/dist/init/refresh-rules.d.ts +39 -0
  202. package/dist/init/refresh-rules.js +51 -0
  203. package/dist/init/run.d.ts +32 -35
  204. package/dist/init/run.js +151 -202
  205. package/dist/init/slash-command.d.ts +1 -1
  206. package/dist/init/snippets.js +90 -31
  207. package/dist/init/vite-config.d.ts +1 -1
  208. package/dist/init/vite-config.js +39 -25
  209. package/dist/init/workspace-apps.d.ts +17 -10
  210. package/dist/init/workspace-apps.js +110 -0
  211. package/dist/input/network-mock-tools.js +3 -6
  212. package/dist/input/real-input.d.ts +2 -0
  213. package/dist/input/real-input.js +28 -7
  214. package/dist/input/scroll-find.d.ts +7 -4
  215. package/dist/input/scroll-find.js +63 -25
  216. package/dist/input/scroll-tools.js +4 -6
  217. package/dist/input/viewport-tools.js +5 -4
  218. package/dist/intent/inline-intent.d.ts +37 -0
  219. package/dist/intent/inline-intent.js +193 -0
  220. package/dist/intent/intent-shard-store.d.ts +72 -0
  221. package/dist/intent/intent-shard-store.js +203 -0
  222. package/dist/intent/intent-shard.d.ts +411 -0
  223. package/dist/intent/intent-shard.js +176 -0
  224. package/dist/intent/intent-store.d.ts +49 -0
  225. package/dist/intent/intent-store.js +133 -0
  226. package/dist/intent/intent-subject.d.ts +45 -0
  227. package/dist/intent/intent-subject.js +177 -0
  228. package/dist/intent/intent-tools.d.ts +2 -0
  229. package/dist/intent/intent-tools.js +143 -0
  230. package/dist/intent/open-intents.d.ts +42 -0
  231. package/dist/intent/open-intents.js +40 -0
  232. package/dist/journal/envelope.d.ts +26 -26
  233. package/dist/journal/journal-recorder.d.ts +23 -0
  234. package/dist/journal/journal-recorder.js +35 -0
  235. package/dist/license/license-env.d.ts +29 -0
  236. package/dist/license/license-env.js +148 -0
  237. package/dist/loopback-agent.d.ts +31 -0
  238. package/dist/loopback-agent.js +57 -0
  239. package/dist/mcp/attach-memory.d.ts +83 -0
  240. package/dist/mcp/attach-memory.js +185 -0
  241. package/dist/mcp/client-identity.d.ts +31 -0
  242. package/dist/mcp/client-identity.js +51 -0
  243. package/dist/mcp/mcp-post-transport.d.ts +26 -0
  244. package/dist/mcp/mcp-post-transport.js +109 -0
  245. package/dist/mcp/mcp-proxy.d.ts +2 -8
  246. package/dist/mcp/mcp-proxy.js +64 -62
  247. package/dist/mcp/mcp.d.ts +2 -0
  248. package/dist/mcp/mcp.js +52 -24
  249. package/dist/mcp/proxy-backoff.d.ts +11 -0
  250. package/dist/mcp/proxy-backoff.js +13 -0
  251. package/dist/mcp/proxy-daemon-probe.d.ts +1 -0
  252. package/dist/mcp/proxy-daemon-probe.js +7 -1
  253. package/dist/mcp/proxy-handshake.d.ts +23 -0
  254. package/dist/mcp/proxy-handshake.js +31 -0
  255. package/dist/mcp/server-instructions.js +45 -3
  256. package/dist/mcp/tool-catalog-cache.d.ts +16 -0
  257. package/dist/mcp/tool-catalog-cache.js +22 -0
  258. package/dist/memory/memory-tools.d.ts +2 -0
  259. package/dist/memory/memory-tools.js +82 -0
  260. package/dist/memory/project-memory.d.ts +34 -0
  261. package/dist/memory/project-memory.js +96 -0
  262. package/dist/oracles/flow-instrument-gaps.d.ts +4 -4
  263. package/dist/oracles/flow-instrument-gaps.js +4 -5
  264. package/dist/oracles/propose-consequences.js +12 -3
  265. package/dist/oracles/self-instrument.d.ts +9 -9
  266. package/dist/oracles/self-instrument.js +19 -13
  267. package/dist/pool/browser-pool.d.ts +75 -5
  268. package/dist/pool/browser-pool.js +144 -5
  269. package/dist/pool/playwright-launcher.js +12 -1
  270. package/dist/project/fs-port.d.ts +3 -0
  271. package/dist/project/fs-port.js +3 -2
  272. package/dist/project/memory-fs.js +6 -1
  273. package/dist/project/project-tools.js +6 -35
  274. package/dist/project/reticle-dir.d.ts +17 -2
  275. package/dist/project/reticle-dir.js +22 -4
  276. package/dist/runs/context-tools.d.ts +2 -0
  277. package/dist/runs/context-tools.js +89 -0
  278. package/dist/runs/run-context.d.ts +40 -0
  279. package/dist/runs/run-context.js +158 -0
  280. package/dist/runs/run-diff.d.ts +2 -1
  281. package/dist/runs/run-store.d.ts +10 -0
  282. package/dist/runs/run-store.js +10 -0
  283. package/dist/runs/verification-sync.d.ts +0 -11
  284. package/dist/runs/verification-sync.js +24 -3
  285. package/dist/runs/verify-server.d.ts +8 -1
  286. package/dist/runs/verify-server.js +14 -2
  287. package/dist/session/command-timeout.d.ts +5 -2
  288. package/dist/session/command-timeout.js +5 -4
  289. package/dist/session/dev-server-probe.d.ts +0 -8
  290. package/dist/session/dev-server-probe.js +1 -1
  291. package/dist/session/last-act.d.ts +7 -1
  292. package/dist/session/last-act.js +2 -2
  293. package/dist/session/lease-visibility.d.ts +67 -0
  294. package/dist/session/lease-visibility.js +76 -0
  295. package/dist/session/live-control-tools.js +1 -42
  296. package/dist/session/no-session-diagnosis.d.ts +33 -0
  297. package/dist/session/no-session-diagnosis.js +82 -24
  298. package/dist/session/no-session-next-action.d.ts +12 -0
  299. package/dist/session/no-session-next-action.js +77 -6
  300. package/dist/session/no-session-watch.d.ts +18 -6
  301. package/dist/session/no-session-watch.js +125 -75
  302. package/dist/session/output-budget.js +4 -1
  303. package/dist/session/pending-commands.d.ts +11 -1
  304. package/dist/session/pending-commands.js +12 -2
  305. package/dist/session/resolve-within.d.ts +34 -0
  306. package/dist/session/resolve-within.js +53 -0
  307. package/dist/session/session-health.d.ts +9 -0
  308. package/dist/session/session-health.js +23 -0
  309. package/dist/session/session-info.d.ts +18 -0
  310. package/dist/session/session-info.js +11 -0
  311. package/dist/session/session-manager.d.ts +14 -0
  312. package/dist/session/session-manager.js +111 -4
  313. package/dist/session/session-mirror.d.ts +16 -0
  314. package/dist/session/session-mirror.js +22 -0
  315. package/dist/session/session-recommendation.d.ts +19 -5
  316. package/dist/session/session-recommendation.js +25 -9
  317. package/dist/session/session-successor.d.ts +37 -0
  318. package/dist/session/session-successor.js +113 -0
  319. package/dist/session/session-tools.js +2 -2
  320. package/dist/session/session.d.ts +53 -3
  321. package/dist/session/session.js +162 -9
  322. package/dist/session/wall-clock.d.ts +12 -0
  323. package/dist/session/wall-clock.js +14 -0
  324. package/dist/setup/agent-configs.d.ts +96 -0
  325. package/dist/setup/agent-configs.js +333 -0
  326. package/dist/setup/agent-io.d.ts +9 -0
  327. package/dist/setup/agent-io.js +16 -0
  328. package/dist/setup/agent-writer.d.ts +51 -0
  329. package/dist/setup/agent-writer.js +115 -0
  330. package/dist/setup/approval-migration.d.ts +47 -0
  331. package/dist/setup/approval-migration.js +68 -0
  332. package/dist/setup/auto-approve.d.ts +85 -0
  333. package/dist/setup/auto-approve.js +150 -0
  334. package/dist/setup/bridge-port.d.ts +15 -0
  335. package/dist/setup/bridge-port.js +24 -0
  336. package/dist/setup/desktop-shape.d.ts +57 -0
  337. package/dist/setup/desktop-shape.js +63 -0
  338. package/dist/setup/dev-server-wait.d.ts +60 -0
  339. package/dist/setup/dev-server-wait.js +65 -0
  340. package/dist/setup/drive-agent.d.ts +71 -0
  341. package/dist/setup/drive-agent.js +171 -0
  342. package/dist/setup/drive-plan.d.ts +68 -0
  343. package/dist/setup/drive-plan.js +130 -0
  344. package/dist/setup/ensure-daemon.d.ts +29 -0
  345. package/dist/setup/ensure-daemon.js +58 -0
  346. package/dist/setup/init-runtime.d.ts +47 -0
  347. package/dist/setup/init-runtime.js +160 -0
  348. package/dist/setup/interrupt.d.ts +25 -0
  349. package/dist/setup/interrupt.js +40 -0
  350. package/dist/setup/license-key.d.ts +39 -0
  351. package/dist/setup/license-key.js +72 -0
  352. package/dist/setup/listeners.d.ts +37 -0
  353. package/dist/setup/listeners.js +85 -0
  354. package/dist/setup/node-effects.d.ts +60 -0
  355. package/dist/setup/node-effects.js +210 -0
  356. package/dist/setup/page-probe.d.ts +39 -0
  357. package/dist/setup/page-probe.js +49 -0
  358. package/dist/setup/relaunch.d.ts +36 -0
  359. package/dist/setup/relaunch.js +57 -0
  360. package/dist/setup/remaining-steps.d.ts +19 -0
  361. package/dist/setup/remaining-steps.js +31 -0
  362. package/dist/setup/run-setup.d.ts +99 -0
  363. package/dist/setup/run-setup.js +187 -0
  364. package/dist/setup/session-pick.d.ts +47 -0
  365. package/dist/setup/session-pick.js +51 -0
  366. package/dist/setup/setup-command.d.ts +45 -0
  367. package/dist/setup/setup-command.js +199 -0
  368. package/dist/setup/setup-options.d.ts +74 -0
  369. package/dist/setup/setup-options.js +67 -0
  370. package/dist/setup/transcripts.d.ts +16 -0
  371. package/dist/setup/transcripts.js +43 -0
  372. package/dist/status-payload.d.ts +14 -1
  373. package/dist/status-payload.js +23 -1
  374. package/dist/telemetry/automation-hint.d.ts +2 -1
  375. package/dist/telemetry/daemon-telemetry.d.ts +8 -1
  376. package/dist/telemetry/daemon-telemetry.js +52 -5
  377. package/dist/telemetry/error-class.js +1 -1
  378. package/dist/telemetry/feedback-context.d.ts +9 -1
  379. package/dist/telemetry/feedback-context.js +60 -29
  380. package/dist/telemetry/feedback-local.d.ts +15 -0
  381. package/dist/telemetry/feedback-local.js +56 -0
  382. package/dist/telemetry/feedback-outbox.d.ts +2 -1
  383. package/dist/telemetry/feedback.d.ts +11 -0
  384. package/dist/telemetry/feedback.js +19 -6
  385. package/dist/telemetry/init-telemetry.d.ts +2 -0
  386. package/dist/telemetry/init-telemetry.js +2 -0
  387. package/dist/telemetry/install-source.d.ts +14 -0
  388. package/dist/telemetry/install-source.js +30 -0
  389. package/dist/telemetry/license-activation.d.ts +7 -1
  390. package/dist/telemetry/license-activation.js +9 -5
  391. package/dist/telemetry/mcp-connection.js +7 -0
  392. package/dist/telemetry/project-profile.d.ts +12 -9
  393. package/dist/telemetry/project-profile.js +12 -7
  394. package/dist/telemetry/proxy-telemetry.d.ts +3 -0
  395. package/dist/telemetry/proxy-telemetry.js +21 -0
  396. package/dist/telemetry/run-telemetry.d.ts +0 -2
  397. package/dist/telemetry/run-telemetry.js +1 -1
  398. package/dist/telemetry/session-metrics.d.ts +12 -0
  399. package/dist/telemetry/session-metrics.js +35 -1
  400. package/dist/telemetry/telemetry.js +17 -2
  401. package/dist/telemetry/tool-refused.d.ts +3 -1
  402. package/dist/telemetry/tool-refused.js +34 -2
  403. package/dist/telemetry/verification-of.js +2 -2
  404. package/dist/tools/act-capsule.d.ts +10 -0
  405. package/dist/tools/act-capsule.js +8 -2
  406. package/dist/tools/act-danger.d.ts +2 -1
  407. package/dist/tools/act-danger.js +11 -3
  408. package/dist/tools/act-observation.d.ts +41 -0
  409. package/dist/tools/act-observation.js +36 -0
  410. package/dist/tools/act-preflight.d.ts +20 -0
  411. package/dist/tools/act-preflight.js +98 -0
  412. package/dist/tools/act-sequence-retry.d.ts +45 -0
  413. package/dist/tools/act-sequence-retry.js +58 -0
  414. package/dist/tools/act-sequence-tool.d.ts +13 -0
  415. package/dist/tools/act-sequence-tool.js +135 -0
  416. package/dist/tools/act-target.d.ts +14 -0
  417. package/dist/tools/act-target.js +47 -0
  418. package/dist/tools/act-tools.d.ts +21 -1
  419. package/dist/tools/act-tools.js +196 -204
  420. package/dist/tools/assert-source.d.ts +22 -0
  421. package/dist/tools/assert-source.js +53 -0
  422. package/dist/tools/assert-verdict.d.ts +20 -3
  423. package/dist/tools/assert-verdict.js +58 -20
  424. package/dist/tools/browser-tools.js +6 -22
  425. package/dist/tools/complete-snapshot.d.ts +48 -0
  426. package/dist/tools/complete-snapshot.js +75 -0
  427. package/dist/tools/coverage-tools.d.ts +10 -1
  428. package/dist/tools/coverage-tools.js +123 -69
  429. package/dist/tools/dynamic-tools.js +53 -13
  430. package/dist/tools/error-recovery.d.ts +25 -14
  431. package/dist/tools/error-recovery.js +78 -9
  432. package/dist/tools/feedback-tools.d.ts +1 -1
  433. package/dist/tools/feedback-tools.js +2 -2
  434. package/dist/tools/invoke-tool.js +121 -11
  435. package/dist/tools/lease-hint.d.ts +10 -0
  436. package/dist/tools/lease-hint.js +53 -4
  437. package/dist/tools/lease-tools.d.ts +35 -5
  438. package/dist/tools/lease-tools.js +191 -13
  439. package/dist/tools/merged-name-redirect.d.ts +13 -0
  440. package/dist/tools/merged-name-redirect.js +22 -0
  441. package/dist/tools/navigate-arrival.d.ts +0 -2
  442. package/dist/tools/navigate-arrival.js +1 -1
  443. package/dist/tools/numeric-bounds.d.ts +100 -0
  444. package/dist/tools/numeric-bounds.js +125 -0
  445. package/dist/tools/observe-tools.js +109 -65
  446. package/dist/tools/read-tools.js +31 -8
  447. package/dist/tools/real-input-attempt.d.ts +32 -0
  448. package/dist/tools/real-input-attempt.js +212 -2
  449. package/dist/tools/reconcile-tools.js +22 -8
  450. package/dist/tools/resolve-target.d.ts +0 -7
  451. package/dist/tools/sdk-marker-probe.d.ts +29 -0
  452. package/dist/tools/sdk-marker-probe.js +57 -0
  453. package/dist/tools/snapshot-delta.d.ts +1 -1
  454. package/dist/tools/tool-kit.d.ts +24 -0
  455. package/dist/tools/tool-kit.js +21 -0
  456. package/dist/tools/tool-names.d.ts +5 -0
  457. package/dist/tools/tool-names.js +5 -0
  458. package/dist/tools/tool-surface.d.ts +85 -6
  459. package/dist/tools/tool-surface.js +122 -2
  460. package/dist/tools/tools-helpers.d.ts +19 -0
  461. package/dist/tools/tools-helpers.js +46 -0
  462. package/dist/tools/tools.d.ts +2 -0
  463. package/dist/tools/tools.js +78 -12
  464. package/dist/tools/unadvertised-help.js +4 -4
  465. package/dist/tools/verify-next-baton.d.ts +53 -0
  466. package/dist/tools/verify-next-baton.js +52 -0
  467. package/dist/update/reticle-deps.d.ts +9 -0
  468. package/dist/update/reticle-deps.js +27 -0
  469. package/dist/update/sdk-sync.d.ts +1 -2
  470. package/dist/update/sdk-sync.js +1 -21
  471. package/dist/update/update-nudge.d.ts +25 -10
  472. package/dist/update/update-nudge.js +32 -3
  473. package/dist/update/updater.d.ts +1 -1
  474. package/dist/version/peer-announce.js +4 -2
  475. package/dist/version/sdk-fix.d.ts +47 -0
  476. package/dist/version/sdk-fix.js +127 -0
  477. package/dist/version/version-nudge.d.ts +14 -0
  478. package/dist/version/version-nudge.js +21 -0
  479. package/dist/version/version-skew.d.ts +38 -3
  480. package/dist/version/version-skew.js +83 -7
  481. package/dist/visual/visual-store.d.ts +6 -6
  482. package/dist/visual/visual-store.js +18 -16
  483. package/dist/visual/visual-tools.js +57 -12
  484. package/docs/README.md +2 -1
  485. package/docs/cli/cloud.mdx +1 -1
  486. package/docs/cli/doctor.mdx +2 -0
  487. package/docs/cli/serve.mdx +4 -2
  488. package/docs/cli/verify.mdx +4 -5
  489. package/docs/cli.mdx +7 -3
  490. package/docs/desktop.mdx +1 -1
  491. package/docs/docs.json +2 -2
  492. package/docs/gates.md +1 -1
  493. package/docs/getting-started.md +19 -3
  494. package/docs/index.mdx +9 -6
  495. package/docs/install-agentic.mdx +38 -12
  496. package/docs/install-manual.mdx +1 -1
  497. package/docs/packages/browser.mdx +2 -2
  498. package/docs/packages/server.mdx +1 -1
  499. package/docs/packages.mdx +8 -6
  500. package/docs/platform-integration.md +17 -0
  501. package/docs/predicates.mdx +8 -0
  502. package/docs/reticle-manual.md +106 -0
  503. package/docs/skill-file.mdx +3 -3
  504. package/docs/telemetry-contract.md +108 -4
  505. package/docs/tools-act-sequence.mdx +16 -0
  506. package/docs/tools-tools-and-run.mdx +2 -4
  507. package/docs/troubleshooting.mdx +9 -0
  508. package/docs/usage.md +4 -3
  509. package/package.json +4 -3
  510. package/dist/init/cursor.d.ts +0 -24
  511. package/dist/init/cursor.js +0 -80
  512. package/dist/session/session-readiness.d.ts +0 -34
  513. package/dist/session/session-readiness.js +0 -36
@@ -0,0 +1,38 @@
1
+ import { DAEMON_INNER_COMMAND, DRIVE_FLAG, HEADED_FLAG, HTTP_FLAG, HTTP_PORT_FLAG, HTTP_TOKEN_FLAG, PORT_FLAG, } from './cli-parse.js';
2
+ export function daemonStartOptions(parsed) {
3
+ return {
4
+ port: parsed.port,
5
+ headless: parsed.headless,
6
+ ...(parsed.driveUrl !== undefined ? { driveUrl: parsed.driveUrl } : {}),
7
+ ...(parsed.http
8
+ ? {
9
+ httpVerify: true,
10
+ ...(parsed.httpPort !== undefined ? { httpVerifyPort: parsed.httpPort } : {}),
11
+ ...(parsed.httpToken !== undefined ? { httpVerifyToken: parsed.httpToken } : {}),
12
+ }
13
+ : {}),
14
+ };
15
+ }
16
+ /**
17
+ * The argv a parent hands its detached `_daemon` child.
18
+ *
19
+ * `serve` and `mcp` each built this list by hand and each made the SAME mistake as the options
20
+ * builder above — pushing `--headed` only inside the `driveUrl` branch — so the flag survived
21
+ * parsing, was dropped at the spawn, and the child launched a hidden pool. Three sites, one wrong
22
+ * idea: that headless belongs to the drive URL. It belongs to the pool, which exists either way.
23
+ */
24
+ export function daemonSpawnArgs(parsed) {
25
+ const args = [DAEMON_INNER_COMMAND, PORT_FLAG, String(parsed.port)];
26
+ if (parsed.driveUrl !== undefined)
27
+ args.push(DRIVE_FLAG, parsed.driveUrl);
28
+ if (!parsed.headless)
29
+ args.push(HEADED_FLAG);
30
+ if (parsed.http) {
31
+ args.push(HTTP_FLAG);
32
+ if (parsed.httpPort !== undefined)
33
+ args.push(HTTP_PORT_FLAG, String(parsed.httpPort));
34
+ if (parsed.httpToken !== undefined)
35
+ args.push(HTTP_TOKEN_FLAG, parsed.httpToken);
36
+ }
37
+ return args;
38
+ }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * The browser device flow's wire shapes and its hand-off to a real browser.
3
+ *
4
+ * `reticle login` works like `gh auth login`: the CLI asks the cloud for a device code, opens the
5
+ * approval page, and polls until a human confirms it in a session that is already signed in. That
6
+ * confirmation is the whole security of the flow — only somebody already authenticated in the
7
+ * dashboard can turn a device code into a token for their org.
8
+ *
9
+ * Split from `cloud-cli` because it is self-contained: the schemas describe two endpoints and the
10
+ * opener knows only about platforms. Nothing here reads a session, a credential, or a project.
11
+ */
12
+ import { z } from 'zod';
13
+ /** What `/v1/auth/device/start` answers: the codes, where to approve, and how fast to poll. */
14
+ export declare const DeviceStartSchema: z.ZodObject<{
15
+ deviceCode: z.ZodString;
16
+ userCode: z.ZodString;
17
+ verificationUri: z.ZodString;
18
+ verificationUriComplete: z.ZodString;
19
+ interval: z.ZodNumber;
20
+ expiresAt: z.ZodNumber;
21
+ }, "strip", z.ZodTypeAny, {
22
+ deviceCode: string;
23
+ userCode: string;
24
+ verificationUri: string;
25
+ verificationUriComplete: string;
26
+ interval: number;
27
+ expiresAt: number;
28
+ }, {
29
+ deviceCode: string;
30
+ userCode: string;
31
+ verificationUri: string;
32
+ verificationUriComplete: string;
33
+ interval: number;
34
+ expiresAt: number;
35
+ }>;
36
+ /**
37
+ * What `/v1/auth/device/token` answers while polling. Everything past `status` is optional because
38
+ * only an APPROVED poll carries a token, and `org.id` is absent on a cloud older than org-scoped
39
+ * credentials.
40
+ */
41
+ export declare const DevicePollSchema: z.ZodObject<{
42
+ status: z.ZodString;
43
+ token: z.ZodOptional<z.ZodString>;
44
+ org: z.ZodOptional<z.ZodObject<{
45
+ id: z.ZodOptional<z.ZodString>;
46
+ name: z.ZodString;
47
+ }, "strip", z.ZodTypeAny, {
48
+ name: string;
49
+ id?: string | undefined;
50
+ }, {
51
+ name: string;
52
+ id?: string | undefined;
53
+ }>>;
54
+ }, "strip", z.ZodTypeAny, {
55
+ status: string;
56
+ org?: {
57
+ name: string;
58
+ id?: string | undefined;
59
+ } | undefined;
60
+ token?: string | undefined;
61
+ }, {
62
+ status: string;
63
+ org?: {
64
+ name: string;
65
+ id?: string | undefined;
66
+ } | undefined;
67
+ token?: string | undefined;
68
+ }>;
69
+ /**
70
+ * Best-effort open the approval page in the default browser.
71
+ *
72
+ * Every failure is swallowed on purpose: the URL is printed either way, and a headless box, a
73
+ * container, or an SSH session has no opener at all. Refusing to log in because a convenience did
74
+ * not work would break the one environment that most needs the printed fallback.
75
+ */
76
+ export declare const openBrowser: (target: string) => void;
@@ -0,0 +1,56 @@
1
+ /**
2
+ * The browser device flow's wire shapes and its hand-off to a real browser.
3
+ *
4
+ * `reticle login` works like `gh auth login`: the CLI asks the cloud for a device code, opens the
5
+ * approval page, and polls until a human confirms it in a session that is already signed in. That
6
+ * confirmation is the whole security of the flow — only somebody already authenticated in the
7
+ * dashboard can turn a device code into a token for their org.
8
+ *
9
+ * Split from `cloud-cli` because it is self-contained: the schemas describe two endpoints and the
10
+ * opener knows only about platforms. Nothing here reads a session, a credential, or a project.
11
+ */
12
+ import { z } from 'zod';
13
+ import { spawn } from 'node:child_process';
14
+ import { NodePlatform } from '../platform.js';
15
+ /** What `/v1/auth/device/start` answers: the codes, where to approve, and how fast to poll. */
16
+ export const DeviceStartSchema = z.object({
17
+ deviceCode: z.string(),
18
+ userCode: z.string(),
19
+ verificationUri: z.string(),
20
+ verificationUriComplete: z.string(),
21
+ interval: z.number(),
22
+ expiresAt: z.number(),
23
+ });
24
+ /**
25
+ * What `/v1/auth/device/token` answers while polling. Everything past `status` is optional because
26
+ * only an APPROVED poll carries a token, and `org.id` is absent on a cloud older than org-scoped
27
+ * credentials.
28
+ */
29
+ export const DevicePollSchema = z.object({
30
+ status: z.string(),
31
+ token: z.string().optional(),
32
+ org: z.object({ id: z.string().optional(), name: z.string() }).optional(),
33
+ });
34
+ /**
35
+ * Best-effort open the approval page in the default browser.
36
+ *
37
+ * Every failure is swallowed on purpose: the URL is printed either way, and a headless box, a
38
+ * container, or an SSH session has no opener at all. Refusing to log in because a convenience did
39
+ * not work would break the one environment that most needs the printed fallback.
40
+ */
41
+ export const openBrowser = (target) => {
42
+ const cmd = NodePlatform.MACOS === process.platform
43
+ ? 'open'
44
+ : NodePlatform.WINDOWS === process.platform
45
+ ? 'cmd'
46
+ : 'xdg-open';
47
+ const args = NodePlatform.WINDOWS === process.platform ? ['/c', 'start', '', target] : [target];
48
+ try {
49
+ const child = spawn(cmd, args, { stdio: 'ignore', detached: true });
50
+ child.on('error', () => undefined);
51
+ child.unref();
52
+ }
53
+ catch {
54
+ /* no opener available — the user opens the printed URL manually */
55
+ }
56
+ };
@@ -18,7 +18,8 @@
18
18
  * row added and never documented.
19
19
  *
20
20
  * NOT every row prints on every run. `version` prints only on skew, `port check` only on a
21
- * mismatch, `desktop` only on a desktop project, and `sessions` only when a daemon answered. The
21
+ * mismatch, `sibling` only when a well-known Reticle port other than ours has a listener,
22
+ * `desktop` only on a desktop project, and `sessions` only when a daemon answered. The
22
23
  * vocabulary is therefore "what this command can print", not "what one run printed" — a guard built
23
24
  * by scraping a single invocation would silently stop covering the conditional rows, which are
24
25
  * exactly the ones a reader is least likely to have seen before.
@@ -30,8 +31,18 @@ export declare const DoctorRow: {
30
31
  readonly DAEMON: "daemon";
31
32
  readonly VERSION: "version";
32
33
  readonly SESSIONS: "sessions";
34
+ /**
35
+ * The agent-to-daemon hop, which every other row here is blind to.
36
+ *
37
+ * Reported from the field: a user with the SDK injected, the overlay visible, a live session on
38
+ * the daemon and every tool listed in their client was still told by three separate agents that
39
+ * Reticle was not present. Their words: "four green checkmarks that don't add up to a working
40
+ * verification." Every row in this list checks a COMPONENT; none checked a LINK.
41
+ */
42
+ readonly AGENT_LINK: "agent link";
33
43
  readonly BRIDGE_PORT: "bridge port";
34
44
  readonly PORT_CHECK: "port check";
45
+ readonly SIBLING: "sibling";
35
46
  readonly DAEMON_LOG: "daemon log";
36
47
  readonly TRACING: "tracing";
37
48
  readonly DESKTOP: "desktop";
@@ -18,7 +18,8 @@
18
18
  * row added and never documented.
19
19
  *
20
20
  * NOT every row prints on every run. `version` prints only on skew, `port check` only on a
21
- * mismatch, `desktop` only on a desktop project, and `sessions` only when a daemon answered. The
21
+ * mismatch, `sibling` only when a well-known Reticle port other than ours has a listener,
22
+ * `desktop` only on a desktop project, and `sessions` only when a daemon answered. The
22
23
  * vocabulary is therefore "what this command can print", not "what one run printed" — a guard built
23
24
  * by scraping a single invocation would silently stop covering the conditional rows, which are
24
25
  * exactly the ones a reader is least likely to have seen before.
@@ -30,8 +31,18 @@ export const DoctorRow = {
30
31
  DAEMON: 'daemon',
31
32
  VERSION: 'version',
32
33
  SESSIONS: 'sessions',
34
+ /**
35
+ * The agent-to-daemon hop, which every other row here is blind to.
36
+ *
37
+ * Reported from the field: a user with the SDK injected, the overlay visible, a live session on
38
+ * the daemon and every tool listed in their client was still told by three separate agents that
39
+ * Reticle was not present. Their words: "four green checkmarks that don't add up to a working
40
+ * verification." Every row in this list checks a COMPONENT; none checked a LINK.
41
+ */
42
+ AGENT_LINK: 'agent link',
33
43
  BRIDGE_PORT: 'bridge port',
34
44
  PORT_CHECK: 'port check',
45
+ SIBLING: 'sibling',
35
46
  DAEMON_LOG: 'daemon log',
36
47
  TRACING: 'tracing',
37
48
  DESKTOP: 'desktop',
@@ -78,6 +78,19 @@ export declare function readAttachResponse(port: number, url: string, res: {
78
78
  export declare function requestDriveSession(port: number, url: string, post?: DrivePost): Promise<DriveAttachResult>;
79
79
  /** The success line. Names the session, because that is the handle everything downstream needs. */
80
80
  export declare function describeAttached(port: number, url: string, session: DriveSession): string;
81
+ /**
82
+ * What an attach cannot honour: the window.
83
+ *
84
+ * `drive` documents a HEADED browser and parses `--headless` as the opt-out, but the attach path
85
+ * POSTs a url and nothing else — the daemon answers from a pool it launched headless at boot, and no
86
+ * per-request flag can change a browser that is already running. So the run is invisible, which is
87
+ * the half of the field report that reads "the app starts to run headlessly".
88
+ *
89
+ * Saying so is the honest half. The other half is that the run is no longer unwatchable: the HUD
90
+ * feed is mirrored to every other tab of the same app (see Session.setViewers), so a tab the human
91
+ * already has open shows the narration and the counters as the leased context is driven.
92
+ */
93
+ export declare function driveHeadlessOnAttach(port: number, url: string): string;
81
94
  /**
82
95
  * A stranger holds the port, in words `drive` can honour.
83
96
  *
@@ -147,6 +147,24 @@ export function describeAttached(port, url, session) {
147
147
  return (`attached to the Reticle daemon on :${String(port)} — it is driving ${url} as session ` +
148
148
  `${session.sessionId}, which every Reticle tool can address.${life}`);
149
149
  }
150
+ /**
151
+ * What an attach cannot honour: the window.
152
+ *
153
+ * `drive` documents a HEADED browser and parses `--headless` as the opt-out, but the attach path
154
+ * POSTs a url and nothing else — the daemon answers from a pool it launched headless at boot, and no
155
+ * per-request flag can change a browser that is already running. So the run is invisible, which is
156
+ * the half of the field report that reads "the app starts to run headlessly".
157
+ *
158
+ * Saying so is the honest half. The other half is that the run is no longer unwatchable: the HUD
159
+ * feed is mirrored to every other tab of the same app (see Session.setViewers), so a tab the human
160
+ * already has open shows the narration and the counters as the leased context is driven.
161
+ */
162
+ export function driveHeadlessOnAttach(port, url) {
163
+ return (`note: this session runs inside the daemon on :${String(port)}, whose browser was launched ` +
164
+ 'headless — there is no window to show, and `--headless` is not a per-drive choice. Open ' +
165
+ `${url} in your own browser to watch: any tab of that app mirrors this session's HUD. To get a ` +
166
+ 'window instead, stop the daemon and run `reticle drive` again with the port free.');
167
+ }
150
168
  /**
151
169
  * A stranger holds the port, in words `drive` can honour.
152
170
  *
@@ -13,7 +13,7 @@ import { probeDaemon } from '../mcp/mcp-proxy.js';
13
13
  import { readPid } from '../daemon/daemon.js';
14
14
  import { fetchStatus } from './cli-launch.js';
15
15
  import { captureLookup, findPortHolder } from './port-holder.js';
16
- import { DriveMode, decideDriveMode, describeAttached, driveForeignHolder, driveRaceLost, requestDriveSession, } from './drive-attach.js';
16
+ import { DriveMode, decideDriveMode, describeAttached, driveForeignHolder, driveHeadlessOnAttach, driveRaceLost, requestDriveSession, } from './drive-attach.js';
17
17
  export function handleDrive(parsed) {
18
18
  void driveWithHonestConflict(parsed);
19
19
  }
@@ -43,7 +43,7 @@ async function driveWithHonestConflict(parsed) {
43
43
  return;
44
44
  }
45
45
  if (DriveMode.ATTACH === mode) {
46
- await attachToRunningDaemon(parsed.port, parsed.driveUrl);
46
+ await attachToRunningDaemon(parsed.port, parsed.driveUrl, parsed.headless);
47
47
  return;
48
48
  }
49
49
  // The probe cannot close the race: something can take the port between here and the bind, and the
@@ -81,7 +81,7 @@ async function driveWithHonestConflict(parsed) {
81
81
  * against it renews its lease), and a foreground hold here could only keep it alive by faking tool
82
82
  * calls at it.
83
83
  */
84
- async function attachToRunningDaemon(port, url) {
84
+ async function attachToRunningDaemon(port, url, headless) {
85
85
  const result = await requestDriveSession(port, url);
86
86
  if (!result.ok) {
87
87
  log('reticle_drive_attach_failed', { port, url, reason: result.reason });
@@ -96,6 +96,10 @@ async function attachToRunningDaemon(port, url) {
96
96
  ready: result.session.ready,
97
97
  });
98
98
  process.stderr.write(`${describeAttached(port, url, result.session)}\n`);
99
+ // The command's own default is headed, and this path cannot deliver that. Said out loud rather
100
+ // than left for the user to discover by watching a window that never opens.
101
+ if (!headless)
102
+ process.stderr.write(`${driveHeadlessOnAttach(port, url)}\n`);
99
103
  // A tab that never connected is not a drive session, and reporting one is the false green this
100
104
  // command exists to avoid.
101
105
  if (!result.session.ready)
@@ -0,0 +1,21 @@
1
+ /**
2
+ * What `reticle gate` means by its exit code.
3
+ *
4
+ * Split out of the handler because two different readers depend on it and they want opposite
5
+ * things. CI wants any problem to be a failure. A Stop hook — the layer that makes verification
6
+ * unskippable — wants to block a real regression and NOT block a project that has simply not
7
+ * recorded a flow yet, which is every project on its first day.
8
+ *
9
+ * `NOTHING_TO_CHECK` stays non-zero deliberately. The gate exits non-zero over an empty suite
10
+ * because "nothing was checked" and "everything passed" are indistinguishable from the outside,
11
+ * and that reasoning does not stop being true just because a second caller appeared.
12
+ */
13
+ export declare const GateExit: {
14
+ /** Affected flows were checked and hold. */
15
+ readonly PASS: 0;
16
+ /** Something is actually wrong: an affected flow with no passing artifact, a downgraded
17
+ * assertion, deleted coverage. A caller should stop here. */
18
+ readonly FAIL: 1;
19
+ /** The gate ran and had nothing to judge — no flows recorded. Non-zero, but not a regression. */
20
+ readonly NOTHING_TO_CHECK: 2;
21
+ };
@@ -0,0 +1,21 @@
1
+ /**
2
+ * What `reticle gate` means by its exit code.
3
+ *
4
+ * Split out of the handler because two different readers depend on it and they want opposite
5
+ * things. CI wants any problem to be a failure. A Stop hook — the layer that makes verification
6
+ * unskippable — wants to block a real regression and NOT block a project that has simply not
7
+ * recorded a flow yet, which is every project on its first day.
8
+ *
9
+ * `NOTHING_TO_CHECK` stays non-zero deliberately. The gate exits non-zero over an empty suite
10
+ * because "nothing was checked" and "everything passed" are indistinguishable from the outside,
11
+ * and that reasoning does not stop being true just because a second caller appeared.
12
+ */
13
+ export const GateExit = {
14
+ /** Affected flows were checked and hold. */
15
+ PASS: 0,
16
+ /** Something is actually wrong: an affected flow with no passing artifact, a downgraded
17
+ * assertion, deleted coverage. A caller should stop here. */
18
+ FAIL: 1,
19
+ /** The gate ran and had nothing to judge — no flows recorded. Non-zero, but not a regression. */
20
+ NOTHING_TO_CHECK: 2,
21
+ };
@@ -0,0 +1,33 @@
1
+ /**
2
+ * What a Stop hook shows the human when the gate blocks.
3
+ *
4
+ * A hook runs at the moment an agent says it is finished, and whatever it prints is the last thing
5
+ * anybody reads. The gate's normal output is one long JSON line built for CI to parse; pasted into
6
+ * a terminal at the end of a session it is a wall of names with no instruction attached.
7
+ *
8
+ * So the hook path gets prose. It has to answer three things in the order a person asks them: what
9
+ * is not verified, why that matters right now, and what to do about it — including how to proceed
10
+ * anyway, because a gate with no honest escape hatch gets disabled entirely and then protects
11
+ * nothing.
12
+ */
13
+ /** Named so a reader of the hook output knows the flag is real and not a suggestion. */
14
+ export declare const GATE_SKIP_ENV = "RETICLE_SKIP_GATE";
15
+ export interface GateHookInput {
16
+ /** Flows the changed files touch that have no passing artifact. */
17
+ uncovered: readonly string[];
18
+ /** Flows held back as flaky — reported, never counted as a pass. */
19
+ quarantined: readonly string[];
20
+ /** Assertions that dropped from a real consequence to a fakeable one. */
21
+ downgraded: readonly string[];
22
+ /** Flows whose coverage was deleted while their files changed. */
23
+ deleted: readonly string[];
24
+ }
25
+ /**
26
+ * The message, or undefined when there is nothing to say.
27
+ *
28
+ * `NOTHING_TO_CHECK` returns undefined on purpose: a project that has not recorded a flow yet is
29
+ * every project on its first day, and blocking there would make the first experience of Reticle an
30
+ * agent that cannot stop. The gate still reports it — this decides only whether a HUMAN is
31
+ * interrupted.
32
+ */
33
+ export declare function gateHookMessage(exit: number, input: GateHookInput): string | undefined;
@@ -0,0 +1,46 @@
1
+ /**
2
+ * What a Stop hook shows the human when the gate blocks.
3
+ *
4
+ * A hook runs at the moment an agent says it is finished, and whatever it prints is the last thing
5
+ * anybody reads. The gate's normal output is one long JSON line built for CI to parse; pasted into
6
+ * a terminal at the end of a session it is a wall of names with no instruction attached.
7
+ *
8
+ * So the hook path gets prose. It has to answer three things in the order a person asks them: what
9
+ * is not verified, why that matters right now, and what to do about it — including how to proceed
10
+ * anyway, because a gate with no honest escape hatch gets disabled entirely and then protects
11
+ * nothing.
12
+ */
13
+ import { GateExit } from './gate-exit.js';
14
+ /** Named so a reader of the hook output knows the flag is real and not a suggestion. */
15
+ export const GATE_SKIP_ENV = 'RETICLE_SKIP_GATE';
16
+ const MAX_NAMED = 5;
17
+ /** A short list, then a count — a hook that prints forty names is a hook people turn off. */
18
+ function name(list) {
19
+ const shown = list.slice(0, MAX_NAMED).join(', ');
20
+ return list.length > MAX_NAMED ? `${shown} (+${String(list.length - MAX_NAMED)} more)` : shown;
21
+ }
22
+ /**
23
+ * The message, or undefined when there is nothing to say.
24
+ *
25
+ * `NOTHING_TO_CHECK` returns undefined on purpose: a project that has not recorded a flow yet is
26
+ * every project on its first day, and blocking there would make the first experience of Reticle an
27
+ * agent that cannot stop. The gate still reports it — this decides only whether a HUMAN is
28
+ * interrupted.
29
+ */
30
+ export function gateHookMessage(exit, input) {
31
+ if (GateExit.FAIL !== exit)
32
+ return undefined;
33
+ const lines = ['Reticle: this change is not verified.'];
34
+ if (input.uncovered.length > 0)
35
+ lines.push(` no passing run covers: ${name(input.uncovered)}`);
36
+ if (input.downgraded.length > 0)
37
+ lines.push(` assertions weakened to something fakeable: ${name(input.downgraded)} — a check that cannot fail is not a check`);
38
+ if (input.deleted.length > 0)
39
+ lines.push(` coverage deleted while its files changed: ${name(input.deleted)}`);
40
+ if (input.quarantined.length > 0)
41
+ lines.push(` held back as flaky, so proving nothing: ${name(input.quarantined)}`);
42
+ lines.push('');
43
+ lines.push(' Replay them: npx @reticlehq/server verify <your app url>');
44
+ lines.push(` Or proceed anyway: ${GATE_SKIP_ENV}=1 — it is recorded, not silent.`);
45
+ return lines.join('\n');
46
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * `reticle mcp` — the process an editor launches, and the one whose failure a user experiences as
3
+ * "the MCP server disconnected".
4
+ *
5
+ * Split out of `cli.ts` when that file passed the 1000-line backstop. It earns its own module rather
6
+ * than being an arbitrary slice: everything here is about ONE decision — which daemon this agent
7
+ * talks to for the rest of its life — and the port resolution at the top is the fix for agents on
8
+ * different projects silently sharing one daemon, one identity and one blast radius.
9
+ */
10
+ /**
11
+ * MCP proxy mode: ensures the daemon is running, then bridges Claude Code's
12
+ * stdin/stdout to the daemon's SSE endpoint. This is the recommended way to
13
+ * configure Reticle in.mcp.json — users never need to manage the daemon manually.
14
+ *
15
+ * Pass --drive <url> to have the daemon launch its own Playwright browser at that
16
+ * URL. The agent then has full autonomous control without relying on the user's browser.
17
+ */
18
+ export declare function handleMcp(opts: {
19
+ port: number;
20
+ driveUrl?: string;
21
+ headless: boolean;
22
+ http: boolean;
23
+ httpPort?: number;
24
+ httpToken?: string;
25
+ }): Promise<void>;
@@ -0,0 +1,174 @@
1
+ /**
2
+ * `reticle mcp` — the process an editor launches, and the one whose failure a user experiences as
3
+ * "the MCP server disconnected".
4
+ *
5
+ * Split out of `cli.ts` when that file passed the 1000-line backstop. It earns its own module rather
6
+ * than being an arbitrary slice: everything here is about ONE decision — which daemon this agent
7
+ * talks to for the rest of its life — and the port resolution at the top is the fix for agents on
8
+ * different projects silently sharing one daemon, one identity and one blast radius.
9
+ */
10
+ import { log } from '../log.js';
11
+ import { isAlive, spawnDaemon, reticleStateHome } from '../daemon/daemon.js';
12
+ import { PortPresence, probePresence, presenceIsUsable, describePresence, } from '../daemon/port-presence.js';
13
+ import { waitForDaemon, startMcpProxy, probeDaemon, proxyLog, setProxyLogPort, } from '../mcp/mcp-proxy.js';
14
+ import { installProxyResilience } from '../daemon/daemon-resilience.js';
15
+ import { readProjectId } from './cli-port.js';
16
+ import { resolveMcpPort, daemonProjectAt } from '../daemon/daemon-resolve.js';
17
+ import { daemonSpawnArgs } from './daemon-start-options.js';
18
+ import { WakeAction, decideWake } from '../daemon/wake-decision.js';
19
+ import { pickDaemonPortToBind } from '../daemon/free-port.js';
20
+ import { fetchStatus } from './cli-launch.js';
21
+ import { migrateApprovals } from '../setup/approval-migration.js';
22
+ import { ReticleEnv } from '@reticlehq/core';
23
+ import { agentIo } from '../setup/agent-io.js';
24
+ import { SERVER_VERSION } from '../version/server-version.js';
25
+ import { homedir } from 'node:os';
26
+ /**
27
+ * MCP proxy mode: ensures the daemon is running, then bridges Claude Code's
28
+ * stdin/stdout to the daemon's SSE endpoint. This is the recommended way to
29
+ * configure Reticle in.mcp.json — users never need to manage the daemon manually.
30
+ *
31
+ * Pass --drive <url> to have the daemon launch its own Playwright browser at that
32
+ * URL. The agent then has full autonomous control without relying on the user's browser.
33
+ */
34
+ export async function handleMcp(opts) {
35
+ const { driveUrl, headless, http, httpPort, httpToken } = opts;
36
+ // Decided before anything else, because every line below is about ONE port and the wrong one means
37
+ // serving another project's daemon. `resolveMcpPort` prefers our own daemon wherever it moved to,
38
+ // takes the requested port when it is free or unclaimed, and relocates to an OS-assigned port
39
+ // rather than adopting a daemon that belongs to somebody else.
40
+ //
41
+ // Synchronous-looking on purpose: a probe is one loopback connect, and doing it here keeps the
42
+ // proxy's transport built around a single fixed number. Making the proxy chase a moving port would
43
+ // mean reworking the reconnect path, which is the one piece whose failure reads to a user as
44
+ // "the MCP server disappeared mid-session".
45
+ // Read once and reused by the wake path below: the boot resolution and every later wake must be
46
+ // defending the SAME identity, and computing it twice is how they drift apart.
47
+ // Before the port work, because it is the only moment we are guaranteed to get on a machine that
48
+ // installed Reticle before the pre-approval rules existed. Once per version, never a create that
49
+ // supersedes an allowlist we cannot read, and it cannot throw: see approval-migration.
50
+ // A sandboxed state dir means a test, a gate or a fixture, none of which are asking us to rewrite
51
+ // the real user's editor configuration.
52
+ if (undefined === process.env[ReticleEnv.STATE_DIR]) {
53
+ migrateApprovals({
54
+ io: agentIo,
55
+ home: homedir(),
56
+ platform: process.platform,
57
+ stateHome: reticleStateHome(),
58
+ version: SERVER_VERSION,
59
+ log,
60
+ });
61
+ }
62
+ const projectId = readProjectId(process.cwd());
63
+ const port = await resolveMcpPort(opts.port, projectId, reticleStateHome(), {
64
+ alive: isAlive,
65
+ daemonPresent: async (p) => presenceIsUsable(await probePresence(p, { tcpOpen: probeDaemon, status: fetchStatus })),
66
+ pickPort: (p) => pickDaemonPortToBind(p),
67
+ });
68
+ if (port !== opts.port) {
69
+ log('reticle_mcp_port_relocated', {
70
+ requested: opts.port,
71
+ port,
72
+ reason: 'the requested port serves a different project',
73
+ });
74
+ }
75
+ // The proxy IS the MCP server the editor launched. Nothing respawns it, so an uncaught throw here
76
+ // is what a user experiences as "the MCP server disconnected — open /mcp and reconnect". Log it,
77
+ // report it, keep serving: the reconnect and dormant paths already know how to rebuild the only
78
+ // state this process has. See installProxyResilience for why its rule inverts the daemon's.
79
+ // The proxy's crash handlers must write to the proxy's LOG FILE, not just stderr. The editor
80
+ // swallows stderr, so wiring these to `log` meant a crash was handled and then unrecorded — the
81
+ // failure a user reports as "it disconnected" left nothing behind to read.
82
+ setProxyLogPort(port);
83
+ installProxyResilience(process, proxyLog);
84
+ /**
85
+ * Make sure a daemon is on the port, spawning one if not.
86
+ *
87
+ * Called on first start AND on every reconnect. The proxy used to only retry the socket, so a
88
+ * daemon that exited — crashed, `reticle stop`, or self-shut-down as idle — meant the retries hit
89
+ * a dead port until the budget ran out and the agent's MCP server exited with it. Reticle simply
90
+ * disappeared mid-session with nothing said. Respawning here makes the reconnect self-healing.
91
+ */
92
+ const ensure = async () => {
93
+ // The same question every other surface asks. It used to be a bare TCP connect, so a stranger on
94
+ // the bridge port answered "a daemon is here" — the proxy connected, the stream ended, and each
95
+ // client request woke into the identical non-answer. Rejecting here is what puts the proxy
96
+ // dormant with a reason, instead of pretending the wake succeeded.
97
+ const presence = await probePresence(port, { tcpOpen: probeDaemon, status: fetchStatus });
98
+ // Identity, not just presence. `resolveMcpPort` above already refuses to adopt a stranger's
99
+ // daemon at boot; this is the same question asked on every WAKE, which is where it was missing.
100
+ //
101
+ // Reachable by ordinary use, with no error anywhere: a daemon retires on its idle timer and
102
+ // frees the port, another project's daemon binds it, and this project's dormant proxy wakes
103
+ // straight onto it. The agent then drives, asserts and reports a verdict about a DIFFERENT
104
+ // APPLICATION. That is worse than the disconnect it was reintroduced under the cover of, because
105
+ // a disconnect is visible and this is a false green wearing a valid session.
106
+ const wake = decideWake(presence, daemonProjectAt(port, reticleStateHome()), projectId);
107
+ if (WakeAction.USE === wake)
108
+ return;
109
+ if (WakeAction.REFUSE === wake) {
110
+ throw new Error(PortPresence.DAEMON === presence
111
+ ? `the daemon on port ${String(port)} belongs to a different project, so this one will ` +
112
+ 'not adopt it. Stop it, or start this project on its own port.'
113
+ : describePresence(presence, port));
114
+ }
115
+ const scriptPath = process.argv[1];
116
+ if (scriptPath === undefined) {
117
+ log('reticle_mcp_no_script', {});
118
+ process.exit(1);
119
+ }
120
+ const daemonArgs = daemonSpawnArgs({
121
+ port,
122
+ headless,
123
+ http,
124
+ ...(driveUrl !== undefined ? { driveUrl } : {}),
125
+ ...(httpPort !== undefined ? { httpPort } : {}),
126
+ ...(httpToken !== undefined ? { httpToken } : {}),
127
+ });
128
+ spawnDaemon(process.execPath, scriptPath, daemonArgs, port);
129
+ // Announce the daemon only once the PORT ACCEPTS. This line used to be written the instant the
130
+ // child was spawned, which on a Windows first bootstrap meant `reticle_mcp_daemon_started`
131
+ // followed about ten seconds later by `reticle_mcp_daemon_unavailable` and a first
132
+ // `reticle_sessions` that expired. A readiness signal that precedes readiness is worse than
133
+ // none: a client that believes it stops waiting for the thing that has not happened.
134
+ await waitForDaemon(port);
135
+ log('reticle_mcp_daemon_started', { port, ...(driveUrl !== undefined ? { driveUrl } : {}) });
136
+ };
137
+ // Start the proxy WHATEVER happened to the daemon.
138
+ //
139
+ // This used to exit(1) when `ensure` failed, which is the third way an MCP server disappears on a
140
+ // user: something else is holding the bridge port — a foreign daemon from another project, a
141
+ // half-dead process, a port a colleague's tool grabbed — the spawned daemon cannot bind, and the
142
+ // editor shows a server that failed to start. Nothing about that is unrecoverable: the proxy
143
+ // answers `initialize` itself, serves the cached catalog, and its wake path retries a daemon on
144
+ // every client request. Present-and-complaining beats absent, because absent needs a human.
145
+ // `void`: the chain handles its own failure and the process must not wait on it — the proxy is
146
+ // started from `finally` either way.
147
+ void ensure()
148
+ .catch((err) => {
149
+ log('reticle_mcp_daemon_unavailable', {
150
+ error: err instanceof Error ? err.message : String(err),
151
+ note: 'serving anyway — the next tool call will try to start a daemon again',
152
+ });
153
+ })
154
+ .finally(() => {
155
+ // Also `void`: the proxy runs for the life of the process and is never awaited by anyone.
156
+ //
157
+ // The `.catch` is load-bearing despite that. `startMcpProxy` rejects when its FIRST connect
158
+ // fails — which is precisely the case this block exists to tolerate, a daemon that is not
159
+ // there yet — and with nothing attached that reject became an unhandledRejection. Reported
160
+ // from a win32 user as a crash reading `connect ECONNREFUSED` with an EMPTY frame list,
161
+ // because the stack of a refused socket is entirely node internals and the privacy filter
162
+ // keeps only Reticle frames. So the one path we most want diagnosable arrived as an anonymous
163
+ // crash. The proxy itself is unaffected — it has already installed its stdin reader and goes
164
+ // on serving from cache, waking a daemon on the next request — which is exactly why this
165
+ // must be logged as the expected condition it is rather than reported as a defect.
166
+ void startMcpProxy(port, ensure).catch((err) => {
167
+ log('reticle_mcp_proxy_first_connect_failed', {
168
+ port,
169
+ error: err instanceof Error ? err.message : String(err),
170
+ note: 'serving from cache; the next client request will try to start a daemon',
171
+ });
172
+ });
173
+ });
174
+ }