@fitlab-ai/agent-infra 0.9.8 → 0.9.10
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 +1 -1
- package/README.zh-CN.md +1 -1
- package/dist/bin/internal-cli.js +11 -1
- package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +58 -21
- package/dist/lib/agent-clients/adapters/codex-lifecycle/build-identity.js +25 -11
- package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +173 -10
- package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-context.js +127 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/evidence.js +14 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +63 -92
- package/dist/lib/agent-clients/adapters/codex-sandbox.js +72 -11
- package/dist/lib/agent-clients/adapters/index.js +3 -1
- package/dist/lib/agent-clients/adapters/traecli.js +66 -0
- package/dist/lib/agent-clients/tokens.js +2 -2
- package/dist/lib/agent-clients/types.js +2 -1
- package/dist/lib/defaults.json +5 -0
- package/dist/lib/git/workflow.js +10 -1
- package/dist/lib/internal/codex-lifecycle.js +20 -6
- package/dist/lib/internal/codex-sandbox-controller.js +9 -15
- package/dist/lib/internal/git-workflow.js +5 -7
- package/dist/lib/internal/platform-pr.js +20 -7
- package/dist/lib/internal/release-workflow.js +2 -1
- package/dist/lib/internal/sandbox-control.js +79 -2
- package/dist/lib/internal/task-finalization.js +54 -0
- package/dist/lib/internal/task-orchestration.js +156 -291
- package/dist/lib/{task/commands/validate.js → internal/task-validate.js} +75 -42
- package/dist/lib/platform/pr-summary.js +48 -10
- package/dist/lib/platform/pull-requests.js +205 -41
- package/dist/lib/run/tui.js +5 -2
- package/dist/lib/sandbox/commands/rm.js +316 -55
- package/dist/lib/sandbox/control/client.js +190 -6
- package/dist/lib/sandbox/control/controller-registration.js +275 -0
- package/dist/lib/sandbox/control/executor.js +140 -5
- package/dist/lib/sandbox/control/lifecycle.js +3 -1
- package/dist/lib/sandbox/control/protocol.js +67 -6
- package/dist/lib/sandbox/index.js +5 -3
- package/dist/lib/sandbox/managed-fs.js +5 -0
- package/dist/lib/sandbox/task-resolver.js +50 -6
- package/dist/lib/sandbox/workspace-identity.js +27 -0
- package/dist/lib/sandbox/worktree-safety.js +215 -2
- package/dist/lib/task/codex-orchestration.js +161 -39
- package/dist/lib/task/commands/status.js +59 -1
- package/dist/lib/task/commit-operation.js +335 -0
- package/dist/lib/task/commit-policy.js +18 -0
- package/dist/lib/task/delegation-receipts.js +248 -6
- package/dist/lib/task/finalization.js +593 -0
- package/dist/lib/task/index.js +0 -9
- package/dist/lib/task/operation-outcome.js +33 -0
- package/dist/lib/task/orchestration.js +187 -761
- package/dist/lib/task/short-id.js +59 -1
- package/dist/lib/task/task-execution-lock.js +6 -1
- package/dist/lib/task/verification-engine.js +110 -60
- package/dist/lib/task/verification.js +2 -1
- package/dist/lib/task/workflow-warning-intents.js +50 -1
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +61 -19
- package/lib/agent-clients/adapters/codex-lifecycle/build-identity.ts +37 -10
- package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +276 -12
- package/lib/agent-clients/adapters/codex-lifecycle/controller-context.ts +163 -0
- package/lib/agent-clients/adapters/codex-lifecycle/evidence.ts +19 -0
- package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +9 -1
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +91 -118
- package/lib/agent-clients/adapters/codex-sandbox.ts +89 -14
- package/lib/agent-clients/adapters/index.ts +3 -1
- package/lib/agent-clients/adapters/traecli.ts +67 -0
- package/lib/agent-clients/tokens.ts +2 -2
- package/lib/agent-clients/types.ts +2 -1
- package/lib/defaults.json +5 -0
- package/lib/git/workflow.ts +11 -2
- package/lib/internal/codex-lifecycle.ts +19 -6
- package/lib/internal/codex-sandbox-controller.ts +9 -14
- package/lib/internal/git-workflow.ts +5 -6
- package/lib/internal/platform-pr.ts +14 -7
- package/lib/internal/release-workflow.ts +2 -1
- package/lib/internal/sandbox-control.ts +92 -2
- package/lib/internal/task-finalization.ts +68 -0
- package/lib/internal/task-orchestration.ts +20 -103
- package/lib/{task/commands/validate.ts → internal/task-validate.ts} +75 -40
- package/lib/platform/pr-summary.ts +55 -11
- package/lib/platform/pull-requests.ts +211 -41
- package/lib/run/tui.ts +5 -3
- package/lib/sandbox/commands/rm.ts +413 -61
- package/lib/sandbox/control/client.ts +255 -6
- package/lib/sandbox/control/controller-registration.ts +352 -0
- package/lib/sandbox/control/executor.ts +169 -5
- package/lib/sandbox/control/lifecycle.ts +2 -1
- package/lib/sandbox/control/protocol.ts +80 -7
- package/lib/sandbox/index.ts +5 -3
- package/lib/sandbox/managed-fs.ts +5 -0
- package/lib/sandbox/task-resolver.ts +58 -7
- package/lib/sandbox/workspace-identity.ts +50 -2
- package/lib/sandbox/workspace-view.ts +3 -3
- package/lib/sandbox/worktree-safety.ts +236 -2
- package/lib/task/codex-orchestration.ts +168 -40
- package/lib/task/commands/status.ts +78 -1
- package/lib/task/commit-operation.ts +430 -0
- package/lib/task/commit-policy.ts +30 -0
- package/lib/task/delegation-receipts.ts +263 -4
- package/lib/task/finalization.ts +761 -0
- package/lib/task/index.ts +0 -8
- package/lib/task/operation-outcome.ts +48 -0
- package/lib/task/orchestration.ts +218 -982
- package/lib/task/short-id.ts +65 -1
- package/lib/task/task-execution-lock.ts +10 -1
- package/lib/task/verification-engine.ts +115 -63
- package/lib/task/verification.ts +3 -2
- package/lib/task/workflow-warning-intents.ts +57 -2
- package/package.json +2 -2
- package/templates/.agents/QUICKSTART.en.md +3 -4
- package/templates/.agents/QUICKSTART.zh-CN.md +3 -4
- package/templates/.agents/hooks/lifecycle-delegation.js +1 -21
- package/templates/.agents/rules/README.en.md +1 -0
- package/templates/.agents/rules/README.zh-CN.md +1 -0
- package/templates/.agents/rules/compatibility-policy.en.md +39 -0
- package/templates/.agents/rules/compatibility-policy.zh-CN.md +39 -0
- package/templates/.agents/rules/lifecycle-orchestration.en.md +4 -4
- package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +4 -4
- package/templates/.agents/rules/pr-sync.en.md +1 -1
- package/templates/.agents/rules/pr-sync.github.en.md +2 -1
- package/templates/.agents/rules/pr-sync.github.zh-CN.md +2 -0
- package/templates/.agents/rules/pr-sync.zh-CN.md +1 -0
- package/templates/.agents/rules/review-method.en.md +1 -0
- package/templates/.agents/rules/review-method.zh-CN.md +1 -0
- package/templates/.agents/rules/task-management.en.md +2 -2
- package/templates/.agents/rules/task-management.zh-CN.md +2 -2
- package/templates/.agents/skills/analyze-task/SKILL.en.md +1 -0
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +1 -0
- package/templates/.agents/skills/block-task/SKILL.en.md +2 -4
- package/templates/.agents/skills/block-task/SKILL.zh-CN.md +2 -4
- package/templates/.agents/skills/cancel-task/SKILL.en.md +3 -3
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/close-codescan/SKILL.en.md +6 -2
- package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +6 -2
- package/templates/.agents/skills/close-dependabot/SKILL.en.md +6 -2
- package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +6 -2
- package/templates/.agents/skills/code-task/SKILL.en.md +1 -0
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +2 -0
- package/templates/.agents/skills/commit/SKILL.en.md +41 -125
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +52 -123
- package/templates/.agents/skills/commit/reference/commit-message.en.md +1 -1
- package/templates/.agents/skills/commit/reference/commit-message.zh-CN.md +1 -1
- package/templates/.agents/skills/commit/reference/commit-orchestration.en.md +14 -43
- package/templates/.agents/skills/commit/reference/commit-orchestration.zh-CN.md +14 -49
- package/templates/.agents/skills/commit/reference/pr-summary-sync.en.md +1 -1
- package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +1 -1
- package/templates/.agents/skills/commit/reference/task-status-update.en.md +2 -35
- package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +2 -35
- package/templates/.agents/skills/complete-task/SKILL.en.md +31 -38
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +31 -38
- package/templates/.agents/skills/complete-task/config/verify.en.json +1 -0
- package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +1 -0
- package/templates/.agents/skills/complete-task/reference/external-delivery.en.md +1 -1
- package/templates/.agents/skills/complete-task/reference/external-delivery.zh-CN.md +1 -1
- package/templates/.agents/skills/create-pr/SKILL.en.md +17 -9
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +17 -9
- package/templates/.agents/skills/create-pr/reference/change-report.en.md +41 -0
- package/templates/.agents/skills/create-pr/reference/change-report.zh-CN.md +41 -0
- package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +2 -2
- package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +2 -2
- package/templates/.agents/skills/plan-task/SKILL.en.md +2 -1
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +2 -1
- package/templates/.agents/skills/review-code/reference/migration-risks.en.md +8 -6
- package/templates/.agents/skills/review-code/reference/migration-risks.zh-CN.md +8 -6
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +3 -3
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +1 -1
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +1 -1
- package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +2 -2
- package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +2 -2
- package/templates/.agents/skills/run-task/SKILL.en.md +6 -6
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +6 -6
- package/templates/.agents/skills/run-task/reference/host-validation.en.md +2 -2
- package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +2 -2
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +24 -0
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +1 -1
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +1 -1
- package/templates/.codex/hooks.json +4 -4
- package/templates/.trae/skills/update-agent-infra.en.md +8 -0
- package/templates/.trae/skills/update-agent-infra.zh-CN.md +8 -0
- package/dist/lib/task/commit-finalization.js +0 -305
- package/dist/lib/task/commit-intent.js +0 -165
- package/lib/task/commit-finalization.ts +0 -419
- package/lib/task/commit-intent.ts +0 -230
package/README.md
CHANGED
|
@@ -182,7 +182,7 @@ The most-used lifecycle commands, in delivery order. The command prefix varies b
|
|
|
182
182
|
| `code-task` → `review-code` | Implement and test, then run a structured code review |
|
|
183
183
|
| `commit` → `create-pr` → `complete-task` | Commit, open a PR, and archive the task |
|
|
184
184
|
|
|
185
|
-
Start with one atomic role policy, for example: `$run-task 42 --executor-model <id> --executor-reasoning-effort <value> --reviewer-model <id> --reviewer-reasoning-effort <value>`. Both roles may use the same model. With no explicit policy, `run-task` reads the current Agent Client's optional `agentClients[].orchestration`; if neither source is complete, it shows the host model-selection guidance before creating any run. Re-entry uses the persisted
|
|
185
|
+
Start with one atomic role policy, for example: `$run-task 42 --executor-model <id> --executor-reasoning-effort <value> --reviewer-model <id> --reviewer-reasoning-effort <value>`. Both roles may use the same model. With no explicit policy, `run-task` reads the current Agent Client's optional `agentClients[].orchestration`; if neither source is complete, it shows the host model-selection guidance before creating any run. Re-entry uses the persisted run policy. Finish or clear active runs before upgrading agent-infra; unsupported disk state fails closed without rewriting.
|
|
186
186
|
|
|
187
187
|
See the full catalog — task status, release, security, and project-maintenance skills — in [Built-in AI Skills](./docs/en/skills.md).
|
|
188
188
|
|
package/README.zh-CN.md
CHANGED
|
@@ -181,7 +181,7 @@ ai agent-client configure
|
|
|
181
181
|
| `code-task` → `review-code` | 实现并测试,再执行结构化代码审查 |
|
|
182
182
|
| `commit` → `create-pr` → `complete-task` | 提交、创建 PR、归档任务 |
|
|
183
183
|
|
|
184
|
-
启动时可提供原子角色策略,例如:`$run-task 42 --executor-model <id> --executor-reasoning-effort <值> --reviewer-model <id> --reviewer-reasoning-effort <值>`。两个角色可以使用同一模型。完全没有显式策略时,`run-task` 读取当前 Agent Client 可选的 `agentClients[].orchestration`;两个来源都不完整时,会在创建任何 run 前展示宿主模型选择指引。重入使用已持久化的
|
|
184
|
+
启动时可提供原子角色策略,例如:`$run-task 42 --executor-model <id> --executor-reasoning-effort <值> --reviewer-model <id> --reviewer-reasoning-effort <值>`。两个角色可以使用同一模型。完全没有显式策略时,`run-task` 读取当前 Agent Client 可选的 `agentClients[].orchestration`;两个来源都不完整时,会在创建任何 run 前展示宿主模型选择指引。重入使用已持久化的 run 策略。升级 agent-infra 前必须完成或清空 active run;不受支持的磁盘状态会失败关闭且不改写。
|
|
185
185
|
|
|
186
186
|
完整清单(任务状态、发布、安全、项目维护等 skill)见 [内置 AI Skills](./docs/zh-CN/skills.md)。
|
|
187
187
|
|
package/dist/bin/internal-cli.js
CHANGED
|
@@ -6,7 +6,7 @@ if (major < 22 || (major === 22 && minor < 9)) {
|
|
|
6
6
|
}
|
|
7
7
|
const command = process.argv[2] || '';
|
|
8
8
|
if (process.env.AGENT_INFRA_CONTROL_TOKEN
|
|
9
|
-
&& (command === 'task-lifecycle' || command === 'task-orchestration')) {
|
|
9
|
+
&& (command === 'task-lifecycle' || command === 'task-orchestration' || command === 'task-finalization')) {
|
|
10
10
|
const { sandboxControl } = await import('../lib/internal/sandbox-control.js');
|
|
11
11
|
await sandboxControl(['client', command, ...process.argv.slice(3)]);
|
|
12
12
|
}
|
|
@@ -132,6 +132,11 @@ else
|
|
|
132
132
|
taskLifecycle(process.argv.slice(3));
|
|
133
133
|
break;
|
|
134
134
|
}
|
|
135
|
+
case 'task-finalization': {
|
|
136
|
+
const { taskFinalization } = await import('../lib/internal/task-finalization.js');
|
|
137
|
+
taskFinalization(process.argv.slice(3));
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
135
140
|
case 'task-override': {
|
|
136
141
|
const { taskOverride } = await import('../lib/internal/task-override.js');
|
|
137
142
|
taskOverride(process.argv.slice(3));
|
|
@@ -152,6 +157,11 @@ else
|
|
|
152
157
|
taskVerify(process.argv.slice(3));
|
|
153
158
|
break;
|
|
154
159
|
}
|
|
160
|
+
case 'task-validate': {
|
|
161
|
+
const { taskValidate } = await import('../lib/internal/task-validate.js');
|
|
162
|
+
taskValidate(process.argv.slice(3));
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
155
165
|
default:
|
|
156
166
|
process.stdout.write(`${JSON.stringify({
|
|
157
167
|
status: 'failed',
|
|
@@ -13,6 +13,10 @@ const LIFECYCLE_HOOKS = Object.freeze([
|
|
|
13
13
|
]);
|
|
14
14
|
const DEFAULT_ROLLOUT_READ_ATTEMPTS = 81;
|
|
15
15
|
const DEFAULT_ROLLOUT_RETRY_MS = 100;
|
|
16
|
+
const DEFAULT_THREAD_READ_ATTEMPTS = 21;
|
|
17
|
+
const DEFAULT_THREAD_RETRY_MS = 100;
|
|
18
|
+
const DEFAULT_TERMINAL_READ_ATTEMPTS = 21;
|
|
19
|
+
const DEFAULT_TERMINAL_RETRY_MS = 100;
|
|
16
20
|
function object(value) {
|
|
17
21
|
return value && typeof value === 'object' && !Array.isArray(value)
|
|
18
22
|
? value
|
|
@@ -62,16 +66,20 @@ function resolveCodexSpawnedChild(transcriptPath, expected) {
|
|
|
62
66
|
throw new Error('Codex parent rollout spawn identity does not match the hook event');
|
|
63
67
|
const activities = records.filter((record) => {
|
|
64
68
|
const payload = object(record?.payload);
|
|
69
|
+
const item = object(payload?.item);
|
|
65
70
|
return record?.type === 'event_msg'
|
|
66
|
-
&& payload?.type === '
|
|
67
|
-
&& payload.
|
|
68
|
-
&& payload.
|
|
69
|
-
&&
|
|
70
|
-
&&
|
|
71
|
+
&& payload?.type === 'item_completed'
|
|
72
|
+
&& payload.thread_id === expected.sessionId
|
|
73
|
+
&& payload.turn_id === expected.turnId
|
|
74
|
+
&& item?.type === 'SubAgentActivity'
|
|
75
|
+
&& item.id === expected.toolUseId
|
|
76
|
+
&& item.kind === 'started'
|
|
77
|
+
&& item.agent_path === `/root/${expected.taskName}`
|
|
78
|
+
&& nonEmpty(item.agent_thread_id);
|
|
71
79
|
});
|
|
72
80
|
if (activities.length !== 1)
|
|
73
81
|
throw new Error('Codex parent rollout child activity was not found uniquely');
|
|
74
|
-
return nonEmpty(object(activities[0]?.payload)?.agent_thread_id);
|
|
82
|
+
return nonEmpty(object(object(activities[0]?.payload)?.item)?.agent_thread_id);
|
|
75
83
|
}
|
|
76
84
|
function validateCodexLifecycleHookConfig(value) {
|
|
77
85
|
const hooks = object(object(value)?.hooks);
|
|
@@ -86,7 +94,7 @@ function validateCodexLifecycleHookConfig(value) {
|
|
|
86
94
|
return entry.hooks.some((hookValue) => {
|
|
87
95
|
const hook = object(hookValue);
|
|
88
96
|
return hook?.type === 'command'
|
|
89
|
-
&& hook.timeout ===
|
|
97
|
+
&& hook.timeout === 30
|
|
90
98
|
&& hook.command === `node "$(git rev-parse --show-toplevel)/.agents/hooks/lifecycle-delegation.js" --client codex --event ${phase}`;
|
|
91
99
|
});
|
|
92
100
|
});
|
|
@@ -410,14 +418,26 @@ async function resolveCodexThread(childThreadId, options = {}) {
|
|
|
410
418
|
const readThread = object(readResult?.thread);
|
|
411
419
|
if (nonEmpty(readThread?.id) !== childThreadId)
|
|
412
420
|
throw new Error('Codex App Server returned the wrong child thread');
|
|
413
|
-
let
|
|
421
|
+
let rolloutRecords;
|
|
414
422
|
try {
|
|
415
|
-
|
|
416
|
-
resolution = parseCodexThreadResolution(readResult, rolloutRecords);
|
|
423
|
+
rolloutRecords = await resolveCodexRolloutRecords(readResult, childThreadId, options.rolloutReadAttempts ?? DEFAULT_ROLLOUT_READ_ATTEMPTS, options.rolloutRetryMs ?? DEFAULT_ROLLOUT_RETRY_MS);
|
|
417
424
|
}
|
|
418
425
|
catch {
|
|
419
426
|
throw new Error('Codex App Server rollout metadata is unavailable');
|
|
420
427
|
}
|
|
428
|
+
let resolution = parseCodexThreadResolution(readResult, rolloutRecords);
|
|
429
|
+
const threadReadAttempts = options.threadReadAttempts ?? DEFAULT_THREAD_READ_ATTEMPTS;
|
|
430
|
+
for (let attempt = 1; resolution.thread.forkedFromId !== null && attempt < threadReadAttempts; attempt += 1) {
|
|
431
|
+
await new Promise((resolve) => setTimeout(resolve, options.threadRetryMs ?? DEFAULT_THREAD_RETRY_MS));
|
|
432
|
+
const refreshed = object(await transport.request('thread/read', { threadId: childThreadId, includeTurns: false }));
|
|
433
|
+
if (nonEmpty(object(refreshed?.thread)?.id) !== childThreadId) {
|
|
434
|
+
throw new Error('Codex App Server returned the wrong child thread');
|
|
435
|
+
}
|
|
436
|
+
resolution = parseCodexThreadResolution(refreshed, rolloutRecords);
|
|
437
|
+
}
|
|
438
|
+
if (resolution.thread.forkedFromId !== null) {
|
|
439
|
+
throw new Error('Codex App Server fresh thread metadata is unavailable');
|
|
440
|
+
}
|
|
421
441
|
const reroutes = transport.notifications
|
|
422
442
|
.filter((entry) => entry.method === 'model/rerouted')
|
|
423
443
|
.map((entry) => parseCodexModelReroute(entry.params))
|
|
@@ -434,21 +454,38 @@ async function resolveCodexThread(childThreadId, options = {}) {
|
|
|
434
454
|
transport.close();
|
|
435
455
|
}
|
|
436
456
|
}
|
|
437
|
-
async function resolveCodexTerminal(childThreadId,
|
|
457
|
+
async function resolveCodexTerminal(childThreadId, expectedTurnIdOrOptions = {}, terminalOptions = {}) {
|
|
458
|
+
const expectedTurnId = typeof expectedTurnIdOrOptions === 'string' ? expectedTurnIdOrOptions : null;
|
|
459
|
+
const options = typeof expectedTurnIdOrOptions === 'string' ? terminalOptions : expectedTurnIdOrOptions;
|
|
438
460
|
const transport = new CodexAppServerTransport(options);
|
|
439
461
|
transport.start();
|
|
440
462
|
try {
|
|
441
463
|
await transport.initialize();
|
|
442
|
-
const
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
464
|
+
const attempts = expectedTurnId ? options.terminalReadAttempts ?? DEFAULT_TERMINAL_READ_ATTEMPTS : 1;
|
|
465
|
+
let abnormal = null;
|
|
466
|
+
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
467
|
+
const result = object(await transport.request('thread/read', { threadId: childThreadId, includeTurns: true }));
|
|
468
|
+
const thread = object(result?.thread);
|
|
469
|
+
if (nonEmpty(thread?.id) !== childThreadId)
|
|
470
|
+
throw new Error('Codex App Server returned the wrong child thread');
|
|
471
|
+
const turns = Array.isArray(thread?.turns) ? thread.turns : [];
|
|
472
|
+
const turn = expectedTurnId
|
|
473
|
+
? turns.find((candidate) => object(candidate)?.id === expectedTurnId)
|
|
474
|
+
: turns.at(-1);
|
|
475
|
+
const turnValue = object(turn);
|
|
476
|
+
if (turnValue && turnValue.status !== 'inProgress') {
|
|
477
|
+
const terminal = parseCodexTurnCompleted({ threadId: childThreadId, turn });
|
|
478
|
+
if (terminal.status === 'completed')
|
|
479
|
+
return terminal;
|
|
480
|
+
abnormal = terminal;
|
|
481
|
+
}
|
|
482
|
+
if (attempt + 1 < attempts) {
|
|
483
|
+
await new Promise((resolve) => setTimeout(resolve, options.terminalRetryMs ?? DEFAULT_TERMINAL_RETRY_MS));
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
if (abnormal)
|
|
487
|
+
return abnormal;
|
|
488
|
+
throw new Error('CODEX_TURN_NOT_TERMINAL');
|
|
452
489
|
}
|
|
453
490
|
finally {
|
|
454
491
|
transport.close();
|
|
@@ -2,10 +2,17 @@ import crypto from 'node:crypto';
|
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import semver from 'semver';
|
|
5
6
|
const LIFECYCLE_PROTOCOL_VERSION = 3;
|
|
6
7
|
function exactText(value) {
|
|
7
8
|
return typeof value === 'string' && value.trim() === value && value.length > 0;
|
|
8
9
|
}
|
|
10
|
+
function isCanonicalLifecyclePackageVersion(value) {
|
|
11
|
+
return typeof value === 'string' && semver.valid(value) === value;
|
|
12
|
+
}
|
|
13
|
+
function isLifecycleProtocolVersion(value) {
|
|
14
|
+
return value === LIFECYCLE_PROTOCOL_VERSION;
|
|
15
|
+
}
|
|
9
16
|
function normalizeRelativePath(value) {
|
|
10
17
|
return value.replaceAll('\\', '/');
|
|
11
18
|
}
|
|
@@ -138,25 +145,32 @@ function verifyLifecycleBuildIdentity(expected, actual) {
|
|
|
138
145
|
return {
|
|
139
146
|
ok: false,
|
|
140
147
|
code: 'CODEX_LIFECYCLE_PROTOCOL_MISMATCH',
|
|
141
|
-
message: 'Codex lifecycle protocol version does not match'
|
|
148
|
+
message: 'Codex lifecycle protocol version does not match',
|
|
149
|
+
warnings: []
|
|
142
150
|
};
|
|
143
151
|
}
|
|
152
|
+
const warnings = [];
|
|
144
153
|
if (actual.packageVersion !== expected.packageVersion
|
|
145
154
|
|| actual.internalExecutableBuildHash !== expected.internalExecutableBuildHash) {
|
|
146
|
-
|
|
147
|
-
ok: false,
|
|
155
|
+
warnings.push({
|
|
148
156
|
code: 'CODEX_LIFECYCLE_BUILD_MISMATCH',
|
|
149
|
-
message: 'Codex lifecycle executable build identity
|
|
150
|
-
|
|
157
|
+
message: 'Codex lifecycle executable build identity differs; rebuild the sandbox if the runtime is stale',
|
|
158
|
+
action: 'rebuild-sandbox'
|
|
159
|
+
});
|
|
151
160
|
}
|
|
152
161
|
if (actual.lifecycleContractHash !== expected.lifecycleContractHash) {
|
|
153
|
-
|
|
154
|
-
ok: false,
|
|
162
|
+
warnings.push({
|
|
155
163
|
code: 'CODEX_LIFECYCLE_CONTRACT_MISMATCH',
|
|
156
|
-
message: 'Codex lifecycle contract identity
|
|
157
|
-
|
|
164
|
+
message: 'Codex lifecycle contract identity differs; rebuild the sandbox if the runtime is stale',
|
|
165
|
+
action: 'rebuild-sandbox'
|
|
166
|
+
});
|
|
158
167
|
}
|
|
159
|
-
return {
|
|
168
|
+
return {
|
|
169
|
+
ok: true,
|
|
170
|
+
code: null,
|
|
171
|
+
message: null,
|
|
172
|
+
warnings: Object.freeze(warnings)
|
|
173
|
+
};
|
|
160
174
|
}
|
|
161
|
-
export { LIFECYCLE_PROTOCOL_VERSION, computeLifecycleBuildIdentity, readLifecycleManifestFiles, resolveLifecycleExecutableFiles, verifyLifecycleBuildIdentity };
|
|
175
|
+
export { LIFECYCLE_PROTOCOL_VERSION, computeLifecycleBuildIdentity, isCanonicalLifecyclePackageVersion, isLifecycleProtocolVersion, readLifecycleManifestFiles, resolveLifecycleExecutableFiles, verifyLifecycleBuildIdentity };
|
|
162
176
|
//# sourceMappingURL=build-identity.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import crypto from 'node:crypto';
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
-
import { verifyLifecycleBuildIdentity } from './build-identity.js';
|
|
4
|
+
import { isCanonicalLifecyclePackageVersion, isLifecycleProtocolVersion, LIFECYCLE_PROTOCOL_VERSION, verifyLifecycleBuildIdentity } from './build-identity.js';
|
|
5
5
|
import { resolveAgentRuntimeStoreRoot } from '../../../runtime/agent-runtime.js';
|
|
6
6
|
const DEFAULT_TTL_MS = 30_000;
|
|
7
7
|
const DEFAULT_TOMBSTONE_MS = 86_400_000;
|
|
@@ -9,17 +9,168 @@ const DEFAULT_MAX_RECORDS = 128;
|
|
|
9
9
|
const LOCK_RETRY_MS = 10;
|
|
10
10
|
const LOCK_TIMEOUT_MS = 1_000;
|
|
11
11
|
const LOCK_STALE_MS = 30_000;
|
|
12
|
+
const DETAIL_DOMAIN = 'codex-capability-provenance/v1';
|
|
13
|
+
const TASK_ID_PATTERN = /^TASK-[0-9]{8}-[0-9]{6}$/u;
|
|
14
|
+
const SHA256_PATTERN = /^[0-9a-f]{64}$/u;
|
|
15
|
+
const DIGEST_PREFIX_PATTERN = /^sha256:[0-9a-f]{16}$/u;
|
|
12
16
|
function digest(value) {
|
|
13
17
|
return crypto.createHash('sha256').update(value).digest('hex');
|
|
14
18
|
}
|
|
15
|
-
function capabilityError(code, message) {
|
|
19
|
+
function capabilityError(code, message, detail) {
|
|
16
20
|
const error = new Error(`${code}: ${message}`);
|
|
17
21
|
error.name = code;
|
|
22
|
+
if (detail)
|
|
23
|
+
error.detail = detail;
|
|
18
24
|
return error;
|
|
19
25
|
}
|
|
26
|
+
function objectValue(value, key) {
|
|
27
|
+
if (!value || typeof value !== 'object')
|
|
28
|
+
return undefined;
|
|
29
|
+
return value[key];
|
|
30
|
+
}
|
|
31
|
+
function digestPrefix(fieldName, value) {
|
|
32
|
+
return {
|
|
33
|
+
kind: 'digest-prefix',
|
|
34
|
+
value: `sha256:${crypto.createHash('sha256')
|
|
35
|
+
.update(`${DETAIL_DOMAIN}\0${fieldName}\0${value}`)
|
|
36
|
+
.digest('hex')
|
|
37
|
+
.slice(0, 16)}`
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function projectDigest(fieldName, value, pattern) {
|
|
41
|
+
return typeof value === 'string' && pattern.test(value) ? digestPrefix(fieldName, value) : { kind: 'absent' };
|
|
42
|
+
}
|
|
43
|
+
function projectTaskId(value) {
|
|
44
|
+
return projectDigest('taskId', value, TASK_ID_PATTERN);
|
|
45
|
+
}
|
|
46
|
+
function projectHash(fieldName, value) {
|
|
47
|
+
return projectDigest(fieldName, value, SHA256_PATTERN);
|
|
48
|
+
}
|
|
49
|
+
function projectProtocol(value) {
|
|
50
|
+
return isLifecycleProtocolVersion(value)
|
|
51
|
+
? { kind: 'protocol-version', value: LIFECYCLE_PROTOCOL_VERSION }
|
|
52
|
+
: { kind: 'absent' };
|
|
53
|
+
}
|
|
54
|
+
function projectPackageVersion(value) {
|
|
55
|
+
return isCanonicalLifecyclePackageVersion(value)
|
|
56
|
+
? { kind: 'semver', value }
|
|
57
|
+
: { kind: 'absent' };
|
|
58
|
+
}
|
|
59
|
+
function projectPresence(value) {
|
|
60
|
+
return { kind: 'presence', present: Boolean(value && typeof value === 'object') };
|
|
61
|
+
}
|
|
62
|
+
function field(matches, expected, actual) {
|
|
63
|
+
return { matches, expected, actual };
|
|
64
|
+
}
|
|
65
|
+
function provenanceDetail(record, expected) {
|
|
66
|
+
const actualBuild = record.buildIdentity;
|
|
67
|
+
const expectedBuild = expected.buildIdentity;
|
|
68
|
+
const actualController = record.controller;
|
|
69
|
+
const expectedController = expected.controller ?? null;
|
|
70
|
+
return {
|
|
71
|
+
kind: 'codex-capability-provenance-mismatch',
|
|
72
|
+
version: 1,
|
|
73
|
+
fields: {
|
|
74
|
+
taskId: field(record.taskId === expected.taskId, projectTaskId(expected.taskId), projectTaskId(record.taskId)),
|
|
75
|
+
hookDefinitionHash: field(record.hookDefinitionHash === expected.hookDefinitionHash, projectHash('hookDefinitionHash', expected.hookDefinitionHash), projectHash('hookDefinitionHash', record.hookDefinitionHash)),
|
|
76
|
+
buildIdentity: {
|
|
77
|
+
protocolVersion: field(objectValue(actualBuild, 'protocolVersion') === objectValue(expectedBuild, 'protocolVersion'), projectProtocol(objectValue(expectedBuild, 'protocolVersion')), projectProtocol(objectValue(actualBuild, 'protocolVersion'))),
|
|
78
|
+
packageVersion: field(objectValue(actualBuild, 'packageVersion') === objectValue(expectedBuild, 'packageVersion'), projectPackageVersion(objectValue(expectedBuild, 'packageVersion')), projectPackageVersion(objectValue(actualBuild, 'packageVersion'))),
|
|
79
|
+
internalExecutableBuildHash: field(objectValue(actualBuild, 'internalExecutableBuildHash') === objectValue(expectedBuild, 'internalExecutableBuildHash'), projectHash('buildIdentity.internalExecutableBuildHash', objectValue(expectedBuild, 'internalExecutableBuildHash')), projectHash('buildIdentity.internalExecutableBuildHash', objectValue(actualBuild, 'internalExecutableBuildHash'))),
|
|
80
|
+
lifecycleContractHash: field(objectValue(actualBuild, 'lifecycleContractHash') === objectValue(expectedBuild, 'lifecycleContractHash'), projectHash('buildIdentity.lifecycleContractHash', objectValue(expectedBuild, 'lifecycleContractHash')), projectHash('buildIdentity.lifecycleContractHash', objectValue(actualBuild, 'lifecycleContractHash')))
|
|
81
|
+
},
|
|
82
|
+
controller: {
|
|
83
|
+
instanceDigest: field(sameControllerField(actualController, expectedController, 'instanceDigest'), projectPresence(objectValue(expectedController, 'instanceDigest') === undefined ? null : expectedController), projectPresence(objectValue(actualController, 'instanceDigest') === undefined ? null : actualController)),
|
|
84
|
+
controlGeneration: field(sameControllerField(actualController, expectedController, 'controlGeneration'), projectPresence(objectValue(expectedController, 'controlGeneration') === undefined ? null : expectedController), projectPresence(objectValue(actualController, 'controlGeneration') === undefined ? null : actualController))
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
function exactKeys(value, keys) {
|
|
90
|
+
return Object.keys(value).length === keys.length && keys.every((key) => Object.hasOwn(value, key));
|
|
91
|
+
}
|
|
92
|
+
function isCodexControllerBinding(value) {
|
|
93
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
94
|
+
return false;
|
|
95
|
+
const record = value;
|
|
96
|
+
return exactKeys(record, ['instanceDigest', 'controlGeneration'])
|
|
97
|
+
&& typeof record.instanceDigest === 'string'
|
|
98
|
+
&& typeof record.controlGeneration === 'string';
|
|
99
|
+
}
|
|
100
|
+
function isProjection(value) {
|
|
101
|
+
if (!value || typeof value !== 'object')
|
|
102
|
+
return false;
|
|
103
|
+
const record = value;
|
|
104
|
+
if (record.kind === 'absent')
|
|
105
|
+
return exactKeys(record, ['kind']);
|
|
106
|
+
if (record.kind === 'protocol-version') {
|
|
107
|
+
return exactKeys(record, ['kind', 'value']) && isLifecycleProtocolVersion(record.value);
|
|
108
|
+
}
|
|
109
|
+
if (record.kind === 'semver') {
|
|
110
|
+
return exactKeys(record, ['kind', 'value']) && isCanonicalLifecyclePackageVersion(record.value);
|
|
111
|
+
}
|
|
112
|
+
if (record.kind === 'digest-prefix') {
|
|
113
|
+
return exactKeys(record, ['kind', 'value'])
|
|
114
|
+
&& typeof record.value === 'string'
|
|
115
|
+
&& DIGEST_PREFIX_PATTERN.test(record.value);
|
|
116
|
+
}
|
|
117
|
+
if (record.kind === 'presence') {
|
|
118
|
+
return exactKeys(record, ['kind', 'present']) && typeof record.present === 'boolean';
|
|
119
|
+
}
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
function isProvenanceField(value) {
|
|
123
|
+
if (!value || typeof value !== 'object')
|
|
124
|
+
return false;
|
|
125
|
+
const record = value;
|
|
126
|
+
return exactKeys(record, ['matches', 'expected', 'actual'])
|
|
127
|
+
&& typeof record.matches === 'boolean'
|
|
128
|
+
&& isProjection(record.expected)
|
|
129
|
+
&& isProjection(record.actual);
|
|
130
|
+
}
|
|
131
|
+
function isCodexCapabilityProvenanceDetail(value) {
|
|
132
|
+
if (!value || typeof value !== 'object')
|
|
133
|
+
return false;
|
|
134
|
+
const record = value;
|
|
135
|
+
if (!exactKeys(record, ['kind', 'version', 'fields'])
|
|
136
|
+
|| record.kind !== 'codex-capability-provenance-mismatch'
|
|
137
|
+
|| record.version !== 1
|
|
138
|
+
|| !record.fields || typeof record.fields !== 'object')
|
|
139
|
+
return false;
|
|
140
|
+
const fields = record.fields;
|
|
141
|
+
if (!exactKeys(fields, ['taskId', 'hookDefinitionHash', 'buildIdentity', 'controller'])
|
|
142
|
+
|| !isProvenanceField(fields.taskId)
|
|
143
|
+
|| !isProvenanceField(fields.hookDefinitionHash))
|
|
144
|
+
return false;
|
|
145
|
+
const buildIdentity = fields.buildIdentity;
|
|
146
|
+
const controller = fields.controller;
|
|
147
|
+
if (!buildIdentity || typeof buildIdentity !== 'object' || !controller || typeof controller !== 'object')
|
|
148
|
+
return false;
|
|
149
|
+
const build = buildIdentity;
|
|
150
|
+
const binding = controller;
|
|
151
|
+
return exactKeys(build, ['protocolVersion', 'packageVersion', 'internalExecutableBuildHash', 'lifecycleContractHash'])
|
|
152
|
+
&& exactKeys(binding, ['instanceDigest', 'controlGeneration'])
|
|
153
|
+
&& isProvenanceField(build.protocolVersion)
|
|
154
|
+
&& isProvenanceField(build.packageVersion)
|
|
155
|
+
&& isProvenanceField(build.internalExecutableBuildHash)
|
|
156
|
+
&& isProvenanceField(build.lifecycleContractHash)
|
|
157
|
+
&& isProvenanceField(binding.instanceDigest)
|
|
158
|
+
&& isProvenanceField(binding.controlGeneration);
|
|
159
|
+
}
|
|
160
|
+
function sameControllerField(left, right, key) {
|
|
161
|
+
if (left === null || right === null)
|
|
162
|
+
return left === null && right === null;
|
|
163
|
+
return isCodexControllerBinding(left)
|
|
164
|
+
&& isCodexControllerBinding(right)
|
|
165
|
+
&& left[key] === right[key];
|
|
166
|
+
}
|
|
20
167
|
function sameController(left, right) {
|
|
21
|
-
|
|
22
|
-
|
|
168
|
+
if (left === null || right === null)
|
|
169
|
+
return left === null && right === null;
|
|
170
|
+
return isCodexControllerBinding(left)
|
|
171
|
+
&& isCodexControllerBinding(right)
|
|
172
|
+
&& left.instanceDigest === right.instanceDigest
|
|
173
|
+
&& left.controlGeneration === right.controlGeneration;
|
|
23
174
|
}
|
|
24
175
|
function createCodexCapabilityStore(options = {}) {
|
|
25
176
|
const root = options.root
|
|
@@ -211,22 +362,33 @@ function createCodexCapabilityStore(options = {}) {
|
|
|
211
362
|
write(file, next, record.revision);
|
|
212
363
|
return next;
|
|
213
364
|
}
|
|
214
|
-
function
|
|
215
|
-
sweep();
|
|
216
|
-
const { file, record } = loadActive(token);
|
|
365
|
+
function validateRecord(record, expected) {
|
|
217
366
|
if (record.status === 'consumed') {
|
|
218
367
|
throw capabilityError('CODEX_CAPABILITY_REPLAY', 'capability token was already consumed');
|
|
219
368
|
}
|
|
220
369
|
if (record.status !== 'attested') {
|
|
221
370
|
throw capabilityError('CODEX_CAPABILITY_NOT_ATTESTED', 'capability token has no current-session hook attestation');
|
|
222
371
|
}
|
|
223
|
-
const identity =
|
|
372
|
+
const identity = record.buildIdentity && typeof record.buildIdentity === 'object'
|
|
373
|
+
? verifyLifecycleBuildIdentity(record.buildIdentity, expected.buildIdentity)
|
|
374
|
+
: { ok: false };
|
|
224
375
|
if (!identity.ok
|
|
225
376
|
|| record.taskId !== expected.taskId
|
|
226
377
|
|| record.hookDefinitionHash !== expected.hookDefinitionHash
|
|
227
378
|
|| !sameController(record.controller, expected.controller ?? null)) {
|
|
228
|
-
throw capabilityError('CODEX_CAPABILITY_PROVENANCE_MISMATCH', 'capability consumption identity does not match');
|
|
379
|
+
throw capabilityError('CODEX_CAPABILITY_PROVENANCE_MISMATCH', 'capability consumption identity does not match', provenanceDetail(record, expected));
|
|
229
380
|
}
|
|
381
|
+
return record;
|
|
382
|
+
}
|
|
383
|
+
function validate(token, expected) {
|
|
384
|
+
sweep();
|
|
385
|
+
const { record } = loadActive(token);
|
|
386
|
+
return validateRecord(record, expected);
|
|
387
|
+
}
|
|
388
|
+
function consume(token, expected) {
|
|
389
|
+
sweep();
|
|
390
|
+
const { file, record } = loadActive(token);
|
|
391
|
+
validateRecord(record, expected);
|
|
230
392
|
const next = Object.freeze({
|
|
231
393
|
...record,
|
|
232
394
|
revision: record.revision + 1,
|
|
@@ -239,7 +401,8 @@ function createCodexCapabilityStore(options = {}) {
|
|
|
239
401
|
function inspect(token) {
|
|
240
402
|
return loadActive(token).record;
|
|
241
403
|
}
|
|
242
|
-
return Object.freeze({ arm, attest, consume, inspect, sweep });
|
|
404
|
+
return Object.freeze({ arm, attest, consume, inspect, validate, sweep });
|
|
243
405
|
}
|
|
244
406
|
export { createCodexCapabilityStore };
|
|
407
|
+
export { isCodexCapabilityProvenanceDetail };
|
|
245
408
|
//# sourceMappingURL=capability-store.js.map
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { getProcessIdentityState } from '../../../server/process-state.js';
|
|
5
|
+
import { computeLifecycleBuildIdentity, verifyLifecycleBuildIdentity } from './build-identity.js';
|
|
6
|
+
const HEX_256 = /^[a-f0-9]{64}$/u;
|
|
7
|
+
function exactKeys(value, keys) {
|
|
8
|
+
return Object.keys(value).sort().join(',') === [...keys].sort().join(',');
|
|
9
|
+
}
|
|
10
|
+
function validProcess(value) {
|
|
11
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
12
|
+
return false;
|
|
13
|
+
const identity = value;
|
|
14
|
+
return exactKeys(identity, ['pid', 'startTime'])
|
|
15
|
+
&& Number.isSafeInteger(identity.pid) && identity.pid > 0
|
|
16
|
+
&& Number.isSafeInteger(identity.startTime) && identity.startTime >= 0;
|
|
17
|
+
}
|
|
18
|
+
function validBuildIdentity(value) {
|
|
19
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
20
|
+
return false;
|
|
21
|
+
const identity = value;
|
|
22
|
+
return exactKeys(identity, [
|
|
23
|
+
'internalExecutableBuildHash', 'lifecycleContractHash', 'packageVersion', 'protocolVersion'
|
|
24
|
+
])
|
|
25
|
+
&& identity.protocolVersion === 3
|
|
26
|
+
&& typeof identity.packageVersion === 'string' && identity.packageVersion.length > 0
|
|
27
|
+
&& typeof identity.internalExecutableBuildHash === 'string' && HEX_256.test(identity.internalExecutableBuildHash)
|
|
28
|
+
&& typeof identity.lifecycleContractHash === 'string' && HEX_256.test(identity.lifecycleContractHash);
|
|
29
|
+
}
|
|
30
|
+
function validateContext(value) {
|
|
31
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
32
|
+
throw new Error('CODEX_SANDBOX_CONTROLLER_CONTEXT_INVALID');
|
|
33
|
+
}
|
|
34
|
+
const context = value;
|
|
35
|
+
const lease = context.controllerLease;
|
|
36
|
+
if (!exactKeys(context, [
|
|
37
|
+
'buildIdentity', 'controlGeneration', 'controllerInstanceDigest', 'controllerLease',
|
|
38
|
+
'controllerProcess', 'expiresAt', 'hookDefinitionHash', 'issuedAt',
|
|
39
|
+
'taskId', 'version'
|
|
40
|
+
])
|
|
41
|
+
|| context.version !== 2
|
|
42
|
+
|| typeof context.taskId !== 'string' || context.taskId.length === 0
|
|
43
|
+
|| typeof context.controlGeneration !== 'string' || context.controlGeneration.length === 0
|
|
44
|
+
|| typeof context.controllerInstanceDigest !== 'string' || !HEX_256.test(context.controllerInstanceDigest)
|
|
45
|
+
|| !validProcess(context.controllerProcess)
|
|
46
|
+
|| !lease || !exactKeys(lease, ['leaseId', 'leaseSecret', 'version'])
|
|
47
|
+
|| lease.version !== 1
|
|
48
|
+
|| typeof lease.leaseId !== 'string' || !HEX_256.test(lease.leaseId)
|
|
49
|
+
|| typeof lease.leaseSecret !== 'string' || !HEX_256.test(lease.leaseSecret)
|
|
50
|
+
|| !Number.isSafeInteger(context.issuedAt)
|
|
51
|
+
|| !Number.isSafeInteger(context.expiresAt)
|
|
52
|
+
|| context.expiresAt <= context.issuedAt
|
|
53
|
+
|| !validBuildIdentity(context.buildIdentity)
|
|
54
|
+
|| typeof context.hookDefinitionHash !== 'string' || !HEX_256.test(context.hookDefinitionHash)) {
|
|
55
|
+
throw new Error('CODEX_SANDBOX_CONTROLLER_CONTEXT_INVALID');
|
|
56
|
+
}
|
|
57
|
+
return context;
|
|
58
|
+
}
|
|
59
|
+
export function contextFromControllerLease(lease, input) {
|
|
60
|
+
return Object.freeze({
|
|
61
|
+
version: 2,
|
|
62
|
+
taskId: lease.taskId,
|
|
63
|
+
controlGeneration: lease.controlGeneration,
|
|
64
|
+
controllerInstanceDigest: lease.controllerInstanceDigest,
|
|
65
|
+
controllerProcess: lease.controllerProcess,
|
|
66
|
+
controllerLease: Object.freeze({ version: 1, leaseId: lease.leaseId, leaseSecret: lease.leaseSecret }),
|
|
67
|
+
issuedAt: lease.issuedAt,
|
|
68
|
+
expiresAt: lease.expiresAt,
|
|
69
|
+
buildIdentity: lease.buildIdentity,
|
|
70
|
+
hookDefinitionHash: input.hookDefinitionHash
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
export function writeCodexSandboxControllerContext(contextPath, context) {
|
|
74
|
+
validateContext(context);
|
|
75
|
+
fs.mkdirSync(path.dirname(contextPath), { recursive: true, mode: 0o700 });
|
|
76
|
+
const temporary = path.join(path.dirname(contextPath), `.${path.basename(contextPath)}.${process.pid}.${crypto.randomUUID()}.tmp`);
|
|
77
|
+
try {
|
|
78
|
+
fs.writeFileSync(temporary, `${JSON.stringify(context, null, 2)}\n`, { mode: 0o600, flag: 'wx' });
|
|
79
|
+
fs.chmodSync(temporary, 0o600);
|
|
80
|
+
fs.renameSync(temporary, contextPath);
|
|
81
|
+
}
|
|
82
|
+
finally {
|
|
83
|
+
fs.rmSync(temporary, { force: true });
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
export function verifyCodexSandboxControllerContextWithWarnings(contextPath, options = {}) {
|
|
87
|
+
let stat;
|
|
88
|
+
try {
|
|
89
|
+
stat = fs.lstatSync(contextPath);
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
throw new Error('CODEX_SANDBOX_CONTROLLER_CONTEXT_INVALID');
|
|
93
|
+
}
|
|
94
|
+
if (!stat.isFile() || stat.isSymbolicLink()
|
|
95
|
+
|| (process.platform !== 'win32' && (stat.mode & 0o077) !== 0)) {
|
|
96
|
+
throw new Error('CODEX_SANDBOX_CONTROLLER_CONTEXT_INVALID');
|
|
97
|
+
}
|
|
98
|
+
let context;
|
|
99
|
+
try {
|
|
100
|
+
context = validateContext(JSON.parse(fs.readFileSync(contextPath, 'utf8')));
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
throw new Error('CODEX_SANDBOX_CONTROLLER_CONTEXT_INVALID');
|
|
104
|
+
}
|
|
105
|
+
const repoRoot = options.repoRoot ?? process.cwd();
|
|
106
|
+
const generation = options.generation ?? process.env.AGENT_INFRA_CONTROL_GENERATION;
|
|
107
|
+
const state = (options.probeProcess ?? getProcessIdentityState)(context.controllerProcess);
|
|
108
|
+
if (context.controlGeneration !== generation
|
|
109
|
+
|| context.expiresAt <= (options.now ?? Date.now())
|
|
110
|
+
|| state !== 'alive') {
|
|
111
|
+
throw new Error('CODEX_SANDBOX_CONTROLLER_CONTEXT_INVALID');
|
|
112
|
+
}
|
|
113
|
+
const currentBuildIdentity = computeLifecycleBuildIdentity(repoRoot);
|
|
114
|
+
const identity = verifyLifecycleBuildIdentity(context.buildIdentity, currentBuildIdentity);
|
|
115
|
+
if (!identity.ok)
|
|
116
|
+
throw new Error(`${identity.code}: ${identity.message}`);
|
|
117
|
+
return Object.freeze({ context: Object.freeze(context), warnings: identity.warnings });
|
|
118
|
+
}
|
|
119
|
+
export function controllerProofFromContext(context) {
|
|
120
|
+
return Object.freeze({
|
|
121
|
+
version: 1,
|
|
122
|
+
leaseId: context.controllerLease.leaseId,
|
|
123
|
+
leaseSecret: context.controllerLease.leaseSecret,
|
|
124
|
+
controllerProcess: context.controllerProcess
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=controller-context.js.map
|
|
@@ -29,6 +29,12 @@ function invalid(state, code, message) {
|
|
|
29
29
|
function same(left, right) {
|
|
30
30
|
return JSON.stringify(left) === JSON.stringify(right);
|
|
31
31
|
}
|
|
32
|
+
function sameChildCorrelation(left, right) {
|
|
33
|
+
return left.sessionId === right.sessionId
|
|
34
|
+
&& left.childThreadId === right.childThreadId
|
|
35
|
+
&& left.parentThreadId === right.parentThreadId
|
|
36
|
+
&& left.nativeAgent === right.nativeAgent;
|
|
37
|
+
}
|
|
32
38
|
function requiredStrings(event) {
|
|
33
39
|
return Object.entries(event).every(([key, value]) => key === 'forkedFromId'
|
|
34
40
|
|| (typeof value !== 'string' || value.trim() !== ''));
|
|
@@ -152,6 +158,14 @@ function reduceCodexLifecycleEvent(state, event) {
|
|
|
152
158
|
if (existing) {
|
|
153
159
|
if (same(existing, event))
|
|
154
160
|
return state;
|
|
161
|
+
if (existing.type === 'hook-child'
|
|
162
|
+
&& event.type === 'hook-child'
|
|
163
|
+
&& sameChildCorrelation(existing, event)
|
|
164
|
+
&& (existing.source === 'parent-rollout') !== (event.source === 'parent-rollout')) {
|
|
165
|
+
if (existing.source !== 'parent-rollout')
|
|
166
|
+
return state;
|
|
167
|
+
return derive(Object.freeze({ ...state, child: Object.freeze({ ...event }) }));
|
|
168
|
+
}
|
|
155
169
|
return invalid(state, 'CODEX_EVIDENCE_REPLAY_CONFLICT', `conflicting '${event.type}' event`);
|
|
156
170
|
}
|
|
157
171
|
return derive(Object.freeze({ ...state, [key]: Object.freeze({ ...event }) }));
|