@zhushanwen/pi-subagent-workflow 5.0.2 → 7.0.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/agents/{reviewer.md → code-reviewer.md} +20 -3
- package/agents/context-builder.md +5 -0
- package/agents/doc-reviewer.md +9 -2
- package/agents/explorer.md +5 -0
- package/agents/general-purpose.md +5 -0
- package/agents/oracle.md +20 -4
- package/agents/orchestrator.md +6 -1
- package/agents/planner.md +5 -0
- package/agents/researcher.md +5 -0
- package/agents/worker.md +5 -0
- package/package.json +6 -4
- package/src/execution/__tests__/agent-registry.test.ts +189 -119
- package/src/execution/__tests__/crash-recovery.test.ts +0 -1
- package/src/execution/__tests__/execute-options-mapper.test.ts +4 -4
- package/src/execution/__tests__/index-session-start.test.ts +0 -1
- package/src/execution/__tests__/model-resolver.test.ts +20 -0
- package/src/execution/__tests__/session-start-reaper.test.ts +0 -2
- package/src/execution/__tests__/subprocess-agent-runner.test.ts +1 -1
- package/src/execution/agent-registry.ts +92 -169
- package/src/execution/execute-options-mapper.ts +2 -2
- package/src/execution/model-config-service.ts +13 -34
- package/src/execution/model-resolver.ts +5 -3
- package/src/execution/subagent-service.ts +9 -6
- package/src/execution/subprocess-agent-runner.ts +3 -2
- package/src/index.ts +4 -25
- package/src/injectors/__tests__/subagent-list-injector.test.ts +266 -14
- package/src/injectors/__tests__/workflow-list-injector.test.ts +236 -32
- package/src/injectors/subagent-list-injector.ts +99 -48
- package/src/injectors/workflow-list-injector.ts +65 -50
- package/src/interface/__tests__/detectors.test.ts +100 -43
- package/src/interface/__tests__/subagent-tool-prompt.test.ts +8 -12
- package/src/interface/__tests__/tool-workflow-script-generate.test.ts +163 -0
- package/src/interface/__tests__/workflow-tool-prompt.test.ts +55 -9
- package/src/interface/subagent-tool.ts +7 -4
- package/src/interface/tool-workflow-script.ts +27 -10
- package/src/interface/tool-workflow.ts +174 -81
- package/src/orchestration/__tests__/args-validator.test.ts +143 -0
- package/src/orchestration/__tests__/config-loader.test.ts +124 -40
- package/src/orchestration/__tests__/launcher-nested-workflow.test.ts +33 -2
- package/src/orchestration/__tests__/lifecycle.test.ts +59 -2
- package/src/orchestration/__tests__/review-fix-loop-e2e.test.ts +116 -51
- package/src/orchestration/__tests__/script-lint.test.ts +167 -1
- package/src/orchestration/__tests__/worker-host.test.ts +120 -0
- package/src/orchestration/__tests__/worker-script-builder-runtime.test.ts +69 -0
- package/src/orchestration/__tests__/worker-script-builder.test.ts +51 -1
- package/src/orchestration/__tests__/workflow-nesting-e2e.test.ts +2 -2
- package/src/orchestration/__tests__/workflows-e2e.test.ts +177 -24
- package/src/orchestration/agent-opts-resolver.ts +51 -101
- package/src/orchestration/args-validator.ts +127 -0
- package/src/orchestration/config-loader.ts +63 -94
- package/src/orchestration/error-recovery.ts +6 -9
- package/src/orchestration/launcher.ts +77 -41
- package/src/orchestration/lifecycle.ts +7 -0
- package/src/orchestration/models/ports.ts +0 -14
- package/src/orchestration/models/run-spec.ts +24 -1
- package/src/orchestration/models/types.ts +15 -8
- package/src/orchestration/models/workflow-script-registry.ts +3 -0
- package/src/orchestration/models/workflow-script.ts +10 -14
- package/src/orchestration/script-lint.ts +159 -0
- package/src/orchestration/worker-host.ts +5 -0
- package/src/orchestration/worker-script-builder.ts +15 -4
- package/src/orchestration/workflow-script-registry-impl.ts +34 -29
- package/src/shared/__tests__/meta-parser.test.ts +304 -0
- package/src/shared/__tests__/resource-discovery.test.ts +167 -7
- package/src/shared/__tests__/resource-meta.test.ts +51 -0
- package/src/shared/agent-ref.ts +36 -0
- package/src/shared/meta-parser.ts +257 -0
- package/src/shared/resource-discovery.ts +88 -2
- package/src/shared/resource-meta.ts +60 -0
- package/workflows/README.md +2 -2
- package/workflows/_shared/agent-refs.cjs +40 -0
- package/workflows/chain.js +30 -6
- package/workflows/map-reduce.js +33 -5
- package/workflows/parallel.js +34 -7
- package/workflows/review-fix-loop-utils.cjs +23 -103
- package/workflows/review-fix-loop.js +121 -58
- package/workflows/scatter-gather.js +30 -6
|
@@ -32,7 +32,9 @@ export type DoneReason =
|
|
|
32
32
|
| "failed"
|
|
33
33
|
| "aborted"
|
|
34
34
|
| "budget_limited"
|
|
35
|
-
| "time_limited"
|
|
35
|
+
| "time_limited"
|
|
36
|
+
// m3:runAndWait 合成返回值专用——参数校验失败(run 从未创建,不进入 run.state.reason)
|
|
37
|
+
| "invalid_args";
|
|
36
38
|
|
|
37
39
|
/** 合法的状态转换。空数组 = 无出边(done 终态)。 */
|
|
38
40
|
export const VALID_RUN_TRANSITIONS: Record<RunStatus, readonly RunStatus[]> = {
|
|
@@ -47,6 +49,7 @@ export const ALL_DONE_REASONS: readonly DoneReason[] = [
|
|
|
47
49
|
"completed",
|
|
48
50
|
"failed",
|
|
49
51
|
"aborted",
|
|
52
|
+
"invalid_args",
|
|
50
53
|
"budget_limited",
|
|
51
54
|
"time_limited",
|
|
52
55
|
] as const;
|
|
@@ -87,7 +90,7 @@ export interface AgentCallOpts {
|
|
|
87
90
|
/**
|
|
88
91
|
* Thinking level override (e.g. "high", "medium", "low").
|
|
89
92
|
* M2: Added to align with subagent path's ExecuteOptions.thinkingLevel.
|
|
90
|
-
* When omitted, agent .md frontmatter thinkingLevel is used (via
|
|
93
|
+
* When omitted, agent .md frontmatter thinkingLevel is used (via resolveIdentity/getAgentConfig).
|
|
91
94
|
*/
|
|
92
95
|
thinkingLevel?: string;
|
|
93
96
|
/** Scene name for model-switch advisor recommendation. */
|
|
@@ -111,16 +114,20 @@ export interface AgentCallOpts {
|
|
|
111
114
|
/** Human-readable description for logging and debugging. */
|
|
112
115
|
description?: string;
|
|
113
116
|
/**
|
|
114
|
-
* Agent
|
|
115
|
-
* agent's systemPrompt
|
|
117
|
+
* Agent ref (absolute .md path). Resolved by resolveIdentity via getAgentConfig,
|
|
118
|
+
* which injects the agent's systemPrompt/model/tools/thinkingLevel. Not handled by
|
|
119
|
+
* resolveAgentOpts (single-responsibility: agent ref ownership belongs to resolveIdentity,
|
|
120
|
+
* M2 fix — previously overlapped causing double-injection + model-tier confusion).
|
|
116
121
|
*/
|
|
117
122
|
agent?: string;
|
|
118
123
|
/**
|
|
119
|
-
*
|
|
120
|
-
* Set by agent-opts-resolver:
|
|
121
|
-
*
|
|
124
|
+
* System prompt injection CONTENT (not file paths).
|
|
125
|
+
* Set by agent-opts-resolver: schema structured-output instruction string.
|
|
126
|
+
* Agent systemPrompt is NOT included here (handled by resolveIdentity/agentConfig).
|
|
127
|
+
* mapToExecuteOptions passes this through to ExecuteOptions.appendSystemPrompt
|
|
128
|
+
* (same name/semantics, transparent passthrough).
|
|
122
129
|
*/
|
|
123
|
-
|
|
130
|
+
appendSystemPrompt?: string[];
|
|
124
131
|
/**
|
|
125
132
|
* Schema JSON for PI_WORKFLOW_SCHEMA env var.
|
|
126
133
|
* Set by agent-opts-resolver when opts.schema is present; passed as env var
|
|
@@ -27,6 +27,9 @@ export interface WorkflowScriptRegistry {
|
|
|
27
27
|
/** 按名查单个脚本(含缓存)。返回 undefined 当 name 不存在。 */
|
|
28
28
|
get(name: string): Promise<WorkflowScript | undefined>;
|
|
29
29
|
|
|
30
|
+
/** 按绝对路径加载单个脚本(workflowRef 统一解析入口——S2 路径统一)。 */
|
|
31
|
+
getPath(ref: string): Promise<WorkflowScript | undefined>;
|
|
32
|
+
|
|
30
33
|
/** 失效缓存——下次 loadAll/get 重新扫描文件系统。 */
|
|
31
34
|
invalidate(): void;
|
|
32
35
|
}
|
|
@@ -18,19 +18,14 @@
|
|
|
18
18
|
import { type LintResult,lintScript } from "../script-lint.ts";
|
|
19
19
|
// LintFinding/LintResult 类型规范归属 engine/script-lint.ts(canonical 源)。
|
|
20
20
|
|
|
21
|
+
// WorkflowMeta 规范来源是 shared/resource-meta.ts(m1 DM1,含 parameters/usage/when/notFor)。
|
|
22
|
+
// m2:删本地封闭 3 字段 interface,re-export m1 的判别联合。
|
|
23
|
+
import type { WorkflowMeta } from "../../shared/resource-meta.ts";
|
|
24
|
+
export type { WorkflowMeta };
|
|
25
|
+
|
|
21
26
|
/** 脚本来源:saved(.pi/workflows/ 固定)或 tmp(.pi/workflows/.tmp/ 临时)。 */
|
|
22
27
|
export type WorkflowSource = "saved" | "tmp";
|
|
23
28
|
|
|
24
|
-
/** 脚本元信息(regex 提取,不执行用户代码)。 */
|
|
25
|
-
export interface WorkflowMeta {
|
|
26
|
-
name: string;
|
|
27
|
-
description: string;
|
|
28
|
-
phases: (string | { title: string; detail?: string })[];
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/** strip `export const meta` → `const meta`(lifecycle.ts:66 逻辑迁移)。 */
|
|
32
|
-
const EXPORT_META_PATTERN = /\bexport\s+const\s+meta\b/g;
|
|
33
|
-
|
|
34
29
|
/**
|
|
35
30
|
* WorkflowScript 实体。
|
|
36
31
|
*
|
|
@@ -79,12 +74,13 @@ export class WorkflowScript {
|
|
|
79
74
|
}
|
|
80
75
|
|
|
81
76
|
/**
|
|
82
|
-
*
|
|
77
|
+
* 返回可执行源。
|
|
83
78
|
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
79
|
+
* m2:不再 strip `export const meta`——meta 现为 @pi-meta 块注释(合法 JS,
|
|
80
|
+
* worker 天然忽略),无 const meta 变量。toExecutable 返回原文(含块注释)。
|
|
81
|
+
* Worker 线程 wrap(注入 globals)由 infra/worker-script-builder.ts buildWorkerScript 完成。
|
|
86
82
|
*/
|
|
87
83
|
toExecutable(): string {
|
|
88
|
-
return this.sourceCode
|
|
84
|
+
return this.sourceCode;
|
|
89
85
|
}
|
|
90
86
|
}
|
|
@@ -25,6 +25,13 @@
|
|
|
25
25
|
* 参考:domain-models.md §7(validate 语义)。
|
|
26
26
|
*/
|
|
27
27
|
|
|
28
|
+
/** m4 W2:meta 描述字段长度上限(§5.1 注入段预算约束)。 */
|
|
29
|
+
const DESC_MAX_LENGTH = 200;
|
|
30
|
+
/** m4 W4:agent examples 最少条数(正反各一需 ≥2)。 */
|
|
31
|
+
const EXAMPLES_MIN_COUNT = 2;
|
|
32
|
+
/** m5:examples 上限(注入是每 turn 常驻成本——防数据无界膨胀)。 */
|
|
33
|
+
const EXAMPLES_MAX_COUNT = 4;
|
|
34
|
+
|
|
28
35
|
/** Lint 检查发现项。 */
|
|
29
36
|
export interface LintFinding {
|
|
30
37
|
/** error = 会导致运行时崩溃; warning = 可能的错误 */
|
|
@@ -41,6 +48,9 @@ export interface LintResult {
|
|
|
41
48
|
}
|
|
42
49
|
|
|
43
50
|
/** 必须命中其一——workflow 脚本不调用任何编排函数等于空跑。 */
|
|
51
|
+
import type { AgentMeta } from "../shared/resource-meta.ts";
|
|
52
|
+
import { parseResourceMeta } from "../shared/meta-parser.ts";
|
|
53
|
+
|
|
44
54
|
const ENTRY_POINT_PATTERNS = [/\bagent\s*\(/, /\bparallel\s*\(/, /\bpipeline\s*\(/] as const;
|
|
45
55
|
|
|
46
56
|
/**
|
|
@@ -568,6 +578,148 @@ function checkPhaseConsistency(source: string): LintFinding[] {
|
|
|
568
578
|
* @param source 脚本源码(原始文件内容)
|
|
569
579
|
* @returns LintResult(valid = 无 error 级 finding)
|
|
570
580
|
*/
|
|
581
|
+
/**
|
|
582
|
+
* W2/W3 共享 helper:meta 描述字段长度上限 + 单句判定(workflow lintScript 与
|
|
583
|
+
* agent lintAgentMeta 共用同一规则——S-5 对称,避免 agent 长 description 常驻膨胀)。
|
|
584
|
+
*
|
|
585
|
+
* W2:>200 字符 → error。
|
|
586
|
+
* W3:括号内容剥离((…)/(…)与 (...))后含换行/。;/'. ' 分句 → error。
|
|
587
|
+
* 输出格式与 severity 约定与 checkMetaQuality 一致(error / line 1 / meta.<field> 前缀)。
|
|
588
|
+
*/
|
|
589
|
+
function checkMetaFieldQuality(field: string, value: string): LintFinding[] {
|
|
590
|
+
const findings: LintFinding[] = [];
|
|
591
|
+
// W2
|
|
592
|
+
if (value.length > DESC_MAX_LENGTH) {
|
|
593
|
+
findings.push({
|
|
594
|
+
severity: "error",
|
|
595
|
+
line: 1,
|
|
596
|
+
message: `meta.${field} 长度 ${value.length} 超过 ${DESC_MAX_LENGTH} 字符(§5.1 注入段预算约束)`,
|
|
597
|
+
suggestion: "精简为单句路由描述,细节移入 usage",
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
// W3:括号剥离后分句判定
|
|
601
|
+
const stripped = value
|
|
602
|
+
.replace(/([^)]*)/g, "")
|
|
603
|
+
.replace(/\([^)]*\)/g, "")
|
|
604
|
+
.replace(/\b(?:e\.g|i\.e)\.\s/g, ""); // 缩写剔除(exec-review F8:括号外 e.g. 不误报)
|
|
605
|
+
if (/[\n。;]|\.\s/.test(stripped)) {
|
|
606
|
+
findings.push({
|
|
607
|
+
severity: "error",
|
|
608
|
+
line: 1,
|
|
609
|
+
message: `meta.${field} 非单句(含换行/分句标点)——§5.1 注入段要求一句话路由描述`,
|
|
610
|
+
suggestion: "精简为单句,细节移入 when/notFor/usage",
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
return findings;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
/**
|
|
617
|
+
* W1-W3:SSOT lint(m4)——保 §5.1 注入段 description 短单句、不含已声明参数名。
|
|
618
|
+
* 仅对 parseResourceMeta 解析成功的 meta 执行(旧 const meta 格式不检查)。
|
|
619
|
+
*
|
|
620
|
+
* W1 参数名匹配(非形态匹配):'note:'/'Example:'/'a=b' 等 prose 不误报(design-review
|
|
621
|
+
* 探针实测形态匹配误报面);参数名集合 = meta.parameters.properties keys + patternProperties
|
|
622
|
+
* 的 word 前缀(^word\\d+$ 转义形态)。检查面 = description + when + notFor 三字段
|
|
623
|
+
* (同进 §5.1 注入段)。W2/W3 抽到 checkMetaFieldQuality(lintAgentMeta 共用)。
|
|
624
|
+
*/
|
|
625
|
+
function checkMetaQuality(meta: { description?: string; when?: string; notFor?: string; parameters?: Record<string, unknown> }): LintFinding[] {
|
|
626
|
+
const findings: LintFinding[] = [];
|
|
627
|
+
const description = meta.description ?? "";
|
|
628
|
+
|
|
629
|
+
// W1 参数名集合
|
|
630
|
+
const paramNames = new Set<string>();
|
|
631
|
+
if (meta.parameters && typeof meta.parameters === "object") {
|
|
632
|
+
const props = (meta.parameters as Record<string, unknown>).properties;
|
|
633
|
+
if (props !== null && typeof props === "object") {
|
|
634
|
+
for (const k of Object.keys(props as Record<string, unknown>)) paramNames.add(k);
|
|
635
|
+
}
|
|
636
|
+
const pp = (meta.parameters as Record<string, unknown>).patternProperties;
|
|
637
|
+
if (pp !== null && typeof pp === "object") {
|
|
638
|
+
for (const p of Object.keys(pp as Record<string, unknown>)) {
|
|
639
|
+
const m = p.match(/^\^([a-zA-Z]+)\\d\+\$$/);
|
|
640
|
+
if (m) paramNames.add(m[1] as string);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
const fields: Array<[string, string]> = [
|
|
646
|
+
["description", description],
|
|
647
|
+
["when", meta.when ?? ""],
|
|
648
|
+
["notFor", meta.notFor ?? ""],
|
|
649
|
+
];
|
|
650
|
+
for (const [field, value] of fields) {
|
|
651
|
+
if (value.length === 0) continue;
|
|
652
|
+
// W1:已声明参数名的 ':'/'=' 形态(参数名转义防 RegExp 注入崩溃——exec-review F2;
|
|
653
|
+
// ':' 形态加 \\b 前缀防子串误报(subtask: 不命中 task——exec-review F6))
|
|
654
|
+
for (const name of paramNames) {
|
|
655
|
+
const nameEsc = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
656
|
+
if (new RegExp(`\\b${nameEsc}:\\s`).test(value) || new RegExp(`\\b${nameEsc}=\\S`).test(value)) {
|
|
657
|
+
findings.push({
|
|
658
|
+
severity: "error",
|
|
659
|
+
line: 1,
|
|
660
|
+
message: `meta.${field} 包含已声明参数名 '${name}'('${name}:' / '${name}=' 形态)——参数契约请 read 脚本文件的 @pi-meta parameters 查询,description/when/notFor 只放路由信息`,
|
|
661
|
+
suggestion: `从 meta.${field} 移除 '${name}: ...' / '${name}=...',改在 parameters/usage 中声明`,
|
|
662
|
+
});
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
findings.push(...checkMetaFieldQuality(field, value));
|
|
666
|
+
}
|
|
667
|
+
return findings;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
/**
|
|
671
|
+
* W2/W3 + W4(m4/m5 挂 agent 加载路径):description/when/notFor 长度与单句检查
|
|
672
|
+
* (与 workflow 同规则,S-5 对称)+ examples 正反各一。
|
|
673
|
+
* RoutingExample.positive 判别(action 是 string 非 null);examples 缺失 → 无 finding
|
|
674
|
+
* (未迁移 agent 不报错——m5 挂载安全根基)。
|
|
675
|
+
*/
|
|
676
|
+
export function lintAgentMeta(meta: AgentMeta): LintFinding[] {
|
|
677
|
+
// W2/W3(S-5 对称):agent 的 description/when/notFor 与 workflow 同规则
|
|
678
|
+
// (长度上限 + 单句判定)——formatAgentList 原样注入 systemPrompt,长描述是每 turn 常驻成本。
|
|
679
|
+
const findings: LintFinding[] = [];
|
|
680
|
+
const fields: Array<[string, string]> = [
|
|
681
|
+
["description", meta.description],
|
|
682
|
+
["when", meta.when ?? ""],
|
|
683
|
+
["notFor", meta.notFor ?? ""],
|
|
684
|
+
];
|
|
685
|
+
for (const [field, value] of fields) {
|
|
686
|
+
if (value.length === 0) continue;
|
|
687
|
+
findings.push(...checkMetaFieldQuality(field, value));
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
const examples = meta.examples;
|
|
691
|
+
if (examples === undefined) return findings; // 未迁移 agent(无 examples 字段)不报错
|
|
692
|
+
if (examples.length === 0) {
|
|
693
|
+
findings.push({
|
|
694
|
+
severity: "error",
|
|
695
|
+
line: 1,
|
|
696
|
+
message: `agent '${meta.name}' 声明了 examples 但为空——需 ≥2 条且正反各一`,
|
|
697
|
+
suggestion: "补正向样本(何时调用)+ 反向样本(何时不调用)",
|
|
698
|
+
});
|
|
699
|
+
return findings;
|
|
700
|
+
}
|
|
701
|
+
const hasPositive = examples.some((e) => e.positive === true);
|
|
702
|
+
const hasNegative = examples.some((e) => e.positive === false);
|
|
703
|
+
if (examples.length < EXAMPLES_MIN_COUNT || !hasPositive || !hasNegative) {
|
|
704
|
+
findings.push({
|
|
705
|
+
severity: "error",
|
|
706
|
+
line: 1,
|
|
707
|
+
message: `agent '${meta.name}' 的 examples 需 ≥2 条且正反各一(positive:true 触发路由 + positive:false 反例)`,
|
|
708
|
+
suggestion: "补正向样本(何时调用)+ 反向样本(何时不调用)",
|
|
709
|
+
});
|
|
710
|
+
return findings;
|
|
711
|
+
}
|
|
712
|
+
if (examples.length > EXAMPLES_MAX_COUNT) {
|
|
713
|
+
findings.push({
|
|
714
|
+
severity: "error",
|
|
715
|
+
line: 1,
|
|
716
|
+
message: `agent '${meta.name}' 的 examples ${examples.length} 条超过上限 ${EXAMPLES_MAX_COUNT}(注入段是每 turn 常驻成本)`,
|
|
717
|
+
suggestion: "精简到 2-4 条:正反各一 + 最多 2 条冗余",
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
return findings;
|
|
721
|
+
}
|
|
722
|
+
|
|
571
723
|
export function lintScript(source: string): LintResult {
|
|
572
724
|
const lines = source.split("\n");
|
|
573
725
|
const findings: LintFinding[] = [];
|
|
@@ -591,6 +743,13 @@ export function lintScript(source: string): LintResult {
|
|
|
591
743
|
// 最终靠 worker-host → handleReturn → release → abort 的调用栈定位。
|
|
592
744
|
findings.push(...checkBareAsyncIIFE(source));
|
|
593
745
|
|
|
746
|
+
// m4 W1-W3:meta 质量(description/when/notFor 短单句 + 不含已声明参数名)。
|
|
747
|
+
// 仅对 IF1 解析成功的 @pi-meta 执行——旧 const meta 格式(D1 无 adapter)不检查。
|
|
748
|
+
const meta = parseResourceMeta(source, "workflow");
|
|
749
|
+
if (meta && meta.kind === "workflow") {
|
|
750
|
+
findings.push(...checkMetaQuality(meta));
|
|
751
|
+
}
|
|
752
|
+
|
|
594
753
|
// 显示性检查(warning):agent 缺 description / meta.phases 形式 / phase 一致性。
|
|
595
754
|
// 目的:让 TUI /workflows 视图避免 unnamed agent 与 (unnamed) phase 分组。
|
|
596
755
|
findings.push(...checkAgentDescription(source));
|
|
@@ -67,6 +67,11 @@ export class WorkerHostImpl implements WorkerHost {
|
|
|
67
67
|
usedTokens: 0,
|
|
68
68
|
usedCost: 0,
|
|
69
69
|
},
|
|
70
|
+
// Option B symmetric single-path: model/thinkingLevel flow through
|
|
71
|
+
// workerData → $MODEL/$THINKING_LEVEL worker globals (injected by
|
|
72
|
+
// worker-script-builder) → agent() fallback. Not merged into args.
|
|
73
|
+
model: spec.model,
|
|
74
|
+
thinkingLevel: spec.thinkingLevel,
|
|
70
75
|
},
|
|
71
76
|
});
|
|
72
77
|
|
|
@@ -114,6 +114,12 @@ export function buildWorkerScript(userScript: string): string {
|
|
|
114
114
|
' remaining() { return Math.max(0, _budgetData.total - _budgetData._spentTokens); },',
|
|
115
115
|
' };',
|
|
116
116
|
'',
|
|
117
|
+
' // ── Run-level model/thinkingLevel override (Option B symmetric single-path) ──',
|
|
118
|
+
' // Injected from workerData (set by workerHost from RunSpec.model/thinkingLevel).',
|
|
119
|
+
' // agent() reads these as run-level defaults; per-call explicit opts still win.',
|
|
120
|
+
' const $MODEL = (workerData.model && typeof workerData.model === "string") ? workerData.model : undefined;',
|
|
121
|
+
' const $THINKING_LEVEL = (workerData.thinkingLevel && typeof workerData.thinkingLevel === "string") ? workerData.thinkingLevel : undefined;',
|
|
122
|
+
'',
|
|
117
123
|
' // ── WorkflowAbortedError ──',
|
|
118
124
|
' class WorkflowAbortedError extends Error {',
|
|
119
125
|
' constructor(reason) {',
|
|
@@ -188,10 +194,10 @@ export function buildWorkerScript(userScript: string): string {
|
|
|
188
194
|
' prompt: firstArg,',
|
|
189
195
|
' description: (secondArg && typeof secondArg === "object" && secondArg.label) || undefined,',
|
|
190
196
|
' schema: (secondArg && typeof secondArg === "object" && secondArg.schema) || undefined,',
|
|
191
|
-
' model: (secondArg && typeof secondArg === "object" && secondArg.model) ||
|
|
197
|
+
' model: (secondArg && typeof secondArg === "object" && secondArg.model) || $MODEL,',
|
|
192
198
|
' scene: (secondArg && typeof secondArg === "object" && secondArg.scene) || undefined,\n' +
|
|
193
199
|
' phase: (secondArg && typeof secondArg === "object" && secondArg.phase) || undefined,',
|
|
194
|
-
' thinkingLevel: (secondArg && typeof secondArg === "object" && secondArg.thinkingLevel) ||
|
|
200
|
+
' thinkingLevel: (secondArg && typeof secondArg === "object" && secondArg.thinkingLevel) || $THINKING_LEVEL,',
|
|
195
201
|
' };',
|
|
196
202
|
' } else if (typeof firstArg === "object" && firstArg !== null) {',
|
|
197
203
|
' if (firstArg.prompt) {',
|
|
@@ -204,7 +210,7 @@ export function buildWorkerScript(userScript: string): string {
|
|
|
204
210
|
' description: firstArg.label || firstArg.description,',
|
|
205
211
|
' agent: firstArg.agent,',
|
|
206
212
|
' schema: firstArg.schema,',
|
|
207
|
-
' model: firstArg.model,',
|
|
213
|
+
' model: firstArg.model || $MODEL,',
|
|
208
214
|
' scene: firstArg.scene,',
|
|
209
215
|
' skill: firstArg.skill,',
|
|
210
216
|
' timeoutMs: firstArg.timeoutMs,',
|
|
@@ -212,7 +218,7 @@ export function buildWorkerScript(userScript: string): string {
|
|
|
212
218
|
' fork: firstArg.fork,',
|
|
213
219
|
' worktree: firstArg.worktree,',
|
|
214
220
|
' returnMeta: firstArg.returnMeta,',
|
|
215
|
-
' thinkingLevel: firstArg.thinkingLevel,',
|
|
221
|
+
' thinkingLevel: firstArg.thinkingLevel || $THINKING_LEVEL,',
|
|
216
222
|
' };',
|
|
217
223
|
' } else {',
|
|
218
224
|
' opts = firstArg;',
|
|
@@ -221,6 +227,11 @@ export function buildWorkerScript(userScript: string): string {
|
|
|
221
227
|
' throw new Error("agent() requires a prompt string or options object as first argument");',
|
|
222
228
|
' }',
|
|
223
229
|
'',
|
|
230
|
+
' // Run-level fallback for object branches (opts = firstArg direct assign).',
|
|
231
|
+
' // Priority: agent() explicit > $MODEL global (set from workerData.model).',
|
|
232
|
+
' if (!opts.model && $MODEL) opts.model = $MODEL;',
|
|
233
|
+
' if (!opts.thinkingLevel && $THINKING_LEVEL) opts.thinkingLevel = $THINKING_LEVEL;',
|
|
234
|
+
'',
|
|
224
235
|
' // Validate known agent() fields to catch API misuse early',
|
|
225
236
|
' const _knownFields = new Set(["prompt", "description", "schema", "model", "scene", "label", "task", "agent", "phase", "skill", "timeoutMs", "cwd", "fork", "worktree", "returnMeta", "thinkingLevel"]);',
|
|
226
237
|
' const _unknownFields = Object.keys(opts).filter((k) => !_knownFields.has(k));',
|
|
@@ -16,17 +16,14 @@
|
|
|
16
16
|
* - get(name) 精确匹配;fuzzy 匹配由 Interface 层 tool 负责。
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
import {
|
|
20
|
-
|
|
21
|
-
import {
|
|
22
|
-
type WorkflowMeta,
|
|
23
|
-
WorkflowScript,
|
|
24
|
-
type WorkflowSource,
|
|
25
|
-
} from "./models/workflow-script.ts";
|
|
19
|
+
import { WorkflowScript } from "./models/workflow-script.ts";
|
|
20
|
+
import { getCachedFileContent } from "../shared/resource-discovery.ts";
|
|
26
21
|
import type { WorkflowScriptRegistry } from "./models/workflow-script-registry.ts";
|
|
27
22
|
import {
|
|
23
|
+
type CachedWorkflowMeta,
|
|
28
24
|
discoverWorkflows,
|
|
29
25
|
getWorkflow,
|
|
26
|
+
getWorkflowByPath,
|
|
30
27
|
invalidateCache,
|
|
31
28
|
loadWorkflows,
|
|
32
29
|
type WorkflowScanConfig,
|
|
@@ -73,6 +70,15 @@ export class WorkflowScriptRegistryImpl implements WorkflowScriptRegistry {
|
|
|
73
70
|
return meta ? this.toScript(meta) : undefined;
|
|
74
71
|
}
|
|
75
72
|
|
|
73
|
+
/**
|
|
74
|
+
* 按绝对路径加载单个脚本(S2 路径统一)。任意路径(不限扫描源)。
|
|
75
|
+
* 供 workflow tool 的 run/info(name 参数 = workflowRef)。
|
|
76
|
+
*/
|
|
77
|
+
async getPath(ref: string): Promise<WorkflowScript | undefined> {
|
|
78
|
+
const meta = await getWorkflowByPath(ref);
|
|
79
|
+
return meta ? this.toScript(meta) : undefined;
|
|
80
|
+
}
|
|
81
|
+
|
|
76
82
|
/** 失效缓存——下次 loadAll/get 重新扫描文件系统。 */
|
|
77
83
|
invalidate(): void {
|
|
78
84
|
invalidateCache();
|
|
@@ -81,34 +87,29 @@ export class WorkflowScriptRegistryImpl implements WorkflowScriptRegistry {
|
|
|
81
87
|
/**
|
|
82
88
|
* 把 CachedWorkflowMeta 转换为 WorkflowScript 实体。
|
|
83
89
|
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
* - available:meta 提取失败(config-loader 标 available=false)或文件不可读时为 false
|
|
90
|
+
* m2:整对象透传——m 已是 WorkflowMeta(CachedWorkflowMeta extends WorkflowMeta),
|
|
91
|
+
* 直接传 meta: m,不再 {name,description,phases} 重建。消灭第 3 处重映射,
|
|
92
|
+
* parameters/usage/when/notFor 一路流到 script.meta。
|
|
93
|
+
*
|
|
94
|
+
* sourceCode 在此 readFile 填充(FR-2:registry 是唯一读文件处)。
|
|
95
|
+
* available:meta 提取失败或文件不可读时为 false。
|
|
91
96
|
*/
|
|
92
|
-
private toScript(m: {
|
|
93
|
-
name: string;
|
|
94
|
-
description: string;
|
|
95
|
-
phases: (string | { title: string; detail?: string })[];
|
|
96
|
-
path: string;
|
|
97
|
-
available: boolean;
|
|
98
|
-
source: WorkflowSource;
|
|
99
|
-
}): WorkflowScript {
|
|
100
|
-
const meta: WorkflowMeta = {
|
|
101
|
-
name: m.name,
|
|
102
|
-
description: m.description,
|
|
103
|
-
phases: m.phases,
|
|
104
|
-
};
|
|
97
|
+
private toScript(m: CachedWorkflowMeta): WorkflowScript {
|
|
105
98
|
// FR-2: registry 是唯一读文件处。readFileSync 填 sourceCode —— 这样 launcher/tool
|
|
106
99
|
// 直接调 toExecutable/validate 即可,无需各自 readFile(避免重复读,60s TTL 缓存生效)。
|
|
107
100
|
let sourceCode = "";
|
|
108
101
|
let available = m.available;
|
|
109
102
|
if (available) {
|
|
110
103
|
try {
|
|
111
|
-
|
|
104
|
+
const cachedContent = getCachedFileContent(m.path); // m5:统一 mtime 缓存层
|
|
105
|
+
if (cachedContent === null) {
|
|
106
|
+
// ENOENT/不可读竞态 → available=false(exec-review major-3:?? '' 会静默
|
|
107
|
+
// 返回空源码 workflow——恢复旧 readFileSync throw → catch → available=false 语义)
|
|
108
|
+
sourceCode = "";
|
|
109
|
+
available = false;
|
|
110
|
+
} else {
|
|
111
|
+
sourceCode = cachedContent;
|
|
112
|
+
}
|
|
112
113
|
} catch {
|
|
113
114
|
// 文件不可读(race condition 删除、权限等)——标 available=false,
|
|
114
115
|
// 与 meta 提取失败的现有语义一致(loader "never throws")。
|
|
@@ -121,7 +122,11 @@ export class WorkflowScriptRegistryImpl implements WorkflowScriptRegistry {
|
|
|
121
122
|
source: m.source,
|
|
122
123
|
path: m.path,
|
|
123
124
|
sourceCode,
|
|
124
|
-
meta
|
|
125
|
+
// meta: m 整对象透传(m2 决策):故意不做显式投影——投影会重引入
|
|
126
|
+
// {name,description,phases} 解构重映射反模式(m2 消灭的丢字段 bug)。
|
|
127
|
+
// CachedWorkflowMeta 多带的 path/available/source 是良性泄漏(无消费者
|
|
128
|
+
// 序列化 script.meta);若未来有消费者,改为组合式 { meta, path, ... } 而非投影。
|
|
129
|
+
meta: m,
|
|
125
130
|
available,
|
|
126
131
|
});
|
|
127
132
|
}
|