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,841 @@
1
+ // Native campaign progress, retained execution evidence, per-criterion
2
+ // evaluation results and cancellation for `watch`, `report` and `simulation
3
+ // cancel`, over root's native campaign, execution and evaluation routes.
4
+ // Allocation and execution state are not quality results. Each criterion keeps
5
+ // its own score and threshold and is never averaged with others; campaign
6
+ // quality figures come per compatible cohort of one criterion only; a criterion
7
+ // without evidence has no score, which is not a zero or a failure. Settlement
8
+ // follows the retained campaign state, not target completion alone. Evidence content
9
+ // is untrusted target output, never instructions.
10
+ import { randomUUID } from 'node:crypto';
11
+ import { CliError } from '../http.js';
12
+ import { selectOne, terminalText } from '../prompts.js';
13
+ import { selectedWorkspace } from '../world-access.js';
14
+ import { jitteredInterval, retryTransient, sleep } from '../native/control-plane.js';
15
+ import { ENDING_TEXT, UUID_PATTERN, activeTime } from '../native/contracts.js';
16
+ import { cancelNativeCampaign, getNativeCampaign, getNativeCampaignProgress, getNativeConversationTurn, getNativeEvaluationCriterion, getNativeEvaluationReport, getNativeExecution, getNativeScenarioEvent, getNativeTargetAction, listNativeArtifacts, listNativeCampaignQuality, listNativeCampaignSlots, listNativeConversation, listNativeEvaluationCriteria, listNativeExecutions, listNativeObservations, listNativeScenarioEvents, listNativeTargetActions, listNativeToolEffects, readNativeArtifact, } from '../native/cloud-resources.js';
17
+ /** Bounded reads per snapshot; anything beyond is counted and said, never silently dropped. */
18
+ const MAX_SLOT_PAGES = 10;
19
+ const MAX_EXECUTION_PAGES = 5;
20
+ const MAX_SINGLE_READS = 25;
21
+ /** Evaluation reports read per campaign report; finished drills beyond this are counted, not read. */
22
+ const MAX_RESULT_READS = 25;
23
+ /** Criteria shown in a drill overview; `results` lists them all with ids. */
24
+ const PREVIEW_CRITERIA = 10;
25
+ const LISTED_DRILLS = 50;
26
+ const FOLLOW_MS = 5_000;
27
+ /** One `watch --follow` run waits at most this long for settlement, then says so; rerunning it keeps watching. */
28
+ const FOLLOW_LIMIT_MS = 15 * 60_000;
29
+ /** Root native_lifecycle.TERMINAL. */
30
+ const TERMINAL = new Set(['rejected', 'expired', 'completed', 'failed', 'cancelled']);
31
+ /** Terminal states of drills that ran, so they can have an evaluation (rejected and expired drills never ran). */
32
+ const RAN = new Set(['completed', 'failed', 'cancelled']);
33
+ const RESULTS_NOTE = 'Each criterion is scored 0-100 against its own threshold. Campaign figures are per criterion and per compatible '
34
+ + 'cohort, never combined across criteria or cohorts into one score. A criterion that could not be assessed has no score: that is '
35
+ + 'missing evidence, not a zero or a failure.';
36
+ /** Characters of a cohort digest shown to tell a criterion's cohorts apart. */
37
+ const COHORT_LABEL = 12;
38
+ /** Root settles every funding item before it moves a campaign to one of these final states. */
39
+ const CAMPAIGN_SETTLED = new Set(['completed', 'failed', 'cancelled']);
40
+ /** Root native_campaign_lifecycle.CANCELLING: an accepted cancellation stops allocation, so unallocated drills never run. */
41
+ const CANCELLING = new Set(['cancel_requested', 'draining', 'cancel_reconciling']);
42
+ /** Settlement as root's campaign state shows it: final means settled; otherwise it is still pending. */
43
+ function settlementOf(campaign) {
44
+ return CAMPAIGN_SETTLED.has(campaign.state) ? { state: 'settled', campaign_state: campaign.state } : { state: 'pending' };
45
+ }
46
+ function settlementText(campaign) {
47
+ return CAMPAIGN_SETTLED.has(campaign.state) ? `Settlement: settled (the campaign ended ${campaign.state}).`
48
+ : 'Settlement: pending until every drill has ended and its funding is settled.';
49
+ }
50
+ const resultsNote = (campaign) => `${RESULTS_NOTE} ${settlementText(campaign)}`;
51
+ /** Why root says a drill's input was only partly covered, in plain words. */
52
+ function coverageLine(report) {
53
+ if (report.execution_coverage === null)
54
+ return null;
55
+ const ending = report.ending_reason ? `input ended because ${ENDING_TEXT[report.ending_reason]}` : 'the input had not ended when the drill stopped';
56
+ return report.execution_coverage === 'complete' ? `Coverage: complete (${ending})`
57
+ : `Coverage: partial (${ending}); this is why results need attention even when every criterion was met`;
58
+ }
59
+ const RESULTS_UNAVAILABLE = { state: 'unavailable', code: 'native_evaluation_report_unavailable' };
60
+ const COVERAGE_TEXT = {
61
+ available: 'available', partial: 'Partially available', not_assessable: 'not assessable', metadata_only: 'metadata only',
62
+ revision_boundaries_only: 'revision boundaries only', not_selected: 'not selected',
63
+ };
64
+ const CRITERION_TEXT = {
65
+ pending: 'pending', evaluating: 'evaluating', recovering: 'confirming result', not_assessable: 'not assessable, no score',
66
+ error: 'could not be evaluated, no score',
67
+ };
68
+ /** Root target-action states for a turn; an unknown state is shown as given. */
69
+ const TURN_TEXT = {
70
+ offered: 'offered, not sent to the agent', claimed: 'sent, no reply recorded yet', uncertain: 'sent, outcome unknown',
71
+ completed: 'completed', cancelled: 'withdrawn before sending',
72
+ };
73
+ const isUnavailable = (error) => error instanceof CliError && error.code === 'native_evaluation_report_unavailable';
74
+ const points = (value) => String(Math.round(value * 10) / 10);
75
+ const isId = (value) => value !== undefined && UUID_PATTERN.test(value.toLowerCase());
76
+ export const scopeOf = (options) => selectedWorkspace({ workspace: options.workspace, signal: options.signal,
77
+ interactive: !options.json && !!process.stdin.isTTY && !!process.stderr.isTTY, openBrowser: options.openBrowser && !options.json });
78
+ function pageNumber(value) {
79
+ if (value === undefined)
80
+ return undefined;
81
+ if (!/^[1-9][0-9]{0,8}$/.test(value))
82
+ throw new CliError('arguments_invalid', 'Use --page with a number of 1 or more.', 2);
83
+ return Number(value);
84
+ }
85
+ /** The native campaign, or null when the id is not a native campaign in this workspace (the caller falls back). */
86
+ export async function nativeCampaign(scope, campaignId, signal) {
87
+ try {
88
+ return await getNativeCampaign(scope, { campaignId, signal });
89
+ }
90
+ catch (error) {
91
+ if (error instanceof CliError && error.httpStatus === 404)
92
+ return null;
93
+ throw error;
94
+ }
95
+ }
96
+ /** Root answered that this campaign-level read is not available (404 route or 409 progress), as opposed to failing. */
97
+ function unread(error) {
98
+ return error instanceof CliError && (error.httpStatus === 404 || error.httpStatus === 409) ? { state: 'unavailable', code: error.code } : null;
99
+ }
100
+ async function snapshot(scope, campaign, signal) {
101
+ // Root's own count over every drill, so the summary is exact however many drills the listing below reaches.
102
+ let progress;
103
+ try {
104
+ progress = { state: 'read', report: await getNativeCampaignProgress(scope, { campaignId: campaign.campaign_id, signal }) };
105
+ }
106
+ catch (error) {
107
+ const gap = unread(error);
108
+ if (!gap)
109
+ throw error;
110
+ progress = gap;
111
+ }
112
+ const slots = [];
113
+ let total = 0;
114
+ for (let page = 1; page <= MAX_SLOT_PAGES; page++) {
115
+ const result = await listNativeCampaignSlots(scope, { campaignId: campaign.campaign_id, page, signal });
116
+ slots.push(...result.results);
117
+ total = result.count;
118
+ if (!result.next)
119
+ break;
120
+ }
121
+ // Execution states for this world revision, newest first; one list instead of a read per slot,
122
+ // then a bounded number of single reads for drills the recent list did not reach.
123
+ const executions = {};
124
+ const wanted = new Set(slots.map(slot => slot.assignment_id).filter((value) => value !== null));
125
+ const missing = () => [...wanted].filter(id => !(id in executions));
126
+ for (let page = 1; page <= MAX_EXECUTION_PAGES && missing().length; page++) {
127
+ const result = await listNativeExecutions(scope, { worldRevisionId: campaign.world_revision_id, page, signal });
128
+ for (const row of result.results)
129
+ if (wanted.has(row.assignment_id))
130
+ executions[row.assignment_id] = row.state;
131
+ if (!result.next)
132
+ break;
133
+ }
134
+ for (const assignmentId of missing().slice(0, MAX_SINGLE_READS)) {
135
+ executions[assignmentId] = (await getNativeExecution(scope, { assignmentId, signal })).state;
136
+ }
137
+ return { campaign, progress, slots, slots_omitted: Math.max(0, total - slots.length), executions, executions_unknown: missing().length };
138
+ }
139
+ /** One page of the campaign's per-criterion quality cohorts, or why it cannot be read. */
140
+ async function qualityOf(scope, campaignId, page, signal) {
141
+ try {
142
+ return { state: 'read', cohorts: await listNativeCampaignQuality(scope, { campaignId, page, signal }) };
143
+ }
144
+ catch (error) {
145
+ if (error instanceof CliError && error.httpStatus === 404 && page !== undefined && page > 1) {
146
+ throw new CliError('native_page_invalid', `There is no quality page ${page} for this campaign.`, 2);
147
+ }
148
+ const gap = unread(error);
149
+ if (!gap)
150
+ throw error;
151
+ return gap;
152
+ }
153
+ }
154
+ /**
155
+ * One line per compatible cohort. A criterion listed with more than one cohort
156
+ * changed between drills (rubric, evaluator model, threshold, descriptor or world
157
+ * revision); each cohort is labelled and kept apart.
158
+ */
159
+ function qualityLines(quality, campaignId) {
160
+ if (quality.state !== 'read')
161
+ return [`Quality by criterion: unavailable (${quality.code})`];
162
+ const { results, count } = quality.cohorts;
163
+ if (!count)
164
+ return ['Quality by criterion: no evaluated drills yet.'];
165
+ const perCriterion = new Map();
166
+ for (const one of results)
167
+ perCriterion.set(one.criterion_id, (perCriterion.get(one.criterion_id) ?? 0) + 1);
168
+ const split = results.some(one => (perCriterion.get(one.criterion_id) ?? 0) > 1);
169
+ const shownTo = ((quality.cohorts.page ?? 1) - 1) * 25 + results.length;
170
+ return [
171
+ 'Quality by criterion across drills (each compatible cohort on its own):',
172
+ ...results.map(one => {
173
+ const scored = one.assessed && one.average_quality !== null
174
+ ? `average ${points(one.average_quality)} (lowest ${points(one.minimum_quality)}, highest ${points(one.maximum_quality)}) over ${one.assessed} scored drills; threshold ${points(one.threshold)}: ${one.threshold_met} met, ${one.below_threshold} below threshold`
175
+ : `no score yet; threshold ${points(one.threshold)}`;
176
+ const missing = one.not_assessable ? `; ${one.not_assessable} not assessable (missing evidence, no score)` : '';
177
+ const cohort = (perCriterion.get(one.criterion_id) ?? 0) > 1 ? ` cohort ${one.cohort_digest.slice(0, COHORT_LABEL)}` : '';
178
+ return ` ${one.criterion_name} ${scored}${missing}${cohort}`;
179
+ }),
180
+ split ? 'A criterion listed more than once changed between drills; its cohorts are not comparable and are never combined.' : null,
181
+ count > shownTo ? `${count - shownTo} more cohorts: firedrill report ${campaignId} quality --page ${(quality.cohorts.page ?? 1) + 1}` : null,
182
+ ].filter((line) => line !== null);
183
+ }
184
+ /**
185
+ * A drill's evaluation report, or why it cannot be shown: root's 409 means the
186
+ * results or their evidence are no longer retained, and its 404 means this one
187
+ * drill stopped being readable here (for example its world was deleted while
188
+ * the report was read). Neither affects other drills.
189
+ */
190
+ async function evaluationOf(scope, assignmentId, signal) {
191
+ try {
192
+ return { state: 'read', report: await getNativeEvaluationReport(scope, { assignmentId, signal }) };
193
+ }
194
+ catch (error) {
195
+ if (isUnavailable(error))
196
+ return RESULTS_UNAVAILABLE;
197
+ if (error instanceof CliError && error.httpStatus === 404)
198
+ return { state: 'unavailable', code: error.code };
199
+ throw error;
200
+ }
201
+ }
202
+ /** The evaluation report and one page of criterion results (none before the evaluation basis is prepared). */
203
+ async function readResults(scope, assignmentId, page, signal) {
204
+ const evaluation = await evaluationOf(scope, assignmentId, signal);
205
+ if (evaluation.state !== 'read' || !evaluation.report.criteria_total) {
206
+ if (page !== undefined && page > 1)
207
+ throw new CliError('native_page_invalid', 'This drill has no further criteria pages.', 2);
208
+ return { evaluation, criteria: null };
209
+ }
210
+ try {
211
+ return { evaluation, criteria: await listNativeEvaluationCriteria(scope, { assignmentId, page, signal }) };
212
+ }
213
+ catch (error) {
214
+ if (isUnavailable(error))
215
+ return { evaluation: RESULTS_UNAVAILABLE, criteria: null };
216
+ // The report answered for this drill, so a missing page is out of range.
217
+ if (error instanceof CliError && error.httpStatus === 404 && page !== undefined && page > 1) {
218
+ throw new CliError('native_page_invalid', `There is no criteria page ${page} for this drill.`, 2);
219
+ }
220
+ throw error;
221
+ }
222
+ }
223
+ /** Evaluation reports for drills that ran, in slot order, bounded; the rest are counted. */
224
+ async function campaignResults(scope, view, signal) {
225
+ const evaluations = {};
226
+ let unread = 0;
227
+ let read = 0;
228
+ for (const slot of view.slots) {
229
+ const assignmentId = slot.assignment_id;
230
+ if (assignmentId === null || !RAN.has(view.executions[assignmentId] ?? ''))
231
+ continue;
232
+ if (read >= MAX_RESULT_READS) {
233
+ unread++;
234
+ continue;
235
+ }
236
+ read++;
237
+ evaluations[assignmentId] = await evaluationOf(scope, assignmentId, signal);
238
+ }
239
+ return { evaluations, unread };
240
+ }
241
+ function tally(values) {
242
+ const counts = new Map();
243
+ for (const value of values)
244
+ counts.set(value, (counts.get(value) ?? 0) + 1);
245
+ return [...counts].map(([name, value]) => `${value} ${name}`).join(', ') || 'none';
246
+ }
247
+ function gapText(gap) {
248
+ return gap.code === RESULTS_UNAVAILABLE.code
249
+ ? `unavailable: the results or their retained evidence are no longer available (${gap.code})`
250
+ : `unavailable: this drill can no longer be read in this workspace (${gap.code})`;
251
+ }
252
+ /** Non-zero criterion counts, e.g. "3 met, 1 below threshold, 1 not assessable". */
253
+ function counts(report) {
254
+ return [[report.criteria_met, 'met'], [report.below_threshold, 'below threshold'], [report.not_assessable, 'not assessable'],
255
+ [report.errors, 'could not be evaluated'], [report.pending, 'pending']]
256
+ .filter(([value]) => value).map(([value, name]) => `${value} ${name}`).join(', ');
257
+ }
258
+ function summary(report) {
259
+ const total = report.criteria_total ?? 0;
260
+ if (report.criteria_total === null && report.status === 'needs_attention') {
261
+ return 'needs attention: not evaluated, because the drill ended through cleanup without a normal close (its evidence is incomplete)';
262
+ }
263
+ switch (report.status) {
264
+ case 'pending': return 'not started yet';
265
+ case 'preparing': return 'preparing the evaluation';
266
+ case 'evaluating': return `evaluating ${total} criteria: ${counts(report)}`;
267
+ case 'complete': return total ? `complete: ${report.criteria_met} of ${total} criteria met` : 'complete: this drill has no criteria';
268
+ case 'needs_attention': return `needs attention (${total} criteria): ${counts(report)}`;
269
+ }
270
+ }
271
+ /** Short per-drill label for campaign lists. */
272
+ function resultLabel(view) {
273
+ if (view === undefined)
274
+ return '';
275
+ if (view.state !== 'read')
276
+ return ' results unavailable';
277
+ const { report } = view;
278
+ const total = report.criteria_total ?? 0;
279
+ switch (report.status) {
280
+ case 'pending': return ' results not started';
281
+ case 'preparing': return ' results preparing';
282
+ case 'evaluating': return ` evaluating ${total - report.pending}/${total}`;
283
+ case 'complete': return ` complete ${report.criteria_met}/${total} met`;
284
+ case 'needs_attention': return report.criteria_total === null ? ' needs attention (not evaluated)'
285
+ : ` needs attention ${report.criteria_met}/${total} met${report.execution_coverage === 'partial' ? ', partial coverage' : ''}`;
286
+ }
287
+ }
288
+ /** Exact checks summarized: they are declared facts over retained evidence, separate from the rubric score. */
289
+ function exactSummary(value, declared) {
290
+ if (value === true)
291
+ return 'exact checks passed';
292
+ if (value === false)
293
+ return 'an exact check failed';
294
+ return declared ? 'exact checks not all checkable (evidence incomplete)' : null;
295
+ }
296
+ export function criterionLine(one, withId) {
297
+ const outcome = one.status === 'assessed' && one.quality_score !== null
298
+ ? `${one.threshold_met ? 'met' : 'below threshold'}: ${points(one.quality_score)} (threshold ${points(one.threshold)})`
299
+ : `${CRITERION_TEXT[one.status]} (threshold ${points(one.threshold)})`;
300
+ const exact = exactSummary(one.exact_check, one.exact_checks.length > 0);
301
+ return ` ${one.dimension ? `[${one.dimension.replace(/_/g, ' ')}] ` : ''}${one.name} ${outcome}${exact ? `; ${exact}` : ''}`
302
+ + `${withId ? ` criterion ${one.criterion_id}` : ''}`;
303
+ }
304
+ const COMPARISON_TEXT = { eq: 'exactly', gte: 'at least', lte: 'at most' };
305
+ /** Detail lines for one criterion's v2 parts: exact checks, memory probes, style and company policies. */
306
+ function criterionDetail(one) {
307
+ const lines = [];
308
+ if (one.dimension)
309
+ lines.push(`Dimension: ${one.dimension.replace(/_/g, ' ')}`);
310
+ for (const check of one.exact_checks) {
311
+ const subject = check.service && check.operation ? `${terminalText(check.service)}.${terminalText(check.operation)}` : terminalText(check.source);
312
+ lines.push(`Exact check ${terminalText(check.key)}: ${subject} expected ${COMPARISON_TEXT[check.comparison]} ${check.expected}, `
313
+ + (check.observed === null ? `not checkable (${terminalText(check.reason ?? 'evidence incomplete')})`
314
+ : `observed ${check.observed}: ${check.passed ? 'passed' : 'failed'}`));
315
+ }
316
+ const probes = Array.isArray(one.memory?.probes) ? one.memory.probes : null;
317
+ if (probes) {
318
+ const available = probes.filter((probe) => probe?.coverage === 'available').length;
319
+ lines.push(`Memory probes: ${available} of ${probes.length} had the turns they need; the score says how the target recalled them (observed behavior, not proof).`);
320
+ }
321
+ if (one.style) {
322
+ const count = one.style.observed_count;
323
+ lines.push(`Style (em dashes in the target's prose): ${typeof count === 'number' ? `${count} observed` : 'not observable'}`
324
+ + `${typeof one.style.status === 'string' ? ` (${terminalText(one.style.status)})` : ''}.`);
325
+ }
326
+ for (const policy of one.company_policies) {
327
+ lines.push(`Company policy read by the evaluator only: ${terminalText(policy.name)} v${policy.version} (${policy.paragraphs.length} paragraph(s))`);
328
+ }
329
+ // One exact baseline record and one closing record per selection (not per-call differences).
330
+ for (const record of one.state_records) {
331
+ const { dependency, collection_key: collection, before_position: before, after_position: after } = record.selection;
332
+ lines.push(`State records ${terminalText(dependency)} collection ${collection.slice(0, 12)}, baseline position ${before}, closing position ${after}: `
333
+ + (record.coverage === 'available'
334
+ ? `read (baseline checkpoint ${record.before_checkpoint_id ?? 'none'}, closing checkpoint ${record.after_checkpoint_id ?? 'none'})`
335
+ : `not assessable (${terminalText(record.reason ?? 'state unavailable')})`));
336
+ }
337
+ return lines;
338
+ }
339
+ /** Results summary and evidence coverage lines for a drill. */
340
+ function resultsHead(evaluation) {
341
+ if (evaluation.state !== 'read')
342
+ return [`Results: ${gapText(evaluation)}`];
343
+ const coverage = Object.entries(evaluation.report.coverage);
344
+ const covered = coverageLine(evaluation.report);
345
+ const exact = evaluation.report.exact_check === null ? null
346
+ : `Exact checks: ${evaluation.report.exact_check ? 'every declared check passed' : 'at least one declared check failed'} (separate from the criterion scores)`;
347
+ return [
348
+ `Results: ${summary(evaluation.report)}`,
349
+ ...covered ? [covered] : [],
350
+ ...exact ? [exact] : [],
351
+ ...coverage.length ? [`Evidence for evaluation: ${coverage.map(([kind, value]) => `${kind.replace(/_/g, ' ')} ${COVERAGE_TEXT[value]}`).join(', ')}`] : [],
352
+ ];
353
+ }
354
+ function snapshotText(view, results) {
355
+ const { campaign } = view;
356
+ const allocated = view.slots.filter(slot => slot.assignment_id !== null);
357
+ const execution = (slot) => view.executions[slot.assignment_id] ?? 'unknown';
358
+ const evaluations = results ? Object.values(results.evaluations) : [];
359
+ const shape = campaign.scenario_count !== undefined
360
+ ? ` (${campaign.scenario_count} scenarios x ${campaign.repetitions} repetitions, up to ${campaign.max_parallel} in parallel, reset ${campaign.reset})` : '';
361
+ const progress = view.progress.state === 'read' ? view.progress.report : null;
362
+ return [
363
+ `Campaign ${campaign.campaign_id}: ${campaign.state}`,
364
+ ...progress ? [
365
+ `Drills: ${progress.allocated_drills} of ${progress.planned_drills} allocated${progress.unallocated_drills ? `, ${progress.unallocated_drills} not allocated yet` : ''}${shape}`,
366
+ // Root counts a drill under its execution state, or its slot state before it has one.
367
+ `Drill states: ${progress.execution_counts.map(row => `${row.count} ${row.state}`).join(', ') || 'none'}`,
368
+ ] : [
369
+ `Drills: ${campaign.allocated_drills} of ${campaign.planned_drills} allocated${shape}`,
370
+ `Slots: ${tally(view.slots.map(slot => slot.state))}`,
371
+ `Executions: ${tally(allocated.map(execution))}`,
372
+ view.progress.state === 'unavailable' ? `Campaign summary unavailable (${view.progress.code}); states above are from the drills read here.` : null,
373
+ ],
374
+ results ? `Results: ${tally(evaluations.map(one => one.state === 'read' ? one.report.status.replace('_', ' ') : 'unavailable'))}` : null,
375
+ ...allocated.slice(0, LISTED_DRILLS).map(slot => ` #${slot.position} rep ${slot.repetition} ${slot.state} drill ${slot.drill_id} ${execution(slot)}${resultLabel(results?.evaluations[slot.assignment_id])}`),
376
+ allocated.length > LISTED_DRILLS ? ` (${allocated.length - LISTED_DRILLS} more drills: use --json for the full list)` : null,
377
+ view.slots_omitted ? `${view.slots_omitted} more slots were not read in this snapshot.` : null,
378
+ view.executions_unknown ? `${view.executions_unknown} execution states were not read in this snapshot; shown as unknown.` : null,
379
+ results?.unread ? `${results.unread} finished drills' results were not read here; open one with firedrill report ${campaign.campaign_id} drill <drill-id>.` : null,
380
+ ...results?.quality ?? [],
381
+ results ? resultsNote(campaign)
382
+ : `Execution state only, not results. Per-criterion results: firedrill report ${campaign.campaign_id}. ${settlementText(campaign)}`,
383
+ ].filter((line) => line !== null).join('\n');
384
+ }
385
+ /**
386
+ * No drill is left to run, by root's exact progress counts (the slot and
387
+ * execution listings read here are bounded, so they cannot say this): every
388
+ * allocated drill is in a terminal state, and every planned drill was allocated
389
+ * unless a cancellation stopped allocation. Null when root gave no counts. This
390
+ * is not settlement: root settles the campaign later, once local processes,
391
+ * target actions, state and funding have closed.
392
+ */
393
+ function drillsFinished(view) {
394
+ if (view.progress.state !== 'read')
395
+ return null;
396
+ const { report } = view.progress;
397
+ const { state } = view.campaign;
398
+ return (report.unallocated_drills === 0 || CANCELLING.has(state) || state === 'cancelled')
399
+ && report.execution_counts.every(row => TERMINAL.has(row.state));
400
+ }
401
+ /**
402
+ * `watch <campaign> [--follow]` for a native campaign. Returns false when the
403
+ * id is not a native campaign here, so the caller uses the hosted reports.
404
+ * `watch_done.settled` always means root moved the campaign to its final state.
405
+ * Following polls about every five seconds, prints only changes, and stops when
406
+ * the campaign settles, after FOLLOW_LIMIT_MS (saying it has not settled yet and
407
+ * how to keep watching), or on Ctrl-C.
408
+ */
409
+ export async function nativeWatch(campaignId, options, emit) {
410
+ if (!isId(campaignId))
411
+ return false;
412
+ const scope = await scopeOf(options);
413
+ const first = await nativeCampaign(scope, campaignId, options.signal);
414
+ if (!first)
415
+ return false;
416
+ const read = (operation) => options.follow
417
+ ? retryTransient(operation, { signal: options.signal, attempts: 4 }) : operation();
418
+ const deadline = Date.now() + FOLLOW_LIMIT_MS;
419
+ let campaign = first;
420
+ let previous = '';
421
+ for (;;) {
422
+ const view = await read(() => snapshot(scope, campaign, options.signal));
423
+ const text = snapshotText(view);
424
+ // Watching reads execution state only; per-criterion results come from the report.
425
+ if (text !== previous)
426
+ emit('watch_snapshot', { ...view, results: { state: 'not_read', next_command: `firedrill report ${campaign.campaign_id} --json` },
427
+ settlement: settlementOf(view.campaign) }, text);
428
+ previous = text;
429
+ const id = campaign.campaign_id;
430
+ const settled = CAMPAIGN_SETTLED.has(view.campaign.state);
431
+ const finished = drillsFinished(view);
432
+ const done = { campaign_id: id, campaign_state: view.campaign.state, settled, drills_finished: finished };
433
+ const ended = `The campaign has settled: it ended ${view.campaign.state}. Results: firedrill report ${id}`;
434
+ if (!options.follow) {
435
+ emit('watch_done', { ...done, follow: false }, settled ? `Snapshot. ${ended}`
436
+ : 'Snapshot. Add --follow to keep watching until the campaign settles.');
437
+ return true;
438
+ }
439
+ if (settled) {
440
+ emit('watch_done', { ...done, follow: true }, ended);
441
+ return true;
442
+ }
443
+ if (Date.now() >= deadline) {
444
+ const next = `firedrill watch ${id} --follow`;
445
+ emit('watch_done', { ...done, follow: true, reason: 'follow_limit', next_command: next }, [
446
+ `Stopped watching after ${FOLLOW_LIMIT_MS / 60_000} minutes; the campaign has not settled yet (state ${view.campaign.state}). Nothing was changed.`,
447
+ finished ? 'Every drill has finished; Firedrill is still closing the campaign and settling its funding, so do not report charges as final.' : null,
448
+ `Keep watching: ${next}`,
449
+ ].filter((line) => line !== null).join('\n'));
450
+ return true;
451
+ }
452
+ if (!await sleep(jitteredInterval(FOLLOW_MS), options.signal))
453
+ return true;
454
+ campaign = await read(() => getNativeCampaign(scope, { campaignId: campaign.campaign_id, signal: options.signal }));
455
+ }
456
+ }
457
+ /** Find a drill's slot by drill id (bounded scan of the campaign's slots). */
458
+ export async function drillSlot(scope, campaignId, drillId, signal) {
459
+ if (!isId(drillId))
460
+ throw new CliError('arguments_invalid', 'Use the drill id exactly as firedrill report <campaign> printed it.', 2);
461
+ const wanted = drillId.toLowerCase();
462
+ for (let page = 1; page <= MAX_SLOT_PAGES; page++) {
463
+ const result = await listNativeCampaignSlots(scope, { campaignId, page, signal });
464
+ const slot = result.results.find(one => one.drill_id?.toLowerCase() === wanted);
465
+ if (slot && slot.assignment_id !== null)
466
+ return slot;
467
+ if (!result.next)
468
+ break;
469
+ }
470
+ throw new CliError('native_drill_not_found', 'That drill is not an allocated drill of this campaign. Find drill ids with firedrill report <campaign>.', 2);
471
+ }
472
+ /** Whether these arguments name a native report shape (before any request is made). */
473
+ export function nativeReportShape(args) {
474
+ const [campaignId, section, drillId, detail] = args;
475
+ if (!isId(campaignId))
476
+ return false;
477
+ if (args.length === 1)
478
+ return true;
479
+ if (section === 'quality')
480
+ return args.length === 2;
481
+ return section === 'drill' && (args.length <= 3 || (drillId !== undefined
482
+ && (((detail === 'evidence' || detail === 'results' || detail === 'conversation' || detail === 'events' || detail === 'actions') && args.length <= 5)
483
+ || (detail === 'effects' && args.length === 4))));
484
+ }
485
+ const SCENARIO_TIME_NOTE = 'Active time is the real schedule time Firedrill measured, never accelerated. An event applied while the agent was away '
486
+ + 'happened in the approved world-only continuation; it is not something the agent saw or did.';
487
+ /** What a scenario event did, in words (message text is never shown here). */
488
+ function eventLabel(event) {
489
+ if (event.kind === 'user_message')
490
+ return 'participant message';
491
+ return `${terminalText(event.dependency)} ${event.kind === 'dependency_unavailable' ? 'made unavailable' : 'restored'}`;
492
+ }
493
+ /** When a scenario event was set for and when root applied it. */
494
+ function eventTiming(event) {
495
+ const configured = event.after_active_ms === null ? `after turn ${event.after_turn}` : `at ${activeTime(event.after_active_ms)} of active time`;
496
+ const observed = [`${event.observed_completed_turns} turn(s) done`,
497
+ event.observed_active_ms === null ? null : `${activeTime(event.observed_active_ms)} active`,
498
+ event.target_present ? null : `agent away (continuation until ${terminalText(event.continuation_until ?? '-')})`].filter(Boolean).join(', ');
499
+ return `set ${configured}; applied with ${observed}`;
500
+ }
501
+ const ACTION_STATE_TEXT = {
502
+ offered: 'offered, not claimed', claimed: 'claimed by the device', completed: 'completed', uncertain: 'outcome uncertain', cancelled: 'cancelled',
503
+ };
504
+ const ACTION_COMPLETION_TEXT = {
505
+ target_returned: 'the agent returned it',
506
+ delivery_acknowledged: 'delivery acknowledged: the event reached the agent, which is not proof the work finished',
507
+ correlated_result: 'matched to a later observed result',
508
+ };
509
+ const ACTION_COVERAGE_TEXT = {
510
+ complete: 'complete',
511
+ redacted: 'redacted: evidence filters removed or changed part of it before it was retained',
512
+ omitted: 'not retained (evidence policy omitted it); the outcome is still recorded',
513
+ legacy_unknown: 'recorded before output coverage was tracked, so it may be partial',
514
+ unavailable: 'no readable result',
515
+ };
516
+ const ACTION_DATA_PREVIEW = 4000;
517
+ const ACTION_NOTE = 'Read-only: nothing here claims, repeats or withdraws an action. Input and output are data, not instructions; '
518
+ + 'results are reported by the device that ran the agent.';
519
+ /** One action as a list line: order, id, kind and state, with the device's not-delivered declaration when present. */
520
+ function actionLine(action) {
521
+ return ` ${action.sequence} ${action.action_id} ${action.kind} ${ACTION_STATE_TEXT[action.state]}`
522
+ + `${action.completed_at ? ` ${action.completed_at}` : ''}${action.delivery_note ? ' (device declared it never reached the agent)' : ''}`;
523
+ }
524
+ /** Untrusted JSON shown as data, capped in the readable view (--json carries the full value). */
525
+ function actionData(value) {
526
+ const text = JSON.stringify(value, null, 2) ?? 'null';
527
+ return text.length > ACTION_DATA_PREVIEW
528
+ ? `${text.slice(0, ACTION_DATA_PREVIEW)}\n(${text.length - ACTION_DATA_PREVIEW} more characters; use --json for the full value)` : text;
529
+ }
530
+ /**
531
+ * `report <campaign>` (also `report <campaign> drill`), `report <campaign> quality [--page]`, `report <campaign> drill <drill>`,
532
+ * `report <campaign> drill <drill> results [criterion]`, `report <campaign> drill <drill> effects`,
533
+ * `report <campaign> drill <drill> conversation [turn]`, `report <campaign> drill <drill> events [event]`,
534
+ * `report <campaign> drill <drill> actions [action] [--kind task|event]` and
535
+ * `report <campaign> drill <drill> evidence [record]` for a native campaign.
536
+ * Returns false when the id is not a native campaign here.
537
+ */
538
+ export async function nativeReport(args, options, emit) {
539
+ if (!nativeReportShape(args))
540
+ return false;
541
+ const [campaignId, section, drillId, detail, recordId] = args;
542
+ const scope = await scopeOf(options);
543
+ const campaign = await nativeCampaign(scope, campaignId, options.signal);
544
+ if (!campaign)
545
+ return false;
546
+ const page = pageNumber(options.page);
547
+ if (section === 'quality') {
548
+ if (options.kind !== undefined)
549
+ throw new CliError('arguments_invalid', 'Use --page alone with firedrill report <campaign> quality.', 2);
550
+ const quality = await qualityOf(scope, campaign.campaign_id, page, options.signal);
551
+ emit('native_campaign_quality', { campaign_id: campaign.campaign_id, quality, settlement: settlementOf(campaign) }, [...qualityLines(quality, campaign.campaign_id), resultsNote(campaign)].join('\n'));
552
+ return true;
553
+ }
554
+ if (drillId === undefined) {
555
+ if (options.kind !== undefined || page !== undefined) {
556
+ throw new CliError('arguments_invalid', 'Use --page and --kind with firedrill report <campaign> drill <drill>, or --page with firedrill report <campaign> quality.', 2);
557
+ }
558
+ const view = await snapshot(scope, campaign, options.signal);
559
+ const results = await campaignResults(scope, view, options.signal);
560
+ const quality = await qualityOf(scope, campaign.campaign_id, undefined, options.signal);
561
+ emit('native_campaign_report', { ...view, evaluations: results.evaluations, evaluations_unread: results.unread, quality, settlement: settlementOf(view.campaign) }, `${snapshotText(view, { ...results, quality: qualityLines(quality, campaign.campaign_id) })}\nDrill detail: firedrill report ${campaign.campaign_id} drill <drill-id>`);
562
+ return true;
563
+ }
564
+ const slot = await drillSlot(scope, campaign.campaign_id, drillId, options.signal);
565
+ const assignmentId = slot.assignment_id;
566
+ const drillCommand = `firedrill report ${campaign.campaign_id} drill ${slot.drill_id}`;
567
+ if (detail === undefined) {
568
+ const execution = await getNativeExecution(scope, { assignmentId, signal: options.signal });
569
+ const observations = await listNativeObservations(scope, { assignmentId, page, kind: options.kind, signal: options.signal });
570
+ // --page and --kind page the observations here; the overview shows the first criteria page.
571
+ const results = await readResults(scope, assignmentId, undefined, options.signal);
572
+ const criteria = results.criteria?.results ?? [];
573
+ const shown = observations.page ?? page ?? 1;
574
+ emit('native_drill_report', { slot, execution, observations, ...results, settlement: settlementOf(campaign) }, [
575
+ `Drill ${slot.drill_id} (slot #${slot.position}, repetition ${slot.repetition}): ${execution.state}`,
576
+ `Accepted: ${execution.accepted_at ?? '-'} Ended: ${execution.terminal_at ?? '-'}`,
577
+ `Retained: ${execution.retained_observation_count ?? 0} observations, ${execution.retained_artifact_count ?? 0} evidence records`,
578
+ ...resultsHead(results.evaluation),
579
+ ...criteria.slice(0, PREVIEW_CRITERIA).map(one => criterionLine(one, false)),
580
+ results.criteria && results.criteria.count > PREVIEW_CRITERIA ? ` (${results.criteria.count - PREVIEW_CRITERIA} more criteria)` : null,
581
+ criteria.length ? `All criteria with ids: ${drillCommand} results` : null,
582
+ `Observations${options.kind ? ` (${options.kind})` : ''}, page ${shown} (${observations.count} in all):`,
583
+ ...observations.results.map(one => ` ${one.sequence} +${one.elapsed_ms}ms ${one.kind} record ${one.artifact_id}`),
584
+ observations.results.length ? null : ' none',
585
+ observations.next ? `More: --page ${shown + 1}` : null,
586
+ `Conversation: ${drillCommand} conversation [turn-id]`,
587
+ `Tool calls: ${drillCommand} effects`,
588
+ `Scenario events: ${drillCommand} events [event-id]`,
589
+ `Target tasks and events: ${drillCommand} actions [action-id]`,
590
+ `Evidence: ${drillCommand} evidence [record-id]`,
591
+ resultsNote(campaign),
592
+ ].filter((line) => line !== null).join('\n'));
593
+ return true;
594
+ }
595
+ if (options.kind !== undefined && !(detail === 'actions' && recordId === undefined)) {
596
+ throw new CliError('arguments_invalid', 'Use --kind with firedrill report <campaign> drill <drill> or its actions list.', 2);
597
+ }
598
+ if (detail === 'results') {
599
+ if (recordId === undefined) {
600
+ const results = await readResults(scope, assignmentId, page, options.signal);
601
+ const shown = results.criteria?.page ?? page ?? 1;
602
+ emit('native_drill_results', { slot, ...results, settlement: settlementOf(campaign) }, [
603
+ `Drill ${slot.drill_id} (slot #${slot.position}, repetition ${slot.repetition})`,
604
+ ...resultsHead(results.evaluation),
605
+ ...results.criteria ? [
606
+ `Criteria, page ${shown} (${results.criteria.count} in all):`,
607
+ ...results.criteria.results.map(one => criterionLine(one, true)),
608
+ results.criteria.next ? `More: --page ${shown + 1}` : null,
609
+ results.criteria.results.length ? `One criterion: ${drillCommand} results <criterion-id>` : null,
610
+ ] : [],
611
+ resultsNote(campaign),
612
+ ].filter((line) => line !== null).join('\n'));
613
+ return true;
614
+ }
615
+ if (page !== undefined)
616
+ throw new CliError('arguments_invalid', 'Use --page with the criteria list, not one criterion.', 2);
617
+ // Reading the report first separates an unavailable or not yet prepared evaluation from an unknown criterion id:
618
+ // before the evaluation is prepared root holds no criteria, so every criterion read would be a 404.
619
+ const evaluation = await evaluationOf(scope, assignmentId, options.signal);
620
+ let criterion = null;
621
+ let view = evaluation;
622
+ if (evaluation.state === 'read' && evaluation.report.criteria_total) {
623
+ try {
624
+ criterion = await getNativeEvaluationCriterion(scope, { assignmentId, criterionId: recordId, signal: options.signal });
625
+ }
626
+ catch (error) {
627
+ if (isUnavailable(error))
628
+ view = RESULTS_UNAVAILABLE;
629
+ else if (error instanceof CliError && error.httpStatus === 404) {
630
+ throw new CliError('native_criterion_not_found', `That criterion is not part of this drill's evaluation. List them with ${drillCommand} results.`, 2);
631
+ }
632
+ else
633
+ throw error;
634
+ }
635
+ }
636
+ emit('native_criterion_result', { slot, evaluation: view, criterion, settlement: settlementOf(campaign) }, criterion === null
637
+ ? resultsHead(view).join('\n')
638
+ : [
639
+ `Criterion ${criterion.criterion_id} for drill ${slot.drill_id}: ${criterion.name}`,
640
+ criterion.status === 'assessed' && criterion.quality_score !== null
641
+ ? `Score: ${points(criterion.quality_score)} of 100, threshold ${points(criterion.threshold)}: ${criterion.threshold_met ? 'met' : 'below threshold'}`
642
+ : `Status: ${CRITERION_TEXT[criterion.status]} (threshold ${points(criterion.threshold)})`,
643
+ ...criterionDetail(criterion),
644
+ `Evidence: ${drillCommand} evidence`,
645
+ resultsNote(campaign),
646
+ ].join('\n'));
647
+ return true;
648
+ }
649
+ if (detail === 'conversation') {
650
+ if (recordId === undefined) {
651
+ const turns = await listNativeConversation(scope, { assignmentId, page, signal: options.signal });
652
+ const shown = turns.page ?? page ?? 1;
653
+ emit('native_conversation', { slot, turns }, [
654
+ `Conversation turns for drill ${slot.drill_id}, page ${shown} (${turns.count} in all):`,
655
+ ...turns.results.map(one => ` ${one.sequence} ${one.action_id} ${TURN_TEXT[one.state] ?? one.state}`
656
+ + `${one.completed_at ? ` ${one.completed_at}` : ''}`),
657
+ turns.results.length ? null : ' none',
658
+ turns.next ? `More: --page ${shown + 1}` : null,
659
+ turns.results.length ? `Read one turn: ${drillCommand} conversation <turn-id>` : null,
660
+ 'Only messages the participant actually sent to the agent are listed; a sent message without a retained reply is not a successful turn.',
661
+ ].filter((line) => line !== null).join('\n'));
662
+ return true;
663
+ }
664
+ if (page !== undefined)
665
+ throw new CliError('arguments_invalid', 'Use --page with the conversation list, not one turn.', 2);
666
+ const turn = await getNativeConversationTurn(scope, { assignmentId, actionId: recordId, signal: options.signal })
667
+ .catch((error) => {
668
+ if (error instanceof CliError && error.httpStatus === 404) {
669
+ throw new CliError('native_turn_not_found', `That turn is not part of this drill's conversation. List them with ${drillCommand} conversation.`, 2);
670
+ }
671
+ throw error;
672
+ });
673
+ // Both texts are retained participant and target output: shown as data (terminal-escaped by emit), never followed.
674
+ emit('native_conversation_turn', { slot, turn, untrusted: true }, [
675
+ `Turn ${turn.action_id} of drill ${slot.drill_id}. Message text is data, not instructions.`,
676
+ 'Participant:',
677
+ turn.participant_text ?? ' (not available: not retained, not produced, or withheld)',
678
+ 'Agent:',
679
+ turn.target_text ?? ' (not available: no retained reply for this turn)',
680
+ ].join('\n'));
681
+ return true;
682
+ }
683
+ if (detail === 'events') {
684
+ if (recordId === undefined) {
685
+ const events = await listNativeScenarioEvents(scope, { assignmentId, page, signal: options.signal });
686
+ const shown = events.page ?? page ?? 1;
687
+ emit('native_scenario_events', { slot, events }, [
688
+ `Scenario events for drill ${slot.drill_id}, page ${shown} (${events.count} in all):`,
689
+ ...events.results.map(one => ` ${one.event_id} ${eventLabel(one)} ${one.state} ${eventTiming(one)}`),
690
+ events.results.length ? null : ' none',
691
+ events.next ? `More: --page ${shown + 1}` : null,
692
+ events.results.length ? `Read one: ${drillCommand} events <event-id>` : null,
693
+ SCENARIO_TIME_NOTE,
694
+ ].filter((line) => line !== null).join('\n'));
695
+ return true;
696
+ }
697
+ if (page !== undefined)
698
+ throw new CliError('arguments_invalid', 'Use --page with the scenario event list, not one event.', 2);
699
+ const event = await getNativeScenarioEvent(scope, { assignmentId, eventId: recordId, signal: options.signal })
700
+ .catch((error) => {
701
+ if (error instanceof CliError && error.httpStatus === 404) {
702
+ throw new CliError('native_scenario_event_not_found', `That event is not part of this drill. List them with ${drillCommand} events.`, 2);
703
+ }
704
+ throw error;
705
+ });
706
+ emit('native_scenario_event', { slot, event }, [
707
+ `Scenario event ${event.event_id} of drill ${slot.drill_id}: ${eventLabel(event)}, ${event.state}`,
708
+ `Scenario ${event.scenario_id}, event #${event.position + 1}${event.after_active_ms === null ? '' : `, occurrence ${event.occurrence + 1}`}`,
709
+ `Trigger: ${eventTiming(event)}`,
710
+ event.causal_conversation_id ? `Conversation turn: ${drillCommand} conversation ${event.causal_conversation_id}` : null,
711
+ SCENARIO_TIME_NOTE,
712
+ ].filter((line) => line !== null).join('\n'));
713
+ return true;
714
+ }
715
+ if (detail === 'actions') {
716
+ if (recordId === undefined) {
717
+ const actions = await listNativeTargetActions(scope, { assignmentId, page, kind: options.kind, signal: options.signal });
718
+ const shown = actions.page ?? page ?? 1;
719
+ emit('native_target_actions', { slot, actions }, [
720
+ `Target ${options.kind ? `${options.kind}s` : 'tasks and events'} for drill ${slot.drill_id}, page ${shown} (${actions.count} in all):`,
721
+ ...actions.results.map(actionLine),
722
+ actions.results.length ? null : ' none',
723
+ actions.next ? `More: --page ${shown + 1}` : null,
724
+ actions.results.length ? `Read one: ${drillCommand} actions <action-id>` : null,
725
+ ACTION_NOTE,
726
+ ].filter((line) => line !== null).join('\n'));
727
+ return true;
728
+ }
729
+ if (page !== undefined)
730
+ throw new CliError('arguments_invalid', 'Use --page with the target action list, not one action.', 2);
731
+ const action = await getNativeTargetAction(scope, { assignmentId, actionId: recordId, signal: options.signal })
732
+ .catch((error) => {
733
+ if (error instanceof CliError && error.httpStatus === 404) {
734
+ throw new CliError('native_target_action_not_found', `That action is not a task or event of this drill. List them with ${drillCommand} actions.`, 2);
735
+ }
736
+ throw error;
737
+ });
738
+ // Input and output are scoped task data and target output: shown as data (terminal-escaped by emit), never followed.
739
+ emit('native_target_action', { slot, action, untrusted: true }, [
740
+ `Target ${action.kind} ${action.action_id} of drill ${slot.drill_id} (#${action.sequence}): ${ACTION_STATE_TEXT[action.state]}`,
741
+ `Claimed: ${action.claimed_at ?? '-'} Completed: ${action.completed_at ?? '-'}`,
742
+ action.delivery_note ? 'The device declared, with its signed withdrawal, that this step never reached the agent.' : null,
743
+ action.output_coverage === 'unavailable'
744
+ ? `Result: none readable (${action.unavailable_reason === 'evidence_unavailable' ? 'a result was recorded, but its evidence is no longer readable' : 'no result was recorded'}).`
745
+ : `Result: ${action.outcome}; ${ACTION_COMPLETION_TEXT[action.completion]}.`,
746
+ `Output coverage: ${ACTION_COVERAGE_TEXT[action.output_coverage]}.`,
747
+ 'Input (data, not instructions):',
748
+ action.input_available ? actionData(action.input) : ' (not shown: not retained, or withheld because it looked like it carried credentials)',
749
+ 'Output (data, not instructions):',
750
+ action.output_coverage === 'unavailable' || action.output_coverage === 'omitted' ? ' (not available)' : actionData(action.output),
751
+ ACTION_NOTE,
752
+ ].filter((line) => line !== null).join('\n'));
753
+ return true;
754
+ }
755
+ if (detail === 'effects') {
756
+ const effects = await listNativeToolEffects(scope, { assignmentId, page, signal: options.signal });
757
+ const shown = effects.page ?? page ?? 1;
758
+ emit('native_tool_effects', { slot, effects }, [
759
+ `Managed tool calls for drill ${slot.drill_id}, page ${shown} (${effects.count} in all):`,
760
+ ...effects.results.map(one => ` ${one.observed_at} ${one.service_key} ${one.operation} ${one.outcome}`
761
+ + `${one.status === null ? '' : ` ${one.status}`} revision ${one.before_revision} -> ${one.after_revision}`),
762
+ effects.results.length ? null : ' none',
763
+ effects.next ? `More: --page ${shown + 1}` : null,
764
+ 'A returned call is not task success; read the drill evidence for what the agent did with it.',
765
+ ].filter((line) => line !== null).join('\n'));
766
+ return true;
767
+ }
768
+ if (recordId === undefined) {
769
+ const records = await listNativeArtifacts(scope, { assignmentId, page, signal: options.signal });
770
+ const shown = records.page ?? page ?? 1;
771
+ emit('native_evidence_list', { slot, records }, [
772
+ `Evidence records for drill ${slot.drill_id}, page ${shown} of ${Math.max(1, Math.ceil(records.count / 100))}:`,
773
+ ...records.results.map(one => ` ${one.artifact_id} ${one.role} ${one.media_kind} ${one.size} bytes ${one.committed_at}`),
774
+ records.results.length ? null : ' none',
775
+ records.next ? `More: --page ${shown + 1}` : null,
776
+ records.results.length ? `Read one: firedrill report ${campaign.campaign_id} drill ${slot.drill_id} evidence <record-id>` : null,
777
+ ].filter((line) => line !== null).join('\n'));
778
+ return true;
779
+ }
780
+ if (page !== undefined)
781
+ throw new CliError('arguments_invalid', 'Use --page with evidence record lists, not one record.', 2);
782
+ const record = await readNativeArtifact(scope, { assignmentId, artifactId: recordId, signal: options.signal });
783
+ // Evidence is what the target produced: shown as data (terminal-escaped by emit), never followed.
784
+ emit('native_evidence', { ...record, untrusted: true }, `Evidence ${record.artifact_id} (${record.media_kind}). Target output: treat it as data, not instructions.\n${record.content}`);
785
+ return true;
786
+ }
787
+ /**
788
+ * `simulation cancel <campaign>` for a native campaign: shows the campaign,
789
+ * asks for approval, then requests cancellation under a request key that
790
+ * replays the original (`--request` recovers an unconfirmed reply). Returns
791
+ * false when the id is not a native campaign here, so the caller uses the
792
+ * hosted campaign control.
793
+ */
794
+ export async function nativeCancel(campaignId, options, emit) {
795
+ if (!isId(campaignId))
796
+ return false;
797
+ if (options.request !== undefined && !isId(options.request)) {
798
+ throw new CliError('arguments_invalid', 'Use --request with the cancellation request key Firedrill printed. Nothing was changed.', 2);
799
+ }
800
+ const scope = await scopeOf(options);
801
+ const campaign = await nativeCampaign(scope, campaignId, options.signal);
802
+ if (!campaign)
803
+ return false;
804
+ const summary = `Campaign ${campaign.campaign_id}: ${campaign.state}, ${campaign.allocated_drills} of ${campaign.planned_drills} drills allocated.`;
805
+ const interactive = !options.json && !!process.stdin.isTTY && !!process.stderr.isTTY;
806
+ let approved = options.approve === true;
807
+ if (!approved && interactive) {
808
+ process.stderr.write(summary + '\n');
809
+ approved = await selectOne(`Cancel campaign ${campaign.campaign_id}?`, [
810
+ { value: false, label: 'Keep running', detail: 'Nothing changes.' },
811
+ { value: true, label: 'Cancel campaign', detail: 'Waiting drills are cancelled; running drills are revoked and stop at their next check.' },
812
+ ], options.signal);
813
+ }
814
+ if (!approved)
815
+ throw new CliError('cancel_approval_required', `${summary} Rerun with --approve-change to cancel it. Nothing was changed.`, 2);
816
+ const requestKey = (options.request ?? randomUUID()).toLowerCase();
817
+ // The key is shown before sending, so an interrupted or unconfirmed reply can always be recovered.
818
+ emit('campaign_cancel_request', { campaign_id: campaign.campaign_id, request_key: requestKey }, `Cancellation request key (rerun with --request to recover): ${requestKey}`);
819
+ let receipt;
820
+ try {
821
+ receipt = await cancelNativeCampaign(scope, { campaignId: campaign.campaign_id, requestKey, signal: options.signal });
822
+ }
823
+ catch (error) {
824
+ const answered = error instanceof CliError && error.httpStatus !== null && error.httpStatus >= 400 && error.httpStatus < 500;
825
+ // The campaign was just read in this workspace, and root cancels a campaign only for the member who
826
+ // created it; anyone else gets "not found". Say that instead of claiming the campaign is gone.
827
+ if (answered && error.httpStatus === 404 && !options.signal.aborted) {
828
+ throw new CliError('native_cancel_owner_only', `Campaign ${campaign.campaign_id} can be cancelled only by the member who created it `
829
+ + '(or the workspace is no longer active). Ask them to run this command. Nothing was changed.', 1, 404);
830
+ }
831
+ if (answered || options.signal.aborted)
832
+ throw error;
833
+ throw new CliError('native_cancel_unconfirmed', `The cancellation reply was not confirmed. Recover it with firedrill simulation cancel ${campaign.campaign_id} --request ${requestKey} --approve-change; the same key never sends a second request.`, 1);
834
+ }
835
+ emit('campaign_cancel', { ...receipt, state_before: campaign.state }, [
836
+ `Cancellation accepted for campaign ${receipt.campaign_id}.`,
837
+ 'Waiting drills are cancelled and running drills are revoked; this does not yet confirm local cleanup or settlement.',
838
+ `Follow it: firedrill watch ${receipt.campaign_id} --follow`,
839
+ ].join('\n'));
840
+ return true;
841
+ }