@fitlab-ai/agent-infra 0.6.5 → 0.7.1
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 +51 -25
- package/README.zh-CN.md +49 -23
- package/bin/cli.ts +1 -1
- package/dist/bin/cli.js +1 -1
- package/dist/lib/builtin-tuis.js +45 -0
- package/dist/lib/defaults.json +4 -0
- package/dist/lib/init.js +65 -23
- package/dist/lib/prompt.js +49 -1
- package/dist/lib/sandbox/commands/create.js +4 -2
- package/dist/lib/sandbox/commands/enter.js +15 -4
- package/dist/lib/sandbox/commands/list-running.js +153 -0
- package/dist/lib/sandbox/commands/ls.js +24 -45
- package/dist/lib/sandbox/commands/rebuild.js +7 -13
- package/dist/lib/sandbox/commands/rm.js +2 -0
- package/dist/lib/sandbox/config.js +3 -0
- package/dist/lib/sandbox/image-prune.js +18 -0
- package/dist/lib/sandbox/index.js +2 -1
- package/dist/lib/sandbox/runtimes/ai-tools.dockerfile +10 -6
- package/dist/lib/sandbox/task-resolver.js +18 -0
- package/dist/lib/sandbox/tools.js +213 -8
- package/dist/lib/update.js +70 -18
- package/lib/builtin-tuis.ts +55 -0
- package/lib/defaults.json +4 -0
- package/lib/init.ts +97 -35
- package/lib/prompt.ts +54 -1
- package/lib/sandbox/commands/create.ts +10 -2
- package/lib/sandbox/commands/enter.ts +14 -4
- package/lib/sandbox/commands/list-running.ts +188 -0
- package/lib/sandbox/commands/ls.ts +28 -49
- package/lib/sandbox/commands/rebuild.ts +12 -14
- package/lib/sandbox/commands/rm.ts +3 -0
- package/lib/sandbox/config.ts +7 -0
- package/lib/sandbox/image-prune.ts +23 -0
- package/lib/sandbox/index.ts +2 -1
- package/lib/sandbox/runtimes/ai-tools.dockerfile +10 -6
- package/lib/sandbox/task-resolver.ts +23 -1
- package/lib/sandbox/tools.ts +248 -9
- package/lib/update.ts +85 -30
- package/package.json +1 -1
- package/templates/.agents/QUICKSTART.en.md +1 -1
- package/templates/.agents/QUICKSTART.zh-CN.md +1 -1
- package/templates/.agents/README.en.md +111 -2
- package/templates/.agents/README.zh-CN.md +111 -2
- package/templates/.agents/rules/create-issue.en.md +1 -1
- package/templates/.agents/rules/create-issue.github.en.md +1 -1
- package/templates/.agents/rules/create-issue.github.zh-CN.md +1 -1
- package/templates/.agents/rules/create-issue.zh-CN.md +1 -1
- package/templates/.agents/rules/issue-sync.github.en.md +6 -5
- package/templates/.agents/rules/issue-sync.github.zh-CN.md +6 -5
- package/templates/.agents/rules/milestone-inference.github.en.md +2 -2
- package/templates/.agents/rules/milestone-inference.github.zh-CN.md +2 -2
- package/templates/.agents/rules/no-mid-flow-questions.en.md +57 -0
- package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +57 -0
- package/templates/.agents/rules/pr-sync.github.en.md +4 -5
- package/templates/.agents/rules/pr-sync.github.zh-CN.md +4 -5
- package/templates/.agents/rules/task-management.en.md +9 -6
- package/templates/.agents/rules/task-management.zh-CN.md +9 -6
- package/templates/.agents/rules/task-short-id.en.md +141 -0
- package/templates/.agents/rules/task-short-id.zh-CN.md +124 -0
- package/templates/.agents/rules/testing-discipline.en.md +2 -2
- package/templates/.agents/rules/testing-discipline.zh-CN.md +2 -2
- package/templates/.agents/scripts/task-short-id.js +713 -0
- package/templates/.agents/scripts/validate-artifact.js +1 -1
- package/templates/.agents/skills/analyze-task/SKILL.en.md +20 -4
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +20 -5
- package/templates/.agents/skills/block-task/SKILL.en.md +12 -0
- package/templates/.agents/skills/block-task/SKILL.zh-CN.md +12 -1
- package/templates/.agents/skills/cancel-task/SKILL.en.md +12 -0
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +12 -1
- package/templates/.agents/skills/check-task/SKILL.en.md +47 -32
- package/templates/.agents/skills/check-task/SKILL.zh-CN.md +46 -32
- package/templates/.agents/skills/close-codescan/SKILL.en.md +11 -0
- package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +11 -0
- package/templates/.agents/skills/close-dependabot/SKILL.en.md +11 -0
- package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +11 -0
- package/templates/.agents/skills/code-task/SKILL.en.md +121 -0
- package/templates/.agents/skills/{implement-task → code-task}/SKILL.zh-CN.md +55 -25
- package/templates/.agents/skills/{implement-task → code-task}/config/verify.en.json +4 -4
- package/templates/.agents/skills/{implement-task → code-task}/config/verify.zh-CN.json +4 -4
- package/templates/.agents/skills/{implement-task → code-task}/reference/branch-management.zh-CN.md +2 -2
- package/templates/.agents/skills/{implement-task/reference/implementation-rules.en.md → code-task/reference/code-rules.en.md} +6 -6
- package/templates/.agents/skills/{implement-task/reference/implementation-rules.zh-CN.md → code-task/reference/code-rules.zh-CN.md} +3 -3
- package/templates/.agents/skills/code-task/reference/dual-mode.en.md +69 -0
- package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +69 -0
- package/templates/.agents/skills/{refine-task/reference/fix-workflow.en.md → code-task/reference/fix-mode.en.md} +12 -12
- package/templates/.agents/skills/{refine-task/reference/fix-workflow.zh-CN.md → code-task/reference/fix-mode.zh-CN.md} +8 -8
- package/templates/.agents/skills/code-task/reference/output-template.en.md +20 -0
- package/templates/.agents/skills/code-task/reference/output-template.zh-CN.md +20 -0
- package/templates/.agents/skills/{implement-task → code-task}/reference/report-template.en.md +4 -4
- package/templates/.agents/skills/{implement-task → code-task}/reference/report-template.zh-CN.md +3 -3
- package/templates/.agents/skills/code-task/scripts/detect-mode.js +370 -0
- package/templates/.agents/skills/commit/SKILL.en.md +6 -2
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +6 -2
- package/templates/.agents/skills/commit/reference/task-status-update.en.md +10 -6
- package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +10 -6
- package/templates/.agents/skills/complete-task/SKILL.en.md +17 -3
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +17 -4
- package/templates/.agents/skills/create-pr/SKILL.en.md +21 -1
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +21 -1
- package/templates/.agents/skills/create-task/SKILL.en.md +14 -0
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +14 -1
- package/templates/.agents/skills/import-codescan/SKILL.en.md +15 -1
- package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +15 -1
- package/templates/.agents/skills/import-dependabot/SKILL.en.md +16 -2
- package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +16 -2
- package/templates/.agents/skills/import-issue/SKILL.en.md +17 -3
- package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +17 -3
- package/templates/.agents/skills/plan-task/SKILL.en.md +8 -4
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +8 -5
- package/templates/.agents/skills/restore-task/SKILL.en.md +16 -3
- package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +16 -4
- package/templates/.agents/skills/review-analysis/SKILL.en.md +80 -0
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +105 -0
- package/templates/.agents/skills/review-analysis/config/verify.en.json +51 -0
- package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +51 -0
- package/templates/.agents/skills/review-analysis/reference/output-templates.en.md +87 -0
- package/templates/.agents/skills/review-analysis/reference/output-templates.zh-CN.md +87 -0
- package/templates/.agents/skills/review-analysis/reference/report-template.en.md +90 -0
- package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +91 -0
- package/templates/.agents/skills/review-analysis/reference/review-criteria.en.md +47 -0
- package/templates/.agents/skills/review-analysis/reference/review-criteria.zh-CN.md +47 -0
- package/templates/.agents/skills/{review-task → review-code}/SKILL.en.md +15 -9
- package/templates/.agents/skills/{review-task → review-code}/SKILL.zh-CN.md +19 -10
- package/templates/.agents/skills/{review-task → review-code}/config/verify.en.json +7 -5
- package/templates/.agents/skills/{review-task → review-code}/config/verify.zh-CN.json +6 -4
- package/templates/.agents/skills/{review-task → review-code}/reference/output-templates.en.md +21 -17
- package/templates/.agents/skills/{review-task → review-code}/reference/output-templates.zh-CN.md +19 -15
- package/templates/.agents/skills/{review-task → review-code}/reference/report-template.en.md +5 -6
- package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +91 -0
- package/templates/.agents/skills/review-code/reference/review-criteria.en.md +48 -0
- package/templates/.agents/skills/{review-task → review-code}/reference/review-criteria.zh-CN.md +10 -4
- package/templates/.agents/skills/review-plan/SKILL.en.md +80 -0
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +105 -0
- package/templates/.agents/skills/{refine-task → review-plan}/config/verify.en.json +14 -10
- package/templates/.agents/skills/{refine-task → review-plan}/config/verify.zh-CN.json +14 -10
- package/templates/.agents/skills/review-plan/reference/output-templates.en.md +87 -0
- package/templates/.agents/skills/review-plan/reference/output-templates.zh-CN.md +87 -0
- package/templates/.agents/skills/review-plan/reference/report-template.en.md +90 -0
- package/templates/.agents/skills/{review-task → review-plan}/reference/report-template.zh-CN.md +3 -3
- package/templates/.agents/skills/review-plan/reference/review-criteria.en.md +47 -0
- package/templates/.agents/skills/review-plan/reference/review-criteria.zh-CN.md +47 -0
- package/templates/.agents/skills/test/SKILL.en.md +2 -2
- package/templates/.agents/skills/test/SKILL.zh-CN.md +13 -31
- package/templates/.agents/skills/update-agent-infra/SKILL.en.md +1 -0
- package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +1 -0
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +113 -21
- package/templates/.agents/templates/task.en.md +4 -3
- package/templates/.agents/templates/task.zh-CN.md +3 -2
- package/templates/.agents/workflows/bug-fix.en.yaml +126 -80
- package/templates/.agents/workflows/bug-fix.zh-CN.yaml +90 -44
- package/templates/.agents/workflows/feature-development.en.yaml +115 -70
- package/templates/.agents/workflows/feature-development.zh-CN.yaml +92 -47
- package/templates/.agents/workflows/refactoring.en.yaml +123 -78
- package/templates/.agents/workflows/refactoring.zh-CN.yaml +89 -44
- package/templates/.claude/commands/code-task.en.md +8 -0
- package/templates/.claude/commands/code-task.zh-CN.md +8 -0
- package/templates/.claude/commands/review-analysis.en.md +8 -0
- package/templates/.claude/commands/review-analysis.zh-CN.md +8 -0
- package/templates/.claude/commands/review-code.en.md +8 -0
- package/templates/.claude/commands/review-code.zh-CN.md +8 -0
- package/templates/.claude/commands/review-plan.en.md +8 -0
- package/templates/.claude/commands/review-plan.zh-CN.md +8 -0
- package/templates/.gemini/commands/_project_/archive-tasks.zh-CN.toml +1 -1
- package/templates/.gemini/commands/_project_/code-task.en.toml +8 -0
- package/templates/.gemini/commands/_project_/code-task.zh-CN.toml +8 -0
- package/templates/.gemini/commands/_project_/init-labels.zh-CN.toml +1 -1
- package/templates/.gemini/commands/_project_/init-milestones.zh-CN.toml +1 -1
- package/templates/.gemini/commands/_project_/review-analysis.en.toml +8 -0
- package/templates/.gemini/commands/_project_/review-analysis.zh-CN.toml +8 -0
- package/templates/.gemini/commands/_project_/review-code.en.toml +8 -0
- package/templates/.gemini/commands/_project_/review-code.zh-CN.toml +8 -0
- package/templates/.gemini/commands/_project_/review-plan.en.toml +8 -0
- package/templates/.gemini/commands/_project_/review-plan.zh-CN.toml +8 -0
- package/templates/.opencode/commands/code-task.en.md +11 -0
- package/templates/.opencode/commands/code-task.zh-CN.md +11 -0
- package/templates/.opencode/commands/review-analysis.en.md +11 -0
- package/templates/.opencode/commands/review-analysis.zh-CN.md +11 -0
- package/templates/.opencode/commands/review-code.en.md +11 -0
- package/templates/.opencode/commands/review-code.zh-CN.md +11 -0
- package/templates/.opencode/commands/review-plan.en.md +11 -0
- package/templates/.opencode/commands/review-plan.zh-CN.md +11 -0
- package/templates/.agents/skills/implement-task/SKILL.en.md +0 -173
- package/templates/.agents/skills/implement-task/reference/output-template.en.md +0 -20
- package/templates/.agents/skills/implement-task/reference/output-template.zh-CN.md +0 -20
- package/templates/.agents/skills/refine-task/SKILL.en.md +0 -153
- package/templates/.agents/skills/refine-task/SKILL.zh-CN.md +0 -153
- package/templates/.agents/skills/refine-task/reference/report-template.en.md +0 -64
- package/templates/.agents/skills/refine-task/reference/report-template.zh-CN.md +0 -64
- package/templates/.agents/skills/review-task/reference/review-criteria.en.md +0 -42
- package/templates/.claude/commands/implement-task.en.md +0 -8
- package/templates/.claude/commands/implement-task.zh-CN.md +0 -8
- package/templates/.claude/commands/refine-task.en.md +0 -8
- package/templates/.claude/commands/refine-task.zh-CN.md +0 -8
- package/templates/.claude/commands/review-task.en.md +0 -8
- package/templates/.claude/commands/review-task.zh-CN.md +0 -8
- package/templates/.gemini/commands/_project_/implement-task.en.toml +0 -8
- package/templates/.gemini/commands/_project_/implement-task.zh-CN.toml +0 -8
- package/templates/.gemini/commands/_project_/refine-task.en.toml +0 -8
- package/templates/.gemini/commands/_project_/refine-task.zh-CN.toml +0 -8
- package/templates/.gemini/commands/_project_/review-task.en.toml +0 -8
- package/templates/.gemini/commands/_project_/review-task.zh-CN.toml +0 -8
- package/templates/.opencode/commands/implement-task.en.md +0 -11
- package/templates/.opencode/commands/implement-task.zh-CN.md +0 -11
- package/templates/.opencode/commands/refine-task.en.md +0 -11
- package/templates/.opencode/commands/refine-task.zh-CN.md +0 -11
- package/templates/.opencode/commands/review-task.en.md +0 -11
- package/templates/.opencode/commands/review-task.zh-CN.md +0 -11
- /package/templates/.agents/skills/{implement-task → code-task}/reference/branch-management.en.md +0 -0
|
@@ -7,8 +7,14 @@ import type { SandboxConfig } from '../config.ts';
|
|
|
7
7
|
import { prepareDockerfile } from '../dockerfile.ts';
|
|
8
8
|
import { sandboxImageConfigLabel, sandboxLabel } from '../constants.ts';
|
|
9
9
|
import { detectEngine, ensureDocker } from '../engine.ts';
|
|
10
|
-
import { runEngine,
|
|
11
|
-
import {
|
|
10
|
+
import { runEngine, runSafeEngine, runVerboseEngine } from '../shell.ts';
|
|
11
|
+
import { pruneSandboxDanglingImages } from '../image-prune.ts';
|
|
12
|
+
import {
|
|
13
|
+
imageSignatureFields,
|
|
14
|
+
resolveTools,
|
|
15
|
+
toolNpmPackagesArg,
|
|
16
|
+
toolShellInstallScriptBase64
|
|
17
|
+
} from '../tools.ts';
|
|
12
18
|
import type { SandboxTool } from '../tools.ts';
|
|
13
19
|
import { toEnginePath } from '../engines/wsl2-paths.ts';
|
|
14
20
|
import { resolveBuildUid } from '../engines/native.ts';
|
|
@@ -23,7 +29,7 @@ function buildSignature(preparedDockerfile: PreparedDockerfile, tools: SandboxTo
|
|
|
23
29
|
return createHash('sha256')
|
|
24
30
|
.update(JSON.stringify({
|
|
25
31
|
dockerfile: preparedDockerfile.signature,
|
|
26
|
-
tools: tools
|
|
32
|
+
tools: imageSignatureFields(tools)
|
|
27
33
|
}))
|
|
28
34
|
.digest('hex')
|
|
29
35
|
.slice(0, 12);
|
|
@@ -66,6 +72,8 @@ export function buildArgs(
|
|
|
66
72
|
`HOST_GID=${hostGid}`,
|
|
67
73
|
'--build-arg',
|
|
68
74
|
`AI_TOOL_PACKAGES=${toolNpmPackagesArg(tools)}`,
|
|
75
|
+
'--build-arg',
|
|
76
|
+
`AI_TOOLS_SHELL_INSTALL_B64=${toolShellInstallScriptBase64(tools)}`,
|
|
69
77
|
'--label',
|
|
70
78
|
sandboxLabel(config),
|
|
71
79
|
'--label',
|
|
@@ -82,12 +90,6 @@ export function buildArgs(
|
|
|
82
90
|
return args;
|
|
83
91
|
}
|
|
84
92
|
|
|
85
|
-
function removeImageIfPresent(imageName: string, engine: string): void {
|
|
86
|
-
if (runOkEngine(engine, 'docker', ['image', 'inspect', imageName])) {
|
|
87
|
-
runEngine(engine, 'docker', ['rmi', imageName]);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
93
|
export async function rebuild(args: string[]): Promise<void> {
|
|
92
94
|
const { values } = parseArgs({
|
|
93
95
|
args,
|
|
@@ -119,17 +121,12 @@ export async function rebuild(args: string[]): Promise<void> {
|
|
|
119
121
|
try {
|
|
120
122
|
if (quiet) {
|
|
121
123
|
const spinner = p.spinner();
|
|
122
|
-
spinner.start(`Removing old image ${config.imageName}...`);
|
|
123
|
-
removeImageIfPresent(config.imageName, engine);
|
|
124
|
-
spinner.stop('Old image removed');
|
|
125
124
|
spinner.start('Building image...');
|
|
126
125
|
runEngine(engine, 'docker', buildArgs(config, tools, preparedDockerfile.path, imageSignature, { engine, refresh }), {
|
|
127
126
|
cwd: config.repoRoot
|
|
128
127
|
});
|
|
129
128
|
spinner.stop(pc.green('Sandbox image rebuilt'));
|
|
130
129
|
} else {
|
|
131
|
-
p.log.step(`Removing old image ${config.imageName}`);
|
|
132
|
-
removeImageIfPresent(config.imageName, engine);
|
|
133
130
|
p.log.step('Building image');
|
|
134
131
|
runVerboseEngine(
|
|
135
132
|
engine,
|
|
@@ -139,6 +136,7 @@ export async function rebuild(args: string[]): Promise<void> {
|
|
|
139
136
|
);
|
|
140
137
|
p.log.success(pc.green('Sandbox image rebuilt'));
|
|
141
138
|
}
|
|
139
|
+
pruneSandboxDanglingImages(config, engine);
|
|
142
140
|
} finally {
|
|
143
141
|
preparedDockerfile.cleanup();
|
|
144
142
|
}
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
worktreeDirCandidates
|
|
16
16
|
} from '../constants.ts';
|
|
17
17
|
import { ENGINES, detectEngine, engineDisplayName, isManagedEngine, stopManagedVm } from '../engine.ts';
|
|
18
|
+
import { pruneSandboxDanglingImages } from '../image-prune.ts';
|
|
18
19
|
import { removeManagedDir, removeWorktreeDir } from '../managed-fs.ts';
|
|
19
20
|
import { runOk, runSafe, runSafeEngine } from '../shell.ts';
|
|
20
21
|
import { resolveTaskBranch } from '../task-resolver.ts';
|
|
@@ -208,6 +209,8 @@ async function rmAll(config: SandboxConfig, tools: SandboxTool[]): Promise<void>
|
|
|
208
209
|
runSafeEngine(engine, 'docker', ['rmi', config.imageName]);
|
|
209
210
|
}
|
|
210
211
|
|
|
212
|
+
pruneSandboxDanglingImages(config, engine);
|
|
213
|
+
|
|
211
214
|
if (isManagedEngine(engine)) {
|
|
212
215
|
if (engine === ENGINES.WSL2) {
|
|
213
216
|
p.log.warn('Windows uses Docker Desktop with WSL2. Stop it from Docker Desktop or run "wsl --shutdown" manually.');
|
package/lib/sandbox/config.ts
CHANGED
|
@@ -6,6 +6,8 @@ import pc from 'picocolors';
|
|
|
6
6
|
import { validateSandboxEngine } from './engine.ts';
|
|
7
7
|
import { hostJoin } from './engines/wsl2-paths.ts';
|
|
8
8
|
import { findRuntimeEngineMismatches } from './runtime-engines.ts';
|
|
9
|
+
import { parseCustomTools } from './tools.ts';
|
|
10
|
+
import type { SandboxTool } from './tools.ts';
|
|
9
11
|
|
|
10
12
|
const DEFAULTS = Object.freeze({
|
|
11
13
|
engine: null,
|
|
@@ -26,6 +28,7 @@ type SandboxConfigInput = {
|
|
|
26
28
|
engine?: string | null;
|
|
27
29
|
runtimes?: string[];
|
|
28
30
|
tools?: string[];
|
|
31
|
+
customTools?: unknown;
|
|
29
32
|
dockerfile?: string | null;
|
|
30
33
|
vm?: Record<string, unknown>;
|
|
31
34
|
};
|
|
@@ -51,6 +54,7 @@ export type SandboxConfig = {
|
|
|
51
54
|
engine: string | null;
|
|
52
55
|
runtimes: string[];
|
|
53
56
|
tools: string[];
|
|
57
|
+
customTools: SandboxTool[];
|
|
54
58
|
dockerfile: string | null;
|
|
55
59
|
vm: SandboxVmConfig;
|
|
56
60
|
};
|
|
@@ -136,6 +140,8 @@ export function loadConfig({
|
|
|
136
140
|
}
|
|
137
141
|
}
|
|
138
142
|
|
|
143
|
+
const customTools = parseCustomTools(sandbox.customTools, { home });
|
|
144
|
+
|
|
139
145
|
return {
|
|
140
146
|
repoRoot,
|
|
141
147
|
configPath,
|
|
@@ -153,6 +159,7 @@ export function loadConfig({
|
|
|
153
159
|
tools: Array.isArray(sandbox.tools) && sandbox.tools.length > 0
|
|
154
160
|
? [...sandbox.tools]
|
|
155
161
|
: defaults.tools,
|
|
162
|
+
customTools,
|
|
156
163
|
dockerfile,
|
|
157
164
|
vm: {
|
|
158
165
|
cpu: asPositiveNumberOrNull(sandbox.vm?.cpu) ?? defaults.vm.cpu,
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as p from '@clack/prompts';
|
|
2
|
+
import type { SandboxConfig } from './config.ts';
|
|
3
|
+
import { sandboxLabel } from './constants.ts';
|
|
4
|
+
import { runEngine } from './shell.ts';
|
|
5
|
+
|
|
6
|
+
export function pruneSandboxDanglingImages(
|
|
7
|
+
config: Pick<SandboxConfig, 'project'>,
|
|
8
|
+
engine: string
|
|
9
|
+
): void {
|
|
10
|
+
try {
|
|
11
|
+
runEngine(engine, 'docker', [
|
|
12
|
+
'image',
|
|
13
|
+
'prune',
|
|
14
|
+
'-f',
|
|
15
|
+
'--filter',
|
|
16
|
+
`label=${sandboxLabel(config)}`
|
|
17
|
+
]);
|
|
18
|
+
} catch {
|
|
19
|
+
p.log.warn(
|
|
20
|
+
`Failed to prune dangling sandbox images (label=${sandboxLabel(config)}); leaving them in place.`
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
}
|
package/lib/sandbox/index.ts
CHANGED
|
@@ -2,7 +2,8 @@ const USAGE = `Usage: ai sandbox <command> [options]
|
|
|
2
2
|
|
|
3
3
|
Commands:
|
|
4
4
|
create <branch> [base] Create a sandbox (VM + image + worktree + container)
|
|
5
|
-
exec <branch> [cmd...]
|
|
5
|
+
exec <branch | '#N'> [cmd...]
|
|
6
|
+
Enter sandbox or run a command (use leftmost '#' column from 'ls')
|
|
6
7
|
ls List sandboxes for the current project
|
|
7
8
|
prune [--dry-run] Remove orphaned per-branch state dirs
|
|
8
9
|
rebuild [--quiet] [--refresh]
|
|
@@ -4,16 +4,20 @@ ENV OPENCODE_DISABLE_AUTOUPDATE=1
|
|
|
4
4
|
ENV NPM_CONFIG_PREFIX=/home/devuser/.npm-global
|
|
5
5
|
ENV PATH="/home/devuser/.npm-global/bin:${PATH}"
|
|
6
6
|
|
|
7
|
-
ARG AI_TOOL_PACKAGES
|
|
8
|
-
RUN
|
|
9
|
-
echo "AI_TOOL_PACKAGES build arg is required"; \
|
|
10
|
-
exit 1; \
|
|
11
|
-
fi && \
|
|
12
|
-
set -e && \
|
|
7
|
+
ARG AI_TOOL_PACKAGES=
|
|
8
|
+
RUN set -e && \
|
|
13
9
|
for pkg in ${AI_TOOL_PACKAGES}; do \
|
|
14
10
|
npm install -g "$pkg"; \
|
|
15
11
|
done
|
|
16
12
|
|
|
13
|
+
ARG AI_TOOLS_SHELL_INSTALL_B64=
|
|
14
|
+
RUN if [ -n "${AI_TOOLS_SHELL_INSTALL_B64}" ]; then \
|
|
15
|
+
set -e && \
|
|
16
|
+
echo "${AI_TOOLS_SHELL_INSTALL_B64}" | base64 -d > /tmp/ai-tools-install.sh && \
|
|
17
|
+
bash /tmp/ai-tools-install.sh && \
|
|
18
|
+
rm /tmp/ai-tools-install.sh; \
|
|
19
|
+
fi
|
|
20
|
+
|
|
17
21
|
RUN npm install -g pyright
|
|
18
22
|
|
|
19
23
|
RUN mkdir -p /home/devuser/.local/share /home/devuser/.local/state
|
|
@@ -1,9 +1,27 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
1
2
|
import fs from 'node:fs';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
|
|
4
5
|
const TASK_ID_RE = /^TASK-\d{8}-\d{6}$/;
|
|
6
|
+
const SHORT_ID_RE = /^#\d+$/;
|
|
5
7
|
const WORKSPACE_DIRS = ['active', 'completed', 'blocked', 'archive'];
|
|
6
8
|
|
|
9
|
+
function resolveShortIdStrict(arg: string, repoRoot: string): string {
|
|
10
|
+
const scriptPath = path.join(repoRoot, '.agents', 'scripts', 'task-short-id.js');
|
|
11
|
+
if (!fs.existsSync(scriptPath)) {
|
|
12
|
+
throw new Error(
|
|
13
|
+
`Short id '${arg}' provided but task-short-id.js script is missing at ${scriptPath}`
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
const result = spawnSync('node', [scriptPath, 'resolve', arg], { encoding: 'utf8', cwd: repoRoot });
|
|
17
|
+
if (result.status !== 0) {
|
|
18
|
+
throw new Error(
|
|
19
|
+
`Short id '${arg}' not found in active task registry: ${(result.stderr || '').trim()}`
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
return result.stdout.trim();
|
|
23
|
+
}
|
|
24
|
+
|
|
7
25
|
function stripQuotes(value: string): string {
|
|
8
26
|
return value.replace(/^(["'])(.*)\1$/, '$2');
|
|
9
27
|
}
|
|
@@ -33,10 +51,14 @@ function resolveBranchFromTaskContent(content: string, taskId: string): string {
|
|
|
33
51
|
}
|
|
34
52
|
|
|
35
53
|
export function resolveTaskBranch(arg: string, repoRoot: string): string {
|
|
54
|
+
if (SHORT_ID_RE.test(arg)) {
|
|
55
|
+
const taskId = resolveShortIdStrict(arg, repoRoot);
|
|
56
|
+
const content = readTaskContent(repoRoot, taskId);
|
|
57
|
+
return resolveBranchFromTaskContent(content, taskId);
|
|
58
|
+
}
|
|
36
59
|
if (!TASK_ID_RE.test(arg)) {
|
|
37
60
|
return arg;
|
|
38
61
|
}
|
|
39
|
-
|
|
40
62
|
const content = readTaskContent(repoRoot, arg);
|
|
41
63
|
return resolveBranchFromTaskContent(content, arg);
|
|
42
64
|
}
|
package/lib/sandbox/tools.ts
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { safeNameCandidates, sanitizeBranchName } from './constants.ts';
|
|
2
2
|
import { hostJoin } from './engines/wsl2-paths.ts';
|
|
3
3
|
|
|
4
|
+
export type SandboxToolInstall =
|
|
5
|
+
| { type: 'npm'; cmd: string }
|
|
6
|
+
| { type: 'shell'; cmd: string };
|
|
7
|
+
|
|
4
8
|
export type SandboxTool = {
|
|
5
9
|
id: string;
|
|
6
10
|
name: string;
|
|
7
|
-
|
|
11
|
+
install: SandboxToolInstall;
|
|
8
12
|
sandboxBase: string;
|
|
9
13
|
containerMount: string;
|
|
10
14
|
versionCmd: string;
|
|
@@ -21,14 +25,17 @@ type ToolsConfig = {
|
|
|
21
25
|
home: string;
|
|
22
26
|
project: string;
|
|
23
27
|
tools: string[];
|
|
28
|
+
customTools?: SandboxTool[];
|
|
24
29
|
};
|
|
25
30
|
|
|
31
|
+
const TOOL_ID_PATTERN = /^[a-z0-9][a-z0-9-]*$/;
|
|
32
|
+
|
|
26
33
|
function createBuiltinTools(home: string, project: string): Record<string, SandboxTool> {
|
|
27
34
|
return {
|
|
28
35
|
'claude-code': {
|
|
29
36
|
id: 'claude-code',
|
|
30
37
|
name: 'Claude Code',
|
|
31
|
-
|
|
38
|
+
install: { type: 'npm', cmd: '@anthropic-ai/claude-code@stable' },
|
|
32
39
|
sandboxBase: hostJoin(home, '.agent-infra', 'sandboxes', 'claude-code'),
|
|
33
40
|
containerMount: '/home/devuser/.claude',
|
|
34
41
|
versionCmd: 'claude --version',
|
|
@@ -58,7 +65,7 @@ function createBuiltinTools(home: string, project: string): Record<string, Sandb
|
|
|
58
65
|
codex: {
|
|
59
66
|
id: 'codex',
|
|
60
67
|
name: 'Codex',
|
|
61
|
-
|
|
68
|
+
install: { type: 'npm', cmd: '@openai/codex' },
|
|
62
69
|
sandboxBase: hostJoin(home, '.agent-infra', 'sandboxes', 'codex'),
|
|
63
70
|
containerMount: '/home/devuser/.codex',
|
|
64
71
|
versionCmd: 'codex --version',
|
|
@@ -73,7 +80,7 @@ function createBuiltinTools(home: string, project: string): Record<string, Sandb
|
|
|
73
80
|
opencode: {
|
|
74
81
|
id: 'opencode',
|
|
75
82
|
name: 'OpenCode',
|
|
76
|
-
|
|
83
|
+
install: { type: 'npm', cmd: 'opencode-ai' },
|
|
77
84
|
sandboxBase: hostJoin(home, '.agent-infra', 'sandboxes', 'opencode'),
|
|
78
85
|
containerMount: '/home/devuser/.local/share/opencode',
|
|
79
86
|
versionCmd: 'opencode version',
|
|
@@ -92,7 +99,7 @@ function createBuiltinTools(home: string, project: string): Record<string, Sandb
|
|
|
92
99
|
'gemini-cli': {
|
|
93
100
|
id: 'gemini-cli',
|
|
94
101
|
name: 'Gemini CLI',
|
|
95
|
-
|
|
102
|
+
install: { type: 'npm', cmd: '@google/gemini-cli' },
|
|
96
103
|
sandboxBase: hostJoin(home, '.agent-infra', 'sandboxes', 'gemini-cli'),
|
|
97
104
|
containerMount: '/home/devuser/.gemini',
|
|
98
105
|
versionCmd: 'gemini --version',
|
|
@@ -108,16 +115,224 @@ function createBuiltinTools(home: string, project: string): Record<string, Sandb
|
|
|
108
115
|
};
|
|
109
116
|
}
|
|
110
117
|
|
|
118
|
+
export function builtinToolIds(): string[] {
|
|
119
|
+
return Object.keys(createBuiltinTools('', ''));
|
|
120
|
+
}
|
|
121
|
+
|
|
111
122
|
function validateTool(tool: SandboxTool): void {
|
|
112
|
-
if (!tool.
|
|
113
|
-
throw new Error(`Invalid sandbox tool
|
|
123
|
+
if (!tool.id || !TOOL_ID_PATTERN.test(tool.id)) {
|
|
124
|
+
throw new Error(`Invalid sandbox tool id: ${String(tool.id)}`);
|
|
125
|
+
}
|
|
126
|
+
if (!tool.install || (tool.install.type !== 'npm' && tool.install.type !== 'shell')) {
|
|
127
|
+
throw new Error(`Sandbox tool ${tool.id} has invalid install.type`);
|
|
128
|
+
}
|
|
129
|
+
if (!tool.install.cmd) {
|
|
130
|
+
throw new Error(`Sandbox tool ${tool.id} has empty install.cmd`);
|
|
131
|
+
}
|
|
132
|
+
if (!tool.containerMount || !tool.containerMount.startsWith('/')) {
|
|
133
|
+
throw new Error(`Sandbox tool ${tool.id} containerMount must be an absolute path`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
138
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function asString(value: unknown, field: string, context: string): string {
|
|
142
|
+
if (typeof value !== 'string') {
|
|
143
|
+
throw new Error(`${context}: field "${field}" must be a string`);
|
|
144
|
+
}
|
|
145
|
+
return value;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function asOptionalNonEmptyString(value: unknown, field: string, context: string): string | undefined {
|
|
149
|
+
if (value === undefined) {
|
|
150
|
+
return undefined;
|
|
151
|
+
}
|
|
152
|
+
if (typeof value !== 'string') {
|
|
153
|
+
throw new Error(`${context}: field "${field}" must be a string when provided`);
|
|
154
|
+
}
|
|
155
|
+
if (value.length === 0) {
|
|
156
|
+
throw new Error(`${context}: field "${field}" must be non-empty when provided`);
|
|
157
|
+
}
|
|
158
|
+
return value;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function asStringRecord(value: unknown, field: string, context: string): Record<string, string> | undefined {
|
|
162
|
+
if (value === undefined) {
|
|
163
|
+
return undefined;
|
|
164
|
+
}
|
|
165
|
+
if (!isPlainObject(value)) {
|
|
166
|
+
throw new Error(`${context}: field "${field}" must be an object when provided`);
|
|
167
|
+
}
|
|
168
|
+
const out: Record<string, string> = {};
|
|
169
|
+
for (const [key, val] of Object.entries(value)) {
|
|
170
|
+
if (typeof val !== 'string') {
|
|
171
|
+
throw new Error(`${context}: field "${field}.${key}" must be a string`);
|
|
172
|
+
}
|
|
173
|
+
out[key] = val;
|
|
174
|
+
}
|
|
175
|
+
return out;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function asStringArray(value: unknown, field: string, context: string): string[] | undefined {
|
|
179
|
+
if (value === undefined) {
|
|
180
|
+
return undefined;
|
|
181
|
+
}
|
|
182
|
+
if (!Array.isArray(value)) {
|
|
183
|
+
throw new Error(`${context}: field "${field}" must be an array when provided`);
|
|
184
|
+
}
|
|
185
|
+
return value.map((item, index) => {
|
|
186
|
+
if (typeof item !== 'string') {
|
|
187
|
+
throw new Error(`${context}: field "${field}[${index}]" must be a string`);
|
|
188
|
+
}
|
|
189
|
+
return item;
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function parseInstall(value: unknown, context: string): SandboxToolInstall {
|
|
194
|
+
if (!isPlainObject(value)) {
|
|
195
|
+
throw new Error(`${context}: field "install" must be an object`);
|
|
196
|
+
}
|
|
197
|
+
const type = value.type;
|
|
198
|
+
if (type !== 'npm' && type !== 'shell') {
|
|
199
|
+
throw new Error(`${context}: field "install.type" must be "npm" or "shell"`);
|
|
200
|
+
}
|
|
201
|
+
const cmd = asString(value.cmd, 'install.cmd', context);
|
|
202
|
+
if (!cmd) {
|
|
203
|
+
throw new Error(`${context}: field "install.cmd" must be non-empty`);
|
|
204
|
+
}
|
|
205
|
+
return { type, cmd };
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function parseHostPreSeedFiles(value: unknown, context: string): SandboxTool['hostPreSeedFiles'] {
|
|
209
|
+
if (value === undefined) {
|
|
210
|
+
return undefined;
|
|
211
|
+
}
|
|
212
|
+
if (!Array.isArray(value)) {
|
|
213
|
+
throw new Error(`${context}: field "hostPreSeedFiles" must be an array when provided`);
|
|
214
|
+
}
|
|
215
|
+
return value.map((item, index) => {
|
|
216
|
+
if (!isPlainObject(item)) {
|
|
217
|
+
throw new Error(`${context}: field "hostPreSeedFiles[${index}]" must be an object`);
|
|
218
|
+
}
|
|
219
|
+
return {
|
|
220
|
+
hostPath: asString(item.hostPath, `hostPreSeedFiles[${index}].hostPath`, context),
|
|
221
|
+
sandboxName: asString(item.sandboxName, `hostPreSeedFiles[${index}].sandboxName`, context)
|
|
222
|
+
};
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function parseHostPreSeedDirs(value: unknown, context: string): SandboxTool['hostPreSeedDirs'] {
|
|
227
|
+
if (value === undefined) {
|
|
228
|
+
return undefined;
|
|
229
|
+
}
|
|
230
|
+
if (!Array.isArray(value)) {
|
|
231
|
+
throw new Error(`${context}: field "hostPreSeedDirs" must be an array when provided`);
|
|
114
232
|
}
|
|
233
|
+
return value.map((item, index) => {
|
|
234
|
+
if (!isPlainObject(item)) {
|
|
235
|
+
throw new Error(`${context}: field "hostPreSeedDirs[${index}]" must be an object`);
|
|
236
|
+
}
|
|
237
|
+
return {
|
|
238
|
+
hostDir: asString(item.hostDir, `hostPreSeedDirs[${index}].hostDir`, context),
|
|
239
|
+
sandboxSubdir: asString(item.sandboxSubdir, `hostPreSeedDirs[${index}].sandboxSubdir`, context)
|
|
240
|
+
};
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function parseHostLiveMounts(value: unknown, context: string): SandboxTool['hostLiveMounts'] {
|
|
245
|
+
if (value === undefined) {
|
|
246
|
+
return undefined;
|
|
247
|
+
}
|
|
248
|
+
if (!Array.isArray(value)) {
|
|
249
|
+
throw new Error(`${context}: field "hostLiveMounts" must be an array when provided`);
|
|
250
|
+
}
|
|
251
|
+
return value.map((item, index) => {
|
|
252
|
+
if (!isPlainObject(item)) {
|
|
253
|
+
throw new Error(`${context}: field "hostLiveMounts[${index}]" must be an object`);
|
|
254
|
+
}
|
|
255
|
+
return {
|
|
256
|
+
hostPath: asString(item.hostPath, `hostLiveMounts[${index}].hostPath`, context),
|
|
257
|
+
containerSubpath: asString(item.containerSubpath, `hostLiveMounts[${index}].containerSubpath`, context)
|
|
258
|
+
};
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export function parseCustomTool(
|
|
263
|
+
entry: unknown,
|
|
264
|
+
index: number,
|
|
265
|
+
options: { home: string }
|
|
266
|
+
): SandboxTool {
|
|
267
|
+
const context = `customTools[${index}]`;
|
|
268
|
+
if (!isPlainObject(entry)) {
|
|
269
|
+
throw new Error(`${context} must be an object`);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const id = asString(entry.id, 'id', context);
|
|
273
|
+
if (!TOOL_ID_PATTERN.test(id)) {
|
|
274
|
+
throw new Error(`${context}: field "id" must match ${TOOL_ID_PATTERN.source}`);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const containerMount = asOptionalNonEmptyString(entry.containerMount, 'containerMount', context)
|
|
278
|
+
?? `/home/devuser/.${id}`;
|
|
279
|
+
if (!containerMount.startsWith('/')) {
|
|
280
|
+
throw new Error(`${context}: field "containerMount" must be an absolute path`);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const tool: SandboxTool = {
|
|
284
|
+
id,
|
|
285
|
+
name: asOptionalNonEmptyString(entry.name, 'name', context) ?? id,
|
|
286
|
+
install: parseInstall(entry.install, context),
|
|
287
|
+
sandboxBase: hostJoin(options.home, '.agent-infra', 'sandboxes', id),
|
|
288
|
+
containerMount,
|
|
289
|
+
versionCmd: asOptionalNonEmptyString(entry.versionCmd, 'versionCmd', context) ?? `which ${id}`,
|
|
290
|
+
setupHint: asOptionalNonEmptyString(entry.setupHint, 'setupHint', context)
|
|
291
|
+
?? `Run \`${id}\` inside the container to set up.`,
|
|
292
|
+
envVars: asStringRecord(entry.envVars, 'envVars', context),
|
|
293
|
+
hostPreSeedFiles: parseHostPreSeedFiles(entry.hostPreSeedFiles, context),
|
|
294
|
+
hostPreSeedDirs: parseHostPreSeedDirs(entry.hostPreSeedDirs, context),
|
|
295
|
+
pathRewriteFiles: asStringArray(entry.pathRewriteFiles, 'pathRewriteFiles', context),
|
|
296
|
+
hostLiveMounts: parseHostLiveMounts(entry.hostLiveMounts, context),
|
|
297
|
+
postSetupCmds: asStringArray(entry.postSetupCmds, 'postSetupCmds', context)
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
validateTool(tool);
|
|
301
|
+
return tool;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export function parseCustomTools(value: unknown, options: { home: string }): SandboxTool[] {
|
|
305
|
+
if (value === undefined || value === null) {
|
|
306
|
+
return [];
|
|
307
|
+
}
|
|
308
|
+
if (!Array.isArray(value)) {
|
|
309
|
+
throw new Error('sandbox: "customTools" must be an array');
|
|
310
|
+
}
|
|
311
|
+
return value.map((entry, index) => parseCustomTool(entry, index, options));
|
|
115
312
|
}
|
|
116
313
|
|
|
117
314
|
export function resolveTools(config: ToolsConfig): SandboxTool[] {
|
|
118
315
|
const builtins = createBuiltinTools(config.home, config.project);
|
|
316
|
+
const customs = config.customTools ?? [];
|
|
317
|
+
|
|
318
|
+
const seen = new Set<string>();
|
|
319
|
+
for (const tool of customs) {
|
|
320
|
+
if (builtins[tool.id]) {
|
|
321
|
+
throw new Error(`Custom sandbox tool id "${tool.id}" collides with a built-in tool`);
|
|
322
|
+
}
|
|
323
|
+
if (seen.has(tool.id)) {
|
|
324
|
+
throw new Error(`Duplicate sandbox tool id "${tool.id}" in customTools`);
|
|
325
|
+
}
|
|
326
|
+
seen.add(tool.id);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const merged: Record<string, SandboxTool> = { ...builtins };
|
|
330
|
+
for (const tool of customs) {
|
|
331
|
+
merged[tool.id] = tool;
|
|
332
|
+
}
|
|
333
|
+
|
|
119
334
|
return config.tools.map((id) => {
|
|
120
|
-
const tool =
|
|
335
|
+
const tool = merged[id];
|
|
121
336
|
if (!tool) {
|
|
122
337
|
throw new Error(`Unknown sandbox tool: ${id}`);
|
|
123
338
|
}
|
|
@@ -139,5 +354,29 @@ export function toolProjectDirCandidates(tool: SandboxTool, project: string): st
|
|
|
139
354
|
}
|
|
140
355
|
|
|
141
356
|
export function toolNpmPackagesArg(tools: SandboxTool[]): string {
|
|
142
|
-
return tools
|
|
357
|
+
return tools
|
|
358
|
+
.filter((tool) => tool.install.type === 'npm')
|
|
359
|
+
.map((tool) => tool.install.cmd)
|
|
360
|
+
.join(' ');
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
export function toolShellInstallScript(tools: SandboxTool[]): string {
|
|
364
|
+
const blocks = tools
|
|
365
|
+
.filter((tool) => tool.install.type === 'shell')
|
|
366
|
+
.map((tool) => `# install: ${tool.id}\n${tool.install.cmd}`);
|
|
367
|
+
|
|
368
|
+
if (blocks.length === 0) {
|
|
369
|
+
return '';
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
return ['#!/bin/bash', 'set -e', '', ...blocks, ''].join('\n');
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
export function toolShellInstallScriptBase64(tools: SandboxTool[]): string {
|
|
376
|
+
const script = toolShellInstallScript(tools);
|
|
377
|
+
return script ? Buffer.from(script, 'utf8').toString('base64') : '';
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
export function imageSignatureFields(tools: SandboxTool[]): Array<{ id: string; install: SandboxToolInstall }> {
|
|
381
|
+
return tools.map((tool) => ({ id: tool.id, install: tool.install }));
|
|
143
382
|
}
|