@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
|
@@ -21,7 +21,7 @@ import type {
|
|
|
21
21
|
MutationReceipt,
|
|
22
22
|
MilestoneInitialization,
|
|
23
23
|
PlatformContextSnapshot,
|
|
24
|
-
|
|
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:
|
|
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'
|
|
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:
|
|
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'
|
|
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}
|
|
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
|
|
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)
|
|
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
|
|
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'
|
|
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)
|
|
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}
|
|
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
|
|
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
|
|
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
|
|
47
|
+
function failure(error: { code: string; message: string; retryable?: boolean }) {
|
|
48
|
+
const retryable = error.retryable ?? providerErrorRetryable(error.code);
|
|
47
49
|
return {
|
|
48
|
-
status:
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
):
|
|
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
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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<
|
|
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<
|
|
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 {
|