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,145 @@
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 { selectedWorld } from './world-access.js';
6
+ import { identifier } from './world-contracts.js';
7
+ import { worldSessionMatches, worldSessionsPageMatches } from '../../contracts/session-contracts.js';
8
+ import { sessionStateActionMatches, sessionStateRequestMatches, sessionCapturesPageMatches, sessionResetsPageMatches, sessionStateCounter } from '../../contracts/session-state-contracts.js';
9
+ export const SESSION_STATE_ACTIONS = ['captures', 'capture', 'capture-show', 'capture-request', 'resets', 'reset', 'reset-request'];
10
+ const captureText = (row) => `${row.id} ${row.state}`
11
+ + (row.reset_count === null ? '' : ` · state version ${row.reset_count}`)
12
+ + (row.observation ? `\n${row.observation.collection_count.toLocaleString()} collections · ${row.observation.record_count.toLocaleString()} records`
13
+ + (row.observation.captured_at ? ` · ${row.observation.captured_at}` : '') + `\nCapture: ${row.observation.id}` : '')
14
+ + (row.problem ? '\n' + row.problem : '');
15
+ const resetText = (row) => `${row.id} ${row.state}`
16
+ + (row.reset_at ? `\nStarting data restored ${row.reset_at} · state version ${row.reset_count}` : '\nA reset is not confirmed until its runtime receipt is retained.')
17
+ + (row.problem ? '\n' + row.problem : '');
18
+ /** Shares the browser's original session journal. No ambiguous write is repeated. */
19
+ export async function worldSessionState(args, options, output) {
20
+ const action = args[0], writing = action === 'capture' || action === 'reset';
21
+ const kind = action.startsWith('capture') ? 'capture' : 'reset';
22
+ const exact = ['capture-show', 'capture-request', 'reset-request'].includes(action);
23
+ if (!SESSION_STATE_ACTIONS.includes(action) || (exact ? args.length !== 3 : args.length > 2)
24
+ || options.search !== undefined || options.name !== undefined || options.checkpoint !== undefined
25
+ || options.shareCheckpointWithDevice !== undefined || options.approveRealSession !== undefined
26
+ || options.local !== undefined || options.managed !== undefined || options.device !== undefined || options.grant !== undefined
27
+ || options.seconds !== undefined || options.dataset !== undefined || options.shareDatasetWithDevice !== undefined
28
+ || options.acknowledgeSharedState !== undefined || options.run !== undefined && !writing
29
+ || options.page !== undefined && (!['captures', 'resets'].includes(action) || !/^[1-9][0-9]{0,8}$/.test(options.page))) {
30
+ throw new CliError('arguments_invalid', 'Use sessions captures, capture, capture-show, capture-request, resets, reset or reset-request with the matching session and optional history page. --run approves only a new capture or reset.', 2);
31
+ }
32
+ const tty = !options.json && Boolean(process.stdin.isTTY && process.stderr.isTTY);
33
+ const world = await selectedWorld({ ...options, interactive: tty });
34
+ const auth = { actor: world.owner, workspace: world.workspace, signal: options.signal };
35
+ const worldScope = { actor: world.owner, workspace: world.workspace, world: world.world };
36
+ let sessionId = args[1];
37
+ if (!sessionId) {
38
+ if (!tty)
39
+ throw new CliError('arguments_invalid', 'Provide a session ID from firedrill sessions.', 2);
40
+ let page = 1;
41
+ while (!sessionId) {
42
+ const result = await api(`${world.prefix}/runtime-sessions?page=${page}`, auth);
43
+ if (!worldSessionsPageMatches(result, worldScope))
44
+ throw new CliError('world_session_receipt_invalid', 'Session history could not be confirmed in this world.');
45
+ const choices = result.results.map(row => ({ value: row.id, label: row.id, detail: `${row.execution_location} · ${row.state}` }));
46
+ if (page > 1)
47
+ choices.push({ value: 'previous', label: 'Previous page', detail: '' });
48
+ if (result.next)
49
+ choices.push({ value: 'next', label: 'Next page', detail: '' });
50
+ choices.push({ value: 'cancel', label: 'Cancel', detail: '' });
51
+ const choice = await selectOne('World session', choices, options.signal);
52
+ if (choice === 'cancel')
53
+ throw new CliError('canceled', 'No state action was submitted.', 130);
54
+ if (choice === 'previous')
55
+ page--;
56
+ else if (choice === 'next')
57
+ page++;
58
+ else
59
+ sessionId = identifier(choice);
60
+ }
61
+ }
62
+ sessionId = identifier(sessionId);
63
+ const scope = { ...worldScope, session: sessionId }, base = `${world.prefix}/runtime-sessions/${sessionId}`;
64
+ const unconfirmed = () => new CliError('world_session_acceptance_unconfirmed', 'The original state request could not be confirmed. Inspect its key; no replacement was submitted.', 2);
65
+ function describe(row) { return 'observation' in row ? captureText(row) : resetText(row); }
66
+ async function lookup(key, present = true) {
67
+ const receipt = kind === 'capture'
68
+ ? await api(`${base}/capture-intent?request_key=${identifier(key)}`, auth)
69
+ : await api(`${base}/reset-intent?request_key=${identifier(key)}`, auth);
70
+ if (!sessionStateRequestMatches(receipt, scope, key, kind))
71
+ throw unconfirmed();
72
+ if (present)
73
+ output(`world_session_${kind}_request`, receipt, receipt.request ? describe(receipt.request)
74
+ : `Request ${key} is not recorded yet. A delayed original request may still arrive. Inspect it again or close the session; do not submit a replacement.`);
75
+ return receipt;
76
+ }
77
+ if (action.endsWith('-request')) {
78
+ await lookup(identifier(args[2]));
79
+ return;
80
+ }
81
+ if (action === 'capture-show') {
82
+ const id = identifier(args[2]), row = await api(`${base}/captures/${id}`, auth);
83
+ if (!sessionStateActionMatches(row, scope, 'capture', { id }))
84
+ throw unconfirmed();
85
+ output('world_session_capture', row, captureText(row));
86
+ return;
87
+ }
88
+ if (action === 'captures' || action === 'resets') {
89
+ const page = action === 'captures'
90
+ ? await api(`${base}/captures?page=${options.page ?? '1'}`, auth)
91
+ : await api(`${base}/resets?page=${options.page ?? '1'}`, auth);
92
+ if ('uncertain_reset' in page ? !sessionResetsPageMatches(page, scope) : !sessionCapturesPageMatches(page, scope))
93
+ throw unconfirmed();
94
+ const continuation = `firedrill sessions ${action} ${sessionId} --world ${world.world} --workspace ${world.workspace} --page ${page.page + 1}${options.json ? ' --json' : ''}`;
95
+ output(`world_session_${action}`, page, (page.results.map(describe).join('\n\n') || `No ${action} recorded for this session.`)
96
+ + `\nPage ${page.page}; ${page.count.toLocaleString()} matching entries.${page.next ? '\nContinue: ' + continuation : ''}`);
97
+ return;
98
+ }
99
+ const session = await api(base, auth);
100
+ if (!worldSessionMatches(session, worldScope, { id: sessionId }))
101
+ throw unconfirmed();
102
+ if (!session.can_control || session.terminal_at || session.state !== 'active' || !session.observation_current
103
+ || sessionStateCounter(session.reset_count) === null || kind === 'reset' && (session.reset_pending || session.voice_call_open
104
+ || sessionStateCounter(session.reset_count) >= 9223372036854775807n)) {
105
+ throw new CliError('world_session_state_unavailable', 'Use a current, controllable active session. End a voice call or inspect pending reset activity before resetting.', 2);
106
+ }
107
+ if (!options.run) {
108
+ if (!tty)
109
+ throw new CliError('world_session_consent_required', 'Use --run to approve this exact capture or reset in automation.', 2);
110
+ const detail = kind === 'capture' ? 'Captures supported current database records. This does not reset data or resolve an earlier uncertain write.'
111
+ : 'Restores the session’s sealed starting databases and restarts the agent and services. Other files and real-account changes are not restored. Tool views close.'
112
+ + (session.external_effects ? ' Restarting the agent may make new real requests using its approved access.' : '');
113
+ if (!await selectOne(kind === 'capture' ? 'Capture current database records?' : 'Reset to starting data?', [
114
+ { value: false, label: 'Cancel' }, { value: true, label: kind === 'capture' ? 'Capture data' : 'Reset to starting data', detail }
115
+ ], options.signal)) {
116
+ throw new CliError('canceled', 'No state action was submitted.', 130);
117
+ }
118
+ }
119
+ const key = randomUUID();
120
+ output('world_session_state_request_key', { session_id: sessionId, request_key: key, kind }, `Request: ${key}\nRecover: firedrill sessions ${kind}-request ${sessionId} ${key} --world ${world.world} --workspace ${world.workspace}. Do not repeat an uncertain action.`);
121
+ let result;
122
+ try {
123
+ if (kind === 'capture')
124
+ result = await api(`${base}/captures`, { ...auth, method: 'POST', idempotencyKey: key, body: {} });
125
+ else {
126
+ const body = { expected_reset_count: session.reset_count };
127
+ result = await api(`${base}/resets`, { ...auth, method: 'POST', idempotencyKey: key, body });
128
+ }
129
+ }
130
+ catch (error) {
131
+ if (options.signal.aborted)
132
+ throw error;
133
+ const receipt = await lookup(key, false);
134
+ if (!receipt.request)
135
+ throw unconfirmed();
136
+ result = receipt.request;
137
+ }
138
+ const selected = { key, ...(kind === 'reset' ? { counter: session.reset_count } : {}) };
139
+ if (!sessionStateActionMatches(result, scope, kind, selected))
140
+ throw unconfirmed();
141
+ const original = await lookup(key, false);
142
+ if (!sessionStateRequestMatches(original, scope, key, kind, selected) || original.request?.id !== result.id)
143
+ throw unconfirmed();
144
+ output(`world_session_${kind}`, original.request, describe(original.request));
145
+ }
@@ -0,0 +1,66 @@
1
+ import { workspaceLink } from './browser-links.js';
2
+ import { api } from './auth.js';
3
+ import { CliError } from './http.js';
4
+ import { selectOne } from './prompts.js';
5
+ import { selectedWorld } from './world-access.js';
6
+ import { identifier } from './world-contracts.js';
7
+ import { worldSessionsPageMatches } from '../../contracts/session-contracts.js';
8
+ import { manualVoicePageMatches, manualVoiceRequestMatches } from '../../contracts/session-voice-contracts.js';
9
+ export const SESSION_VOICE_ACTIONS = ['voice', 'voice-request'];
10
+ const describe = (row) => `${row.id} ${row.kind} · ${row.state}\nCall ${row.call_id} · ${row.created_at}`
11
+ + (row.audio_available && row.audio ? `\n${row.audio.duration_ms} ms response audio currently available.`
12
+ : row.audio_recorded ? '\nAudio was recorded; playback is no longer available.' : '')
13
+ + (row.omitted_samples ? '\nSome received audio was omitted; this is not a complete call recording.' : '')
14
+ + (row.problem ? '\n' + row.problem : '');
15
+ export async function worldSessionVoice(args, options, output) {
16
+ const action = args[0];
17
+ if (!SESSION_VOICE_ACTIONS.includes(action) || (action === 'voice-request' ? args.length !== 3 : args.length > 2)
18
+ || options.page !== undefined && (action !== 'voice' || !/^[1-9][0-9]{0,8}$/.test(options.page)))
19
+ throw new CliError('arguments_invalid', 'Use sessions voice [session] [--page N] or sessions voice-request <session> <key>.', 2);
20
+ const tty = !options.json && Boolean(process.stdin.isTTY && process.stderr.isTTY);
21
+ const world = await selectedWorld({ ...options, interactive: tty });
22
+ const auth = { actor: world.owner, workspace: world.workspace, signal: options.signal };
23
+ const identity = { actor: world.owner, workspace: world.workspace, world: world.world };
24
+ let id = args[1], page = 1;
25
+ if (!id && !tty)
26
+ throw new CliError('arguments_invalid', 'Provide a session from firedrill sessions.', 2);
27
+ while (!id) {
28
+ const value = await api(`${world.prefix}/runtime-sessions?page=${page}`, auth);
29
+ if (!worldSessionsPageMatches(value, identity))
30
+ throw new CliError('world_session_receipt_invalid', 'Session history could not be confirmed.');
31
+ const choices = value.results.map(row => ({ value: row.id, label: row.id, detail: row.state }));
32
+ if (page > 1)
33
+ choices.push({ value: 'previous', label: 'Previous page', detail: '' });
34
+ if (value.next)
35
+ choices.push({ value: 'next', label: 'Next page', detail: '' });
36
+ choices.push({ value: 'cancel', label: 'Cancel', detail: '' });
37
+ const selected = await selectOne('World session', choices, options.signal);
38
+ if (selected === 'cancel')
39
+ throw new CliError('canceled', 'Voice inspection canceled. No call was changed.', 130);
40
+ if (selected === 'previous')
41
+ page--;
42
+ else if (selected === 'next')
43
+ page++;
44
+ else
45
+ id = identifier(selected);
46
+ }
47
+ id = identifier(id);
48
+ const scope = { ...identity, session: id }, base = `${world.prefix}/runtime-sessions/${id}`;
49
+ const unconfirmed = () => new CliError('manual_voice_acceptance_unconfirmed', 'The selected voice records could not be confirmed. No audio request was submitted.', 2);
50
+ if (action === 'voice-request') {
51
+ const key = identifier(args[2]), value = await api(`${base}/voice-intent?request_key=${key}`, auth);
52
+ if (!manualVoiceRequestMatches(value, scope, key))
53
+ throw unconfirmed();
54
+ output('world_voice_request', value, value.request ? describe(value.request)
55
+ : 'The original request is not recorded yet. It may still arrive. Inspect this key or close the session; do not start a replacement call.');
56
+ return;
57
+ }
58
+ const value = await api(`${base}/voice?page=${options.page ?? '1'}`, auth);
59
+ if (!manualVoicePageMatches(value, scope))
60
+ throw unconfirmed();
61
+ const link = workspaceLink(world.workspace, `/worlds/${world.world}?world_tab=sessions&session=${id}`);
62
+ output('world_voice_history', value, (value.results.map(describe).join('\n\n') || 'No voice actions recorded for this session.')
63
+ + `\nPage ${value.page}; ${value.count.toLocaleString()} matching actions.${value.next
64
+ ? `\nContinue: firedrill sessions voice ${id} --world ${world.world} --workspace ${world.workspace} --page ${value.page + 1}${options.json ? ' --json' : ''}` : ''}`
65
+ + (value.supported || value.call ? `\nVoice controls: ${link}\nUse workspace ${world.workspace} in the browser.` : ''));
66
+ }
@@ -0,0 +1,288 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { api } from './auth.js';
3
+ import { CliError } from './http.js';
4
+ import { selectOne, terminalText } from './prompts.js';
5
+ import { execution, startingData } from './simulation-run.js';
6
+ import { executeAssignment } from './runner-execute.js';
7
+ import { selectedWorld } from './world-access.js';
8
+ import { identifier } from './world-contracts.js';
9
+ import { CHECKPOINT_ACTIONS, worldSessionCheckpoints } from './world-session-checkpoints.js';
10
+ import { SESSION_REQUEST_ACTIONS, worldSessionRequests } from './world-session-requests.js';
11
+ import { SESSION_STATE_ACTIONS, worldSessionState } from './world-session-state.js';
12
+ import { SESSION_VOICE_ACTIONS, worldSessionVoice } from './world-session-voice.js';
13
+ import { WORLD_LIFECYCLE_ACTIONS, worldLifecycleCommand, worldAllocationApproval } from './world-session-lifecycle.js';
14
+ import { COORDINATED_ACTIONS, coordinatedCheckpointsCommand } from './coordinated-checkpoints.js';
15
+ import { worldSessionMatches, worldSessionRequestMatches, worldSessionsPageMatches, worldSessionOpeningScopeMatches } from '../../contracts/session-contracts.js';
16
+ const description = (row) => `${row.id} ${row.state} ${row.execution_location === 'local' ? 'Local device' : 'Cloud'}\n`
17
+ + (row.terminal_at ? `Closed ${row.terminal_at}` : `Expires ${row.expires_at}`)
18
+ + (row.dataset_id ? row.starting_data ? `\n${row.starting_data.edit_count} saved edits applied ${row.starting_data.applied_at}; ${row.starting_data.rows_affected} rows directly affected.`
19
+ : '\nSaved starting-data application has not been confirmed.' : '')
20
+ + (row.fork ? `\nCheckpoint ${row.fork.checkpoint_id}: ${row.fork.observed_at ? 'applied ' + row.fork.observed_at : 'application not yet confirmed'}.` : '')
21
+ + (row.external_effects ? `\n${row.external_effects.real_connections_count} shared real-account connections. External changes are not reset or undone.` : '')
22
+ + (row.problem ? '\n' + row.problem : '');
23
+ /** One shared manual-world API. Opening is not a trial or an evaluation.
24
+ * Lost replies are recovered by reading the ORIGINAL request, never retrying
25
+ * a newly generated intent or changing execution location.
26
+ */
27
+ export async function worldSessionsCommand(args, options, output) {
28
+ const requestedAction = args[0] ?? 'list', action = requestedAction === 'resume' ? 'open' : requestedAction;
29
+ if (options.acknowledgePartialState && requestedAction !== 'resume')
30
+ throw new CliError('arguments_invalid', 'Use --acknowledge-partial-state only for sessions resume.', 2);
31
+ if (COORDINATED_ACTIONS.includes(action)) {
32
+ const allowed = new Set(['json', 'workspace', 'world', 'expectedOwner', 'openBrowser', 'signal', 'page', 'search', 'request', 'name', 'run']);
33
+ if (Object.entries(options).some(([key, value]) => value !== undefined && !allowed.has(key)))
34
+ throw new CliError('arguments_invalid', 'Use checkpoint scope, name, original request and explicit capture approval only.', 2);
35
+ return coordinatedCheckpointsCommand(args, options, output);
36
+ }
37
+ if (WORLD_LIFECYCLE_ACTIONS.includes(action)) {
38
+ const allowed = new Set(['json', 'workspace', 'world', 'expectedOwner', 'openBrowser', 'signal', 'page', 'search', 'request', 'file', 'run', 'seconds', 'usageQuote', 'name']);
39
+ if (Object.entries(options).some(([key, value]) => value !== undefined && !allowed.has(key)))
40
+ throw new CliError('arguments_invalid', 'Use the selected session, its original request and only the lifecycle action’s supported options.', 2);
41
+ return worldLifecycleCommand(args, options, output);
42
+ }
43
+ if (SESSION_VOICE_ACTIONS.includes(action)) {
44
+ const allowed = new Set(['json', 'workspace', 'world', 'expectedOwner', 'openBrowser', 'signal', 'page']);
45
+ if (Object.entries(options).some(([key, value]) => value !== undefined && !allowed.has(key)))
46
+ throw new CliError('arguments_invalid', 'Use session scope and history page only. Voice input controls open in the browser.', 2);
47
+ return worldSessionVoice(args, options, output);
48
+ }
49
+ if (SESSION_REQUEST_ACTIONS.includes(action)) {
50
+ if (options.name !== undefined || options.local !== undefined || options.managed !== undefined || options.device !== undefined
51
+ || options.grant !== undefined || options.seconds !== undefined || options.dataset !== undefined || options.shareDatasetWithDevice !== undefined
52
+ || options.checkpoint !== undefined || options.shareCheckpointWithDevice !== undefined || options.approveRealSession !== undefined || options.acknowledgeSharedState !== undefined) {
53
+ throw new CliError('arguments_invalid', 'Use the existing session, optional request file, paging and --run consent only.', 2);
54
+ }
55
+ return worldSessionRequests(args, options, output);
56
+ }
57
+ if (options.file !== undefined)
58
+ throw new CliError('arguments_invalid', 'Use --file only with sessions call.', 2);
59
+ if (SESSION_STATE_ACTIONS.includes(action))
60
+ return worldSessionState(args, options, output);
61
+ if (CHECKPOINT_ACTIONS.includes(action))
62
+ return worldSessionCheckpoints(args, options, output);
63
+ if (!['list', 'show', 'open', 'close', 'request', 'withdraw'].includes(action)
64
+ || (requestedAction === 'resume' ? args.length !== 2 : ['show', 'close', 'request', 'withdraw'].includes(action) ? args.length !== 2 : args.length > 1)
65
+ || options.search !== undefined || options.name !== undefined
66
+ || action !== 'list' && options.page !== undefined
67
+ || options.page !== undefined && (!/^[1-9][0-9]{0,8}$/.test(options.page) || !Number.isSafeInteger(Number(options.page)))
68
+ || action !== 'open' && (options.local !== undefined || options.managed !== undefined || options.device !== undefined
69
+ || options.grant !== undefined || options.seconds !== undefined || options.dataset !== undefined || options.shareDatasetWithDevice !== undefined
70
+ || options.checkpoint !== undefined || options.shareCheckpointWithDevice !== undefined
71
+ || options.approveRealSession !== undefined || options.acknowledgeSharedState !== undefined)) {
72
+ throw new CliError('arguments_invalid', 'Use sessions [list], show <session>, open, close <session>, request <key> or withdraw <key>.', 2);
73
+ }
74
+ const tty = !options.json && Boolean(process.stdin.isTTY && process.stderr.isTTY);
75
+ const scope = await selectedWorld({ ...options, interactive: tty });
76
+ const auth = { actor: scope.owner, workspace: scope.workspace, signal: options.signal };
77
+ const identity = { actor: scope.owner, workspace: scope.workspace, world: scope.world };
78
+ const prefix = `${scope.prefix}/runtime-sessions`;
79
+ function session(row) {
80
+ if (!worldSessionMatches(row, identity))
81
+ throw new CliError('world_session_receipt_invalid', 'The session reference could not be confirmed. No replacement was started.');
82
+ output('world_session', row, description(row));
83
+ }
84
+ async function lookup(key) {
85
+ const receipt = await api(`${scope.prefix}/runtime-session-request?request_key=${identifier(key)}`, auth);
86
+ if (!worldSessionRequestMatches(receipt, identity, key)) {
87
+ throw new CliError('world_session_receipt_invalid', 'The original session request could not be confirmed. It was not resubmitted.');
88
+ }
89
+ output('world_session_request', receipt, `Request: ${key}\n${receipt.state}`
90
+ + (receipt.state === 'not_observed' ? '\nA delayed original request may still arrive. Inspect or withdraw this exact key; do not submit another opening.' : ''));
91
+ if (receipt.session)
92
+ session(receipt.session);
93
+ return receipt;
94
+ }
95
+ async function approve(label, detail) {
96
+ if (options.run)
97
+ return;
98
+ if (!tty)
99
+ throw new CliError('world_session_consent_required', 'Use --run to approve this exact world-session action in automation.', 2);
100
+ if (!await selectOne(label, [{ value: false, label: 'Cancel' }, { value: true, label: 'Continue', detail }], options.signal)) {
101
+ throw new CliError('canceled', 'No world-session action was submitted.', 130);
102
+ }
103
+ }
104
+ if (action === 'list') {
105
+ const result = await api(`${prefix}?page=${options.page ?? '1'}`, auth);
106
+ if (!worldSessionsPageMatches(result, identity))
107
+ throw new CliError('world_session_receipt_invalid', 'Session history could not be confirmed in this world.');
108
+ const continuation = `firedrill sessions list --world ${scope.world} --workspace ${scope.workspace} --page ${result.page + 1}${options.json ? ' --json' : ''}`;
109
+ output('world_sessions', result, (result.results.map(description).join('\n\n') || 'No world sessions have been opened.')
110
+ + `\nPage ${result.page}, ${result.count} matching sessions.${result.next ? '\nContinue: ' + continuation : ''}`);
111
+ return;
112
+ }
113
+ if (action === 'show') {
114
+ const id = identifier(args[1]), row = await api(`${prefix}/${id}`, auth);
115
+ if (!worldSessionMatches(row, identity, { id }))
116
+ throw new CliError('world_session_receipt_invalid', 'The selected session could not be confirmed.');
117
+ session(row);
118
+ return;
119
+ }
120
+ if (action === 'request') {
121
+ await lookup(identifier(args[1]));
122
+ return;
123
+ }
124
+ if (action === 'withdraw') {
125
+ const key = identifier(args[1]);
126
+ await approve('Withdraw this original opening?', 'A queued opening is fenced. An accepted session is requested to close; cleanup is separate.');
127
+ const saved = await api(`${scope.prefix}/runtime-session-request`, { ...auth, method: 'POST', body: { request_key: key } });
128
+ if (!worldSessionRequestMatches(saved, identity, key) || !saved.withdrawn)
129
+ throw new CliError('world_session_receipt_invalid', 'The original withdrawal could not be confirmed. Inspect that request before another action.');
130
+ const original = await lookup(key);
131
+ if (!original.withdrawn)
132
+ throw new CliError('world_session_receipt_invalid', 'Withdrawal is not confirmed. No replacement was submitted.');
133
+ return;
134
+ }
135
+ if (action === 'close') {
136
+ const key = identifier(args[1]), row = await api(`${prefix}/${key}`, auth);
137
+ if (!worldSessionMatches(row, identity, { id: key }))
138
+ throw new CliError('world_session_receipt_invalid', 'Choose a session in this world.');
139
+ if (row.terminal_at) {
140
+ session(row);
141
+ return;
142
+ }
143
+ await approve('Close this world session?', 'Stops controlled running state. Retained evidence remains; this cannot undo external effects.');
144
+ const closing = await api(`${prefix}/${key}`, { ...auth, method: 'DELETE' });
145
+ if (!worldSessionMatches(closing, identity, { id: key }) || !closing.cancel_requested && !closing.terminal_at)
146
+ throw new CliError('world_session_receipt_invalid', 'Closing is not confirmed. Inspect this same session before another action.');
147
+ session(closing);
148
+ return;
149
+ }
150
+ const current = await api(`${prefix}?state=active&page_size=1`, auth);
151
+ if (!worldSessionsPageMatches(current, identity, 'active'))
152
+ throw new CliError('world_session_scope_invalid', 'The current opening scope could not be confirmed. No session was queued.', 2);
153
+ if (current.opening_restriction) {
154
+ throw new CliError('world_session_opening_restricted', terminalText(current.opening_restriction), 2);
155
+ }
156
+ const opening = current.opening_scope;
157
+ if (!worldSessionOpeningScopeMatches(opening)) {
158
+ throw new CliError('world_session_scope_invalid', 'The current source and configuration could not be confirmed. No session was queued.', 2);
159
+ }
160
+ const checkpoint = options.checkpoint === undefined ? null : identifier(options.checkpoint);
161
+ const application = requestedAction === 'resume' ? await api(`${scope.prefix}/application-checkpoints/${identifier(args[1])}`, auth) : null;
162
+ if (application && (!application.available || checkpoint))
163
+ throw new CliError('application_checkpoint_incompatible', 'Choose available source-owned state without another checkpoint.', 2);
164
+ if (checkpoint && (options.dataset !== undefined || options.shareDatasetWithDevice !== undefined)) {
165
+ throw new CliError('arguments_invalid', 'Choose a checkpoint or saved starting-data version, not both.', 2);
166
+ }
167
+ if (!checkpoint && current.primary_active)
168
+ throw new CliError('world_session_exists', 'Inspect or close the original session before opening another. No replacement was started.', 2);
169
+ const selected = await execution(scope, 'world_session', options, output);
170
+ if (application) {
171
+ output('application_resume_review', application, `Application state: ${application.components.map(one => one.name).join(', ')}\nNot included: ${application.excluded.join(', ')}. Controlled services use the separately selected starting data.`);
172
+ if (!options.acknowledgePartialState && !(tty && await selectOne('Continue with this declared state coverage in a new process?', [{ value: false, label: 'Cancel' }, { value: true, label: 'Continue from saved application state' }], options.signal)))
173
+ throw new CliError('application_checkpoint_consent_required', 'Approve the exact coverage with --acknowledge-partial-state. This is not heap or socket recovery.', 2);
174
+ }
175
+ const dataset = checkpoint ? null : await startingData(scope, { ...opening, agent_comparison: null }, options);
176
+ let shareDataset = options.shareDatasetWithDevice === true;
177
+ if (selected.location === 'local' && dataset) {
178
+ if (!shareDataset && tty)
179
+ shareDataset = await selectOne('Download this starting data to the selected device?', [
180
+ { value: false, label: 'Cancel' }, { value: true, label: 'Download the selected data',
181
+ detail: 'Use a private runner cache outside the project. Firedrill cannot remotely erase device copies or backups.' }
182
+ ], options.signal);
183
+ if (!shareDataset)
184
+ throw new CliError('local_dataset_consent_required', 'Approve this download interactively or use --share-dataset-with-device. No session was admitted.', 2);
185
+ }
186
+ else if (shareDataset)
187
+ throw new CliError('local_dataset_consent_invalid', 'Use --share-dataset-with-device only with saved data and local execution.', 2);
188
+ let shareCheckpoint = options.shareCheckpointWithDevice === true;
189
+ if (selected.location === 'local' && (checkpoint || application)) {
190
+ if (!shareCheckpoint && tty)
191
+ shareCheckpoint = await selectOne('Download this checkpoint for a separate local session?', [
192
+ { value: false, label: 'Cancel' }, { value: true, label: 'Download the selected checkpoint',
193
+ detail: 'The original session stays unchanged. Private runner cache is outside the project; device copies and backups cannot be erased remotely.' }
194
+ ], options.signal);
195
+ if (!shareCheckpoint)
196
+ throw new CliError('local_checkpoint_consent_required', 'Approve the download interactively or use --share-checkpoint-with-device. No session was admitted.', 2);
197
+ }
198
+ else if (shareCheckpoint)
199
+ throw new CliError('local_checkpoint_consent_invalid', 'Use --share-checkpoint-with-device only with a selected checkpoint and local execution.', 2);
200
+ const seconds = options.seconds ?? String(opening.duration_seconds);
201
+ if (!/^[1-9][0-9]*$/.test(seconds) || !Number.isSafeInteger(Number(seconds)) || Number(seconds) > opening.max_duration_seconds) {
202
+ throw new CliError('world_session_duration_invalid', 'Use a positive whole number of seconds within runtime capacity.', 2);
203
+ }
204
+ if (selected.location === 'local' && !selected.grant.document.consent.evidence_upload) {
205
+ throw new CliError('local_world_session_permission_required', 'Authorize world_session with evidence upload before opening locally.', 2);
206
+ }
207
+ let realConsent;
208
+ if (opening.real_connections_count > 0) {
209
+ if (selected.location === 'local' && selected.grant.document.consent.real_account_access !== true) {
210
+ throw new CliError('local_grant_consent_required', 'Authorize the reviewed real accounts for this device first. No session was admitted.', 2);
211
+ }
212
+ let effects = options.approveRealSession === true, shared = options.acknowledgeSharedState === true;
213
+ if (!effects && tty) {
214
+ effects = await selectOne('Allow real-account requests in this session?', [
215
+ { value: false, label: 'Cancel' }, { value: true, label: 'Allow reviewed operations',
216
+ detail: `${opening.real_connections_count} connections, configuration ${opening.configuration_revision}, ${seconds} seconds. Includes startup, background work and explicit commands.` }
217
+ ], options.signal);
218
+ if (!effects)
219
+ throw new CliError('canceled', 'No world session was submitted.', 130);
220
+ }
221
+ if (!shared && tty) {
222
+ shared = await selectOne('Accept shared external state?', [
223
+ { value: false, label: 'Cancel' }, { value: true, label: 'Accept shared state',
224
+ detail: 'Reset, restore, closing and forks cannot undo or isolate real-account changes. Restarting the agent can make real requests.' }
225
+ ], options.signal);
226
+ if (!shared)
227
+ throw new CliError('canceled', 'No world session was submitted.', 130);
228
+ }
229
+ if (!effects || !shared)
230
+ throw new CliError('world_session_real_consent_required', 'Approve session operations and shared state interactively, or use --approve-real-session --acknowledge-shared-state. Startup, simulation and --run approvals do not authorize this session.', 2);
231
+ realConsent = { actor_id: scope.owner, acknowledge_effects: true, acknowledge_shared_state: true };
232
+ }
233
+ else if (options.approveRealSession !== undefined || options.acknowledgeSharedState !== undefined) {
234
+ throw new CliError('world_session_real_consent_invalid', 'This world has no real-account connection. Remove the real-session approval options.', 2);
235
+ }
236
+ output('world_session_review', { world_id: scope.world, execution_location: selected.location, duration_seconds: Number(seconds),
237
+ checkpoint_id: checkpoint, real_connections_count: opening.real_connections_count }, `${selected.location === 'local' ? 'This device' : 'Cloud'} · ${seconds} seconds · ${checkpoint ? 'checkpoint ' + checkpoint : dataset ? ('name' in dataset && typeof dataset.name === 'string' ? terminalText(dataset.name) : 'Saved starting data') : 'prepared data'}\n`
238
+ + (selected.location === 'local' ? 'Keep this terminal open. Saved evidence is uploaded with customer-device provenance.' : 'The prepared agent starts in isolated compute.')
239
+ + (realConsent ? '\nReviewed real-account operations remain brokered. External state is shared and cannot be reset.' : ''));
240
+ await approve('Open this world session?', selected.location === 'local' ? 'Open the original local assignment and keep this terminal running.' : 'Queue one managed world session.');
241
+ if (selected.location === 'managed' && options.usageQuote && !options.request)
242
+ throw new CliError('billing_quote_required', 'Use the original quote launch key with --request.', 2);
243
+ const key = identifier(options.request ?? randomUUID());
244
+ const quote = selected.location === 'managed' ? await worldAllocationApproval(scope, options, {
245
+ world_revision: opening.world_revision, configuration_revision: opening.configuration_revision, duration_seconds: Number(seconds),
246
+ dataset_id: dataset?.id ?? null, checkpoint_id: checkpoint, launch_request_key: key,
247
+ ...(application ? { application_checkpoint_id: application.id } : {})
248
+ }, output) : null;
249
+ const body = { world_revision: opening.world_revision, configuration_revision: opening.configuration_revision,
250
+ source_id: identifier(opening.source_id), source_revision: opening.source_revision, duration_seconds: Number(seconds),
251
+ ...(quote ? { usage_quote_id: quote } : {}),
252
+ ...(application ? { application_checkpoint_id: application.id, acknowledge_partial_state: true } : {}),
253
+ ...(dataset ? { dataset_id: dataset.id } : {}), ...(shareDataset ? { share_dataset_with_device: true } : {}),
254
+ ...(checkpoint ? { checkpoint_id: checkpoint } : {}), ...(shareCheckpoint ? { share_checkpoint_with_device: true } : {}),
255
+ ...(realConsent ? { real_account_consent: realConsent } : {}),
256
+ execution: selected.location === 'local' ? { location: 'local', grant_id: selected.grant.id } : { location: 'managed' } };
257
+ output('world_session_opening', { request_key: key, world_id: scope.world }, `Request: ${key}\nIf interrupted, use sessions request ${key}. Withdraw this same key instead of submitting a replacement.`);
258
+ let row;
259
+ try {
260
+ row = await api(prefix, { ...auth, method: 'POST', idempotencyKey: key, body });
261
+ }
262
+ catch (error) {
263
+ if (options.signal.aborted)
264
+ throw error;
265
+ const receipt = await lookup(key);
266
+ if (!receipt.session || receipt.withdrawn)
267
+ throw new CliError('world_session_acceptance_unconfirmed', `Inspect sessions request ${key}. The original opening was not resubmitted.`, 2);
268
+ row = receipt.session;
269
+ }
270
+ if (!worldSessionMatches(row, identity, { key, body }) || row.world_revision !== opening.world_revision || row.configuration_revision !== opening.configuration_revision
271
+ || row.dataset_id !== (dataset?.id ?? null) || (row.fork?.checkpoint_id ?? null) !== checkpoint
272
+ || (row.external_effects?.real_connections_count ?? 0) !== opening.real_connections_count
273
+ || row.execution_location !== selected.location || selected.location === 'local'
274
+ && (row.local_execution?.grant_id !== selected.grant.id || row.local_execution?.device_key !== selected.device)) {
275
+ throw new CliError('world_session_acceptance_unconfirmed', `Inspect sessions request ${key}. The returned identity does not match the reviewed opening.`, 2);
276
+ }
277
+ const acceptance = await lookup(key);
278
+ if (acceptance.withdrawn || !acceptance.session || acceptance.session.id !== row.id
279
+ || !worldSessionMatches(acceptance.session, identity, { key, body })) {
280
+ throw new CliError('world_session_acceptance_unconfirmed', `Inspect sessions request ${key}. The original acceptance could not be confirmed. No replacement was started.`, 2);
281
+ }
282
+ row = acceptance.session;
283
+ session(row);
284
+ if (selected.location === 'local' && row.local_execution?.awaiting_runner && !row.cancel_requested) {
285
+ await executeAssignment(selected.device, identifier(row.local_execution.job_id), { ...options, world: scope.world,
286
+ workspace: scope.workspace, grant: selected.request, approve: true }, output);
287
+ }
288
+ }
@@ -0,0 +1,99 @@
1
+ import { setTimeout as sleep } from 'node:timers/promises';
2
+ import { access } from './auth.js';
3
+ import { CliError, endpoint, responseError } from './http.js';
4
+ import { notificationCursor, readWorldNotification, worldStreamPath } from '../../contracts/world-stream-contracts.js';
5
+ function position(value) {
6
+ const parsed = notificationCursor(value);
7
+ if (parsed === null) {
8
+ throw new CliError('stream_position_invalid', 'Reopen this world to reconnect its updates.');
9
+ }
10
+ return parsed;
11
+ }
12
+ // A cursor is acknowledged only after the authorized snapshot was read. A
13
+ // reconnect reads retained notifications, never replays a user command.
14
+ export async function watchWorld(context, initial, signal, changed, disconnected) {
15
+ let cursor = initial, failures = 0;
16
+ position(cursor);
17
+ while (!signal.aborted) {
18
+ let reader;
19
+ try {
20
+ // Serialize refresh, not the 55-second stream. Tokens stay in memory
21
+ // and headers; no cookies, redirects, query credentials or transcript log.
22
+ const token = await access(async (value) => value);
23
+ signal.throwIfAborted();
24
+ const response = await fetch(endpoint(worldStreamPath(context.workspace, context.world)), { redirect: 'error', credentials: 'omit',
25
+ headers: { Authorization: `Bearer ${token}`, Accept: 'text/event-stream',
26
+ 'X-Firedrill-Actor': context.owner, 'X-Firedrill-Workspace': context.workspace, 'Last-Event-ID': cursor },
27
+ signal: AbortSignal.any([signal, AbortSignal.timeout(65000)]) });
28
+ if (!response.ok) {
29
+ await response.body?.cancel();
30
+ throw responseError(null, response.status);
31
+ }
32
+ if (!response.headers.get('Content-Type')?.startsWith('text/event-stream') || !response.body) {
33
+ await response.body?.cancel();
34
+ throw new CliError('stream_unavailable', 'Live updates are unavailable. Reopen this world to reconnect.');
35
+ }
36
+ reader = response.body.getReader();
37
+ const decoder = new TextDecoder('utf-8', { fatal: true });
38
+ let buffer = '', received = false;
39
+ while (!signal.aborted) {
40
+ const part = await reader.read();
41
+ if (part.done)
42
+ break;
43
+ buffer += decoder.decode(part.value, { stream: true }).replace(/\r\n/g, '\n');
44
+ if (buffer.length > 65536)
45
+ throw new CliError('stream_invalid', 'Live updates could not be read safely.');
46
+ let boundary;
47
+ while ((boundary = buffer.indexOf('\n\n')) !== -1) {
48
+ const frame = buffer.slice(0, boundary);
49
+ buffer = buffer.slice(boundary + 2);
50
+ if (frame.length > 16384)
51
+ throw new CliError('stream_invalid', 'Live updates could not be read safely.');
52
+ let event = '', id = '', body = '';
53
+ for (const line of frame.split('\n')) {
54
+ if (line.startsWith('event:'))
55
+ event = line.slice(6).trim();
56
+ else if (line.startsWith('id:'))
57
+ id = line.slice(3).trim();
58
+ else if (line.startsWith('data:'))
59
+ body += (body ? '\n' : '') + line.slice(5).trimStart();
60
+ }
61
+ if (event === 'access_changed') {
62
+ // The API can close at JWT expiry. A fresh authorized read either
63
+ // refreshes that same identity or refuses revoked access.
64
+ await changed();
65
+ received = true;
66
+ break;
67
+ }
68
+ if (event === 'changed' || event === 'snapshot') {
69
+ const value = readWorldNotification(body, context.workspace, context.world, event, id);
70
+ if (!value)
71
+ throw new CliError('stream_invalid', 'Live update identity could not be confirmed. No user action was repeated.');
72
+ if (position(value.sequence) > position(cursor) || event === 'snapshot' && position(value.sequence) === position(cursor)) {
73
+ await changed();
74
+ cursor = value.sequence;
75
+ received = true;
76
+ }
77
+ }
78
+ }
79
+ }
80
+ if (received)
81
+ failures = 0;
82
+ }
83
+ catch (error) {
84
+ if (signal.aborted)
85
+ return;
86
+ const problem = error instanceof CliError ? error : new CliError('stream_disconnected', 'Live updates disconnected. Saved work has not been canceled.');
87
+ if (problem.exitCode === 2 || problem.exitCode === 3 || ++failures >= 3) {
88
+ disconnected(problem);
89
+ return;
90
+ }
91
+ }
92
+ finally {
93
+ await reader?.cancel().catch(() => undefined);
94
+ reader?.releaseLock();
95
+ }
96
+ if (!signal.aborted)
97
+ await sleep(Math.min(8000, 1000 * 2 ** failures), undefined, { signal }).catch(() => undefined);
98
+ }
99
+ }