pi-web-ui 0.2.14 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -9
- package/README.zh-CN.md +351 -0
- package/bin/pi-web-ui.mjs +47 -23
- package/deploy/pi-web-ui-task.xml +7 -5
- package/dist/server/agent-service.js +204 -8
- package/dist/server/index.js +35 -2
- package/package.json +1 -1
- package/web/dist/assets/index-Kxsj8yv_.css +41 -0
- package/web/dist/assets/{index-Tt1wcNur.js → index-u6b09GFS.js} +24 -24
- package/web/dist/index.html +2 -2
- package/web/dist/assets/index-Cjz-c1Wr.css +0 -41
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# pi-web-ui
|
|
2
2
|
|
|
3
|
+
**English** | [简体中文](README.zh-CN.md)
|
|
4
|
+
|
|
3
5
|
A web chat interface for the [pi coding agent](https://pi.dev), built directly on
|
|
4
6
|
the **pi SDK** ([`@earendil-works/pi-coding-agent`](https://www.npmjs.com/package/@earendil-works/pi-coding-agent)) —
|
|
5
7
|
no subprocess, no JSON-RPC shim. The agent runs in the server process and streams
|
|
@@ -139,11 +141,20 @@ pi-web-ui server uninstall # remove the service entirely
|
|
|
139
141
|
- **Linux** → systemd unit (auto-sudo): writes
|
|
140
142
|
`/etc/systemd/system/pi-web-ui.service` and runs `systemctl enable --now`,
|
|
141
143
|
logs via `journalctl -u pi-web-ui -f`.
|
|
142
|
-
- **Windows** → Task Scheduler
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
144
|
+
- **Windows** → Task Scheduler: creates a user task that starts at logon
|
|
145
|
+
(same as a launchd agent; usually no admin needed, but on some machines
|
|
146
|
+
`schtasks /Create` requires an elevated PowerShell — if `install` fails
|
|
147
|
+
with `ERROR: Access is denied`, rerun it from an admin shell). It runs a
|
|
148
|
+
PowerShell launcher generated at
|
|
149
|
+
`%APPDATA%\pi-web-ui\pi-web-ui.ps1` via `powershell.exe -WindowStyle
|
|
150
|
+
Hidden` — no black console window stays open, so there's nothing to
|
|
151
|
+
accidentally close/kill. The launcher sets env, cd's to the workspace,
|
|
152
|
+
launches node, appends logs to `%USERPROFILE%\pi-web-ui.log`. The task XML
|
|
153
|
+
is saved next to it; restarts on failure. **Always pass `--cwd`
|
|
154
|
+
explicitly** — the task inherits the installing shell's directory, and an
|
|
155
|
+
admin shell defaults to `C:\WINDOWS\system32`, which the non-elevated
|
|
156
|
+
task cannot write to (EPERM at startup). See
|
|
157
|
+
[Windows — Task Scheduler](#windows--task-scheduler) for details.
|
|
147
158
|
- Options: `--port` (default 8787 or `$PORT`), `--cwd` (default `$PI_WEB_CWD`
|
|
148
159
|
or the current directory), `--data-dir` (sessions), `--name` (custom service
|
|
149
160
|
name; on macOS the label is `com.xingshuyin.pi-web-ui`, custom names become
|
|
@@ -325,7 +336,7 @@ launchctl bootout gui/$(id -u)/com.xingshuyin.pi-web-ui # stop + remove auto-s
|
|
|
325
336
|
|
|
326
337
|
### Windows — Task Scheduler
|
|
327
338
|
|
|
328
|
-
Easiest path (
|
|
339
|
+
Easiest path (generates everything, no manual XML editing):
|
|
329
340
|
|
|
330
341
|
```bat
|
|
331
342
|
npm i -g pi-web-ui
|
|
@@ -336,18 +347,42 @@ pi-web-ui server stop :: stop the running instance (auto-start stays)
|
|
|
336
347
|
pi-web-ui server uninstall :: remove the task entirely
|
|
337
348
|
```
|
|
338
349
|
|
|
339
|
-
What it does: writes `%APPDATA%\pi-web-ui\pi-web-ui.
|
|
350
|
+
What it does: writes `%APPDATA%\pi-web-ui\pi-web-ui.ps1` (a PowerShell launcher
|
|
340
351
|
that sets `PORT`/`PI_WEB_CWD`, cd's to the workspace, launches node and
|
|
341
352
|
appends output to `%USERPROFILE%\pi-web-ui.log`) plus the Task Scheduler XML,
|
|
342
353
|
then registers a **logon** task (`schtasks /Create /XML` — the task runs when
|
|
343
|
-
you log in, same as a launchd agent; no admin needed
|
|
344
|
-
|
|
354
|
+
you log in, same as a launchd agent; usually no admin needed, but see the
|
|
355
|
+
troubleshooting note below if you get access denied). The task invokes
|
|
356
|
+
`powershell.exe -WindowStyle Hidden`, so the server runs with **no black
|
|
357
|
+
console window** — there is nothing to accidentally close or kill. Preview
|
|
358
|
+
both generated files without installing: `pi-web-ui server install --print`.
|
|
345
359
|
|
|
346
360
|
Manual alternative with `deploy/pi-web-ui-task.xml`: edit the paths, save the
|
|
347
361
|
file as **UTF-16 LE** (schtasks requires it), then
|
|
348
362
|
`schtasks /Create /TN "pi-web-ui" /XML pi-web-ui-task.xml /F` and
|
|
349
363
|
`schtasks /Run /TN "pi-web-ui"`.
|
|
350
364
|
|
|
365
|
+
> **Windows troubleshooting**
|
|
366
|
+
>
|
|
367
|
+
> - **`install` fails with `ERROR: Access is denied` (错误: 拒绝访问)** — on
|
|
368
|
+
> some machines Task Scheduler refuses to let a non-elevated token create
|
|
369
|
+
> tasks (deleting your own task with `schtasks /Delete` still works, which
|
|
370
|
+
> is why `server uninstall` succeeds). Fix: run
|
|
371
|
+
> `pi-web-ui server install` from an **elevated (admin) PowerShell**.
|
|
372
|
+
> - **Always pass `--cwd` explicitly, and point it at a user-writable
|
|
373
|
+
> directory.** The task inherits the installing shell's current directory
|
|
374
|
+
> as its working directory. Installing from an elevated shell without
|
|
375
|
+
> `--cwd` registers the task with `C:\WINDOWS\system32`, and the server
|
|
376
|
+
> then fails at startup with
|
|
377
|
+
> `EPERM: operation not permitted, mkdir 'C:\WINDOWS\system32\.pi-web\sessions\...'`
|
|
378
|
+
> because the logon task runs with a least-privilege token that cannot
|
|
379
|
+
> write under `system32`. Use e.g. `--cwd C:\Users\<you>` (sessions then
|
|
380
|
+
> go to `C:\Users\<you>\.pi-web`).
|
|
381
|
+
> - **Fix an already-broken task** (task created with the wrong directory):
|
|
382
|
+
> `pi-web-ui server uninstall`, then
|
|
383
|
+
> `pi-web-ui server install --cwd C:\Users\<you>` from an elevated shell.
|
|
384
|
+
> Rerunning `install` with new options also regenerates the task in place.
|
|
385
|
+
|
|
351
386
|
> **Boot-start without login?** A logon task needs an interactive session, just
|
|
352
387
|
> like a launchd agent. For headless/always-on Windows use Docker (see above).
|
|
353
388
|
|
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
# pi-web-ui
|
|
2
|
+
|
|
3
|
+
[English](README.md) | **简体中文**
|
|
4
|
+
|
|
5
|
+
面向 [pi 编码智能体](https://pi.dev) 的 Web 聊天界面,直接构建在
|
|
6
|
+
**pi SDK**([`@earendil-works/pi-coding-agent`](https://www.npmjs.com/package/@earendil-works/pi-coding-agent))之上——
|
|
7
|
+
无子进程、无 JSON-RPC 中间层。智能体运行在服务端进程内,通过 WebSocket 把事件流式推给浏览器。
|
|
8
|
+
|
|
9
|
+
灵感来自 [Pintra (pi-vsc)](https://github.com/bilalbentoumi/pi-vsc)——它在 VS Code 里通过
|
|
10
|
+
`pi --mode rpc` 实现同样的事情。本项目改用 SDK 的 `createAgentSessionRuntime` API 进程内调用
|
|
11
|
+
(SDK 文档也推荐 Node.js 应用走这条路而非 RPC),因此有类型安全、直接的状态访问,以及你现有的
|
|
12
|
+
pi 认证/配置/扩展——无需额外安装或配置任何东西。
|
|
13
|
+
|
|
14
|
+
## 功能特性
|
|
15
|
+
|
|
16
|
+
- 🧠 完整智能体循环:**思考**块(可折叠)+ 流式文本输出
|
|
17
|
+
- 🛠 工具执行卡片:**实时输出流**、状态(排队 → 运行中 → 完成/出错)、参数可复制
|
|
18
|
+
- 💬 会话**历史按浏览器持久化**(localStorage clientId + 每客户端会话目录)——刷新或重启后聊天记录都在。
|
|
19
|
+
会话面板还会列出当前文件夹下的 pi CLI/TUI 会话(标记为 `TUI`),可以直接从 Web 界面续聊终端对话
|
|
20
|
+
- 🔄 模型与思考强度切换(与 pi TUI 一致)、新对话、中止/停止
|
|
21
|
+
- 📎 Markdown 渲染:GFM 表格、语法高亮代码块、复制按钮
|
|
22
|
+
- 📁 工作区感知:智能体在你指定的目录里读/改/跑代码,使用**你自己的** `~/.pi/agent` 认证、模型、技能和扩展
|
|
23
|
+
- 🌐 多个浏览器客户端各自独立会话(每个 clientId 私有会话目录)
|
|
24
|
+
- 🖥 内置**终端**(xterm.js + node-pty,无需 VS Code):三栏布局——左侧**命令列表**
|
|
25
|
+
(用户自定义命令,支持 `${pwd}`,持久化在项目 `.pi/commands.json`)、中间**终端**、
|
|
26
|
+
右侧 VSCode 风格**标签条**支持多个并发 shell。通过顶栏按钮在对话/终端视图间切换。
|
|
27
|
+
|
|
28
|
+
## 快速开始
|
|
29
|
+
|
|
30
|
+
需要 Node.js ≥ 22.19(pi SDK 的要求;旧版 Node 加载 SDK 时会报 `Unexpected token 'with'`)
|
|
31
|
+
以及一个配置好的 pi 安装(先运行一次 `pi` 登录)。
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm install
|
|
35
|
+
npm run dev # 服务端 :8787,Web UI :5173(自动代理)
|
|
36
|
+
# 打开 http://localhost:5173
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
生产模式:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npm run build # 编译服务端 (tsc) + 前端 (vite)
|
|
43
|
+
npm start # 在 http://localhost:8787 提供全部服务
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## npm 包(安装 / 启动 / 停止 / 更新 / 卸载)
|
|
47
|
+
|
|
48
|
+
包已发布到 npm:[`pi-web-ui`](https://www.npmjs.com/package/pi-web-ui)。
|
|
49
|
+
|
|
50
|
+
### 安装
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
# 全局安装(推荐)
|
|
54
|
+
npm i -g pi-web-ui
|
|
55
|
+
|
|
56
|
+
# 或免安装直接跑(拉取最新版,启动在 :8787)
|
|
57
|
+
npx pi-web-ui
|
|
58
|
+
|
|
59
|
+
# 或安装本地 checkout(发布前测试改动用)
|
|
60
|
+
npm i -g .
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
> **npm 由 pi 托管?** 如果你的 `npm` 是拦截依赖安装脚本的 pi 包装器,装完后需要批准一次
|
|
64
|
+
> node-pty 的原生构建:`npm approve-scripts node-pty@1.1.0`(标准 npm 会自动完成)。
|
|
65
|
+
|
|
66
|
+
### 启动
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
pi-web-ui # 前台,http://localhost:8787
|
|
70
|
+
PORT=9000 PI_WEB_CWD=/path/to/project pi-web-ui # 自定义端口 / 工作目录
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
想后台运行或开机自启,请使用系统服务——见
|
|
74
|
+
[部署与开机自启](#部署与开机自启)(systemd / launchd / Docker)。
|
|
75
|
+
|
|
76
|
+
`pi-web-ui` 命令从包安装位置提供编译好的前端和 WebSocket API——不需要仓库 checkout。
|
|
77
|
+
它使用**你的** `~/.pi/agent` 配置(认证/模型/技能),并把每客户端会话存在
|
|
78
|
+
`<PI_WEB_CWD>/.pi-web` 下。
|
|
79
|
+
|
|
80
|
+
### 停止
|
|
81
|
+
|
|
82
|
+
- **前台**:在运行它的终端按 `Ctrl+C`。
|
|
83
|
+
- **systemd**:`sudo systemctl stop pi-web-ui`
|
|
84
|
+
- **launchd**:`launchctl bootout gui/$(id -u)/com.xingshuyin.pi-web-ui`
|
|
85
|
+
- **Windows(计划任务)**:`pi-web-ui server stop`(或 `schtasks /End /TN pi-web-ui`;
|
|
86
|
+
停止运行中的实例,自启保留到 `server uninstall` 为止)
|
|
87
|
+
- **Docker**:`docker compose stop`(停止并删除容器:`docker compose down`)
|
|
88
|
+
|
|
89
|
+
(后台进程应该用系统服务管理,而不是 `nohup`——上面的服务停止命令同时会停掉并禁用自启。)
|
|
90
|
+
|
|
91
|
+
### 验证 / 版本
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
pi-web-ui --version # CLI 版本
|
|
95
|
+
npm ls -g pi-web-ui # 是否已安装?哪个版本?
|
|
96
|
+
which pi-web-ui # 可执行文件位置
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### 更新
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
npm i -g pi-web-ui@latest # 升级到最新发布版
|
|
103
|
+
# 之后重启服务,新版本才会生效
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### 卸载
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
npm uninstall -g pi-web-ui
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
卸载**不会**删除你的聊天记录:会话数据存放在 `<PI_WEB_CWD>/.pi-web`
|
|
113
|
+
(或 `PI_WEB_DATA_DIR`),卸载/升级后依然保留。
|
|
114
|
+
|
|
115
|
+
### 作为系统服务管理(开机自启)
|
|
116
|
+
|
|
117
|
+
把服务端安装为开机自启的系统服务,可自定义端口和工作目录:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
pi-web-ui server install --port 9000 --cwd /path/to/project # 安装并启动
|
|
121
|
+
pi-web-ui server status # 运行中?自启?
|
|
122
|
+
pi-web-ui server restart # 重启(配置变更后同样用它)
|
|
123
|
+
pi-web-ui server stop # 停止 + 禁用自启
|
|
124
|
+
pi-web-ui server start # 重新启动
|
|
125
|
+
pi-web-ui server uninstall # 彻底移除服务
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
- **macOS** → launchd 代理(无需 sudo):写入并加载
|
|
129
|
+
`~/Library/LaunchAgents/com.xingshuyin.pi-web-ui.plist`,崩溃自动重启
|
|
130
|
+
(`KeepAlive`),日志在 `/tmp/pi-web-ui.log` / `/tmp/pi-web-ui.err`。
|
|
131
|
+
- **Linux** → systemd 单元(自动 sudo):写入
|
|
132
|
+
`/etc/systemd/system/pi-web-ui.service` 并执行 `systemctl enable --now`,
|
|
133
|
+
日志用 `journalctl -u pi-web-ui -f` 查看。
|
|
134
|
+
- **Windows** → 任务计划程序:创建登录时启动的用户任务(与 launchd 代理一致;
|
|
135
|
+
通常不需要管理员,但部分机器上 `schtasks /Create` 需要提权的 PowerShell——
|
|
136
|
+
如果 `install` 报 `ERROR: Access is denied`,请用管理员 shell 重跑)。任务通过
|
|
137
|
+
`powershell.exe -WindowStyle Hidden` 运行生成在 `%APPDATA%\pi-web-ui\pi-web-ui.ps1`
|
|
138
|
+
的 PowerShell 启动器——**不会有黑色控制台窗口**常驻,没有可被误关/误杀的东西。
|
|
139
|
+
启动器设置环境变量、cd 到工作目录、启动 node,并把日志追加到
|
|
140
|
+
`%USERPROFILE%\pi-web-ui.log`。任务 XML 保存在旁边;失败会自动重启。
|
|
141
|
+
**务必显式传 `--cwd`**——任务会继承安装时 shell 的目录,而管理员 shell 默认是
|
|
142
|
+
`C:\WINDOWS\system32`,非提权任务写不进去(启动即 EPERM)。详见
|
|
143
|
+
[Windows — 任务计划程序](#windows--任务计划程序)。
|
|
144
|
+
- 选项:`--port`(默认 8787 或 `$PORT`)、`--cwd`(默认 `$PI_WEB_CWD` 或当前目录)、
|
|
145
|
+
`--data-dir`(会话目录)、`--name`(自定义服务名;macOS 标签为
|
|
146
|
+
`com.xingshuyin.pi-web-ui`,自定义名变成 `com.<name>.server`)。`--print` 预览
|
|
147
|
+
生成的 unit/plist/任务文件而不实际应用。
|
|
148
|
+
- 用新参数重跑 `install` 会重新生成配置并重启服务——这就是修改已装服务端口/cwd 的方式。
|
|
149
|
+
|
|
150
|
+
## 配置
|
|
151
|
+
|
|
152
|
+
| 环境变量 | 默认值 | 说明 |
|
|
153
|
+
| --- | --- | --- |
|
|
154
|
+
| `PORT` | `8787` | HTTP/WebSocket 端口 |
|
|
155
|
+
| `PI_WEB_CWD` | 服务端 cwd | 智能体操作的工作区目录(读/编辑/bash/写) |
|
|
156
|
+
| `PI_WEB_DATA_DIR` | `<cwd>/.pi-web` | 每客户端会话目录的存放位置 |
|
|
157
|
+
| `PI_WEB_INLINE_FILE_MAX` | `12288` (12KB) | 小于等于该大小的文本附件直接内联进模型上下文;更大的文件以路径引用方式传入,模型按需用 read 工具读取(小改动省 token) |
|
|
158
|
+
| `PI_CODING_AGENT_DIR` | `~/.pi/agent` | pi 配置目录(auth.json、models.json、skills、extensions) |
|
|
159
|
+
|
|
160
|
+
示例——让智能体面向某个项目:
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
PI_WEB_CWD=/path/to/your/project npm run dev
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## 架构
|
|
167
|
+
|
|
168
|
+
```text
|
|
169
|
+
Browser (React + Vite)
|
|
170
|
+
│ WebSocket JSON — 快照驱动协议 (server/protocol.ts)
|
|
171
|
+
▼
|
|
172
|
+
server/index.ts express 静态 + ws 端点
|
|
173
|
+
│
|
|
174
|
+
server/agent-service.ts 每客户端 ClientSession:
|
|
175
|
+
│ createAgentSessionRuntime({ sessionManager: SessionManager.continueRecent(cwd, sessionDir) })
|
|
176
|
+
│ session.subscribe(events) → 节流全量快照 + 实时工具增量
|
|
177
|
+
▼
|
|
178
|
+
@earendil-works/pi-coding-agent (SDK, 进程内)
|
|
179
|
+
│ ModelRuntime (auth 来自 ~/.pi/agent) · tools · extensions · skills
|
|
180
|
+
▼
|
|
181
|
+
你的 LLM 提供商
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
关键设计点:
|
|
185
|
+
|
|
186
|
+
- **快照驱动 UI。** 服务端是唯一事实源:每次 SDK 事件后调度一个节流(60 ms)的全量快照,
|
|
187
|
+
浏览器纯粹按快照渲染。重连只需重新请求 `get_state`。大载荷(工具输出、文本)在序列化时
|
|
188
|
+
做了截断(`server/serialize.ts`)。
|
|
189
|
+
- **助手实时流式输出。** 进行中的消息(SDK `agent.state.streamingMessage`)被序列化进每个快照,
|
|
190
|
+
所以思考块和回答文本是**边生成边**出现在浏览器里(带闪烁光标),而不是等整轮结束才显示。
|
|
191
|
+
部分消息拿到稳定的 `stream-<ts>` id,跨快照保持挂载(展开的思考/工具块状态不丢)。
|
|
192
|
+
- **按大小感知的附件。** 点击 + 把文件加入附件队列(显示在输入框上方的 chips)。发送时服务端
|
|
193
|
+
把每个文件作为独立的 custom message 附加(SDK `sendCustomMessage` + `nextTurn` asides)——
|
|
194
|
+
用户消息保持干净,每个文件渲染成自己可折叠的卡片:小文本文件(≤ `PI_WEB_INLINE_FILE_MAX`,
|
|
195
|
+
默认 12KB)直接内联,模型立即看到;更大的文件以 `<file path=...>` 引用传入,模型按需用
|
|
196
|
+
`read` 工具读取——所以附加一个 5 MB 的文件在模型真正查看前只花几个 token。图片始终以
|
|
197
|
+
image content 附加。
|
|
198
|
+
- **带行号选区的文件预览。** 在右侧面板点击文件名(或其 👁 按钮)打开带行号的预览弹窗。
|
|
199
|
+
点击 / 拖拽 / Shift+点击选择行区间,然后点"添加到对话"把它作为 `lines` 附件入队——
|
|
200
|
+
服务端只内联选中的区间(`<file path=... lines="2-3">`),可以精确指向想说的代码而不必
|
|
201
|
+
倾倒整个文件。预览读取上限 512 KB,二进制文件会被检测并拒绝。
|
|
202
|
+
- **实时工具输出。** `bash_execution_update` / `tool_execution_update` 事件被转发为轻量
|
|
203
|
+
`tool_delta` 消息,终端输出实时流动;最终输出在下一个快照的 toolResult 消息里到达,取代
|
|
204
|
+
delta 缓冲。
|
|
205
|
+
- **隔离会话。** 每个浏览器客户端在数据目录下拥有 `sessions/<clientId>/`,重连时通过
|
|
206
|
+
`SessionManager.continueRecent` 续接。
|
|
207
|
+
- **你已经拥有的一切。** 无需单独认证步骤——SDK 读取 `~/.pi/agent/auth.json` 并自动加载
|
|
208
|
+
你的全局扩展/技能。
|
|
209
|
+
|
|
210
|
+
## 终端
|
|
211
|
+
|
|
212
|
+
从顶栏切换终端视图(对话/终端)。它以三栏布局替代聊天界面:
|
|
213
|
+
|
|
214
|
+
- **左 — 命令**:点击命令在对应目录打开终端标签页并运行。可在面板里增/改/删命令;它们保存到
|
|
215
|
+
`<project>/.pi/commands.json`(提交进仓库,与队友共享):
|
|
216
|
+
|
|
217
|
+
```json
|
|
218
|
+
{
|
|
219
|
+
"commands": [
|
|
220
|
+
{ "name": "dev", "command": "npm run dev", "cwd": "${pwd}" },
|
|
221
|
+
{ "name": "test", "command": "npm test", "cwd": "${pwd}/server" },
|
|
222
|
+
{ "name": "build", "command": "npm run build", "cwd": "~/other-project" }
|
|
223
|
+
]
|
|
224
|
+
}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
`${pwd}` 解析为智能体当前工作目录(聊天视图文件面板里显示的那个,可用 set_cwd 修改);
|
|
228
|
+
`~` 和相对路径同样有效。命令面板顶部的 `+` 按钮新建条目。
|
|
229
|
+
- **中 — 终端**:每个标签页是一个真实 PTY(macOS/Linux 是你的 `$SHELL`;Windows 是
|
|
230
|
+
PowerShell 或 cmd.exe——`$COMSPEC`);输出实时流动,可以输入、Ctrl+C、调整大小等,和桌面
|
|
231
|
+
终端一模一样。Windows 上的 Git Bash 用户会自动拿到 `$SHELL`。
|
|
232
|
+
- **右 — 终端(标签)**:VSCode 风格纵向标签条。`+` 在当前目录打开一个普通 shell。
|
|
233
|
+
关闭标签页会杀掉它的进程。
|
|
234
|
+
|
|
235
|
+
说明:
|
|
236
|
+
|
|
237
|
+
- 切回聊天视图时,运行中的命令继续运行。
|
|
238
|
+
- 客户端的最后一个浏览器标签断开时终端会被杀掉(不留孤儿 dev server),所以断线会重置终端视图。
|
|
239
|
+
|
|
240
|
+
## 协议
|
|
241
|
+
|
|
242
|
+
完整 wire 格式见 `server/protocol.ts`。客户端 → 服务端:`hello`、`prompt`、`abort`、
|
|
243
|
+
`new_chat`、`cycle_model`、`cycle_thinking`、`get_state`、`list_sessions`、
|
|
244
|
+
`switch_session`、`list_files`、`list_models`、`set_model`、`set_thinking`、`set_cwd`、
|
|
245
|
+
`complete_path`、`dialog_response`、`terminal_create`、`terminal_input`、
|
|
246
|
+
`terminal_resize`、`terminal_kill`、`run_command`、`list_commands`、`save_commands`。
|
|
247
|
+
服务端 → 客户端:`ready`、`snapshot`(完整 `UiState`)、`tool_delta`、`notice`、
|
|
248
|
+
`terminal_output`、`terminal_exit`、`commands`。
|
|
249
|
+
|
|
250
|
+
## 脚本
|
|
251
|
+
|
|
252
|
+
| 脚本 | 作用 |
|
|
253
|
+
| --- | --- |
|
|
254
|
+
| `npm run dev` | 服务端(tsx watch)+ Vite dev server + WS 代理 |
|
|
255
|
+
| `npm run build` | 类型检查 + 构建前端和服务端 |
|
|
256
|
+
| `npm start` | 运行生产服务端(提供 `web/dist`) |
|
|
257
|
+
| `npm run typecheck` | 双端 `tsc --noEmit` |
|
|
258
|
+
| `node terminal-smoke-test.mjs` | WS 层终端/命令协议测试(先 build) |
|
|
259
|
+
| `node terminal-browser-test.mjs` | 终端视图的无头浏览器 E2E(先 build) |
|
|
260
|
+
|
|
261
|
+
## 部署与开机自启
|
|
262
|
+
|
|
263
|
+
最快的路径:`pi-web-ui server install --port 8787 --cwd /path`——安装并让服务开机自启
|
|
264
|
+
(见[作为系统服务管理(开机自启)](#作为系统服务管理开机自启))。
|
|
265
|
+
下面的手动方案保留给参考 / 非标准场景。
|
|
266
|
+
|
|
267
|
+
### Docker(一条命令)
|
|
268
|
+
|
|
269
|
+
```bash
|
|
270
|
+
docker compose up -d # 构建,启动在 :8787,开机自动重启
|
|
271
|
+
docker compose stop # 停止(保留容器)
|
|
272
|
+
docker compose down # 停止并删除容器
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
`docker-compose.yml` 里的 `restart: unless-stopped` 让 Docker 守护进程启动时(开机、崩溃、
|
|
276
|
+
重启)把服务拉起来。挂载一个卷给 `/app/.pi-web`(会话持久化),可选地挂载你的 `~/.pi/agent`
|
|
277
|
+
配置和工作区——见 `docker-compose.yml` 里的注释。
|
|
278
|
+
|
|
279
|
+
### Linux — systemd
|
|
280
|
+
|
|
281
|
+
```bash
|
|
282
|
+
sudo npm i -g pi-web-ui
|
|
283
|
+
sudo cp deploy/pi-web-ui.service /etc/systemd/system/
|
|
284
|
+
# 先编辑 unit 里的 User/WorkingDirectory/Environment
|
|
285
|
+
sudo systemctl daemon-reload
|
|
286
|
+
sudo systemctl enable --now pi-web-ui # 立即启动 + 每次开机启动
|
|
287
|
+
sudo systemctl stop pi-web-ui # 停止
|
|
288
|
+
sudo systemctl disable pi-web-ui # 取消开机自启
|
|
289
|
+
journalctl -u pi-web-ui -f # 日志
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
### macOS — launchd
|
|
293
|
+
|
|
294
|
+
```bash
|
|
295
|
+
npm i -g pi-web-ui
|
|
296
|
+
cp deploy/com.xingshuyin.pi-web-ui.plist ~/Library/LaunchAgents/
|
|
297
|
+
# 编辑 ProgramArguments / WorkingDirectory / PI_WEB_CWD(which pi-web-ui)
|
|
298
|
+
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.xingshuyin.pi-web-ui.plist
|
|
299
|
+
launchctl bootout gui/$(id -u)/com.xingshuyin.pi-web-ui # 停止 + 移除自启
|
|
300
|
+
# 日志:/tmp/pi-web-ui.log、/tmp/pi-web-ui.err
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
### Windows — 任务计划程序
|
|
304
|
+
|
|
305
|
+
最简路径(自动生成一切,无需手改 XML):
|
|
306
|
+
|
|
307
|
+
```bat
|
|
308
|
+
npm i -g pi-web-ui
|
|
309
|
+
pi-web-ui server install --port 8787 --cwd C:\path\to\project
|
|
310
|
+
pi-web-ui server status
|
|
311
|
+
pi-web-ui server restart
|
|
312
|
+
pi-web-ui server stop :: 停止运行中的实例(自启保留)
|
|
313
|
+
pi-web-ui server uninstall :: 彻底移除任务
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
它的做法:写入 `%APPDATA%\pi-web-ui\pi-web-ui.ps1`(一个 PowerShell 启动器:设置
|
|
317
|
+
`PORT`/`PI_WEB_CWD`、cd 到工作目录、启动 node 并把输出追加到 `%USERPROFILE%\pi-web-ui.log`)
|
|
318
|
+
和任务计划程序 XML,然后注册一个**登录时**任务(`schtasks /Create /XML`——你登录时运行,
|
|
319
|
+
与 launchd 代理一致;通常不需要管理员,但如果遇到拒绝访问,请看下面的排障说明)。任务调用
|
|
320
|
+
`powershell.exe -WindowStyle Hidden`,所以服务运行**没有黑色控制台窗口**——没有可被误关/误杀
|
|
321
|
+
的东西。不实际安装即可预览两个生成文件:`pi-web-ui server install --print`。
|
|
322
|
+
|
|
323
|
+
手工方案用 `deploy/pi-web-ui-task.xml`:改好路径,把文件存成 **UTF-16 LE**(schtasks 要求),
|
|
324
|
+
然后 `schtasks /Create /TN "pi-web-ui" /XML pi-web-ui-task.xml /F` 和
|
|
325
|
+
`schtasks /Run /TN "pi-web-ui"`。
|
|
326
|
+
|
|
327
|
+
> **Windows 排障**
|
|
328
|
+
>
|
|
329
|
+
> - **`install` 报 `ERROR: Access is denied`(错误: 拒绝访问)**——部分机器上任务计划程序
|
|
330
|
+
> 不允许非提权令牌创建任务(删除自己拥有的任务 `schtasks /Delete` 却可以,所以
|
|
331
|
+
> `server uninstall` 正常)。解决:在**管理员(提权)PowerShell** 里执行
|
|
332
|
+
> `pi-web-ui server install`。
|
|
333
|
+
> - **务必显式传 `--cwd`,且指向用户可写目录。** 任务会继承安装时 shell 的当前目录作为
|
|
334
|
+
> 工作目录。从提权 shell 安装且不带 `--cwd` 时,任务会注册成 `C:\WINDOWS\system32`,
|
|
335
|
+
> 服务端启动时随即报
|
|
336
|
+
> `EPERM: operation not permitted, mkdir 'C:\WINDOWS\system32\.pi-web\sessions\...'`
|
|
337
|
+
> ——因为登录任务以最小权限令牌运行,无法在 `system32` 下写入。请用例如
|
|
338
|
+
> `--cwd C:\Users\<you>`(会话随之存到 `C:\Users\<you>\.pi-web`)。
|
|
339
|
+
> - **修复已装坏的任务**(目录注册错的任务):`pi-web-ui server uninstall`,然后
|
|
340
|
+
> 在提权 shell 里 `pi-web-ui server install --cwd C:\Users\<you>`。
|
|
341
|
+
> 用新选项重跑 `install` 也会就地重新生成任务。
|
|
342
|
+
>
|
|
343
|
+
> **不登录也要开机启动?** 登录任务需要交互式会话,与 launchd 代理一样。
|
|
344
|
+
> 无头/常开 Windows 请用 Docker(见上)。
|
|
345
|
+
|
|
346
|
+
三套模板分别使用 `KeepAlive` / `Restart=on-failure` / `RestartOnFailure`,服务崩溃后
|
|
347
|
+
自动重启,并在登录/开机时自动启动。
|
|
348
|
+
|
|
349
|
+
## License
|
|
350
|
+
|
|
351
|
+
MIT
|
package/bin/pi-web-ui.mjs
CHANGED
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
* (--name 自定义时 com.<name>.server),无需 sudo
|
|
15
15
|
* - Linux → systemd 单元 <name>.service(/etc/systemd/system/,自动 sudo)
|
|
16
16
|
* - Windows → 计划任务(Task Scheduler / schtasks,登录后自启,无需管理员),
|
|
17
|
-
*
|
|
17
|
+
* 隐藏窗口启动(无黑窗);PowerShell 启动脚本与任务 XML 生成在
|
|
18
|
+
* %APPDATA%\pi-web-ui\
|
|
18
19
|
*
|
|
19
20
|
* 环境变量(前台与系统服务均适用):PORT / PI_WEB_CWD / PI_WEB_DATA_DIR /
|
|
20
21
|
* PI_CODING_AGENT_DIR。
|
|
@@ -62,7 +63,7 @@ server 选项:
|
|
|
62
63
|
--print 只打印将生成的配置文件,不实际安装
|
|
63
64
|
|
|
64
65
|
平台: macOS → launchd 用户代理 · Linux → systemd · Windows → 计划任务(schtasks)
|
|
65
|
-
(Windows
|
|
66
|
+
(Windows 任务登录后自启、无需管理员、隐藏窗口运行;stop 停止,uninstall 移除)
|
|
66
67
|
|
|
67
68
|
环境变量(前台与系统服务均适用):
|
|
68
69
|
PORT / PI_WEB_CWD / PI_WEB_DATA_DIR / PI_CODING_AGENT_DIR
|
|
@@ -214,6 +215,10 @@ function winCmdPath(name) {
|
|
|
214
215
|
return join(winServiceDir(), `${name}.cmd`);
|
|
215
216
|
}
|
|
216
217
|
|
|
218
|
+
function winPs1Path(name) {
|
|
219
|
+
return join(winServiceDir(), `${name}.ps1`);
|
|
220
|
+
}
|
|
221
|
+
|
|
217
222
|
function winTaskXmlPath(name) {
|
|
218
223
|
return join(winServiceDir(), `${name}.xml`);
|
|
219
224
|
}
|
|
@@ -230,17 +235,30 @@ function winTaskExists(name) {
|
|
|
230
235
|
);
|
|
231
236
|
}
|
|
232
237
|
|
|
233
|
-
/**
|
|
234
|
-
function
|
|
238
|
+
/** Single-quote a string for embedding in a generated PowerShell script. */
|
|
239
|
+
function psQuote(s) {
|
|
240
|
+
return "'" + s.replace(/'/g, "''") + "'";
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Build the PowerShell launcher the scheduled task runs. Task Scheduler
|
|
245
|
+
* launches it with -WindowStyle Hidden, so the console window is created
|
|
246
|
+
* hidden (SW_HIDE) — no black cmd window stays open while the server runs,
|
|
247
|
+
* and there is nothing to accidentally close/kill. The script sets the env,
|
|
248
|
+
* cd's to the workspace, then runs node in the foreground with output
|
|
249
|
+
* appended to the log, so the task instance IS the powershell process and
|
|
250
|
+
* `schtasks /End` still stops the whole tree.
|
|
251
|
+
*/
|
|
252
|
+
function buildWinStartPs1(env, cwd, logPath) {
|
|
235
253
|
const sets = Object.entries(env)
|
|
236
|
-
.map(([k, v]) =>
|
|
254
|
+
.map(([k, v]) => `$env:${k} = ${psQuote(v)}`)
|
|
237
255
|
.join("\r\n");
|
|
238
256
|
return [
|
|
239
|
-
"
|
|
240
|
-
"
|
|
257
|
+
"# Generated by: pi-web-ui server install (rerun to change)",
|
|
258
|
+
"# Starts the server with a hidden console window (no black cmd box).",
|
|
241
259
|
sets,
|
|
242
|
-
`
|
|
243
|
-
|
|
260
|
+
`Set-Location ${psQuote(cwd)}`,
|
|
261
|
+
`& ${psQuote(NODE)} ${psQuote(SERVER_ENTRY)} *>> ${psQuote(logPath)}`,
|
|
244
262
|
"",
|
|
245
263
|
].join("\r\n");
|
|
246
264
|
}
|
|
@@ -248,13 +266,16 @@ function buildWinCmd(cwd, env, logPath) {
|
|
|
248
266
|
/**
|
|
249
267
|
* Build the Task Scheduler XML for a user task: LogonTrigger (starts at logon,
|
|
250
268
|
* like a launchd agent — no admin needed), InteractiveToken, auto-restart on
|
|
251
|
-
* failure. The task runs the
|
|
269
|
+
* failure. The task runs the PowerShell launcher via
|
|
270
|
+
* powershell.exe -WindowStyle Hidden, so no console window ever appears.
|
|
252
271
|
*/
|
|
253
|
-
function buildWinTaskXml(
|
|
254
|
-
const
|
|
272
|
+
function buildWinTaskXml(ps1Path, cwd) {
|
|
273
|
+
const powershell = join(
|
|
255
274
|
process.env.SystemRoot ?? "C:\\Windows",
|
|
256
275
|
"System32",
|
|
257
|
-
"
|
|
276
|
+
"WindowsPowerShell",
|
|
277
|
+
"v1.0",
|
|
278
|
+
"powershell.exe",
|
|
258
279
|
);
|
|
259
280
|
return `<?xml version="1.0" encoding="UTF-16"?>
|
|
260
281
|
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
|
|
@@ -297,8 +318,8 @@ function buildWinTaskXml(cmdPath, cwd) {
|
|
|
297
318
|
</Settings>
|
|
298
319
|
<Actions Context="Author">
|
|
299
320
|
<Exec>
|
|
300
|
-
<Command>${esc(
|
|
301
|
-
<Arguments
|
|
321
|
+
<Command>${esc(powershell)}</Command>
|
|
322
|
+
<Arguments>-NoProfile -NonInteractive -ExecutionPolicy Bypass -WindowStyle Hidden -File "${esc(ps1Path)}"</Arguments>
|
|
302
323
|
<WorkingDirectory>${esc(cwd)}</WorkingDirectory>
|
|
303
324
|
</Exec>
|
|
304
325
|
</Actions>
|
|
@@ -502,17 +523,20 @@ function uninstallSystemd(opts) {
|
|
|
502
523
|
function installWindows(opts) {
|
|
503
524
|
const { name, port, cwd, dataDir } = serviceOptions(opts);
|
|
504
525
|
const env = serviceEnv(port, cwd, dataDir);
|
|
505
|
-
const
|
|
526
|
+
const ps1Path = winPs1Path(name);
|
|
506
527
|
const xmlPath = winTaskXmlPath(name);
|
|
507
|
-
const
|
|
508
|
-
const xml = buildWinTaskXml(
|
|
528
|
+
const ps1 = buildWinStartPs1(env, cwd, winLogPath());
|
|
529
|
+
const xml = buildWinTaskXml(ps1Path, cwd);
|
|
509
530
|
if (opts.print) {
|
|
510
|
-
console.log(`# ${
|
|
531
|
+
console.log(`# ${ps1Path}\n${ps1}`);
|
|
511
532
|
console.log(`# ${xmlPath}\n${xml}`);
|
|
512
533
|
return;
|
|
513
534
|
}
|
|
514
|
-
mkdirSync(dirname(
|
|
515
|
-
|
|
535
|
+
mkdirSync(dirname(ps1Path), { recursive: true });
|
|
536
|
+
// UTF-8 with BOM: Windows PowerShell 5.1 misreads BOM-less UTF-8 as ANSI.
|
|
537
|
+
writeFileSync(ps1Path, "\uFEFF" + ps1, "utf8");
|
|
538
|
+
// Remove the old-style .cmd wrapper from previous installs.
|
|
539
|
+
if (existsSync(winCmdPath(name))) rmSync(winCmdPath(name));
|
|
516
540
|
// schtasks /Create /XML requires a UTF-16 file (with BOM).
|
|
517
541
|
writeFileSync(xmlPath, "\uFEFF" + xml, "utf16le");
|
|
518
542
|
if (winTaskExists(name)) {
|
|
@@ -523,7 +547,7 @@ function installWindows(opts) {
|
|
|
523
547
|
}
|
|
524
548
|
run("schtasks", ["/Create", "/TN", name, "/XML", xmlPath, "/F"]);
|
|
525
549
|
run("schtasks", ["/Run", "/TN", name], { ignoreError: true });
|
|
526
|
-
console.log(`✅ 已安装并启动计划任务 ${name}
|
|
550
|
+
console.log(`✅ 已安装并启动计划任务 ${name}(隐藏窗口运行,无黑窗)`);
|
|
527
551
|
console.log(` 端口 : ${port}`);
|
|
528
552
|
console.log(` 目录 : ${cwd}`);
|
|
529
553
|
console.log(` 访问 : http://localhost:${port}`);
|
|
@@ -539,7 +563,7 @@ function uninstallWindows(opts) {
|
|
|
539
563
|
if (winTaskExists(name)) {
|
|
540
564
|
run("schtasks", ["/Delete", "/TN", name, "/F"], { ignoreError: true });
|
|
541
565
|
}
|
|
542
|
-
for (const f of [winCmdPath(name), winTaskXmlPath(name)]) {
|
|
566
|
+
for (const f of [winCmdPath(name), winPs1Path(name), winTaskXmlPath(name)]) {
|
|
543
567
|
if (existsSync(f)) rmSync(f);
|
|
544
568
|
}
|
|
545
569
|
console.log(`🗑 已卸载 ${name}(计划任务已删除,不再自启)`);
|
|
@@ -12,9 +12,11 @@
|
|
|
12
12
|
schtasks /Run /TN "pi-web-ui"
|
|
13
13
|
|
|
14
14
|
Notes:
|
|
15
|
-
- The task
|
|
16
|
-
%APPDATA%\pi-web-ui\pi-web-ui.
|
|
17
|
-
|
|
15
|
+
- The task runs the PowerShell launcher the CLI generates at
|
|
16
|
+
%APPDATA%\pi-web-ui\pi-web-ui.ps1 with -WindowStyle Hidden, so the
|
|
17
|
+
server runs with no black console window (nothing to accidentally
|
|
18
|
+
close/kill). The ps1 sets PORT/PI_WEB_CWD, cd's to the workspace,
|
|
19
|
+
launches node, and appends output to %USERPROFILE%\pi-web-ui.log.
|
|
18
20
|
Preview both generated files with: pi-web-ui server install --print
|
|
19
21
|
- Save this file as UTF-16 LE (schtasks requires it; the CLI does this
|
|
20
22
|
automatically when it writes the task XML).
|
|
@@ -61,8 +63,8 @@
|
|
|
61
63
|
</Settings>
|
|
62
64
|
<Actions Context="Author">
|
|
63
65
|
<Exec>
|
|
64
|
-
<Command>C:\Windows\System32\
|
|
65
|
-
<Arguments
|
|
66
|
+
<Command>C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe</Command>
|
|
67
|
+
<Arguments>-NoProfile -NonInteractive -ExecutionPolicy Bypass -WindowStyle Hidden -File "%APPDATA%\pi-web-ui\pi-web-ui.ps1"</Arguments>
|
|
66
68
|
<WorkingDirectory>%USERPROFILE%</WorkingDirectory>
|
|
67
69
|
</Exec>
|
|
68
70
|
</Actions>
|