@reticlehq/server 3.5.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 (328) 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/cli-flow-commands.js +4 -8
  10. package/dist/command/cli/cli-parse.d.ts +1 -1
  11. package/dist/command/cli/cli-parse.js +10 -10
  12. package/dist/command/cli/cli-verify.d.ts +1 -1
  13. package/dist/command/cli/cli-verify.js +35 -25
  14. package/dist/command/cli/cloud-cli.js +51 -12
  15. package/dist/command/cli/cloud-login.d.ts +8 -0
  16. package/dist/command/cli/cloud-login.js +36 -2
  17. package/dist/command/cli/demo/demo-page.js +11 -3
  18. package/dist/command/cli/doctor/browser/chromium-hint.d.ts +46 -0
  19. package/dist/command/cli/doctor/browser/chromium-hint.js +99 -1
  20. package/dist/command/cli/drive/drive-attach.d.ts +27 -2
  21. package/dist/command/cli/drive/drive-attach.js +71 -4
  22. package/dist/command/cli/drive/drive-command.d.ts +2 -1
  23. package/dist/command/cli/drive/drive-command.js +13 -7
  24. package/dist/command/cli/lifecycle/daemon-lifecycle.js +4 -1
  25. package/dist/command/cli/ports/port-holder.d.ts +10 -0
  26. package/dist/command/cli/ports/port-holder.js +24 -0
  27. package/dist/command/cli/setup-mcp-cli.js +6 -4
  28. package/dist/command/cli/tutorial.d.ts +2 -2
  29. package/dist/command/cli/tutorial.js +6 -9
  30. package/dist/command/cli.d.ts +6 -0
  31. package/dist/command/cli.js +28 -22
  32. package/dist/command/daemon/binding/daemon-status-probe.d.ts +2 -0
  33. package/dist/command/daemon/binding/daemon-status-probe.js +1 -0
  34. package/dist/command/daemon/daemon-resolve.d.ts +14 -0
  35. package/dist/command/daemon/daemon-resolve.js +33 -2
  36. package/dist/command/setup/agent-writer.d.ts +1 -1
  37. package/dist/command/setup/agent-writer.js +3 -2
  38. package/dist/command/setup/bringup/connect-lease.d.ts +1 -0
  39. package/dist/command/setup/bringup/connect-lease.js +1 -0
  40. package/dist/command/setup/bringup/dev-server-wait.d.ts +8 -0
  41. package/dist/command/setup/bringup/dev-server-wait.js +19 -0
  42. package/dist/command/setup/init/init-port.d.ts +4 -0
  43. package/dist/command/setup/init/init-port.js +10 -0
  44. package/dist/command/setup/init/init-runtime.d.ts +10 -0
  45. package/dist/command/setup/init/init-runtime.js +18 -1
  46. package/dist/command/setup/node-effects.js +2 -1
  47. package/dist/command/setup/remaining-steps.js +2 -1
  48. package/dist/command/setup/run-setup.d.ts +5 -0
  49. package/dist/command/setup/run-setup.js +35 -27
  50. package/dist/command/setup/setup-command.d.ts +2 -1
  51. package/dist/command/setup/setup-command.js +2 -0
  52. package/dist/command/version/version-skew.js +11 -2
  53. package/dist/features/crawl/crawl-tools.js +4 -0
  54. package/dist/features/crawl/crawl.d.ts +6 -0
  55. package/dist/features/crawl/crawl.js +9 -1
  56. package/dist/features/exhaust/ledger.d.ts +7 -0
  57. package/dist/features/exhaust/ledger.js +35 -9
  58. package/dist/features/harness/app-findings.d.ts +2 -0
  59. package/dist/features/harness/app-findings.js +52 -0
  60. package/dist/features/harness/drive-report.d.ts +27 -1
  61. package/dist/features/harness/drive-report.js +162 -11
  62. package/dist/features/harness/drivers.d.ts +10 -14
  63. package/dist/features/harness/drivers.js +10 -14
  64. package/dist/features/harness/fill-values.d.ts +3 -73
  65. package/dist/features/harness/fill-values.js +3 -169
  66. package/dist/features/harness/goals.d.ts +21 -0
  67. package/dist/features/harness/goals.js +77 -0
  68. package/dist/features/harness/harness.d.ts +21 -0
  69. package/dist/features/harness/harness.js +69 -2
  70. package/dist/features/harness/platform/drive-target.d.ts +30 -0
  71. package/dist/features/harness/platform/drive-target.js +81 -0
  72. package/dist/features/harness/platform/local-apps.d.ts +94 -0
  73. package/dist/features/harness/platform/local-apps.js +204 -0
  74. package/dist/features/harness/platform/remote-drive.d.ts +180 -0
  75. package/dist/features/harness/platform/remote-drive.js +365 -0
  76. package/dist/features/harness/platform/script.d.ts +48 -0
  77. package/dist/features/harness/platform/script.js +128 -0
  78. package/dist/features/harness/platform/server-driver.d.ts +41 -0
  79. package/dist/features/harness/platform/server-driver.js +179 -0
  80. package/dist/features/harness/platform-config.d.ts +7 -0
  81. package/dist/features/harness/platform-config.js +14 -2
  82. package/dist/features/harness/script/app-controls.d.ts +5 -0
  83. package/dist/features/harness/script/app-controls.js +40 -0
  84. package/dist/features/harness/script/lane-ids.d.ts +14 -0
  85. package/dist/features/harness/script/lane-ids.js +16 -0
  86. package/dist/features/harness/script/script-run.d.ts +41 -0
  87. package/dist/features/harness/script/script-run.js +299 -0
  88. package/dist/features/license/license.d.ts +2 -2
  89. package/dist/features/visual/visual-tools.d.ts +10 -1
  90. package/dist/features/visual/visual-tools.js +40 -3
  91. package/dist/hooks/driven-by.d.ts +4 -0
  92. package/dist/hooks/driven-by.js +16 -0
  93. package/dist/hooks/hook-commands.js +15 -4
  94. package/dist/hooks/tool-log.d.ts +9 -0
  95. package/dist/hooks/tool-log.js +29 -0
  96. package/dist/index.d.ts +1 -0
  97. package/dist/index.js +76 -82
  98. package/dist/judgement/runs/artifact/build-verification-run.js +6 -0
  99. package/dist/judgement/runs/artifact/replay-mapping.d.ts +1 -1
  100. package/dist/judgement/runs/artifact/replay-mapping.js +16 -6
  101. package/dist/judgement/runs/artifact/run-store.d.ts +1 -1
  102. package/dist/judgement/runs/artifact/run-store.js +26 -1
  103. package/dist/judgement/runs/drive-run.d.ts +22 -0
  104. package/dist/judgement/runs/drive-run.js +99 -1
  105. package/dist/judgement/runs/repair-prompt.js +1 -1
  106. package/dist/judgement/runs/reticle-runner.js +8 -1
  107. package/dist/judgement/runs/verification-sync.d.ts +3 -2
  108. package/dist/judgement/runs/verification-sync.js +4 -3
  109. package/dist/language/flows/change/gate.d.ts +10 -0
  110. package/dist/language/flows/change/gate.js +22 -0
  111. package/dist/language/flows/decision.js +34 -0
  112. package/dist/language/flows/flow-anchor.d.ts +54 -8
  113. package/dist/language/flows/flow-anchor.js +88 -3
  114. package/dist/language/flows/flow-cross-step.d.ts +15 -0
  115. package/dist/language/flows/flow-cross-step.js +21 -0
  116. package/dist/language/flows/flow-learning.js +8 -11
  117. package/dist/language/flows/flow-mutate-tools.js +5 -3
  118. package/dist/language/flows/flow-replay-run.d.ts +23 -9
  119. package/dist/language/flows/flow-replay-run.js +101 -94
  120. package/dist/language/flows/flow-replay.d.ts +23 -1
  121. package/dist/language/flows/flow-replay.js +92 -19
  122. package/dist/language/flows/flow-scope.js +5 -1
  123. package/dist/language/flows/flow-step-runners.d.ts +20 -2
  124. package/dist/language/flows/flow-step-runners.js +236 -42
  125. package/dist/language/flows/flow-store-for-session.d.ts +2 -2
  126. package/dist/language/flows/flow-store-for-session.js +2 -2
  127. package/dist/language/flows/flow-tools.js +58 -34
  128. package/dist/language/flows/flows.js +5 -2
  129. package/dist/language/flows/heal-run.js +6 -4
  130. package/dist/language/flows/recording/flake.d.ts +2 -2
  131. package/dist/language/flows/recording/tape/recordings.d.ts +30 -4
  132. package/dist/language/flows/recording/tape/recordings.js +40 -8
  133. package/dist/language/flows/replay-from-hud.d.ts +20 -0
  134. package/dist/language/flows/replay-from-hud.js +51 -0
  135. package/dist/language/flows/replay.d.ts +9 -5
  136. package/dist/language/flows/replay.js +27 -9
  137. package/dist/language/flows/suite/suite-flakes.js +2 -0
  138. package/dist/language/flows/suite-selection.d.ts +2 -15
  139. package/dist/language/flows/suite-selection.js +4 -3
  140. package/dist/language/flows/verify-change-tools.js +8 -4
  141. package/dist/launch-chromium.d.ts +27 -5
  142. package/dist/launch-chromium.js +38 -8
  143. package/dist/memory/cloud/cloud-config.d.ts +12 -0
  144. package/dist/memory/cloud/cloud-config.js +15 -0
  145. package/dist/memory/cloud/cloud-sync.d.ts +17 -14
  146. package/dist/memory/cloud/cloud-sync.js +12 -1
  147. package/dist/memory/cloud/harness-config.d.ts +26 -1
  148. package/dist/memory/cloud/harness-config.js +99 -4
  149. package/dist/memory/cloud/hud-notices-source.d.ts +21 -0
  150. package/dist/memory/cloud/hud-notices-source.js +128 -0
  151. package/dist/memory/cloud/memory-scope.d.ts +144 -0
  152. package/dist/memory/cloud/memory-scope.js +205 -0
  153. package/dist/memory/cloud/sync-cycle.d.ts +12 -3
  154. package/dist/memory/cloud/sync-cycle.js +16 -3
  155. package/dist/memory/cloud/sync-daemon.js +7 -5
  156. package/dist/memory/cloud/sync-disk.d.ts +1 -1
  157. package/dist/memory/cloud/sync-disk.js +5 -8
  158. package/dist/memory/impact/account-watch.d.ts +19 -0
  159. package/dist/memory/impact/account-watch.js +34 -0
  160. package/dist/memory/impact/impact-recorder.d.ts +8 -0
  161. package/dist/memory/impact/impact-recorder.js +12 -2
  162. package/dist/memory/impact/impact-store.d.ts +16 -0
  163. package/dist/memory/impact/impact-store.js +142 -26
  164. package/dist/memory/intent/intent-shard.d.ts +2 -2
  165. package/dist/memory/intent/intent-tools.js +80 -2
  166. package/dist/memory/journal/envelope.d.ts +8 -8
  167. package/dist/memory/journal/journal-recorder.js +7 -1
  168. package/dist/memory/journal/on-disk/retention.d.ts +3 -0
  169. package/dist/memory/journal/on-disk/retention.js +5 -0
  170. package/dist/memory/journal/on-disk/startup-maintenance.js +3 -1
  171. package/dist/memory/journal/on-disk/workspace-tiers.js +7 -4
  172. package/dist/memory/journal/session-end.js +6 -4
  173. package/dist/memory/project/artifact-root-resolver.d.ts +26 -1
  174. package/dist/memory/project/artifact-root-resolver.js +94 -13
  175. package/dist/memory/project/artifact-root.d.ts +42 -5
  176. package/dist/memory/project/artifact-root.js +87 -8
  177. package/dist/memory/project/dir/reticle-dir.d.ts +4 -2
  178. package/dist/memory/project/dir/reticle-dir.js +3 -1
  179. package/dist/memory/project/project-tools.js +22 -3
  180. package/dist/memory/project/session-root.d.ts +24 -1
  181. package/dist/memory/project/session-root.js +41 -5
  182. package/dist/memory/project/sync-status.d.ts +39 -0
  183. package/dist/memory/project/sync-status.js +90 -0
  184. package/dist/memory/recall/memory-tools.js +2 -0
  185. package/dist/memory/recall/project-memory.d.ts +19 -0
  186. package/dist/memory/recall/project-memory.js +45 -6
  187. package/dist/portal/bridge/bridge.d.ts +3 -1
  188. package/dist/portal/bridge/bridge.js +19 -2
  189. package/dist/portal/bridge/pairing-token.d.ts +9 -0
  190. package/dist/portal/bridge/pairing-token.js +17 -0
  191. package/dist/portal/input/real-input.d.ts +81 -10
  192. package/dist/portal/input/real-input.js +207 -22
  193. package/dist/portal/input/viewport-tools.js +3 -3
  194. package/dist/portal/pool/browser-pool.d.ts +22 -9
  195. package/dist/portal/pool/browser-pool.js +70 -35
  196. package/dist/portal/pool/playwright-launcher.d.ts +13 -0
  197. package/dist/portal/pool/playwright-launcher.js +17 -1
  198. package/dist/portal/pool/pool-contract.d.ts +7 -4
  199. package/dist/portal/session/command-payload.d.ts +8 -0
  200. package/dist/portal/session/command-payload.js +19 -0
  201. package/dist/portal/session/dev-server/dev-command.d.ts +12 -0
  202. package/dist/portal/session/dev-server/dev-command.js +61 -12
  203. package/dist/portal/session/facts/departure-note.d.ts +22 -0
  204. package/dist/portal/session/facts/departure-note.js +47 -0
  205. package/dist/portal/session/facts/session-replaced.d.ts +5 -0
  206. package/dist/portal/session/facts/session-replaced.js +5 -0
  207. package/dist/portal/session/first-run-wiring.d.ts +42 -0
  208. package/dist/portal/session/first-run-wiring.js +132 -0
  209. package/dist/portal/session/human/notes-ledger.d.ts +20 -0
  210. package/dist/portal/session/human/notes-ledger.js +55 -0
  211. package/dist/portal/session/human/review-store.d.ts +12 -0
  212. package/dist/portal/session/human/review-store.js +18 -10
  213. package/dist/portal/session/live-control-tools.d.ts +0 -13
  214. package/dist/portal/session/live-control-tools.js +28 -3
  215. package/dist/portal/session/no-session-diagnosis.d.ts +7 -0
  216. package/dist/portal/session/no-session-diagnosis.js +15 -0
  217. package/dist/portal/session/no-session-next-action.d.ts +11 -0
  218. package/dist/portal/session/no-session-next-action.js +31 -5
  219. package/dist/portal/session/no-session-watch.d.ts +14 -1
  220. package/dist/portal/session/no-session-watch.js +63 -10
  221. package/dist/portal/session/presence/session-mirror.js +3 -0
  222. package/dist/portal/session/serving-directory.d.ts +27 -0
  223. package/dist/portal/session/serving-directory.js +45 -0
  224. package/dist/portal/session/session-health.d.ts +7 -0
  225. package/dist/portal/session/session-health.js +6 -0
  226. package/dist/portal/session/session-info.d.ts +8 -1
  227. package/dist/portal/session/session-info.js +6 -0
  228. package/dist/portal/session/session-manager.d.ts +21 -0
  229. package/dist/portal/session/session-manager.js +44 -8
  230. package/dist/portal/session/session-reaper.js +4 -1
  231. package/dist/portal/session/session-successor.d.ts +5 -0
  232. package/dist/portal/session/session-tools.js +26 -3
  233. package/dist/portal/session/session.d.ts +7 -2
  234. package/dist/portal/session/session.js +21 -18
  235. package/dist/start-options.d.ts +6 -0
  236. package/dist/status-payload.d.ts +10 -0
  237. package/dist/status-payload.js +23 -0
  238. package/dist/surface/http-server.js +11 -1
  239. package/dist/surface/mcp/mcp-proxy.d.ts +5 -0
  240. package/dist/surface/mcp/mcp-proxy.js +36 -5
  241. package/dist/surface/mcp/mcp.d.ts +11 -6
  242. package/dist/surface/mcp/mcp.js +27 -13
  243. package/dist/surface/mcp/server-instructions.d.ts +4 -2
  244. package/dist/surface/mcp/server-instructions.js +14 -4
  245. package/dist/surface/mcp/shared-params.js +2 -2
  246. package/dist/surface/tools/act/navigation/navigate-arrival.d.ts +8 -0
  247. package/dist/surface/tools/act/navigation/navigate-arrival.js +56 -2
  248. package/dist/surface/tools/act/settle-in-flight.js +25 -3
  249. package/dist/surface/tools/act-sequence-tool.js +2 -2
  250. package/dist/surface/tools/act-tools.js +23 -7
  251. package/dist/surface/tools/assert/assert-verdict.d.ts +1 -1
  252. package/dist/surface/tools/assert/assert-verdict.js +17 -2
  253. package/dist/surface/tools/browser-tools.js +8 -1
  254. package/dist/surface/tools/chat-drives.d.ts +26 -0
  255. package/dist/surface/tools/chat-drives.js +165 -0
  256. package/dist/surface/tools/empty-session-list.d.ts +41 -0
  257. package/dist/surface/tools/empty-session-list.js +80 -0
  258. package/dist/surface/tools/error-recovery.d.ts +6 -0
  259. package/dist/surface/tools/error-recovery.js +37 -4
  260. package/dist/surface/tools/explore-tools.d.ts +10 -1
  261. package/dist/surface/tools/explore-tools.js +82 -13
  262. package/dist/surface/tools/feedback-tools.js +14 -0
  263. package/dist/surface/tools/harness-explore.d.ts +96 -14
  264. package/dist/surface/tools/harness-explore.js +227 -206
  265. package/dist/surface/tools/harness-plan.d.ts +12 -0
  266. package/dist/surface/tools/harness-plan.js +31 -0
  267. package/dist/surface/tools/harness-script.d.ts +8 -0
  268. package/dist/surface/tools/harness-script.js +342 -0
  269. package/dist/surface/tools/harness-toolset.d.ts +14 -1
  270. package/dist/surface/tools/harness-toolset.js +77 -6
  271. package/dist/surface/tools/invoke-tool.js +52 -1
  272. package/dist/surface/tools/lease-seed.d.ts +29 -0
  273. package/dist/surface/tools/lease-seed.js +123 -0
  274. package/dist/surface/tools/lease-tools.d.ts +3 -23
  275. package/dist/surface/tools/lease-tools.js +30 -131
  276. package/dist/surface/tools/live-call-text.js +2 -2
  277. package/dist/surface/tools/next-step.d.ts +19 -0
  278. package/dist/surface/tools/next-step.js +159 -0
  279. package/dist/surface/tools/observe-tools.js +2 -4
  280. package/dist/surface/tools/platform-moment.d.ts +12 -0
  281. package/dist/surface/tools/platform-moment.js +168 -0
  282. package/dist/surface/tools/query-hint-schema.d.ts +7 -0
  283. package/dist/surface/tools/query-hint-schema.js +10 -0
  284. package/dist/surface/tools/read-tools.js +8 -2
  285. package/dist/surface/tools/real-input-attempt.js +96 -13
  286. package/dist/surface/tools/tool-kit.d.ts +15 -1
  287. package/dist/surface/tools/tool-kit.js +4 -6
  288. package/dist/surface/tools/tools.js +20 -48
  289. package/dist/telemetry/hud-metrics.d.ts +2 -0
  290. package/dist/telemetry/hud-metrics.js +17 -0
  291. package/dist/telemetry/telemetry.d.ts +12 -0
  292. package/dist/telemetry/telemetry.js +43 -14
  293. package/dist/telemetry/verification-of.js +1 -0
  294. package/dist/wire-journal.js +4 -1
  295. package/docs/README.md +1 -1
  296. package/docs/agent-cheatsheet.md +1 -1
  297. package/docs/autodrive.md +29 -25
  298. package/docs/cli/doctor.mdx +6 -0
  299. package/docs/cli/drive.mdx +8 -2
  300. package/docs/cli/init.mdx +11 -13
  301. package/docs/cli/status.mdx +5 -1
  302. package/docs/cli/version.mdx +3 -3
  303. package/docs/cli.mdx +1 -1
  304. package/docs/desktop.mdx +5 -1
  305. package/docs/environment.mdx +3 -7
  306. package/docs/getting-started.md +13 -11
  307. package/docs/human-control.md +1 -2
  308. package/docs/index.mdx +4 -3
  309. package/docs/install-manual.mdx +2 -2
  310. package/docs/packages/tauri.mdx +2 -2
  311. package/docs/packages/vite-plugin.mdx +22 -1
  312. package/docs/packages.mdx +1 -1
  313. package/docs/quickstart.mdx +17 -7
  314. package/docs/telemetry.md +1 -1
  315. package/docs/tools/session-and-feedback.mdx +9 -9
  316. package/docs/usage.md +5 -2
  317. package/docs/what-is-recorded.md +13 -3
  318. package/docs/what-is-reticle.mdx +3 -1
  319. package/package.json +8 -12
  320. package/dist/features/harness/driver.d.ts +0 -50
  321. package/dist/features/harness/driver.js +0 -236
  322. package/dist/features/harness/jev-driver.d.ts +0 -188
  323. package/dist/features/harness/jev-driver.js +0 -678
  324. package/dist/features/harness/openai-driver.d.ts +0 -58
  325. package/dist/features/harness/openai-driver.js +0 -206
  326. package/dist/memory/project/dir/fill-value-store.d.ts +0 -32
  327. package/dist/memory/project/dir/fill-value-store.js +0 -88
  328. package/docs/local-registry.md +0 -143
@@ -0,0 +1,80 @@
1
+ /**
2
+ * What `reticle_session { action: "list" }` answers when nothing is connected.
3
+ *
4
+ * An empty list is the most common thing that tool returns, and on its own it is a dead end: the
5
+ * agent asked the one question it knows to ask and got no next step. So it carries why nothing is
6
+ * connected, the executable next action, the last tab that was here, and, on a project that has
7
+ * never connected, the first-run wiring itself (see first-run-wiring.ts).
8
+ */
9
+ import { dirname } from 'node:path';
10
+ import { z } from 'zod';
11
+ import { DiscoveryInvite, NoSessionAction } from '@reticlehq/core';
12
+ /** The answers that mean "this app is not set up yet", which a first run can settle by wiring it. */
13
+ const FIRST_RUN_ACTIONS = new Set([
14
+ NoSessionAction.RUN_INIT,
15
+ NoSessionAction.START_DEV_SERVER,
16
+ NoSessionAction.OPEN_APP,
17
+ ]);
18
+ /** The fields only an empty list carries, declared so a schema-aware client does not strip them. */
19
+ export const EMPTY_SESSION_LIST_OUTPUT = {
20
+ why: z
21
+ .string()
22
+ .optional()
23
+ .describe('Present ONLY when `sessions` is empty: why nothing is connected, and the next action that fixes it. An empty list is never the end of the road — read this before concluding the app cannot be driven.'),
24
+ next_action: z
25
+ .object({
26
+ action: z.nativeEnum(NoSessionAction),
27
+ command: z.string().optional(),
28
+ port: z.number().optional(),
29
+ reason: z.string(),
30
+ })
31
+ .optional()
32
+ .describe("Present ONLY when `sessions` is empty: the same answer as `why`, executable. `command` is the LITERAL command to run, sourced from this project's own package.json scripts and lockfile — it is absent, never guessed, when the project declares no dev script. `action` is one of daemon_split | start_dev_server | run_init | open_app | reopen_app. `daemon_split` outranks the rest and means the app IS running and instrumented, on a DIFFERENT daemon than the one you are attached to — do not start or re-init anything, read `reason`."),
33
+ talk_to_us: z
34
+ .string()
35
+ .optional()
36
+ .describe('Present ONLY when `sessions` is empty: a call with the founder for the person you are working with. Offer it; never act on it.'),
37
+ wired: z
38
+ .record(z.unknown())
39
+ .optional()
40
+ .describe('Files the first run changed (`steps`); tell the user.'),
41
+ lastKnown: z
42
+ .object({
43
+ sessionId: z.string(),
44
+ url: z.string(),
45
+ })
46
+ .optional()
47
+ .describe('Present ONLY when `sessions` is empty AND a tab was connected to this daemon earlier: the last sessionId and URL that tab was on when it disappeared. A route that 500s tears the page down and the SDK never reconnects — this is the last thing we knew, not a route-500 verdict. An empty list with no lastKnown is a tab that never arrived.'),
48
+ };
49
+ export async function emptySessionList(deps, sessions) {
50
+ const why = deps.sessions.noSessionHint();
51
+ // The executable half. `why` is for the human reading the transcript; this is the one the
52
+ // agent acts on, so it never has to parse a paragraph to find a command inside it.
53
+ const next = deps.sessions.noSessionNextAction();
54
+ // The first run wires the app, not the installation: the daemon runs init itself, once,
55
+ // in its own project, and says what it changed. A daemon outside any project asks instead.
56
+ // Not only on `run_init`: an unwired app whose dev server is not running yet answers
57
+ // `start_dev_server`, which is the commonest first run of all, and init starts that server itself.
58
+ // `projectDirectory` refuses a project that is already wired, so a wired app is never re-run.
59
+ const dir = next !== undefined && FIRST_RUN_ACTIONS.has(next.action)
60
+ ? deps.firstRun?.projectDirectory(dirname(deps.reticleRoot))
61
+ : undefined;
62
+ if (dir !== undefined && deps.firstRun !== undefined) {
63
+ const wired = await deps.firstRun.wire(dir);
64
+ const connected = deps.sessions.list();
65
+ if (wired.ok && 0 < connected.length)
66
+ return { sessions: connected, wired };
67
+ return { sessions, wired, ...(why === undefined ? {} : { why }) };
68
+ }
69
+ // The last tab, when one was here. Optional-call: test stubs of SessionManager predate it.
70
+ const known = deps.sessions.lastKnown?.();
71
+ const lastKnown = undefined === known ? undefined : { sessionId: known.id, url: known.url };
72
+ return {
73
+ sessions,
74
+ ...(why === undefined ? {} : { why }),
75
+ ...(next === undefined ? {} : { next_action: next }),
76
+ ...(undefined === lastKnown ? {} : { lastKnown }),
77
+ // Setup that never connects is where most people give up; it is the talk worth having.
78
+ talk_to_us: DiscoveryInvite.AGENT,
79
+ };
80
+ }
@@ -66,6 +66,12 @@ export declare const RECOVERY: {
66
66
  readonly COMMAND_TIMEOUT: string;
67
67
  readonly FLOW_STEP_MISSING: string;
68
68
  readonly NO_POOL: string;
69
+ /**
70
+ * RETICLE_CHROMIUM_PATH names a browser the daemon cannot launch. The call was fine and so is the
71
+ * Playwright install, so neither a retry nor the install command can help: only the daemon's own
72
+ * environment can, and a running daemon keeps the values it started with.
73
+ */
74
+ readonly CHROMIUM_PATH: string;
69
75
  /**
70
76
  * The two ways a platform answer can refuse a drive. Neither is a fault, and both were telling the
71
77
  * agent that a perfectly understood refusal might be a defect in Reticle — which is how a person
@@ -8,9 +8,10 @@
8
8
  * on stable, human-authored substrings of the thrown messages (see session-manager.ts, the flow/
9
9
  * baseline stores). No clock, no IO — unit-testable in isolation.
10
10
  */
11
- import { RefusalReason, TRANSPORT_LIMITS } from '@reticlehq/core';
11
+ import { RefusalReason, ReticleEnv, TRANSPORT_LIMITS } from '@reticlehq/core';
12
12
  import { SELF_RECOVERING_MARKER } from '../../portal/session/no-session-diagnosis.js';
13
- import { chromiumInstallCommand, bundledPlaywrightVersion, } from '../../command/cli/doctor/browser/chromium-hint.js';
13
+ import { BODY_CLAUSE_REFUSAL_OPENING } from '@reticlehq/engine/evidence/body-capture-remedy.js';
14
+ import { CHROMIUM_PATH_REFUSAL_PREFIX, chromiumInstallCommand, bundledPlaywrightVersion, } from '../../command/cli/doctor/browser/chromium-hint.js';
14
15
  /** Marks where an over-long message had its middle removed. Named so tests assert it by value. */
15
16
  const ELISION = '… [elided] …';
16
17
  /**
@@ -33,11 +34,21 @@ function capMessage(message) {
33
34
  const keep = Math.floor((max - ELISION.length) / 2);
34
35
  return `${message.slice(0, keep)}${ELISION}${message.slice(message.length - keep)}`;
35
36
  }
37
+ /**
38
+ * A fixed phrase as a pattern for the table below, which matches on phrasing.
39
+ *
40
+ * The phrase comes from the module that throws it rather than being retyped here, and a constant is
41
+ * still text: escaped so a future reword containing `(` or `?` cannot turn into a pattern that means
42
+ * something else — or fails to compile at import time, taking the whole surface with it.
43
+ */
44
+ function literally(phrase) {
45
+ return new RegExp(phrase.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i');
46
+ }
36
47
  /**
37
48
  * Messages that already spell out the exact retry, so any hint we add can only argue with them.
38
49
  *
39
50
  * The no-session diagnosis is one (it inspected the machine and named the cause). The native-input
40
- * refusal is the other: it names the tool, the argument and the follow-up call to make instead — and
51
+ * refusal is another: it names the tool, the argument and the follow-up call to make instead — and
41
52
  * it was still collecting the defect ask, inviting a bug report about a refusal that had just told
42
53
  * the agent precisely what to do.
43
54
  */
@@ -47,6 +58,11 @@ const SELF_RECOVERING = [
47
58
  // the current ids" to a message that just named them contradicts the shorter path it offers, and
48
59
  // a contradicted instruction is how one agent came to retry a dead id twelve times.
49
60
  { match: /Connected right now:/i, reason: RefusalReason.NO_SESSION },
61
+ // The body-clause pre-flight: nothing ran, no action was spent, and the message names the setting
62
+ // to switch on — or says the page's SDK predates it, which is a limit and not a defect either. It
63
+ // was collecting the defect ask on top of its own remedy, so the envelope contradicted itself and
64
+ // asked for a bug report about somebody's connect() options.
65
+ { match: literally(BODY_CLAUSE_REFUSAL_OPENING), reason: RefusalReason.UNSUPPORTED },
50
66
  ];
51
67
  /** A message that already carries its own concrete next action, so nothing should be appended. */
52
68
  function isSelfRecovering(message) {
@@ -199,6 +215,17 @@ export const RECOVERY = {
199
215
  chromiumInstallCommand(bundledPlaywrightVersion()) +
200
216
  '` if that is what is missing. Meanwhile drive ' +
201
217
  'a tab the human already has open — reticle_sessions lists them.',
218
+ /**
219
+ * RETICLE_CHROMIUM_PATH names a browser the daemon cannot launch. The call was fine and so is the
220
+ * Playwright install, so neither a retry nor the install command can help: only the daemon's own
221
+ * environment can, and a running daemon keeps the values it started with.
222
+ */
223
+ CHROMIUM_PATH: `Reticle cannot launch the browser ${ReticleEnv.CHROMIUM_PATH} names (the message above says ` +
224
+ 'what is wrong with the path). The call itself was fine, so retrying it will not help, and ' +
225
+ 'neither will installing Chromium. Ask the human to fix that path, or unset it to use ' +
226
+ "Playwright's own Chromium, in the environment the daemon starts from, then run " +
227
+ '`npx @reticlehq/server restart`. Meanwhile drive a tab the human already has open, which ' +
228
+ 'reticle_sessions lists. This is configuration, not a Reticle defect: there is nothing to report.',
202
229
  /**
203
230
  * The two ways a platform answer can refuse a drive. Neither is a fault, and both were telling the
204
231
  * agent that a perfectly understood refusal might be a defect in Reticle — which is how a person
@@ -227,6 +254,7 @@ const REASON_OF = {
227
254
  THROTTLED: RefusalReason.NOT_READY,
228
255
  COMMAND_TIMEOUT: RefusalReason.NOT_READY,
229
256
  NO_POOL: RefusalReason.NOT_READY,
257
+ CHROMIUM_PATH: RefusalReason.NOT_READY,
230
258
  TOKEN_REQUIRED: RefusalReason.NOT_READY,
231
259
  MISSING_BASELINE: RefusalReason.NO_MATCH,
232
260
  MISSING_RECORDING: RefusalReason.NO_MATCH,
@@ -252,6 +280,11 @@ const REASON_OF = {
252
280
  const REASON_BY_HINT = new Map(Object.entries(RECOVERY).map(([key, hint]) => [hint, REASON_OF[key]]));
253
281
  /** Ordered match rules; the first hit wins. Substrings track the thrown messages they recover. */
254
282
  const RULES = [
283
+ // First, because the message carries a path the user chose, and a path can hold any word a later
284
+ // rule keys on. Without it the variable's name read as a `reticle_*` tool and the agent was told
285
+ // its call failed the schema. Not anchored, so a launch error that wraps it is still caught. The
286
+ // prefix has no regex metacharacters, so it is used as is.
287
+ { match: new RegExp(CHROMIUM_PATH_REFUSAL_PREFIX), hint: RECOVERY.CHROMIUM_PATH },
255
288
  { match: /no browser session connected/i, hint: RECOVERY.NO_SESSION },
256
289
  { match: /multiple sessions connected/i, hint: RECOVERY.MULTIPLE_SESSIONS },
257
290
  { match: /no connected session with id/i, hint: RECOVERY.UNKNOWN_SESSION },
@@ -267,7 +300,7 @@ const RULES = [
267
300
  // needs its own rule: none of them contains "no browser session connected" (the scope miss is
268
301
  // "no browser session FOR project 'x'", which is the opposite claim — sessions exist).
269
302
  {
270
- match: /scope mismatch|session\(s\) ARE connected under a different project/i,
303
+ match: /scope mismatch|session\(s\) ARE connected (?:under a different project|with no projectId)/i,
271
304
  hint: RECOVERY.SCOPE_MISMATCH,
272
305
  },
273
306
  { match: /^session disconnected$|session .* never connected/i, hint: RECOVERY.SESSION_GONE },
@@ -10,5 +10,14 @@
10
10
  * Those flows are the point. From the second run onwards `reticle_verify { action: "flows" }` replays
11
11
  * them deterministically with no model in the loop at all.
12
12
  */
13
- import type { ToolDef } from './tool-kit.js';
13
+ import type { ToolDef, ToolDeps } from './tool-kit.js';
14
+ import { type RemoteDriveOutcome } from '../../features/harness/platform/remote-drive.js';
14
15
  export declare const EXPLORE_TOOLS: ToolDef[];
16
+ /**
17
+ * A drive the platform's chat asked for, run as the very tool an agent would call.
18
+ *
19
+ * Through `runTool`, not a second path to `exploreApp`: a dispatch that skips it is a drive nobody
20
+ * counted, and the chat's answer is then the same derived summary the agent would have read — never
21
+ * a model's account of its own drive.
22
+ */
23
+ export declare function driveForChat(deps: ToolDeps, goal: string, sessionId?: string): Promise<RemoteDriveOutcome>;
@@ -11,28 +11,28 @@
11
11
  * them deterministically with no model in the loop at all.
12
12
  */
13
13
  import { z } from 'zod';
14
+ import { unprovedGoals } from '../../features/harness/goals.js';
14
15
  import { ReticleTool, asRecord } from '@reticlehq/core';
15
16
  import { stepCountSchema } from './args/numeric-bounds.js';
17
+ import { runTool } from './invoke-tool.js';
18
+ import { driveVerdict } from '../../features/harness/platform/remote-drive.js';
16
19
  import { exploreApp, harnessAvailable, withLinkedCredential, MSG_NO_HARNESS_KEY, } from './harness-explore.js';
17
- import { DRIVER_NAMES } from '../../features/harness/drivers.js';
18
- import { describeDrive, replayedFlows } from '../../features/harness/drive-report.js';
20
+ import { EXPLORE_NEEDS } from '../../features/harness/drivers.js';
21
+ import { checkTally, describeDrive, replayedFlows } from '../../features/harness/drive-report.js';
19
22
  import { StopReason } from '../../features/harness/harness.js';
20
23
  export const EXPLORE_TOOLS = [
21
24
  {
22
25
  name: ReticleTool.VERIFY_EXPLORE,
23
- description: 'Drive the app yourself? Do not — call this instead. A model inside the daemon drives it through this same tool surface, records what it drove as saved flows, and answers in a few lines, so the whole drive costs you one tool call instead of a context full of snapshots. Pass `persona` to say who to be or what to accomplish ("a returning customer checking out", "an admin revoking a seat") and it completes that whole journey rather than clicking at random. Returns { stopReason, steps, savedFlows, summary, usage }. The saved flows are the point: from the next run on, reticle_verify { action: "flows" } replays them deterministically with NO model in the loop. DESTRUCTIVE — it really drives the app, and it spends model budget. Needs ANTHROPIC_API_KEY in the daemon environment.',
26
+ description: 'Drive the app yourself? Do not — call this instead. The Reticle Harness drives it through this same tool surface (it decides on the Reticle platform, this daemon executes each step), records what it drove as saved flows, and answers in a few lines, so the whole drive costs you one tool call instead of a context full of snapshots. Pass `persona` to say who to be or what to accomplish ("a returning customer checking out", "an admin revoking a seat") and it completes that whole journey rather than clicking at random. Returns { stopReason, steps, savedFlows, summary, usage }. The saved flows are the point: from the next run on, reticle_verify { action: "flows" } replays them deterministically with NO model in the loop. DESTRUCTIVE — it really drives the app, and it spends Harness credits. ' +
27
+ EXPLORE_NEEDS,
24
28
  inputSchema: {
25
29
  persona: z
26
30
  .string()
27
31
  .optional()
28
- .describe('Who to be, or what to accomplish. A journey ("sign up, then invite a teammate") drives far better than no focus at all.'),
32
+ .describe('The journey in plain words; any "quoted text" must be on the page when it ends (checked).'),
29
33
  maxSteps: stepCountSchema
30
34
  .optional()
31
- .describe('Ceiling on model turns. Bounds cost, not value — the drive is graded however it ends.'),
32
- driver: z
33
- .enum(DRIVER_NAMES)
34
- .optional()
35
- .describe('Which model drives. An unconfigured one is an error, never a substitution, so an A/B cannot measure the same driver twice. Omit for the default.'),
35
+ .describe('Ceiling on model turns; the drive is graded however it ends.'),
36
36
  sessionId: z
37
37
  .string()
38
38
  .optional()
@@ -47,6 +47,21 @@ export const EXPLORE_TOOLS = [
47
47
  savedFlows: z.array(z.string()),
48
48
  /** Flows that already existed and were driven and written again. A second run's ordinary result. */
49
49
  rewroteFlows: z.array(z.string()),
50
+ /** Saved flows with no step that asserts anything: their replay verifies nothing. */
51
+ unverifiedFlows: z.array(z.string()),
52
+ /** Whether the drive ran at least one check. A drive that did not proved nothing. */
53
+ proved: z.boolean(),
54
+ /**
55
+ * Whether every journey reached its goal, as the platform judged when the drive finished.
56
+ * Absent when no journey's goal was judged. Checks that held are not the goal reached.
57
+ */
58
+ goalMet: z.boolean().optional(),
59
+ /** How the drive's checks came out, one per control and claim at its worst. */
60
+ checks: z.object({ held: z.number(), failed: z.number(), undecided: z.number() }),
61
+ /** The runs this drive syncs as (`harness-<uuid>`): what the platform links its check to. */
62
+ runIds: z.array(z.string()).optional(),
63
+ /** One verdict per requested goal, checked by the harness itself. Only `yes` is proved. */
64
+ goals: z.array(z.object({ text: z.string(), verified: z.string() })),
50
65
  /**
51
66
  * What the drive set out to do, read from `.reticle` BEFORE it started — every recorded
52
67
  * journey with the consequence that must still hold, and the declared intent nobody has
@@ -81,12 +96,10 @@ export const EXPLORE_TOOLS = [
81
96
  const persona = args['persona'];
82
97
  const maxSteps = args['maxSteps'];
83
98
  const sessionId = args['sessionId'];
84
- const driver = args['driver'];
85
- const { drive, savedFlows, rewroteFlows, driverName, plan } = await exploreApp(deps, env, {
99
+ const { drive, savedFlows, rewroteFlows, unverifiedFlows, driverName, plan, goals, planLines, runIds, } = await exploreApp(deps, env, {
86
100
  ...('string' === typeof persona ? { focus: persona } : {}),
87
101
  ...('number' === typeof maxSteps ? { maxSteps } : {}),
88
102
  ...('string' === typeof sessionId ? { sessionId } : {}),
89
- ...('string' === typeof driver ? { driverName: driver } : {}),
90
103
  });
91
104
  return {
92
105
  stopReason: drive.stopReason,
@@ -94,11 +107,19 @@ export const EXPLORE_TOOLS = [
94
107
  steps: drive.steps,
95
108
  savedFlows: [...savedFlows],
96
109
  rewroteFlows: [...rewroteFlows],
110
+ unverifiedFlows: [...unverifiedFlows],
111
+ proved: drive.proved,
112
+ checks: checkTally(drive.toolCalls),
113
+ ...(drive.goalMet === undefined ? {} : { goalMet: drive.goalMet }),
114
+ ...(runIds === undefined ? {} : { runIds: [...runIds] }),
115
+ goals: [...goals],
97
116
  plan: { summary: plan.summary, steps: [...plan.steps] },
98
117
  // Derived, not narrated. The driver's own `summary` is appended only when it said
99
118
  // something — it is the one part of this a model authored, so it goes last and is labelled.
100
119
  summary: [
101
- describeDrive(drive.toolCalls, [...savedFlows, ...rewroteFlows]),
120
+ ...(planLines ?? []),
121
+ describeDrive(drive.toolCalls, [...savedFlows, ...rewroteFlows], unverifiedFlows),
122
+ ...[unprovedGoals(goals)].filter((line) => line !== undefined),
102
123
  ...(0 === drive.summary.length ? [] : [`The driver's own account: ${drive.summary}`]),
103
124
  ].join('\n'),
104
125
  ...(drive.error === undefined ? {} : { error: drive.error }),
@@ -159,4 +180,52 @@ const NOTHING_RECORDED = {
159
180
  [StopReason.BUDGET]: 'The drive ran out of steps before saving a flow. Raise `maxSteps`, or give it a narrower `persona` so it spends the budget on one journey.',
160
181
  [StopReason.STALLED]: 'The drive stopped asking for tools without finishing. Nothing was saved, and nothing is proved.',
161
182
  [StopReason.BROKEN]: 'The drive broke before saving anything — read `error`. Nothing is proved.',
183
+ [StopReason.STOPPED]: 'Autonomous driving was switched off before the drive saved anything. Nothing is proved; switch it back on to drive again.',
162
184
  };
185
+ /**
186
+ * A drive the platform's chat asked for, run as the very tool an agent would call.
187
+ *
188
+ * Through `runTool`, not a second path to `exploreApp`: a dispatch that skips it is a drive nobody
189
+ * counted, and the chat's answer is then the same derived summary the agent would have read — never
190
+ * a model's account of its own drive.
191
+ */
192
+ export async function driveForChat(deps, goal, sessionId) {
193
+ const explore = EXPLORE_TOOLS.find((tool) => ReticleTool.VERIFY_EXPLORE === tool.name);
194
+ if (explore === undefined)
195
+ throw new Error('this build has no Harness drive');
196
+ const out = asRecord(await runTool(explore, deps, {
197
+ persona: goal,
198
+ ...(sessionId === undefined ? {} : { sessionId }),
199
+ }));
200
+ const summary = 'string' === typeof out['summary'] ? out['summary'] : '';
201
+ const error = 'string' === typeof out['error'] ? out['error'] : undefined;
202
+ const note = 'string' === typeof out['note'] ? out['note'] : undefined;
203
+ const goals = Array.isArray(out['goals'])
204
+ ? out['goals'].flatMap((g) => {
205
+ const verified = asRecord(g)['verified'];
206
+ return 'string' === typeof verified ? [{ verified }] : [];
207
+ })
208
+ : [];
209
+ const goalMet = out['goalMet'];
210
+ const tally = asRecord(out['checks']);
211
+ const count = (key) => ('number' === typeof tally[key] ? tally[key] : 0);
212
+ const checks = { held: count('held'), failed: count('failed'), undecided: count('undecided') };
213
+ const runIds = Array.isArray(out['runIds'])
214
+ ? out['runIds'].filter((id) => 'string' === typeof id)
215
+ : [];
216
+ return {
217
+ // Only a drive that ran a check and did not break counts as one that proved anything.
218
+ ok: true === out['proved'] && error === undefined,
219
+ ...(0 === runIds.length ? {} : { runIds }),
220
+ verdict: driveVerdict({
221
+ proved: true === out['proved'],
222
+ checks,
223
+ goals,
224
+ ...(error === undefined ? {} : { error }),
225
+ ...('boolean' === typeof goalMet ? { goalMet } : {}),
226
+ }),
227
+ summary: [summary, note, error === undefined ? undefined : `Error: ${error}`]
228
+ .filter((line) => line !== undefined && 0 < line.length)
229
+ .join('\n'),
230
+ };
231
+ }
@@ -17,6 +17,17 @@ import { ReticleTool } from '@reticlehq/core';
17
17
  import { asNumber, asString } from '@reticlehq/core';
18
18
  import { submitFeedback, feedbackDisabled } from '../../telemetry/feedback.js';
19
19
  import { getTelemetry } from '../../telemetry/telemetry.js';
20
+ import { dirname } from 'node:path';
21
+ import { sessionRoot } from '../../memory/project/session-root.js';
22
+ /** The project folder this call's session belongs to, when one can be told. */
23
+ function projectDir(deps, sessionId) {
24
+ try {
25
+ return { cwd: dirname(sessionRoot(deps, sessionId)) };
26
+ }
27
+ catch {
28
+ return {};
29
+ }
30
+ }
20
31
  /** One optional string arg, present only when the agent actually supplied it. */
21
32
  function optionalText(args, key) {
22
33
  const value = asString(args[key]);
@@ -175,6 +186,9 @@ export const FEEDBACK_TOOLS = [
175
186
  ...optionalRating(args),
176
187
  }, {
177
188
  session: sessionFacts(deps, asString(args['sessionId'])),
189
+ // A report that cannot be sent is written into the project being verified, never into
190
+ // whatever folder the daemon happened to start in.
191
+ ...projectDir(deps, asString(args['sessionId'])),
178
192
  // The AGENT does not wait for the network. `reticle feedback` (a human, at a terminal,
179
193
  // watching for an answer) still does — see submitFeedback's `background`.
180
194
  background: true,
@@ -7,13 +7,17 @@
7
7
  * nothing grades) but the flows that now exist on disk, because every future run replays those
8
8
  * deterministically with no model in the loop at all.
9
9
  */
10
+ import { type FlowFile } from '@reticlehq/core';
10
11
  import type { ToolDeps } from './tool-kit.js';
11
12
  import { type HarnessPlan } from './harness-plan.js';
12
13
  import { type ConfigFetch } from '../../features/harness/platform-config.js';
13
- import { type HarnessResult, type ModelDriver, type ToolOutcome } from '../../features/harness/harness.js';
14
+ import { type HarnessResult, type HarnessToolset, type ModelDriver, type ToolOutcome } from '../../features/harness/harness.js';
15
+ import { type GoalCheck } from '../../features/harness/goals.js';
14
16
  export interface ExploreOptions {
15
17
  /** Who to be, or what to accomplish. Appended to the standing instruction. */
16
18
  focus?: string;
19
+ /** Texts the drive must leave on the page. Default: whatever `focus` quoted. See goals.ts. */
20
+ goals?: readonly string[];
17
21
  /** Pinned tab, when the app has more than one connected. */
18
22
  sessionId?: string;
19
23
  /** Hard ceiling on model turns. Bounds cost, not value — the drive is usable however it ends. */
@@ -51,6 +55,8 @@ export interface ExploreResult {
51
55
  plan: HarnessPlan;
52
56
  /** Flows that exist now and did not before — the part of a drive that is worth paying for twice. */
53
57
  savedFlows: readonly string[];
58
+ /** Of the saved and rewritten flows, the ones with no step that checks anything. */
59
+ unverifiedFlows: readonly string[];
54
60
  /**
55
61
  * Flows that already existed and were written again by this drive.
56
62
  *
@@ -64,6 +70,10 @@ export interface ExploreResult {
64
70
  * journeys under the same names — so this is the ordinary case, not the corner one.
65
71
  */
66
72
  rewroteFlows: readonly string[];
73
+ /** One verdict per goal, checked by the harness after the drive rather than taken on its word. */
74
+ goals: readonly GoalCheck[];
75
+ /** The persona plan and how each persona ended, when the platform proposed the personas. */
76
+ planLines?: readonly string[];
67
77
  /**
68
78
  * Which driver actually drove.
69
79
  *
@@ -73,6 +83,8 @@ export interface ExploreResult {
73
83
  * comparison. `custom` is an injected driver, which is neither of ours to name.
74
84
  */
75
85
  driverName: string;
86
+ /** The run this drive syncs as (`harness-<id>`), so a caller can find it on the platform. */
87
+ runIds?: readonly string[];
76
88
  }
77
89
  /**
78
90
  * Why the harness is unavailable, phrased as the way to make it available.
@@ -82,10 +94,6 @@ export interface ExploreResult {
82
94
  * print it, and two copies of this sentence would drift.
83
95
  */
84
96
  export declare const MSG_NO_HARNESS_KEY: string;
85
- /** Asked for Jev specifically and it is not configured. Distinct from having no model at all. */
86
- export declare const MSG_NO_JEV_KEY: string;
87
- /** Asked for OpenAI specifically and it is not configured. Never substituted, same as Jev. */
88
- export declare const MSG_NO_OPENAI_KEY: string;
89
97
  /**
90
98
  * The environment, plus the credential this machine already has.
91
99
  *
@@ -106,7 +114,10 @@ export declare const MSG_NO_OPENAI_KEY: string;
106
114
  * the tool surface reach into `memory/cloud` — a reach the directory guard refused, correctly.
107
115
  */
108
116
  export declare function withLinkedCredential(deps: ToolDeps, env: Record<string, string | undefined>): Promise<Record<string, string | undefined>>;
109
- /** Is there a model the harness can drive with? A read, because "not configured" is not a failure. */
117
+ /**
118
+ * Can this machine reach the Harness? Only through the platform: a linked project or a platform
119
+ * key. Whether the workspace is entitled is the platform's answer, asked when the drive starts.
120
+ */
110
121
  export declare function harnessAvailable(env: Record<string, string | undefined>): boolean;
111
122
  /**
112
123
  * Read the step ceiling out of the environment.
@@ -125,6 +136,29 @@ export declare function maxStepsFromEnv(env: Record<string, string | undefined>)
125
136
  * record and did not — and this product does not get to make that mistake about its own work.
126
137
  */
127
138
  export declare function exploreApp(deps: ToolDeps, env: Record<string, string | undefined>, options?: ExploreOptions): Promise<ExploreResult>;
139
+ /** A line in the HUD's Agent Log for the person watching; nobody watching is not an error. */
140
+ export declare function narrator(deps: ToolDeps, options: ExploreOptions): (text: string) => void;
141
+ /** What this project's memory says must hold, in its own words. Nothing recorded is none. */
142
+ export declare function productRules(deps: ToolDeps, sessionId?: string): Promise<string[]>;
143
+ /** `deps` whose flow store is the session's own project, where the drive's saves land. */
144
+ export declare function withProjectFlows(deps: ToolDeps, sessionId?: string): ToolDeps;
145
+ /**
146
+ * Write the persona onto every flow the drive saved or rewrote without an intent.
147
+ *
148
+ * Whichever driver saved it, and in whatever words: the cheap driver's own teardown saved its flow
149
+ * with no intent at all, so a replay could never say which journey it was meant to prove. A flow
150
+ * that already carries an intent keeps it. Best effort: a flow that cannot be read or rewritten is
151
+ * still a saved flow.
152
+ */
153
+ export declare function recordPersona(deps: ToolDeps, names: readonly string[], persona: string): Promise<void>;
154
+ /**
155
+ * The saved flows with no step that asserts a consequence.
156
+ *
157
+ * Such a flow replays as "verified nothing": it clicks through the journey and would pass with the
158
+ * feature broken. Named so the drive's report does not count it as evidence. A flow that cannot be
159
+ * read is left out rather than guessed at.
160
+ */
161
+ export declare function flowsThatCheckNothing(deps: ToolDeps, names: readonly string[]): Promise<readonly string[]>;
128
162
  /**
129
163
  * What a drive actually left behind, split into flows that are NEW and flows it wrote over.
130
164
  *
@@ -161,18 +195,66 @@ export declare const MSG_HARNESS_DISABLED: string;
161
195
  /**
162
196
  * The other reason a drive can be refused, and it is NOT the same reason.
163
197
  *
164
- * A drive through the platform spends Reticle's model budget. That is free for three months and
165
- * included on a paid plan, and outside both it is somebody else's money being spent on nothing. The
166
- * message says which of the two it is and what to do, because "the harness is off" told to a person
167
- * who never turned anything off is a support ticket rather than an answer.
198
+ * A drive through the platform spends Reticle's model budget, bounded by the workspace's monthly
199
+ * Harness credits. The platform answers this only when something other than the switch stops it.
168
200
  */
169
201
  export declare const MSG_HARNESS_UNCLAIMED: string;
202
+ /** The platform could not be asked, and the drive would spend Reticle's budget without its yes. */
203
+ export declare const MSG_HARNESS_UNCONFIRMED: string;
204
+ /**
205
+ * The reason this drive must not start, or `undefined` to go ahead.
206
+ *
207
+ * A drive on somebody's OWN model key never waits on the platform beyond the off switch: their key,
208
+ * their spend. A drive through the platform proxy bills Reticle, and that one needs a confirmed yes
209
+ * — enabled, entitled, and a model ready. It used to fail open: `fetchPlatformConfig` answers
210
+ * `undefined` on a network error, a non-2xx, a body that does not parse, or a two-second timeout,
211
+ * and all four read as "carry on", so a slow second on a settings endpoint let an unentitled
212
+ * workspace drive on Reticle's budget. Now the same silence refuses with a message that says to
213
+ * retry or bring a key. Fields an older platform omits still default to yes inside
214
+ * `fetchPlatformConfig`; only an answer that never arrived is a no.
215
+ */
216
+ export declare function refusedByPlatform(env: Record<string, string | undefined>, options: ExploreOptions): Promise<string | undefined>;
217
+ /**
218
+ * Close and save whatever recording the drive left open.
219
+ *
220
+ * The driver reserves turns for its own teardown, which covers the ordinary ending. It does NOT
221
+ * cover a drive that broke, or one whose model went quiet, or one cut off by a budget the caller
222
+ * shortened — and in every one of those the app really was driven and the record of it is thrown
223
+ * away. Measured before this existed: a 24-step journey through a real dashboard, saved nothing.
224
+ *
225
+ * Failures are swallowed on purpose. This is a last chance, not a checkpoint: if the recording was
226
+ * already closed the stop is refused and there is nothing to do, and a drive must not fail at the
227
+ * finish line because the thing it was trying to rescue did not need rescuing.
228
+ */
170
229
  /**
171
- * A stored preference, kept only if this build can actually honour it.
230
+ * What a banked recording says it was for: the persona the caller named, when there was one.
172
231
  *
173
- * Exported because it is the whole of the asymmetry with the per-call argument, and the asymmetry
174
- * is the part somebody will later think is a bug.
232
+ * It used to be "Autonomous drive of X, banked after the run ended" whatever the caller asked for,
233
+ * so a replay could never say which journey the flow was meant to prove.
175
234
  */
176
- export declare function knownDriver(provider: string | undefined): string | undefined;
235
+ export declare function bankedIntent(persona: string | undefined, open: string): string;
236
+ export declare function bankOpenRecording(toolset: HarnessToolset, drive: HarnessResult, persona: string | undefined): Promise<void>;
177
237
  /** The recording started and never stopped, if there is one. Exported because it is the decision. */
178
238
  export declare function openRecordingName(toolCalls: readonly ToolOutcome[]): string | undefined;
239
+ /**
240
+ * Read the project's own record of what it does and what is proved about it.
241
+ *
242
+ * No browser, no model, no source. A failure here is "nothing is recorded yet", which is the
243
+ * ordinary state of a new project and not a reason to refuse to drive — so it answers an empty
244
+ * plan rather than throwing.
245
+ */
246
+ export declare function readFlows(deps: ToolDeps): Promise<FlowFile[]>;
247
+ export declare function readPlan(deps: ToolDeps, sessionId?: string): Promise<HarnessPlan>;
248
+ export declare function pinned(options: ExploreOptions): {
249
+ sessionId?: string;
250
+ };
251
+ /**
252
+ * The driver: the platform's Harness, which decides every step on its side while this machine
253
+ * executes. There is no other: the decisions, the models and the planning run only on the platform.
254
+ */
255
+ export declare function buildDriver(env: Record<string, string | undefined>, maxSteps: number, plan: HarnessPlan, requested?: string, persona?: string,
256
+ /** A goal driven with no persona: it names the drive's flows as a persona would. */
257
+ journey?: string): {
258
+ driver: ModelDriver;
259
+ name: string;
260
+ };