@reticlehq/server 2.13.1 → 2.14.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 (449) hide show
  1. package/SKILL.md +8 -18
  2. package/bin/reticle.js +3 -0
  3. package/dist/bridge/auth-failure-reason.js +12 -1
  4. package/dist/bridge/bridge-security.d.ts +8 -0
  5. package/dist/bridge/bridge-security.js +6 -1
  6. package/dist/bridge/bridge.d.ts +3 -0
  7. package/dist/bridge/bridge.js +23 -1
  8. package/dist/bridge/bridge.test-harness.d.ts +10 -1
  9. package/dist/bridge/bridge.test-harness.js +7 -1
  10. package/dist/bridge/pairing-token.d.ts +17 -0
  11. package/dist/bridge/pairing-token.js +26 -0
  12. package/dist/bridge/token-auth.d.ts +7 -0
  13. package/dist/bridge/token-auth.js +7 -1
  14. package/dist/capsule/capsule-store.d.ts +4 -4
  15. package/dist/capsule/capsule.d.ts +1 -1
  16. package/dist/capsule/capsule.js +3 -3
  17. package/dist/capsule/causal-summary.d.ts +21 -1
  18. package/dist/capsule/causal-summary.js +8 -6
  19. package/dist/capsule/divergence.d.ts +1 -1
  20. package/dist/capsule/divergence.js +26 -10
  21. package/dist/chromium-launch-options.d.ts +2 -1
  22. package/dist/cli/adhoc-verdict.d.ts +58 -0
  23. package/dist/cli/adhoc-verdict.js +115 -0
  24. package/dist/cli/chromium-hint.d.ts +18 -4
  25. package/dist/cli/chromium-hint.js +54 -6
  26. package/dist/cli/cli-doctor.js +1 -2
  27. package/dist/cli/cli-flow-commands.d.ts +16 -2
  28. package/dist/cli/cli-flow-commands.js +28 -9
  29. package/dist/cli/cli-parse-feedback.js +1 -1
  30. package/dist/cli/cli-parse.d.ts +2 -1
  31. package/dist/cli/cli-parse.js +38 -0
  32. package/dist/cli/cli-port.d.ts +5 -13
  33. package/dist/cli/cli-port.js +5 -38
  34. package/dist/cli/cli-update-commands.js +4 -4
  35. package/dist/cli/cli-verify.d.ts +24 -7
  36. package/dist/cli/cli-verify.js +87 -13
  37. package/dist/cli/cloud-keystore.d.ts +2 -1
  38. package/dist/cli/cloud-login.d.ts +2 -1
  39. package/dist/cli/cloud-session.d.ts +2 -1
  40. package/dist/cli/cloud-session.js +1 -1
  41. package/dist/cli/daemon-start-options.d.ts +2 -1
  42. package/dist/cli/device-flow.d.ts +2 -2
  43. package/dist/cli/doctor-daemon-line.d.ts +2 -1
  44. package/dist/cli/doctor-project-line.d.ts +2 -1
  45. package/dist/cli/doctor-rows.d.ts +2 -1
  46. package/dist/cli/drive-attach.d.ts +4 -3
  47. package/dist/cli/gate-hook-message.d.ts +2 -1
  48. package/dist/cli/status-next-action.js +10 -11
  49. package/dist/cli.d.ts +1 -0
  50. package/dist/cli.js +21 -9
  51. package/dist/cloud/account-state.d.ts +20 -0
  52. package/dist/cloud/account-state.js +65 -0
  53. package/dist/cloud/cloud-sync.d.ts +46 -3
  54. package/dist/cloud/cloud-sync.js +116 -0
  55. package/dist/cloud/sync-cycle.d.ts +5 -4
  56. package/dist/cloud/sync-cycle.js +1 -1
  57. package/dist/cloud/sync-daemon.d.ts +3 -19
  58. package/dist/cloud/sync-daemon.js +2 -2
  59. package/dist/crawl/crawl-tools.js +1 -1
  60. package/dist/crawl/crawl.d.ts +5 -5
  61. package/dist/crawl/crawl.js +138 -4
  62. package/dist/daemon/daemon-resilience.d.ts +3 -2
  63. package/dist/daemon/daemon-resilience.js +43 -4
  64. package/dist/daemon/daemon-resolve.d.ts +0 -9
  65. package/dist/daemon/daemon-resolve.js +2 -2
  66. package/dist/daemon/dev-servers.d.ts +1 -1
  67. package/dist/daemon/dev-servers.js +1 -1
  68. package/dist/daemon/loopback-alias.d.ts +2 -1
  69. package/dist/daemon/startup-failure.d.ts +11 -0
  70. package/dist/daemon/startup-failure.js +46 -0
  71. package/dist/events/contradictions.d.ts +10 -16
  72. package/dist/events/contradictions.js +149 -17
  73. package/dist/events/declared.d.ts +13 -1
  74. package/dist/events/declared.js +59 -3
  75. package/dist/events/echo-mismatch.js +75 -6
  76. package/dist/events/lineage.d.ts +52 -0
  77. package/dist/events/lineage.js +122 -0
  78. package/dist/events/net-evidence.d.ts +21 -0
  79. package/dist/events/net-evidence.js +81 -0
  80. package/dist/events/predicate-console.d.ts +13 -0
  81. package/dist/events/predicate-console.js +138 -0
  82. package/dist/events/predicate-element.d.ts +13 -0
  83. package/dist/events/predicate-element.js +214 -0
  84. package/dist/events/predicate-eval.d.ts +15 -3
  85. package/dist/events/predicate-eval.js +128 -110
  86. package/dist/events/predicate-parse.js +34 -1
  87. package/dist/events/predicate-request-body.d.ts +50 -0
  88. package/dist/events/predicate-request-body.js +120 -0
  89. package/dist/events/predicate-route.d.ts +21 -1
  90. package/dist/events/predicate-route.js +149 -21
  91. package/dist/events/predicate-schema.d.ts +8 -1
  92. package/dist/events/predicate-schema.js +34 -0
  93. package/dist/events/predicate.d.ts +2 -0
  94. package/dist/events/predicate.js +52 -159
  95. package/dist/events/split-text-miss.d.ts +6 -4
  96. package/dist/events/split-text-miss.js +12 -5
  97. package/dist/flows/affected-tools.js +22 -9
  98. package/dist/flows/annotate.d.ts +2 -1
  99. package/dist/flows/annotate.js +19 -1
  100. package/dist/flows/coverage.d.ts +2 -2
  101. package/dist/flows/flake.d.ts +3 -3
  102. package/dist/flows/flow-expect-grammar.d.ts +33 -0
  103. package/dist/flows/flow-expect-grammar.js +155 -0
  104. package/dist/flows/flow-replay-run.d.ts +1 -1
  105. package/dist/flows/flow-replay-run.js +28 -15
  106. package/dist/flows/flow-replay.d.ts +10 -0
  107. package/dist/flows/flow-replay.js +57 -22
  108. package/dist/flows/flow-secret-field.d.ts +14 -0
  109. package/dist/flows/flow-secret-field.js +22 -0
  110. package/dist/flows/flow-step-runners.d.ts +1 -0
  111. package/dist/flows/flow-step-runners.js +91 -34
  112. package/dist/flows/flow-success.js +22 -0
  113. package/dist/flows/flow-tools.js +27 -8
  114. package/dist/flows/flow-upload-resolve.d.ts +23 -0
  115. package/dist/flows/flow-upload-resolve.js +46 -0
  116. package/dist/flows/flows.d.ts +15 -0
  117. package/dist/flows/flows.js +17 -14
  118. package/dist/flows/git-changed.d.ts +23 -4
  119. package/dist/flows/git-changed.js +32 -6
  120. package/dist/flows/heal-run.js +2 -2
  121. package/dist/flows/predicate-to-expect.d.ts +3 -2
  122. package/dist/flows/predicate-to-expect.js +11 -5
  123. package/dist/flows/recording-backtrack.d.ts +24 -0
  124. package/dist/flows/recording-backtrack.js +73 -0
  125. package/dist/flows/recordings.d.ts +6 -0
  126. package/dist/flows/replay-halt.d.ts +26 -0
  127. package/dist/flows/replay-halt.js +30 -0
  128. package/dist/flows/replay.d.ts +0 -8
  129. package/dist/flows/replay.js +15 -3
  130. package/dist/flows/suite-flakes.d.ts +2 -1
  131. package/dist/flows/verify-change-tools.js +16 -4
  132. package/dist/honesty/already-true.d.ts +14 -0
  133. package/dist/honesty/already-true.js +47 -4
  134. package/dist/honesty/blind-spots.d.ts +3 -1
  135. package/dist/honesty/blind-spots.js +24 -1
  136. package/dist/honesty/body-capture-remedy.d.ts +42 -0
  137. package/dist/honesty/body-capture-remedy.js +95 -0
  138. package/dist/honesty/feature-capture.d.ts +3 -2
  139. package/dist/honesty/feature-capture.js +1 -1
  140. package/dist/honesty/gap-ledger.d.ts +1 -1
  141. package/dist/honesty/gap-ledger.js +1 -1
  142. package/dist/honesty/instrumentation-gaps.d.ts +8 -1
  143. package/dist/honesty/instrumentation-gaps.js +12 -2
  144. package/dist/honesty/observability.d.ts +4 -3
  145. package/dist/honesty/tool-hit-rate.d.ts +3 -2
  146. package/dist/honesty/uncaptured-bodies.d.ts +3 -1
  147. package/dist/honesty/uncaptured-bodies.js +6 -2
  148. package/dist/honesty/unsettled.d.ts +9 -0
  149. package/dist/honesty/unsettled.js +45 -0
  150. package/dist/honesty/verified.d.ts +32 -1
  151. package/dist/honesty/verified.js +37 -10
  152. package/dist/impact/impact-store.d.ts +5 -14
  153. package/dist/impact/impact-store.js +49 -6
  154. package/dist/index.js +1 -1
  155. package/dist/input/network-detail.d.ts +26 -1
  156. package/dist/input/network-detail.js +44 -12
  157. package/dist/input/network-mock-tools.js +24 -17
  158. package/dist/input/real-input.js +1 -1
  159. package/dist/input/viewport-tools.js +25 -13
  160. package/dist/intent/inline-intent.d.ts +0 -10
  161. package/dist/intent/inline-intent.js +1 -1
  162. package/dist/intent/intent-shard-store.d.ts +3 -2
  163. package/dist/intent/intent-shard-store.js +1 -1
  164. package/dist/intent/intent-shard.d.ts +14 -14
  165. package/dist/intent/intent-shard.js +4 -4
  166. package/dist/intent/intent-store.d.ts +1 -1
  167. package/dist/intent/intent-store.js +1 -1
  168. package/dist/intent/intent-subject.d.ts +2 -1
  169. package/dist/intent/open-intents.d.ts +3 -2
  170. package/dist/license/license.d.ts +4 -4
  171. package/dist/license/license.js +1 -1
  172. package/dist/mcp/attach-memory.d.ts +2 -1
  173. package/dist/mcp/client-identity.d.ts +2 -1
  174. package/dist/mcp/mcp-outage.d.ts +1 -1
  175. package/dist/mcp/mcp-outage.js +1 -1
  176. package/dist/mcp/mcp-proxy.d.ts +3 -1
  177. package/dist/mcp/mcp-proxy.js +32 -7
  178. package/dist/mcp/mcp.js +3 -1
  179. package/dist/mcp/proxy-handshake.d.ts +2 -1
  180. package/dist/mcp/proxy-idle-exit.d.ts +45 -0
  181. package/dist/mcp/proxy-idle-exit.js +94 -0
  182. package/dist/mcp/server-instructions.d.ts +2 -1
  183. package/dist/memory/project-memory.d.ts +2 -3
  184. package/dist/memory/project-memory.js +1 -1
  185. package/dist/oracles/propose-consequences.js +5 -5
  186. package/dist/oracles/self-instrument.d.ts +2 -2
  187. package/dist/oracles/self-instrument.js +1 -1
  188. package/dist/platform.d.ts +3 -5
  189. package/dist/platform.js +3 -5
  190. package/dist/pool/browser-pool.d.ts +92 -0
  191. package/dist/pool/browser-pool.js +119 -5
  192. package/dist/pool/playwright-launcher.js +29 -2
  193. package/dist/pool/storage-seed.d.ts +23 -0
  194. package/dist/pool/storage-seed.js +198 -0
  195. package/dist/project/artifact-root.d.ts +2 -1
  196. package/dist/project/memory-fs.d.ts +2 -1
  197. package/dist/project/reticle-dir.js +1 -1
  198. package/dist/project/session-root.d.ts +8 -0
  199. package/dist/project/session-root.js +9 -2
  200. package/dist/runs/reticle-runner.d.ts +14 -1
  201. package/dist/runs/reticle-runner.js +39 -2
  202. package/dist/session/attachment-history.d.ts +2 -1
  203. package/dist/session/fake-session.d.ts +37 -0
  204. package/dist/session/fake-session.js +81 -0
  205. package/dist/session/lease-visibility.d.ts +2 -1
  206. package/dist/session/live-control-tools.js +6 -0
  207. package/dist/session/no-session-diagnosis.d.ts +17 -1
  208. package/dist/session/no-session-diagnosis.js +32 -6
  209. package/dist/session/no-session-next-action.d.ts +14 -0
  210. package/dist/session/no-session-next-action.js +18 -0
  211. package/dist/session/no-session-watch.js +46 -3
  212. package/dist/session/registered-projects.d.ts +54 -0
  213. package/dist/session/registered-projects.js +73 -0
  214. package/dist/session/review-store.d.ts +2 -1
  215. package/dist/session/session-health.d.ts +62 -1
  216. package/dist/session/session-health.js +82 -2
  217. package/dist/session/session-manager.d.ts +11 -1
  218. package/dist/session/session-manager.js +18 -3
  219. package/dist/session/session-replaced.d.ts +24 -0
  220. package/dist/session/session-replaced.js +29 -0
  221. package/dist/session/session-successor.js +20 -4
  222. package/dist/session/session.d.ts +13 -5
  223. package/dist/session/session.js +31 -23
  224. package/dist/setup/agent-configs.d.ts +3 -2
  225. package/dist/setup/agent-writer.d.ts +3 -2
  226. package/dist/setup/approval-migration.d.ts +2 -1
  227. package/dist/setup/approval-migration.js +1 -1
  228. package/dist/setup/auto-approve.d.ts +11 -19
  229. package/dist/setup/auto-approve.js +15 -12
  230. package/dist/{init → setup}/confirm.js +1 -1
  231. package/dist/setup/desktop-shape.d.ts +3 -2
  232. package/dist/setup/drive-agent.d.ts +2 -3
  233. package/dist/setup/drive-agent.js +1 -1
  234. package/dist/setup/drive-plan.d.ts +3 -2
  235. package/dist/setup/ensure-daemon.d.ts +2 -1
  236. package/dist/setup/existing-app.d.ts +12 -0
  237. package/dist/setup/existing-app.js +13 -0
  238. package/dist/setup/init-host.d.ts +11 -0
  239. package/dist/setup/init-host.js +26 -0
  240. package/dist/setup/init-runtime.d.ts +4 -10
  241. package/dist/setup/init-runtime.js +23 -16
  242. package/dist/setup/license-key.d.ts +2 -1
  243. package/dist/setup/listeners.d.ts +2 -1
  244. package/dist/setup/loopback-probe-urls.d.ts +18 -0
  245. package/dist/setup/loopback-probe-urls.js +52 -0
  246. package/dist/setup/node-effects.d.ts +3 -0
  247. package/dist/setup/node-effects.js +26 -11
  248. package/dist/setup/page-probe.d.ts +5 -0
  249. package/dist/setup/relaunch.d.ts +2 -1
  250. package/dist/setup/run-setup.d.ts +5 -1
  251. package/dist/setup/run-setup.js +141 -50
  252. package/dist/setup/setup-command.d.ts +3 -2
  253. package/dist/setup/setup-command.js +8 -0
  254. package/dist/setup/setup-options.d.ts +5 -3
  255. package/dist/setup/setup-options.js +3 -2
  256. package/dist/setup/wait-progress.d.ts +28 -0
  257. package/dist/setup/wait-progress.js +43 -0
  258. package/dist/telemetry/app-instrumented.d.ts +2 -1
  259. package/dist/telemetry/app-instrumented.js +1 -1
  260. package/dist/telemetry/automation-hint.d.ts +1 -1
  261. package/dist/telemetry/automation-hint.js +1 -1
  262. package/dist/telemetry/cli-telemetry.js +1 -1
  263. package/dist/telemetry/connect-failure.d.ts +1 -1
  264. package/dist/telemetry/connect-failure.js +1 -1
  265. package/dist/telemetry/crash-cause.d.ts +3 -2
  266. package/dist/telemetry/crash-cause.js +1 -1
  267. package/dist/telemetry/daemon-telemetry.d.ts +3 -2
  268. package/dist/telemetry/daemon-telemetry.js +1 -1
  269. package/dist/telemetry/error-class.d.ts +3 -2
  270. package/dist/telemetry/error-class.js +1 -1
  271. package/dist/telemetry/feedback-cli.js +1 -1
  272. package/dist/telemetry/feedback-context.d.ts +3 -2
  273. package/dist/telemetry/feedback-context.js +5 -4
  274. package/dist/telemetry/feedback-local.d.ts +3 -2
  275. package/dist/telemetry/feedback.d.ts +4 -3
  276. package/dist/telemetry/feedback.js +1 -1
  277. package/dist/telemetry/git-facts.d.ts +2 -2
  278. package/dist/telemetry/git-facts.js +1 -1
  279. package/dist/telemetry/identify.d.ts +1 -1
  280. package/dist/telemetry/identify.js +1 -1
  281. package/dist/telemetry/init-telemetry.d.ts +2 -14
  282. package/dist/telemetry/init-telemetry.js +4 -13
  283. package/dist/telemetry/install-source.d.ts +7 -13
  284. package/dist/telemetry/install-source.js +7 -29
  285. package/dist/telemetry/license-activation.d.ts +3 -2
  286. package/dist/telemetry/license-activation.js +1 -1
  287. package/dist/telemetry/machine-snapshot.d.ts +1 -1
  288. package/dist/telemetry/mcp-connection.js +1 -1
  289. package/dist/telemetry/project-profile.d.ts +0 -14
  290. package/dist/telemetry/project-profile.js +1 -1
  291. package/dist/telemetry/proxy-telemetry.js +1 -1
  292. package/dist/telemetry/session-metrics.d.ts +1 -1
  293. package/dist/telemetry/session-metrics.js +1 -1
  294. package/dist/telemetry/telemetry.d.ts +1 -17
  295. package/dist/telemetry/telemetry.js +4 -4
  296. package/dist/telemetry/tool-refused.d.ts +1 -1
  297. package/dist/telemetry/tool-refused.js +1 -1
  298. package/dist/tools/act-observation.d.ts +3 -2
  299. package/dist/tools/act-preflight.js +8 -3
  300. package/dist/tools/act-target.d.ts +4 -0
  301. package/dist/tools/act-target.js +14 -5
  302. package/dist/tools/act-tools.d.ts +0 -2
  303. package/dist/tools/act-tools.js +45 -15
  304. package/dist/tools/assert-source.d.ts +30 -0
  305. package/dist/tools/assert-source.js +21 -0
  306. package/dist/tools/assert-verdict.d.ts +1 -1
  307. package/dist/tools/assert-verdict.js +14 -4
  308. package/dist/tools/browser-tools.js +28 -8
  309. package/dist/tools/complete-snapshot.d.ts +3 -2
  310. package/dist/tools/dynamic-tools.js +8 -0
  311. package/dist/tools/error-recovery.d.ts +10 -0
  312. package/dist/tools/error-recovery.js +23 -0
  313. package/dist/tools/feedback-tools.js +1 -1
  314. package/dist/tools/invoke-tool.js +2 -0
  315. package/dist/tools/lease-tools.d.ts +48 -20
  316. package/dist/tools/lease-tools.js +311 -76
  317. package/dist/tools/lineage-tools.d.ts +14 -0
  318. package/dist/tools/lineage-tools.js +64 -0
  319. package/dist/tools/merge-tools.js +1 -3
  320. package/dist/tools/navigate-arrival.d.ts +45 -1
  321. package/dist/tools/navigate-arrival.js +39 -6
  322. package/dist/tools/navigate-result.d.ts +8 -1
  323. package/dist/tools/navigate-result.js +14 -4
  324. package/dist/tools/numeric-bounds.d.ts +0 -11
  325. package/dist/tools/numeric-bounds.js +4 -4
  326. package/dist/tools/observe-tools.js +22 -26
  327. package/dist/tools/observed-nothing.d.ts +9 -0
  328. package/dist/tools/observed-nothing.js +2 -1
  329. package/dist/tools/read-tools.js +12 -17
  330. package/dist/tools/real-input-attempt.d.ts +14 -6
  331. package/dist/tools/real-input-attempt.js +75 -18
  332. package/dist/tools/reconcile-tools.js +2 -1
  333. package/dist/tools/reload-result.d.ts +6 -1
  334. package/dist/tools/reload-result.js +7 -2
  335. package/dist/tools/resolve-target.d.ts +7 -1
  336. package/dist/tools/resolve-target.js +72 -9
  337. package/dist/tools/settle-in-flight.d.ts +1 -1
  338. package/dist/tools/settle-in-flight.js +9 -2
  339. package/dist/tools/snapshot-delta.d.ts +24 -1
  340. package/dist/tools/snapshot-delta.js +66 -7
  341. package/dist/tools/tool-names.d.ts +1 -0
  342. package/dist/tools/tool-names.js +1 -0
  343. package/dist/tools/tool-surface.js +20 -2
  344. package/dist/tools/tools.js +120 -11
  345. package/dist/tools/verify-next-baton.d.ts +2 -1
  346. package/dist/update/sdk-sync.d.ts +1 -1
  347. package/dist/update/sdk-sync.js +1 -1
  348. package/dist/update/updater.js +1 -1
  349. package/dist/version/sdk-fix.d.ts +2 -1
  350. package/dist/version/version-skew.d.ts +0 -8
  351. package/dist/version/version-skew.js +1 -1
  352. package/docs/README.md +8 -6
  353. package/docs/actions.mdx +1 -1
  354. package/docs/agent-cheatsheet.md +4 -2
  355. package/docs/architecture.md +1 -1
  356. package/docs/cli/mcp.mdx +3 -1
  357. package/docs/cli/watch.mdx +1 -1
  358. package/docs/cli.mdx +1 -1
  359. package/docs/desktop.mdx +1 -1
  360. package/docs/docs.json +8 -3
  361. package/docs/faq.mdx +9 -0
  362. package/docs/first-drive.md +136 -0
  363. package/docs/frameworks.mdx +29 -7
  364. package/docs/gate-plan.md +2 -2
  365. package/docs/gates.md +19 -11
  366. package/docs/getting-started.md +7 -1
  367. package/docs/http-transport.md +164 -0
  368. package/docs/index.mdx +5 -3
  369. package/docs/install-manual.mdx +1 -1
  370. package/docs/packages/tauri.mdx +1 -1
  371. package/docs/packages.mdx +15 -3
  372. package/docs/platform-integration.md +1 -1
  373. package/docs/predicates.mdx +2 -0
  374. package/docs/system-map.md +2 -2
  375. package/docs/telemetry-contract.md +4 -4
  376. package/docs/telemetry-events.mdx +1 -1
  377. package/docs/telemetry.md +3 -3
  378. package/docs/tools-navigate.mdx +14 -6
  379. package/docs/usage.md +4 -4
  380. package/package.json +7 -4
  381. package/dist/init/agent-rules.d.ts +0 -116
  382. package/dist/init/agent-rules.js +0 -279
  383. package/dist/init/app-choice.d.ts +0 -20
  384. package/dist/init/app-choice.js +0 -25
  385. package/dist/init/astro-host.d.ts +0 -21
  386. package/dist/init/astro-host.js +0 -27
  387. package/dist/init/astro-patch.d.ts +0 -33
  388. package/dist/init/astro-patch.js +0 -300
  389. package/dist/init/capabilities.d.ts +0 -40
  390. package/dist/init/capabilities.js +0 -150
  391. package/dist/init/closing-hint.d.ts +0 -36
  392. package/dist/init/closing-hint.js +0 -120
  393. package/dist/init/connect-steps.d.ts +0 -2
  394. package/dist/init/connect-steps.js +0 -50
  395. package/dist/init/cra.d.ts +0 -79
  396. package/dist/init/cra.js +0 -178
  397. package/dist/init/csp-check.d.ts +0 -38
  398. package/dist/init/csp-check.js +0 -107
  399. package/dist/init/csp-doctor.d.ts +0 -32
  400. package/dist/init/csp-doctor.js +0 -54
  401. package/dist/init/desktop-doctor.d.ts +0 -41
  402. package/dist/init/desktop-doctor.js +0 -198
  403. package/dist/init/detect.d.ts +0 -96
  404. package/dist/init/detect.js +0 -209
  405. package/dist/init/dev-script.d.ts +0 -50
  406. package/dist/init/dev-script.js +0 -66
  407. package/dist/init/format-generated.d.ts +0 -18
  408. package/dist/init/format-generated.js +0 -39
  409. package/dist/init/install-hint.d.ts +0 -9
  410. package/dist/init/install-hint.js +0 -29
  411. package/dist/init/mcp-clients.d.ts +0 -89
  412. package/dist/init/mcp-clients.js +0 -299
  413. package/dist/init/mcp-registered.d.ts +0 -12
  414. package/dist/init/mcp-registered.js +0 -14
  415. package/dist/init/mcp.d.ts +0 -89
  416. package/dist/init/mcp.js +0 -129
  417. package/dist/init/next-patch.d.ts +0 -39
  418. package/dist/init/next-patch.js +0 -196
  419. package/dist/init/node-io.d.ts +0 -7
  420. package/dist/init/node-io.js +0 -117
  421. package/dist/init/non-js-project.d.ts +0 -27
  422. package/dist/init/non-js-project.js +0 -84
  423. package/dist/init/patch-kind.d.ts +0 -20
  424. package/dist/init/patch-kind.js +0 -10
  425. package/dist/init/plan-framework.d.ts +0 -64
  426. package/dist/init/plan-framework.js +0 -565
  427. package/dist/init/plan.d.ts +0 -242
  428. package/dist/init/plan.js +0 -703
  429. package/dist/init/preflight.d.ts +0 -28
  430. package/dist/init/preflight.js +0 -38
  431. package/dist/init/project-id.d.ts +0 -12
  432. package/dist/init/project-id.js +0 -25
  433. package/dist/init/refresh-rules.d.ts +0 -39
  434. package/dist/init/refresh-rules.js +0 -51
  435. package/dist/init/run.d.ts +0 -117
  436. package/dist/init/run.js +0 -806
  437. package/dist/init/slash-command.d.ts +0 -27
  438. package/dist/init/slash-command.js +0 -132
  439. package/dist/init/snippets.d.ts +0 -134
  440. package/dist/init/snippets.js +0 -572
  441. package/dist/init/vite-config.d.ts +0 -18
  442. package/dist/init/vite-config.js +0 -114
  443. package/dist/init/windows-quote.d.ts +0 -38
  444. package/dist/init/windows-quote.js +0 -70
  445. package/dist/init/workspace-apps.d.ts +0 -30
  446. package/dist/init/workspace-apps.js +0 -178
  447. package/dist/project/remember-project.d.ts +0 -24
  448. package/dist/project/remember-project.js +0 -40
  449. /package/dist/{init → setup}/confirm.d.ts +0 -0
@@ -9,7 +9,7 @@
9
9
  * cloud copy is an enhancement, so a push error is logged and swallowed.
10
10
  */
11
11
  import { z } from 'zod';
12
- import type { FlowFile, ReticleVerificationRun, RunRecord } from '@reticlehq/core';
12
+ import { type FlowFile, type ReticleVerificationRun, type RunRecord, type VerifyProgressEvent } from '@reticlehq/core';
13
13
  /** Env var names — the presence of BOTH is what "logged in" means for cloud sync. */
14
14
  export declare const CloudEnv: {
15
15
  readonly URL: "RETICLE_CLOUD_URL";
@@ -115,21 +115,21 @@ declare const ServerVerificationSchema: z.ZodObject<{
115
115
  }>, "many">;
116
116
  summary: z.ZodString;
117
117
  }, "strip", z.ZodTypeAny, {
118
+ verificationId: string;
118
119
  verdict: string | null;
119
120
  flows: {
120
121
  status: string;
121
122
  name: string;
122
123
  }[];
123
124
  summary: string;
124
- verificationId: string;
125
125
  }, {
126
+ verificationId: string;
126
127
  verdict: string | null;
127
128
  flows: {
128
129
  status: string;
129
130
  name: string;
130
131
  }[];
131
132
  summary: string;
132
- verificationId: string;
133
133
  }>;
134
134
  export type ServerVerification = z.infer<typeof ServerVerificationSchema>;
135
135
  /**
@@ -144,4 +144,47 @@ export declare function submitServerVerification(body: {
144
144
  flows: string[];
145
145
  source: string;
146
146
  }, config: CloudConfig | null, fetchImpl: FetchPostJsonLike): Promise<ServerVerification | null>;
147
+ /**
148
+ * Narrate a run in flight to the dashboard.
149
+ *
150
+ * ## Why this exists
151
+ *
152
+ * Everything else here syncs a FINISHED artifact. That is the right shape for evidence and the wrong
153
+ * shape for the minutes a verification is actually running: a browser launches, flows replay one at
154
+ * a time, and until the run ends the dashboard has nothing to show but a spinner. A run that is
155
+ * working and a run that has died are indistinguishable for the whole of its duration, and somebody
156
+ * watching sat on exactly that ambiguity for fifteen minutes.
157
+ *
158
+ * ## Why it is batched
159
+ *
160
+ * One request per flow is one request per flow. A fifty-flow suite would open a hundred connections
161
+ * to say things that are only interesting in aggregate, so events accumulate and go out on a timer.
162
+ * Two seconds is well under the time a person waits before deciding a page is broken, and well over
163
+ * the time a fast flow takes — so a burst of quick replays becomes one request, not twenty.
164
+ *
165
+ * ## Why it can never matter
166
+ *
167
+ * Opt-in like every other cloud call (no credentials → no-op, nothing leaves the machine), and
168
+ * best-effort at every layer: a failed flush is dropped, never retried and never surfaced. This is
169
+ * narration. The run artifact is the record, it syncs separately, and nothing here is graded or
170
+ * allowed to influence a verdict or an exit code.
171
+ */
172
+ export declare function syncProgressToCloud(runId: string, events: readonly VerifyProgressEvent[], config: CloudConfig | null, fetchImpl: FetchLike): Promise<SyncResult>;
173
+ /** How long events accumulate before a flush. See the batching note above. */
174
+ export declare const PROGRESS_FLUSH_MS = 2000;
175
+ /**
176
+ * A listener that buffers progress and ships it on a timer.
177
+ *
178
+ * Returned with its own `flush` and `stop` so the caller can push the last events out when the run
179
+ * ends — otherwise the most interesting event of all, the one saying it finished, waits for a timer
180
+ * that is about to be cleared.
181
+ *
182
+ * The timer is `unref`'d where the runtime supports it: a narration timer must never be the reason a
183
+ * CLI process stays alive after its work is done.
184
+ */
185
+ export declare function createProgressReporter(runId: string, config: CloudConfig | null, fetchImpl: FetchLike, flushMs?: number): {
186
+ onProgress: (event: VerifyProgressEvent) => void;
187
+ flush: () => Promise<void>;
188
+ stop: () => void;
189
+ };
147
190
  export {};
@@ -9,6 +9,7 @@
9
9
  * cloud copy is an enhancement, so a push error is logged and swallowed.
10
10
  */
11
11
  import { z } from 'zod';
12
+ import { VERIFY_PROGRESS_MAX_EVENTS, } from '@reticlehq/core';
12
13
  /** Env var names — the presence of BOTH is what "logged in" means for cloud sync. */
13
14
  export const CloudEnv = {
14
15
  URL: 'RETICLE_CLOUD_URL',
@@ -20,6 +21,7 @@ const CLOUD_RUNS_PATH = '/v1/runs';
20
21
  const CLOUD_PROJECT_RUNS_PATH = '/v1/project/runs';
21
22
  const CLOUD_PROJECT_REGRESSION_PATH = '/v1/project/regression';
22
23
  const CLOUD_VERIFICATIONS_PATH = '/v1/verifications';
24
+ const CLOUD_PROGRESS_PATH = '/v1/connect/progress';
23
25
  /** Resolve cloud credentials from the environment, or null when not logged in (sync disabled). */
24
26
  export function resolveCloudConfig(env) {
25
27
  const url = env[CloudEnv.URL];
@@ -236,3 +238,117 @@ export async function submitServerVerification(body, config, fetchImpl) {
236
238
  return null;
237
239
  }
238
240
  }
241
+ /**
242
+ * Narrate a run in flight to the dashboard.
243
+ *
244
+ * ## Why this exists
245
+ *
246
+ * Everything else here syncs a FINISHED artifact. That is the right shape for evidence and the wrong
247
+ * shape for the minutes a verification is actually running: a browser launches, flows replay one at
248
+ * a time, and until the run ends the dashboard has nothing to show but a spinner. A run that is
249
+ * working and a run that has died are indistinguishable for the whole of its duration, and somebody
250
+ * watching sat on exactly that ambiguity for fifteen minutes.
251
+ *
252
+ * ## Why it is batched
253
+ *
254
+ * One request per flow is one request per flow. A fifty-flow suite would open a hundred connections
255
+ * to say things that are only interesting in aggregate, so events accumulate and go out on a timer.
256
+ * Two seconds is well under the time a person waits before deciding a page is broken, and well over
257
+ * the time a fast flow takes — so a burst of quick replays becomes one request, not twenty.
258
+ *
259
+ * ## Why it can never matter
260
+ *
261
+ * Opt-in like every other cloud call (no credentials → no-op, nothing leaves the machine), and
262
+ * best-effort at every layer: a failed flush is dropped, never retried and never surfaced. This is
263
+ * narration. The run artifact is the record, it syncs separately, and nothing here is graded or
264
+ * allowed to influence a verdict or an exit code.
265
+ */
266
+ export async function syncProgressToCloud(runId, events, config, fetchImpl) {
267
+ if (null === config)
268
+ return { outcome: SyncOutcome.SKIPPED };
269
+ if (0 === events.length)
270
+ return { outcome: SyncOutcome.SKIPPED };
271
+ try {
272
+ const res = await fetchImpl(`${config.url}${CLOUD_PROGRESS_PATH}`, {
273
+ method: 'POST',
274
+ headers: {
275
+ 'content-type': 'application/json',
276
+ authorization: `Bearer ${config.apiKey}`,
277
+ },
278
+ // Bounded on the way OUT as well as in the schema: a receiver that rejects an oversized batch
279
+ // would drop the whole window, and the newest events are the ones somebody is waiting on.
280
+ body: JSON.stringify({ runId, events: events.slice(-VERIFY_PROGRESS_MAX_EVENTS) }),
281
+ });
282
+ return res.ok
283
+ ? { outcome: SyncOutcome.SYNCED, status: res.status }
284
+ : { outcome: SyncOutcome.FAILED, status: res.status };
285
+ }
286
+ catch (err) {
287
+ return { outcome: SyncOutcome.FAILED, error: err instanceof Error ? err.message : String(err) };
288
+ }
289
+ }
290
+ /** How long events accumulate before a flush. See the batching note above. */
291
+ export const PROGRESS_FLUSH_MS = 2_000;
292
+ /**
293
+ * A listener that buffers progress and ships it on a timer.
294
+ *
295
+ * Returned with its own `flush` and `stop` so the caller can push the last events out when the run
296
+ * ends — otherwise the most interesting event of all, the one saying it finished, waits for a timer
297
+ * that is about to be cleared.
298
+ *
299
+ * The timer is `unref`'d where the runtime supports it: a narration timer must never be the reason a
300
+ * CLI process stays alive after its work is done.
301
+ */
302
+ export function createProgressReporter(runId, config, fetchImpl, flushMs = PROGRESS_FLUSH_MS) {
303
+ /*
304
+ * The WHOLE window, re-sent each flush — not the events since the last one.
305
+ *
306
+ * The receiver REPLACES the row it keeps for a project rather than appending, because one row per
307
+ * project is what stops narration accumulating into a second, ungraded history of the run. A
308
+ * client that sent deltas against that would destroy its own story: each flush would overwrite the
309
+ * row with only the handful of events since the previous one, and a finished run would be
310
+ * represented by whatever its last two seconds happened to contain.
311
+ *
312
+ * Found by running a real verification and watching the dashboard freeze on flow 2 of 3.
313
+ *
314
+ * Re-sending is cheap and idempotent: the window is bounded, the events are tiny, and a replace is
315
+ * the same operation however many times it arrives. `dirty` keeps an unchanged window off the
316
+ * wire, so a run that has gone quiet stops posting rather than resending forever.
317
+ */
318
+ /* Named `accumulated`, not `window`: this is Node, and shadowing the global reads badly. */
319
+ const accumulated = [];
320
+ let dirty = false;
321
+ let timer = null;
322
+ const flush = async () => {
323
+ if (!dirty || 0 === accumulated.length)
324
+ return;
325
+ // Cleared BEFORE the await so a slow flush does not let the next tick send the same window
326
+ // again; a genuinely new event during the send re-sets it and the next tick picks it up.
327
+ dirty = false;
328
+ await syncProgressToCloud(runId, [...accumulated], config, fetchImpl).catch(() => undefined);
329
+ };
330
+ const start = () => {
331
+ if (null !== timer || null === config)
332
+ return;
333
+ timer = setInterval(() => void flush(), flushMs);
334
+ timer.unref?.();
335
+ };
336
+ return {
337
+ onProgress: (event) => {
338
+ if (null === config)
339
+ return; // not logged in: nothing is buffered and nothing is sent
340
+ // Bounded, keeping the NEWEST: they are the ones somebody is waiting on.
341
+ if (accumulated.length >= VERIFY_PROGRESS_MAX_EVENTS)
342
+ accumulated.shift();
343
+ accumulated.push(event);
344
+ dirty = true;
345
+ start();
346
+ },
347
+ flush,
348
+ stop: () => {
349
+ if (null !== timer)
350
+ clearInterval(timer);
351
+ timer = null;
352
+ },
353
+ };
354
+ }
@@ -4,7 +4,7 @@
4
4
  * A list rather than three hand-written blocks so adding a fourth is one line and cannot be
5
5
  * half-done — the bundle, the hashing and the reporting all walk this.
6
6
  */
7
- export declare const DERIVED_RECORDS: readonly [{
7
+ declare const DERIVED_RECORDS: readonly [{
8
8
  readonly kind: "impact";
9
9
  readonly file: "impact.json";
10
10
  }, {
@@ -14,7 +14,7 @@ export declare const DERIVED_RECORDS: readonly [{
14
14
  readonly kind: "intent";
15
15
  readonly file: "intent.json";
16
16
  }];
17
- export type DerivedKind = (typeof DERIVED_RECORDS)[number]['kind'];
17
+ type DerivedKind = (typeof DERIVED_RECORDS)[number]['kind'];
18
18
  /** What the machine reads from disk. Injected so a cycle is testable with no filesystem at all. */
19
19
  export interface SyncSource {
20
20
  /** Every run artifact currently on disk. */
@@ -35,7 +35,7 @@ export interface SyncSink {
35
35
  writeState: (state: CloudSyncState) => void;
36
36
  }
37
37
  /** A decision a human made on the dashboard, as the machine stores it. */
38
- export interface PulledIssue {
38
+ interface PulledIssue {
39
39
  status: string;
40
40
  flowName: string | null;
41
41
  title: string;
@@ -82,7 +82,7 @@ export interface SyncReport {
82
82
  /** Set when the cycle could not complete. The local record is untouched either way. */
83
83
  error?: string;
84
84
  }
85
- export interface SyncDeps {
85
+ interface SyncDeps {
86
86
  config: {
87
87
  url: string;
88
88
  apiKey: string;
@@ -108,3 +108,4 @@ export interface SyncDeps {
108
108
  export declare function runSyncCycle(deps: SyncDeps): Promise<SyncReport>;
109
109
  /** One line a human can read, for the CLI and the daemon log. */
110
110
  export declare function describeSync(report: SyncReport): string;
111
+ export {};
@@ -42,7 +42,7 @@ const SYNC_PULL_PATH = '/v1/sync/pull';
42
42
  * A list rather than three hand-written blocks so adding a fourth is one line and cannot be
43
43
  * half-done — the bundle, the hashing and the reporting all walk this.
44
44
  */
45
- export const DERIVED_RECORDS = [
45
+ const DERIVED_RECORDS = [
46
46
  { kind: 'impact', file: ReticleDir.IMPACT_FILE },
47
47
  { kind: 'flake', file: ReticleDir.FLAKE_FILE },
48
48
  { kind: 'intent', file: ReticleDir.INTENT_FILE },
@@ -1,23 +1,6 @@
1
1
  import { type SyncReport } from './sync-cycle.js';
2
2
  import type { ProjectCloud } from './cloud-config.js';
3
- /** How often the daemon cycles. See the note above on why this is a constant and not a curve. */
4
- export declare const DAEMON_SYNC_INTERVAL_MS = 60000;
5
- /**
6
- * The cadence while something is actually happening.
7
- *
8
- * A fixed minute is the wrong shape for both states it has to cover. During a drive the ledger
9
- * changes on every tool call and a minute of lag is what makes a dashboard feel dead; idle, a minute
10
- * is already more often than nothing has changed deserves.
11
- *
12
- * So the interval follows the work: a cycle that MOVED something earns the fast rate, a cycle that
13
- * moved nothing backs off to the slow one. No session plumbing and no new configuration — activity
14
- * is inferred from what the last cycle actually sent, which is the only honest evidence available.
15
- *
16
- * Deliberately not per-tool-call. A push in the tool path would put the network in the agent's inner
17
- * loop, and verification working with the network down is a promise this product makes.
18
- */
19
- export declare const DAEMON_SYNC_ACTIVE_INTERVAL_MS = 5000;
20
- export interface SyncDaemonDeps {
3
+ interface SyncDaemonDeps {
21
4
  reticleRoot: string;
22
5
  /** Resolved per tick, not once: a repo linked while the daemon is alive starts syncing itself. */
23
6
  cloud: () => Promise<ProjectCloud>;
@@ -46,7 +29,7 @@ export interface SyncDaemonDeps {
46
29
  text: string;
47
30
  }>;
48
31
  }
49
- export interface SyncDaemon {
32
+ interface SyncDaemon {
50
33
  /** Run one cycle now, whatever the timer is doing. Returns undefined when not linked. */
51
34
  syncNow: () => Promise<SyncReport | undefined>;
52
35
  /**
@@ -67,3 +50,4 @@ export interface SyncDaemon {
67
50
  * nothing until one appears, which is what lets `reticle link` take effect without a restart.
68
51
  */
69
52
  export declare function startSyncDaemon(deps: SyncDaemonDeps): SyncDaemon;
53
+ export {};
@@ -25,7 +25,7 @@ import { log } from '../log.js';
25
25
  import { describeSync, runSyncCycle } from './sync-cycle.js';
26
26
  import { diskSink, diskSource, readCloudState } from './sync-disk.js';
27
27
  /** How often the daemon cycles. See the note above on why this is a constant and not a curve. */
28
- export const DAEMON_SYNC_INTERVAL_MS = 60_000;
28
+ const DAEMON_SYNC_INTERVAL_MS = 60_000;
29
29
  /**
30
30
  * The cadence while something is actually happening.
31
31
  *
@@ -40,7 +40,7 @@ export const DAEMON_SYNC_INTERVAL_MS = 60_000;
40
40
  * Deliberately not per-tool-call. A push in the tool path would put the network in the agent's inner
41
41
  * loop, and verification working with the network down is a promise this product makes.
42
42
  */
43
- export const DAEMON_SYNC_ACTIVE_INTERVAL_MS = 5_000;
43
+ const DAEMON_SYNC_ACTIVE_INTERVAL_MS = 5_000;
44
44
  /** Given to the first cycle so a freshly-started daemon does not race the session that woke it. */
45
45
  const FIRST_CYCLE_DELAY_MS = 5_000;
46
46
  /**
@@ -51,7 +51,7 @@ export const CRAWL_TOOLS = [
51
51
  source: z
52
52
  .string()
53
53
  .optional()
54
- .describe('Where the control is written, as `file:line` — open this to fix it.'),
54
+ .describe('Where the fault is written, as `file:line`; console errors prefer their own stack and fall back to the clicked control.'),
55
55
  })),
56
56
  counts: z.record(z.number()),
57
57
  visited: z.array(z.string()).describe('The controls actually clicked, in order.'),
@@ -45,12 +45,12 @@ export interface CrawlAnomaly {
45
45
  desc: string;
46
46
  detail: string;
47
47
  /**
48
- * Where the control is written, as `file:line`.
48
+ * Where the fault is written, as `file:line`.
49
49
  *
50
- * A crawl reports problems across a whole app, so it is the output most likely to be read as a
51
- * work list — and "e42 does nothing" is a work list item that starts with a search. The location
52
- * comes free: the crawl already clicks each control, and an act result carries the source captured
53
- * alongside its anchor. Absent in production builds and apps without the build plugin.
50
+ * Console faults prefer their own component/error stack. The clicked control stays in `ref` and
51
+ * `desc`, and its source is only the fallback: a navigation link explains what exposed a render
52
+ * crash, while the crashing component explains what to fix. Other anomaly kinds still use the
53
+ * control source captured alongside the action. Absent when neither source can be localized.
54
54
  */
55
55
  source?: string;
56
56
  }
@@ -2,6 +2,7 @@ import { span } from '../trace.js';
2
2
  import { ActionType, CRAWL_DEFAULTS, CrawlAnomalyKind, DANGEROUS_ACTION_CONFIRM_ARG, EventType, ReticleCommand, } from '@reticlehq/core';
3
3
  import { crawlEmptyNote } from './crawl-empty.js';
4
4
  import { parseInteractive, asRecord, asNumber, asString, sourceOf, } from '../tools/tools-helpers.js';
5
+ import { isSessionReplacedError } from '../session/session-replaced.js';
5
6
  import { ReticleTool } from '../tools/tool-names.js';
6
7
  import { findContradictions } from '../events/contradictions.js';
7
8
  /** Said out loud when the page was larger than one snapshot, so a clean sweep cannot mean "all clear". */
@@ -38,6 +39,115 @@ function isActivity(e) {
38
39
  function isConsoleError(e) {
39
40
  return e.type === EventType.CONSOLE_ERROR || e.type === EventType.ERROR_UNCAUGHT;
40
41
  }
42
+ const SOURCE_FRAME = /^(.*):(\d+):(\d+)$/;
43
+ const COMPONENT_STACK_FIELDS = ['componentStack', 'message'];
44
+ const URL_SOURCE_PROTOCOLS = new Set([
45
+ 'http:',
46
+ 'https:',
47
+ 'file:',
48
+ 'webpack:',
49
+ 'webpack-internal:',
50
+ ]);
51
+ /** Turn a browser/dev-server frame URL into the file-shaped pointer the rest of Reticle emits. */
52
+ function sourceFile(raw) {
53
+ let file = raw.trim();
54
+ if (0 === file.length)
55
+ return undefined;
56
+ try {
57
+ const parsed = new URL(file);
58
+ if (URL_SOURCE_PROTOCOLS.has(parsed.protocol))
59
+ file = decodeURIComponent(parsed.pathname);
60
+ }
61
+ catch {
62
+ // A repo-relative or Windows path is already the more useful form; keep it as-is.
63
+ }
64
+ file = file.replace(/[?#].*$/, '').replace(/\\/g, '/');
65
+ const bundledPrefix = file.lastIndexOf('/./');
66
+ if (-1 !== bundledPrefix)
67
+ file = file.slice(bundledPrefix + 3);
68
+ if (file.startsWith('/@fs/'))
69
+ file = file.slice('/@fs/'.length);
70
+ if (/^\/[A-Za-z]:\//.test(file))
71
+ file = file.slice(1);
72
+ else
73
+ file = file.replace(/^\/+/, '');
74
+ if (file.startsWith('./'))
75
+ file = file.slice(2);
76
+ return 0 === file.length ? undefined : file;
77
+ }
78
+ /** The location part of a Chrome/React or Firefox stack frame. */
79
+ function frameLocation(line) {
80
+ let frame = line.trim();
81
+ if (frame.startsWith('at '))
82
+ frame = frame.slice(3).trim();
83
+ else {
84
+ const firefoxSeparator = frame.lastIndexOf('@');
85
+ if (-1 === firefoxSeparator)
86
+ return undefined;
87
+ frame = frame.slice(firefoxSeparator + 1);
88
+ }
89
+ const opening = frame.lastIndexOf('(');
90
+ if (-1 !== opening && frame.endsWith(')'))
91
+ return frame.slice(opening + 1, -1);
92
+ return frame;
93
+ }
94
+ function isFrameworkSource(file) {
95
+ const normalized = `/${file.toLowerCase().replace(/^\/+/, '')}`;
96
+ return normalized.includes('/node_modules/') || normalized.startsWith('/node:');
97
+ }
98
+ /** First editable app frame in a component/error stack, excluding framework internals. */
99
+ function sourceFromStack(stack) {
100
+ for (const line of stack.split('\n')) {
101
+ const location = frameLocation(line);
102
+ if (location === undefined)
103
+ continue;
104
+ const match = SOURCE_FRAME.exec(location);
105
+ if (null === match)
106
+ continue;
107
+ const [, rawFile, rawLine] = match;
108
+ if (rawFile === undefined || rawLine === undefined)
109
+ continue;
110
+ const file = sourceFile(rawFile);
111
+ const lineNumber = Number(rawLine);
112
+ if (file === undefined ||
113
+ isFrameworkSource(file) ||
114
+ !Number.isSafeInteger(lineNumber) ||
115
+ lineNumber <= 0) {
116
+ continue;
117
+ }
118
+ return `${file}:${String(lineNumber)}`;
119
+ }
120
+ return undefined;
121
+ }
122
+ /** Prefer where a console fault occurred over the control whose click merely exposed it. */
123
+ function sourceFromConsoleError(event) {
124
+ // React currently passes its component stack as a console.error string argument, so the browser
125
+ // observer carries it inside `message`; richer producers can send `componentStack` directly.
126
+ for (const field of COMPONENT_STACK_FIELDS) {
127
+ const componentStack = asString(event.data[field]);
128
+ if (componentStack === undefined)
129
+ continue;
130
+ const componentSource = sourceFromStack(componentStack);
131
+ if (componentSource !== undefined)
132
+ return componentSource;
133
+ }
134
+ // The ordinary error stack can also identify a component better than ErrorEvent.filename (for
135
+ // example, a bundle or click-handler frame), so try app frames before the structured fallback.
136
+ const stack = asString(event.data['stack']);
137
+ if (stack !== undefined) {
138
+ const stackSource = sourceFromStack(stack);
139
+ if (stackSource !== undefined)
140
+ return stackSource;
141
+ }
142
+ const directFile = asString(event.data['source']);
143
+ const directLine = asNumber(event.data['line']);
144
+ if (directFile === undefined || directLine === undefined || directLine <= 0)
145
+ return undefined;
146
+ const file = sourceFile(directFile);
147
+ return file === undefined || isFrameworkSource(file)
148
+ ? undefined
149
+ : `${file}:${String(directLine)}`;
150
+ }
41
151
  function failedRequests(events, floor) {
42
152
  return events.filter((e) => {
43
153
  if (e.type !== EventType.NET_REQUEST)
@@ -163,6 +273,18 @@ export async function crawl(session, opts, sleep) {
163
273
  const since = session.elapsed();
164
274
  session.beginAction?.(ReticleTool.CRAWL, { ref: item.ref, action: ActionType.CLICK });
165
275
  let act;
276
+ /*
277
+ * A full page load reconnects the SDK, which rejects whatever command was in flight.
278
+ *
279
+ * On a server-rendered app that is what following a link DOES — every navigation replaces the
280
+ * session — so the rejection is evidence the click worked, not that it failed. Crawl used to let
281
+ * it propagate and died on link one of every Django, Rails or plain-HTML app.
282
+ *
283
+ * Recorded as a navigation rather than swallowed: a control that took the page somewhere is the
284
+ * opposite of a dead one, and reporting it as dead would be the false negative crawl exists to
285
+ * find.
286
+ */
287
+ let navigatedAway = false;
166
288
  try {
167
289
  // One span per control clicked, so a slow crawl names the control rather than reporting a
168
290
  // single multi-second total. The settle sleep below is INSIDE it deliberately: it is part of
@@ -177,6 +299,11 @@ export async function crawl(session, opts, sleep) {
177
299
  return clicked;
178
300
  });
179
301
  }
302
+ catch (err) {
303
+ if (!isSessionReplacedError(err))
304
+ throw err;
305
+ navigatedAway = true;
306
+ }
180
307
  finally {
181
308
  // Close on every exit so a throw cannot leak the window onto the next control's events.
182
309
  session.finishAction?.();
@@ -184,16 +311,18 @@ export async function crawl(session, opts, sleep) {
184
311
  const events = session.eventsSince(since);
185
312
  // Captured at act time, so it survives a click that unmounts its own control.
186
313
  const src = sourceOf(asRecord(act?.result)['source']);
187
- const source = src === undefined ? {} : { source: `${src.file}:${String(src.line)}` };
314
+ const controlSource = src === undefined ? undefined : `${src.file}:${String(src.line)}`;
315
+ const source = controlSource === undefined ? {} : { source: controlSource };
188
316
  const errs = events.filter(isConsoleError);
189
317
  for (const e of errs) {
190
318
  counts.consoleErrors += 1;
319
+ const errorSource = sourceFromConsoleError(e) ?? controlSource;
191
320
  anomalies.push({
192
321
  kind: CrawlAnomalyKind.CONSOLE_ERROR,
193
322
  ref: item.ref,
194
323
  desc: item.desc,
195
324
  detail: asString(e.data['message']) ?? e.type,
196
- ...source,
325
+ ...(errorSource === undefined ? {} : { source: errorSource }),
197
326
  });
198
327
  }
199
328
  for (const e of failedRequests(events, CRAWL_DEFAULTS.FAILED_STATUS)) {
@@ -242,8 +371,13 @@ export async function crawl(session, opts, sleep) {
242
371
  // Deliberately NOT fixed by counting focus as activity: focus moving is not the app reacting, and
243
372
  // treating it as such would make a genuinely dead button that takes focus look alive — trading
244
373
  // noise for the false negative this check exists to catch.
245
- const dispatched = asRecord(act.result)['dispatched'] !== false && act.ok;
246
- if (dispatched &&
374
+ const dispatched = asRecord(act?.result)['dispatched'] !== false && true === act?.ok;
375
+ if (
376
+ // A control that took the page somewhere is the OPPOSITE of a dead one. Without this, every
377
+ // link on a server-rendered app would be reported as an anomaly by the check that exists to
378
+ // find controls which do nothing.
379
+ !navigatedAway &&
380
+ dispatched &&
247
381
  0 === errs.length &&
248
382
  !events.some(isActivity) &&
249
383
  !legitimatelyInert(item.desc)) {
@@ -67,7 +67,8 @@ export declare function installDaemonResilience(proc: ProcessLike, log: LogFn, o
67
67
  * dormant paths). A crashed-but-serving proxy answers the handshake and `tools/list` from cache; a
68
68
  * dead one answers nothing and needs a human.
69
69
  *
70
- * `onCrash` is for reporting only — it must not end the process.
70
+ * `onCrash` is for reporting only — it must not end the process. `onGone` is the ONE exception, and
71
+ * it is the subject of DISCONNECT_STORM below.
71
72
  */
72
- export declare function installProxyResilience(proc: ProcessLike, log: LogFn, onCrash?: (kind: CrashKind, cause: unknown) => void): void;
73
+ export declare function installProxyResilience(proc: ProcessLike, log: LogFn, onCrash?: (kind: CrashKind, cause: unknown) => void, onGone?: () => void): void;
73
74
  export {};
@@ -53,6 +53,24 @@ const DISCONNECT_CODES = new Set([
53
53
  * `connect_error` on `mcp_connection_lost`, which is the metric that can actually act on it.
54
54
  */
55
55
  const UNREACHABLE_CODES = new Set(['ECONNREFUSED']);
56
+ /**
57
+ * How many absorbed client disconnects mean the client is GONE rather than between reconnects.
58
+ *
59
+ * Absorbing a disconnect and continuing is right for one: the client closed a socket, the proxy
60
+ * rebuilds it. It is catastrophic for a stream of them, because the write that failed is retried
61
+ * immediately, fails identically, and is absorbed again — a tight loop with no backoff and no exit.
62
+ *
63
+ * Measured in the field: one proxy ran four days after its editor closed, at 97-98% of a core, with
64
+ * 1473 minutes of CPU time and ~930 MB/hour of identical log lines, every entry stamped to the same
65
+ * millisecond. Nothing in Reticle's own output showed it — the daemon beside it reported healthy,
66
+ * `sessions: 0` — so it was findable only by running `ps` by hand, and thirteen more idle pairs from
67
+ * earlier sessions were still resident behind it.
68
+ *
69
+ * Chosen well above the handful a genuine reconnect produces and far below a runaway. The proxy's
70
+ * whole purpose is to be the stdio server its editor launched; once that editor is gone there is
71
+ * nobody left to serve, and the correct thing is to leave rather than to burn a core proving it.
72
+ */
73
+ const DISCONNECT_STORM = 20;
56
74
  /** The expected-failure code this value carries, and which class it belongs to. */
57
75
  function expectedCode(value) {
58
76
  const raw = value?.code;
@@ -253,22 +271,43 @@ export function installDaemonResilience(proc, log, onFatal) {
253
271
  * dormant paths). A crashed-but-serving proxy answers the handshake and `tools/list` from cache; a
254
272
  * dead one answers nothing and needs a human.
255
273
  *
256
- * `onCrash` is for reporting only — it must not end the process.
274
+ * `onCrash` is for reporting only — it must not end the process. `onGone` is the ONE exception, and
275
+ * it is the subject of DISCONNECT_STORM below.
257
276
  */
258
- export function installProxyResilience(proc, log, onCrash = reportCrash) {
277
+ export function installProxyResilience(proc, log, onCrash = reportCrash, onGone = () => process.exit(0)) {
259
278
  const expected = {
260
279
  disconnected: 'reticle_mcp_proxy_client_disconnected',
261
280
  unreachable: 'reticle_mcp_proxy_daemon_unreachable',
262
281
  };
282
+ // "Absorb the disconnect and keep serving" is right for ONE disconnect and catastrophic for a
283
+ // stream of them — see DISCONNECT_STORM.
284
+ let disconnects = 0;
285
+ let gone = false;
286
+ const absorb = (value) => {
287
+ if (!absorbExpected(value, log, expected))
288
+ return false;
289
+ if (false === expectedCode(value)?.unreachable) {
290
+ disconnects += 1;
291
+ if (disconnects >= DISCONNECT_STORM && !gone) {
292
+ gone = true;
293
+ log('reticle_mcp_proxy_client_gone', {
294
+ disconnects,
295
+ note: 'the client end is gone for good, not between reconnects — exiting instead of retrying forever',
296
+ });
297
+ onGone();
298
+ }
299
+ }
300
+ return true;
301
+ };
263
302
  proc.on('unhandledRejection', (reason) => {
264
- if (absorbExpected(reason, log, expected))
303
+ if (absorb(reason))
265
304
  return;
266
305
  log('reticle_mcp_proxy_unhandled_rejection', { reason: describe(reason) });
267
306
  onCrash(CrashKind.UNHANDLED_REJECTION, reason);
268
307
  });
269
308
  proc.on('uncaughtException', (err) => {
270
309
  // The client closing its end of stdio is how this process is SUPPOSED to end its day.
271
- if (absorbExpected(err, log, expected))
310
+ if (absorb(err))
272
311
  return;
273
312
  log('reticle_mcp_proxy_uncaught_exception', {
274
313
  error: describe(err),
@@ -16,15 +16,6 @@
16
16
  * This module is the one answer both sides now use. It is pure and injectable: no process spawning,
17
17
  * no port binding, nothing that needs a real `~/.reticle` to test.
18
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
19
  /**
29
20
  * The port of the live daemon serving `projectId`, or undefined when this project has none.
30
21
  *
@@ -18,7 +18,7 @@
18
18
  */
19
19
  import { readdirSync, readFileSync } from 'node:fs';
20
20
  import { join } from 'node:path';
21
- import { DaemonRegistryEntrySchema, daemonRegistryPort, pickDaemonPort, } from '@reticlehq/core';
21
+ import { DaemonRegistryEntrySchema, daemonRegistryPort, pickDaemonPort, } from '@reticlehq/core/artifacts';
22
22
  /**
23
23
  * Every well-formed registry entry in `home`.
24
24
  *
@@ -26,7 +26,7 @@ import { DaemonRegistryEntrySchema, daemonRegistryPort, pickDaemonPort, } from '
26
26
  * processes that can be killed mid-write, and one bad file must not make a machine's daemons
27
27
  * undiscoverable. Validated through core's schema so a shape change cannot silently pass here.
28
28
  */
29
- export function readDaemonRegistry(home) {
29
+ function readDaemonRegistry(home) {
30
30
  let files;
31
31
  try {
32
32
  files = readdirSync(home);