@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.
Files changed (86) hide show
  1. package/dist/bin/internal-cli.js +5 -0
  2. package/dist/lib/agent-clients/adapters/codex-lifecycle/build-identity.js +17 -10
  3. package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-context.js +11 -31
  4. package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +15 -33
  5. package/dist/lib/agent-clients/adapters/codex-sandbox.js +72 -11
  6. package/dist/lib/agent-clients/adapters/index.js +3 -1
  7. package/dist/lib/agent-clients/adapters/traecli.js +66 -0
  8. package/dist/lib/agent-clients/tokens.js +2 -2
  9. package/dist/lib/agent-clients/types.js +2 -1
  10. package/dist/lib/defaults.json +5 -0
  11. package/dist/lib/internal/codex-lifecycle.js +2 -2
  12. package/dist/lib/internal/codex-sandbox-controller.js +4 -3
  13. package/dist/lib/internal/sandbox-control.js +2 -2
  14. package/dist/lib/{task/commands/validate.js → internal/task-validate.js} +75 -42
  15. package/dist/lib/run/tui.js +5 -2
  16. package/dist/lib/sandbox/control/controller-registration.js +4 -2
  17. package/dist/lib/sandbox/control/executor.js +4 -1
  18. package/dist/lib/task/codex-orchestration.js +31 -34
  19. package/dist/lib/task/delegation-receipts.js +8 -7
  20. package/dist/lib/task/finalization.js +72 -46
  21. package/dist/lib/task/index.js +0 -9
  22. package/dist/lib/task/orchestration.js +1 -1
  23. package/dist/lib/task/verification-engine.js +0 -3
  24. package/dist/lifecycle-build-manifest.json +3 -3
  25. package/lib/agent-clients/adapters/codex-lifecycle/build-identity.ts +25 -10
  26. package/lib/agent-clients/adapters/codex-lifecycle/controller-context.ts +16 -33
  27. package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +16 -35
  28. package/lib/agent-clients/adapters/codex-sandbox.ts +89 -14
  29. package/lib/agent-clients/adapters/index.ts +3 -1
  30. package/lib/agent-clients/adapters/traecli.ts +67 -0
  31. package/lib/agent-clients/tokens.ts +2 -2
  32. package/lib/agent-clients/types.ts +2 -1
  33. package/lib/defaults.json +5 -0
  34. package/lib/internal/codex-lifecycle.ts +2 -2
  35. package/lib/internal/codex-sandbox-controller.ts +4 -3
  36. package/lib/internal/sandbox-control.ts +2 -2
  37. package/lib/{task/commands/validate.ts → internal/task-validate.ts} +75 -40
  38. package/lib/run/tui.ts +5 -3
  39. package/lib/sandbox/control/controller-registration.ts +8 -3
  40. package/lib/sandbox/control/executor.ts +8 -2
  41. package/lib/task/codex-orchestration.ts +38 -44
  42. package/lib/task/delegation-receipts.ts +8 -6
  43. package/lib/task/finalization.ts +84 -42
  44. package/lib/task/index.ts +0 -8
  45. package/lib/task/orchestration.ts +2 -1
  46. package/lib/task/verification-engine.ts +0 -3
  47. package/package.json +1 -1
  48. package/templates/.agents/QUICKSTART.en.md +2 -3
  49. package/templates/.agents/QUICKSTART.zh-CN.md +2 -3
  50. package/templates/.agents/rules/README.en.md +1 -0
  51. package/templates/.agents/rules/README.zh-CN.md +1 -0
  52. package/templates/.agents/rules/compatibility-policy.en.md +39 -0
  53. package/templates/.agents/rules/compatibility-policy.zh-CN.md +39 -0
  54. package/templates/.agents/rules/lifecycle-orchestration.en.md +4 -4
  55. package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +4 -4
  56. package/templates/.agents/rules/pr-sync.en.md +1 -1
  57. package/templates/.agents/rules/pr-sync.github.en.md +2 -1
  58. package/templates/.agents/rules/pr-sync.github.zh-CN.md +2 -0
  59. package/templates/.agents/rules/pr-sync.zh-CN.md +1 -0
  60. package/templates/.agents/rules/review-method.en.md +1 -0
  61. package/templates/.agents/rules/review-method.zh-CN.md +1 -0
  62. package/templates/.agents/skills/analyze-task/SKILL.en.md +1 -0
  63. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +1 -0
  64. package/templates/.agents/skills/code-task/SKILL.en.md +1 -0
  65. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +2 -0
  66. package/templates/.agents/skills/create-pr/SKILL.en.md +12 -6
  67. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +12 -6
  68. package/templates/.agents/skills/create-pr/reference/change-report.en.md +41 -0
  69. package/templates/.agents/skills/create-pr/reference/change-report.zh-CN.md +41 -0
  70. package/templates/.agents/skills/plan-task/SKILL.en.md +2 -1
  71. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +2 -1
  72. package/templates/.agents/skills/review-code/reference/migration-risks.en.md +8 -6
  73. package/templates/.agents/skills/review-code/reference/migration-risks.zh-CN.md +8 -6
  74. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +3 -3
  75. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +3 -3
  76. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +1 -1
  77. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +1 -1
  78. package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +2 -2
  79. package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +2 -2
  80. package/templates/.agents/skills/run-task/SKILL.en.md +2 -2
  81. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +2 -2
  82. package/templates/.agents/skills/run-task/reference/host-validation.en.md +2 -2
  83. package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +2 -2
  84. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +24 -0
  85. package/templates/.trae/skills/update-agent-infra.en.md +8 -0
  86. 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 { computeLifecycleBuildIdentity, type LifecycleBuildIdentity } from './build-identity.ts';
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
- 'lifecycleProfilesHash', 'taskId', 'version'
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
- hashes: Readonly<{ hookDefinitionHash: string; lifecycleProfilesHash: string }>
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: hashes.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 verifyCodexSandboxControllerContext(
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
- if (JSON.stringify(computeLifecycleBuildIdentity(repoRoot)) !== JSON.stringify(context.buildIdentity)
166
- || crypto.createHash('sha256').update(fs.readFileSync(path.join(repoRoot, '.codex', 'hooks.json'))).digest('hex') !== context.hookDefinitionHash
167
- || digestProfiles(repoRoot) !== context.lifecycleProfilesHash) {
168
- throw new Error('CODEX_SANDBOX_CONTROLLER_BUNDLE_MISMATCH');
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 { computeLifecycleBuildIdentity } from './build-identity.ts';
17
+ import { LIFECYCLE_PROTOCOL_VERSION, type LifecycleIdentityWarning } from './build-identity.ts';
18
18
  import {
19
19
  contextFromControllerLease,
20
20
  controllerProofFromContext,
21
- verifyCodexSandboxControllerContext as verifyContextFile,
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 verifyCodexSandboxControllerContext(
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 context = verifyContextFile(contextPath, {
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 context;
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
- verifyCodexSandboxControllerContext,
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
- ensureCodexModelInheritance(entry.dir, create.hostHome, entry.tool.containerMount);
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
@@ -2,7 +2,8 @@ const AGENT_CLIENT_IDS = [
2
2
  'claude-code',
3
3
  'codex',
4
4
  'antigravity-cli',
5
- 'opencode'
5
+ 'opencode',
6
+ 'traecli'
6
7
  ] as const;
7
8
 
8
9
  type AgentClientId = (typeof AGENT_CLIENT_IDS)[number];
package/lib/defaults.json CHANGED
@@ -22,6 +22,11 @@
22
22
  "id": "opencode",
23
23
  "enabled": true,
24
24
  "installInSandbox": true
25
+ },
26
+ {
27
+ "id": "traecli",
28
+ "enabled": true,
29
+ "installInSandbox": true
25
30
  }
26
31
  ],
27
32
  "sandbox": {
@@ -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 { verifyCodexSandboxControllerContext } from '../agent-clients/adapters/codex-lifecycle/sandbox-controller.ts';
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 = verifyCodexSandboxControllerContext(contextPath, { repoRoot: process.cwd() });
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
- verifyCodexSandboxControllerContext
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 context = verifyCodexSandboxControllerContext(contextPath);
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
- verifyCodexSandboxControllerContext
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(verifyCodexSandboxControllerContext(contextPath))
121
+ ? controllerProofFromContext(verifyCodexSandboxControllerContextWithWarnings(contextPath).context)
122
122
  : null;
123
123
  response = requestSandboxTaskControl({ family, args: commandArgs, controllerProof: proof });
124
124
  } else {