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,336 @@
1
+ /**
2
+ * Mixed tool setups (root `native_tool_setup*`): the one review, quote and
3
+ * admission flow the web app, the remote MCP and this CLI share for starting
4
+ * tools outside any drill.
5
+ *
6
+ * Each selected service runs in one of two lanes, decided by root:
7
+ * - `pooled`: a reviewed catalog service on the shared tool pool (starting data
8
+ * is a seed; `dataset_id` must be null);
9
+ * - `isolated`: the whole package in its own isolated runtime, which is how an
10
+ * uploaded custom package runs (starting data is a saved dataset; `seed_id`
11
+ * must be null). One package appears at most once per setup.
12
+ *
13
+ * Flow: review (Idempotency-Key = request key; recovered at `requests/{key}`)
14
+ * -> quotes (Idempotency-Key = quote key; root keeps exactly one estimate per
15
+ * setup, `quote_request_key`, and replays it for that key only) -> admission of
16
+ * exactly that bundle digest with `start_consent`, which reserves credits and
17
+ * starts each child independently. Stop takes `stop_consent`. Older standalone
18
+ * batches stay readable through `standalone-tools.ts` for history.
19
+ */
20
+ import { api } from '../auth.js';
21
+ import { creditApprovalPolicy } from './credit-approval.js';
22
+ import { CliError } from '../http.js';
23
+ export const SETUP_MAX_SELECTIONS = 32;
24
+ export const SETUP_MAX_OPERATIONS = 256;
25
+ export const SETUP_MIN_SECONDS = 60;
26
+ export const SETUP_MAX_SECONDS = 31_536_000;
27
+ /** List categories root accepts. */
28
+ export const SETUP_CATEGORIES = ['all', 'active', 'preparing', 'closed'];
29
+ /** Summary states once no child is unfinished. */
30
+ export const SETUP_ENDED = new Set(['stopped', 'closed']);
31
+ /** A child instance that is running (pooled and isolated instances both say `active`). */
32
+ export const SETUP_RUNNING = 'active';
33
+ /** Child instance states after which it never runs again. */
34
+ export const SETUP_CHILD_ENDED = new Set(['stopped', 'cancelled', 'canceled', 'rejected', 'failed', 'expired']);
35
+ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
36
+ const SHA256 = /^[0-9a-f]{64}$/;
37
+ const STATE = /^[a-z][a-z_]{0,23}$/;
38
+ const RESPONSE_LIMIT = 4 * 1024 * 1024;
39
+ const LANES = new Set(['pooled', 'isolated']);
40
+ const object = (value) => value !== null && typeof value === 'object' && !Array.isArray(value);
41
+ const uuid = (value) => typeof value === 'string' && UUID.test(value);
42
+ const count = (value, min = 0) => typeof value === 'number' && Number.isSafeInteger(value) && value >= min;
43
+ const time = (value) => typeof value === 'string' && Number.isFinite(Date.parse(value));
44
+ const nullableTime = (value) => value === null || time(value);
45
+ const text = (value, max) => typeof value === 'string' && value.length >= 1 && value.length <= max;
46
+ const state = (value) => typeof value === 'string' && STATE.test(value);
47
+ const names = (value) => Array.isArray(value) && value.length >= 1 && value.length <= SETUP_MAX_OPERATIONS
48
+ && value.every((one) => text(one, 200)) && new Set(value).size === value.length;
49
+ const lower = (value) => value.toLowerCase();
50
+ const unconfirmed = (what) => new CliError('native_response_unconfirmed', `The ${what} response could not be confirmed. Nothing further was sent for it.`);
51
+ const scoped = (scope, signal) => ({ actor: scope.owner, workspace: scope.workspace, signal, maximumResponseBytes: RESPONSE_LIMIT });
52
+ const base = (scope) => `/api/workspaces/${scope.workspace}/tools/native`;
53
+ const sameWorkspace = (value, scope) => lower(value) === lower(scope.workspace);
54
+ function id(value, label) {
55
+ if (!uuid(value))
56
+ throw new CliError('arguments_invalid', `Use the ${label} id exactly as Firedrill printed it. Nothing was sent.`, 2);
57
+ return lower(value);
58
+ }
59
+ function query(values) {
60
+ const params = new URLSearchParams();
61
+ for (const [key, value] of Object.entries(values))
62
+ if (value !== undefined)
63
+ params.set(key, String(value));
64
+ return params.toString();
65
+ }
66
+ function pageNumber(value) {
67
+ const number = value ?? 1;
68
+ if (!Number.isSafeInteger(number) || number < 1)
69
+ throw new CliError('native_page_invalid', 'Use a page number of 1 or more.', 2);
70
+ return number;
71
+ }
72
+ function search(value) {
73
+ const trimmed = value?.trim();
74
+ if (trimmed !== undefined && (trimmed.length > 200 || /[\u0000-\u001f\u007f]/.test(trimmed))) {
75
+ throw new CliError('arguments_invalid', 'Use a search of up to 200 printable characters.', 2);
76
+ }
77
+ return trimmed || undefined;
78
+ }
79
+ function page(raw, row, what) {
80
+ if (!object(raw) || !count(raw.count) || !Array.isArray(raw.results))
81
+ throw unconfirmed(what);
82
+ return { count: raw.count, page: typeof raw.page === 'number' ? raw.page : null,
83
+ next: typeof raw.next === 'string' ? raw.next : null, previous: typeof raw.previous === 'string' ? raw.previous : null,
84
+ results: raw.results.map(row) };
85
+ }
86
+ function lane(value) {
87
+ // Root derives scope and starting data from the lane; anything else is not this contract.
88
+ return LANES.has(value.lane) && value.execution_scope === (value.lane === 'pooled' ? 'selected_service' : 'isolated_package')
89
+ && value.starting_data === (value.lane === 'pooled' ? 'seed' : 'dataset') && typeof value.has_preview === 'boolean';
90
+ }
91
+ function optionService(value) {
92
+ if (!object(value) || !text(value.service_key, 200) || !lane(value) || !count(value.operation_count, 1)
93
+ || value.operation_count > SETUP_MAX_OPERATIONS)
94
+ throw unconfirmed('tool setup options');
95
+ return { service_key: value.service_key, lane: value.lane, has_preview: value.has_preview,
96
+ execution_scope: value.execution_scope, starting_data: value.starting_data,
97
+ operation_count: value.operation_count };
98
+ }
99
+ function optionRow(value) {
100
+ if (!object(value) || !uuid(value.implementation_id) || !text(value.name, 200) || !text(value.version, 128)
101
+ || !text(value.contract_digest, 128) || typeof value.available !== 'boolean' || !Array.isArray(value.services)
102
+ || value.services.length > SETUP_MAX_SELECTIONS || value.available !== value.services.length > 0)
103
+ throw unconfirmed('tool setup options');
104
+ const services = value.services.map(optionService);
105
+ if (new Set(services.map((one) => one.service_key)).size !== services.length)
106
+ throw unconfirmed('tool setup options');
107
+ return { implementation_id: lower(value.implementation_id), name: value.name, version: value.version,
108
+ contract_digest: value.contract_digest, available: value.available, services };
109
+ }
110
+ function summaryRow(scope, expected = {}) {
111
+ return (value) => {
112
+ if (!object(value) || !uuid(value.id) || !uuid(value.workspace_id) || !uuid(value.actor_id) || !uuid(value.request_key)
113
+ || typeof value.original_digest !== 'string' || !SHA256.test(value.original_digest) || !state(value.state)
114
+ || !count(value.item_count, 1) || value.item_count > SETUP_MAX_SELECTIONS
115
+ || !count(value.duration_seconds, SETUP_MIN_SECONDS) || value.duration_seconds > SETUP_MAX_SECONDS
116
+ || !time(value.created_at) || !nullableTime(value.approved_at) || !nullableTime(value.stop_requested_at)
117
+ || !(value.bundle_digest === null || (typeof value.bundle_digest === 'string' && SHA256.test(value.bundle_digest)))
118
+ || !(value.quote_request_key === null || uuid(value.quote_request_key))
119
+ // An estimate exists exactly when a quote key was used; an approval needs one.
120
+ || (value.bundle_digest === null) !== (value.quote_request_key === null)
121
+ || (value.approved_at !== null && value.bundle_digest === null))
122
+ throw unconfirmed('tool session');
123
+ const row = {
124
+ id: lower(value.id), workspace_id: lower(value.workspace_id), actor_id: lower(value.actor_id), request_key: lower(value.request_key),
125
+ original_digest: value.original_digest, state: value.state, item_count: value.item_count, duration_seconds: value.duration_seconds,
126
+ created_at: value.created_at, approved_at: value.approved_at, stop_requested_at: value.stop_requested_at,
127
+ bundle_digest: value.bundle_digest, quote_request_key: value.quote_request_key === null ? null : lower(value.quote_request_key),
128
+ };
129
+ if (!sameWorkspace(row.workspace_id, scope) || (expected.setupId !== undefined && row.id !== expected.setupId)
130
+ || (expected.requestKey !== undefined && row.request_key !== expected.requestKey))
131
+ throw unconfirmed('tool session');
132
+ return row;
133
+ };
134
+ }
135
+ function itemRow(value) {
136
+ if (!object(value) || !count(value.position) || value.position >= SETUP_MAX_SELECTIONS || !LANES.has(value.lane)
137
+ || !uuid(value.implementation_id) || !text(value.name, 200) || !text(value.service_key, 200) || !names(value.operations)
138
+ || !state(value.admission_state) || !(value.failure_code === null || text(value.failure_code, 128))
139
+ || !(value.instance_id === null || uuid(value.instance_id)) || !(value.instance_state === null || state(value.instance_state))
140
+ || !(value.instance_path === null || text(value.instance_path, 512)) || !nullableTime(value.terminal_at)
141
+ || !(value.pooled_batch_id === null || uuid(value.pooled_batch_id)))
142
+ throw unconfirmed('tool session');
143
+ // A child has an instance, its state and path together; only a pooled child names its pool batch.
144
+ const instance = value.instance_id !== null;
145
+ if ((value.instance_state !== null) !== instance || (value.instance_path !== null) !== instance
146
+ || (value.lane === 'isolated' && value.pooled_batch_id !== null)
147
+ || (value.lane === 'pooled' && instance && value.pooled_batch_id === null))
148
+ throw unconfirmed('tool session');
149
+ return { position: value.position, lane: value.lane, implementation_id: lower(value.implementation_id), name: value.name,
150
+ service_key: value.service_key, operations: [...value.operations], admission_state: value.admission_state,
151
+ failure_code: value.failure_code, instance_id: instance ? lower(value.instance_id) : null,
152
+ instance_state: value.instance_state, instance_path: value.instance_path,
153
+ terminal_at: value.terminal_at, pooled_batch_id: value.pooled_batch_id === null ? null : lower(value.pooled_batch_id) };
154
+ }
155
+ function quoteRow(value) {
156
+ if (!object(value) || !LANES.has(value.lane) || !uuid(value.quote_id) || !text(value.activity, 64) || !text(value.denomination, 64)
157
+ || !count(value.credit_scale, 1) || !count(value.estimated_units) || !count(value.maximum_units)
158
+ || value.estimated_units > value.maximum_units || !time(value.expires_at))
159
+ throw unconfirmed('tool quote');
160
+ // The pool quotes preparation and running time for the whole pool lane; each isolated child has its own runtime quote.
161
+ if (value.lane === 'pooled' ? value.position !== null || (value.component !== 'baseline' && value.component !== 'runtime')
162
+ : !count(value.position) || value.position >= SETUP_MAX_SELECTIONS || value.component !== 'runtime')
163
+ throw unconfirmed('tool quote');
164
+ return { ...creditApprovalPolicy(value), position: value.position, lane: value.lane, component: value.component,
165
+ quote_id: lower(value.quote_id), activity: value.activity, denomination: value.denomination, credit_scale: value.credit_scale,
166
+ estimated_units: value.estimated_units, maximum_units: value.maximum_units, expires_at: value.expires_at };
167
+ }
168
+ /** Saved versions in the workspace with the lane each service would start in (newest name order, `page_size` up to 100). */
169
+ export async function listSetupOptions(scope, input) {
170
+ const size = input.pageSize ?? 32;
171
+ if (!Number.isSafeInteger(size) || size < 1 || size > 100)
172
+ throw new CliError('native_page_invalid', 'Use a page size from 1 to 100.', 2);
173
+ return page(await api(`${base(scope)}/setup-options?${query({ page: pageNumber(input.page), page_size: size, q: search(input.search) })}`, scoped(scope, input.signal)), optionRow, 'tool setup options');
174
+ }
175
+ /** One service of a saved version: its exact operation names and lane. */
176
+ export async function getSetupOption(scope, input) {
177
+ const implementationId = id(input.implementationId, 'tool version');
178
+ if (!text(input.serviceKey, 200))
179
+ throw new CliError('arguments_invalid', 'Use a service key from firedrill tools runnable. Nothing was sent.', 2);
180
+ const raw = await api(`${base(scope)}/setup-options/${implementationId}?${query({ service_key: input.serviceKey })}`, scoped(scope, input.signal));
181
+ if (!object(raw) || !uuid(raw.implementation_id) || lower(raw.implementation_id) !== implementationId
182
+ || raw.service_key !== input.serviceKey || !text(raw.contract_digest, 128) || !names(raw.operations) || !lane(raw))
183
+ throw unconfirmed('tool version');
184
+ return { implementation_id: implementationId, contract_digest: raw.contract_digest, service_key: raw.service_key, operations: [...raw.operations],
185
+ lane: raw.lane, has_preview: raw.has_preview, execution_scope: raw.execution_scope,
186
+ starting_data: raw.starting_data };
187
+ }
188
+ /**
189
+ * Review an exact selection (nothing is allocated or charged). The same request
190
+ * key with the same body replays the original; a lost answer is recovered with
191
+ * `recoverSetup`, never sent again under another key. The children are read
192
+ * back so the setup holds exactly what was asked, in order.
193
+ */
194
+ export async function reviewSetup(scope, input) {
195
+ const requestKey = id(input.requestKey, 'request');
196
+ checkSelections(input.durationSeconds, input.selections);
197
+ const body = { duration_seconds: input.durationSeconds, selections: input.selections.map((one) => ({
198
+ implementation_id: one.implementation_id, service_key: one.service_key, operations: [...one.operations],
199
+ seed_id: one.seed_id, dataset_id: one.dataset_id
200
+ })) };
201
+ const raw = await api(`${base(scope)}/setups`, { ...scoped(scope, input.signal), method: 'POST', idempotencyKey: requestKey, body });
202
+ const setup = summaryRow(scope, { requestKey })(raw);
203
+ if (setup.duration_seconds !== input.durationSeconds || setup.item_count !== input.selections.length)
204
+ throw unconfirmed('tool session');
205
+ const items = await setupItems(scope, { setup, signal: input.signal });
206
+ const asked = input.selections.map((one) => `${lower(one.implementation_id)}\0${one.service_key}\0${[...one.operations].sort().join('\0')}`);
207
+ const held = items.map((one) => `${one.implementation_id}\0${one.service_key}\0${[...one.operations].sort().join('\0')}`);
208
+ if (asked.join('\n') !== held.join('\n'))
209
+ throw unconfirmed('tool session');
210
+ return { setup, items };
211
+ }
212
+ /** The original review made under a request key by this user, or null when none was made. */
213
+ export async function recoverSetup(scope, input) {
214
+ const requestKey = id(input.requestKey, 'request');
215
+ try {
216
+ return summaryRow(scope, { requestKey })(await api(`${base(scope)}/setups/requests/${requestKey}`, scoped(scope, input.signal)));
217
+ }
218
+ catch (error) {
219
+ if (error instanceof CliError && error.httpStatus === 404)
220
+ return null;
221
+ throw error;
222
+ }
223
+ }
224
+ export async function getSetup(scope, input) {
225
+ const setupId = id(input.setupId, 'tool session');
226
+ return summaryRow(scope, { setupId })(await api(`${base(scope)}/setups/${setupId}`, scoped(scope, input.signal)));
227
+ }
228
+ /** Tool sessions in the workspace, newest first (25 per page). `preparing`: nothing running yet but not ended. */
229
+ export async function listSetups(scope, input) {
230
+ if (input.category !== undefined && !SETUP_CATEGORIES.includes(input.category)) {
231
+ throw new CliError('arguments_invalid', `Use --state ${SETUP_CATEGORIES.join('|')}.`, 2);
232
+ }
233
+ return page(await api(`${base(scope)}/setups?${query({ page: pageNumber(input.page), page_size: 25, category: input.category, q: search(input.search) })}`, scoped(scope, input.signal)), summaryRow(scope), 'tool sessions');
234
+ }
235
+ /** Every child of a setup in position order (at most 32, one page). */
236
+ export async function setupItems(scope, input) {
237
+ const raw = page(await api(`${base(scope)}/setups/${input.setup.id}/items?${query({ page: 1, page_size: SETUP_MAX_SELECTIONS })}`, scoped(scope, input.signal)), itemRow, 'tool session');
238
+ if (raw.count !== input.setup.item_count || raw.results.length !== input.setup.item_count
239
+ || !raw.results.every((one, index) => one.position === index))
240
+ throw unconfirmed('tool session');
241
+ // The shared pool runs all pooled children of one setup as one batch.
242
+ const batches = new Set(raw.results.flatMap((one) => one.pooled_batch_id ? [one.pooled_batch_id] : []));
243
+ if (batches.size > 1)
244
+ throw unconfirmed('tool session');
245
+ return raw.results;
246
+ }
247
+ /**
248
+ * The setup's one estimate. Quoting reserves nothing. Root keeps the first quote
249
+ * key it saw for a setup and replays that estimate for it; any other key is
250
+ * refused (`native_tool_setup_quote_original_required`), so a setup is never
251
+ * re-quoted. After approval or a stop, an unquoted setup cannot be quoted.
252
+ */
253
+ export async function quoteSetup(scope, input) {
254
+ const requestKey = id(input.requestKey, 'quote request');
255
+ const raw = await api(`${base(scope)}/setups/${input.setup.id}/quotes`, { ...scoped(scope, input.signal), method: 'POST', idempotencyKey: requestKey, body: {} });
256
+ if (!object(raw) || raw.setup_id !== input.setup.id || raw.setup_digest !== input.setup.original_digest
257
+ || typeof raw.bundle_digest !== 'string' || !SHA256.test(raw.bundle_digest) || !Array.isArray(raw.quotes))
258
+ throw unconfirmed('tool quote');
259
+ const quotes = raw.quotes.map(quoteRow);
260
+ // Exactly two pool quotes when any child is pooled, and one runtime quote per isolated child, in position order.
261
+ const pooled = quotes.filter((one) => one.lane === 'pooled');
262
+ const isolated = quotes.filter((one) => one.lane === 'isolated');
263
+ const isolatedPositions = input.items.filter((one) => one.lane === 'isolated').map((one) => one.position);
264
+ const anyPooled = input.items.some((one) => one.lane === 'pooled');
265
+ if ((anyPooled ? pooled.length !== 2 || pooled[0].component !== 'baseline' || pooled[1].component !== 'runtime' : pooled.length !== 0)
266
+ || isolated.map((one) => one.position).join(',') !== isolatedPositions.join(',')
267
+ || new Set(quotes.map((one) => one.quote_id)).size !== quotes.length || !quotes.length
268
+ || pooled.some((one) => one.denomination !== pooled[0].denomination || one.credit_scale !== pooled[0].credit_scale))
269
+ throw unconfirmed('tool quote');
270
+ return { setup_id: input.setup.id, setup_digest: input.setup.original_digest, bundle_digest: raw.bundle_digest, quote_key: requestKey, quotes };
271
+ }
272
+ /**
273
+ * Approve exactly this estimate: root reserves its credits and starts each child.
274
+ * Repeating the same admission continues it; a different bundle is refused.
275
+ */
276
+ export async function admitSetup(scope, input) {
277
+ const raw = await api(`${base(scope)}/setups/${input.quotes.setup_id}/admit`, { ...scoped(scope, input.signal), method: 'POST', body: { bundle_digest: input.quotes.bundle_digest, start_consent: true, credit_approval: input.creditApproval === true } });
278
+ const setup = summaryRow(scope, { setupId: input.quotes.setup_id })(raw);
279
+ if (setup.original_digest !== input.quotes.setup_digest || setup.bundle_digest !== input.quotes.bundle_digest || setup.approved_at === null)
280
+ throw unconfirmed('tool session');
281
+ return setup;
282
+ }
283
+ /** Ask root to stop every child (idempotent). Acceptance is not proof that storage or processes are gone. */
284
+ export async function stopSetup(scope, input) {
285
+ const setupId = id(input.setupId, 'tool session');
286
+ const setup = summaryRow(scope, { setupId })(await api(`${base(scope)}/setups/${setupId}`, { ...scoped(scope, input.signal), method: 'DELETE', body: { stop_consent: true } }));
287
+ if (setup.stop_requested_at === null)
288
+ throw unconfirmed('tool session');
289
+ return setup;
290
+ }
291
+ /**
292
+ * A new review of a stopped, never-approved setup: root copies its exact retained selections, seeds and duration.
293
+ * It reserves and starts nothing. The same request key returns the same replacement, and root refuses one whose
294
+ * key belongs to another original, or an approved or still-open source (`native_tool_setup_replacement_requires_unused_stop`).
295
+ */
296
+ export async function replaceSetup(scope, input) {
297
+ const setupId = id(input.setupId, 'tool session');
298
+ const requestKey = id(input.requestKey, 'request');
299
+ const raw = await api(`${base(scope)}/setups/${setupId}/replacement-review`, { ...scoped(scope, input.signal), method: 'POST', body: { request_key: requestKey, replace_consent: true } });
300
+ if (!object(raw) || Object.keys(raw).length !== 2 || !uuid(raw.previous_setup_id) || lower(raw.previous_setup_id) !== setupId) {
301
+ throw unconfirmed('replacement review');
302
+ }
303
+ const setup = summaryRow(scope, { requestKey })(raw.setup);
304
+ if (setup.id === setupId || setup.actor_id !== lower(scope.owner))
305
+ throw unconfirmed('replacement review');
306
+ return setup;
307
+ }
308
+ function checkSelections(durationSeconds, selections) {
309
+ if (!Number.isSafeInteger(durationSeconds) || durationSeconds < SETUP_MIN_SECONDS || durationSeconds > SETUP_MAX_SECONDS) {
310
+ throw new CliError('tool_duration_invalid', `Choose a duration from ${SETUP_MIN_SECONDS} seconds to one year. Nothing was sent.`, 2);
311
+ }
312
+ if (!Array.isArray(selections) || selections.length < 1 || selections.length > SETUP_MAX_SELECTIONS
313
+ || !selections.every((one) => object(one) && uuid(one.implementation_id) && text(one.service_key, 200) && names(one.operations)
314
+ && (one.seed_id === null || uuid(one.seed_id)) && (one.dataset_id === null || uuid(one.dataset_id))
315
+ && (one.seed_id === null || one.dataset_id === null))) {
316
+ throw new CliError('tool_selection_invalid', `Choose 1 to ${SETUP_MAX_SELECTIONS} tools, each with 1 to ${SETUP_MAX_OPERATIONS} distinct operations, `
317
+ + 'and at most one of seed_id (shared pool) or dataset_id (isolated). Nothing was sent.', 2);
318
+ }
319
+ }
320
+ /** A selection file: exactly root's review input `{duration_seconds, selections}`. */
321
+ export function parseSetupInput(value) {
322
+ const keys = 'dataset_id,implementation_id,operations,seed_id,service_key';
323
+ if (!object(value) || Object.keys(value).some((key) => key !== 'duration_seconds' && key !== 'selections')
324
+ || !Array.isArray(value.selections) || !value.selections.every((one) => object(one) && Object.keys(one).sort().join(',') === keys)) {
325
+ throw new CliError('tool_selection_invalid', 'Use a file with exactly {"duration_seconds", "selections": [{"implementation_id", "service_key", '
326
+ + '"operations", "seed_id", "dataset_id"}]}. Nothing was sent.', 2);
327
+ }
328
+ const selections = value.selections.map((one) => ({
329
+ implementation_id: typeof one.implementation_id === 'string' ? lower(one.implementation_id) : '',
330
+ service_key: one.service_key, operations: one.operations,
331
+ seed_id: typeof one.seed_id === 'string' ? lower(one.seed_id) : one.seed_id,
332
+ dataset_id: typeof one.dataset_id === 'string' ? lower(one.dataset_id) : one.dataset_id
333
+ }));
334
+ checkSelections(value.duration_seconds, selections);
335
+ return { durationSeconds: value.duration_seconds, selections };
336
+ }
@@ -0,0 +1,288 @@
1
+ /**
2
+ * World definition reuse (Plan20 R10; root `native_world_reuse.py`, contract
3
+ * `accounts/native_world_reuse_catalog.py`, base `/native-world-reuse`).
4
+ *
5
+ * - Sharing is per exact immutable world revision, with workspaces of the same
6
+ * organization only. An administrator of the owner workspace grants read or
7
+ * read-and-use, lists grants and revokes.
8
+ * - A grant shares the closed declaration only: references and configuration
9
+ * names, never the donor's descriptor, sources, configuration values,
10
+ * credentials, spending or execution. It never grants the declaration's own
11
+ * dependencies (tool versions, library versions); each needs its own access.
12
+ * - A fork is an explicit, approved private copy into a new world of this
13
+ * workspace, bound to this workspace's agent and descriptor revision, from a
14
+ * definition the caller supplies (the source as read, or remapped). It is
15
+ * pinned to the source's exact digest. Archive and state forks of tools are
16
+ * a different thing and are not touched here.
17
+ * - A lost fork answer is read back by its request key, or withdrawn while it
18
+ * never landed; never sent again under a new key.
19
+ * Nothing here provisions, runs or spends anything.
20
+ */
21
+ import { api } from '../auth.js';
22
+ import { CliError } from '../http.js';
23
+ import { SHA256_HEX } from './contracts.js';
24
+ export const WORLD_DEPENDENT_KINDS = ['fork', 'campaign'];
25
+ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
26
+ const RESPONSE_LIMIT = 4 * 1024 * 1024;
27
+ const PAGE = 25;
28
+ const PAGE_KEYS = 'count,next,page,page_size,previous,results';
29
+ const SUMMARY_KEYS = 'can_use,created_at,definition_digest,id,name,owner_workspace_id,revision';
30
+ const DETAIL_KEYS = 'can_use,created_at,definition,definition_digest,id,name,owner_workspace_id,revision';
31
+ const GRANT_KEYS = 'allow_use,created_at,id,recipient_workspace_id,request_key,revision_id,revoked_at';
32
+ const FORK_KEYS = 'created_at,id,request_key,source_digest,source_revision_id,target_digest,target_revision_id,world_id';
33
+ const object = (value) => value !== null && typeof value === 'object' && !Array.isArray(value);
34
+ const uuid = (value) => typeof value === 'string' && UUID.test(value);
35
+ const sha = (value) => typeof value === 'string' && SHA256_HEX.test(value);
36
+ const time = (value) => typeof value === 'string' && Number.isFinite(Date.parse(value));
37
+ const keys = (value) => Object.keys(value).sort().join(',');
38
+ const unconfirmed = (what) => new CliError('native_response_unconfirmed', `The ${what} response could not be confirmed. Nothing further was sent for it.`);
39
+ const scoped = (scope, signal) => ({ actor: scope.owner, workspace: scope.workspace, signal, maximumResponseBytes: RESPONSE_LIMIT });
40
+ const base = (scope) => `/api/workspaces/${scope.workspace}/native-world-reuse`;
41
+ /** A UUID argument exactly as Firedrill printed it, lowercased. */
42
+ export function worldReuseId(value, label) {
43
+ if (!uuid(value))
44
+ throw new CliError('arguments_invalid', `Use the ${label} exactly as Firedrill printed it. Nothing was sent.`, 2);
45
+ return value.toLowerCase();
46
+ }
47
+ function pageNumber(value) {
48
+ const page = value ?? 1;
49
+ if (!Number.isSafeInteger(page) || page < 1)
50
+ throw new CliError('arguments_invalid', 'Use --page with a positive page number.', 2);
51
+ return page;
52
+ }
53
+ /** Root's standard page envelope for the 25-row page that was asked for. */
54
+ function pageOf(raw, page, row, what) {
55
+ if (!object(raw) || keys(raw) !== PAGE_KEYS || !Number.isSafeInteger(raw.count) || raw.count < 0 || raw.page !== page
56
+ || raw.page_size !== PAGE || (raw.next !== null && typeof raw.next !== 'string') || (raw.previous !== null && typeof raw.previous !== 'string')
57
+ || !Array.isArray(raw.results) || raw.results.length > PAGE || (raw.next !== null) !== (page * PAGE < raw.count))
58
+ throw unconfirmed(what);
59
+ return { count: raw.count, page, next: raw.next, previous: raw.previous, results: raw.results.map(row) };
60
+ }
61
+ function worldRow(withDefinition) {
62
+ return (value) => {
63
+ if (!object(value) || keys(value) !== (withDefinition ? DETAIL_KEYS : SUMMARY_KEYS) || !uuid(value.id) || !uuid(value.owner_workspace_id)
64
+ || typeof value.name !== 'string' || [...value.name].length > 120 || !Number.isSafeInteger(value.revision) || value.revision < 1
65
+ || !sha(value.definition_digest) || !time(value.created_at) || typeof value.can_use !== 'boolean'
66
+ || (withDefinition && !object(value.definition)))
67
+ throw unconfirmed('world version');
68
+ return { id: value.id.toLowerCase(), owner_workspace_id: value.owner_workspace_id.toLowerCase(), name: value.name, revision: value.revision,
69
+ definition_digest: value.definition_digest, created_at: value.created_at, can_use: value.can_use,
70
+ ...(withDefinition ? { definition: value.definition } : {}) };
71
+ };
72
+ }
73
+ function grantRow(revisionId) {
74
+ return (value) => {
75
+ if (!object(value) || keys(value) !== GRANT_KEYS || !uuid(value.id) || typeof value.revision_id !== 'string' || value.revision_id.toLowerCase() !== revisionId
76
+ || !uuid(value.recipient_workspace_id) || typeof value.allow_use !== 'boolean' || !uuid(value.request_key) || !time(value.created_at)
77
+ || !(value.revoked_at === null || time(value.revoked_at)))
78
+ throw unconfirmed('world sharing');
79
+ return { id: value.id.toLowerCase(), revision_id: revisionId, recipient_workspace_id: value.recipient_workspace_id.toLowerCase(),
80
+ allow_use: value.allow_use, request_key: value.request_key.toLowerCase(), created_at: value.created_at, revoked_at: value.revoked_at };
81
+ };
82
+ }
83
+ function forkRow(value) {
84
+ if (!object(value) || keys(value) !== FORK_KEYS || !uuid(value.id) || !uuid(value.source_revision_id) || !sha(value.source_digest)
85
+ || !uuid(value.target_revision_id) || !sha(value.target_digest) || !uuid(value.world_id) || !uuid(value.request_key)
86
+ || !time(value.created_at))
87
+ throw unconfirmed('world fork');
88
+ return { id: value.id.toLowerCase(), source_revision_id: value.source_revision_id.toLowerCase(), source_digest: value.source_digest,
89
+ target_revision_id: value.target_revision_id.toLowerCase(), target_digest: value.target_digest, world_id: value.world_id.toLowerCase(),
90
+ request_key: value.request_key.toLowerCase(), created_at: value.created_at };
91
+ }
92
+ function campaignRow(value) {
93
+ if (!object(value) || keys(value) !== 'created_at,id,state' || !uuid(value.id) || typeof value.state !== 'string' || !value.state
94
+ || !time(value.created_at))
95
+ throw unconfirmed('world dependents');
96
+ return { id: value.id.toLowerCase(), state: value.state, created_at: value.created_at };
97
+ }
98
+ /** World revisions this workspace can read (its own and those shared with it), newest first, by declaration name prefix. */
99
+ export async function listReusableWorlds(scope, input) {
100
+ const page = pageNumber(input.page);
101
+ const params = new URLSearchParams({ page: String(page), page_size: String(PAGE) });
102
+ if (input.search !== undefined) {
103
+ // Root counts characters (code points), not UTF-16 units.
104
+ if ([...input.search].length > 120)
105
+ throw new CliError('arguments_invalid', 'Use a world name prefix of up to 120 characters.', 2);
106
+ if (input.search)
107
+ params.set('q', input.search);
108
+ }
109
+ return pageOf(await api(`${base(scope)}/revisions?${params}`, scoped(scope, input.signal)), page, worldRow(false), 'world versions');
110
+ }
111
+ /** One readable world revision with its closed declaration. */
112
+ export async function getReusableWorld(scope, input) {
113
+ const revisionId = worldReuseId(input.revisionId, 'world version id');
114
+ const row = worldRow(true)(await api(`${base(scope)}/revisions/${revisionId}`, scoped(scope, input.signal)));
115
+ if (row.id !== revisionId)
116
+ throw unconfirmed('world version');
117
+ return row;
118
+ }
119
+ /** The sharing grants of one world revision this workspace owns, newest first, revoked ones included. Administrators only. */
120
+ export async function listWorldGrants(scope, input) {
121
+ const revisionId = worldReuseId(input.revisionId, 'world version id');
122
+ const page = pageNumber(input.page);
123
+ const raw = await api(`${base(scope)}/revisions/${revisionId}/grants?page=${page}&page_size=${PAGE}`, scoped(scope, input.signal));
124
+ return pageOf(raw, page, grantRow(revisionId), 'world sharing');
125
+ }
126
+ /**
127
+ * Share one exact world revision with another workspace of this organization
128
+ * under the caller's request key. The same key returns the original grant
129
+ * (revoked or not); a different recipient or access under it is refused.
130
+ * Administrators only; the human must approve first.
131
+ */
132
+ export async function shareWorldVersion(scope, input) {
133
+ const revisionId = worldReuseId(input.revisionId, 'world version id');
134
+ const recipient = worldReuseId(input.recipientWorkspaceId, 'workspace id');
135
+ const requestKey = worldReuseId(input.requestKey, 'request key');
136
+ if (recipient === scope.workspace.toLowerCase()) {
137
+ throw new CliError('arguments_invalid', 'Share with another workspace of this organization; this one already owns the world. Nothing was sent.', 2);
138
+ }
139
+ const row = grantRow(revisionId)(await api(`${base(scope)}/revisions/${revisionId}/grants`, {
140
+ ...scoped(scope, input.signal), method: 'POST', body: { request_key: requestKey, recipient_workspace_id: recipient, allow_use: input.allowUse }
141
+ }));
142
+ if (row.request_key !== requestKey || row.recipient_workspace_id !== recipient || row.allow_use !== input.allowUse)
143
+ throw unconfirmed('world sharing');
144
+ return row;
145
+ }
146
+ /** The caller's original grant under this key, or null when root has none (which does not prove it never arrived). Administrators only. */
147
+ export async function recoverWorldGrant(scope, input) {
148
+ const revisionId = worldReuseId(input.revisionId, 'world version id');
149
+ const requestKey = worldReuseId(input.requestKey, 'request key');
150
+ try {
151
+ const row = grantRow(revisionId)(await api(`${base(scope)}/revisions/${revisionId}/grants/requests/${requestKey}`, scoped(scope, input.signal)));
152
+ if (row.request_key !== requestKey)
153
+ throw unconfirmed('world sharing');
154
+ return row;
155
+ }
156
+ catch (error) {
157
+ if (error instanceof CliError && error.httpStatus === 404)
158
+ return null;
159
+ throw error;
160
+ }
161
+ }
162
+ /**
163
+ * Withdraw a sharing request that never landed, after the human approved it,
164
+ * so it can never land later. A grant that already landed is returned, not
165
+ * undone (revoke it instead). Administrators of the owner workspace only.
166
+ */
167
+ export async function withdrawWorldGrantRequest(scope, input) {
168
+ const revisionId = worldReuseId(input.revisionId, 'world version id');
169
+ const requestKey = worldReuseId(input.requestKey, 'request key');
170
+ const raw = await api(`${base(scope)}/revisions/${revisionId}/grants/requests/${requestKey}`, {
171
+ ...scoped(scope, input.signal), method: 'DELETE', body: { withdraw_consent: true }
172
+ });
173
+ if (object(raw) && keys(raw) === 'request_key,state' && raw.state === 'withdrawn' && typeof raw.request_key === 'string'
174
+ && raw.request_key.toLowerCase() === requestKey)
175
+ return { state: 'withdrawn' };
176
+ if (object(raw) && keys(raw) === 'receipt,state' && raw.state === 'committed') {
177
+ const grant = grantRow(revisionId)(raw.receipt);
178
+ if (grant.request_key === requestKey)
179
+ return { state: 'committed', grant };
180
+ }
181
+ throw unconfirmed('world sharing withdrawal');
182
+ }
183
+ /**
184
+ * True when the service answered with its own refusal code: nothing was
185
+ * written, so the request must not be reported as lost or retried under its
186
+ * key. Only answers without a service code (no response, a proxy error) are lost.
187
+ */
188
+ export function answeredRefusal(error) {
189
+ return error instanceof CliError && error.httpStatus !== null && error.code !== 'request_failed';
190
+ }
191
+ /** Revoke one grant after the human's approval. Idempotent: an already revoked grant is returned as it is. */
192
+ export async function revokeWorldGrant(scope, input) {
193
+ const revisionId = worldReuseId(input.revisionId, 'world version id');
194
+ const grantId = worldReuseId(input.grantId, 'grant id');
195
+ const row = grantRow(revisionId)(await api(`${base(scope)}/revisions/${revisionId}/grants/${grantId}`, {
196
+ ...scoped(scope, input.signal), method: 'DELETE', body: { revoke_consent: true }
197
+ }));
198
+ if (row.id !== grantId || row.revoked_at === null)
199
+ throw unconfirmed('world sharing');
200
+ return row;
201
+ }
202
+ /**
203
+ * Fork one usable world revision into a new private world of this workspace,
204
+ * after the human approved it. The definition is the caller's (the source as
205
+ * read, or remapped to resources this workspace can use); the source is pinned
206
+ * by its exact digest. The same key returns the original fork.
207
+ */
208
+ export async function forkWorld(scope, input) {
209
+ const sourceId = worldReuseId(input.source.id, 'world version id');
210
+ if (!sha(input.source.definition_digest))
211
+ throw unconfirmed('world version');
212
+ const requestKey = worldReuseId(input.requestKey, 'request key');
213
+ const row = forkRow(await api(`${base(scope)}/forks`, { ...scoped(scope, input.signal), method: 'POST', body: {
214
+ request_key: requestKey, source_revision_id: sourceId, source_digest: input.source.definition_digest,
215
+ agent_id: worldReuseId(input.agentId, 'agent id'), native_descriptor_id: worldReuseId(input.descriptorRevisionId, 'descriptor revision id'),
216
+ definition: input.definition, fork_consent: true
217
+ } }));
218
+ return checkFork(row, { sourceId, sourceDigest: input.source.definition_digest, requestKey });
219
+ }
220
+ /** A fork receipt for exactly this source (and digest, when known) under exactly this key. */
221
+ export function checkFork(row, expected) {
222
+ if (row.source_revision_id !== expected.sourceId.toLowerCase() || row.request_key !== expected.requestKey.toLowerCase()
223
+ || (expected.sourceDigest !== undefined && row.source_digest !== expected.sourceDigest))
224
+ throw unconfirmed('world fork');
225
+ return row;
226
+ }
227
+ /** The caller's original fork under this key, as root holds it now. Never sends it again. */
228
+ export async function recoverWorldFork(scope, input) {
229
+ const requestKey = worldReuseId(input.requestKey, 'request key');
230
+ try {
231
+ const fork = forkRow(await api(`${base(scope)}/forks/requests/${requestKey}`, scoped(scope, input.signal)));
232
+ if (fork.request_key !== requestKey)
233
+ throw unconfirmed('world fork');
234
+ return { state: 'committed', fork };
235
+ }
236
+ catch (error) {
237
+ if (error instanceof CliError && error.httpStatus === 404)
238
+ return { state: 'not_found' };
239
+ if (error instanceof CliError && error.code === 'native_world_fork_withdrawn')
240
+ return { state: 'withdrawn' };
241
+ throw error;
242
+ }
243
+ }
244
+ /**
245
+ * Withdraw a fork request that never landed, after the human approved it, so
246
+ * it can never land later. A fork that already landed is returned, not undone.
247
+ */
248
+ export async function withdrawWorldFork(scope, input) {
249
+ const requestKey = worldReuseId(input.requestKey, 'request key');
250
+ const raw = await api(`${base(scope)}/forks/requests/${requestKey}`, { ...scoped(scope, input.signal), method: 'DELETE', body: { withdraw_consent: true } });
251
+ if (object(raw) && keys(raw) === 'request_key,state' && raw.state === 'withdrawn' && typeof raw.request_key === 'string'
252
+ && raw.request_key.toLowerCase() === requestKey)
253
+ return { state: 'withdrawn' };
254
+ if (object(raw) && keys(raw) === 'receipt,state' && raw.state === 'committed') {
255
+ const fork = forkRow(raw.receipt);
256
+ if (fork.request_key === requestKey)
257
+ return { state: 'committed', fork };
258
+ }
259
+ throw unconfirmed('world fork withdrawal');
260
+ }
261
+ /** This workspace's forks of one world revision, or its campaigns planned from it (25 per page). Never another workspace's. */
262
+ export async function listWorldDependents(scope, input) {
263
+ const revisionId = worldReuseId(input.revisionId, 'world version id');
264
+ if (!WORLD_DEPENDENT_KINDS.includes(input.kind))
265
+ throw new CliError('arguments_invalid', `Use --kind ${WORLD_DEPENDENT_KINDS.join('|')} with world dependents.`, 2);
266
+ const page = pageNumber(input.page);
267
+ const raw = await api(`${base(scope)}/revisions/${revisionId}/dependents?kind=${input.kind}&page=${page}&page_size=${PAGE}`, scoped(scope, input.signal));
268
+ const row = input.kind === 'fork' ? (value) => {
269
+ const fork = forkRow(value);
270
+ if (fork.source_revision_id !== revisionId)
271
+ throw unconfirmed('world dependents');
272
+ return fork;
273
+ } : campaignRow;
274
+ return pageOf(raw, page, row, 'world dependents');
275
+ }
276
+ /** What a closed declaration references, so a human can see what a fork needs before approving it. */
277
+ export function declarationNeeds(definition) {
278
+ const list = (value) => Array.isArray(value) ? value.filter(object) : [];
279
+ const dependencies = list(definition.dependencies);
280
+ const resources = list(definition.resources);
281
+ return {
282
+ simulatedTools: dependencies.filter((row) => row.mode === 'simulated').length,
283
+ realTools: dependencies.filter((row) => row.mode === 'real').length,
284
+ libraryVersions: resources.filter((row) => row.origin === 'native_library').length,
285
+ worldItems: resources.filter((row) => row.origin === 'world_item').length,
286
+ configurationNames: list(definition.configuration_names).map((row) => row.name).filter((name) => typeof name === 'string'),
287
+ };
288
+ }
@@ -0,0 +1,23 @@
1
+ import { api, openBrowser } from './auth.js';
2
+ import { APP_ORIGIN, CliError } from './http.js';
3
+ import { terminalText } from './prompts.js';
4
+ import { onboardingRead } from '../../contracts/onboarding-contracts.js';
5
+ /** Account-only read/handoff. No workspace or local runner is required. */
6
+ export async function onboardingCommand(args, options, output) {
7
+ if (args.length > 1 || args[0] && !['show', 'open'].includes(args[0]))
8
+ throw new CliError('arguments_invalid', 'Use onboarding show or onboarding open. Optional account setup is edited in the browser.', 2);
9
+ const value = onboardingRead(await api('/api/auth/native/onboarding', { signal: options.signal }));
10
+ const url = new URL('/onboarding', APP_ORIGIN).href;
11
+ output({ ...value, browser_url: url }, [
12
+ `Optional setup: ${value.state} · step ${value.step} of 2`,
13
+ value.values.display_name ? `Name: ${terminalText(value.values.display_name)}` : '',
14
+ value.values.role ? `Role: ${terminalText(value.catalog.roles.find(row => row.value === value.values.role)?.label || value.values.role)}` : '',
15
+ value.values.industry ? `Industry: ${terminalText(value.catalog.industries.find(row => row.value === value.values.industry)?.label || value.values.industry)}` : '',
16
+ value.values.country_code ? `Country or territory: ${value.values.country_code}` : '',
17
+ value.workspace_request ? `Original workspace request: ${value.workspace_request.request_key} · ${value.workspace_request.state}` : '',
18
+ `Open ${url} to edit, skip or resume. No agent is run and no profile change is submitted by this command.`,
19
+ ].filter(Boolean).join('\n'));
20
+ const interactive = !options.json && process.stdin.isTTY && process.stderr.isTTY;
21
+ if (args[0] !== 'show' && options.openBrowser && interactive)
22
+ await openBrowser(url);
23
+ }