@reticlehq/server 2.11.0 → 2.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (513) hide show
  1. package/SKILL.md +93 -111
  2. package/dist/bridge/auth-failure-reason.d.ts +6 -2
  3. package/dist/bridge/auth-failure-reason.js +15 -8
  4. package/dist/bridge/bridge.d.ts +18 -14
  5. package/dist/bridge/bridge.js +248 -165
  6. package/dist/bridge/bridge.test-harness.d.ts +2 -0
  7. package/dist/bridge/bridge.test-harness.js +8 -1
  8. package/dist/bridge/pairing-token.d.ts +5 -0
  9. package/dist/bridge/pairing-token.js +28 -0
  10. package/dist/bridge/protocol-skew.d.ts +37 -0
  11. package/dist/bridge/protocol-skew.js +45 -0
  12. package/dist/capsule/capsule-store.d.ts +8 -7
  13. package/dist/capsule/capsule-store.js +1 -1
  14. package/dist/capsule/causal-summary.d.ts +2 -2
  15. package/dist/capsule/causal-summary.js +7 -2
  16. package/dist/chromium-launch-options.d.ts +28 -0
  17. package/dist/chromium-launch-options.js +29 -0
  18. package/dist/cli/cli-doctor.js +39 -1
  19. package/dist/cli/cli-flow-commands.d.ts +3 -1
  20. package/dist/cli/cli-flow-commands.js +34 -4
  21. package/dist/cli/cli-kill.d.ts +2 -1
  22. package/dist/cli/cli-launch.d.ts +11 -1
  23. package/dist/cli/cli-launch.js +59 -10
  24. package/dist/cli/cli-parse.d.ts +17 -1
  25. package/dist/cli/cli-parse.js +238 -12
  26. package/dist/cli/cli-update-commands.js +20 -1
  27. package/dist/cli/cli-verify.d.ts +2 -1
  28. package/dist/cli/cli-verify.js +5 -4
  29. package/dist/cli/cloud-cli.js +428 -253
  30. package/dist/cli/cloud-keystore.d.ts +37 -0
  31. package/dist/cli/cloud-keystore.js +97 -0
  32. package/dist/cli/cloud-kit.d.ts +37 -0
  33. package/dist/cli/cloud-kit.js +121 -0
  34. package/dist/cli/cloud-login.d.ts +17 -0
  35. package/dist/cli/cloud-login.js +183 -0
  36. package/dist/cli/cloud-session.d.ts +48 -0
  37. package/dist/cli/cloud-session.js +53 -0
  38. package/dist/cli/config-discovery.d.ts +2 -1
  39. package/dist/cli/config-discovery.js +1 -0
  40. package/dist/cli/daemon-start-options.d.ts +32 -0
  41. package/dist/cli/daemon-start-options.js +38 -0
  42. package/dist/cli/device-flow.d.ts +76 -0
  43. package/dist/cli/device-flow.js +56 -0
  44. package/dist/cli/doctor-rows.d.ts +12 -1
  45. package/dist/cli/doctor-rows.js +12 -1
  46. package/dist/cli/drive-attach.d.ts +13 -0
  47. package/dist/cli/drive-attach.js +18 -0
  48. package/dist/cli/drive-command.js +7 -3
  49. package/dist/cli/gate-exit.d.ts +21 -0
  50. package/dist/cli/gate-exit.js +21 -0
  51. package/dist/cli/gate-hook-message.d.ts +33 -0
  52. package/dist/cli/gate-hook-message.js +46 -0
  53. package/dist/cli/mcp-command.d.ts +25 -0
  54. package/dist/cli/mcp-command.js +174 -0
  55. package/dist/cli/project-name.d.ts +30 -0
  56. package/dist/cli/project-name.js +44 -0
  57. package/dist/cli/sibling-ports.d.ts +37 -0
  58. package/dist/cli/sibling-ports.js +56 -0
  59. package/dist/cli/status-next-action.d.ts +14 -0
  60. package/dist/cli/status-next-action.js +4 -4
  61. package/dist/cli.d.ts +1 -0
  62. package/dist/cli.js +167 -186
  63. package/dist/cloud/cloud-config.d.ts +24 -2
  64. package/dist/cloud/cloud-config.js +64 -5
  65. package/dist/cloud/cloud-sync.d.ts +16 -5
  66. package/dist/cloud/cloud-sync.js +64 -1
  67. package/dist/cloud/sync-cycle.d.ts +110 -0
  68. package/dist/cloud/sync-cycle.js +270 -0
  69. package/dist/cloud/sync-daemon.d.ts +69 -0
  70. package/dist/cloud/sync-daemon.js +260 -0
  71. package/dist/cloud/sync-disk.d.ts +9 -0
  72. package/dist/cloud/sync-disk.js +191 -0
  73. package/dist/cloud/sync-hash.d.ts +2 -0
  74. package/dist/cloud/sync-hash.js +35 -0
  75. package/dist/crawl/crawl-tools.js +5 -3
  76. package/dist/crawl/crawl.d.ts +12 -13
  77. package/dist/crawl/crawl.js +25 -3
  78. package/dist/daemon/daemon-resolve.d.ts +122 -0
  79. package/dist/daemon/daemon-resolve.js +187 -0
  80. package/dist/daemon/dev-servers.d.ts +15 -0
  81. package/dist/daemon/dev-servers.js +68 -0
  82. package/dist/daemon/free-port.d.ts +29 -0
  83. package/dist/daemon/free-port.js +55 -0
  84. package/dist/daemon/heartbeat.d.ts +0 -4
  85. package/dist/daemon/heartbeat.js +2 -2
  86. package/dist/daemon/wake-decision.d.ts +32 -0
  87. package/dist/daemon/wake-decision.js +38 -0
  88. package/dist/dev/stale-issue-guard.d.ts +0 -2
  89. package/dist/dev/stale-issue-guard.js +1 -1
  90. package/dist/domain/domain-tools.js +2 -2
  91. package/dist/events/contradictions.d.ts +59 -0
  92. package/dist/events/contradictions.js +253 -27
  93. package/dist/events/declared.d.ts +17 -0
  94. package/dist/events/declared.js +43 -1
  95. package/dist/events/echo-mismatch.js +65 -1
  96. package/dist/events/event-filters.d.ts +8 -0
  97. package/dist/events/event-filters.js +19 -2
  98. package/dist/events/observed-in-window.d.ts +12 -0
  99. package/dist/events/observed-in-window.js +17 -0
  100. package/dist/events/predicate-asks.d.ts +13 -0
  101. package/dist/events/predicate-asks.js +22 -0
  102. package/dist/events/predicate-eval.js +117 -27
  103. package/dist/events/predicate-parse.d.ts +0 -6
  104. package/dist/events/predicate-parse.js +43 -3
  105. package/dist/events/predicate-precheck.d.ts +8 -0
  106. package/dist/events/predicate-precheck.js +41 -0
  107. package/dist/events/predicate-route.d.ts +14 -0
  108. package/dist/events/predicate-route.js +36 -8
  109. package/dist/events/predicate-schema.d.ts +44 -0
  110. package/dist/events/predicate-schema.js +126 -23
  111. package/dist/events/predicate.d.ts +20 -0
  112. package/dist/events/predicate.js +77 -6
  113. package/dist/events/split-text-miss.d.ts +30 -0
  114. package/dist/events/split-text-miss.js +44 -0
  115. package/dist/flows/coverage.d.ts +0 -8
  116. package/dist/flows/coverage.js +1 -1
  117. package/dist/flows/decision.d.ts +5 -1
  118. package/dist/flows/decision.js +33 -7
  119. package/dist/flows/flake.d.ts +6 -8
  120. package/dist/flows/flake.js +1 -1
  121. package/dist/flows/flow-intent.d.ts +52 -0
  122. package/dist/flows/flow-intent.js +123 -0
  123. package/dist/flows/flow-memory-consult.d.ts +55 -0
  124. package/dist/flows/flow-memory-consult.js +38 -0
  125. package/dist/flows/flow-replay-run.d.ts +18 -7
  126. package/dist/flows/flow-replay-run.js +263 -23
  127. package/dist/flows/flow-replay.js +11 -2
  128. package/dist/flows/flow-step-runners.d.ts +8 -0
  129. package/dist/flows/flow-step-runners.js +8 -2
  130. package/dist/flows/flow-store-for-session.d.ts +31 -0
  131. package/dist/flows/flow-store-for-session.js +33 -0
  132. package/dist/flows/flow-success.js +15 -3
  133. package/dist/flows/flow-tools.js +87 -33
  134. package/dist/flows/flows.d.ts +14 -1
  135. package/dist/flows/flows.js +502 -24
  136. package/dist/flows/heal-run.js +5 -2
  137. package/dist/flows/predicate-to-expect.d.ts +17 -12
  138. package/dist/flows/predicate-to-expect.js +36 -15
  139. package/dist/flows/recordings.d.ts +13 -1
  140. package/dist/flows/recordings.js +17 -2
  141. package/dist/flows/replay.d.ts +22 -2
  142. package/dist/flows/replay.js +28 -2
  143. package/dist/flows/server-verify.js +16 -2
  144. package/dist/flows/verify-change-tools.js +9 -3
  145. package/dist/honesty/blind-spots.d.ts +51 -15
  146. package/dist/honesty/blind-spots.js +64 -4
  147. package/dist/honesty/feature-capture.d.ts +150 -0
  148. package/dist/honesty/feature-capture.js +233 -0
  149. package/dist/honesty/honesty.d.ts +24 -0
  150. package/dist/honesty/honesty.js +25 -1
  151. package/dist/honesty/instrumentation-gaps.d.ts +39 -6
  152. package/dist/honesty/instrumentation-gaps.js +88 -5
  153. package/dist/honesty/tool-hit-rate.d.ts +86 -0
  154. package/dist/honesty/tool-hit-rate.js +90 -0
  155. package/dist/honesty/undeclared-change.d.ts +51 -0
  156. package/dist/honesty/undeclared-change.js +56 -0
  157. package/dist/honesty/verified.d.ts +16 -0
  158. package/dist/honesty/verified.js +84 -5
  159. package/dist/http-server.d.ts +0 -1
  160. package/dist/http-server.js +13 -1
  161. package/dist/hunt/hunt-report.d.ts +0 -2
  162. package/dist/hunt/hunt-report.js +1 -1
  163. package/dist/impact/impact-recorder.d.ts +31 -8
  164. package/dist/impact/impact-recorder.js +104 -21
  165. package/dist/impact/impact-store.d.ts +13 -4
  166. package/dist/impact/impact-store.js +44 -3
  167. package/dist/index.d.ts +4 -1
  168. package/dist/index.js +113 -7
  169. package/dist/init/agent-rules.d.ts +30 -6
  170. package/dist/init/agent-rules.js +54 -13
  171. package/dist/init/astro-patch.d.ts +16 -0
  172. package/dist/init/astro-patch.js +28 -0
  173. package/dist/init/capabilities.d.ts +4 -3
  174. package/dist/init/capabilities.js +23 -3
  175. package/dist/init/closing-hint.d.ts +36 -0
  176. package/dist/init/closing-hint.js +120 -0
  177. package/dist/init/confirm.d.ts +27 -28
  178. package/dist/init/confirm.js +51 -11
  179. package/dist/init/cra.d.ts +34 -5
  180. package/dist/init/cra.js +96 -25
  181. package/dist/init/csp-check.js +17 -2
  182. package/dist/init/csp-doctor.d.ts +8 -1
  183. package/dist/init/csp-doctor.js +8 -3
  184. package/dist/init/dev-script.d.ts +50 -0
  185. package/dist/init/dev-script.js +66 -0
  186. package/dist/init/format-generated.d.ts +18 -0
  187. package/dist/init/format-generated.js +39 -0
  188. package/dist/init/install-hint.d.ts +9 -0
  189. package/dist/init/install-hint.js +29 -0
  190. package/dist/init/mcp-clients.d.ts +7 -0
  191. package/dist/init/mcp-clients.js +7 -0
  192. package/dist/init/mcp.d.ts +17 -6
  193. package/dist/init/mcp.js +27 -11
  194. package/dist/init/node-io.js +11 -1
  195. package/dist/init/plan-framework.d.ts +0 -5
  196. package/dist/init/plan-framework.js +147 -37
  197. package/dist/init/plan.d.ts +30 -37
  198. package/dist/init/plan.js +50 -60
  199. package/dist/init/preflight.d.ts +28 -0
  200. package/dist/init/preflight.js +38 -0
  201. package/dist/init/refresh-rules.d.ts +39 -0
  202. package/dist/init/refresh-rules.js +51 -0
  203. package/dist/init/run.d.ts +32 -35
  204. package/dist/init/run.js +151 -202
  205. package/dist/init/slash-command.d.ts +1 -1
  206. package/dist/init/snippets.js +90 -31
  207. package/dist/init/vite-config.d.ts +1 -1
  208. package/dist/init/vite-config.js +39 -25
  209. package/dist/init/workspace-apps.d.ts +17 -10
  210. package/dist/init/workspace-apps.js +110 -0
  211. package/dist/input/network-mock-tools.js +3 -6
  212. package/dist/input/real-input.d.ts +2 -0
  213. package/dist/input/real-input.js +28 -7
  214. package/dist/input/scroll-find.d.ts +7 -4
  215. package/dist/input/scroll-find.js +63 -25
  216. package/dist/input/scroll-tools.js +4 -6
  217. package/dist/input/viewport-tools.js +5 -4
  218. package/dist/intent/inline-intent.d.ts +37 -0
  219. package/dist/intent/inline-intent.js +193 -0
  220. package/dist/intent/intent-shard-store.d.ts +72 -0
  221. package/dist/intent/intent-shard-store.js +203 -0
  222. package/dist/intent/intent-shard.d.ts +411 -0
  223. package/dist/intent/intent-shard.js +176 -0
  224. package/dist/intent/intent-store.d.ts +49 -0
  225. package/dist/intent/intent-store.js +133 -0
  226. package/dist/intent/intent-subject.d.ts +45 -0
  227. package/dist/intent/intent-subject.js +177 -0
  228. package/dist/intent/intent-tools.d.ts +2 -0
  229. package/dist/intent/intent-tools.js +143 -0
  230. package/dist/intent/open-intents.d.ts +42 -0
  231. package/dist/intent/open-intents.js +40 -0
  232. package/dist/journal/envelope.d.ts +26 -26
  233. package/dist/journal/journal-recorder.d.ts +23 -0
  234. package/dist/journal/journal-recorder.js +35 -0
  235. package/dist/license/license-env.d.ts +29 -0
  236. package/dist/license/license-env.js +148 -0
  237. package/dist/loopback-agent.d.ts +31 -0
  238. package/dist/loopback-agent.js +57 -0
  239. package/dist/mcp/attach-memory.d.ts +83 -0
  240. package/dist/mcp/attach-memory.js +185 -0
  241. package/dist/mcp/client-identity.d.ts +31 -0
  242. package/dist/mcp/client-identity.js +51 -0
  243. package/dist/mcp/mcp-post-transport.d.ts +26 -0
  244. package/dist/mcp/mcp-post-transport.js +109 -0
  245. package/dist/mcp/mcp-proxy.d.ts +2 -8
  246. package/dist/mcp/mcp-proxy.js +64 -62
  247. package/dist/mcp/mcp.d.ts +2 -0
  248. package/dist/mcp/mcp.js +52 -24
  249. package/dist/mcp/proxy-backoff.d.ts +11 -0
  250. package/dist/mcp/proxy-backoff.js +13 -0
  251. package/dist/mcp/proxy-daemon-probe.d.ts +1 -0
  252. package/dist/mcp/proxy-daemon-probe.js +7 -1
  253. package/dist/mcp/proxy-handshake.d.ts +23 -0
  254. package/dist/mcp/proxy-handshake.js +31 -0
  255. package/dist/mcp/server-instructions.js +45 -3
  256. package/dist/mcp/tool-catalog-cache.d.ts +16 -0
  257. package/dist/mcp/tool-catalog-cache.js +22 -0
  258. package/dist/memory/memory-tools.d.ts +2 -0
  259. package/dist/memory/memory-tools.js +82 -0
  260. package/dist/memory/project-memory.d.ts +34 -0
  261. package/dist/memory/project-memory.js +96 -0
  262. package/dist/oracles/flow-instrument-gaps.d.ts +4 -4
  263. package/dist/oracles/flow-instrument-gaps.js +4 -5
  264. package/dist/oracles/propose-consequences.js +12 -3
  265. package/dist/oracles/self-instrument.d.ts +9 -9
  266. package/dist/oracles/self-instrument.js +19 -13
  267. package/dist/pool/browser-pool.d.ts +75 -5
  268. package/dist/pool/browser-pool.js +144 -5
  269. package/dist/pool/playwright-launcher.js +12 -1
  270. package/dist/project/fs-port.d.ts +3 -0
  271. package/dist/project/fs-port.js +3 -2
  272. package/dist/project/memory-fs.js +6 -1
  273. package/dist/project/project-tools.js +6 -35
  274. package/dist/project/reticle-dir.d.ts +17 -2
  275. package/dist/project/reticle-dir.js +22 -4
  276. package/dist/runs/context-tools.d.ts +2 -0
  277. package/dist/runs/context-tools.js +89 -0
  278. package/dist/runs/run-context.d.ts +40 -0
  279. package/dist/runs/run-context.js +158 -0
  280. package/dist/runs/run-diff.d.ts +2 -1
  281. package/dist/runs/run-store.d.ts +10 -0
  282. package/dist/runs/run-store.js +10 -0
  283. package/dist/runs/verification-sync.d.ts +0 -11
  284. package/dist/runs/verification-sync.js +24 -3
  285. package/dist/runs/verify-server.d.ts +8 -1
  286. package/dist/runs/verify-server.js +14 -2
  287. package/dist/session/command-timeout.d.ts +5 -2
  288. package/dist/session/command-timeout.js +5 -4
  289. package/dist/session/dev-server-probe.d.ts +0 -8
  290. package/dist/session/dev-server-probe.js +1 -1
  291. package/dist/session/last-act.d.ts +7 -1
  292. package/dist/session/last-act.js +2 -2
  293. package/dist/session/lease-visibility.d.ts +67 -0
  294. package/dist/session/lease-visibility.js +76 -0
  295. package/dist/session/live-control-tools.js +1 -42
  296. package/dist/session/no-session-diagnosis.d.ts +33 -0
  297. package/dist/session/no-session-diagnosis.js +82 -24
  298. package/dist/session/no-session-next-action.d.ts +12 -0
  299. package/dist/session/no-session-next-action.js +77 -6
  300. package/dist/session/no-session-watch.d.ts +18 -6
  301. package/dist/session/no-session-watch.js +125 -75
  302. package/dist/session/output-budget.js +4 -1
  303. package/dist/session/pending-commands.d.ts +11 -1
  304. package/dist/session/pending-commands.js +12 -2
  305. package/dist/session/resolve-within.d.ts +34 -0
  306. package/dist/session/resolve-within.js +53 -0
  307. package/dist/session/session-health.d.ts +9 -0
  308. package/dist/session/session-health.js +23 -0
  309. package/dist/session/session-info.d.ts +18 -0
  310. package/dist/session/session-info.js +11 -0
  311. package/dist/session/session-manager.d.ts +14 -0
  312. package/dist/session/session-manager.js +111 -4
  313. package/dist/session/session-mirror.d.ts +16 -0
  314. package/dist/session/session-mirror.js +22 -0
  315. package/dist/session/session-recommendation.d.ts +19 -5
  316. package/dist/session/session-recommendation.js +25 -9
  317. package/dist/session/session-successor.d.ts +37 -0
  318. package/dist/session/session-successor.js +113 -0
  319. package/dist/session/session-tools.js +2 -2
  320. package/dist/session/session.d.ts +53 -3
  321. package/dist/session/session.js +162 -9
  322. package/dist/session/wall-clock.d.ts +12 -0
  323. package/dist/session/wall-clock.js +14 -0
  324. package/dist/setup/agent-configs.d.ts +96 -0
  325. package/dist/setup/agent-configs.js +333 -0
  326. package/dist/setup/agent-io.d.ts +9 -0
  327. package/dist/setup/agent-io.js +16 -0
  328. package/dist/setup/agent-writer.d.ts +51 -0
  329. package/dist/setup/agent-writer.js +115 -0
  330. package/dist/setup/approval-migration.d.ts +47 -0
  331. package/dist/setup/approval-migration.js +68 -0
  332. package/dist/setup/auto-approve.d.ts +85 -0
  333. package/dist/setup/auto-approve.js +150 -0
  334. package/dist/setup/bridge-port.d.ts +15 -0
  335. package/dist/setup/bridge-port.js +24 -0
  336. package/dist/setup/desktop-shape.d.ts +57 -0
  337. package/dist/setup/desktop-shape.js +63 -0
  338. package/dist/setup/dev-server-wait.d.ts +60 -0
  339. package/dist/setup/dev-server-wait.js +65 -0
  340. package/dist/setup/drive-agent.d.ts +71 -0
  341. package/dist/setup/drive-agent.js +171 -0
  342. package/dist/setup/drive-plan.d.ts +68 -0
  343. package/dist/setup/drive-plan.js +130 -0
  344. package/dist/setup/ensure-daemon.d.ts +29 -0
  345. package/dist/setup/ensure-daemon.js +58 -0
  346. package/dist/setup/init-runtime.d.ts +47 -0
  347. package/dist/setup/init-runtime.js +160 -0
  348. package/dist/setup/interrupt.d.ts +25 -0
  349. package/dist/setup/interrupt.js +40 -0
  350. package/dist/setup/license-key.d.ts +39 -0
  351. package/dist/setup/license-key.js +72 -0
  352. package/dist/setup/listeners.d.ts +37 -0
  353. package/dist/setup/listeners.js +85 -0
  354. package/dist/setup/node-effects.d.ts +60 -0
  355. package/dist/setup/node-effects.js +210 -0
  356. package/dist/setup/page-probe.d.ts +39 -0
  357. package/dist/setup/page-probe.js +49 -0
  358. package/dist/setup/relaunch.d.ts +36 -0
  359. package/dist/setup/relaunch.js +57 -0
  360. package/dist/setup/remaining-steps.d.ts +19 -0
  361. package/dist/setup/remaining-steps.js +31 -0
  362. package/dist/setup/run-setup.d.ts +99 -0
  363. package/dist/setup/run-setup.js +187 -0
  364. package/dist/setup/session-pick.d.ts +47 -0
  365. package/dist/setup/session-pick.js +51 -0
  366. package/dist/setup/setup-command.d.ts +45 -0
  367. package/dist/setup/setup-command.js +199 -0
  368. package/dist/setup/setup-options.d.ts +74 -0
  369. package/dist/setup/setup-options.js +67 -0
  370. package/dist/setup/transcripts.d.ts +16 -0
  371. package/dist/setup/transcripts.js +43 -0
  372. package/dist/status-payload.d.ts +14 -1
  373. package/dist/status-payload.js +23 -1
  374. package/dist/telemetry/automation-hint.d.ts +2 -1
  375. package/dist/telemetry/daemon-telemetry.d.ts +8 -1
  376. package/dist/telemetry/daemon-telemetry.js +52 -5
  377. package/dist/telemetry/error-class.js +1 -1
  378. package/dist/telemetry/feedback-context.d.ts +9 -1
  379. package/dist/telemetry/feedback-context.js +60 -29
  380. package/dist/telemetry/feedback-local.d.ts +15 -0
  381. package/dist/telemetry/feedback-local.js +56 -0
  382. package/dist/telemetry/feedback-outbox.d.ts +2 -1
  383. package/dist/telemetry/feedback.d.ts +11 -0
  384. package/dist/telemetry/feedback.js +19 -6
  385. package/dist/telemetry/init-telemetry.d.ts +2 -0
  386. package/dist/telemetry/init-telemetry.js +2 -0
  387. package/dist/telemetry/install-source.d.ts +14 -0
  388. package/dist/telemetry/install-source.js +30 -0
  389. package/dist/telemetry/license-activation.d.ts +7 -1
  390. package/dist/telemetry/license-activation.js +9 -5
  391. package/dist/telemetry/mcp-connection.js +7 -0
  392. package/dist/telemetry/project-profile.d.ts +12 -9
  393. package/dist/telemetry/project-profile.js +12 -7
  394. package/dist/telemetry/proxy-telemetry.d.ts +3 -0
  395. package/dist/telemetry/proxy-telemetry.js +21 -0
  396. package/dist/telemetry/run-telemetry.d.ts +0 -2
  397. package/dist/telemetry/run-telemetry.js +1 -1
  398. package/dist/telemetry/session-metrics.d.ts +12 -0
  399. package/dist/telemetry/session-metrics.js +35 -1
  400. package/dist/telemetry/telemetry.js +17 -2
  401. package/dist/telemetry/tool-refused.d.ts +3 -1
  402. package/dist/telemetry/tool-refused.js +34 -2
  403. package/dist/telemetry/verification-of.js +2 -2
  404. package/dist/tools/act-capsule.d.ts +10 -0
  405. package/dist/tools/act-capsule.js +8 -2
  406. package/dist/tools/act-danger.d.ts +2 -1
  407. package/dist/tools/act-danger.js +11 -3
  408. package/dist/tools/act-observation.d.ts +41 -0
  409. package/dist/tools/act-observation.js +36 -0
  410. package/dist/tools/act-preflight.d.ts +20 -0
  411. package/dist/tools/act-preflight.js +98 -0
  412. package/dist/tools/act-sequence-retry.d.ts +45 -0
  413. package/dist/tools/act-sequence-retry.js +58 -0
  414. package/dist/tools/act-sequence-tool.d.ts +13 -0
  415. package/dist/tools/act-sequence-tool.js +135 -0
  416. package/dist/tools/act-target.d.ts +14 -0
  417. package/dist/tools/act-target.js +47 -0
  418. package/dist/tools/act-tools.d.ts +21 -1
  419. package/dist/tools/act-tools.js +196 -204
  420. package/dist/tools/assert-source.d.ts +22 -0
  421. package/dist/tools/assert-source.js +53 -0
  422. package/dist/tools/assert-verdict.d.ts +20 -3
  423. package/dist/tools/assert-verdict.js +58 -20
  424. package/dist/tools/browser-tools.js +6 -22
  425. package/dist/tools/complete-snapshot.d.ts +48 -0
  426. package/dist/tools/complete-snapshot.js +75 -0
  427. package/dist/tools/coverage-tools.d.ts +10 -1
  428. package/dist/tools/coverage-tools.js +123 -69
  429. package/dist/tools/dynamic-tools.js +53 -13
  430. package/dist/tools/error-recovery.d.ts +25 -14
  431. package/dist/tools/error-recovery.js +78 -9
  432. package/dist/tools/feedback-tools.d.ts +1 -1
  433. package/dist/tools/feedback-tools.js +2 -2
  434. package/dist/tools/invoke-tool.js +121 -11
  435. package/dist/tools/lease-hint.d.ts +10 -0
  436. package/dist/tools/lease-hint.js +53 -4
  437. package/dist/tools/lease-tools.d.ts +35 -5
  438. package/dist/tools/lease-tools.js +191 -13
  439. package/dist/tools/merged-name-redirect.d.ts +13 -0
  440. package/dist/tools/merged-name-redirect.js +22 -0
  441. package/dist/tools/navigate-arrival.d.ts +0 -2
  442. package/dist/tools/navigate-arrival.js +1 -1
  443. package/dist/tools/numeric-bounds.d.ts +100 -0
  444. package/dist/tools/numeric-bounds.js +125 -0
  445. package/dist/tools/observe-tools.js +109 -65
  446. package/dist/tools/read-tools.js +31 -8
  447. package/dist/tools/real-input-attempt.d.ts +32 -0
  448. package/dist/tools/real-input-attempt.js +212 -2
  449. package/dist/tools/reconcile-tools.js +22 -8
  450. package/dist/tools/resolve-target.d.ts +0 -7
  451. package/dist/tools/sdk-marker-probe.d.ts +29 -0
  452. package/dist/tools/sdk-marker-probe.js +57 -0
  453. package/dist/tools/snapshot-delta.d.ts +1 -1
  454. package/dist/tools/tool-kit.d.ts +24 -0
  455. package/dist/tools/tool-kit.js +21 -0
  456. package/dist/tools/tool-names.d.ts +5 -0
  457. package/dist/tools/tool-names.js +5 -0
  458. package/dist/tools/tool-surface.d.ts +85 -6
  459. package/dist/tools/tool-surface.js +122 -2
  460. package/dist/tools/tools-helpers.d.ts +19 -0
  461. package/dist/tools/tools-helpers.js +46 -0
  462. package/dist/tools/tools.d.ts +2 -0
  463. package/dist/tools/tools.js +78 -12
  464. package/dist/tools/unadvertised-help.js +4 -4
  465. package/dist/tools/verify-next-baton.d.ts +53 -0
  466. package/dist/tools/verify-next-baton.js +52 -0
  467. package/dist/update/reticle-deps.d.ts +9 -0
  468. package/dist/update/reticle-deps.js +27 -0
  469. package/dist/update/sdk-sync.d.ts +1 -2
  470. package/dist/update/sdk-sync.js +1 -21
  471. package/dist/update/update-nudge.d.ts +25 -10
  472. package/dist/update/update-nudge.js +32 -3
  473. package/dist/update/updater.d.ts +1 -1
  474. package/dist/version/peer-announce.js +4 -2
  475. package/dist/version/sdk-fix.d.ts +47 -0
  476. package/dist/version/sdk-fix.js +127 -0
  477. package/dist/version/version-nudge.d.ts +14 -0
  478. package/dist/version/version-nudge.js +21 -0
  479. package/dist/version/version-skew.d.ts +38 -3
  480. package/dist/version/version-skew.js +83 -7
  481. package/dist/visual/visual-store.d.ts +6 -6
  482. package/dist/visual/visual-store.js +18 -16
  483. package/dist/visual/visual-tools.js +57 -12
  484. package/docs/README.md +2 -1
  485. package/docs/cli/cloud.mdx +1 -1
  486. package/docs/cli/doctor.mdx +2 -0
  487. package/docs/cli/serve.mdx +4 -2
  488. package/docs/cli/verify.mdx +4 -5
  489. package/docs/cli.mdx +7 -3
  490. package/docs/desktop.mdx +1 -1
  491. package/docs/docs.json +2 -2
  492. package/docs/gates.md +1 -1
  493. package/docs/getting-started.md +19 -3
  494. package/docs/index.mdx +9 -6
  495. package/docs/install-agentic.mdx +38 -12
  496. package/docs/install-manual.mdx +1 -1
  497. package/docs/packages/browser.mdx +2 -2
  498. package/docs/packages/server.mdx +1 -1
  499. package/docs/packages.mdx +8 -6
  500. package/docs/platform-integration.md +17 -0
  501. package/docs/predicates.mdx +8 -0
  502. package/docs/reticle-manual.md +106 -0
  503. package/docs/skill-file.mdx +3 -3
  504. package/docs/telemetry-contract.md +108 -4
  505. package/docs/tools-act-sequence.mdx +16 -0
  506. package/docs/tools-tools-and-run.mdx +2 -4
  507. package/docs/troubleshooting.mdx +9 -0
  508. package/docs/usage.md +4 -3
  509. package/package.json +4 -3
  510. package/dist/init/cursor.d.ts +0 -24
  511. package/dist/init/cursor.js +0 -80
  512. package/dist/session/session-readiness.d.ts +0 -34
  513. package/dist/session/session-readiness.js +0 -36
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Which daemon does THIS project belong to?
3
+ *
4
+ * The system answered that two incompatible ways. Build plugins asked the registry by projectId, the
5
+ * way `pickDaemonPort` was designed to be asked. The CLI and the MCP proxy asked a NUMBER —
6
+ * `envPort ?? projectPort ?? 4400` — and then attached to whatever owned it, whoever it belonged to.
7
+ *
8
+ * So every project on a machine funnelled into one daemon. Its recorded identity was whichever
9
+ * project won the race to the port, which made the registry entry wrong the moment a second project
10
+ * attached, and made one `kill` on one well-known port a machine-wide outage for every agent at once.
11
+ *
12
+ * It worked anyway, by luck: everyone defaulted to the same number, so everyone found each other.
13
+ * The luck ends the moment a daemon is anywhere but the default — which is exactly when discovery was
14
+ * supposed to help, and exactly when it returned nothing.
15
+ *
16
+ * This module is the one answer both sides now use. It is pure and injectable: no process spawning,
17
+ * no port binding, nothing that needs a real `~/.reticle` to test.
18
+ */
19
+ import { type DaemonRegistryEntry } from '@reticlehq/core';
20
+ /**
21
+ * Every well-formed registry entry in `home`.
22
+ *
23
+ * A corrupt or half-written entry is skipped rather than thrown on: the registry is written by other
24
+ * processes that can be killed mid-write, and one bad file must not make a machine's daemons
25
+ * undiscoverable. Validated through core's schema so a shape change cannot silently pass here.
26
+ */
27
+ export declare function readDaemonRegistry(home: string): DaemonRegistryEntry[];
28
+ /**
29
+ * The port of the live daemon serving `projectId`, or undefined when this project has none.
30
+ *
31
+ * Undefined means "start one", never "use the default and hope". That distinction is the fix: the old
32
+ * path had no way to express "no daemon of mine exists", so it fell through to a number and attached
33
+ * to a stranger.
34
+ *
35
+ * The selection rule is core's `pickDaemonPort`, shared with every build plugin, so the daemon side
36
+ * and the app side cannot disagree about which daemon is ours.
37
+ */
38
+ export declare function resolveDaemonForProject(projectId: string | undefined, home: string, alive: (pid: number) => boolean): number | undefined;
39
+ /**
40
+ * May `caller` take over a daemon already sitting on the port it wanted?
41
+ *
42
+ * Two deliberate escape hatches, both of which keep an existing install working:
43
+ *
44
+ * A daemon that claims NO project was started somewhere without a `.reticle.json` — the global MCP
45
+ * registration in a directory that is not an app, which is the majority of daemons in the field. It
46
+ * belongs to nobody, so it belongs to whoever asks; refusing would break every user running Reticle
47
+ * globally, which is how it is documented to be installed.
48
+ *
49
+ * A caller with no project of its own has no identity to defend and nothing to be confused about, so
50
+ * it keeps the old permissive behaviour rather than being locked out of a daemon it can use.
51
+ *
52
+ * Everything else — a named daemon and a differently-named caller — is the cross-project bleed, and
53
+ * is the one case this refuses.
54
+ */
55
+ export declare function adoptable(daemonProjectId: string | undefined, callerProjectId: string | undefined): boolean;
56
+ /** The project a daemon on `port` claims, or undefined when it claims none / is not registered. */
57
+ export declare function daemonProjectAt(port: number, home: string): string | undefined;
58
+ /**
59
+ * Live daemons OTHER than `myPort` that an app for this project has connected to.
60
+ *
61
+ * The split brain this whole module was written to prevent has a second half nobody was reporting.
62
+ * `resolveMcpPort` correctly refuses to adopt a stranger's daemon and relocates to an OS-assigned
63
+ * port — and the APP, which resolves its own port independently (core's `pickDaemonPort` in the
64
+ * build plugin, falling back to the documented default when nothing matches), can land on the
65
+ * stranger. The pairing token is per USER, not per project, so that connect SUCCEEDS: the app is
66
+ * live on one daemon and the agent is attached to a different, empty one.
67
+ *
68
+ * Every individual check is then green. The browser shows an instrumented page, `reticle status`
69
+ * reports a session, the agent reports Reticle registered and enabled — and the agent's tools see
70
+ * nothing, with a no-session diagnosis that tells it to start a dev server that is already running.
71
+ *
72
+ * `connectedOn` is injected rather than imported so this stays free of the session layer (which
73
+ * imports from here) and so the rule is testable without a real `~/.reticle`. Callers pass
74
+ * `hasProjectConnectedBefore` bound to the same projectId.
75
+ *
76
+ * Only LIVE daemons are named: a port a session used yesterday is history, not a split.
77
+ */
78
+ export declare function daemonsServingProjectElsewhere(projectId: string | undefined, myPort: number, home: string, alive: (pid: number) => boolean, connectedOn: (port: number) => boolean): readonly number[];
79
+ /**
80
+ * The sentence for a split brain, or `undefined` when there is none.
81
+ *
82
+ * Stated as a fact about two ports rather than a hedge, because unlike the sibling-port observation
83
+ * next door this is not "something is listening somewhere" — it is this project's own connection
84
+ * record naming a daemon that is not the one being asked. The wording says which side is empty,
85
+ * since the failure is invisible precisely because both sides look healthy from where they stand.
86
+ */
87
+ export declare function splitBrainNote(myPort: number, elsewhere: readonly number[]): string | undefined;
88
+ /**
89
+ * The other stance on the same split, for a command standing on the port that HAS the app.
90
+ *
91
+ * `splitBrainNote` is asked from the empty daemon and reads the connection record. From the daemon
92
+ * the app is connected to there is nothing missing to notice — sessions are live, every check is
93
+ * green — and the only visible fact is that this project ALSO owns a live daemon somewhere else.
94
+ * One project, two daemons, is a split whichever end you are standing on, and saying it from both
95
+ * ends is the difference between a user inferring it from two green checkmarks and being told.
96
+ *
97
+ * Silent for a caller with no project (it owns no daemon to be split from) and silent when the port
98
+ * being inspected is the project's own.
99
+ */
100
+ export declare function wrongDaemonNote(inspectedPort: number, ownPort: number | undefined): string | undefined;
101
+ /**
102
+ * The port this agent's MCP proxy should serve, decided BEFORE the proxy starts.
103
+ *
104
+ * Resolved once rather than per reconnect because the proxy's transport is built around a fixed
105
+ * number, and making it chase a moving port would mean reworking the reconnect path — the one piece
106
+ * of this system whose failure mode is "the MCP server disappeared mid-session". The port it settles
107
+ * on is this project's own, so the thing it would be chasing is a daemon it alone is responsible for.
108
+ *
109
+ * The order is the whole fix:
110
+ * 1. our project's live daemon, wherever it is listening — a daemon that moved is still ours;
111
+ * 2. the preferred port, but only if free or holding a daemon we may adopt;
112
+ * 3. a port the OS gives us, when the preferred one belongs to somebody else.
113
+ *
114
+ * Step 3 is what stops a second project from attaching to the first project's daemon. That single
115
+ * silent adoption is what made every agent on a machine share one process, one identity and one
116
+ * blast radius.
117
+ */
118
+ export declare function resolveMcpPort(preferred: number, projectId: string | undefined, home: string, deps: {
119
+ alive: (pid: number) => boolean;
120
+ daemonPresent: (port: number) => Promise<boolean>;
121
+ pickPort: (preferred: number) => Promise<number>;
122
+ }): Promise<number>;
@@ -0,0 +1,187 @@
1
+ /**
2
+ * Which daemon does THIS project belong to?
3
+ *
4
+ * The system answered that two incompatible ways. Build plugins asked the registry by projectId, the
5
+ * way `pickDaemonPort` was designed to be asked. The CLI and the MCP proxy asked a NUMBER —
6
+ * `envPort ?? projectPort ?? 4400` — and then attached to whatever owned it, whoever it belonged to.
7
+ *
8
+ * So every project on a machine funnelled into one daemon. Its recorded identity was whichever
9
+ * project won the race to the port, which made the registry entry wrong the moment a second project
10
+ * attached, and made one `kill` on one well-known port a machine-wide outage for every agent at once.
11
+ *
12
+ * It worked anyway, by luck: everyone defaulted to the same number, so everyone found each other.
13
+ * The luck ends the moment a daemon is anywhere but the default — which is exactly when discovery was
14
+ * supposed to help, and exactly when it returned nothing.
15
+ *
16
+ * This module is the one answer both sides now use. It is pure and injectable: no process spawning,
17
+ * no port binding, nothing that needs a real `~/.reticle` to test.
18
+ */
19
+ import { readdirSync, readFileSync } from 'node:fs';
20
+ import { join } from 'node:path';
21
+ import { DaemonRegistryEntrySchema, daemonRegistryPort, pickDaemonPort, } from '@reticlehq/core';
22
+ /**
23
+ * Every well-formed registry entry in `home`.
24
+ *
25
+ * A corrupt or half-written entry is skipped rather than thrown on: the registry is written by other
26
+ * processes that can be killed mid-write, and one bad file must not make a machine's daemons
27
+ * undiscoverable. Validated through core's schema so a shape change cannot silently pass here.
28
+ */
29
+ export function readDaemonRegistry(home) {
30
+ let files;
31
+ try {
32
+ files = readdirSync(home);
33
+ }
34
+ catch {
35
+ return []; // no ~/.reticle yet: no daemon has ever run here
36
+ }
37
+ const entries = [];
38
+ for (const file of files) {
39
+ if (null === daemonRegistryPort(file))
40
+ continue;
41
+ try {
42
+ const parsed = DaemonRegistryEntrySchema.safeParse(JSON.parse(readFileSync(join(home, file), 'utf8')));
43
+ if (parsed.success)
44
+ entries.push(parsed.data);
45
+ }
46
+ catch {
47
+ continue;
48
+ }
49
+ }
50
+ return entries;
51
+ }
52
+ /**
53
+ * The port of the live daemon serving `projectId`, or undefined when this project has none.
54
+ *
55
+ * Undefined means "start one", never "use the default and hope". That distinction is the fix: the old
56
+ * path had no way to express "no daemon of mine exists", so it fell through to a number and attached
57
+ * to a stranger.
58
+ *
59
+ * The selection rule is core's `pickDaemonPort`, shared with every build plugin, so the daemon side
60
+ * and the app side cannot disagree about which daemon is ours.
61
+ */
62
+ export function resolveDaemonForProject(projectId, home, alive) {
63
+ return pickDaemonPort(readDaemonRegistry(home), projectId, alive) ?? undefined;
64
+ }
65
+ /**
66
+ * May `caller` take over a daemon already sitting on the port it wanted?
67
+ *
68
+ * Two deliberate escape hatches, both of which keep an existing install working:
69
+ *
70
+ * A daemon that claims NO project was started somewhere without a `.reticle.json` — the global MCP
71
+ * registration in a directory that is not an app, which is the majority of daemons in the field. It
72
+ * belongs to nobody, so it belongs to whoever asks; refusing would break every user running Reticle
73
+ * globally, which is how it is documented to be installed.
74
+ *
75
+ * A caller with no project of its own has no identity to defend and nothing to be confused about, so
76
+ * it keeps the old permissive behaviour rather than being locked out of a daemon it can use.
77
+ *
78
+ * Everything else — a named daemon and a differently-named caller — is the cross-project bleed, and
79
+ * is the one case this refuses.
80
+ */
81
+ export function adoptable(daemonProjectId, callerProjectId) {
82
+ if (daemonProjectId === undefined || 0 === daemonProjectId.length)
83
+ return true;
84
+ if (callerProjectId === undefined || 0 === callerProjectId.length)
85
+ return true;
86
+ return daemonProjectId === callerProjectId;
87
+ }
88
+ /** The project a daemon on `port` claims, or undefined when it claims none / is not registered. */
89
+ export function daemonProjectAt(port, home) {
90
+ return readDaemonRegistry(home).find((e) => e.port === port)?.projectId;
91
+ }
92
+ /**
93
+ * Live daemons OTHER than `myPort` that an app for this project has connected to.
94
+ *
95
+ * The split brain this whole module was written to prevent has a second half nobody was reporting.
96
+ * `resolveMcpPort` correctly refuses to adopt a stranger's daemon and relocates to an OS-assigned
97
+ * port — and the APP, which resolves its own port independently (core's `pickDaemonPort` in the
98
+ * build plugin, falling back to the documented default when nothing matches), can land on the
99
+ * stranger. The pairing token is per USER, not per project, so that connect SUCCEEDS: the app is
100
+ * live on one daemon and the agent is attached to a different, empty one.
101
+ *
102
+ * Every individual check is then green. The browser shows an instrumented page, `reticle status`
103
+ * reports a session, the agent reports Reticle registered and enabled — and the agent's tools see
104
+ * nothing, with a no-session diagnosis that tells it to start a dev server that is already running.
105
+ *
106
+ * `connectedOn` is injected rather than imported so this stays free of the session layer (which
107
+ * imports from here) and so the rule is testable without a real `~/.reticle`. Callers pass
108
+ * `hasProjectConnectedBefore` bound to the same projectId.
109
+ *
110
+ * Only LIVE daemons are named: a port a session used yesterday is history, not a split.
111
+ */
112
+ export function daemonsServingProjectElsewhere(projectId, myPort, home, alive, connectedOn) {
113
+ if (projectId === undefined || 0 === projectId.length)
114
+ return [];
115
+ return readDaemonRegistry(home)
116
+ .filter((entry) => entry.port !== myPort && alive(entry.pid) && connectedOn(entry.port))
117
+ .map((entry) => entry.port)
118
+ .sort((a, b) => a - b);
119
+ }
120
+ /**
121
+ * The sentence for a split brain, or `undefined` when there is none.
122
+ *
123
+ * Stated as a fact about two ports rather than a hedge, because unlike the sibling-port observation
124
+ * next door this is not "something is listening somewhere" — it is this project's own connection
125
+ * record naming a daemon that is not the one being asked. The wording says which side is empty,
126
+ * since the failure is invisible precisely because both sides look healthy from where they stand.
127
+ */
128
+ export function splitBrainNote(myPort, elsewhere) {
129
+ if (0 === elsewhere.length)
130
+ return undefined;
131
+ const listed = elsewhere.map((port) => `:${String(port)}`).join(', ');
132
+ const first = elsewhere[0] ?? myPort;
133
+ return (`this project's app is connected to a DIFFERENT Reticle daemon (${listed}), while this one is ` +
134
+ `on :${String(myPort)} — two daemons for one project, and the agent is attached to the empty ` +
135
+ 'half. Nothing is wrong with the app or the wiring. Stop the other daemon with ' +
136
+ `\`reticle stop --port ${String(first)}\` and restart the dev server so the app rediscovers ` +
137
+ 'this one, or start the agent and the dev server from the same project directory so both ' +
138
+ 'resolve the same daemon.');
139
+ }
140
+ /**
141
+ * The other stance on the same split, for a command standing on the port that HAS the app.
142
+ *
143
+ * `splitBrainNote` is asked from the empty daemon and reads the connection record. From the daemon
144
+ * the app is connected to there is nothing missing to notice — sessions are live, every check is
145
+ * green — and the only visible fact is that this project ALSO owns a live daemon somewhere else.
146
+ * One project, two daemons, is a split whichever end you are standing on, and saying it from both
147
+ * ends is the difference between a user inferring it from two green checkmarks and being told.
148
+ *
149
+ * Silent for a caller with no project (it owns no daemon to be split from) and silent when the port
150
+ * being inspected is the project's own.
151
+ */
152
+ export function wrongDaemonNote(inspectedPort, ownPort) {
153
+ if (ownPort === undefined || ownPort === inspectedPort)
154
+ return undefined;
155
+ return (`this is not this project's own daemon: the registry says it owns a live daemon on ` +
156
+ `:${String(ownPort)} and this command is looking at :${String(inspectedPort)}. Two daemons for ` +
157
+ 'one project means the half your agent is attached to and the half your app is connected to can ' +
158
+ 'be different processes, with both reporting healthy. Stop one of them and restart the dev ' +
159
+ 'server so everything lands on the same port.');
160
+ }
161
+ /**
162
+ * The port this agent's MCP proxy should serve, decided BEFORE the proxy starts.
163
+ *
164
+ * Resolved once rather than per reconnect because the proxy's transport is built around a fixed
165
+ * number, and making it chase a moving port would mean reworking the reconnect path — the one piece
166
+ * of this system whose failure mode is "the MCP server disappeared mid-session". The port it settles
167
+ * on is this project's own, so the thing it would be chasing is a daemon it alone is responsible for.
168
+ *
169
+ * The order is the whole fix:
170
+ * 1. our project's live daemon, wherever it is listening — a daemon that moved is still ours;
171
+ * 2. the preferred port, but only if free or holding a daemon we may adopt;
172
+ * 3. a port the OS gives us, when the preferred one belongs to somebody else.
173
+ *
174
+ * Step 3 is what stops a second project from attaching to the first project's daemon. That single
175
+ * silent adoption is what made every agent on a machine share one process, one identity and one
176
+ * blast radius.
177
+ */
178
+ export async function resolveMcpPort(preferred, projectId, home, deps) {
179
+ const mine = resolveDaemonForProject(projectId, home, deps.alive);
180
+ if (mine !== undefined)
181
+ return mine;
182
+ if (!(await deps.daemonPresent(preferred)))
183
+ return preferred;
184
+ return adoptable(daemonProjectAt(preferred, home), projectId)
185
+ ? preferred
186
+ : deps.pickPort(preferred);
187
+ }
@@ -0,0 +1,15 @@
1
+ import { type DevServerEntry } from '@reticlehq/core';
2
+ /** Injected so the reading is unit-testable without a real home or real processes. */
3
+ export interface DevServerIo {
4
+ readdir: (dir: string) => string[];
5
+ readFile: (path: string) => string;
6
+ isAlive: (pid: number) => boolean;
7
+ }
8
+ /**
9
+ * Every dev server that announced itself and is still running, lowest port first.
10
+ *
11
+ * Fails soft at every step. This runs on the path that explains to a user why setup has not worked,
12
+ * so a half-written file, a hand-edit, or a missing directory must degrade the answer rather than
13
+ * replace the diagnosis with a stack trace.
14
+ */
15
+ export declare function readDevServers(home: string, io?: DevServerIo): DevServerEntry[];
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Reading what dev servers have announced about themselves.
3
+ *
4
+ * The consumer half of `devserver-<port>.json`. A build plugin writes one when its dev server is
5
+ * actually listening; this reads them back, so `init` and `status` can tell three states apart that
6
+ * used to be one:
7
+ *
8
+ * nothing announced no dev server with Reticle loaded is running
9
+ * announced, no page the bundle is wired and no browser dialled — and the entry names the URL
10
+ * announced + session working
11
+ *
12
+ * The middle one is the case that had no message. It was folded into "restart your dev server and
13
+ * load the app", which is two instructions where only the second one applies, addressed to someone
14
+ * who has already done the first.
15
+ *
16
+ * Fs plumbing only — the selection rule is `liveDevServers` in core, shared with anything else that
17
+ * reads this registry, for the same reason `pickDaemonPort` lives there.
18
+ */
19
+ import { readdirSync, readFileSync } from 'node:fs';
20
+ import { join } from 'node:path';
21
+ import { DevServerEntrySchema, devServerRegistryPort, liveDevServers, } from '@reticlehq/core';
22
+ /** process.kill(pid, 0) throws iff the process is gone — the same probe the daemon registry uses. */
23
+ function isAliveNow(pid) {
24
+ try {
25
+ process.kill(pid, 0);
26
+ return true;
27
+ }
28
+ catch {
29
+ return false;
30
+ }
31
+ }
32
+ const nodeIo = {
33
+ readdir: (dir) => readdirSync(dir),
34
+ readFile: (path) => readFileSync(path, 'utf8'),
35
+ isAlive: isAliveNow,
36
+ };
37
+ /**
38
+ * Every dev server that announced itself and is still running, lowest port first.
39
+ *
40
+ * Fails soft at every step. This runs on the path that explains to a user why setup has not worked,
41
+ * so a half-written file, a hand-edit, or a missing directory must degrade the answer rather than
42
+ * replace the diagnosis with a stack trace.
43
+ */
44
+ export function readDevServers(home, io = nodeIo) {
45
+ let files;
46
+ try {
47
+ files = io.readdir(home);
48
+ }
49
+ catch {
50
+ return []; // No state directory yet — nothing has ever run here.
51
+ }
52
+ const entries = [];
53
+ for (const file of files) {
54
+ // Strict on the filename BEFORE reading: the daemon's own `daemon-<port>.json` is a sibling, and
55
+ // parsing one as a dev server would report the daemon itself as an instrumented app.
56
+ if (null === devServerRegistryPort(file))
57
+ continue;
58
+ try {
59
+ const parsed = DevServerEntrySchema.safeParse(JSON.parse(io.readFile(join(home, file))));
60
+ if (parsed.success)
61
+ entries.push(parsed.data);
62
+ }
63
+ catch {
64
+ // Truncated, mid-write, or hand-edited. One bad file is not a reason to report none.
65
+ }
66
+ }
67
+ return liveDevServers(entries, io.isAlive);
68
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * A port this machine will actually let us bind.
3
+ *
4
+ * Once daemons are per-project, "which port" stops being a constant and becomes a question. The
5
+ * answer must not be "the next number up": probing 4401, 4402, 4403 races every other agent doing
6
+ * the same arithmetic at the same moment, and the loser finds out at bind time, which is after it has
7
+ * already told the caller which port to use.
8
+ *
9
+ * So the OS assigns it. Binding port 0 asks the kernel for a free one and hands back the number it
10
+ * chose, and nothing else can be holding it at that instant. There is still a window between closing
11
+ * the probe socket and the daemon binding for real — unavoidable without passing the listener itself
12
+ * to the child — but it is microseconds wide and loses to a retry, where a guessed number loses every
13
+ * time on a busy machine.
14
+ *
15
+ * The preferred port is tried first so the ordinary single-project install keeps landing on the
16
+ * documented default, and every doc, log line and troubleshooting page that says 4400 stays true.
17
+ */
18
+ /** Can we bind `port` on the loopback interface right now? */
19
+ export declare function portIsFree(port: number, host?: string): Promise<boolean>;
20
+ /** Ask the kernel for a free port. Rejects only if the machine cannot bind loopback at all. */
21
+ export declare function osAssignedPort(host?: string): Promise<number>;
22
+ /**
23
+ * `preferred` when it is free, otherwise whatever the OS gives us.
24
+ *
25
+ * Injectable so the decision is unit-testable without binding anything: the interesting case is the
26
+ * one where the preferred port is taken, and a test must be able to say so without racing a real
27
+ * socket.
28
+ */
29
+ export declare function pickDaemonPortToBind(preferred: number, isFree?: (port: number) => Promise<boolean>, assign?: () => Promise<number>): Promise<number>;
@@ -0,0 +1,55 @@
1
+ /**
2
+ * A port this machine will actually let us bind.
3
+ *
4
+ * Once daemons are per-project, "which port" stops being a constant and becomes a question. The
5
+ * answer must not be "the next number up": probing 4401, 4402, 4403 races every other agent doing
6
+ * the same arithmetic at the same moment, and the loser finds out at bind time, which is after it has
7
+ * already told the caller which port to use.
8
+ *
9
+ * So the OS assigns it. Binding port 0 asks the kernel for a free one and hands back the number it
10
+ * chose, and nothing else can be holding it at that instant. There is still a window between closing
11
+ * the probe socket and the daemon binding for real — unavoidable without passing the listener itself
12
+ * to the child — but it is microseconds wide and loses to a retry, where a guessed number loses every
13
+ * time on a busy machine.
14
+ *
15
+ * The preferred port is tried first so the ordinary single-project install keeps landing on the
16
+ * documented default, and every doc, log line and troubleshooting page that says 4400 stays true.
17
+ */
18
+ import { createServer } from 'node:net';
19
+ import { LOOPBACK_HOST } from '@reticlehq/core';
20
+ /** Can we bind `port` on the loopback interface right now? */
21
+ export function portIsFree(port, host = LOOPBACK_HOST) {
22
+ return new Promise((resolve) => {
23
+ const probe = createServer();
24
+ probe.once('error', () => resolve(false));
25
+ probe.listen(port, host, () => {
26
+ probe.close(() => resolve(true));
27
+ });
28
+ });
29
+ }
30
+ /** Ask the kernel for a free port. Rejects only if the machine cannot bind loopback at all. */
31
+ export function osAssignedPort(host = LOOPBACK_HOST) {
32
+ return new Promise((resolve, reject) => {
33
+ const probe = createServer();
34
+ probe.once('error', reject);
35
+ probe.listen(0, host, () => {
36
+ const address = probe.address();
37
+ if (null === address || 'string' === typeof address) {
38
+ probe.close(() => reject(new Error('could not read an OS-assigned port')));
39
+ return;
40
+ }
41
+ const { port } = address;
42
+ probe.close(() => resolve(port));
43
+ });
44
+ });
45
+ }
46
+ /**
47
+ * `preferred` when it is free, otherwise whatever the OS gives us.
48
+ *
49
+ * Injectable so the decision is unit-testable without binding anything: the interesting case is the
50
+ * one where the preferred port is taken, and a test must be able to say so without racing a real
51
+ * socket.
52
+ */
53
+ export async function pickDaemonPortToBind(preferred, isFree = portIsFree, assign = osAssignedPort) {
54
+ return (await isFree(preferred)) ? preferred : assign();
55
+ }
@@ -19,10 +19,6 @@
19
19
  */
20
20
  /** The event name a beat carries. One `grep` separates liveness from everything else in the file. */
21
21
  export declare const DAEMON_HEARTBEAT_EVENT = "reticle_daemon_alive";
22
- /** Written by installExitTrace when the process exits through Node. */
23
- export declare const DAEMON_EXIT_EVENT = "reticle_daemon_exiting";
24
- /** Written by installExitTrace when a signal arrives, which is a CAUSE the exit line does not carry. */
25
- export declare const DAEMON_SIGNAL_EVENT = "reticle_daemon_signalled";
26
22
  /**
27
23
  * How often a live daemon says so.
28
24
  *
@@ -20,9 +20,9 @@
20
20
  /** The event name a beat carries. One `grep` separates liveness from everything else in the file. */
21
21
  export const DAEMON_HEARTBEAT_EVENT = 'reticle_daemon_alive';
22
22
  /** Written by installExitTrace when the process exits through Node. */
23
- export const DAEMON_EXIT_EVENT = 'reticle_daemon_exiting';
23
+ const DAEMON_EXIT_EVENT = 'reticle_daemon_exiting';
24
24
  /** Written by installExitTrace when a signal arrives, which is a CAUSE the exit line does not carry. */
25
- export const DAEMON_SIGNAL_EVENT = 'reticle_daemon_signalled';
25
+ const DAEMON_SIGNAL_EVENT = 'reticle_daemon_signalled';
26
26
  /**
27
27
  * How often a live daemon says so.
28
28
  *
@@ -0,0 +1,32 @@
1
+ /**
2
+ * What a woken MCP proxy may do with whatever is sitting on its port.
3
+ *
4
+ * Pulled out as a pure function because the wake path lives inside a long side-effecting closure in
5
+ * `mcp-command.ts`, and the decision it was getting wrong is three lines of reasoning that deserve a
6
+ * test of their own.
7
+ *
8
+ * `resolveMcpPort` asks the identity question at BOOT and relocates rather than adopting a stranger.
9
+ * The wake path asked only whether a daemon answered, so after an idle exit freed the port and
10
+ * another project's daemon bound it, a dormant proxy could reattach to a daemon that was not its
11
+ * own — and then serve an agent verdicts about a different application. `adoptable` is the same rule
12
+ * boot uses; this is just the wake path finally asking it.
13
+ */
14
+ import { PortPresence } from './port-presence.js';
15
+ export declare const WakeAction: {
16
+ /** A daemon we may use is already here. */
17
+ readonly USE: "use";
18
+ /** Nothing is listening: start one. */
19
+ readonly SPAWN: "spawn";
20
+ /** Somebody else's. Go dormant with a reason rather than pretend the wake succeeded. */
21
+ readonly REFUSE: "refuse";
22
+ };
23
+ export type WakeAction = (typeof WakeAction)[keyof typeof WakeAction];
24
+ /**
25
+ * Identity is asked only of a port that actually holds a daemon.
26
+ *
27
+ * A FREE port must stay spawnable whatever the registry says about it, because a stale entry is the
28
+ * registry's ordinary state for the moment after any daemon exits — and refusing there would turn
29
+ * every idle retirement into a permanent lockout, which is the failure this whole path exists to
30
+ * prevent.
31
+ */
32
+ export declare function decideWake(presence: PortPresence, daemonProjectId: string | undefined, callerProjectId: string | undefined): WakeAction;
@@ -0,0 +1,38 @@
1
+ /**
2
+ * What a woken MCP proxy may do with whatever is sitting on its port.
3
+ *
4
+ * Pulled out as a pure function because the wake path lives inside a long side-effecting closure in
5
+ * `mcp-command.ts`, and the decision it was getting wrong is three lines of reasoning that deserve a
6
+ * test of their own.
7
+ *
8
+ * `resolveMcpPort` asks the identity question at BOOT and relocates rather than adopting a stranger.
9
+ * The wake path asked only whether a daemon answered, so after an idle exit freed the port and
10
+ * another project's daemon bound it, a dormant proxy could reattach to a daemon that was not its
11
+ * own — and then serve an agent verdicts about a different application. `adoptable` is the same rule
12
+ * boot uses; this is just the wake path finally asking it.
13
+ */
14
+ import { PortPresence } from './port-presence.js';
15
+ import { adoptable } from './daemon-resolve.js';
16
+ export const WakeAction = {
17
+ /** A daemon we may use is already here. */
18
+ USE: 'use',
19
+ /** Nothing is listening: start one. */
20
+ SPAWN: 'spawn',
21
+ /** Somebody else's. Go dormant with a reason rather than pretend the wake succeeded. */
22
+ REFUSE: 'refuse',
23
+ };
24
+ /**
25
+ * Identity is asked only of a port that actually holds a daemon.
26
+ *
27
+ * A FREE port must stay spawnable whatever the registry says about it, because a stale entry is the
28
+ * registry's ordinary state for the moment after any daemon exits — and refusing there would turn
29
+ * every idle retirement into a permanent lockout, which is the failure this whole path exists to
30
+ * prevent.
31
+ */
32
+ export function decideWake(presence, daemonProjectId, callerProjectId) {
33
+ if (PortPresence.FREE === presence)
34
+ return WakeAction.SPAWN;
35
+ if (PortPresence.DAEMON !== presence)
36
+ return WakeAction.REFUSE;
37
+ return adoptable(daemonProjectId, callerProjectId) ? WakeAction.USE : WakeAction.REFUSE;
38
+ }
@@ -27,8 +27,6 @@ export interface IssueState {
27
27
  state: 'open' | 'closed';
28
28
  labels: readonly string[];
29
29
  }
30
- /** The label that means "fixed, waiting for a release" — an honest reason to still be open. */
31
- export declare const PENDING_RELEASE_LABEL = "fixed-pending-release";
32
30
  export declare function closedRefsIn(commitMessage: string): number[];
33
31
  /**
34
32
  * Of the issues these commits claimed to close, the ones still reading as available work.
@@ -23,7 +23,7 @@
23
23
  * below testable without a network or a repo.
24
24
  */
25
25
  /** The label that means "fixed, waiting for a release" — an honest reason to still be open. */
26
- export const PENDING_RELEASE_LABEL = 'fixed-pending-release';
26
+ const PENDING_RELEASE_LABEL = 'fixed-pending-release';
27
27
  /**
28
28
  * Issue numbers a commit message CLAIMS to have closed.
29
29
  *
@@ -2,6 +2,7 @@ import { z } from 'zod';
2
2
  import { sessionRoot } from '../project/session-root.js';
3
3
  import { asNumber, asString } from '../tools/tools-helpers.js';
4
4
  import { ReticleTool } from '../tools/tool-names.js';
5
+ import { countSchema } from '../tools/numeric-bounds.js';
5
6
  import { readContract } from '../project/reticle-dir.js';
6
7
  import { buildDomainModel } from './domain-model.js';
7
8
  import { proposeInstrumentation } from '../oracles/self-instrument.js';
@@ -19,8 +20,7 @@ export const DOMAIN_TOOLS = [
19
20
  name: ReticleTool.DOMAIN,
20
21
  description: 'Read the app domain model BEFORE testing: every saved flow with its assertion grade, the consequence that MUST hold for it (mustHold = what it actually tests), the anchors/signals it exercises, plus GAPS — declared signals/testids that NO flow asserts (untested intent), and flows that assert no observable consequence. Use this to decide what to test and where the real risk is, instead of crawling the whole app. Reads .reticle/flows/ + .reticle/contract.json (no browser needed).',
21
22
  inputSchema: {
22
- limit: z
23
- .number()
23
+ limit: countSchema
24
24
  .optional()
25
25
  .describe('Most-recent N flows to detail. Defaults to 25 — the per-flow list grows with every saved flow (measured at ~11KB / ~2,700 tokens at 22 flows) and a large project would spend most of an agent context on flows it is not asking about. `flowCount` is always the true total, and the gaps/coverage summary is computed over ALL flows regardless, so capping the detail never changes the analysis.'),
26
26
  },