agency-orchestrator 0.6.14 → 0.6.16

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/dist/cli/demo.js CHANGED
@@ -1,129 +1,18 @@
1
1
  /**
2
2
  * ao demo — zero-config multi-agent collaboration showcase
3
3
  *
4
- * Phase 1: Mock replay with pre-written content (no API key needed)
5
- * Phase 2: Detect available LLMs, offer real run
4
+ * 设计原则(v0.6.16 起):
5
+ * - 检测到可用 LLM 直接真跑 story-creation 工作流(最强体验,零废话)
6
+ * - 没检测到 → 显示 DAG 结构 + 行动指引(不再放预录 mock,避免误导期望)
7
+ *
8
+ * 之前的 mock 回放在用户配置 LLM 之前展示精修过的内容,让用户对真跑的输出
9
+ * 期望被错误抬高;同时占用注意力 5 秒后还要再问 y/n 真跑,链路太长。
6
10
  */
7
11
  import { resolve, dirname } from 'node:path';
8
12
  import { fileURLToPath } from 'node:url';
9
13
  import { existsSync } from 'node:fs';
10
14
  import { createInterface } from 'node:readline';
11
- // ─── Mock Content (pre-written, high-quality Chinese) ───
12
15
  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
16
  // ─── LLM Detection ───
128
17
  export async function detectAvailableLLMs() {
129
18
  const results = [];
@@ -165,38 +54,7 @@ export async function detectAvailableLLMs() {
165
54
  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' });
166
55
  return results;
167
56
  }
168
- // ─── Mock Replay ───
169
- function sleep(ms) {
170
- return new Promise(resolve => setTimeout(resolve, ms));
171
- }
172
- async function replayMockSteps() {
173
- const totalSteps = MOCK_STEPS.length;
174
- for (const step of MOCK_STEPS) {
175
- if (step.parallel) {
176
- // Assumption: all parallel steps share the same delay and are adjacent in MOCK_STEPS.
177
- // The first one prints all headers, waits once, then prints all results.
178
- // Non-first parallel steps hit `continue` and are skipped.
179
- const parallelSteps = MOCK_STEPS.filter(s => s.parallel && s.delay === step.delay);
180
- if (step === parallelSteps[0]) {
181
- // Print all parallel step headers
182
- for (const ps of parallelSteps) {
183
- const marker = parallelSteps.length > 1 ? ' ← 并行' : '';
184
- console.log(`\n ── [${MOCK_STEPS.indexOf(ps) + 1}/${totalSteps}] ${ps.id} (${ps.roleName}) ──${marker}`);
185
- }
186
- await sleep(step.delay);
187
- for (const ps of parallelSteps) {
188
- console.log(` Done | ${(ps.delay / 1000).toFixed(1)}s | mock`);
189
- }
190
- }
191
- // Skip non-first parallel steps (already handled)
192
- continue;
193
- }
194
- console.log(`\n ── [${MOCK_STEPS.indexOf(step) + 1}/${totalSteps}] ${step.id} (${step.roleName}) ──`);
195
- await sleep(step.delay);
196
- console.log(` Done | ${(step.delay / 1000).toFixed(1)}s | mock`);
197
- }
198
- }
199
- // ─── Phase 2: Ask for real run ───
57
+ // ─── Helpers ───
200
58
  function askQuestion(question) {
201
59
  const rl = createInterface({ input: process.stdin, output: process.stdout });
202
60
  return new Promise((resolve) => {
@@ -215,77 +73,78 @@ function resolveWorkflowPath() {
215
73
  }
216
74
  return p;
217
75
  }
76
+ /** 没检测到 LLM 时:展示 DAG + 行动指引 */
77
+ async function showDagAndGuide() {
78
+ console.log(' ❌ 没检测到可用 LLM。先看一下 ao 工作流的 DAG 结构:\n');
79
+ // 用 ao 自身的 plan 能力展示 DAG(确保展示逻辑和 ao plan 命令一致)
80
+ try {
81
+ const { parseWorkflow } = await import('../core/parser.js');
82
+ const { buildDAG, formatDAG } = await import('../core/dag.js');
83
+ const wf = parseWorkflow(resolveWorkflowPath());
84
+ const dag = buildDAG(wf);
85
+ const dagText = formatDAG(dag);
86
+ // 缩进对齐 demo 输出风格
87
+ for (const line of dagText.split('\n')) {
88
+ console.log(` ${line}`);
89
+ }
90
+ console.log('\n 4 个 AI 角色协作完成短篇小说创作(第二层并行)。\n');
91
+ }
92
+ catch (err) {
93
+ // DAG 渲染失败兜底,给一个文字描述
94
+ console.log(' Level 1: 叙事学家(搭故事结构)');
95
+ console.log(' Level 2: 心理学家 + 叙事设计师(并行:人物 + 关键场景)');
96
+ console.log(' Level 3: 内容创作者(基于以上整合,写完整短篇)\n');
97
+ }
98
+ console.log(' 最快上手(任选一个):');
99
+ console.log('');
100
+ console.log(' Claude Code 订阅:');
101
+ console.log(' 装 Claude Code CLI 后重跑 ao demo 即可(零配置)');
102
+ console.log('');
103
+ console.log(' DeepSeek API(最便宜稳):');
104
+ console.log(' 在 https://platform.deepseek.com 申请 key(10 块够跑很久)');
105
+ console.log(' export DEEPSEEK_API_KEY=sk-...');
106
+ console.log(' ao demo');
107
+ console.log('');
108
+ console.log(' 本地 Ollama:');
109
+ console.log(' ollama serve && ollama pull qwen2.5:7b');
110
+ console.log(' ao demo');
111
+ console.log('');
112
+ console.log(' 完整文档: https://github.com/jnMetaCode/agency-orchestrator');
113
+ }
218
114
  // ─── Main Entry ───
219
115
  export async function runDemo() {
220
116
  console.log(`
221
117
  🎬 Agency Orchestrator Demo
222
118
  ${'─'.repeat(40)}
223
-
224
- Phase 1: 模拟演示(无需 API Key)
225
-
226
- Workflow: 短篇小说创作
119
+ Workflow: 短篇小说创作(4 个 AI 角色协作)
227
120
  Premise: "${DEMO_PREMISE}"
228
- Steps: 4 | Roles: 叙事学家, 心理学家, 叙事设计师, 内容创作者
229
- ${'─'.repeat(40)}`);
230
- // Phase 1: Mock replay
231
- await replayMockSteps();
232
- console.log(`
233
- ${'═'.repeat(40)}
234
- ✅ 4 个角色协作完成 | ~5.0s (mock)
235
- ${'═'.repeat(40)}`);
236
- // Show story preview (last step output, truncated)
237
- const story = MOCK_STEPS[MOCK_STEPS.length - 1].output;
238
- const previewLines = story.split('\n').slice(0, 8);
239
- console.log(`
240
- 📖 故事预览:
241
- ${'─'.repeat(20)}`);
242
- for (const line of previewLines) {
243
- console.log(` ${line}`);
244
- }
245
- console.log(` ...(共 ${story.length} 字)`);
246
- // Phase 2: Real LLM
247
- console.log(`\n ${'─'.repeat(40)}`);
248
- console.log(' Phase 2: 用真实 AI 运行?\n');
249
- // Skip Phase 2 if not interactive
250
- if (!process.stdin.isTTY) {
251
- console.log(' (非交互环境,跳过 Phase 2)');
252
- console.log(' 试试: ao run workflows/story-creation.yaml -i premise="你的故事创意"');
253
- return;
254
- }
121
+ ${'─'.repeat(40)}
122
+ `);
255
123
  const llms = await detectAvailableLLMs();
256
- console.log(' 检测到:');
257
- for (const llm of llms) {
258
- const icon = llm.available ? '✅' : '❌';
259
- const hint = !llm.available && llm.envVar ? ` (需设置 ${llm.envVar})` : '';
260
- console.log(` ${icon} ${llm.name}${hint}`);
261
- }
262
124
  const available = llms.filter(l => l.available);
125
+ // 没 LLM → DAG + 行动指引
263
126
  if (available.length === 0) {
264
- console.log('\n 未检测到可用的 LLM。请设置 API key 后重试:');
265
- console.log(' export DEEPSEEK_API_KEY=your-key');
266
- console.log(' ao run workflows/story-creation.yaml -i premise="你的故事创意"\n');
127
+ await showDagAndGuide();
267
128
  return;
268
129
  }
269
- // Pick provider
130
+ // LLM → 选 provider + 真跑
131
+ console.log(' ✅ 检测到可用 LLM:');
132
+ for (const llm of available) {
133
+ console.log(` • ${llm.name}`);
134
+ }
135
+ console.log('');
270
136
  let selectedProvider;
271
- if (available.length === 1) {
137
+ if (available.length === 1 || !process.stdin.isTTY) {
272
138
  selectedProvider = available[0];
273
139
  }
274
140
  else {
275
- console.log('\n 可用的 LLM:');
141
+ console.log(' 可用列表:');
276
142
  available.forEach((llm, i) => console.log(` ${i + 1}) ${llm.name}`));
277
143
  const choice = await askQuestion(`\n 选择 (1-${available.length}, 默认 1): `);
278
144
  const idx = parseInt(choice) - 1;
279
145
  selectedProvider = available[idx >= 0 && idx < available.length ? idx : 0];
280
146
  }
281
- const answer = await askQuestion(`\n 用 ${selectedProvider.name} 运行真实版本?(y/N) `);
282
- if (answer !== 'y' && answer !== 'yes') {
283
- console.log('\n 跳过。试试:');
284
- console.log(' ao run workflows/story-creation.yaml -i premise="你的故事创意"\n');
285
- return;
286
- }
287
- // Run real workflow
288
- console.log(`\n 🚀 使用 ${selectedProvider.name} 运行...\n`);
147
+ console.log(`\n 🚀 用 ${selectedProvider.name} 真跑 story-creation 工作流...\n`);
289
148
  const { run } = await import('../index.js');
290
149
  const workflowPath = resolveWorkflowPath();
291
150
  const modelMap = {
@@ -294,10 +153,21 @@ export async function runDemo() {
294
153
  openai: 'gpt-4o',
295
154
  ollama: 'qwen2.5:7b',
296
155
  };
297
- await run(workflowPath, { premise: DEMO_PREMISE }, {
156
+ const result = await run(workflowPath, { premise: DEMO_PREMISE }, {
298
157
  llmOverride: {
299
158
  provider: selectedProvider.provider,
300
- model: modelMap[selectedProvider.provider] || 'deepseek-chat',
159
+ model: modelMap[selectedProvider.provider] || selectedProvider.provider,
301
160
  },
302
161
  });
162
+ console.log('\n ' + '─'.repeat(40));
163
+ if (result.success) {
164
+ console.log(' ✅ Demo 跑通了 ao 的核心能力\n');
165
+ console.log(' 下一步:');
166
+ console.log(' ao compose "你的需求一句话" --run # AI 自动编排工作流');
167
+ console.log(' ao roles # 看所有可用角色');
168
+ console.log(' ao run workflows/<模板>.yaml # 跑现成模板\n');
169
+ }
170
+ else {
171
+ console.log(' ⚠️ Demo 没全部跑通,看上面错误信息排查\n');
172
+ }
303
173
  }
@@ -102,8 +102,14 @@ export class CLIBaseConnector {
102
102
  const content = this.cfg.parseOutput
103
103
  ? this.cfg.parseOutput(stdout)
104
104
  : stdout.trim();
105
- if (!content && stderr) {
106
- reject(new Error(`${this.cfg.displayName} 返回空内容,stderr: ${stderr.slice(0, 500)}`));
105
+ // 空内容是严重错误(LLM 应当返回内容)。区分两类原因给具体 hint,避免上层
106
+ // 拿到空字符串后报出迷惑性的"无效 YAML"错误。
107
+ if (!content) {
108
+ const stderrSnippet = stderr.trim().slice(0, 400);
109
+ const hint = stderrSnippet
110
+ ? `stderr: ${stderrSnippet}`
111
+ : `进程退出码 ${code} 但 stdout/stderr 都为空。可能原因: CLI 命令格式已变(参考 issue #14 hermes 的 chat -q → -z)/ agent 或 model 配置不对 / CLI 需要先认证。建议在终端直接跑一次:\n ${this.cfg.command} ${args.slice(0, 4).join(' ')}${args.length > 4 ? ' ...' : ''}\n 看真实输出再调整 ao 配置`;
112
+ reject(new Error(`${this.cfg.displayName} 返回空内容。${hint}`));
107
113
  return;
108
114
  }
109
115
  // 检测 CLI 输出中的 API 错误(进程 exit 0 但内容是错误信息)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agency-orchestrator",
3
- "version": "0.6.14",
3
+ "version": "0.6.16",
4
4
  "description": "Multi-agent YAML workflow engine — 211 AI roles, auto DAG parallelism, zero code. One sentence → multiple AI roles collaborate → complete plan in minutes. 10 LLM providers, 7 need no API key.",
5
5
  "keywords": [
6
6
  "multi-agent",
@@ -64,7 +64,7 @@
64
64
  "scripts": {
65
65
  "build": "tsc",
66
66
  "dev": "tsc --watch",
67
- "test": "npx tsx test/run.ts && npx tsx test/condition.ts && npx tsx test/cli.ts && npx tsx test/compose.ts && npx tsx test/demo.ts && npx tsx test/factory-custom.ts && npx tsx test/step-llm.ts && npx tsx test/step-llm-yaml.ts && npx tsx test/stdin-limit.ts && npx tsx test/compose-name.ts && npx tsx test/e2e.ts && npx tsx test/e2e-condition.ts && npx tsx test/e2e-loop.ts && npx tsx test/mcp.ts",
67
+ "test": "npx tsx test/run.ts && npx tsx test/condition.ts && npx tsx test/cli.ts && npx tsx test/cli-base.ts && npx tsx test/compose.ts && npx tsx test/demo.ts && npx tsx test/factory-custom.ts && npx tsx test/step-llm.ts && npx tsx test/step-llm-yaml.ts && npx tsx test/stdin-limit.ts && npx tsx test/compose-name.ts && npx tsx test/e2e.ts && npx tsx test/e2e-condition.ts && npx tsx test/e2e-loop.ts && npx tsx test/mcp.ts",
68
68
  "prepublishOnly": "npm run build"
69
69
  },
70
70
  "files": [