zcode-pro 1.4.1 → 1.5.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zcode-pro",
3
- "version": "1.4.1",
3
+ "version": "1.5.0",
4
4
  "description": "ZCode Pro — 为 ZCode 桌面版增加界面增强(不修改客户端文件,通过 ZCode Pro 快捷方式启动即可),核心功能:项目自定义别名",
5
5
  "type": "module",
6
6
  "bin": {
@@ -17,7 +17,7 @@ import { reorderWorkspaceTasks, reorderGroupMembers } from './taskOrder.mjs';
17
17
  import { webdavRequest, webdavPutWithMkcol, webdavHttpText, parseWebdavListing, findCurl } from './webdav.mjs';
18
18
  import { resolveZcodeExecutable } from './paths.mjs';
19
19
 
20
- const VERSION = '1.4.1';
20
+ const VERSION = '1.5.0';
21
21
 
22
22
  // 全局提示词固定在用户主目录:官方加载器按 HOME/USERPROFILE 拼 .zcode/AGENTS.md,
23
23
  // 不读 ZCODE_DATA_BASE_DIR(数据根迁走时全局指令仍在原位)。
@@ -159,6 +159,8 @@ export function defaultConfig() {
159
159
  idleReclaim: true, // 空闲项目内存回收:长时间无活动的项目释放其会话进程(时长 idleReclaimMinutes)
160
160
  toolbarIcons: true, // 侧栏菜单并入顶栏:新建任务/搜索/自动化/插件市场收成顶栏图标按钮(原菜单隐藏,点击转发)
161
161
  pinnedCollapse: true, // 已置顶分区可折叠:标题可点折叠/展开任务列表(状态记 localStorage)
162
+ tableCopy: true, // 表格工具条"复制表格"按钮:富文本剪贴板,粘贴即表格(table-copy.js)
163
+ composerProvider: true, // 输入框模型名前显示供应商名(composer-provider.js)
162
164
  },
163
165
  // 空闲项目内存回收的判定时长(分钟,1–1440):项目无运行中会话、无后台命令
164
166
  // 且非当前选中,持续满该时长后释放其会话进程
@@ -127,7 +127,7 @@ const zh = {
127
127
  agentsSave: '保存',
128
128
  agentsSaved: '全局提示词已保存',
129
129
  agentsLoadFailed: '读取全局提示词失败',
130
- proxyDesc: '为 ZCode Pro 本地助手发起的网络访问设置 HTTP 代理:插件市场的更新与安装、插件依赖软件的安装与升级、WebDAV 同步都可走该代理,用下方开关选择生效范围。不影响 ZCode 应用本身与模型请求,保存后立即生效。',
130
+ proxyDesc: '为 ZCode Pro 本地助手发起的网络访问设置 HTTP 代理:插件市场的更新与安装、插件依赖软件的安装与升级、WebDAV 同步都可走该代理,用下方开关选择生效范围。不影响 ZCode 应用本身与模型请求。',
131
131
  proxyPlaceholder: 'http://127.0.0.1:7890',
132
132
  proxySave: '保存',
133
133
  proxySaved: '代理设置已保存',
@@ -141,7 +141,7 @@ const zh = {
141
141
  proxyScopeDeps: '插件依赖软件',
142
142
  proxyScopeDepsDesc: 'headroom 与 rtk 的安装、检查更新与升级(pip 与 GitHub 下载)走代理。',
143
143
  proxyScopeSync: '同步',
144
- proxyScopeSyncDesc: 'WebDAV 上传与下载走代理。默认关闭:坚果云等国内网盘直连即可,代理停了同步会跟着失败。',
144
+ proxyScopeSyncDesc: 'WebDAV 上传与下载走代理。',
145
145
  tabSync: '同步',
146
146
  tabDebug: '调试',
147
147
  testNoSessionHint: '选择“跟随当前会话[当前模型]”时,选择上次会话中使用的供应商[模型]',
@@ -228,13 +228,13 @@ const zh = {
228
228
  defaultValue: '默认',
229
229
  resetDefault: '恢复默认',
230
230
  featureProjectMenu: '项目菜单增强',
231
- featureProjectMenuDesc: '项目“更多”菜单中的自定义别名、切换文件夹、打开文件夹与复制路径(别名同时作用于侧边栏显示)。',
231
+ featureProjectMenuDesc: '项目更多菜单:打开文件夹与复制路径(别名与切换文件夹已停用)。',
232
232
  featureTaskOrder: '侧边栏会话拖动排序',
233
- featureTaskOrderDesc: '让置顶、项目与分组中的会话拖动后记住顺序,刷新后保持。',
233
+ featureTaskOrderDesc: '拖动调整置顶、项目与分组中的会话顺序,重启后不变。',
234
234
  featureSessionSwitch: '会话快捷切换',
235
- featureSessionSwitchDesc: '{last} 在当前与上次会话间来回切换;按住 alt 再按 {prev}/{next} 弹出最近会话列表前后选择(初始 10 条,{next} 到底再加载 5 条),{recent} 直接打开最近 10 条,{attention} 打开运行中与运行完未查看的任务;方向键同样可选,Enter 或松开 alt 切换(类 alt+tab)',
235
+ featureSessionSwitchDesc: '{last} 来回上次会话;alt+方向键/快捷键选最近会话;键位可改',
236
236
  featureTitleLock: '重命名会话名锁定',
237
- featureTitleLockDesc: '手动重命名过的会话不再被自动标题覆盖:会话有新回答后界面可能改显自动标题,重启后又变回,本功能让侧栏始终显示重命名后的名字。',
237
+ featureTitleLockDesc: '手动重命名过的会话不被自动标题覆盖,侧栏始终显示重命名后的名字。',
238
238
  tabTitleGen: '任务名提示词',
239
239
  titleGenShortDesc: '新会话开始作答时,按所选供应商与提示词生成任务名并按手动重命名保存,官方自动标题不再覆盖。',
240
240
  titleGenEnabled: '启用',
@@ -260,7 +260,7 @@ const zh = {
260
260
  titleGenTestOk: '生成结果:',
261
261
  titleGenTestFailed: '生成失败',
262
262
  featureWsRunning: '折叠时任务运行中提示',
263
- featureWsRunningDesc: '有会话在运行时,折叠的项目与“任务”“已置顶”等分区头显示与任务运行中同款的加载图标,全部结束后消失。',
263
+ featureWsRunningDesc: '有会话运行时,折叠的项目与任务分区头显示加载图标,全部结束消失。',
264
264
  tabPerformance: '性能',
265
265
  perfDesc: 'ZCode 为每个打开过的项目常驻一个会话进程,应用自身不会回收,项目多了内存占用就高。开启后,长时间未使用且没有任务在跑的项目会被自动释放:会话记录不受影响,再次打开时自动恢复,仅首次回复有几秒等待。正在使用的项目、运行中的会话与后台命令、定时任务都不会被回收。后台命令检测依赖系统进程信息(Linux / macOS / Windows 各走系统自带工具),检测不可用时功能自动停用。',
266
266
  featureIdleReclaim: '空闲项目内存回收',
@@ -270,17 +270,17 @@ const zh = {
270
270
  idleReclaimStartupHint: '应用启动会为每个恢复的项目预热进程,到点把未使用的项目一次收掉;0 = 启动后立即回收,该次不受上一项空闲时长限制',
271
271
  idleReclaimUnit: '分钟',
272
272
  featureToolbarIcons: '侧栏菜单并入顶栏',
273
- featureToolbarIconsDesc: '把侧边栏顶部的“新建任务 / 搜索 / 自动化 / 插件市场”收成顶部导航栏(后退/前进旁)的图标按钮:悬停显示名称,对应界面打开时高亮,快捷键照常可用;侧边栏原菜单隐藏。',
273
+ featureToolbarIconsDesc: '新建任务/搜索/自动化/插件市场收成顶栏图标按钮。',
274
274
  tbNewTask: '新建任务',
275
275
  tbSearch: '搜索',
276
276
  tbAutomations: '自动化',
277
277
  tbPluginStore: '插件市场',
278
278
  featurePinnedCollapse: '已置顶分区可折叠',
279
- featurePinnedCollapseDesc: '侧边栏“已置顶”标题可点击折叠/展开其任务列表(与“项目”“分组”分区一致),状态记住。',
279
+ featurePinnedCollapseDesc: '侧边栏已置顶标题可点击折叠/展开其任务列表。',
280
280
  featureSandboxReclaim: '插件界面沙箱回收',
281
- featureSandboxReclaimDesc: '插件卡片沙箱数量达到上限时自动释放最早创建的(正在查看的排最后),避免 64 个名额耗尽后新卡片打不开;被释放的旧卡片重新打开所在会话即恢复(预算可在配置文件 sandboxReclaimBudget 调整)。',
281
+ featureSandboxReclaimDesc: '沙箱满 64 个时自动释放最早的,新卡片不再打不开;旧卡重新打开所在会话即可恢复。',
282
282
  featurePluginCardRetry: '插件卡片自动重试',
283
- featurePluginCardRetryDesc: '因名额上限加载失败的插件卡片自动点击“重新加载”,名额空出后自动恢复,无需手动蹲守。',
283
+ featurePluginCardRetryDesc: '因名额上限加载失败的插件卡片自动点击重新加载,名额空出后自动恢复。',
284
284
  visionCardUi: '思考过程显示识图卡片',
285
285
  pinnedToggleAria: '折叠/展开已置顶',
286
286
  sidebarProjectSpacingName: '项目间距',
@@ -303,7 +303,7 @@ const zh = {
303
303
  switcherAttentionHint: '运行中 / 运行完未查看的任务 · {prev} / {next} 或 ↑ / ↓ 选择,Enter 或松开 alt 切换',
304
304
  switcherAttentionEmpty: '暂无运行中或未查看的任务',
305
305
  featureNewtabPanels: '新标签页插件面板入口',
306
- featureNewtabPanelsDesc: '官方“新标签页”只列官方面板与推荐的官方插件,此功能把第三方市场插件(headroom、rtk 等)的面板卡片加进去,点击在侧边面板打开。',
306
+ featureNewtabPanelsDesc: '新标签页加入第三方插件(headroom、rtk 等)的面板卡片,点击在侧边面板打开。',
307
307
  ntpDescSuffix: '插件面板',
308
308
  ntpNeedSession: '先打开一个任务会话,再从新标签页打开插件面板',
309
309
  featureStatusBarDesc: '提问输入框右下角和每轮回答最后显示统计项。',
@@ -361,6 +361,8 @@ const zh = {
361
361
  themeBgPanel: '面板不透明度',
362
362
  themeBgPanelHint: '侧栏、面板与标签栏的不透明程度。越低,透出的图片越多。',
363
363
  themeBgDeletedActive: '当前背景已删除,已自动停用背景',
364
+ tableCopyBtn: '复制表格',
365
+ tableCopied: '表格已复制',
364
366
  sbItemCtx: '上下文占用',
365
367
  sbItemQuestions: '提问次数',
366
368
  sbMaster: '启用统计',
@@ -413,7 +415,11 @@ const zh = {
413
415
  featureFileActions: '文件菜单增强',
414
416
  featureFileActionsDesc: '在会话中文件链接的右键菜单里新增“默认应用打开”与“打开所在目录”。',
415
417
  featureImageCopy: '图片右键复制',
418
+ featureTableCopy: '复制表格',
419
+ featureComposerProvider: '模型名显示供应商',
416
420
  featureImageCopyDesc: '右键会话中的图片或点击放大的预览图,可将图片复制到剪贴板。',
421
+ featureTableCopyDesc: '表格工具条新增复制表格,粘贴进 Excel / 富文本编辑器即为干净表格。',
422
+ featureComposerProviderDesc: '模型名前显示供应商名(如 BigModel-Max1 / glm-5.3),区分同名模型。',
417
423
  imageCopy: '复制图片',
418
424
  imageCopied: '图片已复制到剪贴板',
419
425
  imageCopyFailed: '复制图片失败',
@@ -436,7 +442,7 @@ const zh = {
436
442
  pluginsUpdatesAvailable: 'zcode-plugins 有 {n} 个插件可更新(ZCode Pro 设置 → 功能)',
437
443
  pluginsAutoUpdated: 'zcode-plugins 插件已更新',
438
444
  tabVision: 'Vision',
439
- visionDesc: '主模型不支持图片输入时(如 glm-5.3),自动把消息里的图片交给下面的视觉模型识别,把描述带给主模型(zcode-vision 插件;/vision-* 命令编辑同一配置)。改动自动保存。',
445
+ visionDesc: '主模型不支持图片输入时(如 glm-5.3),自动把消息里的图片交给下面的视觉模型识别,把描述带给主模型(zcode-vision 插件;/vision-* 命令编辑同一配置)。',
440
446
  visionEnabled: '启用',
441
447
  visionForceIntercept: '主模型能看图也拦截',
442
448
  visionMode: '链模式',
@@ -467,14 +473,14 @@ const zh = {
467
473
  visionCompressKBHint: '超过该大小的图先压缩再识别(最长边 2000、JPEG);0 = 不压缩',
468
474
  visionLoadFailed: '读取视觉代理配置失败',
469
475
  tabRtk: 'rtk',
470
- rtkDesc: 'rtk 插件:把常见开发命令的输出压缩 60-90% 再进入上下文(/rtk-* 命令编辑同一配置)。改动自动保存并立即生效。',
476
+ rtkDesc: 'rtk 插件:把常见开发命令的输出压缩 60-90% 再进入上下文(/rtk-* 命令编辑同一配置)。',
471
477
  rtkEnabled: '启用',
472
478
  rtkNotInstalled: '未安装 rtk 本体;安装后以下设置生效(会话里执行 /rtk-setup 也可以)',
473
479
  rtkInstallBin: '安装 rtk 本体',
474
480
  rtkBinInstalled: 'rtk 本体已安装;PATH 需注销重登后对会话生效,插件钩子即时生效',
475
481
  rtkWhitelistTitle: '白名单(不压缩直接放行)',
476
482
  rtkBuiltinLabel: '内置放行(只读,随插件更新):',
477
- rtkWhitelistDesc: '每条 name(如 docker)或 git:name(如 git:clone)。输出极小或纯副作用的命令无需压缩,加入后不再压缩;内置的 git add/commit/push、mkdir/cp 等不可移除。删除本文件即恢复默认。',
483
+ rtkWhitelistDesc: '每条 name(如 docker)或 git:name(如 git:clone),加入后不再压缩;内置的 git add/commit/push、mkdir/cp 等不可移除。',
478
484
  rtkWhitelistPlaceholder: 'name 或 git:name,回车添加',
479
485
  rtkWhitelistAdd: '添加',
480
486
  rtkWhitelistClear: '清空',
@@ -482,18 +488,18 @@ const zh = {
482
488
  rtkWhitelistInvalid: '条目格式应为 name 或 git:name',
483
489
  rtkLoadFailed: '读取 rtk 配置失败',
484
490
  tabHeadroom: 'Headroom',
485
- headroomDesc: 'Headroom 插件:本地压缩代理驻留 127.0.0.1(默认 8787 端口),压缩发往模型的上下文以省 token(/hr-* 命令编辑同一配置)。压缩设备与省电切换保存后立即生效;切换设备会重启代理,正在处理的请求可能中断一次。',
491
+ headroomDesc: 'Headroom 插件:本地压缩代理驻留 127.0.0.1(默认 8787 端口),压缩发往模型的上下文以省 token(/hr-* 命令编辑同一配置)。切换设备会重启代理,正在处理的请求可能中断一次。',
486
492
  headroomBinMissing: '未找到 headroom 程序,代理无法启动;安装 headroom 后以下设置自动生效',
487
493
  hrInstallBin: '安装 headroom 本体',
488
494
  hrBinInstalled: 'headroom 本体已安装;重新打开会话后代理可用(SessionStart 钩子自动拉起)',
489
495
  headroomInstallTitle: '未安装 headroom 插件',
490
- headroomInstallDesc: '从 zcode-plugins 市场安装后即可在此管理压缩代理;建议安装后重启会话生效。',
496
+ headroomInstallDesc: '从 zcode-plugins 市场安装后即可在此管理压缩代理;重启会话后生效。',
491
497
  headroomInstall: '安装',
492
498
  headroomInstalling: '安装中…',
493
499
  headroomInstalled: '已安装 headroom 插件',
494
500
  headroomInstallRetry: '安装未生效,请重试或手动在插件市场安装',
495
501
  pluginInstallCardTitle: '未安装 {name} 插件',
496
- pluginInstallCardDesc: '从 zcode-plugins 市场安装后即可在此管理;建议安装后重启会话生效。',
502
+ pluginInstallCardDesc: '从 zcode-plugins 市场安装后即可在此管理;重启会话后生效。',
497
503
  pluginInstallDone: '已安装 {name} 插件',
498
504
  pluginEnable: '启用',
499
505
  hrStarting: '正在启动压缩代理…',
@@ -536,7 +542,7 @@ const zh = {
536
542
  headroomBackendAuto: '自动 · 显卡优先',
537
543
  headroomBackendCpu: '省电 · CPU 优先',
538
544
  headroomBackendNative: '(原生值)',
539
- headroomBackendDesc: '自动:headroom 选最快设备(CUDA/MPS 显卡优先);CPU:强制 CPU 压缩,省电且不占显存。也支持 headroom 原生值(onnx、pytorch_mps 等)。切换会重启代理立即生效。',
545
+ headroomBackendDesc: '自动:headroom 选最快设备(CUDA/MPS 显卡优先);CPU:强制 CPU 压缩,省电且不占显存。也支持 headroom 原生值(onnx、pytorch_mps 等)。',
540
546
  headroomPower: '省电自动切换',
541
547
  headroomPowerOff: '关闭',
542
548
  headroomPowerOffDesc: '不自动切换,始终按“压缩设备”运行。',
@@ -546,7 +552,7 @@ const zh = {
546
552
  headroomPowerSaverDesc: '仅系统进入省电模式 → CPU 压缩,退出切回。',
547
553
  hrCpuPinnedHint: '压缩设备已固定为 CPU,省电自动切换不会再改变它。',
548
554
  headroomInterval: '监视间隔',
549
- headroomIntervalDesc: '省电监视器轮询电源状态的间隔(10–3600 秒,默认 60);保存后从下一轮巡检起生效,无需重启。',
555
+ headroomIntervalDesc: '省电监视器轮询电源状态的间隔(10–3600 秒,默认 60);保存后从下一轮巡检起生效。',
550
556
  headroomSecUnit: '秒',
551
557
  upgradeCheck: '检查更新',
552
558
  upgradeChecking: '检查中…',
@@ -634,7 +640,7 @@ const en = {
634
640
  agentsSave: 'Save',
635
641
  agentsSaved: 'Global prompt saved.',
636
642
  agentsLoadFailed: 'Failed to load the global prompt',
637
- proxyDesc: 'Set an HTTP proxy for network access made by the local helper: plugin marketplace updates and installs, plugin dependency installs and upgrades, and WebDAV sync can all use it — pick the scopes with the toggles below. Does not affect the ZCode app itself or model requests. Applies immediately.',
643
+ proxyDesc: 'Set an HTTP proxy for network access made by the local helper: plugin marketplace updates and installs, plugin dependency installs and upgrades, and WebDAV sync can all use it — pick the scopes with the toggles below. Does not affect the ZCode app itself or model requests.',
638
644
  proxyPlaceholder: 'http://127.0.0.1:7890',
639
645
  proxySave: 'Save',
640
646
  proxySaved: 'Proxy settings saved.',
@@ -648,7 +654,7 @@ const en = {
648
654
  proxyScopeDeps: 'Plugin dependencies',
649
655
  proxyScopeDepsDesc: 'headroom and rtk installs, update checks and upgrades (pip and GitHub downloads) go through the proxy.',
650
656
  proxyScopeSync: 'Sync',
651
- proxyScopeSyncDesc: 'WebDAV upload and download go through the proxy. Off by default: domestic WebDAV providers work fine direct, and a stopped proxy breaks sync.',
657
+ proxyScopeSyncDesc: 'WebDAV upload and download go through the proxy.',
652
658
  tabSync: 'Sync',
653
659
  tabDebug: 'Debug',
654
660
  testNoSessionHint: 'With “follow current session [current model]” selected, the provider [model] of the last session is used.',
@@ -735,13 +741,13 @@ const en = {
735
741
  defaultValue: 'default',
736
742
  resetDefault: 'Reset to default',
737
743
  featureProjectMenu: 'Project menu actions',
738
- featureProjectMenuDesc: 'Custom alias, switch folder, open folder and copy path in the project "More" menu (the alias also applies to the sidebar).',
744
+ featureProjectMenuDesc: 'Open folder and copy path in the project More menu (alias and folder switch retired).',
739
745
  featureTaskOrder: 'Sidebar session drag ordering',
740
- featureTaskOrderDesc: 'Makes session drags in Pinned, Projects and Groups persist across refreshes.',
746
+ featureTaskOrderDesc: 'Drag to reorder sessions in Pinned, Projects and Groups; survives restarts.',
741
747
  featureSessionSwitch: 'Session quick switch',
742
- featureSessionSwitchDesc: '{last} toggles between the current and the last session; hold alt and press {prev}/{next} to move the highlight across recently used sessions (10 shown initially, pressing {next} at the bottom loads 5 more), {recent} opens the last 10 directly, {attention} opens running / finished-but-unseen tasks; arrow keys also move the highlight, Enter or release alt to switch (like alt+tab)',
748
+ featureSessionSwitchDesc: '{last} toggles to the last session; alt + arrows pick recent sessions; keys remappable',
743
749
  featureTitleLock: 'Keep renamed session titles',
744
- featureTitleLockDesc: 'Sessions you renamed keep their custom title: after new replies the app may show an auto-generated title instead, which reverts after a restart; this keeps the renamed title in the sidebar.',
750
+ featureTitleLockDesc: 'Sessions you renamed keep their custom title in the sidebar.',
745
751
  tabTitleGen: 'Task name prompt',
746
752
  titleGenShortDesc: 'When a new session starts answering, generates the task name with the chosen provider and prompt and saves it as a manual rename the built-in title never overrides.',
747
753
  titleGenEnabled: 'Enable',
@@ -767,7 +773,7 @@ const en = {
767
773
  titleGenTestOk: 'Result: ',
768
774
  titleGenTestFailed: 'Generation failed',
769
775
  featureWsRunning: 'Running indicator when collapsed',
770
- featureWsRunningDesc: 'Shows the same spinning loader as running tasks on collapsed projects and on section headers (“Tasks”, “Pinned”) while any session is running; clears when they all finish.',
776
+ featureWsRunningDesc: 'Collapsed project and task headers show a loader while sessions run.',
771
777
  tabPerformance: 'Performance',
772
778
  perfDesc: 'ZCode keeps a session process resident for every project you have opened and never reclaims it, so memory grows with projects. When enabled, projects unused for a while with nothing running release their memory automatically: sessions are untouched and reload when reopened, with a few seconds wait on the first reply. The project in use, running sessions, background commands and scheduled tasks are never reclaimed. Background-command detection reads system process info via built-in tools (Linux / macOS / Windows); when unavailable, the feature disables itself.',
773
779
  featureIdleReclaim: 'Idle project memory reclaim',
@@ -777,15 +783,15 @@ const en = {
777
783
  idleReclaimStartupHint: 'Startup pre-spawns a process per restored project; this pass collects the unused ones once. 0 = reclaim right after startup, ignoring the idle minutes above',
778
784
  idleReclaimUnit: 'min',
779
785
  featureToolbarIcons: 'Sidebar menu in the top bar',
780
- featureToolbarIconsDesc: 'Turns the sidebar menu (New task / Search / Automations / Plugin store) into icon buttons next to the back/forward arrows in the top bar: hover shows the name, the button stays highlighted while its view is open, and shortcuts still work; the original sidebar menu is hidden.',
786
+ featureToolbarIconsDesc: 'New task / search / automations / plugin store become top-bar icon buttons.',
781
787
  tbNewTask: 'New task',
782
788
  tbSearch: 'Search',
783
789
  tbAutomations: 'Automations',
784
790
  tbPluginStore: 'Plugin store',
785
791
  featurePinnedCollapse: 'Collapsible Pinned section',
786
- featurePinnedCollapseDesc: 'The sidebar "Pinned" header collapses/expands its task list (like Projects and Groups); the state is remembered.',
792
+ featurePinnedCollapseDesc: 'The Pinned header collapses/expands its task list.',
787
793
  featureSandboxReclaim: 'Plugin card sandbox reclaim',
788
- featureSandboxReclaimDesc: 'Releases the oldest plugin-card sandboxes once the cap is hit (the one being viewed goes last), so new cards keep opening after the 64-slot pool fills up; released cards come back when their session is reopened (budget adjustable via sandboxReclaimBudget in the config file).',
794
+ featureSandboxReclaimDesc: 'Reclaims the oldest sandboxes at the 64 cap so new cards keep opening; released cards come back when their session is reopened.',
789
795
  featurePluginCardRetry: 'Plugin card auto-retry',
790
796
  featurePluginCardRetryDesc: 'Auto-clicks "Reload" on cards that failed the slot cap; they recover as soon as a slot frees up.',
791
797
  visionCardUi: 'Vision cards',
@@ -810,7 +816,7 @@ const en = {
810
816
  switcherAttentionHint: 'Running / finished but unseen tasks · {prev} / {next} or ↑ / ↓ to choose, Enter or release alt to switch',
811
817
  switcherAttentionEmpty: 'No running or unseen tasks',
812
818
  featureNewtabPanels: 'Plugin panels on the new tab page',
813
- featureNewtabPanelsDesc: "The built-in \"new tab page\" only lists built-in panels and recommended official plugins; this adds cards for third-party marketplace plugin panels (headroom, rtk, …) that open in the side pane.",
819
+ featureNewtabPanelsDesc: "Adds third-party plugin panels (headroom, rtk, ...) to the new tab page; click to open in the side pane.",
814
820
  ntpDescSuffix: 'plugin panel',
815
821
  ntpNeedSession: 'Open a task session first, then open plugin panels from the new tab page',
816
822
  featureStatusBarDesc: 'Shows statistics at the bottom-right of the input box and at the end of each answer.',
@@ -868,6 +874,8 @@ const en = {
868
874
  themeBgPanel: 'Panel opacity',
869
875
  themeBgPanelHint: 'Opacity of sidebar, panels and tab bar. Lower reveals more of the image.',
870
876
  themeBgDeletedActive: 'Active background was deleted; background disabled',
877
+ tableCopyBtn: 'Copy table',
878
+ tableCopied: 'Table copied',
871
879
  sbItemCtx: 'Context usage',
872
880
  sbItemQuestions: 'Questions asked',
873
881
  sbMaster: 'Enable statistics',
@@ -920,7 +928,11 @@ const en = {
920
928
  featureFileActions: 'File menu actions',
921
929
  featureFileActionsDesc: 'Adds "Open with default app" and "Reveal in file manager" to the right-click menu of file links in chat.',
922
930
  featureImageCopy: 'Image right-click copy',
931
+ featureTableCopy: 'Copy table',
932
+ featureComposerProvider: 'Provider in model name',
923
933
  featureImageCopyDesc: 'Right-click an image in chat or the enlarged preview to copy it to the clipboard.',
934
+ featureTableCopyDesc: 'Copy-table button on the table toolbar; pastes into Excel / editors as a clean table.',
935
+ featureComposerProviderDesc: 'Shows the provider before the model name (e.g. BigModel-Max1 / glm-5.3).',
924
936
  imageCopy: 'Copy image',
925
937
  imageCopied: 'Image copied to the clipboard.',
926
938
  imageCopyFailed: 'Failed to copy the image.',
@@ -943,7 +955,7 @@ const en = {
943
955
  pluginsUpdatesAvailable: '{n} zcode-plugins update(s) available (ZCode Pro settings → Features)',
944
956
  pluginsAutoUpdated: 'zcode-plugins updated',
945
957
  tabVision: 'Vision',
946
- visionDesc: 'When the main model cannot see images (e.g. glm-5.3), images in messages are recognized by the vision models below and passed to the main model as text (zcode-vision plugin; /vision-* commands edit the same file). Changes save automatically.',
958
+ visionDesc: 'When the main model cannot see images (e.g. glm-5.3), images in messages are recognized by the vision models below and passed to the main model as text (zcode-vision plugin; /vision-* commands edit the same file).',
947
959
  visionEnabled: 'Enable',
948
960
  visionForceIntercept: 'Intercept even if the main model sees images',
949
961
  visionMode: 'Chain mode',
@@ -974,14 +986,14 @@ const en = {
974
986
  visionCompressKBHint: 'Images above this size are compressed first (max edge 2000, JPEG); 0 = never',
975
987
  visionLoadFailed: 'Failed to load the vision config',
976
988
  tabRtk: 'rtk',
977
- rtkDesc: 'rtk plugin: compresses common dev command output by 60-90% before it reaches the context (/rtk-* commands edit the same files). Changes save automatically and apply immediately.',
989
+ rtkDesc: 'rtk plugin: compresses common dev command output by 60-90% before it reaches the context (/rtk-* commands edit the same files).',
978
990
  rtkEnabled: 'Enable',
979
991
  rtkNotInstalled: 'rtk is not installed. The settings below take effect after installation (/rtk-setup in a session).',
980
992
  rtkInstallBin: 'Install rtk binary',
981
993
  rtkBinInstalled: 'rtk installed; PATH takes effect for sessions after signing out and back in — the plugin hook works immediately',
982
994
  rtkWhitelistTitle: 'Whitelist (pass through without compression)',
983
995
  rtkBuiltinLabel: 'Built-in exemptions (read-only, follow plugin updates):',
984
- rtkWhitelistDesc: 'Each entry is name (e.g. docker) or git:name (e.g. git:clone). Commands with tiny or no output need no compression; adding them skips compression. Built-ins (git add/commit/push, mkdir/cp, …) cannot be removed. Delete the file to reset.',
996
+ rtkWhitelistDesc: 'Each entry is name (e.g. docker) or git:name (e.g. git:clone); added commands skip compression. Built-ins (git add/commit/push, mkdir/cp, …) cannot be removed.',
985
997
  rtkWhitelistPlaceholder: 'name or git:name, press Enter to add',
986
998
  rtkWhitelistAdd: 'Add',
987
999
  rtkWhitelistClear: 'Clear',
@@ -989,18 +1001,18 @@ const en = {
989
1001
  rtkWhitelistInvalid: 'Entry must be name or git:name',
990
1002
  rtkLoadFailed: 'Failed to load the rtk config',
991
1003
  tabHeadroom: 'Headroom',
992
- headroomDesc: 'Headroom plugin: a local compression proxy on 127.0.0.1 (port 8787 by default) that compresses context sent to the model to save tokens (/hr-* commands edit the same file). Device and power-save changes apply immediately; switching the device restarts the proxy, so requests in progress may be interrupted once.',
1004
+ headroomDesc: 'Headroom plugin: a local compression proxy on 127.0.0.1 (port 8787 by default) that compresses context sent to the model to save tokens (/hr-* commands edit the same file). Switching the device restarts the proxy, so requests in progress may be interrupted once.',
993
1005
  headroomBinMissing: 'The headroom program was not found, so the proxy cannot start. The settings below take effect once it is installed.',
994
1006
  hrInstallBin: 'Install headroom binary',
995
1007
  hrBinInstalled: 'headroom installed; reopen a session and the hook will start the proxy automatically',
996
1008
  headroomInstallTitle: 'headroom plugin not installed',
997
- headroomInstallDesc: 'Install it from the zcode-plugins marketplace to manage the compression proxy here; restarting the session afterwards is recommended.',
1009
+ headroomInstallDesc: 'Install it from the zcode-plugins marketplace to manage the compression proxy here; takes effect after a session restart.',
998
1010
  headroomInstall: 'Install',
999
1011
  headroomInstalling: 'Installing…',
1000
1012
  headroomInstalled: 'headroom plugin installed',
1001
1013
  headroomInstallRetry: 'Installation did not take effect; retry or install it from the marketplace manually',
1002
1014
  pluginInstallCardTitle: '{name} plugin not installed',
1003
- pluginInstallCardDesc: 'Install it from the zcode-plugins marketplace to manage it here; restarting the session afterwards is recommended.',
1015
+ pluginInstallCardDesc: 'Install it from the zcode-plugins marketplace to manage it here; takes effect after a session restart.',
1004
1016
  pluginInstallDone: '{name} plugin installed',
1005
1017
  pluginEnable: 'Enable',
1006
1018
  hrStarting: 'Starting compression proxy…',
@@ -1053,7 +1065,7 @@ const en = {
1053
1065
  headroomPowerSaverDesc: 'Only the system power-saver mode → CPU; back when it exits.',
1054
1066
  hrCpuPinnedHint: 'The compression device is pinned to CPU, so power-save switching no longer changes anything.',
1055
1067
  headroomInterval: 'Watch interval',
1056
- headroomIntervalDesc: 'Seconds between power checks (10–3600, default 60); applies from the next round, no restart needed.',
1068
+ headroomIntervalDesc: 'Seconds between power checks (10–3600, default 60); applies from the next round.',
1057
1069
  headroomSecUnit: 's',
1058
1070
  upgradeCheck: 'Check update',
1059
1071
  upgradeChecking: 'Checking…',
@@ -0,0 +1,112 @@
1
+ // 输入框模型名前显示供应商名:3.16.0 起官方触发器只显示模型名(如 glm-5.3),
2
+ // 同名模型在不同供应商下无法区分。按钮的 aria-label 本就带全名
3
+ //("BigModel-Max1/glm-5.3 最高"),从那里取 “/” 前的供应商段补到显示文本前,
4
+ // 显示为 “BigModel-Max1 / glm-5.3”。不监听点击、不改官方状态,纯显示增强;
5
+ // 应用重渲染会冲掉文本,MutationObserver 维持。
6
+ import { t, getConfig } from '../core.js';
7
+
8
+ let installed = false;
9
+ let enabled = true; // helper features.composerProvider(默认开)
10
+
11
+ // 从 aria-label 提取 “供应商 / 模型” 显示文本。
12
+ // label 形态:"<供应商>/<模型名> <附加说明(如 最高)>";有的供应商名本身
13
+ // 不含 /、有的官方项可能没有 /(旧版内置项),没有 / 时返回 null 不改
14
+ function labelToDisplay(label) {
15
+ const i = label.indexOf('/');
16
+ if (i <= 0) return null;
17
+ const provider = label.slice(0, i).trim();
18
+ const rest = label.slice(i + 1);
19
+ const model = rest.trim().split(/\s+/)[0];
20
+ if (!provider || !model) return null;
21
+ return `${provider} / ${model}`;
22
+ }
23
+
24
+ function apply() {
25
+ if (!enabled) {
26
+ // 关闭时把此前改写的文本还原为官方模型名(aria-label 的 “/” 后段),
27
+ // 不必等应用重渲染
28
+ for (const btn of document.querySelectorAll('[data-testid="chat-model-select-trigger"]')) {
29
+ if (!btn.dataset.zcodeproProviderShown) continue;
30
+ const label = btn.getAttribute('aria-label') || '';
31
+ const i = label.indexOf('/');
32
+ if (i <= 0) continue;
33
+ const model = label.slice(i + 1).trim().split(/\s+/)[0];
34
+ if (!model) continue;
35
+ for (const sp of btn.querySelectorAll('span[title]')) sp.setAttribute('title', model);
36
+ for (const el of btn.querySelectorAll('span[title] span:not([title])')) {
37
+ if (!el.querySelector('span') && el.textContent.includes('/')) el.textContent = model;
38
+ }
39
+ delete btn.dataset.zcodeproProviderShown;
40
+ }
41
+ return;
42
+ }
43
+ for (const btn of document.querySelectorAll('[data-testid="chat-model-select-trigger"]')) {
44
+ // 只处理 a[data-composer-combined-model] 主触发器;aria-label 缺失时跳过
45
+ const label = btn.getAttribute('aria-label') || '';
46
+ const display = labelToDisplay(label);
47
+ if (!display) continue;
48
+ // 显示 span 是嵌套两层的 title 结构(外层裁剪容器 + 内层相对定位层),
49
+ // 文本都在最内层 span;只改文本与 title,不动结构
50
+ // 官方结构:外层 title 容器 > 内层 title 层 > 纯文本 span(模型名)。
51
+ // 只改 title 链内的文本——按“祖先带 title”识别模型名文本;同级的
52
+ // “最高/开启”说明 span(无 title 祖先链上的 title 层)不属于模型名,
53
+ // 不能碰(首版误把它也替换成全名)
54
+ const titleSpans = [...btn.querySelectorAll('span[title]')];
55
+ if (!titleSpans.length) continue;
56
+ const innermost = titleSpans.filter((sp) => !sp.querySelector('span[title]'));
57
+ if (!innermost.length) continue;
58
+ const holders = [];
59
+ for (const sp of innermost) {
60
+ sp.querySelectorAll('span:not([title])').forEach((el) => {
61
+ if (!el.querySelector('span') && el.textContent.trim() && !el.textContent.includes('/')) holders.push(el);
62
+ });
63
+ }
64
+ if (!holders.length) continue;
65
+ for (const sp of titleSpans) {
66
+ if (sp.getAttribute('title') !== display) sp.setAttribute('title', display);
67
+ }
68
+ for (const el of holders) el.textContent = display;
69
+ btn.dataset.zcodeproProviderShown = '1';
70
+ }
71
+ }
72
+
73
+ export function startComposerProvider() {
74
+ if (installed || typeof document === 'undefined') return;
75
+ installed = true;
76
+ // 先过滤再调度:变更不在输入框工具栏子树(composer 容器)内的批次直接忽略,
77
+ // 连去抖定时器都不排——流式输出的高频文本变更不会叫醒本模块
78
+ const COMPOSER_SEL = '[data-composer-layout], [data-composer-leading-content], [data-testid="chat-model-select-trigger"]';
79
+ const relevant = (muts) => {
80
+ for (const m of muts) {
81
+ if (m.type === 'childList') {
82
+ for (const n of [...m.addedNodes, ...m.removedNodes]) {
83
+ if (n instanceof Element && (n.matches?.(COMPOSER_SEL) || n.querySelector?.(COMPOSER_SEL))) return true;
84
+ }
85
+ } else if (m.type === 'characterData') {
86
+ let el = m.target.parentElement;
87
+ while (el && el !== document.body) {
88
+ if (el.matches?.('[data-composer-layout], [data-composer-leading-actions], [data-composer-leading-content]')) return true;
89
+ el = el.parentElement;
90
+ }
91
+ }
92
+ }
93
+ return false;
94
+ };
95
+ const schedule = () => {
96
+ clearTimeout(schedule.t);
97
+ schedule.t = setTimeout(apply, 120);
98
+ };
99
+ void getConfig().then((cfg) => { enabled = cfg.features ? cfg.features.composerProvider !== false : true; }).catch(() => {});
100
+ window.addEventListener('zcodepro:config-changed', () => {
101
+ void getConfig(true).then((cfg) => {
102
+ const next = cfg.features ? cfg.features.composerProvider !== false : true;
103
+ if (next !== enabled) {
104
+ enabled = next;
105
+ apply(); // 开关切换即时生效:关时还原官方文本,开时补供应商名
106
+ }
107
+ }).catch(() => {});
108
+ });
109
+ const observer = new MutationObserver((muts) => { if (enabled && relevant(muts)) schedule(); });
110
+ observer.observe(document.body, { childList: true, subtree: true, characterData: true });
111
+ apply();
112
+ }
@@ -244,8 +244,17 @@ export function openSettingsDialog() {
244
244
  h('span', { class: 'text-foreground-subtle/70' }, `${health.version} · ${HELPER_URL}`)
245
245
  );
246
246
 
247
- const rows = h('div', { class: 'divide-y divide-border rounded-xl border border-border' });
247
+ // 双列网格:功能开关行短小(名称+开关+折行描述),两列排布把 15+ 行压到
248
+ // 一屏内。列间加分隔线、行距拉开(gap-y-1),格内不留底 padding——
249
+ // 悬停底色由行自身圆角承担,避免一格一格的补丁感
250
+ const rows = h('div', { class: 'grid grid-cols-2 gap-x-0 gap-y-1 rounded-xl border border-border p-2 divide-x divide-border' });
248
251
  const pluginsStatusLine = h('span', { class: 'max-w-40 truncate text-ui-xs/relaxed text-foreground-subtle' }, '…');
252
+ // 状态文字写入统一入口:文本与悬停 title 一起更新(描述位被动态内容替换后
253
+ // 仍保持悬停全文约定)
254
+ const setPluginsStatus = (text) => {
255
+ setPluginsStatus(text);
256
+ pluginsStatusLine.setAttribute('title', text);
257
+ };
249
258
  const pluginsBtn = btnSmall(L.pluginsCheckNow, () => { void runPluginsUpdate(); });
250
259
  let pluginsUpdating = false;
251
260
  const runPluginsUpdate = async () => {
@@ -253,18 +262,18 @@ export function openSettingsDialog() {
253
262
  if (pluginsUpdating) return;
254
263
  pluginsUpdating = true;
255
264
  pluginsBtn.disabled = true;
256
- pluginsStatusLine.textContent = L.pluginsUpdating;
265
+ setPluginsStatus(L.pluginsUpdating);
257
266
  const res = await rpc('/plugins/update', { method: 'POST', body: {} });
258
267
  pluginsUpdating = false;
259
268
  pluginsBtn.disabled = false;
260
269
  if (!res.ok) {
261
- pluginsStatusLine.textContent = '';
270
+ setPluginsStatus('');
262
271
  showToast(L.pluginsUpdateFailed + ': ' + errText(res), 'error');
263
272
  return;
264
273
  }
265
- pluginsStatusLine.textContent = res.updated > 0
274
+ setPluginsStatus(res.updated > 0
266
275
  ? L.pluginsUpdatedDone.replaceAll('{n}', String(res.updated))
267
- : L.pluginsUpToDate;
276
+ : L.pluginsUpToDate);
268
277
  showToast(pluginsStatusLine.textContent, 'success');
269
278
  };
270
279
  void (async () => {
@@ -272,15 +281,15 @@ export function openSettingsDialog() {
272
281
  // 状态行如实显示过程与结果;开关关闭时只检查并显示“N 个可更新”
273
282
  const cfg = await getConfig(true).catch(() => null);
274
283
  const res = await rpc('/plugins/status');
275
- if (!res.ok) { pluginsStatusLine.textContent = ''; return; }
284
+ if (!res.ok) { setPluginsStatus(''); return; }
276
285
  const n = (res.updates || []).length;
277
286
  if (cfg && (cfg.features || {}).autoUpdatePlugins === true && n > 0) {
278
287
  void runPluginsUpdate();
279
288
  return;
280
289
  }
281
- pluginsStatusLine.textContent = n > 0
290
+ setPluginsStatus(n > 0
282
291
  ? L.pluginsUpdatesFound.replaceAll('{n}', String(n))
283
- : L.pluginsUpToDate;
292
+ : L.pluginsUpToDate);
284
293
  })();
285
294
 
286
295
  // 会话快捷切换:入口键位(“快捷键”标签页可改);此处定义在 refreshRows 之前,
@@ -360,6 +369,16 @@ export function openSettingsDialog() {
360
369
  if (await setFeature('imageCopy', next)) f.imageCopy = next;
361
370
  refreshRows();
362
371
  }),
372
+ settingRow(L.featureTableCopy, L.featureTableCopyDesc, f.tableCopy !== false, async () => {
373
+ const next = !(f.tableCopy !== false);
374
+ if (await setFeature('tableCopy', next)) f.tableCopy = next;
375
+ refreshRows();
376
+ }),
377
+ settingRow(L.featureComposerProvider, L.featureComposerProviderDesc, f.composerProvider !== false, async () => {
378
+ const next = !(f.composerProvider !== false);
379
+ if (await setFeature('composerProvider', next)) f.composerProvider = next;
380
+ refreshRows();
381
+ }),
363
382
  settingRow(L.featureSandboxReclaim, L.featureSandboxReclaimDesc, f.sandboxReclaim !== false, async () => {
364
383
  const next = !(f.sandboxReclaim !== false);
365
384
  if (await setFeature('sandboxReclaim', next)) f.sandboxReclaim = next;
@@ -381,7 +400,7 @@ export function openSettingsDialog() {
381
400
  refreshRows();
382
401
  }),
383
402
  (() => {
384
- // 标题 = “自动更新 duanluan/zcode-plugins 插件”,仓库名可点击跳转仓库
403
+ // 标题 = “自动更新 duanluan/zcode-plugins”,仓库名可点击跳转仓库
385
404
  const repoLink = h('span', {
386
405
  class: 'cursor-pointer underline-offset-4 hover:underline',
387
406
  title: PLUGINS_REPO_URL,
@@ -392,17 +411,19 @@ export function openSettingsDialog() {
392
411
  if (await setFeature('autoUpdatePlugins', next)) f.autoUpdatePlugins = next;
393
412
  refreshRows();
394
413
  }, h('div', { class: 'flex items-center gap-2' }, pluginsBtn));
395
- // 标题行:自动更新 <链接> 插件;描述位放状态字(检查结果/更新进度)
414
+ // 标题行:自动更新 <链接>;描述位放状态字(检查结果/更新进度)
396
415
  const nameEl = row.querySelector('.text-ui-sm.font-medium');
397
416
  if (nameEl) {
398
417
  nameEl.replaceChildren(
399
418
  document.createTextNode(L.featureAutoUpdatePlugins + ' '),
400
419
  repoLink,
401
- document.createTextNode(' 插件'),
402
420
  );
403
421
  }
404
422
  const descEl = row.querySelector('[data-zcodepro-row-desc]');
405
- if (descEl) descEl.replaceChildren(pluginsStatusLine);
423
+ if (descEl) {
424
+ descEl.replaceChildren(pluginsStatusLine);
425
+ descEl.setAttribute('title', descEl.textContent.trim());
426
+ }
406
427
  return row;
407
428
  })(),
408
429
  );
@@ -560,12 +581,13 @@ export function openSettingsDialog() {
560
581
  const paneTitleGen = h('div', { role: 'tabpanel', class: 'mt-4', style: 'display:none' });
561
582
  const paneDebug = h('div', { role: 'tabpanel', class: 'mt-4', style: 'display:none' });
562
583
  const panes = { features: paneFeatures, shortcuts: paneShortcuts, styles: paneStyles, perf: panePerf, agents: paneAgents, proxy: paneProxy, sync: paneSync, vision: paneVision, headroom: paneHeadroom, rtk: paneRtk, debug: paneDebug };
584
+ // 一级 tab 顺序:功能 → 外观 → 行为 → 性能 → 快捷键 → 代理/同步/插件页
563
585
  const tabDefs = [
564
586
  ['features', L.tabFeatures],
565
- ['shortcuts', L.tabShortcuts],
566
- ['agents', L.tabBehavior],
567
587
  ['styles', L.tabStyles],
588
+ ['agents', L.tabBehavior],
568
589
  ['perf', L.tabPerformance],
590
+ ['shortcuts', L.tabShortcuts],
569
591
  ['proxy', L.tabProxy],
570
592
  ['sync', L.tabSync],
571
593
  ['vision', L.tabVision],