@fitlab-ai/agent-infra 0.11.9 → 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/dist/bin/internal-cli.js +0 -6
- package/dist/lib/git/workflow.js +1 -1
- 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/removal.js +0 -1
- 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 +10 -36
- 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/write.js +0 -11
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/git/workflow.ts +1 -1
- 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/removal.ts +2 -10
- 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 +10 -35
- 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/write.ts +0 -19
- package/package.json +1 -1
- 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/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/complete-task/SKILL.en.md +15 -12
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +11 -10
- package/templates/.agents/skills/update-agent-infra/SKILL.en.md +1 -1
- package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +1 -1
- package/templates/.agents/templates/task.zh-CN.md +0 -12
- package/templates/.github/workflows/pr-label.yml +1 -2
- 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/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
|
@@ -3,12 +3,11 @@ import {
|
|
|
3
3
|
dispatchTaskControlOperation,
|
|
4
4
|
parseTaskControlOperation
|
|
5
5
|
} from '../task/control-authority.ts';
|
|
6
|
-
import { applyTaskFinalization
|
|
7
|
-
import { publishTaskFinalizationHandoff } from '../task/finalization-handoff.ts';
|
|
6
|
+
import { applyTaskFinalization } from '../task/finalization.ts';
|
|
8
7
|
import { detectRepoRoot, resolveTaskRef } from '../task/resolve-ref.ts';
|
|
9
|
-
import { verifyTaskEvent } from '../task/verification.ts';
|
|
10
8
|
import { resolveSandboxControlTransport } from './task-operation-registry.ts';
|
|
11
9
|
import { requestSandboxTaskFinalization, SandboxControlClientError } from '../sandbox/control/client.ts';
|
|
10
|
+
import { serializeTaskFinalizationEnvelope } from '../task/finalization-envelope.ts';
|
|
12
11
|
import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.ts';
|
|
13
12
|
|
|
14
13
|
const USAGE = 'Usage: agent-infra-internal task-finalization <N | TASK-id> complete --agent <agent>\n';
|
|
@@ -17,12 +16,13 @@ type FinalizationError = Readonly<{ code: string; message: string; retryable: bo
|
|
|
17
16
|
|
|
18
17
|
function envelope(
|
|
19
18
|
status: 'completed' | 'failed' | 'blocked' | 'unknown',
|
|
20
|
-
changed: boolean,
|
|
19
|
+
changed: boolean | null,
|
|
21
20
|
accepted: boolean,
|
|
22
21
|
result: Awaited<ReturnType<typeof applyTaskFinalization>> | null,
|
|
23
|
-
error: FinalizationError | null
|
|
22
|
+
error: FinalizationError | null,
|
|
23
|
+
requestId: string | null = null
|
|
24
24
|
): string {
|
|
25
|
-
return
|
|
25
|
+
return serializeTaskFinalizationEnvelope({ status, changed, accepted, requestId, result, error });
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
function exitCode(status: 'completed' | 'failed' | 'blocked' | 'unknown'): number {
|
|
@@ -79,33 +79,25 @@ async function taskFinalization(args: string[] = []): Promise<void> {
|
|
|
79
79
|
};
|
|
80
80
|
const transport = resolveSandboxControlTransport(process.env, { localWorkflow: true });
|
|
81
81
|
if (transport.kind === 'sandbox-local') {
|
|
82
|
-
const prepared = await prepareTaskFinalization(boundOperation.request, {
|
|
83
|
-
repoRoot,
|
|
84
|
-
preflight: (request, options) => verifyTaskEvent({ ...request, event: 'complete-task.hard-preflight' }, options)
|
|
85
|
-
});
|
|
86
|
-
if (prepared.status !== 'prepared') {
|
|
87
|
-
process.stdout.write(envelope(prepared.status, prepared.changed, true, prepared, prepared.error));
|
|
88
|
-
process.exitCode = exitCode(prepared.status);
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
82
|
try {
|
|
92
83
|
const response = requestSandboxTaskFinalization({
|
|
93
|
-
agent: boundOperation.request.agent
|
|
94
|
-
prepareHandoff: (binding) => {
|
|
95
|
-
const receipt = bindTaskFinalizationReceipt(repoRoot, boundOperation.request.taskRef, binding);
|
|
96
|
-
return publishTaskFinalizationHandoff('/share/branch', receipt, binding);
|
|
97
|
-
}
|
|
84
|
+
agent: boundOperation.request.agent
|
|
98
85
|
});
|
|
99
86
|
process.stdout.write(response.stdout);
|
|
100
87
|
process.stderr.write(response.stderr);
|
|
101
88
|
process.exitCode = response.exitCode ?? 1;
|
|
102
89
|
return;
|
|
103
90
|
} catch (error) {
|
|
104
|
-
const
|
|
105
|
-
|
|
91
|
+
const clientError = error instanceof SandboxControlClientError ? error : null;
|
|
92
|
+
const detail = clientError
|
|
93
|
+
? clientError.detail
|
|
106
94
|
: { code: 'SANDBOX_CONTROL_CLIENT_FAILED', message: error instanceof Error ? error.message : String(error), retryable: false };
|
|
107
|
-
|
|
108
|
-
|
|
95
|
+
const status = detail.code === 'SANDBOX_CONTROL_RESULT_UNKNOWN'
|
|
96
|
+
? 'unknown'
|
|
97
|
+
: detail.retryable ? 'blocked' : 'failed';
|
|
98
|
+
const changed = status === 'unknown' ? null : false;
|
|
99
|
+
process.stdout.write(envelope(status, changed, clientError?.accepted ?? false, null, detail, clientError?.requestId ?? null));
|
|
100
|
+
process.exitCode = exitCode(status);
|
|
109
101
|
return;
|
|
110
102
|
}
|
|
111
103
|
}
|
|
@@ -142,9 +142,8 @@ function internalTaskRoutes(): TaskOperationDescriptor[] {
|
|
|
142
142
|
descriptor('internal', 'task-review', 'preflight', 'task-bound', 'artifact-write'),
|
|
143
143
|
descriptor('internal', 'task-review', 'finalize-summary', 'task-bound', 'progress'),
|
|
144
144
|
descriptor('internal', 'task-event', 'event', 'task-bound', 'progress'),
|
|
145
|
-
descriptor('internal', 'task-invalidation', 'reconcile', 'task-bound', 'progress'),
|
|
146
145
|
descriptor('internal', 'task-lifecycle', 'intent', 'task-bound', 'progress'),
|
|
147
|
-
descriptor('internal', 'task-finalization', 'complete', 'task-bound', '
|
|
146
|
+
descriptor('internal', 'task-finalization', 'complete', 'task-bound', 'recovery'),
|
|
148
147
|
descriptor('internal', 'task-platform-recovery', 'inspect', 'task-bound', 'diagnostic'),
|
|
149
148
|
descriptor('internal', 'task-platform-recovery', 'recover', 'task-bound', 'recovery'),
|
|
150
149
|
descriptor('internal', 'task-short-id', 'list', 'conditional', 'diagnostic'),
|
|
@@ -222,7 +221,7 @@ export const INTERNAL_DISPATCHER_ROUTES = Object.freeze([
|
|
|
222
221
|
'git-workflow', 'task-delivery', 'release-workflow', 'platform-release-notes', 'platform-security', 'platform-metadata', 'platform-context',
|
|
223
222
|
'platform-comment', 'platform-issue', 'platform-pr', 'platform-pr-review', 'pr-review-grade',
|
|
224
223
|
'platform-checks', 'task-context', 'task-ledger', 'task-warning', 'task-activity', 'task-artifact',
|
|
225
|
-
'task-orchestration', 'task-review', 'task-event', 'task-
|
|
224
|
+
'task-orchestration', 'task-review', 'task-event', 'task-lifecycle',
|
|
226
225
|
'task-finalization', 'task-platform-recovery', 'task-short-id', 'task-snapshot', 'task-verify', 'task-validate', 'manual-validation'
|
|
227
226
|
]);
|
|
228
227
|
export const PUBLIC_DISPATCHER_ROUTES = Object.freeze([
|
|
@@ -3,7 +3,7 @@ import { readPlatformOperationJournal } from '../task/platform-operation-journal
|
|
|
3
3
|
import { recoverPlatformOperations } from '../task/platform-operation-recovery.ts';
|
|
4
4
|
import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.ts';
|
|
5
5
|
|
|
6
|
-
const USAGE = 'Usage: agent-infra-internal task-platform-recovery <N | TASK-id> <inspect | recover> [--agent <agent>] [--selection required|deferred|all]\n';
|
|
6
|
+
const USAGE = 'Usage: agent-infra-internal task-platform-recovery <N | TASK-id> <inspect | recover> [--agent <agent>] [--selection required|deferred|all] [--attempts <positive-integer>]\n';
|
|
7
7
|
|
|
8
8
|
function option(args: readonly string[], name: string): string | undefined {
|
|
9
9
|
const index = args.indexOf(name);
|
|
@@ -38,14 +38,19 @@ async function taskPlatformRecovery(args: string[] = []): Promise<void> {
|
|
|
38
38
|
}
|
|
39
39
|
const agent = option(args, '--agent');
|
|
40
40
|
const selection = option(args, '--selection') ?? 'all';
|
|
41
|
+
const attemptsInput = option(args, '--attempts');
|
|
42
|
+
const attempts = attemptsInput === undefined ? undefined : Number(attemptsInput);
|
|
43
|
+
const validAttempts = attemptsInput === undefined
|
|
44
|
+
|| (attempts !== undefined && Number.isSafeInteger(attempts) && attempts > 0);
|
|
41
45
|
if (!agent || !/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/u.test(agent)
|
|
42
|
-
|| !['required', 'deferred', 'all'].includes(selection)
|
|
43
|
-
|
|
46
|
+
|| !['required', 'deferred', 'all'].includes(selection)
|
|
47
|
+
|| !validAttempts) {
|
|
48
|
+
process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: 'PLATFORM_OPERATION_RECOVERY_PAYLOAD_INVALID', message: 'recover requires a valid --agent, --selection, and positive --attempts when supplied' } })}\n`);
|
|
44
49
|
process.stderr.write(USAGE);
|
|
45
50
|
process.exitCode = 1;
|
|
46
51
|
return;
|
|
47
52
|
}
|
|
48
|
-
const result = await recoverPlatformOperations(task.taskId, selection as 'required' | 'deferred' | 'all', { agent, cwd: repoRoot });
|
|
53
|
+
const result = await recoverPlatformOperations(task.taskId, selection as 'required' | 'deferred' | 'all', { agent, cwd: repoRoot, ...(attempts === undefined ? {} : { attempts }) });
|
|
49
54
|
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
50
55
|
process.exitCode = result.status === 'applied' || result.status === 'no-op' ? 0 : result.status === 'blocked' ? 2 : 1;
|
|
51
56
|
}
|
|
@@ -10,6 +10,7 @@ import { applyWorkflowWarningIntent } from '../task/workflow-warning-intents.ts'
|
|
|
10
10
|
import { captureTaskWriteMetadata } from '../task/write.ts';
|
|
11
11
|
import { syncPlatformComment } from './issue-comments.ts';
|
|
12
12
|
import type { PlatformClient } from './context.ts';
|
|
13
|
+
import { providerErrorRetryable } from './provider-validation.ts';
|
|
13
14
|
import { platformResult } from './types.ts';
|
|
14
15
|
import type { PlatformOperation, PlatformResult } from './types.ts';
|
|
15
16
|
import { taskIssueIdentity } from './task-identities.ts';
|
|
@@ -151,7 +152,7 @@ async function backfillCompletionComments(
|
|
|
151
152
|
})));
|
|
152
153
|
artifacts.push({ artifact: artifact.name, status: latest.status });
|
|
153
154
|
if (latest.status === 'failed' || latest.status === 'blocked') return result({ ...latest, operations }, artifacts, []);
|
|
154
|
-
if (latest.error) return result(platformResult(latest.error.retryable ? 'blocked' : 'failed', {
|
|
155
|
+
if (latest.error) return result(platformResult(latest.error.retryable || providerErrorRetryable(latest.error.code) ? 'blocked' : 'failed', {
|
|
155
156
|
platform: latest.platform,
|
|
156
157
|
resource: latest.resource,
|
|
157
158
|
capabilities: latest.capabilities,
|
package/lib/platform/context.ts
CHANGED
|
@@ -2,9 +2,12 @@ import path from 'node:path';
|
|
|
2
2
|
|
|
3
3
|
import { loadPlatformProvider } from './provider-loader.ts';
|
|
4
4
|
import { defaultGitRemote, parseGitHubRemote } from './github-provider.ts';
|
|
5
|
+
import { providerErrorRetryable } from './provider-validation.ts';
|
|
5
6
|
import { platformResult } from './types.ts';
|
|
6
7
|
import type { PlatformResult } from './types.ts';
|
|
7
|
-
import type {
|
|
8
|
+
import type { PlatformContextSnapshot } from './provider-contract.ts';
|
|
9
|
+
import type { PlatformError } from './types.ts';
|
|
10
|
+
import type { LoadedRuntimeProvider } from './provider-validation.ts';
|
|
8
11
|
|
|
9
12
|
type PlatformClientResult<T> =
|
|
10
13
|
| { ok: true; value: T }
|
|
@@ -23,7 +26,7 @@ type ContextOptions = {
|
|
|
23
26
|
};
|
|
24
27
|
|
|
25
28
|
type LoadedContext = {
|
|
26
|
-
provider:
|
|
29
|
+
provider: LoadedRuntimeProvider;
|
|
27
30
|
providerType: string;
|
|
28
31
|
repositoryRoot: string;
|
|
29
32
|
workingDirectory: string;
|
|
@@ -33,7 +36,7 @@ type LoadedContext = {
|
|
|
33
36
|
};
|
|
34
37
|
|
|
35
38
|
function errorStatus(error: PlatformError): PlatformResult['status'] {
|
|
36
|
-
if (error.retryable || error.code === 'AUTH_REQUIRED' || error.code === 'PLATFORM_DEPENDENCY_MISSING') return 'blocked';
|
|
39
|
+
if (error.retryable || providerErrorRetryable(error.code) || error.code === 'AUTH_REQUIRED' || error.code === 'PLATFORM_DEPENDENCY_MISSING') return 'blocked';
|
|
37
40
|
if (error.code === 'REMOTE_MISSING' || error.code === 'PLATFORM_UNSUPPORTED') return 'no-op';
|
|
38
41
|
return 'failed';
|
|
39
42
|
}
|
|
@@ -43,9 +46,10 @@ function contextError(
|
|
|
43
46
|
error: PlatformError,
|
|
44
47
|
repository: string | null = null
|
|
45
48
|
): PlatformResult {
|
|
49
|
+
const retryable = error.retryable ?? providerErrorRetryable(error.code);
|
|
46
50
|
return platformResult(errorStatus(error), {
|
|
47
51
|
platform: { type: providerType || null, repository, currentUser: null },
|
|
48
|
-
error: { code: error.code, message: error.message, retryable
|
|
52
|
+
error: { code: error.code, message: error.message, retryable }
|
|
49
53
|
});
|
|
50
54
|
}
|
|
51
55
|
|
|
@@ -3,13 +3,13 @@ import spawn from 'cross-spawn';
|
|
|
3
3
|
|
|
4
4
|
import semver from 'semver';
|
|
5
5
|
|
|
6
|
-
import type { PlatformError } from './types.ts';
|
|
7
|
-
|
|
8
6
|
type RunResult = { status: number | null; stdout: string; stderr: string; error?: Error };
|
|
9
7
|
type RunOptions = { cwd?: string; input?: string };
|
|
10
8
|
type Runner = (args: string[], options: RunOptions) => RunResult;
|
|
11
9
|
type RequestOptions = RunOptions & { method?: 'GET' | 'PATCH' | 'POST' | 'PUT' | 'DELETE' };
|
|
12
|
-
type
|
|
10
|
+
type GitHubError = { code: string; message: string };
|
|
11
|
+
type ClientResult<T> = { ok: true; value: T } | { ok: false; error: GitHubError };
|
|
12
|
+
type GitHubRequestFailure = GitHubError & { requestRetryable: boolean };
|
|
13
13
|
type ResponseMetadata = {
|
|
14
14
|
status: number;
|
|
15
15
|
requestUrl: string;
|
|
@@ -86,36 +86,40 @@ function defaultSleep(delayMs: number): void {
|
|
|
86
86
|
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, delayMs);
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
function classifyGitHubFailure(result: RunResult):
|
|
89
|
+
function classifyGitHubFailure(result: RunResult): GitHubRequestFailure {
|
|
90
90
|
const errorCode = result.error && 'code' in result.error ? (result.error as NodeJS.ErrnoException).code : undefined;
|
|
91
91
|
if (errorCode === 'ENOBUFS') {
|
|
92
92
|
return {
|
|
93
93
|
code: 'PLATFORM_OUTPUT_TOO_LARGE',
|
|
94
94
|
message: 'GitHub CLI output exceeded the configured limit',
|
|
95
|
-
|
|
95
|
+
requestRetryable: false
|
|
96
96
|
};
|
|
97
97
|
}
|
|
98
98
|
const detail = boundedFailureDetail(result);
|
|
99
99
|
const lower = detail.toLowerCase();
|
|
100
100
|
if (/\b401\b|bad credentials|authentication required|not logged into/.test(lower)) {
|
|
101
|
-
return { code: 'AUTH_REQUIRED', message: detail || 'GitHub authentication is required',
|
|
101
|
+
return { code: 'AUTH_REQUIRED', message: detail || 'GitHub authentication is required', requestRetryable: false };
|
|
102
102
|
}
|
|
103
103
|
if (/\b429\b|rate limit|secondary rate|\b5\d\d\b|timeout|timed out|econnreset|enotfound|dns|tls|socket|network/.test(lower)) {
|
|
104
|
-
return { code: 'NETWORK_TRANSIENT', message: detail || 'GitHub request failed temporarily',
|
|
104
|
+
return { code: 'NETWORK_TRANSIENT', message: detail || 'GitHub request failed temporarily', requestRetryable: true };
|
|
105
105
|
}
|
|
106
106
|
if (/\b403\b|resource not accessible|permission denied/.test(lower)) {
|
|
107
|
-
return { code: 'PERMISSION_DENIED', message: detail || 'GitHub permission denied',
|
|
107
|
+
return { code: 'PERMISSION_DENIED', message: detail || 'GitHub permission denied', requestRetryable: false };
|
|
108
108
|
}
|
|
109
109
|
if (/\b404\b/.test(lower)) {
|
|
110
|
-
return { code: 'RESOURCE_NOT_FOUND', message: detail || 'GitHub resource not found',
|
|
110
|
+
return { code: 'RESOURCE_NOT_FOUND', message: detail || 'GitHub resource not found', requestRetryable: false };
|
|
111
111
|
}
|
|
112
112
|
if (/\b422\b|validation failed/.test(lower)) {
|
|
113
|
-
return { code: 'PLATFORM_REQUEST_INVALID', message: detail || 'GitHub rejected the request',
|
|
113
|
+
return { code: 'PLATFORM_REQUEST_INVALID', message: detail || 'GitHub rejected the request', requestRetryable: false };
|
|
114
114
|
}
|
|
115
115
|
if (errorCode === 'ENOENT') {
|
|
116
|
-
return { code: 'PLATFORM_DEPENDENCY_MISSING', message: detail || 'GitHub CLI is unavailable',
|
|
116
|
+
return { code: 'PLATFORM_DEPENDENCY_MISSING', message: detail || 'GitHub CLI is unavailable', requestRetryable: false };
|
|
117
117
|
}
|
|
118
|
-
return { code: 'PLATFORM_REQUEST_FAILED', message: detail || 'GitHub request failed',
|
|
118
|
+
return { code: 'PLATFORM_REQUEST_FAILED', message: detail || 'GitHub request failed', requestRetryable: false };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function clientError(error: GitHubError): GitHubError {
|
|
122
|
+
return { code: error.code, message: error.message };
|
|
119
123
|
}
|
|
120
124
|
|
|
121
125
|
function responseUrl(args: string[]): string {
|
|
@@ -164,13 +168,13 @@ function createGitHubClient(options: ClientOptions = {}): GitHubClient {
|
|
|
164
168
|
const result = runner(args, request);
|
|
165
169
|
if (result.status === 0) return { ok: true, value: result.stdout };
|
|
166
170
|
const error = classifyGitHubFailure(result);
|
|
167
|
-
if (!retryableMethod || !error.
|
|
171
|
+
if (!retryableMethod || !error.requestRetryable) return { ok: false, error: clientError(error) };
|
|
168
172
|
if (attempt >= delays.length) {
|
|
169
173
|
return {
|
|
170
174
|
ok: false,
|
|
171
175
|
error: error.code === 'NETWORK_TRANSIENT'
|
|
172
|
-
? { ...error, code: 'NETWORK_RETRY_EXHAUSTED', message: `${error.message} (retry exhausted)` }
|
|
173
|
-
: error
|
|
176
|
+
? { ...clientError(error), code: 'NETWORK_RETRY_EXHAUSTED', message: `${error.message} (retry exhausted)` }
|
|
177
|
+
: clientError(error)
|
|
174
178
|
};
|
|
175
179
|
}
|
|
176
180
|
sleep(delays[attempt]!);
|
|
@@ -181,12 +185,12 @@ function createGitHubClient(options: ClientOptions = {}): GitHubClient {
|
|
|
181
185
|
return {
|
|
182
186
|
version(request = {}) {
|
|
183
187
|
const result = runner(['--version'], request);
|
|
184
|
-
if (result.status !== 0) return { ok: false, error: classifyGitHubFailure(result) };
|
|
188
|
+
if (result.status !== 0) return { ok: false, error: clientError(classifyGitHubFailure(result)) };
|
|
185
189
|
const version = result.stdout.match(/^gh version (\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)(?:\s|$)/m)?.[1];
|
|
186
190
|
if (!version || !semver.valid(version)) {
|
|
187
191
|
return {
|
|
188
192
|
ok: false,
|
|
189
|
-
error: { code: 'GH_CLI_VERSION_INVALID', message: 'GitHub CLI returned an invalid version'
|
|
193
|
+
error: { code: 'GH_CLI_VERSION_INVALID', message: 'GitHub CLI returned an invalid version' }
|
|
190
194
|
};
|
|
191
195
|
}
|
|
192
196
|
return { ok: true, value: version };
|
|
@@ -203,7 +207,7 @@ function createGitHubClient(options: ClientOptions = {}): GitHubClient {
|
|
|
203
207
|
} catch {
|
|
204
208
|
return {
|
|
205
209
|
ok: false,
|
|
206
|
-
error: { code: 'INVALID_PLATFORM_RESPONSE', message: 'GitHub returned invalid JSON'
|
|
210
|
+
error: { code: 'INVALID_PLATFORM_RESPONSE', message: 'GitHub returned invalid JSON' }
|
|
207
211
|
};
|
|
208
212
|
}
|
|
209
213
|
},
|
|
@@ -217,7 +221,7 @@ function createGitHubClient(options: ClientOptions = {}): GitHubClient {
|
|
|
217
221
|
if (!parsed) {
|
|
218
222
|
return {
|
|
219
223
|
ok: false,
|
|
220
|
-
error: { code: 'INVALID_PLATFORM_RESPONSE', message: 'GitHub response metadata or JSON is invalid'
|
|
224
|
+
error: { code: 'INVALID_PLATFORM_RESPONSE', message: 'GitHub response metadata or JSON is invalid' }
|
|
221
225
|
};
|
|
222
226
|
}
|
|
223
227
|
return { ok: true, value: parsed };
|
|
@@ -225,5 +229,5 @@ function createGitHubClient(options: ClientOptions = {}): GitHubClient {
|
|
|
225
229
|
};
|
|
226
230
|
}
|
|
227
231
|
|
|
228
|
-
export {
|
|
232
|
+
export { createGitHubClient, MINIMUM_GITHUB_CLI_VERSION, parseIncludedResponse };
|
|
229
233
|
export type { ClientResult, GitHubClient, JsonResponse, RequestOptions, ResponseMetadata, RunOptions, RunResult, Runner };
|
|
@@ -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,
|