pi-web-ui 0.69.0 → 0.71.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 +302 -0
- package/dist/server/agent-service.js +13 -4
- package/dist/server/client-state.js +44 -17
- package/dist/server/dsh/dsh-agent-service.js +3 -0
- package/dist/server/index.js +1 -0
- package/dist/server/locales.js +1 -1
- package/dist/server/marker-service.js +19 -15
- package/dist/server/settings-service.js +1 -0
- package/dist/server/subagents.js +13 -6
- package/dist/server/terminals.js +12 -1
- package/dist/server/webui-context.js +9 -0
- package/package.json +2 -1
- package/themes/cyberpunk.css +97 -81
- package/themes/dazzle.css +97 -81
- package/themes/md-preview.css +114 -98
- package/themes/translucent.css +78 -0
- package/themes/transparent.css +78 -0
- package/themes/white.css +176 -160
- package/web/dist/assets/{TerminalPanel-Cj8zsjx-.js → TerminalPanel-DXNpevIG.js} +1 -1
- package/web/dist/assets/index-4ge_ioPK.css +10 -0
- package/web/dist/assets/index-Cue012aF.js +335 -0
- package/web/dist/index.html +2 -2
- package/web/dist/assets/index-DCOcsPFm.js +0 -334
- package/web/dist/assets/index-jH2Bb-0X.css +0 -10
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
> 面向使用者的版本变更记录:升级前先看这里,再决定是否升级。
|
|
4
|
+
> 版本号规则:npm 上的版本不带 `v` 前缀(如 `0.70.0`),GitHub 的 tag / Release 带 `v` 前缀(如 `v0.70.0`),两者数字部分一一对应。
|
|
5
|
+
> 日期为 npm 发布时间(UTC+8 换算后的日历日)。
|
|
6
|
+
|
|
7
|
+
格式说明:`Added` 新增功能、`Fixed` 修复、`Changed` 行为/样式变更、`i18n` 多语言相关。
|
|
8
|
+
每个版本的内容按"实际合入该版本发布的提交"归档(以 `package.json` 的 version 变更提交为准),
|
|
9
|
+
而不是按提交日期聚类——连续快速发布的 patch 版本以此为准最准确。
|
|
10
|
+
|
|
11
|
+
## [Unreleased]
|
|
12
|
+
|
|
13
|
+
暂无未发布内容。
|
|
14
|
+
|
|
15
|
+
## [0.71.0] — 2026-09-09
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- 全屏壁纸与容器背景变量,新增半透明(translucent)/全透明(transparent)主题。
|
|
20
|
+
- 右侧文件列表加"复制名称 / 复制路径"按钮。
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
|
|
24
|
+
- CollapsedMessage:`button` 改 `div`,窄消息区 rail 避让仅桌面生效。
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
|
|
28
|
+
- prettier 收尾:`web/src/i18n.tsx` 上游 drift 格式化(post-#102)。
|
|
29
|
+
|
|
30
|
+
## [0.70.0] — 2026-09-09
|
|
31
|
+
|
|
32
|
+
### Added
|
|
33
|
+
|
|
34
|
+
- 全局共享设置:服务端持久化 + 服务端 quick-phrases 种子、quick-seed 标记、动态 marker overlay。
|
|
35
|
+
- 输入框快捷短语改为服务端下发种子;超长 notice 自动换行。
|
|
36
|
+
- 聊天壁纸设置基础(issue #100,含主题 cyberpunk 壁纸变量与 `wallpaper-settings` 单测;全屏壁纸与容器背景变量见 0.71.0)。
|
|
37
|
+
- 终端"运行中"列表只统计存活 PTY(countLive)。
|
|
38
|
+
|
|
39
|
+
### Fixed
|
|
40
|
+
|
|
41
|
+
- 语言包 slot 语法与换行转义;条件分支预渲染 segments。
|
|
42
|
+
|
|
43
|
+
### i18n
|
|
44
|
+
|
|
45
|
+
- 葡萄牙语 serverStrings 163 条翻译 + 文案润色。
|
|
46
|
+
|
|
47
|
+
### Changed
|
|
48
|
+
|
|
49
|
+
- 面板按钮对比度、主题英文名、思考/工具头重排。
|
|
50
|
+
|
|
51
|
+
## [0.69.0] — 2026-09-08
|
|
52
|
+
|
|
53
|
+
### Added
|
|
54
|
+
|
|
55
|
+
- 目标模式总开关:关闭时隐藏目标条,阻断向导与审查。
|
|
56
|
+
- `PI_WEB_TABS`(实例提供哪些 tab)与 `PI_WEB_MANAGED`(外部更新的实例)及文档。
|
|
57
|
+
- 首次访问语言跟随浏览器,而非固定默认。
|
|
58
|
+
|
|
59
|
+
### Fixed
|
|
60
|
+
|
|
61
|
+
- 用户气泡保留单个换行;快捷短语输入后重新聚焦。
|
|
62
|
+
- 子代理归属其所属会话(issue #95)。
|
|
63
|
+
- E2E 明确上报 zh locale;terminal-smoke 与终端工具默认关对齐。
|
|
64
|
+
|
|
65
|
+
### Changed
|
|
66
|
+
|
|
67
|
+
- pt-BR 文案润色;`index.mjs` 参数化查询(#97)。
|
|
68
|
+
|
|
69
|
+
## [0.68.2] — 2026-09-08
|
|
70
|
+
|
|
71
|
+
### Added
|
|
72
|
+
|
|
73
|
+
- 意大利语(it) locale;可下载语言包(核心只带 zh/en)。
|
|
74
|
+
- 标准 pi 引擎接入 `ask_user_question` 问卷。
|
|
75
|
+
- 文本块与思考块复制按钮。
|
|
76
|
+
|
|
77
|
+
### Fixed
|
|
78
|
+
|
|
79
|
+
- 网页终端里 vim 无法输入(提高 Vite 构建 target)。
|
|
80
|
+
- Android/Termux 死锁:服务端热路径消除全部 fork。
|
|
81
|
+
- pt-BR 字符串润色(markers 描述 + locale 列表报错)。
|
|
82
|
+
|
|
83
|
+
## [0.68.1] — 2026-09-07
|
|
84
|
+
|
|
85
|
+
### Added
|
|
86
|
+
|
|
87
|
+
- 运行轨迹时间线插件:`host.onRunEvent` 轨迹事件通道、harness 式轨迹分析 v2、vis-timeline 专业引擎(vendor 自带)、工具按名着色 + 图例、自绘即时悬浮层。
|
|
88
|
+
- 浏览器标题显示项目名。
|
|
89
|
+
|
|
90
|
+
### Fixed
|
|
91
|
+
|
|
92
|
+
- webmail:secret 存储失败时不再丢密码。
|
|
93
|
+
- 宽屏消息列/输入列按实测滚动条宽精确对齐;重试提示条幅与消息正文列同宽;goalbar 对齐。
|
|
94
|
+
|
|
95
|
+
## [0.68.0] — 2026-09-07
|
|
96
|
+
|
|
97
|
+
### Added
|
|
98
|
+
|
|
99
|
+
- 输入框快捷短语:一键发送 + 设置页管理。
|
|
100
|
+
|
|
101
|
+
## [0.67.0] — 2026-09-07
|
|
102
|
+
|
|
103
|
+
### Added
|
|
104
|
+
|
|
105
|
+
- 系统提示词模板组合(system prompt template composition)+ `edit_soft` 宽松编辑工具。
|
|
106
|
+
|
|
107
|
+
## [0.66.0] — 2026-09-07
|
|
108
|
+
|
|
109
|
+
### Added
|
|
110
|
+
|
|
111
|
+
- Termux(Android)安装指南。
|
|
112
|
+
- Mermaid 图表跟随当前主题。
|
|
113
|
+
|
|
114
|
+
### Fixed
|
|
115
|
+
|
|
116
|
+
- 同步 pi CLI 探测改为异步后台探测(解决死锁,#79)。
|
|
117
|
+
- Mermaid 主题同步与排版规整。
|
|
118
|
+
|
|
119
|
+
## [0.65.0] — 2026-09-06
|
|
120
|
+
|
|
121
|
+
### Added
|
|
122
|
+
|
|
123
|
+
- 插件市场 + fenced-code 渲染插件(mermaid 插件化)。
|
|
124
|
+
- 超宽屏聊天列开关。
|
|
125
|
+
|
|
126
|
+
## [0.64.8] — 2026-09-06
|
|
127
|
+
|
|
128
|
+
### Fixed
|
|
129
|
+
|
|
130
|
+
- 后台服务列表过滤桌面软件噪音进程。
|
|
131
|
+
|
|
132
|
+
## [0.64.7] — 2026-09-06
|
|
133
|
+
|
|
134
|
+
### Added
|
|
135
|
+
|
|
136
|
+
- 子代理父子链接树、保留与干净 rpc 绑定。
|
|
137
|
+
|
|
138
|
+
## [0.64.6] — 2026-09-06
|
|
139
|
+
|
|
140
|
+
### Added
|
|
141
|
+
|
|
142
|
+
- 子代理:错误透出、模型选择、`wait-for-all` 工具。
|
|
143
|
+
|
|
144
|
+
## [0.64.5] — 2026-09-06
|
|
145
|
+
|
|
146
|
+
### Fixed
|
|
147
|
+
|
|
148
|
+
- 重启浏览器恢复上次工作目录。
|
|
149
|
+
- 目录消失时列表/会话刷新不再崩溃(issue #74)。
|
|
150
|
+
|
|
151
|
+
## [0.64.4] — 2026-09-06
|
|
152
|
+
|
|
153
|
+
### Added
|
|
154
|
+
|
|
155
|
+
- 设置 → 显示:mermaid 图表渲染开关。
|
|
156
|
+
|
|
157
|
+
### Fixed
|
|
158
|
+
|
|
159
|
+
- 历史/最近项目遵循 `PI_CODING_AGENT_SESSION_DIR`。
|
|
160
|
+
|
|
161
|
+
## [0.64.3] — 2026-09-05
|
|
162
|
+
|
|
163
|
+
### Added
|
|
164
|
+
|
|
165
|
+
- `pi-web-ui --help` 中英双语(按 LANG 检测,#72)。
|
|
166
|
+
|
|
167
|
+
## [0.64.2] — 2026-09-05
|
|
168
|
+
|
|
169
|
+
### Added
|
|
170
|
+
|
|
171
|
+
- mermaid 代码块渲染为图表。
|
|
172
|
+
- 机器浏览模式:`@root` 机器根 + 绝对 wire 路径跨盘符浏览。
|
|
173
|
+
|
|
174
|
+
### Fixed
|
|
175
|
+
|
|
176
|
+
- `PI_WEB_TOKEN` 改口令后 cookie 自动刷新/过期,一次 `?token=` 即恢复(issue #71)。
|
|
177
|
+
- mermaid 原生 SVG 尺寸、流式路径渲染、取消渲染清理;宽图表可读宽度保持。
|
|
178
|
+
|
|
179
|
+
## [0.64.1] — 2026-09-05
|
|
180
|
+
|
|
181
|
+
### Added
|
|
182
|
+
|
|
183
|
+
- 33 条 notice 的英文 textEn、locale 实时退出横幅与页面标题。
|
|
184
|
+
|
|
185
|
+
### Fixed
|
|
186
|
+
|
|
187
|
+
- 移动端发送按钮盖过思考强度底弹层(#70)。
|
|
188
|
+
- Windows 下 stale-marker 单测时间戳(pre-1970 mtime 回绕到 2106)。
|
|
189
|
+
|
|
190
|
+
## [0.64.0] — 2026-09-04
|
|
191
|
+
|
|
192
|
+
### Added
|
|
193
|
+
|
|
194
|
+
- 全局提示词历史、目录选择器(含新建文件夹)与 UI 抛光。
|
|
195
|
+
|
|
196
|
+
## [0.63.4] — 2026-09-04
|
|
197
|
+
|
|
198
|
+
- 版本重发(随带 `rename` 内置 marker 一行修正),无功能变更。
|
|
199
|
+
|
|
200
|
+
## [0.63.3] — 2026-09-04
|
|
201
|
+
|
|
202
|
+
### Added
|
|
203
|
+
|
|
204
|
+
- PWA 支持与移动端键盘回车换行(#64);会话结束/需输入时桌面通知(#65);PWA 资源与通知图标子路径感知(nginx `/pi/` 部署)。
|
|
205
|
+
- 历史对话与终端标签 UI 重命名(#63);`/name` 命令重命名当前会话(#66)。
|
|
206
|
+
- 行内 marker 系统:todo/svc/notify/rename,无需工具往返。
|
|
207
|
+
|
|
208
|
+
### Fixed
|
|
209
|
+
|
|
210
|
+
- SCM 提交遵循暂存区,并新增"提交全部"。
|
|
211
|
+
|
|
212
|
+
## [0.63.2] — 2026-09-03
|
|
213
|
+
|
|
214
|
+
### Fixed
|
|
215
|
+
|
|
216
|
+
- 主题名跟随界面语言(英文用 nameEn,#61)。
|
|
217
|
+
- terminal-smoke 改轮询 shell banner,消除慢 CI 机器抖动。
|
|
218
|
+
|
|
219
|
+
## [0.63.1] — 2026-09-03
|
|
220
|
+
|
|
221
|
+
### Fixed
|
|
222
|
+
|
|
223
|
+
- 左侧栏折叠区块统一样式:折叠后三标题一致、切换不位移、收起按钮垂直居中、标题高亮通栏居中。
|
|
224
|
+
|
|
225
|
+
## [0.63.0] — 2026-09-03
|
|
226
|
+
|
|
227
|
+
### Added
|
|
228
|
+
|
|
229
|
+
- 子代理模板:角色系统提示词(append/replace)+ 技能/扩展白名单,AI 可选用、可停用,内置 6 个默认模板。
|
|
230
|
+
- 对话可从运行列表移出(dismiss_conversation)+ pi-subagents 存活检测(WIP)。
|
|
231
|
+
|
|
232
|
+
### Changed
|
|
233
|
+
|
|
234
|
+
- 全仓库 prettier(Tab/120)与 oxlint 接入 CI(#57/#58)。
|
|
235
|
+
- DSH notice、终端退出横幅、目标状态跟随界面语言(#54 及后续)。
|
|
236
|
+
|
|
237
|
+
### Fixed
|
|
238
|
+
|
|
239
|
+
- 插件目录搬迁后坏掉的冒烟测试 fixture(#59)。
|
|
240
|
+
|
|
241
|
+
## [0.62.1] — 2026-09-03
|
|
242
|
+
|
|
243
|
+
### Fixed
|
|
244
|
+
|
|
245
|
+
- SCM 的 `git add/reset` 路径引号闭合(issue #51)。
|
|
246
|
+
|
|
247
|
+
## [0.62.0] — 2026-09-02
|
|
248
|
+
|
|
249
|
+
### Added
|
|
250
|
+
|
|
251
|
+
- 排队消息可删除(✕);底部栏实时缓存命中率与生成速率。
|
|
252
|
+
|
|
253
|
+
### Fixed
|
|
254
|
+
|
|
255
|
+
- `terminal_create` 带 title 下发,服务端不再用中文默认覆盖。
|
|
256
|
+
|
|
257
|
+
## [0.61.0] — 2026-09-02
|
|
258
|
+
|
|
259
|
+
### Added
|
|
260
|
+
|
|
261
|
+
- 内置服务商多密钥管理 + 项目级模型/key 记忆。
|
|
262
|
+
|
|
263
|
+
## 更早版本
|
|
264
|
+
|
|
265
|
+
0.62 之前的版本没有逐版归档,以下是发布提交记录中的要点(详见 `git log`):
|
|
266
|
+
|
|
267
|
+
- 0.60.0(2026-09-02):移除 `PORT` 环境变量兼容,仅保留 `PI_WEB_PORT`;修复 `--host` 直启失效。
|
|
268
|
+
- 0.59.0(2026-09-01)、0.58.0(2026-08-30)。
|
|
269
|
+
- 0.56.1 / 0.55.0 / 0.53.0 / 0.51.0(2026-08-30)。
|
|
270
|
+
- 0.50.0 / 0.49.0(2026-08-29):0.49.0 起移除 Electron 桌面壳,只保留纯 Web。
|
|
271
|
+
- 0.48.3 / 0.48.2(2026-08-29):pi SDK 升到 `^0.84.4`。
|
|
272
|
+
- 0.44.1(2026-08-28)。
|
|
273
|
+
- 0.35.1(2026-08-27):编辑重问保留附件(#18)+ 全窗口拖放(#19)。
|
|
274
|
+
- 0.29.0(2026-08-23):全局搜索弹窗(Ctrl+K)+ 消息列表惰性窗口化。
|
|
275
|
+
|
|
276
|
+
[Unreleased]: https://github.com/xing-shuyin/pi-web-ui/compare/v0.71.0...main
|
|
277
|
+
[0.71.0]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.71.0
|
|
278
|
+
[0.70.0]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.70.0
|
|
279
|
+
[0.69.0]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.69.0
|
|
280
|
+
[0.68.2]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.68.2
|
|
281
|
+
[0.68.1]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.68.1
|
|
282
|
+
[0.68.0]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.68.0
|
|
283
|
+
[0.67.0]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.67.0
|
|
284
|
+
[0.66.0]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.66.0
|
|
285
|
+
[0.65.0]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.65.0
|
|
286
|
+
[0.64.8]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.64.8
|
|
287
|
+
[0.64.7]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.64.7
|
|
288
|
+
[0.64.6]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.64.6
|
|
289
|
+
[0.64.5]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.64.5
|
|
290
|
+
[0.64.4]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.64.4
|
|
291
|
+
[0.64.3]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.64.3
|
|
292
|
+
[0.64.2]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.64.2
|
|
293
|
+
[0.64.1]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.64.1
|
|
294
|
+
[0.64.0]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.64.0
|
|
295
|
+
[0.63.4]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.63.4
|
|
296
|
+
[0.63.3]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.63.3
|
|
297
|
+
[0.63.2]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.63.2
|
|
298
|
+
[0.63.1]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.63.1
|
|
299
|
+
[0.63.0]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.63.0
|
|
300
|
+
[0.62.1]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.62.1
|
|
301
|
+
[0.62.0]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.62.0
|
|
302
|
+
[0.61.0]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.61.0
|
|
@@ -1110,9 +1110,15 @@ export class ClientSession {
|
|
|
1110
1110
|
// 复用现有重命名路径(内存标题 + 磁盘 session_info)
|
|
1111
1111
|
void this.renameConversation(convId, title);
|
|
1112
1112
|
},
|
|
1113
|
+
// 标记 widget 合并进扩展 widget 里,跟随当前活动会话渲染(切换会话即刷新)。
|
|
1114
|
+
refreshMarkers: () => this.webUi.refresh(),
|
|
1113
1115
|
// issue #91:标记引导/错误按客户端 UI 语言出中英(英文默认)。
|
|
1114
1116
|
lang: () => this.getLang(),
|
|
1115
1117
|
});
|
|
1118
|
+
// 标记 widget 动态渲染「当前活动会话」的 todo/overlay:切换会话时只要刷新
|
|
1119
|
+
// webUi(见 switchConversation/setCwd/newChat)就会显示对应会话的标记,
|
|
1120
|
+
// 且与扩展 widget 合并下发、不会互相覆盖。
|
|
1121
|
+
this.webUi.setDynamicWidget("markers", () => this.markerSvc.overlayLines(this.activeId));
|
|
1116
1122
|
this.settingsSvc = new SettingsService({
|
|
1117
1123
|
clientId,
|
|
1118
1124
|
stateStore,
|
|
@@ -2839,7 +2845,10 @@ export class ClientSession {
|
|
|
2839
2845
|
}
|
|
2840
2846
|
/** Persist + apply a partial settings update (prompt text/mode, toggles). */
|
|
2841
2847
|
async setSettings(partial) {
|
|
2842
|
-
const { markersEnabled, disabledMarkers, ...rest } = partial;
|
|
2848
|
+
const { markersEnabled, disabledMarkers, quickPhrasesSeeded, ...rest } = partial;
|
|
2849
|
+
// 快捷短语「已 seed」是全局标记(非 per-clientId):置位一次后永久生效。
|
|
2850
|
+
if (quickPhrasesSeeded)
|
|
2851
|
+
this.stateStore.markQuickPhrasesSeeded();
|
|
2843
2852
|
let markerChanged = false;
|
|
2844
2853
|
if (markersEnabled !== undefined || disabledMarkers !== undefined) {
|
|
2845
2854
|
this.markerSvc.setAll({
|
|
@@ -3475,7 +3484,7 @@ export class ClientSession {
|
|
|
3475
3484
|
reviewing: conv.goal.reviewing,
|
|
3476
3485
|
wizardRunning: conv.wizardRunning,
|
|
3477
3486
|
streaming: conv.session.isStreaming,
|
|
3478
|
-
openTerminals: conv.terminals.
|
|
3487
|
+
openTerminals: conv.terminals.countLive(),
|
|
3479
3488
|
listed: conv.listed,
|
|
3480
3489
|
promptedSinceActive: conv.promptedSinceActive,
|
|
3481
3490
|
hasActiveSubagentRun: () => hasActiveSubagentRun({ sessionId: conv.session.sessionFile }),
|
|
@@ -3872,7 +3881,7 @@ export class ClientSession {
|
|
|
3872
3881
|
reviewing: conv.goal.reviewing,
|
|
3873
3882
|
wizardRunning: conv.wizardRunning,
|
|
3874
3883
|
streaming: conv.session.isStreaming,
|
|
3875
|
-
openTerminals: conv.terminals.
|
|
3884
|
+
openTerminals: conv.terminals.countLive(),
|
|
3876
3885
|
listed: conv.listed,
|
|
3877
3886
|
promptedSinceActive: conv.promptedSinceActive,
|
|
3878
3887
|
hasActiveSubagentRun: () => hasActiveSubagentRun({ sessionId: conv.session.sessionFile }),
|
|
@@ -3887,7 +3896,7 @@ export class ClientSession {
|
|
|
3887
3896
|
textEn: `Conversation "${conv.title}" is still running — wait for it to finish or press Stop before removing`,
|
|
3888
3897
|
});
|
|
3889
3898
|
}
|
|
3890
|
-
else if (conv.terminals.
|
|
3899
|
+
else if (conv.terminals.countLive() > 0) {
|
|
3891
3900
|
this.emit({
|
|
3892
3901
|
type: "notice",
|
|
3893
3902
|
level: "warning",
|
|
@@ -76,6 +76,14 @@ export class ClientStateStore {
|
|
|
76
76
|
constructor(filePath) {
|
|
77
77
|
this.filePath = filePath;
|
|
78
78
|
}
|
|
79
|
+
/** 长期设置(设置面板 config + 预设 + 标记开关)的固定存储键。
|
|
80
|
+
*
|
|
81
|
+
* 为什么用固定全局键而非 per-clientId:clientId 存 sessionStorage(每标签页独立、
|
|
82
|
+
* 关浏览器即失),按 clientId 存设置会在每次新会话/重启后生成新 id → 设置全部重置、
|
|
83
|
+
* 且各标签页/浏览器各有一套互不同步。改为全局共享后:所有客户端(标签页/浏览器)
|
|
84
|
+
* 使用同一套配置,且持久化在服务端,重启不丢(「同一套配置」)。会话级状态
|
|
85
|
+
* (最近项目 / lastCwd / 项目模型与密钥等)仍按 clientId 各自保留。 */
|
|
86
|
+
static GLOBAL_SETTINGS_KEY = "__settings__";
|
|
79
87
|
/** <dataDir>(client-state.json 的上一级)——共享配置(子代理模板库等)落在这里。 */
|
|
80
88
|
get dataDir() {
|
|
81
89
|
return dirname(this.filePath);
|
|
@@ -197,9 +205,9 @@ export class ClientStateStore {
|
|
|
197
205
|
}
|
|
198
206
|
return list;
|
|
199
207
|
}
|
|
200
|
-
/**
|
|
201
|
-
getSettings(
|
|
202
|
-
const s = this.load()[
|
|
208
|
+
/** 设置面板状态(系统提示词模式/文字 + 禁用技能/扩展)——全局共享同一套配置。 */
|
|
209
|
+
getSettings(_clientId) {
|
|
210
|
+
const s = this.load()[ClientStateStore.GLOBAL_SETTINGS_KEY];
|
|
203
211
|
const stored = s?.settings;
|
|
204
212
|
// 旧存档(promptMode/customSystemPrompt)迁移到 compose:追加文字成为独立
|
|
205
213
|
// {{append}} 覆盖、替换文字成为 {{soul}} 覆盖;无自定义则用默认模板。
|
|
@@ -242,10 +250,10 @@ export class ClientStateStore {
|
|
|
242
250
|
disabledPlugins: stored?.disabledPlugins ?? [],
|
|
243
251
|
};
|
|
244
252
|
}
|
|
245
|
-
/** Persist the
|
|
246
|
-
saveSettings(
|
|
253
|
+
/** Persist the settings-panel state (partial merge) — global shared config. */
|
|
254
|
+
saveSettings(_clientId, settings) {
|
|
247
255
|
const all = this.load();
|
|
248
|
-
const state = (all[
|
|
256
|
+
const state = (all[ClientStateStore.GLOBAL_SETTINGS_KEY] ??= { projects: [] });
|
|
249
257
|
const cur = state.settings ?? {};
|
|
250
258
|
state.settings = {
|
|
251
259
|
promptMode: settings.promptMode ?? cur.promptMode ?? "append",
|
|
@@ -276,9 +284,9 @@ export class ClientStateStore {
|
|
|
276
284
|
};
|
|
277
285
|
this.save();
|
|
278
286
|
}
|
|
279
|
-
/** Named settings presets for a client (empty if never saved). */
|
|
280
|
-
getPresets(
|
|
281
|
-
return (this.load()[
|
|
287
|
+
/** Named settings presets for a client (empty if never saved) — global shared. */
|
|
288
|
+
getPresets(_clientId) {
|
|
289
|
+
return (this.load()[ClientStateStore.GLOBAL_SETTINGS_KEY]?.presets ?? []).map((p) => ({
|
|
282
290
|
...p,
|
|
283
291
|
// Older client-state files predate review settings.
|
|
284
292
|
reviewPrompt: p.reviewPrompt ?? "",
|
|
@@ -287,10 +295,10 @@ export class ClientStateStore {
|
|
|
287
295
|
retryMaxAttempts: normalizeRetryMaxAttempts(p.retryMaxAttempts),
|
|
288
296
|
}));
|
|
289
297
|
}
|
|
290
|
-
/** Persist the
|
|
291
|
-
savePresets(
|
|
298
|
+
/** Persist the named settings presets — global shared config. */
|
|
299
|
+
savePresets(_clientId, presets) {
|
|
292
300
|
const all = this.load();
|
|
293
|
-
const state = (all[
|
|
301
|
+
const state = (all[ClientStateStore.GLOBAL_SETTINGS_KEY] ??= { projects: [] });
|
|
294
302
|
state.presets = presets;
|
|
295
303
|
this.save();
|
|
296
304
|
}
|
|
@@ -346,17 +354,36 @@ export class ClientStateStore {
|
|
|
346
354
|
delete all[clientId].projectModels;
|
|
347
355
|
this.save();
|
|
348
356
|
}
|
|
349
|
-
/**
|
|
350
|
-
|
|
351
|
-
|
|
357
|
+
/** 全局「快捷短语已 seed」标记(非 per-clientId)。
|
|
358
|
+
*
|
|
359
|
+
* 为什么全局:clientId 存 sessionStorage(每标签页独立、关浏览器即失),按
|
|
360
|
+
* clientId 记 seed 会在每次新会话生成新 clientId 时误判为「从未 seed」,导致
|
|
361
|
+
* 用户删掉的默认短语又被填回默认。seed 只需一次(首次见空列表),之后即为用户
|
|
362
|
+
* 数据,增删改/恢复默认/关闭都走设置面板。存服务端而非浏览器 localStorage,
|
|
363
|
+
* 任何浏览器/标签页/清缓存都不受影响。 */
|
|
364
|
+
getQuickPhrasesSeeded() {
|
|
365
|
+
const meta = this.load()[ClientStateStore.GLOBAL_SETTINGS_KEY];
|
|
366
|
+
return !!meta?.quickPhrasesSeeded;
|
|
367
|
+
}
|
|
368
|
+
markQuickPhrasesSeeded() {
|
|
369
|
+
const all = this.load();
|
|
370
|
+
const meta = (all[ClientStateStore.GLOBAL_SETTINGS_KEY] ??= { projects: [] });
|
|
371
|
+
if (meta.quickPhrasesSeeded)
|
|
372
|
+
return;
|
|
373
|
+
meta.quickPhrasesSeeded = true;
|
|
374
|
+
this.save();
|
|
375
|
+
}
|
|
376
|
+
/** 内置标记工具开关(全局共享同一套 + 按 marker 禁用)。 */
|
|
377
|
+
getMarkerSettings(_clientId) {
|
|
378
|
+
const s = this.load()[ClientStateStore.GLOBAL_SETTINGS_KEY]?.markers;
|
|
352
379
|
return {
|
|
353
380
|
markersEnabled: s?.markersEnabled ?? true,
|
|
354
381
|
disabledMarkers: s?.disabledMarkers ?? [],
|
|
355
382
|
};
|
|
356
383
|
}
|
|
357
|
-
saveMarkerSettings(
|
|
384
|
+
saveMarkerSettings(_clientId, settings) {
|
|
358
385
|
const all = this.load();
|
|
359
|
-
const state = (all[
|
|
386
|
+
const state = (all[ClientStateStore.GLOBAL_SETTINGS_KEY] ??= { projects: [] });
|
|
360
387
|
const cur = state.markers ?? { markersEnabled: true, disabledMarkers: [] };
|
|
361
388
|
state.markers = {
|
|
362
389
|
markersEnabled: settings.markersEnabled ?? cur.markersEnabled ?? true,
|
|
@@ -2141,10 +2141,13 @@ export class DshClientSession {
|
|
|
2141
2141
|
subagentModels: [],
|
|
2142
2142
|
quickPhrases: [...this.settings.quickPhrases],
|
|
2143
2143
|
quickPhrasesEnabled: this.settings.quickPhrasesEnabled,
|
|
2144
|
+
quickPhrasesSeeded: this.stateStore.getQuickPhrasesSeeded(),
|
|
2144
2145
|
};
|
|
2145
2146
|
this.emit({ type: "settings_state", settings });
|
|
2146
2147
|
}
|
|
2147
2148
|
async setSettings(partial) {
|
|
2149
|
+
if (partial.quickPhrasesSeeded)
|
|
2150
|
+
this.stateStore.markQuickPhrasesSeeded();
|
|
2148
2151
|
if (partial.promptMode !== undefined)
|
|
2149
2152
|
this.settings.promptMode = partial.promptMode;
|
|
2150
2153
|
if (partial.customSystemPrompt !== undefined)
|
package/dist/server/index.js
CHANGED
|
@@ -947,6 +947,7 @@ wss.on("connection", (ws) => {
|
|
|
947
947
|
disabledMarkers: msg.disabledMarkers,
|
|
948
948
|
quickPhrases: msg.quickPhrases,
|
|
949
949
|
quickPhrasesEnabled: msg.quickPhrasesEnabled,
|
|
950
|
+
quickPhrasesSeeded: msg.quickPhrasesSeeded,
|
|
950
951
|
});
|
|
951
952
|
break;
|
|
952
953
|
case "extensions_reload":
|
package/dist/server/locales.js
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
17
17
|
import { join } from "node:path";
|
|
18
|
-
import { extractServerStrings, registerServerStrings, unregisterServerStrings
|
|
18
|
+
import { extractServerStrings, registerServerStrings, unregisterServerStrings } from "./i18n.js";
|
|
19
19
|
/** Packs available for download (alphabetical by code). */
|
|
20
20
|
export const LOCALE_PACKS = [
|
|
21
21
|
{ code: "de", nativeName: "Deutsch" },
|
|
@@ -44,15 +44,18 @@ export class MarkerService {
|
|
|
44
44
|
const lines = collectGuidance(disabled, this.lang());
|
|
45
45
|
if (lines.length === 0)
|
|
46
46
|
return "";
|
|
47
|
+
// The dynamic lines ride the `{lines}` slot so translator packs can
|
|
48
|
+
// place them (a static table value could never inline them).
|
|
49
|
+
const linesText = lines.join("\n");
|
|
47
50
|
return pick(this.lang(), "\n\n────────── 内联标记工具(重要)──────────\n" +
|
|
48
51
|
"状态类操作(任务、标记位、通知)请直接写在回答正文里,采用内联标记语法,而不要调用对应工具。\n" +
|
|
49
52
|
"插件会替你执行并把标记从展示文本中移除,不会中断你的回答。\n\n" +
|
|
50
|
-
|
|
53
|
+
linesText +
|
|
51
54
|
"\n──────────\n", "\n\n────────── Inline marker tools (important) ──────────\n" +
|
|
52
55
|
"Write status operations (tasks, marker bits, notifications) directly in your reply text using inline marker syntax — do not call a tool for them.\n" +
|
|
53
56
|
"The plugin executes them and strips the markers from the displayed text without interrupting your reply.\n\n" +
|
|
54
|
-
|
|
55
|
-
"\n──────────\n", "markers.service.guidance.frame");
|
|
57
|
+
linesText +
|
|
58
|
+
"\n──────────\n", "markers.service.guidance.frame", { lines: linesText });
|
|
56
59
|
}
|
|
57
60
|
setEnabled(enabled) {
|
|
58
61
|
this.settings.markersEnabled = !!enabled;
|
|
@@ -205,6 +208,18 @@ export class MarkerService {
|
|
|
205
208
|
this.pushOverlay(conversationId);
|
|
206
209
|
}
|
|
207
210
|
pushOverlay(conversationId) {
|
|
211
|
+
const lines = this.overlayLines(conversationId);
|
|
212
|
+
const key = `markers:${conversationId}`;
|
|
213
|
+
const prev = this.overlayCache.get(key);
|
|
214
|
+
const next = lines.length ? lines : [];
|
|
215
|
+
if (prev && prev.join("\n") === next.join("\n"))
|
|
216
|
+
return;
|
|
217
|
+
this.overlayCache.set(key, next);
|
|
218
|
+
// 通过宿主的 widget 合并(跟随当前活动会话,不覆盖扩展 widget)。
|
|
219
|
+
this.host.refreshMarkers();
|
|
220
|
+
}
|
|
221
|
+
/** 计算某个会话的标记 overlay 行(供 UI widget 动态渲染当前活动会话)。 */
|
|
222
|
+
overlayLines(conversationId) {
|
|
208
223
|
const lines = [];
|
|
209
224
|
for (const m of allMarkers()) {
|
|
210
225
|
if (this.settings.disabledMarkers.includes(m.name))
|
|
@@ -230,18 +245,7 @@ export class MarkerService {
|
|
|
230
245
|
lines.push(`[${ov.tool}]`);
|
|
231
246
|
lines.push(...ov.lines.map((l) => ` ${l}`));
|
|
232
247
|
}
|
|
233
|
-
|
|
234
|
-
const prev = this.overlayCache.get(key);
|
|
235
|
-
const next = lines.length ? lines : [];
|
|
236
|
-
if (prev && prev.join("\n") === next.join("\n"))
|
|
237
|
-
return;
|
|
238
|
-
this.overlayCache.set(key, next);
|
|
239
|
-
if (lines.length > 0) {
|
|
240
|
-
this.host.emit({ type: "widgets", widgets: [{ key: "markers", lines }] });
|
|
241
|
-
}
|
|
242
|
-
else {
|
|
243
|
-
this.host.emit({ type: "widgets", widgets: [] });
|
|
244
|
-
}
|
|
248
|
+
return lines;
|
|
245
249
|
}
|
|
246
250
|
describe(conversationId, tool, includeDeleted = false) {
|
|
247
251
|
const st = this.getState(conversationId, TODO_NAMESPACE, initTodoState);
|
|
@@ -232,6 +232,7 @@ export class SettingsService {
|
|
|
232
232
|
subagentModels: this.collectSubagentModels(),
|
|
233
233
|
quickPhrases: [...this.settings.quickPhrases],
|
|
234
234
|
quickPhrasesEnabled: this.settings.quickPhrasesEnabled,
|
|
235
|
+
quickPhrasesSeeded: this.host.stateStore.getQuickPhrasesSeeded(),
|
|
235
236
|
},
|
|
236
237
|
});
|
|
237
238
|
}
|
package/dist/server/subagents.js
CHANGED
|
@@ -126,18 +126,25 @@ export function makeSubagentTools(host, lang) {
|
|
|
126
126
|
const convId = await host.spawnSubagent(p.prompt, p.type ?? "general", p.cwd ?? ctx.cwd, p.template, p.model);
|
|
127
127
|
const subagentType = p.type ?? "general";
|
|
128
128
|
const subagentTitleText = subagentTitle(p.prompt);
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
129
|
+
// Optional segments are pre-rendered per language (translators pick
|
|
130
|
+
// the Zh/En variant through the vars table; inline ternaries would
|
|
131
|
+
// leak source syntax into packs that copy them verbatim).
|
|
132
|
+
const templateLineZh = p.template ? `\n模板:${p.template}` : "";
|
|
133
|
+
const templateLineEn = p.template ? `\nTemplate: ${p.template}` : "";
|
|
134
|
+
const modelLineZh = p.model ? `\n模型:${p.model}` : "";
|
|
135
|
+
const modelLineEn = p.model ? `\nModel: ${p.model}` : "";
|
|
136
|
+
return text(pick(getLang(), `子代理已启动(运行列表可见):${convId}\n类型:${subagentType} · 标题:${subagentTitleText}${templateLineZh}${modelLineZh}` +
|
|
137
|
+
`\n用 subagent_wait_all 一次等全部完成(不用轮询),subagent_get_result 取单个结果,subagent_list 看运行态,subagent_steer 改向,subagent_stop 停止。`, `Subagent started (visible in the running list): ${convId}\nType: ${subagentType} · Title: ${subagentTitleText}${templateLineEn}${modelLineEn}` +
|
|
135
138
|
`\nUse subagent_wait_all to wait for all at once (no polling), subagent_get_result for a single result, subagent_list for live status, subagent_steer to redirect, subagent_stop to stop.`, "subagents.spawn.started", {
|
|
136
139
|
convId: convId,
|
|
137
140
|
subagentType: subagentType,
|
|
138
141
|
subagentTitleText: subagentTitleText,
|
|
139
142
|
"p.template": p.template,
|
|
140
143
|
"p.model": p.model,
|
|
144
|
+
templateLineZh: templateLineZh,
|
|
145
|
+
templateLineEn: templateLineEn,
|
|
146
|
+
modelLineZh: modelLineZh,
|
|
147
|
+
modelLineEn: modelLineEn,
|
|
141
148
|
}), { convId, template: p.template, model: p.model });
|
|
142
149
|
},
|
|
143
150
|
}),
|
package/dist/server/terminals.js
CHANGED
|
@@ -1009,6 +1009,17 @@ export class TerminalManager {
|
|
|
1009
1009
|
list() {
|
|
1010
1010
|
return [...this.terms.values(), ...this.history.values()].map((entry) => this.info(entry));
|
|
1011
1011
|
}
|
|
1012
|
+
/** Count of LIVE PTYs only — exited terminals that merely retain their
|
|
1013
|
+
* output for review (history) do NOT count. Conversations are kept in the
|
|
1014
|
+
* running list while a live terminal exists; exited leftovers must not
|
|
1015
|
+
* pin an idle conversation forever. */
|
|
1016
|
+
countLive() {
|
|
1017
|
+
let n = 0;
|
|
1018
|
+
for (const entry of this.terms.values())
|
|
1019
|
+
if (!entry.exited)
|
|
1020
|
+
n++;
|
|
1021
|
+
return n;
|
|
1022
|
+
}
|
|
1012
1023
|
emitList() {
|
|
1013
1024
|
this.emit({ type: "terminal_list", terminals: this.list() });
|
|
1014
1025
|
}
|
|
@@ -1539,7 +1550,7 @@ function backgroundResult(terminals, opts, command, partialText, silentSeconds,
|
|
|
1539
1550
|
text: pick(lang, `命令仍在持久终端 ai-bash 中运行(已连续 ${silentSeconds} 秒无输出,未结束)。` +
|
|
1540
1551
|
`本次调用不阻塞——命令继续在后台执行,结束时你会收到自动通知。\n` +
|
|
1541
1552
|
`已有输出:\n${partialZh}\n` +
|
|
1542
|
-
`要重新阻塞等它结束就用 terminal_wait(terminalId="ai-bash")(无需反复轮询);需要交互用 terminal_input / terminal_key(Ctrl+C 可终止)。`, `Command still running in the persistent terminal ai-bash (no output for ${silentSeconds}s, not finished)
|
|
1553
|
+
`要重新阻塞等它结束就用 terminal_wait(terminalId="ai-bash")(无需反复轮询);需要交互用 terminal_input / terminal_key(Ctrl+C 可终止)。`, `Command still running in the persistent terminal ai-bash (no output for ${silentSeconds}s, not finished). ` +
|
|
1543
1554
|
`This call does not block — the command keeps running in the background and you will be notified automatically when it finishes.\n` +
|
|
1544
1555
|
`Partial output:\n${partialEn}\n` +
|
|
1545
1556
|
`To block until it finishes, use terminal_wait(terminalId="ai-bash") (no polling needed); use terminal_input / terminal_key to interact (Ctrl+C aborts).`, "terminals.bash.background.running", { silentSeconds, partialZh, partialEn }),
|
|
@@ -45,6 +45,15 @@ export class WebUIContext {
|
|
|
45
45
|
this.emit = emit;
|
|
46
46
|
}
|
|
47
47
|
// -- widgets -------------------------------------------------------------
|
|
48
|
+
/** Register a widget whose lines come from a plain getter, re-evaluated on
|
|
49
|
+
* every render/refresh (no SDK Component required). Used for
|
|
50
|
+
* conversation-scoped overlays (e.g. markers) so switching conversations
|
|
51
|
+
* re-renders them without re-registering. */
|
|
52
|
+
setDynamicWidget(key, lines) {
|
|
53
|
+
this.widgets.set(key, { render: () => lines() });
|
|
54
|
+
this.lastLines.delete(key);
|
|
55
|
+
this.push();
|
|
56
|
+
}
|
|
48
57
|
/** Matches ExtensionUIContext's overloaded setWidget exactly. */
|
|
49
58
|
setWidget = (key, content, options) => {
|
|
50
59
|
void options;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-web-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.71.0",
|
|
4
4
|
"description": "Web chat interface for the pi coding agent, powered by the pi SDK (@earendil-works/pi-coding-agent) — one-command run, Docker/systemd/launchd deployable",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
"deploy/",
|
|
22
22
|
"extensions/",
|
|
23
23
|
"plugins/catalog.json",
|
|
24
|
+
"CHANGELOG.md",
|
|
24
25
|
"README.md",
|
|
25
26
|
"LICENSE"
|
|
26
27
|
],
|