pi-web-ui 0.80.0 → 0.80.2
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/CHANGELOG.md +45 -3
- package/README.md +204 -36
- package/README.zh-CN.md +190 -25
- package/bin/pi-web-ui.mjs +21 -4
- package/deploy/nginx-subpath.conf +85 -88
- package/dist/server/agent-service.js +22 -7
- package/dist/server/dsh/dsh-agent-service.js +16 -6
- package/dist/server/index.js +38 -1
- package/dist/server/launch-origin.js +148 -0
- package/dist/server/slash-commands.js +19 -3
- package/dist/server/terminals.js +17 -9
- package/package.json +2 -2
- package/plugins/catalog.json +9 -0
- package/web/dist/assets/{TerminalPanel-BYigWYyx.js → TerminalPanel-CeruoZjh.js} +1 -1
- package/web/dist/assets/{index-GKv6Qo91.css → index-BduNm7_u.css} +1 -1
- package/web/dist/assets/index-DtBJSe33.js +347 -0
- package/web/dist/index.html +2 -2
- package/web/dist/assets/index-D7BpHJ59.js +0 -347
package/README.zh-CN.md
CHANGED
|
@@ -28,13 +28,36 @@
|
|
|
28
28
|
- WebSocket 流式聊天 —— pi SDK 在服务端进程内运行,事件以快照(60ms 节流)推送,浏览器按快照渲染。
|
|
29
29
|
- 思考块、工具调用卡片、bash 输出,实时显示状态(执行中 → 已结束 · 等模型 · 耗时)。
|
|
30
30
|
- **补充(steer)** —— 回复流式中可排队发送跟进消息,当前回合工具结算后立即注入(对应 pi CLI 的 Enter 打断语义)。
|
|
31
|
-
- **斜杠命令** —— 输入 `/` 弹出命令选择器(内置 / 扩展 / 模板 / 技能);内置 `/new /model /compact /cwd /thinking /resume`,另有 `/help`(命令清单)与 `/copy
|
|
31
|
+
- **斜杠命令** —— 输入 `/` 弹出命令选择器(内置 / 扩展 / 模板 / 技能);内置 `/new /model /compact /cwd /thinking /resume`,另有 `/help`(命令清单)与 `/copy`(复制上一条回复)。`/new` 可带首条提示(`/new 修一下失败的测试`),会作为新对话的第一条消息发出去。
|
|
32
32
|
- **每项目多对话并发** —— 每个对话独立 agent runtime,切走后仍在后台运行;「运行的对话」列表显示流式进度,可随时切回。
|
|
33
33
|
- **编辑重问** —— 把任意历史问题 fork 成新分支重新提问,原对话不受影响。
|
|
34
34
|
- 超过 30 条的消息自动折叠为摘要行(惰性渲染,点击展开)。
|
|
35
35
|
- 问题导航 —— 右侧浮动导航条 + 每个问题顶部的序号标签,一键跳转。
|
|
36
36
|
- **提示词模板** —— 空对话状态展示一键模板库(仓库初始化、代码审查、调研、合并冲突……);点卡片即填入输入框,也可把当前草稿存成自己的模板。
|
|
37
|
-
- **模型报错自动重试** ——
|
|
37
|
+
- **模型报错自动重试** —— 按对话可配置重试次数(默认 6,`0` = 失败即停);次数用完则失败轮次标红,红色报错旁有一键「重试」按钮。
|
|
38
|
+
- **排队可控** —— 排队/插队的气泡可以 ✕ 移除,也可以 ↩ **撤回**:文字落回输入框(输入框非空时另起一行追加,绝不覆盖正在打的字)。
|
|
39
|
+
- **消息自带信息** —— 每条消息头部显示角色、产出它的模型和本地 `HH:MM` 时间,每段文本都有复制按钮。附件渲染为独立可折叠卡片(模式徽章 `lines`/`ref`/`bridged`/`inline n lines` + 复制按钮 + 视觉桥「已转写」说明),技能调用渲染为技能卡(展开可见完整 `SKILL.md`),你输入的参数单独成气泡。
|
|
40
|
+
- **压缩看得见** —— 上下文压缩显示为一张卡片(「已压缩 N tokens」),到货时自动展开并跳转;压缩进行中横幅实时计数(「正在压缩 · 12s」)并标明触发原因(手动 / 阈值 / 溢出)。
|
|
41
|
+
|
|
42
|
+
**项目与会话**
|
|
43
|
+
|
|
44
|
+
- **切换项目** —— 工作区根目录(agent 读写的位置、终端启动位置)随时可切,不用重启:
|
|
45
|
+
- **右下角状态栏的路径** —— 点 `📁 <路径>` 打开目录选择器:`Tab` 补全、`↑` 回上级、`💻` 跳到「电脑」根以便换盘符、点文件夹进入后按「选择」,或直接「选择当前目录」;「+ 新建文件夹」可当场建目录,`Esc` 或点击别处关闭。
|
|
46
|
+
- **右侧文件树** —— 在任意文件夹上点右键 →「以项目打开」(同一菜单里还有「上传文件到此文件夹」)。
|
|
47
|
+
- **左栏「最近项目」**,或输入框里 `/cwd <路径>`(只输 `/cwd` 显示当前目录)。
|
|
48
|
+
- 启动默认工作目录来自 `--cwd <dir>` / `PI_WEB_CWD`。
|
|
49
|
+
- **对话并行** —— 每个对话独立 agent runtime,切走后仍在后台流式;每项目最多同时 8 个(子代理不计入)。
|
|
50
|
+
- **「运行的对话」列表** —— 按项目分组(当前项目置顶),子代理缩进挂在父对话下,带子代理 / 报错(悬停看原因)/ 流式徽标;✎ 行内改名;✕ 可选「仅关已结束的子代理」或「强行全关」(运行中会二次确认);右键某行只作用于该对话的子代理子树。
|
|
51
|
+
- **历史会话** —— 读的是 `<agentDir>/sessions/--<cwd>--/`,也就是 pi CLI/TUI 写的同一份转录:浏览器和终端里的 `pi` 共用每个项目的一份列表。支持 ✎ 行内重命名(写入的 `session_info` 与 pi 的 `/name` 同机制)与两步确认删除。
|
|
52
|
+
- **最近项目** —— 本浏览器的记录 ∪ 所有有转录的目录,去掉你删过的(墓碑)和不存在的路径,按最近使用排序(显示 20 条,最多存 30)。
|
|
53
|
+
- **回到现场** —— 重连会恢复上次用的工作目录(并提示落在哪),标签标题可显示当前项目名,每个项目记住自己的「模型 + 该服务商当前密钥」用于**新建**对话(已有内容的对话不被覆盖)。
|
|
54
|
+
- 上次关服时仍在回答的对话,会在下次连接时一次性提示「上次运行被打断」,而不是历史里凭空少一段。
|
|
55
|
+
|
|
56
|
+
**搜索与导航**
|
|
57
|
+
|
|
58
|
+
- **全局搜索(Ctrl/Cmd+K)** —— 一个输入框搜三处:对话转录全文(含助手输出,最多 50 条、每条带跳转锚点)、最近项目、工作区文件名(受限遍历:50 条结果 / 2 万条目 / 4 秒 / 深度 24,触顶时会明确提示而不是卡住)。`↑`/`↓` + `Enter` 打开、`Esc` 关闭;点对话=恢复并跳到命中消息,点项目=切工作区并重搜,点文件=打开预览。
|
|
59
|
+
- **会话内搜索(Ctrl/Cmd+F)** —— 搜当前对话**实际渲染出来的文本**(大小写不敏感,走 CSS Custom Highlight API 高亮),`Enter` 下一个、`Shift+Enter` 上一个、`Esc` 关闭。被折叠的旧消息用消息数据建索引,所以长会话仍可搜——只有你跳到的那一条才会展开。
|
|
60
|
+
- **长会话体验** —— 超过 30 条的消息折叠成摘要行(含思考/工具/bash/图片计数与 90 字预览);问题导航条列出问过的每个问题;上滚后浮出「回到底部」;只有贴底时才自动跟随输出(你主动上滚就不会被拽回);远离视口的消息替换为等高占位。
|
|
38
61
|
|
|
39
62
|
**子代理与模板**
|
|
40
63
|
|
|
@@ -49,19 +72,68 @@
|
|
|
49
72
|
- 免工作区路径附加任意文件 —— 存入全局上传目录,小文件内联,其余以绝对路径引用。
|
|
50
73
|
- 文件预览 —— 行号、点选/拖拽/Shift 选区(可添加到对话为 lines 附件)、GBK 回退解码、二进制十六进制视图、媒体 HTTP 预览(支持 Range)、下载按钮。
|
|
51
74
|
- 实时文件树 —— 服务端对当前列出目录 fs.watch,改动即静默重列;超大目录显示截断提示。
|
|
75
|
+
- **能浏览到工作区之外** —— 文件树可以越过工作区根到 💻「此电脑」层,列出所有盘符(POSIX 下是 `/`);面包屑可直接跳到任意层级,`..` 回上级;目录被删/改名/无权限时降级为空列表 + 提示,而不是报错页。
|
|
76
|
+
- **行内操作** —— 悬停文件:下载 / 内联附件(+)/ 引用附件(🔗)/ 复制名称 / 复制路径;悬停文件夹:引用附件、复制名称、复制路径(纯 HTTP 环境下剪贴板不可用时自动走兜底实现)。
|
|
77
|
+
- **从文件树上传** —— 右键**文件夹行** →「上传文件到此文件夹」(同一菜单里还有「以项目打开」);右键文件行或面板空白 →「上传文件到当前目录」(你正在浏览的那一层)。把系统文件拖到文件夹行上就上传到那一个文件夹(该行高亮),拖到面板则上传到当前目录;拖入的是**文件夹**会明确提示不支持,而不是静默没反应。单文件上限 100MB,空文件会被拒绝,文件名只取 basename 并替换 Windows 非法字符(限 200 字),目标目录不存在会自动创建,上传完成后列表会刷新——哪怕你当时正浏览别的地方。
|
|
78
|
+
- **列表状态的边界** —— Windows/macOS 用工作区根的递归监听,**任何**子目录的改动都会刷新(400ms 防抖);不支持监听的网络盘回退为 10 秒轮询(每个工作区只提示一次);POSIX 隐藏构建噪声(`node_modules`、`.git`、`dist`、`.venv` ……)且最多 500 条,Windows 只隐藏依赖/VCS/数据目录且最多 2000 条,两者被截断时都会明说。
|
|
79
|
+
- **预览也是编辑器** —— 文本文件可直接改并用 Ctrl/Cmd+S 保存(限 2MB、未改动时保存置灰、带未保存改动关闭会先确认);Markdown 可切渲染/原文;HTML 在沙箱 iframe 里渲染,走目录映射 URL 让相对 CSS/图片正常加载(「启用脚本」是逐文件开关,且永不开同源);图片/视频走 HTTP Range 流式;二进制给十六进制视图;文本支持行号、点选/拖拽/Shift 选区(作为 `lines` 附件添加)、50–200% 缩放、自动换行开关与全屏。
|
|
80
|
+
- **下载不跟安全浏览打架** —— 先取字节再用浏览器的保存框存档(不支持时回退 blob 链接,超 200MB 走原生下载),Windows 非法文件名自动改写,取消保存框不算错误。
|
|
52
81
|
|
|
53
82
|
**终端与 Git**
|
|
54
83
|
|
|
55
|
-
- 内置终端(xterm.js + node-pty),每客户端独立 PTY 管理;Windows
|
|
56
|
-
-
|
|
84
|
+
- 内置终端(xterm.js + node-pty),每客户端独立 PTY 管理;Windows 优先 Git Bash,回退到随包下载的 busybox,再回退 `cmd`。同时最多 16 个在线终端(agent 自己开的不计入),每个标签切换时保留自己的 8000 行回滚;标签可行内重命名、可关闭(真杀进程),退出码会写进回滚。
|
|
85
|
+
- **常用命令列表** —— 终端侧栏上半是当前项目的 `.pi/commands.json` 列表(`name` + `command` + `cwd`,`${pwd}` 展开为工作区):点一行即运行(同名标签复用并重启,类 VSCode task),可增删改,也能从磁盘重读文件。
|
|
86
|
+
- **AI bash 分组** —— agent 通过终端接管开的终端收进可折叠的「AI bash」分组,不会淹没你自己的标签。
|
|
87
|
+
- **终端接管 bash**(设置 → 工具,默认关)—— 开启后 agent 的 `bash` 工具在可见的常驻终端里跑,而不是隐藏进程,因此 `cd`/venv/ssh 等 shell 状态能跨调用保留;静默阈值(默认 15 秒,`0` = 一直等)决定何时把安静的命令转后台,`head`/`tail` 控制模型要读的行数。
|
|
88
|
+
- **活力检测** —— agent 用过的终端在对话仍流式时长时间没输出,服务端会把尾部输出作为 steer 推给 AI(「去读/回答/关掉它」),而不是干等。
|
|
89
|
+
- **源代码管理(Git)面板** —— 经隐藏查询终端展示 status / branch / diff / 历史 / 未跟踪文件,另有逐文件暂存(+)与取消暂存(-)、提交框(Enter 提交、兼容输入法)与「全部提交」(`git add -A && git commit`)、本地/远程跟踪分支分组的切换器(选远程分支会在本地建跟踪分支)、分离 HEAD 与 `↑领先 ↓落后` 徽标。「提交树」页加载 `git log --graph` 并可看每个提交的完整 diff。写操作(提交 / 切分支 / 推送 / 拉取)都在可见终端里跑并自动切到终端视图;仓库真实 git 目录变动时(含 worktree)以及 30 秒轮询兜底都会自动刷新,因此在浏览器之外提交也能自己出现在面板里。
|
|
57
90
|
|
|
58
91
|
**模型与设置**
|
|
59
92
|
|
|
60
93
|
- 模型管理 —— UI 里编辑 models.json、按 provider 设置 API key(密钥/headers 永不下发浏览器)。
|
|
94
|
+
- **模型选择器** —— 可按名称/provider/id 搜索,多个服务商时左侧有服务商栏;你常用的模型自动置顶并标「用过 N 次」,另带推理/视觉徽标;打开时滚到当前模型,底栏固定「刷新模型 / 管理模型」。
|
|
95
|
+
- **一个服务商多把密钥** —— 内置服务商可存多个命名密钥(`<agentDir>/provider-keys.json`):加第二把不会丢掉第一把,可按名激活/删除(删掉当前活跃的会自动提升下一把)。选择器按密钥分组列出,点某把密钥下的模型即切过去;浏览器只拿到密钥昵称。
|
|
96
|
+
- **自定义服务商** —— 可增删改(API 类型、`baseUrl`、密钥、可选鉴权头)并逐模型配置上下文窗口/最大输出/文本或图片/推理;**抓取模型**在**服务端**探测 `/models`(所以局域网/回环地址不受 CORS 限制)并合并结果,已保存的服务商也能重新探测。手改过的 `models.json` 用「重新加载 models.json」拉进来(允许注释,与 SDK 一致)。
|
|
97
|
+
- 思考强度(thinking level)按模型切换 —— 共七档,模型不支持的档位置灰,而不是静默换到别的档。
|
|
98
|
+
- 首次配置引导 —— 没装 pi CLI 时可直接一键安装(失败有详情、可重试/跳过),然后选服务商 + 填密钥就能开用。
|
|
99
|
+
- 设置面板:
|
|
100
|
+
- **系统提示词** —— 11 个来源(soul / tools / guidelines / pi 文档 / append / persona / terminal / markers / context / skills / cwd)拼成的 `{{token}}` 组合模板,点 token 芯片即可追加;每个来源可单独覆盖(`auto` 徽标、「以默认为底改写」、单独恢复默认,环境类来源保持只读);另有两个查看器分别展示**实际生效的完整提示词**与**真正发给模型的工具 schema**。
|
|
101
|
+
- **输入历史与快捷短语** —— 历史有上限(1–500 条,可选单条字数上限,两步确认清空),用 `↑`/`↓` 翻;输入框上方的快捷短语可逐条编辑/上下移/删除/恢复默认。
|
|
102
|
+
- **技能** —— 逐个启停,另有「全文」芯片把整个 `SKILL.md` 注入提示词(单文件 8KB、总量 32KB)。
|
|
103
|
+
- **扩展** —— 逐个启停,`npm:` 装的可在可见终端里一键卸载(`pi remove npm:<包名>`)。
|
|
104
|
+
- **界面插件 / 目标审查 / 视觉桥 / 子代理模板** 各有自己的页,见 [界面插件](#界面插件)。
|
|
105
|
+
- **预设** —— 把当前组合(提示词模板/模式/覆盖、技能与扩展开关、工具开关、终端接管、重试次数、审查提示词、技能全文名单)存成命名预设,随时应用或删除;有意**不**包含(问卷、目标模式、显示偏好、视觉桥、默认子代理模型、快捷短语),应用预设后它们保持原值。
|
|
106
|
+
- **生效时机** —— 工具开关、重试次数、显示偏好、标记与技能全文名单即时生效;提示词模板/覆盖与技能扩展开关需重载会话,回答中改的会延后到「本回复结束后生效」(有提示)。
|
|
107
|
+
- **显示偏好** —— 思考块默认展开或折叠、工具卡默认展开、宽屏聊天列(宽屏下取消 860px 上限)、标签标题显示项目名、聊天壁纸(地址或上传,带压暗/模糊滑杆)。
|
|
61
108
|
- 主题切换 —— 顶栏选择主题;主题是纯 `:root` 调色板覆盖(布局唯一在 styles.css)。如何添加自定义主题或向仓库贡献主题,见 [主题](#主题)。
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
109
|
+
|
|
110
|
+
**代理工具与内联标记**
|
|
111
|
+
|
|
112
|
+
- **工具开关** —— 设置 →「工具」把所有可选工具逐个列出:7 个终端工具(默认**关**)、7 个 `subagent_*` 工具(默认开)、`edit_soft`(默认关)、`delegate_task`/`ask_user_question`/`todo_list`(默认开)。开关即时生效、不重启,工具只是被禁用仍保留注册以便随时开回;`bash` 与 SDK 自带的 `edit`/`read` 有意不可关。
|
|
113
|
+
- **内联标记** —— 状态改变不需要工具往返,AI 直接把标记写进回复:任务列表用 `[[todo:new:<主题>]]` / `[[todo:set:<id>,in_progress]]` / `[[todo:remove:<id>]]` / `[[todo:dep:<id>,blocks=<id>]]`,不打断的提醒用 `[[notify:<级别>:<内容>]]`,改对话标题用 `[[conv:rename:<标题>]]`。气泡定稿即执行,标记写错会以浏览器提示回显;任务列表同时以常驻 widget 显示在右栏文件树下方(`N/M done` + ✓/◐/○),跟随当前对话,且因为存在该对话自己的会话分支里,刷新后仍在。设置 →「工具」另有总开关与逐标记开关(这两项全局共享)。
|
|
114
|
+
- **`edit_soft`** —— 更宽松的 `edit`(默认关):缩进/空白导致内置工具失败时用它,先精确子串、再按去空白逐行核心匹配,`newText` 原样写入并保留文件换行符/BOM,结果带 diff 与 unified patch。
|
|
115
|
+
- **`delegate_task`** —— 强制六段派单(TASK / EXPECTED OUTCOME / REQUIRED TOOLS / MUST DO / MUST NOT DO / CONTEXT)并在服务端校验:模板不可用、任务少于 20 字或任一段为空都会被打回,并把可用模板清单回给模型。卡片按六段结构化展示,跑完后可一键跳到对应子代理对话。
|
|
116
|
+
- **`ask_user_question`** —— pi 引擎本身没有问卷工具,这是 pi-web-ui 加的:模型可以问结构化问题(单选/多选 + 富文本选项预览 + 自由文本),以对话框弹出;回答作为工具结果回给模型,取消则以工具错误返回,等你回答的时间不受工具看门狗限制,未答的问卷刷新/重连后会恢复。
|
|
117
|
+
- **MCP 服务器** —— 放一份 `<dataDir>/mcp.json`(`{"servers":{"github":{"command":"node","args":["mcp.js"],"cwd":"/x"}}}`),该 stdio MCP 服务器声明的工具就会作为普通工具交给 AI(服务端执行);某一个起不来只记一行日志,不影响其他。文件在启动时读取,改完需重启 pi-web-ui。
|
|
118
|
+
- **扩展 UI 桥** —— pi 扩展可以驱动浏览器:`setWidget` 在文件树下方渲染实时面板(点标题居中放大),`setStatus` 在底栏显示状态文本,`notify` 弹通知,`select`/`confirm`/`input` 在输入框上方弹出非模态请求面板(选项走 Markdown 渲染,`Esc` 当作取消);widget 文本里的 ANSI 色码会被剥掉,不会把扩展底栏变成转义序列噪声。
|
|
119
|
+
- **插件能力** —— 插件可注册 `/命令`(选择器标 plugin 来源、服务端执行不耗 token)、注册带停止按钮的后台任务、声明设置表单、订阅运行/工具/对话事件,并在前端经 `window.__piWebUiHost` 切视图、新建对话。详见 [界面插件](#界面插件)。
|
|
120
|
+
|
|
121
|
+
**声音与通知**
|
|
122
|
+
|
|
123
|
+
- **声音提醒** —— 总开关 + 四个事件各自开关(提问 / 完成 / 开始 / 报错),每个都带试听按钮,另有音量滑杆(0–100%)。
|
|
124
|
+
- **桌面 / 系统通知** —— 默认关;开启时在点击处申请浏览器权限,被拒则自动关回(并记住)。通知经 Service Worker 发出,所以装成 PWA 后后台也能收到,覆盖「完成 / 提问 / 报错」,点通知会把应用窗口拉到前台。你明显正在看页面时不会打扰——包括 Windows 上浏览器仍声称有焦点/可见但窗口已最小化的情况(改用原生窗口矩形判定)。
|
|
125
|
+
|
|
126
|
+
**PWA 与离线**
|
|
127
|
+
|
|
128
|
+
- **可安装** —— 带 web app manifest(独立窗口、192/512/1024 + maskable 图标、`./` 相对路径所以子路径部署也能装),Chrome/Edge 的「安装应用」或手机「添加到主屏幕」即可得到独立窗口与图标。
|
|
129
|
+
- **离线应用壳** —— Service Worker 对导航请求走网络优先 + 缓存外壳兜底(后端宕机/重启时页面仍能打开),哈希静态资源缓存优先,而 `/ws`、`/api`、`/themes`、`/plugins` 永不缓存;新 worker 会立即接管已打开的页面。
|
|
130
|
+
- **该提醒时才提醒** —— 页面加载的构建与服务端 wire 协议不一致时(比如刚更新完)会固定显示刷新提示条;标签标题可显示当前项目文件夹。
|
|
131
|
+
|
|
132
|
+
**语言与语言包**
|
|
133
|
+
|
|
134
|
+
- 顶栏语言菜单按母语名列出所有语言,「获取更多语言」打开的管理器列出 8 个可下载语言包的版本与下载/移除按钮(另有刷新);语言包落在 `<dataDir>/locales/`,所以也能手工放进去做完全离线的安装。
|
|
135
|
+
- 首次访问没有存过选择时,按浏览器语言 → 实例默认(`PI_WEB_LOCALE`)→ 英文的顺序决定;一旦你选过就一直跟着。
|
|
136
|
+
- 服务端面向模型/工具的文案也跟随同一语言(工具返回值、提示词段落、提醒),所以中文界面下 `subagent_list` 这类工具也返回中文。
|
|
65
137
|
|
|
66
138
|
**目标(Goal)模式**
|
|
67
139
|
|
|
@@ -78,26 +150,65 @@
|
|
|
78
150
|
- **提问对话框** —— 模型 `ask_user_question` 弹出浏览器对话框(单选/多选 + 自由文本),支持排队与倒计时。
|
|
79
151
|
- **工具 & MCP 桥** —— 插件 AI 工具与外部 MCP 服务器(`mcp.json`)都桥进 DSH 运行时,DSH 模型可直接调用(服务端执行)。
|
|
80
152
|
- **技能启停** —— 设置面板暴露 DSH 技能目录;禁用即运行时过滤该技能,模型不可见。
|
|
81
|
-
- **DSH 用户补丁** —— 在 `<dataDir>/dsh-patches/` 放 `.yml` Cordis
|
|
153
|
+
- **DSH 用户补丁** —— 在 `<dataDir>/dsh-patches/` 放 `.yml` Cordis 补丁扩展运行时,设置面板一键重扫生效;设置 →「插件」会列出补丁文件(大小/时间)与解析后的目录路径,坏文件跳过并把错误打到运行时的 stderr。
|
|
154
|
+
|
|
155
|
+
**DSH 与 pi 引擎的差异**(切换前值得知道):
|
|
156
|
+
|
|
157
|
+
- 思考强度固定 `high`,改档会回答「DeepSeek V4 只支持高思考强度」。
|
|
158
|
+
- 底栏的 token/成本/上下文按 DeepSeek 官方每百万定价与 100 万窗口计算。
|
|
159
|
+
- 打开历史会话是只读回放:一发消息就会开**新分支**并把旧对话作为上下文注入(运行时不允许原地续聊);编辑重问同理。
|
|
160
|
+
- **停止**会杀掉运行时进程树,所以所有进行中的 DSH 对话都会停(有提示),半成品目标会先清除;不支持只中止 bash 工具。
|
|
161
|
+
- 会话存在 `<dataDir>/dsh-sessions/`(与 pi 引擎的转录隔离),超过 `PI_WEB_DSH_SESSION_RETENTION_DAYS`(90)天自动清理;每项目最多同时 8 个对话;运行时崩溃按 1s/3s/9s 退避重启,60 秒内最多 2 次,超限就停下并提示你去查 API key 与 DSH 依赖。
|
|
162
|
+
- 工具跑在 `workspace-write` 沙箱里、审批为 never——你的「停止」按钮就是控制阀。问卷是逐题向导(带选项预览与倒计时)。
|
|
163
|
+
- pi 专属能力(会话重命名、`/compact`、`/reload`、扩展热重载、子代理模板、自定义服务商/多密钥、服务商模型探测、装 pi CLI、视觉桥、逐工具开关)都会给明确提示并被隐藏入口,而不是静默失败。
|
|
82
164
|
|
|
83
165
|
**后台任务**
|
|
84
166
|
|
|
85
|
-
- 后台任务面板 ——
|
|
86
|
-
-
|
|
167
|
+
- 后台任务面板 —— 在 bash 前后对比监听端口,检测 agent 启动的服务并列出端口 / pid / 名称 / 命令行(点命令行可展开全文);可单独停止或全部关闭,顶栏按钮带实时数量徽标。
|
|
168
|
+
- 列表属于**浏览器客户端**而非对话:切项目、切对话、重连都不丢,服务端每 30 秒刷新一次并剔除已退出的进程。检测会排除已知桌面软件,以及父链回溯到 `explorer` 而不是服务进程的进程(所以你自己开的浏览器不会被当成「AI 启动的服务」)。
|
|
169
|
+
- 插件注册的任务带 🧩 标记与实时状态文本,走插件自己的停止回调(比如邮件轮询任务)。
|
|
170
|
+
- 工具看门狗 —— 单个工具调用超过 20 分钟自动中断会话(`PI_WEB_TOOL_TIMEOUT_MS`,问卷豁免)。
|
|
87
171
|
- **只停止 bash 命令** —— 中止运行中的 bash 工具而不打断对话。
|
|
172
|
+
- **失联警告** —— 流式运行完全没事件超过 3 分钟(`PI_WEB_STALL_NOTIFY_MS`,`0` = 关)会指名对话地提醒一句,但不自动中止。
|
|
88
173
|
|
|
89
174
|
**安全与运维**
|
|
90
175
|
|
|
91
176
|
- 默认只绑 loopback;局域网 / 容器需显式 `PI_WEB_HOST=0.0.0.0`。
|
|
92
|
-
-
|
|
93
|
-
-
|
|
94
|
-
-
|
|
95
|
-
-
|
|
177
|
+
- **口令鉴权** —— `PI_WEB_TOKEN` 可经 `Authorization: Bearer …`、`X-PI-Token: …`、`?token=…` 或 `pi_web_token` cookie 任一通过;`?token=` 链接一次登录、从地址栏抹除并把口令写入 cookie,每次授权请求都会刷新它,失效 cookie 在 401 时立即过期——所以改完口令后一次正确的 `?token=` 进入就永久恢复。`/api/health` 保持开放给探针。
|
|
178
|
+
- WebSocket Origin/Host 同权威校验 —— 跨源页面直接拒绝(403),`Origin: null`(`file://` 页面)一律拒绝,设了口令时凭据不对会在升级前就被 401;反代场景用 `PI_WEB_ALLOW_ORIGINS` 白名单。
|
|
179
|
+
- **Host 白名单** —— `PI_WEB_ALLOW_HOSTS=host1,host2` 在始终生效的同权威校验之上再加一层严格主机名白名单。
|
|
180
|
+
- **实例收窄** —— `PI_WEB_TABS=chat,terminal,git` 只开放这些标签页,未列出的标签页在**服务端**也会被拒绝(对应消息返回说明),`chat` 永不可关。`PI_WEB_MANAGED=1` 声明实例由外部部署管理:自更新、装 pi CLI、插件市场安装都会被服务端拒绝并给出原因,前端也隐藏这些入口(版本按钮变成纯标签)。
|
|
181
|
+
- **文件边界** —— 工作区相对路径的读写一律做 `..` 逃逸校验(工作区外的路径只能经显式绝对路径/机器浏览到达);`/api/file` 内联只放行图片/视频/HTML,二进制不可能被 `<img>` 带走——其他类型必须走 `?download=1`(附件下载)。HTML 预览路由一律以 sandbox 下发。
|
|
182
|
+
- 本地控制 socket 提供 `server status|quiesce|unquiesce`(排空模式:拒绝新 prompt/编辑重问/会话恢复,DSH 下还会拒绝新客户端连接,存量跑完)。
|
|
183
|
+
- 凭据不下发浏览器 —— provider headers(可能含 Authorization)永不发送到前端,服务商 API key 只以昵称形式到达浏览器。
|
|
184
|
+
- 9 种界面语言(中英内置 + 8 个可下载语言包:德/西/法/意/日/韩/葡/俄),语言包可在顶栏菜单里装/卸(见上方「语言与语言包」)。
|
|
185
|
+
- **保留期** —— `uploads/` 里超过 `PI_WEB_UPLOAD_RETENTION_DAYS`(14 天,`0` = 不清理)的文件会在启动时与之后每 6 小时清理一次;DSH 会话有自己 90 天的清理。
|
|
186
|
+
- **运维看门狗**(工具超时、模型失联、终端活力)都可调,见 [环境变量调优](#环境变量调优)。
|
|
96
187
|
|
|
97
188
|
**部署与更新**
|
|
98
189
|
|
|
99
|
-
- 前台运行 / 全局 npm 安装 / Docker
|
|
100
|
-
-
|
|
190
|
+
- 前台运行 / 全局 npm 安装 / Docker(见 [Docker](#docker))/ macOS launchd / Linux systemd / Windows 登录自启(HKCU `Run` 键 + 无控制台启动器 + 崩溃看门狗)/ 桌面快捷方式(`server shortcut`)。
|
|
191
|
+
- `server install --print` 只打印将要写入的 launchd plist / systemd unit / Windows 启动器就退出,可在真正安装前先审阅。
|
|
192
|
+
- **更新面板** —— 版本按钮在有新版本时显示黄点,另有「N 个更新」徽标;「检查全部更新」会比对本体、全局安装的 pi 核心与 `<agentDir>/npm/package.json` 里声明的直接依赖,每行都能单独更新,另有「全部更新」与「重新检查」;命令在可见终端里跑(pi 扩展走 `pi update npm:<名字>`,这是唯一能更新 pi 真正加载的那份的命令;其余走 `npm i -g <名字>@latest`)。刚发布不足 30 分钟会提醒 npm 缓存元数据可能还没同步。被 launchd/systemd/Windows 看门狗托管的实例多一个「重启服务」按钮;前台运行的实例没有,因为没有东西会把它拉回来。
|
|
193
|
+
- **命令行更新插件** —— `pi-web-ui plugins --check-updates` 逐个对比插件记录的提交与远端 HEAD 并给出确切更新命令;每次 `install --force` 都会把旧版本快照到 `<dataDir>/plugin-backups/`(只留最近 3 份,拷贝失败自动回滚),所以 `pi-web-ui plugins --rollback <id>` 可以退回上一版。
|
|
194
|
+
- pi CLI 里还有 `/webui`(来自随包的 `extensions/webui.ts`):`/webui` 从 8787 起挑第一个空闲端口拉起服务,`--port 9000`、`--cwd <路径>`、`--no-browser`、`status`、`stop` 分别控制它;每个 pi 会话一个子进程,会话关闭时回收,不留孤儿进程。
|
|
195
|
+
|
|
196
|
+
## 快捷键
|
|
197
|
+
|
|
198
|
+
| 按键 | 作用 |
|
|
199
|
+
| --- | --- |
|
|
200
|
+
| `Enter` | 发送。触屏设备上 `Enter` 改为换行,`Ctrl/Cmd+Enter` 才发送(Windows 触屏笔记本当作桌面)。 |
|
|
201
|
+
| `Shift+Enter` | 输入框内换行。 |
|
|
202
|
+
| `↑` / `↓` | 光标在首/末行时翻全局输入历史(跨对话持久化);`Esc` 回到草稿。 |
|
|
203
|
+
| `Ctrl/Cmd+K` | 全局搜索(对话 / 项目 / 工作区文件名)。 |
|
|
204
|
+
| `Ctrl/Cmd+F` | 搜当前对话 —— `Enter` 下一个命中,`Shift+Enter` 上一个,`Esc` 关闭。 |
|
|
205
|
+
| `/` | 打开斜杠命令选择器(`↑`/`↓` 选择、`Tab` 或 `Enter` 补全、`Esc` 关闭;输入空格则自动关闭)。 |
|
|
206
|
+
| `Ctrl/Cmd+S` | 预览里编辑文件时保存。 |
|
|
207
|
+
| `Ctrl/Cmd+A` | 预览里全选行(光标不在文本框时)。 |
|
|
208
|
+
| `Ctrl/Cmd+Enter` | 提交「编辑重问」编辑器。 |
|
|
209
|
+
| `Ctrl/Cmd+C` / `Ctrl/Cmd+V` | 终端里:有选中则复制(无选中时 `^C` 仍发给 shell)/ 原生粘贴。 |
|
|
210
|
+
| `Esc` | 关闭预览、对话框、命令选择器、问卷或扩展请求面板(预览有未保存改动时会先问)。 |
|
|
211
|
+
| 拖放 | 窗口任意位置拖入文件 = 附件到对话;拖到文件树 = 上传到那一个目录;不支持拖文件夹(展开后选文件)。 |
|
|
101
212
|
|
|
102
213
|
## 界面截图
|
|
103
214
|
|
|
@@ -190,10 +301,11 @@ pi-web-ui # 前台,http://localhost:
|
|
|
190
301
|
| --- | --- | --- | --- |
|
|
191
302
|
| `--port <n>` | `PI_WEB_PORT` | `8787` | HTTP 端口 |
|
|
192
303
|
| `--cwd <dir>` | `PI_WEB_CWD` | 当前目录 | 工作区根(读/写/终端) |
|
|
193
|
-
| `--data-dir <dir>` | `PI_WEB_DATA_DIR` | `~/.pi-web` |
|
|
304
|
+
| `--data-dir <dir>` | `PI_WEB_DATA_DIR` | `~/.pi-web` | 数据目录(界面状态/插件/上传/主题/语言包) |
|
|
194
305
|
| `--engine <pi\|dsh>` | `PI_WEB_ENGINE` | `pi` | 智能体引擎;`--engine dsh` = DeepSeek Harness |
|
|
195
306
|
| `--host <addr>` | `PI_WEB_HOST` | `127.0.0.1` | 监听地址(`0.0.0.0` 供局域网/Docker) |
|
|
196
|
-
| `--agent-dir <dir>` | `PI_CODING_AGENT_DIR` | `~/.pi/agent` | pi 配置目录(auth.json
|
|
307
|
+
| `--agent-dir <dir>` | `PI_CODING_AGENT_DIR` | `~/.pi/agent` | pi 配置目录(auth.json、models.json、会话、技能) |
|
|
308
|
+
| `--no-browser` | — | 关 | 启动但不自动打开浏览器 |
|
|
197
309
|
| _仅环境变量_ | `PI_WEB_TOKEN` | 空 | 可选共享鉴权口令 |
|
|
198
310
|
| _仅环境变量_ | `PI_WEB_DSH_*` | — | dsh 运行时、补丁与调试设置 |
|
|
199
311
|
|
|
@@ -210,7 +322,7 @@ PI_WEB_ENGINE=dsh PI_WEB_PORT=9000 PI_WEB_CWD=/path/to/project pi-web-ui
|
|
|
210
322
|
## 停止
|
|
211
323
|
|
|
212
324
|
- **前台**:在运行它的终端里按 `Ctrl+C`。
|
|
213
|
-
- **作为服务**:`pi-web-ui server stop
|
|
325
|
+
- **作为服务**:`pi-web-ui server stop`。**Linux 和 Windows** 上会保留开机自启(下次登录/开机会回来,直到 `server uninstall`);**macOS** 上 `stop` 会卸载 launchd 代理,因此不再登录自启——用 `pi-web-ui server start` 恢复。
|
|
214
326
|
|
|
215
327
|
## 更新
|
|
216
328
|
|
|
@@ -225,8 +337,7 @@ pi-web-ui server restart # 重启服务使新版本生效(前台运行则
|
|
|
225
337
|
npm uninstall -g pi-web-ui
|
|
226
338
|
```
|
|
227
339
|
|
|
228
|
-
|
|
229
|
-
卸载/升级后依然保留。
|
|
340
|
+
卸载**不会**删除你的聊天记录:历史面板里的转录存在 `<agentDir>/sessions/`(默认 `~/.pi/agent/sessions/`,按项目分子目录),其余状态(界面设置、最近项目、插件、上传、主题、语言包)存在 `<dataDir>`(默认 `~/.pi-web/`)。两者都能跨卸载/升级/重装保留,之后重跑 `pi-web-ui server install` 会重新读到(若要删除它们,先备份 `sessions/` 与 `plugins/` —— 卸载本身永不动这两处)。
|
|
230
341
|
|
|
231
342
|
## 作为系统服务(开机自启)
|
|
232
343
|
|
|
@@ -247,10 +358,11 @@ pi-web-ui server unquiesce # 解除排空,恢复接收新工
|
|
|
247
358
|
|
|
248
359
|
- **macOS** → launchd 代理(无需 sudo),日志 `/tmp/pi-web-ui.log` / `.err`
|
|
249
360
|
- **Linux** → systemd unit(`systemctl enable --now`),日志 `journalctl -u pi-web-ui -f`
|
|
250
|
-
- **Windows** →
|
|
361
|
+
- **Windows** → 登录自启 Run 键(HKCU,无需管理员)+ wscript 无窗口启动器 + 10 秒崩溃看门狗(PID 写在 `%APPDATA%\pi-web-ui\`)
|
|
251
362
|
|
|
252
|
-
选项:`--port`(默认 8787)、`--cwd`(工作目录)、`--data-dir
|
|
253
|
-
`--engine <pi|dsh>`、`--host`、`--agent-dir`、`--name
|
|
363
|
+
选项:`--port`(默认 8787)、`--cwd`(工作目录)、`--data-dir`(数据目录)、
|
|
364
|
+
`--engine <pi|dsh>`、`--host`、`--agent-dir`、`--name`(自定义服务名)、
|
|
365
|
+
`--print`(只打印将生成的配置,不安装)。重复执行 `server install`
|
|
254
366
|
并传入新选项即可重新生成配置并重启服务 —— 这就是修改已装服务端口/工作目录/引擎的方式。
|
|
255
367
|
`--engine` / `--host` / `--agent-dir` 会自动烘焙进服务;仅环境变量的(`PI_WEB_TOKEN`、
|
|
256
368
|
`PI_WEB_DSH_*`)需手动写进服务配置。见上方「启动参数 & 环境变量」表。
|
|
@@ -259,6 +371,32 @@ pi-web-ui server unquiesce # 解除排空,恢复接收新工
|
|
|
259
371
|
pi-web-ui server install --engine dsh --port 9000 --cwd /path/to/project
|
|
260
372
|
```
|
|
261
373
|
|
|
374
|
+
## Docker
|
|
375
|
+
|
|
376
|
+
镜像会构建前后端、保留 `node-pty` 需要的编译工具链、预装 DSH 运行时(所以 `PI_WEB_ENGINE=dsh` 无需额外步骤)、以非 root 的 `node` 用户运行,并声明 `/app/.pi-web` 为数据卷:
|
|
377
|
+
|
|
378
|
+
```bash
|
|
379
|
+
docker compose up -d # 然后打开 http://localhost:8787
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
`docker-compose.yml` 已设好容器必需的 `PI_WEB_HOST=0.0.0.0`(端口映射的前提),并用命名卷 `pi-web-data` 持久化数据目录。文件里注释掉的块覆盖常见容器调整 —— 切 DSH 引擎、挂 `dsh-patches` 目录、把项目挂为 `PI_WEB_CWD`、把 `~/.pi/agent` 只读挂为 `PI_CODING_AGENT_DIR`(让容器能看到你的 API key 与模型配置):
|
|
383
|
+
|
|
384
|
+
```yaml
|
|
385
|
+
services:
|
|
386
|
+
pi-web-ui:
|
|
387
|
+
build: .
|
|
388
|
+
ports: ["8787:8787"]
|
|
389
|
+
environment:
|
|
390
|
+
PI_WEB_HOST: 0.0.0.0
|
|
391
|
+
# PI_WEB_ENGINE: dsh
|
|
392
|
+
volumes:
|
|
393
|
+
- pi-web-data:/app/.pi-web
|
|
394
|
+
# - ./my-project:/workspace:ro
|
|
395
|
+
# - ~/.pi/agent:/root/.pi/agent:ro
|
|
396
|
+
volumes:
|
|
397
|
+
pi-web-data:
|
|
398
|
+
```
|
|
399
|
+
|
|
262
400
|
## 界面插件
|
|
263
401
|
|
|
264
402
|
插件是可选的界面组件(顶栏多出一个 tab,背后是插件自己的视图,可带服务端入口和 AI 工具)。
|
|
@@ -277,9 +415,12 @@ pi-web-ui server install --engine dsh --port 9000 --cwd /path/to/project
|
|
|
277
415
|
| 📝 [编辑器 + 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`。 |
|
|
278
416
|
| 📊 [图表 mermaid](https://github.com/xing-shuyin/pi-web-ui/tree/main/plugins/mermaid) | 把对话里的 ` ```mermaid ` 围栏渲染成 SVG 图表(fenced-code 渲染插件,本地引擎离线优先)。 |
|
|
279
417
|
| 🧭 [运行轨迹 run-trace](https://github.com/xing-shuyin/pi-web-ui/tree/main/plugins/run-trace) | 运行轨迹:任务 → 思考 → 工具 → 文件改动 → 结果的时间线聚合视图,支持回放与节点详情。 |
|
|
418
|
+
| 📖 [阅读 legado-web](https://github.com/xing-shuyin/pi-web-ui/tree/main/plugins/legado-web) | Legado 阅读(文本源):基于兼容安卓书源的搜书 / 发现 / 详情 / 目录 / 正文阅读,支持书源导入、检测与删废源,并提供四个修源 AI 工具(`legado_rules`、`legado_book_sources`、`legado_source_probe`、`legado_run_rule`)与「🤖 AI 修复源」按钮(带失败现场直接开新对话)。书源/书架/进度存在 `<dataDir>/legado-web/`。 |
|
|
280
419
|
|
|
281
420
|
`plugins/demo-mailbox` 作为最小插件模板保留在仓库里(服务端入口 + 客户端视图 + 双向消息协议),兼作测试夹具——想自己写插件从这里入手。
|
|
282
421
|
|
|
422
|
+
也可以直接在界面里装:**设置 → 界面插件 → 插件市场**列出可维护插件(同一套,随包在 `plugins/catalog.json`)并提供**安装 / 更新 / 卸载**(更新保留 `config.json`),还能用「添加插件」把任何第三方插件(填 `owner/repo` 或 `owner/repo/子目录`)加进列表——你加的条目存在 `<dataDir>/plugin-catalog.json`。插件作者想让插件进内置列表,往 `plugins/catalog.json` 提一行 PR 即可。
|
|
423
|
+
|
|
283
424
|
安装示例(网页邮箱):
|
|
284
425
|
|
|
285
426
|
```bash
|
|
@@ -324,6 +465,7 @@ pi-web-ui install https://github.com/xing-shuyin/pi-web-ui/tree/main/plugins/web
|
|
|
324
465
|
- 存放在插件目录**其他位置**的本地数据不在保留范围内(如 db-client 的
|
|
325
466
|
`db-connections.json`、vscode-editor 的 `ssh-hosts.json`)——强制重装前请先备份。
|
|
326
467
|
- 更新后刷新浏览器即可生效,无需重启服务。
|
|
468
|
+
- 想知道哪些插件过时了:`pi-web-ui plugins --check-updates` 逐个对比记录的提交与远端 HEAD;退回上一版用 `pi-web-ui plugins --rollback <id>`(每次 `--force` 升级前都会自动备份旧目录,只留最近 3 份)。
|
|
327
469
|
|
|
328
470
|
### 生效方式
|
|
329
471
|
|
|
@@ -384,6 +526,30 @@ pi-web-ui uninstall <id> # 卸载插件
|
|
|
384
526
|
|
|
385
527
|
合并主题的规则:必须是单一 CSS 文件、设置 `--term-*` 变量保证终端可读、浅色主题覆盖 `.hljs` 语法高亮色以保证代码可读。
|
|
386
528
|
|
|
529
|
+
|
|
530
|
+
## 环境变量调优
|
|
531
|
+
|
|
532
|
+
以下全部可选——默认值就是开发时一直在用的配置。完整参考:[`docs/env-vars.md`](docs/env-vars.md)。
|
|
533
|
+
|
|
534
|
+
| 变量 | 默认 | 作用 |
|
|
535
|
+
| --- | --- | --- |
|
|
536
|
+
| `PI_WEB_TOOL_TIMEOUT_MS` | `1200000`(20 分) | 单工具调用看门狗;超时自动中止(`ask_user_question` 豁免)。 |
|
|
537
|
+
| `PI_WEB_STALL_NOTIFY_MS` | `180000`(3 分) | 流式运行完全没事件时给警告(不中止);`0` = 关。 |
|
|
538
|
+
| `PI_WEB_TERMINAL_IDLE_MS` | `15000` | AI 开过的终端静默这么久就催它去看一眼;`0` = 关。 |
|
|
539
|
+
| `PI_WEB_TERMINAL_IDLE_LINES` | `10` | 该催命消息回送的终端尾部行数(1–500)。 |
|
|
540
|
+
| `PI_WEB_INLINE_FILE_MAX` | `12288`(12KB) | 小于它且无工作区归属的上传文件被内联而不是只给路径。 |
|
|
541
|
+
| `PI_WEB_VISION_TIMEOUT_MS` | `90000` | 视觉桥整批转写的超时。 |
|
|
542
|
+
| `PI_WEB_UPLOAD_RETENTION_DAYS` | `14` | `<dataDir>/uploads/` 保留天数;`0` = 不清理。 |
|
|
543
|
+
| `PI_WEB_SHELL` | 自动 | 仅 Windows:node-pty 用哪个 shell(自动顺序:`PI_WEB_SHELL` → `$SHELL` → Git Bash → 随包 busybox → `%COMSPEC%` → PowerShell)。 |
|
|
544
|
+
| `PI_WEB_TABS` | 全部标签页 | 逗号分隔的标签页白名单(`chat,terminal,git,search,tasks,settings,plugins`);未列入的在服务端也被拒绝,`chat` 不可关。 |
|
|
545
|
+
| `PI_WEB_MANAGED` | 关 | `1`/`true` 声明实例由外部部署管理:自更新、装 pi CLI、装插件都被拒绝并说明原因,前端也隐藏入口。 |
|
|
546
|
+
| `PI_WEB_ALLOW_HOSTS` | 空 | 严格的主机名白名单(叠加在始终生效的同权威校验之上)。 |
|
|
547
|
+
| `PI_WEB_LOCALE` | 空 | 首访回退语言(显式选择与浏览器语言优先级更高)。 |
|
|
548
|
+
| `PI_WEB_LOCALE_BASE_URL` | GitHub raw | 语言包下载根 —— 指向镜像即可做离线/内网安装。 |
|
|
549
|
+
| `PI_WEB_PKG_ROOT` | 自动 | 显式指定包根目录(非标准安装位置时用)。 |
|
|
550
|
+
| `PI_CODING_AGENT_SESSION_DIR` | 空 | 让 pi 把转录扁平写入该目录(而非 `<agentDir>/sessions/--<cwd>--/`,会改变历史列表读到的内容)。 |
|
|
551
|
+
| `DSH_*` | — | DSH 运行时旋钮:`PI_WEB_DSH_RUNTIME`、`PI_WEB_DSH_DATA_DIR`、`PI_WEB_DSH_PATCH_DIR`、`PI_WEB_DSH_QUESTION_TIMEOUT_MS`、`PI_WEB_DSH_TOOL_TIMEOUT_MS`、`PI_WEB_DSH_SESSION_RETENTION_DAYS`、`PI_WEB_DSH_DEBUG`。 |
|
|
552
|
+
|
|
387
553
|
## 安全
|
|
388
554
|
|
|
389
555
|
- **默认只绑 loopback** —— 服务器只监听 `127.0.0.1`,不暴露到网络;需要局域网访问或
|
|
@@ -435,7 +601,6 @@ server {
|
|
|
435
601
|
# 构建产物的绝对路径资源/API(根路径,不带 /pi/)
|
|
436
602
|
location /assets/ { proxy_pass http://127.0.0.1:8787; }
|
|
437
603
|
location = /favicon.svg { proxy_pass http://127.0.0.1:8787; }
|
|
438
|
-
location = /favicon-streaming.svg { proxy_pass http://127.0.0.1:8787; }
|
|
439
604
|
location = /api/file { proxy_pass http://127.0.0.1:8787; }
|
|
440
605
|
location = /api/health { proxy_pass http://127.0.0.1:8787; }
|
|
441
606
|
}
|
package/bin/pi-web-ui.mjs
CHANGED
|
@@ -1022,11 +1022,19 @@ function serviceOptions(opts) {
|
|
|
1022
1022
|
return { name, port, cwd, dataDir, engine, host, agentDir };
|
|
1023
1023
|
}
|
|
1024
1024
|
|
|
1025
|
-
function serviceEnv(port, cwd, dataDir, engine, host, agentDir) {
|
|
1025
|
+
function serviceEnv(port, cwd, dataDir, engine, host, agentDir, service = {}) {
|
|
1026
1026
|
const env = {
|
|
1027
1027
|
PI_WEB_PORT: port,
|
|
1028
1028
|
PI_WEB_CWD: cwd,
|
|
1029
1029
|
};
|
|
1030
|
+
// 启动来源标记(见 server/launch-origin.ts):只有真正被平台服务管理器托管的
|
|
1031
|
+
// 启动器才写。桌面快捷方式 / .command 在「未安装服务」时是前台跑(退出不回来),
|
|
1032
|
+
// 不能带这个标记,所以它们不传 service。已装好的老服务没有这两个变量,服务端
|
|
1033
|
+
// 仍能靠运行时判据(XPC_SERVICE_NAME / INVOCATION_ID / PID 文件)认出来。
|
|
1034
|
+
if (service.name) {
|
|
1035
|
+
env.PI_WEB_LAUNCHED_BY = "service";
|
|
1036
|
+
env.PI_WEB_SERVICE_NAME = service.name;
|
|
1037
|
+
}
|
|
1030
1038
|
// Interactive Windows tasks inherit the user's PATH; only systemd/launchd
|
|
1031
1039
|
// run with a minimal environment that needs an explicit PATH.
|
|
1032
1040
|
if (!isWin) env.PATH = process.env.PATH ?? "/usr/local/bin:/usr/bin:/bin";
|
|
@@ -1047,7 +1055,7 @@ function installLaunchd(opts) {
|
|
|
1047
1055
|
const { name, port, cwd, dataDir, engine, host, agentDir } = serviceOptions(opts);
|
|
1048
1056
|
const label = serviceLabel(name);
|
|
1049
1057
|
const plist = launchAgentPlist(name);
|
|
1050
|
-
const content = buildPlist(label, cwd, serviceEnv(port, cwd, dataDir, engine, host, agentDir));
|
|
1058
|
+
const content = buildPlist(label, cwd, serviceEnv(port, cwd, dataDir, engine, host, agentDir, { name }));
|
|
1051
1059
|
if (opts.print) {
|
|
1052
1060
|
console.log(`# ${plist}\n${content}`);
|
|
1053
1061
|
return;
|
|
@@ -1071,7 +1079,7 @@ function installLaunchd(opts) {
|
|
|
1071
1079
|
|
|
1072
1080
|
function installSystemd(opts) {
|
|
1073
1081
|
const { name, port, cwd, dataDir, engine, host, agentDir } = serviceOptions(opts);
|
|
1074
|
-
const content = buildUnit(cwd, serviceEnv(port, cwd, dataDir, engine, host, agentDir));
|
|
1082
|
+
const content = buildUnit(cwd, serviceEnv(port, cwd, dataDir, engine, host, agentDir, { name }));
|
|
1075
1083
|
const unitPath = systemdUnitPath(name);
|
|
1076
1084
|
if (opts.print) {
|
|
1077
1085
|
console.log(`# ${unitPath}\n${content}`);
|
|
@@ -1120,7 +1128,7 @@ function uninstallSystemd(opts) {
|
|
|
1120
1128
|
|
|
1121
1129
|
function installWindows(opts) {
|
|
1122
1130
|
const { name, port, cwd, dataDir, engine, host, agentDir } = serviceOptions(opts);
|
|
1123
|
-
const env = serviceEnv(port, cwd, dataDir, engine, host, agentDir);
|
|
1131
|
+
const env = serviceEnv(port, cwd, dataDir, engine, host, agentDir, { name });
|
|
1124
1132
|
const ps1Path = winPs1Path(name);
|
|
1125
1133
|
const vbsPath = winVbsPath(name);
|
|
1126
1134
|
const pidPath = winPidFilePath(name);
|
|
@@ -1244,6 +1252,15 @@ async function printLiveStatus(opts) {
|
|
|
1244
1252
|
console.log(" --- 实时状态 (control socket) ---");
|
|
1245
1253
|
console.log(` 版本 : ${st.version} · PID ${st.pid}`);
|
|
1246
1254
|
console.log(` 目录 : ${st.cwd}`);
|
|
1255
|
+
// 启动来源(server/launch-origin.ts):有 supervisor = 这个进程退出后会被自动
|
|
1256
|
+
// 拉起(server restart / 更新面板的「重启服务」才有意义)。
|
|
1257
|
+
console.log(
|
|
1258
|
+
` 启动 : ${
|
|
1259
|
+
st.service
|
|
1260
|
+
? `pi-web-ui 服务(${st.service.supervisor} · ${st.service.name})`
|
|
1261
|
+
: "前台 / 开发模式(无 supervisor,退出不自动重启)"
|
|
1262
|
+
}`,
|
|
1263
|
+
);
|
|
1247
1264
|
console.log(` 排空 : ${st.quiesced ? `是(自 ${new Date(st.quiescedSince).toLocaleString()})` : "否"}`);
|
|
1248
1265
|
console.log(
|
|
1249
1266
|
` 连接 : ${st.connectedClients} 个浏览器 · ${st.activeConversations} 个运行中对话 · ${st.pendingMessages} 条排队消息`,
|
|
@@ -1,88 +1,85 @@
|
|
|
1
|
-
# pi-web-ui behind nginx at a sub-path: http://<host>:83/pi/
|
|
2
|
-
# Backend app: http://127.0.0.1:8787 (default PI_WEB_PORT env)
|
|
3
|
-
#
|
|
4
|
-
# IMPORTANT: pi-web-ui 0.23+ checks the WebSocket Origin against the request
|
|
5
|
-
# Host (hostname AND port). Every proxied location MUST forward the original
|
|
6
|
-
# Host with $http_host (keeps the port). Using $host (drops the port) or
|
|
7
|
-
# leaving Host unset (defaults to 127.0.0.1:8787) makes the upgrade fail with
|
|
8
|
-
# 403 — the page loads but chat/terminal keep reconnecting.
|
|
9
|
-
#
|
|
10
|
-
# Topology (two listeners on one port: frp + LAN coexist):
|
|
11
|
-
# frp (public) -> <PUBLIC_IP>:<PUBLIC_PORT> -> 127.0.0.1:83 (PROXY protocol v2)
|
|
12
|
-
# LAN users -> http://<LAN_IP>:83/pi/ (plain HTTP listener)
|
|
13
|
-
#
|
|
14
|
-
# frpc sends PROXY v2 to nginx (transport.proxyProtocolVersion = "v2"):
|
|
15
|
-
# * 127.0.0.1:83 proxy_protocol — only the local frp client connects
|
|
16
|
-
# here (it speaks PROXY v2; nginx then sees the real visitor IP).
|
|
17
|
-
# * <LAN_IP>:83 plain HTTP — LAN browsers, no PROXY header needed.
|
|
18
|
-
#
|
|
19
|
-
# Simpler alternative (no real client IPs): drop proxy_protocol entirely and
|
|
20
|
-
# use a single `listen 83;` — then frpc must NOT set proxyProtocolVersion.
|
|
21
|
-
#
|
|
22
|
-
# The frontend uses absolute paths (/ws WebSocket, /assets/*, /favicon.svg,
|
|
23
|
-
# /api/file…) so those get their own proxied locations next to /pi/.
|
|
24
|
-
|
|
25
|
-
# Reuse for Upgrade/Connection headers (WebSocket).
|
|
26
|
-
map $http_upgrade $connection_upgrade {
|
|
27
|
-
default upgrade;
|
|
28
|
-
'' close;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
server {
|
|
32
|
-
listen 127.0.0.1:83 proxy_protocol;
|
|
33
|
-
listen <LAN_IP>:83;
|
|
34
|
-
|
|
35
|
-
server_name _;
|
|
36
|
-
|
|
37
|
-
# Trust PROXY-protocol headers only from the local frp client; plain
|
|
38
|
-
# (LAN) connections keep their real $remote_addr untouched.
|
|
39
|
-
set_real_ip_from 127.0.0.1;
|
|
40
|
-
real_ip_header proxy_protocol;
|
|
41
|
-
|
|
42
|
-
# ---- main entry: strip /pi/ and forward to the app root ----
|
|
43
|
-
location /pi/ {
|
|
44
|
-
proxy_pass http://127.0.0.1:8787/;
|
|
45
|
-
proxy_http_version 1.1;
|
|
46
|
-
# $http_host keeps the port — origin check compares hostname AND port.
|
|
47
|
-
proxy_set_header Host $http_host;
|
|
48
|
-
proxy_set_header X-Real-IP $remote_addr;
|
|
49
|
-
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
50
|
-
proxy_set_header X-Forwarded-Proto $scheme;
|
|
51
|
-
proxy_set_header Upgrade $http_upgrade;
|
|
52
|
-
proxy_set_header Connection $connection_upgrade;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
# ---- WebSocket (the frontend connects to ws://<host>/ws) ----
|
|
56
|
-
location /ws {
|
|
57
|
-
proxy_pass http://127.0.0.1:8787;
|
|
58
|
-
proxy_http_version 1.1;
|
|
59
|
-
proxy_set_header Host $http_host;
|
|
60
|
-
proxy_set_header Upgrade $http_upgrade;
|
|
61
|
-
proxy_set_header Connection $connection_upgrade;
|
|
62
|
-
proxy_read_timeout 3600s;
|
|
63
|
-
proxy_send_timeout 3600s;
|
|
64
|
-
proxy_set_header X-Real-IP $remote_addr;
|
|
65
|
-
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
# ---- absolute asset paths baked into index.html ----
|
|
69
|
-
location /assets/ {
|
|
70
|
-
proxy_pass http://127.0.0.1:8787;
|
|
71
|
-
}
|
|
72
|
-
location = /favicon.svg {
|
|
73
|
-
proxy_pass http://127.0.0.1:8787;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
return 302 /pi/;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
1
|
+
# pi-web-ui behind nginx at a sub-path: http://<host>:83/pi/
|
|
2
|
+
# Backend app: http://127.0.0.1:8787 (default PI_WEB_PORT env)
|
|
3
|
+
#
|
|
4
|
+
# IMPORTANT: pi-web-ui 0.23+ checks the WebSocket Origin against the request
|
|
5
|
+
# Host (hostname AND port). Every proxied location MUST forward the original
|
|
6
|
+
# Host with $http_host (keeps the port). Using $host (drops the port) or
|
|
7
|
+
# leaving Host unset (defaults to 127.0.0.1:8787) makes the upgrade fail with
|
|
8
|
+
# 403 — the page loads but chat/terminal keep reconnecting.
|
|
9
|
+
#
|
|
10
|
+
# Topology (two listeners on one port: frp + LAN coexist):
|
|
11
|
+
# frp (public) -> <PUBLIC_IP>:<PUBLIC_PORT> -> 127.0.0.1:83 (PROXY protocol v2)
|
|
12
|
+
# LAN users -> http://<LAN_IP>:83/pi/ (plain HTTP listener)
|
|
13
|
+
#
|
|
14
|
+
# frpc sends PROXY v2 to nginx (transport.proxyProtocolVersion = "v2"):
|
|
15
|
+
# * 127.0.0.1:83 proxy_protocol — only the local frp client connects
|
|
16
|
+
# here (it speaks PROXY v2; nginx then sees the real visitor IP).
|
|
17
|
+
# * <LAN_IP>:83 plain HTTP — LAN browsers, no PROXY header needed.
|
|
18
|
+
#
|
|
19
|
+
# Simpler alternative (no real client IPs): drop proxy_protocol entirely and
|
|
20
|
+
# use a single `listen 83;` — then frpc must NOT set proxyProtocolVersion.
|
|
21
|
+
#
|
|
22
|
+
# The frontend uses absolute paths (/ws WebSocket, /assets/*, /favicon.svg,
|
|
23
|
+
# /api/file…) so those get their own proxied locations next to /pi/.
|
|
24
|
+
|
|
25
|
+
# Reuse for Upgrade/Connection headers (WebSocket).
|
|
26
|
+
map $http_upgrade $connection_upgrade {
|
|
27
|
+
default upgrade;
|
|
28
|
+
'' close;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
server {
|
|
32
|
+
listen 127.0.0.1:83 proxy_protocol;
|
|
33
|
+
listen <LAN_IP>:83;
|
|
34
|
+
|
|
35
|
+
server_name _;
|
|
36
|
+
|
|
37
|
+
# Trust PROXY-protocol headers only from the local frp client; plain
|
|
38
|
+
# (LAN) connections keep their real $remote_addr untouched.
|
|
39
|
+
set_real_ip_from 127.0.0.1;
|
|
40
|
+
real_ip_header proxy_protocol;
|
|
41
|
+
|
|
42
|
+
# ---- main entry: strip /pi/ and forward to the app root ----
|
|
43
|
+
location /pi/ {
|
|
44
|
+
proxy_pass http://127.0.0.1:8787/;
|
|
45
|
+
proxy_http_version 1.1;
|
|
46
|
+
# $http_host keeps the port — origin check compares hostname AND port.
|
|
47
|
+
proxy_set_header Host $http_host;
|
|
48
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
49
|
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
50
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
51
|
+
proxy_set_header Upgrade $http_upgrade;
|
|
52
|
+
proxy_set_header Connection $connection_upgrade;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
# ---- WebSocket (the frontend connects to ws://<host>/ws) ----
|
|
56
|
+
location /ws {
|
|
57
|
+
proxy_pass http://127.0.0.1:8787;
|
|
58
|
+
proxy_http_version 1.1;
|
|
59
|
+
proxy_set_header Host $http_host;
|
|
60
|
+
proxy_set_header Upgrade $http_upgrade;
|
|
61
|
+
proxy_set_header Connection $connection_upgrade;
|
|
62
|
+
proxy_read_timeout 3600s;
|
|
63
|
+
proxy_send_timeout 3600s;
|
|
64
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
65
|
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
# ---- absolute asset paths baked into index.html ----
|
|
69
|
+
location /assets/ {
|
|
70
|
+
proxy_pass http://127.0.0.1:8787;
|
|
71
|
+
}
|
|
72
|
+
location = /favicon.svg {
|
|
73
|
+
proxy_pass http://127.0.0.1:8787;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
# ---- media preview / download / health API ----
|
|
77
|
+
location /api/ {
|
|
78
|
+
proxy_pass http://127.0.0.1:8787;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
# bare root → app entry
|
|
82
|
+
location = / {
|
|
83
|
+
return 302 /pi/;
|
|
84
|
+
}
|
|
85
|
+
}
|