@fitlab-ai/agent-infra 0.9.4 → 0.9.6

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 (130) hide show
  1. package/README.md +1 -1
  2. package/README.zh-CN.md +1 -1
  3. package/dist/bin/internal-cli.js +13 -3
  4. package/dist/lib/agent-clients/adapter.js +2 -2
  5. package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +492 -0
  6. package/dist/lib/agent-clients/adapters/codex-lifecycle/evidence.js +175 -0
  7. package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +255 -0
  8. package/dist/lib/agent-clients/adapters/codex.js +3 -3
  9. package/dist/lib/git/workflow.js +0 -1
  10. package/dist/lib/git/worktree-identity.js +64 -0
  11. package/dist/lib/internal/codex-lifecycle.js +300 -0
  12. package/dist/lib/internal/release-workflow.js +108 -11
  13. package/dist/lib/internal/sandbox-control.js +43 -6
  14. package/dist/lib/internal/task-create.js +56 -0
  15. package/dist/lib/internal/task-orchestration.js +71 -31
  16. package/dist/lib/platform/issue-comments.js +2 -1
  17. package/dist/lib/release/workflow.js +29 -21
  18. package/dist/lib/sandbox/commands/create.js +16 -16
  19. package/dist/lib/sandbox/commands/prune.js +40 -16
  20. package/dist/lib/sandbox/commands/rm.js +318 -89
  21. package/dist/lib/sandbox/control/client.js +152 -22
  22. package/dist/lib/sandbox/control/executor.js +136 -0
  23. package/dist/lib/sandbox/control/lifecycle.js +348 -0
  24. package/dist/lib/sandbox/control/protocol.js +73 -18
  25. package/dist/lib/sandbox/control/server.js +235 -80
  26. package/dist/lib/sandbox/control/state.js +181 -0
  27. package/dist/lib/sandbox/index.js +2 -2
  28. package/dist/lib/sandbox/managed-fs.js +14 -2
  29. package/dist/lib/sandbox/mounts.js +35 -24
  30. package/dist/lib/sandbox/recovery.js +262 -45
  31. package/dist/lib/sandbox/workspace-view.js +80 -11
  32. package/dist/lib/sandbox/worktree-safety.js +196 -0
  33. package/dist/lib/server/process-control.js +6 -0
  34. package/dist/lib/task/activity-log.js +32 -5
  35. package/dist/lib/task/artifact-lifecycle.js +5 -4
  36. package/dist/lib/task/codex-orchestration.js +161 -0
  37. package/dist/lib/task/commands/status.js +3 -0
  38. package/dist/lib/task/commands/validate.js +289 -0
  39. package/dist/lib/task/commit-finalization.js +50 -12
  40. package/dist/lib/task/create-service.js +137 -0
  41. package/dist/lib/task/create.js +333 -0
  42. package/dist/lib/task/delegation-receipts.js +25 -0
  43. package/dist/lib/task/events.js +10 -4
  44. package/dist/lib/task/finalization-artifacts.js +2 -1
  45. package/dist/lib/task/implementation-inputs.js +1 -7
  46. package/dist/lib/task/index.js +9 -0
  47. package/dist/lib/task/orchestration.js +283 -18
  48. package/dist/lib/task/review-fingerprint.js +4 -0
  49. package/dist/lib/task/verification-engine.js +38 -6
  50. package/dist/lib/task/verification.js +1 -0
  51. package/dist/lib/task/workspace-snapshot.js +78 -3
  52. package/lib/agent-clients/adapter.ts +4 -4
  53. package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +569 -0
  54. package/lib/agent-clients/adapters/codex-lifecycle/evidence.ts +323 -0
  55. package/lib/agent-clients/adapters/codex-lifecycle/store.ts +298 -0
  56. package/lib/agent-clients/adapters/codex.ts +3 -3
  57. package/lib/git/workflow.ts +0 -1
  58. package/lib/git/worktree-identity.ts +86 -0
  59. package/lib/internal/codex-lifecycle.ts +308 -0
  60. package/lib/internal/release-workflow.ts +107 -11
  61. package/lib/internal/sandbox-control.ts +38 -6
  62. package/lib/internal/task-create.ts +61 -0
  63. package/lib/internal/task-orchestration.ts +57 -31
  64. package/lib/platform/issue-comments.ts +2 -1
  65. package/lib/release/workflow.ts +69 -20
  66. package/lib/sandbox/commands/create.ts +18 -21
  67. package/lib/sandbox/commands/prune.ts +53 -15
  68. package/lib/sandbox/commands/rm.ts +368 -103
  69. package/lib/sandbox/control/client.ts +173 -25
  70. package/lib/sandbox/control/executor.ts +163 -0
  71. package/lib/sandbox/control/lifecycle.ts +342 -0
  72. package/lib/sandbox/control/protocol.ts +110 -48
  73. package/lib/sandbox/control/server.ts +252 -85
  74. package/lib/sandbox/control/state.ts +196 -0
  75. package/lib/sandbox/index.ts +2 -2
  76. package/lib/sandbox/managed-fs.ts +17 -2
  77. package/lib/sandbox/mounts.ts +31 -13
  78. package/lib/sandbox/recovery.ts +301 -60
  79. package/lib/sandbox/workspace-view.ts +89 -12
  80. package/lib/sandbox/worktree-safety.ts +234 -0
  81. package/lib/server/process-control.ts +11 -0
  82. package/lib/task/activity-log.ts +46 -7
  83. package/lib/task/artifact-lifecycle.ts +5 -4
  84. package/lib/task/codex-orchestration.ts +233 -0
  85. package/lib/task/commands/status.ts +2 -0
  86. package/lib/task/commands/validate.ts +282 -0
  87. package/lib/task/commit-finalization.ts +75 -12
  88. package/lib/task/create-service.ts +171 -0
  89. package/lib/task/create.ts +372 -0
  90. package/lib/task/delegation-receipts.ts +52 -3
  91. package/lib/task/events.ts +9 -4
  92. package/lib/task/finalization-artifacts.ts +2 -1
  93. package/lib/task/implementation-inputs.ts +1 -5
  94. package/lib/task/index.ts +8 -0
  95. package/lib/task/orchestration.ts +381 -22
  96. package/lib/task/review-fingerprint.ts +9 -0
  97. package/lib/task/verification-engine.ts +42 -8
  98. package/lib/task/verification.ts +2 -1
  99. package/lib/task/workspace-snapshot.ts +98 -4
  100. package/package.json +7 -7
  101. package/templates/.agents/hooks/lifecycle-delegation.js +88 -8
  102. package/templates/.agents/rules/lifecycle-orchestration.en.md +1 -1
  103. package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +1 -1
  104. package/templates/.agents/rules/no-mid-flow-questions.en.md +1 -0
  105. package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +1 -0
  106. package/templates/.agents/rules/task-management.en.md +1 -0
  107. package/templates/.agents/rules/task-management.zh-CN.md +1 -0
  108. package/templates/.agents/skills/commit/SKILL.en.md +3 -9
  109. package/templates/.agents/skills/commit/SKILL.zh-CN.md +6 -10
  110. package/templates/.agents/skills/commit/reference/commit-orchestration.en.md +4 -3
  111. package/templates/.agents/skills/commit/reference/commit-orchestration.zh-CN.md +5 -4
  112. package/templates/.agents/skills/create-task/SKILL.en.md +26 -43
  113. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +26 -43
  114. package/templates/.agents/skills/post-release/SKILL.en.md +32 -6
  115. package/templates/.agents/skills/post-release/SKILL.zh-CN.md +32 -6
  116. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +42 -0
  117. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +42 -0
  118. package/templates/.agents/skills/run-manual-validation/config/verify.en.json +14 -0
  119. package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +14 -0
  120. package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +44 -0
  121. package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +44 -0
  122. package/templates/.agents/skills/run-task/SKILL.en.md +2 -2
  123. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +2 -2
  124. package/templates/.agents/skills/run-task/reference/host-validation.en.md +3 -3
  125. package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +3 -3
  126. package/templates/.claude/commands/run-manual-validation.en.md +9 -0
  127. package/templates/.claude/commands/run-manual-validation.zh-CN.md +9 -0
  128. package/templates/.codex/hooks.json +50 -0
  129. package/templates/.opencode/commands/run-manual-validation.en.md +11 -0
  130. package/templates/.opencode/commands/run-manual-validation.zh-CN.md +11 -0
@@ -2,15 +2,22 @@ import { execFileSync, spawnSync } from 'node:child_process';
2
2
  import fs from 'node:fs';
3
3
  import path from 'node:path';
4
4
 
5
- import { locateActivityLog, appendActivityEntry, pairEntries } from './activity-log.ts';
5
+ import {
6
+ locateActivityLog,
7
+ appendActivityEntry,
8
+ pairEntries,
9
+ parseCommitAttemptStarted
10
+ } from './activity-log.ts';
11
+ import type { CommitAttempt } from './activity-log.ts';
6
12
  import { commitIntentPath, digest, readCommitIntent } from './commit-intent.ts';
7
13
  import type { CommitIntent } from './commit-intent.ts';
8
14
  import { parseTypedTaskFrontmatter } from './frontmatter.ts';
9
- import { parseReviewSummary } from './review-artifacts.ts';
15
+ import { artifactName, maxRound, parseReviewSummary } from './review-artifacts.ts';
10
16
  import {
11
17
  extractReviewBaseline,
12
18
  extractReviewedSnapshotTree,
13
- findAuthoritativeReviewCodeArtifact
19
+ findAuthoritativeReviewCodeArtifact,
20
+ parseReviewedGitTree
14
21
  } from './review-fingerprint.ts';
15
22
  import type { TaskMutation } from './write.ts';
16
23
 
@@ -20,6 +27,7 @@ type CommitFinalizationDisposition =
20
27
  | 'recoverable'
21
28
  | 'invalid'
22
29
  | 'conflict'
30
+ | 'retryable-start'
23
31
  | 'orphaned-start';
24
32
 
25
33
  type CommitFinalizationCode =
@@ -35,10 +43,12 @@ type CommitFinalizationInspection = Readonly<{
35
43
  currentHead: string;
36
44
  committedHead: string | null;
37
45
  commitNote: string | null;
46
+ activityNote: string | null;
38
47
  needsAnchor: boolean;
39
48
  needsLog: boolean;
40
49
  intent: CommitIntent | null;
41
50
  intentDigest: string | null;
51
+ attempt: CommitAttempt | null;
42
52
  }>;
43
53
 
44
54
  type CreatePrCommitGate = Readonly<{
@@ -78,6 +88,28 @@ function isAncestor(repoRoot: string, ancestor: string, descendant: string): boo
78
88
  }).status === 0;
79
89
  }
80
90
 
91
+ function hasMatchingApprovedReview(
92
+ taskDir: string,
93
+ beforeRound: number,
94
+ baseline: string,
95
+ tree: string
96
+ ): boolean {
97
+ const entries = fs.readdirSync(taskDir);
98
+ for (let round = Math.min(beforeRound - 1, maxRound(entries, 'review-code')); round >= 1; round -= 1) {
99
+ const file = path.join(taskDir, artifactName('review-code', round));
100
+ if (!fs.existsSync(file)) continue;
101
+ const content = fs.readFileSync(file, 'utf8');
102
+ const summary = parseReviewSummary(content);
103
+ if (
104
+ summary.ok
105
+ && summary.summary.verdict === 'Approved'
106
+ && extractReviewBaseline(content) === baseline
107
+ && extractReviewedSnapshotTree(content) === tree
108
+ ) return true;
109
+ }
110
+ return false;
111
+ }
112
+
81
113
  function outcome(
82
114
  disposition: CommitFinalizationDisposition,
83
115
  overrides: Partial<CommitFinalizationInspection> = {}
@@ -89,10 +121,12 @@ function outcome(
89
121
  currentHead: '',
90
122
  committedHead: null,
91
123
  commitNote: null,
124
+ activityNote: null,
92
125
  needsAnchor: false,
93
126
  needsLog: false,
94
127
  intent: null,
95
128
  intentDigest: null,
129
+ attempt: null,
96
130
  ...overrides
97
131
  };
98
132
  }
@@ -128,6 +162,16 @@ function inspectCommitFinalization(
128
162
  const openRows = commitRows.filter((row) => row.started !== '' && row.done === '');
129
163
  const file = commitIntentPath(taskDir);
130
164
  if (!fs.existsSync(file)) {
165
+ if (openRows.length === 1) {
166
+ const attempt = parseCommitAttemptStarted(openRows[0]!.note);
167
+ if (attempt && attempt.baseline === currentHead) {
168
+ return outcome('retryable-start', {
169
+ message: 'a structured Commit attempt can be safely reused',
170
+ currentHead,
171
+ attempt
172
+ });
173
+ }
174
+ }
131
175
  if (openRows.length > 0) {
132
176
  return outcome('orphaned-start', {
133
177
  code: 'COMMIT_FINALIZATION_EVIDENCE_MISSING',
@@ -192,6 +236,12 @@ function inspectCommitFinalization(
192
236
  });
193
237
  }
194
238
 
239
+ const commitNote = git(repoRoot, ['show', '-s', '--format=%h%x20%s', intent.committedHead]);
240
+ const matchingDone = commitRows.filter((row) => row.done !== '' && row.note === commitNote);
241
+ const pushOnlyRetry = intent.phase === 'pushed'
242
+ && intent.baselineHead === intent.committedHead
243
+ && matchingDone.length === 1
244
+ && openRows.length === 1;
195
245
  const review = findAuthoritativeReviewCodeArtifact(taskDir);
196
246
  let needsAnchor = false;
197
247
  if (review.ok && review.path) {
@@ -204,11 +254,20 @@ function inspectCommitFinalization(
204
254
  const summary = parseReviewSummary(reviewContent);
205
255
  const reviewBaseline = extractReviewBaseline(reviewContent);
206
256
  const reviewTree = extractReviewedSnapshotTree(reviewContent);
207
- if (!summary.ok || summary.summary.verdict !== 'Approved' || !commitExists(repoRoot, reviewBaseline) || !SHA_RE.test(reviewTree)) {
257
+ if (!summary.ok || summary.summary.verdict !== 'Approved' || !commitExists(repoRoot, reviewBaseline) || !parseReviewedGitTree(reviewTree)) {
208
258
  return invalid('authoritative review-code evidence is not a valid approved snapshot', currentHead, 'COMMIT_FINALIZATION_EVIDENCE_INVALID', shared);
209
259
  }
210
260
  const committedTree = git(repoRoot, ['rev-parse', `${intent.committedHead}^{tree}`]);
211
- if (reviewBaseline !== intent.baselineHead || reviewTree !== committedTree) {
261
+ const matchingCurrentReview = reviewBaseline === intent.baselineHead && reviewTree === committedTree;
262
+ if (
263
+ !matchingCurrentReview
264
+ && !hasMatchingApprovedReview(
265
+ taskDir,
266
+ review.round,
267
+ intent.baselineHead,
268
+ committedTree
269
+ )
270
+ ) {
212
271
  return outcome('conflict', {
213
272
  ...shared,
214
273
  code: 'COMMIT_FINALIZATION_CONFLICT',
@@ -217,18 +276,16 @@ function inspectCommitFinalization(
217
276
  }
218
277
 
219
278
  const anchor = String(metadata.last_reviewed_commit ?? '').trim();
220
- if (anchor && anchor !== intent.committedHead) {
279
+ if (anchor && anchor !== intent.committedHead && anchor !== intent.baselineHead) {
221
280
  return outcome('conflict', {
222
281
  ...shared,
223
282
  code: 'COMMIT_FINALIZATION_CONFLICT',
224
283
  message: 'task review anchor conflicts with the committed intent'
225
284
  });
226
285
  }
227
- needsAnchor = anchor === '';
286
+ needsAnchor = anchor !== intent.committedHead;
228
287
  }
229
- const commitNote = git(repoRoot, ['show', '-s', '--format=%h%x20%s', intent.committedHead]);
230
- const matchingDone = commitRows.filter((row) => row.done !== '' && row.note === commitNote);
231
- const needsLog = matchingDone.length === 0 && openRows.length === 1;
288
+ const needsLog = (matchingDone.length === 0 && openRows.length === 1) || pushOnlyRetry;
232
289
  if (!(needsLog || (matchingDone.length === 1 && openRows.length === 0))) {
233
290
  return outcome('conflict', {
234
291
  ...shared,
@@ -241,6 +298,7 @@ function inspectCommitFinalization(
241
298
  ...shared,
242
299
  message: 'commit finalization can be recovered',
243
300
  commitNote,
301
+ activityNote: pushOnlyRetry ? `Pushed ${commitNote}` : commitNote,
244
302
  needsAnchor,
245
303
  needsLog
246
304
  });
@@ -252,7 +310,12 @@ function planCommitTaskFinalization(
252
310
  agent: string,
253
311
  timestamp: string
254
312
  ): CommitTaskFinalizationPlan {
255
- if (inspection.disposition !== 'recoverable' || !inspection.committedHead || !inspection.commitNote) {
313
+ if (
314
+ inspection.disposition !== 'recoverable'
315
+ || !inspection.committedHead
316
+ || !inspection.commitNote
317
+ || !inspection.activityNote
318
+ ) {
256
319
  throw new Error('commit finalization task mutations require a recoverable inspection');
257
320
  }
258
321
  const taskContent = fs.readFileSync(path.join(taskDir, 'task.md'), 'utf8');
@@ -273,7 +336,7 @@ function planCommitTaskFinalization(
273
336
  time: timestamp,
274
337
  step: 'Commit',
275
338
  agent,
276
- note: inspection.commitNote
339
+ note: inspection.activityNote
277
340
  })
278
341
  });
279
342
  }
@@ -0,0 +1,171 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+
4
+ import { syncPlatformComment } from '../platform/issue-comments.ts';
5
+ import { createPlatformIssue, syncPlatformIssue } from '../platform/issues.ts';
6
+ import type { PlatformResult } from '../platform/types.ts';
7
+ import { VERSION } from '../version.ts';
8
+ import { applyWorkflowWarningIntent } from './workflow-warning-intents.ts';
9
+ import { createLocalTask, type TaskCreateCandidateV1 } from './create.ts';
10
+ import { verifyTaskEvent } from './verification.ts';
11
+
12
+ type TaskCreateStatus = 'applied' | 'no-op' | 'degraded' | 'failed' | 'blocked';
13
+ type TaskCreateOperation = { name: string; status: string; reasonCode: string | null };
14
+ type TaskCreateWarning = { code: string; severity: string; action: string };
15
+ type TaskCreateResult = Readonly<{
16
+ status: TaskCreateStatus;
17
+ changed: boolean;
18
+ task: { id: string | null; shortId: string | null };
19
+ issue: { number: number; url: string } | null;
20
+ operations: readonly TaskCreateOperation[];
21
+ warnings: readonly TaskCreateWarning[];
22
+ error: { code: string; message: string; retryable: boolean } | null;
23
+ }>;
24
+
25
+ type CreateTaskOptions = Readonly<{
26
+ repoRoot: string;
27
+ agentInfraVersion?: string;
28
+ dependencies?: Partial<TaskCreateDependencies>;
29
+ }>;
30
+
31
+ type TaskCreateDependencies = Readonly<{
32
+ createIssue: typeof createPlatformIssue;
33
+ syncIssue: typeof syncPlatformIssue;
34
+ syncComment: typeof syncPlatformComment;
35
+ addWarning: typeof applyWorkflowWarningIntent;
36
+ }>;
37
+
38
+ const DEFAULT_DEPENDENCIES: TaskCreateDependencies = {
39
+ createIssue: createPlatformIssue,
40
+ syncIssue: syncPlatformIssue,
41
+ syncComment: syncPlatformComment,
42
+ addWarning: applyWorkflowWarningIntent
43
+ };
44
+
45
+ function verifyCreatedTask(repoRoot: string, taskId: string): { operation: TaskCreateOperation; error: TaskCreateResult['error'] } {
46
+ const verification = verifyTaskEvent(
47
+ { taskRef: taskId, event: 'create-task.completed' },
48
+ { repoRoot }
49
+ );
50
+ if (verification.status === 'pass') {
51
+ return { operation: { name: 'task:verify', status: 'pass', reasonCode: null }, error: null };
52
+ }
53
+ const blocked = verification.status === 'blocked';
54
+ return {
55
+ operation: {
56
+ name: 'task:verify', status: verification.status,
57
+ reasonCode: verification.error?.code ?? 'TASK_CREATE_VERIFICATION_FAILED'
58
+ },
59
+ error: {
60
+ code: verification.error?.code ?? 'TASK_CREATE_VERIFICATION_FAILED',
61
+ message: `Created task did not pass the ${verification.event} gate`,
62
+ retryable: blocked
63
+ }
64
+ };
65
+ }
66
+
67
+ function platformOperations(prefix: string, operations: readonly { name: string; status: string; reasonCode?: string | null }[]): TaskCreateOperation[] {
68
+ return operations.map((operation) => ({
69
+ name: `${prefix}:${operation.name}`,
70
+ status: operation.status,
71
+ reasonCode: operation.reasonCode ?? null
72
+ }));
73
+ }
74
+
75
+ function issueFromTask(repoRoot: string, taskId: string): { number: number; url: string } | null {
76
+ const content = fs.readFileSync(path.join(repoRoot, '.agents', 'workspace', 'active', taskId, 'task.md'), 'utf8');
77
+ const match = /^issue_number:\s*(\d+)\s*$/m.exec(content);
78
+ return match ? { number: Number(match[1]), url: '' } : null;
79
+ }
80
+
81
+ function createTask(value: unknown, options: CreateTaskOptions): TaskCreateResult {
82
+ const dependencies = { ...DEFAULT_DEPENDENCIES, ...options.dependencies };
83
+ let candidate: TaskCreateCandidateV1;
84
+ let local;
85
+ try {
86
+ candidate = value as TaskCreateCandidateV1;
87
+ local = createLocalTask(candidate, {
88
+ repoRoot: options.repoRoot,
89
+ agentInfraVersion: options.agentInfraVersion ?? VERSION
90
+ });
91
+ } catch (error) {
92
+ const message = error instanceof Error ? error.message : String(error);
93
+ const code = /^([A-Z][A-Z0-9_]+)/.exec(message)?.[1] ?? 'TASK_CREATE_FAILED';
94
+ return {
95
+ status: 'failed', changed: false, task: { id: null, shortId: null }, issue: null,
96
+ operations: [], warnings: [], error: { code, message, retryable: code === 'TASK_CREATE_LOCK_TIMEOUT' }
97
+ };
98
+ }
99
+
100
+ const operations: TaskCreateOperation[] = [{
101
+ name: 'task:local', status: local.status, reasonCode: null
102
+ }];
103
+ let issue = issueFromTask(options.repoRoot, local.task.id);
104
+ const created = dependencies.createIssue(local.task.id, { cwd: options.repoRoot, agent: candidate.agent });
105
+ operations.push(...platformOperations('platform-create', created.operations));
106
+ if (created.issue) issue = { number: created.issue.number, url: created.issue.url };
107
+
108
+ let platformFailure: PlatformResult | null = created.status === 'failed' || created.status === 'blocked' ? created : null;
109
+ if (!platformFailure && (created.task.issueNumber || issue)) {
110
+ const synced = dependencies.syncIssue(local.task.id, {
111
+ cwd: options.repoRoot,
112
+ agent: candidate.agent,
113
+ status: 'waiting-for-triage',
114
+ assignees: 'current',
115
+ milestone: 'initial',
116
+ issueType: true,
117
+ fields: true
118
+ });
119
+ operations.push(...platformOperations('platform-sync', synced.operations));
120
+ if (synced.issue) issue = { number: synced.issue.number, url: synced.issue.url };
121
+ if (synced.status === 'failed' || synced.status === 'blocked') platformFailure = synced;
122
+ if (!platformFailure) {
123
+ const commented = dependencies.syncComment(local.task.id, { cwd: options.repoRoot, kind: 'task', agent: candidate.agent });
124
+ operations.push(...platformOperations('platform-comment', commented.operations));
125
+ if (commented.status === 'failed' || commented.status === 'blocked') platformFailure = commented;
126
+ }
127
+ }
128
+
129
+ if (platformFailure) {
130
+ const error = platformFailure.error ?? { code: 'ISSUE_CREATE_FAILED', message: 'Platform operation failed', retryable: false };
131
+ dependencies.addWarning({
132
+ kind: 'add', taskRef: local.task.id, step: 'create-task', severity: 'ACTION_REQUIRED',
133
+ code: 'ISSUE_CREATE_FAILED', target: 'issue', message: `${error.code}: ${error.message}`,
134
+ action: 'Fix platform authentication or connectivity, then retry Issue creation for this task.'
135
+ }, { repoRoot: options.repoRoot });
136
+ const verified = verifyCreatedTask(options.repoRoot, local.task.id);
137
+ operations.push(verified.operation);
138
+ if (verified.error) return {
139
+ status: verified.error.retryable ? 'blocked' : 'failed', changed: local.changed,
140
+ task: local.task, issue, operations, warnings: [], error: verified.error
141
+ };
142
+ return {
143
+ status: platformFailure.status === 'blocked' ? 'blocked' : 'degraded',
144
+ changed: local.changed,
145
+ task: local.task,
146
+ issue,
147
+ operations,
148
+ warnings: [{ code: 'ISSUE_CREATE_FAILED', severity: 'ACTION_REQUIRED', action: 'Retry Issue creation after fixing the platform failure.' }],
149
+ error
150
+ };
151
+ }
152
+
153
+ const verified = verifyCreatedTask(options.repoRoot, local.task.id);
154
+ operations.push(verified.operation);
155
+ if (verified.error) return {
156
+ status: verified.error.retryable ? 'blocked' : 'failed', changed: local.changed,
157
+ task: local.task, issue, operations, warnings: [], error: verified.error
158
+ };
159
+ return {
160
+ status: local.status,
161
+ changed: local.changed,
162
+ task: local.task,
163
+ issue,
164
+ operations,
165
+ warnings: [],
166
+ error: null
167
+ };
168
+ }
169
+
170
+ export { createTask };
171
+ export type { CreateTaskOptions, TaskCreateDependencies, TaskCreateOperation, TaskCreateResult, TaskCreateStatus, TaskCreateWarning };