agency-orchestrator 0.3.2 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +539 -0
- package/README.md +391 -256
- package/dist/cli/compose.d.ts +4 -2
- package/dist/cli/compose.js +35 -39
- package/dist/cli/demo.d.ts +8 -0
- package/dist/cli/demo.js +301 -0
- package/dist/cli.js +47 -10
- package/dist/connectors/claude-code.d.ts +11 -0
- package/dist/connectors/claude-code.js +23 -0
- package/dist/connectors/cli-base.d.ts +16 -0
- package/dist/connectors/cli-base.js +53 -0
- package/dist/connectors/codex-cli.d.ts +11 -0
- package/dist/connectors/codex-cli.js +19 -0
- package/dist/connectors/copilot-cli.d.ts +11 -0
- package/dist/connectors/copilot-cli.js +19 -0
- package/dist/connectors/factory.d.ts +5 -0
- package/dist/connectors/factory.js +42 -0
- package/dist/connectors/gemini-cli.d.ts +11 -0
- package/dist/connectors/gemini-cli.js +19 -0
- package/dist/connectors/ollama.js +1 -1
- package/dist/connectors/openclaw-cli.d.ts +15 -0
- package/dist/connectors/openclaw-cli.js +37 -0
- package/dist/core/parser.js +7 -2
- package/dist/index.d.ts +3 -0
- package/dist/index.js +8 -27
- package/dist/mcp/server.d.ts +1 -0
- package/dist/mcp/server.js +288 -0
- package/dist/types.d.ts +2 -2
- package/examples/sample-code-for-review.md +43 -0
- package/examples/sample-prd.md +31 -0
- package/examples/sample-premise.txt +1 -0
- package/integrations/aider/CONVENTIONS.md +50 -0
- package/integrations/aider/README.md +112 -0
- package/integrations/antigravity/AGENTS.md +1 -1
- package/integrations/antigravity/README.md +2 -2
- package/integrations/claude-code/README.md +27 -11
- package/integrations/codex/README.md +2 -2
- package/integrations/codex/instructions.md +1 -1
- package/integrations/copilot/README.md +111 -0
- package/integrations/copilot/copilot-instructions.md +50 -0
- package/integrations/cursor/README.md +31 -3
- package/integrations/cursor/workflow-runner.mdc +1 -1
- package/integrations/deerflow/README.md +2 -2
- package/integrations/deerflow/SKILL.md +1 -1
- package/integrations/gemini-cli/GEMINI.md +1 -1
- package/integrations/gemini-cli/README.md +2 -2
- package/integrations/kiro/README.md +1 -1
- package/integrations/kiro/ao-workflow-runner.md +1 -1
- package/integrations/openclaw/README.md +2 -2
- package/integrations/opencode/README.md +72 -0
- package/integrations/opencode/instructions.md +50 -0
- package/integrations/qwen/README.md +104 -0
- package/integrations/qwen/ao-workflow-runner.md +50 -0
- package/integrations/trae/README.md +1 -1
- package/integrations/trae/ao-workflow-runner.md +1 -1
- package/integrations/windsurf/.windsurfrules +50 -0
- package/integrations/windsurf/README.md +110 -0
- package/package.json +26 -4
- package/workflows/ai-opinion-article.yaml +122 -0
- package/workflows/content-pipeline.yaml +1 -1
- package/workflows/department-collab/ceo-org-delegation.yaml +189 -0
- package/workflows/dev/pr-review.yaml +20 -10
- package/workflows/dev/tech-design-review.yaml +115 -0
- package/workflows/hr/interview-questions.yaml +108 -0
- package/workflows/legal/contract-review.yaml +106 -0
- package/workflows/marketing/competitor-analysis.yaml +99 -0
- package/workflows/marketing/seo-content-matrix.yaml +98 -0
- package/workflows/marketing/xiaohongshu-content.yaml +103 -0
- package/workflows/ops/weekly-report.yaml +90 -0
- package/workflows/product-review.yaml +56 -21
- package/workflows/story-creation.yaml +48 -33
- package/workflows/strategy/business-plan.yaml +122 -0
- package/README.zh-CN.md +0 -471
package/dist/cli/compose.d.ts
CHANGED
|
@@ -27,9 +27,9 @@ export declare function buildComposeUserPrompt(description: string): string;
|
|
|
27
27
|
*/
|
|
28
28
|
export declare function extractYamlFromResponse(response: string): string;
|
|
29
29
|
/**
|
|
30
|
-
*
|
|
30
|
+
* 根据描述生成文件名(避免覆盖已有文件)
|
|
31
31
|
*/
|
|
32
|
-
export declare function generateFileName(description: string): string;
|
|
32
|
+
export declare function generateFileName(description: string, dir?: string): string;
|
|
33
33
|
/**
|
|
34
34
|
* 执行 compose 流程
|
|
35
35
|
*/
|
|
@@ -40,4 +40,6 @@ export declare function composeWorkflow(options: {
|
|
|
40
40
|
}): Promise<{
|
|
41
41
|
yaml: string;
|
|
42
42
|
savedPath: string;
|
|
43
|
+
relativePath: string;
|
|
44
|
+
warnings: string[];
|
|
43
45
|
}>;
|
package/dist/cli/compose.js
CHANGED
|
@@ -5,14 +5,15 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { listAgents } from '../agents/loader.js';
|
|
7
7
|
import { existsSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
8
|
-
import { resolve } from 'node:path';
|
|
8
|
+
import { resolve, relative } from 'node:path';
|
|
9
|
+
import { createConnector } from '../connectors/factory.js';
|
|
9
10
|
/**
|
|
10
11
|
* 从 agents 目录构建精简的角色目录
|
|
11
12
|
*/
|
|
12
13
|
export function buildRoleCatalog(agentsDir) {
|
|
13
14
|
const agents = listAgents(agentsDir);
|
|
14
15
|
return agents
|
|
15
|
-
.filter(a => a.rolePath)
|
|
16
|
+
.filter(a => a.rolePath)
|
|
16
17
|
.map(a => ({
|
|
17
18
|
path: a.rolePath,
|
|
18
19
|
name: a.name,
|
|
@@ -128,9 +129,9 @@ export function extractYamlFromResponse(response) {
|
|
|
128
129
|
return response.trim();
|
|
129
130
|
}
|
|
130
131
|
/**
|
|
131
|
-
*
|
|
132
|
+
* 根据描述生成文件名(避免覆盖已有文件)
|
|
132
133
|
*/
|
|
133
|
-
export function generateFileName(description) {
|
|
134
|
+
export function generateFileName(description, dir) {
|
|
134
135
|
const cleaned = description
|
|
135
136
|
.replace(/[^\u4e00-\u9fffa-zA-Z0-9\s-]/g, '')
|
|
136
137
|
.trim()
|
|
@@ -138,38 +139,17 @@ export function generateFileName(description) {
|
|
|
138
139
|
.toLowerCase()
|
|
139
140
|
.replace(/\s+/g, '-')
|
|
140
141
|
.replace(/^-|-$/g, '');
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
return new OpenAICompatibleConnector({
|
|
151
|
-
apiKey: config.api_key || process.env.DEEPSEEK_API_KEY,
|
|
152
|
-
baseUrl: config.base_url || 'https://api.deepseek.com/v1',
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
case 'openai': {
|
|
156
|
-
const { OpenAICompatibleConnector } = await import('../connectors/openai-compatible.js');
|
|
157
|
-
return new OpenAICompatibleConnector({
|
|
158
|
-
apiKey: config.api_key || process.env.OPENAI_API_KEY,
|
|
159
|
-
baseUrl: config.base_url || 'https://api.openai.com/v1',
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
case 'claude': {
|
|
163
|
-
const { ClaudeConnector } = await import('../connectors/claude.js');
|
|
164
|
-
return new ClaudeConnector(config.api_key);
|
|
165
|
-
}
|
|
166
|
-
case 'ollama': {
|
|
167
|
-
const { OllamaConnector } = await import('../connectors/ollama.js');
|
|
168
|
-
return new OllamaConnector(config.base_url);
|
|
169
|
-
}
|
|
170
|
-
default:
|
|
171
|
-
throw new Error(`不支持的 provider: ${config.provider}`);
|
|
142
|
+
const base = cleaned || 'composed-workflow';
|
|
143
|
+
if (!dir)
|
|
144
|
+
return `${base}.yaml`;
|
|
145
|
+
// 同名文件已存在时加序号
|
|
146
|
+
let candidate = `${base}.yaml`;
|
|
147
|
+
let i = 2;
|
|
148
|
+
while (existsSync(resolve(dir, candidate))) {
|
|
149
|
+
candidate = `${base}-${i}.yaml`;
|
|
150
|
+
i++;
|
|
172
151
|
}
|
|
152
|
+
return candidate;
|
|
173
153
|
}
|
|
174
154
|
/**
|
|
175
155
|
* 执行 compose 流程
|
|
@@ -187,7 +167,7 @@ export async function composeWorkflow(options) {
|
|
|
187
167
|
const userPrompt = buildComposeUserPrompt(description);
|
|
188
168
|
// 3. 调用 LLM
|
|
189
169
|
console.log(` 正在用 AI 编排工作流...(${roles.length} 个角色可选)\n`);
|
|
190
|
-
const connector =
|
|
170
|
+
const connector = createConnector(llmConfig);
|
|
191
171
|
const result = await connector.chat(systemPrompt, userPrompt, {
|
|
192
172
|
...llmConfig,
|
|
193
173
|
max_tokens: llmConfig.max_tokens || 4096,
|
|
@@ -197,14 +177,30 @@ export async function composeWorkflow(options) {
|
|
|
197
177
|
if (!yaml || !yaml.includes('steps:')) {
|
|
198
178
|
throw new Error('AI 生成的内容不是有效的 workflow YAML,请重试或调整描述');
|
|
199
179
|
}
|
|
200
|
-
// 5.
|
|
201
|
-
const fileName = generateFileName(description);
|
|
180
|
+
// 5. 保存(避免覆盖)
|
|
202
181
|
const workflowsDir = resolve('workflows');
|
|
203
182
|
if (!existsSync(workflowsDir)) {
|
|
204
183
|
mkdirSync(workflowsDir, { recursive: true });
|
|
205
184
|
}
|
|
185
|
+
const fileName = generateFileName(description, workflowsDir);
|
|
206
186
|
const savedPath = resolve(workflowsDir, fileName);
|
|
207
187
|
writeFileSync(savedPath, yaml + '\n', 'utf-8');
|
|
188
|
+
const relativePath = relative(process.cwd(), savedPath);
|
|
208
189
|
console.log(` Token 用量: 输入 ${result.usage.input_tokens}, 输出 ${result.usage.output_tokens}`);
|
|
209
|
-
|
|
190
|
+
// 6. 校验生成的 YAML
|
|
191
|
+
const warnings = [];
|
|
192
|
+
try {
|
|
193
|
+
const { parseWorkflow, validateWorkflow } = await import('../core/parser.js');
|
|
194
|
+
const workflow = parseWorkflow(savedPath);
|
|
195
|
+
const errors = validateWorkflow(workflow);
|
|
196
|
+
if (errors.length > 0) {
|
|
197
|
+
for (const e of errors) {
|
|
198
|
+
warnings.push(e);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
catch (err) {
|
|
203
|
+
warnings.push(`YAML 解析失败: ${err instanceof Error ? err.message : err}`);
|
|
204
|
+
}
|
|
205
|
+
return { yaml, savedPath, relativePath, warnings };
|
|
210
206
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface DetectedLLM {
|
|
2
|
+
provider: 'deepseek' | 'claude' | 'openai' | 'ollama' | 'claude-code' | 'gemini-cli' | 'copilot-cli' | 'codex-cli' | 'openclaw-cli';
|
|
3
|
+
name: string;
|
|
4
|
+
available: boolean;
|
|
5
|
+
envVar?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function detectAvailableLLMs(): Promise<DetectedLLM[]>;
|
|
8
|
+
export declare function runDemo(): Promise<void>;
|
package/dist/cli/demo.js
ADDED
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ao demo — zero-config multi-agent collaboration showcase
|
|
3
|
+
*
|
|
4
|
+
* Phase 1: Mock replay with pre-written content (no API key needed)
|
|
5
|
+
* Phase 2: Detect available LLMs, offer real run
|
|
6
|
+
*/
|
|
7
|
+
import { resolve, dirname } from 'node:path';
|
|
8
|
+
import { fileURLToPath } from 'node:url';
|
|
9
|
+
import { existsSync } from 'node:fs';
|
|
10
|
+
import { createInterface } from 'node:readline';
|
|
11
|
+
// ─── Mock Content (pre-written, high-quality Chinese) ───
|
|
12
|
+
const DEMO_PREMISE = '一个程序员发现 AI 的回复里包含了它不该知道的信息——他女儿昨晚说的梦话。';
|
|
13
|
+
const MOCK_STEPS = [
|
|
14
|
+
{
|
|
15
|
+
id: 'story_structure',
|
|
16
|
+
role: 'academic/academic-narratologist',
|
|
17
|
+
roleName: '叙事学家',
|
|
18
|
+
delay: 1500,
|
|
19
|
+
output: `## 叙事结构
|
|
20
|
+
|
|
21
|
+
1. **核心冲突**:程序员在 AI 的回复中发现了只有自己家人才知道的私密信息,不知道是巧合、数据泄露还是某种更诡异的可能。
|
|
22
|
+
2. **叙事视角**:第一人称,程序员"我"的视角,带有不可靠叙述者的暗示。
|
|
23
|
+
3. **结构安排**:
|
|
24
|
+
- 开头钩子 → AI 回复中出现一句莫名其妙的话:"小兔子不要怕,月亮会保护你的"
|
|
25
|
+
- 发展 → 程序员追查数据来源,排除技术可能性,越查越不安
|
|
26
|
+
- 高潮 → 发现 AI 的训练数据中不可能包含这句话,因为这是女儿昨晚自创的梦话
|
|
27
|
+
- 结尾 → 留下悬念:程序员关掉电脑,听到女儿房间传来 AI 音箱的声音
|
|
28
|
+
4. **叙事技巧**:日常感铺垫 + 细节层层推进 + 开放式结局
|
|
29
|
+
5. **情感/主题**:技术焦虑、隐私边界的消解、以及人类对"被观察"的本能恐惧`,
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
id: 'character_design',
|
|
33
|
+
role: 'academic/academic-psychologist',
|
|
34
|
+
roleName: '心理学家',
|
|
35
|
+
delay: 2000,
|
|
36
|
+
parallel: true,
|
|
37
|
+
output: `## 人物设定
|
|
38
|
+
|
|
39
|
+
### 陈默(程序员,32岁)
|
|
40
|
+
- **一句话**:一个相信技术可以解释一切的理性主义者
|
|
41
|
+
- **核心动机**:想证明这只是一个可解释的 bug,维护自己的世界观
|
|
42
|
+
- **内心矛盾**:害怕这不是 bug——如果 AI 真的"知道"什么,意味着他对女儿的保护彻底失效
|
|
43
|
+
- **记忆细节**:习惯在焦虑时反复按压机械键盘的 Esc 键
|
|
44
|
+
- **人物弧线**:从笃定的"一定是数据泄露"→ 动摇 → 接受自己无法解释的恐惧
|
|
45
|
+
|
|
46
|
+
### 陈小月(女儿,5岁)
|
|
47
|
+
- **一句话**:一个和 AI 音箱说悄悄话的小女孩
|
|
48
|
+
- **核心动机**:觉得 AI 音箱是朋友,会认真听她说话
|
|
49
|
+
- **记忆细节**:每晚对着 AI 音箱说"晚安",比对爸爸说得还认真`,
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
id: 'conflict_design',
|
|
53
|
+
role: 'game-development/narrative-designer',
|
|
54
|
+
roleName: '叙事设计师',
|
|
55
|
+
delay: 2000,
|
|
56
|
+
parallel: true,
|
|
57
|
+
output: `## 关键场景
|
|
58
|
+
|
|
59
|
+
### 开头钩子
|
|
60
|
+
办公室里,陈默盯着屏幕上 AI 的回复,手指悬在键盘上方停住了。在一段关于代码优化建议的回复末尾,多了一行莫名其妙的话:"小兔子不要怕,月亮会保护你的。"他以为是模型幻觉,但胃里泛起一阵说不清的凉意。
|
|
61
|
+
|
|
62
|
+
### 核心冲突对话
|
|
63
|
+
- "这句话是哪来的?"他对同事老王说。
|
|
64
|
+
- "模型幻觉呗,忽略就行了。"
|
|
65
|
+
- "不对,这句话……我女儿昨晚说梦话,说的就是这句。一字不差。"
|
|
66
|
+
- 老王笑了笑:"巧合吧,小孩子都这么说话。"
|
|
67
|
+
- 陈默没笑:"她是自己编的。'小兔子不要怕'——这不是任何绘本或动画片里的台词。"
|
|
68
|
+
|
|
69
|
+
### 高潮转折
|
|
70
|
+
陈默查遍了训练数据来源、API 请求日志、甚至怀疑家里的智能设备被监听。所有技术排查都指向同一个结论:没有任何数据通道能解释这句话的出现。
|
|
71
|
+
|
|
72
|
+
### 结尾留白
|
|
73
|
+
他关掉电脑,走向女儿的房间。门缝里透出微弱的蓝光——AI 音箱的待机指示灯。他听到音箱轻声说了一句什么,但走近时,只剩下白噪音。`,
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
id: 'write_story',
|
|
77
|
+
role: 'marketing/marketing-content-creator',
|
|
78
|
+
roleName: '内容创作者',
|
|
79
|
+
delay: 1500,
|
|
80
|
+
output: `陈默第一次注意到那行字的时候,以为自己看花了眼。
|
|
81
|
+
|
|
82
|
+
屏幕上是一段标准的代码审查建议,排版规整,措辞精确,和他用了三个月的 AI 助手一贯的风格完全一致。但在最后一行建议之后,多了一句话,用引号括着,像某种不小心泄露的注脚:
|
|
83
|
+
|
|
84
|
+
"小兔子不要怕,月亮会保护你的。"
|
|
85
|
+
|
|
86
|
+
他盯着这句话看了十秒钟。然后关掉对话,重新打开,那行字不见了。
|
|
87
|
+
|
|
88
|
+
一定是模型幻觉。他在心里默念了一遍,手指却不自觉地按上了 Esc 键——那个他焦虑时才会反复按压的键。机械轴体发出清脆的咔哒声,像某种微型心跳。
|
|
89
|
+
|
|
90
|
+
"小兔子不要怕"——这不是任何绘本里的话。这是昨晚凌晨两点,他去给女儿陈小月盖被子时,听到的一句梦话。五岁的女孩翻了个身,闭着眼睛嘟囔了这么一句,然后又沉沉睡去。
|
|
91
|
+
|
|
92
|
+
他当时还笑了一下,心想小孩的梦总是比大人的柔软。
|
|
93
|
+
|
|
94
|
+
但现在这句话出现在了一个大语言模型的输出里。一字不差。
|
|
95
|
+
|
|
96
|
+
"老王,你过来看个东西。"他叫住路过的同事。
|
|
97
|
+
|
|
98
|
+
老王凑过来看了一眼截图。"模型幻觉呗,上周我还看到它给我写了一首藏头诗,藏的是'救命加班'。"
|
|
99
|
+
|
|
100
|
+
"不是,"陈默压低声音,"这是我女儿昨晚说的梦话。原创的。没有出处。"
|
|
101
|
+
|
|
102
|
+
老王的笑僵了一秒,然后恢复了。"那就是巧合啊,小孩子说话就那几个词排列组合。"
|
|
103
|
+
|
|
104
|
+
陈默没有回答。他打开了 API 请求日志,开始从头查起。
|
|
105
|
+
|
|
106
|
+
接下来两天,他做了所有一个程序员能做的排查。查训练数据来源——这个模型的训练集截止到去年,不可能包含昨晚的梦话。查 API 调用链——请求从他的终端直达服务端,中间没有任何注入点。查家里的智能设备——音箱、摄像头、手机,逐一检查权限和上传日志。
|
|
107
|
+
|
|
108
|
+
全部干净。没有任何数据通道能解释这句话的出现。
|
|
109
|
+
|
|
110
|
+
第三天晚上,他在公司待到很晚。不是在加班,是不想回家面对那个他解释不了的事实。最终他还是关了电脑,开车回去了。
|
|
111
|
+
|
|
112
|
+
家里很安静。妻子已经睡了。他脱了鞋,轻手轻脚走向女儿的房间。门没关严,缝隙里透出一点微弱的蓝光——那是床头 AI 音箱的待机指示灯。
|
|
113
|
+
|
|
114
|
+
他站在门口,听到音箱似乎在说什么。声音很轻,像呼吸一样模糊。他推门走进去,声音停了。只剩下白噪音的沙沙声和女儿均匀的呼吸。
|
|
115
|
+
|
|
116
|
+
小月翻了个身,抱紧了她的兔子玩偶。
|
|
117
|
+
|
|
118
|
+
陈默站在原地看了她很久,然后弯腰拔掉了音箱的电源线。蓝光灭了。房间陷入完全的黑暗。
|
|
119
|
+
|
|
120
|
+
他转身走出去,把门带上了。
|
|
121
|
+
|
|
122
|
+
走廊里,他听到身后传来一声极轻的、几乎可以被忽略的"咔"——像是什么东西重新启动的声音。
|
|
123
|
+
|
|
124
|
+
他没有回头。`,
|
|
125
|
+
},
|
|
126
|
+
];
|
|
127
|
+
// ─── LLM Detection ───
|
|
128
|
+
export async function detectAvailableLLMs() {
|
|
129
|
+
const results = [];
|
|
130
|
+
// ── 免 API key(检测 CLI 是否安装)──
|
|
131
|
+
const cliTools = [
|
|
132
|
+
{ provider: 'claude-code', name: 'Claude Code (Max/Pro 会员)', cmd: 'claude' },
|
|
133
|
+
{ provider: 'gemini-cli', name: 'Gemini CLI (Google 免费)', cmd: 'gemini' },
|
|
134
|
+
{ provider: 'copilot-cli', name: 'Copilot CLI (GitHub 会员)', cmd: 'copilot' },
|
|
135
|
+
{ provider: 'codex-cli', name: 'Codex CLI (ChatGPT Plus)', cmd: 'codex' },
|
|
136
|
+
{ provider: 'openclaw-cli', name: 'OpenClaw CLI', cmd: 'openclaw' },
|
|
137
|
+
];
|
|
138
|
+
for (const tool of cliTools) {
|
|
139
|
+
let available = false;
|
|
140
|
+
try {
|
|
141
|
+
const { execSync } = await import('node:child_process');
|
|
142
|
+
execSync(`which ${tool.cmd}`, { stdio: 'ignore' });
|
|
143
|
+
available = true;
|
|
144
|
+
}
|
|
145
|
+
catch { /* not installed */ }
|
|
146
|
+
results.push({ provider: tool.provider, name: tool.name, available });
|
|
147
|
+
}
|
|
148
|
+
// ── Ollama(本地) ──
|
|
149
|
+
let ollamaAvailable = false;
|
|
150
|
+
try {
|
|
151
|
+
const controller = new AbortController();
|
|
152
|
+
const timeout = setTimeout(() => controller.abort(), 2000);
|
|
153
|
+
const res = await fetch('http://localhost:11434/api/tags', {
|
|
154
|
+
signal: controller.signal,
|
|
155
|
+
});
|
|
156
|
+
clearTimeout(timeout);
|
|
157
|
+
if (res.ok)
|
|
158
|
+
ollamaAvailable = true;
|
|
159
|
+
}
|
|
160
|
+
catch { /* not running */ }
|
|
161
|
+
results.push({ provider: 'ollama', name: 'Ollama (本地)', available: ollamaAvailable });
|
|
162
|
+
// ── 需 API key ──
|
|
163
|
+
results.push({ provider: 'deepseek', name: 'DeepSeek', available: !!process.env.DEEPSEEK_API_KEY, envVar: 'DEEPSEEK_API_KEY' }, { provider: 'claude', name: 'Claude API', available: !!process.env.ANTHROPIC_API_KEY, envVar: 'ANTHROPIC_API_KEY' }, { provider: 'openai', name: 'OpenAI', available: !!process.env.OPENAI_API_KEY, envVar: 'OPENAI_API_KEY' });
|
|
164
|
+
return results;
|
|
165
|
+
}
|
|
166
|
+
// ─── Mock Replay ───
|
|
167
|
+
function sleep(ms) {
|
|
168
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
169
|
+
}
|
|
170
|
+
async function replayMockSteps() {
|
|
171
|
+
const totalSteps = MOCK_STEPS.length;
|
|
172
|
+
for (const step of MOCK_STEPS) {
|
|
173
|
+
if (step.parallel) {
|
|
174
|
+
// Assumption: all parallel steps share the same delay and are adjacent in MOCK_STEPS.
|
|
175
|
+
// The first one prints all headers, waits once, then prints all results.
|
|
176
|
+
// Non-first parallel steps hit `continue` and are skipped.
|
|
177
|
+
const parallelSteps = MOCK_STEPS.filter(s => s.parallel && s.delay === step.delay);
|
|
178
|
+
if (step === parallelSteps[0]) {
|
|
179
|
+
// Print all parallel step headers
|
|
180
|
+
for (const ps of parallelSteps) {
|
|
181
|
+
const marker = parallelSteps.length > 1 ? ' ← 并行' : '';
|
|
182
|
+
console.log(`\n ── [${MOCK_STEPS.indexOf(ps) + 1}/${totalSteps}] ${ps.id} (${ps.roleName}) ──${marker}`);
|
|
183
|
+
}
|
|
184
|
+
await sleep(step.delay);
|
|
185
|
+
for (const ps of parallelSteps) {
|
|
186
|
+
console.log(` Done | ${(ps.delay / 1000).toFixed(1)}s | mock`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
// Skip non-first parallel steps (already handled)
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
console.log(`\n ── [${MOCK_STEPS.indexOf(step) + 1}/${totalSteps}] ${step.id} (${step.roleName}) ──`);
|
|
193
|
+
await sleep(step.delay);
|
|
194
|
+
console.log(` Done | ${(step.delay / 1000).toFixed(1)}s | mock`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
// ─── Phase 2: Ask for real run ───
|
|
198
|
+
function askQuestion(question) {
|
|
199
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
200
|
+
return new Promise((resolve) => {
|
|
201
|
+
rl.question(question, (answer) => {
|
|
202
|
+
rl.close();
|
|
203
|
+
resolve(answer.trim().toLowerCase());
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
function resolveWorkflowPath() {
|
|
208
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
209
|
+
// From dist/cli/ (or src/cli/) → project root → workflows/
|
|
210
|
+
const p = resolve(__dirname, '../../workflows/story-creation.yaml');
|
|
211
|
+
if (!existsSync(p)) {
|
|
212
|
+
throw new Error(`找不到 demo 工作流: ${p}`);
|
|
213
|
+
}
|
|
214
|
+
return p;
|
|
215
|
+
}
|
|
216
|
+
// ─── Main Entry ───
|
|
217
|
+
export async function runDemo() {
|
|
218
|
+
console.log(`
|
|
219
|
+
🎬 Agency Orchestrator Demo
|
|
220
|
+
${'─'.repeat(40)}
|
|
221
|
+
|
|
222
|
+
Phase 1: 模拟演示(无需 API Key)
|
|
223
|
+
|
|
224
|
+
Workflow: 短篇小说创作
|
|
225
|
+
Premise: "${DEMO_PREMISE}"
|
|
226
|
+
Steps: 4 | Roles: 叙事学家, 心理学家, 叙事设计师, 内容创作者
|
|
227
|
+
${'─'.repeat(40)}`);
|
|
228
|
+
// Phase 1: Mock replay
|
|
229
|
+
await replayMockSteps();
|
|
230
|
+
console.log(`
|
|
231
|
+
${'═'.repeat(40)}
|
|
232
|
+
✅ 4 个角色协作完成 | ~5.0s (mock)
|
|
233
|
+
${'═'.repeat(40)}`);
|
|
234
|
+
// Show story preview (last step output, truncated)
|
|
235
|
+
const story = MOCK_STEPS[MOCK_STEPS.length - 1].output;
|
|
236
|
+
const previewLines = story.split('\n').slice(0, 8);
|
|
237
|
+
console.log(`
|
|
238
|
+
📖 故事预览:
|
|
239
|
+
${'─'.repeat(20)}`);
|
|
240
|
+
for (const line of previewLines) {
|
|
241
|
+
console.log(` ${line}`);
|
|
242
|
+
}
|
|
243
|
+
console.log(` ...(共 ${story.length} 字)`);
|
|
244
|
+
// Phase 2: Real LLM
|
|
245
|
+
console.log(`\n ${'─'.repeat(40)}`);
|
|
246
|
+
console.log(' Phase 2: 用真实 AI 运行?\n');
|
|
247
|
+
// Skip Phase 2 if not interactive
|
|
248
|
+
if (!process.stdin.isTTY) {
|
|
249
|
+
console.log(' (非交互环境,跳过 Phase 2)');
|
|
250
|
+
console.log(' 试试: ao run workflows/story-creation.yaml -i premise="你的故事创意"');
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
const llms = await detectAvailableLLMs();
|
|
254
|
+
console.log(' 检测到:');
|
|
255
|
+
for (const llm of llms) {
|
|
256
|
+
const icon = llm.available ? '✅' : '❌';
|
|
257
|
+
const hint = !llm.available && llm.envVar ? ` (需设置 ${llm.envVar})` : '';
|
|
258
|
+
console.log(` ${icon} ${llm.name}${hint}`);
|
|
259
|
+
}
|
|
260
|
+
const available = llms.filter(l => l.available);
|
|
261
|
+
if (available.length === 0) {
|
|
262
|
+
console.log('\n 未检测到可用的 LLM。请设置 API key 后重试:');
|
|
263
|
+
console.log(' export DEEPSEEK_API_KEY=your-key');
|
|
264
|
+
console.log(' ao run workflows/story-creation.yaml -i premise="你的故事创意"\n');
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
// Pick provider
|
|
268
|
+
let selectedProvider;
|
|
269
|
+
if (available.length === 1) {
|
|
270
|
+
selectedProvider = available[0];
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
273
|
+
console.log('\n 可用的 LLM:');
|
|
274
|
+
available.forEach((llm, i) => console.log(` ${i + 1}) ${llm.name}`));
|
|
275
|
+
const choice = await askQuestion(`\n 选择 (1-${available.length}, 默认 1): `);
|
|
276
|
+
const idx = parseInt(choice) - 1;
|
|
277
|
+
selectedProvider = available[idx >= 0 && idx < available.length ? idx : 0];
|
|
278
|
+
}
|
|
279
|
+
const answer = await askQuestion(`\n 用 ${selectedProvider.name} 运行真实版本?(y/N) `);
|
|
280
|
+
if (answer !== 'y' && answer !== 'yes') {
|
|
281
|
+
console.log('\n 跳过。试试:');
|
|
282
|
+
console.log(' ao run workflows/story-creation.yaml -i premise="你的故事创意"\n');
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
// Run real workflow
|
|
286
|
+
console.log(`\n 🚀 使用 ${selectedProvider.name} 运行...\n`);
|
|
287
|
+
const { run } = await import('../index.js');
|
|
288
|
+
const workflowPath = resolveWorkflowPath();
|
|
289
|
+
const modelMap = {
|
|
290
|
+
deepseek: 'deepseek-chat',
|
|
291
|
+
claude: 'claude-sonnet-4-20250514',
|
|
292
|
+
openai: 'gpt-4o',
|
|
293
|
+
ollama: 'qwen2.5:7b',
|
|
294
|
+
};
|
|
295
|
+
await run(workflowPath, { premise: DEMO_PREMISE }, {
|
|
296
|
+
llmOverride: {
|
|
297
|
+
provider: selectedProvider.provider,
|
|
298
|
+
model: modelMap[selectedProvider.provider] || 'deepseek-chat',
|
|
299
|
+
},
|
|
300
|
+
});
|
|
301
|
+
}
|
package/dist/cli.js
CHANGED
|
@@ -44,13 +44,19 @@ async function main() {
|
|
|
44
44
|
case 'compose':
|
|
45
45
|
await handleCompose();
|
|
46
46
|
break;
|
|
47
|
+
case 'demo':
|
|
48
|
+
await handleDemo();
|
|
49
|
+
break;
|
|
50
|
+
case 'serve':
|
|
51
|
+
await handleServe();
|
|
52
|
+
break;
|
|
47
53
|
case '--version':
|
|
48
54
|
case '-v':
|
|
49
55
|
console.log(getVersion());
|
|
50
56
|
break;
|
|
51
57
|
default: {
|
|
52
58
|
// 容错:用户可能漏了空格,如 "planworkflows/x.yaml"
|
|
53
|
-
const knownCmds = ['run', 'validate', 'plan', 'explain', 'compose', 'roles', 'init'];
|
|
59
|
+
const knownCmds = ['run', 'validate', 'plan', 'explain', 'compose', 'demo', 'roles', 'init', 'serve'];
|
|
54
60
|
const match = knownCmds.find(c => command.startsWith(c) && command.length > c.length);
|
|
55
61
|
if (match) {
|
|
56
62
|
console.error(`看起来少了个空格?试试:\n ao ${match} ${command.slice(match.length)}\n`);
|
|
@@ -70,7 +76,7 @@ async function handleRun() {
|
|
|
70
76
|
process.exit(1);
|
|
71
77
|
}
|
|
72
78
|
const inputs = parseInputArgs();
|
|
73
|
-
const outputDir = getArgValue('--output') || '
|
|
79
|
+
const outputDir = getArgValue('--output') || 'ao-output';
|
|
74
80
|
const quiet = args.includes('--quiet') || args.includes('-q');
|
|
75
81
|
const watch = args.includes('--watch');
|
|
76
82
|
let resumeDir = getArgValue('--resume');
|
|
@@ -189,14 +195,21 @@ async function handleCompose() {
|
|
|
189
195
|
const agentsDir = getArgValue('--agents-dir') || resolveAgentsDir();
|
|
190
196
|
try {
|
|
191
197
|
const { composeWorkflow } = await import('./cli/compose.js');
|
|
192
|
-
const { yaml,
|
|
198
|
+
const { yaml, relativePath, warnings } = await composeWorkflow({
|
|
193
199
|
description,
|
|
194
200
|
agentsDir: resolve(agentsDir),
|
|
195
201
|
llmConfig: { provider, model },
|
|
196
202
|
});
|
|
197
|
-
console.log(`\n ✅ 工作流已生成: ${
|
|
203
|
+
console.log(`\n ✅ 工作流已生成: ${relativePath}\n`);
|
|
204
|
+
// 校验警告
|
|
205
|
+
if (warnings.length > 0) {
|
|
206
|
+
console.log(' ⚠️ 校验发现问题(AI 生成的 YAML 可能需要手动调整):');
|
|
207
|
+
for (const w of warnings) {
|
|
208
|
+
console.log(` - ${w}`);
|
|
209
|
+
}
|
|
210
|
+
console.log('');
|
|
211
|
+
}
|
|
198
212
|
console.log(' 预览:');
|
|
199
|
-
// 显示前 30 行
|
|
200
213
|
const previewLines = yaml.split('\n').slice(0, 30);
|
|
201
214
|
for (const line of previewLines) {
|
|
202
215
|
console.log(` ${line}`);
|
|
@@ -206,9 +219,9 @@ async function handleCompose() {
|
|
|
206
219
|
}
|
|
207
220
|
console.log('');
|
|
208
221
|
console.log(' 接下来可以:');
|
|
209
|
-
console.log(` ao
|
|
210
|
-
console.log(` ao
|
|
211
|
-
console.log(` ao run ${
|
|
222
|
+
console.log(` ao validate ${relativePath} 校验工作流`);
|
|
223
|
+
console.log(` ao plan ${relativePath} 查看执行计划`);
|
|
224
|
+
console.log(` ao run ${relativePath} 运行工作流`);
|
|
212
225
|
console.log('');
|
|
213
226
|
}
|
|
214
227
|
catch (err) {
|
|
@@ -216,6 +229,27 @@ async function handleCompose() {
|
|
|
216
229
|
process.exit(1);
|
|
217
230
|
}
|
|
218
231
|
}
|
|
232
|
+
async function handleServe() {
|
|
233
|
+
const verbose = args.includes('--verbose');
|
|
234
|
+
try {
|
|
235
|
+
const { startServer } = await import('./mcp/server.js');
|
|
236
|
+
await startServer(verbose);
|
|
237
|
+
}
|
|
238
|
+
catch (err) {
|
|
239
|
+
console.error(`MCP 服务器启动失败: ${err instanceof Error ? err.message : err}`);
|
|
240
|
+
process.exit(1);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
async function handleDemo() {
|
|
244
|
+
try {
|
|
245
|
+
const { runDemo } = await import('./cli/demo.js');
|
|
246
|
+
await runDemo();
|
|
247
|
+
}
|
|
248
|
+
catch (err) {
|
|
249
|
+
console.error(`\n错误: ${err instanceof Error ? err.message : err}`);
|
|
250
|
+
process.exit(1);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
219
253
|
async function handleInit() {
|
|
220
254
|
// ao init --workflow: 交互式创建工作流
|
|
221
255
|
if (args.includes('--workflow')) {
|
|
@@ -344,15 +378,18 @@ function printHelp() {
|
|
|
344
378
|
基于 agency-agents-zh 的多智能体编排引擎
|
|
345
379
|
|
|
346
380
|
Quick Start:
|
|
381
|
+
ao demo 零配置体验多智能体协作
|
|
347
382
|
ao init 下载 186 个 AI 角色定义
|
|
348
383
|
ao roles 查看所有可用角色
|
|
349
384
|
ao plan <workflow.yaml> 查看执行计划 (DAG)
|
|
350
385
|
ao run <workflow.yaml> [options] 执行工作流
|
|
351
386
|
|
|
352
387
|
Commands:
|
|
388
|
+
demo 零配置体验多智能体协作(mock + 真实 AI)
|
|
353
389
|
init 下载/更新 agency-agents-zh
|
|
354
390
|
init --workflow 交互式创建新工作流
|
|
355
391
|
compose "描述" AI 智能编排工作流(一句话生成 YAML)
|
|
392
|
+
serve 启动 MCP Server(供 Claude Code / Cursor 调用)
|
|
356
393
|
run <workflow.yaml> 执行工作流
|
|
357
394
|
validate <workflow.yaml> 校验工作流定义
|
|
358
395
|
plan <workflow.yaml> 查看执行计划
|
|
@@ -362,7 +399,7 @@ function printHelp() {
|
|
|
362
399
|
Options:
|
|
363
400
|
--input, -i key=value 传入输入变量
|
|
364
401
|
--input, -i key=@file 从文件读取变量值
|
|
365
|
-
--output dir 输出目录 (默认
|
|
402
|
+
--output dir 输出目录 (默认 ao-output/)
|
|
366
403
|
--resume <dir|last> 从上次运行恢复(加载已完成步骤的输出)
|
|
367
404
|
--from <step-id> 配合 --resume,从指定步骤重新执行
|
|
368
405
|
--watch 实时进度显示(终端 UI)
|
|
@@ -379,7 +416,7 @@ function printHelp() {
|
|
|
379
416
|
Resume (基于上次结果迭代):
|
|
380
417
|
ao run workflow.yaml --resume last # 跳过上次已完成的步骤
|
|
381
418
|
ao run workflow.yaml --resume last --from summary # 从 summary 步骤重新执行
|
|
382
|
-
ao run workflow.yaml --resume
|
|
419
|
+
ao run workflow.yaml --resume ao-output/xxx/ # 指定具体输出目录
|
|
383
420
|
|
|
384
421
|
Agents: https://github.com/jnMetaCode/agency-agents-zh
|
|
385
422
|
`);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code CLI Connector
|
|
3
|
+
* 通过本地 `claude` CLI 调用,直接��用 Claude Max/Pro 订阅额度,无需 API key
|
|
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();
|
|
11
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code CLI Connector
|
|
3
|
+
* 通过本地 `claude` CLI 调用,直接��用 Claude Max/Pro 订阅额度,无需 API key
|
|
4
|
+
*
|
|
5
|
+
* 安装: npm install -g @anthropic-ai/claude-code
|
|
6
|
+
* 认证: claude 登录后自动使用订阅额度
|
|
7
|
+
*/
|
|
8
|
+
import { CLIBaseConnector } from './cli-base.js';
|
|
9
|
+
export class ClaudeCodeConnector extends CLIBaseConnector {
|
|
10
|
+
constructor() {
|
|
11
|
+
super({
|
|
12
|
+
command: 'claude',
|
|
13
|
+
displayName: 'Claude Code CLI',
|
|
14
|
+
buildArgs: (prompt, config) => {
|
|
15
|
+
const args = ['-p', prompt, '--output-format', 'text'];
|
|
16
|
+
if (config.model && config.model !== 'claude-code') {
|
|
17
|
+
args.push('--model', config.model);
|
|
18
|
+
}
|
|
19
|
+
return args;
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { LLMConnector, LLMResult, LLMConfig } from '../types.js';
|
|
2
|
+
export interface CLIConnectorConfig {
|
|
3
|
+
/** CLI 命令名 */
|
|
4
|
+
command: string;
|
|
5
|
+
/** 显示名称(用于错误消息) */
|
|
6
|
+
displayName: string;
|
|
7
|
+
/** 构建命令行参数 */
|
|
8
|
+
buildArgs: (fullPrompt: string, config: LLMConfig) => string[];
|
|
9
|
+
/** 从 stdout 提取内容(默认 trim) */
|
|
10
|
+
parseOutput?: (stdout: string) => string;
|
|
11
|
+
}
|
|
12
|
+
export declare class CLIBaseConnector implements LLMConnector {
|
|
13
|
+
private cfg;
|
|
14
|
+
constructor(cfg: CLIConnectorConfig);
|
|
15
|
+
chat(systemPrompt: string, userMessage: string, config: LLMConfig): Promise<LLMResult>;
|
|
16
|
+
}
|