@fitlab-ai/agent-infra 0.8.2 → 0.8.4
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.zh-CN.md +10 -0
- package/bin/cli.ts +1 -1
- package/dist/bin/cli.js +2 -2
- package/dist/lib/cp.js +57 -16
- 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 +10 -8
- package/dist/lib/sandbox/clipboard/bridge.js +59 -21
- package/dist/lib/sandbox/clipboard/inbox.js +82 -0
- 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 +106 -88
- 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 +10 -4
- 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/base.dockerfile +3 -0
- package/dist/lib/sandbox/shell.js +9 -1
- package/dist/lib/sandbox/tools.js +4 -4
- package/dist/lib/server/adapters/feishu/index.js +10 -3
- package/dist/lib/server/adapters/feishu/renderer.js +88 -0
- package/dist/lib/server/adapters/feishu/transport.js +2 -13
- 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 +2 -2
- 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 +4 -4
- package/lib/cp.ts +56 -15
- package/lib/defaults.json +2 -0
- package/lib/sandbox/capture.ts +5 -3
- package/lib/sandbox/clipboard/bridge.ts +62 -18
- package/lib/sandbox/clipboard/inbox.ts +83 -0
- 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 +132 -88
- package/lib/sandbox/commands/rebuild.ts +42 -54
- package/lib/sandbox/config.ts +13 -1
- package/lib/sandbox/constants.ts +4 -0
- package/lib/sandbox/image-build.ts +134 -0
- package/lib/sandbox/runtimes/base.dockerfile +3 -0
- package/lib/sandbox/shell.ts +11 -2
- package/lib/sandbox/tools.ts +5 -5
- package/lib/server/adapters/_contract.ts +3 -0
- package/lib/server/adapters/feishu/index.ts +11 -3
- package/lib/server/adapters/feishu/renderer.ts +99 -0
- package/lib/server/adapters/feishu/transport.ts +5 -18
- package/lib/server/daemon.ts +74 -38
- package/lib/server/display.ts +136 -0
- 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/package.json +2 -2
- package/templates/.agents/README.en.md +3 -0
- package/templates/.agents/README.zh-CN.md +3 -0
- package/templates/.agents/rules/create-issue.github.en.md +4 -4
- package/templates/.agents/rules/create-issue.github.zh-CN.md +4 -4
- 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/review-handshake.en.md +2 -2
- package/templates/.agents/rules/review-handshake.zh-CN.md +2 -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/rules/testing-discipline.en.md +3 -41
- package/templates/.agents/rules/testing-discipline.zh-CN.md +3 -41
- package/templates/.agents/scripts/platform-adapters/platform-sync.github.js +57 -0
- package/templates/.agents/scripts/validate-artifact.js +213 -0
- package/templates/.agents/scripts/workflow-warnings.js +290 -0
- package/templates/.agents/skills/analyze-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/archive-tasks/scripts/archive-tasks.sh +1 -1
- package/templates/.agents/skills/block-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/block-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/cancel-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/check-task/SKILL.zh-CN.md +9 -9
- package/templates/.agents/skills/close-codescan/SKILL.en.md +1 -1
- package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/close-dependabot/SKILL.en.md +1 -1
- package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/code-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/code-task/reference/branch-management.zh-CN.md +2 -2
- package/templates/.agents/skills/code-task/reference/fix-mode.en.md +2 -2
- package/templates/.agents/skills/code-task/reference/fix-mode.zh-CN.md +2 -2
- package/templates/.agents/skills/code-task/reference/output-template.zh-CN.md +1 -1
- package/templates/.agents/skills/commit/SKILL.en.md +1 -1
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/commit/reference/task-status-update.en.md +6 -6
- package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +6 -6
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +142 -0
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +142 -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/complete-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/create-pr/SKILL.en.md +6 -4
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +6 -4
- package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +1 -1
- package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +1 -1
- package/templates/.agents/skills/create-task/SKILL.en.md +6 -3
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +9 -6
- package/templates/.agents/skills/import-codescan/SKILL.en.md +2 -2
- package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/import-dependabot/SKILL.en.md +2 -2
- package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/import-issue/SKILL.en.md +2 -2
- package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/init-labels/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/init-milestones/SKILL.en.md +3 -2
- package/templates/.agents/skills/init-milestones/SKILL.zh-CN.md +4 -3
- package/templates/.agents/skills/init-milestones/scripts/init-milestones.github.sh +165 -41
- package/templates/.agents/skills/plan-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/restore-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/review-analysis/SKILL.en.md +1 -1
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/review-analysis/reference/output-templates.en.md +5 -5
- package/templates/.agents/skills/review-analysis/reference/output-templates.zh-CN.md +5 -5
- package/templates/.agents/skills/review-code/SKILL.en.md +8 -4
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +6 -3
- package/templates/.agents/skills/review-code/config/verify.en.json +1 -0
- package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -0
- package/templates/.agents/skills/review-code/reference/output-templates.en.md +5 -5
- package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +5 -5
- package/templates/.agents/skills/review-code/reference/report-template.en.md +2 -2
- package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +2 -2
- package/templates/.agents/skills/review-plan/SKILL.en.md +1 -1
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/review-plan/reference/output-templates.en.md +5 -5
- package/templates/.agents/skills/review-plan/reference/output-templates.zh-CN.md +5 -5
- package/templates/.agents/skills/test/SKILL.zh-CN.md +26 -8
- package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +36 -6
- package/templates/.agents/skills/watch-pr/SKILL.en.md +2 -2
- package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +8 -1
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +8 -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
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
const PROBE_TIMEOUT_MS = 2_000;
|
|
6
|
+
const READ_IMAGE_TIMEOUT_MS = 5_000;
|
|
7
|
+
const PNG_MAGIC = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
|
|
8
|
+
const POWERSHELL_ARGS = ['-NoProfile', '-NonInteractive', '-STA', '-Command'];
|
|
9
|
+
export function createWin32ClipboardAdapter({ execFn = execFileSync, mkdtempFn = fs.mkdtempSync, readFileFn = fs.readFileSync, rmFn = fs.rmSync, existsFn = fs.existsSync } = {}) {
|
|
10
|
+
return {
|
|
11
|
+
available() {
|
|
12
|
+
try {
|
|
13
|
+
execFn('powershell.exe', [...POWERSHELL_ARGS, clipboardProbeScript()], {
|
|
14
|
+
encoding: 'utf8',
|
|
15
|
+
timeout: PROBE_TIMEOUT_MS
|
|
16
|
+
});
|
|
17
|
+
return { ok: true };
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return {
|
|
21
|
+
ok: false,
|
|
22
|
+
reason: 'Windows PowerShell Get-Clipboard is unavailable; install or enable powershell.exe to use image paste'
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
readImagePng() {
|
|
27
|
+
const tmpDir = mkdtempFn(path.join(os.tmpdir(), 'agent-infra-clipboard-'));
|
|
28
|
+
const outputPath = path.join(tmpDir, 'clipboard.png');
|
|
29
|
+
try {
|
|
30
|
+
execFn('powershell.exe', [...POWERSHELL_ARGS, pngWriteScript(outputPath)], {
|
|
31
|
+
encoding: 'utf8',
|
|
32
|
+
timeout: READ_IMAGE_TIMEOUT_MS
|
|
33
|
+
});
|
|
34
|
+
const png = Buffer.from(readFileFn(outputPath));
|
|
35
|
+
return isPng(png) ? png : null;
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
finally {
|
|
41
|
+
rmFn(tmpDir, { recursive: true, force: true });
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
readImageFromPath(imagePath) {
|
|
45
|
+
const tmpDir = mkdtempFn(path.join(os.tmpdir(), 'agent-infra-clipboard-'));
|
|
46
|
+
const outputPath = path.join(tmpDir, 'clipboard.png');
|
|
47
|
+
try {
|
|
48
|
+
execFn('powershell.exe', [...POWERSHELL_ARGS, pngFromPathScript(imagePath, outputPath)], {
|
|
49
|
+
encoding: 'utf8',
|
|
50
|
+
timeout: READ_IMAGE_TIMEOUT_MS
|
|
51
|
+
});
|
|
52
|
+
const png = Buffer.from(readFileFn(outputPath));
|
|
53
|
+
return isPng(png) ? png : null;
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
finally {
|
|
59
|
+
rmFn(tmpDir, { recursive: true, force: true });
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
readImageFromText(text) {
|
|
63
|
+
const trimmed = text.trim();
|
|
64
|
+
if (!isWindowsImagePath(trimmed)) {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
if (!existsFn(trimmed)) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
return this.readImageFromPath(trimmed);
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
function clipboardProbeScript() {
|
|
75
|
+
return 'if ($null -eq (Get-Command Get-Clipboard -ErrorAction SilentlyContinue)) { exit 1 }';
|
|
76
|
+
}
|
|
77
|
+
// Embed a filesystem path in a PowerShell single-quoted string literal.
|
|
78
|
+
// Single-quoted strings in PowerShell are verbatim (no variable expansion,
|
|
79
|
+
// no escape sequences). The only special character is a single quote itself,
|
|
80
|
+
// which is escaped by doubling it.
|
|
81
|
+
function psLiteral(value) {
|
|
82
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
83
|
+
}
|
|
84
|
+
function pngWriteScript(outputPath) {
|
|
85
|
+
const psOutputPath = psLiteral(outputPath);
|
|
86
|
+
return [
|
|
87
|
+
'$ErrorActionPreference = "Stop"',
|
|
88
|
+
`$outputPath = ${psOutputPath}`,
|
|
89
|
+
'$image = $null',
|
|
90
|
+
'try {',
|
|
91
|
+
' Add-Type -AssemblyName System.Drawing',
|
|
92
|
+
' $image = Get-Clipboard -Format Image -ErrorAction SilentlyContinue',
|
|
93
|
+
' if ($null -ne $image) {',
|
|
94
|
+
' $image.Save($outputPath, [System.Drawing.Imaging.ImageFormat]::Png)',
|
|
95
|
+
' return',
|
|
96
|
+
' }',
|
|
97
|
+
' $files = Get-Clipboard -Format FileDropList -ErrorAction SilentlyContinue',
|
|
98
|
+
' if ($null -ne $files) {',
|
|
99
|
+
' $allowed = @(".png", ".jpg", ".jpeg", ".gif", ".bmp", ".webp", ".tiff", ".tif", ".ico")',
|
|
100
|
+
' foreach ($file in $files) {',
|
|
101
|
+
' if (-not (Test-Path -LiteralPath $file -PathType Leaf)) { continue }',
|
|
102
|
+
' $ext = [System.IO.Path]::GetExtension($file)',
|
|
103
|
+
' if ($allowed -notcontains $ext) { continue }',
|
|
104
|
+
' try {',
|
|
105
|
+
' $image = [System.Drawing.Image]::FromFile($file)',
|
|
106
|
+
' $image.Save($outputPath, [System.Drawing.Imaging.ImageFormat]::Png)',
|
|
107
|
+
' return',
|
|
108
|
+
' } catch {}',
|
|
109
|
+
' }',
|
|
110
|
+
' }',
|
|
111
|
+
' throw "clipboard has no image"',
|
|
112
|
+
'} finally {',
|
|
113
|
+
' if ($null -ne $image -and $image -is [System.IDisposable]) { $image.Dispose() }',
|
|
114
|
+
'}'
|
|
115
|
+
].join('\n');
|
|
116
|
+
}
|
|
117
|
+
function pngFromPathScript(imagePath, outputPath) {
|
|
118
|
+
const psImagePath = psLiteral(imagePath);
|
|
119
|
+
const psOutputPath = psLiteral(outputPath);
|
|
120
|
+
return [
|
|
121
|
+
'$ErrorActionPreference = "Stop"',
|
|
122
|
+
`$imagePath = ${psImagePath}`,
|
|
123
|
+
`$outputPath = ${psOutputPath}`,
|
|
124
|
+
'$image = $null',
|
|
125
|
+
'try {',
|
|
126
|
+
' Add-Type -AssemblyName System.Drawing',
|
|
127
|
+
' if (-not (Test-Path -LiteralPath $imagePath -PathType Leaf)) { throw "file not found: $imagePath" }',
|
|
128
|
+
' $image = [System.Drawing.Image]::FromFile($imagePath)',
|
|
129
|
+
' $image.Save($outputPath, [System.Drawing.Imaging.ImageFormat]::Png)',
|
|
130
|
+
'} finally {',
|
|
131
|
+
' if ($null -ne $image -and $image -is [System.IDisposable]) { $image.Dispose() }',
|
|
132
|
+
'}'
|
|
133
|
+
].join('\n');
|
|
134
|
+
}
|
|
135
|
+
const IMAGE_EXTENSIONS_RE = /\.(png|jpg|jpeg|gif|bmp|webp|tiff?|ico)$/i;
|
|
136
|
+
function isWindowsImagePath(text) {
|
|
137
|
+
// Must be a Windows absolute path: drive letter + colon + backslash/slash
|
|
138
|
+
// followed by a path ending with a known image extension.
|
|
139
|
+
return /^[A-Za-z]:[\\/].+$/.test(text) && IMAGE_EXTENSIONS_RE.test(text);
|
|
140
|
+
}
|
|
141
|
+
function isPng(buffer) {
|
|
142
|
+
return buffer.length >= PNG_MAGIC.length && PNG_MAGIC.every((byte, index) => buffer[index] === byte);
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=win32.js.map
|
|
@@ -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);
|
|
@@ -218,12 +209,14 @@ export function writeSanitizedGitconfig({ home, hostConfigDir, stripGpg, repoRoo
|
|
|
218
209
|
// Keep in sync with the symlink block in lib/sandbox/runtimes/ai-tools.dockerfile.
|
|
219
210
|
const SHELL_CONFIG_SYMLINKS = ['.gitconfig', '.gitignore_global', '.stCommitMsg', '.bash_aliases'];
|
|
220
211
|
export function ensureShellConfigSymlinks(engine, container, execFn = execEngine) {
|
|
221
|
-
// Idempotent symlink setup.
|
|
222
|
-
//
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
212
|
+
// Idempotent symlink setup. Avoid a shell command here because Windows .cmd
|
|
213
|
+
// engine shims would interpret metacharacters in a `bash -lc` script before
|
|
214
|
+
// forwarding it to Docker.
|
|
215
|
+
for (const file of SHELL_CONFIG_SYMLINKS) {
|
|
216
|
+
execFn(engine, 'docker', [
|
|
217
|
+
'exec', container, 'ln', '-sf', `.host-shell-config/${file}`, `${CONTAINER_HOME}/${file}`
|
|
218
|
+
], { stdio: 'ignore' });
|
|
219
|
+
}
|
|
227
220
|
}
|
|
228
221
|
export function prepareHostShellConfig({ home, project, branch, repoRoot }) {
|
|
229
222
|
const hostDir = hostShellConfigDir(home, project, branch);
|
|
@@ -433,9 +426,12 @@ function formatEnvFileEntry(key, value) {
|
|
|
433
426
|
return `${key}=${value}`;
|
|
434
427
|
}
|
|
435
428
|
export function buildContainerEnvFile(resolvedTools, engine, runSafeEngineFn = runSafeEngine, options = {}) {
|
|
436
|
-
const { mkdtempFn = fs.mkdtempSync, writeFileFn = fs.writeFileSync, chmodFn = fs.chmodSync, rmFn = fs.rmSync, tmpDir = os.tmpdir() } = options;
|
|
429
|
+
const { mkdtempFn = fs.mkdtempSync, writeFileFn = fs.writeFileSync, chmodFn = fs.chmodSync, rmFn = fs.rmSync, tmpDir = os.tmpdir(), runSafeFn = runSafe } = options;
|
|
437
430
|
const entries = resolvedTools.flatMap(({ tool }) => Object.entries(tool.envVars ?? {}));
|
|
438
|
-
|
|
431
|
+
let ghToken = runSafeEngineFn(engine, 'gh', ['auth', 'token']);
|
|
432
|
+
if (!ghToken && engine === 'wsl2') {
|
|
433
|
+
ghToken = runSafeFn('gh', ['auth', 'token']);
|
|
434
|
+
}
|
|
439
435
|
if (ghToken) {
|
|
440
436
|
entries.push(['GH_TOKEN', ghToken]);
|
|
441
437
|
}
|
|
@@ -908,34 +904,25 @@ export function buildTmpfsRunArgs(containerMount, tmpfs) {
|
|
|
908
904
|
const size = tmpfs.size ?? '512m';
|
|
909
905
|
return ['--tmpfs', `${containerMount}:rw,size=${size}`];
|
|
910
906
|
}
|
|
911
|
-
export function buildImage(config, tools, dockerfilePath, imageSignature, { engine, runFn = runEngine, runSafeFn = runSafeEngine, runVerboseFn = runVerboseEngine, env = process.env } = {}) {
|
|
907
|
+
export function buildImage(config, tools, dockerfilePath, imageSignature, { engine, runFn = runEngine, runSafeFn = runSafeEngine, runVerboseFn = runVerboseEngine, env = process.env, refresh = false, lastRefresh } = {}) {
|
|
912
908
|
const selectedEngine = engine ?? detectEngine({ engine: config.engine });
|
|
913
|
-
|
|
909
|
+
runVerboseFn(selectedEngine, 'docker', buildSandboxImageArgs(config, tools, dockerfilePath, imageSignature, {
|
|
914
910
|
engine: selectedEngine,
|
|
915
911
|
runFn,
|
|
916
912
|
runSafeFn,
|
|
917
|
-
env
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
'
|
|
926
|
-
|
|
927
|
-
'
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
`AI_TOOLS_SHELL_INSTALL_B64=${toolShellInstallScriptBase64(tools)}`,
|
|
931
|
-
'--label',
|
|
932
|
-
sandboxLabel(config),
|
|
933
|
-
'--label',
|
|
934
|
-
`${sandboxImageConfigLabel(config)}=${imageSignature}`,
|
|
935
|
-
'-f',
|
|
936
|
-
toEnginePath(selectedEngine, dockerfilePath),
|
|
937
|
-
toEnginePath(selectedEngine, config.repoRoot)
|
|
938
|
-
], { cwd: config.repoRoot });
|
|
913
|
+
env,
|
|
914
|
+
refresh,
|
|
915
|
+
lastRefresh
|
|
916
|
+
}), { cwd: config.repoRoot });
|
|
917
|
+
}
|
|
918
|
+
function readImageLabels(config, engine) {
|
|
919
|
+
return parseImageLabels(runSafeEngine(engine, 'docker', [
|
|
920
|
+
'image',
|
|
921
|
+
'inspect',
|
|
922
|
+
'--format',
|
|
923
|
+
'{{ json .Config.Labels }}',
|
|
924
|
+
config.imageName
|
|
925
|
+
]));
|
|
939
926
|
}
|
|
940
927
|
export async function create(args) {
|
|
941
928
|
const { values, positionals } = parseArgs({
|
|
@@ -945,6 +932,7 @@ export async function create(args) {
|
|
|
945
932
|
options: {
|
|
946
933
|
cpu: { type: 'string' },
|
|
947
934
|
memory: { type: 'string' },
|
|
935
|
+
'no-refresh': { type: 'boolean' },
|
|
948
936
|
help: { type: 'boolean', short: 'h' }
|
|
949
937
|
}
|
|
950
938
|
});
|
|
@@ -986,7 +974,7 @@ export async function create(args) {
|
|
|
986
974
|
const shareBranch = shareBranchDir(effectiveConfig, branch);
|
|
987
975
|
const preparedDockerfile = prepareDockerfile(effectiveConfig);
|
|
988
976
|
const baseBranch = base ?? runSafe('git', ['-C', effectiveConfig.repoRoot, 'branch', '--show-current']);
|
|
989
|
-
const expectedImageSignature =
|
|
977
|
+
const expectedImageSignature = buildImageSignature(preparedDockerfile, tools);
|
|
990
978
|
const engine = detectEngine(effectiveConfig);
|
|
991
979
|
p.intro(pc.cyan('AI Sandbox'));
|
|
992
980
|
p.log.info(`Project: ${pc.bold(effectiveConfig.project)} | Branch: ${pc.bold(branch)} | Base: ${pc.bold(baseBranch || 'HEAD')}`);
|
|
@@ -1002,20 +990,41 @@ export async function create(args) {
|
|
|
1002
990
|
});
|
|
1003
991
|
p.log.success('Docker is ready');
|
|
1004
992
|
const imageExists = runOkEngine(engine, 'docker', ['image', 'inspect', effectiveConfig.imageName]);
|
|
1005
|
-
const
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
])
|
|
1013
|
-
|
|
1014
|
-
const needsImageBuild =
|
|
993
|
+
const imageLabels = imageExists ? readImageLabels(effectiveConfig, engine) : {};
|
|
994
|
+
const currentImageSignature = imageLabels[sandboxImageConfigLabel(effectiveConfig)] ?? '';
|
|
995
|
+
const currentLastRefresh = parseRefreshTimestamp(imageLabels[sandboxImageRefreshLabel(effectiveConfig)] ?? '');
|
|
996
|
+
const signatureStale = !imageExists || currentImageSignature !== expectedImageSignature;
|
|
997
|
+
const now = Date.now();
|
|
998
|
+
const refreshDue = imageExists
|
|
999
|
+
&& !signatureStale
|
|
1000
|
+
&& !isRefreshDisabled(process.env, values['no-refresh'] ?? false)
|
|
1001
|
+
&& isRefreshDue(currentLastRefresh, now, effectiveConfig.refreshIntervalDays);
|
|
1002
|
+
const needsImageBuild = signatureStale || refreshDue;
|
|
1015
1003
|
if (needsImageBuild) {
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
p.log.
|
|
1004
|
+
const buildRefresh = !imageExists || refreshDue;
|
|
1005
|
+
const buildLastRefresh = buildRefresh ? now : currentLastRefresh || 0;
|
|
1006
|
+
p.log.step(refreshDue
|
|
1007
|
+
? 'Refreshing stale image...'
|
|
1008
|
+
: imageExists
|
|
1009
|
+
? 'Rebuilding stale image...'
|
|
1010
|
+
: 'Building image for first use...');
|
|
1011
|
+
try {
|
|
1012
|
+
buildImage(effectiveConfig, tools, preparedDockerfile.path, expectedImageSignature, { engine, refresh: buildRefresh, lastRefresh: buildLastRefresh });
|
|
1013
|
+
p.log.success(refreshDue
|
|
1014
|
+
? 'Image refreshed'
|
|
1015
|
+
: imageExists
|
|
1016
|
+
? 'Image rebuilt'
|
|
1017
|
+
: 'Image built');
|
|
1018
|
+
}
|
|
1019
|
+
catch (error) {
|
|
1020
|
+
if (refreshDue && !signatureStale && imageExists) {
|
|
1021
|
+
p.log.warn('Scheduled sandbox image refresh failed; continuing with the existing image. ' +
|
|
1022
|
+
commandErrorMessage(error));
|
|
1023
|
+
}
|
|
1024
|
+
else {
|
|
1025
|
+
throw error;
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1019
1028
|
}
|
|
1020
1029
|
else {
|
|
1021
1030
|
p.log.step(`Using existing image ${effectiveConfig.imageName}`);
|
|
@@ -1141,6 +1150,7 @@ export async function create(args) {
|
|
|
1141
1150
|
: null;
|
|
1142
1151
|
const envFile = buildContainerEnvFile(effectiveResolvedTools, engine);
|
|
1143
1152
|
let hostShellConfig;
|
|
1153
|
+
let tmpfsSeedPlan = [];
|
|
1144
1154
|
try {
|
|
1145
1155
|
const claudeCodeEntry = effectiveResolvedTools.find(({ tool }) => tool.id === 'claude-code');
|
|
1146
1156
|
if (claudeCodeEntry) {
|
|
@@ -1177,21 +1187,23 @@ export async function create(args) {
|
|
|
1177
1187
|
'-v',
|
|
1178
1188
|
volumeArg(engine, hostPath, containerPath, ':ro')
|
|
1179
1189
|
]);
|
|
1180
|
-
//
|
|
1181
|
-
//
|
|
1182
|
-
// the
|
|
1183
|
-
//
|
|
1184
|
-
//
|
|
1185
|
-
//
|
|
1186
|
-
|
|
1187
|
-
// runtime files left in the host dir (e.g. a stale logs_2.sqlite or
|
|
1188
|
-
// sessions/ from a previous bind-mount era) must NOT be re-mounted,
|
|
1189
|
-
// or the high-churn writes would land on the host SSD again.
|
|
1190
|
-
const tmpfsSeedVolumes = effectiveResolvedTools.flatMap(({ tool, dir }) => (tool.tmpfs?.seed ?? []).flatMap((entry) => {
|
|
1190
|
+
// Mount each declared seed read-only at an isolated staging path.
|
|
1191
|
+
// After docker run mounts the empty tmpfs, the container's default
|
|
1192
|
+
// user copies these entries into it so the runtime targets are normal,
|
|
1193
|
+
// writable files rather than nested mount points. The allowlist keeps
|
|
1194
|
+
// stale runtime files (logs_2.sqlite, sessions, etc.) on the host from
|
|
1195
|
+
// being exposed or written through.
|
|
1196
|
+
tmpfsSeedPlan = effectiveResolvedTools.flatMap(({ tool, dir }) => (tool.tmpfs?.seed ?? []).flatMap((entry, index) => {
|
|
1191
1197
|
const hostPath = path.join(dir, entry);
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1198
|
+
if (!fs.existsSync(hostPath)) {
|
|
1199
|
+
return [];
|
|
1200
|
+
}
|
|
1201
|
+
const stagingPath = path.posix.join('/run/agent-infra/tmpfs-seeds', tool.id, String(index));
|
|
1202
|
+
return [{
|
|
1203
|
+
stagingPath,
|
|
1204
|
+
targetPath: path.posix.join(tool.containerMount, entry),
|
|
1205
|
+
volumeArgs: ['-v', volumeArg(engine, hostPath, stagingPath, ':ro')]
|
|
1206
|
+
}];
|
|
1195
1207
|
}));
|
|
1196
1208
|
const liveMountVolumes = effectiveResolvedTools.flatMap(({ tool }) => (tool.hostLiveMounts ?? [])
|
|
1197
1209
|
.filter(({ hostPath }) => fs.existsSync(hostPath))
|
|
@@ -1231,12 +1243,10 @@ export async function create(args) {
|
|
|
1231
1243
|
...buildClipboardVolumeArgs(engine, effectiveConfig.home),
|
|
1232
1244
|
'-v',
|
|
1233
1245
|
volumeArg(engine, path.join(effectiveConfig.repoRoot, '.git'), `${toEnginePath(engine, effectiveConfig.repoRoot)}/.git`),
|
|
1234
|
-
'-v',
|
|
1235
|
-
volumeArg(engine, hostJoin(effectiveConfig.home, '.ssh'), '/home/devuser/.ssh', ':ro'),
|
|
1236
1246
|
...dotfilesMount,
|
|
1237
1247
|
...toolVolumes,
|
|
1238
1248
|
...tmpfsArgs,
|
|
1239
|
-
...
|
|
1249
|
+
...tmpfsSeedPlan.flatMap(({ volumeArgs }) => volumeArgs),
|
|
1240
1250
|
...liveMountVolumes,
|
|
1241
1251
|
...shellConfigVolumes,
|
|
1242
1252
|
...envFile.dockerArgs,
|
|
@@ -1249,6 +1259,14 @@ export async function create(args) {
|
|
|
1249
1259
|
finally {
|
|
1250
1260
|
envFile.cleanup();
|
|
1251
1261
|
}
|
|
1262
|
+
for (const { stagingPath, targetPath } of tmpfsSeedPlan) {
|
|
1263
|
+
runEngineTaskCommand(engine, 'docker', [
|
|
1264
|
+
'exec', container, 'mkdir', '-p', path.posix.dirname(targetPath)
|
|
1265
|
+
]);
|
|
1266
|
+
runEngineTaskCommand(engine, 'docker', [
|
|
1267
|
+
'exec', container, 'cp', '-R', '--', stagingPath, targetPath
|
|
1268
|
+
]);
|
|
1269
|
+
}
|
|
1252
1270
|
// Belt-and-suspenders: re-create the four shell-config symlinks at
|
|
1253
1271
|
// runtime so users with a custom `sandbox.dockerfile` (which won't
|
|
1254
1272
|
// include the ai-tools.dockerfile symlink fragment) still get
|
|
@@ -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)) {
|