@fitlab-ai/agent-infra 0.11.7 → 0.11.9
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 +8 -2
- package/dist/lib/agent-clients/adapters/codex.js +1 -1
- package/dist/lib/agent-clients/config.js +11 -3
- 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/defaults.json +6 -3
- package/dist/lib/init.js +2 -2
- package/dist/lib/internal/agent-client.js +3 -3
- package/dist/lib/internal/cli-route-inventory.js +3 -0
- package/dist/lib/internal/platform-pr-review.js +20 -3
- package/dist/lib/internal/task-operation-registry.js +3 -1
- package/dist/lib/internal/task-platform-recovery.js +56 -0
- package/dist/lib/merge.js +25 -5
- package/dist/lib/platform/completion-backfill.js +2 -1
- package/dist/lib/platform/in-label-sync.js +12 -1
- package/dist/lib/platform/issue-comments.js +58 -4
- package/dist/lib/platform/issues.js +105 -5
- package/dist/lib/platform/operation-coordinator.js +74 -0
- package/dist/lib/platform/pr-review.js +107 -1
- package/dist/lib/platform/pr-summary.js +79 -57
- package/dist/lib/platform/pull-requests.js +96 -4
- package/dist/lib/sandbox/commands/create.js +6 -3
- package/dist/lib/sandbox/commands/ls.js +4 -3
- package/dist/lib/sandbox/commands/rm.js +1 -1
- package/dist/lib/sandbox/config.js +22 -4
- package/dist/lib/sandbox/git-metadata.js +50 -0
- package/dist/lib/sandbox/index.js +1 -1
- package/dist/lib/sandbox/mounts.js +12 -0
- package/dist/lib/sandbox/recovery.js +12 -22
- package/dist/lib/sandbox/removal.js +58 -20
- package/dist/lib/sandbox/tools.js +31 -7
- package/dist/lib/task/activity-intent.js +4 -3
- package/dist/lib/task/create-service.js +5 -3
- package/dist/lib/task/create.js +22 -5
- package/dist/lib/task/finalization-artifacts.js +4 -1
- package/dist/lib/task/finalization.js +243 -113
- package/dist/lib/task/lifecycle.js +38 -2
- package/dist/lib/task/platform-operation-journal.js +152 -0
- package/dist/lib/task/platform-operation-recovery.js +459 -0
- package/dist/lib/task/verification.js +0 -1
- package/dist/lib/task/workflow-warning-intents.js +7 -1
- package/dist/lib/update.js +2 -2
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapters/codex.ts +1 -1
- package/lib/agent-clients/config.ts +9 -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/defaults.json +6 -3
- package/lib/init.ts +2 -2
- package/lib/internal/agent-client.ts +3 -3
- package/lib/internal/cli-route-inventory.ts +2 -0
- package/lib/internal/platform-pr-review.ts +14 -3
- package/lib/internal/task-operation-registry.ts +3 -1
- package/lib/internal/task-platform-recovery.ts +53 -0
- package/lib/merge.ts +26 -6
- package/lib/platform/completion-backfill.ts +2 -1
- package/lib/platform/in-label-sync.ts +12 -0
- package/lib/platform/issue-comments.ts +59 -3
- package/lib/platform/issues.ts +103 -5
- package/lib/platform/operation-coordinator.ts +96 -0
- package/lib/platform/pr-review.ts +113 -1
- package/lib/platform/pr-summary.ts +22 -1
- package/lib/platform/pull-requests.ts +105 -5
- package/lib/sandbox/commands/create.ts +7 -8
- package/lib/sandbox/commands/ls.ts +4 -3
- package/lib/sandbox/commands/rm.ts +1 -1
- package/lib/sandbox/config.ts +23 -5
- package/lib/sandbox/git-metadata.ts +66 -0
- package/lib/sandbox/index.ts +1 -1
- package/lib/sandbox/mounts.ts +14 -0
- package/lib/sandbox/recovery.ts +13 -25
- package/lib/sandbox/removal.ts +57 -19
- package/lib/sandbox/tool-types.ts +2 -1
- package/lib/sandbox/tools.ts +38 -7
- package/lib/task/activity-intent.ts +4 -3
- package/lib/task/create-service.ts +5 -3
- package/lib/task/create.ts +17 -5
- package/lib/task/finalization-artifacts.ts +4 -1
- package/lib/task/finalization.ts +242 -106
- package/lib/task/lifecycle.ts +35 -3
- package/lib/task/platform-operation-journal.ts +230 -0
- package/lib/task/platform-operation-recovery.ts +435 -0
- package/lib/task/verification.ts +1 -2
- package/lib/task/workflow-warning-intents.ts +9 -1
- package/lib/update.ts +2 -3
- package/package.json +1 -1
- package/templates/.agents/README.en.md +20 -66
- package/templates/.agents/README.zh-CN.md +19 -65
- package/templates/.agents/rules/README.en.md +1 -1
- package/templates/.agents/rules/README.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/rules/task-management.en.md +12 -0
- package/templates/.agents/rules/task-management.zh-CN.md +12 -0
- package/templates/.agents/scripts/lib/agent-infra-package.js +0 -7
- package/templates/.agents/skills/cancel-task/SKILL.en.md +4 -2
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +4 -2
- package/templates/.agents/skills/code-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +2 -2
- 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 +2 -0
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +2 -0
- 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/plan-task/config/verify.en.json +1 -1
- package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +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/review-pr/SKILL.en.md +11 -11
- package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +11 -11
- package/templates/.agents/skills/review-pr/reference/output-guidance.en.md +3 -3
- package/templates/.agents/skills/review-pr/reference/output-guidance.zh-CN.md +3 -3
- package/templates/.agents/skills/review-pr/reference/report-template.en.md +4 -0
- package/templates/.agents/skills/review-pr/reference/report-template.zh-CN.md +4 -0
- 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/skills/update-agent-infra/scripts/sync-templates.js +21 -274
- package/dist/lib/agent-clients/custom-tuis.js +0 -99
- package/lib/agent-clients/custom-tuis.ts +0 -149
package/dist/lib/update.js
CHANGED
|
@@ -132,8 +132,8 @@ async function cmdSync() {
|
|
|
132
132
|
ok('Seed files synced successfully!');
|
|
133
133
|
console.log('');
|
|
134
134
|
if (workflowPlan.nextSteps.length === 0) {
|
|
135
|
-
console.log(' No Agent Client
|
|
136
|
-
console.log(` Configure "
|
|
135
|
+
console.log(' No Agent Client or sandbox custom tool integration enabled.');
|
|
136
|
+
console.log(` Configure a custom tool in "sandbox.tools.definitions" and select it in "sandbox.tools.ids" in ${CONFIG_PATH}.`);
|
|
137
137
|
console.log('');
|
|
138
138
|
}
|
|
139
139
|
else {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"protocolVersion": 3,
|
|
3
|
-
"packageVersion": "0.11.
|
|
4
|
-
"sourceInputHash": "
|
|
5
|
-
"internalExecutableBuildHash": "
|
|
3
|
+
"packageVersion": "0.11.9",
|
|
4
|
+
"sourceInputHash": "17fe6c91408c93e3aa5185e816025d590ded7db7e6a305fdce00751e78723abc",
|
|
5
|
+
"internalExecutableBuildHash": "8de9870e4ece1050564d7a3e77ee211a789dd975e3f94593300b7362ed815a00"
|
|
6
6
|
}
|
|
@@ -54,7 +54,7 @@ const codexAdapter = defineAgentClientAdapter({
|
|
|
54
54
|
containerMount: '/home/devuser/.codex',
|
|
55
55
|
versionCmd: 'codex --version',
|
|
56
56
|
setupHint: 'Run codex once inside the container and choose Device Code login if needed.',
|
|
57
|
-
tmpfs: { size: '512m', seed: ['config.toml', 'model-catalogs'] },
|
|
57
|
+
tmpfs: { size: '512m', seed: ['config.toml', 'model-catalogs'], exec: true },
|
|
58
58
|
hostLiveMounts: [
|
|
59
59
|
{
|
|
60
60
|
hostPath: hostJoin(home, '.codex', 'auth.json'),
|
|
@@ -167,10 +167,16 @@ function normalizeAgentClients(
|
|
|
167
167
|
}
|
|
168
168
|
if (hasOwn(input, 'tuis')) fail('INVALID_AGENT_CLIENTS', 'tuis');
|
|
169
169
|
const sandbox = isRecord(input.sandbox) ? input.sandbox : undefined;
|
|
170
|
-
if (
|
|
171
|
-
|
|
170
|
+
if (sandbox && hasOwn(sandbox, 'customTools')) {
|
|
171
|
+
fail('INVALID_AGENT_CLIENTS', 'sandbox.customTools');
|
|
172
|
+
}
|
|
173
|
+
if (sandbox && hasOwn(sandbox, 'tools')) {
|
|
174
|
+
if (!isRecord(sandbox.tools)) fail('INVALID_AGENT_CLIENTS', 'sandbox.tools');
|
|
175
|
+
const tools = sandbox.tools;
|
|
176
|
+
if (!Array.isArray(tools.ids)) fail('INVALID_AGENT_CLIENTS', 'sandbox.tools.ids');
|
|
177
|
+
for (const [index, tool] of tools.ids.entries()) {
|
|
172
178
|
if (isAgentClientId(tool)) {
|
|
173
|
-
fail('INVALID_AGENT_CLIENTS', `sandbox.tools[${index}]`);
|
|
179
|
+
fail('INVALID_AGENT_CLIENTS', `sandbox.tools.ids[${index}]`);
|
|
174
180
|
}
|
|
175
181
|
}
|
|
176
182
|
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
type CustomToolInvocation = Readonly<{
|
|
2
|
+
name: string;
|
|
3
|
+
invocation: string;
|
|
4
|
+
}>;
|
|
5
|
+
|
|
6
|
+
type CustomToolInvocationDiagnosticCode =
|
|
7
|
+
| 'INVALID_CUSTOM_TOOL_CONFIG'
|
|
8
|
+
| 'INVALID_CUSTOM_TOOL_INVOCATION'
|
|
9
|
+
| 'INVALID_CUSTOM_TOOL_INVOCATION_PLACEHOLDER';
|
|
10
|
+
|
|
11
|
+
type CustomToolInvocationDiagnostic = Readonly<{
|
|
12
|
+
code: CustomToolInvocationDiagnosticCode;
|
|
13
|
+
path: string;
|
|
14
|
+
}>;
|
|
15
|
+
|
|
16
|
+
type NormalizeCustomToolInvocationsResult = Readonly<{
|
|
17
|
+
items: readonly CustomToolInvocation[];
|
|
18
|
+
diagnostics: readonly CustomToolInvocationDiagnostic[];
|
|
19
|
+
}>;
|
|
20
|
+
|
|
21
|
+
const ALLOWED_PLACEHOLDERS = Object.freeze(['skillName', 'projectName']);
|
|
22
|
+
|
|
23
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
24
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function isNonEmptySingleLine(value: unknown): value is string {
|
|
28
|
+
return typeof value === 'string'
|
|
29
|
+
&& value.trim() !== ''
|
|
30
|
+
&& !/[\r\n]/.test(value);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function hasValidPlaceholders(invocation: string): boolean {
|
|
34
|
+
const placeholders = [
|
|
35
|
+
...invocation.matchAll(/\$\{([^}]+)\}/g)
|
|
36
|
+
].map((match) => match[1]!);
|
|
37
|
+
return placeholders.includes('skillName')
|
|
38
|
+
&& placeholders.every((placeholder) =>
|
|
39
|
+
ALLOWED_PLACEHOLDERS.includes(placeholder)
|
|
40
|
+
)
|
|
41
|
+
&& !invocation
|
|
42
|
+
.replaceAll('${skillName}', '')
|
|
43
|
+
.replaceAll('${projectName}', '')
|
|
44
|
+
.includes('${');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function normalizeCustomToolInvocations(config: unknown): NormalizeCustomToolInvocationsResult {
|
|
48
|
+
const items: CustomToolInvocation[] = [];
|
|
49
|
+
const diagnostics: CustomToolInvocationDiagnostic[] = [];
|
|
50
|
+
|
|
51
|
+
const root = isRecord(config) ? config : {};
|
|
52
|
+
const sandbox = isRecord(root.sandbox) ? root.sandbox : {};
|
|
53
|
+
const sandboxTools = isRecord(sandbox.tools) ? sandbox.tools : { ids: ['agent-infra'] };
|
|
54
|
+
const definitions = sandboxTools.definitions ?? {};
|
|
55
|
+
const selectedIds = sandboxTools.ids;
|
|
56
|
+
|
|
57
|
+
if (!Array.isArray(selectedIds)) {
|
|
58
|
+
return Object.freeze({
|
|
59
|
+
items: Object.freeze([]),
|
|
60
|
+
diagnostics: Object.freeze([
|
|
61
|
+
Object.freeze({
|
|
62
|
+
code: 'INVALID_CUSTOM_TOOL_CONFIG' as const,
|
|
63
|
+
path: 'sandbox.tools.ids'
|
|
64
|
+
})
|
|
65
|
+
])
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (!isRecord(definitions)) {
|
|
70
|
+
return Object.freeze({
|
|
71
|
+
items: Object.freeze([]),
|
|
72
|
+
diagnostics: Object.freeze([
|
|
73
|
+
Object.freeze({
|
|
74
|
+
code: 'INVALID_CUSTOM_TOOL_CONFIG' as const,
|
|
75
|
+
path: 'sandbox.tools.definitions'
|
|
76
|
+
})
|
|
77
|
+
])
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
for (const [index, id] of selectedIds.entries()) {
|
|
82
|
+
if (typeof id !== 'string') {
|
|
83
|
+
diagnostics.push({
|
|
84
|
+
code: 'INVALID_CUSTOM_TOOL_INVOCATION',
|
|
85
|
+
path: `sandbox.tools.ids[${index}]`
|
|
86
|
+
});
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
const candidate = definitions[id];
|
|
90
|
+
if (!isRecord(candidate)) continue;
|
|
91
|
+
if (candidate.invoke === undefined) continue;
|
|
92
|
+
const base = `sandbox.tools.definitions.${id}`;
|
|
93
|
+
const name = candidate.name === undefined ? id : candidate.name;
|
|
94
|
+
if (!isNonEmptySingleLine(name)) {
|
|
95
|
+
diagnostics.push({ code: 'INVALID_CUSTOM_TOOL_INVOCATION', path: `${base}.name` });
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (!isNonEmptySingleLine(candidate.invoke)) {
|
|
99
|
+
diagnostics.push({ code: 'INVALID_CUSTOM_TOOL_INVOCATION', path: `${base}.invoke` });
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (!hasValidPlaceholders(candidate.invoke)) {
|
|
103
|
+
diagnostics.push({
|
|
104
|
+
code: 'INVALID_CUSTOM_TOOL_INVOCATION_PLACEHOLDER',
|
|
105
|
+
path: `${base}.invoke`
|
|
106
|
+
});
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
items.push(Object.freeze({
|
|
110
|
+
name,
|
|
111
|
+
invocation: candidate.invoke
|
|
112
|
+
}));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return Object.freeze({
|
|
116
|
+
items: Object.freeze(items),
|
|
117
|
+
diagnostics: Object.freeze(
|
|
118
|
+
diagnostics.map((diagnostic) => Object.freeze(diagnostic))
|
|
119
|
+
)
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export { normalizeCustomToolInvocations };
|
|
124
|
+
export type {
|
|
125
|
+
CustomToolInvocation,
|
|
126
|
+
CustomToolInvocationDiagnostic,
|
|
127
|
+
CustomToolInvocationDiagnosticCode,
|
|
128
|
+
NormalizeCustomToolInvocationsResult
|
|
129
|
+
};
|
|
@@ -2,7 +2,7 @@ import { listEnabledAgentClientAdapters } from './registry.ts';
|
|
|
2
2
|
import { renderAgentClientInvocation } from './invocation.ts';
|
|
3
3
|
import semver from 'semver';
|
|
4
4
|
import { isTaskScopeSkill } from '../task/skill-scope.ts';
|
|
5
|
-
import type {
|
|
5
|
+
import type { CustomToolInvocation } from './custom-tool-invocations.ts';
|
|
6
6
|
import type { AgentClientId, AgentClientState } from './types.ts';
|
|
7
7
|
|
|
8
8
|
type NextStepCommand = Readonly<{
|
|
@@ -15,7 +15,7 @@ type NextStepCommand = Readonly<{
|
|
|
15
15
|
type RenderNextStepsInput = Readonly<{
|
|
16
16
|
projectName: string;
|
|
17
17
|
state: AgentClientState;
|
|
18
|
-
|
|
18
|
+
customToolInvocations: readonly CustomToolInvocation[];
|
|
19
19
|
skillName: string;
|
|
20
20
|
taskRef?: string;
|
|
21
21
|
version?: string;
|
|
@@ -69,7 +69,7 @@ function renderNextStepCommands(
|
|
|
69
69
|
)
|
|
70
70
|
})
|
|
71
71
|
);
|
|
72
|
-
const custom = input.
|
|
72
|
+
const custom = input.customToolInvocations.map(
|
|
73
73
|
(tool): NextStepCommand => Object.freeze({
|
|
74
74
|
source: 'custom',
|
|
75
75
|
displayName: tool.name,
|
|
@@ -4,7 +4,7 @@ import path from 'node:path';
|
|
|
4
4
|
|
|
5
5
|
import defaults from '../defaults.json' with { type: 'json' };
|
|
6
6
|
import { normalizeAgentClients, serializeAgentClients } from './config.ts';
|
|
7
|
-
import {
|
|
7
|
+
import { normalizeCustomToolInvocations } from './custom-tool-invocations.ts';
|
|
8
8
|
import { renderNextStepCommands } from './next-steps.ts';
|
|
9
9
|
import { planAgentClientProjectAssets } from './project-assets.ts';
|
|
10
10
|
import {
|
|
@@ -216,11 +216,11 @@ function planAgentClientReconciliation(input: Readonly<{
|
|
|
216
216
|
delete (nextConfig.files as Record<string, unknown>).managedBaselines;
|
|
217
217
|
}
|
|
218
218
|
|
|
219
|
-
const custom =
|
|
219
|
+
const custom = normalizeCustomToolInvocations(input.config);
|
|
220
220
|
const nextSteps = renderNextStepCommands({
|
|
221
221
|
projectName,
|
|
222
222
|
state: desired,
|
|
223
|
-
|
|
223
|
+
customToolInvocations: custom.items,
|
|
224
224
|
skillName: 'update-agent-infra'
|
|
225
225
|
});
|
|
226
226
|
const actionableSeed = seedOperations.some((operation) =>
|
|
@@ -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/defaults.json
CHANGED
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 } : {})
|
|
@@ -63,6 +63,7 @@ export const INTERNAL_HANDLER_ROUTE_SELECTORS = Object.freeze({
|
|
|
63
63
|
'task-invalidation': ['reconcile'],
|
|
64
64
|
'task-lifecycle': ['intent'],
|
|
65
65
|
'task-finalization': ['complete'],
|
|
66
|
+
'task-platform-recovery': ['inspect', 'recover'],
|
|
66
67
|
'task-short-id': ['list', 'list-verify', 'alloc', 'release', 'resolve'],
|
|
67
68
|
'task-snapshot': ['snapshot'],
|
|
68
69
|
'task-verify': ['event'],
|
|
@@ -89,6 +90,7 @@ export function internalRouteSelector(command: string, args: readonly string[]):
|
|
|
89
90
|
if (command === 'task-qualification') return args[1] ?? '';
|
|
90
91
|
if (command === 'task-lifecycle') return args[1] ? 'intent' : '';
|
|
91
92
|
if (command === 'task-finalization') return args[1] === 'complete' ? 'complete' : '';
|
|
93
|
+
if (command === 'task-platform-recovery') return args[1] ?? '';
|
|
92
94
|
if (command === 'task-event') return args[1] ? 'event' : '';
|
|
93
95
|
if (command === 'task-verify') return args[0] && args[1] ? 'event' : '';
|
|
94
96
|
if (command === 'task-snapshot') return 'snapshot';
|
|
@@ -6,14 +6,15 @@ import { listPrReviews, publishPrReview } from '../platform/pr-review.ts';
|
|
|
6
6
|
import type { PrReviewEvent } from '../platform/pr-review.ts';
|
|
7
7
|
import type { PlatformResult } from '../platform/types.ts';
|
|
8
8
|
import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.ts';
|
|
9
|
+
import { normalizeAgentToken, AGENT_USAGE_HINT } from '../agent-clients/tokens.ts';
|
|
9
10
|
|
|
10
11
|
const USAGE = `Usage: agent-infra-internal platform-pr-review inspect --pr <token> [--cwd <path>]
|
|
11
12
|
agent-infra-internal platform-pr-review list --pr <token> [--cwd <path>]
|
|
12
|
-
agent-infra-internal platform-pr-review publish --pr <token> --scope <taskId|pr{N}> --round <N> --commit <sha> --event <COMMENT|APPROVE|REQUEST_CHANGES> --body-file <path|-> [--dry-run] [--cwd <path>]
|
|
13
|
+
agent-infra-internal platform-pr-review publish --pr <token> --scope <taskId|pr{N}> --round <N> --commit <sha> --event <COMMENT|APPROVE|REQUEST_CHANGES> --body-file <path|-> [--artifact <pr-review-artifact>] [--agent <agent>] [--dry-run] [--cwd <path>]
|
|
13
14
|
`;
|
|
14
15
|
|
|
15
16
|
const BOOLEAN_FLAGS = new Set(['--dry-run']);
|
|
16
|
-
const VALUE_FLAGS = new Set(['--cwd', '--pr', '--scope', '--round', '--commit', '--event', '--body-file']);
|
|
17
|
+
const VALUE_FLAGS = new Set(['--cwd', '--pr', '--scope', '--round', '--commit', '--event', '--body-file', '--artifact', '--agent']);
|
|
17
18
|
|
|
18
19
|
function key(flag: string): string {
|
|
19
20
|
return flag.slice(2).replace(/-([a-z])/g, (_, letter: string) => letter.toUpperCase());
|
|
@@ -67,7 +68,7 @@ async function platformPrReview(args: string[] = []): Promise<void> {
|
|
|
67
68
|
const allowed: Record<string, string[]> = {
|
|
68
69
|
inspect: ['cwd', 'pr'],
|
|
69
70
|
list: ['cwd', 'pr'],
|
|
70
|
-
publish: ['cwd', 'pr', 'scope', 'round', 'commit', 'event', 'bodyFile', 'dryRun']
|
|
71
|
+
publish: ['cwd', 'pr', 'scope', 'round', 'commit', 'event', 'bodyFile', 'artifact', 'agent', 'dryRun']
|
|
71
72
|
};
|
|
72
73
|
const unexpected = Object.keys(values).find((name) => !allowed[operation]!.includes(name));
|
|
73
74
|
if (unexpected) { fail(`${operation} does not accept --${unexpected}`); return; }
|
|
@@ -86,6 +87,8 @@ async function platformPrReview(args: string[] = []): Promise<void> {
|
|
|
86
87
|
const round = Number(values.round);
|
|
87
88
|
const commit = typeof values.commit === 'string' ? values.commit : '';
|
|
88
89
|
const event = values.event as string;
|
|
90
|
+
const rawAgent = typeof values.agent === 'string' ? values.agent : '';
|
|
91
|
+
const agent = rawAgent ? normalizeAgentToken(rawAgent) : null;
|
|
89
92
|
if (!scope || !/^(?:pr\d+|TASK-\d{8}-\d{6})$/.test(scope)) { fail('publish requires --scope <taskId|pr{N}>'); return; }
|
|
90
93
|
if (!internalHandlerRoute('platform-pr-review', 'publish-task', publishSelector)
|
|
91
94
|
&& !internalHandlerRoute('platform-pr-review', 'publish-pr', publishSelector)) {
|
|
@@ -95,6 +98,12 @@ async function platformPrReview(args: string[] = []): Promise<void> {
|
|
|
95
98
|
if (!/^[0-9a-f]{7,40}$/i.test(commit)) { fail('publish requires --commit <sha>'); return; }
|
|
96
99
|
if (!['COMMENT', 'APPROVE', 'REQUEST_CHANGES'].includes(event)) { fail('publish requires --event <COMMENT|APPROVE|REQUEST_CHANGES>'); return; }
|
|
97
100
|
if (typeof values.bodyFile !== 'string') { fail('publish requires --body-file'); return; }
|
|
101
|
+
if (/^TASK-\d{8}-\d{6}$/u.test(scope) && typeof values.artifact !== 'string') { fail('task-scoped publish requires --artifact <pr-review-artifact>'); return; }
|
|
102
|
+
if (/^TASK-\d{8}-\d{6}$/u.test(scope) && !agent) {
|
|
103
|
+
fail(`task-scoped publish requires --agent <agent>${rawAgent ? `: invalid '${rawAgent}'` : ''}: ${AGENT_USAGE_HINT}`);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (rawAgent && !agent) { fail(`invalid --agent '${rawAgent}': ${AGENT_USAGE_HINT}`); return; }
|
|
98
107
|
let body: string;
|
|
99
108
|
try {
|
|
100
109
|
body = readBodyFile(values.bodyFile, cwd);
|
|
@@ -108,6 +117,8 @@ async function platformPrReview(args: string[] = []): Promise<void> {
|
|
|
108
117
|
identity: { scope, round, commitSha: commit },
|
|
109
118
|
event: event as PrReviewEvent,
|
|
110
119
|
body,
|
|
120
|
+
...(typeof values.artifact === 'string' ? { recoveryArtifact: values.artifact } : {}),
|
|
121
|
+
...(agent ? { agent } : {}),
|
|
111
122
|
dryRun: values.dryRun === true
|
|
112
123
|
}));
|
|
113
124
|
}
|
|
@@ -145,6 +145,8 @@ function internalTaskRoutes(): TaskOperationDescriptor[] {
|
|
|
145
145
|
descriptor('internal', 'task-invalidation', 'reconcile', 'task-bound', 'progress'),
|
|
146
146
|
descriptor('internal', 'task-lifecycle', 'intent', 'task-bound', 'progress'),
|
|
147
147
|
descriptor('internal', 'task-finalization', 'complete', 'task-bound', 'progress'),
|
|
148
|
+
descriptor('internal', 'task-platform-recovery', 'inspect', 'task-bound', 'diagnostic'),
|
|
149
|
+
descriptor('internal', 'task-platform-recovery', 'recover', 'task-bound', 'recovery'),
|
|
148
150
|
descriptor('internal', 'task-short-id', 'list', 'conditional', 'diagnostic'),
|
|
149
151
|
descriptor('internal', 'task-short-id', 'list-verify', 'conditional', 'diagnostic'),
|
|
150
152
|
descriptor('internal', 'task-short-id', 'alloc', 'task-bound', 'progress'),
|
|
@@ -221,7 +223,7 @@ export const INTERNAL_DISPATCHER_ROUTES = Object.freeze([
|
|
|
221
223
|
'platform-comment', 'platform-issue', 'platform-pr', 'platform-pr-review', 'pr-review-grade',
|
|
222
224
|
'platform-checks', 'task-context', 'task-ledger', 'task-warning', 'task-activity', 'task-artifact',
|
|
223
225
|
'task-orchestration', 'task-review', 'task-event', 'task-invalidation', 'task-lifecycle',
|
|
224
|
-
'task-finalization', 'task-short-id', 'task-snapshot', 'task-verify', 'task-validate', 'manual-validation'
|
|
226
|
+
'task-finalization', 'task-platform-recovery', 'task-short-id', 'task-snapshot', 'task-verify', 'task-validate', 'manual-validation'
|
|
225
227
|
]);
|
|
226
228
|
export const PUBLIC_DISPATCHER_ROUTES = Object.freeze([
|
|
227
229
|
'agent-client', 'cp', 'data', 'decide', 'help', 'init', 'merge', 'run', 'sandbox', 'server', 'task', 'sync', 'update', 'version'
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { detectRepoRoot, resolveTaskRef } from '../task/resolve-ref.ts';
|
|
2
|
+
import { readPlatformOperationJournal } from '../task/platform-operation-journal.ts';
|
|
3
|
+
import { recoverPlatformOperations } from '../task/platform-operation-recovery.ts';
|
|
4
|
+
import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.ts';
|
|
5
|
+
|
|
6
|
+
const USAGE = 'Usage: agent-infra-internal task-platform-recovery <N | TASK-id> <inspect | recover> [--agent <agent>] [--selection required|deferred|all]\n';
|
|
7
|
+
|
|
8
|
+
function option(args: readonly string[], name: string): string | undefined {
|
|
9
|
+
const index = args.indexOf(name);
|
|
10
|
+
return index >= 0 ? args[index + 1] : undefined;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async function taskPlatformRecovery(args: string[] = []): Promise<void> {
|
|
14
|
+
if (args[0] === '--help' || args[0] === '-h') { process.stdout.write(USAGE); return; }
|
|
15
|
+
if (!ensureInternalHandlerRoute('task-platform-recovery', args)) return;
|
|
16
|
+
const action = args[1];
|
|
17
|
+
const registered = action === 'inspect'
|
|
18
|
+
? internalHandlerRoute('task-platform-recovery', 'inspect', action)
|
|
19
|
+
: action === 'recover' && internalHandlerRoute('task-platform-recovery', 'recover', action);
|
|
20
|
+
if (!registered) return;
|
|
21
|
+
const repoRoot = detectRepoRoot();
|
|
22
|
+
const task = resolveTaskRef(args[0] ?? '', { repoRoot });
|
|
23
|
+
if (!task.ok) {
|
|
24
|
+
process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: task.code, message: task.message } })}\n`);
|
|
25
|
+
process.exitCode = 1;
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (action === 'inspect') {
|
|
29
|
+
try {
|
|
30
|
+
const journal = readPlatformOperationJournal(task.taskId, repoRoot);
|
|
31
|
+
process.stdout.write(`${JSON.stringify({ status: 'ready', changed: false, taskId: task.taskId, journal })}\n`);
|
|
32
|
+
} catch (error) {
|
|
33
|
+
const value = error as { code?: string; message?: string };
|
|
34
|
+
process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: value.code || 'PLATFORM_OPERATION_JOURNAL_INVALID', message: value.message || String(error) } })}\n`);
|
|
35
|
+
process.exitCode = 1;
|
|
36
|
+
}
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const agent = option(args, '--agent');
|
|
40
|
+
const selection = option(args, '--selection') ?? 'all';
|
|
41
|
+
if (!agent || !/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/u.test(agent)
|
|
42
|
+
|| !['required', 'deferred', 'all'].includes(selection)) {
|
|
43
|
+
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`);
|
|
44
|
+
process.stderr.write(USAGE);
|
|
45
|
+
process.exitCode = 1;
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const result = await recoverPlatformOperations(task.taskId, selection as 'required' | 'deferred' | 'all', { agent, cwd: repoRoot });
|
|
49
|
+
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
50
|
+
process.exitCode = result.status === 'applied' || result.status === 'no-op' ? 0 : result.status === 'blocked' ? 2 : 1;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export { taskPlatformRecovery };
|
package/lib/merge.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
import crypto from 'node:crypto';
|
|
4
5
|
import { removeDirRecursive } from './remove-dir.ts';
|
|
@@ -424,6 +425,24 @@ function formatBackupTimestamp(date: Date): string {
|
|
|
424
425
|
].join('') + `-${String(date.getHours()).padStart(2, '0')}${String(date.getMinutes()).padStart(2, '0')}${String(date.getSeconds()).padStart(2, '0')}`;
|
|
425
426
|
}
|
|
426
427
|
|
|
428
|
+
function resolveRecoveryProjectKey(repoRoot: string): string {
|
|
429
|
+
let projectKey: string | undefined;
|
|
430
|
+
try {
|
|
431
|
+
const config = JSON.parse(fs.readFileSync(path.join(repoRoot, '.agents', '.airc.json'), 'utf8')) as { project?: unknown };
|
|
432
|
+
if (typeof config.project === 'string' && config.project.trim() !== '') {
|
|
433
|
+
projectKey = config.project.trim();
|
|
434
|
+
}
|
|
435
|
+
} catch {
|
|
436
|
+
// Fall back to the repository directory when project config is unavailable.
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
projectKey ??= path.basename(repoRoot);
|
|
440
|
+
if (!/^[A-Za-z0-9_.@-]+$/.test(projectKey) || projectKey === '.' || projectKey === '..') {
|
|
441
|
+
throw new Error(`Invalid project name for recovery backups: ${projectKey}`);
|
|
442
|
+
}
|
|
443
|
+
return projectKey;
|
|
444
|
+
}
|
|
445
|
+
|
|
427
446
|
function toPosixPath(relativePath: string): string {
|
|
428
447
|
return relativePath.split(path.sep).join('/');
|
|
429
448
|
}
|
|
@@ -541,7 +560,7 @@ function buildWorkspaceIndex(workspaceDir: string): Map<string, WorkspaceRecord>
|
|
|
541
560
|
function backupTaskDir(backupRoot: string, section: MutableSection, taskDir: string, taskId: string): string {
|
|
542
561
|
const backupDir = path.join(backupRoot, section, taskId);
|
|
543
562
|
fs.mkdirSync(path.dirname(backupDir), { recursive: true });
|
|
544
|
-
fs.cpSync(taskDir, backupDir, { recursive: true });
|
|
563
|
+
fs.cpSync(taskDir, backupDir, { recursive: true, verbatimSymlinks: true });
|
|
545
564
|
return backupDir;
|
|
546
565
|
}
|
|
547
566
|
|
|
@@ -1085,16 +1104,17 @@ async function cmdMerge(args: string[]): Promise<void> {
|
|
|
1085
1104
|
throw new Error(`Source path is not a directory: ${sourcePath}`);
|
|
1086
1105
|
}
|
|
1087
1106
|
|
|
1088
|
-
const
|
|
1107
|
+
const repoRoot = process.cwd();
|
|
1108
|
+
const workspaceDir = path.join(repoRoot, '.agents', 'workspace');
|
|
1089
1109
|
const archiveDir = path.join(workspaceDir, 'archive');
|
|
1090
1110
|
const backupStamp = formatBackupTimestamp(new Date());
|
|
1091
|
-
const
|
|
1092
|
-
const
|
|
1093
|
-
const
|
|
1111
|
+
const projectKey = resolveRecoveryProjectKey(repoRoot);
|
|
1112
|
+
const recoveryRoot = path.join(os.homedir(), '.agent-infra', 'recovery-backups', projectKey, 'workspace-merge');
|
|
1113
|
+
const backupRoot = path.join(recoveryRoot, backupStamp);
|
|
1114
|
+
const report = createReport(resolvedSource, backupRoot);
|
|
1094
1115
|
detectSourceMode(resolvedSource);
|
|
1095
1116
|
validateSourceWorkspace(resolvedSource);
|
|
1096
1117
|
validateArchiveSection(workspaceDir, new Map());
|
|
1097
|
-
|
|
1098
1118
|
for (const section of ALL_SECTIONS) {
|
|
1099
1119
|
fs.mkdirSync(path.join(workspaceDir, section), { recursive: true });
|
|
1100
1120
|
}
|
|
@@ -51,7 +51,7 @@ function referencedBackfillArtifact(taskDir: string, message: string): BackfillA
|
|
|
51
51
|
.map((match) => match[1]!);
|
|
52
52
|
if (names.length !== 1) return { status: 'ignored', artifact: null, error: null };
|
|
53
53
|
const parsed = parseArtifactName(names[0]!);
|
|
54
|
-
if (!parsed ||
|
|
54
|
+
if (!parsed || !COMPLETION_BACKFILL_FAMILIES.includes(parsed.family as typeof COMPLETION_BACKFILL_FAMILIES[number])) {
|
|
55
55
|
return { status: 'ignored', artifact: null, error: null };
|
|
56
56
|
}
|
|
57
57
|
const artifactPath = path.join(taskDir, parsed.name);
|
|
@@ -141,6 +141,7 @@ async function backfillCompletionComments(
|
|
|
141
141
|
artifact: artifact.name,
|
|
142
142
|
agent: options.agent,
|
|
143
143
|
backfill: true,
|
|
144
|
+
dependency: 'required',
|
|
144
145
|
cwd: resolved.repoRoot,
|
|
145
146
|
client: options.client
|
|
146
147
|
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { computeInLabels } from './metadata-labels.ts';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
2
3
|
import type { PlatformError } from './types.ts';
|
|
3
4
|
|
|
4
5
|
type InLabelPlan = {
|
|
@@ -68,6 +69,16 @@ function validateInLabelMapping(value: unknown): { ok: true; value: Record<strin
|
|
|
68
69
|
return { ok: true, value: normalized };
|
|
69
70
|
}
|
|
70
71
|
|
|
72
|
+
function inLabelMappingDigest(value: unknown): { ok: true; digest: string } | { ok: false; error: InLabelValidationError } {
|
|
73
|
+
const mapping = validateInLabelMapping(value);
|
|
74
|
+
if (!mapping.ok) return mapping;
|
|
75
|
+
const canonical = Object.fromEntries(Object.keys(mapping.value).sort().map((name) => [
|
|
76
|
+
name,
|
|
77
|
+
[...new Set(mapping.value[name]!)].sort()
|
|
78
|
+
]));
|
|
79
|
+
return { ok: true, digest: createHash('sha256').update(JSON.stringify(canonical)).digest('hex') };
|
|
80
|
+
}
|
|
81
|
+
|
|
71
82
|
function repositoryLabelPages(value: unknown): unknown[][] | null {
|
|
72
83
|
if (!Array.isArray(value)) return null;
|
|
73
84
|
if (value.length === 0) return [];
|
|
@@ -197,6 +208,7 @@ export {
|
|
|
197
208
|
extractRepositoryLabelNames,
|
|
198
209
|
flattenPages,
|
|
199
210
|
inLabels,
|
|
211
|
+
inLabelMappingDigest,
|
|
200
212
|
labelDelta,
|
|
201
213
|
mergeInLabels,
|
|
202
214
|
planInLabelUpdate,
|