@fitlab-ai/agent-infra 0.11.8 → 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/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/init.js +2 -2
- package/dist/lib/internal/agent-client.js +3 -3
- package/dist/lib/sandbox/recovery.js +1 -22
- package/dist/lib/sandbox/tools.js +2 -0
- package/dist/lib/task/finalization.js +90 -93
- package/dist/lib/task/verification.js +0 -1
- 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/init.ts +2 -2
- package/lib/internal/agent-client.ts +3 -3
- package/lib/sandbox/recovery.ts +0 -25
- package/lib/sandbox/tool-types.ts +1 -0
- package/lib/sandbox/tools.ts +2 -0
- package/lib/task/finalization.ts +91 -88
- package/lib/task/verification.ts +1 -2
- 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/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/commit/SKILL.en.md +1 -1
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +1 -1
- 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 +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 +2 -268
- 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 更新策略
|
|
@@ -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/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 } : {})
|
|
@@ -4,8 +4,7 @@ import path from 'node:path';
|
|
|
4
4
|
import { spawn } from 'node:child_process';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
6
|
import { nodeEntryArgs } from './control/executor.js';
|
|
7
|
-
import { containerNameCandidates, sandboxBranchLabel, sandboxLabel,
|
|
8
|
-
import { isAgentClientId } from '../agent-clients/types.js';
|
|
7
|
+
import { containerNameCandidates, sandboxBranchLabel, sandboxLabel, sandboxTaskIdLabel, sandboxWorkspaceModeLabel, worktreeDirCandidates } from './constants.js';
|
|
9
8
|
import { createSandboxCapabilityPlan } from './agent-client-reconciler.js';
|
|
10
9
|
import { toEnginePath } from './engines/wsl2-paths.js';
|
|
11
10
|
import { sandboxCoreBindMounts } from './mounts.js';
|
|
@@ -277,12 +276,6 @@ export function classifySandboxRecovery(snapshot) {
|
|
|
277
276
|
: `Container identity ${actualIdentity} on branch ${snapshot.actualBranch ?? 'unknown'} does not match requested ${expectedIdentity} on branch ${snapshot.expectedBranch}.`
|
|
278
277
|
});
|
|
279
278
|
}
|
|
280
|
-
if (snapshot.runtimeCapabilityOk === false) {
|
|
281
|
-
findings.push({
|
|
282
|
-
repairKind: 'hard-failure',
|
|
283
|
-
message: 'Container runtime capability signature does not match the current sandbox plan.'
|
|
284
|
-
});
|
|
285
|
-
}
|
|
286
279
|
if (snapshot.workspaceTopology === 'legacy-parent') {
|
|
287
280
|
findings.push({
|
|
288
281
|
repairKind: 'hard-failure',
|
|
@@ -307,13 +300,6 @@ export function classifySandboxRecovery(snapshot) {
|
|
|
307
300
|
path: '/run/agent-infra/runtime'
|
|
308
301
|
});
|
|
309
302
|
}
|
|
310
|
-
for (const mountPath of snapshot.unexpectedCapabilityMounts ?? []) {
|
|
311
|
-
findings.push({
|
|
312
|
-
repairKind: 'hard-failure',
|
|
313
|
-
message: `Disabled Agent Client capability remains mounted at ${mountPath}.`,
|
|
314
|
-
path: mountPath
|
|
315
|
-
});
|
|
316
|
-
}
|
|
317
303
|
for (const mount of snapshot.mounts) {
|
|
318
304
|
if (snapshot.workspaceTopology === 'legacy-parent'
|
|
319
305
|
&& mount.path.startsWith('/workspace/.agents/workspace'))
|
|
@@ -679,11 +665,6 @@ export function collectSandboxRecoverySnapshot(params) {
|
|
|
679
665
|
mode: sandboxWorkspaceModeLabel(params.config),
|
|
680
666
|
taskId: sandboxTaskIdLabel(params.config)
|
|
681
667
|
});
|
|
682
|
-
const selectedToolIds = new Set(tools.map((tool) => tool.id));
|
|
683
|
-
const unexpectedCapabilityMounts = capabilityPlan.cleanupInventory
|
|
684
|
-
.filter((tool) => isAgentClientId(tool.id) && !selectedToolIds.has(tool.id))
|
|
685
|
-
.map((tool) => tool.containerMount)
|
|
686
|
-
.filter((mountPath) => mountsByDestination.has(mountPath));
|
|
687
668
|
const mountSnapshots = expectedMounts({
|
|
688
669
|
config: params.config,
|
|
689
670
|
branch: params.branch,
|
|
@@ -736,8 +717,6 @@ export function collectSandboxRecoverySnapshot(params) {
|
|
|
736
717
|
identityOk: typeof inspection.Id === 'string' && inspection.Id.length > 0
|
|
737
718
|
&& branchLabel === params.branch
|
|
738
719
|
&& sameSandboxWorkspaceIdentity(containerWorkspace, workspace),
|
|
739
|
-
runtimeCapabilityOk: labels[sandboxRuntimeCapabilityLabel(params.config)] === capabilityPlan.runtimeSignature,
|
|
740
|
-
unexpectedCapabilityMounts,
|
|
741
720
|
mounts: mountSnapshots,
|
|
742
721
|
tmpfs,
|
|
743
722
|
seeds,
|
|
@@ -187,6 +187,7 @@ export function parseCustomTool(entry, index, options, context = `customTools[${
|
|
|
187
187
|
if (!containerMount.startsWith('/')) {
|
|
188
188
|
throw new Error(`${context}: field "containerMount" must be an absolute path`);
|
|
189
189
|
}
|
|
190
|
+
const invoke = asOptionalNonEmptyString(entry.invoke, 'invoke', context);
|
|
190
191
|
const tool = {
|
|
191
192
|
id,
|
|
192
193
|
name: asOptionalNonEmptyString(entry.name, 'name', context) ?? id,
|
|
@@ -196,6 +197,7 @@ export function parseCustomTool(entry, index, options, context = `customTools[${
|
|
|
196
197
|
versionCmd: asOptionalNonEmptyString(entry.versionCmd, 'versionCmd', context) ?? `which ${id}`,
|
|
197
198
|
setupHint: asOptionalNonEmptyString(entry.setupHint, 'setupHint', context)
|
|
198
199
|
?? `Run \`${id}\` inside the container to set up.`,
|
|
200
|
+
...(invoke === undefined ? {} : { invoke }),
|
|
199
201
|
envVars: asStringRecord(entry.envVars, 'envVars', context),
|
|
200
202
|
hostPreSeedFiles: parseHostPreSeedFiles(entry.hostPreSeedFiles, context),
|
|
201
203
|
hostPreSeedDirs: parseHostPreSeedDirs(entry.hostPreSeedDirs, context),
|
|
@@ -619,9 +619,12 @@ function terminalResult(taskId, receipt, steps, changed, error = null) {
|
|
|
619
619
|
const blocked = [steps.backfill, steps.lifecycle, steps.taskComment, steps.verification, steps.summary].some((step) => step?.status === 'blocked');
|
|
620
620
|
const warnings = openWarnings(receipt);
|
|
621
621
|
const postLifecyclePending = receipt.lifecycle === 'done' && (pending.some((step) => step !== 'lifecycle') || receipt.warningProjection === 'pending');
|
|
622
|
-
const
|
|
622
|
+
const terminalVerificationPending = receipt.lifecycle === 'done' && receipt.verification === 'pending';
|
|
623
|
+
const resultError = error ?? (terminalVerificationPending ? steps.verification?.error : null) ?? null;
|
|
624
|
+
const hardError = error?.code.startsWith('FINALIZATION_') || error?.code === 'TASK_FINALIZATION_RECEIPT_INVALID'
|
|
625
|
+
|| terminalVerificationPending;
|
|
623
626
|
return {
|
|
624
|
-
status: hardError ? (
|
|
627
|
+
status: hardError ? (resultError?.retryable ? 'blocked' : 'failed') : pending.length === 0 ? 'completed' : postLifecyclePending ? 'completed' : blocked ? 'blocked' : 'failed',
|
|
625
628
|
changed,
|
|
626
629
|
taskId,
|
|
627
630
|
backfill: steps.backfill ?? null,
|
|
@@ -632,7 +635,7 @@ function terminalResult(taskId, receipt, steps, changed, error = null) {
|
|
|
632
635
|
completedSteps: completedSteps(receipt),
|
|
633
636
|
pendingSteps: pending,
|
|
634
637
|
result: hardError
|
|
635
|
-
? (
|
|
638
|
+
? (resultError?.retryable ? 'blocked' : 'failed')
|
|
636
639
|
: postLifecyclePending
|
|
637
640
|
? 'completed_with_warnings'
|
|
638
641
|
: pending.length === 0
|
|
@@ -641,7 +644,7 @@ function terminalResult(taskId, receipt, steps, changed, error = null) {
|
|
|
641
644
|
? 'blocked'
|
|
642
645
|
: 'failed',
|
|
643
646
|
warnings,
|
|
644
|
-
error: hardError ?
|
|
647
|
+
error: hardError ? resultError : postLifecyclePending ? null : resultError
|
|
645
648
|
};
|
|
646
649
|
}
|
|
647
650
|
function preparedResult(taskId, receipt, steps, changed) {
|
|
@@ -663,7 +666,6 @@ async function prepareUnderLock(request, taskId, options) {
|
|
|
663
666
|
const commentSync = options.commentSync ?? syncPlatformComment;
|
|
664
667
|
const issueSync = options.issueSync ?? syncPlatformIssue;
|
|
665
668
|
const issueInspect = options.issueInspect ?? inspectPlatformIssue;
|
|
666
|
-
const verify = options.verify ?? verifyTaskEvent;
|
|
667
669
|
const consumedCapabilities = new Set();
|
|
668
670
|
let receipt;
|
|
669
671
|
try {
|
|
@@ -732,7 +734,8 @@ async function prepareUnderLock(request, taskId, options) {
|
|
|
732
734
|
}
|
|
733
735
|
const issueOperations = synced.operations;
|
|
734
736
|
const badOperation = issueOperations?.find((operation) => ['requirements', 'issue-type', 'fields', ...(base ? ['labels:in'] : [])].includes(operation.name)
|
|
735
|
-
&& operation.status !== 'applied' && operation.status !== 'no-op'
|
|
737
|
+
&& operation.status !== 'applied' && operation.status !== 'no-op'
|
|
738
|
+
&& !(operation.name === 'requirements' && operation.status === 'skipped' && operation.reasonCode === 'NO_REQUIREMENTS_ANCHOR'));
|
|
736
739
|
if ((synced.status !== 'applied' && synced.status !== 'no-op') || synced.error || badOperation) {
|
|
737
740
|
const error = synced.error ?? {
|
|
738
741
|
code: badOperation?.reasonCode || 'FINALIZATION_ISSUE_METADATA_UNCONFIRMED',
|
|
@@ -907,88 +910,9 @@ async function prepareUnderLock(request, taskId, options) {
|
|
|
907
910
|
metadataProvider: () => ({ timestamp: projection.timestamp, agentInfraVersion: projection.agentInfraVersion })
|
|
908
911
|
})
|
|
909
912
|
: undefined;
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
}
|
|
914
|
-
else {
|
|
915
|
-
try {
|
|
916
|
-
receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
|
|
917
|
-
const verificationState = resolveTaskRef(taskId, { repoRoot });
|
|
918
|
-
const verificationEvent = verificationState.ok && verificationState.state === 'completed'
|
|
919
|
-
? 'complete-task.completed'
|
|
920
|
-
: 'complete-task.prepared';
|
|
921
|
-
const result = await verify({ taskRef: taskId, event: verificationEvent }, { repoRoot });
|
|
922
|
-
verification = verificationStep(result);
|
|
923
|
-
if (result.status === 'pass') {
|
|
924
|
-
const resolvedTaskWarning = receipt.warnings.some((warning) => warning.step === 'verification' && warning.status === 'open');
|
|
925
|
-
const warnings = applyVerificationWarnings(receipt, result);
|
|
926
|
-
receipt = updateReceipt(repoRoot, receipt, {
|
|
927
|
-
verification: 'done', taskComment: resolvedTaskWarning ? 'pending' : receipt.taskComment,
|
|
928
|
-
warningProjection: warnings.length > 0 ? 'pending' : 'done', warnings, lastError: null
|
|
929
|
-
});
|
|
930
|
-
receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
|
|
931
|
-
const finalComment = await syncPendingTaskComment({
|
|
932
|
-
repoRoot, taskId, agent: request.agent, receipt, projection: currentProjection(), refreshProjection: currentProjection, commentSync, consumedCapabilities
|
|
933
|
-
});
|
|
934
|
-
receipt = finalComment.receipt;
|
|
935
|
-
if (finalComment.step.status !== 'no-op')
|
|
936
|
-
taskComment = finalComment.step;
|
|
937
|
-
changed = changed || finalComment.changed;
|
|
938
|
-
if (finalComment.error) {
|
|
939
|
-
return terminalResult(taskId, receipt, { backfill: backfillResult, lifecycle: lifecycleResult, taskComment, verification }, changed, finalComment.error);
|
|
940
|
-
}
|
|
941
|
-
}
|
|
942
|
-
else {
|
|
943
|
-
const detail = verification.error ?? { code: 'VERIFY_FAILED', message: 'verification failed', retryable: true };
|
|
944
|
-
const warnings = applyVerificationWarnings(receipt, result);
|
|
945
|
-
receipt = updateReceipt(repoRoot, receipt, {
|
|
946
|
-
verification: 'pending', taskComment: 'pending', warningProjection: 'pending', warnings, lastError: detail
|
|
947
|
-
});
|
|
948
|
-
receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
|
|
949
|
-
const warningComment = await syncPendingTaskComment({
|
|
950
|
-
repoRoot, taskId, agent: request.agent, receipt, projection: currentProjection(), refreshProjection: currentProjection, commentSync, consumedCapabilities
|
|
951
|
-
});
|
|
952
|
-
receipt = warningComment.receipt;
|
|
953
|
-
taskComment = warningComment.step;
|
|
954
|
-
changed = changed || warningComment.changed;
|
|
955
|
-
if (warningComment.error) {
|
|
956
|
-
return terminalResult(taskId, receipt, { backfill: backfillResult, lifecycle: lifecycleResult, taskComment, verification }, changed, warningComment.error);
|
|
957
|
-
}
|
|
958
|
-
return terminalResult(taskId, receipt, { backfill: backfillResult, lifecycle: lifecycleResult, taskComment, verification }, changed, detail);
|
|
959
|
-
}
|
|
960
|
-
}
|
|
961
|
-
catch (error) {
|
|
962
|
-
const detail = errorOf(error, 'VERIFY_FAILED', true);
|
|
963
|
-
try {
|
|
964
|
-
const warnings = replaceWarning(receipt, warningFromError('verification', detail), 'open');
|
|
965
|
-
receipt = updateReceipt(repoRoot, receipt, {
|
|
966
|
-
verification: 'pending', taskComment: 'pending', warningProjection: 'pending', warnings, lastError: detail
|
|
967
|
-
});
|
|
968
|
-
receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
|
|
969
|
-
}
|
|
970
|
-
catch (writeError) {
|
|
971
|
-
const persistence = errorOf(writeError, 'FINALIZATION_RECEIPT_WRITE_FAILED', true);
|
|
972
|
-
const hardError = {
|
|
973
|
-
code: 'FINALIZATION_RECEIPT_WRITE_FAILED',
|
|
974
|
-
message: `Unable to persist verification failure (${detail.code}: ${detail.message}): ${persistence.message}`,
|
|
975
|
-
retryable: true
|
|
976
|
-
};
|
|
977
|
-
return terminalResult(taskId, receipt, {
|
|
978
|
-
backfill: backfillResult, lifecycle: lifecycleResult, taskComment, verification: { status: 'blocked', changed: false, error: detail }
|
|
979
|
-
}, changed, hardError);
|
|
980
|
-
}
|
|
981
|
-
try {
|
|
982
|
-
const warningComment = await syncPendingTaskComment({ repoRoot, taskId, agent: request.agent, receipt, projection: currentProjection(), refreshProjection: currentProjection, commentSync, consumedCapabilities });
|
|
983
|
-
receipt = warningComment.receipt;
|
|
984
|
-
if (warningComment.step.status !== 'no-op')
|
|
985
|
-
taskComment = warningComment.step;
|
|
986
|
-
changed = changed || warningComment.changed;
|
|
987
|
-
}
|
|
988
|
-
catch { /* preserve the primary error */ }
|
|
989
|
-
return terminalResult(taskId, receipt, { backfill: backfillResult, lifecycle: lifecycleResult, taskComment, verification: { status: 'blocked', changed: false, error: detail } }, changed, detail);
|
|
990
|
-
}
|
|
991
|
-
}
|
|
913
|
+
const verification = receipt.verification === 'done'
|
|
914
|
+
? { status: 'no-op', changed: false, error: null }
|
|
915
|
+
: { status: 'skipped', changed: false, error: null };
|
|
992
916
|
let summary = receipt.summary === 'pending'
|
|
993
917
|
? await syncPendingSummary({ repoRoot, taskId, agent: request.agent, receipt, commentSync })
|
|
994
918
|
: { receipt, step: { status: 'no-op', changed: false, error: null }, changed: false, error: null };
|
|
@@ -1039,7 +963,7 @@ async function commitPreparedTaskFinalization(request, options) {
|
|
|
1039
963
|
if (!resolved.ok)
|
|
1040
964
|
return failed(resolved.taskId, { code: resolved.code, message: resolved.message, retryable: false });
|
|
1041
965
|
try {
|
|
1042
|
-
return await withTaskExecutionLock(repoRoot, resolved.taskId, 'task-finalization.commit', () => {
|
|
966
|
+
return await withTaskExecutionLock(repoRoot, resolved.taskId, 'task-finalization.commit', async () => {
|
|
1043
967
|
if (options.controlBinding && options.manifestPath) {
|
|
1044
968
|
try {
|
|
1045
969
|
const manifest = readSandboxControlManifest(options.manifestPath);
|
|
@@ -1091,7 +1015,7 @@ async function commitPreparedTaskFinalization(request, options) {
|
|
|
1091
1015
|
return failed(resolved.taskId, {
|
|
1092
1016
|
code: 'TASK_FINALIZATION_PREPARATION_REQUIRED', message: 'sandbox preparation receipt is unavailable', retryable: true
|
|
1093
1017
|
});
|
|
1094
|
-
if (receipt.lifecycle === 'done' && resolved.state === 'completed') {
|
|
1018
|
+
if (receipt.lifecycle === 'done' && receipt.verification !== 'pending' && resolved.state === 'completed') {
|
|
1095
1019
|
return terminalResult(resolved.taskId, receipt, { lifecycle: { status: 'no-op', changed: false, error: null } }, false);
|
|
1096
1020
|
}
|
|
1097
1021
|
if (options.controlBinding) {
|
|
@@ -1130,7 +1054,7 @@ async function commitPreparedTaskFinalization(request, options) {
|
|
|
1130
1054
|
}
|
|
1131
1055
|
}
|
|
1132
1056
|
}
|
|
1133
|
-
if (receipt.taskComment === 'pending' || receipt.
|
|
1057
|
+
if (receipt.taskComment === 'pending' || receipt.summary === 'pending' || receipt.warningProjection === 'pending') {
|
|
1134
1058
|
return failed(resolved.taskId, {
|
|
1135
1059
|
code: 'TASK_FINALIZATION_PREPARATION_REQUIRED', message: 'sandbox preparation has pending external steps', retryable: true
|
|
1136
1060
|
}, { completedSteps: completedSteps(receipt), pendingSteps: pendingSteps(receipt), warnings: openWarnings(receipt) });
|
|
@@ -1142,6 +1066,7 @@ async function commitPreparedTaskFinalization(request, options) {
|
|
|
1142
1066
|
message: `cannot verify canonical short-id registry: ${registry.error.code}: ${registry.error.message}`,
|
|
1143
1067
|
retryable: false
|
|
1144
1068
|
});
|
|
1069
|
+
receipt = updateReceipt(repoRoot, receipt, { verification: 'pending' });
|
|
1145
1070
|
const lifecycle = options.lifecycle ?? applyTaskLifecycle;
|
|
1146
1071
|
const result = lifecycle({ taskRef: resolved.taskId, intent: 'complete', agent: request.agent }, { repoRoot, ...(options.metadataProvider ? { metadataProvider: options.metadataProvider } : {}) });
|
|
1147
1072
|
const step = lifecycleStep(result);
|
|
@@ -1149,8 +1074,80 @@ async function commitPreparedTaskFinalization(request, options) {
|
|
|
1149
1074
|
const updated = updateReceipt(repoRoot, receipt, { lifecycle: 'pending', lastError: step.error });
|
|
1150
1075
|
return terminalResult(resolved.taskId, updated, { lifecycle: step }, result.changed, step.error);
|
|
1151
1076
|
}
|
|
1152
|
-
|
|
1153
|
-
|
|
1077
|
+
let verificationResult;
|
|
1078
|
+
try {
|
|
1079
|
+
verificationResult = await (options.verify ?? verifyTaskEvent)({ taskRef: resolved.taskId, event: 'complete-task.completed' }, { repoRoot });
|
|
1080
|
+
}
|
|
1081
|
+
catch (error) {
|
|
1082
|
+
const detail = errorOf(error, 'VERIFY_FAILED', true);
|
|
1083
|
+
const warnings = replaceWarning(receipt, warningFromError('verification', detail), 'open');
|
|
1084
|
+
let updated;
|
|
1085
|
+
try {
|
|
1086
|
+
updated = updateReceipt(repoRoot, receipt, {
|
|
1087
|
+
lifecycle: 'done', verification: 'pending', taskComment: 'pending', warningProjection: 'pending',
|
|
1088
|
+
warnings, lastError: detail
|
|
1089
|
+
});
|
|
1090
|
+
}
|
|
1091
|
+
catch (persistCause) {
|
|
1092
|
+
const persistError = errorOf(persistCause, 'FINALIZATION_RECEIPT_WRITE_FAILED', true);
|
|
1093
|
+
persistError.code = 'FINALIZATION_RECEIPT_WRITE_FAILED';
|
|
1094
|
+
persistError.message = `${persistError.message}; verification also failed: ${detail.code}: ${detail.message}`;
|
|
1095
|
+
return failed(resolved.taskId, persistError, {
|
|
1096
|
+
changed: result.changed,
|
|
1097
|
+
lifecycle: step,
|
|
1098
|
+
verification: { status: 'blocked', changed: false, error: persistError },
|
|
1099
|
+
completedSteps: completedSteps(receipt), pendingSteps: pendingSteps(receipt), warnings: openWarnings(receipt)
|
|
1100
|
+
});
|
|
1101
|
+
}
|
|
1102
|
+
const verification = { status: detail.retryable ? 'blocked' : 'failed', changed: false, error: detail };
|
|
1103
|
+
return terminalResult(resolved.taskId, updated, { lifecycle: step, verification }, result.changed, detail);
|
|
1104
|
+
}
|
|
1105
|
+
const verification = verificationStep(verificationResult);
|
|
1106
|
+
if (verificationResult.status === 'pass') {
|
|
1107
|
+
const resolvedVerificationWarning = receipt.warnings.some((warning) => warning.step === 'verification' && warning.status === 'open');
|
|
1108
|
+
const warnings = applyVerificationWarnings(receipt, verificationResult);
|
|
1109
|
+
const hasOpenWarnings = warnings.some((warning) => warning.status === 'open');
|
|
1110
|
+
let updated;
|
|
1111
|
+
try {
|
|
1112
|
+
updated = updateReceipt(repoRoot, receipt, {
|
|
1113
|
+
lifecycle: 'done', verification: 'done',
|
|
1114
|
+
taskComment: resolvedVerificationWarning ? 'pending' : receipt.taskComment,
|
|
1115
|
+
warningProjection: hasOpenWarnings ? 'pending' : 'done', warnings, lastError: null
|
|
1116
|
+
});
|
|
1117
|
+
}
|
|
1118
|
+
catch (persistCause) {
|
|
1119
|
+
const persistError = errorOf(persistCause, 'FINALIZATION_RECEIPT_WRITE_FAILED', true);
|
|
1120
|
+
persistError.code = 'FINALIZATION_RECEIPT_WRITE_FAILED';
|
|
1121
|
+
return failed(resolved.taskId, persistError, {
|
|
1122
|
+
changed: result.changed,
|
|
1123
|
+
lifecycle: step,
|
|
1124
|
+
verification,
|
|
1125
|
+
completedSteps: completedSteps(receipt), pendingSteps: pendingSteps(receipt), warnings: openWarnings(receipt)
|
|
1126
|
+
});
|
|
1127
|
+
}
|
|
1128
|
+
return terminalResult(resolved.taskId, updated, { lifecycle: step, verification }, result.changed);
|
|
1129
|
+
}
|
|
1130
|
+
const detail = verification.error ?? { code: 'VERIFY_FAILED', message: 'verification failed', retryable: true };
|
|
1131
|
+
const warnings = applyVerificationWarnings(receipt, verificationResult);
|
|
1132
|
+
let updated;
|
|
1133
|
+
try {
|
|
1134
|
+
updated = updateReceipt(repoRoot, receipt, {
|
|
1135
|
+
lifecycle: 'done', verification: 'pending', taskComment: 'pending', warningProjection: 'pending',
|
|
1136
|
+
warnings, lastError: detail
|
|
1137
|
+
});
|
|
1138
|
+
}
|
|
1139
|
+
catch (persistCause) {
|
|
1140
|
+
const persistError = errorOf(persistCause, 'FINALIZATION_RECEIPT_WRITE_FAILED', true);
|
|
1141
|
+
persistError.code = 'FINALIZATION_RECEIPT_WRITE_FAILED';
|
|
1142
|
+
persistError.message = `${persistError.message}; verification also failed: ${detail.code}: ${detail.message}`;
|
|
1143
|
+
return failed(resolved.taskId, persistError, {
|
|
1144
|
+
changed: result.changed,
|
|
1145
|
+
lifecycle: step,
|
|
1146
|
+
verification: { status: 'blocked', changed: false, error: persistError },
|
|
1147
|
+
completedSteps: completedSteps(receipt), pendingSteps: pendingSteps(receipt), warnings: openWarnings(receipt)
|
|
1148
|
+
});
|
|
1149
|
+
}
|
|
1150
|
+
return terminalResult(resolved.taskId, updated, { lifecycle: step, verification }, result.changed, detail);
|
|
1154
1151
|
});
|
|
1155
1152
|
}
|
|
1156
1153
|
catch (error) {
|
|
@@ -22,7 +22,6 @@ const VERIFICATION_CATALOG = {
|
|
|
22
22
|
'commit.completed': gate('commit', 'active'),
|
|
23
23
|
'complete-task.preflight': { skill: 'complete-task', expectedState: 'active', mode: 'checks', checks: ['required-pr-delivery'] },
|
|
24
24
|
'complete-task.hard-preflight': { skill: 'complete-task', expectedState: 'active', mode: 'checks', checks: ['required-pr-delivery'] },
|
|
25
|
-
'complete-task.prepared': gate('complete-task', 'active'),
|
|
26
25
|
'complete-task.completed': gate('complete-task', 'completed'),
|
|
27
26
|
'create-pr.completed': gate('create-pr', 'active'),
|
|
28
27
|
'create-task.completed': gate('create-task', 'active'),
|
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
|
}
|