@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.
Files changed (173) hide show
  1. package/README.md +2 -0
  2. package/README.zh-CN.md +2 -0
  3. package/bin/cli.ts +53 -18
  4. package/dist/bin/cli.js +56 -19
  5. package/dist/lib/decide.js +110 -0
  6. package/dist/lib/run/host.js +39 -0
  7. package/dist/lib/run/index.js +102 -0
  8. package/dist/lib/run/prompt.js +2 -0
  9. package/dist/lib/run/skills.js +29 -0
  10. package/dist/lib/run/tui.js +35 -0
  11. package/dist/lib/sandbox/capture.js +49 -0
  12. package/dist/lib/sandbox/commands/create.js +55 -6
  13. package/dist/lib/sandbox/commands/ls.js +4 -33
  14. package/dist/lib/sandbox/commands/show.js +67 -0
  15. package/dist/lib/sandbox/index.js +7 -0
  16. package/dist/lib/server/adapters/_contract.js +9 -0
  17. package/dist/lib/server/adapters/feishu/index.js +40 -0
  18. package/dist/lib/server/adapters/feishu/transport.js +95 -0
  19. package/dist/lib/server/auth.js +19 -0
  20. package/dist/lib/server/config.js +183 -0
  21. package/dist/lib/server/daemon.js +96 -0
  22. package/dist/lib/server/index.js +47 -0
  23. package/dist/lib/server/logger.js +34 -0
  24. package/dist/lib/server/plugin-loader.js +73 -0
  25. package/dist/lib/server/process-control.js +200 -0
  26. package/dist/lib/server/protocol.js +69 -0
  27. package/dist/lib/server/redact.js +12 -0
  28. package/dist/lib/server/runner.js +75 -0
  29. package/dist/lib/server/streamer.js +44 -0
  30. package/dist/lib/task/commands/decisions.js +258 -0
  31. package/dist/lib/task/commands/issue-body.js +94 -0
  32. package/dist/lib/task/commands/log.js +67 -9
  33. package/dist/lib/task/index.js +17 -0
  34. package/dist/lib/task/issue-form.js +66 -0
  35. package/dist/lib/task/ledger.js +66 -0
  36. package/dist/lib/task/sections.js +73 -0
  37. package/lib/decide.ts +124 -0
  38. package/lib/run/host.ts +47 -0
  39. package/lib/run/index.ts +124 -0
  40. package/lib/run/prompt.ts +1 -0
  41. package/lib/run/skills.ts +34 -0
  42. package/lib/run/tui.ts +40 -0
  43. package/lib/sandbox/capture.ts +84 -0
  44. package/lib/sandbox/commands/create.ts +61 -9
  45. package/lib/sandbox/commands/ls.ts +4 -36
  46. package/lib/sandbox/commands/show.ts +80 -0
  47. package/lib/sandbox/index.ts +7 -0
  48. package/lib/server/adapters/_contract.ts +42 -0
  49. package/lib/server/adapters/feishu/index.ts +47 -0
  50. package/lib/server/adapters/feishu/transport.ts +128 -0
  51. package/lib/server/auth.ts +28 -0
  52. package/lib/server/config.ts +228 -0
  53. package/lib/server/daemon.ts +110 -0
  54. package/lib/server/index.ts +50 -0
  55. package/lib/server/logger.ts +48 -0
  56. package/lib/server/plugin-loader.ts +84 -0
  57. package/lib/server/process-control.ts +225 -0
  58. package/lib/server/protocol.ts +79 -0
  59. package/lib/server/redact.ts +12 -0
  60. package/lib/server/runner.ts +95 -0
  61. package/lib/server/server.schema.json +127 -0
  62. package/lib/server/streamer.ts +57 -0
  63. package/lib/task/commands/decisions.ts +272 -0
  64. package/lib/task/commands/issue-body.ts +102 -0
  65. package/lib/task/commands/log.ts +73 -9
  66. package/lib/task/index.ts +17 -0
  67. package/lib/task/issue-form.ts +77 -0
  68. package/lib/task/ledger.ts +73 -0
  69. package/lib/task/sections.ts +73 -0
  70. package/package.json +9 -4
  71. package/templates/.agents/QUICKSTART.zh-CN.md +2 -2
  72. package/templates/.agents/README.en.md +3 -1
  73. package/templates/.agents/README.zh-CN.md +3 -1
  74. package/templates/.agents/rules/create-issue.github.en.md +20 -29
  75. package/templates/.agents/rules/create-issue.github.zh-CN.md +20 -29
  76. package/templates/.agents/rules/issue-fields.github.en.md +1 -1
  77. package/templates/.agents/rules/issue-fields.github.zh-CN.md +1 -1
  78. package/templates/.agents/rules/issue-pr-commands.github.en.md +6 -0
  79. package/templates/.agents/rules/issue-pr-commands.github.zh-CN.md +6 -0
  80. package/templates/.agents/rules/next-step-output.en.md +30 -1
  81. package/templates/.agents/rules/next-step-output.zh-CN.md +30 -1
  82. package/templates/.agents/rules/no-mid-flow-questions.en.md +7 -2
  83. package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +7 -2
  84. package/templates/.agents/rules/review-handshake.en.md +5 -1
  85. package/templates/.agents/rules/review-handshake.zh-CN.md +5 -1
  86. package/templates/.agents/rules/task-management.en.md +3 -1
  87. package/templates/.agents/rules/task-management.zh-CN.md +3 -1
  88. package/templates/.agents/scripts/lib/post-review-commit.js +15 -18
  89. package/templates/.agents/scripts/validate-artifact.js +8 -0
  90. package/templates/.agents/skills/analyze-task/SKILL.en.md +18 -4
  91. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +18 -4
  92. package/templates/.agents/skills/analyze-task/config/verify.en.json +2 -1
  93. package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +2 -1
  94. package/templates/.agents/skills/archive-tasks/SKILL.en.md +3 -1
  95. package/templates/.agents/skills/archive-tasks/SKILL.zh-CN.md +3 -1
  96. package/templates/.agents/skills/block-task/SKILL.en.md +3 -1
  97. package/templates/.agents/skills/block-task/SKILL.zh-CN.md +3 -1
  98. package/templates/.agents/skills/cancel-task/SKILL.en.md +3 -1
  99. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +3 -1
  100. package/templates/.agents/skills/check-task/SKILL.en.md +30 -81
  101. package/templates/.agents/skills/check-task/SKILL.zh-CN.md +30 -80
  102. package/templates/.agents/skills/close-codescan/SKILL.en.md +3 -1
  103. package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +3 -1
  104. package/templates/.agents/skills/close-dependabot/SKILL.en.md +3 -1
  105. package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +3 -1
  106. package/templates/.agents/skills/code-task/SKILL.en.md +4 -2
  107. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +4 -2
  108. package/templates/.agents/skills/code-task/reference/dual-mode.en.md +7 -2
  109. package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +7 -2
  110. package/templates/.agents/skills/code-task/scripts/detect-mode.js +6 -14
  111. package/templates/.agents/skills/commit/SKILL.en.md +27 -5
  112. package/templates/.agents/skills/commit/SKILL.zh-CN.md +27 -5
  113. package/templates/.agents/skills/complete-task/SKILL.en.md +4 -1
  114. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +4 -1
  115. package/templates/.agents/skills/complete-task/config/verify.en.json +2 -1
  116. package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +2 -1
  117. package/templates/.agents/skills/create-pr/SKILL.en.md +3 -1
  118. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +3 -1
  119. package/templates/.agents/skills/create-release-note/SKILL.en.md +3 -1
  120. package/templates/.agents/skills/create-release-note/SKILL.zh-CN.md +3 -1
  121. package/templates/.agents/skills/create-task/SKILL.en.md +4 -4
  122. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +4 -4
  123. package/templates/.agents/skills/import-codescan/SKILL.en.md +3 -4
  124. package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +3 -4
  125. package/templates/.agents/skills/import-dependabot/SKILL.en.md +3 -2
  126. package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +3 -2
  127. package/templates/.agents/skills/import-issue/SKILL.en.md +20 -4
  128. package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +20 -4
  129. package/templates/.agents/skills/init-labels/SKILL.en.md +3 -1
  130. package/templates/.agents/skills/init-labels/SKILL.zh-CN.md +3 -1
  131. package/templates/.agents/skills/init-milestones/SKILL.en.md +3 -1
  132. package/templates/.agents/skills/init-milestones/SKILL.zh-CN.md +3 -1
  133. package/templates/.agents/skills/plan-task/SKILL.en.md +4 -3
  134. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +4 -3
  135. package/templates/.agents/skills/post-release/SKILL.en.md +3 -1
  136. package/templates/.agents/skills/post-release/SKILL.zh-CN.md +3 -1
  137. package/templates/.agents/skills/refine-title/SKILL.en.md +3 -1
  138. package/templates/.agents/skills/refine-title/SKILL.zh-CN.md +3 -1
  139. package/templates/.agents/skills/release/SKILL.en.md +3 -1
  140. package/templates/.agents/skills/release/SKILL.zh-CN.md +3 -1
  141. package/templates/.agents/skills/restore-task/SKILL.en.md +3 -1
  142. package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +3 -1
  143. package/templates/.agents/skills/review-analysis/SKILL.en.md +6 -2
  144. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +6 -2
  145. package/templates/.agents/skills/review-analysis/reference/output-templates.en.md +1 -1
  146. package/templates/.agents/skills/review-analysis/reference/output-templates.zh-CN.md +1 -1
  147. package/templates/.agents/skills/review-analysis/reference/report-template.en.md +1 -1
  148. package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +1 -2
  149. package/templates/.agents/skills/review-code/SKILL.en.md +5 -2
  150. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +5 -2
  151. package/templates/.agents/skills/review-code/reference/output-templates.en.md +1 -1
  152. package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +1 -1
  153. package/templates/.agents/skills/review-code/reference/report-template.en.md +1 -1
  154. package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +1 -2
  155. package/templates/.agents/skills/review-plan/SKILL.en.md +6 -2
  156. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +6 -2
  157. package/templates/.agents/skills/review-plan/reference/output-templates.en.md +1 -1
  158. package/templates/.agents/skills/review-plan/reference/output-templates.zh-CN.md +1 -1
  159. package/templates/.agents/skills/review-plan/reference/report-template.en.md +1 -1
  160. package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +1 -2
  161. package/templates/.agents/skills/test/SKILL.en.md +3 -1
  162. package/templates/.agents/skills/test/SKILL.zh-CN.md +1 -1
  163. package/templates/.agents/skills/test-integration/SKILL.en.md +3 -1
  164. package/templates/.agents/skills/test-integration/SKILL.zh-CN.md +3 -1
  165. package/templates/.agents/skills/update-agent-infra/SKILL.en.md +3 -1
  166. package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +3 -1
  167. package/templates/.agents/skills/upgrade-dependency/SKILL.en.md +3 -1
  168. package/templates/.agents/skills/upgrade-dependency/SKILL.zh-CN.md +3 -1
  169. package/templates/.agents/skills/watch-pr/SKILL.en.md +3 -1
  170. package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +3 -1
  171. package/templates/.agents/templates/task.en.md +3 -3
  172. package/templates/.agents/templates/task.zh-CN.md +3 -3
  173. 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
- sandbox Manage Docker-based AI sandboxes
23
- task Read-only views over .agents/workspace tasks (cat / files / grep / log / ls / show / status)
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 command = process.argv[2] || '';
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 'init': {
64
- const imported = await importCommand('../lib/init.ts');
74
+ case 'cp': {
75
+ const imported = await importCommand('../lib/cp.ts');
65
76
  if (!imported) break;
66
- const { cmdInit } = imported;
67
- await cmdInit().catch((e: unknown) => {
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
- process.exitCode = 1;
80
+ return 1;
70
81
  });
82
+ if (code) process.exitCode = code;
71
83
  break;
72
84
  }
73
- case 'update': {
74
- const imported = await importCommand('../lib/update.ts');
85
+ case 'decide': {
86
+ const imported = await importCommand('../lib/decide.ts');
75
87
  if (!imported) break;
76
- const { cmdUpdate } = imported;
77
- await cmdUpdate().catch((e: unknown) => {
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 'cp': {
114
- const imported = await importCommand('../lib/cp.ts');
149
+ case 'update': {
150
+ const imported = await importCommand('../lib/update.ts');
115
151
  if (!imported) break;
116
- const { cmdCp } = imported;
117
- const code = await cmdCp(process.argv.slice(3)).catch((e: unknown) => {
152
+ const { cmdUpdate } = imported;
153
+ await cmdUpdate().catch((e: unknown) => {
118
154
  process.stderr.write(`Error: ${errorMessage(e)}\n`);
119
- return 1;
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
- sandbox Manage Docker-based AI sandboxes
27
- task Read-only views over .agents/workspace tasks (cat / files / grep / log / ls / show / status)
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 command = process.argv[2] || '';
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 'init': {
63
- const imported = await importCommand('../lib/init.ts');
72
+ case 'cp': {
73
+ const imported = await importCommand('../lib/cp.ts');
64
74
  if (!imported)
65
75
  break;
66
- const { cmdInit } = imported;
67
- await cmdInit().catch((e) => {
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
- process.exitCode = 1;
79
+ return 1;
70
80
  });
81
+ if (code)
82
+ process.exitCode = code;
71
83
  break;
72
84
  }
73
- case 'update': {
74
- const imported = await importCommand('../lib/update.ts');
85
+ case 'decide': {
86
+ const imported = await importCommand('../lib/decide.ts');
75
87
  if (!imported)
76
88
  break;
77
- const { cmdUpdate } = imported;
78
- await cmdUpdate().catch((e) => {
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 'cp': {
118
- const imported = await importCommand('../lib/cp.ts');
156
+ case 'update': {
157
+ const imported = await importCommand('../lib/update.ts');
119
158
  if (!imported)
120
159
  break;
121
- const { cmdCp } = imported;
122
- const code = await cmdCp(process.argv.slice(3)).catch((e) => {
160
+ const { cmdUpdate } = imported;
161
+ await cmdUpdate().catch((e) => {
123
162
  process.stderr.write(`Error: ${errorMessage(e)}\n`);
124
- return 1;
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,2 @@
1
+ export { renderPrompt } from "./tui.js";
2
+ //# sourceMappingURL=prompt.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