@fitlab-ai/agent-infra 0.9.6 → 0.9.8

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 (178) hide show
  1. package/dist/bin/internal-cli.js +10 -0
  2. package/dist/lib/agent-clients/adapters/claude-code.js +3 -3
  3. package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +47 -5
  4. package/dist/lib/agent-clients/adapters/codex-lifecycle/build-identity.js +162 -0
  5. package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +245 -0
  6. package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +398 -0
  7. package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +23 -6
  8. package/dist/lib/decide.js +68 -14
  9. package/dist/lib/defaults.json +10 -2
  10. package/dist/lib/git/workflow.js +30 -3
  11. package/dist/lib/init.js +1 -1
  12. package/dist/lib/internal/codex-lifecycle.js +69 -1
  13. package/dist/lib/internal/codex-sandbox-controller.js +106 -0
  14. package/dist/lib/internal/release-workflow.js +1 -3
  15. package/dist/lib/internal/task-activity.js +57 -7
  16. package/dist/lib/internal/task-event.js +57 -4
  17. package/dist/lib/internal/task-ledger.js +59 -5
  18. package/dist/lib/internal/task-lifecycle.js +59 -8
  19. package/dist/lib/internal/task-orchestration.js +48 -8
  20. package/dist/lib/internal/task-override.js +131 -0
  21. package/dist/lib/internal/task-review.js +64 -6
  22. package/dist/lib/internal/task-verify.js +65 -3
  23. package/dist/lib/internal/task-warning.js +57 -4
  24. package/dist/lib/platform/context.js +6 -4
  25. package/dist/lib/platform/github-client.js +55 -2
  26. package/dist/lib/platform/pr-summary.js +5 -3
  27. package/dist/lib/process-data/checkpoint.js +319 -0
  28. package/dist/lib/process-data/index.js +128 -60
  29. package/dist/lib/process-data/materialize.js +84 -0
  30. package/dist/lib/process-data/normalize.js +34 -1
  31. package/dist/lib/process-data/reconcile.js +55 -1
  32. package/dist/lib/process-data/sources.js +422 -1
  33. package/dist/lib/process-data/store.js +341 -11
  34. package/dist/lib/runtime/agent-runtime.js +56 -0
  35. package/dist/lib/sandbox/commands/create.js +394 -264
  36. package/dist/lib/sandbox/commands/rm.js +64 -15
  37. package/dist/lib/sandbox/commands/start.js +4 -3
  38. package/dist/lib/sandbox/control/container-identity.js +64 -0
  39. package/dist/lib/sandbox/control/executor.js +23 -9
  40. package/dist/lib/sandbox/control/lifecycle.js +590 -61
  41. package/dist/lib/sandbox/control/protocol.js +7 -0
  42. package/dist/lib/sandbox/control/server.js +202 -26
  43. package/dist/lib/sandbox/control/state.js +51 -25
  44. package/dist/lib/sandbox/control/timing.js +8 -0
  45. package/dist/lib/sandbox/engine.js +14 -7
  46. package/dist/lib/sandbox/mounts.js +7 -0
  47. package/dist/lib/sandbox/recovery.js +135 -50
  48. package/dist/lib/sandbox/workspace-view.js +59 -20
  49. package/dist/lib/server/daemon.js +1 -1
  50. package/dist/lib/server/process-control.js +11 -1
  51. package/dist/lib/server/process-state.js +107 -20
  52. package/dist/lib/task/activity-intent.js +4 -1
  53. package/dist/lib/task/artifact-lifecycle.js +82 -11
  54. package/dist/lib/task/artifact-receipts.js +128 -0
  55. package/dist/lib/task/codex-orchestration.js +81 -6
  56. package/dist/lib/task/commands/validate.js +1 -1
  57. package/dist/lib/task/create.js +10 -2
  58. package/dist/lib/task/decision-intents.js +4 -2
  59. package/dist/lib/task/delegation-receipts.js +120 -17
  60. package/dist/lib/task/events.js +148 -7
  61. package/dist/lib/task/guard-override.js +31 -0
  62. package/dist/lib/task/human-override.js +1240 -0
  63. package/dist/lib/task/ledger-intents.js +6 -3
  64. package/dist/lib/task/ledger.js +13 -1
  65. package/dist/lib/task/lifecycle.js +69 -14
  66. package/dist/lib/task/orchestration.js +293 -18
  67. package/dist/lib/task/review-finalization.js +3 -2
  68. package/dist/lib/task/sections.js +5 -1
  69. package/dist/lib/task/task-execution-lock.js +5 -4
  70. package/dist/lib/task/verification-engine.js +80 -22
  71. package/dist/lib/task/verification.js +13 -1
  72. package/dist/lib/task/workflow-warning-intents.js +4 -2
  73. package/dist/lib/task/write.js +2 -1
  74. package/dist/lifecycle-build-manifest.json +6 -0
  75. package/lib/agent-clients/adapters/claude-code.ts +3 -3
  76. package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +62 -4
  77. package/lib/agent-clients/adapters/codex-lifecycle/build-identity.ts +227 -0
  78. package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +322 -0
  79. package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +26 -0
  80. package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +480 -0
  81. package/lib/agent-clients/adapters/codex-lifecycle/store.ts +35 -5
  82. package/lib/decide.ts +59 -14
  83. package/lib/defaults.json +10 -2
  84. package/lib/git/workflow.ts +25 -2
  85. package/lib/init.ts +1 -1
  86. package/lib/internal/codex-lifecycle.ts +68 -1
  87. package/lib/internal/codex-sandbox-controller.ts +109 -0
  88. package/lib/internal/release-workflow.ts +1 -3
  89. package/lib/internal/task-activity.ts +46 -6
  90. package/lib/internal/task-event.ts +53 -4
  91. package/lib/internal/task-ledger.ts +48 -4
  92. package/lib/internal/task-lifecycle.ts +56 -12
  93. package/lib/internal/task-orchestration.ts +43 -8
  94. package/lib/internal/task-override.ts +138 -0
  95. package/lib/internal/task-review.ts +51 -6
  96. package/lib/internal/task-verify.ts +52 -3
  97. package/lib/internal/task-warning.ts +44 -3
  98. package/lib/platform/context.ts +8 -5
  99. package/lib/platform/github-client.ts +61 -3
  100. package/lib/platform/pr-summary.ts +10 -3
  101. package/lib/process-data/checkpoint.ts +335 -0
  102. package/lib/process-data/index.ts +112 -60
  103. package/lib/process-data/materialize.ts +83 -0
  104. package/lib/process-data/normalize.ts +29 -1
  105. package/lib/process-data/reconcile.ts +64 -2
  106. package/lib/process-data/sources.ts +449 -2
  107. package/lib/process-data/store.ts +304 -11
  108. package/lib/process-data/types.ts +124 -2
  109. package/lib/runtime/agent-runtime.ts +76 -0
  110. package/lib/sandbox/commands/create.ts +139 -6
  111. package/lib/sandbox/commands/rm.ts +64 -14
  112. package/lib/sandbox/commands/start.ts +4 -3
  113. package/lib/sandbox/config.ts +2 -1
  114. package/lib/sandbox/control/container-identity.ts +93 -0
  115. package/lib/sandbox/control/executor.ts +24 -12
  116. package/lib/sandbox/control/lifecycle.ts +685 -67
  117. package/lib/sandbox/control/protocol.ts +35 -6
  118. package/lib/sandbox/control/server.ts +184 -24
  119. package/lib/sandbox/control/state.ts +67 -33
  120. package/lib/sandbox/control/timing.ts +12 -0
  121. package/lib/sandbox/engine.ts +20 -9
  122. package/lib/sandbox/mounts.ts +8 -0
  123. package/lib/sandbox/recovery.ts +159 -49
  124. package/lib/sandbox/workspace-view.ts +73 -22
  125. package/lib/server/daemon.ts +1 -1
  126. package/lib/server/process-control.ts +13 -3
  127. package/lib/server/process-state.ts +129 -27
  128. package/lib/task/activity-intent.ts +4 -2
  129. package/lib/task/artifact-lifecycle.ts +73 -10
  130. package/lib/task/artifact-receipts.ts +161 -0
  131. package/lib/task/codex-orchestration.ts +91 -5
  132. package/lib/task/commands/validate.ts +1 -1
  133. package/lib/task/create.ts +11 -2
  134. package/lib/task/decision-intents.ts +4 -2
  135. package/lib/task/delegation-receipts.ts +205 -25
  136. package/lib/task/events.ts +132 -6
  137. package/lib/task/guard-override.ts +50 -0
  138. package/lib/task/human-override.ts +1503 -0
  139. package/lib/task/ledger-intents.ts +6 -3
  140. package/lib/task/ledger.ts +16 -1
  141. package/lib/task/lifecycle.ts +71 -14
  142. package/lib/task/orchestration.ts +443 -22
  143. package/lib/task/review-finalization.ts +6 -2
  144. package/lib/task/sections.ts +6 -2
  145. package/lib/task/task-execution-lock.ts +11 -9
  146. package/lib/task/verification-engine.ts +93 -32
  147. package/lib/task/verification.ts +15 -1
  148. package/lib/task/workflow-warning-intents.ts +4 -2
  149. package/lib/task/write.ts +4 -1
  150. package/package.json +1 -1
  151. package/templates/.agents/README.en.md +7 -0
  152. package/templates/.agents/README.zh-CN.md +5 -0
  153. package/templates/.agents/hooks/lifecycle-delegation.js +33 -4
  154. package/templates/.agents/rules/lifecycle-orchestration.en.md +10 -3
  155. package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +10 -3
  156. package/templates/.agents/rules/pr-checks-commands.github.en.md +1 -1
  157. package/templates/.agents/rules/pr-checks-commands.github.zh-CN.md +1 -1
  158. package/templates/.agents/rules/review-handshake.en.md +3 -1
  159. package/templates/.agents/rules/review-handshake.zh-CN.md +3 -1
  160. package/templates/.agents/skills/code-task/reference/dual-mode.en.md +3 -1
  161. package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +3 -1
  162. package/templates/.agents/skills/complete-task/SKILL.en.md +4 -0
  163. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +4 -0
  164. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +6 -6
  165. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +6 -6
  166. package/templates/.agents/skills/run-manual-validation/config/verify.en.json +14 -3
  167. package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +14 -3
  168. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +67 -0
  169. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +67 -0
  170. package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +25 -0
  171. package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +25 -0
  172. package/templates/.agents/skills/run-task/SKILL.en.md +9 -7
  173. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +9 -7
  174. package/templates/.agents/skills/run-task/reference/host-validation.en.md +5 -1
  175. package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +5 -1
  176. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +75 -3
  177. package/templates/.agents/workspace/README.en.md +0 -26
  178. package/templates/.agents/workspace/README.zh-CN.md +0 -26
package/dist/lib/init.js CHANGED
@@ -180,7 +180,7 @@ async function cmdInit() {
180
180
  agentClients: serializeAgentClients(defaultState)
181
181
  };
182
182
  if (sandboxEngine)
183
- config.sandbox.engine = sandboxEngine;
183
+ config.sandbox.engine = { [currentPlatform]: sandboxEngine };
184
184
  if (templateSources.length > 0)
185
185
  config.templates = { sources: templateSources };
186
186
  if (skillSources.length > 0)
@@ -4,8 +4,12 @@ import fs from 'node:fs';
4
4
  import path from 'node:path';
5
5
  import { preflightCodexLifecycleEvidence, resolveCodexTerminal, resolveCodexThread } from '../agent-clients/adapters/codex-lifecycle/app-server.js';
6
6
  import { createCodexLifecycleStore } from '../agent-clients/adapters/codex-lifecycle/store.js';
7
+ import { createCodexCapabilityStore } from '../agent-clients/adapters/codex-lifecycle/capability-store.js';
8
+ import { computeLifecycleBuildIdentity } from '../agent-clients/adapters/codex-lifecycle/build-identity.js';
9
+ import { verifyCodexSandboxControllerContext } from '../agent-clients/adapters/codex-lifecycle/sandbox-controller.js';
10
+ import { resolveTaskRef } from '../task/resolve-ref.js';
7
11
  import { activateCodexOrchestrationDelegation, activateCodexSpawnDelegation, reconcileCodexOrchestrationDelegation, sealCodexOrchestrationDelegation, sealCodexParentDelegation } from '../task/codex-orchestration.js';
8
- const USAGE = 'Usage: agent-infra-internal codex-lifecycle <hook-event|resolve-start|resolve-stop|preflight|consume> [options]\n';
12
+ const USAGE = 'Usage: agent-infra-internal codex-lifecycle <capability-arm|hook-event|resolve-start|resolve-stop|preflight|consume> [options]\n';
9
13
  const MANAGED_AGENT = /^agent-infra-lifecycle-(executor|reviewer)$/;
10
14
  function output(value) {
11
15
  process.stdout.write(`${JSON.stringify(value)}\n`);
@@ -34,6 +38,7 @@ function parse(args) {
34
38
  return null;
35
39
  }
36
40
  const allowed = {
41
+ 'capability-arm': ['--task-id'],
37
42
  'hook-event': ['--event', '--bridge'],
38
43
  'resolve-start': ['--child-id'],
39
44
  'resolve-stop': ['--child-id'],
@@ -71,6 +76,16 @@ function hookDefinitionHash() {
71
76
  const file = path.join(process.cwd(), '.codex', 'hooks.json');
72
77
  return crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex');
73
78
  }
79
+ function controllerBinding(taskId) {
80
+ const contextPath = process.env.AGENT_INFRA_CODEX_CONTROLLER_CONTEXT;
81
+ if (!contextPath)
82
+ return undefined;
83
+ const context = verifyCodexSandboxControllerContext(contextPath, taskId, { repoRoot: process.cwd() });
84
+ return {
85
+ instanceDigest: context.controllerInstanceDigest,
86
+ controlGeneration: context.controlGeneration
87
+ };
88
+ }
74
89
  async function readStdin() {
75
90
  let input = '';
76
91
  process.stdin.setEncoding('utf8');
@@ -156,6 +171,29 @@ async function codexLifecycle(args = []) {
156
171
  output({ status: 'ready', changed: false, evidence: result, diagnostics: result.diagnostics, error: null });
157
172
  return;
158
173
  }
174
+ if (parsed.operation === 'capability-arm') {
175
+ const taskId = parsed.values['--task-id'];
176
+ if (!taskId)
177
+ throw new Error('capability-arm requires --task-id');
178
+ const resolved = resolveTaskRef(taskId, { repoRoot: process.cwd() });
179
+ if (!resolved.ok)
180
+ throw new Error(`${resolved.code}: ${resolved.message}`);
181
+ const armed = createCodexCapabilityStore().arm({
182
+ taskId: resolved.taskId,
183
+ buildIdentity: computeLifecycleBuildIdentity(process.cwd()),
184
+ controller: controllerBinding(resolved.taskId)
185
+ });
186
+ output({
187
+ status: 'armed',
188
+ changed: true,
189
+ capabilityToken: armed.token,
190
+ marker: armed.marker,
191
+ expiresAt: armed.expiresAt,
192
+ buildIdentity: armed.buildIdentity,
193
+ error: null
194
+ });
195
+ return;
196
+ }
159
197
  const store = createCodexLifecycleStore({ cliVersion: cliVersion() });
160
198
  if (parsed.operation === 'hook-event') {
161
199
  const phase = parsed.values['--event'];
@@ -166,6 +204,36 @@ async function codexLifecycle(args = []) {
166
204
  throw new Error("hook-event --bridge must be 'true'");
167
205
  }
168
206
  const payload = await readStdin();
207
+ if (phase === 'post-tool') {
208
+ const capabilityToken = payloadText(payload, 'capabilityToken');
209
+ if (capabilityToken) {
210
+ const capabilityStore = createCodexCapabilityStore();
211
+ const armed = capabilityStore.inspect(capabilityToken);
212
+ const capability = createCodexCapabilityStore().attest({
213
+ token: capabilityToken,
214
+ sessionId: payloadText(payload, 'sessionId'),
215
+ turnId: payloadText(payload, 'turnId'),
216
+ toolUseId: payloadText(payload, 'toolUseId'),
217
+ hookDefinitionHash: payloadText(payload, 'hookDefinitionHash'),
218
+ buildIdentity: computeLifecycleBuildIdentity(process.cwd()),
219
+ controller: controllerBinding(armed.taskId)
220
+ });
221
+ output({
222
+ status: capability.status,
223
+ changed: true,
224
+ evidence: {
225
+ revision: capability.revision,
226
+ sessionId: capability.sessionId,
227
+ turnId: capability.turnId,
228
+ toolUseId: capability.toolUseId,
229
+ expiresAt: capability.expiresAt
230
+ },
231
+ diagnostics: [],
232
+ error: null
233
+ });
234
+ return;
235
+ }
236
+ }
169
237
  if (phase === 'post-tool' && parsed.values['--bridge'] === 'true') {
170
238
  const toolName = payloadText(payload, 'toolName');
171
239
  if (toolName === 'collaborationspawn_agent') {
@@ -0,0 +1,106 @@
1
+ import { runCodexSandboxController, verifyCodexSandboxControllerContext } from '../agent-clients/adapters/codex-lifecycle/sandbox-controller.js';
2
+ const USAGE = 'Usage: agent-infra-internal codex-sandbox-controller <run|verify-context> [options]\n';
3
+ function fail(code, message, exitCode = 1) {
4
+ process.stdout.write(`${JSON.stringify({
5
+ status: 'failed',
6
+ changed: false,
7
+ error: { code, message }
8
+ })}\n`);
9
+ process.exitCode = exitCode;
10
+ }
11
+ function parse(args) {
12
+ if (args[0] === '--help' || args[0] === '-h') {
13
+ process.stdout.write(USAGE);
14
+ return null;
15
+ }
16
+ const operation = args[0];
17
+ if (!operation || !['run', 'verify-context'].includes(operation)) {
18
+ process.stderr.write(USAGE);
19
+ fail('CODEX_SANDBOX_CONTROLLER_PAYLOAD_INVALID', 'operation must be run or verify-context', 2);
20
+ return null;
21
+ }
22
+ const allowed = new Set([
23
+ '--task-id',
24
+ '--task-ref',
25
+ '--context',
26
+ '--executor-model',
27
+ '--executor-reasoning-effort',
28
+ '--reviewer-model',
29
+ '--reviewer-reasoning-effort'
30
+ ]);
31
+ const values = {};
32
+ for (let index = 1; index < args.length; index += 1) {
33
+ const flag = args[index];
34
+ const value = args[++index];
35
+ if (!allowed.has(flag) || Object.hasOwn(values, flag) || !value || value.startsWith('--')) {
36
+ fail('CODEX_SANDBOX_CONTROLLER_PAYLOAD_INVALID', `invalid option '${flag}'`, 2);
37
+ return null;
38
+ }
39
+ values[flag] = value;
40
+ }
41
+ return { operation, values };
42
+ }
43
+ async function codexSandboxController(args = []) {
44
+ const parsed = parse(args);
45
+ if (!parsed || process.exitCode)
46
+ return;
47
+ const taskId = parsed.values['--task-id'];
48
+ if (!taskId) {
49
+ fail('CODEX_SANDBOX_CONTROLLER_PAYLOAD_INVALID', '--task-id is required', 2);
50
+ return;
51
+ }
52
+ try {
53
+ if (parsed.operation === 'verify-context') {
54
+ const contextPath = parsed.values['--context']
55
+ ?? process.env.AGENT_INFRA_CODEX_CONTROLLER_CONTEXT;
56
+ if (!contextPath)
57
+ throw new Error('CODEX_SANDBOX_CONTROLLER_CONTEXT_MISSING');
58
+ const context = verifyCodexSandboxControllerContext(contextPath, taskId);
59
+ process.stdout.write(`${JSON.stringify({
60
+ status: 'ready',
61
+ changed: false,
62
+ context: {
63
+ taskId: context.taskId,
64
+ controlGeneration: context.controlGeneration,
65
+ controllerInstanceDigest: context.controllerInstanceDigest,
66
+ expiresAt: context.expiresAt,
67
+ buildIdentity: context.buildIdentity,
68
+ hookDefinitionHash: context.hookDefinitionHash,
69
+ lifecycleProfilesHash: context.lifecycleProfilesHash
70
+ },
71
+ error: null
72
+ })}\n`);
73
+ return;
74
+ }
75
+ const taskRef = parsed.values['--task-ref'];
76
+ if (!taskRef)
77
+ throw new Error('CODEX_SANDBOX_CONTROLLER_TASK_REF_REQUIRED');
78
+ const policy = [
79
+ '--executor-model',
80
+ '--executor-reasoning-effort',
81
+ '--reviewer-model',
82
+ '--reviewer-reasoning-effort'
83
+ ];
84
+ if (policy.some((flag) => parsed.values[flag] !== undefined)
85
+ && policy.some((flag) => parsed.values[flag] === undefined)) {
86
+ throw new Error('CODEX_SANDBOX_CONTROLLER_MODEL_POLICY_INVALID');
87
+ }
88
+ const exitCode = await runCodexSandboxController({
89
+ taskId,
90
+ taskRef,
91
+ executorModel: parsed.values['--executor-model'],
92
+ executorReasoningEffort: parsed.values['--executor-reasoning-effort'],
93
+ reviewerModel: parsed.values['--reviewer-model'],
94
+ reviewerReasoningEffort: parsed.values['--reviewer-reasoning-effort']
95
+ });
96
+ process.exitCode = exitCode;
97
+ }
98
+ catch (error) {
99
+ const message = error instanceof Error ? error.message : String(error);
100
+ const code = /^([A-Z][A-Z0-9_]+)/u.exec(message)?.[1]
101
+ ?? 'CODEX_SANDBOX_CONTROLLER_FAILED';
102
+ fail(code, message);
103
+ }
104
+ }
105
+ export { codexSandboxController };
106
+ //# sourceMappingURL=codex-sandbox-controller.js.map
@@ -21,9 +21,7 @@ const DEMO_INPUT_PATHS = [
21
21
  'lib/paths.ts',
22
22
  'lib/render.ts',
23
23
  'lib/builtin-tuis.ts',
24
- 'lib/sandbox/engines/',
25
- 'src/sync-templates.js',
26
- 'templates/'
24
+ 'lib/sandbox/engines/'
27
25
  ];
28
26
  const DEMO_DIGEST_PATH = 'assets/demo-init.inputs.sha256';
29
27
  const DEMO_OUTPUT_PATH = 'assets/demo-init.gif';
@@ -1,4 +1,7 @@
1
1
  import { applyPrReviewActivityIntent, inspectPrReviewActivity } from '../task/activity-intent.js';
2
+ import { consumeHumanOverride, failureId, overrideDryRunConflict } from '../task/human-override.js';
3
+ import { resolveTaskRef } from '../task/resolve-ref.js';
4
+ import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.js';
2
5
  const USAGE = `Usage: agent-infra-internal task-activity <task-ref> pr-review-inspect
3
6
  agent-infra-internal task-activity <task-ref> pr-review-start --agent <agent> --artifact <canonical.md> --head <40hex> [--dry-run]
4
7
  agent-infra-internal task-activity <task-ref> pr-review-complete --agent <agent> --artifact <canonical.md> --head <40hex> --verdict <approved|changes-requested|commented> --blockers <N> --major <N> --minor <N> [--dry-run]
@@ -14,14 +17,15 @@ const FLAGS = {
14
17
  '--major': 'major',
15
18
  '--minor': 'minor',
16
19
  '--outcome': 'outcome',
17
- '--reason': 'reason'
20
+ '--reason': 'reason',
21
+ '--override-ticket': 'overrideTicket', '--override-target': 'overrideTarget', '--override-scope': 'overrideScope'
18
22
  };
19
23
  const COMMON = ['agent', 'artifact', 'head'];
20
24
  const ALLOWED = {
21
25
  'pr-review-inspect': new Set(['kind', 'taskRef']),
22
- 'pr-review-start': new Set(['kind', 'taskRef', 'agent', 'artifact', 'head', 'dryRun']),
23
- 'pr-review-complete': new Set(['kind', 'taskRef', 'agent', 'artifact', 'head', 'verdict', 'blockers', 'major', 'minor', 'dryRun']),
24
- 'pr-review-terminate': new Set(['kind', 'taskRef', 'agent', 'artifact', 'head', 'outcome', 'reason', 'dryRun'])
26
+ 'pr-review-start': new Set(['kind', 'taskRef', 'agent', 'artifact', 'head', 'dryRun', 'overrideTicket', 'overrideTarget', 'overrideScope']),
27
+ 'pr-review-complete': new Set(['kind', 'taskRef', 'agent', 'artifact', 'head', 'verdict', 'blockers', 'major', 'minor', 'dryRun', 'overrideTicket', 'overrideTarget', 'overrideScope']),
28
+ 'pr-review-terminate': new Set(['kind', 'taskRef', 'agent', 'artifact', 'head', 'outcome', 'reason', 'dryRun', 'overrideTicket', 'overrideTarget', 'overrideScope'])
25
29
  };
26
30
  function usageFailure(message) {
27
31
  process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: 'ACTIVITY_PAYLOAD_INVALID', message } })}\n`);
@@ -96,9 +100,55 @@ function taskActivity(args = []) {
96
100
  return;
97
101
  }
98
102
  }
99
- const result = applyPrReviewActivityIntent(values);
100
- process.stdout.write(`${JSON.stringify(result)}\n`);
101
- if (result.status === 'failed')
103
+ const dryRunConflict = overrideDryRunConflict(values);
104
+ if (dryRunConflict) {
105
+ usageFailure(dryRunConflict.message);
106
+ return;
107
+ }
108
+ const resolved = resolveTaskRef(taskRef);
109
+ if (!resolved.ok) {
110
+ usageFailure(resolved.message);
111
+ return;
112
+ }
113
+ let result;
114
+ let humanOverride = null;
115
+ try {
116
+ result = withTaskExecutionLock(resolved.repoRoot, resolved.taskId, `task-activity.${kind}`, () => {
117
+ let current = applyPrReviewActivityIntent(values, { lockAlreadyHeld: true });
118
+ if (current.status !== 'failed' || !values.overrideTicket)
119
+ return current;
120
+ if (!values.overrideTarget || !values.overrideScope) {
121
+ usageFailure('override ticket requires target and scope');
122
+ return current;
123
+ }
124
+ const consumed = consumeHumanOverride({
125
+ taskRef, ticketId: String(values.overrideTicket),
126
+ failureId: failureId('activity-intent', current.error?.code ?? 'TASK_STATE_MISMATCH'),
127
+ target: String(values.overrideTarget), scope: String(values.overrideScope)
128
+ }, {
129
+ effectExecutor: (capability) => {
130
+ const retried = applyPrReviewActivityIntent(values, { lockAlreadyHeld: true, manualOverride: capability });
131
+ current = retried;
132
+ return retried.status === 'failed' || retried.status === 'planned'
133
+ ? { code: 'OVERRIDE_EFFECT_FAILED', message: retried.status === 'planned' ? 'producer returned planned; no activity effect was committed' : `${retried.error?.code ?? 'ACTIVITY_FAILED'}: ${retried.error?.message ?? 'manual activity effect failed'}` }
134
+ : null;
135
+ }
136
+ });
137
+ humanOverride = consumed;
138
+ return current;
139
+ });
140
+ }
141
+ catch (error) {
142
+ if (!(error instanceof TaskExecutionLockError))
143
+ throw error;
144
+ process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: error.code, message: error.message } })}\n`);
145
+ process.exitCode = 1;
146
+ return;
147
+ }
148
+ process.stdout.write(`${JSON.stringify(humanOverride ? { ...result, humanOverride } : result)}\n`);
149
+ const overrideFailed = Boolean(humanOverride && typeof humanOverride === 'object' &&
150
+ humanOverride.status === 'failed');
151
+ if (result.status === 'failed' || overrideFailed)
102
152
  process.exitCode = 1;
103
153
  }
104
154
  export { taskActivity };
@@ -1,5 +1,8 @@
1
1
  import { normalizeAgentToken, AGENT_USAGE_HINT } from '../agent-clients/tokens.js';
2
2
  import { applyTaskEvent, eventCatalog } from '../task/events.js';
3
+ import { consumeHumanOverride, failureId, overrideDryRunConflict } from '../task/human-override.js';
4
+ import { resolveTaskRef } from '../task/resolve-ref.js';
5
+ import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.js';
3
6
  const USAGE = `Usage: agent-infra-internal task-event <N | TASK-id> <event> --agent <agent> [event options] [--orchestrated] [--dry-run]
4
7
 
5
8
  Apply one closed-set task lifecycle event and print a structured JSON result.
@@ -10,7 +13,8 @@ const FLAGS = {
10
13
  '--fix-for': 'fixFor', '--implementation-input': 'implementationInput',
11
14
  '--verdict': 'verdict', '--blockers': 'blockers', '--major': 'major',
12
15
  '--minor': 'minor', '--manual-validation': 'manualValidation', '--files-modified': 'filesModified',
13
- '--tests-passed': 'testsPassed', '--summary-result': 'summaryResult'
16
+ '--tests-passed': 'testsPassed', '--summary-result': 'summaryResult',
17
+ '--override-ticket': 'overrideTicket', '--override-target': 'overrideTarget', '--override-scope': 'overrideScope'
14
18
  };
15
19
  const NUMERIC = new Set(['round', 'question', 'blockers', 'major', 'minor', 'manualValidation', 'filesModified', 'testsPassed']);
16
20
  function usageFailure(message) {
@@ -77,9 +81,58 @@ function taskEvent(args = []) {
77
81
  return;
78
82
  }
79
83
  request.agent = agent;
80
- const result = applyTaskEvent(request);
81
- process.stdout.write(`${JSON.stringify(result)}\n`);
82
- if (result.status === 'failed')
84
+ const dryRunConflict = overrideDryRunConflict(request);
85
+ if (dryRunConflict) {
86
+ usageFailure(dryRunConflict.message);
87
+ return;
88
+ }
89
+ const resolved = resolveTaskRef(request.taskRef);
90
+ if (!resolved.ok) {
91
+ process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: resolved.code, message: resolved.message } })}\n`);
92
+ process.exitCode = 1;
93
+ return;
94
+ }
95
+ let result;
96
+ let humanOverride = null;
97
+ try {
98
+ result = withTaskExecutionLock(resolved.repoRoot, resolved.taskId, `task-event.${request.event}`, () => {
99
+ let current = applyTaskEvent(request, { lockAlreadyHeld: true });
100
+ const values = request;
101
+ if (current.status !== 'failed' || !values.overrideTicket)
102
+ return current;
103
+ if (!values.overrideTarget || !values.overrideScope) {
104
+ return { ...current, humanOverride: { status: 'failed', error: { code: 'OVERRIDE_PAYLOAD_INVALID', message: 'override ticket requires target and scope' } } };
105
+ }
106
+ const consumed = consumeHumanOverride({
107
+ taskRef: request.taskRef,
108
+ ticketId: String(values.overrideTicket),
109
+ failureId: failureId('task-event', current.error?.code ?? 'EVENT_TRANSITION_INVALID'),
110
+ target: String(values.overrideTarget),
111
+ scope: String(values.overrideScope)
112
+ }, {
113
+ effectExecutor: (capability) => {
114
+ const retried = applyTaskEvent(request, { lockAlreadyHeld: true, manualOverride: capability });
115
+ current = retried;
116
+ return retried.status === 'failed' || retried.status === 'planned'
117
+ ? { code: 'OVERRIDE_EFFECT_FAILED', message: retried.status === 'planned' ? 'producer returned planned; no task event was committed' : `${retried.error?.code ?? 'EVENT_FAILED'}: ${retried.error?.message ?? 'manual task event effect failed'}` }
118
+ : null;
119
+ }
120
+ });
121
+ humanOverride = consumed;
122
+ return current;
123
+ });
124
+ }
125
+ catch (error) {
126
+ if (!(error instanceof TaskExecutionLockError))
127
+ throw error;
128
+ process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: error.code, message: error.message } })}\n`);
129
+ process.exitCode = 1;
130
+ return;
131
+ }
132
+ process.stdout.write(`${JSON.stringify(humanOverride ? { ...result, humanOverride } : result)}\n`);
133
+ const overrideFailed = Boolean(humanOverride && typeof humanOverride === 'object' &&
134
+ humanOverride.status === 'failed');
135
+ if (result.status === 'failed' || overrideFailed)
83
136
  process.exitCode = 1;
84
137
  }
85
138
  export { taskEvent };
@@ -1,12 +1,15 @@
1
1
  import fs from 'node:fs';
2
2
  import { applyLedgerIntent } from '../task/ledger-intents.js';
3
+ import { consumeHumanOverride, failureId, overrideDryRunConflict } from '../task/human-override.js';
3
4
  import { isReviewStage, parseLedger, summarizeLedgerStage, validateLedgerRows } from '../task/ledger.js';
4
5
  import { resolveTaskRef } from '../task/resolve-ref.js';
6
+ import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.js';
5
7
  const USAGE = `Usage: agent-infra-internal task-ledger <task-ref> <intent> [intent flags] [--dry-run]\n\nIntents: finding-upsert, finding-respond, finding-review, decision-next-id, decision-upsert, stage-status\n`;
6
8
  const FLAGS = {
7
9
  '--stage': 'stage', '--review-artifact': 'reviewArtifact', '--ordinal': 'ordinal',
8
10
  '--severity': 'severity', '--evidence': 'evidence', '--id': 'id', '--round': 'round',
9
- '--status': 'status', '--artifact': 'artifact', '--needs-implementation': 'needsImplementation'
11
+ '--status': 'status', '--artifact': 'artifact', '--needs-implementation': 'needsImplementation',
12
+ '--override-ticket': 'overrideTicket', '--override-target': 'overrideTarget', '--override-scope': 'overrideScope'
10
13
  };
11
14
  const NUMERIC = new Set(['ordinal', 'round']);
12
15
  const BOOLEAN = new Set(['needsImplementation']);
@@ -75,7 +78,10 @@ function taskLedger(args = []) {
75
78
  'finding-review': ['needsImplementation'],
76
79
  'decision-upsert': ['needsImplementation']
77
80
  };
78
- const allowed = new Set(['kind', 'taskRef', 'dryRun', ...required[kind], ...(optional[kind] ?? [])]);
81
+ const overrideFields = kind === 'stage-status' || kind === 'decision-next-id'
82
+ ? []
83
+ : ['overrideTicket', 'overrideTarget', 'overrideScope'];
84
+ const allowed = new Set(['kind', 'taskRef', 'dryRun', ...required[kind], ...(optional[kind] ?? []), ...overrideFields]);
79
85
  const unexpected = Object.keys(values).find((key) => !allowed.has(key));
80
86
  const missing = required[kind].find((key) => values[key] === undefined);
81
87
  if (unexpected) {
@@ -86,6 +92,11 @@ function taskLedger(args = []) {
86
92
  usageFailure(`${kind} requires '${missing}'`);
87
93
  return;
88
94
  }
95
+ const dryRunConflict = overrideDryRunConflict(values);
96
+ if (dryRunConflict) {
97
+ usageFailure(dryRunConflict.message);
98
+ return;
99
+ }
89
100
  if (kind === 'stage-status') {
90
101
  if (!isReviewStage(String(values.stage))) {
91
102
  usageFailure(`stage-status requires a valid review stage`);
@@ -114,9 +125,52 @@ function taskLedger(args = []) {
114
125
  }
115
126
  return;
116
127
  }
117
- const result = applyLedgerIntent(values);
118
- process.stdout.write(`${JSON.stringify(result)}\n`);
119
- if (result.status === 'failed')
128
+ const lockResolved = resolveTaskRef(taskRef);
129
+ if (!lockResolved.ok) {
130
+ usageFailure(lockResolved.message);
131
+ return;
132
+ }
133
+ let result;
134
+ let humanOverride = null;
135
+ try {
136
+ result = withTaskExecutionLock(lockResolved.repoRoot, lockResolved.taskId, `task-ledger.${kind}`, () => {
137
+ let current = applyLedgerIntent(values);
138
+ if (current.status !== 'failed' || !values.overrideTicket)
139
+ return current;
140
+ if (!values.overrideTarget || !values.overrideScope) {
141
+ usageFailure('override ticket requires target and scope');
142
+ return current;
143
+ }
144
+ const consumed = consumeHumanOverride({
145
+ taskRef,
146
+ ticketId: String(values.overrideTicket),
147
+ failureId: failureId('ledger-intent', current.error?.code ?? 'TASK_STATE_MISMATCH'),
148
+ target: String(values.overrideTarget),
149
+ scope: String(values.overrideScope)
150
+ }, {
151
+ effectExecutor: (capability) => {
152
+ const retried = applyLedgerIntent(values, { manualOverride: capability });
153
+ current = retried;
154
+ return retried.status === 'failed'
155
+ ? { code: 'OVERRIDE_EFFECT_FAILED', message: `${retried.error?.code ?? 'LEDGER_FAILED'}: ${retried.error?.message ?? 'manual ledger effect failed'}` }
156
+ : null;
157
+ }
158
+ });
159
+ humanOverride = consumed;
160
+ return current;
161
+ });
162
+ }
163
+ catch (error) {
164
+ if (!(error instanceof TaskExecutionLockError))
165
+ throw error;
166
+ process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: error.code, message: error.message } })}\n`);
167
+ process.exitCode = 1;
168
+ return;
169
+ }
170
+ process.stdout.write(`${JSON.stringify(humanOverride ? { ...result, humanOverride } : result)}\n`);
171
+ const overrideFailed = Boolean(humanOverride && typeof humanOverride === 'object' &&
172
+ humanOverride.status === 'failed');
173
+ if (result.status === 'failed' || overrideFailed)
120
174
  process.exitCode = 1;
121
175
  }
122
176
  export { taskLedger };
@@ -1,12 +1,14 @@
1
1
  import { normalizeAgentToken, AGENT_USAGE_HINT } from '../agent-clients/tokens.js';
2
+ import { consumeHumanOverride, failureId, overrideDryRunConflict } from '../task/human-override.js';
2
3
  import { applyTaskLifecycle, lifecycleIntentCatalog } from '../task/lifecycle.js';
3
- import { resolveTaskRef } from '../task/resolve-ref.js';
4
+ import { detectRepoRoot, resolveTaskRef } from '../task/resolve-ref.js';
4
5
  import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.js';
5
- const USAGE = `Usage: agent-infra-internal task-lifecycle <N | TASK-id> <intent> --agent <agent> [intent flags] [--dry-run]\n\nIntents: ${lifecycleIntentCatalog.join(', ')}\n`;
6
+ const USAGE = `Usage: agent-infra-internal task-lifecycle <N | TASK-id> <intent> --agent <agent> [intent flags] [--dry-run]\n\nIntents: ${lifecycleIntentCatalog.join(', ')}\nOverride: --override-ticket <ticket> --override-target <target> --override-scope <scope>\n`;
6
7
  const FLAGS = {
7
8
  '--agent': 'agent', '--reason': 'reason', '--unblock-condition': 'unblockCondition',
8
9
  '--note': 'note', '--alert-number': 'alertNumber', '--staging-dir': 'stagingDir',
9
- '--issue-number': 'issueNumber'
10
+ '--issue-number': 'issueNumber', '--override-ticket': 'overrideTicket',
11
+ '--override-target': 'overrideTarget', '--override-scope': 'overrideScope'
10
12
  };
11
13
  function usageFailure(message) {
12
14
  process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: 'LIFECYCLE_PAYLOAD_INVALID', message } })}\n`);
@@ -58,15 +60,64 @@ function taskLifecycle(args = []) {
58
60
  return;
59
61
  }
60
62
  values.agent = agent;
61
- const resolved = resolveTaskRef(String(values.taskRef));
62
- if (!resolved.ok) {
63
- process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: resolved.code, message: resolved.message } })}\n`);
64
- process.exitCode = 1;
63
+ const dryRunConflict = overrideDryRunConflict(values);
64
+ if (dryRunConflict) {
65
+ usageFailure(dryRunConflict.message);
65
66
  return;
66
67
  }
68
+ let repoRoot;
69
+ let taskId;
70
+ if (values.intent === 'restore') {
71
+ // restore's precondition is that the task does NOT exist locally yet (it only lives
72
+ // in a staging dir), so resolveTaskRef's existence check is inapplicable here; identity
73
+ // is guaranteed by applyTaskLifecycle's own full-TASK-id check plus staging validation.
74
+ try {
75
+ repoRoot = detectRepoRoot();
76
+ }
77
+ catch (error) {
78
+ process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: 'REPO_ROOT_NOT_FOUND', message: error instanceof Error ? error.message : String(error) } })}\n`);
79
+ process.exitCode = 1;
80
+ return;
81
+ }
82
+ taskId = String(values.taskRef);
83
+ }
84
+ else {
85
+ const resolved = resolveTaskRef(String(values.taskRef));
86
+ if (!resolved.ok) {
87
+ process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: resolved.code, message: resolved.message } })}\n`);
88
+ process.exitCode = 1;
89
+ return;
90
+ }
91
+ repoRoot = resolved.repoRoot;
92
+ taskId = resolved.taskId;
93
+ }
67
94
  let result;
68
95
  try {
69
- result = withTaskExecutionLock(resolved.repoRoot, resolved.taskId, `task-lifecycle.${String(values.intent)}`, () => applyTaskLifecycle(values));
96
+ result = withTaskExecutionLock(repoRoot, taskId, `task-lifecycle.${String(values.intent)}`, () => {
97
+ const lifecycleResult = applyTaskLifecycle(values);
98
+ if (lifecycleResult.status !== 'failed' || !values.overrideTicket)
99
+ return lifecycleResult;
100
+ if (!values.overrideTarget || !values.overrideScope) {
101
+ return {
102
+ ...lifecycleResult,
103
+ error: { code: 'OVERRIDE_PAYLOAD_INVALID', message: 'override ticket requires --override-target and --override-scope' }
104
+ };
105
+ }
106
+ const override = consumeHumanOverride({
107
+ taskRef: String(values.taskRef),
108
+ ticketId: String(values.overrideTicket),
109
+ failureId: failureId('lifecycle.apply', lifecycleResult.error?.code ?? 'LIFECYCLE_FAILED'),
110
+ target: String(values.overrideTarget),
111
+ scope: String(values.overrideScope),
112
+ intent: String(values.intent),
113
+ ...(values.alertNumber ? { alertNumber: Number(values.alertNumber) } : {}),
114
+ ...(values.issueNumber ? { issueNumber: Number(values.issueNumber) } : {}),
115
+ ...(values.stagingDir ? { stagingDir: String(values.stagingDir) } : {})
116
+ });
117
+ if (override.status === 'failed')
118
+ return { ...lifecycleResult, humanOverride: override };
119
+ return { ...lifecycleResult, ...override, humanOverride: override, error: null };
120
+ });
70
121
  }
71
122
  catch (error) {
72
123
  if (!(error instanceof TaskExecutionLockError))