agency-orchestrator 0.6.8 → 0.6.10

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 CHANGED
@@ -345,7 +345,7 @@ Cursor (`.cursor/mcp.json`):
345
345
  | `llm.provider` | string | Yes | `claude-code` / `gemini-cli` / `copilot-cli` / `codex-cli` / `openclaw-cli` / `hermes-cli` / `ollama` / `claude` / `deepseek` / `openai` |
346
346
  | `llm.model` | string | Yes | Model name |
347
347
  | `llm.max_tokens` | number | No | Default 4096 |
348
- | `llm.timeout` | number | No | Step timeout in ms (default 120000) |
348
+ | `llm.timeout` | number | No | Step timeout in ms (default API 120000 / CLI/ollama 600000). Automatically extends x1.5 on timeout retry up to 3600000. `0` means no timeout |
349
349
  | `llm.retry` | number | No | Retry count (default 3) |
350
350
  | `concurrency` | number | No | Max parallel steps (default 2) |
351
351
  | `inputs` | array | No | Input variable definitions |
package/README.md CHANGED
@@ -366,7 +366,7 @@ ao serve --verbose # 带调试日志
366
366
  | `llm.provider` | string | 是 | `claude-code` / `gemini-cli` / `copilot-cli` / `codex-cli` / `openclaw-cli` / `hermes-cli` / `ollama` / `claude` / `deepseek` / `openai` |
367
367
  | `llm.model` | string | 是 | 模型名称 |
368
368
  | `llm.max_tokens` | number | 否 | 默认 4096 |
369
- | `llm.timeout` | number | 否 | 步骤超时毫秒数(默认 120000 |
369
+ | `llm.timeout` | number | 否 | 步骤超时毫秒数(默认 API 120000 / CLI/ollama 600000)。因超时重试时自动 x1.5 递增,上限 3600000。`0` 表示不限时 |
370
370
  | `llm.retry` | number | 否 | 重试次数(默认 3) |
371
371
  | `concurrency` | number | 否 | 最大并行步骤数(默认 2) |
372
372
  | `inputs` | array | 否 | 输入变量定义 |
@@ -64,3 +64,19 @@ export declare function composeWorkflow(options: {
64
64
  relativePath: string;
65
65
  warnings: string[];
66
66
  }>;
67
+ /**
68
+ * 自动修复 compose 生成 YAML 中的变量引用错误。
69
+ *
70
+ * 核心约束:替换目标必须在当前 step 的 DAG 上游(递归 depends_on 闭包)内的 step.output 集合里。
71
+ * 这避免了"早期 step 引用下游 step output"的拓扑错误(旧版策略 2 模糊匹配会把
72
+ * personal_assessment 错误地指向 final_report 这种最终汇总 output)。
73
+ *
74
+ * 如果某 step 没有上游或上游 output 都对不上,bad var 留给 LLM 二次修复(repairWithLLM)。
75
+ */
76
+ export declare function autoFixVariableRefs(yamlPath: string): Promise<{
77
+ fixed: number;
78
+ details: {
79
+ from: string;
80
+ to: string;
81
+ }[];
82
+ }>;
@@ -149,6 +149,8 @@ ${catalog}
149
149
 
150
150
  - The role value must strictly use paths from the role catalog (e.g., "engineering/engineering-code-reviewer") — do NOT make up role paths
151
151
  - **Variable names must use underscores**, no spaces. Correct: "market_analysis", "tech_report". Wrong: "market analysis", "tech report". All id, output, and depends_on values must be snake_case
152
+ - **Variables must have a source**: every \`{{X}}\` referenced in a step's task MUST appear either as an \`inputs\` name OR as the \`output\` field of an earlier step. Do NOT invent variable names that no step produces
153
+ - **Merge / aggregation steps**: if a step references \`{{a}}\`, \`{{b}}\`, \`{{c}}\` from upstream, its \`depends_on\` MUST list every upstream step that produces those outputs. Cross-check before emitting
152
154
  - Only output the YAML code block, nothing else
153
155
  - Set concurrency to the maximum number of parallel steps
154
156
  - **Important: Split large tasks**. When writing long articles, don't let one step generate more than 800 words. Split by sections into multiple parallel steps (e.g., write_ch1, write_ch2, write_ch3), then use a merge step to rewrite into a coherent complete article
@@ -241,6 +243,8 @@ ${catalog}
241
243
 
242
244
  - role 的值必须严格使用角色目录中的 path(如 "engineering/engineering-code-reviewer"),不要自己编造
243
245
  - **变量名必须用下划线**,不能有空格。正确:"market_analysis"、"tech_report"。错误:"market analysis"、"tech report"。id、output、depends_on 中的值都必须用 snake_case
246
+ - **变量必须有来源**:每个 task 中的 \`{{X}}\` 引用,X 必须是 \`inputs\` 中的某个 name,或者是前面某个 step 的 \`output\` 字段。不要凭空写一个没有任何 step 产生的变量名
247
+ - **合并/汇总类步骤**:如果一个步骤的 task 里引用了 \`{{a}}\`、\`{{b}}\`、\`{{c}}\` 这些上游变量,它的 \`depends_on\` 必须列出所有产生这些 output 的上游 step。生成完后请逐一核对一遍
244
248
  - 只输出 YAML 代码块,不要输出其他内容
245
249
  - concurrency 设为并行步骤的最大数量
246
250
  - **重要:拆分大任务**。写长文章时,不要让一个步骤生成超过 800 字的内容。应该按章节拆分成多个并行步骤(如 write_ch1、write_ch2、write_ch3),最后用一个合并步骤重写为连贯的完整文章
@@ -386,52 +390,77 @@ export async function composeWorkflow(options) {
386
390
  if (retryYaml && retryYaml.includes('steps:')) {
387
391
  writeFileSync(savedPath, retryYaml + '\n', 'utf-8');
388
392
  const second = await validateGenerated(savedPath);
389
- // 重试后仍有变量引用错误 → 自动修复
390
- const retryVarErrors = second.errors.filter(e => e.includes('未定义的变量'));
391
- if (retryVarErrors.length > 0) {
392
- const fixResult = await autoFixVariableRefs(savedPath);
393
- if (fixResult.fixed > 0) {
394
- console.log(` 自动修复了 ${fixResult.fixed} 个变量引用:`);
395
- for (const f of fixResult.details)
396
- console.log(` {{${f.from}}} → {{${f.to}}}`);
397
- const afterFix = await validateGenerated(savedPath);
398
- warnings.push(...afterFix.errors);
399
- const fixedYaml = readFileSync(savedPath, 'utf-8').trim();
400
- return { yaml: fixedYaml, savedPath, relativePath, warnings };
401
- }
402
- }
403
- warnings.push(...second.errors);
404
- return { yaml: retryYaml, savedPath, relativePath, warnings };
393
+ // 重试后仍有变量引用错误 → 走 fix 链(autoFix → LLM 二次修复)
394
+ const finalErrors = await runVariableFixChain(savedPath, second.errors, validateGenerated, llmConfig, lang);
395
+ warnings.push(...finalErrors);
396
+ const fixedYaml = readFileSync(savedPath, 'utf-8').trim();
397
+ return { yaml: fixedYaml, savedPath, relativePath, warnings };
405
398
  }
406
399
  }
407
400
  catch (err) {
408
401
  warnings.push(`自动修正失败(保留原始输出): ${err instanceof Error ? err.message : err}`);
409
402
  }
410
403
  }
411
- // 自动修复未定义的变量引用(LLM 常见错误:变量名与 output 名不一致)
412
- const varErrors = first.errors.filter(e => e.includes('未定义的变量'));
413
- if (varErrors.length > 0) {
414
- const fixResult = await autoFixVariableRefs(savedPath);
415
- if (fixResult.fixed > 0) {
416
- console.log(` 自动修复了 ${fixResult.fixed} 个变量引用:`);
417
- for (const f of fixResult.details) {
418
- console.log(` {{${f.from}}}{{${f.to}}}`);
419
- }
420
- // 重新校验
421
- const afterFix = await validateGenerated(savedPath);
422
- warnings.push(...afterFix.errors);
423
- const fixedYaml = readFileSync(savedPath, 'utf-8').trim();
424
- return { yaml: fixedYaml, savedPath, relativePath, warnings };
404
+ // 首次生成无幻觉角色 直接走变量 fix 链
405
+ const finalErrors = await runVariableFixChain(savedPath, first.errors, validateGenerated, llmConfig, lang);
406
+ warnings.push(...finalErrors);
407
+ const finalYaml = readFileSync(savedPath, 'utf-8').trim();
408
+ return { yaml: finalYaml, savedPath, relativePath, warnings };
409
+ }
410
+ /**
411
+ * 变量引用错误的修复链:autoFix(启发式)→ LLM 二次修复 重新校验。
412
+ * 返回最终仍未解决的 errors。
413
+ */
414
+ async function runVariableFixChain(savedPath, initialErrors, validateGenerated, llmConfig, lang) {
415
+ const hasVarError = (errs) => errs.some(e => e.includes('未定义的变量') || e.toLowerCase().includes('undefined variable'));
416
+ if (!hasVarError(initialErrors))
417
+ return initialErrors;
418
+ // 阶段 1: autoFix(启发式,只在 DAG 上游内替换)
419
+ const fixResult = await autoFixVariableRefs(savedPath);
420
+ if (fixResult.fixed > 0) {
421
+ console.log(` 自动修复了 ${fixResult.fixed} 个变量引用:`);
422
+ for (const f of fixResult.details)
423
+ console.log(` {{${f.from}}} → {{${f.to}}}`);
424
+ }
425
+ let current = await validateGenerated(savedPath);
426
+ if (!hasVarError(current.errors))
427
+ return current.errors;
428
+ // 阶段 2: LLM 二次修复(autoFix 修不动的让 LLM 决策)
429
+ const remainingVars = extractUndefinedVarNames(current.errors);
430
+ if (remainingVars.length === 0)
431
+ return current.errors;
432
+ console.log(` ${remainingVars.length} 个变量启发式修不动,调 LLM 二次修复...`);
433
+ const repair = await repairWithLLM(savedPath, remainingVars, llmConfig, lang);
434
+ if (repair.replaced) {
435
+ current = await validateGenerated(savedPath);
436
+ if (!hasVarError(current.errors)) {
437
+ console.log(' LLM 修复成功');
438
+ return current.errors;
425
439
  }
440
+ console.log(` LLM 修复后仍有 ${remainingVars.length} 个变量未解决,需人工检查`);
441
+ }
442
+ return current.errors;
443
+ }
444
+ /** 从 "step \"X\" 引用了未定义的变量: {{Y}}" 这类消息里提取变量名 Y */
445
+ function extractUndefinedVarNames(errors) {
446
+ const names = new Set();
447
+ for (const e of errors) {
448
+ const m = e.match(/\{\{(\w+)\}\}/);
449
+ if (m)
450
+ names.add(m[1]);
426
451
  }
427
- warnings.push(...first.errors);
428
- return { yaml, savedPath, relativePath, warnings };
452
+ return [...names];
429
453
  }
430
454
  /**
431
455
  * 自动修复 compose 生成 YAML 中的变量引用错误。
432
- * 常见情况:LLM 用 step id 或 role 名代替 output 变量名。
456
+ *
457
+ * 核心约束:替换目标必须在当前 step 的 DAG 上游(递归 depends_on 闭包)内的 step.output 集合里。
458
+ * 这避免了"早期 step 引用下游 step output"的拓扑错误(旧版策略 2 模糊匹配会把
459
+ * personal_assessment 错误地指向 final_report 这种最终汇总 output)。
460
+ *
461
+ * 如果某 step 没有上游或上游 output 都对不上,bad var 留给 LLM 二次修复(repairWithLLM)。
433
462
  */
434
- async function autoFixVariableRefs(yamlPath) {
463
+ export async function autoFixVariableRefs(yamlPath) {
435
464
  const { parseWorkflow } = await import('../core/parser.js');
436
465
  const content = readFileSync(yamlPath, 'utf-8');
437
466
  let workflow;
@@ -442,59 +471,86 @@ async function autoFixVariableRefs(yamlPath) {
442
471
  return { fixed: 0, details: [] };
443
472
  }
444
473
  const inputNames = new Set((workflow.inputs || []).map((i) => i.name));
445
- const outputNames = new Set();
446
- const stepIdToOutput = new Map();
474
+ const stepById = new Map();
475
+ const allOutputs = new Set();
447
476
  for (const step of workflow.steps) {
448
- if (step.output) {
449
- outputNames.add(step.output);
450
- stepIdToOutput.set(step.id, step.output);
477
+ stepById.set(step.id, step);
478
+ if (step.output)
479
+ allOutputs.add(step.output);
480
+ }
481
+ const allDefined = new Set([...inputNames, ...allOutputs, '_loop_iteration']);
482
+ // 计算 stepId 的 DAG 上游 step ids(递归 depends_on 闭包,不含自身)
483
+ function upstreamStepIds(stepId) {
484
+ const out = new Set();
485
+ const stack = [stepId];
486
+ while (stack.length > 0) {
487
+ const cur = stack.pop();
488
+ const step = stepById.get(cur);
489
+ if (!step)
490
+ continue;
491
+ for (const dep of step.depends_on || []) {
492
+ if (out.has(dep))
493
+ continue;
494
+ out.add(dep);
495
+ stack.push(dep);
496
+ }
451
497
  }
498
+ return out;
452
499
  }
453
- const allDefined = new Set([...inputNames, ...outputNames, '_loop_iteration']);
454
500
  const replacements = [];
455
501
  let fixedContent = content;
456
- // 找出所有未定义的变量引用
457
- const undefinedVars = new Set();
502
+ // 同名 bad var 在多个 step 里只处理一次(避免重复全局 replace)
503
+ const globallyHandled = new Set();
458
504
  for (const step of workflow.steps) {
459
505
  const refs = step.task?.match(/\{\{(\w+)\}\}/g) || [];
506
+ const badVarsInStep = [];
460
507
  for (const ref of refs) {
461
508
  const varName = ref.slice(2, -2);
462
- if (!allDefined.has(varName)) {
463
- undefinedVars.add(varName);
509
+ if (!allDefined.has(varName) && !globallyHandled.has(varName)) {
510
+ badVarsInStep.push(varName);
464
511
  }
465
512
  }
466
- }
467
- for (const badVar of undefinedVars) {
468
- // 策略1:badVar 是某个 step id,该 step 有 output → 替换为 output
469
- if (stepIdToOutput.has(badVar)) {
470
- const goodVar = stepIdToOutput.get(badVar);
471
- fixedContent = fixedContent.replace(new RegExp(`\\{\\{${badVar}\\}\\}`, 'g'), `{{${goodVar}}}`);
472
- replacements.push({ from: badVar, to: goodVar });
473
- continue;
474
- }
475
- // 策略2:模糊匹配 — 找子串包含关系最强的 output 变量
476
- const best = findBestMatch(badVar, [...outputNames]);
477
- if (best) {
478
- fixedContent = fixedContent.replace(new RegExp(`\\{\\{${badVar}\\}\\}`, 'g'), `{{${best}}}`);
479
- replacements.push({ from: badVar, to: best });
513
+ if (badVarsInStep.length === 0)
480
514
  continue;
515
+ // 当前 step 的 DAG 上游 outputs(仅这些是合法替换目标)
516
+ const upStepIds = upstreamStepIds(step.id);
517
+ const upstreamOutputs = [];
518
+ for (const id of upStepIds) {
519
+ const s = stepById.get(id);
520
+ if (s?.output)
521
+ upstreamOutputs.push(s.output);
481
522
  }
482
- // 策略3:按 depends_on 找上游有 output 且尚未被引用的步骤
483
- const alreadyUsed = new Set(replacements.map(r => r.to));
484
- for (const step of workflow.steps) {
485
- const refs = step.task?.match(/\{\{(\w+)\}\}/g) || [];
486
- const hasBadRef = refs.some((r) => r.slice(2, -2) === badVar);
487
- if (hasBadRef && step.depends_on?.length) {
488
- // 优先选还没被占用的上游 output
489
- const deps = step.depends_on.filter(d => stepIdToOutput.has(d));
490
- const unusedDep = deps.find(d => !alreadyUsed.has(stepIdToOutput.get(d)));
491
- const depId = unusedDep || deps[0];
492
- if (depId) {
493
- const goodVar = stepIdToOutput.get(depId);
494
- fixedContent = fixedContent.replace(new RegExp(`\\{\\{${badVar}\\}\\}`, 'g'), `{{${goodVar}}}`);
523
+ if (upstreamOutputs.length === 0)
524
+ continue; // 无上游可用 跳过,等 LLM
525
+ const usedInThisStep = new Set();
526
+ for (const badVar of badVarsInStep) {
527
+ let goodVar;
528
+ // 策略 1:badVar 等于某个上游 step.id(含传递闭包),用该 step.output
529
+ if (upStepIds.has(badVar)) {
530
+ const depStep = stepById.get(badVar);
531
+ if (depStep?.output)
532
+ goodVar = depStep.output;
533
+ }
534
+ // 策略 2:在上游 outputs 中模糊匹配(已被本 step 用过的优先级降低)
535
+ if (!goodVar) {
536
+ const candidates = upstreamOutputs.filter(o => !usedInThisStep.has(o));
537
+ const pool = candidates.length > 0 ? candidates : upstreamOutputs;
538
+ const match = findBestMatch(badVar, pool);
539
+ if (match)
540
+ goodVar = match;
541
+ }
542
+ // 策略 3:上游里第一个还没被本 step 占用的 output
543
+ if (!goodVar) {
544
+ goodVar = upstreamOutputs.find(o => !usedInThisStep.has(o));
545
+ }
546
+ if (goodVar && goodVar !== badVar) {
547
+ const re = new RegExp(`\\{\\{${escapeRegex(badVar)}\\}\\}`, 'g');
548
+ if (re.test(fixedContent)) {
549
+ fixedContent = fixedContent.replace(re, `{{${goodVar}}}`);
495
550
  replacements.push({ from: badVar, to: goodVar });
551
+ usedInThisStep.add(goodVar);
552
+ globallyHandled.add(badVar);
496
553
  }
497
- break;
498
554
  }
499
555
  }
500
556
  }
@@ -503,6 +559,9 @@ async function autoFixVariableRefs(yamlPath) {
503
559
  }
504
560
  return { fixed: replacements.length, details: replacements };
505
561
  }
562
+ function escapeRegex(s) {
563
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
564
+ }
506
565
  /**
507
566
  * 模糊匹配:找子串包含、前缀/后缀重叠最多的候选
508
567
  */
@@ -542,3 +601,111 @@ function longestCommonSubstring(a, b) {
542
601
  }
543
602
  return max;
544
603
  }
604
+ /**
605
+ * 当 autoFix 修不全时,调一次 LLM 让它修剩余的未定义变量。
606
+ * Prompt 给完整 YAML + 错误清单 + 可用 inputs/outputs,让 LLM 决定:
607
+ * - 改 task 里的 {{X}} 引用
608
+ * - 给某 step 加 output 字段
609
+ * - 补 merge step 的 depends_on
610
+ */
611
+ async function repairWithLLM(yamlPath, undefinedVars, llmConfig, lang) {
612
+ const { parseWorkflow } = await import('../core/parser.js');
613
+ const currentYaml = readFileSync(yamlPath, 'utf-8');
614
+ let workflow;
615
+ try {
616
+ workflow = parseWorkflow(yamlPath);
617
+ }
618
+ catch {
619
+ return { ok: false, replaced: false };
620
+ }
621
+ const inputNames = (workflow.inputs || []).map((i) => i.name);
622
+ const outputNames = [];
623
+ const stepIds = [];
624
+ for (const step of workflow.steps) {
625
+ stepIds.push(step.id);
626
+ if (step.output)
627
+ outputNames.push(step.output);
628
+ }
629
+ const prompt = lang === 'en'
630
+ ? `Your previously generated workflow YAML has unresolved variable references. Fix and output the complete YAML.
631
+
632
+ # Current YAML
633
+
634
+ \`\`\`yaml
635
+ ${currentYaml}
636
+ \`\`\`
637
+
638
+ # Undefined variables
639
+
640
+ These \`{{X}}\` references appear in some step's task but no step produces them as \`output\`:
641
+ ${undefinedVars.map(v => ` - {{${v}}}`).join('\n')}
642
+
643
+ # Available names
644
+
645
+ inputs: ${inputNames.length > 0 ? inputNames.join(', ') : '(none)'}
646
+ existing step.output: ${outputNames.length > 0 ? outputNames.join(', ') : '(none)'}
647
+ existing step.id: ${stepIds.join(', ')}
648
+
649
+ # How to fix (pick whichever fits each case)
650
+
651
+ 1. If the reference meant an existing upstream output → rename \`{{X}}\` to that output name in the task
652
+ 2. If a step should be producing this output but lacks the field → add \`output: X\` to that step
653
+ 3. If a merge/aggregation step's \`depends_on\` is incomplete → add the upstream step ids that produce the referenced outputs
654
+
655
+ # Output rules
656
+
657
+ - Output ONLY the corrected complete YAML code block, nothing else
658
+ - Preserve roles, descriptions, structure as much as possible
659
+ - DO NOT add commentary
660
+ `
661
+ : `你之前生成的工作流 YAML 中有未解决的变量引用错误。请修正后输出完整的 YAML。
662
+
663
+ # 当前 YAML
664
+
665
+ \`\`\`yaml
666
+ ${currentYaml}
667
+ \`\`\`
668
+
669
+ # 未定义的变量
670
+
671
+ 以下 \`{{X}}\` 在某个 step 的 task 中被引用,但没有任何 step 用 \`output\` 字段产生它们:
672
+ ${undefinedVars.map(v => ` - {{${v}}}`).join('\n')}
673
+
674
+ # 可用的名字
675
+
676
+ inputs: ${inputNames.length > 0 ? inputNames.join('、') : '(无)'}
677
+ 已有的 step.output: ${outputNames.length > 0 ? outputNames.join('、') : '(无)'}
678
+ 已有的 step.id: ${stepIds.join('、')}
679
+
680
+ # 修复方式(按场景任选)
681
+
682
+ 1. 如果引用的本意是上游某个已存在的 output → 把 task 里的 \`{{X}}\` 改成该 output 的名字
683
+ 2. 如果某个 step 本应产生这个 output 但少写了字段 → 给该 step 加 \`output: X\`
684
+ 3. 如果合并/汇总类 step 的 \`depends_on\` 不全 → 补上产生这些 output 的上游 step.id
685
+
686
+ # 输出要求
687
+
688
+ - 只输出修正后的完整 YAML 代码块,不要输出其他文字
689
+ - 保持角色、描述、结构尽量不变
690
+ - 不要加解释
691
+ `;
692
+ const systemPrompt = lang === 'en'
693
+ ? 'You are a YAML workflow repair assistant. Output only the corrected YAML code block.'
694
+ : '你是一个 YAML 工作流修复助手。只输出修正后的 YAML 代码块。';
695
+ try {
696
+ const connector = createConnector(llmConfig);
697
+ const result = await connector.chat(systemPrompt, prompt, {
698
+ ...llmConfig,
699
+ max_tokens: llmConfig.max_tokens || 4096,
700
+ });
701
+ const fixedYaml = extractYamlFromResponse(result.content);
702
+ if (!fixedYaml || !fixedYaml.includes('steps:')) {
703
+ return { ok: false, replaced: false };
704
+ }
705
+ writeFileSync(yamlPath, fixedYaml + '\n', 'utf-8');
706
+ return { ok: true, replaced: true };
707
+ }
708
+ catch {
709
+ return { ok: false, replaced: false };
710
+ }
711
+ }
package/dist/cli.js CHANGED
@@ -10,6 +10,7 @@
10
10
  */
11
11
  import { readFileSync, existsSync } from 'node:fs';
12
12
  import { resolve, join, dirname } from 'node:path';
13
+ import { fileURLToPath } from 'node:url';
13
14
  import { execSync } from 'node:child_process';
14
15
  import { parseWorkflow, validateWorkflow } from './core/parser.js';
15
16
  import { buildDAG, formatDAG } from './core/dag.js';
@@ -321,10 +322,27 @@ async function handleCompose() {
321
322
  console.log('');
322
323
  }
323
324
  if (autoRun) {
324
- // --run 模式:校验有严重问题时不执行
325
- if (warnings.some(w => w.includes('解析失败') || w.toLowerCase().includes('parse failed'))) {
325
+ // --run 模式:校验有严重问题时不进入 run 阶段
326
+ // run() 内部会重新 validate hard-fail,提前拦下来给用户更清晰的提示
327
+ const fatalParse = warnings.some(w => w.includes('解析失败') || w.toLowerCase().includes('parse failed'));
328
+ const fatalUndefVar = warnings.some(w => w.includes('未定义的变量') || w.toLowerCase().includes('undefined variable'));
329
+ const fatalInvalidRole = warnings.some(w => w.includes('不存在于角色库') || w.toLowerCase().includes('does not exist in catalog'));
330
+ if (fatalParse || fatalUndefVar || fatalInvalidRole) {
326
331
  console.error(` ${t('compose.retry_yaml_bad')}`);
327
- console.error(` ao run ${relativePath}`);
332
+ console.error('');
333
+ console.error(' 以下问题需要先解决 / The following issues need to be resolved:');
334
+ for (const w of warnings) {
335
+ if (w.includes('未定义的变量') || w.includes('解析失败') || w.includes('不存在于角色库')
336
+ || w.toLowerCase().includes('undefined variable') || w.toLowerCase().includes('parse failed')
337
+ || w.toLowerCase().includes('does not exist in catalog')) {
338
+ console.error(` - ${w}`);
339
+ }
340
+ }
341
+ console.error('');
342
+ console.error(' 建议 / Suggestions:');
343
+ console.error(' 1. 重新生成 / Regenerate: ao compose "..." --run');
344
+ console.error(` 2. 手动修改 / Manually edit: ${relativePath}`);
345
+ console.error(` 然后用 / then run: ao run ${relativePath}`);
328
346
  process.exit(1);
329
347
  }
330
348
  console.log('─'.repeat(50));
@@ -611,7 +629,9 @@ function parseInputArgs() {
611
629
  */
612
630
  function resolveAgentsDir(preferLang) {
613
631
  // scriptDir = dist/ inside the installed package
614
- const scriptDir = dirname(new URL(import.meta.url).pathname);
632
+ // Windows: 必须用 fileURLToPath,不能用 new URL(url).pathname
633
+ // 否则会得到 "/C:/Users/..." 非法路径,包内置 / node_modules 候选都失效
634
+ const scriptDir = dirname(fileURLToPath(import.meta.url));
615
635
  const zhFirst = [
616
636
  './agency-agents-zh',
617
637
  './agency-agents',
package/dist/index.js CHANGED
@@ -24,6 +24,7 @@ import { loadAgent } from './agents/loader.js';
24
24
  import { saveResults, printStepResult, printStepRunning, clearRunningLine, printSummary, loadPreviousContext, getCompletedStepIds } from './output/reporter.js';
25
25
  import { existsSync, readFileSync } from 'node:fs';
26
26
  import { resolve, dirname, join } from 'node:path';
27
+ import { fileURLToPath } from 'node:url';
27
28
  /**
28
29
  * 一行运行工作流(高级 API)
29
30
  */
@@ -220,7 +221,9 @@ function resolveAgentsDir(agentsDir, workflowPath) {
220
221
  // 3. 按用户指定的 agents_dir 名字,在常见位置查找同名目录
221
222
  // (尊重用户意图:指定 "agency-agents" 不会 fallback 到 "agency-agents-zh")
222
223
  const baseName = agentsDir.replace(/[\/\\]+$/, '').split(/[\/\\]/).pop() || agentsDir;
223
- const scriptDir = dirname(new URL(import.meta.url).pathname);
224
+ // Windows: 必须用 fileURLToPath,不能用 new URL(url).pathname
225
+ // 否则会得到 "/C:/Users/..." 非法路径,所有 scriptDir 相关候选都失效
226
+ const scriptDir = dirname(fileURLToPath(import.meta.url));
224
227
  const sameNameCandidates = [
225
228
  resolve(baseName),
226
229
  resolve('..', baseName),
@@ -12,6 +12,7 @@ import { z } from 'zod';
12
12
  import { resolve, relative, dirname } from 'node:path';
13
13
  import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
14
14
  import { createRequire } from 'node:module';
15
+ import { fileURLToPath } from 'node:url';
15
16
  import * as yaml from 'js-yaml';
16
17
  import { run } from '../index.js';
17
18
  import { parseWorkflow, validateWorkflow } from '../core/parser.js';
@@ -30,8 +31,9 @@ function findAgentsDir(hint) {
30
31
  resolve('agents'),
31
32
  resolve('node_modules/agency-agents-zh'),
32
33
  resolve('node_modules/agency-agents'),
33
- resolve(dirname(new URL(import.meta.url).pathname), '../../node_modules/agency-agents-zh'),
34
- resolve(dirname(new URL(import.meta.url).pathname), '../../node_modules/agency-agents'),
34
+ // Windows: 必须用 fileURLToPath,不能用 new URL(url).pathname(会得到 "/C:/..." 非法路径)
35
+ resolve(dirname(fileURLToPath(import.meta.url)), '../../node_modules/agency-agents-zh'),
36
+ resolve(dirname(fileURLToPath(import.meta.url)), '../../node_modules/agency-agents'),
35
37
  ];
36
38
  for (const dir of candidates) {
37
39
  if (existsSync(dir))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agency-orchestrator",
3
- "version": "0.6.8",
3
+ "version": "0.6.10",
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",