firedrill-run 2.2.5 → 3.0.1

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 +176 -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 +506 -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 +1627 -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 +96 -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,183 @@
1
+ /**
2
+ * Renewing a running shared-pool tool batch (Plan20 R27, root
3
+ * `native_standalone_renewal_views.py`): extending the same live batch's end
4
+ * time for all of its instances, with no reset, restart or new instance. It is
5
+ * never a heartbeat. Root reviews the extension under the caller's request key
6
+ * and quotes it at once (`reviewed`, nothing reserved); the extension and its
7
+ * credit hold commit together only when the exact quote is approved
8
+ * (`applied`). An unused review is closed explicitly (`cancelled`), and an
9
+ * original request whose answer was lost is read back by its key or withdrawn.
10
+ *
11
+ * Isolated tools keep their own extension flow, and drill-owned tools never
12
+ * extend a campaign's deadline: this module only speaks to standalone batches.
13
+ */
14
+ import { api } from '../auth.js';
15
+ import { creditApprovalPolicy, withoutCreditPolicyKeys } from './credit-approval.js';
16
+ import { CliError } from '../http.js';
17
+ export const RENEWAL_MIN_SECONDS = 60;
18
+ export const RENEWAL_MAX_SECONDS = 31_536_000;
19
+ export const RENEWAL_STATES = ['reviewed', 'applied', 'cancelled'];
20
+ const RENEWAL_PAGE = 25;
21
+ const RENEWAL_SCHEMA = 'firedrill.native-standalone-renewal.v1';
22
+ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
23
+ const SHA256 = /^[0-9a-f]{64}$/;
24
+ const RESPONSE_LIMIT = 4 * 1024 * 1024;
25
+ const object = (value) => value !== null && typeof value === 'object' && !Array.isArray(value);
26
+ const uuid = (value) => typeof value === 'string' && UUID.test(value);
27
+ const count = (value, min = 0) => typeof value === 'number' && Number.isSafeInteger(value) && value >= min;
28
+ const time = (value) => typeof value === 'string' && Number.isFinite(Date.parse(value));
29
+ const text = (value, max) => typeof value === 'string' && value.length >= 1 && value.length <= max;
30
+ const keys = (value) => Object.keys(value).sort().join(',');
31
+ const sameTime = (left, right) => time(left) && time(right) && Date.parse(left) === Date.parse(right);
32
+ const unconfirmed = (what) => new CliError('native_response_unconfirmed', `The ${what} response could not be confirmed. Nothing further was sent for it.`);
33
+ const scoped = (scope, signal) => ({ actor: scope.owner, workspace: scope.workspace, signal, maximumResponseBytes: RESPONSE_LIMIT });
34
+ const base = (scope, batchId) => `/api/workspaces/${scope.workspace}/tools/native/batches/${batchId}/renewals`;
35
+ export function renewalId(value, label) {
36
+ if (!uuid(value))
37
+ throw new CliError('arguments_invalid', `Use the ${label} exactly as Firedrill printed it. Nothing was sent.`, 2);
38
+ return value.toLowerCase();
39
+ }
40
+ const ROW_KEYS = 'additional_seconds,applied_at,batch_id,created_at,id,instance_count,new_expires_at,previous_expires_at,quote,request_key,state';
41
+ const QUOTE_KEYS = 'activity,credit_scale,denomination,estimated_quantities,estimated_units,execution_location,expires_at,id,maximum_units,organization_id,'
42
+ + 'resource_class,scope,scope_digest,tariff_digest,tariff_version,workspace_id';
43
+ const SCOPE_KEYS = 'batch_id,instance_count,local_target_compute_charged,new_expires_at,previous_expires_at,renewal_digest,renewal_id,schema';
44
+ /**
45
+ * One renewal exactly as root defines it: this workspace's batch, the asked-for
46
+ * batch, a new end exactly `additional_seconds` after the previous one, applied
47
+ * exactly when it has an `applied_at`, and a quote frozen to this renewal's own
48
+ * end times and instance count that never charges local compute.
49
+ */
50
+ function renewalRow(scope, batchId) {
51
+ return (value) => {
52
+ const quote = object(value) ? value.quote : null;
53
+ const frozen = object(quote) ? quote.scope : null;
54
+ const quantities = object(quote) ? quote.estimated_quantities : null;
55
+ if (!object(value) || keys(value) !== ROW_KEYS || !uuid(value.id) || value.batch_id !== batchId || !uuid(value.request_key)
56
+ || !RENEWAL_STATES.includes(value.state)
57
+ || !count(value.additional_seconds, RENEWAL_MIN_SECONDS) || value.additional_seconds > RENEWAL_MAX_SECONDS
58
+ || !time(value.previous_expires_at) || !time(value.new_expires_at)
59
+ || Date.parse(value.new_expires_at) - Date.parse(value.previous_expires_at) !== value.additional_seconds * 1000
60
+ || !count(value.instance_count, 1) || value.instance_count > 32 || !time(value.created_at)
61
+ || (value.applied_at !== null && !time(value.applied_at)) || (value.state === 'applied') !== (value.applied_at !== null)
62
+ || !object(quote) || withoutCreditPolicyKeys(quote) !== QUOTE_KEYS || !uuid(quote.id) || quote.workspace_id !== scope.workspace.toLowerCase()
63
+ || !uuid(quote.organization_id) || quote.activity !== 'tool_runtime' || quote.execution_location !== 'managed'
64
+ || !text(quote.resource_class, 64) || !text(quote.tariff_version, 64) || !text(quote.denomination, 64)
65
+ || typeof quote.scope_digest !== 'string' || !SHA256.test(quote.scope_digest)
66
+ || typeof quote.tariff_digest !== 'string' || !SHA256.test(quote.tariff_digest) || !count(quote.credit_scale, 1)
67
+ || !count(quote.estimated_units) || !count(quote.maximum_units) || quote.estimated_units > quote.maximum_units || !time(quote.expires_at)
68
+ || !object(quantities) || keys(quantities) !== 'allocated_ms'
69
+ || quantities.allocated_ms !== value.additional_seconds * 1000 * value.instance_count
70
+ || !object(frozen) || keys(frozen) !== SCOPE_KEYS || frozen.schema !== RENEWAL_SCHEMA || frozen.renewal_id !== value.id
71
+ || frozen.batch_id !== batchId || typeof frozen.renewal_digest !== 'string' || !SHA256.test(frozen.renewal_digest)
72
+ || !sameTime(frozen.previous_expires_at, value.previous_expires_at) || !sameTime(frozen.new_expires_at, value.new_expires_at)
73
+ || frozen.instance_count !== value.instance_count || frozen.local_target_compute_charged !== false)
74
+ throw unconfirmed('tool renewal');
75
+ return {
76
+ id: value.id.toLowerCase(), batch_id: batchId, request_key: value.request_key.toLowerCase(), state: value.state,
77
+ additional_seconds: value.additional_seconds, previous_expires_at: value.previous_expires_at, new_expires_at: value.new_expires_at,
78
+ instance_count: value.instance_count, created_at: value.created_at, applied_at: value.applied_at,
79
+ quote: { ...creditApprovalPolicy(quote), id: quote.id.toLowerCase(), denomination: quote.denomination, credit_scale: quote.credit_scale, estimated_units: quote.estimated_units,
80
+ maximum_units: quote.maximum_units, expires_at: quote.expires_at, resource_class: quote.resource_class, allocated_ms: quantities.allocated_ms },
81
+ };
82
+ };
83
+ }
84
+ /** A batch's reviews and applied extensions, newest first (25 per page). */
85
+ export async function listRenewals(scope, input) {
86
+ const batchId = renewalId(input.batchId, 'tool session id');
87
+ const page = input.page ?? 1;
88
+ if (!count(page, 1))
89
+ throw new CliError('arguments_invalid', 'Use --page with a positive page number.', 2);
90
+ const raw = await api(`${base(scope, batchId)}?page=${page}&page_size=${RENEWAL_PAGE}`, scoped(scope, input.signal));
91
+ if (!object(raw) || keys(raw) !== 'count,next,page,page_size,previous,results' || !count(raw.count) || raw.page !== page
92
+ || raw.page_size !== RENEWAL_PAGE || (raw.next !== null && typeof raw.next !== 'string') || (raw.previous !== null && typeof raw.previous !== 'string')
93
+ || !Array.isArray(raw.results) || raw.results.length > RENEWAL_PAGE || (raw.next !== null) !== (page * RENEWAL_PAGE < raw.count)) {
94
+ throw unconfirmed('tool renewals');
95
+ }
96
+ return { count: raw.count, page, next: raw.next, previous: raw.previous, results: raw.results.map(renewalRow(scope, batchId)) };
97
+ }
98
+ /** One renewal by id (a review, an applied extension or a closed review). */
99
+ export async function getRenewal(scope, input) {
100
+ const batchId = renewalId(input.batchId, 'tool session id');
101
+ const id = renewalId(input.renewalId, 'renewal id');
102
+ const row = renewalRow(scope, batchId)(await api(`${base(scope, batchId)}/${id}`, scoped(scope, input.signal)));
103
+ if (row.id !== id)
104
+ throw unconfirmed('tool renewal');
105
+ return row;
106
+ }
107
+ /**
108
+ * Review extending the batch by `seconds` under the caller's request key. It
109
+ * reserves nothing and extends nothing; the same key returns the same original
110
+ * review (a different length under it is refused by root).
111
+ */
112
+ export async function reviewRenewal(scope, input) {
113
+ const batchId = renewalId(input.batchId, 'tool session id');
114
+ const requestKey = renewalId(input.requestKey, 'request key');
115
+ const row = renewalRow(scope, batchId)(await api(base(scope, batchId), {
116
+ ...scoped(scope, input.signal), method: 'POST', idempotencyKey: requestKey, body: { additional_seconds: input.seconds }
117
+ }));
118
+ if (row.request_key !== requestKey || row.additional_seconds !== input.seconds)
119
+ throw unconfirmed('tool renewal');
120
+ return row;
121
+ }
122
+ /** The caller's original review under this key, or null when root has none yet (which does not prove it will never arrive). */
123
+ export async function recoverRenewal(scope, input) {
124
+ const batchId = renewalId(input.batchId, 'tool session id');
125
+ const requestKey = renewalId(input.requestKey, 'request key');
126
+ try {
127
+ const row = renewalRow(scope, batchId)(await api(`${base(scope, batchId)}/requests/${requestKey}`, scoped(scope, input.signal)));
128
+ if (row.request_key !== requestKey)
129
+ throw unconfirmed('tool renewal');
130
+ return row;
131
+ }
132
+ catch (error) {
133
+ if (error instanceof CliError && error.httpStatus === 404)
134
+ return null;
135
+ throw error;
136
+ }
137
+ }
138
+ /**
139
+ * Apply a reviewed extension with exactly its quote, after the human approved
140
+ * it. The credit hold and the new end time commit together; an already applied
141
+ * renewal is returned unchanged, so a lost answer is recovered, never charged twice.
142
+ */
143
+ export async function applyRenewal(scope, input) {
144
+ const batchId = renewalId(input.renewal.batch_id, 'tool session id');
145
+ const id = renewalId(input.renewal.id, 'renewal id');
146
+ const row = renewalRow(scope, batchId)(await api(`${base(scope, batchId)}/${id}`, {
147
+ ...scoped(scope, input.signal), method: 'POST', body: { quote_id: input.renewal.quote.id, renew_consent: true, credit_approval: input.creditApproval === true }
148
+ }));
149
+ if (row.id !== id || row.quote.id !== input.renewal.quote.id)
150
+ throw unconfirmed('tool renewal');
151
+ return row;
152
+ }
153
+ /** Close an unused review after the human's approval; an applied extension is returned unchanged (never undone). */
154
+ export async function cancelRenewal(scope, input) {
155
+ const batchId = renewalId(input.batchId, 'tool session id');
156
+ const id = renewalId(input.renewalId, 'renewal id');
157
+ const row = renewalRow(scope, batchId)(await api(`${base(scope, batchId)}/${id}`, {
158
+ ...scoped(scope, input.signal), method: 'DELETE', body: { cancel_review_consent: true }
159
+ }));
160
+ if (row.id !== id || row.state === 'reviewed')
161
+ throw unconfirmed('tool renewal');
162
+ return row;
163
+ }
164
+ /**
165
+ * Withdraw an original review request whose answer was lost, after the human's
166
+ * approval. Root either fences the key for good (`withdrawn`) or returns the
167
+ * review it already holds (`committed`); nothing is undone.
168
+ */
169
+ export async function withdrawRenewal(scope, input) {
170
+ const batchId = renewalId(input.batchId, 'tool session id');
171
+ const requestKey = renewalId(input.requestKey, 'request key');
172
+ const raw = await api(`${base(scope, batchId)}/requests/${requestKey}`, { ...scoped(scope, input.signal), method: 'DELETE', body: { withdraw_consent: true } });
173
+ if (!object(raw) || keys(raw) !== 'receipt,state')
174
+ throw unconfirmed('renewal withdrawal');
175
+ if (raw.state === 'withdrawn' && raw.receipt === null)
176
+ return { state: 'withdrawn', receipt: null };
177
+ if (raw.state !== 'committed')
178
+ throw unconfirmed('renewal withdrawal');
179
+ const receipt = renewalRow(scope, batchId)(raw.receipt);
180
+ if (receipt.request_key !== requestKey)
181
+ throw unconfirmed('renewal withdrawal');
182
+ return { state: 'committed', receipt };
183
+ }
@@ -0,0 +1,193 @@
1
+ /**
2
+ * State operations on one shared-pool tool instance (Plan20, root
3
+ * `native_standalone_state*`): save a checkpoint of its data, or change its data
4
+ * through one of its own declared operations. Each request carries a key the
5
+ * caller keeps (sent as `Idempotency-Key`): sending it again returns the same
6
+ * original and never runs it twice. Root runs the operation asynchronously, so
7
+ * an accepted request is followed by reading it back.
8
+ *
9
+ * When an answer is lost, the original is read back by its key. A 404 there is
10
+ * not proof the request never arrived: it stays unknown, and the caller either
11
+ * replays the same key or withdraws it explicitly. Withdrawal is only ever the
12
+ * human's explicit choice; it blocks a late arrival and never undoes an effect.
13
+ *
14
+ * Starting from a snapshot (reset or fork) is a new reviewed tool session made
15
+ * elsewhere; this module can only read back or withdraw such a request by key.
16
+ */
17
+ import { api } from '../auth.js';
18
+ import { CliError } from '../http.js';
19
+ import { standaloneBatchRow } from './standalone-tools.js';
20
+ export const STATE_ACTIONS = ['checkpoint', 'edit'];
21
+ /** States root may still change; `unknown` is waiting for root to confirm an outcome. */
22
+ export const STATE_OPEN = new Set(['pending', 'effect_pending', 'unknown']);
23
+ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
24
+ const STATE = /^[a-z][a-z_]{0,23}$/;
25
+ const RESPONSE_LIMIT = 1024 * 1024;
26
+ const RESULT_KEYS = new Set(['snapshot_id', 'before_revision', 'after_revision', 'status', 'outcome']);
27
+ const object = (value) => value !== null && typeof value === 'object' && !Array.isArray(value);
28
+ const uuid = (value) => typeof value === 'string' && UUID.test(value);
29
+ const count = (value) => typeof value === 'number' && Number.isSafeInteger(value) && value >= 0;
30
+ const time = (value) => typeof value === 'string' && Number.isFinite(Date.parse(value));
31
+ const unconfirmed = (what) => new CliError('native_response_unconfirmed', `The ${what} response could not be confirmed. Nothing further was sent for it.`);
32
+ const scoped = (scope, signal) => ({ actor: scope.owner, workspace: scope.workspace, signal, maximumResponseBytes: RESPONSE_LIMIT });
33
+ const instanceBase = (scope, instanceId) => `/api/workspaces/${scope.workspace}/tools/native/instances/${instanceId}`;
34
+ export function stateId(value, label) {
35
+ if (!uuid(value))
36
+ throw new CliError('arguments_invalid', `Use the ${label} exactly as Firedrill printed it. Nothing was sent.`, 2);
37
+ return value.toLowerCase();
38
+ }
39
+ function resultRow(value) {
40
+ if (value === null)
41
+ return null;
42
+ if (!object(value) || !Object.keys(value).every((key) => RESULT_KEYS.has(key))
43
+ || (value.snapshot_id !== undefined && !uuid(value.snapshot_id))
44
+ || (value.before_revision !== undefined && !count(value.before_revision))
45
+ || (value.after_revision !== undefined && !count(value.after_revision))
46
+ || (value.status !== undefined && value.status !== null && !(count(value.status) && value.status >= 100 && value.status <= 599))
47
+ || (value.outcome !== undefined && !(typeof value.outcome === 'string' && STATE.test(value.outcome))))
48
+ throw unconfirmed('tool state operation');
49
+ return { ...value, ...(value.snapshot_id ? { snapshot_id: value.snapshot_id.toLowerCase() } : {}) };
50
+ }
51
+ function operationRow(instanceId, expectedKey) {
52
+ return (value) => {
53
+ if (!object(value) || !uuid(value.id) || value.instance_id !== instanceId || !uuid(value.request_key)
54
+ || (expectedKey !== undefined && value.request_key.toLowerCase() !== expectedKey)
55
+ || !STATE_ACTIONS.includes(value.action)
56
+ || (value.action === 'checkpoint' ? value.operation !== null : !(typeof value.operation === 'string' && value.operation.length >= 1 && value.operation.length <= 200))
57
+ || typeof value.state !== 'string' || !STATE.test(value.state) || !time(value.created_at)
58
+ || (value.finished_at !== null && !time(value.finished_at))
59
+ // Root shows a result only for a completed operation; a checkpoint's result names its snapshot.
60
+ || ((value.state === 'completed') !== (value.result !== null)))
61
+ throw unconfirmed('tool state operation');
62
+ const result = resultRow(value.result);
63
+ if (result && value.action === 'checkpoint' && !result.snapshot_id)
64
+ throw unconfirmed('tool state operation');
65
+ return { ...value, id: value.id.toLowerCase(), request_key: value.request_key.toLowerCase(), result };
66
+ };
67
+ }
68
+ function snapshotRow(instanceId) {
69
+ return (value) => {
70
+ if (!object(value) || !uuid(value.id) || value.instance_id !== instanceId || typeof value.kind !== 'string' || !STATE.test(value.kind)
71
+ || (value.operation_id !== null && !uuid(value.operation_id)) || !time(value.created_at) || typeof value.available !== 'boolean') {
72
+ throw unconfirmed('tool snapshot');
73
+ }
74
+ return { ...value, id: value.id.toLowerCase(),
75
+ operation_id: value.operation_id === null ? null : value.operation_id.toLowerCase() };
76
+ };
77
+ }
78
+ function page(raw, row, what) {
79
+ if (!object(raw) || !count(raw.count) || !count(raw.page) || raw.page < 1 || raw.page_size !== 25
80
+ || !Array.isArray(raw.results) || raw.results.length > 25 || raw.results.length > raw.count
81
+ || (raw.next !== null && typeof raw.next !== 'string') || (raw.previous !== null && typeof raw.previous !== 'string'))
82
+ throw unconfirmed(what);
83
+ return { count: raw.count, page: raw.page, next: raw.next,
84
+ previous: raw.previous, results: raw.results.map(row) };
85
+ }
86
+ function query(pageNumber) {
87
+ return new URLSearchParams({ page_size: '25', ...(pageNumber === undefined ? {} : { page: String(pageNumber) }) }).toString();
88
+ }
89
+ /**
90
+ * Send one state request under the caller's key. Root answers the original
91
+ * (new or replayed); it must be this key's request for the same action and operation.
92
+ */
93
+ export async function requestStateOperation(scope, input) {
94
+ const instanceId = stateId(input.instanceId, 'tool instance id');
95
+ const requestKey = stateId(input.requestKey, 'request key');
96
+ const row = operationRow(instanceId, requestKey)(await api(`${instanceBase(scope, instanceId)}/state-operations`, { ...scoped(scope, input.signal), method: 'POST', idempotencyKey: requestKey, body: input.request }));
97
+ if (!stateMatches(row, input.request))
98
+ throw unconfirmed('tool state operation');
99
+ return row;
100
+ }
101
+ /** The original is this request: same action, and for an edit the same operation. */
102
+ export function stateMatches(row, request) {
103
+ return row.action === request.action && row.operation === (request.action === 'edit' ? request.operation : null);
104
+ }
105
+ /** Newest first, 25 per page. */
106
+ export async function listStateOperations(scope, input) {
107
+ const instanceId = stateId(input.instanceId, 'tool instance id');
108
+ return page(await api(`${instanceBase(scope, instanceId)}/state-operations?${query(input.page)}`, scoped(scope, input.signal)), operationRow(instanceId), 'tool state operations');
109
+ }
110
+ export async function getStateOperation(scope, input) {
111
+ const instanceId = stateId(input.instanceId, 'tool instance id');
112
+ const operationId = stateId(input.operationId, 'state operation id');
113
+ const row = operationRow(instanceId)(await api(`${instanceBase(scope, instanceId)}/state-operations/${operationId}`, scoped(scope, input.signal)));
114
+ if (row.id !== operationId)
115
+ throw unconfirmed('tool state operation');
116
+ return row;
117
+ }
118
+ /** This actor's original state request by key, or null when root has none (which is not proof it never arrives). */
119
+ export async function recoverStateOperation(scope, input) {
120
+ const instanceId = stateId(input.instanceId, 'tool instance id');
121
+ const requestKey = stateId(input.requestKey, 'request key');
122
+ try {
123
+ return operationRow(instanceId, requestKey)(await api(`${instanceBase(scope, instanceId)}/state-operations/requests/${requestKey}`, scoped(scope, input.signal)));
124
+ }
125
+ catch (error) {
126
+ if (error instanceof CliError && error.httpStatus === 404)
127
+ return null;
128
+ throw error;
129
+ }
130
+ }
131
+ /** Snapshots of the instance's data (its baseline and each checkpoint), newest first. */
132
+ export async function listStateSnapshots(scope, input) {
133
+ const instanceId = stateId(input.instanceId, 'tool instance id');
134
+ return page(await api(`${instanceBase(scope, instanceId)}/snapshots?${query(input.page)}${input.kind ? `&kind=${input.kind}` : ''}`, scoped(scope, input.signal)), snapshotRow(instanceId), 'tool snapshots');
135
+ }
136
+ export const CLONE_ACTIONS = ['reset', 'fork'];
137
+ /**
138
+ * Ask root for a reset (from the instance's baseline snapshot) or a fork (from
139
+ * any of its snapshots) under the caller's key. Root reviews it as a new
140
+ * one-tool session (`reviewed`): nothing is reserved or started until it is
141
+ * quoted and admitted. The same key and body replay the original review.
142
+ */
143
+ export async function requestClone(scope, input) {
144
+ const instanceId = stateId(input.instanceId, 'tool instance id');
145
+ const requestKey = stateId(input.requestKey, 'request key');
146
+ const snapshotId = stateId(input.snapshotId, 'snapshot id');
147
+ if (!CLONE_ACTIONS.includes(input.action))
148
+ throw new CliError('arguments_invalid', 'Use reset or fork. Nothing was sent.', 2);
149
+ const batch = standaloneBatchRow(scope)(await api(`${instanceBase(scope, instanceId)}/clones`, { ...scoped(scope, input.signal),
150
+ method: 'POST', idempotencyKey: requestKey, body: { snapshot_id: snapshotId, duration_seconds: input.durationSeconds, action: input.action } }));
151
+ if (batch.request_key !== requestKey || batch.instances.length !== 1 || batch.duration_seconds !== input.durationSeconds
152
+ || batch.clone_source?.source_instance_id !== instanceId || batch.clone_source.source_snapshot_id !== snapshotId
153
+ || batch.clone_source.action !== input.action)
154
+ throw unconfirmed('tool session');
155
+ return batch;
156
+ }
157
+ /** This actor's original reset or fork request by key (a reviewed tool session), or null when root has none yet. */
158
+ export async function recoverCloneRequest(scope, input) {
159
+ const instanceId = stateId(input.instanceId, 'tool instance id');
160
+ const requestKey = stateId(input.requestKey, 'request key');
161
+ try {
162
+ const batch = standaloneBatchRow(scope)(await api(`${instanceBase(scope, instanceId)}/clones/requests/${requestKey}`, scoped(scope, input.signal)));
163
+ if (batch.request_key !== requestKey || batch.clone_source?.source_instance_id !== instanceId)
164
+ throw unconfirmed('tool session');
165
+ return batch;
166
+ }
167
+ catch (error) {
168
+ if (error instanceof CliError && error.httpStatus === 404)
169
+ return null;
170
+ throw error;
171
+ }
172
+ }
173
+ /**
174
+ * Withdraw an original request after the human's explicit approval. Root
175
+ * either blocks it for good (`withdrawn`) or returns the original it already
176
+ * accepted (`committed`); nothing that already happened is undone.
177
+ */
178
+ export async function withdrawStateRequest(scope, input) {
179
+ const instanceId = stateId(input.instanceId, 'tool instance id');
180
+ const requestKey = stateId(input.requestKey, 'request key');
181
+ const path = input.kind === 'state' ? 'state-operations' : 'clones';
182
+ const raw = await api(`${instanceBase(scope, instanceId)}/${path}/requests/${requestKey}`, { ...scoped(scope, input.signal), method: 'DELETE', body: { withdraw_consent: true } });
183
+ if (!object(raw) || Object.keys(raw).sort().join(',') !== 'receipt,state')
184
+ throw unconfirmed('withdrawal');
185
+ if (raw.state === 'withdrawn' && raw.receipt === null)
186
+ return { state: 'withdrawn', receipt: null };
187
+ if (raw.state !== 'committed')
188
+ throw unconfirmed('withdrawal');
189
+ const receipt = input.kind === 'state' ? operationRow(instanceId, requestKey)(raw.receipt) : standaloneBatchRow(scope)(raw.receipt);
190
+ if (receipt.request_key !== requestKey)
191
+ throw unconfirmed('withdrawal');
192
+ return { state: 'committed', receipt };
193
+ }
@@ -0,0 +1,187 @@
1
+ /**
2
+ * Standalone tool batches on the shared reviewed tool pool (Plan20, root
3
+ * `native_standalone_*`). New tool sessions start through mixed setups
4
+ * (`tool-setups.ts`), whose pooled children the shared pool runs as one batch;
5
+ * sessions started before mixed setups are batches too. What stays here is
6
+ * what both still need: reading and stopping a batch, reading an instance, and
7
+ * the terminal credential that `tools access` uses to serve a pooled instance.
8
+ *
9
+ * Terminal access (`/instances/{id}/credentials`) returns a token that lives
10
+ * for at most 30 seconds and belongs only in this process's memory: it is
11
+ * wrapped in a SecretValue at once and never printed, logged or persisted.
12
+ */
13
+ import { createHash } from 'node:crypto';
14
+ import { api } from '../auth.js';
15
+ import { creditApprovalPolicy } from './credit-approval.js';
16
+ import { CliError } from '../http.js';
17
+ import { SecretValue } from './contracts.js';
18
+ /** Root limits. */
19
+ const STANDALONE_MAX_SELECTIONS = 32;
20
+ const STANDALONE_MAX_OPERATIONS = 256;
21
+ const STANDALONE_MIN_SECONDS = 60;
22
+ const STANDALONE_MAX_SECONDS = 31_536_000;
23
+ export const STANDALONE_PROTOCOL = 'firedrill.logical-operations.v1';
24
+ export const STANDALONE_MCP_VERSION = '2025-06-18';
25
+ /** Root's batch states; a state outside these is shown as given. */
26
+ export const STANDALONE_ACTIVE = 'active';
27
+ export const STANDALONE_TERMINAL = new Set(['stopped', 'cancelled', 'rejected', 'failed']);
28
+ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
29
+ const SHA256 = /^[0-9a-f]{64}$/;
30
+ const STATE = /^[a-z][a-z_]{0,23}$/;
31
+ const RESPONSE_LIMIT = 4 * 1024 * 1024;
32
+ const object = (value) => value !== null && typeof value === 'object' && !Array.isArray(value);
33
+ const uuid = (value) => typeof value === 'string' && UUID.test(value);
34
+ const count = (value, min = 0) => typeof value === 'number' && Number.isSafeInteger(value) && value >= min;
35
+ const time = (value) => typeof value === 'string' && Number.isFinite(Date.parse(value));
36
+ const nullableTime = (value) => value === null || time(value);
37
+ const text = (value, max) => typeof value === 'string' && value.length >= 1 && value.length <= max;
38
+ const names = (value) => Array.isArray(value) && value.length >= 1 && value.length <= STANDALONE_MAX_OPERATIONS
39
+ && value.every((one) => text(one, 200)) && new Set(value).size === value.length;
40
+ const unconfirmed = (what) => new CliError('native_response_unconfirmed', `The ${what} response could not be confirmed. Nothing further was sent for it.`);
41
+ const scoped = (scope, signal) => ({ actor: scope.owner, workspace: scope.workspace, signal, maximumResponseBytes: RESPONSE_LIMIT });
42
+ const base = (scope) => `/api/workspaces/${scope.workspace}/tools/native`;
43
+ function id(value, label) {
44
+ if (!uuid(value))
45
+ throw new CliError('arguments_invalid', `Use the ${label} id exactly as Firedrill printed it. Nothing was sent.`, 2);
46
+ return value.toLowerCase();
47
+ }
48
+ function instanceRow(batchId) {
49
+ return (value) => {
50
+ if (!object(value) || !uuid(value.id) || !uuid(value.batch_id) || (batchId !== null && value.batch_id.toLowerCase() !== batchId)
51
+ || !count(value.position) || value.position >= STANDALONE_MAX_SELECTIONS || !uuid(value.workspace_id) || !uuid(value.implementation_id)
52
+ || !text(value.name, 200) || !text(value.family, 128) || !text(value.version, 128) || !text(value.service_key, 200)
53
+ || !names(value.operations) || typeof value.has_preview !== 'boolean' || !count(value.generation, 1)
54
+ || typeof value.state !== 'string' || !STATE.test(value.state) || !time(value.created_at)
55
+ || !nullableTime(value.expires_at) || !nullableTime(value.terminal_at))
56
+ throw unconfirmed('tool instance');
57
+ return { ...value, id: value.id.toLowerCase(), batch_id: value.batch_id.toLowerCase(),
58
+ implementation_id: value.implementation_id.toLowerCase(), operations: [...value.operations] };
59
+ };
60
+ }
61
+ function summaryFields(value) {
62
+ return uuid(value.id) && uuid(value.workspace_id) && uuid(value.actor_id) && uuid(value.request_key)
63
+ && typeof value.original_digest === 'string' && SHA256.test(value.original_digest) && typeof value.state === 'string' && STATE.test(value.state)
64
+ && count(value.duration_seconds, STANDALONE_MIN_SECONDS) && value.duration_seconds <= STANDALONE_MAX_SECONDS && time(value.created_at)
65
+ && nullableTime(value.expires_at) && nullableTime(value.stop_requested_at);
66
+ }
67
+ function batchRow(scope, expected) {
68
+ return (value) => {
69
+ if (!object(value) || !summaryFields(value) || !Array.isArray(value.instances) || value.instances.length < 1
70
+ || value.instances.length > STANDALONE_MAX_SELECTIONS)
71
+ throw unconfirmed('tool session');
72
+ const batchId = value.id.toLowerCase();
73
+ const instances = value.instances.map(instanceRow(batchId));
74
+ const source = value.clone_source;
75
+ if (source !== null && (!object(source) || Object.keys(source).sort().join(',') !== 'action,source_instance_id,source_snapshot_id'
76
+ || (source.action !== 'reset' && source.action !== 'fork') || !uuid(source.source_instance_id) || !uuid(source.source_snapshot_id))) {
77
+ throw unconfirmed('tool session');
78
+ }
79
+ // The session is this workspace's, the one asked for, and its instances are in position order.
80
+ if (value.workspace_id.toLowerCase() !== scope.workspace.toLowerCase()
81
+ || (expected.batchId !== undefined && batchId !== expected.batchId)
82
+ || !instances.every((one, index) => one.position === index))
83
+ throw unconfirmed('tool session');
84
+ return { ...value, id: batchId, request_key: value.request_key.toLowerCase(), instances };
85
+ };
86
+ }
87
+ /** A batch of this workspace as root returns it (a tool session, or a reviewed reset or fork of one instance). */
88
+ export const standaloneBatchRow = (scope) => batchRow(scope, {});
89
+ const QUOTE_COMPONENTS = ['baseline', 'runtime'];
90
+ /**
91
+ * Quote a reviewed batch under the caller's quote key. Quoting reserves
92
+ * nothing; the same key replays the same two quotes (`replayed`), so a later
93
+ * run admits exactly the estimate the human saw.
94
+ */
95
+ export async function quoteStandaloneBatch(scope, input) {
96
+ const batchId = id(input.batch.id, 'tool session');
97
+ const quoteKey = id(input.quoteKey, 'quote key');
98
+ const raw = await api(`${base(scope)}/batches/${batchId}/quotes`, { ...scoped(scope, input.signal), method: 'POST', idempotencyKey: quoteKey, body: {} });
99
+ if (!object(raw) || Object.keys(raw).sort().join(',') !== 'batch_digest,batch_id,bundle_digest,quotes,replayed'
100
+ || raw.batch_id !== batchId || raw.batch_digest !== input.batch.original_digest || typeof raw.replayed !== 'boolean'
101
+ || typeof raw.bundle_digest !== 'string' || !SHA256.test(raw.bundle_digest) || !Array.isArray(raw.quotes) || raw.quotes.length !== 2) {
102
+ throw unconfirmed('tool estimate');
103
+ }
104
+ const quotes = raw.quotes.map((value, index) => {
105
+ const frozen = object(value) ? value.scope : null;
106
+ // Each quote is for this batch's reviewed original, in root's fixed order, and never charges local compute.
107
+ if (!object(value) || !uuid(value.id) || value.workspace_id !== scope.workspace.toLowerCase() || value.execution_location !== 'managed'
108
+ || !text(value.activity, 64) || !text(value.denomination, 64) || !count(value.credit_scale, 1)
109
+ || !count(value.estimated_units) || !count(value.maximum_units) || value.estimated_units > value.maximum_units || !time(value.expires_at)
110
+ || !object(frozen) || frozen.schema !== 'firedrill.native-standalone-funding.v1' || frozen.batch_id !== batchId
111
+ || frozen.batch_digest !== input.batch.original_digest || frozen.component !== QUOTE_COMPONENTS[index]
112
+ || frozen.local_target_compute_charged !== false)
113
+ throw unconfirmed('tool estimate');
114
+ return { ...creditApprovalPolicy(value), id: value.id.toLowerCase(), component: index === 0 ? 'baseline' : 'runtime', activity: value.activity, denomination: value.denomination,
115
+ credit_scale: value.credit_scale, estimated_units: value.estimated_units, maximum_units: value.maximum_units, expires_at: value.expires_at };
116
+ });
117
+ // Root's bundle digest is over the two quote ids in order; it is what admission is bound to.
118
+ const ids = quotes.map((one) => one.id);
119
+ if (createHash('sha256').update(JSON.stringify(ids)).digest('hex') !== raw.bundle_digest)
120
+ throw unconfirmed('tool estimate');
121
+ return { batch_id: batchId, batch_digest: raw.batch_digest, replayed: raw.replayed, bundle_digest: raw.bundle_digest, quote_key: quoteKey,
122
+ quotes: [quotes[0], quotes[1]] };
123
+ }
124
+ /**
125
+ * Admit a reviewed batch with exactly this quote bundle, after the human
126
+ * approved it. Root reserves the quoted credits and queues the batch on the
127
+ * shared pool (202). The same bundle replays the admitted batch; any other
128
+ * bundle for an admitted batch is refused, so nothing is admitted twice.
129
+ */
130
+ export async function admitStandaloneBatch(scope, input) {
131
+ const batchId = id(input.quotes.batch_id, 'tool session');
132
+ const batch = batchRow(scope, { batchId })(await api(`${base(scope)}/batches/${batchId}/admit`, { ...scoped(scope, input.signal), method: 'POST',
133
+ body: { quote_ids: input.quotes.quotes.map((one) => one.id), bundle_digest: input.quotes.bundle_digest, credit_approval: input.creditApproval === true } }));
134
+ if (batch.original_digest !== input.quotes.batch_digest)
135
+ throw unconfirmed('tool admission');
136
+ return batch;
137
+ }
138
+ export async function getStandaloneBatch(scope, input) {
139
+ const batchId = id(input.batchId, 'tool session');
140
+ return batchRow(scope, { batchId })(await api(`${base(scope)}/batches/${batchId}`, scoped(scope, input.signal)));
141
+ }
142
+ /** Ask root to stop a batch (idempotent). Acceptance is not proof that storage or processes are gone. */
143
+ export async function stopStandaloneBatch(scope, input) {
144
+ const batchId = id(input.batchId, 'tool session');
145
+ return batchRow(scope, { batchId })(await api(`${base(scope)}/batches/${batchId}`, { ...scoped(scope, input.signal), method: 'DELETE', body: { stop_consent: true } }));
146
+ }
147
+ export async function getStandaloneInstance(scope, input) {
148
+ const instanceId = id(input.instanceId, 'tool instance');
149
+ const row = instanceRow(null)(await api(`${base(scope)}/instances/${instanceId}`, scoped(scope, input.signal)));
150
+ if (row.id !== instanceId || row.workspace_id.toLowerCase() !== scope.workspace.toLowerCase())
151
+ throw unconfirmed('tool instance');
152
+ return row;
153
+ }
154
+ /**
155
+ * A short-lived terminal credential for one active instance. Only a signed-in
156
+ * terminal of the selected workspace may ask. The token is wrapped at once and
157
+ * must stay in this process's memory: never output, logs, files or argv.
158
+ */
159
+ export async function standaloneCredential(scope, input) {
160
+ const instance = input.instance;
161
+ const raw = await api(`${base(scope)}/instances/${instance.id}/credentials`, { ...scoped(scope, input.signal), method: 'POST', body: { generation: instance.generation, mode: input.mode } });
162
+ if (!object(raw) || typeof raw.access_token !== 'string' || raw.access_token.length < 1 || raw.access_token.length > 16_384
163
+ || raw.token_type !== 'Bearer' || raw.instance_id !== instance.id || raw.generation !== instance.generation
164
+ || raw.service_key !== instance.service_key || !count(raw.expires_at, 1) || raw.protocol !== STANDALONE_PROTOCOL
165
+ || raw.mcp_protocol_version !== STANDALONE_MCP_VERSION || typeof raw.invoke_url !== 'string' || typeof raw.mcp_url !== 'string') {
166
+ throw unconfirmed('tool access');
167
+ }
168
+ // Both endpoints live on one isolated https origin, at exactly the paths root defines.
169
+ let origin;
170
+ try {
171
+ const invoke = new URL(raw.invoke_url);
172
+ origin = invoke.origin;
173
+ if (invoke.protocol !== 'https:' || invoke.username || invoke.password || invoke.search || invoke.hash
174
+ || raw.invoke_url !== `${origin}/v1/invoke` || raw.mcp_url !== `${origin}/mcp/${pathSegment(instance.service_key)}`)
175
+ throw new Error();
176
+ }
177
+ catch {
178
+ throw unconfirmed('tool access');
179
+ }
180
+ return { instance_id: instance.id, generation: instance.generation, service_key: instance.service_key,
181
+ token: new SecretValue(raw.access_token, `tool-access:${instance.id}`), expires_at: raw.expires_at,
182
+ invoke_url: raw.invoke_url, mcp_url: raw.mcp_url };
183
+ }
184
+ /** Python `quote(value, safe="")`, as root builds the MCP path: only unreserved characters stay literal. */
185
+ function pathSegment(value) {
186
+ return encodeURIComponent(value).replace(/[!'()*]/g, (char) => `%${char.charCodeAt(0).toString(16).toUpperCase()}`);
187
+ }