@fitlab-ai/agent-infra 0.9.14 → 0.9.15

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 (250) hide show
  1. package/README.md +10 -0
  2. package/README.zh-CN.md +10 -0
  3. package/bin/internal-cli.sh +45 -0
  4. package/dist/bin/internal-cli.js +93 -30
  5. package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +165 -27
  6. package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +3 -2
  7. package/dist/lib/defaults.json +103 -103
  8. package/dist/lib/fs/durable-write.js +37 -0
  9. package/dist/lib/fs/file-lock.js +111 -0
  10. package/dist/lib/host-control/audit.js +19 -0
  11. package/dist/lib/host-control/client.js +142 -0
  12. package/dist/lib/host-control/command.js +49 -0
  13. package/dist/lib/host-control/path.js +160 -0
  14. package/dist/lib/host-control/secure-fs.js +140 -0
  15. package/dist/lib/host-control/server.js +159 -0
  16. package/dist/lib/host-control/service.js +112 -0
  17. package/dist/lib/init.js +11 -2
  18. package/dist/lib/internal/cli-route-inventory.js +3 -1
  19. package/dist/lib/internal/codex-lifecycle.js +6 -6
  20. package/dist/lib/internal/demo-promotion.js +322 -0
  21. package/dist/lib/internal/demo-transcript.js +454 -0
  22. package/dist/lib/internal/host-control.js +28 -0
  23. package/dist/lib/internal/manual-validation.js +322 -0
  24. package/dist/lib/internal/platform-pr.js +18 -4
  25. package/dist/lib/internal/release-workflow.js +63 -84
  26. package/dist/lib/internal/sandbox-control.js +149 -2
  27. package/dist/lib/internal/task-artifact.js +15 -262
  28. package/dist/lib/internal/task-create.js +6 -5
  29. package/dist/lib/internal/task-event.js +37 -37
  30. package/dist/lib/internal/task-operation-registry.js +101 -3
  31. package/dist/lib/internal/task-review.js +8 -53
  32. package/dist/lib/internal/task-validate.js +30 -10
  33. package/dist/lib/owned-path.js +33 -0
  34. package/dist/lib/platform/check-status.js +11 -0
  35. package/dist/lib/platform/comment-safety.js +4 -1
  36. package/dist/lib/platform/completion-backfill.js +1 -1
  37. package/dist/lib/platform/github-data.js +241 -0
  38. package/dist/lib/platform/github-provider.js +15 -20
  39. package/dist/lib/platform/issue-comments.js +2 -2
  40. package/dist/lib/platform/issues.js +1 -179
  41. package/dist/lib/platform/pr-checks.js +12 -65
  42. package/dist/lib/platform/pr-summary.js +55 -3
  43. package/dist/lib/platform/pull-requests.js +2 -105
  44. package/dist/lib/platform/snapshots.js +2 -0
  45. package/dist/lib/platform/verification-sync.js +127 -142
  46. package/dist/lib/release/workflow.js +1 -1
  47. package/dist/lib/sandbox/commands/create.js +7 -2
  48. package/dist/lib/sandbox/commands/rm.js +3 -1514
  49. package/dist/lib/sandbox/control/audit.js +203 -0
  50. package/dist/lib/sandbox/control/client.js +93 -54
  51. package/dist/lib/sandbox/control/executor.js +129 -65
  52. package/dist/lib/sandbox/control/finalization-response.js +27 -0
  53. package/dist/lib/sandbox/control/identity-sentinel.js +107 -0
  54. package/dist/lib/sandbox/control/lifecycle.js +36 -2
  55. package/dist/lib/sandbox/control/native-file-lock.js +6 -98
  56. package/dist/lib/sandbox/control/protocol.js +55 -4
  57. package/dist/lib/sandbox/control/server.js +491 -120
  58. package/dist/lib/sandbox/control/state.js +58 -0
  59. package/dist/lib/sandbox/control/task-workflow.js +74 -0
  60. package/dist/lib/sandbox/control/workflow-executor.js +99 -0
  61. package/dist/lib/sandbox/index.js +2 -2
  62. package/dist/lib/sandbox/recovery.js +28 -4
  63. package/dist/lib/sandbox/removal.js +1717 -0
  64. package/dist/lib/sandbox/task-cleanup.js +217 -0
  65. package/dist/lib/sandbox/workspace-view.js +13 -14
  66. package/dist/lib/task/activity-intent.js +2 -1
  67. package/dist/lib/task/activity-log.js +30 -24
  68. package/dist/lib/task/artifact-command.js +101 -0
  69. package/dist/lib/task/artifact-lifecycle.js +30 -28
  70. package/dist/lib/task/artifact-name.js +36 -0
  71. package/dist/lib/task/artifact-operations.js +23 -69
  72. package/dist/lib/task/artifact-receipts.js +3 -15
  73. package/dist/lib/task/artifact-repair-intent.js +87 -16
  74. package/dist/lib/task/artifact-schema.js +6 -14
  75. package/dist/lib/task/capabilities.js +12 -19
  76. package/dist/lib/task/codex-orchestration.js +71 -25
  77. package/dist/lib/task/commit-identity.js +28 -0
  78. package/dist/lib/task/commit-operation.js +2 -16
  79. package/dist/lib/task/control-authority.js +371 -5
  80. package/dist/lib/task/control-recovery.js +128 -0
  81. package/dist/lib/task/create.js +62 -9
  82. package/dist/lib/task/decision-details.js +2 -46
  83. package/dist/lib/task/events.js +120 -9
  84. package/dist/lib/task/finalization-state.js +9 -0
  85. package/dist/lib/task/human-override.js +3 -6
  86. package/dist/lib/task/implementation-inputs.js +3 -3
  87. package/dist/lib/task/intermediate-cleanup.js +383 -0
  88. package/dist/lib/task/invalidation.js +18 -65
  89. package/dist/lib/task/ledger-intents.js +7 -22
  90. package/dist/lib/task/ledger.js +1 -3
  91. package/dist/lib/task/lifecycle.js +19 -1
  92. package/dist/lib/task/local-artifact-finalization.js +132 -157
  93. package/dist/lib/task/manual-validation-completion.js +26 -0
  94. package/dist/lib/task/manual-validation-evidence.js +130 -0
  95. package/dist/lib/task/manual-validation-receipt.js +101 -0
  96. package/dist/lib/task/manual-validation-shared.js +40 -0
  97. package/dist/lib/task/manual-validation-transaction.js +243 -0
  98. package/dist/lib/task/markdown.js +47 -0
  99. package/dist/lib/task/orchestration.js +14 -17
  100. package/dist/lib/task/qualification-audit.js +20 -34
  101. package/dist/lib/task/review-artifacts.js +2 -18
  102. package/dist/lib/task/review-command.js +25 -0
  103. package/dist/lib/task/review-finalization.js +125 -193
  104. package/dist/lib/task/review-fingerprint.js +2 -2
  105. package/dist/lib/task/sections.js +34 -56
  106. package/dist/lib/task/verification-engine.js +116 -323
  107. package/dist/lib/task/verification.js +1 -1
  108. package/dist/lib/task/workflow-command.js +17 -0
  109. package/dist/lib/task/workflow-warning-intents.js +0 -16
  110. package/dist/lib/task/workflow-warnings.js +28 -2
  111. package/dist/lib/update.js +2 -0
  112. package/dist/lifecycle-build-manifest.json +3 -3
  113. package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +223 -33
  114. package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +3 -2
  115. package/lib/fs/durable-write.ts +29 -0
  116. package/lib/fs/file-lock.ts +113 -0
  117. package/lib/host-control/audit.ts +22 -0
  118. package/lib/host-control/client.ts +172 -0
  119. package/lib/host-control/command.ts +60 -0
  120. package/lib/host-control/path.ts +167 -0
  121. package/lib/host-control/secure-fs.ts +135 -0
  122. package/lib/host-control/server.ts +180 -0
  123. package/lib/host-control/service.ts +133 -0
  124. package/lib/init.ts +15 -2
  125. package/lib/internal/cli-route-inventory.ts +3 -1
  126. package/lib/internal/codex-lifecycle.ts +6 -6
  127. package/lib/internal/demo-promotion.ts +354 -0
  128. package/lib/internal/demo-transcript.ts +419 -0
  129. package/lib/internal/host-control.ts +28 -0
  130. package/lib/internal/manual-validation.ts +306 -0
  131. package/lib/internal/platform-pr.ts +15 -4
  132. package/lib/internal/release-workflow.ts +74 -82
  133. package/lib/internal/sandbox-control.ts +162 -1
  134. package/lib/internal/task-artifact.ts +13 -216
  135. package/lib/internal/task-create.ts +5 -5
  136. package/lib/internal/task-event.ts +20 -11
  137. package/lib/internal/task-operation-registry.ts +111 -3
  138. package/lib/internal/task-review.ts +6 -51
  139. package/lib/internal/task-validate.ts +26 -10
  140. package/lib/owned-path.ts +26 -0
  141. package/lib/platform/check-status.ts +9 -0
  142. package/lib/platform/comment-safety.ts +5 -0
  143. package/lib/platform/completion-backfill.ts +1 -1
  144. package/lib/platform/github-data.ts +322 -0
  145. package/lib/platform/github-provider.ts +15 -20
  146. package/lib/platform/issue-comments.ts +3 -4
  147. package/lib/platform/issues.ts +4 -245
  148. package/lib/platform/pr-checks.ts +13 -77
  149. package/lib/platform/pr-summary.ts +61 -5
  150. package/lib/platform/pull-requests.ts +3 -170
  151. package/lib/platform/snapshots.ts +53 -0
  152. package/lib/platform/verification-sync.ts +145 -155
  153. package/lib/release/workflow.ts +3 -3
  154. package/lib/sandbox/commands/create.ts +7 -6
  155. package/lib/sandbox/commands/rm.ts +3 -1909
  156. package/lib/sandbox/control/audit.ts +253 -0
  157. package/lib/sandbox/control/client.ts +109 -66
  158. package/lib/sandbox/control/executor.ts +134 -74
  159. package/lib/sandbox/control/finalization-response.ts +29 -0
  160. package/lib/sandbox/control/identity-sentinel.ts +137 -0
  161. package/lib/sandbox/control/lifecycle.ts +45 -3
  162. package/lib/sandbox/control/native-file-lock.ts +6 -107
  163. package/lib/sandbox/control/protocol.ts +75 -6
  164. package/lib/sandbox/control/server.ts +538 -123
  165. package/lib/sandbox/control/state.ts +78 -0
  166. package/lib/sandbox/control/task-workflow.ts +95 -0
  167. package/lib/sandbox/control/workflow-executor.ts +102 -0
  168. package/lib/sandbox/index.ts +2 -2
  169. package/lib/sandbox/recovery.ts +28 -5
  170. package/lib/sandbox/removal.ts +2144 -0
  171. package/lib/sandbox/task-cleanup.ts +209 -0
  172. package/lib/sandbox/workspace-view.ts +17 -15
  173. package/lib/task/activity-intent.ts +2 -2
  174. package/lib/task/activity-log.ts +27 -19
  175. package/lib/task/artifact-command.ts +104 -0
  176. package/lib/task/artifact-lifecycle.ts +31 -26
  177. package/lib/task/artifact-name.ts +55 -0
  178. package/lib/task/artifact-operations.ts +22 -68
  179. package/lib/task/artifact-receipts.ts +3 -14
  180. package/lib/task/artifact-repair-intent.ts +99 -18
  181. package/lib/task/artifact-schema.ts +5 -34
  182. package/lib/task/capabilities.ts +13 -22
  183. package/lib/task/codex-orchestration.ts +85 -24
  184. package/lib/task/commit-identity.ts +31 -0
  185. package/lib/task/commit-operation.ts +2 -16
  186. package/lib/task/control-authority.ts +502 -5
  187. package/lib/task/control-recovery.ts +174 -0
  188. package/lib/task/create.ts +63 -8
  189. package/lib/task/decision-details.ts +3 -71
  190. package/lib/task/events.ts +131 -10
  191. package/lib/task/finalization-state.ts +11 -0
  192. package/lib/task/human-override.ts +3 -8
  193. package/lib/task/implementation-inputs.ts +3 -3
  194. package/lib/task/intermediate-cleanup.ts +455 -0
  195. package/lib/task/invalidation.ts +18 -62
  196. package/lib/task/ledger-intents.ts +7 -24
  197. package/lib/task/ledger.ts +1 -3
  198. package/lib/task/lifecycle.ts +17 -1
  199. package/lib/task/local-artifact-finalization.ts +148 -182
  200. package/lib/task/manual-validation-completion.ts +40 -0
  201. package/lib/task/manual-validation-evidence.ts +209 -0
  202. package/lib/task/manual-validation-receipt.ts +152 -0
  203. package/lib/task/manual-validation-shared.ts +50 -0
  204. package/lib/task/manual-validation-transaction.ts +304 -0
  205. package/lib/task/markdown.ts +70 -0
  206. package/lib/task/orchestration.ts +19 -19
  207. package/lib/task/qualification-audit.ts +22 -33
  208. package/lib/task/review-artifacts.ts +1 -20
  209. package/lib/task/review-command.ts +23 -0
  210. package/lib/task/review-finalization.ts +131 -259
  211. package/lib/task/review-fingerprint.ts +2 -2
  212. package/lib/task/sections.ts +33 -54
  213. package/lib/task/verification-engine.ts +110 -366
  214. package/lib/task/verification-types.ts +17 -0
  215. package/lib/task/verification.ts +1 -1
  216. package/lib/task/workflow-command.ts +18 -0
  217. package/lib/task/workflow-warning-intents.ts +0 -13
  218. package/lib/task/workflow-warnings.ts +21 -2
  219. package/lib/update.ts +6 -0
  220. package/package.json +6 -6
  221. package/templates/.agents/hooks/lifecycle-delegation.js +6 -6
  222. package/templates/.agents/rules/testing-discipline.en.md +2 -0
  223. package/templates/.agents/rules/testing-discipline.zh-CN.md +2 -0
  224. package/templates/.agents/scripts/package.json +3 -0
  225. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +32 -20
  226. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +32 -20
  227. package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +1 -1
  228. package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +1 -1
  229. package/templates/.agents/skills/complete-manual-validation/reference/report-template.en.md +2 -0
  230. package/templates/.agents/skills/complete-manual-validation/reference/report-template.zh-CN.md +2 -0
  231. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +16 -7
  232. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +16 -7
  233. package/templates/.agents/skills/complete-task/SKILL.en.md +1 -1
  234. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +1 -1
  235. package/templates/.agents/skills/release/SKILL.en.md +7 -3
  236. package/templates/.agents/skills/release/SKILL.zh-CN.md +7 -3
  237. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +20 -3
  238. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +20 -3
  239. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +4 -1
  240. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +4 -1
  241. package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +2 -0
  242. package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +2 -0
  243. package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
  244. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
  245. package/templates/.agents/skills/test/SKILL.en.md +17 -6
  246. package/templates/.agents/skills/test/SKILL.zh-CN.md +17 -6
  247. package/templates/.claude/commands/run-manual-validation.en.md +1 -1
  248. package/templates/.claude/commands/run-manual-validation.zh-CN.md +1 -1
  249. package/dist/lib/platform/adapters.js +0 -56
  250. package/lib/platform/adapters.ts +0 -197
@@ -1,8 +1,13 @@
1
+ import { createHash, randomUUID } from 'node:crypto';
2
+ import { readFileSync } from 'node:fs';
3
+ import path from 'node:path';
1
4
  import {
2
5
  recoverSandboxControl,
3
6
  requestSandboxControl,
4
7
  requestSandboxTaskFinalization,
5
8
  requestSandboxTaskControl,
9
+ requestSandboxTaskWorkflow,
10
+ resolveVisibleActiveShortId,
6
11
  SandboxControlClientError
7
12
  } from '../sandbox/control/client.ts';
8
13
  import { normalizeAgentToken, AGENT_USAGE_HINT } from '../agent-clients/tokens.ts';
@@ -14,6 +19,113 @@ import {
14
19
  } from '../agent-clients/adapters/codex-lifecycle/sandbox-controller.ts';
15
20
  import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.ts';
16
21
  import { parseTaskCreateResult, taskCreateExitCode } from '../task/create-service.ts';
22
+ import { createTaskWorkflowRequest } from '../sandbox/control/task-workflow.ts';
23
+ import { computeLifecycleBuildIdentity } from '../agent-clients/adapters/codex-lifecycle/build-identity.ts';
24
+ import { routeOrchestration } from '../task/orchestration.ts';
25
+ import { parseArtifactName } from '../task/artifact-name.ts';
26
+ import type { LifecycleAuthorityRequestV1 } from '../task/control-authority.ts';
27
+
28
+ function valueAfter(args: readonly string[], flag: string): string | undefined {
29
+ for (let index = 0; index < args.length; index += 1) {
30
+ if (args[index] === flag) return args[index + 1];
31
+ if (args[index]?.startsWith(`${flag}=`)) return args[index]!.slice(flag.length + 1);
32
+ }
33
+ return undefined;
34
+ }
35
+
36
+ function withoutValue(args: readonly string[], flag: string): string[] {
37
+ const result: string[] = [];
38
+ for (let index = 0; index < args.length; index += 1) {
39
+ if (args[index] === flag) { index += 1; continue; }
40
+ if (args[index]?.startsWith(`${flag}=`)) continue;
41
+ result.push(args[index]!);
42
+ }
43
+ return result;
44
+ }
45
+
46
+ function lifecycleIds(authorityRef: string, taskId: string): Readonly<{ operationId: string; lifecycleRequestId: string }> {
47
+ const digest = (label: string) => createHash('sha256')
48
+ .update(`${label}\0${taskId}\0${authorityRef}`, 'utf8')
49
+ .digest('hex');
50
+ return { operationId: digest('operation'), lifecycleRequestId: digest('lifecycle-request') };
51
+ }
52
+
53
+ function lifecycleAuthorityIdentity(
54
+ authorityRef: string,
55
+ taskId: string,
56
+ requestId: string,
57
+ phase: LifecycleAuthorityRequestV1['phase'],
58
+ family: LifecycleAuthorityRequestV1['family'],
59
+ artifact: string,
60
+ round: number
61
+ ): LifecycleAuthorityRequestV1 | undefined {
62
+ const contextPath = process.env.AGENT_INFRA_CODEX_CONTROLLER_CONTEXT;
63
+ if (!authorityRef || !contextPath) return undefined;
64
+ const routed = verifyCodexSandboxControllerContextWithWarnings(contextPath, { repoRoot: process.cwd() });
65
+ const buildIdentity = computeLifecycleBuildIdentity(process.cwd());
66
+ const hookDefinitionHash = createHash('sha256')
67
+ .update(readFileSync(path.join(process.cwd(), '.codex', 'hooks.json')))
68
+ .digest('hex');
69
+ const ids = lifecycleIds(authorityRef, taskId);
70
+ return {
71
+ version: 1,
72
+ requestId,
73
+ operationId: ids.operationId,
74
+ phase,
75
+ taskId,
76
+ family,
77
+ artifact,
78
+ round,
79
+ lifecycleRequestId: ids.lifecycleRequestId,
80
+ authorityRef,
81
+ expectedControlGeneration: routed.context.controlGeneration,
82
+ expectedControllerInstanceDigest: routed.context.controllerInstanceDigest,
83
+ expectedBuildIdentityDigest: createHash('sha256').update(JSON.stringify(buildIdentity)).digest('hex'),
84
+ expectedHookDefinitionHash: hookDefinitionHash
85
+ };
86
+ }
87
+
88
+ function lifecycleAuthoritySelector(args: readonly string[]): LifecycleAuthorityRequestV1 | undefined {
89
+ if (!isCanonicalCodexPrepare(args)) return undefined;
90
+ const authorityRef = valueAfter(args, '--capability-ref');
91
+ const taskId = args[0];
92
+ if (!authorityRef || !taskId) return undefined;
93
+ const routed = routeOrchestration(taskId);
94
+ if (!routed.next || !['analysis', 'plan', 'code'].includes(routed.next.stage)) return undefined;
95
+ return lifecycleAuthorityIdentity(
96
+ authorityRef,
97
+ taskId,
98
+ randomUUID(),
99
+ 'orchestration.prepare',
100
+ routed.next.stage === 'analysis' ? 'analysis' : routed.next.stage === 'plan' ? 'plan' : 'code',
101
+ routed.next.artifact,
102
+ routed.next.round
103
+ );
104
+ }
105
+
106
+ function lifecycleWorkflowAuthoritySelector(
107
+ originalArgs: readonly string[],
108
+ workflow: ReturnType<typeof createTaskWorkflowRequest>
109
+ ): LifecycleAuthorityRequestV1 | undefined {
110
+ const authorityRef = valueAfter(originalArgs, '--capability-ref');
111
+ if (!authorityRef) return undefined;
112
+ if (workflow.operation === 'artifact-finalize-local') {
113
+ const family = valueAfter(workflow.args, '--family');
114
+ const artifact = valueAfter(workflow.args, '--artifact');
115
+ const parsed = artifact ? parseArtifactName(artifact) : null;
116
+ if ((family !== 'analysis' && family !== 'plan' && family !== 'code')
117
+ || !parsed || parsed.family !== family) return undefined;
118
+ return lifecycleAuthorityIdentity(authorityRef, workflow.taskId, workflow.id,
119
+ 'artifact.finalize-local', family, artifact!, parsed.round);
120
+ }
121
+ if (workflow.operation !== 'event') return undefined;
122
+ const completed = /^(analysis|plan|code)\.completed$/u.exec(workflow.args[1] ?? '');
123
+ const artifact = valueAfter(workflow.args, '--artifact');
124
+ const parsed = artifact ? parseArtifactName(artifact) : null;
125
+ if (!completed || !parsed || parsed.family !== completed[1]) return undefined;
126
+ return lifecycleAuthorityIdentity(authorityRef, workflow.taskId, workflow.id,
127
+ 'task-event.completed', completed[1] as LifecycleAuthorityRequestV1['family'], artifact!, parsed.round);
128
+ }
17
129
 
18
130
  function isCanonicalCodexPrepare(args: readonly string[]): boolean {
19
131
  if (args[1] !== 'prepare') return false;
@@ -151,6 +263,49 @@ async function sandboxControl(args: string[]): Promise<void> {
151
263
  sandboxFinalizationClient(commandArgs);
152
264
  return;
153
265
  }
266
+ if (family === 'task-workflow') {
267
+ const command = commandArgs[0] as Parameters<typeof createTaskWorkflowRequest>[0] | undefined;
268
+ const taskId = process.env.AGENT_INFRA_TASK_ID;
269
+ const generation = process.env.AGENT_INFRA_CONTROL_GENERATION;
270
+ if (!command || !taskId || !generation) {
271
+ process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: 'SANDBOX_CONTROL_REQUEST_INVALID', message: 'task-workflow requires a bound task and generation' } })}\n`);
272
+ process.exitCode = 1;
273
+ return;
274
+ }
275
+ let workflow;
276
+ try {
277
+ const args = withoutValue(commandArgs.slice(1), '--capability-ref');
278
+ if (/^\d+$/.test(args[0] ?? '')) {
279
+ const resolved = resolveVisibleActiveShortId(args[0]!);
280
+ if (resolved !== taskId) throw new Error('SANDBOX_TASK_REF_MISMATCH');
281
+ args[0] = resolved;
282
+ }
283
+ workflow = createTaskWorkflowRequest(command, args, taskId, generation);
284
+ }
285
+ catch (error) {
286
+ process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: 'SANDBOX_CONTROL_REQUEST_INVALID', message: error instanceof Error ? error.message : String(error) } })}\n`);
287
+ process.exitCode = 1;
288
+ return;
289
+ }
290
+ let response;
291
+ try {
292
+ const authority = lifecycleWorkflowAuthoritySelector(commandArgs, workflow);
293
+ response = requestSandboxTaskWorkflow({
294
+ workflow,
295
+ ...(authority ? { authority } : {})
296
+ });
297
+ }
298
+ catch (error) {
299
+ if (!(error instanceof SandboxControlClientError)) throw error;
300
+ writeClientError(error);
301
+ process.exitCode = error.detail.retryable ? 75 : 1;
302
+ return;
303
+ }
304
+ process.stdout.write(response.stdout);
305
+ process.stderr.write(response.stderr);
306
+ process.exitCode = response.phase === 'rejected' ? response.error?.retryable ? 75 : 1 : response.exitCode ?? 1;
307
+ return;
308
+ }
154
309
  let response;
155
310
  try {
156
311
  if (family === 'task-orchestration' && isCanonicalCodexPrepare(commandArgs)) {
@@ -158,7 +313,13 @@ async function sandboxControl(args: string[]): Promise<void> {
158
313
  const proof = contextPath
159
314
  ? controllerProofFromContext(verifyCodexSandboxControllerContextWithWarnings(contextPath).context)
160
315
  : null;
161
- response = requestSandboxTaskControl({ family, args: commandArgs, controllerProof: proof });
316
+ const authority = lifecycleAuthoritySelector(commandArgs);
317
+ response = requestSandboxTaskControl({
318
+ family,
319
+ args: commandArgs,
320
+ controllerProof: authority ? null : proof,
321
+ ...(authority ? { authority } : {})
322
+ });
162
323
  } else {
163
324
  response = requestSandboxControl({ family, args: commandArgs });
164
325
  }
@@ -1,229 +1,26 @@
1
- import fs from 'node:fs';
2
- import path from 'node:path';
3
-
4
- import { resolveArtifactContext } from '../task/artifact-lifecycle.ts';
5
- import { parseArtifactName } from '../task/artifact-lifecycle.ts';
6
- import { locateActivityLog, pairEntries, startedBackedRows } from '../task/activity-log.ts';
7
- import { finalizeLocalArtifact } from '../task/local-artifact-finalization.ts';
8
- import type { LocalArtifactFamily } from '../task/local-artifact-finalization.ts';
9
- import { resolveTaskRef } from '../task/resolve-ref.ts';
10
- import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.ts';
11
- import { getArtifactSchema } from '../task/artifact-schema.ts';
12
- import {
13
- applyArtifactRepair,
14
- initializeArtifactSkeleton,
15
- inspectArtifactStructure
16
- } from '../task/artifact-operations.ts';
1
+ import { parseArtifactCommand, executeArtifactCommand } from '../task/artifact-command.ts';
2
+ import { ensureInternalHandlerRoute } from './cli-route-inventory.ts';
17
3
 
18
4
  const USAGE = `Usage: agent-infra-internal task-artifact <N | TASK-id> inspect --family <family>\n agent-infra-internal task-artifact <N | TASK-id> init --family <family> --artifact <artifact> [--locale <zh-CN|en>]\n agent-infra-internal task-artifact <N | TASK-id> repair --family <family> --artifact <artifact> --expected-sha256 <sha256> --expected-semantic-digest <digest>\n agent-infra-internal task-artifact <N | TASK-id> finalize-local --family <analysis|plan|code> --artifact <artifact>\n\nInspect, initialize, repair, or finalize a workflow artifact without changing task state.\n`;
19
5
 
20
- function failUsage(message: string): void {
21
- process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: 'ARTIFACT_PAYLOAD_INVALID', message } })}\n`);
22
- process.stderr.write(USAGE);
23
- process.exitCode = 2;
24
- }
25
-
26
- function hasOpenLocalArtifactRound(taskDir: string, family: string, round: number): boolean {
27
- try {
28
- const activity = locateActivityLog(fs.readFileSync(path.join(taskDir, 'task.md'), 'utf8'));
29
- if (!activity) return false;
30
- const label = family === 'analysis'
31
- ? 'Analyze Task'
32
- : family === 'review-analysis'
33
- ? 'Review Analysis'
34
- : family === 'plan'
35
- ? 'Plan Task'
36
- : family === 'review-plan'
37
- ? 'Review Plan'
38
- : family === 'code'
39
- ? 'Code Task'
40
- : 'Review Code';
41
- const qualifier = family === 'code'
42
- ? '(?:, (?:fix for review-code(?:-r\\d+)?\\.md|decision II-[1-9]\\d*))?'
43
- : '';
44
- const expected = new RegExp(`^${label} \\(Round ${round}${qualifier}\\)$`);
45
- return startedBackedRows(pairEntries(activity.entries)).some((row) => expected.test(row.step) && !row.done);
46
- } catch {
47
- return false;
48
- }
49
- }
50
6
 
51
7
  function taskArtifact(args: string[] = []): void {
52
8
  if (!ensureInternalHandlerRoute('task-artifact', args)) return;
53
9
  if (args[0] === '--help' || args[0] === '-h') { process.stdout.write(USAGE); return; }
54
- if (args.length < 2) { failUsage('task ref and operation are required'); return; }
55
- const operation = args[1];
56
- if (!internalHandlerRoute('task-artifact', 'inspect', operation ?? '')
57
- && !internalHandlerRoute('task-artifact', 'init', operation ?? '')
58
- && !internalHandlerRoute('task-artifact', 'repair', operation ?? '')
59
- && !internalHandlerRoute('task-artifact', 'finalize-local', operation ?? '')) { failUsage(`unknown operation '${operation}'`); return; }
60
- let family = '';
61
- let artifact = '';
62
- let locale: 'zh-CN' | 'en' | undefined;
63
- let expectedSha256 = '';
64
- let expectedSemanticDigest = '';
65
- const seen = new Set<string>();
66
- for (let index = 2; index < args.length; index += 1) {
67
- const flag = args[index]!;
68
- if (flag !== '--family' && flag !== '--artifact' && flag !== '--locale' && flag !== '--expected-sha256' && flag !== '--expected-semantic-digest') { failUsage(`unknown option '${flag}'`); return; }
69
- if (seen.has(flag)) { failUsage(`duplicate option '${flag}'`); return; }
70
- const value = args[++index];
71
- if (!value || value.startsWith('--')) { failUsage(`option '${flag}' requires a value`); return; }
72
- seen.add(flag);
73
- if (flag === '--family') family = value;
74
- else if (flag === '--artifact') artifact = value;
75
- else if (flag === '--locale') {
76
- if (value !== 'zh-CN' && value !== 'en') { failUsage("option '--locale' must be 'zh-CN' or 'en'"); return; }
77
- locale = value;
78
- } else if (flag === '--expected-sha256') expectedSha256 = value;
79
- else expectedSemanticDigest = value;
80
- }
81
- if (!family) { failUsage("option '--family' is required"); return; }
82
- if ((operation === 'init' || operation === 'repair' || operation === 'finalize-local') && !artifact) { failUsage("option '--artifact' is required"); return; }
83
- if (internalHandlerRoute('task-artifact', 'inspect', operation ?? '')) {
84
- if (artifact || locale || expectedSha256 || expectedSemanticDigest) { failUsage("artifact options are only valid for init, repair, or finalize-local"); return; }
85
- const result = resolveArtifactContext(args[0]!, family);
86
- const output = family === 'code' && result.codeMode ? {
87
- ...result,
88
- mode: result.codeMode.mode,
89
- code_max: result.codeMode.codeMax,
90
- rev_max: result.codeMode.reviewMax,
91
- verdict: result.codeMode.verdict,
92
- next_round: result.next?.round ?? null,
93
- next_artifact: result.next?.name ?? null,
94
- review_artifact: result.codeMode.reviewArtifact,
95
- implementation_input: result.codeMode.implementationInput,
96
- decision_id: result.codeMode.decisionId,
97
- decision_evidence: result.codeMode.decisionEvidence,
98
- message: result.codeMode.message
99
- } : result;
100
- process.stdout.write(`${JSON.stringify(output)}\n`);
101
- if (result.status === 'refused') process.exitCode = 1;
102
- else if (result.status === 'failed') process.exitCode = 2;
103
- return;
104
- }
105
- if (internalHandlerRoute('task-artifact', 'init', operation ?? '')) {
106
- if (expectedSha256 || expectedSemanticDigest) { failUsage("repair baseline options are only valid for repair"); return; }
107
- const schema = getArtifactSchema(family);
108
- const resolved = resolveTaskRef(args[0]!);
109
- if (!schema) { failUsage(`init does not support artifact family '${family}'`); return; }
110
- if (!resolved.ok) {
111
- process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: resolved.code, message: resolved.message } })}\n`);
112
- process.exitCode = 1;
113
- return;
114
- }
115
- const parsed = parseArtifactName(artifact);
116
- if (!parsed || parsed.family !== family) {
117
- process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: 'ARTIFACT_INIT_CONTEXT_INVALID', message: `artifact '${artifact}' does not match ${family}` } })}\n`);
118
- process.exitCode = 1;
119
- return;
120
- }
121
- const context = resolveArtifactContext(args[0]!, family);
122
- const isNextArtifact = context.status === 'ready' && context.next?.name === artifact;
123
- const isCurrentOpenArtifact = context.status === 'ready'
124
- && context.taskDir !== null
125
- && hasOpenLocalArtifactRound(context.taskDir, family, parsed.round);
126
- if (!isNextArtifact && !isCurrentOpenArtifact) {
127
- process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: 'ARTIFACT_INIT_CONTEXT_INVALID', message: context.error?.message ?? `artifact '${artifact}' is not the next ${family} artifact` } })}\n`);
128
- process.exitCode = 1;
129
- return;
130
- }
131
- if (!context.taskId || !context.taskDir) {
132
- process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: 'ARTIFACT_INIT_CONTEXT_INVALID', message: `artifact '${artifact}' does not match ${family}` } })}\n`);
133
- process.exitCode = 1;
134
- return;
135
- }
136
- try {
137
- const existing = fs.lstatSync(path.join(resolved.taskDir, artifact));
138
- if (existing.isSymbolicLink() || !existing.isFile()) {
139
- process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: 'ARTIFACT_INIT_TARGET_INVALID', message: 'artifact target is not a regular file' } })}\n`);
140
- process.exitCode = 1;
141
- return;
142
- }
143
- const result = initializeArtifactSkeleton({ repoRoot: resolved.repoRoot, taskId: resolved.taskId, taskDir: resolved.taskDir, family: schema.family, artifact, ...(locale ? { locale } : {}) });
144
- process.stdout.write(`${JSON.stringify({ ...result, taskId: resolved.taskId, taskDir: resolved.taskDir, family, artifact })}\n`);
145
- if (result.status === 'failed') process.exitCode = 1;
146
- return;
147
- } catch (error) {
148
- if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {
149
- process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: 'ARTIFACT_INIT_TARGET_INVALID', message: String(error) } })}\n`);
150
- process.exitCode = 1;
151
- return;
152
- }
153
- }
154
- const result = initializeArtifactSkeleton({
155
- repoRoot: resolved.repoRoot,
156
- taskId: context.taskId,
157
- taskDir: context.taskDir,
158
- family: schema.family,
159
- artifact,
160
- ...(locale ? { locale } : {})
161
- });
162
- process.stdout.write(`${JSON.stringify({ ...result, taskId: context.taskId, taskDir: context.taskDir, family, artifact })}\n`);
163
- if (result.status === 'failed') process.exitCode = 1;
164
- return;
165
- }
166
- if (internalHandlerRoute('task-artifact', 'repair', operation ?? '')) {
167
- if (locale) { failUsage("option '--locale' is only valid for init"); return; }
168
- if (!/^[a-f0-9]{64}$/.test(expectedSha256) || !/^[a-f0-9]{64}$/.test(expectedSemanticDigest)) {
169
- failUsage("repair requires lowercase 64-character '--expected-sha256' and '--expected-semantic-digest'");
170
- return;
171
- }
172
- const schema = getArtifactSchema(family);
173
- const resolved = resolveTaskRef(args[0]!);
174
- const parsed = parseArtifactName(artifact);
175
- if (!schema || !parsed || parsed.family !== family) {
176
- process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: 'ARTIFACT_REPAIR_TARGET_INVALID', message: `artifact '${artifact}' does not match ${family}` } })}\n`);
177
- process.exitCode = 1;
178
- return;
179
- }
180
- if (!resolved.ok) {
181
- process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: resolved.code, message: resolved.message } })}\n`);
182
- process.exitCode = 1;
183
- return;
184
- }
185
- let content: string;
186
- try { content = fs.readFileSync(path.join(resolved.taskDir, artifact), 'utf8'); }
187
- catch (error) {
188
- process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: 'ARTIFACT_REPAIR_TARGET_INVALID', message: String(error) } })}\n`);
189
- process.exitCode = 1;
190
- return;
191
- }
192
- const inspection = inspectArtifactStructure(content, schema);
193
- if (!inspection.repair) {
194
- process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, artifact, family, diagnostics: inspection.diagnostics, error: { code: 'ARTIFACT_REPAIR_UNSAFE', message: inspection.diagnostics.map((item) => `${item.code}: ${item.message}`).join('; ') || 'no deterministic structural repair is available' } })}\n`);
195
- process.exitCode = 1;
196
- return;
197
- }
198
- const result = applyArtifactRepair({
199
- repoRoot: resolved.repoRoot,
200
- taskId: resolved.taskId,
201
- taskDir: resolved.taskDir,
202
- family: schema.family,
203
- artifact,
204
- expectedSha256,
205
- expectedSemanticDigest,
206
- operation: inspection.repair
207
- });
208
- process.stdout.write(`${JSON.stringify({ ...result, taskId: resolved.taskId, taskDir: resolved.taskDir, family, artifact })}\n`);
209
- if (result.status === 'failed') process.exitCode = 1;
210
- return;
211
- }
212
- const resolved = resolveTaskRef(args[0]!);
213
- const repositoryRoot = resolved.ok ? resolved.repoRoot : process.cwd();
214
- if (!artifact) { failUsage("option '--artifact' is required"); return; }
215
- if (family !== 'analysis' && family !== 'plan' && family !== 'code') {
216
- failUsage("finalize-local only supports 'analysis', 'plan', and 'code'");
10
+ let command;
11
+ try { command = parseArtifactCommand(args); }
12
+ catch (error) {
13
+ process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: {
14
+ code: 'ARTIFACT_PAYLOAD_INVALID', message: error instanceof Error ? error.message : String(error)
15
+ } })}\n`);
16
+ process.stderr.write(USAGE);
17
+ process.exitCode = 2;
217
18
  return;
218
19
  }
219
- const result = finalizeLocalArtifact({
220
- taskRef: args[0]!,
221
- family: family as LocalArtifactFamily,
222
- artifact,
223
- repoRoot: repositoryRoot
224
- });
20
+ const result = executeArtifactCommand(command);
225
21
  process.stdout.write(`${JSON.stringify(result)}\n`);
226
- if (result.status === 'failed') process.exitCode = 1;
22
+ if (result.status === 'refused') process.exitCode = 1;
23
+ else if (result.status === 'failed') process.exitCode = command.operation === 'inspect' ? 2 : 1;
227
24
  }
228
25
 
229
26
  export { taskArtifact };
@@ -1,8 +1,8 @@
1
1
  import fs from 'node:fs';
2
+ import { resolveSandboxControlTransport } from './task-operation-registry.ts';
2
3
  import path from 'node:path';
3
4
 
4
5
  import {
5
- classifySandboxControlEnvironment,
6
6
  requestSandboxTaskCreate,
7
7
  SandboxControlClientError
8
8
  } from '../sandbox/control/client.ts';
@@ -83,12 +83,12 @@ async function taskCreate(args: string[]): Promise<void> {
83
83
  if (!stat.isFile() || stat.isSymbolicLink()) throw new Error('TASK_CREATE_INPUT_INVALID: input must be a regular file');
84
84
  if (stat.size > SANDBOX_CONTROL_MAX_BYTES) throw new Error('TASK_CREATE_INPUT_TOO_LARGE: input exceeds the control limit');
85
85
  const candidate = validateTaskCreateCandidate(JSON.parse(fs.readFileSync(inputPath, 'utf8')));
86
- const environment = classifySandboxControlEnvironment();
87
- if (environment.kind === 'invalid') {
88
- output(failed(environment.code ?? 'TASK_CONTROL_TRANSPORT_INVALID', environment.message ?? 'sandbox client control configuration is invalid'));
86
+ const environment = resolveSandboxControlTransport();
87
+ if (environment.kind === 'fail-closed') {
88
+ output(failed(environment.reasonCode ?? 'TASK_CONTROL_TRANSPORT_INVALID', 'sandbox client control configuration is invalid'));
89
89
  return;
90
90
  }
91
- if (environment.kind === 'controlled') {
91
+ if (environment.kind === 'broker-client') {
92
92
  const response = requestSandboxTaskCreate({ candidate });
93
93
  if (response.phase === 'rejected') {
94
94
  const accepted = response.error?.code === 'SANDBOX_CONTROL_RESULT_UNKNOWN';
@@ -21,6 +21,7 @@ const FLAGS: Record<string, keyof TaskEventRequest> = {
21
21
  '--verdict': 'verdict', '--blockers': 'blockers', '--major': 'major',
22
22
  '--minor': 'minor', '--manual-validation': 'manualValidation', '--files-modified': 'filesModified',
23
23
  '--tests-passed': 'testsPassed', '--summary-result': 'summaryResult',
24
+ '--transaction-id': 'transactionId', '--receipt-digest': 'receiptDigest', '--pr-head-sha': 'prHeadSha',
24
25
  '--override-ticket': 'overrideTicket', '--override-target': 'overrideTarget', '--override-scope': 'overrideScope'
25
26
  };
26
27
  const NUMERIC = new Set(['round', 'question', 'blockers', 'major', 'minor', 'manualValidation', 'filesModified', 'testsPassed']);
@@ -31,37 +32,45 @@ function usageFailure(message: string): void {
31
32
  process.exitCode = 1;
32
33
  }
33
34
 
34
- async function taskEvent(args: string[] = []): Promise<void> {
35
- if (!ensureInternalHandlerRoute('task-event', args)) return;
36
- if (args[0] === '--help' || args[0] === '-h') { process.stdout.write(USAGE); return; }
37
- if (args.length < 2 || !internalHandlerRoute('task-event', 'event', args[1] ? 'event' : '')) { usageFailure('task ref and event are required'); return; }
35
+ export function parseTaskEventRequest(args: readonly string[]): TaskEventRequest {
36
+ if (args.length < 2) throw new Error('task ref and event are required');
38
37
  const request: TaskEventRequest = { taskRef: args[0]!, event: args[1]!, agent: '' };
39
38
  const seen = new Set<string>();
40
39
  for (let index = 2; index < args.length; index += 1) {
41
40
  const flag = args[index]!;
42
41
  if (flag === '--orchestrated') {
43
- if (seen.has(flag)) { usageFailure(`duplicate option '${flag}'`); return; }
42
+ if (seen.has(flag)) throw new Error(`duplicate option '${flag}'`);
44
43
  seen.add(flag); request.orchestrated = true; continue;
45
44
  }
46
45
  if (flag === '--dry-run') {
47
- if (seen.has(flag)) { usageFailure(`duplicate option '${flag}'`); return; }
46
+ if (seen.has(flag)) throw new Error(`duplicate option '${flag}'`);
48
47
  seen.add(flag); request.dryRun = true; continue;
49
48
  }
50
49
  const key = FLAGS[flag];
51
- if (!key) { usageFailure(`unknown option '${flag}'`); return; }
52
- if (seen.has(flag)) { usageFailure(`duplicate option '${flag}'`); return; }
50
+ if (!key) throw new Error(`unknown option '${flag}'`);
51
+ if (seen.has(flag)) throw new Error(`duplicate option '${flag}'`);
53
52
  const value = args[++index];
54
- if (value === undefined || value.startsWith('--')) { usageFailure(`option '${flag}' requires a value`); return; }
53
+ if (value === undefined || value.startsWith('--')) throw new Error(`option '${flag}' requires a value`);
55
54
  seen.add(flag);
56
55
  if (NUMERIC.has(key)) (request as Record<string, unknown>)[key] = Number(value);
57
56
  else if (key === 'verdict') request.verdict = value as Verdict;
58
57
  else (request as Record<string, unknown>)[key] = value;
59
58
  }
60
59
  const agent = normalizeAgentToken(String(request.agent ?? ''));
61
- if (!agent) { usageFailure(`invalid --agent '${request.agent}': ${AGENT_USAGE_HINT}`); return; }
60
+ if (!agent) throw new Error(`invalid --agent '${request.agent}': ${AGENT_USAGE_HINT}`);
62
61
  request.agent = agent;
63
62
  const dryRunConflict = overrideDryRunConflict(request as unknown as Record<string, unknown>);
64
- if (dryRunConflict) { usageFailure(dryRunConflict.message); return; }
63
+ if (dryRunConflict) throw new Error(dryRunConflict.message);
64
+ return request;
65
+ }
66
+
67
+ async function taskEvent(args: string[] = []): Promise<void> {
68
+ if (!ensureInternalHandlerRoute('task-event', args)) return;
69
+ if (args[0] === '--help' || args[0] === '-h') { process.stdout.write(USAGE); return; }
70
+ if (!internalHandlerRoute('task-event', 'event', args[1] ? 'event' : '')) { usageFailure('task ref and event are required'); return; }
71
+ let request: TaskEventRequest;
72
+ try { request = parseTaskEventRequest(args); }
73
+ catch (error) { usageFailure(error instanceof Error ? error.message : String(error)); return; }
65
74
  const resolved = resolveTaskRef(request.taskRef);
66
75
  if (!resolved.ok) {
67
76
  process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: resolved.code, message: resolved.message } })}\n`);