@fitlab-ai/agent-infra 0.9.9 → 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/dist/bin/internal-cli.js +5 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/build-identity.js +17 -10
- package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-context.js +11 -31
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +15 -33
- 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/internal/codex-lifecycle.js +2 -2
- package/dist/lib/internal/codex-sandbox-controller.js +4 -3
- package/dist/lib/internal/sandbox-control.js +2 -2
- package/dist/lib/{task/commands/validate.js → internal/task-validate.js} +75 -42
- package/dist/lib/run/tui.js +5 -2
- package/dist/lib/sandbox/control/controller-registration.js +4 -2
- package/dist/lib/sandbox/control/executor.js +4 -1
- package/dist/lib/task/codex-orchestration.js +31 -34
- package/dist/lib/task/delegation-receipts.js +8 -7
- package/dist/lib/task/finalization.js +72 -46
- package/dist/lib/task/index.js +0 -9
- package/dist/lib/task/orchestration.js +1 -1
- package/dist/lib/task/verification-engine.js +0 -3
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapters/codex-lifecycle/build-identity.ts +25 -10
- package/lib/agent-clients/adapters/codex-lifecycle/controller-context.ts +16 -33
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +16 -35
- 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/internal/codex-lifecycle.ts +2 -2
- package/lib/internal/codex-sandbox-controller.ts +4 -3
- package/lib/internal/sandbox-control.ts +2 -2
- package/lib/{task/commands/validate.ts → internal/task-validate.ts} +75 -40
- package/lib/run/tui.ts +5 -3
- package/lib/sandbox/control/controller-registration.ts +8 -3
- package/lib/sandbox/control/executor.ts +8 -2
- package/lib/task/codex-orchestration.ts +38 -44
- package/lib/task/delegation-receipts.ts +8 -6
- package/lib/task/finalization.ts +84 -42
- package/lib/task/index.ts +0 -8
- package/lib/task/orchestration.ts +2 -1
- package/lib/task/verification-engine.ts +0 -3
- package/package.json +1 -1
- package/templates/.agents/QUICKSTART.en.md +2 -3
- package/templates/.agents/QUICKSTART.zh-CN.md +2 -3
- 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/skills/analyze-task/SKILL.en.md +1 -0
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +1 -0
- 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/create-pr/SKILL.en.md +12 -6
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +12 -6
- 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/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 +2 -2
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +2 -2
- 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/.trae/skills/update-agent-infra.en.md +8 -0
- package/templates/.trae/skills/update-agent-infra.zh-CN.md +8 -0
|
@@ -4,7 +4,12 @@ import path from 'node:path';
|
|
|
4
4
|
|
|
5
5
|
import { getProcessIdentityState, type ProcessIdentity } from '../../../server/process-state.ts';
|
|
6
6
|
import type { CodexControllerLeaseProofV1, CodexControllerLeaseV1 } from '../../../sandbox/control/controller-registration.ts';
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
computeLifecycleBuildIdentity,
|
|
9
|
+
verifyLifecycleBuildIdentity,
|
|
10
|
+
type LifecycleBuildIdentity,
|
|
11
|
+
type LifecycleIdentityWarning
|
|
12
|
+
} from './build-identity.ts';
|
|
8
13
|
|
|
9
14
|
const HEX_256 = /^[a-f0-9]{64}$/u;
|
|
10
15
|
|
|
@@ -19,30 +24,12 @@ export type CodexSandboxControllerContextV2 = Readonly<{
|
|
|
19
24
|
expiresAt: number;
|
|
20
25
|
buildIdentity: LifecycleBuildIdentity;
|
|
21
26
|
hookDefinitionHash: string;
|
|
22
|
-
lifecycleProfilesHash: string;
|
|
23
27
|
}>;
|
|
24
28
|
|
|
25
29
|
function exactKeys(value: Record<string, unknown>, keys: readonly string[]): boolean {
|
|
26
30
|
return Object.keys(value).sort().join(',') === [...keys].sort().join(',');
|
|
27
31
|
}
|
|
28
32
|
|
|
29
|
-
function digestProfiles(repoRoot: string): string {
|
|
30
|
-
const files = [
|
|
31
|
-
path.join(repoRoot, '.codex', 'agents', 'agent-infra-lifecycle-executor.toml'),
|
|
32
|
-
path.join(repoRoot, '.codex', 'agents', 'agent-infra-lifecycle-reviewer.toml')
|
|
33
|
-
];
|
|
34
|
-
const hash = crypto.createHash('sha256');
|
|
35
|
-
for (const file of files.sort()) {
|
|
36
|
-
const stat = fs.lstatSync(file);
|
|
37
|
-
if (!stat.isFile() || stat.isSymbolicLink()) throw new Error('CODEX_SANDBOX_CONTROLLER_BUNDLE_MISMATCH');
|
|
38
|
-
hash.update(path.basename(file));
|
|
39
|
-
hash.update('\0');
|
|
40
|
-
hash.update(fs.readFileSync(file));
|
|
41
|
-
hash.update('\0');
|
|
42
|
-
}
|
|
43
|
-
return hash.digest('hex');
|
|
44
|
-
}
|
|
45
|
-
|
|
46
33
|
function validProcess(value: unknown): value is ProcessIdentity {
|
|
47
34
|
if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
|
|
48
35
|
const identity = value as Record<string, unknown>;
|
|
@@ -72,7 +59,7 @@ function validateContext(value: unknown): CodexSandboxControllerContextV2 {
|
|
|
72
59
|
if (!exactKeys(context, [
|
|
73
60
|
'buildIdentity', 'controlGeneration', 'controllerInstanceDigest', 'controllerLease',
|
|
74
61
|
'controllerProcess', 'expiresAt', 'hookDefinitionHash', 'issuedAt',
|
|
75
|
-
'
|
|
62
|
+
'taskId', 'version'
|
|
76
63
|
])
|
|
77
64
|
|| context.version !== 2
|
|
78
65
|
|| typeof context.taskId !== 'string' || context.taskId.length === 0
|
|
@@ -87,8 +74,7 @@ function validateContext(value: unknown): CodexSandboxControllerContextV2 {
|
|
|
87
74
|
|| !Number.isSafeInteger(context.expiresAt)
|
|
88
75
|
|| (context.expiresAt as number) <= (context.issuedAt as number)
|
|
89
76
|
|| !validBuildIdentity(context.buildIdentity)
|
|
90
|
-
|| typeof context.hookDefinitionHash !== 'string' || !HEX_256.test(context.hookDefinitionHash)
|
|
91
|
-
|| typeof context.lifecycleProfilesHash !== 'string' || !HEX_256.test(context.lifecycleProfilesHash)) {
|
|
77
|
+
|| typeof context.hookDefinitionHash !== 'string' || !HEX_256.test(context.hookDefinitionHash)) {
|
|
92
78
|
throw new Error('CODEX_SANDBOX_CONTROLLER_CONTEXT_INVALID');
|
|
93
79
|
}
|
|
94
80
|
return context as unknown as CodexSandboxControllerContextV2;
|
|
@@ -96,7 +82,7 @@ function validateContext(value: unknown): CodexSandboxControllerContextV2 {
|
|
|
96
82
|
|
|
97
83
|
export function contextFromControllerLease(
|
|
98
84
|
lease: CodexControllerLeaseV1,
|
|
99
|
-
|
|
85
|
+
input: Readonly<{ hookDefinitionHash: string }>
|
|
100
86
|
): CodexSandboxControllerContextV2 {
|
|
101
87
|
return Object.freeze({
|
|
102
88
|
version: 2,
|
|
@@ -108,8 +94,7 @@ export function contextFromControllerLease(
|
|
|
108
94
|
issuedAt: lease.issuedAt,
|
|
109
95
|
expiresAt: lease.expiresAt,
|
|
110
96
|
buildIdentity: lease.buildIdentity,
|
|
111
|
-
hookDefinitionHash:
|
|
112
|
-
lifecycleProfilesHash: hashes.lifecycleProfilesHash
|
|
97
|
+
hookDefinitionHash: input.hookDefinitionHash
|
|
113
98
|
});
|
|
114
99
|
}
|
|
115
100
|
|
|
@@ -129,7 +114,7 @@ export function writeCodexSandboxControllerContext(
|
|
|
129
114
|
}
|
|
130
115
|
}
|
|
131
116
|
|
|
132
|
-
export function
|
|
117
|
+
export function verifyCodexSandboxControllerContextWithWarnings(
|
|
133
118
|
contextPath: string,
|
|
134
119
|
options: Readonly<{
|
|
135
120
|
repoRoot?: string;
|
|
@@ -137,7 +122,7 @@ export function verifyCodexSandboxControllerContext(
|
|
|
137
122
|
generation?: string;
|
|
138
123
|
probeProcess?: (identity: ProcessIdentity) => 'alive' | 'dead' | 'unknown';
|
|
139
124
|
}> = {}
|
|
140
|
-
): CodexSandboxControllerContextV2 {
|
|
125
|
+
): Readonly<{ context: CodexSandboxControllerContextV2; warnings: readonly LifecycleIdentityWarning[] }> {
|
|
141
126
|
let stat: fs.Stats;
|
|
142
127
|
try {
|
|
143
128
|
stat = fs.lstatSync(contextPath);
|
|
@@ -162,12 +147,10 @@ export function verifyCodexSandboxControllerContext(
|
|
|
162
147
|
|| state !== 'alive') {
|
|
163
148
|
throw new Error('CODEX_SANDBOX_CONTROLLER_CONTEXT_INVALID');
|
|
164
149
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
}
|
|
170
|
-
return Object.freeze(context);
|
|
150
|
+
const currentBuildIdentity = computeLifecycleBuildIdentity(repoRoot);
|
|
151
|
+
const identity = verifyLifecycleBuildIdentity(context.buildIdentity, currentBuildIdentity);
|
|
152
|
+
if (!identity.ok) throw new Error(`${identity.code}: ${identity.message}`);
|
|
153
|
+
return Object.freeze({ context: Object.freeze(context), warnings: identity.warnings });
|
|
171
154
|
}
|
|
172
155
|
|
|
173
156
|
export function controllerProofFromContext(context: CodexSandboxControllerContextV2): CodexControllerLeaseProofV1 {
|
|
@@ -14,11 +14,11 @@ import {
|
|
|
14
14
|
} from '../../../sandbox/control/client.ts';
|
|
15
15
|
import { readSandboxControlStatus } from '../../../sandbox/control/state.ts';
|
|
16
16
|
import { getProcessStartTime, type ProcessIdentity } from '../../../server/process-state.ts';
|
|
17
|
-
import {
|
|
17
|
+
import { LIFECYCLE_PROTOCOL_VERSION, type LifecycleIdentityWarning } from './build-identity.ts';
|
|
18
18
|
import {
|
|
19
19
|
contextFromControllerLease,
|
|
20
20
|
controllerProofFromContext,
|
|
21
|
-
|
|
21
|
+
verifyCodexSandboxControllerContextWithWarnings as verifyContextFileWithWarnings,
|
|
22
22
|
writeCodexSandboxControllerContext,
|
|
23
23
|
type CodexSandboxControllerContextV2
|
|
24
24
|
} from './controller-context.ts';
|
|
@@ -70,21 +70,6 @@ type VerifyContextOptions = Readonly<{
|
|
|
70
70
|
requestControllerVerify?: typeof requestCodexControllerVerify;
|
|
71
71
|
}>;
|
|
72
72
|
|
|
73
|
-
function digestFiles(files: readonly string[]): string {
|
|
74
|
-
const hash = crypto.createHash('sha256');
|
|
75
|
-
for (const file of [...files].sort()) {
|
|
76
|
-
const stat = fs.lstatSync(file);
|
|
77
|
-
if (!stat.isFile() || stat.isSymbolicLink()) {
|
|
78
|
-
throw new Error('CODEX_SANDBOX_CONTROLLER_BUNDLE_MISMATCH');
|
|
79
|
-
}
|
|
80
|
-
hash.update(path.basename(file));
|
|
81
|
-
hash.update('\0');
|
|
82
|
-
hash.update(fs.readFileSync(file));
|
|
83
|
-
hash.update('\0');
|
|
84
|
-
}
|
|
85
|
-
return hash.digest('hex');
|
|
86
|
-
}
|
|
87
|
-
|
|
88
73
|
function copyRegular(source: string, destination: string, mode: number): void {
|
|
89
74
|
const stat = fs.lstatSync(source);
|
|
90
75
|
if (!stat.isFile() || stat.isSymbolicLink()) {
|
|
@@ -166,17 +151,18 @@ function verifyControl(taskId: string, control: ControllerControl): void {
|
|
|
166
151
|
if (payload.taskId !== taskId) throw new Error('CODEX_SANDBOX_CONTROLLER_TASK_BINDING_INVALID');
|
|
167
152
|
}
|
|
168
153
|
|
|
169
|
-
function
|
|
154
|
+
function verifyCodexSandboxControllerContextWithWarnings(
|
|
170
155
|
contextPath: string,
|
|
171
156
|
options: VerifyContextOptions = {}
|
|
172
|
-
): CodexSandboxControllerContextV2 {
|
|
157
|
+
): Readonly<{ context: CodexSandboxControllerContextV2; warnings: readonly LifecycleIdentityWarning[] }> {
|
|
173
158
|
const control = options.control ?? controlFromEnvironment();
|
|
174
|
-
const
|
|
159
|
+
const fileVerification = verifyContextFileWithWarnings(contextPath, {
|
|
175
160
|
repoRoot: options.repoRoot,
|
|
176
161
|
now: options.now,
|
|
177
162
|
generation: control.generation,
|
|
178
163
|
probeProcess: options.probeProcess
|
|
179
164
|
});
|
|
165
|
+
const context = fileVerification.context;
|
|
180
166
|
const verified = (options.requestControllerVerify ?? requestCodexControllerVerify)({
|
|
181
167
|
controllerProof: controllerProofFromContext(context),
|
|
182
168
|
...control,
|
|
@@ -187,7 +173,10 @@ function verifyCodexSandboxControllerContext(
|
|
|
187
173
|
|| verified.binding.controllerInstanceDigest !== context.controllerInstanceDigest) {
|
|
188
174
|
throw new Error('CODEX_SANDBOX_CONTROLLER_CONTEXT_INVALID');
|
|
189
175
|
}
|
|
190
|
-
return
|
|
176
|
+
return Object.freeze({
|
|
177
|
+
context,
|
|
178
|
+
warnings: fileVerification.warnings
|
|
179
|
+
});
|
|
191
180
|
}
|
|
192
181
|
|
|
193
182
|
function detectedCodexVersion(): string {
|
|
@@ -337,30 +326,22 @@ function prepareCodexSandboxController(
|
|
|
337
326
|
copyRegular(hooks, path.join(home, 'hooks.json'), 0o600);
|
|
338
327
|
copyRegular(executor, path.join(home, 'agents', path.basename(executor)), 0o600);
|
|
339
328
|
copyRegular(reviewer, path.join(home, 'agents', path.basename(reviewer)), 0o600);
|
|
340
|
-
if (crypto.createHash('sha256').update(fs.readFileSync(path.join(home, 'hooks.json'))).digest('hex')
|
|
341
|
-
!== crypto.createHash('sha256').update(fs.readFileSync(hooks)).digest('hex')
|
|
342
|
-
|| digestFiles([
|
|
343
|
-
path.join(home, 'agents', path.basename(executor)),
|
|
344
|
-
path.join(home, 'agents', path.basename(reviewer))
|
|
345
|
-
]) !== digestFiles([executor, reviewer])) {
|
|
346
|
-
throw new Error('CODEX_SANDBOX_CONTROLLER_BUNDLE_MISMATCH');
|
|
347
|
-
}
|
|
348
329
|
|
|
349
|
-
const buildIdentity = computeLifecycleBuildIdentity(repoRoot);
|
|
350
330
|
const opened = (options.openController ?? requestCodexControllerOpen)({
|
|
351
331
|
controllerProcess: { pid: process.pid, startTime: parentStartTime },
|
|
352
332
|
...control,
|
|
353
333
|
timeoutMs: 30_000
|
|
354
334
|
});
|
|
355
335
|
const taskId = opened.lease.taskId;
|
|
336
|
+
if (opened.lease.buildIdentity.protocolVersion !== LIFECYCLE_PROTOCOL_VERSION) {
|
|
337
|
+
throw new Error('CODEX_LIFECYCLE_PROTOCOL_MISMATCH: Codex lifecycle protocol version does not match');
|
|
338
|
+
}
|
|
356
339
|
context = contextFromControllerLease(opened.lease, {
|
|
357
|
-
hookDefinitionHash: crypto.createHash('sha256').update(fs.readFileSync(hooks)).digest('hex')
|
|
358
|
-
lifecycleProfilesHash: digestFiles([executor, reviewer])
|
|
340
|
+
hookDefinitionHash: crypto.createHash('sha256').update(fs.readFileSync(hooks)).digest('hex')
|
|
359
341
|
});
|
|
360
342
|
if (opened.lease.controlGeneration !== control.generation
|
|
361
343
|
|| opened.lease.controllerProcess.pid !== process.pid
|
|
362
|
-
|| opened.lease.controllerProcess.startTime !== parentStartTime
|
|
363
|
-
|| JSON.stringify(opened.lease.buildIdentity) !== JSON.stringify(buildIdentity)) {
|
|
344
|
+
|| opened.lease.controllerProcess.startTime !== parentStartTime) {
|
|
364
345
|
throw new Error('SANDBOX_CONTROL_RESULT_INVALID');
|
|
365
346
|
}
|
|
366
347
|
(options.verifyTaskBinding ?? verifyControl)(taskId, control);
|
|
@@ -460,7 +441,7 @@ async function runCodexSandboxController(
|
|
|
460
441
|
export {
|
|
461
442
|
prepareCodexSandboxController,
|
|
462
443
|
runCodexSandboxController,
|
|
463
|
-
|
|
444
|
+
verifyCodexSandboxControllerContextWithWarnings,
|
|
464
445
|
controllerProofFromContext
|
|
465
446
|
};
|
|
466
447
|
export type {
|
|
@@ -37,6 +37,8 @@ function resolveHostCatalogPath(value: unknown, hostHomeDir: string): string | n
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
const CODEX_DISABLED_FEATURE_FLAGS = ['apps', 'enable_mcp_apps'] as const;
|
|
40
|
+
const SANDBOX_CODEX_HOOKS_PATH = '/workspace/.codex/hooks.json';
|
|
41
|
+
const CODEX_HOOK_TRUST_HASH = /^sha256:[a-f0-9]{64}$/;
|
|
40
42
|
|
|
41
43
|
function removeCodexMcpServers(sandboxParsed: JsonObject): boolean {
|
|
42
44
|
if (!Object.hasOwn(sandboxParsed, 'mcp_servers')) {
|
|
@@ -75,27 +77,75 @@ function inheritDisabledCodexFeatureFlags(
|
|
|
75
77
|
return changed;
|
|
76
78
|
}
|
|
77
79
|
|
|
80
|
+
function inheritCodexHookTrustState(
|
|
81
|
+
sandboxParsed: JsonObject,
|
|
82
|
+
hostParsed: JsonObject,
|
|
83
|
+
hostProjectDir?: string
|
|
84
|
+
): boolean {
|
|
85
|
+
if (!hostProjectDir) {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (Object.hasOwn(sandboxParsed, 'hooks') && !isJsonObjectRecord(sandboxParsed.hooks)) {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
const sandboxHooks = (sandboxParsed.hooks as JsonObject | undefined) ?? {};
|
|
93
|
+
if (Object.hasOwn(sandboxHooks, 'state') && !isJsonObjectRecord(sandboxHooks.state)) {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const sandboxState = (sandboxHooks.state as JsonObject | undefined) ?? {};
|
|
98
|
+
const sandboxPrefix = `${SANDBOX_CODEX_HOOKS_PATH}:`;
|
|
99
|
+
const nextState = Object.fromEntries(
|
|
100
|
+
Object.entries(sandboxState).filter(([key]) => !key.startsWith(sandboxPrefix))
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
const hostState = isJsonObjectRecord(hostParsed.hooks)
|
|
104
|
+
&& isJsonObjectRecord(hostParsed.hooks.state)
|
|
105
|
+
? hostParsed.hooks.state
|
|
106
|
+
: {};
|
|
107
|
+
const hostPrefix = `${path.join(hostProjectDir, '.codex', 'hooks.json')}:`;
|
|
108
|
+
for (const [key, value] of Object.entries(hostState)) {
|
|
109
|
+
if (!key.startsWith(hostPrefix) || !isJsonObjectRecord(value)) {
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
const trustedHash = value.trusted_hash;
|
|
113
|
+
if (typeof trustedHash !== 'string' || !CODEX_HOOK_TRUST_HASH.test(trustedHash)) {
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
nextState[`${sandboxPrefix}${key.slice(hostPrefix.length)}`] = {
|
|
117
|
+
trusted_hash: trustedHash,
|
|
118
|
+
...(typeof value.enabled === 'boolean' ? { enabled: value.enabled } : {})
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (JSON.stringify(sandboxState) === JSON.stringify(nextState)) {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
if (Object.keys(nextState).length > 0) {
|
|
126
|
+
sandboxHooks.state = nextState;
|
|
127
|
+
sandboxParsed.hooks = sandboxHooks;
|
|
128
|
+
} else {
|
|
129
|
+
delete sandboxHooks.state;
|
|
130
|
+
if (Object.keys(sandboxHooks).length > 0) {
|
|
131
|
+
sandboxParsed.hooks = sandboxHooks;
|
|
132
|
+
} else {
|
|
133
|
+
delete sandboxParsed.hooks;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
|
|
78
139
|
function ensureCodexModelInheritance(
|
|
79
140
|
toolDir: string,
|
|
80
141
|
hostHomeDir?: string,
|
|
81
|
-
containerCodexDir: string = '/home/devuser/.codex'
|
|
142
|
+
containerCodexDir: string = '/home/devuser/.codex',
|
|
143
|
+
hostProjectDir?: string
|
|
82
144
|
): void {
|
|
83
145
|
if (!hostHomeDir) {
|
|
84
146
|
return;
|
|
85
147
|
}
|
|
86
148
|
|
|
87
|
-
const hostConfigPath = path.join(hostHomeDir, '.codex', 'config.toml');
|
|
88
|
-
if (!fs.existsSync(hostConfigPath)) {
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
let hostParsed: JsonObject;
|
|
93
|
-
try {
|
|
94
|
-
hostParsed = toml.parse(fs.readFileSync(hostConfigPath, 'utf8')) as JsonObject;
|
|
95
|
-
} catch {
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
149
|
const sandboxConfigPath = path.join(toolDir, 'config.toml');
|
|
100
150
|
// This rewrites sandbox-side TOML and drops comments; the host config stays untouched.
|
|
101
151
|
let sandboxParsed: JsonObject = {};
|
|
@@ -107,8 +157,25 @@ function ensureCodexModelInheritance(
|
|
|
107
157
|
}
|
|
108
158
|
}
|
|
109
159
|
|
|
160
|
+
const hostConfigPath = path.join(hostHomeDir, '.codex', 'config.toml');
|
|
161
|
+
let hostParsed: JsonObject | null = null;
|
|
162
|
+
if (fs.existsSync(hostConfigPath)) {
|
|
163
|
+
try {
|
|
164
|
+
hostParsed = toml.parse(fs.readFileSync(hostConfigPath, 'utf8')) as JsonObject;
|
|
165
|
+
} catch {
|
|
166
|
+
// An unavailable host trust source must not leave previously projected trust active.
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (!hostParsed) {
|
|
170
|
+
if (inheritCodexHookTrustState(sandboxParsed, {}, hostProjectDir)) {
|
|
171
|
+
fs.writeFileSync(sandboxConfigPath, `${toml.stringify(sandboxParsed)}\n`, 'utf8');
|
|
172
|
+
}
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
|
|
110
176
|
let changed = removeCodexMcpServers(sandboxParsed);
|
|
111
177
|
changed = inheritDisabledCodexFeatureFlags(sandboxParsed, hostParsed) || changed;
|
|
178
|
+
changed = inheritCodexHookTrustState(sandboxParsed, hostParsed, hostProjectDir) || changed;
|
|
112
179
|
|
|
113
180
|
const inheritSpecs: Array<readonly [string, 'string' | 'number']> = [
|
|
114
181
|
['model', 'string'],
|
|
@@ -180,7 +247,15 @@ const codexBeforeContainerCreateHook: AgentClientSandboxHook = {
|
|
|
180
247
|
message: 'Codex sandbox hook requires its resolved tool state.'
|
|
181
248
|
};
|
|
182
249
|
}
|
|
183
|
-
|
|
250
|
+
const hostProjectDir = typeof context.config?.repoRoot === 'string'
|
|
251
|
+
? context.config.repoRoot
|
|
252
|
+
: undefined;
|
|
253
|
+
ensureCodexModelInheritance(
|
|
254
|
+
entry.dir,
|
|
255
|
+
create.hostHome,
|
|
256
|
+
entry.tool.containerMount,
|
|
257
|
+
hostProjectDir
|
|
258
|
+
);
|
|
184
259
|
ensureCodexWorkspaceTrust(entry.dir);
|
|
185
260
|
return { status: 'ready' };
|
|
186
261
|
}
|
|
@@ -4,13 +4,15 @@ import { claudeCodeAdapter } from './claude-code.ts';
|
|
|
4
4
|
import { codexAdapter } from './codex.ts';
|
|
5
5
|
import { antigravityCliAdapter } from './antigravity-cli.ts';
|
|
6
6
|
import { opencodeAdapter } from './opencode.ts';
|
|
7
|
+
import { traecliAdapter } from './traecli.ts';
|
|
7
8
|
|
|
8
9
|
const adaptersById = new Map(
|
|
9
10
|
[
|
|
10
11
|
claudeCodeAdapter,
|
|
11
12
|
codexAdapter,
|
|
12
13
|
antigravityCliAdapter,
|
|
13
|
-
opencodeAdapter
|
|
14
|
+
opencodeAdapter,
|
|
15
|
+
traecliAdapter
|
|
14
16
|
].map((adapter) => [adapter.id, adapter])
|
|
15
17
|
);
|
|
16
18
|
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { defineAgentClientAdapter } from '../adapter.ts';
|
|
2
|
+
import { hostJoin } from '../../sandbox/engines/wsl2-paths.ts';
|
|
3
|
+
|
|
4
|
+
const traecliAdapter = defineAgentClientAdapter({
|
|
5
|
+
id: 'traecli',
|
|
6
|
+
displayName: 'TraeCode CLI',
|
|
7
|
+
invocation: '/${skillName}',
|
|
8
|
+
capabilities: {
|
|
9
|
+
instructions: { level: 'compatible' },
|
|
10
|
+
skills: { level: 'compatible' },
|
|
11
|
+
commands: { level: 'integrated' },
|
|
12
|
+
hooks: { level: 'compatible' },
|
|
13
|
+
subagents: { level: 'experimental' },
|
|
14
|
+
orchestration: { level: 'experimental' },
|
|
15
|
+
sandbox: { level: 'integrated' },
|
|
16
|
+
verification: { level: 'compatible' }
|
|
17
|
+
},
|
|
18
|
+
modelSelection: {
|
|
19
|
+
kind: 'interactive-only',
|
|
20
|
+
command: '/model',
|
|
21
|
+
guidance: 'Use the host model picker for the complete model and reasoning-effort catalog.'
|
|
22
|
+
},
|
|
23
|
+
delegationEvidence: {
|
|
24
|
+
actualModel: 'unavailable',
|
|
25
|
+
actualReasoningEffort: 'unavailable'
|
|
26
|
+
},
|
|
27
|
+
project: {
|
|
28
|
+
ownedPathPrefixes: ['.trae/'],
|
|
29
|
+
managed: ['.trae/skills/'],
|
|
30
|
+
merged: [],
|
|
31
|
+
ejected: [],
|
|
32
|
+
seedCommands: [{
|
|
33
|
+
templates: {
|
|
34
|
+
en: '.trae/skills/update-agent-infra.en.md',
|
|
35
|
+
'zh-CN': '.trae/skills/update-agent-infra.zh-CN.md'
|
|
36
|
+
},
|
|
37
|
+
target: '.trae/skills/update-agent-infra.md'
|
|
38
|
+
}],
|
|
39
|
+
customCommand: {
|
|
40
|
+
target: '.trae/skills/${skillName}.md',
|
|
41
|
+
frontmatter: {},
|
|
42
|
+
includeUsage: true,
|
|
43
|
+
inheritDisableModelInvocation: true
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
sandbox: {
|
|
47
|
+
createTool: ({ home }) => ({
|
|
48
|
+
id: 'traecli',
|
|
49
|
+
name: 'TraeCode CLI',
|
|
50
|
+
install: {
|
|
51
|
+
type: 'shell',
|
|
52
|
+
cmd: 'curl -fsSL https://trae.cn/trae-cli/install_v2.sh | bash'
|
|
53
|
+
},
|
|
54
|
+
sandboxBase: hostJoin(home, '.agent-infra', 'sandboxes', 'traecli'),
|
|
55
|
+
containerMount: '/home/devuser/.trae',
|
|
56
|
+
versionCmd: 'traecli --version',
|
|
57
|
+
setupHint: 'Run traecli login inside the container to authenticate.'
|
|
58
|
+
}),
|
|
59
|
+
aliases: [
|
|
60
|
+
{ name: 'traecli-yolo', command: 'traecli --dangerously-bypass-approvals-and-sandbox; tput ed' },
|
|
61
|
+
{ name: 'ty', command: 'traecli --dangerously-bypass-approvals-and-sandbox; tput ed' }
|
|
62
|
+
],
|
|
63
|
+
hooks: []
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
export { traecliAdapter };
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// internal commands and the rendering side share one definition. Historical
|
|
4
4
|
// Gemini tokens remain read-only and are handled by classifyAgent below.
|
|
5
5
|
export const KNOWN_AI_AGENTS: ReadonlySet<string> =
|
|
6
|
-
new Set(['claude', 'codex', 'antigravity', 'opencode', 'cursor']);
|
|
6
|
+
new Set(['claude', 'codex', 'antigravity', 'opencode', 'cursor', 'traecli']);
|
|
7
7
|
|
|
8
8
|
// Long name -> short name mapping (HD-4, decision A): `claude-code` reuses the
|
|
9
9
|
// existing orchestration precedent, and `antigravity-cli` is the active
|
|
@@ -14,7 +14,7 @@ export const AGENT_LONG_NAMES: Readonly<Record<string, string>> = {
|
|
|
14
14
|
};
|
|
15
15
|
|
|
16
16
|
export const AGENT_USAGE_HINT =
|
|
17
|
-
"agent must be a short token (claude/codex/antigravity/opencode/cursor), " +
|
|
17
|
+
"agent must be a short token (claude/codex/antigravity/opencode/cursor/traecli), " +
|
|
18
18
|
"a long name (claude-code -> claude, antigravity-cli -> antigravity), or 'human'";
|
|
19
19
|
|
|
20
20
|
// Read-side historical compatibility for activity-log rendering: `gemini` and
|
package/lib/defaults.json
CHANGED
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
import { createCodexLifecycleStore } from '../agent-clients/adapters/codex-lifecycle/store.ts';
|
|
12
12
|
import { createCodexCapabilityStore } from '../agent-clients/adapters/codex-lifecycle/capability-store.ts';
|
|
13
13
|
import { computeLifecycleBuildIdentity } from '../agent-clients/adapters/codex-lifecycle/build-identity.ts';
|
|
14
|
-
import {
|
|
14
|
+
import { verifyCodexSandboxControllerContextWithWarnings } from '../agent-clients/adapters/codex-lifecycle/sandbox-controller.ts';
|
|
15
15
|
import type { CodexLifecycleEvent } from '../agent-clients/adapters/codex-lifecycle/evidence.ts';
|
|
16
16
|
import { resolveTaskRef } from '../task/resolve-ref.ts';
|
|
17
17
|
import { hasSealableOrchestrationDelegation } from '../task/orchestration.ts';
|
|
@@ -101,7 +101,7 @@ function hookDefinitionHash(): string {
|
|
|
101
101
|
function controllerBinding(taskId: string) {
|
|
102
102
|
const contextPath = process.env.AGENT_INFRA_CODEX_CONTROLLER_CONTEXT;
|
|
103
103
|
if (!contextPath) return undefined;
|
|
104
|
-
const context =
|
|
104
|
+
const context = verifyCodexSandboxControllerContextWithWarnings(contextPath, { repoRoot: process.cwd() }).context;
|
|
105
105
|
return {
|
|
106
106
|
instanceDigest: context.controllerInstanceDigest,
|
|
107
107
|
controlGeneration: context.controlGeneration
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
runCodexSandboxController,
|
|
3
|
-
|
|
3
|
+
verifyCodexSandboxControllerContextWithWarnings
|
|
4
4
|
} from '../agent-clients/adapters/codex-lifecycle/sandbox-controller.ts';
|
|
5
5
|
|
|
6
6
|
const USAGE = 'Usage: agent-infra-internal codex-sandbox-controller <run|verify-context> [options]\n';
|
|
@@ -58,7 +58,8 @@ async function codexSandboxController(args: string[] = []): Promise<void> {
|
|
|
58
58
|
const contextPath = parsed.values['--context']
|
|
59
59
|
?? process.env.AGENT_INFRA_CODEX_CONTROLLER_CONTEXT;
|
|
60
60
|
if (!contextPath) throw new Error('CODEX_SANDBOX_CONTROLLER_CONTEXT_MISSING');
|
|
61
|
-
const
|
|
61
|
+
const verified = verifyCodexSandboxControllerContextWithWarnings(contextPath);
|
|
62
|
+
const context = verified.context;
|
|
62
63
|
process.stdout.write(`${JSON.stringify({
|
|
63
64
|
status: 'ready',
|
|
64
65
|
changed: false,
|
|
@@ -69,8 +70,8 @@ async function codexSandboxController(args: string[] = []): Promise<void> {
|
|
|
69
70
|
expiresAt: context.expiresAt,
|
|
70
71
|
buildIdentity: context.buildIdentity,
|
|
71
72
|
hookDefinitionHash: context.hookDefinitionHash,
|
|
72
|
-
lifecycleProfilesHash: context.lifecycleProfilesHash
|
|
73
73
|
},
|
|
74
|
+
...(verified.warnings.length > 0 ? { warnings: verified.warnings } : {}),
|
|
74
75
|
error: null
|
|
75
76
|
})}\n`);
|
|
76
77
|
return;
|
|
@@ -9,7 +9,7 @@ import { serveSandboxControl } from '../sandbox/control/server.ts';
|
|
|
9
9
|
import { runSandboxControlExecutor } from '../sandbox/control/executor.ts';
|
|
10
10
|
import {
|
|
11
11
|
controllerProofFromContext,
|
|
12
|
-
|
|
12
|
+
verifyCodexSandboxControllerContextWithWarnings
|
|
13
13
|
} from '../agent-clients/adapters/codex-lifecycle/sandbox-controller.ts';
|
|
14
14
|
|
|
15
15
|
function isCanonicalCodexPrepare(args: readonly string[]): boolean {
|
|
@@ -118,7 +118,7 @@ async function sandboxControl(args: string[]): Promise<void> {
|
|
|
118
118
|
if (family === 'task-orchestration' && isCanonicalCodexPrepare(commandArgs)) {
|
|
119
119
|
const contextPath = process.env.AGENT_INFRA_CODEX_CONTROLLER_CONTEXT;
|
|
120
120
|
const proof = contextPath
|
|
121
|
-
? controllerProofFromContext(
|
|
121
|
+
? controllerProofFromContext(verifyCodexSandboxControllerContextWithWarnings(contextPath).context)
|
|
122
122
|
: null;
|
|
123
123
|
response = requestSandboxTaskControl({ family, args: commandArgs, controllerProof: proof });
|
|
124
124
|
} else {
|