firedrill-run 2.2.4 → 3.0.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 (392) hide show
  1. package/README.md +178 -22
  2. package/bin/firedrill.mjs +29 -5
  3. package/dist/assembly.json +13 -0
  4. package/dist/cli/runtime/candidate_copy.py +395 -0
  5. package/dist/cli/runtime/source_snapshot.py +391 -0
  6. package/dist/cli/src/administration.js +295 -0
  7. package/dist/cli/src/agent-changes.js +496 -0
  8. package/dist/cli/src/assessment-requests.js +67 -0
  9. package/dist/cli/src/auth.js +465 -0
  10. package/dist/cli/src/billing-commerce-contracts.js +1 -0
  11. package/dist/cli/src/billing-usage.js +227 -0
  12. package/dist/cli/src/billing-wallet-contracts.js +1 -0
  13. package/dist/cli/src/billing.js +170 -0
  14. package/dist/cli/src/bootstrap-process-lock.js +136 -0
  15. package/dist/cli/src/browser-links.js +14 -0
  16. package/dist/cli/src/campaign-controls.js +122 -0
  17. package/dist/cli/src/campaign-diagnoses.js +118 -0
  18. package/dist/cli/src/campaign-reports.js +1215 -0
  19. package/dist/cli/src/campaign-world.js +156 -0
  20. package/dist/cli/src/candidate-copy.js +376 -0
  21. package/dist/cli/src/catalog-contracts.js +1 -0
  22. package/dist/cli/src/ci-access-contracts.js +1 -0
  23. package/dist/cli/src/ci-configuration-contracts.js +1 -0
  24. package/dist/cli/src/ci-coverage.js +64 -0
  25. package/dist/cli/src/ci-delivery-contracts.js +1 -0
  26. package/dist/cli/src/ci-result-contracts.js +1 -0
  27. package/dist/cli/src/ci-run-contracts.js +1 -0
  28. package/dist/cli/src/ci-setup-proposals.js +52 -0
  29. package/dist/cli/src/company-policies.js +229 -0
  30. package/dist/cli/src/completion.js +79 -0
  31. package/dist/cli/src/configuration-intents.js +277 -0
  32. package/dist/cli/src/configuration.js +369 -0
  33. package/dist/cli/src/continuous-simulation.js +277 -0
  34. package/dist/cli/src/coordinated-checkpoints.js +133 -0
  35. package/dist/cli/src/credential-mode.js +139 -0
  36. package/dist/cli/src/credentials.js +331 -0
  37. package/dist/cli/src/credit-format.js +8 -0
  38. package/dist/cli/src/dataset-contracts.js +1 -0
  39. package/dist/cli/src/dataset-export.js +154 -0
  40. package/dist/cli/src/dataset-reads.js +37 -0
  41. package/dist/cli/src/dependency-preparation.js +239 -0
  42. package/dist/cli/src/director-tool-setup.js +104 -0
  43. package/dist/cli/src/driver-review.js +225 -0
  44. package/dist/cli/src/evaluation-authoring.js +315 -0
  45. package/dist/cli/src/evidence-bundles.js +367 -0
  46. package/dist/cli/src/execution-proposals.js +36 -0
  47. package/dist/cli/src/http.js +302 -0
  48. package/dist/cli/src/local-container-boundary.js +38 -0
  49. package/dist/cli/src/local-container-policy.js +201 -0
  50. package/dist/cli/src/local-execution-setup.js +81 -0
  51. package/dist/cli/src/local-source-basis.js +73 -0
  52. package/dist/cli/src/local-source.js +66 -0
  53. package/dist/cli/src/local-value-store.js +91 -0
  54. package/dist/cli/src/local-values.js +163 -0
  55. package/dist/cli/src/login-organizations.js +70 -0
  56. package/dist/cli/src/main.js +1622 -0
  57. package/dist/cli/src/mcp-connection.js +109 -0
  58. package/dist/cli/src/native/bindings.js +137 -0
  59. package/dist/cli/src/native/canonical.js +370 -0
  60. package/dist/cli/src/native/cloud-resources.js +942 -0
  61. package/dist/cli/src/native/companion-channel.js +421 -0
  62. package/dist/cli/src/native/companion-ipc.js +473 -0
  63. package/dist/cli/src/native/contracts.js +218 -0
  64. package/dist/cli/src/native/control-plane.js +1354 -0
  65. package/dist/cli/src/native/credit-approval.js +15 -0
  66. package/dist/cli/src/native/dependency-proxy.js +1006 -0
  67. package/dist/cli/src/native/drill-cleanup.js +115 -0
  68. package/dist/cli/src/native/drill-runner.js +1562 -0
  69. package/dist/cli/src/native/drivers/action-trace.js +210 -0
  70. package/dist/cli/src/native/drivers/function-wrapper.js +152 -0
  71. package/dist/cli/src/native/drivers/http.js +442 -0
  72. package/dist/cli/src/native/drivers/index.js +47 -0
  73. package/dist/cli/src/native/drivers/jsonl-process.js +538 -0
  74. package/dist/cli/src/native/drivers/playwright.js +600 -0
  75. package/dist/cli/src/native/drivers/signed-event.js +244 -0
  76. package/dist/cli/src/native/drivers/types.js +578 -0
  77. package/dist/cli/src/native/evidence-filter.js +255 -0
  78. package/dist/cli/src/native/isolated-access.js +241 -0
  79. package/dist/cli/src/native/library-sharing.js +220 -0
  80. package/dist/cli/src/native/local-run.js +667 -0
  81. package/dist/cli/src/native/local-secrets.js +19 -0
  82. package/dist/cli/src/native/mcp-config.js +123 -0
  83. package/dist/cli/src/native/metadata-requests.js +120 -0
  84. package/dist/cli/src/native/observations.js +1159 -0
  85. package/dist/cli/src/native/owned-groups.js +46 -0
  86. package/dist/cli/src/native/plan.js +594 -0
  87. package/dist/cli/src/native/png.js +159 -0
  88. package/dist/cli/src/native/ports.js +43 -0
  89. package/dist/cli/src/native/readiness.js +139 -0
  90. package/dist/cli/src/native/reassessments.js +206 -0
  91. package/dist/cli/src/native/redaction.js +235 -0
  92. package/dist/cli/src/native/registration.js +155 -0
  93. package/dist/cli/src/native/roots.js +110 -0
  94. package/dist/cli/src/native/run-cleanup.js +199 -0
  95. package/dist/cli/src/native/scratch.js +117 -0
  96. package/dist/cli/src/native/screenshots.js +60 -0
  97. package/dist/cli/src/native/session.js +556 -0
  98. package/dist/cli/src/native/standalone-proxy.js +580 -0
  99. package/dist/cli/src/native/standalone-renewals.js +183 -0
  100. package/dist/cli/src/native/standalone-state.js +193 -0
  101. package/dist/cli/src/native/standalone-tools.js +187 -0
  102. package/dist/cli/src/native/supervisor.js +831 -0
  103. package/dist/cli/src/native/target-auth.js +161 -0
  104. package/dist/cli/src/native/target-state.js +330 -0
  105. package/dist/cli/src/native/tool-reuse.js +364 -0
  106. package/dist/cli/src/native/tool-setups.js +336 -0
  107. package/dist/cli/src/native/world-reuse.js +288 -0
  108. package/dist/cli/src/onboarding.js +23 -0
  109. package/dist/cli/src/priority-requests.js +70 -0
  110. package/dist/cli/src/priority.js +223 -0
  111. package/dist/cli/src/prompts.js +184 -0
  112. package/dist/cli/src/real-operations.js +221 -0
  113. package/dist/cli/src/recipe-intents.js +58 -0
  114. package/dist/cli/src/recorded-context-intents.js +59 -0
  115. package/dist/cli/src/recorded-context.js +248 -0
  116. package/dist/cli/src/recordings.js +69 -0
  117. package/dist/cli/src/registration-guide.js +39 -0
  118. package/dist/cli/src/report-exports.js +213 -0
  119. package/dist/cli/src/request-input.js +47 -0
  120. package/dist/cli/src/resource-lifecycle.js +141 -0
  121. package/dist/cli/src/revision-requests.js +55 -0
  122. package/dist/cli/src/runner-abandon.js +53 -0
  123. package/dist/cli/src/runner-campaign-controls.js +262 -0
  124. package/dist/cli/src/runner-claim-reads.js +36 -0
  125. package/dist/cli/src/runner-completion.js +65 -0
  126. package/dist/cli/src/runner-configuration.js +546 -0
  127. package/dist/cli/src/runner-container.js +794 -0
  128. package/dist/cli/src/runner-evidence-erasure.js +351 -0
  129. package/dist/cli/src/runner-evidence-privacy.js +37 -0
  130. package/dist/cli/src/runner-evidence-retention.js +161 -0
  131. package/dist/cli/src/runner-evidence.js +326 -0
  132. package/dist/cli/src/runner-execute.js +92 -0
  133. package/dist/cli/src/runner-execution-deadline.js +25 -0
  134. package/dist/cli/src/runner-grants.js +307 -0
  135. package/dist/cli/src/runner-identity.js +615 -0
  136. package/dist/cli/src/runner-inputs.js +308 -0
  137. package/dist/cli/src/runner-jobs.js +49 -0
  138. package/dist/cli/src/runner-model-transport.js +392 -0
  139. package/dist/cli/src/runner-ownership.js +332 -0
  140. package/dist/cli/src/runner-process-lock.js +175 -0
  141. package/dist/cli/src/runner-runtime.js +396 -0
  142. package/dist/cli/src/runner-session-channel.js +377 -0
  143. package/dist/cli/src/runner-trial-completion.js +34 -0
  144. package/dist/cli/src/runner-trial-journal.js +358 -0
  145. package/dist/cli/src/runner-trial-live.js +393 -0
  146. package/dist/cli/src/runner-trial-protocol.js +289 -0
  147. package/dist/cli/src/runner-trial-sync.js +110 -0
  148. package/dist/cli/src/runner-trial-transfer.js +107 -0
  149. package/dist/cli/src/runner-voice.js +40 -0
  150. package/dist/cli/src/runner-world-claim.js +21 -0
  151. package/dist/cli/src/runner-world-collector.js +159 -0
  152. package/dist/cli/src/runner-world-command-evidence.js +487 -0
  153. package/dist/cli/src/runner-world-commands.js +268 -0
  154. package/dist/cli/src/runner-world-completion.js +76 -0
  155. package/dist/cli/src/runner-world-erasure.js +95 -0
  156. package/dist/cli/src/runner-world-evidence-sync.js +91 -0
  157. package/dist/cli/src/runner-world-journal.js +510 -0
  158. package/dist/cli/src/runner-world-native-filters.js +134 -0
  159. package/dist/cli/src/runner-world-observations.js +50 -0
  160. package/dist/cli/src/runner-world-session-protocol.js +134 -0
  161. package/dist/cli/src/saved-records.js +40 -0
  162. package/dist/cli/src/scenario-authoring.js +212 -0
  163. package/dist/cli/src/scenario-discovery.js +216 -0
  164. package/dist/cli/src/scenario-paths.js +68 -0
  165. package/dist/cli/src/scenario-requests.js +4 -0
  166. package/dist/cli/src/service-receipts.js +105 -0
  167. package/dist/cli/src/simple/authoring.js +270 -0
  168. package/dist/cli/src/simple/credit-approval.js +16 -0
  169. package/dist/cli/src/simple/help.js +98 -0
  170. package/dist/cli/src/simple/init.js +61 -0
  171. package/dist/cli/src/simple/library-sharing.js +414 -0
  172. package/dist/cli/src/simple/metadata-requests.js +46 -0
  173. package/dist/cli/src/simple/native-report.js +841 -0
  174. package/dist/cli/src/simple/native-tools.js +780 -0
  175. package/dist/cli/src/simple/plan-apply.js +506 -0
  176. package/dist/cli/src/simple/reassessment.js +323 -0
  177. package/dist/cli/src/simple/register.js +123 -0
  178. package/dist/cli/src/simple/run.js +122 -0
  179. package/dist/cli/src/simple/status.js +49 -0
  180. package/dist/cli/src/simple/tool-renewal.js +308 -0
  181. package/dist/cli/src/simple/tool-reuse.js +526 -0
  182. package/dist/cli/src/simple/tool-start-catalog.js +100 -0
  183. package/dist/cli/src/simple/tool-state.js +426 -0
  184. package/dist/cli/src/simple/tools-author.js +222 -0
  185. package/dist/cli/src/simple/watch.js +28 -0
  186. package/dist/cli/src/simple/world-reuse.js +586 -0
  187. package/dist/cli/src/simulation-intents.js +406 -0
  188. package/dist/cli/src/simulation-run.js +1058 -0
  189. package/dist/cli/src/source-batch.js +322 -0
  190. package/dist/cli/src/source-connection.js +451 -0
  191. package/dist/cli/src/source-runtime.js +346 -0
  192. package/dist/cli/src/source-selection-intents.js +75 -0
  193. package/dist/cli/src/source-sets.js +214 -0
  194. package/dist/cli/src/target-models.js +109 -0
  195. package/dist/cli/src/tool-catalog.js +224 -0
  196. package/dist/cli/src/tool-command-contracts.js +1 -0
  197. package/dist/cli/src/tool-command-export.js +245 -0
  198. package/dist/cli/src/tool-connection-contracts.js +1 -0
  199. package/dist/cli/src/tool-connection-reads.js +23 -0
  200. package/dist/cli/src/tool-contributions.js +58 -0
  201. package/dist/cli/src/tool-data.js +116 -0
  202. package/dist/cli/src/tool-datasets.js +176 -0
  203. package/dist/cli/src/tool-group-contracts.js +1 -0
  204. package/dist/cli/src/tool-group-reads.js +26 -0
  205. package/dist/cli/src/tool-group-state.js +147 -0
  206. package/dist/cli/src/tool-groups.js +311 -0
  207. package/dist/cli/src/tool-instances.js +186 -0
  208. package/dist/cli/src/tool-lifecycle-contracts.js +1 -0
  209. package/dist/cli/src/tool-lifecycle-reads.js +30 -0
  210. package/dist/cli/src/tool-lifecycle.js +323 -0
  211. package/dist/cli/src/tool-package-contracts.js +1 -0
  212. package/dist/cli/src/tool-package-reads.js +41 -0
  213. package/dist/cli/src/tool-records.js +53 -0
  214. package/dist/cli/src/tool-runtime.js +96 -0
  215. package/dist/cli/src/tool-state-contracts.js +1 -0
  216. package/dist/cli/src/tool-state-reads.js +31 -0
  217. package/dist/cli/src/tools.js +306 -0
  218. package/dist/cli/src/trial-actions.js +93 -0
  219. package/dist/cli/src/trial-checkpoints.js +133 -0
  220. package/dist/cli/src/trial-checks.js +70 -0
  221. package/dist/cli/src/trial-state.js +197 -0
  222. package/dist/cli/src/vault.js +266 -0
  223. package/dist/cli/src/workspace-registration.js +383 -0
  224. package/dist/cli/src/workspace-team-contracts.js +1 -0
  225. package/dist/cli/src/world-access.js +231 -0
  226. package/dist/cli/src/world-agent.js +4 -0
  227. package/dist/cli/src/world-checkpoint-scopes.js +15 -0
  228. package/dist/cli/src/world-contracts.js +6 -0
  229. package/dist/cli/src/world-creation.js +201 -0
  230. package/dist/cli/src/world-datasets.js +207 -0
  231. package/dist/cli/src/world-inspection.js +257 -0
  232. package/dist/cli/src/world-recipes.js +103 -0
  233. package/dist/cli/src/world-requests.js +241 -0
  234. package/dist/cli/src/world-session-checkpoints.js +196 -0
  235. package/dist/cli/src/world-session-lifecycle.js +207 -0
  236. package/dist/cli/src/world-session-requests.js +215 -0
  237. package/dist/cli/src/world-session-state.js +145 -0
  238. package/dist/cli/src/world-session-voice.js +66 -0
  239. package/dist/cli/src/world-sessions.js +288 -0
  240. package/dist/cli/src/world-stream.js +99 -0
  241. package/dist/cli/src/world-time-report.js +58 -0
  242. package/dist/contracts/agent-change-contracts.js +75 -0
  243. package/dist/contracts/agent-execution-contracts.js +38 -0
  244. package/dist/contracts/application-state-contracts.js +26 -0
  245. package/dist/contracts/assessment-aggregate-contracts.js +15 -0
  246. package/dist/contracts/assessment-contracts.js +74 -0
  247. package/dist/contracts/billing-commerce-contracts.js +107 -0
  248. package/dist/contracts/billing-wallet-contracts.js +43 -0
  249. package/dist/contracts/campaign-lifecycle.js +1 -0
  250. package/dist/contracts/campaign-world-contracts.js +80 -0
  251. package/dist/contracts/catalog-contracts.js +73 -0
  252. package/dist/contracts/ci-access-contracts.js +68 -0
  253. package/dist/contracts/ci-configuration-contracts.js +88 -0
  254. package/dist/contracts/ci-coverage-contracts.js +65 -0
  255. package/dist/contracts/ci-coverage-labels.js +25 -0
  256. package/dist/contracts/ci-delivery-contracts.js +61 -0
  257. package/dist/contracts/ci-result-contracts.js +103 -0
  258. package/dist/contracts/ci-run-contracts.js +51 -0
  259. package/dist/contracts/ci-setup-contracts.js +88 -0
  260. package/dist/contracts/clip-contracts.js +32 -0
  261. package/dist/contracts/comparison-contracts.js +53 -0
  262. package/dist/contracts/composite-contracts.js +34 -0
  263. package/dist/contracts/context-contracts.js +76 -0
  264. package/dist/contracts/continuation-report-contracts.js +1 -0
  265. package/dist/contracts/continuity-requirements.js +8 -0
  266. package/dist/contracts/coordinated-checkpoint-contracts.js +34 -0
  267. package/dist/contracts/dataset-contracts.js +88 -0
  268. package/dist/contracts/dependency-decision-contracts.js +36 -0
  269. package/dist/contracts/dependency-protocol-contracts.js +43 -0
  270. package/dist/contracts/director-execution.js +51 -0
  271. package/dist/contracts/driver-review-contracts.js +99 -0
  272. package/dist/contracts/evidence-contracts.js +65 -0
  273. package/dist/contracts/evidence-coverage-contracts.js +10 -0
  274. package/dist/contracts/export-contracts.js +44 -0
  275. package/dist/contracts/github-directory-contracts.js +96 -0
  276. package/dist/contracts/insight-contracts.js +11 -0
  277. package/dist/contracts/inspection-contracts.js +1 -0
  278. package/dist/contracts/local-claim-contracts.js +39 -0
  279. package/dist/contracts/local-configuration-contracts.js +7 -0
  280. package/dist/contracts/local-evidence-contracts.js +32 -0
  281. package/dist/contracts/local-grant-contracts.js +83 -0
  282. package/dist/contracts/local-input-contracts.js +19 -0
  283. package/dist/contracts/local-runner-contracts.js +42 -0
  284. package/dist/contracts/local-trial-live-contracts.js +16 -0
  285. package/dist/contracts/local-value-source-contracts.js +92 -0
  286. package/dist/contracts/local-world-native-contracts.js +8 -0
  287. package/dist/contracts/mcp-contracts.js +42 -0
  288. package/dist/contracts/media-contracts.js +28 -0
  289. package/dist/contracts/native-check-contracts.js +31 -0
  290. package/dist/contracts/native-identity-contracts.js +52 -0
  291. package/dist/contracts/onboarding-contracts.js +32 -0
  292. package/dist/contracts/performance-contracts.js +85 -0
  293. package/dist/contracts/planning-contracts.js +81 -0
  294. package/dist/contracts/real-operation-contracts.js +30 -0
  295. package/dist/contracts/recommendation-contracts.js +54 -0
  296. package/dist/contracts/report-contracts.js +1 -0
  297. package/dist/contracts/resource-lifecycle-contracts.js +57 -0
  298. package/dist/contracts/revision-contracts.js +12 -0
  299. package/dist/contracts/revision-request-contracts.js +39 -0
  300. package/dist/contracts/saved-record-contracts.js +76 -0
  301. package/dist/contracts/scenario-draft-comparison.js +91 -0
  302. package/dist/contracts/scenario-world-contracts.js +14 -0
  303. package/dist/contracts/service-contracts.js +1 -0
  304. package/dist/contracts/session-command-contracts.js +56 -0
  305. package/dist/contracts/session-contracts.js +65 -0
  306. package/dist/contracts/session-state-contracts.js +67 -0
  307. package/dist/contracts/session-voice-contracts.js +45 -0
  308. package/dist/contracts/source-set-contracts.js +26 -0
  309. package/dist/contracts/source-upload-contracts.js +22 -0
  310. package/dist/contracts/tool-command-contracts.js +57 -0
  311. package/dist/contracts/tool-connection-contracts.js +87 -0
  312. package/dist/contracts/tool-contribution-contracts.js +35 -0
  313. package/dist/contracts/tool-group-contracts.js +53 -0
  314. package/dist/contracts/tool-group-state-contracts.js +63 -0
  315. package/dist/contracts/tool-lifecycle-contracts.js +94 -0
  316. package/dist/contracts/tool-package-contracts.js +91 -0
  317. package/dist/contracts/tool-protocol-contracts.js +71 -0
  318. package/dist/contracts/tool-runtime-billing-contracts.js +28 -0
  319. package/dist/contracts/tool-setup-proposal.js +59 -0
  320. package/dist/contracts/tool-state-contracts.js +79 -0
  321. package/dist/contracts/trial-state-contracts.js +30 -0
  322. package/dist/contracts/typescript/agent-change.js +1 -0
  323. package/dist/contracts/typescript/assessment-aggregate.js +1 -0
  324. package/dist/contracts/typescript/assessment.js +1 -0
  325. package/dist/contracts/typescript/billing-commerce.js +1 -0
  326. package/dist/contracts/typescript/campaigns.js +1 -0
  327. package/dist/contracts/typescript/catalog.js +1 -0
  328. package/dist/contracts/typescript/ci-access.js +1 -0
  329. package/dist/contracts/typescript/ci-configuration.js +1 -0
  330. package/dist/contracts/typescript/ci-delivery.js +1 -0
  331. package/dist/contracts/typescript/ci-result.js +1 -0
  332. package/dist/contracts/typescript/ci-runs.js +1 -0
  333. package/dist/contracts/typescript/clips.js +1 -0
  334. package/dist/contracts/typescript/comparison.js +1 -0
  335. package/dist/contracts/typescript/composite.js +1 -0
  336. package/dist/contracts/typescript/connections.js +1 -0
  337. package/dist/contracts/typescript/context.js +1 -0
  338. package/dist/contracts/typescript/datasets.js +1 -0
  339. package/dist/contracts/typescript/evidence-coverage.js +1 -0
  340. package/dist/contracts/typescript/export.js +1 -0
  341. package/dist/contracts/typescript/github-directory.js +1 -0
  342. package/dist/contracts/typescript/insight.js +1 -0
  343. package/dist/contracts/typescript/inspection.js +1 -0
  344. package/dist/contracts/typescript/intake.js +1 -0
  345. package/dist/contracts/typescript/local-claim.js +1 -0
  346. package/dist/contracts/typescript/local-configuration.js +1 -0
  347. package/dist/contracts/typescript/local-evidence.js +1 -0
  348. package/dist/contracts/typescript/local-grant.js +1 -0
  349. package/dist/contracts/typescript/local-input.js +1 -0
  350. package/dist/contracts/typescript/local-runner.js +1 -0
  351. package/dist/contracts/typescript/local-trial-live.js +1 -0
  352. package/dist/contracts/typescript/local-value-source.js +1 -0
  353. package/dist/contracts/typescript/local-world-native.js +1 -0
  354. package/dist/contracts/typescript/mcp.js +1 -0
  355. package/dist/contracts/typescript/media.js +1 -0
  356. package/dist/contracts/typescript/native-check.js +1 -0
  357. package/dist/contracts/typescript/native-identity.js +1 -0
  358. package/dist/contracts/typescript/onboarding.js +1 -0
  359. package/dist/contracts/typescript/performance.js +1 -0
  360. package/dist/contracts/typescript/planning.js +1 -0
  361. package/dist/contracts/typescript/preparation.js +1 -0
  362. package/dist/contracts/typescript/real-operation.js +1 -0
  363. package/dist/contracts/typescript/recommendation.js +1 -0
  364. package/dist/contracts/typescript/reports.js +1 -0
  365. package/dist/contracts/typescript/resource-lifecycle.js +1 -0
  366. package/dist/contracts/typescript/saved-record.js +1 -0
  367. package/dist/contracts/typescript/scenarios.js +1 -0
  368. package/dist/contracts/typescript/services.js +1 -0
  369. package/dist/contracts/typescript/session-command.js +1 -0
  370. package/dist/contracts/typescript/session-state.js +1 -0
  371. package/dist/contracts/typescript/session-voice.js +1 -0
  372. package/dist/contracts/typescript/session.js +1 -0
  373. package/dist/contracts/typescript/tool-command.js +1 -0
  374. package/dist/contracts/typescript/tool-connection.js +1 -0
  375. package/dist/contracts/typescript/tool-contribution.js +1 -0
  376. package/dist/contracts/typescript/tool-groups.js +1 -0
  377. package/dist/contracts/typescript/tool-lifecycle.js +1 -0
  378. package/dist/contracts/typescript/tool-package.js +1 -0
  379. package/dist/contracts/typescript/tool-state.js +1 -0
  380. package/dist/contracts/typescript/trial-state.js +1 -0
  381. package/dist/contracts/typescript/vault.js +1 -0
  382. package/dist/contracts/typescript/workspace-team.js +1 -0
  383. package/dist/contracts/typescript/world-stream.js +1 -0
  384. package/dist/contracts/workspace-team-contracts.js +55 -0
  385. package/dist/contracts/world-command.js +65 -0
  386. package/dist/contracts/world-lifetime-contracts.js +74 -0
  387. package/dist/contracts/world-recipe-contracts.js +27 -0
  388. package/dist/contracts/world-stream-contracts.js +27 -0
  389. package/dist/package.json +1 -0
  390. package/package.json +16 -16
  391. package/LICENSE.md +0 -23
  392. package/PACKAGING.json +0 -50
@@ -0,0 +1,600 @@
1
+ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
2
+ if (typeof path === "string" && /^\.\.?\//.test(path)) {
3
+ return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
4
+ return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
5
+ });
6
+ }
7
+ return path;
8
+ };
9
+ /**
10
+ * playwright.v1 driver (Plan20 spec 3.3).
11
+ *
12
+ * Playwright is customer-provided: it is resolved from the approved customer
13
+ * root with createRequire(<root>/package.json) and loaded with a dynamic import
14
+ * of the resolved file URL. Firedrill never bundles, installs or downloads it
15
+ * (nor its browsers). describe() reports unavailable, with the reason, when the
16
+ * package cannot be resolved; prepare() also fails clearly when the browser
17
+ * binary cannot be launched. The browser used is Chromium, headless.
18
+ *
19
+ * Flow module: an approved file inside the root, pinned by ctx.adapterSha256,
20
+ * re-hashed before import and imported via its file URL (digest in the query so
21
+ * a changed file is never served from the module cache). It exports
22
+ * login(page, identity) identity = target-auth PlaywrightLoginAccessor | null
23
+ * send(page, step) step = target-visible view only; returns observed output
24
+ * reset(page) optional; resets client-side view state
25
+ * as named exports or as properties of the default export.
26
+ *
27
+ * One browser context per drill session (fresh cookies/storage). A context reset
28
+ * or the flow's reset() is reported as a browser-context reset with
29
+ * backend_reset:false; it is never reported as a backend reset.
30
+ *
31
+ * Screenshots are off unless the approved plan turns them on (`screenshots`).
32
+ * Then each one is the visible page as a PNG, taken with every form field and
33
+ * the plan's selectors blacked out first (screenshots.ts), re-encoded without
34
+ * metadata (png.ts) and capped in count. Masking is not secret detection: other
35
+ * text on screen is captured as shown, so flows should keep secrets off screen.
36
+ * Each drill records that capture was requested and whether it can run
37
+ * (`capture.requested`); a step whose capture produced no image records why
38
+ * (`capture.unavailable`), never the error or page content.
39
+ * With `screenshots.frames` the driver also captures masked frames while each
40
+ * step's send() runs, one per interval and at most `max_per_step` per step,
41
+ * with animations left running so the flow's page is not changed. They are
42
+ * captured frames, not a video. A frame that produced no image, and every
43
+ * interval past the step's count, is counted with its reason.
44
+ * With `trace: "sanitized-actions"` the flow gets its page through a recording
45
+ * view (action-trace.ts) that keeps only operation kinds, outcomes and times.
46
+ * Playwright video, Playwright trace files and HAR are never used: they would
47
+ * carry unmasked frames, input values or network bodies.
48
+ * The flow runs in the companion process as trusted, user-approved local code.
49
+ * A step whose send() timed out leaves the page in an unknown state, so that
50
+ * session's context is closed and later steps in it fail explicitly.
51
+ */
52
+ import { readFileSync } from 'node:fs';
53
+ import { createRequire } from 'node:module';
54
+ import path from 'node:path';
55
+ import { pathToFileURL } from 'node:url';
56
+ import { CliError } from '../../http.js';
57
+ import { CAPTURE_SCHEMA, FRAME_OMISSION_REASONS, NATIVE_ERRORS, nativeError } from '../contracts.js';
58
+ import { metadataFreePng } from '../png.js';
59
+ import { MASK_COLOR, maskingPolicy } from '../screenshots.js';
60
+ import { ActionTrace } from './action-trace.js';
61
+ import { sha256Hex } from '../canonical.js';
62
+ import { insideRoot } from '../roots.js';
63
+ import { playwrightLogin } from '../target-auth.js';
64
+ import { DRIVER_ERRORS, OwnDigest, StepRecorder, clockOf, driverBounds, driverPlanFor, emit, emitTrace, loopbackBase, loopbackTarget, planError, precheck, readPinned, rootPath, serviceHandle, stepAbort, stepInput, stepTimeoutMs, unattested, } from './types.js';
65
+ /** Playwright's own version from the project's installed package, or null when it cannot be read. */
66
+ function playwrightVersion(root) {
67
+ try {
68
+ const manifest = createRequire(path.join(root, 'package.json')).resolve('playwright/package.json');
69
+ const version = JSON.parse(readFileSync(manifest, 'utf8'))?.version;
70
+ const match = typeof version === 'string' ? /^(\d+)\.(\d+)\./.exec(version) : null;
71
+ return match ? [Number(match[1]), Number(match[2])] : null;
72
+ }
73
+ catch {
74
+ return null;
75
+ }
76
+ }
77
+ /** Screenshot masking with a fixed mask color needs Playwright 1.35 or later. */
78
+ const MASK_MIN_VERSION = [1, 35];
79
+ /** Resolve the customer's Playwright entry from a root, or null when absent. */
80
+ export function resolvePlaywright(root) {
81
+ if (!root || !path.isAbsolute(root))
82
+ return null;
83
+ try {
84
+ return createRequire(path.join(root, 'package.json')).resolve('playwright');
85
+ }
86
+ catch {
87
+ return null;
88
+ }
89
+ }
90
+ function browserTypeFrom(module) {
91
+ const record = module;
92
+ const candidate = (record?.chromium ?? record?.default?.chromium);
93
+ return candidate && typeof candidate.launch === 'function' ? candidate : null;
94
+ }
95
+ function flowFrom(module) {
96
+ const record = module;
97
+ const source = (typeof record?.send === 'function' ? record : record?.default);
98
+ if (!source || typeof source.login !== 'function' || typeof source.send !== 'function')
99
+ return null;
100
+ if (source.reset !== undefined && typeof source.reset !== 'function')
101
+ return null;
102
+ return {
103
+ login: source.login,
104
+ send: source.send,
105
+ reset: source.reset,
106
+ };
107
+ }
108
+ const BROWSER_ENV = ['PATH', 'HOME', 'LANG', 'LC_ALL', 'TMPDIR', 'TMP', 'TEMP', 'SystemRoot', 'USERPROFILE', 'LOCALAPPDATA', 'XDG_RUNTIME_DIR'];
109
+ function browserEnv(env) {
110
+ const output = {};
111
+ for (const name of BROWSER_ENV)
112
+ if (typeof env[name] === 'string')
113
+ output[name] = env[name];
114
+ return output;
115
+ }
116
+ class FlowAborted extends Error {
117
+ }
118
+ class FlowTimeout extends Error {
119
+ }
120
+ /** Frames wait in memory until their step ends: at most this many bytes of them per step. */
121
+ const FRAME_STEP_MAX_BYTES = 16 * 1024 * 1024;
122
+ /** Wait `ms`, ending early when `signal` aborts; true when the whole wait passed. */
123
+ function pause(ms, signal) {
124
+ return new Promise((resolve) => {
125
+ if (signal.aborted) {
126
+ resolve(false);
127
+ return;
128
+ }
129
+ const done = (passed) => { clearTimeout(timer); signal.removeEventListener('abort', aborted); resolve(passed); };
130
+ const aborted = () => done(false);
131
+ const timer = setTimeout(() => done(true), ms);
132
+ signal.addEventListener('abort', aborted, { once: true });
133
+ });
134
+ }
135
+ function withFrames(capture, frames) {
136
+ return capture && frames ? { ...capture, frames } : capture;
137
+ }
138
+ function raceAbort(work, signal) {
139
+ return new Promise((resolve, reject) => {
140
+ if (signal.aborted) {
141
+ void work.catch(() => undefined);
142
+ reject(new FlowAborted());
143
+ return;
144
+ }
145
+ const onAbort = () => reject(new FlowAborted());
146
+ signal.addEventListener('abort', onAbort, { once: true });
147
+ work.then((value) => { signal.removeEventListener('abort', onAbort); resolve(value); }, (error) => { signal.removeEventListener('abort', onAbort); reject(error); });
148
+ });
149
+ }
150
+ async function withTimeout(work, ms, signal) {
151
+ if (signal.aborted)
152
+ throw new FlowAborted();
153
+ const timer = AbortSignal.timeout(Math.max(1, ms));
154
+ const combined = AbortSignal.any([timer, signal]);
155
+ try {
156
+ return await raceAbort(Promise.resolve().then(() => { if (combined.aborted)
157
+ throw new FlowAborted(); return work(); }), combined);
158
+ }
159
+ catch (error) {
160
+ if (error instanceof FlowAborted && timer.aborted)
161
+ throw new FlowTimeout();
162
+ throw error;
163
+ }
164
+ }
165
+ /** A timed-out allocation may still return a handle. Keep its original cleanup
166
+ * obligation until that exact handle has closed; never turn timeout into absence. */
167
+ async function acquire(work, ms, signal, pending) {
168
+ if (signal.aborted)
169
+ throw new FlowAborted();
170
+ let finish;
171
+ const obligation = new Promise(resolve => { finish = resolve; });
172
+ pending.add(obligation);
173
+ void obligation.then(() => pending.delete(obligation), () => undefined);
174
+ const original = Promise.resolve().then(() => { if (signal.aborted)
175
+ throw new FlowAborted(); return work(); });
176
+ try {
177
+ const handle = await withTimeout(() => original, ms, signal);
178
+ finish();
179
+ return handle;
180
+ }
181
+ catch (error) {
182
+ const cleanup = original.then(handle => handle.close(), () => undefined);
183
+ finish(cleanup);
184
+ throw error;
185
+ }
186
+ }
187
+ function errorSummary(error) {
188
+ if (error instanceof Error)
189
+ return { name: error.name, message: error.message };
190
+ return { name: typeof error };
191
+ }
192
+ export class PlaywrightDriver {
193
+ profile = 'playwright.v1';
194
+ plan;
195
+ prepared = null;
196
+ sessions = new Set();
197
+ pendingCleanup = new Set();
198
+ cleanupMs = 60_000;
199
+ disposing = false;
200
+ lifecycle = new AbortController();
201
+ constructor(options = {}) { this.plan = options.plan ?? null; }
202
+ async describe() {
203
+ const plan = this.plan;
204
+ const roots = !plan ? [] : plan.driver.profile === 'playwright.v1'
205
+ ? plan.roots.filter((root) => root.alias === plan.driver.root_alias)
206
+ : plan.roots;
207
+ if (!roots.length)
208
+ return { available: false, reason: 'No approved project root is available to resolve the customer-provided Playwright package from.' };
209
+ for (const root of roots)
210
+ if (resolvePlaywright(root.path))
211
+ return { available: true, reason: null };
212
+ return { available: false,
213
+ reason: 'Playwright is not installed in the approved project root. Firedrill does not install or bundle it; add it to the project to run browser drills.' };
214
+ }
215
+ async prepare(ctx) {
216
+ ctx = { ...ctx, signal: AbortSignal.any([ctx.signal, this.lifecycle.signal]) };
217
+ const plan = driverPlanFor(ctx, 'playwright.v1');
218
+ if (this.prepared)
219
+ throw planError('The browser driver is already prepared; a second browser was not started.');
220
+ const bounds = driverBounds(ctx);
221
+ this.cleanupMs = bounds.flowSetupMs;
222
+ if (this.disposing || this.pendingCleanup.size)
223
+ throw planError('The previous browser startup has unresolved cleanup. No replacement was started.');
224
+ const root = rootPath(ctx, plan.root_alias);
225
+ const resolved = resolvePlaywright(root);
226
+ if (!resolved)
227
+ throw nativeError('driver_unavailable', 'Playwright is not installed in the approved project root, so no browser was started. Firedrill does not install it.');
228
+ let browserType = null;
229
+ try {
230
+ browserType = browserTypeFrom(await withTimeout(() => import(__rewriteRelativeImportExtension(pathToFileURL(resolved).href)), bounds.flowSetupMs, ctx.signal));
231
+ }
232
+ catch {
233
+ browserType = null;
234
+ }
235
+ if (!browserType)
236
+ throw nativeError('driver_unavailable', 'The project\'s Playwright package could not be loaded, so no browser was started.');
237
+ const flowPath = await withTimeout(() => insideRoot(root, plan.flow), bounds.flowSetupMs, ctx.signal);
238
+ const pinned = await withTimeout(() => readPinned(flowPath, ctx.adapterSha256), bounds.flowSetupMs, ctx.signal);
239
+ let flow = null;
240
+ try {
241
+ flow = flowFrom(await withTimeout(() => import(__rewriteRelativeImportExtension(`${pathToFileURL(flowPath).href}?sha256=${pinned.sha256}`)), bounds.flowSetupMs, ctx.signal));
242
+ }
243
+ catch {
244
+ flow = null;
245
+ }
246
+ if (!flow)
247
+ throw planError('The approved browser flow could not be loaded or does not export login(page, identity) and send(page, step), so no browser was started.');
248
+ emit(ctx, 'state.observed', { type: 'adapter.verified', profile: this.profile, adapter_sha256: new OwnDigest(pinned.sha256) });
249
+ const handle = serviceHandle(ctx, plan.service_alias);
250
+ const base = loopbackBase(handle.url, plan.service_alias);
251
+ const start = loopbackTarget(base, plan.start_path, plan.service_alias);
252
+ let browser;
253
+ // Playwright's own signal handlers close the browser and then exit the process (130) on the first Ctrl-C,
254
+ // which would skip the companion's stop, signed close, local-run close and owned-group sweep. The companion
255
+ // owns signals and closes the browser itself (retainClose / dispose).
256
+ try {
257
+ browser = await acquire(() => browserType.launch({ headless: true, env: browserEnv(ctx.env), timeout: bounds.flowSetupMs,
258
+ handleSIGINT: false, handleSIGTERM: false, handleSIGHUP: false }), bounds.flowSetupMs, ctx.signal, this.pendingCleanup);
259
+ }
260
+ catch {
261
+ throw nativeError('driver_unavailable', 'The local Playwright browser could not be started (its browser binary may not be installed in this project), so no browser drill ran.');
262
+ }
263
+ if (this.disposing || ctx.signal.aborted) {
264
+ await this.retainClose(browser);
265
+ throw nativeError('driver_unavailable', 'Browser startup was cancelled. No browser session was opened.');
266
+ }
267
+ // Screenshots only with the approved policy and a Playwright that can apply it; never an unmasked image.
268
+ // One record per drill says capture was requested and whether it can run (selector count, never selectors).
269
+ let masking = null;
270
+ if (plan.screenshots) {
271
+ const approved = maskingPolicy(plan.screenshots);
272
+ const version = playwrightVersion(root);
273
+ const available = version !== null
274
+ && (version[0] > MASK_MIN_VERSION[0] || (version[0] === MASK_MIN_VERSION[0] && version[1] >= MASK_MIN_VERSION[1]));
275
+ const frames = plan.screenshots.frames ?? null;
276
+ if (available)
277
+ masking = { selectors: approved.policy.selectors, sha256: approved.sha256, frames };
278
+ emit(ctx, 'state.observed', { type: 'capture.requested', schema: CAPTURE_SCHEMA, capture: 'screenshot',
279
+ masking_policy_sha256: new OwnDigest(approved.sha256), selector_count: approved.policy.selectors.length,
280
+ max_per_session: bounds.maxScreenshotsPerSession, available,
281
+ // Masked frames while each step runs (null when the plan takes none). Frames count toward max_per_session.
282
+ frame_interval_ms: frames?.interval_ms ?? null, max_frames_per_step: frames?.max_per_step ?? null,
283
+ // Playwright older than 1.35, or its version could not be read.
284
+ reason: available ? null : 'driver_unsupported' });
285
+ }
286
+ this.prepared = { browser, flow, base, start, flowSha256: pinned.sha256, masking, trace: plan.trace === 'sanitized-actions' };
287
+ }
288
+ async openSession(ctx, sessionKey) {
289
+ ctx = { ...ctx, signal: AbortSignal.any([ctx.signal, this.lifecycle.signal]) };
290
+ const prepared = this.prepared;
291
+ if (!prepared || this.disposing)
292
+ throw new CliError(DRIVER_ERRORS.session_failed, 'The browser driver was not prepared, so no session was opened.');
293
+ if (!sessionKey)
294
+ throw new CliError(DRIVER_ERRORS.session_failed, 'The session key is empty, so no session was opened.');
295
+ const bounds = driverBounds(ctx);
296
+ let context = null;
297
+ let trace = null;
298
+ const accessor = playwrightLogin(ctx.identity);
299
+ try {
300
+ context = await acquire(() => prepared.browser.newContext({ baseURL: prepared.base.origin, acceptDownloads: false }), bounds.flowSetupMs, ctx.signal, this.pendingCleanup);
301
+ const page = await withTimeout(() => context.newPage(), bounds.flowSetupMs, ctx.signal);
302
+ await withTimeout(() => page.goto(prepared.start.href, { timeout: bounds.flowSetupMs, waitUntil: 'load' }), bounds.flowSetupMs, ctx.signal);
303
+ // The approved trace records the flow's own calls, from login on; the driver's navigation above is not one.
304
+ // One capture per browser session.
305
+ trace = prepared.trace ? new ActionTrace(clockOf(ctx), (segment) => { emitTrace(ctx, segment); return true; }) : null;
306
+ const flowPage = trace ? trace.view(page) : page;
307
+ await withTimeout(() => prepared.flow.login(flowPage, accessor), bounds.flowSetupMs, ctx.signal);
308
+ if (this.disposing || ctx.signal.aborted)
309
+ throw new FlowAborted();
310
+ trace?.cut();
311
+ emit(ctx, 'state.observed', {
312
+ type: 'browser.context.fresh', scope: 'browser-context', session_key: sessionKey, backend_reset: false,
313
+ login: accessor ? 'synthetic-credentials' : 'none',
314
+ });
315
+ const session = new PlaywrightSession(ctx, prepared.flow, context, page, flowPage, trace, sessionKey, prepared.masking, () => this.sessions.delete(session));
316
+ this.sessions.add(session);
317
+ emit(ctx, 'action.observed', { type: 'session.opened', profile: this.profile, session_key: sessionKey });
318
+ return session;
319
+ }
320
+ catch (error) {
321
+ trace?.end(ctx.signal.aborted ? 'cancelled' : 'driver_failed');
322
+ if (context)
323
+ await this.retainClose(context);
324
+ emit(ctx, 'driver.failed', { type: 'browser.session_failed', session_key: sessionKey,
325
+ cause: error instanceof FlowTimeout ? 'timeout' : 'error' });
326
+ throw new CliError(DRIVER_ERRORS.session_failed, 'The browser session could not be opened (start page or flow login did not finish), so no step was sent.');
327
+ }
328
+ finally {
329
+ accessor?.dispose();
330
+ }
331
+ }
332
+ /** SHA-256 of the HTML the approved start path served, loaded in a throwaway browser context (closed after). */
333
+ async readinessProbe(ctx) {
334
+ ctx = { ...ctx, signal: AbortSignal.any([ctx.signal, this.lifecycle.signal]) };
335
+ const prepared = this.prepared;
336
+ if (!prepared || this.disposing)
337
+ throw unattested('The browser driver was not prepared, so the start document cannot be attested. Nothing was claimed.');
338
+ const bounds = driverBounds(ctx);
339
+ let context = null;
340
+ try {
341
+ context = await acquire(() => prepared.browser.newContext({ baseURL: prepared.base.origin, acceptDownloads: false }), bounds.flowSetupMs, ctx.signal, this.pendingCleanup);
342
+ const page = await withTimeout(() => context.newPage(), bounds.flowSetupMs, ctx.signal);
343
+ await withTimeout(() => page.goto(prepared.start.href, { timeout: bounds.flowSetupMs, waitUntil: 'load' }), bounds.flowSetupMs, ctx.signal);
344
+ const html = await withTimeout(() => page.content(), bounds.flowSetupMs, ctx.signal);
345
+ if (typeof html !== 'string')
346
+ throw new Error('no document');
347
+ return { kind: 'browser_document', response_sha256: sha256Hex(Buffer.from(html, 'utf8')) };
348
+ }
349
+ catch {
350
+ throw unattested('The approved start page did not load in the local browser, so it cannot be attested. Nothing was claimed.');
351
+ }
352
+ finally {
353
+ if (context)
354
+ await this.retainClose(context);
355
+ }
356
+ }
357
+ async retainClose(handle) {
358
+ const close = Promise.resolve().then(() => handle.close());
359
+ this.pendingCleanup.add(close);
360
+ void close.then(() => this.pendingCleanup.delete(close), () => undefined);
361
+ await withTimeout(() => close, this.cleanupMs, new AbortController().signal).catch(() => undefined);
362
+ }
363
+ async dispose() {
364
+ this.disposing = true;
365
+ this.lifecycle.abort();
366
+ const prepared = this.prepared;
367
+ this.prepared = null;
368
+ // Close the browser concurrently: a stuck context must not prevent browser cleanup.
369
+ const work = [...this.sessions].map(session => session.close());
370
+ if (prepared)
371
+ work.push(this.retainClose(prepared.browser));
372
+ const results = await Promise.allSettled(work);
373
+ if (this.pendingCleanup.size)
374
+ await withTimeout(() => Promise.all([...this.pendingCleanup]), this.cleanupMs, new AbortController().signal);
375
+ if (results.some(result => result.status === 'rejected'))
376
+ throw new CliError(DRIVER_ERRORS.session_failed, 'Browser cleanup is not confirmed. Keep the original local execution for recovery.');
377
+ }
378
+ }
379
+ class PlaywrightSession {
380
+ sessionKey;
381
+ ctx;
382
+ flow;
383
+ context;
384
+ /** The real page: the driver's own captures use it. */
385
+ page;
386
+ /** What the flow gets: the page itself, or its recording view when the approved trace is on. */
387
+ flowPage;
388
+ trace;
389
+ onClose;
390
+ bounds;
391
+ /** The approved masking policy, or null when the plan captures no screenshots. */
392
+ masking;
393
+ /** Captures taken in this session, screenshots and frames together. */
394
+ screenshots = 0;
395
+ closed = false;
396
+ busy = false;
397
+ closing = null;
398
+ constructor(ctx, flow, context, page, flowPage, trace, sessionKey, masking, onClose) {
399
+ this.ctx = ctx;
400
+ this.flow = flow;
401
+ this.context = context;
402
+ this.page = page;
403
+ this.sessionKey = sessionKey;
404
+ this.onClose = onClose;
405
+ this.flowPage = flowPage;
406
+ this.trace = trace;
407
+ this.bounds = driverBounds(ctx);
408
+ this.masking = masking;
409
+ }
410
+ none(masking, unavailable) {
411
+ return { unavailable, masking_policy_sha256: masking.sha256, session_key: this.sessionKey };
412
+ }
413
+ /**
414
+ * The step's approved capture: the masked image, or why there is none (a
415
+ * reason only, never the error or page content); null when the plan captures
416
+ * no screenshots. Best-effort evidence only: skipped once the step is
417
+ * aborted, and raced against the abort with a short bound so a slow capture
418
+ * never turns a completed step into an interrupted one or leaves the session busy.
419
+ */
420
+ async capture(signal) {
421
+ return this.masking ? this.shoot(this.masking, signal, 'disabled') : null;
422
+ }
423
+ /**
424
+ * One masked capture: the step's screenshot after send() returned (animations
425
+ * settled, caret hidden), or a frame while it runs (`allow`: animations and
426
+ * the caret are left as they are, so the flow's page is not changed).
427
+ */
428
+ async shoot(masking, signal, motion) {
429
+ const none = (unavailable) => this.none(masking, unavailable);
430
+ if (this.closed)
431
+ return none('page_closed');
432
+ if (this.screenshots >= this.bounds.maxScreenshotsPerSession)
433
+ return none('session_limit');
434
+ if (signal.aborted)
435
+ return none('capture_failed');
436
+ try {
437
+ if (this.page.isClosed())
438
+ return none('page_closed');
439
+ this.screenshots += 1;
440
+ // Playwright paints the box of every element the selectors match before capture. A selector that
441
+ // matches nothing masks nothing (absent form fields are normal); an error while capturing or
442
+ // masking fails closed, with no image. Masking hides matching elements, not every sensitive value.
443
+ const mask = masking.selectors.map((selector) => this.page.locator(selector));
444
+ const bytes = await raceAbort(Promise.resolve(this.page.screenshot({ type: 'png', fullPage: false, scale: 'css', timeout: 2_000,
445
+ animations: motion, caret: motion === 'disabled' ? 'hide' : 'initial', mask, maskColor: MASK_COLOR })), AbortSignal.any([signal, AbortSignal.timeout(2_000)]));
446
+ if (!(bytes instanceof Uint8Array))
447
+ return none('capture_failed');
448
+ if (bytes.byteLength > this.bounds.maxAttachmentBytes * 8)
449
+ return none('image_too_large');
450
+ const png = metadataFreePng(bytes);
451
+ return png ? { png, masking_policy_sha256: masking.sha256, session_key: this.sessionKey } : none('image_unsupported');
452
+ }
453
+ catch {
454
+ return none('capture_failed');
455
+ }
456
+ }
457
+ /**
458
+ * Masked frames while the flow's send() runs (approved `screenshots.frames`):
459
+ * one per interval, at most `max_per_step` per step, each masked like the
460
+ * step's screenshot and counted toward the session's captures. A frame that
461
+ * produced no image is counted with its reason, and so is every interval past
462
+ * the step's count (`step_limit`), so the step says how much of it the frames
463
+ * cover. `stop()` lets a frame being taken finish (bounded like any capture)
464
+ * and hands the frames over once.
465
+ */
466
+ frames(signal) {
467
+ const masking = this.masking;
468
+ const plan = masking?.frames;
469
+ if (!masking || !plan)
470
+ return null;
471
+ const shots = [];
472
+ const reasons = new Set();
473
+ let omitted = 0, bytes = 0, handed = false;
474
+ const skip = (reason) => { omitted += 1; reasons.add(reason); };
475
+ const stopped = new AbortController();
476
+ const ticks = AbortSignal.any([signal, stopped.signal]);
477
+ const now = clockOf(this.ctx);
478
+ const running = (async () => {
479
+ while (await pause(plan.interval_ms, ticks)) {
480
+ if (shots.length >= plan.max_per_step) {
481
+ skip('step_limit');
482
+ continue;
483
+ }
484
+ const at = now();
485
+ const frame = await this.shoot(masking, signal, 'allow');
486
+ if (!('png' in frame)) {
487
+ skip(frame.unavailable);
488
+ continue;
489
+ }
490
+ if (bytes + frame.png.byteLength > FRAME_STEP_MAX_BYTES) {
491
+ skip('evidence_capacity');
492
+ continue;
493
+ }
494
+ bytes += frame.png.byteLength;
495
+ shots.push({ png: frame.png, masking_policy_sha256: frame.masking_policy_sha256, session_key: frame.session_key, at_ms: at });
496
+ }
497
+ })().catch(() => undefined);
498
+ return {
499
+ stop: async () => {
500
+ stopped.abort();
501
+ await running;
502
+ if (handed)
503
+ return undefined;
504
+ handed = true;
505
+ return { shots, omitted, reasons: FRAME_OMISSION_REASONS.filter((reason) => reasons.has(reason)) };
506
+ },
507
+ };
508
+ }
509
+ async invoke(step, signal) {
510
+ const recorder = new StepRecorder(this.ctx, step);
511
+ if (this.closed)
512
+ return recorder.finish('failed', null, DRIVER_ERRORS.session_closed);
513
+ if (this.busy)
514
+ return recorder.finish('failed', null, DRIVER_ERRORS.step_unsupported);
515
+ const refused = precheck(step, this.sessionKey, ['message', 'task']);
516
+ if (refused)
517
+ return recorder.finish('failed', null, refused);
518
+ const input = stepInput(step);
519
+ const view = Object.freeze({
520
+ step_id: step.step_id, sequence: step.sequence, kind: input.kind, session_key: this.sessionKey,
521
+ text: input.kind === 'message' ? input.input : null,
522
+ input: input.kind === 'task' ? input.input : null,
523
+ });
524
+ recorder.record('action.observed', { type: 'target.exchange', direction: 'to_target', kind: input.kind, session_key: this.sessionKey, content: input.input });
525
+ const abort = stepAbort(this.ctx, signal, stepTimeoutMs(step, this.bounds));
526
+ if (abort.signal.aborted) {
527
+ abort.dispose();
528
+ return recorder.interrupted(abort.cause() ?? 'cancelled', false);
529
+ }
530
+ this.busy = true;
531
+ // Trace segments cut while this step runs are its own records, queued after its capture so frames keep their times.
532
+ this.trace?.attach((segment) => recorder.recordTrace(segment), abort.signal);
533
+ const frames = this.frames(abort.signal);
534
+ try {
535
+ const output = await raceAbort(Promise.resolve().then(() => this.flow.send(this.flowPage, view)), abort.signal);
536
+ // Frames end before the step's screenshot. The completed result is kept whether or not either produced an image.
537
+ const shots = await frames?.stop();
538
+ const capture = withFrames(await this.capture(abort.signal), shots);
539
+ recorder.record('action.observed', { type: 'target.exchange', direction: 'from_target', content: output }, undefined, capture);
540
+ this.trace?.cut();
541
+ return recorder.finish('completed', output, null);
542
+ }
543
+ catch (error) {
544
+ const shots = await frames?.stop();
545
+ if (error instanceof FlowAborted) {
546
+ // send() may still be acting on the page: no screenshot is taken and this context is retired. Frames
547
+ // taken before the stop are kept, recorded ahead of the cleanup so they keep their times.
548
+ const cause = abort.cause() ?? 'cancelled';
549
+ recorder.noteInterrupted(cause, true, this.masking ? withFrames(this.none(this.masking, 'capture_failed'), shots) : null);
550
+ recorder.record('state.observed', { type: 'browser.context.closing', scope: 'browser-context', backend_reset: false, reason: 'step interrupted' });
551
+ this.trace?.end('cancelled');
552
+ await this.close();
553
+ recorder.record('state.observed', { type: 'browser.context.closed', scope: 'browser-context', backend_reset: false, reason: 'step interrupted' });
554
+ return recorder.interrupted(cause, true);
555
+ }
556
+ const capture = withFrames(await this.capture(abort.signal), shots);
557
+ recorder.record('action.observed', { type: 'flow.error', error: errorSummary(error) }, undefined, capture);
558
+ this.trace?.cut();
559
+ return recorder.interrupted('thrown', true);
560
+ }
561
+ finally {
562
+ this.trace?.detach();
563
+ this.busy = false;
564
+ abort.dispose();
565
+ }
566
+ }
567
+ /** Run the flow's optional reset(page). Client-side only; never a backend reset. */
568
+ async resetLocal() {
569
+ const result = (applied, error_code) => {
570
+ emit(this.ctx, 'state.observed', { type: 'browser.context.reset', scope: 'browser-context', session_key: this.sessionKey,
571
+ applied, backend_reset: false, error_code });
572
+ return { scope: 'browser-context', applied, backend_reset: false, error_code };
573
+ };
574
+ if (this.closed)
575
+ return result(false, DRIVER_ERRORS.session_closed);
576
+ const reset = this.flow.reset;
577
+ if (!reset)
578
+ return result(false, NATIVE_ERRORS.state_unsupported);
579
+ try {
580
+ await withTimeout(() => reset(this.flowPage), this.bounds.flowSetupMs, this.ctx.signal);
581
+ return result(true, null);
582
+ }
583
+ catch {
584
+ return result(false, DRIVER_ERRORS.session_failed);
585
+ }
586
+ finally {
587
+ this.trace?.cut();
588
+ }
589
+ }
590
+ async close() {
591
+ if (this.closing)
592
+ return withTimeout(() => this.closing, this.bounds.flowSetupMs, new AbortController().signal);
593
+ this.closed = true;
594
+ // The capture ends with the session, before the context closes, so calls the close ends are not recorded as failures.
595
+ this.trace?.end();
596
+ this.closing = Promise.resolve().then(() => this.context.close());
597
+ void this.closing.then(() => this.onClose(), () => undefined);
598
+ await withTimeout(() => this.closing, this.bounds.flowSetupMs, new AbortController().signal);
599
+ }
600
+ }