@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
@@ -16,7 +16,7 @@ import { readProjectFramework, readProjectId } from '../cli/cli-port.js';
16
16
  import { hasConnectedBefore } from '../session/connection-memory.js';
17
17
  import { AGENT_DRIVING_ELSEWHERE, AGENT_DRIVING_HERE_AGAIN, watchersToNotify, } from '../session/lease-visibility.js';
18
18
  import { reticleStateHome } from '../daemon/daemon.js';
19
- import { RETICLE_URL_PARAM, RETICLE_DEFAULT_PORT } from '@reticlehq/core';
19
+ import { LeaseNotReadyReason, REDACTED_VALUE, RETICLE_URL_PARAM, RETICLE_DEFAULT_PORT, ReticleCommand, SeedStorageSchema, scrubKnownSecrets, } from '@reticlehq/core';
20
20
  import { ReticleTool } from './tool-names.js';
21
21
  import { asString } from './tools-helpers.js';
22
22
  import { chromiumHint } from '../cli/chromium-hint.js';
@@ -84,13 +84,67 @@ export function appendReticleParams(url, session, projectId) {
84
84
  return `${url}${sep}${RETICLE_URL_PARAM.SESSION}=${encodeURIComponent(session)}${proj}`;
85
85
  }
86
86
  }
87
+ function redactExactValue(text, value) {
88
+ if (0 === value.length)
89
+ return text;
90
+ const escaped = value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
91
+ return text.replace(new RegExp(`(^|[^a-zA-Z0-9])${escaped}(?=$|[^a-zA-Z0-9])`, 'g'), `$1${REDACTED_VALUE}`);
92
+ }
93
+ /**
94
+ * Scrub known secret shapes and raw seedStorage values (cookies, localStorage, sessionStorage)
95
+ * from an error string before it reaches daemon logs, metrics, or MCP error responses.
96
+ */
97
+ export function scrubSeedFromError(text, seed) {
98
+ let out = scrubKnownSecrets(text);
99
+ if (undefined !== seed && null !== seed && 'object' === typeof seed) {
100
+ const s = seed;
101
+ const cookies = s['cookies'];
102
+ if (undefined !== cookies && null !== cookies) {
103
+ if (Array.isArray(cookies)) {
104
+ for (const c of cookies) {
105
+ if (undefined !== c && null !== c && 'object' === typeof c) {
106
+ const val = c['value'];
107
+ if (undefined !== val && 'string' === typeof val) {
108
+ out = redactExactValue(out, val);
109
+ }
110
+ }
111
+ }
112
+ }
113
+ else if ('object' === typeof cookies) {
114
+ for (const v of Object.values(cookies)) {
115
+ if ('string' === typeof v) {
116
+ out = redactExactValue(out, v);
117
+ }
118
+ }
119
+ }
120
+ }
121
+ const local = s['local'];
122
+ if (undefined !== local && null !== local && 'object' === typeof local) {
123
+ for (const v of Object.values(local)) {
124
+ if ('string' === typeof v) {
125
+ out = redactExactValue(out, v);
126
+ }
127
+ }
128
+ }
129
+ const session = s['session'];
130
+ if (undefined !== session && null !== session && 'object' === typeof session) {
131
+ for (const v of Object.values(session)) {
132
+ if ('string' === typeof v) {
133
+ out = redactExactValue(out, v);
134
+ }
135
+ }
136
+ }
137
+ }
138
+ return out;
139
+ }
87
140
  /**
88
141
  * Turn a raw navigation failure (Playwright's `page.goto: net::ERR_… at <url>\nCall log:…`, often
89
142
  * with ANSI codes) into a short, clean reason — so the agent/user sees "is the app running?" instead
90
143
  * of an internals-leaking wall of text.
91
144
  */
92
- export function cleanNavError(err) {
93
- const msg = err instanceof Error ? err.message : String(err);
145
+ export function cleanNavError(err, seed) {
146
+ const rawMsg = err instanceof Error ? err.message : String(err);
147
+ const msg = scrubSeedFromError(rawMsg, seed);
94
148
  // Strip ANSI color codes (ESC[…m) Playwright emits — built via fromCharCode to keep the
95
149
  // control character out of a regex literal (no-control-regex).
96
150
  const ansi = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, 'g');
@@ -106,6 +160,44 @@ export function cleanNavError(err) {
106
160
  .trim()
107
161
  .slice(0, 100);
108
162
  }
163
+ /**
164
+ * Determine if a required seedStorage application precondition failed.
165
+ *
166
+ * Checks strong signals:
167
+ * 1. HTTP 401/403 status on the initial navigation.
168
+ * 2. Cross-origin redirect when localStorage or sessionStorage was requested (skipped by origin-scoping).
169
+ * 3. Clear redirect to an authentication/login endpoint when the requested URL was not a login page.
170
+ *
171
+ * Normal app redirects (e.g. / → /dashboard) are valid and return undefined.
172
+ */
173
+ export function evaluateSeedPrecondition(requestedUrl, actualUrl, seed, navStatus) {
174
+ if (401 === navStatus || 403 === navStatus) {
175
+ return `HTTP ${navStatus} returned on initial seeded navigation to ${requestedUrl}`;
176
+ }
177
+ if (actualUrl === undefined || '' === actualUrl.trim())
178
+ return undefined;
179
+ let reqParsed;
180
+ let actParsed;
181
+ try {
182
+ reqParsed = new URL(requestedUrl);
183
+ actParsed = new URL(actualUrl);
184
+ }
185
+ catch {
186
+ return undefined;
187
+ }
188
+ // Cross-origin redirect when local or session storage was requested
189
+ const hasStorage = (undefined !== seed.local && 0 < Object.keys(seed.local).length) ||
190
+ (undefined !== seed.session && 0 < Object.keys(seed.session).length);
191
+ if (hasStorage && reqParsed.origin !== actParsed.origin) {
192
+ return `seeded storage precondition not established: cross-origin redirect from ${reqParsed.origin} to ${actParsed.origin} skipped origin-scoped storage injection`;
193
+ }
194
+ // Clear redirect to an authentication/login endpoint when the requested URL was not a login page
195
+ const isAuthPath = (pathname) => /(?:^|\/)(?:login|signin|sign-in|auth\/login|auth\/signin|session\/new)(?:$|\/)/i.test(pathname);
196
+ if (!isAuthPath(reqParsed.pathname) && isAuthPath(actParsed.pathname)) {
197
+ return `seeded authentication precondition not established: redirected from ${reqParsed.pathname} to login page (${actParsed.pathname})`;
198
+ }
199
+ return undefined;
200
+ }
109
201
  /** A fresh, collision-resistant lease id. Uses crypto at the I/O boundary (not pure logic). */
110
202
  function newLeaseId() {
111
203
  const uuid = 'function' === typeof globalThis.crypto?.randomUUID
@@ -176,6 +268,46 @@ function sessionParamOf(url) {
176
268
  }
177
269
  const LEASE_READY_ATTEMPTS = 100;
178
270
  const LEASE_READY_POLL_MS = 100;
271
+ /**
272
+ * How long a liveness probe waits for the tab to say anything at all.
273
+ *
274
+ * Short on purpose. This is not "finish the work", it is "are you there" — a page executing
275
+ * JavaScript answers a no-argument command in single-digit milliseconds, and a wedged one is not
276
+ * going to answer in two seconds either.
277
+ */
278
+ const LEASE_PROBE_TIMEOUT_MS = 1_500;
279
+ /**
280
+ * Does this tab still answer?
281
+ *
282
+ * `ready: true` used to mean "a row is in the sessions map", which is how a lease came back ready
283
+ * and was then rejected by `snapshot`, `state` and `console`. Presence is not liveness: the map
284
+ * still holds a tab that is attached, streaming events, and answering nothing (#692).
285
+ *
286
+ * ANY reply counts, including one that reports the command failed. The question is whether the SDK
287
+ * answers at all, not what it says — so an SDK too old to know the command replies
288
+ * `unknown command '…'`, and that is proof. Only the absence of a reply is evidence of absence:
289
+ * `PendingCommands.track` REJECTS on timeout and on a dropped socket, and resolves on every real
290
+ * answer, so the two cases are already separated for us.
291
+ *
292
+ * `CAPABILITIES` is the probe because it takes no arguments and returns a small fixed list. No new
293
+ * wire command is introduced: this rides an existing round trip.
294
+ *
295
+ * Fails OPEN. A registry entry with no `command` is a shape this code did not put there, and
296
+ * turning a lease that works into a refusal over a probe that could not run would be a worse
297
+ * failure than the one being fixed.
298
+ */
299
+ export async function probeLeaseAlive(session, timeoutMs = LEASE_PROBE_TIMEOUT_MS) {
300
+ const send = session?.command;
301
+ if (send === undefined)
302
+ return true;
303
+ try {
304
+ await send.call(session, ReticleCommand.CAPABILITIES, {}, timeoutMs);
305
+ return true;
306
+ }
307
+ catch {
308
+ return false;
309
+ }
310
+ }
179
311
  const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
180
312
  /**
181
313
  * Poll until the leased tab's SDK has actually registered on the bridge, so the sessionId we return
@@ -188,9 +320,18 @@ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
188
320
  * Returns the session id plus its release; the caller must release in a finally so a crashed flow never
189
321
  * leaks a pool slot and starves the rest of the suite.
190
322
  */
191
- export async function acquireLeasedSession(pool, sessions, url, projectId) {
323
+ export async function acquireLeasedSession(pool, sessions, url, projectId, seedStorage) {
192
324
  const sessionId = newLeaseId();
193
- const lease = await pool.acquire(appendReticleParams(url, sessionId, projectId), { sessionId });
325
+ let lease;
326
+ try {
327
+ lease = await pool.acquire(appendReticleParams(url, sessionId, projectId), {
328
+ sessionId,
329
+ ...(seedStorage !== undefined ? { seedStorage } : {}),
330
+ });
331
+ }
332
+ catch (err) {
333
+ throw new Error(scrubSeedFromError(err instanceof Error ? err.message : String(err), seedStorage));
334
+ }
194
335
  // Resolved, not assumed — see resolveLeasedSessionId. The parallel suite replays against whatever
195
336
  // id comes back, so handing it the lease id for an app that named its own session sent every flow
196
337
  // in the run at a session that does not exist.
@@ -201,7 +342,17 @@ export async function acquireLeasedSession(pool, sessions, url, projectId) {
201
342
  });
202
343
  if (registeredId !== undefined)
203
344
  pool.alias?.(registeredId, lease.sessionId);
204
- return { sessionId: registeredId ?? lease.sessionId, release: () => lease.release() };
345
+ const effectiveId = registeredId ?? lease.sessionId;
346
+ if (seedStorage !== undefined) {
347
+ const session = sessions.get(effectiveId);
348
+ if (session?.setPreconditionFailure !== undefined) {
349
+ const failureReason = evaluateSeedPrecondition(url, session.url, seedStorage, lease.navStatus);
350
+ if (failureReason !== undefined) {
351
+ session.setPreconditionFailure(failureReason);
352
+ }
353
+ }
354
+ }
355
+ return { sessionId: effectiveId, release: () => lease.release() };
205
356
  }
206
357
  export async function waitForLeasedSession(isConnected, sleeper = sleep, attempts = LEASE_READY_ATTEMPTS, pollMs = LEASE_READY_POLL_MS) {
207
358
  for (let i = 0; i < attempts; i++) {
@@ -220,6 +371,12 @@ export async function waitForLeasedSession(isConnected, sleeper = sleep, attempt
220
371
  * handler keeps that literal — the drive route dispatches through `runTool`, so it is counted like
221
372
  * any other call instead of being a second, invisible dispatch path.
222
373
  */
374
+ // Serializes lease acquisitions on the same origin so concurrent callers (e.g. seeded replacements)
375
+ // do not race and establish multiple overlapping contexts on that origin.
376
+ const originAcquireLocks = new Map();
377
+ export function hasOriginLock(origin) {
378
+ return originAcquireLocks.has(origin);
379
+ }
223
380
  export const LEASE_ACQUIRE_TOOL = {
224
381
  name: ReticleTool.LEASE_ACQUIRE,
225
382
  description: 'Lease a fresh isolated headless browser context from the shared pool and navigate it to the app URL (the app must already be running and embed @reticlehq/core). If this origin is already leased and still connected, this returns THAT session rather than minting a second tab — a second acquire on the same origin poisons default session resolution. Returns the sessionId the leased tab registers — pass it to other tools. The pool keeps all leases in ONE browser and caps concurrency; if at capacity this waits for a free slot. Release with reticle_lease{action:"release"} when the flow is done. PREFER AN ALREADY-OPEN TAB: if reticle_sessions lists a non-leased session for this app, drive THAT instead — a lease is invisible to the person watching the app, whose HUD lives in their own tab, and a tab flagged hidden/throttled is often still driveable. Lease for isolation you actually need (a second identity, a clean context, parallel flows) or when driving the open tab has failed — this call answers with `preferExisting` when a live tab was available.',
@@ -231,13 +388,18 @@ export const LEASE_ACQUIRE_TOOL = {
231
388
  .string()
232
389
  .optional()
233
390
  .describe('Stable project id to stamp on the leased tab so the agent can scope to it.'),
391
+ seedStorage: SeedStorageSchema.optional().describe('Initial storage state (localStorage, sessionStorage, cookies) to seed before the first navigation (e.g. to start already authenticated).'),
234
392
  },
235
393
  outputSchema: {
236
394
  sessionId: z.string(),
237
395
  url: z.string(),
238
396
  ready: z
239
397
  .boolean()
240
- .describe('Whether the leased tab connected — false ⇒ the app may not embed @reticlehq/core.'),
398
+ .describe('Whether the leased tab is connected AND answering. On a reused lease this is probed, not assumed — a row in the session map is presence, not liveness. False ⇒ read notReadyReason.'),
399
+ notReadyReason: z
400
+ .enum([LeaseNotReadyReason.SDK_NEVER_DIALLED, LeaseNotReadyReason.SDK_STOPPED_ANSWERING])
401
+ .optional()
402
+ .describe('Present only when ready is false. sdk_never_dialled ⇒ nothing connected within the wait, so check the install (the app may not embed @reticlehq/core). sdk_stopped_answering ⇒ an SDK did connect and has stopped replying, so the tab is wedged and needs recovering, not reinstalling.'),
241
403
  expiresInMs: z
242
404
  .number()
243
405
  .describe('Milliseconds until this lease expires if untouched. Each tool call that targets this session resets the clock. Plan your work to finish or re-acquire before this runs out.'),
@@ -278,83 +440,156 @@ export const LEASE_ACQUIRE_TOOL = {
278
440
  }
279
441
  }
280
442
  const projectId = asString(args['projectId']);
443
+ const seedStorageArg = args['seedStorage'];
444
+ let validatedSeed;
445
+ if (seedStorageArg !== undefined) {
446
+ const parsed = SeedStorageSchema.safeParse(seedStorageArg);
447
+ if (!parsed.success) {
448
+ const issuesMsg = scrubSeedFromError(parsed.error.issues.map((i) => `${i.path.join('.')}: ${i.message}`).join(', '), seedStorageArg);
449
+ throw new Error(`reticle_lease{action:"acquire"} seedStorage is invalid: ${issuesMsg}`);
450
+ }
451
+ validatedSeed = parsed.data;
452
+ }
281
453
  // Sampled BEFORE acquiring: afterwards this lease is itself a session, and the point is to name
282
454
  // a tab that already existed. A human's open tab is the one they can watch, so if one is here
283
455
  // the agent should be told at the moment it is choosing — not after it has gone dark on them.
284
456
  const alreadyOpen = liveTabFor(deps, projectId);
285
457
  const origin = originOf(url);
286
- const existing = origin === undefined ? undefined : pool.leaseIdOnOrigin?.(origin);
287
- if (existing !== undefined && origin !== undefined) {
288
- if (deps.sessions.get(existing) !== undefined) {
289
- pool.touch(existing);
290
- return {
291
- sessionId: existing,
292
- url,
293
- ready: true,
294
- reused: true,
295
- expiresInMs: pool.leaseTtlMs(),
296
- leased: pool.activeCount(),
297
- queued: pool.queuedCount(),
298
- hint: alreadyHeldHint(existing, origin),
299
- ...(alreadyOpen === undefined
300
- ? {}
301
- : { preferExisting: { sessionId: alreadyOpen, note: PREFER_EXISTING_NOTE } }),
302
- };
303
- }
304
- // The lease is still held but the tab has gone — a reload dropped the session. Free the
305
- // slot and mint, rather than handing back a dead id or leaving both contexts occupied.
306
- await pool.release(existing);
458
+ const priorLock = origin !== undefined ? originAcquireLocks.get(origin) : undefined;
459
+ let releaseLock;
460
+ let lockPromise;
461
+ if (origin !== undefined) {
462
+ lockPromise = new Promise((resolve) => {
463
+ releaseLock = resolve;
464
+ });
465
+ originAcquireLocks.set(origin, lockPromise);
466
+ }
467
+ if (priorLock !== undefined) {
468
+ await priorLock.catch(() => undefined);
307
469
  }
308
- const sessionId = newLeaseId();
309
- const navUrl = appendReticleParams(url, sessionId, projectId);
310
- let lease;
311
470
  try {
312
- lease = await pool.acquire(navUrl, { sessionId });
471
+ const existing = origin === undefined ? undefined : pool.leaseIdOnOrigin?.(origin);
472
+ if (validatedSeed !== undefined && existing !== undefined) {
473
+ // Caller explicitly requested storage seeding: do not reuse an existing context on this origin,
474
+ // release it so the new lease starts clean with the caller-provided state.
475
+ await pool.release(existing);
476
+ }
477
+ else if (existing !== undefined && origin !== undefined) {
478
+ // Resolved, not looked up — the same resolver the mint path below uses, for the same reason
479
+ // it states: an app that names its own session registers under that name, so the id the pool
480
+ // holds is not always an id the other tools accept. A direct `sessions.get` missed exactly
481
+ // those apps and fell through to the release, which frees a LIVE lease and mints a second
482
+ // context — the tab-poisoning this branch exists to prevent, reached through the branch
483
+ // itself. It bites when the first acquire returned `ready: false`, since the mint path only
484
+ // aliases once its wait has resolved, so nothing recorded the app's own name (#692).
485
+ const resolved = resolveLeasedSessionId(deps.sessions, existing);
486
+ if (resolved !== undefined) {
487
+ // The other name this lease answers to, told to the pool exactly as the mint path tells it.
488
+ // A no-op when the id resolved to itself; load-bearing when it did not, because every later
489
+ // touch and release arrives under the id being handed back here.
490
+ pool.alias(resolved, existing);
491
+ pool.touch(existing);
492
+ // Probed only HERE, never on the mint path below. There, the readiness wait resolved
493
+ // moments ago and IS the liveness evidence; on reuse the last evidence may be minutes
494
+ // old, or there may be none at all — `unresponsive` is set by past commands failing, and
495
+ // its own contract says absence means "answering, OR NOT ASKED YET". So the happy path of
496
+ // a first acquire pays nothing for this.
497
+ const alive = await probeLeaseAlive(deps.sessions.get(resolved));
498
+ return {
499
+ sessionId: resolved,
500
+ url,
501
+ ready: alive,
502
+ ...(alive ? {} : { notReadyReason: LeaseNotReadyReason.SDK_STOPPED_ANSWERING }),
503
+ reused: true,
504
+ expiresInMs: pool.leaseTtlMs(),
505
+ leased: pool.activeCount(),
506
+ queued: pool.queuedCount(),
507
+ hint: alreadyHeldHint(resolved, origin),
508
+ ...(alreadyOpen === undefined
509
+ ? {}
510
+ : { preferExisting: { sessionId: alreadyOpen, note: PREFER_EXISTING_NOTE } }),
511
+ };
512
+ }
513
+ // The lease is still held but the tab has gone — a reload dropped the session, and no session
514
+ // anywhere is driving it. Free the slot and mint, rather than handing back a dead id or
515
+ // leaving both contexts occupied.
516
+ await pool.release(existing);
517
+ }
518
+ const sessionId = newLeaseId();
519
+ const navUrl = appendReticleParams(url, sessionId, projectId);
520
+ let lease;
521
+ try {
522
+ lease = await pool.acquire(navUrl, {
523
+ sessionId,
524
+ ...(validatedSeed !== undefined ? { seedStorage: validatedSeed } : {}),
525
+ });
526
+ }
527
+ catch (err) {
528
+ if (err instanceof Error && err.message.startsWith('Storage seeding failed')) {
529
+ throw new Error(scrubSeedFromError(err.message, validatedSeed));
530
+ }
531
+ // A raw page.goto failure is noisy and leaks the internal URL params — surface a clean,
532
+ // actionable message instead.
533
+ throw new Error(`could not open ${url} — is the app running there? (${cleanNavError(err, validatedSeed)})`);
534
+ }
535
+ // Wait for the leased tab's SDK to connect so the returned sessionId is usable right away.
536
+ // Resolved rather than assumed: an app that names its own session registers under that name,
537
+ // and the id we hand back has to be the one the agent can actually drive.
538
+ let registeredId;
539
+ const ready = await waitForLeasedSession(() => {
540
+ registeredId = resolveLeasedSessionId(deps.sessions, lease.sessionId);
541
+ return registeredId !== undefined;
542
+ });
543
+ // Tell the pool the other name this lease answers to. Every later touch and release arrives
544
+ // under the id we are about to hand back, and the pool is keyed by the id it navigated with;
545
+ // without this the touches miss, the lease ages out despite continuous activity, and the
546
+ // reaper closes the context mid-flow. See BrowserPool.alias and #157.
547
+ if (registeredId !== undefined)
548
+ pool.alias(registeredId, lease.sessionId);
549
+ const effectiveSessionId = registeredId ?? lease.sessionId;
550
+ const session = deps.sessions.get(effectiveSessionId);
551
+ if (validatedSeed !== undefined && session !== undefined) {
552
+ const failureReason = evaluateSeedPrecondition(url, session.url, validatedSeed, lease.navStatus);
553
+ if (failureReason !== undefined) {
554
+ session.setPreconditionFailure?.(failureReason);
555
+ }
556
+ }
557
+ // The lease now exists, so any HUD a human is watching has just gone dark. Say so.
558
+ tellWatchers(deps, projectId, AGENT_DRIVING_ELSEWHERE);
559
+ // ready means the SDK dialled in — not that contracts match. Carry the skew warning on acquire
560
+ // so the agent does not learn it only after a CDP tool invents a closed page (#688).
561
+ const versionSkew = registeredId === undefined ? undefined : deps.sessions.get(registeredId)?.versionSkew;
562
+ return {
563
+ sessionId: registeredId ?? lease.sessionId,
564
+ url,
565
+ ready,
566
+ // The other half of the pair. `ready: false` carried two opposite situations under one
567
+ // word: no SDK ever dialled in (look at the install) versus one dialled in and stopped
568
+ // answering (recover the tab). They want different next actions, so they get names.
569
+ ...(ready ? {} : { notReadyReason: LeaseNotReadyReason.SDK_NEVER_DIALLED }),
570
+ expiresInMs: pool.leaseTtlMs(),
571
+ leased: pool.activeCount(),
572
+ queued: pool.queuedCount(),
573
+ ...(alreadyOpen === undefined
574
+ ? {}
575
+ : {
576
+ preferExisting: {
577
+ sessionId: alreadyOpen,
578
+ note: PREFER_EXISTING_NOTE,
579
+ },
580
+ }),
581
+ ...(versionSkew === undefined ? {} : { versionSkew }),
582
+ ...(ready
583
+ ? {}
584
+ : { hint: await notConnectedHint(deps, url, pool.dialFailureUrl?.(lease.sessionId)) }),
585
+ };
313
586
  }
314
- catch (err) {
315
- // A raw page.goto failure is noisy and leaks the internal URL params — surface a clean,
316
- // actionable message instead.
317
- throw new Error(`could not open ${url} — is the app running there? (${cleanNavError(err)})`);
587
+ finally {
588
+ releaseLock?.();
589
+ if (origin !== undefined && originAcquireLocks.get(origin) === lockPromise) {
590
+ originAcquireLocks.delete(origin);
591
+ }
318
592
  }
319
- // Wait for the leased tab's SDK to connect so the returned sessionId is usable right away.
320
- // Resolved rather than assumed: an app that names its own session registers under that name,
321
- // and the id we hand back has to be the one the agent can actually drive.
322
- let registeredId;
323
- const ready = await waitForLeasedSession(() => {
324
- registeredId = resolveLeasedSessionId(deps.sessions, lease.sessionId);
325
- return registeredId !== undefined;
326
- });
327
- // Tell the pool the other name this lease answers to. Every later touch and release arrives
328
- // under the id we are about to hand back, and the pool is keyed by the id it navigated with;
329
- // without this the touches miss, the lease ages out despite continuous activity, and the
330
- // reaper closes the context mid-flow. See BrowserPool.alias and #157.
331
- if (registeredId !== undefined)
332
- pool.alias(registeredId, lease.sessionId);
333
- // The lease now exists, so any HUD a human is watching has just gone dark. Say so.
334
- tellWatchers(deps, projectId, AGENT_DRIVING_ELSEWHERE);
335
- // ready means the SDK dialled in — not that contracts match. Carry the skew warning on acquire
336
- // so the agent does not learn it only after a CDP tool invents a closed page (#688).
337
- const versionSkew = registeredId === undefined ? undefined : deps.sessions.get(registeredId)?.versionSkew;
338
- return {
339
- sessionId: registeredId ?? lease.sessionId,
340
- url,
341
- ready,
342
- expiresInMs: pool.leaseTtlMs(),
343
- leased: pool.activeCount(),
344
- queued: pool.queuedCount(),
345
- ...(alreadyOpen === undefined
346
- ? {}
347
- : {
348
- preferExisting: {
349
- sessionId: alreadyOpen,
350
- note: PREFER_EXISTING_NOTE,
351
- },
352
- }),
353
- ...(versionSkew === undefined ? {} : { versionSkew }),
354
- ...(ready
355
- ? {}
356
- : { hint: await notConnectedHint(deps, url, pool.dialFailureUrl?.(lease.sessionId)) }),
357
- };
358
593
  },
359
594
  };
360
595
  /** The whole not-connected diagnosis, gathered and worded. Split out so the acquire path stays flat. */
@@ -405,7 +640,7 @@ const LEASE_RELEASE_TOOL = {
405
640
  * when both exist the visible one is the better default and the agent should hear that here, where
406
641
  * it is choosing, rather than discover it when somebody asks why nothing is happening.
407
642
  */
408
- export const PREFER_EXISTING_NOTE = 'a non-leased tab for this app was already connected — that is the one a human can see, and this lease is not. Unless you need an isolated context (a second identity, a clean profile, parallel flows), release this lease and drive that sessionId instead.';
643
+ const PREFER_EXISTING_NOTE = 'a non-leased tab for this app was already connected — that is the one a human can see, and this lease is not. Unless you need an isolated context (a second identity, a clean profile, parallel flows), release this lease and drive that sessionId instead.';
409
644
  /**
410
645
  * The first live non-leased session for this project, if any.
411
646
  *
@@ -0,0 +1,14 @@
1
+ /**
2
+ * `reticle_lineage` — where a runtime value came from.
3
+ *
4
+ * When a verdict is `no`, or a contradiction fires, the next question is always the same. Today an
5
+ * agent answers it by hand: `reticle_state` for the value, `reticle_network` for the requests,
6
+ * `reticle_observe` for the window, then correlates the three itself — spending tokens and judgement
7
+ * on a join the daemon can do once from data it already holds.
8
+ *
9
+ * READ-ONLY BY CONSTRUCTION. It produces no verdict, so it is registered in `TOOLS` and deliberately
10
+ * NOT in the verification set, and nothing on an assert path reaches it. The decision logic lives in
11
+ * `events/lineage.ts` and is where the honesty rules are argued; this file is the surface.
12
+ */
13
+ import type { ToolDef } from './tools.js';
14
+ export declare const LINEAGE_TOOLS: ToolDef[];
@@ -0,0 +1,64 @@
1
+ /**
2
+ * `reticle_lineage` — where a runtime value came from.
3
+ *
4
+ * When a verdict is `no`, or a contradiction fires, the next question is always the same. Today an
5
+ * agent answers it by hand: `reticle_state` for the value, `reticle_network` for the requests,
6
+ * `reticle_observe` for the window, then correlates the three itself — spending tokens and judgement
7
+ * on a join the daemon can do once from data it already holds.
8
+ *
9
+ * READ-ONLY BY CONSTRUCTION. It produces no verdict, so it is registered in `TOOLS` and deliberately
10
+ * NOT in the verification set, and nothing on an assert path reaches it. The decision logic lives in
11
+ * `events/lineage.ts` and is where the honesty rules are argued; this file is the surface.
12
+ */
13
+ import { z } from 'zod';
14
+ import { ReticleTool } from './tool-names.js';
15
+ import { traceLineage } from '../events/lineage.js';
16
+ import { asString } from './tools-helpers.js';
17
+ import { cursorSchema } from './numeric-bounds.js';
18
+ export const LINEAGE_TOOLS = [
19
+ {
20
+ name: ReticleTool.LINEAGE,
21
+ example: { path: 'user.profile.name' },
22
+ description: 'Trace BACKWARDS from a runtime value: which state change set it, which signal preceded that, and which request preceded the signal. Read-only — it draws no verdict and changes nothing. The state change is OBSERVED; every link above it is an INFERENCE from timing and says so in its own text. Where several candidates could explain a link it names ALL of them rather than choosing, because timing alone cannot justify the choice — so a chain here is a place to look, never a proven cause. Use it after a `no` verdict or a contradiction, instead of correlating reticle_state, reticle_network and reticle_observe by hand.',
23
+ inputSchema: {
24
+ path: z
25
+ .string()
26
+ .describe('The state path to trace, e.g. "user.profile.name". Matched as a substring.'),
27
+ value: z
28
+ .string()
29
+ .optional()
30
+ .describe('Narrow to the change that set THIS value, when the path changed more than once.'),
31
+ since: cursorSchema
32
+ .optional()
33
+ .describe('Only consider events after this cursor (from an act or observe result).'),
34
+ ...{},
35
+ },
36
+ outputSchema: {
37
+ found: z
38
+ .boolean()
39
+ .describe('False when the path was never seen to change — not the same as an empty chain.'),
40
+ chain: z
41
+ .array(z.object({
42
+ kind: z.string(),
43
+ text: z.string(),
44
+ observed: z.boolean(),
45
+ candidates: z.array(z.string()).optional(),
46
+ }))
47
+ .describe('Most recent change first. `observed` is true only for events we hold; every join is false.'),
48
+ note: z
49
+ .string()
50
+ .optional()
51
+ .describe('Why the chain stops where it does, when it stops early.'),
52
+ },
53
+ handler: (deps, args) => {
54
+ const session = deps.sessions.resolve(asString(args['sessionId']));
55
+ const since = 'number' === typeof args['since'] ? args['since'] : 0;
56
+ const path = asString(args['path']) ?? '';
57
+ const value = asString(args['value']);
58
+ return Promise.resolve(traceLineage(session.eventsSince(since), {
59
+ path,
60
+ ...(value === undefined ? {} : { value }),
61
+ }));
62
+ },
63
+ },
64
+ ];
@@ -51,9 +51,7 @@ export function mergeTools(spec) {
51
51
  description: spec.description,
52
52
  ...(spec.example === undefined ? {} : { example: spec.example }),
53
53
  inputSchema: {
54
- action: z
55
- .enum(actionNames)
56
- .describe(`Which operation to run: ${actionNames.join(' | ')}.`),
54
+ action: z.enum(actionNames).describe('Which operation to run.'),
57
55
  ...unionShape(spec.actions),
58
56
  },
59
57
  handler: (deps, args) => {
@@ -10,9 +10,53 @@
10
10
  * Bounded and best-effort by construction: a navigation to a page that is not instrumented, or not
11
11
  * there at all, must still return promptly with `confirmed:false` rather than hanging. The clock and
12
12
  * the sleep are injected so this is testable without waiting on a real one.
13
+ *
14
+ * The bound is the CALLER'S to set. It was a fixed 5s that `reticle_navigate` did not expose, and a
15
+ * Nuxt SPA reattaching under HMR was measured coming back in 30–60s — so every navigation to it
16
+ * answered `confirmed:false` while the app was still on its way, and nothing the agent could pass
17
+ * would make the daemon wait. `assert` / `wait_for` / `act_and_wait` all spend the caller's
18
+ * `timeout_ms` (resolve-within.ts); this is the same budget applied to the same wait.
13
19
  */
14
20
  import type { SessionManager } from '../session/session-manager.js';
15
21
  import type { NavigateArrival } from './navigate-result.js';
22
+ /**
23
+ * The default when the caller gives no `timeout_ms`: long enough for a dev server to serve a page
24
+ * and the SDK to dial back; short enough to not hang. A caller who knows the app is slower says so.
25
+ */
26
+ export declare const ARRIVAL_TIMEOUT_MS = 5000;
27
+ /**
28
+ * Which session an arrival may be attributed to.
29
+ *
30
+ * Arrival means a session is at the target BECAUSE OF THIS NAVIGATION. The scan had no notion of
31
+ * that: it returned the first session whose page matched, whoever it belonged to. A stale row parked
32
+ * on the origin — in the field report, one belonging to a different app that had previously held the
33
+ * same port — therefore answered for a navigation it had nothing to do with, and `reticle_navigate`
34
+ * reported ITS id as the session the caller had arrived at. Targeting was never wrong; the result
35
+ * was (#785).
36
+ *
37
+ * `sessions.all()` is Map insertion order, so the older stale row is scanned FIRST. That made the
38
+ * wrong id the likely answer rather than a coin flip.
39
+ */
40
+ export interface ArrivalScope {
41
+ /**
42
+ * The session the navigation was dispatched to.
43
+ *
44
+ * Exempt from the exclusion below, because it is legitimately at the target both before and after
45
+ * a navigation that keeps the document — a reload, or a same-page route change. Excluding it would
46
+ * report `confirmed: false` for the navigations most likely to have worked.
47
+ */
48
+ navigatedId: string;
49
+ /** Sessions already on the target BEFORE dispatch. They cannot be evidence of this arrival. */
50
+ priorIds: ReadonlySet<string>;
51
+ }
52
+ /**
53
+ * The sessions already on `target` — the set `ArrivalScope.priorIds` is built from, sampled before
54
+ * the navigation is dispatched.
55
+ *
56
+ * Shares `samePage` with the arrival scan on purpose: "was it already there" and "is it there now"
57
+ * are the same question asked at two moments, and two spellings of it would eventually disagree.
58
+ */
59
+ export declare function idsAtTarget(sessions: SessionManager, target: string): ReadonlySet<string>;
16
60
  export interface ArrivalClock {
17
61
  now: () => number;
18
62
  sleep: (ms: number) => Promise<void>;
@@ -21,4 +65,4 @@ export interface ArrivalClock {
21
65
  * Poll until a session is present at `target`, or the budget runs out. Returns `null` on timeout —
22
66
  * never throws, because a failure to confirm is a legitimate answer, not an error.
23
67
  */
24
- export declare function awaitArrival(sessions: SessionManager, target: string, timeoutMs?: number, clock?: ArrivalClock): Promise<NavigateArrival | null>;
68
+ export declare function awaitArrival(sessions: SessionManager, target: string, scope: ArrivalScope, timeoutMs?: number, clock?: ArrivalClock): Promise<NavigateArrival | null>;