@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
|
@@ -7,7 +7,8 @@ import type { ExecFileSyncOptions, StdioOptions } from 'node:child_process';
|
|
|
7
7
|
import { parseArgs } from 'node:util';
|
|
8
8
|
import * as p from '@clack/prompts';
|
|
9
9
|
import pc from 'picocolors';
|
|
10
|
-
import
|
|
10
|
+
import { listAgentClientAdapters } from '../../agent-clients/registry.ts';
|
|
11
|
+
import type { SandboxAlias } from '../tool-types.ts';
|
|
11
12
|
import { loadConfig } from '../config.ts';
|
|
12
13
|
import {
|
|
13
14
|
assertValidBranchName,
|
|
@@ -18,12 +19,19 @@ import {
|
|
|
18
19
|
sandboxImageConfigLabel,
|
|
19
20
|
sandboxImageRefreshLabel,
|
|
20
21
|
sandboxLabel,
|
|
22
|
+
sandboxRuntimeCapabilityLabel,
|
|
21
23
|
shareBranchDir,
|
|
22
24
|
shareCommonDir,
|
|
23
25
|
shellConfigDir,
|
|
24
26
|
worktreeDirCandidates
|
|
25
27
|
} from '../constants.ts';
|
|
26
28
|
import { prepareDockerfile } from '../dockerfile.ts';
|
|
29
|
+
import {
|
|
30
|
+
assertBuildProxyCompatibility,
|
|
31
|
+
buildProxyFailureHint,
|
|
32
|
+
prepareBuildProxy,
|
|
33
|
+
redactBuildProxyValues
|
|
34
|
+
} from '../build-proxy.ts';
|
|
27
35
|
import { detectEngine, ensureDocker } from '../engine.ts';
|
|
28
36
|
import {
|
|
29
37
|
commandForEngine,
|
|
@@ -37,6 +45,11 @@ import {
|
|
|
37
45
|
runVerboseEngine
|
|
38
46
|
} from '../shell.ts';
|
|
39
47
|
import { resolveTaskBranch } from '../task-resolver.ts';
|
|
48
|
+
import {
|
|
49
|
+
createSandboxCapabilityPlan,
|
|
50
|
+
runSandboxHooks,
|
|
51
|
+
runBoundedSandboxHookCommand
|
|
52
|
+
} from '../agent-client-reconciler.ts';
|
|
40
53
|
import {
|
|
41
54
|
resolveTools,
|
|
42
55
|
tmpfsSeedStagingPath,
|
|
@@ -55,6 +68,7 @@ import { clipboardHostDir, CONTAINER_CLIPBOARD_MOUNT } from '../clipboard/paths.
|
|
|
55
68
|
import { validateSelinuxDisableEnv } from '../engines/selinux.ts';
|
|
56
69
|
import { dotfilesCacheDir, materializeDotfiles } from '../dotfiles.ts';
|
|
57
70
|
import { ensureSandboxDiscoveryReadmes } from '../readme-scaffold.ts';
|
|
71
|
+
import { removeDirRecursive } from '../../remove-dir.ts';
|
|
58
72
|
import {
|
|
59
73
|
prepareClaudeCredentials,
|
|
60
74
|
redactCommandError,
|
|
@@ -70,32 +84,16 @@ import {
|
|
|
70
84
|
parseRefreshTimestamp
|
|
71
85
|
} from '../image-build.ts';
|
|
72
86
|
|
|
73
|
-
|
|
87
|
+
export {
|
|
88
|
+
ensureCodexModelInheritance,
|
|
89
|
+
ensureCodexWorkspaceTrust
|
|
90
|
+
} from '../../agent-clients/adapters/codex-sandbox.ts';
|
|
91
|
+
|
|
74
92
|
const SANDBOX_ALIAS_BLOCK_BEGIN = '# >>> agent-infra managed aliases >>>';
|
|
75
93
|
const SANDBOX_ALIAS_BLOCK_END = '# <<< agent-infra managed aliases <<<';
|
|
76
|
-
const SANDBOX_ALIAS_NAMES = [
|
|
77
|
-
'claude-yolo',
|
|
78
|
-
'opencode-yolo',
|
|
79
|
-
'codex-yolo',
|
|
80
|
-
'gemini-yolo',
|
|
81
|
-
'cy',
|
|
82
|
-
'oy',
|
|
83
|
-
'xy',
|
|
84
|
-
'gy'
|
|
85
|
-
];
|
|
86
|
-
const DEFAULT_SANDBOX_ALIASES = `alias claude-yolo='claude --dangerously-skip-permissions; tput ed'
|
|
87
|
-
alias opencode-yolo='OPENCODE_PERMISSION='\\''${OPENCODE_YOLO_PERMISSION}'\\'' opencode; tput ed'
|
|
88
|
-
alias codex-yolo='codex --yolo; tput ed'
|
|
89
|
-
alias gemini-yolo='gemini --yolo; tput ed'
|
|
90
|
-
|
|
91
|
-
alias cy='claude --dangerously-skip-permissions; tput ed'
|
|
92
|
-
alias oy='OPENCODE_PERMISSION='\\''${OPENCODE_YOLO_PERMISSION}'\\'' opencode; tput ed'
|
|
93
|
-
alias xy='codex --yolo; tput ed'
|
|
94
|
-
alias gy='gemini --yolo; tput ed'
|
|
95
|
-
`;
|
|
96
94
|
const CONTAINER_HOME = '/home/devuser';
|
|
97
95
|
const CONTAINER_SHELL_CONFIG_MOUNT = `${CONTAINER_HOME}/.host-shell-config`;
|
|
98
|
-
const USAGE = `Usage: ai sandbox create <branch> [base] [--cpu <n>] [--memory <n>] [--no-refresh] [--inherit-proxy|-P]
|
|
96
|
+
const USAGE = `Usage: ai sandbox create <branch> [base] [--cpu <n>] [--memory <n>] [--no-refresh] [--inherit-proxy|-P] [--inherit-build-proxy|-B]
|
|
99
97
|
|
|
100
98
|
Host aliases:
|
|
101
99
|
${'~'}/.agent-infra/aliases/sandbox.sh is auto-created on first run and exposed
|
|
@@ -105,7 +103,9 @@ Host aliases:
|
|
|
105
103
|
|
|
106
104
|
Proxy:
|
|
107
105
|
--inherit-proxy, -P Copy non-empty standard host proxy variables into the
|
|
108
|
-
container environment through the private env file
|
|
106
|
+
container environment through the private env file.
|
|
107
|
+
--inherit-build-proxy, -B Pass uppercase HTTP_PROXY, HTTPS_PROXY, and NO_PROXY
|
|
108
|
+
to managed image build steps for this invocation.`;
|
|
109
109
|
const HOST_PROXY_ENV_KEYS = [
|
|
110
110
|
'http_proxy',
|
|
111
111
|
'HTTP_PROXY',
|
|
@@ -116,6 +116,15 @@ const HOST_PROXY_ENV_KEYS = [
|
|
|
116
116
|
'no_proxy',
|
|
117
117
|
'NO_PROXY'
|
|
118
118
|
] as const;
|
|
119
|
+
const MANAGED_GITHUB_GIT_CONFIG = [
|
|
120
|
+
'[credential]',
|
|
121
|
+
'\thelper = !gh auth git-credential',
|
|
122
|
+
'[credential "https://github.com"]',
|
|
123
|
+
'\thelper =',
|
|
124
|
+
'\thelper = !gh auth git-credential',
|
|
125
|
+
'[url "https://github.com/"]',
|
|
126
|
+
'\tinsteadOf = git@github.com:'
|
|
127
|
+
];
|
|
119
128
|
|
|
120
129
|
type SandboxCreateConfig = ReturnType<typeof loadConfig>;
|
|
121
130
|
type PreparedDockerfile = ReturnType<typeof prepareDockerfile>;
|
|
@@ -134,9 +143,9 @@ type ExecSyncOptions = ExecFileSyncOptions & {
|
|
|
134
143
|
};
|
|
135
144
|
type ExecSyncFn = (cmd: string, args: string[], options?: ExecSyncOptions) => Buffer | string;
|
|
136
145
|
type EngineExecFn = (engine: string, cmd: string, args: string[], opts?: ExecFileSyncOptions) => Buffer | string;
|
|
137
|
-
type EngineRunFn = (engine: string, cmd: string, args: string[], opts?: { cwd?: string }) => string;
|
|
146
|
+
type EngineRunFn = (engine: string, cmd: string, args: string[], opts?: { cwd?: string; env?: NodeJS.ProcessEnv }) => string;
|
|
138
147
|
type EngineRunSafeFn = EngineRunFn;
|
|
139
|
-
type EngineRunVerboseFn = (engine: string, cmd: string, args: string[], opts?: { cwd?: string }) => void;
|
|
148
|
+
type EngineRunVerboseFn = (engine: string, cmd: string, args: string[], opts?: { cwd?: string; env?: NodeJS.ProcessEnv }) => void;
|
|
140
149
|
type DirectRunFn = (cmd: string, args: string[], opts?: { cwd?: string }) => string;
|
|
141
150
|
type DirectRunSafeFn = DirectRunFn;
|
|
142
151
|
type DirectRunVerboseFn = (cmd: string, args: string[], opts?: { cwd?: string }) => void;
|
|
@@ -255,6 +264,16 @@ function normalizeContainerHomeSeparators(content: string): string {
|
|
|
255
264
|
return content.replace(containerHomePattern, (value) => value.replaceAll('\\', '/'));
|
|
256
265
|
}
|
|
257
266
|
|
|
267
|
+
function isGitHubCliCredentialHelper(line: string): boolean {
|
|
268
|
+
const helperMatch = line.match(/^\s*helper\s*=\s*(.+?)\s*$/i);
|
|
269
|
+
if (!helperMatch) {
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const helper = (helperMatch[1] ?? '').trim().replace(/^"(.*)"$/, '$1');
|
|
274
|
+
return /^!\s*(?:"[^"]*[\\/]+gh"|(?:\S*[\\/]+)?gh)\s+auth\s+git-credential(?:\s|$)/i.test(helper);
|
|
275
|
+
}
|
|
276
|
+
|
|
258
277
|
export function sanitizeGitConfig(
|
|
259
278
|
gitconfig: string,
|
|
260
279
|
home: string,
|
|
@@ -272,11 +291,13 @@ export function sanitizeGitConfig(
|
|
|
272
291
|
const sanitized = [];
|
|
273
292
|
let inGpgSection = false;
|
|
274
293
|
let currentSection = '';
|
|
294
|
+
let currentSectionName = '';
|
|
275
295
|
|
|
276
296
|
for (const line of lines) {
|
|
277
297
|
const sectionMatch = line.match(/^\s*\[([^\]]+)\]\s*$/);
|
|
278
298
|
if (sectionMatch) {
|
|
279
299
|
const sectionName = (sectionMatch[1] ?? '').trim();
|
|
300
|
+
currentSectionName = sectionName;
|
|
280
301
|
currentSection = ((sectionName.match(/^([^\s"]+)/)?.[1]) ?? '').toLowerCase();
|
|
281
302
|
inGpgSection = /^gpg(?:\s+"[^"]+")?$/i.test(sectionName);
|
|
282
303
|
if (stripGpg && inGpgSection) {
|
|
@@ -304,11 +325,23 @@ export function sanitizeGitConfig(
|
|
|
304
325
|
if (stripGpg && currentSection === 'user' && /^\s*signingKey\s*=.*$/i.test(line)) {
|
|
305
326
|
continue;
|
|
306
327
|
}
|
|
328
|
+
if (currentSection === 'credential' && isGitHubCliCredentialHelper(line)) {
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
if (
|
|
332
|
+
/^url\s+"https:\/\/github\.com\/"$/i.test(currentSectionName)
|
|
333
|
+
&& /^\s*insteadOf\s*=\s*git@github\.com:\s*$/i.test(line)
|
|
334
|
+
) {
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
307
337
|
|
|
308
338
|
sanitized.push(line);
|
|
309
339
|
}
|
|
310
340
|
|
|
311
|
-
return
|
|
341
|
+
return [
|
|
342
|
+
...appendSafeDirectories(sanitized, repoRoot),
|
|
343
|
+
...MANAGED_GITHUB_GIT_CONFIG
|
|
344
|
+
].join('\n');
|
|
312
345
|
}
|
|
313
346
|
|
|
314
347
|
export function hostHasGpgKeys(home: string, execFn: ExecSyncFn = execFileSync): boolean {
|
|
@@ -369,7 +402,7 @@ export function prepareHostShellConfig({
|
|
|
369
402
|
repoRoot: string;
|
|
370
403
|
}): HostShellConfig {
|
|
371
404
|
const hostDir = hostShellConfigDir(home, project, branch);
|
|
372
|
-
|
|
405
|
+
removeDirRecursive(hostDir);
|
|
373
406
|
fs.mkdirSync(hostDir, { recursive: true });
|
|
374
407
|
|
|
375
408
|
writeSanitizedGitconfig({
|
|
@@ -418,10 +451,19 @@ function normalizeWorktreePath(worktreePath: string): string {
|
|
|
418
451
|
return '';
|
|
419
452
|
}
|
|
420
453
|
|
|
454
|
+
const wslMount = process.platform === 'win32'
|
|
455
|
+
? worktreePath.match(/^\/mnt\/([A-Za-z])(?:\/(.*))?$/)
|
|
456
|
+
: null;
|
|
457
|
+
const hostPath = wslMount
|
|
458
|
+
? `${wslMount[1]!.toUpperCase()}:\\${(wslMount[2] ?? '').replace(/\//g, '\\')}`
|
|
459
|
+
: worktreePath;
|
|
460
|
+
|
|
421
461
|
try {
|
|
422
|
-
|
|
462
|
+
const normalized = fs.existsSync(hostPath) ? fs.realpathSync(hostPath) : path.resolve(hostPath);
|
|
463
|
+
return process.platform === 'win32' ? normalized.toLowerCase() : normalized;
|
|
423
464
|
} catch {
|
|
424
|
-
|
|
465
|
+
const normalized = path.resolve(hostPath);
|
|
466
|
+
return process.platform === 'win32' ? normalized.toLowerCase() : normalized;
|
|
425
467
|
}
|
|
426
468
|
}
|
|
427
469
|
|
|
@@ -673,7 +715,7 @@ export function buildContainerEnvFile(
|
|
|
673
715
|
mkdtempFn = fs.mkdtempSync,
|
|
674
716
|
writeFileFn = fs.writeFileSync,
|
|
675
717
|
chmodFn = fs.chmodSync,
|
|
676
|
-
rmFn = fs.rmSync,
|
|
718
|
+
rmFn = removeDirRecursive as typeof fs.rmSync,
|
|
677
719
|
tmpDir = os.tmpdir(),
|
|
678
720
|
runSafeFn = runSafe
|
|
679
721
|
} = options;
|
|
@@ -930,154 +972,6 @@ export function ensureClaudeSettings(toolDir: string, hostHomeDir?: string): voi
|
|
|
930
972
|
}
|
|
931
973
|
}
|
|
932
974
|
|
|
933
|
-
function resolveHostCatalogPath(value: unknown, hostHomeDir: string): string | null {
|
|
934
|
-
if (typeof value !== 'string' || value === '') {
|
|
935
|
-
return null;
|
|
936
|
-
}
|
|
937
|
-
let resolved: string;
|
|
938
|
-
if (value === '~' || value.startsWith('~/') || value.startsWith('~\\')) {
|
|
939
|
-
resolved = path.join(hostHomeDir, value.slice(1).replace(/^[/\\]+/, ''));
|
|
940
|
-
} else if (path.isAbsolute(value)) {
|
|
941
|
-
resolved = value;
|
|
942
|
-
} else {
|
|
943
|
-
resolved = path.join(hostHomeDir, '.codex', value);
|
|
944
|
-
}
|
|
945
|
-
try {
|
|
946
|
-
if (!fs.statSync(resolved).isFile()) {
|
|
947
|
-
return null;
|
|
948
|
-
}
|
|
949
|
-
fs.accessSync(resolved, fs.constants.R_OK);
|
|
950
|
-
return resolved;
|
|
951
|
-
} catch {
|
|
952
|
-
return null;
|
|
953
|
-
}
|
|
954
|
-
}
|
|
955
|
-
|
|
956
|
-
const CODEX_DISABLED_FEATURE_FLAGS = ['apps', 'enable_mcp_apps'] as const;
|
|
957
|
-
|
|
958
|
-
function removeCodexMcpServers(sandboxParsed: JsonObject): boolean {
|
|
959
|
-
if (!Object.hasOwn(sandboxParsed, 'mcp_servers')) {
|
|
960
|
-
return false;
|
|
961
|
-
}
|
|
962
|
-
delete sandboxParsed.mcp_servers;
|
|
963
|
-
return true;
|
|
964
|
-
}
|
|
965
|
-
|
|
966
|
-
function inheritDisabledCodexFeatureFlags(sandboxParsed: JsonObject, hostParsed: JsonObject): boolean {
|
|
967
|
-
if (!isJsonObjectRecord(hostParsed.features)) {
|
|
968
|
-
return false;
|
|
969
|
-
}
|
|
970
|
-
if (Object.hasOwn(sandboxParsed, 'features') && !isJsonObjectRecord(sandboxParsed.features)) {
|
|
971
|
-
return false;
|
|
972
|
-
}
|
|
973
|
-
|
|
974
|
-
let sandboxFeatures = sandboxParsed.features as JsonObject | undefined;
|
|
975
|
-
let changed = false;
|
|
976
|
-
for (const key of CODEX_DISABLED_FEATURE_FLAGS) {
|
|
977
|
-
if (hostParsed.features[key] !== false) {
|
|
978
|
-
continue;
|
|
979
|
-
}
|
|
980
|
-
if (!sandboxFeatures) {
|
|
981
|
-
sandboxFeatures = {};
|
|
982
|
-
sandboxParsed.features = sandboxFeatures;
|
|
983
|
-
}
|
|
984
|
-
if (sandboxFeatures[key] !== false) {
|
|
985
|
-
sandboxFeatures[key] = false;
|
|
986
|
-
changed = true;
|
|
987
|
-
}
|
|
988
|
-
}
|
|
989
|
-
return changed;
|
|
990
|
-
}
|
|
991
|
-
|
|
992
|
-
export function ensureCodexModelInheritance(
|
|
993
|
-
toolDir: string,
|
|
994
|
-
hostHomeDir?: string,
|
|
995
|
-
containerCodexDir: string = '/home/devuser/.codex'
|
|
996
|
-
): void {
|
|
997
|
-
if (!hostHomeDir) {
|
|
998
|
-
return;
|
|
999
|
-
}
|
|
1000
|
-
|
|
1001
|
-
const hostConfigPath = path.join(hostHomeDir, '.codex', 'config.toml');
|
|
1002
|
-
if (!fs.existsSync(hostConfigPath)) {
|
|
1003
|
-
return;
|
|
1004
|
-
}
|
|
1005
|
-
|
|
1006
|
-
let hostParsed: JsonObject;
|
|
1007
|
-
try {
|
|
1008
|
-
hostParsed = toml.parse(fs.readFileSync(hostConfigPath, 'utf8')) as JsonObject;
|
|
1009
|
-
} catch {
|
|
1010
|
-
return;
|
|
1011
|
-
}
|
|
1012
|
-
|
|
1013
|
-
const sandboxConfigPath = path.join(toolDir, 'config.toml');
|
|
1014
|
-
// This rewrites sandbox-side TOML and drops comments; the host config stays untouched.
|
|
1015
|
-
let sandboxParsed: JsonObject = {};
|
|
1016
|
-
if (fs.existsSync(sandboxConfigPath)) {
|
|
1017
|
-
try {
|
|
1018
|
-
sandboxParsed = toml.parse(fs.readFileSync(sandboxConfigPath, 'utf8')) as JsonObject;
|
|
1019
|
-
} catch {
|
|
1020
|
-
return;
|
|
1021
|
-
}
|
|
1022
|
-
}
|
|
1023
|
-
|
|
1024
|
-
let changed = removeCodexMcpServers(sandboxParsed);
|
|
1025
|
-
changed = inheritDisabledCodexFeatureFlags(sandboxParsed, hostParsed) || changed;
|
|
1026
|
-
|
|
1027
|
-
const inheritSpecs: Array<readonly [string, 'string' | 'number']> = [
|
|
1028
|
-
['model', 'string'],
|
|
1029
|
-
['model_reasoning_effort', 'string'],
|
|
1030
|
-
['model_auto_compact_token_limit', 'number']
|
|
1031
|
-
];
|
|
1032
|
-
|
|
1033
|
-
for (const [key, type] of inheritSpecs) {
|
|
1034
|
-
if (Object.hasOwn(sandboxParsed, key)) {
|
|
1035
|
-
continue;
|
|
1036
|
-
}
|
|
1037
|
-
const value = hostParsed[key];
|
|
1038
|
-
if (type === 'string' && (typeof value !== 'string' || value === '')) {
|
|
1039
|
-
continue;
|
|
1040
|
-
}
|
|
1041
|
-
if (type === 'number' && (typeof value !== 'number' || !Number.isFinite(value) || value <= 0)) {
|
|
1042
|
-
continue;
|
|
1043
|
-
}
|
|
1044
|
-
sandboxParsed[key] = value;
|
|
1045
|
-
changed = true;
|
|
1046
|
-
}
|
|
1047
|
-
|
|
1048
|
-
if (!Object.hasOwn(sandboxParsed, 'model_catalog_json')) {
|
|
1049
|
-
const hostCatalogPath = resolveHostCatalogPath(hostParsed['model_catalog_json'], hostHomeDir);
|
|
1050
|
-
if (hostCatalogPath) {
|
|
1051
|
-
try {
|
|
1052
|
-
const basename = path.basename(hostCatalogPath);
|
|
1053
|
-
const destDir = path.join(toolDir, 'model-catalogs');
|
|
1054
|
-
fs.mkdirSync(destDir, { recursive: true });
|
|
1055
|
-
fs.copyFileSync(hostCatalogPath, path.join(destDir, basename));
|
|
1056
|
-
sandboxParsed['model_catalog_json'] = path.posix.join(containerCodexDir, 'model-catalogs', basename);
|
|
1057
|
-
changed = true;
|
|
1058
|
-
} catch {
|
|
1059
|
-
// Copy failed (e.g. permissions): skip catalog, keep scalar inheritance intact.
|
|
1060
|
-
}
|
|
1061
|
-
}
|
|
1062
|
-
}
|
|
1063
|
-
|
|
1064
|
-
if (changed) {
|
|
1065
|
-
fs.writeFileSync(sandboxConfigPath, `${toml.stringify(sandboxParsed)}\n`, 'utf8');
|
|
1066
|
-
}
|
|
1067
|
-
}
|
|
1068
|
-
|
|
1069
|
-
export function ensureCodexWorkspaceTrust(toolDir: string): void {
|
|
1070
|
-
const configPath = path.join(toolDir, 'config.toml');
|
|
1071
|
-
let content = '';
|
|
1072
|
-
if (fs.existsSync(configPath)) {
|
|
1073
|
-
content = fs.readFileSync(configPath, 'utf8');
|
|
1074
|
-
}
|
|
1075
|
-
if (!content.includes('[projects."/workspace"]')) {
|
|
1076
|
-
const entry = '\n[projects."/workspace"]\ntrust_level = "trusted"\n';
|
|
1077
|
-
fs.writeFileSync(configPath, content + entry, 'utf8');
|
|
1078
|
-
}
|
|
1079
|
-
}
|
|
1080
|
-
|
|
1081
975
|
export function ensureOpenCodeModelInheritance(toolDir: string, hostHomeDir?: string): void {
|
|
1082
976
|
if (!hostHomeDir) {
|
|
1083
977
|
return;
|
|
@@ -1148,7 +1042,10 @@ function stripManagedSandboxAliasBlocks(content: string): string {
|
|
|
1148
1042
|
return content.replace(blockPattern, '').trimEnd();
|
|
1149
1043
|
}
|
|
1150
1044
|
|
|
1151
|
-
function isLegacyManagedSandboxAliasFile(
|
|
1045
|
+
function isLegacyManagedSandboxAliasFile(
|
|
1046
|
+
content: string,
|
|
1047
|
+
aliases: readonly SandboxAlias[]
|
|
1048
|
+
): boolean {
|
|
1152
1049
|
const lines = content
|
|
1153
1050
|
.split(/\r?\n/)
|
|
1154
1051
|
.map((line) => line.trim())
|
|
@@ -1158,13 +1055,29 @@ function isLegacyManagedSandboxAliasFile(content: string): boolean {
|
|
|
1158
1055
|
return false;
|
|
1159
1056
|
}
|
|
1160
1057
|
|
|
1161
|
-
const aliasPattern = new RegExp(
|
|
1058
|
+
const aliasPattern = new RegExp(
|
|
1059
|
+
`^alias (${aliases.map((alias) => escapeRegExp(alias.name)).join('|')})=`
|
|
1060
|
+
);
|
|
1162
1061
|
return lines.every((line) => aliasPattern.test(line));
|
|
1163
1062
|
}
|
|
1164
1063
|
|
|
1165
|
-
|
|
1064
|
+
function registrySandboxAliases(): readonly SandboxAlias[] {
|
|
1065
|
+
return listAgentClientAdapters().flatMap((adapter) => adapter.sandbox.aliases);
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
function renderSandboxAliases(aliases: readonly SandboxAlias[]): string {
|
|
1069
|
+
return `${aliases.map((alias) =>
|
|
1070
|
+
`alias ${alias.name}='${alias.command.replaceAll("'", "'\\''")}'`
|
|
1071
|
+
).join('\n')}\n`;
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
export function ensureSandboxAliasesFile(
|
|
1075
|
+
home: string,
|
|
1076
|
+
aliases: readonly SandboxAlias[] = registrySandboxAliases()
|
|
1077
|
+
): { created: boolean; path: string } {
|
|
1166
1078
|
const aliasesPath = sandboxAliasesPath(home);
|
|
1167
|
-
const managedBlock =
|
|
1079
|
+
const managedBlock =
|
|
1080
|
+
`${SANDBOX_ALIAS_BLOCK_BEGIN}\n${renderSandboxAliases(aliases)}${SANDBOX_ALIAS_BLOCK_END}\n`;
|
|
1168
1081
|
fs.mkdirSync(path.dirname(aliasesPath), { recursive: true });
|
|
1169
1082
|
const created = !fs.existsSync(aliasesPath);
|
|
1170
1083
|
let existing = '';
|
|
@@ -1173,7 +1086,7 @@ export function ensureSandboxAliasesFile(home: string): { created: boolean; path
|
|
|
1173
1086
|
existing = fs.readFileSync(aliasesPath, 'utf8');
|
|
1174
1087
|
}
|
|
1175
1088
|
|
|
1176
|
-
const userContent = isLegacyManagedSandboxAliasFile(existing)
|
|
1089
|
+
const userContent = isLegacyManagedSandboxAliasFile(existing, aliases)
|
|
1177
1090
|
? ''
|
|
1178
1091
|
: stripManagedSandboxAliasBlocks(existing);
|
|
1179
1092
|
const nextContent = userContent
|
|
@@ -1231,7 +1144,9 @@ export function buildImage(
|
|
|
1231
1144
|
runVerboseFn = runVerboseEngine,
|
|
1232
1145
|
env = process.env,
|
|
1233
1146
|
refresh = false,
|
|
1234
|
-
lastRefresh
|
|
1147
|
+
lastRefresh,
|
|
1148
|
+
buildProxyArgs = [],
|
|
1149
|
+
buildEnv
|
|
1235
1150
|
}: {
|
|
1236
1151
|
engine?: string;
|
|
1237
1152
|
runFn?: EngineRunFn;
|
|
@@ -1240,6 +1155,8 @@ export function buildImage(
|
|
|
1240
1155
|
env?: NodeJS.ProcessEnv;
|
|
1241
1156
|
refresh?: boolean;
|
|
1242
1157
|
lastRefresh?: number;
|
|
1158
|
+
buildProxyArgs?: string[];
|
|
1159
|
+
buildEnv?: NodeJS.ProcessEnv;
|
|
1243
1160
|
} = {}
|
|
1244
1161
|
): void {
|
|
1245
1162
|
const selectedEngine = engine ?? detectEngine({ engine: config.engine });
|
|
@@ -1252,9 +1169,10 @@ export function buildImage(
|
|
|
1252
1169
|
runSafeFn,
|
|
1253
1170
|
env,
|
|
1254
1171
|
refresh,
|
|
1255
|
-
lastRefresh
|
|
1172
|
+
lastRefresh,
|
|
1173
|
+
buildProxyArgs
|
|
1256
1174
|
}),
|
|
1257
|
-
{ cwd: config.repoRoot }
|
|
1175
|
+
{ cwd: config.repoRoot, env: buildEnv }
|
|
1258
1176
|
);
|
|
1259
1177
|
}
|
|
1260
1178
|
|
|
@@ -1278,6 +1196,7 @@ export async function create(args: string[]): Promise<void> {
|
|
|
1278
1196
|
memory: { type: 'string' },
|
|
1279
1197
|
'no-refresh': { type: 'boolean' },
|
|
1280
1198
|
'inherit-proxy': { type: 'boolean', short: 'P' },
|
|
1199
|
+
'inherit-build-proxy': { type: 'boolean', short: 'B' },
|
|
1281
1200
|
help: { type: 'boolean', short: 'h' }
|
|
1282
1201
|
}
|
|
1283
1202
|
});
|
|
@@ -1308,19 +1227,18 @@ export async function create(args: string[]): Promise<void> {
|
|
|
1308
1227
|
};
|
|
1309
1228
|
const worktreeCandidates = worktreeDirCandidates(effectiveConfig, branch);
|
|
1310
1229
|
assertBranchAvailable(config.repoRoot, branch, { allowedWorktrees: worktreeCandidates });
|
|
1311
|
-
const
|
|
1230
|
+
const capabilityPlan = createSandboxCapabilityPlan(effectiveConfig);
|
|
1231
|
+
const tools = [...capabilityPlan.tools];
|
|
1312
1232
|
const resolvedTools = resolveToolDirs(effectiveConfig, tools, branch);
|
|
1313
1233
|
// Fatal credential states still fail before filesystem/docker side effects.
|
|
1314
|
-
//
|
|
1315
|
-
//
|
|
1234
|
+
// Missing credentials leave the selected Claude Code capability installed
|
|
1235
|
+
// and mounted; only the absent live credential file is omitted.
|
|
1316
1236
|
const credentialOutcome = prepareClaudeCredentials(
|
|
1317
1237
|
effectiveConfig.home,
|
|
1318
1238
|
effectiveConfig.project,
|
|
1319
1239
|
resolvedTools
|
|
1320
1240
|
);
|
|
1321
|
-
const effectiveResolvedTools =
|
|
1322
|
-
? resolvedTools.filter(({ tool }) => tool.id !== 'claude-code')
|
|
1323
|
-
: resolvedTools;
|
|
1241
|
+
const effectiveResolvedTools = resolvedTools;
|
|
1324
1242
|
const container = containerName(effectiveConfig, branch);
|
|
1325
1243
|
const worktree = worktreeCandidates.find((candidate) => fs.existsSync(candidate)) ?? worktreeCandidates[0] ?? '';
|
|
1326
1244
|
const shareCommon = shareCommonDir(effectiveConfig);
|
|
@@ -1345,6 +1263,16 @@ export async function create(args: string[]): Promise<void> {
|
|
|
1345
1263
|
|
|
1346
1264
|
try {
|
|
1347
1265
|
p.log.step('Checking container engine...');
|
|
1266
|
+
const prepareHookResults = await runSandboxHooks({
|
|
1267
|
+
hooks: capabilityPlan.hooksByPhase.prepare,
|
|
1268
|
+
phase: 'prepare',
|
|
1269
|
+
context: { config: effectiveConfig, plan: capabilityPlan },
|
|
1270
|
+
runCommand: runBoundedSandboxHookCommand
|
|
1271
|
+
});
|
|
1272
|
+
const prepareFailure = prepareHookResults.find((result) => result.status === 'fatal');
|
|
1273
|
+
if (prepareFailure) {
|
|
1274
|
+
throw new Error(prepareFailure.message ?? `Sandbox hook '${prepareFailure.hookId}' failed.`);
|
|
1275
|
+
}
|
|
1348
1276
|
await ensureDocker(effectiveConfig, (detail: string) => {
|
|
1349
1277
|
p.log.info(` ${detail}`);
|
|
1350
1278
|
});
|
|
@@ -1363,6 +1291,15 @@ export async function create(args: string[]): Promise<void> {
|
|
|
1363
1291
|
const needsImageBuild = signatureStale || refreshDue;
|
|
1364
1292
|
|
|
1365
1293
|
if (needsImageBuild) {
|
|
1294
|
+
if (values['inherit-build-proxy'] && effectiveConfig.dockerfile) {
|
|
1295
|
+
throw new Error('Build proxy inheritance is unavailable with a custom sandbox Dockerfile.');
|
|
1296
|
+
}
|
|
1297
|
+
const buildProxy = prepareBuildProxy(
|
|
1298
|
+
values['inherit-build-proxy'] ?? false,
|
|
1299
|
+
process.env,
|
|
1300
|
+
engine
|
|
1301
|
+
);
|
|
1302
|
+
if (values['inherit-build-proxy']) assertBuildProxyCompatibility(engine);
|
|
1366
1303
|
const buildRefresh = !imageExists || refreshDue;
|
|
1367
1304
|
const buildLastRefresh = buildRefresh ? now : currentLastRefresh || 0;
|
|
1368
1305
|
|
|
@@ -1379,7 +1316,13 @@ export async function create(args: string[]): Promise<void> {
|
|
|
1379
1316
|
tools,
|
|
1380
1317
|
preparedDockerfile.path,
|
|
1381
1318
|
expectedImageSignature,
|
|
1382
|
-
{
|
|
1319
|
+
{
|
|
1320
|
+
engine,
|
|
1321
|
+
refresh: buildRefresh,
|
|
1322
|
+
lastRefresh: buildLastRefresh,
|
|
1323
|
+
buildProxyArgs: buildProxy.args,
|
|
1324
|
+
buildEnv: buildProxy.env
|
|
1325
|
+
}
|
|
1383
1326
|
);
|
|
1384
1327
|
p.log.success(
|
|
1385
1328
|
refreshDue
|
|
@@ -1392,10 +1335,14 @@ export async function create(args: string[]): Promise<void> {
|
|
|
1392
1335
|
if (refreshDue && !signatureStale && imageExists) {
|
|
1393
1336
|
p.log.warn(
|
|
1394
1337
|
'Scheduled sandbox image refresh failed; continuing with the existing image. ' +
|
|
1395
|
-
commandErrorMessage(error)
|
|
1338
|
+
redactBuildProxyValues(commandErrorMessage(error), buildProxy.redactionValues)
|
|
1339
|
+
+ (values['inherit-build-proxy'] ? ` ${buildProxyFailureHint(engine)}` : '')
|
|
1396
1340
|
);
|
|
1397
1341
|
} else {
|
|
1398
|
-
|
|
1342
|
+
const hint = values['inherit-build-proxy'] ? `\n${buildProxyFailureHint(engine)}` : '';
|
|
1343
|
+
throw new Error(
|
|
1344
|
+
`${redactBuildProxyValues(commandErrorMessage(error), buildProxy.redactionValues)}${hint}`
|
|
1345
|
+
);
|
|
1399
1346
|
}
|
|
1400
1347
|
}
|
|
1401
1348
|
} else {
|
|
@@ -1410,7 +1357,7 @@ export async function create(args: string[]): Promise<void> {
|
|
|
1410
1357
|
if (fs.readdirSync(worktree).length > 0) {
|
|
1411
1358
|
return `Worktree exists at ${worktree}`;
|
|
1412
1359
|
}
|
|
1413
|
-
|
|
1360
|
+
removeDirRecursive(worktree);
|
|
1414
1361
|
}
|
|
1415
1362
|
|
|
1416
1363
|
const branchExists = runOk('git', [
|
|
@@ -1507,7 +1454,10 @@ export async function create(args: string[]): Promise<void> {
|
|
|
1507
1454
|
}
|
|
1508
1455
|
}
|
|
1509
1456
|
|
|
1510
|
-
const aliasesFile = ensureSandboxAliasesFile(
|
|
1457
|
+
const aliasesFile = ensureSandboxAliasesFile(
|
|
1458
|
+
effectiveConfig.home,
|
|
1459
|
+
capabilityPlan.aliases
|
|
1460
|
+
);
|
|
1511
1461
|
if (aliasesFile.created) {
|
|
1512
1462
|
message(`Created default sandbox aliases at ${aliasesFile.path}`);
|
|
1513
1463
|
}
|
|
@@ -1543,6 +1493,28 @@ export async function create(args: string[]): Promise<void> {
|
|
|
1543
1493
|
let hostShellConfig: HostShellConfig;
|
|
1544
1494
|
let tmpfsSeedPlan: TmpfsSeedPlanEntry[] = [];
|
|
1545
1495
|
try {
|
|
1496
|
+
const beforeCreateResults = await runSandboxHooks({
|
|
1497
|
+
hooks: capabilityPlan.hooksByPhase['before-container-create'],
|
|
1498
|
+
phase: 'before-container-create',
|
|
1499
|
+
context: {
|
|
1500
|
+
config: effectiveConfig,
|
|
1501
|
+
plan: capabilityPlan,
|
|
1502
|
+
create: {
|
|
1503
|
+
hostHome: effectiveConfig.home,
|
|
1504
|
+
resolvedTools: effectiveResolvedTools
|
|
1505
|
+
}
|
|
1506
|
+
},
|
|
1507
|
+
runCommand: runBoundedSandboxHookCommand
|
|
1508
|
+
});
|
|
1509
|
+
const beforeCreateFailure = beforeCreateResults.find(
|
|
1510
|
+
(result) => result.status === 'fatal'
|
|
1511
|
+
);
|
|
1512
|
+
if (beforeCreateFailure) {
|
|
1513
|
+
throw new Error(
|
|
1514
|
+
beforeCreateFailure.message
|
|
1515
|
+
?? `Sandbox hook '${beforeCreateFailure.hookId}' failed.`
|
|
1516
|
+
);
|
|
1517
|
+
}
|
|
1546
1518
|
const claudeCodeEntry = effectiveResolvedTools.find(({ tool }) => tool.id === 'claude-code');
|
|
1547
1519
|
if (claudeCodeEntry) {
|
|
1548
1520
|
ensureClaudeOnboarding(claudeCodeEntry.dir, effectiveConfig.home);
|
|
@@ -1551,11 +1523,6 @@ export async function create(args: string[]): Promise<void> {
|
|
|
1551
1523
|
// provider/API settings are enough for Claude Code. If no usable
|
|
1552
1524
|
// auth was present, the claude-code entry was removed above.
|
|
1553
1525
|
}
|
|
1554
|
-
const codexEntry = effectiveResolvedTools.find(({ tool }) => tool.id === 'codex');
|
|
1555
|
-
if (codexEntry) {
|
|
1556
|
-
ensureCodexModelInheritance(codexEntry.dir, effectiveConfig.home, codexEntry.tool.containerMount);
|
|
1557
|
-
ensureCodexWorkspaceTrust(codexEntry.dir);
|
|
1558
|
-
}
|
|
1559
1526
|
const geminiEntry = effectiveResolvedTools.find(({ tool }) => tool.id === 'gemini-cli');
|
|
1560
1527
|
if (geminiEntry) {
|
|
1561
1528
|
ensureGeminiWorkspaceTrust(geminiEntry.dir);
|
|
@@ -1643,6 +1610,8 @@ export async function create(args: string[]): Promise<void> {
|
|
|
1643
1610
|
sandboxLabel(effectiveConfig),
|
|
1644
1611
|
'--label',
|
|
1645
1612
|
`${sandboxBranchLabel(effectiveConfig)}=${branch}`,
|
|
1613
|
+
'--label',
|
|
1614
|
+
`${sandboxRuntimeCapabilityLabel(effectiveConfig)}=${capabilityPlan.runtimeSignature}`,
|
|
1646
1615
|
...coreVolumes,
|
|
1647
1616
|
...buildClipboardVolumeArgs(engine, effectiveConfig.home),
|
|
1648
1617
|
'-v',
|
|
@@ -1739,6 +1708,22 @@ export async function create(args: string[]): Promise<void> {
|
|
|
1739
1708
|
}
|
|
1740
1709
|
}
|
|
1741
1710
|
|
|
1711
|
+
const afterStartResults = await runSandboxHooks({
|
|
1712
|
+
hooks: capabilityPlan.hooksByPhase['after-container-start'],
|
|
1713
|
+
phase: 'after-container-start',
|
|
1714
|
+
context: { config: effectiveConfig, plan: capabilityPlan },
|
|
1715
|
+
runCommand: runBoundedSandboxHookCommand
|
|
1716
|
+
});
|
|
1717
|
+
const afterStartFailure = afterStartResults.find(
|
|
1718
|
+
(result) => result.status === 'fatal'
|
|
1719
|
+
);
|
|
1720
|
+
if (afterStartFailure) {
|
|
1721
|
+
throw new Error(
|
|
1722
|
+
afterStartFailure.message
|
|
1723
|
+
?? `Sandbox hook '${afterStartFailure.hookId}' failed.`
|
|
1724
|
+
);
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1742
1727
|
return 'Container started';
|
|
1743
1728
|
}
|
|
1744
1729
|
}
|
|
@@ -7,6 +7,11 @@ import {
|
|
|
7
7
|
} from '../constants.ts';
|
|
8
8
|
import { detectEngine } from '../engine.ts';
|
|
9
9
|
import { ensureSandboxReady } from '../recovery.ts';
|
|
10
|
+
import {
|
|
11
|
+
createSandboxCapabilityPlan,
|
|
12
|
+
runBoundedSandboxHookCommand,
|
|
13
|
+
runSandboxHooks
|
|
14
|
+
} from '../agent-client-reconciler.ts';
|
|
10
15
|
import {
|
|
11
16
|
formatCredentialWarnings,
|
|
12
17
|
formatRemaining,
|
|
@@ -186,8 +191,25 @@ export async function enter(args: string[]): Promise<number> {
|
|
|
186
191
|
});
|
|
187
192
|
const container = ready.container;
|
|
188
193
|
const cmd = parsed.command;
|
|
194
|
+
const capabilityPlan = createSandboxCapabilityPlan(config);
|
|
195
|
+
const enterHookResults = await runSandboxHooks({
|
|
196
|
+
hooks: capabilityPlan.hooksByPhase['before-enter'],
|
|
197
|
+
phase: 'before-enter',
|
|
198
|
+
context: { config, plan: capabilityPlan },
|
|
199
|
+
runCommand: runBoundedSandboxHookCommand
|
|
200
|
+
});
|
|
201
|
+
for (const result of enterHookResults) {
|
|
202
|
+
if (result.status === 'fatal') {
|
|
203
|
+
throw new Error(result.message ?? `Sandbox hook '${result.hookId}' failed.`);
|
|
204
|
+
}
|
|
205
|
+
if (result.status === 'warning') {
|
|
206
|
+
process.stderr.write(
|
|
207
|
+
`Warning: ${result.message ?? `Sandbox hook '${result.hookId}' did not complete.`}\n`
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
189
211
|
|
|
190
|
-
if (
|
|
212
|
+
if (capabilityPlan.selectedAgentClients.some((adapter) => adapter.id === 'claude-code')) {
|
|
191
213
|
try {
|
|
192
214
|
// Scan all projects so a refresh from a neighbouring sandbox can still flow back to the host.
|
|
193
215
|
const providerAuthAvailable = hasClaudeProviderAuth(config.home);
|