@fitlab-ai/agent-infra 0.9.14 → 0.9.15
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 +10 -0
- package/README.zh-CN.md +10 -0
- package/bin/internal-cli.sh +45 -0
- package/dist/bin/internal-cli.js +93 -30
- package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +165 -27
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +3 -2
- package/dist/lib/defaults.json +103 -103
- package/dist/lib/fs/durable-write.js +37 -0
- package/dist/lib/fs/file-lock.js +111 -0
- package/dist/lib/host-control/audit.js +19 -0
- package/dist/lib/host-control/client.js +142 -0
- package/dist/lib/host-control/command.js +49 -0
- package/dist/lib/host-control/path.js +160 -0
- package/dist/lib/host-control/secure-fs.js +140 -0
- package/dist/lib/host-control/server.js +159 -0
- package/dist/lib/host-control/service.js +112 -0
- package/dist/lib/init.js +11 -2
- package/dist/lib/internal/cli-route-inventory.js +3 -1
- package/dist/lib/internal/codex-lifecycle.js +6 -6
- package/dist/lib/internal/demo-promotion.js +322 -0
- package/dist/lib/internal/demo-transcript.js +454 -0
- package/dist/lib/internal/host-control.js +28 -0
- package/dist/lib/internal/manual-validation.js +322 -0
- package/dist/lib/internal/platform-pr.js +18 -4
- package/dist/lib/internal/release-workflow.js +63 -84
- package/dist/lib/internal/sandbox-control.js +149 -2
- package/dist/lib/internal/task-artifact.js +15 -262
- package/dist/lib/internal/task-create.js +6 -5
- package/dist/lib/internal/task-event.js +37 -37
- package/dist/lib/internal/task-operation-registry.js +101 -3
- package/dist/lib/internal/task-review.js +8 -53
- package/dist/lib/internal/task-validate.js +30 -10
- package/dist/lib/owned-path.js +33 -0
- package/dist/lib/platform/check-status.js +11 -0
- package/dist/lib/platform/comment-safety.js +4 -1
- package/dist/lib/platform/completion-backfill.js +1 -1
- package/dist/lib/platform/github-data.js +241 -0
- package/dist/lib/platform/github-provider.js +15 -20
- package/dist/lib/platform/issue-comments.js +2 -2
- package/dist/lib/platform/issues.js +1 -179
- package/dist/lib/platform/pr-checks.js +12 -65
- package/dist/lib/platform/pr-summary.js +55 -3
- package/dist/lib/platform/pull-requests.js +2 -105
- package/dist/lib/platform/snapshots.js +2 -0
- package/dist/lib/platform/verification-sync.js +127 -142
- package/dist/lib/release/workflow.js +1 -1
- package/dist/lib/sandbox/commands/create.js +7 -2
- package/dist/lib/sandbox/commands/rm.js +3 -1514
- package/dist/lib/sandbox/control/audit.js +203 -0
- package/dist/lib/sandbox/control/client.js +93 -54
- package/dist/lib/sandbox/control/executor.js +129 -65
- package/dist/lib/sandbox/control/finalization-response.js +27 -0
- package/dist/lib/sandbox/control/identity-sentinel.js +107 -0
- package/dist/lib/sandbox/control/lifecycle.js +36 -2
- package/dist/lib/sandbox/control/native-file-lock.js +6 -98
- package/dist/lib/sandbox/control/protocol.js +55 -4
- package/dist/lib/sandbox/control/server.js +491 -120
- package/dist/lib/sandbox/control/state.js +58 -0
- package/dist/lib/sandbox/control/task-workflow.js +74 -0
- package/dist/lib/sandbox/control/workflow-executor.js +99 -0
- package/dist/lib/sandbox/index.js +2 -2
- package/dist/lib/sandbox/recovery.js +28 -4
- package/dist/lib/sandbox/removal.js +1717 -0
- package/dist/lib/sandbox/task-cleanup.js +217 -0
- package/dist/lib/sandbox/workspace-view.js +13 -14
- package/dist/lib/task/activity-intent.js +2 -1
- package/dist/lib/task/activity-log.js +30 -24
- package/dist/lib/task/artifact-command.js +101 -0
- package/dist/lib/task/artifact-lifecycle.js +30 -28
- package/dist/lib/task/artifact-name.js +36 -0
- package/dist/lib/task/artifact-operations.js +23 -69
- package/dist/lib/task/artifact-receipts.js +3 -15
- package/dist/lib/task/artifact-repair-intent.js +87 -16
- package/dist/lib/task/artifact-schema.js +6 -14
- package/dist/lib/task/capabilities.js +12 -19
- package/dist/lib/task/codex-orchestration.js +71 -25
- package/dist/lib/task/commit-identity.js +28 -0
- package/dist/lib/task/commit-operation.js +2 -16
- package/dist/lib/task/control-authority.js +371 -5
- package/dist/lib/task/control-recovery.js +128 -0
- package/dist/lib/task/create.js +62 -9
- package/dist/lib/task/decision-details.js +2 -46
- package/dist/lib/task/events.js +120 -9
- package/dist/lib/task/finalization-state.js +9 -0
- package/dist/lib/task/human-override.js +3 -6
- package/dist/lib/task/implementation-inputs.js +3 -3
- package/dist/lib/task/intermediate-cleanup.js +383 -0
- package/dist/lib/task/invalidation.js +18 -65
- package/dist/lib/task/ledger-intents.js +7 -22
- package/dist/lib/task/ledger.js +1 -3
- package/dist/lib/task/lifecycle.js +19 -1
- package/dist/lib/task/local-artifact-finalization.js +132 -157
- package/dist/lib/task/manual-validation-completion.js +26 -0
- package/dist/lib/task/manual-validation-evidence.js +130 -0
- package/dist/lib/task/manual-validation-receipt.js +101 -0
- package/dist/lib/task/manual-validation-shared.js +40 -0
- package/dist/lib/task/manual-validation-transaction.js +243 -0
- package/dist/lib/task/markdown.js +47 -0
- package/dist/lib/task/orchestration.js +14 -17
- package/dist/lib/task/qualification-audit.js +20 -34
- package/dist/lib/task/review-artifacts.js +2 -18
- package/dist/lib/task/review-command.js +25 -0
- package/dist/lib/task/review-finalization.js +125 -193
- package/dist/lib/task/review-fingerprint.js +2 -2
- package/dist/lib/task/sections.js +34 -56
- package/dist/lib/task/verification-engine.js +116 -323
- package/dist/lib/task/verification.js +1 -1
- package/dist/lib/task/workflow-command.js +17 -0
- package/dist/lib/task/workflow-warning-intents.js +0 -16
- package/dist/lib/task/workflow-warnings.js +28 -2
- package/dist/lib/update.js +2 -0
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +223 -33
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +3 -2
- package/lib/fs/durable-write.ts +29 -0
- package/lib/fs/file-lock.ts +113 -0
- package/lib/host-control/audit.ts +22 -0
- package/lib/host-control/client.ts +172 -0
- package/lib/host-control/command.ts +60 -0
- package/lib/host-control/path.ts +167 -0
- package/lib/host-control/secure-fs.ts +135 -0
- package/lib/host-control/server.ts +180 -0
- package/lib/host-control/service.ts +133 -0
- package/lib/init.ts +15 -2
- package/lib/internal/cli-route-inventory.ts +3 -1
- package/lib/internal/codex-lifecycle.ts +6 -6
- package/lib/internal/demo-promotion.ts +354 -0
- package/lib/internal/demo-transcript.ts +419 -0
- package/lib/internal/host-control.ts +28 -0
- package/lib/internal/manual-validation.ts +306 -0
- package/lib/internal/platform-pr.ts +15 -4
- package/lib/internal/release-workflow.ts +74 -82
- package/lib/internal/sandbox-control.ts +162 -1
- package/lib/internal/task-artifact.ts +13 -216
- package/lib/internal/task-create.ts +5 -5
- package/lib/internal/task-event.ts +20 -11
- package/lib/internal/task-operation-registry.ts +111 -3
- package/lib/internal/task-review.ts +6 -51
- package/lib/internal/task-validate.ts +26 -10
- package/lib/owned-path.ts +26 -0
- package/lib/platform/check-status.ts +9 -0
- package/lib/platform/comment-safety.ts +5 -0
- package/lib/platform/completion-backfill.ts +1 -1
- package/lib/platform/github-data.ts +322 -0
- package/lib/platform/github-provider.ts +15 -20
- package/lib/platform/issue-comments.ts +3 -4
- package/lib/platform/issues.ts +4 -245
- package/lib/platform/pr-checks.ts +13 -77
- package/lib/platform/pr-summary.ts +61 -5
- package/lib/platform/pull-requests.ts +3 -170
- package/lib/platform/snapshots.ts +53 -0
- package/lib/platform/verification-sync.ts +145 -155
- package/lib/release/workflow.ts +3 -3
- package/lib/sandbox/commands/create.ts +7 -6
- package/lib/sandbox/commands/rm.ts +3 -1909
- package/lib/sandbox/control/audit.ts +253 -0
- package/lib/sandbox/control/client.ts +109 -66
- package/lib/sandbox/control/executor.ts +134 -74
- package/lib/sandbox/control/finalization-response.ts +29 -0
- package/lib/sandbox/control/identity-sentinel.ts +137 -0
- package/lib/sandbox/control/lifecycle.ts +45 -3
- package/lib/sandbox/control/native-file-lock.ts +6 -107
- package/lib/sandbox/control/protocol.ts +75 -6
- package/lib/sandbox/control/server.ts +538 -123
- package/lib/sandbox/control/state.ts +78 -0
- package/lib/sandbox/control/task-workflow.ts +95 -0
- package/lib/sandbox/control/workflow-executor.ts +102 -0
- package/lib/sandbox/index.ts +2 -2
- package/lib/sandbox/recovery.ts +28 -5
- package/lib/sandbox/removal.ts +2144 -0
- package/lib/sandbox/task-cleanup.ts +209 -0
- package/lib/sandbox/workspace-view.ts +17 -15
- package/lib/task/activity-intent.ts +2 -2
- package/lib/task/activity-log.ts +27 -19
- package/lib/task/artifact-command.ts +104 -0
- package/lib/task/artifact-lifecycle.ts +31 -26
- package/lib/task/artifact-name.ts +55 -0
- package/lib/task/artifact-operations.ts +22 -68
- package/lib/task/artifact-receipts.ts +3 -14
- package/lib/task/artifact-repair-intent.ts +99 -18
- package/lib/task/artifact-schema.ts +5 -34
- package/lib/task/capabilities.ts +13 -22
- package/lib/task/codex-orchestration.ts +85 -24
- package/lib/task/commit-identity.ts +31 -0
- package/lib/task/commit-operation.ts +2 -16
- package/lib/task/control-authority.ts +502 -5
- package/lib/task/control-recovery.ts +174 -0
- package/lib/task/create.ts +63 -8
- package/lib/task/decision-details.ts +3 -71
- package/lib/task/events.ts +131 -10
- package/lib/task/finalization-state.ts +11 -0
- package/lib/task/human-override.ts +3 -8
- package/lib/task/implementation-inputs.ts +3 -3
- package/lib/task/intermediate-cleanup.ts +455 -0
- package/lib/task/invalidation.ts +18 -62
- package/lib/task/ledger-intents.ts +7 -24
- package/lib/task/ledger.ts +1 -3
- package/lib/task/lifecycle.ts +17 -1
- package/lib/task/local-artifact-finalization.ts +148 -182
- package/lib/task/manual-validation-completion.ts +40 -0
- package/lib/task/manual-validation-evidence.ts +209 -0
- package/lib/task/manual-validation-receipt.ts +152 -0
- package/lib/task/manual-validation-shared.ts +50 -0
- package/lib/task/manual-validation-transaction.ts +304 -0
- package/lib/task/markdown.ts +70 -0
- package/lib/task/orchestration.ts +19 -19
- package/lib/task/qualification-audit.ts +22 -33
- package/lib/task/review-artifacts.ts +1 -20
- package/lib/task/review-command.ts +23 -0
- package/lib/task/review-finalization.ts +131 -259
- package/lib/task/review-fingerprint.ts +2 -2
- package/lib/task/sections.ts +33 -54
- package/lib/task/verification-engine.ts +110 -366
- package/lib/task/verification-types.ts +17 -0
- package/lib/task/verification.ts +1 -1
- package/lib/task/workflow-command.ts +18 -0
- package/lib/task/workflow-warning-intents.ts +0 -13
- package/lib/task/workflow-warnings.ts +21 -2
- package/lib/update.ts +6 -0
- package/package.json +6 -6
- package/templates/.agents/hooks/lifecycle-delegation.js +6 -6
- package/templates/.agents/rules/testing-discipline.en.md +2 -0
- package/templates/.agents/rules/testing-discipline.zh-CN.md +2 -0
- package/templates/.agents/scripts/package.json +3 -0
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +32 -20
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +32 -20
- package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +1 -1
- package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/complete-manual-validation/reference/report-template.en.md +2 -0
- package/templates/.agents/skills/complete-manual-validation/reference/report-template.zh-CN.md +2 -0
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +16 -7
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +16 -7
- package/templates/.agents/skills/complete-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/release/SKILL.en.md +7 -3
- package/templates/.agents/skills/release/SKILL.zh-CN.md +7 -3
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +20 -3
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +20 -3
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +4 -1
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +4 -1
- package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +2 -0
- package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +2 -0
- package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/test/SKILL.en.md +17 -6
- package/templates/.agents/skills/test/SKILL.zh-CN.md +17 -6
- package/templates/.claude/commands/run-manual-validation.en.md +1 -1
- package/templates/.claude/commands/run-manual-validation.zh-CN.md +1 -1
- package/dist/lib/platform/adapters.js +0 -56
- package/lib/platform/adapters.ts +0 -197
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import type { SandboxControlManifest, SandboxControlFamily, SandboxControlRequest } from './protocol.ts';
|
|
5
|
+
import { identityDigest as digestIdentity } from './identity-sentinel.ts';
|
|
6
|
+
import { acquireSandboxResourceLock } from './native-file-lock.ts';
|
|
7
|
+
import { fsyncDirectory, writeDurableFile } from '../../fs/durable-write.ts';
|
|
8
|
+
|
|
9
|
+
export const SANDBOX_CONTROL_AUDIT_PHASES = Object.freeze([
|
|
10
|
+
'validated', 'gated', 'reserved', 'prepared', 'accepted-authorized', 'accepted-committed',
|
|
11
|
+
'start-authorized', 'started-committed', 'completed', 'evidence-written',
|
|
12
|
+
'publish-authorized', 'published-committed', 'rejected', 'recovered'
|
|
13
|
+
] as const);
|
|
14
|
+
export type SandboxControlAuditPhase = typeof SANDBOX_CONTROL_AUDIT_PHASES[number];
|
|
15
|
+
export type SandboxControlAuditOutcome = 'not-executed' | 'in-progress' | 'success' | 'failure' | 'unknown' | 'rejected';
|
|
16
|
+
|
|
17
|
+
export type SandboxControlAuditContext = Readonly<{
|
|
18
|
+
requestId: string;
|
|
19
|
+
taskId: string | null;
|
|
20
|
+
family: SandboxControlFamily | null;
|
|
21
|
+
operation: string | null;
|
|
22
|
+
phase: SandboxControlAuditPhase;
|
|
23
|
+
outcome: SandboxControlAuditOutcome;
|
|
24
|
+
generation: string;
|
|
25
|
+
identityDigest: string;
|
|
26
|
+
}>;
|
|
27
|
+
|
|
28
|
+
export type SandboxControlTransition = Readonly<{
|
|
29
|
+
version: 1;
|
|
30
|
+
requestId: string;
|
|
31
|
+
generation: string;
|
|
32
|
+
phase: SandboxControlAuditPhase;
|
|
33
|
+
at: number;
|
|
34
|
+
identityDigest: string;
|
|
35
|
+
reference: string | null;
|
|
36
|
+
}>;
|
|
37
|
+
|
|
38
|
+
const REQUEST_ID = /^[a-f0-9-]{16,64}$/u;
|
|
39
|
+
const SAFE_OPERATION = /^[a-z][a-z0-9.-]{0,96}$/u;
|
|
40
|
+
const AUDIT_MAX_BYTES = 1024 * 1024;
|
|
41
|
+
|
|
42
|
+
export function requestAuditFields(manifest: SandboxControlManifest, request: SandboxControlRequest) {
|
|
43
|
+
return {
|
|
44
|
+
requestId: request.id, requestFamily: request.family, sandboxTaskId: manifest.taskId,
|
|
45
|
+
requestGeneration: request.generation, requestIssuedAt: request.issuedAt, requestExpiresAt: request.expiresAt
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function resultAuditFields(result: Readonly<{ exitCode: number; stdout: string; stderr: string }>) {
|
|
50
|
+
return {
|
|
51
|
+
exitCode: result.exitCode,
|
|
52
|
+
outputBytes: Buffer.byteLength(result.stdout, 'utf8'), errorBytes: Buffer.byteLength(result.stderr, 'utf8'),
|
|
53
|
+
outputDigest: createHash('sha256').update(result.stdout, 'utf8').digest('hex'),
|
|
54
|
+
errorDigest: createHash('sha256').update(result.stderr, 'utf8').digest('hex')
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function auditRoot(manifest: SandboxControlManifest): string {
|
|
59
|
+
return path.dirname(path.resolve(manifest.publicStatusDir));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function auditPath(manifest: SandboxControlManifest): string {
|
|
63
|
+
return path.join(auditRoot(manifest), 'audit.ndjson');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function manifestIdentityDigest(manifest: SandboxControlManifest): string {
|
|
67
|
+
return digestIdentity({
|
|
68
|
+
version: 1,
|
|
69
|
+
mode: manifest.mode,
|
|
70
|
+
taskId: manifest.taskId,
|
|
71
|
+
generation: manifest.generation,
|
|
72
|
+
controlRootId: manifest.controlRootId
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function createSandboxControlAuditContext(
|
|
77
|
+
manifest: SandboxControlManifest,
|
|
78
|
+
params: Readonly<{
|
|
79
|
+
requestId: string;
|
|
80
|
+
family: SandboxControlFamily | null;
|
|
81
|
+
operation?: string | null;
|
|
82
|
+
phase: SandboxControlAuditPhase;
|
|
83
|
+
outcome: SandboxControlAuditOutcome;
|
|
84
|
+
}>
|
|
85
|
+
): SandboxControlAuditContext {
|
|
86
|
+
if (!REQUEST_ID.test(params.requestId)) throw new Error('SANDBOX_CONTROL_AUDIT_REQUEST_INVALID');
|
|
87
|
+
if (params.operation !== null && params.operation !== undefined && !SAFE_OPERATION.test(params.operation)) {
|
|
88
|
+
throw new Error('SANDBOX_CONTROL_AUDIT_OPERATION_INVALID');
|
|
89
|
+
}
|
|
90
|
+
if (!SANDBOX_CONTROL_AUDIT_PHASES.includes(params.phase)) throw new Error('SANDBOX_CONTROL_AUDIT_PHASE_INVALID');
|
|
91
|
+
return {
|
|
92
|
+
requestId: params.requestId,
|
|
93
|
+
taskId: manifest.taskId,
|
|
94
|
+
family: params.family,
|
|
95
|
+
operation: params.operation ?? null,
|
|
96
|
+
phase: params.phase,
|
|
97
|
+
outcome: params.outcome,
|
|
98
|
+
generation: manifest.generation,
|
|
99
|
+
identityDigest: manifestIdentityDigest(manifest)
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function safeFields(fields: Readonly<Record<string, string | number | boolean | null>>): Record<string, string | number | boolean | null> {
|
|
104
|
+
const result: Record<string, string | number | boolean | null> = {};
|
|
105
|
+
for (const [key, value] of Object.entries(fields)) {
|
|
106
|
+
if (!/^[a-z][a-zA-Z0-9]{0,63}$/u.test(key)) continue;
|
|
107
|
+
if (/(token|secret|credential|proof|password|path|root|cwd|stdout|stderr|args|command|taskref)/iu.test(key)) continue;
|
|
108
|
+
if (typeof value === 'string' && value.length > 256) continue;
|
|
109
|
+
result[key] = value;
|
|
110
|
+
}
|
|
111
|
+
return result;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function requestHasTerminalTransition(directory: string): boolean {
|
|
115
|
+
const transitions = path.join(directory, 'transitions');
|
|
116
|
+
if (!fs.existsSync(transitions)) return false;
|
|
117
|
+
return ['published-committed', 'recovered'].some((phase) => fs.existsSync(path.join(transitions, `${phase}.json`)));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function hasActiveRequest(manifest: SandboxControlManifest): boolean {
|
|
121
|
+
if (!fs.existsSync(manifest.processingDir)) return false;
|
|
122
|
+
return fs.readdirSync(manifest.processingDir, { withFileTypes: true })
|
|
123
|
+
.some((entry) => entry.isDirectory() && REQUEST_ID.test(entry.name)
|
|
124
|
+
&& !requestHasTerminalTransition(path.join(manifest.processingDir, entry.name)));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function rotateAuditUnderLock(manifest: SandboxControlManifest, filePath: string): void {
|
|
128
|
+
if (!fs.existsSync(filePath) || fs.statSync(filePath).size < AUDIT_MAX_BYTES) return;
|
|
129
|
+
if (hasActiveRequest(manifest)) return;
|
|
130
|
+
fs.rmSync(`${filePath}.1`, { force: true });
|
|
131
|
+
fs.renameSync(filePath, `${filePath}.1`);
|
|
132
|
+
fsyncDirectory(auditRoot(manifest));
|
|
133
|
+
const descriptor = fs.openSync(filePath, 'a', 0o600);
|
|
134
|
+
try {
|
|
135
|
+
fs.fsyncSync(descriptor);
|
|
136
|
+
} finally {
|
|
137
|
+
fs.closeSync(descriptor);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function appendAuditRecord(
|
|
142
|
+
manifest: SandboxControlManifest,
|
|
143
|
+
record: Readonly<Record<string, unknown>>,
|
|
144
|
+
critical: boolean
|
|
145
|
+
): void {
|
|
146
|
+
const root = auditRoot(manifest);
|
|
147
|
+
fs.mkdirSync(root, { recursive: true, mode: 0o700 });
|
|
148
|
+
const lock = acquireSandboxResourceLock(`sandbox-control-audit:${root}`);
|
|
149
|
+
try {
|
|
150
|
+
const filePath = auditPath(manifest);
|
|
151
|
+
rotateAuditUnderLock(manifest, filePath);
|
|
152
|
+
const descriptor = fs.openSync(filePath, 'a', 0o600);
|
|
153
|
+
try {
|
|
154
|
+
fs.writeSync(descriptor, `${JSON.stringify(record)}\n`, undefined, 'utf8');
|
|
155
|
+
if (critical) fs.fsyncSync(descriptor);
|
|
156
|
+
} finally {
|
|
157
|
+
fs.closeSync(descriptor);
|
|
158
|
+
}
|
|
159
|
+
} catch (error) {
|
|
160
|
+
if (critical) throw new Error(`SANDBOX_CONTROL_AUDIT_UNAVAILABLE: ${error instanceof Error ? error.message : String(error)}`);
|
|
161
|
+
} finally {
|
|
162
|
+
lock.release();
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function appendCriticalAudit(
|
|
167
|
+
manifest: SandboxControlManifest,
|
|
168
|
+
context: SandboxControlAuditContext,
|
|
169
|
+
fields: Readonly<Record<string, string | number | boolean | null>> = {},
|
|
170
|
+
now = Date.now()
|
|
171
|
+
): void {
|
|
172
|
+
appendAuditRecord(manifest, {
|
|
173
|
+
version: 2,
|
|
174
|
+
at: now,
|
|
175
|
+
event: context.phase,
|
|
176
|
+
...context,
|
|
177
|
+
...safeFields(fields)
|
|
178
|
+
}, true);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export function appendDiagnosticAudit(
|
|
182
|
+
manifest: SandboxControlManifest,
|
|
183
|
+
event: string,
|
|
184
|
+
fields: Readonly<Record<string, string | number | boolean | null>> = {},
|
|
185
|
+
now = Date.now()
|
|
186
|
+
): void {
|
|
187
|
+
try {
|
|
188
|
+
appendAuditRecord(manifest, {
|
|
189
|
+
version: 2,
|
|
190
|
+
at: now,
|
|
191
|
+
event,
|
|
192
|
+
generation: manifest.generation,
|
|
193
|
+
identityDigest: manifestIdentityDigest(manifest),
|
|
194
|
+
...safeFields(fields)
|
|
195
|
+
}, false);
|
|
196
|
+
} catch {
|
|
197
|
+
// Diagnostics are deliberately best effort.
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function transitionPath(manifest: SandboxControlManifest, requestId: string, phase: SandboxControlAuditPhase): string {
|
|
202
|
+
if (!REQUEST_ID.test(requestId)) throw new Error('SANDBOX_CONTROL_TRANSITION_REQUEST_INVALID');
|
|
203
|
+
return path.join(manifest.processingDir, requestId, 'transitions', `${phase}.json`);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export function writeSandboxControlTransition(
|
|
207
|
+
manifest: SandboxControlManifest,
|
|
208
|
+
params: Readonly<{
|
|
209
|
+
requestId: string;
|
|
210
|
+
phase: SandboxControlAuditPhase;
|
|
211
|
+
reference?: string | null;
|
|
212
|
+
now?: number;
|
|
213
|
+
}>
|
|
214
|
+
): SandboxControlTransition {
|
|
215
|
+
const transition: SandboxControlTransition = {
|
|
216
|
+
version: 1,
|
|
217
|
+
requestId: params.requestId,
|
|
218
|
+
generation: manifest.generation,
|
|
219
|
+
phase: params.phase,
|
|
220
|
+
at: params.now ?? Date.now(),
|
|
221
|
+
identityDigest: manifestIdentityDigest(manifest),
|
|
222
|
+
reference: params.reference ?? null
|
|
223
|
+
};
|
|
224
|
+
const target = transitionPath(manifest, params.requestId, params.phase);
|
|
225
|
+
fs.mkdirSync(path.dirname(target), { recursive: true, mode: 0o700 });
|
|
226
|
+
try {
|
|
227
|
+
writeDurableFile(target, `${JSON.stringify(transition)}\n`, { mode: 0o600, replace: false });
|
|
228
|
+
} catch (error) {
|
|
229
|
+
if ((error as NodeJS.ErrnoException).code !== 'EEXIST') throw error;
|
|
230
|
+
const existing = readSandboxControlTransition(manifest, params.requestId, params.phase);
|
|
231
|
+
if (JSON.stringify(existing) !== JSON.stringify(transition)) throw new Error('SANDBOX_CONTROL_TRANSITION_CONFLICT');
|
|
232
|
+
}
|
|
233
|
+
return transition;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export function readSandboxControlTransition(
|
|
237
|
+
manifest: SandboxControlManifest,
|
|
238
|
+
requestId: string,
|
|
239
|
+
phase: SandboxControlAuditPhase
|
|
240
|
+
): SandboxControlTransition {
|
|
241
|
+
const filePath = transitionPath(manifest, requestId, phase);
|
|
242
|
+
if (!fs.existsSync(filePath)) throw new Error('SANDBOX_CONTROL_TRANSITION_MISSING');
|
|
243
|
+
let value: unknown;
|
|
244
|
+
try { value = JSON.parse(fs.readFileSync(filePath, 'utf8')); } catch { throw new Error('SANDBOX_CONTROL_TRANSITION_INVALID'); }
|
|
245
|
+
const candidate = value as Partial<SandboxControlTransition> | null;
|
|
246
|
+
if (!candidate || candidate.version !== 1 || candidate.requestId !== requestId
|
|
247
|
+
|| candidate.generation !== manifest.generation || candidate.phase !== phase
|
|
248
|
+
|| !Number.isSafeInteger(candidate.at) || typeof candidate.identityDigest !== 'string'
|
|
249
|
+
|| (candidate.reference !== null && typeof candidate.reference !== 'string')) {
|
|
250
|
+
throw new Error('SANDBOX_CONTROL_TRANSITION_INVALID');
|
|
251
|
+
}
|
|
252
|
+
return candidate as SandboxControlTransition;
|
|
253
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
|
+
import { hasTaskBoundMarker } from '../../internal/task-operation-registry.ts';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
import { randomUUID } from 'node:crypto';
|
|
4
5
|
import {
|
|
@@ -12,7 +13,8 @@ import {
|
|
|
12
13
|
type SandboxTaskCreateRequest,
|
|
13
14
|
type SandboxTaskCommandRequest,
|
|
14
15
|
type SandboxTaskFinalizationRequest,
|
|
15
|
-
type SandboxCodexControllerRequest
|
|
16
|
+
type SandboxCodexControllerRequest,
|
|
17
|
+
type SandboxTaskWorkflowRequest
|
|
16
18
|
} from './protocol.ts';
|
|
17
19
|
import type {
|
|
18
20
|
CodexControllerLeaseProofV1,
|
|
@@ -23,65 +25,13 @@ import { normalizeAgentToken } from '../../agent-clients/tokens.ts';
|
|
|
23
25
|
import { readSandboxControlPayload, readSandboxControlStatus } from './state.ts';
|
|
24
26
|
import type { TaskCreateCandidateV1 } from '../../task/create.ts';
|
|
25
27
|
import { accessSandboxTaskView, taskViewFromStatus, type TaskViewAccessEffect } from './task-view.ts';
|
|
28
|
+
import { readSandboxControlIdentitySentinel } from './identity-sentinel.ts';
|
|
29
|
+
import type { TaskWorkflowRequest } from './task-workflow.ts';
|
|
30
|
+
import type { LifecycleAuthorityRequestV1 } from '../../task/control-authority.ts';
|
|
31
|
+
import { configuredShortIdLength, resolveShortIdReadOnly } from '../../task/short-id.ts';
|
|
26
32
|
|
|
27
33
|
const SANDBOX_CONTROL_RESPONSE_SETTLE_MS = 250;
|
|
28
34
|
|
|
29
|
-
type SandboxControlEnvironmentClassification = Readonly<{
|
|
30
|
-
kind: 'direct' | 'controlled' | 'invalid';
|
|
31
|
-
mode?: 'task-bound' | 'branch-only';
|
|
32
|
-
code?: 'TASK_CONTROL_TRANSPORT_INVALID';
|
|
33
|
-
message?: string;
|
|
34
|
-
}>;
|
|
35
|
-
|
|
36
|
-
export function classifySandboxControlEnvironment(
|
|
37
|
-
env: NodeJS.ProcessEnv = process.env
|
|
38
|
-
): SandboxControlEnvironmentClassification {
|
|
39
|
-
const controlKeys = [
|
|
40
|
-
'AGENT_INFRA_CONTROL_TOKEN',
|
|
41
|
-
'AGENT_INFRA_CONTROL_GENERATION',
|
|
42
|
-
'AGENT_INFRA_CONTROL_DIR',
|
|
43
|
-
'AGENT_INFRA_CONTROL_STATUS_DIR'
|
|
44
|
-
] as const;
|
|
45
|
-
const hasControlMarker = controlKeys.some((key) => Boolean(env[key]));
|
|
46
|
-
const hasTaskIdentity = Boolean(env.AGENT_INFRA_TASK_ID);
|
|
47
|
-
const hasRuntime = Boolean(env.AGENT_INFRA_RUNTIME_DIR);
|
|
48
|
-
const hasExecutorMarker = Boolean(env.AGENT_INFRA_EXECUTOR_MANIFEST);
|
|
49
|
-
const hasControllerBinding = Boolean(env.AGENT_INFRA_CONTROL_CONTROLLER_BINDING);
|
|
50
|
-
const hasAnyMarker = hasControlMarker || hasTaskIdentity || hasRuntime || hasExecutorMarker || hasControllerBinding;
|
|
51
|
-
const hasCompleteControl = controlKeys.every((key) => Boolean(env[key]));
|
|
52
|
-
|
|
53
|
-
if (!hasAnyMarker) return { kind: 'direct' };
|
|
54
|
-
if (hasExecutorMarker) {
|
|
55
|
-
return {
|
|
56
|
-
kind: 'invalid',
|
|
57
|
-
code: 'TASK_CONTROL_TRANSPORT_INVALID',
|
|
58
|
-
message: 'executor context is only valid for sandbox-control execute'
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
if (hasControllerBinding) {
|
|
62
|
-
return {
|
|
63
|
-
kind: 'invalid',
|
|
64
|
-
code: 'TASK_CONTROL_TRANSPORT_INVALID',
|
|
65
|
-
message: 'sandbox client control configuration is incomplete or conflicting'
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
if (!hasCompleteControl) {
|
|
69
|
-
return {
|
|
70
|
-
kind: 'invalid',
|
|
71
|
-
code: 'TASK_CONTROL_TRANSPORT_INVALID',
|
|
72
|
-
message: 'sandbox client control configuration is incomplete or conflicting'
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
if (hasTaskIdentity !== hasRuntime) {
|
|
76
|
-
return {
|
|
77
|
-
kind: 'invalid',
|
|
78
|
-
code: 'TASK_CONTROL_TRANSPORT_INVALID',
|
|
79
|
-
message: 'sandbox task identity and runtime binding must be provided together'
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
return { kind: 'controlled', mode: hasTaskIdentity ? 'task-bound' : 'branch-only' };
|
|
83
|
-
}
|
|
84
|
-
|
|
85
35
|
export class SandboxControlClientError extends Error {
|
|
86
36
|
readonly detail: SandboxControlError;
|
|
87
37
|
readonly accepted: boolean;
|
|
@@ -109,12 +59,6 @@ function clientError(
|
|
|
109
59
|
throw new SandboxControlClientError({ code, message: `${code}: ${message}`, retryable }, accepted, requestId);
|
|
110
60
|
}
|
|
111
61
|
|
|
112
|
-
function hasTaskBoundMarker(env: NodeJS.ProcessEnv = process.env): boolean {
|
|
113
|
-
return Boolean(env.AGENT_INFRA_TASK_ID)
|
|
114
|
-
&& Boolean(env.AGENT_INFRA_CONTROL_TOKEN || env.AGENT_INFRA_CONTROL_GENERATION
|
|
115
|
-
|| env.AGENT_INFRA_CONTROL_STATUS_DIR || env.AGENT_INFRA_RUNTIME_DIR || env.AGENT_INFRA_CONTROL_DIR);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
62
|
function preflight(
|
|
119
63
|
statusDir: string,
|
|
120
64
|
generation: string,
|
|
@@ -122,6 +66,23 @@ function preflight(
|
|
|
122
66
|
now = Date.now(),
|
|
123
67
|
env: NodeJS.ProcessEnv = process.env
|
|
124
68
|
): void {
|
|
69
|
+
if (fs.existsSync(statusDir)) {
|
|
70
|
+
let identity;
|
|
71
|
+
try {
|
|
72
|
+
identity = readSandboxControlIdentitySentinel(statusDir);
|
|
73
|
+
} catch (error) {
|
|
74
|
+
const code = error instanceof Error && error.message.endsWith('MISSING')
|
|
75
|
+
? 'SANDBOX_CONTROL_IDENTITY_MISSING'
|
|
76
|
+
: 'SANDBOX_CONTROL_IDENTITY_MALFORMED';
|
|
77
|
+
clientError(code, 'sandbox control identity is missing or invalid', false);
|
|
78
|
+
}
|
|
79
|
+
if (identity.generation !== generation) {
|
|
80
|
+
clientError('SANDBOX_CONTROL_IDENTITY_GENERATION_MISMATCH', 'sandbox control identity generation does not match the request', false);
|
|
81
|
+
}
|
|
82
|
+
if (env.AGENT_INFRA_CONTROL_ROOT_ID && identity.controlRootId !== env.AGENT_INFRA_CONTROL_ROOT_ID) {
|
|
83
|
+
clientError('SANDBOX_CONTROL_IDENTITY_ROOT_ID_MISMATCH', 'sandbox control identity root does not match the client configuration', false);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
125
86
|
let status;
|
|
126
87
|
try {
|
|
127
88
|
status = readSandboxControlStatus(statusDir);
|
|
@@ -159,6 +120,7 @@ function preflight(
|
|
|
159
120
|
function taskViewEffectForRequest(request: SandboxControlRequest): TaskViewAccessEffect | null {
|
|
160
121
|
if (request.family === 'task-lifecycle' || request.family === 'task-finalization') return 'progress';
|
|
161
122
|
if (request.family === 'task-orchestration') return request.args[1] === 'status' ? 'diagnostic' : 'progress';
|
|
123
|
+
if (request.family === 'task-workflow') return request.workflow.operation === 'artifact-inspect' || request.workflow.operation === 'decision-next-id' ? 'diagnostic' : 'progress';
|
|
162
124
|
return null;
|
|
163
125
|
}
|
|
164
126
|
|
|
@@ -204,6 +166,44 @@ function cancelPendingRequest(requestPath: string): boolean {
|
|
|
204
166
|
}
|
|
205
167
|
}
|
|
206
168
|
|
|
169
|
+
const CONTROL_TASK_ID_RE = /^TASK-\d{8}-\d{6}$/;
|
|
170
|
+
const CONTROL_SHORT_ID_RE = /^\d+$/;
|
|
171
|
+
|
|
172
|
+
type CanonicalRequestTask =
|
|
173
|
+
| Readonly<{ kind: 'none' }>
|
|
174
|
+
| Readonly<{ kind: 'task'; taskId: string }>
|
|
175
|
+
| Readonly<{ kind: 'unresolved' }>;
|
|
176
|
+
|
|
177
|
+
// Use the current short-id contract for the first visible active registry.
|
|
178
|
+
export function resolveVisibleActiveShortId(ref: string): string | null {
|
|
179
|
+
let dir = process.cwd();
|
|
180
|
+
for (let depth = 0; depth < 64; depth += 1) {
|
|
181
|
+
const registryPath = path.join(dir, '.agents', 'workspace', 'active', '.short-ids.json');
|
|
182
|
+
if (fs.existsSync(registryPath)) {
|
|
183
|
+
const resolved = resolveShortIdReadOnly(ref, dir, { shortIdLength: configuredShortIdLength(dir) });
|
|
184
|
+
return resolved.ok ? resolved.taskId : null;
|
|
185
|
+
}
|
|
186
|
+
const parent = path.dirname(dir);
|
|
187
|
+
if (parent === dir) break;
|
|
188
|
+
dir = parent;
|
|
189
|
+
}
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// A request's leading argument is only a task reference for some families and
|
|
194
|
+
// may be a documented short id. Compare canonical ids so a bound sandbox keeps
|
|
195
|
+
// refusing other tasks without rejecting its own task's short id.
|
|
196
|
+
function canonicalRequestTask(request: SandboxControlRequest): CanonicalRequestTask {
|
|
197
|
+
const ref = request.family === 'task-workflow'
|
|
198
|
+
? request.workflow.taskId
|
|
199
|
+
: 'args' in request ? request.args[0] ?? null : null;
|
|
200
|
+
if (!ref) return { kind: 'none' };
|
|
201
|
+
if (CONTROL_TASK_ID_RE.test(ref)) return { kind: 'task', taskId: ref };
|
|
202
|
+
if (!CONTROL_SHORT_ID_RE.test(ref)) return { kind: 'none' };
|
|
203
|
+
const resolved = resolveVisibleActiveShortId(ref);
|
|
204
|
+
return resolved ? { kind: 'task', taskId: resolved } : { kind: 'unresolved' };
|
|
205
|
+
}
|
|
206
|
+
|
|
207
207
|
function exchangeSandboxControl(request: SandboxControlRequest, params: Readonly<{
|
|
208
208
|
channelDir?: string;
|
|
209
209
|
statusDir?: string;
|
|
@@ -213,6 +213,20 @@ function exchangeSandboxControl(request: SandboxControlRequest, params: Readonly
|
|
|
213
213
|
const statusDir = params.statusDir ?? process.env.AGENT_INFRA_CONTROL_STATUS_DIR ?? '/run/agent-infra/control-status';
|
|
214
214
|
const taskViewEffect = taskViewEffectForRequest(request);
|
|
215
215
|
preflight(statusDir, request.generation, taskViewEffect);
|
|
216
|
+
const identityPath = path.join(statusDir, 'identity.json');
|
|
217
|
+
if (fs.existsSync(statusDir)) {
|
|
218
|
+
const identity = readSandboxControlIdentitySentinel(statusDir);
|
|
219
|
+
const requestTask = canonicalRequestTask(request);
|
|
220
|
+
if (identity.mode === 'task-bound'
|
|
221
|
+
&& ((process.env.AGENT_INFRA_TASK_ID && process.env.AGENT_INFRA_TASK_ID !== identity.taskId)
|
|
222
|
+
|| requestTask.kind === 'unresolved'
|
|
223
|
+
|| (requestTask.kind === 'task' && requestTask.taskId !== identity.taskId))) {
|
|
224
|
+
clientError('SANDBOX_CONTROL_IDENTITY_TOPOLOGY_MISMATCH', 'request task does not match the sandbox identity', false);
|
|
225
|
+
}
|
|
226
|
+
if (identity.mode === 'branch-only' && (request.family === 'task-finalization' || request.family === 'task-workflow')) {
|
|
227
|
+
clientError('SANDBOX_CONTROL_BRANCH_ONLY', 'branch-only sandboxes cannot finalize tasks', false);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
216
230
|
const encoded = `${JSON.stringify(request)}\n`;
|
|
217
231
|
if (Buffer.byteLength(encoded, 'utf8') > SANDBOX_CONTROL_MAX_BYTES) {
|
|
218
232
|
clientError('SANDBOX_CONTROL_REQUEST_TOO_LARGE', 'request exceeds the control limit', false, false, request.id);
|
|
@@ -370,7 +384,7 @@ export function requestSandboxControl(params: Readonly<{
|
|
|
370
384
|
token?: string; generation?: string; timeoutMs?: number;
|
|
371
385
|
}>): SandboxControlResponse {
|
|
372
386
|
if (!isSandboxControlFamily(params.family)) clientError('SANDBOX_CONTROL_COMMAND_DENIED', `'${params.family}' is not allowed`, false);
|
|
373
|
-
if (params.family === 'task-create' || params.family === 'codex-controller' || params.family === 'task-finalization') {
|
|
387
|
+
if (params.family === 'task-create' || params.family === 'codex-controller' || params.family === 'task-finalization' || params.family === 'task-workflow') {
|
|
374
388
|
clientError('SANDBOX_CONTROL_COMMAND_DENIED', `'${params.family}' requires a typed request`, false);
|
|
375
389
|
}
|
|
376
390
|
const auth = authority(params);
|
|
@@ -389,6 +403,7 @@ export function requestSandboxTaskControl(params: Readonly<{
|
|
|
389
403
|
family: 'task-lifecycle' | 'task-orchestration';
|
|
390
404
|
args: string[];
|
|
391
405
|
controllerProof: CodexControllerLeaseProofV1 | null;
|
|
406
|
+
authority?: LifecycleAuthorityRequestV1;
|
|
392
407
|
channelDir?: string;
|
|
393
408
|
statusDir?: string;
|
|
394
409
|
token?: string;
|
|
@@ -399,14 +414,15 @@ export function requestSandboxTaskControl(params: Readonly<{
|
|
|
399
414
|
const issuedAt = Date.now();
|
|
400
415
|
const request: SandboxTaskCommandRequest = {
|
|
401
416
|
version: 3,
|
|
402
|
-
id: randomUUID(),
|
|
417
|
+
id: params.authority?.requestId ?? randomUUID(),
|
|
403
418
|
...auth,
|
|
404
419
|
issuedAt,
|
|
405
420
|
expiresAt: issuedAt + SANDBOX_CONTROL_ADMISSION_WINDOW_MS,
|
|
406
421
|
family: params.family,
|
|
407
422
|
args: params.args,
|
|
408
423
|
controllerProcess: null,
|
|
409
|
-
controllerProof: params.controllerProof
|
|
424
|
+
controllerProof: params.authority ? null : params.controllerProof,
|
|
425
|
+
...(params.authority ? { authority: params.authority } : {})
|
|
410
426
|
};
|
|
411
427
|
return exchangeSandboxControl(request, params);
|
|
412
428
|
}
|
|
@@ -439,6 +455,33 @@ export function requestSandboxTaskFinalization(params: Readonly<{
|
|
|
439
455
|
return exchangeSandboxControl(request, params);
|
|
440
456
|
}
|
|
441
457
|
|
|
458
|
+
export function requestSandboxTaskWorkflow(params: Readonly<{
|
|
459
|
+
workflow: TaskWorkflowRequest;
|
|
460
|
+
channelDir?: string;
|
|
461
|
+
statusDir?: string;
|
|
462
|
+
token?: string;
|
|
463
|
+
generation?: string;
|
|
464
|
+
timeoutMs?: number;
|
|
465
|
+
authority?: LifecycleAuthorityRequestV1;
|
|
466
|
+
}>): SandboxControlResponse {
|
|
467
|
+
const auth = authority(params);
|
|
468
|
+
const issuedAt = Date.now();
|
|
469
|
+
const request: SandboxTaskWorkflowRequest = {
|
|
470
|
+
version: 3,
|
|
471
|
+
id: params.workflow.id,
|
|
472
|
+
...auth,
|
|
473
|
+
issuedAt,
|
|
474
|
+
expiresAt: issuedAt + SANDBOX_CONTROL_ADMISSION_WINDOW_MS,
|
|
475
|
+
family: 'task-workflow',
|
|
476
|
+
args: [],
|
|
477
|
+
controllerProcess: null,
|
|
478
|
+
controllerProof: null,
|
|
479
|
+
workflow: params.workflow,
|
|
480
|
+
...(params.authority ? { authority: params.authority } : {})
|
|
481
|
+
};
|
|
482
|
+
return exchangeSandboxControl(request, params);
|
|
483
|
+
}
|
|
484
|
+
|
|
442
485
|
type CodexControllerClosed = Readonly<{
|
|
443
486
|
version: 1;
|
|
444
487
|
status: 'closed';
|