@fitlab-ai/agent-infra 0.9.11 → 0.9.13
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 +13 -13
- package/README.zh-CN.md +13 -13
- package/dist/bin/internal-cli.js +29 -19
- package/dist/lib/agent-clients/adapters/codex-lifecycle/build-identity.js +71 -10
- package/dist/lib/agent-clients/next-steps.js +6 -1
- package/dist/lib/decide.js +3 -3
- package/dist/lib/defaults.json +4 -0
- package/dist/lib/git/workflow.js +43 -1
- package/dist/lib/init.js +1 -0
- package/dist/lib/internal/git-workflow.js +8 -1
- package/dist/lib/internal/platform-checks.js +3 -3
- package/dist/lib/internal/platform-comment.js +9 -9
- package/dist/lib/internal/platform-context.js +2 -2
- package/dist/lib/internal/platform-issue.js +8 -9
- package/dist/lib/internal/platform-pr-review.js +10 -10
- package/dist/lib/internal/platform-pr.js +68 -23
- package/dist/lib/internal/platform-release-notes.js +3 -3
- package/dist/lib/internal/pr-review-grade.js +11 -9
- package/dist/lib/internal/release-workflow.js +4 -4
- package/dist/lib/internal/sandbox-control.js +30 -4
- package/dist/lib/internal/task-activity.js +3 -3
- package/dist/lib/internal/task-artifact.js +76 -24
- package/dist/lib/internal/task-create.js +2 -2
- package/dist/lib/internal/task-delivery.js +59 -0
- package/dist/lib/internal/task-event.js +6 -3
- package/dist/lib/internal/task-finalization.js +2 -2
- package/dist/lib/internal/task-invalidation.js +60 -0
- package/dist/lib/internal/task-ledger.js +9 -6
- package/dist/lib/internal/task-lifecycle.js +2 -2
- package/dist/lib/internal/task-override.js +4 -4
- package/dist/lib/internal/task-review.js +3 -3
- package/dist/lib/internal/task-verify.js +6 -6
- package/dist/lib/internal/task-warning.js +3 -3
- package/dist/lib/platform/adapters.js +1 -3
- package/dist/lib/platform/change-request-git-evidence.js +27 -7
- package/dist/lib/platform/completion-backfill.js +6 -5
- package/dist/lib/platform/context.js +70 -130
- package/dist/lib/platform/github-client.js +8 -2
- package/dist/lib/platform/github-provider.js +903 -0
- package/dist/lib/platform/in-label-sync.js +143 -0
- package/dist/lib/platform/issue-comments.js +94 -41
- package/dist/lib/platform/issues.js +335 -189
- package/dist/lib/platform/metadata-labels.js +6 -2
- package/dist/lib/platform/none-provider.js +28 -0
- package/dist/lib/platform/pr-change-report.js +551 -0
- package/dist/lib/platform/pr-checks.js +124 -106
- package/dist/lib/platform/pr-review.js +88 -72
- package/dist/lib/platform/pr-summary.js +523 -49
- package/dist/lib/platform/provider-bridge.js +45 -0
- package/dist/lib/platform/provider-contract.d.ts +487 -0
- package/dist/lib/platform/provider-contract.js +95 -0
- package/dist/lib/platform/provider-loader.js +251 -0
- package/dist/lib/platform/provider-validation.js +504 -0
- package/dist/lib/platform/pull-request-metadata.js +8 -6
- package/dist/lib/platform/pull-requests.js +1050 -502
- package/dist/lib/platform/release-notes.js +78 -55
- package/dist/lib/platform/releases.js +79 -50
- package/dist/lib/platform/resource-identity.d.ts +26 -0
- package/dist/lib/platform/resource-identity.js +92 -0
- package/dist/lib/platform/task-identities.js +31 -0
- package/dist/lib/platform/verification-required.js +22 -12
- package/dist/lib/platform/verification-sync.js +118 -239
- package/dist/lib/pr-review/evidence-grading.js +30 -9
- package/dist/lib/run/index.js +63 -23
- package/dist/lib/sandbox/config.js +5 -0
- package/dist/lib/sandbox/control/client.js +96 -15
- package/dist/lib/sandbox/control/executor.js +3 -3
- package/dist/lib/sandbox/control/lifecycle.js +29 -7
- package/dist/lib/sandbox/control/protocol.js +77 -0
- package/dist/lib/sandbox/control/server.js +398 -25
- package/dist/lib/sandbox/control/state.js +204 -1
- package/dist/lib/server/process-state.js +2 -2
- package/dist/lib/server/protocol.js +38 -1
- package/dist/lib/task/activity-log.js +8 -1
- package/dist/lib/task/artifact-lifecycle.js +35 -7
- package/dist/lib/task/capabilities.js +239 -0
- package/dist/lib/task/commands/status.js +32 -0
- package/dist/lib/task/commit-intent.js +100 -0
- package/dist/lib/task/commit-operation.js +207 -102
- package/dist/lib/task/control-authority.js +8 -5
- package/dist/lib/task/create-service.js +8 -8
- package/dist/lib/task/create.js +33 -7
- package/dist/lib/task/current-contract.js +5 -1
- package/dist/lib/task/decision-details.js +3 -48
- package/dist/lib/task/delivery-target.js +97 -0
- package/dist/lib/task/delivery.js +153 -0
- package/dist/lib/task/events.js +297 -17
- package/dist/lib/task/finalization.js +37 -13
- package/dist/lib/task/human-override.js +61 -36
- package/dist/lib/task/invalidation-command.js +86 -0
- package/dist/lib/task/invalidation.js +260 -0
- package/dist/lib/task/ledger-intents.js +37 -0
- package/dist/lib/task/lifecycle.js +106 -9
- package/dist/lib/task/local-artifact-finalization.js +374 -0
- package/dist/lib/task/orchestration.js +100 -151
- package/dist/lib/task/pr-delivery-fact.js +183 -0
- package/dist/lib/task/review-finalization.js +4 -8
- package/dist/lib/task/review-fingerprint.js +32 -2
- package/dist/lib/task/rework-intent.js +138 -0
- package/dist/lib/task/skill-scope.js +23 -0
- package/dist/lib/task/task-execution-lock.js +27 -17
- package/dist/lib/task/verification-engine.js +89 -38
- package/dist/lib/task/verification-review.js +1 -1
- package/dist/lib/task/verification.js +3 -3
- package/dist/lib/task/workflow-warning-intents.js +1 -1
- package/dist/lib/task/write.js +11 -0
- package/dist/lib/update.js +9 -0
- package/dist/lib/version.js +11 -1
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapters/codex-lifecycle/build-identity.ts +105 -10
- package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +12 -1
- package/lib/agent-clients/next-steps.ts +6 -1
- package/lib/decide.ts +3 -3
- package/lib/defaults.json +4 -0
- package/lib/git/workflow.ts +38 -1
- package/lib/init.ts +4 -1
- package/lib/internal/git-workflow.ts +7 -1
- package/lib/internal/platform-checks.ts +3 -3
- package/lib/internal/platform-comment.ts +8 -8
- package/lib/internal/platform-context.ts +2 -2
- package/lib/internal/platform-issue.ts +7 -8
- package/lib/internal/platform-pr-review.ts +9 -9
- package/lib/internal/platform-pr.ts +53 -21
- package/lib/internal/platform-release-notes.ts +3 -3
- package/lib/internal/pr-review-grade.ts +10 -8
- package/lib/internal/release-workflow.ts +4 -4
- package/lib/internal/sandbox-control.ts +27 -3
- package/lib/internal/task-activity.ts +3 -3
- package/lib/internal/task-artifact.ts +65 -22
- package/lib/internal/task-create.ts +2 -2
- package/lib/internal/task-delivery.ts +53 -0
- package/lib/internal/task-event.ts +6 -3
- package/lib/internal/task-finalization.ts +3 -3
- package/lib/internal/task-invalidation.ts +39 -0
- package/lib/internal/task-ledger.ts +11 -8
- package/lib/internal/task-lifecycle.ts +2 -2
- package/lib/internal/task-override.ts +6 -6
- package/lib/internal/task-review.ts +3 -3
- package/lib/internal/task-verify.ts +6 -6
- package/lib/internal/task-warning.ts +3 -3
- package/lib/platform/adapters.ts +3 -2
- package/lib/platform/change-request-git-evidence.ts +36 -9
- package/lib/platform/completion-backfill.ts +9 -8
- package/lib/platform/context.ts +97 -134
- package/lib/platform/github-client.ts +10 -3
- package/lib/platform/github-provider.ts +910 -0
- package/lib/platform/in-label-sync.ts +208 -0
- package/lib/platform/issue-comments.ts +97 -47
- package/lib/platform/issues.ts +344 -174
- package/lib/platform/metadata-labels.ts +5 -2
- package/lib/platform/none-provider.ts +35 -0
- package/lib/platform/pr-change-report.ts +645 -0
- package/lib/platform/pr-checks.ts +129 -107
- package/lib/platform/pr-review.ts +88 -85
- package/lib/platform/pr-summary.ts +562 -52
- package/lib/platform/provider-bridge.ts +82 -0
- package/lib/platform/provider-contract.ts +536 -0
- package/lib/platform/provider-loader.ts +307 -0
- package/lib/platform/provider-validation.ts +529 -0
- package/lib/platform/pull-request-metadata.ts +10 -7
- package/lib/platform/pull-requests.ts +1126 -505
- package/lib/platform/release-notes.ts +76 -59
- package/lib/platform/releases.ts +77 -43
- package/lib/platform/resource-identity.ts +121 -0
- package/lib/platform/task-identities.ts +40 -0
- package/lib/platform/types.ts +4 -2
- package/lib/platform/verification-required.ts +21 -13
- package/lib/platform/verification-sync.ts +118 -293
- package/lib/pr-review/evidence-grading.ts +31 -11
- package/lib/run/index.ts +57 -22
- package/lib/sandbox/config.ts +7 -0
- package/lib/sandbox/control/client.ts +105 -15
- package/lib/sandbox/control/executor.ts +4 -4
- package/lib/sandbox/control/lifecycle.ts +30 -8
- package/lib/sandbox/control/protocol.ts +113 -0
- package/lib/sandbox/control/server.ts +431 -23
- package/lib/sandbox/control/state.ts +251 -1
- package/lib/server/process-state.ts +2 -2
- package/lib/server/protocol.ts +36 -1
- package/lib/task/activity-log.ts +8 -0
- package/lib/task/artifact-lifecycle.ts +34 -8
- package/lib/task/capabilities.ts +262 -0
- package/lib/task/commands/status.ts +38 -0
- package/lib/task/commit-intent.ts +137 -0
- package/lib/task/commit-operation.ts +186 -88
- package/lib/task/control-authority.ts +11 -8
- package/lib/task/create-service.ts +8 -8
- package/lib/task/create.ts +34 -9
- package/lib/task/current-contract.ts +6 -1
- package/lib/task/decision-details.ts +8 -64
- package/lib/task/delivery-target.ts +130 -0
- package/lib/task/delivery.ts +160 -0
- package/lib/task/events.ts +296 -16
- package/lib/task/finalization.ts +50 -14
- package/lib/task/human-override.ts +73 -46
- package/lib/task/invalidation-command.ts +86 -0
- package/lib/task/invalidation.ts +332 -0
- package/lib/task/ledger-intents.ts +35 -1
- package/lib/task/lifecycle.ts +93 -4
- package/lib/task/local-artifact-finalization.ts +563 -0
- package/lib/task/orchestration.ts +110 -146
- package/lib/task/pr-delivery-fact.ts +237 -0
- package/lib/task/review-finalization.ts +4 -14
- package/lib/task/review-fingerprint.ts +33 -2
- package/lib/task/rework-intent.ts +165 -0
- package/lib/task/skill-scope.ts +24 -0
- package/lib/task/task-execution-lock.ts +23 -14
- package/lib/task/verification-engine.ts +95 -43
- package/lib/task/verification-review.ts +2 -0
- package/lib/task/verification.ts +4 -4
- package/lib/task/workflow-warning-intents.ts +1 -1
- package/lib/task/write.ts +19 -0
- package/lib/update.ts +11 -1
- package/lib/version.ts +15 -1
- package/package.json +7 -1
- package/templates/.agents/rules/README.en.md +4 -2
- package/templates/.agents/rules/README.zh-CN.md +4 -2
- package/templates/.agents/rules/create-issue.en.md +11 -2
- package/templates/.agents/rules/create-issue.zh-CN.md +24 -2
- package/templates/.agents/rules/issue-pr-commands.en.md +4 -17
- package/templates/.agents/rules/issue-pr-commands.zh-CN.md +1 -1
- package/templates/.agents/rules/issue-sync.en.md +28 -5
- package/templates/.agents/rules/issue-sync.zh-CN.md +64 -5
- package/templates/.agents/rules/label-milestone-setup.en.md +15 -2
- package/templates/.agents/rules/label-milestone-setup.zh-CN.md +15 -2
- package/templates/.agents/rules/local-artifact-repair.en.md +53 -0
- package/templates/.agents/rules/local-artifact-repair.zh-CN.md +53 -0
- package/templates/.agents/rules/milestone-inference.en.md +12 -2
- package/templates/.agents/rules/milestone-inference.zh-CN.md +25 -3
- package/templates/.agents/rules/pr-sync.en.md +28 -13
- package/templates/.agents/rules/pr-sync.zh-CN.md +39 -5
- package/templates/.agents/rules/release-commands.en.md +42 -3
- package/templates/.agents/rules/release-commands.zh-CN.md +42 -3
- package/templates/.agents/rules/security-alerts.en.md +17 -2
- package/templates/.agents/rules/security-alerts.zh-CN.md +17 -2
- package/templates/.agents/rules/sync-content-generation.en.md +8 -0
- package/templates/.agents/rules/sync-content-generation.zh-CN.md +8 -0
- package/templates/.agents/rules/task-management.en.md +1 -1
- package/templates/.agents/rules/task-management.zh-CN.md +1 -1
- package/templates/.agents/rules/task-short-id.en.md +1 -4
- package/templates/.agents/rules/task-short-id.zh-CN.md +1 -3
- package/templates/.agents/scripts/security-alerts.github.sh +151 -0
- package/templates/.agents/scripts/security-alerts.sh +31 -0
- package/templates/.agents/skills/analyze-task/SKILL.en.md +14 -3
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +11 -3
- package/templates/.agents/skills/check-task/SKILL.en.md +16 -37
- package/templates/.agents/skills/check-task/SKILL.zh-CN.md +16 -37
- package/templates/.agents/skills/close-codescan/SKILL.en.md +2 -2
- package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/close-dependabot/SKILL.en.md +2 -2
- package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/code-task/SKILL.en.md +32 -10
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +35 -13
- package/templates/.agents/skills/code-task/config/verify.en.json +1 -0
- package/templates/.agents/skills/code-task/config/verify.zh-CN.json +1 -0
- package/templates/.agents/skills/commit/SKILL.en.md +3 -3
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/commit/config/verify.json +1 -1
- package/templates/.agents/skills/commit/reference/commit-orchestration.en.md +3 -3
- package/templates/.agents/skills/commit/reference/commit-orchestration.zh-CN.md +3 -3
- package/templates/.agents/skills/commit/reference/issue-metadata-sync.en.md +2 -0
- package/templates/.agents/skills/commit/reference/issue-metadata-sync.zh-CN.md +2 -0
- package/templates/.agents/skills/commit/reference/pr-summary-sync.en.md +4 -4
- package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +4 -4
- package/templates/.agents/skills/commit/reference/task-status-update.en.md +2 -2
- package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +2 -2
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +7 -4
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +7 -4
- 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/summary-update.en.md +5 -4
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +5 -4
- package/templates/.agents/skills/complete-task/SKILL.en.md +31 -13
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +30 -13
- package/templates/.agents/skills/complete-task/reference/external-delivery.en.md +2 -2
- package/templates/.agents/skills/complete-task/reference/external-delivery.zh-CN.md +2 -2
- package/templates/.agents/skills/create-pr/SKILL.en.md +11 -10
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +11 -11
- package/templates/.agents/skills/create-pr/config/verify.json +1 -0
- package/templates/.agents/skills/create-pr/reference/change-report.en.md +16 -4
- package/templates/.agents/skills/create-pr/reference/change-report.zh-CN.md +16 -4
- package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +5 -3
- package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +5 -3
- package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +1 -1
- package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +1 -1
- package/templates/.agents/skills/create-pr/scripts/change-report.mjs +3 -0
- package/templates/.agents/skills/create-task/SKILL.en.md +1 -0
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +1 -0
- package/templates/.agents/skills/import-codescan/SKILL.en.md +1 -1
- package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/import-dependabot/SKILL.en.md +1 -1
- package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/init-labels/SKILL.en.md +9 -10
- package/templates/.agents/skills/init-labels/SKILL.zh-CN.md +9 -10
- package/templates/.agents/skills/init-labels/scripts/init-labels.github.sh +57 -1
- package/templates/.agents/skills/init-labels/scripts/init-labels.sh +27 -1
- package/templates/.agents/skills/init-milestones/SKILL.en.md +3 -5
- package/templates/.agents/skills/init-milestones/SKILL.zh-CN.md +3 -5
- package/templates/.agents/skills/plan-task/SKILL.en.md +14 -3
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +11 -3
- package/templates/.agents/skills/review-analysis/SKILL.en.md +6 -5
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +6 -5
- package/templates/.agents/skills/review-analysis/reference/output-templates.en.md +18 -0
- package/templates/.agents/skills/review-analysis/reference/output-templates.zh-CN.md +18 -0
- package/templates/.agents/skills/review-code/SKILL.en.md +10 -9
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +10 -9
- package/templates/.agents/skills/review-code/reference/output-templates.en.md +19 -1
- package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +19 -1
- package/templates/.agents/skills/review-code/reference/report-template.en.md +4 -2
- package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +4 -2
- package/templates/.agents/skills/review-plan/SKILL.en.md +6 -5
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +6 -5
- package/templates/.agents/skills/review-plan/reference/output-templates.en.md +18 -0
- package/templates/.agents/skills/review-plan/reference/output-templates.zh-CN.md +18 -0
- package/templates/.agents/skills/review-pr/SKILL.en.md +1 -0
- package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +1 -0
- package/templates/.agents/skills/review-pr/reference/evidence-grading.en.md +2 -2
- package/templates/.agents/skills/review-pr/reference/evidence-grading.zh-CN.md +2 -2
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +5 -2
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +5 -2
- 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/update-agent-infra/scripts/sync-templates.js +4 -0
- package/templates/.agents/skills/watch-pr/SKILL.en.md +6 -6
- package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +6 -6
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +6 -0
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +6 -0
- package/templates/.agents/templates/task.en.md +23 -2
- package/templates/.agents/templates/task.zh-CN.md +23 -2
- package/templates/.agents/workflows/bug-fix.en.yaml +9 -8
- package/templates/.agents/workflows/bug-fix.zh-CN.yaml +9 -8
- package/templates/.agents/workflows/feature-development.en.yaml +9 -8
- package/templates/.agents/workflows/feature-development.zh-CN.yaml +9 -8
- package/templates/.agents/workflows/refactoring.en.yaml +9 -8
- package/templates/.agents/workflows/refactoring.zh-CN.yaml +9 -8
- package/templates/.github/workflows/pr-label.yml +7 -26
- package/templates/.agents/rules/create-issue.github.en.md +0 -14
- package/templates/.agents/rules/create-issue.github.zh-CN.md +0 -27
- package/templates/.agents/rules/issue-pr-commands.github.en.md +0 -34
- package/templates/.agents/rules/issue-pr-commands.github.zh-CN.md +0 -49
- package/templates/.agents/rules/issue-sync.github.en.md +0 -40
- package/templates/.agents/rules/issue-sync.github.zh-CN.md +0 -76
- package/templates/.agents/rules/label-milestone-setup.github.en.md +0 -60
- package/templates/.agents/rules/label-milestone-setup.github.zh-CN.md +0 -60
- package/templates/.agents/rules/milestone-inference.github.en.md +0 -15
- package/templates/.agents/rules/milestone-inference.github.zh-CN.md +0 -27
- package/templates/.agents/rules/pr-checks-commands.github.en.md +0 -18
- package/templates/.agents/rules/pr-checks-commands.github.zh-CN.md +0 -34
- package/templates/.agents/rules/pr-sync.github.en.md +0 -43
- package/templates/.agents/rules/pr-sync.github.zh-CN.md +0 -69
- package/templates/.agents/rules/release-commands.github.en.md +0 -44
- package/templates/.agents/rules/release-commands.github.zh-CN.md +0 -44
- package/templates/.agents/rules/security-alerts.github.en.md +0 -43
- package/templates/.agents/rules/security-alerts.github.zh-CN.md +0 -43
- /package/templates/.agents/rules/{issue-fields.github.en.md → issue-fields.en.md} +0 -0
- /package/templates/.agents/rules/{issue-fields.github.zh-CN.md → issue-fields.zh-CN.md} +0 -0
package/lib/run/index.ts
CHANGED
|
@@ -38,54 +38,90 @@ export type RunSkillOptions = {
|
|
|
38
38
|
writeStderr?: (chunk: string) => void;
|
|
39
39
|
};
|
|
40
40
|
|
|
41
|
-
const USAGE = `Usage: ai run <skill> [task-ref] [
|
|
41
|
+
const USAGE = `Usage: ai run --skill <skill> [--task <task-ref>] [--tui <name>] [--recreate] [-- <skill-args...>]
|
|
42
42
|
|
|
43
43
|
Task skills are scheduled inside the sandbox tmux session; ai run returns once
|
|
44
44
|
the tmux window is created. --recreate authorizes container-only replacement
|
|
45
45
|
when in-place sandbox recovery fails; it is not included in the TUI prompt.
|
|
46
46
|
|
|
47
47
|
Examples:
|
|
48
|
-
ai run create-task "describe the task" --tui codex
|
|
49
|
-
ai run code-task
|
|
48
|
+
ai run --skill create-task "describe the task" --tui codex
|
|
49
|
+
ai run --skill code-task --task 7 --tui codex --recreate`;
|
|
50
50
|
|
|
51
|
-
function
|
|
52
|
-
const
|
|
51
|
+
function optionValue(args: string[], index: number, option: string): string {
|
|
52
|
+
const value = args[index + 1];
|
|
53
|
+
if (!value || value.startsWith('--')) throw new Error(`${option} requires a value`);
|
|
54
|
+
return value;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function extractHostOptions(args: string[]): {
|
|
58
|
+
positionals: string[];
|
|
59
|
+
forwarded: string[];
|
|
60
|
+
skill: string | null;
|
|
61
|
+
taskRef: string | null;
|
|
62
|
+
tui: string | null;
|
|
63
|
+
recreate: boolean;
|
|
64
|
+
} {
|
|
65
|
+
const positionals: string[] = [];
|
|
66
|
+
const forwarded: string[] = [];
|
|
67
|
+
let skill: string | null = null;
|
|
68
|
+
let taskRef: string | null = null;
|
|
53
69
|
let tui: string | null = null;
|
|
70
|
+
let recreate = false;
|
|
71
|
+
let separator = false;
|
|
54
72
|
for (let i = 0; i < args.length; i += 1) {
|
|
55
73
|
const arg = args[i] as string;
|
|
56
|
-
if (
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
74
|
+
if (separator) {
|
|
75
|
+
forwarded.push(arg);
|
|
76
|
+
} else if (arg === '--') {
|
|
77
|
+
separator = true;
|
|
78
|
+
} else if (arg === '--skill') {
|
|
79
|
+
if (skill !== null) throw new Error("duplicate option '--skill'");
|
|
80
|
+
skill = optionValue(args, i, '--skill');
|
|
60
81
|
i += 1;
|
|
61
|
-
|
|
82
|
+
} else if (arg === '--task') {
|
|
83
|
+
if (taskRef !== null) throw new Error("duplicate option '--task'");
|
|
84
|
+
taskRef = optionValue(args, i, '--task');
|
|
85
|
+
i += 1;
|
|
86
|
+
} else if (arg === '--tui') {
|
|
87
|
+
if (tui !== null) throw new Error("duplicate option '--tui'");
|
|
88
|
+
tui = optionValue(args, i, '--tui');
|
|
89
|
+
i += 1;
|
|
90
|
+
} else if (arg === '--recreate') {
|
|
91
|
+
if (recreate) throw new Error("duplicate option '--recreate'");
|
|
92
|
+
recreate = true;
|
|
93
|
+
} else if (arg.startsWith('-')) {
|
|
94
|
+
throw new Error(`Unknown option '${arg}'`);
|
|
95
|
+
} else {
|
|
96
|
+
positionals.push(arg);
|
|
62
97
|
}
|
|
63
|
-
rest.push(arg);
|
|
64
98
|
}
|
|
65
|
-
return {
|
|
99
|
+
return { positionals, forwarded, skill, taskRef, tui, recreate };
|
|
66
100
|
}
|
|
67
101
|
|
|
68
102
|
export function parseRunArgs(args: string[]): ParsedRunArgs {
|
|
69
103
|
if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
|
|
70
104
|
throw new Error(USAGE);
|
|
71
105
|
}
|
|
72
|
-
const {
|
|
73
|
-
|
|
74
|
-
const rest = withRecoveryFlags.filter((arg) => arg !== '--recreate');
|
|
75
|
-
const [skill = '', maybeTaskRef, ...remaining] = rest;
|
|
106
|
+
const { positionals, forwarded, skill, taskRef, tui, recreate } = extractHostOptions(args);
|
|
107
|
+
if (!skill) throw new Error("option '--skill' is required");
|
|
76
108
|
const spec = getSkillRunSpec(skill);
|
|
77
109
|
if (!spec) throw new Error(`Unknown skill '${skill}'`);
|
|
78
110
|
if (spec.kind === 'create') {
|
|
111
|
+
if (taskRef !== null) throw new Error("create-task does not accept '--task'");
|
|
79
112
|
if (recreate) throw new Error('--recreate is only valid for sandbox task runs');
|
|
80
|
-
const createArgs =
|
|
113
|
+
const createArgs = [...positionals, ...forwarded];
|
|
81
114
|
if (createArgs.length === 0) throw new Error('create-task requires a description');
|
|
82
115
|
return { skill, taskRef: null, args: createArgs, tui };
|
|
83
116
|
}
|
|
84
|
-
if (
|
|
117
|
+
if (positionals.length > 0) {
|
|
118
|
+
throw new Error('ai run requires --skill and --task; positional host arguments are not supported');
|
|
119
|
+
}
|
|
120
|
+
if (taskRef === null) throw new Error(`${skill} requires a task-ref`);
|
|
85
121
|
return {
|
|
86
122
|
skill,
|
|
87
|
-
taskRef
|
|
88
|
-
args:
|
|
123
|
+
taskRef,
|
|
124
|
+
args: forwarded,
|
|
89
125
|
tui,
|
|
90
126
|
...(recreate ? { recreate: true as const } : {})
|
|
91
127
|
};
|
|
@@ -200,8 +236,7 @@ export async function runSkill(args: string[], options: RunSkillOptions = {}): P
|
|
|
200
236
|
const commandConfig = options.command ?? config?.command ?? {};
|
|
201
237
|
assertAllowedByConfig(parsed.skill, commandConfig);
|
|
202
238
|
const tui = selectTui(parsed.skill, { cliTui: parsed.tui, command: commandConfig });
|
|
203
|
-
const
|
|
204
|
-
const prompt = renderPrompt({ tui, skill: parsed.skill, args: promptArgs });
|
|
239
|
+
const prompt = renderPrompt({ tui, skill: parsed.skill, args: parsed.args });
|
|
205
240
|
const [file, argv] = buildTuiCommand(tui, prompt);
|
|
206
241
|
const command = [file, ...argv];
|
|
207
242
|
|
package/lib/sandbox/config.ts
CHANGED
|
@@ -63,6 +63,7 @@ export type SandboxConfig = {
|
|
|
63
63
|
tools: string[];
|
|
64
64
|
customTools: SandboxTool[];
|
|
65
65
|
agentClientState: AgentClientState;
|
|
66
|
+
delivery?: { remote: string; baseRef: string };
|
|
66
67
|
refreshIntervalDays: number;
|
|
67
68
|
dockerfile: string | null;
|
|
68
69
|
vm: SandboxVmConfig;
|
|
@@ -73,6 +74,7 @@ type AircConfig = {
|
|
|
73
74
|
org?: unknown;
|
|
74
75
|
agentClients?: unknown;
|
|
75
76
|
sandbox?: SandboxConfigInput;
|
|
77
|
+
delivery?: { remote?: unknown; baseRef?: unknown };
|
|
76
78
|
};
|
|
77
79
|
|
|
78
80
|
function detectRepoRoot(): string {
|
|
@@ -135,6 +137,7 @@ export function loadConfig({
|
|
|
135
137
|
const agentClients = normalizeAgentClients(airc);
|
|
136
138
|
const defaults = cloneDefaults();
|
|
137
139
|
const sandbox = airc.sandbox ?? {};
|
|
140
|
+
const delivery = airc.delivery ?? {};
|
|
138
141
|
const engine = validateSandboxEngine(sandbox.engine ?? defaults.engine, { platformFn });
|
|
139
142
|
const project = airc.project;
|
|
140
143
|
|
|
@@ -188,6 +191,10 @@ export function loadConfig({
|
|
|
188
191
|
tools,
|
|
189
192
|
customTools,
|
|
190
193
|
agentClientState: agentClients.state,
|
|
194
|
+
delivery: {
|
|
195
|
+
remote: typeof delivery.remote === 'string' ? delivery.remote : 'origin',
|
|
196
|
+
baseRef: typeof delivery.baseRef === 'string' ? delivery.baseRef : 'main'
|
|
197
|
+
},
|
|
191
198
|
refreshIntervalDays: asNonNegativeIntegerOrDefault(
|
|
192
199
|
sandbox.refreshIntervalDays,
|
|
193
200
|
defaults.refreshIntervalDays
|
|
@@ -20,7 +20,7 @@ import type {
|
|
|
20
20
|
} from './controller-registration.ts';
|
|
21
21
|
import type { ProcessIdentity } from '../../server/process-state.ts';
|
|
22
22
|
import { normalizeAgentToken } from '../../agent-clients/tokens.ts';
|
|
23
|
-
import { readSandboxControlStatus } from './state.ts';
|
|
23
|
+
import { readSandboxControlPayload, readSandboxControlStatus } from './state.ts';
|
|
24
24
|
import type { TaskCreateCandidateV1 } from '../../task/create.ts';
|
|
25
25
|
|
|
26
26
|
const SANDBOX_CONTROL_RESPONSE_SETTLE_MS = 250;
|
|
@@ -28,11 +28,13 @@ const SANDBOX_CONTROL_RESPONSE_SETTLE_MS = 250;
|
|
|
28
28
|
export class SandboxControlClientError extends Error {
|
|
29
29
|
readonly detail: SandboxControlError;
|
|
30
30
|
readonly accepted: boolean;
|
|
31
|
-
|
|
31
|
+
readonly requestId: string | null;
|
|
32
|
+
constructor(detail: SandboxControlError, accepted = false, requestId: string | null = null) {
|
|
32
33
|
super(detail.message);
|
|
33
34
|
this.name = 'SandboxControlClientError';
|
|
34
35
|
this.detail = detail;
|
|
35
36
|
this.accepted = accepted;
|
|
37
|
+
this.requestId = requestId;
|
|
36
38
|
}
|
|
37
39
|
}
|
|
38
40
|
|
|
@@ -40,8 +42,14 @@ function sleep(milliseconds: number): void {
|
|
|
40
42
|
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, milliseconds);
|
|
41
43
|
}
|
|
42
44
|
|
|
43
|
-
function clientError(
|
|
44
|
-
|
|
45
|
+
function clientError(
|
|
46
|
+
code: string,
|
|
47
|
+
message: string,
|
|
48
|
+
retryable: boolean,
|
|
49
|
+
accepted = false,
|
|
50
|
+
requestId: string | null = null
|
|
51
|
+
): never {
|
|
52
|
+
throw new SandboxControlClientError({ code, message: `${code}: ${message}`, retryable }, accepted, requestId);
|
|
45
53
|
}
|
|
46
54
|
|
|
47
55
|
function preflight(statusDir: string, generation: string, now = Date.now()): void {
|
|
@@ -68,13 +76,35 @@ function preflight(statusDir: string, generation: string, now = Date.now()): voi
|
|
|
68
76
|
function parseResponse(raw: string, id: string): SandboxControlResponse {
|
|
69
77
|
const response = JSON.parse(raw) as SandboxControlResponse;
|
|
70
78
|
if (response.version !== 2 || response.id !== id
|
|
71
|
-
|| !['
|
|
72
|
-
|| (response.phase === 'completed' && !Number.isInteger(response.exitCode))
|
|
73
|
-
|
|
79
|
+
|| !['completed', 'rejected'].includes(response.phase)
|
|
80
|
+
|| (response.phase === 'completed' && !Number.isInteger(response.exitCode))
|
|
81
|
+
|| (response.outputState !== undefined && response.outputState !== 'available' && response.outputState !== 'unavailable')
|
|
82
|
+
|| (response.outputState === 'available' && !response.payload)
|
|
83
|
+
|| (response.outputState === 'unavailable' && response.payload !== null && response.payload !== undefined)) {
|
|
84
|
+
clientError('SANDBOX_CONTROL_RESPONSE_INVALID', 'broker response is invalid', false, false, id);
|
|
74
85
|
}
|
|
75
86
|
return response;
|
|
76
87
|
}
|
|
77
88
|
|
|
89
|
+
function readPublishedResponse(raw: string, id: string, channelDir: string): SandboxControlResponse {
|
|
90
|
+
const response = parseResponse(raw, id);
|
|
91
|
+
if (response.outputState !== 'available') return response;
|
|
92
|
+
const filePath = path.join(channelDir, 'responses', `${id}.payload.json`);
|
|
93
|
+
let payload;
|
|
94
|
+
try {
|
|
95
|
+
payload = readSandboxControlPayload(filePath);
|
|
96
|
+
} catch {
|
|
97
|
+
clientError('SANDBOX_CONTROL_RESPONSE_INVALID', 'broker payload is missing or invalid', false, true, id);
|
|
98
|
+
}
|
|
99
|
+
if (!response.payload || response.payload.version !== payload.version
|
|
100
|
+
|| response.payload.id !== payload.id || response.payload.generation !== payload.generation
|
|
101
|
+
|| response.payload.stdoutBytes !== payload.stdoutBytes || response.payload.stderrBytes !== payload.stderrBytes
|
|
102
|
+
|| response.payload.stdoutSha256 !== payload.stdoutSha256 || response.payload.stderrSha256 !== payload.stderrSha256) {
|
|
103
|
+
clientError('SANDBOX_CONTROL_RESPONSE_INVALID', 'broker payload reference is invalid', false, true, id);
|
|
104
|
+
}
|
|
105
|
+
return { ...response, stdout: payload.stdout, stderr: payload.stderr };
|
|
106
|
+
}
|
|
107
|
+
|
|
78
108
|
function cancelPendingRequest(requestPath: string): boolean {
|
|
79
109
|
try {
|
|
80
110
|
fs.unlinkSync(requestPath);
|
|
@@ -95,13 +125,16 @@ function exchangeSandboxControl(request: SandboxControlRequest, params: Readonly
|
|
|
95
125
|
preflight(statusDir, request.generation);
|
|
96
126
|
const encoded = `${JSON.stringify(request)}\n`;
|
|
97
127
|
if (Buffer.byteLength(encoded, 'utf8') > SANDBOX_CONTROL_MAX_BYTES) {
|
|
98
|
-
clientError('SANDBOX_CONTROL_REQUEST_TOO_LARGE', 'request exceeds the control limit', false);
|
|
128
|
+
clientError('SANDBOX_CONTROL_REQUEST_TOO_LARGE', 'request exceeds the control limit', false, false, request.id);
|
|
99
129
|
}
|
|
100
130
|
const requestsDir = path.join(channelDir, 'requests');
|
|
101
131
|
const responsesDir = path.join(channelDir, 'responses');
|
|
102
132
|
const temporary = path.join(requestsDir, `.${request.id}.tmp`);
|
|
103
133
|
const requestPath = path.join(requestsDir, `${request.id}.json`);
|
|
104
134
|
const responsePath = path.join(responsesDir, `${request.id}.json`);
|
|
135
|
+
if (fs.existsSync(responsePath)) {
|
|
136
|
+
clientError('SANDBOX_CONTROL_REQUEST_REPLAYED', 'request id already has a terminal response', false, false, request.id);
|
|
137
|
+
}
|
|
105
138
|
fs.writeFileSync(temporary, encoded, { mode: 0o600, flag: 'wx' });
|
|
106
139
|
fs.renameSync(temporary, requestPath);
|
|
107
140
|
const deadline = Date.now() + (params.timeoutMs ?? 30_000);
|
|
@@ -109,6 +142,8 @@ function exchangeSandboxControl(request: SandboxControlRequest, params: Readonly
|
|
|
109
142
|
let malformedResponseRaw: string | null = null;
|
|
110
143
|
let malformedResponseObservedAt = 0;
|
|
111
144
|
while (Date.now() < deadline) {
|
|
145
|
+
const acceptedPath = path.join(responsesDir, `${request.id}.accepted.json`);
|
|
146
|
+
if (fs.existsSync(acceptedPath)) accepted = true;
|
|
112
147
|
if (fs.existsSync(responsePath)) {
|
|
113
148
|
let raw: string;
|
|
114
149
|
try {
|
|
@@ -119,7 +154,7 @@ function exchangeSandboxControl(request: SandboxControlRequest, params: Readonly
|
|
|
119
154
|
}
|
|
120
155
|
let response: SandboxControlResponse;
|
|
121
156
|
try {
|
|
122
|
-
response =
|
|
157
|
+
response = readPublishedResponse(raw, request.id, channelDir);
|
|
123
158
|
malformedResponseRaw = null;
|
|
124
159
|
} catch (error) {
|
|
125
160
|
if (!(error instanceof SyntaxError)) throw error;
|
|
@@ -128,7 +163,7 @@ function exchangeSandboxControl(request: SandboxControlRequest, params: Readonly
|
|
|
128
163
|
malformedResponseRaw = raw;
|
|
129
164
|
malformedResponseObservedAt = now;
|
|
130
165
|
} else if (now - malformedResponseObservedAt >= SANDBOX_CONTROL_RESPONSE_SETTLE_MS) {
|
|
131
|
-
clientError('SANDBOX_CONTROL_RESPONSE_INVALID', 'broker response remained malformed', false, accepted);
|
|
166
|
+
clientError('SANDBOX_CONTROL_RESPONSE_INVALID', 'broker response remained malformed', false, accepted, request.id);
|
|
132
167
|
}
|
|
133
168
|
sleep(25);
|
|
134
169
|
continue;
|
|
@@ -136,7 +171,6 @@ function exchangeSandboxControl(request: SandboxControlRequest, params: Readonly
|
|
|
136
171
|
if (response.phase === 'accepted') {
|
|
137
172
|
accepted = true;
|
|
138
173
|
} else {
|
|
139
|
-
fs.rmSync(responsePath, { force: true });
|
|
140
174
|
return response;
|
|
141
175
|
}
|
|
142
176
|
}
|
|
@@ -149,7 +183,8 @@ function exchangeSandboxControl(request: SandboxControlRequest, params: Readonly
|
|
|
149
183
|
'SANDBOX_CONTROL_RESULT_UNKNOWN',
|
|
150
184
|
'request was claimed before broker availability was lost; inspect domain state before retrying',
|
|
151
185
|
false,
|
|
152
|
-
true
|
|
186
|
+
true,
|
|
187
|
+
request.id
|
|
153
188
|
);
|
|
154
189
|
}
|
|
155
190
|
}
|
|
@@ -160,7 +195,8 @@ function exchangeSandboxControl(request: SandboxControlRequest, params: Readonly
|
|
|
160
195
|
'SANDBOX_CONTROL_RESULT_UNKNOWN',
|
|
161
196
|
'accepted request did not produce a final result; inspect domain state before retrying',
|
|
162
197
|
false,
|
|
163
|
-
true
|
|
198
|
+
true,
|
|
199
|
+
request.id
|
|
164
200
|
);
|
|
165
201
|
}
|
|
166
202
|
if (!cancelPendingRequest(requestPath)) {
|
|
@@ -168,10 +204,64 @@ function exchangeSandboxControl(request: SandboxControlRequest, params: Readonly
|
|
|
168
204
|
'SANDBOX_CONTROL_RESULT_UNKNOWN',
|
|
169
205
|
'request was claimed but no acceptance result was observed; inspect domain state before retrying',
|
|
170
206
|
false,
|
|
171
|
-
true
|
|
207
|
+
true,
|
|
208
|
+
request.id
|
|
172
209
|
);
|
|
173
210
|
}
|
|
174
|
-
clientError('SANDBOX_CONTROL_BROKER_UNAVAILABLE', `broker did not accept the request within ${params.timeoutMs ?? 30_000}ms`, true);
|
|
211
|
+
clientError('SANDBOX_CONTROL_BROKER_UNAVAILABLE', `broker did not accept the request within ${params.timeoutMs ?? 30_000}ms`, true, false, request.id);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export function recoverSandboxControl(requestId: string, params: Readonly<{
|
|
215
|
+
channelDir?: string;
|
|
216
|
+
timeoutMs?: number;
|
|
217
|
+
}> = {}): SandboxControlResponse {
|
|
218
|
+
if (!/^[a-f0-9-]{16,64}$/u.test(requestId)) {
|
|
219
|
+
clientError('SANDBOX_CONTROL_REQUEST_INVALID', 'request id is invalid', false, true);
|
|
220
|
+
}
|
|
221
|
+
const channelDir = params.channelDir ?? process.env.AGENT_INFRA_CONTROL_DIR ?? '/run/agent-infra/control';
|
|
222
|
+
const responsePath = path.join(channelDir, 'responses', `${requestId}.json`);
|
|
223
|
+
const deadline = Date.now() + (params.timeoutMs ?? 30_000);
|
|
224
|
+
let accepted = false;
|
|
225
|
+
let malformedResponseRaw: string | null = null;
|
|
226
|
+
let malformedResponseObservedAt = 0;
|
|
227
|
+
while (Date.now() < deadline) {
|
|
228
|
+
if (fs.existsSync(path.join(channelDir, 'responses', `${requestId}.accepted.json`))) accepted = true;
|
|
229
|
+
if (fs.existsSync(responsePath)) {
|
|
230
|
+
let raw: string;
|
|
231
|
+
try {
|
|
232
|
+
raw = fs.readFileSync(responsePath, 'utf8');
|
|
233
|
+
} catch (error) {
|
|
234
|
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') continue;
|
|
235
|
+
throw error;
|
|
236
|
+
}
|
|
237
|
+
let response: SandboxControlResponse;
|
|
238
|
+
try {
|
|
239
|
+
response = readPublishedResponse(raw, requestId, channelDir);
|
|
240
|
+
malformedResponseRaw = null;
|
|
241
|
+
} catch (error) {
|
|
242
|
+
if (!(error instanceof SyntaxError)) throw error;
|
|
243
|
+
const now = Date.now();
|
|
244
|
+
if (malformedResponseRaw !== raw) {
|
|
245
|
+
malformedResponseRaw = raw;
|
|
246
|
+
malformedResponseObservedAt = now;
|
|
247
|
+
} else if (now - malformedResponseObservedAt >= SANDBOX_CONTROL_RESPONSE_SETTLE_MS) {
|
|
248
|
+
clientError('SANDBOX_CONTROL_RESPONSE_INVALID', 'broker response remained malformed', false, accepted, requestId);
|
|
249
|
+
}
|
|
250
|
+
sleep(25);
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
if (response.phase === 'accepted') accepted = true;
|
|
254
|
+
else return response;
|
|
255
|
+
}
|
|
256
|
+
sleep(25);
|
|
257
|
+
}
|
|
258
|
+
clientError(
|
|
259
|
+
'SANDBOX_CONTROL_RESULT_UNKNOWN',
|
|
260
|
+
'request did not produce a final result; inspect domain state before retrying',
|
|
261
|
+
false,
|
|
262
|
+
accepted,
|
|
263
|
+
requestId
|
|
264
|
+
);
|
|
175
265
|
}
|
|
176
266
|
|
|
177
267
|
function authority(params: { token?: string; generation?: string }): { token: string; generation: string } {
|
|
@@ -218,7 +218,7 @@ function lifecycleFailure(code: string, message: string): SandboxControlExecutio
|
|
|
218
218
|
};
|
|
219
219
|
}
|
|
220
220
|
|
|
221
|
-
function finalizationResult(result: ReturnType<typeof applyTaskFinalization
|
|
221
|
+
function finalizationResult(result: Awaited<ReturnType<typeof applyTaskFinalization>>): SandboxControlExecutionResult {
|
|
222
222
|
const payload = {
|
|
223
223
|
version: 1,
|
|
224
224
|
status: result.status,
|
|
@@ -246,7 +246,7 @@ export async function executeRequest(
|
|
|
246
246
|
options: ExecuteRequestOptions = {}
|
|
247
247
|
): Promise<SandboxControlExecutionResult> {
|
|
248
248
|
if (request.family === 'task-create') {
|
|
249
|
-
const result = createTask(request.candidate, { repoRoot: manifest.repoRoot });
|
|
249
|
+
const result = await createTask(request.candidate, { repoRoot: manifest.repoRoot });
|
|
250
250
|
return {
|
|
251
251
|
exitCode: result.status === 'blocked' ? 2 : result.status === 'failed' ? 1 : 0,
|
|
252
252
|
stdout: `${JSON.stringify(result)}\n`,
|
|
@@ -313,7 +313,7 @@ export async function executeRequest(
|
|
|
313
313
|
}),
|
|
314
314
|
operation
|
|
315
315
|
);
|
|
316
|
-
return finalizationResult(result);
|
|
316
|
+
return finalizationResult(await result);
|
|
317
317
|
}
|
|
318
318
|
const boundArgs = bindSandboxControlTask(request, manifest.taskId!);
|
|
319
319
|
if (request.family === 'task-orchestration') {
|
|
@@ -387,7 +387,7 @@ export async function executeRequest(
|
|
|
387
387
|
return format(await dispatchTaskControlOperation(context, operation));
|
|
388
388
|
}
|
|
389
389
|
if (operation.family !== 'task-lifecycle') throw new Error('SANDBOX_CONTROL_LIFECYCLE_OPERATION_INVALID');
|
|
390
|
-
|
|
390
|
+
return format(await dispatchTaskControlOperation(context, operation));
|
|
391
391
|
}
|
|
392
392
|
|
|
393
393
|
export async function runSandboxControlExecutor(requestPath: string, nonce: string): Promise<void> {
|
|
@@ -494,30 +494,32 @@ function readSandboxControlManifestValue(manifestPath: string): SandboxControlMa
|
|
|
494
494
|
} catch {
|
|
495
495
|
throw new Error('SANDBOX_CONTROL_MANIFEST_INVALID');
|
|
496
496
|
}
|
|
497
|
-
const candidate = manifest as Partial<SandboxControlManifest
|
|
497
|
+
const candidate = manifest as Partial<SandboxControlManifest>;
|
|
498
498
|
const expectedKeys = [
|
|
499
499
|
'branch', 'channelDir', 'container', 'containerIdentity', 'engine', 'generation',
|
|
500
500
|
'mode', 'processingDir', 'project', 'publicStatusDir', 'repoRoot', 'runtimeDir',
|
|
501
501
|
'taskId', 'token', 'worktreeRoot'
|
|
502
502
|
];
|
|
503
|
-
if ('
|
|
504
|
-
|
|
505
|
-
throw new Error('SANDBOX_CONTROL_MANIFEST_REBUILD_REQUIRED: container-only recreation/rebuild is required');
|
|
503
|
+
if (Object.keys(manifest).sort().join(',') !== expectedKeys.sort().join(',')) {
|
|
504
|
+
throw new Error('SANDBOX_CONTROL_MANIFEST_INVALID');
|
|
506
505
|
}
|
|
507
506
|
if (typeof candidate.containerIdentity !== 'object' || candidate.containerIdentity === null
|
|
508
507
|
|| Array.isArray(candidate.containerIdentity)) throw new Error('SANDBOX_CONTROL_MANIFEST_INVALID');
|
|
509
508
|
const containerIdentity = candidate.containerIdentity as Record<string, unknown>;
|
|
510
|
-
if (
|
|
509
|
+
if (Object.keys(containerIdentity).sort().join(',') !== 'id,labels'
|
|
510
|
+
|| typeof candidate.engine !== 'string' || candidate.engine.length === 0
|
|
511
511
|
|| typeof candidate.repoRoot !== 'string' || typeof candidate.worktreeRoot !== 'string'
|
|
512
512
|
|| typeof candidate.project !== 'string' || typeof candidate.container !== 'string'
|
|
513
513
|
|| typeof containerIdentity.id !== 'string' || containerIdentity.id.length === 0
|
|
514
514
|
|| typeof containerIdentity.labels !== 'object' || containerIdentity.labels === null
|
|
515
515
|
|| Array.isArray(containerIdentity.labels)
|
|
516
516
|
|| Object.values(containerIdentity.labels).some((value) => typeof value !== 'string')
|
|
517
|
-
|| typeof candidate.branch !== 'string'
|
|
517
|
+
|| typeof candidate.branch !== 'string'
|
|
518
|
+
|| (candidate.mode !== 'task-bound' && candidate.mode !== 'branch-only')
|
|
518
519
|
|| (candidate.taskId !== null && typeof candidate.taskId !== 'string')
|
|
519
520
|
|| typeof candidate.channelDir !== 'string' || typeof candidate.publicStatusDir !== 'string'
|
|
520
|
-
|| typeof candidate.processingDir !== 'string' || typeof candidate.
|
|
521
|
+
|| typeof candidate.processingDir !== 'string' || typeof candidate.runtimeDir !== 'string'
|
|
522
|
+
|| typeof candidate.token !== 'string'
|
|
521
523
|
|| typeof candidate.generation !== 'string'
|
|
522
524
|
|| (candidate.mode === 'task-bound' && (typeof candidate.taskId !== 'string' || candidate.taskId.length === 0))
|
|
523
525
|
|| (candidate.mode === 'branch-only' && candidate.taskId !== null)) throw new Error('SANDBOX_CONTROL_MANIFEST_INVALID');
|
|
@@ -530,7 +532,27 @@ function readSandboxControlManifestValue(manifestPath: string): SandboxControlMa
|
|
|
530
532
|
if (path.resolve(candidate.runtimeDir) !== path.join(root, 'runtime')) {
|
|
531
533
|
throw new Error('SANDBOX_CONTROL_MANIFEST_INVALID');
|
|
532
534
|
}
|
|
533
|
-
|
|
535
|
+
const normalized: SandboxControlManifest = {
|
|
536
|
+
engine: candidate.engine,
|
|
537
|
+
repoRoot: candidate.repoRoot,
|
|
538
|
+
worktreeRoot: candidate.worktreeRoot,
|
|
539
|
+
project: candidate.project,
|
|
540
|
+
container: candidate.container,
|
|
541
|
+
containerIdentity: {
|
|
542
|
+
id: containerIdentity.id as string,
|
|
543
|
+
labels: { ...(containerIdentity.labels as Record<string, string>) }
|
|
544
|
+
},
|
|
545
|
+
branch: candidate.branch,
|
|
546
|
+
mode: candidate.mode,
|
|
547
|
+
taskId: candidate.taskId,
|
|
548
|
+
token: candidate.token,
|
|
549
|
+
generation: candidate.generation,
|
|
550
|
+
channelDir: candidate.channelDir,
|
|
551
|
+
publicStatusDir: candidate.publicStatusDir,
|
|
552
|
+
processingDir: candidate.processingDir,
|
|
553
|
+
runtimeDir: candidate.runtimeDir
|
|
554
|
+
};
|
|
555
|
+
return normalized;
|
|
534
556
|
}
|
|
535
557
|
|
|
536
558
|
export function readSandboxControlManifest(manifestPath: string): SandboxControlManifest {
|
|
@@ -1,9 +1,14 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
1
2
|
import { validateTaskCreateCandidate, type TaskCreateCandidateV1 } from '../../task/create.ts';
|
|
2
3
|
import { normalizeAgentToken } from '../../agent-clients/tokens.ts';
|
|
3
4
|
import type { ProcessIdentity } from '../../server/process-state.ts';
|
|
4
5
|
import type { CodexControllerLeaseProofV1 } from './controller-registration.ts';
|
|
5
6
|
|
|
6
7
|
export const SANDBOX_CONTROL_MAX_BYTES = 64 * 1024;
|
|
8
|
+
export const SANDBOX_CONTROL_MAX_LOGICAL_RECORDS = 1024;
|
|
9
|
+
export const SANDBOX_CONTROL_MAX_RESPONSE_BYTES = 64 * 1024 * 1024;
|
|
10
|
+
export const SANDBOX_CONTROL_MAX_TERMINAL_RECORD_BYTES = 1024 * 1024;
|
|
11
|
+
export const SANDBOX_CONTROL_RESERVATION_BYTES = SANDBOX_CONTROL_MAX_TERMINAL_RECORD_BYTES + 4 * 1024;
|
|
7
12
|
export const SANDBOX_CONTROL_ADMISSION_WINDOW_MS = 2_000;
|
|
8
13
|
export const SANDBOX_CONTROL_STATUS_INTERVAL_MS = 250;
|
|
9
14
|
export const SANDBOX_CONTROL_STATUS_STALE_MS = 1_500;
|
|
@@ -63,9 +68,43 @@ export type SandboxCodexControllerRequest = RequestBase & Readonly<{
|
|
|
63
68
|
}>;
|
|
64
69
|
export type SandboxControlRequest = SandboxTaskCommandRequest | SandboxTaskFinalizationRequest | SandboxTaskCreateRequest | SandboxCodexControllerRequest;
|
|
65
70
|
export type SandboxControlError = Readonly<{ code: string; message: string; retryable: boolean }>;
|
|
71
|
+
export type SandboxControlResultEvidence = Readonly<{
|
|
72
|
+
version: 1;
|
|
73
|
+
id: string;
|
|
74
|
+
generation: string;
|
|
75
|
+
exitCode: number;
|
|
76
|
+
stdoutBytes: number;
|
|
77
|
+
stderrBytes: number;
|
|
78
|
+
stdoutSha256: string;
|
|
79
|
+
stderrSha256: string;
|
|
80
|
+
captureState: 'metadata-only';
|
|
81
|
+
}>;
|
|
82
|
+
export type SandboxControlPayloadReference = Readonly<{
|
|
83
|
+
version: 1;
|
|
84
|
+
id: string;
|
|
85
|
+
generation: string;
|
|
86
|
+
stdoutBytes: number;
|
|
87
|
+
stderrBytes: number;
|
|
88
|
+
stdoutSha256: string;
|
|
89
|
+
stderrSha256: string;
|
|
90
|
+
}>;
|
|
91
|
+
export type SandboxControlPayload = SandboxControlPayloadReference & Readonly<{
|
|
92
|
+
stdout: string;
|
|
93
|
+
stderr: string;
|
|
94
|
+
}>;
|
|
95
|
+
export type SandboxControlReservation = Readonly<{
|
|
96
|
+
version: 1;
|
|
97
|
+
id: string;
|
|
98
|
+
generation: string;
|
|
99
|
+
logicalRecords: 1;
|
|
100
|
+
bytes: number;
|
|
101
|
+
createdAt: number;
|
|
102
|
+
}>;
|
|
66
103
|
export type SandboxControlResponse = Readonly<{
|
|
67
104
|
version: 2; id: string; phase: 'accepted' | 'completed' | 'rejected'; exitCode: number | null;
|
|
68
105
|
stdout: string; stderr: string; error: SandboxControlError | null;
|
|
106
|
+
outputState?: 'available' | 'unavailable';
|
|
107
|
+
payload?: SandboxControlPayloadReference | null;
|
|
69
108
|
}>;
|
|
70
109
|
export type SandboxControlStatus = Readonly<{
|
|
71
110
|
version: 2; generation: string; broker: ProcessIdentity & { brokerId: string };
|
|
@@ -97,6 +136,80 @@ function fail(code: string, message: string, retryable = false): never {
|
|
|
97
136
|
throw new SandboxControlProtocolError(code, message, retryable);
|
|
98
137
|
}
|
|
99
138
|
|
|
139
|
+
export function parseSandboxControlResultEvidence(value: unknown): SandboxControlResultEvidence {
|
|
140
|
+
const evidence = value as Partial<SandboxControlResultEvidence> | null;
|
|
141
|
+
const keys = evidence && typeof evidence === 'object' ? Object.keys(evidence).sort().join(',') : '';
|
|
142
|
+
if (keys !== 'captureState,exitCode,generation,id,stderrBytes,stderrSha256,stdoutBytes,stdoutSha256,version'
|
|
143
|
+
|| evidence?.version !== 1
|
|
144
|
+
|| typeof evidence.id !== 'string' || !/^[a-f0-9-]{16,64}$/u.test(evidence.id)
|
|
145
|
+
|| typeof evidence.generation !== 'string' || evidence.generation.length === 0
|
|
146
|
+
|| !Number.isSafeInteger(evidence.exitCode)
|
|
147
|
+
|| !Number.isSafeInteger(evidence.stdoutBytes) || (evidence.stdoutBytes as number) < 0
|
|
148
|
+
|| !Number.isSafeInteger(evidence.stderrBytes) || (evidence.stderrBytes as number) < 0
|
|
149
|
+
|| (evidence.stdoutBytes as number) + (evidence.stderrBytes as number) > SANDBOX_CONTROL_MAX_RESPONSE_BYTES
|
|
150
|
+
|| typeof evidence.stdoutSha256 !== 'string' || !/^[a-f0-9]{64}$/u.test(evidence.stdoutSha256)
|
|
151
|
+
|| typeof evidence.stderrSha256 !== 'string' || !/^[a-f0-9]{64}$/u.test(evidence.stderrSha256)
|
|
152
|
+
|| evidence.captureState !== 'metadata-only') {
|
|
153
|
+
throw new Error('SANDBOX_CONTROL_RESULT_EVIDENCE_INVALID');
|
|
154
|
+
}
|
|
155
|
+
return evidence as SandboxControlResultEvidence;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function validPayloadReference(value: unknown, id: string, generation: string): value is SandboxControlPayloadReference {
|
|
159
|
+
const payload = value as Partial<SandboxControlPayloadReference> | null;
|
|
160
|
+
const keys = payload && typeof payload === 'object' ? Object.keys(payload).sort().join(',') : '';
|
|
161
|
+
return keys === 'generation,id,stderrBytes,stderrSha256,stdoutBytes,stdoutSha256,version'
|
|
162
|
+
&& payload?.version === 1
|
|
163
|
+
&& payload.id === id
|
|
164
|
+
&& payload.generation === generation
|
|
165
|
+
&& Number.isSafeInteger(payload.stdoutBytes) && (payload.stdoutBytes as number) >= 0
|
|
166
|
+
&& Number.isSafeInteger(payload.stderrBytes) && (payload.stderrBytes as number) >= 0
|
|
167
|
+
&& (payload.stdoutBytes as number) + (payload.stderrBytes as number) <= SANDBOX_CONTROL_MAX_RESPONSE_BYTES
|
|
168
|
+
&& typeof payload.stdoutSha256 === 'string' && /^[a-f0-9]{64}$/u.test(payload.stdoutSha256)
|
|
169
|
+
&& typeof payload.stderrSha256 === 'string' && /^[a-f0-9]{64}$/u.test(payload.stderrSha256);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function parseSandboxControlPayload(value: unknown): SandboxControlPayload {
|
|
173
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('SANDBOX_CONTROL_PAYLOAD_INVALID');
|
|
174
|
+
const payload = value as Partial<SandboxControlPayload>;
|
|
175
|
+
const reference: Partial<SandboxControlPayloadReference> = {
|
|
176
|
+
version: payload.version,
|
|
177
|
+
id: payload.id,
|
|
178
|
+
generation: payload.generation,
|
|
179
|
+
stdoutBytes: payload.stdoutBytes,
|
|
180
|
+
stderrBytes: payload.stderrBytes,
|
|
181
|
+
stdoutSha256: payload.stdoutSha256,
|
|
182
|
+
stderrSha256: payload.stderrSha256
|
|
183
|
+
};
|
|
184
|
+
const keys = Object.keys(payload).sort().join(',');
|
|
185
|
+
if (keys !== 'generation,id,stderr,stderrBytes,stderrSha256,stdout,stdoutBytes,stdoutSha256,version'
|
|
186
|
+
|| !validPayloadReference(reference, reference.id as string, reference.generation as string)
|
|
187
|
+
|| typeof payload.stdout !== 'string' || typeof payload.stderr !== 'string'
|
|
188
|
+
|| Buffer.byteLength(payload.stdout, 'utf8') !== payload.stdoutBytes
|
|
189
|
+
|| Buffer.byteLength(payload.stderr, 'utf8') !== payload.stderrBytes
|
|
190
|
+
|| createHash('sha256').update(payload.stdout, 'utf8').digest('hex') !== payload.stdoutSha256
|
|
191
|
+
|| createHash('sha256').update(payload.stderr, 'utf8').digest('hex') !== payload.stderrSha256) {
|
|
192
|
+
throw new Error('SANDBOX_CONTROL_PAYLOAD_INVALID');
|
|
193
|
+
}
|
|
194
|
+
return payload as SandboxControlPayload;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export function parseSandboxControlReservation(value: unknown): SandboxControlReservation {
|
|
198
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('SANDBOX_CONTROL_RESERVATION_INVALID');
|
|
199
|
+
const reservation = value as Partial<SandboxControlReservation>;
|
|
200
|
+
const keys = Object.keys(reservation).sort().join(',');
|
|
201
|
+
if (keys !== 'bytes,createdAt,generation,id,logicalRecords,version'
|
|
202
|
+
|| reservation.version !== 1
|
|
203
|
+
|| typeof reservation.id !== 'string' || !/^[a-f0-9-]{16,64}$/u.test(reservation.id)
|
|
204
|
+
|| typeof reservation.generation !== 'string' || reservation.generation.length === 0
|
|
205
|
+
|| reservation.logicalRecords !== 1
|
|
206
|
+
|| !Number.isSafeInteger(reservation.bytes) || (reservation.bytes as number) < SANDBOX_CONTROL_RESERVATION_BYTES
|
|
207
|
+
|| !Number.isSafeInteger(reservation.createdAt)) {
|
|
208
|
+
throw new Error('SANDBOX_CONTROL_RESERVATION_INVALID');
|
|
209
|
+
}
|
|
210
|
+
return reservation as SandboxControlReservation;
|
|
211
|
+
}
|
|
212
|
+
|
|
100
213
|
export function isSandboxControlFamily(value: string): value is SandboxControlFamily {
|
|
101
214
|
return SANDBOX_CONTROL_FAMILIES.includes(value as SandboxControlFamily);
|
|
102
215
|
}
|