@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.
- package/README.md +5 -11
- package/package.json +7 -10
- package/skills/workflow-script-format/SKILL.md +32 -13
- package/src/host/__tests__/pi-host.test.ts +23 -2
- package/src/host/pi-host.ts +57 -3
- package/src/index.ts +56 -110
- package/src/injectors/__tests__/engine-awareness.test.ts +2 -2
- package/src/injectors/__tests__/engine-section-stability.test.ts +6 -4
- package/src/injectors/__tests__/model-list-injector.test.ts +18 -21
- package/src/injectors/__tests__/subagent-list-injector.test.ts +54 -14
- package/src/injectors/__tests__/workflow-list-injector.test.ts +26 -12
- package/src/injectors/engine-awareness.ts +0 -4
- package/src/injectors/model-list-injector.ts +15 -58
- package/src/injectors/subagent-list-injector.ts +55 -113
- package/src/injectors/workflow-list-injector.ts +33 -66
- package/src/interface/__tests__/detectors.test.ts +45 -34
- package/src/interface/__tests__/subagent-tool-prompt.test.ts +6 -3
- package/src/interface/__tests__/tool-workflow-run-builtin-name.test.ts +216 -0
- package/src/interface/__tests__/tool-workflow-script-generate.test.ts +17 -6
- package/src/interface/__tests__/tool-workflow-throw-paths.test.ts +37 -1
- package/src/interface/bg-notify-render.ts +3 -13
- package/src/interface/command-actions.ts +5 -15
- package/src/interface/commands.ts +1 -1
- package/src/interface/format.ts +15 -4
- package/src/interface/gui-mappers.ts +18 -22
- package/src/interface/helpers.ts +8 -129
- package/src/interface/list-component.ts +1 -1
- package/src/interface/list-shared.ts +1 -1
- package/src/interface/list-view.ts +1 -1
- package/src/interface/subagent-actions.ts +51 -676
- package/src/interface/subagent-tool-schema.ts +1 -4
- package/src/interface/subagent-tool.ts +1 -1
- package/src/interface/subagents.ts +1 -1
- package/src/interface/tool-render.ts +3 -13
- package/src/interface/tool-workflow-script.ts +33 -75
- package/src/interface/tool-workflow.ts +51 -88
- package/src/interface/views/WorkflowsView.ts +3 -11
- package/src/interface/views/format.ts +19 -58
- package/src/jsonl-run-store.ts +134 -222
- package/agents/analyst.md +0 -61
- package/agents/coder.md +0 -70
- package/agents/debugger.md +0 -67
- package/agents/doc-reviewer.md +0 -50
- package/agents/explorer.md +0 -64
- package/agents/general-purpose.md +0 -32
- package/agents/orchestrator.md +0 -63
- package/agents/planner.md +0 -54
- package/agents/researcher.md +0 -65
- package/agents/reviewer.md +0 -74
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ Pi 的 subagent + workflow 合并包:任务委派 + 多 agent 编排(chain /
|
|
|
4
4
|
|
|
5
5
|
## 内置 Agents
|
|
6
6
|
|
|
7
|
-
按「读/写 × 视角」正交切分,
|
|
7
|
+
按「读/写 × 视角」正交切分,10 个角色零重叠(C1 起随 `@zhushanwen/subagent-core` 的 `agents/` 资产分发,`<available_subagents>` 的 `<location>` 指向 core 包目录):
|
|
8
8
|
|
|
9
9
|
| Agent | 角色 | 读/写 |
|
|
10
10
|
|-------|------|-------|
|
|
@@ -12,6 +12,7 @@ Pi 的 subagent + workflow 合并包:任务委派 + 多 agent 编排(chain /
|
|
|
12
12
|
| `planner` | 复杂任务拆解为有序实施计划(合并需求澄清) | 只读产文档 |
|
|
13
13
|
| `coder` | 代码实现、修改、测试(唯一改代码的角色) | 可改 |
|
|
14
14
|
| `reviewer` | 代码审查与需求验收(含 git diff) | 只读 |
|
|
15
|
+
| `doc-reviewer` | 文档审查(四遍方法论,事实锚点核实;spec / 设计文档) | 只读 |
|
|
15
16
|
| `debugger` | 运行时故障诊断,钉根因 | 只读* |
|
|
16
17
|
| `analyst` | 深度项目分析,产出给人读的报告 | 只读 |
|
|
17
18
|
| `researcher` | 外部资料调研(依赖 tavily skill) | 只读 |
|
|
@@ -30,19 +31,12 @@ Pi 的 subagent + workflow 合并包:任务委派 + 多 agent 编排(chain /
|
|
|
30
31
|
|
|
31
32
|
## Orchestrator 协调器模式
|
|
32
33
|
|
|
33
|
-
|
|
34
|
+
orchestrator 是纯协调器角色:拆解任务 → 委派 subagent → 汇总结果,自身不做执行类工作。orchestrator agent 自身也可递归委派子 orchestrator,实现分层任务拆解(深度受 `Depth: N/10` 护栏保护)。
|
|
34
35
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
### 启动命令
|
|
36
|
+
**工具约束变化(C1/D-5)**:内置模板不再携带 `tools:` frontmatter 白名单,subagent 不再以 `--tools` 白名单启动——工具约束回归宿主默认工具面,orchestrator 靠角色职责(职责边界段)约束自身只做协调。想要白名单的用户在 `<workspace>/.agents/agents/` 放同名 `.md` 覆写(project 级源稳定遮蔽内置,是唯一逃生门),或沿用 pi CLI 的 `--tools` 白名单(临时验证):
|
|
38
37
|
|
|
39
38
|
```bash
|
|
40
|
-
# 方式一:CLI 工具白名单(临时验证最快)
|
|
41
39
|
pi --tools todo,goal_control,workflow,subagent,ask_user
|
|
42
|
-
|
|
43
|
-
# 方式二:白名单 + 注入 orchestrator 的 system prompt(推荐,主进程也具备协调器视角)
|
|
44
|
-
pi --tools todo,goal_control,workflow,subagent,ask_user \
|
|
45
|
-
--append-system-prompt "$(cat ~/.pi/agent/npm/node_modules/@zhushanwen/pi-subagent-workflow/agents/orchestrator.md)"
|
|
46
40
|
```
|
|
47
41
|
|
|
48
42
|
> **依赖**:需先安装本包及相关扩展
|
|
@@ -59,7 +53,7 @@ pi --tools todo,goal_control,workflow,subagent,ask_user \
|
|
|
59
53
|
|
|
60
54
|
## Workflow 生命周期(one-shot)
|
|
61
55
|
|
|
62
|
-
Workflow run 是一次性执行,状态机两态:`running → done`(`done` 唯一终态,reason 区分 completed / aborted / failed / budget_limited / time_limited)。`workflow` tool 仅 3 个 action:`run` / `status` / `abort`。
|
|
56
|
+
Workflow run 是一次性执行,状态机两态:`running → done`(`done` 唯一终态,reason 区分 completed / aborted / failed / budget_limited / time_limited)。`workflow` tool 仅 3 个 action:`run` / `status` / `abort`。run 的 `name` 接受 `<available_workflows>` 列出的 workflow 名(内置 chain / parallel / map-reduce / scatter-gather / review-fix-loop 或已保存脚本)或 `.js` 绝对路径。
|
|
63
57
|
|
|
64
58
|
Runs are one-shot: there is no pause/resume — to stop a run early use abort; for a fresh result start a new run.
|
|
65
59
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhushanwen/pi-subagent-workflow",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.8.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "index.ts",
|
|
6
6
|
"description": "Unified subagent execution and multi-agent workflow orchestration for Pi — spawned-process agent runtime with sync/background modes, stateful workflow management with persistence, state machine, and execution tracing.",
|
|
@@ -19,7 +19,6 @@
|
|
|
19
19
|
},
|
|
20
20
|
"files": [
|
|
21
21
|
"index.ts",
|
|
22
|
-
"agents/",
|
|
23
22
|
"skills/",
|
|
24
23
|
"scripts/",
|
|
25
24
|
"relay/",
|
|
@@ -51,24 +50,22 @@
|
|
|
51
50
|
],
|
|
52
51
|
"skills": [
|
|
53
52
|
"./skills"
|
|
54
|
-
],
|
|
55
|
-
"agents": [
|
|
56
|
-
"./agents"
|
|
57
53
|
]
|
|
58
54
|
},
|
|
59
55
|
"dependencies": {
|
|
60
56
|
"@xyz-agent/session-delivery": "0.3.0",
|
|
61
|
-
"@zhushanwen/pi-
|
|
62
|
-
"@
|
|
63
|
-
"@
|
|
57
|
+
"@zhushanwen/pi-ext-guards": "0.2.0",
|
|
58
|
+
"@zhushanwen/pi-extension-logger": "0.4.0",
|
|
59
|
+
"@xyz-agent/extension-protocol": "0.8.0",
|
|
60
|
+
"@zhushanwen/subagent-core": "0.4.0"
|
|
64
61
|
},
|
|
65
62
|
"peerDependencies": {
|
|
66
63
|
"@earendil-works/pi-ai": "^0.84.4",
|
|
67
64
|
"@earendil-works/pi-coding-agent": "^0.84.4",
|
|
68
65
|
"@earendil-works/pi-tui": "^0.84.4",
|
|
69
66
|
"typebox": "*",
|
|
70
|
-
"@zhushanwen/pi-
|
|
71
|
-
"@zhushanwen/pi-
|
|
67
|
+
"@zhushanwen/pi-structured-output": "5.1.2",
|
|
68
|
+
"@zhushanwen/pi-pending-notifications": "0.5.2"
|
|
72
69
|
},
|
|
73
70
|
"peerDependenciesMeta": {
|
|
74
71
|
"@earendil-works/pi-coding-agent": {
|
|
@@ -39,13 +39,22 @@ This is enforced by `lintScript`:
|
|
|
39
39
|
|
|
40
40
|
## Required: Meta Declaration
|
|
41
41
|
|
|
42
|
-
Every script MUST declare
|
|
42
|
+
Every script MUST declare its metadata as a `/* @pi-meta */` YAML block comment at the top level — NOT a `const meta` variable:
|
|
43
43
|
|
|
44
44
|
```javascript
|
|
45
|
-
|
|
45
|
+
/* @pi-meta
|
|
46
|
+
name: workflow-name
|
|
47
|
+
description: One-line description of what the workflow does
|
|
48
|
+
phases: ['phase1', 'phase2']
|
|
49
|
+
*/
|
|
46
50
|
```
|
|
47
51
|
|
|
48
|
-
`name` must match the filename stem. `phases` is for display only.
|
|
52
|
+
- `name` must match the filename stem. `phases` is for display only.
|
|
53
|
+
- Optional fields: `parameters` (JSON Schema for `$ARGS`) and `usage` (markdown).
|
|
54
|
+
- The YAML body starts on the line after `/* @pi-meta`; the closing `*/` must sit alone at column 0.
|
|
55
|
+
- `generate` round-trip-validates the YAML and reports line/col on error (common pitfall: patternProperties regex must use double backslash `\\d`, not `\d`).
|
|
56
|
+
|
|
57
|
+
> **Legacy `const meta = { ... }` (transition only):** `generate` still accepts a legacy top-level `const meta` during the transition window, but the core discovery chain does NOT recognize it (no legacy fallback) — such scripts stay invisible to discovery and get no metadata injection. Always use `/* @pi-meta */` for new scripts.
|
|
49
58
|
|
|
50
59
|
## [MANDATORY] Display: description + phase
|
|
51
60
|
|
|
@@ -59,11 +68,11 @@ TUI `/workflows` 视图按**运行时 `phase()` 调用**分组(非 `meta.phase
|
|
|
59
68
|
### Minimal 示例(三者齐备)
|
|
60
69
|
|
|
61
70
|
```javascript
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
71
|
+
/* @pi-meta
|
|
72
|
+
name: review-fix
|
|
73
|
+
description: review then fix
|
|
74
|
+
phases: ['review', 'fix']
|
|
75
|
+
*/
|
|
67
76
|
|
|
68
77
|
phase('review');
|
|
69
78
|
const r = await agent({ prompt: 'review diff', description: 'review-diff' });
|
|
@@ -79,11 +88,17 @@ return { done: true };
|
|
|
79
88
|
// ❌ agent() 无 description → TUI 显示 unnamed agent
|
|
80
89
|
await agent({ prompt: 'review diff' });
|
|
81
90
|
|
|
82
|
-
// ❌
|
|
83
|
-
|
|
91
|
+
// ❌ phases 用对象数组 → 引擎忽略,全部归入 (unnamed)
|
|
92
|
+
/* @pi-meta
|
|
93
|
+
name: x
|
|
94
|
+
phases: [{ title: 'review' }, { title: 'fix' }]
|
|
95
|
+
*/
|
|
84
96
|
|
|
85
97
|
// ❌ 声明了 phases 但从不 phase() 调用 → 运行时分组失效
|
|
86
|
-
|
|
98
|
+
/* @pi-meta
|
|
99
|
+
name: x
|
|
100
|
+
phases: ['review', 'fix']
|
|
101
|
+
*/
|
|
87
102
|
// ... 直接 await agent(...) 从不调 phase('review') / phase('fix')
|
|
88
103
|
```
|
|
89
104
|
|
|
@@ -201,7 +216,7 @@ const results = await parallel(
|
|
|
201
216
|
);
|
|
202
217
|
```
|
|
203
218
|
|
|
204
|
-
> 内置通用编排 workflow(chain / parallel / scatter-gather / map-reduce,可直接 `workflow run`,用 `agent()` 自包含实现)见 `extensions/universal/subagent-workflow/workflows/`。本段教 `workflow()` 嵌套 API,workflows 目录是开箱即用的通用编排工具(用 `agent()` 而非 `workflow()` 嵌套)。
|
|
219
|
+
> 内置通用编排 workflow(chain / parallel / scatter-gather / map-reduce / review-fix-loop,可直接 `workflow run`,用 `agent()` 自包含实现)见 `extensions/universal/subagent-workflow/workflows/`。本段教 `workflow()` 嵌套 API,workflows 目录是开箱即用的通用编排工具(用 `agent()` 而非 `workflow()` 嵌套)。
|
|
205
220
|
|
|
206
221
|
### Other globals
|
|
207
222
|
|
|
@@ -249,7 +264,11 @@ agent({ prompt: '...', description: 'review-business-logic' });
|
|
|
249
264
|
## Complete Example
|
|
250
265
|
|
|
251
266
|
```javascript
|
|
252
|
-
|
|
267
|
+
/* @pi-meta
|
|
268
|
+
name: review-fix-loop
|
|
269
|
+
description: "Loop: review → fix → commit until clean"
|
|
270
|
+
phases: ['review-fix']
|
|
271
|
+
*/
|
|
253
272
|
|
|
254
273
|
const MAX_ROUNDS = 10;
|
|
255
274
|
let round = 0;
|
|
@@ -12,7 +12,9 @@
|
|
|
12
12
|
// mock 返回 {count: n} 形状直接验证「适配读 .count」;session-delivery mock
|
|
13
13
|
// 验证 createDelivery 透传(参数 + 返回句柄)。
|
|
14
14
|
|
|
15
|
-
import { join } from "node:path";
|
|
15
|
+
import { join, dirname } from "node:path";
|
|
16
|
+
import { createRequire } from "node:module";
|
|
17
|
+
import { existsSync } from "node:fs";
|
|
16
18
|
|
|
17
19
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
18
20
|
|
|
@@ -72,17 +74,36 @@ describe("createPiHostServices.discoveryRoots(与现推导逐项一致)", ()
|
|
|
72
74
|
vi.mocked(getAgentDir).mockReturnValue(AGENT_DIR);
|
|
73
75
|
}
|
|
74
76
|
|
|
75
|
-
it("agents
|
|
77
|
+
it("agents 四根:user-pi → npm → npm-dev → core 包父目录(C5⑥,source npm 追加末位)", () => {
|
|
76
78
|
stubAgentDir();
|
|
77
79
|
const roots = createPiHostServices().discoveryRoots?.().agents;
|
|
78
80
|
|
|
81
|
+
// 期望的 core 注入根用与 pi-host.ts 相同的锚点解析(./workflows/* 子入口在
|
|
82
|
+
// workspace 与 npm dist 双形态同径;探针证据见 probe-c5.md P1/P3)
|
|
83
|
+
const require = createRequire(import.meta.url);
|
|
84
|
+
const anchor = require.resolve("@zhushanwen/subagent-core/workflows/README.md");
|
|
85
|
+
const coreParent = dirname(dirname(dirname(anchor)));
|
|
86
|
+
|
|
79
87
|
expect(roots).toEqual([
|
|
80
88
|
{ dir: join(AGENT_DIR, "agents"), source: "user-pi" },
|
|
81
89
|
{ dir: join(AGENT_DIR, "npm", "node_modules"), source: "npm" },
|
|
82
90
|
{ dir: join(AGENT_DIR, "extensions"), source: "npm-dev" },
|
|
91
|
+
// C5⑥:core 包一级父目录(npm 槽语义:一级子项 = 包目录,无 pi manifest 扫
|
|
92
|
+
// agents/ 约定目录);追加在既有 npm 根之后——同标签靠后者胜(新版遮蔽旧残留)
|
|
93
|
+
{ dir: coreParent, source: "npm" },
|
|
83
94
|
]);
|
|
84
95
|
});
|
|
85
96
|
|
|
97
|
+
it("agents 第 4 根指向 core 包的父目录(其下 subagent-core/agents/ 存在 10 内置角色)", () => {
|
|
98
|
+
stubAgentDir();
|
|
99
|
+
const roots = createPiHostServices().discoveryRoots?.().agents;
|
|
100
|
+
const coreRoot = roots?.[3];
|
|
101
|
+
|
|
102
|
+
expect(coreRoot?.source).toBe("npm");
|
|
103
|
+
// 便捷断言:core 包根(注入 dir 的子目录)下 agents/ 真实存在
|
|
104
|
+
expect(existsSync(join(coreRoot!.dir, "subagent-core", "agents"))).toBe(true);
|
|
105
|
+
});
|
|
106
|
+
|
|
86
107
|
it("workflows 三根:与 agents 同构,末级目录名切换为 workflows", () => {
|
|
87
108
|
stubAgentDir();
|
|
88
109
|
const roots = createPiHostServices().discoveryRoots?.().workflows;
|
package/src/host/pi-host.ts
CHANGED
|
@@ -16,7 +16,9 @@
|
|
|
16
16
|
// buildScanTargets、orchestration/skill-discovery.ts resolveSkillPath 的现推导
|
|
17
17
|
// 逐项一致(user-pi / npm / npm-dev 字面即现 ResourceSource 标签)。
|
|
18
18
|
// project/workspace 根不在壳侧提供——core 消费方按 workspaceRoot 自行推导
|
|
19
|
-
// (u0-data-discovery 波次接注入消费)。
|
|
19
|
+
// (u0-data-discovery 波次接注入消费)。C5⑥ 起 agents kind 追加第 4 根:
|
|
20
|
+
// core 包一级父目录(source "npm")——core agents/ 资产进 pi 发现面,见
|
|
21
|
+
// corePackageNpmRoot 注释。
|
|
20
22
|
// - countActiveFromEntries 适配:pi 侧真函数返回 CountActiveResult 对象,core
|
|
21
23
|
// 端口契约是 number(core 消费面只读 .count,notify-ports.ts 契约注释)——
|
|
22
24
|
// foundation 单元登记给本单元的适配责任。
|
|
@@ -25,7 +27,8 @@
|
|
|
25
27
|
// 是结构超集成员,多不碍兼容)——结构兼容由本注入点 typecheck 守护,上游签名
|
|
26
28
|
// 漂移即红(notify-ports.ts「闭包红线」段)。
|
|
27
29
|
|
|
28
|
-
import {
|
|
30
|
+
import { createRequire } from "node:module";
|
|
31
|
+
import { dirname, join } from "node:path";
|
|
29
32
|
|
|
30
33
|
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
31
34
|
import { getLogger } from "@zhushanwen/pi-extension-logger";
|
|
@@ -36,12 +39,50 @@ import type { DiscoveryRoot, HostServices } from "@zhushanwen/subagent-core/core
|
|
|
36
39
|
import type { LogLevel } from "@zhushanwen/subagent-core/core/logger.ts";
|
|
37
40
|
import type { NotifyDomainPorts } from "@zhushanwen/subagent-core/core/notify-ports.ts";
|
|
38
41
|
|
|
42
|
+
/**
|
|
43
|
+
* core 包(@zhushanwen/subagent-core)agents/ 资产进 pi 发现面的注入根(C5⑥,
|
|
44
|
+
* convergence §5.4 检查点 2;探针证据 docs/design/subagent-core-convergence.probe-c5.md)。
|
|
45
|
+
*
|
|
46
|
+
* 解析锚点 = `@zhushanwen/subagent-core/workflows/README.md`:`./workflows/*` 子入口
|
|
47
|
+
* 在 workspace TS 直引与 npm dist 两种发布形态下同径(publishConfig 保留该子入口),
|
|
48
|
+
* README.md 是两形态都必在的资产文件。core 包根 = 锚点上两级;npm 槽注入其一级
|
|
49
|
+
* 父目录——npm 槽语义:dir 下一级子项 = 包目录,core 无 pi manifest → 扫 agents/
|
|
50
|
+
* 约定目录命中 10 内置角色。
|
|
51
|
+
*
|
|
52
|
+
* 布局覆盖(探针 P2-P5 实测):dev workspace(core 在仓库 packages/ 下)与发布态
|
|
53
|
+
* 嵌套布局(core 在本包 node_modules 内)下这是唯一命中通路;发布态平铺布局
|
|
54
|
+
* (core 与本包同层)下既有 npm 根已命中,本注入是幂等兜底(重复发现被 core
|
|
55
|
+
* realpath 去重吸收)。
|
|
56
|
+
*
|
|
57
|
+
* 每次调用现解析(不 memo):发现调用点稀疏(session_start + 缓存 miss),解析
|
|
58
|
+
* 成本可忽略;失败(异常布局/解析器不可用)降级为不注入并 warn——绝不因资产
|
|
59
|
+
* 接线失败阻断发现主链。
|
|
60
|
+
*/
|
|
61
|
+
function corePackageNpmRoot(): string | undefined {
|
|
62
|
+
try {
|
|
63
|
+
// createRequire 锚定本模块(jiti 加载器下 import.meta.url 可用;不可用则随
|
|
64
|
+
// catch 降级)。require.resolve 沿 pi-sw 自身的依赖解析链——workspace 与发布态
|
|
65
|
+
// 都从本包出发命中 core。
|
|
66
|
+
const require = createRequire(import.meta.url);
|
|
67
|
+
const anchor = require.resolve("@zhushanwen/subagent-core/workflows/README.md");
|
|
68
|
+
return dirname(dirname(dirname(anchor)));
|
|
69
|
+
} catch (err) {
|
|
70
|
+
// getLogger 惰性调用(catch 是冷路径——测试环境对 pi-extension-logger 的
|
|
71
|
+
// module-level mock 可能返回 undefined,模块级持有会在 import 期踩 undefined)
|
|
72
|
+
getLogger("pi-host").warn(
|
|
73
|
+
"[pi-host] core 包 agents/ 注入根解析失败——10 内置角色可能不可发现",
|
|
74
|
+
{ reason: err instanceof Error ? err.message : String(err) },
|
|
75
|
+
);
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
39
80
|
/** agents/workflows 共享的 agentDir 派生根(末级目录名由 kind 决定)。
|
|
40
81
|
* 顺序与 source 标签逐项对齐 resource-discovery.ts buildScanTargets 的
|
|
41
82
|
* user-pi → npm → npm-dev 段(根列表按优先级低→高排列,D2 语义边界)。 */
|
|
42
83
|
function agentDirKindRoots(kind: "agents" | "workflows"): DiscoveryRoot[] {
|
|
43
84
|
const agentDir = getAgentDir();
|
|
44
|
-
|
|
85
|
+
const roots: DiscoveryRoot[] = [
|
|
45
86
|
// 1. user .pi/agent/<kind>/
|
|
46
87
|
{ dir: join(agentDir, kind), source: "user-pi" },
|
|
47
88
|
// 2. npm global: agentDir/npm/node_modules/*/<pkg>/
|
|
@@ -49,6 +90,19 @@ function agentDirKindRoots(kind: "agents" | "workflows"): DiscoveryRoot[] {
|
|
|
49
90
|
// 3. npm dev symlink: agentDir/extensions/*/<pkg>/
|
|
50
91
|
{ dir: join(agentDir, "extensions"), source: "npm-dev" },
|
|
51
92
|
];
|
|
93
|
+
// 4. core 包根(C5⑥,仅 agents kind):追加在既有 npm 根之后——同标签多根依注入
|
|
94
|
+
// 序扫描 + last-writer-wins,core(随本包依赖分发的新模板)遮蔽同 agentDir 内
|
|
95
|
+
// 旧版残留副本;序位仍在 user 级之上、npm-dev/project 级之下(红线 1)。
|
|
96
|
+
// workflows kind 刻意不注入:<available_workflows> 的 <location> 是 CA2 快照
|
|
97
|
+
// 不豁免面(红线 8 豁免仅限 10 内置 agent 角色路径前缀),注入会翻转内置
|
|
98
|
+
// workflow 的胜出路径。
|
|
99
|
+
if (kind === "agents") {
|
|
100
|
+
const coreNpmRoot = corePackageNpmRoot();
|
|
101
|
+
if (coreNpmRoot !== undefined) {
|
|
102
|
+
roots.push({ dir: coreNpmRoot, source: "npm" });
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return roots;
|
|
52
106
|
}
|
|
53
107
|
|
|
54
108
|
/** skills 的 agentDir 派生根。对齐 skill-discovery.ts resolveSkillPath 的两处
|
package/src/index.ts
CHANGED
|
@@ -25,6 +25,7 @@ import { configureCore } from "@zhushanwen/subagent-core/core/host-services.ts";
|
|
|
25
25
|
import { configureNotifyDomain } from "@zhushanwen/subagent-core/core/notify-ports.ts";
|
|
26
26
|
import { createPiHostServices, createPiNotifyDomainPorts } from "./host/pi-host.ts";
|
|
27
27
|
|
|
28
|
+
import { oncePerProcess } from "@zhushanwen/pi-ext-guards";
|
|
28
29
|
import { bestEffort } from "@zhushanwen/subagent-core/execution/best-effort.ts";
|
|
29
30
|
// ═══ execution/ 层(subagents 核心 + 运行时) ═══
|
|
30
31
|
import { getOrCreateChannelRegistry } from "@zhushanwen/subagent-core/execution/channel-registry-access.ts";
|
|
@@ -49,7 +50,7 @@ import {
|
|
|
49
50
|
} from "@zhushanwen/subagent-core/execution/model-config-service.ts";
|
|
50
51
|
import { bindNotifyLedgerHost, getBoundNotifyLedger, type NotifyLedgerHost } from "@zhushanwen/subagent-core/execution/notify-ledger.ts";
|
|
51
52
|
import { IDENTITY_CUSTOM_TYPE, type SubagentIdentityData } from "@zhushanwen/subagent-core/execution/session-reconstructor.ts";
|
|
52
|
-
import type { ExecutionMode
|
|
53
|
+
import type { ExecutionMode } from "@zhushanwen/subagent-core/execution/types.ts";
|
|
53
54
|
import { maybeCleanupExpiredSessionFiles } from "@zhushanwen/subagent-core/execution/session-file-gc.ts";
|
|
54
55
|
import {
|
|
55
56
|
getSubagentService,
|
|
@@ -59,8 +60,10 @@ import {
|
|
|
59
60
|
import { killAllSpawnedChildren } from "@zhushanwen/subagent-core/execution/session-runner.ts";
|
|
60
61
|
import { SubprocessAgentRunner } from "@zhushanwen/subagent-core/execution/subprocess-agent-runner.ts";
|
|
61
62
|
import { WorktreeManager } from "@zhushanwen/subagent-core/execution/worktree-manager.ts";
|
|
62
|
-
// [engine-awareness U3] per-turn 引擎检测编排(D1/D1b/D2/D3/D5
|
|
63
|
-
|
|
63
|
+
// [engine-awareness U3] per-turn 引擎检测编排(D1/D1b/D2/D3/D5);normalizeEngineId
|
|
64
|
+
// 单一权威源在 core registry(原经 engine-awareness 再导出,导入面已折叠直连)
|
|
65
|
+
import { normalizeEngineId } from "@zhushanwen/subagent-core/execution/engine/registry.ts";
|
|
66
|
+
import { runEngineAwarenessTurn } from "./injectors/engine-awareness.ts";
|
|
64
67
|
import { setupModelListInjector } from "./injectors/model-list-injector.ts";
|
|
65
68
|
import { setupSubagentListInjector } from "./injectors/subagent-list-injector.ts";
|
|
66
69
|
import { setupWorkflowListInjector } from "./injectors/workflow-list-injector.ts";
|
|
@@ -81,6 +84,7 @@ import { executeNestedWorkflow, runAndWait, type WorkflowRunResult } from "@zhus
|
|
|
81
84
|
import {
|
|
82
85
|
evictDoneRunsBeyondCap,
|
|
83
86
|
MAX_RETAINED_DONE_RUNS,
|
|
87
|
+
recoverCrashedRuns,
|
|
84
88
|
scheduleTimeBudget,
|
|
85
89
|
terminateRunningRuns,
|
|
86
90
|
} from "@zhushanwen/subagent-core/orchestration/lifecycle.ts";
|
|
@@ -106,42 +110,6 @@ declare module "@earendil-works/pi-coding-agent" {
|
|
|
106
110
|
// 模块级 logger(setPiHandle 注入后自动走 appendEntry)
|
|
107
111
|
const logger = getLogger("subagents");
|
|
108
112
|
|
|
109
|
-
// ── subagent 状态快照格式化 ──
|
|
110
|
-
//
|
|
111
|
-
// [v4 A-6] before_agent_start 注入 hook 已删(活跃 subagent 清单改由 agent 按需调
|
|
112
|
-
// action:'list' 拉取,消除每 loop 注入的上下文税与盲点)。本函数保留为纯格式化工具:
|
|
113
|
-
// before-agent-start-injection / parent-child-matrix 测试覆盖其正确性,未来 list
|
|
114
|
-
// 视图或其他注入点可复用。
|
|
115
|
-
|
|
116
|
-
/** 活跃 subagent 数量上限(超过截断显示)。 */
|
|
117
|
-
const MAX_STATUS_INJECTION = 10;
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* 将活跃 subagent record 格式化为一行一条的快照文本。
|
|
121
|
-
*
|
|
122
|
-
* 格式:
|
|
123
|
-
* [subagent-status] N active subagents:
|
|
124
|
-
* - sa-xxx (slug): running, rounds 0
|
|
125
|
-
* - sa-yyy (slug): idle, rounds 3
|
|
126
|
-
* +2 more, use action:'list'
|
|
127
|
-
*
|
|
128
|
-
* @param records 已筛选的活跃 record(running + idle)
|
|
129
|
-
*/
|
|
130
|
-
export function formatSubagentStatusSnapshot(records: SubagentRecord[]): string {
|
|
131
|
-
const lines = [`[subagent-status] ${records.length} active subagent${records.length === 1 ? "" : "s"}:`];
|
|
132
|
-
const shown = records.slice(0, MAX_STATUS_INJECTION);
|
|
133
|
-
for (const r of shown) {
|
|
134
|
-
const slug = r.slug || r.agent;
|
|
135
|
-
const roundPart = r.round !== undefined && r.round > 0 ? `, rounds ${r.round}` : "";
|
|
136
|
-
lines.push(`- ${r.id} (${slug}): ${r.status}${roundPart}`);
|
|
137
|
-
}
|
|
138
|
-
const remaining = records.length - MAX_STATUS_INJECTION;
|
|
139
|
-
if (remaining > 0) {
|
|
140
|
-
lines.push(`+${remaining} more, use action:'list'`);
|
|
141
|
-
}
|
|
142
|
-
return lines.join("\n");
|
|
143
|
-
}
|
|
144
|
-
|
|
145
113
|
// ═══ [V2 决策 7 防线 i] process 级 shutdown hook ═══
|
|
146
114
|
//
|
|
147
115
|
// session_shutdown 是 pi 的 async hook,进程被 SIGTERM/SIGINT 强杀或崩溃时来不及
|
|
@@ -248,12 +216,10 @@ export default function subagentsWorkflowExtension(pi: ExtensionAPI): void {
|
|
|
248
216
|
}
|
|
249
217
|
}
|
|
250
218
|
|
|
251
|
-
// resources_discover
|
|
252
|
-
// pi 核心 auto-discovery 已覆盖 .agents/skills
|
|
253
|
-
// --skill 由 agent({skill}) 调用方显式传入,无需
|
|
254
|
-
|
|
255
|
-
return {};
|
|
256
|
-
});
|
|
219
|
+
// resources_discover:不再注册 handler(v4 决策:不再注入额外 skill 目录,
|
|
220
|
+
// ADR-031 废弃 discovery.json)。pi 核心 auto-discovery 已覆盖 .agents/skills
|
|
221
|
+
// 等标准目录,子 session 的 --skill 由 agent({skill}) 调用方显式传入,无需
|
|
222
|
+
// extension 额外补充。
|
|
257
223
|
|
|
258
224
|
// ════════════════════════════════════════════════════════════
|
|
259
225
|
// workflow 域:tools + command + pi.__workflowRun + state
|
|
@@ -381,8 +347,9 @@ export default function subagentsWorkflowExtension(pi: ExtensionAPI): void {
|
|
|
381
347
|
lsRef.lastSessionId = sessionId;
|
|
382
348
|
|
|
383
349
|
// [U7] 引擎列表同步 engines.json(幂等零写 + fail-safe;组合根注册已在
|
|
384
|
-
// extension 工厂体完成,此处 registry
|
|
385
|
-
|
|
350
|
+
// extension 工厂体完成,此处 registry 已含全部引擎)。写 agentDir 全局文件属
|
|
351
|
+
// 跨 session 副作用——oncePerProcess 守卫防 factory 二调/handler 累积双跑(u-audit-fix)。
|
|
352
|
+
oncePerProcess("subagent-workflow:sync-engines-file", () => syncEnginesFile(agentDir));
|
|
386
353
|
|
|
387
354
|
// skill 路径两级缓存 session 级失效:pi 同进程可能有多个 session(TUI /new、/fork),
|
|
388
355
|
// 运行中安装的 skill 需对新 session 可见(含曾 miss 缓存的 undefined 条目与 npm 新装
|
|
@@ -527,21 +494,27 @@ export default function subagentsWorkflowExtension(pi: ExtensionAPI): void {
|
|
|
527
494
|
setSubagentService(service);
|
|
528
495
|
}
|
|
529
496
|
|
|
530
|
-
// S-2: 启动 idle record GC 定时器(30 天 TTL
|
|
531
|
-
|
|
497
|
+
// S-2: 启动 idle record GC 定时器(30 天 TTL,每小时检查一次)。
|
|
498
|
+
// 注册 setInterval 属进程级副作用——oncePerProcess 守卫防双跑(u-audit-fix)。
|
|
499
|
+
oncePerProcess("subagent-workflow:start-gc-timer", () => service.startGcTimer());
|
|
532
500
|
|
|
533
501
|
try {
|
|
534
|
-
|
|
502
|
+
// 递归扫描 <agentDir>/subagents + unlink 超 TTL 跨 session 文件属进程级维护
|
|
503
|
+
// ——oncePerProcess 守卫防双跑(u-audit-fix)。
|
|
504
|
+
oncePerProcess("subagent-workflow:cleanup-expired-session-files", () =>
|
|
505
|
+
maybeCleanupExpiredSessionFiles(agentDir, cwd));
|
|
535
506
|
} catch (err) {
|
|
536
507
|
logger.warn("[subagents] expired session file cleanup failed", {
|
|
537
508
|
reason: err instanceof Error ? err.message : String(err),
|
|
538
509
|
});
|
|
539
510
|
}
|
|
540
511
|
|
|
541
|
-
// ADR-035 启动恢复:扫描 manifest tmp 残留(崩溃打断的 writeManifest
|
|
542
|
-
//
|
|
512
|
+
// ADR-035 启动恢复:扫描 manifest tmp 残留(崩溃打断的 writeManifest 留下,promote/unlink)。
|
|
513
|
+
// 扫描属进程级维护——oncePerProcess 守卫防双跑(u-audit-fix);第二派发重放首次
|
|
514
|
+
// Promise(结果缓存语义),recovered 计数日志可能重打,无文件副作用。
|
|
543
515
|
try {
|
|
544
|
-
const recovered = await
|
|
516
|
+
const recovered = await oncePerProcess("subagent-workflow:recover-manifest-tmp-files", () =>
|
|
517
|
+
service.recoverManifestTmpFiles());
|
|
545
518
|
if (recovered.recovered > 0 || recovered.deleted > 0) {
|
|
546
519
|
logger.warn(`[subagents] manifest tmp recovery: ${recovered.recovered} promoted, ${recovered.deleted} deleted`);
|
|
547
520
|
}
|
|
@@ -552,8 +525,9 @@ export default function subagentsWorkflowExtension(pi: ExtensionAPI): void {
|
|
|
552
525
|
}
|
|
553
526
|
|
|
554
527
|
try {
|
|
555
|
-
|
|
556
|
-
|
|
528
|
+
// 孤儿 worktree 清理(git/rm 进程操作 + 注册表/目录扫描)属进程级维护
|
|
529
|
+
// ——oncePerProcess 守卫防双跑(u-audit-fix)。
|
|
530
|
+
await oncePerProcess("subagent-workflow:worktree-scan", () => new WorktreeManager(agentDir).scan());
|
|
557
531
|
} catch (err) {
|
|
558
532
|
logger.warn("[subagents] worktree reaper scan failed", {
|
|
559
533
|
reason: err instanceof Error ? err.message : String(err),
|
|
@@ -575,49 +549,30 @@ export default function subagentsWorkflowExtension(pi: ExtensionAPI): void {
|
|
|
575
549
|
// resolveIdentity),无需经 state 透传——modelService 是唯一 registry 源。
|
|
576
550
|
|
|
577
551
|
// MF-1: store 健康度跟踪。loadAll 失败 → storeHealthy=false,workflow 域启动时 fail-fast。
|
|
552
|
+
// 崩溃恢复四步(loadAll → failed → save → evict)收口到 core recoverCrashedRuns(D8:
|
|
553
|
+
// 宿主各写一遍正是 failure-mode-B);pending:unregister 经 hooks 外置发射(位置在
|
|
554
|
+
// transition 后、save 前,对齐原内联实现);save 走 store 冷路径(done 绕过去抖)——
|
|
555
|
+
// 冷路径语义在 JsonlRunStore.save 内,不随循环归属转移。loadAll 失败的 fail-fast
|
|
556
|
+
// (storeHealthy=false 停初始化)是宿主职责,core 原样上抛、这里 catch 兜住。
|
|
578
557
|
let storeHealthy = true;
|
|
579
558
|
try {
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
logger.error("[subagent-workflow] kill-9 recovery store.save failed", {
|
|
598
|
-
runId: run.runId,
|
|
599
|
-
reason: err instanceof Error ? err.message : String(err),
|
|
600
|
-
});
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
runs.set(run.runId, run);
|
|
604
|
-
}
|
|
605
|
-
// done run 内存有界性:loadAll 全量重水合后立即裁剪到 K。kill-9 恢复(上方
|
|
606
|
-
// running → transition("done","failed"))的 run completedAt 为 transition 时刻
|
|
607
|
-
// (当前时间=全局最新)参与排序且必在保留端;多条恢复 run 同 ms completedAt →
|
|
608
|
-
// tie 稳定排序。淘汰只 delete runs Map 条目——磁盘 state 文件与
|
|
609
|
-
// workflow-state-link 指针条目均不动(历史审计保留);下次 session_start loadAll
|
|
610
|
-
// 从指针全量重水合后再次裁剪,该循环每次 session 启动重复且可接受:内存峰值只在
|
|
611
|
-
// 启动期,常驻 O(K + 活跃 run)。消除启动峰值需指针 compaction,属 append+replay
|
|
612
|
-
// 长期方案问题域,非本范围。
|
|
613
|
-
const evicted = evictDoneRunsBeyondCap(runs, MAX_RETAINED_DONE_RUNS);
|
|
614
|
-
if (evicted > 0) {
|
|
615
|
-
logger.debug("[subagent-workflow] evicted done runs beyond cap after loadAll", {
|
|
616
|
-
evicted,
|
|
617
|
-
keep: MAX_RETAINED_DONE_RUNS,
|
|
618
|
-
sessionId,
|
|
619
|
-
});
|
|
620
|
-
}
|
|
559
|
+
// 崩溃恢复 loadAll 扫 cwd 共享 sessionDir(同 cwd 跨 session 共享)并把 running run
|
|
560
|
+
// 转 failed 落盘——写非本 session 的 run state 文件属跨 session 副作用,oncePerProcess
|
|
561
|
+
// 守卫防双跑(u-audit-fix)。第二派发重放首次 Promise:不再落盘、不再 emit。
|
|
562
|
+
await oncePerProcess(
|
|
563
|
+
"subagent-workflow:recover-crashed-runs",
|
|
564
|
+
() =>
|
|
565
|
+
recoverCrashedRuns(
|
|
566
|
+
store,
|
|
567
|
+
runs,
|
|
568
|
+
"Process killed (kill-9 or crash recovery)",
|
|
569
|
+
{
|
|
570
|
+
onRunRecovered: (payload) => {
|
|
571
|
+
pi.events.emit("pending:unregister", payload);
|
|
572
|
+
},
|
|
573
|
+
},
|
|
574
|
+
),
|
|
575
|
+
);
|
|
621
576
|
} catch (err) {
|
|
622
577
|
// QMF-4 fix: store.loadAll 失败是关键路径错误,workflow 域将未初始化
|
|
623
578
|
logger.error("[subagent-workflow] store.loadAll failed, workflow domain uninitialized", {
|
|
@@ -997,17 +952,8 @@ function getOrCreateDialogQueue(): DialogGlobalQueue {
|
|
|
997
952
|
return queue;
|
|
998
953
|
}
|
|
999
954
|
|
|
1000
|
-
//
|
|
1001
|
-
//
|
|
1002
|
-
//
|
|
1003
|
-
//
|
|
1004
|
-
//
|
|
1005
|
-
// 让 subagent 子进程的 UI 请求(ask_user 等)透传到主进程渲染。
|
|
1006
|
-
// 重新导出 channel-registry-access 的公开 API——稳定 surface,
|
|
1007
|
-
// 内部存储实现演进不影响消费者。
|
|
1008
|
-
|
|
1009
|
-
export {
|
|
1010
|
-
getOrCreateChannelRegistry,
|
|
1011
|
-
type UiChannelRegistry,
|
|
1012
|
-
type ChannelHandler,
|
|
1013
|
-
} from "@zhushanwen/subagent-core/execution/channel-registry-access.ts";
|
|
955
|
+
// 跨扩展 channel handler 注册入口已收口到 core 深路径
|
|
956
|
+
// `@zhushanwen/subagent-core/execution/channel-registry-access.ts`
|
|
957
|
+
// (getOrCreateChannelRegistry / UiChannelRegistry / ChannelHandler)。
|
|
958
|
+
// 历史上的包根 re-export 已删:ask-user 等跨扩展消费者经 globalThis 握手
|
|
959
|
+
// (DIALOG_QUEUE_KEY 同款进程级单例),不再经包根 import 消费本模块。
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// engine-awareness 单测([engine-awareness U3])
|
|
2
2
|
//
|
|
3
3
|
// 覆盖(设计 docs/design/subagent-engine-awareness-injection.md 验收挂钩 D1/D1b/D2/D3/D5):
|
|
4
|
-
// 1. normalizeEngineId:缺省/空白归一到 'pi'(单一权威源 registry.ts
|
|
4
|
+
// 1. normalizeEngineId:缺省/空白归一到 'pi'(单一权威源 registry.ts,直连导入)
|
|
5
5
|
// 2. buildEngineChangeNotice:§3.1 文案骨架、pi/非 pi 指路段分界、不含任何模型清单(D4)
|
|
6
6
|
// 3. runEngineAwarenessTurn 编排:
|
|
7
7
|
// - 变更触发 apply + 通知(D2 顺序硬约束:提交缓存先于通知先于记账)
|
|
@@ -18,11 +18,11 @@ import { describe, expect, it } from "vitest";
|
|
|
18
18
|
import {
|
|
19
19
|
buildEngineChangeNotice,
|
|
20
20
|
ENGINE_CHANGE_CUSTOM_TYPE,
|
|
21
|
-
normalizeEngineId,
|
|
22
21
|
runEngineAwarenessTurn,
|
|
23
22
|
type EngineAwarenessDeps,
|
|
24
23
|
type EngineAwarenessOutcome,
|
|
25
24
|
} from "../engine-awareness";
|
|
25
|
+
import { normalizeEngineId } from "@zhushanwen/subagent-core/execution/engine/registry.ts";
|
|
26
26
|
import type { GlobalConfigReadResult } from "@zhushanwen/subagent-core/execution/config.ts";
|
|
27
27
|
|
|
28
28
|
// ── 测试数据 ────────────────────────────────────────────
|
|
@@ -26,7 +26,9 @@ import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
|
26
26
|
|
|
27
27
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
// C5①:formatModelList/ModelEntry 下沉 core barrel(setupModelListInjector 留 injector)
|
|
30
|
+
import { formatModelList, type ModelEntry } from "@zhushanwen/subagent-core";
|
|
31
|
+
import { MODEL_LIST_GUIDE, setupModelListInjector } from "../model-list-injector.ts";
|
|
30
32
|
import type { EnginePort } from "@zhushanwen/subagent-core/execution/engine/port.ts";
|
|
31
33
|
import { clearEngines, registerEngine } from "@zhushanwen/subagent-core/execution/engine/registry.ts";
|
|
32
34
|
import {
|
|
@@ -80,7 +82,7 @@ const APPEND_SEPARATOR = "\n\n";
|
|
|
80
82
|
|
|
81
83
|
/** 合成一个 turn 的 system prompt 尾部:BASE + provider models 段 + engine 追加段。 */
|
|
82
84
|
function composeTurn(defaultEngine: string | undefined): string {
|
|
83
|
-
const providerSection = formatModelList(PROVIDER_ENTRIES);
|
|
85
|
+
const providerSection = formatModelList(PROVIDER_ENTRIES, { guide: MODEL_LIST_GUIDE });
|
|
84
86
|
const afterProvider = providerSection === "" ? BASE : BASE + providerSection;
|
|
85
87
|
const append = composeEngineAppend(defaultEngine);
|
|
86
88
|
return append === "" ? afterProvider : `${afterProvider}${APPEND_SEPARATOR}${append}`;
|
|
@@ -190,7 +192,7 @@ describe("段渲染确定性(D7:同输入恒同输出)", () => {
|
|
|
190
192
|
* - 切换后的引擎追加段恰居 prompt 尾部。
|
|
191
193
|
*/
|
|
192
194
|
function expectOnlyTailDiffers(from: string | undefined, to: string | undefined): void {
|
|
193
|
-
const stableHead = BASE + formatModelList(PROVIDER_ENTRIES);
|
|
195
|
+
const stableHead = BASE + formatModelList(PROVIDER_ENTRIES, { guide: MODEL_LIST_GUIDE });
|
|
194
196
|
const before = composeTurn(from);
|
|
195
197
|
const after = composeTurn(to);
|
|
196
198
|
const beforeAppend = composeEngineAppend(from);
|
|
@@ -279,7 +281,7 @@ describe("段序守护:engine 段恒链尾(D7)", () => {
|
|
|
279
281
|
// 恒链尾:engine 追加段是 prompt 的最后内容
|
|
280
282
|
expect(prompt.endsWith(`${APPEND_SEPARATOR}${composeEngineAppend(defaultEngine)}`)).toBe(true);
|
|
281
283
|
// model list handler 注入形态锚定(BASE + injection 无分隔,生产 handler 同款)
|
|
282
|
-
expect(prompt.startsWith(BASE + formatModelList(PROVIDER_ENTRIES))).toBe(true);
|
|
284
|
+
expect(prompt.startsWith(BASE + formatModelList(PROVIDER_ENTRIES, { guide: MODEL_LIST_GUIDE }))).toBe(true);
|
|
283
285
|
});
|
|
284
286
|
|
|
285
287
|
it("对照:注册序颠倒(engine 先、model list 后)时段序断言必失败——守护有判别力", async () => {
|