@zhushanwen/pi-subagent-workflow 8.7.0 → 8.8.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.
Files changed (49) hide show
  1. package/README.md +5 -11
  2. package/package.json +7 -10
  3. package/skills/workflow-script-format/SKILL.md +32 -13
  4. package/src/host/__tests__/pi-host.test.ts +23 -2
  5. package/src/host/pi-host.ts +57 -3
  6. package/src/index.ts +56 -110
  7. package/src/injectors/__tests__/engine-awareness.test.ts +2 -2
  8. package/src/injectors/__tests__/engine-section-stability.test.ts +6 -4
  9. package/src/injectors/__tests__/model-list-injector.test.ts +18 -21
  10. package/src/injectors/__tests__/subagent-list-injector.test.ts +54 -14
  11. package/src/injectors/__tests__/workflow-list-injector.test.ts +26 -12
  12. package/src/injectors/engine-awareness.ts +0 -4
  13. package/src/injectors/model-list-injector.ts +15 -58
  14. package/src/injectors/subagent-list-injector.ts +55 -113
  15. package/src/injectors/workflow-list-injector.ts +33 -66
  16. package/src/interface/__tests__/detectors.test.ts +45 -34
  17. package/src/interface/__tests__/subagent-tool-prompt.test.ts +6 -3
  18. package/src/interface/__tests__/tool-workflow-run-builtin-name.test.ts +216 -0
  19. package/src/interface/__tests__/tool-workflow-script-generate.test.ts +17 -6
  20. package/src/interface/__tests__/tool-workflow-throw-paths.test.ts +37 -1
  21. package/src/interface/bg-notify-render.ts +3 -13
  22. package/src/interface/command-actions.ts +5 -15
  23. package/src/interface/commands.ts +1 -1
  24. package/src/interface/format.ts +15 -4
  25. package/src/interface/gui-mappers.ts +18 -22
  26. package/src/interface/helpers.ts +8 -129
  27. package/src/interface/list-component.ts +1 -1
  28. package/src/interface/list-shared.ts +1 -1
  29. package/src/interface/list-view.ts +1 -1
  30. package/src/interface/subagent-actions.ts +51 -676
  31. package/src/interface/subagent-tool-schema.ts +1 -4
  32. package/src/interface/subagent-tool.ts +1 -1
  33. package/src/interface/subagents.ts +1 -1
  34. package/src/interface/tool-render.ts +3 -13
  35. package/src/interface/tool-workflow-script.ts +33 -75
  36. package/src/interface/tool-workflow.ts +51 -88
  37. package/src/interface/views/WorkflowsView.ts +3 -11
  38. package/src/interface/views/format.ts +19 -58
  39. package/src/jsonl-run-store.ts +134 -222
  40. package/agents/analyst.md +0 -61
  41. package/agents/coder.md +0 -70
  42. package/agents/debugger.md +0 -67
  43. package/agents/doc-reviewer.md +0 -50
  44. package/agents/explorer.md +0 -64
  45. package/agents/general-purpose.md +0 -32
  46. package/agents/orchestrator.md +0 -63
  47. package/agents/planner.md +0 -54
  48. package/agents/researcher.md +0 -65
  49. package/agents/reviewer.md +0 -74
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Subagent List Injector(迁移自 unified-hooks + P3/P4 改造)
2
+ * Subagent List Injector(迁移自 unified-hooks + P3/P4 改造;C5① 渲染改接 core)
3
3
  *
4
4
  * 发现所有可用 subagent(builtin + user + project 各源)并通过 before_agent_start
5
5
  * 每 turn 注入 `<available_subagents>` 段(name + description),让模型能选对 agent
@@ -7,15 +7,23 @@
7
7
  *
8
8
  * 改造点:
9
9
  * - P4:发现路径由自实现扫 4 目录改为同包 ADR-031 统一发现 discoverResources
10
- * (覆盖 7 源:user-pi/user-agents/npm/npm-dev/project-pi/project-agents + manifest
11
- * 模式 + 优先级合并)。discoverResources 只返回 DiscoveredResource(path/source/
10
+ * (P4 时点覆盖 7 源;C2 扩源后为统一发现全源 9 值 ResourceSource,含
11
+ * user-extension-paths/project-pi-tmp/project-host——序见 resource-discovery.ts
12
+ * 模块头注 + 优先级合并)。discoverResources 只返回 DiscoveredResource(path/source/
12
13
  * available),不含 name/description——保留 parseAgentFrontmatter 解析每个 .md 的
13
14
  * frontmatter 提取 name+description。
14
- * - P3:formatAgentList 开头补正向触发引导(何时该 delegate),保留原有「ONLY use
15
- * agent names from this list」名字约束。
15
+ * - P3:注入段引导语含正向触发引导(何时该 delegate)+ 名字约束(文案见
16
+ * SUBAGENT_LIST_GUIDE)。
17
+ * - C5①(convergence D-3):formatAgentList/sortByCodepoint 下沉 core,本文件改调
18
+ * core barrel(渲染骨架与条目模板逐字节同 pi 旧本地实现——CA2 快照验收前提);
19
+ * guide 文案是 pi 宿主注入(core 不内嵌平台文案)。
20
+ * - U11(sink 设计):装配循环(发现→解析→去重→排序 + warn/error 口径)整体改
21
+ * 消费 core discoverAgents(execution/agents-assembly,U2/A6)——壳侧收缩为
22
+ * 「宿主注入根现取 + 委托」,语义等值口径见 agents-assembly.ts 头注(⛔1 探针
23
+ * 对照注入 XML diff 为空验收)。
16
24
  *
17
- * 归位原因:injector 是 subagent-workflow 的内聚功能(让 LLM 知道有哪些 agent 可用),
18
- * 与同包 resource-discovery 同包后可直接 import,消除跨包依赖。
25
+ * 归位原因:injector 是 subagent-workflow 的内聚功能壳——事件接线与数据获取留
26
+ * 在插件层(before_agent_start / modelRegistry),解析/渲染算法消费 core。
19
27
  */
20
28
 
21
29
 
@@ -31,18 +39,35 @@ import type {
31
39
  import { getLogger } from "@zhushanwen/pi-extension-logger";
32
40
 
33
41
  import { getHostServices } from "@zhushanwen/subagent-core/core/host-services.ts";
34
-
42
+ // U11(sink 设计):agent 装配循环单源 core discoverAgents(U2/A6 装配函数,
43
+ // 深路径消费);渲染(formatAgentList)与 workspace 根推导(findWorkspaceRoot)
44
+ // 走 barrel,parseResourceMeta 仍服务 parseAgentFrontmatter(严格注入投影语义锚)。
45
+ // 上方 getHostServices 深路径刻意保留:core 服务定位器(core 内模块统一经它取用
46
+ // 宿主端口),barrel 刻意不导出(D5 exports 面即 semver 契约、逐名列出,未列名
47
+ // 的内部件不经 barrel——见 subagent-core src/index.ts 头注),壳侧深路径消费经包
48
+ // `./*` -> src 通配豁免。
35
49
  import {
36
- discoverResources,
50
+ type AgentEntry,
37
51
  findWorkspaceRoot,
38
- getCachedFileContent,
39
- getCachedParsed,
40
- } from "@zhushanwen/subagent-core/shared/resource-discovery.ts";
41
- import { parseResourceMeta } from "@zhushanwen/subagent-core/shared/meta-parser.ts";
42
- import { escapeXml, renderXmlSection } from "@zhushanwen/subagent-core/shared/xml-injection.ts";
52
+ formatAgentList,
53
+ parseResourceMeta,
54
+ } from "@zhushanwen/subagent-core";
55
+ import { discoverAgents } from "@zhushanwen/subagent-core/execution/agents-assembly.ts";
43
56
 
44
57
  const logger = getLogger("injector");
45
58
 
59
+ /**
60
+ * pi 版注入引导文案(C5① guide 参数化——core 渲染函数不内嵌平台文案,宿主注入)。
61
+ *
62
+ * 2026-08 C5 重写依据(pi 现参数面,src/interface/subagent-tool-schema.ts):
63
+ * 旧句「pass systemPrompt alongside the agent name to create a dynamic agent」
64
+ * 指向的 systemPrompt 参数已不在 subagent tool schema——现 agent 参数 = .md 绝对
65
+ * 路径(<location>),缺省落 general-purpose(继承主 agent 模型与项目上下文),
66
+ * 动态指引经 task 文本 / appendSystemPrompt 参数承载。
67
+ */
68
+ export const SUBAGENT_LIST_GUIDE =
69
+ "The following subagents are available. PRIORITY: when a task involves reading 3+ files, writing 100+ lines, parallel research, or specialized review, delegate to a matching subagent FIRST instead of doing it yourself — this keeps your context focused on orchestration. Do NOT call list to discover available subagents; use list only for running state. When using the subagent tool, ONLY use agents from this list — pass the <location> path (absolute .md path) as the agent param. If no agent matches your task, omit agent (a general-purpose agent is used) and put all role-specific instructions in the task text.";
70
+
46
71
  /**
47
72
  * Session 级 agent 列表缓存(per-process = per-session)。
48
73
  *
@@ -64,17 +89,8 @@ let agentInjectionCache: string | null = null;
64
89
  /** agentCache 唯一写点:数据与渲染缓存同步更新(null 清空两者)。 */
65
90
  function setAgentCache(entries: AgentEntry[] | null): void {
66
91
  agentCache = entries;
67
- agentInjectionCache = entries !== null ? formatAgentList(entries) : null;
68
- }
69
-
70
- /** 从 .md frontmatter 提取的最小 agent 信息(m5:+ when/examples 路由样本;S1:+ path) */
71
- export interface AgentEntry {
72
- name: string;
73
- description: string;
74
- when?: string;
75
- examples?: Array<{ match: string; action: string; positive: boolean }>;
76
- /** agentRef:agent .md 文件的绝对路径(注入段 <location>,模型直接引用) */
77
- path: string;
92
+ agentInjectionCache =
93
+ entries !== null ? formatAgentList(entries, { guide: SUBAGENT_LIST_GUIDE }) : null;
78
94
  }
79
95
 
80
96
  /**
@@ -97,103 +113,29 @@ export function parseAgentFrontmatter(content: string): AgentEntry | null {
97
113
  }
98
114
 
99
115
  /**
100
- * 用统一资源发现(ADR-031)发现所有可用 agent
116
+ * 发现所有可用 agent(注入清单装配入口)。
101
117
  *
102
- * discoverResources 返回按文件名 stem 去重、优先级合并后的 DiscoveredResource[]
103
- * (project > user > builtin,返回顺序低→高优先级——Map 后写覆盖依赖此序,不可在
104
- * 发现层重排)。此处逐个解析 frontmatter 提取 name+description(经 getCachedParsed
105
- * mtime 级缓存),再按 agent name 去重(高优先级靠后,Map.set 后者覆盖前者,故最终
106
- * 保留最高优先级同名 agent)。
118
+ * U11(sink 设计):原手写装配循环(discoverResources 逐个 parseAgentFrontmatter
119
+ * 解析 agent name 去重(高优先级靠后 Map 后写胜)→ name 码点序排序 + warn/error
120
+ * 口径)整体改消费 core `discoverAgents`(U2/A6 装配函数)——解析/去重/排序/warn
121
+ * 口径单源 core,第三宿主免复刻(G3/S5)。等值口径(IF1 严格层可见性、warn 仅限
122
+ * 「有 frontmatter 但解析失败」、KV-cache 码点序契约:注入段进每 turn system
123
+ * prompt,顺序与 readdir 枚举序解耦,目录内容不变时任意重建渲染逐字节一致)登记见
124
+ * core execution/agents-assembly.ts 头注。
107
125
  *
108
- * 输出按 name 码点序排序(KV-cache 契约):注入段进每 turn system prompt,顺序必须
109
- * 与文件系统枚举序(readdir 无契约)解耦——目录内容不变时,session_start / fallback /
110
- * resume 任意重建的渲染结果逐字节一致;仅条目增减时文本才变化。
126
+ * 壳侧保留职责:签名(workspaceRoot 单参)与宿主注入根现取(pi discoveryRoots
127
+ * 每次现取 getAgentDir,实例隔离;agentDir 形参已删——其唯一用途就是喂 ScanConfig,
128
+ * u0-data-discovery 偏差 #7)。
111
129
  *
112
130
  * 永不抛错——发现本身 fail-safe,单个文件读失败仅记日志。
113
131
  */
114
132
  export async function discoverAllAgents(
115
133
  workspaceRoot: string,
116
134
  ): Promise<AgentEntry[]> {
117
- const resources = await discoverResources({
118
- kind: "agents",
135
+ return discoverAgents(
119
136
  workspaceRoot,
120
- // 宿主注入根现取(pi 壳 discoveryRoots 每次现取 getAgentDir,实例隔离);
121
- // agentDir 形参已删——其唯一用途就是喂 ScanConfig(u0-data-discovery 偏差 #7)
122
- hostRoots: getHostServices().discoveryRoots?.()?.agents ?? [],
123
- });
124
-
125
- const agentMap = new Map<string, AgentEntry>();
126
- for (const resource of resources) {
127
- if (!resource.available) continue;
128
- try {
129
- const agent = getCachedParsed(resource.path, parseAgentFrontmatter);
130
- if (agent) {
131
- agentMap.set(agent.name, { ...agent, path: resource.path });
132
- } else if (startsWithFrontmatter(resource.path)) {
133
- // m5(评审 M3/F2 + minor-5):仅「有 frontmatter 但解析失败」才 warn
134
- // (缺 name/description/examples 单条非法致整体 reject)——README 等
135
- // 无 frontmatter 的 .md 不刷 warn(每 turn 扫描)。
136
- logger.warn(
137
- `[subagent-list-injector] ${resource.path}: agent frontmatter 解析失败(IF1 校验不通过)——agent 未注入`,
138
- );
139
- }
140
- } catch (err) {
141
- // 单个文件读失败不阻断整条 agent 列表注入
142
- logger.error(
143
- `[subagent-list-injector] skip unreadable agent file ${resource.path}`,
144
- { reason: err instanceof Error ? err.message : String(err) },
145
- );
146
- }
147
- }
148
- return sortByCodepoint([...agentMap.values()], (a) => a.name);
149
- }
150
-
151
- /** 码点序排序(显式契约,禁 localeCompare——宿主 locale 差异会破坏跨环境字节一致)。 */
152
- function sortByCodepoint<T>(items: T[], key: (item: T) => string): T[] {
153
- return items.sort((a, b) => {
154
- const ka = key(a);
155
- const kb = key(b);
156
- return ka < kb ? -1 : ka > kb ? 1 : 0;
157
- });
158
- }
159
-
160
- /** 内容以 frontmatter 分隔符开头(解析失败才值得 warn 的判据)。 */
161
- function startsWithFrontmatter(filePath: string): boolean {
162
- return (getCachedFileContent(filePath) ?? "").trimStart().startsWith("---");
163
- }
164
-
165
- /**
166
- * 将 agent 列表格式化为 XML 注入段。
167
- *
168
- * P3:引导语开头补正向触发条件(何时该 delegate),再保留原「ONLY use agent names
169
- * from this list」名字约束。空列表返回空串(不注入)。
170
- */
171
- export function formatAgentList(agents: AgentEntry[]): string {
172
- if (agents.length === 0) return "";
173
-
174
- const items = agents.map((agent) => {
175
- let block = ` <agent><name>${escapeXml(agent.name)}</name><description>${escapeXml(agent.description)}</description>`;
176
- // m5:路由样本(when + examples 正反原样渲染——negative 的 action 由作者写
177
- // 「不调用(原因)」,渲染器不硬编码;全部内容 escapeXml 防 XML 注入段破坏)
178
- if (agent.when) {
179
- block += `<when>${escapeXml(agent.when)}</when>`;
180
- }
181
- if (agent.examples && agent.examples.length > 0) {
182
- // 两极性原样渲染——negative 的 action 由作者写「不调用(原因)」,
183
- // 渲染器不硬编码后缀(exec-review major-1:曾追加「(不调用)」致双后缀)
184
- const exampleLines = agent.examples.map(
185
- (e) => ` - "${escapeXml(e.match)}" → ${escapeXml(e.action)}`,
186
- );
187
- block += `\n <examples>\n${exampleLines.join("\n")}\n </examples>`;
188
- }
189
- block += `<location>${escapeXml(agent.path)}</location></agent>`;
190
- return block;
191
- });
192
- return renderXmlSection({
193
- tag: "available_subagents",
194
- guide: "The following subagents are available. PRIORITY: when a task involves reading 3+ files, writing 100+ lines, parallel research, or specialized review, delegate to a matching subagent FIRST instead of doing it yourself — this keeps your context focused on orchestration. Do NOT call list to discover available subagents; use list only for running state. When using the subagent tool, ONLY use agent names from this list. If no agent matches your task, pass systemPrompt alongside the agent name to create a dynamic agent.",
195
- items,
196
- });
137
+ getHostServices().discoveryRoots?.()?.agents ?? [],
138
+ );
197
139
  }
198
140
 
199
141
  /**
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Workflow List Injector(B2 / P1)
2
+ * Workflow List Injector(B2 / P1;C5① 渲染改接 core
3
3
  *
4
4
  * 发现所有可用 workflow(.js/.mjs)并通过 before_agent_start 每 turn 注入
5
5
  * `<available_workflows>` 段(name + description),与 subagent 注入段对称。
@@ -10,9 +10,13 @@
10
10
  * 实现要点:
11
11
  * - 发现走同包 ADR-031 统一发现 discoverResources({kind:"workflows", includeTmp:true})
12
12
  * (includeTmp 覆盖 .pi/workflows/.tmp/,即 workflow-script generate 的产物)
13
- * - 解析每个 workflow 的 `const meta = {name, description, ...}` 提取 name+description
13
+ * - 解析每个 workflow 的 meta 提取 name+description(IF1 parseResourceMeta)
14
14
  * - description 截断为 prompt 友好的摘要(builtin 的 review-fix-loop 描述超长,全量
15
- * 注入每 turn 会膨胀 prompt
15
+ * 注入每 turn 会膨胀 prompt)——summarizeDescription C5① 起改用 core barrel
16
+ * (算法逐字同本地旧实现)
17
+ * - C5①(convergence D-3):formatWorkflowList 下沉 core,本文件改调 core barrel
18
+ * (渲染骨架与条目模板逐字节同 pi 旧本地实现——CA2 快照验收前提);guide 文案
19
+ * 是 pi 宿主注入(core 不内嵌平台文案)
16
20
  */
17
21
 
18
22
 
@@ -28,14 +32,24 @@ import type {
28
32
  import { getLogger } from "@zhushanwen/pi-extension-logger";
29
33
 
30
34
  import { getHostServices } from "@zhushanwen/subagent-core/core/host-services.ts";
31
-
35
+ // C5①/C5⑦/C5b:发现与渲染统一走 core barrel——发现链辅助
36
+ // (resource-discovery/meta-parser 面:findWorkspaceRoot/getCachedParsed/
37
+ // parseResourceMeta)深路径 import 已归零。上方 getHostServices 深路径刻意
38
+ // 保留:core 服务定位器(core 内模块统一经它取用宿主端口),barrel 刻意不
39
+ // 导出(D5 exports 面即 semver 契约、逐名列出,未列名的内部件不经 barrel——
40
+ // 见 subagent-core src/index.ts 头注),壳侧深路径消费经包 `./*` -> src 通配豁免。
32
41
  import {
33
42
  discoverResources,
34
43
  findWorkspaceRoot,
44
+ formatWorkflowList,
35
45
  getCachedParsed,
36
- } from "@zhushanwen/subagent-core/shared/resource-discovery.ts";
37
- import { parseResourceMeta } from "@zhushanwen/subagent-core/shared/meta-parser.ts";
38
- import { escapeXml, renderXmlSection } from "@zhushanwen/subagent-core/shared/xml-injection.ts";
46
+ parseResourceMeta,
47
+ summarizeDescription,
48
+ type WorkflowEntry,
49
+ } from "@zhushanwen/subagent-core";
50
+ // U11(sink 设计):注入清单排序码点序单源——深路径消费 core sortByCodepoint
51
+ // (与 barrel 版同一实现,深路径遵循任务指定消费形态;`./*` -> src 通配豁免)。
52
+ import { sortByCodepoint } from "@zhushanwen/subagent-core/shared/injection-render.ts";
39
53
 
40
54
  const logger = getLogger("injector");
41
55
 
@@ -54,44 +68,19 @@ let workflowInjectionCache: string | null = null;
54
68
  /** workflowCache 唯一写点:数据与渲染缓存同步更新(null 清空两者)。 */
55
69
  function setWorkflowCache(entries: WorkflowEntry[] | null): void {
56
70
  workflowCache = entries;
57
- workflowInjectionCache = entries !== null ? formatWorkflowList(entries) : null;
58
- }
59
-
60
- /** 注入段中单个 workflow 的最大描述长度(控制每 turn prompt 体积) */
61
- const MAX_DESC_LEN = 160;
62
-
63
- /** 断句阈值比例:句末标点位置须 >= maxLen 的 40% 才采用,否则硬截断保留更多信息 */
64
- const DESC_BOUNDARY_MIN_RATIO = 0.4;
65
-
66
- /** 解析后的 workflow 条目(name + 截断后的 description + agentRef 路径) */
67
- export interface WorkflowEntry {
68
- name: string;
69
- description: string;
70
- /** workflowRef:脚本 .js 文件的绝对路径(注入段 <location>,模型直接引用) */
71
- path: string;
71
+ workflowInjectionCache =
72
+ entries !== null ? formatWorkflowList(entries, { guide: WORKFLOW_LIST_GUIDE }) : null;
72
73
  }
73
74
 
74
75
  /**
75
- * workflow description 截断为 prompt 友好的摘要。
76
- * 优先在 limit 内的最后一个句末标点处断句;无合适断点则硬截断 + 省略号。
76
+ * pi 版注入引导文案(C5① guide 参数化——core 渲染函数不内嵌平台文案,宿主注入;
77
+ * 文案与改造前本地实现逐字一致——workflow guide 无过期问题)。
78
+ *
79
+ * 引导语与具体 workflow 解耦:不写死内置名(列表本身已含全部 workflow,名字/描述
80
+ * 每 turn 由 @pi-meta 动态注入),只给通用路由指引 + read location 参数指针。
77
81
  */
78
- export function summarizeDescription(
79
- desc: string,
80
- maxLen = MAX_DESC_LEN,
81
- ): string {
82
- const trimmed = desc.trim();
83
- if (trimmed.length <= maxLen) return trimmed;
84
- const slice = trimmed.slice(0, maxLen);
85
- const boundary = Math.max(
86
- slice.lastIndexOf("。"),
87
- slice.lastIndexOf(";"),
88
- slice.lastIndexOf(";"),
89
- slice.lastIndexOf(". "),
90
- );
91
- // 断点过靠前(< 阈值比例)时不采用,改硬截断保留更多信息
92
- if (boundary > maxLen * DESC_BOUNDARY_MIN_RATIO) return slice.slice(0, boundary + 1);
93
- return `${slice}…`;
94
- }
82
+ export const WORKFLOW_LIST_GUIDE =
83
+ "The following workflows are available. Do NOT call list to discover available workflows — they are listed below; use list only for running state. All listed workflows run directly via action:run — do NOT use workflow-script generate for any listed workflow. For parameter details, read the <location> script file (script header has @pi-meta parameters + usage).";
95
84
 
96
85
  /**
97
86
  * 解析 workflow .js 文件的 meta(name + description),经 IF1 parseResourceMeta。
@@ -138,31 +127,9 @@ export async function discoverAllWorkflows(
138
127
  );
139
128
  }
140
129
  }
141
- return [...map.values()].sort((a, b) =>
142
- a.name < b.name ? -1 : a.name > b.name ? 1 : 0,
143
- );
144
- }
145
-
146
- /**
147
- * 将 workflow 列表格式化为 XML 注入段。
148
- *
149
- * 引导语对齐 subagent injector:workflow 已在下方列出,模型应直接 run;
150
- * list 仅用于查询运行态。builtin workflow 点名「run directly」。
151
- * 空列表返回空串(不注入)。
152
- */
153
- export function formatWorkflowList(workflows: WorkflowEntry[]): string {
154
- if (workflows.length === 0) return "";
155
-
156
- const items = workflows.map((wf) =>
157
- ` <workflow><name>${escapeXml(wf.name)}</name><description>${escapeXml(wf.description)}</description><location>${escapeXml(wf.path)}</location></workflow>`,
158
- );
159
- return renderXmlSection({
160
- tag: "available_workflows",
161
- // 引导语与具体 workflow 解耦:不写死内置名(列表本身已含全部 workflow,
162
- // 名字/描述每 turn 由 @pi-meta 动态注入),只给通用路由指引 + read location 参数指针。
163
- guide: "The following workflows are available. Do NOT call list to discover available workflows — they are listed below; use list only for running state. All listed workflows run directly via action:run — do NOT use workflow-script generate for any listed workflow. For parameter details, read the <location> script file (script header has @pi-meta parameters + usage).",
164
- items,
165
- });
130
+ // U11:内联码点序 sort 折叠为 core sortByCodepoint(实现逐字等价——同比较器
131
+ // 语义:ka < kb ? -1 : ka > kb ? 1 : 0,非变异副本排序)
132
+ return sortByCodepoint([...map.values()], (a) => a.name);
166
133
  }
167
134
 
168
135
  /**
@@ -17,63 +17,73 @@ import { describe, expect, it } from "vitest";
17
17
  import { readFileSync } from "node:fs";
18
18
  import { join } from "node:path";
19
19
 
20
- import { argKeysFromMeta, findFlattenedArgKeys } from "../tool-workflow";
20
+ // D9 下沉后权威实现迁至 core args-meta——宿主差异(TOOL_TOP_LEVEL 撞名保护)经
21
+ // reservedKeys 注入,与 tool-workflow 调用点保持同一契约。
22
+ import {
23
+ argKeysFromMeta,
24
+ findFlattenedArgKeys,
25
+ } from "@zhushanwen/subagent-core/orchestration/args-meta.ts";
26
+ import { TOOL_TOP_LEVEL } from "../tool-workflow";
21
27
  import { parseResourceMeta } from "@zhushanwen/subagent-core/shared/meta-parser.ts";
22
28
 
23
- // 真实参数集(argKeysFromMeta 产物——m6 动态数据源,与 TC2 同源防漂移)
24
- function rflKeys() {
29
+ // 宿主 reservedKeys 注入(与 tool-workflow ARGS_META_OPTIONS 等值契约)
30
+ const OPT = { reservedKeys: TOOL_TOP_LEVEL };
31
+
32
+ // 真实参数 meta(@pi-meta parameters——m6 动态数据源,与 TC2 同源防漂移)
33
+ function rflMeta() {
25
34
  const src = readFileSync(join(__dirname, "../../../node_modules/@zhushanwen/subagent-core/workflows/review-fix-loop.js"), "utf-8");
26
35
  const meta = parseResourceMeta(src, "workflow");
27
36
  if (!meta || meta.kind !== "workflow" || !meta.parameters) throw new Error("rfl meta");
28
- return argKeysFromMeta(meta.parameters);
37
+ return meta.parameters;
29
38
  }
30
- function chainKeys() {
39
+ function chainMeta() {
31
40
  const src = readFileSync(join(__dirname, "../../../node_modules/@zhushanwen/subagent-core/workflows/chain.js"), "utf-8");
32
41
  const meta = parseResourceMeta(src, "workflow");
33
42
  if (!meta || meta.kind !== "workflow" || !meta.parameters) throw new Error("chain meta");
34
- return argKeysFromMeta(meta.parameters);
43
+ return meta.parameters;
35
44
  }
36
- const RFL = rflKeys();
37
- const CHAIN = chainKeys();
45
+ const RFL = rflMeta();
46
+ const CHAIN = chainMeta();
38
47
 
39
48
  describe("findFlattenedArgKeys (workflow args flatten detector — P0)", () => {
40
49
  it("TC3a: trigger——chain 参数集平铺 task 被识别;跨 workflow 区分(items 非 chain 参数不触发)", () => {
41
50
  expect(
42
- findFlattenedArgKeys({ action: "run", name: "chain", task: "x" }, CHAIN.exact, CHAIN.patterns),
51
+ findFlattenedArgKeys({ action: "run", name: "chain", task: "x" }, CHAIN, OPT),
43
52
  ).toEqual(["task"]);
44
53
  // 跨 workflow:items 是 map-reduce 参数非 chain 参数 → 不触发(m6 动态集语义)
45
54
  expect(
46
- findFlattenedArgKeys({ action: "run", name: "chain", items: ["a"] }, CHAIN.exact, CHAIN.patterns),
55
+ findFlattenedArgKeys({ action: "run", name: "chain", items: ["a"] }, CHAIN, OPT),
47
56
  ).toEqual([]);
48
57
  });
49
58
 
50
59
  it("TC3b: 嵌套 args 不触发", () => {
51
60
  expect(
52
- findFlattenedArgKeys({ action: "run", name: "x", args: { task: "x", items: ["a"] } }, CHAIN.exact, CHAIN.patterns),
61
+ findFlattenedArgKeys({ action: "run", name: "x", args: { task: "x", items: ["a"] } }, CHAIN, OPT),
53
62
  ).toEqual([]);
54
63
  });
55
64
 
56
65
  it("TC3c: 顶层 + args 内共存不触发(args-排除保留)", () => {
57
66
  expect(
58
- findFlattenedArgKeys({ action: "run", name: "x", args: { task: "x" }, task: "y" }, CHAIN.exact, CHAIN.patterns),
67
+ findFlattenedArgKeys({ action: "run", name: "x", args: { task: "x" }, task: "y" }, CHAIN, OPT),
59
68
  ).toEqual([]);
60
69
  });
61
70
 
62
71
  it("TC3d: 无已知键不触发;空集不触发", () => {
63
72
  expect(
64
- findFlattenedArgKeys({ action: "run", name: "x", args: {} }, CHAIN.exact, CHAIN.patterns),
73
+ findFlattenedArgKeys({ action: "run", name: "x", args: {} }, CHAIN, OPT),
65
74
  ).toEqual([]);
75
+ // meta undefined → 空键集(原「空集不触发」等价形态)
66
76
  expect(
67
- findFlattenedArgKeys({ action: "status" }, new Set(), []),
77
+ findFlattenedArgKeys({ action: "status" }, undefined),
68
78
  ).toEqual([]);
69
79
  });
70
80
 
71
81
  it("TC3e: review-fix-loop fixAgent 平铺被识别(动态集 exact)", () => {
72
82
  expect(
73
- findFlattenedArgKeys({ action: "run", name: "review-fix-loop", fixAgent: "worker" }, RFL.exact, RFL.patterns),
83
+ findFlattenedArgKeys({ action: "run", name: "review-fix-loop", fixAgent: "worker" }, RFL, OPT),
74
84
  ).toEqual(["fixAgent"]);
75
85
  expect(
76
- findFlattenedArgKeys({ action: "run", name: "review-fix-loop", args: { fixAgent: "worker" } }, RFL.exact, RFL.patterns),
86
+ findFlattenedArgKeys({ action: "run", name: "review-fix-loop", args: { fixAgent: "worker" } }, RFL, OPT),
77
87
  ).toEqual([]);
78
88
  });
79
89
 
@@ -81,27 +91,27 @@ describe("findFlattenedArgKeys (workflow args flatten detector — P0)", () => {
81
91
  expect(
82
92
  findFlattenedArgKeys(
83
93
  { action: "run", name: "review-fix-loop", targetType: "git-diff", target: "main" },
84
- RFL.exact,
85
- RFL.patterns,
94
+ RFL,
95
+ OPT,
86
96
  ),
87
97
  ).toEqual(["targetType", "target"]);
88
98
  expect(
89
99
  findFlattenedArgKeys(
90
100
  { action: "run", name: "review-fix-loop", batch1: "reviewer", autoCommit: true },
91
- RFL.exact,
92
- RFL.patterns,
101
+ RFL,
102
+ OPT,
93
103
  ),
94
104
  ).toEqual(["batch1", "autoCommit"]);
95
105
  expect(
96
106
  findFlattenedArgKeys(
97
107
  { action: "run", name: "review-fix-loop", args: { targetType: "git-diff", target: "main" } },
98
- RFL.exact,
99
- RFL.patterns,
108
+ RFL,
109
+ OPT,
100
110
  ),
101
111
  ).toEqual([]);
102
112
  // batchl 拼错(非数字后缀)不触发(m6 评审 C-1:pattern 自带数字后缀校验)
103
113
  expect(
104
- findFlattenedArgKeys({ action: "run", name: "x", batchl: "reviewer" }, RFL.exact, RFL.patterns),
114
+ findFlattenedArgKeys({ action: "run", name: "x", batchl: "reviewer" }, RFL, OPT),
105
115
  ).toEqual([]);
106
116
  });
107
117
 
@@ -109,42 +119,43 @@ describe("findFlattenedArgKeys (workflow args flatten detector — P0)", () => {
109
119
  expect(
110
120
  findFlattenedArgKeys(
111
121
  { action: "run", name: "review-fix-loop", model: "ds-flash", maxFixAttempts: 3, convergeNewIssues: 2, convergeRounds: 3 },
112
- RFL.exact,
113
- RFL.patterns,
122
+ RFL,
123
+ OPT,
114
124
  ),
115
125
  ).toEqual(["maxFixAttempts", "convergeNewIssues", "convergeRounds"]);
116
126
  expect(
117
127
  findFlattenedArgKeys(
118
128
  { action: "run", name: "review-fix-loop", args: { model: "ds-flash", maxFixAttempts: 3 }, convergeRounds: 3 },
119
- RFL.exact,
120
- RFL.patterns,
129
+ RFL,
130
+ OPT,
121
131
  ),
122
132
  ).toEqual(["convergeRounds"]);
123
133
  });
124
134
 
125
135
  it("TC3h: non-object 输入返回 []", () => {
126
- expect(findFlattenedArgKeys(null, new Set(), [])).toEqual([]);
127
- expect(findFlattenedArgKeys(undefined, new Set(), [])).toEqual([]);
136
+ expect(findFlattenedArgKeys(null)).toEqual([]);
137
+ expect(findFlattenedArgKeys(undefined)).toEqual([]);
128
138
  });
129
139
 
130
140
  it("TC3i: tool 顶层键不误报(M-3 回归——合成 parameters 直测 argKeysFromMeta 排除)", () => {
131
141
  // 合成 parameters:workflow 声明参数 name(tool 键撞名)——argKeysFromMeta 必须排除
132
- const keys = argKeysFromMeta({
142
+ const synthetic = {
133
143
  type: "object",
134
144
  properties: { name: { type: "string" }, task: { type: "string" } },
135
145
  required: ["name"],
136
- });
146
+ };
147
+ const keys = argKeysFromMeta(synthetic, OPT);
137
148
  expect(keys.exact.has("task")).toBe(true);
138
149
  expect(keys.exact.has("name")).toBe(false); // TOOL_TOP_LEVEL 排除(M-3 真回归锁定)
139
150
  // 合法调用不误报
140
151
  expect(
141
152
  findFlattenedArgKeys(
142
153
  { action: "run", name: "mywf", args: { name: "x" } },
143
- keys.exact,
144
- keys.patterns,
154
+ synthetic,
155
+ OPT,
145
156
  ),
146
157
  ).toEqual([]);
147
158
  // 真实 meta 不含 name(探针实测 16 键)——附加验证
148
- expect(RFL.exact.has("name")).toBe(false);
159
+ expect(argKeysFromMeta(RFL, OPT).exact.has("name")).toBe(false);
149
160
  });
150
161
  });
@@ -12,6 +12,7 @@
12
12
  import { readFileSync } from "node:fs";
13
13
  import { dirname,join } from "node:path";
14
14
  import { fileURLToPath } from "node:url";
15
+ import { createRequire } from "node:module";
15
16
 
16
17
  import { describe, expect, it } from "vitest";
17
18
 
@@ -153,12 +154,14 @@ describe("subagent tool runtime handler — 错误文案含纠正正例", () =>
153
154
  // 让弱模型撞错后第二次能直接照抄正确形态。
154
155
  // 拍平后:startParam envelope 删除,平铺 task/slug 是合法形态;
155
156
  // 平铺检测 guard(hasFlattenedStartFields)已删除,源码不应再含此表达式。
156
- it("subagent-actions.ts startHandler throw 含 Correct 纠正正例(平铺形态)", () => {
157
+ it("startHandler throw 含 Correct 纠正正例(平铺形态)", () => {
158
+ // [D6②] startHandler 内核(含 Correct 文案)已下沉 core subagent-actions-core,
159
+ // 断言目标跟随文案权威源(resolve 方式同 prompt-quality-batch1 U4 先例)。
157
160
  const actionsSrc = readFileSync(
158
- join(__dirname, "../subagent-actions.ts"),
161
+ createRequire(import.meta.url).resolve("@zhushanwen/subagent-core/execution/subagent-actions-core.ts"),
159
162
  "utf-8",
160
163
  );
161
- // 三处 throw(input 缺失 / task 空白 / slug 空白)都应含 Correct 正例。
164
+ // 四处 throw(input 缺失 / task 空白 / slug 空白 / slug 超长)都应含 Correct 正例。
162
165
  // 用 occurrences 计数——至少 3 处。
163
166
  const occurrences = (actionsSrc.match(/Correct: \{"action":"start"/g) ?? []).length;
164
167
  expect(occurrences).toBeGreaterThanOrEqual(3);