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.
- package/README.md +178 -22
- package/bin/firedrill.mjs +29 -5
- package/dist/assembly.json +13 -0
- package/dist/cli/runtime/candidate_copy.py +395 -0
- package/dist/cli/runtime/source_snapshot.py +391 -0
- package/dist/cli/src/administration.js +295 -0
- package/dist/cli/src/agent-changes.js +496 -0
- package/dist/cli/src/assessment-requests.js +67 -0
- package/dist/cli/src/auth.js +465 -0
- package/dist/cli/src/billing-commerce-contracts.js +1 -0
- package/dist/cli/src/billing-usage.js +227 -0
- package/dist/cli/src/billing-wallet-contracts.js +1 -0
- package/dist/cli/src/billing.js +170 -0
- package/dist/cli/src/bootstrap-process-lock.js +136 -0
- package/dist/cli/src/browser-links.js +14 -0
- package/dist/cli/src/campaign-controls.js +122 -0
- package/dist/cli/src/campaign-diagnoses.js +118 -0
- package/dist/cli/src/campaign-reports.js +1215 -0
- package/dist/cli/src/campaign-world.js +156 -0
- package/dist/cli/src/candidate-copy.js +376 -0
- package/dist/cli/src/catalog-contracts.js +1 -0
- package/dist/cli/src/ci-access-contracts.js +1 -0
- package/dist/cli/src/ci-configuration-contracts.js +1 -0
- package/dist/cli/src/ci-coverage.js +64 -0
- package/dist/cli/src/ci-delivery-contracts.js +1 -0
- package/dist/cli/src/ci-result-contracts.js +1 -0
- package/dist/cli/src/ci-run-contracts.js +1 -0
- package/dist/cli/src/ci-setup-proposals.js +52 -0
- package/dist/cli/src/company-policies.js +229 -0
- package/dist/cli/src/completion.js +79 -0
- package/dist/cli/src/configuration-intents.js +277 -0
- package/dist/cli/src/configuration.js +369 -0
- package/dist/cli/src/continuous-simulation.js +277 -0
- package/dist/cli/src/coordinated-checkpoints.js +133 -0
- package/dist/cli/src/credential-mode.js +139 -0
- package/dist/cli/src/credentials.js +331 -0
- package/dist/cli/src/credit-format.js +8 -0
- package/dist/cli/src/dataset-contracts.js +1 -0
- package/dist/cli/src/dataset-export.js +154 -0
- package/dist/cli/src/dataset-reads.js +37 -0
- package/dist/cli/src/dependency-preparation.js +239 -0
- package/dist/cli/src/director-tool-setup.js +104 -0
- package/dist/cli/src/driver-review.js +225 -0
- package/dist/cli/src/evaluation-authoring.js +315 -0
- package/dist/cli/src/evidence-bundles.js +367 -0
- package/dist/cli/src/execution-proposals.js +36 -0
- package/dist/cli/src/http.js +302 -0
- package/dist/cli/src/local-container-boundary.js +38 -0
- package/dist/cli/src/local-container-policy.js +201 -0
- package/dist/cli/src/local-execution-setup.js +81 -0
- package/dist/cli/src/local-source-basis.js +73 -0
- package/dist/cli/src/local-source.js +66 -0
- package/dist/cli/src/local-value-store.js +91 -0
- package/dist/cli/src/local-values.js +163 -0
- package/dist/cli/src/login-organizations.js +70 -0
- package/dist/cli/src/main.js +1622 -0
- package/dist/cli/src/mcp-connection.js +109 -0
- package/dist/cli/src/native/bindings.js +137 -0
- package/dist/cli/src/native/canonical.js +370 -0
- package/dist/cli/src/native/cloud-resources.js +942 -0
- package/dist/cli/src/native/companion-channel.js +421 -0
- package/dist/cli/src/native/companion-ipc.js +473 -0
- package/dist/cli/src/native/contracts.js +218 -0
- package/dist/cli/src/native/control-plane.js +1354 -0
- package/dist/cli/src/native/credit-approval.js +15 -0
- package/dist/cli/src/native/dependency-proxy.js +1006 -0
- package/dist/cli/src/native/drill-cleanup.js +115 -0
- package/dist/cli/src/native/drill-runner.js +1562 -0
- package/dist/cli/src/native/drivers/action-trace.js +210 -0
- package/dist/cli/src/native/drivers/function-wrapper.js +152 -0
- package/dist/cli/src/native/drivers/http.js +442 -0
- package/dist/cli/src/native/drivers/index.js +47 -0
- package/dist/cli/src/native/drivers/jsonl-process.js +538 -0
- package/dist/cli/src/native/drivers/playwright.js +600 -0
- package/dist/cli/src/native/drivers/signed-event.js +244 -0
- package/dist/cli/src/native/drivers/types.js +578 -0
- package/dist/cli/src/native/evidence-filter.js +255 -0
- package/dist/cli/src/native/isolated-access.js +241 -0
- package/dist/cli/src/native/library-sharing.js +220 -0
- package/dist/cli/src/native/local-run.js +667 -0
- package/dist/cli/src/native/local-secrets.js +19 -0
- package/dist/cli/src/native/mcp-config.js +123 -0
- package/dist/cli/src/native/metadata-requests.js +120 -0
- package/dist/cli/src/native/observations.js +1159 -0
- package/dist/cli/src/native/owned-groups.js +46 -0
- package/dist/cli/src/native/plan.js +594 -0
- package/dist/cli/src/native/png.js +159 -0
- package/dist/cli/src/native/ports.js +43 -0
- package/dist/cli/src/native/readiness.js +139 -0
- package/dist/cli/src/native/reassessments.js +206 -0
- package/dist/cli/src/native/redaction.js +235 -0
- package/dist/cli/src/native/registration.js +155 -0
- package/dist/cli/src/native/roots.js +110 -0
- package/dist/cli/src/native/run-cleanup.js +199 -0
- package/dist/cli/src/native/scratch.js +117 -0
- package/dist/cli/src/native/screenshots.js +60 -0
- package/dist/cli/src/native/session.js +556 -0
- package/dist/cli/src/native/standalone-proxy.js +580 -0
- package/dist/cli/src/native/standalone-renewals.js +183 -0
- package/dist/cli/src/native/standalone-state.js +193 -0
- package/dist/cli/src/native/standalone-tools.js +187 -0
- package/dist/cli/src/native/supervisor.js +831 -0
- package/dist/cli/src/native/target-auth.js +161 -0
- package/dist/cli/src/native/target-state.js +330 -0
- package/dist/cli/src/native/tool-reuse.js +364 -0
- package/dist/cli/src/native/tool-setups.js +336 -0
- package/dist/cli/src/native/world-reuse.js +288 -0
- package/dist/cli/src/onboarding.js +23 -0
- package/dist/cli/src/priority-requests.js +70 -0
- package/dist/cli/src/priority.js +223 -0
- package/dist/cli/src/prompts.js +184 -0
- package/dist/cli/src/real-operations.js +221 -0
- package/dist/cli/src/recipe-intents.js +58 -0
- package/dist/cli/src/recorded-context-intents.js +59 -0
- package/dist/cli/src/recorded-context.js +248 -0
- package/dist/cli/src/recordings.js +69 -0
- package/dist/cli/src/registration-guide.js +39 -0
- package/dist/cli/src/report-exports.js +213 -0
- package/dist/cli/src/request-input.js +47 -0
- package/dist/cli/src/resource-lifecycle.js +141 -0
- package/dist/cli/src/revision-requests.js +55 -0
- package/dist/cli/src/runner-abandon.js +53 -0
- package/dist/cli/src/runner-campaign-controls.js +262 -0
- package/dist/cli/src/runner-claim-reads.js +36 -0
- package/dist/cli/src/runner-completion.js +65 -0
- package/dist/cli/src/runner-configuration.js +546 -0
- package/dist/cli/src/runner-container.js +794 -0
- package/dist/cli/src/runner-evidence-erasure.js +351 -0
- package/dist/cli/src/runner-evidence-privacy.js +37 -0
- package/dist/cli/src/runner-evidence-retention.js +161 -0
- package/dist/cli/src/runner-evidence.js +326 -0
- package/dist/cli/src/runner-execute.js +92 -0
- package/dist/cli/src/runner-execution-deadline.js +25 -0
- package/dist/cli/src/runner-grants.js +307 -0
- package/dist/cli/src/runner-identity.js +615 -0
- package/dist/cli/src/runner-inputs.js +308 -0
- package/dist/cli/src/runner-jobs.js +49 -0
- package/dist/cli/src/runner-model-transport.js +392 -0
- package/dist/cli/src/runner-ownership.js +332 -0
- package/dist/cli/src/runner-process-lock.js +175 -0
- package/dist/cli/src/runner-runtime.js +396 -0
- package/dist/cli/src/runner-session-channel.js +377 -0
- package/dist/cli/src/runner-trial-completion.js +34 -0
- package/dist/cli/src/runner-trial-journal.js +358 -0
- package/dist/cli/src/runner-trial-live.js +393 -0
- package/dist/cli/src/runner-trial-protocol.js +289 -0
- package/dist/cli/src/runner-trial-sync.js +110 -0
- package/dist/cli/src/runner-trial-transfer.js +107 -0
- package/dist/cli/src/runner-voice.js +40 -0
- package/dist/cli/src/runner-world-claim.js +21 -0
- package/dist/cli/src/runner-world-collector.js +159 -0
- package/dist/cli/src/runner-world-command-evidence.js +487 -0
- package/dist/cli/src/runner-world-commands.js +268 -0
- package/dist/cli/src/runner-world-completion.js +76 -0
- package/dist/cli/src/runner-world-erasure.js +95 -0
- package/dist/cli/src/runner-world-evidence-sync.js +91 -0
- package/dist/cli/src/runner-world-journal.js +510 -0
- package/dist/cli/src/runner-world-native-filters.js +134 -0
- package/dist/cli/src/runner-world-observations.js +50 -0
- package/dist/cli/src/runner-world-session-protocol.js +134 -0
- package/dist/cli/src/saved-records.js +40 -0
- package/dist/cli/src/scenario-authoring.js +212 -0
- package/dist/cli/src/scenario-discovery.js +216 -0
- package/dist/cli/src/scenario-paths.js +68 -0
- package/dist/cli/src/scenario-requests.js +4 -0
- package/dist/cli/src/service-receipts.js +105 -0
- package/dist/cli/src/simple/authoring.js +270 -0
- package/dist/cli/src/simple/credit-approval.js +16 -0
- package/dist/cli/src/simple/help.js +98 -0
- package/dist/cli/src/simple/init.js +61 -0
- package/dist/cli/src/simple/library-sharing.js +414 -0
- package/dist/cli/src/simple/metadata-requests.js +46 -0
- package/dist/cli/src/simple/native-report.js +841 -0
- package/dist/cli/src/simple/native-tools.js +780 -0
- package/dist/cli/src/simple/plan-apply.js +506 -0
- package/dist/cli/src/simple/reassessment.js +323 -0
- package/dist/cli/src/simple/register.js +123 -0
- package/dist/cli/src/simple/run.js +122 -0
- package/dist/cli/src/simple/status.js +49 -0
- package/dist/cli/src/simple/tool-renewal.js +308 -0
- package/dist/cli/src/simple/tool-reuse.js +526 -0
- package/dist/cli/src/simple/tool-start-catalog.js +100 -0
- package/dist/cli/src/simple/tool-state.js +426 -0
- package/dist/cli/src/simple/tools-author.js +222 -0
- package/dist/cli/src/simple/watch.js +28 -0
- package/dist/cli/src/simple/world-reuse.js +586 -0
- package/dist/cli/src/simulation-intents.js +406 -0
- package/dist/cli/src/simulation-run.js +1058 -0
- package/dist/cli/src/source-batch.js +322 -0
- package/dist/cli/src/source-connection.js +451 -0
- package/dist/cli/src/source-runtime.js +346 -0
- package/dist/cli/src/source-selection-intents.js +75 -0
- package/dist/cli/src/source-sets.js +214 -0
- package/dist/cli/src/target-models.js +109 -0
- package/dist/cli/src/tool-catalog.js +224 -0
- package/dist/cli/src/tool-command-contracts.js +1 -0
- package/dist/cli/src/tool-command-export.js +245 -0
- package/dist/cli/src/tool-connection-contracts.js +1 -0
- package/dist/cli/src/tool-connection-reads.js +23 -0
- package/dist/cli/src/tool-contributions.js +58 -0
- package/dist/cli/src/tool-data.js +116 -0
- package/dist/cli/src/tool-datasets.js +176 -0
- package/dist/cli/src/tool-group-contracts.js +1 -0
- package/dist/cli/src/tool-group-reads.js +26 -0
- package/dist/cli/src/tool-group-state.js +147 -0
- package/dist/cli/src/tool-groups.js +311 -0
- package/dist/cli/src/tool-instances.js +186 -0
- package/dist/cli/src/tool-lifecycle-contracts.js +1 -0
- package/dist/cli/src/tool-lifecycle-reads.js +30 -0
- package/dist/cli/src/tool-lifecycle.js +323 -0
- package/dist/cli/src/tool-package-contracts.js +1 -0
- package/dist/cli/src/tool-package-reads.js +41 -0
- package/dist/cli/src/tool-records.js +53 -0
- package/dist/cli/src/tool-runtime.js +96 -0
- package/dist/cli/src/tool-state-contracts.js +1 -0
- package/dist/cli/src/tool-state-reads.js +31 -0
- package/dist/cli/src/tools.js +306 -0
- package/dist/cli/src/trial-actions.js +93 -0
- package/dist/cli/src/trial-checkpoints.js +133 -0
- package/dist/cli/src/trial-checks.js +70 -0
- package/dist/cli/src/trial-state.js +197 -0
- package/dist/cli/src/vault.js +266 -0
- package/dist/cli/src/workspace-registration.js +383 -0
- package/dist/cli/src/workspace-team-contracts.js +1 -0
- package/dist/cli/src/world-access.js +231 -0
- package/dist/cli/src/world-agent.js +4 -0
- package/dist/cli/src/world-checkpoint-scopes.js +15 -0
- package/dist/cli/src/world-contracts.js +6 -0
- package/dist/cli/src/world-creation.js +201 -0
- package/dist/cli/src/world-datasets.js +207 -0
- package/dist/cli/src/world-inspection.js +257 -0
- package/dist/cli/src/world-recipes.js +103 -0
- package/dist/cli/src/world-requests.js +241 -0
- package/dist/cli/src/world-session-checkpoints.js +196 -0
- package/dist/cli/src/world-session-lifecycle.js +207 -0
- package/dist/cli/src/world-session-requests.js +215 -0
- package/dist/cli/src/world-session-state.js +145 -0
- package/dist/cli/src/world-session-voice.js +66 -0
- package/dist/cli/src/world-sessions.js +288 -0
- package/dist/cli/src/world-stream.js +99 -0
- package/dist/cli/src/world-time-report.js +58 -0
- package/dist/contracts/agent-change-contracts.js +75 -0
- package/dist/contracts/agent-execution-contracts.js +38 -0
- package/dist/contracts/application-state-contracts.js +26 -0
- package/dist/contracts/assessment-aggregate-contracts.js +15 -0
- package/dist/contracts/assessment-contracts.js +74 -0
- package/dist/contracts/billing-commerce-contracts.js +107 -0
- package/dist/contracts/billing-wallet-contracts.js +43 -0
- package/dist/contracts/campaign-lifecycle.js +1 -0
- package/dist/contracts/campaign-world-contracts.js +80 -0
- package/dist/contracts/catalog-contracts.js +73 -0
- package/dist/contracts/ci-access-contracts.js +68 -0
- package/dist/contracts/ci-configuration-contracts.js +88 -0
- package/dist/contracts/ci-coverage-contracts.js +65 -0
- package/dist/contracts/ci-coverage-labels.js +25 -0
- package/dist/contracts/ci-delivery-contracts.js +61 -0
- package/dist/contracts/ci-result-contracts.js +103 -0
- package/dist/contracts/ci-run-contracts.js +51 -0
- package/dist/contracts/ci-setup-contracts.js +88 -0
- package/dist/contracts/clip-contracts.js +32 -0
- package/dist/contracts/comparison-contracts.js +53 -0
- package/dist/contracts/composite-contracts.js +34 -0
- package/dist/contracts/context-contracts.js +76 -0
- package/dist/contracts/continuation-report-contracts.js +1 -0
- package/dist/contracts/continuity-requirements.js +8 -0
- package/dist/contracts/coordinated-checkpoint-contracts.js +34 -0
- package/dist/contracts/dataset-contracts.js +88 -0
- package/dist/contracts/dependency-decision-contracts.js +36 -0
- package/dist/contracts/dependency-protocol-contracts.js +43 -0
- package/dist/contracts/director-execution.js +51 -0
- package/dist/contracts/driver-review-contracts.js +99 -0
- package/dist/contracts/evidence-contracts.js +65 -0
- package/dist/contracts/evidence-coverage-contracts.js +10 -0
- package/dist/contracts/export-contracts.js +44 -0
- package/dist/contracts/github-directory-contracts.js +96 -0
- package/dist/contracts/insight-contracts.js +11 -0
- package/dist/contracts/inspection-contracts.js +1 -0
- package/dist/contracts/local-claim-contracts.js +39 -0
- package/dist/contracts/local-configuration-contracts.js +7 -0
- package/dist/contracts/local-evidence-contracts.js +32 -0
- package/dist/contracts/local-grant-contracts.js +83 -0
- package/dist/contracts/local-input-contracts.js +19 -0
- package/dist/contracts/local-runner-contracts.js +42 -0
- package/dist/contracts/local-trial-live-contracts.js +16 -0
- package/dist/contracts/local-value-source-contracts.js +92 -0
- package/dist/contracts/local-world-native-contracts.js +8 -0
- package/dist/contracts/mcp-contracts.js +42 -0
- package/dist/contracts/media-contracts.js +28 -0
- package/dist/contracts/native-check-contracts.js +31 -0
- package/dist/contracts/native-identity-contracts.js +52 -0
- package/dist/contracts/onboarding-contracts.js +32 -0
- package/dist/contracts/performance-contracts.js +85 -0
- package/dist/contracts/planning-contracts.js +81 -0
- package/dist/contracts/real-operation-contracts.js +30 -0
- package/dist/contracts/recommendation-contracts.js +54 -0
- package/dist/contracts/report-contracts.js +1 -0
- package/dist/contracts/resource-lifecycle-contracts.js +57 -0
- package/dist/contracts/revision-contracts.js +12 -0
- package/dist/contracts/revision-request-contracts.js +39 -0
- package/dist/contracts/saved-record-contracts.js +76 -0
- package/dist/contracts/scenario-draft-comparison.js +91 -0
- package/dist/contracts/scenario-world-contracts.js +14 -0
- package/dist/contracts/service-contracts.js +1 -0
- package/dist/contracts/session-command-contracts.js +56 -0
- package/dist/contracts/session-contracts.js +65 -0
- package/dist/contracts/session-state-contracts.js +67 -0
- package/dist/contracts/session-voice-contracts.js +45 -0
- package/dist/contracts/source-set-contracts.js +26 -0
- package/dist/contracts/source-upload-contracts.js +22 -0
- package/dist/contracts/tool-command-contracts.js +57 -0
- package/dist/contracts/tool-connection-contracts.js +87 -0
- package/dist/contracts/tool-contribution-contracts.js +35 -0
- package/dist/contracts/tool-group-contracts.js +53 -0
- package/dist/contracts/tool-group-state-contracts.js +63 -0
- package/dist/contracts/tool-lifecycle-contracts.js +94 -0
- package/dist/contracts/tool-package-contracts.js +91 -0
- package/dist/contracts/tool-protocol-contracts.js +71 -0
- package/dist/contracts/tool-runtime-billing-contracts.js +28 -0
- package/dist/contracts/tool-setup-proposal.js +59 -0
- package/dist/contracts/tool-state-contracts.js +79 -0
- package/dist/contracts/trial-state-contracts.js +30 -0
- package/dist/contracts/typescript/agent-change.js +1 -0
- package/dist/contracts/typescript/assessment-aggregate.js +1 -0
- package/dist/contracts/typescript/assessment.js +1 -0
- package/dist/contracts/typescript/billing-commerce.js +1 -0
- package/dist/contracts/typescript/campaigns.js +1 -0
- package/dist/contracts/typescript/catalog.js +1 -0
- package/dist/contracts/typescript/ci-access.js +1 -0
- package/dist/contracts/typescript/ci-configuration.js +1 -0
- package/dist/contracts/typescript/ci-delivery.js +1 -0
- package/dist/contracts/typescript/ci-result.js +1 -0
- package/dist/contracts/typescript/ci-runs.js +1 -0
- package/dist/contracts/typescript/clips.js +1 -0
- package/dist/contracts/typescript/comparison.js +1 -0
- package/dist/contracts/typescript/composite.js +1 -0
- package/dist/contracts/typescript/connections.js +1 -0
- package/dist/contracts/typescript/context.js +1 -0
- package/dist/contracts/typescript/datasets.js +1 -0
- package/dist/contracts/typescript/evidence-coverage.js +1 -0
- package/dist/contracts/typescript/export.js +1 -0
- package/dist/contracts/typescript/github-directory.js +1 -0
- package/dist/contracts/typescript/insight.js +1 -0
- package/dist/contracts/typescript/inspection.js +1 -0
- package/dist/contracts/typescript/intake.js +1 -0
- package/dist/contracts/typescript/local-claim.js +1 -0
- package/dist/contracts/typescript/local-configuration.js +1 -0
- package/dist/contracts/typescript/local-evidence.js +1 -0
- package/dist/contracts/typescript/local-grant.js +1 -0
- package/dist/contracts/typescript/local-input.js +1 -0
- package/dist/contracts/typescript/local-runner.js +1 -0
- package/dist/contracts/typescript/local-trial-live.js +1 -0
- package/dist/contracts/typescript/local-value-source.js +1 -0
- package/dist/contracts/typescript/local-world-native.js +1 -0
- package/dist/contracts/typescript/mcp.js +1 -0
- package/dist/contracts/typescript/media.js +1 -0
- package/dist/contracts/typescript/native-check.js +1 -0
- package/dist/contracts/typescript/native-identity.js +1 -0
- package/dist/contracts/typescript/onboarding.js +1 -0
- package/dist/contracts/typescript/performance.js +1 -0
- package/dist/contracts/typescript/planning.js +1 -0
- package/dist/contracts/typescript/preparation.js +1 -0
- package/dist/contracts/typescript/real-operation.js +1 -0
- package/dist/contracts/typescript/recommendation.js +1 -0
- package/dist/contracts/typescript/reports.js +1 -0
- package/dist/contracts/typescript/resource-lifecycle.js +1 -0
- package/dist/contracts/typescript/saved-record.js +1 -0
- package/dist/contracts/typescript/scenarios.js +1 -0
- package/dist/contracts/typescript/services.js +1 -0
- package/dist/contracts/typescript/session-command.js +1 -0
- package/dist/contracts/typescript/session-state.js +1 -0
- package/dist/contracts/typescript/session-voice.js +1 -0
- package/dist/contracts/typescript/session.js +1 -0
- package/dist/contracts/typescript/tool-command.js +1 -0
- package/dist/contracts/typescript/tool-connection.js +1 -0
- package/dist/contracts/typescript/tool-contribution.js +1 -0
- package/dist/contracts/typescript/tool-groups.js +1 -0
- package/dist/contracts/typescript/tool-lifecycle.js +1 -0
- package/dist/contracts/typescript/tool-package.js +1 -0
- package/dist/contracts/typescript/tool-state.js +1 -0
- package/dist/contracts/typescript/trial-state.js +1 -0
- package/dist/contracts/typescript/vault.js +1 -0
- package/dist/contracts/typescript/workspace-team.js +1 -0
- package/dist/contracts/typescript/world-stream.js +1 -0
- package/dist/contracts/workspace-team-contracts.js +55 -0
- package/dist/contracts/world-command.js +65 -0
- package/dist/contracts/world-lifetime-contracts.js +74 -0
- package/dist/contracts/world-recipe-contracts.js +27 -0
- package/dist/contracts/world-stream-contracts.js +27 -0
- package/dist/package.json +1 -0
- package/package.json +16 -16
- package/LICENSE.md +0 -23
- package/PACKAGING.json +0 -50
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { open } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { api } from './auth.js';
|
|
5
|
+
import { privateDirectory } from './credentials.js';
|
|
6
|
+
import { CliError, releaseClosed } from './http.js';
|
|
7
|
+
import { choosePaged, interactive } from './configuration.js';
|
|
8
|
+
import { selectOne, terminalText } from './prompts.js';
|
|
9
|
+
import { ownedSourceMetadataFile, readSourceMetadata, writeSourceMetadata } from './source-connection.js';
|
|
10
|
+
import { withBootstrapProcessLock } from './bootstrap-process-lock.js';
|
|
11
|
+
import { selectedWorkspace } from './world-access.js';
|
|
12
|
+
import { identifier } from './world-contracts.js';
|
|
13
|
+
const invalid = () => new CliError('world_creation_unconfirmed', 'The original world request needs attention. No replacement world or build was started.', 2);
|
|
14
|
+
const location = async (scope, key) => join(await privateDirectory(), `world-create-${identifier(scope.owner)}-${identifier(scope.workspace)}-${identifier(key)}.json`);
|
|
15
|
+
const activeLocation = async (scope, agent) => join(await privateDirectory(), `world-create-active-${identifier(scope.owner)}-${identifier(scope.workspace)}-${identifier(agent)}.json`);
|
|
16
|
+
const access = (scope, signal) => ({ actor: scope.owner, workspace: scope.workspace, signal });
|
|
17
|
+
const base = (intent) => `/api/workspaces/${intent.workspace}/agents/${intent.agent}`;
|
|
18
|
+
const body = (intent) => ({ actor_id: intent.owner, source_id: intent.source });
|
|
19
|
+
const requestPath = (intent) => `${base(intent)}/world-requests/${intent.key}`;
|
|
20
|
+
async function readFile(path) {
|
|
21
|
+
const file = await ownedSourceMetadataFile(path, 4096);
|
|
22
|
+
try {
|
|
23
|
+
return await readSourceMetadata(file, 4096);
|
|
24
|
+
}
|
|
25
|
+
finally {
|
|
26
|
+
await file.close();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
async function retained(scope, key) {
|
|
30
|
+
const value = await readFile(await location(scope, key));
|
|
31
|
+
if (!value || value.schema !== 'firedrill.local-world-creation.v1' || value.owner !== scope.owner
|
|
32
|
+
|| value.workspace !== scope.workspace || value.key !== identifier(key) || typeof value.dispatched !== 'boolean'
|
|
33
|
+
|| Object.keys(value).sort().join(',') !== 'agent,dispatched,key,owner,schema,source,workspace')
|
|
34
|
+
throw invalid();
|
|
35
|
+
identifier(value.agent);
|
|
36
|
+
identifier(value.source);
|
|
37
|
+
return value;
|
|
38
|
+
}
|
|
39
|
+
async function active(scope, agent) {
|
|
40
|
+
let value;
|
|
41
|
+
try {
|
|
42
|
+
value = await readFile(await activeLocation(scope, agent));
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
if (error.code === 'ENOENT')
|
|
46
|
+
return null;
|
|
47
|
+
throw error;
|
|
48
|
+
}
|
|
49
|
+
if (!value || value.schema !== 'firedrill.local-world-active.v1' || value.owner !== scope.owner
|
|
50
|
+
|| value.workspace !== scope.workspace || value.agent !== agent || typeof value.key !== 'string'
|
|
51
|
+
|| Object.keys(value).sort().join(',') !== 'agent,key,owner,schema,workspace')
|
|
52
|
+
throw invalid();
|
|
53
|
+
return retained(scope, identifier(value.key));
|
|
54
|
+
}
|
|
55
|
+
async function saveNew(intent) {
|
|
56
|
+
try {
|
|
57
|
+
const file = await open(await location(intent, intent.key), 'wx', 0o600);
|
|
58
|
+
try {
|
|
59
|
+
await file.writeFile(JSON.stringify(intent));
|
|
60
|
+
await file.sync();
|
|
61
|
+
}
|
|
62
|
+
finally {
|
|
63
|
+
await file.close();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
if (error.code !== 'EEXIST')
|
|
68
|
+
throw error;
|
|
69
|
+
const original = await retained(intent, intent.key);
|
|
70
|
+
if (original.agent !== intent.agent || original.source !== intent.source)
|
|
71
|
+
throw invalid();
|
|
72
|
+
intent.dispatched = original.dispatched;
|
|
73
|
+
}
|
|
74
|
+
await writeSourceMetadata(await activeLocation(intent, intent.agent), { schema: 'firedrill.local-world-active.v1',
|
|
75
|
+
owner: intent.owner, workspace: intent.workspace, agent: intent.agent, key: intent.key });
|
|
76
|
+
}
|
|
77
|
+
function checked(value, intent) {
|
|
78
|
+
if (!value || value.schema !== 'firedrill.world-creation.v1' || value.actor_id !== intent.owner
|
|
79
|
+
|| value.workspace_id !== intent.workspace || value.agent_id !== intent.agent || value.source_id !== intent.source
|
|
80
|
+
|| value.request_key !== intent.key || !['not_received', 'accepted', 'withdrawn'].includes(value.state)
|
|
81
|
+
|| typeof value.resource_available !== 'boolean'
|
|
82
|
+
|| value.state === 'accepted' && (!value.world_id || identifier(value.world_id) !== value.world_id)
|
|
83
|
+
|| value.state !== 'accepted' && (value.world_id !== null || value.resource_available)
|
|
84
|
+
|| (value.resource_available ? value.detail_url !== `/worlds/${value.world_id}` : value.detail_url !== null))
|
|
85
|
+
throw invalid();
|
|
86
|
+
return value;
|
|
87
|
+
}
|
|
88
|
+
async function lookup(intent, signal) {
|
|
89
|
+
return checked(await api(`${requestPath(intent)}?${new URLSearchParams(body(intent))}`, access(intent, signal)), intent);
|
|
90
|
+
}
|
|
91
|
+
function show(value, output) {
|
|
92
|
+
output(value, value.state === 'accepted' && value.resource_available
|
|
93
|
+
? `World created: ${value.world_id}\nInspect it with firedrill worlds ${value.world_id}. No build or simulation started.`
|
|
94
|
+
: value.state === 'accepted' ? 'The original world is no longer available. No replacement was created.'
|
|
95
|
+
: value.state === 'withdrawn' ? 'The unsent world request was withdrawn. A delayed request cannot create it.'
|
|
96
|
+
: `World request ${value.request_key} is not yet confirmed. It may still arrive.\nCheck: firedrill worlds creation ${value.request_key}`);
|
|
97
|
+
}
|
|
98
|
+
async function approve(options, label, confirmLabel = 'Create world') {
|
|
99
|
+
if (options.approve)
|
|
100
|
+
return;
|
|
101
|
+
if (!interactive(options))
|
|
102
|
+
throw new CliError('world_creation_approval_required', 'Review the agent source and use --approve-change to create a world. No build or simulation will start.', 2);
|
|
103
|
+
if (!await selectOne(label, [{ value: false, label: 'Cancel' }, { value: true, label: confirmLabel }], options.signal))
|
|
104
|
+
throw new CliError('canceled', 'No world request was submitted.', 130);
|
|
105
|
+
}
|
|
106
|
+
async function submit(intent, options, output, explicitContinuation = false) {
|
|
107
|
+
let current = await lookup(intent, options.signal);
|
|
108
|
+
if (current.state !== 'not_received') {
|
|
109
|
+
show(current, output);
|
|
110
|
+
return current;
|
|
111
|
+
}
|
|
112
|
+
if (intent.dispatched && !explicitContinuation) {
|
|
113
|
+
show(current, output);
|
|
114
|
+
throw new CliError('world_creation_pending', `Inspect or continue the original request with firedrill worlds continue-creation ${intent.key}. No request was repeated.`, 2);
|
|
115
|
+
}
|
|
116
|
+
intent.dispatched = true;
|
|
117
|
+
await writeSourceMetadata(await location(intent, intent.key), intent);
|
|
118
|
+
output({ phase: 'world_request_prepared', request_key: intent.key, agent_id: intent.agent, source_id: intent.source }, `World request ${intent.key}. Recovery: firedrill worlds creation ${intent.key}.`);
|
|
119
|
+
try {
|
|
120
|
+
current = checked(await api(`${base(intent)}/worlds`, { ...access(intent, options.signal),
|
|
121
|
+
method: 'POST', body: body(intent), idempotencyKey: intent.key }), intent);
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
if (options.signal.aborted || releaseClosed(error))
|
|
125
|
+
throw error;
|
|
126
|
+
// A lost write response permits a read of the same intent, not another POST.
|
|
127
|
+
current = await lookup(intent, options.signal);
|
|
128
|
+
if (current.state === 'not_received') {
|
|
129
|
+
show(current, output);
|
|
130
|
+
throw new CliError('world_creation_pending', `Creation is not confirmed. Check the original request ${intent.key}; do not start a replacement.`, 2);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
show(current, output);
|
|
134
|
+
return current;
|
|
135
|
+
}
|
|
136
|
+
export async function createWorld(options, output, selectedAgent, scope) {
|
|
137
|
+
const context = scope ?? await selectedWorkspace({ ...options, interactive: interactive(options) });
|
|
138
|
+
let agentId = selectedAgent ? identifier(selectedAgent) : undefined;
|
|
139
|
+
if (!agentId && interactive(options))
|
|
140
|
+
agentId = (await choosePaged('Agent', `/api/workspaces/${context.workspace}/agents`, context, options, row => ({ label: row.name, detail: row.source_label })))?.id;
|
|
141
|
+
if (!agentId)
|
|
142
|
+
throw new CliError('agent_required', 'Use worlds new <agent> or select a saved agent interactively.', 2);
|
|
143
|
+
const agent = identifier(agentId);
|
|
144
|
+
return withBootstrapProcessLock(join(await privateDirectory(), `world-create-lock-${context.owner}-${context.workspace}-${agent}`), options.signal, async (signal) => {
|
|
145
|
+
const currentOptions = { ...options, signal };
|
|
146
|
+
const previous = options.request ? await retained(context, identifier(options.request)).catch(error => {
|
|
147
|
+
if (error.code === 'ENOENT')
|
|
148
|
+
return null;
|
|
149
|
+
throw error;
|
|
150
|
+
}) : await active(context, agent);
|
|
151
|
+
if (previous) {
|
|
152
|
+
if (previous.agent !== agent)
|
|
153
|
+
throw invalid();
|
|
154
|
+
const receipt = await lookup(previous, signal);
|
|
155
|
+
if (receipt.state === 'not_received')
|
|
156
|
+
return submit(previous, currentOptions, output);
|
|
157
|
+
if (options.request || !interactive(options)) {
|
|
158
|
+
show(receipt, output);
|
|
159
|
+
return receipt;
|
|
160
|
+
}
|
|
161
|
+
const next = await selectOne('World', [{ value: 'existing', label: receipt.resource_available ? 'Continue with the existing world' : 'Show the original request' },
|
|
162
|
+
{ value: 'new', label: 'Create another world', detail: 'Separate state using the agent’s current source.' }], signal);
|
|
163
|
+
if (next === 'existing') {
|
|
164
|
+
show(receipt, output);
|
|
165
|
+
return receipt;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
const saved = await api(`/api/workspaces/${context.workspace}/agents/${agent}`, access(context, signal));
|
|
169
|
+
if (saved.id !== agent || !saved.source?.id)
|
|
170
|
+
throw new CliError('agent_source_required', 'This agent has no connected source. Finish its source connection first.', 2);
|
|
171
|
+
const source = identifier(saved.source.id);
|
|
172
|
+
await approve(currentOptions, `Create a world for ${terminalText(saved.name)}? Source: ${terminalText(saved.source.label)} · ${terminalText(saved.source.revision)}. No build starts.`);
|
|
173
|
+
const intent = { ...context, schema: 'firedrill.local-world-creation.v1', key: options.request ? identifier(options.request) : randomUUID(),
|
|
174
|
+
agent, source, dispatched: false };
|
|
175
|
+
await saveNew(intent);
|
|
176
|
+
return submit(intent, currentOptions, output);
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
export async function worldCreationCommand(args, options, output) {
|
|
180
|
+
if (args.length !== 2 || !['creation', 'continue-creation', 'withdraw-creation'].includes(args[0]) || options.request)
|
|
181
|
+
throw invalid();
|
|
182
|
+
const scope = await selectedWorkspace({ ...options, interactive: interactive(options) });
|
|
183
|
+
const key = identifier(args[1]), original = await retained(scope, key);
|
|
184
|
+
return withBootstrapProcessLock(join(await privateDirectory(), `world-create-lock-${scope.owner}-${scope.workspace}-${original.agent}`), options.signal, async (signal) => {
|
|
185
|
+
const currentOptions = { ...options, signal }, intent = await retained(scope, key);
|
|
186
|
+
const value = await lookup(intent, signal);
|
|
187
|
+
if (args[0] === 'creation' || value.state !== 'not_received') {
|
|
188
|
+
show(value, output);
|
|
189
|
+
return value;
|
|
190
|
+
}
|
|
191
|
+
await approve(currentOptions, args[0] === 'continue-creation' ? 'Continue the exact original world request?' : 'Withdraw the unsent world request?', args[0] === 'continue-creation' ? 'Continue original request' : 'Withdraw request');
|
|
192
|
+
if (args[0] === 'continue-creation')
|
|
193
|
+
return submit(intent, currentOptions, output, true);
|
|
194
|
+
const withdrawn = checked(await api(requestPath(intent), { ...access(scope, signal), method: 'DELETE', body: body(intent) }), intent);
|
|
195
|
+
const observed = await lookup(intent, signal);
|
|
196
|
+
if (withdrawn.state !== observed.state || withdrawn.world_id !== observed.world_id)
|
|
197
|
+
throw invalid();
|
|
198
|
+
show(observed, output);
|
|
199
|
+
return observed;
|
|
200
|
+
});
|
|
201
|
+
}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { createInterface } from 'node:readline/promises';
|
|
3
|
+
import { api } from './auth.js';
|
|
4
|
+
import { CliError } from './http.js';
|
|
5
|
+
import { selectOne, terminalText } from './prompts.js';
|
|
6
|
+
import { datasetRecipeFile } from './tool-datasets.js';
|
|
7
|
+
import { identifier } from './world-contracts.js';
|
|
8
|
+
import { selectedWorld } from './world-access.js';
|
|
9
|
+
import { exportDataset } from './dataset-export.js';
|
|
10
|
+
import { datasetReads } from './dataset-reads.js';
|
|
11
|
+
import { datasetRead, datasetMatches, datasetEdits } from './dataset-contracts.js';
|
|
12
|
+
import { worldSessionMatches, worldSessionRequestMatches, worldSessionsPageMatches } from '../../contracts/session-contracts.js';
|
|
13
|
+
const text = (row) => `${row.id} ${terminalText(row.name)} · version ${row.version} · ${row.state}\n${row.edit_count} saved edits`
|
|
14
|
+
+ (row.problem ? '\n' + terminalText(row.problem) : '') + '\nSaving edits does not change a running world.';
|
|
15
|
+
async function question(title, signal) {
|
|
16
|
+
const prompt = createInterface({ input: process.stdin, output: process.stderr, terminal: false });
|
|
17
|
+
try {
|
|
18
|
+
return (await prompt.question(title, { signal })).trim();
|
|
19
|
+
}
|
|
20
|
+
finally {
|
|
21
|
+
prompt.close();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export async function worldDatasetCommand(args, options, output) {
|
|
25
|
+
const action = args[0] ?? 'list', save = action === 'save', start = action === 'start', write = save || start || action === 'withdraw';
|
|
26
|
+
// Opening a managed world session is retired (FIR-504); start-status still reads an original opening request.
|
|
27
|
+
if (start)
|
|
28
|
+
throw new CliError('legacy_launch_retired', 'Opening a managed world session is retired. Saved dataset versions and original opening requests (datasets start-status) stay readable. Nothing was sent.', 2);
|
|
29
|
+
if (!['list', 'show', 'document', 'export', 'request', 'save', 'withdraw', 'start', 'start-status'].includes(action) || args.length > (save ? 3 : 2)
|
|
30
|
+
|| !save && options.file !== undefined || !save && !start && options.request !== undefined || !write && options.approve !== undefined
|
|
31
|
+
|| action !== 'export' && (options.into !== undefined || options.approveDownload !== undefined)
|
|
32
|
+
|| action !== 'list' && (options.page !== undefined || options.search !== undefined)
|
|
33
|
+
|| options.page !== undefined && !/^[1-9][0-9]{0,8}$/.test(options.page) || (options.search?.length ?? 0) > 200) {
|
|
34
|
+
throw new CliError('arguments_invalid', 'Use datasets list [capture], show|document <version>, export [version] --into <file.json>, request <request>, save [capture] [name] --file <edits.json>, withdraw <version>, start [version], or start-status <request>.', 2);
|
|
35
|
+
}
|
|
36
|
+
const tty = !options.json && Boolean(process.stdin.isTTY && process.stderr.isTTY);
|
|
37
|
+
const scope = await selectedWorld({ ...options, interactive: tty });
|
|
38
|
+
const auth = { actor: scope.owner, workspace: scope.workspace, signal: options.signal };
|
|
39
|
+
const identity = { actor: scope.owner, workspace: scope.workspace, world: scope.world };
|
|
40
|
+
const reads = datasetReads(scope.prefix, scope, options.signal), selectedScope = { ...identity, kind: 'world' };
|
|
41
|
+
if (action === 'export')
|
|
42
|
+
return exportDataset(scope.prefix, args[1], scope, options, output);
|
|
43
|
+
const showSession = (row) => output('world_dataset_session', row, `Session: ${row.id}\n${terminalText(row.state)}\n`
|
|
44
|
+
+ (row.starting_data ? `${row.starting_data.edit_count} starting-data edits applied at ${row.starting_data.applied_at}; ${row.starting_data.rows_affected} rows directly affected.`
|
|
45
|
+
: 'Starting-data application has not been confirmed.') + (row.problem ? '\n' + terminalText(row.problem) : ''));
|
|
46
|
+
if (action === 'start-status') {
|
|
47
|
+
const key = identifier(args[1] ?? '');
|
|
48
|
+
const result = await api(`${scope.prefix}/runtime-session-request?request_key=${key}`, auth);
|
|
49
|
+
if (!worldSessionRequestMatches(result, identity, key))
|
|
50
|
+
throw new CliError('world_session_recovery_unavailable', 'The original request could not be confirmed. No session was started.', 2);
|
|
51
|
+
output('world_dataset_session_request', result, `Request: ${key}\n${terminalText(result.state)}`);
|
|
52
|
+
if (result.session)
|
|
53
|
+
showSession(result.session);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (start) {
|
|
57
|
+
let id = args[1];
|
|
58
|
+
if (!id && tty) {
|
|
59
|
+
let page = 1;
|
|
60
|
+
while (!id) {
|
|
61
|
+
const result = await reads.page({ startable: true, page });
|
|
62
|
+
const choices = result.results.filter(one => one.kind === 'world' && one.can_start_session).map(one => ({ value: one.id,
|
|
63
|
+
label: `${one.name} · Version ${one.version}`, detail: `${one.edit_count} starting-data edits` }));
|
|
64
|
+
choices.unshift({ value: 'cancel', label: 'Cancel', detail: 'No session will be opened.' });
|
|
65
|
+
if (page > 1)
|
|
66
|
+
choices.push({ value: 'previous', label: 'Previous page', detail: '' });
|
|
67
|
+
if (result.next)
|
|
68
|
+
choices.push({ value: 'next', label: 'Next page', detail: '' });
|
|
69
|
+
const choice = await selectOne('Starting data for a managed world session', choices, options.signal);
|
|
70
|
+
if (choice === 'cancel')
|
|
71
|
+
throw new CliError('canceled', 'No world session was opened.', 130);
|
|
72
|
+
if (choice === 'next')
|
|
73
|
+
page += 1;
|
|
74
|
+
else if (choice === 'previous')
|
|
75
|
+
page -= 1;
|
|
76
|
+
else
|
|
77
|
+
id = identifier(choice);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
id = identifier(id ?? '');
|
|
81
|
+
const key = identifier(options.request ?? randomUUID());
|
|
82
|
+
if (options.request) {
|
|
83
|
+
const receipt = await api(`${scope.prefix}/runtime-session-request?request_key=${key}`, auth);
|
|
84
|
+
if (!worldSessionRequestMatches(receipt, identity, key) || receipt.withdrawn || receipt.session && receipt.session.dataset_id !== id)
|
|
85
|
+
throw new CliError('world_session_recovery_unavailable', 'The original request is withdrawn or belongs to different data. No replacement was started.', 2);
|
|
86
|
+
if (receipt.session) {
|
|
87
|
+
identifier(receipt.session.id);
|
|
88
|
+
showSession(receipt.session);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (receipt.state !== 'not_observed')
|
|
92
|
+
throw new CliError('world_session_pending', `Inspect datasets start-status ${key} before retrying. No replacement was started.`, 2);
|
|
93
|
+
}
|
|
94
|
+
const dataset = await reads.detail(id);
|
|
95
|
+
if (dataset.kind !== 'world' || !dataset.available || !dataset.can_start_session)
|
|
96
|
+
throw new CliError('world_dataset_base_changed', 'Choose retained starting data for this exact prepared world. No session was opened.', 2);
|
|
97
|
+
const current = await api(`${scope.prefix}/runtime-sessions?state=active&page_size=1`, auth);
|
|
98
|
+
if (!worldSessionsPageMatches(current, identity, 'active'))
|
|
99
|
+
throw new CliError('world_session_scope_invalid', 'The current opening scope could not be confirmed. No session was queued.', 2);
|
|
100
|
+
if (current.opening_restriction) {
|
|
101
|
+
throw new CliError('world_session_opening_restricted', terminalText(current.opening_restriction), 2);
|
|
102
|
+
}
|
|
103
|
+
const selected = current.opening_scope;
|
|
104
|
+
if (!selected || !Number.isSafeInteger(selected.world_revision) || selected.world_revision < 1
|
|
105
|
+
|| !Number.isSafeInteger(selected.configuration_revision) || selected.configuration_revision < 0
|
|
106
|
+
|| !Number.isSafeInteger(selected.duration_seconds) || selected.duration_seconds < 1 || selected.duration_seconds > 2147483647
|
|
107
|
+
|| selected.real_connections_count !== 0 || typeof selected.source_revision !== 'string' || !selected.source_revision.length) {
|
|
108
|
+
throw new CliError('world_session_scope_invalid', 'The current isolated world and starting-data scope could not be confirmed. No session was opened.', 2);
|
|
109
|
+
}
|
|
110
|
+
output('world_dataset_session_scope', { world_id: scope.world, dataset_id: id, name: dataset.name, version: dataset.version,
|
|
111
|
+
duration_seconds: selected.duration_seconds, real_connections_count: 0 }, `${terminalText(dataset.name)} · Version ${dataset.version}\nManaged session: ${selected.duration_seconds} seconds. Startup uses the agent's approved runtime. No real tool accounts are included.`);
|
|
112
|
+
const approved = options.approve || tty && await selectOne('Open a managed world session?', [{ value: false, label: 'Cancel' }, { value: true, label: 'Open session with saved data' }], options.signal);
|
|
113
|
+
if (!approved)
|
|
114
|
+
throw new CliError('dataset_consent_required', 'Use --approve-dataset to open a managed session with this saved data. No session was queued.', 2);
|
|
115
|
+
const body = { world_revision: selected.world_revision, configuration_revision: selected.configuration_revision,
|
|
116
|
+
source_id: identifier(selected.source_id), source_revision: selected.source_revision, duration_seconds: selected.duration_seconds, dataset_id: id };
|
|
117
|
+
output('world_dataset_session_intent', { request_key: key, world_id: scope.world, dataset_id: id }, `Request: ${key}\nIf interrupted, use datasets start-status ${key}. Do not submit a replacement request.`);
|
|
118
|
+
const row = await api(`${scope.prefix}/runtime-sessions`, { ...auth, method: 'POST', idempotencyKey: key, body });
|
|
119
|
+
if (!worldSessionMatches(row, identity, { key, body })) {
|
|
120
|
+
throw new CliError('world_session_acceptance_unconfirmed', `Read datasets start-status ${key}. The returned session identity could not be confirmed.`, 2);
|
|
121
|
+
}
|
|
122
|
+
const receipt = await api(`${scope.prefix}/runtime-session-request?request_key=${key}`, auth);
|
|
123
|
+
if (!worldSessionRequestMatches(receipt, identity, key, body) || receipt.withdrawn || !receipt.session || receipt.session.id !== row.id)
|
|
124
|
+
throw new CliError('world_session_acceptance_unconfirmed', `Read datasets start-status ${key}. Original acceptance could not be confirmed.`, 2);
|
|
125
|
+
showSession(receipt.session);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (action === 'list') {
|
|
129
|
+
const result = await reads.page({ page: Number(options.page ?? 1), query: options.search, source: args[1] ? identifier(args[1]) : undefined });
|
|
130
|
+
output('world_datasets', result, (result.results.map(text).join('\n\n') || 'No matching datasets in this world.')
|
|
131
|
+
+ `\nPage ${result.page}, ${result.count} matching datasets.${result.next ? ` Use --page ${result.page + 1} with the same capture and search to continue.` : ''}`);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
if (['show', 'document', 'request'].includes(action)) {
|
|
135
|
+
const key = identifier(args[1] ?? ''), document = action === 'document';
|
|
136
|
+
const row = action === 'request' ? await reads.original(key) : await reads.detail(key);
|
|
137
|
+
if (document) {
|
|
138
|
+
const value = await reads.document(row);
|
|
139
|
+
output('world_dataset_document', value, JSON.stringify(value, null, 2));
|
|
140
|
+
}
|
|
141
|
+
else
|
|
142
|
+
output('world_dataset', row, text(row));
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
if (action === 'withdraw') {
|
|
146
|
+
const key = identifier(args[1] ?? ''), row = await reads.detail(key);
|
|
147
|
+
const approved = options.approve || tty && await selectOne(`Withdraw ${terminalText(row.name)} version ${row.version}? Other versions and running worlds stay unchanged.`, [{ value: false, label: 'Cancel' }, { value: true, label: 'Withdraw version' }], options.signal);
|
|
148
|
+
if (!approved)
|
|
149
|
+
throw new CliError('dataset_consent_required', 'Use --approve-dataset to withdraw this exact version. No change was submitted.', 2);
|
|
150
|
+
const result = await api(`${scope.prefix}/datasets/${key}`, { ...auth, method: 'DELETE' });
|
|
151
|
+
datasetRead(result, datasetMatches(result, selectedScope, { id: key, digest: row.request_digest, withdrawn: true }));
|
|
152
|
+
output('world_dataset', result, text(result));
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
let capture = args[1];
|
|
156
|
+
if (!capture) {
|
|
157
|
+
const world = await api(`${scope.prefix}/preparation`, auth);
|
|
158
|
+
if (!world.starting_data.available || !world.starting_data.id)
|
|
159
|
+
throw new CliError('world_dataset_base_unavailable', 'This world has no retained starting-data capture. No file was uploaded.', 2);
|
|
160
|
+
capture = world.starting_data.id;
|
|
161
|
+
}
|
|
162
|
+
capture = identifier(capture);
|
|
163
|
+
const name = args[2] ?? (tty ? await question('Dataset name: ', options.signal) : '');
|
|
164
|
+
if (!name || name !== name.trim() || name.length > 120 || /[\u0000-\u001f\u007f-\u009f]/.test(name))
|
|
165
|
+
throw new CliError('world_dataset_name_required', 'Provide a dataset name of up to 120 characters.', 2);
|
|
166
|
+
const path = options.file ?? (tty ? await question('Dataset edits JSON path: ', options.signal) : '');
|
|
167
|
+
if (!path)
|
|
168
|
+
throw new CliError('world_dataset_file_required', 'Provide --file <edits.json>. No file was read or uploaded.', 2);
|
|
169
|
+
const catalog = await reads.page({ source: capture });
|
|
170
|
+
const maximum = catalog.capacity?.recipe_bytes;
|
|
171
|
+
if (!Number.isSafeInteger(maximum) || maximum < 1 || maximum > 8 * 1024 * 1024)
|
|
172
|
+
throw new CliError('world_dataset_capacity_invalid', 'Dataset transport capacity is unavailable. No file was read.', 2);
|
|
173
|
+
const input = await datasetRecipeFile(path, maximum, options.signal);
|
|
174
|
+
let edits = input, previous = null;
|
|
175
|
+
if (input && typeof input === 'object' && !Array.isArray(input)) {
|
|
176
|
+
const value = input;
|
|
177
|
+
if (value.schema === 'firedrill.world-dataset.v1') {
|
|
178
|
+
if (value.world_id !== scope.world || value.observation_id !== capture)
|
|
179
|
+
throw new CliError('world_dataset_base_invalid', 'This export belongs to different starting data. No edits were submitted.', 2);
|
|
180
|
+
edits = value.edits;
|
|
181
|
+
}
|
|
182
|
+
else if (Object.keys(value).every(key => ['edits', 'previous_version_id'].includes(key)) && Array.isArray(value.edits)) {
|
|
183
|
+
edits = value.edits;
|
|
184
|
+
if (value.previous_version_id !== null && value.previous_version_id !== undefined)
|
|
185
|
+
previous = identifier(String(value.previous_version_id));
|
|
186
|
+
}
|
|
187
|
+
else
|
|
188
|
+
throw new CliError('world_dataset_file_invalid', 'Use an edit array, an exported world dataset or edits with an optional previous_version_id.', 2);
|
|
189
|
+
}
|
|
190
|
+
if (!datasetEdits(edits))
|
|
191
|
+
throw new CliError('world_dataset_file_invalid', 'Use a typed edit array. Keep exact integer and decimal values as strings; unsafe numeric values are not rewritten.', 2);
|
|
192
|
+
const body = { observation_id: capture, previous_version_id: previous, name, edits, save_consent: true };
|
|
193
|
+
if (Buffer.byteLength(JSON.stringify(body)) > maximum)
|
|
194
|
+
throw new CliError('world_dataset_capacity', 'These edits exceed dataset transport capacity.', 2);
|
|
195
|
+
const approved = options.approve || tty && await selectOne(`Save ${edits.length} synthetic-data edits as a private version? This will not change a running world.`, [{ value: false, label: 'Cancel' }, { value: true, label: 'Save dataset' }], options.signal);
|
|
196
|
+
if (!approved)
|
|
197
|
+
throw new CliError('dataset_consent_required', 'Use --approve-dataset to save the selected edits. No edits were submitted.', 2);
|
|
198
|
+
const key = identifier(options.request ?? randomUUID());
|
|
199
|
+
output('world_dataset_intent', { request_key: key, world_id: scope.world, observation_id: capture, name }, `Request: ${key}\nIf interrupted, use datasets request ${key}. Keep the original file and request ID; do not submit a replacement.`);
|
|
200
|
+
const row = await api(`${scope.prefix}/datasets`, { ...auth, method: 'POST', body, idempotencyKey: key });
|
|
201
|
+
const selection = { key, source: capture, previous, name, editCount: edits.length };
|
|
202
|
+
datasetRead(row, datasetMatches(row, selectedScope, selection));
|
|
203
|
+
const recorded = await reads.original(key, { ...selection, id: row.id, digest: row.request_digest });
|
|
204
|
+
output('world_dataset', recorded, text(recorded));
|
|
205
|
+
if (['failed', 'withdrawn'].includes(recorded.state))
|
|
206
|
+
throw new CliError('world_dataset_not_saved', recorded.problem ?? 'The original dataset save did not complete.', 2);
|
|
207
|
+
}
|