@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
|
|
|
3
5
|
import { normalizeAgentToken } from '../agent-clients/tokens.ts';
|
|
4
6
|
import { isAgentClientId } from '../agent-clients/types.ts';
|
|
@@ -37,9 +39,484 @@ import {
|
|
|
37
39
|
type TaskLifecycleRequest,
|
|
38
40
|
type TaskLifecycleResult
|
|
39
41
|
} from './lifecycle.ts';
|
|
42
|
+
import {
|
|
43
|
+
recoverStartedLifecycleUnderLock,
|
|
44
|
+
recoveryFailure,
|
|
45
|
+
type LifecycleRecoveryResult
|
|
46
|
+
} from './lifecycle-recovery.ts';
|
|
47
|
+
import type { LifecycleRecoveryRequest } from './lifecycle-recovery.ts';
|
|
48
|
+
import { locateActivityLog } from './activity-log.ts';
|
|
40
49
|
import { resolveTaskRef, TASK_ID_RE } from './resolve-ref.ts';
|
|
41
50
|
import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.ts';
|
|
42
51
|
import { verifyTaskEvent } from './verification.ts';
|
|
52
|
+
import { createCodexCapabilityStore } from '../agent-clients/adapters/codex-lifecycle/capability-store.ts';
|
|
53
|
+
import {
|
|
54
|
+
findArtifactRecoveryIntentsByOperation,
|
|
55
|
+
writeArtifactRecoveryIntent,
|
|
56
|
+
type ArtifactRecoveryIntent
|
|
57
|
+
} from './artifact-repair-intent.ts';
|
|
58
|
+
import {
|
|
59
|
+
computeLifecycleBuildIdentity,
|
|
60
|
+
verifyLifecycleBuildIdentity,
|
|
61
|
+
type LifecycleBuildIdentity
|
|
62
|
+
} from '../agent-clients/adapters/codex-lifecycle/build-identity.ts';
|
|
63
|
+
|
|
64
|
+
export type LifecycleAuthorityPhase =
|
|
65
|
+
| 'orchestration.prepare'
|
|
66
|
+
| 'artifact.finalize-local'
|
|
67
|
+
| 'task-event.completed';
|
|
68
|
+
|
|
69
|
+
export type LifecycleAuthorityRequestV1 = Readonly<{
|
|
70
|
+
version: 1;
|
|
71
|
+
requestId: string;
|
|
72
|
+
operationId: string;
|
|
73
|
+
phase: LifecycleAuthorityPhase;
|
|
74
|
+
taskId: string;
|
|
75
|
+
family: 'analysis' | 'plan' | 'code';
|
|
76
|
+
artifact: string;
|
|
77
|
+
round: number;
|
|
78
|
+
lifecycleRequestId: string;
|
|
79
|
+
authorityRef: string;
|
|
80
|
+
expectedControlGeneration: string;
|
|
81
|
+
expectedControllerInstanceDigest: string;
|
|
82
|
+
expectedBuildIdentityDigest: string;
|
|
83
|
+
expectedHookDefinitionHash: string;
|
|
84
|
+
}>;
|
|
85
|
+
|
|
86
|
+
export type LifecycleRecoveryAttestationV1 = Readonly<{
|
|
87
|
+
version: 1;
|
|
88
|
+
attestationId: string;
|
|
89
|
+
authorityRefDigest: string;
|
|
90
|
+
operationId: string;
|
|
91
|
+
phase: LifecycleAuthorityPhase;
|
|
92
|
+
requestId: string;
|
|
93
|
+
lifecycleRequestId: string;
|
|
94
|
+
taskId: string;
|
|
95
|
+
family: 'analysis' | 'plan' | 'code';
|
|
96
|
+
artifact: string;
|
|
97
|
+
round: number;
|
|
98
|
+
controlGeneration: string;
|
|
99
|
+
controllerInstanceDigest: string;
|
|
100
|
+
sessionId: string;
|
|
101
|
+
turnId: string;
|
|
102
|
+
toolUseId: string;
|
|
103
|
+
buildIdentityDigest: string;
|
|
104
|
+
hookDefinitionHash: string;
|
|
105
|
+
issuedAt: number;
|
|
106
|
+
expiresAt: number;
|
|
107
|
+
}>;
|
|
108
|
+
|
|
109
|
+
export type LifecycleAuthorityResponseV1 = Readonly<{
|
|
110
|
+
version: 1;
|
|
111
|
+
requestId: string;
|
|
112
|
+
operationId: string;
|
|
113
|
+
phase: LifecycleAuthorityPhase;
|
|
114
|
+
status: 'issued' | 'already-completed' | 'rejected';
|
|
115
|
+
attestation: LifecycleRecoveryAttestationV1 | null;
|
|
116
|
+
error: { code: string; message: string } | null;
|
|
117
|
+
}>;
|
|
118
|
+
|
|
119
|
+
type LifecycleAuthorityIssuerOptions = Readonly<{
|
|
120
|
+
controllerBinding?: TaskControlControllerBinding | null;
|
|
121
|
+
capabilityStore?: ReturnType<typeof createCodexCapabilityStore>;
|
|
122
|
+
buildIdentity?: LifecycleBuildIdentity;
|
|
123
|
+
now?: () => number;
|
|
124
|
+
operationAttestations?: Map<string, LifecycleRecoveryAttestationV1>;
|
|
125
|
+
}>;
|
|
126
|
+
|
|
127
|
+
export type LifecycleRecoveryOperationQueryV1 = Readonly<{
|
|
128
|
+
version: 1;
|
|
129
|
+
operationId: string;
|
|
130
|
+
status: 'unknown' | 'in-progress' | 'committed';
|
|
131
|
+
capabilityState: 'unknown' | 'reserved' | 'consumed';
|
|
132
|
+
phases: readonly Readonly<{
|
|
133
|
+
phase: LifecycleAuthorityPhase;
|
|
134
|
+
requestId: string;
|
|
135
|
+
attestationId: string | null;
|
|
136
|
+
state: 'issued' | 'consumed' | 'observed';
|
|
137
|
+
}>[];
|
|
138
|
+
}>;
|
|
139
|
+
|
|
140
|
+
const lifecycleAuthorityPhases: readonly LifecycleAuthorityPhase[] = [
|
|
141
|
+
'orchestration.prepare', 'artifact.finalize-local', 'task-event.completed'
|
|
142
|
+
];
|
|
143
|
+
const lifecycleAuthorityFamilies = ['analysis', 'plan', 'code'] as const;
|
|
144
|
+
const lifecycleAuthorityArtifact = /^(?:analysis|plan|code)(?:-r[1-9]\d*)?\.md$/u;
|
|
145
|
+
const lifecycleAuthorityDigest = (value: string): string => crypto.createHash('sha256').update(value, 'utf8').digest('hex');
|
|
146
|
+
const lifecycleAuthorityBuildDigest = (value: LifecycleBuildIdentity): string => lifecycleAuthorityDigest(JSON.stringify(value));
|
|
147
|
+
|
|
148
|
+
export function lifecycleRecoveryAttestationDigest(value: LifecycleRecoveryAttestationV1): string {
|
|
149
|
+
return lifecycleAuthorityDigest(JSON.stringify(value));
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function authorityExactKeys(value: Record<string, unknown>, keys: readonly string[]): boolean {
|
|
153
|
+
return Object.keys(value).sort().join(',') === [...keys].sort().join(',');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function validateLifecycleAuthorityRequest(value: unknown): LifecycleAuthorityRequestV1 {
|
|
157
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('LIFECYCLE_AUTHORITY_REQUEST_INVALID');
|
|
158
|
+
const request = value as Record<string, unknown>;
|
|
159
|
+
if (!authorityExactKeys(request, [
|
|
160
|
+
'artifact', 'authorityRef', 'expectedBuildIdentityDigest', 'expectedControlGeneration',
|
|
161
|
+
'expectedControllerInstanceDigest', 'expectedHookDefinitionHash', 'family', 'lifecycleRequestId',
|
|
162
|
+
'operationId', 'phase', 'requestId', 'round', 'taskId', 'version'
|
|
163
|
+
])
|
|
164
|
+
|| request.version !== 1
|
|
165
|
+
|| typeof request.requestId !== 'string' || !/^[a-f0-9-]{16,64}$/u.test(request.requestId)
|
|
166
|
+
|| typeof request.operationId !== 'string' || !/^[a-f0-9-]{16,64}$/u.test(request.operationId)
|
|
167
|
+
|| !lifecycleAuthorityPhases.includes(request.phase as LifecycleAuthorityPhase)
|
|
168
|
+
|| typeof request.taskId !== 'string' || !/^TASK-\d{8}-\d{6}$/u.test(request.taskId)
|
|
169
|
+
|| !lifecycleAuthorityFamilies.includes(request.family as typeof lifecycleAuthorityFamilies[number])
|
|
170
|
+
|| typeof request.artifact !== 'string' || !lifecycleAuthorityArtifact.test(request.artifact)
|
|
171
|
+
|| !Number.isSafeInteger(request.round) || (request.round as number) < 1
|
|
172
|
+
|| typeof request.lifecycleRequestId !== 'string' || !request.lifecycleRequestId || /[\r\n]/u.test(request.lifecycleRequestId)
|
|
173
|
+
|| typeof request.authorityRef !== 'string' || !request.authorityRef || /[\r\n]/u.test(request.authorityRef)
|
|
174
|
+
|| typeof request.expectedControlGeneration !== 'string' || !request.expectedControlGeneration
|
|
175
|
+
|| typeof request.expectedControllerInstanceDigest !== 'string' || !/^[a-f0-9]{64}$/u.test(request.expectedControllerInstanceDigest)
|
|
176
|
+
|| typeof request.expectedBuildIdentityDigest !== 'string' || !/^[a-f0-9]{64}$/u.test(request.expectedBuildIdentityDigest)
|
|
177
|
+
|| typeof request.expectedHookDefinitionHash !== 'string' || !/^[a-f0-9]{64}$/u.test(request.expectedHookDefinitionHash)) {
|
|
178
|
+
throw new Error('LIFECYCLE_AUTHORITY_REQUEST_INVALID');
|
|
179
|
+
}
|
|
180
|
+
const expectedArtifactFamily = request.artifact as string;
|
|
181
|
+
if (!expectedArtifactFamily.startsWith(request.family as string)) throw new Error('LIFECYCLE_AUTHORITY_REQUEST_INVALID');
|
|
182
|
+
return request as LifecycleAuthorityRequestV1;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export function validateLifecycleRecoveryAttestation(
|
|
186
|
+
value: unknown,
|
|
187
|
+
now = Date.now()
|
|
188
|
+
): LifecycleRecoveryAttestationV1 {
|
|
189
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('LIFECYCLE_AUTHORITY_ATTESTATION_INVALID');
|
|
190
|
+
const attestation = value as Record<string, unknown>;
|
|
191
|
+
if (!authorityExactKeys(attestation, [
|
|
192
|
+
'artifact', 'attestationId', 'authorityRefDigest', 'buildIdentityDigest', 'controlGeneration',
|
|
193
|
+
'controllerInstanceDigest', 'expiresAt', 'family', 'hookDefinitionHash', 'issuedAt',
|
|
194
|
+
'lifecycleRequestId', 'operationId', 'phase', 'requestId', 'round', 'sessionId',
|
|
195
|
+
'taskId', 'toolUseId', 'turnId', 'version'
|
|
196
|
+
])
|
|
197
|
+
|| attestation.version !== 1
|
|
198
|
+
|| typeof attestation.attestationId !== 'string' || !/^[a-f0-9-]{16,64}$/u.test(attestation.attestationId)
|
|
199
|
+
|| typeof attestation.authorityRefDigest !== 'string' || !/^[a-f0-9]{64}$/u.test(attestation.authorityRefDigest)
|
|
200
|
+
|| typeof attestation.operationId !== 'string' || !/^[a-f0-9-]{16,64}$/u.test(attestation.operationId)
|
|
201
|
+
|| !lifecycleAuthorityPhases.includes(attestation.phase as LifecycleAuthorityPhase)
|
|
202
|
+
|| typeof attestation.requestId !== 'string' || !/^[a-f0-9-]{16,64}$/u.test(attestation.requestId)
|
|
203
|
+
|| typeof attestation.lifecycleRequestId !== 'string' || !attestation.lifecycleRequestId
|
|
204
|
+
|| typeof attestation.taskId !== 'string' || !/^TASK-\d{8}-\d{6}$/u.test(attestation.taskId)
|
|
205
|
+
|| !lifecycleAuthorityFamilies.includes(attestation.family as typeof lifecycleAuthorityFamilies[number])
|
|
206
|
+
|| typeof attestation.artifact !== 'string' || !lifecycleAuthorityArtifact.test(attestation.artifact)
|
|
207
|
+
|| !Number.isSafeInteger(attestation.round) || (attestation.round as number) < 1
|
|
208
|
+
|| typeof attestation.controlGeneration !== 'string' || !attestation.controlGeneration
|
|
209
|
+
|| typeof attestation.controllerInstanceDigest !== 'string' || !/^[a-f0-9]{64}$/u.test(attestation.controllerInstanceDigest)
|
|
210
|
+
|| typeof attestation.sessionId !== 'string' || !attestation.sessionId
|
|
211
|
+
|| typeof attestation.turnId !== 'string' || !attestation.turnId
|
|
212
|
+
|| typeof attestation.toolUseId !== 'string' || !attestation.toolUseId
|
|
213
|
+
|| typeof attestation.buildIdentityDigest !== 'string' || !/^[a-f0-9]{64}$/u.test(attestation.buildIdentityDigest)
|
|
214
|
+
|| typeof attestation.hookDefinitionHash !== 'string' || !/^[a-f0-9]{64}$/u.test(attestation.hookDefinitionHash)
|
|
215
|
+
|| !Number.isSafeInteger(attestation.issuedAt) || !Number.isSafeInteger(attestation.expiresAt)
|
|
216
|
+
|| (attestation.expiresAt as number) <= (attestation.issuedAt as number)
|
|
217
|
+
|| (attestation.expiresAt as number) <= now) {
|
|
218
|
+
throw new Error('LIFECYCLE_AUTHORITY_ATTESTATION_INVALID');
|
|
219
|
+
}
|
|
220
|
+
return attestation as LifecycleRecoveryAttestationV1;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const authorityOperationMap = new WeakMap<object, Map<string, LifecycleRecoveryAttestationV1>>();
|
|
224
|
+
const authorityRecords = new Map<string, Readonly<{
|
|
225
|
+
store: ReturnType<typeof createCodexCapabilityStore>;
|
|
226
|
+
capabilityRef: string;
|
|
227
|
+
expected: Readonly<{
|
|
228
|
+
taskId: string;
|
|
229
|
+
hookDefinitionHash: string;
|
|
230
|
+
buildIdentity: LifecycleBuildIdentity;
|
|
231
|
+
controller: TaskControlControllerBinding;
|
|
232
|
+
}>;
|
|
233
|
+
}>>();
|
|
234
|
+
const consumedAuthorityPhases = new Set<string>();
|
|
235
|
+
|
|
236
|
+
export function issueLifecycleRecoveryAttestation(
|
|
237
|
+
input: LifecycleAuthorityRequestV1,
|
|
238
|
+
options: LifecycleAuthorityIssuerOptions = {}
|
|
239
|
+
): LifecycleAuthorityResponseV1 {
|
|
240
|
+
let request: LifecycleAuthorityRequestV1;
|
|
241
|
+
try { request = validateLifecycleAuthorityRequest(input); }
|
|
242
|
+
catch (error) {
|
|
243
|
+
return { version: 1, requestId: String((input as { requestId?: unknown })?.requestId ?? ''), operationId: String((input as { operationId?: unknown })?.operationId ?? ''), phase: (input as { phase?: LifecycleAuthorityPhase })?.phase ?? 'artifact.finalize-local', status: 'rejected', attestation: null, error: { code: 'LIFECYCLE_AUTHORITY_REQUEST_INVALID', message: error instanceof Error ? error.message : String(error) } };
|
|
244
|
+
}
|
|
245
|
+
const binding = options.controllerBinding ?? null;
|
|
246
|
+
if (!binding) return authorityRejected(request, 'LOCAL_EXECUTION_AUTHORITY_UNAVAILABLE', 'current controller authority is unavailable');
|
|
247
|
+
if (binding.controlGeneration !== request.expectedControlGeneration
|
|
248
|
+
|| binding.instanceDigest !== request.expectedControllerInstanceDigest) {
|
|
249
|
+
return authorityRejected(request, 'LIFECYCLE_AUTHORITY_CONTROLLER_MISMATCH', 'current controller binding does not match the request');
|
|
250
|
+
}
|
|
251
|
+
const buildIdentity = options.buildIdentity ?? computeLifecycleBuildIdentity(process.cwd());
|
|
252
|
+
if (lifecycleAuthorityBuildDigest(buildIdentity) !== request.expectedBuildIdentityDigest) {
|
|
253
|
+
return authorityRejected(request, 'LIFECYCLE_AUTHORITY_BUILD_MISMATCH', 'current lifecycle build does not match the request');
|
|
254
|
+
}
|
|
255
|
+
const store = options.capabilityStore ?? createCodexCapabilityStore();
|
|
256
|
+
const registry = options.operationAttestations ?? authorityOperationMap.get(store) ?? new Map<string, LifecycleRecoveryAttestationV1>();
|
|
257
|
+
authorityOperationMap.set(store, registry);
|
|
258
|
+
const key = `${request.requestId}\0${request.operationId}\0${request.phase}`;
|
|
259
|
+
const prior = registry.get(key);
|
|
260
|
+
if (prior) return { version: 1, requestId: request.requestId, operationId: request.operationId, phase: request.phase, status: 'already-completed', attestation: prior, error: null };
|
|
261
|
+
try {
|
|
262
|
+
const expected = {
|
|
263
|
+
taskId: request.taskId,
|
|
264
|
+
hookDefinitionHash: request.expectedHookDefinitionHash,
|
|
265
|
+
buildIdentity,
|
|
266
|
+
controller: binding
|
|
267
|
+
};
|
|
268
|
+
const record = store.reserveReference(request.authorityRef, request.operationId, expected);
|
|
269
|
+
if (!record.sessionId || !record.turnId || !record.toolUseId) throw new Error('CODEX_CAPABILITY_IDENTITY_INVALID: capability hook attestation is incomplete');
|
|
270
|
+
const now = (options.now ?? Date.now)();
|
|
271
|
+
const attestation = validateLifecycleRecoveryAttestation({
|
|
272
|
+
version: 1,
|
|
273
|
+
attestationId: crypto.randomUUID(),
|
|
274
|
+
authorityRefDigest: lifecycleAuthorityDigest(request.authorityRef),
|
|
275
|
+
operationId: request.operationId,
|
|
276
|
+
phase: request.phase,
|
|
277
|
+
requestId: request.requestId,
|
|
278
|
+
lifecycleRequestId: request.lifecycleRequestId,
|
|
279
|
+
taskId: request.taskId,
|
|
280
|
+
family: request.family,
|
|
281
|
+
artifact: request.artifact,
|
|
282
|
+
round: request.round,
|
|
283
|
+
controlGeneration: binding.controlGeneration,
|
|
284
|
+
controllerInstanceDigest: binding.instanceDigest,
|
|
285
|
+
sessionId: record.sessionId,
|
|
286
|
+
turnId: record.turnId,
|
|
287
|
+
toolUseId: record.toolUseId,
|
|
288
|
+
buildIdentityDigest: lifecycleAuthorityBuildDigest(buildIdentity),
|
|
289
|
+
hookDefinitionHash: request.expectedHookDefinitionHash,
|
|
290
|
+
issuedAt: now,
|
|
291
|
+
expiresAt: Math.min(record.expiresAt, now + 30_000)
|
|
292
|
+
}, now);
|
|
293
|
+
store.reserveRecoveryPhase(
|
|
294
|
+
request.authorityRef,
|
|
295
|
+
request.operationId,
|
|
296
|
+
request.phase,
|
|
297
|
+
request.requestId,
|
|
298
|
+
expected
|
|
299
|
+
);
|
|
300
|
+
registry.set(key, attestation);
|
|
301
|
+
authorityRecords.set(attestation.attestationId, { store, capabilityRef: request.authorityRef, expected });
|
|
302
|
+
return { version: 1, requestId: request.requestId, operationId: request.operationId, phase: request.phase, status: 'issued', attestation, error: null };
|
|
303
|
+
} catch (error) {
|
|
304
|
+
return authorityRejected(request, error instanceof Error && error.name.startsWith('CODEX_') ? error.name : 'LIFECYCLE_AUTHORITY_REJECTED', error instanceof Error ? error.message : String(error));
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
export function consumeLifecycleRecoveryAttestation(
|
|
309
|
+
value: LifecycleRecoveryAttestationV1,
|
|
310
|
+
now = Date.now()
|
|
311
|
+
): void {
|
|
312
|
+
const attestation = validateLifecycleRecoveryAttestation(value, now);
|
|
313
|
+
if (consumedAuthorityPhases.has(attestation.attestationId)) return;
|
|
314
|
+
const record = authorityRecords.get(attestation.attestationId);
|
|
315
|
+
if (!record) throw new Error('LIFECYCLE_AUTHORITY_ATTESTATION_UNKNOWN');
|
|
316
|
+
record.store.consumeRecoveryPhase(
|
|
317
|
+
record.capabilityRef,
|
|
318
|
+
attestation.operationId,
|
|
319
|
+
attestation.phase,
|
|
320
|
+
attestation.requestId,
|
|
321
|
+
record.expected
|
|
322
|
+
);
|
|
323
|
+
if (attestation.phase === 'task-event.completed') {
|
|
324
|
+
record.store.consumeReference(record.capabilityRef, attestation.operationId, record.expected);
|
|
325
|
+
}
|
|
326
|
+
consumedAuthorityPhases.add(attestation.attestationId);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
export function queryLifecycleRecoveryOperation(
|
|
330
|
+
operationId: string,
|
|
331
|
+
options: Readonly<{
|
|
332
|
+
repoRoot?: string;
|
|
333
|
+
capabilityStore?: ReturnType<typeof createCodexCapabilityStore>;
|
|
334
|
+
}> = {}
|
|
335
|
+
): LifecycleRecoveryOperationQueryV1 {
|
|
336
|
+
if (!/^[a-f0-9-]{16,64}$/u.test(operationId)) {
|
|
337
|
+
throw new Error('LIFECYCLE_RECOVERY_OPERATION_INVALID');
|
|
338
|
+
}
|
|
339
|
+
const store = options.capabilityStore ?? createCodexCapabilityStore();
|
|
340
|
+
const registry = authorityOperationMap.get(store);
|
|
341
|
+
const known = [...(registry?.values() ?? [])].filter((value) => value.operationId === operationId);
|
|
342
|
+
const intents = options.repoRoot ? findArtifactRecoveryIntentsByOperation(options.repoRoot, operationId) : [];
|
|
343
|
+
const capabilities = store.findByRecoveryOperation(operationId);
|
|
344
|
+
const phaseMap = new Map<string, LifecycleRecoveryOperationQueryV1['phases'][number]>();
|
|
345
|
+
for (const capability of capabilities) {
|
|
346
|
+
for (const phase of capability.recoveryPhases) {
|
|
347
|
+
phaseMap.set(`${phase.phase}\0${phase.requestId}`, {
|
|
348
|
+
phase: phase.phase,
|
|
349
|
+
requestId: phase.requestId,
|
|
350
|
+
attestationId: null,
|
|
351
|
+
state: phase.state === 'consumed' ? 'consumed' : 'observed'
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
for (const attestation of known) {
|
|
356
|
+
const key = `${attestation.phase}\0${attestation.requestId}`;
|
|
357
|
+
const durable = phaseMap.get(key);
|
|
358
|
+
phaseMap.set(key, {
|
|
359
|
+
phase: attestation.phase,
|
|
360
|
+
requestId: attestation.requestId,
|
|
361
|
+
attestationId: attestation.attestationId,
|
|
362
|
+
state: durable?.state === 'consumed' || consumedAuthorityPhases.has(attestation.attestationId)
|
|
363
|
+
? 'consumed' : 'issued'
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
for (const intent of intents) {
|
|
367
|
+
if (!intent.phase) continue;
|
|
368
|
+
const key = `${intent.phase}\0${intent.requestId}`;
|
|
369
|
+
if (phaseMap.has(key)) continue;
|
|
370
|
+
phaseMap.set(key, {
|
|
371
|
+
phase: intent.phase,
|
|
372
|
+
requestId: intent.requestId,
|
|
373
|
+
attestationId: null,
|
|
374
|
+
state: intent.state === 'consumed' ? 'consumed' : 'observed'
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
const capabilityState = capabilities.some((record) => record.recoveryState === 'consumed')
|
|
378
|
+
? 'consumed'
|
|
379
|
+
: capabilities.some((record) => record.recoveryState === 'reserved') ? 'reserved' : 'unknown';
|
|
380
|
+
const committed = capabilityState === 'consumed' || intents.some((intent) => intent.state === 'consumed');
|
|
381
|
+
return {
|
|
382
|
+
version: 1,
|
|
383
|
+
operationId,
|
|
384
|
+
status: committed ? 'committed' : capabilityState === 'reserved' || phaseMap.size > 0 ? 'in-progress' : 'unknown',
|
|
385
|
+
capabilityState,
|
|
386
|
+
phases: Object.freeze([...phaseMap.values()])
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
type LifecycleRecoveryCompensationOptions = Readonly<{
|
|
391
|
+
repoRoot: string;
|
|
392
|
+
controllerBinding: TaskControlControllerBinding;
|
|
393
|
+
capabilityStore?: ReturnType<typeof createCodexCapabilityStore>;
|
|
394
|
+
buildIdentity?: LifecycleBuildIdentity;
|
|
395
|
+
now?: () => number;
|
|
396
|
+
}>;
|
|
397
|
+
|
|
398
|
+
function lifecycleRecoveryCompensationError(code: string, message: string): Error {
|
|
399
|
+
const error = new Error(`${code}: ${message}`);
|
|
400
|
+
error.name = code;
|
|
401
|
+
return error;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function lifecycleRecoveryEventCommitted(
|
|
405
|
+
selector: LifecycleAuthorityRequestV1,
|
|
406
|
+
repoRoot: string
|
|
407
|
+
): boolean {
|
|
408
|
+
const resolved = resolveTaskRef(selector.taskId, { repoRoot });
|
|
409
|
+
if (!resolved.ok || resolved.taskId !== selector.taskId) return false;
|
|
410
|
+
let content: string;
|
|
411
|
+
try { content = fs.readFileSync(resolved.taskMdPath, 'utf8'); }
|
|
412
|
+
catch { return false; }
|
|
413
|
+
const section = locateActivityLog(content);
|
|
414
|
+
if (!section) return false;
|
|
415
|
+
const labels = { analysis: 'Analyze Task', plan: 'Plan Task', code: 'Code Task' } as const;
|
|
416
|
+
const prefix = `${labels[selector.family]} (Round ${selector.round}`;
|
|
417
|
+
return section.entries.some((entry) => {
|
|
418
|
+
if (entry.step.endsWith(' [started]') || !entry.step.startsWith(prefix)) return false;
|
|
419
|
+
const delimiter = entry.step[prefix.length];
|
|
420
|
+
return (delimiter === ',' || delimiter === ')') && entry.note.endsWith(`→ ${selector.artifact}`);
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function lifecycleRecoveryIntentMatches(
|
|
425
|
+
intent: ArtifactRecoveryIntent,
|
|
426
|
+
selector: LifecycleAuthorityRequestV1
|
|
427
|
+
): boolean {
|
|
428
|
+
return intent.taskId === selector.taskId
|
|
429
|
+
&& intent.family === selector.family
|
|
430
|
+
&& intent.artifact === selector.artifact
|
|
431
|
+
&& (intent.state === 'commit-started' || intent.state === 'consumed');
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Completes only the durable tail of a committed task event after a process
|
|
436
|
+
* restart. The task event itself is the commit boundary and is never replayed.
|
|
437
|
+
*/
|
|
438
|
+
export function recoverLifecycleRecoveryOperation(
|
|
439
|
+
input: LifecycleAuthorityRequestV1,
|
|
440
|
+
options: LifecycleRecoveryCompensationOptions
|
|
441
|
+
): LifecycleRecoveryOperationQueryV1 {
|
|
442
|
+
const selector = validateLifecycleAuthorityRequest(input);
|
|
443
|
+
if (selector.phase !== 'task-event.completed') {
|
|
444
|
+
throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_PHASE_UNSUPPORTED', 'only task-event.completed supports durable compensation');
|
|
445
|
+
}
|
|
446
|
+
const buildIdentity = options.buildIdentity ?? computeLifecycleBuildIdentity(options.repoRoot);
|
|
447
|
+
if (selector.expectedBuildIdentityDigest !== lifecycleAuthorityBuildDigest(buildIdentity)) {
|
|
448
|
+
throw lifecycleRecoveryCompensationError('LIFECYCLE_AUTHORITY_BUILD_MISMATCH', 'current lifecycle build does not match the recovery request');
|
|
449
|
+
}
|
|
450
|
+
if (selector.expectedControlGeneration !== options.controllerBinding.controlGeneration
|
|
451
|
+
|| selector.expectedControllerInstanceDigest !== options.controllerBinding.instanceDigest) {
|
|
452
|
+
throw lifecycleRecoveryCompensationError('LIFECYCLE_AUTHORITY_CONTROLLER_MISMATCH', 'current controller binding does not match the recovery request');
|
|
453
|
+
}
|
|
454
|
+
const store = options.capabilityStore ?? createCodexCapabilityStore();
|
|
455
|
+
const expected = {
|
|
456
|
+
taskId: selector.taskId,
|
|
457
|
+
hookDefinitionHash: selector.expectedHookDefinitionHash,
|
|
458
|
+
buildIdentity,
|
|
459
|
+
controller: options.controllerBinding
|
|
460
|
+
};
|
|
461
|
+
const capabilities = store.findByRecoveryOperation(selector.operationId);
|
|
462
|
+
if (capabilities.length !== 1 || capabilities[0]!.capabilityRefDigest !== lifecycleAuthorityDigest(selector.authorityRef)) {
|
|
463
|
+
throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_PROVENANCE_INVALID', 'durable capability provenance does not identify this operation');
|
|
464
|
+
}
|
|
465
|
+
const capability = capabilities[0]!;
|
|
466
|
+
const build = verifyLifecycleBuildIdentity(capability.buildIdentity, buildIdentity);
|
|
467
|
+
const expiredReserved = capability.status === 'expired' && capability.recoveryState === 'reserved';
|
|
468
|
+
if (!build.ok
|
|
469
|
+
|| capability.taskId !== selector.taskId
|
|
470
|
+
|| capability.hookDefinitionHash !== selector.expectedHookDefinitionHash
|
|
471
|
+
|| capability.controller?.controlGeneration !== options.controllerBinding.controlGeneration
|
|
472
|
+
|| capability.controller?.instanceDigest !== options.controllerBinding.instanceDigest
|
|
473
|
+
|| capability.recoveryState === 'unreserved'
|
|
474
|
+
|| capability.recoveryOperationId !== selector.operationId
|
|
475
|
+
|| (!['attested', 'consumed'].includes(capability.status) && !expiredReserved)) {
|
|
476
|
+
throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_PROVENANCE_INVALID', 'durable capability provenance does not match the recovery request');
|
|
477
|
+
}
|
|
478
|
+
const phase = capability.recoveryPhases.find((entry) => entry.phase === selector.phase);
|
|
479
|
+
if (!phase) {
|
|
480
|
+
throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_PHASE_MISSING', 'durable task completion phase is missing');
|
|
481
|
+
}
|
|
482
|
+
const intents = findArtifactRecoveryIntentsByOperation(options.repoRoot, selector.operationId)
|
|
483
|
+
.filter((intent) => intent.taskId === selector.taskId
|
|
484
|
+
&& intent.family === selector.family
|
|
485
|
+
&& intent.artifact === selector.artifact);
|
|
486
|
+
if (intents.length !== 1) {
|
|
487
|
+
throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_INTENT_INVALID', 'durable artifact recovery intent does not identify this task event');
|
|
488
|
+
}
|
|
489
|
+
const intent = intents[0]!;
|
|
490
|
+
if (!lifecycleRecoveryIntentMatches(intent, selector)
|
|
491
|
+
|| intent.phase !== selector.phase
|
|
492
|
+
|| intent.requestId !== selector.lifecycleRequestId
|
|
493
|
+
|| intent.authorityDigest === null) {
|
|
494
|
+
throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_INTENT_INVALID', 'durable artifact recovery intent does not match the recovery request');
|
|
495
|
+
}
|
|
496
|
+
if (!lifecycleRecoveryEventCommitted(selector, options.repoRoot)) {
|
|
497
|
+
throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_COMMIT_UNCONFIRMED', 'task completion is not durably present; compensation is not allowed');
|
|
498
|
+
}
|
|
499
|
+
if (capability.recoveryState === 'reserved') {
|
|
500
|
+
if (phase.state === 'issued') {
|
|
501
|
+
store.consumeRecoveryPhase(selector.authorityRef, selector.operationId, selector.phase, phase.requestId, expected, { allowExpiredReserved: true });
|
|
502
|
+
}
|
|
503
|
+
store.consumeReference(selector.authorityRef, selector.operationId, expected, { allowExpiredReserved: true });
|
|
504
|
+
} else if (phase.state !== 'consumed') {
|
|
505
|
+
throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_STATE_INVALID', 'consumed capability has an unconsumed completion phase');
|
|
506
|
+
}
|
|
507
|
+
if (intent.state !== 'consumed') {
|
|
508
|
+
writeArtifactRecoveryIntent(options.repoRoot, {
|
|
509
|
+
...intent,
|
|
510
|
+
state: 'consumed',
|
|
511
|
+
updatedAt: (options.now ?? Date.now)()
|
|
512
|
+
}, { expected: intent });
|
|
513
|
+
}
|
|
514
|
+
return queryLifecycleRecoveryOperation(selector.operationId, { repoRoot: options.repoRoot, capabilityStore: store });
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
function authorityRejected(request: LifecycleAuthorityRequestV1, code: string, message: string): LifecycleAuthorityResponseV1 {
|
|
518
|
+
return { version: 1, requestId: request.requestId, operationId: request.operationId, phase: request.phase, status: 'rejected', attestation: null, error: { code, message } };
|
|
519
|
+
}
|
|
43
520
|
|
|
44
521
|
export type TaskControlControllerBinding = Readonly<{
|
|
45
522
|
instanceDigest: string;
|
|
@@ -53,6 +530,7 @@ export type TaskControlExecutionContext =
|
|
|
53
530
|
repoRoot: string;
|
|
54
531
|
runtimeDir?: string;
|
|
55
532
|
controllerBinding?: TaskControlControllerBinding | null;
|
|
533
|
+
lifecycleRecoveryAttestation?: LifecycleRecoveryAttestationV1 | null;
|
|
56
534
|
}>
|
|
57
535
|
| Readonly<{
|
|
58
536
|
source: 'sandbox-executor';
|
|
@@ -66,6 +544,7 @@ export type TaskControlExecutionContext =
|
|
|
66
544
|
requestId: string;
|
|
67
545
|
diagnosticLog?: OrchestrationDiagnosticLogger;
|
|
68
546
|
controllerBinding?: TaskControlControllerBinding | null;
|
|
547
|
+
lifecycleRecoveryAttestation?: LifecycleRecoveryAttestationV1 | null;
|
|
69
548
|
}>;
|
|
70
549
|
|
|
71
550
|
export type TaskControlOrchestrationIntent =
|
|
@@ -92,7 +571,15 @@ export type TaskControlOperation =
|
|
|
92
571
|
options?: Omit<OrchestrationOptions, 'repoRoot'>;
|
|
93
572
|
}>;
|
|
94
573
|
|
|
95
|
-
export type TaskControlDispatchResult = TaskLifecycleResult | TaskFinalizationResult | OrchestrationResult;
|
|
574
|
+
export type TaskControlDispatchResult = TaskLifecycleResult | LifecycleRecoveryResult | TaskFinalizationResult | OrchestrationResult;
|
|
575
|
+
|
|
576
|
+
export function taskControlOperationKey(operation: TaskControlOperation): string {
|
|
577
|
+
if (operation.family === 'task-finalization') return 'complete';
|
|
578
|
+
if (operation.family === 'task-lifecycle') return operation.request.intent;
|
|
579
|
+
return operation.intent === 'route'
|
|
580
|
+
? operation.input && operation.input.completed === true ? 'route.clean-completion' : 'route.read'
|
|
581
|
+
: operation.intent;
|
|
582
|
+
}
|
|
96
583
|
|
|
97
584
|
export type TaskLifecycleControlRequest = TaskLifecycleRequest & Readonly<{
|
|
98
585
|
overrideTicket?: string;
|
|
@@ -121,13 +608,18 @@ export function createDirectHostExecutionContext(params: Readonly<{
|
|
|
121
608
|
repoRoot: string;
|
|
122
609
|
runtimeDir?: string;
|
|
123
610
|
controllerBinding?: TaskControlControllerBinding | null;
|
|
611
|
+
lifecycleRecoveryAttestation?: LifecycleRecoveryAttestationV1 | null;
|
|
124
612
|
}>): TaskControlExecutionContext {
|
|
125
613
|
return {
|
|
126
614
|
source: 'direct-host',
|
|
127
615
|
mode: 'direct-host',
|
|
128
616
|
repoRoot: absolute('repoRoot', params.repoRoot),
|
|
129
617
|
...(params.runtimeDir === undefined ? {} : { runtimeDir: absolute('runtimeDir', params.runtimeDir) }),
|
|
130
|
-
...(params.controllerBinding === undefined ? {} : { controllerBinding: binding(params.controllerBinding) })
|
|
618
|
+
...(params.controllerBinding === undefined ? {} : { controllerBinding: binding(params.controllerBinding) }),
|
|
619
|
+
...(params.lifecycleRecoveryAttestation === undefined ? {} : {
|
|
620
|
+
lifecycleRecoveryAttestation: params.lifecycleRecoveryAttestation === null
|
|
621
|
+
? null : validateLifecycleRecoveryAttestation(params.lifecycleRecoveryAttestation)
|
|
622
|
+
})
|
|
131
623
|
};
|
|
132
624
|
}
|
|
133
625
|
|
|
@@ -141,6 +633,7 @@ export function createSandboxExecutorExecutionContext(params: Readonly<{
|
|
|
141
633
|
requestId: string;
|
|
142
634
|
diagnosticLog?: OrchestrationDiagnosticLogger;
|
|
143
635
|
controllerBinding?: TaskControlControllerBinding | null;
|
|
636
|
+
lifecycleRecoveryAttestation?: LifecycleRecoveryAttestationV1 | null;
|
|
144
637
|
}>): TaskControlExecutionContext {
|
|
145
638
|
if (!/^TASK-\d{8}-\d{6}$/u.test(params.taskId) || !params.generation || !params.requestId) {
|
|
146
639
|
invalidContext('sandbox binding is incomplete');
|
|
@@ -160,7 +653,11 @@ export function createSandboxExecutorExecutionContext(params: Readonly<{
|
|
|
160
653
|
manifestPath: absolute('manifestPath', params.manifestPath),
|
|
161
654
|
requestId: params.requestId,
|
|
162
655
|
...(params.diagnosticLog === undefined ? {} : { diagnosticLog: params.diagnosticLog }),
|
|
163
|
-
...(params.controllerBinding === undefined ? {} : { controllerBinding: binding(params.controllerBinding) })
|
|
656
|
+
...(params.controllerBinding === undefined ? {} : { controllerBinding: binding(params.controllerBinding) }),
|
|
657
|
+
...(params.lifecycleRecoveryAttestation === undefined ? {} : {
|
|
658
|
+
lifecycleRecoveryAttestation: params.lifecycleRecoveryAttestation === null
|
|
659
|
+
? null : validateLifecycleRecoveryAttestation(params.lifecycleRecoveryAttestation)
|
|
660
|
+
})
|
|
164
661
|
};
|
|
165
662
|
}
|
|
166
663
|
|
|
@@ -170,6 +667,7 @@ export function assertTaskControlExecutionContext(context: TaskControlExecutionC
|
|
|
170
667
|
absolute('repoRoot', context.repoRoot);
|
|
171
668
|
if (context.runtimeDir !== undefined) absolute('runtimeDir', context.runtimeDir);
|
|
172
669
|
binding(context.controllerBinding);
|
|
670
|
+
if (context.lifecycleRecoveryAttestation) validateLifecycleRecoveryAttestation(context.lifecycleRecoveryAttestation);
|
|
173
671
|
return;
|
|
174
672
|
}
|
|
175
673
|
if (context.mode !== 'task-bound-sandbox' || !/^TASK-\d{8}-\d{6}$/u.test(context.taskId)
|
|
@@ -184,6 +682,7 @@ export function assertTaskControlExecutionContext(context: TaskControlExecutionC
|
|
|
184
682
|
invalidContext('runtimeDir is not bound to the manifest control root');
|
|
185
683
|
}
|
|
186
684
|
binding(context.controllerBinding);
|
|
685
|
+
if (context.lifecycleRecoveryAttestation) validateLifecycleRecoveryAttestation(context.lifecycleRecoveryAttestation);
|
|
187
686
|
}
|
|
188
687
|
|
|
189
688
|
function operationTaskId(context: TaskControlExecutionContext, taskRef: string): void {
|
|
@@ -219,12 +718,15 @@ function lifecycleTaskId(request: TaskLifecycleControlRequest, repoRoot: string)
|
|
|
219
718
|
async function applyLifecycleWithAuthority(
|
|
220
719
|
context: TaskControlExecutionContext,
|
|
221
720
|
request: TaskLifecycleControlRequest
|
|
222
|
-
): Promise<TaskLifecycleResult & { humanOverride?: unknown }> {
|
|
721
|
+
): Promise<(TaskLifecycleResult | LifecycleRecoveryResult) & { humanOverride?: unknown }> {
|
|
223
722
|
const conflict = overrideDryRunConflict(request as unknown as Record<string, unknown>);
|
|
224
723
|
if (conflict) {
|
|
225
724
|
return taskLifecycleFailure(request, { code: 'LIFECYCLE_PAYLOAD_INVALID', message: conflict.message });
|
|
226
725
|
}
|
|
227
|
-
const execute = async (): Promise<TaskLifecycleResult & { humanOverride?: unknown }> => {
|
|
726
|
+
const execute = async (): Promise<(TaskLifecycleResult | LifecycleRecoveryResult) & { humanOverride?: unknown }> => {
|
|
727
|
+
if (request.intent === 'recover-started') {
|
|
728
|
+
return recoverStartedLifecycleUnderLock(request as LifecycleRecoveryRequest, { repoRoot: context.repoRoot });
|
|
729
|
+
}
|
|
228
730
|
const lifecycleResult = applyTaskLifecycle(request, { repoRoot: context.repoRoot });
|
|
229
731
|
if (lifecycleResult.status !== 'failed' || !request.overrideTicket) return lifecycleResult;
|
|
230
732
|
if (!request.overrideTarget || !request.overrideScope) {
|
|
@@ -258,6 +760,9 @@ async function applyLifecycleWithAuthority(
|
|
|
258
760
|
);
|
|
259
761
|
} catch (error) {
|
|
260
762
|
if (!(error instanceof TaskExecutionLockError)) throw error;
|
|
763
|
+
if (request.intent === 'recover-started') {
|
|
764
|
+
return recoveryFailure(request as LifecycleRecoveryRequest, 'owner-unknown', error.code, error.message);
|
|
765
|
+
}
|
|
261
766
|
return taskLifecycleFailure(request, { code: error.code, message: error.message }, taskId);
|
|
262
767
|
}
|
|
263
768
|
}
|
|
@@ -284,12 +789,16 @@ function orchestration(
|
|
|
284
789
|
client: input.client as AgentClientId,
|
|
285
790
|
requestedModel: input.requestedModel as string | undefined,
|
|
286
791
|
requestedReasoningEffort: input.requestedReasoningEffort as string | undefined,
|
|
287
|
-
...(input.
|
|
792
|
+
...(input.capabilityRef === undefined ? {} : { capabilityRef: input.capabilityRef as string }),
|
|
793
|
+
...(context.lifecycleRecoveryAttestation === undefined
|
|
794
|
+
? {}
|
|
795
|
+
: { lifecycleRecoveryAttestation: context.lifecycleRecoveryAttestation })
|
|
288
796
|
};
|
|
289
797
|
if (prepareInput.client === 'codex') {
|
|
290
798
|
return prepareCodexOrchestrationDelegation(operation.taskRef, prepareInput, {
|
|
291
799
|
repoRoot: context.repoRoot,
|
|
292
800
|
orchestrationOptions: options,
|
|
801
|
+
deferLifecycleRecoveryConsumption: context.source === 'sandbox-executor',
|
|
293
802
|
...(context.controllerBinding ? { controllerBinding: context.controllerBinding } : {})
|
|
294
803
|
});
|
|
295
804
|
}
|
|
@@ -332,7 +841,7 @@ function orchestrationFailure(error: unknown): OrchestrationResult {
|
|
|
332
841
|
export function dispatchTaskControlOperation(
|
|
333
842
|
context: TaskControlExecutionContext,
|
|
334
843
|
operation: Extract<TaskControlOperation, { family: 'task-lifecycle' }>
|
|
335
|
-
): Promise<TaskLifecycleResult & { humanOverride?: unknown }>;
|
|
844
|
+
): Promise<(TaskLifecycleResult | LifecycleRecoveryResult) & { humanOverride?: unknown }>;
|
|
336
845
|
export function dispatchTaskControlOperation(
|
|
337
846
|
context: TaskControlExecutionContext,
|
|
338
847
|
operation: Extract<TaskControlOperation, { family: 'task-finalization' }>
|
|
@@ -395,7 +904,7 @@ function operationInvalid(message: string): never {
|
|
|
395
904
|
|
|
396
905
|
const LIFECYCLE_FLAGS = new Set([
|
|
397
906
|
'--agent', '--reason', '--unblock-condition', '--note', '--alert-number', '--staging-dir', '--issue-number',
|
|
398
|
-
'--override-ticket', '--override-target', '--override-scope', '--dry-run'
|
|
907
|
+
'--stage', '--round', '--artifact', '--override-ticket', '--override-target', '--override-scope', '--dry-run'
|
|
399
908
|
]);
|
|
400
909
|
|
|
401
910
|
const FINALIZATION_FLAGS = new Set(['--agent']);
|
|
@@ -403,7 +912,7 @@ const FINALIZATION_FLAGS = new Set(['--agent']);
|
|
|
403
912
|
const ORCHESTRATION_FLAGS = new Set([
|
|
404
913
|
'--agent', '--max-steps', '--executor-model', '--executor-reasoning-effort', '--reviewer-model',
|
|
405
914
|
'--reviewer-reasoning-effort', '--client', '--requested-model', '--requested-reasoning-effort',
|
|
406
|
-
'--capability-
|
|
915
|
+
'--capability-ref', '--parent-id', '--before-fingerprint', '--stage', '--round', '--artifact', '--role',
|
|
407
916
|
'--native-agent', '--child-id', '--spawn-mode', '--actual-model', '--actual-reasoning-effort',
|
|
408
917
|
'--model-fallback-reason', '--reasoning-effort-fallback-reason', '--exit-code', '--after-fingerprint',
|
|
409
918
|
'--changed-paths', '--code', '--message', '--recoverable', '--git-worktree-root'
|
|
@@ -475,8 +984,29 @@ export function parseTaskControlOperation(
|
|
|
475
984
|
...(value(values, '--override-scope') ? { overrideScope: value(values, '--override-scope') } : {}),
|
|
476
985
|
...(value(values, '--alert-number') ? { alertNumber: Number(value(values, '--alert-number')) } : {}),
|
|
477
986
|
...(value(values, '--issue-number') ? { issueNumber: Number(value(values, '--issue-number')) } : {}),
|
|
987
|
+
...(value(values, '--stage') ? { stage: value(values, '--stage') } : {}),
|
|
988
|
+
...(value(values, '--round') ? { round: Number(value(values, '--round')) } : {}),
|
|
989
|
+
...(value(values, '--artifact') ? { artifact: value(values, '--artifact') } : {}),
|
|
478
990
|
...(values['--dry-run'] === true ? { dryRun: true } : {})
|
|
479
991
|
};
|
|
992
|
+
if (intent === 'recover-started') {
|
|
993
|
+
const recoveryFlags = new Set(['--agent', '--stage', '--round', '--artifact', '--reason']);
|
|
994
|
+
for (const flag of Object.keys(values)) {
|
|
995
|
+
if (!recoveryFlags.has(flag)) operationInvalid(`option '${flag}' is not supported for recover-started`);
|
|
996
|
+
}
|
|
997
|
+
required(values, ['--stage', '--round', '--artifact', '--reason']);
|
|
998
|
+
const stage = value(values, '--stage');
|
|
999
|
+
if (!['analysis', 'review-analysis', 'plan', 'review-plan', 'code', 'review-code'].includes(stage ?? '')) {
|
|
1000
|
+
operationInvalid('--stage must be a supported lifecycle stage');
|
|
1001
|
+
}
|
|
1002
|
+
const round = Number(value(values, '--round'));
|
|
1003
|
+
if (!Number.isSafeInteger(round) || round < 1) operationInvalid('--round must be a positive integer');
|
|
1004
|
+
if (value(values, '--reason')!.includes('\n') || value(values, '--reason')!.includes('\r')) {
|
|
1005
|
+
operationInvalid('--reason must be a single line');
|
|
1006
|
+
}
|
|
1007
|
+
} else if (values['--stage'] !== undefined || values['--round'] !== undefined || values['--artifact'] !== undefined) {
|
|
1008
|
+
operationInvalid('--stage, --round, and --artifact are only supported for recover-started');
|
|
1009
|
+
}
|
|
480
1010
|
return { family, request: input as unknown as TaskLifecycleControlRequest };
|
|
481
1011
|
}
|
|
482
1012
|
|
|
@@ -519,7 +1049,7 @@ export function parseTaskControlOperation(
|
|
|
519
1049
|
if (parsedIntent === 'prepare') {
|
|
520
1050
|
input.requestedModel = value(values, '--requested-model');
|
|
521
1051
|
input.requestedReasoningEffort = value(values, '--requested-reasoning-effort');
|
|
522
|
-
input.
|
|
1052
|
+
input.capabilityRef = value(values, '--capability-ref');
|
|
523
1053
|
}
|
|
524
1054
|
if (parsedIntent === 'await-activation') {
|
|
525
1055
|
required(values, ['--stage', '--round', '--artifact', '--role']);
|