@fitlab-ai/agent-infra 0.9.8 → 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 (133) hide show
  1. package/README.md +1 -1
  2. package/README.zh-CN.md +1 -1
  3. package/dist/bin/internal-cli.js +6 -1
  4. package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +58 -21
  5. package/dist/lib/agent-clients/adapters/codex-lifecycle/build-identity.js +8 -1
  6. package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +173 -10
  7. package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-context.js +147 -0
  8. package/dist/lib/agent-clients/adapters/codex-lifecycle/evidence.js +14 -0
  9. package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +56 -67
  10. package/dist/lib/git/workflow.js +10 -1
  11. package/dist/lib/internal/codex-lifecycle.js +19 -5
  12. package/dist/lib/internal/codex-sandbox-controller.js +6 -13
  13. package/dist/lib/internal/git-workflow.js +5 -7
  14. package/dist/lib/internal/platform-pr.js +20 -7
  15. package/dist/lib/internal/release-workflow.js +2 -1
  16. package/dist/lib/internal/sandbox-control.js +79 -2
  17. package/dist/lib/internal/task-finalization.js +54 -0
  18. package/dist/lib/internal/task-orchestration.js +156 -291
  19. package/dist/lib/platform/pr-summary.js +48 -10
  20. package/dist/lib/platform/pull-requests.js +205 -41
  21. package/dist/lib/sandbox/commands/rm.js +316 -55
  22. package/dist/lib/sandbox/control/client.js +190 -6
  23. package/dist/lib/sandbox/control/controller-registration.js +273 -0
  24. package/dist/lib/sandbox/control/executor.js +137 -5
  25. package/dist/lib/sandbox/control/lifecycle.js +3 -1
  26. package/dist/lib/sandbox/control/protocol.js +67 -6
  27. package/dist/lib/sandbox/index.js +5 -3
  28. package/dist/lib/sandbox/managed-fs.js +5 -0
  29. package/dist/lib/sandbox/task-resolver.js +50 -6
  30. package/dist/lib/sandbox/workspace-identity.js +27 -0
  31. package/dist/lib/sandbox/worktree-safety.js +215 -2
  32. package/dist/lib/task/codex-orchestration.js +158 -33
  33. package/dist/lib/task/commands/status.js +59 -1
  34. package/dist/lib/task/commit-operation.js +335 -0
  35. package/dist/lib/task/commit-policy.js +18 -0
  36. package/dist/lib/task/delegation-receipts.js +243 -2
  37. package/dist/lib/task/finalization.js +567 -0
  38. package/dist/lib/task/operation-outcome.js +33 -0
  39. package/dist/lib/task/orchestration.js +187 -761
  40. package/dist/lib/task/short-id.js +59 -1
  41. package/dist/lib/task/task-execution-lock.js +6 -1
  42. package/dist/lib/task/verification-engine.js +113 -60
  43. package/dist/lib/task/verification.js +2 -1
  44. package/dist/lib/task/workflow-warning-intents.js +50 -1
  45. package/dist/lifecycle-build-manifest.json +3 -3
  46. package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +61 -19
  47. package/lib/agent-clients/adapters/codex-lifecycle/build-identity.ts +12 -0
  48. package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +276 -12
  49. package/lib/agent-clients/adapters/codex-lifecycle/controller-context.ts +180 -0
  50. package/lib/agent-clients/adapters/codex-lifecycle/evidence.ts +19 -0
  51. package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +9 -1
  52. package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +83 -91
  53. package/lib/git/workflow.ts +11 -2
  54. package/lib/internal/codex-lifecycle.ts +18 -5
  55. package/lib/internal/codex-sandbox-controller.ts +6 -12
  56. package/lib/internal/git-workflow.ts +5 -6
  57. package/lib/internal/platform-pr.ts +14 -7
  58. package/lib/internal/release-workflow.ts +2 -1
  59. package/lib/internal/sandbox-control.ts +92 -2
  60. package/lib/internal/task-finalization.ts +68 -0
  61. package/lib/internal/task-orchestration.ts +20 -103
  62. package/lib/platform/pr-summary.ts +55 -11
  63. package/lib/platform/pull-requests.ts +211 -41
  64. package/lib/sandbox/commands/rm.ts +413 -61
  65. package/lib/sandbox/control/client.ts +255 -6
  66. package/lib/sandbox/control/controller-registration.ts +347 -0
  67. package/lib/sandbox/control/executor.ts +163 -5
  68. package/lib/sandbox/control/lifecycle.ts +2 -1
  69. package/lib/sandbox/control/protocol.ts +80 -7
  70. package/lib/sandbox/index.ts +5 -3
  71. package/lib/sandbox/managed-fs.ts +5 -0
  72. package/lib/sandbox/task-resolver.ts +58 -7
  73. package/lib/sandbox/workspace-identity.ts +50 -2
  74. package/lib/sandbox/workspace-view.ts +3 -3
  75. package/lib/sandbox/worktree-safety.ts +236 -2
  76. package/lib/task/codex-orchestration.ts +167 -33
  77. package/lib/task/commands/status.ts +78 -1
  78. package/lib/task/commit-operation.ts +430 -0
  79. package/lib/task/commit-policy.ts +30 -0
  80. package/lib/task/delegation-receipts.ts +258 -1
  81. package/lib/task/finalization.ts +719 -0
  82. package/lib/task/operation-outcome.ts +48 -0
  83. package/lib/task/orchestration.ts +217 -982
  84. package/lib/task/short-id.ts +65 -1
  85. package/lib/task/task-execution-lock.ts +10 -1
  86. package/lib/task/verification-engine.ts +118 -63
  87. package/lib/task/verification.ts +3 -2
  88. package/lib/task/workflow-warning-intents.ts +57 -2
  89. package/package.json +2 -2
  90. package/templates/.agents/QUICKSTART.en.md +2 -2
  91. package/templates/.agents/QUICKSTART.zh-CN.md +2 -2
  92. package/templates/.agents/hooks/lifecycle-delegation.js +1 -21
  93. package/templates/.agents/rules/lifecycle-orchestration.en.md +1 -1
  94. package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +1 -1
  95. package/templates/.agents/rules/task-management.en.md +2 -2
  96. package/templates/.agents/rules/task-management.zh-CN.md +2 -2
  97. package/templates/.agents/skills/block-task/SKILL.en.md +2 -4
  98. package/templates/.agents/skills/block-task/SKILL.zh-CN.md +2 -4
  99. package/templates/.agents/skills/cancel-task/SKILL.en.md +3 -3
  100. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +3 -3
  101. package/templates/.agents/skills/close-codescan/SKILL.en.md +6 -2
  102. package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +6 -2
  103. package/templates/.agents/skills/close-dependabot/SKILL.en.md +6 -2
  104. package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +6 -2
  105. package/templates/.agents/skills/commit/SKILL.en.md +41 -125
  106. package/templates/.agents/skills/commit/SKILL.zh-CN.md +52 -123
  107. package/templates/.agents/skills/commit/reference/commit-message.en.md +1 -1
  108. package/templates/.agents/skills/commit/reference/commit-message.zh-CN.md +1 -1
  109. package/templates/.agents/skills/commit/reference/commit-orchestration.en.md +14 -43
  110. package/templates/.agents/skills/commit/reference/commit-orchestration.zh-CN.md +14 -49
  111. package/templates/.agents/skills/commit/reference/pr-summary-sync.en.md +1 -1
  112. package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +1 -1
  113. package/templates/.agents/skills/commit/reference/task-status-update.en.md +2 -35
  114. package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +2 -35
  115. package/templates/.agents/skills/complete-task/SKILL.en.md +31 -38
  116. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +31 -38
  117. package/templates/.agents/skills/complete-task/config/verify.en.json +1 -0
  118. package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +1 -0
  119. package/templates/.agents/skills/complete-task/reference/external-delivery.en.md +1 -1
  120. package/templates/.agents/skills/complete-task/reference/external-delivery.zh-CN.md +1 -1
  121. package/templates/.agents/skills/create-pr/SKILL.en.md +6 -4
  122. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +6 -4
  123. package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +2 -2
  124. package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +2 -2
  125. package/templates/.agents/skills/run-task/SKILL.en.md +5 -5
  126. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +5 -5
  127. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +1 -1
  128. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +1 -1
  129. package/templates/.codex/hooks.json +4 -4
  130. package/dist/lib/task/commit-finalization.js +0 -305
  131. package/dist/lib/task/commit-intent.js +0 -165
  132. package/lib/task/commit-finalization.ts +0 -419
  133. package/lib/task/commit-intent.ts +0 -230
@@ -0,0 +1,68 @@
1
+ import { normalizeAgentToken, AGENT_USAGE_HINT } from '../agent-clients/tokens.ts';
2
+ import { applyTaskFinalization } from '../task/finalization.ts';
3
+ import { detectRepoRoot, resolveTaskRef } from '../task/resolve-ref.ts';
4
+ import { verifyTaskEvent } from '../task/verification.ts';
5
+
6
+ const USAGE = 'Usage: agent-infra-internal task-finalization <N | TASK-id> complete --agent <agent>\n';
7
+
8
+ type FinalizationError = Readonly<{ code: string; message: string; retryable: boolean }>;
9
+
10
+ function envelope(
11
+ status: 'completed' | 'failed' | 'blocked' | 'unknown',
12
+ changed: boolean,
13
+ accepted: boolean,
14
+ result: ReturnType<typeof applyTaskFinalization> | null,
15
+ error: FinalizationError | null
16
+ ): string {
17
+ return `${JSON.stringify({ version: 1, status, changed, accepted, result, error })}\n`;
18
+ }
19
+
20
+ function exitCode(status: 'completed' | 'failed' | 'blocked' | 'unknown'): number {
21
+ return status === 'completed' ? 0 : status === 'blocked' ? 2 : 1;
22
+ }
23
+
24
+ function fail(message: string): void {
25
+ const error = { code: 'TASK_FINALIZATION_PAYLOAD_INVALID', message, retryable: false };
26
+ process.stdout.write(envelope('failed', false, false, null, error));
27
+ process.stderr.write(USAGE);
28
+ process.exitCode = 1;
29
+ }
30
+
31
+ function taskFinalization(args: string[] = []): void {
32
+ if (args[0] === '--help' || args[0] === '-h') { process.stdout.write(USAGE); return; }
33
+ if (args.length !== 4 || !args[0] || args[1] !== 'complete' || args[2] !== '--agent' || !args[3]) {
34
+ fail('task ref, complete intent, and --agent are required');
35
+ return;
36
+ }
37
+ const agent = normalizeAgentToken(args[3]);
38
+ if (!agent) {
39
+ fail(`invalid --agent: ${AGENT_USAGE_HINT}`);
40
+ return;
41
+ }
42
+ let repoRoot: string;
43
+ try {
44
+ repoRoot = detectRepoRoot();
45
+ } catch (error) {
46
+ const detail = { code: 'REPO_ROOT_NOT_FOUND', message: error instanceof Error ? error.message : String(error), retryable: false };
47
+ process.stdout.write(envelope('failed', false, true, null, detail));
48
+ process.exitCode = 1;
49
+ return;
50
+ }
51
+ const resolved = resolveTaskRef(args[0], { repoRoot });
52
+ if (!resolved.ok) {
53
+ const detail = { code: resolved.code, message: resolved.message, retryable: false };
54
+ process.stdout.write(envelope('failed', false, true, null, detail));
55
+ process.exitCode = 1;
56
+ return;
57
+ }
58
+ const result = applyTaskFinalization(
59
+ { taskRef: resolved.taskId, intent: 'complete', agent },
60
+ { repoRoot, preflight: (request, options) => verifyTaskEvent(
61
+ { ...request, event: 'complete-task.hard-preflight' }, options
62
+ ) }
63
+ );
64
+ process.stdout.write(envelope(result.status, result.changed, true, result, result.error));
65
+ process.exitCode = exitCode(result.status);
66
+ }
67
+
68
+ export { taskFinalization };
@@ -1,31 +1,23 @@
1
- import { normalizeAgentToken, AGENT_USAGE_HINT } from '../agent-clients/tokens.ts';
2
1
  import {
3
- abortCommitIntent,
4
2
  activateMatchingOrchestrationDelegation,
5
3
  activateOrchestrationDelegation,
6
4
  advanceOrchestration,
7
5
  awaitOrchestrationDelegationActivation,
8
- beginCommitIntent,
9
6
  beginOrResumeOrchestration,
10
- checkpointCommitIntent,
11
- completeCommitIntent,
12
7
  dispatchOrchestrationDelegation,
8
+ OrchestrationStateError,
13
9
  pauseOrchestration,
14
- recoverCommitIntent,
15
10
  recoverPreparedOrchestrationDelegation,
16
11
  routeOrchestration,
17
12
  sealMatchingOrchestrationDelegation,
18
13
  sealOrchestrationDelegation,
19
- startCommitAttempt,
20
- statusCommitIntent,
21
- statusOrchestration,
22
- terminateCommitAttempt
14
+ statusOrchestration
23
15
  } from '../task/orchestration.ts';
24
16
  import { prepareCodexOrchestrationDelegation } from '../task/codex-orchestration.ts';
25
17
  import { isAgentClientId } from '../agent-clients/types.ts';
26
18
  import type { AgentClientId } from '../agent-clients/types.ts';
27
19
 
28
- const USAGE = 'Usage: agent-infra-internal task-orchestration <task-ref|auto> <begin-or-resume|route|prepare|dispatch|await-activation|recover-prepared|hook-start|hook-stop|advance|pause|status|commit-start|commit-begin|commit-checkpoint|commit-complete|commit-recover|commit-abort|commit-terminate|commit-status> [options]\n';
20
+ const USAGE = 'Usage: agent-infra-internal task-orchestration <task-ref|auto> <begin-or-resume|route|prepare|dispatch|await-activation|recover-prepared|hook-start|hook-stop|advance|pause|status> [options]\n';
29
21
 
30
22
  function usageFailure(message: string): void {
31
23
  process.stdout.write(`${JSON.stringify({
@@ -48,23 +40,15 @@ async function taskOrchestration(args: string[] = []): Promise<void> {
48
40
  const [taskRef, intent] = args;
49
41
  if (![
50
42
  'begin-or-resume', 'route', 'prepare', 'dispatch', 'await-activation', 'recover-prepared',
51
- 'hook-start', 'hook-stop', 'advance', 'pause', 'status',
52
- 'commit-start', 'commit-begin', 'commit-checkpoint', 'commit-complete', 'commit-recover', 'commit-abort', 'commit-terminate', 'commit-status'
43
+ 'hook-start', 'hook-stop', 'advance', 'pause', 'status'
53
44
  ].includes(intent!)) {
54
45
  usageFailure(`unknown intent '${intent}'`);
55
46
  return;
56
47
  }
57
48
  const values: Record<string, string> = {};
58
49
  const seen = new Set<string>();
59
- let orchestrated = false;
60
50
  for (let index = 2; index < args.length; index += 1) {
61
51
  const flag = args[index]!;
62
- if (flag === '--orchestrated') {
63
- if (seen.has(flag)) { usageFailure(`duplicate option '${flag}'`); return; }
64
- seen.add(flag);
65
- orchestrated = true;
66
- continue;
67
- }
68
52
  if (![
69
53
  '--max-steps', '--executor-model', '--executor-reasoning-effort',
70
54
  '--reviewer-model', '--reviewer-reasoning-effort',
@@ -74,8 +58,7 @@ async function taskOrchestration(args: string[] = []): Promise<void> {
74
58
  '--native-agent', '--child-id', '--spawn-mode', '--actual-model', '--actual-reasoning-effort',
75
59
  '--model-fallback-reason', '--reasoning-effort-fallback-reason',
76
60
  '--exit-code', '--after-fingerprint', '--changed-paths', '--code', '--message', '--recoverable', '--agent',
77
- '--baseline-head', '--token', '--kind', '--head', '--remote', '--ref', '--expected-head'
78
- , '--attempt', '--git-worktree-root'
61
+ '--git-worktree-root'
79
62
  ].includes(flag)) {
80
63
  usageFailure(`unknown option '${flag}'`);
81
64
  return;
@@ -92,26 +75,6 @@ async function taskOrchestration(args: string[] = []): Promise<void> {
92
75
  }
93
76
  values[flag] = value;
94
77
  }
95
- const commitIntentFlags: Record<string, readonly string[]> = {
96
- 'commit-start': ['--agent', '--git-worktree-root'],
97
- 'commit-begin': ['--agent', '--baseline-head', '--attempt', '--orchestrated', '--git-worktree-root'],
98
- 'commit-checkpoint': ['--token', '--kind', '--head', '--remote', '--ref', '--git-worktree-root'],
99
- 'commit-complete': ['--token', '--agent', '--git-worktree-root'],
100
- 'commit-recover': ['--agent', '--git-worktree-root'],
101
- 'commit-abort': ['--token', '--expected-head', '--git-worktree-root'],
102
- 'commit-terminate': ['--attempt', '--agent', '--code', '--git-worktree-root'],
103
- 'commit-status': ['--git-worktree-root']
104
- };
105
- if (intent! in commitIntentFlags) {
106
- const allowed = commitIntentFlags[intent!]!;
107
- const invalid = [...seen].find((flag) => !allowed.includes(flag));
108
- if (invalid) { usageFailure(`intent '${intent}' does not accept '${invalid}'`); return; }
109
- } else {
110
- const commitOnly = [...seen].find((flag) => [
111
- '--orchestrated', '--baseline-head', '--attempt', '--token', '--kind', '--head', '--remote', '--ref', '--expected-head'
112
- ].includes(flag));
113
- if (commitOnly) { usageFailure(`intent '${intent}' does not accept '${commitOnly}'`); return; }
114
- }
115
78
  const requireValues = (flags: string[]) => flags.find((flag) => values[flag] === undefined);
116
79
  const coreOptions = values['--git-worktree-root'] === undefined
117
80
  ? {}
@@ -121,66 +84,8 @@ async function taskOrchestration(args: string[] = []): Promise<void> {
121
84
  return;
122
85
  }
123
86
  let result;
124
- if (intent === 'commit-start') {
125
- const missing = requireValues(['--agent']);
126
- if (missing) { usageFailure(`intent 'commit-start' requires '${missing}'`); return; }
127
- const agent = normalizeAgentToken(values['--agent']!);
128
- if (!agent) { usageFailure(`invalid --agent '${values['--agent']}': ${AGENT_USAGE_HINT}`); return; }
129
- result = startCommitAttempt(taskRef!, { agent }, coreOptions);
130
- } else if (intent === 'commit-begin') {
131
- const missing = requireValues(['--agent', '--baseline-head', '--attempt']);
132
- if (missing) { usageFailure(`intent 'commit-begin' requires '${missing}'`); return; }
133
- const agent = normalizeAgentToken(values['--agent']!);
134
- if (!agent) { usageFailure(`invalid --agent '${values['--agent']}': ${AGENT_USAGE_HINT}`); return; }
135
- result = beginCommitIntent(taskRef!, {
136
- agent, orchestrated, baselineHead: values['--baseline-head']!, attempt: values['--attempt']!
137
- }, coreOptions);
138
- } else if (intent === 'commit-checkpoint') {
139
- const missing = requireValues(['--token', '--kind', '--head']);
140
- if (missing) { usageFailure(`intent 'commit-checkpoint' requires '${missing}'`); return; }
141
- if (!['committed', 'pushed'].includes(values['--kind']!)) {
142
- usageFailure('--kind must be committed or pushed'); return;
143
- }
144
- if (values['--kind'] === 'pushed') {
145
- const pushedMissing = requireValues(['--remote', '--ref']);
146
- if (pushedMissing) { usageFailure(`pushed checkpoint requires '${pushedMissing}'`); return; }
147
- } else if (values['--remote'] !== undefined || values['--ref'] !== undefined) {
148
- usageFailure('committed checkpoint does not accept --remote or --ref'); return;
149
- }
150
- result = checkpointCommitIntent(taskRef!, {
151
- token: values['--token']!, kind: values['--kind'] as 'committed' | 'pushed',
152
- head: values['--head']!, remote: values['--remote'], ref: values['--ref']
153
- }, coreOptions);
154
- } else if (intent === 'commit-complete') {
155
- const missing = requireValues(['--token', '--agent']);
156
- if (missing) { usageFailure(`intent 'commit-complete' requires '${missing}'`); return; }
157
- const agent = normalizeAgentToken(values['--agent']!);
158
- if (!agent) { usageFailure(`invalid --agent '${values['--agent']}': ${AGENT_USAGE_HINT}`); return; }
159
- result = completeCommitIntent(taskRef!, { token: values['--token']!, agent }, coreOptions);
160
- } else if (intent === 'commit-recover') {
161
- const missing = requireValues(['--agent']);
162
- if (missing) { usageFailure(`intent 'commit-recover' requires '${missing}'`); return; }
163
- const agent = normalizeAgentToken(values['--agent']!);
164
- if (!agent) { usageFailure(`invalid --agent '${values['--agent']}': ${AGENT_USAGE_HINT}`); return; }
165
- result = recoverCommitIntent(taskRef!, { agent }, coreOptions);
166
- } else if (intent === 'commit-abort') {
167
- const missing = requireValues(['--token', '--expected-head']);
168
- if (missing) { usageFailure(`intent 'commit-abort' requires '${missing}'`); return; }
169
- result = abortCommitIntent(taskRef!, { token: values['--token']!, expectedHead: values['--expected-head']! }, coreOptions);
170
- } else if (intent === 'commit-terminate') {
171
- const missing = requireValues(['--attempt', '--agent', '--code']);
172
- if (missing) { usageFailure(`intent 'commit-terminate' requires '${missing}'`); return; }
173
- const agent = normalizeAgentToken(values['--agent']!);
174
- if (!agent) { usageFailure(`invalid --agent '${values['--agent']}': ${AGENT_USAGE_HINT}`); return; }
175
- if (!/^[A-Z][A-Z0-9_]{2,63}$/.test(values['--code']!)) {
176
- usageFailure('--code must be a stable uppercase identifier'); return;
177
- }
178
- result = terminateCommitAttempt(taskRef!, {
179
- attempt: values['--attempt']!, agent, code: values['--code']!
180
- }, coreOptions);
181
- } else if (intent === 'commit-status') {
182
- result = statusCommitIntent(taskRef!, coreOptions);
183
- } else if (intent === 'begin-or-resume') {
87
+ try {
88
+ if (intent === 'begin-or-resume') {
184
89
  const missing = requireValues(['--client']);
185
90
  if (missing) { usageFailure(`intent 'begin-or-resume' requires '${missing}'`); return; }
186
91
  const maxSteps = values['--max-steps'] === undefined ? undefined : Number(values['--max-steps']);
@@ -295,7 +200,19 @@ async function taskOrchestration(args: string[] = []): Promise<void> {
295
200
  if (!['true', 'false'].includes(values['--recoverable']!)) { usageFailure('--recoverable must be true or false'); return; }
296
201
  result = pauseOrchestration(taskRef!, values['--code']!, values['--message']!, values['--recoverable'] === 'true', coreOptions);
297
202
  }
298
- process.stdout.write(`${JSON.stringify(result)}\n`);
203
+ } catch (error) {
204
+ if (!(error instanceof OrchestrationStateError)) throw error;
205
+ result = {
206
+ status: 'failed' as const,
207
+ changed: false,
208
+ taskId: error.taskId,
209
+ run: null,
210
+ next: null,
211
+ error: { code: error.code, message: error.message }
212
+ };
213
+ }
214
+ const output = result;
215
+ process.stdout.write(`${JSON.stringify(output)}\n`);
299
216
  if (result.status === 'failed') process.exitCode = 1;
300
217
  }
301
218
 
@@ -11,12 +11,24 @@ import type { GitHubClient } from './github-client.ts';
11
11
  import { listRemoteComments, normalizeCommentContent, writeComment } from './issue-comments.ts';
12
12
  import { platformResult } from './types.ts';
13
13
  import type { PlatformResult } from './types.ts';
14
+ import type { OperationWarning } from '../task/operation-outcome.ts';
14
15
 
15
16
  type SummaryComment = { id: number; body: string };
16
17
  type SummaryContextResult = PlatformResult & {
17
18
  task: { id: string | null; prNumber: number | null };
18
19
  artifacts: Array<{ family: string; name: string; path: string }>;
19
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
+ }
20
32
 
21
33
  function summaryMarker(taskId: string): string {
22
34
  return `<!-- sync-pr:${taskId}:summary -->`;
@@ -61,20 +73,50 @@ function summaryContext(taskRef: string, options: { cwd?: string; client?: GitHu
61
73
  return { ...platformResult('no-op'), task: { id: resolved.taskId, prNumber }, artifacts: canonicalArtifacts(resolved.taskDir) };
62
74
  }
63
75
 
64
- 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
+ };
65
106
  const resolved = resolveTaskRef(taskRef, options.cwd ? { repoRoot: options.cwd } : {});
66
- 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 } }));
67
108
  const frontmatter = parseTaskFrontmatter(fs.readFileSync(resolved.taskMdPath, 'utf8'));
68
109
  const prNumber = Number(frontmatter.pr_number);
69
- 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;
70
112
  const client = options.client || createGitHubClient();
71
113
  const context = resolvePlatformContext({ cwd: resolved.repoRoot, client });
72
- 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);
73
115
  let headSha: string;
74
116
  try {
75
117
  headSha = execFileSync('git', ['rev-parse', 'HEAD'], { cwd: resolved.repoRoot, encoding: 'utf8' }).trim();
76
118
  } catch (error) {
77
- 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 } }));
78
120
  }
79
121
  const desired = buildPullRequestSummary(
80
122
  resolved.taskId,
@@ -83,15 +125,17 @@ function syncPullRequestSummary(taskRef: string, options: { agent: string; body:
83
125
  renderHumanOverrideAudit(fs.readFileSync(resolved.taskMdPath, 'utf8'))
84
126
  );
85
127
  const listed = listRemoteComments(client, context.platform.repository, prNumber, resolved.repoRoot);
86
- 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 }));
87
129
  const reconciliation = reconcileSummaryComment(listed.value, resolved.taskId, desired);
88
- 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 } });
89
- 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 });
90
- 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 }));
91
133
  const written = writeComment(client, context.platform.repository, prNumber, resolved.repoRoot, desired, reconciliation.commentId || undefined);
92
- 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 }));
93
135
  const id = Number(written.value.id || reconciliation.commentId);
94
- 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 }));
95
137
  }
96
138
 
97
139
  export { buildPullRequestSummary, reconcileSummaryComment, summaryContext, summaryMarker, syncPullRequestSummary };
140
+ export type { PullRequestSummaryResult, SummaryContextResult };
141
+ export { warningResultForPrimary };