@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
package/README.md
CHANGED
|
@@ -93,6 +93,16 @@ Every command above works the same way in Claude Code, Codex, Antigravity CLI, a
|
|
|
93
93
|
- **AI-assisted updates**: template changes can be merged while preserving project-specific customization
|
|
94
94
|
- **Auditable process-data archive**: preserve local tasks and GitHub evidence in append-only, verifiable snapshots
|
|
95
95
|
|
|
96
|
+
### Sandbox control boundary
|
|
97
|
+
|
|
98
|
+
Task-bound sandboxes use a host-only control service for lifecycle and workflow writes. On Linux its fixed endpoint is `/run/user/<uid>/agent-infra/host-control.sock`; on macOS it is `/Users/<login>/Library/Application Support/agent-infra/run/host-control.sock`, resolved from the account identity rather than `HOME` or `TMPDIR`. The service directory is private (`0700`) and the socket is private (`0600`). If the endpoint or sandbox identity is unavailable, task-control commands fail closed.
|
|
99
|
+
|
|
100
|
+
The shell installer creates and starts the user-scoped service through systemd user units on Linux or launchd user agents on macOS. After a manual npm install, run `agent-infra-internal host-control install`, then enable the generated user service; `agent-infra-internal host-control status` reports endpoint health. No root or setuid service is required.
|
|
101
|
+
|
|
102
|
+
On macOS, the installer retries service registration during reload. If registration or startup still fails, it exits with an error and a recovery command.
|
|
103
|
+
|
|
104
|
+
The task directory exposed inside a task-bound sandbox is a writable projection. Artifact commands accept only typed workflow operations and canonical artifact basenames. The host opens candidates with `O_NOFOLLOW`, reads and hashes them through one file descriptor, checks observable metadata changes, and atomically lands validated bytes into the authoritative task directory. Changes to the projection's protected topology fail closed and never turn the projection into task state.
|
|
105
|
+
|
|
96
106
|
## Quick Start
|
|
97
107
|
|
|
98
108
|
### 1. Install agent-infra
|
package/README.zh-CN.md
CHANGED
|
@@ -92,6 +92,16 @@ agent-infra 的目标就是把这层共享基础设施标准化。它为所有
|
|
|
92
92
|
- **AI 辅助升级**:模板升级时可合并变更,同时尽量保留项目侧定制
|
|
93
93
|
- **可审计的过程数据归档**:把本地任务与 GitHub 证据保存为只追加、可校验的快照
|
|
94
94
|
|
|
95
|
+
### 沙箱控制边界
|
|
96
|
+
|
|
97
|
+
任务沙箱的生命周期和工作流写操作由宿主专用控制服务执行。Linux 固定端点为 `/run/user/<uid>/agent-infra/host-control.sock`;macOS 固定端点为 `/Users/<login>/Library/Application Support/agent-infra/run/host-control.sock`,路径根据账户身份解析,不读取 `HOME` 或 `TMPDIR`。服务目录权限为 `0700`,socket 权限为 `0600`。端点或沙箱身份不可用时,task-control 命令会失败关闭。
|
|
98
|
+
|
|
99
|
+
Shell 安装脚本会在 Linux 通过 systemd 用户单元、在 macOS 通过 launchd 用户代理创建并启动该服务。手动 npm 安装后,可运行 `agent-infra-internal host-control install` 创建服务配置,再启用对应的用户服务;`agent-infra-internal host-control status` 可检查端点健康状态。整个服务不需要 root 或 setuid。
|
|
100
|
+
|
|
101
|
+
macOS 重载期间,安装脚本会重试服务注册;若最终仍无法注册或启动,则以错误退出并提供恢复命令。
|
|
102
|
+
|
|
103
|
+
任务沙箱内的 task 目录是可写投影。产物命令只接受类型化 workflow 操作和规范的产物 basename。宿主使用 `O_NOFOLLOW` 打开候选文件,通过同一个文件描述符读取并计算摘要,检查可观察的元数据变化,再把已校验字节原子落入权威 task 目录。投影的受保护拓扑发生变化时会失败关闭,投影修改不会变成任务状态。
|
|
104
|
+
|
|
95
105
|
## 快速开始
|
|
96
106
|
|
|
97
107
|
### 1. 安装 agent-infra
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
set -eu
|
|
3
|
+
|
|
4
|
+
script_path=$0
|
|
5
|
+
case "$script_path" in
|
|
6
|
+
[A-Za-z]:*)
|
|
7
|
+
drive=${script_path%"${script_path#?}"}
|
|
8
|
+
drive=$(printf '%s' "$drive" | tr '[:upper:]' '[:lower:]')
|
|
9
|
+
script_path="/${drive}${script_path#?:}"
|
|
10
|
+
script_path=$(printf '%s' "$script_path" | tr '\\' '/')
|
|
11
|
+
;;
|
|
12
|
+
/*) ;;
|
|
13
|
+
*) script_path=$PWD/$script_path ;;
|
|
14
|
+
esac
|
|
15
|
+
while [ -L "$script_path" ]; do
|
|
16
|
+
link_target=$(readlink "$script_path")
|
|
17
|
+
case "$link_target" in
|
|
18
|
+
/*) script_path=$link_target ;;
|
|
19
|
+
*) script_path=$(dirname -- "$script_path")/$link_target ;;
|
|
20
|
+
esac
|
|
21
|
+
done
|
|
22
|
+
script_dir=$(CDPATH= cd -- "$(dirname -- "$script_path")" && pwd -P)
|
|
23
|
+
if [ -f "$script_dir/internal-cli.js" ]; then
|
|
24
|
+
internal_cli="$script_dir/internal-cli.js"
|
|
25
|
+
else
|
|
26
|
+
internal_cli="$script_dir/../dist/bin/internal-cli.js"
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
command=${1-}
|
|
30
|
+
task_control_command=false
|
|
31
|
+
case "$command" in
|
|
32
|
+
task-lifecycle|task-orchestration|task-finalization) task_control_command=true ;;
|
|
33
|
+
esac
|
|
34
|
+
|
|
35
|
+
status_mount_present=false
|
|
36
|
+
if [ -d /run/agent-infra/control-status ]; then status_mount_present=true; fi
|
|
37
|
+
launch_config=${AGENT_INFRA_TASK_ID-}${AGENT_INFRA_CONTROL_TOKEN-}${AGENT_INFRA_CONTROL_GENERATION-}${AGENT_INFRA_CONTROL_DIR-}${AGENT_INFRA_CONTROL_STATUS_DIR-}${AGENT_INFRA_RUNTIME_DIR-}${AGENT_INFRA_EXECUTOR_MANIFEST-}
|
|
38
|
+
if [ "$task_control_command" = true ] && [ "$status_mount_present" = true ] && [ -z "$launch_config" ] && [ -z "${AGENT_INFRA_TEST_HOST_CONTROL_ENDPOINT-}" ]; then
|
|
39
|
+
printf '%s\n' '{"status":"failed","changed":false,"error":{"code":"SANDBOX_CONTROL_IDENTITY_MISSING","message":"sandbox control identity is present but its launch configuration is missing"}}'
|
|
40
|
+
exit 1
|
|
41
|
+
fi
|
|
42
|
+
# The outer launcher owns the Node startup boundary. A task-bound process must
|
|
43
|
+
# not pass preload, import, or loader injection into the control router.
|
|
44
|
+
unset NODE_OPTIONS NODE_PATH
|
|
45
|
+
exec node "$internal_cli" "$@"
|
package/dist/bin/internal-cli.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import { formatTaskViewDiagnostic, guardTaskOperation, TaskViewOperationError } from '../lib/internal/task-operation-registry.js';
|
|
2
|
+
import { formatTaskViewDiagnostic, guardTaskOperation, resolveSandboxControlTransport, TaskViewOperationError } from '../lib/internal/task-operation-registry.js';
|
|
4
3
|
import { INTERNAL_HANDLER_ROUTE_SELECTORS } from '../lib/internal/cli-route-inventory.js';
|
|
4
|
+
import { hostControlRequestForCommand, requestHostControl, HostControlClientError, HOST_CONTROL_TEST_ENVIRONMENT_KEYS } from '../lib/host-control/client.js';
|
|
5
|
+
import { resolveHostControlEndpoint, readHostControlWorkerToken } from '../lib/host-control/path.js';
|
|
5
6
|
const [major = 0, minor = 0] = process.versions.node.split('.').map((part) => parseInt(part, 10));
|
|
6
7
|
if (major < 22 || (major === 22 && minor < 9)) {
|
|
7
8
|
process.stderr.write(`agent-infra-internal requires Node.js >= 22.9.0 (current: ${process.version})\n`);
|
|
@@ -10,6 +11,16 @@ if (major < 22 || (major === 22 && minor < 9)) {
|
|
|
10
11
|
const command = process.argv[2] || '';
|
|
11
12
|
const internalRouteRegistered = Object.hasOwn(INTERNAL_HANDLER_ROUTE_SELECTORS, command);
|
|
12
13
|
const taskControlCommand = command === 'task-lifecycle' || command === 'task-orchestration' || command === 'task-finalization';
|
|
14
|
+
const taskWorkflowCommand = command === 'task-artifact'
|
|
15
|
+
|| command === 'task-review'
|
|
16
|
+
|| command === 'task-event'
|
|
17
|
+
|| command === 'task-ledger'
|
|
18
|
+
|| command === 'task-invalidation'
|
|
19
|
+
|| command === 'task-warning';
|
|
20
|
+
const manualValidationWorkflowCommand = command === 'manual-validation';
|
|
21
|
+
const localTaskControlHelp = taskControlCommand
|
|
22
|
+
&& (process.argv[3] === '--help' || process.argv[3] === '-h')
|
|
23
|
+
&& !process.env.AGENT_INFRA_TEST_HOST_CONTROL_ENDPOINT;
|
|
13
24
|
let taskViewGuardFailed = false;
|
|
14
25
|
try {
|
|
15
26
|
const guard = guardTaskOperation('internal', command, process.argv.slice(3));
|
|
@@ -27,44 +38,86 @@ catch (error) {
|
|
|
27
38
|
throw error;
|
|
28
39
|
}
|
|
29
40
|
}
|
|
30
|
-
function taskControlTransportFailure(message) {
|
|
41
|
+
function taskControlTransportFailure(message, code = 'TASK_CONTROL_TRANSPORT_INVALID', changed = false) {
|
|
31
42
|
process.stdout.write(`${JSON.stringify({
|
|
32
|
-
status: 'failed', changed
|
|
33
|
-
error: { code
|
|
43
|
+
status: 'failed', changed,
|
|
44
|
+
error: { code, message }
|
|
34
45
|
})}\n`);
|
|
35
46
|
process.exit(1);
|
|
36
47
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
48
|
+
async function runHostControlCommand(commandName, args) {
|
|
49
|
+
let response;
|
|
50
|
+
try {
|
|
51
|
+
const testEndpoint = process.env.AGENT_INFRA_TEST_HOST_CONTROL_ENDPOINT;
|
|
52
|
+
const forwardedEnvironment = testEndpoint
|
|
53
|
+
? Object.fromEntries(HOST_CONTROL_TEST_ENVIRONMENT_KEYS.flatMap((key) => process.env[key] === undefined ? [] : [[key, process.env[key]]]))
|
|
54
|
+
: {};
|
|
55
|
+
response = await requestHostControl({
|
|
56
|
+
request: hostControlRequestForCommand(commandName, args, process.cwd(), forwardedEnvironment),
|
|
57
|
+
endpoint: process.env.AGENT_INFRA_TEST_HOST_CONTROL_ENDPOINT ?? resolveHostControlEndpoint()
|
|
58
|
+
});
|
|
41
59
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
60
|
+
catch (error) {
|
|
61
|
+
if (error instanceof HostControlClientError) {
|
|
62
|
+
taskControlTransportFailure(error.message, error.changed === false
|
|
63
|
+
? 'SANDBOX_CONTROL_HOST_AUTHORITY_UNAVAILABLE' : error.code, error.changed);
|
|
64
|
+
}
|
|
65
|
+
throw error;
|
|
45
66
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
67
|
+
process.stdout.write(response.stdout);
|
|
68
|
+
process.stderr.write(response.stderr);
|
|
69
|
+
process.exitCode = response.exitCode;
|
|
70
|
+
}
|
|
71
|
+
const hostWorkerRequested = process.env.AGENT_INFRA_HOST_CONTROL_WORKER === '1';
|
|
72
|
+
const hostWorker = hostWorkerRequested && (() => {
|
|
73
|
+
try {
|
|
74
|
+
const endpoint = process.env.AGENT_INFRA_TEST_HOST_CONTROL_ENDPOINT ?? resolveHostControlEndpoint();
|
|
75
|
+
return process.env.AGENT_INFRA_HOST_CONTROL_WORKER_TOKEN === readHostControlWorkerToken(endpoint);
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
})();
|
|
81
|
+
if (hostWorkerRequested && !hostWorker && (taskControlCommand || taskWorkflowCommand || manualValidationWorkflowCommand)) {
|
|
82
|
+
taskControlTransportFailure('host-control worker authorization is invalid', 'SANDBOX_CONTROL_HOST_AUTHORITY_UNAVAILABLE');
|
|
83
|
+
}
|
|
84
|
+
let controlRouted = false;
|
|
85
|
+
if (!taskViewGuardFailed && (taskControlCommand || taskWorkflowCommand || manualValidationWorkflowCommand) && !hostWorker && !localTaskControlHelp) {
|
|
86
|
+
const transport = resolveSandboxControlTransport(process.env);
|
|
87
|
+
switch (transport.kind) {
|
|
88
|
+
case 'fail-closed': {
|
|
89
|
+
const reason = transport.reasonCode ?? 'TASK_CONTROL_TRANSPORT_INVALID';
|
|
90
|
+
taskControlTransportFailure(reason, reason.startsWith('SANDBOX_CONTROL_IDENTITY_') ? reason : undefined);
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
case 'direct-host':
|
|
94
|
+
await runHostControlCommand(command, process.argv.slice(3));
|
|
95
|
+
break;
|
|
96
|
+
case 'broker-client': {
|
|
97
|
+
const { sandboxControl } = await import('../lib/internal/sandbox-control.js');
|
|
98
|
+
await sandboxControl(['client', ...(taskControlCommand ? [command] : ['task-workflow', command]), ...process.argv.slice(3)]);
|
|
99
|
+
break;
|
|
63
100
|
}
|
|
64
101
|
}
|
|
102
|
+
controlRouted = true;
|
|
65
103
|
}
|
|
66
|
-
|
|
104
|
+
if (!controlRouted && !taskViewGuardFailed && internalRouteRegistered)
|
|
67
105
|
switch (command) {
|
|
106
|
+
case 'task-orchestration': {
|
|
107
|
+
const { taskOrchestration } = await import('../lib/internal/task-orchestration.js');
|
|
108
|
+
await taskOrchestration(process.argv.slice(3));
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
case 'task-lifecycle': {
|
|
112
|
+
const { taskLifecycle } = await import('../lib/internal/task-lifecycle.js');
|
|
113
|
+
await taskLifecycle(process.argv.slice(3));
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
case 'task-finalization': {
|
|
117
|
+
const { taskFinalization } = await import('../lib/internal/task-finalization.js');
|
|
118
|
+
await taskFinalization(process.argv.slice(3));
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
68
121
|
case 'task-create': {
|
|
69
122
|
const { taskCreate } = await import('../lib/internal/task-create.js');
|
|
70
123
|
await taskCreate(process.argv.slice(3));
|
|
@@ -75,6 +128,11 @@ else if (!taskViewGuardFailed && internalRouteRegistered)
|
|
|
75
128
|
await sandboxControl(process.argv.slice(3));
|
|
76
129
|
break;
|
|
77
130
|
}
|
|
131
|
+
case 'host-control': {
|
|
132
|
+
const { hostControl } = await import('../lib/internal/host-control.js');
|
|
133
|
+
await hostControl(process.argv.slice(3));
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
78
136
|
case 'agent-client': {
|
|
79
137
|
const { agentClient } = await import('../lib/internal/agent-client.js');
|
|
80
138
|
agentClient(process.argv.slice(3));
|
|
@@ -240,6 +298,11 @@ else if (!taskViewGuardFailed && internalRouteRegistered)
|
|
|
240
298
|
taskValidate(process.argv.slice(3));
|
|
241
299
|
break;
|
|
242
300
|
}
|
|
301
|
+
case 'manual-validation': {
|
|
302
|
+
const { manualValidation } = await import('../lib/internal/manual-validation.js');
|
|
303
|
+
await manualValidation(process.argv.slice(3));
|
|
304
|
+
break;
|
|
305
|
+
}
|
|
243
306
|
default:
|
|
244
307
|
process.stdout.write(`${JSON.stringify({
|
|
245
308
|
status: 'failed',
|
|
@@ -89,6 +89,21 @@ function provenanceDetail(record, expected) {
|
|
|
89
89
|
function exactKeys(value, keys) {
|
|
90
90
|
return Object.keys(value).length === keys.length && keys.every((key) => Object.hasOwn(value, key));
|
|
91
91
|
}
|
|
92
|
+
function isRecoveryPhase(value) {
|
|
93
|
+
return value === 'orchestration.prepare'
|
|
94
|
+
|| value === 'artifact.finalize-local'
|
|
95
|
+
|| value === 'task-event.completed';
|
|
96
|
+
}
|
|
97
|
+
function isRecoveryPhaseRecord(value) {
|
|
98
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
99
|
+
return false;
|
|
100
|
+
const phase = value;
|
|
101
|
+
return exactKeys(phase, ['phase', 'requestId', 'state'])
|
|
102
|
+
&& isRecoveryPhase(phase.phase)
|
|
103
|
+
&& typeof phase.requestId === 'string'
|
|
104
|
+
&& /^[a-f0-9-]{16,64}$/u.test(phase.requestId)
|
|
105
|
+
&& (phase.state === 'issued' || phase.state === 'consumed');
|
|
106
|
+
}
|
|
92
107
|
function isCodexControllerBinding(value) {
|
|
93
108
|
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
94
109
|
return false;
|
|
@@ -176,18 +191,26 @@ function createCodexCapabilityStore(options = {}) {
|
|
|
176
191
|
const root = options.root
|
|
177
192
|
?? resolveAgentRuntimeStoreRoot({ store: 'capabilities' });
|
|
178
193
|
const now = options.now ?? Date.now;
|
|
179
|
-
const
|
|
194
|
+
const createReference = options.reference ?? (() => crypto.randomBytes(32).toString('base64url'));
|
|
180
195
|
const ttlMs = options.ttlMs ?? DEFAULT_TTL_MS;
|
|
181
196
|
const tombstoneMs = options.tombstoneMs ?? DEFAULT_TOMBSTONE_MS;
|
|
182
197
|
const maxRecords = options.maxRecords ?? DEFAULT_MAX_RECORDS;
|
|
183
198
|
fs.mkdirSync(root, { recursive: true, mode: 0o700 });
|
|
184
199
|
fs.chmodSync(root, 0o700);
|
|
185
|
-
function fileFor(
|
|
186
|
-
return path.join(root, `${digest(
|
|
200
|
+
function fileFor(capabilityRef) {
|
|
201
|
+
return path.join(root, `${digest(capabilityRef)}.json`);
|
|
187
202
|
}
|
|
188
203
|
function read(file) {
|
|
189
204
|
const value = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
190
|
-
|
|
205
|
+
const keys = Object.keys(value).sort().join(',');
|
|
206
|
+
if (keys !== 'armedAt,attestedAt,buildIdentity,capabilityRefDigest,consumedAt,controller,expiresAt,hookDefinitionHash,recoveryOperationId,recoveryPhases,recoveryState,reservedAt,revision,schemaVersion,sessionId,status,taskId,toolUseId,turnId'
|
|
207
|
+
|| value.schemaVersion !== 2 || !Number.isSafeInteger(value.revision) || value.revision < 1
|
|
208
|
+
|| typeof value.capabilityRefDigest !== 'string' || !/^[a-f0-9]{64}$/u.test(value.capabilityRefDigest)
|
|
209
|
+
|| !['unreserved', 'reserved', 'consumed'].includes(value.recoveryState)
|
|
210
|
+
|| (value.recoveryOperationId !== null && typeof value.recoveryOperationId !== 'string')
|
|
211
|
+
|| (value.reservedAt !== null && !Number.isSafeInteger(value.reservedAt))
|
|
212
|
+
|| !Array.isArray(value.recoveryPhases)
|
|
213
|
+
|| !value.recoveryPhases.every((phase) => isRecoveryPhaseRecord(phase))) {
|
|
191
214
|
throw capabilityError('CODEX_CAPABILITY_STATE_INVALID', 'capability record schema is invalid');
|
|
192
215
|
}
|
|
193
216
|
return value;
|
|
@@ -294,17 +317,21 @@ function createCodexCapabilityStore(options = {}) {
|
|
|
294
317
|
}
|
|
295
318
|
function arm(input) {
|
|
296
319
|
sweep();
|
|
297
|
-
const
|
|
298
|
-
const file = fileFor(
|
|
320
|
+
const capabilityRef = createReference();
|
|
321
|
+
const file = fileFor(capabilityRef);
|
|
299
322
|
if (fs.existsSync(file))
|
|
300
323
|
throw capabilityError('CODEX_CAPABILITY_TOKEN_COLLISION', 'capability token already exists');
|
|
301
324
|
const timestamp = now();
|
|
302
325
|
const record = Object.freeze({
|
|
303
|
-
schemaVersion:
|
|
326
|
+
schemaVersion: 2,
|
|
304
327
|
revision: 1,
|
|
305
|
-
|
|
328
|
+
capabilityRefDigest: digest(capabilityRef),
|
|
306
329
|
taskId: input.taskId,
|
|
307
330
|
status: 'armed',
|
|
331
|
+
recoveryOperationId: null,
|
|
332
|
+
recoveryState: 'unreserved',
|
|
333
|
+
reservedAt: null,
|
|
334
|
+
recoveryPhases: Object.freeze([]),
|
|
308
335
|
buildIdentity: input.buildIdentity,
|
|
309
336
|
controller: input.controller ?? null,
|
|
310
337
|
sessionId: null,
|
|
@@ -317,26 +344,27 @@ function createCodexCapabilityStore(options = {}) {
|
|
|
317
344
|
consumedAt: null
|
|
318
345
|
});
|
|
319
346
|
write(file, record, 0);
|
|
320
|
-
|
|
347
|
+
const result = {
|
|
321
348
|
...record,
|
|
322
|
-
|
|
349
|
+
capabilityRef,
|
|
323
350
|
path: file,
|
|
324
|
-
marker: `agent-infra-codex-capability:${
|
|
325
|
-
}
|
|
351
|
+
marker: `agent-infra-codex-capability:${capabilityRef}`
|
|
352
|
+
};
|
|
353
|
+
return Object.freeze(result);
|
|
326
354
|
}
|
|
327
|
-
function loadActive(
|
|
328
|
-
const file = fileFor(
|
|
355
|
+
function loadActive(capabilityRef, options = {}) {
|
|
356
|
+
const file = fileFor(capabilityRef);
|
|
329
357
|
if (!fs.existsSync(file)) {
|
|
330
358
|
throw capabilityError('CODEX_CAPABILITY_MISSING', 'capability token was not found');
|
|
331
359
|
}
|
|
332
360
|
const record = expire(read(file), file, now());
|
|
333
|
-
if (record.status === 'expired') {
|
|
361
|
+
if (record.status === 'expired' && !options.allowExpiredReserved) {
|
|
334
362
|
throw capabilityError('CODEX_CAPABILITY_EXPIRED', 'capability token expired');
|
|
335
363
|
}
|
|
336
364
|
return { file, record };
|
|
337
365
|
}
|
|
338
|
-
function
|
|
339
|
-
const { file, record } = loadActive(input.
|
|
366
|
+
function attestByReference(input) {
|
|
367
|
+
const { file, record } = loadActive(input.capabilityRef);
|
|
340
368
|
if (record.status !== 'armed') {
|
|
341
369
|
throw capabilityError(record.status === 'consumed' ? 'CODEX_CAPABILITY_REPLAY' : 'CODEX_CAPABILITY_STATE_INVALID', `capability is ${record.status}, expected armed`);
|
|
342
370
|
}
|
|
@@ -362,11 +390,14 @@ function createCodexCapabilityStore(options = {}) {
|
|
|
362
390
|
write(file, next, record.revision);
|
|
363
391
|
return next;
|
|
364
392
|
}
|
|
365
|
-
function validateRecord(record, expected) {
|
|
393
|
+
function validateRecord(record, expected, options = {}) {
|
|
366
394
|
if (record.status === 'consumed') {
|
|
367
395
|
throw capabilityError('CODEX_CAPABILITY_REPLAY', 'capability token was already consumed');
|
|
368
396
|
}
|
|
369
|
-
|
|
397
|
+
const expiredReserved = options.allowExpiredReserved
|
|
398
|
+
&& record.status === 'expired'
|
|
399
|
+
&& record.recoveryState === 'reserved';
|
|
400
|
+
if (record.status !== 'attested' && !expiredReserved) {
|
|
370
401
|
throw capabilityError('CODEX_CAPABILITY_NOT_ATTESTED', 'capability token has no current-session hook attestation');
|
|
371
402
|
}
|
|
372
403
|
const identity = record.buildIdentity && typeof record.buildIdentity === 'object'
|
|
@@ -380,28 +411,135 @@ function createCodexCapabilityStore(options = {}) {
|
|
|
380
411
|
}
|
|
381
412
|
return record;
|
|
382
413
|
}
|
|
383
|
-
function
|
|
414
|
+
function validateReference(capabilityRef, expected) {
|
|
384
415
|
sweep();
|
|
385
|
-
const { record } = loadActive(
|
|
416
|
+
const { record } = loadActive(capabilityRef);
|
|
386
417
|
return validateRecord(record, expected);
|
|
387
418
|
}
|
|
388
|
-
function
|
|
419
|
+
function consumeReference(capabilityRef, operationId, expected, options = {}) {
|
|
389
420
|
sweep();
|
|
390
|
-
|
|
391
|
-
|
|
421
|
+
if (!operationId || /[\r\n]/u.test(operationId)) {
|
|
422
|
+
throw capabilityError('CODEX_CAPABILITY_RECOVERY_INVALID', 'recovery operation id is invalid');
|
|
423
|
+
}
|
|
424
|
+
const { file, record } = loadActive(capabilityRef, options);
|
|
425
|
+
if (record.status === 'consumed' && record.recoveryState === 'consumed' && record.recoveryOperationId === operationId) {
|
|
426
|
+
return record;
|
|
427
|
+
}
|
|
428
|
+
validateRecord(record, expected, options);
|
|
429
|
+
if (record.recoveryState === 'consumed') {
|
|
430
|
+
if (record.recoveryOperationId === operationId)
|
|
431
|
+
return record;
|
|
432
|
+
throw capabilityError('CODEX_CAPABILITY_REPLAY', 'capability recovery operation was already consumed');
|
|
433
|
+
}
|
|
434
|
+
if (record.recoveryState !== 'reserved' || record.recoveryOperationId !== operationId) {
|
|
435
|
+
throw capabilityError('CODEX_CAPABILITY_RECOVERY_INVALID', 'capability must be reserved by this operation before consumption');
|
|
436
|
+
}
|
|
392
437
|
const next = Object.freeze({
|
|
393
438
|
...record,
|
|
394
439
|
revision: record.revision + 1,
|
|
395
440
|
status: 'consumed',
|
|
441
|
+
recoveryState: 'consumed',
|
|
396
442
|
consumedAt: now()
|
|
397
443
|
});
|
|
398
444
|
write(file, next, record.revision);
|
|
399
445
|
return next;
|
|
400
446
|
}
|
|
401
|
-
function
|
|
402
|
-
|
|
447
|
+
function reserveRecoveryPhase(capabilityRef, operationId, phase, requestId, expected) {
|
|
448
|
+
if (!operationId || /[\r\n]/u.test(operationId) || !isRecoveryPhase(phase)
|
|
449
|
+
|| !/^[a-f0-9-]{16,64}$/u.test(requestId)) {
|
|
450
|
+
throw capabilityError('CODEX_CAPABILITY_RECOVERY_INVALID', 'recovery phase identity is invalid');
|
|
451
|
+
}
|
|
452
|
+
sweep();
|
|
453
|
+
const { file, record } = loadActive(capabilityRef);
|
|
454
|
+
validateRecord(record, expected);
|
|
455
|
+
if (record.recoveryState !== 'reserved' || record.recoveryOperationId !== operationId) {
|
|
456
|
+
throw capabilityError('CODEX_CAPABILITY_RECOVERY_INVALID', 'capability must be reserved by this operation before phase reservation');
|
|
457
|
+
}
|
|
458
|
+
if (record.recoveryPhases.some((entry) => entry.phase === phase)) {
|
|
459
|
+
throw capabilityError('CODEX_CAPABILITY_PHASE_REPLAY', 'recovery phase was already reserved');
|
|
460
|
+
}
|
|
461
|
+
const next = Object.freeze({
|
|
462
|
+
...record,
|
|
463
|
+
revision: record.revision + 1,
|
|
464
|
+
recoveryPhases: Object.freeze([
|
|
465
|
+
...record.recoveryPhases,
|
|
466
|
+
Object.freeze({ phase, requestId, state: 'issued' })
|
|
467
|
+
])
|
|
468
|
+
});
|
|
469
|
+
write(file, next, record.revision);
|
|
470
|
+
return next;
|
|
471
|
+
}
|
|
472
|
+
function consumeRecoveryPhase(capabilityRef, operationId, phase, requestId, expected, options = {}) {
|
|
473
|
+
if (!operationId || /[\r\n]/u.test(operationId) || !isRecoveryPhase(phase)
|
|
474
|
+
|| !/^[a-f0-9-]{16,64}$/u.test(requestId)) {
|
|
475
|
+
throw capabilityError('CODEX_CAPABILITY_RECOVERY_INVALID', 'recovery phase identity is invalid');
|
|
476
|
+
}
|
|
477
|
+
sweep();
|
|
478
|
+
const { file, record } = loadActive(capabilityRef, options);
|
|
479
|
+
validateRecord(record, expected, options);
|
|
480
|
+
if (record.recoveryState !== 'reserved' && record.recoveryState !== 'consumed') {
|
|
481
|
+
throw capabilityError('CODEX_CAPABILITY_RECOVERY_INVALID', 'capability has no reserved recovery operation');
|
|
482
|
+
}
|
|
483
|
+
if (record.recoveryOperationId !== operationId) {
|
|
484
|
+
throw capabilityError('CODEX_CAPABILITY_REPLAY', 'capability recovery operation does not match');
|
|
485
|
+
}
|
|
486
|
+
const current = record.recoveryPhases.find((entry) => entry.phase === phase);
|
|
487
|
+
if (!current || current.requestId !== requestId) {
|
|
488
|
+
throw capabilityError('CODEX_CAPABILITY_PHASE_REPLAY', 'recovery phase identity does not match');
|
|
489
|
+
}
|
|
490
|
+
if (current.state === 'consumed')
|
|
491
|
+
return record;
|
|
492
|
+
const next = Object.freeze({
|
|
493
|
+
...record,
|
|
494
|
+
revision: record.revision + 1,
|
|
495
|
+
recoveryPhases: Object.freeze(record.recoveryPhases.map((entry) => entry.phase === phase ? Object.freeze({ ...entry, state: 'consumed' }) : entry))
|
|
496
|
+
});
|
|
497
|
+
write(file, next, record.revision);
|
|
498
|
+
return next;
|
|
499
|
+
}
|
|
500
|
+
function reserveReference(capabilityRef, operationId, expected) {
|
|
501
|
+
if (!operationId || /[\r\n]/u.test(operationId)) {
|
|
502
|
+
throw capabilityError('CODEX_CAPABILITY_RECOVERY_INVALID', 'recovery operation id is invalid');
|
|
503
|
+
}
|
|
504
|
+
sweep();
|
|
505
|
+
const { file, record } = loadActive(capabilityRef);
|
|
506
|
+
validateRecord(record, expected);
|
|
507
|
+
if (record.recoveryState === 'reserved') {
|
|
508
|
+
if (record.recoveryOperationId === operationId)
|
|
509
|
+
return record;
|
|
510
|
+
throw capabilityError('CODEX_CAPABILITY_BUSY', 'capability is reserved by another recovery operation');
|
|
511
|
+
}
|
|
512
|
+
if (record.recoveryState === 'consumed') {
|
|
513
|
+
if (record.recoveryOperationId === operationId)
|
|
514
|
+
return record;
|
|
515
|
+
throw capabilityError('CODEX_CAPABILITY_REPLAY', 'capability recovery operation was already consumed');
|
|
516
|
+
}
|
|
517
|
+
const next = Object.freeze({
|
|
518
|
+
...record,
|
|
519
|
+
revision: record.revision + 1,
|
|
520
|
+
recoveryOperationId: operationId,
|
|
521
|
+
recoveryState: 'reserved',
|
|
522
|
+
reservedAt: now()
|
|
523
|
+
});
|
|
524
|
+
write(file, next, record.revision);
|
|
525
|
+
return next;
|
|
526
|
+
}
|
|
527
|
+
function inspectReference(capabilityRef) {
|
|
528
|
+
return loadActive(capabilityRef).record;
|
|
529
|
+
}
|
|
530
|
+
function findByRecoveryOperation(operationId) {
|
|
531
|
+
if (!operationId || /[\r\n]/u.test(operationId)) {
|
|
532
|
+
throw capabilityError('CODEX_CAPABILITY_RECOVERY_INVALID', 'recovery operation id is invalid');
|
|
533
|
+
}
|
|
534
|
+
return Object.freeze(files()
|
|
535
|
+
.map((file) => read(file))
|
|
536
|
+
.filter((record) => record.recoveryOperationId === operationId));
|
|
403
537
|
}
|
|
404
|
-
return Object.freeze({
|
|
538
|
+
return Object.freeze({
|
|
539
|
+
arm, attestByReference, reserveReference, reserveRecoveryPhase, consumeRecoveryPhase,
|
|
540
|
+
consumeReference, inspectReference,
|
|
541
|
+
findByRecoveryOperation, validateReference, sweep
|
|
542
|
+
});
|
|
405
543
|
}
|
|
406
544
|
export { createCodexCapabilityStore };
|
|
407
545
|
export { isCodexCapabilityProvenanceDetail };
|
|
@@ -22,13 +22,14 @@ function copyRegular(source, destination, mode) {
|
|
|
22
22
|
function controlFromEnvironment() {
|
|
23
23
|
const token = process.env.AGENT_INFRA_CONTROL_TOKEN;
|
|
24
24
|
const generation = process.env.AGENT_INFRA_CONTROL_GENERATION;
|
|
25
|
+
const rootId = process.env.AGENT_INFRA_CONTROL_ROOT_ID;
|
|
25
26
|
const channelDir = process.env.AGENT_INFRA_CONTROL_DIR;
|
|
26
27
|
const statusDir = process.env.AGENT_INFRA_CONTROL_STATUS_DIR;
|
|
27
28
|
const runtimeDir = process.env.AGENT_INFRA_RUNTIME_DIR;
|
|
28
|
-
if (!token || !generation || !channelDir || !statusDir || !runtimeDir) {
|
|
29
|
+
if (!token || !generation || !rootId || !channelDir || !statusDir || !runtimeDir) {
|
|
29
30
|
throw new Error('CODEX_SANDBOX_CONTROLLER_CONTROL_MISSING');
|
|
30
31
|
}
|
|
31
|
-
return { token, generation, channelDir, statusDir, runtimeDir };
|
|
32
|
+
return { token, generation, rootId, channelDir, statusDir, runtimeDir };
|
|
32
33
|
}
|
|
33
34
|
function verifyRuntime(runtimeDir) {
|
|
34
35
|
let stat;
|
|
@@ -188,6 +189,9 @@ function isolatedEnvironment(home, shimDir, contextPath, taskId, control, provid
|
|
|
188
189
|
AGENT_INFRA_TASK_ID: taskId,
|
|
189
190
|
AGENT_INFRA_CONTROL_TOKEN: control.token,
|
|
190
191
|
AGENT_INFRA_CONTROL_GENERATION: control.generation,
|
|
192
|
+
...(control.rootId || sourceEnvironment.AGENT_INFRA_CONTROL_ROOT_ID
|
|
193
|
+
? { AGENT_INFRA_CONTROL_ROOT_ID: control.rootId ?? sourceEnvironment.AGENT_INFRA_CONTROL_ROOT_ID }
|
|
194
|
+
: {}),
|
|
191
195
|
AGENT_INFRA_CONTROL_DIR: control.channelDir,
|
|
192
196
|
AGENT_INFRA_CONTROL_STATUS_DIR: control.statusDir,
|
|
193
197
|
AGENT_INFRA_RUNTIME_DIR: control.runtimeDir,
|
|
@@ -274,9 +278,10 @@ function prepareCodexSandboxController(input, options = {}) {
|
|
|
274
278
|
const shimDir = path.join(home, 'bin');
|
|
275
279
|
fs.mkdirSync(shimDir, { mode: 0o700 });
|
|
276
280
|
const internalCli = path.resolve(process.argv[1] ?? path.join(repoRoot, 'bin', 'internal-cli.ts'));
|
|
281
|
+
const launcherPrefix = ['#!/bin/sh', 'set -eu', 'unset NODE_OPTIONS NODE_PATH'].join('\n') + '\n';
|
|
277
282
|
const source = internalCli.endsWith('.ts')
|
|
278
|
-
?
|
|
279
|
-
:
|
|
283
|
+
? `${launcherPrefix}exec ${shellQuote(process.execPath)} --experimental-strip-types ${shellQuote(internalCli)} "$@"\n`
|
|
284
|
+
: `${launcherPrefix}exec ${shellQuote(process.execPath)} ${shellQuote(internalCli)} "$@"\n`;
|
|
280
285
|
fs.writeFileSync(path.join(shimDir, 'agent-infra-internal'), source, { mode: 0o700 });
|
|
281
286
|
const policy = [
|
|
282
287
|
input.executorModel ? `--executor-model ${input.executorModel}` : '',
|
|
@@ -6,9 +6,19 @@ import { resolveAgentRuntimeStoreRoot } from '../../../runtime/agent-runtime.js'
|
|
|
6
6
|
const LOCK_RETRY_MS = 10;
|
|
7
7
|
const LOCK_TIMEOUT_MS = 1_000;
|
|
8
8
|
const LOCK_STALE_MS = 30_000;
|
|
9
|
+
const RECOVERY_CONSUMER_RE = /^lifecycle-recovery:([^:\r\n]+):([^:\r\n]+)$/u;
|
|
9
10
|
function digest(value) {
|
|
10
11
|
return crypto.createHash('sha256').update(value).digest('hex');
|
|
11
12
|
}
|
|
13
|
+
function isRecoveryConsumer(value) {
|
|
14
|
+
return RECOVERY_CONSUMER_RE.test(value);
|
|
15
|
+
}
|
|
16
|
+
function recoveryConsumer(taskId, receiptId) {
|
|
17
|
+
if (!/^[^:\r\n]+$/u.test(taskId) || !/^[^:\r\n]+$/u.test(receiptId)) {
|
|
18
|
+
throw new Error('Codex lifecycle recovery claim identity is invalid');
|
|
19
|
+
}
|
|
20
|
+
return `lifecycle-recovery:${taskId}:${receiptId}`;
|
|
21
|
+
}
|
|
12
22
|
function readRecord(file) {
|
|
13
23
|
const value = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
14
24
|
if (value.schemaVersion !== 1
|
|
@@ -202,11 +212,13 @@ function createCodexLifecycleStore(options) {
|
|
|
202
212
|
}
|
|
203
213
|
function read(childThreadId) {
|
|
204
214
|
const matches = findByChild(childThreadId);
|
|
205
|
-
if (matches.length
|
|
215
|
+
if (matches.length === 0)
|
|
206
216
|
throw new Error(`Codex lifecycle child '${childThreadId}' was not found uniquely`);
|
|
217
|
+
if (matches.length > 1)
|
|
218
|
+
throw new Error(`Codex lifecycle child '${childThreadId}' is ambiguous`);
|
|
207
219
|
return readRecord(matches[0]);
|
|
208
220
|
}
|
|
209
|
-
function
|
|
221
|
+
function consumeInternal(childThreadId, consumer, expectedHookDefinitionHash) {
|
|
210
222
|
if (!consumer.trim())
|
|
211
223
|
throw new Error('Codex lifecycle consumer is required');
|
|
212
224
|
return withWriteLock(() => {
|
|
@@ -240,6 +252,32 @@ function createCodexLifecycleStore(options) {
|
|
|
240
252
|
return next;
|
|
241
253
|
});
|
|
242
254
|
}
|
|
255
|
+
function consume(childThreadId, consumer, expectedHookDefinitionHash) {
|
|
256
|
+
if (!consumer.trim())
|
|
257
|
+
throw new Error('Codex lifecycle consumer is required');
|
|
258
|
+
if (consumer.startsWith('lifecycle-recovery:')) {
|
|
259
|
+
throw new Error('Codex lifecycle recovery claims require claimRecovery');
|
|
260
|
+
}
|
|
261
|
+
return consumeInternal(childThreadId, consumer, expectedHookDefinitionHash);
|
|
262
|
+
}
|
|
263
|
+
function claimRecovery(childThreadId, taskId, receiptId, expectedHookDefinitionHash) {
|
|
264
|
+
return consumeInternal(childThreadId, recoveryConsumer(taskId, receiptId), expectedHookDefinitionHash);
|
|
265
|
+
}
|
|
266
|
+
function releaseRecovery(childThreadId, consumer) {
|
|
267
|
+
if (!/^lifecycle-recovery:[^:\r\n]+:[^:\r\n]+$/.test(consumer))
|
|
268
|
+
return false;
|
|
269
|
+
return withWriteLock(() => {
|
|
270
|
+
const matches = findByChild(childThreadId);
|
|
271
|
+
if (matches.length !== 1)
|
|
272
|
+
return false;
|
|
273
|
+
const file = matches[0];
|
|
274
|
+
const current = readRecord(file);
|
|
275
|
+
if (current.state.status !== 'stop-ready' || current.consumer !== consumer || !current.consumedAt)
|
|
276
|
+
return false;
|
|
277
|
+
fs.unlinkSync(file);
|
|
278
|
+
return true;
|
|
279
|
+
});
|
|
280
|
+
}
|
|
243
281
|
function expireBefore(cutoff) {
|
|
244
282
|
return withWriteLock(() => {
|
|
245
283
|
let changed = 0;
|
|
@@ -247,6 +285,8 @@ function createCodexLifecycleStore(options) {
|
|
|
247
285
|
const current = readRecord(file);
|
|
248
286
|
if (current.updatedAt >= cutoff)
|
|
249
287
|
continue;
|
|
288
|
+
if (current.consumer && isRecoveryConsumer(current.consumer))
|
|
289
|
+
continue;
|
|
250
290
|
if (current.consumer || ['invalid', 'expired', 'stop-ready'].includes(current.state.status)) {
|
|
251
291
|
fs.unlinkSync(file);
|
|
252
292
|
changed += 1;
|
|
@@ -266,7 +306,7 @@ function createCodexLifecycleStore(options) {
|
|
|
266
306
|
return changed;
|
|
267
307
|
});
|
|
268
308
|
}
|
|
269
|
-
return Object.freeze({ root, apply, applyToSpawn, consume, expireBefore, findByParent, read });
|
|
309
|
+
return Object.freeze({ root, apply, applyToSpawn, consume, claimRecovery, releaseRecovery, expireBefore, findByParent, read });
|
|
270
310
|
}
|
|
271
311
|
export { createCodexLifecycleStore, hasActiveCodexLifecycleEvidence };
|
|
272
312
|
//# sourceMappingURL=store.js.map
|