@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.
Files changed (124) hide show
  1. package/dist/bin/internal-cli.js +0 -6
  2. package/dist/lib/git/workflow.js +1 -1
  3. package/dist/lib/internal/cli-route-inventory.js +0 -3
  4. package/dist/lib/internal/sandbox-control.js +30 -5
  5. package/dist/lib/internal/task-finalization.js +14 -23
  6. package/dist/lib/internal/task-operation-registry.js +2 -3
  7. package/dist/lib/internal/task-platform-recovery.js +9 -4
  8. package/dist/lib/platform/completion-backfill.js +2 -1
  9. package/dist/lib/platform/context.js +4 -2
  10. package/dist/lib/platform/github-client.js +20 -17
  11. package/dist/lib/platform/github-provider.js +42 -22
  12. package/dist/lib/platform/github-release-notes.js +4 -2
  13. package/dist/lib/platform/in-label-sync.js +2 -1
  14. package/dist/lib/platform/issue-comments.js +13 -9
  15. package/dist/lib/platform/operation-coordinator.js +1 -1
  16. package/dist/lib/platform/pr-review.js +2 -1
  17. package/dist/lib/platform/provider-bridge.js +4 -5
  18. package/dist/lib/platform/provider-contract.d.ts +3 -6
  19. package/dist/lib/platform/provider-contract.js +3 -15
  20. package/dist/lib/platform/provider-validation.js +9 -4
  21. package/dist/lib/platform/pull-requests.js +4 -2
  22. package/dist/lib/sandbox/control/client.js +54 -6
  23. package/dist/lib/sandbox/control/executor.js +8 -3
  24. package/dist/lib/sandbox/removal.js +0 -1
  25. package/dist/lib/task/artifact-command.js +11 -1
  26. package/dist/lib/task/artifact-lifecycle.js +119 -62
  27. package/dist/lib/task/artifact-selection.js +1 -1
  28. package/dist/lib/task/capabilities.js +55 -21
  29. package/dist/lib/task/commands/status.js +0 -27
  30. package/dist/lib/task/commit-identity.js +22 -7
  31. package/dist/lib/task/commit-intent.js +67 -26
  32. package/dist/lib/task/commit-operation.js +19 -8
  33. package/dist/lib/task/completion-facts.js +32 -10
  34. package/dist/lib/task/control-authority.js +2 -4
  35. package/dist/lib/task/current-contract.js +1 -5
  36. package/dist/lib/task/events.js +8 -274
  37. package/dist/lib/task/execution-inputs.js +14 -0
  38. package/dist/lib/task/finalization-envelope.js +4 -0
  39. package/dist/lib/task/finalization.js +10 -36
  40. package/dist/lib/task/frontmatter.js +1 -1
  41. package/dist/lib/task/ledger-intents.js +4 -7
  42. package/dist/lib/task/orchestration.js +11 -10
  43. package/dist/lib/task/platform-operation-journal.js +17 -10
  44. package/dist/lib/task/platform-operation-recovery.js +95 -28
  45. package/dist/lib/task/review-fingerprint.js +4 -26
  46. package/dist/lib/task/verification-engine.js +95 -6
  47. package/dist/lib/task/write.js +0 -11
  48. package/dist/lifecycle-build-manifest.json +3 -3
  49. package/lib/git/workflow.ts +1 -1
  50. package/lib/internal/cli-route-inventory.ts +0 -2
  51. package/lib/internal/sandbox-control.ts +39 -6
  52. package/lib/internal/task-finalization.ts +16 -24
  53. package/lib/internal/task-operation-registry.ts +2 -3
  54. package/lib/internal/task-platform-recovery.ts +9 -4
  55. package/lib/platform/completion-backfill.ts +2 -1
  56. package/lib/platform/context.ts +8 -4
  57. package/lib/platform/github-client.ts +24 -20
  58. package/lib/platform/github-provider.ts +47 -26
  59. package/lib/platform/github-release-notes.ts +5 -3
  60. package/lib/platform/in-label-sync.ts +2 -1
  61. package/lib/platform/issue-comments.ts +14 -9
  62. package/lib/platform/operation-coordinator.ts +1 -1
  63. package/lib/platform/pr-review.ts +2 -1
  64. package/lib/platform/provider-bridge.ts +10 -9
  65. package/lib/platform/provider-contract.ts +6 -21
  66. package/lib/platform/provider-loader.ts +7 -6
  67. package/lib/platform/provider-validation.ts +30 -8
  68. package/lib/platform/pull-requests.ts +4 -2
  69. package/lib/platform/repository-metadata.ts +3 -2
  70. package/lib/platform/security-alerts.ts +1 -1
  71. package/lib/platform/types.ts +2 -0
  72. package/lib/sandbox/control/client.ts +57 -6
  73. package/lib/sandbox/control/executor.ts +8 -3
  74. package/lib/sandbox/removal.ts +2 -10
  75. package/lib/task/artifact-command.ts +10 -1
  76. package/lib/task/artifact-lifecycle.ts +100 -52
  77. package/lib/task/artifact-selection.ts +1 -1
  78. package/lib/task/capabilities.ts +47 -24
  79. package/lib/task/commands/status.ts +0 -31
  80. package/lib/task/commit-identity.ts +24 -7
  81. package/lib/task/commit-intent.ts +58 -24
  82. package/lib/task/commit-operation.ts +17 -8
  83. package/lib/task/completion-facts.ts +31 -9
  84. package/lib/task/control-authority.ts +1 -4
  85. package/lib/task/current-contract.ts +1 -6
  86. package/lib/task/events.ts +8 -247
  87. package/lib/task/execution-inputs.ts +25 -0
  88. package/lib/task/finalization-envelope.ts +21 -0
  89. package/lib/task/finalization.ts +10 -35
  90. package/lib/task/frontmatter.ts +1 -1
  91. package/lib/task/ledger-intents.ts +4 -7
  92. package/lib/task/orchestration.ts +11 -17
  93. package/lib/task/platform-operation-journal.ts +18 -11
  94. package/lib/task/platform-operation-recovery.ts +90 -31
  95. package/lib/task/review-fingerprint.ts +4 -26
  96. package/lib/task/verification-engine.ts +103 -9
  97. package/lib/task/write.ts +0 -19
  98. package/package.json +1 -1
  99. package/templates/.agents/rules/commit-and-pr.en.md +5 -5
  100. package/templates/.agents/rules/commit-and-pr.zh-CN.md +5 -5
  101. package/templates/.agents/rules/decision-qualification.en.md +1 -1
  102. package/templates/.agents/rules/decision-qualification.zh-CN.md +1 -1
  103. package/templates/.agents/skills/code-task/SKILL.en.md +25 -9
  104. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +26 -9
  105. package/templates/.agents/skills/code-task/reference/code-rules.en.md +1 -1
  106. package/templates/.agents/skills/code-task/reference/code-rules.zh-CN.md +1 -1
  107. package/templates/.agents/skills/code-task/reference/fix-mode.en.md +5 -16
  108. package/templates/.agents/skills/code-task/reference/fix-mode.zh-CN.md +5 -16
  109. package/templates/.agents/skills/code-task/reference/report-template.en.md +1 -1
  110. package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +1 -1
  111. package/templates/.agents/skills/complete-task/SKILL.en.md +15 -12
  112. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +11 -10
  113. package/templates/.agents/skills/update-agent-infra/SKILL.en.md +1 -1
  114. package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +1 -1
  115. package/templates/.agents/templates/task.zh-CN.md +0 -12
  116. package/templates/.github/workflows/pr-label.yml +1 -2
  117. package/dist/lib/internal/task-invalidation.js +0 -63
  118. package/dist/lib/task/intermediate-cleanup.js +0 -327
  119. package/dist/lib/task/invalidation-command.js +0 -88
  120. package/dist/lib/task/invalidation.js +0 -242
  121. package/lib/internal/task-invalidation.ts +0 -41
  122. package/lib/task/intermediate-cleanup.ts +0 -393
  123. package/lib/task/invalidation-command.ts +0 -88
  124. package/lib/task/invalidation.ts +0 -313
@@ -13,7 +13,6 @@ const taskWorkflowCommand = command === 'task-artifact'
13
13
  || command === 'task-review'
14
14
  || command === 'task-event'
15
15
  || command === 'task-ledger'
16
- || command === 'task-invalidation'
17
16
  || command === 'task-warning';
18
17
  const taskPlatformRecoveryCommand = command === 'task-platform-recovery';
19
18
  const manualValidationWorkflowCommand = command === 'manual-validation';
@@ -231,11 +230,6 @@ if (!controlRouted && !taskViewGuardFailed && internalRouteRegistered)
231
230
  await taskEvent(process.argv.slice(3));
232
231
  break;
233
232
  }
234
- case 'task-invalidation': {
235
- const { taskInvalidation } = await import('../lib/internal/task-invalidation.js');
236
- taskInvalidation(process.argv.slice(3));
237
- break;
238
- }
239
233
  case 'task-lifecycle': {
240
234
  const { taskLifecycle } = await import('../lib/internal/task-lifecycle.js');
241
235
  await taskLifecycle(process.argv.slice(3));
@@ -103,7 +103,7 @@ function commitExplicitPaths(input, runner = defaultRunner) {
103
103
  if (tree !== input.expectedTree)
104
104
  return { status: 'failed', changed: false, snapshot: inspectGitWorkflow(input.cwd, runner).snapshot, operations: [{ name: 'stage', status: 'applied' }], error: { code: 'GIT_TREE_MISMATCH', message: `Expected staged tree ${input.expectedTree}, received ${tree ?? 'unavailable'}` } };
105
105
  }
106
- const commit = run(runner, input.cwd, ['commit', '-m', input.message]);
106
+ const commit = run(runner, input.cwd, ['commit', '--cleanup=verbatim', '-m', input.message]);
107
107
  const after = inspectGitWorkflow(input.cwd, runner);
108
108
  if (commit.status !== 0)
109
109
  return { status: 'failed', changed: false, snapshot: after.snapshot, operations: [{ name: 'commit', status: 'failed', message: commit.stderr.trim() }], error: { code: 'GIT_COMMIT_FAILED', message: commit.stderr.trim() } };
@@ -57,7 +57,6 @@ export const INTERNAL_HANDLER_ROUTE_SELECTORS = Object.freeze({
57
57
  'task-orchestration': ['status', 'progress'],
58
58
  'task-review': ['preflight', 'finalize-summary'],
59
59
  'task-event': ['event'],
60
- 'task-invalidation': ['reconcile'],
61
60
  'task-lifecycle': ['intent'],
62
61
  'task-finalization': ['complete'],
63
62
  'task-platform-recovery': ['inspect', 'recover'],
@@ -105,8 +104,6 @@ export function internalRouteSelector(command, args) {
105
104
  return args[1] === 'status' ? 'status' : args[1] ? 'progress' : '';
106
105
  if (command === 'task-review')
107
106
  return args[1] ?? '';
108
- if (command === 'task-invalidation')
109
- return args[1] ?? '';
110
107
  if (command === 'task-artifact')
111
108
  return args[1] ?? '';
112
109
  if (command === 'platform-pr-review' && first(args) === 'publish') {
@@ -4,8 +4,9 @@ import { serveSandboxControl } from '../sandbox/control/server.js';
4
4
  import { runSandboxControlExecutor } from '../sandbox/control/executor.js';
5
5
  import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.js';
6
6
  import { parseTaskCreateResult, taskCreateExitCode } from '../task/create-service.js';
7
- function writeFinalizationEnvelope(status, accepted, error, changed = false, result = null) {
8
- process.stdout.write(`${JSON.stringify({ version: 1, status, changed, accepted, result, error })}\n`);
7
+ import { serializeTaskFinalizationEnvelope } from '../task/finalization-envelope.js';
8
+ function writeFinalizationEnvelope(status, accepted, error, changed = false, result = null, requestId = null) {
9
+ process.stdout.write(serializeTaskFinalizationEnvelope({ status, changed, accepted, requestId, result, error }));
9
10
  }
10
11
  function finalizationErrorStatus(error) {
11
12
  if (error.code === 'SANDBOX_CONTROL_RESULT_UNKNOWN')
@@ -30,6 +31,28 @@ function recoveredExitCode(response) {
30
31
  }
31
32
  return response.exitCode ?? 1;
32
33
  }
34
+ function finalizationResponseOutput(response) {
35
+ try {
36
+ const value = JSON.parse(response.stdout);
37
+ if (value.version === 2
38
+ && value.status
39
+ && ['completed', 'failed', 'blocked', 'unknown'].includes(value.status)
40
+ && (typeof value.changed === 'boolean' || value.changed === null)) {
41
+ return serializeTaskFinalizationEnvelope({
42
+ status: value.status,
43
+ changed: value.changed,
44
+ accepted: true,
45
+ requestId: response.id,
46
+ result: value.result ?? null,
47
+ error: value.error ?? null
48
+ });
49
+ }
50
+ }
51
+ catch {
52
+ // Preserve non-envelope output for diagnostics from the executor.
53
+ }
54
+ return response.stdout;
55
+ }
33
56
  function sandboxFinalizationClient(args) {
34
57
  if (args.length !== 4 || !args[0] || args[1] !== 'complete' || args[2] !== '--agent' || !args[3]) {
35
58
  const error = { code: 'TASK_FINALIZATION_PAYLOAD_INVALID', message: 'task ref, complete intent, and --agent are required', retryable: false };
@@ -52,7 +75,8 @@ function sandboxFinalizationClient(args) {
52
75
  catch (error) {
53
76
  if (!(error instanceof SandboxControlClientError))
54
77
  throw error;
55
- writeFinalizationEnvelope(finalizationErrorStatus(error.detail), error.accepted, error.detail);
78
+ const status = finalizationErrorStatus(error.detail);
79
+ writeFinalizationEnvelope(status, error.accepted, error.detail, status === 'unknown' ? null : false, null, error.requestId);
56
80
  writeClientError(error);
57
81
  process.exitCode = error.detail.code === 'SANDBOX_CONTROL_RESULT_UNKNOWN' ? 1 : error.detail.retryable ? 2 : 1;
58
82
  return;
@@ -63,12 +87,13 @@ function sandboxFinalizationClient(args) {
63
87
  message: 'sandbox control rejected the finalization request',
64
88
  retryable: false
65
89
  };
66
- writeFinalizationEnvelope(finalizationErrorStatus(error), error.code === 'SANDBOX_CONTROL_RESULT_UNKNOWN', error);
90
+ const status = finalizationErrorStatus(error);
91
+ writeFinalizationEnvelope(status, error.code === 'SANDBOX_CONTROL_RESULT_UNKNOWN', error, status === 'unknown' ? null : false, null, response.id);
67
92
  process.stderr.write(`${error.message}\n`);
68
93
  process.exitCode = error.code === 'SANDBOX_CONTROL_RESULT_UNKNOWN' ? 1 : error.retryable ? 2 : 1;
69
94
  return;
70
95
  }
71
- process.stdout.write(response.stdout);
96
+ process.stdout.write(finalizationResponseOutput(response));
72
97
  process.stderr.write(response.stderr);
73
98
  process.exitCode = response.exitCode ?? 1;
74
99
  }
@@ -1,14 +1,13 @@
1
1
  import { createDirectHostExecutionContext, dispatchTaskControlOperation, parseTaskControlOperation } from '../task/control-authority.js';
2
- import { applyTaskFinalization, bindTaskFinalizationReceipt, prepareTaskFinalization } from '../task/finalization.js';
3
- import { publishTaskFinalizationHandoff } from '../task/finalization-handoff.js';
2
+ import { applyTaskFinalization } from '../task/finalization.js';
4
3
  import { detectRepoRoot, resolveTaskRef } from '../task/resolve-ref.js';
5
- import { verifyTaskEvent } from '../task/verification.js';
6
4
  import { resolveSandboxControlTransport } from './task-operation-registry.js';
7
5
  import { requestSandboxTaskFinalization, SandboxControlClientError } from '../sandbox/control/client.js';
6
+ import { serializeTaskFinalizationEnvelope } from '../task/finalization-envelope.js';
8
7
  import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.js';
9
8
  const USAGE = 'Usage: agent-infra-internal task-finalization <N | TASK-id> complete --agent <agent>\n';
10
- function envelope(status, changed, accepted, result, error) {
11
- return `${JSON.stringify({ version: 1, status, changed, accepted, result, error })}\n`;
9
+ function envelope(status, changed, accepted, result, error, requestId = null) {
10
+ return serializeTaskFinalizationEnvelope({ status, changed, accepted, requestId, result, error });
12
11
  }
13
12
  function exitCode(status) {
14
13
  return status === 'completed' ? 0 : status === 'blocked' ? 2 : 1;
@@ -65,22 +64,9 @@ async function taskFinalization(args = []) {
65
64
  };
66
65
  const transport = resolveSandboxControlTransport(process.env, { localWorkflow: true });
67
66
  if (transport.kind === 'sandbox-local') {
68
- const prepared = await prepareTaskFinalization(boundOperation.request, {
69
- repoRoot,
70
- preflight: (request, options) => verifyTaskEvent({ ...request, event: 'complete-task.hard-preflight' }, options)
71
- });
72
- if (prepared.status !== 'prepared') {
73
- process.stdout.write(envelope(prepared.status, prepared.changed, true, prepared, prepared.error));
74
- process.exitCode = exitCode(prepared.status);
75
- return;
76
- }
77
67
  try {
78
68
  const response = requestSandboxTaskFinalization({
79
- agent: boundOperation.request.agent,
80
- prepareHandoff: (binding) => {
81
- const receipt = bindTaskFinalizationReceipt(repoRoot, boundOperation.request.taskRef, binding);
82
- return publishTaskFinalizationHandoff('/share/branch', receipt, binding);
83
- }
69
+ agent: boundOperation.request.agent
84
70
  });
85
71
  process.stdout.write(response.stdout);
86
72
  process.stderr.write(response.stderr);
@@ -88,11 +74,16 @@ async function taskFinalization(args = []) {
88
74
  return;
89
75
  }
90
76
  catch (error) {
91
- const detail = error instanceof SandboxControlClientError
92
- ? error.detail
77
+ const clientError = error instanceof SandboxControlClientError ? error : null;
78
+ const detail = clientError
79
+ ? clientError.detail
93
80
  : { code: 'SANDBOX_CONTROL_CLIENT_FAILED', message: error instanceof Error ? error.message : String(error), retryable: false };
94
- process.stdout.write(envelope(detail.retryable ? 'blocked' : 'failed', false, false, null, detail));
95
- process.exitCode = detail.retryable ? 2 : 1;
81
+ const status = detail.code === 'SANDBOX_CONTROL_RESULT_UNKNOWN'
82
+ ? 'unknown'
83
+ : detail.retryable ? 'blocked' : 'failed';
84
+ const changed = status === 'unknown' ? null : false;
85
+ process.stdout.write(envelope(status, changed, clientError?.accepted ?? false, null, detail, clientError?.requestId ?? null));
86
+ process.exitCode = exitCode(status);
96
87
  return;
97
88
  }
98
89
  }
@@ -102,9 +102,8 @@ function internalTaskRoutes() {
102
102
  descriptor('internal', 'task-review', 'preflight', 'task-bound', 'artifact-write'),
103
103
  descriptor('internal', 'task-review', 'finalize-summary', 'task-bound', 'progress'),
104
104
  descriptor('internal', 'task-event', 'event', 'task-bound', 'progress'),
105
- descriptor('internal', 'task-invalidation', 'reconcile', 'task-bound', 'progress'),
106
105
  descriptor('internal', 'task-lifecycle', 'intent', 'task-bound', 'progress'),
107
- descriptor('internal', 'task-finalization', 'complete', 'task-bound', 'progress'),
106
+ descriptor('internal', 'task-finalization', 'complete', 'task-bound', 'recovery'),
108
107
  descriptor('internal', 'task-platform-recovery', 'inspect', 'task-bound', 'diagnostic'),
109
108
  descriptor('internal', 'task-platform-recovery', 'recover', 'task-bound', 'recovery'),
110
109
  descriptor('internal', 'task-short-id', 'list', 'conditional', 'diagnostic'),
@@ -179,7 +178,7 @@ export const INTERNAL_DISPATCHER_ROUTES = Object.freeze([
179
178
  'git-workflow', 'task-delivery', 'release-workflow', 'platform-release-notes', 'platform-security', 'platform-metadata', 'platform-context',
180
179
  'platform-comment', 'platform-issue', 'platform-pr', 'platform-pr-review', 'pr-review-grade',
181
180
  'platform-checks', 'task-context', 'task-ledger', 'task-warning', 'task-activity', 'task-artifact',
182
- 'task-orchestration', 'task-review', 'task-event', 'task-invalidation', 'task-lifecycle',
181
+ 'task-orchestration', 'task-review', 'task-event', 'task-lifecycle',
183
182
  'task-finalization', 'task-platform-recovery', 'task-short-id', 'task-snapshot', 'task-verify', 'task-validate', 'manual-validation'
184
183
  ]);
185
184
  export const PUBLIC_DISPATCHER_ROUTES = Object.freeze([
@@ -2,7 +2,7 @@ import { detectRepoRoot, resolveTaskRef } from '../task/resolve-ref.js';
2
2
  import { readPlatformOperationJournal } from '../task/platform-operation-journal.js';
3
3
  import { recoverPlatformOperations } from '../task/platform-operation-recovery.js';
4
4
  import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.js';
5
- const USAGE = 'Usage: agent-infra-internal task-platform-recovery <N | TASK-id> <inspect | recover> [--agent <agent>] [--selection required|deferred|all]\n';
5
+ const USAGE = 'Usage: agent-infra-internal task-platform-recovery <N | TASK-id> <inspect | recover> [--agent <agent>] [--selection required|deferred|all] [--attempts <positive-integer>]\n';
6
6
  function option(args, name) {
7
7
  const index = args.indexOf(name);
8
8
  return index >= 0 ? args[index + 1] : undefined;
@@ -41,14 +41,19 @@ async function taskPlatformRecovery(args = []) {
41
41
  }
42
42
  const agent = option(args, '--agent');
43
43
  const selection = option(args, '--selection') ?? 'all';
44
+ const attemptsInput = option(args, '--attempts');
45
+ const attempts = attemptsInput === undefined ? undefined : Number(attemptsInput);
46
+ const validAttempts = attemptsInput === undefined
47
+ || (attempts !== undefined && Number.isSafeInteger(attempts) && attempts > 0);
44
48
  if (!agent || !/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/u.test(agent)
45
- || !['required', 'deferred', 'all'].includes(selection)) {
46
- process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: 'PLATFORM_OPERATION_RECOVERY_PAYLOAD_INVALID', message: 'recover requires a valid --agent and --selection' } })}\n`);
49
+ || !['required', 'deferred', 'all'].includes(selection)
50
+ || !validAttempts) {
51
+ 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`);
47
52
  process.stderr.write(USAGE);
48
53
  process.exitCode = 1;
49
54
  return;
50
55
  }
51
- const result = await recoverPlatformOperations(task.taskId, selection, { agent, cwd: repoRoot });
56
+ const result = await recoverPlatformOperations(task.taskId, selection, { agent, cwd: repoRoot, ...(attempts === undefined ? {} : { attempts }) });
52
57
  process.stdout.write(`${JSON.stringify(result)}\n`);
53
58
  process.exitCode = result.status === 'applied' || result.status === 'no-op' ? 0 : result.status === 'blocked' ? 2 : 1;
54
59
  }
@@ -8,6 +8,7 @@ import { getOpenWorkflowWarnings } from '../task/workflow-warnings.js';
8
8
  import { applyWorkflowWarningIntent } from '../task/workflow-warning-intents.js';
9
9
  import { captureTaskWriteMetadata } from '../task/write.js';
10
10
  import { syncPlatformComment } from './issue-comments.js';
11
+ import { providerErrorRetryable } from './provider-validation.js';
11
12
  import { platformResult } from './types.js';
12
13
  import { taskIssueIdentity } from './task-identities.js';
13
14
  function result(base, artifacts, warnings) {
@@ -124,7 +125,7 @@ async function backfillCompletionComments(taskRef, options) {
124
125
  if (latest.status === 'failed' || latest.status === 'blocked')
125
126
  return result({ ...latest, operations }, artifacts, []);
126
127
  if (latest.error)
127
- return result(platformResult(latest.error.retryable ? 'blocked' : 'failed', {
128
+ return result(platformResult(latest.error.retryable || providerErrorRetryable(latest.error.code) ? 'blocked' : 'failed', {
128
129
  platform: latest.platform,
129
130
  resource: latest.resource,
130
131
  capabilities: latest.capabilities,
@@ -1,18 +1,20 @@
1
1
  import path from 'node:path';
2
2
  import { loadPlatformProvider } from './provider-loader.js';
3
3
  import { defaultGitRemote, parseGitHubRemote } from './github-provider.js';
4
+ import { providerErrorRetryable } from './provider-validation.js';
4
5
  import { platformResult } from './types.js';
5
6
  function errorStatus(error) {
6
- if (error.retryable || error.code === 'AUTH_REQUIRED' || error.code === 'PLATFORM_DEPENDENCY_MISSING')
7
+ if (error.retryable || providerErrorRetryable(error.code) || error.code === 'AUTH_REQUIRED' || error.code === 'PLATFORM_DEPENDENCY_MISSING')
7
8
  return 'blocked';
8
9
  if (error.code === 'REMOTE_MISSING' || error.code === 'PLATFORM_UNSUPPORTED')
9
10
  return 'no-op';
10
11
  return 'failed';
11
12
  }
12
13
  function contextError(providerType, error, repository = null) {
14
+ const retryable = error.retryable ?? providerErrorRetryable(error.code);
13
15
  return platformResult(errorStatus(error), {
14
16
  platform: { type: providerType || null, repository, currentUser: null },
15
- error: { code: error.code, message: error.message, retryable: error.retryable }
17
+ error: { code: error.code, message: error.message, retryable }
16
18
  });
17
19
  }
18
20
  function contextFromSnapshot(providerType, snapshot) {
@@ -59,30 +59,33 @@ function classifyGitHubFailure(result) {
59
59
  return {
60
60
  code: 'PLATFORM_OUTPUT_TOO_LARGE',
61
61
  message: 'GitHub CLI output exceeded the configured limit',
62
- retryable: false
62
+ requestRetryable: false
63
63
  };
64
64
  }
65
65
  const detail = boundedFailureDetail(result);
66
66
  const lower = detail.toLowerCase();
67
67
  if (/\b401\b|bad credentials|authentication required|not logged into/.test(lower)) {
68
- return { code: 'AUTH_REQUIRED', message: detail || 'GitHub authentication is required', retryable: false };
68
+ return { code: 'AUTH_REQUIRED', message: detail || 'GitHub authentication is required', requestRetryable: false };
69
69
  }
70
70
  if (/\b429\b|rate limit|secondary rate|\b5\d\d\b|timeout|timed out|econnreset|enotfound|dns|tls|socket|network/.test(lower)) {
71
- return { code: 'NETWORK_TRANSIENT', message: detail || 'GitHub request failed temporarily', retryable: true };
71
+ return { code: 'NETWORK_TRANSIENT', message: detail || 'GitHub request failed temporarily', requestRetryable: true };
72
72
  }
73
73
  if (/\b403\b|resource not accessible|permission denied/.test(lower)) {
74
- return { code: 'PERMISSION_DENIED', message: detail || 'GitHub permission denied', retryable: false };
74
+ return { code: 'PERMISSION_DENIED', message: detail || 'GitHub permission denied', requestRetryable: false };
75
75
  }
76
76
  if (/\b404\b/.test(lower)) {
77
- return { code: 'RESOURCE_NOT_FOUND', message: detail || 'GitHub resource not found', retryable: false };
77
+ return { code: 'RESOURCE_NOT_FOUND', message: detail || 'GitHub resource not found', requestRetryable: false };
78
78
  }
79
79
  if (/\b422\b|validation failed/.test(lower)) {
80
- return { code: 'PLATFORM_REQUEST_INVALID', message: detail || 'GitHub rejected the request', retryable: false };
80
+ return { code: 'PLATFORM_REQUEST_INVALID', message: detail || 'GitHub rejected the request', requestRetryable: false };
81
81
  }
82
82
  if (errorCode === 'ENOENT') {
83
- return { code: 'PLATFORM_DEPENDENCY_MISSING', message: detail || 'GitHub CLI is unavailable', retryable: false };
83
+ return { code: 'PLATFORM_DEPENDENCY_MISSING', message: detail || 'GitHub CLI is unavailable', requestRetryable: false };
84
84
  }
85
- return { code: 'PLATFORM_REQUEST_FAILED', message: detail || 'GitHub request failed', retryable: false };
85
+ return { code: 'PLATFORM_REQUEST_FAILED', message: detail || 'GitHub request failed', requestRetryable: false };
86
+ }
87
+ function clientError(error) {
88
+ return { code: error.code, message: error.message };
86
89
  }
87
90
  function responseUrl(args) {
88
91
  const endpoint = args.find((arg, index) => index > 0 && !arg.startsWith('-')) || '';
@@ -132,14 +135,14 @@ function createGitHubClient(options = {}) {
132
135
  if (result.status === 0)
133
136
  return { ok: true, value: result.stdout };
134
137
  const error = classifyGitHubFailure(result);
135
- if (!retryableMethod || !error.retryable)
136
- return { ok: false, error };
138
+ if (!retryableMethod || !error.requestRetryable)
139
+ return { ok: false, error: clientError(error) };
137
140
  if (attempt >= delays.length) {
138
141
  return {
139
142
  ok: false,
140
143
  error: error.code === 'NETWORK_TRANSIENT'
141
- ? { ...error, code: 'NETWORK_RETRY_EXHAUSTED', message: `${error.message} (retry exhausted)` }
142
- : error
144
+ ? { ...clientError(error), code: 'NETWORK_RETRY_EXHAUSTED', message: `${error.message} (retry exhausted)` }
145
+ : clientError(error)
143
146
  };
144
147
  }
145
148
  sleep(delays[attempt]);
@@ -150,12 +153,12 @@ function createGitHubClient(options = {}) {
150
153
  version(request = {}) {
151
154
  const result = runner(['--version'], request);
152
155
  if (result.status !== 0)
153
- return { ok: false, error: classifyGitHubFailure(result) };
156
+ return { ok: false, error: clientError(classifyGitHubFailure(result)) };
154
157
  const version = result.stdout.match(/^gh version (\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)(?:\s|$)/m)?.[1];
155
158
  if (!version || !semver.valid(version)) {
156
159
  return {
157
160
  ok: false,
158
- error: { code: 'GH_CLI_VERSION_INVALID', message: 'GitHub CLI returned an invalid version', retryable: false }
161
+ error: { code: 'GH_CLI_VERSION_INVALID', message: 'GitHub CLI returned an invalid version' }
159
162
  };
160
163
  }
161
164
  return { ok: true, value: version };
@@ -174,7 +177,7 @@ function createGitHubClient(options = {}) {
174
177
  catch {
175
178
  return {
176
179
  ok: false,
177
- error: { code: 'INVALID_PLATFORM_RESPONSE', message: 'GitHub returned invalid JSON', retryable: true }
180
+ error: { code: 'INVALID_PLATFORM_RESPONSE', message: 'GitHub returned invalid JSON' }
178
181
  };
179
182
  }
180
183
  },
@@ -189,12 +192,12 @@ function createGitHubClient(options = {}) {
189
192
  if (!parsed) {
190
193
  return {
191
194
  ok: false,
192
- error: { code: 'INVALID_PLATFORM_RESPONSE', message: 'GitHub response metadata or JSON is invalid', retryable: true }
195
+ error: { code: 'INVALID_PLATFORM_RESPONSE', message: 'GitHub response metadata or JSON is invalid' }
193
196
  };
194
197
  }
195
198
  return { ok: true, value: parsed };
196
199
  }
197
200
  };
198
201
  }
199
- export { classifyGitHubFailure, createGitHubClient, MINIMUM_GITHUB_CLI_VERSION, parseIncludedResponse };
202
+ export { createGitHubClient, MINIMUM_GITHUB_CLI_VERSION, parseIncludedResponse };
200
203
  //# sourceMappingURL=github-client.js.map
@@ -7,6 +7,32 @@ import semver from 'semver';
7
7
  import { createGitHubClient, MINIMUM_GITHUB_CLI_VERSION } from './github-client.js';
8
8
  import { resourceIdentityNumber, resourceIdentityString } from './resource-identity.js';
9
9
  import { extractPullRequestFileNames, syncLabelDelta } from './in-label-sync.js';
10
+ function providerError(error) {
11
+ return { code: error.code, message: error.message };
12
+ }
13
+ function publicProviderErrors(provider) {
14
+ const sanitize = async (call) => {
15
+ const result = await call();
16
+ if (!result || typeof result !== 'object' || result.ok !== false)
17
+ return result;
18
+ const envelope = result;
19
+ return envelope.error ? { ...result, error: providerError(envelope.error) } : result;
20
+ };
21
+ const wrapped = { ...provider, context: { ...provider.context } };
22
+ wrapped.context.resolve = (input) => sanitize(() => provider.context.resolve(input));
23
+ for (const groupName of ['issues', 'comments', 'changeRequests', 'checks', 'reviews', 'releases', 'securityAlerts', 'repositoryMetadata', 'verification']) {
24
+ const group = provider[groupName];
25
+ if (!group)
26
+ continue;
27
+ const target = { ...group };
28
+ for (const method of Object.keys(group)) {
29
+ const original = group[method];
30
+ target[method] = (input) => sanitize(() => original(input));
31
+ }
32
+ wrapped[groupName] = target;
33
+ }
34
+ return wrapped;
35
+ }
10
36
  const CURRENT_USER_QUERY = 'query { viewer { login } }';
11
37
  const ISSUE_TYPES_QUERY = `query($owner:String!){organization(login:$owner){issueTypes(first:20){nodes{id name pinnedFields{__typename ... on IssueFieldSingleSelect{id name options{id name}} ... on IssueFieldDate{id name} ... on IssueFieldText{id name} ... on IssueFieldNumber{id name}}}}}}`;
12
38
  const CLOSING_ISSUES_QUERY = 'query($owner:String!,$name:String!,$number:Int!,$cursor:String){repository(owner:$owner,name:$name){pullRequest(number:$number){closingIssuesReferences(first:100,after:$cursor){nodes{number} pageInfo{hasNextPage endCursor}}}}}';
@@ -57,7 +83,6 @@ function resolveGitHubChangeRequestGitEvidence({ cwd, repository, number, baseRe
57
83
  error: {
58
84
  code: 'PR_MERGE_EVIDENCE_SOURCE_UNAVAILABLE',
59
85
  message: 'Pull request repository identity is inconsistent',
60
- retryable: false
61
86
  }
62
87
  };
63
88
  }
@@ -80,7 +105,6 @@ function resolveGitHubChangeRequestGitEvidence({ cwd, repository, number, baseRe
80
105
  error: {
81
106
  code: 'PR_MERGE_EVIDENCE_SOURCE_UNAVAILABLE',
82
107
  message: 'Pull request Git refs are invalid',
83
- retryable: false
84
108
  }
85
109
  };
86
110
  }
@@ -97,7 +121,6 @@ function resolveGitHubChangeRequestGitEvidence({ cwd, repository, number, baseRe
97
121
  error: {
98
122
  code: 'PR_MERGE_EVIDENCE_SOURCE_UNAVAILABLE',
99
123
  message: `No GitHub remote can provide evidence for ${repository}`,
100
- retryable: false
101
124
  }
102
125
  };
103
126
  }
@@ -107,7 +130,7 @@ function resolveGitHubChangeRequestGitEvidence({ cwd, repository, number, baseRe
107
130
  };
108
131
  }
109
132
  function failure(error) {
110
- return { ok: false, error };
133
+ return { ok: false, error: providerError(error) };
111
134
  }
112
135
  function resolveContext(client, input) {
113
136
  const cwd = input.workingDirectory;
@@ -118,19 +141,17 @@ function resolveContext(client, input) {
118
141
  return failure({
119
142
  code: 'GH_CLI_VERSION_UNSUPPORTED',
120
143
  message: 'GitHub CLI ' + version.value + ' is unsupported; install gh >= ' + MINIMUM_GITHUB_CLI_VERSION,
121
- retryable: false
122
144
  });
123
145
  }
124
146
  const remote = input.gitRemote || defaultGitRemote(cwd);
125
147
  if (!remote) {
126
- return failure({ code: 'REMOTE_MISSING', message: 'Git origin remote is not configured', retryable: false });
148
+ return failure({ code: 'REMOTE_MISSING', message: 'Git origin remote is not configured' });
127
149
  }
128
150
  const ownerRepo = parseGitHubRemote(remote);
129
151
  if (!ownerRepo) {
130
152
  return failure({
131
153
  code: /github\.com/i.test(remote) ? 'REMOTE_INVALID' : 'PLATFORM_UNSUPPORTED',
132
154
  message: 'Unable to parse GitHub owner/repo from configured remote',
133
- retryable: false
134
155
  });
135
156
  }
136
157
  const repository = client.json(['api', 'repos/' + ownerRepo], { cwd });
@@ -142,7 +163,6 @@ function resolveContext(client, input) {
142
163
  return failure({
143
164
  code: 'UPSTREAM_UNRESOLVED',
144
165
  message: 'Unable to resolve the upstream repository',
145
- retryable: false
146
166
  });
147
167
  const user = client.json(['api', 'graphql', '-f', 'query=' + CURRENT_USER_QUERY], { cwd });
148
168
  if (!user.ok)
@@ -193,7 +213,7 @@ function verifyRemoteBranch(cwd, repositoryName, head) {
193
213
  if (!wanted.ref || !/^[A-Za-z0-9._/-]+$/.test(wanted.ref))
194
214
  return {
195
215
  ok: false,
196
- error: { code: 'PR_HEAD_INVALID', message: 'Pull request head ref is invalid', retryable: false }
216
+ error: { code: 'PR_HEAD_INVALID', message: 'Pull request head ref is invalid' }
197
217
  };
198
218
  const remotes = configuredGitRemotes(cwd);
199
219
  const remote = remotes.find((item) => item.repository.toLowerCase() === wanted.repository.toLowerCase())
@@ -201,7 +221,7 @@ function verifyRemoteBranch(cwd, repositoryName, head) {
201
221
  if (!remote)
202
222
  return {
203
223
  ok: false,
204
- error: { code: 'PR_REMOTE_BRANCH_MISSING', message: `No configured remote can verify ${wanted.repository}:${wanted.ref}`, retryable: false }
224
+ error: { code: 'PR_REMOTE_BRANCH_MISSING', message: `No configured remote can verify ${wanted.repository}:${wanted.ref}` }
205
225
  };
206
226
  try {
207
227
  const output = execFileSync('git', ['ls-remote', '--refs', remote.name, `refs/heads/${wanted.ref}`], {
@@ -212,19 +232,19 @@ function verifyRemoteBranch(cwd, repositoryName, head) {
212
232
  if (!sha || !/^[a-f0-9]{40}$/i.test(sha))
213
233
  return {
214
234
  ok: false,
215
- error: { code: 'PR_REMOTE_BRANCH_MISSING', message: `Remote branch ${wanted.repository}:${wanted.ref} does not exist`, retryable: false }
235
+ error: { code: 'PR_REMOTE_BRANCH_MISSING', message: `Remote branch ${wanted.repository}:${wanted.ref} does not exist` }
216
236
  };
217
237
  return { ok: true, value: sha };
218
238
  }
219
239
  catch (error) {
220
240
  return {
221
241
  ok: false,
222
- error: { code: 'PR_REMOTE_BRANCH_UNAVAILABLE', message: error instanceof Error ? error.message : String(error), retryable: true }
242
+ error: { code: 'PR_REMOTE_BRANCH_UNAVAILABLE', message: error instanceof Error ? error.message : String(error) }
223
243
  };
224
244
  }
225
245
  }
226
246
  function invalid(code, message) {
227
- return { ok: false, error: { code, message, retryable: false } };
247
+ return { ok: false, error: { code, message } };
228
248
  }
229
249
  function changeRequestSnapshot(value) {
230
250
  return {
@@ -292,7 +312,7 @@ function createReceipt(remoteId) {
292
312
  return { ok: true, value: { changed: true, remoteId } };
293
313
  }
294
314
  function partialFailure(response, value) {
295
- return { ok: false, error: response.error, value };
315
+ return { ok: false, error: providerError(response.error), value };
296
316
  }
297
317
  function labelReconciliation(created, updated, removed, skipped) {
298
318
  return {
@@ -319,8 +339,8 @@ function syncLabels(client, repositoryName, number, cwd, current, target, prefix
319
339
  status: 'blocked',
320
340
  changed: true,
321
341
  error: synced.error
322
- ? { ...synced.error, code: 'IN_LABEL_SYNC_PARTIAL', message: `In-label synchronization is partial or unknown: ${synced.error.message}` }
323
- : { code: 'IN_LABEL_SYNC_PARTIAL', message: 'In-label synchronization is partial or unknown', retryable: true }
342
+ ? { ...providerError(synced.error), code: 'IN_LABEL_SYNC_PARTIAL', message: `In-label synchronization is partial or unknown: ${synced.error.message}` }
343
+ : { code: 'IN_LABEL_SYNC_PARTIAL', message: 'In-label synchronization is partial or unknown' }
324
344
  };
325
345
  return synced;
326
346
  }
@@ -599,7 +619,7 @@ function createGitHubOperations(client) {
599
619
  localHead = repositoryHead(context.workingDirectory);
600
620
  }
601
621
  catch (error) {
602
- return { ok: false, error: { code: 'PR_LOCAL_HEAD_UNAVAILABLE', message: error instanceof Error ? error.message : String(error), retryable: false } };
622
+ return { ok: false, error: { code: 'PR_LOCAL_HEAD_UNAVAILABLE', message: error instanceof Error ? error.message : String(error) } };
603
623
  }
604
624
  const remote = verifyRemoteBranch(context.workingDirectory, repository(context), head);
605
625
  if (!remote.ok)
@@ -607,7 +627,7 @@ function createGitHubOperations(client) {
607
627
  if (remote.value !== localHead)
608
628
  return {
609
629
  ok: false,
610
- error: { code: 'PR_REMOTE_HEAD_MISMATCH', message: `Remote head ${remote.value} does not match local HEAD ${localHead}`, retryable: false }
630
+ error: { code: 'PR_REMOTE_HEAD_MISMATCH', message: `Remote head ${remote.value} does not match local HEAD ${localHead}` }
611
631
  };
612
632
  return { ok: true, value: { sha: localHead } };
613
633
  },
@@ -904,7 +924,7 @@ function createGitHubOperations(client) {
904
924
  const tag = resourceIdentityString(release) || '';
905
925
  const result = module.publishGitHubReleaseNotes({ repository: repository(context), tag, title, notesFile }, { cwd: context.workingDirectory, client });
906
926
  if (result.status === 'failed' || result.status === 'blocked')
907
- return { ok: false, error: result.error || { code: 'RELEASE_NOTES_PUBLISH_FAILED', message: 'Release notes publish failed', retryable: false } };
927
+ return { ok: false, error: result.error ? providerError(result.error) : { code: 'RELEASE_NOTES_PUBLISH_FAILED', message: 'Release notes publish failed' } };
908
928
  return { ok: true, value: { changed: result.changed, remoteId: tag } };
909
929
  }
910
930
  finally {
@@ -917,7 +937,7 @@ function createGitHubOperations(client) {
917
937
  repository: repository(context), commitOids, branch, historyLimit, fromTime, toTime
918
938
  }, { cwd: context.workingDirectory, client });
919
939
  if (collected.status === 'failed' || collected.status === 'blocked' || !('pullRequests' in collected)) {
920
- return { ok: false, error: collected.error || { code: 'RELEASE_NOTES_COLLECTION_FAILED', message: 'Release notes could not be collected', retryable: false } };
940
+ return { ok: false, error: collected.error ? providerError(collected.error) : { code: 'RELEASE_NOTES_COLLECTION_FAILED', message: 'Release notes could not be collected' } };
921
941
  }
922
942
  const mergedPullRequests = collected.pullRequests.map((item) => ({
923
943
  id: String(item.number), identity: { kind: 'number', value: Number(item.number) }, number: Number(item.number),
@@ -1114,7 +1134,7 @@ function createGitHubOperations(client) {
1114
1134
  return { issues, comments, changeRequests, checks, reviews, releases, securityAlerts, repositoryMetadata, verification };
1115
1135
  }
1116
1136
  function createGitHubProvider(input, client = createGitHubClient()) {
1117
- return {
1137
+ return publicProviderErrors({
1118
1138
  type: input.providerType,
1119
1139
  contractVersion: 2,
1120
1140
  identity: { issue: 'number', 'pull-request': 'number', comment: 'number', release: 'key' },
@@ -1124,7 +1144,7 @@ function createGitHubProvider(input, client = createGitHubClient()) {
1124
1144
  return resolveContext(client, contextInput);
1125
1145
  }
1126
1146
  }
1127
- };
1147
+ });
1128
1148
  }
1129
1149
  export { configuredGitRemotes, createGitHubProvider, defaultGitRemote, parseGitHubRemote, resolveGitHubChangeRequestGitEvidence };
1130
1150
  //# sourceMappingURL=github-provider.js.map
@@ -1,4 +1,5 @@
1
1
  import { createGitHubClient } from './github-client.js';
2
+ import { providerErrorRetryable } from './provider-validation.js';
2
3
  function normalizeGitHubActor(actor) {
3
4
  const name = String(actor.name || '').trim();
4
5
  const email = actor.email ? String(actor.email).trim() : null;
@@ -26,12 +27,13 @@ function normalizePlatformAuthor(author) {
26
27
  };
27
28
  }
28
29
  function failure(error) {
30
+ const retryable = error.retryable ?? providerErrorRetryable(error.code);
29
31
  return {
30
- status: error.retryable ? 'blocked' : 'failed',
32
+ status: retryable ? 'blocked' : 'failed',
31
33
  changed: false,
32
34
  operation: null,
33
35
  url: null,
34
- error
36
+ error: { code: error.code, message: error.message }
35
37
  };
36
38
  }
37
39
  function publishGitHubReleaseNotes(input, options = {}) {
@@ -1,5 +1,6 @@
1
1
  import { computeInLabels } from './metadata-labels.js';
2
2
  import { createHash } from 'node:crypto';
3
+ import { providerErrorRetryable } from './provider-validation.js';
3
4
  function inLabels(labels) {
4
5
  return [...new Set(labels.filter((label) => label.startsWith('in:')))].sort();
5
6
  }
@@ -96,7 +97,7 @@ function syncLabelDelta(client, repository, number, cwd, current, target, prefix
96
97
  const delta = labelDelta(current, target, prefix);
97
98
  let changed = false;
98
99
  const failure = (error) => {
99
- if (changed || error.retryable) {
100
+ if (changed || error.retryable || providerErrorRetryable(error.code)) {
100
101
  return {
101
102
  status: 'blocked',
102
103
  changed,