@fitlab-ai/agent-infra 0.7.0 → 0.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.ts +12 -1
- package/dist/bin/cli.js +13 -1
- package/dist/lib/builtin-tuis.js +45 -0
- package/dist/lib/defaults.json +3 -0
- package/dist/lib/init.js +62 -23
- package/dist/lib/prompt.js +49 -1
- package/dist/lib/sandbox/commands/create.js +10 -2
- package/dist/lib/sandbox/commands/enter.js +8 -7
- package/dist/lib/sandbox/commands/list-running.js +62 -28
- package/dist/lib/sandbox/commands/ls.js +20 -22
- package/dist/lib/sandbox/commands/rebuild.js +3 -11
- package/dist/lib/sandbox/commands/rm.js +2 -0
- package/dist/lib/sandbox/image-prune.js +18 -0
- package/dist/lib/sandbox/index.js +7 -3
- package/dist/lib/sandbox/task-resolver.js +18 -0
- package/dist/lib/sandbox/tools.js +1 -1
- package/dist/lib/table.js +29 -0
- package/dist/lib/task/commands/ls.js +122 -0
- package/dist/lib/task/commands/show.js +135 -0
- package/dist/lib/task/frontmatter.js +32 -0
- package/dist/lib/task/index.js +41 -0
- package/dist/lib/task/short-id.js +80 -0
- package/dist/lib/update.js +59 -18
- package/lib/builtin-tuis.ts +55 -0
- package/lib/defaults.json +3 -0
- package/lib/init.ts +87 -35
- package/lib/prompt.ts +54 -1
- package/lib/sandbox/commands/create.ts +11 -2
- package/lib/sandbox/commands/enter.ts +8 -7
- package/lib/sandbox/commands/list-running.ts +70 -31
- package/lib/sandbox/commands/ls.ts +25 -25
- package/lib/sandbox/commands/rebuild.ts +3 -12
- package/lib/sandbox/commands/rm.ts +3 -0
- package/lib/sandbox/image-prune.ts +23 -0
- package/lib/sandbox/index.ts +7 -3
- package/lib/sandbox/task-resolver.ts +23 -1
- package/lib/sandbox/tools.ts +1 -1
- package/lib/table.ts +32 -0
- package/lib/task/commands/ls.ts +138 -0
- package/lib/task/commands/show.ts +139 -0
- package/lib/task/frontmatter.ts +30 -0
- package/lib/task/index.ts +44 -0
- package/lib/task/short-id.ts +97 -0
- package/lib/update.ts +71 -30
- package/package.json +1 -1
- package/templates/.agents/README.en.md +32 -0
- package/templates/.agents/README.zh-CN.md +32 -0
- package/templates/.agents/hooks/auto-resume.sh +87 -0
- 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/milestone-inference.github.en.md +4 -1
- package/templates/.agents/rules/milestone-inference.github.zh-CN.md +4 -1
- package/templates/.agents/rules/next-step-output.en.md +59 -0
- package/templates/.agents/rules/next-step-output.zh-CN.md +59 -0
- package/templates/.agents/rules/task-short-id.en.md +133 -0
- package/templates/.agents/rules/task-short-id.zh-CN.md +105 -0
- package/templates/.agents/scripts/platform-adapters/platform-sync.github.js +17 -0
- package/templates/.agents/scripts/task-short-id.js +556 -0
- package/templates/.agents/skills/analyze-task/SKILL.en.md +13 -11
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +13 -12
- package/templates/.agents/skills/analyze-task/config/verify.en.json +1 -1
- package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/block-task/SKILL.en.md +17 -5
- package/templates/.agents/skills/block-task/SKILL.zh-CN.md +17 -6
- package/templates/.agents/skills/block-task/config/verify.json +1 -1
- package/templates/.agents/skills/cancel-task/SKILL.en.md +17 -5
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +17 -6
- package/templates/.agents/skills/cancel-task/config/verify.json +1 -1
- package/templates/.agents/skills/check-task/SKILL.en.md +15 -9
- package/templates/.agents/skills/check-task/SKILL.zh-CN.md +15 -10
- package/templates/.agents/skills/close-codescan/SKILL.en.md +16 -5
- package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +16 -5
- package/templates/.agents/skills/close-dependabot/SKILL.en.md +16 -5
- package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +16 -5
- package/templates/.agents/skills/code-task/SKILL.en.md +13 -5
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +14 -6
- package/templates/.agents/skills/code-task/config/verify.en.json +2 -1
- package/templates/.agents/skills/code-task/config/verify.zh-CN.json +2 -1
- package/templates/.agents/skills/code-task/reference/fix-mode.en.md +10 -5
- package/templates/.agents/skills/code-task/reference/fix-mode.zh-CN.md +10 -5
- package/templates/.agents/skills/code-task/reference/output-template.en.md +3 -3
- package/templates/.agents/skills/code-task/reference/output-template.zh-CN.md +3 -3
- package/templates/.agents/skills/code-task/reference/report-template.en.md +8 -0
- package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +8 -0
- package/templates/.agents/skills/commit/SKILL.en.md +5 -1
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +5 -1
- package/templates/.agents/skills/commit/reference/task-status-update.en.md +9 -9
- package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +9 -9
- package/templates/.agents/skills/complete-task/SKILL.en.md +17 -1
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +17 -2
- package/templates/.agents/skills/complete-task/config/verify.en.json +1 -1
- package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/create-pr/SKILL.en.md +9 -5
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +9 -5
- package/templates/.agents/skills/create-pr/config/verify.json +2 -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-pr/reference/pr-body-template.en.md +3 -3
- package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +3 -3
- package/templates/.agents/skills/create-task/SKILL.en.md +29 -15
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +29 -16
- package/templates/.agents/skills/create-task/config/verify.json +1 -1
- package/templates/.agents/skills/import-codescan/SKILL.en.md +20 -6
- package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +20 -6
- package/templates/.agents/skills/import-codescan/config/verify.json +1 -1
- package/templates/.agents/skills/import-dependabot/SKILL.en.md +20 -6
- package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +20 -6
- package/templates/.agents/skills/import-dependabot/config/verify.json +1 -1
- package/templates/.agents/skills/import-issue/SKILL.en.md +19 -5
- package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +19 -5
- package/templates/.agents/skills/plan-task/SKILL.en.md +13 -11
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +13 -12
- package/templates/.agents/skills/plan-task/config/verify.en.json +1 -1
- package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/restore-task/SKILL.en.md +12 -0
- package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +12 -1
- package/templates/.agents/skills/review-analysis/SKILL.en.md +7 -1
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +7 -2
- package/templates/.agents/skills/review-analysis/config/verify.en.json +3 -2
- package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +3 -2
- package/templates/.agents/skills/review-analysis/reference/output-templates.en.md +15 -15
- package/templates/.agents/skills/review-analysis/reference/output-templates.zh-CN.md +15 -15
- package/templates/.agents/skills/review-analysis/reference/report-template.en.md +7 -1
- package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +7 -1
- package/templates/.agents/skills/review-analysis/reference/review-criteria.en.md +2 -0
- package/templates/.agents/skills/review-analysis/reference/review-criteria.zh-CN.md +2 -0
- package/templates/.agents/skills/review-code/SKILL.en.md +8 -1
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +8 -2
- package/templates/.agents/skills/review-code/config/verify.en.json +3 -2
- package/templates/.agents/skills/review-code/config/verify.zh-CN.json +3 -2
- package/templates/.agents/skills/review-code/reference/output-templates.en.md +9 -9
- package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +9 -9
- package/templates/.agents/skills/review-code/reference/report-template.en.md +7 -1
- package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +7 -1
- package/templates/.agents/skills/review-code/reference/review-criteria.en.md +2 -0
- package/templates/.agents/skills/review-code/reference/review-criteria.zh-CN.md +2 -0
- package/templates/.agents/skills/review-plan/SKILL.en.md +7 -1
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +7 -2
- package/templates/.agents/skills/review-plan/config/verify.en.json +3 -2
- package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +3 -2
- package/templates/.agents/skills/review-plan/reference/output-templates.en.md +15 -15
- package/templates/.agents/skills/review-plan/reference/output-templates.zh-CN.md +15 -15
- package/templates/.agents/skills/review-plan/reference/report-template.en.md +7 -1
- package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +7 -1
- package/templates/.agents/skills/review-plan/reference/review-criteria.en.md +2 -0
- package/templates/.agents/skills/review-plan/reference/review-criteria.zh-CN.md +2 -0
- 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 +112 -21
- package/templates/.agents/workflows/bug-fix.en.yaml +1 -1
- package/templates/.agents/workflows/bug-fix.zh-CN.yaml +1 -1
- package/templates/.agents/workflows/feature-development.en.yaml +1 -1
- package/templates/.agents/workflows/feature-development.zh-CN.yaml +1 -1
- package/templates/.agents/workflows/refactoring.en.yaml +1 -1
- package/templates/.agents/workflows/refactoring.zh-CN.yaml +1 -1
- package/templates/.claude/settings.json +11 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const BUILTIN_TUI_IDS = ['claude-code', 'codex', 'gemini-cli', 'opencode'] as const;
|
|
2
|
+
type BuiltinTUIId = (typeof BUILTIN_TUI_IDS)[number];
|
|
3
|
+
|
|
4
|
+
const BUILTIN_TUI_DISPLAY: Record<BuiltinTUIId, string> = {
|
|
5
|
+
'claude-code': 'Claude Code',
|
|
6
|
+
'codex': 'Codex',
|
|
7
|
+
'gemini-cli': 'Gemini CLI',
|
|
8
|
+
'opencode': 'OpenCode'
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const BUILTIN_TUI_OWNED_PATH_PREFIXES: Record<BuiltinTUIId, string[]> = {
|
|
12
|
+
'claude-code': ['.claude/'],
|
|
13
|
+
'codex': ['.codex/'],
|
|
14
|
+
'gemini-cli': ['.gemini/'],
|
|
15
|
+
'opencode': ['.opencode/']
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
function isBuiltinTUIId(value: unknown): value is BuiltinTUIId {
|
|
19
|
+
return typeof value === 'string' && (BUILTIN_TUI_IDS as readonly string[]).includes(value);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function resolveEnabledTUIs(value: unknown): Set<BuiltinTUIId> {
|
|
23
|
+
// Missing field / null / non-array → full set (backward compat for legacy
|
|
24
|
+
// .airc.json predating the `tuis` field).
|
|
25
|
+
if (!Array.isArray(value)) return new Set(BUILTIN_TUI_IDS);
|
|
26
|
+
// Empty array is a meaningful, user-set value: "no built-in TUI managed".
|
|
27
|
+
// This supports the customTUI-only project layout.
|
|
28
|
+
const set = new Set<BuiltinTUIId>();
|
|
29
|
+
for (const v of value) {
|
|
30
|
+
if (isBuiltinTUIId(v)) set.add(v);
|
|
31
|
+
}
|
|
32
|
+
return set;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function isPathOwnedByDisabledTUI(rel: string, enabled: Set<BuiltinTUIId>): boolean {
|
|
36
|
+
const normalized = String(rel || '').replace(/\\/g, '/').replace(/^\.\//, '');
|
|
37
|
+
for (const tui of BUILTIN_TUI_IDS) {
|
|
38
|
+
if (enabled.has(tui)) continue;
|
|
39
|
+
for (const prefix of BUILTIN_TUI_OWNED_PATH_PREFIXES[tui]) {
|
|
40
|
+
const trimmed = prefix.replace(/\/$/, '');
|
|
41
|
+
if (normalized === trimmed || normalized.startsWith(prefix)) return true;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export {
|
|
48
|
+
BUILTIN_TUI_IDS,
|
|
49
|
+
BUILTIN_TUI_DISPLAY,
|
|
50
|
+
BUILTIN_TUI_OWNED_PATH_PREFIXES,
|
|
51
|
+
isBuiltinTUIId,
|
|
52
|
+
resolveEnabledTUIs,
|
|
53
|
+
isPathOwnedByDisabledTUI
|
|
54
|
+
};
|
|
55
|
+
export type { BuiltinTUIId };
|
package/lib/defaults.json
CHANGED
package/lib/init.ts
CHANGED
|
@@ -3,11 +3,18 @@ import path from 'node:path';
|
|
|
3
3
|
import { execSync } from 'node:child_process';
|
|
4
4
|
import { platform } from 'node:os';
|
|
5
5
|
import { info, ok, err } from './log.ts';
|
|
6
|
-
import { prompt, select, closePrompt } from './prompt.ts';
|
|
6
|
+
import { prompt, select, multiSelect, closePrompt } from './prompt.ts';
|
|
7
7
|
import { resolveTemplateDir } from './paths.ts';
|
|
8
8
|
import { renderFile, copySkillDir, KNOWN_PLATFORMS } from './render.ts';
|
|
9
9
|
import { enginesForPlatform } from './sandbox/engines/index.ts';
|
|
10
10
|
import { VERSION } from './version.ts';
|
|
11
|
+
import {
|
|
12
|
+
BUILTIN_TUI_IDS,
|
|
13
|
+
BUILTIN_TUI_DISPLAY,
|
|
14
|
+
isPathOwnedByDisabledTUI,
|
|
15
|
+
resolveEnabledTUIs
|
|
16
|
+
} from './builtin-tuis.ts';
|
|
17
|
+
import type { BuiltinTUIId } from './builtin-tuis.ts';
|
|
11
18
|
|
|
12
19
|
type FileRegistry = {
|
|
13
20
|
managed: string[];
|
|
@@ -23,6 +30,7 @@ type SourceEntry = {
|
|
|
23
30
|
type Defaults = {
|
|
24
31
|
files: FileRegistry;
|
|
25
32
|
sandbox: Record<string, unknown>;
|
|
33
|
+
task: { shortIdLength: number };
|
|
26
34
|
labels: Record<string, unknown>;
|
|
27
35
|
requiresPullRequest: boolean;
|
|
28
36
|
};
|
|
@@ -35,8 +43,10 @@ type AgentConfig = {
|
|
|
35
43
|
requiresPullRequest: boolean;
|
|
36
44
|
templateVersion: string;
|
|
37
45
|
sandbox: Record<string, unknown>;
|
|
46
|
+
task: { shortIdLength: number };
|
|
38
47
|
labels: Record<string, unknown>;
|
|
39
48
|
files: FileRegistry;
|
|
49
|
+
tuis: string[];
|
|
40
50
|
templates?: { sources: SourceEntry[] };
|
|
41
51
|
skills?: { sources: SourceEntry[] };
|
|
42
52
|
};
|
|
@@ -60,10 +70,15 @@ function isPathOwnedByOtherPlatform(relativePath: string, platformType: string):
|
|
|
60
70
|
return candidate !== platformType;
|
|
61
71
|
}
|
|
62
72
|
|
|
63
|
-
function buildDefaultFiles(platformType: string): FileRegistry {
|
|
73
|
+
function buildDefaultFiles(platformType: string, enabledTUIs: Set<BuiltinTUIId>): FileRegistry {
|
|
74
|
+
const ownedByDisabled = (entry: string) => isPathOwnedByDisabledTUI(entry, enabledTUIs);
|
|
64
75
|
return {
|
|
65
|
-
managed: (defaults.files.managed || []).filter(
|
|
66
|
-
|
|
76
|
+
managed: (defaults.files.managed || []).filter(
|
|
77
|
+
(entry) => !isPathOwnedByOtherPlatform(entry, platformType) && !ownedByDisabled(entry)
|
|
78
|
+
),
|
|
79
|
+
merged: (defaults.files.merged || []).filter(
|
|
80
|
+
(entry) => !isPathOwnedByOtherPlatform(entry, platformType) && !ownedByDisabled(entry)
|
|
81
|
+
),
|
|
67
82
|
ejected: structuredClone(defaults.files.ejected || [])
|
|
68
83
|
};
|
|
69
84
|
}
|
|
@@ -216,6 +231,20 @@ async function cmdInit(): Promise<void> {
|
|
|
216
231
|
);
|
|
217
232
|
const requiresPullRequest = requiresPRChoice !== 'no';
|
|
218
233
|
|
|
234
|
+
let enabledTUIs: string[];
|
|
235
|
+
try {
|
|
236
|
+
enabledTUIs = await multiSelect(
|
|
237
|
+
'Built-in TUI command files to install/manage',
|
|
238
|
+
BUILTIN_TUI_IDS.map((id) => ({ id, label: BUILTIN_TUI_DISPLAY[id] }))
|
|
239
|
+
);
|
|
240
|
+
} catch (e) {
|
|
241
|
+
err(e instanceof Error ? e.message : String(e));
|
|
242
|
+
closePrompt();
|
|
243
|
+
process.exitCode = 1;
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
const enabledTUISet = resolveEnabledTUIs(enabledTUIs);
|
|
247
|
+
|
|
219
248
|
const templateSources = parseLocalSources(await prompt(
|
|
220
249
|
'Template sources (optional, comma-separated local paths, e.g. ~/my-templates; Enter to skip)',
|
|
221
250
|
''
|
|
@@ -259,29 +288,35 @@ async function cmdInit(): Promise<void> {
|
|
|
259
288
|
);
|
|
260
289
|
ok('Installed .agents/skills/update-agent-infra/');
|
|
261
290
|
|
|
262
|
-
// install Claude command
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
291
|
+
// install Claude command (only if enabled)
|
|
292
|
+
if (enabledTUISet.has('claude-code')) {
|
|
293
|
+
renderFile(
|
|
294
|
+
path.join(templateDir, '.claude', 'commands', claudeSrc),
|
|
295
|
+
path.join('.claude', 'commands', 'update-agent-infra.md'),
|
|
296
|
+
replacements
|
|
297
|
+
);
|
|
298
|
+
ok('Installed .claude/commands/update-agent-infra.md');
|
|
299
|
+
}
|
|
269
300
|
|
|
270
|
-
// install Gemini command
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
301
|
+
// install Gemini command (only if enabled)
|
|
302
|
+
if (enabledTUISet.has('gemini-cli')) {
|
|
303
|
+
renderFile(
|
|
304
|
+
path.join(templateDir, '.gemini', 'commands', '_project_', geminiSrc),
|
|
305
|
+
path.join('.gemini', 'commands', project, 'update-agent-infra.toml'),
|
|
306
|
+
replacements
|
|
307
|
+
);
|
|
308
|
+
ok(`Installed .gemini/commands/${project}/update-agent-infra.toml`);
|
|
309
|
+
}
|
|
277
310
|
|
|
278
|
-
// install OpenCode command
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
311
|
+
// install OpenCode command (only if enabled)
|
|
312
|
+
if (enabledTUISet.has('opencode')) {
|
|
313
|
+
renderFile(
|
|
314
|
+
path.join(templateDir, '.opencode', 'commands', opencodeSrc),
|
|
315
|
+
path.join('.opencode', 'commands', 'update-agent-infra.md'),
|
|
316
|
+
replacements
|
|
317
|
+
);
|
|
318
|
+
ok('Installed .opencode/commands/update-agent-infra.md');
|
|
319
|
+
}
|
|
285
320
|
|
|
286
321
|
// generate .agents/.airc.json
|
|
287
322
|
const config: AgentConfig = {
|
|
@@ -292,8 +327,10 @@ async function cmdInit(): Promise<void> {
|
|
|
292
327
|
requiresPullRequest,
|
|
293
328
|
templateVersion: VERSION,
|
|
294
329
|
sandbox: structuredClone(defaults.sandbox),
|
|
330
|
+
task: structuredClone(defaults.task),
|
|
295
331
|
labels: structuredClone(defaults.labels),
|
|
296
|
-
files: buildDefaultFiles(platformType)
|
|
332
|
+
files: buildDefaultFiles(platformType, enabledTUISet),
|
|
333
|
+
tuis: enabledTUIs
|
|
297
334
|
};
|
|
298
335
|
|
|
299
336
|
if (sandboxEngine) {
|
|
@@ -320,15 +357,30 @@ async function cmdInit(): Promise<void> {
|
|
|
320
357
|
console.log('');
|
|
321
358
|
ok('Project initialized successfully!');
|
|
322
359
|
console.log('');
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
360
|
+
if (enabledTUISet.size === 0) {
|
|
361
|
+
console.log(' No built-in TUI selected.');
|
|
362
|
+
console.log(` Configure "customTUIs" in ${configPath} before running update-agent-infra.`);
|
|
363
|
+
console.log('');
|
|
364
|
+
} else {
|
|
365
|
+
console.log(' Next step: open this project in any AI TUI and run:');
|
|
366
|
+
console.log('');
|
|
367
|
+
const claudeOrOpencode: string[] = [];
|
|
368
|
+
if (enabledTUISet.has('claude-code')) claudeOrOpencode.push('Claude Code');
|
|
369
|
+
if (enabledTUISet.has('opencode')) claudeOrOpencode.push('OpenCode');
|
|
370
|
+
if (claudeOrOpencode.length > 0) {
|
|
371
|
+
console.log(` ${claudeOrOpencode.join(' / ')}: /update-agent-infra`);
|
|
372
|
+
}
|
|
373
|
+
if (enabledTUISet.has('gemini-cli')) {
|
|
374
|
+
console.log(` Gemini CLI: /${project}:update-agent-infra`);
|
|
375
|
+
}
|
|
376
|
+
if (enabledTUISet.has('codex')) {
|
|
377
|
+
console.log(' Codex CLI: $update-agent-infra');
|
|
378
|
+
}
|
|
379
|
+
console.log('');
|
|
380
|
+
console.log(' This will render all templates and set up the full');
|
|
381
|
+
console.log(' AI collaboration infrastructure.');
|
|
382
|
+
console.log('');
|
|
383
|
+
}
|
|
332
384
|
}
|
|
333
385
|
|
|
334
386
|
export { cmdInit };
|
package/lib/prompt.ts
CHANGED
|
@@ -86,6 +86,59 @@ async function select(question: string, choices: string[], defaultValue?: string
|
|
|
86
86
|
return trimmed;
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
async function multiSelect(
|
|
90
|
+
question: string,
|
|
91
|
+
choices: { id: string; label: string }[]
|
|
92
|
+
): Promise<string[]> {
|
|
93
|
+
process.stdout.write(` ${question}:\n`);
|
|
94
|
+
const idWidth = Math.max(...choices.map((c) => c.id.length));
|
|
95
|
+
choices.forEach((c, i) => {
|
|
96
|
+
process.stdout.write(` ${i + 1}) ${c.id.padEnd(idWidth)} (${c.label})\n`);
|
|
97
|
+
});
|
|
98
|
+
ask('Enter comma-separated numbers or ids to keep, or "none" to select nothing [default: all]: ');
|
|
99
|
+
|
|
100
|
+
setupInterface();
|
|
101
|
+
|
|
102
|
+
const line = await nextLine();
|
|
103
|
+
// Strictly distinguish bare Enter (null/empty string) from whitespace input.
|
|
104
|
+
if (line === null || line === '') return choices.map((c) => c.id);
|
|
105
|
+
// Explicit empty selection: "none" means deliberately zero built-in choices.
|
|
106
|
+
if (line.trim().toLowerCase() === 'none') return [];
|
|
107
|
+
|
|
108
|
+
const tokens = line.split(',').map((t) => t.trim());
|
|
109
|
+
if (tokens.some((t) => t === '')) {
|
|
110
|
+
throw new Error(`Invalid selection input: "${line}" (empty token)`);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const idSet = new Set(choices.map((c) => c.id));
|
|
114
|
+
const seenIds = new Set<string>();
|
|
115
|
+
for (const t of tokens) {
|
|
116
|
+
let resolvedId: string | undefined;
|
|
117
|
+
if (/^[0-9]+$/.test(t)) {
|
|
118
|
+
const n = Number.parseInt(t, 10);
|
|
119
|
+
if (n < 1 || n > choices.length) {
|
|
120
|
+
throw new Error(`Selection out of range: "${t}" (expected 1..${choices.length})`);
|
|
121
|
+
}
|
|
122
|
+
resolvedId = choices[n - 1]!.id;
|
|
123
|
+
} else if (idSet.has(t)) {
|
|
124
|
+
resolvedId = t;
|
|
125
|
+
} else {
|
|
126
|
+
throw new Error(`Unknown TUI selection token: "${t}"`);
|
|
127
|
+
}
|
|
128
|
+
if (seenIds.has(resolvedId)) {
|
|
129
|
+
throw new Error(`Duplicate selection: "${t}" resolves to already-selected "${resolvedId}"`);
|
|
130
|
+
}
|
|
131
|
+
seenIds.add(resolvedId);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Normalize to prompt order: users can type tokens in any order, but the
|
|
135
|
+
// persisted array follows the canonical choices order to keep .airc.json
|
|
136
|
+
// diffs stable. An empty result here is impossible (tokens.length > 0 and
|
|
137
|
+
// every token resolves to an id), so no separate empty guard is needed —
|
|
138
|
+
// explicit "none" handled above.
|
|
139
|
+
return choices.map((c) => c.id).filter((id) => seenIds.has(id));
|
|
140
|
+
}
|
|
141
|
+
|
|
89
142
|
function closePrompt(): void {
|
|
90
143
|
if (_rl) {
|
|
91
144
|
_rl.close();
|
|
@@ -94,4 +147,4 @@ function closePrompt(): void {
|
|
|
94
147
|
}
|
|
95
148
|
}
|
|
96
149
|
|
|
97
|
-
export { prompt, select, closePrompt };
|
|
150
|
+
export { prompt, select, multiSelect, closePrompt };
|
|
@@ -868,13 +868,22 @@ export function ensureCodexModelInheritance(toolDir: string, hostHomeDir?: strin
|
|
|
868
868
|
}
|
|
869
869
|
}
|
|
870
870
|
|
|
871
|
+
const inheritSpecs: Array<readonly [string, 'string' | 'number']> = [
|
|
872
|
+
['model', 'string'],
|
|
873
|
+
['model_reasoning_effort', 'string'],
|
|
874
|
+
['model_auto_compact_token_limit', 'number']
|
|
875
|
+
];
|
|
876
|
+
|
|
871
877
|
let changed = false;
|
|
872
|
-
for (const key of
|
|
878
|
+
for (const [key, type] of inheritSpecs) {
|
|
873
879
|
if (Object.hasOwn(sandboxParsed, key)) {
|
|
874
880
|
continue;
|
|
875
881
|
}
|
|
876
882
|
const value = hostParsed[key];
|
|
877
|
-
if (typeof value !== 'string' || value === '') {
|
|
883
|
+
if (type === 'string' && (typeof value !== 'string' || value === '')) {
|
|
884
|
+
continue;
|
|
885
|
+
}
|
|
886
|
+
if (type === 'number' && (typeof value !== 'number' || !Number.isFinite(value) || value <= 0)) {
|
|
878
887
|
continue;
|
|
879
888
|
}
|
|
880
889
|
sandboxParsed[key] = value;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { loadConfig } from '../config.ts';
|
|
2
|
-
import { assertValidBranchName, containerNameCandidates
|
|
2
|
+
import { assertValidBranchName, containerNameCandidates } from '../constants.ts';
|
|
3
3
|
import { detectEngine } from '../engine.ts';
|
|
4
4
|
import {
|
|
5
5
|
formatCredentialWarnings,
|
|
@@ -13,12 +13,14 @@ import { resolveTaskBranch } from '../task-resolver.ts';
|
|
|
13
13
|
import { dotfilesCacheDir, materializeDotfiles } from '../dotfiles.ts';
|
|
14
14
|
import { runInteractiveWithClipboardBridge } from '../clipboard/bridge.ts';
|
|
15
15
|
import { detectHostTimezone } from '../host-timezone.ts';
|
|
16
|
-
import {
|
|
16
|
+
import { isTaskShortRef, resolveTaskShortRef } from './list-running.ts';
|
|
17
17
|
|
|
18
|
-
const USAGE = `Usage: ai sandbox exec <branch | TASK-id | '#N'> [cmd...]
|
|
18
|
+
const USAGE = `Usage: ai sandbox exec <branch | TASK-id | N | '#N'> [cmd...]
|
|
19
19
|
|
|
20
|
-
'#N'
|
|
21
|
-
|
|
20
|
+
N (bare) and '#N' both reference the same active task short id from
|
|
21
|
+
.agents/workspace/active/.short-ids.json. They resolve only via that
|
|
22
|
+
registry — they do not reference a container's row position in
|
|
23
|
+
'ai sandbox ls' output.`;
|
|
22
24
|
const TMUX_ENTRY_PATH = '/usr/local/bin/sandbox-tmux-entry';
|
|
23
25
|
|
|
24
26
|
// Terminal-detection variables that interactive TUIs (e.g. claude-code)
|
|
@@ -122,8 +124,7 @@ export async function enter(args: string[]): Promise<number> {
|
|
|
122
124
|
const [firstArg = '', ...cmd] = args;
|
|
123
125
|
let branch: string;
|
|
124
126
|
if (isTaskShortRef(firstArg)) {
|
|
125
|
-
|
|
126
|
-
branch = resolveTaskShortRef(firstArg, { running });
|
|
127
|
+
branch = resolveTaskShortRef(firstArg, { repoRoot: config.repoRoot });
|
|
127
128
|
} else {
|
|
128
129
|
branch = resolveTaskBranch(firstArg, config.repoRoot);
|
|
129
130
|
}
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
1
4
|
import { runSafeEngine } from '../shell.ts';
|
|
2
5
|
|
|
3
6
|
export type SandboxRow = {
|
|
@@ -85,51 +88,87 @@ export function fetchSandboxRows(
|
|
|
85
88
|
}
|
|
86
89
|
|
|
87
90
|
/**
|
|
88
|
-
* Returns true iff `arg` is a syntactically valid task short reference
|
|
91
|
+
* Returns true iff `arg` is a syntactically valid task short reference.
|
|
92
|
+
* Accepts both bare numeric ('11') and '#'-prefixed ('#11') forms.
|
|
89
93
|
* Zero IO. Callers MUST use this as the gate before constructing any context
|
|
90
94
|
* for resolveTaskShortRef — that way non-matching arguments (e.g. '#abc',
|
|
91
95
|
* '#1.5', '#') never trigger sandbox list IO.
|
|
92
96
|
*/
|
|
93
97
|
export function isTaskShortRef(arg: string): boolean {
|
|
94
|
-
return
|
|
98
|
+
return /^#?\d+$/.test(arg);
|
|
95
99
|
}
|
|
96
100
|
|
|
101
|
+
type RegistryLookup =
|
|
102
|
+
| { status: 'miss' }
|
|
103
|
+
| { status: 'hit'; branch: string };
|
|
104
|
+
|
|
97
105
|
/**
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
* Current implementation: treats the digits as a 1-based index into the
|
|
101
|
-
* supplied running-sandbox list (ls view order). This is the *only*
|
|
102
|
-
* resolution path until the global task-short-id registry lands in a
|
|
103
|
-
* follow-up task; do NOT read task.md or scan .agents/workspace/ from this
|
|
104
|
-
* helper here.
|
|
106
|
+
* Try to resolve a short ref against the global task-short-id registry.
|
|
105
107
|
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
108
|
+
* Tri-state semantics (review-code Round 1 M-1 fix):
|
|
109
|
+
* - 'miss' → script reports no entry (or registry script missing). Caller may fall back.
|
|
110
|
+
* - 'hit' → registry resolved to a task id and branch is found in task.md.
|
|
111
|
+
* - throws → registry hit but task.md is missing or branch metadata is unparseable;
|
|
112
|
+
* surfacing this error is critical — never silently fall back to running index.
|
|
110
113
|
*/
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}
|
|
119
|
-
const
|
|
120
|
-
if (
|
|
121
|
-
throw new Error(`No running sandbox to reference with '${arg}'`);
|
|
122
|
-
}
|
|
123
|
-
if (n > running.length) {
|
|
114
|
+
function tryResolveFromRegistry(arg: string, repoRoot: string): RegistryLookup {
|
|
115
|
+
const scriptPath = path.join(repoRoot, '.agents', 'scripts', 'task-short-id.js');
|
|
116
|
+
if (!fs.existsSync(scriptPath)) return { status: 'miss' };
|
|
117
|
+
// Strip leading '#' when forwarding bare-numeric input through the script's CLI.
|
|
118
|
+
// (Script accepts both forms, but this avoids shell quoting confusion in error
|
|
119
|
+
// messages echoed back to the user.)
|
|
120
|
+
const result = spawnSync('node', [scriptPath, 'resolve', arg], { encoding: 'utf8', cwd: repoRoot });
|
|
121
|
+
if (result.status !== 0) return { status: 'miss' };
|
|
122
|
+
const taskId = (result.stdout || '').trim();
|
|
123
|
+
if (!/^TASK-\d{8}-\d{6}$/.test(taskId)) {
|
|
124
124
|
throw new Error(
|
|
125
|
-
`
|
|
125
|
+
`Registry returned malformed task id for '${arg}': ${JSON.stringify(taskId)}`
|
|
126
126
|
);
|
|
127
127
|
}
|
|
128
|
-
const
|
|
129
|
-
|
|
128
|
+
for (const sub of ['active', 'completed', 'blocked', 'archive']) {
|
|
129
|
+
const taskMdPath = path.join(repoRoot, '.agents', 'workspace', sub, taskId, 'task.md');
|
|
130
|
+
if (!fs.existsSync(taskMdPath)) continue;
|
|
131
|
+
const content = fs.readFileSync(taskMdPath, 'utf8');
|
|
132
|
+
const fm = content.match(/^branch:\s*(.+)$/m);
|
|
133
|
+
if (fm?.[1]?.trim()) {
|
|
134
|
+
return { status: 'hit', branch: fm[1].trim().replace(/^(["'])(.*)\1$/, '$2') };
|
|
135
|
+
}
|
|
136
|
+
const ctx = content.match(/^- \*\*(?:分支|Branch)\*\*:[ \t]*`?([^`\n]+)`?$/m);
|
|
137
|
+
if (ctx?.[1]?.trim()) {
|
|
138
|
+
return { status: 'hit', branch: ctx[1].trim().replace(/^(["'])(.*)\1$/, '$2') };
|
|
139
|
+
}
|
|
130
140
|
throw new Error(
|
|
131
|
-
`
|
|
141
|
+
`Short ref '${arg}' resolved to task ${taskId} but task.md has no branch field`
|
|
132
142
|
);
|
|
133
143
|
}
|
|
134
|
-
|
|
144
|
+
throw new Error(
|
|
145
|
+
`Short ref '${arg}' resolved to task ${taskId} but task.md was not found under any workspace dir`
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
/**
|
|
151
|
+
* Resolve a task short reference (bare 'N' or '#N') to a branch name for the
|
|
152
|
+
* sandbox entrypoint.
|
|
153
|
+
*
|
|
154
|
+
* Resolution: registry-only. Look up the short id in the global task-short-id
|
|
155
|
+
* registry under repoRoot; if hit, read the branch from the matching task.md.
|
|
156
|
+
* On miss (registry empty or short id absent), throw with an actionable
|
|
157
|
+
* message instead of falling back to a container's row position in
|
|
158
|
+
* 'ai sandbox ls' output — that fallback would make the same syntax mean
|
|
159
|
+
* different things depending on `docker ps` state.
|
|
160
|
+
*
|
|
161
|
+
* Precondition: callers MUST gate on isTaskShortRef(arg) === true.
|
|
162
|
+
*/
|
|
163
|
+
export function resolveTaskShortRef(
|
|
164
|
+
arg: string,
|
|
165
|
+
ctx: { repoRoot: string }
|
|
166
|
+
): string {
|
|
167
|
+
const lookup = tryResolveFromRegistry(arg, ctx.repoRoot);
|
|
168
|
+
if (lookup.status === 'hit') return lookup.branch;
|
|
169
|
+
throw new Error(
|
|
170
|
+
`short ref '${arg}' is not in the active task registry. ` +
|
|
171
|
+
`'#N' and bare N resolve only via the registry (not by row position in 'ai sandbox ls'); ` +
|
|
172
|
+
`use a task short id (e.g. 'ai sandbox exec 11'), a TASK-id, or a branch name.`
|
|
173
|
+
);
|
|
135
174
|
}
|
|
@@ -6,40 +6,35 @@ import { loadConfig } from '../config.ts';
|
|
|
6
6
|
import { sandboxBranchLabel, sandboxLabel } from '../constants.ts';
|
|
7
7
|
import { detectEngine } from '../engine.ts';
|
|
8
8
|
import { resolveTools, toolProjectDirCandidates } from '../tools.ts';
|
|
9
|
+
import { formatTable } from '../../table.ts';
|
|
10
|
+
import { lookupShortIdByBranch } from '../../task/short-id.ts';
|
|
9
11
|
import { fetchSandboxRows } from './list-running.ts';
|
|
10
12
|
|
|
11
13
|
export { containerListFormat, parseLabels } from './list-running.ts';
|
|
12
14
|
|
|
13
15
|
const USAGE = `Usage: ai sandbox ls
|
|
14
16
|
|
|
15
|
-
Lists all containers for the current project. The
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
Lists all containers for the current project. The '#' column is a
|
|
18
|
+
display-only row number; the 'SHORT' column shows the active task short
|
|
19
|
+
id bound to each container's branch (via
|
|
20
|
+
.agents/workspace/active/.short-ids.json), or '-' if no active task is
|
|
21
|
+
bound. Pass the SHORT value to "ai sandbox exec" (e.g. 'ai sandbox exec 11').`;
|
|
18
22
|
|
|
19
|
-
const CONTAINER_TABLE_HEADERS = ['#', 'NAMES', 'STATUS', 'BRANCH'] as const;
|
|
23
|
+
const CONTAINER_TABLE_HEADERS = ['#', 'SHORT', 'NAMES', 'STATUS', 'BRANCH'] as const;
|
|
20
24
|
|
|
21
25
|
type ContainerTableRow = {
|
|
22
|
-
|
|
26
|
+
row: string;
|
|
27
|
+
shortId: string;
|
|
23
28
|
name: string;
|
|
24
29
|
status: string;
|
|
25
30
|
branch: string;
|
|
26
31
|
};
|
|
27
32
|
|
|
28
33
|
export function formatContainerTable(rows: ContainerTableRow[]): string[] {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
Math.max(CONTAINER_TABLE_HEADERS[2].length, ...rows.map((row) => row.status.length)),
|
|
34
|
-
Math.max(CONTAINER_TABLE_HEADERS[3].length, ...rows.map((row) => row.branch.length))
|
|
35
|
-
] as const;
|
|
36
|
-
const renderRow = (values: readonly [string, string, string, string]): string =>
|
|
37
|
-
`${values[0].padEnd(widths[0])} ${values[1].padEnd(widths[1])} ${values[2].padEnd(widths[2])} ${values[3]}`.trimEnd();
|
|
38
|
-
|
|
39
|
-
return [
|
|
40
|
-
renderRow(CONTAINER_TABLE_HEADERS),
|
|
41
|
-
...columns.map((column) => renderRow(column))
|
|
42
|
-
];
|
|
34
|
+
return formatTable(
|
|
35
|
+
CONTAINER_TABLE_HEADERS,
|
|
36
|
+
rows.map((r) => [r.row, r.shortId, r.name, r.status, r.branch])
|
|
37
|
+
);
|
|
43
38
|
}
|
|
44
39
|
|
|
45
40
|
function listChildren(dir: string): string[] {
|
|
@@ -69,15 +64,20 @@ export function ls(args: string[] = []): void {
|
|
|
69
64
|
if (ordered.length === 0) {
|
|
70
65
|
p.log.warn(' No sandbox containers');
|
|
71
66
|
} else {
|
|
72
|
-
const tableRows: ContainerTableRow[] = ordered.map((
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
67
|
+
const tableRows: ContainerTableRow[] = ordered.map((container, i) => {
|
|
68
|
+
const shortId = container.branch ? lookupShortIdByBranch(container.branch, config.repoRoot) : null;
|
|
69
|
+
return {
|
|
70
|
+
row: String(i + 1),
|
|
71
|
+
shortId: shortId ?? '-',
|
|
72
|
+
name: container.name,
|
|
73
|
+
status: container.status,
|
|
74
|
+
branch: container.branch
|
|
75
|
+
};
|
|
76
|
+
});
|
|
78
77
|
for (const line of formatContainerTable(tableRows)) {
|
|
79
78
|
process.stdout.write(` ${line}\n`);
|
|
80
79
|
}
|
|
80
|
+
process.stdout.write(` Total: ${ordered.length} containers\n`);
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
p.log.step('Worktrees');
|
|
@@ -7,7 +7,8 @@ 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,
|
|
10
|
+
import { runEngine, runSafeEngine, runVerboseEngine } from '../shell.ts';
|
|
11
|
+
import { pruneSandboxDanglingImages } from '../image-prune.ts';
|
|
11
12
|
import {
|
|
12
13
|
imageSignatureFields,
|
|
13
14
|
resolveTools,
|
|
@@ -89,12 +90,6 @@ export function buildArgs(
|
|
|
89
90
|
return args;
|
|
90
91
|
}
|
|
91
92
|
|
|
92
|
-
function removeImageIfPresent(imageName: string, engine: string): void {
|
|
93
|
-
if (runOkEngine(engine, 'docker', ['image', 'inspect', imageName])) {
|
|
94
|
-
runEngine(engine, 'docker', ['rmi', imageName]);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
93
|
export async function rebuild(args: string[]): Promise<void> {
|
|
99
94
|
const { values } = parseArgs({
|
|
100
95
|
args,
|
|
@@ -126,17 +121,12 @@ export async function rebuild(args: string[]): Promise<void> {
|
|
|
126
121
|
try {
|
|
127
122
|
if (quiet) {
|
|
128
123
|
const spinner = p.spinner();
|
|
129
|
-
spinner.start(`Removing old image ${config.imageName}...`);
|
|
130
|
-
removeImageIfPresent(config.imageName, engine);
|
|
131
|
-
spinner.stop('Old image removed');
|
|
132
124
|
spinner.start('Building image...');
|
|
133
125
|
runEngine(engine, 'docker', buildArgs(config, tools, preparedDockerfile.path, imageSignature, { engine, refresh }), {
|
|
134
126
|
cwd: config.repoRoot
|
|
135
127
|
});
|
|
136
128
|
spinner.stop(pc.green('Sandbox image rebuilt'));
|
|
137
129
|
} else {
|
|
138
|
-
p.log.step(`Removing old image ${config.imageName}`);
|
|
139
|
-
removeImageIfPresent(config.imageName, engine);
|
|
140
130
|
p.log.step('Building image');
|
|
141
131
|
runVerboseEngine(
|
|
142
132
|
engine,
|
|
@@ -146,6 +136,7 @@ export async function rebuild(args: string[]): Promise<void> {
|
|
|
146
136
|
);
|
|
147
137
|
p.log.success(pc.green('Sandbox image rebuilt'));
|
|
148
138
|
}
|
|
139
|
+
pruneSandboxDanglingImages(config, engine);
|
|
149
140
|
} finally {
|
|
150
141
|
preparedDockerfile.cleanup();
|
|
151
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.');
|