pi-web-ui 0.85.0 → 0.86.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/CHANGELOG.md +117 -1
- package/bin/pi-web-ui.mjs +98 -8
- package/dist/server/agent-service.js +542 -16
- package/dist/server/client-state.js +99 -1
- package/dist/server/dsh/dsh-agent-service.js +239 -7
- package/dist/server/index.js +205 -3
- package/dist/server/managed.js +4 -0
- package/dist/server/plugin-catalog-sync.js +117 -0
- package/dist/server/plugin-catalog.js +67 -0
- package/dist/server/plugin-grants.js +281 -0
- package/dist/server/plugin-installer.js +238 -0
- package/dist/server/plugin-project.js +406 -0
- package/dist/server/plugins.js +390 -4
- package/dist/server/settings-service.js +34 -1
- package/dist/server/terminals.js +7 -2
- package/dist/server/update-check.js +100 -6
- package/dist/server/wait-subscription-scan.js +1 -1
- package/package.json +1 -1
- package/themes/dark-teal.css +209 -0
- package/web/dist/assets/TerminalPanel-fC8DLbDz.js +6 -0
- package/web/dist/assets/index-CCrF953V.js +361 -0
- package/web/dist/assets/index-CKoVyDkP.css +10 -0
- package/web/dist/index.html +2 -2
- package/web/dist/assets/TerminalPanel-CNz4vxHW.js +0 -6
- package/web/dist/assets/index-BjODtvwn.css +0 -10
- package/web/dist/assets/index-DM-A59lk.js +0 -348
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,114 @@
|
|
|
8
8
|
每个版本的内容按"实际合入该版本发布的提交"归档(以 `package.json` 的 version 变更提交为准),
|
|
9
9
|
而不是按提交日期聚类——连续快速发布的 patch 版本以此为准最准确。
|
|
10
10
|
|
|
11
|
+
## [Unreleased]
|
|
12
|
+
|
|
13
|
+
## [0.86.1] — 2026-09-15
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- **插件后台作业的取消/看门狗在 Linux/macOS 上杀不掉进程** —— `PluginInstaller` 起 CLI 子进程时没设 `detached`,POSIX 下子进程跟 server 同进程组,`killPidTree` 的 `kill(-pid)` 指向一个不存在的组而静默失败:取消点了没反应、超时作业也杀不掉,单作业锁还一直占着(直到 30 分钟看门狗……它自己也杀不掉)。Windows 走 `taskkill` 不受影响,所以本地一直是绿的、CI(ubuntu-latest)的 busy 单测连续 5 秒超时挂红。现在与 `plugin-project.ts` 同一写法:非 Windows 起独立进程组,整棵树一次带走。
|
|
18
|
+
|
|
19
|
+
## [0.86.0] — 2026-09-15
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- **插件安装 / 更新 / 卸载改为后台作业,不再抢走设置面板**(issue #152)—— 以前点安装会在可见终端里跑 CLI,同时把设置弹窗
|
|
24
|
+
关掉、主视图切到终端;连装几个插件就得「装一个、重开设置、再导航回市场」。现在作业跑在服务端(`server/plugin-installer.ts`
|
|
25
|
+
,执行的仍是同一个 CLI),输出按行回传,**设置面板原地显示进度**(进行中带最后一行输出,失败可就地展开输出尾部);同一时刻
|
|
26
|
+
只允许一个作业(两个 install 写同一目录必出半装状态),另有 15 分钟看门狗与「取消」。托管实例(`PI_WEB_MANAGED=1`)与
|
|
27
|
+
CLI 缺失都会明确拒绝。
|
|
28
|
+
- **`host.reloadCatalog()`:受支持的插件市场目录同步**(issue #148)—— 第三方插件以前只能派发私有浏览器事件 + 开可见终
|
|
29
|
+
端来同步自己的插件清单(私有事件随时会变、宿主更新没有回执)。现在有一条一等公民路径:`reloadCatalog(url 或本地路径,
|
|
30
|
+
{ install?, replace? })` —— 服务端拉取 → 用市场「添加到列表」同一套规则校验 → **原子写** `<dataDir>/plugin-catalog.json`
|
|
31
|
+
(形状不对 / 解析失败 / 读不到时一个字节都不写,旧目录保持有效)→ 可选逐条安装(已装走更新,失败逐条记录不中断整批)→
|
|
32
|
+
重载插件并把新列表推给所有客户端 → 结构化回执 `{ok, error?, entries?, installed?}`。
|
|
33
|
+
- **插件 UI 扩展点框架:11 个挂载点,插件只声明、宿主负责渲染**(issue #146 完整版)—— v1 那条 `topbar` 专用声明长成
|
|
34
|
+
了一套通用 slot 框架。manifest 的 `ui` 字段(或运行时 `host.ui.*`)可以往 `topbar.primary` / `topbar.overflow` /
|
|
35
|
+
`bottombar` / `composer.actions` / `message.actions` / `rightpanel.tabs` / `contextmenu.topbar|message|session|file` /
|
|
36
|
+
`settings.pages` 这 11 个挂载点声明条目(`{id, label, labelEn?, icon?, kind?, order?, group?, hidden?, action?, view?,
|
|
37
|
+
when?, children?}`,也收 `topbar` / `settings` 这类简写别名);宿主负责渲染、排序、**溢出菜单**、可访问性、用户偏好与
|
|
38
|
+
审计,插件不碰 DOM,动作由插件客户端经 `host.onUiAction(name, fn)` 接管(按需加载它的 bundle,最终没人接管就提示一句,
|
|
39
|
+
不让按钮看起来点了没用)。
|
|
40
|
+
- **插件能整理宿主内置条目**:`ui.arrange` 可以把内置入口(id 形如 `host:settings`)移到别的槽位、隐藏、改顺序/分组/
|
|
41
|
+
文案。内置条目表是 `web/src/ui-slots.ts` 的 `BUILTIN_UI_ITEMS`(32 条,逐条对应代码里真实存在的入口,不臆造)。
|
|
42
|
+
- **用户偏好永远最后说话**:设置面板「界面插件 → 界面布局」按槽位列出所有条目,可逐条隐藏 / ↑↓ 调序 / 「恢复」单条
|
|
43
|
+
或全部;被插件动过的条目标「插件调整过」并给恢复按钮。**顶栏、底栏、右键菜单看到的顺序与设置里一致** —— 两边跑
|
|
44
|
+
同一个纯函数(`buildUiSlots`),这是本框架的核心不变量。隐藏的条目仍能在顶栏溢出菜单里点到,不是消失。
|
|
45
|
+
- **宿主内置条目也真的听布局页**(本轮补齐):底栏那一条、顶栏「桌面工具组」(搜索 / 浏览器操作 / 后台任务 / 设置 /
|
|
46
|
+
声音 / 语言 / 主题 / 版本 / GitHub)、视图三连、右上(历史 / 文件 / 新对话)、消息 hover 工具条、右栏 tab 全部改成
|
|
47
|
+
按 slot 结果渲染 —— 勾掉即消失、↑↓ 真的换位置(以前一部分写死在组件 JSX 里,勾选框是摆设)。被隐藏的**菜单型**
|
|
48
|
+
条目(声音 / 语言 / 主题 / 版本 / GitHub / 浏览器操作)会把整个组件搬进「⋯」溢出菜单(点了照旧能用),
|
|
49
|
+
扁平动作类(搜索 / 任务 / 设置 / 历史 / 文件 / 新对话)在菜单里是一条可点的菜单项;一条都不剩时消息工具条
|
|
50
|
+
整条不画,不留空壳。
|
|
51
|
+
- **插件条目的悬浮提示真的会显示**:`hint` / `hintEn`(可被 `arrange` 改写)会落成渲染层的 `title`
|
|
52
|
+
(顶栏条目、底栏条目、右键菜单项、右栏 tab、设置面板插件页),只给一种语言时另一种回落它 ——
|
|
53
|
+
以前这个字段只被解析、没有任何渲染层用它。
|
|
54
|
+
- **通用右键菜单**:右栏文件树与左栏会话原先各弹一套本地 `.ctx-menu`,现在由 `App` 里唯一的 `ContextMenu.tsx`
|
|
55
|
+
渲染(portal + fixed 定位 + 先渲染再实测尺寸钳制,不被滚动容器裁剪);内置条目(上传到文件夹 / 以项目打开 / 添加为
|
|
56
|
+
工作区根 / 关闭已结束子代理…)照旧。四个 `contextmenu.*` 槽位都已就绪,但**宿主今天只在文件树与会话两处有内置条目**
|
|
57
|
+
(消息/顶栏两处还没有右键菜单,所以一条都不登记 —— 宁缺勿造),插件可以往这四个槽位加自己的操作。
|
|
58
|
+
- **插件自定义设置页**(issue #146)—— manifest `ui.settings`(或 `kind: "page"`)声明一页,设置面板左侧导航就多一项,
|
|
59
|
+
内容由 `PluginPage.tsx` 挂载插件自己的 client bundle 渲染(`mount(container, ctx)`,**切走即调 cleanup** —— 设置页是
|
|
60
|
+
「看完就走」的场景,不让插件的定时器一直留在 DOM 里)。插件页排在内置分区之后;`hidden: true` 的页默认不出现,用户
|
|
61
|
+
可在「界面布局」里放出来。插件配置从此有个像样的落点,不必再抢一个顶栏视图 tab。
|
|
62
|
+
- **插件能在工作区外读写文件,但每一次都要用户点过头**(issue #146)—— 新增目录授权表 `server/plugin-grants.ts`(存
|
|
63
|
+
`<dataDir>/plugin-grants.json`,与 UI 偏好分文件存,可审计、可手改、设置面板「已授权目录」可撤销):
|
|
64
|
+
`host.fs.requestAccess(dir, reason)` 弹确认框,同意后可勾「记住」。授权天然是**子树**授权(批准 `/proj` 就覆盖
|
|
65
|
+
`/proj/src`;反向不成立 —— 只批了 `/proj/a` 时读 `/proj` 仍要再问)。`host.fs.authorizedDirs()` 列已授权目录,
|
|
66
|
+
`listPath` / `readPath` / `readTextPath` / `writePath` / `removePath` 每次操作都要求路径已授权(本来就在工作区内的
|
|
67
|
+
免打扰)。以前插件只能靠裸 `node:fs` 无告知地碰任意路径,现在多了一条「用户点过头才算数」的受支持路径。
|
|
68
|
+
- **`host.project.create()`:让宿主帮插件组装一个项目**(issue #146)—— 插件给一份规格(根目录 + 要 clone 的仓库 +
|
|
69
|
+
要写的文件 + 可选 `git init`),宿主逐行回报进度:clone(不走 shell)、写文件、失败即停并回传**第一个**失败点与已走过
|
|
70
|
+
的日志 —— 插件会把日志原样展示给用户,一个半成品项目配「成功」比直接报错更坏。所有校验在**动磁盘之前**做完;路径过
|
|
71
|
+
三道越界防线(拒绝绝对路径与 `..`,目标最近一个已存在祖先的 realpath 仍须落在授权根内),junction / 符号链接也带不
|
|
72
|
+
出去。根目录必须已授权或本来就在工作区内。
|
|
73
|
+
- **额外工作区根:一个项目可以挂多个目录**(issue #146 完整版)—— 以前右栏文件树只看当前工作目录,插件想读同机的兄弟
|
|
74
|
+
目录得单独授权。现在右栏有「工作区根」选择器:右键文件树条目「添加为工作区根」即可在树里切换浏览(选择器里能切根、
|
|
75
|
+
能移除根;加根有两个入口:文件树右键「添加为工作区根」、底栏工作目录选择器里的「+ 添加为工作区根」),
|
|
76
|
+
**按项目(cwd)持久化**、上限 8 个、切项目各带各的。语义边界写死:**AI 仍然只在主 cwd 里干活**(终端与
|
|
77
|
+
「以项目打开」口径不变),变的只是「哪些算工作区内」—— 因此插件 `host.fs.*` 读这些根**不必再单独授权**(见上一条)。
|
|
78
|
+
插件也可经 `host.openSession({ folders/roots })` 一次给出 cwd + 额外根。
|
|
79
|
+
- **插件会话 API `host.sessions` 与宿主 API v6**(issue #146 完整版)—— 插件现在能列出**可打开的会话**
|
|
80
|
+
(`sessions.list()`:本客户端运行中的对话 + 当前项目的历史会话,带 title / cwd / kind / isStreaming)并打开其中一个
|
|
81
|
+
(`sessions.open(id)`);`host.openSession()` 也从「只支持单目录」升级为支持多根 —— `folders` / `roots` 的第一个当
|
|
82
|
+
cwd,其余当额外工作区根,每个目录都要过授权确认(`startChat` 仍是那条无需等待的短形式)。宿主 API 版本 4 → **6**
|
|
83
|
+
(4 加了 `reloadCatalog` / `openSession` / `onTopbarAction`,5 把顶栏动作推广成通用 `onUiAction`,6 加了 `sessions`
|
|
84
|
+
与多根 `openSession`;`onTopbarAction` 保留为别名)。插件可用 `version` 判断宿主能力,老宿主上不会拿到 undefined 接口。
|
|
85
|
+
- **`pi-web-ui install --build`:源码安装时隔离构建**(issue #150)—— 插件仓库可以只提交 TypeScript 源码,不必再把
|
|
86
|
+
`index.mjs` / `client/entry.mjs` 产物提交进仓库。构建在临时目录里完成:只装插件声明的构建依赖
|
|
87
|
+
(`npm install --ignore-scripts`,不执行任意生命周期脚本)→ 跑 manifest.build.command(缺省回落 package.json 的
|
|
88
|
+
`scripts.build`)→ 校验 `outputs` 产物齐全 → **成功后才替换目标目录**(失败时上一版插件原样可用、无半装状态)。设置面
|
|
89
|
+
板的插件市场有「源码构建」勾选项,等价 `--build`,网络安装入口全部纳入托管实例(`PI_WEB_MANAGED`)拒绝面。
|
|
90
|
+
- **vscode-editor 插件:SSH 主机支持私钥路径 / 口令 / agent,还能从 `~/.ssh/config` 批量导入**(issue #149)—— 以前主机编辑只有密码与内联 PEM 私钥两项:带口令的私钥没地方填口令(连上就挂),用 `~/.ssh/id_rsa` 这类文件路径的得把私钥全文粘贴进来,ssh-agent 更没入口。现在编辑框多了三项:私钥路径(支持 `~` 展开,填写则优先用文件、不必粘贴全文)、私钥口令 passphrase(留空=保持不变,连接时透给 ssh2)、agent socket(如 `$SSH_AUTH_SOCK`,与密码/私钥互斥);「从 ssh config 导入」按钮解析本机 `~/.ssh/config` 列出候选(已导入的标出跳过),勾选批量导入 —— 导入只存私钥路径引用,不读私钥内容。解析语义对齐 OpenSSH:同块先出现的值优先,`Host *` 块只充当默认值继承、不产出候选,含通配符的别名不产出。
|
|
91
|
+
- **检查更新走你自己的 npm 源,不再卡在官方源上**(issue #151)—— 配了镜像/私有源的用户(`<agentDir>/npm/.npmrc`,`pi update` 经 npm 本来就认这一份),以前顶栏更新检查还直连 `registry.npmjs.org`:镜像用户查不到新版、私有源用户直接 401。现在检查更新读同一份 `.npmrc` 解析 registry + 认证头(`_authToken` 优先、`_auth` 其次,同源才带),无文件/无配置时回落官方源。pi 与 DSH 双引擎同修。
|
|
92
|
+
- **桌面版里「浏览器操作」给明确结论,不再让人白装扩展**(issue #153)—— 桌面窗口(Electron)里没有 Chrome 扩展运行时,page-picker 扩展永远装不上;以前面板还是网页版那四步安装引导,用户跟着做完才发现此路不通,模型调 `browser_page` 还要干等 3 秒桥超时。现在桌面壳里面板直接给结论 + 「用默认浏览器打开当前地址」按钮(去网页版按四步装即可),`queryBrowserControl` 与服务端 `pageCall` 都短路返回「改用网页版」的错误,不碰扩展桥。
|
|
93
|
+
- **桌面版点对话里的链接不再把窗口带走**(issue #154)—— 以前对话正文里的外链是裸 `href`,桌面壳里一点就是同帧导航:整个应用窗口被带到外部页面(无地址栏、无后退键,只能重启)。现在两层修复:正文外链统一 `target="_blank"`(网页版行为也更符合预期:新标签页打开;站内锚点与相对路径不动),桌面壳另加 `will-navigate` 守卫 —— 应用自身 origin 放行,其余一律转系统浏览器(`setWindowOpenHandler` 只拦新窗口请求,拦不住同帧导航,所以两层都要)。
|
|
94
|
+
|
|
95
|
+
### Changed
|
|
96
|
+
|
|
97
|
+
- **插件 `apiVersion: 2` 起「不写 `permissions`」等于默认拒绝**(issue #146 完整版)—— 宿主设施版本升到
|
|
98
|
+
`PLUGIN_API_VERSION = 2`。以前 `permissions` 缺省是「旧格式全权模式」:受控宿主 API 一律放行、只在日志里警告一次;
|
|
99
|
+
现在只要 manifest 声明了 `apiVersion: 2`,没写 `permissions` 就按**空能力集**处理 —— `fs` / `http` / `tools` / `ui` /
|
|
100
|
+
`chat` 这些受控入口逐个拒绝(`manifest.ui` 整份忽略),日志里写明缺哪个能力族。**对插件作者的含义**:升到 2 就得同时
|
|
101
|
+
补上 `permissions`(哪怕只是加一个顶栏按钮,也要写 `"permissions": ["ui"]`)。不写 `apiVersion` 的老插件仍是 v1 +
|
|
102
|
+
旧全权模式(只警告、行为不变),所以升级可以按插件逐个进行;`apiVersion` 比宿主新才会被拒绝激活,并提示升级 pi-web-ui。
|
|
103
|
+
|
|
104
|
+
### Fixed
|
|
105
|
+
|
|
106
|
+
- **「看不见的第二个 agent」不会再出现了**(issue #145)—— 换设备 / 新开标签页打开一条正在跑的对话,以前 UI 显示空闲可发,一发消息就给同一份会话再开一支 run,两支 agent 在同一工作区并行动手、事后只有一支可查。现在服务端跨客户端查重:同一份记录在别处正在跑时,`switch_session` / `prompt` 直接拒绝并告诉你去原窗口继续,第二个 writer 从机制上造不出来;owner 空闲后可正常打开(会提醒你别处也开着、只留一处发送)。**新标签页也不再默认落进正在跑的那条**:初始恢复与切项目首访恢复在建之前就查一遍,命中正在跑就停在空白新对话并告诉你原因。同项目不同对话仍可并行(适合改不同文件),但两边都会收到并行提醒,AI 还会收到一条冲突评估提醒(拿不准就用 `ask_user_question` 让你选:并行 / 等它跑完 / 只读围观)。左栏「运行的对话」里直接能看到其他标签页 / 设备的运行(带“另一处”标签,只读不可点)。pi 与 DSH 双引擎同修,回归测试 `tests/cross-client-session-test.mjs`(改前红改后绿,覆盖拒绝双写/默认落点/并行感知)。
|
|
107
|
+
- **刷新页面不再把已退出的 AI bash 终端复活成用户终端并反复弹「终端数量已达上限」**(issue #147)—— 开着「终端接管 bash」跑一批命令后,每个一次性 AI 终端都会在 history 里留一条记录;以前刷新/重连/切对话时前端会为其中每一条重发 `terminal_create`(还不带 `agentBash`),服务端于是把它们重建成普通用户终端:白白拉起几十个 shell 进程不说,攒到 16 个就触发上限报错、每条再各弹一次全局通知。现在三层修复:前端挂载时发现终端已退出(`running === false`)就只做展示(保留输出照旧由服务端 replay 推送),不再重建 PTY;`terminal_create` 消息新增 `agentBash` 字段并全链路透传;服务端 `create()` 在字段缺省(旧前端)时从 history 继承原有身份。已退出的**用户**终端在满额时重建仍被拒绝(history 不预留名额),`terminal_create` 工具建的常驻终端也照旧计入用户名额。回归测试 `tests/terminal-smoke-test.mjs`(WS 透传 + 继承/拒绝口径)。
|
|
108
|
+
- **输入框里的一行 JSX 注释不再渲染成可见文本** —— `ChatInput.tsx` 里 `/* … */` 写在了 JSX 子节点位置,会被当成文本渲染出来;已改为 `{/* … */}`。
|
|
109
|
+
|
|
110
|
+
<!-- auto-i18n:start -->
|
|
111
|
+
### i18n
|
|
112
|
+
|
|
113
|
+
- 前端新增 key(44):`elsewhereBadge`、`elsewhereTip`、`workspaceRoots`、`workspaceRootsHint`、`addWorkspaceRoot`、`addWorkspaceRootHint`、`removeWorkspaceRoot`、`browserControlDesktop`、`browserControlDesktopLead`、`browserControlOpenInBrowser`、`devNoCache`、`devNoCacheDesc`、`autoReload`、`autoReloadDesc`、`pluginJobRunning`、`pluginJobDone`、`pluginJobFailed`、`pluginBuildSource`、`pluginBuildHint`、`uiLayoutTitle`、`uiLayoutHint`、`pluginTopbarMore`、`uiLayoutTopbar`、`uiLayoutTopbarOverflow`、`uiLayoutBottombar`、`uiLayoutComposer`、`uiLayoutMessage`、`uiLayoutRightPanel`、`uiLayoutSettingsPages`、`uiLayoutRestore`、`uiLayoutRestoreAll`、`uiLayoutArranged`、`uiLayoutEmpty`、`pluginGrantsTitle`、`pluginGrantsHint`、`pluginGrantsEmpty`、`pluginGrantsRevoke`、`pluginGrantRequestTitle`、`pluginGrantRequestBody`、`pluginGrantAllow`、`pluginGrantDeny`、`pluginUiNoHandler`、`pluginSessionGrantTitle`、`pluginSessionGrantBody`
|
|
114
|
+
- 前端中文变更(3):`pluginUpdateHint`、`pluginInstallHint`、`pluginUninstallHint`
|
|
115
|
+
- 前端英文变更(3):`pluginUpdateHint`、`pluginInstallHint`、`pluginUninstallHint`
|
|
116
|
+
- 服务端新增 key(15):`plugincatalog.sync.fetch.failed`、`plugincatalog.sync.http`、`plugincatalog.sync.too.large`、`plugincatalog.sync.source.invalid`、`plugincatalog.sync.read.failed`、`plugincatalog.sync.source.missing`、`plugincatalog.sync.parse.failed`、`plugincatalog.sync.shape`、`plugininstaller.id.invalid`、`plugininstaller.source.invalid`、`plugininstaller.managed`、`plugininstaller.busy`、`plugininstaller.cli.missing`、`plugininstaller.cancelled`、`plugininstaller.timeout`
|
|
117
|
+
<!-- auto-i18n:end -->
|
|
118
|
+
|
|
11
119
|
## [0.85.0] — 2026-09-14
|
|
12
120
|
|
|
13
121
|
### Added
|
|
@@ -31,9 +139,11 @@
|
|
|
31
139
|
- **目标(goal)展开时内容不再被挤扁**(issue #141)—— `.goalbar` 纵向容器原来是 `align-items: center`,展开态的两行被压成内容宽度,输入框缩成一小截。现在容器改 `align-items: stretch` 让展开的行铺满整列,只有折叠态(小药丸)保留居中。
|
|
32
140
|
|
|
33
141
|
<!-- auto-i18n:start -->
|
|
142
|
+
|
|
34
143
|
### i18n
|
|
35
144
|
|
|
36
145
|
- 前端新增 key(7):`browserControlInstall`、`browserControlInstallLead`、`browserControlInstallDownload`、`browserControlInstallStep1`、`browserControlInstallStep2`、`browserControlInstallStep3`、`browserControlInstallStep4`
|
|
146
|
+
|
|
37
147
|
<!-- auto-i18n:end -->
|
|
38
148
|
|
|
39
149
|
## [0.84.0] — 2026-09-13
|
|
@@ -55,9 +165,11 @@
|
|
|
55
165
|
- 分组逻辑抽成纯函数 `web/src/conv-groups.ts` + 单测 `tests/unit/conv-groups.test.ts`,另有真浏览器逐帧回归 `tests/conv-group-flash-test.mjs`(MutationObserver 记录每一帧 DOM,两个方向都断言不再渲染项目名;改回只看 `cwd` 即变红)。
|
|
56
166
|
|
|
57
167
|
<!-- auto-i18n:start -->
|
|
168
|
+
|
|
58
169
|
### i18n
|
|
59
170
|
|
|
60
171
|
- 服务端新增 key(1):`terminals.cwd.outside.workspace`
|
|
172
|
+
|
|
61
173
|
<!-- auto-i18n:end -->
|
|
62
174
|
|
|
63
175
|
## [0.83.0] — 2026-09-13
|
|
@@ -147,11 +259,13 @@
|
|
|
147
259
|
暂无其他未发布内容。
|
|
148
260
|
|
|
149
261
|
<!-- auto-i18n:start -->
|
|
262
|
+
|
|
150
263
|
### i18n
|
|
151
264
|
|
|
152
265
|
- 前端新增 key(28):`browserPageEnabledDesc`、`browserPageOffHint`、`browserControl`、`browserControlTip`、`browserControlChecking`、`browserControlOffline`、`browserControlEmpty`、`browserControlDisabled`、`browserControlPages`、`browserControlPageOpen`、`browserControlPageClosed`、`browserControlExamples`、`browserControlExample1`、`browserControlExample2`、`browserControlOpenOptions`、`browserControlRefresh`、`browserControlCite`、`browserControlCiteTip`、`browserControlCiteNote`、`browserControlCited`、`browserControlCiteFailed`、`browserControlOpenPanel`、`browserControlSingleTip`、`attachPage`、`attachPageShort`、`tplThinkingLabel`、`tplThinkingFollowMain`、`tplThinkingHint`
|
|
153
266
|
- 前端中文变更(2):`settingsSubagentTemplatesDesc`、`noSubagentTemplates`
|
|
154
267
|
- 前端英文变更(2):`settingsSubagentTemplatesDesc`、`noSubagentTemplates`
|
|
268
|
+
|
|
155
269
|
<!-- auto-i18n:end -->
|
|
156
270
|
|
|
157
271
|
## [0.82.0] — 2026-09-13
|
|
@@ -734,7 +848,9 @@
|
|
|
734
848
|
- 0.35.1(2026-08-27):编辑重问保留附件(#18)+ 全窗口拖放(#19)。
|
|
735
849
|
- 0.29.0(2026-08-23):全局搜索弹窗(Ctrl+K)+ 消息列表惰性窗口化。
|
|
736
850
|
|
|
737
|
-
[Unreleased]: https://github.com/xing-shuyin/pi-web-ui/compare/v0.
|
|
851
|
+
[Unreleased]: https://github.com/xing-shuyin/pi-web-ui/compare/v0.86.1...main
|
|
852
|
+
[0.86.1]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.86.1
|
|
853
|
+
[0.86.0]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.86.0
|
|
738
854
|
[0.84.0]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.84.0
|
|
739
855
|
[0.83.0]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.83.0
|
|
740
856
|
[0.80.1]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.80.1
|
package/bin/pi-web-ui.mjs
CHANGED
|
@@ -270,6 +270,9 @@ function parseFlags(argv) {
|
|
|
270
270
|
case "--force":
|
|
271
271
|
opts.force = true;
|
|
272
272
|
break;
|
|
273
|
+
case "--build":
|
|
274
|
+
opts.build = true;
|
|
275
|
+
break;
|
|
273
276
|
case "--check-updates":
|
|
274
277
|
opts.checkUpdates = true;
|
|
275
278
|
break;
|
|
@@ -1446,12 +1449,13 @@ const PLUGIN_HELP = `用法:
|
|
|
1446
1449
|
https://github.com/owner/repo 完整 URL(.git 可省)
|
|
1447
1450
|
https://github.com/o/r/tree/dev/sub/dir 指定分支 + 仓库内子目录
|
|
1448
1451
|
以上任意写法末尾加 #分支或tag 指定分支/tag(如 owner/repo#v1.2)
|
|
1449
|
-
/path/to/plugin-dir
|
|
1450
|
-
|
|
1451
|
-
install 选项:
|
|
1452
|
+
/path/to/plugin-dir install 选项:
|
|
1452
1453
|
--name <id> 插件目录名/id(默认取仓库名或 manifest.id,仅限字母数字-_)
|
|
1453
1454
|
--data-dir <dir> 数据目录(默认 ~/.pi-web 或 $PI_WEB_DATA_DIR)
|
|
1454
1455
|
--force 目标目录已存在时覆盖(覆盖前自动备份旧版本)
|
|
1456
|
+
--build 源码安装:在隔离临时目录里按插件声明安装构建依赖并编译
|
|
1457
|
+
(manifest.build 或 package.json 的 scripts.build),
|
|
1458
|
+
成功且产物齐全后才替换目标目录,失败不留半装状态 目标目录已存在时覆盖(覆盖前自动备份旧版本)
|
|
1455
1459
|
|
|
1456
1460
|
plugins 选项:
|
|
1457
1461
|
--check-updates 逐个对比最近安装版本与远端 HEAD,列出可更新插件
|
|
@@ -1581,6 +1585,77 @@ function locatePluginRoot(checkout, subpath, repoLabel) {
|
|
|
1581
1585
|
return hits[0];
|
|
1582
1586
|
}
|
|
1583
1587
|
|
|
1588
|
+
/**
|
|
1589
|
+
* 插件的源码构建声明(--build,issue #150):
|
|
1590
|
+
* manifest.json: { "build": { "install"?: "npm install --ignore-scripts",
|
|
1591
|
+
* "command": "npm run build",
|
|
1592
|
+
* "outputs"?: ["index.mjs", "client/entry.mjs"] } }
|
|
1593
|
+
* 缺 command 时回落到 package.json 的 scripts.build;两者都没有 = 没声明构建方式。
|
|
1594
|
+
* 返回 null 表示该插件没声明构建(--build 会明确报错,而不是猜一个命令出来)。
|
|
1595
|
+
*/
|
|
1596
|
+
function resolveBuildPlan(pluginRoot, manifest) {
|
|
1597
|
+
const declared = manifest && typeof manifest.build === "object" && manifest.build ? manifest.build : null;
|
|
1598
|
+
let command = declared && typeof declared.command === "string" ? declared.command.trim() : "";
|
|
1599
|
+
if (!command) {
|
|
1600
|
+
try {
|
|
1601
|
+
const pkg = JSON.parse(readFileSync(join(pluginRoot, "package.json"), "utf8"));
|
|
1602
|
+
if (pkg?.scripts && typeof pkg.scripts.build === "string" && pkg.scripts.build.trim()) command = "npm run build";
|
|
1603
|
+
} catch {
|
|
1604
|
+
/* 没有 package.json */
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
if (!command) return null;
|
|
1608
|
+
const install =
|
|
1609
|
+
declared && typeof declared.install === "string" && declared.install.trim()
|
|
1610
|
+
? declared.install.trim()
|
|
1611
|
+
: "npm install --ignore-scripts --no-audit --no-fund";
|
|
1612
|
+
const outputs =
|
|
1613
|
+
declared && Array.isArray(declared.outputs)
|
|
1614
|
+
? declared.outputs.filter((s) => typeof s === "string" && s.trim()).map((s) => (s ?? "").trim())
|
|
1615
|
+
: ["index.mjs", "client/entry.mjs"];
|
|
1616
|
+
return { install, command, outputs };
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
/** 拷贝插件树时的过滤:不带 .git / node_modules(与安装同一套)。 */
|
|
1620
|
+
const PLUGIN_COPY_FILTER = (s) => !/(^|[\\/])(\.git|node_modules)([\\/]|$)/.test(s);
|
|
1621
|
+
|
|
1622
|
+
/** 在给定目录跑一条外部命令(shell 执行,继承 stdio 让用户看到进度)。失败抛错。 */
|
|
1623
|
+
function runBuildCommand(cmd, cwd, label) {
|
|
1624
|
+
console.log(`· ${label}: ${cmd}`);
|
|
1625
|
+
const res = spawnSync(cmd, {
|
|
1626
|
+
cwd,
|
|
1627
|
+
stdio: "inherit",
|
|
1628
|
+
shell: true,
|
|
1629
|
+
timeout: 600_000,
|
|
1630
|
+
env: { ...process.env, npm_config_audit: "false", npm_config_fund: "false", NO_COLOR: "1" },
|
|
1631
|
+
});
|
|
1632
|
+
if (res.status !== 0) throw new Error(`${label} 失败(退出码 ${res.status ?? "?"}):${cmd}`);
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
/**
|
|
1636
|
+
* 隔离构建:把插件源码拷进临时目录 → 只在那里安装声明的构建依赖(默认
|
|
1637
|
+
* npm install --ignore-scripts:不执行任意生命周期脚本)→ 执行声明的构建命令 →
|
|
1638
|
+
* 校验产物齐全 → 返回可安装的目录。
|
|
1639
|
+
*
|
|
1640
|
+
* 全程不碰目标目录:构建失败时上一版插件原样还在(替换只在构建成功后才发生)。
|
|
1641
|
+
*/
|
|
1642
|
+
function buildPluginSource(pluginRoot, tmpDir, manifest) {
|
|
1643
|
+
const plan = resolveBuildPlan(pluginRoot, manifest);
|
|
1644
|
+
if (!plan)
|
|
1645
|
+
throw new Error(
|
|
1646
|
+
"插件没有声明构建方式:请在 manifest.json 里加 build.command(或 package.json 的 scripts.build),或去掉 --build",
|
|
1647
|
+
);
|
|
1648
|
+
const buildDir = join(tmpDir, "build");
|
|
1649
|
+
mkdirSync(buildDir, { recursive: true });
|
|
1650
|
+
cpSync(pluginRoot, buildDir, { recursive: true, filter: PLUGIN_COPY_FILTER });
|
|
1651
|
+
runBuildCommand(plan.install, buildDir, "安装构建依赖");
|
|
1652
|
+
runBuildCommand(plan.command, buildDir, "构建");
|
|
1653
|
+
const missing = plan.outputs.filter((o) => !existsSync(join(buildDir, o)));
|
|
1654
|
+
if (missing.length) throw new Error(`构建产物缺失:${missing.join(", ")}(manifest.build.outputs 声明)`);
|
|
1655
|
+
console.log(`· 构建完成,产物齐全:${plan.outputs.join(", ")}`);
|
|
1656
|
+
return buildDir;
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1584
1659
|
async function pluginInstallCmd(argv) {
|
|
1585
1660
|
const { opts, positionals } = parseFlags(argv);
|
|
1586
1661
|
if (opts.help) {
|
|
@@ -1588,7 +1663,7 @@ async function pluginInstallCmd(argv) {
|
|
|
1588
1663
|
return;
|
|
1589
1664
|
}
|
|
1590
1665
|
if (positionals.length !== 1)
|
|
1591
|
-
fail(`用法: pi-web-ui install <源> [--name <id>] [--data-dir <dir>] [--force]\n${PLUGIN_HELP}`);
|
|
1666
|
+
fail(`用法: pi-web-ui install <源> [--name <id>] [--data-dir <dir>] [--force] [--build]\n${PLUGIN_HELP}`);
|
|
1592
1667
|
const rawSpec = positionals[0];
|
|
1593
1668
|
const pluginsDir = join(pluginDataDir(opts), "plugins");
|
|
1594
1669
|
// 本地目录直接装(离线开发调试),否则从 GitHub 拉取
|
|
@@ -1614,6 +1689,24 @@ async function pluginInstallCmd(argv) {
|
|
|
1614
1689
|
} catch (err) {
|
|
1615
1690
|
fail(`manifest.json 不是合法 JSON:${err?.message ?? err}`);
|
|
1616
1691
|
}
|
|
1692
|
+
// 源码构建(--build,issue #150):在临时目录里装依赖 + 编译,成功后才进入
|
|
1693
|
+
// 覆盖流程——构建失败 = 目标目录完全没被动过(上一版插件照常可用)。
|
|
1694
|
+
let installRoot = pluginRoot;
|
|
1695
|
+
if (opts.build) {
|
|
1696
|
+
try {
|
|
1697
|
+
installRoot = buildPluginSource(pluginRoot, tmp, manifest);
|
|
1698
|
+
} catch (err) {
|
|
1699
|
+
process.exitCode = 1;
|
|
1700
|
+
console.error(`✖ ${err?.message ?? err}`);
|
|
1701
|
+
return;
|
|
1702
|
+
}
|
|
1703
|
+
} else if (
|
|
1704
|
+
resolveBuildPlan(pluginRoot, manifest) &&
|
|
1705
|
+
!existsSync(join(pluginRoot, "index.mjs")) &&
|
|
1706
|
+
!existsSync(join(pluginRoot, "client", "entry.mjs"))
|
|
1707
|
+
) {
|
|
1708
|
+
console.log("· 这个插件声明了构建、但目录里还没有产物:加 --build 可在安装时构建");
|
|
1709
|
+
}
|
|
1617
1710
|
// 默认 id:子目录名 > 仓库名 > 本地目录名
|
|
1618
1711
|
const sourceName = src?.subpath ? src.subpath.split("/").pop() : (src?.repo ?? localCandidate.split(/[\\/]/).pop());
|
|
1619
1712
|
const fallbackId =
|
|
@@ -1640,10 +1733,7 @@ async function pluginInstallCmd(argv) {
|
|
|
1640
1733
|
}
|
|
1641
1734
|
mkdirSync(target, { recursive: true });
|
|
1642
1735
|
try {
|
|
1643
|
-
cpSync(
|
|
1644
|
-
recursive: true,
|
|
1645
|
-
filter: (s) => !/(^|[\\/])(\.git|node_modules)([\\/]|$)/.test(s),
|
|
1646
|
-
});
|
|
1736
|
+
cpSync(installRoot, target, { recursive: true, filter: PLUGIN_COPY_FILTER });
|
|
1647
1737
|
} catch (err) {
|
|
1648
1738
|
// 拷贝失败 → 有备份则自动回滚,保持旧版本可用
|
|
1649
1739
|
if (backupTs && restorePluginBackup(pluginDataDir(opts), id)) {
|