@fitlab-ai/agent-infra 0.7.1 → 0.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -1
- package/README.zh-CN.md +9 -3
- package/bin/cli.ts +11 -0
- package/dist/bin/cli.js +12 -0
- package/dist/lib/defaults.json +0 -1
- package/dist/lib/init.js +0 -3
- package/dist/lib/sandbox/commands/create.js +10 -2
- package/dist/lib/sandbox/commands/enter.js +17 -18
- package/dist/lib/sandbox/commands/list-running.js +56 -32
- package/dist/lib/sandbox/commands/ls.js +27 -24
- package/dist/lib/sandbox/commands/start.js +36 -0
- package/dist/lib/sandbox/index.js +15 -3
- package/dist/lib/sandbox/task-resolver.js +1 -1
- package/dist/lib/sandbox/tools.js +1 -1
- package/dist/lib/table.js +38 -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 +90 -0
- package/dist/lib/update.js +25 -8
- package/lib/defaults.json +0 -1
- package/lib/init.ts +0 -10
- package/lib/sandbox/commands/create.ts +11 -2
- package/lib/sandbox/commands/enter.ts +40 -20
- package/lib/sandbox/commands/list-running.ts +65 -37
- package/lib/sandbox/commands/ls.ts +35 -27
- package/lib/sandbox/commands/start.ts +61 -0
- package/lib/sandbox/index.ts +15 -3
- package/lib/sandbox/task-resolver.ts +1 -1
- package/lib/sandbox/tools.ts +1 -1
- package/lib/table.ts +44 -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 +107 -0
- package/lib/update.ts +28 -10
- package/package.json +1 -1
- package/templates/.agents/hooks/auto-resume.sh +104 -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 +62 -0
- package/templates/.agents/rules/next-step-output.zh-CN.md +62 -0
- package/templates/.agents/rules/pr-checks-commands.en.md +5 -0
- package/templates/.agents/rules/pr-checks-commands.github.en.md +62 -0
- package/templates/.agents/rules/pr-checks-commands.github.zh-CN.md +62 -0
- package/templates/.agents/rules/pr-checks-commands.zh-CN.md +5 -0
- package/templates/.agents/rules/pr-sync.github.en.md +7 -0
- package/templates/.agents/rules/pr-sync.github.zh-CN.md +7 -0
- package/templates/.agents/rules/task-short-id.en.md +54 -62
- package/templates/.agents/rules/task-short-id.zh-CN.md +35 -54
- package/templates/.agents/scripts/platform-adapters/platform-sync.github.js +17 -0
- package/templates/.agents/scripts/task-short-id.js +32 -189
- package/templates/.agents/skills/analyze-task/SKILL.en.md +10 -12
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +10 -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 +13 -6
- package/templates/.agents/skills/block-task/SKILL.zh-CN.md +13 -6
- package/templates/.agents/skills/block-task/config/verify.json +1 -1
- package/templates/.agents/skills/cancel-task/SKILL.en.md +13 -6
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +13 -6
- package/templates/.agents/skills/cancel-task/config/verify.json +1 -1
- package/templates/.agents/skills/check-task/SKILL.en.md +12 -10
- package/templates/.agents/skills/check-task/SKILL.zh-CN.md +12 -10
- package/templates/.agents/skills/close-codescan/SKILL.en.md +13 -6
- package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +13 -6
- package/templates/.agents/skills/close-dependabot/SKILL.en.md +13 -6
- package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +13 -6
- package/templates/.agents/skills/code-task/SKILL.en.md +10 -6
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +11 -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 +3 -4
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +3 -4
- package/templates/.agents/skills/commit/reference/task-status-update.en.md +37 -29
- package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +37 -29
- package/templates/.agents/skills/complete-task/SKILL.en.md +41 -4
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +41 -4
- 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 +20 -11
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +20 -11
- package/templates/.agents/skills/create-pr/config/verify.json +2 -1
- package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +2 -1
- package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +2 -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 +17 -17
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +17 -17
- package/templates/.agents/skills/create-task/config/verify.json +1 -1
- package/templates/.agents/skills/import-codescan/SKILL.en.md +8 -8
- package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +8 -8
- package/templates/.agents/skills/import-codescan/config/verify.json +1 -1
- package/templates/.agents/skills/import-dependabot/SKILL.en.md +8 -8
- package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +8 -8
- package/templates/.agents/skills/import-dependabot/config/verify.json +1 -1
- package/templates/.agents/skills/import-issue/SKILL.en.md +7 -7
- package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +7 -7
- package/templates/.agents/skills/plan-task/SKILL.en.md +10 -12
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +10 -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 +1 -1
- package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/review-analysis/SKILL.en.md +4 -2
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +4 -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 +5 -2
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +5 -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 +4 -2
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +4 -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/scripts/sync-templates.js +0 -1
- package/templates/.agents/skills/watch-pr/SKILL.en.md +131 -0
- package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +131 -0
- package/templates/.agents/skills/watch-pr/config/verify.json +22 -0
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +43 -0
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +43 -0
- package/templates/.agents/templates/task.en.md +1 -1
- package/templates/.agents/templates/task.zh-CN.md +1 -1
- package/templates/.agents/workflows/bug-fix.en.yaml +7 -5
- package/templates/.agents/workflows/bug-fix.zh-CN.yaml +6 -5
- package/templates/.agents/workflows/feature-development.en.yaml +7 -5
- package/templates/.agents/workflows/feature-development.zh-CN.yaml +6 -5
- package/templates/.agents/workflows/refactoring.en.yaml +7 -5
- package/templates/.agents/workflows/refactoring.zh-CN.yaml +6 -5
- package/templates/.claude/commands/watch-pr.en.md +8 -0
- package/templates/.claude/commands/watch-pr.zh-CN.md +8 -0
- package/templates/.claude/settings.json +11 -0
- package/templates/.gemini/commands/_project_/watch-pr.en.toml +8 -0
- package/templates/.gemini/commands/_project_/watch-pr.zh-CN.toml +8 -0
- package/templates/.opencode/commands/watch-pr.en.md +11 -0
- package/templates/.opencode/commands/watch-pr.zh-CN.md +11 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { execFileSync } from 'node:child_process';
|
|
4
|
+
import { formatTable } from "../../table.js";
|
|
5
|
+
import { parseTaskFrontmatter, extractTitle } from "../frontmatter.js";
|
|
6
|
+
import { loadShortIdByTaskId } from "../short-id.js";
|
|
7
|
+
const USAGE = `Usage: ai task ls [--all | --blocked | --completed]
|
|
8
|
+
|
|
9
|
+
Lists tasks under .agents/workspace/. Defaults to active tasks only.
|
|
10
|
+
--all Include active + blocked + completed (excludes archive)
|
|
11
|
+
--blocked Only blocked tasks
|
|
12
|
+
--completed Only completed tasks
|
|
13
|
+
|
|
14
|
+
Columns: # (display-only row number) / SHORT (task short id, usable as an argument) / type / status / current_step / branch / title
|
|
15
|
+
`;
|
|
16
|
+
const TASK_ID_RE = /^TASK-\d{8}-\d{6}$/;
|
|
17
|
+
const TABLE_HEADERS = ['#', 'SHORT', 'TYPE', 'STATUS', 'STEP', 'BRANCH', 'TITLE'];
|
|
18
|
+
function detectRepoRoot() {
|
|
19
|
+
try {
|
|
20
|
+
return execFileSync('git', ['rev-parse', '--show-toplevel'], {
|
|
21
|
+
encoding: 'utf8',
|
|
22
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
23
|
+
}).trim();
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
throw new Error('ai task: current directory is not inside a git repository');
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function parseSelection(args) {
|
|
30
|
+
const positional = args.filter((a) => !a.startsWith('--'));
|
|
31
|
+
if (positional.length > 0) {
|
|
32
|
+
return {
|
|
33
|
+
ok: false,
|
|
34
|
+
message: `ai task ls: unexpected positional argument(s): ${positional.join(' ')}`
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
const flags = args.filter((a) => a.startsWith('--'));
|
|
38
|
+
if (flags.length === 0)
|
|
39
|
+
return { ok: true, selection: ['active'] };
|
|
40
|
+
if (flags.length > 1) {
|
|
41
|
+
return {
|
|
42
|
+
ok: false,
|
|
43
|
+
message: 'ai task ls: pass at most one of --all / --blocked / --completed'
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
switch (flags[0]) {
|
|
47
|
+
case '--all':
|
|
48
|
+
return { ok: true, selection: ['active', 'blocked', 'completed'] };
|
|
49
|
+
case '--blocked':
|
|
50
|
+
return { ok: true, selection: ['blocked'] };
|
|
51
|
+
case '--completed':
|
|
52
|
+
return { ok: true, selection: ['completed'] };
|
|
53
|
+
default:
|
|
54
|
+
return { ok: false, message: `ai task ls: unknown flag: ${flags[0]}` };
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function collectTasks(repoRoot, state) {
|
|
58
|
+
const dir = path.join(repoRoot, '.agents', 'workspace', state);
|
|
59
|
+
if (!fs.existsSync(dir))
|
|
60
|
+
return [];
|
|
61
|
+
// Short ids live only in the registry and only for active tasks; archived
|
|
62
|
+
// (blocked/completed) tasks have released their short id and render '-'.
|
|
63
|
+
const shortIdByTaskId = state === 'active' ? loadShortIdByTaskId(repoRoot) : new Map();
|
|
64
|
+
const rows = [];
|
|
65
|
+
for (const entry of fs.readdirSync(dir).sort()) {
|
|
66
|
+
if (!TASK_ID_RE.test(entry))
|
|
67
|
+
continue;
|
|
68
|
+
const taskMdPath = path.join(dir, entry, 'task.md');
|
|
69
|
+
if (!fs.existsSync(taskMdPath))
|
|
70
|
+
continue;
|
|
71
|
+
const content = fs.readFileSync(taskMdPath, 'utf8');
|
|
72
|
+
const fm = parseTaskFrontmatter(content);
|
|
73
|
+
const title = extractTitle(content);
|
|
74
|
+
const shortId = shortIdByTaskId.get(entry) ?? '-';
|
|
75
|
+
rows.push({
|
|
76
|
+
shortId,
|
|
77
|
+
type: fm.type ?? '-',
|
|
78
|
+
status: fm.status ?? state,
|
|
79
|
+
step: fm.current_step ?? '-',
|
|
80
|
+
branch: fm.branch ?? '-',
|
|
81
|
+
title: title || fm.id || entry
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
return rows;
|
|
85
|
+
}
|
|
86
|
+
function ls(args = []) {
|
|
87
|
+
if (args[0] === '--help' || args[0] === '-h') {
|
|
88
|
+
process.stdout.write(USAGE);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const result = parseSelection(args);
|
|
92
|
+
if (!result.ok) {
|
|
93
|
+
process.stderr.write(`${result.message}\n`);
|
|
94
|
+
process.exitCode = 1;
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const { selection } = result;
|
|
98
|
+
const repoRoot = detectRepoRoot();
|
|
99
|
+
const rows = [];
|
|
100
|
+
for (const state of selection) {
|
|
101
|
+
rows.push(...collectTasks(repoRoot, state));
|
|
102
|
+
}
|
|
103
|
+
if (rows.length === 0) {
|
|
104
|
+
process.stdout.write(`No tasks under .agents/workspace/${selection.join('|')}\n`);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const tableRows = rows.map((r, i) => [
|
|
108
|
+
String(i + 1),
|
|
109
|
+
r.shortId,
|
|
110
|
+
r.type,
|
|
111
|
+
r.status,
|
|
112
|
+
r.step,
|
|
113
|
+
r.branch,
|
|
114
|
+
r.title
|
|
115
|
+
]);
|
|
116
|
+
for (const line of formatTable(TABLE_HEADERS, tableRows, { zebra: Boolean(process.stdout.isTTY) })) {
|
|
117
|
+
process.stdout.write(`${line}\n`);
|
|
118
|
+
}
|
|
119
|
+
process.stdout.write(`Total: ${rows.length} tasks\n`);
|
|
120
|
+
}
|
|
121
|
+
export { ls };
|
|
122
|
+
//# sourceMappingURL=ls.js.map
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { execFileSync, spawnSync } from 'node:child_process';
|
|
4
|
+
import { normalizeShortIdInput } from "../short-id.js";
|
|
5
|
+
const USAGE = `Usage: ai task show <N | #N | TASK-id>
|
|
6
|
+
|
|
7
|
+
Prints the task.md content for the matching task.
|
|
8
|
+
N (bare numeric) Recommended; resolves the active short id via the registry.
|
|
9
|
+
'#N' Compatibility form for old commands.
|
|
10
|
+
TASK-YYYYMMDD-HHMMSS Locates a task in active / blocked / completed / archive.
|
|
11
|
+
`;
|
|
12
|
+
const TASK_ID_RE = /^TASK-\d{8}-\d{6}$/;
|
|
13
|
+
// Flat-structured workspace dirs that hold tasks under `{dir}/{taskId}/task.md`.
|
|
14
|
+
// Note: `archive` uses a three-level YYYY/MM/DD layout and is handled separately.
|
|
15
|
+
const FLAT_WORKSPACE_DIRS = ['active', 'blocked', 'completed'];
|
|
16
|
+
function detectRepoRoot() {
|
|
17
|
+
try {
|
|
18
|
+
return execFileSync('git', ['rev-parse', '--show-toplevel'], {
|
|
19
|
+
encoding: 'utf8',
|
|
20
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
21
|
+
}).trim();
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
throw new Error('ai task: current directory is not inside a git repository');
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function readShortIdLength(repoRoot) {
|
|
28
|
+
try {
|
|
29
|
+
const cfg = JSON.parse(fs.readFileSync(path.join(repoRoot, '.agents', '.airc.json'), 'utf8'));
|
|
30
|
+
const v = cfg?.task?.shortIdLength;
|
|
31
|
+
if (typeof v === 'number' && Number.isFinite(v) && v >= 1)
|
|
32
|
+
return v;
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
// fall through to default
|
|
36
|
+
}
|
|
37
|
+
return 2;
|
|
38
|
+
}
|
|
39
|
+
function resolveShortIdToTaskId(arg, repoRoot) {
|
|
40
|
+
const scriptPath = path.join(repoRoot, '.agents', 'scripts', 'task-short-id.js');
|
|
41
|
+
if (!fs.existsSync(scriptPath)) {
|
|
42
|
+
throw new Error(`task-short-id.js not found at ${scriptPath}`);
|
|
43
|
+
}
|
|
44
|
+
const result = spawnSync('node', [scriptPath, 'resolve', arg], {
|
|
45
|
+
encoding: 'utf8',
|
|
46
|
+
cwd: repoRoot
|
|
47
|
+
});
|
|
48
|
+
if (result.status !== 0) {
|
|
49
|
+
throw new Error((result.stderr || '').trim() || `failed to resolve '${arg}'`);
|
|
50
|
+
}
|
|
51
|
+
return result.stdout.trim();
|
|
52
|
+
}
|
|
53
|
+
function listSortedNumeric(dir, width) {
|
|
54
|
+
if (!fs.existsSync(dir))
|
|
55
|
+
return [];
|
|
56
|
+
const pattern = new RegExp(`^\\d{${width}}$`);
|
|
57
|
+
return fs
|
|
58
|
+
.readdirSync(dir)
|
|
59
|
+
.filter((entry) => pattern.test(entry))
|
|
60
|
+
.sort()
|
|
61
|
+
.reverse();
|
|
62
|
+
}
|
|
63
|
+
function findInArchive(repoRoot, taskId) {
|
|
64
|
+
// archive-tasks SKILL writes to .agents/workspace/archive/YYYY/MM/DD/{taskId}/task.md
|
|
65
|
+
// where YYYY/MM/DD comes from completed_at (or updated_at fallback) — NOT from
|
|
66
|
+
// the task id's creation date. So we cannot derive the path from taskId alone;
|
|
67
|
+
// walk the bounded YYYY/MM/DD tree instead. Newest-first to favor recent archives.
|
|
68
|
+
const archiveDir = path.join(repoRoot, '.agents', 'workspace', 'archive');
|
|
69
|
+
for (const year of listSortedNumeric(archiveDir, 4)) {
|
|
70
|
+
const yearDir = path.join(archiveDir, year);
|
|
71
|
+
for (const month of listSortedNumeric(yearDir, 2)) {
|
|
72
|
+
const monthDir = path.join(yearDir, month);
|
|
73
|
+
for (const day of listSortedNumeric(monthDir, 2)) {
|
|
74
|
+
const candidate = path.join(monthDir, day, taskId, 'task.md');
|
|
75
|
+
if (fs.existsSync(candidate))
|
|
76
|
+
return candidate;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
function findTaskMd(repoRoot, taskId) {
|
|
83
|
+
for (const sub of FLAT_WORKSPACE_DIRS) {
|
|
84
|
+
const candidate = path.join(repoRoot, '.agents', 'workspace', sub, taskId, 'task.md');
|
|
85
|
+
if (fs.existsSync(candidate))
|
|
86
|
+
return candidate;
|
|
87
|
+
}
|
|
88
|
+
return findInArchive(repoRoot, taskId);
|
|
89
|
+
}
|
|
90
|
+
function show(args = []) {
|
|
91
|
+
if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
|
|
92
|
+
process.stdout.write(USAGE);
|
|
93
|
+
if (args.length === 0)
|
|
94
|
+
process.exitCode = 1;
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const repoRoot = detectRepoRoot();
|
|
98
|
+
const arg = args[0];
|
|
99
|
+
let taskId;
|
|
100
|
+
if (TASK_ID_RE.test(arg)) {
|
|
101
|
+
taskId = arg;
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
const shortIdLength = readShortIdLength(repoRoot);
|
|
105
|
+
const normalized = normalizeShortIdInput(arg, { shortIdLength });
|
|
106
|
+
if (normalized.kind === 'error') {
|
|
107
|
+
process.stderr.write(`ai task show: ${normalized.message}\n`);
|
|
108
|
+
process.exitCode = 1;
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (normalized.kind === 'pass') {
|
|
112
|
+
process.stderr.write(`ai task show: '${arg}' is not a valid short id or TASK-id; ` +
|
|
113
|
+
`expected bare digits, '#N', or 'TASK-YYYYMMDD-HHMMSS'\n`);
|
|
114
|
+
process.exitCode = 1;
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
try {
|
|
118
|
+
taskId = resolveShortIdToTaskId(normalized.value, repoRoot);
|
|
119
|
+
}
|
|
120
|
+
catch (e) {
|
|
121
|
+
process.stderr.write(`ai task show: ${e.message}\n`);
|
|
122
|
+
process.exitCode = 1;
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
const taskMdPath = findTaskMd(repoRoot, taskId);
|
|
127
|
+
if (!taskMdPath) {
|
|
128
|
+
process.stderr.write(`ai task show: task ${taskId} not found in active / blocked / completed / archive\n`);
|
|
129
|
+
process.exitCode = 1;
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
process.stdout.write(fs.readFileSync(taskMdPath, 'utf8'));
|
|
133
|
+
}
|
|
134
|
+
export { show };
|
|
135
|
+
//# sourceMappingURL=show.js.map
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
function parseTaskFrontmatter(content) {
|
|
2
|
+
const result = {};
|
|
3
|
+
if (!content.startsWith('---'))
|
|
4
|
+
return result;
|
|
5
|
+
const end = content.indexOf('\n---', 3);
|
|
6
|
+
if (end === -1)
|
|
7
|
+
return result;
|
|
8
|
+
const body = content.slice(3, end);
|
|
9
|
+
for (const rawLine of body.split('\n')) {
|
|
10
|
+
const line = rawLine.replace(/\r$/, '');
|
|
11
|
+
if (!line.trim())
|
|
12
|
+
continue;
|
|
13
|
+
const colon = line.indexOf(':');
|
|
14
|
+
if (colon === -1)
|
|
15
|
+
continue;
|
|
16
|
+
const key = line.slice(0, colon).trim();
|
|
17
|
+
const value = line.slice(colon + 1).trim();
|
|
18
|
+
if (key)
|
|
19
|
+
result[key] = value;
|
|
20
|
+
}
|
|
21
|
+
return result;
|
|
22
|
+
}
|
|
23
|
+
function extractTitle(content) {
|
|
24
|
+
for (const line of content.split('\n')) {
|
|
25
|
+
const m = /^#\s+(?:任务[::]?\s*)?(.+)$/.exec(line.trim());
|
|
26
|
+
if (m && m[1])
|
|
27
|
+
return m[1].trim();
|
|
28
|
+
}
|
|
29
|
+
return '';
|
|
30
|
+
}
|
|
31
|
+
export { parseTaskFrontmatter, extractTitle };
|
|
32
|
+
//# sourceMappingURL=frontmatter.js.map
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
const USAGE = `Usage: ai task <command> [options]
|
|
2
|
+
|
|
3
|
+
Commands:
|
|
4
|
+
ls [--all | --blocked | --completed] List tasks (default: active)
|
|
5
|
+
show <N | #N | TASK-id> Print a task.md
|
|
6
|
+
|
|
7
|
+
Examples:
|
|
8
|
+
ai task ls
|
|
9
|
+
ai task show 11
|
|
10
|
+
ai task show TASK-20260612-162737
|
|
11
|
+
|
|
12
|
+
Run 'ai task <command> --help' for details.`;
|
|
13
|
+
export async function runTask(args) {
|
|
14
|
+
const [subcommand, ...rest] = args;
|
|
15
|
+
if (!subcommand) {
|
|
16
|
+
process.stdout.write(`${USAGE}\n`);
|
|
17
|
+
process.exitCode = 1;
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
if (subcommand === '--help' || subcommand === '-h' || subcommand === 'help') {
|
|
21
|
+
process.stdout.write(`${USAGE}\n`);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
switch (subcommand) {
|
|
25
|
+
case 'ls': {
|
|
26
|
+
const { ls } = await import("./commands/ls.js");
|
|
27
|
+
ls(rest);
|
|
28
|
+
break;
|
|
29
|
+
}
|
|
30
|
+
case 'show': {
|
|
31
|
+
const { show } = await import("./commands/show.js");
|
|
32
|
+
show(rest);
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
default:
|
|
36
|
+
process.stderr.write(`Unknown task command: ${subcommand}\n\n`);
|
|
37
|
+
process.stdout.write(`${USAGE}\n`);
|
|
38
|
+
process.exitCode = 1;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
const REGISTRY_NAME = '.short-ids.json';
|
|
4
|
+
function normalizeShortIdInput(input, opts) {
|
|
5
|
+
const L = opts.shortIdLength;
|
|
6
|
+
const m = /^#?(\d+)$/.exec(input);
|
|
7
|
+
if (!m) {
|
|
8
|
+
return { kind: 'pass', value: input };
|
|
9
|
+
}
|
|
10
|
+
const n = Number(m[1]);
|
|
11
|
+
if (n === 0) {
|
|
12
|
+
return {
|
|
13
|
+
kind: 'error',
|
|
14
|
+
message: `short id '${input}' is invalid (#${'0'.repeat(L)} is reserved)`
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
const max = Math.pow(10, L) - 1;
|
|
18
|
+
if (n > max) {
|
|
19
|
+
return {
|
|
20
|
+
kind: 'error',
|
|
21
|
+
message: `short id ${n} exceeds shortIdLength=${L} capacity (max=${max}); archive tasks or raise task.shortIdLength in .agents/.airc.json`
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
return { kind: 'shortId', value: `#${String(n).padStart(L, '0')}` };
|
|
25
|
+
}
|
|
26
|
+
function readRegistry(repoRoot) {
|
|
27
|
+
const registryPath = path.join(repoRoot, '.agents', 'workspace', 'active', REGISTRY_NAME);
|
|
28
|
+
if (!fs.existsSync(registryPath))
|
|
29
|
+
return null;
|
|
30
|
+
try {
|
|
31
|
+
const raw = fs.readFileSync(registryPath, 'utf8');
|
|
32
|
+
const data = JSON.parse(raw);
|
|
33
|
+
if (!data || typeof data !== 'object' || !data.ids)
|
|
34
|
+
return null;
|
|
35
|
+
return data;
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function readBranchFromTaskMd(repoRoot, taskId) {
|
|
42
|
+
const taskMdPath = path.join(repoRoot, '.agents', 'workspace', 'active', taskId, 'task.md');
|
|
43
|
+
if (!fs.existsSync(taskMdPath))
|
|
44
|
+
return null;
|
|
45
|
+
const content = fs.readFileSync(taskMdPath, 'utf8');
|
|
46
|
+
const m = content.match(/^branch:\s*(.+)$/m);
|
|
47
|
+
if (!m || !m[1])
|
|
48
|
+
return null;
|
|
49
|
+
return m[1].trim().replace(/^(["'])(.*)\1$/, '$2');
|
|
50
|
+
}
|
|
51
|
+
function loadShortIdByTaskId(repoRoot) {
|
|
52
|
+
const registry = readRegistry(repoRoot);
|
|
53
|
+
const map = new Map();
|
|
54
|
+
if (!registry)
|
|
55
|
+
return map;
|
|
56
|
+
for (const [key, taskId] of Object.entries(registry.ids)) {
|
|
57
|
+
map.set(taskId, `#${key}`);
|
|
58
|
+
}
|
|
59
|
+
return map;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Resolve a branch to its active-task short id (`#NN`), or `null` when no
|
|
63
|
+
* active task is bound to that branch.
|
|
64
|
+
*
|
|
65
|
+
* Two-state semantics: this only consults the active registry
|
|
66
|
+
* (`active/.short-ids.json`) plus each `active/{taskId}/task.md`. Tasks moved
|
|
67
|
+
* to completed/blocked/cancelled/archive have already released their short id,
|
|
68
|
+
* so their branches return `null` — in `ai sandbox ls` that surfaces as `-`,
|
|
69
|
+
* meaning the sandbox is free to remove.
|
|
70
|
+
*/
|
|
71
|
+
function lookupShortIdByBranch(branch, repoRoot, _opts) {
|
|
72
|
+
const registry = readRegistry(repoRoot);
|
|
73
|
+
if (!registry)
|
|
74
|
+
return null;
|
|
75
|
+
const matches = [];
|
|
76
|
+
for (const [key, taskId] of Object.entries(registry.ids)) {
|
|
77
|
+
const taskBranch = readBranchFromTaskMd(repoRoot, taskId);
|
|
78
|
+
if (taskBranch && taskBranch === branch) {
|
|
79
|
+
matches.push(`#${key}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (matches.length === 0)
|
|
83
|
+
return null;
|
|
84
|
+
if (matches.length > 1) {
|
|
85
|
+
process.stderr.write(`Warning: branch '${branch}' is bound to multiple active tasks: ${matches.join(', ')}; using ${matches[0]}\n`);
|
|
86
|
+
}
|
|
87
|
+
return matches[0];
|
|
88
|
+
}
|
|
89
|
+
export { normalizeShortIdInput, lookupShortIdByBranch, loadShortIdByTaskId };
|
|
90
|
+
//# sourceMappingURL=short-id.js.map
|
package/dist/lib/update.js
CHANGED
|
@@ -7,6 +7,24 @@ import { isPathOwnedByDisabledTUI, resolveEnabledTUIs } from "./builtin-tuis.js"
|
|
|
7
7
|
const defaults = JSON.parse(fs.readFileSync(new URL('./defaults.json', import.meta.url), 'utf8'));
|
|
8
8
|
const CONFIG_DIR = '.agents';
|
|
9
9
|
const CONFIG_PATH = path.join(CONFIG_DIR, '.airc.json');
|
|
10
|
+
// One-time migration of the legacy project-level PR switch to the three-state
|
|
11
|
+
// `prFlow` preference. `true` (the old default / "PR flow on") maps to the
|
|
12
|
+
// strong constraint `required`; `false` maps to `disabled`. A missing or
|
|
13
|
+
// already-migrated config is left untouched (idempotent). Returns the new
|
|
14
|
+
// prFlow value when a migration happened, otherwise null.
|
|
15
|
+
function migratePrFlow(config) {
|
|
16
|
+
if (config.requiresPullRequest === true) {
|
|
17
|
+
delete config.requiresPullRequest;
|
|
18
|
+
config.prFlow = 'required';
|
|
19
|
+
return 'required';
|
|
20
|
+
}
|
|
21
|
+
if (config.requiresPullRequest === false) {
|
|
22
|
+
delete config.requiresPullRequest;
|
|
23
|
+
config.prFlow = 'disabled';
|
|
24
|
+
return 'disabled';
|
|
25
|
+
}
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
10
28
|
function isPathOwnedByOtherPlatform(relativePath, platformType) {
|
|
11
29
|
const top = String(relativePath || '').replace(/\\/g, '/').replace(/^\.\//, '').split('/')[0] ?? '';
|
|
12
30
|
if (!top.startsWith('.'))
|
|
@@ -134,7 +152,7 @@ async function cmdUpdate() {
|
|
|
134
152
|
const sandboxAdded = !config.sandbox;
|
|
135
153
|
const taskAdded = !config.task;
|
|
136
154
|
const labelsAdded = !config.labels;
|
|
137
|
-
const
|
|
155
|
+
const prFlowMigrated = migratePrFlow(config);
|
|
138
156
|
let configChanged = changed;
|
|
139
157
|
if (platformAdded) {
|
|
140
158
|
config.platform = structuredClone(defaults.platform);
|
|
@@ -152,8 +170,7 @@ async function cmdUpdate() {
|
|
|
152
170
|
config.labels = structuredClone(defaults.labels);
|
|
153
171
|
configChanged = true;
|
|
154
172
|
}
|
|
155
|
-
if (
|
|
156
|
-
config.requiresPullRequest = defaults.requiresPullRequest;
|
|
173
|
+
if (prFlowMigrated) {
|
|
157
174
|
configChanged = true;
|
|
158
175
|
}
|
|
159
176
|
if (configChanged) {
|
|
@@ -167,7 +184,7 @@ async function cmdUpdate() {
|
|
|
167
184
|
ok(` merged: ${entry}`);
|
|
168
185
|
}
|
|
169
186
|
}
|
|
170
|
-
else if (platformAdded || sandboxAdded || taskAdded || labelsAdded ||
|
|
187
|
+
else if (platformAdded || sandboxAdded || taskAdded || labelsAdded || prFlowMigrated) {
|
|
171
188
|
if (platformAdded) {
|
|
172
189
|
info(`Default platform config added to ${CONFIG_PATH}.`);
|
|
173
190
|
}
|
|
@@ -180,8 +197,8 @@ async function cmdUpdate() {
|
|
|
180
197
|
if (labelsAdded) {
|
|
181
198
|
info(`Default labels.in config added to ${CONFIG_PATH}.`);
|
|
182
199
|
}
|
|
183
|
-
if (
|
|
184
|
-
info(`
|
|
200
|
+
if (prFlowMigrated) {
|
|
201
|
+
info(`Migrated legacy requiresPullRequest to prFlow="${prFlowMigrated}" in ${CONFIG_PATH}.`);
|
|
185
202
|
}
|
|
186
203
|
}
|
|
187
204
|
else {
|
|
@@ -199,8 +216,8 @@ async function cmdUpdate() {
|
|
|
199
216
|
if (hasNewEntries && platformAdded) {
|
|
200
217
|
info(`Default platform config added to ${CONFIG_PATH}.`);
|
|
201
218
|
}
|
|
202
|
-
if (hasNewEntries &&
|
|
203
|
-
info(`
|
|
219
|
+
if (hasNewEntries && prFlowMigrated) {
|
|
220
|
+
info(`Migrated legacy requiresPullRequest to prFlow="${prFlowMigrated}" in ${CONFIG_PATH}.`);
|
|
204
221
|
}
|
|
205
222
|
fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + '\n', 'utf8');
|
|
206
223
|
ok(`Updated ${CONFIG_PATH}`);
|
package/lib/defaults.json
CHANGED
package/lib/init.ts
CHANGED
|
@@ -32,7 +32,6 @@ type Defaults = {
|
|
|
32
32
|
sandbox: Record<string, unknown>;
|
|
33
33
|
task: { shortIdLength: number };
|
|
34
34
|
labels: Record<string, unknown>;
|
|
35
|
-
requiresPullRequest: boolean;
|
|
36
35
|
};
|
|
37
36
|
|
|
38
37
|
type AgentConfig = {
|
|
@@ -40,7 +39,6 @@ type AgentConfig = {
|
|
|
40
39
|
org: string;
|
|
41
40
|
language: string;
|
|
42
41
|
platform: { type: string };
|
|
43
|
-
requiresPullRequest: boolean;
|
|
44
42
|
templateVersion: string;
|
|
45
43
|
sandbox: Record<string, unknown>;
|
|
46
44
|
task: { shortIdLength: number };
|
|
@@ -224,13 +222,6 @@ async function cmdInit(): Promise<void> {
|
|
|
224
222
|
);
|
|
225
223
|
}
|
|
226
224
|
|
|
227
|
-
const requiresPRChoice = await select(
|
|
228
|
-
'Require Pull Request flow?',
|
|
229
|
-
['yes', 'no'],
|
|
230
|
-
'yes'
|
|
231
|
-
);
|
|
232
|
-
const requiresPullRequest = requiresPRChoice !== 'no';
|
|
233
|
-
|
|
234
225
|
let enabledTUIs: string[];
|
|
235
226
|
try {
|
|
236
227
|
enabledTUIs = await multiSelect(
|
|
@@ -324,7 +315,6 @@ async function cmdInit(): Promise<void> {
|
|
|
324
315
|
org: orgName,
|
|
325
316
|
language,
|
|
326
317
|
platform: { type: platformType },
|
|
327
|
-
requiresPullRequest,
|
|
328
318
|
templateVersion: VERSION,
|
|
329
319
|
sandbox: structuredClone(defaults.sandbox),
|
|
330
320
|
task: structuredClone(defaults.task),
|
|
@@ -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,10 @@
|
|
|
1
1
|
import { loadConfig } from '../config.ts';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
assertValidBranchName,
|
|
4
|
+
containerNameCandidates,
|
|
5
|
+
sandboxBranchLabel,
|
|
6
|
+
sandboxLabel
|
|
7
|
+
} from '../constants.ts';
|
|
3
8
|
import { detectEngine } from '../engine.ts';
|
|
4
9
|
import {
|
|
5
10
|
formatCredentialWarnings,
|
|
@@ -8,17 +13,23 @@ import {
|
|
|
8
13
|
redactCommandError,
|
|
9
14
|
validateClaudeCredentialsEnvOverride
|
|
10
15
|
} from '../credentials.ts';
|
|
11
|
-
import { runInteractiveEngine
|
|
12
|
-
import { resolveTaskBranch } from '../task-resolver.ts';
|
|
16
|
+
import { runInteractiveEngine } from '../shell.ts';
|
|
13
17
|
import { dotfilesCacheDir, materializeDotfiles } from '../dotfiles.ts';
|
|
14
18
|
import { runInteractiveWithClipboardBridge } from '../clipboard/bridge.ts';
|
|
15
19
|
import { detectHostTimezone } from '../host-timezone.ts';
|
|
16
|
-
import {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
import {
|
|
21
|
+
fetchSandboxRows,
|
|
22
|
+
resolveBranchArg,
|
|
23
|
+
selectSandboxContainer,
|
|
24
|
+
startSandboxContainer
|
|
25
|
+
} from './list-running.ts';
|
|
26
|
+
|
|
27
|
+
const USAGE = `Usage: ai sandbox exec <branch | TASK-id | N | '#N'> [cmd...]
|
|
28
|
+
|
|
29
|
+
N (bare) and '#N' both reference the same active task short id from
|
|
30
|
+
.agents/workspace/active/.short-ids.json. They resolve only via that
|
|
31
|
+
registry — they do not reference a container's row position in
|
|
32
|
+
'ai sandbox ls' output.`;
|
|
22
33
|
const TMUX_ENTRY_PATH = '/usr/local/bin/sandbox-tmux-entry';
|
|
23
34
|
|
|
24
35
|
// Terminal-detection variables that interactive TUIs (e.g. claude-code)
|
|
@@ -120,20 +131,29 @@ export async function enter(args: string[]): Promise<number> {
|
|
|
120
131
|
validateClaudeCredentialsEnvOverride();
|
|
121
132
|
const engine = detectEngine(config);
|
|
122
133
|
const [firstArg = '', ...cmd] = args;
|
|
123
|
-
|
|
124
|
-
if (isTaskShortRef(firstArg)) {
|
|
125
|
-
const { running } = fetchSandboxRows(engine, sandboxLabel(config), sandboxBranchLabel(config));
|
|
126
|
-
branch = resolveTaskShortRef(firstArg, { running, repoRoot: config.repoRoot });
|
|
127
|
-
} else {
|
|
128
|
-
branch = resolveTaskBranch(firstArg, config.repoRoot);
|
|
129
|
-
}
|
|
134
|
+
const branch = resolveBranchArg(firstArg, { repoRoot: config.repoRoot });
|
|
130
135
|
assertValidBranchName(branch);
|
|
131
|
-
const running = runSafeEngine(engine, 'docker', ['ps', '--format', '{{.Names}}']).split('\n');
|
|
132
|
-
const container = containerNameCandidates(config, branch).find((name) => running.includes(name));
|
|
133
136
|
|
|
134
|
-
|
|
135
|
-
|
|
137
|
+
const { running, nonRunning } = fetchSandboxRows(
|
|
138
|
+
engine,
|
|
139
|
+
sandboxLabel(config),
|
|
140
|
+
sandboxBranchLabel(config)
|
|
141
|
+
);
|
|
142
|
+
const found = selectSandboxContainer(
|
|
143
|
+
[...running, ...nonRunning],
|
|
144
|
+
containerNameCandidates(config, branch)
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
if (!found) {
|
|
148
|
+
throw new Error(
|
|
149
|
+
`No sandbox found for branch '${branch}'. Run 'ai sandbox create ${branch}' to create one.`
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
if (!found.running) {
|
|
153
|
+
process.stderr.write(`Sandbox '${found.name}' is stopped; starting it...\n`);
|
|
154
|
+
startSandboxContainer(engine, found.name);
|
|
136
155
|
}
|
|
156
|
+
const container = found.name;
|
|
137
157
|
|
|
138
158
|
if (config.tools.includes('claude-code')) {
|
|
139
159
|
try {
|