@reticlehq/server 2.4.0 → 2.5.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 (416) hide show
  1. package/SKILL.md +702 -0
  2. package/dist/bridge/auth-failure-reason.d.ts +19 -0
  3. package/dist/bridge/auth-failure-reason.js +29 -0
  4. package/dist/{bridge-security.d.ts → bridge/bridge-security.d.ts} +1 -1
  5. package/dist/{bridge-security.js → bridge/bridge-security.js} +1 -1
  6. package/dist/{bridge.d.ts → bridge/bridge.d.ts} +1 -1
  7. package/dist/{bridge.js → bridge/bridge.js} +63 -13
  8. package/dist/{bridge.test-harness.d.ts → bridge/bridge.test-harness.d.ts} +1 -11
  9. package/dist/{bridge.test-harness.js → bridge/bridge.test-harness.js} +9 -9
  10. package/dist/{pairing-token.js → bridge/pairing-token.js} +1 -1
  11. package/dist/capsule/capsule-store.d.ts +8 -3
  12. package/dist/capsule/capsule-store.js +7 -2
  13. package/dist/capsule/capsule.js +3 -3
  14. package/dist/capsule/causal-summary.d.ts +8 -0
  15. package/dist/capsule/causal-summary.js +22 -12
  16. package/dist/capsule/divergence.d.ts +9 -4
  17. package/dist/capsule/divergence.js +8 -8
  18. package/dist/capsule/predicate-to-links.js +11 -8
  19. package/dist/{cli-doctor.js → cli/cli-doctor.js} +9 -2
  20. package/dist/{cli-flow-commands.d.ts → cli/cli-flow-commands.d.ts} +2 -2
  21. package/dist/{cli-flow-commands.js → cli/cli-flow-commands.js} +15 -15
  22. package/dist/{cli-launch.d.ts → cli/cli-launch.d.ts} +32 -3
  23. package/dist/cli/cli-launch.js +171 -0
  24. package/dist/{cli-parse.d.ts → cli/cli-parse.d.ts} +2 -11
  25. package/dist/{cli-parse.js → cli/cli-parse.js} +113 -60
  26. package/dist/{cli-port.d.ts → cli/cli-port.d.ts} +12 -0
  27. package/dist/{cli-port.js → cli/cli-port.js} +9 -9
  28. package/dist/cli/cli-update-commands.d.ts +4 -0
  29. package/dist/cli/cli-update-commands.js +93 -0
  30. package/dist/{cli-verify.js → cli/cli-verify.js} +14 -14
  31. package/dist/{cloud-cli.js → cli/cloud-cli.js} +29 -24
  32. package/dist/cli.d.ts +2 -2
  33. package/dist/cli.js +174 -92
  34. package/dist/cloud/cloud-config.d.ts +0 -1
  35. package/dist/cloud/cloud-config.js +9 -9
  36. package/dist/cloud/cloud-sync.d.ts +1 -7
  37. package/dist/cloud/cloud-sync.js +12 -12
  38. package/dist/crawl/crawl-empty.d.ts +16 -0
  39. package/dist/crawl/crawl-empty.js +24 -0
  40. package/dist/crawl/crawl-tools.js +4 -1
  41. package/dist/crawl/crawl.js +26 -7
  42. package/dist/daemon/daemon-resilience.d.ts +49 -0
  43. package/dist/daemon/daemon-resilience.js +191 -0
  44. package/dist/daemon/daemon-usefulness.d.ts +61 -0
  45. package/dist/daemon/daemon-usefulness.js +62 -0
  46. package/dist/{daemon.d.ts → daemon/daemon.d.ts} +33 -8
  47. package/dist/{daemon.js → daemon/daemon.js} +80 -48
  48. package/dist/daemon/idle-grace.d.ts +32 -0
  49. package/dist/daemon/idle-grace.js +41 -0
  50. package/dist/{idle-shutdown.d.ts → daemon/idle-shutdown.d.ts} +13 -0
  51. package/dist/{idle-shutdown.js → daemon/idle-shutdown.js} +27 -4
  52. package/dist/domain/domain-model.js +2 -2
  53. package/dist/events/body-failures.js +8 -8
  54. package/dist/events/contradictions.d.ts +1 -1
  55. package/dist/events/contradictions.js +60 -10
  56. package/dist/events/echo-mismatch.js +10 -10
  57. package/dist/events/event-filters.js +6 -6
  58. package/dist/events/json-salvage.d.ts +2 -1
  59. package/dist/events/json-salvage.js +6 -6
  60. package/dist/events/observed-in-window.d.ts +12 -0
  61. package/dist/events/observed-in-window.js +21 -0
  62. package/dist/events/predicate-eval.d.ts +43 -20
  63. package/dist/events/predicate-eval.js +159 -55
  64. package/dist/events/predicate.d.ts +2 -2
  65. package/dist/events/predicate.js +112 -35
  66. package/dist/events/reconcile.js +5 -5
  67. package/dist/events/ring-buffer.js +1 -1
  68. package/dist/events/stale-response.js +6 -6
  69. package/dist/events/testid-near-miss.d.ts +22 -0
  70. package/dist/events/testid-near-miss.js +33 -0
  71. package/dist/events/unit-mismatch.js +8 -8
  72. package/dist/flows/affected-tools.js +19 -4
  73. package/dist/flows/affected.js +1 -1
  74. package/dist/flows/annotate-target.d.ts +1 -0
  75. package/dist/flows/annotate-target.js +20 -0
  76. package/dist/flows/annotate-tools.js +25 -3
  77. package/dist/flows/annotate.js +17 -4
  78. package/dist/flows/assertion-integrity.d.ts +2 -1
  79. package/dist/flows/assertion-tiers-store.d.ts +3 -66
  80. package/dist/flows/assertion-tiers-store.js +2 -2
  81. package/dist/flows/attributed-failure.d.ts +17 -0
  82. package/dist/flows/attributed-failure.js +20 -0
  83. package/dist/flows/buddy-status.d.ts +2 -1
  84. package/dist/flows/buddy-status.js +2 -2
  85. package/dist/flows/coverage.d.ts +3 -2
  86. package/dist/flows/coverage.js +2 -2
  87. package/dist/flows/decision.js +24 -9
  88. package/dist/flows/empty-flow.d.ts +6 -0
  89. package/dist/flows/empty-flow.js +24 -0
  90. package/dist/flows/flake-store.d.ts +0 -1
  91. package/dist/flows/flake-store.js +1 -1
  92. package/dist/flows/flake.d.ts +2 -1
  93. package/dist/flows/flake.js +2 -2
  94. package/dist/flows/flow-classify.js +12 -0
  95. package/dist/flows/flow-replay-run.js +4 -4
  96. package/dist/flows/flow-replay.d.ts +5 -7
  97. package/dist/flows/flow-replay.js +114 -47
  98. package/dist/flows/flow-report.js +1 -1
  99. package/dist/flows/flow-scope.d.ts +1 -6
  100. package/dist/flows/flow-step-runners.d.ts +26 -6
  101. package/dist/flows/flow-step-runners.js +157 -6
  102. package/dist/flows/flow-success.js +20 -15
  103. package/dist/flows/flow-tools.js +53 -7
  104. package/dist/flows/flows.js +15 -11
  105. package/dist/flows/gate.d.ts +4 -3
  106. package/dist/flows/gate.js +1 -1
  107. package/dist/flows/heal-run.d.ts +0 -8
  108. package/dist/flows/heal-run.js +5 -5
  109. package/dist/flows/heal.js +3 -3
  110. package/dist/flows/predicate-to-expect.d.ts +38 -0
  111. package/dist/flows/predicate-to-expect.js +95 -0
  112. package/dist/flows/replay.d.ts +16 -1
  113. package/dist/flows/replay.js +58 -22
  114. package/dist/flows/role-anchor-nearest.d.ts +6 -0
  115. package/dist/flows/role-anchor-nearest.js +45 -0
  116. package/dist/flows/server-verify.d.ts +1 -1
  117. package/dist/flows/server-verify.js +5 -5
  118. package/dist/flows/verify-change-tools.js +32 -10
  119. package/dist/flows/verify-change-verdict.d.ts +15 -0
  120. package/dist/flows/verify-change-verdict.js +25 -0
  121. package/dist/flows/watch-batcher.d.ts +3 -2
  122. package/dist/honesty/already-true.d.ts +2 -0
  123. package/dist/honesty/already-true.js +31 -0
  124. package/dist/honesty/blind-spots.d.ts +16 -3
  125. package/dist/honesty/blind-spots.js +24 -12
  126. package/dist/honesty/honesty.d.ts +3 -2
  127. package/dist/honesty/honesty.js +4 -4
  128. package/dist/honesty/uncaptured-bodies.js +1 -1
  129. package/dist/honesty/unread-outcome.js +2 -2
  130. package/dist/honesty/verified.d.ts +22 -3
  131. package/dist/honesty/verified.js +48 -8
  132. package/dist/http-server.js +14 -7
  133. package/dist/hunt/hunt-report.d.ts +2 -1
  134. package/dist/hunt/hunt-report.js +2 -2
  135. package/dist/index.d.ts +8 -6
  136. package/dist/index.js +32 -25
  137. package/dist/init/agent-rules.d.ts +2 -1
  138. package/dist/init/agent-rules.js +45 -7
  139. package/dist/init/app-choice.d.ts +20 -0
  140. package/dist/init/app-choice.js +25 -0
  141. package/dist/init/astro-host.d.ts +21 -0
  142. package/dist/init/astro-host.js +27 -0
  143. package/dist/init/astro-patch.d.ts +16 -0
  144. package/dist/init/astro-patch.js +240 -0
  145. package/dist/init/capabilities.js +4 -1
  146. package/dist/init/cra.d.ts +37 -0
  147. package/dist/init/cra.js +90 -0
  148. package/dist/init/cursor.js +39 -4
  149. package/dist/init/desktop-doctor.d.ts +3 -2
  150. package/dist/init/desktop-doctor.js +4 -4
  151. package/dist/init/detect.d.ts +3 -2
  152. package/dist/init/detect.js +16 -5
  153. package/dist/init/mcp-registered.d.ts +12 -0
  154. package/dist/init/mcp-registered.js +14 -0
  155. package/dist/init/mcp.d.ts +27 -2
  156. package/dist/init/mcp.js +40 -4
  157. package/dist/init/next-patch.d.ts +2 -4
  158. package/dist/init/next-patch.js +109 -19
  159. package/dist/init/node-io.js +23 -6
  160. package/dist/init/plan-framework.d.ts +16 -1
  161. package/dist/init/plan-framework.js +132 -8
  162. package/dist/init/plan.d.ts +73 -8
  163. package/dist/init/plan.js +133 -34
  164. package/dist/init/project-id.d.ts +5 -9
  165. package/dist/init/project-id.js +10 -22
  166. package/dist/init/run.d.ts +5 -0
  167. package/dist/init/run.js +210 -36
  168. package/dist/init/slash-command.d.ts +9 -0
  169. package/dist/init/slash-command.js +10 -1
  170. package/dist/init/snippets.d.ts +7 -10
  171. package/dist/init/snippets.js +44 -15
  172. package/dist/init/vite-config.js +1 -1
  173. package/dist/init/workspace-apps.d.ts +23 -0
  174. package/dist/init/workspace-apps.js +68 -0
  175. package/dist/input/driven-redaction.js +1 -1
  176. package/dist/input/network-detail.js +8 -8
  177. package/dist/input/network-mock-tools.js +2 -2
  178. package/dist/input/network-mock.js +4 -4
  179. package/dist/input/real-input.js +8 -3
  180. package/dist/input/scroll-find.js +2 -2
  181. package/dist/input/scroll-tools.d.ts +0 -5
  182. package/dist/input/scroll-tools.js +11 -4
  183. package/dist/input/viewport-tools.js +2 -2
  184. package/dist/journal/ambient-store.d.ts +0 -2
  185. package/dist/journal/ambient-store.js +1 -1
  186. package/dist/journal/ambient.js +1 -1
  187. package/dist/journal/attach-journal.d.ts +2 -1
  188. package/dist/journal/deviation-report.js +6 -6
  189. package/dist/journal/deviation-service.js +1 -1
  190. package/dist/journal/envelope-store.d.ts +0 -2
  191. package/dist/journal/envelope-store.js +1 -1
  192. package/dist/journal/envelope.d.ts +2 -3
  193. package/dist/journal/envelope.js +3 -3
  194. package/dist/journal/journal-query.js +1 -1
  195. package/dist/journal/journal-recorder.d.ts +2 -1
  196. package/dist/journal/journal-recorder.js +2 -2
  197. package/dist/journal/rollups.js +9 -9
  198. package/dist/journal/session-end.d.ts +2 -1
  199. package/dist/journal/session-journal.js +4 -4
  200. package/dist/license/license.d.ts +0 -14
  201. package/dist/license/license.js +8 -8
  202. package/dist/log.d.ts +10 -0
  203. package/dist/log.js +11 -1
  204. package/dist/mcp/mcp-is-error.d.ts +13 -0
  205. package/dist/mcp/mcp-is-error.js +18 -0
  206. package/dist/mcp/mcp-outage.d.ts +31 -0
  207. package/dist/mcp/mcp-outage.js +48 -0
  208. package/dist/mcp/mcp-proxy.d.ts +124 -0
  209. package/dist/mcp/mcp-proxy.js +697 -0
  210. package/dist/{mcp.d.ts → mcp/mcp.d.ts} +13 -10
  211. package/dist/{mcp.js → mcp/mcp.js} +110 -35
  212. package/dist/mcp/proxy-handshake.d.ts +3 -0
  213. package/dist/mcp/proxy-handshake.js +54 -0
  214. package/dist/mcp/proxy-lifecycle.d.ts +64 -0
  215. package/dist/mcp/proxy-lifecycle.js +70 -0
  216. package/dist/mcp/tool-catalog-cache.d.ts +29 -0
  217. package/dist/mcp/tool-catalog-cache.js +55 -0
  218. package/dist/oracles/propose-consequences.d.ts +2 -1
  219. package/dist/oracles/propose-consequences.js +16 -11
  220. package/dist/oracles/self-instrument.d.ts +2 -1
  221. package/dist/oracles/self-instrument.js +1 -1
  222. package/dist/phenomena/phenomena.js +3 -3
  223. package/dist/platform.d.ts +16 -0
  224. package/dist/platform.js +16 -0
  225. package/dist/pool/playwright-launcher.d.ts +15 -2
  226. package/dist/pool/playwright-launcher.js +21 -2
  227. package/dist/project/fs-port.js +1 -1
  228. package/dist/project/project-tools.js +2 -2
  229. package/dist/runs/build-verification-run.js +1 -1
  230. package/dist/runs/repair-prompt.d.ts +0 -6
  231. package/dist/runs/repair-prompt.js +3 -3
  232. package/dist/runs/risk-classify.js +1 -1
  233. package/dist/runs/run-diff.d.ts +0 -2
  234. package/dist/runs/run-diff.js +3 -3
  235. package/dist/runs/run-tools.js +2 -2
  236. package/dist/runs/verification-sync.js +2 -2
  237. package/dist/runs/verify-http.js +1 -1
  238. package/dist/runs/verify-server.js +1 -1
  239. package/dist/session/command-timeout.d.ts +11 -3
  240. package/dist/session/command-timeout.js +2 -2
  241. package/dist/session/control-envelope.d.ts +6 -0
  242. package/dist/session/control-envelope.js +7 -1
  243. package/dist/session/dev-server-probe.d.ts +13 -0
  244. package/dist/session/dev-server-probe.js +52 -0
  245. package/dist/session/last-act.d.ts +17 -3
  246. package/dist/session/last-act.js +16 -4
  247. package/dist/session/live-control-tools.js +7 -2
  248. package/dist/session/live-control.js +1 -1
  249. package/dist/session/looks-like-dev-server.d.ts +14 -0
  250. package/dist/session/looks-like-dev-server.js +25 -0
  251. package/dist/session/no-session-diagnosis.d.ts +38 -0
  252. package/dist/session/no-session-diagnosis.js +70 -0
  253. package/dist/session/no-session-watch.d.ts +19 -0
  254. package/dist/session/no-session-watch.js +61 -0
  255. package/dist/session/observed-state.d.ts +4 -0
  256. package/dist/session/observed-state.js +19 -1
  257. package/dist/session/output-budget.js +1 -1
  258. package/dist/session/session-health.d.ts +8 -1
  259. package/dist/session/session-health.js +8 -7
  260. package/dist/session/session-info.d.ts +26 -7
  261. package/dist/session/session-info.js +42 -1
  262. package/dist/session/session-lease.d.ts +0 -11
  263. package/dist/session/session-lease.js +1 -1
  264. package/dist/session/session-manager.d.ts +3 -0
  265. package/dist/session/session-manager.js +52 -9
  266. package/dist/session/session-readiness.d.ts +1 -1
  267. package/dist/session/session-readiness.js +6 -1
  268. package/dist/session/session-reaper.js +1 -1
  269. package/dist/session/session-reconnect.d.ts +32 -0
  270. package/dist/session/session-reconnect.js +42 -0
  271. package/dist/session/session.d.ts +14 -2
  272. package/dist/session/session.js +69 -26
  273. package/dist/session/state-select.d.ts +1 -1
  274. package/dist/session/state-select.js +1 -1
  275. package/dist/status-payload.d.ts +19 -0
  276. package/dist/status-payload.js +11 -0
  277. package/dist/telemetry/argument-shape.js +10 -1
  278. package/dist/telemetry/browser-mode.d.ts +27 -0
  279. package/dist/telemetry/browser-mode.js +33 -0
  280. package/dist/telemetry/bug-found.d.ts +17 -21
  281. package/dist/telemetry/bug-found.js +120 -8
  282. package/dist/telemetry/cli-telemetry.d.ts +2 -12
  283. package/dist/telemetry/cli-telemetry.js +27 -4
  284. package/dist/telemetry/daemon-telemetry.d.ts +19 -0
  285. package/dist/telemetry/daemon-telemetry.js +12 -2
  286. package/dist/telemetry/dev-repo.js +4 -4
  287. package/dist/telemetry/error-fingerprint.js +21 -2
  288. package/dist/telemetry/feedback-cli.d.ts +1 -1
  289. package/dist/telemetry/feedback-cli.js +4 -4
  290. package/dist/telemetry/feedback-context.d.ts +22 -0
  291. package/dist/telemetry/feedback-context.js +41 -1
  292. package/dist/telemetry/feedback-delivery.d.ts +20 -0
  293. package/dist/telemetry/feedback-delivery.js +30 -0
  294. package/dist/telemetry/feedback-outbox.d.ts +22 -0
  295. package/dist/telemetry/feedback-outbox.js +87 -0
  296. package/dist/telemetry/feedback.d.ts +22 -1
  297. package/dist/telemetry/feedback.js +67 -8
  298. package/dist/telemetry/git-facts.js +1 -1
  299. package/dist/telemetry/session-metrics.d.ts +2 -0
  300. package/dist/telemetry/session-metrics.js +68 -6
  301. package/dist/telemetry/telemetry.d.ts +27 -2
  302. package/dist/telemetry/telemetry.js +119 -29
  303. package/dist/telemetry/verification-of.d.ts +22 -0
  304. package/dist/telemetry/verification-of.js +84 -0
  305. package/dist/tools/act-capsule.js +2 -2
  306. package/dist/tools/act-danger.d.ts +12 -0
  307. package/dist/tools/act-danger.js +20 -3
  308. package/dist/tools/act-helpers.js +4 -4
  309. package/dist/tools/act-tools.d.ts +0 -8
  310. package/dist/tools/act-tools.js +84 -115
  311. package/dist/tools/act-view.js +7 -7
  312. package/dist/tools/alias-args.d.ts +17 -0
  313. package/dist/tools/alias-args.js +25 -0
  314. package/dist/tools/assert-grade.js +9 -9
  315. package/dist/tools/assert-verdict.d.ts +3 -1
  316. package/dist/tools/assert-verdict.js +8 -5
  317. package/dist/tools/browser-tools.js +30 -11
  318. package/dist/tools/contract-tools.js +1 -1
  319. package/dist/tools/coverage-identity.d.ts +34 -0
  320. package/dist/tools/coverage-identity.js +46 -0
  321. package/dist/tools/coverage-tools.js +10 -14
  322. package/dist/tools/dynamic-tools.d.ts +2 -1
  323. package/dist/tools/dynamic-tools.js +90 -18
  324. package/dist/tools/error-recovery.d.ts +20 -2
  325. package/dist/tools/error-recovery.js +179 -4
  326. package/dist/tools/feedback-tools.d.ts +12 -1
  327. package/dist/tools/feedback-tools.js +28 -5
  328. package/dist/tools/invoke-tool.d.ts +0 -6
  329. package/dist/tools/invoke-tool.js +57 -32
  330. package/dist/tools/lease-hint.d.ts +17 -0
  331. package/dist/tools/lease-hint.js +24 -0
  332. package/dist/tools/lease-tools.js +6 -5
  333. package/dist/tools/merge-tools.d.ts +10 -1
  334. package/dist/tools/merge-tools.js +9 -3
  335. package/dist/tools/merged-name-redirect.d.ts +13 -0
  336. package/dist/tools/merged-name-redirect.js +55 -0
  337. package/dist/tools/navigate-result.d.ts +17 -0
  338. package/dist/tools/navigate-result.js +31 -0
  339. package/dist/tools/observe-tools.js +54 -20
  340. package/dist/tools/observed-nothing.d.ts +25 -0
  341. package/dist/tools/observed-nothing.js +33 -0
  342. package/dist/tools/query-paginate.js +2 -2
  343. package/dist/tools/query-shape.d.ts +18 -0
  344. package/dist/tools/query-shape.js +39 -0
  345. package/dist/tools/read-tools.js +81 -20
  346. package/dist/tools/real-input-attempt.d.ts +28 -0
  347. package/dist/tools/real-input-attempt.js +88 -0
  348. package/dist/tools/reconcile-tools.js +7 -7
  349. package/dist/tools/reload-result.d.ts +14 -0
  350. package/dist/tools/reload-result.js +29 -0
  351. package/dist/tools/snapshot-delta.d.ts +4 -5
  352. package/dist/tools/snapshot-delta.js +0 -0
  353. package/dist/tools/tool-kit.d.ts +8 -0
  354. package/dist/tools/tool-surface.d.ts +83 -0
  355. package/dist/tools/tool-surface.js +204 -0
  356. package/dist/tools/tools-helpers.js +6 -6
  357. package/dist/tools/tools.d.ts +15 -0
  358. package/dist/tools/tools.js +53 -17
  359. package/dist/tools/unadvertised-help.d.ts +5 -0
  360. package/dist/tools/unadvertised-help.js +39 -0
  361. package/dist/trace.d.ts +69 -0
  362. package/dist/trace.js +150 -0
  363. package/dist/update/nudge-credit.d.ts +4 -0
  364. package/dist/update/nudge-credit.js +53 -0
  365. package/dist/update/sdk-sync.d.ts +21 -0
  366. package/dist/update/sdk-sync.js +42 -0
  367. package/dist/update/update-checker.js +3 -3
  368. package/dist/update/update-nudge.d.ts +63 -6
  369. package/dist/update/update-nudge.js +128 -15
  370. package/dist/update/updater.d.ts +1 -9
  371. package/dist/update/updater.js +13 -8
  372. package/dist/version/peer-announce.d.ts +4 -0
  373. package/dist/version/peer-announce.js +30 -0
  374. package/dist/{server-version.js → version/server-version.js} +5 -1
  375. package/dist/version/version-nudge.d.ts +28 -0
  376. package/dist/version/version-nudge.js +40 -0
  377. package/dist/version/version-skew.d.ts +66 -0
  378. package/dist/version/version-skew.js +71 -0
  379. package/dist/visual/visual-diff.js +2 -2
  380. package/dist/visual/visual-tools.js +4 -4
  381. package/docs/agent-cheatsheet.md +85 -0
  382. package/docs/architecture.md +113 -0
  383. package/docs/benchmarks.md +255 -0
  384. package/docs/debugging.md +102 -0
  385. package/docs/deploy-checks.md +69 -0
  386. package/docs/desktop-apps.md +256 -0
  387. package/docs/enterprise.md +62 -0
  388. package/docs/fixtures.md +28 -0
  389. package/docs/flows.md +166 -0
  390. package/docs/getting-started.md +469 -0
  391. package/docs/human-control.md +63 -0
  392. package/docs/integration-patterns.md +171 -0
  393. package/docs/local-registry.md +126 -0
  394. package/docs/mint.json +61 -0
  395. package/docs/multi-agent-testing.md +55 -0
  396. package/docs/platform-integration.md +122 -0
  397. package/docs/telemetry-contract.md +214 -0
  398. package/docs/telemetry.md +184 -0
  399. package/docs/testing.md +82 -0
  400. package/docs/token-efficiency.md +80 -0
  401. package/docs/usage.md +1046 -0
  402. package/package.json +8 -5
  403. package/dist/cli-launch.js +0 -112
  404. package/dist/daemon-resilience.d.ts +0 -22
  405. package/dist/daemon-resilience.js +0 -85
  406. package/dist/mcp-proxy.d.ts +0 -67
  407. package/dist/mcp-proxy.js +0 -337
  408. package/dist/tools/profiles.d.ts +0 -37
  409. package/dist/tools/profiles.js +0 -139
  410. /package/dist/{pairing-token.d.ts → bridge/pairing-token.d.ts} +0 -0
  411. /package/dist/{token-auth.d.ts → bridge/token-auth.d.ts} +0 -0
  412. /package/dist/{token-auth.js → bridge/token-auth.js} +0 -0
  413. /package/dist/{cli-doctor.d.ts → cli/cli-doctor.d.ts} +0 -0
  414. /package/dist/{cli-verify.d.ts → cli/cli-verify.d.ts} +0 -0
  415. /package/dist/{cloud-cli.d.ts → cli/cloud-cli.d.ts} +0 -0
  416. /package/dist/{server-version.d.ts → version/server-version.d.ts} +0 -0
@@ -7,15 +7,16 @@
7
7
  * is `<repo>/.reticle/cloud.json`. Auth for a command = `RETICLE_CLOUD_KEY` env (agent) OR the login token.
8
8
  */
9
9
  import { mkdir, writeFile, readFile } from 'node:fs/promises';
10
+ import { NodePlatform } from '../platform.js';
10
11
  import { spawn } from 'node:child_process';
11
12
  import { setTimeout as sleep } from 'node:timers/promises';
12
13
  import { homedir } from 'node:os';
13
14
  import { join } from 'node:path';
14
15
  import { z } from 'zod';
15
- import { createNodeFileSystem } from './project/fs-port.js';
16
- import { RunStore } from './runs/run-store.js';
17
- import { resolveProjectCloud } from './cloud/cloud-config.js';
18
- import { syncRunToCloud, SyncOutcome } from './cloud/cloud-sync.js';
16
+ import { createNodeFileSystem } from '../project/fs-port.js';
17
+ import { RunStore } from '../runs/run-store.js';
18
+ import { resolveProjectCloud } from '../cloud/cloud-config.js';
19
+ import { syncRunToCloud, SyncOutcome } from '../cloud/cloud-sync.js';
19
20
  const DEFAULT_URL = 'http://localhost:8890';
20
21
  const RETICLE_DIR = '.reticle';
21
22
  const SESSION_FILE = 'session.json';
@@ -162,8 +163,12 @@ const DevicePollSchema = z.object({
162
163
  });
163
164
  /** Best-effort open the approval page in the default browser; the printed URL is the headless fallback. */
164
165
  const openBrowser = (target) => {
165
- const cmd = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'cmd' : 'xdg-open';
166
- const args = process.platform === 'win32' ? ['/c', 'start', '', target] : [target];
166
+ const cmd = NodePlatform.MACOS === process.platform
167
+ ? 'open'
168
+ : NodePlatform.WINDOWS === process.platform
169
+ ? 'cmd'
170
+ : 'xdg-open';
171
+ const args = NodePlatform.WINDOWS === process.platform ? ['/c', 'start', '', target] : [target];
167
172
  try {
168
173
  const child = spawn(cmd, args, { stdio: 'ignore', detached: true });
169
174
  child.on('error', () => undefined);
@@ -193,18 +198,18 @@ const cmdLoginDevice = async () => {
193
198
  for (;;) {
194
199
  await sleep(intervalMs);
195
200
  const poll = DevicePollSchema.parse(await api('POST', `${url}/v1/auth/device/token`, null, { deviceCode: started.deviceCode }));
196
- if (poll.status === 'approved' && poll.token !== undefined && poll.org !== undefined) {
201
+ if ('approved' === poll.status && poll.token !== undefined && poll.org !== undefined) {
197
202
  await writeSession(url, poll.token, poll.org.name);
198
203
  return 0;
199
204
  }
200
- if (poll.status === 'pending') {
205
+ if ('pending' === poll.status) {
201
206
  if (Date.now() > started.expiresAt) {
202
207
  err('device login expired — run `reticle login` again');
203
208
  return 1;
204
209
  }
205
210
  continue;
206
211
  }
207
- err(poll.status === 'denied'
212
+ err('denied' === poll.status
208
213
  ? 'device login was denied in the browser'
209
214
  : 'device login expired — run `reticle login` again');
210
215
  return 1;
@@ -264,7 +269,7 @@ const cmdWhoami = async () => {
264
269
  verify: cloud.verify,
265
270
  },
266
271
  });
267
- if (cloud.config === null)
272
+ if (null === cloud.config)
268
273
  hint('this repo is not attached — run `reticle link`');
269
274
  return 0;
270
275
  };
@@ -273,18 +278,18 @@ const cmdProject = async (argv) => {
273
278
  const session = await readSession();
274
279
  const token = bearer(session);
275
280
  const url = baseUrl(session);
276
- if (token === null) {
281
+ if (null === token) {
277
282
  err('run `reticle login` first, or set RETICLE_CLOUD_KEY');
278
283
  return 2;
279
284
  }
280
285
  const sub = argv[0];
281
- if (sub === 'ls') {
286
+ if ('ls' === sub) {
282
287
  emit(await api('GET', `${url}/v1/projects`, token));
283
288
  return 0;
284
289
  }
285
- if (sub === 'create') {
290
+ if ('create' === sub) {
286
291
  const name = argv.slice(1).join(' ').trim();
287
- if (name.length === 0) {
292
+ if (0 === name.length) {
288
293
  err('usage: reticle project create <name>');
289
294
  return 2;
290
295
  }
@@ -293,17 +298,17 @@ const cmdProject = async (argv) => {
293
298
  hint(`next: \`reticle link --project ${created.projectId}\` to bind this repo`);
294
299
  return 0;
295
300
  }
296
- if (sub === 'rename') {
301
+ if ('rename' === sub) {
297
302
  const id = argv[1];
298
303
  const name = argv.slice(2).join(' ').trim();
299
- if (id === undefined || name.length === 0) {
304
+ if (id === undefined || 0 === name.length) {
300
305
  err('usage: reticle project rename <projectId> <new name>');
301
306
  return 2;
302
307
  }
303
308
  emit(await api('PATCH', `${url}/v1/projects/${encodeURIComponent(id)}`, token, { name }));
304
309
  return 0;
305
310
  }
306
- if (sub === 'rm' || sub === 'delete') {
311
+ if ('rm' === sub || 'delete' === sub) {
307
312
  const id = argv[1];
308
313
  if (id === undefined) {
309
314
  err('usage: reticle project rm <projectId>');
@@ -357,7 +362,7 @@ const cmdLink = async (argv) => {
357
362
  await mkdir(reticleDir, { recursive: true });
358
363
  const linkPath = join(reticleDir, CLOUD_LINK_FILE);
359
364
  const prev = await readJson(linkPath);
360
- const prevObj = typeof prev === 'object' && prev !== null ? prev : {};
365
+ const prevObj = 'object' === typeof prev && prev !== null ? prev : {};
361
366
  const cloudJson = {
362
367
  projectId,
363
368
  projectName,
@@ -369,7 +374,7 @@ const cmdLink = async (argv) => {
369
374
  await mkdir(home(), { recursive: true });
370
375
  const credPath = join(home(), CREDENTIALS_FILE);
371
376
  const creds = (await readJson(credPath)) ?? {};
372
- const credObj = typeof creds === 'object' && creds !== null ? creds : {};
377
+ const credObj = 'object' === typeof creds && creds !== null ? creds : {};
373
378
  credObj[projectId] = key;
374
379
  await writeFile(credPath, `${JSON.stringify(credObj, null, 2)}\n`);
375
380
  emit({ linked: projectName, projectId, cloudJson: linkPath, credentials: credPath });
@@ -381,15 +386,15 @@ const cmdConfig = async (argv) => {
381
386
  const f = flags(argv);
382
387
  const linkPath = join(process.cwd(), RETICLE_DIR, CLOUD_LINK_FILE);
383
388
  const raw = await readJson(linkPath);
384
- if (raw === null || typeof raw !== 'object') {
389
+ if (null === raw || typeof raw !== 'object') {
385
390
  err('no .reticle/cloud.json here — run `reticle link` first');
386
391
  return 2;
387
392
  }
388
393
  const cfg = raw;
389
- const sync = typeof cfg['sync'] === 'object' && cfg['sync'] !== null
394
+ const sync = 'object' === typeof cfg['sync'] && cfg['sync'] !== null
390
395
  ? cfg['sync']
391
396
  : { runs: true, memory: true, flows: true };
392
- const onoff = (v) => v === 'on' ? true : v === 'off' ? false : undefined;
397
+ const onoff = (v) => 'on' === v ? true : 'off' === v ? false : undefined;
393
398
  for (const k of ['runs', 'memory', 'flows']) {
394
399
  if (f[k] === undefined)
395
400
  continue;
@@ -417,7 +422,7 @@ const cmdPush = async () => {
417
422
  const fs = createNodeFileSystem();
418
423
  const reticleRoot = join(process.cwd(), RETICLE_DIR);
419
424
  const cloud = await resolveProjectCloud(fs, reticleRoot, homedir(), process.env);
420
- if (cloud.config === null) {
425
+ if (null === cloud.config) {
421
426
  err('cloud not attached here — run `reticle link` (or set RETICLE_CLOUD_URL/KEY)');
422
427
  return 1;
423
428
  }
@@ -448,7 +453,7 @@ const cmdPush = async () => {
448
453
  const repoCloud = async () => {
449
454
  const fs = createNodeFileSystem();
450
455
  const cloud = await resolveProjectCloud(fs, join(process.cwd(), RETICLE_DIR), homedir(), process.env);
451
- if (cloud.config === null)
456
+ if (null === cloud.config)
452
457
  throw new Error('cloud not attached here — run `reticle link` (or set RETICLE_CLOUD_URL/KEY)');
453
458
  return cloud.config;
454
459
  };
package/dist/cli.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import { parseCliArgs, CLI_USAGE } from './cli-parse.js';
2
+ import { parseCliArgs, CLI_USAGE } from './cli/cli-parse.js';
3
3
  export { parseCliArgs, CLI_USAGE };
4
- export type { CliResult } from './cli-parse.js';
4
+ export type { CliResult } from './cli/cli-parse.js';
package/dist/cli.js CHANGED
@@ -2,30 +2,30 @@
2
2
  import { pathToFileURL } from 'node:url';
3
3
  import { realpathSync } from 'node:fs';
4
4
  import { join } from 'node:path';
5
- import { handleWatch, handleCapsules, handleGate, loadNamedFlows, resolveChangedFiles, } from './cli-flow-commands.js';
5
+ import { handleWatch, handleCapsules, handleGate, loadNamedFlows, resolveChangedFiles, } from './cli/cli-flow-commands.js';
6
6
  import { RETICLE_DEFAULT_PORT, ReticleDir, ReticleEnv } from '@reticlehq/core';
7
7
  import { loadDotEnv } from './telemetry/dev-repo.js';
8
8
  import { createNodeFileSystem } from './project/fs-port.js';
9
9
  import { affectedSavedFlows } from './flows/flow-sources.js';
10
- import { checkForUpdate } from './update/update-checker.js';
11
- import { applyUpdate, rollback } from './update/updater.js';
10
+ import { availableUpdate } from './update/update-nudge.js';
11
+ import { handleUpdate, handleRollback } from './cli/cli-update-commands.js';
12
12
  import { start, startDaemon } from './index.js';
13
- import { isCloudCommand, runCloudCommand } from './cloud-cli.js';
14
- import { SERVER_VERSION } from './server-version.js';
13
+ import { isCloudCommand, runCloudCommand } from './cli/cloud-cli.js';
14
+ import { SERVER_VERSION } from './version/server-version.js';
15
15
  import { log } from './log.js';
16
- import { readPid, isAlive, isRunning, removePid, spawnDaemon, discoverDaemonPort, writeDaemonRegistry, } from './daemon.js';
17
- import { waitForDaemon, startMcpProxy, probeDaemon } from './mcp-proxy.js';
18
- import { installDaemonResilience } from './daemon-resilience.js';
19
- import { IdleShutdown, resolveIdleShutdownMs } from './idle-shutdown.js';
20
- import { fetchStatus, summarizeStatus, decideOpen, openInBrowser } from './cli-launch.js';
21
- import { handleVerify } from './cli-verify.js';
16
+ import { readPid, isAlive, isRunning, removePid, spawnDaemon, discoverDaemonPort, writeDaemonRegistry, } from './daemon/daemon.js';
17
+ import { waitForDaemon, startMcpProxy, probeDaemon, proxyLog, setProxyLogPort, } from './mcp/mcp-proxy.js';
18
+ import { installDaemonResilience, installProxyResilience } from './daemon/daemon-resilience.js';
19
+ import { IdleShutdown, resolveIdleShutdownMs, resolveIdleCheckMs } from './daemon/idle-shutdown.js';
20
+ import { fetchStatus, summarizeStatus, warnOnDaemonSkew, decideOpen, openInBrowser, } from './cli/cli-launch.js';
21
+ import { handleVerify } from './cli/cli-verify.js';
22
22
  import { summarizeHunt } from './hunt/hunt-report.js';
23
23
  import { runInit } from './init/run.js';
24
- import { handleDoctor } from './cli-doctor.js';
24
+ import { handleDoctor } from './cli/cli-doctor.js';
25
25
  import { buildNodeIo } from './init/node-io.js';
26
26
  import { describeLicense } from './license/license.js';
27
- import { isLikelyDevServerPort, devServerPortWarning, readProjectPort, readProjectId } from './cli-port.js';
28
- import { DAEMON_INNER_COMMAND, PORT_FLAG, DRIVE_FLAG, HEADED_FLAG, HTTP_FLAG, HTTP_PORT_FLAG, HTTP_TOKEN_FLAG, parseCliArgs, CLI_USAGE, } from './cli-parse.js';
27
+ import { isLikelyDevServerPort, devServerPortWarning, readProjectPort, readProjectId, } from './cli/cli-port.js';
28
+ 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';
29
29
  import { handleFeedback, handleIdentify, handleTelemetry } from './telemetry/feedback-cli.js';
30
30
  import { installDaemonTelemetry } from './telemetry/daemon-telemetry.js';
31
31
  import { reportCliRun } from './telemetry/cli-telemetry.js';
@@ -33,7 +33,14 @@ import { reportCliRun } from './telemetry/cli-telemetry.js';
33
33
  export { parseCliArgs, CLI_USAGE };
34
34
  function handleInit(parsed) {
35
35
  const cwd = process.cwd();
36
- const result = runInit({ cwd, port: parsed.port, mcp: parsed.mcp, dryRun: parsed.dryRun, install: parsed.install }, buildNodeIo(cwd));
36
+ const result = runInit({
37
+ cwd,
38
+ port: parsed.port,
39
+ mcp: parsed.mcp,
40
+ dryRun: parsed.dryRun,
41
+ install: parsed.install,
42
+ ...(parsed.app === undefined ? {} : { app: parsed.app }),
43
+ }, buildNodeIo(cwd));
37
44
  if (!result.ok)
38
45
  process.exit(1);
39
46
  }
@@ -66,7 +73,7 @@ function handleServe(parsed) {
66
73
  }
67
74
  function handleStop(port, quiet) {
68
75
  const pid = readPid(port);
69
- if (pid === null || !isAlive(pid)) {
76
+ if (null === pid || !isAlive(pid)) {
70
77
  removePid(port);
71
78
  if (!quiet)
72
79
  log('reticle_daemon_not_running', { port });
@@ -92,17 +99,22 @@ function handleStop(port, quiet) {
92
99
  }
93
100
  function handleStatus(port) {
94
101
  const pid = readPid(port);
95
- if (pid === null || !isAlive(pid)) {
102
+ if (null === pid || !isAlive(pid)) {
96
103
  log('reticle_status', { port, running: false });
97
104
  return;
98
105
  }
99
106
  // The daemon is up — ask it for live sessions + health so status is at-a-glance, not just a pid.
100
107
  void fetchStatus(port).then((payload) => {
108
+ // `status` is the second-most-run command and the one a HUMAN types. The update nudge otherwise
109
+ // only rides a tool result, which the majority of daemons never produce — so the people most in
110
+ // need of an upgrade were the ones with no path to hearing about it.
111
+ const update = availableUpdate();
112
+ const nudge = update === undefined ? {} : { updateAvailable: update };
101
113
  if (payload === undefined) {
102
- log('reticle_status', { port, running: true, pid });
114
+ log('reticle_status', { port, running: true, pid, ...nudge });
103
115
  return;
104
116
  }
105
- log('reticle_status', { port, running: true, pid, ...summarizeStatus(payload) });
117
+ log('reticle_status', { port, running: true, pid, ...summarizeStatus(payload), ...nudge });
106
118
  });
107
119
  }
108
120
  /** Print the running package version (resolved once in server-version.ts). */
@@ -172,43 +184,15 @@ async function handleHunt(dir) {
172
184
  });
173
185
  }
174
186
  }
175
- /** `reticle update` — install the latest server version and restart (moved off the MCP surface). */
176
- async function handleUpdate() {
177
- try {
178
- const manifest = await checkForUpdate(SERVER_VERSION, () => Date.now());
179
- if (!manifest.updateAvailable || manifest.latestVersion === undefined) {
180
- log('reticle_update', {
181
- ok: false,
182
- message: 'already on the latest version',
183
- version: SERVER_VERSION,
184
- });
185
- return;
186
- }
187
- log('reticle_update', { ok: true, from: SERVER_VERSION, to: manifest.latestVersion });
188
- await applyUpdate(manifest.latestVersion); // calls process.exit; Claude Code restarts
189
- }
190
- catch (error) {
191
- log('reticle_update_failed', { error: error instanceof Error ? error.message : String(error) });
192
- process.exitCode = 1;
193
- }
194
- }
195
- /** `reticle rollback` — restore the previous server version and restart. */
196
- async function handleRollback() {
197
- try {
198
- await rollback(); // calls process.exit
199
- }
200
- catch (error) {
201
- log('reticle_rollback_failed', {
202
- error: error instanceof Error ? error.message : String(error),
203
- });
204
- process.exitCode = 1;
205
- }
206
- }
207
187
  /** Ensure a daemon is reachable on `port` (probe the real port; spawn + wait only if nothing's there). */
208
188
  function ensureDaemon(port) {
209
- return probeDaemon(port).then((listening) => {
189
+ return probeDaemon(port).then(async (listening) => {
190
+ // Attaching to whatever already owns the port is the whole point of a daemon — but it means an
191
+ // upgrade does NOT take effect until that daemon dies, and nothing used to say so. Say it here,
192
+ // where both versions are in hand, and keep attaching: killing another agent's daemon on a
193
+ // version bump is worse than a loud warning.
210
194
  if (listening)
211
- return undefined;
195
+ return warnOnDaemonSkew(port);
212
196
  const scriptPath = process.argv[1];
213
197
  if (scriptPath === undefined)
214
198
  throw new Error('cannot locate the reticle daemon script');
@@ -222,6 +206,24 @@ function ensureDaemon(port) {
222
206
  * the daemon, then reuses the already-connected tab or opens a new browser at the url. Idempotent:
223
207
  * re-running never piles up duplicate tabs.
224
208
  */
209
+ /** How long `reticle open` waits for the launched page to register before reporting on it. */
210
+ const OPEN_SESSION_WAIT_MS = 8000;
211
+ /**
212
+ * Poll until a NEW session shows up, or the wait runs out. Returns whether one did.
213
+ *
214
+ * Compares against the count taken before launching rather than against zero, so opening a second
215
+ * app while one is already connected is not reported as an instant success.
216
+ */
217
+ async function waitForNewSession(port, before) {
218
+ const deadline = Date.now() + OPEN_SESSION_WAIT_MS;
219
+ while (Date.now() < deadline) {
220
+ await new Promise((resolve) => setTimeout(resolve, 400));
221
+ const { sessions } = summarizeStatus(await fetchStatus(port));
222
+ if (sessions.length > before)
223
+ return true;
224
+ }
225
+ return false;
226
+ }
225
227
  function handleOpen(requestedPort, url) {
226
228
  probeDaemon(requestedPort)
227
229
  .then((here) => (here ? requestedPort : (discoverDaemonPort() ?? requestedPort)))
@@ -229,7 +231,7 @@ function handleOpen(requestedPort, url) {
229
231
  await ensureDaemon(port);
230
232
  const { sessions } = summarizeStatus(await fetchStatus(port));
231
233
  const decision = decideOpen(sessions, url);
232
- if (decision.action === 'need-url') {
234
+ if ('need-url' === decision.action) {
233
235
  log('reticle_open', {
234
236
  port,
235
237
  error: 'no app connected — pass a url: reticle open <url>. Desktop app (Electron/Tauri)? ' +
@@ -237,12 +239,53 @@ function handleOpen(requestedPort, url) {
237
239
  });
238
240
  return;
239
241
  }
240
- if (decision.action === 'reuse') {
242
+ if ('reuse' === decision.action) {
241
243
  log('reticle_open', { port, reusing: decision.url });
242
244
  return;
243
245
  }
244
- openInBrowser(decision.url);
245
- log('reticle_open', { port, opened: decision.url });
246
+ // A tab on the right origin, on the WRONG page. Kept (that is what stops a tab piling up per
247
+ // run) but never reported as done: `reusing` here read as "your url is open" for a page nobody
248
+ // had opened.
249
+ if ('left-as-is' === decision.action) {
250
+ log('reticle_open', {
251
+ port,
252
+ reusing: decision.url,
253
+ requested: decision.requested,
254
+ note: `a tab is connected on this origin but sitting on ${decision.url} — it was LEFT THERE, ` +
255
+ `not navigated to ${decision.requested}. Drive it with reticle_navigate, or open the url ` +
256
+ 'in the browser yourself.',
257
+ });
258
+ return;
259
+ }
260
+ const launchError = await openInBrowser(decision.url);
261
+ if (launchError !== null) {
262
+ log('reticle_open', {
263
+ port,
264
+ error: `could not launch a browser: ${launchError}`,
265
+ recovery: 'Open the url yourself, or run `reticle doctor` — it checks the pieces this command ' +
266
+ 'depends on.',
267
+ });
268
+ process.exit(1);
269
+ return;
270
+ }
271
+ // Say whether a SESSION appeared, not merely that a launcher was invoked. `{"opened": url}`
272
+ // was printed unconditionally, so a run where nothing ever opened looked identical to a run
273
+ // where it did — reported from the field as twenty minutes lost to a phantom port problem.
274
+ const connected = await waitForNewSession(port, sessions.length);
275
+ log('reticle_open', {
276
+ port,
277
+ opened: decision.url,
278
+ connected,
279
+ ...(connected
280
+ ? {}
281
+ : {
282
+ note: 'the browser was launched but no Reticle session appeared. The app may still be ' +
283
+ 'loading, or it is not wired to this bridge — run `reticle doctor`, and check the ' +
284
+ 'app connects on port ' +
285
+ String(port) +
286
+ '.',
287
+ }),
288
+ });
246
289
  })
247
290
  .catch((err) => {
248
291
  log('reticle_open', { error: err instanceof Error ? err.message : String(err) });
@@ -306,9 +349,18 @@ function handleDaemonInner(parsed) {
306
349
  process.on('SIGINT', shutdown);
307
350
  // Self-shut-down when idle so a detached daemon (and any headless Chromium it launched) never
308
351
  // lingers on the user's machine after the editor closes. Reuses the same clean shutdown path.
352
+ const attachedGraceEnv = process.env[ReticleEnv.IDLE_ATTACHED];
309
353
  const idleShutdown = new IdleShutdown({
310
354
  graceMs: resolveIdleShutdownMs(process.env[ReticleEnv.IDLE_SHUTDOWN]),
355
+ checkIntervalMs: resolveIdleCheckMs(process.env[ReticleEnv.IDLE_CHECK]),
311
356
  isIdle: server.isIdle ?? (() => false),
357
+ ...(server.agentAttached === undefined ? {} : { agentAttached: server.agentAttached }),
358
+ // Only when the var is actually SET. `resolveIdleShutdownMs(undefined)` returns the 5-minute
359
+ // DEFAULT, so passing it unconditionally would override the derived attached grace with the
360
+ // very number this change exists to stop using — shipping the bug while looking fixed.
361
+ ...(attachedGraceEnv === undefined || '' === attachedGraceEnv.trim()
362
+ ? {}
363
+ : { attachedGraceMs: resolveIdleShutdownMs(attachedGraceEnv) }),
312
364
  onShutdown: () => {
313
365
  log('reticle_daemon_idle_exit', { port: parsed.port });
314
366
  shutdown();
@@ -333,43 +385,69 @@ function handleDaemonInner(parsed) {
333
385
  */
334
386
  function handleMcp(opts) {
335
387
  const { port, driveUrl, headless, http, httpPort, httpToken } = opts;
336
- // Probe the port first — a daemon with a stale PID file is still usable.
337
- // Only spawn when nothing is actually listening on the port.
338
- probeDaemon(port)
339
- .then((listening) => {
340
- if (!listening) {
341
- const scriptPath = process.argv[1];
342
- if (scriptPath === undefined) {
343
- log('reticle_mcp_no_script', {});
344
- process.exit(1);
345
- return;
346
- }
347
- const daemonArgs = [DAEMON_INNER_COMMAND, PORT_FLAG, String(port)];
348
- if (driveUrl !== undefined) {
349
- daemonArgs.push(DRIVE_FLAG, driveUrl);
350
- if (!headless)
351
- daemonArgs.push(HEADED_FLAG);
352
- }
353
- // Forward the HTTP-verify flags too (previously silently dropped for `reticle mcp`).
354
- if (http) {
355
- daemonArgs.push(HTTP_FLAG);
356
- if (httpPort !== undefined)
357
- daemonArgs.push(HTTP_PORT_FLAG, String(httpPort));
358
- if (httpToken !== undefined)
359
- daemonArgs.push(HTTP_TOKEN_FLAG, httpToken);
360
- }
361
- spawnDaemon(process.execPath, scriptPath, daemonArgs, port);
362
- log('reticle_mcp_daemon_started', {
363
- port,
364
- ...(driveUrl !== undefined ? { driveUrl } : {}),
365
- });
388
+ // The proxy IS the MCP server the editor launched. Nothing respawns it, so an uncaught throw here
389
+ // is what a user experiences as "the MCP server disconnected — open /mcp and reconnect". Log it,
390
+ // report it, keep serving: the reconnect and dormant paths already know how to rebuild the only
391
+ // state this process has. See installProxyResilience for why its rule inverts the daemon's.
392
+ // The proxy's crash handlers must write to the proxy's LOG FILE, not just stderr. The editor
393
+ // swallows stderr, so wiring these to `log` meant a crash was handled and then unrecorded — the
394
+ // failure a user reports as "it disconnected" left nothing behind to read.
395
+ setProxyLogPort(port);
396
+ installProxyResilience(process, proxyLog);
397
+ /**
398
+ * Make sure a daemon is on the port, spawning one if not.
399
+ *
400
+ * Called on first start AND on every reconnect. The proxy used to only retry the socket, so a
401
+ * daemon that exited — crashed, `reticle stop`, or self-shut-down as idle — meant the retries hit
402
+ * a dead port until the budget ran out and the agent's MCP server exited with it. Reticle simply
403
+ * disappeared mid-session with nothing said. Respawning here makes the reconnect self-healing.
404
+ */
405
+ const ensure = async () => {
406
+ if (await probeDaemon(port))
407
+ return;
408
+ const scriptPath = process.argv[1];
409
+ if (scriptPath === undefined) {
410
+ log('reticle_mcp_no_script', {});
411
+ process.exit(1);
366
412
  }
367
- return waitForDaemon(port).then(() => startMcpProxy(port));
368
- })
413
+ const daemonArgs = [DAEMON_INNER_COMMAND, PORT_FLAG, String(port)];
414
+ if (driveUrl !== undefined) {
415
+ daemonArgs.push(DRIVE_FLAG, driveUrl);
416
+ if (!headless)
417
+ daemonArgs.push(HEADED_FLAG);
418
+ }
419
+ // Forward the HTTP-verify flags too (previously silently dropped for `reticle mcp`).
420
+ if (http) {
421
+ daemonArgs.push(HTTP_FLAG);
422
+ if (httpPort !== undefined)
423
+ daemonArgs.push(HTTP_PORT_FLAG, String(httpPort));
424
+ if (httpToken !== undefined)
425
+ daemonArgs.push(HTTP_TOKEN_FLAG, httpToken);
426
+ }
427
+ spawnDaemon(process.execPath, scriptPath, daemonArgs, port);
428
+ log('reticle_mcp_daemon_started', { port, ...(driveUrl !== undefined ? { driveUrl } : {}) });
429
+ await waitForDaemon(port);
430
+ };
431
+ // Start the proxy WHATEVER happened to the daemon.
432
+ //
433
+ // This used to exit(1) when `ensure` failed, which is the third way an MCP server disappears on a
434
+ // user: something else is holding the bridge port — a foreign daemon from another project, a
435
+ // half-dead process, a port a colleague's tool grabbed — the spawned daemon cannot bind, and the
436
+ // editor shows a server that failed to start. Nothing about that is unrecoverable: the proxy
437
+ // answers `initialize` itself, serves the cached catalog, and its wake path retries a daemon on
438
+ // every client request. Present-and-complaining beats absent, because absent needs a human.
439
+ // `void`: the chain handles its own failure and the process must not wait on it — the proxy is
440
+ // started from `finally` either way.
441
+ void ensure()
369
442
  .catch((err) => {
370
- const message = err instanceof Error ? err.message : String(err);
371
- log('reticle_mcp_proxy_error', { error: message });
372
- process.exit(1);
443
+ log('reticle_mcp_daemon_unavailable', {
444
+ error: err instanceof Error ? err.message : String(err),
445
+ note: 'serving anyway — the next tool call will try to start a daemon again',
446
+ });
447
+ })
448
+ .finally(() => {
449
+ // Also `void`: the proxy runs for the life of the process and is never awaited by anyone.
450
+ void startMcpProxy(port, ensure);
373
451
  });
374
452
  }
375
453
  function handleLegacyDrive(parsed) {
@@ -424,7 +502,11 @@ function main() {
424
502
  const parsed = parseCliArgs(argv, defaultPort, process.env['CI'] !== undefined);
425
503
  switch (parsed.kind) {
426
504
  case 'error':
505
+ // Two audiences, two channels. The structured line is for logs and scripts; the plain text is
506
+ // for the person who just typed the command. Emitting only the JSON meant a single typo came
507
+ // back as an escaped one-line wall of the entire help text — see the ParseError builders.
427
508
  log('reticle_usage_error', { message: parsed.message });
509
+ process.stderr.write(parsed.message === CLI_USAGE ? `${CLI_USAGE}\n` : `${parsed.message}\n\n${CLI_USAGE}\n`);
428
510
  process.exit(1);
429
511
  break;
430
512
  case 'init':
@@ -13,7 +13,6 @@ export interface SyncPolicy {
13
13
  /** Sync saved flow files (the shared regression suite). */
14
14
  flows: boolean;
15
15
  }
16
- export declare const DEFAULT_SYNC_POLICY: SyncPolicy;
17
16
  /** Where a verification actually executes. Reserved for the hosted-runner path; default local. */
18
17
  export declare const VerifyMode: {
19
18
  readonly LOCAL: "local";
@@ -16,10 +16,10 @@ import { resolveCloudConfig } from './cloud-sync.js';
16
16
  export const CLOUD_LINK_FILE = 'cloud.json';
17
17
  /** `~/.reticle/credentials.json` — the user's per-cloud-project API keys (the only secret). */
18
18
  export const CREDENTIALS_FILE = 'credentials.json';
19
- export const DEFAULT_SYNC_POLICY = { runs: true, memory: true, flows: true };
19
+ const DEFAULT_SYNC_POLICY = { runs: true, memory: true, flows: true };
20
20
  /** Where a verification actually executes. Reserved for the hosted-runner path; default local. */
21
21
  export const VerifyMode = { LOCAL: 'local', SERVER: 'server' };
22
- const asBool = (v, fallback) => (typeof v === 'boolean' ? v : fallback);
22
+ const asBool = (v, fallback) => ('boolean' === typeof v ? v : fallback);
23
23
  /** Read + JSON-parse a file, or null on any problem (missing/malformed) — never throws. */
24
24
  async function readJson(fs, path) {
25
25
  try {
@@ -33,14 +33,14 @@ async function readJson(fs, path) {
33
33
  }
34
34
  /** Validate the shape of `.reticle/cloud.json`. Requires projectId + url; policy/verify default in. */
35
35
  function parseLink(raw) {
36
- if (typeof raw !== 'object' || raw === null)
36
+ if (typeof raw !== 'object' || null === raw)
37
37
  return null;
38
38
  const o = raw;
39
- if (typeof o.projectId !== 'string' || o.projectId.length === 0)
39
+ if (typeof o.projectId !== 'string' || 0 === o.projectId.length)
40
40
  return null;
41
- if (typeof o.url !== 'string' || o.url.length === 0)
41
+ if (typeof o.url !== 'string' || 0 === o.url.length)
42
42
  return null;
43
- const sync = typeof o.sync === 'object' && o.sync !== null ? o.sync : {};
43
+ const sync = 'object' === typeof o.sync && o.sync !== null ? o.sync : {};
44
44
  return {
45
45
  projectId: o.projectId,
46
46
  url: o.url,
@@ -54,10 +54,10 @@ function parseLink(raw) {
54
54
  }
55
55
  /** Look up the API key for a cloud project id in the user credentials map. */
56
56
  function credentialFor(raw, projectId) {
57
- if (typeof raw !== 'object' || raw === null)
57
+ if (typeof raw !== 'object' || null === raw)
58
58
  return null;
59
59
  const key = raw[projectId];
60
- return typeof key === 'string' && key.length > 0 ? key : null;
60
+ return 'string' === typeof key && key.length > 0 ? key : null;
61
61
  }
62
62
  /**
63
63
  * Resolve the cloud picture for a project rooted at `reticleRoot`. Reads the project's link file + the
@@ -66,7 +66,7 @@ function credentialFor(raw, projectId) {
66
66
  */
67
67
  export async function resolveProjectCloud(fs, reticleRoot, homeDir, env) {
68
68
  const link = parseLink(await readJson(fs, join(reticleRoot, CLOUD_LINK_FILE)));
69
- if (link === null) {
69
+ if (null === link) {
70
70
  // No per-project link → the env vars are the whole story (legacy single-project / CI behaviour).
71
71
  return {
72
72
  config: resolveCloudConfig(env),
@@ -15,12 +15,6 @@ export declare const CloudEnv: {
15
15
  readonly URL: "RETICLE_CLOUD_URL";
16
16
  readonly KEY: "RETICLE_CLOUD_KEY";
17
17
  };
18
- /** Paths the OSS server pushes to (match the cloud app's contract). */
19
- export declare const CLOUD_FLOWS_PATH = "/v1/flows";
20
- export declare const CLOUD_RUNS_PATH = "/v1/runs";
21
- export declare const CLOUD_PROJECT_RUNS_PATH = "/v1/project/runs";
22
- export declare const CLOUD_PROJECT_REGRESSION_PATH = "/v1/project/regression";
23
- export declare const CLOUD_VERIFICATIONS_PATH = "/v1/verifications";
24
18
  export interface CloudConfig {
25
19
  url: string;
26
20
  apiKey: string;
@@ -33,7 +27,7 @@ export declare const SyncOutcome: {
33
27
  readonly FAILED: "failed";
34
28
  };
35
29
  export type SyncOutcome = (typeof SyncOutcome)[keyof typeof SyncOutcome];
36
- export interface SyncResult {
30
+ interface SyncResult {
37
31
  outcome: SyncOutcome;
38
32
  status?: number;
39
33
  error?: string;