@fitlab-ai/agent-infra 0.9.10 → 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 +50 -4
- package/dist/lib/agent-client.js +0 -1
- package/dist/lib/agent-clients/adapters/opencode-sandbox.js +0 -4
- package/dist/lib/agent-clients/adapters/traecli.js +7 -8
- 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 +5 -15
- package/dist/lib/decide.js +14 -30
- package/dist/lib/defaults.json +4 -0
- package/dist/lib/init.js +2 -2
- package/dist/lib/internal/release-workflow.js +0 -1
- 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/internal/task-validate.js +2 -1
- 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/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/executor.js +131 -39
- 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 +1 -1
- 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/events.js +11 -5
- package/dist/lib/task/index.js +19 -20
- 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 +50 -13
- 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 -15
- 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/opencode-sandbox.ts +0 -5
- package/lib/agent-clients/adapters/traecli.ts +7 -8
- 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 +5 -18
- package/lib/decide.ts +12 -22
- package/lib/defaults.json +4 -0
- package/lib/init.ts +2 -2
- package/lib/internal/release-workflow.ts +0 -1
- 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/internal/task-validate.ts +3 -2
- package/lib/merge.ts +236 -120
- package/lib/process-data/normalize.ts +2 -8
- package/lib/run/host.ts +63 -14
- 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/executor.ts +148 -51
- 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 +2 -1
- 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/events.ts +10 -5
- package/lib/task/index.ts +19 -20
- 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 +40 -15
- package/lib/task/review-artifacts.ts +53 -15
- package/lib/task/review-finalization.ts +33 -7
- package/lib/task/verification-engine.ts +33 -18
- 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/README.en.md +5 -3
- package/templates/.agents/README.zh-CN.md +5 -3
- package/templates/.agents/rules/compatibility-policy.en.md +5 -4
- package/templates/.agents/rules/compatibility-policy.zh-CN.md +5 -4
- 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/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 +1 -1
- package/templates/.agents/rules/pr-sync.github.en.md +1 -1
- package/templates/.agents/rules/pr-sync.github.zh-CN.md +1 -1
- package/templates/.agents/rules/pr-sync.zh-CN.md +1 -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/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 +3 -3
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +3 -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 +3 -3
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +3 -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 +5 -7
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +5 -7
- package/templates/.agents/skills/create-pr/reference/change-report.en.md +9 -4
- package/templates/.agents/skills/create-pr/reference/change-report.zh-CN.md +9 -4
- 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 +3 -3
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +3 -3
- 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 +2 -1
- package/templates/.agents/skills/review-code/reference/migration-risks.zh-CN.md +2 -1
- 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 +4 -0
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +4 -0
- package/templates/.agents/skills/run-task/SKILL.en.md +4 -0
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +4 -0
- 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 +140 -109
- 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/{.trae/skills → .traecli/commands}/update-agent-infra.en.md +0 -1
- package/templates/{.trae/skills → .traecli/commands}/update-agent-infra.zh-CN.md +0 -1
- package/dist/lib/builtin-tuis.js +0 -40
- package/lib/builtin-tuis.ts +0 -56
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 {
|
|
@@ -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
|
}
|
|
@@ -170,6 +207,17 @@ function orchestrationFailure(code: string, message: string): SandboxControlExec
|
|
|
170
207
|
};
|
|
171
208
|
}
|
|
172
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
|
+
|
|
173
221
|
function finalizationResult(result: ReturnType<typeof applyTaskFinalization>): SandboxControlExecutionResult {
|
|
174
222
|
const payload = {
|
|
175
223
|
version: 1,
|
|
@@ -186,28 +234,17 @@ function finalizationResult(result: ReturnType<typeof applyTaskFinalization>): S
|
|
|
186
234
|
};
|
|
187
235
|
}
|
|
188
236
|
|
|
189
|
-
function isCodexPrepare(args: readonly string[]): boolean {
|
|
190
|
-
if (args[1] !== 'prepare') return false;
|
|
191
|
-
const values: string[] = [];
|
|
192
|
-
for (let index = 2; index < args.length; index += 1) {
|
|
193
|
-
if (args[index] === '--client') values.push(args[index + 1] ?? '');
|
|
194
|
-
else if (args[index]?.startsWith('--client=')) values.push(args[index]!.slice('--client='.length));
|
|
195
|
-
}
|
|
196
|
-
return values.length === 1 && values[0] === 'codex';
|
|
197
|
-
}
|
|
198
|
-
|
|
199
237
|
type ExecuteRequestOptions = Readonly<{
|
|
200
238
|
buildIdentity?: typeof computeLifecycleBuildIdentity;
|
|
201
239
|
resolveControllerBinding?: typeof resolveCodexControllerBinding;
|
|
202
|
-
spawnDomain?: typeof spawnSync;
|
|
203
240
|
}>;
|
|
204
241
|
|
|
205
|
-
export function executeRequest(
|
|
242
|
+
export async function executeRequest(
|
|
206
243
|
manifest: SandboxControlManifest,
|
|
207
244
|
manifestPath: string,
|
|
208
245
|
request: SandboxControlRequest,
|
|
209
246
|
options: ExecuteRequestOptions = {}
|
|
210
|
-
): SandboxControlExecutionResult {
|
|
247
|
+
): Promise<SandboxControlExecutionResult> {
|
|
211
248
|
if (request.family === 'task-create') {
|
|
212
249
|
const result = createTask(request.candidate, { repoRoot: manifest.repoRoot });
|
|
213
250
|
return {
|
|
@@ -260,20 +297,47 @@ export function executeRequest(
|
|
|
260
297
|
}
|
|
261
298
|
}
|
|
262
299
|
if (request.family === 'task-finalization') {
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
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);
|
|
269
317
|
}
|
|
270
318
|
const boundArgs = bindSandboxControlTask(request, manifest.taskId!);
|
|
319
|
+
if (request.family === 'task-orchestration') {
|
|
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
|
+
}
|
|
271
332
|
let controllerBinding: Readonly<{
|
|
272
333
|
instanceDigest: string;
|
|
273
334
|
controlGeneration: string;
|
|
274
335
|
}> | null = null;
|
|
275
336
|
if (request.family === 'task-orchestration') {
|
|
276
|
-
|
|
337
|
+
const codexPrepare = operation.family === 'task-orchestration'
|
|
338
|
+
&& operation.intent === 'prepare'
|
|
339
|
+
&& operation.input.client === 'codex';
|
|
340
|
+
if (codexPrepare) {
|
|
277
341
|
if (!request.controllerProof) {
|
|
278
342
|
return orchestrationFailure(
|
|
279
343
|
'CODEX_SANDBOX_CONTROLLER_PROOF_REQUIRED',
|
|
@@ -299,36 +363,69 @@ export function executeRequest(
|
|
|
299
363
|
'Controller proof is only accepted for canonical Codex prepare'
|
|
300
364
|
);
|
|
301
365
|
}
|
|
302
|
-
boundArgs.push('--git-worktree-root', manifest.worktreeRoot);
|
|
303
366
|
}
|
|
304
|
-
const
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
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));
|
|
320
391
|
}
|
|
321
392
|
|
|
322
393
|
export async function runSandboxControlExecutor(requestPath: string, nonce: string): Promise<void> {
|
|
323
|
-
await waitForGate(nonce);
|
|
394
|
+
const gateOwner = await waitForGate(nonce);
|
|
324
395
|
process.disconnect?.();
|
|
325
|
-
const root = fs.realpathSync.native(process.cwd());
|
|
326
|
-
const request = JSON.parse(fs.readFileSync(requestPath, 'utf8')) as SandboxControlRequest;
|
|
327
396
|
const manifestPath = process.env.AGENT_INFRA_EXECUTOR_MANIFEST;
|
|
328
397
|
if (!manifestPath) throw new Error('SANDBOX_CONTROL_EXECUTOR_MANIFEST_MISSING');
|
|
329
|
-
const manifest =
|
|
398
|
+
const manifest = readSandboxControlManifest(manifestPath);
|
|
399
|
+
const root = fs.realpathSync.native(process.cwd());
|
|
330
400
|
if (fs.realpathSync.native(manifest.repoRoot) !== root) throw new Error('SANDBOX_CONTROL_EXECUTOR_ROOT_INVALID');
|
|
331
|
-
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
|
+
}
|
|
332
429
|
process.stdout.write(result.stdout);
|
|
333
430
|
process.stderr.write(result.stderr);
|
|
334
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;
|
|
@@ -228,6 +228,7 @@ export async function serveSandboxControl(
|
|
|
228
228
|
appendSandboxControlAudit(manifest, 'broker-start', { pid: broker.pid });
|
|
229
229
|
if (!recoverProcessing(manifest, brokerOwns)) return;
|
|
230
230
|
while (!signal.aborted) {
|
|
231
|
+
let settledExecution: ActiveExecution | null = null;
|
|
231
232
|
if (!brokerOwns()) break;
|
|
232
233
|
let current: SandboxControlManifest;
|
|
233
234
|
try {
|
|
@@ -277,18 +278,7 @@ export async function serveSandboxControl(
|
|
|
277
278
|
|
|
278
279
|
if (active?.settled) {
|
|
279
280
|
if (!brokerOwns()) break;
|
|
280
|
-
|
|
281
|
-
if (!brokerOwns()) break;
|
|
282
|
-
writeResponse(manifest, {
|
|
283
|
-
version: 2, id: active.request.id, phase: 'completed', exitCode: active.result.exitCode,
|
|
284
|
-
stdout: active.result.stdout, stderr: active.result.stderr, error: null
|
|
285
|
-
});
|
|
286
|
-
} else {
|
|
287
|
-
if (!brokerOwns()) break;
|
|
288
|
-
writeResponse(manifest, unknown(active.request.id));
|
|
289
|
-
}
|
|
290
|
-
if (!brokerOwns()) break;
|
|
291
|
-
fs.rmSync(path.join(manifest.processingDir, active.request.id), { recursive: true, force: true });
|
|
281
|
+
settledExecution = active;
|
|
292
282
|
active = null;
|
|
293
283
|
}
|
|
294
284
|
|
|
@@ -327,6 +317,19 @@ export async function serveSandboxControl(
|
|
|
327
317
|
appendSandboxControlAudit(manifest, 'broker-state', { state, reasonCode, requestId: active?.request.id ?? null });
|
|
328
318
|
lastState = stateKey;
|
|
329
319
|
}
|
|
320
|
+
if (settledExecution) {
|
|
321
|
+
if (!brokerOwns()) break;
|
|
322
|
+
if (settledExecution.result) {
|
|
323
|
+
writeResponse(manifest, {
|
|
324
|
+
version: 2, id: settledExecution.request.id, phase: 'completed', exitCode: settledExecution.result.exitCode,
|
|
325
|
+
stdout: settledExecution.result.stdout, stderr: settledExecution.result.stderr, error: null
|
|
326
|
+
});
|
|
327
|
+
} else {
|
|
328
|
+
writeResponse(manifest, unknown(settledExecution.request.id));
|
|
329
|
+
}
|
|
330
|
+
if (!brokerOwns()) break;
|
|
331
|
+
fs.rmSync(path.join(manifest.processingDir, settledExecution.request.id), { recursive: true, force: true });
|
|
332
|
+
}
|
|
330
333
|
|
|
331
334
|
let retiring = false;
|
|
332
335
|
for (const name of fs.readdirSync(requestsDir).sort()) {
|
|
@@ -8,7 +8,7 @@ import type { ProcessIdentity, ProcessIdentityProbe } from '../../server/process
|
|
|
8
8
|
import type {
|
|
9
9
|
SandboxControlExecution,
|
|
10
10
|
SandboxControlLease,
|
|
11
|
-
|
|
11
|
+
SandboxControlManifest,
|
|
12
12
|
SandboxControlStatus
|
|
13
13
|
} from './protocol.ts';
|
|
14
14
|
|
|
@@ -36,12 +36,12 @@ export function readJsonFile(filePath: string): unknown {
|
|
|
36
36
|
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
export function statusPath(manifest:
|
|
39
|
+
export function statusPath(manifest: SandboxControlManifest): string {
|
|
40
40
|
return path.join(manifest.publicStatusDir, 'status.json');
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
export function writeSandboxControlStatus(
|
|
44
|
-
manifest:
|
|
44
|
+
manifest: SandboxControlManifest,
|
|
45
45
|
broker: ProcessIdentity & { brokerId: string },
|
|
46
46
|
state: SandboxControlStatus['state'],
|
|
47
47
|
reasonCode: string | null,
|
|
@@ -72,7 +72,7 @@ export function readSandboxControlStatus(directory: string): SandboxControlStatu
|
|
|
72
72
|
return parseSandboxControlStatus(readJsonFile(path.join(directory, 'status.json')));
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
export function readActiveLease(manifest:
|
|
75
|
+
export function readActiveLease(manifest: SandboxControlManifest, now = Date.now()): SandboxControlLease | null {
|
|
76
76
|
const filePath = path.join(path.dirname(manifest.publicStatusDir), 'lease.json');
|
|
77
77
|
if (!fs.existsSync(filePath)) return null;
|
|
78
78
|
const lease = readJsonFile(filePath) as Partial<SandboxControlLease> | null;
|
|
@@ -91,7 +91,7 @@ export function readActiveLease(manifest: SandboxControlManifestLike, now = Date
|
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
export function cleanupStaleSandboxControlLease(
|
|
94
|
-
manifest:
|
|
94
|
+
manifest: SandboxControlManifest,
|
|
95
95
|
now = Date.now(),
|
|
96
96
|
options: Readonly<{ identityProbe?: ProcessIdentityProbe }> = {}
|
|
97
97
|
): boolean {
|
|
@@ -121,7 +121,7 @@ export function cleanupStaleSandboxControlLease(
|
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
export function appendSandboxControlAudit(
|
|
124
|
-
manifest:
|
|
124
|
+
manifest: SandboxControlManifest,
|
|
125
125
|
event: string,
|
|
126
126
|
fields: Record<string, string | number | boolean | null> = {},
|
|
127
127
|
now = Date.now()
|
|
@@ -136,7 +136,7 @@ export function appendSandboxControlAudit(
|
|
|
136
136
|
});
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
export function executionPath(manifest:
|
|
139
|
+
export function executionPath(manifest: SandboxControlManifest, requestId: string): string {
|
|
140
140
|
return path.join(manifest.processingDir, requestId, 'execution.json');
|
|
141
141
|
}
|
|
142
142
|
|
package/lib/sandbox/mounts.ts
CHANGED
|
@@ -27,11 +27,12 @@ export function sandboxCoreBindMounts(
|
|
|
27
27
|
controlDir: string;
|
|
28
28
|
controlStatusDir: string;
|
|
29
29
|
runtimeDir?: string;
|
|
30
|
-
|
|
30
|
+
taskSources?: string[];
|
|
31
31
|
taskId?: string;
|
|
32
32
|
}
|
|
33
33
|
): SandboxBindMountDeclaration[] {
|
|
34
|
-
const
|
|
34
|
+
const taskSources = overrides.taskSources ?? [];
|
|
35
|
+
const taskBound = Boolean(taskSources.length > 0 && overrides.taskId);
|
|
35
36
|
const workspaceMounts = sandboxWorkspaceViewStatePaths(overrides.workspaceViewRoot).map(
|
|
36
37
|
({ state, hostPath }) => taskBound && state === 'active'
|
|
37
38
|
? {
|
|
@@ -57,7 +58,7 @@ export function sandboxCoreBindMounts(
|
|
|
57
58
|
];
|
|
58
59
|
if (taskBound) {
|
|
59
60
|
mounts.push({
|
|
60
|
-
hostPaths:
|
|
61
|
+
hostPaths: taskSources,
|
|
61
62
|
containerPath: path.posix.join('/workspace/.agents/workspace/active', overrides.taskId!),
|
|
62
63
|
readOnly: false
|
|
63
64
|
});
|