@fitlab-ai/agent-infra 0.9.1 → 0.9.2
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 +16 -4
- package/README.zh-CN.md +16 -4
- package/bin/cli.ts +12 -0
- package/dist/bin/cli.js +14 -0
- package/dist/bin/internal-cli.js +15 -0
- package/dist/lib/agent-client.js +163 -0
- package/dist/lib/agent-clients/adapter.js +294 -0
- package/dist/lib/agent-clients/adapters/claude-code.js +62 -0
- package/dist/lib/agent-clients/adapters/codex-sandbox.js +210 -0
- package/dist/lib/agent-clients/adapters/codex.js +54 -0
- package/dist/lib/agent-clients/adapters/gemini-cli.js +64 -0
- package/dist/lib/agent-clients/adapters/index.js +20 -0
- package/dist/lib/agent-clients/adapters/opencode.js +64 -0
- package/dist/lib/agent-clients/config.js +177 -0
- package/dist/lib/agent-clients/custom-tuis.js +99 -0
- package/dist/lib/agent-clients/invocation.js +9 -0
- package/dist/lib/agent-clients/next-steps.js +38 -0
- package/dist/lib/agent-clients/project-assets.js +45 -0
- package/dist/lib/agent-clients/reconcile.js +235 -0
- package/dist/lib/agent-clients/registry.js +72 -0
- package/dist/lib/agent-clients/schema.js +57 -0
- package/dist/lib/agent-clients/types.js +29 -0
- package/dist/lib/builtin-tuis.js +9 -14
- package/dist/lib/defaults.json +85 -73
- package/dist/lib/git/review-snapshot.js +25 -10
- package/dist/lib/git/reviewed-commit-equivalence.js +110 -0
- package/dist/lib/init.js +47 -91
- package/dist/lib/internal/agent-client.js +117 -0
- package/dist/lib/internal/git-workflow.js +1 -1
- package/dist/lib/internal/release-workflow.js +71 -16
- package/dist/lib/internal/task-orchestration.js +151 -0
- package/dist/lib/internal/task-review.js +65 -0
- package/dist/lib/merge.js +2 -1
- package/dist/lib/platform/adapters.js +38 -1
- package/dist/lib/platform/change-request-git-evidence.js +113 -0
- package/dist/lib/platform/merged-pr-equivalence.js +41 -11
- package/dist/lib/platform/pr-checks.js +38 -10
- package/dist/lib/platform/pull-requests.js +110 -6
- package/dist/lib/platform/releases.js +1 -1
- package/dist/lib/platform/verification-required.js +8 -10
- package/dist/lib/prompt.js +15 -3
- package/dist/lib/release/channels.js +4 -1
- package/dist/lib/release/workflow.js +4 -2
- package/dist/lib/remove-dir.js +25 -0
- package/dist/lib/run/tui.js +8 -2
- package/dist/lib/sandbox/agent-client-reconciler.js +234 -0
- package/dist/lib/sandbox/clipboard/linux.js +2 -1
- package/dist/lib/sandbox/commands/create.js +76 -180
- package/dist/lib/sandbox/commands/enter.js +17 -1
- package/dist/lib/sandbox/commands/prune.js +2 -2
- package/dist/lib/sandbox/commands/rm.js +3 -2
- package/dist/lib/sandbox/config.js +21 -4
- package/dist/lib/sandbox/constants.js +3 -0
- package/dist/lib/sandbox/dockerfile.js +2 -1
- package/dist/lib/sandbox/managed-fs.js +10 -5
- package/dist/lib/sandbox/recovery.js +52 -36
- package/dist/lib/sandbox/shell.js +1 -1
- package/dist/lib/sandbox/tool-types.js +8 -0
- package/dist/lib/sandbox/tools.js +2 -126
- package/dist/lib/task/artifact-lifecycle.js +1 -49
- package/dist/lib/task/commands/decisions.js +50 -20
- package/dist/lib/task/delegation-receipts.js +107 -0
- package/dist/lib/task/events.js +100 -1
- package/dist/lib/task/orchestration.js +420 -0
- package/dist/lib/task/review-artifacts.js +143 -59
- package/dist/lib/task/review-finalization.js +166 -0
- package/dist/lib/task/review-fingerprint.js +4 -0
- package/dist/lib/task/verification-checks.js +2 -1
- package/dist/lib/task/verification-engine.js +156 -28
- package/dist/lib/task/verification-review.js +1 -1
- package/dist/lib/task/verification.js +3 -1
- package/dist/lib/task/workspace-snapshot.js +34 -0
- package/dist/lib/update.js +42 -161
- package/lib/agent-client.ts +214 -0
- package/lib/agent-clients/adapter.ts +461 -0
- package/lib/agent-clients/adapters/claude-code.ts +70 -0
- package/lib/agent-clients/adapters/codex-sandbox.ts +245 -0
- package/lib/agent-clients/adapters/codex.ts +58 -0
- package/lib/agent-clients/adapters/gemini-cli.ts +65 -0
- package/lib/agent-clients/adapters/index.ts +27 -0
- package/lib/agent-clients/adapters/opencode.ts +66 -0
- package/lib/agent-clients/config.ts +278 -0
- package/lib/agent-clients/custom-tuis.ts +149 -0
- package/lib/agent-clients/invocation.ts +19 -0
- package/lib/agent-clients/next-steps.ts +72 -0
- package/lib/agent-clients/project-assets.ts +96 -0
- package/lib/agent-clients/reconcile.ts +324 -0
- package/lib/agent-clients/registry.ts +144 -0
- package/lib/agent-clients/schema.ts +62 -0
- package/lib/agent-clients/types.ts +78 -0
- package/lib/benchmark/schemas/v1.0.0/case-manifest.schema.json +120 -0
- package/lib/benchmark/schemas/v1.0.0/common.schema.json +109 -0
- package/lib/benchmark/schemas/v1.0.0/grader-result.schema.json +160 -0
- package/lib/benchmark/schemas/v1.0.0/run-manifest.schema.json +122 -0
- package/lib/benchmark/schemas/v1.0.0/subject.schema.json +227 -0
- package/lib/builtin-tuis.ts +16 -15
- package/lib/defaults.json +24 -12
- package/lib/git/review-snapshot.ts +39 -9
- package/lib/git/reviewed-commit-equivalence.ts +135 -0
- package/lib/init.ts +53 -121
- package/lib/internal/agent-client.ts +131 -0
- package/lib/internal/git-workflow.ts +1 -1
- package/lib/internal/release-workflow.ts +68 -16
- package/lib/internal/task-orchestration.ts +132 -0
- package/lib/internal/task-review.ts +65 -0
- package/lib/merge.ts +2 -1
- package/lib/platform/adapters.ts +142 -3
- package/lib/platform/change-request-git-evidence.ts +159 -0
- package/lib/platform/merged-pr-equivalence.ts +51 -14
- package/lib/platform/pr-checks.ts +50 -22
- package/lib/platform/pull-requests.ts +134 -25
- package/lib/platform/releases.ts +1 -1
- package/lib/platform/verification-required.ts +12 -10
- package/lib/prompt.ts +16 -3
- package/lib/release/channels.ts +4 -1
- package/lib/release/workflow.ts +4 -3
- package/lib/remove-dir.ts +28 -0
- package/lib/run/tui.ts +9 -1
- package/lib/sandbox/agent-client-reconciler.ts +378 -0
- package/lib/sandbox/clipboard/linux.ts +2 -1
- package/lib/sandbox/commands/create.ts +111 -192
- package/lib/sandbox/commands/enter.ts +23 -1
- package/lib/sandbox/commands/prune.ts +2 -2
- package/lib/sandbox/commands/rm.ts +3 -2
- package/lib/sandbox/config.ts +31 -4
- package/lib/sandbox/constants.ts +6 -0
- package/lib/sandbox/dockerfile.ts +2 -1
- package/lib/sandbox/managed-fs.ts +18 -5
- package/lib/sandbox/recovery.ts +98 -67
- package/lib/sandbox/shell.ts +1 -1
- package/lib/sandbox/tool-types.ts +143 -0
- package/lib/sandbox/tools.ts +5 -157
- package/lib/task/artifact-lifecycle.ts +1 -38
- package/lib/task/commands/decisions.ts +53 -20
- package/lib/task/delegation-receipts.ts +170 -0
- package/lib/task/events.ts +110 -2
- package/lib/task/orchestration.ts +543 -0
- package/lib/task/review-artifacts.ts +201 -69
- package/lib/task/review-finalization.ts +275 -0
- package/lib/task/review-fingerprint.ts +5 -0
- package/lib/task/verification-checks.ts +2 -1
- package/lib/task/verification-engine.ts +177 -28
- package/lib/task/verification-review.ts +1 -0
- package/lib/task/verification.ts +5 -2
- package/lib/task/workspace-snapshot.ts +36 -0
- package/lib/update.ts +48 -184
- package/package.json +2 -2
- package/templates/.agents/README.en.md +21 -20
- package/templates/.agents/README.zh-CN.md +21 -20
- package/templates/.agents/hooks/lifecycle-delegation.js +64 -0
- package/templates/.agents/rules/README.en.md +2 -0
- package/templates/.agents/rules/README.zh-CN.md +2 -0
- package/templates/.agents/rules/commit-and-pr.en.md +1 -0
- package/templates/.agents/rules/commit-and-pr.zh-CN.md +1 -0
- package/templates/.agents/rules/human-decision-context.en.md +14 -12
- package/templates/.agents/rules/human-decision-context.zh-CN.md +14 -12
- package/templates/.agents/rules/lifecycle-orchestration.en.md +21 -0
- package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +21 -0
- package/templates/.agents/rules/next-step-output.en.md +16 -0
- package/templates/.agents/rules/next-step-output.zh-CN.md +16 -0
- package/templates/.agents/rules/no-mid-flow-questions.en.md +2 -0
- package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +2 -0
- package/templates/.agents/rules/release-commands.github.en.md +11 -5
- package/templates/.agents/rules/release-commands.github.zh-CN.md +11 -5
- package/templates/.agents/rules/review-handshake.en.md +4 -4
- package/templates/.agents/rules/review-handshake.zh-CN.md +4 -4
- package/templates/.agents/rules/review-method.en.md +113 -0
- package/templates/.agents/rules/review-method.zh-CN.md +98 -0
- package/templates/.agents/skills/analyze-task/SKILL.en.md +5 -5
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/block-task/SKILL.en.md +4 -4
- package/templates/.agents/skills/block-task/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/cancel-task/SKILL.en.md +4 -4
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/check-task/SKILL.en.md +16 -16
- package/templates/.agents/skills/check-task/SKILL.zh-CN.md +16 -16
- package/templates/.agents/skills/close-codescan/SKILL.en.md +4 -4
- package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/close-dependabot/SKILL.en.md +4 -4
- package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/code-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/code-task/reference/fix-mode.en.md +14 -16
- package/templates/.agents/skills/code-task/reference/fix-mode.zh-CN.md +14 -16
- package/templates/.agents/skills/code-task/reference/output-template.en.md +3 -3
- package/templates/.agents/skills/code-task/reference/output-template.zh-CN.md +3 -3
- package/templates/.agents/skills/commit/SKILL.en.md +11 -1
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +11 -1
- package/templates/.agents/skills/commit/reference/task-status-update.en.md +24 -28
- package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +24 -28
- package/templates/.agents/skills/complete-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/create-pr/SKILL.en.md +10 -10
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +10 -10
- package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +3 -3
- package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +3 -3
- package/templates/.agents/skills/create-task/SKILL.en.md +11 -11
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +11 -11
- package/templates/.agents/skills/import-codescan/SKILL.en.md +5 -5
- package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/import-dependabot/SKILL.en.md +5 -5
- package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/import-issue/SKILL.en.md +5 -5
- package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/init-labels/SKILL.en.md +4 -4
- package/templates/.agents/skills/init-labels/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/init-milestones/SKILL.en.md +4 -4
- package/templates/.agents/skills/init-milestones/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/plan-task/SKILL.en.md +5 -5
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/refine-title/SKILL.en.md +1 -1
- package/templates/.agents/skills/refine-title/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/release/SKILL.en.md +1 -1
- package/templates/.agents/skills/review-analysis/SKILL.en.md +13 -2
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +14 -3
- package/templates/.agents/skills/review-analysis/config/verify.en.json +6 -0
- package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +6 -0
- package/templates/.agents/skills/review-analysis/reference/output-templates.en.md +13 -15
- package/templates/.agents/skills/review-analysis/reference/output-templates.zh-CN.md +13 -15
- package/templates/.agents/skills/review-analysis/reference/report-template.en.md +51 -5
- package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +52 -6
- package/templates/.agents/skills/review-analysis/reference/review-criteria.en.md +25 -22
- package/templates/.agents/skills/review-analysis/reference/review-criteria.zh-CN.md +25 -22
- package/templates/.agents/skills/review-code/SKILL.en.md +28 -8
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +28 -8
- package/templates/.agents/skills/review-code/config/verify.en.json +7 -0
- package/templates/.agents/skills/review-code/config/verify.zh-CN.json +7 -0
- package/templates/.agents/skills/review-code/reference/concurrency-risks.en.md +25 -0
- package/templates/.agents/skills/review-code/reference/concurrency-risks.zh-CN.md +25 -0
- package/templates/.agents/skills/review-code/reference/cross-platform-risks.en.md +27 -0
- package/templates/.agents/skills/review-code/reference/cross-platform-risks.zh-CN.md +27 -0
- package/templates/.agents/skills/review-code/reference/documentation-antipatterns.en.md +13 -0
- package/templates/.agents/skills/review-code/reference/documentation-antipatterns.zh-CN.md +13 -0
- package/templates/.agents/skills/review-code/reference/migration-risks.en.md +25 -0
- package/templates/.agents/skills/review-code/reference/migration-risks.zh-CN.md +25 -0
- package/templates/.agents/skills/review-code/reference/output-templates.en.md +22 -24
- package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +22 -24
- package/templates/.agents/skills/review-code/reference/report-template.en.md +54 -7
- package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +54 -7
- package/templates/.agents/skills/review-code/reference/review-criteria.en.md +29 -24
- package/templates/.agents/skills/review-code/reference/review-criteria.zh-CN.md +29 -24
- package/templates/.agents/skills/review-code/reference/security-risks.en.md +25 -0
- package/templates/.agents/skills/review-code/reference/security-risks.zh-CN.md +25 -0
- package/templates/.agents/skills/review-plan/SKILL.en.md +13 -2
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +14 -3
- package/templates/.agents/skills/review-plan/config/verify.en.json +6 -0
- package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +6 -0
- package/templates/.agents/skills/review-plan/reference/output-templates.en.md +13 -15
- package/templates/.agents/skills/review-plan/reference/output-templates.zh-CN.md +13 -15
- package/templates/.agents/skills/review-plan/reference/report-template.en.md +61 -5
- package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +61 -5
- package/templates/.agents/skills/review-plan/reference/review-criteria.en.md +21 -24
- package/templates/.agents/skills/review-plan/reference/review-criteria.zh-CN.md +21 -24
- package/templates/.agents/skills/run-task/SKILL.en.md +22 -0
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +22 -0
- package/templates/.agents/skills/run-task/config/verify.en.json +9 -0
- package/templates/.agents/skills/run-task/config/verify.zh-CN.json +9 -0
- package/templates/.agents/skills/test/SKILL.en.md +4 -4
- package/templates/.agents/skills/test/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/test-integration/SKILL.en.md +4 -4
- package/templates/.agents/skills/test-integration/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/update-agent-infra/SKILL.en.md +5 -5
- package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +647 -203
- package/templates/.agents/skills/upgrade-dependency/SKILL.en.md +4 -4
- package/templates/.agents/skills/upgrade-dependency/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/watch-pr/SKILL.en.md +8 -8
- package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +8 -8
- package/templates/.claude/agents/agent-infra-lifecycle-executor.en.md +6 -0
- package/templates/.claude/agents/agent-infra-lifecycle-executor.zh-CN.md +6 -0
- package/templates/.claude/agents/agent-infra-lifecycle-reviewer.en.md +6 -0
- package/templates/.claude/agents/agent-infra-lifecycle-reviewer.zh-CN.md +6 -0
- package/templates/.claude/commands/run-task.en.md +8 -0
- package/templates/.claude/commands/run-task.zh-CN.md +8 -0
- package/templates/.claude/settings.json +24 -0
- package/templates/.codex/agents/agent-infra-lifecycle-executor.en.toml +3 -0
- package/templates/.codex/agents/agent-infra-lifecycle-executor.zh-CN.toml +3 -0
- package/templates/.codex/agents/agent-infra-lifecycle-reviewer.en.toml +3 -0
- package/templates/.codex/agents/agent-infra-lifecycle-reviewer.zh-CN.toml +3 -0
- package/templates/.gemini/commands/_project_/run-task.en.toml +8 -0
- package/templates/.gemini/commands/_project_/run-task.zh-CN.toml +8 -0
- package/templates/.opencode/commands/run-task.en.md +11 -0
- package/templates/.opencode/commands/run-task.zh-CN.md +11 -0
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { containerNameCandidates, sandboxBranchLabel, sandboxLabel } from './constants.js';
|
|
3
|
+
import { containerNameCandidates, sandboxBranchLabel, sandboxLabel, sandboxRuntimeCapabilityLabel } from './constants.js';
|
|
4
|
+
import { isAgentClientId } from '../agent-clients/types.js';
|
|
5
|
+
import { createSandboxCapabilityPlan } from './agent-client-reconciler.js';
|
|
4
6
|
import { toEnginePath } from './engines/wsl2-paths.js';
|
|
5
7
|
import { sandboxCoreBindMounts } from './mounts.js';
|
|
6
8
|
import { runEngine, runOkEngine, runVerboseEngine } from './shell.js';
|
|
@@ -17,6 +19,19 @@ export function classifySandboxRecovery(snapshot) {
|
|
|
17
19
|
message: 'Container identity does not match the requested sandbox branch.'
|
|
18
20
|
});
|
|
19
21
|
}
|
|
22
|
+
if (snapshot.runtimeCapabilityOk === false) {
|
|
23
|
+
findings.push({
|
|
24
|
+
repairKind: 'hard-failure',
|
|
25
|
+
message: 'Container runtime capability signature does not match the current sandbox plan.'
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
for (const mountPath of snapshot.unexpectedCapabilityMounts ?? []) {
|
|
29
|
+
findings.push({
|
|
30
|
+
repairKind: 'hard-failure',
|
|
31
|
+
message: `Disabled Agent Client capability remains mounted at ${mountPath}.`,
|
|
32
|
+
path: mountPath
|
|
33
|
+
});
|
|
34
|
+
}
|
|
20
35
|
for (const mount of snapshot.mounts) {
|
|
21
36
|
if (mount.actualType !== mount.expectedType
|
|
22
37
|
|| !mount.sourceMatches
|
|
@@ -74,25 +89,11 @@ export function classifySandboxRecovery(snapshot) {
|
|
|
74
89
|
path: '/home/devuser/.bash_aliases'
|
|
75
90
|
});
|
|
76
91
|
}
|
|
77
|
-
|
|
78
|
-
if (!
|
|
79
|
-
findings.push({
|
|
80
|
-
repairKind: 'hard-failure',
|
|
81
|
-
message: 'Codex is not available on PATH inside the sandbox.'
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
if (!snapshot.codex.stateWritable) {
|
|
92
|
+
for (const check of snapshot.agentClientChecks) {
|
|
93
|
+
if (check.applicable && !check.healthy) {
|
|
85
94
|
findings.push({
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
path: '/home/devuser/.codex'
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
if (snapshot.codex.promptsSourceExists && !snapshot.codex.promptsValid) {
|
|
92
|
-
findings.push({
|
|
93
|
-
repairKind: 'builtin-link',
|
|
94
|
-
message: 'Codex prompts link does not point to the workspace commands directory.',
|
|
95
|
-
path: '/home/devuser/.codex/prompts'
|
|
95
|
+
...check.finding,
|
|
96
|
+
...(check.repair === undefined ? {} : { repair: check.repair })
|
|
96
97
|
});
|
|
97
98
|
}
|
|
98
99
|
}
|
|
@@ -154,8 +155,7 @@ function hostSourceAccessible(hostPath, writable) {
|
|
|
154
155
|
}
|
|
155
156
|
}
|
|
156
157
|
function expectedMounts(params) {
|
|
157
|
-
const { config, branch, actualMounts } = params;
|
|
158
|
-
const tools = resolveTools(config);
|
|
158
|
+
const { config, branch, tools, actualMounts } = params;
|
|
159
159
|
const core = sandboxCoreBindMounts(config, branch).map((mount) => ({
|
|
160
160
|
path: mount.containerPath,
|
|
161
161
|
expectedType: 'bind',
|
|
@@ -174,7 +174,9 @@ function expectedMounts(params) {
|
|
|
174
174
|
}];
|
|
175
175
|
}));
|
|
176
176
|
const persistentTools = tools
|
|
177
|
-
.filter((tool) => !tool.tmpfs
|
|
177
|
+
.filter((tool) => !tool.tmpfs
|
|
178
|
+
&& (config.agentClientSource === 'canonical'
|
|
179
|
+
|| actualMounts.has(tool.containerMount)))
|
|
178
180
|
.map((tool) => ({
|
|
179
181
|
path: tool.containerMount,
|
|
180
182
|
expectedType: 'bind',
|
|
@@ -245,7 +247,8 @@ export function collectSandboxRecoverySnapshot(params) {
|
|
|
245
247
|
});
|
|
246
248
|
}
|
|
247
249
|
}
|
|
248
|
-
const
|
|
250
|
+
const capabilityPlan = createSandboxCapabilityPlan(params.config);
|
|
251
|
+
const tools = [...capabilityPlan.tools];
|
|
249
252
|
const seeds = declaredTmpfsSeedEntries(tools).map((seed) => {
|
|
250
253
|
const mounted = mountsByDestination.get(seed.stagingPath)?.Type === 'bind';
|
|
251
254
|
return {
|
|
@@ -266,11 +269,18 @@ export function collectSandboxRecoverySnapshot(params) {
|
|
|
266
269
|
}));
|
|
267
270
|
const labels = inspection.Config?.Labels ?? {};
|
|
268
271
|
const branchLabel = labels[sandboxBranchLabel(params.config)];
|
|
269
|
-
const
|
|
272
|
+
const selectedToolIds = new Set(tools.map((tool) => tool.id));
|
|
273
|
+
const enforceCanonicalPlan = params.config.agentClientSource === 'canonical';
|
|
274
|
+
const unexpectedCapabilityMounts = enforceCanonicalPlan
|
|
275
|
+
? capabilityPlan.cleanupInventory
|
|
276
|
+
.filter((tool) => isAgentClientId(tool.id) && !selectedToolIds.has(tool.id))
|
|
277
|
+
.map((tool) => tool.containerMount)
|
|
278
|
+
.filter((mountPath) => mountsByDestination.has(mountPath))
|
|
279
|
+
: [];
|
|
270
280
|
const mountSnapshots = expectedMounts({
|
|
271
281
|
config: params.config,
|
|
272
282
|
branch: params.branch,
|
|
273
|
-
|
|
283
|
+
tools,
|
|
274
284
|
actualMounts: mountsByDestination
|
|
275
285
|
}).map((expected) => {
|
|
276
286
|
const actual = mountsByDestination.get(expected.path);
|
|
@@ -304,18 +314,25 @@ export function collectSandboxRecoverySnapshot(params) {
|
|
|
304
314
|
identityOk: typeof inspection.Id === 'string'
|
|
305
315
|
&& inspection.Id.length > 0
|
|
306
316
|
&& branchLabel === params.branch,
|
|
317
|
+
runtimeCapabilityOk: enforceCanonicalPlan
|
|
318
|
+
? labels[sandboxRuntimeCapabilityLabel(params.config)] === capabilityPlan.runtimeSignature
|
|
319
|
+
: undefined,
|
|
320
|
+
unexpectedCapabilityMounts,
|
|
307
321
|
mounts: mountSnapshots,
|
|
308
322
|
tmpfs,
|
|
309
323
|
seeds,
|
|
310
324
|
aliasesReadable: probe(params.engine, params.container, 'test -r "$1"', ['/home/devuser/.bash_aliases'], runOkFn),
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
325
|
+
agentClientChecks: capabilityPlan.recoveryChecks.map(({ adapterId, check }) => {
|
|
326
|
+
const applicable = check.when === undefined || probe(params.engine, params.container, check.when.script, [...check.when.args], runOkFn, check.when.user);
|
|
327
|
+
return {
|
|
328
|
+
adapterId,
|
|
329
|
+
checkId: check.id,
|
|
330
|
+
applicable,
|
|
331
|
+
healthy: !applicable || probe(params.engine, params.container, check.probe.script, [...check.probe.args], runOkFn, check.probe.user),
|
|
332
|
+
finding: check.finding,
|
|
333
|
+
...(check.repair === undefined ? {} : { repair: check.repair })
|
|
334
|
+
};
|
|
335
|
+
})
|
|
319
336
|
};
|
|
320
337
|
}
|
|
321
338
|
export function prepareTmpfsMounts(params) {
|
|
@@ -394,10 +411,9 @@ function repairFindings(params) {
|
|
|
394
411
|
deps: params.deps
|
|
395
412
|
});
|
|
396
413
|
const runVerboseFn = params.deps?.runVerbose ?? runVerboseEngine;
|
|
397
|
-
|
|
414
|
+
for (const repair of params.findings.flatMap((finding) => finding.repair === undefined ? [] : [finding.repair])) {
|
|
398
415
|
runVerboseFn(params.engine, 'docker', [
|
|
399
|
-
'exec', '--user',
|
|
400
|
-
'/workspace/.codex/commands', '/home/devuser/.codex/prompts'
|
|
416
|
+
'exec', '--user', repair.user, params.container, repair.command, ...repair.args
|
|
401
417
|
]);
|
|
402
418
|
}
|
|
403
419
|
}
|
|
@@ -114,7 +114,7 @@ export function runSafe(cmd, args, opts = {}) {
|
|
|
114
114
|
export function commandForEngine(engine, cmd, args = []) {
|
|
115
115
|
if (engine === 'wsl2') {
|
|
116
116
|
const resolvedWrapper = resolveCommand('wsl.exe');
|
|
117
|
-
return { cmd: resolvedWrapper, args: ['--', cmd, ...args] };
|
|
117
|
+
return { cmd: resolvedWrapper, args: ['--exec', cmd, ...args] };
|
|
118
118
|
}
|
|
119
119
|
if (cmd === 'docker') {
|
|
120
120
|
const context = getAdapter(engine).dockerContext;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
+
import { createSandboxCapabilityPlan } from './agent-client-reconciler.js';
|
|
2
3
|
import { safeNameCandidates, sanitizeBranchName } from './constants.js';
|
|
3
4
|
import { hostJoin } from './engines/wsl2-paths.js';
|
|
4
5
|
export function tmpfsSeedStagingPath(toolId, index) {
|
|
@@ -20,108 +21,6 @@ export function declaredTmpfsSeedEntries(tools) {
|
|
|
20
21
|
})));
|
|
21
22
|
}
|
|
22
23
|
const TOOL_ID_PATTERN = /^[a-z0-9][a-z0-9-]*$/;
|
|
23
|
-
function createBuiltinTools(home, project) {
|
|
24
|
-
return {
|
|
25
|
-
'claude-code': {
|
|
26
|
-
id: 'claude-code',
|
|
27
|
-
name: 'Claude Code',
|
|
28
|
-
install: { type: 'npm', cmd: '@anthropic-ai/claude-code@latest' },
|
|
29
|
-
sandboxBase: hostJoin(home, '.agent-infra', 'sandboxes', 'claude-code'),
|
|
30
|
-
containerMount: '/home/devuser/.claude',
|
|
31
|
-
versionCmd: 'claude --version',
|
|
32
|
-
setupHint: 'Authenticates via host credentials live-mounted at ~/.claude/.credentials.json',
|
|
33
|
-
// Claude Code stores user data (.claude.json — onboarding state, theme,
|
|
34
|
-
// workspace trust) at $HOME/.claude.json by default, which sits OUTSIDE
|
|
35
|
-
// the bind-mounted /home/devuser/.claude tree, so our preseeded
|
|
36
|
-
// .claude.json never gets read and the theme picker re-runs on every
|
|
37
|
-
// container start. Pinning CLAUDE_CONFIG_DIR to the tool mount relocates
|
|
38
|
-
// .claude.json into the same directory as .credentials.json/settings.json,
|
|
39
|
-
// letting ensureClaudeOnboarding actually take effect.
|
|
40
|
-
envVars: { CLAUDE_CONFIG_DIR: '/home/devuser/.claude' },
|
|
41
|
-
hostPreSeedDirs: [
|
|
42
|
-
{ hostDir: hostJoin(home, '.claude', 'plugins'), sandboxSubdir: 'plugins' }
|
|
43
|
-
],
|
|
44
|
-
pathRewriteFiles: [
|
|
45
|
-
'plugins/installed_plugins.json',
|
|
46
|
-
'plugins/known_marketplaces.json'
|
|
47
|
-
],
|
|
48
|
-
hostLiveMounts: [
|
|
49
|
-
{
|
|
50
|
-
hostPath: hostJoin(home, '.agent-infra', 'credentials', project, 'claude-code', '.credentials.json'),
|
|
51
|
-
containerSubpath: '.credentials.json'
|
|
52
|
-
}
|
|
53
|
-
]
|
|
54
|
-
},
|
|
55
|
-
codex: {
|
|
56
|
-
id: 'codex',
|
|
57
|
-
name: 'Codex',
|
|
58
|
-
install: { type: 'npm', cmd: '@openai/codex' },
|
|
59
|
-
sandboxBase: hostJoin(home, '.agent-infra', 'sandboxes', 'codex'),
|
|
60
|
-
containerMount: '/home/devuser/.codex',
|
|
61
|
-
versionCmd: 'codex --version',
|
|
62
|
-
setupHint: 'Run codex once inside the container and choose Device Code login if needed.',
|
|
63
|
-
// codex churns ~/.codex/logs_2.sqlite heavily (upstream openai/codex#24275);
|
|
64
|
-
// a bind-mount would write-amplify onto the host SSD via virtiofs. Mount the
|
|
65
|
-
// codex home as tmpfs so those logs stay in RAM and die with the container.
|
|
66
|
-
// Seeded config (config.toml, model-catalogs) is copied into the tmpfs at
|
|
67
|
-
// startup; runtime files like logs_2.sqlite stay in RAM.
|
|
68
|
-
tmpfs: { size: '512m', seed: ['config.toml', 'model-catalogs'] },
|
|
69
|
-
hostLiveMounts: [
|
|
70
|
-
{ hostPath: hostJoin(home, '.codex', 'auth.json'), containerSubpath: 'auth.json' }
|
|
71
|
-
],
|
|
72
|
-
postSetupCmds: [
|
|
73
|
-
'test -d /workspace/.codex/commands && ln -sfn /workspace/.codex/commands /home/devuser/.codex/prompts || true'
|
|
74
|
-
]
|
|
75
|
-
},
|
|
76
|
-
opencode: {
|
|
77
|
-
id: 'opencode',
|
|
78
|
-
name: 'OpenCode',
|
|
79
|
-
install: { type: 'npm', cmd: 'opencode-ai' },
|
|
80
|
-
sandboxBase: hostJoin(home, '.agent-infra', 'sandboxes', 'opencode'),
|
|
81
|
-
containerMount: '/home/devuser/.local/share/opencode',
|
|
82
|
-
versionCmd: 'opencode version',
|
|
83
|
-
setupHint: 'Configure OpenCode credentials inside the container before first use.',
|
|
84
|
-
// OpenCode reads opencode.json from $XDG_CONFIG_HOME/opencode by default,
|
|
85
|
-
// outside this tool mount. Pin the config file path so the inherited
|
|
86
|
-
// sandbox opencode.json is the one the TUI actually reads.
|
|
87
|
-
envVars: { OPENCODE_CONFIG: '/home/devuser/.local/share/opencode/opencode.json' },
|
|
88
|
-
hostLiveMounts: [
|
|
89
|
-
{
|
|
90
|
-
hostPath: hostJoin(home, '.local', 'share', 'opencode', 'auth.json'),
|
|
91
|
-
containerSubpath: 'auth.json'
|
|
92
|
-
}
|
|
93
|
-
]
|
|
94
|
-
},
|
|
95
|
-
'gemini-cli': {
|
|
96
|
-
id: 'gemini-cli',
|
|
97
|
-
name: 'Gemini CLI',
|
|
98
|
-
install: { type: 'npm', cmd: '@google/gemini-cli' },
|
|
99
|
-
sandboxBase: hostJoin(home, '.agent-infra', 'sandboxes', 'gemini-cli'),
|
|
100
|
-
containerMount: '/home/devuser/.gemini',
|
|
101
|
-
versionCmd: 'gemini --version',
|
|
102
|
-
setupHint: 'Run gemini inside the container to finish authentication.',
|
|
103
|
-
hostLiveMounts: [
|
|
104
|
-
{ hostPath: hostJoin(home, '.gemini', 'oauth_creds.json'), containerSubpath: 'oauth_creds.json' }
|
|
105
|
-
],
|
|
106
|
-
hostPreSeedFiles: [
|
|
107
|
-
{ hostPath: hostJoin(home, '.gemini', 'settings.json'), sandboxName: 'settings.json' },
|
|
108
|
-
{ hostPath: hostJoin(home, '.gemini', 'google_accounts.json'), sandboxName: 'google_accounts.json' }
|
|
109
|
-
]
|
|
110
|
-
},
|
|
111
|
-
'agent-infra': {
|
|
112
|
-
id: 'agent-infra',
|
|
113
|
-
name: 'agent-infra CLI',
|
|
114
|
-
install: { type: 'npm', cmd: '@fitlab-ai/agent-infra@latest' },
|
|
115
|
-
sandboxBase: hostJoin(home, '.agent-infra', 'sandboxes', 'agent-infra'),
|
|
116
|
-
containerMount: '/home/devuser/.agent-infra-cli',
|
|
117
|
-
versionCmd: 'ai version --raw',
|
|
118
|
-
setupHint: 'Provides the ai and agent-infra CLI commands inside the sandbox.'
|
|
119
|
-
}
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
|
-
export function builtinToolIds() {
|
|
123
|
-
return Object.keys(createBuiltinTools('', ''));
|
|
124
|
-
}
|
|
125
24
|
function validateTool(tool) {
|
|
126
25
|
if (!tool.id || !TOOL_ID_PATTERN.test(tool.id)) {
|
|
127
26
|
throw new Error(`Invalid sandbox tool id: ${String(tool.id)}`);
|
|
@@ -308,30 +207,7 @@ export function parseCustomTools(value, options) {
|
|
|
308
207
|
return value.map((entry, index) => parseCustomTool(entry, index, options));
|
|
309
208
|
}
|
|
310
209
|
export function resolveTools(config) {
|
|
311
|
-
|
|
312
|
-
const customs = config.customTools ?? [];
|
|
313
|
-
const seen = new Set();
|
|
314
|
-
for (const tool of customs) {
|
|
315
|
-
if (builtins[tool.id]) {
|
|
316
|
-
throw new Error(`Custom sandbox tool id "${tool.id}" collides with a built-in tool`);
|
|
317
|
-
}
|
|
318
|
-
if (seen.has(tool.id)) {
|
|
319
|
-
throw new Error(`Duplicate sandbox tool id "${tool.id}" in customTools`);
|
|
320
|
-
}
|
|
321
|
-
seen.add(tool.id);
|
|
322
|
-
}
|
|
323
|
-
const merged = { ...builtins };
|
|
324
|
-
for (const tool of customs) {
|
|
325
|
-
merged[tool.id] = tool;
|
|
326
|
-
}
|
|
327
|
-
return config.tools.map((id) => {
|
|
328
|
-
const tool = merged[id];
|
|
329
|
-
if (!tool) {
|
|
330
|
-
throw new Error(`Unknown sandbox tool: ${id}`);
|
|
331
|
-
}
|
|
332
|
-
validateTool(tool);
|
|
333
|
-
return tool;
|
|
334
|
-
});
|
|
210
|
+
return [...createSandboxCapabilityPlan(config).tools];
|
|
335
211
|
}
|
|
336
212
|
export function toolConfigDir(tool, project, branch) {
|
|
337
213
|
return hostJoin(tool.sandboxBase, project, sanitizeBranchName(branch));
|
|
@@ -3,6 +3,7 @@ import path from 'node:path';
|
|
|
3
3
|
import { resolveTaskRef } from './resolve-ref.js';
|
|
4
4
|
import { locateActivityLog } from './activity-log.js';
|
|
5
5
|
import { parseImplementationInputs, selectPendingImplementationInput } from './implementation-inputs.js';
|
|
6
|
+
import { parseVerdict } from './review-artifacts.js';
|
|
6
7
|
import { extractSection, findSectionHeading } from './sections.js';
|
|
7
8
|
const artifactFamilyCatalog = [
|
|
8
9
|
{ family: 'analysis', sectionAliases: ['分析', 'Analysis'], heading: '分析', labels: ['需求分析报告', 'Requirements Analysis'] },
|
|
@@ -173,24 +174,6 @@ function resolveReviewedInput(taskDir, review, expectedFamily, diagnostics) {
|
|
|
173
174
|
return null;
|
|
174
175
|
}
|
|
175
176
|
}
|
|
176
|
-
function extractMarkdownSection(content, names) {
|
|
177
|
-
const lines = content.split(/\r?\n/);
|
|
178
|
-
const allowed = new Set(names);
|
|
179
|
-
const start = lines.findIndex((line) => {
|
|
180
|
-
const match = /^##\s+(.+?)\s*$/.exec(line.trim());
|
|
181
|
-
return Boolean(match?.[1] && allowed.has(match[1]));
|
|
182
|
-
});
|
|
183
|
-
if (start === -1)
|
|
184
|
-
return '';
|
|
185
|
-
let end = lines.length;
|
|
186
|
-
for (let index = start + 1; index < lines.length; index += 1) {
|
|
187
|
-
if (/^##\s+/.test(lines[index])) {
|
|
188
|
-
end = index;
|
|
189
|
-
break;
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
return lines.slice(start + 1, end).join('\n');
|
|
193
|
-
}
|
|
194
177
|
function assertWritableInventory(inventory) {
|
|
195
178
|
if (inventory.status === 'failed')
|
|
196
179
|
return inventory.error;
|
|
@@ -333,37 +316,6 @@ function resolveDecisionImplementationInput(inventory, review) {
|
|
|
333
316
|
return { error: error instanceof Error ? error.message : String(error) };
|
|
334
317
|
}
|
|
335
318
|
}
|
|
336
|
-
function normalizeVerdict(value) {
|
|
337
|
-
const normalized = value.trim().toLowerCase();
|
|
338
|
-
if (normalized === '通过' || normalized === 'approved')
|
|
339
|
-
return 'Approved';
|
|
340
|
-
if (normalized === '需要修改' || normalized === 'changes requested')
|
|
341
|
-
return 'Changes Requested';
|
|
342
|
-
if (normalized === '拒绝' || normalized === 'rejected')
|
|
343
|
-
return 'Rejected';
|
|
344
|
-
return null;
|
|
345
|
-
}
|
|
346
|
-
function parseVerdict(file) {
|
|
347
|
-
let content;
|
|
348
|
-
try {
|
|
349
|
-
content = fs.readFileSync(file, 'utf8');
|
|
350
|
-
}
|
|
351
|
-
catch (error) {
|
|
352
|
-
return { ok: false, message: String(error) };
|
|
353
|
-
}
|
|
354
|
-
const summary = extractMarkdownSection(content, ['审查摘要', 'Review Summary']);
|
|
355
|
-
const verdictMatch = /^[-*]?\s*\*\*(?:总体结论|Overall Verdict)\*\*[::]\s*(.+?)\s*$/im.exec(summary);
|
|
356
|
-
const verdict = verdictMatch?.[1] ? normalizeVerdict(verdictMatch[1]) : null;
|
|
357
|
-
if (!verdict)
|
|
358
|
-
return { ok: false, message: `cannot parse verdict in ${path.basename(file)}` };
|
|
359
|
-
if (verdict !== 'Approved')
|
|
360
|
-
return { ok: true, verdict };
|
|
361
|
-
const findings = /^[-*]?\s*\*\*(?:发现(AI 可处理)|Findings \(AI-actionable\))\*\*[::]\s*(.+?)\s*$/im.exec(summary)?.[1];
|
|
362
|
-
const counts = findings?.match(/(\d+)\s*(?:阻塞项|blockers?).*?(\d+)\s*(?:主要|majors?).*?(\d+)\s*(?:次要|minors?)/i);
|
|
363
|
-
if (!counts)
|
|
364
|
-
return { ok: false, message: `cannot parse findings count in ${path.basename(file)}` };
|
|
365
|
-
return { ok: true, verdict: counts.slice(1).some((value) => Number(value) > 0) ? 'Approved-with-issues' : 'Approved' };
|
|
366
|
-
}
|
|
367
319
|
function validateCompletedArtifact(taskDir, family, name, round) {
|
|
368
320
|
if (path.basename(name) !== name)
|
|
369
321
|
return { ok: false, error: { code: 'ARTIFACT_PATH_INVALID', message: 'artifact must be a top-level basename' } };
|
|
@@ -9,8 +9,9 @@ import { extractSubSection } from '../sections.js';
|
|
|
9
9
|
const USAGE = `Usage: ai task decisions [--task <ref> | -t <ref>] [--item <selector> | -i <selector>] [options]
|
|
10
10
|
ai task decisions <ref> [selector] [options]
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
Shows what still needs a maintainer's decision. Without a selector, it lists
|
|
13
|
+
the choices. With a selector, it shows the explanation and how to respond.
|
|
14
|
+
Read-only.
|
|
14
15
|
|
|
15
16
|
Omit <ref> Resolve the unique active task for the current branch.
|
|
16
17
|
<ref> Legacy positional task ref.
|
|
@@ -161,28 +162,29 @@ function findDecisionRecord(id, content) {
|
|
|
161
162
|
function titleOf(row, taskDir) {
|
|
162
163
|
const block = findDetailBlock(row, taskDir);
|
|
163
164
|
if (block) {
|
|
164
|
-
|
|
165
|
+
const title = block
|
|
165
166
|
.split('\n')[0]
|
|
166
167
|
.replace(/^###\s+/, '')
|
|
167
168
|
.replace(/\s*\[needs-human-decision\]\s*$/, '')
|
|
168
169
|
.trim();
|
|
170
|
+
return title.replace(new RegExp(`^${row.id}\\s*[::]\\s*`, 'i'), '').trim();
|
|
169
171
|
}
|
|
170
|
-
return
|
|
172
|
+
return '(explanation unavailable)';
|
|
173
|
+
}
|
|
174
|
+
function trackingOf(row) {
|
|
175
|
+
return `stage=${row.stage} · severity=${row.severity} · status=${row.status} · evidence=${row.evidence}`;
|
|
171
176
|
}
|
|
172
177
|
function renderList(rows, format, taskDir) {
|
|
173
178
|
if (rows.length === 0) {
|
|
174
|
-
process.stdout.write('
|
|
179
|
+
process.stdout.write('No pending decisions.\n');
|
|
175
180
|
return;
|
|
176
181
|
}
|
|
177
|
-
const headers = ['#', '
|
|
182
|
+
const headers = ['#', 'CHOICE', 'WHAT NEEDS A DECISION', 'TRACKING'];
|
|
178
183
|
const data = rows.map((r, i) => [
|
|
179
184
|
String(i + 1),
|
|
180
185
|
r.id,
|
|
181
|
-
r
|
|
182
|
-
r
|
|
183
|
-
r.status,
|
|
184
|
-
r.evidence,
|
|
185
|
-
titleOf(r, taskDir)
|
|
186
|
+
titleOf(r, taskDir),
|
|
187
|
+
trackingOf(r)
|
|
186
188
|
]);
|
|
187
189
|
if (format === 'markdown') {
|
|
188
190
|
const sep = headers.map(() => '---');
|
|
@@ -191,10 +193,28 @@ function renderList(rows, format, taskDir) {
|
|
|
191
193
|
`| ${sep.join(' | ')} |`,
|
|
192
194
|
...data.map((row) => `| ${row.join(' | ')} |`)
|
|
193
195
|
];
|
|
194
|
-
process.stdout.write(`${md.join('\n')}\n`);
|
|
196
|
+
process.stdout.write(`${md.join('\n')}\n\nView one item: ai task decisions <task-ref> <ordinal|ledger-id>\n`);
|
|
195
197
|
return;
|
|
196
198
|
}
|
|
197
|
-
process.stdout.write(`${formatTable(headers, data).join('\n')}\n`);
|
|
199
|
+
process.stdout.write(`${formatTable(headers, data).join('\n')}\n\nView one item: ai task decisions <task-ref> <ordinal|ledger-id>\n`);
|
|
200
|
+
}
|
|
201
|
+
function renderTracking(row, format) {
|
|
202
|
+
if (format === 'markdown') {
|
|
203
|
+
return [
|
|
204
|
+
'**Tracking**',
|
|
205
|
+
`- stage: \`${row.stage}\``,
|
|
206
|
+
`- severity: \`${row.severity}\``,
|
|
207
|
+
`- status: \`${row.status}\``,
|
|
208
|
+
`- evidence: \`${row.evidence}\``
|
|
209
|
+
];
|
|
210
|
+
}
|
|
211
|
+
return [
|
|
212
|
+
'Tracking:',
|
|
213
|
+
`stage: ${row.stage}`,
|
|
214
|
+
`severity: ${row.severity}`,
|
|
215
|
+
`status: ${row.status}`,
|
|
216
|
+
`evidence: ${row.evidence}`
|
|
217
|
+
];
|
|
198
218
|
}
|
|
199
219
|
function renderDetail(rows, selector, format, taskDir, content) {
|
|
200
220
|
const selected = selectDecisionItem(rows, selector);
|
|
@@ -204,25 +224,35 @@ function renderDetail(rows, selector, format, taskDir, content) {
|
|
|
204
224
|
}
|
|
205
225
|
const r = selected.row;
|
|
206
226
|
const block = findDetailBlock(r, taskDir);
|
|
227
|
+
const title = titleOf(r, taskDir);
|
|
207
228
|
const lines = [];
|
|
229
|
+
const decided = r.status === 'human-decided';
|
|
208
230
|
if (format === 'markdown') {
|
|
209
|
-
lines.push(
|
|
231
|
+
lines.push(`## ${decided ? 'Decision already recorded' : 'Decision needed'}: ${title}`, '');
|
|
232
|
+
if (!decided) {
|
|
233
|
+
lines.push('**How to record your choice**', `\`ai decide <task-ref> ${r.id} <your choice and rationale>\``, '');
|
|
234
|
+
}
|
|
235
|
+
lines.push('**Original context**', '');
|
|
210
236
|
}
|
|
211
237
|
else {
|
|
212
|
-
lines.push(`${
|
|
238
|
+
lines.push(`${decided ? 'Decision already recorded' : 'Decision needed'}: ${title}`, '');
|
|
239
|
+
if (!decided) {
|
|
240
|
+
lines.push('How to record your choice:', `ai decide <task-ref> ${r.id} <your choice and rationale>`, '');
|
|
241
|
+
}
|
|
242
|
+
lines.push('Original context:');
|
|
213
243
|
}
|
|
214
244
|
if (block) {
|
|
215
245
|
lines.push(block);
|
|
216
246
|
}
|
|
217
247
|
else {
|
|
218
|
-
lines.push(
|
|
248
|
+
lines.push(`This older task does not include a full explanation for ${r.id}.`, 'Use the tracking reference below to find the original context.');
|
|
219
249
|
}
|
|
220
|
-
if (
|
|
250
|
+
if (decided) {
|
|
221
251
|
const record = findDecisionRecord(r.id, content);
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
}
|
|
252
|
+
lines.push('', format === 'markdown' ? '**Recorded choice**' : 'Recorded choice:');
|
|
253
|
+
lines.push(...(record.length ? record : ['No separate ruling record was found.']));
|
|
225
254
|
}
|
|
255
|
+
lines.push('', ...renderTracking(r, format));
|
|
226
256
|
process.stdout.write(`${lines.join('\n')}\n`);
|
|
227
257
|
}
|
|
228
258
|
function decisions(args = []) {
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
const MANAGED_AGENTS = {
|
|
3
|
+
'agent-infra-lifecycle-executor': 'executor',
|
|
4
|
+
'agent-infra-lifecycle-reviewer': 'reviewer'
|
|
5
|
+
};
|
|
6
|
+
function managedDelegationRole(nativeAgent) {
|
|
7
|
+
return MANAGED_AGENTS[nativeAgent] ?? null;
|
|
8
|
+
}
|
|
9
|
+
function fail(code, message) {
|
|
10
|
+
return { ok: false, code, message };
|
|
11
|
+
}
|
|
12
|
+
function prepareDelegation(input, options = {}) {
|
|
13
|
+
return Object.freeze({
|
|
14
|
+
...input,
|
|
15
|
+
id: (options.id ?? randomUUID)(),
|
|
16
|
+
actualModel: null,
|
|
17
|
+
modelFallbackReason: null,
|
|
18
|
+
parentId: null,
|
|
19
|
+
childId: null,
|
|
20
|
+
spawnMode: null,
|
|
21
|
+
agent: null,
|
|
22
|
+
status: 'prepared',
|
|
23
|
+
afterFingerprint: null,
|
|
24
|
+
changedPaths: Object.freeze([]),
|
|
25
|
+
createdAt: (options.now ?? (() => new Date().toISOString()))(),
|
|
26
|
+
activatedAt: null,
|
|
27
|
+
sealedAt: null,
|
|
28
|
+
consumedAt: null
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
function activateDelegation(receipt, event, options = {}) {
|
|
32
|
+
const managedRole = managedDelegationRole(event.nativeAgent);
|
|
33
|
+
if (!managedRole)
|
|
34
|
+
return fail('DELEGATION_IGNORED', `subagent '${event.nativeAgent}' is not lifecycle-managed`);
|
|
35
|
+
if (receipt.status !== 'prepared')
|
|
36
|
+
return fail('DELEGATION_STATE_INVALID', `delegation ${receipt.id} is ${receipt.status}, expected prepared`);
|
|
37
|
+
if (managedRole !== receipt.role)
|
|
38
|
+
return fail('DELEGATION_ROLE_MISMATCH', `managed role ${managedRole} does not match ${receipt.role}`);
|
|
39
|
+
if (!event.parentId || (receipt.parentId !== null && event.parentId !== receipt.parentId) || !event.childId || event.childId === event.parentId) {
|
|
40
|
+
return fail('DELEGATION_IDENTITY_INVALID', 'native parent/child identity does not match the prepared delegation');
|
|
41
|
+
}
|
|
42
|
+
if (event.spawnMode !== 'fresh')
|
|
43
|
+
return fail('DELEGATION_FORK_FORBIDDEN', `spawn mode '${event.spawnMode}' is not fresh`);
|
|
44
|
+
const actualModel = event.actualModel ?? null;
|
|
45
|
+
if (actualModel && receipt.requestedModel && actualModel !== receipt.requestedModel && !event.modelFallbackReason) {
|
|
46
|
+
return fail('DELEGATION_MODEL_FALLBACK_UNRECORDED', 'actual model differs from requested model without a fallback reason');
|
|
47
|
+
}
|
|
48
|
+
return { ok: true, receipt: Object.freeze({
|
|
49
|
+
...receipt,
|
|
50
|
+
status: 'activated',
|
|
51
|
+
parentId: event.parentId,
|
|
52
|
+
childId: event.childId,
|
|
53
|
+
spawnMode: event.spawnMode,
|
|
54
|
+
actualModel,
|
|
55
|
+
modelFallbackReason: event.modelFallbackReason ?? null,
|
|
56
|
+
activatedAt: (options.now ?? (() => new Date().toISOString()))()
|
|
57
|
+
}) };
|
|
58
|
+
}
|
|
59
|
+
function completeDelegationStage(receipt, event) {
|
|
60
|
+
if (receipt.status !== 'activated')
|
|
61
|
+
return fail('DELEGATION_STATE_INVALID', `delegation ${receipt.id} is ${receipt.status}, expected activated`);
|
|
62
|
+
if (event.stage !== receipt.stage || event.round !== receipt.round || event.artifact !== receipt.artifact) {
|
|
63
|
+
return fail('DELEGATION_STAGE_MISMATCH', 'stage completion identity does not match the active delegation');
|
|
64
|
+
}
|
|
65
|
+
const acceptedAgents = receipt.client === 'claude-code' ? ['claude', 'claude-code'] : [receipt.client];
|
|
66
|
+
if (!acceptedAgents.includes(event.agent)) {
|
|
67
|
+
return fail('DELEGATION_AGENT_MISMATCH', `stage agent '${event.agent}' does not match client '${receipt.client}'`);
|
|
68
|
+
}
|
|
69
|
+
return { ok: true, receipt: Object.freeze({ ...receipt, status: 'stage-completed', agent: event.agent }) };
|
|
70
|
+
}
|
|
71
|
+
function sealDelegation(receipt, event, options = {}) {
|
|
72
|
+
if (receipt.status !== 'stage-completed')
|
|
73
|
+
return fail('DELEGATION_STATE_INVALID', `delegation ${receipt.id} is ${receipt.status}, expected stage-completed`);
|
|
74
|
+
if (event.childId !== receipt.childId || event.exitCode !== 0)
|
|
75
|
+
return fail('DELEGATION_STOP_INVALID', 'native stop identity or exit status is invalid');
|
|
76
|
+
if (receipt.role === 'reviewer') {
|
|
77
|
+
const taskRoot = `.agents/workspace/active/${receipt.taskId}/`;
|
|
78
|
+
const allowed = new Set([
|
|
79
|
+
`${taskRoot}${receipt.artifact}`,
|
|
80
|
+
`${taskRoot}task.md`,
|
|
81
|
+
`${taskRoot}orchestration.json`
|
|
82
|
+
]);
|
|
83
|
+
const disallowed = event.changedPaths.find((entry) => !allowed.has(entry));
|
|
84
|
+
if (disallowed)
|
|
85
|
+
return fail('DELEGATION_REVIEWER_WRITE_FORBIDDEN', `reviewer changed forbidden path '${disallowed}'`);
|
|
86
|
+
}
|
|
87
|
+
return { ok: true, receipt: Object.freeze({
|
|
88
|
+
...receipt,
|
|
89
|
+
status: 'sealed',
|
|
90
|
+
afterFingerprint: event.afterFingerprint,
|
|
91
|
+
changedPaths: Object.freeze([...event.changedPaths]),
|
|
92
|
+
sealedAt: (options.now ?? (() => new Date().toISOString()))()
|
|
93
|
+
}) };
|
|
94
|
+
}
|
|
95
|
+
function consumeDelegation(receipt, options = {}) {
|
|
96
|
+
if (receipt.status === 'consumed')
|
|
97
|
+
return fail('DELEGATION_REPLAY', `delegation ${receipt.id} was already consumed`);
|
|
98
|
+
if (receipt.status !== 'sealed')
|
|
99
|
+
return fail('DELEGATION_STATE_INVALID', `delegation ${receipt.id} is ${receipt.status}, expected sealed`);
|
|
100
|
+
return { ok: true, receipt: Object.freeze({
|
|
101
|
+
...receipt,
|
|
102
|
+
status: 'consumed',
|
|
103
|
+
consumedAt: (options.now ?? (() => new Date().toISOString()))()
|
|
104
|
+
}) };
|
|
105
|
+
}
|
|
106
|
+
export { activateDelegation, completeDelegationStage, consumeDelegation, managedDelegationRole, prepareDelegation, sealDelegation };
|
|
107
|
+
//# sourceMappingURL=delegation-receipts.js.map
|