@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
@@ -157,6 +157,11 @@ else
157
157
  taskVerify(process.argv.slice(3));
158
158
  break;
159
159
  }
160
+ case 'task-validate': {
161
+ const { taskValidate } = await import('../lib/internal/task-validate.js');
162
+ taskValidate(process.argv.slice(3));
163
+ break;
164
+ }
160
165
  default:
161
166
  process.stdout.write(`${JSON.stringify({
162
167
  status: 'failed',
@@ -145,25 +145,32 @@ function verifyLifecycleBuildIdentity(expected, actual) {
145
145
  return {
146
146
  ok: false,
147
147
  code: 'CODEX_LIFECYCLE_PROTOCOL_MISMATCH',
148
- message: 'Codex lifecycle protocol version does not match'
148
+ message: 'Codex lifecycle protocol version does not match',
149
+ warnings: []
149
150
  };
150
151
  }
152
+ const warnings = [];
151
153
  if (actual.packageVersion !== expected.packageVersion
152
154
  || actual.internalExecutableBuildHash !== expected.internalExecutableBuildHash) {
153
- return {
154
- ok: false,
155
+ warnings.push({
155
156
  code: 'CODEX_LIFECYCLE_BUILD_MISMATCH',
156
- message: 'Codex lifecycle executable build identity does not match'
157
- };
157
+ message: 'Codex lifecycle executable build identity differs; rebuild the sandbox if the runtime is stale',
158
+ action: 'rebuild-sandbox'
159
+ });
158
160
  }
159
161
  if (actual.lifecycleContractHash !== expected.lifecycleContractHash) {
160
- return {
161
- ok: false,
162
+ warnings.push({
162
163
  code: 'CODEX_LIFECYCLE_CONTRACT_MISMATCH',
163
- message: 'Codex lifecycle contract identity does not match'
164
- };
164
+ message: 'Codex lifecycle contract identity differs; rebuild the sandbox if the runtime is stale',
165
+ action: 'rebuild-sandbox'
166
+ });
165
167
  }
166
- return { ok: true, code: null, message: null };
168
+ return {
169
+ ok: true,
170
+ code: null,
171
+ message: null,
172
+ warnings: Object.freeze(warnings)
173
+ };
167
174
  }
168
175
  export { LIFECYCLE_PROTOCOL_VERSION, computeLifecycleBuildIdentity, isCanonicalLifecyclePackageVersion, isLifecycleProtocolVersion, readLifecycleManifestFiles, resolveLifecycleExecutableFiles, verifyLifecycleBuildIdentity };
169
176
  //# sourceMappingURL=build-identity.js.map
@@ -2,28 +2,11 @@ import crypto from 'node:crypto';
2
2
  import fs from 'node:fs';
3
3
  import path from 'node:path';
4
4
  import { getProcessIdentityState } from '../../../server/process-state.js';
5
- import { computeLifecycleBuildIdentity } from './build-identity.js';
5
+ import { computeLifecycleBuildIdentity, verifyLifecycleBuildIdentity } from './build-identity.js';
6
6
  const HEX_256 = /^[a-f0-9]{64}$/u;
7
7
  function exactKeys(value, keys) {
8
8
  return Object.keys(value).sort().join(',') === [...keys].sort().join(',');
9
9
  }
10
- function digestProfiles(repoRoot) {
11
- const files = [
12
- path.join(repoRoot, '.codex', 'agents', 'agent-infra-lifecycle-executor.toml'),
13
- path.join(repoRoot, '.codex', 'agents', 'agent-infra-lifecycle-reviewer.toml')
14
- ];
15
- const hash = crypto.createHash('sha256');
16
- for (const file of files.sort()) {
17
- const stat = fs.lstatSync(file);
18
- if (!stat.isFile() || stat.isSymbolicLink())
19
- throw new Error('CODEX_SANDBOX_CONTROLLER_BUNDLE_MISMATCH');
20
- hash.update(path.basename(file));
21
- hash.update('\0');
22
- hash.update(fs.readFileSync(file));
23
- hash.update('\0');
24
- }
25
- return hash.digest('hex');
26
- }
27
10
  function validProcess(value) {
28
11
  if (!value || typeof value !== 'object' || Array.isArray(value))
29
12
  return false;
@@ -53,7 +36,7 @@ function validateContext(value) {
53
36
  if (!exactKeys(context, [
54
37
  'buildIdentity', 'controlGeneration', 'controllerInstanceDigest', 'controllerLease',
55
38
  'controllerProcess', 'expiresAt', 'hookDefinitionHash', 'issuedAt',
56
- 'lifecycleProfilesHash', 'taskId', 'version'
39
+ 'taskId', 'version'
57
40
  ])
58
41
  || context.version !== 2
59
42
  || typeof context.taskId !== 'string' || context.taskId.length === 0
@@ -68,13 +51,12 @@ function validateContext(value) {
68
51
  || !Number.isSafeInteger(context.expiresAt)
69
52
  || context.expiresAt <= context.issuedAt
70
53
  || !validBuildIdentity(context.buildIdentity)
71
- || typeof context.hookDefinitionHash !== 'string' || !HEX_256.test(context.hookDefinitionHash)
72
- || typeof context.lifecycleProfilesHash !== 'string' || !HEX_256.test(context.lifecycleProfilesHash)) {
54
+ || typeof context.hookDefinitionHash !== 'string' || !HEX_256.test(context.hookDefinitionHash)) {
73
55
  throw new Error('CODEX_SANDBOX_CONTROLLER_CONTEXT_INVALID');
74
56
  }
75
57
  return context;
76
58
  }
77
- export function contextFromControllerLease(lease, hashes) {
59
+ export function contextFromControllerLease(lease, input) {
78
60
  return Object.freeze({
79
61
  version: 2,
80
62
  taskId: lease.taskId,
@@ -85,8 +67,7 @@ export function contextFromControllerLease(lease, hashes) {
85
67
  issuedAt: lease.issuedAt,
86
68
  expiresAt: lease.expiresAt,
87
69
  buildIdentity: lease.buildIdentity,
88
- hookDefinitionHash: hashes.hookDefinitionHash,
89
- lifecycleProfilesHash: hashes.lifecycleProfilesHash
70
+ hookDefinitionHash: input.hookDefinitionHash
90
71
  });
91
72
  }
92
73
  export function writeCodexSandboxControllerContext(contextPath, context) {
@@ -102,7 +83,7 @@ export function writeCodexSandboxControllerContext(contextPath, context) {
102
83
  fs.rmSync(temporary, { force: true });
103
84
  }
104
85
  }
105
- export function verifyCodexSandboxControllerContext(contextPath, options = {}) {
86
+ export function verifyCodexSandboxControllerContextWithWarnings(contextPath, options = {}) {
106
87
  let stat;
107
88
  try {
108
89
  stat = fs.lstatSync(contextPath);
@@ -129,12 +110,11 @@ export function verifyCodexSandboxControllerContext(contextPath, options = {}) {
129
110
  || state !== 'alive') {
130
111
  throw new Error('CODEX_SANDBOX_CONTROLLER_CONTEXT_INVALID');
131
112
  }
132
- if (JSON.stringify(computeLifecycleBuildIdentity(repoRoot)) !== JSON.stringify(context.buildIdentity)
133
- || crypto.createHash('sha256').update(fs.readFileSync(path.join(repoRoot, '.codex', 'hooks.json'))).digest('hex') !== context.hookDefinitionHash
134
- || digestProfiles(repoRoot) !== context.lifecycleProfilesHash) {
135
- throw new Error('CODEX_SANDBOX_CONTROLLER_BUNDLE_MISMATCH');
136
- }
137
- return Object.freeze(context);
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 });
138
118
  }
139
119
  export function controllerProofFromContext(context) {
140
120
  return Object.freeze({
@@ -8,22 +8,8 @@ import * as toml from 'smol-toml';
8
8
  import { requestCodexControllerClose, requestCodexControllerOpen, requestCodexControllerVerify, requestSandboxControl } from '../../../sandbox/control/client.js';
9
9
  import { readSandboxControlStatus } from '../../../sandbox/control/state.js';
10
10
  import { getProcessStartTime } from '../../../server/process-state.js';
11
- import { computeLifecycleBuildIdentity } from './build-identity.js';
12
- import { contextFromControllerLease, controllerProofFromContext, verifyCodexSandboxControllerContext as verifyContextFile, writeCodexSandboxControllerContext } from './controller-context.js';
13
- function digestFiles(files) {
14
- const hash = crypto.createHash('sha256');
15
- for (const file of [...files].sort()) {
16
- const stat = fs.lstatSync(file);
17
- if (!stat.isFile() || stat.isSymbolicLink()) {
18
- throw new Error('CODEX_SANDBOX_CONTROLLER_BUNDLE_MISMATCH');
19
- }
20
- hash.update(path.basename(file));
21
- hash.update('\0');
22
- hash.update(fs.readFileSync(file));
23
- hash.update('\0');
24
- }
25
- return hash.digest('hex');
26
- }
11
+ import { LIFECYCLE_PROTOCOL_VERSION } from './build-identity.js';
12
+ import { contextFromControllerLease, controllerProofFromContext, verifyCodexSandboxControllerContextWithWarnings as verifyContextFileWithWarnings, writeCodexSandboxControllerContext } from './controller-context.js';
27
13
  function copyRegular(source, destination, mode) {
28
14
  const stat = fs.lstatSync(source);
29
15
  if (!stat.isFile() || stat.isSymbolicLink()) {
@@ -108,14 +94,15 @@ function verifyControl(taskId, control) {
108
94
  if (payload.taskId !== taskId)
109
95
  throw new Error('CODEX_SANDBOX_CONTROLLER_TASK_BINDING_INVALID');
110
96
  }
111
- function verifyCodexSandboxControllerContext(contextPath, options = {}) {
97
+ function verifyCodexSandboxControllerContextWithWarnings(contextPath, options = {}) {
112
98
  const control = options.control ?? controlFromEnvironment();
113
- const context = verifyContextFile(contextPath, {
99
+ const fileVerification = verifyContextFileWithWarnings(contextPath, {
114
100
  repoRoot: options.repoRoot,
115
101
  now: options.now,
116
102
  generation: control.generation,
117
103
  probeProcess: options.probeProcess
118
104
  });
105
+ const context = fileVerification.context;
119
106
  const verified = (options.requestControllerVerify ?? requestCodexControllerVerify)({
120
107
  controllerProof: controllerProofFromContext(context),
121
108
  ...control,
@@ -126,7 +113,10 @@ function verifyCodexSandboxControllerContext(contextPath, options = {}) {
126
113
  || verified.binding.controllerInstanceDigest !== context.controllerInstanceDigest) {
127
114
  throw new Error('CODEX_SANDBOX_CONTROLLER_CONTEXT_INVALID');
128
115
  }
129
- return context;
116
+ return Object.freeze({
117
+ context,
118
+ warnings: fileVerification.warnings
119
+ });
130
120
  }
131
121
  function detectedCodexVersion() {
132
122
  const result = spawnSync('codex', ['--version'], { encoding: 'utf8' });
@@ -261,29 +251,21 @@ function prepareCodexSandboxController(input, options = {}) {
261
251
  copyRegular(hooks, path.join(home, 'hooks.json'), 0o600);
262
252
  copyRegular(executor, path.join(home, 'agents', path.basename(executor)), 0o600);
263
253
  copyRegular(reviewer, path.join(home, 'agents', path.basename(reviewer)), 0o600);
264
- if (crypto.createHash('sha256').update(fs.readFileSync(path.join(home, 'hooks.json'))).digest('hex')
265
- !== crypto.createHash('sha256').update(fs.readFileSync(hooks)).digest('hex')
266
- || digestFiles([
267
- path.join(home, 'agents', path.basename(executor)),
268
- path.join(home, 'agents', path.basename(reviewer))
269
- ]) !== digestFiles([executor, reviewer])) {
270
- throw new Error('CODEX_SANDBOX_CONTROLLER_BUNDLE_MISMATCH');
271
- }
272
- const buildIdentity = computeLifecycleBuildIdentity(repoRoot);
273
254
  const opened = (options.openController ?? requestCodexControllerOpen)({
274
255
  controllerProcess: { pid: process.pid, startTime: parentStartTime },
275
256
  ...control,
276
257
  timeoutMs: 30_000
277
258
  });
278
259
  const taskId = opened.lease.taskId;
260
+ if (opened.lease.buildIdentity.protocolVersion !== LIFECYCLE_PROTOCOL_VERSION) {
261
+ throw new Error('CODEX_LIFECYCLE_PROTOCOL_MISMATCH: Codex lifecycle protocol version does not match');
262
+ }
279
263
  context = contextFromControllerLease(opened.lease, {
280
- hookDefinitionHash: crypto.createHash('sha256').update(fs.readFileSync(hooks)).digest('hex'),
281
- lifecycleProfilesHash: digestFiles([executor, reviewer])
264
+ hookDefinitionHash: crypto.createHash('sha256').update(fs.readFileSync(hooks)).digest('hex')
282
265
  });
283
266
  if (opened.lease.controlGeneration !== control.generation
284
267
  || opened.lease.controllerProcess.pid !== process.pid
285
- || opened.lease.controllerProcess.startTime !== parentStartTime
286
- || JSON.stringify(opened.lease.buildIdentity) !== JSON.stringify(buildIdentity)) {
268
+ || opened.lease.controllerProcess.startTime !== parentStartTime) {
287
269
  throw new Error('SANDBOX_CONTROL_RESULT_INVALID');
288
270
  }
289
271
  (options.verifyTaskBinding ?? verifyControl)(taskId, control);
@@ -383,5 +365,5 @@ async function runCodexSandboxController(input, options = {}) {
383
365
  prepared.cleanup();
384
366
  }
385
367
  }
386
- export { prepareCodexSandboxController, runCodexSandboxController, verifyCodexSandboxControllerContext, controllerProofFromContext };
368
+ export { prepareCodexSandboxController, runCodexSandboxController, verifyCodexSandboxControllerContextWithWarnings, controllerProofFromContext };
387
369
  //# sourceMappingURL=sandbox-controller.js.map
@@ -30,6 +30,8 @@ function resolveHostCatalogPath(value, hostHomeDir) {
30
30
  }
31
31
  }
32
32
  const CODEX_DISABLED_FEATURE_FLAGS = ['apps', 'enable_mcp_apps'];
33
+ const SANDBOX_CODEX_HOOKS_PATH = '/workspace/.codex/hooks.json';
34
+ const CODEX_HOOK_TRUST_HASH = /^sha256:[a-f0-9]{64}$/;
33
35
  function removeCodexMcpServers(sandboxParsed) {
34
36
  if (!Object.hasOwn(sandboxParsed, 'mcp_servers')) {
35
37
  return false;
@@ -61,19 +63,58 @@ function inheritDisabledCodexFeatureFlags(sandboxParsed, hostParsed) {
61
63
  }
62
64
  return changed;
63
65
  }
64
- function ensureCodexModelInheritance(toolDir, hostHomeDir, containerCodexDir = '/home/devuser/.codex') {
65
- if (!hostHomeDir) {
66
- return;
66
+ function inheritCodexHookTrustState(sandboxParsed, hostParsed, hostProjectDir) {
67
+ if (!hostProjectDir) {
68
+ return false;
67
69
  }
68
- const hostConfigPath = path.join(hostHomeDir, '.codex', 'config.toml');
69
- if (!fs.existsSync(hostConfigPath)) {
70
- return;
70
+ if (Object.hasOwn(sandboxParsed, 'hooks') && !isJsonObjectRecord(sandboxParsed.hooks)) {
71
+ return false;
71
72
  }
72
- let hostParsed;
73
- try {
74
- hostParsed = toml.parse(fs.readFileSync(hostConfigPath, 'utf8'));
73
+ const sandboxHooks = sandboxParsed.hooks ?? {};
74
+ if (Object.hasOwn(sandboxHooks, 'state') && !isJsonObjectRecord(sandboxHooks.state)) {
75
+ return false;
75
76
  }
76
- catch {
77
+ const sandboxState = sandboxHooks.state ?? {};
78
+ const sandboxPrefix = `${SANDBOX_CODEX_HOOKS_PATH}:`;
79
+ const nextState = Object.fromEntries(Object.entries(sandboxState).filter(([key]) => !key.startsWith(sandboxPrefix)));
80
+ const hostState = isJsonObjectRecord(hostParsed.hooks)
81
+ && isJsonObjectRecord(hostParsed.hooks.state)
82
+ ? hostParsed.hooks.state
83
+ : {};
84
+ const hostPrefix = `${path.join(hostProjectDir, '.codex', 'hooks.json')}:`;
85
+ for (const [key, value] of Object.entries(hostState)) {
86
+ if (!key.startsWith(hostPrefix) || !isJsonObjectRecord(value)) {
87
+ continue;
88
+ }
89
+ const trustedHash = value.trusted_hash;
90
+ if (typeof trustedHash !== 'string' || !CODEX_HOOK_TRUST_HASH.test(trustedHash)) {
91
+ continue;
92
+ }
93
+ nextState[`${sandboxPrefix}${key.slice(hostPrefix.length)}`] = {
94
+ trusted_hash: trustedHash,
95
+ ...(typeof value.enabled === 'boolean' ? { enabled: value.enabled } : {})
96
+ };
97
+ }
98
+ if (JSON.stringify(sandboxState) === JSON.stringify(nextState)) {
99
+ return false;
100
+ }
101
+ if (Object.keys(nextState).length > 0) {
102
+ sandboxHooks.state = nextState;
103
+ sandboxParsed.hooks = sandboxHooks;
104
+ }
105
+ else {
106
+ delete sandboxHooks.state;
107
+ if (Object.keys(sandboxHooks).length > 0) {
108
+ sandboxParsed.hooks = sandboxHooks;
109
+ }
110
+ else {
111
+ delete sandboxParsed.hooks;
112
+ }
113
+ }
114
+ return true;
115
+ }
116
+ function ensureCodexModelInheritance(toolDir, hostHomeDir, containerCodexDir = '/home/devuser/.codex', hostProjectDir) {
117
+ if (!hostHomeDir) {
77
118
  return;
78
119
  }
79
120
  const sandboxConfigPath = path.join(toolDir, 'config.toml');
@@ -87,8 +128,25 @@ function ensureCodexModelInheritance(toolDir, hostHomeDir, containerCodexDir = '
87
128
  return;
88
129
  }
89
130
  }
131
+ const hostConfigPath = path.join(hostHomeDir, '.codex', 'config.toml');
132
+ let hostParsed = null;
133
+ if (fs.existsSync(hostConfigPath)) {
134
+ try {
135
+ hostParsed = toml.parse(fs.readFileSync(hostConfigPath, 'utf8'));
136
+ }
137
+ catch {
138
+ // An unavailable host trust source must not leave previously projected trust active.
139
+ }
140
+ }
141
+ if (!hostParsed) {
142
+ if (inheritCodexHookTrustState(sandboxParsed, {}, hostProjectDir)) {
143
+ fs.writeFileSync(sandboxConfigPath, `${toml.stringify(sandboxParsed)}\n`, 'utf8');
144
+ }
145
+ return;
146
+ }
90
147
  let changed = removeCodexMcpServers(sandboxParsed);
91
148
  changed = inheritDisabledCodexFeatureFlags(sandboxParsed, hostParsed) || changed;
149
+ changed = inheritCodexHookTrustState(sandboxParsed, hostParsed, hostProjectDir) || changed;
92
150
  const inheritSpecs = [
93
151
  ['model', 'string'],
94
152
  ['model_reasoning_effort', 'string'],
@@ -151,7 +209,10 @@ const codexBeforeContainerCreateHook = {
151
209
  message: 'Codex sandbox hook requires its resolved tool state.'
152
210
  };
153
211
  }
154
- ensureCodexModelInheritance(entry.dir, create.hostHome, entry.tool.containerMount);
212
+ const hostProjectDir = typeof context.config?.repoRoot === 'string'
213
+ ? context.config.repoRoot
214
+ : undefined;
215
+ ensureCodexModelInheritance(entry.dir, create.hostHome, entry.tool.containerMount, hostProjectDir);
155
216
  ensureCodexWorkspaceTrust(entry.dir);
156
217
  return { status: 'ready' };
157
218
  }
@@ -3,11 +3,13 @@ import { claudeCodeAdapter } from './claude-code.js';
3
3
  import { codexAdapter } from './codex.js';
4
4
  import { antigravityCliAdapter } from './antigravity-cli.js';
5
5
  import { opencodeAdapter } from './opencode.js';
6
+ import { traecliAdapter } from './traecli.js';
6
7
  const adaptersById = new Map([
7
8
  claudeCodeAdapter,
8
9
  codexAdapter,
9
10
  antigravityCliAdapter,
10
- opencodeAdapter
11
+ opencodeAdapter,
12
+ traecliAdapter
11
13
  ].map((adapter) => [adapter.id, adapter]));
12
14
  const BUILTIN_AGENT_CLIENT_ADAPTERS = Object.freeze(AGENT_CLIENT_IDS.map((id) => {
13
15
  const adapter = adaptersById.get(id);
@@ -0,0 +1,66 @@
1
+ import { defineAgentClientAdapter } from '../adapter.js';
2
+ import { hostJoin } from '../../sandbox/engines/wsl2-paths.js';
3
+ const traecliAdapter = defineAgentClientAdapter({
4
+ id: 'traecli',
5
+ displayName: 'TraeCode CLI',
6
+ invocation: '/${skillName}',
7
+ capabilities: {
8
+ instructions: { level: 'compatible' },
9
+ skills: { level: 'compatible' },
10
+ commands: { level: 'integrated' },
11
+ hooks: { level: 'compatible' },
12
+ subagents: { level: 'experimental' },
13
+ orchestration: { level: 'experimental' },
14
+ sandbox: { level: 'integrated' },
15
+ verification: { level: 'compatible' }
16
+ },
17
+ modelSelection: {
18
+ kind: 'interactive-only',
19
+ command: '/model',
20
+ guidance: 'Use the host model picker for the complete model and reasoning-effort catalog.'
21
+ },
22
+ delegationEvidence: {
23
+ actualModel: 'unavailable',
24
+ actualReasoningEffort: 'unavailable'
25
+ },
26
+ project: {
27
+ ownedPathPrefixes: ['.trae/'],
28
+ managed: ['.trae/skills/'],
29
+ merged: [],
30
+ ejected: [],
31
+ seedCommands: [{
32
+ templates: {
33
+ en: '.trae/skills/update-agent-infra.en.md',
34
+ 'zh-CN': '.trae/skills/update-agent-infra.zh-CN.md'
35
+ },
36
+ target: '.trae/skills/update-agent-infra.md'
37
+ }],
38
+ customCommand: {
39
+ target: '.trae/skills/${skillName}.md',
40
+ frontmatter: {},
41
+ includeUsage: true,
42
+ inheritDisableModelInvocation: true
43
+ }
44
+ },
45
+ sandbox: {
46
+ createTool: ({ home }) => ({
47
+ id: 'traecli',
48
+ name: 'TraeCode CLI',
49
+ install: {
50
+ type: 'shell',
51
+ cmd: 'curl -fsSL https://trae.cn/trae-cli/install_v2.sh | bash'
52
+ },
53
+ sandboxBase: hostJoin(home, '.agent-infra', 'sandboxes', 'traecli'),
54
+ containerMount: '/home/devuser/.trae',
55
+ versionCmd: 'traecli --version',
56
+ setupHint: 'Run traecli login inside the container to authenticate.'
57
+ }),
58
+ aliases: [
59
+ { name: 'traecli-yolo', command: 'traecli --dangerously-bypass-approvals-and-sandbox; tput ed' },
60
+ { name: 'ty', command: 'traecli --dangerously-bypass-approvals-and-sandbox; tput ed' }
61
+ ],
62
+ hooks: []
63
+ }
64
+ });
65
+ export { traecliAdapter };
66
+ //# sourceMappingURL=traecli.js.map
@@ -2,7 +2,7 @@
2
2
  // activity log. Migrated from lib/task/commands/log.ts so that the write-side
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
- export const KNOWN_AI_AGENTS = new Set(['claude', 'codex', 'antigravity', 'opencode', 'cursor']);
5
+ export const KNOWN_AI_AGENTS = new Set(['claude', 'codex', 'antigravity', 'opencode', 'cursor', 'traecli']);
6
6
  // Long name -> short name mapping (HD-4, decision A): `claude-code` reuses the
7
7
  // existing orchestration precedent, and `antigravity-cli` is the active
8
8
  // client. `gemini-cli` is intentionally not writable.
@@ -10,7 +10,7 @@ export const AGENT_LONG_NAMES = {
10
10
  'claude-code': 'claude',
11
11
  'antigravity-cli': 'antigravity'
12
12
  };
13
- export const AGENT_USAGE_HINT = "agent must be a short token (claude/codex/antigravity/opencode/cursor), " +
13
+ export const AGENT_USAGE_HINT = "agent must be a short token (claude/codex/antigravity/opencode/cursor/traecli), " +
14
14
  "a long name (claude-code -> claude, antigravity-cli -> antigravity), or 'human'";
15
15
  // Read-side historical compatibility for activity-log rendering: `gemini` and
16
16
  // `gemini-cli` stay recognizable when displaying old logs, but are never
@@ -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
  ];
7
8
  const AGENT_CLIENT_CAPABILITY_IDS = [
8
9
  'instructions',
@@ -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": {
@@ -6,7 +6,7 @@ import { preflightCodexLifecycleEvidence, resolveCodexTerminal, resolveCodexThre
6
6
  import { createCodexLifecycleStore } from '../agent-clients/adapters/codex-lifecycle/store.js';
7
7
  import { createCodexCapabilityStore } from '../agent-clients/adapters/codex-lifecycle/capability-store.js';
8
8
  import { computeLifecycleBuildIdentity } from '../agent-clients/adapters/codex-lifecycle/build-identity.js';
9
- import { verifyCodexSandboxControllerContext } from '../agent-clients/adapters/codex-lifecycle/sandbox-controller.js';
9
+ import { verifyCodexSandboxControllerContextWithWarnings } from '../agent-clients/adapters/codex-lifecycle/sandbox-controller.js';
10
10
  import { resolveTaskRef } from '../task/resolve-ref.js';
11
11
  import { hasSealableOrchestrationDelegation } from '../task/orchestration.js';
12
12
  import { activateCodexOrchestrationDelegation, activateCodexSpawnDelegation, reconcileCodexOrchestrationDelegation, sealCodexOrchestrationDelegation, sealCodexParentDelegation } from '../task/codex-orchestration.js';
@@ -81,7 +81,7 @@ function controllerBinding(taskId) {
81
81
  const contextPath = process.env.AGENT_INFRA_CODEX_CONTROLLER_CONTEXT;
82
82
  if (!contextPath)
83
83
  return undefined;
84
- const context = verifyCodexSandboxControllerContext(contextPath, { repoRoot: process.cwd() });
84
+ const context = verifyCodexSandboxControllerContextWithWarnings(contextPath, { repoRoot: process.cwd() }).context;
85
85
  return {
86
86
  instanceDigest: context.controllerInstanceDigest,
87
87
  controlGeneration: context.controlGeneration
@@ -1,4 +1,4 @@
1
- import { runCodexSandboxController, verifyCodexSandboxControllerContext } from '../agent-clients/adapters/codex-lifecycle/sandbox-controller.js';
1
+ import { runCodexSandboxController, verifyCodexSandboxControllerContextWithWarnings } from '../agent-clients/adapters/codex-lifecycle/sandbox-controller.js';
2
2
  const USAGE = 'Usage: agent-infra-internal codex-sandbox-controller <run|verify-context> [options]\n';
3
3
  function fail(code, message, exitCode = 1) {
4
4
  process.stdout.write(`${JSON.stringify({
@@ -53,7 +53,8 @@ async function codexSandboxController(args = []) {
53
53
  ?? process.env.AGENT_INFRA_CODEX_CONTROLLER_CONTEXT;
54
54
  if (!contextPath)
55
55
  throw new Error('CODEX_SANDBOX_CONTROLLER_CONTEXT_MISSING');
56
- const context = verifyCodexSandboxControllerContext(contextPath);
56
+ const verified = verifyCodexSandboxControllerContextWithWarnings(contextPath);
57
+ const context = verified.context;
57
58
  process.stdout.write(`${JSON.stringify({
58
59
  status: 'ready',
59
60
  changed: false,
@@ -64,8 +65,8 @@ async function codexSandboxController(args = []) {
64
65
  expiresAt: context.expiresAt,
65
66
  buildIdentity: context.buildIdentity,
66
67
  hookDefinitionHash: context.hookDefinitionHash,
67
- lifecycleProfilesHash: context.lifecycleProfilesHash
68
68
  },
69
+ ...(verified.warnings.length > 0 ? { warnings: verified.warnings } : {}),
69
70
  error: null
70
71
  })}\n`);
71
72
  return;
@@ -2,7 +2,7 @@ import { requestSandboxControl, requestSandboxTaskFinalization, requestSandboxTa
2
2
  import { normalizeAgentToken, AGENT_USAGE_HINT } from '../agent-clients/tokens.js';
3
3
  import { serveSandboxControl } from '../sandbox/control/server.js';
4
4
  import { runSandboxControlExecutor } from '../sandbox/control/executor.js';
5
- import { controllerProofFromContext, verifyCodexSandboxControllerContext } from '../agent-clients/adapters/codex-lifecycle/sandbox-controller.js';
5
+ import { controllerProofFromContext, verifyCodexSandboxControllerContextWithWarnings } from '../agent-clients/adapters/codex-lifecycle/sandbox-controller.js';
6
6
  function isCanonicalCodexPrepare(args) {
7
7
  if (args[1] !== 'prepare')
8
8
  return false;
@@ -106,7 +106,7 @@ async function sandboxControl(args) {
106
106
  if (family === 'task-orchestration' && isCanonicalCodexPrepare(commandArgs)) {
107
107
  const contextPath = process.env.AGENT_INFRA_CODEX_CONTROLLER_CONTEXT;
108
108
  const proof = contextPath
109
- ? controllerProofFromContext(verifyCodexSandboxControllerContext(contextPath))
109
+ ? controllerProofFromContext(verifyCodexSandboxControllerContextWithWarnings(contextPath).context)
110
110
  : null;
111
111
  response = requestSandboxTaskControl({ family, args: commandArgs, controllerProof: proof });
112
112
  }