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,346 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { createHash, randomUUID } from 'node:crypto';
3
+ import { constants } from 'node:fs';
4
+ import { chmod, lstat, mkdir, mkdtemp, open, realpath, rename, rm } from 'node:fs/promises';
5
+ import { delimiter, dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
6
+ import { pathToFileURL } from 'node:url';
7
+ import { privateDirectory } from './credentials.js';
8
+ import { CliError, endpoint, jsonRequest, responseError } from './http.js';
9
+ import { selectOne } from './prompts.js';
10
+ import { bootstrapProcessLock } from './bootstrap-process-lock.js';
11
+ const digest = /^[a-f0-9]{64}$/;
12
+ const inside = (parent, child) => { const value = relative(parent, child); return !isAbsolute(value) && value !== '..' && !value.startsWith('..' + sep); };
13
+ const fail = () => new CliError('source_runtime_integrity', 'The private source helper could not be confirmed. It was not executed or overwritten.');
14
+ const interactive = () => !!process.stdin.isTTY && !!process.stderr.isTTY;
15
+ function host() {
16
+ if (!['darwin', 'linux'].includes(process.platform) || !['x64', 'arm64'].includes(process.arch)) {
17
+ throw new CliError('source_host_unsupported', 'Local source capture requires macOS or Linux on arm64 or x64.', 2);
18
+ }
19
+ const report = process.report.getReport();
20
+ if (process.platform === 'linux' && !report.header?.glibcVersionRuntime) {
21
+ throw new CliError('source_host_unsupported', 'The source helper currently supports GNU libc Linux. This host was not changed.', 2);
22
+ }
23
+ return { os: process.platform, arch: process.arch };
24
+ }
25
+ function release(value) {
26
+ if (!value || Object.keys(value).sort().join(',') !== 'arch,archive,notices,os,python_version,uv,uv_version'
27
+ || !['darwin', 'linux'].includes(value.os) || !['x64', 'arm64'].includes(value.arch) || value.uv_version !== '0.9.5'
28
+ || !/^3\.(12|13)\.[0-9]{1,3}$/.test(value.python_version))
29
+ throw fail();
30
+ let bucket;
31
+ for (const [name, filename, maximum] of [['uv', 'uv', 128 * 1024 ** 2], ['archive', 'python.tar.gz', 256 * 1024 ** 2], ['notices', 'notices.txt', 1024 ** 2]]) {
32
+ const part = value[name];
33
+ if (!part || Object.keys(part).sort().join(',') !== 'sha256,size,url' || !digest.test(part.sha256)
34
+ || !Number.isSafeInteger(part.size) || part.size < 1 || part.size > maximum || typeof part.url !== 'string')
35
+ throw fail();
36
+ let url;
37
+ try {
38
+ url = new URL(part.url);
39
+ }
40
+ catch {
41
+ throw fail();
42
+ }
43
+ const segments = url.pathname.split('/');
44
+ if (url.origin !== 'https://storage.googleapis.com' || url.username || url.password || url.search || url.hash
45
+ || segments.length !== 6 || !/^[a-z0-9][a-z0-9._-]{1,220}[a-z0-9]$/.test(segments[1])
46
+ || segments.slice(2).join('/') !== `firedrill-source-runtime/v1/${part.sha256}/${filename}` || bucket && bucket !== segments[1])
47
+ throw fail();
48
+ bucket = segments[1];
49
+ }
50
+ return value;
51
+ }
52
+ async function directory() {
53
+ const path = join(await privateDirectory(), 'source-runtime');
54
+ await mkdir(path, { mode: 0o700 }).catch(error => { if (error.code !== 'EEXIST')
55
+ throw error; });
56
+ const value = await lstat(path);
57
+ if (!value.isDirectory() || value.isSymbolicLink() || value.mode & 0o077 || process.getuid && value.uid !== process.getuid())
58
+ throw fail();
59
+ return path;
60
+ }
61
+ async function ownedFile(path, maximum) {
62
+ const before = await lstat(path);
63
+ if (!before.isFile() || before.isSymbolicLink() || before.size > maximum || before.mode & 0o022
64
+ || process.getuid && before.uid !== process.getuid())
65
+ throw fail();
66
+ const file = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW);
67
+ const actual = await file.stat();
68
+ if (actual.ino !== before.ino || actual.dev !== before.dev || actual.size !== before.size) {
69
+ await file.close();
70
+ throw fail();
71
+ }
72
+ return file;
73
+ }
74
+ async function fileHash(path, maximum) {
75
+ const file = await ownedFile(path, maximum);
76
+ try {
77
+ const before = await file.stat(), hash = createHash('sha256');
78
+ let size = 0;
79
+ for await (const part of file.createReadStream({ autoClose: false })) {
80
+ size += part.length;
81
+ if (size > maximum)
82
+ throw fail();
83
+ hash.update(part);
84
+ }
85
+ const after = await file.stat();
86
+ if (before.size !== size || after.size !== size || before.mtimeMs !== after.mtimeMs || before.ctimeMs !== after.ctimeMs)
87
+ throw fail();
88
+ return { size, sha256: hash.digest('hex') };
89
+ }
90
+ finally {
91
+ await file.close();
92
+ }
93
+ }
94
+ function key(value) {
95
+ return `cpython-${value.python_version}-${value.os === 'darwin' ? 'macos' : 'linux'}-${value.arch === 'x64' ? 'x86_64' : 'aarch64'}-${value.os === 'darwin' ? 'none' : 'gnu'}`;
96
+ }
97
+ async function interpreter(root, value, project, allowVerifiedInsideProject = false) {
98
+ const selected = host(), config = release(value.release);
99
+ if (value.schema !== 'firedrill.source-runtime-install.v1' || config.os !== selected.os || config.arch !== selected.arch
100
+ || !/^install-[a-f0-9-]{36}$/.test(value.directory) || !digest.test(value.executable_sha256))
101
+ throw fail();
102
+ const path = join(root, value.directory, 'python', key(config), 'bin', `python${config.python_version.split('.').slice(0, 2).join('.')}`);
103
+ const actual = await realpath(path);
104
+ const tree = join(root, value.directory);
105
+ if (!inside(tree, actual) || !allowVerifiedInsideProject && inside(project, actual))
106
+ throw fail();
107
+ for (let at = dirname(actual); inside(root, at); at = dirname(at)) {
108
+ const info = await lstat(at);
109
+ if (!info.isDirectory() || info.isSymbolicLink() || info.mode & 0o022 || process.getuid && info.uid !== process.getuid())
110
+ throw fail();
111
+ if (at === root)
112
+ break;
113
+ }
114
+ if ((await fileHash(actual, 128 * 1024 ** 2)).sha256 !== value.executable_sha256)
115
+ throw fail();
116
+ return actual;
117
+ }
118
+ async function retained(project, allowVerifiedInsideProject = false) {
119
+ const root = await directory(), selected = host();
120
+ const file = await ownedFile(join(root, `${selected.os}-${selected.arch}.json`), 32768).catch(error => {
121
+ if (error.code === 'ENOENT')
122
+ return null;
123
+ throw error;
124
+ });
125
+ if (!file)
126
+ return null;
127
+ try {
128
+ let value;
129
+ try {
130
+ value = JSON.parse(await file.readFile('utf8'));
131
+ }
132
+ catch {
133
+ throw fail();
134
+ }
135
+ return { value, executable: await interpreter(root, value, project, allowVerifiedInsideProject) };
136
+ }
137
+ finally {
138
+ await file.close();
139
+ }
140
+ }
141
+ async function run(binary, args, cwd, environment, signal, milliseconds, maximum = 2048) {
142
+ const stop = AbortSignal.any([signal, AbortSignal.timeout(milliseconds)]);
143
+ return new Promise((accept, refuse) => {
144
+ let size = 0, overflow = false;
145
+ const chunks = [];
146
+ const child = spawn(binary, args, { shell: false, cwd, env: environment, signal: stop, stdio: ['ignore', 'pipe', 'pipe'] });
147
+ child.stdout.on('data', (part) => { size += part.length; if (size > maximum) {
148
+ overflow = true;
149
+ child.kill('SIGTERM');
150
+ }
151
+ else
152
+ chunks.push(part); });
153
+ child.stderr.resume();
154
+ child.once('error', () => refuse(new CliError('source_runtime_unavailable', 'The source helper stopped. No project code was executed.')));
155
+ child.once('close', code => { if (stop.aborted || overflow)
156
+ refuse(new CliError('source_runtime_unavailable', 'The source helper stopped. No project code was executed.'));
157
+ else
158
+ accept({ code, output: Buffer.concat(chunks).toString('utf8') }); });
159
+ });
160
+ }
161
+ async function compatible(binary, project, signal) {
162
+ if (inside(project, binary))
163
+ return false;
164
+ const value = await lstat(binary);
165
+ if (!value.isFile() || value.mode & 0o022 || !(value.mode & 0o111) || process.getuid && ![0, process.getuid()].includes(value.uid))
166
+ return false;
167
+ const result = await run(binary, ['-I', '-B', '-c', 'import sys; print(str(sys.version_info.major)+"."+str(sys.version_info.minor))'], await directory(), { PATH: '/usr/bin:/bin' }, signal, 3000).catch(error => { signal.throwIfAborted(); return null; });
168
+ return result?.code === 0 && ['3.12', '3.13'].includes(result.output.trim());
169
+ }
170
+ async function download(part, path, signal, output, stage) {
171
+ const file = await open(path, 'wx', 0o600);
172
+ try {
173
+ const response = await fetch(part.url, { redirect: 'error', credentials: 'omit', referrerPolicy: 'no-referrer',
174
+ signal: AbortSignal.any([signal, AbortSignal.timeout(300000)]) });
175
+ if (!response.ok || !response.body)
176
+ throw new CliError('source_runtime_download_failed', 'The source helper download did not finish. No runtime was activated.');
177
+ const reader = response.body.getReader(), hash = createHash('sha256');
178
+ let size = 0, reported = 0;
179
+ try {
180
+ while (true) {
181
+ const chunk = await reader.read();
182
+ if (chunk.done)
183
+ break;
184
+ size += chunk.value.length;
185
+ if (size > part.size)
186
+ throw fail();
187
+ hash.update(chunk.value);
188
+ let at = 0;
189
+ while (at < chunk.value.length) {
190
+ const written = await file.write(chunk.value, at, chunk.value.length - at);
191
+ if (!written.bytesWritten)
192
+ throw fail();
193
+ at += written.bytesWritten;
194
+ }
195
+ if (size - reported >= 4 * 1024 ** 2) {
196
+ reported = size;
197
+ output('source_runtime_download', { stage, bytes: size, total: part.size }, `Downloading source helper: ${size} / ${part.size} bytes`);
198
+ }
199
+ }
200
+ }
201
+ finally {
202
+ await reader.cancel().catch(() => undefined);
203
+ reader.releaseLock();
204
+ }
205
+ if (size !== part.size || hash.digest('hex') !== part.sha256)
206
+ throw fail();
207
+ await file.sync();
208
+ output('source_runtime_download', { stage, bytes: size, total: part.size, verified: true }, `${stage} download verified.`);
209
+ }
210
+ finally {
211
+ await file.close();
212
+ }
213
+ }
214
+ export async function installSourceRuntime(project, signal, approve, output, allowPrompt = false) {
215
+ const current = await retained(project);
216
+ if (current)
217
+ return current;
218
+ const selected = host();
219
+ const response = await jsonRequest(endpoint('/api/auth/cli/source-runtime'), { signal }, 32768);
220
+ if (response.status !== 200)
221
+ throw responseError(response.body, response.status);
222
+ if (response.body.schema !== 'firedrill.source-runtime-catalog.v1' || !Array.isArray(response.body.runtimes) || response.body.runtimes.length > 4)
223
+ throw fail();
224
+ const rows = response.body.runtimes.map(release).filter(row => row.os === selected.os && row.arch === selected.arch);
225
+ if (rows.length !== 1)
226
+ throw new CliError('source_runtime_configuration_required', 'The source helper for this host is not published yet. No runtime or project files were changed.', 2);
227
+ const config = rows[0];
228
+ if (!approve) {
229
+ if (!allowPrompt || !interactive())
230
+ throw new CliError('source_runtime_consent_required', 'Run firedrill source runtime install --approve-download to allow the private helper download, then repeat this command. No source is uploaded.', 2);
231
+ approve = await selectOne(`Download Python ${config.python_version} for source capture (${Math.ceil((config.uv.size + config.archive.size + config.notices.size) / 1024 ** 2)} MB)?`, [{ value: true, label: 'Download helper', detail: 'Private app storage only. No PATH changes, project installs or agent execution.' }, { value: false, label: 'Cancel' }], signal);
232
+ if (!approve)
233
+ throw new CliError('source_runtime_declined', 'Helper download canceled. Project files were not changed.', 2);
234
+ }
235
+ const root = await directory(), lock = await bootstrapProcessLock(join(root, 'install.lock'));
236
+ signal = AbortSignal.any([signal, lock.signal]);
237
+ const nonce = randomUUID();
238
+ let stage, activated = false;
239
+ try {
240
+ lock.assertHeld();
241
+ signal.throwIfAborted();
242
+ const existing = await retained(project);
243
+ if (existing)
244
+ return existing;
245
+ stage = join(root, 'install-' + randomUUID());
246
+ await mkdir(stage, { mode: 0o700 });
247
+ const temporary = await mkdtemp(join(stage, 'scratch-'));
248
+ const uv = join(stage, 'uv'), archive = join(temporary, config.archive.sha256.slice(0, 9) + '-python.tar.gz');
249
+ const intent = await open(join(stage, 'release.json'), 'wx', 0o600);
250
+ try {
251
+ await intent.writeFile(JSON.stringify(config));
252
+ await intent.sync();
253
+ }
254
+ finally {
255
+ await intent.close();
256
+ }
257
+ output('source_runtime_install', { stage: 'downloading', version: config.python_version }, 'Downloading the source capture helper.');
258
+ await download(config.uv, uv, signal, output, 'Installer');
259
+ await download(config.archive, archive, signal, output, 'Python');
260
+ await download(config.notices, join(stage, 'notices.txt'), signal, output, 'Notices');
261
+ await chmod(uv, 0o700);
262
+ const [major, minor, patch] = config.python_version.split('.').map(Number), metadata = join(stage, 'downloads.json');
263
+ const file = await open(metadata, 'wx', 0o600);
264
+ try {
265
+ await file.writeFile(JSON.stringify({ [key(config)]: { name: 'cpython', arch: { family: config.arch === 'x64' ? 'x86_64' : 'aarch64', variant: null },
266
+ os: config.os === 'darwin' ? 'macos' : 'linux', libc: config.os === 'darwin' ? 'none' : 'gnu', major, minor, patch,
267
+ prerelease: null, variant: null, build: null, sha256: config.archive.sha256,
268
+ url: pathToFileURL(archive).href } }));
269
+ await file.sync();
270
+ }
271
+ finally {
272
+ await file.close();
273
+ }
274
+ // uv only extracts the already hash-checked local archive. It has no remote
275
+ // index, customer config, proxy, provider credential or package command.
276
+ const result = await run(uv, ['python', 'install', '--offline', '--no-config', '--no-bin', '--no-progress', '--managed-python',
277
+ '--install-dir', join(stage, 'python'), '--python-downloads-json-url', metadata, key(config)], stage, { HOME: stage, PATH: '/usr/bin:/bin', UV_CACHE_DIR: join(stage, 'cache'), UV_PYTHON_CACHE_DIR: temporary,
278
+ UV_PYTHON_INSTALL_BIN: '0', UV_NO_CONFIG: '1', UV_OFFLINE: '1', TMPDIR: temporary }, signal, 120000);
279
+ if (result.code !== 0)
280
+ throw new CliError('source_runtime_install_failed', 'The private source helper could not be installed. No target or project code was executed.');
281
+ const executable = await realpath(join(stage, 'python', key(config), 'bin', `python${major}.${minor}`));
282
+ if (!inside(stage, executable) || !await compatible(executable, project, signal))
283
+ throw fail();
284
+ const value = { schema: 'firedrill.source-runtime-install.v1', release: config,
285
+ directory: stage.slice(root.length + 1), executable_sha256: (await fileHash(executable, 128 * 1024 ** 2)).sha256 };
286
+ await interpreter(root, value, project);
287
+ const path = join(root, `${config.os}-${config.arch}.json`), receipt = path + '.' + nonce + '.tmp';
288
+ const record = await open(receipt, 'wx', 0o600);
289
+ try {
290
+ await record.writeFile(JSON.stringify(value));
291
+ await record.sync();
292
+ }
293
+ finally {
294
+ await record.close();
295
+ }
296
+ lock.assertHeld();
297
+ signal.throwIfAborted();
298
+ await rename(receipt, path);
299
+ activated = true;
300
+ output('source_runtime_install', { stage: 'installed', version: config.python_version, source_uploaded: false, target_executed: false }, 'Source capture helper installed. No project code was run.');
301
+ return { value, executable };
302
+ }
303
+ finally {
304
+ // Only this invocation's freshly allocated directory is eligible. Never
305
+ // remove a retained install, user project, shared parent or prior lock.
306
+ try {
307
+ if (stage && !activated && inside(root, stage) && /^install-[a-f0-9-]{36}$/.test(stage.slice(root.length + 1)))
308
+ await rm(stage, { recursive: true }).catch(() => undefined);
309
+ }
310
+ finally {
311
+ await lock.close();
312
+ }
313
+ }
314
+ }
315
+ async function resolveSourcePython(project, signal, allowPrompt, allowVerifiedInsideProject = false) {
316
+ host();
317
+ for (const part of (process.env.PATH ?? '').split(delimiter)) {
318
+ if (!part || !isAbsolute(part))
319
+ continue;
320
+ const binary = await realpath(resolve(part, 'python3')).catch(() => null);
321
+ if (binary && await compatible(binary, project, signal))
322
+ return binary;
323
+ }
324
+ const cached = await retained(project, allowVerifiedInsideProject);
325
+ if (cached)
326
+ return cached.executable;
327
+ if (!allowPrompt || !interactive())
328
+ throw new CliError('source_runtime_consent_required', 'Run firedrill source runtime install --approve-download to allow the private helper download, then repeat this command. No source is uploaded.', 2);
329
+ return (await installSourceRuntime(project, signal, false, (_type, _value, readable) => process.stderr.write(readable + '\n'), true)).executable;
330
+ }
331
+ export async function sourcePython(project, signal, allowPrompt = false) {
332
+ return resolveSourcePython(project, signal, allowPrompt);
333
+ }
334
+ /** Fixed first-party offline control code only. PATH candidates retain the
335
+ * normal outside-project trust rule. Only the exact private receipt/path/hash-
336
+ * verified cached interpreter can be inside cwd, for example when cwd is home.
337
+ * This selector never installs, prompts, contacts a provider or imports a target.
338
+ */
339
+ export async function controlPython(signal) {
340
+ return resolveSourcePython(await realpath(process.cwd()), signal, false, true);
341
+ }
342
+ export async function sourceRuntimeCommand(action, options, output) {
343
+ const project = await realpath(process.cwd());
344
+ const value = action === 'install' ? await installSourceRuntime(project, options.signal, options.approve, output, options.interactive) : await retained(project);
345
+ output('result', { host: host(), installed: !!value, version: value?.value.release.python_version ?? null, source_uploaded: false, target_executed: false }, value ? `Source helper: Python ${value.value.release.python_version}. No project code was run.` : 'No private source helper is installed. A compatible Python already on PATH can still be used.');
346
+ }
@@ -0,0 +1,75 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { open } from 'node:fs/promises';
3
+ import { dirname, join } from 'node:path';
4
+ import { z } from 'zod';
5
+ import { privateDirectory } from './credentials.js';
6
+ import { readMetadata } from './configuration-intents.js';
7
+ import { CliError } from './http.js';
8
+ import { identifier } from './world-contracts.js';
9
+ import { nativeMetadataCanonical } from '../../contracts/local-value-source-contracts.js';
10
+ const metadataSchema = z.object({ expected_source_id: z.string().uuid(), consent_to_transfer: z.literal(true), roots: z.array(z.object({ source_id: z.string().uuid(), alias: z.string().regex(/^[a-z][a-z0-9_-]{0,63}$/) }).strict()).min(1).max(128) }).strict();
11
+ const schema = z.object({ schema: z.literal('firedrill.source-selection-intent.v1'), owner: z.string().uuid(), workspace: z.string().uuid(),
12
+ agent: z.string().uuid(), key: z.string().uuid(), metadata: metadataSchema, graph_sha256: z.string().regex(/^[a-f0-9]{64}$/),
13
+ request_sha256: z.string().regex(/^[a-f0-9]{64}$/) }).strict();
14
+ const hash = (value) => createHash('sha256').update(nativeMetadataCanonical(value)).digest('hex');
15
+ export const sourceSelectionDigest = (input) => hash({ ...input, graph: input.graph ?? {} });
16
+ const invalid = () => new CliError('source_selection_metadata_required', 'Keep the original source selection and request ID. A newer source or changed graph was not substituted.', 2);
17
+ const pathFor = async (scope, agent, key) => join(await privateDirectory(), `source-selection-${identifier(scope.owner)}-${identifier(scope.workspace)}-${identifier(agent)}-${identifier(key)}.json`);
18
+ export async function retainedSourceSelection(scope, agent, key) {
19
+ let value;
20
+ try {
21
+ value = await readMetadata(await pathFor(scope, agent, key), true);
22
+ }
23
+ catch (error) {
24
+ if (error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT')
25
+ return null;
26
+ throw invalid();
27
+ }
28
+ const parsed = schema.safeParse(value);
29
+ if (!parsed.success || parsed.data.owner !== scope.owner || parsed.data.workspace !== scope.workspace || parsed.data.agent !== agent || parsed.data.key !== key)
30
+ throw invalid();
31
+ if (parsed.data.graph_sha256 === hash({}) && sourceSelectionDigest(parsed.data.metadata) !== parsed.data.request_sha256)
32
+ throw invalid();
33
+ return parsed.data;
34
+ }
35
+ export function sourceSelectionBody(original, file) {
36
+ const body = file ?? (original.graph_sha256 === hash({}) ? { ...original.metadata, graph: {} } : null);
37
+ if (!body || sourceSelectionDigest(body) !== original.request_sha256 || hash(body.graph ?? {}) !== original.graph_sha256)
38
+ throw invalid();
39
+ return body;
40
+ }
41
+ export async function rememberSourceSelection(scope, agent, key, input) {
42
+ // Source IDs/aliases and digests only. Never journal graph commands or values.
43
+ const metadata = metadataSchema.parse({ expected_source_id: input.expected_source_id, roots: input.roots, consent_to_transfer: input.consent_to_transfer });
44
+ const value = { schema: 'firedrill.source-selection-intent.v1', owner: scope.owner, workspace: scope.workspace,
45
+ agent: identifier(agent), key: identifier(key), metadata, graph_sha256: hash(input.graph ?? {}), request_sha256: sourceSelectionDigest(input) };
46
+ const path = await pathFor(scope, agent, key);
47
+ try {
48
+ const file = await open(path, 'wx', 0o600);
49
+ try {
50
+ await file.writeFile(JSON.stringify(value));
51
+ await file.sync();
52
+ }
53
+ finally {
54
+ await file.close();
55
+ }
56
+ if (process.platform !== 'win32') {
57
+ const parent = await open(dirname(path), 'r');
58
+ try {
59
+ await parent.sync();
60
+ }
61
+ finally {
62
+ await parent.close();
63
+ }
64
+ }
65
+ }
66
+ catch (error) {
67
+ if (!error || typeof error !== 'object' || !('code' in error) || error.code !== 'EEXIST')
68
+ throw error;
69
+ const prior = await retainedSourceSelection(scope, agent, key);
70
+ if (!prior || prior.request_sha256 !== value.request_sha256 || prior.graph_sha256 !== value.graph_sha256)
71
+ throw invalid();
72
+ return prior;
73
+ }
74
+ return value;
75
+ }
@@ -0,0 +1,214 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { z } from 'zod';
3
+ import { api } from './auth.js';
4
+ import { CliError, releaseClosed } from './http.js';
5
+ import { identifier } from './world-contracts.js';
6
+ import { selectedWorkspace } from './world-access.js';
7
+ import { selectMany, selectOne } from './prompts.js';
8
+ import { readSourceSet, readSourceSetWithdrawal, sourceSetWithdrawalSchema, sourceOptionSchema, sourceSetSchema } from '../../contracts/source-set-contracts.js';
9
+ import { rememberSourceSelection, retainedSourceSelection, sourceSelectionBody, sourceSelectionDigest } from './source-selection-intents.js';
10
+ import { readMetadata } from './configuration-intents.js';
11
+ const pageSchema = z.object({ results: z.array(sourceOptionSchema).max(100), count: z.number().int().nonnegative(),
12
+ page: z.number().int().positive(), page_size: z.number().int().positive(), next: z.string().nullable() });
13
+ const historySchema = z.object({ results: z.array(sourceSetSchema).max(100), count: z.number().int().nonnegative(),
14
+ page: z.number().int().positive(), page_size: z.number().int().positive() });
15
+ const inputSchema = z.object({ expected_source_id: z.string().uuid(), consent_to_transfer: z.literal(true),
16
+ roots: z.array(z.object({ alias: z.string().regex(/^[a-z][a-z0-9_-]{0,63}$/), source_id: z.string().uuid() }).strict()).min(1).max(128),
17
+ graph: z.record(z.string(), z.unknown()).optional() }).strict();
18
+ async function inputFile(path) {
19
+ try {
20
+ const value = inputSchema.parse(await readMetadata(path));
21
+ return { ...value, expected_source_id: identifier(value.expected_source_id),
22
+ roots: value.roots.map(root => ({ ...root, source_id: identifier(root.source_id) })) };
23
+ }
24
+ catch {
25
+ throw new CliError('source_set_input_invalid', 'Use a bounded JSON source selection with source IDs, component aliases and an optional service graph. Keep credentials in Vault.', 2);
26
+ }
27
+ }
28
+ export async function sourceSetsCommand(args, options, output) {
29
+ const action = args[0] ?? 'list';
30
+ if (!['list', 'options', 'use', 'status', 'withdraw'].includes(action) || args.length > (['status', 'withdraw'].includes(action) ? 2 : 1)
31
+ || options.file !== undefined && !['use', 'withdraw'].includes(action) || options.change && action !== 'withdraw'
32
+ || (options.page !== undefined || options.search !== undefined) && !['list', 'options'].includes(action)
33
+ || options.search !== undefined && action !== 'options' || options.request !== undefined && !['use', 'status', 'withdraw'].includes(action)
34
+ || options.approve && action !== 'use')
35
+ throw new CliError('arguments_invalid', 'Use sources list|options|use|status|withdraw with --agent <id> and action-specific options.', 2);
36
+ const interactive = !options.json && !!process.stdin.isTTY && !!process.stderr.isTTY;
37
+ const scope = await selectedWorkspace({ workspace: options.workspace, interactive, signal: options.signal, openBrowser: options.openBrowser });
38
+ let agent = options.agent ? identifier(options.agent) : undefined;
39
+ const auth = { actor: scope.owner, workspace: scope.workspace, signal: options.signal };
40
+ if (!agent && interactive) {
41
+ let page = 1;
42
+ while (!agent) {
43
+ const rows = await api(`/api/workspaces/${scope.workspace}/agents?page=${page}&page_size=25`, auth);
44
+ if (!Array.isArray(rows.results) || rows.results.length > 25)
45
+ throw new CliError('source_set_response_changed', 'The agent page changed. Read it again.');
46
+ const choices = rows.results.map(item => ({ value: identifier(item.id), label: item.name }));
47
+ if (page > 1)
48
+ choices.unshift({ value: 'previous', label: 'Previous page' });
49
+ if (rows.next)
50
+ choices.push({ value: 'next', label: 'Next page' });
51
+ if (!choices.length)
52
+ throw new CliError('agent_required', 'Connect an agent first.', 2);
53
+ const choice = await selectOne('Agent', choices, options.signal);
54
+ if (choice === 'next')
55
+ page++;
56
+ else if (choice === 'previous')
57
+ page--;
58
+ else
59
+ agent = choice;
60
+ }
61
+ }
62
+ if (!agent)
63
+ throw new CliError('agent_required', 'Pass --agent <id> to select the original application.', 2);
64
+ const base = `/api/workspaces/${scope.workspace}/agents/${agent}`;
65
+ const suppliedKey = args[1] ?? options.request;
66
+ if (args[1] && options.request && args[1] !== options.request)
67
+ throw new CliError('arguments_invalid', 'Keep one original source-selection request ID.', 2);
68
+ if (action === 'status') {
69
+ const key = identifier(suppliedKey ?? '');
70
+ const saved = await retainedSourceSelection(scope, agent, key);
71
+ const raw = await api(`${base}/source-sets/${key}`, auth);
72
+ const row = raw.state === 'not_received' ? z.object({ state: z.literal('not_received'), request_key: z.literal(key) }).strict().parse(raw)
73
+ : sourceSetWithdrawalSchema.safeParse(raw).success ? readSourceSetWithdrawal(raw, scope.workspace, agent, scope.owner, key, saved?.request_sha256)
74
+ : readSourceSet(raw, scope.workspace, agent, key);
75
+ if ('actor_id' in row && (row.actor_id !== scope.owner || saved && row.request_sha256 !== saved.request_sha256))
76
+ throw new CliError('source_set_request_changed', 'The original source selection could not be confirmed.');
77
+ output('source_set', row, `Source request ${key}: ${row.state}.`);
78
+ return;
79
+ }
80
+ if (action === 'withdraw') {
81
+ const key = identifier(suppliedKey ?? '');
82
+ let saved = await retainedSourceSelection(scope, agent, key);
83
+ if (!saved && options.file)
84
+ saved = await rememberSourceSelection(scope, agent, key, await inputFile(options.file));
85
+ else if (saved && options.file)
86
+ sourceSelectionBody(saved, await inputFile(options.file));
87
+ if (!saved)
88
+ throw new CliError('source_selection_metadata_required', 'Provide the exact original selection file or use the terminal that retained this request. No request was withdrawn.', 2);
89
+ const approved = options.change || interactive && await selectOne('Withdraw this source selection?', [
90
+ { value: false, label: 'Cancel' }, { value: true, label: 'Withdraw original selection', detail: 'A selection that is already connected stays unchanged.' }
91
+ ], options.signal);
92
+ if (!approved)
93
+ throw new CliError('source_change_consent_required', 'No request withdrawn. Review and use --approve-change for automation.', 2);
94
+ const raw = await api(`${base}/source-sets/${key}`, { ...auth, method: 'DELETE', body: { request_sha256: saved.request_sha256 } });
95
+ const row = sourceSetWithdrawalSchema.safeParse(raw).success ? readSourceSetWithdrawal(raw, scope.workspace, agent, scope.owner, key, saved.request_sha256)
96
+ : readSourceSet(raw, scope.workspace, agent, key);
97
+ if (row.actor_id !== scope.owner || row.request_sha256 !== saved.request_sha256)
98
+ throw new CliError('source_set_request_changed', 'Check the original source selection before another action.');
99
+ output('source_set', row, row.state === 'ready' ? 'This source selection was already connected and stays unchanged.' : `Source request ${key}: ${row.state}.`);
100
+ return;
101
+ }
102
+ const page = Number(options.page ?? '1'), query = (options.search ?? '').trim();
103
+ if (!Number.isSafeInteger(page) || page < 1 || page > 999999999 || query.length > 200)
104
+ throw new CliError('query_invalid', 'Use a positive page and a search of up to 200 characters.', 2);
105
+ if (action === 'list') {
106
+ const rows = historySchema.parse(await api(`${base}/source-sets?page=${page}&page_size=25`, auth));
107
+ if (rows.page !== page || rows.page_size !== 25 || rows.results.length > 25 || rows.results.some(row => row.workspace_id !== scope.workspace || row.agent_id !== agent))
108
+ throw new CliError('source_set_response_changed', 'Source history could not be confirmed. Read it again.');
109
+ output('source_sets', rows, rows.results.map(row => `${row.id} ${row.state} ${row.roots.length} components`).join('\n') || 'No combined source selections.');
110
+ return;
111
+ }
112
+ const fetchPage = async (page) => {
113
+ const value = pageSchema.parse(await api(`${base}/source-set-options?${new URLSearchParams({ page: String(page), page_size: '25', q: query })}`, auth));
114
+ if (value.page !== page || value.page_size !== 25 || value.results.length > 25 || new Set(value.results.map(row => row.id)).size !== value.results.length)
115
+ throw new CliError('source_set_response_changed', 'Available source components could not be confirmed. Read them again.');
116
+ return value;
117
+ };
118
+ if (action === 'options') {
119
+ const rows = await fetchPage(page);
120
+ output('source_options', rows, rows.results.map(row => `${row.id} ${row.name} ${row.label}`).join('\n') || 'No connected source matches.');
121
+ return;
122
+ }
123
+ let body;
124
+ const retained = options.request ? await retainedSourceSelection(scope, agent, identifier(options.request)) : null;
125
+ if (retained)
126
+ body = sourceSelectionBody(retained, options.file ? await inputFile(options.file) : undefined);
127
+ else if (options.file)
128
+ body = await inputFile(options.file);
129
+ else {
130
+ if (!interactive)
131
+ throw new CliError('source_selection_required', 'Pass --file <reviewed-source-selection.json> and --upload-source, or use an interactive terminal.', 2);
132
+ const selected = new Map();
133
+ let current = 1, finished = false;
134
+ while (!finished) {
135
+ const rows = await fetchPage(current);
136
+ const choices = rows.results.map(row => ({ value: row.id, label: row.name, detail: row.label }));
137
+ const picked = await selectMany('Source components (Space selects, Enter continues)', choices, options.signal, [...selected.keys()]);
138
+ for (const row of rows.results) {
139
+ if (picked.includes(row.id))
140
+ selected.set(row.id, row);
141
+ else
142
+ selected.delete(row.id);
143
+ }
144
+ if (selected.size > 128)
145
+ throw new CliError('source_capacity_required', 'Narrow the source selection or contact support for more composition capacity.', 2);
146
+ const navigation = [{ value: 'done', label: `Use ${selected.size} selected components` }, { value: 'cancel', label: 'Cancel' }];
147
+ if (rows.next)
148
+ navigation.push({ value: 'next', label: 'Next page' });
149
+ if (current > 1)
150
+ navigation.push({ value: 'previous', label: 'Previous page' });
151
+ const next = await selectOne('Source selection', navigation, options.signal);
152
+ if (next === 'cancel')
153
+ return;
154
+ if (next === 'next')
155
+ current++;
156
+ else if (next === 'previous')
157
+ current--;
158
+ else
159
+ finished = true;
160
+ }
161
+ if (!selected.size)
162
+ throw new CliError('source_selection_required', 'Select at least one connected source.', 2);
163
+ const detail = await api(base, auth);
164
+ if (!detail.source)
165
+ throw new CliError('source_required', 'The agent has no connected source.');
166
+ const used = new Set();
167
+ const roots = [...selected.values()].map((row, index) => {
168
+ const name = row.name.toLowerCase().replace(/[^a-z0-9_-]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 48);
169
+ const stem = /^[a-z]/.test(name) ? name : `component-${index + 1}`;
170
+ let alias = stem, suffix = 2;
171
+ while (used.has(alias))
172
+ alias = `${stem}-${suffix++}`;
173
+ used.add(alias);
174
+ return { alias, source_id: row.id };
175
+ });
176
+ body = { expected_source_id: identifier(detail.source.id), roots, consent_to_transfer: true };
177
+ }
178
+ const approved = options.approve || interactive && await selectOne('Use these sources together in isolated preparation?', [
179
+ { value: false, label: 'Not now' }, { value: true, label: `Use ${body.roots.length} source components` }
180
+ ], options.signal);
181
+ if (!approved)
182
+ throw new CliError('source_transfer_consent_required', 'No sources combined. Review the selection and approve with --upload-source.', 2);
183
+ const key = options.request ? identifier(options.request) : randomUUID();
184
+ const saved = await rememberSourceSelection(scope, agent, key, body);
185
+ const requestDigest = sourceSelectionDigest(body);
186
+ if (saved.request_sha256 !== requestDigest)
187
+ throw new CliError('source_set_request_changed', 'Keep the original source selection before retrying.');
188
+ const original = (row) => {
189
+ if (row.actor_id !== scope.owner || row.request_sha256 !== requestDigest)
190
+ throw new CliError('source_set_request_changed', 'This request belongs to another source selection. Recover its original inputs before choosing a new request.');
191
+ return row;
192
+ };
193
+ output('source_set_request', { agent_id: agent, request_key: key }, `Source request ${key}. Recover with sources status ${key} --agent ${agent}.`);
194
+ const prior = await api(`${base}/source-sets/${key}`, auth);
195
+ if (sourceSetWithdrawalSchema.safeParse(prior).success) {
196
+ readSourceSetWithdrawal(prior, scope.workspace, agent, scope.owner, key, requestDigest);
197
+ throw new CliError('source_set_withdrawn', 'This source selection was withdrawn. Review a new selection to continue.', 2);
198
+ }
199
+ if (prior.state !== 'not_received') {
200
+ const row = original(readSourceSet(prior, scope.workspace, agent, key));
201
+ output('source_set', row, `Original source request ${key}: ${row.state}.`);
202
+ return;
203
+ }
204
+ z.object({ state: z.literal('not_received'), request_key: z.literal(key) }).strict().parse(prior);
205
+ try {
206
+ const row = original(readSourceSet(await api(`${base}/source-sets`, { ...auth, method: 'POST', idempotencyKey: key, body }), scope.workspace, agent, key));
207
+ output('source_set', row, `Source selection ${row.state}. Startup requirements are discovered from the selected components. Existing worlds keep their original source.`);
208
+ }
209
+ catch (error) {
210
+ if (options.signal.aborted || releaseClosed(error))
211
+ throw error;
212
+ throw new CliError('source_set_acceptance_unknown', `Check sources status ${key} --agent ${agent}. The original request was not repeated and no simulation started.`);
213
+ }
214
+ }