agency-orchestrator 0.4.2 → 0.5.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 +7 -4
- package/dist/agents/loader.js +8 -0
- package/dist/cli/compose.d.ts +11 -1
- package/dist/cli/compose.js +52 -18
- package/dist/cli.js +117 -9
- package/dist/connectors/claude-code.d.ts +4 -10
- package/dist/connectors/claude-code.js +136 -14
- package/dist/connectors/cli-base.d.ts +4 -0
- package/dist/connectors/cli-base.js +101 -33
- package/dist/connectors/factory.js +16 -2
- package/dist/connectors/openai-compatible.d.ts +8 -0
- package/dist/connectors/openai-compatible.js +145 -23
- package/dist/connectors/openclaw-cli.d.ts +6 -1
- package/dist/connectors/openclaw-cli.js +25 -3
- package/dist/core/executor.js +80 -17
- package/dist/index.js +37 -4
- package/dist/output/reporter.d.ts +4 -4
- package/dist/output/reporter.js +119 -19
- package/dist/types.d.ts +9 -1
- package/package.json +1 -1
- package/workflows/ai-startup-launch.yaml +82 -0
- package/workflows//344/270/200/344/272/272/345/205/254/345/217/270/345/205/250/345/221/230/345/244/247/344/274/232.yaml +194 -0
- package/workflows/test-deepseek.yaml +0 -68
- package/workflows/test-real.yaml +0 -35
package/README.md
CHANGED
|
@@ -515,11 +515,14 @@ ao-output/产品需求评审-2026-03-22/
|
|
|
515
515
|
Code/Copilot...) Cursor 直接调用)
|
|
516
516
|
```
|
|
517
517
|
|
|
518
|
-
##
|
|
518
|
+
## 交流 · Community
|
|
519
519
|
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
|
520
|
+
微信公众号 **「AI不止语」**(微信搜索 `AI_BuZhiYu`)— 技术问答 · 项目更新 · 实战文章
|
|
521
|
+
|
|
522
|
+
| 渠道 | 加入方式 |
|
|
523
|
+
|------|---------|
|
|
524
|
+
| QQ 群 | [点击加入](https://qm.qq.com/q/EeNQA9xCxy)(群号 1071280067) |
|
|
525
|
+
| 微信群 | 关注公众号后回复「群」获取入群方式 |
|
|
523
526
|
|
|
524
527
|
## 姊妹项目
|
|
525
528
|
|
package/dist/agents/loader.js
CHANGED
|
@@ -18,7 +18,15 @@ import { join, resolve } from 'node:path';
|
|
|
18
18
|
* @param rolePath 角色路径,如 "engineering/engineering-sre"
|
|
19
19
|
*/
|
|
20
20
|
export function loadAgent(agentsDir, rolePath) {
|
|
21
|
+
// 防止路径穿越攻击(如 ../../etc/passwd)
|
|
22
|
+
if (/\.\.[/\\]/.test(rolePath) || /[^a-zA-Z0-9_\-/]/.test(rolePath)) {
|
|
23
|
+
throw new Error(`非法角色路径: ${rolePath}\n角色路径只能包含字母、数字、下划线、连字符和斜杠`);
|
|
24
|
+
}
|
|
21
25
|
const fullPath = resolve(agentsDir, `${rolePath}.md`);
|
|
26
|
+
const resolvedDir = resolve(agentsDir);
|
|
27
|
+
if (!fullPath.startsWith(resolvedDir)) {
|
|
28
|
+
throw new Error(`角色路径越界: ${rolePath}`);
|
|
29
|
+
}
|
|
22
30
|
if (!existsSync(fullPath)) {
|
|
23
31
|
throw new Error(`角色文件不存在: ${fullPath}\n请确认 agents_dir 和 role 路径正确`);
|
|
24
32
|
}
|
package/dist/cli/compose.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { LLMConfig } from '../types.js';
|
|
|
3
3
|
export interface RoleSummary {
|
|
4
4
|
path: string;
|
|
5
5
|
name: string;
|
|
6
|
+
emoji?: string;
|
|
6
7
|
description: string;
|
|
7
8
|
category: string;
|
|
8
9
|
}
|
|
@@ -16,8 +17,14 @@ export declare function buildRoleCatalog(agentsDir: string): RoleSummary[];
|
|
|
16
17
|
export declare function formatCatalogForPrompt(roles: RoleSummary[]): string;
|
|
17
18
|
/**
|
|
18
19
|
* 构建发给 LLM 的 system prompt
|
|
20
|
+
* @param catalog 角色目录文本
|
|
21
|
+
* @param options.autoRun 如果 true,生成的 YAML 不需要 inputs,用户描述直接嵌入 task
|
|
19
22
|
*/
|
|
20
|
-
export declare function buildComposeSystemPrompt(catalog: string
|
|
23
|
+
export declare function buildComposeSystemPrompt(catalog: string, options?: {
|
|
24
|
+
autoRun?: boolean;
|
|
25
|
+
provider?: string;
|
|
26
|
+
model?: string;
|
|
27
|
+
}): string;
|
|
21
28
|
/**
|
|
22
29
|
* 构建 user prompt
|
|
23
30
|
*/
|
|
@@ -37,6 +44,9 @@ export declare function composeWorkflow(options: {
|
|
|
37
44
|
description: string;
|
|
38
45
|
agentsDir: string;
|
|
39
46
|
llmConfig: LLMConfig;
|
|
47
|
+
outputName?: string;
|
|
48
|
+
/** 直接运行模式:生成的 YAML 不需要 inputs */
|
|
49
|
+
autoRun?: boolean;
|
|
40
50
|
}): Promise<{
|
|
41
51
|
yaml: string;
|
|
42
52
|
savedPath: string;
|
package/dist/cli/compose.js
CHANGED
|
@@ -17,6 +17,7 @@ export function buildRoleCatalog(agentsDir) {
|
|
|
17
17
|
.map(a => ({
|
|
18
18
|
path: a.rolePath,
|
|
19
19
|
name: a.name,
|
|
20
|
+
emoji: a.emoji,
|
|
20
21
|
description: a.description || '',
|
|
21
22
|
category: a.rolePath.split('/')[0],
|
|
22
23
|
}));
|
|
@@ -35,7 +36,7 @@ export function formatCatalogForPrompt(roles) {
|
|
|
35
36
|
for (const [cat, list] of byCategory) {
|
|
36
37
|
lines.push(`## ${cat}`);
|
|
37
38
|
for (const r of list) {
|
|
38
|
-
lines.push(`- ${r.path} | ${r.name} | ${r.description}`);
|
|
39
|
+
lines.push(`- ${r.path} | ${r.emoji || ''} ${r.name} | ${r.description}`);
|
|
39
40
|
}
|
|
40
41
|
lines.push('');
|
|
41
42
|
}
|
|
@@ -43,16 +44,43 @@ export function formatCatalogForPrompt(roles) {
|
|
|
43
44
|
}
|
|
44
45
|
/**
|
|
45
46
|
* 构建发给 LLM 的 system prompt
|
|
47
|
+
* @param catalog 角色目录文本
|
|
48
|
+
* @param options.autoRun 如果 true,生成的 YAML 不需要 inputs,用户描述直接嵌入 task
|
|
46
49
|
*/
|
|
47
|
-
export function buildComposeSystemPrompt(catalog) {
|
|
50
|
+
export function buildComposeSystemPrompt(catalog, options) {
|
|
51
|
+
const autoRun = options?.autoRun ?? false;
|
|
52
|
+
const provider = options?.provider || 'deepseek';
|
|
53
|
+
const model = options?.model;
|
|
54
|
+
const inputsSection = autoRun
|
|
55
|
+
? `
|
|
56
|
+
## 重要:直接运行模式
|
|
57
|
+
|
|
58
|
+
这个工作流生成后会立即执行,所以:
|
|
59
|
+
- **不要**生成 inputs 段
|
|
60
|
+
- 把用户描述中的所有具体信息直接写进每个 step 的 task 里
|
|
61
|
+
- 确保工作流无需任何外部输入就能直接运行`
|
|
62
|
+
: `
|
|
63
|
+
inputs:
|
|
64
|
+
- name: variable_name
|
|
65
|
+
description: "变量描述"
|
|
66
|
+
required: true`;
|
|
67
|
+
const inputsYamlExample = autoRun ? '' : `
|
|
68
|
+
inputs:
|
|
69
|
+
- name: variable_name
|
|
70
|
+
description: "变量描述"
|
|
71
|
+
required: true
|
|
72
|
+
`;
|
|
73
|
+
const inputsDesignPrinciple = autoRun
|
|
74
|
+
? '- **自包含**:所有信息直接写在 task 中,不要使用 inputs,工作流必须能直接运行'
|
|
75
|
+
: '- **合理输入**:提取用户需求中的关键变量作为 inputs';
|
|
48
76
|
return `你是一个 AI 工作流编排专家。用户会用一句话描述他想要的工作流,你需要:
|
|
49
77
|
|
|
50
78
|
1. 从下方角色目录中选择最合适的角色(通常 2-6 个)
|
|
51
79
|
2. 设计合理的 DAG 依赖关系(哪些步骤可以并行,哪些必须串行)
|
|
52
80
|
3. 为每个步骤编写详细的 task 描述
|
|
53
|
-
4. 设计合理的输入变量
|
|
81
|
+
${autoRun ? '4. 把用户描述中的具体信息直接写进 task,不要生成 inputs' : '4. 设计合理的输入变量'}
|
|
54
82
|
5. 生成完整的 workflow YAML
|
|
55
|
-
|
|
83
|
+
${autoRun ? inputsSection : ''}
|
|
56
84
|
## 输出格式
|
|
57
85
|
|
|
58
86
|
直接输出一个完整的 YAML 代码块,格式如下:
|
|
@@ -64,23 +92,20 @@ description: "一句话描述"
|
|
|
64
92
|
agents_dir: "agency-agents-zh"
|
|
65
93
|
|
|
66
94
|
llm:
|
|
67
|
-
provider:
|
|
68
|
-
model:
|
|
95
|
+
provider: ${provider}
|
|
96
|
+
${model ? `model: ${model}` : ''}
|
|
69
97
|
max_tokens: 4096
|
|
70
98
|
|
|
71
99
|
concurrency: 2
|
|
72
|
-
|
|
73
|
-
inputs:
|
|
74
|
-
- name: variable_name
|
|
75
|
-
description: "变量描述"
|
|
76
|
-
required: true
|
|
77
|
-
|
|
100
|
+
${inputsYamlExample}
|
|
78
101
|
steps:
|
|
79
102
|
- id: step_id
|
|
80
103
|
role: "category/role-name"
|
|
104
|
+
name: "通俗易懂的角色名(如:老板、产品经理、技术总监)"
|
|
105
|
+
emoji: "👔"
|
|
81
106
|
task: |
|
|
82
107
|
详细的任务描述...
|
|
83
|
-
使用 {{variable_name}} 引用输入变量
|
|
108
|
+
${autoRun ? ' 直接包含用户需求中的具体信息' : ' 使用 {{variable_name}} 引用输入变量'}
|
|
84
109
|
使用 {{previous_output}} 引用上游步骤的输出
|
|
85
110
|
output: output_variable_name
|
|
86
111
|
depends_on: [upstream_step_id] # 仅在有依赖时添加
|
|
@@ -91,9 +116,10 @@ steps:
|
|
|
91
116
|
- **并行优先**:没有数据依赖的步骤应该并行执行(不加 depends_on)
|
|
92
117
|
- **变量串联**:上游步骤的 output 变量名要和下游步骤 task 中的 {{变量}} 对应
|
|
93
118
|
- **角色匹配**:选择最专业的角色,不要用一个角色做所有事
|
|
119
|
+
- **角色命名**:每个步骤必须设置 name(通俗的公司职位名如"老板""产品经理""技术总监")和 emoji,让小白也能一眼看懂谁在说话
|
|
94
120
|
- **任务详细**:task 描述要具体,告诉角色要做什么、输出什么格式
|
|
95
|
-
|
|
96
|
-
-
|
|
121
|
+
${inputsDesignPrinciple}
|
|
122
|
+
- **最终成品**:最后一个步骤必须输出用户想要的最终成品(如完整文章、完整报告),而不是审查意见或修改建议。如果有审校步骤,审校步骤应该直接输出修改后的定稿,而不是"修改建议列表"
|
|
97
123
|
|
|
98
124
|
## 可用角色目录
|
|
99
125
|
|
|
@@ -103,7 +129,9 @@ ${catalog}
|
|
|
103
129
|
|
|
104
130
|
- role 的值必须严格使用角色目录中的 path(如 "engineering/engineering-code-reviewer"),不要自己编造
|
|
105
131
|
- 只输出 YAML 代码块,不要输出其他内容
|
|
106
|
-
- concurrency
|
|
132
|
+
- concurrency 设为并行步骤的最大数量
|
|
133
|
+
- **重要:拆分大任务**。写长文章时,不要让一个步骤生成超过 800 字的内容。应该按章节拆分成多个并行步骤(如 write_ch1、write_ch2、write_ch3),最后用一个合并步骤重写为连贯的完整文章
|
|
134
|
+
- 每个写作步骤的 task 中要限定输出字数(如"500字以内"),避免单步骤生成时间过长`;
|
|
107
135
|
}
|
|
108
136
|
/**
|
|
109
137
|
* 构建 user prompt
|
|
@@ -163,7 +191,11 @@ export async function composeWorkflow(options) {
|
|
|
163
191
|
}
|
|
164
192
|
const catalog = formatCatalogForPrompt(roles);
|
|
165
193
|
// 2. 构建 prompt
|
|
166
|
-
const systemPrompt = buildComposeSystemPrompt(catalog
|
|
194
|
+
const systemPrompt = buildComposeSystemPrompt(catalog, {
|
|
195
|
+
autoRun: options.autoRun,
|
|
196
|
+
provider: options.llmConfig.provider,
|
|
197
|
+
model: options.llmConfig.model,
|
|
198
|
+
});
|
|
167
199
|
const userPrompt = buildComposeUserPrompt(description);
|
|
168
200
|
// 3. 调用 LLM
|
|
169
201
|
console.log(` 正在用 AI 编排工作流...(${roles.length} 个角色可选)\n`);
|
|
@@ -182,7 +214,9 @@ export async function composeWorkflow(options) {
|
|
|
182
214
|
if (!existsSync(workflowsDir)) {
|
|
183
215
|
mkdirSync(workflowsDir, { recursive: true });
|
|
184
216
|
}
|
|
185
|
-
const fileName =
|
|
217
|
+
const fileName = options.outputName
|
|
218
|
+
? (options.outputName.endsWith('.yaml') ? options.outputName : `${options.outputName}.yaml`)
|
|
219
|
+
: generateFileName(description, workflowsDir);
|
|
186
220
|
const savedPath = resolve(workflowsDir, fileName);
|
|
187
221
|
writeFileSync(savedPath, yaml + '\n', 'utf-8');
|
|
188
222
|
const relativePath = relative(process.cwd(), savedPath);
|
package/dist/cli.js
CHANGED
|
@@ -81,6 +81,8 @@ async function handleRun() {
|
|
|
81
81
|
const watch = args.includes('--watch');
|
|
82
82
|
let resumeDir = getArgValue('--resume');
|
|
83
83
|
const fromStep = getArgValue('--from');
|
|
84
|
+
const provider = getArgValue('--provider');
|
|
85
|
+
const model = getArgValue('--model');
|
|
84
86
|
// --resume last: 自动找最近一次的输出目录
|
|
85
87
|
if (resumeDir === 'last') {
|
|
86
88
|
const { findLatestOutput } = await import('./output/reporter.js');
|
|
@@ -92,12 +94,21 @@ async function handleRun() {
|
|
|
92
94
|
resumeDir = latest;
|
|
93
95
|
}
|
|
94
96
|
try {
|
|
97
|
+
// --provider / --model: 命令行覆盖 YAML 中的 LLM 配置
|
|
98
|
+
const cliProviders = ['claude-code', 'gemini-cli', 'copilot-cli', 'codex-cli', 'openclaw-cli'];
|
|
99
|
+
const llmOverride = provider ? {
|
|
100
|
+
provider,
|
|
101
|
+
// CLI provider 不指定 model 时清空(避免 YAML 里的 deepseek-chat 传给 claude CLI)
|
|
102
|
+
model: model || (cliProviders.includes(provider) ? '' : undefined),
|
|
103
|
+
...(cliProviders.includes(provider) ? { timeout: 600_000 } : {}),
|
|
104
|
+
} : undefined;
|
|
95
105
|
const result = await run(resolve(filePath), inputs, {
|
|
96
106
|
outputDir,
|
|
97
107
|
quiet,
|
|
98
108
|
watch,
|
|
99
109
|
resumeDir: resumeDir ? resolve(resumeDir) : undefined,
|
|
100
110
|
fromStep,
|
|
111
|
+
llmOverride,
|
|
101
112
|
});
|
|
102
113
|
process.exit(result.success ? 0 : 1);
|
|
103
114
|
}
|
|
@@ -176,7 +187,21 @@ async function handleExplain() {
|
|
|
176
187
|
}
|
|
177
188
|
}
|
|
178
189
|
async function handleCompose() {
|
|
179
|
-
const
|
|
190
|
+
const autoRun = args.includes('--run');
|
|
191
|
+
// 描述是第一个非 flag 的参数(跳过 compose 本身和 --xxx 的值)
|
|
192
|
+
const flagsWithValue = new Set(['--name', '--provider', '--model', '--agents-dir']);
|
|
193
|
+
let description;
|
|
194
|
+
for (let i = 1; i < args.length; i++) {
|
|
195
|
+
if (args[i] === '--run')
|
|
196
|
+
continue;
|
|
197
|
+
if (args[i].startsWith('--')) {
|
|
198
|
+
if (flagsWithValue.has(args[i]))
|
|
199
|
+
i++; // 跳过 flag 的值
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
description = args[i];
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
180
205
|
if (!description) {
|
|
181
206
|
console.error('用法: ao compose "用一句话描述你想要的工作流"');
|
|
182
207
|
console.error('');
|
|
@@ -186,19 +211,28 @@ async function handleCompose() {
|
|
|
186
211
|
console.error(' ao compose "用户反馈分析,分类后分别给产品和技术团队"');
|
|
187
212
|
console.error('');
|
|
188
213
|
console.error('选项:');
|
|
214
|
+
console.error(' --run 生成后立即运行(一句话出结果)');
|
|
215
|
+
console.error(' --name <filename> 自定义输出文件名 (不含 .yaml 后缀)');
|
|
189
216
|
console.error(' --provider <name> LLM 提供商 (默认 deepseek)');
|
|
190
217
|
console.error(' --model <name> 模型名 (默认 deepseek-chat)');
|
|
191
218
|
process.exit(1);
|
|
192
219
|
}
|
|
193
220
|
const provider = (getArgValue('--provider') || 'deepseek');
|
|
194
|
-
const
|
|
221
|
+
const cliProviders = ['claude-code', 'gemini-cli', 'copilot-cli', 'codex-cli', 'openclaw-cli'];
|
|
222
|
+
const model = getArgValue('--model') || (cliProviders.includes(provider) ? '' :
|
|
223
|
+
provider === 'deepseek' ? 'deepseek-chat' :
|
|
224
|
+
provider === 'claude' ? 'claude-sonnet-4-20250514' :
|
|
225
|
+
'gpt-4o');
|
|
195
226
|
const agentsDir = getArgValue('--agents-dir') || resolveAgentsDir();
|
|
227
|
+
const outputName = getArgValue('--name');
|
|
196
228
|
try {
|
|
197
229
|
const { composeWorkflow } = await import('./cli/compose.js');
|
|
198
|
-
const { yaml, relativePath, warnings } = await composeWorkflow({
|
|
230
|
+
const { yaml, savedPath, relativePath, warnings } = await composeWorkflow({
|
|
199
231
|
description,
|
|
200
232
|
agentsDir: resolve(agentsDir),
|
|
201
233
|
llmConfig: { provider, model },
|
|
234
|
+
outputName,
|
|
235
|
+
autoRun,
|
|
202
236
|
});
|
|
203
237
|
console.log(`\n ✅ 工作流已生成: ${relativePath}\n`);
|
|
204
238
|
// 校验警告
|
|
@@ -209,6 +243,33 @@ async function handleCompose() {
|
|
|
209
243
|
}
|
|
210
244
|
console.log('');
|
|
211
245
|
}
|
|
246
|
+
if (autoRun) {
|
|
247
|
+
// --run 模式:校验有严重问题时不执行
|
|
248
|
+
if (warnings.some(w => w.includes('解析失败'))) {
|
|
249
|
+
console.error(' 生成的 YAML 有解析错误,无法自动运行。请手动修复后执行:');
|
|
250
|
+
console.error(` ao run ${relativePath}`);
|
|
251
|
+
process.exit(1);
|
|
252
|
+
}
|
|
253
|
+
console.log('─'.repeat(50));
|
|
254
|
+
console.log(' 开始执行工作流...\n');
|
|
255
|
+
// 保底:如果 LLM 仍然生成了 required inputs,用用户描述填充
|
|
256
|
+
const { parseWorkflow } = await import('./core/parser.js');
|
|
257
|
+
const workflow = parseWorkflow(resolve(savedPath));
|
|
258
|
+
const inputs = {};
|
|
259
|
+
for (const def of workflow.inputs || []) {
|
|
260
|
+
if (def.required && def.default === undefined) {
|
|
261
|
+
inputs[def.name] = description;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
const result = await run(resolve(savedPath), inputs, {
|
|
265
|
+
quiet: false,
|
|
266
|
+
// 用 compose 时同样的 provider 执行,避免 YAML 里写的 provider 和用户实际可用的不一致
|
|
267
|
+
// CLI provider 单步调用可能很慢(1-20 分钟),给足超时
|
|
268
|
+
llmOverride: { provider, model: model || undefined, timeout: cliProviders.includes(provider) ? 600_000 : 300_000 },
|
|
269
|
+
});
|
|
270
|
+
process.exit(result.success ? 0 : 1);
|
|
271
|
+
}
|
|
272
|
+
// 非 --run 模式:显示预览和下一步提示
|
|
212
273
|
console.log(' 预览:');
|
|
213
274
|
const previewLines = yaml.split('\n').slice(0, 30);
|
|
214
275
|
for (const line of previewLines) {
|
|
@@ -271,13 +332,41 @@ async function handleInit() {
|
|
|
271
332
|
}
|
|
272
333
|
else {
|
|
273
334
|
console.log(' 正在下载 agency-agents-zh (186 个 AI 角色定义)...\n');
|
|
335
|
+
let downloaded = false;
|
|
336
|
+
// 优先用 npm(国内镜像快)
|
|
274
337
|
try {
|
|
275
|
-
execSync('
|
|
276
|
-
|
|
338
|
+
execSync('npm pack agency-agents-zh --pack-destination .', { stdio: 'pipe' });
|
|
339
|
+
const { readdirSync } = await import('node:fs');
|
|
340
|
+
const tgz = readdirSync('.').find(f => f.startsWith('agency-agents-zh-') && f.endsWith('.tgz'));
|
|
341
|
+
if (tgz) {
|
|
342
|
+
const { mkdirSync } = await import('node:fs');
|
|
343
|
+
mkdirSync('agency-agents-zh', { recursive: true });
|
|
344
|
+
execSync(`tar xzf ${tgz} --strip-components=1 -C agency-agents-zh`, { stdio: 'pipe' });
|
|
345
|
+
const { unlinkSync } = await import('node:fs');
|
|
346
|
+
unlinkSync(tgz);
|
|
347
|
+
downloaded = true;
|
|
348
|
+
console.log(' 通过 npm 下载完成!');
|
|
349
|
+
}
|
|
277
350
|
}
|
|
278
351
|
catch {
|
|
279
|
-
|
|
280
|
-
|
|
352
|
+
// npm 失败,回退 git clone
|
|
353
|
+
}
|
|
354
|
+
// 回退: git clone
|
|
355
|
+
if (!downloaded) {
|
|
356
|
+
try {
|
|
357
|
+
console.log(' npm 下载失败,尝试 git clone...\n');
|
|
358
|
+
execSync('git clone --depth 1 https://github.com/jnMetaCode/agency-agents-zh.git', { stdio: 'inherit' });
|
|
359
|
+
console.log('\n 下载完成!');
|
|
360
|
+
downloaded = true;
|
|
361
|
+
}
|
|
362
|
+
catch {
|
|
363
|
+
// ignore
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
if (!downloaded) {
|
|
367
|
+
console.error('\n 下载失败,请手动安装:');
|
|
368
|
+
console.error(' npm pack agency-agents-zh && tar xzf agency-agents-zh-*.tgz && mv package agency-agents-zh');
|
|
369
|
+
console.error(' 或: git clone https://github.com/jnMetaCode/agency-agents-zh.git');
|
|
281
370
|
process.exit(1);
|
|
282
371
|
}
|
|
283
372
|
}
|
|
@@ -294,9 +383,25 @@ function handleRoles() {
|
|
|
294
383
|
try {
|
|
295
384
|
const agents = listAgents(resolve(agentsDir));
|
|
296
385
|
console.log(`\n 共 ${agents.length} 个角色 (${agentsDir}):\n`);
|
|
386
|
+
// 按分类分组
|
|
387
|
+
const byCategory = new Map();
|
|
297
388
|
for (const agent of agents) {
|
|
298
|
-
const
|
|
299
|
-
|
|
389
|
+
const cat = agent.rolePath?.split('/')[0] || 'other';
|
|
390
|
+
const list = byCategory.get(cat) || [];
|
|
391
|
+
list.push(agent);
|
|
392
|
+
byCategory.set(cat, list);
|
|
393
|
+
}
|
|
394
|
+
for (const [category, list] of byCategory) {
|
|
395
|
+
console.log(` ── ${category} (${list.length}) ──`);
|
|
396
|
+
for (const agent of list) {
|
|
397
|
+
const emoji = agent.emoji || ' ';
|
|
398
|
+
const path = agent.rolePath || '';
|
|
399
|
+
console.log(` ${emoji} ${agent.name} ${path}`);
|
|
400
|
+
if (agent.description) {
|
|
401
|
+
console.log(` ${agent.description}`);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
console.log('');
|
|
300
405
|
}
|
|
301
406
|
}
|
|
302
407
|
catch (err) {
|
|
@@ -389,6 +494,7 @@ function printHelp() {
|
|
|
389
494
|
init 下载/更新 agency-agents-zh
|
|
390
495
|
init --workflow 交互式创建新工作流
|
|
391
496
|
compose "描述" AI 智能编排工作流(一句话生成 YAML)
|
|
497
|
+
compose "描述" --run 生成并立即运行(一句话出结果)
|
|
392
498
|
serve 启动 MCP Server(供 Claude Code / Cursor 调用)
|
|
393
499
|
run <workflow.yaml> 执行工作流
|
|
394
500
|
validate <workflow.yaml> 校验工作流定义
|
|
@@ -399,6 +505,8 @@ function printHelp() {
|
|
|
399
505
|
Options:
|
|
400
506
|
--input, -i key=value 传入输入变量
|
|
401
507
|
--input, -i key=@file 从文件读取变量值
|
|
508
|
+
--provider <name> 覆盖 YAML 中的 LLM provider (如 claude-code, deepseek)
|
|
509
|
+
--model <name> 覆盖 YAML 中的模型名
|
|
402
510
|
--output dir 输出目录 (默认 ao-output/)
|
|
403
511
|
--resume <dir|last> 从上次运行恢复(加载已完成步骤的输出)
|
|
404
512
|
--from <step-id> 配合 --resume,从指定步骤重新执行
|
|
@@ -1,11 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
* 安装: npm install -g @anthropic-ai/claude-code
|
|
6
|
-
* 认证: claude 登录后自动使用订阅额度
|
|
7
|
-
*/
|
|
8
|
-
import { CLIBaseConnector } from './cli-base.js';
|
|
9
|
-
export declare class ClaudeCodeConnector extends CLIBaseConnector {
|
|
10
|
-
constructor();
|
|
1
|
+
import type { LLMConnector, LLMResult, LLMConfig } from '../types.js';
|
|
2
|
+
export declare class ClaudeCodeConnector implements LLMConnector {
|
|
3
|
+
chat(systemPrompt: string, userMessage: string, config: LLMConfig): Promise<LLMResult>;
|
|
4
|
+
private _exec;
|
|
11
5
|
}
|
|
@@ -1,23 +1,145 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Claude Code CLI Connector
|
|
3
|
-
* 通过本地 `claude` CLI
|
|
3
|
+
* 通过本地 `claude` CLI 调用,直接使用 Claude Max/Pro 订阅额度,无需 API key
|
|
4
4
|
*
|
|
5
5
|
* 安装: npm install -g @anthropic-ai/claude-code
|
|
6
6
|
* 认证: claude 登录后自动使用订阅额度
|
|
7
|
+
*
|
|
8
|
+
* 关键: 使用 --output-format json 而非 text
|
|
9
|
+
* text 格式在管道模式下有缓冲问题,长输出(>1000 字)会导致子进程挂起
|
|
10
|
+
* json 格式一次性输出完整结果,包含 usage 等元数据
|
|
7
11
|
*/
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
12
|
+
import { spawn } from 'node:child_process';
|
|
13
|
+
import { writeFileSync, unlinkSync } from 'node:fs';
|
|
14
|
+
import { tmpdir } from 'node:os';
|
|
15
|
+
import { join } from 'node:path';
|
|
16
|
+
export class ClaudeCodeConnector {
|
|
17
|
+
async chat(systemPrompt, userMessage, config) {
|
|
18
|
+
const timeout = config.timeout || 600_000; // 默认 10 分钟
|
|
19
|
+
// 用临时文件传系统 prompt(避免命令行过长)
|
|
20
|
+
let systemPromptFile;
|
|
21
|
+
if (systemPrompt) {
|
|
22
|
+
systemPromptFile = join(tmpdir(), `ao-sysprompt-${Date.now()}-${Math.random().toString(36).slice(2)}.txt`);
|
|
23
|
+
writeFileSync(systemPromptFile, systemPrompt, 'utf-8');
|
|
24
|
+
}
|
|
25
|
+
// 使用 json 格式:text 格式在管道中会缓冲挂起
|
|
26
|
+
const args = ['-p', '-', '--output-format', 'json', '--tools', '', '--effort', 'low', '--no-session-persistence'];
|
|
27
|
+
if (systemPromptFile) {
|
|
28
|
+
args.push('--system-prompt-file', systemPromptFile);
|
|
29
|
+
}
|
|
30
|
+
if (config.model && config.model !== 'claude-code') {
|
|
31
|
+
args.push('--model', config.model);
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
return await this._exec(args, userMessage, timeout);
|
|
35
|
+
}
|
|
36
|
+
finally {
|
|
37
|
+
if (systemPromptFile) {
|
|
38
|
+
try {
|
|
39
|
+
unlinkSync(systemPromptFile);
|
|
40
|
+
}
|
|
41
|
+
catch { }
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
_exec(args, stdinData, timeout) {
|
|
46
|
+
return new Promise((resolve, reject) => {
|
|
47
|
+
const child = spawn('claude', args, {
|
|
48
|
+
env: { ...process.env },
|
|
49
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
50
|
+
shell: process.platform === 'win32',
|
|
51
|
+
});
|
|
52
|
+
let stdout = '';
|
|
53
|
+
let stderr = '';
|
|
54
|
+
let killed = false;
|
|
55
|
+
let receivedBytes = 0;
|
|
56
|
+
let lastProgressTime = 0;
|
|
57
|
+
const timer = setTimeout(() => {
|
|
58
|
+
killed = true;
|
|
59
|
+
child.kill('SIGTERM');
|
|
60
|
+
setTimeout(() => { try {
|
|
61
|
+
child.kill('SIGKILL');
|
|
62
|
+
}
|
|
63
|
+
catch { } }, 5000);
|
|
64
|
+
}, timeout);
|
|
65
|
+
child.stdout.on('data', (chunk) => {
|
|
66
|
+
stdout += chunk.toString();
|
|
67
|
+
receivedBytes += chunk.length;
|
|
68
|
+
const now = Date.now();
|
|
69
|
+
if (now - lastProgressTime > 10_000) {
|
|
70
|
+
lastProgressTime = now;
|
|
71
|
+
const kb = (receivedBytes / 1024).toFixed(1);
|
|
72
|
+
process.stderr.write(` 📡 已接收 ${kb}KB...\n`);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
child.stderr.on('data', (chunk) => { stderr += chunk.toString(); });
|
|
76
|
+
child.stdin.on('error', () => { });
|
|
77
|
+
child.stdin.write(stdinData);
|
|
78
|
+
child.stdin.end();
|
|
79
|
+
child.on('error', (err) => {
|
|
80
|
+
clearTimeout(timer);
|
|
81
|
+
if (err.code === 'ENOENT') {
|
|
82
|
+
reject(new Error('找不到 claude 命令,请先安装 Claude Code CLI\n' +
|
|
83
|
+
'安装: npm install -g @anthropic-ai/claude-code\n' +
|
|
84
|
+
'参考: https://github.com/jnMetaCode/agency-orchestrator#llm-配置'));
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
reject(new Error(`Claude Code CLI 调用失败: ${err.message}`));
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
child.on('close', (code) => {
|
|
91
|
+
clearTimeout(timer);
|
|
92
|
+
if (killed) {
|
|
93
|
+
reject(new Error(`Claude Code CLI 超时 (${timeout / 1000}s),可在 YAML 中设置 timeout 增加等待时间`));
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (code !== 0 && !stdout.trim()) {
|
|
97
|
+
reject(new Error(`Claude Code CLI 调用失败 (exit ${code}): ${stderr.slice(0, 500)}`));
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
// 解析 JSON 响应
|
|
101
|
+
try {
|
|
102
|
+
const json = JSON.parse(stdout);
|
|
103
|
+
if (json.is_error) {
|
|
104
|
+
reject(new Error(`Claude Code CLI 错误: ${json.result?.slice(0, 300) || 'unknown error'}`));
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const content = (json.result || '').trim();
|
|
108
|
+
if (!content) {
|
|
109
|
+
reject(new Error(`Claude Code CLI 返回空内容`));
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
// 从 JSON 中提取真实 usage
|
|
113
|
+
const usage = json.usage || json.modelUsage || {};
|
|
114
|
+
resolve({
|
|
115
|
+
content,
|
|
116
|
+
usage: {
|
|
117
|
+
input_tokens: usage.input_tokens || usage.inputTokens || 0,
|
|
118
|
+
output_tokens: usage.output_tokens || usage.outputTokens || 0,
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
// JSON 解析失败,回退到原始文本
|
|
124
|
+
const content = stdout.trim();
|
|
125
|
+
if (!content) {
|
|
126
|
+
reject(new Error(`Claude Code CLI 返回空内容,stderr: ${stderr.slice(0, 500)}`));
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
// 检测 API 错误
|
|
130
|
+
if (content.length < 500) {
|
|
131
|
+
const apiErrorPattern = /^API Error:|^ECONNRESET|^ETIMEDOUT|^ECONNREFUSED|^Unable to connect|^socket hang up/im;
|
|
132
|
+
if (apiErrorPattern.test(content)) {
|
|
133
|
+
reject(new Error(`Claude Code CLI API 错误: ${content.slice(0, 300)}`));
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
resolve({
|
|
138
|
+
content,
|
|
139
|
+
usage: { input_tokens: 0, output_tokens: 0 },
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
});
|
|
21
143
|
});
|
|
22
144
|
}
|
|
23
145
|
}
|
|
@@ -4,8 +4,12 @@ export interface CLIConnectorConfig {
|
|
|
4
4
|
command: string;
|
|
5
5
|
/** 显示名称(用于错误消息) */
|
|
6
6
|
displayName: string;
|
|
7
|
+
/** 安装提示(ENOENT 时显示) */
|
|
8
|
+
installHint?: string;
|
|
7
9
|
/** 构建命令行参数 */
|
|
8
10
|
buildArgs: (fullPrompt: string, config: LLMConfig) => string[];
|
|
11
|
+
/** 构建 stdin 模式的参数(prompt 过长时使用,默认用 buildArgs 替换 prompt 为 '-') */
|
|
12
|
+
buildStdinArgs?: (config: LLMConfig) => string[];
|
|
9
13
|
/** 从 stdout 提取内容(默认 trim) */
|
|
10
14
|
parseOutput?: (stdout: string) => string;
|
|
11
15
|
}
|