@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
@@ -7,6 +7,32 @@ import semver from 'semver';
7
7
  import { createGitHubClient, MINIMUM_GITHUB_CLI_VERSION } from './github-client.js';
8
8
  import { resourceIdentityNumber, resourceIdentityString } from './resource-identity.js';
9
9
  import { extractPullRequestFileNames, syncLabelDelta } from './in-label-sync.js';
10
+ function providerError(error) {
11
+ return { code: error.code, message: error.message };
12
+ }
13
+ function publicProviderErrors(provider) {
14
+ const sanitize = async (call) => {
15
+ const result = await call();
16
+ if (!result || typeof result !== 'object' || result.ok !== false)
17
+ return result;
18
+ const envelope = result;
19
+ return envelope.error ? { ...result, error: providerError(envelope.error) } : result;
20
+ };
21
+ const wrapped = { ...provider, context: { ...provider.context } };
22
+ wrapped.context.resolve = (input) => sanitize(() => provider.context.resolve(input));
23
+ for (const groupName of ['issues', 'comments', 'changeRequests', 'checks', 'reviews', 'releases', 'securityAlerts', 'repositoryMetadata', 'verification']) {
24
+ const group = provider[groupName];
25
+ if (!group)
26
+ continue;
27
+ const target = { ...group };
28
+ for (const method of Object.keys(group)) {
29
+ const original = group[method];
30
+ target[method] = (input) => sanitize(() => original(input));
31
+ }
32
+ wrapped[groupName] = target;
33
+ }
34
+ return wrapped;
35
+ }
10
36
  const CURRENT_USER_QUERY = 'query { viewer { login } }';
11
37
  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}}}}}}`;
12
38
  const CLOSING_ISSUES_QUERY = 'query($owner:String!,$name:String!,$number:Int!,$cursor:String){repository(owner:$owner,name:$name){pullRequest(number:$number){closingIssuesReferences(first:100,after:$cursor){nodes{number} pageInfo{hasNextPage endCursor}}}}}';
@@ -57,7 +83,6 @@ function resolveGitHubChangeRequestGitEvidence({ cwd, repository, number, baseRe
57
83
  error: {
58
84
  code: 'PR_MERGE_EVIDENCE_SOURCE_UNAVAILABLE',
59
85
  message: 'Pull request repository identity is inconsistent',
60
- retryable: false
61
86
  }
62
87
  };
63
88
  }
@@ -80,7 +105,6 @@ function resolveGitHubChangeRequestGitEvidence({ cwd, repository, number, baseRe
80
105
  error: {
81
106
  code: 'PR_MERGE_EVIDENCE_SOURCE_UNAVAILABLE',
82
107
  message: 'Pull request Git refs are invalid',
83
- retryable: false
84
108
  }
85
109
  };
86
110
  }
@@ -97,7 +121,6 @@ function resolveGitHubChangeRequestGitEvidence({ cwd, repository, number, baseRe
97
121
  error: {
98
122
  code: 'PR_MERGE_EVIDENCE_SOURCE_UNAVAILABLE',
99
123
  message: `No GitHub remote can provide evidence for ${repository}`,
100
- retryable: false
101
124
  }
102
125
  };
103
126
  }
@@ -107,7 +130,7 @@ function resolveGitHubChangeRequestGitEvidence({ cwd, repository, number, baseRe
107
130
  };
108
131
  }
109
132
  function failure(error) {
110
- return { ok: false, error };
133
+ return { ok: false, error: providerError(error) };
111
134
  }
112
135
  function resolveContext(client, input) {
113
136
  const cwd = input.workingDirectory;
@@ -118,19 +141,17 @@ function resolveContext(client, input) {
118
141
  return failure({
119
142
  code: 'GH_CLI_VERSION_UNSUPPORTED',
120
143
  message: 'GitHub CLI ' + version.value + ' is unsupported; install gh >= ' + MINIMUM_GITHUB_CLI_VERSION,
121
- retryable: false
122
144
  });
123
145
  }
124
146
  const remote = input.gitRemote || defaultGitRemote(cwd);
125
147
  if (!remote) {
126
- return failure({ code: 'REMOTE_MISSING', message: 'Git origin remote is not configured', retryable: false });
148
+ return failure({ code: 'REMOTE_MISSING', message: 'Git origin remote is not configured' });
127
149
  }
128
150
  const ownerRepo = parseGitHubRemote(remote);
129
151
  if (!ownerRepo) {
130
152
  return failure({
131
153
  code: /github\.com/i.test(remote) ? 'REMOTE_INVALID' : 'PLATFORM_UNSUPPORTED',
132
154
  message: 'Unable to parse GitHub owner/repo from configured remote',
133
- retryable: false
134
155
  });
135
156
  }
136
157
  const repository = client.json(['api', 'repos/' + ownerRepo], { cwd });
@@ -142,7 +163,6 @@ function resolveContext(client, input) {
142
163
  return failure({
143
164
  code: 'UPSTREAM_UNRESOLVED',
144
165
  message: 'Unable to resolve the upstream repository',
145
- retryable: false
146
166
  });
147
167
  const user = client.json(['api', 'graphql', '-f', 'query=' + CURRENT_USER_QUERY], { cwd });
148
168
  if (!user.ok)
@@ -193,7 +213,7 @@ function verifyRemoteBranch(cwd, repositoryName, head) {
193
213
  if (!wanted.ref || !/^[A-Za-z0-9._/-]+$/.test(wanted.ref))
194
214
  return {
195
215
  ok: false,
196
- error: { code: 'PR_HEAD_INVALID', message: 'Pull request head ref is invalid', retryable: false }
216
+ error: { code: 'PR_HEAD_INVALID', message: 'Pull request head ref is invalid' }
197
217
  };
198
218
  const remotes = configuredGitRemotes(cwd);
199
219
  const remote = remotes.find((item) => item.repository.toLowerCase() === wanted.repository.toLowerCase())
@@ -201,7 +221,7 @@ function verifyRemoteBranch(cwd, repositoryName, head) {
201
221
  if (!remote)
202
222
  return {
203
223
  ok: false,
204
- error: { code: 'PR_REMOTE_BRANCH_MISSING', message: `No configured remote can verify ${wanted.repository}:${wanted.ref}`, retryable: false }
224
+ error: { code: 'PR_REMOTE_BRANCH_MISSING', message: `No configured remote can verify ${wanted.repository}:${wanted.ref}` }
205
225
  };
206
226
  try {
207
227
  const output = execFileSync('git', ['ls-remote', '--refs', remote.name, `refs/heads/${wanted.ref}`], {
@@ -212,19 +232,19 @@ function verifyRemoteBranch(cwd, repositoryName, head) {
212
232
  if (!sha || !/^[a-f0-9]{40}$/i.test(sha))
213
233
  return {
214
234
  ok: false,
215
- error: { code: 'PR_REMOTE_BRANCH_MISSING', message: `Remote branch ${wanted.repository}:${wanted.ref} does not exist`, retryable: false }
235
+ error: { code: 'PR_REMOTE_BRANCH_MISSING', message: `Remote branch ${wanted.repository}:${wanted.ref} does not exist` }
216
236
  };
217
237
  return { ok: true, value: sha };
218
238
  }
219
239
  catch (error) {
220
240
  return {
221
241
  ok: false,
222
- error: { code: 'PR_REMOTE_BRANCH_UNAVAILABLE', message: error instanceof Error ? error.message : String(error), retryable: true }
242
+ error: { code: 'PR_REMOTE_BRANCH_UNAVAILABLE', message: error instanceof Error ? error.message : String(error) }
223
243
  };
224
244
  }
225
245
  }
226
246
  function invalid(code, message) {
227
- return { ok: false, error: { code, message, retryable: false } };
247
+ return { ok: false, error: { code, message } };
228
248
  }
229
249
  function changeRequestSnapshot(value) {
230
250
  return {
@@ -292,7 +312,7 @@ function createReceipt(remoteId) {
292
312
  return { ok: true, value: { changed: true, remoteId } };
293
313
  }
294
314
  function partialFailure(response, value) {
295
- return { ok: false, error: response.error, value };
315
+ return { ok: false, error: providerError(response.error), value };
296
316
  }
297
317
  function labelReconciliation(created, updated, removed, skipped) {
298
318
  return {
@@ -319,8 +339,8 @@ function syncLabels(client, repositoryName, number, cwd, current, target, prefix
319
339
  status: 'blocked',
320
340
  changed: true,
321
341
  error: synced.error
322
- ? { ...synced.error, code: 'IN_LABEL_SYNC_PARTIAL', message: `In-label synchronization is partial or unknown: ${synced.error.message}` }
323
- : { code: 'IN_LABEL_SYNC_PARTIAL', message: 'In-label synchronization is partial or unknown', retryable: true }
342
+ ? { ...providerError(synced.error), code: 'IN_LABEL_SYNC_PARTIAL', message: `In-label synchronization is partial or unknown: ${synced.error.message}` }
343
+ : { code: 'IN_LABEL_SYNC_PARTIAL', message: 'In-label synchronization is partial or unknown' }
324
344
  };
325
345
  return synced;
326
346
  }
@@ -599,7 +619,7 @@ function createGitHubOperations(client) {
599
619
  localHead = repositoryHead(context.workingDirectory);
600
620
  }
601
621
  catch (error) {
602
- return { ok: false, error: { code: 'PR_LOCAL_HEAD_UNAVAILABLE', message: error instanceof Error ? error.message : String(error), retryable: false } };
622
+ return { ok: false, error: { code: 'PR_LOCAL_HEAD_UNAVAILABLE', message: error instanceof Error ? error.message : String(error) } };
603
623
  }
604
624
  const remote = verifyRemoteBranch(context.workingDirectory, repository(context), head);
605
625
  if (!remote.ok)
@@ -607,7 +627,7 @@ function createGitHubOperations(client) {
607
627
  if (remote.value !== localHead)
608
628
  return {
609
629
  ok: false,
610
- error: { code: 'PR_REMOTE_HEAD_MISMATCH', message: `Remote head ${remote.value} does not match local HEAD ${localHead}`, retryable: false }
630
+ error: { code: 'PR_REMOTE_HEAD_MISMATCH', message: `Remote head ${remote.value} does not match local HEAD ${localHead}` }
611
631
  };
612
632
  return { ok: true, value: { sha: localHead } };
613
633
  },
@@ -904,7 +924,7 @@ function createGitHubOperations(client) {
904
924
  const tag = resourceIdentityString(release) || '';
905
925
  const result = module.publishGitHubReleaseNotes({ repository: repository(context), tag, title, notesFile }, { cwd: context.workingDirectory, client });
906
926
  if (result.status === 'failed' || result.status === 'blocked')
907
- return { ok: false, error: result.error || { code: 'RELEASE_NOTES_PUBLISH_FAILED', message: 'Release notes publish failed', retryable: false } };
927
+ return { ok: false, error: result.error ? providerError(result.error) : { code: 'RELEASE_NOTES_PUBLISH_FAILED', message: 'Release notes publish failed' } };
908
928
  return { ok: true, value: { changed: result.changed, remoteId: tag } };
909
929
  }
910
930
  finally {
@@ -917,7 +937,7 @@ function createGitHubOperations(client) {
917
937
  repository: repository(context), commitOids, branch, historyLimit, fromTime, toTime
918
938
  }, { cwd: context.workingDirectory, client });
919
939
  if (collected.status === 'failed' || collected.status === 'blocked' || !('pullRequests' in collected)) {
920
- return { ok: false, error: collected.error || { code: 'RELEASE_NOTES_COLLECTION_FAILED', message: 'Release notes could not be collected', retryable: false } };
940
+ return { ok: false, error: collected.error ? providerError(collected.error) : { code: 'RELEASE_NOTES_COLLECTION_FAILED', message: 'Release notes could not be collected' } };
921
941
  }
922
942
  const mergedPullRequests = collected.pullRequests.map((item) => ({
923
943
  id: String(item.number), identity: { kind: 'number', value: Number(item.number) }, number: Number(item.number),
@@ -1114,7 +1134,7 @@ function createGitHubOperations(client) {
1114
1134
  return { issues, comments, changeRequests, checks, reviews, releases, securityAlerts, repositoryMetadata, verification };
1115
1135
  }
1116
1136
  function createGitHubProvider(input, client = createGitHubClient()) {
1117
- return {
1137
+ return publicProviderErrors({
1118
1138
  type: input.providerType,
1119
1139
  contractVersion: 2,
1120
1140
  identity: { issue: 'number', 'pull-request': 'number', comment: 'number', release: 'key' },
@@ -1124,7 +1144,7 @@ function createGitHubProvider(input, client = createGitHubClient()) {
1124
1144
  return resolveContext(client, contextInput);
1125
1145
  }
1126
1146
  }
1127
- };
1147
+ });
1128
1148
  }
1129
1149
  export { configuredGitRemotes, createGitHubProvider, defaultGitRemote, parseGitHubRemote, resolveGitHubChangeRequestGitEvidence };
1130
1150
  //# sourceMappingURL=github-provider.js.map
@@ -1,4 +1,5 @@
1
1
  import { createGitHubClient } from './github-client.js';
2
+ import { providerErrorRetryable } from './provider-validation.js';
2
3
  function normalizeGitHubActor(actor) {
3
4
  const name = String(actor.name || '').trim();
4
5
  const email = actor.email ? String(actor.email).trim() : null;
@@ -26,12 +27,13 @@ function normalizePlatformAuthor(author) {
26
27
  };
27
28
  }
28
29
  function failure(error) {
30
+ const retryable = error.retryable ?? providerErrorRetryable(error.code);
29
31
  return {
30
- status: error.retryable ? 'blocked' : 'failed',
32
+ status: retryable ? 'blocked' : 'failed',
31
33
  changed: false,
32
34
  operation: null,
33
35
  url: null,
34
- error
36
+ error: { code: error.code, message: error.message }
35
37
  };
36
38
  }
37
39
  function publishGitHubReleaseNotes(input, options = {}) {
@@ -1,5 +1,6 @@
1
1
  import { computeInLabels } from './metadata-labels.js';
2
2
  import { createHash } from 'node:crypto';
3
+ import { providerErrorRetryable } from './provider-validation.js';
3
4
  function inLabels(labels) {
4
5
  return [...new Set(labels.filter((label) => label.startsWith('in:')))].sort();
5
6
  }
@@ -96,7 +97,7 @@ function syncLabelDelta(client, repository, number, cwd, current, target, prefix
96
97
  const delta = labelDelta(current, target, prefix);
97
98
  let changed = false;
98
99
  const failure = (error) => {
99
- if (changed || error.retryable) {
100
+ if (changed || error.retryable || providerErrorRetryable(error.code)) {
100
101
  return {
101
102
  status: 'blocked',
102
103
  changed,
@@ -5,6 +5,7 @@ import { parseTaskFrontmatter } from '../task/frontmatter.js';
5
5
  import { resolveTaskRef } from '../task/resolve-ref.js';
6
6
  import { coordinatePlatformWrite } from './operation-coordinator.js';
7
7
  import { resolvePlatformProviderContext } from './context.js';
8
+ import { providerErrorRetryable } from './provider-validation.js';
8
9
  import { platformResult } from './types.js';
9
10
  import { providerError, providerOperationContext, providerStatus, providerResourceToken, unsupportedProviderOperation } from './provider-bridge.js';
10
11
  import { resourceIdentityNumber } from './resource-identity.js';
@@ -12,6 +13,9 @@ import { taskIssueIdentity, taskIssueIdentityError } from './task-identities.js'
12
13
  import { recordForArtifact, recordForCancel, recordForSummary, recordForTask, renderActivityRecoveryMetadata } from './activity-recovery.js';
13
14
  import { projectTaskComment } from './task-comment-projection.js';
14
15
  import { canonicalizeCommentBody, escapeHtmlText, fenceRanges, renderSafeCodeFence } from './comment-safety.js';
16
+ function isRetryable(error) {
17
+ return error.retryable ?? providerErrorRetryable(error.code);
18
+ }
15
19
  function providerCommentId(id, provider) {
16
20
  return provider.identity?.comment === 'number' && /^\d+$/.test(id) ? Number(id) : id;
17
21
  }
@@ -482,7 +486,7 @@ async function syncPlatformCommentImpl(taskRef, options) {
482
486
  const issue = resourceIdentityNumber(issueIdentityFromTask);
483
487
  const listed = await listedComments(loaded.value.provider, loaded.value, issueIdentityFromTask);
484
488
  if (!listed.ok) {
485
- return platformResult(listed.error.retryable ? 'blocked' : 'failed', {
489
+ return platformResult(isRetryable(listed.error) ? 'blocked' : 'failed', {
486
490
  ...contextFields(context), resource: { kind: 'issue', number: issue }, error: listed.error
487
491
  });
488
492
  }
@@ -548,10 +552,10 @@ async function syncPlatformCommentImpl(taskRef, options) {
548
552
  })
549
553
  : unsupportedProviderOperation(loaded.value.provider, 'comments.write');
550
554
  if (!written.ok) {
551
- if (!current && written.error.retryable) {
555
+ if (!current && isRetryable(written.error)) {
552
556
  // A provider owns reconciliation because only it knows how to address the resource.
553
557
  }
554
- return platformResult(written.error.retryable ? 'blocked' : 'failed', {
558
+ return platformResult(isRetryable(written.error) ? 'blocked' : 'failed', {
555
559
  ...contextFields(context),
556
560
  resource: { kind: 'issue', number: issue },
557
561
  operations,
@@ -573,7 +577,7 @@ async function syncPlatformCommentImpl(taskRef, options) {
573
577
  })
574
578
  : unsupportedProviderOperation(loaded.value.provider, 'comments.delete');
575
579
  if (!deleted.ok) {
576
- return platformResult(deleted.error.retryable ? 'blocked' : 'failed', {
580
+ return platformResult(isRetryable(deleted.error) ? 'blocked' : 'failed', {
577
581
  ...contextFields(context),
578
582
  resource: { kind: 'issue', number: issue },
579
583
  operations,
@@ -585,7 +589,7 @@ async function syncPlatformCommentImpl(taskRef, options) {
585
589
  if (options.kind === 'summary') {
586
590
  const refreshed = await listedComments(loaded.value.provider, loaded.value, issueIdentityFromTask);
587
591
  if (!refreshed.ok) {
588
- return platformResult(refreshed.error.retryable ? 'blocked' : 'failed', {
592
+ return platformResult(isRetryable(refreshed.error) ? 'blocked' : 'failed', {
589
593
  ...contextFields(context), resource: { kind: 'issue', number: issue }, operations, error: refreshed.error
590
594
  });
591
595
  }
@@ -613,7 +617,7 @@ async function syncPlatformCommentImpl(taskRef, options) {
613
617
  })
614
618
  : unsupportedProviderOperation(loaded.value.provider, 'comments.delete');
615
619
  if (!deleted.ok) {
616
- return platformResult(deleted.error.retryable ? 'blocked' : 'failed', {
620
+ return platformResult(isRetryable(deleted.error) ? 'blocked' : 'failed', {
617
621
  ...contextFields(context), resource: { kind: 'issue', number: issue }, operations, error: deleted.error
618
622
  });
619
623
  }
@@ -624,7 +628,7 @@ async function syncPlatformCommentImpl(taskRef, options) {
624
628
  })
625
629
  : unsupportedProviderOperation(loaded.value.provider, 'comments.write');
626
630
  if (!written.ok) {
627
- return platformResult(written.error.retryable ? 'blocked' : 'failed', {
631
+ return platformResult(isRetryable(written.error) ? 'blocked' : 'failed', {
628
632
  ...contextFields(context), resource: { kind: 'issue', number: issue }, operations, error: written.error
629
633
  });
630
634
  }
@@ -635,7 +639,7 @@ async function syncPlatformCommentImpl(taskRef, options) {
635
639
  const error = reconciled.ok
636
640
  ? { code: 'SUMMARY_POSITION_UNVERIFIED', message: 'Summary comment is not provably last among task-managed comments', retryable: true }
637
641
  : reconciled.error;
638
- return platformResult(error.retryable ? 'blocked' : 'failed', {
642
+ return platformResult(isRetryable(error) ? 'blocked' : 'failed', {
639
643
  ...contextFields(context), resource: { kind: 'issue', number: issue }, operations, error
640
644
  });
641
645
  }
@@ -748,7 +752,7 @@ async function checkPlatformCommentOwner(taskRef, options = {}) {
748
752
  : response)
749
753
  : unsupportedProviderOperation(loaded.value.provider, 'comments.list');
750
754
  if (!listed.ok)
751
- return platformResult(listed.error.retryable ? 'blocked' : 'failed', { ...contextFields(context), error: listed.error });
755
+ return platformResult(isRetryable(listed.error) ? 'blocked' : 'failed', { ...contextFields(context), error: listed.error });
752
756
  const matches = findMarkerComments(listed.value, MARKERS.task(resolved.taskId));
753
757
  if (matches.length > 1)
754
758
  return platformResult('failed', { ...contextFields(context), error: { code: 'COMMENT_MARKER_CONFLICT', message: 'Multiple task comments use the registered marker', retryable: false } });
@@ -25,7 +25,7 @@ async function coordinatePlatformWrite(input) {
25
25
  excludeId: operationId
26
26
  });
27
27
  if (drained.status !== 'applied' && drained.status !== 'no-op') {
28
- return input.block(drained.error ?? {
28
+ return input.block(drained.error ? { ...drained.error, retryable: true } : {
29
29
  code: 'PLATFORM_OPERATION_QUEUE_BLOCKED',
30
30
  message: 'A previous platform operation is still unresolved; the current write remains queued',
31
31
  retryable: true
@@ -3,6 +3,7 @@ import fs from 'node:fs';
3
3
  import path from 'node:path';
4
4
  import { coordinatePlatformWrite } from './operation-coordinator.js';
5
5
  import { resolvePlatformProviderContext } from './context.js';
6
+ import { providerErrorRetryable } from './provider-validation.js';
6
7
  import { platformResult } from './types.js';
7
8
  import { providerError, providerOperationContext, providerStatus, providerResourceToken, unsupportedProviderOperation } from './provider-bridge.js';
8
9
  import { isResourceIdentity, resourceIdentityEquals, resourceIdentityNumber, reviewMarker as resourceReviewMarker } from './resource-identity.js';
@@ -253,7 +254,7 @@ export async function publishPrReview(options) {
253
254
  })
254
255
  : unsupportedProviderOperation(loaded.value.provider, 'reviews.publish');
255
256
  if (!published.ok) {
256
- if (published.error.retryable) {
257
+ if (published.error.retryable || providerErrorRetryable(published.error.code)) {
257
258
  const reconciled = await listPrReviews(options.prNumber, { cwd: options.cwd, client: options.client });
258
259
  const found = reconciled.reviews.find((review) => firstLine(review.body) === marker);
259
260
  if (found)
@@ -1,5 +1,6 @@
1
1
  import path from 'node:path';
2
2
  import { identityFromRemoteValue, parseResourceIdentity, parseResourceToken, resourceIdentityNumber } from './resource-identity.js';
3
+ import { providerErrorRetryable } from './provider-validation.js';
3
4
  function providerOperationContext(loaded, workingDirectory = loaded.workingDirectory, scopeId = loaded.snapshot.scope.id) {
4
5
  return {
5
6
  repositoryRoot: loaded.repositoryRoot,
@@ -12,11 +13,11 @@ function providerError(error, fallbackCode) {
12
13
  return {
13
14
  code: error.code || fallbackCode,
14
15
  message: error.message || 'Platform provider operation failed',
15
- retryable: error.retryable
16
+ retryable: 'retryable' in error ? error.retryable : providerErrorRetryable(error.code)
16
17
  };
17
18
  }
18
19
  function providerStatus(error) {
19
- return error.retryable ? 'blocked' : 'failed';
20
+ return ('retryable' in error ? error.retryable : providerErrorRetryable(error.code)) ? 'blocked' : 'failed';
20
21
  }
21
22
  function unsupportedProviderOperation(provider, operation) {
22
23
  return {
@@ -24,9 +25,7 @@ function unsupportedProviderOperation(provider, operation) {
24
25
  error: {
25
26
  code: 'PLATFORM_CAPABILITY_UNSUPPORTED',
26
27
  message: `Platform '${provider.type}' does not provide ${operation}`,
27
- retryable: false,
28
- providerType: provider.type,
29
- phase: 'operation'
28
+ retryable: false
30
29
  }
31
30
  };
32
31
  }
@@ -2,19 +2,16 @@ import type { ProviderIdentityDeclaration, ResourceIdentity } from './resource-i
2
2
  type JsonValue = string | number | boolean | null | JsonValue[] | {
3
3
  [key: string]: JsonValue;
4
4
  };
5
- type PlatformError = {
5
+ type ProviderError = {
6
6
  code: string;
7
7
  message: string;
8
- retryable: boolean;
9
- providerType?: string;
10
- phase?: string;
11
8
  };
12
9
  type ProviderResult<T> = {
13
10
  ok: true;
14
11
  value: T;
15
12
  } | {
16
13
  ok: false;
17
- error: PlatformError;
14
+ error: ProviderError;
18
15
  value?: T;
19
16
  };
20
17
  type ProviderOperationContext = {
@@ -575,4 +572,4 @@ declare const PLATFORM_PROVIDER_CONTRACT_VERSION: 2;
575
572
  declare function isPlatformProviderFactory(value: unknown): value is PlatformProviderFactory;
576
573
  declare function validatePlatformProvider(value: unknown, providerType: string): ProviderResult<PlatformProvider>;
577
574
  export { PLATFORM_PROVIDER_CONTRACT_VERSION, isPlatformProviderFactory, validatePlatformProvider };
578
- export type { ChangeRequestSnapshot, CheckLogSnapshot, CheckRunSnapshot, GitEvidenceSnapshot, IssueSnapshot, JsonValue, MilestoneReconciliation, MilestoneDefinition, MilestoneInitialization, LabelDefinition, LabelReconciliation, MutationIdentity, MutationReceipt, PlatformCapabilities, PlatformContextSnapshot, PlatformError, PlatformProvider, PlatformProviderFactory, PlatformProviderFactoryInput, ProviderOperationContext, ProviderResult, ReleaseSnapshot, RemoteCommentSnapshot, RequiredCheckSnapshot, RepositoryMetadataSnapshot, ReleaseNotesFacts, ReleaseNoteAuthor, ReleaseNoteIssue, ReleaseNotePullRequest, ResourceIdentity, SecurityAlertKind, SecurityAlertSnapshot, ReviewSnapshot, VerificationRemoteFacts };
575
+ export type { ChangeRequestSnapshot, CheckLogSnapshot, CheckRunSnapshot, GitEvidenceSnapshot, IssueSnapshot, JsonValue, MilestoneReconciliation, MilestoneDefinition, MilestoneInitialization, LabelDefinition, LabelReconciliation, MutationIdentity, MutationReceipt, PlatformCapabilities, PlatformContextSnapshot, ProviderError, PlatformProvider, PlatformProviderFactory, PlatformProviderFactoryInput, ProviderOperationContext, ProviderResult, ReleaseSnapshot, RemoteCommentSnapshot, RequiredCheckSnapshot, RepositoryMetadataSnapshot, ReleaseNotesFacts, ReleaseNoteAuthor, ReleaseNoteIssue, ReleaseNotePullRequest, ResourceIdentity, SecurityAlertKind, SecurityAlertSnapshot, ReviewSnapshot, VerificationRemoteFacts };
@@ -37,10 +37,7 @@ function validatePlatformProvider(value, providerType) {
37
37
  ok: false,
38
38
  error: {
39
39
  code: 'PLATFORM_PROVIDER_CONTRACT_INVALID',
40
- message: 'Provider must expose matching type, contractVersion, and context.resolve',
41
- retryable: false,
42
- providerType,
43
- phase: 'provider-validation'
40
+ message: 'Provider must expose matching type, contractVersion, and context.resolve'
44
41
  }
45
42
  };
46
43
  }
@@ -51,10 +48,7 @@ function validatePlatformProvider(value, providerType) {
51
48
  ok: false,
52
49
  error: {
53
50
  code: 'PLATFORM_PROVIDER_CONTRACT_INVALID',
54
- message: 'Provider identity declaration is invalid',
55
- retryable: false,
56
- providerType,
57
- phase: 'provider-validation'
51
+ message: 'Provider identity declaration is invalid'
58
52
  }
59
53
  };
60
54
  }
@@ -68,10 +62,7 @@ function validatePlatformProvider(value, providerType) {
68
62
  ok: false,
69
63
  error: {
70
64
  code: 'PLATFORM_PROVIDER_CONTRACT_INVALID',
71
- message: `Provider operation group ${groupName} is incomplete`,
72
- retryable: false,
73
- providerType,
74
- phase: 'provider-validation'
65
+ message: `Provider operation group ${groupName} is incomplete`
75
66
  }
76
67
  };
77
68
  }
@@ -84,9 +75,6 @@ function validatePlatformProvider(value, providerType) {
84
75
  error: {
85
76
  code: 'PLATFORM_PROVIDER_CONTRACT_INVALID',
86
77
  message: `Provider operation group ${groupName} requires an identity declaration for ${resourceKind}`,
87
- retryable: false,
88
- providerType,
89
- phase: 'provider-validation'
90
78
  }
91
79
  };
92
80
  }
@@ -462,9 +462,11 @@ const PROVIDER_ERROR_CATALOG = {
462
462
  REMOTE_INVALID: { message: 'The platform remote is invalid', retryable: false },
463
463
  UPSTREAM_UNRESOLVED: { message: 'The platform upstream repository could not be resolved', retryable: false },
464
464
  RESOURCE_NOT_FOUND: { message: 'The platform resource was not found', retryable: false },
465
+ COMMENT_LIST_FAILED: { message: 'Comments could not be listed', retryable: true },
465
466
  PR_NOT_FOUND: { message: 'The pull request was not found', retryable: false },
466
467
  ISSUE_NOT_FOUND: { message: 'The issue was not found', retryable: false },
467
468
  REMOTE_BUSY: { message: 'Platform provider is temporarily busy', retryable: true },
469
+ IN_LABEL_SYNC_PARTIAL: { message: 'In-label synchronization is partial or unknown', retryable: true },
468
470
  NETWORK_TRANSIENT: { message: 'The platform request failed temporarily', retryable: true },
469
471
  NETWORK_RETRY: { message: 'The platform request failed temporarily', retryable: true },
470
472
  NETWORK_RETRY_EXHAUSTED: { message: 'The platform request retries were exhausted', retryable: true },
@@ -505,11 +507,14 @@ function safeProviderError(providerType, operation, value) {
505
507
  return {
506
508
  code: definition ? item.code : 'PLATFORM_PROVIDER_OPERATION_FAILED',
507
509
  message: definition?.message || PROVIDER_ERROR_CATALOG.PLATFORM_PROVIDER_OPERATION_FAILED.message,
508
- retryable: definition?.retryable || false,
510
+ retryable: definition?.retryable ?? false,
509
511
  providerType,
510
512
  phase: operation
511
513
  };
512
514
  }
515
+ function providerErrorRetryable(code) {
516
+ return PROVIDER_ERROR_CATALOG[code]?.retryable ?? false;
517
+ }
513
518
  function validators(providerType, declaration) {
514
519
  return {
515
520
  'context.resolve': (value) => validateContext(value, providerType),
@@ -608,8 +613,8 @@ async function invokeProviderOperation(providerType, operation, call, validate)
608
613
  try {
609
614
  exactKeys(envelope, Object.hasOwn(envelope, 'value') ? ['ok', 'error', 'value'] : ['ok', 'error'], `${operation} result`);
610
615
  const error = envelope.error;
611
- exactKeys(error, ['code', 'message', 'retryable'], `${operation} error`);
612
- if (typeof error.code !== 'string' || !error.code || typeof error.message !== 'string' || !error.message || typeof error.retryable !== 'boolean')
616
+ exactKeys(error, ['code', 'message'], `${operation} error`);
617
+ if (typeof error.code !== 'string' || !error.code || typeof error.message !== 'string' || !error.message)
613
618
  throw new Error('invalid provider error');
614
619
  return {
615
620
  ok: false,
@@ -648,5 +653,5 @@ function wrapProviderOperations(provider) {
648
653
  }
649
654
  return wrapped;
650
655
  }
651
- export { invokeProviderOperation, validationError, wrapProviderOperations };
656
+ export { invokeProviderOperation, providerErrorRetryable, validationError, wrapProviderOperations };
652
657
  //# sourceMappingURL=provider-validation.js.map
@@ -9,6 +9,7 @@ import { extractSection } from '../task/sections.js';
9
9
  import { captureTaskWriteMetadata, writeTask } from '../task/write.js';
10
10
  import { resolvePlatformProviderContext } from './context.js';
11
11
  import { inspectPlatformIssue } from './issues.js';
12
+ import { providerErrorRetryable } from './provider-validation.js';
12
13
  import { planPullRequestMetadata } from './pull-request-metadata.js';
13
14
  import { planInLabelUpdate, validateInLabelMapping } from './in-label-sync.js';
14
15
  import { platformResult } from './types.js';
@@ -582,9 +583,10 @@ async function syncPlatformPullRequestInLabels(prNumber, options = {}) {
582
583
  : unsupportedProviderOperation(provider, 'changeRequests.update');
583
584
  if (!synced.ok) {
584
585
  const state = resourcesState.find((resource) => resource.kind === item.kind && resourceIdentityEquals(resource.identity, item.identity));
585
- state.effect = synced.error.retryable ? 'unknown' : 'no-op';
586
+ const retryable = synced.error.retryable || providerErrorRetryable(synced.error.code);
587
+ state.effect = retryable ? 'unknown' : 'no-op';
586
588
  state.after = state.effect === 'unknown' ? null : item.snapshot.labels;
587
- const partial = successfulWrites > 0 || synced.error.retryable;
589
+ const partial = successfulWrites > 0 || retryable;
588
590
  const error = partial
589
591
  ? { ...synced.error, code: 'IN_LABEL_SYNC_PARTIAL', message: `In-label synchronization is partial or unknown: ${synced.error.message}` }
590
592
  : synced.error;
@@ -9,6 +9,7 @@ import { accessSandboxTaskView, taskViewFromStatus } from './task-view.js';
9
9
  import { readSandboxControlIdentitySentinel } from './identity-sentinel.js';
10
10
  import { configuredShortIdLength, resolveShortIdReadOnly } from '../../task/short-id.js';
11
11
  const SANDBOX_CONTROL_RESPONSE_SETTLE_MS = 250;
12
+ const SANDBOX_TASK_FINALIZATION_RECOVERY_BUDGET_MS = 5 * 60_000;
12
13
  export class SandboxControlClientError extends Error {
13
14
  detail;
14
15
  accepted;
@@ -79,8 +80,10 @@ function preflight(statusDir, generation, taskViewEffect = null, now = Date.now(
79
80
  }
80
81
  }
81
82
  function taskViewEffectForRequest(request) {
82
- if (request.family === 'task-lifecycle' || request.family === 'task-finalization')
83
+ if (request.family === 'task-lifecycle')
83
84
  return 'progress';
85
+ if (request.family === 'task-finalization')
86
+ return 'recovery';
84
87
  return null;
85
88
  }
86
89
  function parseResponse(raw, id, accepted = false) {
@@ -298,15 +301,24 @@ export function recoverSandboxControl(requestId, params = {}) {
298
301
  else if (now - malformedResponseObservedAt >= SANDBOX_CONTROL_RESPONSE_SETTLE_MS) {
299
302
  clientError('SANDBOX_CONTROL_RESPONSE_INVALID', 'broker response remained malformed', false, accepted, requestId);
300
303
  }
301
- sleep(25);
304
+ const remainingMs = deadline - Date.now();
305
+ if (remainingMs > 0)
306
+ sleep(Math.min(25, Math.ceil(remainingMs)));
302
307
  continue;
303
308
  }
304
- if (response.phase === 'accepted')
309
+ if (response.phase === 'accepted') {
310
+ accepted = true;
311
+ }
312
+ else if (response.phase === 'rejected' && response.error?.code === 'SANDBOX_CONTROL_RESULT_UNKNOWN') {
305
313
  accepted = true;
306
- else
314
+ }
315
+ else {
307
316
  return response;
317
+ }
308
318
  }
309
- sleep(25);
319
+ const remainingMs = deadline - Date.now();
320
+ if (remainingMs > 0)
321
+ sleep(Math.min(25, Math.ceil(remainingMs)));
310
322
  }
311
323
  clientError('SANDBOX_CONTROL_RESULT_UNKNOWN', 'request did not produce a final result; inspect domain state before retrying', false, accepted, requestId);
312
324
  }
@@ -356,7 +368,43 @@ export function requestSandboxTaskFinalization(params) {
356
368
  controllerProcess: null,
357
369
  controllerProof: null
358
370
  };
359
- return exchangeSandboxControl(request, params);
371
+ let response;
372
+ try {
373
+ response = exchangeSandboxControl(request, params);
374
+ }
375
+ catch (error) {
376
+ if (!(error instanceof SandboxControlClientError)
377
+ || error.detail.code !== 'SANDBOX_CONTROL_RESULT_UNKNOWN'
378
+ || !error.accepted || !error.requestId)
379
+ throw error;
380
+ return recoverAcceptedTaskFinalization(error.requestId, params);
381
+ }
382
+ if (response.phase === 'rejected' && response.error?.code === 'SANDBOX_CONTROL_RESULT_UNKNOWN') {
383
+ return recoverAcceptedTaskFinalization(response.id, params);
384
+ }
385
+ return response;
386
+ }
387
+ export function recoverAcceptedTaskFinalization(requestId, params, recover = recoverSandboxControl) {
388
+ const deadline = Date.now() + (params.recoveryBudgetMs ?? SANDBOX_TASK_FINALIZATION_RECOVERY_BUDGET_MS);
389
+ while (Date.now() < deadline) {
390
+ const timeoutMs = Math.min(30_000, deadline - Date.now());
391
+ if (timeoutMs <= 0)
392
+ break;
393
+ try {
394
+ const response = recover(requestId, { channelDir: params.channelDir, timeoutMs });
395
+ if (response.phase === 'rejected' && response.error?.code === 'SANDBOX_CONTROL_RESULT_UNKNOWN')
396
+ continue;
397
+ return response;
398
+ }
399
+ catch (error) {
400
+ if (error instanceof SandboxControlClientError
401
+ && error.detail.code === 'SANDBOX_CONTROL_RESULT_UNKNOWN'
402
+ && error.requestId === requestId)
403
+ continue;
404
+ throw error;
405
+ }
406
+ }
407
+ clientError('SANDBOX_CONTROL_RESULT_UNKNOWN', 'accepted finalization request remained unknown after the automatic recovery budget; inspect the receipt and resume by request id', false, true, requestId);
360
408
  }
361
409
  function exactKeys(value, keys) {
362
410
  return Object.keys(value).sort().join(',') === [...keys].sort().join(',');