@michengai/dsh-codex-ui 1.1.8 → 1.1.10

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.
@@ -0,0 +1,913 @@
1
+ //#region src/client/locales.ts
2
+ const zh = {
3
+ "home.resizeInput": "拖动调整输入区宽度",
4
+ "home.projectSearch": "搜索项目",
5
+ "home.projectEmpty": "没有匹配的项目",
6
+ "home.suggestions": "开始一项任务",
7
+ "home.workspace": "请先选择工作区,再选择任务。",
8
+ "home.draft": "已保留当前草稿和附件;清空输入后可填入任务建议。",
9
+ "home.busy": "当前输入正在处理中,请稍后再试。",
10
+ "home.explore": "探索并理解代码",
11
+ "home.build": "构建新功能、应用或工具",
12
+ "home.review": "审查代码并提出修改建议",
13
+ "home.fix": "修复问题和失败",
14
+ "home.explore.task1": "了解项目结构",
15
+ "home.explore.prompt1": "请探索当前工作区的代码,解释项目结构、主要入口和模块之间的关系。",
16
+ "home.explore.task2": "理解一项功能",
17
+ "home.explore.prompt2": "我想了解一项功能的实现。先问我具体是哪项功能,再沿调用关系解释。",
18
+ "home.build.task1": "实现一项新功能",
19
+ "home.build.prompt1": "我想在当前项目中实现一项新功能。先和我确认需求与验收标准,再实施。",
20
+ "home.build.task2": "构建一个小工具",
21
+ "home.build.prompt2": "我想构建一个小工具。先问我目标、输入和输出,再结合当前项目提出方案。",
22
+ "home.review.task1": "评审当前改动",
23
+ "home.review.prompt1": "请评审当前工作区的未提交改动,优先指出真实缺陷、回归风险和缺少的测试;先不要修改。",
24
+ "home.review.task2": "检查代码质量",
25
+ "home.review.prompt2": "请检查当前项目的代码质量,给出有源码依据的改进建议,先评估不改动。",
26
+ "home.fix.task1": "排查一个问题",
27
+ "home.fix.prompt1": "我遇到了一个问题。先问我复现步骤、预期和实际表现,再定位根因并修复。",
28
+ "home.fix.task2": "修复失败的测试",
29
+ "home.fix.prompt2": "请运行当前项目适用的测试,定位失败根因并做最小修复,保留有效的回归覆盖。",
30
+ "usage.title": "使用统计",
31
+ "usage.loading": "正在加载费用面板…",
32
+ "usage.failed": "费用面板暂时无法嵌入,可重试或打开原弹窗。",
33
+ "usage.closed": "费用面板已关闭。",
34
+ "usage.reopen": "重新打开费用面板",
35
+ "usage.retry": "重试",
36
+ "usage.original": "打开原弹窗",
37
+ "settings.title": "设置",
38
+ "settings.back": "返回应用",
39
+ "settings.openDocument": "打开配置文件",
40
+ "settings.advanced": "高级",
41
+ "settings.documentTitle": "配置文件",
42
+ "settings.documentDescription": "在外部编辑器中查看和编辑配置",
43
+ "settings.documentOpen": "打开",
44
+ "settings.documentOpening": "打开中…",
45
+ "settings.openDocumentError": "无法打开配置文件",
46
+ "settings.search": "搜索设置…",
47
+ "settings.personal": "个人",
48
+ "settings.integrations": "集成",
49
+ "settings.records": "记录与应用",
50
+ "settings.general": "常规",
51
+ "settings.permissions": "权限",
52
+ "settings.editor": "编辑器",
53
+ "settings.noResults": "没有匹配的设置分类",
54
+ "settings.filterHint": "仅筛选导航,选择分类后切换当前页面。",
55
+ "settings.disconnected": "连接异常",
56
+ "settings.reconnect": "立即重连",
57
+ "settings.connecting": "连接中",
58
+ "settings.recovered": "连接成功",
59
+ "input.historyHint": "↑↓ 翻历史",
60
+ "sidebar.label": "DSH 导航",
61
+ "sidebar.expand": "展开侧边栏",
62
+ "sidebar.collapse": "收缩侧边栏",
63
+ "sidebar.newTask": "新建任务",
64
+ "sidebar.search": "搜索会话",
65
+ "sidebar.mainMenu": "主菜单",
66
+ "sidebar.extensions": "扩展管理",
67
+ "sidebar.experts": "专家",
68
+ "sidebar.skills": "技能",
69
+ "sidebar.plugins": "插件",
70
+ "sidebar.marketplace": "插件市场",
71
+ "sidebar.connectors": "连接器",
72
+ "sidebar.schedule": "定时任务",
73
+ "sidebar.assistant": "IM助理",
74
+ "sidebar.imSettings": "IM助理",
75
+ "sidebar.tasksTab": "任务",
76
+ "sidebar.channelsTab": "频道",
77
+ "sidebar.scheduleTab": "定时",
78
+ "sidebar.runsTab": "执行记录",
79
+ "sidebar.overviewTab": "任务总览",
80
+ "channels.empty": "还没有频道会话。先在设置 → IM助理 里连接渠道。",
81
+ "channels.loadError": "无法读取频道会话。",
82
+ "channel.dingtalk": "钉钉",
83
+ "channel.feishu": "飞书",
84
+ "channel.lark": "Lark",
85
+ "channel.weixin": "微信",
86
+ "channel.wecom": "企业微信",
87
+ "channel.qq": "QQ",
88
+ "channel.telegram": "Telegram",
89
+ "channel.unknown": "频道",
90
+ "schedule.empty": "还没有定时任务运行记录。",
91
+ "schedule.groupActions": "{name} 的任务操作",
92
+ "schedule.taskSettings": "任务设置",
93
+ "schedule.archiveGroup": "归档整组会话",
94
+ "schedule.archiveGroupConfirm": "确认归档",
95
+ "schedule.archiveGroupDescription": "将归档“{name}”下的 {count} 个会话。归档后可以在“设置 → 已归档”中恢复。",
96
+ "schedule.archiveGroupPending": "正在归档整组会话…",
97
+ "schedule.archiveGroupPartial": "已归档 {archived} 个会话,另有 {failed} 个失败。可以重试剩余会话。",
98
+ "search.placeholder": "搜索会话、设置和操作",
99
+ "search.empty": "没有匹配的结果。",
100
+ "search.sessions": "会话",
101
+ "search.settings": "设置",
102
+ "search.actions": "快捷操作",
103
+ "workspace.label": "工作区会话",
104
+ "workspace.pinned": "置顶",
105
+ "workspace.pinnedEmpty": "拖动项目到此处置顶",
106
+ "workspace.projects": "项目",
107
+ "workspace.ungrouped": "未分组",
108
+ "workspace.createGroup": "新建分组",
109
+ "workspace.createGroupDescription": "输入分组名称,项目不会自动移动。",
110
+ "workspace.groupName": "分组名称",
111
+ "workspace.moveToGroup": "移动到分组",
112
+ "workspace.removeFromGroup": "移至未分组",
113
+ "workspace.deleteGroup": "删除分组“{name}”",
114
+ "workspace.deleteGroupAction": "删除分组",
115
+ "workspace.deleteGroupDescription": "删除分组只会将其中项目移至未分组,不会删除项目或会话。",
116
+ "workspace.groupActions": "分组“{name}”的操作",
117
+ "workspace.renameGroup": "重命名分组",
118
+ "workspace.recent": "最近",
119
+ "workspace.recentEmpty": "无聊天",
120
+ "workspace.noChat": "暂无聊天",
121
+ "workspace.empty": "暂无项目",
122
+ "workspace.newSession": "新建会话",
123
+ "workspace.rename": "重命名项目",
124
+ "workspace.pin": "置顶项目",
125
+ "workspace.unpin": "取消置顶",
126
+ "workspace.archive": "归档所有会话",
127
+ "workspace.archiveTitle": "归档 {count} 个会话?",
128
+ "workspace.archiveDescription": "这会归档“{name}”中的全部会话,项目和文件夹仍会保留。",
129
+ "workspace.archiveConfirm": "全部归档",
130
+ "workspace.archivePending": "正在归档会话…",
131
+ "workspace.openPath": "在资源管理器中打开",
132
+ "workspace.delete": "移除项目",
133
+ "workspace.deleteDescription": "将把“{name}”从项目列表中移除。文件夹和会话记录会保留。",
134
+ "workspace.deletePending": "正在移除项目…",
135
+ "workspace.actions": "{name} 的项目操作",
136
+ "workspace.taskCount": "{count} 个任务",
137
+ "workspace.taskSummary": "{count} 个任务 · {unreadCount} 条未读",
138
+ "workspace.edit": "重命名项目",
139
+ "sessions.close": "关闭",
140
+ "sessions.cancel": "取消",
141
+ "sessions.save": "保存",
142
+ "sessions.failed": "操作失败:{message}",
143
+ "sessions.unknown": "找不到该会话。",
144
+ "sessions.actions": "{name} 的会话操作",
145
+ "sessions.markUnread": "标记为未读",
146
+ "sessions.markRead": "标记为已读",
147
+ "sessions.unread": "未读",
148
+ "sessions.waitingAnswer": "等待回答",
149
+ "sessions.waitingApproval": "等待审批",
150
+ "sessions.planReview": "计划待审",
151
+ "sessions.rename": "重命名会话",
152
+ "sessions.renameDescription": "保持简短且易于识别",
153
+ "sessions.renameInvalid": "会话名称无效,请修改后重试。",
154
+ "sessions.renameFailed": "暂时无法重命名该会话,请稍后重试。",
155
+ "sessions.archive": "归档会话",
156
+ "sessions.archiveFailed": "暂时无法归档该会话,请稍后重试。",
157
+ "sessions.delete": "删除会话",
158
+ "sessions.deleteDescription": "将永久删除会话“{name}”及其全部记录,此操作不可恢复。",
159
+ "sessions.deletePending": "正在删除会话…",
160
+ "sessions.deleteUnavailable": "未安装或无法连接归档管理插件。",
161
+ "sessions.deleteFailed": "暂时无法删除该会话,请稍后重试。",
162
+ "sessions.fork": "在新会话中继续",
163
+ "sessions.forkUnavailable": "当前会话暂时无法在新会话中继续,请等待本轮结束后重试。",
164
+ "sessions.forkFailed": "暂时无法创建新会话,请稍后重试。",
165
+ "sessions.openPath": "在资源管理器中打开",
166
+ "sessions.moveWorkspace": "项目",
167
+ "sessions.moveUnavailable": "会话移动服务暂不可用,请重启 DSH 后重试。",
168
+ "sessions.moveUnauthorized": "登录状态已失效,请刷新页面并重新登录。",
169
+ "sessions.moveForbidden": "当前访问地址不受信任,或请求来源已被拒绝。",
170
+ "sessions.moveNotFound": "会话或目标项目已不存在,请刷新后重试。",
171
+ "sessions.moveSubagent": "子代理会话不能移动到其他项目。",
172
+ "sessions.moveBusy": "该会话正在移动,请稍后重试。",
173
+ "sessions.moveRollbackFailed": "会话移动失败,自动恢复未完整完成,请查看 DSH 服务端日志。",
174
+ "sessions.moveFailed": "暂时无法移动该会话,原会话已保留。",
175
+ "sessions.moveConfirmTitle": "将会话移至“{project}”?",
176
+ "sessions.moveConfirmDescription": "“{name}”将移动到此项目,工作目录也会随之更改。",
177
+ "sessions.moveConfirmAction": "移动",
178
+ "sessions.movePending": "正在移动会话…",
179
+ "sessions.copyPath": "复制工作目录",
180
+ "sessions.copyTitle": "复制会话标题",
181
+ "sessions.copyId": "复制会话 ID",
182
+ "connectors.title": "连接器",
183
+ "connectors.description": "显示当前会话可用的 MCP 连接器及其工具,不显示地址、命令或凭证。",
184
+ "connectors.openSession": "请先打开一个会话以读取连接器。",
185
+ "connectors.loading": "正在读取连接器…",
186
+ "connectors.failed": "连接器目录暂不可用,请稍后重试。",
187
+ "connectors.toolCount": "{count} 个工具",
188
+ "connectors.empty": "当前会话没有可用的 MCP 连接器。",
189
+ "connectors.promptReady": "已带入新会话",
190
+ "connectors.promptRequired": "Prompt 不能为空。",
191
+ "connectors.workspacesLoading": "DSH 工作区数据尚未就绪,请稍后重试。",
192
+ "connectors.workspaceRequired": "请先选择一个工作区,再使用示例 Prompt。",
193
+ "connectors.workspaceUnavailable": "DSH 工作区服务尚未就绪,请稍后重试。",
194
+ "connectors.conversationUnavailable": "DSH 对话服务尚未就绪,请稍后重试。",
195
+ "connectors.sessionPending": "新会话尚未就绪,请稍后重试。",
196
+ "about.nav": "Codex UI",
197
+ "about.title": "Codex UI",
198
+ "about.viewProject": "GitHub",
199
+ "about.feedback": "问题反馈",
200
+ "about.description": "为 DSH 提供 Codex 风格的侧栏、工作区会话管理、搜索与会话轮次导航。",
201
+ "about.features": "基本功能",
202
+ "about.feature.sidebar": "Codex 风格侧栏、全局会话搜索与设置入口",
203
+ "about.feature.search": "通过顶部搜索快速定位会话、设置页面和新建任务等快捷操作",
204
+ "about.feature.workspace": "项目文件夹可展开、折叠、拖拽排序和置顶,并保留原生工作区能力",
205
+ "about.feature.sessions": "会话支持重命名、置顶、未读标记、归档、删除及右键快捷操作",
206
+ "about.feature.conversation": "保留原生消息、工具调用、输入、权限和模型选择,仅调整容器视觉",
207
+ "about.feature.navigator": "当前会话提供轮次缩略导航,可快速跳转至每一次用户提问",
208
+ "about.dependencies": "配套管理插件",
209
+ "about.dependenciesDescription": "每个配套插件都可单独安装和更新,也可一键安装缺失项并更新旧版本。可分别检查 Codex UI、专家、技能、归档、IM、定时任务、BTW、Simplify、PUA、代码审查、宠物和应用市场。",
210
+ "about.loading": "正在读取依赖安装状态…",
211
+ "about.statusFailed": "暂时无法读取依赖安装状态。",
212
+ "about.refreshFailed": "依赖状态刷新失败,当前显示上次成功读取的结果。",
213
+ "about.installed": "已安装",
214
+ "about.missing": "未安装",
215
+ "about.updateAvailable": "可更新",
216
+ "about.install": "安装",
217
+ "about.update": "更新",
218
+ "about.installing": "安装中",
219
+ "about.updateAll": "全部安装/更新",
220
+ "about.updatingAll": "安装/更新中",
221
+ "about.upToDate": "所有配套插件均已安装并为最新版本。",
222
+ "about.installFailed": "依赖安装失败,请稍后重试。",
223
+ "about.installUnchanged": "安装命令已结束,但插件没有进入当前 Profile。请重试。",
224
+ "about.installExitDesktop": "请先完全退出 DSH Desktop,再重新打开后更新。",
225
+ "about.installStoreMismatch": "插件目录与 pnpm 仓库不一致。请完全退出 DSH Desktop 后再更新。",
226
+ "about.installExitWeb": "无法覆盖正在运行的插件文件。请停止当前 DSH Web 后,在终端更新插件,再重新启动 DSH Web。",
227
+ "about.installStoreMismatchWeb": "插件目录与 pnpm 仓库不一致。请停止当前 DSH Web 后重试;仍失败时重新安装该 Profile 的依赖。",
228
+ "about.installWebFailed": "插件更新失败。请查看 DSH Web 终端输出后重试。",
229
+ "about.installBuildPolicy": "插件依赖的 pnpm 构建脚本策略尚未确认,请更新 Profile 配置后重试。",
230
+ "about.installPnpmMissing": "当前环境找不到 pnpm,请安装 pnpm 并重启 DSH 后重试。",
231
+ "about.installTimeout": "插件安装超时,请检查网络后重试。",
232
+ "about.installServiceUnavailable": "DSH Desktop 依赖管理服务尚未就绪,请重启后重试。",
233
+ "about.restartRequired": "正在热更新插件,窗口会自动刷新。",
234
+ "about.restartManually": "安装完成。当前 Web 无法自动重启,请重启 DSH Web 后刷新页面。",
235
+ "about.progressHint": "正在安装插件…",
236
+ "about.packagesDone": "已完成 {0} 个包",
237
+ "about.progress.resolving": "解析依赖",
238
+ "about.progress.downloading": "下载中",
239
+ "about.progress.linking": "写入文件",
240
+ "about.progress.building": "构建中",
241
+ "about.dependency.dsh": "DeepSeek Harness",
242
+ "about.dependency.suite": "Codex 套件",
243
+ "about.dependency.ui": "Codex UI",
244
+ "about.dependency.experts": "专家管理",
245
+ "about.dependency.skills": "技能管理",
246
+ "about.dependency.archive": "归档会话",
247
+ "about.dependency.im": "IM助理",
248
+ "about.dependency.schedule": "定时任务",
249
+ "about.dependency.pet": "Codex 宠物",
250
+ "about.dependency.btw": "BTW 旁问",
251
+ "about.dependency.simplify": "Simplify",
252
+ "about.dependency.pua": "PUA",
253
+ "about.dependency.review": "代码审查",
254
+ "about.dependency.market": "应用市场",
255
+ "about.totalDownloads": "npm 累计下载",
256
+ "about.downloadsUnavailable": "暂不可用",
257
+ "about.downloadsSource": "npm 官方统计:自包创建日至昨日的累计下载次数,点击查看 npm 包",
258
+ "turns.label": "当前会话轮次导航",
259
+ "turns.untitled": "未命名提问",
260
+ "turns.jump": "跳转到第 {index} 轮:{summary}",
261
+ "time.justNow": "刚刚",
262
+ "time.minutes": "{count}分",
263
+ "time.hours": "{count}小时",
264
+ "time.days": "{count}天",
265
+ "errors.unauthorized": "请先登录 DSH,再重试。",
266
+ "errors.forbidden": "请求被安全检查拒绝,请从可信的 DSH 地址重试。",
267
+ "errors.serviceUnavailable": "服务暂不可用,请稍后重试。",
268
+ "workspace.preferencesLocalOnly": "项目偏好尚未同步,当前使用本地缓存。",
269
+ "errors.generic": "操作未完成,请重试。",
270
+ "errors.groupInvalid": "分组信息无效。",
271
+ "errors.workspaceInvalid": "项目标识无效。",
272
+ "errors.groupMissing": "目标分组不存在。",
273
+ "errors.orderAnchorMissing": "排序位置已失效,请重试。",
274
+ "sessionTitle.system": "为编程助手会话生成标题。\n只输出一行,恰好两段:类型|主题\n类型必须是其中一个:{types}\n主题必须是具体事项,不能重复类型,不要写类型名或空主题。不要写日期或表情,不要引号、前缀、解释或 Markdown。\n主题使用用户消息的语言。",
275
+ "sessionTitle.userFrame": "根据这条用户消息生成会话标题。只返回“类型|主题”一行,不要第三段,不要重复主题。",
276
+ "sessionTitle.type.feature": "功能",
277
+ "sessionTitle.type.design": "设计",
278
+ "sessionTitle.type.fix": "修复",
279
+ "sessionTitle.type.optimize": "优化",
280
+ "sessionTitle.type.release": "发布",
281
+ "sessionTitle.type.explore": "探索",
282
+ "sessionTitle.type.docs": "文档",
283
+ "sessionTitle.type.research": "研究",
284
+ "meta.locale": "zh-CN"
285
+ };
286
+ const en = {
287
+ "home.resizeInput": "Drag to resize the input area",
288
+ "home.projectSearch": "Search projects",
289
+ "home.projectEmpty": "No matching projects",
290
+ "home.suggestions": "Start a task",
291
+ "home.workspace": "Choose a workspace first, then select a task.",
292
+ "home.draft": "Your draft and attachments are preserved. Clear the input to use a suggestion.",
293
+ "home.busy": "The input is busy. Please try again shortly.",
294
+ "home.explore": "Explore and understand code",
295
+ "home.build": "Build features, apps, or tools",
296
+ "home.review": "Review code and suggest changes",
297
+ "home.fix": "Fix bugs and failures",
298
+ "home.explore.task1": "Explain the project structure",
299
+ "home.explore.prompt1": "Explore this workspace and explain its structure, main entry points, and module relationships.",
300
+ "home.explore.task2": "Understand a feature",
301
+ "home.explore.prompt2": "Help me understand a feature. First ask which feature, then explain its implementation and call flow.",
302
+ "home.build.task1": "Implement a feature",
303
+ "home.build.prompt1": "Help me implement a feature in this project. First clarify requirements and acceptance criteria.",
304
+ "home.build.task2": "Build a small tool",
305
+ "home.build.prompt2": "Help me build a small tool. First ask about its goal, inputs, and outputs, then propose an approach.",
306
+ "home.review.task1": "Review current changes",
307
+ "home.review.prompt1": "Review uncommitted changes for bugs, regressions, and missing tests. Do not modify files yet.",
308
+ "home.review.task2": "Inspect code quality",
309
+ "home.review.prompt2": "Inspect code quality in this project and suggest improvements grounded in the source. Assess only; do not edit.",
310
+ "home.fix.task1": "Investigate a bug",
311
+ "home.fix.prompt1": "Help me fix a bug. First ask for reproduction steps, expected behavior, and actual behavior.",
312
+ "home.fix.task2": "Fix failing tests",
313
+ "home.fix.prompt2": "Run the applicable project tests, identify failure causes, and make minimal fixes with meaningful regression coverage.",
314
+ "usage.title": "Usage statistics",
315
+ "usage.loading": "Loading billing dashboard…",
316
+ "usage.failed": "The embedded dashboard is unavailable. Retry or open the original dialog.",
317
+ "usage.closed": "The billing dashboard is closed.",
318
+ "usage.reopen": "Reopen billing dashboard",
319
+ "usage.retry": "Retry",
320
+ "usage.original": "Open original dialog",
321
+ "settings.title": "Settings",
322
+ "settings.back": "Back to app",
323
+ "settings.openDocument": "Open configuration file",
324
+ "settings.advanced": "Advanced",
325
+ "settings.documentTitle": "Configuration file",
326
+ "settings.documentDescription": "View and edit configuration in an external editor",
327
+ "settings.documentOpen": "Open",
328
+ "settings.documentOpening": "Opening…",
329
+ "settings.openDocumentError": "Could not open configuration file",
330
+ "settings.search": "Search settings…",
331
+ "settings.personal": "Personal",
332
+ "settings.integrations": "Integrations",
333
+ "settings.records": "Records and app",
334
+ "settings.general": "General",
335
+ "settings.permissions": "Permissions",
336
+ "settings.editor": "Editor",
337
+ "settings.noResults": "No matching settings categories",
338
+ "settings.filterHint": "Navigation is filtered. Select a category to switch pages.",
339
+ "settings.disconnected": "Disconnected",
340
+ "settings.reconnect": "Reconnect now",
341
+ "settings.connecting": "Connecting",
342
+ "settings.recovered": "Connected",
343
+ "input.historyHint": "↑↓ History",
344
+ "sidebar.label": "DSH navigation",
345
+ "sidebar.expand": "Expand sidebar",
346
+ "sidebar.collapse": "Collapse sidebar",
347
+ "sidebar.newTask": "New task",
348
+ "sidebar.search": "Search conversations",
349
+ "sidebar.mainMenu": "Main menu",
350
+ "sidebar.extensions": "Extensions",
351
+ "sidebar.experts": "Experts",
352
+ "sidebar.skills": "Skills",
353
+ "sidebar.plugins": "Plugins",
354
+ "sidebar.marketplace": "Plugin Market",
355
+ "sidebar.connectors": "Connectors",
356
+ "sidebar.schedule": "Scheduled tasks",
357
+ "sidebar.assistant": "IM Assistant",
358
+ "sidebar.imSettings": "IM Assistant",
359
+ "sidebar.tasksTab": "Tasks",
360
+ "sidebar.channelsTab": "Channels",
361
+ "sidebar.scheduleTab": "Schedule",
362
+ "sidebar.runsTab": "Run history",
363
+ "sidebar.overviewTab": "Task overview",
364
+ "channels.empty": "No channel conversations yet. Connect a channel in Settings → IM Assistant.",
365
+ "channels.loadError": "Could not load channel conversations.",
366
+ "channel.dingtalk": "DingTalk",
367
+ "channel.feishu": "Feishu",
368
+ "channel.lark": "Lark",
369
+ "channel.weixin": "WeChat",
370
+ "channel.wecom": "WeCom",
371
+ "channel.qq": "QQ",
372
+ "channel.telegram": "Telegram",
373
+ "channel.unknown": "Channel",
374
+ "schedule.empty": "No scheduled-task runs yet.",
375
+ "schedule.groupActions": "Task actions for {name}",
376
+ "schedule.taskSettings": "Task settings",
377
+ "schedule.archiveGroup": "Archive all conversations",
378
+ "schedule.archiveGroupConfirm": "Archive all",
379
+ "schedule.archiveGroupDescription": "Archive all {count} conversations for “{name}”. You can restore them later in Settings → Archived.",
380
+ "schedule.archiveGroupPending": "Archiving conversations…",
381
+ "schedule.archiveGroupPartial": "Archived {archived} conversations; {failed} failed. You can retry the remaining conversations.",
382
+ "search.placeholder": "Search conversations, settings, and actions",
383
+ "search.empty": "No matching results.",
384
+ "search.sessions": "Conversations",
385
+ "search.settings": "Settings",
386
+ "search.actions": "Quick actions",
387
+ "workspace.label": "Workspace conversations",
388
+ "workspace.projects": "Projects",
389
+ "workspace.ungrouped": "Ungrouped",
390
+ "workspace.createGroup": "Create group",
391
+ "workspace.createGroupDescription": "Enter a group name. Projects will not move automatically.",
392
+ "workspace.groupName": "Group name",
393
+ "workspace.moveToGroup": "Move to group",
394
+ "workspace.removeFromGroup": "Move to ungrouped",
395
+ "workspace.deleteGroup": "Delete group {name}",
396
+ "workspace.deleteGroupAction": "Delete group",
397
+ "workspace.deleteGroupDescription": "Deleting this group moves its projects to Ungrouped. It does not delete projects or conversations.",
398
+ "workspace.groupActions": "Actions for group {name}",
399
+ "workspace.renameGroup": "Rename group",
400
+ "workspace.recent": "Recents",
401
+ "workspace.recentEmpty": "No chats",
402
+ "workspace.noChat": "No chats",
403
+ "workspace.empty": "No projects",
404
+ "workspace.newSession": "New conversation",
405
+ "workspace.rename": "Rename project",
406
+ "workspace.pin": "Pin project",
407
+ "workspace.unpin": "Unpin",
408
+ "workspace.archive": "Archive all conversations",
409
+ "workspace.archiveTitle": "Archive {count} conversations?",
410
+ "workspace.archiveDescription": "This archives every conversation in “{name}”. The project and folder remain available.",
411
+ "workspace.archiveConfirm": "Archive all",
412
+ "workspace.archivePending": "Archiving conversations…",
413
+ "workspace.pinned": "Pinned",
414
+ "workspace.pinnedEmpty": "Drag a project here to pin it",
415
+ "workspace.openPath": "Open in file explorer",
416
+ "workspace.delete": "Remove project",
417
+ "workspace.deleteDescription": "This removes “{name}” from the project list. The folder and conversation records remain.",
418
+ "workspace.deletePending": "Removing project…",
419
+ "workspace.actions": "Project actions for {name}",
420
+ "workspace.taskCount": "{count} tasks",
421
+ "workspace.taskSummary": "{count} tasks · {unreadCount} unread",
422
+ "workspace.edit": "Rename project",
423
+ "sessions.close": "Close",
424
+ "sessions.cancel": "Cancel",
425
+ "sessions.save": "Save",
426
+ "sessions.failed": "Action failed: {message}",
427
+ "sessions.unknown": "Conversation not found.",
428
+ "sessions.actions": "Conversation actions for {name}",
429
+ "sessions.markUnread": "Mark as unread",
430
+ "sessions.markRead": "Mark as read",
431
+ "sessions.unread": "Unread",
432
+ "sessions.waitingAnswer": "Waiting for answer",
433
+ "sessions.waitingApproval": "Waiting for approval",
434
+ "sessions.planReview": "Plan awaiting review",
435
+ "sessions.rename": "Rename conversation",
436
+ "sessions.renameDescription": "Keep it short and easy to recognize.",
437
+ "sessions.renameInvalid": "The conversation name is invalid. Change it and try again.",
438
+ "sessions.renameFailed": "The conversation could not be renamed. Try again later.",
439
+ "sessions.archive": "Archive conversation",
440
+ "sessions.archiveFailed": "The conversation could not be archived. Try again later.",
441
+ "sessions.delete": "Delete conversation",
442
+ "sessions.deleteDescription": "This permanently deletes “{name}” and all of its records. This cannot be undone.",
443
+ "sessions.deletePending": "Deleting conversation…",
444
+ "sessions.deleteUnavailable": "The archive manager plugin is not installed or unavailable.",
445
+ "sessions.deleteFailed": "The conversation could not be deleted. Try again later.",
446
+ "sessions.fork": "Continue in new conversation",
447
+ "sessions.forkUnavailable": "This conversation cannot be continued in a new conversation right now. Try again after the current turn finishes.",
448
+ "sessions.forkFailed": "A new conversation could not be created. Try again later.",
449
+ "sessions.openPath": "Open in file explorer",
450
+ "sessions.moveWorkspace": "Project",
451
+ "sessions.moveUnavailable": "Conversation moving is unavailable. Restart DSH and try again.",
452
+ "sessions.moveUnauthorized": "Your login session has expired. Refresh the page and sign in again.",
453
+ "sessions.moveForbidden": "The current address is not trusted, or the request origin was rejected.",
454
+ "sessions.moveNotFound": "The conversation or target project no longer exists. Refresh and try again.",
455
+ "sessions.moveSubagent": "Subagent conversations cannot be moved to another project.",
456
+ "sessions.moveBusy": "This conversation is already being moved. Try again shortly.",
457
+ "sessions.moveRollbackFailed": "The conversation could not be moved, and automatic recovery did not complete. Check the DSH server logs.",
458
+ "sessions.moveFailed": "The conversation could not be moved. The original conversation was preserved.",
459
+ "sessions.moveConfirmTitle": "Move conversation to “{project}”?",
460
+ "sessions.moveConfirmDescription": "“{name}” will move to this project, and its working directory will change with it.",
461
+ "sessions.moveConfirmAction": "Move",
462
+ "sessions.movePending": "Moving conversation…",
463
+ "sessions.copyPath": "Copy working directory",
464
+ "sessions.copyTitle": "Copy conversation title",
465
+ "sessions.copyId": "Copy conversation ID",
466
+ "connectors.title": "Connectors",
467
+ "connectors.description": "Shows MCP connectors and tools available to this session without exposing addresses, commands, or credentials.",
468
+ "connectors.openSession": "Open a conversation to load connectors.",
469
+ "connectors.loading": "Loading connectors…",
470
+ "connectors.failed": "Connectors are unavailable. Try again shortly.",
471
+ "connectors.toolCount": "{count} tools",
472
+ "connectors.empty": "This conversation has no available MCP connectors.",
473
+ "connectors.promptReady": "Added to a new conversation",
474
+ "connectors.promptRequired": "The prompt cannot be empty.",
475
+ "connectors.workspacesLoading": "DSH workspace data is still loading. Try again shortly.",
476
+ "connectors.workspaceRequired": "Select a workspace before using an example prompt.",
477
+ "connectors.workspaceUnavailable": "The DSH workspace service is not ready. Try again shortly.",
478
+ "connectors.conversationUnavailable": "The DSH conversation service is not ready. Try again shortly.",
479
+ "connectors.sessionPending": "The new conversation is not ready yet. Try again shortly.",
480
+ "about.nav": "Codex UI",
481
+ "about.title": "Codex UI",
482
+ "about.viewProject": "GitHub",
483
+ "about.feedback": "Issues",
484
+ "about.description": "Adds a Codex-style sidebar, workspace conversation management, search, and turn navigation to DSH.",
485
+ "about.features": "Core features",
486
+ "about.feature.sidebar": "Codex-style sidebar, global conversation search, and settings entry points",
487
+ "about.feature.search": "Find conversations, settings pages, and quick actions from the top search panel",
488
+ "about.feature.workspace": "Expand, collapse, reorder, and pin workspace folders while retaining native workspace behavior",
489
+ "about.feature.sessions": "Rename, pin, mark unread, archive, delete, or use context actions for conversations",
490
+ "about.feature.conversation": "Keeps native messages, tool calls, composer, permissions, and model selection while refining the container visuals",
491
+ "about.feature.navigator": "Navigate directly to each user prompt with the current conversation turn navigator",
492
+ "about.dependencies": "Companion management plugins",
493
+ "about.dependenciesDescription": "Install or update each companion separately, or install missing plugins and update old versions in one action. Check Codex UI, expert, skill, archive, IM, scheduled-task, BTW, Simplify, PUA, code review, pet, and marketplace plugins on their own.",
494
+ "about.loading": "Loading dependency status…",
495
+ "about.statusFailed": "Dependency status is temporarily unavailable.",
496
+ "about.refreshFailed": "Dependency status refresh failed. Showing the last successful result.",
497
+ "about.installed": "Installed",
498
+ "about.missing": "Not installed",
499
+ "about.updateAvailable": "Update available",
500
+ "about.install": "Install",
501
+ "about.update": "Update",
502
+ "about.installing": "Installing",
503
+ "about.updateAll": "Install/update all",
504
+ "about.updatingAll": "Installing/updating",
505
+ "about.upToDate": "All companion plugins are installed and up to date.",
506
+ "about.installFailed": "Dependency installation failed. Try again later.",
507
+ "about.installUnchanged": "The install command finished, but the plugin did not enter this profile. Try again.",
508
+ "about.installExitDesktop": "Quit DSH Desktop completely, reopen it, and try the update again.",
509
+ "about.installStoreMismatch": "The plugin directory and pnpm store do not match. Quit DSH Desktop completely and try again.",
510
+ "about.installExitWeb": "Running plugin files cannot be replaced. Stop the current DSH Web host, update the plugin from a terminal, then restart DSH Web.",
511
+ "about.installStoreMismatchWeb": "The plugin directory and pnpm store do not match. Stop the current DSH Web host and try again; if it persists, reinstall this Profile's dependencies.",
512
+ "about.installWebFailed": "Plugin update failed. Check the DSH Web terminal output and try again.",
513
+ "about.installBuildPolicy": "The pnpm build-script policy for this plugin is not configured. Update the Profile configuration and try again.",
514
+ "about.installPnpmMissing": "pnpm is not available. Install pnpm, restart DSH, and try again.",
515
+ "about.installTimeout": "Plugin installation timed out. Check the network and try again.",
516
+ "about.installServiceUnavailable": "The DSH Desktop dependency service is not ready. Restart DSH Desktop and try again.",
517
+ "about.restartRequired": "Updating plugins now. The window will refresh automatically.",
518
+ "about.restartManually": "Installation complete. This Web host cannot restart itself; restart DSH Web, then refresh the page.",
519
+ "about.progressHint": "Installing plugin…",
520
+ "about.packagesDone": "{0} packages done",
521
+ "about.progress.resolving": "Resolving",
522
+ "about.progress.downloading": "Downloading",
523
+ "about.progress.linking": "Writing files",
524
+ "about.progress.building": "Building",
525
+ "about.dependency.dsh": "DeepSeek Harness",
526
+ "about.dependency.suite": "Codex suite",
527
+ "about.dependency.ui": "Codex UI",
528
+ "about.dependency.experts": "Expert management",
529
+ "about.dependency.skills": "Skill management",
530
+ "about.dependency.archive": "Archived conversations",
531
+ "about.dependency.im": "IM Assistant",
532
+ "about.dependency.schedule": "Scheduled tasks",
533
+ "about.dependency.pet": "Codex Pet",
534
+ "about.dependency.btw": "BTW",
535
+ "about.dependency.simplify": "Simplify",
536
+ "about.dependency.pua": "PUA",
537
+ "about.dependency.review": "Code Review",
538
+ "about.dependency.market": "App Market",
539
+ "about.totalDownloads": "npm total downloads",
540
+ "about.downloadsUnavailable": "Unavailable",
541
+ "about.downloadsSource": "Cumulative npm downloads from package creation through yesterday. View package on npm.",
542
+ "turns.label": "Conversation turn navigation",
543
+ "turns.untitled": "Untitled prompt",
544
+ "turns.jump": "Jump to turn {index}: {summary}",
545
+ "time.justNow": "Just now",
546
+ "time.minutes": "{count}m",
547
+ "time.hours": "{count}h",
548
+ "time.days": "{count}d",
549
+ "errors.unauthorized": "Sign in to DSH, then try again.",
550
+ "errors.forbidden": "The security check rejected this request. Try again from a trusted DSH address.",
551
+ "errors.serviceUnavailable": "The service is unavailable. Try again later.",
552
+ "workspace.preferencesLocalOnly": "Workspace preferences are not synced. Using the local cache.",
553
+ "errors.generic": "The action could not be completed. Try again.",
554
+ "errors.groupInvalid": "The group information is invalid.",
555
+ "errors.workspaceInvalid": "The project identifier is invalid.",
556
+ "errors.groupMissing": "The target group no longer exists.",
557
+ "errors.orderAnchorMissing": "The target position is no longer available. Try again.",
558
+ "sessionTitle.system": "Generate a title for a coding-assistant conversation.\nOutput exactly one line with two parts: Type|Theme\nType must be one of: {types}\nThe theme must be a concrete subject and must not repeat the type. Do not write dates, emoji, quotes, prefixes, explanations, or Markdown.\nWrite the theme in the language of the user message.",
559
+ "sessionTitle.userFrame": "Generate a conversation title from this user message. Return only one Type|Theme line. Do not add a third part or repeat the theme.",
560
+ "sessionTitle.type.feature": "Feature",
561
+ "sessionTitle.type.design": "Design",
562
+ "sessionTitle.type.fix": "Fix",
563
+ "sessionTitle.type.optimize": "Optimize",
564
+ "sessionTitle.type.release": "Release",
565
+ "sessionTitle.type.explore": "Explore",
566
+ "sessionTitle.type.docs": "Docs",
567
+ "sessionTitle.type.research": "Research",
568
+ "meta.locale": "en-US"
569
+ };
570
+ //#endregion
571
+ //#region src/session-title.ts
572
+ const SESSION_TITLE_KINDS = [
573
+ "feature",
574
+ "design",
575
+ "fix",
576
+ "optimize",
577
+ "release",
578
+ "explore",
579
+ "docs",
580
+ "research"
581
+ ];
582
+ const SESSION_TITLE_EMOJI = {
583
+ feature: "✨",
584
+ design: "🎨",
585
+ fix: "🐛",
586
+ optimize: "⚡",
587
+ release: "🚀",
588
+ explore: "🔍",
589
+ docs: "📝",
590
+ research: "🔬"
591
+ };
592
+ const TYPE_KEYS = {
593
+ feature: "sessionTitle.type.feature",
594
+ design: "sessionTitle.type.design",
595
+ fix: "sessionTitle.type.fix",
596
+ optimize: "sessionTitle.type.optimize",
597
+ release: "sessionTitle.type.release",
598
+ explore: "sessionTitle.type.explore",
599
+ docs: "sessionTitle.type.docs",
600
+ research: "sessionTitle.type.research"
601
+ };
602
+ const GENERIC_THEME_SUFFIXES = [
603
+ "类型",
604
+ "分类",
605
+ "相关",
606
+ "需求",
607
+ "问题",
608
+ "会话",
609
+ "标题",
610
+ "主题",
611
+ "内容",
612
+ "type",
613
+ "category",
614
+ "related",
615
+ "request",
616
+ "issue",
617
+ "session",
618
+ "title",
619
+ "topic",
620
+ "content"
621
+ ];
622
+ const LEADING_EMOJI = /^(?:\p{Extended_Pictographic}(?:\uFE0F|\u200D\p{Extended_Pictographic})*)+\s*/u;
623
+ const TITLE_SEPARATORS = /[||│]/g;
624
+ function typeLabel(kind, locale) {
625
+ const key = TYPE_KEYS[kind];
626
+ return locale === "en" ? en[key] : zh[key];
627
+ }
628
+ function typeAliasMap() {
629
+ const aliases = /* @__PURE__ */ new Map();
630
+ for (const kind of SESSION_TITLE_KINDS) {
631
+ aliases.set(kind, kind);
632
+ aliases.set(zh[TYPE_KEYS[kind]], kind);
633
+ aliases.set(en[TYPE_KEYS[kind]].toLowerCase(), kind);
634
+ }
635
+ return aliases;
636
+ }
637
+ const TYPE_ALIASES = typeAliasMap();
638
+ function resolveKind(raw) {
639
+ return TYPE_ALIASES.get(raw) ?? TYPE_ALIASES.get(raw.toLowerCase());
640
+ }
641
+ function sessionTitleTypeLabels(locale) {
642
+ return SESSION_TITLE_KINDS.map((kind) => typeLabel(kind, locale)).join(locale === "en" ? ", " : "、");
643
+ }
644
+ function sessionTitlePrompt(locale) {
645
+ const dict = locale === "en" ? en : zh;
646
+ const types = sessionTitleTypeLabels(locale);
647
+ return {
648
+ system: dict["sessionTitle.system"].replaceAll("{types}", types),
649
+ userFrame: dict["sessionTitle.userFrame"]
650
+ };
651
+ }
652
+ function localeFromTag(tag) {
653
+ const lower = tag.trim().toLowerCase();
654
+ if (lower === "" || lower === "c" || lower === "posix") return void 0;
655
+ if (lower === "en" || lower.startsWith("en-")) return "en";
656
+ if (lower === "zh" || lower.startsWith("zh-")) return "zh";
657
+ }
658
+ function parseLocaleValue(value) {
659
+ if (typeof value === "string") return localeFromTag(value);
660
+ if (value === null || typeof value !== "object") return void 0;
661
+ const row = value;
662
+ if (typeof row.getSnapshot === "function") return parseLocaleValue(row.getSnapshot());
663
+ for (const key of [
664
+ "locale",
665
+ "lang",
666
+ "language",
667
+ "current",
668
+ "preference"
669
+ ]) {
670
+ const parsed = parseLocaleValue(row[key]);
671
+ if (parsed !== void 0) return parsed;
672
+ }
673
+ }
674
+ function inferLocaleFromMessage(text) {
675
+ if (text === void 0 || text.trim() === "") return void 0;
676
+ if (/[㐀-鿿]/.test(text)) return "zh";
677
+ if (/[A-Za-z]/.test(text)) return "en";
678
+ }
679
+ function compactMessageTheme(message) {
680
+ const line = message.replace(/\s+/g, " ").trim();
681
+ if (Buffer.byteLength(line, "utf8") <= 40) return line;
682
+ let used = 0;
683
+ let output = "";
684
+ for (const character of line) {
685
+ const bytes = Buffer.byteLength(character, "utf8");
686
+ if (used + bytes > 40) break;
687
+ output += character;
688
+ used += bytes;
689
+ }
690
+ return output.trimEnd();
691
+ }
692
+ /** 主题必须跟用户消息同一语言;模型串语言时改用消息原文。 */
693
+ function alignThemeToMessage(theme, message) {
694
+ const messageLocale = inferLocaleFromMessage(message);
695
+ const themeLocale = inferLocaleFromMessage(theme);
696
+ if (messageLocale === void 0 || themeLocale === void 0 || messageLocale === themeLocale) return theme;
697
+ const fallback = compactMessageTheme(message);
698
+ return fallback === "" ? theme : fallback;
699
+ }
700
+ function resolveSessionTitleLocale(source, message) {
701
+ return parseLocaleValue(source) ?? inferLocaleFromMessage(message) ?? "zh";
702
+ }
703
+ function inferLocaleFromTypeToken(token) {
704
+ const lower = token.toLowerCase();
705
+ for (const kind of SESSION_TITLE_KINDS) {
706
+ if (en[TYPE_KEYS[kind]].toLowerCase() === lower) return "en";
707
+ if (zh[TYPE_KEYS[kind]] === token) return "zh";
708
+ }
709
+ return "zh";
710
+ }
711
+ function stripLeadingNoise(part) {
712
+ const trimmed = part.trim();
713
+ if (/^\d{4}$/.test(trimmed)) return "";
714
+ return trimmed.replace(/^\d{4}\s*/, "").replace(LEADING_EMOJI, "").trim();
715
+ }
716
+ function parseTypeAndTheme(raw) {
717
+ const parts = raw.trim().replace(/^["'`]+|["'`]+$/g, "").trim().replace(TITLE_SEPARATORS, "|").split("|").map(stripLeadingNoise).filter((part) => part !== "");
718
+ const type = parts[0];
719
+ const theme = parts[1];
720
+ if (type === void 0 || theme === void 0) return void 0;
721
+ const kind = resolveKind(type);
722
+ if (kind === void 0) return void 0;
723
+ return {
724
+ type: kind,
725
+ theme
726
+ };
727
+ }
728
+ function compactToken(value) {
729
+ return value.replace(/[\s·.\-_/]/g, "");
730
+ }
731
+ function isRedundantAgainst(type, theme) {
732
+ const normalized = compactToken(theme);
733
+ const typeNorm = compactToken(type);
734
+ if (normalized === "" || typeNorm === "") return false;
735
+ if (normalized.toLowerCase() === typeNorm.toLowerCase()) return true;
736
+ if (normalized.toLowerCase() === `新${typeNorm}`.toLowerCase()) return true;
737
+ if (normalized.toLowerCase() === `new${typeNorm}`.toLowerCase()) return true;
738
+ if (typeNorm !== "" && normalized.length >= typeNorm.length * 2 && normalized.length % typeNorm.length === 0 && normalized.toLowerCase() === typeNorm.repeat(normalized.length / typeNorm.length).toLowerCase()) return true;
739
+ return GENERIC_THEME_SUFFIXES.some((suffix) => {
740
+ const compactSuffix = compactToken(suffix);
741
+ return normalized.toLowerCase() === compactSuffix.toLowerCase() || normalized.toLowerCase() === `${typeNorm}${compactSuffix}`.toLowerCase();
742
+ });
743
+ }
744
+ function isRedundantTheme(type, theme) {
745
+ const kind = resolveKind(type);
746
+ return (kind === void 0 ? [type] : [
747
+ typeLabel(kind, "zh"),
748
+ typeLabel(kind, "en"),
749
+ kind
750
+ ]).some((label) => isRedundantAgainst(label, theme));
751
+ }
752
+ function assembleSessionTitle(type, theme, locale) {
753
+ const parsed = parseTypeAndTheme(`${type}|${theme}`);
754
+ if (parsed === void 0) return void 0;
755
+ const displayLocale = locale ?? inferLocaleFromTypeToken(type);
756
+ const label = typeLabel(parsed.type, displayLocale);
757
+ const title = isRedundantTheme(parsed.type, parsed.theme) ? `${SESSION_TITLE_EMOJI[parsed.type]} ${label}` : `${SESSION_TITLE_EMOJI[parsed.type]} ${label}|${parsed.theme}`;
758
+ if (Buffer.byteLength(title, "utf8") > 80) return void 0;
759
+ return title;
760
+ }
761
+ function shouldSkipAutoTitle(session) {
762
+ const origin = session.origin ?? session.header?.origin;
763
+ return session.id.startsWith("dsh-automation-session-") || session.id.startsWith("im:") || origin === "subagent" || session.header?.parentSession !== void 0 && session.header.parentSession !== "";
764
+ }
765
+ //#endregion
766
+ //#region src/session-title-provider.ts
767
+ const SESSION_TITLE_PROVIDER_ID = "michengai-codex-ui-session-title";
768
+ const MAX_INPUT_BYTES = 4096;
769
+ const MAX_OUTPUT_TOKENS = 64;
770
+ const TIMEOUT_MS = 6e4;
771
+ function isServiceValue(value) {
772
+ return value !== null && (typeof value === "object" || typeof value === "function");
773
+ }
774
+ function asTitleService(value) {
775
+ if (!isServiceValue(value)) return void 0;
776
+ return typeof value.register === "function" ? value : void 0;
777
+ }
778
+ function asLlm(value) {
779
+ if (!isServiceValue(value)) return void 0;
780
+ return typeof value.stream === "function" ? value : void 0;
781
+ }
782
+ function abortableSignal(signal) {
783
+ const timeout = AbortSignal.timeout(TIMEOUT_MS);
784
+ return signal === void 0 ? timeout : AbortSignal.any([signal, timeout]);
785
+ }
786
+ /**
787
+ * Assemble the streamed text the way the host's `BlockAssembler` does: deltas
788
+ * accumulate per block index and a closing `block-end` replaces them, so the
789
+ * adapters that send both channels cannot duplicate the title text.
790
+ */
791
+ function collectText(chunks) {
792
+ return (async () => {
793
+ const parts = /* @__PURE__ */ new Map();
794
+ let finish = "stop";
795
+ const part = (index) => {
796
+ let existing = parts.get(index);
797
+ if (existing === void 0) {
798
+ existing = {
799
+ text: "",
800
+ closed: false
801
+ };
802
+ parts.set(index, existing);
803
+ }
804
+ return existing;
805
+ };
806
+ for await (const chunk of chunks) {
807
+ if (chunk === null || typeof chunk !== "object") continue;
808
+ const row = chunk;
809
+ if (row.type === "tool-call-delta" || row.block?.type === "tool-call") throw new Error("codex-ui session title output must be text");
810
+ if (row.type === "finish") {
811
+ finish = typeof row.reason?.kind === "string" ? row.reason.kind : "unknown";
812
+ continue;
813
+ }
814
+ if (row.type !== "text-delta" && row.type !== "block-end") continue;
815
+ if (typeof row.index !== "number" || !Number.isSafeInteger(row.index)) continue;
816
+ if (row.type === "text-delta") {
817
+ if (typeof row.text !== "string") continue;
818
+ const target = part(row.index);
819
+ if (!target.closed) target.text += row.text;
820
+ continue;
821
+ }
822
+ if (row.block?.type !== "text" || typeof row.block.text !== "string") continue;
823
+ const target = part(row.index);
824
+ if (target.closed) continue;
825
+ target.text = row.block.text;
826
+ target.closed = true;
827
+ }
828
+ if (finish !== "stop") throw new Error(`codex-ui session title output finished with "${finish}"`);
829
+ return [...parts.values()].map((segment) => segment.text).join("").trim().split(/\r?\n/, 1)[0] ?? "";
830
+ })();
831
+ }
832
+ function hostLocale(host, message) {
833
+ const settings = host.get("settings");
834
+ return resolveSessionTitleLocale((typeof settings?.get === "function" ? settings.get("locale") : void 0) ?? host.get("locale"), message);
835
+ }
836
+ async function generateCodexSessionTitle(llm, request, locale = "zh") {
837
+ request.signal?.throwIfAborted();
838
+ const session = request.session;
839
+ if (session === void 0 || shouldSkipAutoTitle(session)) throw new Error("codex-ui session title skipped");
840
+ const first = request.messages?.[0];
841
+ const text = first?.text?.trim() ?? "";
842
+ if (first === void 0 || text === "") throw new Error("codex-ui session title missing message");
843
+ const route = request.route;
844
+ if (route === void 0 || route.provider === "" || route.model === "") throw new Error("codex-ui session title missing route");
845
+ const prompt = sessionTitlePrompt(locale);
846
+ const framed = `${prompt.userFrame}\n${JSON.stringify([{
847
+ seq: first.seq,
848
+ text
849
+ }])}`;
850
+ if (Buffer.byteLength(framed, "utf8") > MAX_INPUT_BYTES) throw new Error("codex-ui session title input too large");
851
+ const signal = abortableSignal(request.signal);
852
+ const raw = await collectText(llm.stream({
853
+ provider: route.provider,
854
+ model: route.model,
855
+ messages: [{
856
+ id: crypto.randomUUID(),
857
+ role: "user",
858
+ content: [{
859
+ type: "text",
860
+ text: framed
861
+ }],
862
+ source: {
863
+ kind: "plugin",
864
+ plugin: "michengai-codex-ui"
865
+ }
866
+ }],
867
+ system: prompt.system,
868
+ maxTokens: MAX_OUTPUT_TOKENS,
869
+ sessionId: session.id,
870
+ purpose: "session-title",
871
+ signal
872
+ }));
873
+ signal.throwIfAborted();
874
+ const parsed = parseTypeAndTheme(raw);
875
+ if (parsed === void 0) throw new Error("codex-ui session title invalid model output");
876
+ const title = assembleSessionTitle(parsed.type, alignThemeToMessage(parsed.theme, text), locale);
877
+ if (title === void 0) throw new Error("codex-ui session title rejected");
878
+ return {
879
+ title,
880
+ messageSeqs: typeof first.seq === "number" ? [first.seq] : [],
881
+ model: route
882
+ };
883
+ }
884
+ function registerSessionTitleProvider(ctx) {
885
+ const sessionTitle = asTitleService(ctx.get("sessionTitle"));
886
+ const llm = asLlm(ctx.get("llm"));
887
+ if (sessionTitle === void 0 || llm === void 0) {
888
+ ctx.logger?.warn("Codex UI 未能注册会话标题提供方:sessionTitle 或 llm 尚未就绪");
889
+ return;
890
+ }
891
+ try {
892
+ const dispose = sessionTitle.register({
893
+ id: SESSION_TITLE_PROVIDER_ID,
894
+ automatic: "first-prompt",
895
+ generate: (request) => generateCodexSessionTitle(llm, request, hostLocale(ctx, request.messages?.[0]?.text))
896
+ });
897
+ return typeof dispose === "function" ? dispose : void 0;
898
+ } catch (error) {
899
+ const reason = error instanceof Error ? error.message : typeof error;
900
+ ctx.logger?.warn(`Codex UI 未能注册会话标题提供方:${reason}`);
901
+ return;
902
+ }
903
+ }
904
+ //#endregion
905
+ //#region src/session-title-plugin.ts
906
+ const name = "michengai-codex-ui-session-title";
907
+ const inject = ["sessionTitle", "llm"];
908
+ /** 独立挂到官方 session-title-llm 同级,等标题服务和模型服务就绪后再占 first-prompt 位。 */
909
+ function apply(ctx) {
910
+ ctx.effect(() => registerSessionTitleProvider(ctx) ?? (() => {}), "michengai-codex-ui: session title");
911
+ }
912
+ //#endregion
913
+ export { apply, inject, name };