@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
@@ -3,19 +3,19 @@ import fs from 'node:fs';
3
3
  import os from 'node:os';
4
4
  import path from 'node:path';
5
5
  import { randomUUID } from 'node:crypto';
6
- import { loadConfig } from '../../sandbox/config.js';
7
- import { detectEngine } from '../../sandbox/engine.js';
8
- import { runEngine } from '../../sandbox/shell.js';
9
- import { sandboxBranchLabel, sandboxLabel } from '../../sandbox/constants.js';
10
- import { fetchSandboxRows, selectSandboxContainer } from '../../sandbox/commands/list-running.js';
11
- import { containerNameCandidates } from '../../sandbox/constants.js';
12
- import { resolveSandboxTarget } from '../../sandbox/workspace-identity.js';
13
- import { sandboxControlPaths } from '../../sandbox/workspace-view.js';
14
- import { appendSandboxControlAudit, readActiveLease, readSandboxControlStatus } from '../../sandbox/control/state.js';
15
- import { SANDBOX_CONTROL_FUTURE_SKEW_MS, SANDBOX_CONTROL_STATUS_STALE_MS } from '../../sandbox/control/protocol.js';
16
- import { getProcessStartTime } from '../../server/process-state.js';
17
- import { assertGitWorktreeBinding } from '../../git/worktree-identity.js';
18
- const USAGE = `Usage: ai task validate <branch | TASK-id | N> [--scope snapshot|inplace] [--timeout <ms>] [--format text|json] -- <command> [args...]`;
6
+ import { loadConfig } from '../sandbox/config.js';
7
+ import { detectEngine } from '../sandbox/engine.js';
8
+ import { runEngine } from '../sandbox/shell.js';
9
+ import { sandboxBranchLabel, sandboxLabel } from '../sandbox/constants.js';
10
+ import { fetchSandboxRows, selectSandboxContainer } from '../sandbox/commands/list-running.js';
11
+ import { containerNameCandidates } from '../sandbox/constants.js';
12
+ import { resolveSandboxTarget } from '../sandbox/workspace-identity.js';
13
+ import { sandboxControlPaths } from '../sandbox/workspace-view.js';
14
+ import { appendSandboxControlAudit, readActiveLease, readSandboxControlStatus } from '../sandbox/control/state.js';
15
+ import { SANDBOX_CONTROL_FUTURE_SKEW_MS, SANDBOX_CONTROL_STATUS_STALE_MS } from '../sandbox/control/protocol.js';
16
+ import { getProcessStartTime } from '../server/process-state.js';
17
+ import { assertGitWorktreeBinding } from '../git/worktree-identity.js';
18
+ const USAGE = `Usage: agent-infra-internal task-validate <branch | TASK-id | N> [--scope snapshot|inplace] [--timeout <ms>] [--format text|json] -- <command> [args...]`;
19
19
  const MAX_TIMEOUT_MS = 60 * 60 * 1000;
20
20
  const RECOVERY_GRACE_MS = 30_000;
21
21
  export function parseValidateArgs(args) {
@@ -254,36 +254,69 @@ function inplaceValidation(config, target, options) {
254
254
  }
255
255
  }
256
256
  }
257
- export async function validate(args) {
258
- const options = parseValidateArgs(args);
257
+ function errorCode(message) {
258
+ const match = message.match(/^([A-Z][A-Z0-9_]*)/);
259
+ return match && match[1].length >= 3 ? match[1] : 'TASK_VALIDATE_FAILED';
260
+ }
261
+ function fail(format, code, message) {
262
+ if (format === 'json') {
263
+ process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code, message } })}\n`);
264
+ }
265
+ else {
266
+ process.stderr.write(`${message}\n`);
267
+ }
268
+ process.exitCode = 1;
269
+ }
270
+ function sniffFormat(args) {
271
+ const index = args.indexOf('--format');
272
+ return index >= 0 && args[index + 1] === 'json' ? 'json' : 'text';
273
+ }
274
+ function taskValidate(args) {
275
+ let options;
276
+ try {
277
+ options = parseValidateArgs(args);
278
+ }
279
+ catch (error) {
280
+ fail(sniffFormat(args), 'TASK_VALIDATE_ARGS_INVALID', error instanceof Error ? error.message : String(error));
281
+ return;
282
+ }
259
283
  if (options.help) {
260
284
  process.stdout.write(`${USAGE}\n`);
261
- return 0;
285
+ return;
286
+ }
287
+ try {
288
+ const config = loadConfig();
289
+ const target = resolveSandboxTarget(options.target, config.repoRoot);
290
+ const commit = git(config.repoRoot, ['rev-parse', target.branch]);
291
+ const startedAt = new Date().toISOString();
292
+ const result = options.scope === 'snapshot'
293
+ ? snapshotValidation(config.repoRoot, commit, options)
294
+ : inplaceValidation(config, target, options);
295
+ const evidence = {
296
+ version: 1,
297
+ taskId: target.workspace.mode === 'task-bound' ? target.workspace.taskId : null,
298
+ branch: target.branch,
299
+ scope: options.scope,
300
+ commit,
301
+ command: path.basename(options.command[0]),
302
+ startedAt,
303
+ completedAt: new Date().toISOString(),
304
+ exitCode: result.exitCode,
305
+ signal: result.signal,
306
+ cleanup: result.cleanup()
307
+ };
308
+ if (options.format === 'json') {
309
+ process.stdout.write(`${JSON.stringify({ status: 'applied', changed: false, evidence, error: null })}\n`);
310
+ }
311
+ else {
312
+ process.stdout.write(`Validation ${result.exitCode === 0 ? 'passed' : 'failed'} (${options.scope}, ${evidence.command}, cleanup=${evidence.cleanup}).\n`);
313
+ }
314
+ process.exitCode = result.exitCode;
315
+ }
316
+ catch (error) {
317
+ const message = error instanceof Error ? error.message : String(error);
318
+ fail(options.format, errorCode(message), message);
262
319
  }
263
- const config = loadConfig();
264
- const target = resolveSandboxTarget(options.target, config.repoRoot);
265
- const commit = git(config.repoRoot, ['rev-parse', target.branch]);
266
- const startedAt = new Date().toISOString();
267
- const result = options.scope === 'snapshot'
268
- ? snapshotValidation(config.repoRoot, commit, options)
269
- : inplaceValidation(config, target, options);
270
- const evidence = {
271
- version: 1,
272
- taskId: target.workspace.mode === 'task-bound' ? target.workspace.taskId : null,
273
- branch: target.branch,
274
- scope: options.scope,
275
- commit,
276
- command: path.basename(options.command[0]),
277
- startedAt,
278
- completedAt: new Date().toISOString(),
279
- exitCode: result.exitCode,
280
- signal: result.signal,
281
- cleanup: result.cleanup()
282
- };
283
- if (options.format === 'json')
284
- process.stdout.write(`${JSON.stringify(evidence)}\n`);
285
- else
286
- process.stdout.write(`Validation ${result.exitCode === 0 ? 'passed' : 'failed'} (${options.scope}, ${evidence.command}, cleanup=${evidence.cleanup}).\n`);
287
- return result.exitCode;
288
320
  }
289
- //# sourceMappingURL=validate.js.map
321
+ export { taskValidate };
322
+ //# sourceMappingURL=task-validate.js.map
@@ -1,11 +1,12 @@
1
1
  import { renderAgentClientInvocation } from '../agent-clients/invocation.js';
2
2
  import { getAgentClientAdapter } from '../agent-clients/registry.js';
3
- const TUI_NAMES = new Set(['claude', 'codex', 'antigravity', 'opencode']);
3
+ const TUI_NAMES = new Set(['claude', 'codex', 'antigravity', 'opencode', 'traecli']);
4
4
  const TUI_AGENT_CLIENT_IDS = Object.freeze({
5
5
  claude: 'claude-code',
6
6
  codex: 'codex',
7
7
  antigravity: 'antigravity-cli',
8
- opencode: 'opencode'
8
+ opencode: 'opencode',
9
+ traecli: 'traecli'
9
10
  });
10
11
  function isTuiName(value) {
11
12
  return typeof value === 'string' && TUI_NAMES.has(value);
@@ -33,6 +34,8 @@ export function buildTuiCommand(tui, prompt) {
33
34
  return ['agy', ['--dangerously-skip-permissions', '--print', prompt]];
34
35
  if (tui === 'opencode')
35
36
  return ['opencode', ['run', '--dangerously-skip-permissions', prompt]];
37
+ if (tui === 'traecli')
38
+ return ['traecli', ['exec', '--dangerously-bypass-approvals-and-sandbox', prompt]];
36
39
  return ['codex', ['exec', '--dangerously-bypass-approvals-and-sandbox', prompt]];
37
40
  }
38
41
  //# sourceMappingURL=tui.js.map
@@ -126,10 +126,12 @@ function defaultProbe(manifest, identity) {
126
126
  function assertRegistrationBinding(registration, manifest, buildIdentity) {
127
127
  if (registration.taskId !== manifest.taskId
128
128
  || registration.controlGeneration !== manifest.generation
129
- || registration.containerId !== manifest.containerIdentity.id
130
- || JSON.stringify(registration.buildIdentity) !== JSON.stringify(buildIdentity)) {
129
+ || registration.containerId !== manifest.containerIdentity.id) {
131
130
  fail('CODEX_SANDBOX_CONTROLLER_REGISTRATION_INVALID', 'controller registration binding is invalid');
132
131
  }
132
+ if (registration.buildIdentity.protocolVersion !== buildIdentity.protocolVersion) {
133
+ fail('CODEX_LIFECYCLE_PROTOCOL_MISMATCH', 'Codex lifecycle protocol version does not match');
134
+ }
133
135
  }
134
136
  function atomicWrite(file, value) {
135
137
  const temporary = path.join(path.dirname(file), `.${path.basename(file)}.${process.pid}.${crypto.randomUUID()}.tmp`);
@@ -117,12 +117,15 @@ function controllerFailure(error) {
117
117
  ? error.code
118
118
  : /^([A-Z][A-Z0-9_]+)/u.exec(error instanceof Error ? error.message : String(error))?.[1]
119
119
  ?? 'CODEX_SANDBOX_CONTROLLER_FAILED';
120
+ const message = error instanceof CodexControllerRegistrationError
121
+ ? error.message
122
+ : `${code}: controller operation failed; inspect the sandbox controller and rebuild the sandbox if needed`;
120
123
  const payload = {
121
124
  version: 1,
122
125
  status: 'failed',
123
126
  changed: false,
124
127
  lease: null,
125
- error: { code, message: `${code}: controller request failed`, retryable: false }
128
+ error: { code, message, retryable: false }
126
129
  };
127
130
  return { exitCode: 1, stdout: `${JSON.stringify(payload)}\n`, stderr: '' };
128
131
  }
@@ -1,9 +1,9 @@
1
1
  import { preflightCodexLifecycleEvidence, resolveCodexSpawnedChild, resolveCodexTerminal, resolveCodexThread } from '../agent-clients/adapters/codex-lifecycle/app-server.js';
2
2
  import { createCodexLifecycleStore } from '../agent-clients/adapters/codex-lifecycle/store.js';
3
3
  import { createCodexCapabilityStore, isCodexCapabilityProvenanceDetail } from '../agent-clients/adapters/codex-lifecycle/capability-store.js';
4
- import { computeLifecycleBuildIdentity } from '../agent-clients/adapters/codex-lifecycle/build-identity.js';
5
- import { verifyCodexSandboxControllerContext } from '../agent-clients/adapters/codex-lifecycle/sandbox-controller.js';
6
- import { verifyCodexSandboxControllerContext as verifyControllerContextFile } from '../agent-clients/adapters/codex-lifecycle/controller-context.js';
4
+ import { computeLifecycleBuildIdentity, verifyLifecycleBuildIdentity } from '../agent-clients/adapters/codex-lifecycle/build-identity.js';
5
+ import { verifyCodexSandboxControllerContextWithWarnings as verifySandboxControllerWithWarnings } from '../agent-clients/adapters/codex-lifecycle/sandbox-controller.js';
6
+ import { verifyCodexSandboxControllerContextWithWarnings as verifyControllerContextFileWithWarnings } from '../agent-clients/adapters/codex-lifecycle/controller-context.js';
7
7
  import { resolveTaskRef } from './resolve-ref.js';
8
8
  import { activateMatchingOrchestrationDelegation, hasActivatableOrchestrationDelegation, hasSealableOrchestrationDelegation, OrchestrationStateError, pauseMatchingOrchestrationDelegation, prepareOrchestrationDelegation, reconcileMatchingOrchestrationDelegation, sealMatchingOrchestrationDelegationWithHostEvidence } from './orchestration.js';
9
9
  function coreOptions(options) {
@@ -42,29 +42,6 @@ function requiredStore(options) {
42
42
  throw new Error('Codex lifecycle store is required for orchestration bridge events');
43
43
  return options.store;
44
44
  }
45
- function controllerContext(repoRoot, brokerController) {
46
- const contextPath = process.env.AGENT_INFRA_CODEX_CONTROLLER_CONTEXT;
47
- if (!contextPath)
48
- return null;
49
- if (brokerController) {
50
- return verifyControllerContextFile(contextPath, {
51
- repoRoot,
52
- generation: process.env.AGENT_INFRA_CONTROL_GENERATION ?? brokerController.controlGeneration
53
- });
54
- }
55
- return verifyCodexSandboxControllerContext(contextPath, { repoRoot });
56
- }
57
- function brokerBindingConflictsWithContext(repoRoot, brokerController) {
58
- const contextPath = process.env.AGENT_INFRA_CODEX_CONTROLLER_CONTEXT;
59
- if (!contextPath || !brokerController)
60
- return false;
61
- const context = verifyControllerContextFile(contextPath, {
62
- repoRoot,
63
- generation: process.env.AGENT_INFRA_CONTROL_GENERATION ?? brokerController.controlGeneration
64
- });
65
- return context.controllerInstanceDigest !== brokerController.instanceDigest
66
- || context.controlGeneration !== brokerController.controlGeneration;
67
- }
68
45
  function brokerControllerBinding() {
69
46
  const raw = process.env.AGENT_INFRA_CONTROL_CONTROLLER_BINDING;
70
47
  if (!raw)
@@ -101,10 +78,17 @@ async function prepareCodexOrchestrationDelegation(taskRef, input, options = {})
101
78
  }
102
79
  const buildIdentity = options.buildIdentity ?? computeLifecycleBuildIdentity(repoRoot);
103
80
  const brokerController = brokerControllerBinding();
104
- if (brokerBindingConflictsWithContext(repoRoot, brokerController)) {
105
- return bridgeFailure('CODEX_SANDBOX_CONTROLLER_BINDING_MISMATCH', 'broker and local controller bindings do not match');
106
- }
107
- const localController = controllerContext(repoRoot, brokerController);
81
+ const contextPath = process.env.AGENT_INFRA_CODEX_CONTROLLER_CONTEXT;
82
+ const contextVerification = contextPath
83
+ ? (brokerController
84
+ ? verifyControllerContextFileWithWarnings(contextPath, {
85
+ repoRoot,
86
+ generation: process.env.AGENT_INFRA_CONTROL_GENERATION ?? brokerController.controlGeneration
87
+ })
88
+ : verifySandboxControllerWithWarnings(contextPath, { repoRoot }))
89
+ : null;
90
+ const identityWarnings = [...(contextVerification?.warnings ?? [])];
91
+ const localController = contextVerification?.context ?? null;
108
92
  if (localController && brokerController
109
93
  && (localController.controllerInstanceDigest !== brokerController.instanceDigest
110
94
  || localController.controlGeneration !== brokerController.controlGeneration)) {
@@ -116,6 +100,11 @@ async function prepareCodexOrchestrationDelegation(taskRef, input, options = {})
116
100
  } : null);
117
101
  const capabilityStore = options.capabilityStore ?? createCodexCapabilityStore();
118
102
  const attested = capabilityStore.inspect(input.capabilityToken);
103
+ const capabilityIdentity = verifyLifecycleBuildIdentity(attested.buildIdentity, buildIdentity);
104
+ if (!capabilityIdentity.ok) {
105
+ return bridgeFailure(capabilityIdentity.code, capabilityIdentity.message);
106
+ }
107
+ identityWarnings.push(...capabilityIdentity.warnings);
119
108
  const lifecycleProvenance = {
120
109
  ...attested.buildIdentity,
121
110
  hookDefinitionHash: preflight.hookDefinitionHash,
@@ -135,7 +124,7 @@ async function prepareCodexOrchestrationDelegation(taskRef, input, options = {})
135
124
  controlGeneration: controller.controlGeneration
136
125
  } } : {})
137
126
  };
138
- return prepareOrchestrationDelegation(taskRef, {
127
+ const prepared = prepareOrchestrationDelegation(taskRef, {
139
128
  ...input,
140
129
  lifecycleProvenance
141
130
  }, {
@@ -175,6 +164,9 @@ async function prepareCodexOrchestrationDelegation(taskRef, input, options = {})
175
164
  }
176
165
  }
177
166
  });
167
+ return identityWarnings.length > 0 && prepared.status !== 'failed'
168
+ ? { ...prepared, warnings: Object.freeze(identityWarnings) }
169
+ : prepared;
178
170
  }
179
171
  catch (error) {
180
172
  if (error instanceof OrchestrationStateError)
@@ -216,10 +208,11 @@ async function activateCodexOrchestrationDelegation(childThreadId, options = {})
216
208
  });
217
209
  const buildIdentity = options.buildIdentity ?? computeLifecycleBuildIdentity(repoRoot);
218
210
  const contextPath = process.env.AGENT_INFRA_CODEX_CONTROLLER_CONTEXT;
219
- const controller = contextPath
220
- ? verifyCodexSandboxControllerContext(contextPath, { repoRoot })
211
+ const contextVerification = contextPath
212
+ ? verifySandboxControllerWithWarnings(contextPath, { repoRoot })
221
213
  : null;
222
- return activateMatchingOrchestrationDelegation('codex', {
214
+ const controller = contextVerification?.context ?? null;
215
+ const activated = activateMatchingOrchestrationDelegation('codex', {
223
216
  nativeAgent: evidence.nativeAgent,
224
217
  childId: evidence.childThreadId,
225
218
  parentId: evidence.parentThreadId,
@@ -244,6 +237,10 @@ async function activateCodexOrchestrationDelegation(childThreadId, options = {})
244
237
  controlGeneration: controller?.controlGeneration ?? null
245
238
  }
246
239
  }, coreOptions(options));
240
+ const warnings = contextVerification?.warnings ?? [];
241
+ return warnings.length > 0 && activated.status !== 'failed'
242
+ ? { ...activated, warnings: Object.freeze(warnings) }
243
+ : activated;
247
244
  }
248
245
  catch (error) {
249
246
  if (error instanceof OrchestrationStateError)
@@ -15,6 +15,12 @@ function hasExactKeys(value, required, optional = []) {
15
15
  function exactText(value) {
16
16
  return typeof value === 'string' && value.length > 0 && value.trim() === value;
17
17
  }
18
+ function validCodexBuildMetadata(value) {
19
+ return Number.isSafeInteger(value.protocolVersion) && value.protocolVersion > 0
20
+ && exactText(value.packageVersion)
21
+ && exactText(value.internalExecutableBuildHash)
22
+ && exactText(value.lifecycleContractHash);
23
+ }
18
24
  function nullableText(value) {
19
25
  return value === null || exactText(value);
20
26
  }
@@ -92,9 +98,6 @@ function hasCurrentCodexEvidence(receipt) {
92
98
  const host = receipt.hostEvidence;
93
99
  if (host?.kind !== 'codex-lifecycle-v2'
94
100
  || host.protocolVersion !== provenance.protocolVersion
95
- || host.packageVersion !== provenance.packageVersion
96
- || host.internalExecutableBuildHash !== provenance.internalExecutableBuildHash
97
- || host.lifecycleContractHash !== provenance.lifecycleContractHash
98
101
  || host.hookDefinitionHash !== provenance.hookDefinitionHash
99
102
  || host.hookSource !== provenance.hookSource
100
103
  || host.hookSourcePathDigest !== provenance.hookSourcePathDigest
@@ -379,9 +382,6 @@ function activateDelegation(receipt, event, options = {}) {
379
382
  const activationDeadlineAt = Date.parse(receipt.activationDeadlineAt);
380
383
  if (!expected
381
384
  || event.hostEvidence.protocolVersion !== expected.protocolVersion
382
- || event.hostEvidence.packageVersion !== expected.packageVersion
383
- || event.hostEvidence.internalExecutableBuildHash !== expected.internalExecutableBuildHash
384
- || event.hostEvidence.lifecycleContractHash !== expected.lifecycleContractHash
385
385
  || event.hostEvidence.hookDefinitionHash !== expected.hookDefinitionHash
386
386
  || event.hostEvidence.hookSource !== expected.hookSource
387
387
  || event.hostEvidence.hookSourcePathDigest !== expected.hookSourcePathDigest
@@ -395,7 +395,8 @@ function activateDelegation(receipt, event, options = {}) {
395
395
  || spawnObservedAt > activationDeadlineAt
396
396
  || event.parentId !== expected.capabilitySessionId
397
397
  || (event.hostEvidence.controllerInstanceDigest ?? null) !== expected.controllerInstanceDigest
398
- || (event.hostEvidence.controlGeneration ?? null) !== expected.controlGeneration)
398
+ || (event.hostEvidence.controlGeneration ?? null) !== expected.controlGeneration
399
+ || !validCodexBuildMetadata(event.hostEvidence))
399
400
  return fail('DELEGATION_HOST_EVIDENCE_INVALID', 'Codex lifecycle provenance does not match the prepared receipt');
400
401
  }
401
402
  return { ok: true, receipt: Object.freeze({
@@ -269,11 +269,18 @@ function mutationPatch(current, mutation) {
269
269
  }
270
270
  if (mutation.scope === 'verification') {
271
271
  if (mutation.operation === 'succeeded') {
272
+ const resolvedTaskWarning = current.warnings.some((warning) => warning.step === 'verification' && warning.status === 'open');
272
273
  const warnings = resolveStepWarnings(current, 'verification');
273
- return { verification: 'done', warningProjection: warnings.length > 0 ? 'pending' : 'done', warnings, lastError: null };
274
+ return {
275
+ verification: 'done',
276
+ taskComment: resolvedTaskWarning ? 'pending' : current.taskComment,
277
+ warningProjection: warnings.length > 0 ? 'pending' : 'done',
278
+ warnings,
279
+ lastError: null
280
+ };
274
281
  }
275
282
  const warnings = replaceWarning(current, warningFromError('verification', mutation.error), 'open');
276
- return { verification: 'pending', warningProjection: 'pending', warnings, lastError: mutation.error };
283
+ return { verification: 'pending', taskComment: 'pending', warningProjection: 'pending', warnings, lastError: mutation.error };
277
284
  }
278
285
  return mutation.operation === 'succeeded'
279
286
  ? { warningProjection: 'done', lastError: null }
@@ -339,6 +346,50 @@ function reconcileWarningProjection(repoRoot, taskId, receipt, consumed) {
339
346
  return receipt;
340
347
  }
341
348
  }
349
+ function syncPendingTaskComment(input) {
350
+ const { repoRoot, taskId, agent, commentSync, consumedCapabilities } = input;
351
+ let receipt = input.receipt;
352
+ if (receipt.taskComment !== 'pending') {
353
+ return {
354
+ receipt,
355
+ step: { status: receipt.taskComment === 'skipped' ? 'skipped' : 'no-op', changed: false, error: null },
356
+ changed: false,
357
+ error: null
358
+ };
359
+ }
360
+ try {
361
+ const result = commentSync(taskId, { kind: 'task', agent, cwd: repoRoot });
362
+ const step = commentStep(result);
363
+ if (result.status === 'applied' || result.status === 'no-op') {
364
+ const skipped = result.error?.code === 'ISSUE_NOT_LINKED';
365
+ const capability = issueCapability(receipt, 'task-comment');
366
+ receipt = applyFinalizationReceiptMutationUnderLock(repoRoot, receipt, capability, {
367
+ scope: 'task-comment', operation: 'succeeded', state: skipped ? 'skipped' : 'done'
368
+ }, consumedCapabilities);
369
+ receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
370
+ return { receipt, step: skipped ? { ...step, status: 'skipped' } : step, changed: result.changed, error: null };
371
+ }
372
+ const detail = step.error ?? { code: 'COMMENT_SYNC_FAILED', message: 'task comment synchronization failed', retryable: true };
373
+ const capability = issueCapability(receipt, 'task-comment');
374
+ receipt = applyFinalizationReceiptMutationUnderLock(repoRoot, receipt, capability, {
375
+ scope: 'task-comment', operation: 'failed', error: detail
376
+ }, consumedCapabilities);
377
+ receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
378
+ return { receipt, step, changed: result.changed, error: detail };
379
+ }
380
+ catch (error) {
381
+ const detail = errorOf(error, 'COMMENT_SYNC_FAILED', true);
382
+ try {
383
+ const capability = issueCapability(receipt, 'task-comment');
384
+ receipt = applyFinalizationReceiptMutationUnderLock(repoRoot, receipt, capability, {
385
+ scope: 'task-comment', operation: 'failed', error: detail
386
+ }, consumedCapabilities);
387
+ receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
388
+ }
389
+ catch { /* preserve the primary error */ }
390
+ return { receipt, step: { status: 'blocked', changed: false, error: detail }, changed: false, error: detail };
391
+ }
392
+ }
342
393
  function terminalResult(taskId, receipt, steps, changed, error = null) {
343
394
  const pending = pendingSteps(receipt);
344
395
  const blocked = [steps.lifecycle, steps.taskComment, steps.verification].some((step) => step?.status === 'blocked');
@@ -458,50 +509,15 @@ function applyUnderLock(request, taskId, options) {
458
509
  return failed(taskId, detail, { lifecycle: lifecycleResult });
459
510
  }
460
511
  let taskComment = null;
461
- try {
462
- receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
463
- if (receipt.taskComment !== 'pending') {
464
- taskComment = {
465
- status: receipt.taskComment === 'skipped' ? 'skipped' : 'no-op',
466
- changed: false,
467
- error: null
468
- };
469
- }
470
- else {
471
- const result = commentSync(taskId, { kind: 'task', agent: request.agent, cwd: repoRoot });
472
- taskComment = commentStep(result);
473
- if (result.status === 'applied' || result.status === 'no-op') {
474
- const skipped = result.error?.code === 'ISSUE_NOT_LINKED';
475
- const capability = issueCapability(receipt, 'task-comment');
476
- receipt = applyFinalizationReceiptMutationUnderLock(repoRoot, receipt, capability, {
477
- scope: 'task-comment', operation: 'succeeded', state: skipped ? 'skipped' : 'done'
478
- }, consumedCapabilities);
479
- receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
480
- taskComment = skipped ? { ...taskComment, status: 'skipped' } : taskComment;
481
- changed = changed || result.changed;
482
- }
483
- else {
484
- const detail = taskComment.error ?? { code: 'COMMENT_SYNC_FAILED', message: 'task comment synchronization failed', retryable: true };
485
- const capability = issueCapability(receipt, 'task-comment');
486
- receipt = applyFinalizationReceiptMutationUnderLock(repoRoot, receipt, capability, {
487
- scope: 'task-comment', operation: 'failed', error: detail
488
- }, consumedCapabilities);
489
- receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
490
- return terminalResult(taskId, receipt, { lifecycle: lifecycleResult, taskComment, verification: null }, changed, taskComment.error);
491
- }
492
- }
493
- }
494
- catch (error) {
495
- const detail = errorOf(error, 'COMMENT_SYNC_FAILED', true);
496
- try {
497
- const capability = issueCapability(receipt, 'task-comment');
498
- receipt = applyFinalizationReceiptMutationUnderLock(repoRoot, receipt, capability, {
499
- scope: 'task-comment', operation: 'failed', error: detail
500
- }, consumedCapabilities);
501
- receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
502
- }
503
- catch { /* preserve the primary error */ }
504
- return terminalResult(taskId, receipt, { lifecycle: lifecycleResult, taskComment: { status: 'blocked', changed: false, error: detail }, verification: null }, changed, detail);
512
+ receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
513
+ const initialComment = syncPendingTaskComment({
514
+ repoRoot, taskId, agent: request.agent, receipt, commentSync, consumedCapabilities
515
+ });
516
+ receipt = initialComment.receipt;
517
+ taskComment = initialComment.step;
518
+ changed = changed || initialComment.changed;
519
+ if (initialComment.error) {
520
+ return terminalResult(taskId, receipt, { lifecycle: lifecycleResult, taskComment, verification: null }, changed, initialComment.error);
505
521
  }
506
522
  let verification = null;
507
523
  try {
@@ -518,6 +534,16 @@ function applyUnderLock(request, taskId, options) {
518
534
  scope: 'verification', operation: 'succeeded'
519
535
  }, consumedCapabilities);
520
536
  receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
537
+ const finalComment = syncPendingTaskComment({
538
+ repoRoot, taskId, agent: request.agent, receipt, commentSync, consumedCapabilities
539
+ });
540
+ receipt = finalComment.receipt;
541
+ if (finalComment.step.status !== 'no-op')
542
+ taskComment = finalComment.step;
543
+ changed = changed || finalComment.changed;
544
+ if (finalComment.error) {
545
+ return terminalResult(taskId, receipt, { lifecycle: lifecycleResult, taskComment, verification }, changed, finalComment.error);
546
+ }
521
547
  }
522
548
  else {
523
549
  const detail = verification.error ?? { code: 'VERIFY_FAILED', message: 'verification failed', retryable: true };
@@ -10,7 +10,6 @@ Commands:
10
10
  ls [--all | --blocked | --completed] List tasks (default: active)
11
11
  show [--task <ref>] Print a task.md
12
12
  status [--task <ref>] Aggregated status view (metadata / artifacts / git / platform)
13
- validate <ref> [options] -- <command> Run host validation in snapshot or inplace scope
14
13
 
15
14
  Examples:
16
15
  ai task cat 11 analysis
@@ -30,7 +29,6 @@ Examples:
30
29
  ai task show 11
31
30
  ai task show TASK-20260612-162737
32
31
  ai task status 11
33
- ai task validate 11 -- npm test
34
32
 
35
33
  Run 'ai task <command> --help' for details.`;
36
34
  export async function runTask(args) {
@@ -91,13 +89,6 @@ export async function runTask(args) {
91
89
  status(rest);
92
90
  break;
93
91
  }
94
- case 'validate': {
95
- const { validate } = await import('./commands/validate.js');
96
- const exitCode = await validate(rest);
97
- if (exitCode !== 0)
98
- process.exitCode = exitCode;
99
- break;
100
- }
101
92
  default:
102
93
  process.stderr.write(`Unknown task command: ${subcommand}\n\n`);
103
94
  process.stdout.write(`${USAGE}\n`);
@@ -25,7 +25,7 @@ function supportsLifecycleDelegation(client) {
25
25
  function orchestrationPath(taskDir) {
26
26
  return path.join(taskDir, 'orchestration.json');
27
27
  }
28
- const ORCHESTRATION_STATE_INVALID_MESSAGE = 'orchestration.json does not match the current runtime structure; finish or clear active runs before upgrading';
28
+ const ORCHESTRATION_STATE_INVALID_MESSAGE = 'orchestration.json does not match the current runtime structure; the file was left unchanged; rebuild the sandbox or manually repair the state before retrying';
29
29
  class OrchestrationStateError extends Error {
30
30
  code = 'ORCHESTRATION_STATE_INVALID';
31
31
  taskId;
@@ -308,9 +308,6 @@ function checkOrchestrationEvidence({ taskDir }) {
308
308
  if (!provenance
309
309
  || (activatedCodex && (host?.kind !== 'codex-lifecycle-v2'
310
310
  || host.protocolVersion !== provenance.protocolVersion
311
- || host.packageVersion !== provenance.packageVersion
312
- || host.internalExecutableBuildHash !== provenance.internalExecutableBuildHash
313
- || host.lifecycleContractHash !== provenance.lifecycleContractHash
314
311
  || host.hookDefinitionHash !== provenance.hookDefinitionHash
315
312
  || host.hookSource !== provenance.hookSource
316
313
  || host.hookSourcePathDigest !== provenance.hookSourcePathDigest
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "protocolVersion": 3,
3
- "packageVersion": "0.9.9",
4
- "sourceInputHash": "2f2db695db557531b3139eccd1bd530fbeb7b99409e2d49338f3b4646aac9ef6",
5
- "internalExecutableBuildHash": "1391ebe2614f3071e7a60920325046a47f1473e7fb23c821327072f947f639d3"
3
+ "packageVersion": "0.9.10",
4
+ "sourceInputHash": "fae928c7a46686dd39f9f57424e3caff8aed3ce5de5700475b4a3a8f2954b3a3",
5
+ "internalExecutableBuildHash": "a5e143239a8fdd93c4696125aab04b28d7baf59c2a65488705fe930d4965f354"
6
6
  }
@@ -28,6 +28,13 @@ type IdentityVerification = Readonly<{
28
28
  ok: boolean;
29
29
  code: string | null;
30
30
  message: string | null;
31
+ warnings: readonly LifecycleIdentityWarning[];
32
+ }>;
33
+
34
+ type LifecycleIdentityWarning = Readonly<{
35
+ code: 'CODEX_LIFECYCLE_BUILD_MISMATCH' | 'CODEX_LIFECYCLE_CONTRACT_MISMATCH';
36
+ message: string;
37
+ action: 'rebuild-sandbox';
31
38
  }>;
32
39
 
33
40
  function exactText(value: unknown): value is string {
@@ -201,25 +208,32 @@ function verifyLifecycleBuildIdentity(
201
208
  return {
202
209
  ok: false,
203
210
  code: 'CODEX_LIFECYCLE_PROTOCOL_MISMATCH',
204
- message: 'Codex lifecycle protocol version does not match'
211
+ message: 'Codex lifecycle protocol version does not match',
212
+ warnings: []
205
213
  };
206
214
  }
215
+ const warnings: LifecycleIdentityWarning[] = [];
207
216
  if (actual.packageVersion !== expected.packageVersion
208
217
  || actual.internalExecutableBuildHash !== expected.internalExecutableBuildHash) {
209
- return {
210
- ok: false,
218
+ warnings.push({
211
219
  code: 'CODEX_LIFECYCLE_BUILD_MISMATCH',
212
- message: 'Codex lifecycle executable build identity does not match'
213
- };
220
+ message: 'Codex lifecycle executable build identity differs; rebuild the sandbox if the runtime is stale',
221
+ action: 'rebuild-sandbox'
222
+ });
214
223
  }
215
224
  if (actual.lifecycleContractHash !== expected.lifecycleContractHash) {
216
- return {
217
- ok: false,
225
+ warnings.push({
218
226
  code: 'CODEX_LIFECYCLE_CONTRACT_MISMATCH',
219
- message: 'Codex lifecycle contract identity does not match'
220
- };
227
+ message: 'Codex lifecycle contract identity differs; rebuild the sandbox if the runtime is stale',
228
+ action: 'rebuild-sandbox'
229
+ });
221
230
  }
222
- return { ok: true, code: null, message: null };
231
+ return {
232
+ ok: true,
233
+ code: null,
234
+ message: null,
235
+ warnings: Object.freeze(warnings)
236
+ };
223
237
  }
224
238
 
225
239
  export {
@@ -233,6 +247,7 @@ export {
233
247
  };
234
248
  export type {
235
249
  IdentityVerification,
250
+ LifecycleIdentityWarning,
236
251
  LifecycleBuildIdentity,
237
252
  LifecycleManifestFiles,
238
253
  LifecycleBuildIdentityOptions