@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/README.md
CHANGED
|
@@ -240,7 +240,7 @@ In-depth guides live under [`docs/en/`](./docs/en/README.md):
|
|
|
240
240
|
- [Feishu Bridge](./docs/en/feishu-bridge.md) — configure the Feishu long-connection adapter, command routing, permissions, and TUI selection
|
|
241
241
|
- [Built-in AI Skills](./docs/en/skills.md) — the full skill catalog by use case
|
|
242
242
|
- [Custom Skills](./docs/en/custom-skills.md) — create and sync project-specific skills
|
|
243
|
-
- [Custom
|
|
243
|
+
- [Custom Tool Invocations](./docs/en/custom-tool-invocations.md) — configure custom-tool next-step commands
|
|
244
244
|
- [Prebuilt Workflows](./docs/en/workflows.md) — the gated delivery lifecycle and example flow
|
|
245
245
|
- [Configuration Reference](./docs/en/configuration.md) — `.agents/.airc.json`, external sources, version management
|
|
246
246
|
- [File Management Strategies](./docs/en/file-management.md) — managed / merged / ejected update strategies
|
package/README.zh-CN.md
CHANGED
|
@@ -239,7 +239,7 @@ my-project/
|
|
|
239
239
|
- [飞书桥接](./docs/zh-CN/feishu-bridge.md) — 配置飞书长连接 adapter、命令路由、权限和 TUI 选择
|
|
240
240
|
- [内置 AI Skills](./docs/zh-CN/skills.md) — 按使用场景分组的完整 skill 清单
|
|
241
241
|
- [自定义 Skills](./docs/zh-CN/custom-skills.md) — 创建并同步项目专属 skill
|
|
242
|
-
- [
|
|
242
|
+
- [自定义工具调用](./docs/zh-CN/custom-tool-invocations.md) — 配置自定义工具的下一步命令
|
|
243
243
|
- [预置工作流](./docs/zh-CN/workflows.md) — 分阶段交付链路与示例流程
|
|
244
244
|
- [配置参考](./docs/zh-CN/configuration.md) — `.agents/.airc.json`、外部源、版本管理
|
|
245
245
|
- [文件管理策略](./docs/zh-CN/file-management.md) — managed / merged / ejected 更新策略
|
package/dist/bin/internal-cli.js
CHANGED
|
@@ -15,6 +15,7 @@ const taskWorkflowCommand = command === 'task-artifact'
|
|
|
15
15
|
|| command === 'task-ledger'
|
|
16
16
|
|| command === 'task-invalidation'
|
|
17
17
|
|| command === 'task-warning';
|
|
18
|
+
const taskPlatformRecoveryCommand = command === 'task-platform-recovery';
|
|
18
19
|
const manualValidationWorkflowCommand = command === 'manual-validation';
|
|
19
20
|
const taskControlMarkers = [
|
|
20
21
|
'AGENT_INFRA_TASK_ID', 'AGENT_INFRA_CONTROL_TOKEN', 'AGENT_INFRA_CONTROL_GENERATION',
|
|
@@ -48,9 +49,9 @@ function taskControlTransportFailure(message, code = 'TASK_CONTROL_TRANSPORT_INV
|
|
|
48
49
|
process.exit(1);
|
|
49
50
|
}
|
|
50
51
|
let controlRouted = false;
|
|
51
|
-
if (!taskViewGuardFailed && !markerlessHelp && (taskControlCommand || taskWorkflowCommand || manualValidationWorkflowCommand)) {
|
|
52
|
+
if (!taskViewGuardFailed && !markerlessHelp && (taskControlCommand || taskWorkflowCommand || manualValidationWorkflowCommand || taskPlatformRecoveryCommand)) {
|
|
52
53
|
const transport = resolveSandboxControlTransport(process.env, {
|
|
53
|
-
localWorkflow: command === 'task-orchestration' || command === 'task-finalization' || taskWorkflowCommand || manualValidationWorkflowCommand
|
|
54
|
+
localWorkflow: command === 'task-orchestration' || command === 'task-finalization' || taskWorkflowCommand || manualValidationWorkflowCommand || taskPlatformRecoveryCommand
|
|
54
55
|
});
|
|
55
56
|
switch (transport.kind) {
|
|
56
57
|
case 'fail-closed': {
|
|
@@ -90,6 +91,11 @@ if (!controlRouted && !taskViewGuardFailed && internalRouteRegistered)
|
|
|
90
91
|
await taskFinalization(process.argv.slice(3));
|
|
91
92
|
break;
|
|
92
93
|
}
|
|
94
|
+
case 'task-platform-recovery': {
|
|
95
|
+
const { taskPlatformRecovery } = await import('../lib/internal/task-platform-recovery.js');
|
|
96
|
+
await taskPlatformRecovery(process.argv.slice(3));
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
93
99
|
case 'task-create': {
|
|
94
100
|
const { taskCreate } = await import('../lib/internal/task-create.js');
|
|
95
101
|
await taskCreate(process.argv.slice(3));
|
|
@@ -50,7 +50,7 @@ const codexAdapter = defineAgentClientAdapter({
|
|
|
50
50
|
containerMount: '/home/devuser/.codex',
|
|
51
51
|
versionCmd: 'codex --version',
|
|
52
52
|
setupHint: 'Run codex once inside the container and choose Device Code login if needed.',
|
|
53
|
-
tmpfs: { size: '512m', seed: ['config.toml', 'model-catalogs'] },
|
|
53
|
+
tmpfs: { size: '512m', seed: ['config.toml', 'model-catalogs'], exec: true },
|
|
54
54
|
hostLiveMounts: [
|
|
55
55
|
{
|
|
56
56
|
hostPath: hostJoin(home, '.codex', 'auth.json'),
|
|
@@ -114,10 +114,18 @@ function normalizeAgentClients(input) {
|
|
|
114
114
|
if (hasOwn(input, 'tuis'))
|
|
115
115
|
fail('INVALID_AGENT_CLIENTS', 'tuis');
|
|
116
116
|
const sandbox = isRecord(input.sandbox) ? input.sandbox : undefined;
|
|
117
|
-
if (
|
|
118
|
-
|
|
117
|
+
if (sandbox && hasOwn(sandbox, 'customTools')) {
|
|
118
|
+
fail('INVALID_AGENT_CLIENTS', 'sandbox.customTools');
|
|
119
|
+
}
|
|
120
|
+
if (sandbox && hasOwn(sandbox, 'tools')) {
|
|
121
|
+
if (!isRecord(sandbox.tools))
|
|
122
|
+
fail('INVALID_AGENT_CLIENTS', 'sandbox.tools');
|
|
123
|
+
const tools = sandbox.tools;
|
|
124
|
+
if (!Array.isArray(tools.ids))
|
|
125
|
+
fail('INVALID_AGENT_CLIENTS', 'sandbox.tools.ids');
|
|
126
|
+
for (const [index, tool] of tools.ids.entries()) {
|
|
119
127
|
if (isAgentClientId(tool)) {
|
|
120
|
-
fail('INVALID_AGENT_CLIENTS', `sandbox.tools[${index}]`);
|
|
128
|
+
fail('INVALID_AGENT_CLIENTS', `sandbox.tools.ids[${index}]`);
|
|
121
129
|
}
|
|
122
130
|
}
|
|
123
131
|
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
const ALLOWED_PLACEHOLDERS = Object.freeze(['skillName', 'projectName']);
|
|
2
|
+
function isRecord(value) {
|
|
3
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
4
|
+
}
|
|
5
|
+
function isNonEmptySingleLine(value) {
|
|
6
|
+
return typeof value === 'string'
|
|
7
|
+
&& value.trim() !== ''
|
|
8
|
+
&& !/[\r\n]/.test(value);
|
|
9
|
+
}
|
|
10
|
+
function hasValidPlaceholders(invocation) {
|
|
11
|
+
const placeholders = [
|
|
12
|
+
...invocation.matchAll(/\$\{([^}]+)\}/g)
|
|
13
|
+
].map((match) => match[1]);
|
|
14
|
+
return placeholders.includes('skillName')
|
|
15
|
+
&& placeholders.every((placeholder) => ALLOWED_PLACEHOLDERS.includes(placeholder))
|
|
16
|
+
&& !invocation
|
|
17
|
+
.replaceAll('${skillName}', '')
|
|
18
|
+
.replaceAll('${projectName}', '')
|
|
19
|
+
.includes('${');
|
|
20
|
+
}
|
|
21
|
+
function normalizeCustomToolInvocations(config) {
|
|
22
|
+
const items = [];
|
|
23
|
+
const diagnostics = [];
|
|
24
|
+
const root = isRecord(config) ? config : {};
|
|
25
|
+
const sandbox = isRecord(root.sandbox) ? root.sandbox : {};
|
|
26
|
+
const sandboxTools = isRecord(sandbox.tools) ? sandbox.tools : { ids: ['agent-infra'] };
|
|
27
|
+
const definitions = sandboxTools.definitions ?? {};
|
|
28
|
+
const selectedIds = sandboxTools.ids;
|
|
29
|
+
if (!Array.isArray(selectedIds)) {
|
|
30
|
+
return Object.freeze({
|
|
31
|
+
items: Object.freeze([]),
|
|
32
|
+
diagnostics: Object.freeze([
|
|
33
|
+
Object.freeze({
|
|
34
|
+
code: 'INVALID_CUSTOM_TOOL_CONFIG',
|
|
35
|
+
path: 'sandbox.tools.ids'
|
|
36
|
+
})
|
|
37
|
+
])
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
if (!isRecord(definitions)) {
|
|
41
|
+
return Object.freeze({
|
|
42
|
+
items: Object.freeze([]),
|
|
43
|
+
diagnostics: Object.freeze([
|
|
44
|
+
Object.freeze({
|
|
45
|
+
code: 'INVALID_CUSTOM_TOOL_CONFIG',
|
|
46
|
+
path: 'sandbox.tools.definitions'
|
|
47
|
+
})
|
|
48
|
+
])
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
for (const [index, id] of selectedIds.entries()) {
|
|
52
|
+
if (typeof id !== 'string') {
|
|
53
|
+
diagnostics.push({
|
|
54
|
+
code: 'INVALID_CUSTOM_TOOL_INVOCATION',
|
|
55
|
+
path: `sandbox.tools.ids[${index}]`
|
|
56
|
+
});
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
const candidate = definitions[id];
|
|
60
|
+
if (!isRecord(candidate))
|
|
61
|
+
continue;
|
|
62
|
+
if (candidate.invoke === undefined)
|
|
63
|
+
continue;
|
|
64
|
+
const base = `sandbox.tools.definitions.${id}`;
|
|
65
|
+
const name = candidate.name === undefined ? id : candidate.name;
|
|
66
|
+
if (!isNonEmptySingleLine(name)) {
|
|
67
|
+
diagnostics.push({ code: 'INVALID_CUSTOM_TOOL_INVOCATION', path: `${base}.name` });
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (!isNonEmptySingleLine(candidate.invoke)) {
|
|
71
|
+
diagnostics.push({ code: 'INVALID_CUSTOM_TOOL_INVOCATION', path: `${base}.invoke` });
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (!hasValidPlaceholders(candidate.invoke)) {
|
|
75
|
+
diagnostics.push({
|
|
76
|
+
code: 'INVALID_CUSTOM_TOOL_INVOCATION_PLACEHOLDER',
|
|
77
|
+
path: `${base}.invoke`
|
|
78
|
+
});
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
items.push(Object.freeze({
|
|
82
|
+
name,
|
|
83
|
+
invocation: candidate.invoke
|
|
84
|
+
}));
|
|
85
|
+
}
|
|
86
|
+
return Object.freeze({
|
|
87
|
+
items: Object.freeze(items),
|
|
88
|
+
diagnostics: Object.freeze(diagnostics.map((diagnostic) => Object.freeze(diagnostic)))
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
export { normalizeCustomToolInvocations };
|
|
92
|
+
//# sourceMappingURL=custom-tool-invocations.js.map
|
|
@@ -37,7 +37,7 @@ function renderNextStepCommands(input) {
|
|
|
37
37
|
args
|
|
38
38
|
})
|
|
39
39
|
}));
|
|
40
|
-
const custom = input.
|
|
40
|
+
const custom = input.customToolInvocations.map((tool) => Object.freeze({
|
|
41
41
|
source: 'custom',
|
|
42
42
|
displayName: tool.name,
|
|
43
43
|
command: renderAgentClientInvocation(tool.invocation, {
|
|
@@ -3,7 +3,7 @@ import fs from 'node:fs';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import defaults from '../defaults.json' with { type: 'json' };
|
|
5
5
|
import { normalizeAgentClients, serializeAgentClients } from './config.js';
|
|
6
|
-
import {
|
|
6
|
+
import { normalizeCustomToolInvocations } from './custom-tool-invocations.js';
|
|
7
7
|
import { renderNextStepCommands } from './next-steps.js';
|
|
8
8
|
import { planAgentClientProjectAssets } from './project-assets.js';
|
|
9
9
|
import { listAgentClientAdapters, listEnabledAgentClientAdapters } from './registry.js';
|
|
@@ -141,11 +141,11 @@ function planAgentClientReconciliation(input) {
|
|
|
141
141
|
if (Object.keys(baselines).length === 0) {
|
|
142
142
|
delete nextConfig.files.managedBaselines;
|
|
143
143
|
}
|
|
144
|
-
const custom =
|
|
144
|
+
const custom = normalizeCustomToolInvocations(input.config);
|
|
145
145
|
const nextSteps = renderNextStepCommands({
|
|
146
146
|
projectName,
|
|
147
147
|
state: desired,
|
|
148
|
-
|
|
148
|
+
customToolInvocations: custom.items,
|
|
149
149
|
skillName: 'update-agent-infra'
|
|
150
150
|
});
|
|
151
151
|
const actionableSeed = seedOperations.some((operation) => operation.kind === 'write' || operation.kind === 'remove');
|
|
@@ -38,16 +38,24 @@ const AGENT_CLIENTS_SCHEMA = {
|
|
|
38
38
|
}
|
|
39
39
|
}))
|
|
40
40
|
},
|
|
41
|
-
|
|
42
|
-
type: '
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
41
|
+
sandbox: {
|
|
42
|
+
type: 'object',
|
|
43
|
+
properties: {
|
|
44
|
+
tools: {
|
|
45
|
+
type: 'object',
|
|
46
|
+
properties: {
|
|
47
|
+
ids: { type: 'array', items: { type: 'string' } },
|
|
48
|
+
definitions: {
|
|
49
|
+
type: 'object',
|
|
50
|
+
additionalProperties: {
|
|
51
|
+
type: 'object',
|
|
52
|
+
properties: {
|
|
53
|
+
name: { type: 'string' },
|
|
54
|
+
invoke: { type: 'string' }
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
51
59
|
}
|
|
52
60
|
}
|
|
53
61
|
}
|
package/dist/lib/defaults.json
CHANGED
package/dist/lib/init.js
CHANGED
|
@@ -228,8 +228,8 @@ async function cmdInit() {
|
|
|
228
228
|
console.log(' npm install -g @fitlab-ai/agent-infra');
|
|
229
229
|
console.log('');
|
|
230
230
|
if (workflowPlan.nextSteps.length === 0) {
|
|
231
|
-
console.log(' No Agent Client
|
|
232
|
-
console.log(` Configure "
|
|
231
|
+
console.log(' No Agent Client or sandbox custom tool integration enabled.');
|
|
232
|
+
console.log(` Configure a custom tool in "sandbox.tools.definitions" and select it in "sandbox.tools.ids" in ${configPath}.`);
|
|
233
233
|
console.log('');
|
|
234
234
|
}
|
|
235
235
|
else {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { AgentClientConfigError, normalizeAgentClients } from '../agent-clients/config.js';
|
|
4
|
-
import {
|
|
4
|
+
import { normalizeCustomToolInvocations } from '../agent-clients/custom-tool-invocations.js';
|
|
5
5
|
import { renderNextStepCommands } from '../agent-clients/next-steps.js';
|
|
6
6
|
import { getAgentClientModelSelection } from '../agent-clients/registry.js';
|
|
7
7
|
import { isAgentClientId } from '../agent-clients/types.js';
|
|
@@ -139,11 +139,11 @@ function agentClient(args = []) {
|
|
|
139
139
|
}
|
|
140
140
|
try {
|
|
141
141
|
const clients = normalizeAgentClients(config);
|
|
142
|
-
const custom =
|
|
142
|
+
const custom = normalizeCustomToolInvocations(config);
|
|
143
143
|
const commands = renderNextStepCommands({
|
|
144
144
|
projectName: String(config.project ?? ''),
|
|
145
145
|
state: clients.state,
|
|
146
|
-
|
|
146
|
+
customToolInvocations: custom.items,
|
|
147
147
|
skillName: parsed.skillName,
|
|
148
148
|
...(parsed.taskRef ? { taskRef: parsed.taskRef } : {}),
|
|
149
149
|
...(parsed.version ? { version: parsed.version } : {})
|
|
@@ -60,6 +60,7 @@ export const INTERNAL_HANDLER_ROUTE_SELECTORS = Object.freeze({
|
|
|
60
60
|
'task-invalidation': ['reconcile'],
|
|
61
61
|
'task-lifecycle': ['intent'],
|
|
62
62
|
'task-finalization': ['complete'],
|
|
63
|
+
'task-platform-recovery': ['inspect', 'recover'],
|
|
63
64
|
'task-short-id': ['list', 'list-verify', 'alloc', 'release', 'resolve'],
|
|
64
65
|
'task-snapshot': ['snapshot'],
|
|
65
66
|
'task-verify': ['event'],
|
|
@@ -88,6 +89,8 @@ export function internalRouteSelector(command, args) {
|
|
|
88
89
|
return args[1] ? 'intent' : '';
|
|
89
90
|
if (command === 'task-finalization')
|
|
90
91
|
return args[1] === 'complete' ? 'complete' : '';
|
|
92
|
+
if (command === 'task-platform-recovery')
|
|
93
|
+
return args[1] ?? '';
|
|
91
94
|
if (command === 'task-event')
|
|
92
95
|
return args[1] ? 'event' : '';
|
|
93
96
|
if (command === 'task-verify')
|
|
@@ -3,12 +3,13 @@ import path from 'node:path';
|
|
|
3
3
|
import { inspectPlatformPullRequestByNumber } from '../platform/pull-requests.js';
|
|
4
4
|
import { listPrReviews, publishPrReview } from '../platform/pr-review.js';
|
|
5
5
|
import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.js';
|
|
6
|
+
import { normalizeAgentToken, AGENT_USAGE_HINT } from '../agent-clients/tokens.js';
|
|
6
7
|
const USAGE = `Usage: agent-infra-internal platform-pr-review inspect --pr <token> [--cwd <path>]
|
|
7
8
|
agent-infra-internal platform-pr-review list --pr <token> [--cwd <path>]
|
|
8
|
-
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>]
|
|
9
|
+
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>]
|
|
9
10
|
`;
|
|
10
11
|
const BOOLEAN_FLAGS = new Set(['--dry-run']);
|
|
11
|
-
const VALUE_FLAGS = new Set(['--cwd', '--pr', '--scope', '--round', '--commit', '--event', '--body-file']);
|
|
12
|
+
const VALUE_FLAGS = new Set(['--cwd', '--pr', '--scope', '--round', '--commit', '--event', '--body-file', '--artifact', '--agent']);
|
|
12
13
|
function key(flag) {
|
|
13
14
|
return flag.slice(2).replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
14
15
|
}
|
|
@@ -74,7 +75,7 @@ async function platformPrReview(args = []) {
|
|
|
74
75
|
const allowed = {
|
|
75
76
|
inspect: ['cwd', 'pr'],
|
|
76
77
|
list: ['cwd', 'pr'],
|
|
77
|
-
publish: ['cwd', 'pr', 'scope', 'round', 'commit', 'event', 'bodyFile', 'dryRun']
|
|
78
|
+
publish: ['cwd', 'pr', 'scope', 'round', 'commit', 'event', 'bodyFile', 'artifact', 'agent', 'dryRun']
|
|
78
79
|
};
|
|
79
80
|
const unexpected = Object.keys(values).find((name) => !allowed[operation].includes(name));
|
|
80
81
|
if (unexpected) {
|
|
@@ -99,6 +100,8 @@ async function platformPrReview(args = []) {
|
|
|
99
100
|
const round = Number(values.round);
|
|
100
101
|
const commit = typeof values.commit === 'string' ? values.commit : '';
|
|
101
102
|
const event = values.event;
|
|
103
|
+
const rawAgent = typeof values.agent === 'string' ? values.agent : '';
|
|
104
|
+
const agent = rawAgent ? normalizeAgentToken(rawAgent) : null;
|
|
102
105
|
if (!scope || !/^(?:pr\d+|TASK-\d{8}-\d{6})$/.test(scope)) {
|
|
103
106
|
fail('publish requires --scope <taskId|pr{N}>');
|
|
104
107
|
return;
|
|
@@ -124,6 +127,18 @@ async function platformPrReview(args = []) {
|
|
|
124
127
|
fail('publish requires --body-file');
|
|
125
128
|
return;
|
|
126
129
|
}
|
|
130
|
+
if (/^TASK-\d{8}-\d{6}$/u.test(scope) && typeof values.artifact !== 'string') {
|
|
131
|
+
fail('task-scoped publish requires --artifact <pr-review-artifact>');
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
if (/^TASK-\d{8}-\d{6}$/u.test(scope) && !agent) {
|
|
135
|
+
fail(`task-scoped publish requires --agent <agent>${rawAgent ? `: invalid '${rawAgent}'` : ''}: ${AGENT_USAGE_HINT}`);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (rawAgent && !agent) {
|
|
139
|
+
fail(`invalid --agent '${rawAgent}': ${AGENT_USAGE_HINT}`);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
127
142
|
let body;
|
|
128
143
|
try {
|
|
129
144
|
body = readBodyFile(values.bodyFile, cwd);
|
|
@@ -138,6 +153,8 @@ async function platformPrReview(args = []) {
|
|
|
138
153
|
identity: { scope, round, commitSha: commit },
|
|
139
154
|
event: event,
|
|
140
155
|
body,
|
|
156
|
+
...(typeof values.artifact === 'string' ? { recoveryArtifact: values.artifact } : {}),
|
|
157
|
+
...(agent ? { agent } : {}),
|
|
141
158
|
dryRun: values.dryRun === true
|
|
142
159
|
}));
|
|
143
160
|
}
|
|
@@ -105,6 +105,8 @@ function internalTaskRoutes() {
|
|
|
105
105
|
descriptor('internal', 'task-invalidation', 'reconcile', 'task-bound', 'progress'),
|
|
106
106
|
descriptor('internal', 'task-lifecycle', 'intent', 'task-bound', 'progress'),
|
|
107
107
|
descriptor('internal', 'task-finalization', 'complete', 'task-bound', 'progress'),
|
|
108
|
+
descriptor('internal', 'task-platform-recovery', 'inspect', 'task-bound', 'diagnostic'),
|
|
109
|
+
descriptor('internal', 'task-platform-recovery', 'recover', 'task-bound', 'recovery'),
|
|
108
110
|
descriptor('internal', 'task-short-id', 'list', 'conditional', 'diagnostic'),
|
|
109
111
|
descriptor('internal', 'task-short-id', 'list-verify', 'conditional', 'diagnostic'),
|
|
110
112
|
descriptor('internal', 'task-short-id', 'alloc', 'task-bound', 'progress'),
|
|
@@ -178,7 +180,7 @@ export const INTERNAL_DISPATCHER_ROUTES = Object.freeze([
|
|
|
178
180
|
'platform-comment', 'platform-issue', 'platform-pr', 'platform-pr-review', 'pr-review-grade',
|
|
179
181
|
'platform-checks', 'task-context', 'task-ledger', 'task-warning', 'task-activity', 'task-artifact',
|
|
180
182
|
'task-orchestration', 'task-review', 'task-event', 'task-invalidation', 'task-lifecycle',
|
|
181
|
-
'task-finalization', 'task-short-id', 'task-snapshot', 'task-verify', 'task-validate', 'manual-validation'
|
|
183
|
+
'task-finalization', 'task-platform-recovery', 'task-short-id', 'task-snapshot', 'task-verify', 'task-validate', 'manual-validation'
|
|
182
184
|
]);
|
|
183
185
|
export const PUBLIC_DISPATCHER_ROUTES = Object.freeze([
|
|
184
186
|
'agent-client', 'cp', 'data', 'decide', 'help', 'init', 'merge', 'run', 'sandbox', 'server', 'task', 'sync', 'update', 'version'
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { detectRepoRoot, resolveTaskRef } from '../task/resolve-ref.js';
|
|
2
|
+
import { readPlatformOperationJournal } from '../task/platform-operation-journal.js';
|
|
3
|
+
import { recoverPlatformOperations } from '../task/platform-operation-recovery.js';
|
|
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';
|
|
6
|
+
function option(args, name) {
|
|
7
|
+
const index = args.indexOf(name);
|
|
8
|
+
return index >= 0 ? args[index + 1] : undefined;
|
|
9
|
+
}
|
|
10
|
+
async function taskPlatformRecovery(args = []) {
|
|
11
|
+
if (args[0] === '--help' || args[0] === '-h') {
|
|
12
|
+
process.stdout.write(USAGE);
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
if (!ensureInternalHandlerRoute('task-platform-recovery', args))
|
|
16
|
+
return;
|
|
17
|
+
const action = args[1];
|
|
18
|
+
const registered = action === 'inspect'
|
|
19
|
+
? internalHandlerRoute('task-platform-recovery', 'inspect', action)
|
|
20
|
+
: action === 'recover' && internalHandlerRoute('task-platform-recovery', 'recover', action);
|
|
21
|
+
if (!registered)
|
|
22
|
+
return;
|
|
23
|
+
const repoRoot = detectRepoRoot();
|
|
24
|
+
const task = resolveTaskRef(args[0] ?? '', { repoRoot });
|
|
25
|
+
if (!task.ok) {
|
|
26
|
+
process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: task.code, message: task.message } })}\n`);
|
|
27
|
+
process.exitCode = 1;
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
if (action === 'inspect') {
|
|
31
|
+
try {
|
|
32
|
+
const journal = readPlatformOperationJournal(task.taskId, repoRoot);
|
|
33
|
+
process.stdout.write(`${JSON.stringify({ status: 'ready', changed: false, taskId: task.taskId, journal })}\n`);
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
const value = error;
|
|
37
|
+
process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: value.code || 'PLATFORM_OPERATION_JOURNAL_INVALID', message: value.message || String(error) } })}\n`);
|
|
38
|
+
process.exitCode = 1;
|
|
39
|
+
}
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const agent = option(args, '--agent');
|
|
43
|
+
const selection = option(args, '--selection') ?? 'all';
|
|
44
|
+
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`);
|
|
47
|
+
process.stderr.write(USAGE);
|
|
48
|
+
process.exitCode = 1;
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const result = await recoverPlatformOperations(task.taskId, selection, { agent, cwd: repoRoot });
|
|
52
|
+
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
53
|
+
process.exitCode = result.status === 'applied' || result.status === 'no-op' ? 0 : result.status === 'blocked' ? 2 : 1;
|
|
54
|
+
}
|
|
55
|
+
export { taskPlatformRecovery };
|
|
56
|
+
//# sourceMappingURL=task-platform-recovery.js.map
|
package/dist/lib/merge.js
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.js';
|
|
@@ -305,6 +306,23 @@ function formatBackupTimestamp(date) {
|
|
|
305
306
|
String(date.getDate()).padStart(2, '0')
|
|
306
307
|
].join('') + `-${String(date.getHours()).padStart(2, '0')}${String(date.getMinutes()).padStart(2, '0')}${String(date.getSeconds()).padStart(2, '0')}`;
|
|
307
308
|
}
|
|
309
|
+
function resolveRecoveryProjectKey(repoRoot) {
|
|
310
|
+
let projectKey;
|
|
311
|
+
try {
|
|
312
|
+
const config = JSON.parse(fs.readFileSync(path.join(repoRoot, '.agents', '.airc.json'), 'utf8'));
|
|
313
|
+
if (typeof config.project === 'string' && config.project.trim() !== '') {
|
|
314
|
+
projectKey = config.project.trim();
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
catch {
|
|
318
|
+
// Fall back to the repository directory when project config is unavailable.
|
|
319
|
+
}
|
|
320
|
+
projectKey ??= path.basename(repoRoot);
|
|
321
|
+
if (!/^[A-Za-z0-9_.@-]+$/.test(projectKey) || projectKey === '.' || projectKey === '..') {
|
|
322
|
+
throw new Error(`Invalid project name for recovery backups: ${projectKey}`);
|
|
323
|
+
}
|
|
324
|
+
return projectKey;
|
|
325
|
+
}
|
|
308
326
|
function toPosixPath(relativePath) {
|
|
309
327
|
return relativePath.split(path.sep).join('/');
|
|
310
328
|
}
|
|
@@ -400,7 +418,7 @@ function buildWorkspaceIndex(workspaceDir) {
|
|
|
400
418
|
function backupTaskDir(backupRoot, section, taskDir, taskId) {
|
|
401
419
|
const backupDir = path.join(backupRoot, section, taskId);
|
|
402
420
|
fs.mkdirSync(path.dirname(backupDir), { recursive: true });
|
|
403
|
-
fs.cpSync(taskDir, backupDir, { recursive: true });
|
|
421
|
+
fs.cpSync(taskDir, backupDir, { recursive: true, verbatimSymlinks: true });
|
|
404
422
|
return backupDir;
|
|
405
423
|
}
|
|
406
424
|
function copyTaskToSection(sourceTask, workspaceDir) {
|
|
@@ -872,12 +890,14 @@ async function cmdMerge(args) {
|
|
|
872
890
|
if (!fs.lstatSync(resolvedSource).isDirectory()) {
|
|
873
891
|
throw new Error(`Source path is not a directory: ${sourcePath}`);
|
|
874
892
|
}
|
|
875
|
-
const
|
|
893
|
+
const repoRoot = process.cwd();
|
|
894
|
+
const workspaceDir = path.join(repoRoot, '.agents', 'workspace');
|
|
876
895
|
const archiveDir = path.join(workspaceDir, 'archive');
|
|
877
896
|
const backupStamp = formatBackupTimestamp(new Date());
|
|
878
|
-
const
|
|
879
|
-
const
|
|
880
|
-
const
|
|
897
|
+
const projectKey = resolveRecoveryProjectKey(repoRoot);
|
|
898
|
+
const recoveryRoot = path.join(os.homedir(), '.agent-infra', 'recovery-backups', projectKey, 'workspace-merge');
|
|
899
|
+
const backupRoot = path.join(recoveryRoot, backupStamp);
|
|
900
|
+
const report = createReport(resolvedSource, backupRoot);
|
|
881
901
|
detectSourceMode(resolvedSource);
|
|
882
902
|
validateSourceWorkspace(resolvedSource);
|
|
883
903
|
validateArchiveSection(workspaceDir, new Map());
|
|
@@ -19,7 +19,7 @@ function referencedBackfillArtifact(taskDir, message) {
|
|
|
19
19
|
if (names.length !== 1)
|
|
20
20
|
return { status: 'ignored', artifact: null, error: null };
|
|
21
21
|
const parsed = parseArtifactName(names[0]);
|
|
22
|
-
if (!parsed ||
|
|
22
|
+
if (!parsed || !COMPLETION_BACKFILL_FAMILIES.includes(parsed.family)) {
|
|
23
23
|
return { status: 'ignored', artifact: null, error: null };
|
|
24
24
|
}
|
|
25
25
|
const artifactPath = path.join(taskDir, parsed.name);
|
|
@@ -112,6 +112,7 @@ async function backfillCompletionComments(taskRef, options) {
|
|
|
112
112
|
artifact: artifact.name,
|
|
113
113
|
agent: options.agent,
|
|
114
114
|
backfill: true,
|
|
115
|
+
dependency: 'required',
|
|
115
116
|
cwd: resolved.repoRoot,
|
|
116
117
|
client: options.client
|
|
117
118
|
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { computeInLabels } from './metadata-labels.js';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
2
3
|
function inLabels(labels) {
|
|
3
4
|
return [...new Set(labels.filter((label) => label.startsWith('in:')))].sort();
|
|
4
5
|
}
|
|
@@ -26,6 +27,16 @@ function validateInLabelMapping(value) {
|
|
|
26
27
|
}
|
|
27
28
|
return { ok: true, value: normalized };
|
|
28
29
|
}
|
|
30
|
+
function inLabelMappingDigest(value) {
|
|
31
|
+
const mapping = validateInLabelMapping(value);
|
|
32
|
+
if (!mapping.ok)
|
|
33
|
+
return mapping;
|
|
34
|
+
const canonical = Object.fromEntries(Object.keys(mapping.value).sort().map((name) => [
|
|
35
|
+
name,
|
|
36
|
+
[...new Set(mapping.value[name])].sort()
|
|
37
|
+
]));
|
|
38
|
+
return { ok: true, digest: createHash('sha256').update(JSON.stringify(canonical)).digest('hex') };
|
|
39
|
+
}
|
|
29
40
|
function repositoryLabelPages(value) {
|
|
30
41
|
if (!Array.isArray(value))
|
|
31
42
|
return null;
|
|
@@ -139,5 +150,5 @@ function extractPullRequestFileNames(value) {
|
|
|
139
150
|
function resourceSnapshot(kind, number, labels) {
|
|
140
151
|
return { kind, number, labels: [...labels].sort() };
|
|
141
152
|
}
|
|
142
|
-
export { extractPullRequestFileNames, extractRepositoryLabelNames, flattenPages, inLabels, labelDelta, mergeInLabels, planInLabelUpdate, resourceSnapshot, syncLabelDelta, validateInLabelMapping, validateRepositoryLabelPayload };
|
|
153
|
+
export { extractPullRequestFileNames, extractRepositoryLabelNames, flattenPages, inLabels, inLabelMappingDigest, labelDelta, mergeInLabels, planInLabelUpdate, resourceSnapshot, syncLabelDelta, validateInLabelMapping, validateRepositoryLabelPayload };
|
|
143
154
|
//# sourceMappingURL=in-label-sync.js.map
|