@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
|
@@ -6,14 +6,15 @@ import { execFileSync } from 'node:child_process';
|
|
|
6
6
|
import { parseArgs } from 'node:util';
|
|
7
7
|
import * as p from '@clack/prompts';
|
|
8
8
|
import pc from 'picocolors';
|
|
9
|
-
import
|
|
9
|
+
import { listAgentClientAdapters } from '../../agent-clients/registry.js';
|
|
10
10
|
import { loadConfig } from '../config.js';
|
|
11
|
-
import { assertValidBranchName, containerName, containerNameCandidates, parsePositiveIntegerOption, sandboxBranchLabel, sandboxImageConfigLabel, sandboxImageRefreshLabel, sandboxLabel, shareBranchDir, shareCommonDir, shellConfigDir, worktreeDirCandidates } from '../constants.js';
|
|
11
|
+
import { assertValidBranchName, containerName, containerNameCandidates, parsePositiveIntegerOption, sandboxBranchLabel, sandboxImageConfigLabel, sandboxImageRefreshLabel, sandboxLabel, sandboxRuntimeCapabilityLabel, shareBranchDir, shareCommonDir, shellConfigDir, worktreeDirCandidates } from '../constants.js';
|
|
12
12
|
import { prepareDockerfile } from '../dockerfile.js';
|
|
13
13
|
import { assertBuildProxyCompatibility, buildProxyFailureHint, prepareBuildProxy, redactBuildProxyValues } from '../build-proxy.js';
|
|
14
14
|
import { detectEngine, ensureDocker } from '../engine.js';
|
|
15
15
|
import { commandForEngine, execEngine, run, runEngine, runOk, runOkEngine, runSafe, runSafeEngine, runVerboseEngine } from '../shell.js';
|
|
16
16
|
import { resolveTaskBranch } from '../task-resolver.js';
|
|
17
|
+
import { createSandboxCapabilityPlan, runSandboxHooks, runBoundedSandboxHookCommand } from '../agent-client-reconciler.js';
|
|
17
18
|
import { resolveTools, tmpfsSeedStagingPath, tmpfsSeedTargetPath, toolConfigDirCandidates } from '../tools.js';
|
|
18
19
|
import { assertFreshSandboxReady, hydrateTmpfsSeedEntries, prepareTmpfsMounts } from '../recovery.js';
|
|
19
20
|
import { hostJoin, toEnginePath, volumeArg } from '../engines/wsl2-paths.js';
|
|
@@ -22,32 +23,13 @@ import { clipboardHostDir, CONTAINER_CLIPBOARD_MOUNT } from '../clipboard/paths.
|
|
|
22
23
|
import { validateSelinuxDisableEnv } from '../engines/selinux.js';
|
|
23
24
|
import { dotfilesCacheDir, materializeDotfiles } from '../dotfiles.js';
|
|
24
25
|
import { ensureSandboxDiscoveryReadmes } from '../readme-scaffold.js';
|
|
26
|
+
import { removeDirRecursive } from '../../remove-dir.js';
|
|
25
27
|
import { prepareClaudeCredentials, redactCommandError, validateClaudeCredentialsEnvOverride } from '../credentials.js';
|
|
26
28
|
import { detectHostTimezone } from '../host-timezone.js';
|
|
27
29
|
import { buildImageSignature, buildSandboxImageArgs, isRefreshDisabled, isRefreshDue, parseImageLabels, parseRefreshTimestamp } from '../image-build.js';
|
|
28
|
-
|
|
30
|
+
export { ensureCodexModelInheritance, ensureCodexWorkspaceTrust } from '../../agent-clients/adapters/codex-sandbox.js';
|
|
29
31
|
const SANDBOX_ALIAS_BLOCK_BEGIN = '# >>> agent-infra managed aliases >>>';
|
|
30
32
|
const SANDBOX_ALIAS_BLOCK_END = '# <<< agent-infra managed aliases <<<';
|
|
31
|
-
const SANDBOX_ALIAS_NAMES = [
|
|
32
|
-
'claude-yolo',
|
|
33
|
-
'opencode-yolo',
|
|
34
|
-
'codex-yolo',
|
|
35
|
-
'gemini-yolo',
|
|
36
|
-
'cy',
|
|
37
|
-
'oy',
|
|
38
|
-
'xy',
|
|
39
|
-
'gy'
|
|
40
|
-
];
|
|
41
|
-
const DEFAULT_SANDBOX_ALIASES = `alias claude-yolo='claude --dangerously-skip-permissions; tput ed'
|
|
42
|
-
alias opencode-yolo='OPENCODE_PERMISSION='\\''${OPENCODE_YOLO_PERMISSION}'\\'' opencode; tput ed'
|
|
43
|
-
alias codex-yolo='codex --yolo; tput ed'
|
|
44
|
-
alias gemini-yolo='gemini --yolo; tput ed'
|
|
45
|
-
|
|
46
|
-
alias cy='claude --dangerously-skip-permissions; tput ed'
|
|
47
|
-
alias oy='OPENCODE_PERMISSION='\\''${OPENCODE_YOLO_PERMISSION}'\\'' opencode; tput ed'
|
|
48
|
-
alias xy='codex --yolo; tput ed'
|
|
49
|
-
alias gy='gemini --yolo; tput ed'
|
|
50
|
-
`;
|
|
51
33
|
const CONTAINER_HOME = '/home/devuser';
|
|
52
34
|
const CONTAINER_SHELL_CONFIG_MOUNT = `${CONTAINER_HOME}/.host-shell-config`;
|
|
53
35
|
const USAGE = `Usage: ai sandbox create <branch> [base] [--cpu <n>] [--memory <n>] [--no-refresh] [--inherit-proxy|-P] [--inherit-build-proxy|-B]
|
|
@@ -268,7 +250,7 @@ export function ensureShellConfigSymlinks(engine, container, execFn = execEngine
|
|
|
268
250
|
}
|
|
269
251
|
export function prepareHostShellConfig({ home, project, branch, repoRoot }) {
|
|
270
252
|
const hostDir = hostShellConfigDir(home, project, branch);
|
|
271
|
-
|
|
253
|
+
removeDirRecursive(hostDir);
|
|
272
254
|
fs.mkdirSync(hostDir, { recursive: true });
|
|
273
255
|
writeSanitizedGitconfig({
|
|
274
256
|
home,
|
|
@@ -306,11 +288,19 @@ function normalizeWorktreePath(worktreePath) {
|
|
|
306
288
|
if (!worktreePath) {
|
|
307
289
|
return '';
|
|
308
290
|
}
|
|
291
|
+
const wslMount = process.platform === 'win32'
|
|
292
|
+
? worktreePath.match(/^\/mnt\/([A-Za-z])(?:\/(.*))?$/)
|
|
293
|
+
: null;
|
|
294
|
+
const hostPath = wslMount
|
|
295
|
+
? `${wslMount[1].toUpperCase()}:\\${(wslMount[2] ?? '').replace(/\//g, '\\')}`
|
|
296
|
+
: worktreePath;
|
|
309
297
|
try {
|
|
310
|
-
|
|
298
|
+
const normalized = fs.existsSync(hostPath) ? fs.realpathSync(hostPath) : path.resolve(hostPath);
|
|
299
|
+
return process.platform === 'win32' ? normalized.toLowerCase() : normalized;
|
|
311
300
|
}
|
|
312
301
|
catch {
|
|
313
|
-
|
|
302
|
+
const normalized = path.resolve(hostPath);
|
|
303
|
+
return process.platform === 'win32' ? normalized.toLowerCase() : normalized;
|
|
314
304
|
}
|
|
315
305
|
}
|
|
316
306
|
export function getGitSigningKey({ home, repoPath = null, execFn = execFileSync } = {}) {
|
|
@@ -481,7 +471,7 @@ export function collectHostProxyEntries(env) {
|
|
|
481
471
|
});
|
|
482
472
|
}
|
|
483
473
|
export function buildContainerEnvFile(resolvedTools, engine, runSafeEngineFn = runSafeEngine, options = {}) {
|
|
484
|
-
const { additionalEntries = [], mkdtempFn = fs.mkdtempSync, writeFileFn = fs.writeFileSync, chmodFn = fs.chmodSync, rmFn =
|
|
474
|
+
const { additionalEntries = [], mkdtempFn = fs.mkdtempSync, writeFileFn = fs.writeFileSync, chmodFn = fs.chmodSync, rmFn = removeDirRecursive, tmpDir = os.tmpdir(), runSafeFn = runSafe } = options;
|
|
485
475
|
const entries = resolvedTools.flatMap(({ tool }) => Object.entries(tool.envVars ?? {}));
|
|
486
476
|
entries.push(...additionalEntries);
|
|
487
477
|
let ghToken = runSafeEngineFn(engine, 'gh', ['auth', 'token']);
|
|
@@ -705,141 +695,6 @@ export function ensureClaudeSettings(toolDir, hostHomeDir) {
|
|
|
705
695
|
fs.writeFileSync(settingsPath, JSON.stringify(data, null, 4), 'utf8');
|
|
706
696
|
}
|
|
707
697
|
}
|
|
708
|
-
function resolveHostCatalogPath(value, hostHomeDir) {
|
|
709
|
-
if (typeof value !== 'string' || value === '') {
|
|
710
|
-
return null;
|
|
711
|
-
}
|
|
712
|
-
let resolved;
|
|
713
|
-
if (value === '~' || value.startsWith('~/') || value.startsWith('~\\')) {
|
|
714
|
-
resolved = path.join(hostHomeDir, value.slice(1).replace(/^[/\\]+/, ''));
|
|
715
|
-
}
|
|
716
|
-
else if (path.isAbsolute(value)) {
|
|
717
|
-
resolved = value;
|
|
718
|
-
}
|
|
719
|
-
else {
|
|
720
|
-
resolved = path.join(hostHomeDir, '.codex', value);
|
|
721
|
-
}
|
|
722
|
-
try {
|
|
723
|
-
if (!fs.statSync(resolved).isFile()) {
|
|
724
|
-
return null;
|
|
725
|
-
}
|
|
726
|
-
fs.accessSync(resolved, fs.constants.R_OK);
|
|
727
|
-
return resolved;
|
|
728
|
-
}
|
|
729
|
-
catch {
|
|
730
|
-
return null;
|
|
731
|
-
}
|
|
732
|
-
}
|
|
733
|
-
const CODEX_DISABLED_FEATURE_FLAGS = ['apps', 'enable_mcp_apps'];
|
|
734
|
-
function removeCodexMcpServers(sandboxParsed) {
|
|
735
|
-
if (!Object.hasOwn(sandboxParsed, 'mcp_servers')) {
|
|
736
|
-
return false;
|
|
737
|
-
}
|
|
738
|
-
delete sandboxParsed.mcp_servers;
|
|
739
|
-
return true;
|
|
740
|
-
}
|
|
741
|
-
function inheritDisabledCodexFeatureFlags(sandboxParsed, hostParsed) {
|
|
742
|
-
if (!isJsonObjectRecord(hostParsed.features)) {
|
|
743
|
-
return false;
|
|
744
|
-
}
|
|
745
|
-
if (Object.hasOwn(sandboxParsed, 'features') && !isJsonObjectRecord(sandboxParsed.features)) {
|
|
746
|
-
return false;
|
|
747
|
-
}
|
|
748
|
-
let sandboxFeatures = sandboxParsed.features;
|
|
749
|
-
let changed = false;
|
|
750
|
-
for (const key of CODEX_DISABLED_FEATURE_FLAGS) {
|
|
751
|
-
if (hostParsed.features[key] !== false) {
|
|
752
|
-
continue;
|
|
753
|
-
}
|
|
754
|
-
if (!sandboxFeatures) {
|
|
755
|
-
sandboxFeatures = {};
|
|
756
|
-
sandboxParsed.features = sandboxFeatures;
|
|
757
|
-
}
|
|
758
|
-
if (sandboxFeatures[key] !== false) {
|
|
759
|
-
sandboxFeatures[key] = false;
|
|
760
|
-
changed = true;
|
|
761
|
-
}
|
|
762
|
-
}
|
|
763
|
-
return changed;
|
|
764
|
-
}
|
|
765
|
-
export function ensureCodexModelInheritance(toolDir, hostHomeDir, containerCodexDir = '/home/devuser/.codex') {
|
|
766
|
-
if (!hostHomeDir) {
|
|
767
|
-
return;
|
|
768
|
-
}
|
|
769
|
-
const hostConfigPath = path.join(hostHomeDir, '.codex', 'config.toml');
|
|
770
|
-
if (!fs.existsSync(hostConfigPath)) {
|
|
771
|
-
return;
|
|
772
|
-
}
|
|
773
|
-
let hostParsed;
|
|
774
|
-
try {
|
|
775
|
-
hostParsed = toml.parse(fs.readFileSync(hostConfigPath, 'utf8'));
|
|
776
|
-
}
|
|
777
|
-
catch {
|
|
778
|
-
return;
|
|
779
|
-
}
|
|
780
|
-
const sandboxConfigPath = path.join(toolDir, 'config.toml');
|
|
781
|
-
// This rewrites sandbox-side TOML and drops comments; the host config stays untouched.
|
|
782
|
-
let sandboxParsed = {};
|
|
783
|
-
if (fs.existsSync(sandboxConfigPath)) {
|
|
784
|
-
try {
|
|
785
|
-
sandboxParsed = toml.parse(fs.readFileSync(sandboxConfigPath, 'utf8'));
|
|
786
|
-
}
|
|
787
|
-
catch {
|
|
788
|
-
return;
|
|
789
|
-
}
|
|
790
|
-
}
|
|
791
|
-
let changed = removeCodexMcpServers(sandboxParsed);
|
|
792
|
-
changed = inheritDisabledCodexFeatureFlags(sandboxParsed, hostParsed) || changed;
|
|
793
|
-
const inheritSpecs = [
|
|
794
|
-
['model', 'string'],
|
|
795
|
-
['model_reasoning_effort', 'string'],
|
|
796
|
-
['model_auto_compact_token_limit', 'number']
|
|
797
|
-
];
|
|
798
|
-
for (const [key, type] of inheritSpecs) {
|
|
799
|
-
if (Object.hasOwn(sandboxParsed, key)) {
|
|
800
|
-
continue;
|
|
801
|
-
}
|
|
802
|
-
const value = hostParsed[key];
|
|
803
|
-
if (type === 'string' && (typeof value !== 'string' || value === '')) {
|
|
804
|
-
continue;
|
|
805
|
-
}
|
|
806
|
-
if (type === 'number' && (typeof value !== 'number' || !Number.isFinite(value) || value <= 0)) {
|
|
807
|
-
continue;
|
|
808
|
-
}
|
|
809
|
-
sandboxParsed[key] = value;
|
|
810
|
-
changed = true;
|
|
811
|
-
}
|
|
812
|
-
if (!Object.hasOwn(sandboxParsed, 'model_catalog_json')) {
|
|
813
|
-
const hostCatalogPath = resolveHostCatalogPath(hostParsed['model_catalog_json'], hostHomeDir);
|
|
814
|
-
if (hostCatalogPath) {
|
|
815
|
-
try {
|
|
816
|
-
const basename = path.basename(hostCatalogPath);
|
|
817
|
-
const destDir = path.join(toolDir, 'model-catalogs');
|
|
818
|
-
fs.mkdirSync(destDir, { recursive: true });
|
|
819
|
-
fs.copyFileSync(hostCatalogPath, path.join(destDir, basename));
|
|
820
|
-
sandboxParsed['model_catalog_json'] = path.posix.join(containerCodexDir, 'model-catalogs', basename);
|
|
821
|
-
changed = true;
|
|
822
|
-
}
|
|
823
|
-
catch {
|
|
824
|
-
// Copy failed (e.g. permissions): skip catalog, keep scalar inheritance intact.
|
|
825
|
-
}
|
|
826
|
-
}
|
|
827
|
-
}
|
|
828
|
-
if (changed) {
|
|
829
|
-
fs.writeFileSync(sandboxConfigPath, `${toml.stringify(sandboxParsed)}\n`, 'utf8');
|
|
830
|
-
}
|
|
831
|
-
}
|
|
832
|
-
export function ensureCodexWorkspaceTrust(toolDir) {
|
|
833
|
-
const configPath = path.join(toolDir, 'config.toml');
|
|
834
|
-
let content = '';
|
|
835
|
-
if (fs.existsSync(configPath)) {
|
|
836
|
-
content = fs.readFileSync(configPath, 'utf8');
|
|
837
|
-
}
|
|
838
|
-
if (!content.includes('[projects."/workspace"]')) {
|
|
839
|
-
const entry = '\n[projects."/workspace"]\ntrust_level = "trusted"\n';
|
|
840
|
-
fs.writeFileSync(configPath, content + entry, 'utf8');
|
|
841
|
-
}
|
|
842
|
-
}
|
|
843
698
|
export function ensureOpenCodeModelInheritance(toolDir, hostHomeDir) {
|
|
844
699
|
if (!hostHomeDir) {
|
|
845
700
|
return;
|
|
@@ -900,7 +755,7 @@ function stripManagedSandboxAliasBlocks(content) {
|
|
|
900
755
|
const blockPattern = new RegExp(`${escapeRegExp(SANDBOX_ALIAS_BLOCK_BEGIN)}[\\s\\S]*?${escapeRegExp(SANDBOX_ALIAS_BLOCK_END)}\\n?`, 'g');
|
|
901
756
|
return content.replace(blockPattern, '').trimEnd();
|
|
902
757
|
}
|
|
903
|
-
function isLegacyManagedSandboxAliasFile(content) {
|
|
758
|
+
function isLegacyManagedSandboxAliasFile(content, aliases) {
|
|
904
759
|
const lines = content
|
|
905
760
|
.split(/\r?\n/)
|
|
906
761
|
.map((line) => line.trim())
|
|
@@ -908,19 +763,25 @@ function isLegacyManagedSandboxAliasFile(content) {
|
|
|
908
763
|
if (lines.length === 0) {
|
|
909
764
|
return false;
|
|
910
765
|
}
|
|
911
|
-
const aliasPattern = new RegExp(`^alias (${
|
|
766
|
+
const aliasPattern = new RegExp(`^alias (${aliases.map((alias) => escapeRegExp(alias.name)).join('|')})=`);
|
|
912
767
|
return lines.every((line) => aliasPattern.test(line));
|
|
913
768
|
}
|
|
914
|
-
|
|
769
|
+
function registrySandboxAliases() {
|
|
770
|
+
return listAgentClientAdapters().flatMap((adapter) => adapter.sandbox.aliases);
|
|
771
|
+
}
|
|
772
|
+
function renderSandboxAliases(aliases) {
|
|
773
|
+
return `${aliases.map((alias) => `alias ${alias.name}='${alias.command.replaceAll("'", "'\\''")}'`).join('\n')}\n`;
|
|
774
|
+
}
|
|
775
|
+
export function ensureSandboxAliasesFile(home, aliases = registrySandboxAliases()) {
|
|
915
776
|
const aliasesPath = sandboxAliasesPath(home);
|
|
916
|
-
const managedBlock = `${SANDBOX_ALIAS_BLOCK_BEGIN}\n${
|
|
777
|
+
const managedBlock = `${SANDBOX_ALIAS_BLOCK_BEGIN}\n${renderSandboxAliases(aliases)}${SANDBOX_ALIAS_BLOCK_END}\n`;
|
|
917
778
|
fs.mkdirSync(path.dirname(aliasesPath), { recursive: true });
|
|
918
779
|
const created = !fs.existsSync(aliasesPath);
|
|
919
780
|
let existing = '';
|
|
920
781
|
if (!created) {
|
|
921
782
|
existing = fs.readFileSync(aliasesPath, 'utf8');
|
|
922
783
|
}
|
|
923
|
-
const userContent = isLegacyManagedSandboxAliasFile(existing)
|
|
784
|
+
const userContent = isLegacyManagedSandboxAliasFile(existing, aliases)
|
|
924
785
|
? ''
|
|
925
786
|
: stripManagedSandboxAliasBlocks(existing);
|
|
926
787
|
const nextContent = userContent
|
|
@@ -1018,15 +879,14 @@ export async function create(args) {
|
|
|
1018
879
|
};
|
|
1019
880
|
const worktreeCandidates = worktreeDirCandidates(effectiveConfig, branch);
|
|
1020
881
|
assertBranchAvailable(config.repoRoot, branch, { allowedWorktrees: worktreeCandidates });
|
|
1021
|
-
const
|
|
882
|
+
const capabilityPlan = createSandboxCapabilityPlan(effectiveConfig);
|
|
883
|
+
const tools = [...capabilityPlan.tools];
|
|
1022
884
|
const resolvedTools = resolveToolDirs(effectiveConfig, tools, branch);
|
|
1023
885
|
// Fatal credential states still fail before filesystem/docker side effects.
|
|
1024
|
-
//
|
|
1025
|
-
//
|
|
886
|
+
// Missing credentials leave the selected Claude Code capability installed
|
|
887
|
+
// and mounted; only the absent live credential file is omitted.
|
|
1026
888
|
const credentialOutcome = prepareClaudeCredentials(effectiveConfig.home, effectiveConfig.project, resolvedTools);
|
|
1027
|
-
const effectiveResolvedTools =
|
|
1028
|
-
? resolvedTools.filter(({ tool }) => tool.id !== 'claude-code')
|
|
1029
|
-
: resolvedTools;
|
|
889
|
+
const effectiveResolvedTools = resolvedTools;
|
|
1030
890
|
const container = containerName(effectiveConfig, branch);
|
|
1031
891
|
const worktree = worktreeCandidates.find((candidate) => fs.existsSync(candidate)) ?? worktreeCandidates[0] ?? '';
|
|
1032
892
|
const shareCommon = shareCommonDir(effectiveConfig);
|
|
@@ -1045,6 +905,16 @@ export async function create(args) {
|
|
|
1045
905
|
}
|
|
1046
906
|
try {
|
|
1047
907
|
p.log.step('Checking container engine...');
|
|
908
|
+
const prepareHookResults = await runSandboxHooks({
|
|
909
|
+
hooks: capabilityPlan.hooksByPhase.prepare,
|
|
910
|
+
phase: 'prepare',
|
|
911
|
+
context: { config: effectiveConfig, plan: capabilityPlan },
|
|
912
|
+
runCommand: runBoundedSandboxHookCommand
|
|
913
|
+
});
|
|
914
|
+
const prepareFailure = prepareHookResults.find((result) => result.status === 'fatal');
|
|
915
|
+
if (prepareFailure) {
|
|
916
|
+
throw new Error(prepareFailure.message ?? `Sandbox hook '${prepareFailure.hookId}' failed.`);
|
|
917
|
+
}
|
|
1048
918
|
await ensureDocker(effectiveConfig, (detail) => {
|
|
1049
919
|
p.log.info(` ${detail}`);
|
|
1050
920
|
});
|
|
@@ -1111,7 +981,7 @@ export async function create(args) {
|
|
|
1111
981
|
if (fs.readdirSync(worktree).length > 0) {
|
|
1112
982
|
return `Worktree exists at ${worktree}`;
|
|
1113
983
|
}
|
|
1114
|
-
|
|
984
|
+
removeDirRecursive(worktree);
|
|
1115
985
|
}
|
|
1116
986
|
const branchExists = runOk('git', [
|
|
1117
987
|
'-C',
|
|
@@ -1200,7 +1070,7 @@ export async function create(args) {
|
|
|
1200
1070
|
runSafeEngine(engine, 'docker', ['rm', name]);
|
|
1201
1071
|
}
|
|
1202
1072
|
}
|
|
1203
|
-
const aliasesFile = ensureSandboxAliasesFile(effectiveConfig.home);
|
|
1073
|
+
const aliasesFile = ensureSandboxAliasesFile(effectiveConfig.home, capabilityPlan.aliases);
|
|
1204
1074
|
if (aliasesFile.created) {
|
|
1205
1075
|
message(`Created default sandbox aliases at ${aliasesFile.path}`);
|
|
1206
1076
|
}
|
|
@@ -1229,6 +1099,24 @@ export async function create(args) {
|
|
|
1229
1099
|
let hostShellConfig;
|
|
1230
1100
|
let tmpfsSeedPlan = [];
|
|
1231
1101
|
try {
|
|
1102
|
+
const beforeCreateResults = await runSandboxHooks({
|
|
1103
|
+
hooks: capabilityPlan.hooksByPhase['before-container-create'],
|
|
1104
|
+
phase: 'before-container-create',
|
|
1105
|
+
context: {
|
|
1106
|
+
config: effectiveConfig,
|
|
1107
|
+
plan: capabilityPlan,
|
|
1108
|
+
create: {
|
|
1109
|
+
hostHome: effectiveConfig.home,
|
|
1110
|
+
resolvedTools: effectiveResolvedTools
|
|
1111
|
+
}
|
|
1112
|
+
},
|
|
1113
|
+
runCommand: runBoundedSandboxHookCommand
|
|
1114
|
+
});
|
|
1115
|
+
const beforeCreateFailure = beforeCreateResults.find((result) => result.status === 'fatal');
|
|
1116
|
+
if (beforeCreateFailure) {
|
|
1117
|
+
throw new Error(beforeCreateFailure.message
|
|
1118
|
+
?? `Sandbox hook '${beforeCreateFailure.hookId}' failed.`);
|
|
1119
|
+
}
|
|
1232
1120
|
const claudeCodeEntry = effectiveResolvedTools.find(({ tool }) => tool.id === 'claude-code');
|
|
1233
1121
|
if (claudeCodeEntry) {
|
|
1234
1122
|
ensureClaudeOnboarding(claudeCodeEntry.dir, effectiveConfig.home);
|
|
@@ -1237,11 +1125,6 @@ export async function create(args) {
|
|
|
1237
1125
|
// provider/API settings are enough for Claude Code. If no usable
|
|
1238
1126
|
// auth was present, the claude-code entry was removed above.
|
|
1239
1127
|
}
|
|
1240
|
-
const codexEntry = effectiveResolvedTools.find(({ tool }) => tool.id === 'codex');
|
|
1241
|
-
if (codexEntry) {
|
|
1242
|
-
ensureCodexModelInheritance(codexEntry.dir, effectiveConfig.home, codexEntry.tool.containerMount);
|
|
1243
|
-
ensureCodexWorkspaceTrust(codexEntry.dir);
|
|
1244
|
-
}
|
|
1245
1128
|
const geminiEntry = effectiveResolvedTools.find(({ tool }) => tool.id === 'gemini-cli');
|
|
1246
1129
|
if (geminiEntry) {
|
|
1247
1130
|
ensureGeminiWorkspaceTrust(geminiEntry.dir);
|
|
@@ -1315,6 +1198,8 @@ export async function create(args) {
|
|
|
1315
1198
|
sandboxLabel(effectiveConfig),
|
|
1316
1199
|
'--label',
|
|
1317
1200
|
`${sandboxBranchLabel(effectiveConfig)}=${branch}`,
|
|
1201
|
+
'--label',
|
|
1202
|
+
`${sandboxRuntimeCapabilityLabel(effectiveConfig)}=${capabilityPlan.runtimeSignature}`,
|
|
1318
1203
|
...coreVolumes,
|
|
1319
1204
|
...buildClipboardVolumeArgs(engine, effectiveConfig.home),
|
|
1320
1205
|
'-v',
|
|
@@ -1392,6 +1277,17 @@ export async function create(args) {
|
|
|
1392
1277
|
runSafeEngine(engine, 'docker', ['exec', container, 'bash', '-lc', command]);
|
|
1393
1278
|
}
|
|
1394
1279
|
}
|
|
1280
|
+
const afterStartResults = await runSandboxHooks({
|
|
1281
|
+
hooks: capabilityPlan.hooksByPhase['after-container-start'],
|
|
1282
|
+
phase: 'after-container-start',
|
|
1283
|
+
context: { config: effectiveConfig, plan: capabilityPlan },
|
|
1284
|
+
runCommand: runBoundedSandboxHookCommand
|
|
1285
|
+
});
|
|
1286
|
+
const afterStartFailure = afterStartResults.find((result) => result.status === 'fatal');
|
|
1287
|
+
if (afterStartFailure) {
|
|
1288
|
+
throw new Error(afterStartFailure.message
|
|
1289
|
+
?? `Sandbox hook '${afterStartFailure.hookId}' failed.`);
|
|
1290
|
+
}
|
|
1395
1291
|
return 'Container started';
|
|
1396
1292
|
}
|
|
1397
1293
|
}
|
|
@@ -2,6 +2,7 @@ import { loadConfig } from '../config.js';
|
|
|
2
2
|
import { assertValidBranchName, containerNameCandidates, sandboxBranchLabel, sandboxLabel } from '../constants.js';
|
|
3
3
|
import { detectEngine } from '../engine.js';
|
|
4
4
|
import { ensureSandboxReady } from '../recovery.js';
|
|
5
|
+
import { createSandboxCapabilityPlan, runBoundedSandboxHookCommand, runSandboxHooks } from '../agent-client-reconciler.js';
|
|
5
6
|
import { formatCredentialWarnings, formatRemaining, hasClaudeProviderAuth, reconcileClaudeCredentials, redactCommandError, validateClaudeCredentialsEnvOverride } from '../credentials.js';
|
|
6
7
|
import { runInteractiveEngine } from '../shell.js';
|
|
7
8
|
import { dotfilesCacheDir, materializeDotfiles } from '../dotfiles.js';
|
|
@@ -123,7 +124,22 @@ export async function enter(args) {
|
|
|
123
124
|
});
|
|
124
125
|
const container = ready.container;
|
|
125
126
|
const cmd = parsed.command;
|
|
126
|
-
|
|
127
|
+
const capabilityPlan = createSandboxCapabilityPlan(config);
|
|
128
|
+
const enterHookResults = await runSandboxHooks({
|
|
129
|
+
hooks: capabilityPlan.hooksByPhase['before-enter'],
|
|
130
|
+
phase: 'before-enter',
|
|
131
|
+
context: { config, plan: capabilityPlan },
|
|
132
|
+
runCommand: runBoundedSandboxHookCommand
|
|
133
|
+
});
|
|
134
|
+
for (const result of enterHookResults) {
|
|
135
|
+
if (result.status === 'fatal') {
|
|
136
|
+
throw new Error(result.message ?? `Sandbox hook '${result.hookId}' failed.`);
|
|
137
|
+
}
|
|
138
|
+
if (result.status === 'warning') {
|
|
139
|
+
process.stderr.write(`Warning: ${result.message ?? `Sandbox hook '${result.hookId}' did not complete.`}\n`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
if (capabilityPlan.selectedAgentClients.some((adapter) => adapter.id === 'claude-code')) {
|
|
127
143
|
try {
|
|
128
144
|
// Scan all projects so a refresh from a neighbouring sandbox can still flow back to the host.
|
|
129
145
|
const providerAuthAvailable = hasClaudeProviderAuth(config.home);
|
|
@@ -10,7 +10,7 @@ import { hostJoin } from '../engines/wsl2-paths.js';
|
|
|
10
10
|
import { removeManagedDir, removeWorktreeDir } from '../managed-fs.js';
|
|
11
11
|
import { parseLabels } from './ls.js';
|
|
12
12
|
import { runEngine, runSafe } from '../shell.js';
|
|
13
|
-
import {
|
|
13
|
+
import { createSandboxCapabilityPlan } from '../agent-client-reconciler.js';
|
|
14
14
|
const USAGE = `Usage: ai sandbox prune [--dry-run]`;
|
|
15
15
|
function listChildDirs(base) {
|
|
16
16
|
if (!fs.existsSync(base)) {
|
|
@@ -129,7 +129,7 @@ export async function prune(args) {
|
|
|
129
129
|
return;
|
|
130
130
|
}
|
|
131
131
|
const config = loadConfig();
|
|
132
|
-
const tools =
|
|
132
|
+
const tools = [...createSandboxCapabilityPlan(config).cleanupInventory];
|
|
133
133
|
const engine = detectEngine(config);
|
|
134
134
|
const psArgs = [
|
|
135
135
|
'ps',
|
|
@@ -10,7 +10,8 @@ import { pruneSandboxDanglingImages } from '../image-prune.js';
|
|
|
10
10
|
import { removeManagedDir, removeWorktreeDir } from '../managed-fs.js';
|
|
11
11
|
import { runOk, runSafe, runSafeEngine } from '../shell.js';
|
|
12
12
|
import { resolveTaskBranch } from '../task-resolver.js';
|
|
13
|
-
import {
|
|
13
|
+
import { toolConfigDirCandidates, toolProjectDirCandidates } from '../tools.js';
|
|
14
|
+
import { createSandboxCapabilityPlan } from '../agent-client-reconciler.js';
|
|
14
15
|
import { fetchSandboxRows } from './list-running.js';
|
|
15
16
|
import { lookupShortIdByBranch } from '../../task/short-id.js';
|
|
16
17
|
const USAGE = `Usage:
|
|
@@ -284,7 +285,7 @@ export async function rm(args) {
|
|
|
284
285
|
throw new Error(USAGE);
|
|
285
286
|
}
|
|
286
287
|
const config = loadConfig();
|
|
287
|
-
const tools =
|
|
288
|
+
const tools = [...createSandboxCapabilityPlan(config).cleanupInventory];
|
|
288
289
|
if (values.purge) {
|
|
289
290
|
await rmPurge(config, tools);
|
|
290
291
|
return;
|
|
@@ -3,6 +3,8 @@ 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.js';
|
|
7
|
+
import { isAgentClientId } from '../agent-clients/types.js';
|
|
6
8
|
import { validateSandboxEngine } from './engine.js';
|
|
7
9
|
import { hostJoin } from './engines/wsl2-paths.js';
|
|
8
10
|
import { findRuntimeEngineMismatches } from './runtime-engines.js';
|
|
@@ -10,7 +12,7 @@ import { parseCustomTools } from './tools.js';
|
|
|
10
12
|
const DEFAULTS = Object.freeze({
|
|
11
13
|
engine: null,
|
|
12
14
|
runtimes: ['node22'],
|
|
13
|
-
tools: ['agent-infra'
|
|
15
|
+
tools: ['agent-infra'],
|
|
14
16
|
refreshIntervalDays: 7,
|
|
15
17
|
dockerfile: null,
|
|
16
18
|
vm: {
|
|
@@ -46,6 +48,18 @@ function cloneDefaults() {
|
|
|
46
48
|
vm: { ...DEFAULTS.vm }
|
|
47
49
|
};
|
|
48
50
|
}
|
|
51
|
+
function resolveSandboxToolIds(sandbox, agentClients, defaults) {
|
|
52
|
+
if (Array.isArray(sandbox.tools)) {
|
|
53
|
+
const tools = sandbox.tools.filter((tool) => typeof tool === 'string');
|
|
54
|
+
return agentClients.source === 'canonical'
|
|
55
|
+
? tools.filter((tool) => !isAgentClientId(tool))
|
|
56
|
+
: tools;
|
|
57
|
+
}
|
|
58
|
+
if (agentClients.remainingSandboxTools !== undefined) {
|
|
59
|
+
return [...agentClients.remainingSandboxTools];
|
|
60
|
+
}
|
|
61
|
+
return [...defaults];
|
|
62
|
+
}
|
|
49
63
|
export function loadConfig({ platformFn = platform, writeStderr = (chunk) => process.stderr.write(chunk) } = {}) {
|
|
50
64
|
const repoRoot = detectRepoRoot();
|
|
51
65
|
const home = homedir();
|
|
@@ -57,6 +71,8 @@ export function loadConfig({ platformFn = platform, writeStderr = (chunk) => pro
|
|
|
57
71
|
throw new Error('No .agents/.airc.json found. Run "ai init" first.');
|
|
58
72
|
}
|
|
59
73
|
const airc = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
74
|
+
const sandboxInput = airc.sandbox;
|
|
75
|
+
const agentClients = normalizeAgentClients(airc);
|
|
60
76
|
const defaults = cloneDefaults();
|
|
61
77
|
const sandbox = airc.sandbox ?? {};
|
|
62
78
|
const engine = validateSandboxEngine(sandbox.engine ?? defaults.engine, { platformFn });
|
|
@@ -83,6 +99,7 @@ export function loadConfig({ platformFn = platform, writeStderr = (chunk) => pro
|
|
|
83
99
|
}
|
|
84
100
|
}
|
|
85
101
|
const customTools = parseCustomTools(sandbox.customTools, { home });
|
|
102
|
+
const tools = resolveSandboxToolIds(sandbox, agentClients, defaults.tools);
|
|
86
103
|
return {
|
|
87
104
|
repoRoot,
|
|
88
105
|
configPath,
|
|
@@ -97,10 +114,10 @@ export function loadConfig({ platformFn = platform, writeStderr = (chunk) => pro
|
|
|
97
114
|
dotfilesDir: hostJoin(home, '.agent-infra', 'dotfiles'),
|
|
98
115
|
engine,
|
|
99
116
|
runtimes,
|
|
100
|
-
tools
|
|
101
|
-
? [...sandbox.tools]
|
|
102
|
-
: defaults.tools,
|
|
117
|
+
tools,
|
|
103
118
|
customTools,
|
|
119
|
+
agentClientState: agentClients.state,
|
|
120
|
+
agentClientSource: agentClients.source,
|
|
104
121
|
refreshIntervalDays: asNonNegativeIntegerOrDefault(sandbox.refreshIntervalDays, defaults.refreshIntervalDays),
|
|
105
122
|
dockerfile,
|
|
106
123
|
vm: {
|
|
@@ -75,6 +75,9 @@ export function sandboxImageConfigLabel(config) {
|
|
|
75
75
|
export function sandboxImageRefreshLabel(config) {
|
|
76
76
|
return `${sandboxLabel(config)}.last-refresh`;
|
|
77
77
|
}
|
|
78
|
+
export function sandboxRuntimeCapabilityLabel(config) {
|
|
79
|
+
return `agent-infra.sandbox.runtime-capability.${config.project}`;
|
|
80
|
+
}
|
|
78
81
|
export function parsePositiveIntegerOption(value, optionName) {
|
|
79
82
|
if (value === undefined || value === null) {
|
|
80
83
|
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.js';
|
|
6
7
|
const RUNTIMES_DIR = path.join(path.dirname(fileURLToPath(import.meta.url)), 'runtimes');
|
|
7
8
|
function listRuntimeFragments() {
|
|
8
9
|
return fs.readdirSync(RUNTIMES_DIR)
|
|
@@ -63,7 +64,7 @@ export function prepareDockerfile(config) {
|
|
|
63
64
|
path: tempPath,
|
|
64
65
|
signature: dockerfileSignature(config),
|
|
65
66
|
cleanup() {
|
|
66
|
-
|
|
67
|
+
removeDirRecursive(tempDir);
|
|
67
68
|
}
|
|
68
69
|
};
|
|
69
70
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
1
|
import path from 'node:path';
|
|
3
|
-
import { run } from './shell.js';
|
|
2
|
+
import { run, runSafe } from './shell.js';
|
|
3
|
+
import { removeDirRecursive } from '../remove-dir.js';
|
|
4
4
|
export function assertManagedPath(root, target) {
|
|
5
5
|
const resolvedRoot = path.resolve(root);
|
|
6
6
|
const resolvedTarget = path.resolve(target);
|
|
@@ -12,14 +12,19 @@ export function assertManagedPath(root, target) {
|
|
|
12
12
|
}
|
|
13
13
|
export function removeManagedDir(root, dir) {
|
|
14
14
|
assertManagedPath(root, dir);
|
|
15
|
-
|
|
15
|
+
removeDirRecursive(dir);
|
|
16
16
|
}
|
|
17
|
-
export function removeWorktreeDir(repoRoot, worktreeBase, dir) {
|
|
17
|
+
export function removeWorktreeDir(repoRoot, worktreeBase, dir, { runFn = run, runSafeFn = runSafe } = {}) {
|
|
18
18
|
try {
|
|
19
|
-
|
|
19
|
+
runFn('git', ['-C', repoRoot, 'worktree', 'remove', dir, '--force']);
|
|
20
20
|
}
|
|
21
21
|
catch {
|
|
22
|
+
// On WSL2 the worktree is registered under its `/mnt/<drive>/...` path, so
|
|
23
|
+
// `git worktree remove <windows-path>` cannot match it. Delete the managed
|
|
24
|
+
// directory directly, then prune the now-dangling worktree metadata so the
|
|
25
|
+
// branch is no longer reported as checked out.
|
|
22
26
|
removeManagedDir(worktreeBase, dir);
|
|
27
|
+
runSafeFn('git', ['-C', repoRoot, 'worktree', 'prune']);
|
|
23
28
|
}
|
|
24
29
|
}
|
|
25
30
|
//# sourceMappingURL=managed-fs.js.map
|