@fitlab-ai/agent-infra 0.9.14 → 0.10.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 +10 -0
- package/README.zh-CN.md +10 -0
- package/bin/internal-cli.sh +45 -0
- package/dist/bin/internal-cli.js +93 -30
- package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +165 -27
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +9 -4
- package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +43 -3
- package/dist/lib/defaults.json +103 -103
- package/dist/lib/fs/durable-write.js +39 -0
- package/dist/lib/fs/file-lock.js +111 -0
- package/dist/lib/host-control/audit.js +19 -0
- package/dist/lib/host-control/client.js +142 -0
- package/dist/lib/host-control/command.js +49 -0
- package/dist/lib/host-control/path.js +160 -0
- package/dist/lib/host-control/secure-fs.js +140 -0
- package/dist/lib/host-control/server.js +159 -0
- package/dist/lib/host-control/service.js +112 -0
- package/dist/lib/init.js +11 -2
- package/dist/lib/internal/cli-route-inventory.js +4 -2
- package/dist/lib/internal/codex-lifecycle.js +6 -6
- package/dist/lib/internal/demo-promotion.js +322 -0
- package/dist/lib/internal/demo-transcript.js +454 -0
- package/dist/lib/internal/host-control.js +28 -0
- package/dist/lib/internal/manual-validation.js +331 -0
- package/dist/lib/internal/platform-pr.js +18 -4
- package/dist/lib/internal/release-workflow.js +63 -84
- package/dist/lib/internal/sandbox-control.js +149 -2
- package/dist/lib/internal/task-artifact.js +16 -263
- package/dist/lib/internal/task-create.js +6 -5
- package/dist/lib/internal/task-event.js +37 -37
- package/dist/lib/internal/task-lifecycle.js +2 -2
- package/dist/lib/internal/task-operation-registry.js +101 -4
- package/dist/lib/internal/task-review.js +11 -56
- package/dist/lib/internal/task-validate.js +30 -10
- package/dist/lib/owned-path.js +33 -0
- package/dist/lib/platform/check-status.js +11 -0
- package/dist/lib/platform/comment-safety.js +4 -1
- package/dist/lib/platform/completion-backfill.js +1 -1
- package/dist/lib/platform/github-data.js +241 -0
- package/dist/lib/platform/github-provider.js +15 -20
- package/dist/lib/platform/issue-comments.js +18 -12
- package/dist/lib/platform/issues.js +2 -180
- package/dist/lib/platform/pr-change-report.js +5 -4
- package/dist/lib/platform/pr-checks.js +12 -65
- package/dist/lib/platform/pr-summary.js +117 -33
- package/dist/lib/platform/pull-requests.js +5 -108
- package/dist/lib/platform/snapshots.js +2 -0
- package/dist/lib/platform/task-identities.js +2 -9
- package/dist/lib/platform/verification-sync.js +132 -143
- package/dist/lib/pr-review/evidence-grading.js +4 -8
- package/dist/lib/release/workflow.js +1 -1
- package/dist/lib/sandbox/commands/create.js +7 -2
- package/dist/lib/sandbox/commands/rm.js +19 -1511
- package/dist/lib/sandbox/control/audit.js +203 -0
- package/dist/lib/sandbox/control/client.js +93 -54
- package/dist/lib/sandbox/control/executor.js +129 -65
- package/dist/lib/sandbox/control/finalization-response.js +27 -0
- package/dist/lib/sandbox/control/identity-sentinel.js +107 -0
- package/dist/lib/sandbox/control/lifecycle.js +36 -2
- package/dist/lib/sandbox/control/native-file-lock.js +6 -98
- package/dist/lib/sandbox/control/protocol.js +55 -4
- package/dist/lib/sandbox/control/server.js +518 -127
- package/dist/lib/sandbox/control/state.js +64 -0
- package/dist/lib/sandbox/control/task-workflow.js +74 -0
- package/dist/lib/sandbox/control/workflow-executor.js +89 -0
- package/dist/lib/sandbox/index.js +1 -2
- package/dist/lib/sandbox/recovery.js +28 -4
- package/dist/lib/sandbox/removal.js +822 -0
- package/dist/lib/sandbox/task-cleanup.js +217 -0
- package/dist/lib/sandbox/workspace-identity.js +17 -1
- package/dist/lib/sandbox/workspace-view.js +13 -14
- package/dist/lib/task/activity-intent.js +2 -1
- package/dist/lib/task/activity-log.js +30 -24
- package/dist/lib/task/artifact-command.js +79 -0
- package/dist/lib/task/artifact-lifecycle.js +30 -28
- package/dist/lib/task/artifact-name.js +36 -0
- package/dist/lib/task/artifact-operations.js +29 -289
- package/dist/lib/task/artifact-receipts.js +3 -15
- package/dist/lib/task/artifact-recovery.js +430 -0
- package/dist/lib/task/artifact-repair-intent.js +134 -29
- package/dist/lib/task/artifact-schema.js +6 -14
- package/dist/lib/task/capabilities.js +12 -19
- package/dist/lib/task/codex-orchestration.js +71 -25
- package/dist/lib/task/commit-identity.js +28 -0
- package/dist/lib/task/commit-operation.js +33 -20
- package/dist/lib/task/control-authority.js +403 -6
- package/dist/lib/task/control-recovery.js +140 -0
- package/dist/lib/task/create-service.js +1 -8
- package/dist/lib/task/create.js +62 -9
- package/dist/lib/task/decision-details.js +2 -46
- package/dist/lib/task/delegation-receipts.js +64 -3
- package/dist/lib/task/events.js +275 -22
- package/dist/lib/task/finalization-state.js +9 -0
- package/dist/lib/task/human-override.js +8 -14
- package/dist/lib/task/implementation-inputs.js +3 -3
- package/dist/lib/task/intermediate-cleanup.js +441 -0
- package/dist/lib/task/invalidation.js +18 -65
- package/dist/lib/task/ledger-intents.js +7 -22
- package/dist/lib/task/ledger.js +1 -3
- package/dist/lib/task/lifecycle-recovery.js +549 -0
- package/dist/lib/task/lifecycle.js +31 -4
- package/dist/lib/task/local-artifact-finalization.js +197 -197
- package/dist/lib/task/manual-validation-completion.js +26 -0
- package/dist/lib/task/manual-validation-evidence.js +130 -0
- package/dist/lib/task/manual-validation-receipt.js +105 -0
- package/dist/lib/task/manual-validation-shared.js +40 -0
- package/dist/lib/task/manual-validation-transaction.js +243 -0
- package/dist/lib/task/markdown.js +47 -0
- package/dist/lib/task/orchestration.js +66 -19
- package/dist/lib/task/pr-delivery-fact.js +12 -56
- package/dist/lib/task/qualification-audit.js +20 -34
- package/dist/lib/task/recovery-warning.js +19 -0
- package/dist/lib/task/review-artifacts.js +2 -18
- package/dist/lib/task/review-command.js +26 -0
- package/dist/lib/task/review-finalization.js +175 -216
- package/dist/lib/task/review-fingerprint.js +2 -2
- package/dist/lib/task/sections.js +34 -56
- package/dist/lib/task/short-id.js +15 -7
- package/dist/lib/task/task-execution-lock.js +1 -1
- package/dist/lib/task/verification-engine.js +119 -337
- package/dist/lib/task/verification.js +1 -1
- package/dist/lib/task/workflow-command.js +16 -0
- package/dist/lib/task/workflow-warning-intents.js +0 -16
- package/dist/lib/task/workflow-warnings.js +28 -2
- package/dist/lib/update.js +2 -0
- package/dist/lib/version.js +1 -11
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +223 -33
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +10 -4
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +55 -3
- package/lib/fs/durable-write.ts +30 -0
- package/lib/fs/file-lock.ts +113 -0
- package/lib/host-control/audit.ts +22 -0
- package/lib/host-control/client.ts +172 -0
- package/lib/host-control/command.ts +60 -0
- package/lib/host-control/path.ts +167 -0
- package/lib/host-control/secure-fs.ts +135 -0
- package/lib/host-control/server.ts +180 -0
- package/lib/host-control/service.ts +133 -0
- package/lib/init.ts +15 -2
- package/lib/internal/cli-route-inventory.ts +4 -2
- package/lib/internal/codex-lifecycle.ts +6 -6
- package/lib/internal/demo-promotion.ts +354 -0
- package/lib/internal/demo-transcript.ts +419 -0
- package/lib/internal/host-control.ts +28 -0
- package/lib/internal/manual-validation.ts +315 -0
- package/lib/internal/platform-pr.ts +15 -4
- package/lib/internal/release-workflow.ts +74 -82
- package/lib/internal/sandbox-control.ts +162 -1
- package/lib/internal/task-artifact.ts +14 -217
- package/lib/internal/task-create.ts +5 -5
- package/lib/internal/task-event.ts +20 -11
- package/lib/internal/task-lifecycle.ts +4 -3
- package/lib/internal/task-operation-registry.ts +111 -4
- package/lib/internal/task-review.ts +9 -54
- package/lib/internal/task-validate.ts +26 -10
- package/lib/owned-path.ts +26 -0
- package/lib/platform/check-status.ts +9 -0
- package/lib/platform/comment-safety.ts +5 -0
- package/lib/platform/completion-backfill.ts +1 -1
- package/lib/platform/github-data.ts +322 -0
- package/lib/platform/github-provider.ts +15 -20
- package/lib/platform/issue-comments.ts +21 -14
- package/lib/platform/issues.ts +5 -246
- package/lib/platform/pr-change-report.ts +7 -5
- package/lib/platform/pr-checks.ts +13 -77
- package/lib/platform/pr-summary.ts +123 -35
- package/lib/platform/pull-requests.ts +6 -173
- package/lib/platform/snapshots.ts +53 -0
- package/lib/platform/task-identities.ts +3 -14
- package/lib/platform/verification-sync.ts +150 -156
- package/lib/pr-review/evidence-grading.ts +4 -9
- package/lib/release/workflow.ts +3 -3
- package/lib/sandbox/commands/create.ts +7 -6
- package/lib/sandbox/commands/rm.ts +21 -1895
- package/lib/sandbox/control/audit.ts +253 -0
- package/lib/sandbox/control/client.ts +109 -66
- package/lib/sandbox/control/executor.ts +134 -74
- package/lib/sandbox/control/finalization-response.ts +29 -0
- package/lib/sandbox/control/identity-sentinel.ts +137 -0
- package/lib/sandbox/control/lifecycle.ts +45 -3
- package/lib/sandbox/control/native-file-lock.ts +6 -107
- package/lib/sandbox/control/protocol.ts +80 -6
- package/lib/sandbox/control/server.ts +567 -132
- package/lib/sandbox/control/state.ts +86 -0
- package/lib/sandbox/control/task-workflow.ts +95 -0
- package/lib/sandbox/control/workflow-executor.ts +92 -0
- package/lib/sandbox/index.ts +1 -2
- package/lib/sandbox/recovery.ts +28 -5
- package/lib/sandbox/removal.ts +1142 -0
- package/lib/sandbox/task-cleanup.ts +209 -0
- package/lib/sandbox/workspace-identity.ts +19 -3
- package/lib/sandbox/workspace-view.ts +17 -15
- package/lib/task/activity-intent.ts +2 -2
- package/lib/task/activity-log.ts +27 -19
- package/lib/task/artifact-command.ts +85 -0
- package/lib/task/artifact-lifecycle.ts +31 -26
- package/lib/task/artifact-name.ts +55 -0
- package/lib/task/artifact-operations.ts +34 -310
- package/lib/task/artifact-receipts.ts +3 -14
- package/lib/task/artifact-recovery.ts +519 -0
- package/lib/task/artifact-repair-intent.ts +162 -33
- package/lib/task/artifact-schema.ts +5 -34
- package/lib/task/capabilities.ts +13 -22
- package/lib/task/codex-orchestration.ts +85 -24
- package/lib/task/commit-identity.ts +31 -0
- package/lib/task/commit-operation.ts +37 -20
- package/lib/task/control-authority.ts +540 -10
- package/lib/task/control-recovery.ts +186 -0
- package/lib/task/create-service.ts +1 -10
- package/lib/task/create.ts +63 -8
- package/lib/task/decision-details.ts +3 -71
- package/lib/task/delegation-receipts.ts +73 -2
- package/lib/task/events.ts +309 -24
- package/lib/task/finalization-state.ts +11 -0
- package/lib/task/human-override.ts +8 -16
- package/lib/task/implementation-inputs.ts +3 -3
- package/lib/task/intermediate-cleanup.ts +493 -0
- package/lib/task/invalidation.ts +18 -62
- package/lib/task/ledger-intents.ts +7 -24
- package/lib/task/ledger.ts +1 -3
- package/lib/task/lifecycle-recovery.ts +691 -0
- package/lib/task/lifecycle.ts +30 -5
- package/lib/task/local-artifact-finalization.ts +244 -234
- package/lib/task/manual-validation-completion.ts +40 -0
- package/lib/task/manual-validation-evidence.ts +209 -0
- package/lib/task/manual-validation-receipt.ts +156 -0
- package/lib/task/manual-validation-shared.ts +50 -0
- package/lib/task/manual-validation-transaction.ts +304 -0
- package/lib/task/markdown.ts +70 -0
- package/lib/task/orchestration.ts +84 -19
- package/lib/task/pr-delivery-fact.ts +15 -62
- package/lib/task/qualification-audit.ts +22 -33
- package/lib/task/recovery-warning.ts +21 -0
- package/lib/task/review-artifacts.ts +1 -20
- package/lib/task/review-command.ts +24 -0
- package/lib/task/review-finalization.ts +183 -292
- package/lib/task/review-fingerprint.ts +2 -2
- package/lib/task/sections.ts +33 -54
- package/lib/task/short-id.ts +27 -5
- package/lib/task/task-execution-lock.ts +1 -1
- package/lib/task/verification-engine.ts +113 -383
- package/lib/task/verification-types.ts +15 -0
- package/lib/task/verification.ts +1 -1
- package/lib/task/workflow-command.ts +17 -0
- package/lib/task/workflow-warning-intents.ts +0 -13
- package/lib/task/workflow-warnings.ts +21 -2
- package/lib/update.ts +6 -0
- package/lib/version.ts +1 -15
- package/package.json +6 -6
- package/templates/.agents/hooks/lifecycle-delegation.js +6 -6
- package/templates/.agents/rules/create-issue.en.md +1 -1
- package/templates/.agents/rules/create-issue.zh-CN.md +1 -1
- package/templates/.agents/rules/issue-sync.en.md +4 -2
- package/templates/.agents/rules/issue-sync.zh-CN.md +4 -3
- package/templates/.agents/rules/local-artifact-repair.en.md +46 -39
- package/templates/.agents/rules/local-artifact-repair.zh-CN.md +43 -36
- package/templates/.agents/rules/testing-discipline.en.md +2 -0
- package/templates/.agents/rules/testing-discipline.zh-CN.md +2 -0
- package/templates/.agents/scripts/package.json +3 -0
- package/templates/.agents/skills/analyze-task/SKILL.en.md +6 -6
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +6 -6
- package/templates/.agents/skills/analyze-task/config/verify.en.json +1 -1
- package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/block-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/block-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/block-task/config/verify.json +1 -1
- package/templates/.agents/skills/cancel-task/SKILL.en.md +3 -3
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/cancel-task/config/verify.json +1 -1
- package/templates/.agents/skills/code-task/SKILL.en.md +7 -7
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +7 -7
- package/templates/.agents/skills/code-task/config/verify.en.json +1 -1
- package/templates/.agents/skills/code-task/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/commit/reference/issue-metadata-sync.en.md +1 -1
- package/templates/.agents/skills/commit/reference/issue-metadata-sync.zh-CN.md +1 -1
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +33 -21
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +33 -21
- package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +1 -1
- package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/complete-manual-validation/reference/report-template.en.md +2 -0
- package/templates/.agents/skills/complete-manual-validation/reference/report-template.zh-CN.md +2 -0
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +16 -7
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +16 -7
- package/templates/.agents/skills/complete-task/SKILL.en.md +3 -3
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/complete-task/config/verify.en.json +2 -2
- package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +2 -2
- package/templates/.agents/skills/complete-task/reference/external-delivery.en.md +1 -1
- package/templates/.agents/skills/complete-task/reference/external-delivery.zh-CN.md +1 -1
- package/templates/.agents/skills/create-pr/SKILL.en.md +2 -2
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/create-pr/config/verify.json +1 -1
- package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +2 -2
- package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +2 -2
- package/templates/.agents/skills/create-task/SKILL.en.md +5 -5
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/create-task/config/verify.json +1 -1
- package/templates/.agents/skills/import-issue/SKILL.en.md +8 -8
- package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +8 -8
- package/templates/.agents/skills/import-issue/config/verify.json +2 -2
- package/templates/.agents/skills/plan-task/SKILL.en.md +4 -4
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/plan-task/config/verify.en.json +1 -1
- package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/release/SKILL.en.md +7 -3
- package/templates/.agents/skills/release/SKILL.zh-CN.md +7 -3
- package/templates/.agents/skills/restore-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/restore-task/config/verify.json +1 -1
- package/templates/.agents/skills/review-analysis/SKILL.en.md +2 -2
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/review-analysis/config/verify.en.json +1 -1
- package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/review-analysis/reference/report-template.en.md +7 -7
- package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +6 -6
- package/templates/.agents/skills/review-code/SKILL.en.md +2 -2
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/review-code/config/verify.en.json +1 -1
- package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/review-code/reference/report-template.en.md +12 -12
- package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +12 -12
- package/templates/.agents/skills/review-plan/SKILL.en.md +2 -2
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/review-plan/config/verify.en.json +1 -1
- package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/review-plan/reference/report-template.en.md +7 -7
- package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +7 -7
- package/templates/.agents/skills/review-pr/config/verify.en.json +1 -1
- package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/review-pr/reference/evidence-grading.en.md +3 -3
- package/templates/.agents/skills/review-pr/reference/evidence-grading.zh-CN.md +3 -3
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +20 -3
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +20 -3
- package/templates/.agents/skills/run-manual-validation/config/verify.en.json +1 -1
- package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +4 -1
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +4 -1
- package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +2 -0
- package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +2 -0
- package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/test/SKILL.en.md +17 -6
- package/templates/.agents/skills/test/SKILL.zh-CN.md +17 -6
- package/templates/.agents/templates/task.en.md +1 -1
- package/templates/.agents/templates/task.zh-CN.md +1 -1
- package/templates/.claude/commands/import-issue.en.md +1 -1
- package/templates/.claude/commands/import-issue.zh-CN.md +1 -1
- package/templates/.claude/commands/restore-task.en.md +1 -1
- package/templates/.claude/commands/restore-task.zh-CN.md +1 -1
- package/templates/.claude/commands/run-manual-validation.en.md +1 -1
- package/templates/.claude/commands/run-manual-validation.zh-CN.md +1 -1
- package/dist/lib/platform/adapters.js +0 -56
- package/lib/platform/adapters.ts +0 -197
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
+
import crypto from 'node:crypto';
|
|
3
|
+
import fs from 'node:fs';
|
|
2
4
|
import { normalizeAgentToken } from '../agent-clients/tokens.js';
|
|
3
5
|
import { isAgentClientId } from '../agent-clients/types.js';
|
|
4
6
|
import { consumeHumanOverride, failureId, overrideDryRunConflict } from './human-override.js';
|
|
@@ -6,9 +8,358 @@ import { activateMatchingOrchestrationDelegation, activateOrchestrationDelegatio
|
|
|
6
8
|
import { prepareCodexOrchestrationDelegation } from './codex-orchestration.js';
|
|
7
9
|
import { applyTaskFinalization } from './finalization.js';
|
|
8
10
|
import { applyTaskLifecycle, taskLifecycleFailure } from './lifecycle.js';
|
|
11
|
+
import { recoverStartedLifecycleUnderLock, recoveryFailure } from './lifecycle-recovery.js';
|
|
12
|
+
import { locateActivityLog } from './activity-log.js';
|
|
9
13
|
import { resolveTaskRef, TASK_ID_RE } from './resolve-ref.js';
|
|
10
14
|
import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.js';
|
|
11
15
|
import { verifyTaskEvent } from './verification.js';
|
|
16
|
+
import { createCodexCapabilityStore } from '../agent-clients/adapters/codex-lifecycle/capability-store.js';
|
|
17
|
+
import { findArtifactRecoveryIntentsByOperation, writeArtifactRecoveryIntent } from './artifact-repair-intent.js';
|
|
18
|
+
import { computeLifecycleBuildIdentity, verifyLifecycleBuildIdentity } from '../agent-clients/adapters/codex-lifecycle/build-identity.js';
|
|
19
|
+
const lifecycleAuthorityPhases = [
|
|
20
|
+
'orchestration.prepare', 'artifact.finalize-local', 'task-event.completed'
|
|
21
|
+
];
|
|
22
|
+
const lifecycleAuthorityFamilies = ['analysis', 'plan', 'code'];
|
|
23
|
+
const lifecycleAuthorityArtifact = /^(?:analysis|plan|code)(?:-r[1-9]\d*)?\.md$/u;
|
|
24
|
+
const lifecycleAuthorityDigest = (value) => crypto.createHash('sha256').update(value, 'utf8').digest('hex');
|
|
25
|
+
const lifecycleAuthorityBuildDigest = (value) => lifecycleAuthorityDigest(JSON.stringify(value));
|
|
26
|
+
export function lifecycleRecoveryAttestationDigest(value) {
|
|
27
|
+
return lifecycleAuthorityDigest(JSON.stringify(value));
|
|
28
|
+
}
|
|
29
|
+
function authorityExactKeys(value, keys) {
|
|
30
|
+
return Object.keys(value).sort().join(',') === [...keys].sort().join(',');
|
|
31
|
+
}
|
|
32
|
+
export function validateLifecycleAuthorityRequest(value) {
|
|
33
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
34
|
+
throw new Error('LIFECYCLE_AUTHORITY_REQUEST_INVALID');
|
|
35
|
+
const request = value;
|
|
36
|
+
if (!authorityExactKeys(request, [
|
|
37
|
+
'artifact', 'authorityRef', 'expectedBuildIdentityDigest', 'expectedControlGeneration',
|
|
38
|
+
'expectedControllerInstanceDigest', 'expectedHookDefinitionHash', 'family', 'lifecycleRequestId',
|
|
39
|
+
'operationId', 'phase', 'requestId', 'round', 'taskId', 'version'
|
|
40
|
+
])
|
|
41
|
+
|| request.version !== 1
|
|
42
|
+
|| typeof request.requestId !== 'string' || !/^[a-f0-9-]{16,64}$/u.test(request.requestId)
|
|
43
|
+
|| typeof request.operationId !== 'string' || !/^[a-f0-9-]{16,64}$/u.test(request.operationId)
|
|
44
|
+
|| !lifecycleAuthorityPhases.includes(request.phase)
|
|
45
|
+
|| typeof request.taskId !== 'string' || !/^TASK-\d{8}-\d{6}$/u.test(request.taskId)
|
|
46
|
+
|| !lifecycleAuthorityFamilies.includes(request.family)
|
|
47
|
+
|| typeof request.artifact !== 'string' || !lifecycleAuthorityArtifact.test(request.artifact)
|
|
48
|
+
|| !Number.isSafeInteger(request.round) || request.round < 1
|
|
49
|
+
|| typeof request.lifecycleRequestId !== 'string' || !request.lifecycleRequestId || /[\r\n]/u.test(request.lifecycleRequestId)
|
|
50
|
+
|| typeof request.authorityRef !== 'string' || !request.authorityRef || /[\r\n]/u.test(request.authorityRef)
|
|
51
|
+
|| typeof request.expectedControlGeneration !== 'string' || !request.expectedControlGeneration
|
|
52
|
+
|| typeof request.expectedControllerInstanceDigest !== 'string' || !/^[a-f0-9]{64}$/u.test(request.expectedControllerInstanceDigest)
|
|
53
|
+
|| typeof request.expectedBuildIdentityDigest !== 'string' || !/^[a-f0-9]{64}$/u.test(request.expectedBuildIdentityDigest)
|
|
54
|
+
|| typeof request.expectedHookDefinitionHash !== 'string' || !/^[a-f0-9]{64}$/u.test(request.expectedHookDefinitionHash)) {
|
|
55
|
+
throw new Error('LIFECYCLE_AUTHORITY_REQUEST_INVALID');
|
|
56
|
+
}
|
|
57
|
+
const expectedArtifactFamily = request.artifact;
|
|
58
|
+
if (!expectedArtifactFamily.startsWith(request.family))
|
|
59
|
+
throw new Error('LIFECYCLE_AUTHORITY_REQUEST_INVALID');
|
|
60
|
+
return request;
|
|
61
|
+
}
|
|
62
|
+
export function validateLifecycleRecoveryAttestation(value, now = Date.now()) {
|
|
63
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
64
|
+
throw new Error('LIFECYCLE_AUTHORITY_ATTESTATION_INVALID');
|
|
65
|
+
const attestation = value;
|
|
66
|
+
if (!authorityExactKeys(attestation, [
|
|
67
|
+
'artifact', 'attestationId', 'authorityRefDigest', 'buildIdentityDigest', 'controlGeneration',
|
|
68
|
+
'controllerInstanceDigest', 'expiresAt', 'family', 'hookDefinitionHash', 'issuedAt',
|
|
69
|
+
'lifecycleRequestId', 'operationId', 'phase', 'requestId', 'round', 'sessionId',
|
|
70
|
+
'taskId', 'toolUseId', 'turnId', 'version'
|
|
71
|
+
])
|
|
72
|
+
|| attestation.version !== 1
|
|
73
|
+
|| typeof attestation.attestationId !== 'string' || !/^[a-f0-9-]{16,64}$/u.test(attestation.attestationId)
|
|
74
|
+
|| typeof attestation.authorityRefDigest !== 'string' || !/^[a-f0-9]{64}$/u.test(attestation.authorityRefDigest)
|
|
75
|
+
|| typeof attestation.operationId !== 'string' || !/^[a-f0-9-]{16,64}$/u.test(attestation.operationId)
|
|
76
|
+
|| !lifecycleAuthorityPhases.includes(attestation.phase)
|
|
77
|
+
|| typeof attestation.requestId !== 'string' || !/^[a-f0-9-]{16,64}$/u.test(attestation.requestId)
|
|
78
|
+
|| typeof attestation.lifecycleRequestId !== 'string' || !attestation.lifecycleRequestId
|
|
79
|
+
|| typeof attestation.taskId !== 'string' || !/^TASK-\d{8}-\d{6}$/u.test(attestation.taskId)
|
|
80
|
+
|| !lifecycleAuthorityFamilies.includes(attestation.family)
|
|
81
|
+
|| typeof attestation.artifact !== 'string' || !lifecycleAuthorityArtifact.test(attestation.artifact)
|
|
82
|
+
|| !Number.isSafeInteger(attestation.round) || attestation.round < 1
|
|
83
|
+
|| typeof attestation.controlGeneration !== 'string' || !attestation.controlGeneration
|
|
84
|
+
|| typeof attestation.controllerInstanceDigest !== 'string' || !/^[a-f0-9]{64}$/u.test(attestation.controllerInstanceDigest)
|
|
85
|
+
|| typeof attestation.sessionId !== 'string' || !attestation.sessionId
|
|
86
|
+
|| typeof attestation.turnId !== 'string' || !attestation.turnId
|
|
87
|
+
|| typeof attestation.toolUseId !== 'string' || !attestation.toolUseId
|
|
88
|
+
|| typeof attestation.buildIdentityDigest !== 'string' || !/^[a-f0-9]{64}$/u.test(attestation.buildIdentityDigest)
|
|
89
|
+
|| typeof attestation.hookDefinitionHash !== 'string' || !/^[a-f0-9]{64}$/u.test(attestation.hookDefinitionHash)
|
|
90
|
+
|| !Number.isSafeInteger(attestation.issuedAt) || !Number.isSafeInteger(attestation.expiresAt)
|
|
91
|
+
|| attestation.expiresAt <= attestation.issuedAt
|
|
92
|
+
|| attestation.expiresAt <= now) {
|
|
93
|
+
throw new Error('LIFECYCLE_AUTHORITY_ATTESTATION_INVALID');
|
|
94
|
+
}
|
|
95
|
+
return attestation;
|
|
96
|
+
}
|
|
97
|
+
const authorityOperationMap = new WeakMap();
|
|
98
|
+
const authorityRecords = new Map();
|
|
99
|
+
const consumedAuthorityPhases = new Set();
|
|
100
|
+
export function issueLifecycleRecoveryAttestation(input, options = {}) {
|
|
101
|
+
let request;
|
|
102
|
+
try {
|
|
103
|
+
request = validateLifecycleAuthorityRequest(input);
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
return { version: 1, requestId: String(input?.requestId ?? ''), operationId: String(input?.operationId ?? ''), phase: input?.phase ?? 'artifact.finalize-local', status: 'rejected', attestation: null, error: { code: 'LIFECYCLE_AUTHORITY_REQUEST_INVALID', message: error instanceof Error ? error.message : String(error) } };
|
|
107
|
+
}
|
|
108
|
+
const binding = options.controllerBinding ?? null;
|
|
109
|
+
if (!binding)
|
|
110
|
+
return authorityRejected(request, 'LOCAL_EXECUTION_AUTHORITY_UNAVAILABLE', 'current controller authority is unavailable');
|
|
111
|
+
if (binding.controlGeneration !== request.expectedControlGeneration
|
|
112
|
+
|| binding.instanceDigest !== request.expectedControllerInstanceDigest) {
|
|
113
|
+
return authorityRejected(request, 'LIFECYCLE_AUTHORITY_CONTROLLER_MISMATCH', 'current controller binding does not match the request');
|
|
114
|
+
}
|
|
115
|
+
const buildIdentity = options.buildIdentity ?? computeLifecycleBuildIdentity(process.cwd());
|
|
116
|
+
if (lifecycleAuthorityBuildDigest(buildIdentity) !== request.expectedBuildIdentityDigest) {
|
|
117
|
+
return authorityRejected(request, 'LIFECYCLE_AUTHORITY_BUILD_MISMATCH', 'current lifecycle build does not match the request');
|
|
118
|
+
}
|
|
119
|
+
const store = options.capabilityStore ?? createCodexCapabilityStore();
|
|
120
|
+
const registry = options.operationAttestations ?? authorityOperationMap.get(store) ?? new Map();
|
|
121
|
+
authorityOperationMap.set(store, registry);
|
|
122
|
+
const key = `${request.requestId}\0${request.operationId}\0${request.phase}`;
|
|
123
|
+
const prior = registry.get(key);
|
|
124
|
+
if (prior)
|
|
125
|
+
return { version: 1, requestId: request.requestId, operationId: request.operationId, phase: request.phase, status: 'already-completed', attestation: prior, error: null };
|
|
126
|
+
try {
|
|
127
|
+
const expected = {
|
|
128
|
+
taskId: request.taskId,
|
|
129
|
+
hookDefinitionHash: request.expectedHookDefinitionHash,
|
|
130
|
+
buildIdentity,
|
|
131
|
+
controller: binding
|
|
132
|
+
};
|
|
133
|
+
const record = store.reserveReference(request.authorityRef, request.operationId, expected);
|
|
134
|
+
if (!record.sessionId || !record.turnId || !record.toolUseId)
|
|
135
|
+
throw new Error('CODEX_CAPABILITY_IDENTITY_INVALID: capability hook attestation is incomplete');
|
|
136
|
+
const now = (options.now ?? Date.now)();
|
|
137
|
+
const attestation = validateLifecycleRecoveryAttestation({
|
|
138
|
+
version: 1,
|
|
139
|
+
attestationId: crypto.randomUUID(),
|
|
140
|
+
authorityRefDigest: lifecycleAuthorityDigest(request.authorityRef),
|
|
141
|
+
operationId: request.operationId,
|
|
142
|
+
phase: request.phase,
|
|
143
|
+
requestId: request.requestId,
|
|
144
|
+
lifecycleRequestId: request.lifecycleRequestId,
|
|
145
|
+
taskId: request.taskId,
|
|
146
|
+
family: request.family,
|
|
147
|
+
artifact: request.artifact,
|
|
148
|
+
round: request.round,
|
|
149
|
+
controlGeneration: binding.controlGeneration,
|
|
150
|
+
controllerInstanceDigest: binding.instanceDigest,
|
|
151
|
+
sessionId: record.sessionId,
|
|
152
|
+
turnId: record.turnId,
|
|
153
|
+
toolUseId: record.toolUseId,
|
|
154
|
+
buildIdentityDigest: lifecycleAuthorityBuildDigest(buildIdentity),
|
|
155
|
+
hookDefinitionHash: request.expectedHookDefinitionHash,
|
|
156
|
+
issuedAt: now,
|
|
157
|
+
expiresAt: Math.min(record.expiresAt, now + 30_000)
|
|
158
|
+
}, now);
|
|
159
|
+
store.reserveRecoveryPhase(request.authorityRef, request.operationId, request.phase, request.requestId, expected);
|
|
160
|
+
registry.set(key, attestation);
|
|
161
|
+
authorityRecords.set(attestation.attestationId, { store, capabilityRef: request.authorityRef, expected });
|
|
162
|
+
return { version: 1, requestId: request.requestId, operationId: request.operationId, phase: request.phase, status: 'issued', attestation, error: null };
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
return authorityRejected(request, error instanceof Error && error.name.startsWith('CODEX_') ? error.name : 'LIFECYCLE_AUTHORITY_REJECTED', error instanceof Error ? error.message : String(error));
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
export function consumeLifecycleRecoveryAttestation(value, now = Date.now()) {
|
|
169
|
+
const attestation = validateLifecycleRecoveryAttestation(value, now);
|
|
170
|
+
if (consumedAuthorityPhases.has(attestation.attestationId))
|
|
171
|
+
return;
|
|
172
|
+
const record = authorityRecords.get(attestation.attestationId);
|
|
173
|
+
if (!record)
|
|
174
|
+
throw new Error('LIFECYCLE_AUTHORITY_ATTESTATION_UNKNOWN');
|
|
175
|
+
record.store.consumeRecoveryPhase(record.capabilityRef, attestation.operationId, attestation.phase, attestation.requestId, record.expected);
|
|
176
|
+
if (attestation.phase === 'task-event.completed') {
|
|
177
|
+
record.store.consumeReference(record.capabilityRef, attestation.operationId, record.expected);
|
|
178
|
+
}
|
|
179
|
+
consumedAuthorityPhases.add(attestation.attestationId);
|
|
180
|
+
}
|
|
181
|
+
export function queryLifecycleRecoveryOperation(operationId, options = {}) {
|
|
182
|
+
if (!/^[a-f0-9-]{16,64}$/u.test(operationId)) {
|
|
183
|
+
throw new Error('LIFECYCLE_RECOVERY_OPERATION_INVALID');
|
|
184
|
+
}
|
|
185
|
+
const store = options.capabilityStore ?? createCodexCapabilityStore();
|
|
186
|
+
const registry = authorityOperationMap.get(store);
|
|
187
|
+
const known = [...(registry?.values() ?? [])].filter((value) => value.operationId === operationId);
|
|
188
|
+
const intents = options.repoRoot ? findArtifactRecoveryIntentsByOperation(options.repoRoot, operationId) : [];
|
|
189
|
+
const capabilities = store.findByRecoveryOperation(operationId);
|
|
190
|
+
const phaseMap = new Map();
|
|
191
|
+
for (const capability of capabilities) {
|
|
192
|
+
for (const phase of capability.recoveryPhases) {
|
|
193
|
+
phaseMap.set(`${phase.phase}\0${phase.requestId}`, {
|
|
194
|
+
phase: phase.phase,
|
|
195
|
+
requestId: phase.requestId,
|
|
196
|
+
attestationId: null,
|
|
197
|
+
state: phase.state === 'consumed' ? 'consumed' : 'observed'
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
for (const attestation of known) {
|
|
202
|
+
const key = `${attestation.phase}\0${attestation.requestId}`;
|
|
203
|
+
const durable = phaseMap.get(key);
|
|
204
|
+
phaseMap.set(key, {
|
|
205
|
+
phase: attestation.phase,
|
|
206
|
+
requestId: attestation.requestId,
|
|
207
|
+
attestationId: attestation.attestationId,
|
|
208
|
+
state: durable?.state === 'consumed' || consumedAuthorityPhases.has(attestation.attestationId)
|
|
209
|
+
? 'consumed' : 'issued'
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
for (const intent of intents) {
|
|
213
|
+
if (!intent.phase)
|
|
214
|
+
continue;
|
|
215
|
+
const key = `${intent.phase}\0${intent.requestId}`;
|
|
216
|
+
if (phaseMap.has(key))
|
|
217
|
+
continue;
|
|
218
|
+
phaseMap.set(key, {
|
|
219
|
+
phase: intent.phase,
|
|
220
|
+
requestId: intent.requestId,
|
|
221
|
+
attestationId: null,
|
|
222
|
+
state: intent.state === 'consumed' ? 'consumed' : 'observed'
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
const capabilityState = capabilities.some((record) => record.recoveryState === 'consumed')
|
|
226
|
+
? 'consumed'
|
|
227
|
+
: capabilities.some((record) => record.recoveryState === 'reserved') ? 'reserved' : 'unknown';
|
|
228
|
+
const committed = capabilityState === 'consumed' || intents.some((intent) => intent.state === 'consumed');
|
|
229
|
+
return {
|
|
230
|
+
version: 1,
|
|
231
|
+
operationId,
|
|
232
|
+
status: committed ? 'committed' : capabilityState === 'reserved' || phaseMap.size > 0 ? 'in-progress' : 'unknown',
|
|
233
|
+
capabilityState,
|
|
234
|
+
phases: Object.freeze([...phaseMap.values()])
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
function lifecycleRecoveryCompensationError(code, message) {
|
|
238
|
+
const error = new Error(`${code}: ${message}`);
|
|
239
|
+
error.name = code;
|
|
240
|
+
return error;
|
|
241
|
+
}
|
|
242
|
+
function lifecycleRecoveryEventCommitted(selector, repoRoot) {
|
|
243
|
+
const resolved = resolveTaskRef(selector.taskId, { repoRoot });
|
|
244
|
+
if (!resolved.ok || resolved.taskId !== selector.taskId)
|
|
245
|
+
return false;
|
|
246
|
+
let content;
|
|
247
|
+
try {
|
|
248
|
+
content = fs.readFileSync(resolved.taskMdPath, 'utf8');
|
|
249
|
+
}
|
|
250
|
+
catch {
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
253
|
+
const section = locateActivityLog(content);
|
|
254
|
+
if (!section)
|
|
255
|
+
return false;
|
|
256
|
+
const labels = { analysis: 'Analyze Task', plan: 'Plan Task', code: 'Code Task' };
|
|
257
|
+
const prefix = `${labels[selector.family]} (Round ${selector.round}`;
|
|
258
|
+
return section.entries.some((entry) => {
|
|
259
|
+
if (entry.step.endsWith(' [started]') || !entry.step.startsWith(prefix))
|
|
260
|
+
return false;
|
|
261
|
+
const delimiter = entry.step[prefix.length];
|
|
262
|
+
return (delimiter === ',' || delimiter === ')') && entry.note.endsWith(`→ ${selector.artifact}`);
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
function lifecycleRecoveryIntentMatches(intent, selector) {
|
|
266
|
+
return intent.taskId === selector.taskId
|
|
267
|
+
&& intent.family === selector.family
|
|
268
|
+
&& intent.artifact === selector.artifact
|
|
269
|
+
&& (intent.state === 'commit-started' || intent.state === 'consumed');
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Completes only the durable tail of a committed task event after a process
|
|
273
|
+
* restart. The task event itself is the commit boundary and is never replayed.
|
|
274
|
+
*/
|
|
275
|
+
export function recoverLifecycleRecoveryOperation(input, options) {
|
|
276
|
+
const selector = validateLifecycleAuthorityRequest(input);
|
|
277
|
+
if (selector.phase !== 'task-event.completed') {
|
|
278
|
+
throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_PHASE_UNSUPPORTED', 'only task-event.completed supports durable compensation');
|
|
279
|
+
}
|
|
280
|
+
const buildIdentity = options.buildIdentity ?? computeLifecycleBuildIdentity(options.repoRoot);
|
|
281
|
+
if (selector.expectedBuildIdentityDigest !== lifecycleAuthorityBuildDigest(buildIdentity)) {
|
|
282
|
+
throw lifecycleRecoveryCompensationError('LIFECYCLE_AUTHORITY_BUILD_MISMATCH', 'current lifecycle build does not match the recovery request');
|
|
283
|
+
}
|
|
284
|
+
if (selector.expectedControlGeneration !== options.controllerBinding.controlGeneration
|
|
285
|
+
|| selector.expectedControllerInstanceDigest !== options.controllerBinding.instanceDigest) {
|
|
286
|
+
throw lifecycleRecoveryCompensationError('LIFECYCLE_AUTHORITY_CONTROLLER_MISMATCH', 'current controller binding does not match the recovery request');
|
|
287
|
+
}
|
|
288
|
+
const store = options.capabilityStore ?? createCodexCapabilityStore();
|
|
289
|
+
const expected = {
|
|
290
|
+
taskId: selector.taskId,
|
|
291
|
+
hookDefinitionHash: selector.expectedHookDefinitionHash,
|
|
292
|
+
buildIdentity,
|
|
293
|
+
controller: options.controllerBinding
|
|
294
|
+
};
|
|
295
|
+
const capabilities = store.findByRecoveryOperation(selector.operationId);
|
|
296
|
+
if (capabilities.length !== 1 || capabilities[0].capabilityRefDigest !== lifecycleAuthorityDigest(selector.authorityRef)) {
|
|
297
|
+
throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_PROVENANCE_INVALID', 'durable capability provenance does not identify this operation');
|
|
298
|
+
}
|
|
299
|
+
const capability = capabilities[0];
|
|
300
|
+
const build = verifyLifecycleBuildIdentity(capability.buildIdentity, buildIdentity);
|
|
301
|
+
const expiredReserved = capability.status === 'expired' && capability.recoveryState === 'reserved';
|
|
302
|
+
if (!build.ok
|
|
303
|
+
|| capability.taskId !== selector.taskId
|
|
304
|
+
|| capability.hookDefinitionHash !== selector.expectedHookDefinitionHash
|
|
305
|
+
|| capability.controller?.controlGeneration !== options.controllerBinding.controlGeneration
|
|
306
|
+
|| capability.controller?.instanceDigest !== options.controllerBinding.instanceDigest
|
|
307
|
+
|| capability.recoveryState === 'unreserved'
|
|
308
|
+
|| capability.recoveryOperationId !== selector.operationId
|
|
309
|
+
|| (!['attested', 'consumed'].includes(capability.status) && !expiredReserved)) {
|
|
310
|
+
throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_PROVENANCE_INVALID', 'durable capability provenance does not match the recovery request');
|
|
311
|
+
}
|
|
312
|
+
const phase = capability.recoveryPhases.find((entry) => entry.phase === selector.phase);
|
|
313
|
+
if (!phase) {
|
|
314
|
+
throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_PHASE_MISSING', 'durable task completion phase is missing');
|
|
315
|
+
}
|
|
316
|
+
const intents = findArtifactRecoveryIntentsByOperation(options.repoRoot, selector.operationId)
|
|
317
|
+
.filter((intent) => intent.taskId === selector.taskId
|
|
318
|
+
&& intent.family === selector.family
|
|
319
|
+
&& intent.artifact === selector.artifact);
|
|
320
|
+
if (intents.length !== 1) {
|
|
321
|
+
throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_INTENT_INVALID', 'durable artifact recovery intent does not identify this task event');
|
|
322
|
+
}
|
|
323
|
+
const intent = intents[0];
|
|
324
|
+
if (!lifecycleRecoveryIntentMatches(intent, selector)
|
|
325
|
+
|| intent.phase !== selector.phase
|
|
326
|
+
|| intent.requestId !== selector.lifecycleRequestId
|
|
327
|
+
|| intent.authorityDigest === null) {
|
|
328
|
+
throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_INTENT_INVALID', 'durable artifact recovery intent does not match the recovery request');
|
|
329
|
+
}
|
|
330
|
+
if (!lifecycleRecoveryEventCommitted(selector, options.repoRoot)) {
|
|
331
|
+
throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_COMMIT_UNCONFIRMED', 'task completion is not durably present; compensation is not allowed');
|
|
332
|
+
}
|
|
333
|
+
if (capability.recoveryState === 'reserved') {
|
|
334
|
+
if (phase.state === 'issued') {
|
|
335
|
+
store.consumeRecoveryPhase(selector.authorityRef, selector.operationId, selector.phase, phase.requestId, expected, { allowExpiredReserved: true });
|
|
336
|
+
}
|
|
337
|
+
store.consumeReference(selector.authorityRef, selector.operationId, expected, { allowExpiredReserved: true });
|
|
338
|
+
}
|
|
339
|
+
else if (phase.state !== 'consumed') {
|
|
340
|
+
throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_STATE_INVALID', 'consumed capability has an unconsumed completion phase');
|
|
341
|
+
}
|
|
342
|
+
if (intent.state !== 'consumed') {
|
|
343
|
+
writeArtifactRecoveryIntent(options.repoRoot, {
|
|
344
|
+
...intent,
|
|
345
|
+
state: 'consumed',
|
|
346
|
+
updatedAt: (options.now ?? Date.now)()
|
|
347
|
+
}, { expected: intent });
|
|
348
|
+
}
|
|
349
|
+
return queryLifecycleRecoveryOperation(selector.operationId, { repoRoot: options.repoRoot, capabilityStore: store });
|
|
350
|
+
}
|
|
351
|
+
function authorityRejected(request, code, message) {
|
|
352
|
+
return { version: 1, requestId: request.requestId, operationId: request.operationId, phase: request.phase, status: 'rejected', attestation: null, error: { code, message } };
|
|
353
|
+
}
|
|
354
|
+
export function taskControlOperationKey(operation) {
|
|
355
|
+
if (operation.family === 'task-finalization')
|
|
356
|
+
return 'complete';
|
|
357
|
+
if (operation.family === 'task-lifecycle')
|
|
358
|
+
return operation.request.intent;
|
|
359
|
+
return operation.intent === 'route'
|
|
360
|
+
? operation.input && operation.input.completed === true ? 'route.clean-completion' : 'route.read'
|
|
361
|
+
: operation.intent;
|
|
362
|
+
}
|
|
12
363
|
function invalidContext(message) {
|
|
13
364
|
throw new Error(`TASK_CONTROL_CONTEXT_INVALID: ${message}`);
|
|
14
365
|
}
|
|
@@ -31,7 +382,11 @@ export function createDirectHostExecutionContext(params) {
|
|
|
31
382
|
mode: 'direct-host',
|
|
32
383
|
repoRoot: absolute('repoRoot', params.repoRoot),
|
|
33
384
|
...(params.runtimeDir === undefined ? {} : { runtimeDir: absolute('runtimeDir', params.runtimeDir) }),
|
|
34
|
-
...(params.controllerBinding === undefined ? {} : { controllerBinding: binding(params.controllerBinding) })
|
|
385
|
+
...(params.controllerBinding === undefined ? {} : { controllerBinding: binding(params.controllerBinding) }),
|
|
386
|
+
...(params.lifecycleRecoveryAttestation === undefined ? {} : {
|
|
387
|
+
lifecycleRecoveryAttestation: params.lifecycleRecoveryAttestation === null
|
|
388
|
+
? null : validateLifecycleRecoveryAttestation(params.lifecycleRecoveryAttestation)
|
|
389
|
+
})
|
|
35
390
|
};
|
|
36
391
|
}
|
|
37
392
|
export function createSandboxExecutorExecutionContext(params) {
|
|
@@ -53,7 +408,11 @@ export function createSandboxExecutorExecutionContext(params) {
|
|
|
53
408
|
manifestPath: absolute('manifestPath', params.manifestPath),
|
|
54
409
|
requestId: params.requestId,
|
|
55
410
|
...(params.diagnosticLog === undefined ? {} : { diagnosticLog: params.diagnosticLog }),
|
|
56
|
-
...(params.controllerBinding === undefined ? {} : { controllerBinding: binding(params.controllerBinding) })
|
|
411
|
+
...(params.controllerBinding === undefined ? {} : { controllerBinding: binding(params.controllerBinding) }),
|
|
412
|
+
...(params.lifecycleRecoveryAttestation === undefined ? {} : {
|
|
413
|
+
lifecycleRecoveryAttestation: params.lifecycleRecoveryAttestation === null
|
|
414
|
+
? null : validateLifecycleRecoveryAttestation(params.lifecycleRecoveryAttestation)
|
|
415
|
+
})
|
|
57
416
|
};
|
|
58
417
|
}
|
|
59
418
|
export function assertTaskControlExecutionContext(context) {
|
|
@@ -64,6 +423,8 @@ export function assertTaskControlExecutionContext(context) {
|
|
|
64
423
|
if (context.runtimeDir !== undefined)
|
|
65
424
|
absolute('runtimeDir', context.runtimeDir);
|
|
66
425
|
binding(context.controllerBinding);
|
|
426
|
+
if (context.lifecycleRecoveryAttestation)
|
|
427
|
+
validateLifecycleRecoveryAttestation(context.lifecycleRecoveryAttestation);
|
|
67
428
|
return;
|
|
68
429
|
}
|
|
69
430
|
if (context.mode !== 'task-bound-sandbox' || !/^TASK-\d{8}-\d{6}$/u.test(context.taskId)
|
|
@@ -78,6 +439,8 @@ export function assertTaskControlExecutionContext(context) {
|
|
|
78
439
|
invalidContext('runtimeDir is not bound to the manifest control root');
|
|
79
440
|
}
|
|
80
441
|
binding(context.controllerBinding);
|
|
442
|
+
if (context.lifecycleRecoveryAttestation)
|
|
443
|
+
validateLifecycleRecoveryAttestation(context.lifecycleRecoveryAttestation);
|
|
81
444
|
}
|
|
82
445
|
function operationTaskId(context, taskRef) {
|
|
83
446
|
if (context.source === 'sandbox-executor' && taskRef !== context.taskId) {
|
|
@@ -111,6 +474,9 @@ async function applyLifecycleWithAuthority(context, request) {
|
|
|
111
474
|
return taskLifecycleFailure(request, { code: 'LIFECYCLE_PAYLOAD_INVALID', message: conflict.message });
|
|
112
475
|
}
|
|
113
476
|
const execute = async () => {
|
|
477
|
+
if (request.intent === 'recover-started') {
|
|
478
|
+
return recoverStartedLifecycleUnderLock(request, { repoRoot: context.repoRoot });
|
|
479
|
+
}
|
|
114
480
|
const lifecycleResult = applyTaskLifecycle(request, { repoRoot: context.repoRoot });
|
|
115
481
|
if (lifecycleResult.status !== 'failed' || !request.overrideTicket)
|
|
116
482
|
return lifecycleResult;
|
|
@@ -144,6 +510,9 @@ async function applyLifecycleWithAuthority(context, request) {
|
|
|
144
510
|
catch (error) {
|
|
145
511
|
if (!(error instanceof TaskExecutionLockError))
|
|
146
512
|
throw error;
|
|
513
|
+
if (request.intent === 'recover-started') {
|
|
514
|
+
return recoveryFailure(request, 'owner-unknown', error.code, error.message);
|
|
515
|
+
}
|
|
147
516
|
return taskLifecycleFailure(request, { code: error.code, message: error.message }, taskId);
|
|
148
517
|
}
|
|
149
518
|
}
|
|
@@ -166,12 +535,16 @@ function orchestration(context, operation) {
|
|
|
166
535
|
client: input.client,
|
|
167
536
|
requestedModel: input.requestedModel,
|
|
168
537
|
requestedReasoningEffort: input.requestedReasoningEffort,
|
|
169
|
-
...(input.
|
|
538
|
+
...(input.capabilityRef === undefined ? {} : { capabilityRef: input.capabilityRef }),
|
|
539
|
+
...(context.lifecycleRecoveryAttestation === undefined
|
|
540
|
+
? {}
|
|
541
|
+
: { lifecycleRecoveryAttestation: context.lifecycleRecoveryAttestation })
|
|
170
542
|
};
|
|
171
543
|
if (prepareInput.client === 'codex') {
|
|
172
544
|
return prepareCodexOrchestrationDelegation(operation.taskRef, prepareInput, {
|
|
173
545
|
repoRoot: context.repoRoot,
|
|
174
546
|
orchestrationOptions: options,
|
|
547
|
+
deferLifecycleRecoveryConsumption: context.source === 'sandbox-executor',
|
|
175
548
|
...(context.controllerBinding ? { controllerBinding: context.controllerBinding } : {})
|
|
176
549
|
});
|
|
177
550
|
}
|
|
@@ -250,13 +623,13 @@ function operationInvalid(message) {
|
|
|
250
623
|
}
|
|
251
624
|
const LIFECYCLE_FLAGS = new Set([
|
|
252
625
|
'--agent', '--reason', '--unblock-condition', '--note', '--alert-number', '--staging-dir', '--issue-number',
|
|
253
|
-
'--override-ticket', '--override-target', '--override-scope', '--dry-run'
|
|
626
|
+
'--stage', '--round', '--artifact', '--override-ticket', '--override-target', '--override-scope', '--dry-run'
|
|
254
627
|
]);
|
|
255
628
|
const FINALIZATION_FLAGS = new Set(['--agent']);
|
|
256
629
|
const ORCHESTRATION_FLAGS = new Set([
|
|
257
630
|
'--agent', '--max-steps', '--executor-model', '--executor-reasoning-effort', '--reviewer-model',
|
|
258
631
|
'--reviewer-reasoning-effort', '--client', '--requested-model', '--requested-reasoning-effort',
|
|
259
|
-
'--capability-
|
|
632
|
+
'--capability-ref', '--parent-id', '--before-fingerprint', '--stage', '--round', '--artifact', '--role',
|
|
260
633
|
'--native-agent', '--child-id', '--spawn-mode', '--actual-model', '--actual-reasoning-effort',
|
|
261
634
|
'--model-fallback-reason', '--reasoning-effort-fallback-reason', '--exit-code', '--after-fingerprint',
|
|
262
635
|
'--changed-paths', '--code', '--message', '--recoverable', '--git-worktree-root'
|
|
@@ -326,8 +699,32 @@ export function parseTaskControlOperation(family, args) {
|
|
|
326
699
|
...(value(values, '--override-scope') ? { overrideScope: value(values, '--override-scope') } : {}),
|
|
327
700
|
...(value(values, '--alert-number') ? { alertNumber: Number(value(values, '--alert-number')) } : {}),
|
|
328
701
|
...(value(values, '--issue-number') ? { issueNumber: Number(value(values, '--issue-number')) } : {}),
|
|
702
|
+
...(value(values, '--stage') ? { stage: value(values, '--stage') } : {}),
|
|
703
|
+
...(value(values, '--round') ? { round: Number(value(values, '--round')) } : {}),
|
|
704
|
+
...(value(values, '--artifact') ? { artifact: value(values, '--artifact') } : {}),
|
|
329
705
|
...(values['--dry-run'] === true ? { dryRun: true } : {})
|
|
330
706
|
};
|
|
707
|
+
if (intent === 'recover-started') {
|
|
708
|
+
const recoveryFlags = new Set(['--agent', '--stage', '--round', '--artifact', '--reason']);
|
|
709
|
+
for (const flag of Object.keys(values)) {
|
|
710
|
+
if (!recoveryFlags.has(flag))
|
|
711
|
+
operationInvalid(`option '${flag}' is not supported for recover-started`);
|
|
712
|
+
}
|
|
713
|
+
required(values, ['--stage', '--round', '--artifact', '--reason']);
|
|
714
|
+
const stage = value(values, '--stage');
|
|
715
|
+
if (!['analysis', 'review-analysis', 'plan', 'review-plan', 'code', 'review-code'].includes(stage ?? '')) {
|
|
716
|
+
operationInvalid('--stage must be a supported lifecycle stage');
|
|
717
|
+
}
|
|
718
|
+
const round = Number(value(values, '--round'));
|
|
719
|
+
if (!Number.isSafeInteger(round) || round < 1)
|
|
720
|
+
operationInvalid('--round must be a positive integer');
|
|
721
|
+
if (value(values, '--reason').includes('\n') || value(values, '--reason').includes('\r')) {
|
|
722
|
+
operationInvalid('--reason must be a single line');
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
else if (values['--stage'] !== undefined || values['--round'] !== undefined || values['--artifact'] !== undefined) {
|
|
726
|
+
operationInvalid('--stage, --round, and --artifact are only supported for recover-started');
|
|
727
|
+
}
|
|
331
728
|
return { family, request: input };
|
|
332
729
|
}
|
|
333
730
|
const values = parseValues(args, 2, ORCHESTRATION_FLAGS);
|
|
@@ -372,7 +769,7 @@ export function parseTaskControlOperation(family, args) {
|
|
|
372
769
|
if (parsedIntent === 'prepare') {
|
|
373
770
|
input.requestedModel = value(values, '--requested-model');
|
|
374
771
|
input.requestedReasoningEffort = value(values, '--requested-reasoning-effort');
|
|
375
|
-
input.
|
|
772
|
+
input.capabilityRef = value(values, '--capability-ref');
|
|
376
773
|
}
|
|
377
774
|
if (parsedIntent === 'await-activation') {
|
|
378
775
|
required(values, ['--stage', '--round', '--artifact', '--role']);
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { isCompletionEvidence } from './orchestration.js';
|
|
3
|
+
import { TASK_WORKFLOW_OPERATIONS } from './workflow-command.js';
|
|
4
|
+
import { sameRecoveryWarning } from './recovery-warning.js';
|
|
5
|
+
/** Normalize the command result once at the control transport boundary. */
|
|
6
|
+
export function parseControlOutput(output) {
|
|
7
|
+
if (output === null)
|
|
8
|
+
return null;
|
|
9
|
+
try {
|
|
10
|
+
const value = JSON.parse(output);
|
|
11
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
12
|
+
return null;
|
|
13
|
+
return value.result && typeof value.result === 'object' && !Array.isArray(value.result) ? value.result : value;
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export const SANDBOX_CONTROL_REQUIRED_COMPLETION_PHASES = Object.freeze([
|
|
20
|
+
'completed', 'evidence-written', 'publish-authorized'
|
|
21
|
+
]);
|
|
22
|
+
const LIFECYCLE_INTENTS = ['block', 'activate', 'cancel', 'complete', 'close-codescan', 'close-dependabot', 'restore', 'recover-started'];
|
|
23
|
+
const ORCHESTRATION_INTENTS = [
|
|
24
|
+
'begin-or-resume', 'route.read', 'route.clean-completion', 'status', 'prepare', 'dispatch',
|
|
25
|
+
'await-activation', 'recover-prepared', 'hook-start', 'hook-stop', 'advance', 'pause'
|
|
26
|
+
];
|
|
27
|
+
export const SANDBOX_CONTROL_RECOVERY_OPERATIONS = Object.freeze([
|
|
28
|
+
...LIFECYCLE_INTENTS.map((intent) => ({ family: 'task-lifecycle', intent, class: 'lifecycle-mutation' })),
|
|
29
|
+
{ family: 'task-finalization', intent: 'complete', class: 'finalization' },
|
|
30
|
+
...ORCHESTRATION_INTENTS.map((intent) => ({
|
|
31
|
+
family: 'task-orchestration',
|
|
32
|
+
intent,
|
|
33
|
+
class: intent === 'route.clean-completion' ? 'route.clean-completion' : intent === 'route.read' || intent === 'status' ? 'read-only' : 'orchestration'
|
|
34
|
+
})),
|
|
35
|
+
{ family: 'task-create', intent: 'create', class: 'task-create' },
|
|
36
|
+
...TASK_WORKFLOW_OPERATIONS.map((intent) => ({ family: 'task-workflow', intent, class: intent === 'artifact-inspect' || intent === 'decision-next-id' ? 'read-only' : 'workflow' })),
|
|
37
|
+
...['open', 'close', 'verify'].map((intent) => ({ family: 'codex-controller', intent, class: 'codex-controller' }))
|
|
38
|
+
]);
|
|
39
|
+
export function digestControlRecoveryIntent(family, intent) {
|
|
40
|
+
return createHash('sha256').update(`${family}\0${intent}`, 'utf8').digest('hex');
|
|
41
|
+
}
|
|
42
|
+
export function findSandboxControlRecoveryOperation(family, intent) {
|
|
43
|
+
return SANDBOX_CONTROL_RECOVERY_OPERATIONS.find((operation) => operation.family === family && operation.intent === intent) ?? null;
|
|
44
|
+
}
|
|
45
|
+
function bindingMatchesResult(binding, result) {
|
|
46
|
+
return result.requestId === binding.requestId
|
|
47
|
+
&& result.generation === binding.generation
|
|
48
|
+
&& (result.taskId === undefined || result.taskId === binding.taskId)
|
|
49
|
+
&& (result.intentDigest === undefined || result.intentDigest === binding.intentDigest);
|
|
50
|
+
}
|
|
51
|
+
function domainEvidenceMatches(operation, result, domain) {
|
|
52
|
+
if (!domain)
|
|
53
|
+
return false;
|
|
54
|
+
if (domain.consistent !== true)
|
|
55
|
+
return false;
|
|
56
|
+
if (operation.class === 'route.clean-completion') {
|
|
57
|
+
const completion = domain.completionEvidence;
|
|
58
|
+
const snapshot = domain.snapshot;
|
|
59
|
+
const lastReviewedCommit = domain.lastReviewedCommit;
|
|
60
|
+
return result.status === 'completed'
|
|
61
|
+
&& domain.status === 'completed'
|
|
62
|
+
&& domain.pendingDelegation === null
|
|
63
|
+
&& isCompletionEvidence(completion)
|
|
64
|
+
&& snapshot?.head === completion.head
|
|
65
|
+
&& snapshot.headTree === completion.headTree
|
|
66
|
+
&& snapshot.worktreeTree === completion.worktreeTree
|
|
67
|
+
&& lastReviewedCommit === completion.lastReviewedCommit;
|
|
68
|
+
}
|
|
69
|
+
if (operation.family === 'task-lifecycle' && operation.intent === 'recover-started') {
|
|
70
|
+
const retryRequired = domain.recoveryState === 'retry-required'
|
|
71
|
+
&& sameRecoveryWarning(domain.warning, result.warning);
|
|
72
|
+
return result.targetState === 'active'
|
|
73
|
+
&& ['applied', 'no-op'].includes(String(result.status))
|
|
74
|
+
&& domain.recovery === true
|
|
75
|
+
&& domain.targetState === 'active'
|
|
76
|
+
&& (domain.recoveryState === 'released' || retryRequired);
|
|
77
|
+
}
|
|
78
|
+
if (operation.class === 'read-only')
|
|
79
|
+
return result.changed === false && domain.snapshotValid === true;
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
export function classifySandboxControlRecovery(input) {
|
|
83
|
+
const result = input.terminalResult ?? null;
|
|
84
|
+
if (input.evidenceConflict)
|
|
85
|
+
return { outcome: 'unknown', responseReconstructable: false, reasonCode: 'RECOVERY_EVIDENCE_CONFLICT' };
|
|
86
|
+
if (input.explicitRejection && !input.startedCommitted) {
|
|
87
|
+
return { outcome: 'not-executed', responseReconstructable: true, reasonCode: 'RECOVERY_EXPLICIT_REJECTION' };
|
|
88
|
+
}
|
|
89
|
+
if (!input.startedCommitted) {
|
|
90
|
+
return { outcome: 'not-executed', responseReconstructable: false, reasonCode: 'RECOVERY_NOT_STARTED' };
|
|
91
|
+
}
|
|
92
|
+
if (!result)
|
|
93
|
+
return { outcome: 'unknown', responseReconstructable: false, reasonCode: 'RECOVERY_TERMINAL_RESULT_MISSING' };
|
|
94
|
+
if (!bindingMatchesResult(input.binding, result)) {
|
|
95
|
+
return { outcome: 'unknown', responseReconstructable: false, reasonCode: 'RECOVERY_RESULT_BINDING_MISMATCH' };
|
|
96
|
+
}
|
|
97
|
+
if (!SANDBOX_CONTROL_REQUIRED_COMPLETION_PHASES.every((phase) => input.criticalPhases?.includes(phase))) {
|
|
98
|
+
return { outcome: 'unknown', responseReconstructable: false, reasonCode: 'RECOVERY_CRITICAL_AUDIT_INCOMPLETE' };
|
|
99
|
+
}
|
|
100
|
+
const status = result.status;
|
|
101
|
+
const failed = status === 'failed' || result.error !== null && result.error !== undefined;
|
|
102
|
+
if (failed) {
|
|
103
|
+
if (input.domain && input.domain.consistent === false) {
|
|
104
|
+
return { outcome: 'failure', responseReconstructable: true, reasonCode: 'RECOVERY_TERMINAL_FAILURE' };
|
|
105
|
+
}
|
|
106
|
+
return { outcome: 'unknown', responseReconstructable: false, reasonCode: 'RECOVERY_FAILURE_EVIDENCE_INCOMPLETE' };
|
|
107
|
+
}
|
|
108
|
+
if (result.changed === null) {
|
|
109
|
+
return { outcome: 'unknown', responseReconstructable: false, reasonCode: 'RECOVERY_RESULT_CHANGE_FLAG_MISSING' };
|
|
110
|
+
}
|
|
111
|
+
if (!domainEvidenceMatches(input.operation, result, input.domain)) {
|
|
112
|
+
if (input.journal?.exists && (input.journal.completedSteps?.length ?? 0) > 0) {
|
|
113
|
+
return { outcome: 'in-progress', responseReconstructable: false, reasonCode: 'RECOVERY_DOMAIN_EVIDENCE_INCOMPLETE' };
|
|
114
|
+
}
|
|
115
|
+
return { outcome: 'unknown', responseReconstructable: false, reasonCode: 'RECOVERY_DOMAIN_EVIDENCE_MISSING' };
|
|
116
|
+
}
|
|
117
|
+
if (input.operation.family === 'task-lifecycle' && input.journal?.exists) {
|
|
118
|
+
if (input.journal.failure) {
|
|
119
|
+
return { outcome: 'failure', responseReconstructable: true, reasonCode: 'RECOVERY_LIFECYCLE_JOURNAL_FAILURE' };
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
outcome: input.journal.completedSteps && input.journal.completedSteps.length > 0 ? 'in-progress' : 'unknown',
|
|
123
|
+
responseReconstructable: false,
|
|
124
|
+
reasonCode: 'RECOVERY_LIFECYCLE_JOURNAL_PRESENT'
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
outcome: 'success',
|
|
129
|
+
responseReconstructable: true,
|
|
130
|
+
reasonCode: input.operation.class === 'route.clean-completion' && result.changed === false
|
|
131
|
+
? 'RECOVERY_ROUTE_COMPLETION_NOOP'
|
|
132
|
+
: input.operation.intent === 'recover-started' && input.domain?.recoveryState === 'retry-required'
|
|
133
|
+
? 'RECOVERY_RELEASE_RETRY_REQUIRED'
|
|
134
|
+
: 'RECOVERY_TERMINAL_AND_DOMAIN_MATCH'
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
export function operationRecoveryBinding(requestId, generation, taskId, family, intent) {
|
|
138
|
+
return { requestId, generation, taskId, intentDigest: digestControlRecoveryIntent(family, intent) };
|
|
139
|
+
}
|
|
140
|
+
//# sourceMappingURL=control-recovery.js.map
|