@reticlehq/server 3.6.0 → 3.7.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 (614) hide show
  1. package/SKILL.md +122 -123
  2. package/dist/command/cli/adhoc-suite.d.ts +14 -0
  3. package/dist/command/cli/adhoc-suite.js +42 -7
  4. package/dist/command/cli/adhoc-verdict.d.ts +18 -2
  5. package/dist/command/cli/adhoc-verdict.js +70 -6
  6. package/dist/command/cli/answers/gate-exit.d.ts +23 -0
  7. package/dist/command/cli/answers/gate-exit.js +45 -0
  8. package/dist/command/cli/answers/gate-hook-message.d.ts +2 -0
  9. package/dist/command/cli/answers/gate-hook-message.js +3 -1
  10. package/dist/command/cli/auth/device-flow.d.ts +13 -2
  11. package/dist/command/cli/auth/device-flow.js +17 -0
  12. package/dist/command/cli/cli-doctor.js +14 -0
  13. package/dist/command/cli/cli-flow-commands.d.ts +0 -11
  14. package/dist/command/cli/cli-flow-commands.js +28 -105
  15. package/dist/command/cli/cli-kill.js +3 -2
  16. package/dist/command/cli/cli-parse-grammar.d.ts +24 -0
  17. package/dist/command/cli/cli-parse-grammar.js +75 -0
  18. package/dist/command/cli/cli-parse-verify.d.ts +2 -0
  19. package/dist/command/cli/cli-parse-verify.js +18 -0
  20. package/dist/command/cli/cli-parse.d.ts +19 -28
  21. package/dist/command/cli/cli-parse.js +82 -182
  22. package/dist/command/cli/cli-update-commands.d.ts +1 -1
  23. package/dist/command/cli/cli-update-commands.js +61 -9
  24. package/dist/command/cli/cli-usage.d.ts +33 -0
  25. package/dist/command/cli/cli-usage.js +287 -0
  26. package/dist/command/cli/cli-verify.d.ts +34 -4
  27. package/dist/command/cli/cli-verify.js +81 -12
  28. package/dist/command/cli/cloud-cli.d.ts +13 -0
  29. package/dist/command/cli/cloud-cli.js +145 -333
  30. package/dist/command/cli/cloud-kit.d.ts +12 -0
  31. package/dist/command/cli/cloud-kit.js +41 -2
  32. package/dist/command/cli/cloud-link.d.ts +57 -0
  33. package/dist/command/cli/cloud-link.js +308 -0
  34. package/dist/command/cli/cloud-login.d.ts +15 -1
  35. package/dist/command/cli/cloud-login.js +67 -13
  36. package/dist/command/cli/context-command.d.ts +1 -0
  37. package/dist/command/cli/context-command.js +33 -0
  38. package/dist/command/cli/daemon-start-options.d.ts +3 -0
  39. package/dist/command/cli/daemon-start-options.js +24 -3
  40. package/dist/command/cli/doctor/doctor-coverage-line.d.ts +2 -0
  41. package/dist/command/cli/doctor/doctor-coverage-line.js +47 -0
  42. package/dist/command/cli/doctor/doctor-project-line.d.ts +0 -12
  43. package/dist/command/cli/doctor/doctor-project-line.js +5 -4
  44. package/dist/command/cli/doctor/doctor-rows.d.ts +6 -0
  45. package/dist/command/cli/doctor/doctor-rows.js +6 -0
  46. package/dist/command/cli/doctor/doctor-signing-line.d.ts +38 -0
  47. package/dist/command/cli/doctor/doctor-signing-line.js +114 -0
  48. package/dist/command/cli/known-regression.d.ts +13 -0
  49. package/dist/command/cli/known-regression.js +34 -0
  50. package/dist/command/cli/launch/sdk-version-match.d.ts +12 -0
  51. package/dist/command/cli/launch/sdk-version-match.js +25 -0
  52. package/dist/command/cli/lifecycle/daemon-lifecycle.js +11 -2
  53. package/dist/command/cli/mcp-command.d.ts +6 -0
  54. package/dist/command/cli/mcp-command.js +13 -5
  55. package/dist/command/cli/ports/port-holder.js +1 -1
  56. package/dist/command/cli/project-name.d.ts +29 -1
  57. package/dist/command/cli/project-name.js +107 -2
  58. package/dist/command/cli/setup-mcp-cli.d.ts +17 -1
  59. package/dist/command/cli/setup-mcp-cli.js +29 -8
  60. package/dist/command/cli/status/status-lines.d.ts +1 -0
  61. package/dist/command/cli/status/status-lines.js +47 -2
  62. package/dist/command/cli/status/workspace-links.d.ts +10 -0
  63. package/dist/command/cli/status/workspace-links.js +29 -0
  64. package/dist/command/cli/try-command.d.ts +96 -0
  65. package/dist/command/cli/try-command.js +362 -0
  66. package/dist/command/cli/tutorial.d.ts +0 -100
  67. package/dist/command/cli/tutorial.js +0 -178
  68. package/dist/command/cli/verify-results.d.ts +19 -0
  69. package/dist/command/cli/verify-results.js +29 -0
  70. package/dist/command/cli-onboarding.d.ts +0 -11
  71. package/dist/command/cli-onboarding.js +0 -15
  72. package/dist/command/cli.d.ts +14 -0
  73. package/dist/command/cli.js +103 -83
  74. package/dist/command/daemon/daemon-resilience.js +4 -4
  75. package/dist/command/daemon/daemon-resolve.d.ts +9 -1
  76. package/dist/command/daemon/daemon-resolve.js +49 -10
  77. package/dist/command/setup/bringup/bridge-port.d.ts +3 -1
  78. package/dist/command/setup/bringup/bridge-port.js +13 -3
  79. package/dist/command/setup/bringup/dev-server-log-cap.js +2 -0
  80. package/dist/command/setup/bringup/ensure-daemon.js +9 -2
  81. package/dist/command/setup/init/agent-restart.d.ts +23 -0
  82. package/dist/command/setup/init/agent-restart.js +28 -0
  83. package/dist/command/setup/init/init-port.d.ts +10 -1
  84. package/dist/command/setup/init/init-port.js +22 -2
  85. package/dist/command/setup/init/init-runtime.d.ts +12 -2
  86. package/dist/command/setup/init/init-runtime.js +96 -37
  87. package/dist/command/setup/node-effects.d.ts +2 -0
  88. package/dist/command/setup/node-effects.js +36 -8
  89. package/dist/command/setup/probe/existing-app.d.ts +12 -0
  90. package/dist/command/setup/probe/existing-app.js +14 -0
  91. package/dist/command/setup/remaining-steps.js +1 -1
  92. package/dist/command/setup/run-setup.d.ts +29 -9
  93. package/dist/command/setup/run-setup.js +34 -28
  94. package/dist/command/setup/setup-command.d.ts +23 -0
  95. package/dist/command/setup/setup-command.js +57 -4
  96. package/dist/command/setup/setup-mcp.js +25 -2
  97. package/dist/command/setup/tauri-signing.d.ts +19 -0
  98. package/dist/command/setup/tauri-signing.js +79 -0
  99. package/dist/command/setup/terminal/listeners.d.ts +10 -1
  100. package/dist/command/setup/terminal/listeners.js +37 -6
  101. package/dist/command/update/update-nudge.d.ts +9 -0
  102. package/dist/command/update/update-nudge.js +1 -1
  103. package/dist/command/update/updater.js +11 -15
  104. package/dist/command/version/version-nudge.js +3 -0
  105. package/dist/command/version/version-skew.d.ts +20 -1
  106. package/dist/command/version/version-skew.js +38 -2
  107. package/dist/features/crawl/crawl.js +14 -1
  108. package/dist/features/exhaust/ledger.d.ts +14 -14
  109. package/dist/features/harness/drive-report.d.ts +39 -5
  110. package/dist/features/harness/drive-report.js +271 -54
  111. package/dist/features/harness/drive-runs.d.ts +120 -0
  112. package/dist/features/harness/drive-runs.js +289 -0
  113. package/dist/features/harness/drivers.d.ts +1 -1
  114. package/dist/features/harness/drivers.js +1 -1
  115. package/dist/features/harness/explore/console-pages.d.ts +15 -0
  116. package/dist/features/harness/explore/console-pages.js +146 -0
  117. package/dist/features/harness/explore/disparity.d.ts +19 -0
  118. package/dist/features/harness/explore/disparity.js +71 -0
  119. package/dist/features/harness/explore/fake-app.d.ts +16 -0
  120. package/dist/features/harness/explore/fake-app.js +49 -0
  121. package/dist/features/harness/explore/goal-search.d.ts +26 -0
  122. package/dist/features/harness/explore/goal-search.js +144 -0
  123. package/dist/features/harness/explore/page-tree.d.ts +49 -0
  124. package/dist/features/harness/explore/page-tree.js +148 -0
  125. package/dist/features/harness/explore/site-map.d.ts +34 -0
  126. package/dist/features/harness/explore/site-map.js +150 -0
  127. package/dist/features/harness/goals.d.ts +68 -7
  128. package/dist/features/harness/goals.js +289 -36
  129. package/dist/features/harness/harness.d.ts +30 -1
  130. package/dist/features/harness/harness.js +141 -7
  131. package/dist/features/harness/platform/local-apps.d.ts +36 -3
  132. package/dist/features/harness/platform/local-apps.js +81 -5
  133. package/dist/features/harness/platform/persona-credentials.d.ts +20 -0
  134. package/dist/features/harness/platform/persona-credentials.js +69 -0
  135. package/dist/features/harness/platform/platform-drives.d.ts +61 -0
  136. package/dist/features/harness/platform/platform-drives.js +114 -0
  137. package/dist/features/harness/platform/refusal.d.ts +52 -0
  138. package/dist/features/harness/platform/refusal.js +71 -0
  139. package/dist/features/harness/platform/remote-drive.d.ts +8 -2
  140. package/dist/features/harness/platform/remote-drive.js +22 -9
  141. package/dist/features/harness/platform/script.d.ts +57 -11
  142. package/dist/features/harness/platform/script.js +144 -12
  143. package/dist/features/harness/platform/server-driver.d.ts +17 -4
  144. package/dist/features/harness/platform/server-driver.js +29 -26
  145. package/dist/features/harness/platform-config.d.ts +38 -5
  146. package/dist/features/harness/platform-config.js +93 -8
  147. package/dist/features/harness/route-guess.d.ts +13 -0
  148. package/dist/features/harness/route-guess.js +49 -0
  149. package/dist/features/harness/script/script-run.d.ts +30 -1
  150. package/dist/features/harness/script/script-run.js +355 -48
  151. package/dist/features/license/license.d.ts +2 -2
  152. package/dist/features/visual/step-shots.d.ts +31 -0
  153. package/dist/features/visual/step-shots.js +231 -0
  154. package/dist/features/visual/visual-tools.js +22 -14
  155. package/dist/hooks/client-directory.d.ts +18 -0
  156. package/dist/hooks/client-directory.js +57 -0
  157. package/dist/hooks/coding-agents.d.ts +34 -0
  158. package/dist/hooks/coding-agents.js +117 -0
  159. package/dist/hooks/driven-by.d.ts +21 -2
  160. package/dist/hooks/driven-by.js +42 -2
  161. package/dist/hooks/hook-bus.d.ts +4 -0
  162. package/dist/hooks/hook-bus.js +18 -0
  163. package/dist/index.d.ts +1 -0
  164. package/dist/index.js +26 -183
  165. package/dist/judgement/capsule/capsule-store.d.ts +2 -2
  166. package/dist/judgement/capsule/causal-summary.d.ts +15 -1
  167. package/dist/judgement/capsule/causal-summary.js +33 -0
  168. package/dist/judgement/runs/artifact/build-verification-run.d.ts +1 -1
  169. package/dist/judgement/runs/artifact/build-verification-run.js +20 -4
  170. package/dist/judgement/runs/artifact/gap-summary.d.ts +0 -18
  171. package/dist/judgement/runs/artifact/gap-summary.js +12 -3
  172. package/dist/judgement/runs/artifact/replay-mapping.js +25 -9
  173. package/dist/judgement/runs/artifact/run-store.d.ts +13 -1
  174. package/dist/judgement/runs/artifact/run-store.js +175 -8
  175. package/dist/judgement/runs/context-tools.js +59 -1
  176. package/dist/judgement/runs/default-run-id.d.ts +3 -0
  177. package/dist/judgement/runs/default-run-id.js +16 -0
  178. package/dist/judgement/runs/drive-run.d.ts +53 -5
  179. package/dist/judgement/runs/drive-run.js +234 -20
  180. package/dist/judgement/runs/resume.d.ts +44 -0
  181. package/dist/judgement/runs/resume.js +137 -0
  182. package/dist/judgement/runs/reticle-runner.js +5 -2
  183. package/dist/judgement/runs/runner-port.d.ts +5 -1
  184. package/dist/judgement/runs/runner-port.js +8 -2
  185. package/dist/judgement/runs/verification-sync.d.ts +2 -0
  186. package/dist/judgement/runs/verification-sync.js +36 -15
  187. package/dist/language/flows/decision.js +2 -1
  188. package/dist/language/flows/fields/flow-secret-field.d.ts +12 -0
  189. package/dist/language/flows/fields/flow-secret-field.js +25 -2
  190. package/dist/language/flows/flow-anchor.d.ts +7 -2
  191. package/dist/language/flows/flow-anchor.js +10 -3
  192. package/dist/language/flows/flow-intent.d.ts +1 -0
  193. package/dist/language/flows/flow-journey.d.ts +6 -0
  194. package/dist/language/flows/flow-journey.js +20 -0
  195. package/dist/language/flows/flow-learning.d.ts +4 -0
  196. package/dist/language/flows/flow-learning.js +37 -3
  197. package/dist/language/flows/flow-replay-run.js +16 -18
  198. package/dist/language/flows/flow-replay-types.d.ts +1 -1
  199. package/dist/language/flows/flow-replay.d.ts +18 -1
  200. package/dist/language/flows/flow-replay.js +22 -7
  201. package/dist/language/flows/flow-result.d.ts +22 -1
  202. package/dist/language/flows/flow-result.js +47 -2
  203. package/dist/language/flows/flow-step-runners.d.ts +2 -0
  204. package/dist/language/flows/flow-step-runners.js +12 -6
  205. package/dist/language/flows/flow-store-for-session.js +5 -1
  206. package/dist/language/flows/flow-tools.d.ts +10 -0
  207. package/dist/language/flows/flow-tools.js +61 -62
  208. package/dist/language/flows/flows.d.ts +16 -1
  209. package/dist/language/flows/flows.js +69 -10
  210. package/dist/language/flows/narrow-write.d.ts +7 -0
  211. package/dist/language/flows/narrow-write.js +36 -1
  212. package/dist/language/flows/recording/recording-backtrack.js +14 -1
  213. package/dist/language/flows/recording/tape/recordings.d.ts +29 -1
  214. package/dist/language/flows/recording/tape/recordings.js +60 -10
  215. package/dist/language/flows/replay.js +29 -6
  216. package/dist/language/flows/single-replay.d.ts +7 -0
  217. package/dist/language/flows/single-replay.js +26 -0
  218. package/dist/language/flows/verify-change-tools.d.ts +1 -0
  219. package/dist/language/flows/verify-change-tools.js +200 -193
  220. package/dist/memory/cloud/cloud-config.d.ts +10 -1
  221. package/dist/memory/cloud/cloud-config.js +19 -5
  222. package/dist/memory/cloud/cloud-sync.js +3 -3
  223. package/dist/memory/cloud/harness-config.d.ts +49 -0
  224. package/dist/memory/cloud/harness-config.js +16 -20
  225. package/dist/memory/cloud/memory-scope.d.ts +17 -0
  226. package/dist/memory/cloud/memory-scope.js +22 -0
  227. package/dist/memory/cloud/sync-cycle.d.ts +32 -4
  228. package/dist/memory/cloud/sync-cycle.js +113 -12
  229. package/dist/memory/cloud/sync-daemon.d.ts +11 -0
  230. package/dist/memory/cloud/sync-daemon.js +114 -4
  231. package/dist/memory/cloud/sync-disk.js +26 -2
  232. package/dist/memory/cloud/sync-screenshots.d.ts +32 -0
  233. package/dist/memory/cloud/sync-screenshots.js +105 -0
  234. package/dist/memory/cloud/unsynced-roots.d.ts +47 -0
  235. package/dist/memory/cloud/unsynced-roots.js +166 -0
  236. package/dist/memory/impact/account-watch.d.ts +16 -1
  237. package/dist/memory/impact/account-watch.js +26 -1
  238. package/dist/memory/impact/impact-store.d.ts +1 -4
  239. package/dist/memory/impact/impact-store.js +32 -42
  240. package/dist/memory/intent/inline-intent.d.ts +1 -0
  241. package/dist/memory/intent/intent-shard-store.js +16 -4
  242. package/dist/memory/intent/intent-shard.d.ts +12 -0
  243. package/dist/memory/intent/intent-store.d.ts +1 -0
  244. package/dist/memory/intent/intent-tools.js +42 -2
  245. package/dist/memory/intent/open-intents.d.ts +6 -0
  246. package/dist/memory/intent/open-intents.js +10 -1
  247. package/dist/memory/intent/settled-words.d.ts +24 -0
  248. package/dist/memory/intent/settled-words.js +130 -0
  249. package/dist/memory/journal/drive-flow.d.ts +16 -0
  250. package/dist/memory/journal/drive-flow.js +34 -2
  251. package/dist/memory/journal/drive-run-flush.d.ts +2 -0
  252. package/dist/memory/journal/drive-run-flush.js +20 -5
  253. package/dist/memory/journal/journal-recorder.js +46 -3
  254. package/dist/memory/journal/on-disk/retention.d.ts +5 -0
  255. package/dist/memory/journal/on-disk/retention.js +27 -2
  256. package/dist/memory/journal/on-disk/workspace-tiers.js +2 -0
  257. package/dist/memory/journal/session-end.d.ts +15 -1
  258. package/dist/memory/journal/session-end.js +38 -7
  259. package/dist/memory/project/artifact-root-resolver.js +33 -5
  260. package/dist/memory/project/artifact-root.d.ts +4 -3
  261. package/dist/memory/project/artifact-root.js +2 -2
  262. package/dist/memory/project/dir/reticle-dir.d.ts +14 -0
  263. package/dist/memory/project/dir/reticle-dir.js +23 -2
  264. package/dist/memory/project/link-directory.d.ts +15 -0
  265. package/dist/memory/project/link-directory.js +44 -0
  266. package/dist/memory/project/root-claims.d.ts +6 -0
  267. package/dist/memory/project/root-claims.js +39 -0
  268. package/dist/memory/project/session-root.d.ts +2 -0
  269. package/dist/memory/project/session-root.js +9 -1
  270. package/dist/memory/project/sync-status.d.ts +46 -0
  271. package/dist/memory/project/sync-status.js +135 -1
  272. package/dist/memory/recall/memory-tools.js +46 -3
  273. package/dist/memory/recall/platform-memory.d.ts +34 -0
  274. package/dist/memory/recall/platform-memory.js +139 -0
  275. package/dist/memory/recall/project-memory.js +36 -8
  276. package/dist/portal/bridge/bridge.d.ts +9 -5
  277. package/dist/portal/bridge/bridge.js +66 -23
  278. package/dist/portal/bridge/harness-request.d.ts +23 -0
  279. package/dist/portal/bridge/harness-request.js +27 -0
  280. package/dist/portal/bridge/wrong-path.d.ts +17 -0
  281. package/dist/portal/bridge/wrong-path.js +35 -0
  282. package/dist/portal/input/network-mock-tools.js +11 -2
  283. package/dist/portal/input/real-input.d.ts +2 -0
  284. package/dist/portal/input/real-input.js +13 -10
  285. package/dist/portal/pool/browser-pool.d.ts +48 -0
  286. package/dist/portal/pool/browser-pool.js +120 -2
  287. package/dist/portal/pool/context-permissions.d.ts +33 -0
  288. package/dist/portal/pool/context-permissions.js +33 -0
  289. package/dist/portal/pool/lease-marker-stamp.d.ts +23 -0
  290. package/dist/portal/pool/lease-marker-stamp.js +49 -0
  291. package/dist/portal/pool/playwright-launcher.d.ts +27 -5
  292. package/dist/portal/pool/playwright-launcher.js +59 -14
  293. package/dist/portal/pool/pool-contract.d.ts +21 -1
  294. package/dist/portal/session/agent-link.d.ts +8 -0
  295. package/dist/portal/session/agent-link.js +17 -0
  296. package/dist/portal/session/command-payload.js +6 -1
  297. package/dist/portal/session/daemon-answers.d.ts +3 -3
  298. package/dist/portal/session/daemon-answers.js +4 -2
  299. package/dist/portal/session/dev-server/dev-command.d.ts +9 -0
  300. package/dist/portal/session/dev-server/dev-command.js +27 -1
  301. package/dist/portal/session/facts/observed-state.d.ts +16 -18
  302. package/dist/portal/session/facts/observed-state.js +46 -3
  303. package/dist/portal/session/human/live-control.d.ts +9 -2
  304. package/dist/portal/session/human/live-control.js +12 -3
  305. package/dist/portal/session/live-control-tools.js +82 -13
  306. package/dist/portal/session/narration-args.d.ts +5 -0
  307. package/dist/portal/session/narration-args.js +17 -0
  308. package/dist/portal/session/no-session-diagnosis.d.ts +1 -1
  309. package/dist/portal/session/no-session-diagnosis.js +31 -9
  310. package/dist/portal/session/no-session-next-action.d.ts +20 -0
  311. package/dist/portal/session/no-session-next-action.js +134 -61
  312. package/dist/portal/session/no-session-watch.js +179 -22
  313. package/dist/portal/session/presence/session-lease.js +1 -1
  314. package/dist/portal/session/recorded-gaps.d.ts +97 -0
  315. package/dist/portal/session/recorded-gaps.js +251 -0
  316. package/dist/portal/session/session-health.d.ts +11 -0
  317. package/dist/portal/session/session-health.js +3 -1
  318. package/dist/portal/session/session-info.js +2 -1
  319. package/dist/portal/session/session-manager.d.ts +18 -0
  320. package/dist/portal/session/session-manager.js +54 -3
  321. package/dist/portal/session/session-successor.d.ts +8 -0
  322. package/dist/portal/session/session-successor.js +1 -1
  323. package/dist/portal/session/session.d.ts +15 -28
  324. package/dist/portal/session/session.js +60 -61
  325. package/dist/portal/session/timing/command-timeout.js +25 -1
  326. package/dist/portal/session/timing/wall-clock.d.ts +1 -1
  327. package/dist/portal/session/timing/wall-clock.js +1 -1
  328. package/dist/portal/session/zero-install-first-call.d.ts +31 -0
  329. package/dist/portal/session/zero-install-first-call.js +88 -0
  330. package/dist/start-options.d.ts +7 -1
  331. package/dist/start-wiring.d.ts +63 -0
  332. package/dist/start-wiring.js +169 -0
  333. package/dist/status-payload.d.ts +13 -1
  334. package/dist/status-payload.js +13 -1
  335. package/dist/surface/http-server.js +7 -1
  336. package/dist/surface/mcp/attach-memory.d.ts +1 -0
  337. package/dist/surface/mcp/attach-memory.js +12 -1
  338. package/dist/surface/mcp/mcp-proxy.d.ts +4 -0
  339. package/dist/surface/mcp/mcp-proxy.js +29 -8
  340. package/dist/surface/mcp/mcp.d.ts +23 -0
  341. package/dist/surface/mcp/mcp.js +34 -4
  342. package/dist/surface/mcp/proxy/drive-calls.d.ts +10 -0
  343. package/dist/surface/mcp/proxy/drive-calls.js +27 -0
  344. package/dist/surface/mcp/proxy/mcp-post-transport.js +5 -0
  345. package/dist/surface/mcp/server-instructions.js +22 -14
  346. package/dist/surface/mcp/shared-params.js +1 -1
  347. package/dist/surface/mcp/surface-vocabulary.d.ts +2 -0
  348. package/dist/surface/mcp/surface-vocabulary.js +1 -0
  349. package/dist/surface/tools/account-tool.d.ts +22 -0
  350. package/dist/surface/tools/account-tool.js +157 -0
  351. package/dist/surface/tools/act/act-danger.d.ts +11 -0
  352. package/dist/surface/tools/act/act-danger.js +38 -1
  353. package/dist/surface/tools/act/act-observation.d.ts +33 -0
  354. package/dist/surface/tools/act/act-observation.js +57 -0
  355. package/dist/surface/tools/act/act-preflight.js +25 -2
  356. package/dist/surface/tools/act/act-target.js +8 -3
  357. package/dist/surface/tools/act/resolve-target.d.ts +1 -1
  358. package/dist/surface/tools/act/resolve-target.js +18 -3
  359. package/dist/surface/tools/act/screen-request.d.ts +34 -0
  360. package/dist/surface/tools/act/screen-request.js +113 -0
  361. package/dist/surface/tools/act/sequence-grade.d.ts +8 -0
  362. package/dist/surface/tools/act/sequence-grade.js +13 -2
  363. package/dist/surface/tools/act-sequence-tool.js +136 -27
  364. package/dist/surface/tools/act-tools.d.ts +0 -9
  365. package/dist/surface/tools/act-tools.js +48 -34
  366. package/dist/surface/tools/agent/app-notes-learning.d.ts +7 -0
  367. package/dist/surface/tools/agent/app-notes-learning.js +80 -0
  368. package/dist/surface/tools/agent/app-notes.d.ts +4 -0
  369. package/dist/surface/tools/agent/app-notes.js +48 -0
  370. package/dist/surface/tools/agent/build-staleness.d.ts +7 -0
  371. package/dist/surface/tools/agent/build-staleness.js +62 -0
  372. package/dist/surface/tools/agent/compact-envelope.d.ts +80 -0
  373. package/dist/surface/tools/agent/compact-envelope.js +374 -0
  374. package/dist/surface/tools/agent/for-the-agent.d.ts +12 -0
  375. package/dist/surface/tools/agent/for-the-agent.js +44 -0
  376. package/dist/surface/tools/agent/notices.d.ts +50 -0
  377. package/dist/surface/tools/agent/notices.js +152 -0
  378. package/dist/surface/tools/agent/reteach.d.ts +11 -0
  379. package/dist/surface/tools/agent/reteach.js +50 -0
  380. package/dist/surface/tools/args/alias-args.d.ts +5 -0
  381. package/dist/surface/tools/args/alias-args.js +10 -0
  382. package/dist/surface/tools/args/confirm-dangerous-arg.d.ts +6 -0
  383. package/dist/surface/tools/args/confirm-dangerous-arg.js +6 -0
  384. package/dist/surface/tools/assert/assert-source.d.ts +15 -0
  385. package/dist/surface/tools/assert/assert-source.js +40 -0
  386. package/dist/surface/tools/assert/assert-verdict.js +25 -15
  387. package/dist/surface/tools/browser-tools.js +43 -1
  388. package/dist/surface/tools/chat-drives.d.ts +75 -0
  389. package/dist/surface/tools/chat-drives.js +265 -21
  390. package/dist/surface/tools/coverage-tools.d.ts +2 -10
  391. package/dist/surface/tools/coverage-tools.js +2 -53
  392. package/dist/surface/tools/dynamic-tools.js +29 -1
  393. package/dist/surface/tools/empty-session-list.d.ts +10 -0
  394. package/dist/surface/tools/empty-session-list.js +45 -5
  395. package/dist/surface/tools/error-recovery.d.ts +8 -0
  396. package/dist/surface/tools/error-recovery.js +69 -5
  397. package/dist/surface/tools/explore-tools.d.ts +23 -2
  398. package/dist/surface/tools/explore-tools.js +377 -74
  399. package/dist/surface/tools/feedback-tools.d.ts +2 -3
  400. package/dist/surface/tools/feedback-tools.js +4 -3
  401. package/dist/surface/tools/gap-novelty.d.ts +3 -1
  402. package/dist/surface/tools/gap-novelty.js +8 -1
  403. package/dist/surface/tools/harness-explore.d.ts +52 -73
  404. package/dist/surface/tools/harness-explore.js +177 -146
  405. package/dist/surface/tools/harness-gate.d.ts +88 -0
  406. package/dist/surface/tools/harness-gate.js +235 -0
  407. package/dist/surface/tools/harness-hunt.d.ts +43 -0
  408. package/dist/surface/tools/harness-hunt.js +215 -0
  409. package/dist/surface/tools/harness-plan.d.ts +5 -0
  410. package/dist/surface/tools/harness-plan.js +7 -0
  411. package/dist/surface/tools/harness-script.d.ts +22 -0
  412. package/dist/surface/tools/harness-script.js +256 -38
  413. package/dist/surface/tools/harness-tab-guard.d.ts +5 -0
  414. package/dist/surface/tools/harness-tab-guard.js +27 -0
  415. package/dist/surface/tools/harness-toolset.d.ts +8 -0
  416. package/dist/surface/tools/harness-toolset.js +27 -4
  417. package/dist/surface/tools/human-notes.d.ts +16 -0
  418. package/dist/surface/tools/human-notes.js +27 -0
  419. package/dist/surface/tools/invoke-tool.d.ts +5 -2
  420. package/dist/surface/tools/invoke-tool.js +220 -93
  421. package/dist/surface/tools/lease-hint.js +26 -5
  422. package/dist/surface/tools/lease-permissions.d.ts +33 -0
  423. package/dist/surface/tools/lease-permissions.js +73 -0
  424. package/dist/surface/tools/lease-readiness.d.ts +38 -0
  425. package/dist/surface/tools/lease-readiness.js +57 -0
  426. package/dist/surface/tools/lease-seed.d.ts +20 -0
  427. package/dist/surface/tools/lease-seed.js +45 -1
  428. package/dist/surface/tools/lease-tools.d.ts +10 -1
  429. package/dist/surface/tools/lease-tools.js +119 -69
  430. package/dist/surface/tools/live-call-text.js +2 -2
  431. package/dist/surface/tools/merge-tools.js +42 -2
  432. package/dist/surface/tools/merged-name-redirect.js +4 -0
  433. package/dist/surface/tools/next-step.d.ts +11 -1
  434. package/dist/surface/tools/next-step.js +73 -18
  435. package/dist/surface/tools/observe-tools.js +43 -5
  436. package/dist/surface/tools/platform-moment.d.ts +60 -0
  437. package/dist/surface/tools/platform-moment.js +132 -4
  438. package/dist/surface/tools/read-tools.js +65 -14
  439. package/dist/surface/tools/reticle-result.d.ts +120 -0
  440. package/dist/surface/tools/reticle-result.js +364 -0
  441. package/dist/surface/tools/tool-kit.d.ts +68 -2
  442. package/dist/surface/tools/tool-kit.js +73 -1
  443. package/dist/surface/tools/tools.js +25 -13
  444. package/dist/telemetry/account-envelope.d.ts +7 -0
  445. package/dist/telemetry/account-envelope.js +61 -0
  446. package/dist/telemetry/agent-client.d.ts +15 -0
  447. package/dist/telemetry/agent-client.js +57 -0
  448. package/dist/telemetry/app-instrumented.js +9 -2
  449. package/dist/telemetry/argument-shape.d.ts +7 -0
  450. package/dist/telemetry/argument-shape.js +86 -1
  451. package/dist/telemetry/automation-hint.d.ts +2 -3
  452. package/dist/telemetry/automation-hint.js +33 -8
  453. package/dist/telemetry/cli-telemetry.js +7 -2
  454. package/dist/telemetry/daemon-telemetry.js +3 -0
  455. package/dist/telemetry/error-fingerprint.d.ts +10 -1
  456. package/dist/telemetry/error-fingerprint.js +61 -5
  457. package/dist/telemetry/feedback-cli.d.ts +0 -13
  458. package/dist/telemetry/feedback-cli.js +0 -77
  459. package/dist/telemetry/feedback.d.ts +13 -0
  460. package/dist/telemetry/feedback.js +183 -17
  461. package/dist/telemetry/harness-run.d.ts +18 -0
  462. package/dist/telemetry/harness-run.js +80 -0
  463. package/dist/telemetry/hud-events.d.ts +16 -0
  464. package/dist/telemetry/hud-events.js +27 -0
  465. package/dist/telemetry/hud-metrics.d.ts +27 -6
  466. package/dist/telemetry/hud-metrics.js +99 -25
  467. package/dist/telemetry/init-telemetry.js +2 -2
  468. package/dist/telemetry/install-source.d.ts +13 -25
  469. package/dist/telemetry/install-source.js +44 -3
  470. package/dist/telemetry/mcp-connection.d.ts +0 -14
  471. package/dist/telemetry/mcp-connection.js +5 -1
  472. package/dist/telemetry/onboarding-firsts.d.ts +32 -0
  473. package/dist/telemetry/onboarding-firsts.js +81 -6
  474. package/dist/telemetry/platform-linked.d.ts +5 -0
  475. package/dist/telemetry/platform-linked.js +32 -0
  476. package/dist/telemetry/project-profile.js +5 -3
  477. package/dist/telemetry/proxy-telemetry.js +4 -1
  478. package/dist/telemetry/session-metrics.d.ts +15 -4
  479. package/dist/telemetry/session-metrics.js +88 -52
  480. package/dist/telemetry/telemetry.d.ts +26 -2
  481. package/dist/telemetry/telemetry.js +102 -21
  482. package/dist/telemetry/verification-of.d.ts +3 -1
  483. package/dist/telemetry/verification-of.js +12 -4
  484. package/dist/wire-journal.d.ts +2 -2
  485. package/dist/wire-journal.js +16 -5
  486. package/docs/README.md +2 -2
  487. package/docs/actions.mdx +6 -3
  488. package/docs/adapters.md +1 -1
  489. package/docs/agent-cheatsheet.md +24 -27
  490. package/docs/architecture.md +5 -5
  491. package/docs/autodrive.md +13 -12
  492. package/docs/benchmarks.md +5 -5
  493. package/docs/best-practices.mdx +27 -9
  494. package/docs/capabilities.mdx +14 -16
  495. package/docs/cli/affected.mdx +1 -1
  496. package/docs/cli/cloud.mdx +54 -75
  497. package/docs/cli/doctor.mdx +8 -3
  498. package/docs/cli/drive.mdx +12 -12
  499. package/docs/cli/feedback.mdx +3 -3
  500. package/docs/cli/gate.mdx +7 -2
  501. package/docs/cli/help.mdx +50 -55
  502. package/docs/cli/init.mdx +32 -15
  503. package/docs/cli/license.mdx +12 -8
  504. package/docs/cli/mcp.mdx +4 -4
  505. package/docs/cli/open.mdx +3 -3
  506. package/docs/cli/report.mdx +4 -2
  507. package/docs/cli/restart.mdx +2 -2
  508. package/docs/cli/rollback.mdx +14 -8
  509. package/docs/cli/serve.mdx +4 -4
  510. package/docs/cli/setup.mdx +23 -0
  511. package/docs/cli/status.mdx +13 -4
  512. package/docs/cli/stop.mdx +67 -8
  513. package/docs/cli/try.mdx +62 -0
  514. package/docs/cli/update.mdx +4 -3
  515. package/docs/cli/verify.mdx +17 -6
  516. package/docs/cli/version.mdx +3 -2
  517. package/docs/cli.mdx +110 -41
  518. package/docs/debugging.md +3 -3
  519. package/docs/deploy-checks.md +1 -1
  520. package/docs/desktop.mdx +5 -3
  521. package/docs/enterprise.md +11 -10
  522. package/docs/environment.mdx +5 -2
  523. package/docs/faq.mdx +3 -3
  524. package/docs/first-drive.md +5 -4
  525. package/docs/flows.md +35 -27
  526. package/docs/for-agents.md +4 -5
  527. package/docs/frameworks.mdx +32 -21
  528. package/docs/getting-started.md +66 -64
  529. package/docs/human-control.md +15 -14
  530. package/docs/index.mdx +15 -43
  531. package/docs/install-agentic.mdx +22 -37
  532. package/docs/install-manual.mdx +22 -12
  533. package/docs/instrumentation.mdx +5 -3
  534. package/docs/integration-patterns.md +7 -7
  535. package/docs/license-activation.mdx +1 -1
  536. package/docs/multi-agent-testing.md +24 -6
  537. package/docs/packages/babel-plugin.mdx +4 -2
  538. package/docs/packages/browser.mdx +16 -10
  539. package/docs/packages/core.mdx +12 -5
  540. package/docs/packages/electron.mdx +3 -2
  541. package/docs/packages/engine.mdx +1 -1
  542. package/docs/packages/eslint-plugin.mdx +18 -12
  543. package/docs/packages/next.mdx +8 -7
  544. package/docs/packages/open-verification.mdx +1 -1
  545. package/docs/packages/server.mdx +5 -5
  546. package/docs/packages/tauri.mdx +8 -8
  547. package/docs/packages/test.mdx +4 -10
  548. package/docs/packages/vite-plugin.mdx +11 -4
  549. package/docs/packages.mdx +11 -15
  550. package/docs/platform-integration.md +6 -5
  551. package/docs/predicates.mdx +40 -24
  552. package/docs/quickstart.mdx +58 -29
  553. package/docs/required-checks.md +8 -7
  554. package/docs/reticle-manual.md +15 -5
  555. package/docs/skill-file.mdx +17 -79
  556. package/docs/skill-setup.md +9 -9
  557. package/docs/skill.md +200 -0
  558. package/docs/system-map.md +7 -7
  559. package/docs/telemetry.md +22 -24
  560. package/docs/testing.md +4 -4
  561. package/docs/token-efficiency.md +12 -12
  562. package/docs/tools/act-and-wait.mdx +9 -2
  563. package/docs/tools/act-sequence.mdx +6 -7
  564. package/docs/tools/act.mdx +2 -2
  565. package/docs/tools/assert.mdx +18 -17
  566. package/docs/tools/console.mdx +9 -9
  567. package/docs/tools/inspect.mdx +16 -7
  568. package/docs/tools/look.mdx +24 -0
  569. package/docs/tools/navigate.mdx +5 -3
  570. package/docs/tools/network.mdx +2 -2
  571. package/docs/tools/observe.mdx +6 -6
  572. package/docs/tools/overview.mdx +34 -33
  573. package/docs/tools/query.mdx +5 -5
  574. package/docs/tools/session-and-feedback.mdx +13 -4
  575. package/docs/tools/sessions.mdx +8 -8
  576. package/docs/tools/snapshot.mdx +6 -5
  577. package/docs/tools/state.mdx +9 -9
  578. package/docs/tools/tools-and-run.mdx +26 -24
  579. package/docs/tools/verify.mdx +28 -0
  580. package/docs/tools/wait-for.mdx +8 -4
  581. package/docs/troubleshooting.mdx +21 -6
  582. package/docs/usage.md +51 -45
  583. package/docs/use-cases.mdx +3 -3
  584. package/docs/vs-chrome-devtools-mcp.mdx +9 -6
  585. package/docs/vs-playwright-mcp.mdx +3 -1
  586. package/docs/what-is-recorded.md +7 -5
  587. package/docs/what-is-reticle.mdx +2 -2
  588. package/docs/why-reticle.mdx +1 -1
  589. package/package.json +6 -5
  590. package/dist/command/cli/demo/demo-page.d.ts +0 -78
  591. package/dist/command/cli/demo/demo-page.js +0 -249
  592. package/dist/command/cli/demo/demo-run.d.ts +0 -38
  593. package/dist/command/cli/demo/demo-run.js +0 -138
  594. package/dist/judgement/hunt/hunt-report.d.ts +0 -49
  595. package/dist/judgement/hunt/hunt-report.js +0 -84
  596. package/dist/language/flows/buddy-status.d.ts +0 -25
  597. package/dist/language/flows/buddy-status.js +0 -32
  598. package/dist/language/flows/change/watch-batcher.d.ts +0 -18
  599. package/dist/language/flows/change/watch-batcher.js +0 -19
  600. package/dist/memory/cloud/harness-offer.d.ts +0 -44
  601. package/dist/memory/cloud/harness-offer.js +0 -145
  602. package/dist/surface/tools/feature-capture.d.ts +0 -151
  603. package/dist/surface/tools/feature-capture.js +0 -235
  604. package/dist/surface/tools/tool-hit-rate.d.ts +0 -87
  605. package/dist/surface/tools/tool-hit-rate.js +0 -90
  606. package/dist/telemetry/harness-drive.d.ts +0 -4
  607. package/dist/telemetry/harness-drive.js +0 -35
  608. package/dist/telemetry/identify.d.ts +0 -22
  609. package/dist/telemetry/identify.js +0 -73
  610. package/docs/cli/capsules.mdx +0 -70
  611. package/docs/cli/hunt.mdx +0 -73
  612. package/docs/cli/identify.mdx +0 -76
  613. package/docs/cli/kill.mdx +0 -91
  614. package/docs/cli/watch.mdx +0 -62
@@ -87,6 +87,13 @@ export function driveFlowName(route, steps = []) {
87
87
  const parts = [lead, ...('' !== why || 0 === leg.length ? [] : [leg])].filter((p) => '' !== p);
88
88
  return asFlowName(`${DRIVE_FLOW_PREFIX}${0 === parts.length ? 'journey' : parts.join('-')}`);
89
89
  }
90
+ /** Why session end saved no flow, as reported on the `flow_recorded` step. */
91
+ export const DriveFlowSkipReason = {
92
+ /** The drive declared no consequence, so a saved flow could never go red. */
93
+ NO_DECLARED_CONSEQUENCE: 'no_declared_consequence',
94
+ /** What it proved rested on network mocks the flow would not carry (#1459). */
95
+ DRIVEN_UNDER_NETWORK_MOCKS: 'driven_under_network_mocks',
96
+ };
90
97
  /**
91
98
  * Turn this session's ambient tape into a flow, when it is worth one.
92
99
  *
@@ -143,24 +150,48 @@ function typesASecret(step) {
143
150
  const field = QueryBy.TESTID === by ? step.args['value'] : step.args['name'];
144
151
  return 'string' === typeof field && defaultIsSensitiveKey(field);
145
152
  }
153
+ /**
154
+ * The tape without the attempts an agent made again. An action that declared nothing, followed at
155
+ * once by the same action on the same control that did, is a retry: saved, the flow clicked sign-in
156
+ * twice, the second click found no form, and every replay of it failed. A step repeated with the
157
+ * same declaration both times (a double submit on purpose) is two steps, and stays two.
158
+ */
159
+ function withoutRetries(steps) {
160
+ return steps.filter((step, i) => {
161
+ const next = steps[i + 1];
162
+ if (next === undefined || step.expect !== undefined || next.expect === undefined)
163
+ return true;
164
+ // Tried again where it was tried: a page later it is a step of the journey, not a retry.
165
+ if (step.route !== next.route || step.page !== next.page || step.mocked !== next.mocked)
166
+ return true;
167
+ const { expect: _declared, ...again } = next.args;
168
+ const { expect: _none, ...first } = step.args;
169
+ return step.tool !== next.tool || JSON.stringify(first) !== JSON.stringify(again);
170
+ });
171
+ }
146
172
  /** A flow per journey, so two journeys in one session are two regression tests rather than one. */
147
173
  export function driveFlowsFrom(tape) {
148
174
  if (tape === undefined || 0 === tape.steps.length)
149
175
  return { programs: [], outcome: {} };
150
176
  const programs = [];
151
177
  let unproven = 0;
152
- for (const segment of segmentsByRoute(tape.steps)) {
178
+ let mocked = 0;
179
+ for (const segment of segmentsByRoute(withoutRetries(tape.steps))) {
153
180
  if (!carriesAnAssertion(segment.steps)) {
154
181
  unproven += segment.steps.length;
155
182
  continue;
156
183
  }
184
+ if (segment.steps.some((step) => true === step.mocked)) {
185
+ mocked += segment.steps.length;
186
+ continue;
187
+ }
157
188
  const startPath = segment.route ?? tape.startPath;
158
189
  programs.push({
159
190
  name: driveFlowName(startPath, segment.steps),
160
191
  version: REPLAY_PROGRAM_VERSION,
161
192
  // The route is recorder-internal and has no business on disk — `startPath` is where the
162
193
  // on-disk flow says the same thing, in the field replay actually reads.
163
- steps: segment.steps.map(({ route: _route, ...step }) => step),
194
+ steps: segment.steps.map(({ route: _route, mocked: _mocked, ...step }) => step),
164
195
  ...(startPath === undefined ? {} : { startPath }),
165
196
  });
166
197
  }
@@ -169,6 +200,7 @@ export function driveFlowsFrom(tape) {
169
200
  outcome: {
170
201
  ...(0 === programs.length ? {} : { saved: programs.map((p) => p.name) }),
171
202
  ...(0 === unproven ? {} : { unprovenSteps: unproven }),
203
+ ...(0 === mocked ? {} : { mockedSteps: mocked }),
172
204
  },
173
205
  };
174
206
  }
@@ -26,6 +26,8 @@ export interface FlushableSession {
26
26
  export interface DriveRunFlushDeps<S extends FlushableSession> {
27
27
  /** Find the live session a verdict came from, or undefined when it has already gone. */
28
28
  resolve: (sessionId: string) => S | undefined;
29
+ /** Every live session: a Harness note rewrites them all, since it does not say which tab drove. */
30
+ all?: () => readonly S[];
29
31
  /** Write this session's run artifact. The same call teardown makes. */
30
32
  write: (session: S) => Promise<void>;
31
33
  /** Injected so the test does not wait three real seconds. */
@@ -20,8 +20,9 @@
20
20
  * the last verdicts of a session are the ones somebody is waiting on.
21
21
  */
22
22
  import { HookEvent } from '@reticlehq/core/hooks';
23
- import { onHook } from '../../hooks/hook-bus.js';
23
+ import { onHook, onPendingWrites } from '../../hooks/hook-bus.js';
24
24
  import { log } from '../../log.js';
25
+ import { onHarnessNoted } from '../../judgement/runs/drive-run.js';
25
26
  /** Long enough to collapse a burst of verdicts, short enough that a human reads it as "live". */
26
27
  const FLUSH_DEBOUNCE_MS = 3_000;
27
28
  /**
@@ -55,10 +56,7 @@ export function attachDriveRunFlush(deps) {
55
56
  log(`drive run flush failed: ${message}`);
56
57
  }
57
58
  };
58
- const off = onHook(HookEvent.VERDICT, (payload) => {
59
- const sessionId = 'sessionId' in payload ? payload.sessionId : undefined;
60
- if (sessionId === undefined)
61
- return;
59
+ const schedule = (sessionId) => {
62
60
  const existing = pending.get(sessionId);
63
61
  if (existing !== undefined)
64
62
  clearTimeout(existing);
@@ -69,11 +67,28 @@ export function attachDriveRunFlush(deps) {
69
67
  // obligation, and holding the process open for it would make `reticle` feel broken on exit.
70
68
  timer.unref?.();
71
69
  pending.set(sessionId, timer);
70
+ };
71
+ const off = onHook(HookEvent.VERDICT, (payload) => {
72
+ const sessionId = 'sessionId' in payload ? payload.sessionId : undefined;
73
+ if (sessionId !== undefined)
74
+ schedule(sessionId);
72
75
  });
76
+ // A Harness drive is graded after its last verdict: rewrite the runs so the grade reaches them.
77
+ // ponytail: every live session, since a note names a drive, not a tab; drives end rarely.
78
+ const offNoted = onHarnessNoted(() => {
79
+ for (const session of deps.all?.() ?? [])
80
+ schedule(session.id);
81
+ });
82
+ const flushAll = async () => {
83
+ await Promise.all([...pending.keys()].map(flush));
84
+ };
85
+ const unregister = onPendingWrites(flushAll);
73
86
  return {
74
87
  flushNow: flush,
75
88
  stop: () => {
76
89
  off();
90
+ unregister();
91
+ offNoted();
77
92
  for (const timer of pending.values())
78
93
  clearTimeout(timer);
79
94
  pending.clear();
@@ -1,6 +1,7 @@
1
- import { EventAttribution, JOURNAL_FILE_VERSION, } from '@reticlehq/core';
1
+ import { ActionType, EventAttribution, REDACTED_VALUE, JOURNAL_FILE_VERSION, } from '@reticlehq/core';
2
2
  import { isSelfObservation } from './self-observation.js';
3
3
  import { currentDrivenBy } from '../../hooks/driven-by.js';
4
+ import { callerClientName } from '../../hooks/coding-agents.js';
4
5
  import { log } from '../../log.js';
5
6
  /**
6
7
  * Logged the FIRST time a journal write is lost, and never again for that recorder.
@@ -13,6 +14,36 @@ import { log } from '../../log.js';
13
14
  * produce one line per batch.
14
15
  */
15
16
  const JOURNAL_WRITE_LOST = 'journal_write_lost';
17
+ /** The actions whose arguments carry what was typed. */
18
+ const TYPING_ACTIONS = new Set([ActionType.FILL, ActionType.TYPE]);
19
+ const TYPED_KEYS = ['value', 'text'];
20
+ /**
21
+ * The arguments as the journal keeps them: every typed value replaced, at any depth (a sequence's
22
+ * steps too). A fill's arguments kept a typed password in clear in `actions.jsonl`; the page cannot
23
+ * tell the daemon which field was secret before the call is written, and nothing reads a typed value
24
+ * back from the journal — the run fold reads the ref, the target and the action — so none is kept.
25
+ */
26
+ function withoutTypedInput(args) {
27
+ if (Array.isArray(args))
28
+ return args.map(withoutTypedInput);
29
+ if ('object' !== typeof args || null === args)
30
+ return args;
31
+ const record = args;
32
+ const typing = TYPING_ACTIONS.has(String(record['action']));
33
+ const out = {};
34
+ for (const [key, value] of Object.entries(record)) {
35
+ const typed = typing && TYPED_KEYS.includes(key) && 'string' === typeof value;
36
+ out[key] = typed ? REDACTED_VALUE : withoutTypedInput(value);
37
+ }
38
+ if (typing && 'object' === typeof record['args'] && null !== record['args']) {
39
+ const inner = { ...record['args'] };
40
+ for (const key of TYPED_KEYS)
41
+ if ('string' === typeof inner[key])
42
+ inner[key] = REDACTED_VALUE;
43
+ out['args'] = inner;
44
+ }
45
+ return out;
46
+ }
16
47
  const DEFAULT_FLUSH_AT = 64;
17
48
  /**
18
49
  * Owns action-window attribution and batched, order-preserving journaling — the durable half of the
@@ -66,7 +97,14 @@ export class JournalRecorder {
66
97
  }
67
98
  /** Open an attribution window. One action is active at a time (the agent drives sequentially). */
68
99
  beginAction(actionId, tool, args) {
69
- this.#active = { actionId, tool, args, tStart: this.#now(), drivenBy: currentDrivenBy() };
100
+ this.#active = {
101
+ actionId,
102
+ tool,
103
+ args: withoutTypedInput(args),
104
+ tStart: this.#now(),
105
+ drivenBy: currentDrivenBy(),
106
+ agent: callerClientName(),
107
+ };
70
108
  }
71
109
  /** Close the active window: persist its buffered events, then the action record. No-op if none active. */
72
110
  finishAction(effect, settled, settledInMs) {
@@ -88,6 +126,7 @@ export class JournalRecorder {
88
126
  tRange: { from: active.tStart, to: this.#now() },
89
127
  at: active.tStart,
90
128
  ...(active.drivenBy === undefined ? {} : { drivenBy: active.drivenBy }),
129
+ ...(active.agent === undefined ? {} : { agent: active.agent }),
91
130
  };
92
131
  this.#enqueueFlush();
93
132
  this.#chain = this.#chain
@@ -117,7 +156,7 @@ export class JournalRecorder {
117
156
  v: JOURNAL_FILE_VERSION,
118
157
  actionId,
119
158
  tool,
120
- args,
159
+ args: withoutTypedInput(args),
121
160
  effect,
122
161
  // No window was opened, so no event carries this id: `seqRange` is omitted rather than
123
162
  // invented, because any range here would claim an attribution that never happened. `tRange` is
@@ -129,6 +168,10 @@ export class JournalRecorder {
129
168
  const by = currentDrivenBy();
130
169
  return by === undefined ? {} : { drivenBy: by };
131
170
  })(),
171
+ ...(() => {
172
+ const agent = callerClientName();
173
+ return agent === undefined ? {} : { agent };
174
+ })(),
132
175
  };
133
176
  this.#enqueueFlush();
134
177
  this.#chain = this.#chain
@@ -13,6 +13,11 @@ export declare const DEFAULT_DIFF_RETENTION = 10;
13
13
  export declare const DEFAULT_FEEDBACK_RETENTION = 20;
14
14
  /** Harness plans kept on disk: the platform keeps every one, this machine the recent ones. */
15
15
  export declare const DEFAULT_PLAN_RETENTION = 50;
16
+ /**
17
+ * Keep the step screenshots of at most this many runs. A run's are looked at while it is fresh, or
18
+ * on the platform it was synced to; the byte budget below is what bounds a single long one.
19
+ */
20
+ export declare const DEFAULT_STEP_SHOT_RUN_RETENTION = 50;
16
21
  /**
17
22
  * The TOTAL the evidence tier of one `.reticle/` may occupy, across sessions, runs, overlay diffs
18
23
  * and feedback copies together.
@@ -1,5 +1,5 @@
1
1
  import { basename, join } from 'node:path';
2
- import { ReticleDir } from '@reticlehq/core';
2
+ import { ReticleDir, STEP_SCREENSHOT } from '@reticlehq/core';
3
3
  import { reticleDirPaths, visualDir } from '../../project/dir/reticle-dir.js';
4
4
  import { EVIDENCE_DIRS } from './workspace-tiers.js';
5
5
  /** Keep at most this many session journals on disk; older ones are pruned by recency. */
@@ -16,6 +16,11 @@ export const DEFAULT_DIFF_RETENTION = 10;
16
16
  export const DEFAULT_FEEDBACK_RETENTION = 20;
17
17
  /** Harness plans kept on disk: the platform keeps every one, this machine the recent ones. */
18
18
  export const DEFAULT_PLAN_RETENTION = 50;
19
+ /**
20
+ * Keep the step screenshots of at most this many runs. A run's are looked at while it is fresh, or
21
+ * on the platform it was synced to; the byte budget below is what bounds a single long one.
22
+ */
23
+ export const DEFAULT_STEP_SHOT_RUN_RETENTION = 50;
19
24
  const DIFF_SUFFIX = '.diff.png';
20
25
  const PNG_SUFFIX = '.png';
21
26
  /**
@@ -173,7 +178,12 @@ export async function pruneVisualDiffs(fs, root, retention = DEFAULT_DIFF_RETENT
173
178
  for (const dir of await visualDirs(fs, root)) {
174
179
  await pruneByRecency(fs, dir, retention, isDiff);
175
180
  }
181
+ // Step screenshots are evidence of one run each, read in the minutes after it or synced with it;
182
+ // the oldest runs' directories go whole.
183
+ await pruneByRecency(fs, stepShotRunsDir(root), DEFAULT_STEP_SHOT_RUN_RETENTION);
176
184
  }
185
+ /** `visual/runs/`, the parent of every run's step screenshots. */
186
+ const stepShotRunsDir = (root) => join(root, ReticleDir.VISUAL_SUBDIR, STEP_SCREENSHOT.RUNS_DIR);
177
187
  /** `visual/` plus its per-runtime subdirectories. Empty when nothing has been captured yet. */
178
188
  async function visualDirs(fs, root) {
179
189
  const top = visualDir(root);
@@ -181,7 +191,8 @@ async function visualDirs(fs, root) {
181
191
  try {
182
192
  // A per-runtime subdirectory is anything here that is not itself a PNG.
183
193
  for (const entry of await fs.readdir(top)) {
184
- if (!entry.endsWith(PNG_SUFFIX))
194
+ // `runs/` holds step screenshots, not baselines, and is bounded on its own.
195
+ if (!entry.endsWith(PNG_SUFFIX) && STEP_SCREENSHOT.RUNS_DIR !== entry)
185
196
  dirs.push(join(top, entry));
186
197
  }
187
198
  }
@@ -230,6 +241,20 @@ async function collectEvidence(fs, root) {
230
241
  for (const under of EVIDENCE_DIRS) {
231
242
  const isVisual = ReticleDir.VISUAL_SUBDIR === under;
232
243
  const dirs = isVisual ? await visualDirs(fs, root) : [join(root, under)];
244
+ // Each run's step screenshots are one evidence entry, evicted whole, oldest first.
245
+ if (isVisual) {
246
+ const runs = stepShotRunsDir(root);
247
+ for (const name of await fs.readdir(runs).catch(() => [])) {
248
+ const path = join(runs, name);
249
+ try {
250
+ const { mtimeMs } = await fs.stat(path);
251
+ entries.push({ path, under, sizeBytes: await sizeOf(fs, path), mtimeMs });
252
+ }
253
+ catch {
254
+ /* unreadable entry — skip */
255
+ }
256
+ }
257
+ }
233
258
  for (const dir of dirs) {
234
259
  let names;
235
260
  try {
@@ -56,6 +56,8 @@ export const WORKSPACE_TIERS = {
56
56
  [ReticleDir.TIERS_FILE]: localFile,
57
57
  // The user's request in their own words: it leaves only with `shareRequests`, never by git push.
58
58
  [ReticleDir.REQUEST_FILE]: localFile,
59
+ // How to target this app, learned from corrected failures: a teammate's agent needs it too.
60
+ [ReticleDir.APP_NOTES_FILE]: sharedFile,
59
61
  [ReticleDir.PLATFORM_MOMENTS_FILE]: localFile,
60
62
  // Notes a human pinned in the HUD: free text about the page. The team reads them on the platform.
61
63
  [ReticleDir.NOTES_FILE]: localFile,
@@ -62,7 +62,8 @@ interface SessionEndDeps {
62
62
  * directories into `language/flows/recording/tape/` for a constant and two types was a dependency
63
63
  * nobody needed. `directory-reach.test.ts` refused it, and its advice was right.
64
64
  */
65
- takeAmbientTape?: () => {
65
+ /** The tape of THIS tab's steps: another app's drive is never saved into this project. */
66
+ takeAmbientTape?: (sessionId: string) => {
66
67
  steps: readonly TapeStep[];
67
68
  startPath?: string;
68
69
  } | undefined;
@@ -120,4 +121,17 @@ export declare function makeSessionEnd(deps: SessionEndDeps): (session: SessionE
120
121
  * dashboard.
121
122
  */
122
123
  export declare function recordDriveRun(deps: SessionEndDeps, session: SessionEndTarget): Promise<void>;
124
+ /**
125
+ * Tear down every session still open, and wait for it.
126
+ *
127
+ * Teardown is what saves a tab's driven flow and its last run, and it normally fires from the
128
+ * socket's close handler, unawaited. A daemon closing with tabs open terminated those sockets after
129
+ * its final sync, so the flow was written too late to be sent, or not at all once the process
130
+ * exited. Called before that final sync instead: each session leaves the registry first, so the
131
+ * socket closing later finds nothing to tear down a second time.
132
+ */
133
+ export declare function endLiveSessions<S>(sessions: {
134
+ all: () => S[];
135
+ remove: (session: S) => boolean;
136
+ }, teardown: (session: S) => Promise<void>): Promise<void>;
123
137
  export {};
@@ -1,4 +1,5 @@
1
- import { DRIVE_FLOW_PREFIX, driveFlowsFrom, } from './drive-flow.js';
1
+ import { DRIVE_FLOW_PREFIX, DriveFlowSkipReason, driveFlowsFrom, } from './drive-flow.js';
2
+ import { basename, dirname } from 'node:path';
2
3
  import { log } from '../../log.js';
3
4
  /** What a session's teardown writes to the daemon log when a by-product fails to save. */
4
5
  const SESSION_END_LOG = {
@@ -7,13 +8,13 @@ const SESSION_END_LOG = {
7
8
  };
8
9
  const errorMessage = (error) => error instanceof Error ? error.message : String(error);
9
10
  import { journalActionsPath, sessionDirPath } from '../project/dir/reticle-dir.js';
10
- import { asSessionId } from '@reticlehq/core';
11
+ import { asRunId, asSessionId } from '@reticlehq/core';
11
12
  import { OnboardingPhase, OnboardingStepStatus, } from '@reticlehq/core/telemetry';
12
13
  import { AmbientStore } from './ambient-store.js';
13
14
  import { subjectOf } from '@reticlehq/core';
14
15
  import { pruneWorkspace } from './on-disk/startup-maintenance.js';
15
16
  import { buildVerificationRun } from '../../judgement/runs/artifact/build-verification-run.js';
16
- import { driveRunId, driveRunsFrom } from '../../judgement/runs/drive-run.js';
17
+ import { agentEndClaims, agentEndCounts, driveRunId, driveRunsFrom, } from '../../judgement/runs/drive-run.js';
17
18
  import { RunStore } from '../../judgement/runs/artifact/run-store.js';
18
19
  /**
19
20
  * Build the teardown handler the bridge fires when a session is removed. Flushes the journal first (so no
@@ -173,6 +174,7 @@ export async function recordDriveRun(deps, session) {
173
174
  if (session.readJournalActions === undefined)
174
175
  return;
175
176
  const actions = await session.readJournalActions();
177
+ const root = session.artifactRoot ?? deps.reticleRoot;
176
178
  const inputs = driveRunsFrom(actions, {
177
179
  // Derived from the session, NOT random. Teardown fires on every socket close, and a reconnecting
178
180
  // tab keeps its session id and appends to the same ledger — so a drive across two page reloads
@@ -180,7 +182,11 @@ export async function recordDriveRun(deps, session) {
180
182
  // the first. A stable id makes the artifact idempotent: the same session rewrites its own run,
181
183
  // and the cloud diffs by runId, so a re-push supersedes rather than duplicates.
182
184
  runId: driveRunId(session.id),
185
+ // Each agent session in the tab, as `reticle_session {end}` cut them, is its own run.
186
+ agentEnds: agentEndCounts(session.id),
187
+ agentClaims: agentEndClaims(session.id),
183
188
  ...(session.projectId === undefined ? {} : { projectId: session.projectId }),
189
+ projectName: basename(dirname(root)),
184
190
  });
185
191
  if (0 === inputs.length)
186
192
  return;
@@ -196,13 +202,18 @@ export async function recordDriveRun(deps, session) {
196
202
  currentDocumentId: session.currentDocumentId,
197
203
  currentEditEpoch: session.currentEditEpoch,
198
204
  });
199
- const store = new RunStore(deps.fs, session.artifactRoot ?? deps.reticleRoot,
205
+ const store = new RunStore(deps.fs, root,
200
206
  // Tell the sync daemon a run landed, rather than letting it find out on its next tick. Teardown
201
207
  // used to write this artifact with no callback at all, so the evidence from a whole session sat
202
208
  // on disk until a timer noticed — which, for the last session of the day, could be never.
203
209
  deps.onRunPersisted === undefined ? undefined : { onWrote: deps.onRunPersisted });
210
+ const clock = deps.now ?? (() => Date.now());
204
211
  for (const input of inputs) {
205
- await store.write(buildVerificationRun({ ...input, ...(subject === undefined ? {} : { subject }) }, deps.now ?? (() => Date.now())));
212
+ // A reload rewrites this session's run under the same id; the first stamp is when it ran, and
213
+ // restamping on every rewrite showed the tab's close as the drive's time.
214
+ const held = await store.read(asRunId(input.runId));
215
+ const firstAt = held.ok ? held.run.createdAt : undefined;
216
+ await store.write(buildVerificationRun({ ...input, ...(subject === undefined ? {} : { subject }) }, firstAt === undefined ? clock : () => firstAt));
206
217
  }
207
218
  }
208
219
  /** Persist what this session drove as a replayable flow, when it declared anything provable. */
@@ -212,7 +223,7 @@ async function saveDrivenFlow(deps, session) {
212
223
  if (takeTape === undefined || flows === undefined)
213
224
  return;
214
225
  // One flow per journey the session contained, not one flow per session — see drive-flow.ts.
215
- const { programs, outcome } = driveFlowsFrom(takeTape());
226
+ const { programs, outcome } = driveFlowsFrom(takeTape(session.id));
216
227
  const author = deps.author?.();
217
228
  for (const program of programs) {
218
229
  // Folded into any saved copy of the same journey, so each session does not add a near-copy.
@@ -228,7 +239,13 @@ async function saveDrivenFlow(deps, session) {
228
239
  phase: OnboardingPhase.FIRST_RUN,
229
240
  step: 'flow_recorded',
230
241
  status: 0 === programs.length ? OnboardingStepStatus.SKIPPED : OnboardingStepStatus.COMPLETED,
231
- ...(outcome.unprovenSteps === undefined ? {} : { reason: 'no_declared_consequence' }),
242
+ ...(outcome.unprovenSteps === undefined
243
+ ? {}
244
+ : { reason: DriveFlowSkipReason.NO_DECLARED_CONSEQUENCE }),
245
+ // Not saved, with the reason: proved against mocked responses, which replay cannot see (#1459).
246
+ ...(0 < programs.length || outcome.mockedSteps === undefined
247
+ ? {}
248
+ : { reason: DriveFlowSkipReason.DRIVEN_UNDER_NETWORK_MOCKS }),
232
249
  });
233
250
  }
234
251
  /**
@@ -249,3 +266,17 @@ async function dropUndrivenJournal(fs, root, sessionId) {
249
266
  // tidying is never the reason a teardown fails
250
267
  }
251
268
  }
269
+ /**
270
+ * Tear down every session still open, and wait for it.
271
+ *
272
+ * Teardown is what saves a tab's driven flow and its last run, and it normally fires from the
273
+ * socket's close handler, unawaited. A daemon closing with tabs open terminated those sockets after
274
+ * its final sync, so the flow was written too late to be sent, or not at all once the process
275
+ * exited. Called before that final sync instead: each session leaves the registry first, so the
276
+ * socket closing later finds nothing to tear down a second time.
277
+ */
278
+ export async function endLiveSessions(sessions, teardown) {
279
+ const ending = sessions.all().filter((session) => sessions.remove(session));
280
+ // Teardown is best-effort everywhere it runs: one that fails must not stop the others.
281
+ await Promise.all(ending.map((session) => teardown(session).catch(() => undefined)));
282
+ }
@@ -18,7 +18,7 @@
18
18
  * what lets a test say "these are the projects on this machine" without one.
19
19
  */
20
20
  import { existsSync, readFileSync, readdirSync } from 'node:fs';
21
- import { dirname, join } from 'node:path';
21
+ import { dirname, isAbsolute, join, relative } from 'node:path';
22
22
  import { homedir } from 'node:os';
23
23
  import { PROJECT_REGISTRY_FILE, emptyProjectRegistry, parseProjectRegistry, } from '@reticlehq/core/artifacts';
24
24
  import { DevServerEntrySchema, ReticleDir, ReticleEnv, devServerRegistryPort, } from '@reticlehq/core';
@@ -60,7 +60,19 @@ function daemonSitsInAProject(daemonRoot) {
60
60
  */
61
61
  function daemonOwnProjectId(daemonRoot, candidates) {
62
62
  const directory = dirname(daemonRoot);
63
- return candidates.find((candidate) => candidate.directory === directory)?.projectId;
63
+ const own = candidates.find((candidate) => candidate.directory === directory)?.projectId;
64
+ // A monorepo root carries the id of the app `init --app` wired, as a pointer for an agent started
65
+ // there. When an app inside the root declares the same id, the root is pointing at it, not being
66
+ // it: answering with the id handed a second checkout's flows to this root with no warning.
67
+ const pointer = candidates.some((candidate) => candidate.projectId === own &&
68
+ candidate.directory !== directory &&
69
+ isInside(directory, candidate.directory));
70
+ return pointer ? undefined : own;
71
+ }
72
+ /** Is `dir` strictly inside `root`? */
73
+ function isInside(root, dir) {
74
+ const rel = relative(root, dir);
75
+ return 0 < rel.length && !rel.startsWith('..') && !isAbsolute(rel);
64
76
  }
65
77
  /**
66
78
  * Every project this machine knows the directory of: discovered `.reticle.json` files first, then
@@ -150,6 +162,16 @@ export function artifactRootResolver(daemonRoot, deps) {
150
162
  const daemonProjectId = daemonIsProject
151
163
  ? daemonOwnProjectId(daemonRoot, candidates())
152
164
  : undefined;
165
+ /** Said once per project, reason and folder: one session's calls repeated it 16 times in 3s. */
166
+ const told = new Set();
167
+ // ponytail: asked once per origin (it shells out); a dev server moved to another folder on the
168
+ // same port is named stale until the daemon restarts.
169
+ const servedFrom = new Map();
170
+ const servingOf = (origin) => {
171
+ if (!servedFrom.has(origin))
172
+ servedFrom.set(origin, servingDirectory(origin));
173
+ return servedFrom.get(origin);
174
+ };
153
175
  return (projectId, origin) => {
154
176
  const known = candidates();
155
177
  let resolved = resolveArtifactRoot({ projectId, candidates: known, daemonRoot });
@@ -161,7 +183,7 @@ export function artifactRootResolver(daemonRoot, deps) {
161
183
  projectId,
162
184
  candidates: known,
163
185
  daemonRoot,
164
- servingDirectory: servingDirectory(origin),
186
+ servingDirectory: servingOf(origin),
165
187
  });
166
188
  }
167
189
  if (resolved.reason === ArtifactRootReason.MATCHED_PROJECT)
@@ -182,7 +204,9 @@ export function artifactRootResolver(daemonRoot, deps) {
182
204
  // app's learned expectations become another's.
183
205
  ...(origin === undefined ? {} : { origin }),
184
206
  });
185
- if (root !== daemonRoot) {
207
+ const key = `${projectId ?? ''}\u0000${resolved.reason}\u0000${root}`;
208
+ if (root !== daemonRoot && !told.has(key)) {
209
+ told.add(key);
186
210
  log('artifact_root_unmatched', {
187
211
  projectId: projectId ?? null,
188
212
  // The other half of the comparison, so a reader can tell "the daemon was a guest" from
@@ -190,8 +214,12 @@ export function artifactRootResolver(daemonRoot, deps) {
190
214
  daemonProjectId: daemonProjectId ?? null,
191
215
  reason: resolved.reason,
192
216
  root,
217
+ ...(resolved.candidates === undefined ? {} : { candidates: resolved.candidates }),
193
218
  });
194
219
  }
195
- return { ...resolved, root };
220
+ // Named so the agent is told WHICH checkout's work is stranded: a clone serving the app was
221
+ // never named, only the primary checkout's folders.
222
+ const serving = origin === undefined ? undefined : servingOf(origin);
223
+ return { ...resolved, root, ...(serving === undefined ? {} : { serving }) };
196
224
  };
197
225
  }
@@ -1,3 +1,4 @@
1
+ import { UNMATCHED_SUBDIR } from './dir/reticle-dir.js';
1
2
  import { type ProjectCandidate, type ProjectId, type ProjectRegistry } from '@reticlehq/core';
2
3
  import type { ConfigDiscovery } from '../../command/cli/config/config-discovery.js';
3
4
  /**
@@ -60,6 +61,8 @@ export interface ArtifactRoot {
60
61
  * needs to name them; a caller that does not can ignore the field.
61
62
  */
62
63
  candidates?: string[];
64
+ /** The folder whose dev server served the page, when the evidence went unmatched and it is known. */
65
+ serving?: string;
63
66
  }
64
67
  /**
65
68
  * Resolve the artifact root for one session.
@@ -79,8 +82,7 @@ export declare function projectCandidatesFrom(discovery: ConfigDiscovery, regist
79
82
  projectId?: string | undefined;
80
83
  root: string;
81
84
  }[]): ProjectCandidate[];
82
- /** Where evidence goes when no project could be named and the daemon is a guest in this tree. */
83
- export declare const UNMATCHED_SUBDIR = "unmatched";
85
+ export { UNMATCHED_SUBDIR };
84
86
  /**
85
87
  * The fallback root for a session whose project could not be resolved.
86
88
  *
@@ -143,4 +145,3 @@ export declare function unmatchedRoot(query: {
143
145
  */
144
146
  origin?: string | undefined;
145
147
  }): string;
146
- export {};
@@ -1,4 +1,5 @@
1
1
  import { isAbsolute, join, relative, sep } from 'node:path';
2
+ import { UNMATCHED_SUBDIR } from './dir/reticle-dir.js';
2
3
  import { SAFE_SEGMENT_PATTERN, ReticleDir, fnv1a, projectCandidates, } from '@reticlehq/core';
3
4
  /**
4
5
  * Which `.reticle/` a session's artifacts belong in.
@@ -130,8 +131,7 @@ export function projectCandidatesFrom(discovery, registry, devServers = []) {
130
131
  : [{ projectId: server.projectId, directory: server.root }]);
131
132
  return [...known, ...announced];
132
133
  }
133
- /** Where evidence goes when no project could be named and the daemon is a guest in this tree. */
134
- export const UNMATCHED_SUBDIR = 'unmatched';
134
+ export { UNMATCHED_SUBDIR };
135
135
  /** What a project with NO identity at all is called on disk. Never blank, so the path is always real. */
136
136
  const UNNAMED_PROJECT = 'unnamed';
137
137
  /**
@@ -41,6 +41,15 @@ export interface ReticleDirPaths {
41
41
  plans: string;
42
42
  }
43
43
  export declare function reticleDirPaths(root: string): ReticleDirPaths;
44
+ /**
45
+ * A Harness drive's own record beside its run —.reticle/runs/<runId>.drive.json. Not a run: every
46
+ * reader that counts or syncs run files skips it.
47
+ */
48
+ export declare const DRIVE_RECORD_SUFFIX = ".drive.json";
49
+ /** Where evidence goes when no project could be named: `~/.reticle/unmatched/<id>`. */
50
+ export declare const UNMATCHED_SUBDIR = "unmatched";
51
+ /** Whether a root is one of those buckets: evidence from a page that never said its project. */
52
+ export declare function isUnmatchedRoot(root: string): boolean;
44
53
  /** The verification-run artifact path for `runId`.reticle/runs/<runId>.json). */
45
54
  export declare function runPath(root: string, runId: RunId): string;
46
55
  /** The journal directory for `sessionId` (.reticle/sessions/<id>). Guard the id first. */
@@ -88,6 +97,11 @@ export declare function visualPath(root: string, name: string, runtime?: string)
88
97
  * every pre-existing baseline came from a driven browser, and moving them would orphan them.
89
98
  */
90
99
  export declare function visualDir(root: string, runtime?: string): string;
100
+ /**
101
+ * Where one run's step screenshots live: `.reticle/visual/runs/<runId>/`. Throws on an unsafe run id,
102
+ * like every other path built from one.
103
+ */
104
+ export declare function stepShotDir(root: string, runId: string): string;
91
105
  /** The overlay-diff PNG path for `name`.reticle/visual/<name>.diff.png). */
92
106
  export declare function visualDiffPath(root: string, name: string, runtime?: string): string;
93
107
  /**
@@ -1,6 +1,6 @@
1
- import { dirname, join } from 'node:path';
1
+ import { basename, dirname, join } from 'node:path';
2
2
  import { realmOf } from '@reticlehq/core';
3
- import { CONTRACT_FILE_VERSION, ContractFileSchema, ContractReadError, FLOW_NAME_PATTERN, SAFE_SEGMENT_PATTERN, ReticleDir, } from '@reticlehq/core';
3
+ import { CONTRACT_FILE_VERSION, ContractFileSchema, ContractReadError, FLOW_NAME_PATTERN, SAFE_SEGMENT_PATTERN, ReticleDir, STEP_SCREENSHOT, } from '@reticlehq/core';
4
4
  export function reticleDirPaths(root) {
5
5
  return {
6
6
  root,
@@ -24,6 +24,18 @@ export function reticleDirPaths(root) {
24
24
  plans: join(root, ReticleDir.PLANS_SUBDIR),
25
25
  };
26
26
  }
27
+ /**
28
+ * A Harness drive's own record beside its run —.reticle/runs/<runId>.drive.json. Not a run: every
29
+ * reader that counts or syncs run files skips it.
30
+ */
31
+ export const DRIVE_RECORD_SUFFIX = '.drive.json';
32
+ /** Where evidence goes when no project could be named: `~/.reticle/unmatched/<id>`. */
33
+ export const UNMATCHED_SUBDIR = 'unmatched';
34
+ /** Whether a root is one of those buckets: evidence from a page that never said its project. */
35
+ export function isUnmatchedRoot(root) {
36
+ const parent = dirname(root);
37
+ return UNMATCHED_SUBDIR === basename(parent) && ReticleDir.ROOT === basename(dirname(parent));
38
+ }
27
39
  /** The verification-run artifact path for `runId`.reticle/runs/<runId>.json). */
28
40
  export function runPath(root, runId) {
29
41
  return join(root, ReticleDir.RUNS_SUBDIR, `${runId}.json`);
@@ -92,6 +104,15 @@ export function visualDir(root, runtime) {
92
104
  ? join(visual, runtime)
93
105
  : visual;
94
106
  }
107
+ /**
108
+ * Where one run's step screenshots live: `.reticle/visual/runs/<runId>/`. Throws on an unsafe run id,
109
+ * like every other path built from one.
110
+ */
111
+ export function stepShotDir(root, runId) {
112
+ if (!isValidRunId(runId))
113
+ throw new Error(`invalid run id: ${JSON.stringify(runId)}`);
114
+ return join(root, ReticleDir.VISUAL_SUBDIR, STEP_SCREENSHOT.RUNS_DIR, runId);
115
+ }
95
116
  /** The overlay-diff PNG path for `name`.reticle/visual/<name>.diff.png). */
96
117
  export function visualDiffPath(root, name, runtime) {
97
118
  return join(visualDir(root, runtime), `${name}.diff.png`);