@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
package/dist/cli.js CHANGED
@@ -5,23 +5,31 @@ import { realpathSync } from 'node:fs';
5
5
  import { join } from 'node:path';
6
6
  import { stateDirProblem } from './daemon/state-dir.js';
7
7
  import { statusNextAction } from './cli/status-next-action.js';
8
- import { hasConnectedBefore } from './session/connection-memory.js';
8
+ import { readDevServers } from './daemon/dev-servers.js';
9
+ import { hasConnectedBefore, hasProjectConnectedBefore } from './session/connection-memory.js';
10
+ import { attachStatusFields } from './mcp/attach-memory.js';
9
11
  import { reticleStateHome } from './daemon/daemon.js';
12
+ import { handleMcp } from './cli/mcp-command.js';
13
+ import { daemonSpawnArgs, daemonStartOptions } from './cli/daemon-start-options.js';
14
+ import { daemonsServingProjectElsewhere, resolveDaemonForProject, splitBrainNote, wrongDaemonNote, } from './daemon/daemon-resolve.js';
10
15
  import { handleWatch, handleCapsules, handleGate, loadNamedFlows, resolveChangedFiles, } from './cli/cli-flow-commands.js';
11
- import { RETICLE_DEFAULT_PORT, ReticleDir, ReticleEnv } from '@reticlehq/core';
16
+ import { RETICLE_DEFAULT_PORT, ReticleDir, ReticleEnv, devServersForProject, } from '@reticlehq/core';
12
17
  import { loadDotEnv } from './telemetry/dev-repo.js';
18
+ import { licenseKeyFromEnvFiles } from './license/license-env.js';
19
+ import { LICENSE_KEY_ENV } from './license/license.js';
13
20
  import { createNodeFileSystem } from './project/fs-port.js';
14
21
  import { affectedSavedFlows } from './flows/flow-sources.js';
15
22
  import { availableUpdate } from './update/update-nudge.js';
16
23
  import { handleUpdate, handleRollback } from './cli/cli-update-commands.js';
17
- import { startDaemon } from './index.js';
24
+ import { startDaemon, RETICLE_VERIFY_DEFAULT_PORT } from './index.js';
25
+ import { verifyEndpointMismatch } from './status-payload.js';
18
26
  import { isCloudCommand, runCloudCommand } from './cli/cloud-cli.js';
19
27
  import { SERVER_VERSION } from './version/server-version.js';
20
28
  import { log } from './log.js';
21
29
  import { readPid, isAlive, removePid, spawnDaemon, discoverDaemonPort, writeDaemonRegistry, logPath, } from './daemon/daemon.js';
22
30
  import { PortPresence, probePresence, presenceIsUsable, describePresence, } from './daemon/port-presence.js';
23
- import { waitForDaemon, startMcpProxy, probeDaemon, proxyLog, setProxyLogPort, } from './mcp/mcp-proxy.js';
24
- import { installDaemonResilience, installProxyResilience, recordExitReason, DaemonExitReason, } from './daemon/daemon-resilience.js';
31
+ import { waitForDaemon, probeDaemon } from './mcp/mcp-proxy.js';
32
+ import { installDaemonResilience, recordExitReason, DaemonExitReason, } from './daemon/daemon-resilience.js';
25
33
  import { IdleShutdown, resolveIdleShutdownMs, resolveIdleCheckMs } from './daemon/idle-shutdown.js';
26
34
  import { DaemonHeartbeat, resolveHeartbeatMs } from './daemon/heartbeat.js';
27
35
  import { everServedToolCall } from './daemon/daemon-usefulness.js';
@@ -31,12 +39,12 @@ import { handleVerify } from './cli/cli-verify.js';
31
39
  import { runKill } from './cli/cli-kill.js';
32
40
  import { summarizeHunt } from './hunt/hunt-report.js';
33
41
  import { runInit } from './init/run.js';
34
- import { confirmInstall, nodeConfirmDeps } from './init/confirm.js';
42
+ import { continueAfterInit } from './setup/init-runtime.js';
35
43
  import { handleDoctor } from './cli/cli-doctor.js';
36
44
  import { buildNodeIo } from './init/node-io.js';
37
45
  import { describeLicense } from './license/license.js';
38
46
  import { isLikelyDevServerPort, devServerPortWarning, readProjectPort, readProjectId, } from './cli/cli-port.js';
39
- import { DAEMON_INNER_COMMAND, PORT_FLAG, DRIVE_FLAG, HEADED_FLAG, HTTP_FLAG, HTTP_PORT_FLAG, HTTP_TOKEN_FLAG, parseCliArgs, CLI_USAGE, } from './cli/cli-parse.js';
47
+ import { DAEMON_INNER_COMMAND, PORT_FLAG, parseCliArgs, CLI_USAGE } from './cli/cli-parse.js';
40
48
  import { handleFeedback, handleIdentify, handleTelemetry } from './telemetry/feedback-cli.js';
41
49
  import { installDaemonTelemetry } from './telemetry/daemon-telemetry.js';
42
50
  import { reportCliRun } from './telemetry/cli-telemetry.js';
@@ -52,14 +60,13 @@ function handleInit(parsed) {
52
60
  dryRun: parsed.dryRun,
53
61
  install: parsed.install,
54
62
  ...(parsed.app === undefined ? {} : { app: parsed.app }),
63
+ captureBodies: true === parsed.captureBodies,
55
64
  // The outcome is reported by confirmInstall instead, once it knows whether an app connected —
56
65
  // `init` writing files was never the same thing as `init` working (#269).
57
66
  deferOutcome: true,
67
+ continuesToRuntime: true !== parsed.filesOnly && true !== parsed.dryRun,
58
68
  }, io);
59
- void confirmInstall(result, io, nodeConfirmDeps(parsed.port ?? RETICLE_DEFAULT_PORT)).then(() => {
60
- if (!result.ok)
61
- process.exit(1);
62
- });
69
+ void continueAfterInit(parsed, result, io, cwd);
63
70
  }
64
71
  function handleServe(parsed) {
65
72
  void serveWithHonestExit(parsed);
@@ -81,6 +88,20 @@ async function serveWithHonestExit(parsed) {
81
88
  status: fetchStatus,
82
89
  });
83
90
  if (presence === PortPresence.DAEMON) {
91
+ // A daemon that is already up was started with ITS flags, not these — `serve` only attaches.
92
+ // Exiting 0 here regardless is how `--http-port` came to be accepted and ignored (#687): the
93
+ // running daemon kept serving whatever it was started with, and the flag vanished without a
94
+ // word. Ask the daemon which verify port it actually serves and refuse when it is not the one
95
+ // requested — a flag that cannot be honoured must say so, not report success.
96
+ if (parsed.http) {
97
+ const mismatch = verifyEndpointMismatch(await fetchStatus(parsed.port), parsed.httpPort ?? RETICLE_VERIFY_DEFAULT_PORT);
98
+ if (mismatch !== undefined) {
99
+ log('reticle_daemon_start_refused', { port: parsed.port, reason: mismatch });
100
+ process.stderr.write(`${mismatch}\n`);
101
+ process.exit(1);
102
+ return;
103
+ }
104
+ }
84
105
  log('reticle_daemon_already_running', { port: parsed.port });
85
106
  return;
86
107
  }
@@ -100,19 +121,7 @@ async function serveWithHonestExit(parsed) {
100
121
  process.exit(1);
101
122
  return;
102
123
  }
103
- const daemonArgs = [DAEMON_INNER_COMMAND, PORT_FLAG, String(parsed.port)];
104
- if (parsed.driveUrl !== undefined) {
105
- daemonArgs.push(DRIVE_FLAG, parsed.driveUrl);
106
- if (!parsed.headless)
107
- daemonArgs.push(HEADED_FLAG);
108
- }
109
- if (parsed.http) {
110
- daemonArgs.push(HTTP_FLAG);
111
- if (parsed.httpPort !== undefined)
112
- daemonArgs.push(HTTP_PORT_FLAG, String(parsed.httpPort));
113
- if (parsed.httpToken !== undefined)
114
- daemonArgs.push(HTTP_TOKEN_FLAG, parsed.httpToken);
115
- }
124
+ const daemonArgs = daemonSpawnArgs(parsed);
116
125
  spawnDaemon(process.execPath, scriptPath, daemonArgs, parsed.port);
117
126
  // The child binds asynchronously and, when it cannot, exits 1 long after this process would have
118
127
  // reported success. Wait for it to ANSWER — `/status` responding is the only evidence a daemon
@@ -229,7 +238,20 @@ function withNextAction(facts) {
229
238
  const next = statusNextAction(facts);
230
239
  return next === undefined ? {} : { nextAction: next };
231
240
  }
232
- function handleStatus(port) {
241
+ /**
242
+ * What `status` says about a project whose daemons have split in two.
243
+ *
244
+ * Both halves in one place because they are one condition asked from two positions, and a command
245
+ * can be standing on either. Silent — no key at all — when there is nothing to report, so a healthy
246
+ * run reads exactly as it did.
247
+ */
248
+ function splitBrainFields(port, projectId) {
249
+ const home = reticleStateHome();
250
+ const elsewhere = splitBrainNote(port, daemonsServingProjectElsewhere(projectId, port, home, isAlive, (other) => hasProjectConnectedBefore(home, other, projectId)));
251
+ const note = elsewhere ?? wrongDaemonNote(port, resolveDaemonForProject(projectId, home, isAlive));
252
+ return note === undefined ? {} : { splitBrain: note };
253
+ }
254
+ export async function handleStatus(port) {
233
255
  const pid = readPid(port);
234
256
  // Durable, so it survives the daemon idling out — which is the state `status` is most often run in.
235
257
  const projectId = readProjectId(process.cwd());
@@ -238,49 +260,94 @@ function handleStatus(port) {
238
260
  // path does the first without the second — so this is the commonest reason `status` has nothing to
239
261
  // report, and it was not among the facts this command could state.
240
262
  const initialized = projectId !== undefined;
241
- if (null === pid || !isAlive(pid)) {
242
- // `running: false` on its own has been reported about a port that was demonstrably occupied,
243
- // because the pid file is not the port. Ask the port before answering.
244
- void probePresence(port, { tcpOpen: probeDaemon, status: fetchStatus }).then((presence) => {
245
- const running = presenceIsUsable(presence);
246
- log('reticle_status', {
247
- port,
248
- running,
249
- presence,
250
- ...(presence === PortPresence.FOREIGN ? { reason: describePresence(presence, port) } : {}),
251
- // `init` promises this command says why the app has not connected. Without it the answer was
252
- // `running: false` and nothing else, which reads as "Reticle is broken" for what is usually
253
- // just a daemon that has not been asked to do anything yet.
254
- ...withNextAction({ running, sessionCount: 0, previouslyConnected, initialized }),
255
- });
263
+ // The OTHER half of the install, and the half this command could not see. A host that registers
264
+ // Reticle and never lists its tools looks exactly like an abandoned install from here — same idle
265
+ // daemon, same zero sessions — and the user sees a tool that does nothing. See attach-memory.ts.
266
+ const client = attachStatusFields(reticleStateHome(), port);
267
+ // What the dev servers themselves said. The reason this command could previously only guess at
268
+ // whether the app was running is that it had no way to see one; now the ones carrying Reticle
269
+ // announce, and the advice below stops contradicting the terminal the reader is looking at.
270
+ // Scoped to THIS project: the registry is machine-wide, and an unrelated app's dev server must
271
+ // not make this project look like it is running.
272
+ const devServerPorts = devServersForProject(readDevServers(reticleStateHome()), {
273
+ projectId,
274
+ root: process.cwd(),
275
+ }).map((d) => d.port);
276
+ // The failure where every individual check is green and the chain is broken: the agent's proxy and
277
+ // the app resolved their ports independently and landed on two different daemons. Reported from
278
+ // BOTH stances, because neither one can see it alone.
279
+ const split = splitBrainFields(port, projectId);
280
+ // Doctor and status must answer the same liveness question. A live pid only says that some
281
+ // process exists; a Reticle daemon is running only when the shared port probe reaches /status.
282
+ const presence = await probePresence(port, { tcpOpen: probeDaemon, status: fetchStatus });
283
+ if (!presenceIsUsable(presence)) {
284
+ log('reticle_status', {
285
+ port,
286
+ running: false,
287
+ presence,
288
+ ...(presence === PortPresence.FOREIGN ? { reason: describePresence(presence, port) } : {}),
289
+ // `init` promises this command says why the app has not connected. Without it the answer was
290
+ // `running: false` and nothing else, which reads as "Reticle is broken" for what is usually
291
+ // just a daemon that has not been asked to do anything yet.
292
+ ...withNextAction({
293
+ running: false,
294
+ sessionCount: 0,
295
+ previouslyConnected,
296
+ initialized,
297
+ devServerPorts,
298
+ }),
299
+ ...client,
300
+ ...split,
256
301
  });
257
302
  return;
258
303
  }
259
- // The daemon is up — ask it for live sessions + health so status is at-a-glance, not just a pid.
260
- void fetchStatus(port).then((payload) => {
261
- // `status` is the second-most-run command and the one a HUMAN types. The update nudge otherwise
262
- // only rides a tool result, which the majority of daemons never produce — so the people most in
263
- // need of an upgrade were the ones with no path to hearing about it.
264
- const update = availableUpdate();
265
- const nudge = update === undefined ? {} : { updateAvailable: update };
266
- if (payload === undefined) {
267
- log('reticle_status', {
268
- port,
304
+ // The daemon answered the same probe doctor trusts — ask it for live sessions + health.
305
+ const payload = await fetchStatus(port);
306
+ // `status` is the second-most-run command and the one a HUMAN types. The update nudge otherwise
307
+ // only rides a tool result, which the majority of daemons never produce — so the people most in
308
+ // need of an upgrade were the ones with no path to hearing about it.
309
+ const update = availableUpdate();
310
+ const nudge = update === undefined ? {} : { updateAvailable: update };
311
+ if (payload === undefined) {
312
+ log('reticle_status', {
313
+ port,
314
+ running: true,
315
+ pid,
316
+ ...nudge,
317
+ ...withNextAction({
269
318
  running: true,
270
- pid,
271
- ...nudge,
272
- ...withNextAction({ running: true, sessionCount: 0, previouslyConnected, initialized }),
273
- });
274
- return;
275
- }
276
- const summary = summarizeStatus(payload);
277
- // Only when the daemon did NOT already explain itself. It has the whole diagnosis in-process and
278
- // puts it on the wire as `why`; printing a second, thinner opinion beside it risks two confident
279
- // answers pointing different ways, which is worse than one.
280
- const next = summary.why === undefined
281
- ? withNextAction({ running: true, ...summary, previouslyConnected, initialized })
282
- : {};
283
- log('reticle_status', { port, running: true, pid, ...summary, ...next, ...nudge });
319
+ sessionCount: 0,
320
+ previouslyConnected,
321
+ initialized,
322
+ devServerPorts,
323
+ }),
324
+ ...client,
325
+ ...split,
326
+ });
327
+ return;
328
+ }
329
+ const summary = summarizeStatus(payload);
330
+ // Only when the daemon did NOT already explain itself. It has the whole diagnosis in-process and
331
+ // puts it on the wire as `why`; printing a second, thinner opinion beside it risks two confident
332
+ // answers pointing different ways, which is worse than one.
333
+ const next = summary.why === undefined
334
+ ? withNextAction({
335
+ running: true,
336
+ ...summary,
337
+ previouslyConnected,
338
+ initialized,
339
+ devServerPorts,
340
+ })
341
+ : {};
342
+ log('reticle_status', {
343
+ port,
344
+ running: true,
345
+ pid,
346
+ ...summary,
347
+ ...next,
348
+ ...client,
349
+ ...split,
350
+ ...nudge,
284
351
  });
285
352
  }
286
353
  /** Print the running package version (resolved once in server-version.ts). */
@@ -398,8 +465,17 @@ async function waitForNewSession(port, before) {
398
465
  return false;
399
466
  }
400
467
  function handleOpen(requestedPort, url) {
468
+ // Our project's daemon first: `discoverDaemonPort` returns the LOWEST live daemon on the machine,
469
+ // whoever it belongs to, so on a machine running two projects `reticle open` could drive the other
470
+ // one's browser. It stays as the last resort for a caller with no project id, where any daemon is
471
+ // better than none and there is no identity to confuse.
472
+ const myProject = readProjectId(process.cwd());
401
473
  probeDaemon(requestedPort)
402
- .then((here) => (here ? requestedPort : (discoverDaemonPort() ?? requestedPort)))
474
+ .then((here) => here
475
+ ? requestedPort
476
+ : (resolveDaemonForProject(myProject, reticleStateHome(), isAlive) ??
477
+ discoverDaemonPort() ??
478
+ requestedPort))
403
479
  .then(async (port) => {
404
480
  await ensureDaemon(port);
405
481
  const { sessions } = summarizeStatus(await fetchStatus(port));
@@ -474,25 +550,13 @@ function handleOpen(requestedPort, url) {
474
550
  });
475
551
  }
476
552
  function handleDaemonInner(parsed) {
477
- const options = {
478
- port: parsed.port,
479
- ...(parsed.driveUrl !== undefined
480
- ? { driveUrl: parsed.driveUrl, headless: parsed.headless }
481
- : {}),
482
- ...(parsed.http
483
- ? {
484
- httpVerify: true,
485
- ...(parsed.httpPort !== undefined ? { httpVerifyPort: parsed.httpPort } : {}),
486
- ...(parsed.httpToken !== undefined ? { httpVerifyToken: parsed.httpToken } : {}),
487
- }
488
- : {}),
489
- };
553
+ const options = daemonStartOptions(parsed);
490
554
  startDaemon(options)
491
555
  .then((server) => {
492
556
  log('reticle_daemon_ready', { port: parsed.port, pid: process.pid });
493
557
  // Daemon lifecycle telemetry: started, a one-shot project profile, the periodic counter flush,
494
558
  // and the rich summary on shutdown. All of it lives in one module — see daemon-telemetry.ts.
495
- const daemonTelemetry = installDaemonTelemetry(process.cwd());
559
+ const daemonTelemetry = installDaemonTelemetry(process.cwd(), undefined, parsed.port);
496
560
  // Publish to the discovery registry so a build plugin can find this daemon by projectId — no
497
561
  // hand-reconciled port. Written from the child (only it knows its cwd); removePid drops it.
498
562
  const registryProjectId = readProjectId(process.cwd());
@@ -585,113 +649,18 @@ function handleDaemonInner(parsed) {
585
649
  process.exit(1);
586
650
  });
587
651
  }
588
- /**
589
- * MCP proxy mode: ensures the daemon is running, then bridges Claude Code's
590
- * stdin/stdout to the daemon's SSE endpoint. This is the recommended way to
591
- * configure Reticle in.mcp.json — users never need to manage the daemon manually.
592
- *
593
- * Pass --drive <url> to have the daemon launch its own Playwright browser at that
594
- * URL. The agent then has full autonomous control without relying on the user's browser.
595
- */
596
- function handleMcp(opts) {
597
- const { port, driveUrl, headless, http, httpPort, httpToken } = opts;
598
- // The proxy IS the MCP server the editor launched. Nothing respawns it, so an uncaught throw here
599
- // is what a user experiences as "the MCP server disconnected — open /mcp and reconnect". Log it,
600
- // report it, keep serving: the reconnect and dormant paths already know how to rebuild the only
601
- // state this process has. See installProxyResilience for why its rule inverts the daemon's.
602
- // The proxy's crash handlers must write to the proxy's LOG FILE, not just stderr. The editor
603
- // swallows stderr, so wiring these to `log` meant a crash was handled and then unrecorded — the
604
- // failure a user reports as "it disconnected" left nothing behind to read.
605
- setProxyLogPort(port);
606
- installProxyResilience(process, proxyLog);
607
- /**
608
- * Make sure a daemon is on the port, spawning one if not.
609
- *
610
- * Called on first start AND on every reconnect. The proxy used to only retry the socket, so a
611
- * daemon that exited — crashed, `reticle stop`, or self-shut-down as idle — meant the retries hit
612
- * a dead port until the budget ran out and the agent's MCP server exited with it. Reticle simply
613
- * disappeared mid-session with nothing said. Respawning here makes the reconnect self-healing.
614
- */
615
- const ensure = async () => {
616
- // The same question every other surface asks. It used to be a bare TCP connect, so a stranger on
617
- // the bridge port answered "a daemon is here" — the proxy connected, the stream ended, and each
618
- // client request woke into the identical non-answer. Rejecting here is what puts the proxy
619
- // dormant with a reason, instead of pretending the wake succeeded.
620
- const presence = await probePresence(port, { tcpOpen: probeDaemon, status: fetchStatus });
621
- if (presenceIsUsable(presence))
622
- return;
623
- if (PortPresence.FREE !== presence)
624
- throw new Error(describePresence(presence, port));
625
- const scriptPath = process.argv[1];
626
- if (scriptPath === undefined) {
627
- log('reticle_mcp_no_script', {});
628
- process.exit(1);
629
- }
630
- const daemonArgs = [DAEMON_INNER_COMMAND, PORT_FLAG, String(port)];
631
- if (driveUrl !== undefined) {
632
- daemonArgs.push(DRIVE_FLAG, driveUrl);
633
- if (!headless)
634
- daemonArgs.push(HEADED_FLAG);
635
- }
636
- // Forward the HTTP-verify flags too (previously silently dropped for `reticle mcp`).
637
- if (http) {
638
- daemonArgs.push(HTTP_FLAG);
639
- if (httpPort !== undefined)
640
- daemonArgs.push(HTTP_PORT_FLAG, String(httpPort));
641
- if (httpToken !== undefined)
642
- daemonArgs.push(HTTP_TOKEN_FLAG, httpToken);
643
- }
644
- spawnDaemon(process.execPath, scriptPath, daemonArgs, port);
645
- // Announce the daemon only once the PORT ACCEPTS. This line used to be written the instant the
646
- // child was spawned, which on a Windows first bootstrap meant `reticle_mcp_daemon_started`
647
- // followed about ten seconds later by `reticle_mcp_daemon_unavailable` and a first
648
- // `reticle_sessions` that expired. A readiness signal that precedes readiness is worse than
649
- // none: a client that believes it stops waiting for the thing that has not happened.
650
- await waitForDaemon(port);
651
- log('reticle_mcp_daemon_started', { port, ...(driveUrl !== undefined ? { driveUrl } : {}) });
652
- };
653
- // Start the proxy WHATEVER happened to the daemon.
654
- //
655
- // This used to exit(1) when `ensure` failed, which is the third way an MCP server disappears on a
656
- // user: something else is holding the bridge port — a foreign daemon from another project, a
657
- // half-dead process, a port a colleague's tool grabbed — the spawned daemon cannot bind, and the
658
- // editor shows a server that failed to start. Nothing about that is unrecoverable: the proxy
659
- // answers `initialize` itself, serves the cached catalog, and its wake path retries a daemon on
660
- // every client request. Present-and-complaining beats absent, because absent needs a human.
661
- // `void`: the chain handles its own failure and the process must not wait on it — the proxy is
662
- // started from `finally` either way.
663
- void ensure()
664
- .catch((err) => {
665
- log('reticle_mcp_daemon_unavailable', {
666
- error: err instanceof Error ? err.message : String(err),
667
- note: 'serving anyway — the next tool call will try to start a daemon again',
668
- });
669
- })
670
- .finally(() => {
671
- // Also `void`: the proxy runs for the life of the process and is never awaited by anyone.
672
- //
673
- // The `.catch` is load-bearing despite that. `startMcpProxy` rejects when its FIRST connect
674
- // fails — which is precisely the case this block exists to tolerate, a daemon that is not
675
- // there yet — and with nothing attached that reject became an unhandledRejection. Reported
676
- // from a win32 user as a crash reading `connect ECONNREFUSED` with an EMPTY frame list,
677
- // because the stack of a refused socket is entirely node internals and the privacy filter
678
- // keeps only Reticle frames. So the one path we most want diagnosable arrived as an anonymous
679
- // crash. The proxy itself is unaffected — it has already installed its stdin reader and goes
680
- // on serving from cache, waking a daemon on the next request — which is exactly why this
681
- // must be logged as the expected condition it is rather than reported as a defect.
682
- void startMcpProxy(port, ensure).catch((err) => {
683
- log('reticle_mcp_proxy_first_connect_failed', {
684
- port,
685
- error: err instanceof Error ? err.message : String(err),
686
- note: 'serving from cache; the next client request will try to start a daemon',
687
- });
688
- });
689
- });
690
- }
691
652
  function main() {
692
653
  // Before anything reads process.env — notably the telemetry gate and the bridge's security
693
654
  // options — fold in a project-local `.env`. Values already in the environment always win.
694
655
  loadDotEnv(process.cwd());
656
+ // The licence key specifically is searched for HARDER than the rest of the environment, because
657
+ // the daemon is spawned without an explicit cwd and inherits the editor's. A key in the app's own
658
+ // `.env` — or in the repo root when the editor started inside the app — was never read, and the
659
+ // customer then produced events that said `missing`, which is indistinguishable from having no
660
+ // licence at all. Only the key is taken; see license-env.ts for why nothing else is.
661
+ const licenseKey = licenseKeyFromEnvFiles(process.cwd());
662
+ if (licenseKey !== undefined)
663
+ process.env[LICENSE_KEY_ENV] = licenseKey;
695
664
  const argv = process.argv.slice(2);
696
665
  // Every invocation passes through here — the single chokepoint for the "how often is it used / how
697
666
  // many distinct machines + projects" metrics. Fire-and-forget: a metric must never delay or fail a run.
@@ -718,7 +687,19 @@ function main() {
718
687
  if (projectPort !== undefined && isLikelyDevServerPort(projectPort)) {
719
688
  process.stderr.write(`${devServerPortWarning(projectPort)}\n`);
720
689
  }
721
- const defaultPort = envPort ?? projectPort ?? RETICLE_DEFAULT_PORT;
690
+ // Registry BEFORE the default, and after both explicit sources.
691
+ //
692
+ // This is the line that ends the split brain. Build plugins have always asked the registry which
693
+ // daemon serves this project; the CLI asked a number and then attached to whoever owned it. So
694
+ // every project on the machine funnelled into one daemon whose identity was whichever project won
695
+ // the race, and one kill on one well-known port was a machine-wide outage.
696
+ //
697
+ // An explicit port still wins, because a person who typed one is answering this question
698
+ // themselves. Below that, our OWN daemon wherever it is listening. Only then the default, which is
699
+ // now a starting preference rather than an assumption.
700
+ const myProjectId = readProjectId(process.cwd());
701
+ const myDaemonPort = resolveDaemonForProject(myProjectId, reticleStateHome(), isAlive);
702
+ const defaultPort = envPort ?? projectPort ?? myDaemonPort ?? RETICLE_DEFAULT_PORT;
722
703
  // Headed by default; hidden only where there is no display to be headed on. A run nobody can see
723
704
  // is a run nobody trusts, and every "did it actually do anything?" cost a human round-trip.
724
705
  const parsed = parseCliArgs(argv, defaultPort, process.env['CI'] !== undefined);
@@ -750,7 +731,7 @@ function main() {
750
731
  void handleRestart(parsed.port, parsed.force);
751
732
  break;
752
733
  case 'status':
753
- handleStatus(parsed.port);
734
+ void handleStatus(parsed.port);
754
735
  break;
755
736
  case 'license':
756
737
  handleLicense();
@@ -792,7 +773,7 @@ function main() {
792
773
  void handleHunt(parsed.dir);
793
774
  break;
794
775
  case 'gate':
795
- void handleGate(parsed.files, parsed.since);
776
+ void handleGate(parsed.files, parsed.since, parsed.hook);
796
777
  break;
797
778
  case 'watch':
798
779
  handleWatch();
@@ -804,7 +785,7 @@ function main() {
804
785
  void handleRollback();
805
786
  break;
806
787
  case 'mcp':
807
- handleMcp(parsed);
788
+ void handleMcp(parsed);
808
789
  break;
809
790
  case '_daemon':
810
791
  handleDaemonInner(parsed);
@@ -1,11 +1,12 @@
1
1
  import type { FileSystemPort } from '../project/fs-port.js';
2
2
  import { type CloudConfig } from './cloud-sync.js';
3
3
  /** `<reticleRoot>/cloud.json` — the project's cloud binding + sync policy (non-secret). */
4
- export declare const CLOUD_LINK_FILE = "cloud.json";
4
+ /** Re-exported so the existing importers keep working; the string itself lives in core. */
5
+ export declare const CLOUD_LINK_FILE: "cloud.json";
5
6
  /** `~/.reticle/credentials.json` — the user's per-cloud-project API keys (the only secret). */
6
7
  export declare const CREDENTIALS_FILE = "credentials.json";
7
8
  /** What the daemon mirrors to the cloud for a project. Each surface is independently toggleable. */
8
- export interface SyncPolicy {
9
+ interface SyncPolicy {
9
10
  /** Push verification-run artifacts to the dashboard Runs tab. */
10
11
  runs: boolean;
11
12
  /** Push per-flow project-memory outcomes (the regression history). */
@@ -27,9 +28,30 @@ export interface ProjectCloud {
27
28
  /** The cloud project id this project is linked to (for display/logging); null when env-fallback. */
28
29
  projectId: string | null;
29
30
  }
31
+ /** Look up the API key for a cloud project id in the user credentials map. */
32
+ /**
33
+ * The API key this machine holds for one project ON ONE CLOUD.
34
+ *
35
+ * The store used to be keyed by cloud project id ALONE, which collides in the case that is not rare
36
+ * at all — it is the default. `reticle link` names every project "default", so a repo linked to a
37
+ * self-hosted install and a repo linked to the hosted service both claim the slot `default`, and
38
+ * whichever was linked last wins. Measured: two repos on one machine, and the production key was
39
+ * being sent to a localhost server, which answered 401.
40
+ *
41
+ * That is the same class of defect as sending a session token to a host that did not issue it, and
42
+ * it deserves the same answer: a credential is scoped to the cloud that minted it, and no credential
43
+ * at all is better than one belonging to somewhere else.
44
+ *
45
+ * Two shapes are accepted. `{ key, url }` is what is written now and is checked against the URL
46
+ * being dialled. A bare string is the legacy shape, has no URL to check, and is still honoured —
47
+ * refusing it would silently unlink every repo that predates this change, which is a worse outage
48
+ * than the collision. Re-linking upgrades a repo to the safe shape.
49
+ */
50
+ export declare const credentialSlot: (url: string, projectId: string, orgId?: string) => string;
30
51
  /**
31
52
  * Resolve the cloud picture for a project rooted at `reticleRoot`. Reads the project's link file + the
32
53
  * user credential store; if the project isn't linked (no cloud.json), falls back to the global env creds
33
54
  * so the single-project / CI flow is unchanged. `homeDir` is injected (testable; `os.homedir()` at call).
34
55
  */
35
56
  export declare function resolveProjectCloud(fs: FileSystemPort, reticleRoot: string, homeDir: string, env: NodeJS.ProcessEnv): Promise<ProjectCloud>;
57
+ export {};
@@ -13,7 +13,8 @@ import { join } from 'node:path';
13
13
  import { ReticleDir } from '@reticlehq/core';
14
14
  import { resolveCloudConfig } from './cloud-sync.js';
15
15
  /** `<reticleRoot>/cloud.json` — the project's cloud binding + sync policy (non-secret). */
16
- export const CLOUD_LINK_FILE = 'cloud.json';
16
+ /** Re-exported so the existing importers keep working; the string itself lives in core. */
17
+ export const CLOUD_LINK_FILE = ReticleDir.CLOUD_LINK_FILE;
17
18
  /** `~/.reticle/credentials.json` — the user's per-cloud-project API keys (the only secret). */
18
19
  export const CREDENTIALS_FILE = 'credentials.json';
19
20
  const DEFAULT_SYNC_POLICY = { runs: true, memory: true, flows: true };
@@ -43,6 +44,7 @@ function parseLink(raw) {
43
44
  const sync = 'object' === typeof o.sync && o.sync !== null ? o.sync : {};
44
45
  return {
45
46
  projectId: o.projectId,
47
+ orgId: 'string' === typeof o.orgId && o.orgId.length > 0 ? o.orgId : undefined,
46
48
  url: o.url,
47
49
  sync: {
48
50
  runs: asBool(sync.runs, DEFAULT_SYNC_POLICY.runs),
@@ -53,12 +55,69 @@ function parseLink(raw) {
53
55
  };
54
56
  }
55
57
  /** Look up the API key for a cloud project id in the user credentials map. */
56
- function credentialFor(raw, projectId) {
58
+ /**
59
+ * The API key this machine holds for one project ON ONE CLOUD.
60
+ *
61
+ * The store used to be keyed by cloud project id ALONE, which collides in the case that is not rare
62
+ * at all — it is the default. `reticle link` names every project "default", so a repo linked to a
63
+ * self-hosted install and a repo linked to the hosted service both claim the slot `default`, and
64
+ * whichever was linked last wins. Measured: two repos on one machine, and the production key was
65
+ * being sent to a localhost server, which answered 401.
66
+ *
67
+ * That is the same class of defect as sending a session token to a host that did not issue it, and
68
+ * it deserves the same answer: a credential is scoped to the cloud that minted it, and no credential
69
+ * at all is better than one belonging to somewhere else.
70
+ *
71
+ * Two shapes are accepted. `{ key, url }` is what is written now and is checked against the URL
72
+ * being dialled. A bare string is the legacy shape, has no URL to check, and is still honoured —
73
+ * refusing it would silently unlink every repo that predates this change, which is a worse outage
74
+ * than the collision. Re-linking upgrades a repo to the safe shape.
75
+ */
76
+ export const credentialSlot = (url, projectId, orgId) => orgId === undefined || 0 === orgId.length
77
+ ? `${normalizeCloudUrl(url)}::${projectId}`
78
+ : `${normalizeCloudUrl(url)}::org::${orgId}::${projectId}`;
79
+ function credentialFor(raw, projectId, url, orgId) {
57
80
  if (typeof raw !== 'object' || null === raw)
58
81
  return null;
59
- const key = raw[projectId];
60
- return 'string' === typeof key && key.length > 0 ? key : null;
82
+ const store = raw;
83
+ /*
84
+ * The ORG slot first, because cloud + project is still not an identity.
85
+ *
86
+ * `link` names every project "default", so two ACCOUNTS on one cloud both claimed the slot
87
+ * `<url>::default` and the last link won. Measured on a laptop: a brand-new workspace ran
88
+ * `reticle login` and was handed a stored key belonging to a different organisation — valid, so
89
+ * every validation passed, and every run it pushed would have landed in a stranger's dashboard.
90
+ * Keying by cloud fixed two clouds colliding; only keying by ORG fixes two tenants colliding.
91
+ */
92
+ const orgScoped = orgId === undefined ? undefined : store[credentialSlot(url, projectId, orgId)];
93
+ if ('string' === typeof orgScoped && orgScoped.length > 0)
94
+ return orgScoped;
95
+ /*
96
+ * Then the cloud+project slot, which is what every repo linked before this change still holds. It
97
+ * is ambiguous between tenants by construction, which is why it is consulted second and why the
98
+ * writer above stopped producing it as the only entry.
99
+ */
100
+ const composite = store[credentialSlot(url, projectId)];
101
+ if ('string' === typeof composite && composite.length > 0)
102
+ return composite;
103
+ const entry = store[projectId];
104
+ if ('string' === typeof entry)
105
+ return entry.length > 0 ? entry : null;
106
+ if (typeof entry !== 'object' || null === entry)
107
+ return null;
108
+ const record = entry;
109
+ const key = record['key'];
110
+ const forUrl = record['url'];
111
+ if ('string' !== typeof key || 0 === key.length)
112
+ return null;
113
+ // A credential stamped with a DIFFERENT cloud is not this project's credential, however much the
114
+ // project ids match. Refuse rather than dial somewhere with somebody else's key.
115
+ if ('string' === typeof forUrl && normalizeCloudUrl(forUrl) !== normalizeCloudUrl(url))
116
+ return null;
117
+ return key;
61
118
  }
119
+ /** Trailing slashes are not identity: `https://x/` and `https://x` are one cloud. */
120
+ const normalizeCloudUrl = (url) => url.replace(/\/+$/, '');
62
121
  /**
63
122
  * Resolve the cloud picture for a project rooted at `reticleRoot`. Reads the project's link file + the
64
123
  * user credential store; if the project isn't linked (no cloud.json), falls back to the global env creds
@@ -80,7 +139,7 @@ export async function resolveProjectCloud(fs, reticleRoot, homeDir, env) {
80
139
  memory: link.sync.memory ?? DEFAULT_SYNC_POLICY.memory,
81
140
  flows: link.sync.flows ?? DEFAULT_SYNC_POLICY.flows,
82
141
  };
83
- const key = credentialFor(await readJson(fs, join(homeDir, ReticleDir.ROOT, CREDENTIALS_FILE)), link.projectId);
142
+ const key = credentialFor(await readJson(fs, join(homeDir, ReticleDir.ROOT, CREDENTIALS_FILE)), link.projectId, link.url, link.orgId);
84
143
  const config = key !== null ? { url: link.url.replace(/\/+$/, ''), apiKey: key } : null;
85
144
  return { config, policy, verify: link.verify, projectId: link.projectId };
86
145
  }