firedrill-run 2.2.5 → 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,496 @@
1
+ import { workspaceLink } from './browser-links.js';
2
+ import { createHash, randomUUID } from 'node:crypto';
3
+ import { constants } from 'node:fs';
4
+ import { lstat, open, unlink } from 'node:fs/promises';
5
+ import { join, resolve } from 'node:path';
6
+ import { createInterface } from 'node:readline/promises';
7
+ import { api } from './auth.js';
8
+ import { privateDirectory } from './credentials.js';
9
+ import { CliError } from './http.js';
10
+ import { selectMany, selectOne } from './prompts.js';
11
+ import { identifier } from './world-contracts.js';
12
+ import { selectedWorkspace } from './world-access.js';
13
+ import { candidateCopy, forgetCandidateCopy } from './candidate-copy.js';
14
+ import { sourceChangeMatches, sourceChangePageMatches, sourceChangeProposalMatches, sourceChangeCommandMatches, sourceChangeDiffMatches, agentComparisonMatches, agentComparisonRequestMatches, patchSourceFileMatches, sourceMetadataDigest } from '../../contracts/agent-change-contracts.js';
15
+ const CAPACITY = 65536;
16
+ const HASH = /^[a-f0-9]{64}$/;
17
+ const MUTATIONS = ['propose', 'approve', 'resume-draft', 'accept', 'retry-save', 'dismiss', 'compare'];
18
+ const serverAction = (action) => action === 'resume-draft' ? 'resume' : action === 'retry-save' ? 'retry' : action;
19
+ const object = (value) => !!value && typeof value === 'object' && !Array.isArray(value);
20
+ const digest = (value) => createHash('sha256').update(JSON.stringify(value)).digest('hex');
21
+ const tty = (options) => !options.json && !!process.stdin.isTTY && !!process.stderr.isTTY;
22
+ const prefix = (scope, campaign) => `/api/workspaces/${identifier(scope.workspace)}/campaigns/${identifier(campaign)}`;
23
+ const auth = (scope, options) => ({ actor: scope.owner, workspace: scope.workspace, signal: options.signal });
24
+ const link = (row) => workspaceLink(row.workspace_id, `/campaigns/${identifier(row.baseline_id)}`);
25
+ const invalidReceipt = () => new CliError('agent_change_receipt_invalid', 'The saved response did not match this workspace, baseline or original source-change request. Inspect the original request before continuing. No replacement request was submitted.');
26
+ function selectedPath(path, scope) {
27
+ const found = path.match(/^\/api\/workspaces\/([^/]+)\/campaigns\/([^/]+)(?:\/source-changes\/([^/?]+))?/);
28
+ if (!found || found[1] !== scope.workspace)
29
+ throw invalidReceipt();
30
+ return { campaign: identifier(found[2]), change: found[3] ? identifier(found[3]) : undefined };
31
+ }
32
+ async function changePage(path, scope, options, page) {
33
+ const selected = selectedPath(path, scope);
34
+ const result = await api(`${path}?page=${page}`, auth(scope, options));
35
+ if (!sourceChangePageMatches(result, scope.workspace, selected.campaign, page)
36
+ || !result.results.every(row => sourceChangeMatches(row, scope.workspace, selected.campaign)))
37
+ throw invalidReceipt();
38
+ return result;
39
+ }
40
+ async function filePage(path, scope, options, page) {
41
+ const selected = selectedPath(path, scope), search = options.search ?? '';
42
+ const result = await api(`${path}?page=${page}&q=${encodeURIComponent(search)}`, auth(scope, options));
43
+ if (!sourceChangePageMatches(result, scope.workspace, selected.campaign, page) || result.query !== search.trim()
44
+ || !result.results.every(patchSourceFileMatches))
45
+ throw invalidReceipt();
46
+ return result;
47
+ }
48
+ async function comparisonsPage(path, scope, options, page) {
49
+ const selected = selectedPath(path, scope);
50
+ if (!selected.change)
51
+ throw invalidReceipt();
52
+ const result = await api(`${path}/comparisons?page=${page}${options.request ? '&request_key=' + identifier(options.request) : ''}`, auth(scope, options));
53
+ if (!sourceChangePageMatches(result, scope.workspace, selected.campaign, page) || result.change_id !== selected.change
54
+ || !result.results.every(row => agentComparisonMatches(row, scope.workspace, selected.campaign, selected.change)))
55
+ throw invalidReceipt();
56
+ if (options.request && !agentComparisonRequestMatches(result, scope.workspace, selected.campaign, selected.change, scope.owner, identifier(options.request)))
57
+ throw invalidReceipt();
58
+ return result;
59
+ }
60
+ function description(row) {
61
+ return [`${row.title} · ${row.state.replaceAll('_', ' ')}`, row.id, row.instruction,
62
+ ...row.files.map(file => `${file.path} · ${file.size} bytes · ${file.sha256}`),
63
+ `Source scope: ${row.scope_sha256}`, ...(row.draft_sha256 ? [`Draft: ${row.draft_sha256}`] : []),
64
+ ...(!row.current ? ['The source, approval or evidence is no longer current. New privileged work is not authorized.'] : []),
65
+ ...(row.problem ? [row.problem] : []), ...(row.candidate_world_id ? [`Candidate: ${workspaceLink(row.workspace_id, `/worlds/${identifier(row.candidate_world_id)}`)}`] : []),
66
+ ...(row.command_receipt?.recovered ? ['Original command receipt recovered. No replacement operation was queued.'] : []), link(row)].join('\n');
67
+ }
68
+ function comparisonDescription(row) {
69
+ return [`${row.id} · ${row.state.replaceAll('_', ' ')}`, `${row.retained_count} original scenarios · ${row.additional_count} additional · ${row.criterion_count} frozen criteria`,
70
+ `${row.repetitions} repetitions · Parallelism ${row.parallelism} · ${row.trial_seconds} seconds per drill`,
71
+ ...(row.current === false ? ['This saved setup is unavailable for new work.']
72
+ : row.current === null ? ['Current inputs and access are reviewed when configuring each comparison world.'] : []), ...(row.problem ? [row.problem] : []),
73
+ ...(row.control_world_id ? [`Control: ${workspaceLink(row.workspace_id, `/worlds/${identifier(row.control_world_id)}`)}`] : []),
74
+ ...(row.candidate_world_id ? [`Candidate: ${workspaceLink(row.workspace_id, `/worlds/${identifier(row.candidate_world_id)}`)}`] : [])].join('\n');
75
+ }
76
+ async function readObject(path, privateFile = false) {
77
+ const absolute = resolve(path), before = await lstat(absolute);
78
+ if (!before.isFile() || before.isSymbolicLink() || before.size < 2 || before.size > CAPACITY
79
+ || privateFile && (process.getuid && before.uid !== process.getuid() || process.platform !== 'win32' && !!(before.mode & 0o077))) {
80
+ throw new CliError('agent_change_file_invalid', 'Use a bounded regular JSON file. Recovery files must remain private to this OS user.', 2);
81
+ }
82
+ const file = await open(absolute, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
83
+ try {
84
+ const actual = await file.stat();
85
+ if (actual.dev !== before.dev || actual.ino !== before.ino || actual.size !== before.size || actual.mtimeMs !== before.mtimeMs
86
+ || !actual.isFile() || privateFile && (process.getuid && actual.uid !== process.getuid() || process.platform !== 'win32' && !!(actual.mode & 0o077)))
87
+ throw new Error();
88
+ const buffer = Buffer.alloc(actual.size);
89
+ let at = 0;
90
+ while (at < buffer.length) {
91
+ const part = await file.read(buffer, at, buffer.length - at, at);
92
+ if (!part.bytesRead)
93
+ throw new Error();
94
+ at += part.bytesRead;
95
+ }
96
+ const after = await file.stat();
97
+ if (after.size !== actual.size || after.mtimeMs !== actual.mtimeMs || after.ctimeMs !== actual.ctimeMs)
98
+ throw new Error();
99
+ const value = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(buffer));
100
+ if (!object(value))
101
+ throw new Error();
102
+ return value;
103
+ }
104
+ catch {
105
+ throw new CliError('agent_change_file_invalid', 'The exact JSON could not be read. No source-change command was submitted.', 2);
106
+ }
107
+ finally {
108
+ await file.close();
109
+ }
110
+ }
111
+ async function journalPath(scope, key) {
112
+ return join(await privateDirectory(), `agent-change-${identifier(scope.owner)}-${identifier(scope.workspace)}-${identifier(key)}.json`);
113
+ }
114
+ function validBody(value, change, comparison = false) {
115
+ if (comparison)
116
+ return change !== null && Object.keys(value).sort().join(',') === 'additional_scenarios,draft_sha256,prepare_comparison,scope_sha256'
117
+ && value.prepare_comparison === true && typeof value.scope_sha256 === 'string' && HASH.test(value.scope_sha256)
118
+ && typeof value.draft_sha256 === 'string' && HASH.test(value.draft_sha256) && Array.isArray(value.additional_scenarios)
119
+ && value.additional_scenarios.every(one => typeof one === 'string' && /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/.test(one))
120
+ && new Set(value.additional_scenarios).size === value.additional_scenarios.length;
121
+ if (change === null)
122
+ return Object.keys(value).sort().join(',') === 'instruction,paths,title'
123
+ && typeof value.title === 'string' && value.title.trim().length > 0 && value.title.length <= 160
124
+ && typeof value.instruction === 'string' && value.instruction.trim().length > 0 && value.instruction.length <= 8000
125
+ && Array.isArray(value.paths) && value.paths.length > 0 && value.paths.every(path => typeof path === 'string' && path.length > 0 && path.length <= 2048)
126
+ && new Set(value.paths).size === value.paths.length;
127
+ const action = value.action;
128
+ return typeof action === 'string' && ['approve', 'resume', 'accept', 'retry', 'dismiss'].includes(action)
129
+ && typeof value.scope_sha256 === 'string' && HASH.test(value.scope_sha256)
130
+ && (action === 'accept' ? Object.keys(value).sort().join(',') === 'action,draft_sha256,reviewed,scope_sha256'
131
+ && value.reviewed === true && typeof value.draft_sha256 === 'string' && HASH.test(value.draft_sha256)
132
+ : Object.keys(value).sort().join(',') === 'action,scope_sha256');
133
+ }
134
+ async function retained(scope, key) {
135
+ const row = await readObject(await journalPath(scope, key), true);
136
+ if (row.schema !== 'firedrill.native-agent-change.v1' || row.owner !== scope.owner || row.workspace !== scope.workspace || row.key !== identifier(key)
137
+ || typeof row.campaign !== 'string' || identifier(row.campaign) !== row.campaign
138
+ || row.change !== null && (typeof row.change !== 'string' || identifier(row.change) !== row.change)
139
+ || row.comparison !== undefined && row.comparison !== true
140
+ || !object(row.body) || !validBody(row.body, row.change, row.comparison === true) || row.digest !== digest(row.body)) {
141
+ throw new CliError('agent_change_request_invalid', 'The retained source-change request cannot be confirmed. Nothing was submitted.');
142
+ }
143
+ return row;
144
+ }
145
+ async function submit(row, scope, options, output) {
146
+ output('agent_change_request', { request_key: row.key, campaign_id: row.campaign, change_id: row.change, body_sha256: row.digest }, `Request: ${row.key}\nIf interrupted: firedrill changes recover --workspace ${scope.workspace} --request-key ${row.key}`);
147
+ if (row.comparison) {
148
+ const body = row.body;
149
+ const existing = await comparisonsPage(`${prefix(scope, row.campaign)}/source-changes/${row.change}`, scope, { ...options, request: row.key }, 1);
150
+ if (existing.results[0]) {
151
+ if (existing.results[0].request_digest !== await sourceMetadataDigest({ actor_id: scope.owner, body }))
152
+ throw invalidReceipt();
153
+ output('result', existing.results[0], comparisonDescription(existing.results[0]) + '\nOriginal setup recovered. No new work was submitted.');
154
+ return;
155
+ }
156
+ const result = await api(`${prefix(scope, row.campaign)}/source-changes/${row.change}/comparisons`, { ...auth(scope, options), method: 'POST', idempotencyKey: row.key, body });
157
+ if (!row.change || !agentComparisonMatches(result, scope.workspace, row.campaign, row.change) || result.request_key !== row.key
158
+ || result.actor_id !== scope.owner || result.request_digest !== await sourceMetadataDigest({ actor_id: scope.owner, body }))
159
+ throw invalidReceipt();
160
+ output('result', result, comparisonDescription(result) + '\nSeparate comparison setup saved. No agent was run and no Vault access was inherited.');
161
+ return;
162
+ }
163
+ const result = await api(`${prefix(scope, row.campaign)}/source-changes${row.change ? '/' + row.change : ''}`, { ...auth(scope, options), method: 'POST', idempotencyKey: row.key, body: row.body });
164
+ if (row.change ? !sourceChangeCommandMatches(result, scope.workspace, row.campaign, row.change, scope.owner, row.key, row.body, await sourceMetadataDigest(row.body))
165
+ : !sourceChangeProposalMatches(result, scope.workspace, row.campaign, scope.owner, row.key, row.body))
166
+ throw invalidReceipt();
167
+ const retainedResult = await api(`${prefix(scope, row.campaign)}/source-changes/${identifier(result.id)}`, auth(scope, options));
168
+ if (!sourceChangeMatches(retainedResult, scope.workspace, row.campaign, result.id) || retainedResult.scope_sha256 !== result.scope_sha256
169
+ || retainedResult.request_key !== result.request_key || retainedResult.source_id !== result.source_id)
170
+ throw invalidReceipt();
171
+ output('result', result, description(result) + '\nOriginal source, baseline and local files are unchanged. No simulation was started.');
172
+ }
173
+ async function remember(scope, campaign, change, body, options, output, comparison = false) {
174
+ if (!validBody(body, change, comparison))
175
+ throw new CliError('agent_change_input_invalid', 'Review the exact source-change fields. Nothing was submitted.', 2);
176
+ const key = options.request ? identifier(options.request) : randomUUID();
177
+ const row = { schema: 'firedrill.native-agent-change.v1', ...scope, campaign, change, key, body, digest: digest(body), ...(comparison ? { comparison: true } : {}) };
178
+ const encoded = JSON.stringify(row);
179
+ if (Buffer.byteLength(encoded) > CAPACITY)
180
+ throw new CliError('agent_change_input_capacity', 'Use a more focused source-change request.', 2);
181
+ try {
182
+ const file = await open(await journalPath(scope, key), 'wx', 0o600);
183
+ try {
184
+ await file.writeFile(encoded);
185
+ await file.sync();
186
+ }
187
+ finally {
188
+ await file.close();
189
+ }
190
+ }
191
+ catch (error) {
192
+ if (!error || typeof error !== 'object' || !('code' in error) || error.code !== 'EEXIST')
193
+ throw error;
194
+ const prior = await retained(scope, key);
195
+ if (prior.campaign !== campaign || prior.change !== change || prior.digest !== row.digest || prior.comparison !== row.comparison) {
196
+ throw new CliError('agent_change_request_conflict', 'This request key belongs to other inputs. Recover its original request instead.', 2);
197
+ }
198
+ }
199
+ await submit(row, scope, options, output);
200
+ }
201
+ async function approval(label, options) {
202
+ if (options.approve || tty(options) && await selectOne(label + ' Keep the exact private local recovery request.', [{ value: false, label: 'Cancel' }, { value: true, label: 'Confirm' }], options.signal))
203
+ return;
204
+ throw new CliError('agent_change_consent_required', 'Nothing was submitted. Use --approve-change only after reviewing the exact source scope and local request retention.', 2);
205
+ }
206
+ async function line(label, maximum, signal) {
207
+ const prompt = createInterface({ input: process.stdin, output: process.stderr, terminal: false });
208
+ try {
209
+ const value = (await prompt.question(`${label}: `, { signal })).trim();
210
+ if (!value || value.length > maximum || /[\u0000-\u001f\u007f]/u.test(value))
211
+ throw new CliError('agent_change_input_invalid', 'Use nonempty text within the field capacity. Do not include credentials.', 2);
212
+ return value;
213
+ }
214
+ finally {
215
+ prompt.close();
216
+ }
217
+ }
218
+ async function choosePage(title, path, scope, options, describe, read) {
219
+ let page = Number(options.page ?? '1');
220
+ while (true) {
221
+ const data = read ? await read(page) : await api(`${path}${path.includes('?') ? '&' : '?'}page=${page}`, auth(scope, options));
222
+ const choices = data.results.map(item => ({ value: { kind: 'item', item }, ...describe(item) }));
223
+ if (page > 1)
224
+ choices.unshift({ value: { kind: 'previous' }, label: 'Previous page' });
225
+ if (data.next)
226
+ choices.push({ value: { kind: 'next' }, label: 'Next page' });
227
+ if (!choices.length || !data.count)
228
+ throw new CliError('agent_change_selection_empty', 'No matching saved items are available.');
229
+ const choice = await selectOne(title, choices, options.signal);
230
+ if (choice.kind === 'item')
231
+ return choice.item;
232
+ page += choice.kind === 'next' ? 1 : -1;
233
+ }
234
+ }
235
+ async function selectedFiles(path, scope, options) {
236
+ const selected = new Set();
237
+ let page = 1;
238
+ while (true) {
239
+ const data = await filePage(path, scope, options, page);
240
+ if (!data.count)
241
+ throw new CliError('agent_change_files_empty', 'No eligible source files match this search.');
242
+ const files = await selectMany('Source files Firedrill may read and propose changes to', data.results.map(file => ({ value: file.path, label: file.path, detail: `${file.size} bytes · ${file.sha256}` })), options.signal, [...selected]);
243
+ for (const file of data.results)
244
+ selected.delete(file.path);
245
+ for (const file of files)
246
+ selected.add(file);
247
+ const next = await selectOne(`${selected.size} files selected across pages`, [
248
+ ...(selected.size ? [{ value: 'done', label: 'Continue with selected files' }] : []),
249
+ ...(data.next ? [{ value: 'next', label: 'Next page' }] : []), ...(page > 1 ? [{ value: 'previous', label: 'Previous page' }] : []),
250
+ { value: 'cancel', label: 'Cancel' }
251
+ ], options.signal);
252
+ if (next === 'done')
253
+ return [...selected].sort();
254
+ if (next === 'cancel')
255
+ throw new CliError('canceled', 'No source-change request was submitted.', 130);
256
+ page += next === 'next' ? 1 : -1;
257
+ }
258
+ }
259
+ async function additionalScenarios(path, scope, options) {
260
+ if (options.additionalScenarios)
261
+ return [...new Set(options.additionalScenarios.map(identifier))].sort();
262
+ if (!tty(options) || !await selectOne('Include additional saved scenarios outside the original run?', [{ value: false, label: 'Use original scenarios only' }, { value: true, label: 'Choose additional scenarios' }], options.signal))
263
+ return [];
264
+ const selected = new Set();
265
+ let page = 1;
266
+ while (true) {
267
+ const selectedScope = selectedPath(path, scope);
268
+ const result = await api(`${path}/comparison-scenarios?page=${page}&q=${encodeURIComponent(options.search ?? '')}`, auth(scope, options));
269
+ if (!sourceChangePageMatches(result, scope.workspace, selectedScope.campaign, page) || result.change_id !== selectedScope.change
270
+ || result.query !== (options.search ?? '').trim() || !result.results.every(row => row && identifier(row.id) === row.id && typeof row.name === 'string' && HASH.test(row.sha256)))
271
+ throw invalidReceipt();
272
+ if (!result.count)
273
+ throw new CliError('comparison_scenarios_empty', 'No additional scenarios match. Run the command again without adding scenarios. Nothing was submitted.', 2);
274
+ const checked = await selectMany('Additional scenarios (not a guarantee they were unseen by the agent or its authors)', result.results.map(row => ({ value: identifier(row.id), label: row.name, detail: row.sha256 })), options.signal, [...selected]);
275
+ for (const row of result.results)
276
+ selected.delete(row.id);
277
+ for (const id of checked)
278
+ selected.add(id);
279
+ const action = await selectOne(`${selected.size} additional scenarios selected across pages`, [
280
+ { value: 'done', label: 'Continue' }, ...(result.next ? [{ value: 'next', label: 'Next page' }] : []),
281
+ ...(page > 1 ? [{ value: 'previous', label: 'Previous page' }] : []), { value: 'cancel', label: 'Cancel' }
282
+ ], options.signal);
283
+ if (action === 'done')
284
+ return [...selected].sort();
285
+ if (action === 'cancel')
286
+ throw new CliError('canceled', 'No comparison setup was submitted.', 130);
287
+ page += action === 'next' ? 1 : -1;
288
+ }
289
+ }
290
+ async function comparisonHistory(path, scope, options, output, runScope) {
291
+ if (!runScope) {
292
+ const result = await comparisonsPage(path, scope, options, Number(options.page ?? 1));
293
+ output('result', result, result.results.map(comparisonDescription).join('\n\n') || 'No comparison setup has been saved.');
294
+ if (options.request) {
295
+ if (!result.count)
296
+ output('comparison_not_received', { request_key: options.request }, 'The original setup is not confirmed. Recover the same retained request to retry; do not create a replacement request.');
297
+ return;
298
+ }
299
+ if (result.next)
300
+ output('next_page', { page: result.page + 1 }, `Next page: --page ${result.page + 1}`);
301
+ if (!tty(options) || !result.count)
302
+ return;
303
+ runScope = identifier((await choosePage('Saved comparison', path + '/comparisons', scope, options, row => ({ label: `${row.retained_count} original and ${row.additional_count} additional scenarios`, detail: row.state }), page => comparisonsPage(path, scope, options, page))).id);
304
+ }
305
+ const side = options.side ?? (tty(options) ? await selectOne('Simulation history', [{ value: 'control', label: 'Control' }, { value: 'candidate', label: 'Candidate' }], options.signal) : undefined);
306
+ if (!side || !['control', 'candidate'].includes(side))
307
+ throw new CliError('comparison_side_required', 'Choose --side control or --side candidate for comparison run history.', 2);
308
+ const selectedScope = selectedPath(path, scope);
309
+ const result = await api(`${path}/comparisons/${identifier(runScope)}/runs?side=${side}&page=${options.page ?? '1'}`, auth(scope, options));
310
+ if (!sourceChangePageMatches(result, scope.workspace, selectedScope.campaign, Number(options.page ?? 1)) || result.change_id !== selectedScope.change
311
+ || result.comparison_id !== runScope || result.side !== side || !result.results.every(row => row && identifier(row.id) === row.id
312
+ && row.report_url === `/campaigns/${row.id}` && Number.isSafeInteger(row.planned_trials) && row.planned_trials > 0))
313
+ throw invalidReceipt();
314
+ output('result', result, result.results.map(row => `${row.id} · ${row.state} · ${row.planned_trials} planned drills\n${workspaceLink(scope.workspace, `/campaigns/${identifier(row.id)}`)}`).join('\n\n') || 'No simulations have been saved for this side yet.');
315
+ if (result.next)
316
+ output('next_page', { page: result.page + 1 }, `Next page: --page ${result.page + 1}`);
317
+ }
318
+ async function showDiffs(row, path, scope, options, output, position) {
319
+ const files = row.draft_files;
320
+ if (!row.current || !row.draft_sha256 || !files?.length)
321
+ throw new CliError('agent_change_draft_unavailable', 'No current draft is available to inspect.');
322
+ if (position !== undefined && (!/^[1-9][0-9]*$/.test(position) || Number(position) > files.length))
323
+ throw new CliError('arguments_invalid', 'Choose a one-based file position from this draft.', 2);
324
+ for (let index = 0; index < files.length; index++) {
325
+ if (position !== undefined && index !== Number(position) - 1)
326
+ continue;
327
+ const file = files[index];
328
+ const data = await api(`${path}/files/${index}`, auth(scope, options));
329
+ if (!sourceChangeDiffMatches(data, row, file, index)) {
330
+ throw new CliError('agent_change_draft_changed', 'The diff does not match the selected draft. Review it again before accepting.');
331
+ }
332
+ output('agent_change_diff', { ...data, draft_sha256: row.draft_sha256, position: index + 1 }, data.diff);
333
+ }
334
+ }
335
+ export async function changesCommand(args, options, output) {
336
+ if (options.page !== undefined && !/^[1-9][0-9]{0,8}$/.test(options.page) || (options.search?.length ?? 0) > 200)
337
+ throw new CliError('arguments_invalid', 'Choose valid paging and search options.', 2);
338
+ if (args.length > 4)
339
+ throw new CliError('arguments_invalid', 'Use changes [campaign] [action] [change] [file-position].', 2);
340
+ const interactive = tty(options), scope = await selectedWorkspace({ ...options, interactive });
341
+ if (args[0] === 'copy-resume' || args[0] === 'copy-forget') {
342
+ if (args.length !== 1 || !options.request || options.page || options.search || options.file || options.reviewedDraft
343
+ || options.into || options.approve || options.additionalScenarios || options.side)
344
+ throw new CliError('arguments_invalid', 'Use changes copy-resume --request-key <id> --workspace <id>.', 2);
345
+ return args[0] === 'copy-forget' ? forgetCandidateCopy(scope, options, output) : candidateCopy(scope, undefined, undefined, options, output);
346
+ }
347
+ if (args[0] === 'recover' || args[0] === 'forget') {
348
+ if (args.length !== 1 || !options.request || options.page || options.search || options.file || options.reviewedDraft
349
+ || options.into || options.approveDownload || options.additionalScenarios || options.side)
350
+ throw new CliError('arguments_invalid', 'Use changes recover|forget --request-key <id> --workspace <id>.', 2);
351
+ const row = await retained(scope, options.request);
352
+ if (args[0] === 'recover')
353
+ return submit(row, scope, options, output);
354
+ await approval('Remove only this local recovery copy? It will not cancel an approved draft or candidate save.', options);
355
+ await unlink(await journalPath(scope, row.key));
356
+ output('result', { request_key: row.key, local_copy_removed: true }, 'Local recovery copy removed. Accepted server work is unchanged.');
357
+ return;
358
+ }
359
+ let campaign = args[0] ? identifier(args[0]) : undefined;
360
+ if (!campaign && !interactive)
361
+ throw new CliError('campaign_required', 'Pass a saved campaign ID, or run firedrill changes interactively.', 2);
362
+ if (!campaign) {
363
+ const row = await choosePage('Baseline simulation', `/api/workspaces/${scope.workspace}/simulations?q=${encodeURIComponent(options.search ?? '')}`, scope, options, row => ({ label: row.name, detail: row.status }));
364
+ campaign = identifier(row.campaign_id);
365
+ }
366
+ const base = prefix(scope, campaign), path = `${base}/source-changes`;
367
+ let action = args[1];
368
+ const guided = interactive && !action;
369
+ if (guided)
370
+ action = await selectOne('Agent source changes', [{ value: 'list', label: 'Review saved changes' },
371
+ { value: 'propose', label: 'Propose a scoped change' }], options.signal);
372
+ action ??= 'list';
373
+ if (!['list', 'files', 'show', 'diff', 'copy', 'comparisons', 'comparison-runs', ...MUTATIONS].includes(action)
374
+ || !MUTATIONS.includes(action) && (options.file || !['copy', 'comparisons'].includes(action) && options.request || options.approve || options.reviewedDraft)
375
+ || action !== 'propose' && options.file || action !== 'accept' && options.reviewedDraft
376
+ || action !== 'copy' && !guided && (options.into || options.approveDownload)
377
+ || args[3] && !['diff', 'comparison-runs'].includes(action) || ['files', 'list', 'propose'].includes(action) && args[2]
378
+ || options.search && !['files', 'propose', 'compare'].includes(action) && !!args[0]
379
+ || options.additionalScenarios && action !== 'compare' || options.side && !['comparisons', 'comparison-runs'].includes(action)
380
+ || options.page && !['files', 'list', 'comparisons', 'comparison-runs'].includes(action) && !!args[0])
381
+ throw new CliError('arguments_invalid', 'Use only the options for the selected source-change action.', 2);
382
+ if (action === 'files' || action === 'list' && !guided) {
383
+ const data = action === 'files' ? await filePage(base + '/source-change-files', scope, options, Number(options.page ?? 1))
384
+ : await changePage(path, scope, options, Number(options.page ?? 1));
385
+ output('result', data, [data.results.map(row => 'path' in row ? `${row.path} · ${row.size} bytes · ${row.sha256}` : description(row)).join('\n\n') || 'No matching items.',
386
+ ...(data.next ? [`Next page: --page ${data.page + 1}`] : [])].join('\n'));
387
+ return;
388
+ }
389
+ if (action === 'propose') {
390
+ let body;
391
+ if (options.file)
392
+ body = await readObject(options.file);
393
+ else {
394
+ if (!interactive)
395
+ throw new CliError('agent_change_file_required', 'Pass --file <change.json> with title, instruction and paths, or run firedrill changes interactively.', 2);
396
+ const paths = await selectedFiles(base + '/source-change-files', scope, options);
397
+ const title = await line('Change name (up to 160 characters)', 160, options.signal);
398
+ const instruction = await line('Intended behavior (up to 8,000 characters, no credentials)', 8000, options.signal);
399
+ body = { title, instruction, paths };
400
+ }
401
+ if (!validBody(body, null))
402
+ throw new CliError('agent_change_input_invalid', 'Use title, instruction and unique paths only.', 2);
403
+ output('agent_change_scope', body, JSON.stringify(body, null, 2));
404
+ await approval('Save this scoped request for review? Source access and candidate saving need separate approval.', options);
405
+ await remember(scope, campaign, null, body, options, output);
406
+ return;
407
+ }
408
+ let change = args[2] ? identifier(args[2]) : undefined;
409
+ if (!change && interactive)
410
+ change = identifier((await choosePage('Saved source change', path, scope, { ...options, search: undefined }, row => ({ label: row.title, detail: row.state }), page => changePage(path, scope, options, page))).id);
411
+ if (!change)
412
+ throw new CliError('agent_change_required', 'Pass a saved source-change ID.', 2);
413
+ const detail = path + '/' + change;
414
+ const row = await api(detail, auth(scope, options));
415
+ if (!sourceChangeMatches(row, scope.workspace, campaign, change) || !Array.isArray(row.draft_files))
416
+ throw new CliError('agent_change_unavailable', 'The scoped source-change response could not be confirmed.');
417
+ output('agent_change', row, description(row));
418
+ if (guided) {
419
+ const choices = [{ value: 'done', label: 'Done' }];
420
+ if (row.current) {
421
+ if (row.draft_files?.length)
422
+ choices.push({ value: 'diff', label: 'Read actual source diffs' });
423
+ if (row.state === 'pending')
424
+ choices.push({ value: 'approve', label: 'Allow drafting for selected files' });
425
+ if (row.state === 'approved' && row.approved_by_id === scope.owner)
426
+ choices.push({ value: 'resume-draft', label: 'Request continued drafting' });
427
+ if (row.state === 'drafted')
428
+ choices.push({ value: 'accept', label: 'Review diff and save candidate' });
429
+ if (row.state === 'creating' && row.problem)
430
+ choices.push({ value: 'retry-save', label: 'Retry the failed candidate save' });
431
+ if (row.state === 'accepted') {
432
+ choices.push({ value: 'copy', label: 'Download candidate to a separate local folder' });
433
+ if (row.comparison_available)
434
+ choices.push({ value: 'compare', label: 'Prepare control and candidate comparison' });
435
+ }
436
+ }
437
+ if (row.state === 'accepted')
438
+ choices.push({ value: 'comparisons', label: 'Read saved comparison setup and run history' });
439
+ if (!['accepted', 'dismissed'].includes(row.state))
440
+ choices.push({ value: 'dismiss', label: 'Dismiss change' });
441
+ const chosen = await selectOne('Source change', choices, options.signal);
442
+ if (chosen === 'done')
443
+ return;
444
+ action = chosen;
445
+ }
446
+ if (action === 'show')
447
+ return;
448
+ if (action === 'comparisons')
449
+ return comparisonHistory(detail, scope, options, output);
450
+ if (action === 'comparison-runs') {
451
+ if (!args[3])
452
+ throw new CliError('comparison_required', 'Pass the saved comparison ID after the change ID.', 2);
453
+ return comparisonHistory(detail, scope, options, output, identifier(args[3]));
454
+ }
455
+ if (action === 'compare') {
456
+ if (!row.comparison_available)
457
+ throw new CliError('agent_comparison_unavailable', 'Review a current approved source candidate before preparing comparison worlds.', 2);
458
+ if (row.state !== 'accepted' || !row.current || !row.draft_sha256)
459
+ throw new CliError('comparison_candidate_required', 'Save a reviewed, current candidate before preparing comparison worlds.', 2);
460
+ const body = { scope_sha256: row.scope_sha256, draft_sha256: row.draft_sha256,
461
+ additional_scenarios: await additionalScenarios(detail, scope, options), prepare_comparison: true };
462
+ output('comparison_scope', body, `Retain original scenarios, criteria, world and run settings. Add ${body.additional_scenarios.length} separately saved scenarios.\nBoth worlds need fresh connection review, validation and explicit run admission.`);
463
+ await approval('Prepare separate control and candidate worlds without running either agent or inheriting Vault access?', options);
464
+ return remember(scope, campaign, change, body, options, output, true);
465
+ }
466
+ if (action === 'diff')
467
+ return showDiffs(row, detail, scope, options, output, args[3]);
468
+ if (action === 'copy') {
469
+ const into = options.into ?? (interactive ? await line('New candidate folder (parent folder must exist)', 4096, options.signal) : undefined);
470
+ return candidateCopy(scope, campaign, change, { ...options, into }, output);
471
+ }
472
+ if (!MUTATIONS.includes(action))
473
+ return;
474
+ const body = { action: serverAction(action), scope_sha256: row.scope_sha256 };
475
+ if (action === 'accept') {
476
+ if (row.state !== 'drafted' || !row.current || !row.draft_sha256)
477
+ throw new CliError('agent_change_draft_required', 'Review a current completed draft before saving a candidate.');
478
+ if (interactive)
479
+ await showDiffs(row, detail, scope, options, output);
480
+ else if (options.reviewedDraft !== row.draft_sha256)
481
+ throw new CliError('agent_change_review_required', 'Inspect changes <campaign> diff <change> first, then supply --reviewed-draft <exact-sha256> and --approve-change.', 2);
482
+ if (options.reviewedDraft && options.reviewedDraft !== row.draft_sha256)
483
+ throw new CliError('agent_change_draft_changed', 'The reviewed draft changed. Inspect the current diff before accepting.', 2);
484
+ body.draft_sha256 = row.draft_sha256;
485
+ body.reviewed = true;
486
+ }
487
+ const labels = {
488
+ approve: 'Allow Firedrill to read and draft changes only in these files? This may use the configured model.',
489
+ 'resume-draft': 'Continue drafting for this exact approved scope? This may use the configured model.',
490
+ accept: 'I reviewed the actual diff, including safety and approval behavior. Save a separate candidate without running it?',
491
+ 'retry-save': 'Retry saving the same approved draft? This does not rerun the agent or request another draft.',
492
+ dismiss: 'Dismiss this source-change request and revoke further draft access?',
493
+ };
494
+ await approval(labels[action], options);
495
+ await remember(scope, campaign, change, body, options, output);
496
+ }
@@ -0,0 +1,67 @@
1
+ import { api } from './auth.js';
2
+ import { CliError } from './http.js';
3
+ import { selectOne } from './prompts.js';
4
+ import { identifier } from './world-contracts.js';
5
+ import { selectedWorkspace } from './world-access.js';
6
+ import { assessmentRequestMatches } from '../../contracts/assessment-contracts.js';
7
+ const keyPattern = /^[A-Za-z0-9][A-Za-z0-9:._-]{0,127}$/;
8
+ const invalid = () => new CliError('assessment_request_invalid', 'The original assessment receipt could not be confirmed. No evaluation was repeated.');
9
+ function checked(value, scope, campaign, trial, key) {
10
+ if (!assessmentRequestMatches(value, { actor: scope.owner, workspace: scope.workspace, campaign, execution: trial }, key))
11
+ throw invalid();
12
+ return value;
13
+ }
14
+ export function assessmentRequestText(value) {
15
+ return `Request: ${value.request_key}\n${value.state === 'accepted'
16
+ ? value.resource_available ? `Assessment accepted · ${value.assessment_state}\n${value.assessment_id}\nAcceptance is not a completed score. No evaluation or agent execution was repeated.`
17
+ : 'The request was accepted, but its assessment is no longer available.'
18
+ : value.state === 'withdrawn' ? 'Canceled before acceptance. This request cannot start an assessment.'
19
+ : 'No acceptance is recorded yet. The original request may still arrive. Check it again or cancel only this unaccepted request.'}`;
20
+ }
21
+ export async function inspectAssessmentRequest(prefix, key, scope, options) {
22
+ const campaign = identifier(prefix.split('/').at(-3)), trial = identifier(prefix.split('/').at(-1));
23
+ if (!keyPattern.test(key) || prefix !== `/api/workspaces/${identifier(scope.workspace)}/campaigns/${campaign}/trials/${trial}`)
24
+ throw invalid();
25
+ return checked(await api(`${prefix}/assessment-request?request_key=${encodeURIComponent(key)}`, { actor: scope.owner, workspace: scope.workspace, signal: options.signal }), scope, campaign, trial, key);
26
+ }
27
+ export async function assessmentRequests(args, options, output) {
28
+ const action = args[0], history = action === 'requests';
29
+ if (!['requests', 'request', 'withdraw-request'].includes(action) || args.length !== (history ? 3 : 4)
30
+ || !history && (options.page !== undefined || options.search !== undefined || options.status !== undefined)
31
+ || action !== 'withdraw-request' && options.approve)
32
+ throw new CliError('arguments_invalid', 'Use assess requests <campaign> <drill>, request <campaign> <drill> <key>, or withdraw-request <campaign> <drill> <key>.', 2);
33
+ const campaign = identifier(args[1]), trial = identifier(args[2]), key = args[3];
34
+ if (!history && !keyPattern.test(key))
35
+ throw invalid();
36
+ const page = options.page ?? '1', query = options.search ?? '', status = options.status ?? '';
37
+ if (!/^[1-9][0-9]{0,8}$/.test(page) || query.length > 200 || !['', 'accepted', 'withdrawn'].includes(status)) {
38
+ throw new CliError('arguments_invalid', 'Use a positive page, a search of up to 200 characters and accepted or withdrawn status.', 2);
39
+ }
40
+ const interactive = !options.json && !!process.stdin.isTTY && !!process.stderr.isTTY;
41
+ const scope = await selectedWorkspace({ ...options, interactive });
42
+ const prefix = `/api/workspaces/${scope.workspace}/campaigns/${campaign}/trials/${trial}`;
43
+ const auth = { actor: scope.owner, workspace: scope.workspace, signal: options.signal };
44
+ if (history) {
45
+ const params = new URLSearchParams({ page, q: query, status });
46
+ const result = await api(`${prefix}/assessment-requests?${params}`, auth);
47
+ const quote = (value) => `'${value.replaceAll("'", "'\\''")}'`;
48
+ output(result, (result.results.map(value => assessmentRequestText(checked(value, scope, campaign, trial))).join('\n\n') || 'No matching requests from your account.')
49
+ + `\nPage ${result.page}, ${result.count} requests.${result.next ? `\nContinue: firedrill assess requests ${campaign} ${trial} --workspace ${scope.workspace} --page ${result.page + 1}${query ? ' --search ' + quote(query) : ''}${status ? ' --status ' + status : ''}${options.json ? ' --json' : ''}` : ''}`);
50
+ return;
51
+ }
52
+ if (action === 'request') {
53
+ const value = await inspectAssessmentRequest(prefix, key, scope, options);
54
+ output(value, assessmentRequestText(value));
55
+ return;
56
+ }
57
+ if (!options.approve) {
58
+ if (!interactive)
59
+ throw new CliError('approval_required', 'Use --approve-assessment to cancel this exact unaccepted request. Accepted assessments are not canceled.', 2);
60
+ if (!await selectOne('Cancel this unaccepted assessment request? An accepted assessment will only be inspected.', [
61
+ { value: false, label: 'Keep request' }, { value: true, label: 'Cancel unaccepted request' }
62
+ ], options.signal))
63
+ return;
64
+ }
65
+ const value = checked(await api(`${prefix}/assessment-request`, { ...auth, method: 'POST', body: { request_key: key } }), scope, campaign, trial, key);
66
+ output(value, assessmentRequestText(value));
67
+ }