@reticlehq/server 3.4.0 → 3.6.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 (350) hide show
  1. package/README.md +1 -1
  2. package/SKILL.md +16 -8
  3. package/dist/chromium-launch-options.d.ts +3 -1
  4. package/dist/chromium-launch-options.js +2 -1
  5. package/dist/command/cli/adhoc-verdict.d.ts +1 -0
  6. package/dist/command/cli/adhoc-verdict.js +6 -3
  7. package/dist/command/cli/answers/open-note.d.ts +2 -0
  8. package/dist/command/cli/answers/open-note.js +22 -0
  9. package/dist/command/cli/auth/cloud-session.d.ts +4 -4
  10. package/dist/command/cli/auth/device-flow.d.ts +2 -2
  11. package/dist/command/cli/cli-flow-commands.js +4 -8
  12. package/dist/command/cli/cli-parse.d.ts +1 -1
  13. package/dist/command/cli/cli-parse.js +13 -11
  14. package/dist/command/cli/cli-verify.d.ts +1 -1
  15. package/dist/command/cli/cli-verify.js +35 -25
  16. package/dist/command/cli/cloud-cli.js +110 -12
  17. package/dist/command/cli/cloud-login.d.ts +9 -1
  18. package/dist/command/cli/cloud-login.js +43 -8
  19. package/dist/command/cli/demo/demo-page.js +11 -3
  20. package/dist/command/cli/doctor/browser/chromium-hint.d.ts +46 -0
  21. package/dist/command/cli/doctor/browser/chromium-hint.js +99 -1
  22. package/dist/command/cli/drive/drive-attach.d.ts +27 -2
  23. package/dist/command/cli/drive/drive-attach.js +71 -4
  24. package/dist/command/cli/drive/drive-command.d.ts +2 -1
  25. package/dist/command/cli/drive/drive-command.js +13 -7
  26. package/dist/command/cli/lifecycle/daemon-lifecycle.js +4 -1
  27. package/dist/command/cli/ports/port-holder.d.ts +10 -0
  28. package/dist/command/cli/ports/port-holder.js +24 -0
  29. package/dist/command/cli/setup-mcp-cli.js +15 -5
  30. package/dist/command/cli/tutorial.d.ts +2 -2
  31. package/dist/command/cli/tutorial.js +6 -9
  32. package/dist/command/cli.d.ts +6 -0
  33. package/dist/command/cli.js +45 -23
  34. package/dist/command/daemon/binding/daemon-status-probe.d.ts +2 -0
  35. package/dist/command/daemon/binding/daemon-status-probe.js +1 -0
  36. package/dist/command/daemon/daemon-resolve.d.ts +14 -0
  37. package/dist/command/daemon/daemon-resolve.js +33 -2
  38. package/dist/command/setup/agent-writer.d.ts +1 -1
  39. package/dist/command/setup/agent-writer.js +3 -2
  40. package/dist/command/setup/bringup/connect-lease.d.ts +1 -0
  41. package/dist/command/setup/bringup/connect-lease.js +1 -0
  42. package/dist/command/setup/bringup/dev-server-wait.d.ts +10 -0
  43. package/dist/command/setup/bringup/dev-server-wait.js +20 -1
  44. package/dist/command/setup/init/init-port.d.ts +4 -0
  45. package/dist/command/setup/init/init-port.js +10 -0
  46. package/dist/command/setup/init/init-runtime.d.ts +10 -0
  47. package/dist/command/setup/init/init-runtime.js +71 -9
  48. package/dist/command/setup/node-effects.js +8 -2
  49. package/dist/command/setup/remaining-steps.js +2 -1
  50. package/dist/command/setup/run-setup.d.ts +7 -0
  51. package/dist/command/setup/run-setup.js +38 -25
  52. package/dist/command/setup/setup-command.d.ts +2 -1
  53. package/dist/command/setup/setup-command.js +2 -0
  54. package/dist/command/setup/setup-mcp.d.ts +2 -0
  55. package/dist/command/setup/setup-mcp.js +14 -5
  56. package/dist/command/version/peer-announce.d.ts +6 -2
  57. package/dist/command/version/peer-announce.js +8 -6
  58. package/dist/command/version/version-nudge.d.ts +17 -3
  59. package/dist/command/version/version-nudge.js +25 -4
  60. package/dist/command/version/version-skew.js +11 -2
  61. package/dist/features/crawl/crawl-tools.js +4 -0
  62. package/dist/features/crawl/crawl.d.ts +6 -0
  63. package/dist/features/crawl/crawl.js +9 -1
  64. package/dist/features/exhaust/ledger.d.ts +7 -0
  65. package/dist/features/exhaust/ledger.js +35 -9
  66. package/dist/features/harness/app-findings.d.ts +2 -0
  67. package/dist/features/harness/app-findings.js +52 -0
  68. package/dist/features/harness/drive-report.d.ts +27 -1
  69. package/dist/features/harness/drive-report.js +162 -11
  70. package/dist/features/harness/drivers.d.ts +10 -14
  71. package/dist/features/harness/drivers.js +10 -14
  72. package/dist/features/harness/fill-values.d.ts +3 -69
  73. package/dist/features/harness/fill-values.js +3 -133
  74. package/dist/features/harness/goals.d.ts +21 -0
  75. package/dist/features/harness/goals.js +77 -0
  76. package/dist/features/harness/harness.d.ts +21 -0
  77. package/dist/features/harness/harness.js +69 -2
  78. package/dist/features/harness/platform/drive-target.d.ts +30 -0
  79. package/dist/features/harness/platform/drive-target.js +81 -0
  80. package/dist/features/harness/platform/local-apps.d.ts +94 -0
  81. package/dist/features/harness/platform/local-apps.js +204 -0
  82. package/dist/features/harness/platform/remote-drive.d.ts +180 -0
  83. package/dist/features/harness/platform/remote-drive.js +365 -0
  84. package/dist/features/harness/platform/script.d.ts +48 -0
  85. package/dist/features/harness/platform/script.js +128 -0
  86. package/dist/features/harness/platform/server-driver.d.ts +41 -0
  87. package/dist/features/harness/platform/server-driver.js +179 -0
  88. package/dist/features/harness/platform-config.d.ts +7 -0
  89. package/dist/features/harness/platform-config.js +14 -2
  90. package/dist/features/harness/script/app-controls.d.ts +5 -0
  91. package/dist/features/harness/script/app-controls.js +40 -0
  92. package/dist/features/harness/script/lane-ids.d.ts +14 -0
  93. package/dist/features/harness/script/lane-ids.js +16 -0
  94. package/dist/features/harness/script/script-run.d.ts +41 -0
  95. package/dist/features/harness/script/script-run.js +299 -0
  96. package/dist/features/license/license.d.ts +2 -2
  97. package/dist/features/visual/visual-tools.d.ts +10 -1
  98. package/dist/features/visual/visual-tools.js +40 -3
  99. package/dist/hooks/driven-by.d.ts +4 -0
  100. package/dist/hooks/driven-by.js +16 -0
  101. package/dist/hooks/hook-commands.js +15 -4
  102. package/dist/hooks/tool-log.d.ts +9 -0
  103. package/dist/hooks/tool-log.js +29 -0
  104. package/dist/index.d.ts +1 -0
  105. package/dist/index.js +91 -89
  106. package/dist/judgement/capsule/capsule-store.d.ts +4 -4
  107. package/dist/judgement/runs/artifact/build-verification-run.js +6 -0
  108. package/dist/judgement/runs/artifact/replay-mapping.d.ts +1 -1
  109. package/dist/judgement/runs/artifact/replay-mapping.js +16 -6
  110. package/dist/judgement/runs/artifact/run-store.d.ts +1 -1
  111. package/dist/judgement/runs/artifact/run-store.js +26 -1
  112. package/dist/judgement/runs/drive-run.d.ts +22 -0
  113. package/dist/judgement/runs/drive-run.js +99 -1
  114. package/dist/judgement/runs/repair-prompt.js +1 -1
  115. package/dist/judgement/runs/reticle-runner.js +8 -1
  116. package/dist/judgement/runs/verification-sync.d.ts +3 -2
  117. package/dist/judgement/runs/verification-sync.js +4 -3
  118. package/dist/language/flows/change/gate.d.ts +10 -0
  119. package/dist/language/flows/change/gate.js +22 -0
  120. package/dist/language/flows/decision.js +34 -0
  121. package/dist/language/flows/flow-anchor.d.ts +54 -8
  122. package/dist/language/flows/flow-anchor.js +88 -3
  123. package/dist/language/flows/flow-cross-step.d.ts +15 -0
  124. package/dist/language/flows/flow-cross-step.js +21 -0
  125. package/dist/language/flows/flow-learning.js +8 -11
  126. package/dist/language/flows/flow-mutate-tools.js +5 -3
  127. package/dist/language/flows/flow-replay-run.d.ts +23 -9
  128. package/dist/language/flows/flow-replay-run.js +101 -94
  129. package/dist/language/flows/flow-replay.d.ts +23 -1
  130. package/dist/language/flows/flow-replay.js +92 -19
  131. package/dist/language/flows/flow-scope.js +5 -1
  132. package/dist/language/flows/flow-step-runners.d.ts +20 -2
  133. package/dist/language/flows/flow-step-runners.js +236 -42
  134. package/dist/language/flows/flow-store-for-session.d.ts +2 -2
  135. package/dist/language/flows/flow-store-for-session.js +2 -2
  136. package/dist/language/flows/flow-tools.js +58 -34
  137. package/dist/language/flows/flows.js +5 -2
  138. package/dist/language/flows/heal-run.js +6 -4
  139. package/dist/language/flows/recording/flake.d.ts +2 -2
  140. package/dist/language/flows/recording/tape/recordings.d.ts +30 -4
  141. package/dist/language/flows/recording/tape/recordings.js +40 -8
  142. package/dist/language/flows/replay-from-hud.d.ts +20 -0
  143. package/dist/language/flows/replay-from-hud.js +51 -0
  144. package/dist/language/flows/replay.d.ts +9 -5
  145. package/dist/language/flows/replay.js +27 -9
  146. package/dist/language/flows/suite/suite-flakes.js +2 -0
  147. package/dist/language/flows/suite-selection.d.ts +2 -15
  148. package/dist/language/flows/suite-selection.js +4 -3
  149. package/dist/language/flows/verify-change-tools.js +8 -4
  150. package/dist/launch-chromium.d.ts +27 -5
  151. package/dist/launch-chromium.js +38 -8
  152. package/dist/memory/cloud/cloud-config.d.ts +12 -0
  153. package/dist/memory/cloud/cloud-config.js +15 -0
  154. package/dist/memory/cloud/cloud-sync.d.ts +17 -14
  155. package/dist/memory/cloud/cloud-sync.js +12 -1
  156. package/dist/memory/cloud/harness-config.d.ts +26 -1
  157. package/dist/memory/cloud/harness-config.js +99 -4
  158. package/dist/memory/cloud/hud-notices-source.d.ts +21 -0
  159. package/dist/memory/cloud/hud-notices-source.js +128 -0
  160. package/dist/memory/cloud/memory-scope.d.ts +144 -0
  161. package/dist/memory/cloud/memory-scope.js +205 -0
  162. package/dist/memory/cloud/sync-cycle.d.ts +21 -3
  163. package/dist/memory/cloud/sync-cycle.js +45 -5
  164. package/dist/memory/cloud/sync-daemon.js +7 -5
  165. package/dist/memory/cloud/sync-disk.d.ts +1 -1
  166. package/dist/memory/cloud/sync-disk.js +5 -8
  167. package/dist/memory/impact/account-watch.d.ts +19 -0
  168. package/dist/memory/impact/account-watch.js +34 -0
  169. package/dist/memory/impact/impact-recorder.d.ts +8 -0
  170. package/dist/memory/impact/impact-recorder.js +12 -2
  171. package/dist/memory/impact/impact-store.d.ts +16 -0
  172. package/dist/memory/impact/impact-store.js +142 -26
  173. package/dist/memory/intent/intent-shard.d.ts +2 -2
  174. package/dist/memory/intent/intent-tools.js +88 -5
  175. package/dist/memory/journal/envelope.d.ts +16 -16
  176. package/dist/memory/journal/journal-query.d.ts +14 -0
  177. package/dist/memory/journal/journal-query.js +15 -0
  178. package/dist/memory/journal/journal-recorder.js +7 -1
  179. package/dist/memory/journal/on-disk/retention.d.ts +3 -0
  180. package/dist/memory/journal/on-disk/retention.js +5 -0
  181. package/dist/memory/journal/on-disk/startup-maintenance.js +3 -1
  182. package/dist/memory/journal/on-disk/workspace-tiers.js +7 -4
  183. package/dist/memory/journal/session-end.js +6 -4
  184. package/dist/memory/project/artifact-root-resolver.d.ts +36 -1
  185. package/dist/memory/project/artifact-root-resolver.js +134 -26
  186. package/dist/memory/project/artifact-root.d.ts +42 -5
  187. package/dist/memory/project/artifact-root.js +87 -8
  188. package/dist/memory/project/dir/reticle-dir.d.ts +4 -2
  189. package/dist/memory/project/dir/reticle-dir.js +3 -1
  190. package/dist/memory/project/project-tools.js +22 -3
  191. package/dist/memory/project/session-root.d.ts +24 -1
  192. package/dist/memory/project/session-root.js +41 -5
  193. package/dist/memory/project/sync-status.d.ts +39 -0
  194. package/dist/memory/project/sync-status.js +90 -0
  195. package/dist/memory/recall/memory-tools.js +2 -0
  196. package/dist/memory/recall/project-memory.d.ts +19 -0
  197. package/dist/memory/recall/project-memory.js +45 -6
  198. package/dist/portal/bridge/bridge.d.ts +7 -4
  199. package/dist/portal/bridge/bridge.js +25 -3
  200. package/dist/portal/bridge/pairing-token.d.ts +9 -0
  201. package/dist/portal/bridge/pairing-token.js +17 -0
  202. package/dist/portal/input/real-input.d.ts +81 -10
  203. package/dist/portal/input/real-input.js +207 -22
  204. package/dist/portal/input/viewport-tools.js +19 -9
  205. package/dist/portal/pool/browser-pool.d.ts +22 -9
  206. package/dist/portal/pool/browser-pool.js +70 -35
  207. package/dist/portal/pool/playwright-launcher.d.ts +13 -0
  208. package/dist/portal/pool/playwright-launcher.js +17 -1
  209. package/dist/portal/pool/pool-contract.d.ts +7 -4
  210. package/dist/portal/session/command-payload.d.ts +8 -0
  211. package/dist/portal/session/command-payload.js +19 -0
  212. package/dist/portal/session/dev-server/dev-command.d.ts +12 -0
  213. package/dist/portal/session/dev-server/dev-command.js +61 -12
  214. package/dist/portal/session/facts/departure-note.d.ts +22 -0
  215. package/dist/portal/session/facts/departure-note.js +47 -0
  216. package/dist/portal/session/facts/session-replaced.d.ts +5 -0
  217. package/dist/portal/session/facts/session-replaced.js +5 -0
  218. package/dist/portal/session/first-run-wiring.d.ts +42 -0
  219. package/dist/portal/session/first-run-wiring.js +132 -0
  220. package/dist/portal/session/human/notes-ledger.d.ts +20 -0
  221. package/dist/portal/session/human/notes-ledger.js +55 -0
  222. package/dist/portal/session/human/review-store.d.ts +12 -0
  223. package/dist/portal/session/human/review-store.js +18 -10
  224. package/dist/portal/session/live-control-tools.d.ts +0 -13
  225. package/dist/portal/session/live-control-tools.js +28 -3
  226. package/dist/portal/session/no-session-diagnosis.d.ts +7 -0
  227. package/dist/portal/session/no-session-diagnosis.js +15 -0
  228. package/dist/portal/session/no-session-next-action.d.ts +11 -0
  229. package/dist/portal/session/no-session-next-action.js +31 -5
  230. package/dist/portal/session/no-session-watch.d.ts +14 -1
  231. package/dist/portal/session/no-session-watch.js +63 -10
  232. package/dist/portal/session/presence/session-mirror.js +3 -0
  233. package/dist/portal/session/serving-directory.d.ts +27 -0
  234. package/dist/portal/session/serving-directory.js +45 -0
  235. package/dist/portal/session/session-health.d.ts +7 -0
  236. package/dist/portal/session/session-health.js +6 -0
  237. package/dist/portal/session/session-info.d.ts +8 -1
  238. package/dist/portal/session/session-info.js +6 -0
  239. package/dist/portal/session/session-manager.d.ts +21 -0
  240. package/dist/portal/session/session-manager.js +44 -8
  241. package/dist/portal/session/session-reaper.js +4 -1
  242. package/dist/portal/session/session-successor.d.ts +5 -0
  243. package/dist/portal/session/session-tools.js +26 -3
  244. package/dist/portal/session/session.d.ts +12 -10
  245. package/dist/portal/session/session.js +30 -31
  246. package/dist/start-options.d.ts +6 -0
  247. package/dist/status-payload.d.ts +10 -0
  248. package/dist/status-payload.js +23 -0
  249. package/dist/surface/http-server.d.ts +1 -1
  250. package/dist/surface/http-server.js +15 -4
  251. package/dist/surface/mcp/mcp-proxy.d.ts +5 -0
  252. package/dist/surface/mcp/mcp-proxy.js +36 -5
  253. package/dist/surface/mcp/mcp.d.ts +11 -6
  254. package/dist/surface/mcp/mcp.js +28 -14
  255. package/dist/surface/mcp/proxy/proxy-log.js +3 -5
  256. package/dist/surface/mcp/server-instructions.d.ts +4 -2
  257. package/dist/surface/mcp/server-instructions.js +14 -4
  258. package/dist/surface/mcp/shared-params.js +2 -2
  259. package/dist/surface/tools/act/navigation/navigate-arrival.d.ts +8 -0
  260. package/dist/surface/tools/act/navigation/navigate-arrival.js +56 -2
  261. package/dist/surface/tools/act/settle-in-flight.js +25 -3
  262. package/dist/surface/tools/act-sequence-tool.js +2 -2
  263. package/dist/surface/tools/act-tools.js +25 -9
  264. package/dist/surface/tools/assert/assert-verdict.d.ts +1 -1
  265. package/dist/surface/tools/assert/assert-verdict.js +17 -2
  266. package/dist/surface/tools/browser-tools.js +8 -1
  267. package/dist/surface/tools/chat-drives.d.ts +26 -0
  268. package/dist/surface/tools/chat-drives.js +165 -0
  269. package/dist/surface/tools/dynamic-tools.js +29 -2
  270. package/dist/surface/tools/empty-session-list.d.ts +41 -0
  271. package/dist/surface/tools/empty-session-list.js +80 -0
  272. package/dist/surface/tools/error-recovery.d.ts +6 -0
  273. package/dist/surface/tools/error-recovery.js +38 -6
  274. package/dist/surface/tools/explore-tools.d.ts +10 -1
  275. package/dist/surface/tools/explore-tools.js +82 -13
  276. package/dist/surface/tools/feedback-tools.js +14 -0
  277. package/dist/surface/tools/harness-explore.d.ts +96 -14
  278. package/dist/surface/tools/harness-explore.js +227 -202
  279. package/dist/surface/tools/harness-plan.d.ts +12 -0
  280. package/dist/surface/tools/harness-plan.js +31 -0
  281. package/dist/surface/tools/harness-script.d.ts +8 -0
  282. package/dist/surface/tools/harness-script.js +342 -0
  283. package/dist/surface/tools/harness-toolset.d.ts +14 -1
  284. package/dist/surface/tools/harness-toolset.js +77 -6
  285. package/dist/surface/tools/invoke-tool.js +64 -9
  286. package/dist/surface/tools/lease-seed.d.ts +29 -0
  287. package/dist/surface/tools/lease-seed.js +123 -0
  288. package/dist/surface/tools/lease-tools.d.ts +3 -23
  289. package/dist/surface/tools/lease-tools.js +30 -131
  290. package/dist/surface/tools/live-call-text.js +2 -2
  291. package/dist/surface/tools/next-step.d.ts +19 -0
  292. package/dist/surface/tools/next-step.js +159 -0
  293. package/dist/surface/tools/observe-tools.js +2 -4
  294. package/dist/surface/tools/platform-moment.d.ts +12 -0
  295. package/dist/surface/tools/platform-moment.js +168 -0
  296. package/dist/surface/tools/query-hint-schema.d.ts +7 -0
  297. package/dist/surface/tools/query-hint-schema.js +10 -0
  298. package/dist/surface/tools/read-tools.js +8 -2
  299. package/dist/surface/tools/real-input-attempt.js +96 -13
  300. package/dist/surface/tools/tool-kit.d.ts +25 -1
  301. package/dist/surface/tools/tool-kit.js +4 -6
  302. package/dist/surface/tools/tools.js +20 -48
  303. package/dist/telemetry/argument-shape.js +1 -1
  304. package/dist/telemetry/hud-metrics.d.ts +2 -0
  305. package/dist/telemetry/hud-metrics.js +17 -0
  306. package/dist/telemetry/onboarding-firsts.d.ts +2 -2
  307. package/dist/telemetry/onboarding-firsts.js +2 -2
  308. package/dist/telemetry/telemetry.d.ts +12 -0
  309. package/dist/telemetry/telemetry.js +43 -14
  310. package/dist/telemetry/verification-of.js +1 -0
  311. package/dist/wire-journal.js +4 -1
  312. package/docs/README.md +1 -1
  313. package/docs/actions.mdx +1 -2
  314. package/docs/agent-cheatsheet.md +1 -1
  315. package/docs/autodrive.md +29 -25
  316. package/docs/cli/cloud.mdx +6 -4
  317. package/docs/cli/doctor.mdx +6 -0
  318. package/docs/cli/drive.mdx +8 -2
  319. package/docs/cli/init.mdx +11 -13
  320. package/docs/cli/status.mdx +5 -1
  321. package/docs/cli/version.mdx +3 -3
  322. package/docs/cli.mdx +14 -8
  323. package/docs/desktop.mdx +5 -1
  324. package/docs/environment.mdx +3 -7
  325. package/docs/getting-started.md +14 -2
  326. package/docs/human-control.md +1 -2
  327. package/docs/index.mdx +4 -3
  328. package/docs/install-agentic.mdx +1 -1
  329. package/docs/install-manual.mdx +2 -2
  330. package/docs/packages/next.mdx +3 -0
  331. package/docs/packages/tauri.mdx +2 -2
  332. package/docs/packages/vite-plugin.mdx +24 -1
  333. package/docs/packages.mdx +1 -1
  334. package/docs/quickstart.mdx +23 -11
  335. package/docs/telemetry.md +1 -1
  336. package/docs/tools/act.mdx +1 -1
  337. package/docs/tools/session-and-feedback.mdx +9 -9
  338. package/docs/usage.md +5 -3
  339. package/docs/what-is-recorded.md +13 -3
  340. package/docs/what-is-reticle.mdx +3 -1
  341. package/package.json +8 -12
  342. package/dist/features/harness/driver.d.ts +0 -50
  343. package/dist/features/harness/driver.js +0 -236
  344. package/dist/features/harness/jev-driver.d.ts +0 -188
  345. package/dist/features/harness/jev-driver.js +0 -678
  346. package/dist/features/harness/openai-driver.d.ts +0 -46
  347. package/dist/features/harness/openai-driver.js +0 -194
  348. package/dist/memory/project/dir/fill-value-store.d.ts +0 -32
  349. package/dist/memory/project/dir/fill-value-store.js +0 -88
  350. package/docs/local-registry.md +0 -143
@@ -1,4 +1,13 @@
1
- import type { ToolDef } from '../../surface/tools/tool-kit.js';
1
+ import type { ToolDef, ToolDeps } from '../../surface/tools/tool-kit.js';
2
+ /**
3
+ * One picture of the tab a chat-requested drive is driving, or undefined when there is none to take.
4
+ *
5
+ * The leased browser first, when this daemon launched the tab; then the desktop window's own capture,
6
+ * the same route `reticle_screenshot` takes. Without the second, a drive of a desktop app sent its
7
+ * steps to the chat and never a picture. Never throws: a frame is a courtesy, and a tab that has
8
+ * gone mid-drive must not break the drive.
9
+ */
10
+ export declare function driveFrame(deps: ToolDeps, sessionId: string, jpegQuality: number): Promise<Uint8Array | undefined>;
2
11
  /**
3
12
  * Only read a capture the shell wrote. The path arrives from the PAGE, and the daemon must not
4
13
  * become a file-read oracle for whatever a compromised renderer names.
@@ -111,6 +111,32 @@ async function desktopCapture(deps, sessionId, fullPage) {
111
111
  return {};
112
112
  }
113
113
  }
114
+ /**
115
+ * One picture of the tab a chat-requested drive is driving, or undefined when there is none to take.
116
+ *
117
+ * The leased browser first, when this daemon launched the tab; then the desktop window's own capture,
118
+ * the same route `reticle_screenshot` takes. Without the second, a drive of a desktop app sent its
119
+ * steps to the chat and never a picture. Never throws: a frame is a courtesy, and a tab that has
120
+ * gone mid-drive must not break the drive.
121
+ */
122
+ export async function driveFrame(deps, sessionId, jpegQuality) {
123
+ try {
124
+ const leased = await deps.pool?.screenshotLease(sessionId, { jpegQuality });
125
+ if (leased !== undefined)
126
+ return leased;
127
+ // The window `reticle drive` opened: neither a lease nor a desktop shell, but its page is ours.
128
+ const url = deps.sessions.resolve(sessionId).url;
129
+ if (true === (await deps.realInput?.isAvailableFor(url))) {
130
+ const shot = await deps.realInput?.screenshot?.(url, { jpegQuality });
131
+ if (shot !== undefined)
132
+ return shot;
133
+ }
134
+ return (await desktopCapture(deps, sessionId, false)).png;
135
+ }
136
+ catch {
137
+ return undefined;
138
+ }
139
+ }
114
140
  /**
115
141
  * Only read a capture the shell wrote. The path arrives from the PAGE, and the daemon must not
116
142
  * become a file-read oracle for whatever a compromised renderer names.
@@ -179,9 +205,20 @@ function runtimeOf(deps, sessionId) {
179
205
  * baseline that says nothing about the content below the fold, and a later diff would call it green.
180
206
  */
181
207
  async function capture(deps, sessionId, args) {
182
- const provider = screenshotProvider(deps);
183
- if (provider !== undefined) {
184
- const session = deps.sessions.resolve(sessionId);
208
+ /*
209
+ * Only a provider driving THIS session's page. A launched provider photographs the one page it
210
+ * owns whatever url it is handed, so asking it about another session saved the driven tab's pixels
211
+ * under that session's name and answered `saved: true` (#1407). Real input asks the same question
212
+ * before every gesture.
213
+ */
214
+ const candidate = screenshotProvider(deps);
215
+ const session = candidate === undefined ? undefined : deps.sessions.resolve(sessionId);
216
+ const provider = candidate !== undefined &&
217
+ session !== undefined &&
218
+ (await candidate.isAvailableFor(session.url))
219
+ ? candidate
220
+ : undefined;
221
+ if (provider !== undefined && session !== undefined) {
185
222
  const png = await provider.screenshot(session.url, await buildOpts(deps, sessionId, args));
186
223
  // A driven browser renders the session's URL in a BROWSER — so the pixels are web even when the
187
224
  // session named is a desktop window. Scoping those under the desktop runtime would corrupt that
@@ -0,0 +1,4 @@
1
+ import type { DrivenBy } from '@reticlehq/core';
2
+ export declare function runDrivenBy<T>(by: DrivenBy, fn: () => Promise<T>): Promise<T>;
3
+ /** The Harness drive this call is part of, or undefined when the connected agent made it. */
4
+ export declare function currentDrivenBy(): DrivenBy | undefined;
@@ -0,0 +1,16 @@
1
+ import { AsyncLocalStorage } from 'node:async_hooks';
2
+ /**
3
+ * Which Harness drive the current tool call belongs to, carried across every await under it.
4
+ *
5
+ * The Harness dispatches through the same tools an agent calls, so nothing in a tool's arguments can
6
+ * say who called it, and adding a field to every tool's schema would advertise it to every agent.
7
+ * The context is set once around each Harness call and read where the journal writes the action.
8
+ */
9
+ const current = new AsyncLocalStorage();
10
+ export function runDrivenBy(by, fn) {
11
+ return current.run(by, fn);
12
+ }
13
+ /** The Harness drive this call is part of, or undefined when the connected agent made it. */
14
+ export function currentDrivenBy() {
15
+ return current.getStore();
16
+ }
@@ -21,7 +21,7 @@
21
21
  * broken gets a log line; their verdicts keep working.
22
22
  */
23
23
  import { spawn } from 'node:child_process';
24
- import { readFileSync, statSync } from 'node:fs';
24
+ import { closeSync, fstatSync, openSync, readFileSync } from 'node:fs';
25
25
  import { join } from 'node:path';
26
26
  import { HOOK_EVENT_NAMES, HookConfigSchema } from '@reticlehq/core/hooks';
27
27
  import { ReticleDir } from '@reticlehq/core';
@@ -65,18 +65,29 @@ let cached;
65
65
  */
66
66
  export function readHookConfig(reticleRoot) {
67
67
  const path = join(reticleRoot, ReticleDir.HOOKS_FILE);
68
- let mtime;
68
+ // Open once, then stat and read the SAME descriptor: the mtime the cache keys on is the mtime of
69
+ // the bytes read, not of whatever the path pointed at a moment earlier.
70
+ let fd;
69
71
  try {
70
- mtime = statSync(path).mtimeMs;
72
+ fd = openSync(path, 'r');
71
73
  }
72
74
  catch {
73
75
  cached = undefined;
74
76
  return {};
75
77
  }
78
+ try {
79
+ return readOpenHookConfig(fd);
80
+ }
81
+ finally {
82
+ closeSync(fd);
83
+ }
84
+ }
85
+ function readOpenHookConfig(fd) {
86
+ const mtime = fstatSync(fd).mtimeMs;
76
87
  if (cached !== undefined && cached.at === mtime)
77
88
  return cached.config;
78
89
  try {
79
- const parsed = HookConfigSchema.safeParse(JSON.parse(readFileSync(path, 'utf8')));
90
+ const parsed = HookConfigSchema.safeParse(JSON.parse(readFileSync(fd, 'utf8')));
80
91
  if (!parsed.success) {
81
92
  reportOnce(`config:${String(mtime)}`, `${ReticleDir.HOOKS_FILE} is not a map of event→command`);
82
93
  cached = { at: mtime, config: {} };
@@ -0,0 +1,9 @@
1
+ export declare function toolLogPath(): string | undefined;
2
+ export declare function logToolCall(path: string, entry: {
3
+ tool: string;
4
+ args: unknown;
5
+ at: number;
6
+ ms: number;
7
+ result?: unknown;
8
+ error?: string;
9
+ }): void;
@@ -0,0 +1,29 @@
1
+ import { appendFileSync } from 'node:fs';
2
+ import { currentDrivenBy } from './driven-by.js';
3
+ /**
4
+ * Every tool call, its arguments and what it answered, appended to a file a person names.
5
+ *
6
+ * Off unless `RETICLE_TOOL_LOG` is set. It exists to put two drives side by side (an agent's and
7
+ * the Harness's) and read what each one asked and was told; `drivenBy` says which was which. Local
8
+ * only: nothing here leaves the machine.
9
+ */
10
+ const TOOL_LOG_ENV = 'RETICLE_TOOL_LOG';
11
+ /** A page snapshot can be large; past this a record keeps its head and says it was cut. */
12
+ const MAX_RESULT_CHARS = 200_000;
13
+ export function toolLogPath() {
14
+ const path = process.env[TOOL_LOG_ENV];
15
+ return path === undefined || 0 === path.trim().length ? undefined : path;
16
+ }
17
+ export function logToolCall(path, entry) {
18
+ try {
19
+ const text = JSON.stringify(entry.result ?? null);
20
+ const result = text.length > MAX_RESULT_CHARS
21
+ ? { truncated: text.slice(0, MAX_RESULT_CHARS) }
22
+ : entry.result;
23
+ const drivenBy = currentDrivenBy();
24
+ appendFileSync(path, `${JSON.stringify({ ...entry, result, ...(drivenBy === undefined ? {} : { drivenBy }) })}\n`);
25
+ }
26
+ catch {
27
+ /* a log that cannot be written never fails the call it describes */
28
+ }
29
+ }
package/dist/index.d.ts CHANGED
@@ -2,6 +2,7 @@ import type { StartOptions } from './start-options.js';
2
2
  export type { StartOptions } from './start-options.js';
3
3
  import { Bridge } from './portal/bridge/bridge.js';
4
4
  import type { RealInputProvider } from './portal/input/real-input.js';
5
+ export { replayVerdictLine } from './language/flows/replay-from-hud.js';
5
6
  export { ReticleTool } from '@reticlehq/core';
6
7
  export { RingBuffer } from '@reticlehq/engine/window/ring-buffer.js';
7
8
  export { Bridge } from './portal/bridge/bridge.js';
package/dist/index.js CHANGED
@@ -1,30 +1,34 @@
1
- import { writeHarnessSwitch } from './memory/cloud/harness-switch.js';
2
- import { harnessConfigSource } from './memory/cloud/harness-config.js';
1
+ import { applyHarnessSwitch, harnessConfigsByRoot } from './memory/cloud/harness-config.js';
2
+ import { coveragePercents } from './features/exhaust/ledger.js';
3
+ import { firstRunWiring } from './portal/session/first-run-wiring.js';
3
4
  import { fetchPlatformConfig } from './features/harness/platform-config.js';
5
+ import { watchAccountFiles } from './memory/impact/account-watch.js';
6
+ import { SESSION_FILE } from './command/cli/cloud-kit.js';
4
7
  import { join } from 'node:path';
5
- import { existsSync, readFileSync } from 'node:fs';
6
8
  import { homedir } from 'node:os';
7
- import { linkedCloudPort, platformEnvPort } from './memory/cloud/cloud-config.js';
9
+ import { linkedCloudPort, platformEnvPort, sessionCloudPort } from './memory/cloud/cloud-config.js';
10
+ import { sessionRoot } from './memory/project/session-root.js';
8
11
  import { attachCloudSync } from './memory/cloud/sync-daemon.js';
9
12
  import { wireHooks } from './hooks/hook-commands.js';
10
- import { PROJECT_REGISTRY_FILE, parseProjectRegistry, projectCandidates, } from '@reticlehq/core/artifacts';
11
- import { discoverProjectConfigs } from './command/cli/config/config-discovery.js';
12
- import { artifactRootResolver } from './memory/project/artifact-root-resolver.js';
13
+ import { currentDrivenBy } from './hooks/driven-by.js';
14
+ import { artifactRootResolver, knownProjectCandidates, projectDirectoryFor, } from './memory/project/artifact-root-resolver.js';
15
+ import { servingDirectoryOf } from './portal/session/serving-directory.js';
13
16
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
14
- import { AGENT_STOPPED_NOTICE, RETICLE_DEFAULT_PORT, bridgeWsUrl, ReticleCommand, ReticleDir, ReticleEnv, LOOPBACK_HOST, ReplayStatus, EventType, } from '@reticlehq/core';
17
+ import { AGENT_STOPPED_NOTICE, RETICLE_DEFAULT_PORT, bridgeWsUrl, ReticleCommand, ReticleDir, ReticleEnv, LOOPBACK_HOST, EventType, } from '@reticlehq/core';
15
18
  import { originOf } from './portal/session/session-manager.js';
16
19
  import { setBrowserMode, BrowserMode } from './telemetry/browser-mode.js';
17
- import { replayAndLearn } from './language/flows/flow-learning.js';
20
+ import { replayFromHud } from './language/flows/replay-from-hud.js';
18
21
  import { createSharedServer } from './surface/http-server.js';
19
22
  import { openLoopbackAlias } from './command/daemon/binding/loopback-alias.js';
20
23
  import { reportAppInstrumented } from './telemetry/app-instrumented.js';
21
24
  import { resolveBridgeSecurityWithAutoToken } from './portal/bridge/bridge-security.js';
22
25
  import { Bridge } from './portal/bridge/bridge.js';
23
26
  import { sdkFixForDirectory } from './command/version/sdk-fix.js';
27
+ import { connectionSkew } from './command/version/version-nudge.js';
24
28
  import { SERVER_VERSION } from './command/version/identity/server-version.js';
25
29
  import { BaselineStore } from './memory/project/baselines.js';
26
30
  import { RecordingStore } from './language/flows/recording/tape/recordings.js';
27
- import { initImpact } from './memory/impact/impact-recorder.js';
31
+ import { initImpact, impactSnapshot } from './memory/impact/impact-recorder.js';
28
32
  import { flowAuthor } from './language/flows/flow-author.js';
29
33
  import { FlowStore } from './language/flows/flows.js';
30
34
  import { buildFlowChips } from './language/flows/flow-scope.js';
@@ -39,7 +43,9 @@ import { createRunnerPort } from './judgement/runs/runner-port.js';
39
43
  import { RunStore } from './judgement/runs/artifact/run-store.js';
40
44
  import { startVerifyServer } from './judgement/runs/verify-server.js';
41
45
  import { createMcpServer } from './surface/mcp/mcp.js';
46
+ import { instructionStateAt } from './surface/mcp/mcp-proxy.js';
42
47
  import { LEASE_ACQUIRE_TOOL } from './surface/tools/lease-tools.js';
48
+ import { startChatDrives } from './surface/tools/chat-drives.js';
43
49
  import { runTool } from './surface/tools/invoke-tool.js';
44
50
  import { SessionReaper, endAllSessions, MCP_DISCONNECT_SUMMARY, } from './portal/session/session-reaper.js';
45
51
  import { wireSessionScope } from './portal/session/no-session-watch.js';
@@ -52,9 +58,7 @@ import { BrowserPool } from './portal/pool/browser-pool.js';
52
58
  import { AGENT_ALREADY_DRIVING_ELSEWHERE, shouldGreetWithLeaseNotice, } from './portal/session/lease-visibility.js';
53
59
  import { playwrightLauncher, resolveMaxContexts } from './portal/pool/playwright-launcher.js';
54
60
  import { LeaseReaper } from './portal/pool/lease-reaper.js';
55
- import { findProjectConfig, projectIdsAt, readJournalEnabled, readProjectId, } from './command/cli/ports/resolve/cli-port.js';
56
- import { hasAnyProjectConnectedBefore } from './memory/recall/prior/connection-memory.js';
57
- import { reticleStateHome } from './command/daemon/daemon.js';
61
+ import { findProjectConfig, readJournalEnabled, readProjectId, } from './command/cli/ports/resolve/cli-port.js';
58
62
  import { probeLaunchableChromium } from './launch-chromium.js';
59
63
  import { attachJournal } from './wire-journal.js';
60
64
  import { reportOnboardingStep } from './telemetry/onboarding-funnel.js';
@@ -62,14 +66,7 @@ import { AMBIENT_RECORDING } from './language/flows/recording/tape/recordings.js
62
66
  import { readRetainPolicy } from './memory/journal/on-disk/retain-policy.js';
63
67
  import { log } from './log.js';
64
68
  import { readReaderBundle, zeroInstallScript as zeroInstallScriptFor, } from './portal/pool/zero-install.js';
65
- /** A human-facing one-liner for a panel replay verdict — ✓ passed / ⚠ drifted / ✗ errored. */
66
- function replayVerdictLine(result) {
67
- if (result.status === ReplayStatus.OK)
68
- return `✓ "${result.name}" passed`;
69
- if (result.status === ReplayStatus.DRIFT)
70
- return `⚠ "${result.name}" drifted — a step no longer matches`;
71
- return `✗ "${result.name}" failed — ${result.error?.message ?? 'could not replay'}`;
72
- }
69
+ export { replayVerdictLine } from './language/flows/replay-from-hud.js';
73
70
  // Re-exported from the contract, where the names now live: a consumer importing the server should
74
71
  // not have to know that the vocabulary moved.
75
72
  export { ReticleTool } from '@reticlehq/core';
@@ -137,6 +134,7 @@ function createBrowserPool(headless, reader) {
137
134
  maxContexts,
138
135
  genSessionId,
139
136
  zeroInstallScript,
137
+ launchHeaded: playwrightLauncher({ headless: false }),
140
138
  });
141
139
  }
142
140
  /**
@@ -225,12 +223,12 @@ async function resolveRealInput(options, onNavigateError, onNetworkDetail) {
225
223
  }
226
224
  /** Start the Reticle bridge (browser WS endpoint) and, by default, the MCP stdio server. */
227
225
  /**
228
- * The SDK-upgrade sentence for THIS project's package.json, evaluated at each HELLO so a
229
- * just-edited manifest is what we name. Falls back to the framework-neutral sensor when cwd is
230
- * not an app.
226
+ * The SDK-upgrade sentence for the package.json of the project the page announced, evaluated at
227
+ * each HELLO so a just-edited manifest is what we name. The daemon's cwd only when that project's
228
+ * directory is unknown, and the framework-neutral sensor when that is not an app either (#1135).
231
229
  */
232
- function sdkFixForCwd() {
233
- return sdkFixForDirectory(SERVER_VERSION, process.cwd());
230
+ function sdkFixForProject(projectId) {
231
+ return sdkFixForDirectory(SERVER_VERSION, projectDirectoryFor(projectId) ?? process.cwd());
234
232
  }
235
233
  /**
236
234
  * Resolve a session's artifact root, from everything this machine knows about where projects live.
@@ -252,44 +250,29 @@ function sdkFixForCwd() {
252
250
  * not. Deciding that here would mean reading every cloud.json on every tick.
253
251
  */
254
252
  function knownProjectRoots() {
255
- const roots = new Set();
256
- try {
257
- const path = join(homedir(), ReticleDir.ROOT, PROJECT_REGISTRY_FILE);
258
- if (existsSync(path)) {
259
- const registry = parseProjectRegistry(JSON.parse(readFileSync(path, 'utf8')));
260
- for (const candidate of projectCandidates(registry))
261
- roots.add(join(candidate.directory, ReticleDir.ROOT));
262
- }
263
- }
264
- catch {
265
- // A registry that cannot be read is an empty one — never a reason to stop syncing.
266
- }
267
- try {
268
- for (const config of discoverProjectConfigs(process.cwd()).found)
269
- roots.add(join(config.directory, ReticleDir.ROOT));
270
- }
271
- catch {
272
- // Same: a diagnostic walk that throws must not take the sync loop with it.
273
- }
274
- return [...roots];
253
+ return [...new Set(knownProjectCandidates().map((c) => join(c.directory, ReticleDir.ROOT)))];
275
254
  }
276
255
  /** This project's platform credential (stored, else the env key), as the env the platform readers take. */
277
256
  const platformEnvFor = (root) => platformEnvPort(createNodeFileSystem(), root ?? join(process.cwd(), ReticleDir.ROOT), homedir(), process.env);
257
+ const loadHarnessConfig = async (root) => fetchPlatformConfig(await platformEnvFor(root)());
278
258
  export async function start(options = {}) {
279
259
  const port = options.port ?? RETICLE_DEFAULT_PORT;
260
+ const configForRoot = harnessConfigsByRoot(loadHarnessConfig);
280
261
  // Open the user's impact record before anything can connect. Not inside the MCP branch: a daemon
281
262
  // serving a browser with no agent attached still has a HUD to answer, and a report that reads
282
263
  // "nothing recorded yet" over a month of history on disk is the worst version of this feature.
283
264
  initImpact({
265
+ sdkVersion: SERVER_VERSION,
284
266
  reticleRoot: options.reticleRoot ?? join(process.cwd(), ReticleDir.ROOT),
285
267
  // The daemon owns both sides of this seam, so it is the layer that may join them: the cache
286
268
  // lives in cloud memory, the platform read lives in the harness feature, and neither is allowed
287
269
  // to reach for the other.
288
- config: harnessConfigSource(async () => fetchPlatformConfig(await platformEnvFor(options.reticleRoot)())),
270
+ configForRoot,
271
+ coverageForRoot: (root) => () => coveragePercents(root),
289
272
  });
290
273
  const uninstallHooks = wireHooks(options.reticleRoot ?? join(process.cwd(), ReticleDir.ROOT), readProjectId(process.cwd()));
291
274
  const security = await resolveBridgeSecurityWithAutoToken(options);
292
- const bridge = new Bridge({ port, sdkFix: sdkFixForCwd, ...security });
275
+ const bridge = new Bridge({ port, sdkFix: sdkFixForProject, ...security });
293
276
  // Server-authoritative liveness: a Node-side reaper (immune to browser throttling) ends sessions
294
277
  // whose agent has gone idle, so a forgotten/crashed agent never leaves the HUD "running" forever.
295
278
  const reaper = new SessionReaper(bridge.sessions);
@@ -301,7 +284,7 @@ export async function start(options = {}) {
301
284
  bridge.sessions.setDefaultScope({ projectId: activeProjectId });
302
285
  }
303
286
  const baselines = new BaselineStore();
304
- const recordings = new RecordingStore();
287
+ const recordings = new RecordingStore(() => currentDrivenBy() !== undefined);
305
288
  // drive precedence: driveUrl (launch+own a browser) → CDP (attach) → none.
306
289
  let pool;
307
290
  let leaseReaper;
@@ -309,6 +292,7 @@ export async function start(options = {}) {
309
292
  // page and the SDK session share an origin, so a NET_DETAIL is pushed to the matching connected session.
310
293
  const routeNetworkDetail = makeNetworkDetailRouter(bridge, options.driveUrl);
311
294
  const { realInput, owned } = await resolveRealInput(options, () => bridge.close(), routeNetworkDetail);
295
+ let chatDrives;
312
296
  if (options.mcp !== false) {
313
297
  // cwd/Date.now are confined to start — never inside reticle-dir.ts's pure logic (rule 7).
314
298
  const fs = createNodeFileSystem();
@@ -366,7 +350,7 @@ export async function start(options = {}) {
366
350
  fs,
367
351
  reticleRoot,
368
352
  linkedCloud: linkedCloudPort(fs, reticleRoot, homedir(), process.env),
369
- artifactRootFor: artifactRootResolver(reticleRoot),
353
+ artifactRootFor: artifactRootResolver(reticleRoot, { servingDirectory: servingDirectoryOf }),
370
354
  now,
371
355
  bridgePort: port,
372
356
  browserProbe: probeLaunchableChromium,
@@ -375,13 +359,13 @@ export async function start(options = {}) {
375
359
  ...(activeProjectId === undefined ? {} : { projectId: activeProjectId }),
376
360
  };
377
361
  const profile = resolveToolSurface(options.toolProfile);
378
- const server = createMcpServer(realInput !== undefined ? { ...deps, realInput } : deps, profile,
379
- // A session live RIGHT NOW is stronger evidence than the durable memory, which can be empty or
380
- // stale for a project the plugin wired without writing `.reticle.json` (see connection-memory.ts's
381
- // KNOWN LIMIT). Without this OR, that project's `initialize` led with the first-install steps
382
- // while a real session was already connected — see #1138.
383
- bridge.sessions.count() > 0 ||
384
- hasAnyProjectConnectedBefore(reticleStateHome(), port, projectIdsAt(process.cwd())));
362
+ const toolDeps = realInput !== undefined ? { ...deps, realInput } : deps;
363
+ // The platform chat sees this app too: `reticle drive` in a window runs here, not in a daemon.
364
+ chatDrives = startChatDrives(toolDeps, bridge.sessions, fs, {
365
+ sessionCloud: sessionCloudPort(fs, homedir(), process.env, (id) => sessionRoot(toolDeps, id)),
366
+ version: SERVER_VERSION,
367
+ }, log);
368
+ const server = createMcpServer(toolDeps, profile, instructionStateAt(port, bridge.sessions.count()));
385
369
  // When the agent (the MCP client) disconnects cleanly, end every active session at once so the
386
370
  // HUD doesn't linger. (If the agent instead KILLS this process, the WS dies and the browser
387
371
  // self-ends via SESSION_LIFECYCLE.BRIDGE_LOST_MS — see transport.ts.)
@@ -400,6 +384,7 @@ export async function start(options = {}) {
400
384
  uninstallHooks();
401
385
  await cleanupCaptureDirectories();
402
386
  leaseReaper?.stop();
387
+ chatDrives?.stop();
403
388
  await pool?.shutdown();
404
389
  await owned?.dispose();
405
390
  await bridge.close();
@@ -414,21 +399,38 @@ export async function start(options = {}) {
414
399
  */
415
400
  export async function startDaemon(options = {}) {
416
401
  const port = options.port ?? RETICLE_DEFAULT_PORT;
402
+ let pushHarnessConfig = () => undefined;
403
+ const configForRoot = harnessConfigsByRoot(loadHarnessConfig, (root) => pushHarnessConfig(root));
417
404
  // The SAME line as in `start`, because these are two entry points that each wire their own world
418
405
  // and the daemon is the one that actually serves people. Wired only in `start`, the impact record
419
406
  // was never opened in the process the HUD talks to: tool calls still recorded (the dispatch
420
407
  // chokepoint opens it lazily), but a tab that connected before the first tool call was pushed
421
408
  // nothing, so the report read "nothing recorded yet" over a file with history in it.
422
409
  initImpact({
410
+ sdkVersion: SERVER_VERSION,
423
411
  reticleRoot: options.reticleRoot ?? join(process.cwd(), ReticleDir.ROOT),
424
412
  // The daemon owns both sides of this seam, so it is the layer that may join them: the cache
425
413
  // lives in cloud memory, the platform read lives in the harness feature, and neither is allowed
426
414
  // to reach for the other.
427
- config: harnessConfigSource(async () => fetchPlatformConfig(await platformEnvFor(options.reticleRoot)())),
415
+ configForRoot,
416
+ coverageForRoot: (root) => () => coveragePercents(root),
428
417
  });
429
418
  const security = await resolveBridgeSecurityWithAutoToken(options);
430
419
  const shared = createSharedServer(security.token === undefined ? {} : { token: security.token });
431
- const bridge = new Bridge({ port, server: shared.httpServer, sdkFix: sdkFixForCwd, ...security });
420
+ const bridge = new Bridge({
421
+ port,
422
+ server: shared.httpServer,
423
+ sdkFix: sdkFixForProject,
424
+ ...security,
425
+ });
426
+ const repaint = (only) => {
427
+ for (const session of bridge.sessions.all()) {
428
+ const root = session.artifactRoot ?? options.reticleRoot ?? join(process.cwd(), ReticleDir.ROOT);
429
+ if (only === undefined || only === root)
430
+ session.pushImpact(() => impactSnapshot(root), true);
431
+ }
432
+ };
433
+ pushHarnessConfig = repaint;
432
434
  // The daemon owns listen (below), so the real bind error is reported there; absorb bridge.ready's
433
435
  // mirror rejection so a port collision can't surface as an unhandled promise rejection.
434
436
  void bridge.ready.catch(() => undefined);
@@ -470,7 +472,7 @@ export async function startDaemon(options = {}) {
470
472
  const flows = new FlowStore(fs, reticleRoot, { now });
471
473
  // Built here rather than inside `deps` below, so teardown can save what a drive recorded. Both
472
474
  // paths pass the same pair — `daemon-parity.test.ts` is what keeps them from drifting apart.
473
- const recordings = new RecordingStore();
475
+ const recordings = new RecordingStore(() => currentDrivenBy() !== undefined);
474
476
  /*
475
477
  * Bound AFTER the sync daemon exists, read only when a run is actually written.
476
478
  *
@@ -523,11 +525,17 @@ export async function startDaemon(options = {}) {
523
525
  // `syncNow`, not `nudge`: a nudge schedules a cycle soon, which is right for "a run landed" and
524
526
  // wrong for a button somebody is watching. Never awaited.
525
527
  bridge.attachSyncRequest(() => void cloudSync.syncNow());
526
- // The panel's harness switch. Written through to the platform rather than kept locally, so the
527
- // console and the panel cannot disagree about a setting they both offer. Nothing is awaited and a
528
- // failure is not surfaced: the next snapshot re-reads the platform, so a lost write shows up as
529
- // the switch springing back, which is the truthful outcome.
530
- bridge.attachHarnessRequest((enabled) => void platformEnvFor(reticleRoot)().then((env) => writeHarnessSwitch(env, enabled)));
528
+ // The panel's harness switch, written through to the platform so console and panel cannot disagree.
529
+ if (options.hudSignIn !== undefined)
530
+ bridge.attachSigninRequest(options.hudSignIn(() => repaint()));
531
+ // logout and link run in another process: repaint when their files change. See account-watch.ts.
532
+ const accountFiles = watchAccountFiles(() => repaint());
533
+ accountFiles.watch(join(homedir(), ReticleDir.ROOT, SESSION_FILE));
534
+ bridge.attachSessionReady((s) => accountFiles.watch(join(s.artifactRoot ?? reticleRoot, ReticleDir.CLOUD_LINK_FILE)));
535
+ bridge.attachHarnessRequest((on, s) => {
536
+ const root = s.artifactRoot ?? reticleRoot;
537
+ applyHarnessSwitch(configForRoot, root, on, platformEnvFor(root));
538
+ });
531
539
  // Scope auto-selection to the active project (from .reticle.json) so a stray tab from another app is
532
540
  // never picked when the agent omits a sessionId. Explicit per-call scope/sessionId still overrides.
533
541
  // Scope + the no-session diagnosis: "no browser session connected" is the error that ends most
@@ -556,26 +564,29 @@ export async function startDaemon(options = {}) {
556
564
  // daemon — the common case — so flows were listed, loaded and healed against wherever the
557
565
  // daemon happened to be launched. Wired in only one of the two places, a resolver is a resolver
558
566
  // that does not run.
559
- artifactRootFor: artifactRootResolver(reticleRoot),
567
+ artifactRootFor: artifactRootResolver(reticleRoot, { servingDirectory: servingDirectoryOf }),
560
568
  now,
561
569
  bridgePort: port,
562
570
  browserProbe: probeLaunchableChromium,
571
+ firstRun: firstRunWiring({ port, cliPath: process.argv[1] ?? '' }),
563
572
  // A finished verification should not sit behind a one-minute timer — see ToolDeps.onRunPersisted.
564
573
  onRunPersisted: () => cloudSync.nudge(),
574
+ syncNow: () => cloudSync.syncNow(),
565
575
  };
566
576
  const profile = resolveToolSurface(options.toolProfile);
567
577
  const effectiveDeps = realInput !== undefined ? { ...deps, realInput } : deps;
568
578
  // Read per attach, not once: a project that gets wired while this daemon is alive should stop
569
579
  // being told to wire itself on the next agent that connects.
570
- shared.attachMcp(() => createMcpServer(effectiveDeps, profile,
571
- // See the sibling call in `start`: a live session outweighs empty/stale durable memory (#1138).
572
- bridge.sessions.count() > 0 ||
573
- hasAnyProjectConnectedBefore(reticleStateHome(), port, projectIdsAt(process.cwd()))));
574
- // `reticle drive <url>` when this daemon already owns the port: it asks HERE instead of trying to
575
- // bind a port we are holding, and gets the same pooled context an agent's reticle_lease returns —
576
- // through runTool, so it is counted and reported like any other call rather than being a second,
577
- // invisible dispatch path. See cli/drive/drive-attach.ts for why attaching beats refereeing the race.
580
+ shared.attachMcp((peerSkew) => createMcpServer(peerSkew === undefined
581
+ ? effectiveDeps
582
+ : { ...effectiveDeps, peerSkew: connectionSkew(peerSkew) }, profile, instructionStateAt(port, bridge.sessions.count())));
583
+ // `reticle drive <url>` against this daemon's port, through runTool. See cli/drive/drive-attach.ts.
578
584
  shared.attachDrive((url) => runTool(LEASE_ACQUIRE_TOOL, effectiveDeps, { url }));
585
+ // The platform chat's apps and drives, run here. See chat-drives.ts.
586
+ const chatDrives = startChatDrives(effectiveDeps, bridge.sessions, fs, {
587
+ sessionCloud: sessionCloudPort(fs, homedir(), process.env, (id) => sessionRoot(effectiveDeps, id)),
588
+ version: SERVER_VERSION,
589
+ }, log);
579
590
  // Optional OEM/CI verify endpoint: a host platform POSTs to /verify and gets an ReticleVerificationRun,
580
591
  // driving the same flow-replay machinery the agent uses — no MCP stdio, no human. Each verdict is
581
592
  // persisted via RunStore. Localhost-bound + token-guarded. Off unless `reticle serve --http`.
@@ -593,16 +604,9 @@ export async function startDaemon(options = {}) {
593
604
  // verdict into the same activity log they watch the agent in. The page animates via the normal
594
605
  // replay path, so they see it re-drive and the ✓/⚠/✗ land.
595
606
  bridge.attachReplay((sessionId, flowName) => {
596
- const session = bridge.sessions.get(sessionId);
597
- if (session === undefined)
607
+ if (bridge.sessions.get(sessionId) === undefined)
598
608
  return;
599
- session.pushNarration(`▶ Replaying "${flowName}"…`);
600
- replayAndLearn(effectiveDeps, { flowName, sessionId })
601
- .then((result) => session.pushNarration(replayVerdictLine(result)))
602
- .catch((error) => {
603
- const message = error instanceof Error ? error.message : String(error);
604
- session.pushNarration(`✗ Replay "${flowName}" failed — ${message}`);
605
- });
609
+ void replayFromHud(effectiveDeps, (id) => bridge.sessions.get(id), sessionId, flowName);
606
610
  });
607
611
  // On connect, hand the panel the replayable flows so it can render the ▶ list. Scoped to the
608
612
  // connecting session's project (a shared daemon serves many apps; each panel shows only its own
@@ -672,6 +676,8 @@ export async function startDaemon(options = {}) {
672
676
  if (vh !== undefined)
673
677
  await new Promise((resolve) => vh.server.close(() => resolve()));
674
678
  leaseReaper.stop();
679
+ chatDrives.stop();
680
+ accountFiles.close();
675
681
  await cloudSync.flush(); // not stop(): the last run written is the one nobody has yet
676
682
  await loopbackAlias.close?.();
677
683
  await pool.shutdown();
@@ -681,12 +687,8 @@ export async function startDaemon(options = {}) {
681
687
  },
682
688
  };
683
689
  }
684
- // The OpenVerification binding, exported so a conformance run can be driven from outside this package.
685
- //
686
- // It was written and then not reachable: `conformanceClient` takes a `WebRealm`, a `WebRealm`
687
- // takes a live `Session`, and a `Session` exists only inside a running daemon. A runner that
688
- // cannot import it cannot score anything, which would have made the whole conformance chain
689
- // complete and unusable.
690
+ // The OpenVerification binding, exported so a conformance run can be driven from outside this
691
+ // package: a `WebRealm` needs a live `Session`, which exists only inside a running daemon.
690
692
  export { WebRealm } from './portal/realm/web-realm.js';
691
693
  export { conformanceClient } from './portal/realm/conformance-client.js';
692
694
  /**
@@ -42,10 +42,10 @@ declare const CapsuleSchema: z.ZodObject<{
42
42
  seen: z.ZodOptional<z.ZodNumber>;
43
43
  lastSeenAt: z.ZodOptional<z.ZodNumber>;
44
44
  }, "strip", z.ZodTypeAny, {
45
- expected: string;
46
- origin: string;
47
45
  version: 1;
48
46
  id: string;
47
+ origin: string;
48
+ expected: string;
49
49
  steps: FlowStep[];
50
50
  createdAt: number;
51
51
  observed: string;
@@ -53,10 +53,10 @@ declare const CapsuleSchema: z.ZodObject<{
53
53
  seen?: number | undefined;
54
54
  lastSeenAt?: number | undefined;
55
55
  }, {
56
- expected: string;
57
- origin: string;
58
56
  version: 1;
59
57
  id: string;
58
+ origin: string;
59
+ expected: string;
60
60
  steps: FlowStep[];
61
61
  createdAt: number;
62
62
  observed: string;
@@ -23,6 +23,7 @@ export function computeVerdict(input) {
23
23
  .length + input.checks.filter((c) => c.status === Verified.YES).length;
24
24
  const fails = input.flows.filter((f) => f.status === RunFlowStatus.FAIL).length +
25
25
  input.checks.filter((c) => c.status === Verified.NO).length;
26
+ const skippedFlows = input.flows.filter((f) => f.status === RunFlowStatus.SKIPPED).length;
26
27
  const blockingRisks = input.risks.filter((r) => r.gated).length;
27
28
  const reasons = [];
28
29
  for (const f of input.flows) {
@@ -31,6 +32,9 @@ export function computeVerdict(input) {
31
32
  ? `flow ${f.name}: ${f.failureReason}`
32
33
  : `flow ${f.name} failed`);
33
34
  }
35
+ else if (f.status === RunFlowStatus.SKIPPED && f.failureReason !== undefined) {
36
+ reasons.push(`flow ${f.name}: ${f.failureReason}`);
37
+ }
34
38
  }
35
39
  for (const c of input.checks) {
36
40
  if (c.status === Verified.NO)
@@ -47,6 +51,8 @@ export function computeVerdict(input) {
47
51
  status = VerdictStatus.PARTIAL;
48
52
  else if (fails > 0)
49
53
  status = VerdictStatus.FAIL;
54
+ else if (skippedFlows > 0 && passes > 0)
55
+ status = VerdictStatus.PARTIAL;
50
56
  else if (0 === passes) {
51
57
  // Nothing passed and nothing failed, so nothing was proved -- an empty run, or one whose every
52
58
  // flow was skipped. Reporting that as PASS is the false green this whole file exists to prevent:
@@ -5,7 +5,7 @@
5
5
  * "why" is lifted from the replay's decision envelope (or its error) into failureReason.
6
6
  */
7
7
  import { ReplayStatus, RunFlowStatus, type FlowFile, type FlowReplayResult, type RunFlowResult } from '@reticlehq/core';
8
- /** OK → PASS; DRIFT/ERROR → FAIL (a healed flow is produced by the heal path, not plain replay). */
8
+ /** OK → PASS; UNVERIFIABLE → SKIPPED; DRIFT/ERROR → FAIL (a healed flow is produced by the heal path, not plain replay). */
9
9
  export declare function runFlowStatusOf(status: ReplayStatus): RunFlowStatus;
10
10
  /** Map one replay (plus its measured duration) into a RunFlowResult for the artifact's flows[]. */
11
11
  export declare function mapReplayToFlowResult(replay: FlowReplayResult, durationMs: number, flow?: FlowFile): RunFlowResult;