@istuen/pt 0.1.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/LICENSE +21 -0
- package/README.md +219 -0
- package/dist/agent/api-bridge.d.ts +8 -0
- package/dist/agent/api-bridge.js +83 -0
- package/dist/agent/index.d.ts +2 -0
- package/dist/agent/index.js +3 -0
- package/dist/agent/pi-adapter.d.ts +35 -0
- package/dist/agent/pi-adapter.js +251 -0
- package/dist/agent/registry.d.ts +4 -0
- package/dist/agent/registry.js +40 -0
- package/dist/asset-health.d.ts +50 -0
- package/dist/asset-health.js +267 -0
- package/dist/asset-pack/loader.d.ts +47 -0
- package/dist/asset-pack/loader.js +121 -0
- package/dist/asset-pack/manifest.d.ts +23 -0
- package/dist/asset-pack/manifest.js +96 -0
- package/dist/asset-pack/md-file-pack.d.ts +41 -0
- package/dist/asset-pack/md-file-pack.js +153 -0
- package/dist/asset-pack/validate.d.ts +55 -0
- package/dist/asset-pack/validate.js +131 -0
- package/dist/builtin/assets/blueprints/dev-knowledge.blueprint.yaml +13 -0
- package/dist/builtin/assets/domains/agent-info.md +15 -0
- package/dist/builtin/assets/domains/authoring.md +201 -0
- package/dist/builtin/assets/domains/pack-repair.md +47 -0
- package/dist/builtin/assets/domains/project-analysis.md +52 -0
- package/dist/builtin/assets/domains/usage.md +81 -0
- package/dist/builtin/assets/domains/user-info.md +15 -0
- package/dist/builtin/assets/profiles/guide.profile.md +28 -0
- package/dist/commands.d.ts +69 -0
- package/dist/commands.js +344 -0
- package/dist/compile/agent-context.d.ts +48 -0
- package/dist/compile/agent-context.js +423 -0
- package/dist/compile/index.d.ts +1 -0
- package/dist/compile/index.js +5 -0
- package/dist/compile/resolve-use.d.ts +36 -0
- package/dist/compile/resolve-use.js +171 -0
- package/dist/compile/type-guards.d.ts +29 -0
- package/dist/compile/type-guards.js +107 -0
- package/dist/config.d.ts +31 -0
- package/dist/config.js +133 -0
- package/dist/constants.d.ts +52 -0
- package/dist/constants.js +81 -0
- package/dist/diagnostics.d.ts +14 -0
- package/dist/diagnostics.js +29 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +838 -0
- package/dist/injection-status.d.ts +45 -0
- package/dist/injection-status.js +134 -0
- package/dist/log.d.ts +64 -0
- package/dist/log.js +139 -0
- package/dist/manual-session.d.ts +24 -0
- package/dist/manual-session.js +152 -0
- package/dist/manual-track.d.ts +35 -0
- package/dist/manual-track.js +107 -0
- package/dist/parse/blueprint.d.ts +5 -0
- package/dist/parse/blueprint.js +59 -0
- package/dist/parse/domain-renderers.d.ts +8 -0
- package/dist/parse/domain-renderers.js +115 -0
- package/dist/parse/domain.d.ts +7 -0
- package/dist/parse/domain.js +69 -0
- package/dist/parse/index.d.ts +9 -0
- package/dist/parse/index.js +223 -0
- package/dist/parse/profile.d.ts +18 -0
- package/dist/parse/profile.js +117 -0
- package/dist/parse/ref-resolver.d.ts +63 -0
- package/dist/parse/ref-resolver.js +174 -0
- package/dist/parse/shared.d.ts +76 -0
- package/dist/parse/shared.js +273 -0
- package/dist/profile-persist.d.ts +22 -0
- package/dist/profile-persist.js +53 -0
- package/dist/render/cache.d.ts +15 -0
- package/dist/render/cache.js +135 -0
- package/dist/render/index.d.ts +3 -0
- package/dist/render/index.js +14 -0
- package/dist/render/session-inject.d.ts +5 -0
- package/dist/render/session-inject.js +24 -0
- package/dist/render/turn-inject.d.ts +36 -0
- package/dist/render/turn-inject.js +248 -0
- package/dist/schema.d.ts +448 -0
- package/dist/schema.js +45 -0
- package/dist/session.d.ts +82 -0
- package/dist/session.js +102 -0
- package/dist/slog.d.ts +3 -0
- package/dist/slog.js +27 -0
- package/dist/transpile.d.ts +34 -0
- package/dist/transpile.js +220 -0
- package/dist/verify/file-hash.d.ts +2 -0
- package/dist/verify/file-hash.js +30 -0
- package/dist/verify/fs-content-match.d.ts +2 -0
- package/dist/verify/fs-content-match.js +26 -0
- package/dist/verify/fs-exists.d.ts +2 -0
- package/dist/verify/fs-exists.js +13 -0
- package/dist/verify/fs-not-exists.d.ts +2 -0
- package/dist/verify/fs-not-exists.js +13 -0
- package/dist/verify/git-status-clean.d.ts +2 -0
- package/dist/verify/git-status-clean.js +19 -0
- package/dist/verify/index.d.ts +7 -0
- package/dist/verify/index.js +46 -0
- package/dist/verify/lint-check.d.ts +2 -0
- package/dist/verify/lint-check.js +17 -0
- package/dist/verify/read-stderr.d.ts +5 -0
- package/dist/verify/read-stderr.js +20 -0
- package/dist/verify/ref-check.d.ts +13 -0
- package/dist/verify/ref-check.js +88 -0
- package/dist/verify/test-pass.d.ts +2 -0
- package/dist/verify/test-pass.js +17 -0
- package/dist/verify/ts-compiles.d.ts +2 -0
- package/dist/verify/ts-compiles.js +17 -0
- package/package.json +62 -0
- package/src/agent/api-bridge.ts +105 -0
- package/src/agent/index.ts +4 -0
- package/src/agent/pi-adapter.ts +326 -0
- package/src/agent/registry.ts +44 -0
- package/src/asset-health.ts +327 -0
- package/src/asset-pack/loader.ts +141 -0
- package/src/asset-pack/manifest.ts +118 -0
- package/src/asset-pack/md-file-pack.ts +202 -0
- package/src/asset-pack/validate.ts +186 -0
- package/src/builtin/assets/blueprints/dev-knowledge.blueprint.yaml +13 -0
- package/src/builtin/assets/domains/agent-info.md +15 -0
- package/src/builtin/assets/domains/authoring.md +201 -0
- package/src/builtin/assets/domains/pack-repair.md +47 -0
- package/src/builtin/assets/domains/project-analysis.md +52 -0
- package/src/builtin/assets/domains/usage.md +81 -0
- package/src/builtin/assets/domains/user-info.md +15 -0
- package/src/builtin/assets/profiles/guide.profile.md +28 -0
- package/src/commands.ts +405 -0
- package/src/compile/agent-context.ts +487 -0
- package/src/compile/index.ts +5 -0
- package/src/compile/resolve-use.ts +208 -0
- package/src/compile/type-guards.ts +132 -0
- package/src/config.ts +154 -0
- package/src/constants.ts +104 -0
- package/src/diagnostics.ts +36 -0
- package/src/index.ts +1028 -0
- package/src/injection-status.ts +155 -0
- package/src/log.ts +173 -0
- package/src/manual-session.ts +190 -0
- package/src/manual-track.ts +127 -0
- package/src/parse/blueprint.ts +82 -0
- package/src/parse/domain-renderers.ts +120 -0
- package/src/parse/domain.ts +78 -0
- package/src/parse/index.ts +266 -0
- package/src/parse/profile.ts +139 -0
- package/src/parse/ref-resolver.ts +198 -0
- package/src/parse/shared.ts +331 -0
- package/src/profile-persist.ts +60 -0
- package/src/render/cache.ts +150 -0
- package/src/render/index.ts +14 -0
- package/src/render/session-inject.ts +26 -0
- package/src/render/turn-inject.ts +278 -0
- package/src/schema.ts +542 -0
- package/src/session.ts +190 -0
- package/src/slog.ts +32 -0
- package/src/transpile.ts +302 -0
- package/src/verify/file-hash.ts +29 -0
- package/src/verify/fs-content-match.ts +28 -0
- package/src/verify/fs-exists.ts +14 -0
- package/src/verify/fs-not-exists.ts +17 -0
- package/src/verify/git-status-clean.ts +23 -0
- package/src/verify/index.ts +57 -0
- package/src/verify/lint-check.ts +21 -0
- package/src/verify/read-stderr.ts +24 -0
- package/src/verify/ref-check.ts +118 -0
- package/src/verify/test-pass.ts +18 -0
- package/src/verify/ts-compiles.ts +21 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 istuen
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# Pt
|
|
2
|
+
|
|
3
|
+
Pt 把领域知识做成可配置的智能体上下文。通过切换配置,Agent 具备不同 Session Context(每轮注入),推理时可通过 Turn Context(按需触发)查阅手册。
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 用 Pt 做什么
|
|
8
|
+
|
|
9
|
+
- **复用 Prompt,不必每次重写**:把重复的 prompt 写成 Pt Domain,配一份 Profile,每次会话切换立即获取领域知识——换项目、换场景,不用重写。
|
|
10
|
+
- **拆分固定与按需,控制上下文成本**:领域知识拆成 Session Context(每轮注入的固定知识 + 参考索引)和 Turn Context(按索引触发的详细手册)——背景每轮都在,详细内容推理时按需查阅。
|
|
11
|
+
- **沉淀对话与经验为可迭代资产**:把对话里的决策、经验、知识用文档显化,入 git 可追溯、可迭代——知识更新改一处,所有会话生效。
|
|
12
|
+
- **连接外部系统与数据源**:领域知识不限于项目内——外部数据源的查询方式、外部系统的操作指引,都可写成 Domain 注入 Agent。
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 安装
|
|
17
|
+
|
|
18
|
+
Pt 是 Pi 扩展,两种安装方式任选其一。
|
|
19
|
+
|
|
20
|
+
### 方式一:Pi 插件安装(推荐)
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pi install npm:@istuen/pt
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Pi 自动加载 Pt。项目级安装(团队共享,写入 `.pi/settings.json`)加 `-l`:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pi install -l npm:@istuen/pt
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### 方式二:npm 安装 + 手动配置
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npm install @istuen/pt
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
然后在 `.pi/settings.json` 或 `~/.pi/agent/settings.json` 配置扩展入口:
|
|
39
|
+
|
|
40
|
+
```json
|
|
41
|
+
{
|
|
42
|
+
"pi": {
|
|
43
|
+
"extensions": ["./node_modules/@istuen/pt/dist/index.js"]
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## 快速开始
|
|
49
|
+
|
|
50
|
+
### 1. 激活内建 Profile
|
|
51
|
+
|
|
52
|
+
进入 pi 后用 `/pt-profile` 选择 `guide`,或直接 `/pt-profile guide`:
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
/pt-profile guide
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
也可以启动时用 flag 激活:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
pi --pt-profile guide
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
内建 `guide` Profile 用 dev-knowledge Blueprint + 五个内建 Domain(`user-info` / `agent-info` / `project-analysis` / `authoring` / `usage`)——Agent 立刻有“用户身份 + Agent 身份 + 怎么写 Pt 资产 / 怎么分析项目 / 怎么用 Pt”的知识。
|
|
65
|
+
|
|
66
|
+
### 2. 加自己的知识
|
|
67
|
+
|
|
68
|
+
用自然语言告诉 Agent 你的项目情况——`guide` Profile 会分析当前项目,帮你创建合适的 Domain 和 Profile,或直接告诉你该怎么创建。
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## 命令速查
|
|
73
|
+
|
|
74
|
+
| 命令 | 作用 |
|
|
75
|
+
|---|---|
|
|
76
|
+
| `/pt-profile` | 列出所有可用 Profile |
|
|
77
|
+
| `/pt-profile <name>` | 切换 Profile(下一轮生效) |
|
|
78
|
+
| `/pt` | 查看当前编译状态 |
|
|
79
|
+
| `/pt flows` | 列出可触发手册 |
|
|
80
|
+
| `/manual:<domain>` | 注入该 Domain 的手册段到 Turn Inject |
|
|
81
|
+
| `/<flow> <args>` | 触发 Domain 的 FlowTemplate |
|
|
82
|
+
| `--pt-profile <name>` | Pi 启动时激活 Profile(CLI 优先级最高) |
|
|
83
|
+
|
|
84
|
+
LLM 工具(Agent 可调用):`pt_status` / `pt_flows` / `pt_manual` / `pt_verify` / `pt_check_refs`。
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Pt 的机制:四个概念
|
|
89
|
+
|
|
90
|
+
Pt 用四个概念组织——写领域知识,定转换结构,组装身份配置,编译出上下文:
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
Pt Domain ──→ Blueprint ──→ Pt Profile ──→ Agent Context
|
|
94
|
+
领域知识 转换结构 身份配置 编译后上下文
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Pt Domain — 领域知识
|
|
98
|
+
|
|
99
|
+
领域知识,一个 `.md` 文件。用 MD 语法组成:**H2 做 Module**(内容段,如 `## Scene`、`## Flows`)、**H3 做语义**(项名,如 `### 角色分工`)、**列表项做描述**(`- desc: ...`)。可写业务领域的设计语义、工作流说明、外部系统操作指导。Module 允许自定义,通过 Profile 引用即可。放 `.pt/assets/domains/`。
|
|
100
|
+
|
|
101
|
+
例子:
|
|
102
|
+
```markdown
|
|
103
|
+
---
|
|
104
|
+
name: user-info
|
|
105
|
+
---
|
|
106
|
+
# user-info
|
|
107
|
+
|
|
108
|
+
## User
|
|
109
|
+
### who-am-i
|
|
110
|
+
- desc: 我是这个项目的开发者
|
|
111
|
+
|
|
112
|
+
### preferences
|
|
113
|
+
- desc: 偏好类型安全、模块化设计
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Blueprint — 转换结构
|
|
117
|
+
|
|
118
|
+
转换结构,一个 `.blueprint.yaml` 文件。定义 Profile 可以把哪些 Domain 注入到 Agent——声明有哪些聚合组(`groups`),每个组聚合哪些 Module,注入到哪(`inject: session` 会话级 / `inject: turn` 轮次级)。跨项目复用。放 `.pt/assets/blueprints/`。
|
|
119
|
+
|
|
120
|
+
```yaml
|
|
121
|
+
name: dev-knowledge
|
|
122
|
+
groups:
|
|
123
|
+
- name: 会话背景
|
|
124
|
+
inject: session
|
|
125
|
+
modules: [Scene, Participant]
|
|
126
|
+
- name: 触发索引
|
|
127
|
+
inject: session
|
|
128
|
+
modules: [Trigger]
|
|
129
|
+
- name: 参考手册
|
|
130
|
+
inject: turn
|
|
131
|
+
modules: [Rules, Flows, Checklists]
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Pt Profile — 身份配置
|
|
135
|
+
|
|
136
|
+
Profile 是 Pt 最核心的功能,承接用户与 Agent 之间的会话配置。切换 Profile 会影响:会话背景、主题、用户与 Agent 的身份信息、参考信息等——让 Agent 专注当前推理范围。一个 `.profile.md` 文件,选一个 Blueprint + 列要用的 Domain。项目级,不跨项目复用。放 `.pt/assets/profiles/`。
|
|
137
|
+
|
|
138
|
+
例子:
|
|
139
|
+
```markdown
|
|
140
|
+
---
|
|
141
|
+
name: my-dev
|
|
142
|
+
blueprint: dev-knowledge
|
|
143
|
+
domains: [user-info, authoring, usage]
|
|
144
|
+
---
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Agent Context — 编译后上下文
|
|
148
|
+
|
|
149
|
+
Profile 编译后的产物,分两面——**Session Context**(每轮注入:会话背景、身份信息、触发索引)和 **Turn Context**(按需触发:推理时查阅的参考手册)。带 hash 缓存(`.pt/cache/agent-contexts/`),资产变了自动重编译。
|
|
150
|
+
|
|
151
|
+
**一句话串起来**:写 Domain → 用 Blueprint 定转换结构 → 用 Profile 组装身份 → 编译出 Agent Context 两面注入。
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## Pt 资产包(Pack)
|
|
156
|
+
|
|
157
|
+
v15.x 起,Pt 资产按 **Pack** 组织——一个 Pack 是一个目录(含 `domains/` + `blueprints/` + `profiles/` 子目录 + 可选 `pt-asset-pack.yaml` manifest)。Pt 加载 4 类 Pack,优先级从高到低:
|
|
158
|
+
|
|
159
|
+
| Pack | 来源 | 寻址名 | 说明 |
|
|
160
|
+
|---|---|---|---|
|
|
161
|
+
| **project** | `<cwd>/.pt/assets/`(或 `pt.project-pack-dir` 配置) | `@prj` | 项目专属资产,永远最高优先 |
|
|
162
|
+
| **settings** | `.pi/settings.json` 的 `pt.asset-packs[]` | `@<manifest-name>` | 团队共享 / 第三方 Pack,按声明顺序后者赢 |
|
|
163
|
+
| **global** | `~/.pt/assets/` | `@gbl` | 用户跨项目共用资产 |
|
|
164
|
+
| **builtin** | `src/builtin/assets/`(随 npm 包) | `@pt` | 内建 fallback(guide / dev-knowledge 等) |
|
|
165
|
+
|
|
166
|
+
### 跨 Pack 引用
|
|
167
|
+
|
|
168
|
+
Profile / Domain / Blueprint 引用可限定 Pack:`@pack-name/asset-name`。不限定时按优先级自动解析(project > settings > global > builtin)。
|
|
169
|
+
|
|
170
|
+
```yaml
|
|
171
|
+
# profile.md frontmatter
|
|
172
|
+
blueprint: @pt-internal/dev-knowledge # 限定到 pt-internal pack
|
|
173
|
+
domains: [@gbl/team-stdlib, workflow] # @gbl 限定 + 无前缀自动解析
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### settings 声明 Pack
|
|
177
|
+
|
|
178
|
+
在 `.pi/settings.json` 加载第三方 / 团队 Pack(只声明 path,name 从 manifest 读):
|
|
179
|
+
|
|
180
|
+
```json
|
|
181
|
+
{
|
|
182
|
+
"pt": {
|
|
183
|
+
"asset-packs": [
|
|
184
|
+
{ "path": "~/projects/pt-team-stdlib/assets" },
|
|
185
|
+
{ "path": "../shared-pt-assets" }
|
|
186
|
+
],
|
|
187
|
+
"project-pack-dir": ".pt/assets"
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
`project-pack-dir` 可指向项目外路径(`~` / 绝对 / 相对 cwd 都支持)。
|
|
193
|
+
|
|
194
|
+
### Profile `use` 单继承
|
|
195
|
+
|
|
196
|
+
Profile 可 `use` 另一 Profile 作为基础,增量覆盖:
|
|
197
|
+
|
|
198
|
+
```yaml
|
|
199
|
+
---
|
|
200
|
+
name: my-dev
|
|
201
|
+
use: @pt-internal/pt-dev # 继承 pt-dev 作为基础
|
|
202
|
+
blueprint: @team-stdlib/minimal # 覆盖 use 的 blueprint
|
|
203
|
+
domains: [my-domain] # 追加到 use 的 domains
|
|
204
|
+
---
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
合并规则:`name` 强制(不继承)/ `blueprint` 覆盖 / `tagline` 覆盖 / `domains` 追加去重 / `groups` 同名替换。不写 `use` = 完全独立 Profile(back-compat)。
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## 开发
|
|
212
|
+
|
|
213
|
+
技术栈:TypeScript + tsup + Vitest + Biome。零运行时依赖(除 yaml 库用于 Blueprint 解析)。
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## License
|
|
218
|
+
|
|
219
|
+
MIT
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import type { AgentAPI, AgentUI } from "../schema.js";
|
|
3
|
+
/** AgentAPI 的 ui 字段类型(NonNullable<AgentAPI["ui"]>)。 */
|
|
4
|
+
type AgentUIContext = AgentUI;
|
|
5
|
+
export declare function toAgentAPI(pi: ExtensionAPI, ctx: {
|
|
6
|
+
ui: AgentUIContext;
|
|
7
|
+
}): AgentAPI;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// src/agent/api-bridge.ts — Pi ExtensionAPI ↔ AgentAPI 桥接(P1.3 抽出)
|
|
2
|
+
//
|
|
3
|
+
// 把 Pi ExtensionAPI 转换成 AgentAPI(结构类型子集)供 AgentAdapter 使用。
|
|
4
|
+
// 同一个 Pi runtime 复用同一个 AgentAPI wrapper——否则 PiAdapter 每次 registerInject
|
|
5
|
+
// 都会得到不同的对象身份,导致 system prompt handler 被重复注册。
|
|
6
|
+
//
|
|
7
|
+
// v12.x(issue pt-session-singleton-pi-web-pollution 修复):
|
|
8
|
+
// - 原设计 `api.log` getter / `onInjected` 直接读写 module-level `session` 单例
|
|
9
|
+
// → tab B 的 wrapper.onInjected 写 tab B 的 lastBuiltPrompt 但走的是 module 单例
|
|
10
|
+
// → tab A 跑 turn 时 `lastBuiltPrompt` 也是 tab B 的(被覆盖)
|
|
11
|
+
// - 改造为 per-pi wrapper 内部维护 `currentSessionId` 闭包变量:
|
|
12
|
+
// - wrapper 的 `api.on` 包装函数在每个 handler 触发前自动从 `args[1]?.sessionManager`
|
|
13
|
+
// 拿 sessionId 写入 `currentSessionId`
|
|
14
|
+
// - `api.log` getter / `onInjected` 通过 `currentSessionId` 走 `getSessionById(...)`
|
|
15
|
+
// 取 per-session state
|
|
16
|
+
// - TUI 模式下只有一个 pi 实例,行为等同 module-level 单例(同一 sessionId 始终)
|
|
17
|
+
import { getSessionById } from "../session.js";
|
|
18
|
+
/** 将 Pi ExtensionAPI 转换为 AgentAPI(结构类型子集,运行时透明)。
|
|
19
|
+
* Pi ExtensionAPI 是 AgentAPI 的超集,多余方法(registerCommand/registerFlag 等)不暴露给 Adapter。
|
|
20
|
+
* v9.1:提供 ui 能力,adapter 可走 ui.notify 报错 / ui.setStatus 设状态(不需 console)。
|
|
21
|
+
* v10.x:提供 log 能力,adapter 的 try/catch 异常走 session.logger(不再 swallow)。
|
|
22
|
+
* ctx 用 Pi 扩展的 ctx(ExtensionContext/ExtensionCommandContext 都含 ui)——子集够用。
|
|
23
|
+
*
|
|
24
|
+
* 同一个 Pi runtime 复用同一个 AgentAPI wrapper,否则 PiAdapter 每次 registerInject
|
|
25
|
+
* 都会得到不同的对象身份,导致系统 prompt handler 被重复注册。 */
|
|
26
|
+
const agentApiCache = new WeakMap();
|
|
27
|
+
export function toAgentAPI(pi, ctx) {
|
|
28
|
+
let holder = agentApiCache.get(pi);
|
|
29
|
+
if (!holder) {
|
|
30
|
+
let currentCtx = ctx;
|
|
31
|
+
// v12.x:closure 捕获 currentSessionId,供 api.log/onInjected 拿 per-session state
|
|
32
|
+
const stateRef = { currentSessionId: undefined };
|
|
33
|
+
// v12.x:on 包装——handler 触发前自动从 args[1] ctx 拿 sessionId 注入 stateRef
|
|
34
|
+
const onWrapped = (event, handler) => {
|
|
35
|
+
pi.on(event, ((event, extCtx) => {
|
|
36
|
+
// 提取 sessionId:extCtx 是 ExtensionContext
|
|
37
|
+
const c = extCtx;
|
|
38
|
+
stateRef.currentSessionId = c?.sessionManager?.getSessionId?.();
|
|
39
|
+
return handler(event, extCtx);
|
|
40
|
+
}));
|
|
41
|
+
};
|
|
42
|
+
const api = {
|
|
43
|
+
on: onWrapped,
|
|
44
|
+
registerCommand: (name, spec) => pi.registerCommand(name, spec),
|
|
45
|
+
registerFlag: (name, spec) => pi.registerFlag(name, spec),
|
|
46
|
+
getFlag: (name) => pi.getFlag(name),
|
|
47
|
+
ui: {
|
|
48
|
+
notify: (msg, level) => currentCtx.ui.notify(msg, level),
|
|
49
|
+
setStatus: (name, text) => currentCtx.ui.setStatus(name, text),
|
|
50
|
+
},
|
|
51
|
+
// v12.x:api.log 走 per-session logger(避免 tab A 写到 tab B 的日志文件)
|
|
52
|
+
get log() {
|
|
53
|
+
const sid = stateRef.currentSessionId;
|
|
54
|
+
return sid ? getSessionById(sid).logger?.toWriter() : undefined;
|
|
55
|
+
},
|
|
56
|
+
// v12.x:onInjected 写 per-session lastBuiltPrompt
|
|
57
|
+
onInjected: (systemPrompt) => {
|
|
58
|
+
const sid = stateRef.currentSessionId;
|
|
59
|
+
if (sid)
|
|
60
|
+
getSessionById(sid).lastBuiltPrompt = systemPrompt;
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
holder = {
|
|
64
|
+
api,
|
|
65
|
+
setContext: (nextCtx) => {
|
|
66
|
+
currentCtx = nextCtx;
|
|
67
|
+
},
|
|
68
|
+
currentSessionId: undefined,
|
|
69
|
+
};
|
|
70
|
+
// 把 stateRef 同步到 holder(保持单一来源)
|
|
71
|
+
Object.defineProperty(holder, "currentSessionId", {
|
|
72
|
+
get: () => stateRef.currentSessionId,
|
|
73
|
+
set: (v) => {
|
|
74
|
+
stateRef.currentSessionId = v;
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
agentApiCache.set(pi, holder);
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
holder.setContext(ctx);
|
|
81
|
+
}
|
|
82
|
+
return holder.api;
|
|
83
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type AgentAdapter, type AgentAPI, type AgentContext, type Blueprint, type Domain, type Profile } from "../schema.js";
|
|
2
|
+
/** PiAdapter:封装 Pi Agent 的注入机制。 */
|
|
3
|
+
export declare class PiAdapter implements AgentAdapter {
|
|
4
|
+
name: string;
|
|
5
|
+
/** Phase term-P4.3:保留 Pi API 名 system_prompt/context_message——
|
|
6
|
+
* 这是 AgentAdapter 映射边界声明(Pi 支持哪些 Agent Runtime 层注入位置)。
|
|
7
|
+
* Blueprint 用 session/turn 语义值(聚合组 inject 字段),Adapter 内部映射到此字段声明的 Pi API 名。 */
|
|
8
|
+
supportedTargets: string[];
|
|
9
|
+
private ctx;
|
|
10
|
+
private blueprint;
|
|
11
|
+
private domains;
|
|
12
|
+
private segment;
|
|
13
|
+
/** 与当前 Pi runtime 的 AgentAPI 绑定;同一 runtime 不重复注册 handler。
|
|
14
|
+
* v12.x:per-pi 实例字段——`registry.ts` 给每个 pi 一个新 PiAdapter,所以 `injectedApi`
|
|
15
|
+
* 不会被其他 session 覆盖。 */
|
|
16
|
+
private injectedApi;
|
|
17
|
+
/** v13.x(issue pt-turn-inject-not-profile-scoped):当前 Profile——turn 路径 scope 过滤用。 */
|
|
18
|
+
private profile;
|
|
19
|
+
/** 设置编译产物(transpile 后调)。v12.x:per-pi 实例字段——本 session 的 segment
|
|
20
|
+
* 不会被其他 session 覆盖。
|
|
21
|
+
* v13.x(issue pt-turn-inject-not-profile-scoped):加 profile 参数——turn 路径 scope 过滤用。 */
|
|
22
|
+
setAgentContext(ctx: AgentContext, blueprint: Blueprint, domains: Domain[], profile: Profile): void;
|
|
23
|
+
/** 清除当前 session 的 context;保留当前 runtime 的 handler 绑定。
|
|
24
|
+
* v12.x:per-pi 实例字段——只清本 session 的状态。 */
|
|
25
|
+
resetInjection(): void;
|
|
26
|
+
/** 启动时注册:把 AgentContext 注入到 Agent。 */
|
|
27
|
+
registerInject(api: AgentAPI, ctx: AgentContext, blueprint: Blueprint, domains?: Domain[], profile?: Profile | null): void;
|
|
28
|
+
/** 查询可用手册(/pt flows 用)。
|
|
29
|
+
* v9:遍历 Blueprint 的 inject=turn 聚合组 → 引用 Domain → 找 FlowTemplate + Rules 段的 Rule。 */
|
|
30
|
+
listManuals(_ctx: AgentContext, blueprint: Blueprint, domains: Domain[]): Array<{
|
|
31
|
+
name: string;
|
|
32
|
+
hint?: string;
|
|
33
|
+
domain: string;
|
|
34
|
+
}>;
|
|
35
|
+
}
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
// src/agent/pi-adapter.ts — PiAdapter:封装 Pi Agent 注入机制
|
|
2
|
+
//
|
|
3
|
+
// Phase 9.6:v9 新增 — AgentAdapter 的 Pi 实现。
|
|
4
|
+
// - system_prompt 注入:api.on("before_agent_start") 每轮追加 segment
|
|
5
|
+
// - context_message 触发:api.on("input") 拦截 /manual:xxx 和 /<flow-name>
|
|
6
|
+
// - 这些是 Agent Runtime 层(Pi API),本 Adapter 在该层做 session→system_prompt、turn→context_message 映射
|
|
7
|
+
//
|
|
8
|
+
// Pt 核心只调 AgentAdapter 接口,不直接调 Pi API。加新 Agent 只加 Adapter。
|
|
9
|
+
//
|
|
10
|
+
// Tech Debt T6: 全用 type guard 收窄,不用 as 断言(pt-quality #1)
|
|
11
|
+
// Tech Debt T2: 用 constants 模块名常量(pt-quality #5)
|
|
12
|
+
//
|
|
13
|
+
// v12.x(issue pt-session-singleton-pi-web-pollution 修复):
|
|
14
|
+
// - handler 内部读 `args[1]?.sessionManager?.getSessionId()` 拿 per-session sessionId
|
|
15
|
+
// - 通过 `getSessionById(sessionId)` 拿 per-session SessionState,**不再读 module-level 单例**
|
|
16
|
+
// - 配合 `src/agent/registry.ts` 的 per-pi WeakMap 改造,
|
|
17
|
+
// `this.ctx / this.blueprint / this.domains / this.segment / this.injectedApi` 五个实例字段
|
|
18
|
+
// 现在是 per-pi 隔离——其他 session 的 setContext 不会覆盖本 session 的 segment
|
|
19
|
+
import { AGENT_PI, MOD_FLOWS, MOD_RULES } from "../constants.js";
|
|
20
|
+
import { isFlowTemplateArray, isRuleArray } from "../compile/type-guards.js";
|
|
21
|
+
import { renderInjectionFooter } from "../injection-status.js";
|
|
22
|
+
import { getSessionById } from "../session.js";
|
|
23
|
+
import { filterDomainsByProfile, } from "../schema.js";
|
|
24
|
+
import { renderTurnInject } from "../render/turn-inject.js";
|
|
25
|
+
import { renderSessionInject } from "../render/session-inject.js";
|
|
26
|
+
/** v12.x:从 handler 的 args[1] ctx 提取 sessionId。
|
|
27
|
+
* pi 的 `pi.on(event, handler)` 触发时传 `(event, ctx)` 两个参数;通过 AgentAPI 包装后
|
|
28
|
+
* handler 拿到 `(...args)`,args[0] = event, args[1] = ctx (ExtensionContext)。 */
|
|
29
|
+
function sessionIdFromArgs(args) {
|
|
30
|
+
const ctx = args[1];
|
|
31
|
+
return ctx?.sessionManager?.getSessionId?.();
|
|
32
|
+
}
|
|
33
|
+
/** PiAdapter:封装 Pi Agent 的注入机制。 */
|
|
34
|
+
export class PiAdapter {
|
|
35
|
+
name = AGENT_PI;
|
|
36
|
+
/** Phase term-P4.3:保留 Pi API 名 system_prompt/context_message——
|
|
37
|
+
* 这是 AgentAdapter 映射边界声明(Pi 支持哪些 Agent Runtime 层注入位置)。
|
|
38
|
+
* Blueprint 用 session/turn 语义值(聚合组 inject 字段),Adapter 内部映射到此字段声明的 Pi API 名。 */
|
|
39
|
+
supportedTargets = ["system_prompt", "context_message"];
|
|
40
|
+
ctx = null;
|
|
41
|
+
blueprint = null;
|
|
42
|
+
domains = [];
|
|
43
|
+
segment = null;
|
|
44
|
+
/** 与当前 Pi runtime 的 AgentAPI 绑定;同一 runtime 不重复注册 handler。
|
|
45
|
+
* v12.x:per-pi 实例字段——`registry.ts` 给每个 pi 一个新 PiAdapter,所以 `injectedApi`
|
|
46
|
+
* 不会被其他 session 覆盖。 */
|
|
47
|
+
injectedApi = null;
|
|
48
|
+
/** v13.x(issue pt-turn-inject-not-profile-scoped):当前 Profile——turn 路径 scope 过滤用。 */
|
|
49
|
+
profile = null;
|
|
50
|
+
/** 设置编译产物(transpile 后调)。v12.x:per-pi 实例字段——本 session 的 segment
|
|
51
|
+
* 不会被其他 session 覆盖。
|
|
52
|
+
* v13.x(issue pt-turn-inject-not-profile-scoped):加 profile 参数——turn 路径 scope 过滤用。 */
|
|
53
|
+
setAgentContext(ctx, blueprint, domains, profile) {
|
|
54
|
+
this.ctx = ctx;
|
|
55
|
+
this.blueprint = blueprint;
|
|
56
|
+
this.domains = domains;
|
|
57
|
+
this.profile = profile;
|
|
58
|
+
this.segment = renderSessionInject(ctx, blueprint);
|
|
59
|
+
}
|
|
60
|
+
/** 清除当前 session 的 context;保留当前 runtime 的 handler 绑定。
|
|
61
|
+
* v12.x:per-pi 实例字段——只清本 session 的状态。 */
|
|
62
|
+
resetInjection() {
|
|
63
|
+
this.ctx = null;
|
|
64
|
+
this.blueprint = null;
|
|
65
|
+
this.domains = [];
|
|
66
|
+
this.profile = null;
|
|
67
|
+
this.segment = null;
|
|
68
|
+
}
|
|
69
|
+
/** 启动时注册:把 AgentContext 注入到 Agent。 */
|
|
70
|
+
registerInject(api, ctx, blueprint, domains = this.domains, profile = this.profile) {
|
|
71
|
+
// 先更新状态;同一 runtime 的后续 Profile 切换不能重新注册 handler,
|
|
72
|
+
// 但 handler 会在事件发生时读取最新的 this.segment / this.ctx。
|
|
73
|
+
this.ctx = ctx;
|
|
74
|
+
this.blueprint = blueprint;
|
|
75
|
+
this.domains = domains;
|
|
76
|
+
this.profile = profile;
|
|
77
|
+
this.segment = renderSessionInject(ctx, blueprint);
|
|
78
|
+
if (this.injectedApi === api) {
|
|
79
|
+
api.log?.debug("agent:registerInject skipped (already injected)");
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
this.injectedApi = api;
|
|
83
|
+
// session 注入(inject=session → Pi system_prompt 事件):每轮追加 segment
|
|
84
|
+
// v10.x:包 try/catch,运行时异常走 api.log.error + ui.notify,不再 swallow
|
|
85
|
+
// v11.x(issue pt-injection-status-manual-track):三分支写 session.injectionState +
|
|
86
|
+
// 调 api.ui?.setStatus,让 footer 三态文字真实反映注入结果(自报,不检测 Pi)
|
|
87
|
+
// v12.x:从 args[1] ctx 拿 per-session sessionId,写 per-session SessionState
|
|
88
|
+
api.on("before_agent_start", async (...args) => {
|
|
89
|
+
const t0 = Date.now();
|
|
90
|
+
try {
|
|
91
|
+
const sessionId = sessionIdFromArgs(args);
|
|
92
|
+
const sessionState = sessionId ? getSessionById(sessionId) : null;
|
|
93
|
+
const currentSegment = this.segment;
|
|
94
|
+
// v14.x(issue pt-asset-migration-visibility Layer 2):footer 末尾追加 ⚠ N issues。
|
|
95
|
+
// 从 sessionState.assetHealthIssues 读计数——session_start 已批量体检过。
|
|
96
|
+
const healthCount = sessionState?.assetHealthIssues?.length ?? 0;
|
|
97
|
+
// v14.x(tagline):footer 拼 `: <tagline>`。从 sessionState.cachedProfile 读——transpile 时已存。
|
|
98
|
+
const tagline = sessionState?.cachedProfile?.tagline ?? null;
|
|
99
|
+
if (!currentSegment) {
|
|
100
|
+
// 无 segment(未加载 Profile / 已被 reset)→ idle
|
|
101
|
+
if (sessionState) {
|
|
102
|
+
sessionState.injectionState = "idle";
|
|
103
|
+
sessionState.injectionError = null;
|
|
104
|
+
api.ui?.setStatus("pt", renderInjectionFooter("idle", sessionState.activeProfile, null, healthCount, "auto", tagline));
|
|
105
|
+
}
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
const event = args[0];
|
|
109
|
+
if (!isSystemPromptEvent(event)) {
|
|
110
|
+
// 事件形状异常:归类为 idle(不视为失败——Pi 可能改了事件签名)
|
|
111
|
+
if (sessionState) {
|
|
112
|
+
sessionState.injectionState = "idle";
|
|
113
|
+
sessionState.injectionError = null;
|
|
114
|
+
api.ui?.setStatus("pt", renderInjectionFooter("idle", sessionState.activeProfile, null, healthCount, "auto", tagline));
|
|
115
|
+
}
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
const final = `${event.systemPrompt}\n\n## 当前任务上下文\n\n${currentSegment}`;
|
|
119
|
+
api.log?.debug("agent:before_agent_start ok", {
|
|
120
|
+
originalLen: event.systemPrompt.length,
|
|
121
|
+
injectedLen: final.length,
|
|
122
|
+
deltaLen: final.length - event.systemPrompt.length,
|
|
123
|
+
durationMs: Date.now() - t0,
|
|
124
|
+
});
|
|
125
|
+
if (sessionState) {
|
|
126
|
+
api.onInjected?.(final);
|
|
127
|
+
// 成功注入 → injected
|
|
128
|
+
sessionState.injectionState = "injected";
|
|
129
|
+
sessionState.injectionError = null;
|
|
130
|
+
api.ui?.setStatus("pt", renderInjectionFooter("injected", sessionState.activeProfile, null, healthCount, "auto", tagline));
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
api.onInjected?.(final);
|
|
134
|
+
}
|
|
135
|
+
return { systemPrompt: final };
|
|
136
|
+
}
|
|
137
|
+
catch (e) {
|
|
138
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
139
|
+
api.log?.error("agent:before_agent_start failed", {
|
|
140
|
+
err: msg,
|
|
141
|
+
durationMs: Date.now() - t0,
|
|
142
|
+
});
|
|
143
|
+
api.ui?.notify(`[pt] before_agent_start failed: ${msg}`, "error");
|
|
144
|
+
// 异常 → failed + 错误消息(footer 追加)
|
|
145
|
+
const sessionId = sessionIdFromArgs(args);
|
|
146
|
+
const sessionState = sessionId ? getSessionById(sessionId) : null;
|
|
147
|
+
// v14.x:catch 路径同样透传 healthCount + tagline(避免修复丢告警 / tagline)
|
|
148
|
+
const healthCount = sessionState?.assetHealthIssues?.length ?? 0;
|
|
149
|
+
const tagline = sessionState?.cachedProfile?.tagline ?? null;
|
|
150
|
+
if (sessionState) {
|
|
151
|
+
sessionState.injectionState = "failed";
|
|
152
|
+
sessionState.injectionError = msg;
|
|
153
|
+
api.ui?.setStatus("pt", renderInjectionFooter("failed", sessionState.activeProfile, msg, healthCount, "auto", tagline));
|
|
154
|
+
}
|
|
155
|
+
return undefined; // 失败降级, 不影响主流程
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
// context_message 触发:/manual:xxx + /<flow-name>
|
|
159
|
+
// v10.x:包 try/catch,renderTurnInject 抛错不再 swallow
|
|
160
|
+
api.on("input", async (...args) => {
|
|
161
|
+
const t0 = Date.now();
|
|
162
|
+
try {
|
|
163
|
+
if (!this.ctx || !this.blueprint)
|
|
164
|
+
return { action: "continue" };
|
|
165
|
+
const event = args[0];
|
|
166
|
+
if (!isInputEvent(event))
|
|
167
|
+
return { action: "continue" };
|
|
168
|
+
// Phase term-P4.3:renderContextMessage → renderTurnInject
|
|
169
|
+
// v13.x(issue pt-turn-inject-not-profile-scoped):按 Profile scope 过滤 domains + 传 profile
|
|
170
|
+
// 让 /manual:<domain> 在未引用该 domain 的 Profile 下返 null(与 /pt flows 列表一致)
|
|
171
|
+
const scoped = filterDomainsByProfile(this.domains, this.profile);
|
|
172
|
+
const result = renderTurnInject(this.ctx, this.blueprint, scoped, this.profile, event.text);
|
|
173
|
+
const durationMs = Date.now() - t0;
|
|
174
|
+
if (result === null) {
|
|
175
|
+
api.log?.debug("agent:input passthrough", {
|
|
176
|
+
inputPreview: event.text.slice(0, 80),
|
|
177
|
+
durationMs,
|
|
178
|
+
});
|
|
179
|
+
return { action: "continue" };
|
|
180
|
+
}
|
|
181
|
+
api.log?.info("agent:input transformed", {
|
|
182
|
+
inputPreview: event.text.slice(0, 80),
|
|
183
|
+
outputLen: result.length,
|
|
184
|
+
durationMs,
|
|
185
|
+
});
|
|
186
|
+
return { action: "transform", text: result };
|
|
187
|
+
}
|
|
188
|
+
catch (e) {
|
|
189
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
190
|
+
api.log?.error("agent:input render failed", {
|
|
191
|
+
err: msg,
|
|
192
|
+
input: args[0],
|
|
193
|
+
durationMs: Date.now() - t0,
|
|
194
|
+
});
|
|
195
|
+
api.ui?.notify(`[pt] input render failed: ${msg}`, "error");
|
|
196
|
+
return { action: "continue" }; // 失败降级: 不拦截 input, 让原文本过 LLM
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
/** 查询可用手册(/pt flows 用)。
|
|
201
|
+
* v9:遍历 Blueprint 的 inject=turn 聚合组 → 引用 Domain → 找 FlowTemplate + Rules 段的 Rule。 */
|
|
202
|
+
listManuals(_ctx, blueprint, domains) {
|
|
203
|
+
const flows = [];
|
|
204
|
+
for (const group of blueprint.groups) {
|
|
205
|
+
// Phase term-P4.3:inject 语义值 context_message → turn
|
|
206
|
+
// Phase term-naming:字段名 target → inject
|
|
207
|
+
if (group.inject !== "turn")
|
|
208
|
+
continue;
|
|
209
|
+
// v13.x(issue pt-turn-inject-not-profile-scoped):按 Profile scope 过滤 domains——
|
|
210
|
+
// adapter 内部统一过滤,调用方(commands.ts flowsText)无需预过滤。
|
|
211
|
+
const scoped = filterDomainsByProfile(domains, this.profile);
|
|
212
|
+
for (const d of scoped) {
|
|
213
|
+
// Phase term-P9.2:FlowTemplate 在 ## Flows 段;Rule[] 在 ## Rules 段;Checklist[] 在 ## Checklists 段。
|
|
214
|
+
const flowsContent = d.modules[MOD_FLOWS];
|
|
215
|
+
if (isFlowTemplateArray(flowsContent)) {
|
|
216
|
+
for (const t of flowsContent) {
|
|
217
|
+
flows.push({ name: t.name, hint: t.argumentHint, domain: d.name });
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
const rulesContent = d.modules[MOD_RULES];
|
|
221
|
+
if (isRuleArray(rulesContent) && rulesContent.length > 0) {
|
|
222
|
+
// term-Domain 的 Rule[] 作为 /manual:<domain> 暴露
|
|
223
|
+
flows.push({
|
|
224
|
+
name: `/manual:${d.name}`,
|
|
225
|
+
hint: `${rulesContent.length} 条规范`,
|
|
226
|
+
domain: d.name,
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
// Checklist[] 暂不单独暴露——/manual:<domain> 命令会统一处理(renderDomainManual)
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
// 去重(按 name)
|
|
233
|
+
const seen = new Set();
|
|
234
|
+
return flows.filter((f) => {
|
|
235
|
+
const key = `${f.domain}/${f.name}`;
|
|
236
|
+
if (seen.has(key))
|
|
237
|
+
return false;
|
|
238
|
+
seen.add(key);
|
|
239
|
+
return true;
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
// ==================== Pi ExtensionAPI 事件 type guards ====================
|
|
244
|
+
function isSystemPromptEvent(x) {
|
|
245
|
+
return (!!x &&
|
|
246
|
+
typeof x === "object" &&
|
|
247
|
+
typeof x.systemPrompt === "string");
|
|
248
|
+
}
|
|
249
|
+
function isInputEvent(x) {
|
|
250
|
+
return !!x && typeof x === "object" && typeof x.text === "string";
|
|
251
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import type { AgentAdapter } from "../schema.js";
|
|
3
|
+
/** 取指定 pi + name 的 AgentAdapter。未找到返 pi 的 per-pi 实例(fallback)。 */
|
|
4
|
+
export declare function getAgentAdapter(pi: ExtensionAPI, name: string): AgentAdapter;
|