@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
@@ -1,6 +1,6 @@
1
+ import { createHash } from 'node:crypto';
1
2
  import fs from 'node:fs';
2
3
  import path from 'node:path';
3
- import { createHash } from 'node:crypto';
4
4
  const NO_FOLLOW = fs.constants.O_NOFOLLOW ?? 0;
5
5
  const DIRECTORY = fs.constants.O_DIRECTORY ?? 0;
6
6
  export class SecureFileError extends Error {
@@ -15,11 +15,8 @@ function conflict(message) {
15
15
  throw new SecureFileError('TASK_ARTIFACT_WRITE_CONFLICT', message);
16
16
  }
17
17
  function sameStat(left, right) {
18
- return left.dev === right.dev
19
- && left.ino === right.ino
20
- && left.size === right.size
21
- && left.mtimeNs === right.mtimeNs
22
- && left.ctimeNs === right.ctimeNs;
18
+ return left.dev === right.dev && left.ino === right.ino && left.size === right.size
19
+ && left.mtimeNs === right.mtimeNs && left.ctimeNs === right.ctimeNs;
23
20
  }
24
21
  function assertNoSymlinkAncestors(candidate) {
25
22
  let current = path.dirname(candidate);
@@ -36,15 +33,12 @@ function assertNoSymlinkAncestors(candidate) {
36
33
  export async function readStableFile(candidate, options) {
37
34
  return readStableFileSync(candidate, options);
38
35
  }
39
- /** Shared bounded reader for synchronous domain commands and async workflow callers. */
40
36
  export function readStableFileSync(candidate, options) {
41
- if (!path.isAbsolute(candidate)) {
37
+ if (!path.isAbsolute(candidate))
42
38
  conflict('candidate path must be absolute and terminal');
43
- }
44
39
  assertNoSymlinkAncestors(candidate);
45
40
  let descriptor;
46
41
  try {
47
- // O_NONBLOCK lets fstat reject a FIFO even when it has no writer.
48
42
  descriptor = fs.openSync(candidate, fs.constants.O_RDONLY | NO_FOLLOW | (fs.constants.O_NONBLOCK ?? 0));
49
43
  const before = fs.fstatSync(descriptor, { bigint: true });
50
44
  if (!before.isFile())
@@ -63,18 +57,16 @@ export function readStableFileSync(candidate, options) {
63
57
  if (!sameStat(before, after))
64
58
  conflict('candidate identity or metadata changed during read');
65
59
  const sha256 = createHash('sha256').update(bytes).digest('hex');
66
- if (options.expectedSha256 !== undefined && sha256 !== options.expectedSha256) {
60
+ if (options.expectedSha256 !== undefined && sha256 !== options.expectedSha256)
67
61
  conflict('candidate digest does not match the expected digest');
68
- }
69
62
  return { bytes, sha256, stat: after };
70
63
  }
71
64
  catch (error) {
72
65
  if (error instanceof SecureFileError)
73
66
  throw error;
74
67
  const code = error.code;
75
- if (code === 'ELOOP' || code === 'ENOTSUP' || code === 'EINVAL') {
68
+ if (code === 'ELOOP' || code === 'ENOTSUP' || code === 'EINVAL')
76
69
  conflict('candidate terminal is not a stable non-symlink file');
77
- }
78
70
  throw new SecureFileError('TASK_ARTIFACT_WRITE_CONFLICT', error instanceof Error ? error.message : String(error));
79
71
  }
80
72
  finally {
@@ -93,9 +85,8 @@ export async function writeAtomicFile(target, bytes, mode = 0o600, expectedSha25
93
85
  catch {
94
86
  throw new SecureFileError('TASK_ARTIFACT_WRITE_CONFLICT', 'authoritative parent directory is missing');
95
87
  }
96
- if (!stat.isDirectory() || stat.isSymbolicLink()) {
88
+ if (!stat.isDirectory() || stat.isSymbolicLink())
97
89
  throw new SecureFileError('TASK_ARTIFACT_WRITE_CONFLICT', 'authoritative parent directory is not a real directory');
98
- }
99
90
  const next = path.dirname(current);
100
91
  if (next === current)
101
92
  break;
@@ -103,9 +94,8 @@ export async function writeAtomicFile(target, bytes, mode = 0o600, expectedSha25
103
94
  }
104
95
  try {
105
96
  const existing = await fs.promises.lstat(target);
106
- if (existing.isSymbolicLink() || !existing.isFile()) {
97
+ if (existing.isSymbolicLink() || !existing.isFile())
107
98
  throw new SecureFileError('TASK_ARTIFACT_WRITE_CONFLICT', 'authoritative target is not a regular file');
108
- }
109
99
  }
110
100
  catch (error) {
111
101
  if (error.code !== 'ENOENT')
@@ -120,7 +110,6 @@ export async function writeAtomicFile(target, bytes, mode = 0o600, expectedSha25
120
110
  await handle.chmod(mode);
121
111
  await handle.close();
122
112
  handle = undefined;
123
- // Check after all asynchronous preparation; keep the final check/rename adjacent.
124
113
  if (expectedSha256 !== undefined)
125
114
  readStableFileSync(target, { maxBytes: 1024 * 1024, expectedSha256 });
126
115
  fs.renameSync(temporary, target);
@@ -199,14 +199,16 @@ function mutateShortIdRegistry(repoRoot, taskId, requested) {
199
199
  const activeDir = path.join(repoRoot, '.agents', 'workspace', 'active');
200
200
  return mutateShortIdRegistryAt(activeDir, configuredShortIdLength(repoRoot), taskId, requested);
201
201
  }
202
- function mutateShortIdRegistryAt(activeDir, width, taskId, requested) {
202
+ function mutateShortIdRegistryAt(activeDir, width, taskId, requested, onlyReleaseWhenInactive = false, pruneOrphans = true) {
203
203
  const registryPath = path.join(activeDir, REGISTRY_NAME);
204
204
  return withRegistryLock(activeDir, () => {
205
205
  const registry = readRegistryStrict(registryPath, width);
206
206
  const original = JSON.stringify(registry.ids);
207
- for (const [key, candidate] of Object.entries(registry.ids)) {
208
- if (candidate !== taskId && !fs.existsSync(path.join(activeDir, candidate, 'task.md')))
209
- delete registry.ids[key];
207
+ if (pruneOrphans) {
208
+ for (const [key, candidate] of Object.entries(registry.ids)) {
209
+ if (candidate !== taskId && !fs.existsSync(path.join(activeDir, candidate, 'task.md')))
210
+ delete registry.ids[key];
211
+ }
210
212
  }
211
213
  const existing = Object.entries(registry.ids).find(([, candidate]) => candidate === taskId);
212
214
  let result;
@@ -232,8 +234,10 @@ function mutateShortIdRegistryAt(activeDir, width, taskId, requested) {
232
234
  }
233
235
  }
234
236
  else if (requested === 'release') {
235
- if (!existing)
236
- result = { effect: 'unchanged', shortId: null, changed: false };
237
+ const taskIsActive = fs.existsSync(path.join(activeDir, taskId, 'task.md'));
238
+ if (!existing || (onlyReleaseWhenInactive && taskIsActive)) {
239
+ result = { effect: 'unchanged', shortId: existing?.[0] ?? null, changed: false };
240
+ }
237
241
  else {
238
242
  delete registry.ids[existing[0]];
239
243
  result = { effect: 'released', shortId: existing[0], changed: true };
@@ -247,6 +251,10 @@ function mutateShortIdRegistryAt(activeDir, width, taskId, requested) {
247
251
  return { ...result, changed };
248
252
  });
249
253
  }
254
+ function releaseStaleShortIdRegistry(repoRoot, taskId) {
255
+ const activeDir = path.join(repoRoot, '.agents', 'workspace', 'active');
256
+ return mutateShortIdRegistryAt(activeDir, configuredShortIdLength(repoRoot), taskId, 'release', true, false);
257
+ }
250
258
  function executeShortIdCommand(request) {
251
259
  try {
252
260
  if (!Number.isInteger(request.shortIdLength) || request.shortIdLength < 1) {
@@ -429,5 +437,5 @@ function lookupShortIdByBranch(branch, repoRoot, _opts) {
429
437
  }
430
438
  return matches[0];
431
439
  }
432
- export { normalizeShortIdInput, isRemovedHashShortIdInput, resolveShortIdReadOnly, lookupShortIdByBranch, loadShortIdByTaskId, inspectShortIdRegistry, configuredShortIdLength, mutateShortIdRegistry, executeShortIdCommand };
440
+ export { normalizeShortIdInput, isRemovedHashShortIdInput, resolveShortIdReadOnly, lookupShortIdByBranch, loadShortIdByTaskId, inspectShortIdRegistry, configuredShortIdLength, mutateShortIdRegistry, releaseStaleShortIdRegistry, executeShortIdCommand };
433
441
  //# sourceMappingURL=short-id.js.map
@@ -231,7 +231,7 @@ function withTaskExecutionLock(repoRoot, taskId, ownerName, callback, options =
231
231
  catch (error) {
232
232
  if (error instanceof TaskExecutionLockError)
233
233
  throw error;
234
- if (error instanceof Error && error.name === 'OrchestrationStateError')
234
+ if (error instanceof Error && ['OrchestrationStateError', 'ArtifactRecoveryError'].includes(error.name))
235
235
  throw error;
236
236
  throw new TaskExecutionLockError('ORCHESTRATION_LOCK_FAILED', 'lifecycle task lock operation failed', { operation: ownsFixed ? 'callback' : 'acquire', errno: errno(error), key });
237
237
  }
@@ -33,6 +33,7 @@ import { manualValidationFinalSummaryProjectionMatches } from "./manual-validati
33
33
  import { readManualValidationCompletion } from "./manual-validation-completion.js";
34
34
  import { sha256File } from "./artifact-receipts.js";
35
35
  import { summaryCommentState } from "../platform/pr-summary.js";
36
+ import { taskIssueIdentity } from "../platform/task-identities.js";
36
37
  const TASK_ENUMS = {
37
38
  type: ["feature", "bugfix", "refactor", "docs", "chore"],
38
39
  workflow: ["feature-development", "bug-fix", "refactoring"],
@@ -78,8 +79,6 @@ const sharedUtils = {
78
79
  failResult,
79
80
  blockedResult,
80
81
  safeStat,
81
- parseIssueNumber,
82
- parsePrNumber
83
82
  };
84
83
  async function runCheck(type, context, shared) {
85
84
  switch (type) {
@@ -314,8 +313,8 @@ function checkTaskMeta({ taskDir, config, repositoryRoot }) {
314
313
  if (expectedStatus && metadata.status !== expectedStatus) {
315
314
  return failResult("task-meta", `Expected status '${expectedStatus}', got '${metadata.status || "(empty)"}'`);
316
315
  }
317
- if (config.require_issue_number && !parseIssueNumber(metadata.issue_number)) {
318
- return failResult("task-meta", "Expected a valid issue_number in task metadata");
316
+ if (config.require_platform_issue_identity && !taskIssueIdentity(metadata)) {
317
+ return failResult("task-meta", "Expected a valid platform_issue_identity in task metadata");
319
318
  }
320
319
  if (config.require_completed_at && isBlank(metadata.completed_at)) {
321
320
  return failResult("task-meta", "Expected completed_at to be present");
@@ -1185,16 +1184,6 @@ function getCheckedRequirements(content) {
1185
1184
  .filter(Boolean)
1186
1185
  .map((match) => match[1].trim());
1187
1186
  }
1188
- function parseIssueNumber(value) {
1189
- if (isBlank(value) || value === "N/A") {
1190
- return null;
1191
- }
1192
- const match = String(value).match(/\d+/);
1193
- return match ? Number(match[0]) : null;
1194
- }
1195
- function parsePrNumber(value) {
1196
- return parseIssueNumber(value);
1197
- }
1198
1187
  // === Utilities ===
1199
1188
  function normalizeContent(text) {
1200
1189
  return String(text || "")
@@ -1,8 +1,9 @@
1
1
  export const TASK_WORKFLOW_COMMANDS = Object.freeze({
2
2
  'artifact-inspect': ['task-artifact', 'inspect'],
3
3
  'artifact-init': ['task-artifact', 'init'],
4
- 'artifact-repair': ['task-artifact', 'repair'],
4
+ 'artifact-preflight': ['task-artifact', 'preflight'],
5
5
  'artifact-finalize-local': ['task-artifact', 'finalize-local'],
6
+ 'review-preflight': ['task-review', 'preflight'],
6
7
  'review-finalize-summary': ['task-review', 'finalize-summary'],
7
8
  event: ['task-event', null],
8
9
  'ledger-finding-response': ['task-ledger', 'finding-respond'],
@@ -0,0 +1,161 @@
1
+ import { reconcileTaskInvalidation } from './invalidation-command.js';
2
+ import { applyLedgerIntent } from './ledger-intents.js';
3
+ import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.js';
4
+ import { applyWorkflowWarningIntent } from './workflow-warning-intents.js';
5
+ function failed(code, message) {
6
+ return { status: 'failed', changed: false, error: { code, message } };
7
+ }
8
+ function isFailure(value) {
9
+ return Boolean(value) && typeof value === 'object' && value.status === 'failed';
10
+ }
11
+ function options(args, allowed) {
12
+ const values = {};
13
+ for (let index = 2; index < args.length; index += 1) {
14
+ const flag = args[index];
15
+ if (flag === '--dry-run') {
16
+ if (!Object.hasOwn(allowed, flag) || values[flag] !== undefined)
17
+ return failed('TASK_WORKFLOW_PAYLOAD_INVALID', `invalid option '${flag}'`);
18
+ values[flag] = true;
19
+ continue;
20
+ }
21
+ const key = allowed[flag];
22
+ if (!key || values[flag] !== undefined)
23
+ return failed('TASK_WORKFLOW_PAYLOAD_INVALID', `invalid option '${flag}'`);
24
+ const value = args[++index];
25
+ if (value === undefined || value.startsWith('--'))
26
+ return failed('TASK_WORKFLOW_PAYLOAD_INVALID', `option '${flag}' requires a value`);
27
+ if (flag === '--needs-implementation' && value !== 'true' && value !== 'false') {
28
+ return failed('TASK_WORKFLOW_PAYLOAD_INVALID', "option '--needs-implementation' must be true or false");
29
+ }
30
+ values[flag] = value;
31
+ }
32
+ return values;
33
+ }
34
+ function value(values, flag) {
35
+ const candidate = values[flag];
36
+ return typeof candidate === 'string' ? candidate : undefined;
37
+ }
38
+ function required(values, flags) {
39
+ const missing = flags.find((flag) => value(values, flag) === undefined);
40
+ return missing ? failed('TASK_WORKFLOW_PAYLOAD_INVALID', `option '${missing}' is required`) : null;
41
+ }
42
+ function resultForLock(error) {
43
+ if (error instanceof TaskExecutionLockError)
44
+ return failed(error.code, error.message);
45
+ throw error;
46
+ }
47
+ function ledgerIntent(operation, args) {
48
+ const [taskRef, kind] = args;
49
+ const mappings = {
50
+ 'ledger-finding-response': {
51
+ kind: 'finding-respond', required: ['--id', '--round', '--status', '--evidence'],
52
+ flags: { '--id': 'id', '--round': 'round', '--status': 'status', '--evidence': 'evidence', '--dry-run': 'dryRun' }
53
+ },
54
+ 'ledger-finding-review': {
55
+ kind: 'finding-review', required: ['--id', '--status', '--evidence'],
56
+ flags: { '--id': 'id', '--status': 'status', '--evidence': 'evidence', '--needs-implementation': 'needsImplementation', '--dry-run': 'dryRun' }
57
+ },
58
+ 'ledger-finding-upsert': {
59
+ kind: 'finding-upsert', required: ['--stage', '--review-artifact', '--ordinal', '--severity', '--evidence'],
60
+ flags: { '--stage': 'stage', '--review-artifact': 'reviewArtifact', '--ordinal': 'ordinal', '--severity': 'severity', '--evidence': 'evidence', '--dry-run': 'dryRun' }
61
+ },
62
+ 'decision-next-id': { kind: 'decision-next-id', required: [], flags: {} },
63
+ 'decision-upsert': {
64
+ kind: 'decision-upsert', required: ['--id', '--stage', '--artifact'],
65
+ flags: { '--id': 'id', '--stage': 'stage', '--artifact': 'artifact', '--needs-implementation': 'needsImplementation', '--dry-run': 'dryRun' }
66
+ }
67
+ };
68
+ const mapping = mappings[operation];
69
+ if (!mapping || !taskRef || kind === undefined)
70
+ return failed('TASK_WORKFLOW_PAYLOAD_INVALID', 'task workflow ledger request is invalid');
71
+ const values = options(args, mapping.flags);
72
+ if (isFailure(values))
73
+ return values;
74
+ const missing = required(values, mapping.required);
75
+ if (missing)
76
+ return missing;
77
+ const dryRun = values['--dry-run'] === true;
78
+ if (mapping.kind === 'decision-next-id')
79
+ return { kind: mapping.kind, taskRef };
80
+ if (mapping.kind === 'finding-respond')
81
+ return {
82
+ kind: mapping.kind, taskRef, id: value(values, '--id'), round: Number(value(values, '--round')),
83
+ status: value(values, '--status'), evidence: value(values, '--evidence'), ...(dryRun ? { dryRun } : {})
84
+ };
85
+ if (mapping.kind === 'finding-review')
86
+ return {
87
+ kind: mapping.kind, taskRef, id: value(values, '--id'), status: value(values, '--status'),
88
+ evidence: value(values, '--evidence'), ...(value(values, '--needs-implementation') === 'true' ? { needsImplementation: true } : {}),
89
+ ...(dryRun ? { dryRun } : {})
90
+ };
91
+ if (mapping.kind === 'finding-upsert')
92
+ return {
93
+ kind: mapping.kind, taskRef, stage: value(values, '--stage'), reviewArtifact: value(values, '--review-artifact'),
94
+ ordinal: Number(value(values, '--ordinal')), severity: value(values, '--severity'), evidence: value(values, '--evidence'),
95
+ ...(dryRun ? { dryRun } : {})
96
+ };
97
+ return {
98
+ kind: mapping.kind, taskRef, id: value(values, '--id'), stage: value(values, '--stage'),
99
+ artifact: value(values, '--artifact'), ...(value(values, '--needs-implementation') === 'true' ? { needsImplementation: true } : {}),
100
+ ...(dryRun ? { dryRun } : {})
101
+ };
102
+ }
103
+ function warningIntent(args) {
104
+ const [taskRef, kind] = args;
105
+ if (!taskRef || kind !== 'add')
106
+ return failed('TASK_WORKFLOW_PAYLOAD_INVALID', 'task workflow warning request is invalid');
107
+ const values = options(args, {
108
+ '--step': 'step', '--severity': 'severity', '--code': 'code', '--target': 'target',
109
+ '--message': 'message', '--action': 'action', '--dry-run': 'dryRun'
110
+ });
111
+ if (isFailure(values))
112
+ return values;
113
+ const missing = required(values, ['--step', '--severity', '--code', '--target', '--message', '--action']);
114
+ if (missing)
115
+ return missing;
116
+ return {
117
+ kind: 'add', taskRef, step: value(values, '--step'), severity: value(values, '--severity'),
118
+ code: value(values, '--code'), target: value(values, '--target'), message: value(values, '--message'),
119
+ action: value(values, '--action'), ...(values['--dry-run'] === true ? { dryRun: true } : {})
120
+ };
121
+ }
122
+ /**
123
+ * The caller has already selected and authorized the execution boundary.
124
+ * This function deliberately invokes task domains in that process: starting a
125
+ * second CLI process would discard the broker binding and could misclassify a
126
+ * sandbox process with cleared environment variables as direct-host.
127
+ */
128
+ export function dispatchWorkflowCommand(repoRoot, operation, args) {
129
+ const taskRef = args[0];
130
+ if (!taskRef)
131
+ return failed('TASK_WORKFLOW_PAYLOAD_INVALID', 'task ref is required');
132
+ try {
133
+ if (operation === 'invalidation-reconcile') {
134
+ const values = options(args, { '--max-targets': 'maxTargets', '--dry-run': 'dryRun' });
135
+ if (isFailure(values))
136
+ return values;
137
+ const maxTargets = value(values, '--max-targets');
138
+ if (maxTargets !== undefined && (!Number.isSafeInteger(Number(maxTargets)) || Number(maxTargets) < 1)) {
139
+ return failed('TASK_WORKFLOW_PAYLOAD_INVALID', '--max-targets must be a positive integer');
140
+ }
141
+ return withTaskExecutionLock(repoRoot, taskRef, 'task-invalidation.reconcile', () => reconcileTaskInvalidation(taskRef, {
142
+ repoRoot, ...(maxTargets === undefined ? {} : { maxTargets: Number(maxTargets) }),
143
+ ...(values['--dry-run'] === true ? { dryRun: true } : {})
144
+ }));
145
+ }
146
+ if (operation === 'warning-add') {
147
+ const intent = warningIntent(args);
148
+ if (isFailure(intent))
149
+ return intent;
150
+ return withTaskExecutionLock(repoRoot, taskRef, 'task-warning.add', () => applyWorkflowWarningIntent(intent, { repoRoot }));
151
+ }
152
+ const intent = ledgerIntent(operation, args);
153
+ if (isFailure(intent))
154
+ return intent;
155
+ return withTaskExecutionLock(repoRoot, taskRef, `task-ledger.${intent.kind}`, () => applyLedgerIntent(intent, { repoRoot }));
156
+ }
157
+ catch (error) {
158
+ return resultForLock(error);
159
+ }
160
+ }
161
+ //# sourceMappingURL=workflow-dispatch.js.map
@@ -1,5 +1,4 @@
1
1
  import { existsSync, readFileSync } from 'node:fs';
2
- import semver from 'semver';
3
2
  const packageJsonUrl = [
4
3
  new URL('../package.json', import.meta.url),
5
4
  new URL('../../package.json', import.meta.url),
@@ -10,17 +9,8 @@ if (!packageJsonUrl) {
10
9
  const { version } = JSON.parse(readFileSync(packageJsonUrl, 'utf8'));
11
10
  const VERSION = `v${version}`;
12
11
  const AGENT_INFRA_VERSION_PATTERN = /^v\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
13
- const LEGACY_COMPATIBILITY_CUTOFF_VERSION = '1.0.0';
14
12
  function isValidAgentInfraVersion(value) {
15
13
  return typeof value === 'string' && AGENT_INFRA_VERSION_PATTERN.test(value);
16
14
  }
17
- function isLegacyCompatibilityEnabled(runtimeVersion = VERSION) {
18
- const normalized = runtimeVersion.startsWith('v') ? runtimeVersion.slice(1) : runtimeVersion;
19
- const parsed = semver.valid(normalized);
20
- return parsed !== null && semver.lt(parsed, LEGACY_COMPATIBILITY_CUTOFF_VERSION);
21
- }
22
- function legacyCompatibilityError(resource, runtimeVersion = VERSION) {
23
- return Object.assign(new Error(`PLATFORM_IDENTITY_LEGACY_UNSUPPORTED: ${resource} requires migration to the current schema before v${LEGACY_COMPATIBILITY_CUTOFF_VERSION} runtime ${runtimeVersion}`), { code: 'PLATFORM_IDENTITY_LEGACY_UNSUPPORTED' });
24
- }
25
- export { LEGACY_COMPATIBILITY_CUTOFF_VERSION, VERSION, isLegacyCompatibilityEnabled, isValidAgentInfraVersion, legacyCompatibilityError };
15
+ export { VERSION, isValidAgentInfraVersion };
26
16
  //# sourceMappingURL=version.js.map
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "protocolVersion": 3,
3
- "packageVersion": "0.9.15",
4
- "sourceInputHash": "d6521519c8d772a6d81c177d8c6e6795c080b04d9f8afcec19bca45c486a70df",
5
- "internalExecutableBuildHash": "f0ab80c423acc4def5130e075a209a64349296985701506e7d9e73f8151bcb2a"
3
+ "packageVersion": "0.11.0",
4
+ "sourceInputHash": "c67e66a46040d5246d67c751af605b796cd61913b98add01ff327829c01ce3db",
5
+ "internalExecutableBuildHash": "f6b4be06df12111b3a7614d335c9e802049c3cea194c6e7c040f3251188c694f"
6
6
  }
@@ -26,6 +26,7 @@ import {
26
26
  type ControllerControl = Readonly<{
27
27
  token: string;
28
28
  generation: string;
29
+ rootId?: string;
29
30
  channelDir: string;
30
31
  statusDir: string;
31
32
  runtimeDir: string;
@@ -83,13 +84,14 @@ function copyRegular(source: string, destination: string, mode: number): void {
83
84
  function controlFromEnvironment(): ControllerControl {
84
85
  const token = process.env.AGENT_INFRA_CONTROL_TOKEN;
85
86
  const generation = process.env.AGENT_INFRA_CONTROL_GENERATION;
87
+ const rootId = process.env.AGENT_INFRA_CONTROL_ROOT_ID;
86
88
  const channelDir = process.env.AGENT_INFRA_CONTROL_DIR;
87
89
  const statusDir = process.env.AGENT_INFRA_CONTROL_STATUS_DIR;
88
90
  const runtimeDir = process.env.AGENT_INFRA_RUNTIME_DIR;
89
- if (!token || !generation || !channelDir || !statusDir || !runtimeDir) {
91
+ if (!token || !generation || !rootId || !channelDir || !statusDir || !runtimeDir) {
90
92
  throw new Error('CODEX_SANDBOX_CONTROLLER_CONTROL_MISSING');
91
93
  }
92
- return { token, generation, channelDir, statusDir, runtimeDir };
94
+ return { token, generation, rootId, channelDir, statusDir, runtimeDir };
93
95
  }
94
96
 
95
97
  function verifyRuntime(runtimeDir: string): void {
@@ -255,6 +257,9 @@ function isolatedEnvironment(
255
257
  AGENT_INFRA_TASK_ID: taskId,
256
258
  AGENT_INFRA_CONTROL_TOKEN: control.token,
257
259
  AGENT_INFRA_CONTROL_GENERATION: control.generation,
260
+ ...(control.rootId || sourceEnvironment.AGENT_INFRA_CONTROL_ROOT_ID
261
+ ? { AGENT_INFRA_CONTROL_ROOT_ID: control.rootId ?? sourceEnvironment.AGENT_INFRA_CONTROL_ROOT_ID }
262
+ : {}),
258
263
  AGENT_INFRA_CONTROL_DIR: control.channelDir,
259
264
  AGENT_INFRA_CONTROL_STATUS_DIR: control.statusDir,
260
265
  AGENT_INFRA_RUNTIME_DIR: control.runtimeDir,
@@ -43,11 +43,23 @@ type ActiveCodexLifecycleEvidenceQuery = Readonly<{
43
43
  const LOCK_RETRY_MS = 10;
44
44
  const LOCK_TIMEOUT_MS = 1_000;
45
45
  const LOCK_STALE_MS = 30_000;
46
+ const RECOVERY_CONSUMER_RE = /^lifecycle-recovery:([^:\r\n]+):([^:\r\n]+)$/u;
46
47
 
47
48
  function digest(value: string): string {
48
49
  return crypto.createHash('sha256').update(value).digest('hex');
49
50
  }
50
51
 
52
+ function isRecoveryConsumer(value: string): boolean {
53
+ return RECOVERY_CONSUMER_RE.test(value);
54
+ }
55
+
56
+ function recoveryConsumer(taskId: string, receiptId: string): string {
57
+ if (!/^[^:\r\n]+$/u.test(taskId) || !/^[^:\r\n]+$/u.test(receiptId)) {
58
+ throw new Error('Codex lifecycle recovery claim identity is invalid');
59
+ }
60
+ return `lifecycle-recovery:${taskId}:${receiptId}`;
61
+ }
62
+
51
63
  function readRecord(file: string): StoredCodexLifecycle {
52
64
  const value = JSON.parse(fs.readFileSync(file, 'utf8')) as StoredCodexLifecycle;
53
65
  if (
@@ -249,11 +261,12 @@ function createCodexLifecycleStore(options: CodexLifecycleStoreOptions) {
249
261
 
250
262
  function read(childThreadId: string): StoredCodexLifecycle {
251
263
  const matches = findByChild(childThreadId);
252
- if (matches.length !== 1) throw new Error(`Codex lifecycle child '${childThreadId}' was not found uniquely`);
264
+ if (matches.length === 0) throw new Error(`Codex lifecycle child '${childThreadId}' was not found uniquely`);
265
+ if (matches.length > 1) throw new Error(`Codex lifecycle child '${childThreadId}' is ambiguous`);
253
266
  return readRecord(matches[0]!);
254
267
  }
255
268
 
256
- function consume(
269
+ function consumeInternal(
257
270
  childThreadId: string,
258
271
  consumer: string,
259
272
  expectedHookDefinitionHash?: string
@@ -291,12 +304,51 @@ function createCodexLifecycleStore(options: CodexLifecycleStoreOptions) {
291
304
  });
292
305
  }
293
306
 
307
+ function consume(
308
+ childThreadId: string,
309
+ consumer: string,
310
+ expectedHookDefinitionHash?: string
311
+ ): StoredCodexLifecycle {
312
+ if (!consumer.trim()) throw new Error('Codex lifecycle consumer is required');
313
+ if (consumer.startsWith('lifecycle-recovery:')) {
314
+ throw new Error('Codex lifecycle recovery claims require claimRecovery');
315
+ }
316
+ return consumeInternal(childThreadId, consumer, expectedHookDefinitionHash);
317
+ }
318
+
319
+ function claimRecovery(
320
+ childThreadId: string,
321
+ taskId: string,
322
+ receiptId: string,
323
+ expectedHookDefinitionHash?: string
324
+ ): StoredCodexLifecycle {
325
+ return consumeInternal(
326
+ childThreadId,
327
+ recoveryConsumer(taskId, receiptId),
328
+ expectedHookDefinitionHash
329
+ );
330
+ }
331
+
332
+ function releaseRecovery(childThreadId: string, consumer: string): boolean {
333
+ if (!/^lifecycle-recovery:[^:\r\n]+:[^:\r\n]+$/.test(consumer)) return false;
334
+ return withWriteLock(() => {
335
+ const matches = findByChild(childThreadId);
336
+ if (matches.length !== 1) return false;
337
+ const file = matches[0]!;
338
+ const current = readRecord(file);
339
+ if (current.state.status !== 'stop-ready' || current.consumer !== consumer || !current.consumedAt) return false;
340
+ fs.unlinkSync(file);
341
+ return true;
342
+ });
343
+ }
344
+
294
345
  function expireBefore(cutoff: string): number {
295
346
  return withWriteLock(() => {
296
347
  let changed = 0;
297
348
  for (const file of recordFiles(root)) {
298
349
  const current = readRecord(file);
299
350
  if (current.updatedAt >= cutoff) continue;
351
+ if (current.consumer && isRecoveryConsumer(current.consumer)) continue;
300
352
  if (current.consumer || ['invalid', 'expired', 'stop-ready'].includes(current.state.status)) {
301
353
  fs.unlinkSync(file);
302
354
  changed += 1;
@@ -316,7 +368,7 @@ function createCodexLifecycleStore(options: CodexLifecycleStoreOptions) {
316
368
  });
317
369
  }
318
370
 
319
- return Object.freeze({ root, apply, applyToSpawn, consume, expireBefore, findByParent, read });
371
+ return Object.freeze({ root, apply, applyToSpawn, consume, claimRecovery, releaseRecovery, expireBefore, findByParent, read });
320
372
  }
321
373
 
322
374
  export { createCodexLifecycleStore, hasActiveCodexLifecycleEvidence };
@@ -3,6 +3,7 @@ import path from 'node:path';
3
3
  import { randomUUID } from 'node:crypto';
4
4
 
5
5
  export function fsyncDirectory(directory: string): void {
6
+ if (process.platform === 'win32') return;
6
7
  const descriptor = fs.openSync(directory, 'r');
7
8
  try { fs.fsyncSync(descriptor); } finally { fs.closeSync(descriptor); }
8
9
  }
@@ -36,7 +36,6 @@ export const INTERNAL_HANDLER_ROUTE_SELECTORS = Object.freeze({
36
36
  'task-create': ['input'],
37
37
  'task-qualification': ['proposal', 'confirm', 'supersede', 'revoke'],
38
38
  'sandbox-control': ['serve', 'execute', 'recover', 'client'],
39
- 'host-control': ['serve', 'status', 'install', 'uninstall'],
40
39
  'agent-client': ['next-steps', 'model-selection'],
41
40
  'codex-lifecycle': ['preflight', 'capability-arm', 'hook-event', 'resolve-start', 'resolve-stop', 'consume'],
42
41
  'codex-sandbox-controller': ['verify-context', 'run'],
@@ -47,7 +46,7 @@ export const INTERNAL_HANDLER_ROUTE_SELECTORS = Object.freeze({
47
46
  'platform-security': ['read', 'dismiss'],
48
47
  'platform-metadata': ['init-labels', 'init-milestones'],
49
48
  'platform-context': ['resolve'],
50
- 'platform-comment': ['list', 'owner', 'backfill', 'sync'],
49
+ 'platform-comment': ['list', 'recover', 'owner', 'backfill', 'sync'],
51
50
  'platform-issue': ['inspect', 'create', 'bind', 'sync'],
52
51
  'platform-pr': ['inspect', 'summary-context', 'resolve-external', 'create', 'bind', 'skip', 'sync', 'change-report', 'summary-sync', 'sync-in-labels'],
53
52
  'platform-pr-review': ['inspect', 'list', 'publish-pr', 'publish-task'],
@@ -57,9 +56,9 @@ export const INTERNAL_HANDLER_ROUTE_SELECTORS = Object.freeze({
57
56
  'task-ledger': ['decision-next-id', 'stage-status', 'finding-upsert', 'finding-respond', 'finding-review', 'decision-upsert', 'rework-intent-upsert'],
58
57
  'task-warning': ['list', 'add', 'set-status'],
59
58
  'task-activity': ['pr-review-inspect', 'pr-review-start', 'pr-review-complete', 'pr-review-terminate'],
60
- 'task-artifact': ['inspect', 'init', 'repair', 'finalize-local'],
59
+ 'task-artifact': ['inspect', 'init', 'preflight', 'finalize-local'],
61
60
  'task-orchestration': ['status', 'progress'],
62
- 'task-review': ['finalize-summary'],
61
+ 'task-review': ['preflight', 'finalize-summary'],
63
62
  'task-event': ['event'],
64
63
  'task-invalidation': ['reconcile'],
65
64
  'task-override': ['diagnose', 'issue', 'consume'],
@@ -43,15 +43,24 @@ function printFailure(format: OutputFormat, error: { code: string; message: stri
43
43
  process.exitCode = 1;
44
44
  }
45
45
 
46
+ 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;
47
+
46
48
  function manualSummaryBody(body: string, phase: 'pending' | 'final', transactionId: string, receiptDigest = '', evidenceDigest = '', prHeadSha = ''): string {
47
- const withoutPrevious = body
48
- .replace(/^###\s+✅\s+(?:Manual Validation Passed|人工验证已通过)\s*$/gmu, '')
49
- .replace(/^###\s+⏳\s+(?:Manual Validation Pending|人工验证待收尾)\s*$/gmu, '')
50
- .replace(/Manual validation passed(?:\s+→)?/giu, 'Manual validation pending');
49
+ const chinese = /###\s+(?:⚠️\s+需人工校验|✅\s+(?:人工验证已通过|无需人工校验)|⏳\s+人工验证待收尾)\s*$/mu.test(body);
51
50
  const section = phase === 'pending'
52
- ? `### ⏳ Manual Validation Pending\n\nManual validation evidence is staged; transaction ${transactionId} is awaiting final promotion.`
53
- : `### ✅ Manual Validation Passed\n\nManual validation passed; transaction=${transactionId}; receipt=${receiptDigest}; evidence=${evidenceDigest}; head=${prHeadSha}.`;
54
- return `${withoutPrevious.replace(/\s+$/u, '')}\n\n${section}\n`;
51
+ ? chinese
52
+ ? `### ⏳ 人工验证待收尾\n\n人工验证证据已暂存;事务 ${transactionId} 正在等待最终完成。`
53
+ : `### ⏳ Manual Validation Pending\n\nManual validation evidence is staged; transaction ${transactionId} is awaiting final promotion.`
54
+ : chinese
55
+ ? `### ✅ 人工验证已通过\n\n人工验证已通过;transaction=${transactionId}; receipt=${receiptDigest}; evidence=${evidenceDigest}; head=${prHeadSha}.`
56
+ : `### ✅ Manual Validation Passed\n\nManual validation passed; transaction=${transactionId}; receipt=${receiptDigest}; evidence=${evidenceDigest}; head=${prHeadSha}.`;
57
+ let inserted = false;
58
+ const updated = body.replace(MANUAL_VALIDATION_STATUS_SECTION, () => {
59
+ if (inserted) return '';
60
+ inserted = true;
61
+ return `${section}\n\n`;
62
+ });
63
+ return inserted ? `${updated.replace(/\s+$/u, '')}\n` : `${body.replace(/\s+$/u, '')}\n\n${section}\n`;
55
64
  }
56
65
 
57
66
  function openManualValidationStarted(taskMdPath: string): { present: boolean; transactionId: string | null } {
@@ -246,7 +255,7 @@ async function executeManualValidationTransactionLocked(
246
255
  });
247
256
  if (!['applied', 'no-op'].includes(promoted.status)) transactionFailure(promoted.error ?? { code: 'MANUAL_VALIDATION_TRANSACTION_RECOVERY_REQUIRED', message: 'final summary promotion failed' });
248
257
  const postWrite = await summaryCommentState(taskRef, { cwd, client: options.client });
249
- if (!postWrite.comment?.body.includes('### ✅ Manual Validation Passed') || postWrite.pullRequest?.head.sha !== receipt.prHeadSha || !manualValidationFinalSummaryProjectionMatches(postWrite.comment.body, receipt)) transactionFailure({ code: 'MANUAL_VALIDATION_TRANSACTION_RECOVERY_REQUIRED', message: 'final summary post-write verification failed' });
258
+ if (!/^###\s+✅\s+(?:Manual Validation Passed|人工验证已通过)\s*$/mu.test(postWrite.comment?.body ?? '') || postWrite.pullRequest?.head.sha !== receipt.prHeadSha || !manualValidationFinalSummaryProjectionMatches(postWrite.comment?.body ?? '', receipt)) transactionFailure({ code: 'MANUAL_VALIDATION_TRANSACTION_RECOVERY_REQUIRED', message: 'final summary post-write verification failed' });
250
259
  transitionAndPersist('committed', { postWriteVerified: true });
251
260
  return result('applied', null, { transaction, receipt, idempotent: false });
252
261
  } catch (error) {