@fitlab-ai/agent-infra 0.11.2 → 0.11.4

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 (162) hide show
  1. package/README.md +17 -8
  2. package/README.zh-CN.md +17 -8
  3. package/dist/bin/internal-cli.js +7 -2
  4. package/dist/lib/agent-clients/adapter.js +3 -0
  5. package/dist/lib/agent-clients/adapters/codex-lifecycle/recovery.js +234 -0
  6. package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +8 -35
  7. package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +1 -34
  8. package/dist/lib/agent-clients/adapters/codex.js +10 -0
  9. package/dist/lib/internal/cli-route-inventory.js +1 -1
  10. package/dist/lib/internal/manual-validation.js +22 -27
  11. package/dist/lib/internal/sandbox-control.js +1 -43
  12. package/dist/lib/internal/task-artifact.js +1 -1
  13. package/dist/lib/internal/task-finalization.js +41 -1
  14. package/dist/lib/internal/task-ledger.js +13 -5
  15. package/dist/lib/internal/task-lifecycle.js +1 -1
  16. package/dist/lib/internal/task-operation-registry.js +7 -2
  17. package/dist/lib/platform/completion-backfill.js +63 -17
  18. package/dist/lib/platform/issue-comments.js +2 -31
  19. package/dist/lib/platform/pr-summary.js +7 -2
  20. package/dist/lib/sandbox/control/client.js +3 -44
  21. package/dist/lib/sandbox/control/executor.js +14 -79
  22. package/dist/lib/sandbox/control/protocol.js +5 -64
  23. package/dist/lib/sandbox/control/server.js +49 -343
  24. package/dist/lib/sandbox/control/state.js +1 -7
  25. package/dist/lib/sandbox/control/task-view.js +1 -2
  26. package/dist/lib/sandbox/task-cleanup.js +13 -20
  27. package/dist/lib/task/artifact-command.js +11 -4
  28. package/dist/lib/task/artifact-lifecycle.js +217 -85
  29. package/dist/lib/task/artifact-operations.js +1 -26
  30. package/dist/lib/task/artifact-receipts.js +4 -1
  31. package/dist/lib/task/artifact-schema.js +1 -0
  32. package/dist/lib/task/artifact-selection.js +25 -0
  33. package/dist/lib/task/capabilities.js +158 -39
  34. package/dist/lib/task/commands/status.js +9 -2
  35. package/dist/lib/task/control-authority.js +37 -38
  36. package/dist/lib/task/control-recovery.js +3 -42
  37. package/dist/lib/task/decision-intents.js +12 -0
  38. package/dist/lib/task/delegation-receipts.js +2 -2
  39. package/dist/lib/task/delivery.js +1 -43
  40. package/dist/lib/task/events.js +80 -64
  41. package/dist/lib/task/finalization-artifacts.js +1 -3
  42. package/dist/lib/task/finalization-state.js +1 -1
  43. package/dist/lib/task/finalization.js +319 -234
  44. package/dist/lib/task/ledger-intents.js +157 -15
  45. package/dist/lib/task/lifecycle-path.js +75 -0
  46. package/dist/lib/task/lifecycle.js +11 -1
  47. package/dist/lib/task/local-artifact-finalization.js +1 -20
  48. package/dist/lib/task/manual-validation-receipt.js +8 -3
  49. package/dist/lib/task/orchestration.js +29 -13
  50. package/dist/lib/task/review-finalization.js +3 -16
  51. package/dist/lib/task/rework-intent.js +70 -4
  52. package/dist/lib/task/verification-engine.js +20 -30
  53. package/dist/lib/task/verification.js +1 -0
  54. package/dist/lib/task/workflow-warning-intents.js +21 -6
  55. package/dist/lib/task/workspace-snapshot.js +7 -2
  56. package/dist/lifecycle-build-manifest.json +3 -3
  57. package/lib/agent-clients/adapter.ts +46 -0
  58. package/lib/agent-clients/adapters/codex-lifecycle/recovery.ts +297 -0
  59. package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +13 -33
  60. package/lib/agent-clients/adapters/codex-lifecycle/store.ts +1 -44
  61. package/lib/agent-clients/adapters/codex.ts +10 -0
  62. package/lib/internal/cli-route-inventory.ts +1 -1
  63. package/lib/internal/manual-validation.ts +22 -26
  64. package/lib/internal/sandbox-control.ts +0 -42
  65. package/lib/internal/task-artifact.ts +1 -1
  66. package/lib/internal/task-finalization.ts +40 -1
  67. package/lib/internal/task-ledger.ts +12 -5
  68. package/lib/internal/task-lifecycle.ts +2 -2
  69. package/lib/internal/task-operation-registry.ts +8 -4
  70. package/lib/platform/completion-backfill.ts +78 -16
  71. package/lib/platform/issue-comments.ts +1 -35
  72. package/lib/platform/pr-summary.ts +10 -2
  73. package/lib/sandbox/control/client.ts +5 -68
  74. package/lib/sandbox/control/executor.ts +14 -81
  75. package/lib/sandbox/control/protocol.ts +7 -83
  76. package/lib/sandbox/control/server.ts +47 -362
  77. package/lib/sandbox/control/state.ts +1 -9
  78. package/lib/sandbox/control/task-view.ts +1 -2
  79. package/lib/sandbox/task-cleanup.ts +12 -20
  80. package/lib/task/artifact-command.ts +14 -4
  81. package/lib/task/artifact-lifecycle.ts +236 -85
  82. package/lib/task/artifact-operations.ts +1 -26
  83. package/lib/task/artifact-receipts.ts +4 -1
  84. package/lib/task/artifact-schema.ts +1 -0
  85. package/lib/task/artifact-selection.ts +53 -0
  86. package/lib/task/capabilities.ts +154 -40
  87. package/lib/task/commands/status.ts +11 -2
  88. package/lib/task/control-authority.ts +41 -48
  89. package/lib/task/control-recovery.ts +3 -43
  90. package/lib/task/decision-intents.ts +9 -0
  91. package/lib/task/delegation-receipts.ts +2 -2
  92. package/lib/task/delivery.ts +1 -40
  93. package/lib/task/events.ts +82 -64
  94. package/lib/task/finalization-artifacts.ts +1 -3
  95. package/lib/task/finalization-state.ts +1 -1
  96. package/lib/task/finalization.ts +310 -216
  97. package/lib/task/ledger-intents.ts +155 -15
  98. package/lib/task/lifecycle-path.ts +98 -0
  99. package/lib/task/lifecycle.ts +15 -2
  100. package/lib/task/local-artifact-finalization.ts +2 -25
  101. package/lib/task/manual-validation-receipt.ts +8 -2
  102. package/lib/task/orchestration.ts +56 -27
  103. package/lib/task/review-finalization.ts +3 -16
  104. package/lib/task/rework-intent.ts +75 -5
  105. package/lib/task/verification-engine.ts +19 -30
  106. package/lib/task/verification.ts +2 -1
  107. package/lib/task/workflow-warning-intents.ts +18 -6
  108. package/lib/task/workspace-snapshot.ts +3 -2
  109. package/package.json +1 -1
  110. package/templates/.agents/rules/lifecycle-orchestration.en.md +19 -5
  111. package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +19 -5
  112. package/templates/.agents/rules/review-handshake.en.md +4 -4
  113. package/templates/.agents/rules/review-handshake.zh-CN.md +4 -4
  114. package/templates/.agents/skills/analyze-task/SKILL.en.md +11 -4
  115. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +11 -4
  116. package/templates/.agents/skills/code-task/SKILL.en.md +14 -8
  117. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +18 -13
  118. package/templates/.agents/skills/code-task/reference/code-rules.en.md +2 -2
  119. package/templates/.agents/skills/code-task/reference/code-rules.zh-CN.md +2 -2
  120. package/templates/.agents/skills/code-task/reference/dual-mode.en.md +4 -4
  121. package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +4 -4
  122. package/templates/.agents/skills/code-task/reference/report-template.en.md +1 -1
  123. package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +1 -1
  124. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +2 -2
  125. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +2 -2
  126. package/templates/.agents/skills/complete-task/SKILL.en.md +7 -8
  127. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +7 -8
  128. package/templates/.agents/skills/plan-task/SKILL.en.md +13 -10
  129. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +13 -10
  130. package/templates/.agents/skills/review-analysis/SKILL.en.md +5 -1
  131. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +5 -1
  132. package/templates/.agents/skills/review-code/SKILL.en.md +5 -1
  133. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +5 -1
  134. package/templates/.agents/skills/review-plan/SKILL.en.md +5 -1
  135. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +5 -1
  136. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +2 -2
  137. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +2 -2
  138. package/templates/.agents/skills/run-task/SKILL.en.md +9 -8
  139. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +9 -8
  140. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +18 -2
  141. package/templates/.agents/templates/task.en.md +2 -2
  142. package/templates/.agents/templates/task.zh-CN.md +2 -2
  143. package/templates/.agents/workflows/bug-fix.en.yaml +11 -6
  144. package/templates/.agents/workflows/bug-fix.zh-CN.yaml +11 -6
  145. package/templates/.agents/workflows/feature-development.en.yaml +11 -6
  146. package/templates/.agents/workflows/feature-development.zh-CN.yaml +11 -6
  147. package/templates/.agents/workflows/refactoring.en.yaml +11 -6
  148. package/templates/.agents/workflows/refactoring.zh-CN.yaml +11 -6
  149. package/dist/lib/sandbox/control/finalization-response.js +0 -35
  150. package/dist/lib/sandbox/control/task-workflow.js +0 -74
  151. package/dist/lib/sandbox/control/workflow-executor.js +0 -142
  152. package/dist/lib/task/lifecycle-recovery.js +0 -549
  153. package/dist/lib/task/recovery-warning.js +0 -19
  154. package/dist/lib/task/workflow-command.js +0 -21
  155. package/dist/lib/task/workflow-dispatch.js +0 -213
  156. package/lib/sandbox/control/finalization-response.ts +0 -37
  157. package/lib/sandbox/control/task-workflow.ts +0 -95
  158. package/lib/sandbox/control/workflow-executor.ts +0 -156
  159. package/lib/task/lifecycle-recovery.ts +0 -691
  160. package/lib/task/recovery-warning.ts +0 -21
  161. package/lib/task/workflow-command.ts +0 -22
  162. package/lib/task/workflow-dispatch.ts +0 -214
package/README.md CHANGED
@@ -9,7 +9,7 @@
9
9
  </p>
10
10
 
11
11
  <p align="center">
12
- <strong>From issue to merged PR in 11 commands.</strong> Define a requirement, let AI handle analysis, planning, coding, and three-stage review — you only step in when it matters.
12
+ <strong>From issue to merged PR through a risk-selected lifecycle.</strong> Every task starts with analysis, and every implementation receives an independent code review.
13
13
  </p>
14
14
 
15
15
  <p align="center">
@@ -51,14 +51,22 @@ Once initialized, open the project in your AI TUI and install the latest skills:
51
51
 
52
52
  > AI reads `.agents/.airc.json`, auto-locates the installed template root, and syncs the latest skill manifests, managed files, and registry deterministically via `sync-templates.js`.
53
53
 
54
- **Scenario**: Issue #42 reports *"Login API returns 500 when email contains a plus sign"*. Here is the full fix lifecycle — AI does the heavy lifting, you stay in control:
54
+ **Scenario**: Issue #42 reports *"Login API returns 500 when email contains a plus sign"*. Every task starts with analysis, which records one canonical path from observable facts:
55
+
56
+ | Path | Stages | Use when |
57
+ |------|--------|----------|
58
+ | Streamlined | `analyze → code → review-code` | Scope and acceptance are clear, with no separate design or document-audit decision |
59
+ | Standard | `analyze → plan → code → review-code` | The implementation needs an explicit cross-module contract, data flow, or test strategy |
60
+ | Full | `analyze → review-analysis → plan → review-plan → code → review-code` | Concrete acceptance disputes, costly interface/schema/migration choices, or real external/security boundaries require independent review |
61
+
62
+ File count, module count, or a merely possible risk does not select the full path. A later finding can return work to analysis, planning, or code when new evidence justifies it; independent `review-code` remains part of every path.
63
+
64
+ Suppose the analysis selects the **standard path** because the application-layer boundary needs an explicit plan:
55
65
 
56
66
  ```bash
57
67
  /import-issue 42 # AI reads the issue, creates a task, extracts requirements
58
- /analyze-task --task <task-id> # AI scans the codebase, finds the root cause, writes analysis.md
59
- /review-analysis --task <task-id> # An isolated reviewer checks the analysis
68
+ /analyze-task --task <task-id> # AI scans the codebase, records the selected path, writes analysis.md
60
69
  /plan-task --task <task-id> # AI proposes a fix plan
61
- /review-plan --task <task-id> # A fresh isolated reviewer checks the plan
62
70
  ```
63
71
 
64
72
  > **You review the plan and reply in natural language:**
@@ -79,7 +87,7 @@ Just fix it at the application layer in LoginService.
79
87
  /complete-task --task <task-id> # task archived
80
88
  ```
81
89
 
82
- **11 commands. 1 natural-language correction. From issue to merged PR.** That is the entire SOP — programming can have a standard operating procedure too.
90
+ **One analysis-selected path, one natural-language correction, and an independent code review.** The lifecycle keeps the evidence and review depth proportional to the task.
83
91
 
84
92
  Every command above works the same way in Claude Code, Codex, Antigravity CLI, and OpenCode. Switch tools mid-task — the workflow state follows. For what each skill does under the hood, see [Built-in AI Skills](./docs/en/skills.md).
85
93
 
@@ -193,8 +201,9 @@ The most-used lifecycle commands, in delivery order. The command prefix varies b
193
201
  |---------|---------|
194
202
  | `create-task` / `import-issue` | Start a task from a description or a GitHub Issue |
195
203
  | `run-task` | Resume the lifecycle with fresh isolated executors/reviewers when the selected client exposes verified actual model/effort evidence; Codex now has an experimental Hooks + App Server evidence channel, but orchestration remains disabled until that channel is connected to delegation receipts |
196
- | `analyze-task` → `review-analysis` | Capture scope and risks, then review the analysis |
197
- | `plan-task` → `review-plan` | Design the approach, then review the plan |
204
+ | `analyze-task` | Capture scope and risks, then select the streamlined, standard, or full path |
205
+ | `review-analysis` | Review the analysis when the selected full path requires an independent requirement checkpoint |
206
+ | `plan-task` / `review-plan` | Design the approach on standard/full paths; independently review it only on the full path |
198
207
  | `code-task` → `review-code` | Implement and test; `code-task` creates a local checkpoint, then a structured review checks that checkpoint |
199
208
  | `create-pr` → `complete-task` | Publish the approved checkpoint to the task-bound target branch, then archive after merge and final gates |
200
209
 
package/README.zh-CN.md CHANGED
@@ -9,7 +9,7 @@
9
9
  </p>
10
10
 
11
11
  <p align="center">
12
- <strong>从 Issue 到合并 PR,只需 11 条命令。</strong> 定义需求,让 AI 完成分析、方案设计、编码与三阶段审查 —— 你只需在关键节点介入。
12
+ <strong>从 Issue 到合并 PR,由风险选择相称的生命周期。</strong> 每个任务都先分析,每份实现都接受独立代码审查。
13
13
  </p>
14
14
 
15
15
  <p align="center">
@@ -50,14 +50,22 @@ agent-infra 的目标就是把这层共享基础设施标准化。它为所有
50
50
 
51
51
  > AI 读取 `.agents/.airc.json`,自动定位已安装的模板根目录,并通过 `sync-templates.js` 确定性地同步最新的 skill 清单、managed 文件和注册表。
52
52
 
53
- **场景**:Issue #42 报告 *"登录接口在邮箱包含加号时返回 500"*。以下是完整的修复流程 —— AI 执行主要工作,你掌控方向:
53
+ **场景**:Issue #42 报告 *"登录接口在邮箱包含加号时返回 500"*。每个任务都从分析开始,并由分析根据可观察事实记录一条规范路径:
54
+
55
+ | 路径 | 阶段 | 适用条件 |
56
+ |------|------|----------|
57
+ | 精简路径 | `analyze → code → review-code` | 范围和验收明确,不需要独立设计或文档审计决策 |
58
+ | 标准路径 | `analyze → plan → code → review-code` | 实现需要明确跨模块契约、数据流或测试策略 |
59
+ | 完整路径 | `analyze → review-analysis → plan → review-plan → code → review-code` | 存在具体的验收争议、高代价接口/schema/迁移选择,或真实外部/安全边界,需要独立审查 |
60
+
61
+ 文件数量、模块数量或可能存在的风险不能单独触发完整路径。后续 finding 只有在新证据成立时才会把工作返回分析、方案或实现阶段;每条路径都保留独立 `review-code`。
62
+
63
+ 假设分析认为应用层边界需要明确方案,因此选择**标准路径**:
54
64
 
55
65
  ```bash
56
66
  /import-issue 42 # AI 读取 Issue,创建任务,提取需求
57
- /analyze-task --task <task-id> # AI 扫描代码库,定位根因,输出 analysis.md
58
- /review-analysis --task <task-id> # 隔离 reviewer 审查分析产物
67
+ /analyze-task --task <task-id> # AI 扫描代码库、记录所选路径,输出 analysis.md
59
68
  /plan-task --task <task-id> # AI 提出修复方案
60
- /review-plan --task <task-id> # fresh 隔离 reviewer 审查方案
61
69
  ```
62
70
 
63
71
  > **你审查方案后用自然语言回复:**
@@ -78,7 +86,7 @@ agent-infra 的目标就是把这层共享基础设施标准化。它为所有
78
86
  /complete-task --task <task-id> # 任务归档
79
87
  ```
80
88
 
81
- **11 条命令,1 次自然语言纠正,从 Issue 到合并 PR。** 这就是完整的 SOP —— 编程也可以有标准作业流程。
89
+ **一条由分析选择的路径、一次自然语言纠正和一次独立代码审查。** 生命周期会让证据与审查深度和任务风险相称。
82
90
 
83
91
  以上每条命令在 Claude Code、Codex、Antigravity CLI、OpenCode 中完全通用。任务进行到一半切换工具,工作流状态照常延续。每个 skill 背后做了什么,见 [内置 AI Skills](./docs/zh-CN/skills.md)。
84
92
 
@@ -192,8 +200,9 @@ ai agent-client configure
192
200
  |------|------|
193
201
  | `create-task` / `import-issue` | 从描述或 GitHub Issue 创建任务 |
194
202
  | `run-task` | 当所选客户端能提供已验证的 actual model/effort 证据时,用 fresh 隔离 executor/reviewer 续跑生命周期;Codex 现已具备实验性的 Hooks + App Server 证据通道,但在该通道接入 delegation receipt 前仍不会启用 orchestration |
195
- | `analyze-task` → `review-analysis` | 明确范围与风险,再审查分析 |
196
- | `plan-task` → `review-plan` | 设计实现路径,再审查方案 |
203
+ | `analyze-task` | 明确范围与风险,再选择精简、标准或完整路径 |
204
+ | `review-analysis` | 仅在完整路径需要独立需求检查点时审查分析 |
205
+ | `plan-task` / `review-plan` | 标准和完整路径需要设计方案;只有完整路径独立审查方案 |
197
206
  | `code-task` → `review-code` | 实现并测试;`code-task` 创建本地 checkpoint,再由结构化审查检视该 checkpoint |
198
207
  | `create-pr` → `complete-task` | 将已通过审查的 checkpoint 发布到任务绑定目标分支,并在合并和最终门禁通过后归档 |
199
208
 
@@ -49,7 +49,9 @@ function taskControlTransportFailure(message, code = 'TASK_CONTROL_TRANSPORT_INV
49
49
  }
50
50
  let controlRouted = false;
51
51
  if (!taskViewGuardFailed && !markerlessHelp && (taskControlCommand || taskWorkflowCommand || manualValidationWorkflowCommand)) {
52
- const transport = resolveSandboxControlTransport(process.env);
52
+ const transport = resolveSandboxControlTransport(process.env, {
53
+ localWorkflow: command === 'task-orchestration' || command === 'task-finalization' || taskWorkflowCommand || manualValidationWorkflowCommand
54
+ });
53
55
  switch (transport.kind) {
54
56
  case 'fail-closed': {
55
57
  const reason = transport.reasonCode ?? 'TASK_CONTROL_TRANSPORT_INVALID';
@@ -60,9 +62,12 @@ if (!taskViewGuardFailed && !markerlessHelp && (taskControlCommand || taskWorkfl
60
62
  // The host CLI is the direct-host authority. Domain handlers retain their
61
63
  // task locks, atomic writes, and operation-specific recovery facts.
62
64
  break;
65
+ case 'sandbox-local': {
66
+ break;
67
+ }
63
68
  case 'broker-client': {
64
69
  const { sandboxControl } = await import('../lib/internal/sandbox-control.js');
65
- await sandboxControl(['client', ...(taskControlCommand ? [command] : ['task-workflow', command]), ...process.argv.slice(3)]);
70
+ await sandboxControl(['client', command, ...process.argv.slice(3)]);
66
71
  break;
67
72
  }
68
73
  }
@@ -408,6 +408,9 @@ function defineAgentClientAdapter(candidate) {
408
408
  capabilities: Object.freeze(capabilities),
409
409
  modelSelection: frozenModelSelection,
410
410
  delegationEvidence: Object.freeze({ ...evidence }),
411
+ ...(candidate.orchestrationAdapter === undefined
412
+ ? {}
413
+ : { orchestrationAdapter: Object.freeze({ ...candidate.orchestrationAdapter }) }),
411
414
  project: Object.freeze({
412
415
  ownedPathPrefixes: Object.freeze(paths),
413
416
  ...projectAssets,
@@ -0,0 +1,234 @@
1
+ import fs from 'node:fs';
2
+ import { createCodexLifecycleStore } from './store.js';
3
+ import { managedDelegationRole } from '../../../task/delegation-receipts.js';
4
+ import { normalizeAgentToken } from '../../tokens.js';
5
+ import { locateActivityLog, pairEntries, appendActivityEntry } from '../../../task/activity-log.js';
6
+ import { captureTaskWriteMetadata, writeTask } from '../../../task/write.js';
7
+ import { parseTypedTaskFrontmatter } from '../../../task/frontmatter.js';
8
+ import { resolveTaskRef } from '../../../task/resolve-ref.js';
9
+ import { resolveAgentRuntimeStoreRoot } from '../../../runtime/agent-runtime.js';
10
+ import { recoverActivatedOrchestrationDelegationUnderLock, readRun } from '../../../task/orchestration.js';
11
+ const ACTIONS = {
12
+ analysis: 'Analyze Task',
13
+ 'review-analysis': 'Review Analysis',
14
+ plan: 'Plan Task',
15
+ 'review-plan': 'Review Plan',
16
+ code: 'Code Task',
17
+ 'review-code': 'Review Code'
18
+ };
19
+ function result(request, status, extra = {}) {
20
+ return {
21
+ status,
22
+ changed: status === 'applied',
23
+ targetState: 'active',
24
+ requestRef: request.taskRef,
25
+ intent: 'recover-started',
26
+ taskId: null,
27
+ receiptId: null,
28
+ childId: null,
29
+ error: null,
30
+ ...extra
31
+ };
32
+ }
33
+ function failure(request, status, code, message, extra = {}) {
34
+ return result(request, status, { error: { code, message }, ...extra });
35
+ }
36
+ function actionPattern(receipt) {
37
+ const action = ACTIONS[receipt.stage];
38
+ if (!action)
39
+ return null;
40
+ const escaped = action.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
41
+ return new RegExp(`^${escaped} \\(Round ${receipt.round}(?:, (?:fix for review-code(?:-r[2-9]|-r[1-9]\\d+)?.md|decision II-[1-9]\\d*))?\\)$`, 'u');
42
+ }
43
+ function openRows(section) {
44
+ return pairEntries([...section.entries]).filter((row) => row.started !== '' && row.done === '');
45
+ }
46
+ function rowForReceipt(rows, receipt) {
47
+ const pattern = actionPattern(receipt);
48
+ return pattern ? rows.filter((row) => pattern.test(row.step)) : [];
49
+ }
50
+ function readLifecycleStore(options, repoRoot) {
51
+ return options.lifecycleStore ?? createCodexLifecycleStore({
52
+ root: resolveAgentRuntimeStoreRoot({ repoRoot, store: 'lifecycle' }),
53
+ cliVersion: 'recovery',
54
+ now: options.now
55
+ });
56
+ }
57
+ function readStoredEvidence(store, childId) {
58
+ try {
59
+ return store.read(childId);
60
+ }
61
+ catch (error) {
62
+ const message = error instanceof Error ? error.message : String(error);
63
+ if (message.includes('was not found uniquely'))
64
+ return { missing: true };
65
+ return { error: error instanceof Error ? error : new Error(message) };
66
+ }
67
+ }
68
+ function validateStopRecord(record, receipt) {
69
+ const start = record.state.startEvidence;
70
+ const stop = record.state.stopEvidence;
71
+ const provenance = receipt.lifecycleProvenance;
72
+ const host = receipt.hostEvidence;
73
+ if (receipt.client !== 'codex'
74
+ || !provenance
75
+ || host?.kind !== 'codex-lifecycle-v2'
76
+ || !start
77
+ || !stop
78
+ || record.state.status !== 'stop-ready'
79
+ || stop.terminalStatus !== 'completed'
80
+ || stop.hookStopObserved !== true
81
+ || start.childThreadId !== receipt.childId
82
+ || start.parentThreadId !== receipt.parentId
83
+ || managedDelegationRole(start.nativeAgent) !== receipt.role
84
+ || start.hookDefinitionHash !== provenance.hookDefinitionHash
85
+ || host.hookDefinitionHash !== provenance.hookDefinitionHash
86
+ || host.capabilitySessionId !== provenance.capabilitySessionId
87
+ || host.capabilityTurnId !== provenance.capabilityTurnId
88
+ || host.capabilityToolUseId !== provenance.capabilityToolUseId
89
+ || host.spawnToolUseId !== start.spawnToolUseId
90
+ || !host.spawnObservedAt
91
+ || host.spawnObservedAt !== record.spawnObservedAt
92
+ || host.controllerInstanceDigest !== provenance.controllerInstanceDigest
93
+ || host.controlGeneration !== provenance.controlGeneration
94
+ || typeof host.controllerInstanceDigest !== 'string'
95
+ || typeof host.controlGeneration !== 'string'
96
+ || host.startRevision < 1
97
+ || record.revision <= host.startRevision
98
+ || record.state.child?.childThreadId !== receipt.childId
99
+ || record.state.stop?.childThreadId !== receipt.childId) {
100
+ return { ok: false, code: 'RECOVERY_STOP_EVIDENCE_INVALID', message: 'Codex stop evidence does not match the activated receipt' };
101
+ }
102
+ if (record.consumer !== null && record.consumer !== receipt.id) {
103
+ return { ok: false, code: 'RECOVERY_CONSUMER_CONFLICT', message: `Codex lifecycle evidence was consumed by '${record.consumer}'` };
104
+ }
105
+ return { ok: true };
106
+ }
107
+ function abortedCandidates(run, rows) {
108
+ const candidates = run.receipts.flatMap((receipt) => {
109
+ if (receipt.status !== 'aborted' || receipt.activatedAt === null || receipt.client !== 'codex')
110
+ return [];
111
+ const matches = rowForReceipt(rows, receipt);
112
+ return matches.length === 1 ? [{ receipt, row: matches[0] }] : [];
113
+ });
114
+ return candidates;
115
+ }
116
+ function appendRecoveryLog(request, taskId, section, candidate, repoRoot, writer) {
117
+ const metadata = captureTaskWriteMetadata();
118
+ const body = appendActivityEntry(section, {
119
+ time: metadata.timestamp,
120
+ step: `${candidate.row.step} [aborted]`,
121
+ agent: request.agent,
122
+ note: `terminated Codex delegation; receipt=${candidate.receipt.id}; child=${candidate.receipt.childId}`
123
+ });
124
+ const written = writer({
125
+ taskRef: taskId,
126
+ expectedState: 'active',
127
+ mutations: [{ kind: 'section', aliases: ['活动日志', 'Activity Log'], heading: section.heading, body }]
128
+ }, { repoRoot, metadataProvider: () => metadata });
129
+ if (written.status === 'failed') {
130
+ return failure(request, 'owner-unknown', 'RECOVERY_LOG_WRITE_FAILED', written.error.message, {
131
+ taskId, receiptId: candidate.receipt.id, childId: candidate.receipt.childId
132
+ });
133
+ }
134
+ return result(request, 'applied', {
135
+ taskId, receiptId: candidate.receipt.id, childId: candidate.receipt.childId
136
+ });
137
+ }
138
+ function recoverStartedLifecycleUnderLock(request, options = {}) {
139
+ if (!request || request.intent !== 'recover-started' || request.auto !== true || normalizeAgentToken(request.agent) !== 'codex') {
140
+ return failure(request, 'conflict', 'RECOVERY_PAYLOAD_INVALID', 'automatic recovery requires a task and the Codex agent');
141
+ }
142
+ const resolved = resolveTaskRef(request.taskRef, { repoRoot: options.repoRoot });
143
+ if (!resolved.ok)
144
+ return failure(request, 'conflict', resolved.code, resolved.message);
145
+ const taskId = resolved.taskId;
146
+ let content;
147
+ try {
148
+ content = fs.readFileSync(resolved.taskMdPath, 'utf8');
149
+ parseTypedTaskFrontmatter(content);
150
+ }
151
+ catch (error) {
152
+ return failure(request, 'conflict', 'RECOVERY_TASK_INVALID', error instanceof Error ? error.message : String(error), { taskId });
153
+ }
154
+ const section = locateActivityLog(content);
155
+ if (!section)
156
+ return failure(request, 'conflict', 'RECOVERY_LOG_INVALID', 'task has no unique Activity Log section', { taskId });
157
+ let run;
158
+ try {
159
+ run = readRun(resolved.taskDir, options.orchestration);
160
+ }
161
+ catch (error) {
162
+ return failure(request, 'owner-unknown', 'RECOVERY_ORCHESTRATION_UNKNOWN', error instanceof Error ? error.message : String(error), { taskId });
163
+ }
164
+ if (!run)
165
+ return result(request, 'no-op', { taskId });
166
+ const rows = openRows(section);
167
+ const pending = run.pendingDelegation;
168
+ if (!pending) {
169
+ const candidates = abortedCandidates(run, rows);
170
+ if (candidates.length === 0)
171
+ return result(request, 'no-op', { taskId });
172
+ if (candidates.length > 1) {
173
+ return failure(request, 'conflict', 'RECOVERY_CANDIDATE_AMBIGUOUS', 'more than one aborted delegation has an open lifecycle row', { taskId });
174
+ }
175
+ return appendRecoveryLog(request, taskId, section, candidates[0], resolved.repoRoot, options.writeTask ?? writeTask);
176
+ }
177
+ if (pending.status !== 'activated')
178
+ return result(request, 'no-op', { taskId });
179
+ if (pending.client !== 'codex' || run.status !== 'running' || run.pause !== null) {
180
+ return failure(request, 'conflict', 'RECOVERY_ORCHESTRATION_INVALID', 'activated recovery requires one running Codex delegation', {
181
+ taskId, receiptId: pending.id, childId: pending.childId
182
+ });
183
+ }
184
+ const matchingRows = rowForReceipt(rows, pending);
185
+ if (matchingRows.length !== 1 || normalizeAgentToken(matchingRows[0].agent) !== 'codex' || matchingRows[0].note !== 'started') {
186
+ return failure(request, 'conflict', 'RECOVERY_LOG_CONFLICT', 'activated delegation does not have one matching open lifecycle row', {
187
+ taskId, receiptId: pending.id, childId: pending.childId
188
+ });
189
+ }
190
+ if (!pending.childId) {
191
+ return failure(request, 'conflict', 'RECOVERY_DELEGATION_INVALID', 'activated delegation has no child identity', { taskId, receiptId: pending.id });
192
+ }
193
+ const store = readLifecycleStore(options, resolved.repoRoot);
194
+ const stored = readStoredEvidence(store, pending.childId);
195
+ if ('error' in stored)
196
+ return failure(request, 'owner-unknown', 'RECOVERY_STORE_UNKNOWN', stored.error.message, { taskId, receiptId: pending.id, childId: pending.childId });
197
+ if ('missing' in stored)
198
+ return failure(request, 'owner-unknown', 'RECOVERY_STOP_EVIDENCE_MISSING', 'matching Codex lifecycle stop evidence is missing', { taskId, receiptId: pending.id, childId: pending.childId });
199
+ const evidence = validateStopRecord(stored, pending);
200
+ if (!evidence.ok)
201
+ return failure(request, evidence.code === 'RECOVERY_CONSUMER_CONFLICT' ? 'conflict' : 'owner-unknown', evidence.code, evidence.message, { taskId, receiptId: pending.id, childId: pending.childId });
202
+ let consumed = stored;
203
+ if (stored.consumer === null) {
204
+ try {
205
+ consumed = store.consume(pending.childId, pending.id, pending.hostEvidence?.hookDefinitionHash);
206
+ }
207
+ catch (error) {
208
+ return failure(request, 'owner-unknown', 'RECOVERY_EVIDENCE_CONSUME_FAILED', error instanceof Error ? error.message : String(error), { taskId, receiptId: pending.id, childId: pending.childId });
209
+ }
210
+ }
211
+ if (!consumed.consumedAt) {
212
+ return failure(request, 'owner-unknown', 'RECOVERY_EVIDENCE_INVALID', 'consumed stop evidence has no timestamp', { taskId, receiptId: pending.id, childId: pending.childId });
213
+ }
214
+ const recovered = recoverActivatedOrchestrationDelegationUnderLock(taskId, {
215
+ receiptId: pending.id,
216
+ stage: pending.stage,
217
+ round: pending.round,
218
+ artifact: pending.artifact,
219
+ startedAgent: request.agent,
220
+ childId: pending.childId,
221
+ stopRevision: consumed.revision,
222
+ consumer: pending.id,
223
+ consumedAt: consumed.consumedAt
224
+ }, { ...options.orchestration, repoRoot: resolved.repoRoot, now: options.now });
225
+ if (recovered.status === 'failed' || !recovered.run) {
226
+ return failure(request, 'owner-unknown', recovered.error?.code ?? 'RECOVERY_ORCHESTRATION_FAILED', recovered.error?.message ?? 'orchestration recovery failed', { taskId, receiptId: pending.id, childId: pending.childId });
227
+ }
228
+ return appendRecoveryLog(request, taskId, section, { receipt: recovered.run.receipts.at(-1), row: matchingRows[0] }, resolved.repoRoot, options.writeTask ?? writeTask);
229
+ }
230
+ function recoverStartedLifecycleFromAdapter(request, options = {}) {
231
+ return recoverStartedLifecycleUnderLock(request, options);
232
+ }
233
+ export { recoverStartedLifecycleFromAdapter, recoverStartedLifecycleUnderLock };
234
+ //# sourceMappingURL=recovery.js.map
@@ -5,8 +5,7 @@ import os from 'node:os';
5
5
  import path from 'node:path';
6
6
  import semver from 'semver';
7
7
  import * as toml from 'smol-toml';
8
- import { requestCodexControllerClose, requestCodexControllerOpen, requestCodexControllerVerify, requestSandboxControl } from '../../../sandbox/control/client.js';
9
- import { readSandboxControlStatus } from '../../../sandbox/control/state.js';
8
+ import { requestCodexControllerClose, requestCodexControllerOpen, requestCodexControllerVerify } from '../../../sandbox/control/client.js';
10
9
  import { getProcessStartTime } from '../../../server/process-state.js';
11
10
  import { LIFECYCLE_PROTOCOL_VERSION } from './build-identity.js';
12
11
  import { contextFromControllerLease, controllerProofFromContext, verifyCodexSandboxControllerContextWithWarnings as verifyContextFileWithWarnings, writeCodexSandboxControllerContext } from './controller-context.js';
@@ -56,44 +55,18 @@ function verifyRuntime(runtimeDir) {
56
55
  fs.rmSync(probe, { force: true });
57
56
  }
58
57
  }
59
- function verifyControl(taskId, control) {
58
+ function verifyControllerBinding(context, control, requestControllerVerify) {
60
59
  verifyRuntime(control.runtimeDir);
61
- let status = null;
62
- let lastError = null;
63
- for (let attempt = 0; attempt < 5; attempt += 1) {
64
- try {
65
- status = readSandboxControlStatus(control.statusDir);
66
- break;
67
- }
68
- catch (error) {
69
- lastError = error;
70
- if (attempt < 4)
71
- Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 25);
72
- }
73
- }
74
- if (!status) {
75
- throw new Error(`CODEX_SANDBOX_CONTROLLER_CONTROL_UNAVAILABLE: ${String(lastError)}`);
76
- }
77
- if (status.generation !== control.generation
78
- || status.state !== 'healthy'
79
- || Date.now() - status.updatedAt > 1_500) {
80
- throw new Error('CODEX_SANDBOX_CONTROLLER_CONTROL_UNAVAILABLE');
81
- }
82
- const response = requestSandboxControl({
83
- family: 'task-orchestration',
84
- args: [taskId, 'status'],
60
+ const verified = requestControllerVerify({
61
+ controllerProof: controllerProofFromContext(context),
85
62
  ...control,
86
63
  timeoutMs: 30_000
87
64
  });
88
- let payload = {};
89
- try {
90
- payload = JSON.parse(response.stdout);
91
- }
92
- catch {
65
+ if (verified.binding.taskId !== context.taskId
66
+ || verified.binding.controlGeneration !== context.controlGeneration
67
+ || verified.binding.controllerInstanceDigest !== context.controllerInstanceDigest) {
93
68
  throw new Error('CODEX_SANDBOX_CONTROLLER_TASK_BINDING_INVALID');
94
69
  }
95
- if (payload.taskId !== taskId)
96
- throw new Error('CODEX_SANDBOX_CONTROLLER_TASK_BINDING_INVALID');
97
70
  }
98
71
  function verifyCodexSandboxControllerContextWithWarnings(contextPath, options = {}) {
99
72
  const control = options.control ?? controlFromEnvironment();
@@ -272,7 +245,7 @@ function prepareCodexSandboxController(input, options = {}) {
272
245
  || opened.lease.controllerProcess.startTime !== parentStartTime) {
273
246
  throw new Error('SANDBOX_CONTROL_RESULT_INVALID');
274
247
  }
275
- (options.verifyTaskBinding ?? verifyControl)(taskId, control);
248
+ verifyControllerBinding(context, control, options.verifyController ?? requestCodexControllerVerify);
276
249
  const contextPath = path.join(home, 'controller-context.json');
277
250
  writeCodexSandboxControllerContext(contextPath, context);
278
251
  const shimDir = path.join(home, 'bin');
@@ -6,19 +6,9 @@ import { resolveAgentRuntimeStoreRoot } from '../../../runtime/agent-runtime.js'
6
6
  const LOCK_RETRY_MS = 10;
7
7
  const LOCK_TIMEOUT_MS = 1_000;
8
8
  const LOCK_STALE_MS = 30_000;
9
- const RECOVERY_CONSUMER_RE = /^lifecycle-recovery:([^:\r\n]+):([^:\r\n]+)$/u;
10
9
  function digest(value) {
11
10
  return crypto.createHash('sha256').update(value).digest('hex');
12
11
  }
13
- function isRecoveryConsumer(value) {
14
- return RECOVERY_CONSUMER_RE.test(value);
15
- }
16
- function recoveryConsumer(taskId, receiptId) {
17
- if (!/^[^:\r\n]+$/u.test(taskId) || !/^[^:\r\n]+$/u.test(receiptId)) {
18
- throw new Error('Codex lifecycle recovery claim identity is invalid');
19
- }
20
- return `lifecycle-recovery:${taskId}:${receiptId}`;
21
- }
22
12
  function readRecord(file) {
23
13
  const value = JSON.parse(fs.readFileSync(file, 'utf8'));
24
14
  if (value.schemaVersion !== 1
@@ -255,29 +245,8 @@ function createCodexLifecycleStore(options) {
255
245
  function consume(childThreadId, consumer, expectedHookDefinitionHash) {
256
246
  if (!consumer.trim())
257
247
  throw new Error('Codex lifecycle consumer is required');
258
- if (consumer.startsWith('lifecycle-recovery:')) {
259
- throw new Error('Codex lifecycle recovery claims require claimRecovery');
260
- }
261
248
  return consumeInternal(childThreadId, consumer, expectedHookDefinitionHash);
262
249
  }
263
- function claimRecovery(childThreadId, taskId, receiptId, expectedHookDefinitionHash) {
264
- return consumeInternal(childThreadId, recoveryConsumer(taskId, receiptId), expectedHookDefinitionHash);
265
- }
266
- function releaseRecovery(childThreadId, consumer) {
267
- if (!/^lifecycle-recovery:[^:\r\n]+:[^:\r\n]+$/.test(consumer))
268
- return false;
269
- return withWriteLock(() => {
270
- const matches = findByChild(childThreadId);
271
- if (matches.length !== 1)
272
- return false;
273
- const file = matches[0];
274
- const current = readRecord(file);
275
- if (current.state.status !== 'stop-ready' || current.consumer !== consumer || !current.consumedAt)
276
- return false;
277
- fs.unlinkSync(file);
278
- return true;
279
- });
280
- }
281
250
  function expireBefore(cutoff) {
282
251
  return withWriteLock(() => {
283
252
  let changed = 0;
@@ -285,8 +254,6 @@ function createCodexLifecycleStore(options) {
285
254
  const current = readRecord(file);
286
255
  if (current.updatedAt >= cutoff)
287
256
  continue;
288
- if (current.consumer && isRecoveryConsumer(current.consumer))
289
- continue;
290
257
  if (current.consumer || ['invalid', 'expired', 'stop-ready'].includes(current.state.status)) {
291
258
  fs.unlinkSync(file);
292
259
  changed += 1;
@@ -306,7 +273,7 @@ function createCodexLifecycleStore(options) {
306
273
  return changed;
307
274
  });
308
275
  }
309
- return Object.freeze({ root, apply, applyToSpawn, consume, claimRecovery, releaseRecovery, expireBefore, findByParent, read });
276
+ return Object.freeze({ root, apply, applyToSpawn, consume, expireBefore, findByParent, read });
310
277
  }
311
278
  export { createCodexLifecycleStore, hasActiveCodexLifecycleEvidence };
312
279
  //# sourceMappingURL=store.js.map
@@ -24,6 +24,16 @@ const codexAdapter = defineAgentClientAdapter({
24
24
  actualModel: 'app-server',
25
25
  actualReasoningEffort: 'app-server'
26
26
  },
27
+ orchestrationAdapter: {
28
+ prepareDelegation: async (...args) => {
29
+ const { prepareCodexOrchestrationDelegation } = await import('../../task/codex-orchestration.js');
30
+ return prepareCodexOrchestrationDelegation(...args);
31
+ },
32
+ recoverStarted: async (...args) => {
33
+ const { recoverStartedLifecycleFromAdapter } = await import('./codex-lifecycle/recovery.js');
34
+ return recoverStartedLifecycleFromAdapter(...args);
35
+ }
36
+ },
27
37
  project: {
28
38
  ownedPathPrefixes: ['.codex/'],
29
39
  managed: ['.codex/hooks.json', '.codex/agents/'],
@@ -50,7 +50,7 @@ export const INTERNAL_HANDLER_ROUTE_SELECTORS = Object.freeze({
50
50
  'pr-review-grade': ['decide', 'resolve-host', 'verify-artifact'],
51
51
  'platform-checks': ['inspect', 'watch', 'resolve-run', 'logs'],
52
52
  'task-context': ['resolve'],
53
- 'task-ledger': ['decision-next-id', 'stage-status', 'finding-upsert', 'finding-respond', 'finding-review', 'decision-upsert', 'rework-intent-upsert'],
53
+ 'task-ledger': ['decision-next-id', 'stage-status', 'finding-upsert', 'finding-respond', 'finding-review', 'decision-upsert', 'rework-intent-upsert', 'rework-intent-rebuild'],
54
54
  'task-warning': ['list', 'add', 'set-status'],
55
55
  'task-activity': ['pr-review-inspect', 'pr-review-start', 'pr-review-complete', 'pr-review-terminate'],
56
56
  'task-artifact': ['inspect', 'init', 'preflight', 'finalize-local'],