@fitlab-ai/agent-infra 0.11.8 → 0.11.10

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 (163) hide show
  1. package/README.md +1 -1
  2. package/README.zh-CN.md +1 -1
  3. package/dist/bin/internal-cli.js +0 -6
  4. package/dist/lib/agent-clients/custom-tool-invocations.js +92 -0
  5. package/dist/lib/agent-clients/next-steps.js +1 -1
  6. package/dist/lib/agent-clients/reconcile.js +3 -3
  7. package/dist/lib/agent-clients/schema.js +18 -10
  8. package/dist/lib/git/workflow.js +1 -1
  9. package/dist/lib/init.js +2 -2
  10. package/dist/lib/internal/agent-client.js +3 -3
  11. package/dist/lib/internal/cli-route-inventory.js +0 -3
  12. package/dist/lib/internal/sandbox-control.js +30 -5
  13. package/dist/lib/internal/task-finalization.js +14 -23
  14. package/dist/lib/internal/task-operation-registry.js +2 -3
  15. package/dist/lib/internal/task-platform-recovery.js +9 -4
  16. package/dist/lib/platform/completion-backfill.js +2 -1
  17. package/dist/lib/platform/context.js +4 -2
  18. package/dist/lib/platform/github-client.js +20 -17
  19. package/dist/lib/platform/github-provider.js +42 -22
  20. package/dist/lib/platform/github-release-notes.js +4 -2
  21. package/dist/lib/platform/in-label-sync.js +2 -1
  22. package/dist/lib/platform/issue-comments.js +13 -9
  23. package/dist/lib/platform/operation-coordinator.js +1 -1
  24. package/dist/lib/platform/pr-review.js +2 -1
  25. package/dist/lib/platform/provider-bridge.js +4 -5
  26. package/dist/lib/platform/provider-contract.d.ts +3 -6
  27. package/dist/lib/platform/provider-contract.js +3 -15
  28. package/dist/lib/platform/provider-validation.js +9 -4
  29. package/dist/lib/platform/pull-requests.js +4 -2
  30. package/dist/lib/sandbox/control/client.js +54 -6
  31. package/dist/lib/sandbox/control/executor.js +8 -3
  32. package/dist/lib/sandbox/recovery.js +1 -22
  33. package/dist/lib/sandbox/removal.js +0 -1
  34. package/dist/lib/sandbox/tools.js +2 -0
  35. package/dist/lib/task/artifact-command.js +11 -1
  36. package/dist/lib/task/artifact-lifecycle.js +119 -62
  37. package/dist/lib/task/artifact-selection.js +1 -1
  38. package/dist/lib/task/capabilities.js +55 -21
  39. package/dist/lib/task/commands/status.js +0 -27
  40. package/dist/lib/task/commit-identity.js +22 -7
  41. package/dist/lib/task/commit-intent.js +67 -26
  42. package/dist/lib/task/commit-operation.js +19 -8
  43. package/dist/lib/task/completion-facts.js +32 -10
  44. package/dist/lib/task/control-authority.js +2 -4
  45. package/dist/lib/task/current-contract.js +1 -5
  46. package/dist/lib/task/events.js +8 -274
  47. package/dist/lib/task/execution-inputs.js +14 -0
  48. package/dist/lib/task/finalization-envelope.js +4 -0
  49. package/dist/lib/task/finalization.js +100 -129
  50. package/dist/lib/task/frontmatter.js +1 -1
  51. package/dist/lib/task/ledger-intents.js +4 -7
  52. package/dist/lib/task/orchestration.js +11 -10
  53. package/dist/lib/task/platform-operation-journal.js +17 -10
  54. package/dist/lib/task/platform-operation-recovery.js +95 -28
  55. package/dist/lib/task/review-fingerprint.js +4 -26
  56. package/dist/lib/task/verification-engine.js +95 -6
  57. package/dist/lib/task/verification.js +0 -1
  58. package/dist/lib/task/write.js +0 -11
  59. package/dist/lib/update.js +2 -2
  60. package/dist/lifecycle-build-manifest.json +3 -3
  61. package/lib/agent-clients/custom-tool-invocations.ts +129 -0
  62. package/lib/agent-clients/next-steps.ts +3 -3
  63. package/lib/agent-clients/reconcile.ts +3 -3
  64. package/lib/agent-clients/schema.ts +18 -10
  65. package/lib/git/workflow.ts +1 -1
  66. package/lib/init.ts +2 -2
  67. package/lib/internal/agent-client.ts +3 -3
  68. package/lib/internal/cli-route-inventory.ts +0 -2
  69. package/lib/internal/sandbox-control.ts +39 -6
  70. package/lib/internal/task-finalization.ts +16 -24
  71. package/lib/internal/task-operation-registry.ts +2 -3
  72. package/lib/internal/task-platform-recovery.ts +9 -4
  73. package/lib/platform/completion-backfill.ts +2 -1
  74. package/lib/platform/context.ts +8 -4
  75. package/lib/platform/github-client.ts +24 -20
  76. package/lib/platform/github-provider.ts +47 -26
  77. package/lib/platform/github-release-notes.ts +5 -3
  78. package/lib/platform/in-label-sync.ts +2 -1
  79. package/lib/platform/issue-comments.ts +14 -9
  80. package/lib/platform/operation-coordinator.ts +1 -1
  81. package/lib/platform/pr-review.ts +2 -1
  82. package/lib/platform/provider-bridge.ts +10 -9
  83. package/lib/platform/provider-contract.ts +6 -21
  84. package/lib/platform/provider-loader.ts +7 -6
  85. package/lib/platform/provider-validation.ts +30 -8
  86. package/lib/platform/pull-requests.ts +4 -2
  87. package/lib/platform/repository-metadata.ts +3 -2
  88. package/lib/platform/security-alerts.ts +1 -1
  89. package/lib/platform/types.ts +2 -0
  90. package/lib/sandbox/control/client.ts +57 -6
  91. package/lib/sandbox/control/executor.ts +8 -3
  92. package/lib/sandbox/recovery.ts +0 -25
  93. package/lib/sandbox/removal.ts +2 -10
  94. package/lib/sandbox/tool-types.ts +1 -0
  95. package/lib/sandbox/tools.ts +2 -0
  96. package/lib/task/artifact-command.ts +10 -1
  97. package/lib/task/artifact-lifecycle.ts +100 -52
  98. package/lib/task/artifact-selection.ts +1 -1
  99. package/lib/task/capabilities.ts +47 -24
  100. package/lib/task/commands/status.ts +0 -31
  101. package/lib/task/commit-identity.ts +24 -7
  102. package/lib/task/commit-intent.ts +58 -24
  103. package/lib/task/commit-operation.ts +17 -8
  104. package/lib/task/completion-facts.ts +31 -9
  105. package/lib/task/control-authority.ts +1 -4
  106. package/lib/task/current-contract.ts +1 -6
  107. package/lib/task/events.ts +8 -247
  108. package/lib/task/execution-inputs.ts +25 -0
  109. package/lib/task/finalization-envelope.ts +21 -0
  110. package/lib/task/finalization.ts +101 -123
  111. package/lib/task/frontmatter.ts +1 -1
  112. package/lib/task/ledger-intents.ts +4 -7
  113. package/lib/task/orchestration.ts +11 -17
  114. package/lib/task/platform-operation-journal.ts +18 -11
  115. package/lib/task/platform-operation-recovery.ts +90 -31
  116. package/lib/task/review-fingerprint.ts +4 -26
  117. package/lib/task/verification-engine.ts +103 -9
  118. package/lib/task/verification.ts +1 -2
  119. package/lib/task/write.ts +0 -19
  120. package/lib/update.ts +2 -3
  121. package/package.json +1 -1
  122. package/templates/.agents/README.en.md +2 -50
  123. package/templates/.agents/README.zh-CN.md +2 -50
  124. package/templates/.agents/rules/commit-and-pr.en.md +5 -5
  125. package/templates/.agents/rules/commit-and-pr.zh-CN.md +5 -5
  126. package/templates/.agents/rules/decision-qualification.en.md +1 -1
  127. package/templates/.agents/rules/decision-qualification.zh-CN.md +1 -1
  128. package/templates/.agents/rules/next-step-output.en.md +1 -1
  129. package/templates/.agents/rules/next-step-output.zh-CN.md +1 -1
  130. package/templates/.agents/scripts/lib/agent-infra-package.js +0 -7
  131. package/templates/.agents/skills/code-task/SKILL.en.md +25 -9
  132. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +26 -9
  133. package/templates/.agents/skills/code-task/reference/code-rules.en.md +1 -1
  134. package/templates/.agents/skills/code-task/reference/code-rules.zh-CN.md +1 -1
  135. package/templates/.agents/skills/code-task/reference/fix-mode.en.md +5 -16
  136. package/templates/.agents/skills/code-task/reference/fix-mode.zh-CN.md +5 -16
  137. package/templates/.agents/skills/code-task/reference/report-template.en.md +1 -1
  138. package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +1 -1
  139. package/templates/.agents/skills/commit/SKILL.en.md +1 -1
  140. package/templates/.agents/skills/commit/SKILL.zh-CN.md +1 -1
  141. package/templates/.agents/skills/complete-task/SKILL.en.md +15 -12
  142. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +11 -10
  143. package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +1 -1
  144. package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +1 -1
  145. package/templates/.agents/skills/review-code/SKILL.en.md +1 -1
  146. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +1 -1
  147. package/templates/.agents/skills/review-code/reference/output-templates.en.md +15 -6
  148. package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +15 -6
  149. package/templates/.agents/skills/update-agent-infra/SKILL.en.md +2 -2
  150. package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +2 -2
  151. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +2 -268
  152. package/templates/.agents/templates/task.zh-CN.md +0 -12
  153. package/templates/.github/workflows/pr-label.yml +1 -2
  154. package/dist/lib/agent-clients/custom-tuis.js +0 -99
  155. package/dist/lib/internal/task-invalidation.js +0 -63
  156. package/dist/lib/task/intermediate-cleanup.js +0 -327
  157. package/dist/lib/task/invalidation-command.js +0 -88
  158. package/dist/lib/task/invalidation.js +0 -242
  159. package/lib/agent-clients/custom-tuis.ts +0 -149
  160. package/lib/internal/task-invalidation.ts +0 -41
  161. package/lib/task/intermediate-cleanup.ts +0 -393
  162. package/lib/task/invalidation-command.ts +0 -88
  163. package/lib/task/invalidation.ts +0 -313
@@ -21,7 +21,7 @@ import type {
21
21
  MutationReceipt,
22
22
  MilestoneInitialization,
23
23
  PlatformContextSnapshot,
24
- PlatformError,
24
+ ProviderError,
25
25
  ProviderOperationContext,
26
26
  PlatformProvider,
27
27
  PlatformProviderFactoryInput,
@@ -38,6 +38,33 @@ import type {
38
38
  } from './provider-contract.ts';
39
39
  import { resourceIdentityNumber, resourceIdentityString } from './resource-identity.ts';
40
40
  import { extractPullRequestFileNames, syncLabelDelta } from './in-label-sync.ts';
41
+ import type { PlatformError } from './types.ts';
42
+
43
+ function providerError(error: PlatformError | ProviderError): ProviderError {
44
+ return { code: error.code, message: error.message };
45
+ }
46
+
47
+ function publicProviderErrors(provider: PlatformProvider): PlatformProvider {
48
+ const sanitize = async (call: () => Promise<unknown>): Promise<unknown> => {
49
+ const result = await call();
50
+ if (!result || typeof result !== 'object' || (result as { ok?: unknown }).ok !== false) return result;
51
+ const envelope = result as { error?: PlatformError | ProviderError };
52
+ return envelope.error ? { ...(result as Record<string, unknown>), error: providerError(envelope.error) } : result;
53
+ };
54
+ const wrapped: PlatformProvider = { ...provider, context: { ...provider.context } };
55
+ wrapped.context.resolve = (input) => sanitize(() => provider.context.resolve(input)) as ReturnType<PlatformProvider['context']['resolve']>;
56
+ for (const groupName of ['issues', 'comments', 'changeRequests', 'checks', 'reviews', 'releases', 'securityAlerts', 'repositoryMetadata', 'verification'] as const) {
57
+ const group = provider[groupName];
58
+ if (!group) continue;
59
+ const target: Record<string, unknown> = { ...group };
60
+ for (const method of Object.keys(group)) {
61
+ const original = group[method as keyof typeof group] as (...args: never[]) => Promise<unknown>;
62
+ target[method] = (input: unknown) => sanitize(() => original(input as never));
63
+ }
64
+ (wrapped as unknown as Record<string, unknown>)[groupName] = target;
65
+ }
66
+ return wrapped;
67
+ }
41
68
 
42
69
  const CURRENT_USER_QUERY = 'query { viewer { login } }';
43
70
  const ISSUE_TYPES_QUERY = `query($owner:String!){organization(login:$owner){issueTypes(first:20){nodes{id name pinnedFields{__typename ... on IssueFieldSingleSelect{id name options{id name}} ... on IssueFieldDate{id name} ... on IssueFieldText{id name} ... on IssueFieldNumber{id name}}}}}}`;
@@ -105,7 +132,6 @@ function resolveGitHubChangeRequestGitEvidence({
105
132
  error: {
106
133
  code: 'PR_MERGE_EVIDENCE_SOURCE_UNAVAILABLE',
107
134
  message: 'Pull request repository identity is inconsistent',
108
- retryable: false
109
135
  }
110
136
  };
111
137
  }
@@ -127,7 +153,6 @@ function resolveGitHubChangeRequestGitEvidence({
127
153
  error: {
128
154
  code: 'PR_MERGE_EVIDENCE_SOURCE_UNAVAILABLE',
129
155
  message: 'Pull request Git refs are invalid',
130
- retryable: false
131
156
  }
132
157
  };
133
158
  }
@@ -145,7 +170,6 @@ function resolveGitHubChangeRequestGitEvidence({
145
170
  error: {
146
171
  code: 'PR_MERGE_EVIDENCE_SOURCE_UNAVAILABLE',
147
172
  message: `No GitHub remote can provide evidence for ${repository}`,
148
- retryable: false
149
173
  }
150
174
  };
151
175
  }
@@ -155,8 +179,8 @@ function resolveGitHubChangeRequestGitEvidence({
155
179
  };
156
180
  }
157
181
 
158
- function failure(error: { code: string; message: string; retryable: boolean }): ProviderResult<PlatformContextSnapshot> {
159
- return { ok: false, error };
182
+ function failure(error: PlatformError | ProviderError): ProviderResult<PlatformContextSnapshot> {
183
+ return { ok: false, error: providerError(error) };
160
184
  }
161
185
 
162
186
  function resolveContext(
@@ -170,19 +194,17 @@ function resolveContext(
170
194
  return failure({
171
195
  code: 'GH_CLI_VERSION_UNSUPPORTED',
172
196
  message: 'GitHub CLI ' + version.value + ' is unsupported; install gh >= ' + MINIMUM_GITHUB_CLI_VERSION,
173
- retryable: false
174
197
  });
175
198
  }
176
199
  const remote = input.gitRemote || defaultGitRemote(cwd);
177
200
  if (!remote) {
178
- return failure({ code: 'REMOTE_MISSING', message: 'Git origin remote is not configured', retryable: false });
201
+ return failure({ code: 'REMOTE_MISSING', message: 'Git origin remote is not configured' });
179
202
  }
180
203
  const ownerRepo = parseGitHubRemote(remote);
181
204
  if (!ownerRepo) {
182
205
  return failure({
183
206
  code: /github\.com/i.test(remote) ? 'REMOTE_INVALID' : 'PLATFORM_UNSUPPORTED',
184
207
  message: 'Unable to parse GitHub owner/repo from configured remote',
185
- retryable: false
186
208
  });
187
209
  }
188
210
  const repository = client.json(['api', 'repos/' + ownerRepo], { cwd });
@@ -192,7 +214,6 @@ function resolveContext(
192
214
  if (!upstream) return failure({
193
215
  code: 'UPSTREAM_UNRESOLVED',
194
216
  message: 'Unable to resolve the upstream repository',
195
- retryable: false
196
217
  });
197
218
  const user = client.json(['api', 'graphql', '-f', 'query=' + CURRENT_USER_QUERY], { cwd });
198
219
  if (!user.ok) return failure(user.error);
@@ -241,18 +262,18 @@ function repositoryHead(cwd: string): string {
241
262
 
242
263
  function verifyRemoteBranch(cwd: string, repositoryName: string, head: string):
243
264
  | { ok: true; value: string }
244
- | { ok: false; error: { code: string; message: string; retryable: boolean } } {
265
+ | { ok: false; error: ProviderError } {
245
266
  const wanted = expectedHeadRepository(repositoryName, head);
246
267
  if (!wanted.ref || !/^[A-Za-z0-9._/-]+$/.test(wanted.ref)) return {
247
268
  ok: false,
248
- error: { code: 'PR_HEAD_INVALID', message: 'Pull request head ref is invalid', retryable: false }
269
+ error: { code: 'PR_HEAD_INVALID', message: 'Pull request head ref is invalid' }
249
270
  };
250
271
  const remotes = configuredGitRemotes(cwd);
251
272
  const remote = remotes.find((item) => item.repository.toLowerCase() === wanted.repository.toLowerCase())
252
273
  ?? remotes.find((item) => item.name === 'origin');
253
274
  if (!remote) return {
254
275
  ok: false,
255
- error: { code: 'PR_REMOTE_BRANCH_MISSING', message: `No configured remote can verify ${wanted.repository}:${wanted.ref}`, retryable: false }
276
+ error: { code: 'PR_REMOTE_BRANCH_MISSING', message: `No configured remote can verify ${wanted.repository}:${wanted.ref}` }
256
277
  };
257
278
  try {
258
279
  const output = execFileSync('git', ['ls-remote', '--refs', remote.name, `refs/heads/${wanted.ref}`], {
@@ -262,19 +283,19 @@ function verifyRemoteBranch(cwd: string, repositoryName: string, head: string):
262
283
  const sha = match?.split(/\s+/)[0] || null;
263
284
  if (!sha || !/^[a-f0-9]{40}$/i.test(sha)) return {
264
285
  ok: false,
265
- error: { code: 'PR_REMOTE_BRANCH_MISSING', message: `Remote branch ${wanted.repository}:${wanted.ref} does not exist`, retryable: false }
286
+ error: { code: 'PR_REMOTE_BRANCH_MISSING', message: `Remote branch ${wanted.repository}:${wanted.ref} does not exist` }
266
287
  };
267
288
  return { ok: true, value: sha };
268
289
  } catch (error) {
269
290
  return {
270
291
  ok: false,
271
- error: { code: 'PR_REMOTE_BRANCH_UNAVAILABLE', message: error instanceof Error ? error.message : String(error), retryable: true }
292
+ error: { code: 'PR_REMOTE_BRANCH_UNAVAILABLE', message: error instanceof Error ? error.message : String(error) }
272
293
  };
273
294
  }
274
295
  }
275
296
 
276
297
  function invalid(code: string, message: string): ProviderResult<never> {
277
- return { ok: false, error: { code, message, retryable: false } };
298
+ return { ok: false, error: { code, message } };
278
299
  }
279
300
 
280
301
  function changeRequestSnapshot(value: any): ChangeRequestSnapshot {
@@ -346,8 +367,8 @@ function createReceipt(remoteId: string): ProviderResult<MutationReceipt> {
346
367
  return { ok: true, value: { changed: true, remoteId } };
347
368
  }
348
369
 
349
- function partialFailure<T>(response: { ok: false; error: PlatformError }, value: T): ProviderResult<T> {
350
- return { ok: false, error: response.error, value };
370
+ function partialFailure<T>(response: { ok: false; error: PlatformError | ProviderError }, value: T): ProviderResult<T> {
371
+ return { ok: false, error: providerError(response.error), value };
351
372
  }
352
373
 
353
374
  function labelReconciliation(
@@ -390,8 +411,8 @@ function syncLabels(
390
411
  status: 'blocked' as const,
391
412
  changed: true,
392
413
  error: synced.error
393
- ? { ...synced.error, code: 'IN_LABEL_SYNC_PARTIAL', message: `In-label synchronization is partial or unknown: ${synced.error.message}` }
394
- : { code: 'IN_LABEL_SYNC_PARTIAL', message: 'In-label synchronization is partial or unknown', retryable: true }
414
+ ? { ...providerError(synced.error), code: 'IN_LABEL_SYNC_PARTIAL', message: `In-label synchronization is partial or unknown: ${synced.error.message}` }
415
+ : { code: 'IN_LABEL_SYNC_PARTIAL', message: 'In-label synchronization is partial or unknown' }
395
416
  };
396
417
  return synced;
397
418
  }
@@ -642,13 +663,13 @@ function createGitHubOperations(client: GitHubClient): Pick<PlatformProvider, 'i
642
663
  try {
643
664
  localHead = repositoryHead(context.workingDirectory);
644
665
  } catch (error) {
645
- return { ok: false, error: { code: 'PR_LOCAL_HEAD_UNAVAILABLE', message: error instanceof Error ? error.message : String(error), retryable: false } };
666
+ return { ok: false, error: { code: 'PR_LOCAL_HEAD_UNAVAILABLE', message: error instanceof Error ? error.message : String(error) } };
646
667
  }
647
668
  const remote = verifyRemoteBranch(context.workingDirectory, repository(context), head);
648
669
  if (!remote.ok) return remote;
649
670
  if (remote.value !== localHead) return {
650
671
  ok: false,
651
- error: { code: 'PR_REMOTE_HEAD_MISMATCH', message: `Remote head ${remote.value} does not match local HEAD ${localHead}`, retryable: false }
672
+ error: { code: 'PR_REMOTE_HEAD_MISMATCH', message: `Remote head ${remote.value} does not match local HEAD ${localHead}` }
652
673
  };
653
674
  return { ok: true, value: { sha: localHead } };
654
675
  },
@@ -909,7 +930,7 @@ function createGitHubOperations(client: GitHubClient): Pick<PlatformProvider, 'i
909
930
  try {
910
931
  const tag = resourceIdentityString(release) || '';
911
932
  const result = module.publishGitHubReleaseNotes({ repository: repository(context), tag, title, notesFile }, { cwd: context.workingDirectory, client });
912
- if (result.status === 'failed' || result.status === 'blocked') return { ok: false, error: result.error || { code: 'RELEASE_NOTES_PUBLISH_FAILED', message: 'Release notes publish failed', retryable: false } };
933
+ if (result.status === 'failed' || result.status === 'blocked') return { ok: false, error: result.error ? providerError(result.error) : { code: 'RELEASE_NOTES_PUBLISH_FAILED', message: 'Release notes publish failed' } };
913
934
  return { ok: true, value: { changed: result.changed, remoteId: tag } };
914
935
  } finally {
915
936
  fs.rmSync(temporaryRoot, { recursive: true, force: true });
@@ -921,7 +942,7 @@ function createGitHubOperations(client: GitHubClient): Pick<PlatformProvider, 'i
921
942
  repository: repository(context), commitOids, branch, historyLimit, fromTime, toTime
922
943
  }, { cwd: context.workingDirectory, client });
923
944
  if (collected.status === 'failed' || collected.status === 'blocked' || !('pullRequests' in collected)) {
924
- return { ok: false, error: collected.error || { code: 'RELEASE_NOTES_COLLECTION_FAILED', message: 'Release notes could not be collected', retryable: false } };
945
+ return { ok: false, error: collected.error ? providerError(collected.error) : { code: 'RELEASE_NOTES_COLLECTION_FAILED', message: 'Release notes could not be collected' } };
925
946
  }
926
947
  const mergedPullRequests = collected.pullRequests.map((item: any) => ({
927
948
  id: String(item.number), identity: { kind: 'number' as const, value: Number(item.number) }, number: Number(item.number),
@@ -1107,7 +1128,7 @@ function createGitHubProvider(
1107
1128
  input: PlatformProviderFactoryInput,
1108
1129
  client: GitHubClient = createGitHubClient()
1109
1130
  ): PlatformProvider {
1110
- return {
1131
+ return publicProviderErrors({
1111
1132
  type: input.providerType,
1112
1133
  contractVersion: 2,
1113
1134
  identity: { issue: 'number', 'pull-request': 'number', comment: 'number', release: 'key' },
@@ -1117,7 +1138,7 @@ function createGitHubProvider(
1117
1138
  return resolveContext(client, contextInput);
1118
1139
  }
1119
1140
  }
1120
- };
1141
+ });
1121
1142
  }
1122
1143
 
1123
1144
  export {
@@ -1,5 +1,6 @@
1
1
  import type { GitHubClient } from './github-client.ts';
2
2
  import { createGitHubClient } from './github-client.ts';
3
+ import { providerErrorRetryable } from './provider-validation.ts';
3
4
 
4
5
  type GitHubActorInput = {
5
6
  name?: string | null;
@@ -43,13 +44,14 @@ function normalizePlatformAuthor(author: { login?: string | null } | null | unde
43
44
  };
44
45
  }
45
46
 
46
- function failure(error: { code: string; message: string; retryable: boolean }) {
47
+ function failure(error: { code: string; message: string; retryable?: boolean }) {
48
+ const retryable = error.retryable ?? providerErrorRetryable(error.code);
47
49
  return {
48
- status: error.retryable ? 'blocked' as const : 'failed' as const,
50
+ status: retryable ? 'blocked' as const : 'failed' as const,
49
51
  changed: false,
50
52
  operation: null,
51
53
  url: null,
52
- error
54
+ error: { code: error.code, message: error.message }
53
55
  };
54
56
  }
55
57
 
@@ -1,5 +1,6 @@
1
1
  import { computeInLabels } from './metadata-labels.ts';
2
2
  import { createHash } from 'node:crypto';
3
+ import { providerErrorRetryable } from './provider-validation.ts';
3
4
  import type { PlatformError } from './types.ts';
4
5
 
5
6
  type InLabelPlan = {
@@ -150,7 +151,7 @@ function syncLabelDelta(
150
151
  const delta = labelDelta(current, target, prefix);
151
152
  let changed = false;
152
153
  const failure = (error: PlatformError): LabelDeltaResult => {
153
- if (changed || error.retryable) {
154
+ if (changed || error.retryable || providerErrorRetryable(error.code)) {
154
155
  return {
155
156
  status: 'blocked',
156
157
  changed,
@@ -7,6 +7,7 @@ import { resolveTaskRef } from '../task/resolve-ref.ts';
7
7
  import { coordinatePlatformWrite } from './operation-coordinator.ts';
8
8
  import { resolvePlatformProviderContext } from './context.ts';
9
9
  import type { PlatformClient } from './context.ts';
10
+ import { providerErrorRetryable } from './provider-validation.ts';
10
11
  import { platformResult } from './types.ts';
11
12
  import type { PlatformOperation, PlatformResult } from './types.ts';
12
13
  import {
@@ -34,6 +35,10 @@ import {
34
35
  } from './comment-safety.ts';
35
36
  import type { FenceRange } from './comment-safety.ts';
36
37
 
38
+ function isRetryable(error: { code: string; retryable?: boolean }): boolean {
39
+ return error.retryable ?? providerErrorRetryable(error.code);
40
+ }
41
+
37
42
  type RemoteComment = {
38
43
  id: number | string;
39
44
  body: string;
@@ -578,7 +583,7 @@ async function syncPlatformCommentImpl(taskRef: string, options: SyncOptions): P
578
583
  const issue = resourceIdentityNumber(issueIdentityFromTask);
579
584
  const listed = await listedComments(loaded.value.provider, loaded.value, issueIdentityFromTask);
580
585
  if (!listed.ok) {
581
- return platformResult(listed.error.retryable ? 'blocked' : 'failed', {
586
+ return platformResult(isRetryable(listed.error) ? 'blocked' : 'failed', {
582
587
  ...contextFields(context), resource: { kind: 'issue', number: issue }, error: listed.error
583
588
  });
584
589
  }
@@ -648,10 +653,10 @@ async function syncPlatformCommentImpl(taskRef: string, options: SyncOptions): P
648
653
  })
649
654
  : unsupportedProviderOperation(loaded.value.provider, 'comments.write');
650
655
  if (!written.ok) {
651
- if (!current && written.error.retryable) {
656
+ if (!current && isRetryable(written.error)) {
652
657
  // A provider owns reconciliation because only it knows how to address the resource.
653
658
  }
654
- return platformResult(written.error.retryable ? 'blocked' : 'failed', {
659
+ return platformResult(isRetryable(written.error) ? 'blocked' : 'failed', {
655
660
  ...contextFields(context),
656
661
  resource: { kind: 'issue', number: issue },
657
662
  operations,
@@ -674,7 +679,7 @@ async function syncPlatformCommentImpl(taskRef: string, options: SyncOptions): P
674
679
  })
675
680
  : unsupportedProviderOperation(loaded.value.provider, 'comments.delete');
676
681
  if (!deleted.ok) {
677
- return platformResult(deleted.error.retryable ? 'blocked' : 'failed', {
682
+ return platformResult(isRetryable(deleted.error) ? 'blocked' : 'failed', {
678
683
  ...contextFields(context),
679
684
  resource: { kind: 'issue', number: issue },
680
685
  operations,
@@ -686,7 +691,7 @@ async function syncPlatformCommentImpl(taskRef: string, options: SyncOptions): P
686
691
  if (options.kind === 'summary') {
687
692
  const refreshed = await listedComments(loaded.value.provider, loaded.value, issueIdentityFromTask);
688
693
  if (!refreshed.ok) {
689
- return platformResult(refreshed.error.retryable ? 'blocked' : 'failed', {
694
+ return platformResult(isRetryable(refreshed.error) ? 'blocked' : 'failed', {
690
695
  ...contextFields(context), resource: { kind: 'issue', number: issue }, operations, error: refreshed.error
691
696
  });
692
697
  }
@@ -714,7 +719,7 @@ async function syncPlatformCommentImpl(taskRef: string, options: SyncOptions): P
714
719
  })
715
720
  : unsupportedProviderOperation(loaded.value.provider, 'comments.delete');
716
721
  if (!deleted.ok) {
717
- return platformResult(deleted.error.retryable ? 'blocked' : 'failed', {
722
+ return platformResult(isRetryable(deleted.error) ? 'blocked' : 'failed', {
718
723
  ...contextFields(context), resource: { kind: 'issue', number: issue }, operations, error: deleted.error
719
724
  });
720
725
  }
@@ -725,7 +730,7 @@ async function syncPlatformCommentImpl(taskRef: string, options: SyncOptions): P
725
730
  })
726
731
  : unsupportedProviderOperation(loaded.value.provider, 'comments.write');
727
732
  if (!written.ok) {
728
- return platformResult(written.error.retryable ? 'blocked' : 'failed', {
733
+ return platformResult(isRetryable(written.error) ? 'blocked' : 'failed', {
729
734
  ...contextFields(context), resource: { kind: 'issue', number: issue }, operations, error: written.error
730
735
  });
731
736
  }
@@ -736,7 +741,7 @@ async function syncPlatformCommentImpl(taskRef: string, options: SyncOptions): P
736
741
  const error = reconciled.ok
737
742
  ? { code: 'SUMMARY_POSITION_UNVERIFIED', message: 'Summary comment is not provably last among task-managed comments', retryable: true }
738
743
  : reconciled.error;
739
- return platformResult(error.retryable ? 'blocked' : 'failed', {
744
+ return platformResult(isRetryable(error) ? 'blocked' : 'failed', {
740
745
  ...contextFields(context), resource: { kind: 'issue', number: issue }, operations, error
741
746
  });
742
747
  }
@@ -837,7 +842,7 @@ async function checkPlatformCommentOwner(taskRef: string, options: { cwd?: strin
837
842
  ? { ok: true as const, value: response.value.map((comment) => ({ id: comment.id, body: comment.body, user: comment.author?.name ? { login: comment.author.name } : undefined })) }
838
843
  : response)
839
844
  : unsupportedProviderOperation(loaded.value.provider, 'comments.list');
840
- if (!listed.ok) return platformResult(listed.error.retryable ? 'blocked' : 'failed', { ...contextFields(context), error: listed.error });
845
+ if (!listed.ok) return platformResult(isRetryable(listed.error) ? 'blocked' : 'failed', { ...contextFields(context), error: listed.error });
841
846
  const matches = findMarkerComments(listed.value, MARKERS.task(resolved.taskId));
842
847
  if (matches.length > 1) return platformResult('failed', { ...contextFields(context), error: { code: 'COMMENT_MARKER_CONFLICT', message: 'Multiple task comments use the registered marker', retryable: false } });
843
848
  const owner = matches[0]?.user?.login;
@@ -48,7 +48,7 @@ async function coordinatePlatformWrite<T extends OperationResult>(input: Coordin
48
48
  excludeId: operationId
49
49
  });
50
50
  if (drained.status !== 'applied' && drained.status !== 'no-op') {
51
- return input.block(drained.error ?? {
51
+ return input.block(drained.error ? { ...drained.error, retryable: true } : {
52
52
  code: 'PLATFORM_OPERATION_QUEUE_BLOCKED',
53
53
  message: 'A previous platform operation is still unresolved; the current write remains queued',
54
54
  retryable: true
@@ -5,6 +5,7 @@ import path from 'node:path';
5
5
  import { coordinatePlatformWrite } from './operation-coordinator.ts';
6
6
  import { resolvePlatformProviderContext } from './context.ts';
7
7
  import type { PlatformClient } from './context.ts';
8
+ import { providerErrorRetryable } from './provider-validation.ts';
8
9
  import { platformResult } from './types.ts';
9
10
  import type { PlatformOperation, PlatformResult } from './types.ts';
10
11
  import {
@@ -283,7 +284,7 @@ export async function publishPrReview(options: {
283
284
  })
284
285
  : unsupportedProviderOperation(loaded.value.provider, 'reviews.publish');
285
286
  if (!published.ok) {
286
- if (published.error.retryable) {
287
+ if (published.error.retryable || providerErrorRetryable(published.error.code)) {
287
288
  const reconciled = await listPrReviews(options.prNumber, { cwd: options.cwd, client: options.client });
288
289
  const found = reconciled.reviews.find((review) => firstLine(review.body) === marker);
289
290
  if (found) return platformResult('applied', {
@@ -1,7 +1,7 @@
1
1
  import path from 'node:path';
2
2
 
3
3
  import type {
4
- PlatformError,
4
+ ProviderError,
5
5
  ProviderOperationContext,
6
6
  PlatformProvider,
7
7
  ProviderResult,
@@ -15,6 +15,9 @@ import {
15
15
  } from './resource-identity.ts';
16
16
  import type { LoadedContext } from './context.ts';
17
17
  import type { PlatformResult } from './types.ts';
18
+ import type { PlatformError } from './types.ts';
19
+ import { providerErrorRetryable } from './provider-validation.ts';
20
+ import type { NormalizedProviderResult } from './provider-validation.ts';
18
21
 
19
22
  function providerOperationContext(
20
23
  loaded: LoadedContext,
@@ -29,30 +32,28 @@ function providerOperationContext(
29
32
  };
30
33
  }
31
34
 
32
- function providerError(error: PlatformError, fallbackCode: string): PlatformError {
35
+ function providerError(error: ProviderError | PlatformError, fallbackCode: string): PlatformError {
33
36
  return {
34
37
  code: error.code || fallbackCode,
35
38
  message: error.message || 'Platform provider operation failed',
36
- retryable: error.retryable
39
+ retryable: 'retryable' in error ? error.retryable : providerErrorRetryable(error.code)
37
40
  };
38
41
  }
39
42
 
40
- function providerStatus(error: PlatformError): PlatformResult['status'] {
41
- return error.retryable ? 'blocked' : 'failed';
43
+ function providerStatus(error: ProviderError | PlatformError): PlatformResult['status'] {
44
+ return ('retryable' in error ? error.retryable : providerErrorRetryable(error.code)) ? 'blocked' : 'failed';
42
45
  }
43
46
 
44
47
  function unsupportedProviderOperation(
45
48
  provider: PlatformProvider,
46
49
  operation: string
47
- ): ProviderResult<never> {
50
+ ): NormalizedProviderResult<never> {
48
51
  return {
49
52
  ok: false,
50
53
  error: {
51
54
  code: 'PLATFORM_CAPABILITY_UNSUPPORTED',
52
55
  message: `Platform '${provider.type}' does not provide ${operation}`,
53
- retryable: false,
54
- providerType: provider.type,
55
- phase: 'operation'
56
+ retryable: false
56
57
  }
57
58
  };
58
59
  }
@@ -12,17 +12,14 @@ type JsonValue =
12
12
  | JsonValue[]
13
13
  | { [key: string]: JsonValue };
14
14
 
15
- type PlatformError = {
15
+ type ProviderError = {
16
16
  code: string;
17
17
  message: string;
18
- retryable: boolean;
19
- providerType?: string;
20
- phase?: string;
21
18
  };
22
19
 
23
20
  type ProviderResult<T> =
24
21
  | { ok: true; value: T }
25
- | { ok: false; error: PlatformError; value?: T };
22
+ | { ok: false; error: ProviderError; value?: T };
26
23
 
27
24
  type ProviderOperationContext = {
28
25
  repositoryRoot: string;
@@ -534,10 +531,7 @@ function validatePlatformProvider(
534
531
  ok: false,
535
532
  error: {
536
533
  code: 'PLATFORM_PROVIDER_CONTRACT_INVALID',
537
- message: 'Provider must expose matching type, contractVersion, and context.resolve',
538
- retryable: false,
539
- providerType,
540
- phase: 'provider-validation'
534
+ message: 'Provider must expose matching type, contractVersion, and context.resolve'
541
535
  }
542
536
  };
543
537
  }
@@ -550,10 +544,7 @@ function validatePlatformProvider(
550
544
  ok: false,
551
545
  error: {
552
546
  code: 'PLATFORM_PROVIDER_CONTRACT_INVALID',
553
- message: 'Provider identity declaration is invalid',
554
- retryable: false,
555
- providerType,
556
- phase: 'provider-validation'
547
+ message: 'Provider identity declaration is invalid'
557
548
  }
558
549
  };
559
550
  }
@@ -567,10 +558,7 @@ function validatePlatformProvider(
567
558
  ok: false,
568
559
  error: {
569
560
  code: 'PLATFORM_PROVIDER_CONTRACT_INVALID',
570
- message: `Provider operation group ${groupName} is incomplete`,
571
- retryable: false,
572
- providerType,
573
- phase: 'provider-validation'
561
+ message: `Provider operation group ${groupName} is incomplete`
574
562
  }
575
563
  };
576
564
  }
@@ -583,9 +571,6 @@ function validatePlatformProvider(
583
571
  error: {
584
572
  code: 'PLATFORM_PROVIDER_CONTRACT_INVALID',
585
573
  message: `Provider operation group ${groupName} requires an identity declaration for ${resourceKind}`,
586
- retryable: false,
587
- providerType,
588
- phase: 'provider-validation'
589
574
  }
590
575
  };
591
576
  }
@@ -617,7 +602,7 @@ export type {
617
602
  MutationReceipt,
618
603
  PlatformCapabilities,
619
604
  PlatformContextSnapshot,
620
- PlatformError,
605
+ ProviderError,
621
606
  PlatformProvider,
622
607
  PlatformProviderFactory,
623
608
  PlatformProviderFactoryInput,
@@ -12,13 +12,14 @@ import {
12
12
  } from './provider-contract.ts';
13
13
  import type {
14
14
  JsonValue,
15
- PlatformError,
16
15
  PlatformProvider,
17
16
  PlatformProviderFactoryInput
18
17
  } from './provider-contract.ts';
19
18
  import { createGitHubProvider } from './github-provider.ts';
20
19
  import { createNoneProvider } from './none-provider.ts';
21
20
  import { wrapProviderOperations } from './provider-validation.ts';
21
+ import type { LoadedRuntimeProvider, RuntimePlatformProvider } from './provider-validation.ts';
22
+ import type { PlatformError } from './types.ts';
22
23
  import type { PlatformClient } from './context.ts';
23
24
 
24
25
  type ProviderLoaderOptions = {
@@ -28,7 +29,7 @@ type ProviderLoaderOptions = {
28
29
  };
29
30
 
30
31
  type LoadedPlatformProvider = {
31
- provider: PlatformProvider;
32
+ provider: LoadedRuntimeProvider;
32
33
  providerType: string;
33
34
  repositoryRoot: string;
34
35
  workingDirectory: string;
@@ -44,7 +45,7 @@ type ResolvedExternalSource = {
44
45
  importUrl: string;
45
46
  };
46
47
 
47
- const sessions = new Map<string, Promise<PlatformProvider>>();
48
+ const sessions = new Map<string, Promise<LoadedRuntimeProvider>>();
48
49
 
49
50
  function findRepositoryRoot(cwd: string): string {
50
51
  try {
@@ -151,15 +152,15 @@ async function instantiateProvider(
151
152
  importUrl: string | null,
152
153
  config: Readonly<Record<string, JsonValue>>,
153
154
  client?: PlatformClient
154
- ): Promise<PlatformProvider> {
155
+ ): Promise<LoadedRuntimeProvider> {
155
156
  const input: PlatformProviderFactoryInput = {
156
157
  providerType,
157
158
  contractVersion: PLATFORM_PROVIDER_CONTRACT_VERSION,
158
159
  repositoryRoot,
159
160
  config
160
161
  };
161
- if (providerType === 'github') return createGitHubProvider(input, client as never);
162
- if (providerType === 'none') return createNoneProvider(input);
162
+ if (providerType === 'github') return createGitHubProvider(input, client as never) as unknown as RuntimePlatformProvider;
163
+ if (providerType === 'none') return createNoneProvider(input) as unknown as RuntimePlatformProvider;
163
164
 
164
165
  let moduleValue: Record<string, unknown>;
165
166
  try {