pi-web-ui 0.58.0 → 0.59.1

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 CHANGED
@@ -96,6 +96,17 @@ theme switching, and a full settings panel — tuned for daily development.
96
96
  - Goal wizard (**AI Refine**) — turns a raw request into a concrete goal through a guided questionnaire.
97
97
  - Automatic review loop — after each turn an independent review session checks the goal against the final text and `git diff HEAD`; on fail the feedback is injected as steer until it passes (or the round cap is hit).
98
98
 
99
+ ### 🤖 DeepSeek Harness engine
100
+
101
+ - **Switchable engine** — `PI_WEB_ENGINE=pi|dsh` (default `pi`). The pi engine runs the agent in-process via the pi SDK; the **DSH engine** runs the official [`@deepseek-ai/dsh`](https://github.com/deepseek-ai/dsh) (DeepSeek Harness) runtime as a subprocess. `/api/health` reports `engine`; the footer shows a DSH badge.
102
+ - **Same wire protocol** — the DSH engine implements the same WebSocket protocol, so goal mode, SCM, background tasks, settings, plugins, terminals, message-delta & snapshots all work identically.
103
+ - **Native goal machinery** — DSH's own goal state machine + round-driver auto-continues rounds; the model judges completion/blocked (no separate review session). The goal wizard drives it via the model's `ask_user_question`.
104
+ - **Real image blocks** — photos are sent as true image content to vision-capable DeepSeek models (e.g. `deepseek-v4-flash-vision-exp`); text-only models get a text-transcription bridge instead.
105
+ - **Question dialog** — the model's `ask_user_question` surfaces as a browser dialog (single/multi-select + free text) with queueing and a countdown.
106
+ - **Tools & MCP bridge** — plugin AI tools and external MCP servers (`mcp.json`) are bridged into the DSH runtime, so the DSH model can call them (executed server-side).
107
+ - **Skill enable/disable** — the DSH skill catalog is exposed in Settings; disabling a skill filters it out of the model's view at runtime.
108
+ - **DSH user patches** — drop `.yml` Cordis patches into `<dataDir>/dsh-patches/` to extend the runtime and reload from Settings.
109
+
99
110
  ### ⚙️ Background tasks
100
111
 
101
112
  - Background-task panel — servers launched by the agent are detected via port snapshots and listed (port/pid/name); stop one or kill all.
@@ -150,13 +161,36 @@ npm i -g --allow-scripts=node-pty,@google/genai,protobufjs pi-web-ui@latest
150
161
 
151
162
  ## Quick start
152
163
 
153
- **Start**
164
+ **Start (foreground)**
154
165
 
155
166
  ```bash
156
167
  pi-web-ui # foreground, http://localhost:8787
157
- PI_WEB_PORT=9000 PI_WEB_CWD=/path/to/project pi-web-ui # custom port / workspace
158
168
  ```
159
169
 
170
+ **Start flags & environment variables** — every setting can be passed as a `--flag` on the command
171
+ line **or** set as an environment variable (flag wins). Pick whichever you prefer:
172
+
173
+ | Flag | Env var | Default | Purpose |
174
+ | --- | --- | --- | --- |
175
+ | `--port <n>` | `PI_WEB_PORT` | `8787` | HTTP port |
176
+ | `--cwd <dir>` | `PI_WEB_CWD` | current dir | workspace root (read/write/terminal) |
177
+ | `--data-dir <dir>` | `PI_WEB_DATA_DIR` | `~/.pi-web` | data dir (sessions, plugins, uploads) |
178
+ | `--engine <pi\|dsh>` | `PI_WEB_ENGINE` | `pi` | agent engine; `--engine dsh` = DeepSeek Harness |
179
+ | `--host <addr>` | `PI_WEB_HOST` | `127.0.0.1` | listen address (`0.0.0.0` for LAN/Docker) |
180
+ | `--agent-dir <dir>` | `PI_CODING_AGENT_DIR` | `~/.pi/agent` | pi config dir (auth.json, models, skills) |
181
+ | _env only_ | `PI_WEB_TOKEN` | empty | optional shared auth token |
182
+ | _env only_ | `PI_WEB_DSH_*` | — | dsh runtime, patches & debug settings |
183
+
184
+ The two are equivalent — pick one:
185
+
186
+ ```bash
187
+ pi-web-ui --engine dsh --port 9000 --cwd /path/to/project
188
+ PI_WEB_ENGINE=dsh PI_WEB_PORT=9000 PI_WEB_CWD=/path/to/project pi-web-ui
189
+ ```
190
+
191
+ For the DSH engine also install the runtime (`npm i -g @deepseek-ai/dsh@0.1.1-rc.2`) and set a
192
+ DeepSeek API key (read from `~/.pi/agent/auth.json`, set in the provider/API-key panel).
193
+
160
194
  **Stop**
161
195
 
162
196
  - **Foreground**: press `Ctrl+C` in the terminal running it.
@@ -202,9 +236,15 @@ socket drives `quiesce`/`unquiesce`.
202
236
  - **Windows** → Task Scheduler logon task (hidden PowerShell window, no black console)
203
237
 
204
238
  Options: `--port` (default 8787), `--cwd` (workspace), `--data-dir` (sessions),
205
- `--name` (custom service name). Rerunning `server install` with new options
206
- regenerates the config and restarts the service — that's how you change its
207
- port/cwd.
239
+ `--engine <pi|dsh>`, `--host`, `--agent-dir`, `--name` (custom service name). Rerunning
240
+ `server install` with new options regenerates the config and restarts the service — that's how
241
+ you change its port/cwd/engine. `--engine` / `--host` / `--agent-dir` are baked into the service
242
+ automatically; env-only vars (`PI_WEB_TOKEN`, `PI_WEB_DSH_*`) must be added to the service config
243
+ by hand. See the [start flags table](#quick-start) above.
244
+
245
+ ```bash
246
+ pi-web-ui server install --engine dsh --port 9000 --cwd /path/to/project
247
+ ```
208
248
 
209
249
 
210
250
  ## Plugins
@@ -219,20 +259,20 @@ up in the UI.
219
259
 
220
260
  ### Plugin catalog
221
261
 
222
- These plugins ship in this repository (`dev/plugins/<id>/`) and can be installed
262
+ These plugins ship in this repository (`plugins/<id>/`) and can be installed
223
263
  straight from GitHub:
224
264
 
225
265
  | Plugin | What it does |
226
266
  | --- | --- |
227
- | 📬 [webmail](https://github.com/xing-shuyin/pi-web-ui/tree/main/dev/plugins/webmail) | IMAP inbox browsing / search / read / mark / delete + SMTP sending, new-mail notifications, and an optional "allow AI to manage my mailbox" switch (six `mail_*` agent tools). Auto-installs its npm deps on first activation. |
228
- | 🗄️ [db-client](https://github.com/xing-shuyin/pi-web-ui/tree/main/dev/plugins/db-client) | Database workbench: connection manager + schema tree for MySQL / PostgreSQL / SQLite / SQL Server / MongoDB / Redis — table structure, paginated data with sorting, SQL editor, and row editing. Drivers auto-install on first use. |
229
- | 📝 [vscode-editor](https://github.com/xing-shuyin/pi-web-ui/tree/main/dev/plugins/vscode-editor) | VS Code-like workbench: multi-root file tree (local + SSH hosts), CodeMirror multi-tab editor, Remote-SSH remote file browsing/editing, draggable multi-terminal panel (xterm.js), SFTP sync & upload/download to your computer. Auto-installs `ssh2`. |
230
- | 📬 [demo-mailbox](https://github.com/xing-shuyin/pi-web-ui/tree/main/dev/plugins/demo-mailbox) | Minimal example plugin demonstrating the server entry + client view + two-way message protocol. Doubles as the plugin test fixture — start here if you want to write your own. |
267
+ | 📬 [webmail](https://github.com/xing-shuyin/pi-web-ui/tree/main/plugins/webmail) | IMAP inbox browsing / search / read / mark / delete + SMTP sending, new-mail notifications, and an optional "allow AI to manage my mailbox" switch (six `mail_*` agent tools). Auto-installs its npm deps on first activation. |
268
+ | 🗄️ [db-client](https://github.com/xing-shuyin/pi-web-ui/tree/main/plugins/db-client) | Database workbench: connection manager + schema tree for MySQL / PostgreSQL / SQLite / SQL Server / MongoDB / Redis — table structure, paginated data with sorting, SQL editor, and row editing. Drivers auto-install on first use. |
269
+ | 📝 [vscode-editor](https://github.com/xing-shuyin/pi-web-ui/tree/main/plugins/vscode-editor) | VS Code-like workbench: multi-root file tree (local + SSH hosts), CodeMirror multi-tab editor, Remote-SSH remote file browsing/editing, draggable multi-terminal panel (xterm.js), SFTP sync & upload/download to your computer. Auto-installs `ssh2`. |
270
+ | 📬 [demo-mailbox](https://github.com/xing-shuyin/pi-web-ui/tree/main/plugins/demo-mailbox) | Minimal example plugin demonstrating the server entry + client view + two-way message protocol. Doubles as the plugin test fixture — start here if you want to write your own. |
231
271
 
232
272
  Example — install the webmail plugin:
233
273
 
234
274
  ```bash
235
- pi-web-ui install https://github.com/xing-shuyin/pi-web-ui/tree/main/dev/plugins/webmail
275
+ pi-web-ui install https://github.com/xing-shuyin/pi-web-ui/tree/main/plugins/webmail
236
276
  ```
237
277
 
238
278
  Each plugin's directory in the repo has its own `README.md` with full feature
@@ -271,7 +311,7 @@ Re-run `install` against the same source with `--force`:
271
311
 
272
312
  ```bash
273
313
  # example: update the webmail plugin to the latest version in the repo
274
- pi-web-ui install https://github.com/xing-shuyin/pi-web-ui/tree/main/dev/plugins/webmail --force
314
+ pi-web-ui install https://github.com/xing-shuyin/pi-web-ui/tree/main/plugins/webmail --force
275
315
  ```
276
316
 
277
317
  - The upgrade preserves the plugin's local `config.json` automatically.
@@ -435,7 +475,7 @@ pi-web-ui is a small open-source project — **your contributions are what make
435
475
 
436
476
  | Way to contribute | How to get started |
437
477
  | --- | --- |
438
- | 🧩 **Write a plugin** | Build your own UI tab + agent tools. Copy `dev/plugins/demo-mailbox` as the minimal template (it doubles as the test fixture), develop locally, then either open a PR to ship it in the [catalog](#plugin-catalog) or [publish it standalone](https://github.com/xing-shuyin/pi-web-ui/tree/main/dev/plugins). |
478
+ | 🧩 **Write a plugin** | Build your own UI tab + agent tools. Copy `plugins/demo-mailbox` as the minimal template (it doubles as the test fixture), develop locally, then either open a PR to ship it in the [catalog](#plugin-catalog) or [publish it standalone](https://github.com/xing-shuyin/pi-web-ui/tree/main/plugins). |
439
479
  | 🎨 **Contribute a theme** | Copy `themes/white.css` (light) or `themes/cyberpunk.css` (dark) as a pure-palette template, tweak the `:root` palette + `--term-*` + `.hljs`, verify with `npm run dev`, then open a PR — full walkthrough in [Contributing a theme](#contributing-a-theme-to-the-repository-github). |
440
480
  | 💻 **Fix a bug / add a feature** | Look for [open issues](https://github.com/xing-shuyin/pi-web-ui/issues) or propose something new. Fork → branch → PR. Keep the code conventions in `AGENTS.md` (tabs, i18n keys in both languages, protocol changes in `server/protocol.ts`). |
441
481
  | 📖 **Docs & translations** | Improve the READMEs, write plugin docs, fix typos, or help translate the UI / docs into more languages. |
package/README.zh-CN.md CHANGED
@@ -62,6 +62,17 @@
62
62
  - 目标调研向导(「AI 提炼」)—— 通过引导式问卷把原始需求收敛成明确目标。
63
63
  - 自动审查循环 —— 每轮结束后用独立审查会话核对「目标 + 最终文本 + git diff HEAD」;不达标就把审查意见作为 steer 注入重改,直到通过或达到轮数上限。
64
64
 
65
+ **DeepSeek Harness(DSH)引擎**
66
+
67
+ - **引擎可切换** —— `PI_WEB_ENGINE=pi|dsh`(默认 `pi`)。pi 引擎在进程内跑 pi SDK;**DSH 引擎**把官方 [`@deepseek-ai/dsh`](https://github.com/deepseek-ai/dsh)(DeepSeek Harness)运行时作为子进程拉起。`/api/health` 返回 `engine`;底栏显示 DSH 徽标。
68
+ - **同一套 wire 协议** —— DSH 引擎实现与 pi 相同的 WebSocket 协议,目标/审查、SCM、后台任务、设置、插件、终端、message_delta 与快照全部一致。
69
+ - **原生目标机制** —— DSH 自己的目标状态机 + round-driver 自动续轮;完成/受阻由模型自判定(无独立审查会话)。目标向导经模型 `ask_user_question` 驱动。
70
+ - **真图片块** —— 图片作为真正的 image 内容发给支持视觉的 DeepSeek 模型(如 `deepseek-v4-flash-vision-exp`);纯文本模型走文字转写桥。
71
+ - **提问对话框** —— 模型 `ask_user_question` 弹出浏览器对话框(单选/多选 + 自由文本),支持排队与倒计时。
72
+ - **工具 & MCP 桥** —— 插件 AI 工具与外部 MCP 服务器(`mcp.json`)都桥进 DSH 运行时,DSH 模型可直接调用(服务端执行)。
73
+ - **技能启停** —— 设置面板暴露 DSH 技能目录;禁用即运行时过滤该技能,模型不可见。
74
+ - **DSH 用户补丁** —— 在 `<dataDir>/dsh-patches/` 放 `.yml` Cordis 补丁扩展运行时,设置面板一键重扫生效。
75
+
65
76
  **后台任务**
66
77
 
67
78
  - 后台任务面板 —— 通过端口快照检测 agent 启动的服务(端口/pid/名称),可单独停止或全部关闭。
@@ -108,11 +119,36 @@ npm i -g --allow-scripts=node-pty,@google/genai,protobufjs pi-web-ui@latest
108
119
 
109
120
  ## 启动
110
121
 
122
+ **前台启动**
123
+
111
124
  ```bash
112
125
  pi-web-ui # 前台,http://localhost:8787
113
- PORT=9000 PI_WEB_CWD=/path/to/project pi-web-ui # 自定义端口 / 工作目录
114
126
  ```
115
127
 
128
+ **启动参数 & 环境变量** —— 每个设置既能用命令行的 `--flag` 传,也能用环境变量设(flag 优先)。
129
+ 二者任选一种即可:
130
+
131
+ | 参数 | 环境变量 | 默认 | 作用 |
132
+ | --- | --- | --- | --- |
133
+ | `--port <n>` | `PI_WEB_PORT` | `8787` | HTTP 端口 |
134
+ | `--cwd <dir>` | `PI_WEB_CWD` | 当前目录 | 工作区根(读/写/终端) |
135
+ | `--data-dir <dir>` | `PI_WEB_DATA_DIR` | `~/.pi-web` | 数据目录(会话/插件/上传) |
136
+ | `--engine <pi\|dsh>` | `PI_WEB_ENGINE` | `pi` | 智能体引擎;`--engine dsh` = DeepSeek Harness |
137
+ | `--host <addr>` | `PI_WEB_HOST` | `127.0.0.1` | 监听地址(`0.0.0.0` 供局域网/Docker) |
138
+ | `--agent-dir <dir>` | `PI_CODING_AGENT_DIR` | `~/.pi/agent` | pi 配置目录(auth.json、模型、技能) |
139
+ | _仅环境变量_ | `PI_WEB_TOKEN` | 空 | 可选共享鉴权口令 |
140
+ | _仅环境变量_ | `PI_WEB_DSH_*` | — | dsh 运行时、补丁与调试设置 |
141
+
142
+ 两者等价 —— 任选其一:
143
+
144
+ ```bash
145
+ pi-web-ui --engine dsh --port 9000 --cwd /path/to/project
146
+ PI_WEB_ENGINE=dsh PI_WEB_PORT=9000 PI_WEB_CWD=/path/to/project pi-web-ui
147
+ ```
148
+
149
+ 若用 dsh 引擎,需先安装运行时(`npm i -g @deepseek-ai/dsh@0.1.1-rc.2`)并准备 DeepSeek API key
150
+ (读 `~/.pi/agent/auth.json`,在服务商/API key 面板设置)。
151
+
116
152
  ## 停止
117
153
 
118
154
  - **前台**:在运行它的终端里按 `Ctrl+C`。
@@ -156,8 +192,14 @@ pi-web-ui server unquiesce # 解除排空,恢复接收新工
156
192
  - **Windows** → 计划任务(登录自启,隐藏 PowerShell 窗口,无黑窗)
157
193
 
158
194
  选项:`--port`(默认 8787)、`--cwd`(工作目录)、`--data-dir`(会话目录)、
159
- `--name`(自定义服务名)。重复执行 `server install` 并传入新选项即可重新生成配置
160
- 并重启服务 —— 这就是修改已装服务端口/工作目录的方式。
195
+ `--engine <pi|dsh>`、`--host`、`--agent-dir`、`--name`(自定义服务名)。重复执行 `server install`
196
+ 并传入新选项即可重新生成配置并重启服务 —— 这就是修改已装服务端口/工作目录/引擎的方式。
197
+ `--engine` / `--host` / `--agent-dir` 会自动烘焙进服务;仅环境变量的(`PI_WEB_TOKEN`、
198
+ `PI_WEB_DSH_*`)需手动写进服务配置。见上方「启动参数 & 环境变量」表。
199
+
200
+ ```bash
201
+ pi-web-ui server install --engine dsh --port 9000 --cwd /path/to/project
202
+ ```
161
203
 
162
204
  ## 界面插件
163
205
 
@@ -168,19 +210,19 @@ pi-web-ui server unquiesce # 解除排空,恢复接收新工
168
210
 
169
211
  ### 插件目录
170
212
 
171
- 以下插件随本仓库发布(`dev/plugins/<id>/`),可直接从 GitHub 安装:
213
+ 以下插件随本仓库发布(`plugins/<id>/`),可直接从 GitHub 安装:
172
214
 
173
215
  | 插件 | 功能 |
174
216
  | --- | --- |
175
- | 📬 [网页邮箱 webmail](https://github.com/xing-shuyin/pi-web-ui/tree/main/dev/plugins/webmail) | IMAP 收件箱浏览/搜索/阅读/标记/删除 + SMTP 发信、新邮件通知,可选「允许 AI 管理邮箱」(六个 `mail_*` AI 工具)。首次激活自动补装 npm 依赖。 |
176
- | 🗄️ [数据库 db-client](https://github.com/xing-shuyin/pi-web-ui/tree/main/dev/plugins/db-client) | 数据库工作台:MySQL / PostgreSQL / SQLite / SQL Server / MongoDB / Redis 连接管理 + 库表树 —— 表结构、分页排序、SQL 编辑器、行编辑。驱动首次使用自动安装。 |
177
- | 📝 [编辑器 + SSH vscode-editor](https://github.com/xing-shuyin/pi-web-ui/tree/main/dev/plugins/vscode-editor) | 类 VSCode 工作台:多根文件树(本地 + SSH 主机)、CodeMirror 多标签编辑器、Remote-SSH 远程文件浏览/编辑、可拖拽多终端面板(xterm.js)、SFTP 同步与下载到电脑。自动安装 `ssh2`。 |
178
- | 📬 [示例邮箱 demo-mailbox](https://github.com/xing-shuyin/pi-web-ui/tree/main/dev/plugins/demo-mailbox) | 最小示例插件:演示服务端入口 + 客户端视图 + 双向消息协议,兼作测试夹具——想自己写插件从这里入手。 |
217
+ | 📬 [网页邮箱 webmail](https://github.com/xing-shuyin/pi-web-ui/tree/main/plugins/webmail) | IMAP 收件箱浏览/搜索/阅读/标记/删除 + SMTP 发信、新邮件通知,可选「允许 AI 管理邮箱」(六个 `mail_*` AI 工具)。首次激活自动补装 npm 依赖。 |
218
+ | 🗄️ [数据库 db-client](https://github.com/xing-shuyin/pi-web-ui/tree/main/plugins/db-client) | 数据库工作台:MySQL / PostgreSQL / SQLite / SQL Server / MongoDB / Redis 连接管理 + 库表树 —— 表结构、分页排序、SQL 编辑器、行编辑。驱动首次使用自动安装。 |
219
+ | 📝 [编辑器 + SSH vscode-editor](https://github.com/xing-shuyin/pi-web-ui/tree/main/plugins/vscode-editor) | 类 VSCode 工作台:多根文件树(本地 + SSH 主机)、CodeMirror 多标签编辑器、Remote-SSH 远程文件浏览/编辑、可拖拽多终端面板(xterm.js)、SFTP 同步与下载到电脑。自动安装 `ssh2`。 |
220
+ | 📬 [示例邮箱 demo-mailbox](https://github.com/xing-shuyin/pi-web-ui/tree/main/plugins/demo-mailbox) | 最小示例插件:演示服务端入口 + 客户端视图 + 双向消息协议,兼作测试夹具——想自己写插件从这里入手。 |
179
221
 
180
222
  安装示例(网页邮箱):
181
223
 
182
224
  ```bash
183
- pi-web-ui install https://github.com/xing-shuyin/pi-web-ui/tree/main/dev/plugins/webmail
225
+ pi-web-ui install https://github.com/xing-shuyin/pi-web-ui/tree/main/plugins/webmail
184
226
  ```
185
227
 
186
228
  每个插件在仓库里的目录都带独立 `README.md`,含完整功能清单、配置说明与注意事项。
@@ -214,7 +256,7 @@ CLI 会浅克隆仓库(无 git 时回退 tarball 下载),定位其中的 `
214
256
 
215
257
  ```bash
216
258
  # 例:把网页邮箱插件更新到仓库里的最新版
217
- pi-web-ui install https://github.com/xing-shuyin/pi-web-ui/tree/main/dev/plugins/webmail --force
259
+ pi-web-ui install https://github.com/xing-shuyin/pi-web-ui/tree/main/plugins/webmail --force
218
260
  ```
219
261
 
220
262
  - 升级时会自动保留插件目录里的 `config.json`(账号凭据等)。
@@ -360,7 +402,7 @@ pi-web-ui 是一个小型开源项目 —— **你的贡献就是它成长的力
360
402
 
361
403
  | 贡献方式 | 如何开始 |
362
404
  | --- | --- |
363
- | 🧩 **写插件** | 打造你自己的界面 tab + AI 工具。以 `dev/plugins/demo-mailbox` 为最小模板(它兼作测试夹具),本地开发后既可开 PR 收录进[插件目录](#插件目录),也可独立发布。 |
405
+ | 🧩 **写插件** | 打造你自己的界面 tab + AI 工具。以 `plugins/demo-mailbox` 为最小模板(它兼作测试夹具),本地开发后既可开 PR 收录进[插件目录](#插件目录),也可独立发布。 |
364
406
  | 🎨 **贡献主题** | 以 `themes/white.css`(浅色)或 `themes/cyberpunk.css`(深色)为纯调色板模板,调整 `:root` 配色 + `--term-*` + `.hljs`,用 `npm run dev` 验证后开 PR —— 完整步骤见[向仓库贡献主题](#向仓库贡献主题github)。 |
365
407
  | 💻 **修 bug / 加功能** | 在 [Issues](https://github.com/xing-shuyin/pi-web-ui/issues) 里挑一个,或提出新想法。Fork → 分支 → PR。代码约定见 `AGENTS.md`(Tab 缩进、i18n 双语 key、协议改动只动 `server/protocol.ts`)。 |
366
408
  | 📖 **文档与翻译** | 完善 README、补插件文档、改错别字,或帮忙把界面/文档翻译成更多语言。 |
package/bin/pi-web-ui.mjs CHANGED
@@ -3,7 +3,7 @@
3
3
  * pi-web-ui CLI.
4
4
  *
5
5
  * pi-web-ui 启动生产服务器(前台,Ctrl+C 停止,自动打开浏览器)
6
- * pi-web-ui --port 9000 --cwd /path 同上,覆盖端口 / 工作目录 / 数据目录
6
+ * pi-web-ui --engine dsh --port 9000 --cwd /path 同上,覆盖引擎/端口/工作目录/数据目录
7
7
  * pi-web-ui --no-browser 启动但不自动打开浏览器
8
8
  * pi-web-ui --version | --help
9
9
  * pi-web-ui server install [选项] 安装系统服务(开机自启)并启动
@@ -21,8 +21,8 @@
21
21
  * PowerShell 启动脚本 / VBS 启动器 / PID 文件生成在
22
22
  * %APPDATA%\pi-web-ui\
23
23
  *
24
- * 环境变量(前台与系统服务均适用):PI_WEB_PORT / PI_WEB_CWD / PI_WEB_DATA_DIR /
25
- * PI_CODING_AGENT_DIR
24
+ * 环境变量(flag 优先,环境变量后备):PI_WEB_PORT / PI_WEB_CWD / PI_WEB_DATA_DIR /
25
+ * PI_WEB_ENGINE / PI_WEB_HOST / PI_CODING_AGENT_DIR;token 仅环境变量,不走命令行。
26
26
  */
27
27
  import { spawn, spawnSync } from "node:child_process";
28
28
  import { createConnection } from "node:net";
@@ -66,7 +66,7 @@ const HELP = `pi-web-ui v${pkg.version} — web chat for the pi coding agent
66
66
 
67
67
  用法:
68
68
  pi-web-ui 启动服务器(前台,Ctrl+C 停止,自动打开浏览器)
69
- pi-web-ui --port 9000 --cwd /path 启动并指定端口 / 工作目录 / 数据目录
69
+ pi-web-ui --engine dsh --port 9000 --cwd /path 启动并指定引擎 / 端口 / 工作目录 / 数据目录
70
70
  pi-web-ui --no-browser 启动但不自动打开浏览器
71
71
  pi-web-ui server install [选项] 安装系统服务(开机自启)并启动
72
72
  pi-web-ui server shortcut [选项] 在桌面创建「一键启动」图标(启动服务并打开浏览器)
@@ -80,6 +80,9 @@ server 选项:
80
80
  --port <n> 端口(默认 8787,或 $PI_WEB_PORT)
81
81
  --cwd <dir> 工作目录(默认 $PI_WEB_CWD 或用户主目录;前台启动默认当前目录)
82
82
  --data-dir <dir> 会话数据目录(默认 <cwd>/.pi-web)
83
+ --engine <pi|dsh> 智能体引擎(默认 $PI_WEB_ENGINE 或 pi)
84
+ --host <addr> 监听地址(默认 $PI_WEB_HOST 或 127.0.0.1;0.0.0.0 供局域网/容器)
85
+ --agent-dir <dir> pi 配置目录(默认 $PI_CODING_AGENT_DIR 或 ~/.pi/agent)
83
86
  --name <name> 服务名(默认 pi-web-ui;macOS 的 launchd label
84
87
  为 com.xingshuyin.pi-web-ui,自定义名时为 com.<name>.server)
85
88
  --print 只打印将生成的配置文件,不实际安装
@@ -100,8 +103,10 @@ server 选项:
100
103
  --data-dir <dir> 数据目录(默认 ~/.pi-web)
101
104
  --force 目标已存在时覆盖
102
105
 
103
- 环境变量(前台与系统服务均适用):
104
- PI_WEB_PORT / PI_WEB_CWD / PI_WEB_DATA_DIR / PI_CODING_AGENT_DIR
106
+ 环境变量(flag 优先,环境变量后备):
107
+ PI_WEB_PORT / PI_WEB_CWD / PI_WEB_DATA_DIR / PI_WEB_ENGINE / PI_WEB_HOST /
108
+ PI_CODING_AGENT_DIR。
109
+ 鉴权口令 PI_WEB_TOKEN:仅环境变量(不走命令行,避免被 ps 看到),需要时手动加入服务配置。
105
110
  `;
106
111
 
107
112
  /** Minimum Node required by the pi SDK (its dist uses `import … with { type: "json" }`). */
@@ -143,6 +148,9 @@ function parseFlags(argv) {
143
148
  cwd: undefined,
144
149
  dataDir: undefined,
145
150
  name: undefined,
151
+ engine: undefined,
152
+ host: undefined,
153
+ agentDir: undefined,
146
154
  print: false,
147
155
  noBrowser: false,
148
156
  force: false,
@@ -174,6 +182,15 @@ function parseFlags(argv) {
174
182
  case "--data-dir":
175
183
  opts.dataDir = take("--data-dir");
176
184
  break;
185
+ case "--engine":
186
+ opts.engine = take("--engine");
187
+ break;
188
+ case "--host":
189
+ opts.host = take("--host");
190
+ break;
191
+ case "--agent-dir":
192
+ opts.agentDir = take("--agent-dir");
193
+ break;
177
194
  case "--name":
178
195
  opts.name = take("--name");
179
196
  break;
@@ -261,6 +278,12 @@ async function startForeground(opts) {
261
278
  if (opts.port) process.env.PI_WEB_PORT = opts.port;
262
279
  if (opts.cwd) process.env.PI_WEB_CWD = resolve(opts.cwd);
263
280
  if (opts.dataDir) process.env.PI_WEB_DATA_DIR = resolve(opts.dataDir);
281
+ if (opts.engine) {
282
+ if (opts.engine !== "pi" && opts.engine !== "dsh") fail(`无效引擎: ${opts.engine}(仅支持 pi / dsh)`);
283
+ process.env.PI_WEB_ENGINE = opts.engine;
284
+ }
285
+ if (opts.host) process.env.PI_WEB_HOST = opts.host;
286
+ if (opts.agentDir) process.env.PI_CODING_AGENT_DIR = resolve(opts.agentDir);
264
287
  const url = `http://localhost:${effectivePort(opts)}`;
265
288
  await import(pathToFileURL(SERVER_ENTRY).href);
266
289
  if (!opts.noBrowser) openBrowserWhenUp(url);
@@ -563,8 +586,8 @@ function buildWinHiddenVbs(ps1Path) {
563
586
  * -WindowStyle Hidden so nothing flashes on double-click.
564
587
  */
565
588
  function installWinShortcut(opts) {
566
- const { name, port, cwd, dataDir } = serviceOptions(opts);
567
- const env = serviceEnv(port, cwd, dataDir);
589
+ const { name, port, cwd, dataDir, engine, host, agentDir } = serviceOptions(opts);
590
+ const env = serviceEnv(port, cwd, dataDir, engine, host, agentDir);
568
591
  const url = `http://localhost:${port}`;
569
592
  const ps1Path = winShortcutPs1Path(name);
570
593
  const ps1 = buildWinShortcutPs1(
@@ -668,13 +691,13 @@ if [ -n "\${SERVER_PID:-}" ]; then wait "$SERVER_PID"; fi
668
691
  }
669
692
 
670
693
  function installMacShortcut(opts) {
671
- const { name, port, cwd, dataDir } = serviceOptions(opts);
694
+ const { name, port, cwd, dataDir, engine, host, agentDir } = serviceOptions(opts);
672
695
  const url = `http://localhost:${port}`;
673
696
  const script = buildMacShortcut(
674
697
  serviceLabel(name),
675
698
  launchAgentPlist(name),
676
699
  url,
677
- serviceEnv(port, cwd, dataDir),
700
+ serviceEnv(port, cwd, dataDir, engine, host, agentDir),
678
701
  );
679
702
  const path = join(homedir(), "Desktop", SHORTCUT_MAC_NAME);
680
703
  if (opts.print) {
@@ -726,7 +749,7 @@ if [ -n "\${SERVER_PID:-}" ]; then wait "$SERVER_PID"; fi
726
749
  }
727
750
 
728
751
  function installLinuxShortcut(opts) {
729
- const { name, port, cwd, dataDir } = serviceOptions(opts);
752
+ const { name, port, cwd, dataDir, engine, host, agentDir } = serviceOptions(opts);
730
753
  const url = `http://localhost:${port}`;
731
754
  const scriptDir = join(homedir(), ".local", "share", "pi-web-ui");
732
755
  const scriptPath = join(scriptDir, `${name}-start.sh`);
@@ -978,10 +1001,17 @@ function serviceOptions(opts) {
978
1001
  } else if (process.env.PI_WEB_DATA_DIR) {
979
1002
  dataDir = resolve(process.env.PI_WEB_DATA_DIR);
980
1003
  }
981
- return { name, port, cwd, dataDir };
1004
+ // 引擎/监听地址/agent 配置目录:flag 优先,环境变量后备(token 不走命令行,仅环境变量)。
1005
+ const engine = opts.engine ?? process.env.PI_WEB_ENGINE ?? "pi";
1006
+ if (engine !== "pi" && engine !== "dsh") fail(`无效引擎: ${engine}(仅支持 pi / dsh)`);
1007
+ const host = opts.host ?? process.env.PI_WEB_HOST;
1008
+ const agentDir = opts.agentDir
1009
+ ? resolve(opts.agentDir)
1010
+ : process.env.PI_CODING_AGENT_DIR;
1011
+ return { name, port, cwd, dataDir, engine, host, agentDir };
982
1012
  }
983
1013
 
984
- function serviceEnv(port, cwd, dataDir) {
1014
+ function serviceEnv(port, cwd, dataDir, engine, host, agentDir) {
985
1015
  const env = {
986
1016
  PI_WEB_PORT: port,
987
1017
  PI_WEB_CWD: cwd,
@@ -996,14 +1026,17 @@ function serviceEnv(port, cwd, dataDir) {
996
1026
  if (!isWin && process.env.LANG) env.LANG = process.env.LANG;
997
1027
  if (!isWin && process.env.LC_ALL) env.LC_ALL = process.env.LC_ALL;
998
1028
  if (dataDir) env.PI_WEB_DATA_DIR = dataDir;
1029
+ if (engine === "dsh") env.PI_WEB_ENGINE = "dsh"; // 仅非默认引擎才烘焙,保持服务单元简洁
1030
+ if (host) env.PI_WEB_HOST = host;
1031
+ if (agentDir) env.PI_CODING_AGENT_DIR = agentDir;
999
1032
  return env;
1000
1033
  }
1001
1034
 
1002
1035
  function installLaunchd(opts) {
1003
- const { name, port, cwd, dataDir } = serviceOptions(opts);
1036
+ const { name, port, cwd, dataDir, engine, host, agentDir } = serviceOptions(opts);
1004
1037
  const label = serviceLabel(name);
1005
1038
  const plist = launchAgentPlist(name);
1006
- const content = buildPlist(label, cwd, serviceEnv(port, cwd, dataDir));
1039
+ const content = buildPlist(label, cwd, serviceEnv(port, cwd, dataDir, engine, host, agentDir));
1007
1040
  if (opts.print) {
1008
1041
  console.log(`# ${plist}\n${content}`);
1009
1042
  return;
@@ -1026,8 +1059,8 @@ function installLaunchd(opts) {
1026
1059
  }
1027
1060
 
1028
1061
  function installSystemd(opts) {
1029
- const { name, port, cwd, dataDir } = serviceOptions(opts);
1030
- const content = buildUnit(cwd, serviceEnv(port, cwd, dataDir));
1062
+ const { name, port, cwd, dataDir, engine, host, agentDir } = serviceOptions(opts);
1063
+ const content = buildUnit(cwd, serviceEnv(port, cwd, dataDir, engine, host, agentDir));
1031
1064
  const unitPath = systemdUnitPath(name);
1032
1065
  if (opts.print) {
1033
1066
  console.log(`# ${unitPath}\n${content}`);
@@ -1075,8 +1108,8 @@ function uninstallSystemd(opts) {
1075
1108
  }
1076
1109
 
1077
1110
  function installWindows(opts) {
1078
- const { name, port, cwd, dataDir } = serviceOptions(opts);
1079
- const env = serviceEnv(port, cwd, dataDir);
1111
+ const { name, port, cwd, dataDir, engine, host, agentDir } = serviceOptions(opts);
1112
+ const env = serviceEnv(port, cwd, dataDir, engine, host, agentDir);
1080
1113
  const ps1Path = winPs1Path(name);
1081
1114
  const vbsPath = winVbsPath(name);
1082
1115
  const pidPath = winPidFilePath(name);
@@ -91,6 +91,11 @@ const WINDOWS_PERSONA = `You are a coding agent running on Windows. The bash too
91
91
  - In the interactive terminal (TTY) — which is Git Bash too, not PowerShell — NEVER use heredocs (<<'EOF' ... EOF) or here-strings, and NEVER start interactive programs (vi, less, python -, node -, npm init): they wait for keyboard input that never arrives and hang the terminal forever. Prefer writing a temp script file (e.g. .pi-tmp.sh) and running it non-interactively. ALWAYS pass a timeout to long-running commands (e.g. \`timeout 120 npm run dev\`).
92
92
 
93
93
  Many legacy Chinese text files (.html/.txt/.md/.log, exported documents) are GBK/GB2312 encoded: the read tool decodes UTF-8 only and will show mojibake (乱码) for them. If a file's content looks garbled, read it through the terminal instead: in Git Bash use \`cat file | iconv -f GBK -t UTF-8\` (or \`iconv -f GBK -t UTF-8 file\`); in cmd use \`chcp 65001 && type file\`; in PowerShell use \`Get-Content -Encoding Default file\`. Never paste mojibake into your reasoning or answer — describe the decoded content instead.`;
94
+ /** bash 工具输出限制/过滤管道引导:模型习惯套 `| tail/-n`、`| head`、`| grep`、`| less`
95
+ * 等限输出。这些管道在持久终端里会①缓冲(可见终端全程哑火、看不到实时进度)②把退出码
96
+ * 错报成管道末尾命令(tail 恒 0、grep 无命中恒 1,灾难性掩盖真实失败)③长驻/出错命令会挂到超时。
97
+ * 让模型改用 bash 的 `tail` 参数限输出;长驻/交互任务改走持久终端工具。 */
98
+ const PIPELESS_BASH_GUIDANCE = `Bash tool output-limiting/filtering: do NOT chain shell pipes to trim or filter output. Avoid \`| tail\`, \`| head\`, \`| grep\`, \`| less\`, \`| more\`, \`| cat\`, \`| sort\`, \`| awk\`, \`| sed\`. They buffer output (so the visible terminal shows nothing live), turn the real exit code into the last pipe command's (tail always 0, grep 1 when no match — hiding the actual failure), and can hang a long-running or failing command until timeout. Instead:\n- To limit returned output use the bash \`tail\` parameter (e.g. \`bash(command=..., tail=20)\`) — the underlying command still streams live to the visible terminal.\n- For a long-running server / watcher / interactive program, use the persistent terminal tools (terminal_create then terminal_read / terminal_input / terminal_key / terminal_wait) instead of piping through bash.\nThe bash tool auto-detects a trailing \`| tail\`/\`| grep\` etc. and runs the underlying command directly so it never hides a failure — but you should still prefer the \`tail\` parameter.`;
94
99
  /**
95
100
  * Killable bash tool: wraps the SDK bash tool with operations that register
96
101
  * their own AbortController into a client-level set. abortBash() aborts only
@@ -434,14 +439,14 @@ export class ClientSession {
434
439
  const mgr = new TerminalManager((msg) => this.emitTerminal(conversationId, msg), cwd);
435
440
  // 终端活力检测:AI 触碰过的终端静默 ≥ 阈值(PI_WEB_TERMINAL_IDLE_MS,
436
441
  // 默认 15s)且该对话正在运行时,注入一条 steer 消息唤醒 AI 去检查。
437
- mgr.onAgentIdle = (terminalId, idleMs, title) => this.notifyTerminalIdle(conversationId, terminalId, idleMs, title);
442
+ mgr.onAgentIdle = (terminalId, idleMs, title, lastLines) => this.notifyTerminalIdle(conversationId, terminalId, idleMs, title, lastLines);
438
443
  return mgr;
439
444
  }
440
445
  /** 终端活力提醒:仅在该对话正在流式运行时注入(sendUserMessage 在流式中
441
446
  * 即 steer 语义——当前回合结算后送达,agent 立即响应);空闲时不打扰。
442
447
  * 一次性语义由 TerminalManager 保证(触发后解除武装,agent 再次触碰才
443
448
  * 重新计时),不会反复刷屏。 */
444
- notifyTerminalIdle(conversationId, terminalId, idleMs, title) {
449
+ notifyTerminalIdle(conversationId, terminalId, idleMs, title, lastLines = "") {
445
450
  const conv = this.convs.get(conversationId);
446
451
  if (!conv || this.disposed)
447
452
  return;
@@ -449,8 +454,9 @@ export class ClientSession {
449
454
  return;
450
455
  const seconds = Math.max(1, Math.round(idleMs / 1000));
451
456
  void conv.runtime.session
452
- .sendUserMessage(`(系统自动提醒:你启动的终端「${title}」已连续 ${seconds} 秒没有任何新输出。` +
453
- `进程可能在等待输入、卡住或已挂起。请用 terminal_read 查看它的当前状态;` +
457
+ .sendUserMessage(`(系统自动提醒:你启动的终端「${title}」(id=${terminalId})已连续 ${seconds} 秒没有任何新输出。` +
458
+ `进程可能在等待输入、卡住或已挂起。\n最近输出:\n${lastLines || "(无输出)"}\n` +
459
+ `请用 terminal_read(terminalId="${terminalId}") 查看/搜索它的当前状态;` +
454
460
  `若在等交互就用 terminal_input / terminal_key 回应;确认不再需要就 terminal_close 关掉它。)`)
455
461
  .catch(() => {
456
462
  // best effort —— 注入失败不影响终端本身
@@ -742,6 +748,11 @@ export class ClientSession {
742
748
  // 而不是一次性 bash——没有这段模型几乎从不主动选终端工具。
743
749
  out.push(TERMINAL_TOOLS_GUIDANCE);
744
750
  }
751
+ // bash 工具输出限制/过滤管道引导:模型习惯套 `| tail/-n`、`| head`、
752
+ // `| grep` 等限输出。这些管道会缓冲(终端全程哑火)、把退出码错报成
753
+ // 管道末尾命令(tail 恒 0、grep 无命中 1)、长驻/出错命令挂到超时。
754
+ // 让模型改用 bash 的 tail 参数,长驻/交互改走持久终端。
755
+ out.push(PIPELESS_BASH_GUIDANCE);
745
756
  return out;
746
757
  },
747
758
  // 技能开关:禁用的技能从系统提示词和 /skill: 目录中剔除。