@fitlab-ai/agent-infra 0.11.12 → 0.11.14

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 (120) hide show
  1. package/dist/bin/internal-cli.js +2 -7
  2. package/dist/lib/agent-clients/adapter.js +0 -3
  3. package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +5 -9
  4. package/dist/lib/agent-clients/adapters/codex-lifecycle/cli.js +1 -5
  5. package/dist/lib/agent-clients/adapters/codex-lifecycle/recovery.js +1 -5
  6. package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +11 -25
  7. package/dist/lib/agent-clients/adapters/codex-orchestration.js +1 -3
  8. package/dist/lib/agent-clients/adapters/codex.js +0 -2
  9. package/dist/lib/internal/cli-route-inventory.js +0 -1
  10. package/dist/lib/internal/sandbox-control.js +1 -81
  11. package/dist/lib/internal/task-finalization.js +29 -20
  12. package/dist/lib/internal/task-lifecycle.js +4 -2
  13. package/dist/lib/internal/task-operation-registry.js +1 -3
  14. package/dist/lib/internal/task-validate.js +14 -124
  15. package/dist/lib/platform/github-client.js +33 -3
  16. package/dist/lib/platform/release-notes.js +2 -2
  17. package/dist/lib/sandbox/commands/control-status.js +0 -1
  18. package/dist/lib/sandbox/commands/create.js +11 -6
  19. package/dist/lib/sandbox/commands/ls.js +4 -5
  20. package/dist/lib/sandbox/commands/rm.js +1 -1
  21. package/dist/lib/sandbox/config.js +12 -0
  22. package/dist/lib/sandbox/control/audit.js +1 -2
  23. package/dist/lib/sandbox/control/client.js +15 -110
  24. package/dist/lib/sandbox/control/executor.js +9 -59
  25. package/dist/lib/sandbox/control/host-recovery.js +89 -27
  26. package/dist/lib/sandbox/control/lifecycle.js +75 -9
  27. package/dist/lib/sandbox/control/protocol.js +11 -50
  28. package/dist/lib/sandbox/control/server.js +203 -253
  29. package/dist/lib/sandbox/control/task-view.js +0 -13
  30. package/dist/lib/sandbox/environment.js +7 -8
  31. package/dist/lib/sandbox/mounts.js +71 -52
  32. package/dist/lib/sandbox/recovery.js +30 -12
  33. package/dist/lib/sandbox/removal.js +37 -3
  34. package/dist/lib/sandbox/workspace-identity.js +18 -6
  35. package/dist/lib/sandbox/workspace-view.js +89 -6
  36. package/dist/lib/task/commands/ls.js +3 -3
  37. package/dist/lib/task/control-authority.js +41 -17
  38. package/dist/lib/task/control-recovery.js +2 -4
  39. package/dist/lib/task/finalization.js +27 -2
  40. package/dist/lib/task/lifecycle.js +294 -63
  41. package/dist/lib/task/orchestration.js +26 -72
  42. package/dist/lib/task/short-id.js +37 -27
  43. package/dist/lib/task/verification-engine.js +4 -11
  44. package/dist/lifecycle-build-manifest.json +3 -3
  45. package/lib/agent-clients/adapter.ts +0 -27
  46. package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +6 -12
  47. package/lib/agent-clients/adapters/codex-lifecycle/cli.ts +1 -5
  48. package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +0 -7
  49. package/lib/agent-clients/adapters/codex-lifecycle/recovery.ts +1 -5
  50. package/lib/agent-clients/adapters/codex-lifecycle/store.ts +21 -27
  51. package/lib/agent-clients/adapters/codex-orchestration.ts +1 -3
  52. package/lib/agent-clients/adapters/codex.ts +0 -2
  53. package/lib/internal/cli-route-inventory.ts +0 -1
  54. package/lib/internal/sandbox-control.ts +0 -97
  55. package/lib/internal/task-finalization.ts +33 -19
  56. package/lib/internal/task-lifecycle.ts +4 -2
  57. package/lib/internal/task-operation-registry.ts +1 -3
  58. package/lib/internal/task-validate.ts +13 -127
  59. package/lib/platform/github-client.ts +34 -3
  60. package/lib/platform/release-notes.ts +2 -2
  61. package/lib/sandbox/commands/control-status.ts +0 -1
  62. package/lib/sandbox/commands/create.ts +11 -5
  63. package/lib/sandbox/commands/ls.ts +4 -5
  64. package/lib/sandbox/commands/rm.ts +1 -1
  65. package/lib/sandbox/config.ts +13 -0
  66. package/lib/sandbox/control/audit.ts +1 -2
  67. package/lib/sandbox/control/client.ts +17 -138
  68. package/lib/sandbox/control/executor.ts +8 -65
  69. package/lib/sandbox/control/host-recovery.ts +94 -21
  70. package/lib/sandbox/control/lifecycle.ts +75 -10
  71. package/lib/sandbox/control/protocol.ts +12 -62
  72. package/lib/sandbox/control/server.ts +11 -61
  73. package/lib/sandbox/control/task-view.ts +0 -19
  74. package/lib/sandbox/environment.ts +7 -8
  75. package/lib/sandbox/mounts.ts +93 -68
  76. package/lib/sandbox/recovery.ts +37 -19
  77. package/lib/sandbox/removal.ts +36 -5
  78. package/lib/sandbox/workspace-identity.ts +16 -6
  79. package/lib/sandbox/workspace-view.ts +81 -7
  80. package/lib/task/commands/ls.ts +3 -3
  81. package/lib/task/control-authority.ts +41 -26
  82. package/lib/task/control-recovery.ts +3 -5
  83. package/lib/task/finalization.ts +29 -2
  84. package/lib/task/lifecycle.ts +282 -57
  85. package/lib/task/orchestration.ts +30 -68
  86. package/lib/task/short-id.ts +33 -40
  87. package/lib/task/verification-engine.ts +3 -9
  88. package/package.json +1 -1
  89. package/templates/.agents/rules/README.en.md +1 -0
  90. package/templates/.agents/rules/README.zh-CN.md +1 -0
  91. package/templates/.agents/rules/lifecycle-orchestration.en.md +3 -4
  92. package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +3 -4
  93. package/templates/.agents/rules/strategy-pattern.en.md +25 -0
  94. package/templates/.agents/rules/strategy-pattern.zh-CN.md +25 -0
  95. package/templates/.agents/rules/task-short-id.en.md +18 -12
  96. package/templates/.agents/rules/task-short-id.zh-CN.md +12 -7
  97. package/templates/.agents/skills/code-task/reference/code-rules.en.md +4 -0
  98. package/templates/.agents/skills/code-task/reference/code-rules.zh-CN.md +4 -0
  99. package/templates/.agents/skills/complete-task/SKILL.en.md +5 -26
  100. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +5 -23
  101. package/templates/.agents/skills/create-release-note/SKILL.en.md +3 -1
  102. package/templates/.agents/skills/create-release-note/SKILL.zh-CN.md +3 -1
  103. package/templates/.agents/skills/plan-task/SKILL.en.md +7 -4
  104. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +7 -4
  105. package/templates/.agents/skills/run-task/SKILL.en.md +2 -2
  106. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +2 -2
  107. package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-client.js +0 -130
  108. package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-context.js +0 -127
  109. package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-operation.js +0 -131
  110. package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-registration.js +0 -275
  111. package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller-cli.js +0 -106
  112. package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +0 -345
  113. package/dist/lib/sandbox/control/agent-operation.js +0 -34
  114. package/lib/agent-clients/adapters/codex-lifecycle/controller-client.ts +0 -158
  115. package/lib/agent-clients/adapters/codex-lifecycle/controller-context.ts +0 -163
  116. package/lib/agent-clients/adapters/codex-lifecycle/controller-operation.ts +0 -151
  117. package/lib/agent-clients/adapters/codex-lifecycle/controller-registration.ts +0 -352
  118. package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller-cli.ts +0 -109
  119. package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +0 -437
  120. package/lib/sandbox/control/agent-operation.ts +0 -49
@@ -6,7 +6,7 @@ import { parseTypedTaskFrontmatter } from './frontmatter.js';
6
6
  import { validateCurrentTaskContract } from './current-contract.js';
7
7
  import { parseReviewSummary } from './review-artifacts.js';
8
8
  import { LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, parseLedgerDocument, summarizeLedgerStage, validateLedgerRows } from './ledger.js';
9
- import { resolveTaskRef } from './resolve-ref.js';
9
+ import { resolveTaskRef, TASK_ID_RE } from './resolve-ref.js';
10
10
  import { activateDelegation, abortActivatedDelegation, abortPreparedDelegation, completeDelegationStage, consumeDelegation, dispatchDelegation, foldBlankToNull, isDelegationReceipt, managedDelegationRole, prepareDelegation, sealDelegation } from './delegation-receipts.js';
11
11
  import { normalizeAgentClients } from '../agent-clients/config.js';
12
12
  import { getAgentClientAdapter, getAgentClientCapability, getAgentClientDelegationEvidence, getAgentClientModelSelection } from '../agent-clients/registry.js';
@@ -77,7 +77,7 @@ function isModelPolicy(value) {
77
77
  }
78
78
  const RECOVERY_GUARD_KEYS = [
79
79
  'stepCount', 'nextStage', 'baselineEmpty', 'receiptCount', 'pendingDelegation',
80
- 'commitAuthorizationUnused', 'completionEvidenceAbsent'
80
+ 'completionEvidenceAbsent'
81
81
  ];
82
82
  function isRecovery(value) {
83
83
  if (!hasExactKeys(value, [
@@ -97,7 +97,6 @@ function isRecovery(value) {
97
97
  && guards.baselineEmpty === true
98
98
  && guards.receiptCount === 0
99
99
  && guards.pendingDelegation === false
100
- && guards.commitAuthorizationUnused === true
101
100
  && guards.completionEvidenceAbsent === true
102
101
  && value.resultingStatus === 'running';
103
102
  }
@@ -117,7 +116,7 @@ export function isCompletionEvidence(value) {
117
116
  const ORCHESTRATION_RUN_KEYS = [
118
117
  'taskId', 'runId', 'status', 'nextStage', 'stepCount', 'maxSteps', 'modelPolicy',
119
118
  'modelPolicySource', 'recoveryHistory', 'baseline', 'pendingDelegation', 'receipts',
120
- 'pause', 'commitAuthorization', 'completionEvidence', 'createdAt', 'updatedAt'
119
+ 'pause', 'completionEvidence', 'createdAt', 'updatedAt'
121
120
  ];
122
121
  function orchestrationStateReasonCodes(value) {
123
122
  if (!hasExactKeys(value, ORCHESTRATION_RUN_KEYS))
@@ -151,15 +150,6 @@ function orchestrationStateReasonCodes(value) {
151
150
  reasons.push('receipts');
152
151
  if (!(record.pause === null || isPause(record.pause)))
153
152
  reasons.push('pause');
154
- if (!hasExactKeys(record.commitAuthorization, ['issuedAt', 'consumedAt'])) {
155
- reasons.push('commitAuthorization');
156
- }
157
- else {
158
- if (!nullableText(record.commitAuthorization.issuedAt))
159
- reasons.push('commitAuthorization.issuedAt');
160
- if (!nullableText(record.commitAuthorization.consumedAt))
161
- reasons.push('commitAuthorization.consumedAt');
162
- }
163
153
  if (!(record.completionEvidence === null || isCompletionEvidence(record.completionEvidence)))
164
154
  reasons.push('completionEvidence');
165
155
  if (!exactText(record.createdAt))
@@ -392,8 +382,6 @@ function canRecoverUnsupportedClientPause(run) {
392
382
  && run.baseline === ''
393
383
  && run.pendingDelegation === null
394
384
  && run.receipts.length === 0
395
- && run.commitAuthorization?.issuedAt === null
396
- && run.commitAuthorization?.consumedAt === null
397
385
  && run.completionEvidence == null
398
386
  && run.recoveryHistory.length === 0;
399
387
  }
@@ -443,7 +431,6 @@ function beginOrResumeOrchestration(taskRef, options = {}) {
443
431
  baselineEmpty: true,
444
432
  receiptCount: 0,
445
433
  pendingDelegation: false,
446
- commitAuthorizationUnused: true,
447
434
  completionEvidenceAbsent: true
448
435
  },
449
436
  resultingStatus: 'running'
@@ -497,7 +484,6 @@ function beginOrResumeOrchestration(taskRef, options = {}) {
497
484
  pendingDelegation: null,
498
485
  receipts: [],
499
486
  pause: null,
500
- commitAuthorization: { issuedAt: null, consumedAt: null },
501
487
  completionEvidence: null,
502
488
  createdAt: now,
503
489
  updatedAt: now
@@ -858,72 +844,40 @@ function dispatchOrchestrationDelegation(taskRef, options = {}) {
858
844
  }
859
845
  }
860
846
  function matchingDelegations(predicate, options) {
847
+ const taskId = options.taskId;
848
+ if (taskId === undefined || taskId === '') {
849
+ return { ok: false, error: failed('ORCHESTRATION_TASK_ID_REQUIRED', 'taskId is required for lifecycle matching') };
850
+ }
851
+ if (typeof taskId !== 'string' || !TASK_ID_RE.test(taskId)) {
852
+ return { ok: false, error: failed('ORCHESTRATION_TASK_ID_INVALID', 'taskId must be a canonical TASK-id') };
853
+ }
861
854
  const repoRoot = options.repoRoot ?? process.cwd();
862
- if (options.taskId) {
863
- const taskDir = path.join(repoRoot, '.agents', 'workspace', 'active', options.taskId);
864
- const run = readRun(taskDir, options);
865
- return run && run.status === 'running' && run.pendingDelegation && predicate(run.pendingDelegation)
866
- ? [{ taskId: options.taskId, run }]
867
- : [];
868
- }
869
- const activeRoot = path.join(repoRoot, '.agents', 'workspace', 'active');
870
- if (!fs.existsSync(activeRoot))
871
- return [];
872
- emitDiagnostic(options, 'orchestration-active-scan-start', {
873
- activeRoot
874
- });
875
- const matches = fs.readdirSync(activeRoot, { withFileTypes: true })
876
- .filter((entry) => entry.isDirectory())
877
- .flatMap((entry) => {
878
- const candidateTaskId = entry.name;
879
- const candidateTaskDir = path.join(activeRoot, candidateTaskId);
880
- emitDiagnostic(options, 'orchestration-active-scan-candidate', {
881
- candidateTaskId,
882
- candidateTaskDir,
883
- orchestrationPath: orchestrationPath(candidateTaskDir)
884
- });
885
- let run;
886
- try {
887
- run = readRun(candidateTaskDir, options);
888
- }
889
- catch (error) {
890
- emitDiagnostic(options, 'orchestration-active-scan-candidate-failed', {
891
- candidateTaskId,
892
- candidateTaskDir,
893
- errorType: error instanceof Error ? error.name : typeof error,
894
- reasonCodes: error instanceof OrchestrationStateError ? error.reasonCodes.join(',') : null
895
- });
896
- throw error;
897
- }
898
- if (!run || run.status !== 'running' || !run.pendingDelegation)
899
- return [];
900
- const receipt = run.pendingDelegation;
901
- return predicate(receipt)
902
- ? [{ taskId: candidateTaskId, run }]
903
- : [];
904
- });
905
- emitDiagnostic(options, 'orchestration-active-scan-finished', {
906
- activeRoot,
907
- matchingDelegations: matches.length
908
- });
909
- return matches;
855
+ const taskDir = path.join(repoRoot, '.agents', 'workspace', 'active', taskId);
856
+ const run = readRun(taskDir, options);
857
+ const matches = run && run.status === 'running' && run.pendingDelegation && predicate(run.pendingDelegation)
858
+ ? [{ taskId, run }]
859
+ : [];
860
+ return { ok: true, matches };
910
861
  }
911
862
  function uniqueMatchingDelegation(client, predicate, options) {
912
- const matches = matchingDelegations((receipt) => receipt.client === client && predicate(receipt), options);
863
+ const lookup = matchingDelegations((receipt) => receipt.client === client && predicate(receipt), options);
864
+ if (!lookup.ok)
865
+ return lookup.error;
866
+ const matches = lookup.matches;
913
867
  if (matches.length === 0)
914
868
  return failed('ORCHESTRATION_DELEGATION_MISSING', 'no matching lifecycle delegation exists');
915
- if (matches.length > 1)
916
- return failed('ORCHESTRATION_DELEGATION_AMBIGUOUS', 'multiple lifecycle delegations match the native hook event');
917
869
  return matches[0];
918
870
  }
919
871
  function hasActivatableOrchestrationDelegation(client, childId, options = {}) {
920
- return matchingDelegations((receipt) => receipt.client === client && (receipt.status === 'prepared'
921
- || (receipt.childId === childId && ['activated', 'stage-completed'].includes(receipt.status))), options).length > 0;
872
+ const lookup = matchingDelegations((receipt) => receipt.client === client && (receipt.status === 'prepared'
873
+ || (receipt.childId === childId && ['activated', 'stage-completed'].includes(receipt.status))), options);
874
+ return lookup.ok && lookup.matches.length > 0;
922
875
  }
923
876
  function hasSealableOrchestrationDelegation(client, childId, options = {}) {
924
- return matchingDelegations((receipt) => receipt.client === client
877
+ const lookup = matchingDelegations((receipt) => receipt.client === client
925
878
  && receipt.childId === childId
926
- && ['stage-completed', 'sealed'].includes(receipt.status), options).length > 0;
879
+ && ['stage-completed', 'sealed'].includes(receipt.status), options);
880
+ return lookup.ok && lookup.matches.length > 0;
927
881
  }
928
882
  function activateMatchingOrchestrationDelegation(client, event, options = {}) {
929
883
  const role = managedDelegationRole(event.nativeAgent);
@@ -1,5 +1,6 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
+ import { parseTypedTaskFrontmatter } from './frontmatter.js';
3
4
  const REGISTRY_NAME = '.short-ids.json';
4
5
  const LOCK_NAME = '.short-ids.json.lock';
5
6
  function normalizeShortIdInput(input, opts) {
@@ -90,20 +91,33 @@ function resolveShortIdReadOnly(input, repoRoot, opts) {
90
91
  const taskId = ids[key];
91
92
  if (!taskId) {
92
93
  const message = Object.keys(ids).length === 0
93
- ? `short id '${key}' not found; active task registry is empty.`
94
- : `short id '${key}' not found in active task registry (it may have been cleaned up after archival; check 'task-short-id.js list').`;
94
+ ? `short id '${key}' not found; task short-id registry is empty.`
95
+ : `short id '${key}' not found in task short-id registry.`;
95
96
  return shortIdFailure('SHORT_ID_NOT_FOUND', message);
96
97
  }
97
- const taskMdPath = path.join(repoRoot, '.agents', 'workspace', 'active', taskId, 'task.md');
98
- if (!fs.existsSync(taskMdPath)) {
99
- const remainingActiveEntries = Object.values(ids).filter((candidateTaskId) => fs.existsSync(path.join(repoRoot, '.agents', 'workspace', 'active', candidateTaskId, 'task.md')));
100
- const message = remainingActiveEntries.length === 0
101
- ? `short id '${key}' not found; active task registry is empty.`
102
- : `short id '${key}' not found in active task registry (it may have been cleaned up after archival; check 'task-short-id.js list').`;
103
- return shortIdFailure('SHORT_ID_STALE', message, taskId);
98
+ const activeTask = path.join(repoRoot, '.agents', 'workspace', 'active', taskId, 'task.md');
99
+ const completedTask = path.join(repoRoot, '.agents', 'workspace', 'completed', taskId, 'task.md');
100
+ let isValidCompletedTask = false;
101
+ try {
102
+ const task = parseTypedTaskFrontmatter(fs.readFileSync(completedTask, 'utf8'));
103
+ isValidCompletedTask = task.id === taskId && task.status === 'completed';
104
+ }
105
+ catch { /* a mapping is valid only while its task content is present */ }
106
+ if (!fs.existsSync(activeTask) && !isValidCompletedTask) {
107
+ return shortIdFailure('SHORT_ID_STALE', `short id '${key}' points to no active or completed task content.`, taskId);
104
108
  }
105
109
  return { ok: true, taskId };
106
110
  }
111
+ function hasRetainedCompletedTask(activeDir, taskId) {
112
+ try {
113
+ const taskPath = path.join(path.dirname(activeDir), 'completed', taskId, 'task.md');
114
+ const task = parseTypedTaskFrontmatter(fs.readFileSync(taskPath, 'utf8'));
115
+ return task.id === taskId && task.status === 'completed';
116
+ }
117
+ catch {
118
+ return false;
119
+ }
120
+ }
107
121
  function configuredShortIdLength(repoRoot) {
108
122
  try {
109
123
  const config = JSON.parse(fs.readFileSync(path.join(repoRoot, '.agents', '.airc.json'), 'utf8'));
@@ -206,7 +220,8 @@ function mutateShortIdRegistryAt(activeDir, width, taskId, requested, onlyReleas
206
220
  const original = JSON.stringify(registry.ids);
207
221
  if (pruneOrphans) {
208
222
  for (const [key, candidate] of Object.entries(registry.ids)) {
209
- if (candidate !== taskId && !fs.existsSync(path.join(activeDir, candidate, 'task.md')))
223
+ if (candidate !== taskId && !fs.existsSync(path.join(activeDir, candidate, 'task.md'))
224
+ && !hasRetainedCompletedTask(activeDir, candidate))
210
225
  delete registry.ids[key];
211
226
  }
212
227
  }
@@ -277,34 +292,29 @@ function executeShortIdCommand(request) {
277
292
  if (normalized.kind !== 'shortId') {
278
293
  throw Object.assign(new Error(`invalid short id format '${request.argument}'`), { code: 'SHORT_ID_FORMAT_INVALID' });
279
294
  }
280
- return withRegistryLock(request.activeDir, () => {
281
- const registry = readRegistryStrict(registryPath, request.shortIdLength);
282
- let changed = false;
283
- for (const [key, taskId] of Object.entries(registry.ids)) {
284
- if (!fs.existsSync(path.join(request.activeDir, taskId, 'task.md'))) {
285
- delete registry.ids[key];
286
- changed = true;
287
- }
288
- }
289
- if (changed)
290
- writeRegistry(registryPath, registry);
291
- const taskId = registry.ids[normalized.value];
292
- if (!taskId)
293
- throw Object.assign(new Error(`short id '${normalized.value}' not found in active task registry`), { code: 'SHORT_ID_NOT_FOUND' });
294
- return { status: changed ? 'applied' : 'no-op', changed, output: taskId, error: null };
295
- });
295
+ const registry = readRegistryStrict(registryPath, request.shortIdLength);
296
+ const taskId = registry.ids[normalized.value];
297
+ if (!taskId)
298
+ throw Object.assign(new Error(`short id '${normalized.value}' not found in task short-id registry`), { code: 'SHORT_ID_NOT_FOUND' });
299
+ if (!fs.existsSync(path.join(request.activeDir, taskId, 'task.md'))
300
+ && !hasRetainedCompletedTask(request.activeDir, taskId)) {
301
+ throw Object.assign(new Error(`short id '${normalized.value}' points to a task without active or completed task content`), { code: 'SHORT_ID_STALE' });
302
+ }
303
+ return { status: 'no-op', changed: false, output: taskId, error: null };
296
304
  }
297
305
  const registry = readRegistryStrict(registryPath, request.shortIdLength);
298
306
  if (request.verify) {
299
307
  const active = fs.existsSync(request.activeDir)
300
308
  ? fs.readdirSync(request.activeDir).filter((entry) => /^TASK-\d{8}-\d{6}$/.test(entry) && fs.existsSync(path.join(request.activeDir, entry, 'task.md')))
301
309
  : [];
310
+ const retained = Object.values(registry.ids).filter((taskId) => hasRetainedCompletedTask(request.activeDir, taskId));
311
+ const validTasks = new Set([...active, ...retained]);
302
312
  const registered = new Map();
303
313
  for (const [key, taskId] of Object.entries(registry.ids))
304
314
  registered.set(taskId, [...(registered.get(taskId) ?? []), key]);
305
315
  const diff = {
306
316
  missing_in_registry: active.filter((taskId) => !registered.has(taskId)).map((taskId) => ({ taskId })),
307
- orphans_in_registry: Object.entries(registry.ids).filter(([, taskId]) => !active.includes(taskId)).map(([key, taskId]) => ({ key, taskId })),
317
+ orphans_in_registry: Object.entries(registry.ids).filter(([, taskId]) => !validTasks.has(taskId)).map(([key, taskId]) => ({ key, taskId })),
308
318
  duplicate_registry_keys: [...registered.entries()].filter(([, keys]) => keys.length > 1).map(([taskId, keys]) => ({ taskId, keys }))
309
319
  };
310
320
  const clean = Object.values(diff).every((items) => items.length === 0);
@@ -147,14 +147,9 @@ function checkOrchestrationState({ taskDir }) {
147
147
  return failResult('orchestration-state', 'Paused run requires a stable pause code and message');
148
148
  }
149
149
  if (run.status === 'completed') {
150
- if (run.completionEvidence != null) {
151
- const evidenceError = validateCleanCompletionEvidence(run);
152
- if (evidenceError)
153
- return failResult('orchestration-state', evidenceError);
154
- }
155
- else if (run.pendingDelegation !== null || !run.commitAuthorization?.consumedAt) {
156
- return failResult('orchestration-state', 'Completed run must consume commit authorization and clear pending delegation');
157
- }
150
+ const evidenceError = validateCleanCompletionEvidence(run);
151
+ if (evidenceError)
152
+ return failResult('orchestration-state', evidenceError);
158
153
  }
159
154
  else if (run.completionEvidence != null) {
160
155
  return failResult('orchestration-state', 'Clean completion evidence requires a completed run');
@@ -186,10 +181,8 @@ function validateCleanCompletionEvidence(run) {
186
181
  }
187
182
  if (run.status !== 'completed'
188
183
  || run.pendingDelegation !== null
189
- || run.commitAuthorization?.issuedAt !== null
190
- || run.commitAuthorization?.consumedAt !== null
191
184
  || run.receipts.some((receipt) => receipt.stage === 'commit')) {
192
- return 'Clean completion evidence conflicts with commit delegation state';
185
+ return 'Clean completion evidence conflicts with pending delegation or commit receipt state';
193
186
  }
194
187
  return null;
195
188
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "protocolVersion": 3,
3
- "packageVersion": "0.11.12",
4
- "sourceInputHash": "9d9be72538afb23d15312d9d5eeac8e0a9fb79b4b9b4f8715f0f0218c3f7cfdd",
5
- "internalExecutableBuildHash": "6f21c8843c158d538c95d789a9e048df47bb25080e7f935d00cf60bcb85163db"
3
+ "packageVersion": "0.11.14",
4
+ "sourceInputHash": "24a7d69b687f9ea8fdee5f47f7e59d1b53e13452a7f4f8edd9ca5b494cb5a0d4",
5
+ "internalExecutableBuildHash": "9825f6d7ca5d82e4c5f774b94f0d5e94e1b312583406eeb89a74d717ad757c4a"
6
6
  }
@@ -23,28 +23,6 @@ import type {
23
23
  SandboxTool,
24
24
  SandboxToolContext
25
25
  } from '../sandbox/tool-types.ts';
26
- import type { SandboxControlManifest } from '../sandbox/control/protocol.ts';
27
-
28
- type AgentClientSandboxControlOperation = Readonly<{
29
- validate: (params: Readonly<{
30
- operation: string;
31
- payload: Record<string, unknown>;
32
- manifest: SandboxControlManifest;
33
- }>) => void;
34
- execute: (params: Readonly<{
35
- operation: string;
36
- payload: Record<string, unknown>;
37
- manifest: SandboxControlManifest;
38
- manifestPath: string;
39
- }>) => Promise<Readonly<{ exitCode: number; stdout: string; stderr: string }>>;
40
- recover?: (params: Readonly<{
41
- operation: string;
42
- manifest: SandboxControlManifest;
43
- manifestPath: string;
44
- stdout: string | null;
45
- }>) => Readonly<Record<string, unknown>>;
46
- }>;
47
-
48
26
  type AgentClientCapabilities = AgentClientCapabilityMap;
49
27
 
50
28
  type AgentClientModelSelectionContext =
@@ -147,7 +125,6 @@ type AgentClientAdapter = Readonly<{
147
125
  modelSelection: AgentClientModelSelectionContext;
148
126
  delegationEvidence: AgentClientDelegationEvidence;
149
127
  orchestrationAdapter?: AgentClientOrchestrationAdapter;
150
- sandboxControlOperation?: AgentClientSandboxControlOperation;
151
128
  project: AgentClientProjectDescriptor;
152
129
  sandbox: AgentClientSandboxDescriptor;
153
130
  }>;
@@ -728,9 +705,6 @@ function defineAgentClientAdapter(
728
705
  ...(candidate.orchestrationAdapter === undefined
729
706
  ? {}
730
707
  : { orchestrationAdapter: Object.freeze({ ...candidate.orchestrationAdapter }) }),
731
- ...(candidate.sandboxControlOperation === undefined
732
- ? {}
733
- : { sandboxControlOperation: Object.freeze({ ...candidate.sandboxControlOperation }) }),
734
708
  project: Object.freeze({
735
709
  ownedPathPrefixes: Object.freeze(paths),
736
710
  ...projectAssets,
@@ -753,7 +727,6 @@ export type {
753
727
  AgentClientCapabilities,
754
728
  AgentClientCustomCommandDescriptor,
755
729
  AgentClientDelegationEvidence,
756
- AgentClientSandboxControlOperation,
757
730
  AgentClientLifecycleRecoveryRequest,
758
731
  AgentClientLifecycleRecoveryResult,
759
732
  AgentClientOrchestrationAdapter,
@@ -170,8 +170,7 @@ function validateCodexLifecycleHookConfig(value: unknown): void {
170
170
 
171
171
  function parseCodexHooksList(
172
172
  value: unknown,
173
- repoRoot: string,
174
- expectedSource: 'direct-host' | 'isolated-user' = 'direct-host'
173
+ repoRoot: string
175
174
  ): readonly DiscoveredHook[] {
176
175
  const root = object(value);
177
176
  const entries = Array.isArray(root?.data) ? root.data : [];
@@ -203,10 +202,8 @@ function parseCodexHooksList(
203
202
  && hook.currentHash.length > 0
204
203
  && hook.sourcePathDigest.length === 64
205
204
  && hook.pluginId === null
206
- && (expectedSource === 'isolated-user'
207
- ? hook.source === 'user' && hook.trustStatus !== 'modified'
208
- : (hook.source === 'project' && hook.trustStatus === 'trusted')
209
- || hook.isManaged && hook.trustStatus === 'managed')
205
+ && (hook.source === 'project' && hook.trustStatus === 'trusted'
206
+ || hook.isManaged && hook.trustStatus === 'managed')
210
207
  ));
211
208
  if (!valid) throw new Error('CODEX_PREFLIGHT_HOOKS_NOT_LOADED: lifecycle hooks are not loaded for this workspace');
212
209
  return Object.freeze(hooks);
@@ -628,7 +625,7 @@ function validateStaticCodexPreflight(repoRoot: string): Readonly<{ cliVersion:
628
625
  async function discoverLifecycleHooks(repoRoot: string): Promise<Readonly<{
629
626
  discoveredHooks: readonly DiscoveredHook[];
630
627
  hookProvenance: Readonly<{
631
- hookSource: 'isolated-user' | 'managed' | 'project';
628
+ hookSource: 'managed' | 'project';
632
629
  hookSourcePathDigest: string;
633
630
  hookSourceHash: string;
634
631
  }>;
@@ -641,8 +638,7 @@ async function discoverLifecycleHooks(repoRoot: string): Promise<Readonly<{
641
638
  await transport.initialize();
642
639
  discoveredHooks = parseCodexHooksList(
643
640
  await transport.request('hooks/list', { cwds: [repoRoot] }),
644
- repoRoot,
645
- process.env.AGENT_INFRA_CODEX_CONTROLLER_CONTEXT ? 'isolated-user' : 'direct-host'
641
+ repoRoot
646
642
  );
647
643
  } finally { transport.close(); }
648
644
  const lifecycleHooks = LIFECYCLE_HOOKS.map(({ event, matcher, phase }) => discoveredHooks.filter((hook) =>
@@ -660,9 +656,7 @@ async function discoverLifecycleHooks(repoRoot: string): Promise<Readonly<{
660
656
  throw new Error('CODEX_PREFLIGHT_HOOK_SOURCE_AMBIGUOUS: lifecycle hooks do not share one exact source');
661
657
  }
662
658
  const source = [...sources][0];
663
- const hookSource = process.env.AGENT_INFRA_CODEX_CONTROLLER_CONTEXT
664
- ? 'isolated-user'
665
- : source === 'managed' ? 'managed' : 'project';
659
+ const hookSource = source === 'managed' ? 'managed' : 'project';
666
660
  return {
667
661
  discoveredHooks,
668
662
  hookProvenance: Object.freeze({
@@ -9,7 +9,6 @@ import {
9
9
  resolveCodexThread
10
10
  } from './app-server.ts';
11
11
  import { createCodexLifecycleStore } from './store.ts';
12
- import { verifyCodexSandboxControllerContextWithWarnings } from './sandbox-controller.ts';
13
12
  import type { CodexLifecycleEvent } from './evidence.ts';
14
13
  import { resolveTaskRef } from '../../../task/resolve-ref.ts';
15
14
  import { resolveTaskContext } from '../../../task/resolve-ref.ts';
@@ -103,10 +102,7 @@ function hookDefinitionHash(): string {
103
102
  }
104
103
 
105
104
  function resolveLifecycleTaskContext() {
106
- const controllerPath = process.env.AGENT_INFRA_CODEX_CONTROLLER_CONTEXT;
107
- const taskId = controllerPath
108
- ? verifyCodexSandboxControllerContextWithWarnings(controllerPath, { repoRoot: process.cwd() }).context.taskId
109
- : process.env.AGENT_INFRA_TASK_ID;
105
+ const taskId = process.env.AGENT_INFRA_TASK_ID;
110
106
  const context = taskId
111
107
  ? resolveTaskRef(taskId, { repoRoot: process.cwd() })
112
108
  : resolveTaskContext(undefined, { repoRoot: process.cwd() });
@@ -2,25 +2,18 @@
2
2
  "executableFiles": [
3
3
  "bin/internal-cli.ts",
4
4
  "lib/agent-clients/adapters/codex-lifecycle/cli.ts",
5
- "lib/agent-clients/adapters/codex-lifecycle/sandbox-controller-cli.ts",
6
5
  "lib/internal/sandbox-control.ts",
7
6
  "lib/internal/task-orchestration.ts",
8
7
  "lib/agent-clients/adapters/codex-orchestration.ts",
9
8
  "lib/task/delegation-receipts.ts",
10
9
  "lib/task/orchestration.ts",
11
10
  "lib/agent-clients/adapters/codex-lifecycle/build-identity.ts",
12
- "lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts",
13
- "lib/agent-clients/adapters/codex-lifecycle/controller-client.ts",
14
- "lib/agent-clients/adapters/codex-lifecycle/controller-operation.ts",
15
- "lib/agent-clients/adapters/codex-lifecycle/controller-context.ts",
16
11
  "lib/agent-clients/adapters/codex-lifecycle/app-server.ts",
17
12
  "lib/agent-clients/adapters/codex-lifecycle/binding.ts",
18
13
  "lib/agent-clients/adapters/codex-lifecycle/evidence.ts",
19
14
  "lib/agent-clients/adapters/codex-lifecycle/store.ts",
20
15
  "lib/agent-clients/adapters/codex-lifecycle/manifest-files.json",
21
- "lib/agent-clients/adapters/codex-lifecycle/controller-registration.ts",
22
16
  "lib/sandbox/control/protocol.ts",
23
- "lib/sandbox/control/agent-operation.ts",
24
17
  "lib/sandbox/control/client.ts",
25
18
  "lib/sandbox/control/executor.ts",
26
19
  "lib/server/process-state.ts",
@@ -216,15 +216,11 @@ function hostEvidenceMatches(
216
216
  && host.spawnToolUseId === start.spawnToolUseId
217
217
  && Boolean(host.spawnObservedAt)
218
218
  && host.spawnObservedAt === record.spawnObservedAt
219
- && typeof host.controllerInstanceDigest === 'string'
220
- && typeof host.controlGeneration === 'string'
221
219
  && hostRevisionMatches(record, receipt, host);
222
220
  }
223
221
 
224
222
  function hostRevisionMatches(record: StoredCodexLifecycle, receipt: DelegationReceipt, host: CodexLifecycleActivationEvidence): boolean {
225
- return typeof host.controllerInstanceDigest === 'string'
226
- && typeof host.controlGeneration === 'string'
227
- && host.startRevision >= 1
223
+ return host.startRevision >= 1
228
224
  && record.revision > host.startRevision
229
225
  && record.state.child?.childThreadId === receipt.childId
230
226
  && record.state.stop?.childThreadId === receipt.childId;
@@ -35,16 +35,13 @@ type CodexLifecycleActivationEvidence = Readonly<{
35
35
  packageVersion?: string;
36
36
  internalExecutableBuildHash?: string;
37
37
  lifecycleContractHash?: string;
38
- hookSource?: 'project' | 'managed' | 'isolated-user';
38
+ hookSource?: 'project' | 'managed';
39
39
  hookSourcePathDigest?: string;
40
40
  hookSourceHash?: string;
41
41
  capabilitySessionId?: string;
42
42
  capabilityTurnId?: string;
43
- capabilityToolUseId?: string;
44
43
  spawnToolUseId?: string;
45
44
  spawnObservedAt?: string;
46
- controllerInstanceDigest?: string | null;
47
- controlGeneration?: string | null;
48
45
  }>;
49
46
 
50
47
  type CodexAdapterState = Readonly<{
@@ -307,18 +304,23 @@ function createCodexLifecycleStore(options: CodexLifecycleStoreOptions) {
307
304
  return matches[0]!;
308
305
  }
309
306
 
310
- function consume(
307
+ function consumeWithLockMode(
311
308
  childThreadId: string,
312
309
  consumer: string,
313
- expectedHookDefinitionHash?: string,
314
- expectedTaskBinding?: CodexLifecycleTaskBinding
310
+ expectedHookDefinitionHash: string | undefined,
311
+ expectedTaskBinding: CodexLifecycleTaskBinding | undefined,
312
+ taskLockHeld: boolean
315
313
  ): StoredCodexLifecycle {
316
314
  if (!consumer.trim()) throw new Error('Codex lifecycle consumer is required');
317
315
  const current = read(childThreadId);
318
316
  const binding = expectedTaskBinding ?? current.taskBinding;
319
317
  if (!binding) throw new Error('Codex lifecycle task receipt binding is missing');
320
318
  const spawn = current.state.spawn;
321
- if (!spawn) throw new Error('Codex lifecycle spawn evidence is missing');
319
+ if (!spawn) {
320
+ throw new Error(taskLockHeld
321
+ ? 'Codex lifecycle task receipt binding is missing'
322
+ : 'Codex lifecycle spawn evidence is missing');
323
+ }
322
324
  const key = recordKey(spawn.sessionId, spawn.turnId, spawn.toolUseId);
323
325
  return update(binding, key, (latest) => {
324
326
  if (!latest || expectedTaskBinding && JSON.stringify(latest.taskBinding) !== JSON.stringify(expectedTaskBinding)) {
@@ -333,7 +335,16 @@ function createCodexLifecycleStore(options: CodexLifecycleStoreOptions) {
333
335
  }
334
336
  if (latest.consumer) return latest;
335
337
  return Object.freeze({ ...latest, revision: latest.revision + 1, consumer, consumedAt: now(), updatedAt: now() });
336
- });
338
+ }, taskLockHeld);
339
+ }
340
+
341
+ function consume(
342
+ childThreadId: string,
343
+ consumer: string,
344
+ expectedHookDefinitionHash?: string,
345
+ expectedTaskBinding?: CodexLifecycleTaskBinding
346
+ ): StoredCodexLifecycle {
347
+ return consumeWithLockMode(childThreadId, consumer, expectedHookDefinitionHash, expectedTaskBinding, false);
337
348
  }
338
349
 
339
350
  function consumeWithinTaskLock(
@@ -342,24 +353,7 @@ function createCodexLifecycleStore(options: CodexLifecycleStoreOptions) {
342
353
  expectedHookDefinitionHash?: string,
343
354
  expectedTaskBinding?: CodexLifecycleTaskBinding
344
355
  ): StoredCodexLifecycle {
345
- if (!consumer.trim()) throw new Error('Codex lifecycle consumer is required');
346
- const current = read(childThreadId);
347
- const binding = expectedTaskBinding ?? current.taskBinding;
348
- if (!binding || !current.state.spawn) throw new Error('Codex lifecycle task receipt binding is missing');
349
- const key = recordKey(current.state.spawn.sessionId, current.state.spawn.turnId, current.state.spawn.toolUseId);
350
- return update(binding, key, (latest) => {
351
- if (!latest || expectedTaskBinding && JSON.stringify(latest.taskBinding) !== JSON.stringify(expectedTaskBinding)) {
352
- throw new Error('Codex lifecycle task binding does not match the expected receipt');
353
- }
354
- if (latest.consumer && latest.consumer !== consumer) {
355
- throw new Error(`Codex lifecycle evidence was already consumed by '${latest.consumer}'`);
356
- }
357
- if (latest.state.status !== 'stop-ready') throw new Error('Codex lifecycle evidence is not stop-ready');
358
- if (expectedHookDefinitionHash && latest.state.startEvidence?.hookDefinitionHash !== expectedHookDefinitionHash) {
359
- throw new Error('Codex lifecycle hook definition hash is stale');
360
- }
361
- return latest.consumer ? latest : Object.freeze({ ...latest, revision: latest.revision + 1, consumer, consumedAt: now(), updatedAt: now() });
362
- }, true);
356
+ return consumeWithLockMode(childThreadId, consumer, expectedHookDefinitionHash, expectedTaskBinding, true);
363
357
  }
364
358
 
365
359
  function expireBefore(cutoff: string): number {
@@ -327,9 +327,7 @@ async function activateCodexOrchestrationDelegation(
327
327
  capabilitySessionId: evidence.parentThreadId,
328
328
  capabilityTurnId: evidence.parentTurnId,
329
329
  spawnToolUseId: evidence.spawnToolUseId,
330
- spawnObservedAt: record.spawnObservedAt ?? undefined,
331
- controllerInstanceDigest: null,
332
- controlGeneration: null
330
+ spawnObservedAt: record.spawnObservedAt ?? undefined
333
331
  }
334
332
  }
335
333
  }, coreOptions(options));
@@ -6,7 +6,6 @@ import {
6
6
  } from './codex-sandbox.ts';
7
7
  import { parseCodexLifecycleBinding } from './codex-lifecycle/binding.ts';
8
8
  import { isCodexDelegationReceiptEvidence, isCodexLifecycleActivationEvidence } from './codex-lifecycle/evidence.ts';
9
- import { codexControllerOperation } from './codex-lifecycle/controller-operation.ts';
10
9
 
11
10
  function codexCarrierError(code: string): Error {
12
11
  const error = new Error(code);
@@ -80,7 +79,6 @@ const codexAdapter = defineAgentClientAdapter({
80
79
  return recoverStartedLifecycleFromAdapter(...args);
81
80
  }
82
81
  },
83
- sandboxControlOperation: codexControllerOperation,
84
82
  project: {
85
83
  ownedPathPrefixes: ['.codex/'],
86
84
  managed: ['.codex/hooks.json', '.codex/agents/'],
@@ -38,7 +38,6 @@ export const INTERNAL_HANDLER_ROUTE_SELECTORS = Object.freeze({
38
38
  'sandbox-control': ['serve', 'execute', 'recover', 'client'],
39
39
  'agent-client': ['next-steps', 'model-selection', 'launch-carrier'],
40
40
  'codex-lifecycle': ['preflight', 'hook-event', 'resolve-start', 'resolve-stop', 'consume'],
41
- 'codex-sandbox-controller': ['verify-context', 'run'],
42
41
  'git-workflow': ['inspect', 'preview-tree', 'snapshot', 'compare-trees', 'commit', 'push-rebased'],
43
42
  'task-delivery': ['deliver'],
44
43
  'release-workflow': ['inspect', 'prepare', 'publish', 'post-prepare', 'post-publish'],