@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
|
@@ -43,16 +43,24 @@ const AGENT_CLIENTS_SCHEMA = {
|
|
|
43
43
|
}
|
|
44
44
|
}))
|
|
45
45
|
},
|
|
46
|
-
|
|
47
|
-
type: '
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
46
|
+
sandbox: {
|
|
47
|
+
type: 'object',
|
|
48
|
+
properties: {
|
|
49
|
+
tools: {
|
|
50
|
+
type: 'object',
|
|
51
|
+
properties: {
|
|
52
|
+
ids: { type: 'array', items: { type: 'string' } },
|
|
53
|
+
definitions: {
|
|
54
|
+
type: 'object',
|
|
55
|
+
additionalProperties: {
|
|
56
|
+
type: 'object',
|
|
57
|
+
properties: {
|
|
58
|
+
name: { type: 'string' },
|
|
59
|
+
invoke: { type: 'string' }
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
56
64
|
}
|
|
57
65
|
}
|
|
58
66
|
}
|
package/lib/git/workflow.ts
CHANGED
|
@@ -100,7 +100,7 @@ function commitExplicitPaths(input: { cwd: string; paths: readonly string[]; mes
|
|
|
100
100
|
const tree = value(runner, input.cwd, ['write-tree']);
|
|
101
101
|
if (tree !== input.expectedTree) return { status: 'failed' as const, changed: false, snapshot: inspectGitWorkflow(input.cwd, runner).snapshot, operations: [{ name: 'stage', status: 'applied' as const }], error: { code: 'GIT_TREE_MISMATCH', message: `Expected staged tree ${input.expectedTree}, received ${tree ?? 'unavailable'}` } };
|
|
102
102
|
}
|
|
103
|
-
const commit = run(runner, input.cwd, ['commit', '-m', input.message]);
|
|
103
|
+
const commit = run(runner, input.cwd, ['commit', '--cleanup=verbatim', '-m', input.message]);
|
|
104
104
|
const after = inspectGitWorkflow(input.cwd, runner);
|
|
105
105
|
if (commit.status !== 0) return { status: 'failed' as const, changed: false, snapshot: after.snapshot, operations: [{ name: 'commit', status: 'failed' as const, message: commit.stderr.trim() }], error: { code: 'GIT_COMMIT_FAILED', message: commit.stderr.trim() } };
|
|
106
106
|
return { status: 'applied' as const, changed: true, snapshot: after.snapshot, operations: [{ name: 'stage', status: 'applied' as const }, { name: 'commit', status: 'applied' as const }], error: null };
|
package/lib/init.ts
CHANGED
|
@@ -314,8 +314,8 @@ async function cmdInit(): Promise<void> {
|
|
|
314
314
|
console.log(' npm install -g @fitlab-ai/agent-infra');
|
|
315
315
|
console.log('');
|
|
316
316
|
if (workflowPlan.nextSteps.length === 0) {
|
|
317
|
-
console.log(' No Agent Client
|
|
318
|
-
console.log(` Configure "
|
|
317
|
+
console.log(' No Agent Client or sandbox custom tool integration enabled.');
|
|
318
|
+
console.log(` Configure a custom tool in "sandbox.tools.definitions" and select it in "sandbox.tools.ids" in ${configPath}.`);
|
|
319
319
|
console.log('');
|
|
320
320
|
} else {
|
|
321
321
|
console.log(' Next step: open this project in any AI TUI and run:');
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
AgentClientConfigError,
|
|
6
6
|
normalizeAgentClients
|
|
7
7
|
} from '../agent-clients/config.ts';
|
|
8
|
-
import {
|
|
8
|
+
import { normalizeCustomToolInvocations } from '../agent-clients/custom-tool-invocations.ts';
|
|
9
9
|
import { renderNextStepCommands } from '../agent-clients/next-steps.ts';
|
|
10
10
|
import { getAgentClientModelSelection } from '../agent-clients/registry.ts';
|
|
11
11
|
import { isAgentClientId } from '../agent-clients/types.ts';
|
|
@@ -148,11 +148,11 @@ function agentClient(args: string[] = []): void {
|
|
|
148
148
|
|
|
149
149
|
try {
|
|
150
150
|
const clients = normalizeAgentClients(config);
|
|
151
|
-
const custom =
|
|
151
|
+
const custom = normalizeCustomToolInvocations(config);
|
|
152
152
|
const commands = renderNextStepCommands({
|
|
153
153
|
projectName: String(config.project ?? ''),
|
|
154
154
|
state: clients.state,
|
|
155
|
-
|
|
155
|
+
customToolInvocations: custom.items,
|
|
156
156
|
skillName: parsed.skillName,
|
|
157
157
|
...(parsed.taskRef ? { taskRef: parsed.taskRef } : {}),
|
|
158
158
|
...(parsed.version ? { version: parsed.version } : {})
|
|
@@ -60,7 +60,6 @@ export const INTERNAL_HANDLER_ROUTE_SELECTORS = Object.freeze({
|
|
|
60
60
|
'task-orchestration': ['status', 'progress'],
|
|
61
61
|
'task-review': ['preflight', 'finalize-summary'],
|
|
62
62
|
'task-event': ['event'],
|
|
63
|
-
'task-invalidation': ['reconcile'],
|
|
64
63
|
'task-lifecycle': ['intent'],
|
|
65
64
|
'task-finalization': ['complete'],
|
|
66
65
|
'task-platform-recovery': ['inspect', 'recover'],
|
|
@@ -98,7 +97,6 @@ export function internalRouteSelector(command: string, args: readonly string[]):
|
|
|
98
97
|
if (command === 'task-short-id') return first(args) === 'list' && args.includes('--verify') ? 'list-verify' : first(args);
|
|
99
98
|
if (command === 'task-orchestration') return args[1] === 'status' ? 'status' : args[1] ? 'progress' : '';
|
|
100
99
|
if (command === 'task-review') return args[1] ?? '';
|
|
101
|
-
if (command === 'task-invalidation') return args[1] ?? '';
|
|
102
100
|
if (command === 'task-artifact') return args[1] ?? '';
|
|
103
101
|
if (command === 'platform-pr-review' && first(args) === 'publish') {
|
|
104
102
|
const scope = optionValue(args, '--scope') ?? '';
|
|
@@ -9,6 +9,7 @@ import { serveSandboxControl } from '../sandbox/control/server.ts';
|
|
|
9
9
|
import { runSandboxControlExecutor } from '../sandbox/control/executor.ts';
|
|
10
10
|
import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.ts';
|
|
11
11
|
import { parseTaskCreateResult, taskCreateExitCode } from '../task/create-service.ts';
|
|
12
|
+
import { serializeTaskFinalizationEnvelope, type TaskFinalizationEnvelope } from '../task/finalization-envelope.ts';
|
|
12
13
|
|
|
13
14
|
type FinalizationStatus = 'completed' | 'failed' | 'blocked' | 'unknown';
|
|
14
15
|
|
|
@@ -16,10 +17,11 @@ function writeFinalizationEnvelope(
|
|
|
16
17
|
status: FinalizationStatus,
|
|
17
18
|
accepted: boolean,
|
|
18
19
|
error: { code: string; message: string; retryable: boolean },
|
|
19
|
-
changed = false,
|
|
20
|
-
result: unknown = null
|
|
20
|
+
changed: boolean | null = false,
|
|
21
|
+
result: unknown = null,
|
|
22
|
+
requestId: string | null = null
|
|
21
23
|
): void {
|
|
22
|
-
process.stdout.write(
|
|
24
|
+
process.stdout.write(serializeTaskFinalizationEnvelope({ status, changed, accepted, requestId, result, error }));
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
function finalizationErrorStatus(error: { retryable: boolean; code: string }): FinalizationStatus {
|
|
@@ -43,6 +45,28 @@ function recoveredExitCode(response: Awaited<ReturnType<typeof recoverSandboxCon
|
|
|
43
45
|
return response.exitCode ?? 1;
|
|
44
46
|
}
|
|
45
47
|
|
|
48
|
+
function finalizationResponseOutput(response: Awaited<ReturnType<typeof requestSandboxTaskFinalization>>): string {
|
|
49
|
+
try {
|
|
50
|
+
const value = JSON.parse(response.stdout) as Partial<TaskFinalizationEnvelope>;
|
|
51
|
+
if (value.version === 2
|
|
52
|
+
&& value.status
|
|
53
|
+
&& ['completed', 'failed', 'blocked', 'unknown'].includes(value.status)
|
|
54
|
+
&& (typeof value.changed === 'boolean' || value.changed === null)) {
|
|
55
|
+
return serializeTaskFinalizationEnvelope({
|
|
56
|
+
status: value.status,
|
|
57
|
+
changed: value.changed,
|
|
58
|
+
accepted: true,
|
|
59
|
+
requestId: response.id,
|
|
60
|
+
result: value.result ?? null,
|
|
61
|
+
error: value.error ?? null
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
} catch {
|
|
65
|
+
// Preserve non-envelope output for diagnostics from the executor.
|
|
66
|
+
}
|
|
67
|
+
return response.stdout;
|
|
68
|
+
}
|
|
69
|
+
|
|
46
70
|
function sandboxFinalizationClient(args: string[]): void {
|
|
47
71
|
if (args.length !== 4 || !args[0] || args[1] !== 'complete' || args[2] !== '--agent' || !args[3]) {
|
|
48
72
|
const error = { code: 'TASK_FINALIZATION_PAYLOAD_INVALID', message: 'task ref, complete intent, and --agent are required', retryable: false };
|
|
@@ -63,7 +87,8 @@ function sandboxFinalizationClient(args: string[]): void {
|
|
|
63
87
|
response = requestSandboxTaskFinalization({ agent });
|
|
64
88
|
} catch (error) {
|
|
65
89
|
if (!(error instanceof SandboxControlClientError)) throw error;
|
|
66
|
-
|
|
90
|
+
const status = finalizationErrorStatus(error.detail);
|
|
91
|
+
writeFinalizationEnvelope(status, error.accepted, error.detail, status === 'unknown' ? null : false, null, error.requestId);
|
|
67
92
|
writeClientError(error);
|
|
68
93
|
process.exitCode = error.detail.code === 'SANDBOX_CONTROL_RESULT_UNKNOWN' ? 1 : error.detail.retryable ? 2 : 1;
|
|
69
94
|
return;
|
|
@@ -74,12 +99,20 @@ function sandboxFinalizationClient(args: string[]): void {
|
|
|
74
99
|
message: 'sandbox control rejected the finalization request',
|
|
75
100
|
retryable: false
|
|
76
101
|
};
|
|
77
|
-
|
|
102
|
+
const status = finalizationErrorStatus(error);
|
|
103
|
+
writeFinalizationEnvelope(
|
|
104
|
+
status,
|
|
105
|
+
error.code === 'SANDBOX_CONTROL_RESULT_UNKNOWN',
|
|
106
|
+
error,
|
|
107
|
+
status === 'unknown' ? null : false,
|
|
108
|
+
null,
|
|
109
|
+
response.id
|
|
110
|
+
);
|
|
78
111
|
process.stderr.write(`${error.message}\n`);
|
|
79
112
|
process.exitCode = error.code === 'SANDBOX_CONTROL_RESULT_UNKNOWN' ? 1 : error.retryable ? 2 : 1;
|
|
80
113
|
return;
|
|
81
114
|
}
|
|
82
|
-
process.stdout.write(response
|
|
115
|
+
process.stdout.write(finalizationResponseOutput(response));
|
|
83
116
|
process.stderr.write(response.stderr);
|
|
84
117
|
process.exitCode = response.exitCode ?? 1;
|
|
85
118
|
}
|
|
@@ -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 };
|