@fitlab-ai/agent-infra 0.9.9 → 0.9.11
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 -3
- package/README.zh-CN.md +13 -3
- package/bin/cli.ts +17 -4
- package/dist/bin/cli.js +19 -4
- package/dist/bin/internal-cli.js +55 -4
- package/dist/lib/agent-client.js +0 -1
- package/dist/lib/agent-clients/adapters/codex-lifecycle/build-identity.js +17 -10
- package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-context.js +11 -31
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +15 -33
- package/dist/lib/agent-clients/adapters/codex-sandbox.js +72 -11
- package/dist/lib/agent-clients/adapters/index.js +3 -1
- package/dist/lib/agent-clients/adapters/opencode-sandbox.js +0 -4
- package/dist/lib/agent-clients/adapters/traecli.js +65 -0
- package/dist/lib/agent-clients/config.js +14 -99
- package/dist/lib/agent-clients/reconcile.js +1 -11
- package/dist/lib/agent-clients/schema.js +5 -4
- package/dist/lib/agent-clients/tokens.js +7 -17
- package/dist/lib/agent-clients/types.js +2 -1
- package/dist/lib/decide.js +14 -30
- package/dist/lib/defaults.json +9 -0
- package/dist/lib/init.js +2 -2
- package/dist/lib/internal/codex-lifecycle.js +2 -2
- package/dist/lib/internal/codex-sandbox-controller.js +4 -3
- package/dist/lib/internal/release-workflow.js +0 -1
- package/dist/lib/internal/sandbox-control.js +2 -2
- package/dist/lib/internal/task-finalization.js +19 -9
- package/dist/lib/internal/task-ledger.js +8 -2
- package/dist/lib/internal/task-lifecycle.js +17 -93
- package/dist/lib/internal/task-orchestration.js +30 -216
- package/dist/lib/{task/commands/validate.js → internal/task-validate.js} +77 -43
- package/dist/lib/merge.js +205 -103
- package/dist/lib/process-data/normalize.js +2 -9
- package/dist/lib/run/host.js +46 -16
- package/dist/lib/run/tui.js +5 -2
- package/dist/lib/sandbox/agent-client-reconciler.js +4 -12
- package/dist/lib/sandbox/commands/create.js +3 -3
- package/dist/lib/sandbox/commands/enter.js +18 -4
- package/dist/lib/sandbox/config.js +3 -12
- package/dist/lib/sandbox/control/controller-registration.js +4 -2
- package/dist/lib/sandbox/control/executor.js +135 -40
- package/dist/lib/sandbox/control/lifecycle.js +29 -20
- package/dist/lib/sandbox/control/server.js +18 -16
- package/dist/lib/sandbox/mounts.js +3 -2
- package/dist/lib/sandbox/recovery.js +123 -22
- package/dist/lib/sandbox/workspace-identity.js +14 -0
- package/dist/lib/sandbox/workspace-view.js +8 -9
- package/dist/lib/self-update.js +278 -0
- package/dist/lib/server/protocol.js +5 -5
- package/dist/lib/task/activity-log.js +4 -1
- package/dist/lib/task/artifact-lifecycle.js +4 -2
- package/dist/lib/task/codex-orchestration.js +32 -35
- package/dist/lib/task/command-options.js +1 -3
- package/dist/lib/task/commands/cat.js +0 -5
- package/dist/lib/task/commands/decisions.js +76 -92
- package/dist/lib/task/commands/files.js +6 -6
- package/dist/lib/task/commands/grep.js +25 -61
- package/dist/lib/task/commands/issue-body.js +6 -6
- package/dist/lib/task/commands/log.js +25 -10
- package/dist/lib/task/commands/show.js +5 -6
- package/dist/lib/task/commands/status.js +8 -8
- package/dist/lib/task/commit-operation.js +21 -25
- package/dist/lib/task/commit-policy.js +1 -1
- package/dist/lib/task/control-authority.js +431 -0
- package/dist/lib/task/create.js +6 -1
- package/dist/lib/task/current-contract.js +35 -0
- package/dist/lib/task/decision-details.js +263 -0
- package/dist/lib/task/decision-intents.js +5 -2
- package/dist/lib/task/delegation-receipts.js +8 -7
- package/dist/lib/task/events.js +11 -5
- package/dist/lib/task/finalization.js +72 -46
- package/dist/lib/task/index.js +19 -29
- package/dist/lib/task/ledger-intents.js +8 -9
- package/dist/lib/task/ledger.js +13 -6
- package/dist/lib/task/lifecycle.js +16 -0
- package/dist/lib/task/orchestration.js +51 -14
- package/dist/lib/task/review-artifacts.js +31 -12
- package/dist/lib/task/review-finalization.js +19 -6
- package/dist/lib/task/verification-engine.js +38 -18
- package/dist/lib/task/verification-review.js +1 -1
- package/dist/lib/task/workspace-snapshot.js +5 -11
- package/dist/lib/task/write.js +11 -1
- package/dist/lib/update.js +6 -37
- package/dist/lib/version.js +5 -1
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-client.ts +0 -1
- package/lib/agent-clients/adapters/codex-lifecycle/build-identity.ts +25 -10
- package/lib/agent-clients/adapters/codex-lifecycle/controller-context.ts +16 -33
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +16 -35
- package/lib/agent-clients/adapters/codex-sandbox.ts +89 -14
- package/lib/agent-clients/adapters/index.ts +3 -1
- package/lib/agent-clients/adapters/opencode-sandbox.ts +0 -5
- package/lib/agent-clients/adapters/traecli.ts +66 -0
- package/lib/agent-clients/config.ts +14 -144
- package/lib/agent-clients/reconcile.ts +1 -12
- package/lib/agent-clients/schema.ts +5 -4
- package/lib/agent-clients/tokens.ts +7 -20
- package/lib/agent-clients/types.ts +2 -1
- package/lib/decide.ts +12 -22
- package/lib/defaults.json +9 -0
- package/lib/init.ts +2 -2
- package/lib/internal/codex-lifecycle.ts +2 -2
- package/lib/internal/codex-sandbox-controller.ts +4 -3
- package/lib/internal/release-workflow.ts +0 -1
- package/lib/internal/sandbox-control.ts +2 -2
- package/lib/internal/task-finalization.ts +27 -13
- package/lib/internal/task-ledger.ts +8 -2
- package/lib/internal/task-lifecycle.ts +33 -76
- package/lib/internal/task-orchestration.ts +33 -190
- package/lib/{task/commands/validate.ts → internal/task-validate.ts} +77 -41
- package/lib/merge.ts +236 -120
- package/lib/process-data/normalize.ts +2 -8
- package/lib/run/host.ts +63 -14
- package/lib/run/tui.ts +5 -3
- package/lib/sandbox/agent-client-reconciler.ts +5 -15
- package/lib/sandbox/commands/create.ts +4 -4
- package/lib/sandbox/commands/enter.ts +24 -4
- package/lib/sandbox/config.ts +2 -14
- package/lib/sandbox/control/container-identity.ts +2 -2
- package/lib/sandbox/control/controller-registration.ts +8 -3
- package/lib/sandbox/control/executor.ts +156 -53
- package/lib/sandbox/control/lifecycle.ts +34 -28
- package/lib/sandbox/control/protocol.ts +1 -3
- package/lib/sandbox/control/server.ts +15 -12
- package/lib/sandbox/control/state.ts +7 -7
- package/lib/sandbox/mounts.ts +4 -3
- package/lib/sandbox/recovery.ts +162 -34
- package/lib/sandbox/tools.ts +1 -1
- package/lib/sandbox/workspace-identity.ts +28 -0
- package/lib/sandbox/workspace-view.ts +14 -10
- package/lib/self-update.ts +341 -0
- package/lib/server/protocol.ts +5 -5
- package/lib/task/activity-log.ts +6 -1
- package/lib/task/artifact-lifecycle.ts +5 -3
- package/lib/task/codex-orchestration.ts +40 -45
- package/lib/task/command-options.ts +1 -2
- package/lib/task/commands/cat.ts +0 -4
- package/lib/task/commands/decisions.ts +80 -75
- package/lib/task/commands/files.ts +6 -6
- package/lib/task/commands/grep.ts +23 -51
- package/lib/task/commands/issue-body.ts +6 -6
- package/lib/task/commands/log.ts +22 -10
- package/lib/task/commands/show.ts +5 -6
- package/lib/task/commands/status.ts +8 -8
- package/lib/task/commit-operation.ts +25 -30
- package/lib/task/commit-policy.ts +1 -2
- package/lib/task/control-authority.ts +563 -0
- package/lib/task/create.ts +6 -1
- package/lib/task/current-contract.ts +49 -0
- package/lib/task/decision-details.ts +340 -0
- package/lib/task/decision-intents.ts +7 -2
- package/lib/task/delegation-receipts.ts +8 -6
- package/lib/task/events.ts +10 -5
- package/lib/task/finalization.ts +84 -42
- package/lib/task/index.ts +19 -28
- package/lib/task/ledger-intents.ts +6 -8
- package/lib/task/ledger.ts +17 -7
- package/lib/task/lifecycle.ts +20 -0
- package/lib/task/orchestration.ts +42 -16
- package/lib/task/review-artifacts.ts +53 -15
- package/lib/task/review-finalization.ts +33 -7
- package/lib/task/verification-engine.ts +33 -21
- package/lib/task/verification-review.ts +1 -1
- package/lib/task/workspace-snapshot.ts +4 -13
- package/lib/task/write.ts +20 -1
- package/lib/update.ts +6 -40
- package/lib/version.ts +6 -1
- package/package.json +1 -1
- package/templates/.agents/QUICKSTART.en.md +2 -3
- package/templates/.agents/QUICKSTART.zh-CN.md +2 -3
- package/templates/.agents/README.en.md +5 -3
- package/templates/.agents/README.zh-CN.md +5 -3
- package/templates/.agents/rules/README.en.md +1 -0
- package/templates/.agents/rules/README.zh-CN.md +1 -0
- package/templates/.agents/rules/compatibility-policy.en.md +40 -0
- package/templates/.agents/rules/compatibility-policy.zh-CN.md +40 -0
- package/templates/.agents/rules/create-issue.github.en.md +1 -1
- package/templates/.agents/rules/create-issue.github.zh-CN.md +1 -1
- package/templates/.agents/rules/human-decision-context.en.md +9 -7
- package/templates/.agents/rules/human-decision-context.zh-CN.md +9 -7
- package/templates/.agents/rules/issue-fields.github.en.md +1 -1
- package/templates/.agents/rules/issue-fields.github.zh-CN.md +1 -1
- package/templates/.agents/rules/issue-pr-commands.en.md +1 -1
- package/templates/.agents/rules/issue-pr-commands.github.en.md +1 -1
- package/templates/.agents/rules/issue-pr-commands.github.zh-CN.md +1 -1
- package/templates/.agents/rules/issue-pr-commands.zh-CN.md +1 -1
- package/templates/.agents/rules/issue-sync.github.en.md +1 -1
- package/templates/.agents/rules/issue-sync.github.zh-CN.md +2 -2
- package/templates/.agents/rules/lifecycle-orchestration.en.md +4 -4
- package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +4 -4
- package/templates/.agents/rules/milestone-inference.github.en.md +1 -1
- package/templates/.agents/rules/milestone-inference.github.zh-CN.md +1 -1
- package/templates/.agents/rules/next-step-output.en.md +3 -3
- package/templates/.agents/rules/next-step-output.zh-CN.md +3 -3
- package/templates/.agents/rules/no-mid-flow-questions.en.md +1 -1
- package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +1 -1
- package/templates/.agents/rules/pr-sync.en.md +2 -2
- package/templates/.agents/rules/pr-sync.github.en.md +3 -2
- package/templates/.agents/rules/pr-sync.github.zh-CN.md +3 -1
- package/templates/.agents/rules/pr-sync.zh-CN.md +2 -1
- package/templates/.agents/rules/release-commands.github.en.md +6 -0
- package/templates/.agents/rules/release-commands.github.zh-CN.md +6 -0
- package/templates/.agents/rules/review-handshake.en.md +1 -1
- package/templates/.agents/rules/review-handshake.zh-CN.md +1 -1
- package/templates/.agents/rules/review-method.en.md +1 -0
- package/templates/.agents/rules/review-method.zh-CN.md +1 -0
- package/templates/.agents/rules/task-management.en.md +1 -2
- package/templates/.agents/rules/task-management.zh-CN.md +1 -2
- package/templates/.agents/skills/analyze-task/SKILL.en.md +4 -3
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +4 -3
- 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/cancel-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/check-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/check-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/close-codescan/SKILL.en.md +1 -1
- package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/close-dependabot/SKILL.en.md +1 -1
- package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/code-task/SKILL.en.md +4 -3
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +5 -3
- package/templates/.agents/skills/commit/SKILL.en.md +27 -7
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +3 -4
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +5 -5
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +5 -5
- 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/create-pr/SKILL.en.md +15 -11
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +15 -11
- package/templates/.agents/skills/create-pr/reference/change-report.en.md +46 -0
- package/templates/.agents/skills/create-pr/reference/change-report.zh-CN.md +46 -0
- package/templates/.agents/skills/create-pr/scripts/change-report.mjs +203 -0
- package/templates/.agents/skills/create-release-note/SKILL.en.md +5 -6
- package/templates/.agents/skills/create-release-note/SKILL.zh-CN.md +5 -6
- package/templates/.agents/skills/create-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/import-issue/SKILL.en.md +1 -1
- package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/plan-task/SKILL.en.md +5 -4
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +5 -4
- package/templates/.agents/skills/restore-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/review-analysis/SKILL.en.md +5 -3
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +5 -3
- package/templates/.agents/skills/review-analysis/config/verify.en.json +1 -0
- package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +1 -0
- package/templates/.agents/skills/review-code/SKILL.en.md +5 -3
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +5 -3
- package/templates/.agents/skills/review-code/config/verify.en.json +1 -0
- package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -0
- package/templates/.agents/skills/review-code/reference/migration-risks.en.md +9 -6
- package/templates/.agents/skills/review-code/reference/migration-risks.zh-CN.md +9 -6
- package/templates/.agents/skills/review-plan/SKILL.en.md +5 -3
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +5 -3
- package/templates/.agents/skills/review-plan/config/verify.en.json +1 -0
- package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +1 -0
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +7 -3
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +7 -3
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +1 -1
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +1 -1
- package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +2 -2
- package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +2 -2
- package/templates/.agents/skills/run-task/SKILL.en.md +6 -2
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +6 -2
- package/templates/.agents/skills/run-task/reference/host-validation.en.md +2 -2
- package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +2 -2
- package/templates/.agents/skills/update-agent-infra/SKILL.en.md +2 -2
- package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +159 -104
- package/templates/.agents/skills/watch-pr/SKILL.en.md +2 -2
- package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +2 -2
- package/templates/.agents/templates/task.en.md +1 -1
- package/templates/.agents/templates/task.zh-CN.md +1 -1
- package/templates/.claude/commands/analyze-task.en.md +1 -1
- package/templates/.claude/commands/analyze-task.zh-CN.md +1 -1
- package/templates/.claude/commands/block-task.en.md +1 -1
- package/templates/.claude/commands/block-task.zh-CN.md +1 -1
- package/templates/.claude/commands/cancel-task.en.md +1 -1
- package/templates/.claude/commands/cancel-task.zh-CN.md +1 -1
- package/templates/.claude/commands/check-task.en.md +1 -1
- package/templates/.claude/commands/check-task.zh-CN.md +1 -1
- package/templates/.claude/commands/code-task.en.md +1 -1
- package/templates/.claude/commands/code-task.zh-CN.md +1 -1
- package/templates/.claude/commands/commit.en.md +1 -1
- package/templates/.claude/commands/commit.zh-CN.md +1 -1
- package/templates/.claude/commands/complete-manual-validation.en.md +1 -1
- package/templates/.claude/commands/complete-manual-validation.zh-CN.md +1 -1
- package/templates/.claude/commands/complete-task.en.md +1 -1
- package/templates/.claude/commands/complete-task.zh-CN.md +1 -1
- package/templates/.claude/commands/create-pr.en.md +1 -1
- package/templates/.claude/commands/create-pr.zh-CN.md +1 -1
- package/templates/.claude/commands/plan-task.en.md +1 -1
- package/templates/.claude/commands/plan-task.zh-CN.md +1 -1
- package/templates/.claude/commands/review-analysis.en.md +1 -1
- package/templates/.claude/commands/review-analysis.zh-CN.md +1 -1
- package/templates/.claude/commands/review-code.en.md +1 -1
- package/templates/.claude/commands/review-code.zh-CN.md +1 -1
- package/templates/.claude/commands/review-plan.en.md +1 -1
- package/templates/.claude/commands/review-plan.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/templates/.claude/commands/run-task.en.md +1 -1
- package/templates/.claude/commands/run-task.zh-CN.md +1 -1
- package/templates/.claude/commands/watch-pr.en.md +1 -1
- package/templates/.claude/commands/watch-pr.zh-CN.md +1 -1
- package/templates/.traecli/commands/update-agent-infra.en.md +7 -0
- package/templates/.traecli/commands/update-agent-infra.zh-CN.md +7 -0
- package/dist/lib/builtin-tuis.js +0 -40
- package/lib/builtin-tuis.ts +0 -56
|
@@ -3,7 +3,9 @@ import {
|
|
|
3
3
|
assertValidBranchName,
|
|
4
4
|
containerNameCandidates,
|
|
5
5
|
sandboxBranchLabel,
|
|
6
|
-
sandboxLabel
|
|
6
|
+
sandboxLabel,
|
|
7
|
+
sandboxTaskIdLabel,
|
|
8
|
+
sandboxWorkspaceModeLabel
|
|
7
9
|
} from '../constants.ts';
|
|
8
10
|
import { detectEngine } from '../engine.ts';
|
|
9
11
|
import { ensureSandboxReady } from '../recovery.ts';
|
|
@@ -21,7 +23,10 @@ import {
|
|
|
21
23
|
fetchSandboxRows,
|
|
22
24
|
selectSandboxContainer,
|
|
23
25
|
} from './list-running.ts';
|
|
24
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
resolveSandboxReentryContext,
|
|
28
|
+
resolveSandboxTarget
|
|
29
|
+
} from '../workspace-identity.ts';
|
|
25
30
|
|
|
26
31
|
const USAGE = `Usage: ai sandbox exec [--recreate] <branch | TASK-id | N> [cmd...]
|
|
27
32
|
|
|
@@ -128,7 +133,11 @@ export async function enter(args: string[]): Promise<number> {
|
|
|
128
133
|
const { running, nonRunning } = fetchSandboxRows(
|
|
129
134
|
engine,
|
|
130
135
|
sandboxLabel(config),
|
|
131
|
-
sandboxBranchLabel(config)
|
|
136
|
+
sandboxBranchLabel(config),
|
|
137
|
+
{
|
|
138
|
+
mode: sandboxWorkspaceModeLabel(config),
|
|
139
|
+
taskId: sandboxTaskIdLabel(config)
|
|
140
|
+
}
|
|
132
141
|
);
|
|
133
142
|
const found = selectSandboxContainer(
|
|
134
143
|
[...running, ...nonRunning],
|
|
@@ -140,11 +149,22 @@ export async function enter(args: string[]): Promise<number> {
|
|
|
140
149
|
`No sandbox found for branch '${branch}'. Run 'ai sandbox create ${branch}' to create one.`
|
|
141
150
|
);
|
|
142
151
|
}
|
|
152
|
+
const containerWorkspace = found.workspaceMode === 'task-bound' && found.taskId
|
|
153
|
+
? { mode: 'task-bound' as const, taskId: found.taskId }
|
|
154
|
+
: found.workspaceMode === 'branch-only'
|
|
155
|
+
? { mode: 'branch-only' as const }
|
|
156
|
+
: { mode: 'legacy-invalid' as const };
|
|
157
|
+
const reentry = resolveSandboxReentryContext({
|
|
158
|
+
target,
|
|
159
|
+
containerWorkspace,
|
|
160
|
+
repoRoot: config.repoRoot
|
|
161
|
+
});
|
|
143
162
|
const ready = await ensureSandboxReady({
|
|
144
163
|
config,
|
|
145
164
|
engine,
|
|
146
165
|
branch,
|
|
147
|
-
workspace:
|
|
166
|
+
workspace: reentry.workspace,
|
|
167
|
+
reentry: reentry.reentry,
|
|
148
168
|
row: found,
|
|
149
169
|
allowRecreate: parsed.recreate,
|
|
150
170
|
recreate: async () => {
|
package/lib/sandbox/config.ts
CHANGED
|
@@ -4,7 +4,6 @@ import { homedir, platform } from 'node:os';
|
|
|
4
4
|
import { execFileSync } from 'node:child_process';
|
|
5
5
|
import pc from 'picocolors';
|
|
6
6
|
import { normalizeAgentClients } from '../agent-clients/config.ts';
|
|
7
|
-
import { isAgentClientId } from '../agent-clients/types.ts';
|
|
8
7
|
import type { AgentClientState } from '../agent-clients/types.ts';
|
|
9
8
|
import { validateSandboxEngine } from './engine.ts';
|
|
10
9
|
import type { SandboxEngineInput } from './engine.ts';
|
|
@@ -64,7 +63,6 @@ export type SandboxConfig = {
|
|
|
64
63
|
tools: string[];
|
|
65
64
|
customTools: SandboxTool[];
|
|
66
65
|
agentClientState: AgentClientState;
|
|
67
|
-
agentClientSource: 'canonical' | 'legacy';
|
|
68
66
|
refreshIntervalDays: number;
|
|
69
67
|
dockerfile: string | null;
|
|
70
68
|
vm: SandboxVmConfig;
|
|
@@ -74,7 +72,6 @@ type AircConfig = {
|
|
|
74
72
|
project?: unknown;
|
|
75
73
|
org?: unknown;
|
|
76
74
|
agentClients?: unknown;
|
|
77
|
-
tuis?: unknown;
|
|
78
75
|
sandbox?: SandboxConfigInput;
|
|
79
76
|
};
|
|
80
77
|
|
|
@@ -110,17 +107,10 @@ function cloneDefaults(): SandboxConfigInput & { vm: SandboxVmConfig; runtimes:
|
|
|
110
107
|
|
|
111
108
|
function resolveSandboxToolIds(
|
|
112
109
|
sandbox: SandboxConfigInput,
|
|
113
|
-
agentClients: ReturnType<typeof normalizeAgentClients>,
|
|
114
110
|
defaults: readonly string[]
|
|
115
111
|
): string[] {
|
|
116
112
|
if (Array.isArray(sandbox.tools)) {
|
|
117
|
-
|
|
118
|
-
return agentClients.source === 'canonical'
|
|
119
|
-
? tools.filter((tool) => !isAgentClientId(tool))
|
|
120
|
-
: tools;
|
|
121
|
-
}
|
|
122
|
-
if (agentClients.remainingSandboxTools !== undefined) {
|
|
123
|
-
return [...agentClients.remainingSandboxTools];
|
|
113
|
+
return sandbox.tools.filter((tool): tool is string => typeof tool === 'string');
|
|
124
114
|
}
|
|
125
115
|
return [...defaults];
|
|
126
116
|
}
|
|
@@ -142,7 +132,6 @@ export function loadConfig({
|
|
|
142
132
|
}
|
|
143
133
|
|
|
144
134
|
const airc = JSON.parse(fs.readFileSync(configPath, 'utf8')) as AircConfig;
|
|
145
|
-
const sandboxInput = airc.sandbox;
|
|
146
135
|
const agentClients = normalizeAgentClients(airc);
|
|
147
136
|
const defaults = cloneDefaults();
|
|
148
137
|
const sandbox = airc.sandbox ?? {};
|
|
@@ -178,7 +167,7 @@ export function loadConfig({
|
|
|
178
167
|
}
|
|
179
168
|
|
|
180
169
|
const customTools = parseCustomTools(sandbox.customTools, { home });
|
|
181
|
-
const tools = resolveSandboxToolIds(sandbox,
|
|
170
|
+
const tools = resolveSandboxToolIds(sandbox, defaults.tools);
|
|
182
171
|
|
|
183
172
|
return {
|
|
184
173
|
repoRoot,
|
|
@@ -199,7 +188,6 @@ export function loadConfig({
|
|
|
199
188
|
tools,
|
|
200
189
|
customTools,
|
|
201
190
|
agentClientState: agentClients.state,
|
|
202
|
-
agentClientSource: agentClients.source,
|
|
203
191
|
refreshIntervalDays: asNonNegativeIntegerOrDefault(
|
|
204
192
|
sandbox.refreshIntervalDays,
|
|
205
193
|
defaults.refreshIntervalDays
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { SandboxControlManifest
|
|
1
|
+
import type { SandboxControlManifest } from './protocol.ts';
|
|
2
2
|
import { commandForEngine, runEngine, runProbe } from '../shell.ts';
|
|
3
3
|
|
|
4
4
|
export type ContainerObservation =
|
|
@@ -48,7 +48,7 @@ export function classifySandboxContainerInspection(
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
export async function inspectSandboxControlContainer(
|
|
51
|
-
manifest:
|
|
51
|
+
manifest: SandboxControlManifest,
|
|
52
52
|
options: { run?: typeof runEngine; probe?: typeof runProbe; timeoutMs?: number } = {}
|
|
53
53
|
): Promise<ContainerObservation> {
|
|
54
54
|
try {
|
|
@@ -188,10 +188,12 @@ function assertRegistrationBinding(
|
|
|
188
188
|
): void {
|
|
189
189
|
if (registration.taskId !== manifest.taskId
|
|
190
190
|
|| registration.controlGeneration !== manifest.generation
|
|
191
|
-
|| registration.containerId !== manifest.containerIdentity.id
|
|
192
|
-
|| JSON.stringify(registration.buildIdentity) !== JSON.stringify(buildIdentity)) {
|
|
191
|
+
|| registration.containerId !== manifest.containerIdentity.id) {
|
|
193
192
|
fail('CODEX_SANDBOX_CONTROLLER_REGISTRATION_INVALID', 'controller registration binding is invalid');
|
|
194
193
|
}
|
|
194
|
+
if (registration.buildIdentity.protocolVersion !== buildIdentity.protocolVersion) {
|
|
195
|
+
fail('CODEX_LIFECYCLE_PROTOCOL_MISMATCH', 'Codex lifecycle protocol version does not match');
|
|
196
|
+
}
|
|
195
197
|
}
|
|
196
198
|
|
|
197
199
|
function atomicWrite(file: string, value: CodexControllerRegistrationV1): void {
|
|
@@ -322,7 +324,10 @@ export function resolveCodexControllerBinding(params: Readonly<{
|
|
|
322
324
|
buildIdentity: LifecycleBuildIdentity;
|
|
323
325
|
now?: number;
|
|
324
326
|
probeProcess?: (identity: ProcessIdentity) => ProcessIdentityState;
|
|
325
|
-
}>): Readonly<{
|
|
327
|
+
}>): Readonly<{
|
|
328
|
+
instanceDigest: string;
|
|
329
|
+
controlGeneration: string;
|
|
330
|
+
}> {
|
|
326
331
|
if (params.manifest.mode !== 'task-bound' || !params.manifest.taskId) {
|
|
327
332
|
fail('SANDBOX_CONTROL_BRANCH_ONLY', 'branch-only sandboxes cannot resolve a Codex controller registration');
|
|
328
333
|
}
|
|
@@ -1,13 +1,19 @@
|
|
|
1
|
-
import { spawn,
|
|
1
|
+
import { spawn, type ChildProcess } from 'node:child_process';
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { randomUUID } from 'node:crypto';
|
|
5
5
|
import { getProcessStartTime } from '../../server/process-state.ts';
|
|
6
6
|
import { createTask } from '../../task/create-service.ts';
|
|
7
7
|
import { applyTaskFinalization } from '../../task/finalization.ts';
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
8
|
+
import { bindSandboxControlTask, validateSandboxControlRequest, type SandboxControlExecution, type SandboxControlManifest, type SandboxControlRequest } from './protocol.ts';
|
|
9
|
+
import { atomicWriteJson, executionPath, readActiveLease, terminateSandboxControlExecution } from './state.ts';
|
|
10
|
+
import {
|
|
11
|
+
createSandboxExecutorExecutionContext,
|
|
12
|
+
dispatchTaskControlOperation,
|
|
13
|
+
parseTaskControlOperation,
|
|
14
|
+
type TaskControlOperation
|
|
15
|
+
} from '../../task/control-authority.ts';
|
|
16
|
+
import { assertSandboxControlBrokerOwner, readSandboxControlManifest, type BrokerOwner } from './lifecycle.ts';
|
|
11
17
|
import { computeLifecycleBuildIdentity } from '../../agent-clients/adapters/codex-lifecycle/build-identity.ts';
|
|
12
18
|
import {
|
|
13
19
|
closeCodexControllerRegistration,
|
|
@@ -29,6 +35,26 @@ export type PreparedSandboxControlExecution = {
|
|
|
29
35
|
terminate(updateState?: boolean): boolean;
|
|
30
36
|
};
|
|
31
37
|
|
|
38
|
+
function sameBrokerOwner(left: BrokerOwner, right: BrokerOwner): boolean {
|
|
39
|
+
return left.version === right.version
|
|
40
|
+
&& left.pid === right.pid
|
|
41
|
+
&& left.startTime === right.startTime
|
|
42
|
+
&& left.brokerId === right.brokerId
|
|
43
|
+
&& left.token === right.token
|
|
44
|
+
&& left.generation === right.generation;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function assertSandboxControlExecutorAuthority(
|
|
48
|
+
manifest: SandboxControlManifest,
|
|
49
|
+
gateOwner?: BrokerOwner
|
|
50
|
+
): void {
|
|
51
|
+
const owner = assertSandboxControlBrokerOwner(manifest);
|
|
52
|
+
if (gateOwner && !sameBrokerOwner(owner, gateOwner)) {
|
|
53
|
+
throw new Error('SANDBOX_CONTROL_GATE_OWNER_MISMATCH');
|
|
54
|
+
}
|
|
55
|
+
if (readActiveLease(manifest)) throw new Error('SANDBOX_CONTROL_HANDOFF_ACTIVE');
|
|
56
|
+
}
|
|
57
|
+
|
|
32
58
|
function safeEnv(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
|
|
33
59
|
return Object.fromEntries(Object.entries(env).filter(([key]) => !key.toUpperCase().startsWith('AGENT_INFRA_CONTROL_')));
|
|
34
60
|
}
|
|
@@ -104,9 +130,11 @@ export async function prepareSandboxControlExecution(params: {
|
|
|
104
130
|
start(canWrite = () => true) {
|
|
105
131
|
if (!child.connected) throw new Error('SANDBOX_CONTROL_EXECUTOR_GATE_CLOSED');
|
|
106
132
|
if (!canWrite()) throw new Error('SANDBOX_CONTROL_OWNER_LOST');
|
|
133
|
+
const gateOwner = assertSandboxControlBrokerOwner(params.manifest);
|
|
134
|
+
if (!canWrite()) throw new Error('SANDBOX_CONTROL_OWNER_LOST');
|
|
107
135
|
atomicWriteJson(executionPath(params.manifest, params.request.id), { ...execution, phase: 'running', updatedAt: Date.now() });
|
|
108
136
|
if (!canWrite()) throw new Error('SANDBOX_CONTROL_OWNER_LOST');
|
|
109
|
-
child.send({ version: 1, nonce });
|
|
137
|
+
child.send({ version: 1, nonce, owner: gateOwner });
|
|
110
138
|
},
|
|
111
139
|
completion,
|
|
112
140
|
terminate(updateState = true) {
|
|
@@ -120,7 +148,7 @@ export async function prepareSandboxControlExecution(params: {
|
|
|
120
148
|
};
|
|
121
149
|
}
|
|
122
150
|
|
|
123
|
-
function waitForGate(nonce: string, timeoutMs = 2_000): Promise<
|
|
151
|
+
function waitForGate(nonce: string, timeoutMs = 2_000): Promise<BrokerOwner> {
|
|
124
152
|
return new Promise((resolve, reject) => {
|
|
125
153
|
const timer = setTimeout(() => reject(new Error('SANDBOX_CONTROL_EXECUTOR_GATE_TIMEOUT')), timeoutMs);
|
|
126
154
|
const onDisconnect = () => {
|
|
@@ -131,12 +159,21 @@ function waitForGate(nonce: string, timeoutMs = 2_000): Promise<void> {
|
|
|
131
159
|
process.once('message', (message: unknown) => {
|
|
132
160
|
clearTimeout(timer);
|
|
133
161
|
process.off('disconnect', onDisconnect);
|
|
134
|
-
const value = message as {
|
|
135
|
-
|
|
162
|
+
const value = message as {
|
|
163
|
+
version?: unknown;
|
|
164
|
+
nonce?: unknown;
|
|
165
|
+
owner?: Partial<BrokerOwner> | null;
|
|
166
|
+
} | null;
|
|
167
|
+
const owner = value?.owner;
|
|
168
|
+
if (!value || value.version !== 1 || value.nonce !== nonce
|
|
169
|
+
|| !owner || owner.version !== 3 || !Number.isSafeInteger(owner.pid) || (owner.pid ?? 0) <= 0
|
|
170
|
+
|| typeof owner.startTime !== 'number' || !Number.isSafeInteger(owner.startTime)
|
|
171
|
+
|| typeof owner.brokerId !== 'string' || owner.brokerId.length === 0
|
|
172
|
+
|| typeof owner.token !== 'string' || typeof owner.generation !== 'string') {
|
|
136
173
|
reject(new Error('SANDBOX_CONTROL_EXECUTOR_GATE_INVALID'));
|
|
137
174
|
return;
|
|
138
175
|
}
|
|
139
|
-
resolve();
|
|
176
|
+
resolve(owner as BrokerOwner);
|
|
140
177
|
});
|
|
141
178
|
});
|
|
142
179
|
}
|
|
@@ -146,12 +183,15 @@ function controllerFailure(error: unknown): SandboxControlExecutionResult {
|
|
|
146
183
|
? error.code
|
|
147
184
|
: /^([A-Z][A-Z0-9_]+)/u.exec(error instanceof Error ? error.message : String(error))?.[1]
|
|
148
185
|
?? 'CODEX_SANDBOX_CONTROLLER_FAILED';
|
|
186
|
+
const message = error instanceof CodexControllerRegistrationError
|
|
187
|
+
? error.message
|
|
188
|
+
: `${code}: controller operation failed; inspect the sandbox controller and rebuild the sandbox if needed`;
|
|
149
189
|
const payload = {
|
|
150
190
|
version: 1,
|
|
151
191
|
status: 'failed',
|
|
152
192
|
changed: false,
|
|
153
193
|
lease: null,
|
|
154
|
-
error: { code, message
|
|
194
|
+
error: { code, message, retryable: false }
|
|
155
195
|
};
|
|
156
196
|
return { exitCode: 1, stdout: `${JSON.stringify(payload)}\n`, stderr: '' };
|
|
157
197
|
}
|
|
@@ -167,6 +207,17 @@ function orchestrationFailure(code: string, message: string): SandboxControlExec
|
|
|
167
207
|
};
|
|
168
208
|
}
|
|
169
209
|
|
|
210
|
+
function lifecycleFailure(code: string, message: string): SandboxControlExecutionResult {
|
|
211
|
+
return {
|
|
212
|
+
exitCode: 1,
|
|
213
|
+
stdout: `${JSON.stringify({
|
|
214
|
+
status: 'failed', changed: false,
|
|
215
|
+
error: { code, message }
|
|
216
|
+
})}\n`,
|
|
217
|
+
stderr: ''
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
170
221
|
function finalizationResult(result: ReturnType<typeof applyTaskFinalization>): SandboxControlExecutionResult {
|
|
171
222
|
const payload = {
|
|
172
223
|
version: 1,
|
|
@@ -183,28 +234,17 @@ function finalizationResult(result: ReturnType<typeof applyTaskFinalization>): S
|
|
|
183
234
|
};
|
|
184
235
|
}
|
|
185
236
|
|
|
186
|
-
function isCodexPrepare(args: readonly string[]): boolean {
|
|
187
|
-
if (args[1] !== 'prepare') return false;
|
|
188
|
-
const values: string[] = [];
|
|
189
|
-
for (let index = 2; index < args.length; index += 1) {
|
|
190
|
-
if (args[index] === '--client') values.push(args[index + 1] ?? '');
|
|
191
|
-
else if (args[index]?.startsWith('--client=')) values.push(args[index]!.slice('--client='.length));
|
|
192
|
-
}
|
|
193
|
-
return values.length === 1 && values[0] === 'codex';
|
|
194
|
-
}
|
|
195
|
-
|
|
196
237
|
type ExecuteRequestOptions = Readonly<{
|
|
197
238
|
buildIdentity?: typeof computeLifecycleBuildIdentity;
|
|
198
239
|
resolveControllerBinding?: typeof resolveCodexControllerBinding;
|
|
199
|
-
spawnDomain?: typeof spawnSync;
|
|
200
240
|
}>;
|
|
201
241
|
|
|
202
|
-
export function executeRequest(
|
|
242
|
+
export async function executeRequest(
|
|
203
243
|
manifest: SandboxControlManifest,
|
|
204
244
|
manifestPath: string,
|
|
205
245
|
request: SandboxControlRequest,
|
|
206
246
|
options: ExecuteRequestOptions = {}
|
|
207
|
-
): SandboxControlExecutionResult {
|
|
247
|
+
): Promise<SandboxControlExecutionResult> {
|
|
208
248
|
if (request.family === 'task-create') {
|
|
209
249
|
const result = createTask(request.candidate, { repoRoot: manifest.repoRoot });
|
|
210
250
|
return {
|
|
@@ -257,17 +297,47 @@ export function executeRequest(
|
|
|
257
297
|
}
|
|
258
298
|
}
|
|
259
299
|
if (request.family === 'task-finalization') {
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
300
|
+
const operation = parseTaskControlOperation(
|
|
301
|
+
'task-finalization', [manifest.taskId!, 'complete', '--agent', request.agent]
|
|
302
|
+
);
|
|
303
|
+
if (operation.family !== 'task-finalization') throw new Error('SANDBOX_CONTROL_FINALIZATION_OPERATION_INVALID');
|
|
304
|
+
const result = dispatchTaskControlOperation(
|
|
305
|
+
createSandboxExecutorExecutionContext({
|
|
306
|
+
repoRoot: manifest.repoRoot,
|
|
307
|
+
worktreeRoot: manifest.worktreeRoot,
|
|
308
|
+
runtimeDir: manifest.runtimeDir,
|
|
309
|
+
taskId: manifest.taskId!,
|
|
310
|
+
generation: manifest.generation,
|
|
311
|
+
manifestPath,
|
|
312
|
+
requestId: request.id
|
|
313
|
+
}),
|
|
314
|
+
operation
|
|
315
|
+
);
|
|
316
|
+
return finalizationResult(result);
|
|
266
317
|
}
|
|
267
318
|
const boundArgs = bindSandboxControlTask(request, manifest.taskId!);
|
|
268
|
-
let controllerBinding: Readonly<{ instanceDigest: string; controlGeneration: string }> | null = null;
|
|
269
319
|
if (request.family === 'task-orchestration') {
|
|
270
|
-
|
|
320
|
+
boundArgs.push('--git-worktree-root', manifest.worktreeRoot);
|
|
321
|
+
}
|
|
322
|
+
let operation: TaskControlOperation;
|
|
323
|
+
try {
|
|
324
|
+
operation = parseTaskControlOperation(request.family, boundArgs);
|
|
325
|
+
} catch (error) {
|
|
326
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
327
|
+
const code = /^([A-Z][A-Z0-9_]+)/u.exec(message)?.[1] ?? 'TASK_CONTROL_OPERATION_INVALID';
|
|
328
|
+
return request.family === 'task-lifecycle'
|
|
329
|
+
? lifecycleFailure('LIFECYCLE_PAYLOAD_INVALID', message)
|
|
330
|
+
: orchestrationFailure(code, message);
|
|
331
|
+
}
|
|
332
|
+
let controllerBinding: Readonly<{
|
|
333
|
+
instanceDigest: string;
|
|
334
|
+
controlGeneration: string;
|
|
335
|
+
}> | null = null;
|
|
336
|
+
if (request.family === 'task-orchestration') {
|
|
337
|
+
const codexPrepare = operation.family === 'task-orchestration'
|
|
338
|
+
&& operation.intent === 'prepare'
|
|
339
|
+
&& operation.input.client === 'codex';
|
|
340
|
+
if (codexPrepare) {
|
|
271
341
|
if (!request.controllerProof) {
|
|
272
342
|
return orchestrationFailure(
|
|
273
343
|
'CODEX_SANDBOX_CONTROLLER_PROOF_REQUIRED',
|
|
@@ -293,36 +363,69 @@ export function executeRequest(
|
|
|
293
363
|
'Controller proof is only accepted for canonical Codex prepare'
|
|
294
364
|
);
|
|
295
365
|
}
|
|
296
|
-
boundArgs.push('--git-worktree-root', manifest.worktreeRoot);
|
|
297
366
|
}
|
|
298
|
-
const
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
367
|
+
const context = createSandboxExecutorExecutionContext({
|
|
368
|
+
repoRoot: manifest.repoRoot,
|
|
369
|
+
worktreeRoot: manifest.worktreeRoot,
|
|
370
|
+
runtimeDir: manifest.runtimeDir,
|
|
371
|
+
taskId: manifest.taskId!,
|
|
372
|
+
generation: manifest.generation,
|
|
373
|
+
manifestPath,
|
|
374
|
+
requestId: request.id,
|
|
375
|
+
...(controllerBinding ? { controllerBinding } : {})
|
|
376
|
+
});
|
|
377
|
+
const format = (value: unknown): SandboxControlExecutionResult => {
|
|
378
|
+
const result = value as { status?: string };
|
|
379
|
+
return {
|
|
380
|
+
exitCode: result.status === 'failed' ? 1 : 0,
|
|
381
|
+
stdout: `${JSON.stringify(value)}\n`,
|
|
382
|
+
stderr: ''
|
|
383
|
+
};
|
|
384
|
+
};
|
|
385
|
+
if (request.family === 'task-orchestration') {
|
|
386
|
+
if (operation.family !== 'task-orchestration') throw new Error('SANDBOX_CONTROL_ORCHESTRATION_OPERATION_INVALID');
|
|
387
|
+
return format(await dispatchTaskControlOperation(context, operation));
|
|
388
|
+
}
|
|
389
|
+
if (operation.family !== 'task-lifecycle') throw new Error('SANDBOX_CONTROL_LIFECYCLE_OPERATION_INVALID');
|
|
390
|
+
return format(dispatchTaskControlOperation(context, operation));
|
|
314
391
|
}
|
|
315
392
|
|
|
316
393
|
export async function runSandboxControlExecutor(requestPath: string, nonce: string): Promise<void> {
|
|
317
|
-
await waitForGate(nonce);
|
|
394
|
+
const gateOwner = await waitForGate(nonce);
|
|
318
395
|
process.disconnect?.();
|
|
319
|
-
const root = fs.realpathSync.native(process.cwd());
|
|
320
|
-
const request = JSON.parse(fs.readFileSync(requestPath, 'utf8')) as SandboxControlRequest;
|
|
321
396
|
const manifestPath = process.env.AGENT_INFRA_EXECUTOR_MANIFEST;
|
|
322
397
|
if (!manifestPath) throw new Error('SANDBOX_CONTROL_EXECUTOR_MANIFEST_MISSING');
|
|
323
|
-
const manifest =
|
|
398
|
+
const manifest = readSandboxControlManifest(manifestPath);
|
|
399
|
+
const root = fs.realpathSync.native(process.cwd());
|
|
324
400
|
if (fs.realpathSync.native(manifest.repoRoot) !== root) throw new Error('SANDBOX_CONTROL_EXECUTOR_ROOT_INVALID');
|
|
325
|
-
const
|
|
401
|
+
const raw = JSON.parse(fs.readFileSync(requestPath, 'utf8')) as unknown;
|
|
402
|
+
const request = validateSandboxControlRequest(raw, manifest);
|
|
403
|
+
const requestDirectory = path.resolve(path.dirname(requestPath));
|
|
404
|
+
const processingDirectory = path.resolve(path.join(manifest.processingDir, request.id));
|
|
405
|
+
const channelRequestDirectory = path.resolve(path.join(manifest.channelDir, 'requests'));
|
|
406
|
+
if (requestDirectory !== processingDirectory && requestDirectory !== channelRequestDirectory) {
|
|
407
|
+
throw new Error('SANDBOX_CONTROL_EXECUTOR_REQUEST_INVALID');
|
|
408
|
+
}
|
|
409
|
+
let result: SandboxControlExecutionResult;
|
|
410
|
+
try {
|
|
411
|
+
assertSandboxControlExecutorAuthority(manifest, gateOwner);
|
|
412
|
+
result = await executeRequest(manifest, manifestPath, request);
|
|
413
|
+
} catch (error) {
|
|
414
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
415
|
+
const code = /^([A-Z][A-Z0-9_]+)/u.exec(detail)?.[1] ?? 'SANDBOX_CONTROL_EXECUTOR_FAILED';
|
|
416
|
+
result = request.family === 'task-finalization'
|
|
417
|
+
? {
|
|
418
|
+
exitCode: 1,
|
|
419
|
+
stdout: `${JSON.stringify({
|
|
420
|
+
version: 1, status: 'failed', changed: false, accepted: true, result: null,
|
|
421
|
+
error: { code, message: detail, retryable: false }
|
|
422
|
+
})}\n`,
|
|
423
|
+
stderr: ''
|
|
424
|
+
}
|
|
425
|
+
: request.family === 'task-lifecycle'
|
|
426
|
+
? lifecycleFailure(code, detail)
|
|
427
|
+
: orchestrationFailure(code, detail);
|
|
428
|
+
}
|
|
326
429
|
process.stdout.write(result.stdout);
|
|
327
430
|
process.stderr.write(result.stderr);
|
|
328
431
|
process.exitCode = result.exitCode;
|
|
@@ -10,9 +10,7 @@ import {
|
|
|
10
10
|
import type { ProcessIdentity, ProcessIdentityProbe, ProcessIdentityState } from '../../server/process-state.ts';
|
|
11
11
|
import type {
|
|
12
12
|
SandboxControlExecution,
|
|
13
|
-
SandboxControlManifestLike,
|
|
14
13
|
SandboxControlManifest,
|
|
15
|
-
SandboxControlLegacyManifest,
|
|
16
14
|
SandboxControlStatus,
|
|
17
15
|
SandboxControlTimingPolicy
|
|
18
16
|
} from './protocol.ts';
|
|
@@ -468,7 +466,6 @@ function readProcessingSnapshot(root: string): SandboxControlCutoverSnapshot['pr
|
|
|
468
466
|
export function captureSandboxControlCutoverSnapshot(root: string): SandboxControlCutoverSnapshot {
|
|
469
467
|
const resolvedRoot = path.resolve(root);
|
|
470
468
|
const manifestPath = path.join(resolvedRoot, 'manifest.json');
|
|
471
|
-
if (fs.existsSync(manifestPath)) readSandboxControlManifestForTransition(manifestPath);
|
|
472
469
|
return {
|
|
473
470
|
root: resolvedRoot,
|
|
474
471
|
manifestRaw: readOptionalRaw(manifestPath),
|
|
@@ -485,10 +482,7 @@ export function assertSandboxControlCutoverSnapshot(snapshot: SandboxControlCuto
|
|
|
485
482
|
}
|
|
486
483
|
}
|
|
487
484
|
|
|
488
|
-
function readSandboxControlManifestValue(
|
|
489
|
-
manifestPath: string,
|
|
490
|
-
allowLegacy: boolean
|
|
491
|
-
): SandboxControlManifestLike {
|
|
485
|
+
function readSandboxControlManifestValue(manifestPath: string): SandboxControlManifest {
|
|
492
486
|
if (!regularFile(manifestPath)) throw new Error('SANDBOX_CONTROL_MANIFEST_INVALID');
|
|
493
487
|
let manifest: Record<string, unknown>;
|
|
494
488
|
try {
|
|
@@ -500,9 +494,15 @@ function readSandboxControlManifestValue(
|
|
|
500
494
|
} catch {
|
|
501
495
|
throw new Error('SANDBOX_CONTROL_MANIFEST_INVALID');
|
|
502
496
|
}
|
|
503
|
-
const candidate = manifest as Partial<SandboxControlManifest> &
|
|
504
|
-
|
|
505
|
-
|
|
497
|
+
const candidate = manifest as Partial<SandboxControlManifest> & { version?: unknown };
|
|
498
|
+
const expectedKeys = [
|
|
499
|
+
'branch', 'channelDir', 'container', 'containerIdentity', 'engine', 'generation',
|
|
500
|
+
'mode', 'processingDir', 'project', 'publicStatusDir', 'repoRoot', 'runtimeDir',
|
|
501
|
+
'taskId', 'token', 'worktreeRoot'
|
|
502
|
+
];
|
|
503
|
+
if ('version' in candidate || typeof candidate.runtimeDir !== 'string'
|
|
504
|
+
|| Object.keys(manifest).sort().join(',') !== expectedKeys.sort().join(',')) {
|
|
505
|
+
throw new Error('SANDBOX_CONTROL_MANIFEST_REBUILD_REQUIRED: container-only recreation/rebuild is required');
|
|
506
506
|
}
|
|
507
507
|
if (typeof candidate.containerIdentity !== 'object' || candidate.containerIdentity === null
|
|
508
508
|
|| Array.isArray(candidate.containerIdentity)) throw new Error('SANDBOX_CONTROL_MANIFEST_INVALID');
|
|
@@ -518,31 +518,23 @@ function readSandboxControlManifestValue(
|
|
|
518
518
|
|| (candidate.taskId !== null && typeof candidate.taskId !== 'string')
|
|
519
519
|
|| typeof candidate.channelDir !== 'string' || typeof candidate.publicStatusDir !== 'string'
|
|
520
520
|
|| typeof candidate.processingDir !== 'string' || typeof candidate.token !== 'string'
|
|
521
|
-
|| typeof candidate.generation !== 'string'
|
|
521
|
+
|| typeof candidate.generation !== 'string'
|
|
522
|
+
|| (candidate.mode === 'task-bound' && (typeof candidate.taskId !== 'string' || candidate.taskId.length === 0))
|
|
523
|
+
|| (candidate.mode === 'branch-only' && candidate.taskId !== null)) throw new Error('SANDBOX_CONTROL_MANIFEST_INVALID');
|
|
522
524
|
const root = path.dirname(path.resolve(manifestPath));
|
|
523
525
|
if (path.resolve(candidate.channelDir) !== path.join(root, 'channel')
|
|
524
526
|
|| path.resolve(candidate.publicStatusDir) !== path.join(root, 'public')
|
|
525
527
|
|| path.resolve(candidate.processingDir) !== path.join(root, 'processing')) {
|
|
526
528
|
throw new Error('SANDBOX_CONTROL_MANIFEST_INVALID');
|
|
527
529
|
}
|
|
528
|
-
if (candidate.
|
|
529
|
-
if (!allowLegacy) {
|
|
530
|
-
throw new Error('SANDBOX_CONTROL_MANIFEST_VERSION_INVALID: expected version 5; container-only recreation is required');
|
|
531
|
-
}
|
|
532
|
-
return candidate as unknown as SandboxControlLegacyManifest;
|
|
533
|
-
}
|
|
534
|
-
if (typeof candidate.runtimeDir !== 'string' || path.resolve(candidate.runtimeDir) !== path.join(root, 'runtime')) {
|
|
530
|
+
if (path.resolve(candidate.runtimeDir) !== path.join(root, 'runtime')) {
|
|
535
531
|
throw new Error('SANDBOX_CONTROL_MANIFEST_INVALID');
|
|
536
532
|
}
|
|
537
|
-
return candidate as
|
|
533
|
+
return candidate as SandboxControlManifest;
|
|
538
534
|
}
|
|
539
535
|
|
|
540
536
|
export function readSandboxControlManifest(manifestPath: string): SandboxControlManifest {
|
|
541
|
-
return readSandboxControlManifestValue(manifestPath
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
export function readSandboxControlManifestForTransition(manifestPath: string): SandboxControlManifestLike {
|
|
545
|
-
return readSandboxControlManifestValue(manifestPath, true);
|
|
537
|
+
return readSandboxControlManifestValue(manifestPath);
|
|
546
538
|
}
|
|
547
539
|
|
|
548
540
|
function readBrokerOwner(filePath: string): BrokerOwner | null {
|
|
@@ -561,6 +553,20 @@ function readBrokerOwner(filePath: string): BrokerOwner | null {
|
|
|
561
553
|
return owner as BrokerOwner;
|
|
562
554
|
}
|
|
563
555
|
|
|
556
|
+
export function assertSandboxControlBrokerOwner(
|
|
557
|
+
manifest: SandboxControlManifest,
|
|
558
|
+
options: Readonly<{ identityProbe?: ProcessIdentityProbe }> = {}
|
|
559
|
+
): BrokerOwner {
|
|
560
|
+
const root = path.dirname(path.resolve(manifest.publicStatusDir));
|
|
561
|
+
const owner = readBrokerOwner(path.join(root, 'broker.json'));
|
|
562
|
+
if (!owner) throw new Error('SANDBOX_CONTROL_OWNER_EVIDENCE_MISSING');
|
|
563
|
+
if (owner.token !== manifest.token || owner.generation !== manifest.generation) {
|
|
564
|
+
throw new Error('SANDBOX_CONTROL_OWNER_MISMATCH');
|
|
565
|
+
}
|
|
566
|
+
if (!ownerLive(owner, options.identityProbe)) throw new Error('SANDBOX_CONTROL_OWNER_LOST');
|
|
567
|
+
return owner;
|
|
568
|
+
}
|
|
569
|
+
|
|
564
570
|
function readStatusOwner(filePath: string): SandboxControlStatus | null {
|
|
565
571
|
if (!fs.existsSync(filePath)) return null;
|
|
566
572
|
if (!regularFile(filePath)) throw new Error('SANDBOX_CONTROL_OWNER_MISMATCH');
|
|
@@ -585,7 +591,7 @@ function ownerLive(
|
|
|
585
591
|
return state === 'alive';
|
|
586
592
|
}
|
|
587
593
|
|
|
588
|
-
function readExecutions(manifest:
|
|
594
|
+
function readExecutions(manifest: SandboxControlManifest): SandboxControlExecution[] {
|
|
589
595
|
if (!fs.existsSync(manifest.processingDir)) return [];
|
|
590
596
|
const stat = fs.lstatSync(manifest.processingDir);
|
|
591
597
|
if (!stat.isDirectory() || stat.isSymbolicLink()) throw new Error('SANDBOX_CONTROL_EXECUTION_STILL_RUNNING');
|
|
@@ -705,7 +711,7 @@ export async function quiesceSandboxControlRoot(
|
|
|
705
711
|
await waitForStartupTransition(resolvedRoot, Math.max(0, forceAt - Date.now()), identityProbe);
|
|
706
712
|
|
|
707
713
|
const manifestPath = path.join(resolvedRoot, 'manifest.json');
|
|
708
|
-
const manifest = fs.existsSync(manifestPath) ?
|
|
714
|
+
const manifest = fs.existsSync(manifestPath) ? readSandboxControlManifest(manifestPath) : null;
|
|
709
715
|
const broker = readBrokerOwner(path.join(resolvedRoot, 'broker.json'));
|
|
710
716
|
const status = readStatusOwner(path.join(resolvedRoot, 'public', 'status.json'));
|
|
711
717
|
const statusOwner = status ? status.broker : null;
|
|
@@ -799,7 +805,7 @@ export async function removeSandboxControlRoot(
|
|
|
799
805
|
const stat = fs.lstatSync(resolvedRoot);
|
|
800
806
|
if (!stat.isDirectory() || stat.isSymbolicLink()) throw new Error('SANDBOX_CONTROL_CHANNEL_INVALID');
|
|
801
807
|
const manifestPath = path.join(resolvedRoot, 'manifest.json');
|
|
802
|
-
const manifest =
|
|
808
|
+
const manifest = readSandboxControlManifest(manifestPath);
|
|
803
809
|
const inspectContainer = options.inspectContainer
|
|
804
810
|
?? ((timeoutMs) => inspectSandboxControlContainer(manifest, { timeoutMs }));
|
|
805
811
|
const timeoutMs = options.timeoutMs ?? options.timing?.quiesceDeadlineMs ?? DEFAULT_QUIESCE_TIMEOUT_MS;
|
|
@@ -870,7 +876,7 @@ export async function removeSandboxControlRoot(
|
|
|
870
876
|
}
|
|
871
877
|
}
|
|
872
878
|
|
|
873
|
-
const currentManifest =
|
|
879
|
+
const currentManifest = readSandboxControlManifest(manifestPath);
|
|
874
880
|
if (currentManifest.token !== manifest.token || currentManifest.generation !== manifest.generation
|
|
875
881
|
|| !isSandboxControlRootQuiescing(resolvedRoot)) {
|
|
876
882
|
throw new Error('SANDBOX_CONTROL_OWNER_TRANSITION');
|
|
@@ -35,9 +35,7 @@ export type SandboxControlManifestBase = Readonly<{
|
|
|
35
35
|
branch: string; mode: 'task-bound' | 'branch-only'; taskId: string | null; token: string;
|
|
36
36
|
generation: string; channelDir: string; publicStatusDir: string; processingDir: string;
|
|
37
37
|
}>;
|
|
38
|
-
export type
|
|
39
|
-
export type SandboxControlManifest = SandboxControlManifestBase & Readonly<{ version: 5; runtimeDir: string }>;
|
|
40
|
-
export type SandboxControlManifestLike = SandboxControlLegacyManifest | SandboxControlManifest;
|
|
38
|
+
export type SandboxControlManifest = SandboxControlManifestBase & Readonly<{ runtimeDir: string }>;
|
|
41
39
|
export type SandboxControlBrokerOwner = ProcessIdentity & Readonly<{
|
|
42
40
|
version: 3;
|
|
43
41
|
brokerId: string;
|