@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,25 @@
1
+ /**
2
+ * Take the dev server with us when setup is interrupted.
3
+ *
4
+ * `setup` starts a dev server the user did not start — detached, in its own process group — and
5
+ * stops it in a `finally`. A `finally` does not run on SIGINT: the process is terminated. So Ctrl-C
6
+ * during setup left that server running forever, holding a port nobody could account for, and the
7
+ * next run's port probe then found a stranger on it.
8
+ *
9
+ * It hid behind an earlier bug. `init` used to exit before the dev server was ever started whenever
10
+ * instrumentation needed a manual step, so the orphan check found nothing and passed for the wrong
11
+ * reason. Letting the runtime phase run is what exposed this.
12
+ */
13
+ /** The parts of `process` this needs, so the behaviour is testable without signalling anything. */
14
+ export interface SignalTarget {
15
+ on(signal: 'SIGINT' | 'SIGTERM', handler: () => void): void;
16
+ off(signal: 'SIGINT' | 'SIGTERM', handler: () => void): void;
17
+ exit(code: number): void;
18
+ }
19
+ /**
20
+ * Run `stop` if this process is interrupted, then leave with the signal's own code.
21
+ *
22
+ * Returns a disposer, and callers must use it: `init` keeps running after setup, and a handler left
23
+ * behind would tear down a server it no longer owns.
24
+ */
25
+ export declare function stopOnInterrupt(stop: () => void, target: SignalTarget): () => void;
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Take the dev server with us when setup is interrupted.
3
+ *
4
+ * `setup` starts a dev server the user did not start — detached, in its own process group — and
5
+ * stops it in a `finally`. A `finally` does not run on SIGINT: the process is terminated. So Ctrl-C
6
+ * during setup left that server running forever, holding a port nobody could account for, and the
7
+ * next run's port probe then found a stranger on it.
8
+ *
9
+ * It hid behind an earlier bug. `init` used to exit before the dev server was ever started whenever
10
+ * instrumentation needed a manual step, so the orphan check found nothing and passed for the wrong
11
+ * reason. Letting the runtime phase run is what exposed this.
12
+ */
13
+ /** 128 + the signal number, which is what a shell reports — so a caller can tell Ctrl-C from a failure. */
14
+ const EXIT_CODE = { SIGINT: 130, SIGTERM: 143 };
15
+ /**
16
+ * Run `stop` if this process is interrupted, then leave with the signal's own code.
17
+ *
18
+ * Returns a disposer, and callers must use it: `init` keeps running after setup, and a handler left
19
+ * behind would tear down a server it no longer owns.
20
+ */
21
+ export function stopOnInterrupt(stop, target) {
22
+ // Once only. `stop` is not idempotent — it kills a process GROUP — and on a recycled pid a second
23
+ // kill is somebody else's process.
24
+ let done = false;
25
+ const handlers = ['SIGINT', 'SIGTERM'].map((signal) => {
26
+ const handler = () => {
27
+ if (!done) {
28
+ done = true;
29
+ stop();
30
+ }
31
+ target.exit(EXIT_CODE[signal]);
32
+ };
33
+ target.on(signal, handler);
34
+ return { signal, handler };
35
+ });
36
+ return () => {
37
+ for (const { signal, handler } of handlers)
38
+ target.off(signal, handler);
39
+ };
40
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Writing a license key where the CLI will find it, without leaking it.
3
+ *
4
+ * The instructions used to ask an agent to do this by hand in three steps: append to `.env`, check
5
+ * `.gitignore`, confirm. Every one of those is deterministic, and the second is the one that costs
6
+ * something when skipped — a license key committed to git is a leaked credential, and it stays
7
+ * leaked after it is deleted.
8
+ *
9
+ * The key is never printed, never returned, and never put in a result object. The only thing said
10
+ * out loud is that a key was written.
11
+ */
12
+ export interface LicenseIo {
13
+ readonly exists: (path: string) => boolean;
14
+ readonly readFile: (path: string) => string;
15
+ readonly writeFile: (path: string, contents: string) => void;
16
+ }
17
+ export declare const LicenseWrite: {
18
+ readonly WRITTEN: "written";
19
+ readonly ALREADY: "already";
20
+ readonly REPLACED: "replaced";
21
+ };
22
+ export type LicenseWrite = (typeof LicenseWrite)[keyof typeof LicenseWrite];
23
+ export interface LicenseResult {
24
+ readonly action: LicenseWrite;
25
+ /** True when `.gitignore` had to be changed so the key cannot be committed. */
26
+ readonly gitignoreUpdated: boolean;
27
+ /** Safe to print. Never contains the key. */
28
+ readonly message: string;
29
+ }
30
+ /** Whether `.gitignore` already covers `.env`, allowing for the forms people actually write. */
31
+ export declare function gitignoreCoversEnv(contents: string): boolean;
32
+ /**
33
+ * Put the key in `.env`, and make sure `.env` cannot be committed.
34
+ *
35
+ * An existing assignment is REPLACED rather than appended to: two `RETICLE_LICENSE_KEY` lines is a
36
+ * file where the answer depends on which one the parser reads last, and nobody should have to know
37
+ * which that is.
38
+ */
39
+ export declare function writeLicenseKey(dir: string, key: string, io: LicenseIo): LicenseResult;
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Writing a license key where the CLI will find it, without leaking it.
3
+ *
4
+ * The instructions used to ask an agent to do this by hand in three steps: append to `.env`, check
5
+ * `.gitignore`, confirm. Every one of those is deterministic, and the second is the one that costs
6
+ * something when skipped — a license key committed to git is a leaked credential, and it stays
7
+ * leaked after it is deleted.
8
+ *
9
+ * The key is never printed, never returned, and never put in a result object. The only thing said
10
+ * out loud is that a key was written.
11
+ */
12
+ const ENV_FILE = '.env';
13
+ const GITIGNORE = '.gitignore';
14
+ const ENV_VAR = 'RETICLE_LICENSE_KEY';
15
+ export const LicenseWrite = {
16
+ WRITTEN: 'written',
17
+ ALREADY: 'already',
18
+ REPLACED: 'replaced',
19
+ };
20
+ const joinPath = (dir, file) => `${dir.replace(/\/$/, '')}/${file}`;
21
+ /** Whether `.gitignore` already covers `.env`, allowing for the forms people actually write. */
22
+ export function gitignoreCoversEnv(contents) {
23
+ return contents
24
+ .split('\n')
25
+ .map((l) => l.trim())
26
+ .some((l) => ENV_FILE === l || '.env*' === l || '*.env' === l || '/.env' === l);
27
+ }
28
+ /**
29
+ * Put the key in `.env`, and make sure `.env` cannot be committed.
30
+ *
31
+ * An existing assignment is REPLACED rather than appended to: two `RETICLE_LICENSE_KEY` lines is a
32
+ * file where the answer depends on which one the parser reads last, and nobody should have to know
33
+ * which that is.
34
+ */
35
+ export function writeLicenseKey(dir, key, io) {
36
+ const envPath = joinPath(dir, ENV_FILE);
37
+ const existing = io.exists(envPath) ? io.readFile(envPath) : '';
38
+ const lines = '' === existing ? [] : existing.split('\n');
39
+ const at = lines.findIndex((l) => l.trimStart().startsWith(`${ENV_VAR}=`));
40
+ const already = -1 !== at && lines[at]?.trim() === `${ENV_VAR}=${key}`;
41
+ let action = LicenseWrite.WRITTEN;
42
+ if (already)
43
+ action = LicenseWrite.ALREADY;
44
+ else if (-1 !== at) {
45
+ lines[at] = `${ENV_VAR}=${key}`;
46
+ action = LicenseWrite.REPLACED;
47
+ }
48
+ else {
49
+ if (0 < lines.length && '' !== lines[lines.length - 1])
50
+ lines.push('');
51
+ lines.push(`${ENV_VAR}=${key}`, '');
52
+ }
53
+ if (LicenseWrite.ALREADY !== action)
54
+ io.writeFile(envPath, lines.join('\n'));
55
+ // The order matters: a key in git is leaked whether or not the file is later removed.
56
+ const ignorePath = joinPath(dir, GITIGNORE);
57
+ const ignore = io.exists(ignorePath) ? io.readFile(ignorePath) : '';
58
+ let gitignoreUpdated = false;
59
+ if (!gitignoreCoversEnv(ignore)) {
60
+ const next = '' === ignore ? `${ENV_FILE}\n` : `${ignore.replace(/\n*$/, '\n')}${ENV_FILE}\n`;
61
+ io.writeFile(ignorePath, next);
62
+ gitignoreUpdated = true;
63
+ }
64
+ const wrote = LicenseWrite.ALREADY === action
65
+ ? `license key already in ${ENV_FILE}`
66
+ : `license key written to ${ENV_FILE}`;
67
+ return {
68
+ action,
69
+ gitignoreUpdated,
70
+ message: gitignoreUpdated ? `${wrote}, and ${ENV_FILE} added to ${GITIGNORE}` : wrote,
71
+ };
72
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Which ports a process tree is listening on, parsed from the tools that can answer that.
3
+ *
4
+ * Kept separate from the commands that produce the text so the Windows behaviour is testable from
5
+ * any machine. That is not a stylistic preference: port discovery on Windows returned nothing at
6
+ * all until this was extracted, so a dev server that prints no parseable URL was undiscoverable
7
+ * there — and `react-scripts` prints no parseable URL outside a tty, which makes it the majority
8
+ * platform's most common React setup.
9
+ */
10
+ /** One LISTENING socket and the process that owns it. */
11
+ export interface Listener {
12
+ readonly port: number;
13
+ readonly pid: number;
14
+ }
15
+ /** `lsof -a -p <pids> -iTCP -sTCP:LISTEN -P -n`, NAME column only. */
16
+ export declare function parseLsofPorts(out: string): number[];
17
+ /**
18
+ * `netstat -ano` on Windows.
19
+ *
20
+ * The port is whatever follows the LAST colon: `[::]:3000` has three of them, so splitting on ':'
21
+ * and taking index 1 reads every IPv6 row as port 0 and silently drops half the table.
22
+ */
23
+ export declare function parseNetstatListeners(out: string): Listener[];
24
+ /** One (pid, parent) pair, as `wmic process get ParentProcessId,ProcessId` prints them. */
25
+ export interface ProcessPair {
26
+ readonly pid: number;
27
+ readonly ppid: number;
28
+ }
29
+ export declare function parseWmicProcesses(out: string): ProcessPair[];
30
+ /**
31
+ * `root` and everything descended from it.
32
+ *
33
+ * The dev server is almost never the process we spawned: `npm run dev` starts a shell, which starts
34
+ * node, which binds the port. Matching only the pid we hold finds nothing, and nothing is
35
+ * indistinguishable from "no server".
36
+ */
37
+ export declare function descendants(pairs: readonly ProcessPair[], root: number): number[];
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Which ports a process tree is listening on, parsed from the tools that can answer that.
3
+ *
4
+ * Kept separate from the commands that produce the text so the Windows behaviour is testable from
5
+ * any machine. That is not a stylistic preference: port discovery on Windows returned nothing at
6
+ * all until this was extracted, so a dev server that prints no parseable URL was undiscoverable
7
+ * there — and `react-scripts` prints no parseable URL outside a tty, which makes it the majority
8
+ * platform's most common React setup.
9
+ */
10
+ /** `lsof -a -p <pids> -iTCP -sTCP:LISTEN -P -n`, NAME column only. */
11
+ export function parseLsofPorts(out) {
12
+ const ports = new Set();
13
+ for (const m of String(out).matchAll(/:(\d+)\s*$/gm))
14
+ ports.add(Number(m[1]));
15
+ return [...ports];
16
+ }
17
+ const NETSTAT_PROTO = 'TCP';
18
+ const NETSTAT_STATE = 'LISTENING';
19
+ const NETSTAT_MIN_COLUMNS = 5;
20
+ /**
21
+ * `netstat -ano` on Windows.
22
+ *
23
+ * The port is whatever follows the LAST colon: `[::]:3000` has three of them, so splitting on ':'
24
+ * and taking index 1 reads every IPv6 row as port 0 and silently drops half the table.
25
+ */
26
+ export function parseNetstatListeners(out) {
27
+ const rows = [];
28
+ for (const line of String(out).split(/\r?\n/)) {
29
+ const f = line.trim().split(/\s+/);
30
+ if (f.length < NETSTAT_MIN_COLUMNS)
31
+ continue;
32
+ if (f[0]?.toUpperCase() !== NETSTAT_PROTO || f[3]?.toUpperCase() !== NETSTAT_STATE)
33
+ continue;
34
+ const local = f[1] ?? '';
35
+ const port = Number(local.slice(local.lastIndexOf(':') + 1));
36
+ const pid = Number(f[4]);
37
+ if (Number.isInteger(port) && port > 0 && Number.isInteger(pid))
38
+ rows.push({ port, pid });
39
+ }
40
+ return rows;
41
+ }
42
+ export function parseWmicProcesses(out) {
43
+ const pairs = [];
44
+ for (const line of String(out).split(/\r?\n/)) {
45
+ const f = line.trim().split(/\s+/);
46
+ if (f.length !== 2)
47
+ continue;
48
+ const ppid = Number(f[0]);
49
+ const pid = Number(f[1]);
50
+ if (Number.isInteger(pid) && Number.isInteger(ppid))
51
+ pairs.push({ pid, ppid });
52
+ }
53
+ return pairs;
54
+ }
55
+ /**
56
+ * `root` and everything descended from it.
57
+ *
58
+ * The dev server is almost never the process we spawned: `npm run dev` starts a shell, which starts
59
+ * node, which binds the port. Matching only the pid we hold finds nothing, and nothing is
60
+ * indistinguishable from "no server".
61
+ */
62
+ export function descendants(pairs, root) {
63
+ const byParent = new Map();
64
+ for (const { pid, ppid } of pairs) {
65
+ const kids = byParent.get(ppid);
66
+ if (kids === undefined)
67
+ byParent.set(ppid, [pid]);
68
+ else
69
+ kids.push(pid);
70
+ }
71
+ const found = new Set([root]);
72
+ const queue = [root];
73
+ while (queue.length > 0) {
74
+ const next = queue.shift();
75
+ if (next === undefined)
76
+ continue;
77
+ for (const child of byParent.get(next) ?? []) {
78
+ if (found.has(child))
79
+ continue;
80
+ found.add(child);
81
+ queue.push(child);
82
+ }
83
+ }
84
+ return [...found];
85
+ }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * The real world, bound to the injected shape `runSetupPhases` expects.
3
+ *
4
+ * Everything here is a thin binding to something that already exists — `openInBrowser`,
5
+ * `fetchStatus`, the driver table — with one exception: the dev server. Nothing else in the package
6
+ * owns a spawned process, and owning one properly is most of this file.
7
+ *
8
+ * The promise this makes about that process: setup leaves it running on success, because an
9
+ * instrumented app the user can watch IS the deliverable, and stops it on every other ending. A
10
+ * server nobody started, holding a port nobody can account for, surviving the terminal that spawned
11
+ * it, is the failure this file exists to avoid — measured, an interrupted run used to leave one
12
+ * listening indefinitely.
13
+ */
14
+ import type { CandidateSession } from './session-pick.js';
15
+ import type { PageProbe } from './page-probe.js';
16
+ /** The dev server this process started, and the only one it will ever stop. */
17
+ export declare class OwnedDevServer {
18
+ private child;
19
+ private buffer;
20
+ private lastOutputAt;
21
+ private handedOver;
22
+ start(command: string, cwd: string, env: Readonly<Record<string, string>>): void;
23
+ /** The process group leader, for a caller that has to clean up what it handed over. */
24
+ pid(): number | undefined;
25
+ output(): string;
26
+ exited(): boolean;
27
+ quietForMs(): number;
28
+ /** Ports anything in this server's process tree is listening on. */
29
+ listeningPorts(): number[];
30
+ /** Hand the running server to the user. After this, `stop()` does nothing. */
31
+ /**
32
+ * Leave the server running and stop holding it.
33
+ *
34
+ * `unref` on the child is NOT enough: its stdout and stderr are pipes owned by this process, and
35
+ * an open pipe keeps the event loop alive on its own. So `init` printed "setup complete" and then
36
+ * hung until something killed it, turning a successful run into a non-zero exit — which is
37
+ * exactly what the install gate reported, on a run whose every other assertion passed.
38
+ *
39
+ * The streams are unref'd rather than destroyed: the server is still writing to them, and
40
+ * destroying the read end of a live pipe risks an EPIPE in somebody else's dev server.
41
+ */
42
+ handOver(): void;
43
+ stop(): void;
44
+ }
45
+ /**
46
+ * Fetch the page and say what came back.
47
+ *
48
+ * A refused certificate is reported separately because the server ANSWERED: a self-signed dev cert
49
+ * is an ordinary local setup, and calling it "nothing is listening" sends somebody to start a
50
+ * server that is already running.
51
+ */
52
+ export declare function probePage(url: string): Promise<PageProbe>;
53
+ /** Sessions the daemon is holding, in the shape the picker reads. */
54
+ export declare function listSessions(port: number): Promise<CandidateSession[]>;
55
+ /** Whether this project has a saved flow, wherever the project keeps one. */
56
+ export declare function flowsSaved(roots: readonly string[]): boolean;
57
+ /** On PATH at all. The separate question of whether it RUNS is asked by chooseDriver. */
58
+ export declare function binaryExists(bin: string): boolean;
59
+ /** Whether a directory looks like a project setup has already wired. */
60
+ export declare function alreadyWired(dir: string): boolean;
@@ -0,0 +1,210 @@
1
+ /**
2
+ * The real world, bound to the injected shape `runSetupPhases` expects.
3
+ *
4
+ * Everything here is a thin binding to something that already exists — `openInBrowser`,
5
+ * `fetchStatus`, the driver table — with one exception: the dev server. Nothing else in the package
6
+ * owns a spawned process, and owning one properly is most of this file.
7
+ *
8
+ * The promise this makes about that process: setup leaves it running on success, because an
9
+ * instrumented app the user can watch IS the deliverable, and stops it on every other ending. A
10
+ * server nobody started, holding a port nobody can account for, surviving the terminal that spawned
11
+ * it, is the failure this file exists to avoid — measured, an interrupted run used to leave one
12
+ * listening indefinitely.
13
+ */
14
+ import { spawn, spawnSync } from 'node:child_process';
15
+ import { Socket } from 'node:net';
16
+ import { existsSync, readdirSync } from 'node:fs';
17
+ import { join } from 'node:path';
18
+ import { fetchStatus } from '../cli/cli-launch.js';
19
+ import { descendants, parseLsofPorts, parseNetstatListeners, parseWmicProcesses, } from './listeners.js';
20
+ const WINDOWS = 'win32' === process.platform;
21
+ /** A page fetch that is slow is a page fetch that failed, for our purposes. */
22
+ const PROBE_TIMEOUT_MS = 5_000;
23
+ /**
24
+ * Stop an inherited stdio pipe from holding the event loop open.
25
+ *
26
+ * A child's piped stdout is a Socket at runtime and a Readable in the types, which has no `unref`.
27
+ * Narrowing through Socket keeps that honest rather than asserting it.
28
+ */
29
+ function releasePipe(stream) {
30
+ if (stream instanceof Socket)
31
+ stream.unref();
32
+ }
33
+ /** The dev server this process started, and the only one it will ever stop. */
34
+ export class OwnedDevServer {
35
+ child;
36
+ buffer = '';
37
+ lastOutputAt = Date.now();
38
+ handedOver = false;
39
+ start(command, cwd, env) {
40
+ const child = spawn(command, {
41
+ cwd,
42
+ shell: true,
43
+ // Its own process group, so stopping it stops what it started rather than only the wrapper.
44
+ detached: !WINDOWS,
45
+ stdio: ['ignore', 'pipe', 'pipe'],
46
+ env: { ...process.env, ...env },
47
+ });
48
+ const collect = (chunk) => {
49
+ this.buffer += String(chunk);
50
+ this.lastOutputAt = Date.now();
51
+ };
52
+ child.stdout?.on('data', collect);
53
+ child.stderr?.on('data', collect);
54
+ this.child = child;
55
+ }
56
+ /** The process group leader, for a caller that has to clean up what it handed over. */
57
+ pid() {
58
+ return this.child?.pid;
59
+ }
60
+ output() {
61
+ return this.buffer;
62
+ }
63
+ exited() {
64
+ return undefined !== this.child && null !== this.child.exitCode;
65
+ }
66
+ quietForMs() {
67
+ return Date.now() - this.lastOutputAt;
68
+ }
69
+ /** Ports anything in this server's process tree is listening on. */
70
+ listeningPorts() {
71
+ const pid = this.child?.pid;
72
+ if (undefined === pid)
73
+ return [];
74
+ if (WINDOWS) {
75
+ const tree = new Set(descendants(windowsProcessPairs(), pid));
76
+ return [
77
+ ...new Set(parseNetstatListeners(run('netstat', ['-ano']))
78
+ .filter((r) => tree.has(r.pid))
79
+ .map((r) => r.port)),
80
+ ];
81
+ }
82
+ const pids = run('sh', ['-c', `ps -o pid= -g ${pid} 2>/dev/null | tr -d ' '`])
83
+ .split('\n')
84
+ .filter(Boolean);
85
+ if (0 === pids.length)
86
+ return [];
87
+ return parseLsofPorts(run('sh', [
88
+ '-c',
89
+ `lsof -a -p ${pids.join(',')} -iTCP -sTCP:LISTEN -P -n 2>/dev/null | awk 'NR>1{print $9}'`,
90
+ ]));
91
+ }
92
+ /** Hand the running server to the user. After this, `stop()` does nothing. */
93
+ /**
94
+ * Leave the server running and stop holding it.
95
+ *
96
+ * `unref` on the child is NOT enough: its stdout and stderr are pipes owned by this process, and
97
+ * an open pipe keeps the event loop alive on its own. So `init` printed "setup complete" and then
98
+ * hung until something killed it, turning a successful run into a non-zero exit — which is
99
+ * exactly what the install gate reported, on a run whose every other assertion passed.
100
+ *
101
+ * The streams are unref'd rather than destroyed: the server is still writing to them, and
102
+ * destroying the read end of a live pipe risks an EPIPE in somebody else's dev server.
103
+ */
104
+ handOver() {
105
+ this.handedOver = true;
106
+ const child = this.child;
107
+ releasePipe(child?.stdout);
108
+ releasePipe(child?.stderr);
109
+ child?.unref();
110
+ }
111
+ stop() {
112
+ const child = this.child;
113
+ if (this.handedOver || undefined === child?.pid)
114
+ return;
115
+ if (WINDOWS)
116
+ run('taskkill', ['/PID', String(child.pid), '/T', '/F']);
117
+ else {
118
+ try {
119
+ process.kill(-child.pid, 'SIGTERM');
120
+ }
121
+ catch {
122
+ try {
123
+ child.kill();
124
+ }
125
+ catch {
126
+ /* already gone */
127
+ }
128
+ }
129
+ }
130
+ this.child = undefined;
131
+ }
132
+ }
133
+ function run(file, args) {
134
+ const r = spawnSync(file, args, { encoding: 'utf8', maxBuffer: 8 * 1024 * 1024 });
135
+ return r.stdout ?? '';
136
+ }
137
+ /**
138
+ * Every (pid, ppid) pair on a Windows machine — from `wmic` if it is there, PowerShell if not.
139
+ *
140
+ * `wmic` was the only source, and Microsoft has been removing it: deprecated since 21H1 and absent
141
+ * from current Windows images. When it is missing `run` returns '' — the honest answer for a tool
142
+ * that does not exist, and a catastrophic one here, because an empty process list means an empty
143
+ * process TREE, which means no observed ports, which means the dev server's url is never found.
144
+ * `init` then waits out the full quiet window and reports "the dev server stopped producing output
145
+ * without ever serving a page" about a Vite server that was serving perfectly the whole time.
146
+ *
147
+ * The fallback prints the same two-integers-per-line shape, so `parseWmicProcesses` reads either
148
+ * without knowing which produced it. Tried in this order because `wmic` is an order of magnitude
149
+ * faster to start than PowerShell, and on a machine that still has it nothing needs to change.
150
+ */
151
+ const POWERSHELL_PAIRS = 'Get-CimInstance Win32_Process | ForEach-Object { "$($_.ParentProcessId) $($_.ProcessId)" }';
152
+ function windowsProcessPairs() {
153
+ const fromWmic = parseWmicProcesses(run('wmic', ['process', 'get', 'ParentProcessId,ProcessId']));
154
+ if (0 < fromWmic.length)
155
+ return fromWmic;
156
+ return parseWmicProcesses(run('powershell', ['-NoProfile', '-NonInteractive', '-Command', POWERSHELL_PAIRS]));
157
+ }
158
+ /**
159
+ * Fetch the page and say what came back.
160
+ *
161
+ * A refused certificate is reported separately because the server ANSWERED: a self-signed dev cert
162
+ * is an ordinary local setup, and calling it "nothing is listening" sends somebody to start a
163
+ * server that is already running.
164
+ */
165
+ export async function probePage(url) {
166
+ try {
167
+ const res = await fetch(url, { signal: AbortSignal.timeout(PROBE_TIMEOUT_MS) });
168
+ const body = await res.text();
169
+ return { served: true, sdkInPage: /@reticlehq|@reticle-connect|reticle-dev/.test(body) };
170
+ }
171
+ catch (err) {
172
+ const message = String(err?.cause?.message ??
173
+ err?.message ??
174
+ '');
175
+ const tlsRefused = /certificate|SELF_SIGNED|DEPTH_ZERO|ERR_TLS|unable to verify/i.test(message);
176
+ return { served: false, sdkInPage: false, tlsRefused };
177
+ }
178
+ }
179
+ /** Sessions the daemon is holding, in the shape the picker reads. */
180
+ export async function listSessions(port) {
181
+ try {
182
+ const payload = await fetchStatus(port);
183
+ const sessions = payload.sessions;
184
+ return Array.isArray(sessions) ? sessions : [];
185
+ }
186
+ catch {
187
+ return [];
188
+ }
189
+ }
190
+ /** Whether this project has a saved flow, wherever the project keeps one. */
191
+ export function flowsSaved(roots) {
192
+ for (const root of roots) {
193
+ try {
194
+ if (0 < readdirSync(join(root, '.reticle', 'flows')).length)
195
+ return true;
196
+ }
197
+ catch {
198
+ /* no flows kept there */
199
+ }
200
+ }
201
+ return false;
202
+ }
203
+ /** On PATH at all. The separate question of whether it RUNS is asked by chooseDriver. */
204
+ export function binaryExists(bin) {
205
+ return 0 === spawnSync(WINDOWS ? 'where' : 'which', [bin], { stdio: 'ignore' }).status;
206
+ }
207
+ /** Whether a directory looks like a project setup has already wired. */
208
+ export function alreadyWired(dir) {
209
+ return existsSync(join(dir, '.reticle.json'));
210
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * The one thing the daemon cannot know: what the page actually contains.
3
+ *
4
+ * `diagnoseNoSession` in session/no-session-diagnosis.ts already tells the four daemon-side cases
5
+ * apart — a session that came and went, something listening that never dialled, a wired project on
6
+ * the wrong port, nothing listening at all — and its sentence is the one to lead with. None of that
7
+ * requires fetching anything, which is also its limit: the daemon never sees the HTML.
8
+ *
9
+ * So this adds only the page-side fact and nothing else. Whether the SDK is IN what the server
10
+ * returned splits the most common failure of all — a dev server still serving the bundle it built
11
+ * before the build config was edited — from a connect that ran and returned early.
12
+ */
13
+ /** What a single fetch of the app's url established. */
14
+ export interface PageProbe {
15
+ /** The server answered at all. */
16
+ readonly served: boolean;
17
+ /** The SDK appears in what it answered with. */
18
+ readonly sdkInPage: boolean;
19
+ /**
20
+ * The server ANSWERED and its certificate was refused. A self-signed dev cert is an ordinary
21
+ * local setup, so treating this as "nothing is listening" tells someone to start a server that is
22
+ * already running.
23
+ */
24
+ readonly tlsRefused?: boolean | undefined;
25
+ }
26
+ export declare const PageFinding: {
27
+ readonly NOT_SERVED: "not-served";
28
+ readonly TLS_REFUSED: "tls-refused";
29
+ readonly SDK_MISSING: "sdk-missing";
30
+ readonly SDK_PRESENT: "sdk-present";
31
+ };
32
+ export type PageFinding = (typeof PageFinding)[keyof typeof PageFinding];
33
+ /** Pure, so the mapping is testable without a server or a browser. */
34
+ export declare function readPage(probe: PageProbe): PageFinding;
35
+ /**
36
+ * What the page adds to the daemon's account. Deliberately a sentence to APPEND, not a replacement:
37
+ * the daemon's diagnosis names the fix, and this says what the page looked like when we checked.
38
+ */
39
+ export declare function describePage(finding: PageFinding, url: string): string;
@@ -0,0 +1,49 @@
1
+ /**
2
+ * The one thing the daemon cannot know: what the page actually contains.
3
+ *
4
+ * `diagnoseNoSession` in session/no-session-diagnosis.ts already tells the four daemon-side cases
5
+ * apart — a session that came and went, something listening that never dialled, a wired project on
6
+ * the wrong port, nothing listening at all — and its sentence is the one to lead with. None of that
7
+ * requires fetching anything, which is also its limit: the daemon never sees the HTML.
8
+ *
9
+ * So this adds only the page-side fact and nothing else. Whether the SDK is IN what the server
10
+ * returned splits the most common failure of all — a dev server still serving the bundle it built
11
+ * before the build config was edited — from a connect that ran and returned early.
12
+ */
13
+ export const PageFinding = {
14
+ NOT_SERVED: 'not-served',
15
+ TLS_REFUSED: 'tls-refused',
16
+ SDK_MISSING: 'sdk-missing',
17
+ SDK_PRESENT: 'sdk-present',
18
+ };
19
+ /** Pure, so the mapping is testable without a server or a browser. */
20
+ export function readPage(probe) {
21
+ if (true === probe.tlsRefused)
22
+ return PageFinding.TLS_REFUSED;
23
+ if (!probe.served)
24
+ return PageFinding.NOT_SERVED;
25
+ return probe.sdkInPage ? PageFinding.SDK_PRESENT : PageFinding.SDK_MISSING;
26
+ }
27
+ /**
28
+ * What the page adds to the daemon's account. Deliberately a sentence to APPEND, not a replacement:
29
+ * the daemon's diagnosis names the fix, and this says what the page looked like when we checked.
30
+ */
31
+ export function describePage(finding, url) {
32
+ switch (finding) {
33
+ case PageFinding.NOT_SERVED:
34
+ // Leads with the cause. The break-matrix asserts this phrasing because it is what a reader
35
+ // greps for after a timeout, and "nothing answered" reads as an observation about the check
36
+ // rather than a statement about the machine.
37
+ return `nothing is serving ${url} — the dev server is not up, so the SDK was never given the chance to load.`;
38
+ case PageFinding.TLS_REFUSED:
39
+ return (`${url} answered over HTTPS with a certificate this process will not accept, so setup could ` +
40
+ 'not read the page. The app itself may be perfectly fine; re-run with ' +
41
+ 'NODE_TLS_REJECT_UNAUTHORIZED=0 if you trust this dev certificate, or use the http origin.');
42
+ case PageFinding.SDK_MISSING:
43
+ return (`${url} is served, and the SDK is NOT in the page. That is almost always a dev server still ` +
44
+ 'serving the bundle it built before the build config was edited: restart it and hard-reload.');
45
+ case PageFinding.SDK_PRESENT:
46
+ return (`The SDK IS in the page at ${url} and never dialled the bridge, so it loaded and returned ` +
47
+ 'early: a localhost guard, a production-only check, or a bridge port that differs on the two sides.');
48
+ }
49
+ }