@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,19 +1,15 @@
1
1
  /**
2
- * The names of the drivers the harness can be asked for.
2
+ * The Harness's driver names.
3
3
  *
4
- * A leaf module on purpose. These used to be re-exported from `harness-explore.ts`, which sits in
5
- * the tool surface and is reached, through the toolset and the tool table, from the very tool that
6
- * needs to declare them as an enum — a cycle that left the list `[undefined, undefined]` at the
7
- * moment zod read it, and failed inside a JSON-schema parser rather than anywhere near the cause.
8
- * Nothing here imports anything, so nothing can be half-initialised when it is read.
4
+ * A leaf module on purpose: the tool table reads these while the tool surface is still loading, and
5
+ * a module that imports nothing cannot be read half-initialised.
6
+ *
7
+ * There is one driver. Every Harness decision, its models and its planning run on the Reticle
8
+ * platform, on the workspace's monthly Harness credits; this machine executes what it is told.
9
9
  */
10
- /** Generates its tool calls as text, and names the journeys it records. The default. */
11
- export const ANTHROPIC_DRIVER_NAME = 'anthropic';
12
- /** Answers typed questions; picks from the elements on the page and cannot invent one. */
13
- export const JEV_DRIVER_NAME = 'jev';
14
- /** Generates its tool calls, over Chat Completions. Here so an A/B has a third arm. */
15
- export const OPENAI_DRIVER_NAME = 'openai';
16
- /** What a caller may name. Order is the order a reader sees them in the tool description. */
17
- export const DRIVER_NAMES = [ANTHROPIC_DRIVER_NAME, JEV_DRIVER_NAME, OPENAI_DRIVER_NAME];
10
+ /** The platform's Harness: it decides on its side, this machine executes. See server-driver.ts. */
11
+ export const SERVER_DRIVER = 'server';
18
12
  /** An injected driver: not selectable, because only a caller in-process can supply one. */
19
13
  export const CUSTOM_DRIVER_NAME = 'custom';
14
+ /** What a drive needs, said once for every place that tells somebody. */
15
+ export const EXPLORE_NEEDS = 'Runs on the Reticle platform: needs a linked project (`reticle connect`); Free has credits.';
@@ -1,73 +1,7 @@
1
1
  /**
2
- * What to type into a field, and the one place in a drive worth paying a generating model for.
2
+ * What to type into a text field, from its label alone.
3
3
  *
4
- * A System One model answers typed questions against a state; it cannot write a string. That is not
5
- * a shortcoming to route around everywhere — it is why the drive is two orders of magnitude cheaper,
6
- * and every decision it makes (which tool, which element, what should follow) is a decision, not a
7
- * composition. But a text field is a composition. "A card number", "a search term that returns
8
- * results", "a description a human would write" cannot be enumerated from the page, and a label-keyed
9
- * table of guesses is what stands in for them today.
10
- *
11
- * ## Why this is escalation rather than partnership
12
- *
13
- * Put a generating model in the per-turn loop and you pay its per-turn tax on every step, which is
14
- * the cost the cheap driver exists to avoid. Typing is a small minority of what a drive does —
15
- * clicks and navigations are the overwhelming majority — so the model is called for the few steps
16
- * that compose text, never for the many that select, and a drive with no text fields never calls it
17
- * at all.
18
- *
19
- * ## Why it is cached, and cached where it is
20
- *
21
- * A generated value is paid for ONCE and then belongs to the project: the same field on the same
22
- * app wants the same value tomorrow, and a replay must send exactly what the recording sent or it
23
- * is not a replay. Writing them to `.reticle` makes the second drive free, makes the values
24
- * reviewable by a human who can correct a bad one by hand, and keeps them out of the model's way
25
- * entirely. It is the same economics as the flows themselves: pay once, replay forever.
26
- *
27
- * ## What it must never do
28
- *
29
- * Fill a slot, never propose a call. The driver decides what to click and what to claim; this
30
- * answers one question — "what text goes in this box" — and a wrong answer costs one act, not a
31
- * wrong verdict. Nothing here touches what is judged.
32
- */
33
- import { type HarnessDriverOptions } from './driver.js';
34
- /** What the caller must be able to do for values to survive past this drive. */
35
- export interface FillCache {
36
- get(label: string): string | undefined;
37
- set(label: string, value: string): void;
38
- }
39
- /**
40
- * The fallback, and still the answer for most fields.
41
- *
42
- * An exploration drive needs PLAUSIBLE input, not creative input: the engine is judging what the app
43
- * did with the value, not the value. A field that rejects everything here is itself worth finding,
44
- * and shows up as an act that did not settle.
4
+ * Used by the crawl, which fills a field to see what the app does with it and never needs the value
5
+ * to be clever. The Harness fills fields on the platform, where its engine decides them.
45
6
  */
46
7
  export declare function heuristicFillValue(label: string): string;
47
- /**
48
- * Whether a label is worth spending a model call on.
49
- *
50
- * The heuristic is RIGHT about an email box and an amount box, and a generated "john@example.com"
51
- * is not an improvement on `harness@reticle.dev`. What it cannot do is a field whose label it does
52
- * not recognise at all, where it falls back to typing the words "reticle harness" into whatever the
53
- * app asked for. That fallback is the whole of what generation is for.
54
- */
55
- export declare function needsGeneration(label: string): boolean;
56
- /**
57
- * The seam the driver calls: a label in, a value out, always.
58
- *
59
- * Three layers, cheapest first, and the expensive one is skipped entirely unless the two above it
60
- * have nothing better than "type the words reticle harness into this box".
61
- */
62
- export declare function fillValues(deps: {
63
- cache?: FillCache;
64
- generator?: HarnessDriverOptions;
65
- /** One line about where the drive is, so a value suits the page it is typed on. */
66
- context?: () => string;
67
- /**
68
- * A value the user SUPPLIED for this field — the same `RETICLE_SECRET_<FIELD>` replay reads for
69
- * a redacted one. Checked first so a drive gets past a login, and never cached: the cache is a
70
- * file on disk.
71
- */
72
- secret?: (label: string) => string | undefined;
73
- }): (label: string) => Promise<string>;
@@ -1,54 +1,8 @@
1
1
  /**
2
- * What to type into a field, and the one place in a drive worth paying a generating model for.
2
+ * What to type into a text field, from its label alone.
3
3
  *
4
- * A System One model answers typed questions against a state; it cannot write a string. That is not
5
- * a shortcoming to route around everywhere — it is why the drive is two orders of magnitude cheaper,
6
- * and every decision it makes (which tool, which element, what should follow) is a decision, not a
7
- * composition. But a text field is a composition. "A card number", "a search term that returns
8
- * results", "a description a human would write" cannot be enumerated from the page, and a label-keyed
9
- * table of guesses is what stands in for them today.
10
- *
11
- * ## Why this is escalation rather than partnership
12
- *
13
- * Put a generating model in the per-turn loop and you pay its per-turn tax on every step, which is
14
- * the cost the cheap driver exists to avoid. Typing is a small minority of what a drive does —
15
- * clicks and navigations are the overwhelming majority — so the model is called for the few steps
16
- * that compose text, never for the many that select, and a drive with no text fields never calls it
17
- * at all.
18
- *
19
- * ## Why it is cached, and cached where it is
20
- *
21
- * A generated value is paid for ONCE and then belongs to the project: the same field on the same
22
- * app wants the same value tomorrow, and a replay must send exactly what the recording sent or it
23
- * is not a replay. Writing them to `.reticle` makes the second drive free, makes the values
24
- * reviewable by a human who can correct a bad one by hand, and keeps them out of the model's way
25
- * entirely. It is the same economics as the flows themselves: pay once, replay forever.
26
- *
27
- * ## What it must never do
28
- *
29
- * Fill a slot, never propose a call. The driver decides what to click and what to claim; this
30
- * answers one question — "what text goes in this box" — and a wrong answer costs one act, not a
31
- * wrong verdict. Nothing here touches what is judged.
32
- */
33
- import { DEFAULT_HARNESS_BASE_URL, } from './driver.js';
34
- /** Deliberately small. A field value is a phrase; anything longer is the model misunderstanding. */
35
- const MAX_TOKENS = 64;
36
- const ANTHROPIC_VERSION = '2023-06-01';
37
- const MESSAGES_PATH = '/v1/messages';
38
- /**
39
- * How many values one drive will generate.
40
- *
41
- * The cache makes the steady state nearly free, but the FIRST drive on a form-heavy app is the one
42
- * that pays, and an uncapped loop on a page with forty inputs is a bill nobody agreed to. Past the
43
- * cap the heuristic answers, which is what every drive used before this existed.
44
- */
45
- const MAX_PER_DRIVE = 12;
46
- /**
47
- * The fallback, and still the answer for most fields.
48
- *
49
- * An exploration drive needs PLAUSIBLE input, not creative input: the engine is judging what the app
50
- * did with the value, not the value. A field that rejects everything here is itself worth finding,
51
- * and shows up as an act that did not settle.
4
+ * Used by the crawl, which fills a field to see what the app does with it and never needs the value
5
+ * to be clever. The Harness fills fields on the platform, where its engine decides them.
52
6
  */
53
7
  export function heuristicFillValue(label) {
54
8
  const l = label.toLowerCase();
@@ -70,87 +24,3 @@ export function heuristicFillValue(label) {
70
24
  return '42';
71
25
  return 'reticle harness';
72
26
  }
73
- /**
74
- * Whether a label is worth spending a model call on.
75
- *
76
- * The heuristic is RIGHT about an email box and an amount box, and a generated "john@example.com"
77
- * is not an improvement on `harness@reticle.dev`. What it cannot do is a field whose label it does
78
- * not recognise at all, where it falls back to typing the words "reticle harness" into whatever the
79
- * app asked for. That fallback is the whole of what generation is for.
80
- */
81
- export function needsGeneration(label) {
82
- return heuristicFillValue(label) === heuristicFillValue('');
83
- }
84
- const PROMPT = (label, context) => `A test harness is filling in a form field on a web application so it can check what the app does with the input.\n\n` +
85
- `The field is labelled: "${label}"\n` +
86
- `${context}\n\n` +
87
- `Reply with ONLY the value to type. No quotes, no explanation, no markdown. It must be something a ` +
88
- `real user would plausibly enter, and short. If the field wants an identifier or a code, invent a ` +
89
- `well-formed one.`;
90
- /** One string from a generating model, or nothing. Never throws: a drive must not fail over a field. */
91
- async function generateOne(options, label, context) {
92
- const call = options.fetch ?? ((url, init) => fetch(url, init));
93
- const baseUrl = options.baseUrl ?? DEFAULT_HARNESS_BASE_URL;
94
- try {
95
- const res = await call(`${baseUrl}${MESSAGES_PATH}`, {
96
- method: 'POST',
97
- headers: {
98
- 'content-type': 'application/json',
99
- 'x-api-key': options.apiKey,
100
- 'anthropic-version': ANTHROPIC_VERSION,
101
- },
102
- body: JSON.stringify({
103
- model: options.model ?? 'claude-sonnet-5',
104
- max_tokens: MAX_TOKENS,
105
- messages: [{ role: 'user', content: PROMPT(label, context) }],
106
- }),
107
- });
108
- if (!res.ok)
109
- return undefined;
110
- const parsed = JSON.parse(await res.text());
111
- const content = parsed.content ?? [];
112
- const text = content
113
- .filter((block) => 'text' === block.type)
114
- .map((block) => block.text ?? '')
115
- .join('')
116
- .trim()
117
- .split('\n')[0];
118
- if (text === undefined || 0 === text.length)
119
- return undefined;
120
- // A model asked for one value occasionally answers with a sentence about the value. Anything
121
- // long enough to be prose is not a field value, and typing prose proves nothing about the app.
122
- return 80 < text.length ? undefined : text.replace(/^["']|["']$/g, '');
123
- }
124
- catch {
125
- // Offline, refused, rate-limited, malformed. All the same answer: use the heuristic.
126
- return undefined;
127
- }
128
- }
129
- /**
130
- * The seam the driver calls: a label in, a value out, always.
131
- *
132
- * Three layers, cheapest first, and the expensive one is skipped entirely unless the two above it
133
- * have nothing better than "type the words reticle harness into this box".
134
- */
135
- export function fillValues(deps) {
136
- let spent = 0;
137
- return async (label) => {
138
- const supplied = deps.secret?.(label);
139
- if (supplied !== undefined && supplied.length > 0)
140
- return supplied;
141
- const cached = deps.cache?.get(label);
142
- if (cached !== undefined)
143
- return cached;
144
- const fallback = heuristicFillValue(label);
145
- if (deps.generator === undefined || !needsGeneration(label) || MAX_PER_DRIVE <= spent)
146
- return fallback;
147
- spent += 1;
148
- const generated = await generateOne(deps.generator, label, deps.context?.() ?? '');
149
- if (generated === undefined)
150
- return fallback;
151
- // Cached under the LABEL, not the ref: refs expire with the page, labels are what the next drive
152
- // and every replay will meet again.
153
- deps.cache?.set(label, generated);
154
- return generated;
155
- };
156
- }
@@ -0,0 +1,21 @@
1
+ export interface GoalCheck {
2
+ /** The text that had to be on the page. */
3
+ text: string;
4
+ /** The engine's verdict on it: only `yes` is proved. */
5
+ verified: string;
6
+ }
7
+ /** The observable goals in a persona: whatever it quoted. */
8
+ export declare function goalsIn(persona: string | undefined): string[];
9
+ /**
10
+ * Check each goal on the page the drive ended on, recording ONE assert for the drive.
11
+ *
12
+ * Each quote used to be its own assert, and every assert is a check in the drive's run, so a goal
13
+ * that quotes where it starts ("from "Count is 0" to "Count is 1"") recorded a "no" for the start
14
+ * text a working app had rightly replaced, and the run synced as a failing flow. The page is read
15
+ * once to answer each quote; the one recorded assert covers the quotes shown, or all of them when
16
+ * none is, so a goal the page never reached still leaves a failed check behind. A check that cannot
17
+ * run is `unknown`, never skipped.
18
+ */
19
+ export declare function checkGoals(invoke: (name: string, args: Record<string, unknown>) => Promise<unknown>, goals: readonly string[]): Promise<GoalCheck[]>;
20
+ /** One line naming the goals the drive did not prove, or undefined when it proved them all. */
21
+ export declare function unprovedGoals(checks: readonly GoalCheck[]): string | undefined;
@@ -0,0 +1,77 @@
1
+ /**
2
+ * The goals a drive was asked to prove, and whether it did.
3
+ *
4
+ * A drive asked to "check that "Ada Lovelace" and "Grace Hopper" are listed" replayed old journeys,
5
+ * dismissed a reminder and reported `finished` without looking for either name (#1316). The driver's
6
+ * word for it is not evidence, so after every drive the harness checks each goal itself, with the
7
+ * same assert any agent would make, and reports one verdict per goal.
8
+ */
9
+ import { PredicateKind, ReticleTool, Verified, asRecord } from '@reticlehq/core';
10
+ /** More than this and the persona is a document, not a set of goals. */
11
+ const MAX_GOALS = 10;
12
+ const MAX_GOAL_LENGTH = 200;
13
+ /** A string the persona quoted: "straight" or “curly”. Single quotes are apostrophes too often. */
14
+ const QUOTED = /"([^"\n]{1,200})"|“([^”\n]{1,200})”/g;
15
+ /** The observable goals in a persona: whatever it quoted. */
16
+ export function goalsIn(persona) {
17
+ if (persona === undefined)
18
+ return [];
19
+ const found = [...persona.matchAll(QUOTED)].map((m) => (m[1] ?? m[2] ?? '').trim());
20
+ return [...new Set(found.filter((g) => 0 < g.length))].slice(0, MAX_GOALS);
21
+ }
22
+ /**
23
+ * Check each goal on the page the drive ended on, recording ONE assert for the drive.
24
+ *
25
+ * Each quote used to be its own assert, and every assert is a check in the drive's run, so a goal
26
+ * that quotes where it starts ("from "Count is 0" to "Count is 1"") recorded a "no" for the start
27
+ * text a working app had rightly replaced, and the run synced as a failing flow. The page is read
28
+ * once to answer each quote; the one recorded assert covers the quotes shown, or all of them when
29
+ * none is, so a goal the page never reached still leaves a failed check behind. A check that cannot
30
+ * run is `unknown`, never skipped.
31
+ */
32
+ export async function checkGoals(invoke, goals) {
33
+ const texts = [
34
+ ...new Set(goals.slice(0, MAX_GOALS).map((g) => g.trim().slice(0, MAX_GOAL_LENGTH))),
35
+ ].filter((t) => 0 < t.length);
36
+ if (0 === texts.length)
37
+ return [];
38
+ let page;
39
+ try {
40
+ const tree = asRecord(await invoke(ReticleTool.SNAPSHOT, { mode: SNAPSHOT_FULL }))['tree'];
41
+ if ('string' === typeof tree)
42
+ page = tree;
43
+ }
44
+ catch {
45
+ /* unread: every quote rests on the assert below */
46
+ }
47
+ const shown = texts.filter((t) => true === page?.includes(t));
48
+ const recorded = 0 < shown.length ? shown : texts;
49
+ let verified = Verified.UNKNOWN;
50
+ try {
51
+ const result = asRecord(await invoke(ReticleTool.ASSERT, { predicate: allOf(recorded) }));
52
+ if ('string' === typeof result['verified'])
53
+ verified = result['verified'];
54
+ }
55
+ catch {
56
+ /* stays unknown: the goal was not proved */
57
+ }
58
+ // A quote the page read did not show is a "no" only beside one it did: with none shown, every
59
+ // quote rests on the recorded assert.
60
+ return texts.map((text) => ({
61
+ text,
62
+ verified: 0 === shown.length || shown.includes(text) ? verified : Verified.NO,
63
+ }));
64
+ }
65
+ const SNAPSHOT_FULL = 'full';
66
+ function allOf(texts) {
67
+ const each = texts.map((contains) => ({ kind: PredicateKind.TEXT, contains }));
68
+ return 1 === each.length ? (each[0] ?? {}) : { kind: PredicateKind.ALL_OF, predicates: each };
69
+ }
70
+ /** One line naming the goals the drive did not prove, or undefined when it proved them all. */
71
+ export function unprovedGoals(checks) {
72
+ const missed = checks.filter((c) => Verified.YES !== c.verified);
73
+ if (0 === missed.length)
74
+ return undefined;
75
+ return (`Not proved: ${missed.map((c) => `"${c.text}" (${c.verified})`).join(', ')}. ` +
76
+ 'The drive did not finish the job it was given, whatever its own account says.');
77
+ }
@@ -107,7 +107,12 @@ export declare const StopReason: {
107
107
  readonly STALLED: "stalled";
108
108
  /** The toolset or the model itself failed in a way the loop cannot continue through. */
109
109
  readonly BROKEN: "broken";
110
+ /** A person switched autonomous driving off while it was driving. Not a failure. */
111
+ readonly STOPPED: "stopped";
110
112
  };
113
+ /** Thrown by a driver when a person, not a fault, ended the drive. */
114
+ export declare class DriveStoppedError extends Error {
115
+ }
111
116
  export type StopReason = (typeof StopReason)[keyof typeof StopReason];
112
117
  export interface HarnessResult {
113
118
  stopReason: StopReason;
@@ -118,7 +123,23 @@ export interface HarnessResult {
118
123
  usage: TokenUsage;
119
124
  /** Set when `stopReason` is BROKEN. */
120
125
  error?: string;
126
+ /**
127
+ * Whether the drive ran at least one check: an `act_and_wait` with an `until`, or an `assert`,
128
+ * that did not error. A drive without one clicked things and proved nothing.
129
+ */
130
+ proved: boolean;
131
+ /**
132
+ * Whether the drive reached its whole goal, as the platform judged when it finished. Checks that
133
+ * held are not the goal reached: "open each section" passed on two presses of Sign in.
134
+ */
135
+ goalMet?: boolean;
121
136
  }
137
+ /**
138
+ * The answer to a `finish` that came before anything was proved. Sent once per drive: a real drive
139
+ * asked to "click twice and check it reads 2" clicked once, finished, and saved a flow whose replay
140
+ * verifies nothing, which is the paid feature producing an empty pass.
141
+ */
142
+ export declare const MSG_FINISH_UNPROVED: string;
122
143
  /**
123
144
  * The one tool the harness adds to Reticle's surface.
124
145
  *
@@ -17,6 +17,7 @@
17
17
  * 3. **Everything is injected** — the model, the tools, the clock. The loop below has no network in
18
18
  * it, which is what lets the whole thing be tested against a scripted model with no API key.
19
19
  */
20
+ import { ReticleTool, asRecord } from '@reticlehq/core';
20
21
  import { withTimeout } from './with-timeout.js';
21
22
  /** Why a run stopped. Every one of these is a real outcome; none of them is an error. */
22
23
  export const StopReason = {
@@ -28,7 +29,33 @@ export const StopReason = {
28
29
  STALLED: 'stalled',
29
30
  /** The toolset or the model itself failed in a way the loop cannot continue through. */
30
31
  BROKEN: 'broken',
32
+ /** A person switched autonomous driving off while it was driving. Not a failure. */
33
+ STOPPED: 'stopped',
31
34
  };
35
+ /** Thrown by a driver when a person, not a fault, ended the drive. */
36
+ export class DriveStoppedError extends Error {
37
+ }
38
+ /** The finish's judgement of the goal, when it carried one. */
39
+ function goalMetOf(args) {
40
+ const met = args['goalMet'];
41
+ return 'boolean' === typeof met ? { goalMet: met } : {};
42
+ }
43
+ /**
44
+ * The answer to a `finish` that came before anything was proved. Sent once per drive: a real drive
45
+ * asked to "click twice and check it reads 2" clicked once, finished, and saved a flow whose replay
46
+ * verifies nothing, which is the paid feature producing an empty pass.
47
+ */
48
+ export const MSG_FINISH_UNPROVED = `Not finished: nothing has been proved yet. Before finishing, end the journey with ` +
49
+ `${ReticleTool.ACT_AND_WAIT} and an \`until\` naming the end state it must reach, or ` +
50
+ `${ReticleTool.ASSERT} on it. A drive that proves nothing replays as "verified nothing".`;
51
+ /** A check that ran: the only thing that turns a drive into evidence. */
52
+ function isProof(outcome) {
53
+ if (outcome.isError)
54
+ return false;
55
+ if (ReticleTool.ASSERT === outcome.name)
56
+ return true;
57
+ return ReticleTool.ACT_AND_WAIT === outcome.name && asRecord(outcome.args)['until'] !== undefined;
58
+ }
32
59
  /**
33
60
  * The one tool the harness adds to Reticle's surface.
34
61
  *
@@ -126,6 +153,8 @@ export async function runHarness(driver, toolset, options = {}) {
126
153
  const history = [{ role: 'user', text: OPENING }];
127
154
  const toolCalls = [];
128
155
  const usage = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
156
+ const proved = () => toolCalls.some(isProof);
157
+ let askedToProve = false;
129
158
  for (let step = 0; step < maxSteps; step += 1) {
130
159
  let turn;
131
160
  try {
@@ -133,11 +162,12 @@ export async function runHarness(driver, toolset, options = {}) {
133
162
  }
134
163
  catch (error) {
135
164
  return {
136
- stopReason: StopReason.BROKEN,
165
+ stopReason: error instanceof DriveStoppedError ? StopReason.STOPPED : StopReason.BROKEN,
137
166
  summary: '',
138
167
  steps: step,
139
168
  toolCalls,
140
169
  usage,
170
+ proved: proved(),
141
171
  error: error instanceof Error ? error.message : String(error),
142
172
  };
143
173
  }
@@ -156,10 +186,38 @@ export async function runHarness(driver, toolset, options = {}) {
156
186
  steps: step + 1,
157
187
  toolCalls,
158
188
  usage,
189
+ proved: proved(),
159
190
  };
160
191
  }
161
192
  history.push({ role: 'assistant', text: turn.text, calls: turn.calls });
162
193
  const finish = turn.calls.find((call) => FINISH_TOOL.name === call.name);
194
+ if (finish !== undefined && !proved() && !askedToProve) {
195
+ // Whatever else the turn asked for runs first: a check sent alongside `finish` is proof.
196
+ const others = await Promise.all(turn.calls.filter((c) => c !== finish).map((c) => invokeOne(toolset, c)));
197
+ toolCalls.push(...others);
198
+ if (proved()) {
199
+ const summary = finish.args['summary'];
200
+ return {
201
+ stopReason: StopReason.FINISHED,
202
+ summary: 'string' === typeof summary ? summary : turn.text,
203
+ steps: step + 1,
204
+ toolCalls,
205
+ usage,
206
+ proved: true,
207
+ ...goalMetOf(finish.args),
208
+ };
209
+ }
210
+ askedToProve = true;
211
+ const refused = {
212
+ id: finish.id,
213
+ name: finish.name,
214
+ args: finish.args,
215
+ result: { error: MSG_FINISH_UNPROVED },
216
+ isError: true,
217
+ };
218
+ history.push({ role: 'tool', outcomes: [...others, refused] });
219
+ continue;
220
+ }
163
221
  if (finish !== undefined) {
164
222
  const summary = finish.args['summary'];
165
223
  return {
@@ -168,6 +226,8 @@ export async function runHarness(driver, toolset, options = {}) {
168
226
  steps: step + 1,
169
227
  toolCalls,
170
228
  usage,
229
+ proved: proved(),
230
+ ...goalMetOf(finish.args),
171
231
  };
172
232
  }
173
233
  // Every call the model asked for is answered, including ones that fail. A dropped result leaves
@@ -177,7 +237,14 @@ export async function runHarness(driver, toolset, options = {}) {
177
237
  toolCalls.push(...outcomes);
178
238
  history.push({ role: 'tool', outcomes });
179
239
  }
180
- return { stopReason: StopReason.BUDGET, summary: '', steps: maxSteps, toolCalls, usage };
240
+ return {
241
+ stopReason: StopReason.BUDGET,
242
+ summary: '',
243
+ steps: maxSteps,
244
+ toolCalls,
245
+ usage,
246
+ proved: proved(),
247
+ };
181
248
  }
182
249
  /**
183
250
  * One tool call, with its failure captured rather than thrown.
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Make ready the tab a platform drive runs in, as its spec asks, and say what was and was not done.
3
+ *
4
+ * The person's own tab, or one opened for the drive: headless, or in a window they can watch. Then
5
+ * the HUD as asked. Every step that cannot be done is reported, never thrown: a drive asked for a
6
+ * window on a machine with no display still runs headless if that is all there is, and says so.
7
+ */
8
+ import { HudVisibility, type DriveApplied, type DriveSpec, type SpecIgnored } from '@reticlehq/core';
9
+ /** How many times the HUD is asked for before giving up: a page's HUD mounts after it connects. */
10
+ export declare const HUD_ATTEMPTS = 4;
11
+ export declare const HUD_RETRY_MS = 500;
12
+ /** What a refused drive is told when no address was given to open. */
13
+ export declare const NO_ADDRESS_TO_OPEN = "Nothing says where this app runs, so no browser could be opened for it. Open the app once with Reticle running, or give the drive an address.";
14
+ export interface DriveTargetPorts {
15
+ /** The person's own tab for the drive, as the chat already picks it. Null: none may be driven. */
16
+ pickTab: () => Promise<string | null | undefined>;
17
+ /** Open `url` in a browser of the daemon's own; answers the tab's session id. Throws why not. */
18
+ open: (url: string, headed: boolean, hud: HudVisibility | undefined) => Promise<string>;
19
+ /** Ask the page for this HUD; throws when the page did not take it (no HUD yet, an older SDK). */
20
+ tuneHud: (sessionId: string, hud: HudVisibility) => Promise<void>;
21
+ sleep: (ms: number) => Promise<void>;
22
+ }
23
+ export interface PreparedDrive {
24
+ sessionId: string | null | undefined;
25
+ applied: DriveApplied;
26
+ ignored: SpecIgnored[];
27
+ /** Why the drive cannot run, in a person's words, when it cannot. */
28
+ refusal?: string;
29
+ }
30
+ export declare function prepareDrive(spec: DriveSpec, fallbackUrl: string | undefined, ports: DriveTargetPorts): Promise<PreparedDrive>;
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Make ready the tab a platform drive runs in, as its spec asks, and say what was and was not done.
3
+ *
4
+ * The person's own tab, or one opened for the drive: headless, or in a window they can watch. Then
5
+ * the HUD as asked. Every step that cannot be done is reported, never thrown: a drive asked for a
6
+ * window on a machine with no display still runs headless if that is all there is, and says so.
7
+ */
8
+ import { DriveMode, DriveTarget, HudVisibility, SpecIgnoredReason, } from '@reticlehq/core';
9
+ /** How many times the HUD is asked for before giving up: a page's HUD mounts after it connects. */
10
+ export const HUD_ATTEMPTS = 4;
11
+ export const HUD_RETRY_MS = 500;
12
+ /** What a refused drive is told when no address was given to open. */
13
+ export const NO_ADDRESS_TO_OPEN = 'Nothing says where this app runs, so no browser could be opened for it. Open the app once with Reticle running, or give the drive an address.';
14
+ export async function prepareDrive(spec, fallbackUrl, ports) {
15
+ const mode = spec.mode ?? DriveMode.LOCAL;
16
+ const ignored = [];
17
+ let target = spec.target ?? DriveTarget.TAB;
18
+ let sessionId;
19
+ let url;
20
+ if (DriveTarget.TAB !== target) {
21
+ url = spec.url ?? fallbackUrl;
22
+ if (url === undefined)
23
+ return {
24
+ sessionId: null,
25
+ applied: { target, mode },
26
+ ignored,
27
+ refusal: NO_ADDRESS_TO_OPEN,
28
+ };
29
+ try {
30
+ sessionId = await ports.open(url, DriveTarget.HEADED === target, spec.hud);
31
+ }
32
+ catch (error) {
33
+ const detail = error instanceof Error ? error.message : String(error);
34
+ // A window that cannot open falls back to a browser with none: the drive still runs.
35
+ if (DriveTarget.HEADED !== target)
36
+ return { sessionId: null, applied: { target, mode, url }, ignored, refusal: detail };
37
+ ignored.push({ field: 'target', reason: SpecIgnoredReason.UNAVAILABLE, detail });
38
+ target = DriveTarget.HEADLESS;
39
+ try {
40
+ sessionId = await ports.open(url, false, spec.hud);
41
+ }
42
+ catch (again) {
43
+ const why = again instanceof Error ? again.message : String(again);
44
+ return { sessionId: null, applied: { target, mode, url }, ignored, refusal: why };
45
+ }
46
+ }
47
+ }
48
+ else {
49
+ sessionId = await ports.pickTab();
50
+ }
51
+ const applied = {
52
+ target,
53
+ mode,
54
+ ...(url === undefined ? {} : { url }),
55
+ ...('string' === typeof sessionId ? { sessionId } : {}),
56
+ };
57
+ if (spec.hud !== undefined && 'string' === typeof sessionId) {
58
+ const taken = await tune(ports, sessionId, spec.hud);
59
+ if (taken === undefined)
60
+ applied.hud = spec.hud;
61
+ else
62
+ ignored.push({ field: 'hud', reason: SpecIgnoredReason.UNAVAILABLE, detail: taken });
63
+ }
64
+ return { sessionId, applied, ignored };
65
+ }
66
+ /** Ask for the HUD until the page takes it; answers why not when it never does. */
67
+ async function tune(ports, sessionId, hud) {
68
+ let why = 'the page did not take it';
69
+ for (let attempt = 1; attempt <= HUD_ATTEMPTS; attempt += 1) {
70
+ try {
71
+ await ports.tuneHud(sessionId, hud);
72
+ return undefined;
73
+ }
74
+ catch (error) {
75
+ why = error instanceof Error ? error.message : String(error);
76
+ if (attempt < HUD_ATTEMPTS)
77
+ await ports.sleep(HUD_RETRY_MS);
78
+ }
79
+ }
80
+ return why;
81
+ }