firedrill-run 2.2.5 → 3.0.1
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 +176 -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 +506 -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 +1627 -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 +96 -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,199 @@
|
|
|
1
|
+
/** Historical run closing evidence. Never launches, signals or guesses process exit. */
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { constants } from 'node:fs';
|
|
4
|
+
import { lstat, open, rename, unlink } from 'node:fs/promises';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { api } from '../auth.js';
|
|
7
|
+
import { privateDirectory } from '../credentials.js';
|
|
8
|
+
import { CliError } from '../http.js';
|
|
9
|
+
import { withHistoricalRunCleanup } from '../runner-identity.js';
|
|
10
|
+
import { selectedWorkspace } from '../world-access.js';
|
|
11
|
+
import { identifier } from '../world-contracts.js';
|
|
12
|
+
const UUID = /^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$/;
|
|
13
|
+
const record = (value) => value !== null && typeof value === 'object' && !Array.isArray(value) ? value : null;
|
|
14
|
+
const fail = (message) => { throw new CliError('native_run_cleanup_unconfirmed', message); };
|
|
15
|
+
function services(value) {
|
|
16
|
+
if (!Array.isArray(value) || value.length < 1 || value.length > 128)
|
|
17
|
+
return fail('The retained service snapshot is invalid. It was not replaced.');
|
|
18
|
+
const aliases = new Set(), launches = new Set();
|
|
19
|
+
for (const item of value) {
|
|
20
|
+
if (!record(item) || typeof item.service !== 'string' || !/^[a-z][a-z0-9_-]{0,63}$/.test(item.service)
|
|
21
|
+
|| typeof item.launch_id !== 'string' || !UUID.test(item.launch_id)
|
|
22
|
+
|| aliases.has(item.service) || launches.has(item.launch_id)
|
|
23
|
+
|| !['planned', 'running', 'unknown', 'not_started', 'reaped'].includes(item.state)
|
|
24
|
+
|| (item.state === 'reaped' ? !Number.isInteger(item.exit_code) || item.exit_code < -255 || item.exit_code > 255 : item.exit_code !== null))
|
|
25
|
+
return fail('The retained service snapshot is invalid. It was not replaced.');
|
|
26
|
+
aliases.add(item.service);
|
|
27
|
+
launches.add(item.launch_id);
|
|
28
|
+
}
|
|
29
|
+
return value;
|
|
30
|
+
}
|
|
31
|
+
async function readPrivate(path) {
|
|
32
|
+
let info;
|
|
33
|
+
try {
|
|
34
|
+
info = await lstat(path);
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
if (error.code === 'ENOENT')
|
|
38
|
+
return null;
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
41
|
+
if (!info.isFile() || info.isSymbolicLink() || info.size > 262144 || (info.mode & 0o077)
|
|
42
|
+
|| process.getuid && info.uid !== process.getuid())
|
|
43
|
+
return fail('The cleanup journal is not a bounded private file. Nothing was submitted.');
|
|
44
|
+
const file = await open(path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
|
|
45
|
+
try {
|
|
46
|
+
const current = await file.stat();
|
|
47
|
+
if (current.ino !== info.ino || current.dev !== info.dev || current.size !== info.size)
|
|
48
|
+
return fail('The cleanup journal changed while reading.');
|
|
49
|
+
const value = record(JSON.parse(await file.readFile('utf8')));
|
|
50
|
+
if (!value)
|
|
51
|
+
return fail('The cleanup journal is invalid.');
|
|
52
|
+
return value;
|
|
53
|
+
}
|
|
54
|
+
finally {
|
|
55
|
+
await file.close();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
async function persist(path, value) {
|
|
59
|
+
const temporary = `${path}.${randomUUID()}.tmp`;
|
|
60
|
+
const file = await open(temporary, 'wx', 0o600);
|
|
61
|
+
try {
|
|
62
|
+
await file.writeFile(JSON.stringify(value));
|
|
63
|
+
await file.sync();
|
|
64
|
+
}
|
|
65
|
+
finally {
|
|
66
|
+
await file.close();
|
|
67
|
+
}
|
|
68
|
+
await rename(temporary, path);
|
|
69
|
+
}
|
|
70
|
+
function sameServices(left, right) {
|
|
71
|
+
return left.length === right.length && left.every(one => right.some(other => one.service === other.service
|
|
72
|
+
&& one.launch_id === other.launch_id && one.state === other.state && one.exit_code === other.exit_code));
|
|
73
|
+
}
|
|
74
|
+
export async function runCleanupCommand(deviceKey, campaignId, options, emit) {
|
|
75
|
+
deviceKey = identifier(deviceKey);
|
|
76
|
+
campaignId = identifier(campaignId);
|
|
77
|
+
const scope = await selectedWorkspace({ workspace: options.workspace, interactive: !options.json && !!process.stdin.isTTY && !!process.stderr.isTTY,
|
|
78
|
+
openBrowser: options.openBrowser, signal: options.signal });
|
|
79
|
+
await withHistoricalRunCleanup(scope, deviceKey, options.signal, async (identity) => {
|
|
80
|
+
const directory = await privateDirectory();
|
|
81
|
+
const intentPath = join(directory, `native-run-cleanup-${scope.owner}-${scope.workspace}-${deviceKey}-${campaignId}.json`);
|
|
82
|
+
const path = `/api/workspaces/${scope.workspace}/native-executors/${identity.deviceId}/campaigns/${campaignId}/run/cleanup`;
|
|
83
|
+
const auth = { actor: scope.owner, workspace: scope.workspace, signal: identity.signal };
|
|
84
|
+
const decode = (value) => {
|
|
85
|
+
const row = record(value);
|
|
86
|
+
if (!row || typeof row.run_id !== 'string' || !UUID.test(row.run_id) || row.campaign_id !== campaignId
|
|
87
|
+
|| row.device_id !== identity.deviceId || typeof row.descriptor_digest !== 'string' || !/^[a-f0-9]{64}$/.test(row.descriptor_digest)
|
|
88
|
+
|| !Number.isSafeInteger(row.sequence) || Number(row.sequence) < 1 || !['active', 'uncertain', 'closed'].includes(String(row.state))
|
|
89
|
+
|| (row.state === 'closed' ? typeof row.closed_at !== 'string' || !Number.isFinite(Date.parse(row.closed_at)) : row.closed_at !== null)
|
|
90
|
+
|| row.process_provenance !== 'customer_device' || row.cloud_cleanup_complete !== false)
|
|
91
|
+
return fail('The cleanup response does not match the original run. Keep its journal.');
|
|
92
|
+
return { ...row, services: services(row.services) };
|
|
93
|
+
};
|
|
94
|
+
let current = decode(await api(path, auth));
|
|
95
|
+
const nextSequence = () => {
|
|
96
|
+
if (current.sequence >= Number.MAX_SAFE_INTEGER)
|
|
97
|
+
return fail('The original run sequence is exhausted. No replacement identity or event was created.');
|
|
98
|
+
return current.sequence + 1;
|
|
99
|
+
};
|
|
100
|
+
emit('native_run_cleanup_status', current, `Local run ${current.run_id}: ${current.state}. Cleanup does not start execution or prove cloud cleanup.`);
|
|
101
|
+
if (!options.approve) {
|
|
102
|
+
emit('native_run_cleanup_review', { campaign_id: campaignId, device_key: deviceKey }, `To recover saved closing evidence: firedrill runner run-cleanup ${deviceKey} ${campaignId} --approve-change. No process is stopped by this command.`);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const send = async (document, sourcePending = false) => {
|
|
106
|
+
const body = record(document.payload);
|
|
107
|
+
if (!body || body.schema !== 'firedrill.native-local-run-lifecycle.v1' || body.device_id !== identity.deviceId
|
|
108
|
+
|| body.campaign_id !== campaignId || body.run_id !== current.run_id || body.descriptor_digest !== current.descriptor_digest
|
|
109
|
+
|| !['observe', 'close'].includes(String(body.action)) || typeof body.request_key !== 'string' || !UUID.test(body.request_key)
|
|
110
|
+
|| !Number.isSafeInteger(body.sequence) || Number(body.sequence) < 1 || typeof document.signature !== 'string'
|
|
111
|
+
|| document.signature.length !== 88)
|
|
112
|
+
return fail('The original cleanup intent has a different scope. It was not submitted.');
|
|
113
|
+
const snapshot = services(body.services);
|
|
114
|
+
if (snapshot.length !== current.services.length || snapshot.some(one => !current.services.some(other => one.service === other.service && one.launch_id === other.launch_id)))
|
|
115
|
+
return fail('The original cleanup intent names different launches.');
|
|
116
|
+
await persist(intentPath, { schema: 'firedrill.run-cleanup-intent.v1', actor_id: scope.owner, workspace_id: scope.workspace,
|
|
117
|
+
device_key: deviceKey, campaign_id: campaignId, document, source_pending: sourcePending });
|
|
118
|
+
let result;
|
|
119
|
+
try {
|
|
120
|
+
result = decode(await api(path, { ...auth, method: 'POST', body: document }));
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
// A historical running observation may not have reached the normal
|
|
124
|
+
// endpoint. The cleanup endpoint definitively refuses that new event;
|
|
125
|
+
// keep the original companion journal, but do not trap cleanup on it.
|
|
126
|
+
if (sourcePending && error instanceof CliError && error.httpStatus === 409 && error.code === 'native_local_run_cleanup_only') {
|
|
127
|
+
await unlink(intentPath);
|
|
128
|
+
return current;
|
|
129
|
+
}
|
|
130
|
+
emit('native_run_cleanup_pending', { campaign_id: campaignId, device_key: deviceKey }, 'Closing evidence is not confirmed. Its exact signed intent is retained; rerun this same cleanup command. No replacement request was sent.');
|
|
131
|
+
throw error;
|
|
132
|
+
}
|
|
133
|
+
if (result.run_id !== current.run_id || result.sequence !== body.sequence || !sameServices(result.services, snapshot)
|
|
134
|
+
|| body.action === 'close' && result.state !== 'closed')
|
|
135
|
+
return fail('The cleanup receipt did not confirm the exact original event. Keep its journal.');
|
|
136
|
+
await unlink(intentPath);
|
|
137
|
+
return result;
|
|
138
|
+
};
|
|
139
|
+
const retained = await readPrivate(intentPath);
|
|
140
|
+
if (retained) {
|
|
141
|
+
if (retained.schema !== 'firedrill.run-cleanup-intent.v1' || retained.actor_id !== scope.owner || retained.workspace_id !== scope.workspace
|
|
142
|
+
|| retained.device_key !== deviceKey || retained.campaign_id !== campaignId || !record(retained.document))
|
|
143
|
+
return fail('The saved cleanup intent belongs to another scope.');
|
|
144
|
+
await send(retained.document, retained.source_pending === true);
|
|
145
|
+
current = decode(await api(path, auth));
|
|
146
|
+
}
|
|
147
|
+
if (current.state !== 'closed') {
|
|
148
|
+
const local = await readPrivate(join(directory, `native-local-run-${campaignId}-${deviceKey}.json`))
|
|
149
|
+
?? await readPrivate(join(directory, `native-local-run-${campaignId}.json`));
|
|
150
|
+
if (local && local.schema === 'firedrill.native-local-run-state.v1' && local.run_id === current.run_id
|
|
151
|
+
&& local.campaign_id === campaignId && local.descriptor_sha256 === current.descriptor_digest) {
|
|
152
|
+
const pending = record(local.pending);
|
|
153
|
+
if (pending && ['observe', 'close'].includes(String(pending.action))) {
|
|
154
|
+
const payload = { schema: 'firedrill.native-local-run-lifecycle.v1', campaign_id: campaignId,
|
|
155
|
+
device_id: identity.deviceId, descriptor_digest: current.descriptor_digest, ...pending };
|
|
156
|
+
await send(await identity.sign(payload), true);
|
|
157
|
+
current = decode(await api(path, auth));
|
|
158
|
+
}
|
|
159
|
+
if (current.state !== 'closed' && local.observed !== undefined && local.observed !== null) {
|
|
160
|
+
const observed = services(local.observed);
|
|
161
|
+
if (observed.length !== current.services.length || observed.some(one => !current.services.some(other => one.service === other.service && one.launch_id === other.launch_id)))
|
|
162
|
+
return fail('The saved exit observations name different launches.');
|
|
163
|
+
const snapshot = current.services.map(prior => {
|
|
164
|
+
if (prior.state === 'reaped' || prior.state === 'not_started')
|
|
165
|
+
return prior;
|
|
166
|
+
const actual = observed.find(one => one.service === prior.service);
|
|
167
|
+
if (actual.state === 'reaped')
|
|
168
|
+
return actual;
|
|
169
|
+
if (actual.state === 'not_started' && prior.state === 'planned')
|
|
170
|
+
return actual;
|
|
171
|
+
return { ...prior, state: 'unknown', exit_code: null };
|
|
172
|
+
});
|
|
173
|
+
if (!sameServices(snapshot, current.services)) {
|
|
174
|
+
const event = { run_id: current.run_id, request_key: randomUUID(), sequence: nextSequence(),
|
|
175
|
+
action: 'observe', services: snapshot };
|
|
176
|
+
current = await send(await identity.sign({ schema: 'firedrill.native-local-run-lifecycle.v1', campaign_id: campaignId,
|
|
177
|
+
device_id: identity.deviceId, descriptor_digest: current.descriptor_digest, ...event }));
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if (current.state === 'closed') {
|
|
183
|
+
emit('native_run_cleanup_closed', current, 'The original local run is closed. Cloud cleanup and accounting remain separate.');
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
const snapshot = current.services.map(one => one.state === 'running' || one.state === 'planned'
|
|
187
|
+
? { ...one, state: 'unknown', exit_code: null } : one);
|
|
188
|
+
const closed = snapshot.every(one => one.state === 'reaped' || one.state === 'not_started');
|
|
189
|
+
if (closed || !sameServices(snapshot, current.services)) {
|
|
190
|
+
const event = { run_id: current.run_id, request_key: randomUUID(), sequence: nextSequence(),
|
|
191
|
+
action: closed ? 'close' : 'observe', services: snapshot };
|
|
192
|
+
current = await send(await identity.sign({ schema: 'firedrill.native-local-run-lifecycle.v1', campaign_id: campaignId,
|
|
193
|
+
device_id: identity.deviceId, descriptor_digest: current.descriptor_digest, ...event }));
|
|
194
|
+
}
|
|
195
|
+
if (!closed)
|
|
196
|
+
throw new CliError('native_local_run_uncertain', 'Process termination remains unknown. This command does not guess PIDs or manufacture exit evidence. Keep the original run records; campaign completion remains blocked until truthful closing evidence is available.');
|
|
197
|
+
emit('native_run_cleanup_closed', current, 'The original local run is closed. Cloud cleanup and accounting remain separate.');
|
|
198
|
+
});
|
|
199
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run-scoped private scratch (Plan20 W09, spec 3.5).
|
|
3
|
+
*
|
|
4
|
+
* One private directory per companion run under the OS temporary directory
|
|
5
|
+
* (mkdtemp, 0700, owned by the current user), with one private subdirectory per
|
|
6
|
+
* drill. Scratch holds only run-scoped material (driver files, MCP client
|
|
7
|
+
* configuration, bounded captures); it is not an evidence journal and is removed
|
|
8
|
+
* when the drill or run ends. A synchronous removal is registered for process
|
|
9
|
+
* exit so a crash path that still reaches `exit` does not leave it behind.
|
|
10
|
+
*
|
|
11
|
+
* On Windows POSIX modes are not enforced by the OS; `permissions` reports
|
|
12
|
+
* `platform-default` so callers can disclose that limitation.
|
|
13
|
+
*/
|
|
14
|
+
import { rmSync } from 'node:fs';
|
|
15
|
+
import { chmod, lstat, mkdir, mkdtemp, realpath, rm } from 'node:fs/promises';
|
|
16
|
+
import { tmpdir } from 'node:os';
|
|
17
|
+
import { join } from 'node:path';
|
|
18
|
+
import { CliError } from '../http.js';
|
|
19
|
+
import { NATIVE_ID } from './contracts.js';
|
|
20
|
+
const POSIX = process.platform !== 'win32';
|
|
21
|
+
function refused() {
|
|
22
|
+
return new CliError('native_scratch_unavailable', 'The private scratch directory could not be created safely. Nothing was started.');
|
|
23
|
+
}
|
|
24
|
+
function checkPrivateDirectory(stats) {
|
|
25
|
+
if (!stats.isDirectory() || stats.isSymbolicLink())
|
|
26
|
+
throw refused();
|
|
27
|
+
if (POSIX) {
|
|
28
|
+
if (typeof process.getuid === 'function' && stats.uid !== process.getuid())
|
|
29
|
+
throw refused();
|
|
30
|
+
if ((stats.mode & 0o077) !== 0)
|
|
31
|
+
throw refused();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export async function createRunScratch() {
|
|
35
|
+
let created;
|
|
36
|
+
try {
|
|
37
|
+
// realpath first so the directory path is stable (e.g. macOS /var -> /private/var).
|
|
38
|
+
const base = await realpath(tmpdir());
|
|
39
|
+
created = await mkdtemp(join(base, 'firedrill-native-'));
|
|
40
|
+
if (POSIX)
|
|
41
|
+
await chmod(created, 0o700);
|
|
42
|
+
checkPrivateDirectory(await lstat(created));
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
if (error instanceof CliError)
|
|
46
|
+
throw error;
|
|
47
|
+
throw refused();
|
|
48
|
+
}
|
|
49
|
+
const root = created;
|
|
50
|
+
let removed = false;
|
|
51
|
+
const drills = new Map();
|
|
52
|
+
const onExit = () => { if (!removed) {
|
|
53
|
+
try {
|
|
54
|
+
rmSync(root, { recursive: true, force: true });
|
|
55
|
+
}
|
|
56
|
+
catch { /* best effort at exit */ }
|
|
57
|
+
} };
|
|
58
|
+
process.once('exit', onExit);
|
|
59
|
+
const cleanup = async () => {
|
|
60
|
+
if (removed)
|
|
61
|
+
return;
|
|
62
|
+
removed = true;
|
|
63
|
+
process.removeListener('exit', onExit);
|
|
64
|
+
drills.clear();
|
|
65
|
+
await rm(root, { recursive: true, force: true, maxRetries: 3 }).catch(() => undefined);
|
|
66
|
+
};
|
|
67
|
+
return {
|
|
68
|
+
path: root,
|
|
69
|
+
permissions: POSIX ? 'posix-0700' : 'platform-default',
|
|
70
|
+
async drill(key) {
|
|
71
|
+
if (removed)
|
|
72
|
+
throw refused();
|
|
73
|
+
if (typeof key !== 'string' || !NATIVE_ID.test(key)) {
|
|
74
|
+
throw new CliError('native_scratch_unavailable', 'The drill scratch directory was not created: its key is malformed.');
|
|
75
|
+
}
|
|
76
|
+
if (drills.has(key))
|
|
77
|
+
throw new CliError('native_scratch_unavailable', 'The drill scratch directory was not created: it already exists for this run.');
|
|
78
|
+
const path = join(root, key);
|
|
79
|
+
try {
|
|
80
|
+
await mkdir(path, { mode: 0o700 });
|
|
81
|
+
if (POSIX)
|
|
82
|
+
await chmod(path, 0o700);
|
|
83
|
+
checkPrivateDirectory(await lstat(path));
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
await rm(path, { recursive: true, force: true }).catch(() => undefined);
|
|
87
|
+
if (error instanceof CliError)
|
|
88
|
+
throw error;
|
|
89
|
+
throw refused();
|
|
90
|
+
}
|
|
91
|
+
let drillRemoved = false;
|
|
92
|
+
const entry = {
|
|
93
|
+
path,
|
|
94
|
+
async cleanup() {
|
|
95
|
+
if (drillRemoved)
|
|
96
|
+
return;
|
|
97
|
+
drillRemoved = true;
|
|
98
|
+
drills.delete(key);
|
|
99
|
+
await rm(path, { recursive: true, force: true, maxRetries: 3 }).catch(() => undefined);
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
drills.set(key, entry);
|
|
103
|
+
return entry;
|
|
104
|
+
},
|
|
105
|
+
cleanup,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
/** Run `work` with a fresh run scratch that is always removed afterwards. */
|
|
109
|
+
export async function withRunScratch(work) {
|
|
110
|
+
const scratch = await createRunScratch();
|
|
111
|
+
try {
|
|
112
|
+
return await work(scratch);
|
|
113
|
+
}
|
|
114
|
+
finally {
|
|
115
|
+
await scratch.cleanup();
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Approved screenshot capture for the browser driver (Plan20 W12, root
|
|
3
|
+
* `client-masked-png-v1`, 2026-11-10).
|
|
4
|
+
*
|
|
5
|
+
* Screenshots are off unless the approved local plan turns them on for the
|
|
6
|
+
* `playwright.v1` driver (`screenshots`). When on, every capture masks the
|
|
7
|
+
* policy's elements before the image is taken: form fields always, plus the
|
|
8
|
+
* selectors the human approved. The policy's digest is part of the plan the
|
|
9
|
+
* human approved and goes to root with each image. Masking covers only those
|
|
10
|
+
* elements in the page's main frame; any other text on screen is uploaded as
|
|
11
|
+
* shown. Nothing here detects secrets in pixels, and root does not either.
|
|
12
|
+
*
|
|
13
|
+
* `frames` (optional, 2026-11-10) adds masked frames while each step runs: one
|
|
14
|
+
* every `interval_ms` (1 to 10 seconds), at most `max_per_step` (1 to 30) per
|
|
15
|
+
* step, plus the screenshot after the step. These are captured frames, not a
|
|
16
|
+
* video: nothing between two frames is recorded.
|
|
17
|
+
*/
|
|
18
|
+
import { canonicalJson, sha256Hex } from './canonical.js';
|
|
19
|
+
export const MASKING_POLICY_SCHEMA = 'firedrill.screenshot-masking.v1';
|
|
20
|
+
/** Always masked: every form field and editable region, whatever the plan adds. */
|
|
21
|
+
export const BASE_MASK_SELECTORS = Object.freeze([
|
|
22
|
+
'input', 'textarea', 'select', '[contenteditable]:not([contenteditable="false"])',
|
|
23
|
+
]);
|
|
24
|
+
export const MASK_COLOR = '#000000';
|
|
25
|
+
export const MAX_MASK_SELECTORS = 64;
|
|
26
|
+
export const MASK_SELECTOR_MAX_CHARS = 256;
|
|
27
|
+
/** The effective policy (base selectors first, then the plan's, without repeats) and its digest. */
|
|
28
|
+
export function maskingPolicy(plan) {
|
|
29
|
+
const selectors = [];
|
|
30
|
+
for (const selector of [...BASE_MASK_SELECTORS, ...plan.mask_selectors])
|
|
31
|
+
if (!selectors.includes(selector))
|
|
32
|
+
selectors.push(selector);
|
|
33
|
+
const policy = { schema: MASKING_POLICY_SCHEMA, selectors, mask_color: MASK_COLOR };
|
|
34
|
+
return { policy, sha256: sha256Hex(canonicalJson(policy)) };
|
|
35
|
+
}
|
|
36
|
+
/** Plain words for the approval screen: what is masked and what is not. */
|
|
37
|
+
export function describeScreenshots(plan) {
|
|
38
|
+
if (!plan)
|
|
39
|
+
return 'no screenshots are captured';
|
|
40
|
+
const extra = plan.mask_selectors.length ? `, plus ${plan.mask_selectors.map((item) => JSON.stringify(item)).join(', ')}` : '';
|
|
41
|
+
const frames = plan.frames
|
|
42
|
+
? `, and while each step runs it also captures up to ${plan.frames.max_per_step} masked frame${plan.frames.max_per_step === 1 ? '' : 's'}, `
|
|
43
|
+
+ `one every ${seconds(plan.frames.interval_ms)} (captured frames, not a video: nothing between frames is recorded)`
|
|
44
|
+
: '';
|
|
45
|
+
return `captures a PNG screenshot of the visible page after each step${frames} and uploads them as evidence. Before each capture it `
|
|
46
|
+
+ `blacks out every form field and editable region${extra}. Anything else on screen, including text outside those elements and inside `
|
|
47
|
+
+ `embedded frames, is uploaded as shown; masking is not secret detection. Remove "screenshots" from the plan to turn this off`;
|
|
48
|
+
}
|
|
49
|
+
function seconds(ms) {
|
|
50
|
+
const value = ms / 1000;
|
|
51
|
+
return `${Number.isInteger(value) ? value : value.toFixed(1)} second${value === 1 ? '' : 's'}`;
|
|
52
|
+
}
|
|
53
|
+
/** Plain words for the approval screen: what the sanitized action trace records and what it never does. */
|
|
54
|
+
export function describeTrace(trace) {
|
|
55
|
+
if (!trace)
|
|
56
|
+
return 'no action trace is recorded';
|
|
57
|
+
return 'records which kind of browser operation the flow ran (navigate, click, fill, press, select, check, uncheck, wait or observe), '
|
|
58
|
+
+ 'how it ended and when, and uploads that as evidence. It never records selectors, addresses, typed values, page text or errors, '
|
|
59
|
+
+ 'and it is not a Playwright trace, network log or video. Remove "trace" from the plan to turn this off';
|
|
60
|
+
}
|