@fitlab-ai/agent-infra 0.8.1 → 0.8.3
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 +1 -1
- package/README.zh-CN.md +1 -1
- package/bin/cli.ts +3 -3
- package/dist/bin/cli.js +4 -4
- package/dist/lib/cp.js +1 -1
- package/dist/lib/decide.js +2 -2
- package/dist/lib/defaults.json +2 -0
- package/dist/lib/init.js +7 -7
- package/dist/lib/merge.js +1 -1
- package/dist/lib/prompt.js +1 -1
- package/dist/lib/run/index.js +7 -7
- package/dist/lib/run/prompt.js +1 -1
- package/dist/lib/sandbox/capture.js +5 -5
- package/dist/lib/sandbox/clipboard/bridge.js +48 -13
- package/dist/lib/sandbox/clipboard/index.js +5 -6
- package/dist/lib/sandbox/clipboard/linux.js +91 -0
- package/dist/lib/sandbox/clipboard/paths.js +1 -1
- package/dist/lib/sandbox/clipboard/win32.js +144 -0
- package/dist/lib/sandbox/commands/create.js +101 -66
- package/dist/lib/sandbox/commands/enter.js +9 -9
- package/dist/lib/sandbox/commands/list-running.js +2 -2
- package/dist/lib/sandbox/commands/ls.js +7 -7
- package/dist/lib/sandbox/commands/prune.js +8 -8
- package/dist/lib/sandbox/commands/rebuild.js +38 -50
- package/dist/lib/sandbox/commands/refresh.js +2 -2
- package/dist/lib/sandbox/commands/rm.js +11 -11
- package/dist/lib/sandbox/commands/show.js +4 -4
- package/dist/lib/sandbox/commands/start.js +4 -4
- package/dist/lib/sandbox/commands/vm.js +4 -4
- package/dist/lib/sandbox/config.js +11 -5
- package/dist/lib/sandbox/constants.js +4 -1
- package/dist/lib/sandbox/credentials.js +1 -1
- package/dist/lib/sandbox/dotfiles.js +1 -1
- package/dist/lib/sandbox/engine.js +3 -3
- package/dist/lib/sandbox/engines/index.js +5 -5
- package/dist/lib/sandbox/engines/wsl2-paths.js +1 -1
- package/dist/lib/sandbox/image-build.js +80 -0
- package/dist/lib/sandbox/image-prune.js +2 -2
- package/dist/lib/sandbox/index.js +10 -10
- package/dist/lib/sandbox/managed-fs.js +1 -1
- package/dist/lib/sandbox/readme-scaffold.js +1 -1
- package/dist/lib/sandbox/runtimes/node20.dockerfile +1 -1
- package/dist/lib/sandbox/runtimes/node22.dockerfile +1 -1
- package/dist/lib/sandbox/tools.js +11 -2
- package/dist/lib/server/adapters/feishu/index.js +12 -3
- package/dist/lib/server/adapters/feishu/renderer.js +88 -0
- package/dist/lib/server/adapters/feishu/transport.js +9 -2
- package/dist/lib/server/daemon.js +64 -43
- package/dist/lib/server/display.js +83 -0
- package/dist/lib/server/index.js +2 -2
- package/dist/lib/server/process-control.js +2 -2
- package/dist/lib/server/protocol.js +22 -6
- package/dist/lib/server/streamer.js +5 -4
- package/dist/lib/task/commands/cat.js +2 -2
- package/dist/lib/task/commands/decisions.js +4 -4
- package/dist/lib/task/commands/files.js +3 -3
- package/dist/lib/task/commands/grep.js +3 -3
- package/dist/lib/task/commands/issue-body.js +4 -4
- package/dist/lib/task/commands/log.js +5 -5
- package/dist/lib/task/commands/ls.js +3 -3
- package/dist/lib/task/commands/show.js +1 -1
- package/dist/lib/task/commands/status.js +71 -25
- package/dist/lib/task/index.js +9 -9
- package/dist/lib/task/resolve-ref.js +1 -1
- package/dist/lib/task/workflow-warnings.js +94 -0
- package/dist/lib/update.js +35 -4
- package/lib/defaults.json +2 -0
- package/lib/sandbox/clipboard/bridge.ts +50 -8
- package/lib/sandbox/clipboard/index.ts +5 -6
- package/lib/sandbox/clipboard/linux.ts +124 -0
- package/lib/sandbox/clipboard/win32.ts +173 -0
- package/lib/sandbox/commands/create.ts +123 -65
- package/lib/sandbox/commands/rebuild.ts +42 -54
- package/lib/sandbox/config.ts +14 -2
- package/lib/sandbox/constants.ts +4 -0
- package/lib/sandbox/image-build.ts +134 -0
- package/lib/sandbox/runtimes/node20.dockerfile +1 -1
- package/lib/sandbox/runtimes/node22.dockerfile +1 -1
- package/lib/sandbox/tools.ts +9 -0
- package/lib/server/adapters/_contract.ts +3 -0
- package/lib/server/adapters/feishu/index.ts +12 -2
- package/lib/server/adapters/feishu/renderer.ts +99 -0
- package/lib/server/adapters/feishu/transport.ts +19 -3
- package/lib/server/daemon.ts +74 -38
- package/lib/server/display.ts +136 -0
- package/lib/server/protocol.ts +20 -4
- package/lib/server/streamer.ts +5 -4
- package/lib/task/commands/log.ts +3 -3
- package/lib/task/commands/status.ts +102 -21
- package/lib/task/workflow-warnings.ts +121 -0
- package/lib/update.ts +34 -0
- package/package.json +3 -3
- package/templates/.agents/README.en.md +3 -3
- package/templates/.agents/README.zh-CN.md +3 -3
- package/templates/.agents/rules/create-issue.github.en.md +3 -3
- package/templates/.agents/rules/create-issue.github.zh-CN.md +3 -3
- package/templates/.agents/rules/issue-pr-commands.github.en.md +7 -2
- package/templates/.agents/rules/issue-pr-commands.github.zh-CN.md +7 -2
- package/templates/.agents/rules/issue-sync.github.en.md +13 -0
- package/templates/.agents/rules/issue-sync.github.zh-CN.md +13 -0
- package/templates/.agents/rules/next-step-output.en.md +15 -1
- package/templates/.agents/rules/next-step-output.zh-CN.md +15 -1
- package/templates/.agents/rules/pr-sync.github.en.md +17 -2
- package/templates/.agents/rules/pr-sync.github.zh-CN.md +17 -2
- package/templates/.agents/rules/task-management.en.md +1 -1
- package/templates/.agents/rules/task-management.zh-CN.md +1 -1
- package/templates/.agents/scripts/platform-adapters/platform-sync.github.js +57 -0
- package/templates/.agents/scripts/validate-artifact.js +120 -0
- package/templates/.agents/scripts/workflow-warnings.js +290 -0
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +118 -0
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +118 -0
- package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +49 -0
- package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +49 -0
- package/templates/.agents/skills/complete-manual-validation/reference/report-template.en.md +48 -0
- package/templates/.agents/skills/complete-manual-validation/reference/report-template.zh-CN.md +48 -0
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +60 -0
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +87 -0
- package/templates/.agents/skills/create-pr/SKILL.en.md +2 -0
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +2 -0
- package/templates/.agents/skills/create-task/SKILL.en.md +4 -1
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +4 -1
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +33 -1
- package/templates/.agents/templates/task.en.md +7 -0
- package/templates/.agents/templates/task.zh-CN.md +7 -0
- package/templates/.claude/commands/complete-manual-validation.en.md +9 -0
- package/templates/.claude/commands/complete-manual-validation.zh-CN.md +9 -0
- package/templates/.gemini/commands/_project_/complete-manual-validation.en.toml +8 -0
- package/templates/.gemini/commands/_project_/complete-manual-validation.zh-CN.toml +8 -0
- package/templates/.github/workflows/metadata-sync.yml +5 -0
- package/templates/.opencode/commands/complete-manual-validation.en.md +11 -0
- package/templates/.opencode/commands/complete-manual-validation.zh-CN.md +11 -0
|
@@ -7,21 +7,21 @@ import { parseArgs } from 'node:util';
|
|
|
7
7
|
import * as p from '@clack/prompts';
|
|
8
8
|
import pc from 'picocolors';
|
|
9
9
|
import * as toml from 'smol-toml';
|
|
10
|
-
import { loadConfig } from
|
|
11
|
-
import { assertValidBranchName, containerName, containerNameCandidates, parsePositiveIntegerOption, sandboxBranchLabel, sandboxImageConfigLabel, sandboxLabel, shareBranchDir, shareCommonDir, shellConfigDir, worktreeDirCandidates } from
|
|
12
|
-
import { prepareDockerfile } from
|
|
13
|
-
import { detectEngine, ensureDocker } from
|
|
14
|
-
import { commandForEngine, execEngine, run, runEngine, runOk, runOkEngine, runSafe, runSafeEngine, runVerboseEngine } from
|
|
15
|
-
import { resolveTaskBranch } from
|
|
16
|
-
import {
|
|
17
|
-
import { hostJoin, toEnginePath, volumeArg } from
|
|
18
|
-
import { clipboardHostDir, CONTAINER_CLIPBOARD_MOUNT } from
|
|
19
|
-
import { validateSelinuxDisableEnv } from
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
10
|
+
import { loadConfig } from '../config.js';
|
|
11
|
+
import { assertValidBranchName, containerName, containerNameCandidates, parsePositiveIntegerOption, sandboxBranchLabel, sandboxImageConfigLabel, sandboxImageRefreshLabel, sandboxLabel, shareBranchDir, shareCommonDir, shellConfigDir, worktreeDirCandidates } from '../constants.js';
|
|
12
|
+
import { prepareDockerfile } from '../dockerfile.js';
|
|
13
|
+
import { detectEngine, ensureDocker } from '../engine.js';
|
|
14
|
+
import { commandForEngine, execEngine, run, runEngine, runOk, runOkEngine, runSafe, runSafeEngine, runVerboseEngine } from '../shell.js';
|
|
15
|
+
import { resolveTaskBranch } from '../task-resolver.js';
|
|
16
|
+
import { resolveTools, toolConfigDirCandidates } from '../tools.js';
|
|
17
|
+
import { hostJoin, toEnginePath, volumeArg } from '../engines/wsl2-paths.js';
|
|
18
|
+
import { clipboardHostDir, CONTAINER_CLIPBOARD_MOUNT } from '../clipboard/paths.js';
|
|
19
|
+
import { validateSelinuxDisableEnv } from '../engines/selinux.js';
|
|
20
|
+
import { dotfilesCacheDir, materializeDotfiles } from '../dotfiles.js';
|
|
21
|
+
import { ensureSandboxDiscoveryReadmes } from '../readme-scaffold.js';
|
|
22
|
+
import { prepareClaudeCredentials, redactCommandError, validateClaudeCredentialsEnvOverride } from '../credentials.js';
|
|
23
|
+
import { detectHostTimezone } from '../host-timezone.js';
|
|
24
|
+
import { buildImageSignature, buildSandboxImageArgs, isRefreshDisabled, isRefreshDue, parseImageLabels, parseRefreshTimestamp } from '../image-build.js';
|
|
25
25
|
const OPENCODE_YOLO_PERMISSION = '{"*":"allow","read":"allow","bash":"allow","edit":"allow","webfetch":"allow","external_directory":"allow","doom_loop":"allow"}';
|
|
26
26
|
const SANDBOX_ALIAS_BLOCK_BEGIN = '# >>> agent-infra managed aliases >>>';
|
|
27
27
|
const SANDBOX_ALIAS_BLOCK_END = '# <<< agent-infra managed aliases <<<';
|
|
@@ -47,22 +47,13 @@ alias gy='gemini --yolo; tput ed'
|
|
|
47
47
|
`;
|
|
48
48
|
const CONTAINER_HOME = '/home/devuser';
|
|
49
49
|
const CONTAINER_SHELL_CONFIG_MOUNT = `${CONTAINER_HOME}/.host-shell-config`;
|
|
50
|
-
const USAGE = `Usage: ai sandbox create <branch> [base] [--cpu <n>] [--memory <n>]
|
|
50
|
+
const USAGE = `Usage: ai sandbox create <branch> [base] [--cpu <n>] [--memory <n>] [--no-refresh]
|
|
51
51
|
|
|
52
52
|
Host aliases:
|
|
53
53
|
${'~'}/.agent-infra/aliases/sandbox.sh is auto-created on first run and exposed
|
|
54
54
|
as ${CONTAINER_HOME}/.bash_aliases inside the sandbox container (the host
|
|
55
55
|
shell-config directory is bind-mounted at ${CONTAINER_SHELL_CONFIG_MOUNT} and
|
|
56
56
|
symlinked into $HOME).`;
|
|
57
|
-
function buildSignature(preparedDockerfile, tools) {
|
|
58
|
-
return createHash('sha256')
|
|
59
|
-
.update(JSON.stringify({
|
|
60
|
-
dockerfile: preparedDockerfile.signature,
|
|
61
|
-
tools: imageSignatureFields(tools)
|
|
62
|
-
}))
|
|
63
|
-
.digest('hex')
|
|
64
|
-
.slice(0, 12);
|
|
65
|
-
}
|
|
66
57
|
function resolveToolDirs(config, tools, branch) {
|
|
67
58
|
return tools.map((tool) => {
|
|
68
59
|
const candidates = toolConfigDirCandidates(tool, config.project, branch);
|
|
@@ -678,6 +669,38 @@ function resolveHostCatalogPath(value, hostHomeDir) {
|
|
|
678
669
|
return null;
|
|
679
670
|
}
|
|
680
671
|
}
|
|
672
|
+
const CODEX_DISABLED_FEATURE_FLAGS = ['apps', 'enable_mcp_apps'];
|
|
673
|
+
function removeCodexMcpServers(sandboxParsed) {
|
|
674
|
+
if (!Object.hasOwn(sandboxParsed, 'mcp_servers')) {
|
|
675
|
+
return false;
|
|
676
|
+
}
|
|
677
|
+
delete sandboxParsed.mcp_servers;
|
|
678
|
+
return true;
|
|
679
|
+
}
|
|
680
|
+
function inheritDisabledCodexFeatureFlags(sandboxParsed, hostParsed) {
|
|
681
|
+
if (!isJsonObjectRecord(hostParsed.features)) {
|
|
682
|
+
return false;
|
|
683
|
+
}
|
|
684
|
+
if (Object.hasOwn(sandboxParsed, 'features') && !isJsonObjectRecord(sandboxParsed.features)) {
|
|
685
|
+
return false;
|
|
686
|
+
}
|
|
687
|
+
let sandboxFeatures = sandboxParsed.features;
|
|
688
|
+
let changed = false;
|
|
689
|
+
for (const key of CODEX_DISABLED_FEATURE_FLAGS) {
|
|
690
|
+
if (hostParsed.features[key] !== false) {
|
|
691
|
+
continue;
|
|
692
|
+
}
|
|
693
|
+
if (!sandboxFeatures) {
|
|
694
|
+
sandboxFeatures = {};
|
|
695
|
+
sandboxParsed.features = sandboxFeatures;
|
|
696
|
+
}
|
|
697
|
+
if (sandboxFeatures[key] !== false) {
|
|
698
|
+
sandboxFeatures[key] = false;
|
|
699
|
+
changed = true;
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
return changed;
|
|
703
|
+
}
|
|
681
704
|
export function ensureCodexModelInheritance(toolDir, hostHomeDir, containerCodexDir = '/home/devuser/.codex') {
|
|
682
705
|
if (!hostHomeDir) {
|
|
683
706
|
return;
|
|
@@ -704,12 +727,13 @@ export function ensureCodexModelInheritance(toolDir, hostHomeDir, containerCodex
|
|
|
704
727
|
return;
|
|
705
728
|
}
|
|
706
729
|
}
|
|
730
|
+
let changed = removeCodexMcpServers(sandboxParsed);
|
|
731
|
+
changed = inheritDisabledCodexFeatureFlags(sandboxParsed, hostParsed) || changed;
|
|
707
732
|
const inheritSpecs = [
|
|
708
733
|
['model', 'string'],
|
|
709
734
|
['model_reasoning_effort', 'string'],
|
|
710
735
|
['model_auto_compact_token_limit', 'number']
|
|
711
736
|
];
|
|
712
|
-
let changed = false;
|
|
713
737
|
for (const [key, type] of inheritSpecs) {
|
|
714
738
|
if (Object.hasOwn(sandboxParsed, key)) {
|
|
715
739
|
continue;
|
|
@@ -875,34 +899,25 @@ export function buildTmpfsRunArgs(containerMount, tmpfs) {
|
|
|
875
899
|
const size = tmpfs.size ?? '512m';
|
|
876
900
|
return ['--tmpfs', `${containerMount}:rw,size=${size}`];
|
|
877
901
|
}
|
|
878
|
-
export function buildImage(config, tools, dockerfilePath, imageSignature, { engine, runFn = runEngine, runSafeFn = runSafeEngine, runVerboseFn = runVerboseEngine, env = process.env } = {}) {
|
|
902
|
+
export function buildImage(config, tools, dockerfilePath, imageSignature, { engine, runFn = runEngine, runSafeFn = runSafeEngine, runVerboseFn = runVerboseEngine, env = process.env, refresh = false, lastRefresh } = {}) {
|
|
879
903
|
const selectedEngine = engine ?? detectEngine({ engine: config.engine });
|
|
880
|
-
|
|
904
|
+
runVerboseFn(selectedEngine, 'docker', buildSandboxImageArgs(config, tools, dockerfilePath, imageSignature, {
|
|
881
905
|
engine: selectedEngine,
|
|
882
906
|
runFn,
|
|
883
907
|
runSafeFn,
|
|
884
|
-
env
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
'
|
|
893
|
-
|
|
894
|
-
'
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
`AI_TOOLS_SHELL_INSTALL_B64=${toolShellInstallScriptBase64(tools)}`,
|
|
898
|
-
'--label',
|
|
899
|
-
sandboxLabel(config),
|
|
900
|
-
'--label',
|
|
901
|
-
`${sandboxImageConfigLabel(config)}=${imageSignature}`,
|
|
902
|
-
'-f',
|
|
903
|
-
toEnginePath(selectedEngine, dockerfilePath),
|
|
904
|
-
toEnginePath(selectedEngine, config.repoRoot)
|
|
905
|
-
], { cwd: config.repoRoot });
|
|
908
|
+
env,
|
|
909
|
+
refresh,
|
|
910
|
+
lastRefresh
|
|
911
|
+
}), { cwd: config.repoRoot });
|
|
912
|
+
}
|
|
913
|
+
function readImageLabels(config, engine) {
|
|
914
|
+
return parseImageLabels(runSafeEngine(engine, 'docker', [
|
|
915
|
+
'image',
|
|
916
|
+
'inspect',
|
|
917
|
+
'--format',
|
|
918
|
+
'{{ json .Config.Labels }}',
|
|
919
|
+
config.imageName
|
|
920
|
+
]));
|
|
906
921
|
}
|
|
907
922
|
export async function create(args) {
|
|
908
923
|
const { values, positionals } = parseArgs({
|
|
@@ -912,6 +927,7 @@ export async function create(args) {
|
|
|
912
927
|
options: {
|
|
913
928
|
cpu: { type: 'string' },
|
|
914
929
|
memory: { type: 'string' },
|
|
930
|
+
'no-refresh': { type: 'boolean' },
|
|
915
931
|
help: { type: 'boolean', short: 'h' }
|
|
916
932
|
}
|
|
917
933
|
});
|
|
@@ -953,7 +969,7 @@ export async function create(args) {
|
|
|
953
969
|
const shareBranch = shareBranchDir(effectiveConfig, branch);
|
|
954
970
|
const preparedDockerfile = prepareDockerfile(effectiveConfig);
|
|
955
971
|
const baseBranch = base ?? runSafe('git', ['-C', effectiveConfig.repoRoot, 'branch', '--show-current']);
|
|
956
|
-
const expectedImageSignature =
|
|
972
|
+
const expectedImageSignature = buildImageSignature(preparedDockerfile, tools);
|
|
957
973
|
const engine = detectEngine(effectiveConfig);
|
|
958
974
|
p.intro(pc.cyan('AI Sandbox'));
|
|
959
975
|
p.log.info(`Project: ${pc.bold(effectiveConfig.project)} | Branch: ${pc.bold(branch)} | Base: ${pc.bold(baseBranch || 'HEAD')}`);
|
|
@@ -969,20 +985,41 @@ export async function create(args) {
|
|
|
969
985
|
});
|
|
970
986
|
p.log.success('Docker is ready');
|
|
971
987
|
const imageExists = runOkEngine(engine, 'docker', ['image', 'inspect', effectiveConfig.imageName]);
|
|
972
|
-
const
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
])
|
|
980
|
-
|
|
981
|
-
const needsImageBuild =
|
|
988
|
+
const imageLabels = imageExists ? readImageLabels(effectiveConfig, engine) : {};
|
|
989
|
+
const currentImageSignature = imageLabels[sandboxImageConfigLabel(effectiveConfig)] ?? '';
|
|
990
|
+
const currentLastRefresh = parseRefreshTimestamp(imageLabels[sandboxImageRefreshLabel(effectiveConfig)] ?? '');
|
|
991
|
+
const signatureStale = !imageExists || currentImageSignature !== expectedImageSignature;
|
|
992
|
+
const now = Date.now();
|
|
993
|
+
const refreshDue = imageExists
|
|
994
|
+
&& !signatureStale
|
|
995
|
+
&& !isRefreshDisabled(process.env, values['no-refresh'] ?? false)
|
|
996
|
+
&& isRefreshDue(currentLastRefresh, now, effectiveConfig.refreshIntervalDays);
|
|
997
|
+
const needsImageBuild = signatureStale || refreshDue;
|
|
982
998
|
if (needsImageBuild) {
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
p.log.
|
|
999
|
+
const buildRefresh = !imageExists || refreshDue;
|
|
1000
|
+
const buildLastRefresh = buildRefresh ? now : currentLastRefresh || 0;
|
|
1001
|
+
p.log.step(refreshDue
|
|
1002
|
+
? 'Refreshing stale image...'
|
|
1003
|
+
: imageExists
|
|
1004
|
+
? 'Rebuilding stale image...'
|
|
1005
|
+
: 'Building image for first use...');
|
|
1006
|
+
try {
|
|
1007
|
+
buildImage(effectiveConfig, tools, preparedDockerfile.path, expectedImageSignature, { engine, refresh: buildRefresh, lastRefresh: buildLastRefresh });
|
|
1008
|
+
p.log.success(refreshDue
|
|
1009
|
+
? 'Image refreshed'
|
|
1010
|
+
: imageExists
|
|
1011
|
+
? 'Image rebuilt'
|
|
1012
|
+
: 'Image built');
|
|
1013
|
+
}
|
|
1014
|
+
catch (error) {
|
|
1015
|
+
if (refreshDue && !signatureStale && imageExists) {
|
|
1016
|
+
p.log.warn('Scheduled sandbox image refresh failed; continuing with the existing image. ' +
|
|
1017
|
+
commandErrorMessage(error));
|
|
1018
|
+
}
|
|
1019
|
+
else {
|
|
1020
|
+
throw error;
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
986
1023
|
}
|
|
987
1024
|
else {
|
|
988
1025
|
p.log.step(`Using existing image ${effectiveConfig.imageName}`);
|
|
@@ -1198,8 +1235,6 @@ export async function create(args) {
|
|
|
1198
1235
|
...buildClipboardVolumeArgs(engine, effectiveConfig.home),
|
|
1199
1236
|
'-v',
|
|
1200
1237
|
volumeArg(engine, path.join(effectiveConfig.repoRoot, '.git'), `${toEnginePath(engine, effectiveConfig.repoRoot)}/.git`),
|
|
1201
|
-
'-v',
|
|
1202
|
-
volumeArg(engine, hostJoin(effectiveConfig.home, '.ssh'), '/home/devuser/.ssh', ':ro'),
|
|
1203
1238
|
...dotfilesMount,
|
|
1204
1239
|
...toolVolumes,
|
|
1205
1240
|
...tmpfsArgs,
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { loadConfig } from
|
|
2
|
-
import { assertValidBranchName, containerNameCandidates, sandboxBranchLabel, sandboxLabel } from
|
|
3
|
-
import { detectEngine } from
|
|
4
|
-
import { formatCredentialWarnings, formatRemaining, hasClaudeProviderAuth, reconcileClaudeCredentials, redactCommandError, validateClaudeCredentialsEnvOverride } from
|
|
5
|
-
import { runInteractiveEngine } from
|
|
6
|
-
import { dotfilesCacheDir, materializeDotfiles } from
|
|
7
|
-
import { runInteractiveWithClipboardBridge } from
|
|
8
|
-
import { detectHostTimezone } from
|
|
9
|
-
import { fetchSandboxRows, resolveBranchArg, selectSandboxContainer, startSandboxContainer } from
|
|
1
|
+
import { loadConfig } from '../config.js';
|
|
2
|
+
import { assertValidBranchName, containerNameCandidates, sandboxBranchLabel, sandboxLabel } from '../constants.js';
|
|
3
|
+
import { detectEngine } from '../engine.js';
|
|
4
|
+
import { formatCredentialWarnings, formatRemaining, hasClaudeProviderAuth, reconcileClaudeCredentials, redactCommandError, validateClaudeCredentialsEnvOverride } from '../credentials.js';
|
|
5
|
+
import { runInteractiveEngine } from '../shell.js';
|
|
6
|
+
import { dotfilesCacheDir, materializeDotfiles } from '../dotfiles.js';
|
|
7
|
+
import { runInteractiveWithClipboardBridge } from '../clipboard/bridge.js';
|
|
8
|
+
import { detectHostTimezone } from '../host-timezone.js';
|
|
9
|
+
import { fetchSandboxRows, resolveBranchArg, selectSandboxContainer, startSandboxContainer } from './list-running.js';
|
|
10
10
|
const USAGE = `Usage: ai sandbox exec <branch | TASK-id | N | '#N'> [cmd...]
|
|
11
11
|
|
|
12
12
|
N (bare) and '#N' both reference the same active task short id from
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { spawnSync } from 'node:child_process';
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
-
import { runSafeEngine, runVerboseEngine } from
|
|
5
|
-
import { resolveTaskBranch } from
|
|
4
|
+
import { runSafeEngine, runVerboseEngine } from '../shell.js';
|
|
5
|
+
import { resolveTaskBranch } from '../task-resolver.js';
|
|
6
6
|
export function containerListFormat() {
|
|
7
7
|
return '{{.Names}}\t{{.Status}}\t{{.Labels}}';
|
|
8
8
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import * as p from '@clack/prompts';
|
|
2
2
|
import pc from 'picocolors';
|
|
3
|
-
import { loadConfig } from
|
|
4
|
-
import { sandboxBranchLabel, sandboxLabel } from
|
|
5
|
-
import { detectEngine } from
|
|
6
|
-
import { formatTable } from
|
|
7
|
-
import { lookupShortIdByBranch } from
|
|
8
|
-
import { fetchSandboxRows } from
|
|
9
|
-
export { containerListFormat, parseLabels } from
|
|
3
|
+
import { loadConfig } from '../config.js';
|
|
4
|
+
import { sandboxBranchLabel, sandboxLabel } from '../constants.js';
|
|
5
|
+
import { detectEngine } from '../engine.js';
|
|
6
|
+
import { formatTable } from '../../table.js';
|
|
7
|
+
import { lookupShortIdByBranch } from '../../task/short-id.js';
|
|
8
|
+
import { fetchSandboxRows } from './list-running.js';
|
|
9
|
+
export { containerListFormat, parseLabels } from './list-running.js';
|
|
10
10
|
const USAGE = `Usage: ai sandbox ls
|
|
11
11
|
|
|
12
12
|
Lists all containers for the current project. The '#' column is a
|
|
@@ -3,14 +3,14 @@ import path from 'node:path';
|
|
|
3
3
|
import { parseArgs } from 'node:util';
|
|
4
4
|
import * as p from '@clack/prompts';
|
|
5
5
|
import pc from 'picocolors';
|
|
6
|
-
import { loadConfig } from
|
|
7
|
-
import { safeNameCandidates, sandboxBranchLabel, sandboxLabel } from
|
|
8
|
-
import { detectEngine } from
|
|
9
|
-
import { hostJoin } from
|
|
10
|
-
import { removeManagedDir, removeWorktreeDir } from
|
|
11
|
-
import { parseLabels } from
|
|
12
|
-
import { runEngine, runSafe } from
|
|
13
|
-
import { resolveTools } from
|
|
6
|
+
import { loadConfig } from '../config.js';
|
|
7
|
+
import { safeNameCandidates, sandboxBranchLabel, sandboxLabel } from '../constants.js';
|
|
8
|
+
import { detectEngine } from '../engine.js';
|
|
9
|
+
import { hostJoin } from '../engines/wsl2-paths.js';
|
|
10
|
+
import { removeManagedDir, removeWorktreeDir } from '../managed-fs.js';
|
|
11
|
+
import { parseLabels } from './ls.js';
|
|
12
|
+
import { runEngine, runSafe } from '../shell.js';
|
|
13
|
+
import { resolveTools } from '../tools.js';
|
|
14
14
|
const USAGE = `Usage: ai sandbox prune [--dry-run]`;
|
|
15
15
|
function listChildDirs(base) {
|
|
16
16
|
if (!fs.existsSync(base)) {
|
|
@@ -1,58 +1,37 @@
|
|
|
1
1
|
import { parseArgs } from 'node:util';
|
|
2
|
-
import { createHash } from 'node:crypto';
|
|
3
2
|
import * as p from '@clack/prompts';
|
|
4
3
|
import pc from 'picocolors';
|
|
5
|
-
import { loadConfig } from
|
|
6
|
-
import { prepareDockerfile } from
|
|
7
|
-
import {
|
|
8
|
-
import { detectEngine, ensureDocker } from
|
|
9
|
-
import { runEngine, runSafeEngine, runVerboseEngine } from
|
|
10
|
-
import { pruneSandboxDanglingImages } from
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import { resolveBuildUid } from "../engines/native.js";
|
|
4
|
+
import { loadConfig } from '../config.js';
|
|
5
|
+
import { prepareDockerfile } from '../dockerfile.js';
|
|
6
|
+
import { sandboxImageRefreshLabel } from '../constants.js';
|
|
7
|
+
import { detectEngine, ensureDocker } from '../engine.js';
|
|
8
|
+
import { runEngine, runOkEngine, runSafeEngine, runVerboseEngine } from '../shell.js';
|
|
9
|
+
import { pruneSandboxDanglingImages } from '../image-prune.js';
|
|
10
|
+
import { resolveTools } from '../tools.js';
|
|
11
|
+
import { buildImageSignature, buildSandboxImageArgs, parseImageLabels, parseRefreshTimestamp } from '../image-build.js';
|
|
14
12
|
const USAGE = `Usage: ai sandbox rebuild [--quiet] [--refresh]`;
|
|
15
|
-
function
|
|
16
|
-
return
|
|
17
|
-
|
|
18
|
-
dockerfile: preparedDockerfile.signature,
|
|
19
|
-
tools: imageSignatureFields(tools)
|
|
20
|
-
}))
|
|
21
|
-
.digest('hex')
|
|
22
|
-
.slice(0, 12);
|
|
23
|
-
}
|
|
24
|
-
export function buildArgs(config, tools, dockerfilePath, imageSignature, { engine, runFn = runEngine, runSafeFn = runSafeEngine, env = process.env, refresh = false } = {}) {
|
|
25
|
-
const selectedEngine = engine ?? detectEngine(config);
|
|
26
|
-
const { uid: hostUid, gid: hostGid } = resolveBuildUid({
|
|
27
|
-
engine: selectedEngine,
|
|
13
|
+
export function buildArgs(config, tools, dockerfilePath, imageSignature, { engine, runFn = runEngine, runSafeFn = runSafeEngine, env = process.env, refresh = false, lastRefresh } = {}) {
|
|
14
|
+
return buildSandboxImageArgs(config, tools, dockerfilePath, imageSignature, {
|
|
15
|
+
engine: engine ?? detectEngine(config),
|
|
28
16
|
runFn,
|
|
29
17
|
runSafeFn,
|
|
30
|
-
env
|
|
18
|
+
env,
|
|
19
|
+
refresh,
|
|
20
|
+
lastRefresh
|
|
31
21
|
});
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
'--build-arg',
|
|
37
|
-
`HOST_UID=${hostUid}`,
|
|
38
|
-
'--build-arg',
|
|
39
|
-
`HOST_GID=${hostGid}`,
|
|
40
|
-
'--build-arg',
|
|
41
|
-
`AI_TOOL_PACKAGES=${toolNpmPackagesArg(tools)}`,
|
|
42
|
-
'--build-arg',
|
|
43
|
-
`AI_TOOLS_SHELL_INSTALL_B64=${toolShellInstallScriptBase64(tools)}`,
|
|
44
|
-
'--label',
|
|
45
|
-
sandboxLabel(config),
|
|
46
|
-
'--label',
|
|
47
|
-
`${sandboxImageConfigLabel(config)}=${imageSignature}`,
|
|
48
|
-
'-f',
|
|
49
|
-
toEnginePath(selectedEngine, dockerfilePath),
|
|
50
|
-
toEnginePath(selectedEngine, config.repoRoot)
|
|
51
|
-
];
|
|
52
|
-
if (refresh) {
|
|
53
|
-
args.splice(1, 0, '--no-cache', '--pull');
|
|
22
|
+
}
|
|
23
|
+
function readExistingLastRefresh(config, engine) {
|
|
24
|
+
if (!runOkEngine(engine, 'docker', ['image', 'inspect', config.imageName])) {
|
|
25
|
+
return 0;
|
|
54
26
|
}
|
|
55
|
-
|
|
27
|
+
const labels = parseImageLabels(runSafeEngine(engine, 'docker', [
|
|
28
|
+
'image',
|
|
29
|
+
'inspect',
|
|
30
|
+
'--format',
|
|
31
|
+
'{{ json .Config.Labels }}',
|
|
32
|
+
config.imageName
|
|
33
|
+
]));
|
|
34
|
+
return parseRefreshTimestamp(labels[sandboxImageRefreshLabel(config)] ?? '');
|
|
56
35
|
}
|
|
57
36
|
export async function rebuild(args) {
|
|
58
37
|
const { values } = parseArgs({
|
|
@@ -72,24 +51,33 @@ export async function rebuild(args) {
|
|
|
72
51
|
const config = loadConfig();
|
|
73
52
|
const tools = resolveTools(config);
|
|
74
53
|
const preparedDockerfile = prepareDockerfile(config);
|
|
75
|
-
const imageSignature =
|
|
54
|
+
const imageSignature = buildImageSignature(preparedDockerfile, tools);
|
|
76
55
|
const quiet = values.quiet ?? false;
|
|
77
56
|
const refresh = values.refresh ?? false;
|
|
78
57
|
const engine = detectEngine(config);
|
|
79
58
|
await ensureDocker(config, undefined);
|
|
59
|
+
const lastRefresh = refresh ? Date.now() : readExistingLastRefresh(config, engine);
|
|
80
60
|
p.intro(pc.cyan('Rebuilding sandbox image'));
|
|
81
61
|
try {
|
|
82
62
|
if (quiet) {
|
|
83
63
|
const spinner = p.spinner();
|
|
84
64
|
spinner.start('Building image...');
|
|
85
|
-
runEngine(engine, 'docker', buildArgs(config, tools, preparedDockerfile.path, imageSignature, {
|
|
65
|
+
runEngine(engine, 'docker', buildArgs(config, tools, preparedDockerfile.path, imageSignature, {
|
|
66
|
+
engine,
|
|
67
|
+
refresh,
|
|
68
|
+
lastRefresh
|
|
69
|
+
}), {
|
|
86
70
|
cwd: config.repoRoot
|
|
87
71
|
});
|
|
88
72
|
spinner.stop(pc.green('Sandbox image rebuilt'));
|
|
89
73
|
}
|
|
90
74
|
else {
|
|
91
75
|
p.log.step('Building image');
|
|
92
|
-
runVerboseEngine(engine, 'docker', buildArgs(config, tools, preparedDockerfile.path, imageSignature, {
|
|
76
|
+
runVerboseEngine(engine, 'docker', buildArgs(config, tools, preparedDockerfile.path, imageSignature, {
|
|
77
|
+
engine,
|
|
78
|
+
refresh,
|
|
79
|
+
lastRefresh
|
|
80
|
+
}), { cwd: config.repoRoot });
|
|
93
81
|
p.log.success(pc.green('Sandbox image rebuilt'));
|
|
94
82
|
}
|
|
95
83
|
pruneSandboxDanglingImages(config, engine);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { homedir } from 'node:os';
|
|
2
2
|
import { parseArgs } from 'node:util';
|
|
3
|
-
import { buildLockedGuidance, discoverProjects, formatCredentialWarnings, formatRemaining, reconcileClaudeCredentials, redactCommandError, validateClaudeCredentialsEnvOverride } from
|
|
4
|
-
import { runProbe } from
|
|
3
|
+
import { buildLockedGuidance, discoverProjects, formatCredentialWarnings, formatRemaining, reconcileClaudeCredentials, redactCommandError, validateClaudeCredentialsEnvOverride } from '../credentials.js';
|
|
4
|
+
import { runProbe } from '../shell.js';
|
|
5
5
|
const USAGE = 'Usage: ai sandbox refresh';
|
|
6
6
|
export function probeClaudeStatus(spawnFn = runProbe) {
|
|
7
7
|
const result = spawnFn('claude', ['/status'], {
|
|
@@ -3,21 +3,21 @@ import path from 'node:path';
|
|
|
3
3
|
import { parseArgs } from 'node:util';
|
|
4
4
|
import * as p from '@clack/prompts';
|
|
5
5
|
import pc from 'picocolors';
|
|
6
|
-
import { loadConfig } from
|
|
7
|
-
import { assertValidBranchName, containerNameCandidates, sandboxBranchLabel, sandboxLabel, shareBranchDir, shellConfigDirCandidates, worktreeDirCandidates } from
|
|
8
|
-
import { ENGINES, detectEngine, engineDisplayName, isManagedEngine, stopManagedVm } from
|
|
9
|
-
import { pruneSandboxDanglingImages } from
|
|
10
|
-
import { removeManagedDir, removeWorktreeDir } from
|
|
11
|
-
import { runOk, runSafe, runSafeEngine } from
|
|
12
|
-
import { resolveTaskBranch } from
|
|
13
|
-
import { resolveTools, toolConfigDirCandidates, toolProjectDirCandidates } from
|
|
14
|
-
import { fetchSandboxRows } from
|
|
15
|
-
import { lookupShortIdByBranch } from
|
|
6
|
+
import { loadConfig } from '../config.js';
|
|
7
|
+
import { assertValidBranchName, containerNameCandidates, sandboxBranchLabel, sandboxLabel, shareBranchDir, shellConfigDirCandidates, worktreeDirCandidates } from '../constants.js';
|
|
8
|
+
import { ENGINES, detectEngine, engineDisplayName, isManagedEngine, stopManagedVm } from '../engine.js';
|
|
9
|
+
import { pruneSandboxDanglingImages } from '../image-prune.js';
|
|
10
|
+
import { removeManagedDir, removeWorktreeDir } from '../managed-fs.js';
|
|
11
|
+
import { runOk, runSafe, runSafeEngine } from '../shell.js';
|
|
12
|
+
import { resolveTaskBranch } from '../task-resolver.js';
|
|
13
|
+
import { resolveTools, toolConfigDirCandidates, toolProjectDirCandidates } from '../tools.js';
|
|
14
|
+
import { fetchSandboxRows } from './list-running.js';
|
|
15
|
+
import { lookupShortIdByBranch } from '../../task/short-id.js';
|
|
16
16
|
const USAGE = `Usage:
|
|
17
17
|
ai sandbox rm <branch> Remove one sandbox (branch | TASK-id | short id)
|
|
18
18
|
ai sandbox rm --all [--dry-run] [--yes] Remove every sandbox not bound to an active task
|
|
19
19
|
ai sandbox rm --purge Tear down ALL sandboxes for the project (containers, worktrees, image, VM)`;
|
|
20
|
-
export { assertManagedPath } from
|
|
20
|
+
export { assertManagedPath } from '../managed-fs.js';
|
|
21
21
|
function projectToolDirs(config, tools) {
|
|
22
22
|
return tools.flatMap((tool) => toolProjectDirCandidates(tool, config.project));
|
|
23
23
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import * as p from '@clack/prompts';
|
|
3
3
|
import pc from 'picocolors';
|
|
4
|
-
import { loadConfig } from
|
|
5
|
-
import { assertValidBranchName, worktreeDirCandidates } from
|
|
6
|
-
import { resolveBranchArg } from
|
|
7
|
-
import { resolveTools, toolConfigDirCandidates } from
|
|
4
|
+
import { loadConfig } from '../config.js';
|
|
5
|
+
import { assertValidBranchName, worktreeDirCandidates } from '../constants.js';
|
|
6
|
+
import { resolveBranchArg } from './list-running.js';
|
|
7
|
+
import { resolveTools, toolConfigDirCandidates } from '../tools.js';
|
|
8
8
|
const USAGE = `Usage: ai sandbox show <branch | TASK-id | N | '#N'>
|
|
9
9
|
|
|
10
10
|
Shows one sandbox's worktree path and per-tool state paths (Claude Code,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { loadConfig } from
|
|
2
|
-
import { assertValidBranchName, containerNameCandidates, sandboxBranchLabel, sandboxLabel } from
|
|
3
|
-
import { detectEngine } from
|
|
4
|
-
import { fetchSandboxRows, resolveBranchArg, selectSandboxContainer, startSandboxContainer } from
|
|
1
|
+
import { loadConfig } from '../config.js';
|
|
2
|
+
import { assertValidBranchName, containerNameCandidates, sandboxBranchLabel, sandboxLabel } from '../constants.js';
|
|
3
|
+
import { detectEngine } from '../engine.js';
|
|
4
|
+
import { fetchSandboxRows, resolveBranchArg, selectSandboxContainer, startSandboxContainer } from './list-running.js';
|
|
5
5
|
const USAGE = `Usage: ai sandbox start <branch | TASK-id | N | '#N'>
|
|
6
6
|
|
|
7
7
|
Start an existing sandbox container that has stopped (for example after the
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { parseArgs } from 'node:util';
|
|
2
2
|
import * as p from '@clack/prompts';
|
|
3
3
|
import pc from 'picocolors';
|
|
4
|
-
import { loadConfig } from
|
|
5
|
-
import { parsePositiveIntegerOption } from
|
|
6
|
-
import { ENGINES, detectEngine, engineDisplayName, isManagedEngine, startManagedVm, stopManagedVm } from
|
|
7
|
-
import { runOk, runSafe } from
|
|
4
|
+
import { loadConfig } from '../config.js';
|
|
5
|
+
import { parsePositiveIntegerOption } from '../constants.js';
|
|
6
|
+
import { ENGINES, detectEngine, engineDisplayName, isManagedEngine, startManagedVm, stopManagedVm } from '../engine.js';
|
|
7
|
+
import { runOk, runSafe } from '../shell.js';
|
|
8
8
|
const USAGE = `Usage: ai sandbox vm <status|start|stop> [--cpu <n>] [--memory <n>]`;
|
|
9
9
|
export function ensureManagedVm(engine) {
|
|
10
10
|
if (engine === ENGINES.NATIVE) {
|
|
@@ -3,14 +3,15 @@ import path from 'node:path';
|
|
|
3
3
|
import { homedir, platform } from 'node:os';
|
|
4
4
|
import { execFileSync } from 'node:child_process';
|
|
5
5
|
import pc from 'picocolors';
|
|
6
|
-
import { validateSandboxEngine } from
|
|
7
|
-
import { hostJoin } from
|
|
8
|
-
import { findRuntimeEngineMismatches } from
|
|
9
|
-
import { parseCustomTools } from
|
|
6
|
+
import { validateSandboxEngine } from './engine.js';
|
|
7
|
+
import { hostJoin } from './engines/wsl2-paths.js';
|
|
8
|
+
import { findRuntimeEngineMismatches } from './runtime-engines.js';
|
|
9
|
+
import { parseCustomTools } from './tools.js';
|
|
10
10
|
const DEFAULTS = Object.freeze({
|
|
11
11
|
engine: null,
|
|
12
12
|
runtimes: ['node22'],
|
|
13
|
-
tools: ['claude-code', 'codex', 'gemini-cli', 'opencode'],
|
|
13
|
+
tools: ['agent-infra', 'claude-code', 'codex', 'gemini-cli', 'opencode'],
|
|
14
|
+
refreshIntervalDays: 7,
|
|
14
15
|
dockerfile: null,
|
|
15
16
|
vm: {
|
|
16
17
|
cpu: null,
|
|
@@ -32,11 +33,15 @@ function detectRepoRoot() {
|
|
|
32
33
|
function asPositiveNumberOrNull(value) {
|
|
33
34
|
return typeof value === 'number' ? value : null;
|
|
34
35
|
}
|
|
36
|
+
function asNonNegativeIntegerOrDefault(value, fallback) {
|
|
37
|
+
return typeof value === 'number' && Number.isInteger(value) && value >= 0 ? value : fallback;
|
|
38
|
+
}
|
|
35
39
|
function cloneDefaults() {
|
|
36
40
|
return {
|
|
37
41
|
engine: DEFAULTS.engine,
|
|
38
42
|
runtimes: [...DEFAULTS.runtimes],
|
|
39
43
|
tools: [...DEFAULTS.tools],
|
|
44
|
+
refreshIntervalDays: DEFAULTS.refreshIntervalDays,
|
|
40
45
|
dockerfile: DEFAULTS.dockerfile,
|
|
41
46
|
vm: { ...DEFAULTS.vm }
|
|
42
47
|
};
|
|
@@ -96,6 +101,7 @@ export function loadConfig({ platformFn = platform, writeStderr = (chunk) => pro
|
|
|
96
101
|
? [...sandbox.tools]
|
|
97
102
|
: defaults.tools,
|
|
98
103
|
customTools,
|
|
104
|
+
refreshIntervalDays: asNonNegativeIntegerOrDefault(sandbox.refreshIntervalDays, defaults.refreshIntervalDays),
|
|
99
105
|
dockerfile,
|
|
100
106
|
vm: {
|
|
101
107
|
cpu: asPositiveNumberOrNull(sandbox.vm?.cpu) ?? defaults.vm.cpu,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import os from 'node:os';
|
|
2
2
|
import { execFileSync } from 'node:child_process';
|
|
3
|
-
import { hostJoin } from
|
|
3
|
+
import { hostJoin } from './engines/wsl2-paths.js';
|
|
4
4
|
const validatedBranches = new Set();
|
|
5
5
|
function dedupe(items) {
|
|
6
6
|
return [...new Set(items)];
|
|
@@ -72,6 +72,9 @@ export function sandboxBranchLabel(config) {
|
|
|
72
72
|
export function sandboxImageConfigLabel(config) {
|
|
73
73
|
return `${sandboxLabel(config)}.image-config`;
|
|
74
74
|
}
|
|
75
|
+
export function sandboxImageRefreshLabel(config) {
|
|
76
|
+
return `${sandboxLabel(config)}.last-refresh`;
|
|
77
|
+
}
|
|
75
78
|
export function parsePositiveIntegerOption(value, optionName) {
|
|
76
79
|
if (value === undefined || value === null) {
|
|
77
80
|
return undefined;
|
|
@@ -2,7 +2,7 @@ import { execFileSync } from 'node:child_process';
|
|
|
2
2
|
import { randomBytes } from 'node:crypto';
|
|
3
3
|
import fs from 'node:fs';
|
|
4
4
|
import path from 'node:path';
|
|
5
|
-
import { hostJoin } from
|
|
5
|
+
import { hostJoin } from './engines/wsl2-paths.js';
|
|
6
6
|
const LOCKED_PATTERN = /errSecInteractionNotAllowed|User interaction is not allowed/i;
|
|
7
7
|
const NOT_FOUND_PATTERN = /errSecItemNotFound|specified item could not be found/i;
|
|
8
8
|
const REDACTION_PATTERNS = [
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { hostJoin } from
|
|
3
|
+
import { hostJoin } from './engines/wsl2-paths.js';
|
|
4
4
|
export function dotfilesCacheDir(home, project) {
|
|
5
5
|
return hostJoin(home, '.agent-infra', '.cache', 'dotfiles-resolved', project);
|
|
6
6
|
}
|