pi-web-ui 0.70.0 → 0.72.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 +324 -0
- package/dist/server/agent-service.js +121 -22
- package/dist/server/client-state.js +56 -0
- package/dist/server/dsh/dsh-agent-service.js +59 -0
- package/dist/server/index.js +4 -0
- package/dist/server/locales.js +1 -1
- package/dist/server/model-admin.js +37 -9
- package/dist/server/protocol-version.js +1 -1
- package/package.json +5 -1
- package/themes/cyberpunk.css +9 -0
- package/themes/dazzle.css +9 -0
- package/themes/md-preview.css +9 -0
- package/themes/translucent.css +78 -0
- package/themes/transparent.css +78 -0
- package/themes/white.css +9 -0
- package/web/dist/assets/{TerminalPanel-UxgczH4c.js → TerminalPanel-SG88Uj0n.js} +1 -1
- package/web/dist/assets/index-CBZtMKV2.js +335 -0
- package/web/dist/assets/index-DAu6etAV.css +10 -0
- package/web/dist/index.html +2 -2
- package/web/dist/assets/index-BH5QutUc.css +0 -10
- package/web/dist/assets/index-CaiIOwy7.js +0 -335
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,324 @@
|
|
|
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
|
+
## [0.72.0] — 2026-09-09
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- 模型报错自动重试次数设置(`retryMaxAttempts`,对话设置):大模型 API 出错时按次数自动重试;次数用完本轮停止并标红,最后一轮红色报错旁有「重试」按钮(`retry_last`,协议 v15),手动再跑一轮;设为 0 则失败即停。面板值覆盖注入 SDK 默认(含子代理与会话重建)。
|
|
18
|
+
- 发布时翻译增量自动公示:`scripts/i18n-diff.mjs`(对比 base tag,统计前端 `zh/en` 与服务端 `pick` 新增/变更的 key)与 `scripts/release-notes.mjs`(拼 GitHub Release 说明,`### i18n` 现场生成;`npm run changelog:i18n` 自动维护本节);打 tag 推送后 Action 自动创建/更新 Release。
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- 多密钥按项目自愈:删除密钥时所有引用该密钥的项目跟随接管密钥(无剩余则解绑);清空服务商密钥时清掉全部项目的残留引用;切换到不存在的密钥不再种下 stale 引用;切项目自动恢复改为静默 + 不存在即删引用,切项目不再刷屏报错。
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
|
|
26
|
+
- 设置「消息显示」改名「对话」(中英 + 8 语言包同步)。
|
|
27
|
+
|
|
28
|
+
<!-- auto-i18n:start -->
|
|
29
|
+
### i18n
|
|
30
|
+
|
|
31
|
+
- 前端新增 key(4):`modelRetryAttempts`、`modelRetryHint`、`retryNow`、`retryLastTip`
|
|
32
|
+
- 前端中文变更(1):`settingsMessageDisplay`
|
|
33
|
+
- 前端英文变更(1):`settingsMessageDisplay`
|
|
34
|
+
<!-- auto-i18n:end -->
|
|
35
|
+
|
|
36
|
+
## [0.71.0] — 2026-09-09
|
|
37
|
+
|
|
38
|
+
### Added
|
|
39
|
+
|
|
40
|
+
- 全屏壁纸与容器背景变量,新增半透明(translucent)/全透明(transparent)主题。
|
|
41
|
+
- 右侧文件列表加"复制名称 / 复制路径"按钮。
|
|
42
|
+
|
|
43
|
+
### Fixed
|
|
44
|
+
|
|
45
|
+
- CollapsedMessage:`button` 改 `div`,窄消息区 rail 避让仅桌面生效。
|
|
46
|
+
|
|
47
|
+
### Changed
|
|
48
|
+
|
|
49
|
+
- prettier 收尾:`web/src/i18n.tsx` 上游 drift 格式化(post-#102)。
|
|
50
|
+
|
|
51
|
+
## [0.70.0] — 2026-09-09
|
|
52
|
+
|
|
53
|
+
### Added
|
|
54
|
+
|
|
55
|
+
- 全局共享设置:服务端持久化 + 服务端 quick-phrases 种子、quick-seed 标记、动态 marker overlay。
|
|
56
|
+
- 输入框快捷短语改为服务端下发种子;超长 notice 自动换行。
|
|
57
|
+
- 聊天壁纸设置基础(issue #100,含主题 cyberpunk 壁纸变量与 `wallpaper-settings` 单测;全屏壁纸与容器背景变量见 0.71.0)。
|
|
58
|
+
- 终端"运行中"列表只统计存活 PTY(countLive)。
|
|
59
|
+
|
|
60
|
+
### Fixed
|
|
61
|
+
|
|
62
|
+
- 语言包 slot 语法与换行转义;条件分支预渲染 segments。
|
|
63
|
+
|
|
64
|
+
### i18n
|
|
65
|
+
|
|
66
|
+
- 葡萄牙语 serverStrings 163 条翻译 + 文案润色。
|
|
67
|
+
|
|
68
|
+
### Changed
|
|
69
|
+
|
|
70
|
+
- 面板按钮对比度、主题英文名、思考/工具头重排。
|
|
71
|
+
|
|
72
|
+
## [0.69.0] — 2026-09-08
|
|
73
|
+
|
|
74
|
+
### Added
|
|
75
|
+
|
|
76
|
+
- 目标模式总开关:关闭时隐藏目标条,阻断向导与审查。
|
|
77
|
+
- `PI_WEB_TABS`(实例提供哪些 tab)与 `PI_WEB_MANAGED`(外部更新的实例)及文档。
|
|
78
|
+
- 首次访问语言跟随浏览器,而非固定默认。
|
|
79
|
+
|
|
80
|
+
### Fixed
|
|
81
|
+
|
|
82
|
+
- 用户气泡保留单个换行;快捷短语输入后重新聚焦。
|
|
83
|
+
- 子代理归属其所属会话(issue #95)。
|
|
84
|
+
- E2E 明确上报 zh locale;terminal-smoke 与终端工具默认关对齐。
|
|
85
|
+
|
|
86
|
+
### Changed
|
|
87
|
+
|
|
88
|
+
- pt-BR 文案润色;`index.mjs` 参数化查询(#97)。
|
|
89
|
+
|
|
90
|
+
## [0.68.2] — 2026-09-08
|
|
91
|
+
|
|
92
|
+
### Added
|
|
93
|
+
|
|
94
|
+
- 意大利语(it) locale;可下载语言包(核心只带 zh/en)。
|
|
95
|
+
- 标准 pi 引擎接入 `ask_user_question` 问卷。
|
|
96
|
+
- 文本块与思考块复制按钮。
|
|
97
|
+
|
|
98
|
+
### Fixed
|
|
99
|
+
|
|
100
|
+
- 网页终端里 vim 无法输入(提高 Vite 构建 target)。
|
|
101
|
+
- Android/Termux 死锁:服务端热路径消除全部 fork。
|
|
102
|
+
- pt-BR 字符串润色(markers 描述 + locale 列表报错)。
|
|
103
|
+
|
|
104
|
+
## [0.68.1] — 2026-09-07
|
|
105
|
+
|
|
106
|
+
### Added
|
|
107
|
+
|
|
108
|
+
- 运行轨迹时间线插件:`host.onRunEvent` 轨迹事件通道、harness 式轨迹分析 v2、vis-timeline 专业引擎(vendor 自带)、工具按名着色 + 图例、自绘即时悬浮层。
|
|
109
|
+
- 浏览器标题显示项目名。
|
|
110
|
+
|
|
111
|
+
### Fixed
|
|
112
|
+
|
|
113
|
+
- webmail:secret 存储失败时不再丢密码。
|
|
114
|
+
- 宽屏消息列/输入列按实测滚动条宽精确对齐;重试提示条幅与消息正文列同宽;goalbar 对齐。
|
|
115
|
+
|
|
116
|
+
## [0.68.0] — 2026-09-07
|
|
117
|
+
|
|
118
|
+
### Added
|
|
119
|
+
|
|
120
|
+
- 输入框快捷短语:一键发送 + 设置页管理。
|
|
121
|
+
|
|
122
|
+
## [0.67.0] — 2026-09-07
|
|
123
|
+
|
|
124
|
+
### Added
|
|
125
|
+
|
|
126
|
+
- 系统提示词模板组合(system prompt template composition)+ `edit_soft` 宽松编辑工具。
|
|
127
|
+
|
|
128
|
+
## [0.66.0] — 2026-09-07
|
|
129
|
+
|
|
130
|
+
### Added
|
|
131
|
+
|
|
132
|
+
- Termux(Android)安装指南。
|
|
133
|
+
- Mermaid 图表跟随当前主题。
|
|
134
|
+
|
|
135
|
+
### Fixed
|
|
136
|
+
|
|
137
|
+
- 同步 pi CLI 探测改为异步后台探测(解决死锁,#79)。
|
|
138
|
+
- Mermaid 主题同步与排版规整。
|
|
139
|
+
|
|
140
|
+
## [0.65.0] — 2026-09-06
|
|
141
|
+
|
|
142
|
+
### Added
|
|
143
|
+
|
|
144
|
+
- 插件市场 + fenced-code 渲染插件(mermaid 插件化)。
|
|
145
|
+
- 超宽屏聊天列开关。
|
|
146
|
+
|
|
147
|
+
## [0.64.8] — 2026-09-06
|
|
148
|
+
|
|
149
|
+
### Fixed
|
|
150
|
+
|
|
151
|
+
- 后台服务列表过滤桌面软件噪音进程。
|
|
152
|
+
|
|
153
|
+
## [0.64.7] — 2026-09-06
|
|
154
|
+
|
|
155
|
+
### Added
|
|
156
|
+
|
|
157
|
+
- 子代理父子链接树、保留与干净 rpc 绑定。
|
|
158
|
+
|
|
159
|
+
## [0.64.6] — 2026-09-06
|
|
160
|
+
|
|
161
|
+
### Added
|
|
162
|
+
|
|
163
|
+
- 子代理:错误透出、模型选择、`wait-for-all` 工具。
|
|
164
|
+
|
|
165
|
+
## [0.64.5] — 2026-09-06
|
|
166
|
+
|
|
167
|
+
### Fixed
|
|
168
|
+
|
|
169
|
+
- 重启浏览器恢复上次工作目录。
|
|
170
|
+
- 目录消失时列表/会话刷新不再崩溃(issue #74)。
|
|
171
|
+
|
|
172
|
+
## [0.64.4] — 2026-09-06
|
|
173
|
+
|
|
174
|
+
### Added
|
|
175
|
+
|
|
176
|
+
- 设置 → 显示:mermaid 图表渲染开关。
|
|
177
|
+
|
|
178
|
+
### Fixed
|
|
179
|
+
|
|
180
|
+
- 历史/最近项目遵循 `PI_CODING_AGENT_SESSION_DIR`。
|
|
181
|
+
|
|
182
|
+
## [0.64.3] — 2026-09-05
|
|
183
|
+
|
|
184
|
+
### Added
|
|
185
|
+
|
|
186
|
+
- `pi-web-ui --help` 中英双语(按 LANG 检测,#72)。
|
|
187
|
+
|
|
188
|
+
## [0.64.2] — 2026-09-05
|
|
189
|
+
|
|
190
|
+
### Added
|
|
191
|
+
|
|
192
|
+
- mermaid 代码块渲染为图表。
|
|
193
|
+
- 机器浏览模式:`@root` 机器根 + 绝对 wire 路径跨盘符浏览。
|
|
194
|
+
|
|
195
|
+
### Fixed
|
|
196
|
+
|
|
197
|
+
- `PI_WEB_TOKEN` 改口令后 cookie 自动刷新/过期,一次 `?token=` 即恢复(issue #71)。
|
|
198
|
+
- mermaid 原生 SVG 尺寸、流式路径渲染、取消渲染清理;宽图表可读宽度保持。
|
|
199
|
+
|
|
200
|
+
## [0.64.1] — 2026-09-05
|
|
201
|
+
|
|
202
|
+
### Added
|
|
203
|
+
|
|
204
|
+
- 33 条 notice 的英文 textEn、locale 实时退出横幅与页面标题。
|
|
205
|
+
|
|
206
|
+
### Fixed
|
|
207
|
+
|
|
208
|
+
- 移动端发送按钮盖过思考强度底弹层(#70)。
|
|
209
|
+
- Windows 下 stale-marker 单测时间戳(pre-1970 mtime 回绕到 2106)。
|
|
210
|
+
|
|
211
|
+
## [0.64.0] — 2026-09-04
|
|
212
|
+
|
|
213
|
+
### Added
|
|
214
|
+
|
|
215
|
+
- 全局提示词历史、目录选择器(含新建文件夹)与 UI 抛光。
|
|
216
|
+
|
|
217
|
+
## [0.63.4] — 2026-09-04
|
|
218
|
+
|
|
219
|
+
- 版本重发(随带 `rename` 内置 marker 一行修正),无功能变更。
|
|
220
|
+
|
|
221
|
+
## [0.63.3] — 2026-09-04
|
|
222
|
+
|
|
223
|
+
### Added
|
|
224
|
+
|
|
225
|
+
- PWA 支持与移动端键盘回车换行(#64);会话结束/需输入时桌面通知(#65);PWA 资源与通知图标子路径感知(nginx `/pi/` 部署)。
|
|
226
|
+
- 历史对话与终端标签 UI 重命名(#63);`/name` 命令重命名当前会话(#66)。
|
|
227
|
+
- 行内 marker 系统:todo/svc/notify/rename,无需工具往返。
|
|
228
|
+
|
|
229
|
+
### Fixed
|
|
230
|
+
|
|
231
|
+
- SCM 提交遵循暂存区,并新增"提交全部"。
|
|
232
|
+
|
|
233
|
+
## [0.63.2] — 2026-09-03
|
|
234
|
+
|
|
235
|
+
### Fixed
|
|
236
|
+
|
|
237
|
+
- 主题名跟随界面语言(英文用 nameEn,#61)。
|
|
238
|
+
- terminal-smoke 改轮询 shell banner,消除慢 CI 机器抖动。
|
|
239
|
+
|
|
240
|
+
## [0.63.1] — 2026-09-03
|
|
241
|
+
|
|
242
|
+
### Fixed
|
|
243
|
+
|
|
244
|
+
- 左侧栏折叠区块统一样式:折叠后三标题一致、切换不位移、收起按钮垂直居中、标题高亮通栏居中。
|
|
245
|
+
|
|
246
|
+
## [0.63.0] — 2026-09-03
|
|
247
|
+
|
|
248
|
+
### Added
|
|
249
|
+
|
|
250
|
+
- 子代理模板:角色系统提示词(append/replace)+ 技能/扩展白名单,AI 可选用、可停用,内置 6 个默认模板。
|
|
251
|
+
- 对话可从运行列表移出(dismiss_conversation)+ pi-subagents 存活检测(WIP)。
|
|
252
|
+
|
|
253
|
+
### Changed
|
|
254
|
+
|
|
255
|
+
- 全仓库 prettier(Tab/120)与 oxlint 接入 CI(#57/#58)。
|
|
256
|
+
- DSH notice、终端退出横幅、目标状态跟随界面语言(#54 及后续)。
|
|
257
|
+
|
|
258
|
+
### Fixed
|
|
259
|
+
|
|
260
|
+
- 插件目录搬迁后坏掉的冒烟测试 fixture(#59)。
|
|
261
|
+
|
|
262
|
+
## [0.62.1] — 2026-09-03
|
|
263
|
+
|
|
264
|
+
### Fixed
|
|
265
|
+
|
|
266
|
+
- SCM 的 `git add/reset` 路径引号闭合(issue #51)。
|
|
267
|
+
|
|
268
|
+
## [0.62.0] — 2026-09-02
|
|
269
|
+
|
|
270
|
+
### Added
|
|
271
|
+
|
|
272
|
+
- 排队消息可删除(✕);底部栏实时缓存命中率与生成速率。
|
|
273
|
+
|
|
274
|
+
### Fixed
|
|
275
|
+
|
|
276
|
+
- `terminal_create` 带 title 下发,服务端不再用中文默认覆盖。
|
|
277
|
+
|
|
278
|
+
## [0.61.0] — 2026-09-02
|
|
279
|
+
|
|
280
|
+
### Added
|
|
281
|
+
|
|
282
|
+
- 内置服务商多密钥管理 + 项目级模型/key 记忆。
|
|
283
|
+
|
|
284
|
+
## 更早版本
|
|
285
|
+
|
|
286
|
+
0.62 之前的版本没有逐版归档,以下是发布提交记录中的要点(详见 `git log`):
|
|
287
|
+
|
|
288
|
+
- 0.60.0(2026-09-02):移除 `PORT` 环境变量兼容,仅保留 `PI_WEB_PORT`;修复 `--host` 直启失效。
|
|
289
|
+
- 0.59.0(2026-09-01)、0.58.0(2026-08-30)。
|
|
290
|
+
- 0.56.1 / 0.55.0 / 0.53.0 / 0.51.0(2026-08-30)。
|
|
291
|
+
- 0.50.0 / 0.49.0(2026-08-29):0.49.0 起移除 Electron 桌面壳,只保留纯 Web。
|
|
292
|
+
- 0.48.3 / 0.48.2(2026-08-29):pi SDK 升到 `^0.84.4`。
|
|
293
|
+
- 0.44.1(2026-08-28)。
|
|
294
|
+
- 0.35.1(2026-08-27):编辑重问保留附件(#18)+ 全窗口拖放(#19)。
|
|
295
|
+
- 0.29.0(2026-08-23):全局搜索弹窗(Ctrl+K)+ 消息列表惰性窗口化。
|
|
296
|
+
|
|
297
|
+
[Unreleased]: https://github.com/xing-shuyin/pi-web-ui/compare/v0.72.0...main
|
|
298
|
+
[0.72.0]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.72.0
|
|
299
|
+
[0.71.0]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.71.0
|
|
300
|
+
[0.70.0]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.70.0
|
|
301
|
+
[0.69.0]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.69.0
|
|
302
|
+
[0.68.2]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.68.2
|
|
303
|
+
[0.68.1]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.68.1
|
|
304
|
+
[0.68.0]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.68.0
|
|
305
|
+
[0.67.0]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.67.0
|
|
306
|
+
[0.66.0]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.66.0
|
|
307
|
+
[0.65.0]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.65.0
|
|
308
|
+
[0.64.8]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.64.8
|
|
309
|
+
[0.64.7]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.64.7
|
|
310
|
+
[0.64.6]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.64.6
|
|
311
|
+
[0.64.5]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.64.5
|
|
312
|
+
[0.64.4]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.64.4
|
|
313
|
+
[0.64.3]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.64.3
|
|
314
|
+
[0.64.2]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.64.2
|
|
315
|
+
[0.64.1]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.64.1
|
|
316
|
+
[0.64.0]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.64.0
|
|
317
|
+
[0.63.4]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.63.4
|
|
318
|
+
[0.63.3]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.63.3
|
|
319
|
+
[0.63.2]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.63.2
|
|
320
|
+
[0.63.1]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.63.1
|
|
321
|
+
[0.63.0]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.63.0
|
|
322
|
+
[0.62.1]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.62.1
|
|
323
|
+
[0.62.0]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.62.0
|
|
324
|
+
[0.61.0]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.61.0
|
|
@@ -724,6 +724,8 @@ export class ClientSession {
|
|
|
724
724
|
conv.listed = true;
|
|
725
725
|
conv.title = subagentTitle(prompt);
|
|
726
726
|
this.convs.set(conv.id, conv);
|
|
727
|
+
// 子代理不走 bindSession——这里同样注入面板的重试次数覆盖。
|
|
728
|
+
this.applyRetryOverrides();
|
|
727
729
|
// 扩展绑定(rpc 模式;uiContext 只给无害的 mock theme/status 槽,避免与主对话
|
|
728
730
|
// 的 widget 冲突。无 uiContext 时扩展的 ctx.ui.theme.fg 会打到 TUI 真 theme
|
|
729
731
|
// 代理上抛 "Theme not initialized",每个扩展一条 error toast。)
|
|
@@ -1535,6 +1537,10 @@ export class ClientSession {
|
|
|
1535
1537
|
},
|
|
1536
1538
|
});
|
|
1537
1539
|
conv.unsubscribe = conv.session.subscribe((event) => this.onEvent(conv, event));
|
|
1540
|
+
// 新会话 / 切换会话 / 强杀重建的必经之路:刚创建的 runtime 用的是 SDK
|
|
1541
|
+
// 默认重试 3 次——这里把面板的 retryMaxAttempts 覆盖注入,否则“设了 6
|
|
1542
|
+
// 次还是按 3 次重试”。已存在会话重复注入是幂等的(同值覆盖)。
|
|
1543
|
+
this.applyRetryOverrides();
|
|
1538
1544
|
this.scheduleSnapshot();
|
|
1539
1545
|
this.webUi.refresh();
|
|
1540
1546
|
this.startWidgetsTimer();
|
|
@@ -2671,8 +2677,11 @@ export class ClientSession {
|
|
|
2671
2677
|
setProviderApiKey(provider, apiKey) {
|
|
2672
2678
|
return this.modelAdmin.setProviderApiKey(provider, apiKey);
|
|
2673
2679
|
}
|
|
2674
|
-
clearProviderApiKey(provider) {
|
|
2675
|
-
|
|
2680
|
+
async clearProviderApiKey(provider) {
|
|
2681
|
+
await this.modelAdmin.clearProviderApiKey(provider);
|
|
2682
|
+
// The provider is back to unconfigured — drop its key preference in
|
|
2683
|
+
// EVERY project, otherwise each project switch re-tries a restore.
|
|
2684
|
+
this.stateStore.deleteProviderEverywhere(provider.trim());
|
|
2676
2685
|
}
|
|
2677
2686
|
listProviders() {
|
|
2678
2687
|
return this.modelAdmin.listProviders();
|
|
@@ -2708,23 +2717,27 @@ export class ClientSession {
|
|
|
2708
2717
|
this.stateStore.saveProjectProviderKey(this.clientId, this.cwd, provider, active);
|
|
2709
2718
|
}
|
|
2710
2719
|
async activateProviderKey(provider, keyName) {
|
|
2711
|
-
await this.modelAdmin.activateProviderKey(provider, keyName);
|
|
2712
|
-
|
|
2720
|
+
const ok = await this.modelAdmin.activateProviderKey(provider, keyName);
|
|
2721
|
+
// Only remember existing keys — a failed switch (deleted key) must not
|
|
2722
|
+
// plant a stale reference that errors on every later project switch.
|
|
2723
|
+
if (ok)
|
|
2724
|
+
this.stateStore.saveProjectProviderKey(this.clientId, this.cwd, provider, keyName);
|
|
2725
|
+
else
|
|
2726
|
+
this.stateStore.deleteProjectProviderKey(this.clientId, this.cwd, provider);
|
|
2713
2727
|
}
|
|
2714
2728
|
async removeProviderKey(provider, keyName) {
|
|
2715
2729
|
await this.modelAdmin.removeProviderKey(provider, keyName);
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
else
|
|
2722
|
-
this.stateStore.deleteProjectProviderKey(this.clientId, this.cwd, provider);
|
|
2723
|
-
}
|
|
2730
|
+
// The deletion may have been made from another project: every project
|
|
2731
|
+
// still pinned to the deleted key must follow the key that took over
|
|
2732
|
+
// (or drop the pin when no keys remain), not just the current one.
|
|
2733
|
+
const active = this.modelAdmin.getActiveKeyName(provider);
|
|
2734
|
+
this.stateStore.repointDeletedKeyEverywhere(provider, keyName, active);
|
|
2724
2735
|
}
|
|
2725
2736
|
/** Restore per-project provider keys when entering a project. For each
|
|
2726
2737
|
* provider that has a saved key for `cwd`, activate it if it differs from
|
|
2727
|
-
* the current global active. Silent
|
|
2738
|
+
* the current global active. Silent + self-healing: a saved key deleted
|
|
2739
|
+
* elsewhere is dropped without notifying (a noisy error here is what
|
|
2740
|
+
* haunted project switches after a key deletion). */
|
|
2728
2741
|
async restoreProjectProviderKeysForCwd(cwd) {
|
|
2729
2742
|
const saved = this.stateStore.getProjectProviderKeys(this.clientId, cwd);
|
|
2730
2743
|
if (!saved)
|
|
@@ -2733,15 +2746,17 @@ export class ClientSession {
|
|
|
2733
2746
|
const cur = this.modelAdmin.getActiveKeyName(provider);
|
|
2734
2747
|
if (cur === keyName)
|
|
2735
2748
|
continue;
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
catch {
|
|
2740
|
-
// saved key may have been deleted — ignore
|
|
2749
|
+
if (!this.modelAdmin.hasProviderKey(provider, keyName)) {
|
|
2750
|
+
this.stateStore.deleteProjectProviderKey(this.clientId, cwd, provider);
|
|
2751
|
+
continue;
|
|
2741
2752
|
}
|
|
2753
|
+
const ok = await this.modelAdmin.activateProviderKey(provider, keyName, { silent: true });
|
|
2754
|
+
if (!ok)
|
|
2755
|
+
this.stateStore.deleteProjectProviderKey(this.clientId, cwd, provider);
|
|
2742
2756
|
}
|
|
2743
2757
|
}
|
|
2744
|
-
/** When a model is set, ensure its provider's per-project key is restored.
|
|
2758
|
+
/** When a model is set, ensure its provider's per-project key is restored.
|
|
2759
|
+
* Silent + self-healing like the bulk restore above. */
|
|
2745
2760
|
async restoreKeyForModel(modelId, cwd) {
|
|
2746
2761
|
const slash = modelId.indexOf("/");
|
|
2747
2762
|
if (slash <= 0)
|
|
@@ -2753,10 +2768,13 @@ export class ClientSession {
|
|
|
2753
2768
|
const cur = this.modelAdmin.getActiveKeyName(provider);
|
|
2754
2769
|
if (cur === saved)
|
|
2755
2770
|
return;
|
|
2756
|
-
|
|
2757
|
-
|
|
2771
|
+
if (!this.modelAdmin.hasProviderKey(provider, saved)) {
|
|
2772
|
+
this.stateStore.deleteProjectProviderKey(this.clientId, cwd, provider);
|
|
2773
|
+
return;
|
|
2758
2774
|
}
|
|
2759
|
-
|
|
2775
|
+
const ok = await this.modelAdmin.activateProviderKey(provider, saved, { silent: true });
|
|
2776
|
+
if (!ok)
|
|
2777
|
+
this.stateStore.deleteProjectProviderKey(this.clientId, cwd, provider);
|
|
2760
2778
|
}
|
|
2761
2779
|
/** Remember the just-selected model (and the key that was active for its
|
|
2762
2780
|
* provider) for the current project. Called IMMEDIATELY on model selection —
|
|
@@ -3129,6 +3147,87 @@ export class ClientSession {
|
|
|
3129
3147
|
await this.interruptRun(this.conv, "已停止");
|
|
3130
3148
|
this.flushSnapshot();
|
|
3131
3149
|
}
|
|
3150
|
+
/** 手动重试上次失败的模型调用:自动重试次数(retryMaxAttempts)用完后
|
|
3151
|
+
* 本轮已停止并标红,用户点「重试」再触发一轮 LLM 调用。不新增用户气泡——
|
|
3152
|
+
* 用 display:false 的 custom 消息 triggerTurn 续跑,模型基于完整上下文
|
|
3153
|
+
* (含上次报错)继续生成。流式中 / 无可重试失败时只发 notice 拒绝。 */
|
|
3154
|
+
async retryLast() {
|
|
3155
|
+
const conv = this.conv;
|
|
3156
|
+
try {
|
|
3157
|
+
if (this.quiesceBlocked())
|
|
3158
|
+
return;
|
|
3159
|
+
const s = this.session;
|
|
3160
|
+
if (s.isStreaming) {
|
|
3161
|
+
this.emit({
|
|
3162
|
+
type: "notice",
|
|
3163
|
+
level: "info",
|
|
3164
|
+
text: "对话正在生成中,无需重试",
|
|
3165
|
+
textEn: "The conversation is still generating — no need to retry",
|
|
3166
|
+
});
|
|
3167
|
+
return;
|
|
3168
|
+
}
|
|
3169
|
+
if (conv.retryState) {
|
|
3170
|
+
this.emit({
|
|
3171
|
+
type: "notice",
|
|
3172
|
+
level: "info",
|
|
3173
|
+
text: "正在自动重试中,稍候即可",
|
|
3174
|
+
textEn: "Auto-retry is in progress — please wait",
|
|
3175
|
+
});
|
|
3176
|
+
return;
|
|
3177
|
+
}
|
|
3178
|
+
// 最后一轮失败的证据:末尾 stopReason=error 的 assistant 消息。
|
|
3179
|
+
let failed = null;
|
|
3180
|
+
try {
|
|
3181
|
+
const msgs = s.agent.state.messages;
|
|
3182
|
+
for (let i = msgs.length - 1; i >= 0; i--) {
|
|
3183
|
+
const m = msgs[i];
|
|
3184
|
+
if (m.role !== "assistant")
|
|
3185
|
+
continue;
|
|
3186
|
+
if ((typeof m.errorMessage === "string" && m.errorMessage.trim()) || m.stopReason === "error") {
|
|
3187
|
+
failed = m;
|
|
3188
|
+
}
|
|
3189
|
+
break;
|
|
3190
|
+
}
|
|
3191
|
+
}
|
|
3192
|
+
catch {
|
|
3193
|
+
// 会话替换中——按无可重试处理
|
|
3194
|
+
}
|
|
3195
|
+
if (!failed) {
|
|
3196
|
+
this.emit({
|
|
3197
|
+
type: "notice",
|
|
3198
|
+
level: "info",
|
|
3199
|
+
text: "没有可重试的失败:上一轮没有报错结束",
|
|
3200
|
+
textEn: "Nothing to retry: the last turn did not end with an error",
|
|
3201
|
+
});
|
|
3202
|
+
return;
|
|
3203
|
+
}
|
|
3204
|
+
// 轨迹用:下一轮 agent_start 消费(否则插件回退为「继续执行」)。
|
|
3205
|
+
conv.pendingTask = "手动重试上次失败的模型请求";
|
|
3206
|
+
await s.sendCustomMessage({
|
|
3207
|
+
customType: "manual-retry",
|
|
3208
|
+
content: [
|
|
3209
|
+
{
|
|
3210
|
+
type: "text",
|
|
3211
|
+
text: "(系统:用户点击了「重试」。请基于完整上下文重新发起上一次失败的模型请求,继续完成用户的任务。)",
|
|
3212
|
+
},
|
|
3213
|
+
],
|
|
3214
|
+
display: false,
|
|
3215
|
+
}, { triggerTurn: true });
|
|
3216
|
+
conv.promptedSinceActive = true;
|
|
3217
|
+
conv.lastActiveAt = Date.now();
|
|
3218
|
+
conv.lastSdkEventAt = Date.now();
|
|
3219
|
+
conv.stallNoticed = false;
|
|
3220
|
+
}
|
|
3221
|
+
catch (err) {
|
|
3222
|
+
this.emit({
|
|
3223
|
+
type: "notice",
|
|
3224
|
+
level: "error",
|
|
3225
|
+
text: `手动重试失败:${err.message}`,
|
|
3226
|
+
textEn: `Manual retry failed: ${err.message}`,
|
|
3227
|
+
});
|
|
3228
|
+
}
|
|
3229
|
+
this.flushSnapshot();
|
|
3230
|
+
}
|
|
3132
3231
|
/**
|
|
3133
3232
|
* Remove ONE queued prompt text (the ✕ on a pending bubble) so it is neither
|
|
3134
3233
|
* shown nor eventually delivered. The pi SDK has no per-item queue API, so we
|
|
@@ -331,6 +331,62 @@ export class ClientStateStore {
|
|
|
331
331
|
}
|
|
332
332
|
this.save();
|
|
333
333
|
}
|
|
334
|
+
/** Remove one provider from EVERY project's saved keys (all clients, all
|
|
335
|
+
* cwds) — e.g. the provider was cleared and returned to unconfigured.
|
|
336
|
+
* Returns the number of entries removed. */
|
|
337
|
+
deleteProviderEverywhere(provider) {
|
|
338
|
+
const all = this.load();
|
|
339
|
+
let removed = 0;
|
|
340
|
+
for (const state of Object.values(all)) {
|
|
341
|
+
const map = state.projectProviderKeys;
|
|
342
|
+
if (!map)
|
|
343
|
+
continue;
|
|
344
|
+
for (const [cwd, inner] of Object.entries(map)) {
|
|
345
|
+
if (inner && provider in inner) {
|
|
346
|
+
delete inner[provider];
|
|
347
|
+
removed++;
|
|
348
|
+
if (Object.keys(inner).length === 0)
|
|
349
|
+
delete map[cwd];
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
if (map && Object.keys(map).length === 0)
|
|
353
|
+
delete state.projectProviderKeys;
|
|
354
|
+
}
|
|
355
|
+
if (removed > 0)
|
|
356
|
+
this.save();
|
|
357
|
+
return removed;
|
|
358
|
+
}
|
|
359
|
+
/** Fix every project that still references a deleted key: point it at the
|
|
360
|
+
* key that took over (`newActive`), or drop the reference when the
|
|
361
|
+
* provider has no keys left (`newActive` null). A key deletion made in
|
|
362
|
+
* one project must not keep haunting every other project that once used
|
|
363
|
+
* the same key on every project switch. Returns entries touched. */
|
|
364
|
+
repointDeletedKeyEverywhere(provider, deletedKeyName, newActive) {
|
|
365
|
+
const all = this.load();
|
|
366
|
+
let touched = 0;
|
|
367
|
+
for (const state of Object.values(all)) {
|
|
368
|
+
const map = state.projectProviderKeys;
|
|
369
|
+
if (!map)
|
|
370
|
+
continue;
|
|
371
|
+
for (const [cwd, inner] of Object.entries(map)) {
|
|
372
|
+
if (inner?.[provider] !== deletedKeyName)
|
|
373
|
+
continue;
|
|
374
|
+
if (newActive)
|
|
375
|
+
inner[provider] = newActive;
|
|
376
|
+
else {
|
|
377
|
+
delete inner[provider];
|
|
378
|
+
if (Object.keys(inner).length === 0)
|
|
379
|
+
delete map[cwd];
|
|
380
|
+
}
|
|
381
|
+
touched++;
|
|
382
|
+
}
|
|
383
|
+
if (map && Object.keys(map).length === 0)
|
|
384
|
+
delete state.projectProviderKeys;
|
|
385
|
+
}
|
|
386
|
+
if (touched > 0)
|
|
387
|
+
this.save();
|
|
388
|
+
return touched;
|
|
389
|
+
}
|
|
334
390
|
/** Get the model the user last selected in a project, or undefined. */
|
|
335
391
|
getProjectModel(clientId, cwd) {
|
|
336
392
|
return this.load()[clientId]?.projectModels?.[cwd];
|
|
@@ -1560,6 +1560,65 @@ export class DshClientSession {
|
|
|
1560
1560
|
textEn: "The DSH engine cannot stop bash alone (stop the whole conversation instead)",
|
|
1561
1561
|
});
|
|
1562
1562
|
}
|
|
1563
|
+
/** 手动重试上次失败的模型调用:找到最后一条用户提问并重发一次
|
|
1564
|
+
* (DSH 运行时以 prompt 驱动回合,无 triggerTurn 语义)。流式中 /
|
|
1565
|
+
* 无可重试失败时只发 notice 拒绝。 */
|
|
1566
|
+
async retryLast() {
|
|
1567
|
+
const conv = this.conv;
|
|
1568
|
+
try {
|
|
1569
|
+
if (this.quiesceBlocked())
|
|
1570
|
+
return;
|
|
1571
|
+
if (conv.isStreaming) {
|
|
1572
|
+
this.emit({
|
|
1573
|
+
type: "notice",
|
|
1574
|
+
level: "info",
|
|
1575
|
+
text: "对话正在生成中,无需重试",
|
|
1576
|
+
textEn: "The conversation is still generating — no need to retry",
|
|
1577
|
+
});
|
|
1578
|
+
return;
|
|
1579
|
+
}
|
|
1580
|
+
let failed = false;
|
|
1581
|
+
for (let i = conv.messages.length - 1; i >= 0; i--) {
|
|
1582
|
+
const m = conv.messages[i];
|
|
1583
|
+
if (m.role !== "assistant")
|
|
1584
|
+
continue;
|
|
1585
|
+
failed = !!m.errorMessage;
|
|
1586
|
+
break;
|
|
1587
|
+
}
|
|
1588
|
+
if (!failed) {
|
|
1589
|
+
this.emit({
|
|
1590
|
+
type: "notice",
|
|
1591
|
+
level: "info",
|
|
1592
|
+
text: "没有可重试的失败:上一轮没有报错结束",
|
|
1593
|
+
textEn: "Nothing to retry: the last turn did not end with an error",
|
|
1594
|
+
});
|
|
1595
|
+
return;
|
|
1596
|
+
}
|
|
1597
|
+
let lastUser = "";
|
|
1598
|
+
for (let i = conv.messages.length - 1; i >= 0; i--) {
|
|
1599
|
+
const m = conv.messages[i];
|
|
1600
|
+
if (m.role !== "user")
|
|
1601
|
+
continue;
|
|
1602
|
+
lastUser = m.content
|
|
1603
|
+
.map((c) => ("text" in c ? c.text : ""))
|
|
1604
|
+
.join("")
|
|
1605
|
+
.trim();
|
|
1606
|
+
if (lastUser)
|
|
1607
|
+
break;
|
|
1608
|
+
}
|
|
1609
|
+
const lang = this.getLang();
|
|
1610
|
+
await this.prompt(lastUser || pick(lang, "请继续", "Please continue", "dsh.prompt.continue"));
|
|
1611
|
+
}
|
|
1612
|
+
catch (err) {
|
|
1613
|
+
this.emit({
|
|
1614
|
+
type: "notice",
|
|
1615
|
+
level: "error",
|
|
1616
|
+
text: `手动重试失败:${err.message}`,
|
|
1617
|
+
textEn: `Manual retry failed: ${err.message}`,
|
|
1618
|
+
});
|
|
1619
|
+
}
|
|
1620
|
+
this.flushSnapshot();
|
|
1621
|
+
}
|
|
1563
1622
|
// -----------------------------------------------------------------------
|
|
1564
1623
|
// 后台任务
|
|
1565
1624
|
// -----------------------------------------------------------------------
|
package/dist/server/index.js
CHANGED
|
@@ -705,6 +705,9 @@ wss.on("connection", (ws) => {
|
|
|
705
705
|
case "abort_bash":
|
|
706
706
|
void cs.abortBash();
|
|
707
707
|
break;
|
|
708
|
+
case "retry_last":
|
|
709
|
+
void cs.retryLast();
|
|
710
|
+
break;
|
|
708
711
|
case "kill_background_server":
|
|
709
712
|
void cs.killBackgroundServer(msg.port, msg.taskId);
|
|
710
713
|
break;
|
|
@@ -941,6 +944,7 @@ wss.on("connection", (ws) => {
|
|
|
941
944
|
visionBridgePromptMode: msg.visionBridgePromptMode,
|
|
942
945
|
visionBridgePrompt: msg.visionBridgePrompt,
|
|
943
946
|
subagentDefaultModel: msg.subagentDefaultModel,
|
|
947
|
+
retryMaxAttempts: msg.retryMaxAttempts,
|
|
944
948
|
reviewPrompt: msg.reviewPrompt,
|
|
945
949
|
reviewDisabledSkills: msg.reviewDisabledSkills,
|
|
946
950
|
markersEnabled: msg.markersEnabled,
|
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" },
|