zcode-pro 0.19.0 → 0.20.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/bin/zcode-pro.mjs CHANGED
@@ -7,8 +7,7 @@ import { spawn, spawnSync } from 'node:child_process';
7
7
  import { fileURLToPath } from 'node:url';
8
8
 
9
9
  // 动态导入必须用 URL 对象:Windows 上绝对路径(C:\...)会被当作 c: 协议报错
10
- const cliUrl = new URL('../cli.mjs', import.meta.url);
11
- const cliPath = fileURLToPath(cliUrl);
10
+ const cliPath = fileURLToPath(new URL('../cli.mjs', import.meta.url));
12
11
  const argv = process.argv.slice(2);
13
12
 
14
13
  async function runWithZcodeRuntime() {
@@ -17,21 +16,45 @@ async function runWithZcodeRuntime() {
17
16
  const env = { ...process.env, ELECTRON_RUN_AS_NODE: '1' };
18
17
  // 预检:注册表等来源找到的 ZCode 可能不完整(缺 resources/ICU 数据等),以 Node
19
18
  // 模式跑不起来的直接抛错回退系统 node,避免启动器跟着报难懂的错误退出
19
+ // --no-warnings:node:sqlite 的 ExperimentalWarning 每次启动必然刷屏、用户
20
+ // 常误当成报错(在模块加载阶段同步打印,进程内拦不住),由调用方加旗标关闭
20
21
  const pre = spawnSync(zcodeBin, ['-e', 'process.exit(0)'], { env, stdio: 'ignore' });
21
22
  if (pre.status !== 0) throw new Error(`ZCode 运行时不可用(${zcodeBin})`);
22
23
  await new Promise((resolve, reject) => {
23
- const child = spawn(zcodeBin, [cliPath, ...argv], { stdio: 'inherit', env });
24
- child.on('error', reject);
25
- child.on('exit', (code, signal) => {
26
- if (signal) process.kill(process.pid, signal);
27
- else process.exit(code ?? 0);
24
+ const child = spawn(zcodeBin, ['--no-warnings', cliPath, ...argv], { stdio: 'inherit', env });
25
+ // spawn 成功才挂退出转发并 resolve。spawn 失败的 error 事件晚一拍才发,同步
26
+ // resolve 会把 reject 变成空操作(失败被吞、进程静默退 0、回退分支不触发);
27
+ // 且失败后 exit 是否触发文档未定义,退出转发提前挂载可能把回退拉起的进程
28
+ // 退成 0。失败时只走 reject → 回退分支
29
+ child.once('spawn', () => {
30
+ child.on('exit', (code, signal) => {
31
+ if (signal) process.kill(process.pid, signal);
32
+ else process.exit(code ?? 0);
33
+ });
34
+ resolve();
28
35
  });
29
- resolve();
36
+ child.once('error', reject);
30
37
  });
31
38
  }
32
39
 
33
40
  try {
34
41
  await runWithZcodeRuntime();
35
42
  } catch {
36
- await import(cliUrl);
43
+ // 回退系统 node。不能在本进程 import cli.mjs:本进程没有 --no-warnings 旗标
44
+ //(进程已启动,无法追加),node:sqlite 的实验特性警告会刷屏——用户误当报错。
45
+ // 同样以带旗标的子进程重跑(npm 环境必有 node,spawn 自身路径可靠)
46
+ const child = spawn(process.execPath, ['--no-warnings', cliPath, ...argv], {
47
+ stdio: 'inherit', env: process.env,
48
+ });
49
+ child.once('error', (err) => {
50
+ console.error(`[zcodepro] failed to launch: ${err?.message || err}`);
51
+ process.exit(1);
52
+ });
53
+ // 退出转发同样等 spawn 成功再挂(理由见 runWithZcodeRuntime 内注释)
54
+ child.once('spawn', () => {
55
+ child.on('exit', (code, signal) => {
56
+ if (signal) process.kill(process.pid, signal);
57
+ else process.exit(code ?? 0);
58
+ });
59
+ });
37
60
  }
package/dist/inject.js CHANGED
@@ -59,12 +59,16 @@
59
59
  proxyScopeSyncDesc: "WebDAV \u4E0A\u4F20\u4E0E\u4E0B\u8F7D\u8D70\u4EE3\u7406\u3002\u9ED8\u8BA4\u5173\u95ED\uFF1A\u575A\u679C\u4E91\u7B49\u56FD\u5185\u7F51\u76D8\u76F4\u8FDE\u5373\u53EF\uFF0C\u4EE3\u7406\u505C\u4E86\u540C\u6B65\u4F1A\u8DDF\u7740\u5931\u8D25\u3002",
60
60
  tabSync: "\u540C\u6B65",
61
61
  tabDebug: "\u8C03\u8BD5",
62
- syncDesc: "\u624B\u52A8\u628A ZCode Pro \u8BBE\u7F6E\u4E0E\u6A21\u578B\u8BBE\u7F6E\u5907\u4EFD\u5230 WebDAV\uFF0C\u6216\u5728\u65B0\u7535\u8111\u4E0A\u6062\u590D\u3002\u4E24\u9879\u5404\u81EA\u72EC\u7ACB\u6210\u6587\u4EF6\uFF0C\u53EF\u53EA\u4E0A\u4F20\u6216\u53EA\u8986\u76D6\u5176\u4E2D\u4E00\u9879\uFF1B\u6BCF\u6B21\u4E0A\u4F20\u4F1A\u989D\u5916\u4FDD\u7559\u4E00\u4EFD\u5E26\u65F6\u95F4\u6233\u7684\u5386\u53F2\u7248\u672C\uFF0C\u4E0B\u8F7D\u65F6\u4EFB\u9009\u4E00\u4E2A\u7248\u672C\u6062\u590D\uFF1B\u4EC5\u5728\u70B9\u51FB\u6309\u94AE\u65F6\u4F20\u8F93\uFF0C\u4E0D\u505A\u81EA\u52A8\u540C\u6B65\u3002",
62
+ syncDesc: "\u624B\u52A8\u628A ZCode Pro \u8BBE\u7F6E\u4E0E\u6A21\u578B\u8BBE\u7F6E\u5907\u4EFD\u5230 WebDAV\uFF0C\u6216\u5728\u65B0\u7535\u8111\u4E0A\u6062\u590D\u3002\u4E24\u9879\u5404\u81EA\u72EC\u7ACB\u6210\u6587\u4EF6\uFF0C\u53EF\u53EA\u4E0A\u4F20\u6216\u53EA\u8986\u76D6\u5176\u4E2D\u4E00\u9879\uFF1B\u6BCF\u6B21\u4E0A\u4F20\u4F1A\u989D\u5916\u4FDD\u7559\u4E00\u4EFD\u5E26\u65F6\u95F4\u6233\u7684\u5386\u53F2\u7248\u672C\uFF08\u4FDD\u7559\u591A\u5C11\u4E2A\u89C1\u201C\u5386\u53F2\u7248\u672C\u201D\u4E00\u884C\uFF0C\u4E0A\u4F20\u540E\u81EA\u52A8\u5220\u9664\u8D85\u51FA\u7684\u6700\u65E7\u7248\u672C\uFF09\uFF0C\u4E0B\u8F7D\u65F6\u4EFB\u9009\u4E00\u4E2A\u7248\u672C\u6062\u590D\uFF1B\u4EC5\u5728\u70B9\u51FB\u6309\u94AE\u65F6\u4F20\u8F93\uFF0C\u4E0D\u505A\u81EA\u52A8\u540C\u6B65\u3002",
63
63
  syncUrlPlaceholder: "https://dav.jianguoyun.com/dav/",
64
64
  syncDir: "\u4E0A\u4F20\u76EE\u5F55",
65
65
  syncDirPlaceholder: "zcode-pro",
66
66
  syncLogin: "\u767B\u5F55",
67
67
  syncPassword: "\u5BC6\u7801",
68
+ syncHistoryLimit: "\u5386\u53F2\u7248\u672C",
69
+ syncHistoryLimitUnit: "\u4E2A",
70
+ syncHistoryLimitHint: "\u4E91\u7AEF\u6700\u591A\u4FDD\u7559\u6700\u8FD1\u51E0\u6B21\u4E0A\u4F20\u7559\u4E0B\u7684\u5386\u53F2\u7248\u672C\uFF0C\u4E0A\u4F20\u6210\u529F\u540E\u81EA\u52A8\u5220\u9664\u66F4\u65E9\u7684\uFF1B\u56FA\u5B9A\u540D\u6700\u65B0\u7248\u4E0D\u53D7\u5F71\u54CD\uFF1B0 = \u4E0D\u4FDD\u7559\u5386\u53F2\u7248\u672C",
71
+ syncPruned: "\u5DF2\u6E05\u7406 {n} \u4E2A\u66F4\u65E9\u7684\u5386\u53F2\u7248\u672C",
68
72
  syncSave: "\u4FDD\u5B58",
69
73
  syncSaved: "\u540C\u6B65\u914D\u7F6E\u5DF2\u4FDD\u5B58",
70
74
  syncFormHint: "\u540C\u6B65\u6587\u4EF6\u5B58\u5230\u201C\u4E0A\u4F20\u76EE\u5F55\u201D\uFF08\u9ED8\u8BA4 zcode-pro\uFF0C\u53EF\u591A\u7EA7\uFF0C\u81EA\u52A8\u521B\u5EFA\uFF09\uFF1Azcodepro.json \u4E0E model.json \u59CB\u7EC8\u662F\u6700\u65B0\u4E00\u7248\uFF0C\u65C1\u8FB9\u53E6\u4FDD\u7559\u5E26\u65F6\u95F4\u6233\u7684\u5386\u53F2\u7248\u672C\u3002",
@@ -85,8 +89,8 @@
85
89
  syncConfirmTitle: "\u4ECE\u4E91\u7AEF\u4E0B\u8F7D",
86
90
  syncConfirmDesc: "\u5C06\u7528\u6240\u9009\u6587\u4EF6\u8986\u76D6\u672C\u673A\u5BF9\u5E94\u7684\u4E00\u9879\uFF08\u8986\u76D6\u524D\u4F1A\u5728\u539F\u6587\u4EF6\u65C1\u7559\u4E0B .zcodepro-backup \u5907\u4EFD\uFF09\uFF1B\u672C\u673A\u7684\u540C\u6B65\u914D\u7F6E\u4E0E\u767B\u5F55\u51ED\u636E\u4E0D\u53D7\u5F71\u54CD\u3002\u6A21\u578B\u8BBE\u7F6E\u6062\u590D\u540E\u5EFA\u8BAE\u91CD\u542F ZCode \u751F\u6548\u3002\u786E\u8BA4\u5BFC\u5165\uFF1F",
87
91
  syncConfirmGo: "\u786E\u8BA4\u5BFC\u5165",
88
- syncPickDesc: "\u56FA\u5B9A\u540D\u6587\u4EF6\u59CB\u7EC8\u662F\u6700\u65B0\u4E00\u7248\uFF0C\u5E26\u65F6\u95F4\u6233\u7684\u662F\u6BCF\u6B21\u4E0A\u4F20\u7559\u4E0B\u7684\u5386\u53F2\u7248\u672C\uFF1B\u300C\u5BFC\u5165\u300D\u7528\u6240\u9009\u6587\u4EF6\u8986\u76D6\u672C\u673A\u5BF9\u5E94\u7684\u4E00\u9879\u3002",
89
- syncVerDeleteLatestNote: "\u6CE8\u610F\uFF1A\u8FD9\u662F\u201C\u6700\u65B0\u201D\u6587\u4EF6\uFF0C\u65E7\u7248 zcode-pro \u7684\u300C\u4ECE\u4E91\u7AEF\u4E0B\u8F7D\u300D\u4F9D\u8D56\u5B83\uFF0C\u5220\u9664\u540E\u65E7\u7248\u673A\u5668\u5C06\u65E0\u6CD5\u4E0B\u8F7D\uFF1B\u5E26\u65F6\u95F4\u6233\u7684\u5386\u53F2\u7248\u672C\u4E0D\u53D7\u5F71\u54CD\u3002",
92
+ syncPickDesc: "\u56FA\u5B9A\u540D\u6587\u4EF6\u59CB\u7EC8\u662F\u6700\u65B0\u4E00\u7248\uFF0C\u5E26\u65F6\u95F4\u6233\u7684\u662F\u6BCF\u6B21\u4E0A\u4F20\u7559\u4E0B\u7684\u5386\u53F2\u7248\u672C\uFF1B\u201C\u5BFC\u5165\u201D\u7528\u6240\u9009\u6587\u4EF6\u8986\u76D6\u672C\u673A\u5BF9\u5E94\u7684\u4E00\u9879\u3002",
93
+ syncVerDeleteLatestNote: "\u6CE8\u610F\uFF1A\u8FD9\u662F\u201C\u6700\u65B0\u201D\u6587\u4EF6\uFF0C\u65E7\u7248 zcode-pro \u7684\u201C\u4ECE\u4E91\u7AEF\u4E0B\u8F7D\u201D\u4F9D\u8D56\u5B83\uFF0C\u5220\u9664\u540E\u65E7\u7248\u673A\u5668\u5C06\u65E0\u6CD5\u4E0B\u8F7D\uFF1B\u5E26\u65F6\u95F4\u6233\u7684\u5386\u53F2\u7248\u672C\u4E0D\u53D7\u5F71\u54CD\u3002",
90
94
  syncHint: "\u540C\u6B65\u767B\u5F55\u51ED\u636E\u4EC5\u4FDD\u5B58\u5728\u672C\u673A zcodepro.json\uFF1B\u6A21\u578B\u8BBE\u7F6E\u5305\u542B\u81EA\u5B9A\u4E49\u4F9B\u5E94\u5546\u7684 API Key\uFF0C\u53EA\u4F1A\u4E0A\u4F20\u5230\u4F60\u81EA\u5DF1\u7684 WebDAV\u3002",
91
95
  syncTest: "\u6D4B\u8BD5",
92
96
  syncTesting: "\u6D4B\u8BD5\u4E2D\u2026",
@@ -110,8 +114,8 @@
110
114
  syncVerDeleted: "\u5DF2\u5220\u9664\u4E91\u7AEF\u5907\u4EFD",
111
115
  syncVerSaved: "\u5DF2\u4FDD\u5B58\u5230",
112
116
  devMode: "\u5F00\u53D1\u6A21\u5F0F",
113
- devModeRowDesc: "\u5F00\u542F\u540E\u51FA\u73B0\u300C\u91CD\u8F7D ZCode Pro\u300D\u6309\u94AE\uFF1B\u4E5F\u53EF\u7ED9\u542F\u52A8\u5668\u8BBE\u73AF\u5883\u53D8\u91CF ZCODEPRO_DEV=1 \u542F\u7528\u3002\u6B64\u6807\u7B7E\u9875\u4EC5\u5728\u5F00\u53D1\u6A21\u5F0F\u751F\u6548\u65F6\u663E\u793A\u3002",
114
- devModeDesc: "\u5F00\u53D1\u8C03\u8BD5\u7528\uFF1A\u6539\u5B8C zcode-pro \u4EE3\u7801\u540E\u4E0D\u5FC5\u91CD\u542F ZCode\uFF0C\u70B9\u300C\u91CD\u8F7D ZCode Pro\u300D\u5373\u53EF\u52A0\u8F7D\u65B0\u4EE3\u7801\u3002\u6D41\u7A0B\u4E3A\u81EA\u52A8\u6784\u5EFA\uFF08\u9700\u5728\u6E90\u7801\u76EE\u5F55\u8FD0\u884C\uFF09\u2192 \u4EE5\u65B0\u8FDB\u7A0B\u91CD\u542F ZCode Pro\uFF08\u672C\u5730\u52A9\u624B\u4E0E\u6CE8\u5165\u811A\u672C\u90FD\u6362\u65B0\uFF09\u2192 \u6240\u6709 ZCode \u7A97\u53E3\u81EA\u52A8\u5237\u65B0\u5E76\u91CD\u65B0\u6CE8\u5165\uFF1BZCode \u672C\u4F53\u4E0E\u5DF2\u6253\u5F00\u7684\u4F1A\u8BDD\u4E0D\u53D7\u5F71\u54CD\u3002",
117
+ devModeRowDesc: "\u5F00\u542F\u540E\u51FA\u73B0\u201C\u91CD\u8F7D ZCode Pro\u201D\u6309\u94AE\uFF1B\u4E5F\u53EF\u7ED9\u542F\u52A8\u5668\u8BBE\u73AF\u5883\u53D8\u91CF ZCODEPRO_DEV=1 \u542F\u7528\u3002\u6B64\u6807\u7B7E\u9875\u4EC5\u5728\u5F00\u53D1\u6A21\u5F0F\u751F\u6548\u65F6\u663E\u793A\u3002",
118
+ devModeDesc: "\u5F00\u53D1\u8C03\u8BD5\u7528\uFF1A\u6539\u5B8C zcode-pro \u4EE3\u7801\u540E\u4E0D\u5FC5\u91CD\u542F ZCode\uFF0C\u70B9\u201C\u91CD\u8F7D ZCode Pro\u201D\u5373\u53EF\u52A0\u8F7D\u65B0\u4EE3\u7801\u3002\u6D41\u7A0B\u4E3A\u81EA\u52A8\u6784\u5EFA\uFF08\u9700\u5728\u6E90\u7801\u76EE\u5F55\u8FD0\u884C\uFF09\u2192 \u4EE5\u65B0\u8FDB\u7A0B\u91CD\u542F ZCode Pro\uFF08\u672C\u5730\u52A9\u624B\u4E0E\u6CE8\u5165\u811A\u672C\u90FD\u6362\u65B0\uFF09\u2192 \u6240\u6709 ZCode \u7A97\u53E3\u81EA\u52A8\u5237\u65B0\u5E76\u91CD\u65B0\u6CE8\u5165\uFF1BZCode \u672C\u4F53\u4E0E\u5DF2\u6253\u5F00\u7684\u4F1A\u8BDD\u4E0D\u53D7\u5F71\u54CD\u3002",
115
119
  devReload: "\u91CD\u8F7D ZCode Pro",
116
120
  devReloading: "\u91CD\u8F7D\u4E2D\u2026",
117
121
  devReloadDesc: "\u70B9\u51FB\u540E\u7EA6 2-3 \u79D2\u5185\u6240\u6709 ZCode \u7A97\u53E3\u81EA\u52A8\u5237\u65B0\u4E00\u6B21\u5E76\u52A0\u8F7D\u65B0\u811A\u672C\u3002",
@@ -132,7 +136,7 @@
132
136
  lineHeightName: "\u56DE\u7B54\u884C\u9AD8",
133
137
  lineHeightDesc: "\u56DE\u7B54\u6B63\u6587\u7684\u884C\u9AD8\uFF08\u500D\u6570\uFF09\u3002",
134
138
  thinkingLineHeightName: "\u601D\u8003\u884C\u9AD8",
135
- thinkingLineHeightDesc: "\u300C\u5DF2\u5DE5\u4F5C/\u5DE5\u4F5C\u4E2D\u300D\u8FC7\u7A0B\u8BB0\u5F55\uFF08\u8BFB\u53D6/\u7F16\u8F91/\u67E5\u9605/\u7EC8\u7AEF\u6761\u76EE\u4E0E\u601D\u8003\u5185\u5BB9\uFF09\u7684\u884C\u9AD8\uFF08\u500D\u6570\uFF09\u3002",
139
+ thinkingLineHeightDesc: "\u201C\u5DF2\u5DE5\u4F5C/\u5DE5\u4F5C\u4E2D\u201D\u8FC7\u7A0B\u8BB0\u5F55\uFF08\u8BFB\u53D6/\u7F16\u8F91/\u67E5\u9605/\u7EC8\u7AEF\u6761\u76EE\u4E0E\u601D\u8003\u5185\u5BB9\uFF09\u7684\u884C\u9AD8\uFF08\u500D\u6570\uFF09\u3002",
136
140
  userLineHeightName: "\u63D0\u95EE\u884C\u9AD8",
137
141
  userLineHeightDesc: "\u63D0\u95EE\u5185\u5BB9\u7684\u884C\u9AD8\uFF08\u500D\u6570\uFF09\u3002",
138
142
  contentWidthName: "\u5185\u5BB9\u5BBD\u5EA6",
@@ -420,12 +424,16 @@
420
424
  proxyScopeSyncDesc: "WebDAV upload and download go through the proxy. Off by default: domestic WebDAV providers work fine direct, and a stopped proxy breaks sync.",
421
425
  tabSync: "Sync",
422
426
  tabDebug: "Debug",
423
- syncDesc: "Manually back up ZCode Pro settings and model settings to WebDAV, or restore them on a new computer. The two items are stored as separate files, so you can upload or overwrite either one alone; every upload also keeps a timestamped history version and download lets you pick any of them. Data moves only when you click, never automatically.",
427
+ syncDesc: 'Manually back up ZCode Pro settings and model settings to WebDAV, or restore them on a new computer. The two items are stored as separate files, so you can upload or overwrite either one alone; every upload also keeps a timestamped history version (how many to keep is set in the "History" row; older ones are removed automatically after upload) and download lets you pick any of them. Data moves only when you click, never automatically.',
424
428
  syncUrlPlaceholder: "https://dav.example.com/dav/",
425
- syncDir: "\u4E0A\u4F20\u76EE\u5F55",
429
+ syncDir: "Folder",
426
430
  syncDirPlaceholder: "zcode-pro",
427
431
  syncLogin: "Login",
428
432
  syncPassword: "Password",
433
+ syncHistoryLimit: "History",
434
+ syncHistoryLimitUnit: "versions",
435
+ syncHistoryLimitHint: "How many recent uploads keep their history versions on the server; older ones are removed automatically after a successful upload. The fixed-name latest file is never removed. 0 = keep no history versions.",
436
+ syncPruned: "removed {n} older history versions",
429
437
  syncSave: "Save",
430
438
  syncSaved: "Sync settings saved.",
431
439
  syncFormHint: "Sync files go inside the folder (default zcode-pro, multi-level allowed, created automatically): zcodepro.json and model.json are always the latest version; timestamped history versions are kept alongside.",
@@ -3654,17 +3662,34 @@
3654
3662
  )
3655
3663
  );
3656
3664
  const savedSync = config.sync && typeof config.sync === "object" ? config.sync : { url: "", dir: "zcode-pro", login: "", password: "", include: { zcodepro: true, model: true }, lastPushAt: null, lastPullAt: null };
3657
- const syncInputCls = "h-8 w-full rounded-lg border border-border bg-input px-2.5 text-ui-sm text-foreground outline-none transition-shadow placeholder:text-foreground-subtle focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/40";
3665
+ const syncInputBase = "h-8 rounded-lg border border-border bg-input px-2.5 text-ui-sm text-foreground outline-none transition-shadow placeholder:text-foreground-subtle focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/40";
3666
+ const syncInputCls = syncInputBase + " w-full";
3667
+ const syncLabel = (text) => h("span", { class: "w-20 shrink-0 text-ui-sm font-medium text-foreground" }, text);
3658
3668
  const syncFormRow = (labelText, node) => h(
3659
3669
  "div",
3660
3670
  { class: "flex items-center gap-3 px-2 py-1.5" },
3661
- h("span", { class: "w-20 shrink-0 text-ui-sm font-medium text-foreground" }, labelText),
3671
+ syncLabel(labelText),
3662
3672
  node
3663
3673
  );
3664
3674
  const syncUrlInput = h("input", { type: "text", value: savedSync.url || "", placeholder: L.syncUrlPlaceholder, spellcheck: "false", autocomplete: "off", class: syncInputCls + " min-w-0 flex-1" });
3665
3675
  const syncDirInput = h("input", { type: "text", value: savedSync.dir || "zcode-pro", placeholder: L.syncDirPlaceholder, spellcheck: "false", autocomplete: "off", class: syncInputCls + " min-w-0 flex-1" });
3666
3676
  const syncLoginInput = h("input", { type: "text", value: savedSync.login || "", spellcheck: "false", autocomplete: "off", class: syncInputCls + " min-w-0 flex-1" });
3667
3677
  const syncPasswordInput = h("input", { type: "password", value: savedSync.password || "", autocomplete: "new-password", class: syncInputCls + " min-w-0 flex-1" });
3678
+ const syncHistoryInput = h("input", {
3679
+ type: "number",
3680
+ min: "0",
3681
+ max: "100",
3682
+ step: "1",
3683
+ value: Number.isInteger(savedSync.historyLimit) ? savedSync.historyLimit : 10,
3684
+ title: L.syncHistoryLimitHint,
3685
+ class: syncInputBase + " w-20 shrink-0 text-center"
3686
+ });
3687
+ const syncHistoryValue = () => {
3688
+ const s = syncHistoryInput.value.trim();
3689
+ if (!s) return 10;
3690
+ const n = Number(s);
3691
+ return Number.isFinite(n) ? Math.min(100, Math.max(0, Math.round(n))) : 10;
3692
+ };
3668
3693
  let syncFormOriginal = null;
3669
3694
  const syncSaveBtn = btnSmall(L.syncSave, () => {
3670
3695
  void saveSyncForm();
@@ -3673,15 +3698,16 @@
3673
3698
  url: syncUrlInput.value.trim(),
3674
3699
  dir: syncDirInput.value.trim(),
3675
3700
  login: syncLoginInput.value.trim(),
3676
- password: syncPasswordInput.value
3701
+ password: syncPasswordInput.value,
3702
+ historyLimit: syncHistoryValue()
3677
3703
  });
3678
3704
  const syncCheckDirty = () => {
3679
3705
  const c = syncFormValues();
3680
- syncSaveBtn.disabled = c.url === syncFormOriginal.url && c.dir === syncFormOriginal.dir && c.login === syncFormOriginal.login && c.password === syncFormOriginal.password;
3706
+ syncSaveBtn.disabled = c.url === syncFormOriginal.url && c.dir === syncFormOriginal.dir && c.login === syncFormOriginal.login && c.password === syncFormOriginal.password && c.historyLimit === syncFormOriginal.historyLimit;
3681
3707
  };
3682
3708
  syncFormOriginal = syncFormValues();
3683
3709
  syncSaveBtn.disabled = true;
3684
- for (const el of [syncUrlInput, syncDirInput, syncLoginInput, syncPasswordInput]) {
3710
+ for (const el of [syncUrlInput, syncDirInput, syncLoginInput, syncPasswordInput, syncHistoryInput]) {
3685
3711
  el.addEventListener("input", syncCheckDirty);
3686
3712
  }
3687
3713
  const saveSyncForm = async () => {
@@ -3692,12 +3718,14 @@
3692
3718
  url: res.sync.url,
3693
3719
  dir: res.sync.dir,
3694
3720
  login: res.sync.login,
3695
- password: res.sync.password
3721
+ password: res.sync.password,
3722
+ historyLimit: Number.isInteger(res.sync.historyLimit) ? res.sync.historyLimit : 10
3696
3723
  };
3697
3724
  syncUrlInput.value = res.sync.url;
3698
3725
  syncDirInput.value = res.sync.dir;
3699
3726
  syncLoginInput.value = res.sync.login;
3700
3727
  syncPasswordInput.value = res.sync.password;
3728
+ syncHistoryInput.value = syncFormOriginal.historyLimit;
3701
3729
  showToast(L.syncSaved);
3702
3730
  } else {
3703
3731
  showToast(L.failed + ": " + errText(res), "error");
@@ -3993,7 +4021,7 @@
3993
4021
  return;
3994
4022
  }
3995
4023
  renderSyncStatus(res.sync || {});
3996
- showToast(L.syncUploaded, "success");
4024
+ showToast(res.pruned ? L.syncUploaded + (isZhLocale() ? "\uFF1B" : "; ") + L.syncPruned.replace("{n}", res.pruned) : L.syncUploaded, "success");
3997
4025
  };
3998
4026
  paneSync.append(
3999
4027
  h("p", { class: "text-ui-sm/relaxed text-foreground-subtle" }, L.syncDesc),
@@ -4001,12 +4029,27 @@
4001
4029
  "div",
4002
4030
  { class: "mt-3 rounded-xl border border-border p-1" },
4003
4031
  syncFormRow("URL", syncUrlInput),
4004
- syncFormRow(L.syncDir, syncDirInput),
4005
- syncFormRow(L.syncLogin, syncLoginInput),
4006
- syncFormRow(L.syncPassword, syncPasswordInput),
4032
+ // 登录与密码一行、上传目录与历史版本一行:短字段成对排,少占纵向空间
4033
+ h(
4034
+ "div",
4035
+ { class: "flex items-center gap-3 px-2 py-1.5" },
4036
+ syncLabel(L.syncLogin),
4037
+ syncLoginInput,
4038
+ syncLabel(L.syncPassword),
4039
+ syncPasswordInput
4040
+ ),
4007
4041
  h(
4008
4042
  "div",
4009
- { class: "flex items-start justify-between gap-2 px-2 py-1.5" },
4043
+ { class: "flex items-center gap-3 px-2 py-1.5" },
4044
+ syncLabel(L.syncDir),
4045
+ syncDirInput,
4046
+ syncLabel(L.syncHistoryLimit),
4047
+ syncHistoryInput,
4048
+ h("span", { class: "shrink-0 text-ui-xs text-foreground-subtle" }, L.syncHistoryLimitUnit)
4049
+ ),
4050
+ h(
4051
+ "div",
4052
+ { class: "flex items-center justify-between gap-2 px-2 py-1.5" },
4010
4053
  h(
4011
4054
  "div",
4012
4055
  { class: "min-w-0" },
@@ -4288,7 +4331,7 @@
4288
4331
  "div",
4289
4332
  { class: "mt-2 grid grid-cols-1 gap-2 sm:grid-cols-2" },
4290
4333
  // 供应商与模型走 provider-fields 共用逻辑:跟随会话时模型下拉为
4291
- // 全部供应商模型汇总,具体供应商为其模型列表;已删 id 标注「已删除」
4334
+ // 全部供应商模型汇总,具体供应商为其模型列表;已删 id 标注“已删除”
4292
4335
  field(L.visionUseProvider, comboField(
4293
4336
  p.useProvider || "",
4294
4337
  providerOptionsOf(titleGenProviders, L),
@@ -4299,7 +4342,7 @@
4299
4342
  },
4300
4343
  { placeholder: L.titleGenProviderSession, staleLabel: L.titleGenProviderDeleted }
4301
4344
  )),
4302
- // 跟随会话时模型下拉头部有「跟随当前会话当前模型」(值为空,
4345
+ // 跟随会话时模型下拉头部有“跟随当前会话当前模型”(值为空,
4303
4346
  // 插件 ≥1.6.1 在调用时解析为会话当前模型);具体供应商为其模型列表
4304
4347
  field(L.visionModel, comboField(
4305
4348
  p.model || "",
@@ -5732,15 +5775,58 @@
5732
5775
  if (m) scan2();
5733
5776
  });
5734
5777
  }
5778
+ var RENAME_CTX_VALID_MS = 15e3;
5779
+ var renameCtx = null;
5780
+ var renamePrefilled = /* @__PURE__ */ new WeakSet();
5781
+ function setNativeValue(el, text) {
5782
+ if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) {
5783
+ Object.getOwnPropertyDescriptor(el.constructor.prototype, "value").set.call(el, text);
5784
+ } else {
5785
+ el.textContent = text;
5786
+ }
5787
+ el.dispatchEvent(new Event("input", { bubbles: true }));
5788
+ }
5789
+ function prefillRenameDialog() {
5790
+ if (!renameCtx || !renameCtx.armed || Date.now() - renameCtx.at > RENAME_CTX_VALID_MS) {
5791
+ renameCtx = null;
5792
+ return;
5793
+ }
5794
+ if (!enabled2 || !pinned) return;
5795
+ const want = pinned.get(sessIdOf(renameCtx.key));
5796
+ if (want === void 0) return;
5797
+ for (const el of document.querySelectorAll('[role="dialog"] input, [role="dialog"] textarea, [role="dialog"] [contenteditable="true"]')) {
5798
+ if (renamePrefilled.has(el) || !el.getClientRects().length) continue;
5799
+ if (el.closest(TASK_SEL) || el.closest(".zcodepro-overlay")) continue;
5800
+ const cur = String(el.value ?? el.textContent ?? "").trim();
5801
+ if (!cur) continue;
5802
+ renamePrefilled.add(el);
5803
+ if (cur !== String(want).trim()) setNativeValue(el, String(want));
5804
+ renameCtx = null;
5805
+ return;
5806
+ }
5807
+ }
5735
5808
  function startTitleLock() {
5736
5809
  if (installed2 || typeof document === "undefined") return;
5737
5810
  installed2 = true;
5738
5811
  void bootstrap();
5739
5812
  new MutationObserver((muts) => {
5813
+ if (renameCtx) prefillRenameDialog();
5740
5814
  if (!taskRowMutations2(muts)) return;
5741
5815
  refreshMapSoon();
5742
5816
  scheduleScan2();
5743
5817
  }).observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ["class"], characterData: true });
5818
+ document.addEventListener("contextmenu", (e) => {
5819
+ const row = e.target instanceof Element && e.target.closest(TASK_SEL);
5820
+ renameCtx = row ? { key: row.getAttribute("data-task-item-key") || "", at: 0, armed: false } : null;
5821
+ }, true);
5822
+ document.addEventListener("click", (e) => {
5823
+ if (!renameCtx || renameCtx.armed) return;
5824
+ const item = e.target instanceof Element && e.target.closest('[role="menuitem"], [data-radix-collection-item]');
5825
+ if (item && /重命名|rename/i.test(item.textContent || "")) {
5826
+ renameCtx.armed = true;
5827
+ renameCtx.at = Date.now();
5828
+ }
5829
+ }, true);
5744
5830
  scheduleBootRetry();
5745
5831
  setInterval(() => {
5746
5832
  if (!document.hidden) scan2();
@@ -5850,7 +5936,13 @@
5850
5936
  convObserver.disconnect();
5851
5937
  }
5852
5938
  if (!texts.length) return;
5853
- const res = await rpc("/title-gen/run", { method: "POST", body: { sessId, text: texts.join("\n") } }).catch(() => null);
5939
+ const callTitleGen = () => rpc("/title-gen/run", { method: "POST", body: { sessId, text: texts.join("\n") } }).catch(() => null);
5940
+ let res = await callTitleGen();
5941
+ for (const delay of [5e3, 2e4]) {
5942
+ if (res && res.ok) break;
5943
+ await sleep(delay);
5944
+ res = await callTitleGen();
5945
+ }
5854
5946
  if (res && res.ok && res.written && res.title) {
5855
5947
  setRowTitle(key, res.title);
5856
5948
  void refreshPinnedTitles();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zcode-pro",
3
- "version": "0.19.0",
3
+ "version": "0.20.0",
4
4
  "description": "ZCode Pro — 为 ZCode 桌面版增加界面增强(不修改客户端文件,通过 ZCode Pro 快捷方式启动即可),核心功能:项目自定义别名",
5
5
  "type": "module",
6
6
  "bin": {