@fitlab-ai/agent-infra 0.7.4 → 0.7.6
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/bin/cli.ts +13 -11
- package/dist/bin/cli.js +13 -11
- package/dist/lib/init.js +1 -1
- package/dist/lib/merge.js +1 -1
- package/dist/lib/sandbox/commands/create.js +26 -4
- package/dist/lib/sandbox/index.js +21 -21
- package/dist/lib/sandbox/tools.js +20 -1
- package/dist/lib/task/commands/log.js +56 -6
- package/dist/lib/task/index.js +13 -13
- package/dist/lib/update.js +1 -1
- package/lib/init.ts +1 -1
- package/lib/merge.ts +1 -1
- package/lib/sandbox/commands/create.ts +33 -4
- package/lib/sandbox/index.ts +21 -21
- package/lib/sandbox/tools.ts +28 -1
- package/lib/task/commands/log.ts +59 -6
- package/lib/task/index.ts +13 -13
- package/lib/update.ts +1 -1
- package/package.json +1 -1
- package/templates/.agents/rules/README.en.md +7 -3
- package/templates/.agents/rules/README.zh-CN.md +7 -3
- package/templates/.agents/rules/cli-help-format.en.md +49 -0
- package/templates/.agents/rules/cli-help-format.zh-CN.md +49 -0
- package/templates/.agents/rules/no-mid-flow-questions.en.md +25 -2
- package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +25 -2
- package/templates/.agents/rules/pr-sync.github.en.md +8 -6
- package/templates/.agents/rules/pr-sync.github.zh-CN.md +8 -6
- package/templates/.agents/rules/review-handshake.en.md +97 -0
- package/templates/.agents/rules/review-handshake.zh-CN.md +97 -0
- package/templates/.agents/rules/task-management.en.md +25 -0
- package/templates/.agents/rules/task-management.zh-CN.md +29 -0
- package/templates/.agents/scripts/lib/post-review-commit.js +56 -0
- package/templates/.agents/scripts/lib/review-artifacts.js +117 -0
- package/templates/.agents/scripts/review-diff-fingerprint.js +99 -0
- package/templates/.agents/scripts/validate-artifact.js +251 -2
- package/templates/.agents/skills/analyze-task/SKILL.en.md +63 -6
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +63 -6
- package/templates/.agents/skills/block-task/SKILL.en.md +10 -0
- package/templates/.agents/skills/block-task/SKILL.zh-CN.md +10 -0
- package/templates/.agents/skills/cancel-task/SKILL.en.md +10 -0
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +11 -1
- package/templates/.agents/skills/close-codescan/SKILL.en.md +10 -0
- package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +10 -0
- package/templates/.agents/skills/close-dependabot/SKILL.en.md +10 -0
- package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +10 -0
- package/templates/.agents/skills/code-task/SKILL.en.md +11 -0
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +11 -0
- package/templates/.agents/skills/code-task/config/verify.en.json +3 -0
- package/templates/.agents/skills/code-task/config/verify.zh-CN.json +3 -0
- package/templates/.agents/skills/code-task/reference/fix-mode.en.md +5 -3
- package/templates/.agents/skills/code-task/reference/fix-mode.zh-CN.md +5 -3
- package/templates/.agents/skills/code-task/reference/report-template.en.md +4 -4
- package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +4 -4
- package/templates/.agents/skills/code-task/scripts/detect-mode.js +2 -107
- package/templates/.agents/skills/commit/SKILL.en.md +16 -0
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +16 -0
- package/templates/.agents/skills/commit/reference/task-status-update.en.md +8 -0
- package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +8 -0
- package/templates/.agents/skills/complete-task/SKILL.en.md +20 -0
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +20 -0
- package/templates/.agents/skills/complete-task/config/verify.en.json +2 -0
- package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +2 -0
- package/templates/.agents/skills/create-pr/SKILL.en.md +20 -1
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +20 -1
- package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +1 -1
- package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +1 -1
- package/templates/.agents/skills/create-release-note/SKILL.en.md +16 -1
- package/templates/.agents/skills/create-release-note/SKILL.zh-CN.md +16 -1
- package/templates/.agents/skills/create-task/SKILL.en.md +11 -0
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +14 -3
- package/templates/.agents/skills/import-codescan/SKILL.en.md +11 -0
- package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +11 -0
- package/templates/.agents/skills/import-dependabot/SKILL.en.md +11 -0
- package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +11 -0
- package/templates/.agents/skills/import-issue/SKILL.en.md +16 -0
- package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +16 -0
- package/templates/.agents/skills/plan-task/SKILL.en.md +13 -1
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +13 -1
- package/templates/.agents/skills/plan-task/config/verify.en.json +3 -0
- package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +3 -0
- package/templates/.agents/skills/restore-task/SKILL.en.md +10 -0
- package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +10 -0
- package/templates/.agents/skills/review-analysis/SKILL.en.md +10 -0
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +10 -0
- package/templates/.agents/skills/review-analysis/config/verify.en.json +2 -1
- package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +2 -1
- package/templates/.agents/skills/review-analysis/reference/output-templates.en.md +5 -4
- package/templates/.agents/skills/review-analysis/reference/output-templates.zh-CN.md +5 -4
- package/templates/.agents/skills/review-analysis/reference/report-template.en.md +4 -0
- package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +4 -0
- package/templates/.agents/skills/review-analysis/reference/review-criteria.en.md +1 -0
- package/templates/.agents/skills/review-analysis/reference/review-criteria.zh-CN.md +1 -0
- package/templates/.agents/skills/review-code/SKILL.en.md +14 -1
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +14 -1
- package/templates/.agents/skills/review-code/config/verify.en.json +5 -2
- package/templates/.agents/skills/review-code/config/verify.zh-CN.json +5 -2
- package/templates/.agents/skills/review-code/reference/output-templates.en.md +5 -4
- package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +5 -4
- package/templates/.agents/skills/review-code/reference/report-template.en.md +6 -0
- package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +6 -0
- package/templates/.agents/skills/review-code/reference/review-criteria.en.md +1 -0
- package/templates/.agents/skills/review-code/reference/review-criteria.zh-CN.md +1 -0
- package/templates/.agents/skills/review-plan/SKILL.en.md +10 -0
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +10 -0
- package/templates/.agents/skills/review-plan/config/verify.en.json +2 -1
- package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +2 -1
- package/templates/.agents/skills/review-plan/reference/output-templates.en.md +5 -4
- package/templates/.agents/skills/review-plan/reference/output-templates.zh-CN.md +5 -4
- package/templates/.agents/skills/review-plan/reference/report-template.en.md +4 -0
- package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +4 -0
- package/templates/.agents/skills/review-plan/reference/review-criteria.en.md +1 -0
- package/templates/.agents/skills/review-plan/reference/review-criteria.zh-CN.md +1 -0
- package/templates/.agents/skills/watch-pr/SKILL.en.md +10 -0
- package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +10 -0
- package/templates/.agents/templates/task.en.md +12 -0
- package/templates/.agents/templates/task.zh-CN.md +12 -0
- package/templates/.github/workflows/metadata-sync.yml +1 -1
- package/templates/.github/workflows/pr-label.yml +1 -1
- package/templates/.github/workflows/status-label.yml +1 -1
package/bin/cli.ts
CHANGED
|
@@ -12,17 +12,19 @@ if (major < 22) {
|
|
|
12
12
|
|
|
13
13
|
const USAGE = `agent-infra ${VERSION} - bootstrap AI collaboration infrastructure
|
|
14
14
|
|
|
15
|
-
Usage:
|
|
16
|
-
agent-infra cp <ssh-alias> Copy local clipboard image to a remote macOS NSPasteboard
|
|
17
|
-
agent-infra help Show this help message
|
|
18
|
-
agent-infra init Initialize a new project with update-agent-infra seed command
|
|
19
|
-
agent-infra merge Merge tasks from another workspace directory (active/blocked/completed/archive)
|
|
20
|
-
agent-infra sandbox Manage Docker-based AI sandboxes
|
|
21
|
-
agent-infra task Read-only views over .agents/workspace tasks (ls / show / files / cat / status / log / grep)
|
|
22
|
-
agent-infra update Update seed files and sync file registry for an existing project
|
|
23
|
-
agent-infra version Show version
|
|
15
|
+
Usage: ai <command> [options]
|
|
24
16
|
|
|
25
|
-
|
|
17
|
+
Commands:
|
|
18
|
+
cp <ssh-alias> Copy local clipboard image to a remote macOS NSPasteboard
|
|
19
|
+
help Show this help message
|
|
20
|
+
init Initialize a new project with update-agent-infra seed command
|
|
21
|
+
merge Merge tasks from another workspace directory (active/blocked/completed/archive)
|
|
22
|
+
sandbox Manage Docker-based AI sandboxes
|
|
23
|
+
task Read-only views over .agents/workspace tasks (cat / files / grep / log / ls / show / status)
|
|
24
|
+
update Update seed files and sync file registry for an existing project
|
|
25
|
+
version Show version
|
|
26
|
+
|
|
27
|
+
'ai' and 'agent-infra' are interchangeable; 'ai' is the shorter form.
|
|
26
28
|
|
|
27
29
|
Install methods:
|
|
28
30
|
npm: npm install -g @fitlab-ai/agent-infra
|
|
@@ -31,7 +33,7 @@ Install methods:
|
|
|
31
33
|
curl: curl -fsSL https://raw.githubusercontent.com/fitlab-ai/agent-infra/main/install.sh | sh (runs npm install -g internally)
|
|
32
34
|
|
|
33
35
|
Examples:
|
|
34
|
-
cd my-project &&
|
|
36
|
+
cd my-project && ai init
|
|
35
37
|
npx @fitlab-ai/agent-infra init
|
|
36
38
|
`;
|
|
37
39
|
|
package/dist/bin/cli.js
CHANGED
|
@@ -16,17 +16,19 @@ if (major < 22) {
|
|
|
16
16
|
}
|
|
17
17
|
const USAGE = `agent-infra ${VERSION} - bootstrap AI collaboration infrastructure
|
|
18
18
|
|
|
19
|
-
Usage:
|
|
20
|
-
agent-infra cp <ssh-alias> Copy local clipboard image to a remote macOS NSPasteboard
|
|
21
|
-
agent-infra help Show this help message
|
|
22
|
-
agent-infra init Initialize a new project with update-agent-infra seed command
|
|
23
|
-
agent-infra merge Merge tasks from another workspace directory (active/blocked/completed/archive)
|
|
24
|
-
agent-infra sandbox Manage Docker-based AI sandboxes
|
|
25
|
-
agent-infra task Read-only views over .agents/workspace tasks (ls / show / files / cat / status / log / grep)
|
|
26
|
-
agent-infra update Update seed files and sync file registry for an existing project
|
|
27
|
-
agent-infra version Show version
|
|
19
|
+
Usage: ai <command> [options]
|
|
28
20
|
|
|
29
|
-
|
|
21
|
+
Commands:
|
|
22
|
+
cp <ssh-alias> Copy local clipboard image to a remote macOS NSPasteboard
|
|
23
|
+
help Show this help message
|
|
24
|
+
init Initialize a new project with update-agent-infra seed command
|
|
25
|
+
merge Merge tasks from another workspace directory (active/blocked/completed/archive)
|
|
26
|
+
sandbox Manage Docker-based AI sandboxes
|
|
27
|
+
task Read-only views over .agents/workspace tasks (cat / files / grep / log / ls / show / status)
|
|
28
|
+
update Update seed files and sync file registry for an existing project
|
|
29
|
+
version Show version
|
|
30
|
+
|
|
31
|
+
'ai' and 'agent-infra' are interchangeable; 'ai' is the shorter form.
|
|
30
32
|
|
|
31
33
|
Install methods:
|
|
32
34
|
npm: npm install -g @fitlab-ai/agent-infra
|
|
@@ -35,7 +37,7 @@ Install methods:
|
|
|
35
37
|
curl: curl -fsSL https://raw.githubusercontent.com/fitlab-ai/agent-infra/main/install.sh | sh (runs npm install -g internally)
|
|
36
38
|
|
|
37
39
|
Examples:
|
|
38
|
-
cd my-project &&
|
|
40
|
+
cd my-project && ai init
|
|
39
41
|
npx @fitlab-ai/agent-infra init
|
|
40
42
|
`;
|
|
41
43
|
const command = process.argv[2] || '';
|
package/dist/lib/init.js
CHANGED
|
@@ -70,7 +70,7 @@ function parseLocalSources(input) {
|
|
|
70
70
|
}
|
|
71
71
|
async function cmdInit() {
|
|
72
72
|
console.log('');
|
|
73
|
-
console.log('
|
|
73
|
+
console.log(' ai init');
|
|
74
74
|
console.log(' ================================');
|
|
75
75
|
console.log(' Optional template and skill sources can be added now or later in .agents/.airc.json.');
|
|
76
76
|
console.log('');
|
package/dist/lib/merge.js
CHANGED
|
@@ -702,7 +702,7 @@ function printReport(report) {
|
|
|
702
702
|
async function cmdMerge(args) {
|
|
703
703
|
const sourcePath = args[0];
|
|
704
704
|
if (!sourcePath) {
|
|
705
|
-
throw new Error('Usage:
|
|
705
|
+
throw new Error('Usage: ai merge <source-path>');
|
|
706
706
|
}
|
|
707
707
|
const resolvedSource = path.resolve(sourcePath);
|
|
708
708
|
if (!fs.existsSync(resolvedSource)) {
|
|
@@ -820,6 +820,12 @@ function runEngineTaskCommand(engine, cmd, args, opts = {}) {
|
|
|
820
820
|
const command = commandForEngine(engine, cmd, args);
|
|
821
821
|
return runTaskCommand(command.cmd, command.args, opts);
|
|
822
822
|
}
|
|
823
|
+
// `docker run` args for mounting a tool's containerMount as an in-container
|
|
824
|
+
// tmpfs. containerMount is an in-container path, so it is NOT engine-converted.
|
|
825
|
+
export function buildTmpfsRunArgs(containerMount, tmpfs) {
|
|
826
|
+
const size = tmpfs.size ?? '512m';
|
|
827
|
+
return ['--tmpfs', `${containerMount}:rw,size=${size}`];
|
|
828
|
+
}
|
|
823
829
|
export function buildImage(config, tools, dockerfilePath, imageSignature, { engine, runFn = runEngine, runSafeFn = runSafeEngine, runVerboseFn = runVerboseEngine, env = process.env } = {}) {
|
|
824
830
|
const selectedEngine = engine ?? detectEngine({ engine: config.engine });
|
|
825
831
|
const { uid: hostUid, gid: hostGid } = resolveBuildUid({
|
|
@@ -1076,10 +1082,8 @@ export async function create(args) {
|
|
|
1076
1082
|
// The TUI reads <toolDir>/opencode.json via OPENCODE_CONFIG pinned in tools.js.
|
|
1077
1083
|
ensureOpenCodeModelInheritance(opencodeEntry.dir, effectiveConfig.home);
|
|
1078
1084
|
}
|
|
1079
|
-
const toolVolumes = effectiveResolvedTools.flatMap(({ tool, dir }) => [
|
|
1080
|
-
|
|
1081
|
-
volumeArg(engine, dir, tool.containerMount)
|
|
1082
|
-
]);
|
|
1085
|
+
const toolVolumes = effectiveResolvedTools.flatMap(({ tool, dir }) => tool.tmpfs ? [] : ['-v', volumeArg(engine, dir, tool.containerMount)]);
|
|
1086
|
+
const tmpfsArgs = effectiveResolvedTools.flatMap(({ tool }) => tool.tmpfs ? buildTmpfsRunArgs(tool.containerMount, tool.tmpfs) : []);
|
|
1083
1087
|
const workspaceDir = path.join(effectiveConfig.repoRoot, '.agents', 'workspace');
|
|
1084
1088
|
hostShellConfig = prepareHostShellConfig({
|
|
1085
1089
|
home: effectiveConfig.home,
|
|
@@ -1091,6 +1095,22 @@ export async function create(args) {
|
|
|
1091
1095
|
'-v',
|
|
1092
1096
|
volumeArg(engine, hostPath, containerPath, ':ro')
|
|
1093
1097
|
]);
|
|
1098
|
+
// A tmpfs containerMount starts empty, so the config seeded into the
|
|
1099
|
+
// host dir before launch would be invisible in-container. Bind only
|
|
1100
|
+
// the explicitly declared seed entries (config.toml, model-catalogs)
|
|
1101
|
+
// back over the tmpfs as nested mounts — the same proven mechanism as
|
|
1102
|
+
// hostLiveMounts/auth.json, established at `docker run` time (no
|
|
1103
|
+
// post-start `docker cp`, which can land under a freshly-mounted
|
|
1104
|
+
// tmpfs instead of inside it). The allowlist is deliberate: any
|
|
1105
|
+
// runtime files left in the host dir (e.g. a stale logs_2.sqlite or
|
|
1106
|
+
// sessions/ from a previous bind-mount era) must NOT be re-mounted,
|
|
1107
|
+
// or the high-churn writes would land on the host SSD again.
|
|
1108
|
+
const tmpfsSeedVolumes = effectiveResolvedTools.flatMap(({ tool, dir }) => (tool.tmpfs?.seed ?? []).flatMap((entry) => {
|
|
1109
|
+
const hostPath = path.join(dir, entry);
|
|
1110
|
+
return fs.existsSync(hostPath)
|
|
1111
|
+
? ['-v', volumeArg(engine, hostPath, path.posix.join(tool.containerMount, entry))]
|
|
1112
|
+
: [];
|
|
1113
|
+
}));
|
|
1094
1114
|
const liveMountVolumes = effectiveResolvedTools.flatMap(({ tool }) => (tool.hostLiveMounts ?? [])
|
|
1095
1115
|
.filter(({ hostPath }) => fs.existsSync(hostPath))
|
|
1096
1116
|
.flatMap(({ hostPath, containerSubpath }) => [
|
|
@@ -1133,6 +1153,8 @@ export async function create(args) {
|
|
|
1133
1153
|
volumeArg(engine, hostJoin(effectiveConfig.home, '.ssh'), '/home/devuser/.ssh', ':ro'),
|
|
1134
1154
|
...dotfilesMount,
|
|
1135
1155
|
...toolVolumes,
|
|
1156
|
+
...tmpfsArgs,
|
|
1157
|
+
...tmpfsSeedVolumes,
|
|
1136
1158
|
...liveMountVolumes,
|
|
1137
1159
|
...shellConfigVolumes,
|
|
1138
1160
|
...envFile.dockerArgs,
|
|
@@ -6,9 +6,6 @@ Commands:
|
|
|
6
6
|
Enter sandbox or run a command. N (bare) is the
|
|
7
7
|
recommended form for task short ids (e.g.
|
|
8
8
|
'ai sandbox exec 11'); '#N' is also accepted.
|
|
9
|
-
start <branch | TASK-id | N | '#N'>
|
|
10
|
-
Start an existing stopped sandbox container
|
|
11
|
-
(e.g. after the Docker daemon restarted)
|
|
12
9
|
ls List sandboxes for the current project (the '#'
|
|
13
10
|
column is a display-only row number; the 'SHORT'
|
|
14
11
|
column shows the active task short id, '-' if none)
|
|
@@ -19,6 +16,9 @@ Commands:
|
|
|
19
16
|
rm <branch> | --all | --purge
|
|
20
17
|
Remove one sandbox, all sandboxes not bound to an
|
|
21
18
|
active task (--all), or tear down everything (--purge)
|
|
19
|
+
start <branch | TASK-id | N | '#N'>
|
|
20
|
+
Start an existing stopped sandbox container
|
|
21
|
+
(e.g. after the Docker daemon restarted)
|
|
22
22
|
vm status|start|stop Manage the sandbox VM (macOS) or check the backend (Windows)
|
|
23
23
|
|
|
24
24
|
Run 'ai sandbox <command> --help' for details.`;
|
|
@@ -47,6 +47,21 @@ export async function runSandbox(args) {
|
|
|
47
47
|
}
|
|
48
48
|
break;
|
|
49
49
|
}
|
|
50
|
+
case 'ls': {
|
|
51
|
+
const { ls } = await import("./commands/ls.js");
|
|
52
|
+
ls(rest);
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
case 'prune': {
|
|
56
|
+
const { prune } = await import("./commands/prune.js");
|
|
57
|
+
await prune(rest);
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
case 'rebuild': {
|
|
61
|
+
const { rebuild } = await import("./commands/rebuild.js");
|
|
62
|
+
await rebuild(rest);
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
50
65
|
case 'refresh': {
|
|
51
66
|
const { refresh } = await import("./commands/refresh.js");
|
|
52
67
|
const exitCode = await refresh(rest);
|
|
@@ -55,24 +70,14 @@ export async function runSandbox(args) {
|
|
|
55
70
|
}
|
|
56
71
|
break;
|
|
57
72
|
}
|
|
58
|
-
case 'start': {
|
|
59
|
-
const { start } = await import("./commands/start.js");
|
|
60
|
-
await start(rest);
|
|
61
|
-
break;
|
|
62
|
-
}
|
|
63
|
-
case 'ls': {
|
|
64
|
-
const { ls } = await import("./commands/ls.js");
|
|
65
|
-
ls(rest);
|
|
66
|
-
break;
|
|
67
|
-
}
|
|
68
73
|
case 'rm': {
|
|
69
74
|
const { rm } = await import("./commands/rm.js");
|
|
70
75
|
await rm(rest);
|
|
71
76
|
break;
|
|
72
77
|
}
|
|
73
|
-
case '
|
|
74
|
-
const {
|
|
75
|
-
await
|
|
78
|
+
case 'start': {
|
|
79
|
+
const { start } = await import("./commands/start.js");
|
|
80
|
+
await start(rest);
|
|
76
81
|
break;
|
|
77
82
|
}
|
|
78
83
|
case 'vm': {
|
|
@@ -80,11 +85,6 @@ export async function runSandbox(args) {
|
|
|
80
85
|
await vm(rest);
|
|
81
86
|
break;
|
|
82
87
|
}
|
|
83
|
-
case 'rebuild': {
|
|
84
|
-
const { rebuild } = await import("./commands/rebuild.js");
|
|
85
|
-
await rebuild(rest);
|
|
86
|
-
break;
|
|
87
|
-
}
|
|
88
88
|
default:
|
|
89
89
|
throw new Error(`Unknown sandbox command: ${subcommand}`);
|
|
90
90
|
}
|
|
@@ -41,6 +41,12 @@ function createBuiltinTools(home, project) {
|
|
|
41
41
|
containerMount: '/home/devuser/.codex',
|
|
42
42
|
versionCmd: 'codex --version',
|
|
43
43
|
setupHint: 'Run codex once inside the container and choose Device Code login if needed.',
|
|
44
|
+
// codex churns ~/.codex/logs_2.sqlite heavily (upstream openai/codex#24275);
|
|
45
|
+
// a bind-mount would write-amplify onto the host SSD via virtiofs. Mount the
|
|
46
|
+
// codex home as tmpfs so those logs stay in RAM and die with the container.
|
|
47
|
+
// Only the seeded config (config.toml, model-catalogs) is bound back over
|
|
48
|
+
// the tmpfs; runtime files like logs_2.sqlite must stay in RAM.
|
|
49
|
+
tmpfs: { size: '512m', seed: ['config.toml', 'model-catalogs'] },
|
|
44
50
|
hostLiveMounts: [
|
|
45
51
|
{ hostPath: hostJoin(home, '.codex', 'auth.json'), containerSubpath: 'auth.json' }
|
|
46
52
|
],
|
|
@@ -218,6 +224,18 @@ function parseHostLiveMounts(value, context) {
|
|
|
218
224
|
};
|
|
219
225
|
});
|
|
220
226
|
}
|
|
227
|
+
function parseTmpfs(value, context) {
|
|
228
|
+
if (value === undefined) {
|
|
229
|
+
return undefined;
|
|
230
|
+
}
|
|
231
|
+
if (!isPlainObject(value)) {
|
|
232
|
+
throw new Error(`${context}: field "tmpfs" must be an object when provided`);
|
|
233
|
+
}
|
|
234
|
+
return {
|
|
235
|
+
size: asOptionalNonEmptyString(value.size, 'tmpfs.size', context),
|
|
236
|
+
seed: asStringArray(value.seed, 'tmpfs.seed', context)
|
|
237
|
+
};
|
|
238
|
+
}
|
|
221
239
|
export function parseCustomTool(entry, index, options) {
|
|
222
240
|
const context = `customTools[${index}]`;
|
|
223
241
|
if (!isPlainObject(entry)) {
|
|
@@ -246,7 +264,8 @@ export function parseCustomTool(entry, index, options) {
|
|
|
246
264
|
hostPreSeedDirs: parseHostPreSeedDirs(entry.hostPreSeedDirs, context),
|
|
247
265
|
pathRewriteFiles: asStringArray(entry.pathRewriteFiles, 'pathRewriteFiles', context),
|
|
248
266
|
hostLiveMounts: parseHostLiveMounts(entry.hostLiveMounts, context),
|
|
249
|
-
postSetupCmds: asStringArray(entry.postSetupCmds, 'postSetupCmds', context)
|
|
267
|
+
postSetupCmds: asStringArray(entry.postSetupCmds, 'postSetupCmds', context),
|
|
268
|
+
tmpfs: parseTmpfs(entry.tmpfs, context)
|
|
250
269
|
};
|
|
251
270
|
validateTool(tool);
|
|
252
271
|
return tool;
|
|
@@ -3,12 +3,14 @@ import { formatTable } from "../../table.js";
|
|
|
3
3
|
import { resolveTaskRef } from "../resolve-ref.js";
|
|
4
4
|
const USAGE = `Usage: ai task log <N | #N | TASK-id>
|
|
5
5
|
|
|
6
|
-
Renders a task's activity log as a
|
|
6
|
+
Renders a task's activity log as a per-step status table. A step's start and
|
|
7
|
+
completion are paired onto one row: STARTED holds the start time, DONE the
|
|
8
|
+
completion time (or '(in progress)' while still running).
|
|
7
9
|
<ref> Bare numeric / '#N' short id, or a full TASK-YYYYMMDD-HHMMSS id.
|
|
8
10
|
|
|
9
|
-
Columns: # (
|
|
11
|
+
Columns: # (row) / STEP / AGENT / STARTED / DONE / NOTE
|
|
10
12
|
`;
|
|
11
|
-
const TABLE_HEADERS = ['#', '
|
|
13
|
+
const TABLE_HEADERS = ['#', 'STEP', 'AGENT', 'STARTED', 'DONE', 'NOTE'];
|
|
12
14
|
// The activity-log H2 heading is language-dependent (zh template / en template).
|
|
13
15
|
const HEADING_RE = /^##\s+(活动日志|Activity Log)\s*$/;
|
|
14
16
|
const NEXT_H2_RE = /^##\s/;
|
|
@@ -16,6 +18,11 @@ const NEXT_H2_RE = /^##\s/;
|
|
|
16
18
|
// (U+2014). STEP/AGENT are non-greedy so a note that itself contains ' — ' or
|
|
17
19
|
// '→' is not mis-split; NOTE greedily takes the rest of the line.
|
|
18
20
|
const ENTRY_RE = /^- (\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}[+-]\d{2}:\d{2}) — \*\*(.+?)\*\* by (.+?) — (.*)$/;
|
|
21
|
+
// A start marker reuses the normal entry grammar and only suffixes its action
|
|
22
|
+
// with ` [started]`; the matching done entry carries the identical base action
|
|
23
|
+
// without the suffix. Pairing therefore keys on the base action (including any
|
|
24
|
+
// `(Round N)`), so every round and every repeated execution pairs on its own.
|
|
25
|
+
const STARTED_SUFFIX_RE = /\s*\[started\]\s*$/;
|
|
19
26
|
function parseActivityLog(content) {
|
|
20
27
|
const lines = content.split('\n');
|
|
21
28
|
let i = 0;
|
|
@@ -40,6 +47,41 @@ function parseActivityLog(content) {
|
|
|
40
47
|
parsed.sort((a, b) => a.epoch - b.epoch || a.order - b.order);
|
|
41
48
|
return { sectionFound: true, entries: parsed.map((p) => p.entry) };
|
|
42
49
|
}
|
|
50
|
+
// Collapse a chronological entry list into per-step rows: a `[started]` marker
|
|
51
|
+
// opens a row, the next matching done entry fills it in place (FIFO per base
|
|
52
|
+
// action). Started-only rows stay in flight; done-only entries (legacy logs with
|
|
53
|
+
// no start marker) render as standalone rows. Result order = first-seen order,
|
|
54
|
+
// which is already ascending because `entries` is sorted ascending.
|
|
55
|
+
function pairEntries(entries) {
|
|
56
|
+
const rows = [];
|
|
57
|
+
const open = new Map();
|
|
58
|
+
for (const e of entries) {
|
|
59
|
+
const isStarted = STARTED_SUFFIX_RE.test(e.step);
|
|
60
|
+
const base = e.step.replace(STARTED_SUFFIX_RE, '');
|
|
61
|
+
if (isStarted) {
|
|
62
|
+
const row = { step: base, agent: e.agent, started: e.time, done: '', note: e.note };
|
|
63
|
+
rows.push(row);
|
|
64
|
+
const queue = open.get(base);
|
|
65
|
+
if (queue)
|
|
66
|
+
queue.push(row);
|
|
67
|
+
else
|
|
68
|
+
open.set(base, [row]);
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
const pending = open.get(base)?.shift();
|
|
72
|
+
if (pending) {
|
|
73
|
+
// Done fills the open row; the done entry carries the meaningful note.
|
|
74
|
+
pending.done = e.time;
|
|
75
|
+
pending.agent = e.agent;
|
|
76
|
+
pending.note = e.note;
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
rows.push({ step: base, agent: e.agent, started: '', done: e.time, note: e.note });
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return rows;
|
|
84
|
+
}
|
|
43
85
|
function log(args = []) {
|
|
44
86
|
if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
|
|
45
87
|
process.stdout.write(USAGE);
|
|
@@ -65,11 +107,19 @@ function log(args = []) {
|
|
|
65
107
|
process.exitCode = 1;
|
|
66
108
|
return;
|
|
67
109
|
}
|
|
68
|
-
const
|
|
110
|
+
const steps = pairEntries(entries);
|
|
111
|
+
const rows = steps.map((s, idx) => [
|
|
112
|
+
String(idx + 1),
|
|
113
|
+
s.step,
|
|
114
|
+
s.agent,
|
|
115
|
+
s.started,
|
|
116
|
+
s.done || (s.started ? '(in progress)' : ''),
|
|
117
|
+
s.note
|
|
118
|
+
]);
|
|
69
119
|
for (const line of formatTable(TABLE_HEADERS, rows, { zebra: Boolean(process.stdout.isTTY) })) {
|
|
70
120
|
process.stdout.write(`${line}\n`);
|
|
71
121
|
}
|
|
72
|
-
process.stdout.write(`Total: ${
|
|
122
|
+
process.stdout.write(`Total: ${steps.length} steps\n`);
|
|
73
123
|
}
|
|
74
|
-
export { log, parseActivityLog };
|
|
124
|
+
export { log, parseActivityLog, pairEntries };
|
|
75
125
|
//# sourceMappingURL=log.js.map
|
package/dist/lib/task/index.js
CHANGED
|
@@ -34,14 +34,9 @@ export async function runTask(args) {
|
|
|
34
34
|
return;
|
|
35
35
|
}
|
|
36
36
|
switch (subcommand) {
|
|
37
|
-
case '
|
|
38
|
-
const {
|
|
39
|
-
|
|
40
|
-
break;
|
|
41
|
-
}
|
|
42
|
-
case 'show': {
|
|
43
|
-
const { show } = await import("./commands/show.js");
|
|
44
|
-
show(rest);
|
|
37
|
+
case 'cat': {
|
|
38
|
+
const { cat } = await import("./commands/cat.js");
|
|
39
|
+
cat(rest);
|
|
45
40
|
break;
|
|
46
41
|
}
|
|
47
42
|
case 'files': {
|
|
@@ -49,11 +44,6 @@ export async function runTask(args) {
|
|
|
49
44
|
files(rest);
|
|
50
45
|
break;
|
|
51
46
|
}
|
|
52
|
-
case 'cat': {
|
|
53
|
-
const { cat } = await import("./commands/cat.js");
|
|
54
|
-
cat(rest);
|
|
55
|
-
break;
|
|
56
|
-
}
|
|
57
47
|
case 'grep': {
|
|
58
48
|
const { grep } = await import("./commands/grep.js");
|
|
59
49
|
grep(rest);
|
|
@@ -64,6 +54,16 @@ export async function runTask(args) {
|
|
|
64
54
|
log(rest);
|
|
65
55
|
break;
|
|
66
56
|
}
|
|
57
|
+
case 'ls': {
|
|
58
|
+
const { ls } = await import("./commands/ls.js");
|
|
59
|
+
ls(rest);
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
case 'show': {
|
|
63
|
+
const { show } = await import("./commands/show.js");
|
|
64
|
+
show(rest);
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
67
|
case 'status': {
|
|
68
68
|
const { status } = await import("./commands/status.js");
|
|
69
69
|
status(rest);
|
package/dist/lib/update.js
CHANGED
|
@@ -83,7 +83,7 @@ function syncFileRegistry(config, platformType, enabledTUIs) {
|
|
|
83
83
|
}
|
|
84
84
|
async function cmdUpdate() {
|
|
85
85
|
console.log('');
|
|
86
|
-
console.log('
|
|
86
|
+
console.log(' ai update');
|
|
87
87
|
console.log(' ==================================');
|
|
88
88
|
console.log('');
|
|
89
89
|
// check config exists
|
package/lib/init.ts
CHANGED
|
@@ -119,7 +119,7 @@ function parseLocalSources(input: string): SourceEntry[] {
|
|
|
119
119
|
|
|
120
120
|
async function cmdInit(): Promise<void> {
|
|
121
121
|
console.log('');
|
|
122
|
-
console.log('
|
|
122
|
+
console.log(' ai init');
|
|
123
123
|
console.log(' ================================');
|
|
124
124
|
console.log(' Optional template and skill sources can be added now or later in .agents/.airc.json.');
|
|
125
125
|
console.log('');
|
package/lib/merge.ts
CHANGED
|
@@ -901,7 +901,7 @@ function printReport(report: MergeReport): void {
|
|
|
901
901
|
async function cmdMerge(args: string[]): Promise<void> {
|
|
902
902
|
const sourcePath = args[0];
|
|
903
903
|
if (!sourcePath) {
|
|
904
|
-
throw new Error('Usage:
|
|
904
|
+
throw new Error('Usage: ai merge <source-path>');
|
|
905
905
|
}
|
|
906
906
|
|
|
907
907
|
const resolvedSource = path.resolve(sourcePath);
|
|
@@ -1084,6 +1084,13 @@ function runEngineTaskCommand(engine: string, cmd: string, args: string[], opts:
|
|
|
1084
1084
|
return runTaskCommand(command.cmd, command.args, opts);
|
|
1085
1085
|
}
|
|
1086
1086
|
|
|
1087
|
+
// `docker run` args for mounting a tool's containerMount as an in-container
|
|
1088
|
+
// tmpfs. containerMount is an in-container path, so it is NOT engine-converted.
|
|
1089
|
+
export function buildTmpfsRunArgs(containerMount: string, tmpfs: { size?: string }): string[] {
|
|
1090
|
+
const size = tmpfs.size ?? '512m';
|
|
1091
|
+
return ['--tmpfs', `${containerMount}:rw,size=${size}`];
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1087
1094
|
export function buildImage(
|
|
1088
1095
|
config: Pick<SandboxCreateConfig, 'project' | 'imageName' | 'repoRoot'> & { engine?: string | null },
|
|
1089
1096
|
tools: SandboxTool[],
|
|
@@ -1397,10 +1404,12 @@ export async function create(args: string[]): Promise<void> {
|
|
|
1397
1404
|
// The TUI reads <toolDir>/opencode.json via OPENCODE_CONFIG pinned in tools.js.
|
|
1398
1405
|
ensureOpenCodeModelInheritance(opencodeEntry.dir, effectiveConfig.home);
|
|
1399
1406
|
}
|
|
1400
|
-
const toolVolumes = effectiveResolvedTools.flatMap(({ tool, dir }) =>
|
|
1401
|
-
'-v',
|
|
1402
|
-
|
|
1403
|
-
|
|
1407
|
+
const toolVolumes = effectiveResolvedTools.flatMap(({ tool, dir }) =>
|
|
1408
|
+
tool.tmpfs ? [] : ['-v', volumeArg(engine, dir, tool.containerMount)]
|
|
1409
|
+
);
|
|
1410
|
+
const tmpfsArgs = effectiveResolvedTools.flatMap(({ tool }) =>
|
|
1411
|
+
tool.tmpfs ? buildTmpfsRunArgs(tool.containerMount, tool.tmpfs) : []
|
|
1412
|
+
);
|
|
1404
1413
|
const workspaceDir = path.join(effectiveConfig.repoRoot, '.agents', 'workspace');
|
|
1405
1414
|
hostShellConfig = prepareHostShellConfig({
|
|
1406
1415
|
home: effectiveConfig.home,
|
|
@@ -1412,6 +1421,24 @@ export async function create(args: string[]): Promise<void> {
|
|
|
1412
1421
|
'-v',
|
|
1413
1422
|
volumeArg(engine, hostPath, containerPath, ':ro')
|
|
1414
1423
|
]);
|
|
1424
|
+
// A tmpfs containerMount starts empty, so the config seeded into the
|
|
1425
|
+
// host dir before launch would be invisible in-container. Bind only
|
|
1426
|
+
// the explicitly declared seed entries (config.toml, model-catalogs)
|
|
1427
|
+
// back over the tmpfs as nested mounts — the same proven mechanism as
|
|
1428
|
+
// hostLiveMounts/auth.json, established at `docker run` time (no
|
|
1429
|
+
// post-start `docker cp`, which can land under a freshly-mounted
|
|
1430
|
+
// tmpfs instead of inside it). The allowlist is deliberate: any
|
|
1431
|
+
// runtime files left in the host dir (e.g. a stale logs_2.sqlite or
|
|
1432
|
+
// sessions/ from a previous bind-mount era) must NOT be re-mounted,
|
|
1433
|
+
// or the high-churn writes would land on the host SSD again.
|
|
1434
|
+
const tmpfsSeedVolumes = effectiveResolvedTools.flatMap(({ tool, dir }) =>
|
|
1435
|
+
(tool.tmpfs?.seed ?? []).flatMap((entry) => {
|
|
1436
|
+
const hostPath = path.join(dir, entry);
|
|
1437
|
+
return fs.existsSync(hostPath)
|
|
1438
|
+
? ['-v', volumeArg(engine, hostPath, path.posix.join(tool.containerMount, entry))]
|
|
1439
|
+
: [];
|
|
1440
|
+
})
|
|
1441
|
+
);
|
|
1415
1442
|
const liveMountVolumes = effectiveResolvedTools.flatMap(({ tool }) =>
|
|
1416
1443
|
(tool.hostLiveMounts ?? [])
|
|
1417
1444
|
.filter(({ hostPath }) => fs.existsSync(hostPath))
|
|
@@ -1466,6 +1493,8 @@ export async function create(args: string[]): Promise<void> {
|
|
|
1466
1493
|
volumeArg(engine, hostJoin(effectiveConfig.home, '.ssh'), '/home/devuser/.ssh', ':ro'),
|
|
1467
1494
|
...dotfilesMount,
|
|
1468
1495
|
...toolVolumes,
|
|
1496
|
+
...tmpfsArgs,
|
|
1497
|
+
...tmpfsSeedVolumes,
|
|
1469
1498
|
...liveMountVolumes,
|
|
1470
1499
|
...shellConfigVolumes,
|
|
1471
1500
|
...envFile.dockerArgs,
|
package/lib/sandbox/index.ts
CHANGED
|
@@ -6,9 +6,6 @@ Commands:
|
|
|
6
6
|
Enter sandbox or run a command. N (bare) is the
|
|
7
7
|
recommended form for task short ids (e.g.
|
|
8
8
|
'ai sandbox exec 11'); '#N' is also accepted.
|
|
9
|
-
start <branch | TASK-id | N | '#N'>
|
|
10
|
-
Start an existing stopped sandbox container
|
|
11
|
-
(e.g. after the Docker daemon restarted)
|
|
12
9
|
ls List sandboxes for the current project (the '#'
|
|
13
10
|
column is a display-only row number; the 'SHORT'
|
|
14
11
|
column shows the active task short id, '-' if none)
|
|
@@ -19,6 +16,9 @@ Commands:
|
|
|
19
16
|
rm <branch> | --all | --purge
|
|
20
17
|
Remove one sandbox, all sandboxes not bound to an
|
|
21
18
|
active task (--all), or tear down everything (--purge)
|
|
19
|
+
start <branch | TASK-id | N | '#N'>
|
|
20
|
+
Start an existing stopped sandbox container
|
|
21
|
+
(e.g. after the Docker daemon restarted)
|
|
22
22
|
vm status|start|stop Manage the sandbox VM (macOS) or check the backend (Windows)
|
|
23
23
|
|
|
24
24
|
Run 'ai sandbox <command> --help' for details.`;
|
|
@@ -51,6 +51,21 @@ export async function runSandbox(args: string[]): Promise<void> {
|
|
|
51
51
|
}
|
|
52
52
|
break;
|
|
53
53
|
}
|
|
54
|
+
case 'ls': {
|
|
55
|
+
const { ls } = await import('./commands/ls.ts');
|
|
56
|
+
ls(rest);
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
case 'prune': {
|
|
60
|
+
const { prune } = await import('./commands/prune.ts');
|
|
61
|
+
await prune(rest);
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
case 'rebuild': {
|
|
65
|
+
const { rebuild } = await import('./commands/rebuild.ts');
|
|
66
|
+
await rebuild(rest);
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
54
69
|
case 'refresh': {
|
|
55
70
|
const { refresh } = await import('./commands/refresh.ts');
|
|
56
71
|
const exitCode = await refresh(rest);
|
|
@@ -59,24 +74,14 @@ export async function runSandbox(args: string[]): Promise<void> {
|
|
|
59
74
|
}
|
|
60
75
|
break;
|
|
61
76
|
}
|
|
62
|
-
case 'start': {
|
|
63
|
-
const { start } = await import('./commands/start.ts');
|
|
64
|
-
await start(rest);
|
|
65
|
-
break;
|
|
66
|
-
}
|
|
67
|
-
case 'ls': {
|
|
68
|
-
const { ls } = await import('./commands/ls.ts');
|
|
69
|
-
ls(rest);
|
|
70
|
-
break;
|
|
71
|
-
}
|
|
72
77
|
case 'rm': {
|
|
73
78
|
const { rm } = await import('./commands/rm.ts');
|
|
74
79
|
await rm(rest);
|
|
75
80
|
break;
|
|
76
81
|
}
|
|
77
|
-
case '
|
|
78
|
-
const {
|
|
79
|
-
await
|
|
82
|
+
case 'start': {
|
|
83
|
+
const { start } = await import('./commands/start.ts');
|
|
84
|
+
await start(rest);
|
|
80
85
|
break;
|
|
81
86
|
}
|
|
82
87
|
case 'vm': {
|
|
@@ -84,11 +89,6 @@ export async function runSandbox(args: string[]): Promise<void> {
|
|
|
84
89
|
await vm(rest);
|
|
85
90
|
break;
|
|
86
91
|
}
|
|
87
|
-
case 'rebuild': {
|
|
88
|
-
const { rebuild } = await import('./commands/rebuild.ts');
|
|
89
|
-
await rebuild(rest);
|
|
90
|
-
break;
|
|
91
|
-
}
|
|
92
92
|
default:
|
|
93
93
|
throw new Error(`Unknown sandbox command: ${subcommand}`);
|
|
94
94
|
}
|
package/lib/sandbox/tools.ts
CHANGED
|
@@ -19,6 +19,13 @@ export type SandboxTool = {
|
|
|
19
19
|
pathRewriteFiles?: string[];
|
|
20
20
|
hostLiveMounts?: Array<{ hostPath: string; containerSubpath: string }>;
|
|
21
21
|
postSetupCmds?: string[];
|
|
22
|
+
// When set, containerMount is mounted as an in-container tmpfs (RAM) instead
|
|
23
|
+
// of bind-mounting the host config dir, keeping high-churn tool logs off the
|
|
24
|
+
// host disk. `seed` lists the host-dir entries (relative to the tool's config
|
|
25
|
+
// dir) to bind back over the tmpfs so seeded config stays visible — it is an
|
|
26
|
+
// explicit allowlist so runtime files (e.g. logs_2.sqlite, sessions) left in
|
|
27
|
+
// the host dir are NOT re-mounted, which would defeat the tmpfs.
|
|
28
|
+
tmpfs?: { size?: string; seed?: string[] };
|
|
22
29
|
};
|
|
23
30
|
|
|
24
31
|
type ToolsConfig = {
|
|
@@ -70,6 +77,12 @@ function createBuiltinTools(home: string, project: string): Record<string, Sandb
|
|
|
70
77
|
containerMount: '/home/devuser/.codex',
|
|
71
78
|
versionCmd: 'codex --version',
|
|
72
79
|
setupHint: 'Run codex once inside the container and choose Device Code login if needed.',
|
|
80
|
+
// codex churns ~/.codex/logs_2.sqlite heavily (upstream openai/codex#24275);
|
|
81
|
+
// a bind-mount would write-amplify onto the host SSD via virtiofs. Mount the
|
|
82
|
+
// codex home as tmpfs so those logs stay in RAM and die with the container.
|
|
83
|
+
// Only the seeded config (config.toml, model-catalogs) is bound back over
|
|
84
|
+
// the tmpfs; runtime files like logs_2.sqlite must stay in RAM.
|
|
85
|
+
tmpfs: { size: '512m', seed: ['config.toml', 'model-catalogs'] },
|
|
73
86
|
hostLiveMounts: [
|
|
74
87
|
{ hostPath: hostJoin(home, '.codex', 'auth.json'), containerSubpath: 'auth.json' }
|
|
75
88
|
],
|
|
@@ -259,6 +272,19 @@ function parseHostLiveMounts(value: unknown, context: string): SandboxTool['host
|
|
|
259
272
|
});
|
|
260
273
|
}
|
|
261
274
|
|
|
275
|
+
function parseTmpfs(value: unknown, context: string): SandboxTool['tmpfs'] {
|
|
276
|
+
if (value === undefined) {
|
|
277
|
+
return undefined;
|
|
278
|
+
}
|
|
279
|
+
if (!isPlainObject(value)) {
|
|
280
|
+
throw new Error(`${context}: field "tmpfs" must be an object when provided`);
|
|
281
|
+
}
|
|
282
|
+
return {
|
|
283
|
+
size: asOptionalNonEmptyString(value.size, 'tmpfs.size', context),
|
|
284
|
+
seed: asStringArray(value.seed, 'tmpfs.seed', context)
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
262
288
|
export function parseCustomTool(
|
|
263
289
|
entry: unknown,
|
|
264
290
|
index: number,
|
|
@@ -294,7 +320,8 @@ export function parseCustomTool(
|
|
|
294
320
|
hostPreSeedDirs: parseHostPreSeedDirs(entry.hostPreSeedDirs, context),
|
|
295
321
|
pathRewriteFiles: asStringArray(entry.pathRewriteFiles, 'pathRewriteFiles', context),
|
|
296
322
|
hostLiveMounts: parseHostLiveMounts(entry.hostLiveMounts, context),
|
|
297
|
-
postSetupCmds: asStringArray(entry.postSetupCmds, 'postSetupCmds', context)
|
|
323
|
+
postSetupCmds: asStringArray(entry.postSetupCmds, 'postSetupCmds', context),
|
|
324
|
+
tmpfs: parseTmpfs(entry.tmpfs, context)
|
|
298
325
|
};
|
|
299
326
|
|
|
300
327
|
validateTool(tool);
|