@fitlab-ai/agent-infra 0.9.1 → 0.9.2

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 (283) hide show
  1. package/README.md +16 -4
  2. package/README.zh-CN.md +16 -4
  3. package/bin/cli.ts +12 -0
  4. package/dist/bin/cli.js +14 -0
  5. package/dist/bin/internal-cli.js +15 -0
  6. package/dist/lib/agent-client.js +163 -0
  7. package/dist/lib/agent-clients/adapter.js +294 -0
  8. package/dist/lib/agent-clients/adapters/claude-code.js +62 -0
  9. package/dist/lib/agent-clients/adapters/codex-sandbox.js +210 -0
  10. package/dist/lib/agent-clients/adapters/codex.js +54 -0
  11. package/dist/lib/agent-clients/adapters/gemini-cli.js +64 -0
  12. package/dist/lib/agent-clients/adapters/index.js +20 -0
  13. package/dist/lib/agent-clients/adapters/opencode.js +64 -0
  14. package/dist/lib/agent-clients/config.js +177 -0
  15. package/dist/lib/agent-clients/custom-tuis.js +99 -0
  16. package/dist/lib/agent-clients/invocation.js +9 -0
  17. package/dist/lib/agent-clients/next-steps.js +38 -0
  18. package/dist/lib/agent-clients/project-assets.js +45 -0
  19. package/dist/lib/agent-clients/reconcile.js +235 -0
  20. package/dist/lib/agent-clients/registry.js +72 -0
  21. package/dist/lib/agent-clients/schema.js +57 -0
  22. package/dist/lib/agent-clients/types.js +29 -0
  23. package/dist/lib/builtin-tuis.js +9 -14
  24. package/dist/lib/defaults.json +85 -73
  25. package/dist/lib/git/review-snapshot.js +25 -10
  26. package/dist/lib/git/reviewed-commit-equivalence.js +110 -0
  27. package/dist/lib/init.js +47 -91
  28. package/dist/lib/internal/agent-client.js +117 -0
  29. package/dist/lib/internal/git-workflow.js +1 -1
  30. package/dist/lib/internal/release-workflow.js +71 -16
  31. package/dist/lib/internal/task-orchestration.js +151 -0
  32. package/dist/lib/internal/task-review.js +65 -0
  33. package/dist/lib/merge.js +2 -1
  34. package/dist/lib/platform/adapters.js +38 -1
  35. package/dist/lib/platform/change-request-git-evidence.js +113 -0
  36. package/dist/lib/platform/merged-pr-equivalence.js +41 -11
  37. package/dist/lib/platform/pr-checks.js +38 -10
  38. package/dist/lib/platform/pull-requests.js +110 -6
  39. package/dist/lib/platform/releases.js +1 -1
  40. package/dist/lib/platform/verification-required.js +8 -10
  41. package/dist/lib/prompt.js +15 -3
  42. package/dist/lib/release/channels.js +4 -1
  43. package/dist/lib/release/workflow.js +4 -2
  44. package/dist/lib/remove-dir.js +25 -0
  45. package/dist/lib/run/tui.js +8 -2
  46. package/dist/lib/sandbox/agent-client-reconciler.js +234 -0
  47. package/dist/lib/sandbox/clipboard/linux.js +2 -1
  48. package/dist/lib/sandbox/commands/create.js +76 -180
  49. package/dist/lib/sandbox/commands/enter.js +17 -1
  50. package/dist/lib/sandbox/commands/prune.js +2 -2
  51. package/dist/lib/sandbox/commands/rm.js +3 -2
  52. package/dist/lib/sandbox/config.js +21 -4
  53. package/dist/lib/sandbox/constants.js +3 -0
  54. package/dist/lib/sandbox/dockerfile.js +2 -1
  55. package/dist/lib/sandbox/managed-fs.js +10 -5
  56. package/dist/lib/sandbox/recovery.js +52 -36
  57. package/dist/lib/sandbox/shell.js +1 -1
  58. package/dist/lib/sandbox/tool-types.js +8 -0
  59. package/dist/lib/sandbox/tools.js +2 -126
  60. package/dist/lib/task/artifact-lifecycle.js +1 -49
  61. package/dist/lib/task/commands/decisions.js +50 -20
  62. package/dist/lib/task/delegation-receipts.js +107 -0
  63. package/dist/lib/task/events.js +100 -1
  64. package/dist/lib/task/orchestration.js +420 -0
  65. package/dist/lib/task/review-artifacts.js +143 -59
  66. package/dist/lib/task/review-finalization.js +166 -0
  67. package/dist/lib/task/review-fingerprint.js +4 -0
  68. package/dist/lib/task/verification-checks.js +2 -1
  69. package/dist/lib/task/verification-engine.js +156 -28
  70. package/dist/lib/task/verification-review.js +1 -1
  71. package/dist/lib/task/verification.js +3 -1
  72. package/dist/lib/task/workspace-snapshot.js +34 -0
  73. package/dist/lib/update.js +42 -161
  74. package/lib/agent-client.ts +214 -0
  75. package/lib/agent-clients/adapter.ts +461 -0
  76. package/lib/agent-clients/adapters/claude-code.ts +70 -0
  77. package/lib/agent-clients/adapters/codex-sandbox.ts +245 -0
  78. package/lib/agent-clients/adapters/codex.ts +58 -0
  79. package/lib/agent-clients/adapters/gemini-cli.ts +65 -0
  80. package/lib/agent-clients/adapters/index.ts +27 -0
  81. package/lib/agent-clients/adapters/opencode.ts +66 -0
  82. package/lib/agent-clients/config.ts +278 -0
  83. package/lib/agent-clients/custom-tuis.ts +149 -0
  84. package/lib/agent-clients/invocation.ts +19 -0
  85. package/lib/agent-clients/next-steps.ts +72 -0
  86. package/lib/agent-clients/project-assets.ts +96 -0
  87. package/lib/agent-clients/reconcile.ts +324 -0
  88. package/lib/agent-clients/registry.ts +144 -0
  89. package/lib/agent-clients/schema.ts +62 -0
  90. package/lib/agent-clients/types.ts +78 -0
  91. package/lib/benchmark/schemas/v1.0.0/case-manifest.schema.json +120 -0
  92. package/lib/benchmark/schemas/v1.0.0/common.schema.json +109 -0
  93. package/lib/benchmark/schemas/v1.0.0/grader-result.schema.json +160 -0
  94. package/lib/benchmark/schemas/v1.0.0/run-manifest.schema.json +122 -0
  95. package/lib/benchmark/schemas/v1.0.0/subject.schema.json +227 -0
  96. package/lib/builtin-tuis.ts +16 -15
  97. package/lib/defaults.json +24 -12
  98. package/lib/git/review-snapshot.ts +39 -9
  99. package/lib/git/reviewed-commit-equivalence.ts +135 -0
  100. package/lib/init.ts +53 -121
  101. package/lib/internal/agent-client.ts +131 -0
  102. package/lib/internal/git-workflow.ts +1 -1
  103. package/lib/internal/release-workflow.ts +68 -16
  104. package/lib/internal/task-orchestration.ts +132 -0
  105. package/lib/internal/task-review.ts +65 -0
  106. package/lib/merge.ts +2 -1
  107. package/lib/platform/adapters.ts +142 -3
  108. package/lib/platform/change-request-git-evidence.ts +159 -0
  109. package/lib/platform/merged-pr-equivalence.ts +51 -14
  110. package/lib/platform/pr-checks.ts +50 -22
  111. package/lib/platform/pull-requests.ts +134 -25
  112. package/lib/platform/releases.ts +1 -1
  113. package/lib/platform/verification-required.ts +12 -10
  114. package/lib/prompt.ts +16 -3
  115. package/lib/release/channels.ts +4 -1
  116. package/lib/release/workflow.ts +4 -3
  117. package/lib/remove-dir.ts +28 -0
  118. package/lib/run/tui.ts +9 -1
  119. package/lib/sandbox/agent-client-reconciler.ts +378 -0
  120. package/lib/sandbox/clipboard/linux.ts +2 -1
  121. package/lib/sandbox/commands/create.ts +111 -192
  122. package/lib/sandbox/commands/enter.ts +23 -1
  123. package/lib/sandbox/commands/prune.ts +2 -2
  124. package/lib/sandbox/commands/rm.ts +3 -2
  125. package/lib/sandbox/config.ts +31 -4
  126. package/lib/sandbox/constants.ts +6 -0
  127. package/lib/sandbox/dockerfile.ts +2 -1
  128. package/lib/sandbox/managed-fs.ts +18 -5
  129. package/lib/sandbox/recovery.ts +98 -67
  130. package/lib/sandbox/shell.ts +1 -1
  131. package/lib/sandbox/tool-types.ts +143 -0
  132. package/lib/sandbox/tools.ts +5 -157
  133. package/lib/task/artifact-lifecycle.ts +1 -38
  134. package/lib/task/commands/decisions.ts +53 -20
  135. package/lib/task/delegation-receipts.ts +170 -0
  136. package/lib/task/events.ts +110 -2
  137. package/lib/task/orchestration.ts +543 -0
  138. package/lib/task/review-artifacts.ts +201 -69
  139. package/lib/task/review-finalization.ts +275 -0
  140. package/lib/task/review-fingerprint.ts +5 -0
  141. package/lib/task/verification-checks.ts +2 -1
  142. package/lib/task/verification-engine.ts +177 -28
  143. package/lib/task/verification-review.ts +1 -0
  144. package/lib/task/verification.ts +5 -2
  145. package/lib/task/workspace-snapshot.ts +36 -0
  146. package/lib/update.ts +48 -184
  147. package/package.json +2 -2
  148. package/templates/.agents/README.en.md +21 -20
  149. package/templates/.agents/README.zh-CN.md +21 -20
  150. package/templates/.agents/hooks/lifecycle-delegation.js +64 -0
  151. package/templates/.agents/rules/README.en.md +2 -0
  152. package/templates/.agents/rules/README.zh-CN.md +2 -0
  153. package/templates/.agents/rules/commit-and-pr.en.md +1 -0
  154. package/templates/.agents/rules/commit-and-pr.zh-CN.md +1 -0
  155. package/templates/.agents/rules/human-decision-context.en.md +14 -12
  156. package/templates/.agents/rules/human-decision-context.zh-CN.md +14 -12
  157. package/templates/.agents/rules/lifecycle-orchestration.en.md +21 -0
  158. package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +21 -0
  159. package/templates/.agents/rules/next-step-output.en.md +16 -0
  160. package/templates/.agents/rules/next-step-output.zh-CN.md +16 -0
  161. package/templates/.agents/rules/no-mid-flow-questions.en.md +2 -0
  162. package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +2 -0
  163. package/templates/.agents/rules/release-commands.github.en.md +11 -5
  164. package/templates/.agents/rules/release-commands.github.zh-CN.md +11 -5
  165. package/templates/.agents/rules/review-handshake.en.md +4 -4
  166. package/templates/.agents/rules/review-handshake.zh-CN.md +4 -4
  167. package/templates/.agents/rules/review-method.en.md +113 -0
  168. package/templates/.agents/rules/review-method.zh-CN.md +98 -0
  169. package/templates/.agents/skills/analyze-task/SKILL.en.md +5 -5
  170. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +5 -5
  171. package/templates/.agents/skills/block-task/SKILL.en.md +4 -4
  172. package/templates/.agents/skills/block-task/SKILL.zh-CN.md +4 -4
  173. package/templates/.agents/skills/cancel-task/SKILL.en.md +4 -4
  174. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +5 -5
  175. package/templates/.agents/skills/check-task/SKILL.en.md +16 -16
  176. package/templates/.agents/skills/check-task/SKILL.zh-CN.md +16 -16
  177. package/templates/.agents/skills/close-codescan/SKILL.en.md +4 -4
  178. package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +4 -4
  179. package/templates/.agents/skills/close-dependabot/SKILL.en.md +4 -4
  180. package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +4 -4
  181. package/templates/.agents/skills/code-task/SKILL.en.md +1 -1
  182. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +2 -2
  183. package/templates/.agents/skills/code-task/reference/fix-mode.en.md +14 -16
  184. package/templates/.agents/skills/code-task/reference/fix-mode.zh-CN.md +14 -16
  185. package/templates/.agents/skills/code-task/reference/output-template.en.md +3 -3
  186. package/templates/.agents/skills/code-task/reference/output-template.zh-CN.md +3 -3
  187. package/templates/.agents/skills/commit/SKILL.en.md +11 -1
  188. package/templates/.agents/skills/commit/SKILL.zh-CN.md +11 -1
  189. package/templates/.agents/skills/commit/reference/task-status-update.en.md +24 -28
  190. package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +24 -28
  191. package/templates/.agents/skills/complete-task/SKILL.en.md +2 -2
  192. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +2 -2
  193. package/templates/.agents/skills/create-pr/SKILL.en.md +10 -10
  194. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +10 -10
  195. package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +3 -3
  196. package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +3 -3
  197. package/templates/.agents/skills/create-task/SKILL.en.md +11 -11
  198. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +11 -11
  199. package/templates/.agents/skills/import-codescan/SKILL.en.md +5 -5
  200. package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +5 -5
  201. package/templates/.agents/skills/import-dependabot/SKILL.en.md +5 -5
  202. package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +5 -5
  203. package/templates/.agents/skills/import-issue/SKILL.en.md +5 -5
  204. package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +5 -5
  205. package/templates/.agents/skills/init-labels/SKILL.en.md +4 -4
  206. package/templates/.agents/skills/init-labels/SKILL.zh-CN.md +4 -4
  207. package/templates/.agents/skills/init-milestones/SKILL.en.md +4 -4
  208. package/templates/.agents/skills/init-milestones/SKILL.zh-CN.md +4 -4
  209. package/templates/.agents/skills/plan-task/SKILL.en.md +5 -5
  210. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +5 -5
  211. package/templates/.agents/skills/refine-title/SKILL.en.md +1 -1
  212. package/templates/.agents/skills/refine-title/SKILL.zh-CN.md +1 -1
  213. package/templates/.agents/skills/release/SKILL.en.md +1 -1
  214. package/templates/.agents/skills/review-analysis/SKILL.en.md +13 -2
  215. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +14 -3
  216. package/templates/.agents/skills/review-analysis/config/verify.en.json +6 -0
  217. package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +6 -0
  218. package/templates/.agents/skills/review-analysis/reference/output-templates.en.md +13 -15
  219. package/templates/.agents/skills/review-analysis/reference/output-templates.zh-CN.md +13 -15
  220. package/templates/.agents/skills/review-analysis/reference/report-template.en.md +51 -5
  221. package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +52 -6
  222. package/templates/.agents/skills/review-analysis/reference/review-criteria.en.md +25 -22
  223. package/templates/.agents/skills/review-analysis/reference/review-criteria.zh-CN.md +25 -22
  224. package/templates/.agents/skills/review-code/SKILL.en.md +28 -8
  225. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +28 -8
  226. package/templates/.agents/skills/review-code/config/verify.en.json +7 -0
  227. package/templates/.agents/skills/review-code/config/verify.zh-CN.json +7 -0
  228. package/templates/.agents/skills/review-code/reference/concurrency-risks.en.md +25 -0
  229. package/templates/.agents/skills/review-code/reference/concurrency-risks.zh-CN.md +25 -0
  230. package/templates/.agents/skills/review-code/reference/cross-platform-risks.en.md +27 -0
  231. package/templates/.agents/skills/review-code/reference/cross-platform-risks.zh-CN.md +27 -0
  232. package/templates/.agents/skills/review-code/reference/documentation-antipatterns.en.md +13 -0
  233. package/templates/.agents/skills/review-code/reference/documentation-antipatterns.zh-CN.md +13 -0
  234. package/templates/.agents/skills/review-code/reference/migration-risks.en.md +25 -0
  235. package/templates/.agents/skills/review-code/reference/migration-risks.zh-CN.md +25 -0
  236. package/templates/.agents/skills/review-code/reference/output-templates.en.md +22 -24
  237. package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +22 -24
  238. package/templates/.agents/skills/review-code/reference/report-template.en.md +54 -7
  239. package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +54 -7
  240. package/templates/.agents/skills/review-code/reference/review-criteria.en.md +29 -24
  241. package/templates/.agents/skills/review-code/reference/review-criteria.zh-CN.md +29 -24
  242. package/templates/.agents/skills/review-code/reference/security-risks.en.md +25 -0
  243. package/templates/.agents/skills/review-code/reference/security-risks.zh-CN.md +25 -0
  244. package/templates/.agents/skills/review-plan/SKILL.en.md +13 -2
  245. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +14 -3
  246. package/templates/.agents/skills/review-plan/config/verify.en.json +6 -0
  247. package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +6 -0
  248. package/templates/.agents/skills/review-plan/reference/output-templates.en.md +13 -15
  249. package/templates/.agents/skills/review-plan/reference/output-templates.zh-CN.md +13 -15
  250. package/templates/.agents/skills/review-plan/reference/report-template.en.md +61 -5
  251. package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +61 -5
  252. package/templates/.agents/skills/review-plan/reference/review-criteria.en.md +21 -24
  253. package/templates/.agents/skills/review-plan/reference/review-criteria.zh-CN.md +21 -24
  254. package/templates/.agents/skills/run-task/SKILL.en.md +22 -0
  255. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +22 -0
  256. package/templates/.agents/skills/run-task/config/verify.en.json +9 -0
  257. package/templates/.agents/skills/run-task/config/verify.zh-CN.json +9 -0
  258. package/templates/.agents/skills/test/SKILL.en.md +4 -4
  259. package/templates/.agents/skills/test/SKILL.zh-CN.md +4 -4
  260. package/templates/.agents/skills/test-integration/SKILL.en.md +4 -4
  261. package/templates/.agents/skills/test-integration/SKILL.zh-CN.md +4 -4
  262. package/templates/.agents/skills/update-agent-infra/SKILL.en.md +5 -5
  263. package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +5 -5
  264. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +647 -203
  265. package/templates/.agents/skills/upgrade-dependency/SKILL.en.md +4 -4
  266. package/templates/.agents/skills/upgrade-dependency/SKILL.zh-CN.md +4 -4
  267. package/templates/.agents/skills/watch-pr/SKILL.en.md +8 -8
  268. package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +8 -8
  269. package/templates/.claude/agents/agent-infra-lifecycle-executor.en.md +6 -0
  270. package/templates/.claude/agents/agent-infra-lifecycle-executor.zh-CN.md +6 -0
  271. package/templates/.claude/agents/agent-infra-lifecycle-reviewer.en.md +6 -0
  272. package/templates/.claude/agents/agent-infra-lifecycle-reviewer.zh-CN.md +6 -0
  273. package/templates/.claude/commands/run-task.en.md +8 -0
  274. package/templates/.claude/commands/run-task.zh-CN.md +8 -0
  275. package/templates/.claude/settings.json +24 -0
  276. package/templates/.codex/agents/agent-infra-lifecycle-executor.en.toml +3 -0
  277. package/templates/.codex/agents/agent-infra-lifecycle-executor.zh-CN.toml +3 -0
  278. package/templates/.codex/agents/agent-infra-lifecycle-reviewer.en.toml +3 -0
  279. package/templates/.codex/agents/agent-infra-lifecycle-reviewer.zh-CN.toml +3 -0
  280. package/templates/.gemini/commands/_project_/run-task.en.toml +8 -0
  281. package/templates/.gemini/commands/_project_/run-task.zh-CN.toml +8 -0
  282. package/templates/.opencode/commands/run-task.en.md +11 -0
  283. package/templates/.opencode/commands/run-task.zh-CN.md +11 -0
@@ -1,6 +1,8 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
- import { containerNameCandidates, sandboxBranchLabel, sandboxLabel } from './constants.js';
3
+ import { containerNameCandidates, sandboxBranchLabel, sandboxLabel, sandboxRuntimeCapabilityLabel } from './constants.js';
4
+ import { isAgentClientId } from '../agent-clients/types.js';
5
+ import { createSandboxCapabilityPlan } from './agent-client-reconciler.js';
4
6
  import { toEnginePath } from './engines/wsl2-paths.js';
5
7
  import { sandboxCoreBindMounts } from './mounts.js';
6
8
  import { runEngine, runOkEngine, runVerboseEngine } from './shell.js';
@@ -17,6 +19,19 @@ export function classifySandboxRecovery(snapshot) {
17
19
  message: 'Container identity does not match the requested sandbox branch.'
18
20
  });
19
21
  }
22
+ if (snapshot.runtimeCapabilityOk === false) {
23
+ findings.push({
24
+ repairKind: 'hard-failure',
25
+ message: 'Container runtime capability signature does not match the current sandbox plan.'
26
+ });
27
+ }
28
+ for (const mountPath of snapshot.unexpectedCapabilityMounts ?? []) {
29
+ findings.push({
30
+ repairKind: 'hard-failure',
31
+ message: `Disabled Agent Client capability remains mounted at ${mountPath}.`,
32
+ path: mountPath
33
+ });
34
+ }
20
35
  for (const mount of snapshot.mounts) {
21
36
  if (mount.actualType !== mount.expectedType
22
37
  || !mount.sourceMatches
@@ -74,25 +89,11 @@ export function classifySandboxRecovery(snapshot) {
74
89
  path: '/home/devuser/.bash_aliases'
75
90
  });
76
91
  }
77
- if (snapshot.codex) {
78
- if (!snapshot.codex.commandAvailable) {
79
- findings.push({
80
- repairKind: 'hard-failure',
81
- message: 'Codex is not available on PATH inside the sandbox.'
82
- });
83
- }
84
- if (!snapshot.codex.stateWritable) {
92
+ for (const check of snapshot.agentClientChecks) {
93
+ if (check.applicable && !check.healthy) {
85
94
  findings.push({
86
- repairKind: 'permissions',
87
- message: 'Codex state directory is not writable by devuser.',
88
- path: '/home/devuser/.codex'
89
- });
90
- }
91
- if (snapshot.codex.promptsSourceExists && !snapshot.codex.promptsValid) {
92
- findings.push({
93
- repairKind: 'builtin-link',
94
- message: 'Codex prompts link does not point to the workspace commands directory.',
95
- path: '/home/devuser/.codex/prompts'
95
+ ...check.finding,
96
+ ...(check.repair === undefined ? {} : { repair: check.repair })
96
97
  });
97
98
  }
98
99
  }
@@ -154,8 +155,7 @@ function hostSourceAccessible(hostPath, writable) {
154
155
  }
155
156
  }
156
157
  function expectedMounts(params) {
157
- const { config, branch, actualMounts } = params;
158
- const tools = resolveTools(config);
158
+ const { config, branch, tools, actualMounts } = params;
159
159
  const core = sandboxCoreBindMounts(config, branch).map((mount) => ({
160
160
  path: mount.containerPath,
161
161
  expectedType: 'bind',
@@ -174,7 +174,9 @@ function expectedMounts(params) {
174
174
  }];
175
175
  }));
176
176
  const persistentTools = tools
177
- .filter((tool) => !tool.tmpfs && actualMounts.has(tool.containerMount))
177
+ .filter((tool) => !tool.tmpfs
178
+ && (config.agentClientSource === 'canonical'
179
+ || actualMounts.has(tool.containerMount)))
178
180
  .map((tool) => ({
179
181
  path: tool.containerMount,
180
182
  expectedType: 'bind',
@@ -245,7 +247,8 @@ export function collectSandboxRecoverySnapshot(params) {
245
247
  });
246
248
  }
247
249
  }
248
- const tools = resolveTools(params.config);
250
+ const capabilityPlan = createSandboxCapabilityPlan(params.config);
251
+ const tools = [...capabilityPlan.tools];
249
252
  const seeds = declaredTmpfsSeedEntries(tools).map((seed) => {
250
253
  const mounted = mountsByDestination.get(seed.stagingPath)?.Type === 'bind';
251
254
  return {
@@ -266,11 +269,18 @@ export function collectSandboxRecoverySnapshot(params) {
266
269
  }));
267
270
  const labels = inspection.Config?.Labels ?? {};
268
271
  const branchLabel = labels[sandboxBranchLabel(params.config)];
269
- const hasCodex = tools.some((tool) => tool.id === 'codex');
272
+ const selectedToolIds = new Set(tools.map((tool) => tool.id));
273
+ const enforceCanonicalPlan = params.config.agentClientSource === 'canonical';
274
+ const unexpectedCapabilityMounts = enforceCanonicalPlan
275
+ ? capabilityPlan.cleanupInventory
276
+ .filter((tool) => isAgentClientId(tool.id) && !selectedToolIds.has(tool.id))
277
+ .map((tool) => tool.containerMount)
278
+ .filter((mountPath) => mountsByDestination.has(mountPath))
279
+ : [];
270
280
  const mountSnapshots = expectedMounts({
271
281
  config: params.config,
272
282
  branch: params.branch,
273
- engine: params.engine,
283
+ tools,
274
284
  actualMounts: mountsByDestination
275
285
  }).map((expected) => {
276
286
  const actual = mountsByDestination.get(expected.path);
@@ -304,18 +314,25 @@ export function collectSandboxRecoverySnapshot(params) {
304
314
  identityOk: typeof inspection.Id === 'string'
305
315
  && inspection.Id.length > 0
306
316
  && branchLabel === params.branch,
317
+ runtimeCapabilityOk: enforceCanonicalPlan
318
+ ? labels[sandboxRuntimeCapabilityLabel(params.config)] === capabilityPlan.runtimeSignature
319
+ : undefined,
320
+ unexpectedCapabilityMounts,
307
321
  mounts: mountSnapshots,
308
322
  tmpfs,
309
323
  seeds,
310
324
  aliasesReadable: probe(params.engine, params.container, 'test -r "$1"', ['/home/devuser/.bash_aliases'], runOkFn),
311
- codex: hasCodex
312
- ? {
313
- commandAvailable: probe(params.engine, params.container, 'command -v codex', [], runOkFn),
314
- stateWritable: probe(params.engine, params.container, 'probe="$1/.agent-infra-codex-state-$$"; trap \'rm -f -- "$probe"\' EXIT; : > "$probe"', ['/home/devuser/.codex'], runOkFn),
315
- promptsSourceExists: probe(params.engine, params.container, 'test -d "$1"', ['/workspace/.codex/commands'], runOkFn),
316
- promptsValid: probe(params.engine, params.container, 'test "$(readlink -- "$1")" = "$2"', ['/home/devuser/.codex/prompts', '/workspace/.codex/commands'], runOkFn)
317
- }
318
- : undefined
325
+ agentClientChecks: capabilityPlan.recoveryChecks.map(({ adapterId, check }) => {
326
+ const applicable = check.when === undefined || probe(params.engine, params.container, check.when.script, [...check.when.args], runOkFn, check.when.user);
327
+ return {
328
+ adapterId,
329
+ checkId: check.id,
330
+ applicable,
331
+ healthy: !applicable || probe(params.engine, params.container, check.probe.script, [...check.probe.args], runOkFn, check.probe.user),
332
+ finding: check.finding,
333
+ ...(check.repair === undefined ? {} : { repair: check.repair })
334
+ };
335
+ })
319
336
  };
320
337
  }
321
338
  export function prepareTmpfsMounts(params) {
@@ -394,10 +411,9 @@ function repairFindings(params) {
394
411
  deps: params.deps
395
412
  });
396
413
  const runVerboseFn = params.deps?.runVerbose ?? runVerboseEngine;
397
- if (params.findings.some((finding) => finding.repairKind === 'builtin-link')) {
414
+ for (const repair of params.findings.flatMap((finding) => finding.repair === undefined ? [] : [finding.repair])) {
398
415
  runVerboseFn(params.engine, 'docker', [
399
- 'exec', '--user', 'devuser', params.container, 'ln', '-sfn',
400
- '/workspace/.codex/commands', '/home/devuser/.codex/prompts'
416
+ 'exec', '--user', repair.user, params.container, repair.command, ...repair.args
401
417
  ]);
402
418
  }
403
419
  }
@@ -114,7 +114,7 @@ export function runSafe(cmd, args, opts = {}) {
114
114
  export function commandForEngine(engine, cmd, args = []) {
115
115
  if (engine === 'wsl2') {
116
116
  const resolvedWrapper = resolveCommand('wsl.exe');
117
- return { cmd: resolvedWrapper, args: ['--', cmd, ...args] };
117
+ return { cmd: resolvedWrapper, args: ['--exec', cmd, ...args] };
118
118
  }
119
119
  if (cmd === 'docker') {
120
120
  const context = getAdapter(engine).dockerContext;
@@ -0,0 +1,8 @@
1
+ const SANDBOX_HOOK_PHASES = [
2
+ 'prepare',
3
+ 'before-container-create',
4
+ 'after-container-start',
5
+ 'before-enter'
6
+ ];
7
+ export { SANDBOX_HOOK_PHASES };
8
+ //# sourceMappingURL=tool-types.js.map
@@ -1,4 +1,5 @@
1
1
  import path from 'node:path';
2
+ import { createSandboxCapabilityPlan } from './agent-client-reconciler.js';
2
3
  import { safeNameCandidates, sanitizeBranchName } from './constants.js';
3
4
  import { hostJoin } from './engines/wsl2-paths.js';
4
5
  export function tmpfsSeedStagingPath(toolId, index) {
@@ -20,108 +21,6 @@ export function declaredTmpfsSeedEntries(tools) {
20
21
  })));
21
22
  }
22
23
  const TOOL_ID_PATTERN = /^[a-z0-9][a-z0-9-]*$/;
23
- function createBuiltinTools(home, project) {
24
- return {
25
- 'claude-code': {
26
- id: 'claude-code',
27
- name: 'Claude Code',
28
- install: { type: 'npm', cmd: '@anthropic-ai/claude-code@latest' },
29
- sandboxBase: hostJoin(home, '.agent-infra', 'sandboxes', 'claude-code'),
30
- containerMount: '/home/devuser/.claude',
31
- versionCmd: 'claude --version',
32
- setupHint: 'Authenticates via host credentials live-mounted at ~/.claude/.credentials.json',
33
- // Claude Code stores user data (.claude.json — onboarding state, theme,
34
- // workspace trust) at $HOME/.claude.json by default, which sits OUTSIDE
35
- // the bind-mounted /home/devuser/.claude tree, so our preseeded
36
- // .claude.json never gets read and the theme picker re-runs on every
37
- // container start. Pinning CLAUDE_CONFIG_DIR to the tool mount relocates
38
- // .claude.json into the same directory as .credentials.json/settings.json,
39
- // letting ensureClaudeOnboarding actually take effect.
40
- envVars: { CLAUDE_CONFIG_DIR: '/home/devuser/.claude' },
41
- hostPreSeedDirs: [
42
- { hostDir: hostJoin(home, '.claude', 'plugins'), sandboxSubdir: 'plugins' }
43
- ],
44
- pathRewriteFiles: [
45
- 'plugins/installed_plugins.json',
46
- 'plugins/known_marketplaces.json'
47
- ],
48
- hostLiveMounts: [
49
- {
50
- hostPath: hostJoin(home, '.agent-infra', 'credentials', project, 'claude-code', '.credentials.json'),
51
- containerSubpath: '.credentials.json'
52
- }
53
- ]
54
- },
55
- codex: {
56
- id: 'codex',
57
- name: 'Codex',
58
- install: { type: 'npm', cmd: '@openai/codex' },
59
- sandboxBase: hostJoin(home, '.agent-infra', 'sandboxes', 'codex'),
60
- containerMount: '/home/devuser/.codex',
61
- versionCmd: 'codex --version',
62
- setupHint: 'Run codex once inside the container and choose Device Code login if needed.',
63
- // codex churns ~/.codex/logs_2.sqlite heavily (upstream openai/codex#24275);
64
- // a bind-mount would write-amplify onto the host SSD via virtiofs. Mount the
65
- // codex home as tmpfs so those logs stay in RAM and die with the container.
66
- // Seeded config (config.toml, model-catalogs) is copied into the tmpfs at
67
- // startup; runtime files like logs_2.sqlite stay in RAM.
68
- tmpfs: { size: '512m', seed: ['config.toml', 'model-catalogs'] },
69
- hostLiveMounts: [
70
- { hostPath: hostJoin(home, '.codex', 'auth.json'), containerSubpath: 'auth.json' }
71
- ],
72
- postSetupCmds: [
73
- 'test -d /workspace/.codex/commands && ln -sfn /workspace/.codex/commands /home/devuser/.codex/prompts || true'
74
- ]
75
- },
76
- opencode: {
77
- id: 'opencode',
78
- name: 'OpenCode',
79
- install: { type: 'npm', cmd: 'opencode-ai' },
80
- sandboxBase: hostJoin(home, '.agent-infra', 'sandboxes', 'opencode'),
81
- containerMount: '/home/devuser/.local/share/opencode',
82
- versionCmd: 'opencode version',
83
- setupHint: 'Configure OpenCode credentials inside the container before first use.',
84
- // OpenCode reads opencode.json from $XDG_CONFIG_HOME/opencode by default,
85
- // outside this tool mount. Pin the config file path so the inherited
86
- // sandbox opencode.json is the one the TUI actually reads.
87
- envVars: { OPENCODE_CONFIG: '/home/devuser/.local/share/opencode/opencode.json' },
88
- hostLiveMounts: [
89
- {
90
- hostPath: hostJoin(home, '.local', 'share', 'opencode', 'auth.json'),
91
- containerSubpath: 'auth.json'
92
- }
93
- ]
94
- },
95
- 'gemini-cli': {
96
- id: 'gemini-cli',
97
- name: 'Gemini CLI',
98
- install: { type: 'npm', cmd: '@google/gemini-cli' },
99
- sandboxBase: hostJoin(home, '.agent-infra', 'sandboxes', 'gemini-cli'),
100
- containerMount: '/home/devuser/.gemini',
101
- versionCmd: 'gemini --version',
102
- setupHint: 'Run gemini inside the container to finish authentication.',
103
- hostLiveMounts: [
104
- { hostPath: hostJoin(home, '.gemini', 'oauth_creds.json'), containerSubpath: 'oauth_creds.json' }
105
- ],
106
- hostPreSeedFiles: [
107
- { hostPath: hostJoin(home, '.gemini', 'settings.json'), sandboxName: 'settings.json' },
108
- { hostPath: hostJoin(home, '.gemini', 'google_accounts.json'), sandboxName: 'google_accounts.json' }
109
- ]
110
- },
111
- 'agent-infra': {
112
- id: 'agent-infra',
113
- name: 'agent-infra CLI',
114
- install: { type: 'npm', cmd: '@fitlab-ai/agent-infra@latest' },
115
- sandboxBase: hostJoin(home, '.agent-infra', 'sandboxes', 'agent-infra'),
116
- containerMount: '/home/devuser/.agent-infra-cli',
117
- versionCmd: 'ai version --raw',
118
- setupHint: 'Provides the ai and agent-infra CLI commands inside the sandbox.'
119
- }
120
- };
121
- }
122
- export function builtinToolIds() {
123
- return Object.keys(createBuiltinTools('', ''));
124
- }
125
24
  function validateTool(tool) {
126
25
  if (!tool.id || !TOOL_ID_PATTERN.test(tool.id)) {
127
26
  throw new Error(`Invalid sandbox tool id: ${String(tool.id)}`);
@@ -308,30 +207,7 @@ export function parseCustomTools(value, options) {
308
207
  return value.map((entry, index) => parseCustomTool(entry, index, options));
309
208
  }
310
209
  export function resolveTools(config) {
311
- const builtins = createBuiltinTools(config.home, config.project);
312
- const customs = config.customTools ?? [];
313
- const seen = new Set();
314
- for (const tool of customs) {
315
- if (builtins[tool.id]) {
316
- throw new Error(`Custom sandbox tool id "${tool.id}" collides with a built-in tool`);
317
- }
318
- if (seen.has(tool.id)) {
319
- throw new Error(`Duplicate sandbox tool id "${tool.id}" in customTools`);
320
- }
321
- seen.add(tool.id);
322
- }
323
- const merged = { ...builtins };
324
- for (const tool of customs) {
325
- merged[tool.id] = tool;
326
- }
327
- return config.tools.map((id) => {
328
- const tool = merged[id];
329
- if (!tool) {
330
- throw new Error(`Unknown sandbox tool: ${id}`);
331
- }
332
- validateTool(tool);
333
- return tool;
334
- });
210
+ return [...createSandboxCapabilityPlan(config).tools];
335
211
  }
336
212
  export function toolConfigDir(tool, project, branch) {
337
213
  return hostJoin(tool.sandboxBase, project, sanitizeBranchName(branch));
@@ -3,6 +3,7 @@ import path from 'node:path';
3
3
  import { resolveTaskRef } from './resolve-ref.js';
4
4
  import { locateActivityLog } from './activity-log.js';
5
5
  import { parseImplementationInputs, selectPendingImplementationInput } from './implementation-inputs.js';
6
+ import { parseVerdict } from './review-artifacts.js';
6
7
  import { extractSection, findSectionHeading } from './sections.js';
7
8
  const artifactFamilyCatalog = [
8
9
  { family: 'analysis', sectionAliases: ['分析', 'Analysis'], heading: '分析', labels: ['需求分析报告', 'Requirements Analysis'] },
@@ -173,24 +174,6 @@ function resolveReviewedInput(taskDir, review, expectedFamily, diagnostics) {
173
174
  return null;
174
175
  }
175
176
  }
176
- function extractMarkdownSection(content, names) {
177
- const lines = content.split(/\r?\n/);
178
- const allowed = new Set(names);
179
- const start = lines.findIndex((line) => {
180
- const match = /^##\s+(.+?)\s*$/.exec(line.trim());
181
- return Boolean(match?.[1] && allowed.has(match[1]));
182
- });
183
- if (start === -1)
184
- return '';
185
- let end = lines.length;
186
- for (let index = start + 1; index < lines.length; index += 1) {
187
- if (/^##\s+/.test(lines[index])) {
188
- end = index;
189
- break;
190
- }
191
- }
192
- return lines.slice(start + 1, end).join('\n');
193
- }
194
177
  function assertWritableInventory(inventory) {
195
178
  if (inventory.status === 'failed')
196
179
  return inventory.error;
@@ -333,37 +316,6 @@ function resolveDecisionImplementationInput(inventory, review) {
333
316
  return { error: error instanceof Error ? error.message : String(error) };
334
317
  }
335
318
  }
336
- function normalizeVerdict(value) {
337
- const normalized = value.trim().toLowerCase();
338
- if (normalized === '通过' || normalized === 'approved')
339
- return 'Approved';
340
- if (normalized === '需要修改' || normalized === 'changes requested')
341
- return 'Changes Requested';
342
- if (normalized === '拒绝' || normalized === 'rejected')
343
- return 'Rejected';
344
- return null;
345
- }
346
- function parseVerdict(file) {
347
- let content;
348
- try {
349
- content = fs.readFileSync(file, 'utf8');
350
- }
351
- catch (error) {
352
- return { ok: false, message: String(error) };
353
- }
354
- const summary = extractMarkdownSection(content, ['审查摘要', 'Review Summary']);
355
- const verdictMatch = /^[-*]?\s*\*\*(?:总体结论|Overall Verdict)\*\*[::]\s*(.+?)\s*$/im.exec(summary);
356
- const verdict = verdictMatch?.[1] ? normalizeVerdict(verdictMatch[1]) : null;
357
- if (!verdict)
358
- return { ok: false, message: `cannot parse verdict in ${path.basename(file)}` };
359
- if (verdict !== 'Approved')
360
- return { ok: true, verdict };
361
- const findings = /^[-*]?\s*\*\*(?:发现(AI 可处理)|Findings \(AI-actionable\))\*\*[::]\s*(.+?)\s*$/im.exec(summary)?.[1];
362
- const counts = findings?.match(/(\d+)\s*(?:阻塞项|blockers?).*?(\d+)\s*(?:主要|majors?).*?(\d+)\s*(?:次要|minors?)/i);
363
- if (!counts)
364
- return { ok: false, message: `cannot parse findings count in ${path.basename(file)}` };
365
- return { ok: true, verdict: counts.slice(1).some((value) => Number(value) > 0) ? 'Approved-with-issues' : 'Approved' };
366
- }
367
319
  function validateCompletedArtifact(taskDir, family, name, round) {
368
320
  if (path.basename(name) !== name)
369
321
  return { ok: false, error: { code: 'ARTIFACT_PATH_INVALID', message: 'artifact must be a top-level basename' } };
@@ -9,8 +9,9 @@ import { extractSubSection } from '../sections.js';
9
9
  const USAGE = `Usage: ai task decisions [--task <ref> | -t <ref>] [--item <selector> | -i <selector>] [options]
10
10
  ai task decisions <ref> [selector] [options]
11
11
 
12
- Lists decision items recorded in a task's review disagreement
13
- ledger, or prints the full detail block for a single item. Read-only.
12
+ Shows what still needs a maintainer's decision. Without a selector, it lists
13
+ the choices. With a selector, it shows the explanation and how to respond.
14
+ Read-only.
14
15
 
15
16
  Omit <ref> Resolve the unique active task for the current branch.
16
17
  <ref> Legacy positional task ref.
@@ -161,28 +162,29 @@ function findDecisionRecord(id, content) {
161
162
  function titleOf(row, taskDir) {
162
163
  const block = findDetailBlock(row, taskDir);
163
164
  if (block) {
164
- return block
165
+ const title = block
165
166
  .split('\n')[0]
166
167
  .replace(/^###\s+/, '')
167
168
  .replace(/\s*\[needs-human-decision\]\s*$/, '')
168
169
  .trim();
170
+ return title.replace(new RegExp(`^${row.id}\\s*[::]\\s*`, 'i'), '').trim();
169
171
  }
170
- return row.evidence || '(无详情)';
172
+ return '(explanation unavailable)';
173
+ }
174
+ function trackingOf(row) {
175
+ return `stage=${row.stage} · severity=${row.severity} · status=${row.status} · evidence=${row.evidence}`;
171
176
  }
172
177
  function renderList(rows, format, taskDir) {
173
178
  if (rows.length === 0) {
174
- process.stdout.write('无待裁决项。\n');
179
+ process.stdout.write('No pending decisions.\n');
175
180
  return;
176
181
  }
177
- const headers = ['#', 'ID', 'STAGE', 'SEVERITY', 'STATUS', 'EVIDENCE', 'TITLE'];
182
+ const headers = ['#', 'CHOICE', 'WHAT NEEDS A DECISION', 'TRACKING'];
178
183
  const data = rows.map((r, i) => [
179
184
  String(i + 1),
180
185
  r.id,
181
- r.stage,
182
- r.severity,
183
- r.status,
184
- r.evidence,
185
- titleOf(r, taskDir)
186
+ titleOf(r, taskDir),
187
+ trackingOf(r)
186
188
  ]);
187
189
  if (format === 'markdown') {
188
190
  const sep = headers.map(() => '---');
@@ -191,10 +193,28 @@ function renderList(rows, format, taskDir) {
191
193
  `| ${sep.join(' | ')} |`,
192
194
  ...data.map((row) => `| ${row.join(' | ')} |`)
193
195
  ];
194
- process.stdout.write(`${md.join('\n')}\n`);
196
+ process.stdout.write(`${md.join('\n')}\n\nView one item: ai task decisions <task-ref> <ordinal|ledger-id>\n`);
195
197
  return;
196
198
  }
197
- process.stdout.write(`${formatTable(headers, data).join('\n')}\n`);
199
+ process.stdout.write(`${formatTable(headers, data).join('\n')}\n\nView one item: ai task decisions <task-ref> <ordinal|ledger-id>\n`);
200
+ }
201
+ function renderTracking(row, format) {
202
+ if (format === 'markdown') {
203
+ return [
204
+ '**Tracking**',
205
+ `- stage: \`${row.stage}\``,
206
+ `- severity: \`${row.severity}\``,
207
+ `- status: \`${row.status}\``,
208
+ `- evidence: \`${row.evidence}\``
209
+ ];
210
+ }
211
+ return [
212
+ 'Tracking:',
213
+ `stage: ${row.stage}`,
214
+ `severity: ${row.severity}`,
215
+ `status: ${row.status}`,
216
+ `evidence: ${row.evidence}`
217
+ ];
198
218
  }
199
219
  function renderDetail(rows, selector, format, taskDir, content) {
200
220
  const selected = selectDecisionItem(rows, selector);
@@ -204,25 +224,35 @@ function renderDetail(rows, selector, format, taskDir, content) {
204
224
  }
205
225
  const r = selected.row;
206
226
  const block = findDetailBlock(r, taskDir);
227
+ const title = titleOf(r, taskDir);
207
228
  const lines = [];
229
+ const decided = r.status === 'human-decided';
208
230
  if (format === 'markdown') {
209
- lines.push(`**${r.id}** (${r.stage}/${r.severity}) · status=\`${r.status}\` · evidence: \`${r.evidence}\``, '');
231
+ lines.push(`## ${decided ? 'Decision already recorded' : 'Decision needed'}: ${title}`, '');
232
+ if (!decided) {
233
+ lines.push('**How to record your choice**', `\`ai decide <task-ref> ${r.id} <your choice and rationale>\``, '');
234
+ }
235
+ lines.push('**Original context**', '');
210
236
  }
211
237
  else {
212
- lines.push(`${r.id} (${r.stage}/${r.severity}) status=${r.status}`, `evidence: ${r.evidence}`, '');
238
+ lines.push(`${decided ? 'Decision already recorded' : 'Decision needed'}: ${title}`, '');
239
+ if (!decided) {
240
+ lines.push('How to record your choice:', `ai decide <task-ref> ${r.id} <your choice and rationale>`, '');
241
+ }
242
+ lines.push('Original context:');
213
243
  }
214
244
  if (block) {
215
245
  lines.push(block);
216
246
  }
217
247
  else {
218
- lines.push(`(详情块未找到:未在任务产物中定位到 \`### ${r.id}\` 锚点,可能为历史产物或尚未写入;evidence 指向 ${r.evidence})`);
248
+ lines.push(`This older task does not include a full explanation for ${r.id}.`, 'Use the tracking reference below to find the original context.');
219
249
  }
220
- if (r.status === 'human-decided') {
250
+ if (decided) {
221
251
  const record = findDecisionRecord(r.id, content);
222
- if (record.length) {
223
- lines.push('', '人工裁定:', ...record);
224
- }
252
+ lines.push('', format === 'markdown' ? '**Recorded choice**' : 'Recorded choice:');
253
+ lines.push(...(record.length ? record : ['No separate ruling record was found.']));
225
254
  }
255
+ lines.push('', ...renderTracking(r, format));
226
256
  process.stdout.write(`${lines.join('\n')}\n`);
227
257
  }
228
258
  function decisions(args = []) {
@@ -0,0 +1,107 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ const MANAGED_AGENTS = {
3
+ 'agent-infra-lifecycle-executor': 'executor',
4
+ 'agent-infra-lifecycle-reviewer': 'reviewer'
5
+ };
6
+ function managedDelegationRole(nativeAgent) {
7
+ return MANAGED_AGENTS[nativeAgent] ?? null;
8
+ }
9
+ function fail(code, message) {
10
+ return { ok: false, code, message };
11
+ }
12
+ function prepareDelegation(input, options = {}) {
13
+ return Object.freeze({
14
+ ...input,
15
+ id: (options.id ?? randomUUID)(),
16
+ actualModel: null,
17
+ modelFallbackReason: null,
18
+ parentId: null,
19
+ childId: null,
20
+ spawnMode: null,
21
+ agent: null,
22
+ status: 'prepared',
23
+ afterFingerprint: null,
24
+ changedPaths: Object.freeze([]),
25
+ createdAt: (options.now ?? (() => new Date().toISOString()))(),
26
+ activatedAt: null,
27
+ sealedAt: null,
28
+ consumedAt: null
29
+ });
30
+ }
31
+ function activateDelegation(receipt, event, options = {}) {
32
+ const managedRole = managedDelegationRole(event.nativeAgent);
33
+ if (!managedRole)
34
+ return fail('DELEGATION_IGNORED', `subagent '${event.nativeAgent}' is not lifecycle-managed`);
35
+ if (receipt.status !== 'prepared')
36
+ return fail('DELEGATION_STATE_INVALID', `delegation ${receipt.id} is ${receipt.status}, expected prepared`);
37
+ if (managedRole !== receipt.role)
38
+ return fail('DELEGATION_ROLE_MISMATCH', `managed role ${managedRole} does not match ${receipt.role}`);
39
+ if (!event.parentId || (receipt.parentId !== null && event.parentId !== receipt.parentId) || !event.childId || event.childId === event.parentId) {
40
+ return fail('DELEGATION_IDENTITY_INVALID', 'native parent/child identity does not match the prepared delegation');
41
+ }
42
+ if (event.spawnMode !== 'fresh')
43
+ return fail('DELEGATION_FORK_FORBIDDEN', `spawn mode '${event.spawnMode}' is not fresh`);
44
+ const actualModel = event.actualModel ?? null;
45
+ if (actualModel && receipt.requestedModel && actualModel !== receipt.requestedModel && !event.modelFallbackReason) {
46
+ return fail('DELEGATION_MODEL_FALLBACK_UNRECORDED', 'actual model differs from requested model without a fallback reason');
47
+ }
48
+ return { ok: true, receipt: Object.freeze({
49
+ ...receipt,
50
+ status: 'activated',
51
+ parentId: event.parentId,
52
+ childId: event.childId,
53
+ spawnMode: event.spawnMode,
54
+ actualModel,
55
+ modelFallbackReason: event.modelFallbackReason ?? null,
56
+ activatedAt: (options.now ?? (() => new Date().toISOString()))()
57
+ }) };
58
+ }
59
+ function completeDelegationStage(receipt, event) {
60
+ if (receipt.status !== 'activated')
61
+ return fail('DELEGATION_STATE_INVALID', `delegation ${receipt.id} is ${receipt.status}, expected activated`);
62
+ if (event.stage !== receipt.stage || event.round !== receipt.round || event.artifact !== receipt.artifact) {
63
+ return fail('DELEGATION_STAGE_MISMATCH', 'stage completion identity does not match the active delegation');
64
+ }
65
+ const acceptedAgents = receipt.client === 'claude-code' ? ['claude', 'claude-code'] : [receipt.client];
66
+ if (!acceptedAgents.includes(event.agent)) {
67
+ return fail('DELEGATION_AGENT_MISMATCH', `stage agent '${event.agent}' does not match client '${receipt.client}'`);
68
+ }
69
+ return { ok: true, receipt: Object.freeze({ ...receipt, status: 'stage-completed', agent: event.agent }) };
70
+ }
71
+ function sealDelegation(receipt, event, options = {}) {
72
+ if (receipt.status !== 'stage-completed')
73
+ return fail('DELEGATION_STATE_INVALID', `delegation ${receipt.id} is ${receipt.status}, expected stage-completed`);
74
+ if (event.childId !== receipt.childId || event.exitCode !== 0)
75
+ return fail('DELEGATION_STOP_INVALID', 'native stop identity or exit status is invalid');
76
+ if (receipt.role === 'reviewer') {
77
+ const taskRoot = `.agents/workspace/active/${receipt.taskId}/`;
78
+ const allowed = new Set([
79
+ `${taskRoot}${receipt.artifact}`,
80
+ `${taskRoot}task.md`,
81
+ `${taskRoot}orchestration.json`
82
+ ]);
83
+ const disallowed = event.changedPaths.find((entry) => !allowed.has(entry));
84
+ if (disallowed)
85
+ return fail('DELEGATION_REVIEWER_WRITE_FORBIDDEN', `reviewer changed forbidden path '${disallowed}'`);
86
+ }
87
+ return { ok: true, receipt: Object.freeze({
88
+ ...receipt,
89
+ status: 'sealed',
90
+ afterFingerprint: event.afterFingerprint,
91
+ changedPaths: Object.freeze([...event.changedPaths]),
92
+ sealedAt: (options.now ?? (() => new Date().toISOString()))()
93
+ }) };
94
+ }
95
+ function consumeDelegation(receipt, options = {}) {
96
+ if (receipt.status === 'consumed')
97
+ return fail('DELEGATION_REPLAY', `delegation ${receipt.id} was already consumed`);
98
+ if (receipt.status !== 'sealed')
99
+ return fail('DELEGATION_STATE_INVALID', `delegation ${receipt.id} is ${receipt.status}, expected sealed`);
100
+ return { ok: true, receipt: Object.freeze({
101
+ ...receipt,
102
+ status: 'consumed',
103
+ consumedAt: (options.now ?? (() => new Date().toISOString()))()
104
+ }) };
105
+ }
106
+ export { activateDelegation, completeDelegationStage, consumeDelegation, managedDelegationRole, prepareDelegation, sealDelegation };
107
+ //# sourceMappingURL=delegation-receipts.js.map