@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.
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/dist/bin/internal-cli.js +0 -6
- package/dist/lib/agent-clients/custom-tool-invocations.js +92 -0
- package/dist/lib/agent-clients/next-steps.js +1 -1
- package/dist/lib/agent-clients/reconcile.js +3 -3
- package/dist/lib/agent-clients/schema.js +18 -10
- package/dist/lib/git/workflow.js +1 -1
- package/dist/lib/init.js +2 -2
- package/dist/lib/internal/agent-client.js +3 -3
- package/dist/lib/internal/cli-route-inventory.js +0 -3
- package/dist/lib/internal/sandbox-control.js +30 -5
- package/dist/lib/internal/task-finalization.js +14 -23
- package/dist/lib/internal/task-operation-registry.js +2 -3
- package/dist/lib/internal/task-platform-recovery.js +9 -4
- package/dist/lib/platform/completion-backfill.js +2 -1
- package/dist/lib/platform/context.js +4 -2
- package/dist/lib/platform/github-client.js +20 -17
- package/dist/lib/platform/github-provider.js +42 -22
- package/dist/lib/platform/github-release-notes.js +4 -2
- package/dist/lib/platform/in-label-sync.js +2 -1
- package/dist/lib/platform/issue-comments.js +13 -9
- package/dist/lib/platform/operation-coordinator.js +1 -1
- package/dist/lib/platform/pr-review.js +2 -1
- package/dist/lib/platform/provider-bridge.js +4 -5
- package/dist/lib/platform/provider-contract.d.ts +3 -6
- package/dist/lib/platform/provider-contract.js +3 -15
- package/dist/lib/platform/provider-validation.js +9 -4
- package/dist/lib/platform/pull-requests.js +4 -2
- package/dist/lib/sandbox/control/client.js +54 -6
- package/dist/lib/sandbox/control/executor.js +8 -3
- package/dist/lib/sandbox/recovery.js +1 -22
- package/dist/lib/sandbox/removal.js +0 -1
- package/dist/lib/sandbox/tools.js +2 -0
- package/dist/lib/task/artifact-command.js +11 -1
- package/dist/lib/task/artifact-lifecycle.js +119 -62
- package/dist/lib/task/artifact-selection.js +1 -1
- package/dist/lib/task/capabilities.js +55 -21
- package/dist/lib/task/commands/status.js +0 -27
- package/dist/lib/task/commit-identity.js +22 -7
- package/dist/lib/task/commit-intent.js +67 -26
- package/dist/lib/task/commit-operation.js +19 -8
- package/dist/lib/task/completion-facts.js +32 -10
- package/dist/lib/task/control-authority.js +2 -4
- package/dist/lib/task/current-contract.js +1 -5
- package/dist/lib/task/events.js +8 -274
- package/dist/lib/task/execution-inputs.js +14 -0
- package/dist/lib/task/finalization-envelope.js +4 -0
- package/dist/lib/task/finalization.js +100 -129
- package/dist/lib/task/frontmatter.js +1 -1
- package/dist/lib/task/ledger-intents.js +4 -7
- package/dist/lib/task/orchestration.js +11 -10
- package/dist/lib/task/platform-operation-journal.js +17 -10
- package/dist/lib/task/platform-operation-recovery.js +95 -28
- package/dist/lib/task/review-fingerprint.js +4 -26
- package/dist/lib/task/verification-engine.js +95 -6
- package/dist/lib/task/verification.js +0 -1
- package/dist/lib/task/write.js +0 -11
- package/dist/lib/update.js +2 -2
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/custom-tool-invocations.ts +129 -0
- package/lib/agent-clients/next-steps.ts +3 -3
- package/lib/agent-clients/reconcile.ts +3 -3
- package/lib/agent-clients/schema.ts +18 -10
- package/lib/git/workflow.ts +1 -1
- package/lib/init.ts +2 -2
- package/lib/internal/agent-client.ts +3 -3
- package/lib/internal/cli-route-inventory.ts +0 -2
- package/lib/internal/sandbox-control.ts +39 -6
- package/lib/internal/task-finalization.ts +16 -24
- package/lib/internal/task-operation-registry.ts +2 -3
- package/lib/internal/task-platform-recovery.ts +9 -4
- package/lib/platform/completion-backfill.ts +2 -1
- package/lib/platform/context.ts +8 -4
- package/lib/platform/github-client.ts +24 -20
- package/lib/platform/github-provider.ts +47 -26
- package/lib/platform/github-release-notes.ts +5 -3
- package/lib/platform/in-label-sync.ts +2 -1
- package/lib/platform/issue-comments.ts +14 -9
- package/lib/platform/operation-coordinator.ts +1 -1
- package/lib/platform/pr-review.ts +2 -1
- package/lib/platform/provider-bridge.ts +10 -9
- package/lib/platform/provider-contract.ts +6 -21
- package/lib/platform/provider-loader.ts +7 -6
- package/lib/platform/provider-validation.ts +30 -8
- package/lib/platform/pull-requests.ts +4 -2
- package/lib/platform/repository-metadata.ts +3 -2
- package/lib/platform/security-alerts.ts +1 -1
- package/lib/platform/types.ts +2 -0
- package/lib/sandbox/control/client.ts +57 -6
- package/lib/sandbox/control/executor.ts +8 -3
- package/lib/sandbox/recovery.ts +0 -25
- package/lib/sandbox/removal.ts +2 -10
- package/lib/sandbox/tool-types.ts +1 -0
- package/lib/sandbox/tools.ts +2 -0
- package/lib/task/artifact-command.ts +10 -1
- package/lib/task/artifact-lifecycle.ts +100 -52
- package/lib/task/artifact-selection.ts +1 -1
- package/lib/task/capabilities.ts +47 -24
- package/lib/task/commands/status.ts +0 -31
- package/lib/task/commit-identity.ts +24 -7
- package/lib/task/commit-intent.ts +58 -24
- package/lib/task/commit-operation.ts +17 -8
- package/lib/task/completion-facts.ts +31 -9
- package/lib/task/control-authority.ts +1 -4
- package/lib/task/current-contract.ts +1 -6
- package/lib/task/events.ts +8 -247
- package/lib/task/execution-inputs.ts +25 -0
- package/lib/task/finalization-envelope.ts +21 -0
- package/lib/task/finalization.ts +101 -123
- package/lib/task/frontmatter.ts +1 -1
- package/lib/task/ledger-intents.ts +4 -7
- package/lib/task/orchestration.ts +11 -17
- package/lib/task/platform-operation-journal.ts +18 -11
- package/lib/task/platform-operation-recovery.ts +90 -31
- package/lib/task/review-fingerprint.ts +4 -26
- package/lib/task/verification-engine.ts +103 -9
- package/lib/task/verification.ts +1 -2
- package/lib/task/write.ts +0 -19
- package/lib/update.ts +2 -3
- package/package.json +1 -1
- package/templates/.agents/README.en.md +2 -50
- package/templates/.agents/README.zh-CN.md +2 -50
- package/templates/.agents/rules/commit-and-pr.en.md +5 -5
- package/templates/.agents/rules/commit-and-pr.zh-CN.md +5 -5
- package/templates/.agents/rules/decision-qualification.en.md +1 -1
- package/templates/.agents/rules/decision-qualification.zh-CN.md +1 -1
- package/templates/.agents/rules/next-step-output.en.md +1 -1
- package/templates/.agents/rules/next-step-output.zh-CN.md +1 -1
- package/templates/.agents/scripts/lib/agent-infra-package.js +0 -7
- package/templates/.agents/skills/code-task/SKILL.en.md +25 -9
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +26 -9
- package/templates/.agents/skills/code-task/reference/code-rules.en.md +1 -1
- package/templates/.agents/skills/code-task/reference/code-rules.zh-CN.md +1 -1
- package/templates/.agents/skills/code-task/reference/fix-mode.en.md +5 -16
- package/templates/.agents/skills/code-task/reference/fix-mode.zh-CN.md +5 -16
- package/templates/.agents/skills/code-task/reference/report-template.en.md +1 -1
- package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +1 -1
- package/templates/.agents/skills/commit/SKILL.en.md +1 -1
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/complete-task/SKILL.en.md +15 -12
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +11 -10
- package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +1 -1
- package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +1 -1
- package/templates/.agents/skills/review-code/SKILL.en.md +1 -1
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/review-code/reference/output-templates.en.md +15 -6
- package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +15 -6
- package/templates/.agents/skills/update-agent-infra/SKILL.en.md +2 -2
- package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +2 -268
- package/templates/.agents/templates/task.zh-CN.md +0 -12
- package/templates/.github/workflows/pr-label.yml +1 -2
- package/dist/lib/agent-clients/custom-tuis.js +0 -99
- package/dist/lib/internal/task-invalidation.js +0 -63
- package/dist/lib/task/intermediate-cleanup.js +0 -327
- package/dist/lib/task/invalidation-command.js +0 -88
- package/dist/lib/task/invalidation.js +0 -242
- package/lib/agent-clients/custom-tuis.ts +0 -149
- package/lib/internal/task-invalidation.ts +0 -41
- package/lib/task/intermediate-cleanup.ts +0 -393
- package/lib/task/invalidation-command.ts +0 -88
- 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'
|
|
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'
|
|
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}
|
|
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
|
|
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)
|
|
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
|
|
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'
|
|
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)
|
|
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}
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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,
|
|
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
|
|
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'
|
|
612
|
-
if (typeof error.code !== 'string' || !error.code || typeof error.message !== 'string' || !error.message
|
|
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
|
-
|
|
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 ||
|
|
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'
|
|
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
|
-
|
|
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
|
-
|
|
314
|
+
}
|
|
315
|
+
else {
|
|
307
316
|
return response;
|
|
317
|
+
}
|
|
308
318
|
}
|
|
309
|
-
|
|
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
|
-
|
|
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(',');
|