@fitlab-ai/agent-infra 0.9.0 → 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 +22 -5
- package/README.zh-CN.md +21 -5
- package/bin/cli.ts +12 -0
- package/dist/bin/cli.js +14 -0
- package/dist/bin/internal-cli.js +20 -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/platform-release-notes.js +53 -0
- package/dist/lib/internal/release-workflow.js +175 -17
- 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/github-release-notes.js +96 -0
- package/dist/lib/platform/issues.js +4 -2
- package/dist/lib/platform/merged-pr-equivalence.js +84 -0
- package/dist/lib/platform/pr-checks.js +43 -15
- package/dist/lib/platform/pull-requests.js +113 -7
- package/dist/lib/platform/release-notes.js +143 -0
- package/dist/lib/platform/releases.js +1 -1
- package/dist/lib/platform/verification-required.js +31 -9
- 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/build-proxy.js +63 -0
- package/dist/lib/sandbox/clipboard/linux.js +2 -1
- package/dist/lib/sandbox/commands/create.js +133 -189
- package/dist/lib/sandbox/commands/enter.js +17 -1
- package/dist/lib/sandbox/commands/prune.js +2 -2
- package/dist/lib/sandbox/commands/rebuild.js +24 -7
- 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/image-build.js +2 -1
- package/dist/lib/sandbox/managed-fs.js +10 -5
- package/dist/lib/sandbox/recovery.js +52 -36
- package/dist/lib/sandbox/runtimes/ai-tools.dockerfile +24 -3
- package/dist/lib/sandbox/runtimes/base.dockerfile +8 -1
- package/dist/lib/sandbox/runtimes/java17.dockerfile +8 -1
- package/dist/lib/sandbox/runtimes/java21.dockerfile +8 -1
- package/dist/lib/sandbox/runtimes/node20.dockerfile +8 -1
- package/dist/lib/sandbox/runtimes/node22.dockerfile +8 -1
- package/dist/lib/sandbox/runtimes/python3.dockerfile +8 -1
- package/dist/lib/sandbox/shell.js +3 -2
- 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/commands/issue-body.js +10 -5
- package/dist/lib/task/delegation-receipts.js +107 -0
- package/dist/lib/task/events.js +101 -2
- package/dist/lib/task/issue-form.js +2 -0
- 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 +13 -0
- package/dist/lib/task/verification-checks.js +2 -1
- package/dist/lib/task/verification-engine.js +168 -23
- package/dist/lib/task/verification-review.js +1 -1
- package/dist/lib/task/verification.js +4 -2
- 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/platform-release-notes.ts +55 -0
- package/lib/internal/release-workflow.ts +172 -18
- 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/github-release-notes.ts +132 -0
- package/lib/platform/issues.ts +6 -4
- package/lib/platform/merged-pr-equivalence.ts +107 -0
- package/lib/platform/pr-checks.ts +57 -28
- package/lib/platform/pull-requests.ts +140 -25
- package/lib/platform/release-notes.ts +167 -0
- package/lib/platform/releases.ts +1 -1
- package/lib/platform/verification-required.ts +37 -9
- 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/build-proxy.ts +94 -0
- package/lib/sandbox/clipboard/linux.ts +2 -1
- package/lib/sandbox/commands/create.ts +188 -203
- package/lib/sandbox/commands/enter.ts +23 -1
- package/lib/sandbox/commands/prune.ts +2 -2
- package/lib/sandbox/commands/rebuild.ts +29 -7
- 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/image-build.ts +4 -1
- package/lib/sandbox/managed-fs.ts +18 -5
- package/lib/sandbox/recovery.ts +98 -67
- package/lib/sandbox/runtimes/ai-tools.dockerfile +24 -3
- package/lib/sandbox/runtimes/base.dockerfile +8 -1
- package/lib/sandbox/runtimes/java17.dockerfile +8 -1
- package/lib/sandbox/runtimes/java21.dockerfile +8 -1
- package/lib/sandbox/runtimes/node20.dockerfile +8 -1
- package/lib/sandbox/runtimes/node22.dockerfile +8 -1
- package/lib/sandbox/runtimes/python3.dockerfile +8 -1
- package/lib/sandbox/shell.ts +4 -2
- 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/commands/issue-body.ts +10 -5
- package/lib/task/delegation-receipts.ts +170 -0
- package/lib/task/events.ts +111 -3
- package/lib/task/issue-form.ts +4 -0
- 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 +15 -1
- package/lib/task/verification-checks.ts +2 -1
- package/lib/task/verification-engine.ts +190 -25
- package/lib/task/verification-review.ts +2 -0
- package/lib/task/verification.ts +6 -3
- 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.en.md +2 -2
- package/templates/.agents/rules/release-commands.github.en.md +15 -39
- package/templates/.agents/rules/release-commands.github.zh-CN.md +14 -38
- package/templates/.agents/rules/release-commands.zh-CN.md +2 -2
- package/templates/.agents/rules/review-handshake.en.md +4 -3
- package/templates/.agents/rules/review-handshake.zh-CN.md +4 -3
- package/templates/.agents/rules/review-method.en.md +113 -0
- package/templates/.agents/rules/review-method.zh-CN.md +98 -0
- package/templates/.agents/rules/testing-discipline.en.md +20 -2
- package/templates/.agents/rules/testing-discipline.zh-CN.md +20 -2
- package/templates/.agents/skills/analyze-task/SKILL.en.md +9 -6
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +9 -6
- 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 +3 -3
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/complete-task/config/verify.en.json +0 -1
- package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +0 -1
- 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-release-note/SKILL.en.md +17 -38
- package/templates/.agents/skills/create-release-note/SKILL.zh-CN.md +16 -37
- package/templates/.agents/skills/create-task/SKILL.en.md +15 -11
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +15 -11
- package/templates/.agents/skills/create-task/reference/context-capture.en.md +29 -0
- package/templates/.agents/skills/create-task/reference/context-capture.zh-CN.md +29 -0
- 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/.agents/templates/task.en.md +18 -0
- package/templates/.agents/templates/task.zh-CN.md +18 -0
- 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,13 +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
|
+
import { assertBuildProxyCompatibility, buildProxyFailureHint, prepareBuildProxy, redactBuildProxyValues } from '../build-proxy.js';
|
|
13
14
|
import { detectEngine, ensureDocker } from '../engine.js';
|
|
14
15
|
import { commandForEngine, execEngine, run, runEngine, runOk, runOkEngine, runSafe, runSafeEngine, runVerboseEngine } from '../shell.js';
|
|
15
16
|
import { resolveTaskBranch } from '../task-resolver.js';
|
|
17
|
+
import { createSandboxCapabilityPlan, runSandboxHooks, runBoundedSandboxHookCommand } from '../agent-client-reconciler.js';
|
|
16
18
|
import { resolveTools, tmpfsSeedStagingPath, tmpfsSeedTargetPath, toolConfigDirCandidates } from '../tools.js';
|
|
17
19
|
import { assertFreshSandboxReady, hydrateTmpfsSeedEntries, prepareTmpfsMounts } from '../recovery.js';
|
|
18
20
|
import { hostJoin, toEnginePath, volumeArg } from '../engines/wsl2-paths.js';
|
|
@@ -21,35 +23,16 @@ import { clipboardHostDir, CONTAINER_CLIPBOARD_MOUNT } from '../clipboard/paths.
|
|
|
21
23
|
import { validateSelinuxDisableEnv } from '../engines/selinux.js';
|
|
22
24
|
import { dotfilesCacheDir, materializeDotfiles } from '../dotfiles.js';
|
|
23
25
|
import { ensureSandboxDiscoveryReadmes } from '../readme-scaffold.js';
|
|
26
|
+
import { removeDirRecursive } from '../../remove-dir.js';
|
|
24
27
|
import { prepareClaudeCredentials, redactCommandError, validateClaudeCredentialsEnvOverride } from '../credentials.js';
|
|
25
28
|
import { detectHostTimezone } from '../host-timezone.js';
|
|
26
29
|
import { buildImageSignature, buildSandboxImageArgs, isRefreshDisabled, isRefreshDue, parseImageLabels, parseRefreshTimestamp } from '../image-build.js';
|
|
27
|
-
|
|
30
|
+
export { ensureCodexModelInheritance, ensureCodexWorkspaceTrust } from '../../agent-clients/adapters/codex-sandbox.js';
|
|
28
31
|
const SANDBOX_ALIAS_BLOCK_BEGIN = '# >>> agent-infra managed aliases >>>';
|
|
29
32
|
const SANDBOX_ALIAS_BLOCK_END = '# <<< agent-infra managed aliases <<<';
|
|
30
|
-
const SANDBOX_ALIAS_NAMES = [
|
|
31
|
-
'claude-yolo',
|
|
32
|
-
'opencode-yolo',
|
|
33
|
-
'codex-yolo',
|
|
34
|
-
'gemini-yolo',
|
|
35
|
-
'cy',
|
|
36
|
-
'oy',
|
|
37
|
-
'xy',
|
|
38
|
-
'gy'
|
|
39
|
-
];
|
|
40
|
-
const DEFAULT_SANDBOX_ALIASES = `alias claude-yolo='claude --dangerously-skip-permissions; tput ed'
|
|
41
|
-
alias opencode-yolo='OPENCODE_PERMISSION='\\''${OPENCODE_YOLO_PERMISSION}'\\'' opencode; tput ed'
|
|
42
|
-
alias codex-yolo='codex --yolo; tput ed'
|
|
43
|
-
alias gemini-yolo='gemini --yolo; tput ed'
|
|
44
|
-
|
|
45
|
-
alias cy='claude --dangerously-skip-permissions; tput ed'
|
|
46
|
-
alias oy='OPENCODE_PERMISSION='\\''${OPENCODE_YOLO_PERMISSION}'\\'' opencode; tput ed'
|
|
47
|
-
alias xy='codex --yolo; tput ed'
|
|
48
|
-
alias gy='gemini --yolo; tput ed'
|
|
49
|
-
`;
|
|
50
33
|
const CONTAINER_HOME = '/home/devuser';
|
|
51
34
|
const CONTAINER_SHELL_CONFIG_MOUNT = `${CONTAINER_HOME}/.host-shell-config`;
|
|
52
|
-
const USAGE = `Usage: ai sandbox create <branch> [base] [--cpu <n>] [--memory <n>] [--no-refresh] [--inherit-proxy|-P]
|
|
35
|
+
const USAGE = `Usage: ai sandbox create <branch> [base] [--cpu <n>] [--memory <n>] [--no-refresh] [--inherit-proxy|-P] [--inherit-build-proxy|-B]
|
|
53
36
|
|
|
54
37
|
Host aliases:
|
|
55
38
|
${'~'}/.agent-infra/aliases/sandbox.sh is auto-created on first run and exposed
|
|
@@ -59,7 +42,9 @@ Host aliases:
|
|
|
59
42
|
|
|
60
43
|
Proxy:
|
|
61
44
|
--inherit-proxy, -P Copy non-empty standard host proxy variables into the
|
|
62
|
-
container environment through the private env file
|
|
45
|
+
container environment through the private env file.
|
|
46
|
+
--inherit-build-proxy, -B Pass uppercase HTTP_PROXY, HTTPS_PROXY, and NO_PROXY
|
|
47
|
+
to managed image build steps for this invocation.`;
|
|
63
48
|
const HOST_PROXY_ENV_KEYS = [
|
|
64
49
|
'http_proxy',
|
|
65
50
|
'HTTP_PROXY',
|
|
@@ -70,6 +55,15 @@ const HOST_PROXY_ENV_KEYS = [
|
|
|
70
55
|
'no_proxy',
|
|
71
56
|
'NO_PROXY'
|
|
72
57
|
];
|
|
58
|
+
const MANAGED_GITHUB_GIT_CONFIG = [
|
|
59
|
+
'[credential]',
|
|
60
|
+
'\thelper = !gh auth git-credential',
|
|
61
|
+
'[credential "https://github.com"]',
|
|
62
|
+
'\thelper =',
|
|
63
|
+
'\thelper = !gh auth git-credential',
|
|
64
|
+
'[url "https://github.com/"]',
|
|
65
|
+
'\tinsteadOf = git@github.com:'
|
|
66
|
+
];
|
|
73
67
|
function resolveToolDirs(config, tools, branch) {
|
|
74
68
|
return tools.map((tool) => {
|
|
75
69
|
const candidates = toolConfigDirCandidates(tool, config.project, branch);
|
|
@@ -158,6 +152,14 @@ function normalizeContainerHomeSeparators(content) {
|
|
|
158
152
|
const containerHomePattern = new RegExp(`${escapeRegExp(CONTAINER_HOME)}\\S*`, 'g');
|
|
159
153
|
return content.replace(containerHomePattern, (value) => value.replaceAll('\\', '/'));
|
|
160
154
|
}
|
|
155
|
+
function isGitHubCliCredentialHelper(line) {
|
|
156
|
+
const helperMatch = line.match(/^\s*helper\s*=\s*(.+?)\s*$/i);
|
|
157
|
+
if (!helperMatch) {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
const helper = (helperMatch[1] ?? '').trim().replace(/^"(.*)"$/, '$1');
|
|
161
|
+
return /^!\s*(?:"[^"]*[\\/]+gh"|(?:\S*[\\/]+)?gh)\s+auth\s+git-credential(?:\s|$)/i.test(helper);
|
|
162
|
+
}
|
|
161
163
|
export function sanitizeGitConfig(gitconfig, home, { stripGpg = false, repoRoot = '' } = {}) {
|
|
162
164
|
const posixHome = home.replaceAll('\\', '/');
|
|
163
165
|
const normalizedGitconfig = gitconfig
|
|
@@ -170,10 +172,12 @@ export function sanitizeGitConfig(gitconfig, home, { stripGpg = false, repoRoot
|
|
|
170
172
|
const sanitized = [];
|
|
171
173
|
let inGpgSection = false;
|
|
172
174
|
let currentSection = '';
|
|
175
|
+
let currentSectionName = '';
|
|
173
176
|
for (const line of lines) {
|
|
174
177
|
const sectionMatch = line.match(/^\s*\[([^\]]+)\]\s*$/);
|
|
175
178
|
if (sectionMatch) {
|
|
176
179
|
const sectionName = (sectionMatch[1] ?? '').trim();
|
|
180
|
+
currentSectionName = sectionName;
|
|
177
181
|
currentSection = ((sectionName.match(/^([^\s"]+)/)?.[1]) ?? '').toLowerCase();
|
|
178
182
|
inGpgSection = /^gpg(?:\s+"[^"]+")?$/i.test(sectionName);
|
|
179
183
|
if (stripGpg && inGpgSection) {
|
|
@@ -199,9 +203,19 @@ export function sanitizeGitConfig(gitconfig, home, { stripGpg = false, repoRoot
|
|
|
199
203
|
if (stripGpg && currentSection === 'user' && /^\s*signingKey\s*=.*$/i.test(line)) {
|
|
200
204
|
continue;
|
|
201
205
|
}
|
|
206
|
+
if (currentSection === 'credential' && isGitHubCliCredentialHelper(line)) {
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
if (/^url\s+"https:\/\/github\.com\/"$/i.test(currentSectionName)
|
|
210
|
+
&& /^\s*insteadOf\s*=\s*git@github\.com:\s*$/i.test(line)) {
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
202
213
|
sanitized.push(line);
|
|
203
214
|
}
|
|
204
|
-
return
|
|
215
|
+
return [
|
|
216
|
+
...appendSafeDirectories(sanitized, repoRoot),
|
|
217
|
+
...MANAGED_GITHUB_GIT_CONFIG
|
|
218
|
+
].join('\n');
|
|
205
219
|
}
|
|
206
220
|
export function hostHasGpgKeys(home, execFn = execFileSync) {
|
|
207
221
|
return currentKeyringFingerprint(home, execFn) !== null;
|
|
@@ -236,7 +250,7 @@ export function ensureShellConfigSymlinks(engine, container, execFn = execEngine
|
|
|
236
250
|
}
|
|
237
251
|
export function prepareHostShellConfig({ home, project, branch, repoRoot }) {
|
|
238
252
|
const hostDir = hostShellConfigDir(home, project, branch);
|
|
239
|
-
|
|
253
|
+
removeDirRecursive(hostDir);
|
|
240
254
|
fs.mkdirSync(hostDir, { recursive: true });
|
|
241
255
|
writeSanitizedGitconfig({
|
|
242
256
|
home,
|
|
@@ -274,11 +288,19 @@ function normalizeWorktreePath(worktreePath) {
|
|
|
274
288
|
if (!worktreePath) {
|
|
275
289
|
return '';
|
|
276
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;
|
|
277
297
|
try {
|
|
278
|
-
|
|
298
|
+
const normalized = fs.existsSync(hostPath) ? fs.realpathSync(hostPath) : path.resolve(hostPath);
|
|
299
|
+
return process.platform === 'win32' ? normalized.toLowerCase() : normalized;
|
|
279
300
|
}
|
|
280
301
|
catch {
|
|
281
|
-
|
|
302
|
+
const normalized = path.resolve(hostPath);
|
|
303
|
+
return process.platform === 'win32' ? normalized.toLowerCase() : normalized;
|
|
282
304
|
}
|
|
283
305
|
}
|
|
284
306
|
export function getGitSigningKey({ home, repoPath = null, execFn = execFileSync } = {}) {
|
|
@@ -449,7 +471,7 @@ export function collectHostProxyEntries(env) {
|
|
|
449
471
|
});
|
|
450
472
|
}
|
|
451
473
|
export function buildContainerEnvFile(resolvedTools, engine, runSafeEngineFn = runSafeEngine, options = {}) {
|
|
452
|
-
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;
|
|
453
475
|
const entries = resolvedTools.flatMap(({ tool }) => Object.entries(tool.envVars ?? {}));
|
|
454
476
|
entries.push(...additionalEntries);
|
|
455
477
|
let ghToken = runSafeEngineFn(engine, 'gh', ['auth', 'token']);
|
|
@@ -673,141 +695,6 @@ export function ensureClaudeSettings(toolDir, hostHomeDir) {
|
|
|
673
695
|
fs.writeFileSync(settingsPath, JSON.stringify(data, null, 4), 'utf8');
|
|
674
696
|
}
|
|
675
697
|
}
|
|
676
|
-
function resolveHostCatalogPath(value, hostHomeDir) {
|
|
677
|
-
if (typeof value !== 'string' || value === '') {
|
|
678
|
-
return null;
|
|
679
|
-
}
|
|
680
|
-
let resolved;
|
|
681
|
-
if (value === '~' || value.startsWith('~/') || value.startsWith('~\\')) {
|
|
682
|
-
resolved = path.join(hostHomeDir, value.slice(1).replace(/^[/\\]+/, ''));
|
|
683
|
-
}
|
|
684
|
-
else if (path.isAbsolute(value)) {
|
|
685
|
-
resolved = value;
|
|
686
|
-
}
|
|
687
|
-
else {
|
|
688
|
-
resolved = path.join(hostHomeDir, '.codex', value);
|
|
689
|
-
}
|
|
690
|
-
try {
|
|
691
|
-
if (!fs.statSync(resolved).isFile()) {
|
|
692
|
-
return null;
|
|
693
|
-
}
|
|
694
|
-
fs.accessSync(resolved, fs.constants.R_OK);
|
|
695
|
-
return resolved;
|
|
696
|
-
}
|
|
697
|
-
catch {
|
|
698
|
-
return null;
|
|
699
|
-
}
|
|
700
|
-
}
|
|
701
|
-
const CODEX_DISABLED_FEATURE_FLAGS = ['apps', 'enable_mcp_apps'];
|
|
702
|
-
function removeCodexMcpServers(sandboxParsed) {
|
|
703
|
-
if (!Object.hasOwn(sandboxParsed, 'mcp_servers')) {
|
|
704
|
-
return false;
|
|
705
|
-
}
|
|
706
|
-
delete sandboxParsed.mcp_servers;
|
|
707
|
-
return true;
|
|
708
|
-
}
|
|
709
|
-
function inheritDisabledCodexFeatureFlags(sandboxParsed, hostParsed) {
|
|
710
|
-
if (!isJsonObjectRecord(hostParsed.features)) {
|
|
711
|
-
return false;
|
|
712
|
-
}
|
|
713
|
-
if (Object.hasOwn(sandboxParsed, 'features') && !isJsonObjectRecord(sandboxParsed.features)) {
|
|
714
|
-
return false;
|
|
715
|
-
}
|
|
716
|
-
let sandboxFeatures = sandboxParsed.features;
|
|
717
|
-
let changed = false;
|
|
718
|
-
for (const key of CODEX_DISABLED_FEATURE_FLAGS) {
|
|
719
|
-
if (hostParsed.features[key] !== false) {
|
|
720
|
-
continue;
|
|
721
|
-
}
|
|
722
|
-
if (!sandboxFeatures) {
|
|
723
|
-
sandboxFeatures = {};
|
|
724
|
-
sandboxParsed.features = sandboxFeatures;
|
|
725
|
-
}
|
|
726
|
-
if (sandboxFeatures[key] !== false) {
|
|
727
|
-
sandboxFeatures[key] = false;
|
|
728
|
-
changed = true;
|
|
729
|
-
}
|
|
730
|
-
}
|
|
731
|
-
return changed;
|
|
732
|
-
}
|
|
733
|
-
export function ensureCodexModelInheritance(toolDir, hostHomeDir, containerCodexDir = '/home/devuser/.codex') {
|
|
734
|
-
if (!hostHomeDir) {
|
|
735
|
-
return;
|
|
736
|
-
}
|
|
737
|
-
const hostConfigPath = path.join(hostHomeDir, '.codex', 'config.toml');
|
|
738
|
-
if (!fs.existsSync(hostConfigPath)) {
|
|
739
|
-
return;
|
|
740
|
-
}
|
|
741
|
-
let hostParsed;
|
|
742
|
-
try {
|
|
743
|
-
hostParsed = toml.parse(fs.readFileSync(hostConfigPath, 'utf8'));
|
|
744
|
-
}
|
|
745
|
-
catch {
|
|
746
|
-
return;
|
|
747
|
-
}
|
|
748
|
-
const sandboxConfigPath = path.join(toolDir, 'config.toml');
|
|
749
|
-
// This rewrites sandbox-side TOML and drops comments; the host config stays untouched.
|
|
750
|
-
let sandboxParsed = {};
|
|
751
|
-
if (fs.existsSync(sandboxConfigPath)) {
|
|
752
|
-
try {
|
|
753
|
-
sandboxParsed = toml.parse(fs.readFileSync(sandboxConfigPath, 'utf8'));
|
|
754
|
-
}
|
|
755
|
-
catch {
|
|
756
|
-
return;
|
|
757
|
-
}
|
|
758
|
-
}
|
|
759
|
-
let changed = removeCodexMcpServers(sandboxParsed);
|
|
760
|
-
changed = inheritDisabledCodexFeatureFlags(sandboxParsed, hostParsed) || changed;
|
|
761
|
-
const inheritSpecs = [
|
|
762
|
-
['model', 'string'],
|
|
763
|
-
['model_reasoning_effort', 'string'],
|
|
764
|
-
['model_auto_compact_token_limit', 'number']
|
|
765
|
-
];
|
|
766
|
-
for (const [key, type] of inheritSpecs) {
|
|
767
|
-
if (Object.hasOwn(sandboxParsed, key)) {
|
|
768
|
-
continue;
|
|
769
|
-
}
|
|
770
|
-
const value = hostParsed[key];
|
|
771
|
-
if (type === 'string' && (typeof value !== 'string' || value === '')) {
|
|
772
|
-
continue;
|
|
773
|
-
}
|
|
774
|
-
if (type === 'number' && (typeof value !== 'number' || !Number.isFinite(value) || value <= 0)) {
|
|
775
|
-
continue;
|
|
776
|
-
}
|
|
777
|
-
sandboxParsed[key] = value;
|
|
778
|
-
changed = true;
|
|
779
|
-
}
|
|
780
|
-
if (!Object.hasOwn(sandboxParsed, 'model_catalog_json')) {
|
|
781
|
-
const hostCatalogPath = resolveHostCatalogPath(hostParsed['model_catalog_json'], hostHomeDir);
|
|
782
|
-
if (hostCatalogPath) {
|
|
783
|
-
try {
|
|
784
|
-
const basename = path.basename(hostCatalogPath);
|
|
785
|
-
const destDir = path.join(toolDir, 'model-catalogs');
|
|
786
|
-
fs.mkdirSync(destDir, { recursive: true });
|
|
787
|
-
fs.copyFileSync(hostCatalogPath, path.join(destDir, basename));
|
|
788
|
-
sandboxParsed['model_catalog_json'] = path.posix.join(containerCodexDir, 'model-catalogs', basename);
|
|
789
|
-
changed = true;
|
|
790
|
-
}
|
|
791
|
-
catch {
|
|
792
|
-
// Copy failed (e.g. permissions): skip catalog, keep scalar inheritance intact.
|
|
793
|
-
}
|
|
794
|
-
}
|
|
795
|
-
}
|
|
796
|
-
if (changed) {
|
|
797
|
-
fs.writeFileSync(sandboxConfigPath, `${toml.stringify(sandboxParsed)}\n`, 'utf8');
|
|
798
|
-
}
|
|
799
|
-
}
|
|
800
|
-
export function ensureCodexWorkspaceTrust(toolDir) {
|
|
801
|
-
const configPath = path.join(toolDir, 'config.toml');
|
|
802
|
-
let content = '';
|
|
803
|
-
if (fs.existsSync(configPath)) {
|
|
804
|
-
content = fs.readFileSync(configPath, 'utf8');
|
|
805
|
-
}
|
|
806
|
-
if (!content.includes('[projects."/workspace"]')) {
|
|
807
|
-
const entry = '\n[projects."/workspace"]\ntrust_level = "trusted"\n';
|
|
808
|
-
fs.writeFileSync(configPath, content + entry, 'utf8');
|
|
809
|
-
}
|
|
810
|
-
}
|
|
811
698
|
export function ensureOpenCodeModelInheritance(toolDir, hostHomeDir) {
|
|
812
699
|
if (!hostHomeDir) {
|
|
813
700
|
return;
|
|
@@ -868,7 +755,7 @@ function stripManagedSandboxAliasBlocks(content) {
|
|
|
868
755
|
const blockPattern = new RegExp(`${escapeRegExp(SANDBOX_ALIAS_BLOCK_BEGIN)}[\\s\\S]*?${escapeRegExp(SANDBOX_ALIAS_BLOCK_END)}\\n?`, 'g');
|
|
869
756
|
return content.replace(blockPattern, '').trimEnd();
|
|
870
757
|
}
|
|
871
|
-
function isLegacyManagedSandboxAliasFile(content) {
|
|
758
|
+
function isLegacyManagedSandboxAliasFile(content, aliases) {
|
|
872
759
|
const lines = content
|
|
873
760
|
.split(/\r?\n/)
|
|
874
761
|
.map((line) => line.trim())
|
|
@@ -876,19 +763,25 @@ function isLegacyManagedSandboxAliasFile(content) {
|
|
|
876
763
|
if (lines.length === 0) {
|
|
877
764
|
return false;
|
|
878
765
|
}
|
|
879
|
-
const aliasPattern = new RegExp(`^alias (${
|
|
766
|
+
const aliasPattern = new RegExp(`^alias (${aliases.map((alias) => escapeRegExp(alias.name)).join('|')})=`);
|
|
880
767
|
return lines.every((line) => aliasPattern.test(line));
|
|
881
768
|
}
|
|
882
|
-
|
|
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()) {
|
|
883
776
|
const aliasesPath = sandboxAliasesPath(home);
|
|
884
|
-
const managedBlock = `${SANDBOX_ALIAS_BLOCK_BEGIN}\n${
|
|
777
|
+
const managedBlock = `${SANDBOX_ALIAS_BLOCK_BEGIN}\n${renderSandboxAliases(aliases)}${SANDBOX_ALIAS_BLOCK_END}\n`;
|
|
885
778
|
fs.mkdirSync(path.dirname(aliasesPath), { recursive: true });
|
|
886
779
|
const created = !fs.existsSync(aliasesPath);
|
|
887
780
|
let existing = '';
|
|
888
781
|
if (!created) {
|
|
889
782
|
existing = fs.readFileSync(aliasesPath, 'utf8');
|
|
890
783
|
}
|
|
891
|
-
const userContent = isLegacyManagedSandboxAliasFile(existing)
|
|
784
|
+
const userContent = isLegacyManagedSandboxAliasFile(existing, aliases)
|
|
892
785
|
? ''
|
|
893
786
|
: stripManagedSandboxAliasBlocks(existing);
|
|
894
787
|
const nextContent = userContent
|
|
@@ -928,7 +821,7 @@ export function buildTmpfsRunArgs(containerMount, tmpfs) {
|
|
|
928
821
|
const size = tmpfs.size ?? '512m';
|
|
929
822
|
return ['--tmpfs', `${containerMount}:rw,size=${size}`];
|
|
930
823
|
}
|
|
931
|
-
export function buildImage(config, tools, dockerfilePath, imageSignature, { engine, runFn = runEngine, runSafeFn = runSafeEngine, runVerboseFn = runVerboseEngine, env = process.env, refresh = false, lastRefresh } = {}) {
|
|
824
|
+
export function buildImage(config, tools, dockerfilePath, imageSignature, { engine, runFn = runEngine, runSafeFn = runSafeEngine, runVerboseFn = runVerboseEngine, env = process.env, refresh = false, lastRefresh, buildProxyArgs = [], buildEnv } = {}) {
|
|
932
825
|
const selectedEngine = engine ?? detectEngine({ engine: config.engine });
|
|
933
826
|
runVerboseFn(selectedEngine, 'docker', buildSandboxImageArgs(config, tools, dockerfilePath, imageSignature, {
|
|
934
827
|
engine: selectedEngine,
|
|
@@ -936,8 +829,9 @@ export function buildImage(config, tools, dockerfilePath, imageSignature, { engi
|
|
|
936
829
|
runSafeFn,
|
|
937
830
|
env,
|
|
938
831
|
refresh,
|
|
939
|
-
lastRefresh
|
|
940
|
-
|
|
832
|
+
lastRefresh,
|
|
833
|
+
buildProxyArgs
|
|
834
|
+
}), { cwd: config.repoRoot, env: buildEnv });
|
|
941
835
|
}
|
|
942
836
|
function readImageLabels(config, engine) {
|
|
943
837
|
return parseImageLabels(runSafeEngine(engine, 'docker', [
|
|
@@ -958,6 +852,7 @@ export async function create(args) {
|
|
|
958
852
|
memory: { type: 'string' },
|
|
959
853
|
'no-refresh': { type: 'boolean' },
|
|
960
854
|
'inherit-proxy': { type: 'boolean', short: 'P' },
|
|
855
|
+
'inherit-build-proxy': { type: 'boolean', short: 'B' },
|
|
961
856
|
help: { type: 'boolean', short: 'h' }
|
|
962
857
|
}
|
|
963
858
|
});
|
|
@@ -984,15 +879,14 @@ export async function create(args) {
|
|
|
984
879
|
};
|
|
985
880
|
const worktreeCandidates = worktreeDirCandidates(effectiveConfig, branch);
|
|
986
881
|
assertBranchAvailable(config.repoRoot, branch, { allowedWorktrees: worktreeCandidates });
|
|
987
|
-
const
|
|
882
|
+
const capabilityPlan = createSandboxCapabilityPlan(effectiveConfig);
|
|
883
|
+
const tools = [...capabilityPlan.tools];
|
|
988
884
|
const resolvedTools = resolveToolDirs(effectiveConfig, tools, branch);
|
|
989
885
|
// Fatal credential states still fail before filesystem/docker side effects.
|
|
990
|
-
//
|
|
991
|
-
//
|
|
886
|
+
// Missing credentials leave the selected Claude Code capability installed
|
|
887
|
+
// and mounted; only the absent live credential file is omitted.
|
|
992
888
|
const credentialOutcome = prepareClaudeCredentials(effectiveConfig.home, effectiveConfig.project, resolvedTools);
|
|
993
|
-
const effectiveResolvedTools =
|
|
994
|
-
? resolvedTools.filter(({ tool }) => tool.id !== 'claude-code')
|
|
995
|
-
: resolvedTools;
|
|
889
|
+
const effectiveResolvedTools = resolvedTools;
|
|
996
890
|
const container = containerName(effectiveConfig, branch);
|
|
997
891
|
const worktree = worktreeCandidates.find((candidate) => fs.existsSync(candidate)) ?? worktreeCandidates[0] ?? '';
|
|
998
892
|
const shareCommon = shareCommonDir(effectiveConfig);
|
|
@@ -1011,6 +905,16 @@ export async function create(args) {
|
|
|
1011
905
|
}
|
|
1012
906
|
try {
|
|
1013
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
|
+
}
|
|
1014
918
|
await ensureDocker(effectiveConfig, (detail) => {
|
|
1015
919
|
p.log.info(` ${detail}`);
|
|
1016
920
|
});
|
|
@@ -1027,6 +931,12 @@ export async function create(args) {
|
|
|
1027
931
|
&& isRefreshDue(currentLastRefresh, now, effectiveConfig.refreshIntervalDays);
|
|
1028
932
|
const needsImageBuild = signatureStale || refreshDue;
|
|
1029
933
|
if (needsImageBuild) {
|
|
934
|
+
if (values['inherit-build-proxy'] && effectiveConfig.dockerfile) {
|
|
935
|
+
throw new Error('Build proxy inheritance is unavailable with a custom sandbox Dockerfile.');
|
|
936
|
+
}
|
|
937
|
+
const buildProxy = prepareBuildProxy(values['inherit-build-proxy'] ?? false, process.env, engine);
|
|
938
|
+
if (values['inherit-build-proxy'])
|
|
939
|
+
assertBuildProxyCompatibility(engine);
|
|
1030
940
|
const buildRefresh = !imageExists || refreshDue;
|
|
1031
941
|
const buildLastRefresh = buildRefresh ? now : currentLastRefresh || 0;
|
|
1032
942
|
p.log.step(refreshDue
|
|
@@ -1035,7 +945,13 @@ export async function create(args) {
|
|
|
1035
945
|
? 'Rebuilding stale image...'
|
|
1036
946
|
: 'Building image for first use...');
|
|
1037
947
|
try {
|
|
1038
|
-
buildImage(effectiveConfig, tools, preparedDockerfile.path, expectedImageSignature, {
|
|
948
|
+
buildImage(effectiveConfig, tools, preparedDockerfile.path, expectedImageSignature, {
|
|
949
|
+
engine,
|
|
950
|
+
refresh: buildRefresh,
|
|
951
|
+
lastRefresh: buildLastRefresh,
|
|
952
|
+
buildProxyArgs: buildProxy.args,
|
|
953
|
+
buildEnv: buildProxy.env
|
|
954
|
+
});
|
|
1039
955
|
p.log.success(refreshDue
|
|
1040
956
|
? 'Image refreshed'
|
|
1041
957
|
: imageExists
|
|
@@ -1045,10 +961,12 @@ export async function create(args) {
|
|
|
1045
961
|
catch (error) {
|
|
1046
962
|
if (refreshDue && !signatureStale && imageExists) {
|
|
1047
963
|
p.log.warn('Scheduled sandbox image refresh failed; continuing with the existing image. ' +
|
|
1048
|
-
commandErrorMessage(error))
|
|
964
|
+
redactBuildProxyValues(commandErrorMessage(error), buildProxy.redactionValues)
|
|
965
|
+
+ (values['inherit-build-proxy'] ? ` ${buildProxyFailureHint(engine)}` : ''));
|
|
1049
966
|
}
|
|
1050
967
|
else {
|
|
1051
|
-
|
|
968
|
+
const hint = values['inherit-build-proxy'] ? `\n${buildProxyFailureHint(engine)}` : '';
|
|
969
|
+
throw new Error(`${redactBuildProxyValues(commandErrorMessage(error), buildProxy.redactionValues)}${hint}`);
|
|
1052
970
|
}
|
|
1053
971
|
}
|
|
1054
972
|
}
|
|
@@ -1063,7 +981,7 @@ export async function create(args) {
|
|
|
1063
981
|
if (fs.readdirSync(worktree).length > 0) {
|
|
1064
982
|
return `Worktree exists at ${worktree}`;
|
|
1065
983
|
}
|
|
1066
|
-
|
|
984
|
+
removeDirRecursive(worktree);
|
|
1067
985
|
}
|
|
1068
986
|
const branchExists = runOk('git', [
|
|
1069
987
|
'-C',
|
|
@@ -1152,7 +1070,7 @@ export async function create(args) {
|
|
|
1152
1070
|
runSafeEngine(engine, 'docker', ['rm', name]);
|
|
1153
1071
|
}
|
|
1154
1072
|
}
|
|
1155
|
-
const aliasesFile = ensureSandboxAliasesFile(effectiveConfig.home);
|
|
1073
|
+
const aliasesFile = ensureSandboxAliasesFile(effectiveConfig.home, capabilityPlan.aliases);
|
|
1156
1074
|
if (aliasesFile.created) {
|
|
1157
1075
|
message(`Created default sandbox aliases at ${aliasesFile.path}`);
|
|
1158
1076
|
}
|
|
@@ -1181,6 +1099,24 @@ export async function create(args) {
|
|
|
1181
1099
|
let hostShellConfig;
|
|
1182
1100
|
let tmpfsSeedPlan = [];
|
|
1183
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
|
+
}
|
|
1184
1120
|
const claudeCodeEntry = effectiveResolvedTools.find(({ tool }) => tool.id === 'claude-code');
|
|
1185
1121
|
if (claudeCodeEntry) {
|
|
1186
1122
|
ensureClaudeOnboarding(claudeCodeEntry.dir, effectiveConfig.home);
|
|
@@ -1189,11 +1125,6 @@ export async function create(args) {
|
|
|
1189
1125
|
// provider/API settings are enough for Claude Code. If no usable
|
|
1190
1126
|
// auth was present, the claude-code entry was removed above.
|
|
1191
1127
|
}
|
|
1192
|
-
const codexEntry = effectiveResolvedTools.find(({ tool }) => tool.id === 'codex');
|
|
1193
|
-
if (codexEntry) {
|
|
1194
|
-
ensureCodexModelInheritance(codexEntry.dir, effectiveConfig.home, codexEntry.tool.containerMount);
|
|
1195
|
-
ensureCodexWorkspaceTrust(codexEntry.dir);
|
|
1196
|
-
}
|
|
1197
1128
|
const geminiEntry = effectiveResolvedTools.find(({ tool }) => tool.id === 'gemini-cli');
|
|
1198
1129
|
if (geminiEntry) {
|
|
1199
1130
|
ensureGeminiWorkspaceTrust(geminiEntry.dir);
|
|
@@ -1267,6 +1198,8 @@ export async function create(args) {
|
|
|
1267
1198
|
sandboxLabel(effectiveConfig),
|
|
1268
1199
|
'--label',
|
|
1269
1200
|
`${sandboxBranchLabel(effectiveConfig)}=${branch}`,
|
|
1201
|
+
'--label',
|
|
1202
|
+
`${sandboxRuntimeCapabilityLabel(effectiveConfig)}=${capabilityPlan.runtimeSignature}`,
|
|
1270
1203
|
...coreVolumes,
|
|
1271
1204
|
...buildClipboardVolumeArgs(engine, effectiveConfig.home),
|
|
1272
1205
|
'-v',
|
|
@@ -1344,6 +1277,17 @@ export async function create(args) {
|
|
|
1344
1277
|
runSafeEngine(engine, 'docker', ['exec', container, 'bash', '-lc', command]);
|
|
1345
1278
|
}
|
|
1346
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
|
+
}
|
|
1347
1291
|
return 'Container started';
|
|
1348
1292
|
}
|
|
1349
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',
|
|
@@ -9,15 +9,17 @@ import { runEngine, runOkEngine, runSafeEngine, runVerboseEngine } from '../shel
|
|
|
9
9
|
import { pruneSandboxDanglingImages } from '../image-prune.js';
|
|
10
10
|
import { resolveTools } from '../tools.js';
|
|
11
11
|
import { buildImageSignature, buildSandboxImageArgs, parseImageLabels, parseRefreshTimestamp } from '../image-build.js';
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
import { assertBuildProxyCompatibility, buildProxyFailureHint, prepareBuildProxy, redactBuildProxyValues } from '../build-proxy.js';
|
|
13
|
+
const USAGE = `Usage: ai sandbox rebuild [--quiet] [--refresh] [--inherit-build-proxy|-B]`;
|
|
14
|
+
export function buildArgs(config, tools, dockerfilePath, imageSignature, { engine, runFn = runEngine, runSafeFn = runSafeEngine, env = process.env, refresh = false, lastRefresh, buildProxyArgs = [] } = {}) {
|
|
14
15
|
return buildSandboxImageArgs(config, tools, dockerfilePath, imageSignature, {
|
|
15
16
|
engine: engine ?? detectEngine(config),
|
|
16
17
|
runFn,
|
|
17
18
|
runSafeFn,
|
|
18
19
|
env,
|
|
19
20
|
refresh,
|
|
20
|
-
lastRefresh
|
|
21
|
+
lastRefresh,
|
|
22
|
+
buildProxyArgs
|
|
21
23
|
});
|
|
22
24
|
}
|
|
23
25
|
function readExistingLastRefresh(config, engine) {
|
|
@@ -41,6 +43,7 @@ export async function rebuild(args) {
|
|
|
41
43
|
options: {
|
|
42
44
|
refresh: { type: 'boolean' },
|
|
43
45
|
quiet: { type: 'boolean', short: 'q' },
|
|
46
|
+
'inherit-build-proxy': { type: 'boolean', short: 'B' },
|
|
44
47
|
help: { type: 'boolean', short: 'h' }
|
|
45
48
|
}
|
|
46
49
|
});
|
|
@@ -56,6 +59,12 @@ export async function rebuild(args) {
|
|
|
56
59
|
const refresh = values.refresh ?? false;
|
|
57
60
|
const engine = detectEngine(config);
|
|
58
61
|
await ensureDocker(config, undefined);
|
|
62
|
+
if (values['inherit-build-proxy'] && config.dockerfile) {
|
|
63
|
+
throw new Error('Build proxy inheritance is unavailable with a custom sandbox Dockerfile.');
|
|
64
|
+
}
|
|
65
|
+
const buildProxy = prepareBuildProxy(values['inherit-build-proxy'] ?? false, process.env, engine);
|
|
66
|
+
if (values['inherit-build-proxy'])
|
|
67
|
+
assertBuildProxyCompatibility(engine);
|
|
59
68
|
const lastRefresh = refresh ? Date.now() : readExistingLastRefresh(config, engine);
|
|
60
69
|
p.intro(pc.cyan('Rebuilding sandbox image'));
|
|
61
70
|
try {
|
|
@@ -65,9 +74,11 @@ export async function rebuild(args) {
|
|
|
65
74
|
runEngine(engine, 'docker', buildArgs(config, tools, preparedDockerfile.path, imageSignature, {
|
|
66
75
|
engine,
|
|
67
76
|
refresh,
|
|
68
|
-
lastRefresh
|
|
77
|
+
lastRefresh,
|
|
78
|
+
buildProxyArgs: buildProxy.args
|
|
69
79
|
}), {
|
|
70
|
-
cwd: config.repoRoot
|
|
80
|
+
cwd: config.repoRoot,
|
|
81
|
+
env: buildProxy.env
|
|
71
82
|
});
|
|
72
83
|
spinner.stop(pc.green('Sandbox image rebuilt'));
|
|
73
84
|
}
|
|
@@ -76,12 +87,18 @@ export async function rebuild(args) {
|
|
|
76
87
|
runVerboseEngine(engine, 'docker', buildArgs(config, tools, preparedDockerfile.path, imageSignature, {
|
|
77
88
|
engine,
|
|
78
89
|
refresh,
|
|
79
|
-
lastRefresh
|
|
80
|
-
|
|
90
|
+
lastRefresh,
|
|
91
|
+
buildProxyArgs: buildProxy.args
|
|
92
|
+
}), { cwd: config.repoRoot, env: buildProxy.env });
|
|
81
93
|
p.log.success(pc.green('Sandbox image rebuilt'));
|
|
82
94
|
}
|
|
83
95
|
pruneSandboxDanglingImages(config, engine);
|
|
84
96
|
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
const message = redactBuildProxyValues(error instanceof Error ? error.message : String(error), buildProxy.redactionValues);
|
|
99
|
+
const hint = values['inherit-build-proxy'] ? `\n${buildProxyFailureHint(engine)}` : '';
|
|
100
|
+
throw new Error(`${message}${hint}`);
|
|
101
|
+
}
|
|
85
102
|
finally {
|
|
86
103
|
preparedDockerfile.cleanup();
|
|
87
104
|
}
|