@reticlehq/server 3.3.0 → 3.5.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 (326) hide show
  1. package/LICENSE-ENTERPRISE +1 -1
  2. package/README.md +3 -3
  3. package/SKILL.md +12 -14
  4. package/dist/chromium-launch-options.d.ts +4 -3
  5. package/dist/chromium-launch-options.js +6 -2
  6. package/dist/command/cli/adhoc-suite.d.ts +22 -0
  7. package/dist/command/cli/adhoc-suite.js +90 -19
  8. package/dist/command/cli/adhoc-verdict.d.ts +35 -0
  9. package/dist/command/cli/adhoc-verdict.js +90 -3
  10. package/dist/command/cli/answers/gate-hook-message.d.ts +6 -0
  11. package/dist/command/cli/answers/gate-hook-message.js +6 -0
  12. package/dist/command/cli/auth/cloud-keystore.d.ts +0 -2
  13. package/dist/command/cli/auth/cloud-keystore.js +0 -3
  14. package/dist/command/cli/auth/cloud-session.d.ts +6 -2
  15. package/dist/command/cli/auth/cloud-session.js +2 -0
  16. package/dist/command/cli/auth/device-flow.d.ts +2 -2
  17. package/dist/command/cli/cli-doctor.js +7 -5
  18. package/dist/command/cli/cli-flow-commands.d.ts +3 -6
  19. package/dist/command/cli/cli-flow-commands.js +67 -4
  20. package/dist/command/cli/cli-parse.d.ts +8 -1
  21. package/dist/command/cli/cli-parse.js +24 -4
  22. package/dist/command/cli/cli-update-commands.js +8 -1
  23. package/dist/command/cli/cli-verify.d.ts +24 -3
  24. package/dist/command/cli/cli-verify.js +54 -11
  25. package/dist/command/cli/cloud-cli.js +64 -2
  26. package/dist/command/cli/cloud-kit.js +3 -5
  27. package/dist/command/cli/cloud-login.d.ts +1 -1
  28. package/dist/command/cli/cloud-login.js +27 -8
  29. package/dist/command/cli/demo/demo-page.js +6 -3
  30. package/dist/command/cli/doctor/browser/chromium-hint.d.ts +5 -0
  31. package/dist/command/cli/doctor/browser/chromium-hint.js +5 -0
  32. package/dist/command/cli/launch/cli-launch.d.ts +8 -0
  33. package/dist/command/cli/launch/cli-launch.js +21 -9
  34. package/dist/command/cli/launch/sdk-version-match.d.ts +84 -0
  35. package/dist/command/cli/launch/sdk-version-match.js +206 -0
  36. package/dist/command/cli/lifecycle/daemon-lifecycle.js +2 -16
  37. package/dist/command/cli/ports/resolve/cli-port.d.ts +25 -0
  38. package/dist/command/cli/ports/resolve/cli-port.js +125 -15
  39. package/dist/command/cli/setup-mcp-cli.js +21 -13
  40. package/dist/command/cli/tutorial.d.ts +2 -2
  41. package/dist/command/cli/tutorial.js +21 -7
  42. package/dist/command/cli.js +79 -11
  43. package/dist/command/daemon/binding/daemon-status-probe.d.ts +14 -2
  44. package/dist/command/daemon/binding/daemon-status-probe.js +34 -6
  45. package/dist/command/daemon/daemon-resolve.d.ts +9 -0
  46. package/dist/command/daemon/daemon-resolve.js +1 -1
  47. package/dist/command/daemon/lifetime/daemon-usefulness.d.ts +7 -1
  48. package/dist/command/daemon/lifetime/daemon-usefulness.js +30 -7
  49. package/dist/command/dev/stale-issue-guard.d.ts +3 -4
  50. package/dist/command/dev/stale-issue-guard.js +8 -8
  51. package/dist/command/setup/bringup/connect-lease.d.ts +17 -0
  52. package/dist/command/setup/bringup/connect-lease.js +38 -0
  53. package/dist/command/setup/bringup/dev-server-files.d.ts +68 -0
  54. package/dist/command/setup/bringup/dev-server-files.js +131 -0
  55. package/dist/command/setup/bringup/dev-server-log-cap.d.ts +35 -0
  56. package/dist/command/setup/bringup/dev-server-log-cap.js +121 -0
  57. package/dist/command/setup/bringup/dev-server-wait.d.ts +2 -0
  58. package/dist/command/setup/bringup/dev-server-wait.js +1 -1
  59. package/dist/command/setup/bringup/ensure-daemon.d.ts +65 -4
  60. package/dist/command/setup/bringup/ensure-daemon.js +135 -16
  61. package/dist/command/setup/bringup/static-page-server.d.ts +35 -0
  62. package/dist/command/setup/bringup/static-page-server.js +151 -0
  63. package/dist/command/setup/desktop-shape.d.ts +4 -0
  64. package/dist/command/setup/desktop-shape.js +18 -2
  65. package/dist/command/setup/init/init-port.d.ts +15 -0
  66. package/dist/command/setup/init/init-port.js +44 -0
  67. package/dist/command/setup/init/init-runtime.d.ts +1 -1
  68. package/dist/command/setup/init/init-runtime.js +91 -9
  69. package/dist/command/setup/node-effects.d.ts +27 -14
  70. package/dist/command/setup/node-effects.js +86 -45
  71. package/dist/command/setup/probe/loopback-probe-urls.d.ts +7 -0
  72. package/dist/command/setup/probe/loopback-probe-urls.js +40 -0
  73. package/dist/command/setup/probe/page-probe.d.ts +13 -2
  74. package/dist/command/setup/probe/page-probe.js +18 -3
  75. package/dist/command/setup/run-setup.d.ts +28 -1
  76. package/dist/command/setup/run-setup.js +103 -15
  77. package/dist/command/setup/session-pick.d.ts +13 -0
  78. package/dist/command/setup/session-pick.js +20 -0
  79. package/dist/command/setup/setup-command.d.ts +2 -14
  80. package/dist/command/setup/setup-command.js +105 -31
  81. package/dist/command/setup/setup-mcp.d.ts +2 -0
  82. package/dist/command/setup/setup-mcp.js +17 -8
  83. package/dist/command/setup/terminal/wait-progress.d.ts +8 -0
  84. package/dist/command/setup/terminal/wait-progress.js +17 -0
  85. package/dist/command/version/peer-announce.d.ts +6 -2
  86. package/dist/command/version/peer-announce.js +8 -6
  87. package/dist/command/version/version-nudge.d.ts +17 -3
  88. package/dist/command/version/version-nudge.js +25 -4
  89. package/dist/features/crawl/crawl-empty.js +1 -1
  90. package/dist/features/crawl/crawl-tools.d.ts +6 -0
  91. package/dist/features/crawl/crawl-tools.js +57 -0
  92. package/dist/features/crawl/crawl.js +171 -157
  93. package/dist/features/exhaust/app-coverage.d.ts +33 -0
  94. package/dist/features/exhaust/app-coverage.js +31 -0
  95. package/dist/features/exhaust/code-coverage.d.ts +47 -0
  96. package/dist/features/exhaust/code-coverage.js +77 -0
  97. package/dist/features/exhaust/exhaust-run.d.ts +33 -0
  98. package/dist/features/exhaust/exhaust-run.js +82 -0
  99. package/dist/features/exhaust/explorer.d.ts +103 -0
  100. package/dist/features/exhaust/explorer.js +195 -0
  101. package/dist/features/exhaust/failure-branches.d.ts +33 -0
  102. package/dist/features/exhaust/failure-branches.js +92 -0
  103. package/dist/features/exhaust/ledger.d.ts +202 -0
  104. package/dist/features/exhaust/ledger.js +252 -0
  105. package/dist/features/exhaust/session-fold.d.ts +31 -0
  106. package/dist/features/exhaust/session-fold.js +79 -0
  107. package/dist/features/exhaust/session-port.d.ts +36 -0
  108. package/dist/features/exhaust/session-port.js +102 -0
  109. package/dist/features/harness/drive-report.d.ts +0 -9
  110. package/dist/features/harness/drive-report.js +18 -0
  111. package/dist/features/harness/driver.js +4 -7
  112. package/dist/features/harness/fill-values.d.ts +10 -0
  113. package/dist/features/harness/fill-values.js +50 -11
  114. package/dist/features/harness/jev-driver.js +30 -13
  115. package/dist/features/harness/openai-driver.d.ts +12 -0
  116. package/dist/features/harness/openai-driver.js +21 -12
  117. package/dist/features/harness/platform-config.js +5 -6
  118. package/dist/features/license/license.d.ts +2 -2
  119. package/dist/index.d.ts +9 -0
  120. package/dist/index.js +56 -19
  121. package/dist/judgement/capsule/capsule-store.d.ts +4 -4
  122. package/dist/judgement/domain/domain-model.js +3 -1
  123. package/dist/judgement/oracles/self-instrument.js +4 -2
  124. package/dist/judgement/runs/artifact/replay-mapping.d.ts +2 -2
  125. package/dist/judgement/runs/artifact/replay-mapping.js +44 -9
  126. package/dist/judgement/runs/verification-sync.d.ts +3 -1
  127. package/dist/judgement/runs/verification-sync.js +1 -1
  128. package/dist/language/flows/change/affected.d.ts +9 -0
  129. package/dist/language/flows/change/affected.js +35 -0
  130. package/dist/language/flows/change/gate.d.ts +20 -0
  131. package/dist/language/flows/change/gate.js +12 -1
  132. package/dist/language/flows/flow-author.d.ts +7 -0
  133. package/dist/language/flows/flow-author.js +19 -0
  134. package/dist/language/flows/flow-expect-grammar.js +1 -1
  135. package/dist/language/flows/flow-journey.d.ts +54 -0
  136. package/dist/language/flows/flow-journey.js +119 -0
  137. package/dist/language/flows/flow-replay-run.d.ts +2 -3
  138. package/dist/language/flows/flow-replay-run.js +13 -47
  139. package/dist/language/flows/flow-replay.d.ts +22 -8
  140. package/dist/language/flows/flow-replay.js +55 -9
  141. package/dist/language/flows/flow-result.d.ts +2 -0
  142. package/dist/language/flows/flow-result.js +25 -0
  143. package/dist/language/flows/flow-tools.js +17 -5
  144. package/dist/language/flows/flows.d.ts +10 -1
  145. package/dist/language/flows/flows.js +90 -14
  146. package/dist/language/flows/recording/replay-halt.js +3 -1
  147. package/dist/language/flows/recording/tape/recordings.d.ts +30 -1
  148. package/dist/language/flows/recording/tape/recordings.js +55 -1
  149. package/dist/language/flows/replay.d.ts +19 -0
  150. package/dist/language/flows/replay.js +42 -6
  151. package/dist/language/flows/verify-change-tools.js +5 -2
  152. package/dist/launch-chromium.d.ts +80 -0
  153. package/dist/launch-chromium.js +218 -0
  154. package/dist/memory/cloud/cloud-config.d.ts +11 -2
  155. package/dist/memory/cloud/cloud-config.js +47 -7
  156. package/dist/memory/cloud/cloud-sync.d.ts +4 -1
  157. package/dist/memory/cloud/cloud-sync.js +6 -9
  158. package/dist/memory/cloud/harness-offer.js +5 -6
  159. package/dist/memory/cloud/harness-switch.js +5 -6
  160. package/dist/memory/cloud/sync-cycle.d.ts +77 -3
  161. package/dist/memory/cloud/sync-cycle.js +325 -55
  162. package/dist/memory/cloud/sync-daemon.js +24 -20
  163. package/dist/memory/cloud/sync-disk.js +16 -2
  164. package/dist/memory/intent/intent-shard.d.ts +10 -32
  165. package/dist/memory/intent/intent-shard.js +0 -10
  166. package/dist/memory/intent/intent-tools.js +8 -3
  167. package/dist/memory/journal/drive-flow.d.ts +18 -13
  168. package/dist/memory/journal/drive-flow.js +45 -37
  169. package/dist/memory/journal/envelope.d.ts +8 -8
  170. package/dist/memory/journal/journal-query.d.ts +14 -0
  171. package/dist/memory/journal/journal-query.js +15 -0
  172. package/dist/memory/journal/on-disk/workspace-tiers.js +2 -0
  173. package/dist/memory/journal/session-end.d.ts +9 -1
  174. package/dist/memory/journal/session-end.js +24 -12
  175. package/dist/memory/project/artifact-root-resolver.d.ts +10 -0
  176. package/dist/memory/project/artifact-root-resolver.js +46 -19
  177. package/dist/memory/project/dir/reticle-dir.d.ts +2 -0
  178. package/dist/memory/project/dir/reticle-dir.js +1 -0
  179. package/dist/memory/project/project-tools.js +3 -1
  180. package/dist/portal/bridge/auth-failure-reason.d.ts +1 -1
  181. package/dist/portal/bridge/auth-failure-reason.js +4 -2
  182. package/dist/portal/bridge/bridge.d.ts +12 -3
  183. package/dist/portal/bridge/bridge.js +49 -3
  184. package/dist/portal/input/js-coverage.d.ts +25 -0
  185. package/dist/portal/input/js-coverage.js +30 -0
  186. package/dist/portal/input/real-input.d.ts +11 -0
  187. package/dist/portal/input/real-input.js +29 -4
  188. package/dist/portal/input/viewport-tools.js +16 -6
  189. package/dist/portal/pool/playwright-launcher.d.ts +6 -0
  190. package/dist/portal/pool/playwright-launcher.js +20 -13
  191. package/dist/portal/session/dev-server/dev-command.d.ts +2 -2
  192. package/dist/portal/session/dev-server/dev-command.js +30 -12
  193. package/dist/portal/session/dev-server/dev-server-probe.js +17 -1
  194. package/dist/portal/session/dev-server/route-status-probe.d.ts +24 -0
  195. package/dist/portal/session/dev-server/route-status-probe.js +57 -0
  196. package/dist/portal/session/facts/observed-state.d.ts +11 -0
  197. package/dist/portal/session/facts/observed-state.js +34 -0
  198. package/dist/portal/session/no-session-diagnosis.d.ts +29 -0
  199. package/dist/portal/session/no-session-diagnosis.js +62 -5
  200. package/dist/portal/session/no-session-next-action.d.ts +13 -0
  201. package/dist/portal/session/no-session-next-action.js +48 -9
  202. package/dist/portal/session/no-session-watch.d.ts +18 -0
  203. package/dist/portal/session/no-session-watch.js +88 -2
  204. package/dist/portal/session/session-health.js +3 -2
  205. package/dist/portal/session/session-manager.d.ts +20 -6
  206. package/dist/portal/session/session-manager.js +19 -3
  207. package/dist/portal/session/session-reaper.d.ts +28 -0
  208. package/dist/portal/session/session-reaper.js +49 -1
  209. package/dist/portal/session/session.d.ts +10 -8
  210. package/dist/portal/session/session.js +24 -32
  211. package/dist/surface/http-server.d.ts +3 -2
  212. package/dist/surface/http-server.js +8 -5
  213. package/dist/surface/mcp/mcp-proxy.d.ts +2 -2
  214. package/dist/surface/mcp/mcp-proxy.js +2 -2
  215. package/dist/surface/mcp/mcp.js +5 -3
  216. package/dist/surface/mcp/proxy/proxy-daemon-probe.d.ts +13 -1
  217. package/dist/surface/mcp/proxy/proxy-daemon-probe.js +27 -2
  218. package/dist/surface/mcp/proxy/proxy-log.js +3 -5
  219. package/dist/surface/mcp/server-instructions.js +9 -7
  220. package/dist/surface/tools/act/after-match.d.ts +17 -0
  221. package/dist/surface/tools/act/after-match.js +30 -0
  222. package/dist/surface/tools/act/durable.d.ts +69 -0
  223. package/dist/surface/tools/act/durable.js +139 -0
  224. package/dist/surface/tools/act/navigation/navigate-arrival.d.ts +13 -6
  225. package/dist/surface/tools/act/navigation/navigate-arrival.js +47 -10
  226. package/dist/surface/tools/act/navigation/navigate-result.d.ts +1 -12
  227. package/dist/surface/tools/act/navigation/navigate-result.js +21 -3
  228. package/dist/surface/tools/act/proved-controls.d.ts +9 -0
  229. package/dist/surface/tools/act/proved-controls.js +11 -0
  230. package/dist/surface/tools/act/sequence-grade.d.ts +5 -0
  231. package/dist/surface/tools/act/sequence-grade.js +32 -1
  232. package/dist/surface/tools/act-capsule.d.ts +7 -0
  233. package/dist/surface/tools/act-capsule.js +46 -1
  234. package/dist/surface/tools/act-sequence-tool.js +25 -8
  235. package/dist/surface/tools/act-tools.js +28 -52
  236. package/dist/surface/tools/assert/assert-grade.js +12 -1
  237. package/dist/surface/tools/assert/assert-verdict.js +2 -2
  238. package/dist/surface/tools/browser-tools.js +14 -2
  239. package/dist/surface/tools/coverage-tools.js +27 -1
  240. package/dist/surface/tools/dynamic-tools.js +29 -2
  241. package/dist/surface/tools/error-recovery.js +5 -6
  242. package/dist/surface/tools/feedback-invite.js +6 -4
  243. package/dist/surface/tools/harness-explore.d.ts +3 -2
  244. package/dist/surface/tools/harness-explore.js +12 -7
  245. package/dist/surface/tools/invoke-tool.js +12 -8
  246. package/dist/surface/tools/lease-tools.js +15 -6
  247. package/dist/surface/tools/live-call-text.js +4 -1
  248. package/dist/surface/tools/observe-tools.js +9 -1
  249. package/dist/surface/tools/tool-kit.d.ts +10 -0
  250. package/dist/surface/tools/tools.js +4 -0
  251. package/dist/telemetry/argument-shape.d.ts +0 -2
  252. package/dist/telemetry/argument-shape.js +1 -5
  253. package/dist/telemetry/error-class.d.ts +2 -3
  254. package/dist/telemetry/error-class.js +2 -3
  255. package/dist/telemetry/harness-drive.d.ts +0 -2
  256. package/dist/telemetry/harness-drive.js +0 -4
  257. package/dist/telemetry/onboarding-firsts.d.ts +2 -2
  258. package/dist/telemetry/onboarding-firsts.js +2 -2
  259. package/dist/wire-journal.d.ts +3 -0
  260. package/dist/wire-journal.js +4 -0
  261. package/dist/workspace-packages.d.ts +0 -2
  262. package/dist/workspace-packages.js +1 -9
  263. package/docs/README.md +4 -2
  264. package/docs/actions.mdx +3 -4
  265. package/docs/agent-cheatsheet.md +4 -4
  266. package/docs/architecture.md +6 -6
  267. package/docs/best-practices.mdx +1 -1
  268. package/docs/capabilities.mdx +32 -1
  269. package/docs/cli/cloud.mdx +20 -8
  270. package/docs/cli/doctor.mdx +4 -0
  271. package/docs/cli/feedback.mdx +2 -2
  272. package/docs/cli/gate.mdx +9 -4
  273. package/docs/cli/update.mdx +10 -0
  274. package/docs/cli.mdx +22 -7
  275. package/docs/desktop.mdx +14 -10
  276. package/docs/environment.mdx +87 -0
  277. package/docs/faq.mdx +18 -16
  278. package/docs/first-drive.md +5 -13
  279. package/docs/flows.md +21 -2
  280. package/docs/frameworks.mdx +29 -10
  281. package/docs/getting-started.md +15 -5
  282. package/docs/http-transport.md +6 -3
  283. package/docs/human-control.md +2 -2
  284. package/docs/index.mdx +16 -6
  285. package/docs/install-agentic.mdx +2 -2
  286. package/docs/install-manual.mdx +1 -1
  287. package/docs/integration-patterns.md +3 -3
  288. package/docs/multi-agent-testing.md +1 -1
  289. package/docs/packages/babel-plugin.mdx +1 -1
  290. package/docs/packages/browser.mdx +2 -2
  291. package/docs/packages/next.mdx +3 -0
  292. package/docs/packages/react.mdx +2 -2
  293. package/docs/packages/tauri.mdx +1 -1
  294. package/docs/packages/vite-plugin.mdx +4 -2
  295. package/docs/packages.mdx +9 -9
  296. package/docs/predicates.mdx +50 -5
  297. package/docs/quickstart.mdx +12 -10
  298. package/docs/recipes.mdx +2 -2
  299. package/docs/state-management.mdx +2 -2
  300. package/docs/system-map.md +1 -1
  301. package/docs/telemetry.md +1 -1
  302. package/docs/token-efficiency.md +3 -3
  303. package/docs/tools/act-and-wait.mdx +1 -0
  304. package/docs/tools/act-sequence.mdx +1 -1
  305. package/docs/tools/act.mdx +1 -1
  306. package/docs/tools/console.mdx +1 -1
  307. package/docs/tools/inspect.mdx +1 -1
  308. package/docs/tools/network.mdx +1 -1
  309. package/docs/tools/query.mdx +1 -1
  310. package/docs/tools/sessions.mdx +1 -1
  311. package/docs/tools/snapshot.mdx +1 -1
  312. package/docs/tools/state.mdx +1 -1
  313. package/docs/tools/wait-for.mdx +1 -1
  314. package/docs/usage.md +7 -7
  315. package/docs/use-cases.mdx +157 -0
  316. package/docs/vs-screenshots.mdx +3 -3
  317. package/docs/what-is-recorded.md +2 -2
  318. package/docs/what-is-reticle.mdx +17 -20
  319. package/docs/why-reticle.mdx +14 -21
  320. package/package.json +10 -9
  321. package/dist/judgement/capsule/minimize.d.ts +0 -12
  322. package/dist/judgement/capsule/minimize.js +0 -22
  323. package/dist/judgement/phenomena/phenomena.d.ts +0 -27
  324. package/dist/judgement/phenomena/phenomena.js +0 -101
  325. package/dist/language/flows/flow-report.d.ts +0 -22
  326. package/dist/language/flows/flow-report.js +0 -106
@@ -21,7 +21,6 @@
21
21
  */
22
22
  import { z } from 'zod';
23
23
  import { type Intent } from '@reticlehq/core/artifacts';
24
- import type { FileSystemPort } from '../project/fs/fs-port.js';
25
24
  /**
26
25
  * How settled a record is.
27
26
  *
@@ -52,12 +51,12 @@ declare const IntentRecordSchema: z.ZodObject<{
52
51
  flow: z.ZodOptional<z.ZodString>;
53
52
  files: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
54
53
  }, "strip", z.ZodTypeAny, {
55
- flow?: string | undefined;
56
54
  route?: string | undefined;
55
+ flow?: string | undefined;
57
56
  files?: string[] | undefined;
58
57
  }, {
59
- flow?: string | undefined;
60
58
  route?: string | undefined;
59
+ flow?: string | undefined;
61
60
  files?: string[] | undefined;
62
61
  }>>;
63
62
  provenBy: z.ZodOptional<z.ZodObject<{
@@ -101,8 +100,8 @@ declare const IntentRecordSchema: z.ZodObject<{
101
100
  statement: string;
102
101
  declaredAt: number;
103
102
  surface?: {
104
- flow?: string | undefined;
105
103
  route?: string | undefined;
104
+ flow?: string | undefined;
106
105
  files?: string[] | undefined;
107
106
  } | undefined;
108
107
  source?: string | undefined;
@@ -126,8 +125,8 @@ declare const IntentRecordSchema: z.ZodObject<{
126
125
  statement: string;
127
126
  declaredAt: number;
128
127
  surface?: {
129
- flow?: string | undefined;
130
128
  route?: string | undefined;
129
+ flow?: string | undefined;
131
130
  files?: string[] | undefined;
132
131
  } | undefined;
133
132
  source?: string | undefined;
@@ -159,12 +158,12 @@ export declare const IntentShardSchema: z.ZodObject<{
159
158
  flow: z.ZodOptional<z.ZodString>;
160
159
  files: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
161
160
  }, "strip", z.ZodTypeAny, {
162
- flow?: string | undefined;
163
161
  route?: string | undefined;
162
+ flow?: string | undefined;
164
163
  files?: string[] | undefined;
165
164
  }, {
166
- flow?: string | undefined;
167
165
  route?: string | undefined;
166
+ flow?: string | undefined;
168
167
  files?: string[] | undefined;
169
168
  }>>;
170
169
  provenBy: z.ZodOptional<z.ZodObject<{
@@ -208,8 +207,8 @@ export declare const IntentShardSchema: z.ZodObject<{
208
207
  statement: string;
209
208
  declaredAt: number;
210
209
  surface?: {
211
- flow?: string | undefined;
212
210
  route?: string | undefined;
211
+ flow?: string | undefined;
213
212
  files?: string[] | undefined;
214
213
  } | undefined;
215
214
  source?: string | undefined;
@@ -233,8 +232,8 @@ export declare const IntentShardSchema: z.ZodObject<{
233
232
  statement: string;
234
233
  declaredAt: number;
235
234
  surface?: {
236
- flow?: string | undefined;
237
235
  route?: string | undefined;
236
+ flow?: string | undefined;
238
237
  files?: string[] | undefined;
239
238
  } | undefined;
240
239
  source?: string | undefined;
@@ -262,8 +261,8 @@ export declare const IntentShardSchema: z.ZodObject<{
262
261
  statement: string;
263
262
  declaredAt: number;
264
263
  surface?: {
265
- flow?: string | undefined;
266
264
  route?: string | undefined;
265
+ flow?: string | undefined;
267
266
  files?: string[] | undefined;
268
267
  } | undefined;
269
268
  source?: string | undefined;
@@ -291,8 +290,8 @@ export declare const IntentShardSchema: z.ZodObject<{
291
290
  statement: string;
292
291
  declaredAt: number;
293
292
  surface?: {
294
- flow?: string | undefined;
295
293
  route?: string | undefined;
294
+ flow?: string | undefined;
296
295
  files?: string[] | undefined;
297
296
  } | undefined;
298
297
  source?: string | undefined;
@@ -311,24 +310,6 @@ export declare const IntentShardSchema: z.ZodObject<{
311
310
  }>;
312
311
  }>;
313
312
  export type IntentShard = z.infer<typeof IntentShardSchema>;
314
- /** One line per intent: enough to decide whether to open the shard, and no more. */
315
- declare const IntentIndexEntrySchema: z.ZodObject<{
316
- id: z.ZodString;
317
- subject: z.ZodString;
318
- statement: z.ZodString;
319
- status: z.ZodEnum<["proposed", "agreed", "proved", "stale"]>;
320
- }, "strip", z.ZodTypeAny, {
321
- status: "proved" | "proposed" | "agreed" | "stale";
322
- id: string;
323
- subject: string;
324
- statement: string;
325
- }, {
326
- status: "proved" | "proposed" | "agreed" | "stale";
327
- id: string;
328
- subject: string;
329
- statement: string;
330
- }>;
331
- export type IntentIndexEntry = z.infer<typeof IntentIndexEntrySchema>;
332
313
  export declare const IntentIndexSchema: z.ZodObject<{
333
314
  version: z.ZodLiteral<1>;
334
315
  entries: z.ZodArray<z.ZodObject<{
@@ -366,7 +347,6 @@ export declare const IntentIndexSchema: z.ZodObject<{
366
347
  }>;
367
348
  export type IntentIndex = z.infer<typeof IntentIndexSchema>;
368
349
  export declare const emptyShard: (subject: string) => IntentShard;
369
- export declare const emptyIndex: () => IntentIndex;
370
350
  /** The verification lifecycle a legacy record carried, mapped onto the settledness axis. */
371
351
  export declare const statusFromState: (state: Intent["state"]) => IntentStatus;
372
352
  /**
@@ -383,6 +363,4 @@ export declare const indexFrom: (shards: readonly IntentShard[]) => IntentIndex;
383
363
  export declare const shardsFrom: (records: readonly IntentRecord[]) => IntentShard[];
384
364
  /** Serialise byte-stably, with keys sorted so a re-save of unchanged data is a no-op diff. */
385
365
  export declare const serialise: (value: unknown) => string;
386
- /** Read + validate one JSON file, failing soft: a hand-merged file with a conflict marker is real. */
387
- export declare const readJsonFile: <T>(fs: FileSystemPort, path: string, parse: (raw: unknown) => T, fallback: T) => Promise<T>;
388
366
  export {};
@@ -96,7 +96,6 @@ export const emptyShard = (subject) => ({
96
96
  subject,
97
97
  intents: {},
98
98
  });
99
- export const emptyIndex = () => ({ version: INTENT_SHARD_VERSION, entries: [] });
100
99
  /** The verification lifecycle a legacy record carried, mapped onto the settledness axis. */
101
100
  export const statusFromState = (state) => 'proved' === state ? IntentStatus.PROVED : IntentStatus.AGREED;
102
101
  /**
@@ -142,12 +141,3 @@ const sortKeys = (value) => {
142
141
  return value;
143
142
  return Object.fromEntries(Object.entries(value).sort(([a], [b]) => a.localeCompare(b)));
144
143
  };
145
- /** Read + validate one JSON file, failing soft: a hand-merged file with a conflict marker is real. */
146
- export const readJsonFile = async (fs, path, parse, fallback) => {
147
- try {
148
- return parse(JSON.parse(await fs.readFile(path)));
149
- }
150
- catch {
151
- return fallback;
152
- }
153
- };
@@ -70,6 +70,8 @@ export const INTENT_TOOLS = [
70
70
  })
71
71
  .optional(),
72
72
  }))
73
+ // Non-empty in the schema too, so the advertised contract says what `declare` enforces.
74
+ .min(1)
73
75
  .optional()
74
76
  .describe('declare only. Batchable — declare every intent for a feature in one call.'),
75
77
  id: z.string().optional().describe('bind only: which intent the predicate proves.'),
@@ -153,9 +155,12 @@ export const INTENT_TOOLS = [
153
155
  return { intents: await store.open() };
154
156
  }
155
157
  const raw = args['intents'];
156
- const entries = Array.isArray(raw)
157
- ? raw
158
- : [];
158
+ // Refused, not read as an empty list: `declare` with nothing to declare stored nothing and
159
+ // answered `{ intents: [] }`, which an agent reads as a declaration that worked (#1118).
160
+ if (!Array.isArray(raw) || 0 === raw.length) {
161
+ throw new Error(`${ReticleTool.INTENT} declare: \`intents\` must be a non-empty array of { id, statement }, got ${raw === undefined ? 'nothing' : JSON.stringify(raw)} — nothing was stored`);
162
+ }
163
+ const entries = raw;
159
164
  const declared = await store.declare(entries);
160
165
  // Said, not swallowed: an agent that declared something and finds nothing stored would
161
166
  // otherwise conclude the ledger lost it.
@@ -1,4 +1,4 @@
1
- import type { FlowName, Predicate } from '@reticlehq/core';
1
+ import type { FlowFile, FlowName, Predicate } from '@reticlehq/core';
2
2
  /**
3
3
  * What this module needs a recorded step to BE, named structurally rather than imported.
4
4
  *
@@ -15,6 +15,10 @@ export interface TapeStep {
15
15
  expect?: Predicate;
16
16
  /** Recorder-internal, and the field journeys are cut on. Never written to the saved flow. */
17
17
  route?: string;
18
+ page?: string;
19
+ endPage?: string;
20
+ /** The intent the agent declared on this action. The first one names the journey. */
21
+ intent?: string;
18
22
  }
19
23
  /** The compiled shape a flow store accepts. Structural, for the same reason as `TapeStep`. */
20
24
  export interface DriveProgram {
@@ -22,6 +26,7 @@ export interface DriveProgram {
22
26
  version: number;
23
27
  steps: TapeStep[];
24
28
  startPath?: string;
29
+ author?: FlowFile['author'];
25
30
  }
26
31
  /**
27
32
  * What a session drove, saved as a replayable flow — without anybody asking for it.
@@ -36,21 +41,21 @@ export interface DriveProgram {
36
41
  */
37
42
  /** The one condition on saving, and the reason the whole thing is safe. */
38
43
  export declare function carriesAnAssertion(steps: readonly TapeStep[]): boolean;
44
+ /** Every auto-saved flow's name starts here, which is what lets a merge tell them from hand-named ones. */
45
+ export declare const DRIVE_FLOW_PREFIX = "drive-";
39
46
  /**
40
- * A stable name for what this session drove — led by what it proved.
47
+ * A readable name for a journey, led by WHY it was driven.
41
48
  *
42
- * Two properties, and the name used to have only the second. It has to be STABLE: teardown fires on
43
- * every socket close and a reconnecting tab keeps its id, so a name that moves leaves one journey
44
- * scattered across several files, each a partial copy of the others. And it has to be READABLE,
45
- * because `presenter-controls.ts` renders it straight into the replay button's `textContent` — so
46
- * the label a person actually sees was `drive-sdc991872-6d66-4adf-8780-f931c62905f9`, twice, for two
47
- * different journeys. A session id is a fact about the socket, not about the feature.
49
+ * The agent's own `intent` on the action is the best name there is — it is the sentence a person
50
+ * would use — so it leads when one was declared. Without one, what the journey proved names it (the
51
+ * strongest claim first), and the route follows so two journeys stay apart.
48
52
  *
49
- * So the claim leads, the route follows, and a SHORT session discriminator trails: two sessions that
50
- * prove the same thing on the same route stay separate files rather than overwriting each other,
51
- * which is the one thing the full id was buying that the claim cannot.
53
+ * No session id. It used to trail every name so two sessions never overwrote each other, which also
54
+ * made every session's drive of the same journey its own file. Two DIFFERENT journeys that land on
55
+ * one name are now told apart at save by their shape, and two copies of the SAME journey merge —
56
+ * see FlowStore.saveJourney.
52
57
  */
53
- export declare function driveFlowName(sessionId: string, route?: string, steps?: readonly TapeStep[]): FlowName;
58
+ export declare function driveFlowName(route?: string, steps?: readonly TapeStep[]): FlowName;
54
59
  export interface DriveFlowOutcome {
55
60
  /** The flows that were written — one per journey the session contained. */
56
61
  readonly saved?: readonly string[];
@@ -65,7 +70,7 @@ export interface DriveFlowOutcome {
65
70
  readonly unprovenSteps?: number;
66
71
  }
67
72
  /** A flow per journey, so two journeys in one session are two regression tests rather than one. */
68
- export declare function driveFlowsFrom(sessionId: string, tape: {
73
+ export declare function driveFlowsFrom(tape: {
69
74
  steps: readonly TapeStep[];
70
75
  startPath?: string;
71
76
  } | undefined): {
@@ -1,4 +1,4 @@
1
- import { PredicateKind, REPLAY_PROGRAM_VERSION, asFlowName } from '@reticlehq/core';
1
+ import { ActionType, PredicateKind, QueryBy, REPLAY_PROGRAM_VERSION, asFlowName, defaultIsSensitiveKey, } from '@reticlehq/core';
2
2
  import { clauseOfKind } from '@reticlehq/core';
3
3
  /**
4
4
  * What a session drove, saved as a replayable flow — without anybody asking for it.
@@ -30,7 +30,7 @@ const segment = (value, max) => value
30
30
  * DOM-shaped claims are the weakest. Naming a flow after the strongest thing it proves is the same
31
31
  * judgement the honesty grade makes, reused where a human reads it.
32
32
  *
33
- * Returns '' when a step claims nothing nameable, and the caller keeps the old session-only name.
33
+ * Returns '' when a step claims nothing nameable.
34
34
  */
35
35
  function provedSlug(steps) {
36
36
  for (const step of steps) {
@@ -63,42 +63,29 @@ function provedSlug(steps) {
63
63
  }
64
64
  return '';
65
65
  }
66
+ /** Every auto-saved flow's name starts here, which is what lets a merge tell them from hand-named ones. */
67
+ export const DRIVE_FLOW_PREFIX = 'drive-';
66
68
  /**
67
- * A stable name for what this session drove — led by what it proved.
69
+ * A readable name for a journey, led by WHY it was driven.
68
70
  *
69
- * Two properties, and the name used to have only the second. It has to be STABLE: teardown fires on
70
- * every socket close and a reconnecting tab keeps its id, so a name that moves leaves one journey
71
- * scattered across several files, each a partial copy of the others. And it has to be READABLE,
72
- * because `presenter-controls.ts` renders it straight into the replay button's `textContent` — so
73
- * the label a person actually sees was `drive-sdc991872-6d66-4adf-8780-f931c62905f9`, twice, for two
74
- * different journeys. A session id is a fact about the socket, not about the feature.
71
+ * The agent's own `intent` on the action is the best name there is — it is the sentence a person
72
+ * would use — so it leads when one was declared. Without one, what the journey proved names it (the
73
+ * strongest claim first), and the route follows so two journeys stay apart.
75
74
  *
76
- * So the claim leads, the route follows, and a SHORT session discriminator trails: two sessions that
77
- * prove the same thing on the same route stay separate files rather than overwriting each other,
78
- * which is the one thing the full id was buying that the claim cannot.
75
+ * No session id. It used to trail every name so two sessions never overwrote each other, which also
76
+ * made every session's drive of the same journey its own file. Two DIFFERENT journeys that land on
77
+ * one name are now told apart at save by their shape, and two copies of the SAME journey merge —
78
+ * see FlowStore.saveJourney.
79
79
  */
80
- export function driveFlowName(sessionId, route, steps = []) {
81
- const safe = sessionId.replace(/[^a-zA-Z0-9-]/g, '-').slice(0, 40);
82
- const proved = provedSlug(steps);
83
- // The route is part of the name, so two journeys in one session do not overwrite each other —
84
- // and so a re-drive of the same journey rewrites its own flow rather than adding a near-duplicate.
85
- const leg = (route ?? '')
86
- .replace(/[^a-zA-Z0-9]+/g, '-')
87
- .replace(/^-|-$/g, '')
88
- .slice(0, 24);
89
- // Minted here, not at the call site. This function is where a drive's flow name comes into
90
- // existence, and every character it can emit has just been forced through the safe-segment
91
- // replacements above — so this IS the validated boundary the brand is meant to be created at.
92
- // Returning `string` pushed a cast onto each caller, which is the hole `flowPath`'s parameter
93
- // type was added to close.
94
- // Nothing nameable was proved, so keep the name this function has always produced.
95
- if ('' === proved) {
96
- return asFlowName(0 === leg.length ? `drive-${safe}` : `drive-${safe}-${leg}`);
97
- }
98
- // Enough of the session to keep two of them apart, and little enough to read past.
99
- const discriminator = safe.replace(/-/g, '').slice(0, 8);
100
- const parts = ['drive', proved, ...(0 === leg.length ? [] : [leg]), discriminator];
101
- return asFlowName(parts.join('-'));
80
+ export function driveFlowName(route, steps = []) {
81
+ const intent = steps.find((step) => step.intent !== undefined)?.intent;
82
+ const leg = segment(route ?? '', 24);
83
+ const why = intent === undefined ? '' : segment(intent, 48);
84
+ const lead = '' !== why ? why : provedSlug(steps);
85
+ // Minted here: every character has just been forced through `segment`, so this IS the validated
86
+ // boundary the brand is meant to be created at.
87
+ const parts = [lead, ...('' !== why || 0 === leg.length ? [] : [leg])].filter((p) => '' !== p);
88
+ return asFlowName(`${DRIVE_FLOW_PREFIX}${0 === parts.length ? 'journey' : parts.join('-')}`);
102
89
  }
103
90
  /**
104
91
  * Turn this session's ambient tape into a flow, when it is worth one.
@@ -125,18 +112,39 @@ export function driveFlowName(sessionId, route, steps = []) {
125
112
  */
126
113
  function segmentsByRoute(steps) {
127
114
  const out = [];
115
+ let signedIn = false;
128
116
  for (const step of steps) {
129
117
  const last = out.at(-1);
130
- if (last !== undefined && last.route === step.route) {
118
+ const previous = last?.steps.at(-1);
119
+ // The same journey when the route is unchanged, or when the previous ACTION moved the app here
120
+ // — cart → shipping → payment is one flow, and cutting it left three fragments that each
121
+ // proved a page and none proved the journey. Not after a sign-in: that is the cut this exists
122
+ // for, and a login that navigates onward must still not open the next flow.
123
+ const caused = previous !== undefined && step.route !== undefined && previous.endPage === step.route;
124
+ if (last !== undefined && (last.route === step.route || (caused && !signedIn))) {
131
125
  last.steps.push(step);
126
+ signedIn ||= typesASecret(step);
132
127
  continue;
133
128
  }
129
+ signedIn = typesASecret(step);
134
130
  out.push({ ...(step.route === undefined ? {} : { route: step.route }), steps: [step] });
135
131
  }
136
132
  return out;
137
133
  }
134
+ /**
135
+ * A fill into a field the network channel would redact — the sign-in a journey must not carry.
136
+ * Read off the recorder's anchor args: a testid names the field by `value`, a role by `name`.
137
+ */
138
+ function typesASecret(step) {
139
+ const action = step.args['action'];
140
+ if (ActionType.FILL !== action && ActionType.TYPE !== action)
141
+ return false;
142
+ const by = step.args['by'];
143
+ const field = QueryBy.TESTID === by ? step.args['value'] : step.args['name'];
144
+ return 'string' === typeof field && defaultIsSensitiveKey(field);
145
+ }
138
146
  /** A flow per journey, so two journeys in one session are two regression tests rather than one. */
139
- export function driveFlowsFrom(sessionId, tape) {
147
+ export function driveFlowsFrom(tape) {
140
148
  if (tape === undefined || 0 === tape.steps.length)
141
149
  return { programs: [], outcome: {} };
142
150
  const programs = [];
@@ -148,7 +156,7 @@ export function driveFlowsFrom(sessionId, tape) {
148
156
  }
149
157
  const startPath = segment.route ?? tape.startPath;
150
158
  programs.push({
151
- name: driveFlowName(sessionId, startPath, segment.steps),
159
+ name: driveFlowName(startPath, segment.steps),
152
160
  version: REPLAY_PROGRAM_VERSION,
153
161
  // The route is recorder-internal and has no business on disk — `startPath` is where the
154
162
  // on-disk flow says the same thing, in the field replay actually reads.
@@ -101,13 +101,13 @@ export declare const RouteEnvelopeSchema: z.ZodObject<{
101
101
  max: number;
102
102
  }>;
103
103
  }, "strip", z.ZodTypeAny, {
104
- durationMs: {
104
+ net: {
105
105
  count: number;
106
106
  mean: number;
107
107
  m2: number;
108
108
  max: number;
109
109
  };
110
- net: {
110
+ durationMs: {
111
111
  count: number;
112
112
  mean: number;
113
113
  m2: number;
@@ -126,13 +126,13 @@ export declare const RouteEnvelopeSchema: z.ZodObject<{
126
126
  max: number;
127
127
  };
128
128
  }, {
129
- durationMs: {
129
+ net: {
130
130
  count: number;
131
131
  mean: number;
132
132
  m2: number;
133
133
  max: number;
134
134
  };
135
- net: {
135
+ durationMs: {
136
136
  count: number;
137
137
  mean: number;
138
138
  m2: number;
@@ -155,13 +155,13 @@ export declare const RouteEnvelopeSchema: z.ZodObject<{
155
155
  route: string;
156
156
  samples: number;
157
157
  stats: {
158
- durationMs: {
158
+ net: {
159
159
  count: number;
160
160
  mean: number;
161
161
  m2: number;
162
162
  max: number;
163
163
  };
164
- net: {
164
+ durationMs: {
165
165
  count: number;
166
166
  mean: number;
167
167
  m2: number;
@@ -184,13 +184,13 @@ export declare const RouteEnvelopeSchema: z.ZodObject<{
184
184
  route: string;
185
185
  samples: number;
186
186
  stats: {
187
- durationMs: {
187
+ net: {
188
188
  count: number;
189
189
  mean: number;
190
190
  m2: number;
191
191
  max: number;
192
192
  };
193
- net: {
193
+ durationMs: {
194
194
  count: number;
195
195
  mean: number;
196
196
  m2: number;
@@ -62,5 +62,19 @@ export declare function readQueryEvents(reader: JournalReader | undefined, buffe
62
62
  * "nothing was lost" — see `JournalReader.readWriteLoss`.
63
63
  */
64
64
  export declare function readJournalWriteLoss(reader: JournalReader | undefined, buffer: QueryBuffer): Promise<JournalWriteLoss | undefined>;
65
+ /** The slice of the ring buffer `windowLost` reads. `RingBuffer` satisfies it structurally. */
66
+ export interface LossBuffer {
67
+ lostSince(cursor: number): boolean;
68
+ }
69
+ /**
70
+ * Did the EVENT STORE lose scarce evidence from a window opened at `cursor`? The input to whether a
71
+ * verdict's capture was clean — see `RingBuffer.lostSince`, and never the raw drop counter, which
72
+ * moves for the age and churn evictions that every live page produces continuously. The journal is
73
+ * the other half of that store, so a durable read that could not reach back to `cursor` lost
74
+ * exactly what an eviction would have. Both boundaries are INCLUSIVE (`t` is a millisecond many
75
+ * records share) and an absent `lostThroughT` impeaches every window rather than none — the
76
+ * conservative direction, both times.
77
+ */
78
+ export declare function windowLost(reader: JournalReader | undefined, buffer: LossBuffer, cursor: number): boolean;
65
79
  /** Apply since/until/actionId bounds to an event list. */
66
80
  export declare function filterEvents(events: readonly ReticleEvent[], options: EventQueryOptions): ReticleEvent[];
@@ -90,6 +90,21 @@ export async function readJournalWriteLoss(reader, buffer) {
90
90
  return undefined;
91
91
  return (await reader?.readWriteLoss?.()) ?? undefined;
92
92
  }
93
+ /**
94
+ * Did the EVENT STORE lose scarce evidence from a window opened at `cursor`? The input to whether a
95
+ * verdict's capture was clean — see `RingBuffer.lostSince`, and never the raw drop counter, which
96
+ * moves for the age and churn evictions that every live page produces continuously. The journal is
97
+ * the other half of that store, so a durable read that could not reach back to `cursor` lost
98
+ * exactly what an eviction would have. Both boundaries are INCLUSIVE (`t` is a millisecond many
99
+ * records share) and an absent `lostThroughT` impeaches every window rather than none — the
100
+ * conservative direction, both times.
101
+ */
102
+ export function windowLost(reader, buffer, cursor) {
103
+ if (buffer.lostSince(cursor))
104
+ return true;
105
+ const lost = reader?.readLoss?.();
106
+ return lost !== undefined && (lost.lostThroughT === undefined || lost.lostThroughT >= cursor);
107
+ }
93
108
  /** Apply since/until/actionId bounds to an event list. */
94
109
  export function filterEvents(events, options) {
95
110
  return events.filter((event) => (options.since === undefined || event.t >= options.since) &&
@@ -49,6 +49,8 @@ export const WORKSPACE_TIERS = {
49
49
  [ReticleDir.AMBIENT_FILE]: localFile,
50
50
  [ReticleDir.ENVELOPES_FILE]: localFile,
51
51
  [ReticleDir.FLAKE_FILE]: localFile,
52
+ // What THIS machine's drives covered, and the best each level reached — the gate's ratchet.
53
+ [ReticleDir.COVERAGE_FILE]: localFile,
52
54
  [ReticleDir.TIERS_FILE]: localFile,
53
55
  // The user's own record of what Reticle did for them, on THIS machine.
54
56
  [ReticleDir.IMPACT_FILE]: localFile,
@@ -79,8 +79,16 @@ interface SessionEndDeps {
79
79
  * this handler follows.
80
80
  */
81
81
  flows?: {
82
- save: (program: DriveProgram, annotations?: undefined, projectId?: ProjectId) => Promise<unknown>;
82
+ saveJourney: (program: DriveProgram, projectId: ProjectId | undefined, prefix: string) => Promise<unknown>;
83
83
  };
84
+ /**
85
+ * The flow store for a session's own `.reticle`. Drive flows used to go to `flows` above — the
86
+ * daemon's launch directory — while replay reads the session's project, so a daemon started
87
+ * anywhere else saved flows nothing could replay. Absent, `flows` is used.
88
+ */
89
+ flowsAt?: (root: string) => NonNullable<SessionEndDeps['flows']>;
90
+ /** Who is driving — stamped on each saved flow. Injected: teardown does not reach into MCP. */
91
+ author?: () => DriveProgram['author'];
84
92
  /**
85
93
  * Called when a run artifact is written, so cloud sync can cycle instead of waiting for its timer.
86
94
  *
@@ -1,4 +1,11 @@
1
- import { driveFlowsFrom } from './drive-flow.js';
1
+ import { DRIVE_FLOW_PREFIX, driveFlowsFrom, } from './drive-flow.js';
2
+ import { log } from '../../log.js';
3
+ /** What a session's teardown writes to the daemon log when a by-product fails to save. */
4
+ const SESSION_END_LOG = {
5
+ FLOW_SAVE_FAILED: 'reticle_drive_flow_save_failed',
6
+ RUN_RECORD_FAILED: 'reticle_drive_run_record_failed',
7
+ };
8
+ const errorMessage = (error) => error instanceof Error ? error.message : String(error);
2
9
  import { journalActionsPath, sessionDirPath } from '../project/dir/reticle-dir.js';
3
10
  import { asSessionId } from '@reticlehq/core';
4
11
  import { OnboardingPhase, OnboardingStepStatus, } from '@reticlehq/core/telemetry';
@@ -78,24 +85,27 @@ export function makeSessionEnd(deps) {
78
85
  // would buy under a minute of latency for a mutable handle held across two wiring sites.
79
86
  // A DRIVE IS A REGRESSION TEST, and it used to need somebody to remember to say so.
80
87
  //
81
- // `record{start}` + `flow_save` is the agent-facing route and agents do not take it: the corpus
82
- // measures 3 of 33 flows mutation-testable and 6 of 112 steps declaring a consequence, while the
83
- // engine catches 84 of the 86 bugs it structurally can. The ceiling is how many flows EXIST that
84
- // could go red, so a drive that is never saved is a regression test paid for and thrown away.
88
+ // `record{start}` + `flow_save` is the agent-facing route and agents do not take it: few saved
89
+ // flows are mutation-testable and few steps declare a consequence, while the engine catches
90
+ // nearly every bug it structurally can. The ceiling is how many flows EXIST that could go red,
91
+ // so a drive that is never saved is a regression test paid for and thrown away.
85
92
  //
86
93
  // Saved only when the tape declared a consequence — see drive-flow.ts for why the alternative is
87
94
  // a false-green factory running once per session.
88
95
  try {
89
96
  await saveDrivenFlow(deps, session);
90
97
  }
91
- catch {
92
- // a flow is a by-product of the session; failing to write one never fails the teardown
98
+ catch (error) {
99
+ // A flow is a by-product of the session; failing to write one never fails the teardown. It is
100
+ // logged, because a drive that leaves no flow and no reason is otherwise undiagnosable.
101
+ log(SESSION_END_LOG.FLOW_SAVE_FAILED, { error: errorMessage(error) });
93
102
  }
94
103
  try {
95
104
  await recordDriveRun(deps, session);
96
105
  }
97
- catch {
98
- // an artifact is a report ABOUT the session; failing to write one never fails the teardown
106
+ catch (error) {
107
+ // An artifact is a report ABOUT the session; failing to write one never fails the teardown.
108
+ log(SESSION_END_LOG.RUN_RECORD_FAILED, { error: errorMessage(error) });
99
109
  }
100
110
  try {
101
111
  // Bound the journal on disk HERE, not only at daemon start.
@@ -196,13 +206,15 @@ export async function recordDriveRun(deps, session) {
196
206
  /** Persist what this session drove as a replayable flow, when it declared anything provable. */
197
207
  async function saveDrivenFlow(deps, session) {
198
208
  const takeTape = deps.takeAmbientTape;
199
- const flows = deps.flows;
209
+ const flows = deps.flowsAt?.(session.artifactRoot ?? deps.reticleRoot) ?? deps.flows;
200
210
  if (takeTape === undefined || flows === undefined)
201
211
  return;
202
212
  // One flow per journey the session contained, not one flow per session — see drive-flow.ts.
203
- const { programs, outcome } = driveFlowsFrom(session.id, takeTape());
213
+ const { programs, outcome } = driveFlowsFrom(takeTape());
214
+ const author = deps.author?.();
204
215
  for (const program of programs) {
205
- await flows.save(program, undefined, session.projectId);
216
+ // Folded into any saved copy of the same journey, so each session does not add a near-copy.
217
+ await flows.saveJourney(author === undefined ? program : { ...program, author }, session.projectId, DRIVE_FLOW_PREFIX);
206
218
  }
207
219
  // The funnel step that makes the SECOND run cheap.
208
220
  //
@@ -1,3 +1,13 @@
1
1
  import { type ProjectId } from '@reticlehq/core';
2
2
  import { type ArtifactRoot } from './artifact-root.js';
3
+ /**
4
+ * The directory of the project a page announced, or undefined when this machine does not know it.
5
+ *
6
+ * A daemon is shared: started from `$HOME`, a monorepo root, or another project's MCP client, its
7
+ * cwd is often not the app that sent the HELLO. The version-skew remedy read `package.json` from
8
+ * cwd anyway, so it fell back to the generic sensor package and told a Next.js project to install
9
+ * `@reticlehq/browser` (#1135). This is the same lookup the artifact root uses, keyed by the page's
10
+ * own project id.
11
+ */
12
+ export declare function projectDirectoryFor(projectId: string | undefined): string | undefined;
3
13
  export declare function artifactRootResolver(daemonRoot: string): (projectId: ProjectId | undefined, origin?: string) => ArtifactRoot;