pi-web-ui 0.15.6 → 0.16.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 +39 -406
- package/README.zh-CN.md +71 -367
- package/dist/server/agent-service.js +67 -2
- package/package.json +1 -1
- package/web/dist/assets/{index-BeAP3RuW.js → index-BslXQ1Pt.js} +40 -40
- package/web/dist/assets/{index-ClTlMcxq.css → index-CyPZoWiz.css} +1 -1
- package/web/dist/index.html +2 -2
package/README.zh-CN.md
CHANGED
|
@@ -1,367 +1,71 @@
|
|
|
1
|
-
# pi-web-ui
|
|
2
|
-
|
|
3
|
-
[English](README.md) | **简体中文**
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
-
|
|
31
|
-
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
npm
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
> node-pty 的原生构建:`npm approve-scripts node-pty@1.1.0`(标准 npm 会自动完成)。
|
|
73
|
-
|
|
74
|
-
### 启动
|
|
75
|
-
|
|
76
|
-
```bash
|
|
77
|
-
pi-web-ui # 前台,http://localhost:8787
|
|
78
|
-
PORT=9000 PI_WEB_CWD=/path/to/project pi-web-ui # 自定义端口 / 工作目录
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
想后台运行或开机自启,请使用系统服务——见
|
|
82
|
-
[部署与开机自启](#部署与开机自启)(systemd / launchd / Docker)。
|
|
83
|
-
|
|
84
|
-
`pi-web-ui` 命令从包安装位置提供编译好的前端和 WebSocket API——不需要仓库 checkout。
|
|
85
|
-
它使用**你的** `~/.pi/agent` 配置(认证/模型/技能),并把每客户端会话存在
|
|
86
|
-
`<PI_WEB_CWD>/.pi-web` 下。
|
|
87
|
-
|
|
88
|
-
### 停止
|
|
89
|
-
|
|
90
|
-
- **前台**:在运行它的终端按 `Ctrl+C`。
|
|
91
|
-
- **systemd**:`sudo systemctl stop pi-web-ui`
|
|
92
|
-
- **launchd**:`launchctl bootout gui/$(id -u)/com.xingshuyin.pi-web-ui`
|
|
93
|
-
- **Windows(计划任务)**:`pi-web-ui server stop`(或 `schtasks /End /TN pi-web-ui`;
|
|
94
|
-
停止运行中的实例,自启保留到 `server uninstall` 为止)
|
|
95
|
-
- **Docker**:`docker compose stop`(停止并删除容器:`docker compose down`)
|
|
96
|
-
|
|
97
|
-
(后台进程应该用系统服务管理,而不是 `nohup`——上面的服务停止命令同时会停掉并禁用自启。)
|
|
98
|
-
|
|
99
|
-
### 验证 / 版本
|
|
100
|
-
|
|
101
|
-
```bash
|
|
102
|
-
pi-web-ui --version # CLI 版本
|
|
103
|
-
npm ls -g pi-web-ui # 是否已安装?哪个版本?
|
|
104
|
-
which pi-web-ui # 可执行文件位置
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
### 更新
|
|
108
|
-
|
|
109
|
-
```bash
|
|
110
|
-
npm i -g pi-web-ui@latest # 升级到最新发布版
|
|
111
|
-
# 之后重启服务,新版本才会生效
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
### 卸载
|
|
115
|
-
|
|
116
|
-
```bash
|
|
117
|
-
npm uninstall -g pi-web-ui
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
卸载**不会**删除你的聊天记录:会话数据存放在 `<PI_WEB_CWD>/.pi-web`
|
|
121
|
-
(或 `PI_WEB_DATA_DIR`),卸载/升级后依然保留。
|
|
122
|
-
|
|
123
|
-
### 作为系统服务管理(开机自启)
|
|
124
|
-
|
|
125
|
-
把服务端安装为开机自启的系统服务,可自定义端口和工作目录:
|
|
126
|
-
|
|
127
|
-
```bash
|
|
128
|
-
pi-web-ui server install --port 9000 --cwd /path/to/project # 安装并启动
|
|
129
|
-
pi-web-ui server status # 运行中?自启?
|
|
130
|
-
pi-web-ui server restart # 重启(配置变更后同样用它)
|
|
131
|
-
pi-web-ui server stop # 停止 + 禁用自启
|
|
132
|
-
pi-web-ui server start # 重新启动
|
|
133
|
-
pi-web-ui server uninstall # 彻底移除服务
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
- **macOS** → launchd 代理(无需 sudo):写入并加载
|
|
137
|
-
`~/Library/LaunchAgents/com.xingshuyin.pi-web-ui.plist`,崩溃自动重启
|
|
138
|
-
(`KeepAlive`),日志在 `/tmp/pi-web-ui.log` / `/tmp/pi-web-ui.err`。
|
|
139
|
-
- **Linux** → systemd 单元(自动 sudo):写入
|
|
140
|
-
`/etc/systemd/system/pi-web-ui.service` 并执行 `systemctl enable --now`,
|
|
141
|
-
日志用 `journalctl -u pi-web-ui -f` 查看。
|
|
142
|
-
- **Windows** → 任务计划程序:创建登录时启动的用户任务(与 launchd 代理一致;
|
|
143
|
-
通常不需要管理员,但部分机器上 `schtasks /Create` 需要提权的 PowerShell——
|
|
144
|
-
如果 `install` 报 `ERROR: Access is denied`,请用管理员 shell 重跑)。任务通过
|
|
145
|
-
`powershell.exe -WindowStyle Hidden` 运行生成在 `%APPDATA%\pi-web-ui\pi-web-ui.ps1`
|
|
146
|
-
的 PowerShell 启动器——**不会有黑色控制台窗口**常驻,没有可被误关/误杀的东西。
|
|
147
|
-
启动器设置环境变量、cd 到工作目录、启动 node,并把日志追加到
|
|
148
|
-
`%USERPROFILE%\pi-web-ui.log`。任务 XML 保存在旁边;失败会自动重启。
|
|
149
|
-
**务必显式传 `--cwd`**——任务会继承安装时 shell 的目录,而管理员 shell 默认是
|
|
150
|
-
`C:\WINDOWS\system32`,非提权任务写不进去(启动即 EPERM)。详见
|
|
151
|
-
[Windows — 任务计划程序](#windows--任务计划程序)。
|
|
152
|
-
- 选项:`--port`(默认 8787 或 `$PORT`)、`--cwd`(默认 `$PI_WEB_CWD` 或当前目录)、
|
|
153
|
-
`--data-dir`(会话目录)、`--name`(自定义服务名;macOS 标签为
|
|
154
|
-
`com.xingshuyin.pi-web-ui`,自定义名变成 `com.<name>.server`)。`--print` 预览
|
|
155
|
-
生成的 unit/plist/任务文件而不实际应用。
|
|
156
|
-
- 用新参数重跑 `install` 会重新生成配置并重启服务——这就是修改已装服务端口/cwd 的方式。
|
|
157
|
-
|
|
158
|
-
## 配置
|
|
159
|
-
|
|
160
|
-
| 环境变量 | 默认值 | 说明 |
|
|
161
|
-
| -------------------------- | ----------------- | ---------------------------------------------------------------------------------------------------------------------- |
|
|
162
|
-
| `PORT` | `8787` | HTTP/WebSocket 端口 |
|
|
163
|
-
| `PI_WEB_CWD` | 服务端 cwd | 智能体操作的工作区目录(读/编辑/bash/写) |
|
|
164
|
-
| `PI_WEB_DATA_DIR` | `<cwd>/.pi-web` | 每客户端会话目录的存放位置 |
|
|
165
|
-
| `PI_WEB_INLINE_FILE_MAX` | `12288` (12KB) | 小于等于该大小的文本附件直接内联进模型上下文;更大的文件以路径引用方式传入,模型按需用 read 工具读取(小改动省 token) |
|
|
166
|
-
| `PI_CODING_AGENT_DIR` | `~/.pi/agent` | pi 配置目录(auth.json、models.json、skills、extensions) |
|
|
167
|
-
|
|
168
|
-
示例——让智能体面向某个项目:
|
|
169
|
-
|
|
170
|
-
```bash
|
|
171
|
-
PI_WEB_CWD=/path/to/your/project npm run dev
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
## 架构
|
|
175
|
-
|
|
176
|
-
```text
|
|
177
|
-
Browser (React + Vite)
|
|
178
|
-
│ WebSocket JSON — 快照驱动协议 (server/protocol.ts)
|
|
179
|
-
▼
|
|
180
|
-
server/index.ts express 静态 + ws 端点
|
|
181
|
-
│
|
|
182
|
-
server/agent-service.ts 每客户端 ClientSession:
|
|
183
|
-
│ createAgentSessionRuntime({ sessionManager: SessionManager.continueRecent(cwd, sessionDir) })
|
|
184
|
-
│ session.subscribe(events) → 节流全量快照 + 实时工具增量
|
|
185
|
-
▼
|
|
186
|
-
@earendil-works/pi-coding-agent (SDK, 进程内)
|
|
187
|
-
│ ModelRuntime (auth 来自 ~/.pi/agent) · tools · extensions · skills
|
|
188
|
-
▼
|
|
189
|
-
你的 LLM 提供商
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
关键设计点:
|
|
193
|
-
|
|
194
|
-
- **快照驱动 UI。** 服务端是唯一事实源:每次 SDK 事件后调度一个节流(60 ms)的全量快照,
|
|
195
|
-
浏览器纯粹按快照渲染。重连只需重新请求 `get_state`。大载荷(工具输出、文本)在序列化时
|
|
196
|
-
做了截断(`server/serialize.ts`)。
|
|
197
|
-
- **助手实时流式输出。** 进行中的消息(SDK `agent.state.streamingMessage`)被序列化进每个快照,
|
|
198
|
-
所以思考块和回答文本是**边生成边**出现在浏览器里(带闪烁光标),而不是等整轮结束才显示。
|
|
199
|
-
部分消息拿到稳定的 `stream-<ts>` id,跨快照保持挂载(展开的思考/工具块状态不丢)。
|
|
200
|
-
- **按大小感知的附件。** 点击 + 把文件加入附件队列(显示在输入框上方的 chips)。发送时服务端
|
|
201
|
-
把每个文件作为独立的 custom message 附加(SDK `sendCustomMessage` + `nextTurn` asides)——
|
|
202
|
-
用户消息保持干净,每个文件渲染成自己可折叠的卡片:小文本文件(≤ `PI_WEB_INLINE_FILE_MAX`,
|
|
203
|
-
默认 12KB)直接内联,模型立即看到;更大的文件以 `<file path=...>` 引用传入,模型按需用
|
|
204
|
-
`read` 工具读取——所以附加一个 5 MB 的文件在模型真正查看前只花几个 token。图片始终以
|
|
205
|
-
image content 附加。
|
|
206
|
-
- **图片问答。** 除了从右侧文件树附加工作区图片,还可以**直接粘贴截图(Ctrl+V)、把图片拖到
|
|
207
|
-
输入框、或点输入框的 🖼 按钮上传**——浏览器先把图片等比缩到 ≤1568px 再编码,图片随消息
|
|
208
|
-
发送(`prompt.attachments[].imageData` base64),无需存在于工作区。当前模型不支持识图时
|
|
209
|
-
会提示;非识图模型看不到图片。
|
|
210
|
-
- **文件对话。** 任意本地文件(文本/二进制)也可以直接**拖入输入框或用 📎 按钮上传**——浏览器
|
|
211
|
-
把内容以 base64 发送(`prompt.attachments[].fileData`),服务端存到 `~/.pi-web/uploads/<clientId>/`
|
|
212
|
-
并作为附件附加:小文本文件直接内联给模型看,大文件/二进制以绝对路径引用(模型的 read 工具
|
|
213
|
-
支持绝对路径,可按需读取);上限 20MB。
|
|
214
|
-
- **带行号选区的文件预览。** 在右侧面板点击文件名(或其 👁 按钮)打开带行号的预览弹窗。
|
|
215
|
-
点击 / 拖拽 / Shift+点击选择行区间,然后点"添加到对话"把它作为 `lines` 附件入队——
|
|
216
|
-
服务端只内联选中的区间(`<file path=... lines="2-3">`),可以精确指向想说的代码而不必
|
|
217
|
-
倾倒整个文件。预览读取上限 512 KB,二进制文件会被检测并拒绝。
|
|
218
|
-
- **实时工具输出。** `bash_execution_update` / `tool_execution_update` 事件被转发为轻量
|
|
219
|
-
`tool_delta` 消息,终端输出实时流动;最终输出在下一个快照的 toolResult 消息里到达,取代
|
|
220
|
-
delta 缓冲。
|
|
221
|
-
- **隔离会话。** 每个浏览器客户端在数据目录下拥有 `sessions/<clientId>/`,重连时通过
|
|
222
|
-
`SessionManager.continueRecent` 续接。
|
|
223
|
-
- **你已经拥有的一切。** 无需单独认证步骤——SDK 读取 `~/.pi/agent/auth.json` 并自动加载
|
|
224
|
-
你的全局扩展/技能。
|
|
225
|
-
|
|
226
|
-
## 终端
|
|
227
|
-
|
|
228
|
-
从顶栏切换终端视图(对话/终端)。它以三栏布局替代聊天界面:
|
|
229
|
-
|
|
230
|
-
- **左 — 命令**:点击命令在对应目录打开终端标签页并运行。可在面板里增/改/删命令;它们保存到
|
|
231
|
-
`<project>/.pi/commands.json`(提交进仓库,与队友共享):
|
|
232
|
-
|
|
233
|
-
```json
|
|
234
|
-
{
|
|
235
|
-
"commands": [
|
|
236
|
-
{ "name": "dev", "command": "npm run dev", "cwd": "${pwd}" },
|
|
237
|
-
{ "name": "test", "command": "npm test", "cwd": "${pwd}/server" },
|
|
238
|
-
{ "name": "build", "command": "npm run build", "cwd": "~/other-project" }
|
|
239
|
-
]
|
|
240
|
-
}
|
|
241
|
-
```
|
|
242
|
-
|
|
243
|
-
`${pwd}` 解析为智能体当前工作目录(聊天视图文件面板里显示的那个,可用 set_cwd 修改);
|
|
244
|
-
`~` 和相对路径同样有效。命令面板顶部的 `+` 按钮新建条目。
|
|
245
|
-
- **中 — 终端**:每个标签页是一个真实 PTY(macOS/Linux 是你的 `$SHELL`;Windows 是
|
|
246
|
-
PowerShell 或 cmd.exe——`$COMSPEC`);输出实时流动,可以输入、Ctrl+C、调整大小等,和桌面
|
|
247
|
-
终端一模一样。Windows 上的 Git Bash 用户会自动拿到 `$SHELL`。
|
|
248
|
-
- **右 — 终端(标签)**:VSCode 风格纵向标签条。`+` 在当前目录打开一个普通 shell。
|
|
249
|
-
关闭标签页会杀掉它的进程。
|
|
250
|
-
|
|
251
|
-
说明:
|
|
252
|
-
|
|
253
|
-
- 切回聊天视图时,运行中的命令继续运行。
|
|
254
|
-
- 客户端的最后一个浏览器标签断开时终端会被杀掉(不留孤儿 dev server),所以断线会重置终端视图。
|
|
255
|
-
|
|
256
|
-
## 协议
|
|
257
|
-
|
|
258
|
-
完整 wire 格式见 `server/protocol.ts`。客户端 → 服务端:`hello`、`prompt`、`abort`、
|
|
259
|
-
`new_chat`、`cycle_model`、`cycle_thinking`、`get_state`、`list_sessions`、
|
|
260
|
-
`switch_session`、`list_files`、`list_models`、`set_model`、`set_thinking`、`set_cwd`、
|
|
261
|
-
`complete_path`、`dialog_response`、`terminal_create`、`terminal_input`、
|
|
262
|
-
`terminal_resize`、`terminal_kill`、`run_command`、`list_commands`、`save_commands`。
|
|
263
|
-
服务端 → 客户端:`ready`、`snapshot`(完整 `UiState`)、`tool_delta`、`notice`、
|
|
264
|
-
`terminal_output`、`terminal_exit`、`commands`。
|
|
265
|
-
|
|
266
|
-
## 脚本
|
|
267
|
-
|
|
268
|
-
| 脚本 | 作用 |
|
|
269
|
-
| ---------------------------------- | ---------------------------------------------- |
|
|
270
|
-
| `npm run dev` | 服务端(tsx watch)+ Vite dev server + WS 代理 |
|
|
271
|
-
| `npm run build` | 类型检查 + 构建前端和服务端 |
|
|
272
|
-
| `npm start` | 运行生产服务端(提供`web/dist`) |
|
|
273
|
-
| `npm run typecheck` | 双端`tsc --noEmit` |
|
|
274
|
-
| `node terminal-smoke-test.mjs` | WS 层终端/命令协议测试(先 build) |
|
|
275
|
-
| `node terminal-browser-test.mjs` | 终端视图的无头浏览器 E2E(先 build) |
|
|
276
|
-
|
|
277
|
-
## 部署与开机自启
|
|
278
|
-
|
|
279
|
-
最快的路径:`pi-web-ui server install --port 8787 --cwd /path`——安装并让服务开机自启
|
|
280
|
-
(见[作为系统服务管理(开机自启)](#作为系统服务管理开机自启))。
|
|
281
|
-
下面的手动方案保留给参考 / 非标准场景。
|
|
282
|
-
|
|
283
|
-
### Docker(一条命令)
|
|
284
|
-
|
|
285
|
-
```bash
|
|
286
|
-
docker compose up -d # 构建,启动在 :8787,开机自动重启
|
|
287
|
-
docker compose stop # 停止(保留容器)
|
|
288
|
-
docker compose down # 停止并删除容器
|
|
289
|
-
```
|
|
290
|
-
|
|
291
|
-
`docker-compose.yml` 里的 `restart: unless-stopped` 让 Docker 守护进程启动时(开机、崩溃、
|
|
292
|
-
重启)把服务拉起来。挂载一个卷给 `/app/.pi-web`(会话持久化),可选地挂载你的 `~/.pi/agent`
|
|
293
|
-
配置和工作区——见 `docker-compose.yml` 里的注释。
|
|
294
|
-
|
|
295
|
-
### Linux — systemd
|
|
296
|
-
|
|
297
|
-
```bash
|
|
298
|
-
sudo npm i -g pi-web-ui
|
|
299
|
-
sudo cp deploy/pi-web-ui.service /etc/systemd/system/
|
|
300
|
-
# 先编辑 unit 里的 User/WorkingDirectory/Environment
|
|
301
|
-
sudo systemctl daemon-reload
|
|
302
|
-
sudo systemctl enable --now pi-web-ui # 立即启动 + 每次开机启动
|
|
303
|
-
sudo systemctl stop pi-web-ui # 停止
|
|
304
|
-
sudo systemctl disable pi-web-ui # 取消开机自启
|
|
305
|
-
journalctl -u pi-web-ui -f # 日志
|
|
306
|
-
```
|
|
307
|
-
|
|
308
|
-
### macOS — launchd
|
|
309
|
-
|
|
310
|
-
```bash
|
|
311
|
-
npm i -g pi-web-ui
|
|
312
|
-
cp deploy/com.xingshuyin.pi-web-ui.plist ~/Library/LaunchAgents/
|
|
313
|
-
# 编辑 ProgramArguments / WorkingDirectory / PI_WEB_CWD(which pi-web-ui)
|
|
314
|
-
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.xingshuyin.pi-web-ui.plist
|
|
315
|
-
launchctl bootout gui/$(id -u)/com.xingshuyin.pi-web-ui # 停止 + 移除自启
|
|
316
|
-
# 日志:/tmp/pi-web-ui.log、/tmp/pi-web-ui.err
|
|
317
|
-
```
|
|
318
|
-
|
|
319
|
-
### Windows — 任务计划程序
|
|
320
|
-
|
|
321
|
-
最简路径(自动生成一切,无需手改 XML):
|
|
322
|
-
|
|
323
|
-
```bat
|
|
324
|
-
npm i -g pi-web-ui
|
|
325
|
-
pi-web-ui server install --port 8787 --cwd C:\path\to\project
|
|
326
|
-
pi-web-ui server status
|
|
327
|
-
pi-web-ui server restart
|
|
328
|
-
pi-web-ui server stop :: 停止运行中的实例(自启保留)
|
|
329
|
-
pi-web-ui server uninstall :: 彻底移除任务
|
|
330
|
-
```
|
|
331
|
-
|
|
332
|
-
它的做法:写入 `%APPDATA%\pi-web-ui\pi-web-ui.ps1`(一个 PowerShell 启动器:设置
|
|
333
|
-
`PORT`/`PI_WEB_CWD`、cd 到工作目录、启动 node 并把输出追加到 `%USERPROFILE%\pi-web-ui.log`)
|
|
334
|
-
和任务计划程序 XML,然后注册一个**登录时**任务(`schtasks /Create /XML`——你登录时运行,
|
|
335
|
-
与 launchd 代理一致;通常不需要管理员,但如果遇到拒绝访问,请看下面的排障说明)。任务调用
|
|
336
|
-
`powershell.exe -WindowStyle Hidden`,所以服务运行**没有黑色控制台窗口**——没有可被误关/误杀
|
|
337
|
-
的东西。不实际安装即可预览两个生成文件:`pi-web-ui server install --print`。
|
|
338
|
-
|
|
339
|
-
手工方案用 `deploy/pi-web-ui-task.xml`:改好路径,把文件存成 **UTF-16 LE**(schtasks 要求),
|
|
340
|
-
然后 `schtasks /Create /TN "pi-web-ui" /XML pi-web-ui-task.xml /F` 和
|
|
341
|
-
`schtasks /Run /TN "pi-web-ui"`。
|
|
342
|
-
|
|
343
|
-
> **Windows 排障**
|
|
344
|
-
>
|
|
345
|
-
> - **`install` 报 `ERROR: Access is denied`(错误: 拒绝访问)**——部分机器上任务计划程序
|
|
346
|
-
> 不允许非提权令牌创建任务(删除自己拥有的任务 `schtasks /Delete` 却可以,所以
|
|
347
|
-
> `server uninstall` 正常)。解决:在**管理员(提权)PowerShell** 里执行
|
|
348
|
-
> `pi-web-ui server install`。
|
|
349
|
-
> - **务必显式传 `--cwd`,且指向用户可写目录。** 任务会继承安装时 shell 的当前目录作为
|
|
350
|
-
> 工作目录。从提权 shell 安装且不带 `--cwd` 时,任务会注册成 `C:\WINDOWS\system32`,
|
|
351
|
-
> 服务端启动时随即报
|
|
352
|
-
> `EPERM: operation not permitted, mkdir 'C:\WINDOWS\system32\.pi-web\sessions\...'`
|
|
353
|
-
> ——因为登录任务以最小权限令牌运行,无法在 `system32` 下写入。请用例如
|
|
354
|
-
> `--cwd C:\Users\<you>`(会话随之存到 `C:\Users\<you>\.pi-web`)。
|
|
355
|
-
> - **修复已装坏的任务**(目录注册错的任务):`pi-web-ui server uninstall`,然后
|
|
356
|
-
> 在提权 shell 里 `pi-web-ui server install --cwd C:\Users\<you>`。
|
|
357
|
-
> 用新选项重跑 `install` 也会就地重新生成任务。
|
|
358
|
-
>
|
|
359
|
-
> **不登录也要开机启动?** 登录任务需要交互式会话,与 launchd 代理一样。
|
|
360
|
-
> 无头/常开 Windows 请用 Docker(见上)。
|
|
361
|
-
|
|
362
|
-
三套模板分别使用 `KeepAlive` / `Restart=on-failure` / `RestartOnFailure`,服务崩溃后
|
|
363
|
-
自动重启,并在登录/开机时自动启动。
|
|
364
|
-
|
|
365
|
-
## License
|
|
366
|
-
|
|
367
|
-
MIT
|
|
1
|
+
# pi-web-ui
|
|
2
|
+
|
|
3
|
+
[English](README.md) | **简体中文**
|
|
4
|
+
|
|
5
|
+
[pi 编码智能体](https://pi.dev) 的 Web 聊天界面 —— 智能体通过 pi SDK 在服务端进程内运行,
|
|
6
|
+
事件经 WebSocket 流式推送到浏览器。支持思考块与工具调用、附件与图片问答、内置终端、
|
|
7
|
+
模型管理等功能。需要 Node.js ≥ 22.19 及配置好的 pi 环境。
|
|
8
|
+
|
|
9
|
+
## 界面截图
|
|
10
|
+
|
|
11
|
+

|
|
12
|
+
|
|
13
|
+
## 安装
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm i -g pi-web-ui # 全局安装(推荐)
|
|
17
|
+
npx pi-web-ui # 或免安装直接跑(拉取最新版,启动在 :8787)
|
|
18
|
+
npm i -g . # 或安装本地 checkout
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## 启动
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pi-web-ui # 前台,http://localhost:8787
|
|
25
|
+
PORT=9000 PI_WEB_CWD=/path/to/project pi-web-ui # 自定义端口 / 工作目录
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## 停止
|
|
29
|
+
|
|
30
|
+
- **前台**:在运行它的终端里按 `Ctrl+C`。
|
|
31
|
+
- **作为服务**:`pi-web-ui server stop`(停止实例;开机自启保留,直到 `server uninstall`)。
|
|
32
|
+
|
|
33
|
+
## 更新
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npm i -g pi-web-ui@latest # 升级到最新发布版本
|
|
37
|
+
pi-web-ui server restart # 重启服务使新版本生效(前台运行则手动重启)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## 卸载
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npm uninstall -g pi-web-ui
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
卸载**不会**删除你的聊天记录 —— 会话数据存放在 `<cwd>/.pi-web`(或 `PI_WEB_DATA_DIR`),
|
|
47
|
+
卸载/升级后依然保留。
|
|
48
|
+
|
|
49
|
+
## 作为系统服务(开机自启)
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pi-web-ui server install --port 9000 --cwd /path/to/project # 安装 + 启动
|
|
53
|
+
pi-web-ui server status # 运行中?开机自启?
|
|
54
|
+
pi-web-ui server restart # 重启(应用配置/版本变更)
|
|
55
|
+
pi-web-ui server stop # 停止(开机自启保留)
|
|
56
|
+
pi-web-ui server start # 再次启动
|
|
57
|
+
pi-web-ui server uninstall # 彻底移除服务
|
|
58
|
+
pi-web-ui server shortcut # 桌面一键启动图标
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
- **macOS** → launchd 代理(无需 sudo),日志 `/tmp/pi-web-ui.log` / `.err`
|
|
62
|
+
- **Linux** → systemd unit(`systemctl enable --now`),日志 `journalctl -u pi-web-ui -f`
|
|
63
|
+
- **Windows** → 计划任务(登录自启,隐藏 PowerShell 窗口,无黑窗)
|
|
64
|
+
|
|
65
|
+
选项:`--port`(默认 8787)、`--cwd`(工作目录)、`--data-dir`(会话目录)、
|
|
66
|
+
`--name`(自定义服务名)。重复执行 `server install` 并传入新选项即可重新生成配置
|
|
67
|
+
并重启服务 —— 这就是修改已装服务端口/工作目录的方式。
|
|
68
|
+
|
|
69
|
+
## License
|
|
70
|
+
|
|
71
|
+
MIT
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* so reconnects just re-request a snapshot.
|
|
12
12
|
*/
|
|
13
13
|
import { spawn } from "node:child_process";
|
|
14
|
-
import { existsSync, readFileSync, statSync, writeFileSync, mkdirSync, } from "node:fs";
|
|
14
|
+
import { existsSync, readFileSync, statSync, writeFileSync, mkdirSync, watch, } from "node:fs";
|
|
15
15
|
import { dirname, join, relative, resolve, sep } from "node:path";
|
|
16
16
|
import { fileURLToPath } from "node:url";
|
|
17
17
|
import { createAgentSessionFromServices, createAgentSessionRuntime, createAgentSessionServices, getAgentDir, SessionManager, } from "@earendil-works/pi-coding-agent";
|
|
@@ -728,6 +728,14 @@ export class ClientSession {
|
|
|
728
728
|
disposed = false;
|
|
729
729
|
/** pi-config readiness check, cached briefly so 60ms snapshots don't hit disk. */
|
|
730
730
|
piCheckCache = null;
|
|
731
|
+
/** fs.watch on the currently-listed directory — file changes push an instant
|
|
732
|
+
* refresh (`file_changed`) so the tree updates without waiting for the 10s
|
|
733
|
+
* poll. Only the listed directory is watched (one level); navigating
|
|
734
|
+
* re-watches the new target. fs.watch isn't available on every platform /
|
|
735
|
+
* filesystem — failures silently fall back to the poll. */
|
|
736
|
+
fsWatcher = null;
|
|
737
|
+
watchPath = null;
|
|
738
|
+
watchTimer = null;
|
|
731
739
|
constructor(clientId, cwd, agentDir, stateStore) {
|
|
732
740
|
this.clientId = clientId;
|
|
733
741
|
this.cwd = cwd;
|
|
@@ -829,8 +837,11 @@ export class ClientSession {
|
|
|
829
837
|
this.sinks.delete(send);
|
|
830
838
|
// No sockets left for this client — kill its terminals so processes don't
|
|
831
839
|
// survive a closed tab / dropped connection.
|
|
832
|
-
if (this.sinks.size === 0)
|
|
840
|
+
if (this.sinks.size === 0) {
|
|
833
841
|
this.terminals.killAll();
|
|
842
|
+
// No sockets → nobody to refresh; drop the dir watcher too.
|
|
843
|
+
this.unwatchDir();
|
|
844
|
+
}
|
|
834
845
|
}
|
|
835
846
|
/** Broadcast to every connected socket of this client. */
|
|
836
847
|
emit(msg) {
|
|
@@ -2432,6 +2443,9 @@ export class ClientSession {
|
|
|
2432
2443
|
// always use "/", but relative() returns "\\" on Windows.
|
|
2433
2444
|
const rel = rawRel.split(sep).join("/");
|
|
2434
2445
|
const { entries, truncated, error } = await readDirForUI(target, rel);
|
|
2446
|
+
// Watch the listed directory (only after a successful read — a missing
|
|
2447
|
+
// dir throws above and must not create a watcher on a phantom path).
|
|
2448
|
+
this.watchDir(target, rel);
|
|
2435
2449
|
if (error) {
|
|
2436
2450
|
// Windows-only: unreadable system dirs degrade to an empty list
|
|
2437
2451
|
// with a warning instead of a hard error — the panel stays usable.
|
|
@@ -2461,6 +2475,56 @@ export class ClientSession {
|
|
|
2461
2475
|
});
|
|
2462
2476
|
}
|
|
2463
2477
|
}
|
|
2478
|
+
/** Watch a directory for changes so the file panel refreshes instantly
|
|
2479
|
+
* instead of waiting for the 10s poll. Watches the directory exactly as
|
|
2480
|
+
* listed (one level); navigating re-watches the new target. fs.watch is
|
|
2481
|
+
* unavailable on some platforms/filesystems — failures silently fall back
|
|
2482
|
+
* to the poll. */
|
|
2483
|
+
watchDir(absPath, rel) {
|
|
2484
|
+
if (this.disposed || this.watchPath === rel)
|
|
2485
|
+
return;
|
|
2486
|
+
this.unwatchDir();
|
|
2487
|
+
this.watchPath = rel;
|
|
2488
|
+
try {
|
|
2489
|
+
// persistent: false — the watcher must not keep the process alive.
|
|
2490
|
+
this.fsWatcher = watch(absPath, { persistent: false }, () => {
|
|
2491
|
+
// Burst events (npm install, git ops, editor save→rename) are
|
|
2492
|
+
// debounced into a single refresh.
|
|
2493
|
+
if (this.watchTimer)
|
|
2494
|
+
return;
|
|
2495
|
+
this.watchTimer = setTimeout(() => {
|
|
2496
|
+
this.watchTimer = null;
|
|
2497
|
+
this.emit({ type: "file_changed", path: this.watchPath ?? "" });
|
|
2498
|
+
}, 400);
|
|
2499
|
+
});
|
|
2500
|
+
this.fsWatcher.on("error", () => {
|
|
2501
|
+
// Directory deleted / unsupported fs — stop watching; the poll (or
|
|
2502
|
+
// the next navigation) restores things.
|
|
2503
|
+
this.unwatchDir();
|
|
2504
|
+
});
|
|
2505
|
+
}
|
|
2506
|
+
catch {
|
|
2507
|
+
// fs.watch unsupported (some network mounts, containers) — poll covers it.
|
|
2508
|
+
this.fsWatcher = null;
|
|
2509
|
+
this.watchPath = null;
|
|
2510
|
+
}
|
|
2511
|
+
}
|
|
2512
|
+
unwatchDir() {
|
|
2513
|
+
if (this.watchTimer) {
|
|
2514
|
+
clearTimeout(this.watchTimer);
|
|
2515
|
+
this.watchTimer = null;
|
|
2516
|
+
}
|
|
2517
|
+
if (this.fsWatcher) {
|
|
2518
|
+
try {
|
|
2519
|
+
this.fsWatcher.close();
|
|
2520
|
+
}
|
|
2521
|
+
catch {
|
|
2522
|
+
// already closed
|
|
2523
|
+
}
|
|
2524
|
+
this.fsWatcher = null;
|
|
2525
|
+
}
|
|
2526
|
+
this.watchPath = null;
|
|
2527
|
+
}
|
|
2464
2528
|
/** Read a workspace file for the preview panel (size-capped, binary-safe). */
|
|
2465
2529
|
async readFile(relPath) {
|
|
2466
2530
|
try {
|
|
@@ -2827,6 +2891,7 @@ export class ClientSession {
|
|
|
2827
2891
|
clearInterval(this.widgetsTimer);
|
|
2828
2892
|
this.widgetsTimer = null;
|
|
2829
2893
|
}
|
|
2894
|
+
this.unwatchDir();
|
|
2830
2895
|
this.webUi.dispose();
|
|
2831
2896
|
for (const conv of this.convs.values()) {
|
|
2832
2897
|
conv.unsubscribe?.();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-web-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.1",
|
|
4
4
|
"description": "Web chat interface for the pi coding agent, powered by the pi SDK (@earendil-works/pi-coding-agent) — one-command run, Docker/systemd/launchd deployable",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|