@fitlab-ai/agent-infra 0.9.7 → 0.9.9

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 (195) hide show
  1. package/README.md +1 -1
  2. package/README.zh-CN.md +1 -1
  3. package/dist/bin/internal-cli.js +11 -1
  4. package/dist/lib/agent-clients/adapters/claude-code.js +3 -3
  5. package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +60 -22
  6. package/dist/lib/agent-clients/adapters/codex-lifecycle/build-identity.js +8 -1
  7. package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +175 -11
  8. package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-context.js +147 -0
  9. package/dist/lib/agent-clients/adapters/codex-lifecycle/evidence.js +14 -0
  10. package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +86 -69
  11. package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +2 -1
  12. package/dist/lib/decide.js +68 -14
  13. package/dist/lib/git/workflow.js +10 -1
  14. package/dist/lib/init.js +1 -1
  15. package/dist/lib/internal/codex-lifecycle.js +19 -5
  16. package/dist/lib/internal/codex-sandbox-controller.js +6 -13
  17. package/dist/lib/internal/git-workflow.js +5 -7
  18. package/dist/lib/internal/platform-pr.js +20 -7
  19. package/dist/lib/internal/release-workflow.js +3 -4
  20. package/dist/lib/internal/sandbox-control.js +79 -2
  21. package/dist/lib/internal/task-activity.js +57 -7
  22. package/dist/lib/internal/task-event.js +57 -4
  23. package/dist/lib/internal/task-finalization.js +54 -0
  24. package/dist/lib/internal/task-ledger.js +59 -5
  25. package/dist/lib/internal/task-lifecycle.js +59 -8
  26. package/dist/lib/internal/task-orchestration.js +161 -288
  27. package/dist/lib/internal/task-override.js +131 -0
  28. package/dist/lib/internal/task-review.js +64 -6
  29. package/dist/lib/internal/task-verify.js +65 -3
  30. package/dist/lib/internal/task-warning.js +57 -4
  31. package/dist/lib/platform/pr-summary.js +53 -13
  32. package/dist/lib/platform/pull-requests.js +205 -41
  33. package/dist/lib/runtime/agent-runtime.js +56 -0
  34. package/dist/lib/sandbox/commands/create.js +389 -311
  35. package/dist/lib/sandbox/commands/rm.js +316 -55
  36. package/dist/lib/sandbox/control/client.js +190 -6
  37. package/dist/lib/sandbox/control/container-identity.js +1 -1
  38. package/dist/lib/sandbox/control/controller-registration.js +273 -0
  39. package/dist/lib/sandbox/control/executor.js +145 -5
  40. package/dist/lib/sandbox/control/lifecycle.js +395 -73
  41. package/dist/lib/sandbox/control/protocol.js +67 -6
  42. package/dist/lib/sandbox/control/state.js +27 -11
  43. package/dist/lib/sandbox/engine.js +14 -7
  44. package/dist/lib/sandbox/index.js +5 -3
  45. package/dist/lib/sandbox/managed-fs.js +5 -0
  46. package/dist/lib/sandbox/mounts.js +20 -5
  47. package/dist/lib/sandbox/recovery.js +78 -10
  48. package/dist/lib/sandbox/task-resolver.js +50 -6
  49. package/dist/lib/sandbox/workspace-identity.js +27 -0
  50. package/dist/lib/sandbox/workspace-view.js +62 -40
  51. package/dist/lib/sandbox/worktree-safety.js +215 -2
  52. package/dist/lib/server/process-state.js +51 -1
  53. package/dist/lib/task/activity-intent.js +4 -1
  54. package/dist/lib/task/codex-orchestration.js +158 -33
  55. package/dist/lib/task/commands/status.js +59 -1
  56. package/dist/lib/task/commit-operation.js +335 -0
  57. package/dist/lib/task/commit-policy.js +18 -0
  58. package/dist/lib/task/decision-intents.js +4 -2
  59. package/dist/lib/task/delegation-receipts.js +279 -17
  60. package/dist/lib/task/events.js +9 -5
  61. package/dist/lib/task/finalization.js +567 -0
  62. package/dist/lib/task/guard-override.js +31 -0
  63. package/dist/lib/task/human-override.js +1240 -0
  64. package/dist/lib/task/ledger-intents.js +6 -3
  65. package/dist/lib/task/lifecycle.js +69 -14
  66. package/dist/lib/task/operation-outcome.js +33 -0
  67. package/dist/lib/task/orchestration.js +199 -719
  68. package/dist/lib/task/review-finalization.js +3 -2
  69. package/dist/lib/task/short-id.js +59 -1
  70. package/dist/lib/task/task-execution-lock.js +6 -1
  71. package/dist/lib/task/verification-engine.js +138 -59
  72. package/dist/lib/task/verification.js +15 -2
  73. package/dist/lib/task/workflow-warning-intents.js +54 -3
  74. package/dist/lib/task/write.js +2 -1
  75. package/dist/lifecycle-build-manifest.json +3 -3
  76. package/lib/agent-clients/adapters/claude-code.ts +3 -3
  77. package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +63 -20
  78. package/lib/agent-clients/adapters/codex-lifecycle/build-identity.ts +12 -0
  79. package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +278 -13
  80. package/lib/agent-clients/adapters/codex-lifecycle/controller-context.ts +180 -0
  81. package/lib/agent-clients/adapters/codex-lifecycle/evidence.ts +19 -0
  82. package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +9 -1
  83. package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +112 -93
  84. package/lib/agent-clients/adapters/codex-lifecycle/store.ts +2 -1
  85. package/lib/decide.ts +59 -14
  86. package/lib/git/workflow.ts +11 -2
  87. package/lib/init.ts +1 -1
  88. package/lib/internal/codex-lifecycle.ts +18 -5
  89. package/lib/internal/codex-sandbox-controller.ts +6 -12
  90. package/lib/internal/git-workflow.ts +5 -6
  91. package/lib/internal/platform-pr.ts +14 -7
  92. package/lib/internal/release-workflow.ts +3 -4
  93. package/lib/internal/sandbox-control.ts +92 -2
  94. package/lib/internal/task-activity.ts +46 -6
  95. package/lib/internal/task-event.ts +53 -4
  96. package/lib/internal/task-finalization.ts +68 -0
  97. package/lib/internal/task-ledger.ts +48 -4
  98. package/lib/internal/task-lifecycle.ts +56 -12
  99. package/lib/internal/task-orchestration.ts +32 -107
  100. package/lib/internal/task-override.ts +138 -0
  101. package/lib/internal/task-review.ts +51 -6
  102. package/lib/internal/task-verify.ts +52 -3
  103. package/lib/internal/task-warning.ts +44 -3
  104. package/lib/platform/pr-summary.ts +65 -14
  105. package/lib/platform/pull-requests.ts +211 -41
  106. package/lib/runtime/agent-runtime.ts +76 -0
  107. package/lib/sandbox/commands/create.ts +88 -4
  108. package/lib/sandbox/commands/rm.ts +413 -61
  109. package/lib/sandbox/config.ts +2 -1
  110. package/lib/sandbox/control/client.ts +255 -6
  111. package/lib/sandbox/control/container-identity.ts +3 -3
  112. package/lib/sandbox/control/controller-registration.ts +347 -0
  113. package/lib/sandbox/control/executor.ts +172 -6
  114. package/lib/sandbox/control/lifecycle.ts +465 -77
  115. package/lib/sandbox/control/protocol.ts +85 -9
  116. package/lib/sandbox/control/state.ts +35 -18
  117. package/lib/sandbox/engine.ts +20 -9
  118. package/lib/sandbox/index.ts +5 -3
  119. package/lib/sandbox/managed-fs.ts +5 -0
  120. package/lib/sandbox/mounts.ts +23 -5
  121. package/lib/sandbox/recovery.ts +90 -9
  122. package/lib/sandbox/task-resolver.ts +58 -7
  123. package/lib/sandbox/workspace-identity.ts +50 -2
  124. package/lib/sandbox/workspace-view.ts +73 -43
  125. package/lib/sandbox/worktree-safety.ts +236 -2
  126. package/lib/server/process-state.ts +48 -1
  127. package/lib/task/activity-intent.ts +4 -2
  128. package/lib/task/codex-orchestration.ts +167 -33
  129. package/lib/task/commands/status.ts +78 -1
  130. package/lib/task/commit-operation.ts +430 -0
  131. package/lib/task/commit-policy.ts +30 -0
  132. package/lib/task/decision-intents.ts +4 -2
  133. package/lib/task/delegation-receipts.ts +304 -19
  134. package/lib/task/events.ts +10 -5
  135. package/lib/task/finalization.ts +719 -0
  136. package/lib/task/guard-override.ts +50 -0
  137. package/lib/task/human-override.ts +1503 -0
  138. package/lib/task/ledger-intents.ts +6 -3
  139. package/lib/task/lifecycle.ts +71 -14
  140. package/lib/task/operation-outcome.ts +48 -0
  141. package/lib/task/orchestration.ts +239 -922
  142. package/lib/task/review-finalization.ts +6 -2
  143. package/lib/task/short-id.ts +65 -1
  144. package/lib/task/task-execution-lock.ts +10 -1
  145. package/lib/task/verification-engine.ts +145 -64
  146. package/lib/task/verification.ts +18 -3
  147. package/lib/task/workflow-warning-intents.ts +61 -4
  148. package/lib/task/write.ts +4 -1
  149. package/package.json +2 -2
  150. package/templates/.agents/QUICKSTART.en.md +2 -2
  151. package/templates/.agents/QUICKSTART.zh-CN.md +2 -2
  152. package/templates/.agents/hooks/lifecycle-delegation.js +9 -24
  153. package/templates/.agents/rules/lifecycle-orchestration.en.md +4 -3
  154. package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +4 -3
  155. package/templates/.agents/rules/task-management.en.md +2 -2
  156. package/templates/.agents/rules/task-management.zh-CN.md +2 -2
  157. package/templates/.agents/skills/block-task/SKILL.en.md +2 -4
  158. package/templates/.agents/skills/block-task/SKILL.zh-CN.md +2 -4
  159. package/templates/.agents/skills/cancel-task/SKILL.en.md +3 -3
  160. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +3 -3
  161. package/templates/.agents/skills/close-codescan/SKILL.en.md +6 -2
  162. package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +6 -2
  163. package/templates/.agents/skills/close-dependabot/SKILL.en.md +6 -2
  164. package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +6 -2
  165. package/templates/.agents/skills/commit/SKILL.en.md +41 -125
  166. package/templates/.agents/skills/commit/SKILL.zh-CN.md +52 -123
  167. package/templates/.agents/skills/commit/reference/commit-message.en.md +1 -1
  168. package/templates/.agents/skills/commit/reference/commit-message.zh-CN.md +1 -1
  169. package/templates/.agents/skills/commit/reference/commit-orchestration.en.md +14 -43
  170. package/templates/.agents/skills/commit/reference/commit-orchestration.zh-CN.md +14 -49
  171. package/templates/.agents/skills/commit/reference/pr-summary-sync.en.md +1 -1
  172. package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +1 -1
  173. package/templates/.agents/skills/commit/reference/task-status-update.en.md +2 -35
  174. package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +2 -35
  175. package/templates/.agents/skills/complete-task/SKILL.en.md +31 -38
  176. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +31 -38
  177. package/templates/.agents/skills/complete-task/config/verify.en.json +1 -0
  178. package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +1 -0
  179. package/templates/.agents/skills/complete-task/reference/external-delivery.en.md +1 -1
  180. package/templates/.agents/skills/complete-task/reference/external-delivery.zh-CN.md +1 -1
  181. package/templates/.agents/skills/create-pr/SKILL.en.md +6 -4
  182. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +6 -4
  183. package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +2 -2
  184. package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +2 -2
  185. package/templates/.agents/skills/run-task/SKILL.en.md +5 -5
  186. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +5 -5
  187. package/templates/.agents/skills/run-task/reference/host-validation.en.md +3 -1
  188. package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +3 -1
  189. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +1 -1
  190. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +1 -1
  191. package/templates/.codex/hooks.json +4 -4
  192. package/dist/lib/task/commit-finalization.js +0 -305
  193. package/dist/lib/task/commit-intent.js +0 -165
  194. package/lib/task/commit-finalization.ts +0 -419
  195. package/lib/task/commit-intent.ts +0 -230
@@ -1,6 +1,9 @@
1
1
  import { finalizeReviewSummary } from '../task/review-finalization.ts';
2
+ import { consumeHumanOverride, failureId, overrideDryRunConflict } from '../task/human-override.ts';
3
+ import { resolveTaskRef } from '../task/resolve-ref.ts';
4
+ import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.ts';
2
5
 
3
- const USAGE = 'Usage: agent-infra-internal task-review <task-ref> finalize-summary --stage <analysis|plan|code> --artifact <review-*.md> [--orchestrated] [--dry-run]\n';
6
+ const USAGE = 'Usage: agent-infra-internal task-review <task-ref> finalize-summary --stage <analysis|plan|code> --artifact <review-*.md> [--orchestrated] [--dry-run] [--override-ticket <ticket> --override-target <target> --override-scope <scope>]\n';
4
7
 
5
8
  function failUsage(message: string): void {
6
9
  process.stdout.write(`${JSON.stringify({
@@ -30,10 +33,13 @@ function taskReview(args: string[] = []): void {
30
33
  let artifact = '';
31
34
  let dryRun = false;
32
35
  let orchestrated = false;
36
+ let overrideTicket = '';
37
+ let overrideTarget = '';
38
+ let overrideScope = '';
33
39
  const seen = new Set<string>();
34
40
  for (let index = 2; index < args.length; index += 1) {
35
41
  const flag = args[index]!;
36
- if (!['--stage', '--artifact', '--orchestrated', '--dry-run'].includes(flag)) {
42
+ if (!['--stage', '--artifact', '--orchestrated', '--dry-run', '--override-ticket', '--override-target', '--override-scope'].includes(flag)) {
37
43
  failUsage(`unknown option '${flag}'`);
38
44
  return;
39
45
  }
@@ -56,15 +62,54 @@ function taskReview(args: string[] = []): void {
56
62
  return;
57
63
  }
58
64
  if (flag === '--stage') stage = value;
59
- else artifact = value;
65
+ else if (flag === '--artifact') artifact = value;
66
+ else if (flag === '--override-ticket') overrideTicket = value;
67
+ else if (flag === '--override-target') overrideTarget = value;
68
+ else if (flag === '--override-scope') overrideScope = value;
60
69
  }
61
70
  if (!stage || !artifact) {
62
71
  failUsage("options '--stage' and '--artifact' are required");
63
72
  return;
64
73
  }
65
- const result = finalizeReviewSummary({ taskRef: args[0]!, stage, artifact, orchestrated, dryRun });
66
- process.stdout.write(`${JSON.stringify(result)}\n`);
67
- if (result.status === 'failed') process.exitCode = 1;
74
+ const dryRunConflict = overrideDryRunConflict({ dryRun, overrideTicket, overrideTarget, overrideScope });
75
+ if (dryRunConflict) { failUsage(dryRunConflict.message); return; }
76
+ const resolved = resolveTaskRef(args[0]!);
77
+ if (!resolved.ok) { failUsage(resolved.message); return; }
78
+ let result;
79
+ let humanOverride: unknown = null;
80
+ try {
81
+ result = withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'task-review.finalize-summary', () => {
82
+ let current = finalizeReviewSummary({ taskRef: args[0]!, stage, artifact, orchestrated, dryRun }, { lockAlreadyHeld: true });
83
+ if (current.status !== 'failed' || !overrideTicket) return current;
84
+ if (!overrideTarget || !overrideScope) { failUsage('override ticket requires target and scope'); return current; }
85
+ const consumed = consumeHumanOverride({
86
+ taskRef: args[0]!, ticketId: overrideTicket,
87
+ failureId: failureId('review-finalization', current.error?.code ?? 'TASK_STATE_MISMATCH'),
88
+ target: overrideTarget, scope: overrideScope
89
+ }, {
90
+ effectExecutor: (capability) => {
91
+ const retried = finalizeReviewSummary({ taskRef: args[0]!, stage, artifact, orchestrated, dryRun }, { lockAlreadyHeld: true, manualOverride: capability });
92
+ current = retried;
93
+ return retried.status === 'failed' || retried.status === 'planned'
94
+ ? { code: 'OVERRIDE_EFFECT_FAILED', message: retried.status === 'planned' ? 'producer returned planned; no review effect was committed' : `${retried.error?.code ?? 'REVIEW_FAILED'}: ${retried.error?.message ?? 'manual review effect failed'}` }
95
+ : null;
96
+ }
97
+ });
98
+ humanOverride = consumed;
99
+ return current;
100
+ });
101
+ } catch (error) {
102
+ if (!(error instanceof TaskExecutionLockError)) throw error;
103
+ process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: error.code, message: error.message } })}\n`);
104
+ process.exitCode = 1;
105
+ return;
106
+ }
107
+ process.stdout.write(`${JSON.stringify(humanOverride ? { ...result, humanOverride } : result)}\n`);
108
+ const overrideFailed = Boolean(
109
+ humanOverride && typeof humanOverride === 'object' &&
110
+ (humanOverride as { status?: unknown }).status === 'failed'
111
+ );
112
+ if (result.status === 'failed' || overrideFailed) process.exitCode = 1;
68
113
  }
69
114
 
70
115
  export { taskReview };
@@ -1,6 +1,9 @@
1
1
  import { renderTaskVerification, verifyTaskEvent } from '../task/verification.ts';
2
+ import { consumeHumanOverride, failureId } from '../task/human-override.ts';
3
+ import { resolveTaskRef } from '../task/resolve-ref.ts';
4
+ import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.ts';
2
5
 
3
- const USAGE = 'Usage: agent-infra-internal task-verify <N | TASK-id> <verification-event> [--artifact <canonical.md>] [--format json|text]\n';
6
+ const USAGE = 'Usage: agent-infra-internal task-verify <N | TASK-id> <verification-event> [--artifact <canonical.md>] [--format json|text] [--override-ticket <ticket> --override-target <target> --override-scope <scope>]\n';
4
7
 
5
8
  function fail(message: string): void {
6
9
  process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: 'VERIFY_PAYLOAD_INVALID', message } })}\n`);
@@ -11,21 +14,67 @@ function fail(message: string): void {
11
14
  function taskVerify(args: string[] = []): void {
12
15
  if (args[0] === '--help' || args[0] === '-h') { process.stdout.write(USAGE); return; }
13
16
  if (!args[0] || !args[1] || args[0].startsWith('--') || args[1].startsWith('--')) { fail('task ref and verification event are required'); return; }
17
+ const taskRef = args[0];
18
+ const event = args[1];
14
19
  let artifact: string | undefined;
15
20
  let format: 'json' | 'text' = 'json';
21
+ let overrideTicket: string | undefined;
22
+ let overrideTarget: string | undefined;
23
+ let overrideScope: string | undefined;
16
24
  const seen = new Set<string>();
17
25
  for (let index = 2; index < args.length; index += 1) {
18
26
  const flag = args[index]!;
19
- if (flag !== '--artifact' && flag !== '--format') { fail(`unknown option '${flag}'`); return; }
27
+ if (!['--artifact', '--format', '--override-ticket', '--override-target', '--override-scope'].includes(flag)) { fail(`unknown option '${flag}'`); return; }
20
28
  if (seen.has(flag)) { fail(`duplicate option '${flag}'`); return; }
21
29
  const value = args[++index];
22
30
  if (!value || value.startsWith('--')) { fail(`option '${flag}' requires a value`); return; }
23
31
  seen.add(flag);
24
32
  if (flag === '--artifact') artifact = value;
33
+ else if (flag === '--override-ticket') overrideTicket = value;
34
+ else if (flag === '--override-target') overrideTarget = value;
35
+ else if (flag === '--override-scope') overrideScope = value;
25
36
  else if (value === 'json' || value === 'text') format = value;
26
37
  else { fail("option '--format' requires 'json' or 'text'"); return; }
27
38
  }
28
- const result = verifyTaskEvent({ taskRef: args[0], event: args[1], ...(artifact ? { artifact } : {}) });
39
+ const resolved = resolveTaskRef(taskRef);
40
+ if (!resolved.ok) { fail(resolved.message); return; }
41
+ let result;
42
+ let humanOverride: unknown = null;
43
+ try {
44
+ result = withTaskExecutionLock(resolved.repoRoot, resolved.taskId, `task-verify.${event}`, () => {
45
+ let current = verifyTaskEvent({ taskRef, event, ...(artifact ? { artifact } : {}) });
46
+ if (current.status === 'pass' || !overrideTicket) return current;
47
+ if (!overrideTarget || !overrideScope) { fail('override ticket requires target and scope'); return current; }
48
+ const failureProducer = current.status === 'blocked' || current.status === 'fail'
49
+ ? 'verification-engine'
50
+ : 'task-verify';
51
+ const failureCode = current.status === 'blocked'
52
+ ? 'CHECK_BLOCKED'
53
+ : current.status === 'fail'
54
+ ? 'CHECK_FAILED'
55
+ : current.error?.code ?? 'VERIFY_TASK_STATE_MISMATCH';
56
+ const consumed = consumeHumanOverride({
57
+ taskRef, ticketId: overrideTicket,
58
+ failureId: failureId(failureProducer, failureCode),
59
+ target: overrideTarget, scope: overrideScope
60
+ }, {
61
+ effectExecutor: (capability) => {
62
+ const retried = verifyTaskEvent({ taskRef, event, ...(artifact ? { artifact } : {}) }, { manualOverride: capability });
63
+ current = retried;
64
+ return retried.status !== 'pass'
65
+ ? { code: 'OVERRIDE_EFFECT_FAILED', message: `${retried.error?.code ?? 'VERIFY_FAILED'}: ${retried.error?.message ?? 'manual verification effect failed'}` }
66
+ : null;
67
+ }
68
+ });
69
+ humanOverride = consumed;
70
+ return current;
71
+ });
72
+ } catch (error) {
73
+ if (!(error instanceof TaskExecutionLockError)) throw error;
74
+ fail(`${error.code}: ${error.message}`);
75
+ return;
76
+ }
77
+ if (humanOverride) result = { ...result, humanOverride } as typeof result;
29
78
  process.stdout.write(format === 'text' ? renderTaskVerification(result) : `${JSON.stringify(result)}\n`);
30
79
  process.exitCode = result.status === 'pass' ? 0 : result.status === 'blocked' ? 2 : 1;
31
80
  }
@@ -1,10 +1,14 @@
1
1
  import { applyWorkflowWarningIntent } from '../task/workflow-warning-intents.ts';
2
2
  import type { WorkflowWarningIntent } from '../task/workflow-warning-intents.ts';
3
+ import { consumeHumanOverride, failureId, overrideDryRunConflict } from '../task/human-override.ts';
4
+ import { resolveTaskRef } from '../task/resolve-ref.ts';
5
+ import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.ts';
3
6
 
4
7
  const USAGE = `Usage: agent-infra-internal task-warning <task-ref> <add|set-status|list> [intent flags] [--dry-run]\n`;
5
8
  const FLAGS: Record<string, string> = {
6
9
  '--step': 'step', '--severity': 'severity', '--code': 'code', '--target': 'target',
7
10
  '--message': 'message', '--action': 'action', '--id': 'id', '--status': 'status', '--resolution': 'resolution'
11
+ , '--override-ticket': 'overrideTicket', '--override-target': 'overrideTarget', '--override-scope': 'overrideScope'
8
12
  };
9
13
 
10
14
  function usageFailure(message: string): void {
@@ -38,13 +42,50 @@ function taskWarning(args: string[] = []): void {
38
42
  };
39
43
  const optional = kind === 'list' ? ['status'] : [];
40
44
  const allowed = new Set(['kind', 'taskRef', 'dryRun', ...required[kind]!, ...optional]);
45
+ allowed.add('overrideTicket'); allowed.add('overrideTarget'); allowed.add('overrideScope');
41
46
  const unexpected = Object.keys(values).find((key) => !allowed.has(key));
42
47
  const missing = required[kind]!.find((key) => values[key] === undefined);
43
48
  if (unexpected) { usageFailure(`${kind} does not accept '${unexpected}'`); return; }
44
49
  if (missing) { usageFailure(`${kind} requires '${missing}'`); return; }
45
- const result = applyWorkflowWarningIntent(values as WorkflowWarningIntent);
46
- process.stdout.write(`${JSON.stringify(result)}\n`);
47
- if (result.status === 'failed') process.exitCode = 1;
50
+ const dryRunConflict = overrideDryRunConflict(values);
51
+ if (dryRunConflict) { usageFailure(dryRunConflict.message); return; }
52
+ const resolved = resolveTaskRef(taskRef);
53
+ if (!resolved.ok) { usageFailure(resolved.message); return; }
54
+ let result;
55
+ let humanOverride: unknown = null;
56
+ try {
57
+ result = withTaskExecutionLock(resolved.repoRoot, resolved.taskId, `task-warning.${kind}`, () => {
58
+ let current = applyWorkflowWarningIntent(values as WorkflowWarningIntent);
59
+ if (current.status !== 'failed' || !values.overrideTicket) return current;
60
+ if (!values.overrideTarget || !values.overrideScope) { usageFailure('override ticket requires target and scope'); return current; }
61
+ const consumed = consumeHumanOverride({
62
+ taskRef, ticketId: String(values.overrideTicket),
63
+ failureId: failureId('workflow-warning', current.error?.code ?? 'TASK_STATE_MISMATCH'),
64
+ target: String(values.overrideTarget), scope: String(values.overrideScope)
65
+ }, {
66
+ effectExecutor: (capability) => {
67
+ const retried = applyWorkflowWarningIntent(values as WorkflowWarningIntent, { manualOverride: capability });
68
+ current = retried;
69
+ return retried.status === 'failed' || retried.status === 'planned'
70
+ ? { code: 'OVERRIDE_EFFECT_FAILED', message: retried.status === 'planned' ? 'producer returned planned; no workflow warning effect was committed' : `${retried.error?.code ?? 'WARNING_FAILED'}: ${retried.error?.message ?? 'manual warning effect failed'}` }
71
+ : null;
72
+ }
73
+ });
74
+ humanOverride = consumed;
75
+ return current;
76
+ });
77
+ } catch (error) {
78
+ if (!(error instanceof TaskExecutionLockError)) throw error;
79
+ process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: error.code, message: error.message } })}\n`);
80
+ process.exitCode = 1;
81
+ return;
82
+ }
83
+ process.stdout.write(`${JSON.stringify(humanOverride ? { ...result, humanOverride } : result)}\n`);
84
+ const overrideFailed = Boolean(
85
+ humanOverride && typeof humanOverride === 'object' &&
86
+ (humanOverride as { status?: unknown }).status === 'failed'
87
+ );
88
+ if (result.status === 'failed' || overrideFailed) process.exitCode = 1;
48
89
  }
49
90
 
50
91
  export { taskWarning };
@@ -3,6 +3,7 @@ import { execFileSync } from 'node:child_process';
3
3
 
4
4
  import { enumerateArtifacts } from '../task/artifacts.ts';
5
5
  import { parseTaskFrontmatter } from '../task/frontmatter.ts';
6
+ import { renderHumanOverrideAudit } from '../task/human-override.ts';
6
7
  import { resolveTaskRef } from '../task/resolve-ref.ts';
7
8
  import { resolvePlatformContext } from './context.ts';
8
9
  import { createGitHubClient } from './github-client.ts';
@@ -10,19 +11,32 @@ import type { GitHubClient } from './github-client.ts';
10
11
  import { listRemoteComments, normalizeCommentContent, writeComment } from './issue-comments.ts';
11
12
  import { platformResult } from './types.ts';
12
13
  import type { PlatformResult } from './types.ts';
14
+ import type { OperationWarning } from '../task/operation-outcome.ts';
13
15
 
14
16
  type SummaryComment = { id: number; body: string };
15
17
  type SummaryContextResult = PlatformResult & {
16
18
  task: { id: string | null; prNumber: number | null };
17
19
  artifacts: Array<{ family: string; name: string; path: string }>;
18
20
  };
21
+ type PullRequestSummaryResult = PlatformResult & {
22
+ result: 'pr_created_with_warnings' | 'pr_reused_with_warnings' | 'no_op_with_warnings' | null;
23
+ warnings: readonly OperationWarning[];
24
+ };
25
+ type PullRequestPrimaryResult = 'pr_created' | 'pr_reused' | 'no_op';
26
+
27
+ function warningResultForPrimary(primaryResult: PullRequestPrimaryResult): NonNullable<PullRequestSummaryResult['result']> {
28
+ if (primaryResult === 'pr_created') return 'pr_created_with_warnings';
29
+ if (primaryResult === 'pr_reused') return 'pr_reused_with_warnings';
30
+ return 'no_op_with_warnings';
31
+ }
19
32
 
20
33
  function summaryMarker(taskId: string): string {
21
34
  return `<!-- sync-pr:${taskId}:summary -->`;
22
35
  }
23
36
 
24
- function buildPullRequestSummary(taskId: string, body: string, headSha: string): string {
25
- return normalizeCommentContent(`${summaryMarker(taskId)}\n<!-- last-commit: ${headSha} -->\n\n${body.replace(/\s+$/, '')}\n`);
37
+ function buildPullRequestSummary(taskId: string, body: string, headSha: string, humanOverrideAudit = ''): string {
38
+ const sections = [body.replace(/\s+$/, ''), humanOverrideAudit.trim()].filter(Boolean).join('\n\n');
39
+ return normalizeCommentContent(`${summaryMarker(taskId)}\n<!-- last-commit: ${headSha} -->\n\n${sections}\n`);
26
40
  }
27
41
 
28
42
  function reconcileSummaryComment(comments: SummaryComment[], taskId: string, desired: string):
@@ -59,32 +73,69 @@ function summaryContext(taskRef: string, options: { cwd?: string; client?: GitHu
59
73
  return { ...platformResult('no-op'), task: { id: resolved.taskId, prNumber }, artifacts: canonicalArtifacts(resolved.taskDir) };
60
74
  }
61
75
 
62
- function syncPullRequestSummary(taskRef: string, options: { agent: string; body: string; cwd?: string; client?: GitHubClient; dryRun?: boolean }): PlatformResult {
76
+ function syncPullRequestSummary(taskRef: string, options: { agent: string; body: string; cwd?: string; client?: GitHubClient; dryRun?: boolean; primaryResult: PullRequestPrimaryResult }): PullRequestSummaryResult {
77
+ const warningResult = warningResultForPrimary(options.primaryResult);
78
+ let knownPrNumber: number | null = null;
79
+ const softenFailure = (output: PlatformResult): PullRequestSummaryResult => {
80
+ const prNumber = output.resource.kind === 'pull-request' && output.resource.number
81
+ ? output.resource.number
82
+ : knownPrNumber;
83
+ const warning = output.error && prNumber
84
+ && output.error.code !== 'PR_NOT_LINKED'
85
+ ? {
86
+ code: output.error.code,
87
+ message: output.error.message,
88
+ retryable: output.error.retryable,
89
+ step: 'pr-summary',
90
+ target: `pull-request:${prNumber}`,
91
+ severity: 'ACTION_REQUIRED' as const
92
+ }
93
+ : null;
94
+ return warning
95
+ ? {
96
+ ...output,
97
+ status: 'applied',
98
+ changed: false,
99
+ resource: { kind: 'pull-request', number: prNumber },
100
+ error: null,
101
+ result: warningResult,
102
+ warnings: [warning]
103
+ }
104
+ : { ...output, result: null, warnings: [] };
105
+ };
63
106
  const resolved = resolveTaskRef(taskRef, options.cwd ? { repoRoot: options.cwd } : {});
64
- if (!resolved.ok) return platformResult('failed', { error: { code: resolved.code, message: resolved.message, retryable: false } });
107
+ if (!resolved.ok) return softenFailure(platformResult('failed', { error: { code: resolved.code, message: resolved.message, retryable: false } }));
65
108
  const frontmatter = parseTaskFrontmatter(fs.readFileSync(resolved.taskMdPath, 'utf8'));
66
109
  const prNumber = Number(frontmatter.pr_number);
67
- if (!Number.isInteger(prNumber) || prNumber <= 0) return platformResult('failed', { error: { code: 'PR_NOT_LINKED', message: 'Task has no valid pr_number', retryable: false } });
110
+ if (!Number.isInteger(prNumber) || prNumber <= 0) return softenFailure(platformResult('failed', { error: { code: 'PR_NOT_LINKED', message: 'Task has no valid pr_number', retryable: false } }));
111
+ knownPrNumber = prNumber;
68
112
  const client = options.client || createGitHubClient();
69
113
  const context = resolvePlatformContext({ cwd: resolved.repoRoot, client });
70
- if (!context.platform.repository || !['no-op', 'degraded'].includes(context.status)) return context;
114
+ if (!context.platform.repository || !['no-op', 'degraded'].includes(context.status)) return softenFailure(context);
71
115
  let headSha: string;
72
116
  try {
73
117
  headSha = execFileSync('git', ['rev-parse', 'HEAD'], { cwd: resolved.repoRoot, encoding: 'utf8' }).trim();
74
118
  } catch (error) {
75
- return platformResult('failed', { platform: context.platform, capabilities: context.capabilities, error: { code: 'GIT_HEAD_UNRESOLVED', message: error instanceof Error ? error.message : String(error), retryable: false } });
119
+ return softenFailure(platformResult('failed', { platform: context.platform, capabilities: context.capabilities, error: { code: 'GIT_HEAD_UNRESOLVED', message: error instanceof Error ? error.message : String(error), retryable: false } }));
76
120
  }
77
- const desired = buildPullRequestSummary(resolved.taskId, options.body, headSha);
121
+ const desired = buildPullRequestSummary(
122
+ resolved.taskId,
123
+ options.body,
124
+ headSha,
125
+ renderHumanOverrideAudit(fs.readFileSync(resolved.taskMdPath, 'utf8'))
126
+ );
78
127
  const listed = listRemoteComments(client, context.platform.repository, prNumber, resolved.repoRoot);
79
- if (!listed.ok) return platformResult(listed.error.retryable ? 'blocked' : 'failed', { platform: context.platform, capabilities: context.capabilities, resource: { kind: 'pull-request', number: prNumber }, error: listed.error });
128
+ if (!listed.ok) return softenFailure(platformResult(listed.error.retryable ? 'blocked' : 'failed', { platform: context.platform, capabilities: context.capabilities, resource: { kind: 'pull-request', number: prNumber }, error: listed.error }));
80
129
  const reconciliation = reconcileSummaryComment(listed.value, resolved.taskId, desired);
81
- if (reconciliation.action === 'conflict') return platformResult('failed', { platform: context.platform, capabilities: context.capabilities, resource: { kind: 'pull-request', number: prNumber }, error: { code: 'PR_SUMMARY_MARKER_AMBIGUOUS', message: 'Multiple PR comments contain the summary marker', retryable: false } });
82
- if (reconciliation.action === 'no-op') return platformResult('no-op', { platform: context.platform, capabilities: context.capabilities, resource: { kind: 'pull-request', number: prNumber }, comment: { kind: 'summary', marker: summaryMarker(resolved.taskId), ids: [reconciliation.commentId!], parts: 1 }, error: null });
83
- if (options.dryRun) return platformResult('planned', { platform: context.platform, capabilities: context.capabilities, resource: { kind: 'pull-request', number: prNumber }, operations: [{ name: `summary:${reconciliation.action}`, status: 'planned', reasonCode: null }], error: null });
130
+ if (reconciliation.action === 'conflict') return softenFailure(platformResult('failed', { platform: context.platform, capabilities: context.capabilities, resource: { kind: 'pull-request', number: prNumber }, error: { code: 'PR_SUMMARY_MARKER_AMBIGUOUS', message: 'Multiple PR comments contain the summary marker', retryable: false } }));
131
+ if (reconciliation.action === 'no-op') return softenFailure(platformResult('no-op', { platform: context.platform, capabilities: context.capabilities, resource: { kind: 'pull-request', number: prNumber }, comment: { kind: 'summary', marker: summaryMarker(resolved.taskId), ids: [reconciliation.commentId!], parts: 1 }, error: null }));
132
+ if (options.dryRun) return softenFailure(platformResult('planned', { platform: context.platform, capabilities: context.capabilities, resource: { kind: 'pull-request', number: prNumber }, operations: [{ name: `summary:${reconciliation.action}`, status: 'planned', reasonCode: null }], error: null }));
84
133
  const written = writeComment(client, context.platform.repository, prNumber, resolved.repoRoot, desired, reconciliation.commentId || undefined);
85
- if (!written.ok) return platformResult(written.error.retryable ? 'blocked' : 'failed', { platform: context.platform, capabilities: context.capabilities, resource: { kind: 'pull-request', number: prNumber }, error: written.error });
134
+ if (!written.ok) return softenFailure(platformResult(written.error.retryable ? 'blocked' : 'failed', { platform: context.platform, capabilities: context.capabilities, resource: { kind: 'pull-request', number: prNumber }, error: written.error }));
86
135
  const id = Number(written.value.id || reconciliation.commentId);
87
- return platformResult('applied', { platform: context.platform, capabilities: context.capabilities, resource: { kind: 'pull-request', number: prNumber }, comment: { kind: 'summary', marker: summaryMarker(resolved.taskId), ids: Number.isInteger(id) ? [id] : [], parts: 1 }, operations: [{ name: `summary:${reconciliation.action}`, status: 'applied', reasonCode: null }], error: null });
136
+ return softenFailure(platformResult('applied', { platform: context.platform, capabilities: context.capabilities, resource: { kind: 'pull-request', number: prNumber }, comment: { kind: 'summary', marker: summaryMarker(resolved.taskId), ids: Number.isInteger(id) ? [id] : [], parts: 1 }, operations: [{ name: `summary:${reconciliation.action}`, status: 'applied', reasonCode: null }], error: null }));
88
137
  }
89
138
 
90
139
  export { buildPullRequestSummary, reconcileSummaryComment, summaryContext, summaryMarker, syncPullRequestSummary };
140
+ export type { PullRequestSummaryResult, SummaryContextResult };
141
+ export { warningResultForPrimary };