@fitlab-ai/agent-infra 0.11.11 → 0.11.12
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/dist/bin/internal-cli.js +2 -2
- package/dist/lib/agent-clients/adapter.js +7 -1
- package/dist/lib/agent-clients/adapters/claude-code.js +5 -1
- package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +78 -63
- package/dist/lib/agent-clients/adapters/codex-lifecycle/binding.js +114 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/cli.js +447 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-client.js +130 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-operation.js +131 -0
- package/dist/lib/{sandbox/control → agent-clients/adapters/codex-lifecycle}/controller-registration.js +2 -2
- package/dist/lib/agent-clients/adapters/codex-lifecycle/evidence.js +48 -1
- package/dist/lib/agent-clients/adapters/codex-lifecycle/recovery.js +124 -62
- package/dist/lib/{internal/codex-sandbox-controller.js → agent-clients/adapters/codex-lifecycle/sandbox-controller-cli.js} +3 -3
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +3 -5
- package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +222 -210
- package/dist/lib/agent-clients/adapters/codex-orchestration.js +468 -0
- package/dist/lib/agent-clients/adapters/codex.js +40 -1
- package/dist/lib/agent-clients/adapters/index.js +2 -10
- package/dist/lib/agent-clients/tokens.js +3 -2
- package/dist/lib/internal/agent-client.js +132 -72
- package/dist/lib/internal/cli-route-inventory.js +2 -2
- package/dist/lib/internal/task-operation-registry.js +1 -1
- package/dist/lib/internal/task-validate.js +1 -1
- package/dist/lib/platform/issues.js +18 -6
- package/dist/lib/runtime/agent-runtime.js +6 -29
- package/dist/lib/sandbox/commands/control-status.js +1 -0
- package/dist/lib/sandbox/commands/create.js +1 -0
- package/dist/lib/sandbox/control/agent-operation.js +34 -0
- package/dist/lib/sandbox/control/client.js +23 -150
- package/dist/lib/sandbox/control/executor.js +9 -76
- package/dist/lib/sandbox/control/protocol.js +14 -42
- package/dist/lib/sandbox/control/server.js +15 -49
- package/dist/lib/sandbox/mounts.js +2 -1
- package/dist/lib/sandbox/recovery.js +3 -1
- package/dist/lib/sandbox/removal.js +9 -5
- package/dist/lib/sandbox/workspace-view.js +8 -17
- package/dist/lib/task/control-authority.js +54 -264
- package/dist/lib/task/control-recovery.js +1 -1
- package/dist/lib/task/create.js +2 -2
- package/dist/lib/task/delegation-receipts.js +74 -161
- package/dist/lib/task/events.js +88 -44
- package/dist/lib/task/finalization.js +21 -6
- package/dist/lib/task/lifecycle.js +84 -97
- package/dist/lib/task/orchestration.js +182 -129
- package/dist/lib/task/runtime-paths.js +10 -0
- package/dist/lib/task/verification-engine.js +28 -15
- package/dist/lib/task/workspace-snapshot.js +1 -1
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapter.ts +39 -0
- package/lib/agent-clients/adapters/claude-code.ts +5 -1
- package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +105 -80
- package/lib/agent-clients/adapters/codex-lifecycle/binding.ts +147 -0
- package/lib/agent-clients/adapters/codex-lifecycle/cli.ts +510 -0
- package/lib/agent-clients/adapters/codex-lifecycle/controller-client.ts +158 -0
- package/lib/agent-clients/adapters/codex-lifecycle/controller-context.ts +1 -1
- package/lib/agent-clients/adapters/codex-lifecycle/controller-operation.ts +151 -0
- package/lib/{sandbox/control → agent-clients/adapters/codex-lifecycle}/controller-registration.ts +4 -4
- package/lib/agent-clients/adapters/codex-lifecycle/evidence.ts +43 -0
- package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +8 -5
- package/lib/agent-clients/adapters/codex-lifecycle/recovery.ts +190 -65
- package/lib/{internal/codex-sandbox-controller.ts → agent-clients/adapters/codex-lifecycle/sandbox-controller-cli.ts} +2 -2
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +3 -5
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +272 -223
- package/lib/agent-clients/adapters/codex-orchestration.ts +632 -0
- package/lib/agent-clients/adapters/codex.ts +44 -1
- package/lib/agent-clients/adapters/index.ts +7 -20
- package/lib/agent-clients/tokens.ts +4 -3
- package/lib/internal/agent-client.ts +142 -73
- package/lib/internal/cli-route-inventory.ts +2 -2
- package/lib/internal/task-operation-registry.ts +1 -1
- package/lib/internal/task-validate.ts +1 -1
- package/lib/platform/issues.ts +18 -6
- package/lib/runtime/agent-runtime.ts +6 -41
- package/lib/sandbox/commands/control-status.ts +1 -0
- package/lib/sandbox/commands/create.ts +1 -0
- package/lib/sandbox/control/agent-operation.ts +49 -0
- package/lib/sandbox/control/client.ts +35 -196
- package/lib/sandbox/control/executor.ts +12 -84
- package/lib/sandbox/control/protocol.ts +21 -51
- package/lib/sandbox/control/server.ts +12 -51
- package/lib/sandbox/mounts.ts +2 -1
- package/lib/sandbox/recovery.ts +3 -1
- package/lib/sandbox/removal.ts +12 -5
- package/lib/sandbox/workspace-view.ts +9 -16
- package/lib/task/control-authority.ts +65 -378
- package/lib/task/control-recovery.ts +2 -2
- package/lib/task/create.ts +3 -3
- package/lib/task/delegation-receipts.ts +87 -231
- package/lib/task/events.ts +100 -42
- package/lib/task/finalization.ts +23 -7
- package/lib/task/lifecycle.ts +82 -87
- package/lib/task/orchestration.ts +237 -176
- package/lib/task/runtime-paths.ts +12 -0
- package/lib/task/verification-engine.ts +27 -16
- package/lib/task/workspace-snapshot.ts +1 -1
- package/package.json +1 -1
- package/templates/.agents/hooks/lifecycle-delegation.js +1 -25
- package/templates/.agents/rules/README.en.md +1 -1
- package/templates/.agents/rules/README.zh-CN.md +1 -1
- package/templates/.agents/rules/next-step-output.en.md +6 -19
- package/templates/.agents/rules/next-step-output.zh-CN.md +6 -19
- package/templates/.agents/skills/analyze-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/check-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/check-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/code-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +0 -1
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +0 -1
- package/templates/.agents/skills/complete-task/SKILL.en.md +0 -3
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +0 -3
- package/templates/.agents/skills/review-analysis/SKILL.en.md +1 -1
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/review-code/SKILL.en.md +1 -1
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/review-plan/SKILL.en.md +1 -1
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +1 -1
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +1 -1
- package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +0 -546
- package/dist/lib/internal/codex-lifecycle.js +0 -385
- package/dist/lib/task/codex-orchestration.js +0 -206
- package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +0 -776
- package/lib/internal/codex-lifecycle.ts +0 -390
- package/lib/task/codex-orchestration.ts +0 -276
|
@@ -12,8 +12,7 @@ import { requestAuditFields, resultAuditFields, appendDiagnosticAudit, createSan
|
|
|
12
12
|
import { validateSandboxControlIdentity } from './identity-sentinel.js';
|
|
13
13
|
import { createSandboxExecutorExecutionContext, dispatchTaskControlOperation, parseTaskControlOperation } from '../../task/control-authority.js';
|
|
14
14
|
import { assertSandboxControlBrokerOwner, readSandboxControlManifest } from './lifecycle.js';
|
|
15
|
-
import {
|
|
16
|
-
import { closeCodexControllerRegistration, CodexControllerRegistrationError, openCodexControllerRegistration, resolveCodexControllerBinding } from './controller-registration.js';
|
|
15
|
+
import { executeAgentClientOperation, validateAgentClientOperation } from './agent-operation.js';
|
|
17
16
|
function sameBrokerOwner(left, right) {
|
|
18
17
|
return left.version === right.version
|
|
19
18
|
&& left.pid === right.pid
|
|
@@ -169,23 +168,6 @@ function waitForGate(nonce, timeoutMs = 2_000) {
|
|
|
169
168
|
});
|
|
170
169
|
});
|
|
171
170
|
}
|
|
172
|
-
function controllerFailure(error) {
|
|
173
|
-
const code = error instanceof CodexControllerRegistrationError
|
|
174
|
-
? error.code
|
|
175
|
-
: /^([A-Z][A-Z0-9_]+)/u.exec(error instanceof Error ? error.message : String(error))?.[1]
|
|
176
|
-
?? 'CODEX_SANDBOX_CONTROLLER_FAILED';
|
|
177
|
-
const message = error instanceof CodexControllerRegistrationError
|
|
178
|
-
? error.message
|
|
179
|
-
: `${code}: controller operation failed; inspect the sandbox controller and rebuild the sandbox if needed`;
|
|
180
|
-
const payload = {
|
|
181
|
-
version: 1,
|
|
182
|
-
status: 'failed',
|
|
183
|
-
changed: false,
|
|
184
|
-
lease: null,
|
|
185
|
-
error: { code, message, retryable: false }
|
|
186
|
-
};
|
|
187
|
-
return { exitCode: 1, stdout: `${JSON.stringify(payload)}\n`, stderr: '' };
|
|
188
|
-
}
|
|
189
171
|
function lifecycleFailure(code, message) {
|
|
190
172
|
return {
|
|
191
173
|
exitCode: 1,
|
|
@@ -241,59 +223,9 @@ async function executeRequestInner(manifest, manifestPath, request, options = {}
|
|
|
241
223
|
return taskCreateExecutionFailure(request.id, code, detail);
|
|
242
224
|
}
|
|
243
225
|
}
|
|
244
|
-
if (request.family === '
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
const binding = (options.resolveControllerBinding ?? resolveCodexControllerBinding)({
|
|
248
|
-
manifest,
|
|
249
|
-
manifestPath,
|
|
250
|
-
proof: request.controllerProof,
|
|
251
|
-
buildIdentity: (options.buildIdentity ?? computeLifecycleBuildIdentity)(manifest.repoRoot)
|
|
252
|
-
});
|
|
253
|
-
return {
|
|
254
|
-
exitCode: 0,
|
|
255
|
-
stdout: `${JSON.stringify({
|
|
256
|
-
version: 1,
|
|
257
|
-
status: 'verified',
|
|
258
|
-
changed: false,
|
|
259
|
-
lease: null,
|
|
260
|
-
binding: {
|
|
261
|
-
taskId: manifest.taskId,
|
|
262
|
-
controlGeneration: binding.controlGeneration,
|
|
263
|
-
controllerInstanceDigest: binding.instanceDigest
|
|
264
|
-
},
|
|
265
|
-
error: null
|
|
266
|
-
})}\n`,
|
|
267
|
-
stderr: ''
|
|
268
|
-
};
|
|
269
|
-
}
|
|
270
|
-
const result = request.command === 'open'
|
|
271
|
-
? openCodexControllerRegistration({
|
|
272
|
-
manifest,
|
|
273
|
-
manifestPath,
|
|
274
|
-
controllerProcess: request.controllerProcess,
|
|
275
|
-
buildIdentity: (options.buildIdentity ?? computeLifecycleBuildIdentity)(manifest.repoRoot)
|
|
276
|
-
})
|
|
277
|
-
: closeCodexControllerRegistration({
|
|
278
|
-
manifest,
|
|
279
|
-
manifestPath,
|
|
280
|
-
proof: request.controllerProof
|
|
281
|
-
});
|
|
282
|
-
return { exitCode: 0, stdout: `${JSON.stringify(result)}\n`, stderr: '' };
|
|
283
|
-
}
|
|
284
|
-
catch (error) {
|
|
285
|
-
const errorCode = error instanceof CodexControllerRegistrationError
|
|
286
|
-
? error.code
|
|
287
|
-
: /^([A-Z][A-Z0-9_]+)/u.exec(error instanceof Error ? error.message : String(error))?.[1]
|
|
288
|
-
?? 'CODEX_SANDBOX_CONTROLLER_FAILED';
|
|
289
|
-
appendExecutorAudit(manifest, 'controller-operation-failed', {
|
|
290
|
-
...requestAuditFields(manifest, request),
|
|
291
|
-
controllerCommand: request.command,
|
|
292
|
-
errorCode,
|
|
293
|
-
errorType: error instanceof Error ? error.name : typeof error,
|
|
294
|
-
});
|
|
295
|
-
return controllerFailure(error);
|
|
296
|
-
}
|
|
226
|
+
if (request.family === 'agent-client') {
|
|
227
|
+
validateAgentClientOperation(request, manifest);
|
|
228
|
+
return executeAgentClientOperation(request, manifest, manifestPath, options.agentClientOperation);
|
|
297
229
|
}
|
|
298
230
|
if (request.family === 'task-finalization') {
|
|
299
231
|
const parsed = parseTaskControlOperation('task-finalization', [manifest.taskId, 'complete', '--agent', request.agent]);
|
|
@@ -346,7 +278,8 @@ export async function executeRequest(manifest, manifestPath, request, options =
|
|
|
346
278
|
const fields = requestAuditFields(manifest, request);
|
|
347
279
|
appendExecutorAudit(manifest, 'executor-request-start', fields);
|
|
348
280
|
try {
|
|
349
|
-
|
|
281
|
+
let result;
|
|
282
|
+
result = await executeRequestInner(manifest, manifestPath, request, options);
|
|
350
283
|
appendExecutorAudit(manifest, 'executor-request-finished', {
|
|
351
284
|
requestId: request.id,
|
|
352
285
|
requestFamily: request.family,
|
|
@@ -424,13 +357,13 @@ export async function runSandboxControlExecutor(requestPath, nonce) {
|
|
|
424
357
|
return null;
|
|
425
358
|
}
|
|
426
359
|
})()
|
|
427
|
-
: null;
|
|
360
|
+
: request.family === 'agent-client' ? request.operation : null;
|
|
428
361
|
const context = createSandboxControlAuditContext(manifest, {
|
|
429
362
|
requestId: request.id,
|
|
430
363
|
family: request.family,
|
|
431
|
-
operation: operation
|
|
364
|
+
operation: operation && typeof operation === 'object' && 'family' in operation && operation.family === 'task-lifecycle'
|
|
432
365
|
? operation.request.intent
|
|
433
|
-
: request.family === 'task-finalization' ? request.operation : null,
|
|
366
|
+
: request.family === 'task-finalization' || request.family === 'agent-client' ? request.operation : null,
|
|
434
367
|
phase: 'started-committed',
|
|
435
368
|
outcome: 'in-progress'
|
|
436
369
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import { validateTaskCreateCandidate } from '../../task/create.js';
|
|
3
3
|
import { normalizeAgentToken } from '../../agent-clients/tokens.js';
|
|
4
|
+
import { isAgentClientId } from '../../agent-clients/types.js';
|
|
4
5
|
export const SANDBOX_CONTROL_MAX_BYTES = 64 * 1024;
|
|
5
6
|
export const SANDBOX_CONTROL_MAX_LOGICAL_RECORDS = 1024;
|
|
6
7
|
export const SANDBOX_CONTROL_MAX_RESPONSE_BYTES = 64 * 1024 * 1024;
|
|
@@ -10,7 +11,7 @@ export const SANDBOX_CONTROL_ADMISSION_WINDOW_MS = 2_000;
|
|
|
10
11
|
export const SANDBOX_CONTROL_STATUS_INTERVAL_MS = 250;
|
|
11
12
|
export const SANDBOX_CONTROL_STATUS_STALE_MS = 1_500;
|
|
12
13
|
export const SANDBOX_CONTROL_FUTURE_SKEW_MS = 1_000;
|
|
13
|
-
export const SANDBOX_CONTROL_FAMILIES = ['task-lifecycle', 'task-finalization', 'task-create', '
|
|
14
|
+
export const SANDBOX_CONTROL_FAMILIES = ['task-lifecycle', 'task-finalization', 'task-create', 'agent-client'];
|
|
14
15
|
export const DEFAULT_SANDBOX_CONTROL_TIMING = Object.freeze({
|
|
15
16
|
controlTickMs: 250,
|
|
16
17
|
parkedBindingInitialMs: 1_000,
|
|
@@ -131,39 +132,31 @@ export function validateSandboxControlRequest(value, manifest, options = {}) {
|
|
|
131
132
|
fail('SANDBOX_CONTROL_REQUEST_TOO_LARGE', 'request exceeds the control limit');
|
|
132
133
|
}
|
|
133
134
|
if (request.family === 'task-create') {
|
|
134
|
-
const expected = ['candidate', '
|
|
135
|
+
const expected = ['candidate', 'expiresAt', 'family', 'generation', 'id', 'issuedAt', 'token', 'version'];
|
|
135
136
|
if (Object.keys(request).sort().join(',') !== expected.sort().join(',')) {
|
|
136
137
|
fail('SANDBOX_CONTROL_REQUEST_INVALID', 'request schema or authorization is invalid');
|
|
137
138
|
}
|
|
138
|
-
if (request.controllerProcess !== null || request.controllerProof !== null) {
|
|
139
|
-
fail('SANDBOX_CONTROL_REQUEST_INVALID', 'task-create cannot carry controller authority');
|
|
140
|
-
}
|
|
141
139
|
return { ...request, candidate: validateTaskCreateCandidate(request.candidate) };
|
|
142
140
|
}
|
|
143
|
-
if (request.family === '
|
|
144
|
-
const expected = ['
|
|
141
|
+
if (request.family === 'agent-client') {
|
|
142
|
+
const expected = ['agentClient', 'expiresAt', 'family', 'generation', 'id', 'issuedAt', 'operation', 'payload', 'token', 'version'];
|
|
145
143
|
if (Object.keys(request).sort().join(',') !== expected.sort().join(',')
|
|
146
|
-
|| !
|
|
147
|
-
||
|
|
148
|
-
|| !
|
|
149
|
-
|
|
150
|
-
|| (request.command !== 'open' && !validControllerProof(request.controllerProof))
|
|
151
|
-
|| (request.command === 'verify'
|
|
152
|
-
&& JSON.stringify(request.controllerProcess) !== JSON.stringify(request.controllerProof.controllerProcess))) {
|
|
153
|
-
fail('SANDBOX_CONTROL_REQUEST_INVALID', 'controller request schema is invalid');
|
|
144
|
+
|| !isAgentClientId(request.agentClient)
|
|
145
|
+
|| typeof request.operation !== 'string' || !/^[a-z][a-z0-9.-]{0,63}$/u.test(request.operation)
|
|
146
|
+
|| !request.payload || typeof request.payload !== 'object' || Array.isArray(request.payload)) {
|
|
147
|
+
fail('SANDBOX_CONTROL_REQUEST_INVALID', 'agent-client request schema is invalid');
|
|
154
148
|
}
|
|
155
149
|
if (manifest.mode !== 'task-bound' || !manifest.taskId) {
|
|
156
|
-
fail('SANDBOX_CONTROL_BRANCH_ONLY', 'branch-only sandboxes cannot
|
|
150
|
+
fail('SANDBOX_CONTROL_BRANCH_ONLY', 'branch-only sandboxes cannot coordinate agent clients');
|
|
157
151
|
}
|
|
158
152
|
return request;
|
|
159
153
|
}
|
|
160
154
|
if (request.family === 'task-finalization') {
|
|
161
|
-
const expected = ['agent', 'args', '
|
|
155
|
+
const expected = ['agent', 'args', 'expiresAt', 'family', 'generation', 'id', 'issuedAt', 'operation', 'token', 'version'];
|
|
162
156
|
if (Object.keys(request).sort().join(',') !== expected.sort().join(',')
|
|
163
157
|
|| request.operation !== 'complete'
|
|
164
158
|
|| !Array.isArray(request.args) || request.args.length !== 0
|
|
165
|
-
|| typeof request.agent !== 'string' || normalizeAgentToken(request.agent) !== request.agent
|
|
166
|
-
|| request.controllerProcess !== null || request.controllerProof !== null) {
|
|
159
|
+
|| typeof request.agent !== 'string' || normalizeAgentToken(request.agent) !== request.agent) {
|
|
167
160
|
fail('SANDBOX_CONTROL_REQUEST_INVALID', 'task-finalization request schema or authorization is invalid');
|
|
168
161
|
}
|
|
169
162
|
if (manifest.mode !== 'task-bound' || !manifest.taskId) {
|
|
@@ -171,7 +164,7 @@ export function validateSandboxControlRequest(value, manifest, options = {}) {
|
|
|
171
164
|
}
|
|
172
165
|
return request;
|
|
173
166
|
}
|
|
174
|
-
const expected = ['args', '
|
|
167
|
+
const expected = ['args', 'expiresAt', 'family', 'generation', 'id', 'issuedAt', 'token', 'version'];
|
|
175
168
|
if (Object.keys(request).sort().join(',') !== expected.sort().join(',')
|
|
176
169
|
|| !Array.isArray(request.args) || !request.args.every((arg) => typeof arg === 'string')) {
|
|
177
170
|
fail('SANDBOX_CONTROL_REQUEST_INVALID', 'request schema or authorization is invalid');
|
|
@@ -179,37 +172,16 @@ export function validateSandboxControlRequest(value, manifest, options = {}) {
|
|
|
179
172
|
if (manifest.mode !== 'task-bound' || !manifest.taskId) {
|
|
180
173
|
fail('SANDBOX_CONTROL_BRANCH_ONLY', "branch-only sandboxes cannot coordinate tasks; return to the host and run 'ai sandbox start --recreate <task-ref-or-correct-branch>'");
|
|
181
174
|
}
|
|
182
|
-
if (request.controllerProcess !== null || request.controllerProof !== null) {
|
|
183
|
-
fail('SANDBOX_CONTROL_REQUEST_INVALID', 'controller authority is invalid');
|
|
184
|
-
}
|
|
185
175
|
return request;
|
|
186
176
|
}
|
|
187
177
|
export function bindSandboxControlTask(request, taskId) {
|
|
188
|
-
if (request.family === 'task-create' || request.family === '
|
|
178
|
+
if (request.family === 'task-create' || request.family === 'agent-client' || request.family === 'task-finalization') {
|
|
189
179
|
fail('SANDBOX_CONTROL_REQUEST_INVALID', `${request.family} requests do not bind a current task`);
|
|
190
180
|
}
|
|
191
181
|
if (request.args.length === 0)
|
|
192
182
|
fail('SANDBOX_CONTROL_REQUEST_INVALID', 'command arguments are required');
|
|
193
183
|
return [taskId, ...request.args.slice(1)];
|
|
194
184
|
}
|
|
195
|
-
function validControllerProcess(value) {
|
|
196
|
-
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
197
|
-
return false;
|
|
198
|
-
const processValue = value;
|
|
199
|
-
return Object.keys(processValue).sort().join(',') === 'pid,startTime'
|
|
200
|
-
&& Number.isSafeInteger(processValue.pid) && processValue.pid > 0
|
|
201
|
-
&& Number.isSafeInteger(processValue.startTime) && processValue.startTime >= 0;
|
|
202
|
-
}
|
|
203
|
-
function validControllerProof(value) {
|
|
204
|
-
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
205
|
-
return false;
|
|
206
|
-
const proof = value;
|
|
207
|
-
return Object.keys(proof).sort().join(',') === 'controllerProcess,leaseId,leaseSecret,version'
|
|
208
|
-
&& proof.version === 1
|
|
209
|
-
&& typeof proof.leaseId === 'string' && /^[a-f0-9]{64}$/u.test(proof.leaseId)
|
|
210
|
-
&& typeof proof.leaseSecret === 'string' && /^[a-f0-9]{64}$/u.test(proof.leaseSecret)
|
|
211
|
-
&& validControllerProcess(proof.controllerProcess);
|
|
212
|
-
}
|
|
213
185
|
export function controlError(error) {
|
|
214
186
|
if (error instanceof SandboxControlProtocolError) {
|
|
215
187
|
return { code: error.code, message: error.message, retryable: error.retryable };
|
|
@@ -18,7 +18,7 @@ import { inspectSandboxControlContainer } from './container-identity.js';
|
|
|
18
18
|
import { acquireSandboxControlBrokerStartup, garbageCollectSandboxControlRoot, isSandboxControlRootQuiescing, readSandboxControlManifest } from './lifecycle.js';
|
|
19
19
|
import { nextSandboxControlBackoff } from './timing.js';
|
|
20
20
|
import { readTaskFinalizationReceipt } from '../../task/finalization.js';
|
|
21
|
-
import {
|
|
21
|
+
import { recoverAgentClientOperation, validateAgentClientOperation } from './agent-operation.js';
|
|
22
22
|
import { validateSandboxControlIdentity } from './identity-sentinel.js';
|
|
23
23
|
import { mergeSandboxTaskView, taskViewAfterFinalization, taskViewForManifest } from './task-view.js';
|
|
24
24
|
import { taskCreateOutputUnavailableResult } from '../../task/create-service.js';
|
|
@@ -57,8 +57,8 @@ function operationKey(request) {
|
|
|
57
57
|
return request.operation;
|
|
58
58
|
if (request.family === 'task-create')
|
|
59
59
|
return 'create';
|
|
60
|
-
if (request.family === '
|
|
61
|
-
return request.
|
|
60
|
+
if (request.family === 'agent-client')
|
|
61
|
+
return request.operation;
|
|
62
62
|
if (request.family !== 'task-lifecycle')
|
|
63
63
|
return null;
|
|
64
64
|
try {
|
|
@@ -217,43 +217,6 @@ function taskCreateDomainEvidence(manifest, output) {
|
|
|
217
217
|
return { consistent: false };
|
|
218
218
|
}
|
|
219
219
|
}
|
|
220
|
-
function controllerDomainEvidence(manifest, manifestPath, request, output) {
|
|
221
|
-
if (!output || output.error !== null || typeof output.status !== 'string')
|
|
222
|
-
return { consistent: false };
|
|
223
|
-
try {
|
|
224
|
-
const registration = readCodexControllerRegistration(manifestPath);
|
|
225
|
-
if (request.family !== 'codex-controller')
|
|
226
|
-
return { consistent: false };
|
|
227
|
-
if (request.command === 'open') {
|
|
228
|
-
const lease = output.lease && typeof output.lease === 'object' && !Array.isArray(output.lease)
|
|
229
|
-
? output.lease : null;
|
|
230
|
-
return {
|
|
231
|
-
consistent: output.status === 'opened' && output.changed === true
|
|
232
|
-
&& lease?.taskId === registration.taskId
|
|
233
|
-
&& lease.controlGeneration === registration.controlGeneration
|
|
234
|
-
&& lease.controllerInstanceDigest === registration.controllerInstanceDigest
|
|
235
|
-
&& registration.taskId === manifest.taskId
|
|
236
|
-
&& registration.controlGeneration === manifest.generation
|
|
237
|
-
};
|
|
238
|
-
}
|
|
239
|
-
if (request.command === 'verify') {
|
|
240
|
-
const binding = output.binding && typeof output.binding === 'object' && !Array.isArray(output.binding)
|
|
241
|
-
? output.binding : null;
|
|
242
|
-
return {
|
|
243
|
-
consistent: output.status === 'verified' && output.changed === false
|
|
244
|
-
&& binding?.taskId === registration.taskId
|
|
245
|
-
&& binding.controlGeneration === registration.controlGeneration
|
|
246
|
-
&& binding.controllerInstanceDigest === registration.controllerInstanceDigest
|
|
247
|
-
};
|
|
248
|
-
}
|
|
249
|
-
return { consistent: false };
|
|
250
|
-
}
|
|
251
|
-
catch (error) {
|
|
252
|
-
return { consistent: request.family === 'codex-controller' && request.command === 'close'
|
|
253
|
-
&& error.code === 'CODEX_SANDBOX_CONTROLLER_REGISTRATION_MISSING'
|
|
254
|
-
&& output?.status === 'closed' && typeof output.changed === 'boolean' };
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
220
|
async function readRecoveryDomain(manifest, manifestPath, request, operation, terminalResult, payloadOutput) {
|
|
258
221
|
if (!operation)
|
|
259
222
|
return { domain: null };
|
|
@@ -261,7 +224,7 @@ async function readRecoveryDomain(manifest, manifestPath, request, operation, te
|
|
|
261
224
|
? manifest.taskId
|
|
262
225
|
: 'args' in request ? request.args[0] ?? null : manifest.taskId;
|
|
263
226
|
const output = parseControlOutput(payloadOutput);
|
|
264
|
-
if (!taskRef && operation.family !== 'task-create' && operation.family !== '
|
|
227
|
+
if (!taskRef && operation.family !== 'task-create' && operation.family !== 'agent-client') {
|
|
265
228
|
return { domain: null };
|
|
266
229
|
}
|
|
267
230
|
if (operation.family === 'task-lifecycle') {
|
|
@@ -289,8 +252,13 @@ async function readRecoveryDomain(manifest, manifestPath, request, operation, te
|
|
|
289
252
|
if (operation.family === 'task-create') {
|
|
290
253
|
return { domain: taskCreateDomainEvidence(manifest, output) };
|
|
291
254
|
}
|
|
292
|
-
if (operation.family === '
|
|
293
|
-
return { domain:
|
|
255
|
+
if (operation.family === 'agent-client' && request.family === 'agent-client') {
|
|
256
|
+
return { domain: recoverAgentClientOperation({
|
|
257
|
+
request,
|
|
258
|
+
manifest,
|
|
259
|
+
manifestPath,
|
|
260
|
+
stdout: payloadOutput
|
|
261
|
+
}) };
|
|
294
262
|
}
|
|
295
263
|
return { domain: null };
|
|
296
264
|
}
|
|
@@ -509,6 +477,8 @@ async function recoverProcessing(manifest, manifestPath, broker, brokerOwns) {
|
|
|
509
477
|
request = validateSandboxControlRequest(rawRequest, manifest, {
|
|
510
478
|
now: typeof rawRequest.issuedAt === 'number' ? rawRequest.issuedAt : undefined
|
|
511
479
|
});
|
|
480
|
+
if (request.family === 'agent-client')
|
|
481
|
+
validateAgentClientOperation(request, manifest);
|
|
512
482
|
}
|
|
513
483
|
catch {
|
|
514
484
|
// Missing or malformed request evidence remains fail-closed below.
|
|
@@ -982,6 +952,8 @@ export async function serveSandboxControl(manifestPath, signal = new AbortContro
|
|
|
982
952
|
if (active)
|
|
983
953
|
throw new Error('SANDBOX_CONTROL_BUSY');
|
|
984
954
|
const request = validateSandboxControlRequest(JSON.parse(fs.readFileSync(claimed, 'utf8')), manifest);
|
|
955
|
+
if (request.family === 'agent-client')
|
|
956
|
+
validateAgentClientOperation(request, manifest);
|
|
985
957
|
validatedRequest = request;
|
|
986
958
|
criticalRequestPhase(manifest, request, 'validated', 'in-progress');
|
|
987
959
|
appendBrokerAudit(manifest, 'request-validated', {
|
|
@@ -1075,12 +1047,6 @@ export async function serveSandboxControl(manifestPath, signal = new AbortContro
|
|
|
1075
1047
|
retiring = true;
|
|
1076
1048
|
continue;
|
|
1077
1049
|
}
|
|
1078
|
-
if (!brokerOwns()) {
|
|
1079
|
-
prepared.terminate(false);
|
|
1080
|
-
active = null;
|
|
1081
|
-
retiring = true;
|
|
1082
|
-
continue;
|
|
1083
|
-
}
|
|
1084
1050
|
criticalRequestPhase(manifest, request, 'accepted-authorized', 'in-progress');
|
|
1085
1051
|
writeAcceptedResponse(manifest, {
|
|
1086
1052
|
version: 2, id, phase: 'accepted', exitCode: null, stdout: '', stderr: '', error: null
|
|
@@ -38,9 +38,10 @@ export function sandboxCoreBindMounts(config, branch, overrides) {
|
|
|
38
38
|
...workspaceMounts,
|
|
39
39
|
];
|
|
40
40
|
if (taskBound) {
|
|
41
|
+
const taskMountPath = path.posix.join('/workspace/.agents/workspace/active', overrides.taskId);
|
|
41
42
|
mounts.push({
|
|
42
43
|
hostPaths: taskSources,
|
|
43
|
-
containerPath:
|
|
44
|
+
containerPath: taskMountPath,
|
|
44
45
|
readOnly: false
|
|
45
46
|
});
|
|
46
47
|
}
|
|
@@ -174,6 +174,7 @@ export async function startSandboxControlBroker(repoRoot, manifestPath) {
|
|
|
174
174
|
async function ensureSandboxControlBroker(params) {
|
|
175
175
|
const control = sandboxControlPaths({
|
|
176
176
|
base: params.config.controlBase ?? path.join(params.config.home, '.agent-infra', 'sandbox-control'),
|
|
177
|
+
repoRoot: params.config.repoRoot,
|
|
177
178
|
project: params.config.project,
|
|
178
179
|
container: params.container,
|
|
179
180
|
identity: params.workspace
|
|
@@ -509,6 +510,7 @@ function expectedMounts(params) {
|
|
|
509
510
|
});
|
|
510
511
|
const control = sandboxControlPaths({
|
|
511
512
|
base: config.controlBase ?? path.join(config.home, '.agent-infra', 'sandbox-control'),
|
|
513
|
+
repoRoot: config.repoRoot,
|
|
512
514
|
project: config.project,
|
|
513
515
|
container: params.container,
|
|
514
516
|
identity: params.workspace
|
|
@@ -905,7 +907,7 @@ export async function ensureSandboxReady(params) {
|
|
|
905
907
|
let completedReentry = null;
|
|
906
908
|
if (params.reentry === 'completed' && params.workspace?.mode === 'task-bound') {
|
|
907
909
|
const control = sandboxControlPaths({
|
|
908
|
-
base: params.config.controlBase, project: params.config.project,
|
|
910
|
+
base: params.config.controlBase, repoRoot: params.config.repoRoot, project: params.config.project,
|
|
909
911
|
container: params.row.name, identity: params.workspace
|
|
910
912
|
});
|
|
911
913
|
const manifest = readSandboxControlManifest(control.manifestPath);
|
|
@@ -568,7 +568,7 @@ function resolveRmTarget(config, tools, cleanupTarget, options = {}) {
|
|
|
568
568
|
? matchedContainers
|
|
569
569
|
: [...new Set([...containerNameCandidates(config, effectiveBranch), ...matchedContainers])];
|
|
570
570
|
const controlRoots = containers.flatMap((container) => identities.map((identity) => sandboxControlPaths({
|
|
571
|
-
base: config.controlBase, project: config.project, container, identity
|
|
571
|
+
base: config.controlBase, repoRoot: config.repoRoot, project: config.project, container, identity
|
|
572
572
|
}).root));
|
|
573
573
|
const workspaceViewRoots = containers.flatMap((container) => identities.map((identity) => sandboxWorkspaceViewPaths({
|
|
574
574
|
base: config.workspaceViewBase, project: config.project, container, identity
|
|
@@ -711,7 +711,9 @@ async function removeUncheckedSandbox(config, target, options) {
|
|
|
711
711
|
}
|
|
712
712
|
for (const root of controlRoots) {
|
|
713
713
|
fs.rmSync(root, { recursive: true, force: true });
|
|
714
|
-
|
|
714
|
+
if (target.workspace.mode !== 'task-bound') {
|
|
715
|
+
removeEmptyManagedParent(path.join(config.controlBase, config.project), root);
|
|
716
|
+
}
|
|
715
717
|
}
|
|
716
718
|
for (const root of workspaceViewRoots) {
|
|
717
719
|
fs.rmSync(root, { recursive: true, force: true });
|
|
@@ -735,13 +737,15 @@ async function removeUncheckedSandbox(config, target, options) {
|
|
|
735
737
|
assertBranchRemovalIdentity(config, effectiveBranch, worktreePermits);
|
|
736
738
|
runSafe('git', ['-C', config.repoRoot, 'branch', '-D', effectiveBranch]);
|
|
737
739
|
}
|
|
738
|
-
|
|
739
|
-
releaseStaleShortIdRegistry(config.repoRoot, target.workspace.taskId);
|
|
740
|
-
}
|
|
740
|
+
releaseTaskBoundSandboxIdentity(config.repoRoot, target.workspace);
|
|
741
741
|
if (!options.quiet)
|
|
742
742
|
p.outro('Sandbox removed');
|
|
743
743
|
return null;
|
|
744
744
|
}
|
|
745
|
+
function releaseTaskBoundSandboxIdentity(repoRoot, workspace) {
|
|
746
|
+
if (workspace.mode === 'task-bound')
|
|
747
|
+
releaseStaleShortIdRegistry(repoRoot, workspace.taskId);
|
|
748
|
+
}
|
|
745
749
|
async function rmOneCore(config, tools, branch, options = {}) {
|
|
746
750
|
const target = options.target ?? resolveRmTarget(config, tools, options.cleanupTarget ?? resolveSandboxCleanupTarget(branch, config.repoRoot));
|
|
747
751
|
return removeUncheckedSandbox(config, target, options);
|
|
@@ -2,6 +2,7 @@ import { createHash, randomBytes } from 'node:crypto';
|
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { resolveTaskWorkspace } from './task-resolver.js';
|
|
5
|
+
import { resolveTaskRuntimeRoot } from '../task/runtime-paths.js';
|
|
5
6
|
import { createSandboxControlIdentitySentinel, writeSandboxControlIdentitySentinel } from './control/identity-sentinel.js';
|
|
6
7
|
export const SANDBOX_WORKSPACE_VIEW_STATES = Object.freeze([
|
|
7
8
|
'active',
|
|
@@ -25,11 +26,12 @@ export function sandboxWorkspaceViewPaths(params) {
|
|
|
25
26
|
return { root };
|
|
26
27
|
}
|
|
27
28
|
export function sandboxControlPaths(params) {
|
|
28
|
-
const
|
|
29
|
-
?
|
|
30
|
-
:
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
const root = params.identity.mode === 'task-bound'
|
|
30
|
+
? path.join(resolveTaskRuntimeRoot(params.identity.taskId, { repoRoot: params.repoRoot }), 'sandbox-control')
|
|
31
|
+
: (() => {
|
|
32
|
+
const digest = createHash('sha256').update('branch-only').digest('hex').slice(0, 16);
|
|
33
|
+
return path.resolve(params.base, params.project, params.container, digest);
|
|
34
|
+
})();
|
|
33
35
|
return {
|
|
34
36
|
root,
|
|
35
37
|
channelDir: path.join(root, 'channel'),
|
|
@@ -155,7 +157,7 @@ export function materializeSandboxWorkspaceView(params) {
|
|
|
155
157
|
}
|
|
156
158
|
export function materializeSandboxControl(params) {
|
|
157
159
|
const { root, channelDir, statusDir, processingDir, runtimeDir, manifestPath } = sandboxControlPaths(params);
|
|
158
|
-
assertSafeDirectory(root, path.resolve(params.base));
|
|
160
|
+
assertSafeDirectory(root, params.identity.mode === 'task-bound' ? path.dirname(root) : path.resolve(params.base));
|
|
159
161
|
if (fs.existsSync(root)) {
|
|
160
162
|
if (!params.replacementLease || path.resolve(params.replacementLease.root) !== root) {
|
|
161
163
|
throw new Error('SANDBOX_CONTROL_REPLACEMENT_REQUIRED');
|
|
@@ -168,18 +170,7 @@ export function materializeSandboxControl(params) {
|
|
|
168
170
|
fs.mkdirSync(consumedDir, { recursive: true, mode: 0o700 });
|
|
169
171
|
for (const directory of [statusDir, processingDir, runtimeDir]) {
|
|
170
172
|
assertSafeDirectory(directory, root);
|
|
171
|
-
if (directory === runtimeDir)
|
|
172
|
-
fs.rmSync(directory, { recursive: true, force: true });
|
|
173
|
-
fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
174
|
-
}
|
|
175
|
-
for (const directory of [
|
|
176
|
-
path.join(runtimeDir, 'clients'),
|
|
177
|
-
path.join(runtimeDir, 'clients', 'codex', 'capabilities'),
|
|
178
|
-
path.join(runtimeDir, 'clients', 'codex', 'lifecycle')
|
|
179
|
-
]) {
|
|
180
|
-
assertSafeDirectory(directory, runtimeDir);
|
|
181
173
|
fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
182
|
-
fs.chmodSync(directory, 0o700);
|
|
183
174
|
}
|
|
184
175
|
for (const queue of ['requests', 'responses']) {
|
|
185
176
|
const directory = path.join(channelDir, queue);
|