@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
|
@@ -7,7 +7,8 @@ import type { ExecFileSyncOptions, StdioOptions } from 'node:child_process';
|
|
|
7
7
|
import { parseArgs } from 'node:util';
|
|
8
8
|
import * as p from '@clack/prompts';
|
|
9
9
|
import pc from 'picocolors';
|
|
10
|
-
import
|
|
10
|
+
import { listAgentClientAdapters } from '../../agent-clients/registry.ts';
|
|
11
|
+
import type { SandboxAlias } from '../tool-types.ts';
|
|
11
12
|
import { loadConfig } from '../config.ts';
|
|
12
13
|
import {
|
|
13
14
|
assertValidBranchName,
|
|
@@ -18,6 +19,7 @@ import {
|
|
|
18
19
|
sandboxImageConfigLabel,
|
|
19
20
|
sandboxImageRefreshLabel,
|
|
20
21
|
sandboxLabel,
|
|
22
|
+
sandboxRuntimeCapabilityLabel,
|
|
21
23
|
shareBranchDir,
|
|
22
24
|
shareCommonDir,
|
|
23
25
|
shellConfigDir,
|
|
@@ -43,6 +45,11 @@ import {
|
|
|
43
45
|
runVerboseEngine
|
|
44
46
|
} from '../shell.ts';
|
|
45
47
|
import { resolveTaskBranch } from '../task-resolver.ts';
|
|
48
|
+
import {
|
|
49
|
+
createSandboxCapabilityPlan,
|
|
50
|
+
runSandboxHooks,
|
|
51
|
+
runBoundedSandboxHookCommand
|
|
52
|
+
} from '../agent-client-reconciler.ts';
|
|
46
53
|
import {
|
|
47
54
|
resolveTools,
|
|
48
55
|
tmpfsSeedStagingPath,
|
|
@@ -61,6 +68,7 @@ import { clipboardHostDir, CONTAINER_CLIPBOARD_MOUNT } from '../clipboard/paths.
|
|
|
61
68
|
import { validateSelinuxDisableEnv } from '../engines/selinux.ts';
|
|
62
69
|
import { dotfilesCacheDir, materializeDotfiles } from '../dotfiles.ts';
|
|
63
70
|
import { ensureSandboxDiscoveryReadmes } from '../readme-scaffold.ts';
|
|
71
|
+
import { removeDirRecursive } from '../../remove-dir.ts';
|
|
64
72
|
import {
|
|
65
73
|
prepareClaudeCredentials,
|
|
66
74
|
redactCommandError,
|
|
@@ -76,29 +84,13 @@ import {
|
|
|
76
84
|
parseRefreshTimestamp
|
|
77
85
|
} from '../image-build.ts';
|
|
78
86
|
|
|
79
|
-
|
|
87
|
+
export {
|
|
88
|
+
ensureCodexModelInheritance,
|
|
89
|
+
ensureCodexWorkspaceTrust
|
|
90
|
+
} from '../../agent-clients/adapters/codex-sandbox.ts';
|
|
91
|
+
|
|
80
92
|
const SANDBOX_ALIAS_BLOCK_BEGIN = '# >>> agent-infra managed aliases >>>';
|
|
81
93
|
const SANDBOX_ALIAS_BLOCK_END = '# <<< agent-infra managed aliases <<<';
|
|
82
|
-
const SANDBOX_ALIAS_NAMES = [
|
|
83
|
-
'claude-yolo',
|
|
84
|
-
'opencode-yolo',
|
|
85
|
-
'codex-yolo',
|
|
86
|
-
'gemini-yolo',
|
|
87
|
-
'cy',
|
|
88
|
-
'oy',
|
|
89
|
-
'xy',
|
|
90
|
-
'gy'
|
|
91
|
-
];
|
|
92
|
-
const DEFAULT_SANDBOX_ALIASES = `alias claude-yolo='claude --dangerously-skip-permissions; tput ed'
|
|
93
|
-
alias opencode-yolo='OPENCODE_PERMISSION='\\''${OPENCODE_YOLO_PERMISSION}'\\'' opencode; tput ed'
|
|
94
|
-
alias codex-yolo='codex --yolo; tput ed'
|
|
95
|
-
alias gemini-yolo='gemini --yolo; tput ed'
|
|
96
|
-
|
|
97
|
-
alias cy='claude --dangerously-skip-permissions; tput ed'
|
|
98
|
-
alias oy='OPENCODE_PERMISSION='\\''${OPENCODE_YOLO_PERMISSION}'\\'' opencode; tput ed'
|
|
99
|
-
alias xy='codex --yolo; tput ed'
|
|
100
|
-
alias gy='gemini --yolo; tput ed'
|
|
101
|
-
`;
|
|
102
94
|
const CONTAINER_HOME = '/home/devuser';
|
|
103
95
|
const CONTAINER_SHELL_CONFIG_MOUNT = `${CONTAINER_HOME}/.host-shell-config`;
|
|
104
96
|
const USAGE = `Usage: ai sandbox create <branch> [base] [--cpu <n>] [--memory <n>] [--no-refresh] [--inherit-proxy|-P] [--inherit-build-proxy|-B]
|
|
@@ -410,7 +402,7 @@ export function prepareHostShellConfig({
|
|
|
410
402
|
repoRoot: string;
|
|
411
403
|
}): HostShellConfig {
|
|
412
404
|
const hostDir = hostShellConfigDir(home, project, branch);
|
|
413
|
-
|
|
405
|
+
removeDirRecursive(hostDir);
|
|
414
406
|
fs.mkdirSync(hostDir, { recursive: true });
|
|
415
407
|
|
|
416
408
|
writeSanitizedGitconfig({
|
|
@@ -459,10 +451,19 @@ function normalizeWorktreePath(worktreePath: string): string {
|
|
|
459
451
|
return '';
|
|
460
452
|
}
|
|
461
453
|
|
|
454
|
+
const wslMount = process.platform === 'win32'
|
|
455
|
+
? worktreePath.match(/^\/mnt\/([A-Za-z])(?:\/(.*))?$/)
|
|
456
|
+
: null;
|
|
457
|
+
const hostPath = wslMount
|
|
458
|
+
? `${wslMount[1]!.toUpperCase()}:\\${(wslMount[2] ?? '').replace(/\//g, '\\')}`
|
|
459
|
+
: worktreePath;
|
|
460
|
+
|
|
462
461
|
try {
|
|
463
|
-
|
|
462
|
+
const normalized = fs.existsSync(hostPath) ? fs.realpathSync(hostPath) : path.resolve(hostPath);
|
|
463
|
+
return process.platform === 'win32' ? normalized.toLowerCase() : normalized;
|
|
464
464
|
} catch {
|
|
465
|
-
|
|
465
|
+
const normalized = path.resolve(hostPath);
|
|
466
|
+
return process.platform === 'win32' ? normalized.toLowerCase() : normalized;
|
|
466
467
|
}
|
|
467
468
|
}
|
|
468
469
|
|
|
@@ -714,7 +715,7 @@ export function buildContainerEnvFile(
|
|
|
714
715
|
mkdtempFn = fs.mkdtempSync,
|
|
715
716
|
writeFileFn = fs.writeFileSync,
|
|
716
717
|
chmodFn = fs.chmodSync,
|
|
717
|
-
rmFn = fs.rmSync,
|
|
718
|
+
rmFn = removeDirRecursive as typeof fs.rmSync,
|
|
718
719
|
tmpDir = os.tmpdir(),
|
|
719
720
|
runSafeFn = runSafe
|
|
720
721
|
} = options;
|
|
@@ -971,154 +972,6 @@ export function ensureClaudeSettings(toolDir: string, hostHomeDir?: string): voi
|
|
|
971
972
|
}
|
|
972
973
|
}
|
|
973
974
|
|
|
974
|
-
function resolveHostCatalogPath(value: unknown, hostHomeDir: string): string | null {
|
|
975
|
-
if (typeof value !== 'string' || value === '') {
|
|
976
|
-
return null;
|
|
977
|
-
}
|
|
978
|
-
let resolved: string;
|
|
979
|
-
if (value === '~' || value.startsWith('~/') || value.startsWith('~\\')) {
|
|
980
|
-
resolved = path.join(hostHomeDir, value.slice(1).replace(/^[/\\]+/, ''));
|
|
981
|
-
} else if (path.isAbsolute(value)) {
|
|
982
|
-
resolved = value;
|
|
983
|
-
} else {
|
|
984
|
-
resolved = path.join(hostHomeDir, '.codex', value);
|
|
985
|
-
}
|
|
986
|
-
try {
|
|
987
|
-
if (!fs.statSync(resolved).isFile()) {
|
|
988
|
-
return null;
|
|
989
|
-
}
|
|
990
|
-
fs.accessSync(resolved, fs.constants.R_OK);
|
|
991
|
-
return resolved;
|
|
992
|
-
} catch {
|
|
993
|
-
return null;
|
|
994
|
-
}
|
|
995
|
-
}
|
|
996
|
-
|
|
997
|
-
const CODEX_DISABLED_FEATURE_FLAGS = ['apps', 'enable_mcp_apps'] as const;
|
|
998
|
-
|
|
999
|
-
function removeCodexMcpServers(sandboxParsed: JsonObject): boolean {
|
|
1000
|
-
if (!Object.hasOwn(sandboxParsed, 'mcp_servers')) {
|
|
1001
|
-
return false;
|
|
1002
|
-
}
|
|
1003
|
-
delete sandboxParsed.mcp_servers;
|
|
1004
|
-
return true;
|
|
1005
|
-
}
|
|
1006
|
-
|
|
1007
|
-
function inheritDisabledCodexFeatureFlags(sandboxParsed: JsonObject, hostParsed: JsonObject): boolean {
|
|
1008
|
-
if (!isJsonObjectRecord(hostParsed.features)) {
|
|
1009
|
-
return false;
|
|
1010
|
-
}
|
|
1011
|
-
if (Object.hasOwn(sandboxParsed, 'features') && !isJsonObjectRecord(sandboxParsed.features)) {
|
|
1012
|
-
return false;
|
|
1013
|
-
}
|
|
1014
|
-
|
|
1015
|
-
let sandboxFeatures = sandboxParsed.features as JsonObject | undefined;
|
|
1016
|
-
let changed = false;
|
|
1017
|
-
for (const key of CODEX_DISABLED_FEATURE_FLAGS) {
|
|
1018
|
-
if (hostParsed.features[key] !== false) {
|
|
1019
|
-
continue;
|
|
1020
|
-
}
|
|
1021
|
-
if (!sandboxFeatures) {
|
|
1022
|
-
sandboxFeatures = {};
|
|
1023
|
-
sandboxParsed.features = sandboxFeatures;
|
|
1024
|
-
}
|
|
1025
|
-
if (sandboxFeatures[key] !== false) {
|
|
1026
|
-
sandboxFeatures[key] = false;
|
|
1027
|
-
changed = true;
|
|
1028
|
-
}
|
|
1029
|
-
}
|
|
1030
|
-
return changed;
|
|
1031
|
-
}
|
|
1032
|
-
|
|
1033
|
-
export function ensureCodexModelInheritance(
|
|
1034
|
-
toolDir: string,
|
|
1035
|
-
hostHomeDir?: string,
|
|
1036
|
-
containerCodexDir: string = '/home/devuser/.codex'
|
|
1037
|
-
): void {
|
|
1038
|
-
if (!hostHomeDir) {
|
|
1039
|
-
return;
|
|
1040
|
-
}
|
|
1041
|
-
|
|
1042
|
-
const hostConfigPath = path.join(hostHomeDir, '.codex', 'config.toml');
|
|
1043
|
-
if (!fs.existsSync(hostConfigPath)) {
|
|
1044
|
-
return;
|
|
1045
|
-
}
|
|
1046
|
-
|
|
1047
|
-
let hostParsed: JsonObject;
|
|
1048
|
-
try {
|
|
1049
|
-
hostParsed = toml.parse(fs.readFileSync(hostConfigPath, 'utf8')) as JsonObject;
|
|
1050
|
-
} catch {
|
|
1051
|
-
return;
|
|
1052
|
-
}
|
|
1053
|
-
|
|
1054
|
-
const sandboxConfigPath = path.join(toolDir, 'config.toml');
|
|
1055
|
-
// This rewrites sandbox-side TOML and drops comments; the host config stays untouched.
|
|
1056
|
-
let sandboxParsed: JsonObject = {};
|
|
1057
|
-
if (fs.existsSync(sandboxConfigPath)) {
|
|
1058
|
-
try {
|
|
1059
|
-
sandboxParsed = toml.parse(fs.readFileSync(sandboxConfigPath, 'utf8')) as JsonObject;
|
|
1060
|
-
} catch {
|
|
1061
|
-
return;
|
|
1062
|
-
}
|
|
1063
|
-
}
|
|
1064
|
-
|
|
1065
|
-
let changed = removeCodexMcpServers(sandboxParsed);
|
|
1066
|
-
changed = inheritDisabledCodexFeatureFlags(sandboxParsed, hostParsed) || changed;
|
|
1067
|
-
|
|
1068
|
-
const inheritSpecs: Array<readonly [string, 'string' | 'number']> = [
|
|
1069
|
-
['model', 'string'],
|
|
1070
|
-
['model_reasoning_effort', 'string'],
|
|
1071
|
-
['model_auto_compact_token_limit', 'number']
|
|
1072
|
-
];
|
|
1073
|
-
|
|
1074
|
-
for (const [key, type] of inheritSpecs) {
|
|
1075
|
-
if (Object.hasOwn(sandboxParsed, key)) {
|
|
1076
|
-
continue;
|
|
1077
|
-
}
|
|
1078
|
-
const value = hostParsed[key];
|
|
1079
|
-
if (type === 'string' && (typeof value !== 'string' || value === '')) {
|
|
1080
|
-
continue;
|
|
1081
|
-
}
|
|
1082
|
-
if (type === 'number' && (typeof value !== 'number' || !Number.isFinite(value) || value <= 0)) {
|
|
1083
|
-
continue;
|
|
1084
|
-
}
|
|
1085
|
-
sandboxParsed[key] = value;
|
|
1086
|
-
changed = true;
|
|
1087
|
-
}
|
|
1088
|
-
|
|
1089
|
-
if (!Object.hasOwn(sandboxParsed, 'model_catalog_json')) {
|
|
1090
|
-
const hostCatalogPath = resolveHostCatalogPath(hostParsed['model_catalog_json'], hostHomeDir);
|
|
1091
|
-
if (hostCatalogPath) {
|
|
1092
|
-
try {
|
|
1093
|
-
const basename = path.basename(hostCatalogPath);
|
|
1094
|
-
const destDir = path.join(toolDir, 'model-catalogs');
|
|
1095
|
-
fs.mkdirSync(destDir, { recursive: true });
|
|
1096
|
-
fs.copyFileSync(hostCatalogPath, path.join(destDir, basename));
|
|
1097
|
-
sandboxParsed['model_catalog_json'] = path.posix.join(containerCodexDir, 'model-catalogs', basename);
|
|
1098
|
-
changed = true;
|
|
1099
|
-
} catch {
|
|
1100
|
-
// Copy failed (e.g. permissions): skip catalog, keep scalar inheritance intact.
|
|
1101
|
-
}
|
|
1102
|
-
}
|
|
1103
|
-
}
|
|
1104
|
-
|
|
1105
|
-
if (changed) {
|
|
1106
|
-
fs.writeFileSync(sandboxConfigPath, `${toml.stringify(sandboxParsed)}\n`, 'utf8');
|
|
1107
|
-
}
|
|
1108
|
-
}
|
|
1109
|
-
|
|
1110
|
-
export function ensureCodexWorkspaceTrust(toolDir: string): void {
|
|
1111
|
-
const configPath = path.join(toolDir, 'config.toml');
|
|
1112
|
-
let content = '';
|
|
1113
|
-
if (fs.existsSync(configPath)) {
|
|
1114
|
-
content = fs.readFileSync(configPath, 'utf8');
|
|
1115
|
-
}
|
|
1116
|
-
if (!content.includes('[projects."/workspace"]')) {
|
|
1117
|
-
const entry = '\n[projects."/workspace"]\ntrust_level = "trusted"\n';
|
|
1118
|
-
fs.writeFileSync(configPath, content + entry, 'utf8');
|
|
1119
|
-
}
|
|
1120
|
-
}
|
|
1121
|
-
|
|
1122
975
|
export function ensureOpenCodeModelInheritance(toolDir: string, hostHomeDir?: string): void {
|
|
1123
976
|
if (!hostHomeDir) {
|
|
1124
977
|
return;
|
|
@@ -1189,7 +1042,10 @@ function stripManagedSandboxAliasBlocks(content: string): string {
|
|
|
1189
1042
|
return content.replace(blockPattern, '').trimEnd();
|
|
1190
1043
|
}
|
|
1191
1044
|
|
|
1192
|
-
function isLegacyManagedSandboxAliasFile(
|
|
1045
|
+
function isLegacyManagedSandboxAliasFile(
|
|
1046
|
+
content: string,
|
|
1047
|
+
aliases: readonly SandboxAlias[]
|
|
1048
|
+
): boolean {
|
|
1193
1049
|
const lines = content
|
|
1194
1050
|
.split(/\r?\n/)
|
|
1195
1051
|
.map((line) => line.trim())
|
|
@@ -1199,13 +1055,29 @@ function isLegacyManagedSandboxAliasFile(content: string): boolean {
|
|
|
1199
1055
|
return false;
|
|
1200
1056
|
}
|
|
1201
1057
|
|
|
1202
|
-
const aliasPattern = new RegExp(
|
|
1058
|
+
const aliasPattern = new RegExp(
|
|
1059
|
+
`^alias (${aliases.map((alias) => escapeRegExp(alias.name)).join('|')})=`
|
|
1060
|
+
);
|
|
1203
1061
|
return lines.every((line) => aliasPattern.test(line));
|
|
1204
1062
|
}
|
|
1205
1063
|
|
|
1206
|
-
|
|
1064
|
+
function registrySandboxAliases(): readonly SandboxAlias[] {
|
|
1065
|
+
return listAgentClientAdapters().flatMap((adapter) => adapter.sandbox.aliases);
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
function renderSandboxAliases(aliases: readonly SandboxAlias[]): string {
|
|
1069
|
+
return `${aliases.map((alias) =>
|
|
1070
|
+
`alias ${alias.name}='${alias.command.replaceAll("'", "'\\''")}'`
|
|
1071
|
+
).join('\n')}\n`;
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
export function ensureSandboxAliasesFile(
|
|
1075
|
+
home: string,
|
|
1076
|
+
aliases: readonly SandboxAlias[] = registrySandboxAliases()
|
|
1077
|
+
): { created: boolean; path: string } {
|
|
1207
1078
|
const aliasesPath = sandboxAliasesPath(home);
|
|
1208
|
-
const managedBlock =
|
|
1079
|
+
const managedBlock =
|
|
1080
|
+
`${SANDBOX_ALIAS_BLOCK_BEGIN}\n${renderSandboxAliases(aliases)}${SANDBOX_ALIAS_BLOCK_END}\n`;
|
|
1209
1081
|
fs.mkdirSync(path.dirname(aliasesPath), { recursive: true });
|
|
1210
1082
|
const created = !fs.existsSync(aliasesPath);
|
|
1211
1083
|
let existing = '';
|
|
@@ -1214,7 +1086,7 @@ export function ensureSandboxAliasesFile(home: string): { created: boolean; path
|
|
|
1214
1086
|
existing = fs.readFileSync(aliasesPath, 'utf8');
|
|
1215
1087
|
}
|
|
1216
1088
|
|
|
1217
|
-
const userContent = isLegacyManagedSandboxAliasFile(existing)
|
|
1089
|
+
const userContent = isLegacyManagedSandboxAliasFile(existing, aliases)
|
|
1218
1090
|
? ''
|
|
1219
1091
|
: stripManagedSandboxAliasBlocks(existing);
|
|
1220
1092
|
const nextContent = userContent
|
|
@@ -1355,19 +1227,18 @@ export async function create(args: string[]): Promise<void> {
|
|
|
1355
1227
|
};
|
|
1356
1228
|
const worktreeCandidates = worktreeDirCandidates(effectiveConfig, branch);
|
|
1357
1229
|
assertBranchAvailable(config.repoRoot, branch, { allowedWorktrees: worktreeCandidates });
|
|
1358
|
-
const
|
|
1230
|
+
const capabilityPlan = createSandboxCapabilityPlan(effectiveConfig);
|
|
1231
|
+
const tools = [...capabilityPlan.tools];
|
|
1359
1232
|
const resolvedTools = resolveToolDirs(effectiveConfig, tools, branch);
|
|
1360
1233
|
// Fatal credential states still fail before filesystem/docker side effects.
|
|
1361
|
-
//
|
|
1362
|
-
//
|
|
1234
|
+
// Missing credentials leave the selected Claude Code capability installed
|
|
1235
|
+
// and mounted; only the absent live credential file is omitted.
|
|
1363
1236
|
const credentialOutcome = prepareClaudeCredentials(
|
|
1364
1237
|
effectiveConfig.home,
|
|
1365
1238
|
effectiveConfig.project,
|
|
1366
1239
|
resolvedTools
|
|
1367
1240
|
);
|
|
1368
|
-
const effectiveResolvedTools =
|
|
1369
|
-
? resolvedTools.filter(({ tool }) => tool.id !== 'claude-code')
|
|
1370
|
-
: resolvedTools;
|
|
1241
|
+
const effectiveResolvedTools = resolvedTools;
|
|
1371
1242
|
const container = containerName(effectiveConfig, branch);
|
|
1372
1243
|
const worktree = worktreeCandidates.find((candidate) => fs.existsSync(candidate)) ?? worktreeCandidates[0] ?? '';
|
|
1373
1244
|
const shareCommon = shareCommonDir(effectiveConfig);
|
|
@@ -1392,6 +1263,16 @@ export async function create(args: string[]): Promise<void> {
|
|
|
1392
1263
|
|
|
1393
1264
|
try {
|
|
1394
1265
|
p.log.step('Checking container engine...');
|
|
1266
|
+
const prepareHookResults = await runSandboxHooks({
|
|
1267
|
+
hooks: capabilityPlan.hooksByPhase.prepare,
|
|
1268
|
+
phase: 'prepare',
|
|
1269
|
+
context: { config: effectiveConfig, plan: capabilityPlan },
|
|
1270
|
+
runCommand: runBoundedSandboxHookCommand
|
|
1271
|
+
});
|
|
1272
|
+
const prepareFailure = prepareHookResults.find((result) => result.status === 'fatal');
|
|
1273
|
+
if (prepareFailure) {
|
|
1274
|
+
throw new Error(prepareFailure.message ?? `Sandbox hook '${prepareFailure.hookId}' failed.`);
|
|
1275
|
+
}
|
|
1395
1276
|
await ensureDocker(effectiveConfig, (detail: string) => {
|
|
1396
1277
|
p.log.info(` ${detail}`);
|
|
1397
1278
|
});
|
|
@@ -1476,7 +1357,7 @@ export async function create(args: string[]): Promise<void> {
|
|
|
1476
1357
|
if (fs.readdirSync(worktree).length > 0) {
|
|
1477
1358
|
return `Worktree exists at ${worktree}`;
|
|
1478
1359
|
}
|
|
1479
|
-
|
|
1360
|
+
removeDirRecursive(worktree);
|
|
1480
1361
|
}
|
|
1481
1362
|
|
|
1482
1363
|
const branchExists = runOk('git', [
|
|
@@ -1573,7 +1454,10 @@ export async function create(args: string[]): Promise<void> {
|
|
|
1573
1454
|
}
|
|
1574
1455
|
}
|
|
1575
1456
|
|
|
1576
|
-
const aliasesFile = ensureSandboxAliasesFile(
|
|
1457
|
+
const aliasesFile = ensureSandboxAliasesFile(
|
|
1458
|
+
effectiveConfig.home,
|
|
1459
|
+
capabilityPlan.aliases
|
|
1460
|
+
);
|
|
1577
1461
|
if (aliasesFile.created) {
|
|
1578
1462
|
message(`Created default sandbox aliases at ${aliasesFile.path}`);
|
|
1579
1463
|
}
|
|
@@ -1609,6 +1493,28 @@ export async function create(args: string[]): Promise<void> {
|
|
|
1609
1493
|
let hostShellConfig: HostShellConfig;
|
|
1610
1494
|
let tmpfsSeedPlan: TmpfsSeedPlanEntry[] = [];
|
|
1611
1495
|
try {
|
|
1496
|
+
const beforeCreateResults = await runSandboxHooks({
|
|
1497
|
+
hooks: capabilityPlan.hooksByPhase['before-container-create'],
|
|
1498
|
+
phase: 'before-container-create',
|
|
1499
|
+
context: {
|
|
1500
|
+
config: effectiveConfig,
|
|
1501
|
+
plan: capabilityPlan,
|
|
1502
|
+
create: {
|
|
1503
|
+
hostHome: effectiveConfig.home,
|
|
1504
|
+
resolvedTools: effectiveResolvedTools
|
|
1505
|
+
}
|
|
1506
|
+
},
|
|
1507
|
+
runCommand: runBoundedSandboxHookCommand
|
|
1508
|
+
});
|
|
1509
|
+
const beforeCreateFailure = beforeCreateResults.find(
|
|
1510
|
+
(result) => result.status === 'fatal'
|
|
1511
|
+
);
|
|
1512
|
+
if (beforeCreateFailure) {
|
|
1513
|
+
throw new Error(
|
|
1514
|
+
beforeCreateFailure.message
|
|
1515
|
+
?? `Sandbox hook '${beforeCreateFailure.hookId}' failed.`
|
|
1516
|
+
);
|
|
1517
|
+
}
|
|
1612
1518
|
const claudeCodeEntry = effectiveResolvedTools.find(({ tool }) => tool.id === 'claude-code');
|
|
1613
1519
|
if (claudeCodeEntry) {
|
|
1614
1520
|
ensureClaudeOnboarding(claudeCodeEntry.dir, effectiveConfig.home);
|
|
@@ -1617,11 +1523,6 @@ export async function create(args: string[]): Promise<void> {
|
|
|
1617
1523
|
// provider/API settings are enough for Claude Code. If no usable
|
|
1618
1524
|
// auth was present, the claude-code entry was removed above.
|
|
1619
1525
|
}
|
|
1620
|
-
const codexEntry = effectiveResolvedTools.find(({ tool }) => tool.id === 'codex');
|
|
1621
|
-
if (codexEntry) {
|
|
1622
|
-
ensureCodexModelInheritance(codexEntry.dir, effectiveConfig.home, codexEntry.tool.containerMount);
|
|
1623
|
-
ensureCodexWorkspaceTrust(codexEntry.dir);
|
|
1624
|
-
}
|
|
1625
1526
|
const geminiEntry = effectiveResolvedTools.find(({ tool }) => tool.id === 'gemini-cli');
|
|
1626
1527
|
if (geminiEntry) {
|
|
1627
1528
|
ensureGeminiWorkspaceTrust(geminiEntry.dir);
|
|
@@ -1709,6 +1610,8 @@ export async function create(args: string[]): Promise<void> {
|
|
|
1709
1610
|
sandboxLabel(effectiveConfig),
|
|
1710
1611
|
'--label',
|
|
1711
1612
|
`${sandboxBranchLabel(effectiveConfig)}=${branch}`,
|
|
1613
|
+
'--label',
|
|
1614
|
+
`${sandboxRuntimeCapabilityLabel(effectiveConfig)}=${capabilityPlan.runtimeSignature}`,
|
|
1712
1615
|
...coreVolumes,
|
|
1713
1616
|
...buildClipboardVolumeArgs(engine, effectiveConfig.home),
|
|
1714
1617
|
'-v',
|
|
@@ -1805,6 +1708,22 @@ export async function create(args: string[]): Promise<void> {
|
|
|
1805
1708
|
}
|
|
1806
1709
|
}
|
|
1807
1710
|
|
|
1711
|
+
const afterStartResults = await runSandboxHooks({
|
|
1712
|
+
hooks: capabilityPlan.hooksByPhase['after-container-start'],
|
|
1713
|
+
phase: 'after-container-start',
|
|
1714
|
+
context: { config: effectiveConfig, plan: capabilityPlan },
|
|
1715
|
+
runCommand: runBoundedSandboxHookCommand
|
|
1716
|
+
});
|
|
1717
|
+
const afterStartFailure = afterStartResults.find(
|
|
1718
|
+
(result) => result.status === 'fatal'
|
|
1719
|
+
);
|
|
1720
|
+
if (afterStartFailure) {
|
|
1721
|
+
throw new Error(
|
|
1722
|
+
afterStartFailure.message
|
|
1723
|
+
?? `Sandbox hook '${afterStartFailure.hookId}' failed.`
|
|
1724
|
+
);
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1808
1727
|
return 'Container started';
|
|
1809
1728
|
}
|
|
1810
1729
|
}
|
|
@@ -7,6 +7,11 @@ import {
|
|
|
7
7
|
} from '../constants.ts';
|
|
8
8
|
import { detectEngine } from '../engine.ts';
|
|
9
9
|
import { ensureSandboxReady } from '../recovery.ts';
|
|
10
|
+
import {
|
|
11
|
+
createSandboxCapabilityPlan,
|
|
12
|
+
runBoundedSandboxHookCommand,
|
|
13
|
+
runSandboxHooks
|
|
14
|
+
} from '../agent-client-reconciler.ts';
|
|
10
15
|
import {
|
|
11
16
|
formatCredentialWarnings,
|
|
12
17
|
formatRemaining,
|
|
@@ -186,8 +191,25 @@ export async function enter(args: string[]): Promise<number> {
|
|
|
186
191
|
});
|
|
187
192
|
const container = ready.container;
|
|
188
193
|
const cmd = parsed.command;
|
|
194
|
+
const capabilityPlan = createSandboxCapabilityPlan(config);
|
|
195
|
+
const enterHookResults = await runSandboxHooks({
|
|
196
|
+
hooks: capabilityPlan.hooksByPhase['before-enter'],
|
|
197
|
+
phase: 'before-enter',
|
|
198
|
+
context: { config, plan: capabilityPlan },
|
|
199
|
+
runCommand: runBoundedSandboxHookCommand
|
|
200
|
+
});
|
|
201
|
+
for (const result of enterHookResults) {
|
|
202
|
+
if (result.status === 'fatal') {
|
|
203
|
+
throw new Error(result.message ?? `Sandbox hook '${result.hookId}' failed.`);
|
|
204
|
+
}
|
|
205
|
+
if (result.status === 'warning') {
|
|
206
|
+
process.stderr.write(
|
|
207
|
+
`Warning: ${result.message ?? `Sandbox hook '${result.hookId}' did not complete.`}\n`
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
189
211
|
|
|
190
|
-
if (
|
|
212
|
+
if (capabilityPlan.selectedAgentClients.some((adapter) => adapter.id === 'claude-code')) {
|
|
191
213
|
try {
|
|
192
214
|
// Scan all projects so a refresh from a neighbouring sandbox can still flow back to the host.
|
|
193
215
|
const providerAuthAvailable = hasClaudeProviderAuth(config.home);
|
|
@@ -11,7 +11,7 @@ import { hostJoin } from '../engines/wsl2-paths.ts';
|
|
|
11
11
|
import { removeManagedDir, removeWorktreeDir } from '../managed-fs.ts';
|
|
12
12
|
import { parseLabels } from './ls.ts';
|
|
13
13
|
import { runEngine, runSafe } from '../shell.ts';
|
|
14
|
-
import {
|
|
14
|
+
import { createSandboxCapabilityPlan } from '../agent-client-reconciler.ts';
|
|
15
15
|
import type { SandboxTool } from '../tools.ts';
|
|
16
16
|
|
|
17
17
|
const USAGE = `Usage: ai sandbox prune [--dry-run]`;
|
|
@@ -158,7 +158,7 @@ export async function prune(args: string[]): Promise<void> {
|
|
|
158
158
|
}
|
|
159
159
|
|
|
160
160
|
const config = loadConfig();
|
|
161
|
-
const tools =
|
|
161
|
+
const tools = [...createSandboxCapabilityPlan(config).cleanupInventory];
|
|
162
162
|
const engine = detectEngine(config);
|
|
163
163
|
const psArgs = [
|
|
164
164
|
'ps',
|
|
@@ -19,7 +19,8 @@ import { pruneSandboxDanglingImages } from '../image-prune.ts';
|
|
|
19
19
|
import { removeManagedDir, removeWorktreeDir } from '../managed-fs.ts';
|
|
20
20
|
import { runOk, runSafe, runSafeEngine } from '../shell.ts';
|
|
21
21
|
import { resolveTaskBranch } from '../task-resolver.ts';
|
|
22
|
-
import {
|
|
22
|
+
import { toolConfigDirCandidates, toolProjectDirCandidates } from '../tools.ts';
|
|
23
|
+
import { createSandboxCapabilityPlan } from '../agent-client-reconciler.ts';
|
|
23
24
|
import type { SandboxTool } from '../tools.ts';
|
|
24
25
|
import { fetchSandboxRows } from './list-running.ts';
|
|
25
26
|
import { lookupShortIdByBranch } from '../../task/short-id.ts';
|
|
@@ -353,7 +354,7 @@ export async function rm(args: string[]): Promise<void> {
|
|
|
353
354
|
}
|
|
354
355
|
|
|
355
356
|
const config = loadConfig();
|
|
356
|
-
const tools =
|
|
357
|
+
const tools = [...createSandboxCapabilityPlan(config).cleanupInventory];
|
|
357
358
|
|
|
358
359
|
if (values.purge) {
|
|
359
360
|
await rmPurge(config, tools);
|
package/lib/sandbox/config.ts
CHANGED
|
@@ -3,6 +3,9 @@ import path from 'node:path';
|
|
|
3
3
|
import { homedir, platform } from 'node:os';
|
|
4
4
|
import { execFileSync } from 'node:child_process';
|
|
5
5
|
import pc from 'picocolors';
|
|
6
|
+
import { normalizeAgentClients } from '../agent-clients/config.ts';
|
|
7
|
+
import { isAgentClientId } from '../agent-clients/types.ts';
|
|
8
|
+
import type { AgentClientState } from '../agent-clients/types.ts';
|
|
6
9
|
import { validateSandboxEngine } from './engine.ts';
|
|
7
10
|
import { hostJoin } from './engines/wsl2-paths.ts';
|
|
8
11
|
import { findRuntimeEngineMismatches } from './runtime-engines.ts';
|
|
@@ -12,7 +15,7 @@ import type { SandboxTool } from './tools.ts';
|
|
|
12
15
|
const DEFAULTS = Object.freeze({
|
|
13
16
|
engine: null,
|
|
14
17
|
runtimes: ['node22'],
|
|
15
|
-
tools: ['agent-infra'
|
|
18
|
+
tools: ['agent-infra'],
|
|
16
19
|
refreshIntervalDays: 7,
|
|
17
20
|
dockerfile: null,
|
|
18
21
|
vm: {
|
|
@@ -57,6 +60,8 @@ export type SandboxConfig = {
|
|
|
57
60
|
runtimes: string[];
|
|
58
61
|
tools: string[];
|
|
59
62
|
customTools: SandboxTool[];
|
|
63
|
+
agentClientState: AgentClientState;
|
|
64
|
+
agentClientSource: 'canonical' | 'legacy';
|
|
60
65
|
refreshIntervalDays: number;
|
|
61
66
|
dockerfile: string | null;
|
|
62
67
|
vm: SandboxVmConfig;
|
|
@@ -65,6 +70,8 @@ export type SandboxConfig = {
|
|
|
65
70
|
type AircConfig = {
|
|
66
71
|
project?: unknown;
|
|
67
72
|
org?: unknown;
|
|
73
|
+
agentClients?: unknown;
|
|
74
|
+
tuis?: unknown;
|
|
68
75
|
sandbox?: SandboxConfigInput;
|
|
69
76
|
};
|
|
70
77
|
|
|
@@ -98,6 +105,23 @@ function cloneDefaults(): SandboxConfigInput & { vm: SandboxVmConfig; runtimes:
|
|
|
98
105
|
};
|
|
99
106
|
}
|
|
100
107
|
|
|
108
|
+
function resolveSandboxToolIds(
|
|
109
|
+
sandbox: SandboxConfigInput,
|
|
110
|
+
agentClients: ReturnType<typeof normalizeAgentClients>,
|
|
111
|
+
defaults: readonly string[]
|
|
112
|
+
): string[] {
|
|
113
|
+
if (Array.isArray(sandbox.tools)) {
|
|
114
|
+
const tools = sandbox.tools.filter((tool): tool is string => typeof tool === 'string');
|
|
115
|
+
return agentClients.source === 'canonical'
|
|
116
|
+
? tools.filter((tool) => !isAgentClientId(tool))
|
|
117
|
+
: tools;
|
|
118
|
+
}
|
|
119
|
+
if (agentClients.remainingSandboxTools !== undefined) {
|
|
120
|
+
return [...agentClients.remainingSandboxTools];
|
|
121
|
+
}
|
|
122
|
+
return [...defaults];
|
|
123
|
+
}
|
|
124
|
+
|
|
101
125
|
export function loadConfig({
|
|
102
126
|
platformFn = platform,
|
|
103
127
|
writeStderr = (chunk) => process.stderr.write(chunk)
|
|
@@ -115,6 +139,8 @@ export function loadConfig({
|
|
|
115
139
|
}
|
|
116
140
|
|
|
117
141
|
const airc = JSON.parse(fs.readFileSync(configPath, 'utf8')) as AircConfig;
|
|
142
|
+
const sandboxInput = airc.sandbox;
|
|
143
|
+
const agentClients = normalizeAgentClients(airc);
|
|
118
144
|
const defaults = cloneDefaults();
|
|
119
145
|
const sandbox = airc.sandbox ?? {};
|
|
120
146
|
const engine = validateSandboxEngine(sandbox.engine ?? defaults.engine, { platformFn });
|
|
@@ -149,6 +175,7 @@ export function loadConfig({
|
|
|
149
175
|
}
|
|
150
176
|
|
|
151
177
|
const customTools = parseCustomTools(sandbox.customTools, { home });
|
|
178
|
+
const tools = resolveSandboxToolIds(sandbox, agentClients, defaults.tools);
|
|
152
179
|
|
|
153
180
|
return {
|
|
154
181
|
repoRoot,
|
|
@@ -164,10 +191,10 @@ export function loadConfig({
|
|
|
164
191
|
dotfilesDir: hostJoin(home, '.agent-infra', 'dotfiles'),
|
|
165
192
|
engine,
|
|
166
193
|
runtimes,
|
|
167
|
-
tools
|
|
168
|
-
? [...sandbox.tools]
|
|
169
|
-
: defaults.tools,
|
|
194
|
+
tools,
|
|
170
195
|
customTools,
|
|
196
|
+
agentClientState: agentClients.state,
|
|
197
|
+
agentClientSource: agentClients.source,
|
|
171
198
|
refreshIntervalDays: asNonNegativeIntegerOrDefault(
|
|
172
199
|
sandbox.refreshIntervalDays,
|
|
173
200
|
defaults.refreshIntervalDays
|
package/lib/sandbox/constants.ts
CHANGED
|
@@ -111,6 +111,12 @@ export function sandboxImageRefreshLabel(config: Pick<SandboxPathConfig, 'projec
|
|
|
111
111
|
return `${sandboxLabel(config)}.last-refresh`;
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
+
export function sandboxRuntimeCapabilityLabel(
|
|
115
|
+
config: Pick<SandboxPathConfig, 'project'>
|
|
116
|
+
): string {
|
|
117
|
+
return `agent-infra.sandbox.runtime-capability.${config.project}`;
|
|
118
|
+
}
|
|
119
|
+
|
|
114
120
|
export function parsePositiveIntegerOption(value: unknown, optionName: string): number | undefined {
|
|
115
121
|
if (value === undefined || value === null) {
|
|
116
122
|
return undefined;
|
|
@@ -3,6 +3,7 @@ import os from 'node:os';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { createHash } from 'node:crypto';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { removeDirRecursive } from '../remove-dir.ts';
|
|
6
7
|
|
|
7
8
|
const RUNTIMES_DIR = path.join(
|
|
8
9
|
path.dirname(fileURLToPath(import.meta.url)),
|
|
@@ -88,7 +89,7 @@ export function prepareDockerfile(config: DockerfileConfig): { path: string; sig
|
|
|
88
89
|
path: tempPath,
|
|
89
90
|
signature: dockerfileSignature(config),
|
|
90
91
|
cleanup() {
|
|
91
|
-
|
|
92
|
+
removeDirRecursive(tempDir);
|
|
92
93
|
}
|
|
93
94
|
};
|
|
94
95
|
}
|