@fitlab-ai/agent-infra 0.11.1 → 0.11.3
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 +7 -2
- package/dist/lib/agent-clients/adapter.js +3 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/recovery.js +234 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +8 -35
- package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +1 -34
- package/dist/lib/agent-clients/adapters/codex.js +10 -0
- package/dist/lib/internal/cli-route-inventory.js +1 -1
- package/dist/lib/internal/manual-validation.js +22 -27
- package/dist/lib/internal/platform-comment.js +59 -0
- package/dist/lib/internal/sandbox-control.js +2 -171
- package/dist/lib/internal/task-artifact.js +1 -1
- package/dist/lib/internal/task-lifecycle.js +1 -1
- package/dist/lib/internal/task-operation-registry.js +7 -2
- package/dist/lib/internal/task-review.js +5 -5
- package/dist/lib/internal/task-validate.js +0 -6
- package/dist/lib/platform/comment-safety.js +7 -1
- package/dist/lib/platform/github-provider.js +13 -3
- package/dist/lib/platform/issue-comments.js +93 -15
- package/dist/lib/platform/pr-summary.js +7 -2
- package/dist/lib/platform/provider-contract.d.ts +2 -0
- package/dist/lib/platform/provider-validation.js +7 -2
- package/dist/lib/platform/verification-sync.js +19 -2
- package/dist/lib/sandbox/control/client.js +3 -44
- package/dist/lib/sandbox/control/executor.js +14 -117
- package/dist/lib/sandbox/control/finalization-response.js +8 -2
- package/dist/lib/sandbox/control/protocol.js +5 -64
- package/dist/lib/sandbox/control/server.js +29 -206
- package/dist/lib/sandbox/control/state.js +1 -7
- package/dist/lib/sandbox/control/task-view.js +2 -1
- package/dist/lib/task/artifact-command.js +6 -6
- package/dist/lib/task/codex-orchestration.js +10 -208
- package/dist/lib/task/commit-operation.js +3 -18
- package/dist/lib/task/control-authority.js +35 -44
- package/dist/lib/task/control-recovery.js +3 -42
- package/dist/lib/task/delegation-receipts.js +16 -78
- package/dist/lib/task/events.js +89 -237
- package/dist/lib/task/finalization-artifacts.js +1 -3
- package/dist/lib/task/finalization-state.js +2 -1
- package/dist/lib/task/finalization.js +208 -57
- package/dist/lib/task/lifecycle-execution.js +2 -11
- package/dist/lib/task/lifecycle.js +1 -1
- package/dist/lib/task/local-artifact-finalization.js +25 -242
- package/dist/lib/task/manual-validation-receipt.js +8 -3
- package/dist/lib/task/orchestration.js +12 -24
- package/dist/lib/task/review-command.js +0 -1
- package/dist/lib/task/review-finalization.js +46 -211
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapter.ts +46 -0
- package/lib/agent-clients/adapters/codex-lifecycle/recovery.ts +297 -0
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +13 -33
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +1 -44
- package/lib/agent-clients/adapters/codex.ts +10 -0
- package/lib/internal/cli-route-inventory.ts +1 -1
- package/lib/internal/manual-validation.ts +22 -26
- package/lib/internal/platform-comment.ts +38 -0
- package/lib/internal/sandbox-control.ts +1 -185
- package/lib/internal/task-artifact.ts +1 -1
- package/lib/internal/task-lifecycle.ts +2 -2
- package/lib/internal/task-operation-registry.ts +8 -4
- package/lib/internal/task-review.ts +5 -5
- package/lib/internal/task-validate.ts +0 -5
- package/lib/platform/comment-safety.ts +8 -0
- package/lib/platform/github-provider.ts +11 -2
- package/lib/platform/issue-comments.ts +102 -17
- package/lib/platform/pr-summary.ts +10 -2
- package/lib/platform/provider-contract.ts +2 -0
- package/lib/platform/provider-validation.ts +6 -2
- package/lib/platform/verification-sync.ts +19 -2
- package/lib/sandbox/control/client.ts +5 -68
- package/lib/sandbox/control/executor.ts +14 -125
- package/lib/sandbox/control/finalization-response.ts +8 -2
- package/lib/sandbox/control/protocol.ts +7 -83
- package/lib/sandbox/control/server.ts +30 -220
- package/lib/sandbox/control/state.ts +1 -9
- package/lib/sandbox/control/task-view.ts +2 -1
- package/lib/task/artifact-command.ts +6 -7
- package/lib/task/codex-orchestration.ts +8 -241
- package/lib/task/commit-operation.ts +3 -23
- package/lib/task/control-authority.ts +39 -55
- package/lib/task/control-recovery.ts +3 -43
- package/lib/task/delegation-receipts.ts +14 -81
- package/lib/task/events.ts +106 -255
- package/lib/task/finalization-artifacts.ts +1 -3
- package/lib/task/finalization-state.ts +2 -1
- package/lib/task/finalization.ts +221 -65
- package/lib/task/lifecycle-execution.ts +10 -21
- package/lib/task/lifecycle.ts +2 -2
- package/lib/task/local-artifact-finalization.ts +23 -281
- package/lib/task/manual-validation-receipt.ts +8 -2
- package/lib/task/orchestration.ts +30 -53
- package/lib/task/review-command.ts +0 -1
- package/lib/task/review-finalization.ts +45 -193
- package/package.json +2 -2
- package/templates/.agents/rules/lifecycle-orchestration.en.md +23 -9
- package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +23 -9
- package/templates/.agents/rules/local-artifact-repair.en.md +14 -60
- package/templates/.agents/rules/local-artifact-repair.zh-CN.md +13 -60
- 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/code-task/SKILL.en.md +3 -3
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +2 -2
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/complete-task/SKILL.en.md +7 -8
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +7 -8
- 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/review-analysis/SKILL.en.md +2 -2
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/review-code/SKILL.en.md +3 -3
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/review-plan/SKILL.en.md +2 -2
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +2 -2
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/run-task/SKILL.en.md +10 -9
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +10 -9
- 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/dist/lib/sandbox/control/task-workflow.js +0 -74
- package/dist/lib/sandbox/control/workflow-executor.js +0 -157
- package/dist/lib/task/lifecycle-recovery.js +0 -549
- package/dist/lib/task/recovery-warning.js +0 -19
- package/dist/lib/task/workflow-command.js +0 -18
- package/dist/lib/task/workflow-dispatch.js +0 -161
- package/lib/sandbox/control/task-workflow.ts +0 -95
- package/lib/sandbox/control/workflow-executor.ts +0 -183
- package/lib/task/lifecycle-recovery.ts +0 -691
- package/lib/task/recovery-warning.ts +0 -21
- package/lib/task/workflow-command.ts +0 -19
- package/lib/task/workflow-dispatch.ts +0 -171
|
@@ -81,10 +81,6 @@ function preflight(statusDir, generation, taskViewEffect = null, now = Date.now(
|
|
|
81
81
|
function taskViewEffectForRequest(request) {
|
|
82
82
|
if (request.family === 'task-lifecycle' || request.family === 'task-finalization')
|
|
83
83
|
return 'progress';
|
|
84
|
-
if (request.family === 'task-orchestration')
|
|
85
|
-
return request.args[1] === 'status' ? 'diagnostic' : 'progress';
|
|
86
|
-
if (request.family === 'task-workflow')
|
|
87
|
-
return request.workflow.operation === 'artifact-inspect' || request.workflow.operation === 'decision-next-id' ? 'diagnostic' : 'progress';
|
|
88
84
|
return null;
|
|
89
85
|
}
|
|
90
86
|
function parseResponse(raw, id, accepted = false) {
|
|
@@ -152,9 +148,7 @@ export function resolveVisibleActiveShortId(ref) {
|
|
|
152
148
|
// may be a documented short id. Compare canonical ids so a bound sandbox keeps
|
|
153
149
|
// refusing other tasks without rejecting its own task's short id.
|
|
154
150
|
function canonicalRequestTask(request) {
|
|
155
|
-
const ref = request.
|
|
156
|
-
? request.workflow.taskId
|
|
157
|
-
: 'args' in request ? request.args[0] ?? null : null;
|
|
151
|
+
const ref = 'args' in request ? request.args[0] ?? null : null;
|
|
158
152
|
if (!ref)
|
|
159
153
|
return { kind: 'none' };
|
|
160
154
|
if (CONTROL_TASK_ID_RE.test(ref))
|
|
@@ -179,7 +173,7 @@ function exchangeSandboxControl(request, params) {
|
|
|
179
173
|
|| (requestTask.kind === 'task' && requestTask.taskId !== identity.taskId))) {
|
|
180
174
|
clientError('SANDBOX_CONTROL_IDENTITY_TOPOLOGY_MISMATCH', 'request task does not match the sandbox identity', false);
|
|
181
175
|
}
|
|
182
|
-
if (identity.mode === 'branch-only' &&
|
|
176
|
+
if (identity.mode === 'branch-only' && request.family === 'task-finalization') {
|
|
183
177
|
clientError('SANDBOX_CONTROL_BRANCH_ONLY', 'branch-only sandboxes cannot finalize tasks', false);
|
|
184
178
|
}
|
|
185
179
|
}
|
|
@@ -326,7 +320,7 @@ function authority(params) {
|
|
|
326
320
|
export function requestSandboxControl(params) {
|
|
327
321
|
if (!isSandboxControlFamily(params.family))
|
|
328
322
|
clientError('SANDBOX_CONTROL_COMMAND_DENIED', `'${params.family}' is not allowed`, false);
|
|
329
|
-
if (params.family === 'task-create' || params.family === 'codex-controller' || params.family === 'task-finalization'
|
|
323
|
+
if (params.family === 'task-create' || params.family === 'codex-controller' || params.family === 'task-finalization') {
|
|
330
324
|
clientError('SANDBOX_CONTROL_COMMAND_DENIED', `'${params.family}' requires a typed request`, false);
|
|
331
325
|
}
|
|
332
326
|
const auth = authority(params);
|
|
@@ -340,23 +334,6 @@ export function requestSandboxControl(params) {
|
|
|
340
334
|
};
|
|
341
335
|
return exchangeSandboxControl(request, params);
|
|
342
336
|
}
|
|
343
|
-
export function requestSandboxTaskControl(params) {
|
|
344
|
-
const auth = authority(params);
|
|
345
|
-
const issuedAt = Date.now();
|
|
346
|
-
const request = {
|
|
347
|
-
version: 3,
|
|
348
|
-
id: params.authority?.requestId ?? randomUUID(),
|
|
349
|
-
...auth,
|
|
350
|
-
issuedAt,
|
|
351
|
-
expiresAt: issuedAt + SANDBOX_CONTROL_ADMISSION_WINDOW_MS,
|
|
352
|
-
family: params.family,
|
|
353
|
-
args: params.args,
|
|
354
|
-
controllerProcess: null,
|
|
355
|
-
controllerProof: params.authority ? null : params.controllerProof,
|
|
356
|
-
...(params.authority ? { authority: params.authority } : {})
|
|
357
|
-
};
|
|
358
|
-
return exchangeSandboxControl(request, params);
|
|
359
|
-
}
|
|
360
337
|
export function requestSandboxTaskFinalization(params) {
|
|
361
338
|
const agent = normalizeAgentToken(params.agent);
|
|
362
339
|
if (!agent)
|
|
@@ -378,24 +355,6 @@ export function requestSandboxTaskFinalization(params) {
|
|
|
378
355
|
};
|
|
379
356
|
return exchangeSandboxControl(request, params);
|
|
380
357
|
}
|
|
381
|
-
export function requestSandboxTaskWorkflow(params) {
|
|
382
|
-
const auth = authority(params);
|
|
383
|
-
const issuedAt = Date.now();
|
|
384
|
-
const request = {
|
|
385
|
-
version: 3,
|
|
386
|
-
id: params.workflow.id,
|
|
387
|
-
...auth,
|
|
388
|
-
issuedAt,
|
|
389
|
-
expiresAt: issuedAt + SANDBOX_CONTROL_ADMISSION_WINDOW_MS,
|
|
390
|
-
family: 'task-workflow',
|
|
391
|
-
args: [],
|
|
392
|
-
controllerProcess: null,
|
|
393
|
-
controllerProof: null,
|
|
394
|
-
workflow: params.workflow,
|
|
395
|
-
...(params.authority ? { authority: params.authority } : {})
|
|
396
|
-
};
|
|
397
|
-
return exchangeSandboxControl(request, params);
|
|
398
|
-
}
|
|
399
358
|
function exactKeys(value, keys) {
|
|
400
359
|
return Object.keys(value).sort().join(',') === [...keys].sort().join(',');
|
|
401
360
|
}
|
|
@@ -9,8 +9,7 @@ import { bindSandboxControlTask, validateSandboxControlRequest } from './protoco
|
|
|
9
9
|
import { atomicWriteJson, executionPath, readActiveLease, terminateSandboxControlExecution } from './state.js';
|
|
10
10
|
import { requestAuditFields, resultAuditFields, appendDiagnosticAudit, createSandboxControlAuditContext, appendCriticalAudit, writeSandboxControlTransition } from './audit.js';
|
|
11
11
|
import { validateSandboxControlIdentity } from './identity-sentinel.js';
|
|
12
|
-
import { createSandboxExecutorExecutionContext, dispatchTaskControlOperation, parseTaskControlOperation
|
|
13
|
-
import { executeTaskWorkflow } from './workflow-executor.js';
|
|
12
|
+
import { createSandboxExecutorExecutionContext, dispatchTaskControlOperation, parseTaskControlOperation } from '../../task/control-authority.js';
|
|
14
13
|
import { assertSandboxControlBrokerOwner, readSandboxControlManifest } from './lifecycle.js';
|
|
15
14
|
import { computeLifecycleBuildIdentity } from '../../agent-clients/adapters/codex-lifecycle/build-identity.js';
|
|
16
15
|
import { closeCodexControllerRegistration, CodexControllerRegistrationError, openCodexControllerRegistration, resolveCodexControllerBinding } from './controller-registration.js';
|
|
@@ -118,17 +117,11 @@ export async function prepareSandboxControlExecution(params) {
|
|
|
118
117
|
atomicWriteJson(executionPath(params.manifest, params.request.id), { ...execution, phase: 'running', updatedAt: Date.now() });
|
|
119
118
|
if (!canWrite())
|
|
120
119
|
throw new Error('SANDBOX_CONTROL_OWNER_LOST');
|
|
121
|
-
const authority = params.lifecycleAuthority?.attestation ?? null;
|
|
122
120
|
child.send({
|
|
123
121
|
version: 2,
|
|
124
122
|
nonce,
|
|
125
123
|
owner: gateOwner,
|
|
126
|
-
requestId: params.request.id
|
|
127
|
-
operationId: authority?.operationId ?? params.request.id,
|
|
128
|
-
phase: authority?.phase ?? (params.request.family === 'task-workflow'
|
|
129
|
-
&& params.request.workflow?.operation === 'artifact-preflight' ? 'artifact.preflight'
|
|
130
|
-
: params.request.family === 'task-workflow' ? 'artifact.finalize-local' : 'orchestration.prepare'),
|
|
131
|
-
authority
|
|
124
|
+
requestId: params.request.id
|
|
132
125
|
});
|
|
133
126
|
},
|
|
134
127
|
completion,
|
|
@@ -155,7 +148,7 @@ function waitForGate(nonce, timeoutMs = 2_000) {
|
|
|
155
148
|
process.off('disconnect', onDisconnect);
|
|
156
149
|
const value = message;
|
|
157
150
|
const owner = value?.owner;
|
|
158
|
-
if (!value || Object.keys(value).sort().join(',') !== '
|
|
151
|
+
if (!value || Object.keys(value).sort().join(',') !== 'nonce,owner,requestId,version'
|
|
159
152
|
|| value.version !== 2 || value.nonce !== nonce
|
|
160
153
|
|| !owner || owner.version !== 3 || !Number.isSafeInteger(owner.pid) || (owner.pid ?? 0) <= 0
|
|
161
154
|
|| typeof owner.startTime !== 'number' || !Number.isSafeInteger(owner.startTime)
|
|
@@ -164,26 +157,13 @@ function waitForGate(nonce, timeoutMs = 2_000) {
|
|
|
164
157
|
reject(new Error('SANDBOX_CONTROL_EXECUTOR_GATE_INVALID'));
|
|
165
158
|
return;
|
|
166
159
|
}
|
|
167
|
-
if (typeof value.requestId !== 'string'
|
|
168
|
-
|| !['orchestration.prepare', 'artifact.preflight', 'artifact.finalize-local', 'task-event.completed'].includes(value.phase)
|
|
169
|
-
|| (value.authority !== null && value.authority !== undefined && (() => {
|
|
170
|
-
try {
|
|
171
|
-
validateLifecycleRecoveryAttestation(value.authority);
|
|
172
|
-
return false;
|
|
173
|
-
}
|
|
174
|
-
catch {
|
|
175
|
-
return true;
|
|
176
|
-
}
|
|
177
|
-
})())) {
|
|
160
|
+
if (typeof value.requestId !== 'string') {
|
|
178
161
|
reject(new Error('SANDBOX_CONTROL_EXECUTOR_GATE_INVALID'));
|
|
179
162
|
return;
|
|
180
163
|
}
|
|
181
164
|
resolve({
|
|
182
165
|
owner: owner,
|
|
183
|
-
|
|
184
|
-
requestId: value.requestId,
|
|
185
|
-
operationId: value.operationId,
|
|
186
|
-
phase: value.phase
|
|
166
|
+
requestId: value.requestId
|
|
187
167
|
});
|
|
188
168
|
});
|
|
189
169
|
});
|
|
@@ -205,16 +185,6 @@ function controllerFailure(error) {
|
|
|
205
185
|
};
|
|
206
186
|
return { exitCode: 1, stdout: `${JSON.stringify(payload)}\n`, stderr: '' };
|
|
207
187
|
}
|
|
208
|
-
function orchestrationFailure(code, message) {
|
|
209
|
-
return {
|
|
210
|
-
exitCode: 1,
|
|
211
|
-
stdout: `${JSON.stringify({
|
|
212
|
-
status: 'failed', changed: false, taskId: null, run: null, next: null,
|
|
213
|
-
error: { code, message }
|
|
214
|
-
})}\n`,
|
|
215
|
-
stderr: ''
|
|
216
|
-
};
|
|
217
|
-
}
|
|
218
188
|
function lifecycleFailure(code, message) {
|
|
219
189
|
return {
|
|
220
190
|
exitCode: 1,
|
|
@@ -324,8 +294,6 @@ async function executeRequestInner(manifest, manifestPath, request, options = {}
|
|
|
324
294
|
return controllerFailure(error);
|
|
325
295
|
}
|
|
326
296
|
}
|
|
327
|
-
if (request.family === 'task-workflow')
|
|
328
|
-
return executeTaskWorkflow(manifest, request.workflow, options.lifecycleRecoveryAttestation ?? null);
|
|
329
297
|
if (request.family === 'task-finalization') {
|
|
330
298
|
const operation = parseTaskControlOperation('task-finalization', [manifest.taskId, 'complete', '--agent', request.agent]);
|
|
331
299
|
if (operation.family !== 'task-finalization')
|
|
@@ -337,15 +305,11 @@ async function executeRequestInner(manifest, manifestPath, request, options = {}
|
|
|
337
305
|
taskId: manifest.taskId,
|
|
338
306
|
generation: manifest.generation,
|
|
339
307
|
manifestPath,
|
|
340
|
-
requestId: request.id
|
|
341
|
-
lifecycleRecoveryAttestation: options.lifecycleRecoveryAttestation ?? null
|
|
308
|
+
requestId: request.id
|
|
342
309
|
}), operation);
|
|
343
310
|
return finalizationResult(await result);
|
|
344
311
|
}
|
|
345
312
|
const boundArgs = bindSandboxControlTask(request, manifest.taskId);
|
|
346
|
-
if (request.family === 'task-orchestration') {
|
|
347
|
-
boundArgs.push('--git-worktree-root', manifest.worktreeRoot);
|
|
348
|
-
}
|
|
349
313
|
let operation;
|
|
350
314
|
try {
|
|
351
315
|
operation = parseTaskControlOperation(request.family, boundArgs);
|
|
@@ -353,55 +317,8 @@ async function executeRequestInner(manifest, manifestPath, request, options = {}
|
|
|
353
317
|
catch (error) {
|
|
354
318
|
const message = error instanceof Error ? error.message : String(error);
|
|
355
319
|
const code = /^([A-Z][A-Z0-9_]+)/u.exec(message)?.[1] ?? 'TASK_CONTROL_OPERATION_INVALID';
|
|
356
|
-
return
|
|
357
|
-
? lifecycleFailure('LIFECYCLE_PAYLOAD_INVALID', message)
|
|
358
|
-
: orchestrationFailure(code, message);
|
|
359
|
-
}
|
|
360
|
-
let controllerBinding = null;
|
|
361
|
-
if (request.family === 'task-orchestration') {
|
|
362
|
-
const codexPrepare = operation.family === 'task-orchestration'
|
|
363
|
-
&& operation.intent === 'prepare'
|
|
364
|
-
&& operation.input.client === 'codex';
|
|
365
|
-
if (codexPrepare) {
|
|
366
|
-
if (!request.controllerProof) {
|
|
367
|
-
const authority = options.lifecycleRecoveryAttestation;
|
|
368
|
-
if (!authority || authority.phase !== 'orchestration.prepare') {
|
|
369
|
-
return orchestrationFailure('CODEX_SANDBOX_CONTROLLER_PROOF_REQUIRED', 'Codex prepare requires a current controller lease proof');
|
|
370
|
-
}
|
|
371
|
-
controllerBinding = {
|
|
372
|
-
instanceDigest: authority.controllerInstanceDigest,
|
|
373
|
-
controlGeneration: authority.controlGeneration
|
|
374
|
-
};
|
|
375
|
-
}
|
|
376
|
-
else {
|
|
377
|
-
try {
|
|
378
|
-
controllerBinding = (options.resolveControllerBinding ?? resolveCodexControllerBinding)({
|
|
379
|
-
manifest,
|
|
380
|
-
manifestPath,
|
|
381
|
-
proof: request.controllerProof,
|
|
382
|
-
buildIdentity: (options.buildIdentity ?? computeLifecycleBuildIdentity)(manifest.repoRoot)
|
|
383
|
-
});
|
|
384
|
-
}
|
|
385
|
-
catch (error) {
|
|
386
|
-
const code = error instanceof CodexControllerRegistrationError
|
|
387
|
-
? error.code
|
|
388
|
-
: 'CODEX_SANDBOX_CONTROLLER_PROOF_INVALID';
|
|
389
|
-
return orchestrationFailure(code, `${code}: Codex controller proof was rejected`);
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
else if (request.controllerProof !== null) {
|
|
394
|
-
return orchestrationFailure('CODEX_SANDBOX_CONTROLLER_PROOF_INVALID', 'Controller proof is only accepted for canonical Codex prepare');
|
|
395
|
-
}
|
|
320
|
+
return lifecycleFailure('LIFECYCLE_PAYLOAD_INVALID', `${code}: ${message}`);
|
|
396
321
|
}
|
|
397
|
-
const diagnosticLog = (event, fields) => {
|
|
398
|
-
appendExecutorAudit(manifest, event.startsWith('orchestration-') ? event : `orchestration-${event}`, {
|
|
399
|
-
requestId: request.id,
|
|
400
|
-
requestFamily: request.family,
|
|
401
|
-
sandboxTaskId: manifest.taskId,
|
|
402
|
-
...fields
|
|
403
|
-
});
|
|
404
|
-
};
|
|
405
322
|
const context = createSandboxExecutorExecutionContext({
|
|
406
323
|
repoRoot: manifest.repoRoot,
|
|
407
324
|
worktreeRoot: manifest.worktreeRoot,
|
|
@@ -409,10 +326,7 @@ async function executeRequestInner(manifest, manifestPath, request, options = {}
|
|
|
409
326
|
taskId: manifest.taskId,
|
|
410
327
|
generation: manifest.generation,
|
|
411
328
|
manifestPath,
|
|
412
|
-
requestId: request.id
|
|
413
|
-
diagnosticLog,
|
|
414
|
-
...(controllerBinding ? { controllerBinding } : {}),
|
|
415
|
-
lifecycleRecoveryAttestation: options.lifecycleRecoveryAttestation ?? null
|
|
329
|
+
requestId: request.id
|
|
416
330
|
});
|
|
417
331
|
const format = (value) => {
|
|
418
332
|
const result = value;
|
|
@@ -422,11 +336,6 @@ async function executeRequestInner(manifest, manifestPath, request, options = {}
|
|
|
422
336
|
stderr: ''
|
|
423
337
|
};
|
|
424
338
|
};
|
|
425
|
-
if (request.family === 'task-orchestration') {
|
|
426
|
-
if (operation.family !== 'task-orchestration')
|
|
427
|
-
throw new Error('SANDBOX_CONTROL_ORCHESTRATION_OPERATION_INVALID');
|
|
428
|
-
return format(await dispatchTaskControlOperation(context, operation));
|
|
429
|
-
}
|
|
430
339
|
if (operation.family !== 'task-lifecycle')
|
|
431
340
|
throw new Error('SANDBOX_CONTROL_LIFECYCLE_OPERATION_INVALID');
|
|
432
341
|
return format(await dispatchTaskControlOperation(context, operation));
|
|
@@ -487,12 +396,6 @@ export async function runSandboxControlExecutor(requestPath, nonce) {
|
|
|
487
396
|
const request = validateSandboxControlRequest(raw, manifest);
|
|
488
397
|
if (gate.requestId !== request.id)
|
|
489
398
|
throw new Error('SANDBOX_CONTROL_EXECUTOR_GATE_REQUEST_MISMATCH');
|
|
490
|
-
if (gate.authority !== null
|
|
491
|
-
&& (gate.authority.requestId !== request.id
|
|
492
|
-
|| gate.authority.operationId !== gate.operationId
|
|
493
|
-
|| gate.authority.phase !== gate.phase)) {
|
|
494
|
-
throw new Error('SANDBOX_CONTROL_EXECUTOR_GATE_AUTHORITY_MISMATCH');
|
|
495
|
-
}
|
|
496
399
|
appendExecutorAudit(manifest, 'executor-request-validated', requestAuditFields(manifest, request));
|
|
497
400
|
const requestDirectory = path.resolve(path.dirname(requestPath));
|
|
498
401
|
const processingDirectory = path.resolve(path.join(manifest.processingDir, request.id));
|
|
@@ -510,7 +413,7 @@ export async function runSandboxControlExecutor(requestPath, nonce) {
|
|
|
510
413
|
let result;
|
|
511
414
|
try {
|
|
512
415
|
assertSandboxControlExecutorAuthority(manifest, gateOwner);
|
|
513
|
-
const operation = request.family === 'task-lifecycle'
|
|
416
|
+
const operation = request.family === 'task-lifecycle'
|
|
514
417
|
? (() => {
|
|
515
418
|
try {
|
|
516
419
|
return parseTaskControlOperation(request.family, request.args);
|
|
@@ -523,11 +426,9 @@ export async function runSandboxControlExecutor(requestPath, nonce) {
|
|
|
523
426
|
const context = createSandboxControlAuditContext(manifest, {
|
|
524
427
|
requestId: request.id,
|
|
525
428
|
family: request.family,
|
|
526
|
-
operation: operation?.family === 'task-
|
|
527
|
-
? operation.intent
|
|
528
|
-
:
|
|
529
|
-
? operation.request.intent
|
|
530
|
-
: request.family === 'task-finalization' ? request.operation : null,
|
|
429
|
+
operation: operation?.family === 'task-lifecycle'
|
|
430
|
+
? operation.request.intent
|
|
431
|
+
: request.family === 'task-finalization' ? request.operation : null,
|
|
531
432
|
phase: 'started-committed',
|
|
532
433
|
outcome: 'in-progress'
|
|
533
434
|
});
|
|
@@ -535,9 +436,7 @@ export async function runSandboxControlExecutor(requestPath, nonce) {
|
|
|
535
436
|
writeSandboxControlTransition(manifest, { requestId: request.id, phase: 'started-committed' });
|
|
536
437
|
const execution = readJsonExecution(executionPath(manifest, request.id));
|
|
537
438
|
atomicWriteJson(executionPath(manifest, request.id), { ...execution, phase: 'running', updatedAt: Date.now() });
|
|
538
|
-
result = await executeRequest(manifest, manifestPath, request
|
|
539
|
-
lifecycleRecoveryAttestation: gate.authority
|
|
540
|
-
});
|
|
439
|
+
result = await executeRequest(manifest, manifestPath, request);
|
|
541
440
|
}
|
|
542
441
|
catch (error) {
|
|
543
442
|
appendExecutorAudit(manifest, 'executor-authority-or-dispatch-failed', {
|
|
@@ -557,9 +456,7 @@ export async function runSandboxControlExecutor(requestPath, nonce) {
|
|
|
557
456
|
}
|
|
558
457
|
: request.family === 'task-create'
|
|
559
458
|
? taskCreateExecutionFailure(request.id, code, detail)
|
|
560
|
-
:
|
|
561
|
-
? lifecycleFailure(code, detail)
|
|
562
|
-
: orchestrationFailure(code, detail);
|
|
459
|
+
: lifecycleFailure(code, detail);
|
|
563
460
|
}
|
|
564
461
|
process.stdout.write(result.stdout);
|
|
565
462
|
process.stderr.write(result.stderr);
|
|
@@ -1,18 +1,24 @@
|
|
|
1
1
|
export function finalizationTerminalResponse(taskId, requestId, receipt) {
|
|
2
2
|
const pendingSteps = [
|
|
3
3
|
receipt.taskComment === 'pending' ? 'task-comment' : null,
|
|
4
|
-
receipt.verification === 'pending' ? 'verification' : null
|
|
4
|
+
receipt.verification === 'pending' ? 'verification' : null,
|
|
5
|
+
receipt.summary === 'pending' ? 'summary' : null
|
|
5
6
|
].filter((step) => step !== null);
|
|
6
|
-
const completedSteps = [
|
|
7
|
+
const completedSteps = [
|
|
8
|
+
'lifecycle', receipt.taskComment === 'pending' ? null : 'task-comment',
|
|
9
|
+
receipt.verification === 'pending' ? null : 'verification', receipt.summary === 'pending' ? null : 'summary'
|
|
10
|
+
]
|
|
7
11
|
.filter((step) => step !== null);
|
|
8
12
|
const warnings = receipt.warnings
|
|
9
13
|
.filter((warning) => warning.status === 'open')
|
|
10
14
|
.map(({ status: _status, resolvedAt: _resolvedAt, ...warning }) => warning);
|
|
11
15
|
const result = {
|
|
12
16
|
status: 'completed', changed: false, taskId,
|
|
17
|
+
backfill: { status: 'no-op', changed: false, error: null },
|
|
13
18
|
lifecycle: { status: 'no-op', changed: false, error: null },
|
|
14
19
|
taskComment: receipt.taskComment === 'pending' ? null : { status: 'no-op', changed: false, error: null },
|
|
15
20
|
verification: receipt.verification === 'pending' ? null : { status: 'no-op', changed: false, error: null },
|
|
21
|
+
summary: receipt.summary === 'pending' ? null : { status: 'no-op', changed: false, error: null },
|
|
16
22
|
completedSteps, pendingSteps,
|
|
17
23
|
result: pendingSteps.length > 0 || receipt.warningProjection === 'pending' || warnings.length > 0
|
|
18
24
|
? 'completed_with_warnings' : 'completed',
|
|
@@ -1,8 +1,6 @@
|
|
|
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 { validateTaskWorkflowRequest } from './task-workflow.js';
|
|
5
|
-
import { validateLifecycleAuthorityRequest } from '../../task/control-authority.js';
|
|
6
4
|
export const SANDBOX_CONTROL_MAX_BYTES = 64 * 1024;
|
|
7
5
|
export const SANDBOX_CONTROL_MAX_LOGICAL_RECORDS = 1024;
|
|
8
6
|
export const SANDBOX_CONTROL_MAX_RESPONSE_BYTES = 64 * 1024 * 1024;
|
|
@@ -12,7 +10,7 @@ export const SANDBOX_CONTROL_ADMISSION_WINDOW_MS = 2_000;
|
|
|
12
10
|
export const SANDBOX_CONTROL_STATUS_INTERVAL_MS = 250;
|
|
13
11
|
export const SANDBOX_CONTROL_STATUS_STALE_MS = 1_500;
|
|
14
12
|
export const SANDBOX_CONTROL_FUTURE_SKEW_MS = 1_000;
|
|
15
|
-
export const SANDBOX_CONTROL_FAMILIES = ['task-lifecycle', 'task-
|
|
13
|
+
export const SANDBOX_CONTROL_FAMILIES = ['task-lifecycle', 'task-finalization', 'task-create', 'codex-controller'];
|
|
16
14
|
export const DEFAULT_SANDBOX_CONTROL_TIMING = Object.freeze({
|
|
17
15
|
controlTickMs: 250,
|
|
18
16
|
parkedBindingInitialMs: 1_000,
|
|
@@ -173,78 +171,21 @@ export function validateSandboxControlRequest(value, manifest, options = {}) {
|
|
|
173
171
|
}
|
|
174
172
|
return request;
|
|
175
173
|
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
const baseExpected = expected.filter((key) => key !== 'authority');
|
|
179
|
-
if (![baseExpected, expected].some((keys) => Object.keys(request).sort().join(',') === keys.sort().join(','))
|
|
180
|
-
|| !Array.isArray(request.args) || request.args.length !== 0
|
|
181
|
-
|| request.controllerProcess !== null || request.controllerProof !== null) {
|
|
182
|
-
fail('SANDBOX_CONTROL_REQUEST_INVALID', 'task-workflow request schema is invalid');
|
|
183
|
-
}
|
|
184
|
-
if (manifest.mode !== 'task-bound' || !manifest.taskId)
|
|
185
|
-
fail('SANDBOX_CONTROL_BRANCH_ONLY', 'branch-only sandboxes cannot use task-workflow');
|
|
186
|
-
const workflow = validateTaskWorkflowRequest(request.workflow);
|
|
187
|
-
if (workflow.taskId !== manifest.taskId || workflow.generation !== manifest.generation || workflow.id !== request.id) {
|
|
188
|
-
fail('SANDBOX_CONTROL_REQUEST_INVALID', 'task-workflow binding does not match the sandbox manifest');
|
|
189
|
-
}
|
|
190
|
-
if (request.authority !== undefined)
|
|
191
|
-
validateAuthoritySelector(request.authority, request, manifest);
|
|
192
|
-
return { ...request, workflow };
|
|
193
|
-
}
|
|
194
|
-
const expected = ['args', 'authority', 'controllerProcess', 'controllerProof', 'expiresAt', 'family', 'generation', 'id', 'issuedAt', 'token', 'version'];
|
|
195
|
-
const baseExpected = expected.filter((key) => key !== 'authority');
|
|
196
|
-
if (![baseExpected, expected].some((keys) => Object.keys(request).sort().join(',') === keys.sort().join(','))
|
|
174
|
+
const expected = ['args', 'controllerProcess', 'controllerProof', 'expiresAt', 'family', 'generation', 'id', 'issuedAt', 'token', 'version'];
|
|
175
|
+
if (Object.keys(request).sort().join(',') !== expected.sort().join(',')
|
|
197
176
|
|| !Array.isArray(request.args) || !request.args.every((arg) => typeof arg === 'string')) {
|
|
198
177
|
fail('SANDBOX_CONTROL_REQUEST_INVALID', 'request schema or authorization is invalid');
|
|
199
178
|
}
|
|
200
179
|
if (manifest.mode !== 'task-bound' || !manifest.taskId) {
|
|
201
180
|
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>'");
|
|
202
181
|
}
|
|
203
|
-
if (request.
|
|
204
|
-
&& request.args.some((arg) => arg === '--git-worktree-root' || arg.startsWith('--git-worktree-root='))) {
|
|
205
|
-
fail('SANDBOX_CONTROL_REQUEST_INVALID', 'worktree binding is reserved for the control broker');
|
|
206
|
-
}
|
|
207
|
-
if (request.controllerProcess !== null
|
|
208
|
-
|| (request.controllerProof !== null && !validControllerProof(request.controllerProof))) {
|
|
182
|
+
if (request.controllerProcess !== null || request.controllerProof !== null) {
|
|
209
183
|
fail('SANDBOX_CONTROL_REQUEST_INVALID', 'controller authority is invalid');
|
|
210
184
|
}
|
|
211
|
-
if (request.family !== 'task-orchestration' && request.controllerProof !== null) {
|
|
212
|
-
fail('SANDBOX_CONTROL_REQUEST_INVALID', 'controller proof is not allowed for this family');
|
|
213
|
-
}
|
|
214
|
-
if (request.authority !== undefined)
|
|
215
|
-
validateAuthoritySelector(request.authority, request, manifest);
|
|
216
185
|
return request;
|
|
217
186
|
}
|
|
218
|
-
function validateAuthoritySelector(value, request, manifest) {
|
|
219
|
-
let authority;
|
|
220
|
-
try {
|
|
221
|
-
authority = validateLifecycleAuthorityRequest(value);
|
|
222
|
-
}
|
|
223
|
-
catch {
|
|
224
|
-
fail('SANDBOX_CONTROL_AUTHORITY_INVALID', 'lifecycle authority selector is invalid');
|
|
225
|
-
}
|
|
226
|
-
if (authority.requestId !== request.id
|
|
227
|
-
|| authority.taskId !== manifest.taskId
|
|
228
|
-
|| authority.expectedControlGeneration !== manifest.generation) {
|
|
229
|
-
fail('SANDBOX_CONTROL_AUTHORITY_INVALID', 'lifecycle authority selector is not bound to this request');
|
|
230
|
-
}
|
|
231
|
-
const args = Array.isArray(request.args) ? request.args : [];
|
|
232
|
-
if (request.family === 'task-orchestration'
|
|
233
|
-
&& (args[1] !== 'prepare' || authority.phase !== 'orchestration.prepare')) {
|
|
234
|
-
fail('SANDBOX_CONTROL_AUTHORITY_INVALID', 'orchestration authority is only valid for prepare');
|
|
235
|
-
}
|
|
236
|
-
const workflow = request.workflow;
|
|
237
|
-
if (request.family === 'task-workflow') {
|
|
238
|
-
const expectedPhase = workflow?.operation === 'artifact-finalize-local'
|
|
239
|
-
? 'artifact.finalize-local'
|
|
240
|
-
: workflow?.operation === 'event' ? 'task-event.completed' : null;
|
|
241
|
-
if (authority.phase !== expectedPhase) {
|
|
242
|
-
fail('SANDBOX_CONTROL_AUTHORITY_INVALID', 'workflow authority phase does not match the workflow operation');
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
187
|
export function bindSandboxControlTask(request, taskId) {
|
|
247
|
-
if (request.family === 'task-create' || request.family === 'codex-controller' || request.family === 'task-finalization'
|
|
188
|
+
if (request.family === 'task-create' || request.family === 'codex-controller' || request.family === 'task-finalization') {
|
|
248
189
|
fail('SANDBOX_CONTROL_REQUEST_INVALID', `${request.family} requests do not bind a current task`);
|
|
249
190
|
}
|
|
250
191
|
if (request.args.length === 0)
|