firedrill-run 2.2.4 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (392) hide show
  1. package/README.md +178 -22
  2. package/bin/firedrill.mjs +29 -5
  3. package/dist/assembly.json +13 -0
  4. package/dist/cli/runtime/candidate_copy.py +395 -0
  5. package/dist/cli/runtime/source_snapshot.py +391 -0
  6. package/dist/cli/src/administration.js +295 -0
  7. package/dist/cli/src/agent-changes.js +496 -0
  8. package/dist/cli/src/assessment-requests.js +67 -0
  9. package/dist/cli/src/auth.js +465 -0
  10. package/dist/cli/src/billing-commerce-contracts.js +1 -0
  11. package/dist/cli/src/billing-usage.js +227 -0
  12. package/dist/cli/src/billing-wallet-contracts.js +1 -0
  13. package/dist/cli/src/billing.js +170 -0
  14. package/dist/cli/src/bootstrap-process-lock.js +136 -0
  15. package/dist/cli/src/browser-links.js +14 -0
  16. package/dist/cli/src/campaign-controls.js +122 -0
  17. package/dist/cli/src/campaign-diagnoses.js +118 -0
  18. package/dist/cli/src/campaign-reports.js +1215 -0
  19. package/dist/cli/src/campaign-world.js +156 -0
  20. package/dist/cli/src/candidate-copy.js +376 -0
  21. package/dist/cli/src/catalog-contracts.js +1 -0
  22. package/dist/cli/src/ci-access-contracts.js +1 -0
  23. package/dist/cli/src/ci-configuration-contracts.js +1 -0
  24. package/dist/cli/src/ci-coverage.js +64 -0
  25. package/dist/cli/src/ci-delivery-contracts.js +1 -0
  26. package/dist/cli/src/ci-result-contracts.js +1 -0
  27. package/dist/cli/src/ci-run-contracts.js +1 -0
  28. package/dist/cli/src/ci-setup-proposals.js +52 -0
  29. package/dist/cli/src/company-policies.js +229 -0
  30. package/dist/cli/src/completion.js +79 -0
  31. package/dist/cli/src/configuration-intents.js +277 -0
  32. package/dist/cli/src/configuration.js +369 -0
  33. package/dist/cli/src/continuous-simulation.js +277 -0
  34. package/dist/cli/src/coordinated-checkpoints.js +133 -0
  35. package/dist/cli/src/credential-mode.js +139 -0
  36. package/dist/cli/src/credentials.js +331 -0
  37. package/dist/cli/src/credit-format.js +8 -0
  38. package/dist/cli/src/dataset-contracts.js +1 -0
  39. package/dist/cli/src/dataset-export.js +154 -0
  40. package/dist/cli/src/dataset-reads.js +37 -0
  41. package/dist/cli/src/dependency-preparation.js +239 -0
  42. package/dist/cli/src/director-tool-setup.js +104 -0
  43. package/dist/cli/src/driver-review.js +225 -0
  44. package/dist/cli/src/evaluation-authoring.js +315 -0
  45. package/dist/cli/src/evidence-bundles.js +367 -0
  46. package/dist/cli/src/execution-proposals.js +36 -0
  47. package/dist/cli/src/http.js +302 -0
  48. package/dist/cli/src/local-container-boundary.js +38 -0
  49. package/dist/cli/src/local-container-policy.js +201 -0
  50. package/dist/cli/src/local-execution-setup.js +81 -0
  51. package/dist/cli/src/local-source-basis.js +73 -0
  52. package/dist/cli/src/local-source.js +66 -0
  53. package/dist/cli/src/local-value-store.js +91 -0
  54. package/dist/cli/src/local-values.js +163 -0
  55. package/dist/cli/src/login-organizations.js +70 -0
  56. package/dist/cli/src/main.js +1622 -0
  57. package/dist/cli/src/mcp-connection.js +109 -0
  58. package/dist/cli/src/native/bindings.js +137 -0
  59. package/dist/cli/src/native/canonical.js +370 -0
  60. package/dist/cli/src/native/cloud-resources.js +942 -0
  61. package/dist/cli/src/native/companion-channel.js +421 -0
  62. package/dist/cli/src/native/companion-ipc.js +473 -0
  63. package/dist/cli/src/native/contracts.js +218 -0
  64. package/dist/cli/src/native/control-plane.js +1354 -0
  65. package/dist/cli/src/native/credit-approval.js +15 -0
  66. package/dist/cli/src/native/dependency-proxy.js +1006 -0
  67. package/dist/cli/src/native/drill-cleanup.js +115 -0
  68. package/dist/cli/src/native/drill-runner.js +1562 -0
  69. package/dist/cli/src/native/drivers/action-trace.js +210 -0
  70. package/dist/cli/src/native/drivers/function-wrapper.js +152 -0
  71. package/dist/cli/src/native/drivers/http.js +442 -0
  72. package/dist/cli/src/native/drivers/index.js +47 -0
  73. package/dist/cli/src/native/drivers/jsonl-process.js +538 -0
  74. package/dist/cli/src/native/drivers/playwright.js +600 -0
  75. package/dist/cli/src/native/drivers/signed-event.js +244 -0
  76. package/dist/cli/src/native/drivers/types.js +578 -0
  77. package/dist/cli/src/native/evidence-filter.js +255 -0
  78. package/dist/cli/src/native/isolated-access.js +241 -0
  79. package/dist/cli/src/native/library-sharing.js +220 -0
  80. package/dist/cli/src/native/local-run.js +667 -0
  81. package/dist/cli/src/native/local-secrets.js +19 -0
  82. package/dist/cli/src/native/mcp-config.js +123 -0
  83. package/dist/cli/src/native/metadata-requests.js +120 -0
  84. package/dist/cli/src/native/observations.js +1159 -0
  85. package/dist/cli/src/native/owned-groups.js +46 -0
  86. package/dist/cli/src/native/plan.js +594 -0
  87. package/dist/cli/src/native/png.js +159 -0
  88. package/dist/cli/src/native/ports.js +43 -0
  89. package/dist/cli/src/native/readiness.js +139 -0
  90. package/dist/cli/src/native/reassessments.js +206 -0
  91. package/dist/cli/src/native/redaction.js +235 -0
  92. package/dist/cli/src/native/registration.js +155 -0
  93. package/dist/cli/src/native/roots.js +110 -0
  94. package/dist/cli/src/native/run-cleanup.js +199 -0
  95. package/dist/cli/src/native/scratch.js +117 -0
  96. package/dist/cli/src/native/screenshots.js +60 -0
  97. package/dist/cli/src/native/session.js +556 -0
  98. package/dist/cli/src/native/standalone-proxy.js +580 -0
  99. package/dist/cli/src/native/standalone-renewals.js +183 -0
  100. package/dist/cli/src/native/standalone-state.js +193 -0
  101. package/dist/cli/src/native/standalone-tools.js +187 -0
  102. package/dist/cli/src/native/supervisor.js +831 -0
  103. package/dist/cli/src/native/target-auth.js +161 -0
  104. package/dist/cli/src/native/target-state.js +330 -0
  105. package/dist/cli/src/native/tool-reuse.js +364 -0
  106. package/dist/cli/src/native/tool-setups.js +336 -0
  107. package/dist/cli/src/native/world-reuse.js +288 -0
  108. package/dist/cli/src/onboarding.js +23 -0
  109. package/dist/cli/src/priority-requests.js +70 -0
  110. package/dist/cli/src/priority.js +223 -0
  111. package/dist/cli/src/prompts.js +184 -0
  112. package/dist/cli/src/real-operations.js +221 -0
  113. package/dist/cli/src/recipe-intents.js +58 -0
  114. package/dist/cli/src/recorded-context-intents.js +59 -0
  115. package/dist/cli/src/recorded-context.js +248 -0
  116. package/dist/cli/src/recordings.js +69 -0
  117. package/dist/cli/src/registration-guide.js +39 -0
  118. package/dist/cli/src/report-exports.js +213 -0
  119. package/dist/cli/src/request-input.js +47 -0
  120. package/dist/cli/src/resource-lifecycle.js +141 -0
  121. package/dist/cli/src/revision-requests.js +55 -0
  122. package/dist/cli/src/runner-abandon.js +53 -0
  123. package/dist/cli/src/runner-campaign-controls.js +262 -0
  124. package/dist/cli/src/runner-claim-reads.js +36 -0
  125. package/dist/cli/src/runner-completion.js +65 -0
  126. package/dist/cli/src/runner-configuration.js +546 -0
  127. package/dist/cli/src/runner-container.js +794 -0
  128. package/dist/cli/src/runner-evidence-erasure.js +351 -0
  129. package/dist/cli/src/runner-evidence-privacy.js +37 -0
  130. package/dist/cli/src/runner-evidence-retention.js +161 -0
  131. package/dist/cli/src/runner-evidence.js +326 -0
  132. package/dist/cli/src/runner-execute.js +92 -0
  133. package/dist/cli/src/runner-execution-deadline.js +25 -0
  134. package/dist/cli/src/runner-grants.js +307 -0
  135. package/dist/cli/src/runner-identity.js +615 -0
  136. package/dist/cli/src/runner-inputs.js +308 -0
  137. package/dist/cli/src/runner-jobs.js +49 -0
  138. package/dist/cli/src/runner-model-transport.js +392 -0
  139. package/dist/cli/src/runner-ownership.js +332 -0
  140. package/dist/cli/src/runner-process-lock.js +175 -0
  141. package/dist/cli/src/runner-runtime.js +396 -0
  142. package/dist/cli/src/runner-session-channel.js +377 -0
  143. package/dist/cli/src/runner-trial-completion.js +34 -0
  144. package/dist/cli/src/runner-trial-journal.js +358 -0
  145. package/dist/cli/src/runner-trial-live.js +393 -0
  146. package/dist/cli/src/runner-trial-protocol.js +289 -0
  147. package/dist/cli/src/runner-trial-sync.js +110 -0
  148. package/dist/cli/src/runner-trial-transfer.js +107 -0
  149. package/dist/cli/src/runner-voice.js +40 -0
  150. package/dist/cli/src/runner-world-claim.js +21 -0
  151. package/dist/cli/src/runner-world-collector.js +159 -0
  152. package/dist/cli/src/runner-world-command-evidence.js +487 -0
  153. package/dist/cli/src/runner-world-commands.js +268 -0
  154. package/dist/cli/src/runner-world-completion.js +76 -0
  155. package/dist/cli/src/runner-world-erasure.js +95 -0
  156. package/dist/cli/src/runner-world-evidence-sync.js +91 -0
  157. package/dist/cli/src/runner-world-journal.js +510 -0
  158. package/dist/cli/src/runner-world-native-filters.js +134 -0
  159. package/dist/cli/src/runner-world-observations.js +50 -0
  160. package/dist/cli/src/runner-world-session-protocol.js +134 -0
  161. package/dist/cli/src/saved-records.js +40 -0
  162. package/dist/cli/src/scenario-authoring.js +212 -0
  163. package/dist/cli/src/scenario-discovery.js +216 -0
  164. package/dist/cli/src/scenario-paths.js +68 -0
  165. package/dist/cli/src/scenario-requests.js +4 -0
  166. package/dist/cli/src/service-receipts.js +105 -0
  167. package/dist/cli/src/simple/authoring.js +270 -0
  168. package/dist/cli/src/simple/credit-approval.js +16 -0
  169. package/dist/cli/src/simple/help.js +98 -0
  170. package/dist/cli/src/simple/init.js +61 -0
  171. package/dist/cli/src/simple/library-sharing.js +414 -0
  172. package/dist/cli/src/simple/metadata-requests.js +46 -0
  173. package/dist/cli/src/simple/native-report.js +841 -0
  174. package/dist/cli/src/simple/native-tools.js +780 -0
  175. package/dist/cli/src/simple/plan-apply.js +506 -0
  176. package/dist/cli/src/simple/reassessment.js +323 -0
  177. package/dist/cli/src/simple/register.js +123 -0
  178. package/dist/cli/src/simple/run.js +122 -0
  179. package/dist/cli/src/simple/status.js +49 -0
  180. package/dist/cli/src/simple/tool-renewal.js +308 -0
  181. package/dist/cli/src/simple/tool-reuse.js +526 -0
  182. package/dist/cli/src/simple/tool-start-catalog.js +100 -0
  183. package/dist/cli/src/simple/tool-state.js +426 -0
  184. package/dist/cli/src/simple/tools-author.js +222 -0
  185. package/dist/cli/src/simple/watch.js +28 -0
  186. package/dist/cli/src/simple/world-reuse.js +586 -0
  187. package/dist/cli/src/simulation-intents.js +406 -0
  188. package/dist/cli/src/simulation-run.js +1058 -0
  189. package/dist/cli/src/source-batch.js +322 -0
  190. package/dist/cli/src/source-connection.js +451 -0
  191. package/dist/cli/src/source-runtime.js +346 -0
  192. package/dist/cli/src/source-selection-intents.js +75 -0
  193. package/dist/cli/src/source-sets.js +214 -0
  194. package/dist/cli/src/target-models.js +109 -0
  195. package/dist/cli/src/tool-catalog.js +224 -0
  196. package/dist/cli/src/tool-command-contracts.js +1 -0
  197. package/dist/cli/src/tool-command-export.js +245 -0
  198. package/dist/cli/src/tool-connection-contracts.js +1 -0
  199. package/dist/cli/src/tool-connection-reads.js +23 -0
  200. package/dist/cli/src/tool-contributions.js +58 -0
  201. package/dist/cli/src/tool-data.js +116 -0
  202. package/dist/cli/src/tool-datasets.js +176 -0
  203. package/dist/cli/src/tool-group-contracts.js +1 -0
  204. package/dist/cli/src/tool-group-reads.js +26 -0
  205. package/dist/cli/src/tool-group-state.js +147 -0
  206. package/dist/cli/src/tool-groups.js +311 -0
  207. package/dist/cli/src/tool-instances.js +186 -0
  208. package/dist/cli/src/tool-lifecycle-contracts.js +1 -0
  209. package/dist/cli/src/tool-lifecycle-reads.js +30 -0
  210. package/dist/cli/src/tool-lifecycle.js +323 -0
  211. package/dist/cli/src/tool-package-contracts.js +1 -0
  212. package/dist/cli/src/tool-package-reads.js +41 -0
  213. package/dist/cli/src/tool-records.js +53 -0
  214. package/dist/cli/src/tool-runtime.js +96 -0
  215. package/dist/cli/src/tool-state-contracts.js +1 -0
  216. package/dist/cli/src/tool-state-reads.js +31 -0
  217. package/dist/cli/src/tools.js +306 -0
  218. package/dist/cli/src/trial-actions.js +93 -0
  219. package/dist/cli/src/trial-checkpoints.js +133 -0
  220. package/dist/cli/src/trial-checks.js +70 -0
  221. package/dist/cli/src/trial-state.js +197 -0
  222. package/dist/cli/src/vault.js +266 -0
  223. package/dist/cli/src/workspace-registration.js +383 -0
  224. package/dist/cli/src/workspace-team-contracts.js +1 -0
  225. package/dist/cli/src/world-access.js +231 -0
  226. package/dist/cli/src/world-agent.js +4 -0
  227. package/dist/cli/src/world-checkpoint-scopes.js +15 -0
  228. package/dist/cli/src/world-contracts.js +6 -0
  229. package/dist/cli/src/world-creation.js +201 -0
  230. package/dist/cli/src/world-datasets.js +207 -0
  231. package/dist/cli/src/world-inspection.js +257 -0
  232. package/dist/cli/src/world-recipes.js +103 -0
  233. package/dist/cli/src/world-requests.js +241 -0
  234. package/dist/cli/src/world-session-checkpoints.js +196 -0
  235. package/dist/cli/src/world-session-lifecycle.js +207 -0
  236. package/dist/cli/src/world-session-requests.js +215 -0
  237. package/dist/cli/src/world-session-state.js +145 -0
  238. package/dist/cli/src/world-session-voice.js +66 -0
  239. package/dist/cli/src/world-sessions.js +288 -0
  240. package/dist/cli/src/world-stream.js +99 -0
  241. package/dist/cli/src/world-time-report.js +58 -0
  242. package/dist/contracts/agent-change-contracts.js +75 -0
  243. package/dist/contracts/agent-execution-contracts.js +38 -0
  244. package/dist/contracts/application-state-contracts.js +26 -0
  245. package/dist/contracts/assessment-aggregate-contracts.js +15 -0
  246. package/dist/contracts/assessment-contracts.js +74 -0
  247. package/dist/contracts/billing-commerce-contracts.js +107 -0
  248. package/dist/contracts/billing-wallet-contracts.js +43 -0
  249. package/dist/contracts/campaign-lifecycle.js +1 -0
  250. package/dist/contracts/campaign-world-contracts.js +80 -0
  251. package/dist/contracts/catalog-contracts.js +73 -0
  252. package/dist/contracts/ci-access-contracts.js +68 -0
  253. package/dist/contracts/ci-configuration-contracts.js +88 -0
  254. package/dist/contracts/ci-coverage-contracts.js +65 -0
  255. package/dist/contracts/ci-coverage-labels.js +25 -0
  256. package/dist/contracts/ci-delivery-contracts.js +61 -0
  257. package/dist/contracts/ci-result-contracts.js +103 -0
  258. package/dist/contracts/ci-run-contracts.js +51 -0
  259. package/dist/contracts/ci-setup-contracts.js +88 -0
  260. package/dist/contracts/clip-contracts.js +32 -0
  261. package/dist/contracts/comparison-contracts.js +53 -0
  262. package/dist/contracts/composite-contracts.js +34 -0
  263. package/dist/contracts/context-contracts.js +76 -0
  264. package/dist/contracts/continuation-report-contracts.js +1 -0
  265. package/dist/contracts/continuity-requirements.js +8 -0
  266. package/dist/contracts/coordinated-checkpoint-contracts.js +34 -0
  267. package/dist/contracts/dataset-contracts.js +88 -0
  268. package/dist/contracts/dependency-decision-contracts.js +36 -0
  269. package/dist/contracts/dependency-protocol-contracts.js +43 -0
  270. package/dist/contracts/director-execution.js +51 -0
  271. package/dist/contracts/driver-review-contracts.js +99 -0
  272. package/dist/contracts/evidence-contracts.js +65 -0
  273. package/dist/contracts/evidence-coverage-contracts.js +10 -0
  274. package/dist/contracts/export-contracts.js +44 -0
  275. package/dist/contracts/github-directory-contracts.js +96 -0
  276. package/dist/contracts/insight-contracts.js +11 -0
  277. package/dist/contracts/inspection-contracts.js +1 -0
  278. package/dist/contracts/local-claim-contracts.js +39 -0
  279. package/dist/contracts/local-configuration-contracts.js +7 -0
  280. package/dist/contracts/local-evidence-contracts.js +32 -0
  281. package/dist/contracts/local-grant-contracts.js +83 -0
  282. package/dist/contracts/local-input-contracts.js +19 -0
  283. package/dist/contracts/local-runner-contracts.js +42 -0
  284. package/dist/contracts/local-trial-live-contracts.js +16 -0
  285. package/dist/contracts/local-value-source-contracts.js +92 -0
  286. package/dist/contracts/local-world-native-contracts.js +8 -0
  287. package/dist/contracts/mcp-contracts.js +42 -0
  288. package/dist/contracts/media-contracts.js +28 -0
  289. package/dist/contracts/native-check-contracts.js +31 -0
  290. package/dist/contracts/native-identity-contracts.js +52 -0
  291. package/dist/contracts/onboarding-contracts.js +32 -0
  292. package/dist/contracts/performance-contracts.js +85 -0
  293. package/dist/contracts/planning-contracts.js +81 -0
  294. package/dist/contracts/real-operation-contracts.js +30 -0
  295. package/dist/contracts/recommendation-contracts.js +54 -0
  296. package/dist/contracts/report-contracts.js +1 -0
  297. package/dist/contracts/resource-lifecycle-contracts.js +57 -0
  298. package/dist/contracts/revision-contracts.js +12 -0
  299. package/dist/contracts/revision-request-contracts.js +39 -0
  300. package/dist/contracts/saved-record-contracts.js +76 -0
  301. package/dist/contracts/scenario-draft-comparison.js +91 -0
  302. package/dist/contracts/scenario-world-contracts.js +14 -0
  303. package/dist/contracts/service-contracts.js +1 -0
  304. package/dist/contracts/session-command-contracts.js +56 -0
  305. package/dist/contracts/session-contracts.js +65 -0
  306. package/dist/contracts/session-state-contracts.js +67 -0
  307. package/dist/contracts/session-voice-contracts.js +45 -0
  308. package/dist/contracts/source-set-contracts.js +26 -0
  309. package/dist/contracts/source-upload-contracts.js +22 -0
  310. package/dist/contracts/tool-command-contracts.js +57 -0
  311. package/dist/contracts/tool-connection-contracts.js +87 -0
  312. package/dist/contracts/tool-contribution-contracts.js +35 -0
  313. package/dist/contracts/tool-group-contracts.js +53 -0
  314. package/dist/contracts/tool-group-state-contracts.js +63 -0
  315. package/dist/contracts/tool-lifecycle-contracts.js +94 -0
  316. package/dist/contracts/tool-package-contracts.js +91 -0
  317. package/dist/contracts/tool-protocol-contracts.js +71 -0
  318. package/dist/contracts/tool-runtime-billing-contracts.js +28 -0
  319. package/dist/contracts/tool-setup-proposal.js +59 -0
  320. package/dist/contracts/tool-state-contracts.js +79 -0
  321. package/dist/contracts/trial-state-contracts.js +30 -0
  322. package/dist/contracts/typescript/agent-change.js +1 -0
  323. package/dist/contracts/typescript/assessment-aggregate.js +1 -0
  324. package/dist/contracts/typescript/assessment.js +1 -0
  325. package/dist/contracts/typescript/billing-commerce.js +1 -0
  326. package/dist/contracts/typescript/campaigns.js +1 -0
  327. package/dist/contracts/typescript/catalog.js +1 -0
  328. package/dist/contracts/typescript/ci-access.js +1 -0
  329. package/dist/contracts/typescript/ci-configuration.js +1 -0
  330. package/dist/contracts/typescript/ci-delivery.js +1 -0
  331. package/dist/contracts/typescript/ci-result.js +1 -0
  332. package/dist/contracts/typescript/ci-runs.js +1 -0
  333. package/dist/contracts/typescript/clips.js +1 -0
  334. package/dist/contracts/typescript/comparison.js +1 -0
  335. package/dist/contracts/typescript/composite.js +1 -0
  336. package/dist/contracts/typescript/connections.js +1 -0
  337. package/dist/contracts/typescript/context.js +1 -0
  338. package/dist/contracts/typescript/datasets.js +1 -0
  339. package/dist/contracts/typescript/evidence-coverage.js +1 -0
  340. package/dist/contracts/typescript/export.js +1 -0
  341. package/dist/contracts/typescript/github-directory.js +1 -0
  342. package/dist/contracts/typescript/insight.js +1 -0
  343. package/dist/contracts/typescript/inspection.js +1 -0
  344. package/dist/contracts/typescript/intake.js +1 -0
  345. package/dist/contracts/typescript/local-claim.js +1 -0
  346. package/dist/contracts/typescript/local-configuration.js +1 -0
  347. package/dist/contracts/typescript/local-evidence.js +1 -0
  348. package/dist/contracts/typescript/local-grant.js +1 -0
  349. package/dist/contracts/typescript/local-input.js +1 -0
  350. package/dist/contracts/typescript/local-runner.js +1 -0
  351. package/dist/contracts/typescript/local-trial-live.js +1 -0
  352. package/dist/contracts/typescript/local-value-source.js +1 -0
  353. package/dist/contracts/typescript/local-world-native.js +1 -0
  354. package/dist/contracts/typescript/mcp.js +1 -0
  355. package/dist/contracts/typescript/media.js +1 -0
  356. package/dist/contracts/typescript/native-check.js +1 -0
  357. package/dist/contracts/typescript/native-identity.js +1 -0
  358. package/dist/contracts/typescript/onboarding.js +1 -0
  359. package/dist/contracts/typescript/performance.js +1 -0
  360. package/dist/contracts/typescript/planning.js +1 -0
  361. package/dist/contracts/typescript/preparation.js +1 -0
  362. package/dist/contracts/typescript/real-operation.js +1 -0
  363. package/dist/contracts/typescript/recommendation.js +1 -0
  364. package/dist/contracts/typescript/reports.js +1 -0
  365. package/dist/contracts/typescript/resource-lifecycle.js +1 -0
  366. package/dist/contracts/typescript/saved-record.js +1 -0
  367. package/dist/contracts/typescript/scenarios.js +1 -0
  368. package/dist/contracts/typescript/services.js +1 -0
  369. package/dist/contracts/typescript/session-command.js +1 -0
  370. package/dist/contracts/typescript/session-state.js +1 -0
  371. package/dist/contracts/typescript/session-voice.js +1 -0
  372. package/dist/contracts/typescript/session.js +1 -0
  373. package/dist/contracts/typescript/tool-command.js +1 -0
  374. package/dist/contracts/typescript/tool-connection.js +1 -0
  375. package/dist/contracts/typescript/tool-contribution.js +1 -0
  376. package/dist/contracts/typescript/tool-groups.js +1 -0
  377. package/dist/contracts/typescript/tool-lifecycle.js +1 -0
  378. package/dist/contracts/typescript/tool-package.js +1 -0
  379. package/dist/contracts/typescript/tool-state.js +1 -0
  380. package/dist/contracts/typescript/trial-state.js +1 -0
  381. package/dist/contracts/typescript/vault.js +1 -0
  382. package/dist/contracts/typescript/workspace-team.js +1 -0
  383. package/dist/contracts/typescript/world-stream.js +1 -0
  384. package/dist/contracts/workspace-team-contracts.js +55 -0
  385. package/dist/contracts/world-command.js +65 -0
  386. package/dist/contracts/world-lifetime-contracts.js +74 -0
  387. package/dist/contracts/world-recipe-contracts.js +27 -0
  388. package/dist/contracts/world-stream-contracts.js +27 -0
  389. package/dist/package.json +1 -0
  390. package/package.json +16 -16
  391. package/LICENSE.md +0 -23
  392. package/PACKAGING.json +0 -50
@@ -0,0 +1,323 @@
1
+ import { APP_ORIGIN } from './http.js';
2
+ import { randomUUID } from 'node:crypto';
3
+ import { constants } from 'node:fs';
4
+ import { lstat, open } from 'node:fs/promises';
5
+ import { join, resolve } from 'node:path';
6
+ import { api, openBrowser } from './auth.js';
7
+ import { privateDirectory } from './credentials.js';
8
+ import { CliError } from './http.js';
9
+ import { selectMany, selectOne } from './prompts.js';
10
+ import { identifier } from './world-contracts.js';
11
+ import { selectedWorkspace } from './world-access.js';
12
+ import { packageReads } from './tool-package-reads.js';
13
+ import { toolLifecycleReads } from './tool-lifecycle-reads.js';
14
+ import { toolConnectionReads } from './tool-connection-reads.js';
15
+ import { allocationApproval } from './tool-runtime.js';
16
+ import { datasetReads } from './dataset-reads.js';
17
+ import { toolConnectionRead, toolConnectionMatches, toolConnectionAccepted, toolConnectionTokenMatches } from './tool-connection-contracts.js';
18
+ import { toolLifecycleRead, toolInstanceDetailMatches, toolStartReceiptMatches, toolRemovalMatches } from './tool-lifecycle-contracts.js';
19
+ const tty = (options) => !options.json && Boolean(process.stdin.isTTY && process.stderr.isTTY);
20
+ const pair = (one) => JSON.stringify([one.service_key, one.operation_name]);
21
+ const removalText = (row) => `Instance: ${row.instance_id}\nRemoval request: ${row.request_key}\n`
22
+ + (row.completed_at ? `Removed ${row.completed_at}.` : `Removal requested ${row.requested_at}. Original cleanup is not yet confirmed.`)
23
+ + '\nActivity and saved data follow workspace retention. Removal is not data erasure.';
24
+ async function permissionFile(path, signal) {
25
+ const filename = resolve(path), before = await lstat(filename);
26
+ if (!before.isFile() || before.isSymbolicLink() || before.size > 1024 * 1024)
27
+ throw new CliError('tool_permissions_invalid', 'Choose a regular connection-permissions JSON file within request capacity.', 2);
28
+ const file = await open(filename, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
29
+ try {
30
+ const held = await file.stat();
31
+ if (held.dev !== before.dev || held.ino !== before.ino || held.size !== before.size || held.mtimeMs !== before.mtimeMs)
32
+ throw new Error();
33
+ const chunks = [];
34
+ let size = 0;
35
+ for await (const chunk of file.createReadStream({ autoClose: false })) {
36
+ signal.throwIfAborted();
37
+ size += chunk.length;
38
+ if (size > 1024 * 1024)
39
+ throw new Error();
40
+ chunks.push(Buffer.from(chunk));
41
+ }
42
+ const raw = Buffer.concat(chunks).toString('utf8'), after = await file.stat();
43
+ if (size !== held.size || after.size !== held.size || after.mtimeMs !== held.mtimeMs || after.ctimeMs !== held.ctimeMs)
44
+ throw new Error();
45
+ const value = JSON.parse(raw);
46
+ if (!value || Object.keys(value).sort().join(',') !== 'duration_seconds,operations,protocols' || !Array.isArray(value.operations)
47
+ || !value.operations.length || !value.operations.every(one => one && Object.keys(one).sort().join(',') === 'operation_name,service_key'
48
+ && typeof one.service_key === 'string' && /^[a-z][a-z0-9_-]{0,79}$/.test(one.service_key)
49
+ && typeof one.operation_name === 'string' && /^[A-Za-z][A-Za-z0-9_.-]{0,127}$/.test(one.operation_name))
50
+ || !Array.isArray(value.protocols) || !value.protocols.length || value.protocols.some(one => !['mcp', 'http'].includes(one))
51
+ || new Set(value.protocols).size !== value.protocols.length || !Number.isSafeInteger(value.duration_seconds) || value.duration_seconds < 60)
52
+ throw new Error();
53
+ return value;
54
+ }
55
+ catch {
56
+ throw new CliError('tool_permissions_invalid', 'Provide operations, protocols and duration_seconds only. No connection was requested.', 2);
57
+ }
58
+ finally {
59
+ await file.close();
60
+ }
61
+ }
62
+ async function choose(path, title, describe, auth, signal, read) {
63
+ let page = 1;
64
+ while (true) {
65
+ const result = read ? await read(page) : await api(`${path}${path.includes('?') ? '&' : '?'}page=${page}`, auth);
66
+ const value = await selectOne(title, [...result.results.flatMap(one => { const label = describe(one); return label === null ? [] : [{ value: one.id, label }]; }),
67
+ ...(result.next ? [{ value: '__next__', label: 'Next page' }] : []), ...(page > 1 ? [{ value: '__previous__', label: 'Previous page' }] : []),
68
+ { value: '__cancel__', label: 'Cancel' }], signal);
69
+ if (value === '__cancel__')
70
+ throw new CliError('canceled', 'Nothing was requested.', 130);
71
+ if (value === '__next__')
72
+ page++;
73
+ else if (value === '__previous__')
74
+ page--;
75
+ else
76
+ return identifier(value);
77
+ }
78
+ }
79
+ export async function toolLifecycleCommand(args, options, output) {
80
+ const action = args[0], read = ['start-status', 'connection-request', 'connections', 'open', 'removal', 'remove-status'].includes(action);
81
+ if (args.length > (['start', 'disconnect', 'connection-request'].includes(action) ? 3 : 2) || options.upload || options.service !== undefined
82
+ || action !== 'connect' && options.file !== undefined || action !== 'connections' && options.page !== undefined
83
+ || !['connections', 'connect'].includes(action) && options.search !== undefined || !['start', 'connect', 'remove'].includes(action) && options.request !== undefined
84
+ || read && options.change || options.seconds !== undefined && action !== 'start'
85
+ || options.usageQuote !== undefined && action !== 'start'
86
+ || options.page !== undefined && !/^[1-9][0-9]{0,8}$/.test(options.page) || (options.search?.length ?? 0) > 200) {
87
+ throw new CliError('arguments_invalid', 'Use tools start [version] [dataset], start-status <request>, stop|remove|removal [instance], remove-status <request>, open [instance], connect [instance], connections [instance], connection-request <instance> <request>, or disconnect <instance> <connection>.', 2);
88
+ }
89
+ const scope = await selectedWorkspace({ ...options, interactive: tty(options) });
90
+ const auth = { actor: scope.owner, workspace: scope.workspace, signal: options.signal }, prefix = `/api/workspaces/${scope.workspace}/tools`;
91
+ const lifecycle = toolLifecycleReads(scope, options.signal), owner = { workspace: scope.workspace, actor: scope.owner };
92
+ if (action === 'remove-status') {
93
+ const row = await lifecycle.removalRequest(identifier(args[1] ?? ''));
94
+ output('tool_removal', row, removalText(row));
95
+ return;
96
+ }
97
+ if (action === 'start-status') {
98
+ const row = await lifecycle.startRequest(identifier(args[1] ?? ''));
99
+ output('tool_instance', row, `${row.name} ${row.version}: ${row.state}\nInstance: ${row.id}${row.problem ? '\n' + row.problem : ''}`);
100
+ if (row.state === 'failed')
101
+ throw new CliError('tool_setup_failed', row.problem ?? 'This setup failed. No replacement was started.', 2);
102
+ return;
103
+ }
104
+ let id = args[1] ? identifier(args[1]) : undefined;
105
+ if (!id && tty(options))
106
+ id = action === 'start'
107
+ ? await choose(`${prefix}/catalog?state=inspected`, 'Tool version', one => one.state === 'inspected' ? `${one.name} ${one.version}` : null, auth, options.signal, packageReads(scope, options.signal).versions)
108
+ : await choose(`${prefix}/instances${['connect', 'stop'].includes(action) ? '?state=active' : ''}`, 'Tool instance', one => `${one.name} ${one.version} · ${one.state}`, auth, options.signal, page => lifecycle.instances(page, '', ['connect', 'stop'].includes(action) ? 'active' : 'all'));
109
+ if (!id)
110
+ throw new CliError('tool_selection_required', 'Provide a saved version for start or an instance ID for the other tool commands.', 2);
111
+ if (action === 'start') {
112
+ let dataset = args[2] ? identifier(args[2]) : null;
113
+ if (!dataset && tty(options)) {
114
+ const mode = await selectOne('Starting data', [{ value: 'packaged', label: 'Packaged data' }, { value: 'saved', label: 'Saved dataset' }], options.signal);
115
+ if (mode === 'saved')
116
+ dataset = await choose(`${prefix}/datasets?implementation_id=${id}&state=retained`, 'Saved dataset', row => row.available ? `${row.name} · version ${row.version}` : null, auth, options.signal, page => datasetReads(prefix, scope, options.signal).page({ page, implementation: id, state: 'retained' }));
117
+ }
118
+ const selected = dataset ? await datasetReads(prefix, scope, options.signal).detail(dataset, { implementation: id }) : null;
119
+ if (selected && (selected.kind !== 'tool' || !selected.available || selected.implementation_id !== id))
120
+ throw new CliError('tool_dataset_base_unavailable', 'Choose an available dataset for this exact tool version. No instance was requested.', 2);
121
+ const setup = await lifecycle.instances();
122
+ const duration = options.seconds === undefined ? setup.setup.default_duration_seconds : Number(options.seconds);
123
+ if (!Number.isSafeInteger(duration) || duration < 60 || duration > setup.setup.maximum_duration_seconds)
124
+ throw new CliError('tool_duration_invalid', 'Choose --seconds within the configured compute capacity.', 2);
125
+ const consent = options.change || tty(options) && await selectOne(`Start this isolated tool for ${duration} seconds using ${selected ? selected.name + ' version ' + selected.version : 'packaged data'}? The original instance stays unchanged.`, [{ value: false, label: 'Cancel' }, { value: true, label: 'Start instance' }], options.signal);
126
+ if (!consent)
127
+ throw new CliError('tool_start_consent_required', 'No instance was requested. Use --approve-tool-change for explicit automation consent.', 2);
128
+ const key = identifier(options.request ?? randomUUID());
129
+ const usageQuote = await allocationApproval(scope, options, { implementation_id: id, release_id: null, dataset_id: dataset,
130
+ restore: null, duration_seconds: duration, launch_request_key: key }, output);
131
+ output('tool_start_intent', { request_key: key, implementation_id: id, dataset_id: dataset }, `Setup request: ${key}\nIf interrupted, use tools start-status ${key}. Do not create a replacement request.`);
132
+ const input = { implementation_id: id, duration_seconds: duration, dataset_id: dataset, start_consent: true, usage_quote_id: usageQuote };
133
+ let row;
134
+ try {
135
+ const accepted = await api(`${prefix}/instances`, { ...auth, method: 'POST', idempotencyKey: key, body: input });
136
+ toolLifecycleRead(accepted, await toolStartReceiptMatches(accepted, owner, key, input));
137
+ row = await lifecycle.startRequest(key, { id: accepted.id, input });
138
+ }
139
+ catch {
140
+ try {
141
+ row = await lifecycle.startRequest(key, { input });
142
+ }
143
+ catch {
144
+ throw new CliError('tool_start_acceptance_unconfirmed', `Setup acceptance is unconfirmed. No replacement was submitted. Use tools start-status ${key} --workspace ${scope.workspace}.`);
145
+ }
146
+ }
147
+ output('tool_instance', row, `${row.name} ${row.version}: ${row.state}\nInstance: ${row.id}\nUse tools instance ${row.id} to inspect actual readiness.`);
148
+ return;
149
+ }
150
+ const path = `${prefix}/instances/${id}`;
151
+ const connections = toolConnectionReads(scope, id, options.signal), connectionScope = { ...owner, instance: id };
152
+ if (action === 'removal') {
153
+ const row = await lifecycle.removal(id);
154
+ output('tool_removal', row, removalText(row));
155
+ return;
156
+ }
157
+ if (action === 'remove') {
158
+ const original = await lifecycle.instance(id);
159
+ if (original.removal) {
160
+ if (options.request && original.removal.request_key !== identifier(options.request)) {
161
+ throw new CliError('tool_removal_pending', `This instance already has another removal. Use tools removal ${id}; no additional request was submitted.`, 2);
162
+ }
163
+ const row = await lifecycle.removal(id);
164
+ output('tool_removal', row, removalText(row));
165
+ return;
166
+ }
167
+ const approved = options.change || tty(options) && await selectOne(`Remove ${original.name} ${original.version}? Access closes and original compute stops. It moves to Removed history after cleanup. Saved activity/data follow workspace retention. Accepted operations may still finish.`, [{ value: false, label: 'Cancel' }, { value: true, label: 'Remove instance' }], options.signal);
168
+ if (!approved)
169
+ throw new CliError('tool_remove_consent_required', 'Nothing changed. Use --approve-tool-change for explicit automation consent.', 2);
170
+ const request = identifier(options.request ?? randomUUID());
171
+ output('tool_removal_intent', { instance_id: id, request_key: request }, `Removal request: ${request}\nIf interrupted, use tools remove-status ${request}. Do not invent a replacement request.`);
172
+ const row = await api(`${path}/removal`, { ...auth, method: 'POST', idempotencyKey: request,
173
+ body: { remove_consent: true, retain_saved_data: true } });
174
+ toolLifecycleRead(row, toolRemovalMatches(row, owner, { key: request, instance: id }));
175
+ const observed = await lifecycle.removalRequest(request, { instance: id, id: row.id });
176
+ output('tool_removal', observed, removalText(observed));
177
+ return;
178
+ }
179
+ if (action === 'open') {
180
+ const row = await lifecycle.instance(id);
181
+ const view = row.visual_interfaces.length ? 'interface' : 'activity';
182
+ const url = `${APP_ORIGIN}/tools/instances/${identifier(row.id)}?workspace=${encodeURIComponent(scope.workspace)}&view=${view}`;
183
+ const opened = options.openBrowser && !options.json ? await openBrowser(url) : false;
184
+ output('tool_browser_handoff', { workspace_id: scope.workspace, instance_id: row.id, view, url, opened }, (opened ? `Opened the tool ${view === 'interface' ? 'interface' : 'activity and history'} page. Browser consent and access stay in your signed-in portal.` : url)
185
+ + `\nUse workspace ${scope.workspace} in the browser.`);
186
+ return;
187
+ }
188
+ if (action === 'connections') {
189
+ const instance = await lifecycle.instance(id);
190
+ const result = await connections.page(Number(options.page ?? 1), options.search ?? '');
191
+ const quote = (value) => "'" + value.replaceAll("'", "'\\''") + "'";
192
+ output('tool_connections', result, (result.results.map(one => `${one.id} · ${one.protocols.join(', ')}\n${one.revoked_at ? 'Revoked'
193
+ : instance.removal || instance.terminal_at || instance.state === 'closing' || Date.parse(one.expires_at) <= Date.now() ? 'Ended' : 'Expires ' + one.expires_at}`).join('\n\n') || 'No connections in this instance.')
194
+ + `\nPage ${result.page}; ${result.count} connections.${result.next ? `\nNext: firedrill tools connections ${id} --workspace ${scope.workspace} --page ${result.page + 1}${options.search ? ' --search ' + quote(options.search) : ''}` : ''}`);
195
+ return;
196
+ }
197
+ if (action === 'connection-request') {
198
+ const row = await connections.original(identifier(args[2] ?? ''));
199
+ output('tool_connection', row, `Connection: ${row.id}\n${row.revoked_at ? 'Revoked.' : 'The token is not recoverable from this response. Use the saved private file, or revoke this connection before creating a replacement.'}`);
200
+ return;
201
+ }
202
+ if (action === 'stop' || action === 'disconnect') {
203
+ let grant = action === 'disconnect' && args[2] ? identifier(args[2]) : null;
204
+ if (action === 'disconnect' && !grant && tty(options))
205
+ grant = await choose(`${path}/connections`, 'Connection to revoke', one => `${one.protocols.join(', ')} · ${one.id}`, auth, options.signal, page => connections.page(page));
206
+ if (action === 'disconnect' && !grant)
207
+ throw new CliError('tool_connection_required', 'Choose a connection from tools connections.', 2);
208
+ const approved = options.change || tty(options) && await selectOne(action === 'stop' ? 'Close connections and stop this tool instance?' : 'Revoke this connection? An accepted operation may still finish.', [{ value: false, label: 'Cancel' }, { value: true, label: action === 'stop' ? 'Stop instance' : 'Revoke connection' }], options.signal);
209
+ if (!approved)
210
+ throw new CliError('tool_change_consent_required', 'Nothing changed. Use --approve-tool-change for explicit automation consent.', 2);
211
+ if (grant) {
212
+ const row = await api(`${path}/connections/${grant}`, { ...auth, method: 'DELETE' });
213
+ toolConnectionRead(row, toolConnectionMatches(row, connectionScope, { id: grant, revoked: true }));
214
+ output('tool_connection', row, 'Connection revoked. Any saved credential copies no longer authorize calls.');
215
+ }
216
+ else {
217
+ const original = await lifecycle.instance(id);
218
+ const accepted = await api(`${path}/stop`, { ...auth, method: 'POST', body: { stop_consent: true } });
219
+ toolLifecycleRead(accepted, toolInstanceDetailMatches(accepted, scope.workspace, { id }) && accepted.request_digest === original.request_digest);
220
+ const observed = await lifecycle.instance(id);
221
+ toolLifecycleRead(observed, observed.request_digest === original.request_digest && (!!observed.terminal_at || observed.state === 'closing'));
222
+ output('tool_instance', observed, observed.terminal_at ? 'Instance stopped.' : 'Stop requested. Inspect the instance for observed termination.');
223
+ }
224
+ return;
225
+ }
226
+ const selectedInstance = await lifecycle.instance(id);
227
+ if (!selectedInstance.observation_current)
228
+ throw new CliError('tool_instance_not_ready', 'Wait for a current instance observation before creating a connection.', 2);
229
+ let permission;
230
+ if (options.file)
231
+ permission = await permissionFile(options.file, options.signal);
232
+ else {
233
+ if (!tty(options))
234
+ throw new CliError('tool_permissions_required', 'Provide --file permissions.json with operations, protocols and duration_seconds. No credentials belong in that file.', 2);
235
+ let page = 1, selected = new Map(), duration = 0;
236
+ while (true) {
237
+ const choices = await connections.options(page, options.search ?? '', selectedInstance.state_epoch);
238
+ duration = choices.duration_seconds;
239
+ if (!choices.results.length && !selected.size)
240
+ throw new CliError('tool_operations_unavailable', 'No supported operations matched. No connection was requested.', 2);
241
+ const items = await selectMany('Allowed tool operations', choices.results.map(one => ({ value: pair(one), label: `${one.service_name}: ${one.operation_name}`, detail: one.effect === 'read' ? 'Read operation' : 'May change state' })), options.signal, choices.results.filter(one => selected.has(pair(one))).map(pair));
242
+ for (const one of choices.results) {
243
+ if (items.includes(pair(one)))
244
+ selected.set(pair(one), { service_key: one.service_key, operation_name: one.operation_name });
245
+ else
246
+ selected.delete(pair(one));
247
+ }
248
+ const next = await selectOne(`${selected.size} operations selected`, [{ value: 'done', label: 'Continue' },
249
+ ...(choices.next ? [{ value: 'next', label: 'Next page' }] : []), ...(page > 1 ? [{ value: 'previous', label: 'Previous page' }] : []), { value: 'cancel', label: 'Cancel' }], options.signal);
250
+ if (next === 'cancel')
251
+ throw new CliError('canceled', 'No connection was requested.', 130);
252
+ if (next === 'next')
253
+ page++;
254
+ else if (next === 'previous')
255
+ page--;
256
+ else
257
+ break;
258
+ }
259
+ const protocols = await selectMany('Connection access', [{ value: 'mcp', label: 'MCP' }, { value: 'http', label: 'HTTP' }], options.signal, ['mcp']);
260
+ permission = { operations: [...selected.values()], protocols, duration_seconds: duration };
261
+ }
262
+ if (!permission.operations.length || !permission.protocols.length || permission.duration_seconds < 60)
263
+ throw new CliError('tool_permissions_required', 'Select operations and a protocol while the tool has at least one minute remaining.', 2);
264
+ const consent = options.change || tty(options) && await selectOne('Create this connection and save its one-time credentials in a private local file?', [{ value: false, label: 'Cancel' }, { value: true, label: 'Create connection', detail: 'The file contains a scoped token. Never commit or share it publicly.' }], options.signal);
265
+ if (!consent)
266
+ throw new CliError('tool_change_consent_required', 'No connection was requested. Use --approve-tool-change for explicit automation consent.', 2);
267
+ // POSIX owner/mode guarantees are required before issuance. A Windows ACL
268
+ // export adapter is not silently replaced with an unprotected plaintext file.
269
+ if (!process.getuid || process.platform === 'win32')
270
+ throw new CliError('tool_private_file_unsupported', 'Use the browser Connections tab on this OS. Secure native credential-file delivery is not available.', 2);
271
+ const key = identifier(options.request ?? randomUUID()), directory = await privateDirectory();
272
+ const filename = join(directory, `tool-connection-${key}.json`);
273
+ let file;
274
+ try {
275
+ file = await open(filename, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | (constants.O_NOFOLLOW ?? 0), 0o600);
276
+ }
277
+ catch {
278
+ throw new CliError('tool_private_file_unavailable', 'The private request file already exists or cannot be created. Inspect the original connection request; no new grant was issued.', 2);
279
+ }
280
+ try {
281
+ const held = await file.stat();
282
+ if (!held.isFile() || held.uid !== process.getuid() || held.nlink !== 1 || (held.mode & 0o777) !== 0o600)
283
+ throw new CliError('tool_private_file_unsafe', 'Private file ownership could not be established. No connection was requested.', 2);
284
+ await file.writeFile(JSON.stringify({ schema: 'firedrill.tool-connection-intent.v1', request_key: key, workspace_id: scope.workspace, instance_id: id }) + '\n');
285
+ await file.sync();
286
+ output('tool_connection_intent', { instance_id: id, request_key: key }, `Connection request: ${key}\nIf interrupted, use tools connection-request ${id} ${key}. No token is printed or retried.`);
287
+ const issued = await api(`${path}/connections`, { ...auth, method: 'POST', idempotencyKey: key, body: { ...permission, data_transfer_consent: true } });
288
+ toolConnectionRead(issued, await toolConnectionAccepted(issued, { ...connectionScope, epoch: selectedInstance.state_epoch }, key, permission) && toolConnectionTokenMatches(issued));
289
+ const recorded = await connections.original(key, { id: issued.id, digest: issued.request_digest, input: permission });
290
+ if (!issued.token_available_once || !issued.token || recorded.revoked_at || Date.parse(recorded.expires_at) <= Date.now())
291
+ throw new CliError('tool_token_unavailable', 'The one-time token was not delivered or its access has ended. Inspect and revoke the original connection before creating a replacement.');
292
+ const configuration = { schema: 'firedrill.tool-connection.v1', workspace_id: scope.workspace, instance_id: id, grant_id: issued.id, expires_at: issued.expires_at,
293
+ http: issued.http_url ? { operations_url: issued.http_url, headers: { Authorization: 'Bearer ' + issued.token, 'Content-Type': 'application/json' },
294
+ request: { method: 'POST', path: '/{service_key}/{operation_name}', body: { arguments: {} }, idempotency_header: 'Idempotency-Key' },
295
+ request_status_url: issued.http_url.replace(/\/operations$/, '/requests/{request_id}'),
296
+ ...(issued.http_services.length ? { native_routes: { services: issued.http_services, headers: { Authorization: 'Bearer ' + issued.token },
297
+ idempotency_header: 'Idempotency-Key', idempotency_value: 'Use a UUID unless the route declares declared_vendor_key; that route accepts a non-secret visible ASCII key up to 255 characters.',
298
+ request_status_url: issued.http_result_url, recovery: 'Reuse only the same original key and arguments. Firedrill-Request-Id returns the retained UUID for request_status_url. Unknown effects are never replayed.' } } : {}) } : null,
299
+ mcpServers: issued.mcp_url ? { synthetic_tool: { type: 'http', url: issued.mcp_url, headers: { Authorization: 'Bearer ' + issued.token } } } : {} };
300
+ const raw = Buffer.from(JSON.stringify(configuration, null, 2) + '\n');
301
+ try {
302
+ await file.truncate(0);
303
+ let offset = 0;
304
+ while (offset < raw.length) {
305
+ const written = await file.write(raw, offset, raw.length - offset, offset);
306
+ if (written.bytesWritten <= 0)
307
+ throw new Error();
308
+ offset += written.bytesWritten;
309
+ }
310
+ await file.sync();
311
+ }
312
+ catch {
313
+ // Best effort only for this newly minted grant. Never claim revocation
314
+ // if the API reply was lost; its exact ID remains in workspace history.
315
+ await api(`${path}/connections/${issued.id}`, { ...auth, method: 'DELETE' }).catch(() => undefined);
316
+ throw new CliError('tool_credential_delivery_failed', 'Credential-file delivery failed. Inspect and revoke the original connection before creating a replacement.');
317
+ }
318
+ output('tool_connection_saved', { id: issued.id, instance_id: id, expires_at: issued.expires_at, credentials_file: filename }, `Connection created. Private configuration: ${filename}\nExpires: ${issued.expires_at}\nDo not commit this file or print its contents in CI logs.`);
319
+ }
320
+ finally {
321
+ await file.close();
322
+ }
323
+ }
@@ -0,0 +1 @@
1
+ export * from '../../contracts/tool-package-contracts.js';
@@ -0,0 +1,41 @@
1
+ import { api } from './auth.js';
2
+ import { identifier } from './world-contracts.js';
3
+ import { packageRead, toolVersionMatches, toolVersionPageMatches, toolServicesMatch, toolServiceMatches, toolOperationsMatch, toolOperationMatches, toolPackageMatches, toolPackagePageMatches } from './tool-package-contracts.js';
4
+ export function packageReads(scope, signal) {
5
+ const prefix = `/api/workspaces/${scope.workspace}/tools`, auth = { actor: scope.owner, workspace: scope.workspace, signal };
6
+ const owner = { actor: scope.owner, workspace: scope.workspace };
7
+ return {
8
+ async versions(page = 1, query = '', state = 'inspected') {
9
+ const value = await api(`${prefix}/catalog?page=${page}&q=${encodeURIComponent(query)}&state=${encodeURIComponent(state)}`, auth);
10
+ return packageRead(value, toolVersionPageMatches(value, scope.workspace, { page, query, state }));
11
+ },
12
+ async version(id) {
13
+ const value = await api(`${prefix}/implementations/${identifier(id)}?summary=true`, auth);
14
+ return packageRead(value, toolVersionMatches(value, scope.workspace, id));
15
+ },
16
+ async services(version, page = 1, query = '') {
17
+ const value = await api(`${prefix}/implementations/${version.id}/services?page=${page}&q=${encodeURIComponent(query)}`, auth);
18
+ return packageRead(value, toolServicesMatch(value, version, query, page));
19
+ },
20
+ async service(version, key) {
21
+ const value = await api(`${prefix}/implementations/${version.id}/services/${encodeURIComponent(key)}`, auth);
22
+ return packageRead(value, toolServiceMatches(value, version, key));
23
+ },
24
+ async operations(version, service, page = 1, query = '') {
25
+ const value = await api(`${prefix}/implementations/${version.id}/services/${encodeURIComponent(service)}/operations?page=${page}&q=${encodeURIComponent(query)}`, auth);
26
+ return packageRead(value, toolOperationsMatch(value, version, service, query, page));
27
+ },
28
+ async operation(version, service, name) {
29
+ const value = await api(`${prefix}/implementations/${version.id}/services/${encodeURIComponent(service)}/operations/${encodeURIComponent(name)}`, auth);
30
+ return packageRead(value, toolOperationMatches(value, version, service, name));
31
+ },
32
+ async request(key, selected = {}) {
33
+ const value = await api(`${prefix}/package-requests/${identifier(key)}`, auth);
34
+ return packageRead(value, toolPackageMatches(value, owner, { key, ...selected }));
35
+ },
36
+ async requests(page = 1, query = '', state = 'all') {
37
+ const value = await api(`${prefix}/package-requests?page=${page}&q=${encodeURIComponent(query)}&state=${encodeURIComponent(state)}`, auth);
38
+ return packageRead(value, toolPackagePageMatches(value, owner, query, state, page));
39
+ },
40
+ };
41
+ }
@@ -0,0 +1,53 @@
1
+ import { api } from './auth.js';
2
+ import { CliError } from './http.js';
3
+ import { selectOne } from './prompts.js';
4
+ import { identifier } from './world-contracts.js';
5
+ import { selectedWorkspace } from './world-access.js';
6
+ import { readSavedRecords } from './saved-records.js';
7
+ import { toolLifecycleReads } from './tool-lifecycle-reads.js';
8
+ import { toolStateReads } from './tool-state-reads.js';
9
+ const interactive = (options) => !options.json && Boolean(process.stdin.isTTY && process.stderr.isTTY);
10
+ const plain = (value) => JSON.stringify(value).slice(1, -1).replace(/[\u007f-\u009f]/g, character => `\\u${character.charCodeAt(0).toString(16).padStart(4, '0')}`);
11
+ async function choose(path, title, auth, signal, choices, read) {
12
+ let page = 1;
13
+ while (true) {
14
+ const response = read ? await read(page) : await api(`${path}?page=${page}`, auth);
15
+ const value = await selectOne(title, [
16
+ ...response.results.map(choices).filter((one) => one !== null).map(one => ({ value: one.id, label: one.label })),
17
+ ...(response.next ? [{ value: '__next__', label: 'Next page' }] : []),
18
+ ...(page > 1 ? [{ value: '__previous__', label: 'Previous page' }] : []),
19
+ { value: '__cancel__', label: 'Cancel' },
20
+ ], signal);
21
+ if (value === '__cancel__')
22
+ throw new CliError('canceled', 'Record inspection canceled. No tool state changed.', 130);
23
+ if (value === '__next__')
24
+ page++;
25
+ else if (value === '__previous__')
26
+ page--;
27
+ else
28
+ return value;
29
+ }
30
+ }
31
+ export async function toolRecordsCommand(args, options, output) {
32
+ if (args.length > 5 || options.file !== undefined || options.upload || options.service !== undefined
33
+ || options.seconds !== undefined || options.change || options.request !== undefined
34
+ || args[4] !== undefined && (options.page !== undefined || options.search !== undefined)
35
+ || options.page !== undefined && !/^[1-9][0-9]{0,8}$/.test(options.page) || (options.search?.length ?? 0) > 200) {
36
+ throw new CliError('arguments_invalid', 'Use tools data [instance] [checkpoint] [collection] [row-position], with --page/--search for lists or --json.', 2);
37
+ }
38
+ const scope = await selectedWorkspace({ ...options, interactive: interactive(options) });
39
+ const auth = { actor: scope.owner, workspace: scope.workspace, signal: options.signal, maximumResponseBytes: 16 * 1024 * 1024 };
40
+ const prefix = `/api/workspaces/${scope.workspace}/tools/instances`;
41
+ let instance = args[1], checkpoint = args[2], collection = args[3];
42
+ if (!instance && interactive(options))
43
+ instance = await choose(prefix, 'Tool instance', auth, options.signal, one => ({ id: one.id, label: `${plain(one.name)} ${plain(one.version)}` }), page => toolLifecycleReads(scope, options.signal).instances(page));
44
+ if (!instance)
45
+ throw new CliError('tool_instance_required', 'Provide an instance from tools instances.', 2);
46
+ instance = identifier(instance);
47
+ if (!checkpoint && interactive(options))
48
+ checkpoint = await choose(`${prefix}/${instance}/checkpoints`, 'Saved tool data', auth, options.signal, one => one.records.available ? { id: one.id, label: `${one.kind} · ${one.captured_at}` } : null, page => toolStateReads(scope, instance, options.signal).checkpoints(page));
49
+ if (!checkpoint)
50
+ throw new CliError('tool_checkpoint_required', 'Provide a checkpoint with available record pages from tools checkpoints.', 2);
51
+ checkpoint = identifier(checkpoint);
52
+ return readSavedRecords(`${prefix}/${instance}/checkpoints/${checkpoint}`, checkpoint, collection, args[4], { ...auth, json: options.json, page: options.page, search: options.search }, `firedrill tools data ${instance} ${checkpoint} --workspace ${scope.workspace}`, output);
53
+ }
@@ -0,0 +1,96 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { api } from './auth.js';
3
+ import { CliError } from './http.js';
4
+ import { selectOne } from './prompts.js';
5
+ import { selectedWorkspace } from './world-access.js';
6
+ import { identifier } from './world-contracts.js';
7
+ import { requestInput } from './request-input.js';
8
+ import { toolLifecycleReads } from './tool-lifecycle-reads.js';
9
+ import { toolExtensionMatches, toolExtensionPageMatches, toolRuntimeQuoteMatches } from './tool-lifecycle-contracts.js';
10
+ import { toolAllocationQuoteMatches, allocationCredits } from '../../contracts/tool-runtime-billing-contracts.js';
11
+ import { creditAmount } from './credit-format.js';
12
+ const tty = (options) => !options.json && Boolean(process.stdin.isTTY && process.stderr.isTTY);
13
+ export async function allocationQuote(scope, options, selection) {
14
+ const auth = { actor: scope.owner, workspace: scope.workspace, signal: options.signal };
15
+ if (!options.usageQuote && !tty(options))
16
+ throw new CliError('billing_quote_required', 'First use tools quote --file selection.json, then provide its approved --usage-quote and the same --request-key launch key. No instance was started.', 2);
17
+ const quote = options.usageQuote
18
+ ? await api(`/api/workspaces/${scope.workspace}/billing/usage/quotes/${identifier(options.usageQuote)}`, auth)
19
+ : await api(`/api/workspaces/${scope.workspace}/tools/runtime-quotes`, { ...auth, method: 'POST', idempotencyKey: randomUUID(), body: selection });
20
+ if (!toolAllocationQuoteMatches(quote, scope.workspace, selection) || Date.parse(quote.expires_at) <= Date.now())
21
+ throw new CliError('billing_scope_mismatch', 'Review a current runtime quote for this exact launch request.', 2);
22
+ return quote;
23
+ }
24
+ export async function allocationApproval(scope, options, selection, output) {
25
+ const quote = await allocationQuote(scope, options, selection);
26
+ output('tool_runtime_quote', quote, `${allocationCredits(quote, 'estimated')} credits estimated. Maximum budget ${allocationCredits(quote)} credits. The estimate is reserved only after approval.`);
27
+ if (tty(options) && !await selectOne('Approve this runtime budget?', [{ value: false, label: 'Cancel' }, { value: true, label: 'Approve runtime' }], options.signal))
28
+ throw new CliError('canceled', 'No runtime was started.', 130);
29
+ return quote.id;
30
+ }
31
+ export async function toolRuntimeCommand(args, options, output) {
32
+ const action = args[0], paged = action === 'extensions', extending = action === 'extend', quoting = action === 'extension-quote';
33
+ if (args.length > (action === 'extension-status' ? 3 : 2) || options.search !== undefined || options.service !== undefined || options.upload
34
+ || options.file !== undefined && action !== 'quote' || options.page !== undefined && !paged || options.seconds !== undefined && !extending && !quoting
35
+ || options.request !== undefined && !extending && action !== 'quote' || options.usageQuote !== undefined && !extending
36
+ || options.change && !extending || options.page !== undefined && !/^[1-9][0-9]{0,8}$/.test(options.page))
37
+ throw new CliError('arguments_invalid', 'Use tools quote --file selection.json, extension-quote|extend <instance> --seconds <duration>, extensions <instance>, or extension-status <instance> <request>.', 2);
38
+ const scope = await selectedWorkspace({ ...options, interactive: tty(options) }), auth = { actor: scope.owner, workspace: scope.workspace, signal: options.signal };
39
+ const prefix = `/api/workspaces/${scope.workspace}/tools`;
40
+ if (action === 'quote') {
41
+ if (args.length !== 1 || !options.file)
42
+ throw new CliError('arguments_invalid', 'Use tools quote --file selection.json.', 2);
43
+ const selected = JSON.parse(await requestInput(options.file, options.signal));
44
+ const quote = await api(`${prefix}/runtime-quotes`, { ...auth, method: 'POST', idempotencyKey: identifier(options.request ?? randomUUID()), body: selected });
45
+ if (!toolAllocationQuoteMatches(quote, scope.workspace, selected))
46
+ throw new CliError('billing_scope_mismatch', 'The quote did not match this selection.', 2);
47
+ output('tool_runtime_quote', quote, `${allocationCredits(quote)} credits maximum. Launch request: ${selected.launch_request_key}\nUse --request-key ${selected.launch_request_key} and --usage-quote ${quote.id} when starting.`);
48
+ return;
49
+ }
50
+ const instance = identifier(args[1] ?? ''), base = `${prefix}/instances/${instance}`, identity = { workspace: scope.workspace, actor: scope.owner, instance };
51
+ if (paged) {
52
+ const page = Number(options.page ?? 1), value = await api(`${base}/extensions?page=${page}`, auth);
53
+ if (!toolExtensionPageMatches(value, identity, page))
54
+ throw new CliError('tool_extension_scope_invalid', 'The runtime history did not match this instance.', 2);
55
+ output('tool_runtime_history', value, value.results.map(one => `${one.request_key} ${one.state} · ${one.confirmed_expires_at ?? one.requested_expires_at}${one.problem ? '\n' + one.problem : ''}`).join('\n')
56
+ + `\nPage ${page}; ${value.count} requests.${value.next ? `\nNext: tools extensions ${instance} --page ${page + 1}` : ''}`);
57
+ return;
58
+ }
59
+ if (action === 'extension-status') {
60
+ const key = identifier(args[2] ?? ''), value = await api(`${base}/extensions/${key}`, auth);
61
+ if (!toolExtensionMatches(value, identity, { key }))
62
+ throw new CliError('tool_extension_scope_invalid', 'The extension did not match the original request.', 2);
63
+ output('tool_runtime_extension', value, `${value.state}${value.problem ? '\n' + value.problem : ''}\n${value.confirmed_expires_at ?? value.requested_expires_at}`);
64
+ return;
65
+ }
66
+ const tool = await toolLifecycleReads(scope, options.signal).instance(instance);
67
+ if (!tool.lifetime.can_extend)
68
+ throw new CliError('tool_extension_not_ready', tool.lifetime.extension_problem ?? 'This instance cannot extend its runtime now.', 2);
69
+ const maximum = Math.max(0, Math.floor(tool.lifetime.maximum_extension_seconds - Math.max(0, (Date.parse(tool.expires_at) - Date.now()) / 1000)));
70
+ if (maximum < 60)
71
+ throw new CliError('tool_extension_not_ready', 'There is no additional runtime available in the current finite extension window.', 2);
72
+ const seconds = options.seconds === undefined && tty(options) ? await selectOne('Additional runtime', [
73
+ { value: 60, label: '1 minute' }, { value: 3600, label: '1 hour' }, { value: 86400, label: '1 day' }, { value: 604800, label: '7 days' }
74
+ ].filter(one => one.value <= maximum), options.signal) : Number(options.seconds);
75
+ if (!Number.isSafeInteger(seconds) || seconds < 60 || seconds > maximum)
76
+ throw new CliError('tool_duration_invalid', 'Choose --seconds within this instance’s available extension window.', 2);
77
+ const selected = { additional_seconds: seconds, idle_timeout_seconds: tool.lifetime.idle_timeout_seconds,
78
+ expected_expires_at: tool.expires_at, state_epoch: tool.state_epoch };
79
+ if (!options.usageQuote && !tty(options) && !quoting)
80
+ throw new CliError('billing_quote_required', 'Use extension-quote first and pass its approved --usage-quote with --approve-tool-change.', 2);
81
+ const quote = options.usageQuote ? await api(`/api/workspaces/${scope.workspace}/billing/usage/quotes/${identifier(options.usageQuote)}`, auth)
82
+ : await api(`${base}/extensions/quote`, { ...auth, method: 'POST', idempotencyKey: randomUUID(), body: selected });
83
+ if (!toolRuntimeQuoteMatches(quote, tool, selected) || Date.parse(quote.expires_at) <= Date.now())
84
+ throw new CliError('billing_scope_mismatch', 'Review a current quote for this exact extension.', 2);
85
+ output('tool_extension_quote', quote, `${creditAmount(quote.maximum_units, quote.credit_scale)} credits maximum. New deadline: ${String(quote.scope.requested_expires_at)}`);
86
+ if (quoting)
87
+ return;
88
+ if (!options.change && !(tty(options) && await selectOne('Approve this finite runtime extension?', [{ value: false, label: 'Cancel' }, { value: true, label: 'Extend runtime' }], options.signal)))
89
+ throw new CliError('tool_extension_consent_required', 'No extension was requested. Use --approve-tool-change for the selected budget.', 2);
90
+ const key = identifier(options.request ?? randomUUID()), input = { ...selected, extend_consent: true, usage_quote_id: quote.id };
91
+ output('tool_extension_intent', { instance_id: instance, request_key: key, input }, `Request: ${key}\nRecovery: tools extension-status ${instance} ${key}`);
92
+ const row = await api(`${base}/extensions`, { ...auth, method: 'POST', idempotencyKey: key, body: input });
93
+ if (!toolExtensionMatches(row, identity, { key, input }))
94
+ throw new CliError('tool_extension_acceptance_unconfirmed', 'Inspect the original extension request. Do not replace it.', 2);
95
+ output('tool_runtime_extension', row, `${row.state}\nInspect tools extension-status ${instance} ${key}. Existing connections keep their own expiry.`);
96
+ }
@@ -0,0 +1 @@
1
+ export * from '../../contracts/tool-state-contracts.js';
@@ -0,0 +1,31 @@
1
+ import { api } from './auth.js';
2
+ import { identifier } from './world-contracts.js';
3
+ import { toolStateRead, toolCaptureAccepted, toolTransitionAccepted, toolCheckpointsMatch, toolCapturesMatch, toolTransitionsMatch } from './tool-state-contracts.js';
4
+ export function toolStateReads(owner, instance, signal) {
5
+ const scope = { workspace: owner.workspace, actor: owner.owner, instance: identifier(instance) };
6
+ const prefix = `/api/workspaces/${scope.workspace}/tools/instances/${scope.instance}`;
7
+ const auth = { actor: owner.owner, workspace: owner.workspace, signal };
8
+ const params = (page, query) => `page=${page}&q=${encodeURIComponent(query)}`;
9
+ return {
10
+ async checkpoints(page = 1, query = '') {
11
+ const value = await api(`${prefix}/checkpoints?${params(page, query)}`, auth);
12
+ return toolStateRead(value, toolCheckpointsMatch(value, scope, query, page));
13
+ },
14
+ async captures(page = 1, query = '') {
15
+ const value = await api(`${prefix}/capture-requests?${params(page, query)}`, auth);
16
+ return toolStateRead(value, toolCapturesMatch(value, scope, query, page));
17
+ },
18
+ async transitions(page = 1, query = '') {
19
+ const value = await api(`${prefix}/transitions?${params(page, query)}`, auth);
20
+ return toolStateRead(value, toolTransitionsMatch(value, scope, query, page));
21
+ },
22
+ async capture(key, selected = {}) {
23
+ const value = await api(`${prefix}/capture-requests/${identifier(key)}`, auth);
24
+ return toolStateRead(value, await toolCaptureAccepted(value, scope, { ...selected, key }));
25
+ },
26
+ async transition(key, selected = {}) {
27
+ const value = await api(`${prefix}/transitions/${identifier(key)}`, auth);
28
+ return toolStateRead(value, await toolTransitionAccepted(value, scope, { ...selected, key }));
29
+ },
30
+ };
31
+ }