@fitlab-ai/agent-infra 0.7.6 → 0.8.0
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 +2 -0
- package/README.zh-CN.md +2 -0
- package/bin/cli.ts +53 -18
- package/dist/bin/cli.js +56 -19
- package/dist/lib/decide.js +110 -0
- package/dist/lib/run/host.js +39 -0
- package/dist/lib/run/index.js +102 -0
- package/dist/lib/run/prompt.js +2 -0
- package/dist/lib/run/skills.js +29 -0
- package/dist/lib/run/tui.js +35 -0
- package/dist/lib/sandbox/capture.js +49 -0
- package/dist/lib/sandbox/commands/create.js +55 -6
- package/dist/lib/sandbox/commands/ls.js +4 -33
- package/dist/lib/sandbox/commands/show.js +67 -0
- package/dist/lib/sandbox/index.js +7 -0
- package/dist/lib/server/adapters/_contract.js +9 -0
- package/dist/lib/server/adapters/feishu/index.js +40 -0
- package/dist/lib/server/adapters/feishu/transport.js +95 -0
- package/dist/lib/server/auth.js +19 -0
- package/dist/lib/server/config.js +183 -0
- package/dist/lib/server/daemon.js +96 -0
- package/dist/lib/server/index.js +47 -0
- package/dist/lib/server/logger.js +34 -0
- package/dist/lib/server/plugin-loader.js +73 -0
- package/dist/lib/server/process-control.js +200 -0
- package/dist/lib/server/protocol.js +69 -0
- package/dist/lib/server/redact.js +12 -0
- package/dist/lib/server/runner.js +75 -0
- package/dist/lib/server/streamer.js +44 -0
- package/dist/lib/task/commands/decisions.js +258 -0
- package/dist/lib/task/commands/issue-body.js +94 -0
- package/dist/lib/task/commands/log.js +67 -9
- package/dist/lib/task/index.js +17 -0
- package/dist/lib/task/issue-form.js +66 -0
- package/dist/lib/task/ledger.js +66 -0
- package/dist/lib/task/sections.js +73 -0
- package/lib/decide.ts +124 -0
- package/lib/run/host.ts +47 -0
- package/lib/run/index.ts +124 -0
- package/lib/run/prompt.ts +1 -0
- package/lib/run/skills.ts +34 -0
- package/lib/run/tui.ts +40 -0
- package/lib/sandbox/capture.ts +84 -0
- package/lib/sandbox/commands/create.ts +61 -9
- package/lib/sandbox/commands/ls.ts +4 -36
- package/lib/sandbox/commands/show.ts +80 -0
- package/lib/sandbox/index.ts +7 -0
- package/lib/server/adapters/_contract.ts +42 -0
- package/lib/server/adapters/feishu/index.ts +47 -0
- package/lib/server/adapters/feishu/transport.ts +128 -0
- package/lib/server/auth.ts +28 -0
- package/lib/server/config.ts +228 -0
- package/lib/server/daemon.ts +110 -0
- package/lib/server/index.ts +50 -0
- package/lib/server/logger.ts +48 -0
- package/lib/server/plugin-loader.ts +84 -0
- package/lib/server/process-control.ts +225 -0
- package/lib/server/protocol.ts +79 -0
- package/lib/server/redact.ts +12 -0
- package/lib/server/runner.ts +95 -0
- package/lib/server/server.schema.json +127 -0
- package/lib/server/streamer.ts +57 -0
- package/lib/task/commands/decisions.ts +272 -0
- package/lib/task/commands/issue-body.ts +102 -0
- package/lib/task/commands/log.ts +73 -9
- package/lib/task/index.ts +17 -0
- package/lib/task/issue-form.ts +77 -0
- package/lib/task/ledger.ts +73 -0
- package/lib/task/sections.ts +73 -0
- package/package.json +9 -4
- package/templates/.agents/QUICKSTART.zh-CN.md +2 -2
- package/templates/.agents/README.en.md +3 -1
- package/templates/.agents/README.zh-CN.md +3 -1
- package/templates/.agents/rules/create-issue.github.en.md +20 -29
- package/templates/.agents/rules/create-issue.github.zh-CN.md +20 -29
- package/templates/.agents/rules/issue-fields.github.en.md +1 -1
- package/templates/.agents/rules/issue-fields.github.zh-CN.md +1 -1
- package/templates/.agents/rules/issue-pr-commands.github.en.md +6 -0
- package/templates/.agents/rules/issue-pr-commands.github.zh-CN.md +6 -0
- package/templates/.agents/rules/next-step-output.en.md +30 -1
- package/templates/.agents/rules/next-step-output.zh-CN.md +30 -1
- package/templates/.agents/rules/no-mid-flow-questions.en.md +7 -2
- package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +7 -2
- package/templates/.agents/rules/review-handshake.en.md +5 -1
- package/templates/.agents/rules/review-handshake.zh-CN.md +5 -1
- package/templates/.agents/rules/task-management.en.md +3 -1
- package/templates/.agents/rules/task-management.zh-CN.md +3 -1
- package/templates/.agents/scripts/lib/post-review-commit.js +15 -18
- package/templates/.agents/scripts/validate-artifact.js +8 -0
- package/templates/.agents/skills/analyze-task/SKILL.en.md +18 -4
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +18 -4
- package/templates/.agents/skills/analyze-task/config/verify.en.json +2 -1
- package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +2 -1
- package/templates/.agents/skills/archive-tasks/SKILL.en.md +3 -1
- package/templates/.agents/skills/archive-tasks/SKILL.zh-CN.md +3 -1
- package/templates/.agents/skills/block-task/SKILL.en.md +3 -1
- package/templates/.agents/skills/block-task/SKILL.zh-CN.md +3 -1
- package/templates/.agents/skills/cancel-task/SKILL.en.md +3 -1
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +3 -1
- package/templates/.agents/skills/check-task/SKILL.en.md +30 -81
- package/templates/.agents/skills/check-task/SKILL.zh-CN.md +30 -80
- package/templates/.agents/skills/close-codescan/SKILL.en.md +3 -1
- package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +3 -1
- package/templates/.agents/skills/close-dependabot/SKILL.en.md +3 -1
- package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +3 -1
- package/templates/.agents/skills/code-task/SKILL.en.md +4 -2
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +4 -2
- package/templates/.agents/skills/code-task/reference/dual-mode.en.md +7 -2
- package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +7 -2
- package/templates/.agents/skills/code-task/scripts/detect-mode.js +6 -14
- package/templates/.agents/skills/commit/SKILL.en.md +27 -5
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +27 -5
- package/templates/.agents/skills/complete-task/SKILL.en.md +4 -1
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +4 -1
- package/templates/.agents/skills/complete-task/config/verify.en.json +2 -1
- package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +2 -1
- package/templates/.agents/skills/create-pr/SKILL.en.md +3 -1
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +3 -1
- package/templates/.agents/skills/create-release-note/SKILL.en.md +3 -1
- package/templates/.agents/skills/create-release-note/SKILL.zh-CN.md +3 -1
- package/templates/.agents/skills/create-task/SKILL.en.md +4 -4
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/import-codescan/SKILL.en.md +3 -4
- package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +3 -4
- package/templates/.agents/skills/import-dependabot/SKILL.en.md +3 -2
- package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +3 -2
- package/templates/.agents/skills/import-issue/SKILL.en.md +20 -4
- package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +20 -4
- package/templates/.agents/skills/init-labels/SKILL.en.md +3 -1
- package/templates/.agents/skills/init-labels/SKILL.zh-CN.md +3 -1
- package/templates/.agents/skills/init-milestones/SKILL.en.md +3 -1
- package/templates/.agents/skills/init-milestones/SKILL.zh-CN.md +3 -1
- package/templates/.agents/skills/plan-task/SKILL.en.md +4 -3
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +4 -3
- package/templates/.agents/skills/post-release/SKILL.en.md +3 -1
- package/templates/.agents/skills/post-release/SKILL.zh-CN.md +3 -1
- package/templates/.agents/skills/refine-title/SKILL.en.md +3 -1
- package/templates/.agents/skills/refine-title/SKILL.zh-CN.md +3 -1
- package/templates/.agents/skills/release/SKILL.en.md +3 -1
- package/templates/.agents/skills/release/SKILL.zh-CN.md +3 -1
- package/templates/.agents/skills/restore-task/SKILL.en.md +3 -1
- package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +3 -1
- package/templates/.agents/skills/review-analysis/SKILL.en.md +6 -2
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +6 -2
- package/templates/.agents/skills/review-analysis/reference/output-templates.en.md +1 -1
- package/templates/.agents/skills/review-analysis/reference/output-templates.zh-CN.md +1 -1
- package/templates/.agents/skills/review-analysis/reference/report-template.en.md +1 -1
- package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +1 -2
- 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/reference/output-templates.en.md +1 -1
- package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +1 -1
- package/templates/.agents/skills/review-code/reference/report-template.en.md +1 -1
- package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +1 -2
- package/templates/.agents/skills/review-plan/SKILL.en.md +6 -2
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +6 -2
- package/templates/.agents/skills/review-plan/reference/output-templates.en.md +1 -1
- package/templates/.agents/skills/review-plan/reference/output-templates.zh-CN.md +1 -1
- package/templates/.agents/skills/review-plan/reference/report-template.en.md +1 -1
- package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +1 -2
- package/templates/.agents/skills/test/SKILL.en.md +3 -1
- package/templates/.agents/skills/test/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/test-integration/SKILL.en.md +3 -1
- package/templates/.agents/skills/test-integration/SKILL.zh-CN.md +3 -1
- package/templates/.agents/skills/update-agent-infra/SKILL.en.md +3 -1
- package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +3 -1
- package/templates/.agents/skills/upgrade-dependency/SKILL.en.md +3 -1
- package/templates/.agents/skills/upgrade-dependency/SKILL.zh-CN.md +3 -1
- package/templates/.agents/skills/watch-pr/SKILL.en.md +3 -1
- package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +3 -1
- package/templates/.agents/templates/task.en.md +3 -3
- package/templates/.agents/templates/task.zh-CN.md +3 -3
- package/templates/.github/workflows/metadata-sync.yml +0 -18
package/README.md
CHANGED
|
@@ -82,6 +82,7 @@ Every command above works the same way in Claude Code, Codex, Gemini CLI, and Op
|
|
|
82
82
|
|
|
83
83
|
- **Multi-AI collaboration**: one shared operating model for Claude Code, Codex, Gemini CLI, and OpenCode
|
|
84
84
|
- **Bootstrap CLI + skill-driven execution**: initialize once, then let AI skills drive day-to-day work
|
|
85
|
+
- **IM command bridge**: route Feishu messages to built-ins, read-only task views, sandbox management, and controlled lifecycle skill runs
|
|
85
86
|
- **Bilingual project docs**: English-first docs with synchronized Chinese translations
|
|
86
87
|
- **Template-source architecture**: `templates/` mirrors the rendered project structure
|
|
87
88
|
- **AI-assisted updates**: template changes can be merged while preserving project-specific customization
|
|
@@ -191,6 +192,7 @@ In-depth guides live under [`docs/en/`](./docs/en/README.md):
|
|
|
191
192
|
- [Architecture Overview](./docs/en/architecture.md) — bootstrap CLI, end-to-end flow, layered architecture
|
|
192
193
|
- [Platform Support](./docs/en/platform-support.md) — macOS, Linux, Windows; sandbox engines and resources
|
|
193
194
|
- [Sandbox](./docs/en/sandbox.md) — sandbox aliases, host-sandbox file exchange, user-level dotfiles channel
|
|
195
|
+
- [Feishu Bridge](./docs/en/feishu-bridge.md) — configure the Feishu long-connection adapter, command routing, permissions, and TUI selection
|
|
194
196
|
- [Built-in AI Skills](./docs/en/skills.md) — the full skill catalog by use case
|
|
195
197
|
- [Custom Skills](./docs/en/custom-skills.md) — create and sync project-specific skills
|
|
196
198
|
- [Custom TUI Configuration](./docs/en/custom-tui.md) — adapt agent-infra to non-built-in AI TUIs
|
package/README.zh-CN.md
CHANGED
|
@@ -82,6 +82,7 @@ agent-infra 的目标就是把这层共享基础设施标准化。它为所有
|
|
|
82
82
|
|
|
83
83
|
- **多 AI 协作**:为 Claude Code、Codex、Gemini CLI、OpenCode 提供统一的协作模型
|
|
84
84
|
- **引导 CLI + skill 驱动执行**:初始化一次,后续日常操作交给 AI skills
|
|
85
|
+
- **IM 命令桥接**:把飞书消息路由到内置命令、任务只读视图、沙箱管理和受控生命周期 skill 执行
|
|
85
86
|
- **双语文档**:英文为主文档,配套同步的中文版本
|
|
86
87
|
- **模板源架构**:`templates/` 目录镜像最终渲染出的项目结构
|
|
87
88
|
- **AI 辅助升级**:模板升级时可合并变更,同时尽量保留项目侧定制
|
|
@@ -191,6 +192,7 @@ my-project/
|
|
|
191
192
|
- [架构概览](./docs/zh-CN/architecture.md) — 引导 CLI、端到端流程、分层架构
|
|
192
193
|
- [平台支持](./docs/zh-CN/platform-support.md) — macOS、Linux、Windows;沙箱引擎与资源配置
|
|
193
194
|
- [沙箱](./docs/zh-CN/sandbox.md) — 沙箱 aliases、宿主-沙箱文件交换、用户级 dotfiles 通道
|
|
195
|
+
- [飞书桥接](./docs/zh-CN/feishu-bridge.md) — 配置飞书长连接 adapter、命令路由、权限和 TUI 选择
|
|
194
196
|
- [内置 AI Skills](./docs/zh-CN/skills.md) — 按使用场景分组的完整 skill 清单
|
|
195
197
|
- [自定义 Skills](./docs/zh-CN/custom-skills.md) — 创建并同步项目专属 skill
|
|
196
198
|
- [自定义 TUI 配置](./docs/zh-CN/custom-tui.md) — 适配非内置的 AI TUI
|
package/bin/cli.ts
CHANGED
|
@@ -16,11 +16,14 @@ Usage: ai <command> [options]
|
|
|
16
16
|
|
|
17
17
|
Commands:
|
|
18
18
|
cp <ssh-alias> Copy local clipboard image to a remote macOS NSPasteboard
|
|
19
|
+
decide Record a human decision for a task HD item
|
|
19
20
|
help Show this help message
|
|
20
21
|
init Initialize a new project with update-agent-infra seed command
|
|
21
22
|
merge Merge tasks from another workspace directory (active/blocked/completed/archive)
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
run Run a whitelisted lifecycle skill through a non-interactive TUI
|
|
24
|
+
sandbox, s Manage Docker-based AI sandboxes
|
|
25
|
+
server Run the local AI collaboration daemon (start/stop/status/logs)
|
|
26
|
+
task, t Read-only views over .agents/workspace tasks (cat / files / grep / log / ls / show / status)
|
|
24
27
|
update Update seed files and sync file registry for an existing project
|
|
25
28
|
version Show version
|
|
26
29
|
|
|
@@ -37,7 +40,15 @@ Examples:
|
|
|
37
40
|
npx @fitlab-ai/agent-infra init
|
|
38
41
|
`;
|
|
39
42
|
|
|
40
|
-
const
|
|
43
|
+
const COMMAND_ALIASES: Record<string, string> = {
|
|
44
|
+
s: 'sandbox',
|
|
45
|
+
t: 'task'
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const rawCommand = process.argv[2] || '';
|
|
49
|
+
const command = Object.hasOwn(COMMAND_ALIASES, rawCommand)
|
|
50
|
+
? COMMAND_ALIASES[rawCommand]
|
|
51
|
+
: rawCommand;
|
|
41
52
|
|
|
42
53
|
function errorMessage(error: unknown): string {
|
|
43
54
|
return error instanceof Error ? error.message : String(error);
|
|
@@ -60,21 +71,29 @@ async function importCommand(importPath: string) {
|
|
|
60
71
|
}
|
|
61
72
|
|
|
62
73
|
switch (command) {
|
|
63
|
-
case '
|
|
64
|
-
const imported = await importCommand('../lib/
|
|
74
|
+
case 'cp': {
|
|
75
|
+
const imported = await importCommand('../lib/cp.ts');
|
|
65
76
|
if (!imported) break;
|
|
66
|
-
const {
|
|
67
|
-
await
|
|
77
|
+
const { cmdCp } = imported;
|
|
78
|
+
const code = await cmdCp(process.argv.slice(3)).catch((e: unknown) => {
|
|
68
79
|
process.stderr.write(`Error: ${errorMessage(e)}\n`);
|
|
69
|
-
|
|
80
|
+
return 1;
|
|
70
81
|
});
|
|
82
|
+
if (code) process.exitCode = code;
|
|
71
83
|
break;
|
|
72
84
|
}
|
|
73
|
-
case '
|
|
74
|
-
const imported = await importCommand('../lib/
|
|
85
|
+
case 'decide': {
|
|
86
|
+
const imported = await importCommand('../lib/decide.ts');
|
|
75
87
|
if (!imported) break;
|
|
76
|
-
const {
|
|
77
|
-
await
|
|
88
|
+
const { cmdDecide } = imported;
|
|
89
|
+
await cmdDecide(process.argv.slice(3));
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
case 'init': {
|
|
93
|
+
const imported = await importCommand('../lib/init.ts');
|
|
94
|
+
if (!imported) break;
|
|
95
|
+
const { cmdInit } = imported;
|
|
96
|
+
await cmdInit().catch((e: unknown) => {
|
|
78
97
|
process.stderr.write(`Error: ${errorMessage(e)}\n`);
|
|
79
98
|
process.exitCode = 1;
|
|
80
99
|
});
|
|
@@ -90,6 +109,13 @@ switch (command) {
|
|
|
90
109
|
});
|
|
91
110
|
break;
|
|
92
111
|
}
|
|
112
|
+
case 'run': {
|
|
113
|
+
const imported = await importCommand('../lib/run/index.ts');
|
|
114
|
+
if (!imported) break;
|
|
115
|
+
const { cmdRun } = imported;
|
|
116
|
+
await cmdRun(process.argv.slice(3));
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
93
119
|
case 'sandbox': {
|
|
94
120
|
const imported = await importCommand('../lib/sandbox/index.ts');
|
|
95
121
|
if (!imported) break;
|
|
@@ -100,6 +126,16 @@ switch (command) {
|
|
|
100
126
|
});
|
|
101
127
|
break;
|
|
102
128
|
}
|
|
129
|
+
case 'server': {
|
|
130
|
+
const imported = await importCommand('../lib/server/index.ts');
|
|
131
|
+
if (!imported) break;
|
|
132
|
+
const { runServer } = imported;
|
|
133
|
+
await runServer(process.argv.slice(3)).catch((e: unknown) => {
|
|
134
|
+
process.stderr.write(`Error: ${errorMessage(e)}\n`);
|
|
135
|
+
process.exit(1);
|
|
136
|
+
});
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
103
139
|
case 'task': {
|
|
104
140
|
const imported = await importCommand('../lib/task/index.ts');
|
|
105
141
|
if (!imported) break;
|
|
@@ -110,15 +146,14 @@ switch (command) {
|
|
|
110
146
|
});
|
|
111
147
|
break;
|
|
112
148
|
}
|
|
113
|
-
case '
|
|
114
|
-
const imported = await importCommand('../lib/
|
|
149
|
+
case 'update': {
|
|
150
|
+
const imported = await importCommand('../lib/update.ts');
|
|
115
151
|
if (!imported) break;
|
|
116
|
-
const {
|
|
117
|
-
|
|
152
|
+
const { cmdUpdate } = imported;
|
|
153
|
+
await cmdUpdate().catch((e: unknown) => {
|
|
118
154
|
process.stderr.write(`Error: ${errorMessage(e)}\n`);
|
|
119
|
-
|
|
155
|
+
process.exitCode = 1;
|
|
120
156
|
});
|
|
121
|
-
if (code) process.exitCode = code;
|
|
122
157
|
break;
|
|
123
158
|
}
|
|
124
159
|
case 'version': {
|
package/dist/bin/cli.js
CHANGED
|
@@ -20,11 +20,14 @@ Usage: ai <command> [options]
|
|
|
20
20
|
|
|
21
21
|
Commands:
|
|
22
22
|
cp <ssh-alias> Copy local clipboard image to a remote macOS NSPasteboard
|
|
23
|
+
decide Record a human decision for a task HD item
|
|
23
24
|
help Show this help message
|
|
24
25
|
init Initialize a new project with update-agent-infra seed command
|
|
25
26
|
merge Merge tasks from another workspace directory (active/blocked/completed/archive)
|
|
26
|
-
|
|
27
|
-
|
|
27
|
+
run Run a whitelisted lifecycle skill through a non-interactive TUI
|
|
28
|
+
sandbox, s Manage Docker-based AI sandboxes
|
|
29
|
+
server Run the local AI collaboration daemon (start/stop/status/logs)
|
|
30
|
+
task, t Read-only views over .agents/workspace tasks (cat / files / grep / log / ls / show / status)
|
|
28
31
|
update Update seed files and sync file registry for an existing project
|
|
29
32
|
version Show version
|
|
30
33
|
|
|
@@ -40,7 +43,14 @@ Examples:
|
|
|
40
43
|
cd my-project && ai init
|
|
41
44
|
npx @fitlab-ai/agent-infra init
|
|
42
45
|
`;
|
|
43
|
-
const
|
|
46
|
+
const COMMAND_ALIASES = {
|
|
47
|
+
s: 'sandbox',
|
|
48
|
+
t: 'task'
|
|
49
|
+
};
|
|
50
|
+
const rawCommand = process.argv[2] || '';
|
|
51
|
+
const command = Object.hasOwn(COMMAND_ALIASES, rawCommand)
|
|
52
|
+
? COMMAND_ALIASES[rawCommand]
|
|
53
|
+
: rawCommand;
|
|
44
54
|
function errorMessage(error) {
|
|
45
55
|
return error instanceof Error ? error.message : String(error);
|
|
46
56
|
}
|
|
@@ -59,23 +69,33 @@ async function importCommand(importPath) {
|
|
|
59
69
|
}
|
|
60
70
|
}
|
|
61
71
|
switch (command) {
|
|
62
|
-
case '
|
|
63
|
-
const imported = await importCommand('../lib/
|
|
72
|
+
case 'cp': {
|
|
73
|
+
const imported = await importCommand('../lib/cp.ts');
|
|
64
74
|
if (!imported)
|
|
65
75
|
break;
|
|
66
|
-
const {
|
|
67
|
-
await
|
|
76
|
+
const { cmdCp } = imported;
|
|
77
|
+
const code = await cmdCp(process.argv.slice(3)).catch((e) => {
|
|
68
78
|
process.stderr.write(`Error: ${errorMessage(e)}\n`);
|
|
69
|
-
|
|
79
|
+
return 1;
|
|
70
80
|
});
|
|
81
|
+
if (code)
|
|
82
|
+
process.exitCode = code;
|
|
71
83
|
break;
|
|
72
84
|
}
|
|
73
|
-
case '
|
|
74
|
-
const imported = await importCommand('../lib/
|
|
85
|
+
case 'decide': {
|
|
86
|
+
const imported = await importCommand('../lib/decide.ts');
|
|
75
87
|
if (!imported)
|
|
76
88
|
break;
|
|
77
|
-
const {
|
|
78
|
-
await
|
|
89
|
+
const { cmdDecide } = imported;
|
|
90
|
+
await cmdDecide(process.argv.slice(3));
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
case 'init': {
|
|
94
|
+
const imported = await importCommand('../lib/init.ts');
|
|
95
|
+
if (!imported)
|
|
96
|
+
break;
|
|
97
|
+
const { cmdInit } = imported;
|
|
98
|
+
await cmdInit().catch((e) => {
|
|
79
99
|
process.stderr.write(`Error: ${errorMessage(e)}\n`);
|
|
80
100
|
process.exitCode = 1;
|
|
81
101
|
});
|
|
@@ -92,6 +112,14 @@ switch (command) {
|
|
|
92
112
|
});
|
|
93
113
|
break;
|
|
94
114
|
}
|
|
115
|
+
case 'run': {
|
|
116
|
+
const imported = await importCommand('../lib/run/index.ts');
|
|
117
|
+
if (!imported)
|
|
118
|
+
break;
|
|
119
|
+
const { cmdRun } = imported;
|
|
120
|
+
await cmdRun(process.argv.slice(3));
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
95
123
|
case 'sandbox': {
|
|
96
124
|
const imported = await importCommand('../lib/sandbox/index.ts');
|
|
97
125
|
if (!imported)
|
|
@@ -103,6 +131,17 @@ switch (command) {
|
|
|
103
131
|
});
|
|
104
132
|
break;
|
|
105
133
|
}
|
|
134
|
+
case 'server': {
|
|
135
|
+
const imported = await importCommand('../lib/server/index.ts');
|
|
136
|
+
if (!imported)
|
|
137
|
+
break;
|
|
138
|
+
const { runServer } = imported;
|
|
139
|
+
await runServer(process.argv.slice(3)).catch((e) => {
|
|
140
|
+
process.stderr.write(`Error: ${errorMessage(e)}\n`);
|
|
141
|
+
process.exit(1);
|
|
142
|
+
});
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
106
145
|
case 'task': {
|
|
107
146
|
const imported = await importCommand('../lib/task/index.ts');
|
|
108
147
|
if (!imported)
|
|
@@ -114,17 +153,15 @@ switch (command) {
|
|
|
114
153
|
});
|
|
115
154
|
break;
|
|
116
155
|
}
|
|
117
|
-
case '
|
|
118
|
-
const imported = await importCommand('../lib/
|
|
156
|
+
case 'update': {
|
|
157
|
+
const imported = await importCommand('../lib/update.ts');
|
|
119
158
|
if (!imported)
|
|
120
159
|
break;
|
|
121
|
-
const {
|
|
122
|
-
|
|
160
|
+
const { cmdUpdate } = imported;
|
|
161
|
+
await cmdUpdate().catch((e) => {
|
|
123
162
|
process.stderr.write(`Error: ${errorMessage(e)}\n`);
|
|
124
|
-
|
|
163
|
+
process.exitCode = 1;
|
|
125
164
|
});
|
|
126
|
-
if (code)
|
|
127
|
-
process.exitCode = code;
|
|
128
165
|
break;
|
|
129
166
|
}
|
|
130
167
|
case 'version': {
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { execFileSync } from 'node:child_process';
|
|
4
|
+
import { VERSION } from "./version.js";
|
|
5
|
+
import { resolveTaskRef } from "./task/resolve-ref.js";
|
|
6
|
+
const TASK_ID_RE = /^TASK-\d{8}-\d{6}$/;
|
|
7
|
+
function detectRepoRoot() {
|
|
8
|
+
return execFileSync('git', ['rev-parse', '--show-toplevel'], {
|
|
9
|
+
encoding: 'utf8',
|
|
10
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
11
|
+
}).trim();
|
|
12
|
+
}
|
|
13
|
+
function defaultNow() {
|
|
14
|
+
return new Intl.DateTimeFormat('sv-SE', {
|
|
15
|
+
timeZoneName: 'longOffset',
|
|
16
|
+
year: 'numeric',
|
|
17
|
+
month: '2-digit',
|
|
18
|
+
day: '2-digit',
|
|
19
|
+
hour: '2-digit',
|
|
20
|
+
minute: '2-digit',
|
|
21
|
+
second: '2-digit',
|
|
22
|
+
hour12: false
|
|
23
|
+
})
|
|
24
|
+
.format(new Date())
|
|
25
|
+
.replace(' GMT', '');
|
|
26
|
+
}
|
|
27
|
+
function taskPath(repoRoot, ref) {
|
|
28
|
+
if (!TASK_ID_RE.test(ref)) {
|
|
29
|
+
const resolved = resolveTaskRef(ref);
|
|
30
|
+
if (!resolved.ok)
|
|
31
|
+
throw new Error(resolved.message);
|
|
32
|
+
if (!resolved.taskDir.includes(`${path.join('.agents', 'workspace', 'active')}${path.sep}`)) {
|
|
33
|
+
throw new Error(`task ${resolved.taskId} is not active`);
|
|
34
|
+
}
|
|
35
|
+
return resolved.taskMdPath;
|
|
36
|
+
}
|
|
37
|
+
const candidate = path.join(repoRoot, '.agents', 'workspace', 'active', ref, 'task.md');
|
|
38
|
+
if (!fs.existsSync(candidate))
|
|
39
|
+
throw new Error(`active task not found: ${ref}`);
|
|
40
|
+
return candidate;
|
|
41
|
+
}
|
|
42
|
+
function replaceFrontmatterField(content, field, value) {
|
|
43
|
+
const re = new RegExp(`^${field}:.*$`, 'm');
|
|
44
|
+
if (re.test(content))
|
|
45
|
+
return content.replace(re, `${field}: ${value}`);
|
|
46
|
+
return content.replace(/^---\n/, `---\n${field}: ${value}\n`);
|
|
47
|
+
}
|
|
48
|
+
function replaceLedgerRow(content, hdId) {
|
|
49
|
+
const lines = content.split('\n');
|
|
50
|
+
let found = false;
|
|
51
|
+
let pending = false;
|
|
52
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
53
|
+
const line = lines[i];
|
|
54
|
+
if (!line.trim().startsWith(`| ${hdId} |`))
|
|
55
|
+
continue;
|
|
56
|
+
found = true;
|
|
57
|
+
const cells = line.split('|').slice(1, -1).map((cell) => cell.trim());
|
|
58
|
+
if (cells[4] !== 'needs-human-decision')
|
|
59
|
+
break;
|
|
60
|
+
pending = true;
|
|
61
|
+
cells[4] = 'human-decided';
|
|
62
|
+
cells[5] = `task.md#${hdId}`;
|
|
63
|
+
lines[i] = `| ${cells.join(' | ')} |`;
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
return { content: lines.join('\n'), found, pending };
|
|
67
|
+
}
|
|
68
|
+
function appendUnderHeading(content, heading, block) {
|
|
69
|
+
if (!content.includes(`${heading}\n`)) {
|
|
70
|
+
return `${content.trimEnd()}\n\n${heading}\n\n${block}\n`;
|
|
71
|
+
}
|
|
72
|
+
const idx = content.indexOf(`${heading}\n`) + heading.length + 1;
|
|
73
|
+
const before = content.slice(0, idx);
|
|
74
|
+
const after = content.slice(idx);
|
|
75
|
+
return `${before}\n${block}\n${after.replace(/^\n/, '')}`;
|
|
76
|
+
}
|
|
77
|
+
export async function decide(args, options = {}) {
|
|
78
|
+
const [taskRef, hdId, ...decisionParts] = args;
|
|
79
|
+
if (!taskRef || !hdId || decisionParts.length === 0) {
|
|
80
|
+
process.stderr.write('Usage: ai decide <task-ref> <HD-id> <decision>\n');
|
|
81
|
+
return 1;
|
|
82
|
+
}
|
|
83
|
+
try {
|
|
84
|
+
const repoRoot = options.repoRoot ?? detectRepoRoot();
|
|
85
|
+
const file = taskPath(repoRoot, taskRef);
|
|
86
|
+
let content = fs.readFileSync(file, 'utf8');
|
|
87
|
+
const replaced = replaceLedgerRow(content, hdId);
|
|
88
|
+
if (!replaced.found)
|
|
89
|
+
throw new Error(`${hdId} not found in review ledger`);
|
|
90
|
+
if (!replaced.pending)
|
|
91
|
+
throw new Error(`${hdId} is not needs-human-decision`);
|
|
92
|
+
content = replaced.content;
|
|
93
|
+
const now = (options.now ?? defaultNow)();
|
|
94
|
+
content = replaceFrontmatterField(content, 'updated_at', now);
|
|
95
|
+
content = replaceFrontmatterField(content, 'agent_infra_version', options.version ?? VERSION);
|
|
96
|
+
const decision = decisionParts.join(' ');
|
|
97
|
+
content = appendUnderHeading(content, '## 人工裁决', `### ${hdId}\n\n- **裁决时间**:${now}\n- **裁决结果**:${decision}`);
|
|
98
|
+
content = appendUnderHeading(content, '## 活动日志', `- ${now} — **Human Decision** by human — ${hdId} decided`);
|
|
99
|
+
fs.writeFileSync(file, content);
|
|
100
|
+
return 0;
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
process.stderr.write(`Error: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
104
|
+
return 1;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
export async function cmdDecide(args) {
|
|
108
|
+
process.exitCode = await decide(args);
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=decide.js.map
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
function resolveCommand(file) {
|
|
5
|
+
if (process.platform !== 'win32' || path.extname(file)) {
|
|
6
|
+
return file;
|
|
7
|
+
}
|
|
8
|
+
const pathValue = process.env.Path || process.env.PATH || '';
|
|
9
|
+
const extensions = (process.env.PATHEXT || '.COM;.EXE;.BAT;.CMD').split(';').filter(Boolean);
|
|
10
|
+
for (const dir of pathValue.split(path.delimiter).filter(Boolean)) {
|
|
11
|
+
for (const extension of extensions) {
|
|
12
|
+
const lowerCandidate = path.join(dir, `${file}${extension.toLowerCase()}`);
|
|
13
|
+
if (fs.existsSync(lowerCandidate))
|
|
14
|
+
return lowerCandidate;
|
|
15
|
+
const upperCandidate = path.join(dir, `${file}${extension.toUpperCase()}`);
|
|
16
|
+
if (fs.existsSync(upperCandidate))
|
|
17
|
+
return upperCandidate;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return file;
|
|
21
|
+
}
|
|
22
|
+
function needsShell(file) {
|
|
23
|
+
return process.platform === 'win32' && /\.(?:bat|cmd)$/i.test(file);
|
|
24
|
+
}
|
|
25
|
+
export async function runHostCommand(command) {
|
|
26
|
+
const [file, ...args] = command;
|
|
27
|
+
if (!file)
|
|
28
|
+
throw new Error('run: missing command');
|
|
29
|
+
return new Promise((resolve, reject) => {
|
|
30
|
+
const resolvedFile = resolveCommand(file);
|
|
31
|
+
const child = spawn(resolvedFile, args, {
|
|
32
|
+
stdio: ['ignore', 'inherit', 'inherit'],
|
|
33
|
+
shell: needsShell(resolvedFile)
|
|
34
|
+
});
|
|
35
|
+
child.on('error', reject);
|
|
36
|
+
child.on('close', (exitCode, signal) => resolve({ exitCode, signal }));
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=host.js.map
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { loadServerConfig } from "../server/config.js";
|
|
2
|
+
import { resolveTaskBranch } from "../sandbox/task-resolver.js";
|
|
3
|
+
import { runInSandbox } from "../sandbox/capture.js";
|
|
4
|
+
import { buildTuiCommand, renderPrompt, selectTui } from "./tui.js";
|
|
5
|
+
import { getSkillRunSpec } from "./skills.js";
|
|
6
|
+
import { runHostCommand } from "./host.js";
|
|
7
|
+
const USAGE = `Usage: ai run <skill> [task-ref] [args...] [--tui <name>]
|
|
8
|
+
|
|
9
|
+
Examples:
|
|
10
|
+
ai run create-task "describe the task" --tui codex
|
|
11
|
+
ai run code-task #7 --tui codex`;
|
|
12
|
+
function extractTui(args) {
|
|
13
|
+
const rest = [];
|
|
14
|
+
let tui = null;
|
|
15
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
16
|
+
const arg = args[i];
|
|
17
|
+
if (arg === '--tui') {
|
|
18
|
+
const value = args[i + 1];
|
|
19
|
+
if (!value)
|
|
20
|
+
throw new Error('--tui requires a value');
|
|
21
|
+
tui = value;
|
|
22
|
+
i += 1;
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
rest.push(arg);
|
|
26
|
+
}
|
|
27
|
+
return { rest, tui };
|
|
28
|
+
}
|
|
29
|
+
export function parseRunArgs(args) {
|
|
30
|
+
if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
|
|
31
|
+
throw new Error(USAGE);
|
|
32
|
+
}
|
|
33
|
+
const { rest, tui } = extractTui(args);
|
|
34
|
+
const [skill = '', maybeTaskRef, ...remaining] = rest;
|
|
35
|
+
const spec = getSkillRunSpec(skill);
|
|
36
|
+
if (!spec)
|
|
37
|
+
throw new Error(`Unknown skill '${skill}'`);
|
|
38
|
+
if (spec.kind === 'create') {
|
|
39
|
+
const createArgs = rest.slice(1);
|
|
40
|
+
if (createArgs.length === 0)
|
|
41
|
+
throw new Error('create-task requires a description');
|
|
42
|
+
return { skill, taskRef: null, args: createArgs, tui };
|
|
43
|
+
}
|
|
44
|
+
if (!maybeTaskRef)
|
|
45
|
+
throw new Error(`${skill} requires a task-ref`);
|
|
46
|
+
return { skill, taskRef: maybeTaskRef, args: remaining, tui };
|
|
47
|
+
}
|
|
48
|
+
function assertAllowedByConfig(skill, commandConfig) {
|
|
49
|
+
const allowed = commandConfig.allowedSkills;
|
|
50
|
+
if (!Array.isArray(allowed))
|
|
51
|
+
return;
|
|
52
|
+
if (!allowed.every((entry) => typeof entry === 'string')) {
|
|
53
|
+
throw new Error('command.allowedSkills must be an array of skill names');
|
|
54
|
+
}
|
|
55
|
+
if (!allowed.includes(skill)) {
|
|
56
|
+
throw new Error(`Skill '${skill}' is not allowed by command.allowedSkills`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
export async function runSkill(args, options = {}) {
|
|
60
|
+
const parsed = parseRunArgs(args);
|
|
61
|
+
const config = options.command ? null : loadServerConfig({ rootDir: options.repoRoot });
|
|
62
|
+
const commandConfig = options.command ?? config?.command ?? {};
|
|
63
|
+
assertAllowedByConfig(parsed.skill, commandConfig);
|
|
64
|
+
const tui = selectTui(parsed.skill, { cliTui: parsed.tui, command: commandConfig });
|
|
65
|
+
const promptArgs = parsed.taskRef === null ? parsed.args : [parsed.taskRef, ...parsed.args];
|
|
66
|
+
const prompt = renderPrompt({ tui, skill: parsed.skill, args: promptArgs });
|
|
67
|
+
const [file, argv] = buildTuiCommand(tui, prompt);
|
|
68
|
+
const command = [file, ...argv];
|
|
69
|
+
if (parsed.taskRef === null) {
|
|
70
|
+
const result = await (options.runHost ?? runHostCommand)(command);
|
|
71
|
+
return result.exitCode ?? (result.signal ? 1 : 0);
|
|
72
|
+
}
|
|
73
|
+
const repoRoot = options.repoRoot ?? config?.repoRoot ?? process.cwd();
|
|
74
|
+
const branch = resolveTaskBranch(parsed.taskRef, repoRoot);
|
|
75
|
+
const runSandbox = options.runSandbox ?? ((request) => runInSandbox(request));
|
|
76
|
+
const result = await runSandbox({ taskRef: parsed.taskRef, branch, command });
|
|
77
|
+
if (result.stdout) {
|
|
78
|
+
(options.writeStdout ?? ((chunk) => process.stdout.write(chunk)))(result.stdout);
|
|
79
|
+
}
|
|
80
|
+
if (result.stderr) {
|
|
81
|
+
(options.writeStderr ?? ((chunk) => process.stderr.write(chunk)))(result.stderr);
|
|
82
|
+
}
|
|
83
|
+
return result.exitCode ?? (result.signal ? 1 : 0);
|
|
84
|
+
}
|
|
85
|
+
export async function cmdRun(args) {
|
|
86
|
+
try {
|
|
87
|
+
const code = await runSkill(args);
|
|
88
|
+
process.exitCode = code;
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
92
|
+
if (message.startsWith('Usage:')) {
|
|
93
|
+
process.stdout.write(`${message}\n`);
|
|
94
|
+
process.exitCode = args.length === 0 ? 1 : 0;
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
process.stderr.write(`Error: ${message}\n`);
|
|
98
|
+
process.exitCode = 1;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const TASK_SKILLS = new Set([
|
|
2
|
+
'analyze-task',
|
|
3
|
+
'block-task',
|
|
4
|
+
'cancel-task',
|
|
5
|
+
'code-task',
|
|
6
|
+
'commit',
|
|
7
|
+
'complete-task',
|
|
8
|
+
'create-pr',
|
|
9
|
+
'plan-task',
|
|
10
|
+
'review-analysis',
|
|
11
|
+
'review-code',
|
|
12
|
+
'review-plan',
|
|
13
|
+
'test',
|
|
14
|
+
'test-integration',
|
|
15
|
+
'watch-pr'
|
|
16
|
+
]);
|
|
17
|
+
export function getSkillRunSpec(skill) {
|
|
18
|
+
if (skill === 'create-task') {
|
|
19
|
+
return { kind: 'create', skill: 'create-task', role: 'exec', requiresSandbox: false };
|
|
20
|
+
}
|
|
21
|
+
if (TASK_SKILLS.has(skill)) {
|
|
22
|
+
return { kind: 'task', skill, role: 'exec', requiresSandbox: true };
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
export function allowedSkillNames() {
|
|
27
|
+
return ['create-task', ...TASK_SKILLS].sort();
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=skills.js.map
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const TUI_NAMES = new Set(['claude', 'codex', 'gemini', 'opencode']);
|
|
2
|
+
function isTuiName(value) {
|
|
3
|
+
return typeof value === 'string' && TUI_NAMES.has(value);
|
|
4
|
+
}
|
|
5
|
+
export function selectTui(skill, options) {
|
|
6
|
+
if (isTuiName(options.cliTui))
|
|
7
|
+
return options.cliTui;
|
|
8
|
+
const defaults = options.command?.skillTuiDefaults;
|
|
9
|
+
if (defaults && typeof defaults === 'object' && !Array.isArray(defaults)) {
|
|
10
|
+
const value = defaults[skill];
|
|
11
|
+
if (isTuiName(value))
|
|
12
|
+
return value;
|
|
13
|
+
}
|
|
14
|
+
if (isTuiName(options.command?.defaultTui))
|
|
15
|
+
return options.command.defaultTui;
|
|
16
|
+
return 'codex';
|
|
17
|
+
}
|
|
18
|
+
export function renderPrompt(params) {
|
|
19
|
+
const suffix = [params.skill, ...params.args].join(' ').trim();
|
|
20
|
+
if (params.tui === 'codex')
|
|
21
|
+
return `$${suffix}`;
|
|
22
|
+
if (params.tui === 'gemini')
|
|
23
|
+
return `/agent-infra:${suffix}`;
|
|
24
|
+
return `/${suffix}`;
|
|
25
|
+
}
|
|
26
|
+
export function buildTuiCommand(tui, prompt) {
|
|
27
|
+
if (tui === 'claude')
|
|
28
|
+
return ['claude', ['--dangerously-skip-permissions', '--print', prompt]];
|
|
29
|
+
if (tui === 'gemini')
|
|
30
|
+
return ['gemini', ['--approval-mode', 'yolo', '--prompt', prompt]];
|
|
31
|
+
if (tui === 'opencode')
|
|
32
|
+
return ['opencode', ['run', '--dangerously-skip-permissions', prompt]];
|
|
33
|
+
return ['codex', ['exec', '--dangerously-bypass-approvals-and-sandbox', prompt]];
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=tui.js.map
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { loadConfig } from "./config.js";
|
|
3
|
+
import { containerNameCandidates, sandboxBranchLabel, sandboxLabel } from "./constants.js";
|
|
4
|
+
import { detectEngine } from "./engine.js";
|
|
5
|
+
import { hostTimezoneEnvFlags, terminalEnvFlags } from "./commands/enter.js";
|
|
6
|
+
import { fetchSandboxRows, selectSandboxContainer, startSandboxContainer } from "./commands/list-running.js";
|
|
7
|
+
async function spawnCapture(file, args) {
|
|
8
|
+
return new Promise((resolve, reject) => {
|
|
9
|
+
const child = spawn(file, args, { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
10
|
+
let stdout = '';
|
|
11
|
+
let stderr = '';
|
|
12
|
+
child.stdout.setEncoding('utf8');
|
|
13
|
+
child.stderr.setEncoding('utf8');
|
|
14
|
+
child.stdout.on('data', (chunk) => {
|
|
15
|
+
stdout += chunk;
|
|
16
|
+
});
|
|
17
|
+
child.stderr.on('data', (chunk) => {
|
|
18
|
+
stderr += chunk;
|
|
19
|
+
});
|
|
20
|
+
child.on('error', reject);
|
|
21
|
+
child.on('close', (exitCode, signal) => resolve({ exitCode, signal, stdout, stderr }));
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
export async function runInSandbox(request, options = {}) {
|
|
25
|
+
const config = options.engine ? null : loadConfig();
|
|
26
|
+
const engine = options.engine ?? detectEngine(config);
|
|
27
|
+
const rows = options.rows ??
|
|
28
|
+
(() => {
|
|
29
|
+
const fetched = fetchSandboxRows(engine, sandboxLabel(config), sandboxBranchLabel(config));
|
|
30
|
+
return [...fetched.running, ...fetched.nonRunning];
|
|
31
|
+
})();
|
|
32
|
+
const candidates = options.containerCandidates ?? containerNameCandidates(config, request.branch);
|
|
33
|
+
const found = selectSandboxContainer(rows, candidates);
|
|
34
|
+
if (!found) {
|
|
35
|
+
throw new Error(`Sandbox for ${request.branch} not found. Create it first with ai sandbox create ${request.taskRef}.`);
|
|
36
|
+
}
|
|
37
|
+
if (!found.running) {
|
|
38
|
+
(options.startContainer ?? ((name) => startSandboxContainer(engine, name)))(found.name);
|
|
39
|
+
}
|
|
40
|
+
const dockerArgs = [
|
|
41
|
+
'exec',
|
|
42
|
+
...terminalEnvFlags(),
|
|
43
|
+
...hostTimezoneEnvFlags(),
|
|
44
|
+
found.name,
|
|
45
|
+
...request.command
|
|
46
|
+
];
|
|
47
|
+
return (options.spawn ?? spawnCapture)('docker', dockerArgs);
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=capture.js.map
|