@fitlab-ai/agent-infra 0.9.15 → 0.11.0

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 (224) hide show
  1. package/README.md +2 -6
  2. package/README.zh-CN.md +2 -6
  3. package/bin/internal-cli.sh +0 -13
  4. package/dist/bin/internal-cli.js +11 -49
  5. package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +6 -2
  6. package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +43 -3
  7. package/dist/lib/fs/durable-write.js +2 -0
  8. package/dist/lib/internal/cli-route-inventory.js +3 -4
  9. package/dist/lib/internal/manual-validation.js +17 -8
  10. package/dist/lib/internal/platform-comment.js +60 -2
  11. package/dist/lib/internal/task-artifact.js +1 -1
  12. package/dist/lib/internal/task-finalization.js +2 -2
  13. package/dist/lib/internal/task-lifecycle.js +4 -4
  14. package/dist/lib/internal/task-operation-registry.js +22 -13
  15. package/dist/lib/internal/task-orchestration.js +2 -2
  16. package/dist/lib/internal/task-review.js +12 -7
  17. package/dist/lib/platform/activity-recovery.js +159 -0
  18. package/dist/lib/platform/issue-comments.js +89 -31
  19. package/dist/lib/platform/issues.js +1 -1
  20. package/dist/lib/platform/pr-change-report.js +5 -4
  21. package/dist/lib/platform/pr-summary.js +64 -32
  22. package/dist/lib/platform/pull-requests.js +3 -3
  23. package/dist/lib/platform/task-comment-projection.js +70 -0
  24. package/dist/lib/platform/task-identities.js +2 -9
  25. package/dist/lib/platform/task-recovery.js +55 -0
  26. package/dist/lib/platform/verification-sync.js +45 -7
  27. package/dist/lib/pr-review/evidence-grading.js +4 -8
  28. package/dist/lib/sandbox/commands/rm.js +21 -2
  29. package/dist/lib/sandbox/control/executor.js +4 -2
  30. package/dist/lib/sandbox/control/server.js +29 -9
  31. package/dist/lib/sandbox/control/state.js +7 -1
  32. package/dist/lib/sandbox/control/task-workflow.js +1 -1
  33. package/dist/lib/sandbox/control/workflow-executor.js +87 -35
  34. package/dist/lib/sandbox/index.js +1 -2
  35. package/dist/lib/sandbox/removal.js +116 -1011
  36. package/dist/lib/sandbox/workspace-identity.js +17 -1
  37. package/dist/lib/server/process-state.js +17 -5
  38. package/dist/lib/task/artifact-command.js +11 -30
  39. package/dist/lib/task/artifact-operations.js +17 -231
  40. package/dist/lib/task/artifact-recovery.js +476 -0
  41. package/dist/lib/task/artifact-repair-intent.js +90 -39
  42. package/dist/lib/task/commit-operation.js +31 -4
  43. package/dist/lib/task/control-authority.js +39 -8
  44. package/dist/lib/task/control-recovery.js +14 -2
  45. package/dist/lib/task/create-service.js +1 -8
  46. package/dist/lib/task/delegation-receipts.js +64 -3
  47. package/dist/lib/task/events.js +165 -23
  48. package/dist/lib/task/human-override.js +5 -8
  49. package/dist/lib/task/intermediate-cleanup.js +91 -8
  50. package/dist/lib/task/lifecycle-recovery.js +549 -0
  51. package/dist/lib/task/lifecycle.js +12 -3
  52. package/dist/lib/task/local-artifact-finalization.js +212 -123
  53. package/dist/lib/task/manual-validation-receipt.js +5 -1
  54. package/dist/lib/task/orchestration.js +52 -2
  55. package/dist/lib/task/pr-delivery-fact.js +12 -56
  56. package/dist/lib/task/recovery-warning.js +19 -0
  57. package/dist/lib/task/review-command.js +3 -2
  58. package/dist/lib/task/review-finalization.js +294 -74
  59. package/dist/lib/{host-control → task}/secure-fs.js +8 -19
  60. package/dist/lib/task/short-id.js +15 -7
  61. package/dist/lib/task/task-execution-lock.js +1 -1
  62. package/dist/lib/task/verification-engine.js +3 -14
  63. package/dist/lib/task/workflow-command.js +2 -1
  64. package/dist/lib/task/workflow-dispatch.js +161 -0
  65. package/dist/lib/version.js +1 -11
  66. package/dist/lifecycle-build-manifest.json +3 -3
  67. package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +7 -2
  68. package/lib/agent-clients/adapters/codex-lifecycle/store.ts +55 -3
  69. package/lib/fs/durable-write.ts +1 -0
  70. package/lib/internal/cli-route-inventory.ts +3 -4
  71. package/lib/internal/manual-validation.ts +17 -8
  72. package/lib/internal/platform-comment.ts +53 -2
  73. package/lib/internal/task-artifact.ts +1 -1
  74. package/lib/internal/task-finalization.ts +1 -1
  75. package/lib/internal/task-lifecycle.ts +5 -4
  76. package/lib/internal/task-operation-registry.ts +22 -13
  77. package/lib/internal/task-orchestration.ts +1 -1
  78. package/lib/internal/task-review.ts +11 -7
  79. package/lib/platform/activity-recovery.ts +179 -0
  80. package/lib/platform/issue-comments.ts +105 -30
  81. package/lib/platform/issues.ts +1 -1
  82. package/lib/platform/pr-change-report.ts +7 -5
  83. package/lib/platform/pr-summary.ts +64 -32
  84. package/lib/platform/pull-requests.ts +3 -3
  85. package/lib/platform/task-comment-projection.ts +79 -0
  86. package/lib/platform/task-identities.ts +3 -14
  87. package/lib/platform/task-recovery.ts +54 -0
  88. package/lib/platform/verification-sync.ts +48 -11
  89. package/lib/pr-review/evidence-grading.ts +4 -9
  90. package/lib/sandbox/commands/rm.ts +34 -2
  91. package/lib/sandbox/control/executor.ts +4 -2
  92. package/lib/sandbox/control/protocol.ts +6 -1
  93. package/lib/sandbox/control/server.ts +30 -10
  94. package/lib/sandbox/control/state.ts +9 -1
  95. package/lib/sandbox/control/task-workflow.ts +1 -1
  96. package/lib/sandbox/control/workflow-executor.ts +106 -35
  97. package/lib/sandbox/index.ts +1 -2
  98. package/lib/sandbox/removal.ts +126 -1128
  99. package/lib/sandbox/workspace-identity.ts +19 -3
  100. package/lib/server/process-state.ts +26 -5
  101. package/lib/task/artifact-command.ts +12 -29
  102. package/lib/task/artifact-operations.ts +22 -252
  103. package/lib/task/artifact-recovery.ts +572 -0
  104. package/lib/task/artifact-repair-intent.ts +126 -51
  105. package/lib/task/commit-operation.ts +35 -4
  106. package/lib/task/control-authority.ts +48 -15
  107. package/lib/task/control-recovery.ts +13 -1
  108. package/lib/task/create-service.ts +1 -10
  109. package/lib/task/delegation-receipts.ts +73 -2
  110. package/lib/task/events.ts +188 -24
  111. package/lib/task/human-override.ts +5 -8
  112. package/lib/task/intermediate-cleanup.ts +66 -12
  113. package/lib/task/lifecycle-recovery.ts +691 -0
  114. package/lib/task/lifecycle.ts +13 -4
  115. package/lib/task/local-artifact-finalization.ts +249 -131
  116. package/lib/task/manual-validation-receipt.ts +5 -1
  117. package/lib/task/orchestration.ts +65 -0
  118. package/lib/task/pr-delivery-fact.ts +15 -62
  119. package/lib/task/recovery-warning.ts +21 -0
  120. package/lib/task/review-command.ts +2 -1
  121. package/lib/task/review-finalization.ts +298 -84
  122. package/lib/{host-control → task}/secure-fs.ts +10 -32
  123. package/lib/task/short-id.ts +27 -5
  124. package/lib/task/task-execution-lock.ts +1 -1
  125. package/lib/task/verification-engine.ts +3 -17
  126. package/lib/task/verification-types.ts +0 -2
  127. package/lib/task/workflow-command.ts +2 -1
  128. package/lib/task/workflow-dispatch.ts +171 -0
  129. package/lib/version.ts +1 -15
  130. package/package.json +1 -1
  131. package/templates/.agents/rules/create-issue.en.md +1 -1
  132. package/templates/.agents/rules/create-issue.zh-CN.md +1 -1
  133. package/templates/.agents/rules/issue-sync.en.md +12 -2
  134. package/templates/.agents/rules/issue-sync.zh-CN.md +13 -5
  135. package/templates/.agents/rules/local-artifact-repair.en.md +47 -39
  136. package/templates/.agents/rules/local-artifact-repair.zh-CN.md +45 -36
  137. package/templates/.agents/skills/analyze-task/SKILL.en.md +6 -6
  138. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +6 -6
  139. package/templates/.agents/skills/analyze-task/config/verify.en.json +1 -1
  140. package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +1 -1
  141. package/templates/.agents/skills/block-task/SKILL.en.md +2 -2
  142. package/templates/.agents/skills/block-task/SKILL.zh-CN.md +2 -2
  143. package/templates/.agents/skills/block-task/config/verify.json +1 -1
  144. package/templates/.agents/skills/cancel-task/SKILL.en.md +3 -3
  145. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +3 -3
  146. package/templates/.agents/skills/cancel-task/config/verify.json +1 -1
  147. package/templates/.agents/skills/code-task/SKILL.en.md +7 -7
  148. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +8 -8
  149. package/templates/.agents/skills/code-task/config/verify.en.json +1 -1
  150. package/templates/.agents/skills/code-task/config/verify.zh-CN.json +1 -1
  151. package/templates/.agents/skills/commit/reference/issue-metadata-sync.en.md +1 -1
  152. package/templates/.agents/skills/commit/reference/issue-metadata-sync.zh-CN.md +1 -1
  153. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +1 -1
  154. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +1 -1
  155. package/templates/.agents/skills/complete-task/SKILL.en.md +2 -2
  156. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +2 -2
  157. package/templates/.agents/skills/complete-task/config/verify.en.json +2 -2
  158. package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +2 -2
  159. package/templates/.agents/skills/complete-task/reference/external-delivery.en.md +1 -1
  160. package/templates/.agents/skills/complete-task/reference/external-delivery.zh-CN.md +1 -1
  161. package/templates/.agents/skills/create-pr/SKILL.en.md +2 -2
  162. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +2 -2
  163. package/templates/.agents/skills/create-pr/config/verify.json +1 -1
  164. package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +2 -2
  165. package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +2 -2
  166. package/templates/.agents/skills/create-task/SKILL.en.md +5 -5
  167. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +5 -5
  168. package/templates/.agents/skills/create-task/config/verify.json +1 -1
  169. package/templates/.agents/skills/import-issue/SKILL.en.md +8 -8
  170. package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +8 -8
  171. package/templates/.agents/skills/import-issue/config/verify.json +2 -2
  172. package/templates/.agents/skills/plan-task/SKILL.en.md +4 -4
  173. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +4 -4
  174. package/templates/.agents/skills/plan-task/config/verify.en.json +1 -1
  175. package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +1 -1
  176. package/templates/.agents/skills/restore-task/SKILL.en.md +11 -6
  177. package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +11 -6
  178. package/templates/.agents/skills/restore-task/config/verify.json +1 -1
  179. package/templates/.agents/skills/review-analysis/SKILL.en.md +3 -3
  180. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +3 -3
  181. package/templates/.agents/skills/review-analysis/config/verify.en.json +1 -1
  182. package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +1 -1
  183. package/templates/.agents/skills/review-analysis/reference/report-template.en.md +7 -7
  184. package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +6 -6
  185. package/templates/.agents/skills/review-code/SKILL.en.md +5 -4
  186. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +5 -4
  187. package/templates/.agents/skills/review-code/config/verify.en.json +1 -1
  188. package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -1
  189. package/templates/.agents/skills/review-code/reference/report-template.en.md +12 -12
  190. package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +12 -12
  191. package/templates/.agents/skills/review-plan/SKILL.en.md +3 -3
  192. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +3 -3
  193. package/templates/.agents/skills/review-plan/config/verify.en.json +1 -1
  194. package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +1 -1
  195. package/templates/.agents/skills/review-plan/reference/report-template.en.md +7 -7
  196. package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +7 -7
  197. package/templates/.agents/skills/review-pr/config/verify.en.json +1 -1
  198. package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +1 -1
  199. package/templates/.agents/skills/review-pr/reference/evidence-grading.en.md +3 -3
  200. package/templates/.agents/skills/review-pr/reference/evidence-grading.zh-CN.md +3 -3
  201. package/templates/.agents/skills/run-manual-validation/config/verify.en.json +1 -1
  202. package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +1 -1
  203. package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
  204. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
  205. package/templates/.agents/templates/task.en.md +1 -1
  206. package/templates/.agents/templates/task.zh-CN.md +1 -1
  207. package/templates/.claude/commands/import-issue.en.md +1 -1
  208. package/templates/.claude/commands/import-issue.zh-CN.md +1 -1
  209. package/templates/.claude/commands/restore-task.en.md +1 -1
  210. package/templates/.claude/commands/restore-task.zh-CN.md +1 -1
  211. package/dist/lib/host-control/audit.js +0 -19
  212. package/dist/lib/host-control/client.js +0 -142
  213. package/dist/lib/host-control/command.js +0 -49
  214. package/dist/lib/host-control/path.js +0 -160
  215. package/dist/lib/host-control/server.js +0 -159
  216. package/dist/lib/host-control/service.js +0 -112
  217. package/dist/lib/internal/host-control.js +0 -28
  218. package/lib/host-control/audit.ts +0 -22
  219. package/lib/host-control/client.ts +0 -172
  220. package/lib/host-control/command.ts +0 -60
  221. package/lib/host-control/path.ts +0 -167
  222. package/lib/host-control/server.ts +0 -180
  223. package/lib/host-control/service.ts +0 -133
  224. package/lib/internal/host-control.ts +0 -28
package/README.md CHANGED
@@ -95,13 +95,9 @@ Every command above works the same way in Claude Code, Codex, Antigravity CLI, a
95
95
 
96
96
  ### Sandbox control boundary
97
97
 
98
- Task-bound sandboxes use a host-only control service for lifecycle and workflow writes. On Linux its fixed endpoint is `/run/user/<uid>/agent-infra/host-control.sock`; on macOS it is `/Users/<login>/Library/Application Support/agent-infra/run/host-control.sock`, resolved from the account identity rather than `HOME` or `TMPDIR`. The service directory is private (`0700`) and the socket is private (`0600`). If the endpoint or sandbox identity is unavailable, task-control commands fail closed.
98
+ Direct-host lifecycle and workflow commands execute in the current CLI process. Task-bound sandboxes route requests to their broker; the broker verifies the request, manifest, generation, owner, lease, and controller before starting a one-request executor. There is no host socket, worker token, or user-level task-control service.
99
99
 
100
- The shell installer creates and starts the user-scoped service through systemd user units on Linux or launchd user agents on macOS. After a manual npm install, run `agent-infra-internal host-control install`, then enable the generated user service; `agent-infra-internal host-control status` reports endpoint health. No root or setuid service is required.
101
-
102
- On macOS, the installer retries service registration during reload. If registration or startup still fails, it exits with an error and a recovery command.
103
-
104
- The task directory exposed inside a task-bound sandbox is a writable projection. Artifact commands accept only typed workflow operations and canonical artifact basenames. The host opens candidates with `O_NOFOLLOW`, reads and hashes them through one file descriptor, checks observable metadata changes, and atomically lands validated bytes into the authoritative task directory. Changes to the projection's protected topology fail closed and never turn the projection into task state.
100
+ The task directory exposed inside a task-bound sandbox is writable only for its bound task. Artifact commands accept typed workflow operations and canonical artifact basenames. The executor opens candidates with `O_NOFOLLOW`, reads and hashes them through one file descriptor, checks observable metadata changes, and atomically lands validated bytes into the authoritative task directory.
105
101
 
106
102
  ## Quick Start
107
103
 
package/README.zh-CN.md CHANGED
@@ -94,13 +94,9 @@ agent-infra 的目标就是把这层共享基础设施标准化。它为所有
94
94
 
95
95
  ### 沙箱控制边界
96
96
 
97
- 任务沙箱的生命周期和工作流写操作由宿主专用控制服务执行。Linux 固定端点为 `/run/user/<uid>/agent-infra/host-control.sock`;macOS 固定端点为 `/Users/<login>/Library/Application Support/agent-infra/run/host-control.sock`,路径根据账户身份解析,不读取 `HOME` 或 `TMPDIR`。服务目录权限为 `0700`,socket 权限为 `0600`。端点或沙箱身份不可用时,task-control 命令会失败关闭。
97
+ direct-host 生命周期和工作流命令在当前 CLI 进程中执行。任务沙箱把请求发送给所属 broker;broker 会先校验请求、manifest、generation、owner、lease 和 controller,再启动一次性 executor。系统不再使用宿主 socket、worker token 或用户级 task-control 服务。
98
98
 
99
- Shell 安装脚本会在 Linux 通过 systemd 用户单元、在 macOS 通过 launchd 用户代理创建并启动该服务。手动 npm 安装后,可运行 `agent-infra-internal host-control install` 创建服务配置,再启用对应的用户服务;`agent-infra-internal host-control status` 可检查端点健康状态。整个服务不需要 root 或 setuid。
100
-
101
- macOS 重载期间,安装脚本会重试服务注册;若最终仍无法注册或启动,则以错误退出并提供恢复命令。
102
-
103
- 任务沙箱内的 task 目录是可写投影。产物命令只接受类型化 workflow 操作和规范的产物 basename。宿主使用 `O_NOFOLLOW` 打开候选文件,通过同一个文件描述符读取并计算摘要,检查可观察的元数据变化,再把已校验字节原子落入权威 task 目录。投影的受保护拓扑发生变化时会失败关闭,投影修改不会变成任务状态。
99
+ 任务沙箱内的 task 目录只允许其绑定任务读写。产物命令只接受类型化 workflow 操作和规范的产物 basename。executor 使用 `O_NOFOLLOW` 打开候选文件,通过同一个文件描述符读取并计算摘要,检查可观察的元数据变化,再把已校验字节原子落入权威 task 目录。
104
100
 
105
101
  ## 快速开始
106
102
 
@@ -26,19 +26,6 @@ else
26
26
  internal_cli="$script_dir/../dist/bin/internal-cli.js"
27
27
  fi
28
28
 
29
- command=${1-}
30
- task_control_command=false
31
- case "$command" in
32
- task-lifecycle|task-orchestration|task-finalization) task_control_command=true ;;
33
- esac
34
-
35
- status_mount_present=false
36
- if [ -d /run/agent-infra/control-status ]; then status_mount_present=true; fi
37
- launch_config=${AGENT_INFRA_TASK_ID-}${AGENT_INFRA_CONTROL_TOKEN-}${AGENT_INFRA_CONTROL_GENERATION-}${AGENT_INFRA_CONTROL_DIR-}${AGENT_INFRA_CONTROL_STATUS_DIR-}${AGENT_INFRA_RUNTIME_DIR-}${AGENT_INFRA_EXECUTOR_MANIFEST-}
38
- if [ "$task_control_command" = true ] && [ "$status_mount_present" = true ] && [ -z "$launch_config" ] && [ -z "${AGENT_INFRA_TEST_HOST_CONTROL_ENDPOINT-}" ]; then
39
- printf '%s\n' '{"status":"failed","changed":false,"error":{"code":"SANDBOX_CONTROL_IDENTITY_MISSING","message":"sandbox control identity is present but its launch configuration is missing"}}'
40
- exit 1
41
- fi
42
29
  # The outer launcher owns the Node startup boundary. A task-bound process must
43
30
  # not pass preload, import, or loader injection into the control router.
44
31
  unset NODE_OPTIONS NODE_PATH
@@ -1,8 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { formatTaskViewDiagnostic, guardTaskOperation, resolveSandboxControlTransport, TaskViewOperationError } from '../lib/internal/task-operation-registry.js';
3
3
  import { INTERNAL_HANDLER_ROUTE_SELECTORS } from '../lib/internal/cli-route-inventory.js';
4
- import { hostControlRequestForCommand, requestHostControl, HostControlClientError, HOST_CONTROL_TEST_ENVIRONMENT_KEYS } from '../lib/host-control/client.js';
5
- import { resolveHostControlEndpoint, readHostControlWorkerToken } from '../lib/host-control/path.js';
6
4
  const [major = 0, minor = 0] = process.versions.node.split('.').map((part) => parseInt(part, 10));
7
5
  if (major < 22 || (major === 22 && minor < 9)) {
8
6
  process.stderr.write(`agent-infra-internal requires Node.js >= 22.9.0 (current: ${process.version})\n`);
@@ -18,9 +16,13 @@ const taskWorkflowCommand = command === 'task-artifact'
18
16
  || command === 'task-invalidation'
19
17
  || command === 'task-warning';
20
18
  const manualValidationWorkflowCommand = command === 'manual-validation';
21
- const localTaskControlHelp = taskControlCommand
22
- && (process.argv[3] === '--help' || process.argv[3] === '-h')
23
- && !process.env.AGENT_INFRA_TEST_HOST_CONTROL_ENDPOINT;
19
+ const taskControlMarkers = [
20
+ 'AGENT_INFRA_TASK_ID', 'AGENT_INFRA_CONTROL_TOKEN', 'AGENT_INFRA_CONTROL_GENERATION',
21
+ 'AGENT_INFRA_CONTROL_DIR', 'AGENT_INFRA_CONTROL_STATUS_DIR', 'AGENT_INFRA_RUNTIME_DIR',
22
+ 'AGENT_INFRA_CONTROL_CONTROLLER_BINDING', 'AGENT_INFRA_EXECUTOR_MANIFEST'
23
+ ];
24
+ const markerlessHelp = ['--help', '-h'].includes(process.argv[3] ?? '')
25
+ && taskControlMarkers.every((key) => !process.env[key]);
24
26
  let taskViewGuardFailed = false;
25
27
  try {
26
28
  const guard = guardTaskOperation('internal', command, process.argv.slice(3));
@@ -45,44 +47,8 @@ function taskControlTransportFailure(message, code = 'TASK_CONTROL_TRANSPORT_INV
45
47
  })}\n`);
46
48
  process.exit(1);
47
49
  }
48
- async function runHostControlCommand(commandName, args) {
49
- let response;
50
- try {
51
- const testEndpoint = process.env.AGENT_INFRA_TEST_HOST_CONTROL_ENDPOINT;
52
- const forwardedEnvironment = testEndpoint
53
- ? Object.fromEntries(HOST_CONTROL_TEST_ENVIRONMENT_KEYS.flatMap((key) => process.env[key] === undefined ? [] : [[key, process.env[key]]]))
54
- : {};
55
- response = await requestHostControl({
56
- request: hostControlRequestForCommand(commandName, args, process.cwd(), forwardedEnvironment),
57
- endpoint: process.env.AGENT_INFRA_TEST_HOST_CONTROL_ENDPOINT ?? resolveHostControlEndpoint()
58
- });
59
- }
60
- catch (error) {
61
- if (error instanceof HostControlClientError) {
62
- taskControlTransportFailure(error.message, error.changed === false
63
- ? 'SANDBOX_CONTROL_HOST_AUTHORITY_UNAVAILABLE' : error.code, error.changed);
64
- }
65
- throw error;
66
- }
67
- process.stdout.write(response.stdout);
68
- process.stderr.write(response.stderr);
69
- process.exitCode = response.exitCode;
70
- }
71
- const hostWorkerRequested = process.env.AGENT_INFRA_HOST_CONTROL_WORKER === '1';
72
- const hostWorker = hostWorkerRequested && (() => {
73
- try {
74
- const endpoint = process.env.AGENT_INFRA_TEST_HOST_CONTROL_ENDPOINT ?? resolveHostControlEndpoint();
75
- return process.env.AGENT_INFRA_HOST_CONTROL_WORKER_TOKEN === readHostControlWorkerToken(endpoint);
76
- }
77
- catch {
78
- return false;
79
- }
80
- })();
81
- if (hostWorkerRequested && !hostWorker && (taskControlCommand || taskWorkflowCommand || manualValidationWorkflowCommand)) {
82
- taskControlTransportFailure('host-control worker authorization is invalid', 'SANDBOX_CONTROL_HOST_AUTHORITY_UNAVAILABLE');
83
- }
84
50
  let controlRouted = false;
85
- if (!taskViewGuardFailed && (taskControlCommand || taskWorkflowCommand || manualValidationWorkflowCommand) && !hostWorker && !localTaskControlHelp) {
51
+ if (!taskViewGuardFailed && !markerlessHelp && (taskControlCommand || taskWorkflowCommand || manualValidationWorkflowCommand)) {
86
52
  const transport = resolveSandboxControlTransport(process.env);
87
53
  switch (transport.kind) {
88
54
  case 'fail-closed': {
@@ -91,7 +57,8 @@ if (!taskViewGuardFailed && (taskControlCommand || taskWorkflowCommand || manual
91
57
  break;
92
58
  }
93
59
  case 'direct-host':
94
- await runHostControlCommand(command, process.argv.slice(3));
60
+ // The host CLI is the direct-host authority. Domain handlers retain their
61
+ // task locks, atomic writes, and operation-specific recovery facts.
95
62
  break;
96
63
  case 'broker-client': {
97
64
  const { sandboxControl } = await import('../lib/internal/sandbox-control.js');
@@ -99,7 +66,7 @@ if (!taskViewGuardFailed && (taskControlCommand || taskWorkflowCommand || manual
99
66
  break;
100
67
  }
101
68
  }
102
- controlRouted = true;
69
+ controlRouted = transport.kind === 'broker-client';
103
70
  }
104
71
  if (!controlRouted && !taskViewGuardFailed && internalRouteRegistered)
105
72
  switch (command) {
@@ -128,11 +95,6 @@ if (!controlRouted && !taskViewGuardFailed && internalRouteRegistered)
128
95
  await sandboxControl(process.argv.slice(3));
129
96
  break;
130
97
  }
131
- case 'host-control': {
132
- const { hostControl } = await import('../lib/internal/host-control.js');
133
- await hostControl(process.argv.slice(3));
134
- break;
135
- }
136
98
  case 'agent-client': {
137
99
  const { agentClient } = await import('../lib/internal/agent-client.js');
138
100
  agentClient(process.argv.slice(3));
@@ -22,13 +22,14 @@ function copyRegular(source, destination, mode) {
22
22
  function controlFromEnvironment() {
23
23
  const token = process.env.AGENT_INFRA_CONTROL_TOKEN;
24
24
  const generation = process.env.AGENT_INFRA_CONTROL_GENERATION;
25
+ const rootId = process.env.AGENT_INFRA_CONTROL_ROOT_ID;
25
26
  const channelDir = process.env.AGENT_INFRA_CONTROL_DIR;
26
27
  const statusDir = process.env.AGENT_INFRA_CONTROL_STATUS_DIR;
27
28
  const runtimeDir = process.env.AGENT_INFRA_RUNTIME_DIR;
28
- if (!token || !generation || !channelDir || !statusDir || !runtimeDir) {
29
+ if (!token || !generation || !rootId || !channelDir || !statusDir || !runtimeDir) {
29
30
  throw new Error('CODEX_SANDBOX_CONTROLLER_CONTROL_MISSING');
30
31
  }
31
- return { token, generation, channelDir, statusDir, runtimeDir };
32
+ return { token, generation, rootId, channelDir, statusDir, runtimeDir };
32
33
  }
33
34
  function verifyRuntime(runtimeDir) {
34
35
  let stat;
@@ -188,6 +189,9 @@ function isolatedEnvironment(home, shimDir, contextPath, taskId, control, provid
188
189
  AGENT_INFRA_TASK_ID: taskId,
189
190
  AGENT_INFRA_CONTROL_TOKEN: control.token,
190
191
  AGENT_INFRA_CONTROL_GENERATION: control.generation,
192
+ ...(control.rootId || sourceEnvironment.AGENT_INFRA_CONTROL_ROOT_ID
193
+ ? { AGENT_INFRA_CONTROL_ROOT_ID: control.rootId ?? sourceEnvironment.AGENT_INFRA_CONTROL_ROOT_ID }
194
+ : {}),
191
195
  AGENT_INFRA_CONTROL_DIR: control.channelDir,
192
196
  AGENT_INFRA_CONTROL_STATUS_DIR: control.statusDir,
193
197
  AGENT_INFRA_RUNTIME_DIR: control.runtimeDir,
@@ -6,9 +6,19 @@ 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;
9
10
  function digest(value) {
10
11
  return crypto.createHash('sha256').update(value).digest('hex');
11
12
  }
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
+ }
12
22
  function readRecord(file) {
13
23
  const value = JSON.parse(fs.readFileSync(file, 'utf8'));
14
24
  if (value.schemaVersion !== 1
@@ -202,11 +212,13 @@ function createCodexLifecycleStore(options) {
202
212
  }
203
213
  function read(childThreadId) {
204
214
  const matches = findByChild(childThreadId);
205
- if (matches.length !== 1)
215
+ if (matches.length === 0)
206
216
  throw new Error(`Codex lifecycle child '${childThreadId}' was not found uniquely`);
217
+ if (matches.length > 1)
218
+ throw new Error(`Codex lifecycle child '${childThreadId}' is ambiguous`);
207
219
  return readRecord(matches[0]);
208
220
  }
209
- function consume(childThreadId, consumer, expectedHookDefinitionHash) {
221
+ function consumeInternal(childThreadId, consumer, expectedHookDefinitionHash) {
210
222
  if (!consumer.trim())
211
223
  throw new Error('Codex lifecycle consumer is required');
212
224
  return withWriteLock(() => {
@@ -240,6 +252,32 @@ function createCodexLifecycleStore(options) {
240
252
  return next;
241
253
  });
242
254
  }
255
+ function consume(childThreadId, consumer, expectedHookDefinitionHash) {
256
+ if (!consumer.trim())
257
+ 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
+ return consumeInternal(childThreadId, consumer, expectedHookDefinitionHash);
262
+ }
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
+ }
243
281
  function expireBefore(cutoff) {
244
282
  return withWriteLock(() => {
245
283
  let changed = 0;
@@ -247,6 +285,8 @@ function createCodexLifecycleStore(options) {
247
285
  const current = readRecord(file);
248
286
  if (current.updatedAt >= cutoff)
249
287
  continue;
288
+ if (current.consumer && isRecoveryConsumer(current.consumer))
289
+ continue;
250
290
  if (current.consumer || ['invalid', 'expired', 'stop-ready'].includes(current.state.status)) {
251
291
  fs.unlinkSync(file);
252
292
  changed += 1;
@@ -266,7 +306,7 @@ function createCodexLifecycleStore(options) {
266
306
  return changed;
267
307
  });
268
308
  }
269
- return Object.freeze({ root, apply, applyToSpawn, consume, expireBefore, findByParent, read });
309
+ return Object.freeze({ root, apply, applyToSpawn, consume, claimRecovery, releaseRecovery, expireBefore, findByParent, read });
270
310
  }
271
311
  export { createCodexLifecycleStore, hasActiveCodexLifecycleEvidence };
272
312
  //# sourceMappingURL=store.js.map
@@ -2,6 +2,8 @@ import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { randomUUID } from 'node:crypto';
4
4
  export function fsyncDirectory(directory) {
5
+ if (process.platform === 'win32')
6
+ return;
5
7
  const descriptor = fs.openSync(directory, 'r');
6
8
  try {
7
9
  fs.fsyncSync(descriptor);
@@ -33,7 +33,6 @@ export const INTERNAL_HANDLER_ROUTE_SELECTORS = Object.freeze({
33
33
  'task-create': ['input'],
34
34
  'task-qualification': ['proposal', 'confirm', 'supersede', 'revoke'],
35
35
  'sandbox-control': ['serve', 'execute', 'recover', 'client'],
36
- 'host-control': ['serve', 'status', 'install', 'uninstall'],
37
36
  'agent-client': ['next-steps', 'model-selection'],
38
37
  'codex-lifecycle': ['preflight', 'capability-arm', 'hook-event', 'resolve-start', 'resolve-stop', 'consume'],
39
38
  'codex-sandbox-controller': ['verify-context', 'run'],
@@ -44,7 +43,7 @@ export const INTERNAL_HANDLER_ROUTE_SELECTORS = Object.freeze({
44
43
  'platform-security': ['read', 'dismiss'],
45
44
  'platform-metadata': ['init-labels', 'init-milestones'],
46
45
  'platform-context': ['resolve'],
47
- 'platform-comment': ['list', 'owner', 'backfill', 'sync'],
46
+ 'platform-comment': ['list', 'recover', 'owner', 'backfill', 'sync'],
48
47
  'platform-issue': ['inspect', 'create', 'bind', 'sync'],
49
48
  'platform-pr': ['inspect', 'summary-context', 'resolve-external', 'create', 'bind', 'skip', 'sync', 'change-report', 'summary-sync', 'sync-in-labels'],
50
49
  'platform-pr-review': ['inspect', 'list', 'publish-pr', 'publish-task'],
@@ -54,9 +53,9 @@ export const INTERNAL_HANDLER_ROUTE_SELECTORS = Object.freeze({
54
53
  'task-ledger': ['decision-next-id', 'stage-status', 'finding-upsert', 'finding-respond', 'finding-review', 'decision-upsert', 'rework-intent-upsert'],
55
54
  'task-warning': ['list', 'add', 'set-status'],
56
55
  'task-activity': ['pr-review-inspect', 'pr-review-start', 'pr-review-complete', 'pr-review-terminate'],
57
- 'task-artifact': ['inspect', 'init', 'repair', 'finalize-local'],
56
+ 'task-artifact': ['inspect', 'init', 'preflight', 'finalize-local'],
58
57
  'task-orchestration': ['status', 'progress'],
59
- 'task-review': ['finalize-summary'],
58
+ 'task-review': ['preflight', 'finalize-summary'],
60
59
  'task-event': ['event'],
61
60
  'task-invalidation': ['reconcile'],
62
61
  'task-override': ['diagnose', 'issue', 'consume'],
@@ -22,15 +22,24 @@ function printFailure(format, error) {
22
22
  process.stderr.write(`${error.code}: ${error.message}\n`);
23
23
  process.exitCode = 1;
24
24
  }
25
+ const MANUAL_VALIDATION_STATUS_SECTION = /^###\s+(?:⚠️\s+(?:需人工校验|Manual Validation Required)|✅\s+(?:人工验证已通过|无需人工校验|Manual Validation Passed|No Manual Validation Required)|⏳\s+(?:人工验证待收尾|Manual Validation Pending))\s*$[\s\S]*?(?=^#{1,3}\s|(?![\s\S]))/gmu;
25
26
  function manualSummaryBody(body, phase, transactionId, receiptDigest = '', evidenceDigest = '', prHeadSha = '') {
26
- const withoutPrevious = body
27
- .replace(/^###\s+✅\s+(?:Manual Validation Passed|人工验证已通过)\s*$/gmu, '')
28
- .replace(/^###\s+⏳\s+(?:Manual Validation Pending|人工验证待收尾)\s*$/gmu, '')
29
- .replace(/Manual validation passed(?:\s+→)?/giu, 'Manual validation pending');
27
+ const chinese = /###\s+(?:⚠️\s+需人工校验|✅\s+(?:人工验证已通过|无需人工校验)|⏳\s+人工验证待收尾)\s*$/mu.test(body);
30
28
  const section = phase === 'pending'
31
- ? `### ⏳ Manual Validation Pending\n\nManual validation evidence is staged; transaction ${transactionId} is awaiting final promotion.`
32
- : `### ✅ Manual Validation Passed\n\nManual validation passed; transaction=${transactionId}; receipt=${receiptDigest}; evidence=${evidenceDigest}; head=${prHeadSha}.`;
33
- return `${withoutPrevious.replace(/\s+$/u, '')}\n\n${section}\n`;
29
+ ? chinese
30
+ ? `### ⏳ 人工验证待收尾\n\n人工验证证据已暂存;事务 ${transactionId} 正在等待最终完成。`
31
+ : `### ⏳ Manual Validation Pending\n\nManual validation evidence is staged; transaction ${transactionId} is awaiting final promotion.`
32
+ : chinese
33
+ ? `### ✅ 人工验证已通过\n\n人工验证已通过;transaction=${transactionId}; receipt=${receiptDigest}; evidence=${evidenceDigest}; head=${prHeadSha}.`
34
+ : `### ✅ Manual Validation Passed\n\nManual validation passed; transaction=${transactionId}; receipt=${receiptDigest}; evidence=${evidenceDigest}; head=${prHeadSha}.`;
35
+ let inserted = false;
36
+ const updated = body.replace(MANUAL_VALIDATION_STATUS_SECTION, () => {
37
+ if (inserted)
38
+ return '';
39
+ inserted = true;
40
+ return `${section}\n\n`;
41
+ });
42
+ return inserted ? `${updated.replace(/\s+$/u, '')}\n` : `${body.replace(/\s+$/u, '')}\n\n${section}\n`;
34
43
  }
35
44
  function openManualValidationStarted(taskMdPath) {
36
45
  let content;
@@ -242,7 +251,7 @@ async function executeManualValidationTransactionLocked(taskRef, values, cwd, re
242
251
  if (!['applied', 'no-op'].includes(promoted.status))
243
252
  transactionFailure(promoted.error ?? { code: 'MANUAL_VALIDATION_TRANSACTION_RECOVERY_REQUIRED', message: 'final summary promotion failed' });
244
253
  const postWrite = await summaryCommentState(taskRef, { cwd, client: options.client });
245
- if (!postWrite.comment?.body.includes('### ✅ Manual Validation Passed') || postWrite.pullRequest?.head.sha !== receipt.prHeadSha || !manualValidationFinalSummaryProjectionMatches(postWrite.comment.body, receipt))
254
+ if (!/^###\s+✅\s+(?:Manual Validation Passed|人工验证已通过)\s*$/mu.test(postWrite.comment?.body ?? '') || postWrite.pullRequest?.head.sha !== receipt.prHeadSha || !manualValidationFinalSummaryProjectionMatches(postWrite.comment?.body ?? '', receipt))
246
255
  transactionFailure({ code: 'MANUAL_VALIDATION_TRANSACTION_RECOVERY_REQUIRED', message: 'final summary post-write verification failed' });
247
256
  transitionAndPersist('committed', { postWriteVerified: true });
248
257
  return result('applied', null, { transaction, receipt, idempotent: false });
@@ -2,9 +2,11 @@ import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { normalizeAgentToken, AGENT_USAGE_HINT } from '../agent-clients/tokens.js';
4
4
  import { checkPlatformCommentOwner, listPlatformComments, syncPlatformComment } from '../platform/issue-comments.js';
5
+ import { recoverTaskFromComments } from '../platform/task-recovery.js';
5
6
  import { backfillCompletionComments } from '../platform/completion-backfill.js';
6
7
  import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.js';
7
8
  const USAGE = `Usage: agent-infra-internal platform-comment list --issue <token> [--cwd <path>]
9
+ agent-infra-internal platform-comment recover --issue <token> --task-id <TASK-id> --output <staging-task.md> [--cwd <path>]
8
10
  agent-infra-internal platform-comment owner <task-ref> [--cwd <path>]
9
11
  agent-infra-internal platform-comment backfill <task-ref> --agent <agent> [--cwd <path>]
10
12
  agent-infra-internal platform-comment sync <task-ref> --kind <kind> --agent <agent> [--artifact <file>] [--body-file <path|->] [--status-label <label>] [--backfill] [--cwd <path>]
@@ -26,7 +28,7 @@ function parseFlags(args, start) {
26
28
  const seen = new Set();
27
29
  for (let index = start; index < args.length; index += 1) {
28
30
  const flag = args[index];
29
- if (!['--issue', '--cwd', '--kind', '--agent', '--artifact', '--body-file', '--status-label', '--backfill'].includes(flag)) {
31
+ if (!['--issue', '--task-id', '--output', '--cwd', '--kind', '--agent', '--artifact', '--body-file', '--status-label', '--backfill'].includes(flag)) {
30
32
  return { values, error: `unknown option '${flag}'` };
31
33
  }
32
34
  if (seen.has(flag))
@@ -58,6 +60,7 @@ async function platformComment(args = []) {
58
60
  const operation = args[0];
59
61
  if (!operation || ![
60
62
  internalHandlerRoute('platform-comment', 'list', operation),
63
+ internalHandlerRoute('platform-comment', 'recover', operation),
61
64
  internalHandlerRoute('platform-comment', 'owner', operation),
62
65
  internalHandlerRoute('platform-comment', 'backfill', operation),
63
66
  internalHandlerRoute('platform-comment', 'sync', operation)
@@ -65,7 +68,7 @@ async function platformComment(args = []) {
65
68
  fail('a valid operation is required');
66
69
  return;
67
70
  }
68
- const hasTaskRef = operation !== 'list';
71
+ const hasTaskRef = operation !== 'list' && operation !== 'recover';
69
72
  const taskRef = hasTaskRef ? args[1] : undefined;
70
73
  if (hasTaskRef && (!taskRef || taskRef.startsWith('--'))) {
71
74
  fail(`${operation} requires a task ref`);
@@ -91,6 +94,61 @@ async function platformComment(args = []) {
91
94
  finish(await listPlatformComments(issue, cwd));
92
95
  return;
93
96
  }
97
+ if (internalHandlerRoute('platform-comment', 'recover', operation)) {
98
+ const issue = parsed.values.issue;
99
+ const taskId = parsed.values.taskId;
100
+ const output = parsed.values.output;
101
+ if (typeof issue !== 'string' || !issue || typeof taskId !== 'string' || !/^TASK-\d{8}-\d{6}$/.test(taskId)
102
+ || typeof output !== 'string' || !output) {
103
+ fail('recover requires --issue <token> --task-id <TASK-id> --output <staging-task.md>');
104
+ return;
105
+ }
106
+ const unexpected = Object.keys(parsed.values).find((key) => !['issue', 'taskId', 'output', 'cwd'].includes(key));
107
+ if (unexpected) {
108
+ fail(`recover does not accept '--${unexpected}'`);
109
+ return;
110
+ }
111
+ const outputPath = path.resolve(cwd, output);
112
+ const workspaceDir = path.resolve(cwd, '.agents', 'workspace');
113
+ const stagingDir = path.dirname(outputPath);
114
+ const stagingParent = fs.existsSync(stagingDir) ? path.dirname(fs.realpathSync(stagingDir)) : '';
115
+ const realWorkspace = fs.existsSync(workspaceDir) ? fs.realpathSync(workspaceDir) : '';
116
+ if (path.basename(outputPath) !== 'task.md' || stagingParent !== realWorkspace
117
+ || !path.basename(stagingDir).startsWith('.restore-staging-') || !fs.existsSync(stagingDir)
118
+ || !fs.lstatSync(stagingDir).isDirectory() || fs.existsSync(outputPath)) {
119
+ fail('recover output must be a new task.md inside an existing .agents/workspace/.restore-staging-* directory');
120
+ return;
121
+ }
122
+ const listed = await listPlatformComments(issue, cwd);
123
+ if (listed.status === 'failed' || listed.status === 'blocked' || !listed.comments) {
124
+ finish(listed);
125
+ return;
126
+ }
127
+ try {
128
+ const taskContent = recoverTaskFromComments({ taskId, comments: listed.comments });
129
+ fs.writeFileSync(outputPath, taskContent, { encoding: 'utf8', flag: 'wx', mode: 0o600 });
130
+ finish({
131
+ ...listed,
132
+ status: 'applied',
133
+ changed: true,
134
+ operations: [{ name: `recovery:task:${taskId}`, status: 'applied', reasonCode: null }],
135
+ error: null
136
+ });
137
+ }
138
+ catch (error) {
139
+ finish({
140
+ ...listed,
141
+ status: 'failed',
142
+ changed: false,
143
+ error: {
144
+ code: 'RECOVERY_EVIDENCE_MISSING',
145
+ message: error instanceof Error ? error.message : String(error),
146
+ retryable: false
147
+ }
148
+ });
149
+ }
150
+ return;
151
+ }
94
152
  if (internalHandlerRoute('platform-comment', 'owner', operation)) {
95
153
  const unexpected = Object.keys(parsed.values).find((key) => key !== 'cwd');
96
154
  if (unexpected) {
@@ -1,6 +1,6 @@
1
1
  import { parseArtifactCommand, executeArtifactCommand } from '../task/artifact-command.js';
2
2
  import { ensureInternalHandlerRoute } from './cli-route-inventory.js';
3
- const USAGE = `Usage: agent-infra-internal task-artifact <N | TASK-id> inspect --family <family>\n agent-infra-internal task-artifact <N | TASK-id> init --family <family> --artifact <artifact> [--locale <zh-CN|en>]\n agent-infra-internal task-artifact <N | TASK-id> repair --family <family> --artifact <artifact> --expected-sha256 <sha256> --expected-semantic-digest <digest>\n agent-infra-internal task-artifact <N | TASK-id> finalize-local --family <analysis|plan|code> --artifact <artifact>\n\nInspect, initialize, repair, or finalize a workflow artifact without changing task state.\n`;
3
+ const USAGE = `Usage: agent-infra-internal task-artifact <N | TASK-id> inspect --family <family>\n agent-infra-internal task-artifact <N | TASK-id> init --family <family> --artifact <artifact> [--locale <zh-CN|en>]\n agent-infra-internal task-artifact <N | TASK-id> preflight|finalize-local --family <analysis|plan|code> --artifact <artifact> [--recovery-id <id>]\n\nInspect, initialize, preflight, or finalize a workflow artifact without changing task state.\n`;
4
4
  function taskArtifact(args = []) {
5
5
  if (!ensureInternalHandlerRoute('task-artifact', args))
6
6
  return;
@@ -20,12 +20,12 @@ function parseFailure(error) {
20
20
  return message.replace(/^TASK_CONTROL_OPERATION_INVALID: /u, '');
21
21
  }
22
22
  async function taskFinalization(args = []) {
23
- if (!ensureInternalHandlerRoute('task-finalization', args))
24
- return;
25
23
  if (args[0] === '--help' || args[0] === '-h') {
26
24
  process.stdout.write(USAGE);
27
25
  return;
28
26
  }
27
+ if (!ensureInternalHandlerRoute('task-finalization', args))
28
+ return;
29
29
  let operation;
30
30
  try {
31
31
  operation = parseTaskControlOperation('task-finalization', args);
@@ -2,7 +2,7 @@ import { createDirectHostExecutionContext, dispatchTaskControlOperation, parseTa
2
2
  import { lifecycleIntentCatalog } from '../task/lifecycle.js';
3
3
  import { detectRepoRoot, resolveTaskRef } from '../task/resolve-ref.js';
4
4
  import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.js';
5
- const USAGE = `Usage: agent-infra-internal task-lifecycle <N | TASK-id> <intent> --agent <agent> [intent flags] [--dry-run]\n\nIntents: ${lifecycleIntentCatalog.join(', ')}\nOverride: --override-ticket <ticket> --override-target <target> --override-scope <scope>\n`;
5
+ const USAGE = `Usage: agent-infra-internal task-lifecycle <N | TASK-id> <intent> --agent <agent> [intent flags] [--dry-run]\n\nIntents: ${lifecycleIntentCatalog.join(', ')}\nrecover-started: --stage <stage> --round <round> --artifact <artifact> --reason <reason>\nOverride: --override-ticket <ticket> --override-target <target> --override-scope <scope>\n`;
6
6
  function usageFailure(message) {
7
7
  process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: 'LIFECYCLE_PAYLOAD_INVALID', message } })}\n`);
8
8
  process.stderr.write(USAGE);
@@ -13,12 +13,12 @@ function parseFailure(error) {
13
13
  return message.replace(/^TASK_CONTROL_OPERATION_INVALID: /u, '');
14
14
  }
15
15
  async function taskLifecycle(args = []) {
16
- if (!ensureInternalHandlerRoute('task-lifecycle', args))
17
- return;
18
16
  if (args[0] === '--help' || args[0] === '-h') {
19
17
  process.stdout.write(USAGE);
20
18
  return;
21
19
  }
20
+ if (!ensureInternalHandlerRoute('task-lifecycle', args))
21
+ return;
22
22
  let operation;
23
23
  try {
24
24
  operation = parseTaskControlOperation('task-lifecycle', args);
@@ -54,7 +54,7 @@ async function taskLifecycle(args = []) {
54
54
  }
55
55
  const result = await dispatchTaskControlOperation(createDirectHostExecutionContext({ repoRoot }), operation);
56
56
  process.stdout.write(`${JSON.stringify(result)}\n`);
57
- if (result.status === 'failed')
57
+ if (result.status === 'failed' || result.status === 'owner-unknown' || result.status === 'conflict')
58
58
  process.exitCode = 1;
59
59
  }
60
60
  export { taskLifecycle };
@@ -16,10 +16,6 @@ function internalTaskRoutes() {
16
16
  descriptor('internal', 'sandbox-control', 'execute', 'non-task', 'progress', 'none'),
17
17
  descriptor('internal', 'sandbox-control', 'recover', 'conditional', 'recovery', 'delegated'),
18
18
  descriptor('internal', 'sandbox-control', 'client', 'conditional', 'progress', 'delegated'),
19
- descriptor('internal', 'host-control', 'serve', 'non-task', 'progress', 'none'),
20
- descriptor('internal', 'host-control', 'status', 'non-task', 'diagnostic', 'none'),
21
- descriptor('internal', 'host-control', 'install', 'non-task', 'progress', 'none'),
22
- descriptor('internal', 'host-control', 'uninstall', 'non-task', 'progress', 'none'),
23
19
  descriptor('internal', 'agent-client', 'next-steps', 'non-task', 'diagnostic', 'none'),
24
20
  descriptor('internal', 'agent-client', 'model-selection', 'non-task', 'diagnostic', 'none'),
25
21
  descriptor('internal', 'codex-lifecycle', 'preflight', 'non-task', 'diagnostic', 'none'),
@@ -51,6 +47,7 @@ function internalTaskRoutes() {
51
47
  descriptor('internal', 'platform-metadata', 'init-milestones', 'non-task', 'remote-write', 'none'),
52
48
  descriptor('internal', 'platform-context', 'resolve', 'non-task', 'diagnostic', 'none'),
53
49
  descriptor('internal', 'platform-comment', 'list', 'non-task', 'diagnostic', 'none'),
50
+ descriptor('internal', 'platform-comment', 'recover', 'non-task', 'recovery', 'none'),
54
51
  descriptor('internal', 'platform-comment', 'owner', 'task-bound', 'diagnostic'),
55
52
  descriptor('internal', 'platform-comment', 'backfill', 'task-bound', 'remote-write'),
56
53
  descriptor('internal', 'platform-comment', 'sync', 'task-bound', 'remote-write'),
@@ -96,10 +93,11 @@ function internalTaskRoutes() {
96
93
  descriptor('internal', 'task-activity', 'pr-review-terminate', 'task-bound', 'progress'),
97
94
  descriptor('internal', 'task-artifact', 'inspect', 'task-bound', 'diagnostic'),
98
95
  descriptor('internal', 'task-artifact', 'init', 'task-bound', 'artifact-write'),
99
- descriptor('internal', 'task-artifact', 'repair', 'task-bound', 'artifact-write'),
96
+ descriptor('internal', 'task-artifact', 'preflight', 'task-bound', 'artifact-write'),
100
97
  descriptor('internal', 'task-artifact', 'finalize-local', 'task-bound', 'artifact-write'),
101
98
  descriptor('internal', 'task-orchestration', 'status', 'task-bound', 'diagnostic'),
102
99
  descriptor('internal', 'task-orchestration', 'progress', 'task-bound', 'progress'),
100
+ descriptor('internal', 'task-review', 'preflight', 'task-bound', 'artifact-write'),
103
101
  descriptor('internal', 'task-review', 'finalize-summary', 'task-bound', 'progress'),
104
102
  descriptor('internal', 'task-event', 'event', 'task-bound', 'progress'),
105
103
  descriptor('internal', 'task-invalidation', 'reconcile', 'task-bound', 'progress'),
@@ -176,7 +174,7 @@ export const TASK_OPERATION_DESCRIPTORS = Object.freeze([
176
174
  ...PUBLIC_OPERATION_DESCRIPTORS
177
175
  ]);
178
176
  export const INTERNAL_DISPATCHER_ROUTES = Object.freeze([
179
- 'task-create', 'task-qualification', 'sandbox-control', 'host-control', 'agent-client', 'codex-lifecycle', 'codex-sandbox-controller',
177
+ 'task-create', 'task-qualification', 'sandbox-control', 'agent-client', 'codex-lifecycle', 'codex-sandbox-controller',
180
178
  'git-workflow', 'task-delivery', 'release-workflow', 'platform-release-notes', 'platform-security', 'platform-metadata', 'platform-context',
181
179
  'platform-comment', 'platform-issue', 'platform-pr', 'platform-pr-review', 'pr-review-grade',
182
180
  'platform-checks', 'task-context', 'task-ledger', 'task-warning', 'task-activity', 'task-artifact',
@@ -288,28 +286,39 @@ function taskMarkerState(env) {
288
286
  return 'incomplete';
289
287
  }
290
288
  export function resolveSandboxControlTransport(env = process.env, options = {}) {
291
- const configuredStatusDir = env.AGENT_INFRA_CONTROL_STATUS_DIR;
292
- const fixedStatusDir = options.statusMountPath
293
- ?? SANDBOX_CONTROL_STATUS_MOUNT;
294
289
  const hasAnyMarker = TASK_MARKER_KEYS.some((key) => Boolean(env[key]))
295
290
  || Boolean(env.AGENT_INFRA_CONTROL_CONTROLLER_BINDING)
296
291
  || Boolean(env.AGENT_INFRA_EXECUTOR_MANIFEST);
297
- const fixedStatusProbe = !env.AGENT_INFRA_TEST_HOST_CONTROL_ENDPOINT && path.isAbsolute(fixedStatusDir)
292
+ const fixedStatusDir = options.statusMountPath
293
+ ?? SANDBOX_CONTROL_STATUS_MOUNT;
294
+ const fixedStatusProbe = path.isAbsolute(fixedStatusDir)
298
295
  ? nativeDirectoryProbe(fixedStatusDir) : 'absent';
299
296
  if (fixedStatusProbe === 'unknown') {
300
297
  return { kind: 'fail-closed', reasonCode: 'SANDBOX_CONTROL_IDENTITY_UNAVAILABLE' };
301
298
  }
302
299
  const fixedStatusMounted = fixedStatusProbe === 'present';
300
+ // A fixed status mount is installed only in a task-bound sandbox. Its
301
+ // presence is not mutable by the sandbox process, so marker removal must
302
+ // never make that process look like a direct host invocation.
303
+ if (!hasAnyMarker) {
304
+ return fixedStatusMounted
305
+ ? { kind: 'fail-closed', reasonCode: 'SANDBOX_CONTROL_CONFIGURATION_INCOMPLETE' }
306
+ : { kind: 'direct-host', reasonCode: null };
307
+ }
308
+ const hasCompleteConfig = TASK_CONTROL_CONFIG_KEYS.every((key) => Boolean(env[key]));
309
+ if (!hasCompleteConfig) {
310
+ return { kind: 'fail-closed', reasonCode: 'SANDBOX_CONTROL_CONFIGURATION_INCOMPLETE' };
311
+ }
312
+ const configuredStatusDir = env.AGENT_INFRA_CONTROL_STATUS_DIR;
303
313
  const configuredStatusProbe = configuredStatusDir && path.isAbsolute(configuredStatusDir)
304
314
  ? nativeDirectoryProbe(configuredStatusDir) : 'absent';
305
315
  if (configuredStatusProbe === 'unknown') {
306
316
  return { kind: 'fail-closed', reasonCode: 'SANDBOX_CONTROL_IDENTITY_UNAVAILABLE' };
307
317
  }
308
- const statusDir = configuredStatusDir && path.isAbsolute(configuredStatusDir) && configuredStatusProbe === 'present'
309
- ? configuredStatusDir
318
+ const statusDir = configuredStatusDir && path.isAbsolute(configuredStatusDir)
319
+ ? configuredStatusProbe === 'present' ? configuredStatusDir : null
310
320
  : fixedStatusMounted ? fixedStatusDir : null;
311
321
  const statusMounted = statusDir !== null;
312
- const hasCompleteConfig = TASK_CONTROL_CONFIG_KEYS.every((key) => Boolean(env[key]));
313
322
  if (env.AGENT_INFRA_EXECUTOR_MANIFEST || env.AGENT_INFRA_CONTROL_CONTROLLER_BINDING) {
314
323
  return { kind: 'fail-closed', reasonCode: 'TASK_CONTROL_TRANSPORT_INVALID' };
315
324
  }
@@ -16,12 +16,12 @@ function parseFailure(error) {
16
16
  return message.replace(/^TASK_CONTROL_OPERATION_INVALID: /u, '');
17
17
  }
18
18
  async function taskOrchestration(args = []) {
19
- if (!ensureInternalHandlerRoute('task-orchestration', args))
20
- return;
21
19
  if (args[0] === '--help' || args[0] === '-h') {
22
20
  process.stdout.write(USAGE);
23
21
  return;
24
22
  }
23
+ if (!ensureInternalHandlerRoute('task-orchestration', args))
24
+ return;
25
25
  const route = internalHandlerRoute('task-orchestration', 'status', args[1] ?? '')
26
26
  ? 'status'
27
27
  : internalHandlerRoute('task-orchestration', 'progress', args[1] ? 'progress' : '')