pi-ui-extension 1.1.9 → 1.1.11
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 +41 -14
- package/README.zh-CN.md +41 -14
- package/extensions/ctx/README.md +3 -3
- package/extensions/ctx/data.test.ts +3 -3
- package/extensions/ctx/tools.test.ts +3 -3
- package/extensions/index.ts +75 -12
- package/extensions/mcp/integration.test.ts +9 -1
- package/extensions/mcp/resources.ts +7 -45
- package/extensions/mcp/status-center.test.ts +147 -0
- package/extensions/mcp/status-center.ts +178 -0
- package/extensions/sidebar/default-panels.test.ts +6 -11
- package/extensions/sidebar/default-panels.ts +0 -2
- package/extensions/tool-view/codemode-compact.ts +135 -0
- package/extensions/tool-view/codemode.test.ts +209 -0
- package/extensions/tool-view/controller.test.ts +2 -1
- package/extensions/tool-view/controller.ts +48 -81
- package/extensions/tool-view/panel.test.ts +8 -36
- package/extensions/tool-view/panel.ts +1 -7
- package/extensions/tool-view/renderer.test.ts +149 -0
- package/extensions/tool-view/usage.test.ts +21 -35
- package/extensions/tool-view/usage.ts +17 -70
- package/package.json +1 -1
- package/extensions/fabric-detection.test.ts +0 -15
- package/extensions/fabric-detection.ts +0 -7
- package/extensions/tool-view/trust.test.ts +0 -41
package/README.md
CHANGED
|
@@ -48,7 +48,18 @@ The bottom of the `SESSIONS` panel exposes four resource categories:
|
|
|
48
48
|
- `Extensions`
|
|
49
49
|
- `MCP`
|
|
50
50
|
|
|
51
|
-
These shortcuts let you inspect the resources
|
|
51
|
+
These shortcuts let you inspect the resources discovered by the project/Pi environment. The MCP view distinguishes configured and registered servers from confirmed connections.
|
|
52
|
+
|
|
53
|
+
#### MCP Status Center
|
|
54
|
+
|
|
55
|
+
Click **MCP** in the sidebar to open a read-only status picker covering global and trusted-project MCP configuration, servers registered by extensions via `pi.registerMcpServer()`, and observations from `pi-mcp-adapter/status/v1`.
|
|
56
|
+
|
|
57
|
+
- See configuration scope, project overrides, and enabled/disabled state (including native Pi `enabled: false`).
|
|
58
|
+
- Connection health is only shown when an adapter explicitly reports it. **Configured, registered, and loaded do not mean connected.**
|
|
59
|
+
- Commands, endpoints, headers, tokens, environment values, and raw errors are never displayed.
|
|
60
|
+
- Tool usage now attributes native Pi `mcp__server__tool` calls to their MCP server, alongside existing gateway and `mcpScript` counts.
|
|
61
|
+
|
|
62
|
+
Pi does not currently expose its complete native MCP connection state to ordinary UI extensions. For live connection diagnostics, reconnects, authentication, or configuration changes, use the active `/mcp` manager (built-in or provided by your MCP adapter).
|
|
52
63
|
|
|
53
64
|
### 4. CTX context inspection
|
|
54
65
|
|
|
@@ -89,7 +100,22 @@ For tool definitions, the extension prefers the observable provider-request tool
|
|
|
89
100
|
|
|
90
101
|
Modes can be changed directly with mouse clicks.
|
|
91
102
|
|
|
92
|
-
|
|
103
|
+
Tool View uses Pi's official `pi.registerToolRenderer()` hook for the built-in tools (`read`, `bash`, `edit`, `write`, `grep`, `find`, and `ls`) **and the built-in `codemode` tool**. It never replaces tool definitions or execution functions. Other tools retain their own renderers.
|
|
104
|
+
|
|
105
|
+
For **Codemode** specifically:
|
|
106
|
+
|
|
107
|
+
- **Normal** retains Pi's native script, nested-call, and output rendering (including its own expand/collapse behavior).
|
|
108
|
+
- **Compact** uses a two-line, status-colored summary with Chinese labels: a highlighted Codemode title and script line count, followed by grouped execution status, nested-call counts, output size, and duration. It omits script source, nested-call arguments, and actual output, even if the row is expanded or execution fails. On narrow terminals it keeps each part to one row and prioritizes status and failures over less important metrics.
|
|
109
|
+
- **Hidden** removes the Codemode transcript row, including expanded and failed results, without stopping execution or removing Tool/MCP usage accounting.
|
|
110
|
+
|
|
111
|
+
Example Compact output:
|
|
112
|
+
|
|
113
|
+
```text
|
|
114
|
+
▸ codemode 8 行代码
|
|
115
|
+
✓ 已完成 │ 1 次调用 · 1 成功 │ 59 行输出 · 85ms
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Changing the Tool View mode also updates previously rendered tool rows in the current session. Other third-party tools are unaffected.
|
|
93
119
|
|
|
94
120
|
### 6. Tool / MCP usage statistics
|
|
95
121
|
|
|
@@ -154,7 +180,7 @@ Example configuration:
|
|
|
154
180
|
To pin a specific version:
|
|
155
181
|
|
|
156
182
|
```bash
|
|
157
|
-
pi install npm:pi-ui-extension@1.1.
|
|
183
|
+
pi install npm:pi-ui-extension@1.1.11
|
|
158
184
|
```
|
|
159
185
|
|
|
160
186
|
or:
|
|
@@ -162,7 +188,7 @@ or:
|
|
|
162
188
|
```json
|
|
163
189
|
{
|
|
164
190
|
"packages": [
|
|
165
|
-
"npm:pi-ui-extension@1.1.
|
|
191
|
+
"npm:pi-ui-extension@1.1.11"
|
|
166
192
|
]
|
|
167
193
|
}
|
|
168
194
|
```
|
|
@@ -190,15 +216,15 @@ Configuration form:
|
|
|
190
216
|
Pin to a Git tag:
|
|
191
217
|
|
|
192
218
|
```bash
|
|
193
|
-
pi install git:github.com/1993yihuan/pi-ui-extension@v1.1.
|
|
219
|
+
pi install git:github.com/1993yihuan/pi-ui-extension@v1.1.11
|
|
194
220
|
```
|
|
195
221
|
|
|
196
|
-
### Temporary
|
|
222
|
+
### Temporary local-source testing
|
|
197
223
|
|
|
198
|
-
To
|
|
224
|
+
To try a local checkout without changing `settings.json`, run from the repository root. Disable automatic extension discovery to avoid loading a previously installed copy of `pi-ui-extension` twice:
|
|
199
225
|
|
|
200
226
|
```bash
|
|
201
|
-
pi -e
|
|
227
|
+
pi --no-extensions -e builtin:mcp -e ./extensions/index.ts --tui-mode fullscreen
|
|
202
228
|
```
|
|
203
229
|
|
|
204
230
|
### Project-local installation
|
|
@@ -289,7 +315,7 @@ npm run typecheck
|
|
|
289
315
|
npm test
|
|
290
316
|
```
|
|
291
317
|
|
|
292
|
-
The test suite covers
|
|
318
|
+
The test suite covers Sidebar, CTX, Session management, official Tool View renderers, MCP resources, mouse scrolling, and resize handling.
|
|
293
319
|
|
|
294
320
|
During development, Pi can load the local source directory directly:
|
|
295
321
|
|
|
@@ -314,7 +340,6 @@ pi-ui-extension/
|
|
|
314
340
|
│ ├── session/ # Session list and actions
|
|
315
341
|
│ ├── sidebar/ # Sidebar layout, scrolling, resize
|
|
316
342
|
│ ├── tool-view/ # Tool View modes and usage statistics
|
|
317
|
-
│ ├── fabric-detection.ts # Fabric ownership detection
|
|
318
343
|
│ └── index.ts # Pi Extension entry point
|
|
319
344
|
├── .github/workflows/
|
|
320
345
|
│ ├── ci.yml # Automatic checks on push / PR
|
|
@@ -337,7 +362,7 @@ Pi's extension entry point is declared explicitly in `package.json`:
|
|
|
337
362
|
|
|
338
363
|
## CI / Release
|
|
339
364
|
|
|
340
|
-
Every push to `main` and every pull request runs
|
|
365
|
+
Every push to `main` and every pull request runs on **both Node.js 22.19.0 and 24**:
|
|
341
366
|
|
|
342
367
|
```text
|
|
343
368
|
npm ci
|
|
@@ -357,6 +382,7 @@ A `v*` tag triggers the release workflow:
|
|
|
357
382
|
|
|
358
383
|
```text
|
|
359
384
|
Checkout
|
|
385
|
+
→ validate release tag format and main ancestry
|
|
360
386
|
→ Node.js 24
|
|
361
387
|
→ npm ci
|
|
362
388
|
→ npm run check
|
|
@@ -364,6 +390,7 @@ Checkout
|
|
|
364
390
|
→ npm pack --dry-run
|
|
365
391
|
→ npm Trusted Publishing (OIDC)
|
|
366
392
|
→ npm publish
|
|
393
|
+
→ verify npm Registry visibility (with retries)
|
|
367
394
|
→ GitHub Release
|
|
368
395
|
```
|
|
369
396
|
|
|
@@ -373,16 +400,16 @@ npm publishing uses GitHub Actions OIDC Trusted Publishing, so no long-lived `NP
|
|
|
373
400
|
|
|
374
401
|
- Does not modify Pi Core.
|
|
375
402
|
- `@earendil-works/pi-coding-agent` and `@earendil-works/pi-tui` are host-provided peer dependencies to avoid duplicate runtime instances.
|
|
376
|
-
-
|
|
403
|
+
- Tool View changes only how built-in tool calls are rendered, using Pi's public renderer resolver rather than overriding their implementations.
|
|
377
404
|
- Session and resource operations use Pi public APIs wherever possible.
|
|
378
405
|
- Context composition is clearly presented as an estimate, not provider-exact token accounting.
|
|
379
406
|
- Fullscreen sidebar behavior degrades safely when fullscreen layout is unavailable.
|
|
380
407
|
|
|
381
408
|
## Requirements
|
|
382
409
|
|
|
383
|
-
- Pi Coding Agent
|
|
410
|
+
- Pi Coding Agent with the official `registerToolRenderer()` API (tested with `@earendil-works/pi-coding-agent@1.1.0`)
|
|
384
411
|
- Node.js `>= 22.19.0` (use a Node version supported by your Pi installation)
|
|
385
|
-
-
|
|
412
|
+
- CI validates Node.js 22.19.0 and 24; releases use Node.js 24
|
|
386
413
|
|
|
387
414
|
## License
|
|
388
415
|
|
package/README.zh-CN.md
CHANGED
|
@@ -48,7 +48,18 @@ Sidebar 支持鼠标交互、滚轮滚动和终端尺寸变化。终端高度不
|
|
|
48
48
|
- `Extensions`
|
|
49
49
|
- `MCP`
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
点击后可以查看当前项目 / Pi 环境发现的相关资源。其中 MCP 状态中心会区分“已配置 / 已注册”和“已确认连接”,避免把配置存在误认为连接成功。
|
|
52
|
+
|
|
53
|
+
#### MCP 状态中心
|
|
54
|
+
|
|
55
|
+
点击 Sidebar 中的 **MCP** 按钮可打开只读状态中心,查看全局和可信项目配置的 MCP Server、扩展通过 `pi.registerMcpServer()` 注册的服务器,以及 `pi-mcp-adapter/status/v1` 提供的运行时观察结果。
|
|
56
|
+
|
|
57
|
+
- 显示服务器来源、启用/禁用状态与配置覆盖情况;项目级 `enabled: false` 会正确覆盖全局配置。
|
|
58
|
+
- 仅当 Adapter 明确报告连接状态时显示 Connected、Connecting、Failed 等状态;**已配置、已注册或已加载不代表已连接**。
|
|
59
|
+
- 不展示命令、URL、环境变量、请求头、Token 或原始错误内容。
|
|
60
|
+
- 工具调用统计支持 Pi 原生 `mcp__server__tool` 格式,继续兼容既有 MCP Gateway / `mcpScript` 统计。
|
|
61
|
+
|
|
62
|
+
Pi 目前没有向普通 UI 扩展公开完整的原生 MCP 实时连接状态。查看连接错误、重新连接、登录或启停服务器,请使用当前生效的 `/mcp` 管理界面(Pi 内置或由第三方 MCP Adapter 提供);本状态中心不执行连接或配置修改。
|
|
52
63
|
|
|
53
64
|
### 4. CTX 上下文观察
|
|
54
65
|
|
|
@@ -89,7 +100,22 @@ Composition 会将当前可观测上下文拆分为:
|
|
|
89
100
|
|
|
90
101
|
可以直接使用鼠标点击模式进行切换。
|
|
91
102
|
|
|
92
|
-
|
|
103
|
+
Tool View 使用 Pi 官方的 `pi.registerToolRenderer()` 接口管理内置工具(`read`、`bash`、`edit`、`write`、`grep`、`find` 和 `ls`)**以及内置 `codemode` 工具**的展示,不再重注册工具定义或接管工具执行逻辑。其他 Tool 保留其自身的渲染方式。
|
|
104
|
+
|
|
105
|
+
对于 **Codemode**:
|
|
106
|
+
|
|
107
|
+
- **Normal**:保留 Pi 原生的脚本、子工具调用、输出详情,以及原生展开 / 收起行为。
|
|
108
|
+
- **Compact**:采用带有状态颜色的双行摘要。第一行突出 Codemode 和代码行数;第二行将状态、子调用统计、输出规模与耗时分组展示,并统一使用中文单位。即使展开或执行失败,也不会展示完整代码、子调用参数或真实输出;终端宽度不足时优先保留状态、失败信息和调用次数,避免摘要自动换成多行。
|
|
109
|
+
- **Hidden**:隐藏 Codemode 工具记录(包括展开和失败结果),但不影响实际执行或 Tool / MCP 调用统计。
|
|
110
|
+
|
|
111
|
+
Compact 效果示例:
|
|
112
|
+
|
|
113
|
+
```text
|
|
114
|
+
▸ codemode 8 行代码
|
|
115
|
+
✓ 已完成 │ 1 次调用 · 1 成功 │ 59 行输出 · 85ms
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
切换 Tool View 模式也会更新当前会话中已经展示的历史工具记录;其他第三方工具保持原有渲染方式。
|
|
93
119
|
|
|
94
120
|
### 6. Tool / MCP 调用统计
|
|
95
121
|
|
|
@@ -154,7 +180,7 @@ Pi 会安装 package,并将声明写入个人配置:
|
|
|
154
180
|
如果希望固定版本:
|
|
155
181
|
|
|
156
182
|
```bash
|
|
157
|
-
pi install npm:pi-ui-extension@1.1.
|
|
183
|
+
pi install npm:pi-ui-extension@1.1.11
|
|
158
184
|
```
|
|
159
185
|
|
|
160
186
|
或者:
|
|
@@ -162,7 +188,7 @@ pi install npm:pi-ui-extension@1.1.1
|
|
|
162
188
|
```json
|
|
163
189
|
{
|
|
164
190
|
"packages": [
|
|
165
|
-
"npm:pi-ui-extension@1.1.
|
|
191
|
+
"npm:pi-ui-extension@1.1.11"
|
|
166
192
|
]
|
|
167
193
|
}
|
|
168
194
|
```
|
|
@@ -190,15 +216,15 @@ pi install git:github.com/1993yihuan/pi-ui-extension
|
|
|
190
216
|
固定到 Git tag:
|
|
191
217
|
|
|
192
218
|
```bash
|
|
193
|
-
pi install git:github.com/1993yihuan/pi-ui-extension@v1.1.
|
|
219
|
+
pi install git:github.com/1993yihuan/pi-ui-extension@v1.1.11
|
|
194
220
|
```
|
|
195
221
|
|
|
196
|
-
###
|
|
222
|
+
### 临时加载本地源码
|
|
197
223
|
|
|
198
|
-
如果只想在一次 Pi
|
|
224
|
+
如果只想在一次 Pi 启动中测试本地源码,不修改 `settings.json`,请在仓库根目录运行。禁用自动扩展发现可以避免与已经安装的 `pi-ui-extension` 重复加载:
|
|
199
225
|
|
|
200
226
|
```bash
|
|
201
|
-
pi -e
|
|
227
|
+
pi --no-extensions -e builtin:mcp -e ./extensions/index.ts --tui-mode fullscreen
|
|
202
228
|
```
|
|
203
229
|
|
|
204
230
|
### 项目级安装
|
|
@@ -289,7 +315,7 @@ npm run typecheck
|
|
|
289
315
|
npm test
|
|
290
316
|
```
|
|
291
317
|
|
|
292
|
-
当前自动化测试覆盖 Sidebar、CTX、Session
|
|
318
|
+
当前自动化测试覆盖 Sidebar、CTX、Session、官方 Tool Renderer、MCP resource、鼠标滚轮 / resize 等主要行为。
|
|
293
319
|
|
|
294
320
|
开发时可以直接让 Pi 加载本地目录:
|
|
295
321
|
|
|
@@ -314,7 +340,6 @@ pi-ui-extension/
|
|
|
314
340
|
│ ├── session/ # Session 列表与操作
|
|
315
341
|
│ ├── sidebar/ # Sidebar 布局、滚动和 resize
|
|
316
342
|
│ ├── tool-view/ # Tool View 模式与调用统计
|
|
317
|
-
│ ├── fabric-detection.ts # Fabric 工具接管检测
|
|
318
343
|
│ └── index.ts # Pi Extension 入口
|
|
319
344
|
├── .github/workflows/
|
|
320
345
|
│ ├── ci.yml # push / PR 自动检查
|
|
@@ -337,7 +362,7 @@ Pi 的扩展入口在 `package.json` 中显式声明:
|
|
|
337
362
|
|
|
338
363
|
## CI / Release
|
|
339
364
|
|
|
340
|
-
提交到 `main` 或创建 PR 时,GitHub Actions
|
|
365
|
+
提交到 `main` 或创建 PR 时,GitHub Actions 会分别在 **Node.js 22.19.0 和 24** 上执行:
|
|
341
366
|
|
|
342
367
|
```text
|
|
343
368
|
npm ci
|
|
@@ -357,6 +382,7 @@ git push --tags
|
|
|
357
382
|
|
|
358
383
|
```text
|
|
359
384
|
Checkout
|
|
385
|
+
→ 校验版本标签格式、确认提交位于 main 历史
|
|
360
386
|
→ Node.js 24
|
|
361
387
|
→ npm ci
|
|
362
388
|
→ npm run check
|
|
@@ -364,6 +390,7 @@ Checkout
|
|
|
364
390
|
→ npm pack --dry-run
|
|
365
391
|
→ npm Trusted Publishing (OIDC)
|
|
366
392
|
→ npm publish
|
|
393
|
+
→ 重试检查 npm Registry 已可查询新版本
|
|
367
394
|
→ GitHub Release
|
|
368
395
|
```
|
|
369
396
|
|
|
@@ -373,16 +400,16 @@ npm 发布使用 GitHub Actions OIDC Trusted Publishing,不需要在 GitHub Re
|
|
|
373
400
|
|
|
374
401
|
- 不修改 Pi Core。
|
|
375
402
|
- Pi host 提供的 `@earendil-works/pi-coding-agent` 和 `@earendil-works/pi-tui` 使用 `peerDependencies`,避免产生重复运行时实例。
|
|
376
|
-
-
|
|
403
|
+
- Tool View 只通过 Pi 官方 Renderer Resolver 控制内置工具的显示,不覆写工具定义或执行逻辑。
|
|
377
404
|
- Session 与资源操作尽量通过 Pi 已公开 API 完成。
|
|
378
405
|
- Context composition 明确标记为估算值,不冒充 provider 精确 token 统计。
|
|
379
406
|
- fullscreen Sidebar 不可用时安全降级,不影响 Pi 主流程。
|
|
380
407
|
|
|
381
408
|
## Requirements
|
|
382
409
|
|
|
383
|
-
- Pi Coding Agent
|
|
410
|
+
- 支持官方 `registerToolRenderer()` API 的 Pi Coding Agent(已使用 `@earendil-works/pi-coding-agent@1.1.0` 验证)
|
|
384
411
|
- Node.js `>= 22.19.0`(建议使用当前 Pi 支持的 Node 版本)
|
|
385
|
-
-
|
|
412
|
+
- CI 覆盖 Node.js 22.19.0 和 24;发布工作流使用 Node.js 24
|
|
386
413
|
|
|
387
414
|
## License
|
|
388
415
|
|
package/extensions/ctx/README.md
CHANGED
|
@@ -6,11 +6,11 @@
|
|
|
6
6
|
|
|
7
7
|
- approx = 本地可观测文本 UTF-16 长度 / 4,逐消息向上取整;不是模型 tokenizer。已知消息先经公开 SDK `convertToLlm` 转换,再提取文本,因此包含摘要包装、bash 无输出/退出/取消/截断提示;仍按原始角色分类。未知角色安全降级为外层 content 文本,`excludeFromContext` 的 bash 不计入。
|
|
8
8
|
- 六类为 System、Tools、Messages、Tool Results、Summary、Other。百分比分母是已覆盖分类的估算 token 合计,不是模型窗口;pi usage 单独展示,不校准分类。
|
|
9
|
-
- System 使用 ctx.getSystemPrompt() 的可观测字符串;无法取得时 unknown。Tools 通过 pi.getActiveTools()/getAllTools() 仅选取当前启用的工具,逐工具序列化 `{name, description, parameters}`,按 UTF-16 长度 / 4 向上取整后合计,并纳入分类百分比分母。不计 sourceInfo、执行函数及已归入 System 的 promptGuidelines
|
|
9
|
+
- System 使用 ctx.getSystemPrompt() 的可观测字符串;无法取得时 unknown。Tools 通过 pi.getActiveTools()/getAllTools() 仅选取当前启用的工具,逐工具序列化 `{name, description, parameters}`,按 UTF-16 长度 / 4 向上取整后合计,并纳入分类百分比分母。不计 sourceInfo、执行函数及已归入 System 的 promptGuidelines;未直接启用的工具不重复计入。无启用工具为 0;API 不可用、启用工具缺少定义或 schema 无法序列化时保持 unknown,避免显示不完整合计。工具定义在每次现有快照事件刷新时重新读取(包括动态启用/禁用),不在 render 中读取;空闲时注册工具将在下一次快照事件反映。
|
|
10
10
|
- Tools 在 `before_provider_request` 事件后优先统计 provider-specific payload 的 `tools` 字段序列化文本(仍按 UTF-16 长度 / 4 估算);请求发出前未观察到 payload 时,回退到当前启用工具 schema 的本地估算。它仍不是模型 tokenizer、HTTP 字节数或计费 token,也不包含 headers;如果后续扩展继续改写 payload,面板值可能是本扩展处理时观察到的版本。Images 仅统计图片块数量,不序列化请求或计算未展示的字节数;在 `before_provider_request` 可取得最终 provider payload 时优先统计该 payload,否则统计当前 context 消息;未能观测时显示 `not observed`。
|
|
11
11
|
- context 事件提供调用前的消息快照;session_start/tree/compact、turn_end、agent_settled 使用 buildContextEntries() 重建 compaction-applied active branch。后者显示 active branch,前者显示 context hook。事件之外只读取缓存,不在 render 中扫描历史。
|
|
12
12
|
- Messages 包括 user/assistant 文本、thinking 和 tool call 名称/参数;Tool Results 仅计 content;Summary 包括 compaction/branch summary;Other 包括可发送 custom message 和非排除的用户 bash。
|
|
13
|
-
- Tool Results
|
|
13
|
+
- Tool Results 按当前快照工具结果文本估算。每条 Tool Result 的 content 只计一次,不遍历内部操作;不读 details、全量 JSONL、沙箱、截断前原始输出或被压缩结果。已返回 content 内的截断提示本身仍属于可发送文本。
|
|
14
14
|
|
|
15
15
|
Composition 的 token 估算使用 `~` 前缀及 K/M 紧凑缩写(四舍五入),百分比保留一位小数。宽度不足时标签与数值拆行;支持的最小侧栏宽 18 可完整显示如 `~100K 100.0%`。
|
|
16
16
|
|
|
@@ -32,4 +32,4 @@ Composition 的 token 估算使用 `~` 前缀及 K/M 紧凑缩写(四舍五入
|
|
|
32
32
|
|
|
33
33
|
这是明确标记的本地估算,不宣称最终请求精确统计。后加载扩展仍可能改写 context 或 provider payload;不同 provider 的转换、剔除、包装开销未知。恢复/回合结束时 active branch 可能不同于上次请求经过 context 扩展裁剪后的快照;下一次 context 事件会替换它。模型生成过程中不逐 token 更新分类,最终回合/settled 时更新。只含图片的消息可见文本为零,不代表图片成本为零。
|
|
34
34
|
|
|
35
|
-
未修改
|
|
35
|
+
未修改 Pi Core 或全局配置。全屏鼠标/极短终端还应在用户真实终端手工确认。
|
|
@@ -3,8 +3,8 @@ import test from "node:test";
|
|
|
3
3
|
import { createReadTool, SessionManager } from "@earendil-works/pi-coding-agent";
|
|
4
4
|
import { estimateContext, installContextEstimates, messagesFromContextEntries } from "./data.ts";
|
|
5
5
|
|
|
6
|
-
const result = (n: number) => ({ role: "toolResult", toolName: "
|
|
7
|
-
const tool = { name: "
|
|
6
|
+
const result = (n: number) => ({ role: "toolResult", toolName: "sample_runner", toolCallId: String(n), content: [{ type: "text", text: "x".repeat(n * 4) }], details: { fullOutput: "x".repeat(100000), operations: [{ toolName: "read" }] } });
|
|
7
|
+
const tool = { name: "sample_runner", description: "Execute TypeScript", parameters: createReadTool(process.cwd()).parameters };
|
|
8
8
|
|
|
9
9
|
test("context estimate keeps tool schemas, summaries, images and active branch semantics", () => {
|
|
10
10
|
const snapshot = estimateContext([{role:"user",content:"abcd"}, result(4), {role:"compactionSummary",summary:"abcd"}], "abcd", "context hook", [tool]);
|
|
@@ -16,7 +16,7 @@ test("context estimate keeps tool schemas, summaries, images and active branch s
|
|
|
16
16
|
|
|
17
17
|
test("tracker filters active tools, refreshes schemas, caches snapshots and resets", () => {
|
|
18
18
|
const handlers = new Map<string, Function>();
|
|
19
|
-
let active = ["
|
|
19
|
+
let active = ["sample_runner"];
|
|
20
20
|
let all = [tool];
|
|
21
21
|
const tracker = installContextEstimates({
|
|
22
22
|
on: (name: string, fn: Function) => handlers.set(name, fn),
|
|
@@ -3,7 +3,7 @@ import test from "node:test";
|
|
|
3
3
|
import { createReadTool } from "@earendil-works/pi-coding-agent";
|
|
4
4
|
import { estimateContext, estimateProviderToolsTokens, installContextEstimates } from "./data.ts";
|
|
5
5
|
|
|
6
|
-
const tool = { name: "
|
|
6
|
+
const tool = { name: "sample_runner", description: "Execute TypeScript", parameters: createReadTool(process.cwd()).parameters };
|
|
7
7
|
const expected = Math.ceil(JSON.stringify(tool).length / 4);
|
|
8
8
|
const toolsCategory = (snapshot: ReturnType<typeof estimateContext>) => snapshot.categories.find(c => c.name === "Tools")!;
|
|
9
9
|
|
|
@@ -25,7 +25,7 @@ test("Tools counts schema JSON only and contributes to totals and percentages",
|
|
|
25
25
|
|
|
26
26
|
test("tracker filters active tools, refreshes dynamic schemas, caches reads and resets", () => {
|
|
27
27
|
const handlers = new Map<string, Function>();
|
|
28
|
-
let active = ["
|
|
28
|
+
let active = ["sample_runner", "sample_runner"];
|
|
29
29
|
let all = [tool, {...tool,name:"captured_read",description:"not exposed".repeat(100)}];
|
|
30
30
|
let reads = 0;
|
|
31
31
|
let fail = false;
|
|
@@ -44,7 +44,7 @@ test("tracker filters active tools, refreshes dynamic schemas, caches reads and
|
|
|
44
44
|
assert.ok(toolsCategory(tracker.getSnapshot()).tokens! > expected);
|
|
45
45
|
handlers.get("before_provider_request")!({payload:{tools:[tool]}},ctx);
|
|
46
46
|
assert.equal(toolsCategory(tracker.getSnapshot()).tokens, Math.ceil(JSON.stringify([tool]).length/4));
|
|
47
|
-
all = [{...tool,description:"changed"}]; active = ["
|
|
47
|
+
all = [{...tool,description:"changed"}]; active = ["sample_runner"];
|
|
48
48
|
handlers.get("turn_end")!({},ctx);
|
|
49
49
|
// The last provider payload remains authoritative through active-branch refresh.
|
|
50
50
|
assert.equal(toolsCategory(tracker.getSnapshot()).tokens, Math.ceil(JSON.stringify([tool]).length/4));
|
package/extensions/index.ts
CHANGED
|
@@ -12,8 +12,7 @@ import {
|
|
|
12
12
|
import { installContextEstimates } from "./ctx/data.ts";
|
|
13
13
|
import { createActivityTracker, type ActivityTracker } from "./ctx/activity.ts";
|
|
14
14
|
import { installFooter, type FooterController } from "./footer/index.ts";
|
|
15
|
-
import {
|
|
16
|
-
import { mcpResourceEntriesFromConfig, mcpResourceEntriesFromStatus, mergeMcpResourceEntries } from "./mcp/resources.ts";
|
|
15
|
+
import { adapterMcpServers, buildMcpStatusSnapshot, describeMcpStatus, type McpStatusSnapshot } from "./mcp/status-center.ts";
|
|
17
16
|
import {
|
|
18
17
|
createDefaultSidebarPanels,
|
|
19
18
|
SESSION_PANEL_ID,
|
|
@@ -103,6 +102,49 @@ function showResourceDialog(
|
|
|
103
102
|
});
|
|
104
103
|
}
|
|
105
104
|
|
|
105
|
+
/** Read-only MCP status picker; only safe metadata is displayed. */
|
|
106
|
+
export function showMcpStatusDialog(
|
|
107
|
+
ctx: { ui: { select(title: string, options: string[]): Promise<string | undefined>; editor(title: string, prefill?: string): Promise<string | undefined>; notify(message: string, level: "info" | "warning"): void } },
|
|
108
|
+
snapshot: McpStatusSnapshot,
|
|
109
|
+
): void {
|
|
110
|
+
const rows = snapshot.servers.map((server) => ({
|
|
111
|
+
name: server.name,
|
|
112
|
+
description: describeMcpStatus(server),
|
|
113
|
+
details: [
|
|
114
|
+
`Server: ${server.name}`,
|
|
115
|
+
`Source: ${server.source}`,
|
|
116
|
+
`Enabled: ${server.enabled ? "yes" : "no"}`,
|
|
117
|
+
`Observed by adapter: ${server.loaded ? "yes" : "no"}`,
|
|
118
|
+
`Status: ${describeMcpStatus(server)}`,
|
|
119
|
+
"",
|
|
120
|
+
"Only an adapter's explicit connection state establishes connected/failed.",
|
|
121
|
+
"Pi native connection health is not exposed to UI extensions.",
|
|
122
|
+
"Use Pi's built-in /mcp manager for live connection details.",
|
|
123
|
+
].join("\n"),
|
|
124
|
+
}));
|
|
125
|
+
const warnings = snapshot.warnings.map((warning) => ({
|
|
126
|
+
name: "⚠ Configuration warning",
|
|
127
|
+
description: warning,
|
|
128
|
+
details: `${warning}\nCheck your MCP configuration with Pi's built-in /mcp manager.`,
|
|
129
|
+
}));
|
|
130
|
+
const all = [...rows, ...warnings];
|
|
131
|
+
if (all.length === 0) {
|
|
132
|
+
ctx.ui.notify("No MCP servers found. Use /mcp to configure servers.", "info");
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
const choices = resourceDialogOptions(all);
|
|
136
|
+
const enabledCount = snapshot.servers.filter((server) => server.enabled).length;
|
|
137
|
+
void ctx.ui.select(
|
|
138
|
+
`MCP Status · ${enabledCount} enabled / ${snapshot.servers.length} total · ↵ details`,
|
|
139
|
+
choices,
|
|
140
|
+
).then((choice) => {
|
|
141
|
+
if (!choice) return;
|
|
142
|
+
const index = choices.indexOf(choice);
|
|
143
|
+
const item = all[index];
|
|
144
|
+
if (item) void ctx.ui.editor(`${item.name} · MCP (read-only)`, item.details).then(() => {});
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
106
148
|
/** Load Agents.md contexts, Skills, and Extensions as display rows. */
|
|
107
149
|
export async function loadResourceEntries(
|
|
108
150
|
pi: ExtensionAPI,
|
|
@@ -185,9 +227,11 @@ async function clearOtherSessions(
|
|
|
185
227
|
export default async function piUiExtension(pi: ExtensionAPI): Promise<void> {
|
|
186
228
|
const toolView = await installToolViewController(pi);
|
|
187
229
|
let mcpResources: readonly ResourceEntry[] = [];
|
|
188
|
-
let
|
|
189
|
-
|
|
190
|
-
|
|
230
|
+
let liveMcpServers: NonNullable<ReturnType<typeof adapterMcpServers>> = [];
|
|
231
|
+
let registeredServers: unknown = [];
|
|
232
|
+
let mcpCwd = process.cwd();
|
|
233
|
+
let mcpTrusted = false;
|
|
234
|
+
let mcpStatus: McpStatusSnapshot = { servers: [], warnings: [] };
|
|
191
235
|
let toolViewMode: ToolViewMode | undefined;
|
|
192
236
|
let footer: FooterController | undefined;
|
|
193
237
|
let sidebar: SidebarController | undefined;
|
|
@@ -221,12 +265,24 @@ export default async function piUiExtension(pi: ExtensionAPI): Promise<void> {
|
|
|
221
265
|
};
|
|
222
266
|
let switchRequested = false;
|
|
223
267
|
const getMcpServerNames = () => mcpResources.map((server) => server.name);
|
|
224
|
-
const
|
|
225
|
-
|
|
226
|
-
mcpResources =
|
|
268
|
+
const updateMcpStatus = (): void => {
|
|
269
|
+
mcpStatus = buildMcpStatusSnapshot(getAgentDir(), mcpCwd, mcpTrusted, registeredServers, liveMcpServers);
|
|
270
|
+
mcpResources = mcpStatus.servers
|
|
271
|
+
.filter((server) => server.enabled)
|
|
272
|
+
.map((server) => ({ name: server.name, description: describeMcpStatus(server) }));
|
|
227
273
|
const branch = getCurrentBranch?.();
|
|
228
274
|
if (branch) toolUsage?.resetSession(branch, getMcpServerNames(), { preserveExchange: true });
|
|
229
275
|
sidebar?.refresh();
|
|
276
|
+
};
|
|
277
|
+
const unsubscribeMcpStatus = pi.events.on("pi-mcp-adapter/status/v1", (value) => {
|
|
278
|
+
const observation = adapterMcpServers(value);
|
|
279
|
+
if (!observation) return;
|
|
280
|
+
liveMcpServers = observation;
|
|
281
|
+
updateMcpStatus();
|
|
282
|
+
});
|
|
283
|
+
pi.on("mcp_servers_change", (event) => {
|
|
284
|
+
registeredServers = event.servers;
|
|
285
|
+
updateMcpStatus();
|
|
230
286
|
});
|
|
231
287
|
|
|
232
288
|
pi.registerCommand(SESSION_RENAME_COMMAND, {
|
|
@@ -456,7 +512,10 @@ export default async function piUiExtension(pi: ExtensionAPI): Promise<void> {
|
|
|
456
512
|
// Pi resets extension UI before starting/reloading a session. Do not call
|
|
457
513
|
// stale controllers from the previous runtime after that reset.
|
|
458
514
|
clearControllers();
|
|
459
|
-
|
|
515
|
+
mcpCwd = ctx.cwd;
|
|
516
|
+
mcpTrusted = ctx.isProjectTrusted();
|
|
517
|
+
registeredServers = typeof pi.getMcpServers === "function" ? pi.getMcpServers() : [];
|
|
518
|
+
updateMcpStatus();
|
|
460
519
|
toolViewMode = toolView.getMode();
|
|
461
520
|
getCurrentBranch = () => ctx.sessionManager.getBranch();
|
|
462
521
|
const branch = ctx.sessionManager.getBranch();
|
|
@@ -464,7 +523,6 @@ export default async function piUiExtension(pi: ExtensionAPI): Promise<void> {
|
|
|
464
523
|
toolUsage = createToolUsageTracker([], getMcpServerNames);
|
|
465
524
|
toolUsage.resetSession(branch);
|
|
466
525
|
if (ctx.mode !== "tui") return;
|
|
467
|
-
const showToolViewModeSelector = (): boolean => !isFabricFullCodeMode(pi.getActiveTools());
|
|
468
526
|
|
|
469
527
|
activity = createActivityTracker(ctx.cwd, refreshContextUi);
|
|
470
528
|
footer = installFooter(
|
|
@@ -483,7 +541,6 @@ export default async function piUiExtension(pi: ExtensionAPI): Promise<void> {
|
|
|
483
541
|
onSelectToolViewMode: (mode): void => {
|
|
484
542
|
void toolView.setMode(mode, ctx);
|
|
485
543
|
},
|
|
486
|
-
showToolViewModeSelector,
|
|
487
544
|
loadSessions: async (): Promise<readonly SessionListItem[]> => {
|
|
488
545
|
const currentSessionId = ctx.sessionManager.getSessionId();
|
|
489
546
|
const sessions = await SessionManager.list(ctx.cwd, ctx.sessionManager.getSessionDir());
|
|
@@ -534,7 +591,12 @@ export default async function piUiExtension(pi: ExtensionAPI): Promise<void> {
|
|
|
534
591
|
ctx.ui.notify("Wait for the current run to finish before opening resources", "warning");
|
|
535
592
|
return;
|
|
536
593
|
}
|
|
537
|
-
|
|
594
|
+
if (button === "mcp") {
|
|
595
|
+
updateMcpStatus();
|
|
596
|
+
showMcpStatusDialog(ctx, mcpStatus);
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
void loadResourceEntries(pi, ctx.cwd, mcpResources, ctx.isProjectTrusted(), button)
|
|
538
600
|
.then((rows) => {
|
|
539
601
|
if (!rows || rows.length === 0) {
|
|
540
602
|
ctx.ui.notify("No resources of this kind loaded", "info");
|
|
@@ -568,6 +630,7 @@ export default async function piUiExtension(pi: ExtensionAPI): Promise<void> {
|
|
|
568
630
|
pi.on("session_shutdown", () => {
|
|
569
631
|
unsubscribeToolViewMode();
|
|
570
632
|
unsubscribeMcpStatus();
|
|
633
|
+
liveMcpServers = [];
|
|
571
634
|
|
|
572
635
|
// The sidebar replaces the fullscreen TUI layout root directly, so Pi
|
|
573
636
|
// cannot tear it down on our behalf during session replacement. Dispose
|
|
@@ -11,6 +11,7 @@ test("entry wires early/late MCP status into restored and live usage without dou
|
|
|
11
11
|
const handlers = new Map<string, any[]>(), bus = new Map<string, any>();
|
|
12
12
|
const listeners = new Set<any>();
|
|
13
13
|
let branch: any[] = [], footer: any;
|
|
14
|
+
let registrations: any[] = [{ name: "native_mcp", config: { command: "not-executed" }, extensionPath: "/extension.ts" }];
|
|
14
15
|
const tui: any = {
|
|
15
16
|
[Symbol.for("@earendil-works/pi-tui/viewport")]: true,
|
|
16
17
|
terminal: { columns: 240, rows: 100 }, layoutRoot: { render: () => [], invalidate() {} }, inputListeners: listeners,
|
|
@@ -19,8 +20,9 @@ test("entry wires early/late MCP status into restored and live usage without dou
|
|
|
19
20
|
};
|
|
20
21
|
const theme: any = { fg: (_: string, text: string) => text, bold: (text: string) => text };
|
|
21
22
|
const pi: any = {
|
|
22
|
-
registerTool() {}, registerCommand() {}, registerShortcut() {},
|
|
23
|
+
registerTool() { throw new Error("Tool View must not register tools"); }, registerToolRenderer() {}, registerCommand() {}, registerShortcut() {},
|
|
23
24
|
getCommands: () => [], getActiveTools: () => [], getAllTools: () => [], getThinkingLevel: () => "off",
|
|
25
|
+
getMcpServers: () => registrations,
|
|
24
26
|
on(name: string, fn: any) { handlers.set(name, [...(handlers.get(name) ?? []), fn]); },
|
|
25
27
|
events: { on(name: string, fn: any) { bus.set(name, fn); return () => bus.delete(name); } },
|
|
26
28
|
};
|
|
@@ -41,6 +43,12 @@ test("entry wires early/late MCP status into restored and live usage without dou
|
|
|
41
43
|
await emit("session_start");
|
|
42
44
|
assert.match(output(), /early\s+0\/1/);
|
|
43
45
|
await emit("before_agent_start", { systemPromptOptions: {} });
|
|
46
|
+
branch.push({ type: "message", message: { role: "toolResult", toolName: "mcp__native_mcp__ping", details: {} } });
|
|
47
|
+
await emit("tool_execution_end", { toolCallId: "native-1", toolName: "mcp__native_mcp__ping", isError: false, result: { details: {} } });
|
|
48
|
+
assert.match(output(), /native_mcp\s+1\/1/);
|
|
49
|
+
registrations = [];
|
|
50
|
+
await emit("mcp_servers_change", { servers: [] });
|
|
51
|
+
assert.doesNotMatch(output(), /native_mcp\s+1\/1/);
|
|
44
52
|
branch.push(saved("late"));
|
|
45
53
|
await emit("tool_execution_end", { toolCallId: "call-1", toolName: "mcpScript", isError: false, result: { details: details("late") } });
|
|
46
54
|
assert.doesNotMatch(output(), /late\s+1\/1/);
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { join } from "node:path";
|
|
1
|
+
import { adapterMcpServers, configuredMcpServers } from "./status-center.ts";
|
|
3
2
|
|
|
4
3
|
export type McpResourceEntry = {
|
|
5
4
|
readonly name: string;
|
|
@@ -7,26 +6,9 @@ export type McpResourceEntry = {
|
|
|
7
6
|
};
|
|
8
7
|
|
|
9
8
|
export function mcpResourceEntriesFromConfig(agentDir: string, cwd: string, trusted: boolean): readonly McpResourceEntry[] {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
for (const path of paths) {
|
|
14
|
-
try {
|
|
15
|
-
const config: unknown = JSON.parse(readFileSync(path, "utf8"));
|
|
16
|
-
if (!config || typeof config !== "object" || Array.isArray(config)) continue;
|
|
17
|
-
const servers = (config as { mcpServers?: unknown }).mcpServers;
|
|
18
|
-
if (!servers || typeof servers !== "object" || Array.isArray(servers)) continue;
|
|
19
|
-
for (const [name, value] of Object.entries(servers)) {
|
|
20
|
-
if (!name.trim() || !value || typeof value !== "object" || Array.isArray(value)) continue;
|
|
21
|
-
if ((value as { disabled?: unknown }).disabled === true) {
|
|
22
|
-
rows.delete(name);
|
|
23
|
-
continue;
|
|
24
|
-
}
|
|
25
|
-
rows.set(name, { name, description: "Configured MCP server (connection not verified)" });
|
|
26
|
-
}
|
|
27
|
-
} catch { /* Missing or invalid configuration is not fatal. */ }
|
|
28
|
-
}
|
|
29
|
-
return [...rows.values()];
|
|
9
|
+
return configuredMcpServers(agentDir, cwd, trusted).servers
|
|
10
|
+
.filter((server) => server.enabled)
|
|
11
|
+
.map(({ name }) => ({ name, description: "Configured MCP server (connection not verified)" }));
|
|
30
12
|
}
|
|
31
13
|
|
|
32
14
|
export function mergeMcpResourceEntries(configured: readonly McpResourceEntry[], live: readonly McpResourceEntry[]): readonly McpResourceEntry[] {
|
|
@@ -35,29 +17,9 @@ export function mergeMcpResourceEntries(configured: readonly McpResourceEntry[],
|
|
|
35
17
|
return [...rows.values()];
|
|
36
18
|
}
|
|
37
19
|
|
|
38
|
-
type McpStatusServer = {
|
|
39
|
-
readonly name?: unknown;
|
|
40
|
-
readonly disabled?: unknown;
|
|
41
|
-
};
|
|
42
|
-
|
|
43
20
|
/** Convert the adapter's read-only status snapshot into configured, enabled MCP rows. */
|
|
44
21
|
export function mcpResourceEntriesFromStatus(value: unknown): readonly McpResourceEntry[] {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const seen = new Set<string>();
|
|
50
|
-
const rows: McpResourceEntry[] = [];
|
|
51
|
-
for (const rawServer of snapshot.servers) {
|
|
52
|
-
if (typeof rawServer !== "object" || rawServer === null || Array.isArray(rawServer)) continue;
|
|
53
|
-
const server = rawServer as McpStatusServer;
|
|
54
|
-
const name = typeof server.name === "string" ? server.name.trim() : "";
|
|
55
|
-
if (!name || server.disabled === true || seen.has(name)) continue;
|
|
56
|
-
seen.add(name);
|
|
57
|
-
rows.push({
|
|
58
|
-
name,
|
|
59
|
-
description: "MCP server loaded in the current Pi session",
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
return rows;
|
|
22
|
+
return (adapterMcpServers(value) ?? [])
|
|
23
|
+
.filter((server) => server.enabled)
|
|
24
|
+
.map(({ name }) => ({ name, description: "MCP server loaded in the current Pi session" }));
|
|
63
25
|
}
|