@fitlab-ai/agent-infra 0.9.8 → 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 (182) hide show
  1. package/README.md +1 -1
  2. package/README.zh-CN.md +1 -1
  3. package/dist/bin/internal-cli.js +11 -1
  4. package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +58 -21
  5. package/dist/lib/agent-clients/adapters/codex-lifecycle/build-identity.js +25 -11
  6. package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +173 -10
  7. package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-context.js +127 -0
  8. package/dist/lib/agent-clients/adapters/codex-lifecycle/evidence.js +14 -0
  9. package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +63 -92
  10. package/dist/lib/agent-clients/adapters/codex-sandbox.js +72 -11
  11. package/dist/lib/agent-clients/adapters/index.js +3 -1
  12. package/dist/lib/agent-clients/adapters/traecli.js +66 -0
  13. package/dist/lib/agent-clients/tokens.js +2 -2
  14. package/dist/lib/agent-clients/types.js +2 -1
  15. package/dist/lib/defaults.json +5 -0
  16. package/dist/lib/git/workflow.js +10 -1
  17. package/dist/lib/internal/codex-lifecycle.js +20 -6
  18. package/dist/lib/internal/codex-sandbox-controller.js +9 -15
  19. package/dist/lib/internal/git-workflow.js +5 -7
  20. package/dist/lib/internal/platform-pr.js +20 -7
  21. package/dist/lib/internal/release-workflow.js +2 -1
  22. package/dist/lib/internal/sandbox-control.js +79 -2
  23. package/dist/lib/internal/task-finalization.js +54 -0
  24. package/dist/lib/internal/task-orchestration.js +156 -291
  25. package/dist/lib/{task/commands/validate.js → internal/task-validate.js} +75 -42
  26. package/dist/lib/platform/pr-summary.js +48 -10
  27. package/dist/lib/platform/pull-requests.js +205 -41
  28. package/dist/lib/run/tui.js +5 -2
  29. package/dist/lib/sandbox/commands/rm.js +316 -55
  30. package/dist/lib/sandbox/control/client.js +190 -6
  31. package/dist/lib/sandbox/control/controller-registration.js +275 -0
  32. package/dist/lib/sandbox/control/executor.js +140 -5
  33. package/dist/lib/sandbox/control/lifecycle.js +3 -1
  34. package/dist/lib/sandbox/control/protocol.js +67 -6
  35. package/dist/lib/sandbox/index.js +5 -3
  36. package/dist/lib/sandbox/managed-fs.js +5 -0
  37. package/dist/lib/sandbox/task-resolver.js +50 -6
  38. package/dist/lib/sandbox/workspace-identity.js +27 -0
  39. package/dist/lib/sandbox/worktree-safety.js +215 -2
  40. package/dist/lib/task/codex-orchestration.js +161 -39
  41. package/dist/lib/task/commands/status.js +59 -1
  42. package/dist/lib/task/commit-operation.js +335 -0
  43. package/dist/lib/task/commit-policy.js +18 -0
  44. package/dist/lib/task/delegation-receipts.js +248 -6
  45. package/dist/lib/task/finalization.js +593 -0
  46. package/dist/lib/task/index.js +0 -9
  47. package/dist/lib/task/operation-outcome.js +33 -0
  48. package/dist/lib/task/orchestration.js +187 -761
  49. package/dist/lib/task/short-id.js +59 -1
  50. package/dist/lib/task/task-execution-lock.js +6 -1
  51. package/dist/lib/task/verification-engine.js +110 -60
  52. package/dist/lib/task/verification.js +2 -1
  53. package/dist/lib/task/workflow-warning-intents.js +50 -1
  54. package/dist/lifecycle-build-manifest.json +3 -3
  55. package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +61 -19
  56. package/lib/agent-clients/adapters/codex-lifecycle/build-identity.ts +37 -10
  57. package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +276 -12
  58. package/lib/agent-clients/adapters/codex-lifecycle/controller-context.ts +163 -0
  59. package/lib/agent-clients/adapters/codex-lifecycle/evidence.ts +19 -0
  60. package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +9 -1
  61. package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +91 -118
  62. package/lib/agent-clients/adapters/codex-sandbox.ts +89 -14
  63. package/lib/agent-clients/adapters/index.ts +3 -1
  64. package/lib/agent-clients/adapters/traecli.ts +67 -0
  65. package/lib/agent-clients/tokens.ts +2 -2
  66. package/lib/agent-clients/types.ts +2 -1
  67. package/lib/defaults.json +5 -0
  68. package/lib/git/workflow.ts +11 -2
  69. package/lib/internal/codex-lifecycle.ts +19 -6
  70. package/lib/internal/codex-sandbox-controller.ts +9 -14
  71. package/lib/internal/git-workflow.ts +5 -6
  72. package/lib/internal/platform-pr.ts +14 -7
  73. package/lib/internal/release-workflow.ts +2 -1
  74. package/lib/internal/sandbox-control.ts +92 -2
  75. package/lib/internal/task-finalization.ts +68 -0
  76. package/lib/internal/task-orchestration.ts +20 -103
  77. package/lib/{task/commands/validate.ts → internal/task-validate.ts} +75 -40
  78. package/lib/platform/pr-summary.ts +55 -11
  79. package/lib/platform/pull-requests.ts +211 -41
  80. package/lib/run/tui.ts +5 -3
  81. package/lib/sandbox/commands/rm.ts +413 -61
  82. package/lib/sandbox/control/client.ts +255 -6
  83. package/lib/sandbox/control/controller-registration.ts +352 -0
  84. package/lib/sandbox/control/executor.ts +169 -5
  85. package/lib/sandbox/control/lifecycle.ts +2 -1
  86. package/lib/sandbox/control/protocol.ts +80 -7
  87. package/lib/sandbox/index.ts +5 -3
  88. package/lib/sandbox/managed-fs.ts +5 -0
  89. package/lib/sandbox/task-resolver.ts +58 -7
  90. package/lib/sandbox/workspace-identity.ts +50 -2
  91. package/lib/sandbox/workspace-view.ts +3 -3
  92. package/lib/sandbox/worktree-safety.ts +236 -2
  93. package/lib/task/codex-orchestration.ts +168 -40
  94. package/lib/task/commands/status.ts +78 -1
  95. package/lib/task/commit-operation.ts +430 -0
  96. package/lib/task/commit-policy.ts +30 -0
  97. package/lib/task/delegation-receipts.ts +263 -4
  98. package/lib/task/finalization.ts +761 -0
  99. package/lib/task/index.ts +0 -8
  100. package/lib/task/operation-outcome.ts +48 -0
  101. package/lib/task/orchestration.ts +218 -982
  102. package/lib/task/short-id.ts +65 -1
  103. package/lib/task/task-execution-lock.ts +10 -1
  104. package/lib/task/verification-engine.ts +115 -63
  105. package/lib/task/verification.ts +3 -2
  106. package/lib/task/workflow-warning-intents.ts +57 -2
  107. package/package.json +2 -2
  108. package/templates/.agents/QUICKSTART.en.md +3 -4
  109. package/templates/.agents/QUICKSTART.zh-CN.md +3 -4
  110. package/templates/.agents/hooks/lifecycle-delegation.js +1 -21
  111. package/templates/.agents/rules/README.en.md +1 -0
  112. package/templates/.agents/rules/README.zh-CN.md +1 -0
  113. package/templates/.agents/rules/compatibility-policy.en.md +39 -0
  114. package/templates/.agents/rules/compatibility-policy.zh-CN.md +39 -0
  115. package/templates/.agents/rules/lifecycle-orchestration.en.md +4 -4
  116. package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +4 -4
  117. package/templates/.agents/rules/pr-sync.en.md +1 -1
  118. package/templates/.agents/rules/pr-sync.github.en.md +2 -1
  119. package/templates/.agents/rules/pr-sync.github.zh-CN.md +2 -0
  120. package/templates/.agents/rules/pr-sync.zh-CN.md +1 -0
  121. package/templates/.agents/rules/review-method.en.md +1 -0
  122. package/templates/.agents/rules/review-method.zh-CN.md +1 -0
  123. package/templates/.agents/rules/task-management.en.md +2 -2
  124. package/templates/.agents/rules/task-management.zh-CN.md +2 -2
  125. package/templates/.agents/skills/analyze-task/SKILL.en.md +1 -0
  126. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +1 -0
  127. package/templates/.agents/skills/block-task/SKILL.en.md +2 -4
  128. package/templates/.agents/skills/block-task/SKILL.zh-CN.md +2 -4
  129. package/templates/.agents/skills/cancel-task/SKILL.en.md +3 -3
  130. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +3 -3
  131. package/templates/.agents/skills/close-codescan/SKILL.en.md +6 -2
  132. package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +6 -2
  133. package/templates/.agents/skills/close-dependabot/SKILL.en.md +6 -2
  134. package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +6 -2
  135. package/templates/.agents/skills/code-task/SKILL.en.md +1 -0
  136. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +2 -0
  137. package/templates/.agents/skills/commit/SKILL.en.md +41 -125
  138. package/templates/.agents/skills/commit/SKILL.zh-CN.md +52 -123
  139. package/templates/.agents/skills/commit/reference/commit-message.en.md +1 -1
  140. package/templates/.agents/skills/commit/reference/commit-message.zh-CN.md +1 -1
  141. package/templates/.agents/skills/commit/reference/commit-orchestration.en.md +14 -43
  142. package/templates/.agents/skills/commit/reference/commit-orchestration.zh-CN.md +14 -49
  143. package/templates/.agents/skills/commit/reference/pr-summary-sync.en.md +1 -1
  144. package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +1 -1
  145. package/templates/.agents/skills/commit/reference/task-status-update.en.md +2 -35
  146. package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +2 -35
  147. package/templates/.agents/skills/complete-task/SKILL.en.md +31 -38
  148. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +31 -38
  149. package/templates/.agents/skills/complete-task/config/verify.en.json +1 -0
  150. package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +1 -0
  151. package/templates/.agents/skills/complete-task/reference/external-delivery.en.md +1 -1
  152. package/templates/.agents/skills/complete-task/reference/external-delivery.zh-CN.md +1 -1
  153. package/templates/.agents/skills/create-pr/SKILL.en.md +17 -9
  154. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +17 -9
  155. package/templates/.agents/skills/create-pr/reference/change-report.en.md +41 -0
  156. package/templates/.agents/skills/create-pr/reference/change-report.zh-CN.md +41 -0
  157. package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +2 -2
  158. package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +2 -2
  159. package/templates/.agents/skills/plan-task/SKILL.en.md +2 -1
  160. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +2 -1
  161. package/templates/.agents/skills/review-code/reference/migration-risks.en.md +8 -6
  162. package/templates/.agents/skills/review-code/reference/migration-risks.zh-CN.md +8 -6
  163. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +3 -3
  164. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +3 -3
  165. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +1 -1
  166. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +1 -1
  167. package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +2 -2
  168. package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +2 -2
  169. package/templates/.agents/skills/run-task/SKILL.en.md +6 -6
  170. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +6 -6
  171. package/templates/.agents/skills/run-task/reference/host-validation.en.md +2 -2
  172. package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +2 -2
  173. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +24 -0
  174. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +1 -1
  175. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +1 -1
  176. package/templates/.codex/hooks.json +4 -4
  177. package/templates/.trae/skills/update-agent-infra.en.md +8 -0
  178. package/templates/.trae/skills/update-agent-infra.zh-CN.md +8 -0
  179. package/dist/lib/task/commit-finalization.js +0 -305
  180. package/dist/lib/task/commit-intent.js +0 -165
  181. package/lib/task/commit-finalization.ts +0 -419
  182. package/lib/task/commit-intent.ts +0 -230
@@ -343,6 +343,64 @@ function loadShortIdByTaskId(repoRoot) {
343
343
  }
344
344
  return map;
345
345
  }
346
+ function inspectShortIdRegistry(repoRoot) {
347
+ const registryPath = path.join(repoRoot, '.agents', 'workspace', 'active', REGISTRY_NAME);
348
+ if (!fs.existsSync(registryPath)) {
349
+ return {
350
+ status: 'missing',
351
+ error: {
352
+ code: 'SHORT_ID_REGISTRY_NOT_FOUND',
353
+ message: 'canonical short-id registry is missing'
354
+ }
355
+ };
356
+ }
357
+ let raw;
358
+ try {
359
+ raw = fs.readFileSync(registryPath, 'utf8');
360
+ }
361
+ catch {
362
+ return {
363
+ status: 'invalid',
364
+ error: {
365
+ code: 'SHORT_ID_REGISTRY_READ_FAILED',
366
+ message: 'canonical short-id registry is unreadable'
367
+ }
368
+ };
369
+ }
370
+ let parsed;
371
+ try {
372
+ parsed = JSON.parse(raw);
373
+ }
374
+ catch {
375
+ return {
376
+ status: 'invalid',
377
+ error: {
378
+ code: 'SHORT_ID_REGISTRY_INVALID_JSON',
379
+ message: 'canonical short-id registry contains invalid JSON'
380
+ }
381
+ };
382
+ }
383
+ try {
384
+ const registry = validateRegistry(parsed, registryPath, configuredShortIdLength(repoRoot));
385
+ const shortIds = new Map();
386
+ for (const [key, taskId] of Object.entries(registry.ids))
387
+ shortIds.set(taskId, key);
388
+ return { status: 'valid', shortIds };
389
+ }
390
+ catch (error) {
391
+ return {
392
+ status: 'invalid',
393
+ error: {
394
+ code: typeof error.code === 'string'
395
+ ? error.code
396
+ : 'SHORT_ID_REGISTRY_INVALID_SCHEMA',
397
+ message: error.code === 'SHORT_ID_REGISTRY_DUPLICATE_TASK'
398
+ ? 'canonical short-id registry contains duplicate task entries'
399
+ : 'canonical short-id registry has invalid schema'
400
+ }
401
+ };
402
+ }
403
+ }
346
404
  /**
347
405
  * Resolve a branch to its active-task short id (`NN`), or `null` when no
348
406
  * active task is bound to that branch.
@@ -371,5 +429,5 @@ function lookupShortIdByBranch(branch, repoRoot, _opts) {
371
429
  }
372
430
  return matches[0];
373
431
  }
374
- export { normalizeShortIdInput, isRemovedHashShortIdInput, resolveShortIdReadOnly, lookupShortIdByBranch, loadShortIdByTaskId, configuredShortIdLength, mutateShortIdRegistry, executeShortIdCommand };
432
+ export { normalizeShortIdInput, isRemovedHashShortIdInput, resolveShortIdReadOnly, lookupShortIdByBranch, loadShortIdByTaskId, inspectShortIdRegistry, configuredShortIdLength, mutateShortIdRegistry, executeShortIdCommand };
375
433
  //# sourceMappingURL=short-id.js.map
@@ -207,6 +207,8 @@ function withTaskExecutionLock(repoRoot, taskId, ownerName, callback, options =
207
207
  catch (error) {
208
208
  if (error instanceof TaskExecutionLockError)
209
209
  throw error;
210
+ if (error instanceof Error && error.name === 'OrchestrationStateError')
211
+ throw error;
210
212
  throw new TaskExecutionLockError('ORCHESTRATION_LOCK_FAILED', 'lifecycle task lock operation failed', { operation: ownsFixed ? 'callback' : 'acquire', errno: errno(error), key });
211
213
  }
212
214
  finally {
@@ -228,5 +230,8 @@ function withTaskExecutionLock(repoRoot, taskId, ownerName, callback, options =
228
230
  }
229
231
  }
230
232
  }
231
- export { TaskExecutionLockError, lockKey, mapLinkError, withTaskExecutionLock };
233
+ function withRepositoryMutationLock(repoRoot, callback, options = {}) {
234
+ return withTaskExecutionLock(repoRoot, '__repository_mutation__', 'repository-mutation', callback, options);
235
+ }
236
+ export { TaskExecutionLockError, lockKey, mapLinkError, withRepositoryMutationLock, withTaskExecutionLock };
232
237
  //# sourceMappingURL=task-execution-lock.js.map
@@ -16,6 +16,7 @@ import { parseLedger, summarizeLedgerStage, validateLedgerRows } from "./ledger.
16
16
  import { equalCounts, parseReviewSummary } from "./review-artifacts.js";
17
17
  import { loadVerificationConfig } from "./verification-config.js";
18
18
  import { snapshotReview } from "../git/review-snapshot.js";
19
+ import { OrchestrationStateError, readRun } from "./orchestration.js";
19
20
  const TASK_ENUMS = {
20
21
  type: ["feature", "bugfix", "refactor", "docs", "chore"],
21
22
  workflow: ["feature-development", "bug-fix", "refactoring"],
@@ -104,6 +105,8 @@ function runCheck(type, context, shared) {
104
105
  return checkReviewFact(context);
105
106
  case "post-review-commit":
106
107
  return checkPostReviewCommit(context);
108
+ case "required-pr-delivery":
109
+ return checkRequiredPrDelivery(context);
107
110
  case "orchestration-state":
108
111
  return checkOrchestrationState(context);
109
112
  case "orchestration-evidence":
@@ -120,22 +123,6 @@ function runCheck(type, context, shared) {
120
123
  }
121
124
  }
122
125
  }
123
- function isFailClosedLegacyPause(run) {
124
- if (run.schemaVersion !== 1
125
- || run.status !== 'paused'
126
- || run.pause?.recoverable !== false
127
- || !exactText(run.pause?.message)
128
- || !Array.isArray(run.receipts)) {
129
- return false;
130
- }
131
- if (run.pause.code === 'ORCHESTRATION_HISTORICAL_EFFORT_UNVERIFIED') {
132
- return run.receipts.length > 0 && run.pendingDelegation === null;
133
- }
134
- if (run.pause.code === 'ORCHESTRATION_DELEGATION_BUSY') {
135
- return run.pendingDelegation != null && run.pendingDelegation.status !== 'sealed';
136
- }
137
- return false;
138
- }
139
126
  function checkOrchestrationState({ taskDir }) {
140
127
  const file = path.join(taskDir, 'orchestration.json');
141
128
  const stat = safeStat(file);
@@ -143,16 +130,14 @@ function checkOrchestrationState({ taskDir }) {
143
130
  return failResult('orchestration-state', 'orchestration.json is missing');
144
131
  let run;
145
132
  try {
146
- run = JSON.parse(fs.readFileSync(file, 'utf8'));
133
+ run = readRun(taskDir);
147
134
  }
148
135
  catch (error) {
149
- return failResult('orchestration-state', `Invalid orchestration.json: ${String(error)}`);
136
+ const message = error instanceof OrchestrationStateError ? error.message : String(error);
137
+ return failResult('orchestration-state', `Invalid orchestration.json: ${message}`);
150
138
  }
151
- if (isFailClosedLegacyPause(run)) {
152
- return passResult('orchestration-state', 'Legacy orchestration run is safely paused');
153
- }
154
- if (![2, 3].includes(run.schemaVersion) || !['paused', 'completed'].includes(run.status)) {
155
- return failResult('orchestration-state', `Expected paused or completed run, received '${run.status}'`);
139
+ if (!run || !['paused', 'completed'].includes(run.status)) {
140
+ return failResult('orchestration-state', `Expected paused or completed run, received '${run?.status ?? 'missing'}'`);
156
141
  }
157
142
  if (run.status === 'paused' && (!run.pause?.code || !run.pause?.message)) {
158
143
  return failResult('orchestration-state', 'Paused run requires a stable pause code and message');
@@ -215,13 +200,11 @@ function checkOrchestrationEvidence({ taskDir }) {
215
200
  return failResult('orchestration-evidence', 'orchestration.json is missing');
216
201
  let run;
217
202
  try {
218
- run = JSON.parse(fs.readFileSync(file, 'utf8'));
203
+ run = readRun(taskDir);
219
204
  }
220
205
  catch (error) {
221
- return failResult('orchestration-evidence', `Invalid orchestration.json: ${String(error)}`);
222
- }
223
- if (isFailClosedLegacyPause(run)) {
224
- return passResult('orchestration-evidence', 'Legacy orchestration evidence is preserved in a fail-closed pause');
206
+ const message = error instanceof OrchestrationStateError ? error.message : String(error);
207
+ return failResult('orchestration-evidence', `Invalid orchestration.json: ${message}`);
225
208
  }
226
209
  const policy = run.modelPolicy;
227
210
  if (!policy
@@ -239,30 +222,8 @@ function checkOrchestrationEvidence({ taskDir }) {
239
222
  return failResult('orchestration-evidence', 'Run model policy source or recovery history is invalid');
240
223
  }
241
224
  for (const recovery of run.recoveryHistory) {
242
- const validModelRecovery = recovery.code === 'MODEL_POLICY_SUPPLEMENTED'
243
- && recovery.previousSchemaVersion === 1
244
- && recovery.receiptCount === 0
245
- && recovery.pendingDelegation === false
246
- && exactText(recovery.recoveredAt)
247
- && recovery.policySource;
248
225
  const guards = recovery.guards;
249
- const validCapabilityRecovery = recovery.code === 'CLIENT_CAPABILITY_ENABLED'
250
- && recovery.previousSchemaVersion === 2
251
- && recovery.previousStatus === 'paused'
252
- && recovery.previousPause?.code === 'ORCHESTRATION_CLIENT_UNSUPPORTED'
253
- && recovery.client === 'codex'
254
- && recovery.resultingStatus === 'running'
255
- && exactText(recovery.recoveredAt)
256
- && guards?.stepCount === 0
257
- && guards?.nextStage === null
258
- && guards?.baselineEmpty === true
259
- && guards?.receiptCount === 0
260
- && guards?.pendingDelegation === false
261
- && guards?.commitAuthorizationUnused === true
262
- && guards?.completionEvidenceAbsent === true
263
- && guards?.commitIntentAbsent === true;
264
- const validClaudeCodeRecovery = recovery.code === 'CLIENT_CAPABILITY_ENABLED_NO_MIGRATION'
265
- && recovery.previousSchemaVersion === 3
226
+ const validClaudeCodeRecovery = recovery.code === 'CLIENT_CAPABILITY_ENABLED'
266
227
  && recovery.previousStatus === 'paused'
267
228
  && recovery.previousPause?.code === 'ORCHESTRATION_CLIENT_UNSUPPORTED'
268
229
  && recovery.client === 'claude-code'
@@ -274,9 +235,8 @@ function checkOrchestrationEvidence({ taskDir }) {
274
235
  && guards?.receiptCount === 0
275
236
  && guards?.pendingDelegation === false
276
237
  && guards?.commitAuthorizationUnused === true
277
- && guards?.completionEvidenceAbsent === true
278
- && guards?.commitIntentAbsent === true;
279
- if (!validModelRecovery && !validCapabilityRecovery && !validClaudeCodeRecovery) {
238
+ && guards?.completionEvidenceAbsent === true;
239
+ if (!validClaudeCodeRecovery) {
280
240
  return failResult('orchestration-evidence', 'Run recovery history contains invalid provenance');
281
241
  }
282
242
  }
@@ -342,11 +302,31 @@ function checkOrchestrationEvidence({ taskDir }) {
342
302
  return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' has an unrelated reasoning-effort fallback reason`);
343
303
  }
344
304
  if (receipt.client === 'codex') {
305
+ const activatedCodex = ['activated', 'stage-completed', 'sealed', 'consumed'].includes(receipt.status);
345
306
  const host = receipt.hostEvidence;
346
- if (host?.kind !== 'codex-lifecycle-v1'
347
- || !exactText(host.hookDefinitionHash)
348
- || !Number.isSafeInteger(host.startRevision)
349
- || host.startRevision < 1) {
307
+ const provenance = receipt.lifecycleProvenance;
308
+ if (!provenance
309
+ || (activatedCodex && (host?.kind !== 'codex-lifecycle-v2'
310
+ || host.protocolVersion !== provenance.protocolVersion
311
+ || host.hookDefinitionHash !== provenance.hookDefinitionHash
312
+ || host.hookSource !== provenance.hookSource
313
+ || host.hookSourcePathDigest !== provenance.hookSourcePathDigest
314
+ || host.hookSourceHash !== provenance.hookSourceHash
315
+ || host.capabilitySessionId !== provenance.capabilitySessionId
316
+ || receipt.parentId !== provenance.capabilitySessionId
317
+ || host.capabilityTurnId !== provenance.capabilityTurnId
318
+ || host.controllerInstanceDigest !== provenance.controllerInstanceDigest
319
+ || host.controlGeneration !== provenance.controlGeneration
320
+ || host.spawnToolUseId === provenance.capabilityToolUseId
321
+ || !exactText(host.spawnToolUseId)
322
+ || !Number.isFinite(Date.parse(host.spawnObservedAt ?? ''))
323
+ || !Number.isFinite(Date.parse(receipt.spawnDispatchedAt ?? ''))
324
+ || !Number.isFinite(Date.parse(receipt.activationDeadlineAt ?? ''))
325
+ || Date.parse(host.spawnObservedAt ?? '') < Date.parse(receipt.spawnDispatchedAt ?? '')
326
+ || Date.parse(host.spawnObservedAt ?? '') > Date.parse(receipt.activationDeadlineAt ?? '')
327
+ || !exactText(host.hookDefinitionHash)
328
+ || !Number.isSafeInteger(host.startRevision)
329
+ || host.startRevision < 1))) {
350
330
  return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' has invalid Codex start evidence`);
351
331
  }
352
332
  if (['sealed', 'consumed'].includes(receipt.status) && (!Number.isSafeInteger(host.stopRevision)
@@ -438,6 +418,60 @@ function checkTaskMeta({ taskDir, config }) {
438
418
  const warningSuffix = warnings.length > 0 ? `; warnings: ${warnings.join("; ")}` : "";
439
419
  return passResult("task-meta", `Task metadata valid (${requiredFields.length} required fields checked${warningSuffix})`, warnings);
440
420
  }
421
+ function checkRequiredPrDelivery({ taskDir, repositoryRoot, mode }) {
422
+ const task = loadTask(taskDir);
423
+ if (!task.ok)
424
+ return failResult('required-pr-delivery', task.message);
425
+ let projectConfig = {};
426
+ try {
427
+ const file = path.join(repositoryRoot, '.agents', '.airc.json');
428
+ if (safeStat(file)?.isFile())
429
+ projectConfig = JSON.parse(fs.readFileSync(file, 'utf8'));
430
+ }
431
+ catch {
432
+ return failResult('required-pr-delivery', 'Project configuration is invalid');
433
+ }
434
+ const flow = projectConfig.prFlow;
435
+ const status = String(task.metadata.pr_status || 'pending');
436
+ const prNumber = parsePrNumber(task.metadata.pr_number);
437
+ if (flow === 'disabled')
438
+ return passResult('required-pr-delivery', 'Pull request delivery is disabled by project policy');
439
+ if (flow === 'required' && (status !== 'created' || prNumber === null)) {
440
+ return failResult('required-pr-delivery', 'Project policy requires a bound pull request before completion');
441
+ }
442
+ if (flow !== 'required' && status === 'pending') {
443
+ return failResult('required-pr-delivery', 'Pull request delivery is pending; create a PR or explicitly skip it');
444
+ }
445
+ if (status === 'created' && prNumber === null) {
446
+ return failResult('required-pr-delivery', 'Task claims a created pull request without a valid pr_number');
447
+ }
448
+ if (flow === 'required') {
449
+ if (mode === 'gate') {
450
+ return passResult('required-pr-delivery', 'Bound pull request shape is valid; merged-state inspection is reserved for hard preflight');
451
+ }
452
+ const inspected = inspectPlatformPullRequest(task.metadata.id, { cwd: repositoryRoot });
453
+ if (inspected.status === 'blocked') {
454
+ return blockedResult('required-pr-delivery', inspected.error?.message || 'Unable to inspect the required pull request', inspected.error?.code || 'PR_INSPECTION_BLOCKED');
455
+ }
456
+ const pullRequest = inspected.pullRequest;
457
+ const repository = inspected.platform.repository?.toLowerCase() || '';
458
+ const taskBranch = String(task.metadata.branch || '').trim();
459
+ if (inspected.status !== 'no-op' || !pullRequest || pullRequest.number !== prNumber) {
460
+ return failResult('required-pr-delivery', 'Bound pull request identity could not be verified');
461
+ }
462
+ if (!repository
463
+ || pullRequest.repository.toLowerCase() !== repository
464
+ || pullRequest.base.repository.toLowerCase() !== repository
465
+ || (taskBranch && pullRequest.head.ref !== taskBranch)) {
466
+ return failResult('required-pr-delivery', 'Bound pull request identity does not match the task');
467
+ }
468
+ if (pullRequest.state !== 'closed' || !pullRequest.mergedAt || !pullRequest.mergeCommitSha) {
469
+ return failResult('required-pr-delivery', 'Project policy requires the bound pull request to be merged');
470
+ }
471
+ return passResult('required-pr-delivery', `Pull request delivery policy satisfied (merged PR #${prNumber})`);
472
+ }
473
+ return passResult('required-pr-delivery', `Pull request delivery policy satisfied (${status})`);
474
+ }
441
475
  function validateTaskBranch(metadata) {
442
476
  if (isBlank(metadata.branch)) {
443
477
  return null;
@@ -1337,7 +1371,14 @@ function verifyInProcess({ mode, skillName, taskDir, artifactFile, checks: reque
1337
1371
  for (const [type, checkConfig] of Object.entries(verifyConfig.checks || {})) {
1338
1372
  if (checkConfig === null)
1339
1373
  continue;
1340
- const result = runCheck(type, { skillName, taskDir: path.resolve(taskDir), artifactFile, config: checkConfig }, shared);
1374
+ const result = runCheck(type, {
1375
+ mode,
1376
+ skillName,
1377
+ taskDir: path.resolve(taskDir),
1378
+ artifactFile,
1379
+ repositoryRoot: repoRoot,
1380
+ config: checkConfig
1381
+ }, shared);
1341
1382
  checks.push(result);
1342
1383
  if (result.status === "blocked")
1343
1384
  break;
@@ -1351,7 +1392,16 @@ function verifyInProcess({ mode, skillName, taskDir, artifactFile, checks: reque
1351
1392
  return { skill: skillName, ...failResult(type, `Unknown check type '${type}' for skill '${skillName}'.`) };
1352
1393
  if (config === null)
1353
1394
  return { skill: skillName, ...passResult(type, `Check '${type}' is disabled for skill '${skillName}'.`) };
1354
- return { skill: skillName, ...runCheck(type, { skillName, taskDir: path.resolve(taskDir), artifactFile, config }, shared) };
1395
+ return {
1396
+ skill: skillName,
1397
+ ...runCheck(type, {
1398
+ skillName,
1399
+ taskDir: path.resolve(taskDir),
1400
+ artifactFile,
1401
+ repositoryRoot: repoRoot,
1402
+ config
1403
+ }, shared)
1404
+ };
1355
1405
  }
1356
1406
  export { verifyInProcess };
1357
1407
  //# sourceMappingURL=verification-engine.js.map
@@ -21,7 +21,8 @@ const VERIFICATION_CATALOG = {
21
21
  'block-task.completed': gate('block-task', 'blocked'),
22
22
  'cancel-task.completed': gate('cancel-task', 'completed'),
23
23
  'commit.completed': gate('commit', 'active'),
24
- 'complete-task.preflight': { skill: 'complete-task', expectedState: 'active', mode: 'checks', checks: ['review-ledger', 'manual-validation', 'post-review-commit', 'platform-sync-preflight'] },
24
+ 'complete-task.preflight': { skill: 'complete-task', expectedState: 'active', mode: 'checks', checks: ['required-pr-delivery'] },
25
+ 'complete-task.hard-preflight': { skill: 'complete-task', expectedState: 'active', mode: 'checks', checks: ['required-pr-delivery'] },
25
26
  'complete-task.completed': gate('complete-task', 'completed'),
26
27
  'create-pr.completed': gate('create-pr', 'active'),
27
28
  'create-task.completed': gate('create-task', 'active'),
@@ -3,6 +3,7 @@ import { resolveTaskRef } from './resolve-ref.js';
3
3
  import { WORKFLOW_WARNING_COLUMNS, WORKFLOW_WARNING_HEADINGS, WORKFLOW_WARNING_SEVERITIES, WORKFLOW_WARNING_STATUSES, parseWorkflowWarnings } from './workflow-warnings.js';
4
4
  import { captureTaskWriteMetadata, writeTask } from './write.js';
5
5
  import { allowsManualOverride } from './guard-override.js';
6
+ import { retryHintForWarning } from './operation-outcome.js';
6
7
  function failed(intent, code, message, taskId = null, entityId = null) {
7
8
  return { status: 'failed', changed: false, intent: intent.kind, taskId, entityId, before: null, after: null, warnings: [], operations: [], error: { code, message } };
8
9
  }
@@ -129,5 +130,53 @@ function applyWorkflowWarningIntent(intent, options = {}) {
129
130
  entityId: after.id, before, after, warnings: [], operations: writeResult.operations, error: null
130
131
  };
131
132
  }
132
- export { applyWorkflowWarningIntent };
133
+ function projectFinalizationWarning(taskRef, warning, options = {}) {
134
+ const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
135
+ if (!resolved.ok)
136
+ return { status: 'failed', changed: false, error: { code: resolved.code, message: resolved.message } };
137
+ if (resolved.state !== 'completed')
138
+ return { status: 'failed', changed: false, error: { code: 'TASK_STATE_MISMATCH', message: `task ${resolved.taskId} is ${resolved.state}, expected completed` } };
139
+ let content;
140
+ let rows;
141
+ try {
142
+ content = fs.readFileSync(resolved.taskMdPath, 'utf8');
143
+ rows = parseWorkflowWarnings(content);
144
+ }
145
+ catch (error) {
146
+ return { status: 'failed', changed: false, error: { code: 'WARNING_DOCUMENT_INVALID', message: error instanceof Error ? error.message : String(error) } };
147
+ }
148
+ const metadata = (options.metadataProvider ?? captureTaskWriteMetadata)();
149
+ const existing = rows.find((row) => row.step === warning.step && row.code === warning.code && row.target === warning.target);
150
+ const after = existing
151
+ ? {
152
+ ...existing,
153
+ severity: warning.severity,
154
+ status: warning.status,
155
+ message: warning.message,
156
+ action: retryHintForWarning(warning),
157
+ resolvedAt: warning.resolvedAt || (warning.status === 'resolved' ? metadata.timestamp : ''),
158
+ resolution: warning.status === 'resolved' ? 'Resolved by a successful finalization retry' : ''
159
+ }
160
+ : {
161
+ id: nextWarningId(rows), time: metadata.timestamp, step: warning.step,
162
+ severity: warning.severity, code: warning.code, status: warning.status,
163
+ target: warning.target, message: warning.message, action: retryHintForWarning(warning),
164
+ resolvedAt: warning.resolvedAt || '', resolution: warning.status === 'resolved' ? 'Resolved by a successful finalization retry' : ''
165
+ };
166
+ if (existing && JSON.stringify(existing) === JSON.stringify(after))
167
+ return { status: 'no-op', changed: false, error: null };
168
+ const written = writeTask({
169
+ taskRef: resolved.taskId,
170
+ expectedState: 'completed',
171
+ mutations: [...sectionMutation(content), rowMutation(after)]
172
+ }, {
173
+ ...options,
174
+ taskLocation: { repoRoot: resolved.repoRoot, taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, state: 'completed' },
175
+ metadataProvider: () => metadata
176
+ });
177
+ if (written.status === 'failed')
178
+ return { status: 'failed', changed: false, error: { code: written.error.code, message: written.error.message } };
179
+ return { status: written.status === 'applied' ? 'applied' : 'no-op', changed: written.changed, error: null };
180
+ }
181
+ export { applyWorkflowWarningIntent, projectFinalizationWarning };
133
182
  //# sourceMappingURL=workflow-warning-intents.js.map
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "protocolVersion": 3,
3
- "packageVersion": "0.9.8",
4
- "sourceInputHash": "fb481e6579519b3fd10c9ca9bf284d65498f935c034a14c047d023345abeb87c",
5
- "internalExecutableBuildHash": "d8dcdf602b61001ba9829aa2027cccadcc9c371ccf9e29c3d681fd02d5a3a5af"
3
+ "packageVersion": "0.9.10",
4
+ "sourceInputHash": "fae928c7a46686dd39f9f57424e3caff8aed3ce5de5700475b4a3a8f2954b3a3",
5
+ "internalExecutableBuildHash": "a5e143239a8fdd93c4696125aab04b28d7baf59c2a65488705fe930d4965f354"
6
6
  }
@@ -36,6 +36,10 @@ type AppServerTransportOptions = Readonly<{
36
36
  timeoutMs?: number;
37
37
  rolloutReadAttempts?: number;
38
38
  rolloutRetryMs?: number;
39
+ threadReadAttempts?: number;
40
+ threadRetryMs?: number;
41
+ terminalReadAttempts?: number;
42
+ terminalRetryMs?: number;
39
43
  }>;
40
44
 
41
45
  type CodexRuntimeIdentity = Readonly<{
@@ -52,6 +56,10 @@ const LIFECYCLE_HOOKS = Object.freeze([
52
56
  ]);
53
57
  const DEFAULT_ROLLOUT_READ_ATTEMPTS = 81;
54
58
  const DEFAULT_ROLLOUT_RETRY_MS = 100;
59
+ const DEFAULT_THREAD_READ_ATTEMPTS = 21;
60
+ const DEFAULT_THREAD_RETRY_MS = 100;
61
+ const DEFAULT_TERMINAL_READ_ATTEMPTS = 21;
62
+ const DEFAULT_TERMINAL_RETRY_MS = 100;
55
63
 
56
64
  function object(value: unknown): JsonObject | null {
57
65
  return value && typeof value === 'object' && !Array.isArray(value)
@@ -67,6 +75,7 @@ function resolveCodexSpawnedChild(
67
75
  transcriptPath: string,
68
76
  expected: Readonly<{
69
77
  sessionId: string;
78
+ turnId: string;
70
79
  toolUseId: string;
71
80
  nativeAgent: string;
72
81
  taskName: string;
@@ -111,15 +120,19 @@ function resolveCodexSpawnedChild(
111
120
  ) throw new Error('Codex parent rollout spawn identity does not match the hook event');
112
121
  const activities = records.filter((record) => {
113
122
  const payload = object(record?.payload);
123
+ const item = object(payload?.item);
114
124
  return record?.type === 'event_msg'
115
- && payload?.type === 'sub_agent_activity'
116
- && payload.event_id === expected.toolUseId
117
- && payload.kind === 'started'
118
- && payload.agent_path === `/root/${expected.taskName}`
119
- && nonEmpty(payload.agent_thread_id);
125
+ && payload?.type === 'item_completed'
126
+ && payload.thread_id === expected.sessionId
127
+ && payload.turn_id === expected.turnId
128
+ && item?.type === 'SubAgentActivity'
129
+ && item.id === expected.toolUseId
130
+ && item.kind === 'started'
131
+ && item.agent_path === `/root/${expected.taskName}`
132
+ && nonEmpty(item.agent_thread_id);
120
133
  });
121
134
  if (activities.length !== 1) throw new Error('Codex parent rollout child activity was not found uniquely');
122
- return nonEmpty(object(activities[0]?.payload)?.agent_thread_id)!;
135
+ return nonEmpty(object(object(activities[0]?.payload)?.item)?.agent_thread_id)!;
123
136
  }
124
137
 
125
138
  function validateCodexLifecycleHookConfig(value: unknown): void {
@@ -133,7 +146,7 @@ function validateCodexLifecycleHookConfig(value: unknown): void {
133
146
  return entry.hooks.some((hookValue) => {
134
147
  const hook = object(hookValue);
135
148
  return hook?.type === 'command'
136
- && hook.timeout === 15
149
+ && hook.timeout === 30
137
150
  && hook.command === `node "$(git rev-parse --show-toplevel)/.agents/hooks/lifecycle-delegation.js" --client codex --event ${phase}`;
138
151
  });
139
152
  });
@@ -478,18 +491,30 @@ async function resolveCodexThread(
478
491
  const readResult = object(await transport.request('thread/read', { threadId: childThreadId, includeTurns: false }));
479
492
  const readThread = object(readResult?.thread);
480
493
  if (nonEmpty(readThread?.id) !== childThreadId) throw new Error('Codex App Server returned the wrong child thread');
481
- let resolution: ThreadResolution;
494
+ let rolloutRecords: readonly unknown[];
482
495
  try {
483
- const rolloutRecords = await resolveCodexRolloutRecords(
496
+ rolloutRecords = await resolveCodexRolloutRecords(
484
497
  readResult,
485
498
  childThreadId,
486
499
  options.rolloutReadAttempts ?? DEFAULT_ROLLOUT_READ_ATTEMPTS,
487
500
  options.rolloutRetryMs ?? DEFAULT_ROLLOUT_RETRY_MS
488
501
  );
489
- resolution = parseCodexThreadResolution(readResult, rolloutRecords);
490
502
  } catch {
491
503
  throw new Error('Codex App Server rollout metadata is unavailable');
492
504
  }
505
+ let resolution = parseCodexThreadResolution(readResult, rolloutRecords);
506
+ const threadReadAttempts = options.threadReadAttempts ?? DEFAULT_THREAD_READ_ATTEMPTS;
507
+ for (let attempt = 1; resolution.thread.forkedFromId !== null && attempt < threadReadAttempts; attempt += 1) {
508
+ await new Promise((resolve) => setTimeout(resolve, options.threadRetryMs ?? DEFAULT_THREAD_RETRY_MS));
509
+ const refreshed = object(await transport.request('thread/read', { threadId: childThreadId, includeTurns: false }));
510
+ if (nonEmpty(object(refreshed?.thread)?.id) !== childThreadId) {
511
+ throw new Error('Codex App Server returned the wrong child thread');
512
+ }
513
+ resolution = parseCodexThreadResolution(refreshed, rolloutRecords);
514
+ }
515
+ if (resolution.thread.forkedFromId !== null) {
516
+ throw new Error('Codex App Server fresh thread metadata is unavailable');
517
+ }
493
518
  const reroutes = transport.notifications
494
519
  .filter((entry) => entry.method === 'model/rerouted')
495
520
  .map((entry) => parseCodexModelReroute(entry.params))
@@ -507,20 +532,37 @@ async function resolveCodexThread(
507
532
 
508
533
  async function resolveCodexTerminal(
509
534
  childThreadId: string,
510
- options: AppServerTransportOptions = {}
535
+ expectedTurnIdOrOptions: string | AppServerTransportOptions = {},
536
+ terminalOptions: AppServerTransportOptions = {}
511
537
  ): Promise<Extract<CodexLifecycleEvent, { type: 'app-terminal' }>> {
538
+ const expectedTurnId = typeof expectedTurnIdOrOptions === 'string' ? expectedTurnIdOrOptions : null;
539
+ const options = typeof expectedTurnIdOrOptions === 'string' ? terminalOptions : expectedTurnIdOrOptions;
512
540
  const transport = new CodexAppServerTransport(options);
513
541
  transport.start();
514
542
  try {
515
543
  await transport.initialize();
516
- const result = object(await transport.request('thread/read', { threadId: childThreadId, includeTurns: true }));
517
- const thread = object(result?.thread);
518
- if (nonEmpty(thread?.id) !== childThreadId) throw new Error('Codex App Server returned the wrong child thread');
519
- const turns = Array.isArray(thread?.turns) ? thread.turns : [];
520
- const turn = turns.at(-1);
521
- const turnValue = object(turn);
522
- if (!turnValue || turnValue.status === 'inProgress') throw new Error('CODEX_TURN_NOT_TERMINAL');
523
- return parseCodexTurnCompleted({ threadId: childThreadId, turn });
544
+ const attempts = expectedTurnId ? options.terminalReadAttempts ?? DEFAULT_TERMINAL_READ_ATTEMPTS : 1;
545
+ let abnormal: Extract<CodexLifecycleEvent, { type: 'app-terminal' }> | null = null;
546
+ for (let attempt = 0; attempt < attempts; attempt += 1) {
547
+ const result = object(await transport.request('thread/read', { threadId: childThreadId, includeTurns: true }));
548
+ const thread = object(result?.thread);
549
+ if (nonEmpty(thread?.id) !== childThreadId) throw new Error('Codex App Server returned the wrong child thread');
550
+ const turns = Array.isArray(thread?.turns) ? thread.turns : [];
551
+ const turn = expectedTurnId
552
+ ? turns.find((candidate) => object(candidate)?.id === expectedTurnId)
553
+ : turns.at(-1);
554
+ const turnValue = object(turn);
555
+ if (turnValue && turnValue.status !== 'inProgress') {
556
+ const terminal = parseCodexTurnCompleted({ threadId: childThreadId, turn });
557
+ if (terminal.status === 'completed') return terminal;
558
+ abnormal = terminal;
559
+ }
560
+ if (attempt + 1 < attempts) {
561
+ await new Promise((resolve) => setTimeout(resolve, options.terminalRetryMs ?? DEFAULT_TERMINAL_RETRY_MS));
562
+ }
563
+ }
564
+ if (abnormal) return abnormal;
565
+ throw new Error('CODEX_TURN_NOT_TERMINAL');
524
566
  } finally {
525
567
  transport.close();
526
568
  }