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,406 @@
1
+ import { createHash, randomUUID } from 'node:crypto';
2
+ import { constants } from 'node:fs';
3
+ import { lstat, open, opendir, rename } from 'node:fs/promises';
4
+ import { join } from 'node:path';
5
+ import { api } from './auth.js';
6
+ import { privateDirectory } from './credentials.js';
7
+ import { CliError } from './http.js';
8
+ import { identifier } from './world-contracts.js';
9
+ import { withBootstrapProcessLock } from './bootstrap-process-lock.js';
10
+ import { isNativeSetupReceipt } from '../../contracts/planning-contracts.js';
11
+ const HASH = /^[a-f0-9]{64}$/;
12
+ const REQUEST_PAGE_SIZE = 25;
13
+ const ACCEPTANCE_READ_LIMIT = 25;
14
+ const invalid = () => new CliError('simulation_intent_invalid', 'The exact saved setup request changed. It was not repeated or replaced.');
15
+ function canonical(value) {
16
+ if (value === null || typeof value === 'string' || typeof value === 'boolean')
17
+ return JSON.stringify(value);
18
+ if (typeof value === 'number' && Number.isSafeInteger(value))
19
+ return JSON.stringify(value);
20
+ if (Array.isArray(value))
21
+ return '[' + value.map(canonical).join(',') + ']';
22
+ if (value && typeof value === 'object')
23
+ return '{' + Object.keys(value).sort().map(key => JSON.stringify(key) + ':'
24
+ + canonical(value[key])).join(',') + '}';
25
+ throw invalid();
26
+ }
27
+ const hash = (value) => createHash('sha256').update(canonical(value)).digest('hex');
28
+ function keys(value, expected) {
29
+ return !!value && !Array.isArray(value) && typeof value === 'object'
30
+ && Object.keys(value).sort().join('\0') === [...expected].sort().join('\0');
31
+ }
32
+ function positive(value) { return Number.isSafeInteger(value) && Number(value) > 0; }
33
+ function choice(value) {
34
+ if (keys(value, ['location']) && value.location === 'managed')
35
+ return;
36
+ if (keys(value, ['location', 'grant_id']) && value.location === 'local' && typeof value.grant_id === 'string') {
37
+ identifier(value.grant_id);
38
+ return;
39
+ }
40
+ throw invalid();
41
+ }
42
+ function bodyFor(kind, body) {
43
+ if (kind === 'validation') {
44
+ const local = body.execution_location === 'local';
45
+ const startup = body.validation_scope === 'target_startup_only';
46
+ if (!keys(body, ['configuration_revision', 'execution_location', ...(local ? ['local_grant_id'] : []),
47
+ ...(startup ? ['validation_scope', 'acknowledge_external_effects', 'acknowledge_startup_calls', 'world_revision', 'source_id', 'source_revision', 'startup_actor_id'] : [])])
48
+ || !Number.isSafeInteger(body.configuration_revision) || Number(body.configuration_revision) < 0
49
+ || startup && (body.acknowledge_external_effects !== true || body.acknowledge_startup_calls !== true)
50
+ || startup && (!positive(body.world_revision) || typeof body.source_id !== 'string'
51
+ || typeof body.source_revision !== 'string' || !body.source_revision.length || body.source_revision.length > 256)
52
+ || !['managed', 'local'].includes(String(body.execution_location)))
53
+ throw invalid();
54
+ if (local)
55
+ identifier(String(body.local_grant_id));
56
+ if (startup) {
57
+ identifier(String(body.source_id));
58
+ identifier(String(body.startup_actor_id));
59
+ }
60
+ }
61
+ else if (kind === 'admission') {
62
+ if (!keys(body, ['plan_id', 'recipe_sha256', 'usage_quote_id']) || !HASH.test(String(body.recipe_sha256)))
63
+ throw invalid();
64
+ identifier(String(body.plan_id));
65
+ identifier(String(body.usage_quote_id));
66
+ }
67
+ else if (kind === 'plan') {
68
+ if (!keys(body, ['previous_plan_id', 'world_revision', 'configuration_revision', 'selection_revision', 'evaluation_revision',
69
+ 'share_evaluation_evidence', 'run_saved_criteria', 'selector', 'repetitions', 'parallelism', 'trial_seconds', 'execution', 'mask_browser_form_values',
70
+ ...('external_effects' in body ? ['external_effects'] : []), ...('record_browser' in body ? ['record_browser'] : []),
71
+ ...('record_voice' in body ? ['record_voice'] : []), ...('dataset_id' in body ? ['dataset_id'] : []),
72
+ ...('record_desktop' in body ? ['record_desktop'] : []), ...('acknowledge_desktop_screen_content' in body ? ['acknowledge_desktop_screen_content'] : []),
73
+ ...('share_evaluation_explanation' in body ? ['share_evaluation_explanation'] : []),
74
+ ...('driver_input_authorized' in body ? ['driver_input_authorized'] : []),
75
+ ...('driver_contract_sha256' in body ? ['driver_contract_sha256'] : []),
76
+ ...('application_checkpoint_id' in body ? ['application_checkpoint_id'] : []),
77
+ ...('acknowledge_fresh_world_state' in body ? ['acknowledge_fresh_world_state'] : []),
78
+ ...('share_application_checkpoint_with_device' in body ? ['share_application_checkpoint_with_device'] : []),
79
+ ...('coordinated_checkpoint_id' in body ? ['coordinated_checkpoint_id'] : []),
80
+ ...('acknowledge_supported_checkpoint_scope' in body ? ['acknowledge_supported_checkpoint_scope'] : []),
81
+ ...('share_coordinated_checkpoint_with_device' in body ? ['share_coordinated_checkpoint_with_device'] : []),
82
+ ...('share_dataset_with_device' in body ? ['share_dataset_with_device'] : [])])
83
+ || !positive(body.world_revision) || !Number.isSafeInteger(body.configuration_revision) || Number(body.configuration_revision) < 0
84
+ || !['repetitions', 'parallelism', 'trial_seconds'].every(key => positive(body[key]))
85
+ || typeof body.selection_revision !== 'string' || !/^[a-z0-9-]{1,128}$/.test(body.selection_revision)
86
+ || typeof body.evaluation_revision !== 'string' || !HASH.test(body.evaluation_revision)
87
+ || typeof body.share_evaluation_evidence !== 'boolean' || typeof body.run_saved_criteria !== 'boolean'
88
+ || 'share_evaluation_explanation' in body && typeof body.share_evaluation_explanation !== 'boolean'
89
+ || body.share_evaluation_explanation === true && body.share_evaluation_evidence !== true
90
+ || typeof body.mask_browser_form_values !== 'boolean'
91
+ || 'driver_input_authorized' in body && typeof body.driver_input_authorized !== 'boolean'
92
+ || body.driver_input_authorized === true && !HASH.test(String(body.driver_contract_sha256))
93
+ || body.driver_input_authorized !== true && body.driver_contract_sha256 != null
94
+ || 'record_browser' in body && typeof body.record_browser !== 'boolean'
95
+ || 'record_voice' in body && typeof body.record_voice !== 'boolean'
96
+ || 'record_desktop' in body && typeof body.record_desktop !== 'boolean'
97
+ || 'acknowledge_desktop_screen_content' in body && typeof body.acknowledge_desktop_screen_content !== 'boolean'
98
+ || (body.record_desktop ?? false) !== (body.acknowledge_desktop_screen_content ?? false)
99
+ || 'share_dataset_with_device' in body && typeof body.share_dataset_with_device !== 'boolean')
100
+ throw invalid();
101
+ if (body.previous_plan_id !== null)
102
+ identifier(String(body.previous_plan_id));
103
+ if (body.dataset_id !== undefined && body.dataset_id !== null) {
104
+ if (typeof body.dataset_id !== 'string')
105
+ throw invalid();
106
+ identifier(body.dataset_id);
107
+ }
108
+ choice(body.execution);
109
+ for (const name of ['application_checkpoint_id', 'coordinated_checkpoint_id']) {
110
+ if (body[name] !== undefined && body[name] !== null) {
111
+ if (typeof body[name] !== 'string')
112
+ throw invalid();
113
+ identifier(body[name]);
114
+ }
115
+ }
116
+ for (const name of ['acknowledge_fresh_world_state', 'share_application_checkpoint_with_device',
117
+ 'acknowledge_supported_checkpoint_scope', 'share_coordinated_checkpoint_with_device'])
118
+ if (name in body && typeof body[name] !== 'boolean')
119
+ throw invalid();
120
+ const app = body.application_checkpoint_id != null, paired = body.coordinated_checkpoint_id != null;
121
+ const local = body.execution.location === 'local';
122
+ if (app && paired || (app || paired) && (body.dataset_id != null || body.external_effects != null)
123
+ || app !== Boolean(body.acknowledge_fresh_world_state) || paired !== Boolean(body.acknowledge_supported_checkpoint_scope)
124
+ || body.share_application_checkpoint_with_device === true && (!app || !local)
125
+ || body.share_coordinated_checkpoint_with_device === true && (!paired || !local)
126
+ || local && app && body.share_application_checkpoint_with_device !== true
127
+ || local && paired && body.share_coordinated_checkpoint_with_device !== true)
128
+ throw invalid();
129
+ if ('external_effects' in body) {
130
+ const consent = body.external_effects;
131
+ if (!keys(consent, ['actor_id', 'acknowledge_effects', 'acknowledge_shared_state'])
132
+ || consent.acknowledge_effects !== true || consent.acknowledge_shared_state !== true || typeof consent.actor_id !== 'string')
133
+ throw invalid();
134
+ identifier(consent.actor_id);
135
+ }
136
+ const selector = body.selector;
137
+ if (!keys(selector, ['mode', 'query', 'ids', 'excluded']) || !['ids', 'all_matching'].includes(String(selector.mode))
138
+ || selector.query !== '' || !Array.isArray(selector.ids) || !Array.isArray(selector.excluded))
139
+ throw invalid();
140
+ for (const value of [...selector.ids, ...selector.excluded])
141
+ identifier(String(value));
142
+ }
143
+ else
144
+ throw invalid();
145
+ if (Buffer.byteLength(canonical(body)) > 2 * 1024 ** 2)
146
+ throw invalid();
147
+ }
148
+ function digest(context, kind, body) {
149
+ bodyFor(kind, body);
150
+ if (kind === 'validation' && body.validation_scope === 'target_startup_only' && body.startup_actor_id !== context.owner)
151
+ throw invalid();
152
+ if (kind === 'plan') {
153
+ if (body.external_effects && body.external_effects.actor_id !== context.owner)
154
+ throw invalid();
155
+ return hash({ actor_id: context.owner, data: body });
156
+ }
157
+ if (kind === 'admission')
158
+ return hash({ actor_id: context.owner, plan_id: body.plan_id, recipe_sha256: body.recipe_sha256 });
159
+ return hash({ actor_id: context.owner, configuration_revision: body.configuration_revision,
160
+ ...(body.validation_scope === 'target_startup_only' ? { validation_scope: body.validation_scope,
161
+ acknowledge_external_effects: body.acknowledge_external_effects, acknowledge_startup_calls: body.acknowledge_startup_calls,
162
+ world_revision: body.world_revision, source_id: body.source_id, source_revision: body.source_revision, startup_actor_id: body.startup_actor_id } : {}),
163
+ ...(body.execution_location === 'local' ? { execution_location: 'local', local_grant_id: body.local_grant_id } : {}) });
164
+ }
165
+ const prefix = (context) => `simulation-intent-${identifier(context.owner)}-${identifier(context.workspace)}-${identifier(context.world)}-`;
166
+ const fileFor = async (context, key) => join(await privateDirectory(), prefix(context) + identifier(key) + '.json');
167
+ async function persist(value, initial = false) {
168
+ const context = { ...value, prefix: '' }, path = await fileFor(context, value.key);
169
+ const pathToWrite = initial ? path : path + '.' + randomUUID() + '.tmp';
170
+ const file = await open(pathToWrite, 'wx', 0o600);
171
+ try {
172
+ await file.writeFile(JSON.stringify(value));
173
+ await file.sync();
174
+ }
175
+ finally {
176
+ await file.close();
177
+ }
178
+ if (!initial)
179
+ await rename(pathToWrite, path);
180
+ }
181
+ export async function loadIntent(context, key) {
182
+ const path = await fileFor(context, key), before = await lstat(path);
183
+ if (!before.isFile() || before.isSymbolicLink() || before.nlink !== 1 || before.mode & 0o077 || before.size > 3 * 1024 ** 2
184
+ || process.getuid && before.uid !== process.getuid())
185
+ throw invalid();
186
+ const file = await open(path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
187
+ try {
188
+ const actual = await file.stat();
189
+ if (actual.ino !== before.ino || actual.dev !== before.dev || actual.size !== before.size)
190
+ throw invalid();
191
+ const value = JSON.parse(await file.readFile('utf8'));
192
+ if (value.schema !== 'firedrill.cli-simulation-intent.v1' || value.key !== identifier(key) || value.owner !== context.owner
193
+ || value.workspace !== context.workspace || value.world !== context.world
194
+ || !['dispatched', 'not_received', 'pending', 'accepted', 'withdrawn', 'admitted', 'canceled'].includes(value.state)
195
+ || !Number.isFinite(Date.parse(value.created_at)) || value.digest !== digest(context, value.kind, value.body))
196
+ throw invalid();
197
+ return value;
198
+ }
199
+ catch {
200
+ throw invalid();
201
+ }
202
+ finally {
203
+ await file.close();
204
+ }
205
+ }
206
+ function readPath(context, intent) {
207
+ return intent.kind === 'admission'
208
+ ? `${context.prefix}/campaign-admission?plan_id=${identifier(String(intent.body.plan_id))}&request_key=${intent.key}`
209
+ : `${context.prefix}/native-simulation-setup/${intent.kind}/${intent.key}`;
210
+ }
211
+ function match(context, intent, value) {
212
+ const allowed = intent.kind === 'admission' ? ['not_received', 'pending', 'admitted', 'canceled'] : ['not_received', 'pending', 'accepted', 'withdrawn'];
213
+ if (!value || !allowed.includes(value.state) || value.request_digest !== null && value.request_digest !== intent.digest)
214
+ throw invalid();
215
+ if (intent.kind === 'admission') {
216
+ if (isNativeSetupReceipt(value) || value.plan_id !== intent.body.plan_id)
217
+ throw invalid();
218
+ if (value.usage_quote_id !== null && value.usage_quote_id !== intent.body.usage_quote_id)
219
+ throw invalid();
220
+ if (value.campaign_id)
221
+ identifier(value.campaign_id);
222
+ }
223
+ else {
224
+ if (!isNativeSetupReceipt(value) || value.request_key !== intent.key || value.world_id !== context.world || value.kind !== intent.kind
225
+ || value.state === 'accepted' && (!value.resource_id || typeof value.resource_available !== 'boolean'))
226
+ throw invalid();
227
+ if (value.resource_id)
228
+ identifier(value.resource_id);
229
+ if (value.state === 'accepted' && value.resource_available) {
230
+ if (intent.kind === 'validation' && value.execution_location !== intent.body.execution_location)
231
+ throw invalid();
232
+ if (intent.kind === 'validation' && value.execution_location === 'local') {
233
+ if (value.local_grant_id !== intent.body.local_grant_id || !value.local_device_key)
234
+ throw invalid();
235
+ identifier(value.local_device_key);
236
+ }
237
+ const plan = value.plan;
238
+ if (intent.kind === 'plan' && (!plan || plan.id !== value.resource_id || !HASH.test(plan.recipe_sha256)
239
+ || typeof plan.current !== 'boolean'))
240
+ throw invalid();
241
+ }
242
+ }
243
+ if (!['not_received', 'withdrawn'].includes(value.state) && !value.request_digest)
244
+ throw invalid();
245
+ }
246
+ async function record(context, intent, value) {
247
+ match(context, intent, value);
248
+ intent.state = value.state;
249
+ await persist(intent);
250
+ return value;
251
+ }
252
+ export async function inspectIntent(context, key, signal) {
253
+ const intent = await loadIntent(context, key);
254
+ const value = await api(readPath(context, intent), { actor: context.owner, workspace: context.workspace, signal });
255
+ return { intent, receipt: await record(context, intent, value) };
256
+ }
257
+ export async function withdrawIntent(context, key, signal) {
258
+ const intent = await loadIntent(context, key), auth = { actor: context.owner, workspace: context.workspace, signal };
259
+ let value;
260
+ try {
261
+ value = intent.kind === 'admission'
262
+ ? await api(`${context.prefix}/campaign-admission`, { ...auth, method: 'POST', idempotencyKey: intent.key,
263
+ body: { plan_id: intent.body.plan_id, action: 'cancel' } })
264
+ : await api(readPath(context, intent), { ...auth, method: 'DELETE' });
265
+ }
266
+ catch (error) {
267
+ if (!(error instanceof CliError) || !['connection_uncertain', 'response_unavailable'].includes(error.code))
268
+ throw error;
269
+ value = await api(readPath(context, intent), auth);
270
+ }
271
+ return { intent, receipt: await record(context, intent, value) };
272
+ }
273
+ async function* intentKeys(context, signal) {
274
+ const scope = prefix(context), directory = await opendir(await privateDirectory());
275
+ // Directory iteration holds one batch, not the complete private history.
276
+ // Partial writes use .tmp names and are never accepted as request journals.
277
+ for await (const entry of directory) {
278
+ signal.throwIfAborted();
279
+ if (!entry.name.startsWith(scope) || !entry.name.endsWith('.json'))
280
+ continue;
281
+ const key = entry.name.slice(scope.length, -5);
282
+ if (!entry.isFile() || identifier(key) !== key)
283
+ throw invalid();
284
+ yield key;
285
+ }
286
+ }
287
+ export async function intentPage(context, after, signal) {
288
+ const cursor = after === undefined ? undefined : identifier(after);
289
+ const selected = [];
290
+ // UUID order is deliberate and stated in the response. Only the next page
291
+ // plus one lookahead key is retained, independent of directory entry order.
292
+ for await (const key of intentKeys(context, signal)) {
293
+ if (cursor && key <= cursor || selected.includes(key))
294
+ continue;
295
+ const position = selected.findIndex(value => key < value);
296
+ selected.splice(position < 0 ? selected.length : position, 0, key);
297
+ if (selected.length > REQUEST_PAGE_SIZE + 1)
298
+ selected.pop();
299
+ }
300
+ const hasMore = selected.length > REQUEST_PAGE_SIZE;
301
+ const results = [];
302
+ for (const key of selected.slice(0, REQUEST_PAGE_SIZE)) {
303
+ signal.throwIfAborted();
304
+ results.push(await inspectIntent(context, key, signal));
305
+ }
306
+ return { results, page_size: REQUEST_PAGE_SIZE, order: 'request_id',
307
+ next_after: hasMore ? results[results.length - 1].intent.key : null };
308
+ }
309
+ async function firstPendingIntent(context, signal) {
310
+ let acceptanceReads = 0;
311
+ for await (const key of intentKeys(context, signal)) {
312
+ const intent = await loadIntent(context, key);
313
+ if (['accepted', 'admitted', 'withdrawn', 'canceled'].includes(intent.state))
314
+ continue;
315
+ if (acceptanceReads >= ACCEPTANCE_READ_LIMIT) {
316
+ throw new CliError('simulation_intent_review_required', `More saved setup requests need a current receipt. Inspect request ${key} with firedrill simulation request status ${key}, or page firedrill simulation requests. No new request was created.`);
317
+ }
318
+ acceptanceReads += 1;
319
+ const current = await inspectIntent(context, key, signal);
320
+ if (['not_received', 'pending'].includes(current.receipt.state))
321
+ return current;
322
+ }
323
+ return null;
324
+ }
325
+ async function intentLock(context, signal, work) {
326
+ const lock = join(await privateDirectory(), prefix(context) + 'lock');
327
+ return withBootstrapProcessLock(lock, signal, work);
328
+ }
329
+ async function sendOriginal(context, intent, signal, output) {
330
+ signal.throwIfAborted();
331
+ try {
332
+ await api(intent.kind === 'validation' ? `${context.prefix}/validation` : intent.kind === 'plan' ? `${context.prefix}/simulation-plan` : `${context.prefix}/campaigns`, {
333
+ actor: context.owner, workspace: context.workspace, signal, method: 'POST', idempotencyKey: intent.key,
334
+ body: intent.kind === 'admission' ? { plan_id: intent.body.plan_id, usage_quote_id: intent.body.usage_quote_id } : intent.body
335
+ });
336
+ }
337
+ catch (error) {
338
+ // At most one original POST in this explicit command. An unknown reply only
339
+ // reads original acceptance; it never repeats a target or selects a new key.
340
+ if (signal.aborted)
341
+ throw error;
342
+ const current = await inspectIntent(context, intent.key, signal);
343
+ output('simulation_request_receipt', current.receipt, `Setup request ${intent.key}: ${current.receipt.state}.`);
344
+ if (current.receipt.state !== 'accepted' && current.receipt.state !== 'admitted')
345
+ throw error;
346
+ return current;
347
+ }
348
+ return inspectIntent(context, intent.key, signal);
349
+ }
350
+ export async function continueIntent(context, key, signal, output) {
351
+ return intentLock(context, signal, async (active) => {
352
+ signal = active;
353
+ const current = await inspectIntent(context, key, signal);
354
+ if (!['not_received', 'pending'].includes(current.receipt.state))
355
+ return current;
356
+ output('simulation_request', { request_key: current.intent.key, kind: current.intent.kind }, `Continuing original setup request ${current.intent.key}.`);
357
+ return sendOriginal(context, current.intent, signal, output);
358
+ });
359
+ }
360
+ export async function optionalIntent(context, key) {
361
+ try {
362
+ return await loadIntent(context, identifier(key));
363
+ }
364
+ catch (error) {
365
+ if (error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT')
366
+ return null;
367
+ throw error;
368
+ }
369
+ }
370
+ export async function createIntent(context, kind, body, signal, output, exactKey) {
371
+ return intentLock(context, signal, async (active) => {
372
+ signal = active;
373
+ const key = exactKey === undefined ? undefined : identifier(exactKey);
374
+ // Reconcile the original journal before the general unresolved-request scan.
375
+ // Never replace its historical body/digest or collide with its exclusive file.
376
+ const original = key ? await optionalIntent(context, key) : null;
377
+ if (original) {
378
+ if (original.kind !== kind || original.digest !== digest(context, kind, body))
379
+ throw invalid();
380
+ const current = await inspectIntent(context, original.key, signal);
381
+ if (!['not_received', 'pending'].includes(current.receipt.state))
382
+ return current;
383
+ output('simulation_request', { request_key: original.key, kind }, `Continuing original setup request ${original.key}.`);
384
+ return sendOriginal(context, original, signal, output);
385
+ }
386
+ const intent = { schema: 'firedrill.cli-simulation-intent.v1', owner: context.owner, workspace: context.workspace,
387
+ world: context.world, key: key ?? randomUUID(), kind, body, digest: digest(context, kind, body), state: 'dispatched', created_at: new Date().toISOString() };
388
+ // An original may have been accepted from another client or before this
389
+ // journal reached disk. Confirm its digest before retaining or posting.
390
+ const existing = key ? await api(readPath(context, intent), { actor: context.owner, workspace: context.workspace, signal }) : null;
391
+ if (existing)
392
+ match(context, intent, existing);
393
+ if (!existing || existing.state === 'not_received') {
394
+ const unresolved = await firstPendingIntent(context, signal);
395
+ if (unresolved)
396
+ throw new CliError('simulation_intent_unresolved', `Inspect, continue or withdraw setup request ${unresolved.intent.key} before creating another.`);
397
+ }
398
+ if (existing)
399
+ intent.state = existing.state;
400
+ await persist(intent, true);
401
+ output('simulation_request', { request_key: intent.key, kind }, `Saved setup request ${intent.key}. Recover with firedrill simulation request status ${intent.key}.`);
402
+ if (existing && !['not_received', 'pending'].includes(existing.state))
403
+ return { intent, receipt: existing };
404
+ return sendOriginal(context, intent, signal, output);
405
+ });
406
+ }