@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/lib/decide.ts CHANGED
@@ -2,6 +2,9 @@ import { VERSION } from './version.ts';
2
2
  import { parseTaskScope } from './task/command-options.ts';
3
3
  import { applyHumanDecision } from './task/decision-intents.ts';
4
4
  import { canonicalTimestamp } from './task/write.ts';
5
+ import { consumeHumanOverride, failureId } from './task/human-override.ts';
6
+ import { resolveTaskRef } from './task/resolve-ref.ts';
7
+ import { TaskExecutionLockError, withTaskExecutionLock } from './task/task-execution-lock.ts';
5
8
 
6
9
  type DecideOptions = {
7
10
  repoRoot?: string;
@@ -9,23 +12,33 @@ type DecideOptions = {
9
12
  version?: string;
10
13
  };
11
14
 
12
- function parseDecisionParts(parts: string[]): { decision: string; needsImplementation: boolean | undefined } {
15
+ function parseDecisionParts(parts: string[]): { decision: string; needsImplementation: boolean | undefined; overrideTicket?: string; overrideTarget?: string; overrideScope?: string } {
13
16
  const decision: string[] = [];
14
17
  let needsImplementation: boolean | undefined;
18
+ let overrideTicket: string | undefined;
19
+ let overrideTarget: string | undefined;
20
+ let overrideScope: string | undefined;
15
21
  for (let index = 0; index < parts.length; index += 1) {
16
- if (parts[index] !== '--needs-implementation') {
22
+ const part = parts[index];
23
+ if (part === '--needs-implementation') {
24
+ if (needsImplementation !== undefined) throw new Error("duplicate option '--needs-implementation'");
25
+ const value = parts[++index];
26
+ if (value !== 'true' && value !== 'false') {
27
+ throw new Error("--needs-implementation must be 'true' or 'false'");
28
+ }
29
+ needsImplementation = value === 'true';
30
+ } else if (part === '--override-ticket' || part === '--override-target' || part === '--override-scope') {
31
+ const value = parts[++index];
32
+ if (!value) throw new Error(`${part} requires a value`);
33
+ if (part === '--override-ticket') overrideTicket = value;
34
+ else if (part === '--override-target') overrideTarget = value;
35
+ else overrideScope = value;
36
+ } else {
17
37
  decision.push(parts[index]!);
18
- continue;
19
38
  }
20
- if (needsImplementation !== undefined) throw new Error("duplicate option '--needs-implementation'");
21
- const value = parts[++index];
22
- if (value !== 'true' && value !== 'false') {
23
- throw new Error("--needs-implementation must be 'true' or 'false'");
24
- }
25
- needsImplementation = value === 'true';
26
39
  }
27
40
  if (decision.length === 0) throw new Error('decision content is required');
28
- return { decision: decision.join(' '), needsImplementation };
41
+ return { decision: decision.join(' '), needsImplementation, overrideTicket, overrideTarget, overrideScope };
29
42
  }
30
43
 
31
44
  export async function decide(args: string[], options: DecideOptions = {}): Promise<number> {
@@ -64,17 +77,49 @@ export async function decide(args: string[], options: DecideOptions = {}): Promi
64
77
  try {
65
78
  const parsedDecision = parseDecisionParts(decisionParts);
66
79
  const now = (options.now ?? canonicalTimestamp)();
67
- const result = applyHumanDecision({
80
+ const request = {
68
81
  taskRef, selector, decision: parsedDecision.decision,
69
82
  needsImplementation: parsedDecision.needsImplementation
70
- }, {
83
+ };
84
+ const writeOptions = {
71
85
  repoRoot: options.repoRoot,
72
86
  metadataProvider: () => ({ timestamp: now, agentInfraVersion: options.version ?? VERSION })
73
- });
87
+ };
88
+ const resolved = taskRef ? resolveTaskRef(taskRef, { repoRoot: options.repoRoot }) : null;
89
+ let result;
90
+ const execute = () => {
91
+ let current = applyHumanDecision(request, writeOptions);
92
+ if (current.status !== 'failed' || !parsedDecision.overrideTicket) return current;
93
+ if (!parsedDecision.overrideTarget || !parsedDecision.overrideScope) throw new Error('override ticket requires --override-target and --override-scope');
94
+ const consumed = consumeHumanOverride({
95
+ taskRef: resolved?.ok ? resolved.taskId : String(taskRef ?? ''),
96
+ ticketId: parsedDecision.overrideTicket,
97
+ failureId: failureId('decision-intent', current.error?.code ?? 'TASK_STATE_MISMATCH'),
98
+ target: parsedDecision.overrideTarget,
99
+ scope: parsedDecision.overrideScope
100
+ }, {
101
+ ...writeOptions,
102
+ effectExecutor: (capability) => {
103
+ const retried = applyHumanDecision(request, { ...writeOptions, manualOverride: capability });
104
+ current = retried;
105
+ return retried.status === 'failed' || retried.status === 'planned'
106
+ ? { code: 'OVERRIDE_EFFECT_FAILED', message: retried.status === 'planned' ? 'producer returned planned; no decision effect was committed' : `${retried.error?.code ?? 'DECISION_FAILED'}: ${retried.error?.message ?? 'manual decision effect failed'}` }
107
+ : null;
108
+ }
109
+ });
110
+ if (consumed.status === 'failed') throw new Error(consumed.error.message);
111
+ return current;
112
+ };
113
+ result = resolved?.ok
114
+ ? withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'task-decision', execute)
115
+ : execute();
74
116
  if (result.error) throw new Error(result.error.message);
75
117
  return 0;
76
118
  } catch (error) {
77
- process.stderr.write(`Error: ${error instanceof Error ? error.message : String(error)}\n`);
119
+ const message = error instanceof TaskExecutionLockError
120
+ ? `${error.code}: ${error.message}`
121
+ : error instanceof Error ? error.message : String(error);
122
+ process.stderr.write(`Error: ${message}\n`);
78
123
  return 1;
79
124
  }
80
125
  }
package/lib/defaults.json CHANGED
@@ -56,7 +56,6 @@
56
56
  ".agents/skills/",
57
57
  ".agents/templates/",
58
58
  ".agents/workflows/",
59
- ".agents/workspace/README.md",
60
59
  ".git-hooks/check-large-files.cjs",
61
60
  ".git-hooks/check-version-format.sh",
62
61
  ".github/scripts/",
@@ -84,6 +83,15 @@
84
83
  ".git-hooks/pre-commit",
85
84
  ".gitignore"
86
85
  ],
87
- "ejected": []
86
+ "ejected": [],
87
+ "retiredManaged": [
88
+ {
89
+ "path": ".agents/workspace/README.md",
90
+ "templateHashes": [
91
+ "sha256:967b27ca4008154c5abf99a1df728242f80ed3ace9bd98eb2828bc1c76af826c",
92
+ "sha256:15aa21942cf2a1396d27834f1d3d723b61a46f8d14600b706f39f3bebc21aea3"
93
+ ]
94
+ }
95
+ ]
88
96
  }
89
97
  }
@@ -20,6 +20,10 @@ function value(runner: GitRunner, cwd: string, args: readonly string[], trim = t
20
20
  return result.status === 0 ? (trim ? result.stdout.trim() : result.stdout.replace(/\n$/, '')) : null;
21
21
  }
22
22
 
23
+ function nulPaths(output: string): string[] {
24
+ return output.split('\0').filter(Boolean);
25
+ }
26
+
23
27
  function cleanMessage(message: string): string {
24
28
  return message.replace(/(https?:\/\/)[^\s/@:]+(?::[^\s/@]*)?@/gi, '$1***@').trim();
25
29
  }
@@ -68,8 +72,27 @@ function commitExplicitPaths(input: { cwd: string; paths: readonly string[]; mes
68
72
  const selected = new Set(input.paths);
69
73
  const unrelatedStaged = before.snapshot.staged.filter((candidate) => !selected.has(candidate));
70
74
  if (unrelatedStaged.length > 0) return { status: 'failed' as const, changed: false, snapshot: before.snapshot, operations: [], error: { code: 'GIT_STAGED_SCOPE_MISMATCH', message: `Unrelated staged paths: ${unrelatedStaged.join(', ')}` } };
71
- const add = run(runner, input.cwd, ['add', '--', ...input.paths]);
72
- if (add.status !== 0) return { status: 'failed' as const, changed: false, snapshot: inspectGitWorkflow(input.cwd, runner).snapshot, operations: [{ name: 'stage', status: 'failed' as const, message: add.stderr.trim() }], error: { code: 'GIT_STAGE_FAILED', message: add.stderr.trim() } };
75
+ const stageFailure = (message: string) => ({ status: 'failed' as const, changed: false, snapshot: inspectGitWorkflow(input.cwd, runner).snapshot, operations: [{ name: 'stage', status: 'failed' as const, message }], error: { code: 'GIT_STAGE_FAILED', message } });
76
+ const indexed = run(runner, input.cwd, ['ls-files', '-z', '--', ...input.paths]);
77
+ if (indexed.status !== 0) return stageFailure(indexed.stderr.trim());
78
+ const headed = run(runner, input.cwd, ['ls-tree', '-r', '--name-only', '-z', 'HEAD', '--', ...input.paths]);
79
+ if (headed.status !== 0) return stageFailure(headed.stderr.trim());
80
+ const indexedPaths = new Set(nulPaths(indexed.stdout));
81
+ const headPaths = new Set(nulPaths(headed.stdout));
82
+ const trackedPaths: string[] = [];
83
+ const addPaths: string[] = [];
84
+ for (const candidate of input.paths) {
85
+ if (indexedPaths.has(candidate)) trackedPaths.push(candidate);
86
+ else if (!headPaths.has(candidate) || fs.existsSync(path.join(input.cwd, candidate))) addPaths.push(candidate);
87
+ }
88
+ if (trackedPaths.length > 0) {
89
+ const update = run(runner, input.cwd, ['add', '-u', '--', ...trackedPaths]);
90
+ if (update.status !== 0) return stageFailure(update.stderr.trim());
91
+ }
92
+ if (addPaths.length > 0) {
93
+ const add = run(runner, input.cwd, ['add', '--', ...addPaths]);
94
+ if (add.status !== 0) return stageFailure(add.stderr.trim());
95
+ }
73
96
  const staged = run(runner, input.cwd, ['diff', '--cached', '--quiet', '--']);
74
97
  if (staged.status === 0) return { status: 'no-op' as const, changed: false, snapshot: inspectGitWorkflow(input.cwd, runner).snapshot, operations: [{ name: 'stage', status: 'no-op' as const }], error: null };
75
98
  if (input.expectedTree) {
package/lib/init.ts CHANGED
@@ -249,7 +249,7 @@ async function cmdInit(): Promise<void> {
249
249
  agentClients: serializeAgentClients(defaultState)
250
250
  };
251
251
 
252
- if (sandboxEngine) config.sandbox.engine = sandboxEngine;
252
+ if (sandboxEngine) config.sandbox.engine = { [currentPlatform]: sandboxEngine };
253
253
  if (templateSources.length > 0) config.templates = { sources: templateSources };
254
254
  if (skillSources.length > 0) config.skills = { sources: skillSources };
255
255
 
@@ -9,7 +9,11 @@ import {
9
9
  resolveCodexThread
10
10
  } from '../agent-clients/adapters/codex-lifecycle/app-server.ts';
11
11
  import { createCodexLifecycleStore } from '../agent-clients/adapters/codex-lifecycle/store.ts';
12
+ import { createCodexCapabilityStore } from '../agent-clients/adapters/codex-lifecycle/capability-store.ts';
13
+ import { computeLifecycleBuildIdentity } from '../agent-clients/adapters/codex-lifecycle/build-identity.ts';
14
+ import { verifyCodexSandboxControllerContext } from '../agent-clients/adapters/codex-lifecycle/sandbox-controller.ts';
12
15
  import type { CodexLifecycleEvent } from '../agent-clients/adapters/codex-lifecycle/evidence.ts';
16
+ import { resolveTaskRef } from '../task/resolve-ref.ts';
13
17
  import {
14
18
  activateCodexOrchestrationDelegation,
15
19
  activateCodexSpawnDelegation,
@@ -18,7 +22,7 @@ import {
18
22
  sealCodexParentDelegation
19
23
  } from '../task/codex-orchestration.ts';
20
24
 
21
- const USAGE = 'Usage: agent-infra-internal codex-lifecycle <hook-event|resolve-start|resolve-stop|preflight|consume> [options]\n';
25
+ const USAGE = 'Usage: agent-infra-internal codex-lifecycle <capability-arm|hook-event|resolve-start|resolve-stop|preflight|consume> [options]\n';
22
26
  const MANAGED_AGENT = /^agent-infra-lifecycle-(executor|reviewer)$/;
23
27
 
24
28
  type Parsed = Readonly<{ operation: string; values: Readonly<Record<string, string>> }>;
@@ -53,6 +57,7 @@ function parse(args: string[]): Parsed | null {
53
57
  return null;
54
58
  }
55
59
  const allowed = {
60
+ 'capability-arm': ['--task-id'],
56
61
  'hook-event': ['--event', '--bridge'],
57
62
  'resolve-start': ['--child-id'],
58
63
  'resolve-stop': ['--child-id'],
@@ -92,6 +97,16 @@ function hookDefinitionHash(): string {
92
97
  return crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex');
93
98
  }
94
99
 
100
+ function controllerBinding(taskId: string) {
101
+ const contextPath = process.env.AGENT_INFRA_CODEX_CONTROLLER_CONTEXT;
102
+ if (!contextPath) return undefined;
103
+ const context = verifyCodexSandboxControllerContext(contextPath, taskId, { repoRoot: process.cwd() });
104
+ return {
105
+ instanceDigest: context.controllerInstanceDigest,
106
+ controlGeneration: context.controlGeneration
107
+ };
108
+ }
109
+
95
110
  async function readStdin(): Promise<unknown> {
96
111
  let input = '';
97
112
  process.stdin.setEncoding('utf8');
@@ -171,6 +186,28 @@ async function codexLifecycle(args: string[] = []): Promise<void> {
171
186
  return;
172
187
  }
173
188
 
189
+ if (parsed.operation === 'capability-arm') {
190
+ const taskId = parsed.values['--task-id'];
191
+ if (!taskId) throw new Error('capability-arm requires --task-id');
192
+ const resolved = resolveTaskRef(taskId, { repoRoot: process.cwd() });
193
+ if (!resolved.ok) throw new Error(`${resolved.code}: ${resolved.message}`);
194
+ const armed = createCodexCapabilityStore().arm({
195
+ taskId: resolved.taskId,
196
+ buildIdentity: computeLifecycleBuildIdentity(process.cwd()),
197
+ controller: controllerBinding(resolved.taskId)
198
+ });
199
+ output({
200
+ status: 'armed',
201
+ changed: true,
202
+ capabilityToken: armed.token,
203
+ marker: armed.marker,
204
+ expiresAt: armed.expiresAt,
205
+ buildIdentity: armed.buildIdentity,
206
+ error: null
207
+ });
208
+ return;
209
+ }
210
+
174
211
  const store = createCodexLifecycleStore({ cliVersion: cliVersion() });
175
212
  if (parsed.operation === 'hook-event') {
176
213
  const phase = parsed.values['--event'];
@@ -181,6 +218,36 @@ async function codexLifecycle(args: string[] = []): Promise<void> {
181
218
  throw new Error("hook-event --bridge must be 'true'");
182
219
  }
183
220
  const payload = await readStdin();
221
+ if (phase === 'post-tool') {
222
+ const capabilityToken = payloadText(payload, 'capabilityToken');
223
+ if (capabilityToken) {
224
+ const capabilityStore = createCodexCapabilityStore();
225
+ const armed = capabilityStore.inspect(capabilityToken);
226
+ const capability = createCodexCapabilityStore().attest({
227
+ token: capabilityToken,
228
+ sessionId: payloadText(payload, 'sessionId'),
229
+ turnId: payloadText(payload, 'turnId'),
230
+ toolUseId: payloadText(payload, 'toolUseId'),
231
+ hookDefinitionHash: payloadText(payload, 'hookDefinitionHash'),
232
+ buildIdentity: computeLifecycleBuildIdentity(process.cwd()),
233
+ controller: controllerBinding(armed.taskId)
234
+ });
235
+ output({
236
+ status: capability.status,
237
+ changed: true,
238
+ evidence: {
239
+ revision: capability.revision,
240
+ sessionId: capability.sessionId,
241
+ turnId: capability.turnId,
242
+ toolUseId: capability.toolUseId,
243
+ expiresAt: capability.expiresAt
244
+ },
245
+ diagnostics: [],
246
+ error: null
247
+ });
248
+ return;
249
+ }
250
+ }
184
251
  if (phase === 'post-tool' && parsed.values['--bridge'] === 'true') {
185
252
  const toolName = payloadText(payload, 'toolName');
186
253
  if (toolName === 'collaborationspawn_agent') {
@@ -0,0 +1,109 @@
1
+ import {
2
+ runCodexSandboxController,
3
+ verifyCodexSandboxControllerContext
4
+ } from '../agent-clients/adapters/codex-lifecycle/sandbox-controller.ts';
5
+
6
+ const USAGE = 'Usage: agent-infra-internal codex-sandbox-controller <run|verify-context> [options]\n';
7
+
8
+ function fail(code: string, message: string, exitCode = 1): void {
9
+ process.stdout.write(`${JSON.stringify({
10
+ status: 'failed',
11
+ changed: false,
12
+ error: { code, message }
13
+ })}\n`);
14
+ process.exitCode = exitCode;
15
+ }
16
+
17
+ function parse(args: string[]): Readonly<{ operation: string; values: Readonly<Record<string, string>> }> | null {
18
+ if (args[0] === '--help' || args[0] === '-h') {
19
+ process.stdout.write(USAGE);
20
+ return null;
21
+ }
22
+ const operation = args[0];
23
+ if (!operation || !['run', 'verify-context'].includes(operation)) {
24
+ process.stderr.write(USAGE);
25
+ fail('CODEX_SANDBOX_CONTROLLER_PAYLOAD_INVALID', 'operation must be run or verify-context', 2);
26
+ return null;
27
+ }
28
+ const allowed = new Set([
29
+ '--task-id',
30
+ '--task-ref',
31
+ '--context',
32
+ '--executor-model',
33
+ '--executor-reasoning-effort',
34
+ '--reviewer-model',
35
+ '--reviewer-reasoning-effort'
36
+ ]);
37
+ const values: Record<string, string> = {};
38
+ for (let index = 1; index < args.length; index += 1) {
39
+ const flag = args[index]!;
40
+ const value = args[++index];
41
+ if (!allowed.has(flag) || Object.hasOwn(values, flag) || !value || value.startsWith('--')) {
42
+ fail('CODEX_SANDBOX_CONTROLLER_PAYLOAD_INVALID', `invalid option '${flag}'`, 2);
43
+ return null;
44
+ }
45
+ values[flag] = value;
46
+ }
47
+ return { operation, values };
48
+ }
49
+
50
+ async function codexSandboxController(args: string[] = []): Promise<void> {
51
+ const parsed = parse(args);
52
+ if (!parsed || process.exitCode) return;
53
+ const taskId = parsed.values['--task-id'];
54
+ if (!taskId) {
55
+ fail('CODEX_SANDBOX_CONTROLLER_PAYLOAD_INVALID', '--task-id is required', 2);
56
+ return;
57
+ }
58
+ try {
59
+ if (parsed.operation === 'verify-context') {
60
+ const contextPath = parsed.values['--context']
61
+ ?? process.env.AGENT_INFRA_CODEX_CONTROLLER_CONTEXT;
62
+ if (!contextPath) throw new Error('CODEX_SANDBOX_CONTROLLER_CONTEXT_MISSING');
63
+ const context = verifyCodexSandboxControllerContext(contextPath, taskId);
64
+ process.stdout.write(`${JSON.stringify({
65
+ status: 'ready',
66
+ changed: false,
67
+ context: {
68
+ taskId: context.taskId,
69
+ controlGeneration: context.controlGeneration,
70
+ controllerInstanceDigest: context.controllerInstanceDigest,
71
+ expiresAt: context.expiresAt,
72
+ buildIdentity: context.buildIdentity,
73
+ hookDefinitionHash: context.hookDefinitionHash,
74
+ lifecycleProfilesHash: context.lifecycleProfilesHash
75
+ },
76
+ error: null
77
+ })}\n`);
78
+ return;
79
+ }
80
+ const taskRef = parsed.values['--task-ref'];
81
+ if (!taskRef) throw new Error('CODEX_SANDBOX_CONTROLLER_TASK_REF_REQUIRED');
82
+ const policy = [
83
+ '--executor-model',
84
+ '--executor-reasoning-effort',
85
+ '--reviewer-model',
86
+ '--reviewer-reasoning-effort'
87
+ ];
88
+ if (policy.some((flag) => parsed.values[flag] !== undefined)
89
+ && policy.some((flag) => parsed.values[flag] === undefined)) {
90
+ throw new Error('CODEX_SANDBOX_CONTROLLER_MODEL_POLICY_INVALID');
91
+ }
92
+ const exitCode = await runCodexSandboxController({
93
+ taskId,
94
+ taskRef,
95
+ executorModel: parsed.values['--executor-model'],
96
+ executorReasoningEffort: parsed.values['--executor-reasoning-effort'],
97
+ reviewerModel: parsed.values['--reviewer-model'],
98
+ reviewerReasoningEffort: parsed.values['--reviewer-reasoning-effort']
99
+ });
100
+ process.exitCode = exitCode;
101
+ } catch (error) {
102
+ const message = error instanceof Error ? error.message : String(error);
103
+ const code = /^([A-Z][A-Z0-9_]+)/u.exec(message)?.[1]
104
+ ?? 'CODEX_SANDBOX_CONTROLLER_FAILED';
105
+ fail(code, message);
106
+ }
107
+ }
108
+
109
+ export { codexSandboxController };
@@ -36,9 +36,7 @@ const DEMO_INPUT_PATHS = [
36
36
  'lib/paths.ts',
37
37
  'lib/render.ts',
38
38
  'lib/builtin-tuis.ts',
39
- 'lib/sandbox/engines/',
40
- 'src/sync-templates.js',
41
- 'templates/'
39
+ 'lib/sandbox/engines/'
42
40
  ] as const;
43
41
  const DEMO_DIGEST_PATH = 'assets/demo-init.inputs.sha256';
44
42
  const DEMO_OUTPUT_PATH = 'assets/demo-init.gif';
@@ -6,6 +6,9 @@ import type {
6
6
  PrReviewActivityIntent,
7
7
  PrReviewInspectIntent
8
8
  } from '../task/activity-intent.ts';
9
+ import { consumeHumanOverride, failureId, overrideDryRunConflict } from '../task/human-override.ts';
10
+ import { resolveTaskRef } from '../task/resolve-ref.ts';
11
+ import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.ts';
9
12
 
10
13
  const USAGE = `Usage: agent-infra-internal task-activity <task-ref> pr-review-inspect
11
14
  agent-infra-internal task-activity <task-ref> pr-review-start --agent <agent> --artifact <canonical.md> --head <40hex> [--dry-run]
@@ -24,13 +27,14 @@ const FLAGS: Record<string, string> = {
24
27
  '--minor': 'minor',
25
28
  '--outcome': 'outcome',
26
29
  '--reason': 'reason'
30
+ , '--override-ticket': 'overrideTicket', '--override-target': 'overrideTarget', '--override-scope': 'overrideScope'
27
31
  };
28
32
  const COMMON = ['agent', 'artifact', 'head'] as const;
29
33
  const ALLOWED: Record<string, ReadonlySet<string>> = {
30
34
  'pr-review-inspect': new Set(['kind', 'taskRef']),
31
- 'pr-review-start': new Set(['kind', 'taskRef', 'agent', 'artifact', 'head', 'dryRun']),
32
- 'pr-review-complete': new Set(['kind', 'taskRef', 'agent', 'artifact', 'head', 'verdict', 'blockers', 'major', 'minor', 'dryRun']),
33
- 'pr-review-terminate': new Set(['kind', 'taskRef', 'agent', 'artifact', 'head', 'outcome', 'reason', 'dryRun'])
35
+ 'pr-review-start': new Set(['kind', 'taskRef', 'agent', 'artifact', 'head', 'dryRun', 'overrideTicket', 'overrideTarget', 'overrideScope']),
36
+ 'pr-review-complete': new Set(['kind', 'taskRef', 'agent', 'artifact', 'head', 'verdict', 'blockers', 'major', 'minor', 'dryRun', 'overrideTicket', 'overrideTarget', 'overrideScope']),
37
+ 'pr-review-terminate': new Set(['kind', 'taskRef', 'agent', 'artifact', 'head', 'outcome', 'reason', 'dryRun', 'overrideTicket', 'overrideTarget', 'overrideScope'])
34
38
  };
35
39
 
36
40
  function usageFailure(message: string): void {
@@ -83,9 +87,45 @@ function taskActivity(args: string[] = []): void {
83
87
  for (const key of operationRequired) {
84
88
  if (values[key] === undefined) { usageFailure(`${kind} requires '${key}'`); return; }
85
89
  }
86
- const result = applyPrReviewActivityIntent(values as PrReviewActivityIntent);
87
- process.stdout.write(`${JSON.stringify(result)}\n`);
88
- if (result.status === 'failed') process.exitCode = 1;
90
+ const dryRunConflict = overrideDryRunConflict(values);
91
+ if (dryRunConflict) { usageFailure(dryRunConflict.message); return; }
92
+ const resolved = resolveTaskRef(taskRef);
93
+ if (!resolved.ok) { usageFailure(resolved.message); return; }
94
+ let result;
95
+ let humanOverride: unknown = null;
96
+ try {
97
+ result = withTaskExecutionLock(resolved.repoRoot, resolved.taskId, `task-activity.${kind}`, () => {
98
+ let current = applyPrReviewActivityIntent(values as PrReviewActivityIntent, { lockAlreadyHeld: true });
99
+ if (current.status !== 'failed' || !values.overrideTicket) return current;
100
+ if (!values.overrideTarget || !values.overrideScope) { usageFailure('override ticket requires target and scope'); return current; }
101
+ const consumed = consumeHumanOverride({
102
+ taskRef, ticketId: String(values.overrideTicket),
103
+ failureId: failureId('activity-intent', current.error?.code ?? 'TASK_STATE_MISMATCH'),
104
+ target: String(values.overrideTarget), scope: String(values.overrideScope)
105
+ }, {
106
+ effectExecutor: (capability) => {
107
+ const retried = applyPrReviewActivityIntent(values as PrReviewActivityIntent, { lockAlreadyHeld: true, manualOverride: capability });
108
+ current = retried;
109
+ return retried.status === 'failed' || retried.status === 'planned'
110
+ ? { 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'}` }
111
+ : null;
112
+ }
113
+ });
114
+ humanOverride = consumed;
115
+ return current;
116
+ });
117
+ } catch (error) {
118
+ if (!(error instanceof TaskExecutionLockError)) throw error;
119
+ process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: error.code, message: error.message } })}\n`);
120
+ process.exitCode = 1;
121
+ return;
122
+ }
123
+ process.stdout.write(`${JSON.stringify(humanOverride ? { ...result, humanOverride } : result)}\n`);
124
+ const overrideFailed = Boolean(
125
+ humanOverride && typeof humanOverride === 'object' &&
126
+ (humanOverride as { status?: unknown }).status === 'failed'
127
+ );
128
+ if (result.status === 'failed' || overrideFailed) process.exitCode = 1;
89
129
  }
90
130
 
91
131
  export { taskActivity };
@@ -1,6 +1,9 @@
1
1
  import { normalizeAgentToken, AGENT_USAGE_HINT } from '../agent-clients/tokens.ts';
2
2
  import { applyTaskEvent, eventCatalog } from '../task/events.ts';
3
3
  import type { TaskEventRequest, Verdict } from '../task/events.ts';
4
+ import { consumeHumanOverride, failureId, overrideDryRunConflict } from '../task/human-override.ts';
5
+ import { resolveTaskRef } from '../task/resolve-ref.ts';
6
+ import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.ts';
4
7
 
5
8
  const USAGE = `Usage: agent-infra-internal task-event <N | TASK-id> <event> --agent <agent> [event options] [--orchestrated] [--dry-run]
6
9
 
@@ -13,7 +16,8 @@ const FLAGS: Record<string, keyof TaskEventRequest> = {
13
16
  '--fix-for': 'fixFor', '--implementation-input': 'implementationInput',
14
17
  '--verdict': 'verdict', '--blockers': 'blockers', '--major': 'major',
15
18
  '--minor': 'minor', '--manual-validation': 'manualValidation', '--files-modified': 'filesModified',
16
- '--tests-passed': 'testsPassed', '--summary-result': 'summaryResult'
19
+ '--tests-passed': 'testsPassed', '--summary-result': 'summaryResult',
20
+ '--override-ticket': 'overrideTicket', '--override-target': 'overrideTarget', '--override-scope': 'overrideScope'
17
21
  };
18
22
  const NUMERIC = new Set(['round', 'question', 'blockers', 'major', 'minor', 'manualValidation', 'filesModified', 'testsPassed']);
19
23
 
@@ -51,9 +55,54 @@ function taskEvent(args: string[] = []): void {
51
55
  const agent = normalizeAgentToken(String(request.agent ?? ''));
52
56
  if (!agent) { usageFailure(`invalid --agent '${request.agent}': ${AGENT_USAGE_HINT}`); return; }
53
57
  request.agent = agent;
54
- const result = applyTaskEvent(request);
55
- process.stdout.write(`${JSON.stringify(result)}\n`);
56
- if (result.status === 'failed') process.exitCode = 1;
58
+ const dryRunConflict = overrideDryRunConflict(request as unknown as Record<string, unknown>);
59
+ if (dryRunConflict) { usageFailure(dryRunConflict.message); return; }
60
+ const resolved = resolveTaskRef(request.taskRef);
61
+ if (!resolved.ok) {
62
+ process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: resolved.code, message: resolved.message } })}\n`);
63
+ process.exitCode = 1;
64
+ return;
65
+ }
66
+ let result;
67
+ let humanOverride: unknown = null;
68
+ try {
69
+ result = withTaskExecutionLock(resolved.repoRoot, resolved.taskId, `task-event.${request.event}`, () => {
70
+ let current = applyTaskEvent(request, { lockAlreadyHeld: true });
71
+ const values = request as Record<string, unknown>;
72
+ if (current.status !== 'failed' || !values.overrideTicket) return current;
73
+ if (!values.overrideTarget || !values.overrideScope) {
74
+ return { ...current, humanOverride: { status: 'failed', error: { code: 'OVERRIDE_PAYLOAD_INVALID', message: 'override ticket requires target and scope' } } } as typeof current & { humanOverride: unknown };
75
+ }
76
+ const consumed = consumeHumanOverride({
77
+ taskRef: request.taskRef,
78
+ ticketId: String(values.overrideTicket),
79
+ failureId: failureId('task-event', current.error?.code ?? 'EVENT_TRANSITION_INVALID'),
80
+ target: String(values.overrideTarget),
81
+ scope: String(values.overrideScope)
82
+ }, {
83
+ effectExecutor: (capability) => {
84
+ const retried = applyTaskEvent(request, { lockAlreadyHeld: true, manualOverride: capability });
85
+ current = retried;
86
+ return retried.status === 'failed' || retried.status === 'planned'
87
+ ? { 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'}` }
88
+ : null;
89
+ }
90
+ });
91
+ humanOverride = consumed;
92
+ return current;
93
+ });
94
+ } catch (error) {
95
+ if (!(error instanceof TaskExecutionLockError)) throw error;
96
+ process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: error.code, message: error.message } })}\n`);
97
+ process.exitCode = 1;
98
+ return;
99
+ }
100
+ process.stdout.write(`${JSON.stringify(humanOverride ? { ...result, humanOverride } : result)}\n`);
101
+ const overrideFailed = Boolean(
102
+ humanOverride && typeof humanOverride === 'object' &&
103
+ (humanOverride as { status?: unknown }).status === 'failed'
104
+ );
105
+ if (result.status === 'failed' || overrideFailed) process.exitCode = 1;
57
106
  }
58
107
 
59
108
  export { taskEvent };
@@ -2,14 +2,17 @@ import fs from 'node:fs';
2
2
 
3
3
  import { applyLedgerIntent } from '../task/ledger-intents.ts';
4
4
  import type { LedgerIntent } from '../task/ledger-intents.ts';
5
+ import { consumeHumanOverride, failureId, overrideDryRunConflict } from '../task/human-override.ts';
5
6
  import { isReviewStage, parseLedger, summarizeLedgerStage, validateLedgerRows } from '../task/ledger.ts';
6
7
  import { resolveTaskRef } from '../task/resolve-ref.ts';
8
+ import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.ts';
7
9
 
8
10
  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`;
9
11
  const FLAGS: Record<string, string> = {
10
12
  '--stage': 'stage', '--review-artifact': 'reviewArtifact', '--ordinal': 'ordinal',
11
13
  '--severity': 'severity', '--evidence': 'evidence', '--id': 'id', '--round': 'round',
12
14
  '--status': 'status', '--artifact': 'artifact', '--needs-implementation': 'needsImplementation'
15
+ , '--override-ticket': 'overrideTicket', '--override-target': 'overrideTarget', '--override-scope': 'overrideScope'
13
16
  };
14
17
  const NUMERIC = new Set(['ordinal', 'round']);
15
18
  const BOOLEAN = new Set(['needsImplementation']);
@@ -58,11 +61,16 @@ function taskLedger(args: string[] = []): void {
58
61
  'finding-review': ['needsImplementation'],
59
62
  'decision-upsert': ['needsImplementation']
60
63
  };
61
- const allowed = new Set(['kind', 'taskRef', 'dryRun', ...required[kind]!, ...(optional[kind] ?? [])]);
64
+ const overrideFields = kind === 'stage-status' || kind === 'decision-next-id'
65
+ ? []
66
+ : ['overrideTicket', 'overrideTarget', 'overrideScope'];
67
+ const allowed = new Set(['kind', 'taskRef', 'dryRun', ...required[kind]!, ...(optional[kind] ?? []), ...overrideFields]);
62
68
  const unexpected = Object.keys(values).find((key) => !allowed.has(key));
63
69
  const missing = required[kind]!.find((key) => values[key] === undefined);
64
70
  if (unexpected) { usageFailure(`${kind} does not accept '${unexpected}'`); return; }
65
71
  if (missing) { usageFailure(`${kind} requires '${missing}'`); return; }
72
+ const dryRunConflict = overrideDryRunConflict(values);
73
+ if (dryRunConflict) { usageFailure(dryRunConflict.message); return; }
66
74
  if (kind === 'stage-status') {
67
75
  if (!isReviewStage(String(values.stage))) { usageFailure(`stage-status requires a valid review stage`); return; }
68
76
  const resolved = resolveTaskRef(taskRef);
@@ -87,9 +95,45 @@ function taskLedger(args: string[] = []): void {
87
95
  }
88
96
  return;
89
97
  }
90
- const result = applyLedgerIntent(values as LedgerIntent);
91
- process.stdout.write(`${JSON.stringify(result)}\n`);
92
- if (result.status === 'failed') process.exitCode = 1;
98
+ const lockResolved = resolveTaskRef(taskRef);
99
+ if (!lockResolved.ok) { usageFailure(lockResolved.message); return; }
100
+ let result;
101
+ let humanOverride: unknown = null;
102
+ try {
103
+ result = withTaskExecutionLock(lockResolved.repoRoot, lockResolved.taskId, `task-ledger.${kind}`, () => {
104
+ let current = applyLedgerIntent(values as LedgerIntent);
105
+ if (current.status !== 'failed' || !values.overrideTicket) return current;
106
+ if (!values.overrideTarget || !values.overrideScope) { usageFailure('override ticket requires target and scope'); return current; }
107
+ const consumed = consumeHumanOverride({
108
+ taskRef,
109
+ ticketId: String(values.overrideTicket),
110
+ failureId: failureId('ledger-intent', current.error?.code ?? 'TASK_STATE_MISMATCH'),
111
+ target: String(values.overrideTarget),
112
+ scope: String(values.overrideScope)
113
+ }, {
114
+ effectExecutor: (capability) => {
115
+ const retried = applyLedgerIntent(values as LedgerIntent, { manualOverride: capability });
116
+ current = retried;
117
+ return retried.status === 'failed'
118
+ ? { code: 'OVERRIDE_EFFECT_FAILED', message: `${retried.error?.code ?? 'LEDGER_FAILED'}: ${retried.error?.message ?? 'manual ledger effect failed'}` }
119
+ : null;
120
+ }
121
+ });
122
+ humanOverride = consumed;
123
+ return current;
124
+ });
125
+ } catch (error) {
126
+ if (!(error instanceof TaskExecutionLockError)) throw error;
127
+ process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: error.code, message: error.message } })}\n`);
128
+ process.exitCode = 1;
129
+ return;
130
+ }
131
+ process.stdout.write(`${JSON.stringify(humanOverride ? { ...result, humanOverride } : result)}\n`);
132
+ const overrideFailed = Boolean(
133
+ humanOverride && typeof humanOverride === 'object' &&
134
+ (humanOverride as { status?: unknown }).status === 'failed'
135
+ );
136
+ if (result.status === 'failed' || overrideFailed) process.exitCode = 1;
93
137
  }
94
138
 
95
139
  export { taskLedger };