pi-web-ui 0.96.1 → 0.98.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/CHANGELOG.md +144 -2
- package/README.md +29 -3
- package/README.zh-CN.md +28 -3
- package/bin/pi-web-ui.mjs +75 -25
- package/dist/server/agent-service.js +1829 -262
- package/dist/server/approval-rules.js +5 -10
- package/dist/server/atomic-file.js +40 -0
- package/dist/server/attachments.js +22 -0
- package/dist/server/claim-store.js +5 -11
- package/dist/server/client-state.js +296 -11
- package/dist/server/compact-context-tool.js +3 -1
- package/dist/server/compacted-history.js +148 -0
- package/dist/server/composer-drafts.js +4 -6
- package/dist/server/control-socket.js +0 -40
- package/dist/server/delegate-mode.js +143 -0
- package/dist/server/delegate-task.js +10 -10
- package/dist/server/dsh/dsh-agent-service.js +66 -31
- package/dist/server/dsh/dsh-client.js +48 -10
- package/dist/server/dsh/dsh-serialize.js +12 -10
- package/dist/server/dsh/dsh-sessions.js +20 -6
- package/dist/server/dsh/runtime/goal-rpc.mjs +14 -1
- package/dist/server/dsh/runtime/runtime-root.mjs +1 -1
- package/dist/server/ensure-bash.js +3 -5
- package/dist/server/eval-tool.js +11 -5
- package/dist/server/goal-review-gate.js +250 -0
- package/dist/server/goal-service.js +1272 -448
- package/dist/server/index.js +118 -16
- package/dist/server/launch-origin.js +0 -4
- package/dist/server/locales.js +3 -5
- package/dist/server/lsp-tool.js +123 -24
- package/dist/server/markers/builtins/notify.js +4 -2
- package/dist/server/markers/builtins/rename.js +0 -1
- package/dist/server/markers/builtins/todo.js +6 -28
- package/dist/server/markers/marker.js +0 -15
- package/dist/server/markers/registry.js +0 -3
- package/dist/server/markers/store.js +0 -3
- package/dist/server/mcp-bridge.js +13 -0
- package/dist/server/mcp-hot-reload.js +12 -5
- package/dist/server/model-admin.js +19 -18
- package/dist/server/model-enrich.js +0 -3
- package/dist/server/patch-tool.js +22 -6
- package/dist/server/patch-turn-end-boundary.js +77 -0
- package/dist/server/permission-preset.js +69 -0
- package/dist/server/plan-manager.js +150 -5
- package/dist/server/plan-mode.js +414 -0
- package/dist/server/plugin-api-catalog.js +2 -0
- package/dist/server/plugin-catalog.js +6 -6
- package/dist/server/plugin-dom.js +3 -4
- package/dist/server/plugin-facilities.js +39 -8
- package/dist/server/plugin-grants.js +5 -8
- package/dist/server/plugin-permissions.js +4 -6
- package/dist/server/plugin-schedule.js +4 -7
- package/dist/server/plugin-updater.js +151 -38
- package/dist/server/plugins.js +22 -5
- package/dist/server/prompt-composer.js +12 -3
- package/dist/server/protocol-version.js +1 -1
- package/dist/server/resolve-global-sdk.js +8 -0
- package/dist/server/schedule-agent-tool.js +5 -1
- package/dist/server/scheduler-tasks.js +46 -27
- package/dist/server/sdk-origin.js +27 -0
- package/dist/server/serialize.js +33 -9
- package/dist/server/settings-service.js +18 -6
- package/dist/server/slash-commands.js +16 -0
- package/dist/server/subagent-templates.js +4 -10
- package/dist/server/subagents.js +379 -416
- package/dist/server/terminals.js +11 -6
- package/dist/server/text-quote.js +59 -0
- package/dist/server/text-sniff.js +15 -9
- package/dist/server/tool-manager.js +60 -7
- package/dist/server/tool-overrides.js +24 -0
- package/dist/server/update-check.js +80 -28
- package/dist/server/wait-subscription-scan.js +5 -1
- package/extensions/webui.ts +15 -1
- package/package.json +33 -21
- package/plugin-sdk/client-utils.mjs +92 -0
- package/plugin-sdk/index.d.ts +21 -0
- package/plugin-sdk/index.mjs +2 -0
- package/plugins/catalog.json +7 -0
- package/themes/zhupi-dark.css +5 -30
- package/themes/zhupi.css +5 -30
- package/web/dist/assets/{TerminalPanel-O3GyFIGG.js → TerminalPanel-C88r3r3Q.js} +1 -1
- package/web/dist/assets/index-2jDtjHvW.css +1 -0
- package/web/dist/assets/index-BjdK7s1M.js +368 -0
- package/web/dist/index.html +2 -2
- package/web/dist/sw.js +2 -1
- package/web/public/sw.js +2 -1
- package/web/dist/assets/index-B7Zp23Vc.css +0 -1
- package/web/dist/assets/index-ByfoaDwp.js +0 -366
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,142 @@
|
|
|
10
10
|
|
|
11
11
|
## [Unreleased]
|
|
12
12
|
|
|
13
|
+
暂无未发布内容。
|
|
14
|
+
|
|
15
|
+
## [0.98.0] — 2026-10-03
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- **聊天内容引用(#430)** —— 选中消息里的文字或代码,点浮动按钮即可把原文作为引用卡片加进输入框:支持多条引用、展开查看、逐条移除与重复去重,发送后能在历史消息里回看,编辑重问时保留。标准引擎与 DSH 引擎的消息序列化、草稿恢复、流式提问都已适配。回归:`quote-selection.test.ts`、`text-quote.test.ts`、`dsh-edit-quotes.test.ts`、`quote-selection-ui-test.mjs`。
|
|
20
|
+
- **侧边图标停靠栏改为布局内槽位 + 拖拽式图标编辑模式(#443)** —— `SideDock` 不再是 `position:fixed` 贴边浮层,而是 `.layout` 里的 flex 子项:面板与主区主动向它让出宽度,不再压住面板里的按钮;该侧没有图标时整条不渲染(连宽度都不占)。想要旧行为可在「设置 → 界面布局 → 侧边图标悬浮显示」(`uiLayout.sideDockFloat`)打开。新增**图标编辑模式**(顶栏「⋯」→「编辑图标」):用 pointer 事件把顶栏、底栏、左右侧边栏四个栏与「待放回」托盘里的条目直接拖进同一份 `uiLayout`(order/align/slots/hidden),与设置页布局互通;「⋯」溢出菜单因此改为顶栏有任意条目时常驻(否则进不去编辑面板)。回归:`ui-layout-edit.test.ts`、`host-icon.test.ts`、`ui-layout-ui-test`(真 Chrome)。
|
|
21
|
+
- **顶栏/底栏条目可移动到上下左右 + 全屏预览增强** —— 条目右键菜单新增「移至顶部 / 底部 / 左侧 / 右侧」,左右侧边停靠栏可折叠展开,偏好持久化;文件全屏预览同步增强。
|
|
22
|
+
- **子代理 `wait_all` 阻塞上限可配(#449)** —— 上限从硬编码 60 秒提升到 0.8×看门狗(默认约 16 分钟),可用 `PI_WEB_SUBAGENT_WAIT_CAP_SECONDS` 覆盖:长子代理任务不再被迫每 60 秒重发一次 `wait_all` 烧轮次。
|
|
23
|
+
- **任务计划跨重启/跨刷新持久化** —— 计划不再只活在内存:按 `sessionId` 落盘到 `<dataDir>/plans.json`,并随会话转录(`plan/update` custom entry)一起走;重建会话时按「落盘文件 → 转录 → 历史 `plan_update` 工具调用」三重兼容顺序回放,旧会话也能找回看板。回归:`plan-state.test.ts`。
|
|
24
|
+
- **任务看板折叠态精简为单行状态条** —— 折叠时只留「标题 + 进度徽标 + 当前步骤」,进度以底边 2px 细线呈现(零额外高度);「净室执行 / 复制 Markdown / 清空」收进展开态,「开始实施」在两种状态都可达。
|
|
25
|
+
- **`ask_user_question` 提问框可折叠(#480)** —— 模型提问卡支持折叠/展开,长问卷不再长期占满消息区。
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
|
|
29
|
+
- **系统提示词不再吞掉其他扩展的增补(#455)** —— 自定义了提示词模板/覆盖、或使用非 standard 预设、或禁用了工具时,内置的提示词组装曾整体替换该轮系统提示词,把别的扩展前置/后置注入的内容(如 `<invoked_skill>` 块)一起丢掉。现在会先把扩展的首尾增补摘出来,渲染完再原样套回去;子代理模板 replace 同样不再丢掉前置增补。
|
|
30
|
+
- **计划模式只读闸门堵住成体系旁路(#436)** —— 六处旁路一次性收口:`terminal_input`/`terminal_key` 从计划模式工具集里整条剔除(PTY 行编辑缓冲在 shell 进程里,无法在服务端可靠镜像,只查缓冲会同时误伤与漏放);`eval` 补进计划 / 审查者 / 目标审查三道闸门的封禁名单(它是能直接落盘的写路径);`BYPASS_TOOLS` / `DISPATCH_TOOLS` 里那批**根本不存在**的工具名(`spawn`/`subagent_spawn`/`schedule_agent`/`set_plan_mode` 等)换成真实注册名,并抽成共享的 `SESSION_DISPATCH_TOOLS`(`delegate_task` + 真实存在的 `schedule_task`),三处不再各自漂移;`subagent` 只放行 `get_result`/`list`/`templates`,`steer`/`stop`/`wait_all`/`handoff` 与未知/缺失 action 一律按旁路拒绝(`steer` 最狠:能把写指令注进一个 `planMode=false`、工具齐全的会话);打开历史会话 / 派生分支 / 冷启动续会话三条注册路径补上 `applyToolGating` 重放,计划模式不再「闸门生效但写工具仍在模型视野里」。回归:`plan-mode` 相关单测。
|
|
31
|
+
- **计划模式有了可靠退出路径(#435)** —— 看板为空时整个 UI 曾无出口(开关已从输入框移除,唯一出口是看板里的「开始实施」)。现在空计划也能从目标条/看板退出计划模式,且状态随转录持久化后仍能正确恢复。
|
|
32
|
+
- **长驻服务内存单调上涨(#440)** —— `sessionFileCache` 原本是无上限的静态 Map,值里还带着整份转录文本(`allMessagesText`,长会话可达数 MB),删掉/改名/轮转出去的转录永远留在内存里。现在列表缓存改成 LRU 上限 512 条且只存元数据;全文搜索改为按需加载到独立小缓存(256 条、30 秒 TTL、单条 256K 字符上限),删除/改名失效与 200 候选的列表语义保持不变。
|
|
33
|
+
- **`client-state.json` 膨胀与界面冻结(#441)** —— 死 `clientId` 键(来自 sessionStorage,每开一个标签页就多一个)此前永不清理,文件与 `save()` 的同步序列化开销线性增长;`getRecentProjects()` 又会在截断到 30 条**之前**对每条合并路径做同步 `existsSync`,Windows 上一个断连网络盘就能把事件循环卡住数秒、冻住所有客户端的 WS/HTTP。现在加载时做保守清扫(30 天不活跃或超出 50 条上限才淘汰,`__settings__` 永不淘汰),探测改成先排序截断再用 `fsPromises.access` 并发探活,`discoverRecentProjectsFromDisk` 的三处同步调用一并异步化。
|
|
34
|
+
- **扁平布局下历史会话列表串项目(#438)** —— 设了 `PI_CODING_AGENT_SESSION_DIR` 时 pi 把各项目转录平铺在会话根目录(cwd 是文件内字段),但快路径从未按 cwd 过滤,导致 A 项目的历史面板里混进 B/C 项目的会话、点一下整个工作区就被切走。现在按全仓统一的 `normalizePathKey` 过滤,并且 200 条名额在**过滤之后**分配——别的项目不再把当前项目挤出自己的列表。
|
|
35
|
+
- **会话缓存首帧种子不校验 cwd(#439)** —— localStorage 里的会话缓存与快照守卫不看 cwd,刷新或切项目时会先显示上一个项目的会话;删除/重命名也不跨标签页失效。
|
|
36
|
+
- **LSP 截断口径如实上报(#447)** —— `cascade` 超 25 个引用方时静默截断且谎报完整,现在文本头与 `details` 都报真实总数并补「还有 N 个未分析」尾注;`documentSymbol` 改为按**展平后**的符号数截断(与文本大纲口径一致,截断的父节点连子树一起去掉),并加序列化体积预算,超大 `.d.ts` / protobuf 文件不再因为整条 `details` 超 64KB 被整块丢弃。
|
|
37
|
+
- **WS 半开连接与背压(#460)** —— 补上 ping/pong 半开连接回收;heartbeat / notice / 调度广播不再绕过 `bufferedAmount` 背压直写死 socket。
|
|
38
|
+
- **迟到的异步副作用复活(#461)** —— 反激活或连接失败后,迟到的异步回调曾把已该结束的东西重新拉起来:vscode-editor 留下僵尸 `connecting`、db-client 工具复活、webmail 泄漏 IMAP 连接、dsh-client 在 close 之后重启运行时。
|
|
39
|
+
- **`lsp`/`patch` 文案未接多语言(#463)** —— 工具返回文案恒英文、scheduler 校验错误中文直通,统一改走 `pick()`。
|
|
40
|
+
- **DSH 视觉附件链路无上限(#467)** —— 视觉附件链路补体量上限与 stdin 背压;`dsh-sessions` 的 zstd 魔数切帧误命中导致的静默丢事件一并修掉。
|
|
41
|
+
- **`ChatInput` 发送按钮白名单漏附件类型(#493)** —— `canSubmit` 漏了 `reference`/`lines`/`page` 附件:纯引用消息发送按钮恒灰但回车照发,流式胶囊也不出现。
|
|
42
|
+
- **后台问卷横幅「切入会话」永久消失(#494)** —— 它复用了 `onClose`,把 `questionId` 记进「已关闭」集合,未作答切走后就再也收不到常驻提醒。
|
|
43
|
+
- **插件目录授权并发覆盖(#495)** —— confirm 没有 busy 守卫,并发授权第二个覆盖第一个,先到的 Promise 永不 settle、悬挂整条 `openSession` 链。
|
|
44
|
+
- **@ 补全与全局搜索抢通道(#496)** —— 两者共用 `chat.fileSearch` 但 `reqId` 编号空间重叠,并发时结果交叉串 feed。
|
|
45
|
+
- **@ 补全浮层词元坐标失效(#442)** —— 光标移动或程序化改文后旧坐标仍被使用,接受补全会把正文拼接写坏。
|
|
46
|
+
- **纯附件提问「直接重问」无效(#443)** —— 只贴图不写字的提问上按钮可见但点击静默无效。
|
|
47
|
+
- **PWA 旧缓存永不淘汰(#504)** —— Service Worker 静态缓存名硬编码 `v1` 且 activate 只清异名缓存,升级后旧 hash 资产永久堆积。
|
|
48
|
+
- **受限网络下安装白等 7.5 分钟(#529)** —— 插件安装的探测/安装阶段补超时与快速失败。
|
|
49
|
+
- **语音输入两处(#445, #446)** —— auto 引擎下本地「超 8 分钟 413 / 忙 429」的前置拦截直接抛出、绕过远端兜底;Whisper 安装轮询器用全局 overlay 判活,可跨浮层存活并在完成回调里无条件 `startRecorderFlow`,劫持或双重开启录音。
|
|
50
|
+
- **vscode-editor「ssh config 自动加载」全链路失效(#429)** —— `loadFile` 作用域错误(ReferenceError 被静默吞掉),并加固了日志。
|
|
51
|
+
- **插件目录非中文语言下恒中文(#505, #432)** —— `catalog.json` 补 `nameEn` 字段并接进 schema 白名单与市场渲染,非 zh 语言不再恒显示中文名。
|
|
52
|
+
- **语言包「中文假翻译」(#502)** —— de/es/fr/it/ko/ru 六个包里各约 130 条的值是中文原文,而 pack 值优先于 en 回落,等于系统性地把中文当成译文发出去;逐条修正并补上文档(#478:README 工具清单、`wait_all` 描述、6 个环境变量、serverStrings 的 ja/pt 缺口)。
|
|
53
|
+
- **db-client 两处硬伤(#458)** —— 三家 SQL 长连接没有 `error` 监听,一个连接错误就能崩掉主进程;Mongo 的保存/删除 100% 抛 `ObjectId` ReferenceError(从未可用)。回归:`tests/unit`。
|
|
54
|
+
- **DSH 提问桥超时后队列永久停摆(#459)** —— `goal-rpc` 的 pending 超时后排队中的提问 Promise 永不 settle,agent 卡死在工具调用上;超时路径现在统一 settle 并派发下一个。
|
|
55
|
+
- **legado 规则引擎启动超时后永久瘫痪(#466)** —— worker 启动超时后 `ready` 永久 rejected,桥再也起不来;现在会复位 worker。`/store`、`/proxy` 的 `readRawBody` 补 10MB 上限。
|
|
56
|
+
- **`forceReset` 后事件订阅不重挂 / 幽灵重建(#484, #485)** —— 重建非活跃会话(子代理/角色对话)后事件订阅没挂回去,表现为快照冻结、看门狗失聪、`turnEndWaiters` 死等;强行关闭与 `forceReset` 交错还会把已移除会话幽灵重建,泄漏无主 runtime 与扩展宿主子进程。
|
|
57
|
+
- **模型列表刷新回滚并发编辑(#486)** —— `refreshProviderModels` 的读-改-写没有互斥,probe 的网络窗口里用户对同一服务商的编辑会被旧快照静默回滚;现在以盘上最新条目为合并基准。
|
|
58
|
+
- **abort/移除会话不结算挂起审批(#487)** —— `pendingQuestions`/`pendingApprovals` 里的悬挂条目会让失联检测被永久豁免,弹窗还会跨轮残留;现在就地结算。
|
|
59
|
+
- **`wait_all` 把「已移出」当「仍在运行」(#488)** —— 等待集合里含被移出的子代理时必然白等满 cap(#449 的放大);现在视为终态。
|
|
60
|
+
- **插件 reload 让剩余守卫整体跳过(#489)** —— reload/uninstall 原地清空 `loaded` Map,`evaluateToolPre` 的迭代器静默终止,剩余插件的 pre 守卫整体 fail-open;改为对快照迭代。
|
|
61
|
+
- **目标重设污染新目标(#490)** —— `dispatchExecutor` spawn 后没有代次复检就写回老执行者,`deliverAndWait` 登记 `verdictWaiter` 前也有 `await` 窗口;两处都补上复检。
|
|
62
|
+
- **可崩全服的 `unhandledRejection`(#491)** —— dispatch 的 `queue_remove` 是唯一缺 `void` 的 async 调用,且 `removeQueued` 内 `clearQueue` 无 try;现已消除该点。
|
|
63
|
+
- **新对话窗口静默吞消息(#492)** —— 新对话 displacement 与 runtime 创建窗口重叠时 `prompt` 被 abort 静默丢弃(无 notice、草稿已清),现在补「消息未送达」回执。
|
|
64
|
+
- **`mcp.json` 读失败被当空配置(#497)** —— EBUSY/EACCES 被当成空配置,会把在跑的 MCP 服务器全杀掉并推「热加载成功 0 个」的误导通知;现在按坏配置口径保留在跑的服务器,仅 ENOENT 视为删除。
|
|
65
|
+
- **markers 三处(#499)** —— `todo dep` 英文反馈方向颠倒、`new` 主题含逗号被静默截断、notify guidance 宣称协议里并不存在的 `success` 级别。
|
|
66
|
+
- **session-migrate 可伪造官方会话(#500)** —— fallback 导入没有来源标记,能伪造出看起来像官方的会话记录,且插件内直写会话库绕过了 outside-workspace 审批;现在补 `[imported-from]` 提示与会话头来源元数据。
|
|
67
|
+
- **内置预览页可被注入(#501)** —— live-preview 的 Markdown 链接/图片替换没有 scheme 白名单,`javascript:` href 可注入同源预览页。
|
|
68
|
+
- **直启路径端口校验(#506)** —— `--port abc` 曾静默绑随机端口并打印 `http://localhost:NaN`,现在 fail-fast。
|
|
69
|
+
- **只读 `bash` 白名单的 `find` 旁路(#483)** —— 白名单命中不等于只读:`find` 的 `-exec`/`-delete`/`-fprintf` 族现在按写副作用拒绝,一条常规 `find` 语法不再能绕过计划模式的三道只读闸门。
|
|
70
|
+
- **@ 引用正文写相对路径(#532)** —— 引用正文改用相对路径,同名文件/文件夹在输入框与聊天记录里可区分;退格整块删除与 chip 的 ✕ 反查口径同步对齐。
|
|
71
|
+
- **Windows 更新面板缺 `pi-core` 行(#533)** —— `readPiCoreVersionFromDisk()` 只认 npm 的 POSIX 布局,Windows 下 cmd-shim 是普通脚本、`realpathSync` 原样返回,向上找 `package.json` 永远找不到;现在先试 `<bin 同级>/node_modules/<pkg>/package.json`,探测不到时也保留该行并诚实标注「无法检测」而不是假装不存在。
|
|
72
|
+
- **Windows `server install` 生成的 VBS 无效(#534)** —— PowerShell 7 的 `C:\Program Files\PowerShell\7\pwsh.exe` 含空格却没加引号,Windows Script Host 报 `80070002`,登录自启与桌面快捷方式都起不来。
|
|
73
|
+
- **已钉住的对话跨服务重启持久化(#433)** —— 钉住状态持久化至 `client-state.json`,服务重启后自动恢复常驻运行列表,历史会话列表同步展示 📌 置顶标记并支持右键切换钉住状态。
|
|
74
|
+
- **计划模式净室执行(Clean Handoff)原子化(#434)** —— 修复净室执行前端四发消息由于服务端异步分发导致的竞态,提供原子协议通道 `plan_clean_handoff`,确保旧会话计划闸门关闭、新隔离会话原子就绪并承接计划步骤与目标后再发起实施。
|
|
75
|
+
- **压缩历史展开区只读防护与序号对齐(#437)** —— 压缩历史展开视窗右键菜单禁用派生分支、回滚与生成长图等写/非会话操作,序列化复用全局序号生成器,杜绝序号重计与碰撞。
|
|
76
|
+
- **PlanBoard 任务看板并发编辑防范(#444)** —— 看板步骤状态切换、内容修改、增量新增与删除改走单步骤级增量协议(`plan_step_update`、`plan_step_delete`、`plan_step_add`),解决快速连点与模型流式更新互相整组清改问题,前端即时响应 `plan_updated` 广播。
|
|
77
|
+
- **多轮压缩历史折叠卡片链式展示与错误本地化(#448)** —— 多次压缩的会话按分支链完整展示前序历史压缩卡片,各卡片按链式切分边界独立展开早期折叠内容;优化压缩记录未找到时的错误提示并接入多语言。
|
|
78
|
+
- **第三方 subagent 扩展同名工具透传支持(#481)** —— 修复内置 `subagent` 工具与第三方扩展(如 `pi-subagents`)同名冲突问题;当第三方扩展注册了同名工具且内置 `subagent` 被关闭/禁用时,自动透传扩展工具,不再强制顶替或剔除。
|
|
79
|
+
- **作为 Pi 扩展包安装时宿主 SDK 识别与加载器告警修复(#482)** —— 声明可选 `peerDependencies` 解决扩展加载器告警;`/webui` 子进程显式透传宿主 SDK 路径并加载 `resolve-global-sdk` 钩子,使服务准确跟随宿主 SDK 版本。
|
|
80
|
+
- **恢复模型管理中的第二把密钥与刷新模型功能(#456)** —— 恢复模型管理面板中克隆内置提供商添加第二把密钥与刷新已存提供商模型列表的功能与按钮。
|
|
81
|
+
- **防止会话交接记录与展开历史无界内存增长(#465)** —— `subagentHandoffs` 增加上限容量保护,前端被折叠压缩历史展开缓存改用 LRU 淘汰机制,防止长时间会话内存泄漏。
|
|
82
|
+
- **目标审查循环状态机健壮性强化(#464, #457)** —— `goal_ask` 超时定时器保证在 `finally` 中清理,避免残留定时器误杀后续调研;`stopDelegated` 异步等待执行者完全移出,消除容量误判;会话过户时主动唤醒等待者并清理目标审查阻塞态。
|
|
83
|
+
- **工具参数 TypeBox 约束与运行时一致性校准(#462)** —— `eval` 工具在执行 TypeScript 代码时启用原生类型剥离(`--experimental-strip-types`);`lsp`、`terminals`、`compact_context`、`patch` 工具参数范围严格钳制与上下限校准。
|
|
84
|
+
- **插件更新检查不再误报** —— 版本号相同就绝不报更新(即使 monorepo 仓库 HEAD SHA 变化);子目录源插件抓不到远端清单时明确报「无法检查(未能获取远端插件清单)」,不再拿仓库根 SHA 当版本号;随包(`pkgRoot`)插件版本可离线直读。CLI `pi-web-ui check-updates` 同步改为按版本号(而非 SHA)展示「已装 → 远端」差距。安装/卸载插件完成后立即重算更新状态,不必等下次轮询。
|
|
85
|
+
- **已结束的对话过户到另一处后可直接继续(#484)** —— 过户只换页面持有者、不重建 runtime,但新页面的会话订阅此前没挂回去,导致过户后发消息收不到回复(必须刷新页面)。现在过户尾部显式重挂事件订阅。回归:`idle-takeover-test`。
|
|
86
|
+
|
|
87
|
+
### Changed
|
|
88
|
+
|
|
89
|
+
- **清掉 103 个没人引用的 i18n key(#472)** —— 前端字典里积了一批没人读的翻译(旧模型管理 UI 残留 54 个、旧语言选择键 10 个、提示词/工具开关重构残留等),白白拖着 8 个语言包一起翻译与审阅。现新增守卫 `tests/unit/i18n-dead-keys.test.ts`(随 `npx vitest run` 进 CI):字典里出现全仓零引用的 key 就直接红,并列出清单。模板拼接的动态 key(`thinking.*` / `promptTok_*`)已显式白名单。
|
|
90
|
+
- **服务端与前端死码清理(#468, #473)** —— 服务端:删掉协议死链(`cycle_model`/`cycle_thinking`、上行 `subagent_handoff`)、`sendControlCommand` 死副本、16 个零引用导出与一批死设置类型;`plugin_job_cancel` 反向补上 UI 入口(设置页作业状态行加「取消」),插件安装/更新这类长任务终于能中止。前端:删掉 11 个只被自己单测养活的 hook/util(约 1000 行)、7 个全死导出、`ui-slots` 三条「可拖可隐藏但对渲染毫无影响」的假条目(`host:lp-projects/lp-running/lp-history`),184 个仅本文件使用的导出去掉 `export`。
|
|
91
|
+
- **基础工具层统一、目录选择器同构合并、复制与格式化组件抽离(#470, #474, #475, #476, #477)** —— 服务端路径归一化 6 套语义分派、手抄 15+ 处的原子写、三份 TTL memoize 收敛为一套;`FooterBar` 与 `ProjectPicker` 复刻的两套目录浏览器(约 250 行同构)合并;`SettingsModal` 6 个逐行同构的提示词覆盖区块与两套 list+modal 编辑器合并;前端复制反馈 8 处内联、`formatSize` 6 份、可折叠区块头 3 份抽成共享组件;11 个插件里逐字复刻的 `esc`/`hostApi`/`apiBase` 等 client utils 下沉到 plugin-sdk 共享层。
|
|
92
|
+
- **构建期依赖归类与产物守卫(#468)** —— 16 个只被 `web/src` 用、随 vite 打进 `web/dist` 的包移入 devDependencies(npm 用户的安装体积与供应链面纯收益);探针从 `server/dsh/` 挪到 `dev/dsh-probes/`(`dev/` 不进 npm 包);新增 `.gitattributes` 与 CI 步骤「重新生成插件 bundle / vendor 后 `git diff --exit-code`」,改了 `src/` 忘了重新生成会被当场拦下。
|
|
93
|
+
- **CI 与 lint 覆盖补口(#479, #503)** —— `lint`/`lint:fix` 纳入 `bin/`(4400 行主入口不再零 lint 防护);`check:protocol` 补 git 启发式:`protocol.ts` 相对上一个 tag 有 diff 而 `PROTOCOL_VERSION` 未 bump 即报错。
|
|
94
|
+
- **`release-notes.mjs --write-changelog` 替换串改函数形式(#498, #515)** —— i18n 文案含 `$&`、`` $` `` 等替换模式时不再静默写坏 `CHANGELOG.md`。
|
|
95
|
+
|
|
96
|
+
<!-- auto-i18n:start -->
|
|
97
|
+
### i18n
|
|
98
|
+
|
|
99
|
+
- 前端新增 key(26):`openInNewTab`、`clickToCloseImage`、`compactedHistoryNotFound`、`uiLayoutSideDockFloat`、`uiIconEdit`、`uiIconEditTitle`、`uiIconEditHint`、`uiIconEditTray`、`uiIconEditDropHere`、`uiLayoutSidebarLeft`、`uiLayoutSidebarRight`、`uiLayoutPosition`、`uiLayoutPosTop`、`uiLayoutPosBottom`、`uiLayoutPosLeft`、`uiLayoutPosRight`、`moveToTop`、`moveToBottom`、`moveToLeft`、`moveToRight`、`sideDockCollapse`、`sideDockExpand`、`quoteSelection`、`quoteText`、`quoteSource`、`removeQuote`
|
|
100
|
+
- 前端删除 key(103):`langZh`、`langEn`、`langIt`、`langJa`、`langKo`、`langFr`、`langDe`、`langEs`、`langRu`、`langPt`、`manageModelsTitle`、`setGlobalDefault`、`clearGlobalDefault`、`globalDefaultBadge`、`slashHelpHint`、`renameSessionConfirm`、`dismissConversationWithSubagents`、`dismissConversationWithSubagentsMixed`、`toolApprovalCommand`、`toolApprovalReason`、`approvalRuleResetConfirm`、`approvalRuleDeleteConfirm`、`planBoardSteps`、`planBoardProgress`、`planBoardNoPlan`、`editHint`、`updateTip`、`pluginAllUpToDate`、`fileSaved`、`dshVisionHiddenNote`、`questionNavTip`、`planModeTipOn`、`scmQueryFailed`、`scmTooManyFailures`、`editProvider`、`builtinProviders`、`hintKeyOnly`、`providerAuthHint`、`keyReady`、`replaceKey`、`replaceKeyTitle`、`cloneProviderTitle`、`pasteKey`、`providerIdPlaceholder`、`baseUrlExamplePh`、`saveAllBatch`、`advancedEdit`、`batchCreateProviders`、`secondKeyTitle`、`noBaseUrlShort`、`providerNameLabel`、`providerNameHint`、`apiKeyLabel`、`secondKeyPlaceholder`、`batchDesc`、`batchKeyLabel`、`modelsCountShort`、`customProviders`、`customDesc`、`noCustomProviders`、`refreshBuiltinCatalog`、`appendModelTitle`、`appendModelIdPh`、`appendModelNamePh`、`appendModelAdd`、`appendModelBusy`、`appendModelCancel`、`appendModelApiTitle`、`appendModelApiAuto`、`appendModelBaseUrlPh`、`modelsCount`、`addProvider`、`modelsTitle`、`modelIdReq`、`textImage`、`maxOutput`、`removeModel`、`addModel`、`fetchModelsErr`、`antigravityTemplateTitle`、`antigravityTemplateDesc`、`antigravityFillOpenAI`、`antigravityFillAnthropic`、`enrichHintPh`、`enrichModelsCancelled`、`goalBarActive`、`goalBarStatusPending`、`goalWizardAnswer`、`promptHistoryCleared`、`settingsPromptMode`、`promptAppendHint`、`promptReplaceHint`、`promptPlaceholder`、`promptReadonlyLockedBadge`、`reviewPromptHint`、`settingsTerminalTools`、`terminalToolsOffHint`、`settingsEditTools`、`visionBridgePromptAppendHint`、`visionBridgePromptReplaceHint`、`dshPresetCurrent`、`schedulerNameLabel`、`schedulerPromptLabel`
|
|
101
|
+
- 前端中文变更(3):`planBoardTitle`、`planImplementBtn`、`planCleanHandoffBtn`
|
|
102
|
+
- 前端英文变更(4):`planBoardTitle`、`planImplementBtn`、`planCleanHandoffBtn`、`browserControlExample2`
|
|
103
|
+
- 服务端新增 key(1):`pluginupdate.subpath.manifest.failed`
|
|
104
|
+
- 服务端文案变更(2):`markers.todo.dep.blocks.updated`、`subagents.wait.pending`
|
|
105
|
+
- 服务端删除 key(1):`markers.todo.list.empty`
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
## [0.97.0] — 2026-09-26
|
|
109
|
+
|
|
110
|
+
### Added
|
|
111
|
+
|
|
112
|
+
- **左侧边栏历史会话置顶(#388)** —— 支持置顶/取消置顶历史会话,置顶项目始终固定在会话历史列表顶端,配合本地客户端状态持久化,多窗口即时同步;优化磁盘扫描算法,仅按需快速探测头部读取元数据,大幅提升大历史会话加载响应速度。回归:`tests/unit/client-state-recent-projects.test.ts`。
|
|
113
|
+
|
|
114
|
+
- **规划门禁与净室交接范式升级(#389)** —— 吸收主流规划与看板范式(DSH / narumitw / plannotator),全面升级「计划与目标」联动架构:
|
|
115
|
+
- **只读规划门禁(Plan Gate)与状态机**:开启计划模式后自动剥离写类工具及旁路工具,物理级杜绝代码倾倒;
|
|
116
|
+
- **净室新会话交接(Clean Handoff)**:支持一键将规划好的目标与步骤交接至全新隔离会话中施工,避免长推理上下文污染;
|
|
117
|
+
- **可视化编辑与导出**:任务看板支持 Markdown 一键复制/导出,步骤支持内联编辑、新增、删除与状态切换;
|
|
118
|
+
- **目标审查深度联动**:审查指令自动附带任务看板步骤推进状态,核验计划真实完成度。
|
|
119
|
+
回归:`tests/plan-mode-test.mjs`、`tests/unit/goal-delegated.test.ts`。
|
|
120
|
+
|
|
121
|
+
- **对话可「钉住」常驻运行列表** —— 左栏「运行的对话」右键新增「钉住」:被钉的对话切到别的对话也不释放运行时(空闲、无存活终端、无后台任务时同样保留,优先级高于「打开未继续即移出」等所有自动规则),直到显式移出或强行关闭;取消钉住后立即恢复原有释放策略。进程内有效、不落盘。回归:`tests/unit/conv-pin.test.ts`、`tests/unit/wait-subscription-scan.test.ts`、`tests/conv-pin-browser-test.mjs`(真浏览器右键 → 钉住/取消钉住 → 切走仍留存 / 对照移出)。
|
|
122
|
+
|
|
123
|
+
- **上下文压缩后支持展开/查看被折叠的历史对话(#398)** —— 严格解耦「UI 展示流」与「LLM 推理视窗」:触发压缩(Compaction)后,在 `CompactionCard` 底部提供「展开查看被折叠的历史」操作栏,按需(On-demand)从会话 DAG 祖先链中还原被该节点折叠的原始历史消息(含提问、回答与工具输出),并以只读流视窗呈现,不占用后续推理 Token,解决长对话截断后无法回溯方案与日志的痛点。回归:`tests/unit/compacted-history.test.ts`。
|
|
124
|
+
|
|
125
|
+
- **粘贴任意文件直接附加** —— 在输入框粘贴从文件管理器复制的文件(文本、PDF、压缩包等)会像拖拽一样变成待发附件,不再只能贴图片:粘贴与拖拽现在共用同一条分流逻辑(图片走视觉管线并保留 text-only 模型的拦截,其余走 fileData 上传,20MB 上限同口径),纯文本粘贴完全不受影响。回归:`tests/unit/clipboard-files.test.ts` + `tests/file-paste-browser-test.mjs`。
|
|
126
|
+
|
|
127
|
+
- **语音输入可主动选识别方式(#383)** —— 麦克风浮层新增常驻的「切换识别方式」面板(浏览器联网识别 / 本地 Whisper / 远端接口),选中即用并写回插件设置,下次点 🎤 直接走这一档;「转写引擎」设为本地或远端时,点 🎤 直接走对应引擎(本地没装就直接弹一键安装,不必再干等浏览器联网失败几秒)。
|
|
128
|
+
|
|
129
|
+
### Fixed
|
|
130
|
+
|
|
131
|
+
- **任务看板 / 目标条窄屏排版协调** — 手机宽度下任务看板的「任务看板」「0/6 (0%)」被当前步骤挤成逐字竖排(行内 flex 子项默认 `min-width: auto` 且允许收缩,CJK 逐字断行),目标条的「最大轮数」标签同样竖排、「锁定:应用到后续所有回合」被挤成两行。现改为:看板标题与计数钉死不收缩、当前步骤 chip 放不下就整块换到第二行(宽屏仍同行);目标条输入框独占一行、按钮组换行右对齐,偏好行允许整块换行、模型长 id 断行不外溢,锁定说明与标签不再被压扁。回归:`tests/unit/text-wrap.test.ts`、`tests/unit/css-tokens.test.ts`、`tests/chat-column-align-test.mjs`。
|
|
132
|
+
- **本地语音运行时「装成功却报没装上」(#383)** —— 修复安装本地 Whisper 后仍报「npm install 没跑通」:Node 的 CJS 解析会把 `package.json` 不存在的负结果缓存整个进程,装完复查永远命中它。依赖探测改为「解析失败再看文件是否已落盘」,插件侧也改为直接按 `package.json` 定位 transformers.js 入口,不再依赖被污染的解析缓存(非重启服务即可恢复)。
|
|
133
|
+
- **模型下载源可配(#383)** —— 新增「模型下载源」设置(也认环境变量 `HF_ENDPOINT`),国内直连 huggingface.co 超时时可填 `https://hf-mirror.com`。
|
|
134
|
+
- **语音浮层计时器泄漏** —— 切换识别方式时上一个浮层的计时器不再残留(此前每切一次泄一个 500ms 定时器)。
|
|
135
|
+
|
|
136
|
+
<!-- auto-i18n:start -->
|
|
137
|
+
|
|
138
|
+
### i18n
|
|
139
|
+
|
|
140
|
+
- 前端新增 key(67):`elsewherePseudoBadge`、`elsewherePseudoTip`、`pinConversation`、`unpinConversation`、`pinnedConversation`、`planImplementBtn`、`planImplementTip`、`planImplementRequest`、`planBoardExportMarkdown`、`planBoardExportSuccess`、`planCleanHandoffBtn`、`planCleanHandoffTip`、`planCleanHandoffPrompt`、`planBoardAddStep`、`planBoardEditStep`、`planBoardDeleteStep`、`planBoardStepTitlePlaceholder`、`planBoardStepDescPlaceholder`、`placeholderPlanMode`、`planModeBadge`、`viewCompactedHistory`、`hideCompactedHistory`、`compactedHistoryLoading`、`compactedHistoryEmpty`、`compactedHistoryBadge`、`compactedHistoryTurns`、`planMode`、`planActionBtn`、`planActionTip`、`planModeTip`、`planModeTipOn`、`goalBarExecModel`、`goalBarExecModelTip`、`goalBarOpenExec`、`goalBarOpenExecTip`、`goalBarStaleBackend`、`goalBarStop`、`goalBarExecuting`、`goalHistory`、`delegateMode`、`delegateModeDesc`、`delegateModeOffHint`、`delegateModeBadge`、`delegateModeBadgeTip`、`delegateModeOpenTip`、`toolCorePowershellDesc`、`toolCoreLsDesc`、`toolCoreGrepDesc`、`toolCoreFindDesc`、`pluginToolsDisabledByPlugin`、`pluginDisabledInPlugins`、`toolDescSubagent`、`planModePromptSettingsTitle`、`planModePromptSettingsDesc`、`planModePromptMode`、`planModePromptPlaceholder`、`planModePromptSettingsHint`、`preset.standard`、`preset.minimal`、`preset.code`、`preset.reader`、`preset.ask`、`preset.standardDesc`、`preset.minimalDesc`、`preset.codeDesc`、`preset.readerDesc`、`preset.askDesc`
|
|
141
|
+
- 前端中文变更(9):`planBoardTitle`、`goalBarPlaceholder`、`goalBarSet`、`goalBarReviewModel`、`goalBarMaxRoundsTip`、`goalWizardBtn`、`toolsCoreHint`、`toolsSubagentDepHint`、`delegateTaskOffHint`
|
|
142
|
+
- 前端英文变更(9):`planBoardTitle`、`goalBarPlaceholder`、`goalBarSet`、`goalBarReviewModel`、`goalBarMaxRoundsTip`、`goalWizardBtn`、`toolsCoreHint`、`toolsSubagentDepHint`、`delegateTaskOffHint`
|
|
143
|
+
- 服务端新增 key(18):`agent.role.stop`、`goal.role.blocked`、`goal.role.conv_title`、`goal.role.exec`、`goal.role.card.start`、`goal.role.card.result`、`goal.role.review`、`goal.role.review.retry`、`subagents.action.missing`、`subagents.spawn.missing.prompt`、`subagents.get.missing.runId`、`subagents.steer.missing.runId`、`subagents.steer.missing.message`、`subagents.stop.missing.runId`、`subagents.wait.item.missing`、`subagents.handoff.missing.toRunId`、`subagents.handoff.missing.payload`、`subagents.action.unknown`
|
|
144
|
+
- 服务端文案变更(10):`delegate.validate.agent`、`delegate.validate.short`、`delegate.started`、`subagents.spawn.template.unavailable`、`subagents.spawn.started`、`subagents.steer.not.found`、`subagents.stop.not.found`、`subagents.wait.empty`、`subagents.templates.list`、`subagents.handoff.not.found`
|
|
145
|
+
- 服务端删除 key(9):`goal.set.kick`、`goal.wizard.kick`、`goal.review.incomplete`、`goal.autonomous.pass`、`goal.review.blocked`、`goal.autonomous.continue`、`goal.review.error`、`goal.review.blocked_msg`、`goal.review.revise`
|
|
146
|
+
|
|
147
|
+
<!-- auto-i18n:end -->
|
|
148
|
+
|
|
13
149
|
## [0.96.1] — 2026-09-26
|
|
14
150
|
|
|
15
151
|
### Fixed
|
|
@@ -24,10 +160,12 @@
|
|
|
24
160
|
- **输入框与顶栏控件排版微调** —— 顶栏 chip 统一幽灵化;输入框底部模型与思考强度 chip 尺寸微调收紧;思考强度 Chip 文案精简为 `{level}`,保留 tooltip 说明;预设选择器图标优化为 `FiSliders`。
|
|
25
161
|
|
|
26
162
|
<!-- auto-i18n:start -->
|
|
163
|
+
|
|
27
164
|
### i18n
|
|
28
165
|
|
|
29
166
|
- 前端中文变更(1):`thinkingChip`
|
|
30
167
|
- 前端英文变更(1):`thinkingChip`
|
|
168
|
+
|
|
31
169
|
<!-- auto-i18n:end -->
|
|
32
170
|
|
|
33
171
|
## [0.96.0] — 2026-09-26
|
|
@@ -52,9 +190,10 @@
|
|
|
52
190
|
### Changed
|
|
53
191
|
|
|
54
192
|
- **输入框 `@` 提及支持技能(skills)自动补全** —— 在消息文本任意位置键入 `@` 或 `@skill:` 即可弹出技能候选列表(名称匹配优先于描述匹配,支持中英文双语描述检索与 `@page` 页面置顶防挤占);点选后自动在光标处插入 `@skill:<name>` 词元,与 Pi 运行时的技能提升扩展无缝联动。
|
|
55
|
-
- **收敛型澄清提问与决策就绪型计划规范(#330)** —— `ask_user_question` 现在单次严格限制 1
|
|
193
|
+
- **收敛型澄清提问与决策就绪型计划规范(#330)** —— `ask_user_question` 现在单次严格限制 1~~3 个问题(优先 1 个,超过 3 个直接报错阻断,防止问卷轰炸),选项 schema 收紧为 2~~4 个互斥选项且推荐方案置顶,选项 description 要求一句话说明影响与权衡;`plan_update` 提示词升级为「决策就绪型」规划:动代码前先在步骤中落实排查发现(Discovery)、受影响文件清单(File Touch List)与风险回滚预案(Rollback),并随执行实时流转步骤状态。目标向导(`goal_ask` / wizardPrompt)与 DSH 澄清提示词同步对齐收敛型交互。
|
|
56
194
|
|
|
57
195
|
<!-- auto-i18n:start -->
|
|
196
|
+
|
|
58
197
|
### i18n
|
|
59
198
|
|
|
60
199
|
- 前端新增 key(49):`reaskDirectly`、`reaskDirectlyTip`、`kindPlugin`、`pluginCheckUpdates`、`pluginCheckUpdatesHint`、`pluginUpdateAvailableBadge`、`pluginUpdateAvailableDetail`、`pluginAllUpToDate`、`piCoreSplitRun`、`piSdkSplitNote`、`piSdkBundledNote`、`installGlobalEngineBtn`、`installGlobalEngineTabTitle`、`saveResultUnknown`、`questionNoneAvailable`、`notifyApprovalTitle`、`notifyApprovalBody`、`notifyApprovalBodyTool`、`sound.approval`、`sound.approval.desc`、`settingsSoundVoice`、`ttsHeader`、`ttsEnable`、`ttsEnableDesc`、`ttsAnnounce`、`ttsAnnounceDesc`、`ttsReadReplies`、`ttsReadRepliesDesc`、`ttsRate`、`ttsVoice`、`ttsVoiceAuto`、`ttsVoiceOnline`、`ttsUnavailable`、`ttsPreviewLine`、`ttsAnnounceDone`、`ttsAnnounceQuestion`、`ttsAnnounceError`、`ttsAnnounceApproval`、`speakMsg`、`stopSpeakingMsg`、`apiKeySavedHint`、`settingsViewPromptTokens`、`settingsPromptContextTotal`、`toolsSectionCore`、`toolsCoreHint`、`toolCoreBashDesc`、`toolCoreReadDesc`、`toolCoreEditDesc`、`toolCoreWriteDesc`
|
|
@@ -63,6 +202,7 @@
|
|
|
63
202
|
- 服务端新增 key(3):`plugincatalog.sync.doc.invalid`、`terminals.bash.nosentinel.note`、`terminals.command.blocked`
|
|
64
203
|
- 服务端文案变更(1):`terminals.bash.timeout`
|
|
65
204
|
- 服务端删除 key(2):`plugincatalog.sync.source.invalid`、`plugincatalog.sync.read.failed`
|
|
205
|
+
|
|
66
206
|
<!-- auto-i18n:end -->
|
|
67
207
|
|
|
68
208
|
## [0.95.0] — 2026-09-24
|
|
@@ -1313,7 +1453,9 @@ when?, children?}`,也收 `topbar` / `settings` 这类简写别名);宿主
|
|
|
1313
1453
|
- 0.35.1(2026-08-27):编辑重问保留附件(#18)+ 全窗口拖放(#19)。
|
|
1314
1454
|
- 0.29.0(2026-08-23):全局搜索弹窗(Ctrl+K)+ 消息列表惰性窗口化。
|
|
1315
1455
|
|
|
1316
|
-
[Unreleased]: https://github.com/xing-shuyin/pi-web-ui/compare/v0.
|
|
1456
|
+
[Unreleased]: https://github.com/xing-shuyin/pi-web-ui/compare/v0.98.0...main
|
|
1457
|
+
[0.98.0]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.98.0
|
|
1458
|
+
[0.97.0]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.97.0
|
|
1317
1459
|
[0.96.1]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.96.1
|
|
1318
1460
|
[0.96.0]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.96.0
|
|
1319
1461
|
[0.95.0]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.95.0
|
package/README.md
CHANGED
|
@@ -103,8 +103,8 @@ QQ群 1126050727
|
|
|
103
103
|
|
|
104
104
|
### 🤖 Subagents & templates
|
|
105
105
|
|
|
106
|
-
- **First-party subagents** — spawn independent background conversations for parallel exploration / implementation / review (`
|
|
107
|
-
- **Subagent templates** — configure reusable presets in Settings → Subagent templates: a role system prompt (append or replace), skills & extensions whitelists, an optional per-template model, and an optional thinking level. The AI picks one via the `
|
|
106
|
+
- **First-party subagents** — spawn independent background conversations for parallel exploration / implementation / review (unified `subagent` tool with `action="spawn"`, with optional `model` override or a template's model); collect results without polling via `subagent` with `action="wait_all"` (blocks up to 60s, capped by watchdog × 0.8 with a 5s lower bound, returning unfinished subagent lists to guide subsequent waits). Manage them like a chat right in the left panel: view live output, inject follow-ups (steer), abort, dismiss — failed runs surface a red dot in the running list and an error notice in the main chat. In-memory sessions — they never touch the history / resume list, and can be nested.
|
|
107
|
+
- **Subagent templates** — configure reusable presets in Settings → Subagent templates: a role system prompt (append or replace), skills & extensions whitelists, an optional per-template model, and an optional thinking level. The AI picks one via the `subagent` tool (`action="templates"`) and `subagent(action="spawn", template="…")`, or spawns without one (default = follow the main conversation's current model **and thinking level**, or the global default subagent model set in the same panel). A template that sets them pins that exact combination (unsupported thinking levels are clamped by the SDK to the nearest one the model supports). Disabled templates stay in the panel for re-enabling but become invisible to the AI tools (can't be listed or picked). Templates are shared globally across browser clients (`<dataDir>/subagent-templates.json`). Thirteen built-in templates seed the list on first run — marked 「Built-in」, editable and deletable like any other: review / implement / research / scout / audit / delegate (adapted from the pi-subagents community projects) and oracle / librarian / explore / metis / momus / multimodal-looker / sisyphus-junior (ported from oh-my-pi's built-in agents).
|
|
108
108
|
|
|
109
109
|
### 🖼️ Files, images & attachments
|
|
110
110
|
|
|
@@ -151,7 +151,7 @@ QQ群 1126050727
|
|
|
151
151
|
|
|
152
152
|
### 🧩 Agent tools & inline markers
|
|
153
153
|
|
|
154
|
-
- **Tool switches** — Settings → Tools lists every optional agent tool as its own switch: the
|
|
154
|
+
- **Tool switches** — Settings → Tools lists every optional agent tool as its own switch: the unified `subagent` tool (default on), and 17 other tools — `edit_soft`, `browser_page`, `eval`, and `lsp` (default off), plus `delegate_task`, `ask_user_question`, `todo_list`, `conversation_read`, `present_files`, `skill`, `schedule_task`, `schedule_list`, `schedule_cancel`, `claim_files`, `plan_update`, `patch`, and `compact_context` (default on). Toggling is live (no reload) and the tools stay registered so they can come back; core built-in tools (`bash`, `read`, `write`, `edit`) are managed in their own Core Tools section.
|
|
155
155
|
- **Inline markers** — instead of a tool round-trip the AI writes state changes straight into its reply: `[[todo:new:<subject>]]` / `[[todo:set:<id>,in_progress]]` / `[[todo:remove:<id>]]` / `[[todo:dep:<id>,blocks=<id>]]` for the task list, `[[notify:<level>:<message>]]` for a non-interruptive notice, and `[[conv:rename:<title>]]` to retitle the chat. Markers are applied as soon as a reply bubble is final, a bad marker comes back as a browser notice, and the task list also renders as a live widget under the file tree (`N/M done` with ✓ / ◐ / ○) that follows the active conversation and survives a reload — it is stored in that conversation's own session branch. Settings → Tools has a master switch plus one switch per marker (these are global, shared by all browsers).
|
|
156
156
|
- **`edit_soft`** — a looser `edit` (default off) for when indentation or whitespace makes the built-in tool fail: exact substring first, then trimmed line-core matching, `newText` written verbatim with the file's line endings/BOM preserved, and a diff + unified patch in the result. It also tolerates sloppy input (a JSON string, a bare object, legacy top-level `oldText`/`newText`).
|
|
157
157
|
- **`delegate_task`** — hands a specialist template a six-section brief (TASK / EXPECTED OUTCOME / REQUIRED TOOLS / MUST DO / MUST NOT DO / CONTEXT) validated on the server: a missing template, a task under 20 characters or any empty section is rejected, and the error tells the model which templates it may use. Cards render the brief as labelled sections, and a finished delegation gets a button that jumps to the subagent's conversation.
|
|
@@ -301,6 +301,32 @@ silences the warning):
|
|
|
301
301
|
npm i -g --allow-scripts=node-pty,@google/genai,protobufjs pi-web-ui@latest
|
|
302
302
|
```
|
|
303
303
|
|
|
304
|
+
### 🐧 Linux build prerequisites
|
|
305
|
+
|
|
306
|
+
`node-pty` (the built-in terminal) publishes prebuilds only for macOS and Windows,
|
|
307
|
+
so on Linux `npm install` always falls back to `node-gyp` and compiles it from
|
|
308
|
+
source. Install a C/C++ toolchain **before** installing pi-web-ui:
|
|
309
|
+
|
|
310
|
+
```bash
|
|
311
|
+
# Debian / Ubuntu
|
|
312
|
+
sudo apt-get install -y build-essential python3
|
|
313
|
+
# Fedora / RHEL
|
|
314
|
+
sudo dnf install -y python3 make gcc-c++
|
|
315
|
+
# Arch
|
|
316
|
+
sudo pacman -S --needed base-devel python
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
Without it the whole install aborts with:
|
|
320
|
+
|
|
321
|
+
```
|
|
322
|
+
npm error path .../node_modules/node-pty
|
|
323
|
+
npm error command sh -c node scripts/prebuild.js || node-gyp rebuild
|
|
324
|
+
npm error > Rebuilding because directory .../node_modules/node-pty/prebuilds/linux-x64 does not exist
|
|
325
|
+
npm error make: g++: No such file or directory
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
Android / Termux is a special case — see [Termux (Android)](#termux-android).
|
|
329
|
+
|
|
304
330
|
### 🖥️ Desktop app (Windows installer)
|
|
305
331
|
|
|
306
332
|
Prefer a window over a browser tab? Every release ships installers built from
|
package/README.zh-CN.md
CHANGED
|
@@ -64,8 +64,8 @@ QQ群 1126050727
|
|
|
64
64
|
|
|
65
65
|
**子代理与模板**
|
|
66
66
|
|
|
67
|
-
- **第一方子代理** —— 后台派发独立对话并行做调研 / 实现 /
|
|
68
|
-
- **子代理模板** —— 设置面板「子代理模板」里配置可复用预设:角色系统提示词(追加或整体替换)+ 技能/扩展白名单 + 可选模型 + 可选思考强度。AI 用 `
|
|
67
|
+
- **第一方子代理** —— 后台派发独立对话并行做调研 / 实现 / 审查(统一的 `subagent` 工具,通过 `action="spawn"` 派发);支持 `action="wait_all"` 批量等待(阻塞上限为 60 秒且不低于 5 秒,受看门狗 × 0.8 约束,超时返回未完成名单引导后续等待);与普通对话一样在左栏管理:实时查看输出、补充(steer)、中止、移出。内存会话——不进历史 / resume 列表,可嵌套派发。
|
|
68
|
+
- **子代理模板** —— 设置面板「子代理模板」里配置可复用预设:角色系统提示词(追加或整体替换)+ 技能/扩展白名单 + 可选模型 + 可选思考强度。AI 用 `subagent(action="templates")` 查询清单、`subagent(action="spawn", template="…")` 选用,也可以不传模板按主会话默认配置运行。模型与思考强度都留空 = 跟随主对话当前设置(与「跟随主对话」的模型回落同语义);模板指定了就固定用那个组合(模型不支持的思考档位会自动收敛)。停用的模板保留在面板可随时重新启用,但对 AI 工具不可见(查不到、不能选)。模板全局共享(`<dataDir>/subagent-templates.json`,所有浏览器客户端一致)。首次运行自带 13 个内置模板 —— 前 6 个(review / implement / research / scout / audit / delegate)改编自 pi-subagents 社区项目,另 7 个(oracle / librarian / explore / metis / momus / multimodal-looker / sisyphus-junior)移植自 oh-my-pi 内置 agent —— 面板标「默认」徽标,可像普通模板一样修改或删除。
|
|
69
69
|
|
|
70
70
|
**文件、图片与附件**
|
|
71
71
|
|
|
@@ -112,7 +112,7 @@ QQ群 1126050727
|
|
|
112
112
|
|
|
113
113
|
**代理工具与内联标记**
|
|
114
114
|
|
|
115
|
-
- **工具开关** —— 设置
|
|
115
|
+
- **工具开关** —— 设置 →「工具」把所有可选工具逐个列出:统一的 `subagent` 工具(默认开)、以及另外 17 个工具 —— `edit_soft`、`browser_page`、`eval` 与 `lsp` 默认关,`delegate_task` / `ask_user_question` / `todo_list` / `conversation_read` / `present_files` / `skill` / `schedule_task` / `schedule_list` / `schedule_cancel` / `claim_files` / `plan_update` / `patch` / `compact_context` 默认开。开关即时生效、不重启,工具只是被禁用仍保留注册以便随时开回;核心工具 `bash` 与 SDK 自带的 `read` / `write` / `edit` 走设置页「核心工具」独立区域管理。
|
|
116
116
|
- **内联标记** —— 状态改变不需要工具往返,AI 直接把标记写进回复:任务列表用 `[[todo:new:<主题>]]` / `[[todo:set:<id>,in_progress]]` / `[[todo:remove:<id>]]` / `[[todo:dep:<id>,blocks=<id>]]`,不打断的提醒用 `[[notify:<级别>:<内容>]]`,改对话标题用 `[[conv:rename:<标题>]]`。气泡定稿即执行,标记写错会以浏览器提示回显;任务列表同时以常驻 widget 显示在右栏文件树下方(`N/M done` + ✓/◐/○),跟随当前对话,且因为存在该对话自己的会话分支里,刷新后仍在。设置 →「工具」另有总开关与逐标记开关(这两项全局共享)。
|
|
117
117
|
- **`edit_soft`** —— 更宽松的 `edit`(默认关):缩进/空白导致内置工具失败时用它,先精确子串、再按去空白逐行核心匹配,`newText` 原样写入并保留文件换行符/BOM,结果带 diff 与 unified patch。
|
|
118
118
|
- **`delegate_task`** —— 强制六段派单(TASK / EXPECTED OUTCOME / REQUIRED TOOLS / MUST DO / MUST NOT DO / CONTEXT)并在服务端校验:模板不可用、任务少于 20 字或任一段为空都会被打回,并把可用模板清单回给模型。卡片按六段结构化展示,跑完后可一键跳到对应子代理对话。
|
|
@@ -250,6 +250,31 @@ node-pty 是原生模块,需要放行其脚本(其余两个包只是 no-op/
|
|
|
250
250
|
npm i -g --allow-scripts=node-pty,@google/genai,protobufjs pi-web-ui@latest
|
|
251
251
|
```
|
|
252
252
|
|
|
253
|
+
### 🐧 Linux:安装前的编译依赖
|
|
254
|
+
|
|
255
|
+
内置终端依赖的 `node-pty` 只发布 macOS / Windows 的预编译产物,Linux 上
|
|
256
|
+
`npm install` 一律回退到 `node-gyp` 源码编译。请在安装 pi-web-ui **之前**装好 C/C++ 工具链:
|
|
257
|
+
|
|
258
|
+
```bash
|
|
259
|
+
# Debian / Ubuntu
|
|
260
|
+
sudo apt-get install -y build-essential python3
|
|
261
|
+
# Fedora / RHEL
|
|
262
|
+
sudo dnf install -y python3 make gcc-c++
|
|
263
|
+
# Arch
|
|
264
|
+
sudo pacman -S --needed base-devel python
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
缺少时会整体安装失败:
|
|
268
|
+
|
|
269
|
+
```
|
|
270
|
+
npm error path .../node_modules/node-pty
|
|
271
|
+
npm error command sh -c node scripts/prebuild.js || node-gyp rebuild
|
|
272
|
+
npm error > Rebuilding because directory .../node_modules/node-pty/prebuilds/linux-x64 does not exist
|
|
273
|
+
npm error make: g++: No such file or directory
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
Android / Termux 是特殊情况,见 [Termux(Android)](#termuxandroid)。
|
|
277
|
+
|
|
253
278
|
### Termux(Android)
|
|
254
279
|
|
|
255
280
|
pi-web-ui 可以通过 [Termux](https://termux.dev) 在 Android 上运行,但原生依赖
|
package/bin/pi-web-ui.mjs
CHANGED
|
@@ -545,6 +545,15 @@ function winWscript() {
|
|
|
545
545
|
return join(process.env.SystemRoot ?? "C:\\Windows", "System32", "wscript.exe");
|
|
546
546
|
}
|
|
547
547
|
|
|
548
|
+
/**
|
|
549
|
+
* The node binary the launcher scripts must bake in: the executable this
|
|
550
|
+
* process itself runs on. Under Bun this resolves to bun, so a Bun-hosted
|
|
551
|
+
* install never bakes in pi.exe.
|
|
552
|
+
*/
|
|
553
|
+
function resolveNode() {
|
|
554
|
+
return process.execPath;
|
|
555
|
+
}
|
|
556
|
+
|
|
548
557
|
/**
|
|
549
558
|
* Resolve the real node binary. fnm/volta/nvm shims (e.g. fnm_multishells)
|
|
550
559
|
* point into temp dirs that vanish when the installing shell exits — the
|
|
@@ -657,7 +666,7 @@ function buildWinShortcutPs1(env, cwd, taskName, url, logPath, pidPath) {
|
|
|
657
666
|
" try { if ((Invoke-WebRequest -Uri $h -UseBasicParsing -TimeoutSec 2).StatusCode -eq 200) { Start-Process $u | Out-Null; break } } catch {}",
|
|
658
667
|
" }",
|
|
659
668
|
"} -ArgumentList $url",
|
|
660
|
-
`& ${psQuote(node)} ${HAS_SDK_HOOK ? `--import ${psQuote(SDK_HOOK)} ` : ""}${psQuote(SERVER_ENTRY)} *>> $log`,
|
|
669
|
+
`& ${psQuote(node)} ${HAS_SDK_HOOK ? `--import ${psQuote(pathToFileURL(SDK_HOOK).href)} ` : ""}${psQuote(SERVER_ENTRY)} *>> $log`,
|
|
661
670
|
"Remove-Item $pidFile -ErrorAction SilentlyContinue",
|
|
662
671
|
"",
|
|
663
672
|
].join("\r\n");
|
|
@@ -673,7 +682,9 @@ function buildWinShortcutPs1(env, cwd, taskName, url, logPath, pidPath) {
|
|
|
673
682
|
* this launcher never creates one at all.
|
|
674
683
|
*/
|
|
675
684
|
function buildWinHiddenVbs(ps1Path) {
|
|
676
|
-
|
|
685
|
+
// 可执行文件必须带引号:winPowershell() 常返回 `C:\Program Files\PowerShell\7\pwsh.exe`,
|
|
686
|
+
// 含空格且未加引号时 WScript.Shell.Run 找不到文件,直接报 80070002(系统找不到指定的文件)。
|
|
687
|
+
const cmd = `"${winPowershell()}" -NoProfile -NonInteractive -ExecutionPolicy Bypass -WindowStyle Hidden -File "${ps1Path}"`;
|
|
677
688
|
// VBScript 字符串没有 \" 转义(也没有 \uXXXX),内嵌引号必须写成 "";
|
|
678
689
|
// 不能用 JSON.stringify —— 它输出 \" 会在 VBScript 里提前结束字符串(语句未结束 800A0401),
|
|
679
690
|
// 且会把非 ASCII 路径转成 \uXXXX 字面量(wscript 不识别,中文用户名直接变成乱码路径)。
|
|
@@ -981,7 +992,7 @@ function buildWinStartPs1(env, cwd, logPath, pidPath) {
|
|
|
981
992
|
`$PID | Out-File -Encoding ascii ${psQuote(pidPath)}`,
|
|
982
993
|
"try {",
|
|
983
994
|
" while ($true) {",
|
|
984
|
-
` & ${psQuote(realNode())} ${HAS_SDK_HOOK ? `--import ${psQuote(SDK_HOOK)} ` : ""}${psQuote(SERVER_ENTRY)} *>> ${psQuote(logPath)}`,
|
|
995
|
+
` & ${psQuote(realNode())} ${HAS_SDK_HOOK ? `--import ${psQuote(pathToFileURL(SDK_HOOK).href)} ` : ""}${psQuote(SERVER_ENTRY)} *>> ${psQuote(logPath)}`,
|
|
985
996
|
" Start-Sleep 10",
|
|
986
997
|
" }",
|
|
987
998
|
"} finally {",
|
|
@@ -1934,19 +1945,40 @@ function parsePluginSource(rawSpec) {
|
|
|
1934
1945
|
return { owner, repo, ref, subpath, cloneUrl: `https://github.com/${owner}/${repo}.git` };
|
|
1935
1946
|
}
|
|
1936
1947
|
|
|
1948
|
+
/** 清理临时目录下残留超过 1 小时的 pi-web-ui-plugin-* 安装目录。 */
|
|
1949
|
+
function cleanStalePluginTmpDirs() {
|
|
1950
|
+
try {
|
|
1951
|
+
const base = tmpdir();
|
|
1952
|
+
const entries = readdirSync(base);
|
|
1953
|
+
const now = Date.now();
|
|
1954
|
+
for (const name of entries) {
|
|
1955
|
+
if (name.startsWith("pi-web-ui-plugin-")) {
|
|
1956
|
+
try {
|
|
1957
|
+
const p = join(base, name);
|
|
1958
|
+
const st = statSync(p);
|
|
1959
|
+
if (now - st.mtimeMs > 3600_000) {
|
|
1960
|
+
rmSync(p, { recursive: true, force: true });
|
|
1961
|
+
}
|
|
1962
|
+
} catch {}
|
|
1963
|
+
}
|
|
1964
|
+
}
|
|
1965
|
+
} catch {}
|
|
1966
|
+
}
|
|
1967
|
+
|
|
1937
1968
|
/** 把仓库拉到 tmpDir 并返回检出根目录。优先 git clone --depth 1,失败回退 codeload tarball + 系统 tar。 */
|
|
1938
1969
|
async function acquireRepo(src, tmpDir) {
|
|
1939
1970
|
const dst = join(tmpDir, "src");
|
|
1940
1971
|
const hasGit = spawnSync("git", ["--version"], { stdio: "ignore", timeout: 10_000 }).status === 0;
|
|
1941
1972
|
if (hasGit) {
|
|
1942
|
-
const
|
|
1973
|
+
const gitTimeout = Number(process.env.PI_WEB_GIT_TIMEOUT_MS) || 45_000;
|
|
1974
|
+
const args = ["-c", "connect.timeout=15", "clone", "--depth", "1", "--single-branch"];
|
|
1943
1975
|
if (src.ref) args.push("--branch", src.ref);
|
|
1944
1976
|
args.push(src.cloneUrl, dst);
|
|
1945
1977
|
console.log(`· git clone --depth 1 ${src.cloneUrl}${src.ref ? ` (${src.ref})` : ""}`);
|
|
1946
1978
|
const res = spawnSync("git", args, {
|
|
1947
1979
|
stdio: "inherit",
|
|
1948
1980
|
env: { ...process.env, GIT_TERMINAL_PROMPT: "0", GCM_INTERACTIVE: "never" },
|
|
1949
|
-
timeout:
|
|
1981
|
+
timeout: gitTimeout,
|
|
1950
1982
|
});
|
|
1951
1983
|
if (res.status === 0 && existsSync(dst)) return dst;
|
|
1952
1984
|
console.log(
|
|
@@ -1960,26 +1992,28 @@ async function acquireRepo(src, tmpDir) {
|
|
|
1960
1992
|
// 注意:这里不用 fail()/process.exit —— async 上下文里还有未关闭的 socket 时
|
|
1961
1993
|
// 直接退出会触发 Windows libuv "UV_HANDLE_CLOSING" 断言崩溃;改为 throw,
|
|
1962
1994
|
// 由 pluginInstallCmd 捕获后设 exitCode 让事件循环自然排空。
|
|
1963
|
-
let
|
|
1995
|
+
let buf;
|
|
1964
1996
|
try {
|
|
1965
|
-
res = await fetch(url, { redirect: "follow", signal: AbortSignal.timeout(120_000) });
|
|
1997
|
+
const res = await fetch(url, { redirect: "follow", signal: AbortSignal.timeout(120_000) });
|
|
1998
|
+
if (!res.ok) {
|
|
1999
|
+
throw new Error(
|
|
2000
|
+
(ZH ? `HTTP 状态码 ${res.status}` : `HTTP status ${res.status}`) +
|
|
2001
|
+
(res.status === 404
|
|
2002
|
+
? ZH
|
|
2003
|
+
? "\n 仓库/分支不存在,或为私有仓库(私有仓库请先在本机配置好 git 凭据再重试,会优先走 git clone)。"
|
|
2004
|
+
: "\n Repository/branch does not exist, or is private (for private repos, configure git credentials locally first and retry; git clone will be tried first)."
|
|
2005
|
+
: ""),
|
|
2006
|
+
);
|
|
2007
|
+
}
|
|
2008
|
+
buf = Buffer.from(await res.arrayBuffer());
|
|
1966
2009
|
} catch (err) {
|
|
1967
2010
|
throw new Error(
|
|
1968
2011
|
ZH
|
|
1969
|
-
?
|
|
1970
|
-
: `Download failed: ${err?.message ?? err}\n Check your network/proxy and retry.`,
|
|
2012
|
+
? `下载失败 (${url}):${err?.message ?? err}\n 请检查网络/代理后重试(可配置 HTTP_PROXY / HTTPS_PROXY 环境变量,或为 git/npm 配置代理)。`
|
|
2013
|
+
: `Download failed (${url}): ${err?.message ?? err}\n Check your network/proxy and retry (consider configuring HTTP_PROXY/HTTPS_PROXY or git/npm proxy).`,
|
|
1971
2014
|
);
|
|
1972
2015
|
}
|
|
1973
|
-
|
|
1974
|
-
throw new Error(
|
|
1975
|
-
(ZH ? `下载失败 HTTP ${res.status}:${url}` : `Download failed HTTP ${res.status}: ${url}`) +
|
|
1976
|
-
(res.status === 404
|
|
1977
|
-
? ZH
|
|
1978
|
-
? "\n 仓库/分支不存在,或为私有仓库(私有仓库请先在本机配置好 git 凭据再重试,会优先走 git clone)。"
|
|
1979
|
-
: "\n Repository/branch does not exist, or is private (for private repos, configure git credentials locally first and retry; git clone will be tried first)."
|
|
1980
|
-
: ""),
|
|
1981
|
-
);
|
|
1982
|
-
writeFileSync(join(tmpDir, "src.tar.gz"), Buffer.from(await res.arrayBuffer()));
|
|
2016
|
+
writeFileSync(join(tmpDir, "src.tar.gz"), buf);
|
|
1983
2017
|
const extractTo = join(tmpDir, "tar");
|
|
1984
2018
|
mkdirSync(extractTo, { recursive: true });
|
|
1985
2019
|
// 相对路径解压:win32 的 GNU tar 会把 "C:\..." 里的 C: 当远程主机(Cannot connect to C:)
|
|
@@ -2200,6 +2234,7 @@ async function confirmSourceBuild(plan) {
|
|
|
2200
2234
|
* 失败抛 Error(目录模式逐条 try/catch 继续下一条,单源模式由调用方转 fail)。
|
|
2201
2235
|
*/
|
|
2202
2236
|
async function installOnePlugin({ rawSpec, name, force, build, noBuild, dataDir, isCatalog = false }) {
|
|
2237
|
+
cleanStalePluginTmpDirs();
|
|
2203
2238
|
const pluginsDir = join(dataDir, "plugins");
|
|
2204
2239
|
const localCandidate = resolve(rawSpec.replace(/^file:\/\//, ""));
|
|
2205
2240
|
const isLocal = existsSync(localCandidate);
|
|
@@ -2351,7 +2386,11 @@ async function installOnePlugin({ rawSpec, name, force, build, noBuild, dataDir,
|
|
|
2351
2386
|
}
|
|
2352
2387
|
return { id, target, manifest, buildMode: decision.mode };
|
|
2353
2388
|
} finally {
|
|
2354
|
-
|
|
2389
|
+
try {
|
|
2390
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
2391
|
+
} catch {
|
|
2392
|
+
/* Windows 等平台杀毒/进程占用可能抛 EBUSY,不应翻转已成功的安装 */
|
|
2393
|
+
}
|
|
2355
2394
|
}
|
|
2356
2395
|
}
|
|
2357
2396
|
|
|
@@ -3278,10 +3317,16 @@ async function checkUpdatesCmd(dataDir) {
|
|
|
3278
3317
|
for (const r of rows) {
|
|
3279
3318
|
const label = r.name && r.name !== r.id ? `${r.id}(${r.name})` : r.id;
|
|
3280
3319
|
if (r.updatable) {
|
|
3320
|
+
const cur = r.version ? (r.version.startsWith("v") ? r.version : `v${r.version}`) : (r.localSha ?? "未知");
|
|
3321
|
+
const lat = r.latestVersion
|
|
3322
|
+
? r.latestVersion.startsWith("v")
|
|
3323
|
+
? r.latestVersion
|
|
3324
|
+
: `v${r.latestVersion}`
|
|
3325
|
+
: (r.remoteSha ?? "");
|
|
3281
3326
|
console.log(
|
|
3282
3327
|
ZH
|
|
3283
|
-
? ` 🔄 ${label}${r.version ? ` v${r.version}` : ""} 可更新(已装 ${
|
|
3284
|
-
: ` 🔄 ${label}${r.version ? ` v${r.version}` : ""} Updatable (installed ${
|
|
3328
|
+
? ` 🔄 ${label}${r.version ? ` v${r.version}` : ""} 可更新(已装 ${cur} → 远端 ${lat})`
|
|
3329
|
+
: ` 🔄 ${label}${r.version ? ` v${r.version}` : ""} Updatable (installed ${cur} → remote ${lat})`,
|
|
3285
3330
|
);
|
|
3286
3331
|
console.log(
|
|
3287
3332
|
ZH
|
|
@@ -3289,11 +3334,16 @@ async function checkUpdatesCmd(dataDir) {
|
|
|
3289
3334
|
: ` Update: pi-web-ui install ${r.source} --name ${r.id} --force`,
|
|
3290
3335
|
);
|
|
3291
3336
|
any = true;
|
|
3292
|
-
} else if (r.remoteSha) {
|
|
3337
|
+
} else if (r.latestVersion || r.remoteSha) {
|
|
3338
|
+
const detail = r.latestVersion
|
|
3339
|
+
? r.latestVersion.startsWith("v")
|
|
3340
|
+
? r.latestVersion
|
|
3341
|
+
: `v${r.latestVersion}`
|
|
3342
|
+
: (r.remoteSha ?? "");
|
|
3293
3343
|
console.log(
|
|
3294
3344
|
ZH
|
|
3295
|
-
? ` ✓ ${label}${r.version ? ` v${r.version}` : ""} 已是最新(${
|
|
3296
|
-
: ` ✓ ${label}${r.version ? ` v${r.version}` : ""} Up to date (${
|
|
3345
|
+
? ` ✓ ${label}${r.version ? ` v${r.version}` : ""} 已是最新(${detail})`
|
|
3346
|
+
: ` ✓ ${label}${r.version ? ` v${r.version}` : ""} Up to date (${detail})`,
|
|
3297
3347
|
);
|
|
3298
3348
|
} else {
|
|
3299
3349
|
console.log(
|