zcode-pro 0.17.2 → 0.18.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/dist/inject.js CHANGED
@@ -2,13 +2,16 @@
2
2
  // src/inject/core.js
3
3
  var BOOT = typeof window !== "undefined" ? window.__ZCODEPRO__ || {} : {};
4
4
  var HELPER_URL = BOOT.helperUrl || "http://127.0.0.1:47889";
5
- var TOKEN = BOOT.token || "";
5
+ function liveBoot() {
6
+ return typeof window !== "undefined" && window.__ZCODEPRO__ || BOOT;
7
+ }
6
8
  function rpc(path, options = {}) {
7
- return fetch(HELPER_URL + path, {
9
+ const boot = liveBoot();
10
+ return fetch((boot.helperUrl || HELPER_URL) + path, {
8
11
  method: options.method || "GET",
9
12
  headers: {
10
13
  "Content-Type": "application/json",
11
- "X-ZCodePro-Token": TOKEN
14
+ "X-ZCodePro-Token": boot.token || ""
12
15
  },
13
16
  body: options.body ? JSON.stringify(options.body) : void 0
14
17
  }).then((r) => r.json()).catch((err) => ({ ok: false, error: String(err && err.message || err) }));
@@ -38,16 +41,24 @@
38
41
  agentsSave: "\u4FDD\u5B58",
39
42
  agentsSaved: "\u5168\u5C40\u63D0\u793A\u8BCD\u5DF2\u4FDD\u5B58",
40
43
  agentsLoadFailed: "\u8BFB\u53D6\u5168\u5C40\u63D0\u793A\u8BCD\u5931\u8D25",
41
- proxyDesc: "\u4E3A ZCode Pro \u672C\u5730\u52A9\u624B\u53D1\u8D77\u7684\u7F51\u7EDC\u8BBF\u95EE\u8BBE\u7F6E HTTP \u4EE3\u7406\uFF1A\u63D2\u4EF6\u5E02\u573A\u7684\u66F4\u65B0\u4E0E\u5B89\u88C5\u3001Headroom \u672C\u4F53\u7684\u68C0\u67E5\u66F4\u65B0\u4E0E\u5347\u7EA7\u90FD\u4F1A\u8D70\u8BE5\u4EE3\u7406\u3002\u4E0D\u5F71\u54CD ZCode \u5E94\u7528\u672C\u8EAB\u4E0E\u6A21\u578B\u8BF7\u6C42\uFF0C\u4FDD\u5B58\u540E\u7ACB\u5373\u751F\u6548\u3002",
44
+ proxyDesc: "\u4E3A ZCode Pro \u672C\u5730\u52A9\u624B\u53D1\u8D77\u7684\u7F51\u7EDC\u8BBF\u95EE\u8BBE\u7F6E HTTP \u4EE3\u7406\uFF1A\u63D2\u4EF6\u5E02\u573A\u7684\u66F4\u65B0\u4E0E\u5B89\u88C5\u3001\u63D2\u4EF6\u4F9D\u8D56\u8F6F\u4EF6\u7684\u5B89\u88C5\u4E0E\u5347\u7EA7\u3001WebDAV \u540C\u6B65\u90FD\u53EF\u8D70\u8BE5\u4EE3\u7406\uFF0C\u7528\u4E0B\u65B9\u5F00\u5173\u9009\u62E9\u751F\u6548\u8303\u56F4\u3002\u4E0D\u5F71\u54CD ZCode \u5E94\u7528\u672C\u8EAB\u4E0E\u6A21\u578B\u8BF7\u6C42\uFF0C\u4FDD\u5B58\u540E\u7ACB\u5373\u751F\u6548\u3002",
42
45
  proxyPlaceholder: "http://127.0.0.1:7890",
43
46
  proxySave: "\u4FDD\u5B58",
44
47
  proxySaved: "\u4EE3\u7406\u8BBE\u7F6E\u5DF2\u4FDD\u5B58",
45
48
  proxyHint: "\u7559\u7A7A\u4FDD\u5B58\u5373\u6E05\u9664\u4EE3\u7406\u3002",
46
49
  proxyTest: "\u68C0\u6D4B",
47
50
  proxyTesting: "\u68C0\u6D4B\u4E2D\u2026",
51
+ proxyTestEmpty: "\u8BF7\u5148\u8F93\u5165\u4EE3\u7406\u5730\u5740",
48
52
  proxyTestFailed: "\u68C0\u6D4B\u5931\u8D25",
53
+ proxyScopePlugins: "\u63D2\u4EF6\u5E02\u573A\u66F4\u65B0",
54
+ proxyScopePluginsDesc: "\u63D2\u4EF6\u5E02\u573A\u7684\u68C0\u67E5\u66F4\u65B0\u3001\u5B89\u88C5\u4E0E\u5378\u8F7D\uFF08zcode CLI \u7684 git \u64CD\u4F5C\uFF09\u8D70\u4EE3\u7406\u3002",
55
+ proxyScopeDeps: "\u63D2\u4EF6\u4F9D\u8D56\u8F6F\u4EF6",
56
+ proxyScopeDepsDesc: "headroom \u4E0E rtk \u7684\u5B89\u88C5\u3001\u68C0\u67E5\u66F4\u65B0\u4E0E\u5347\u7EA7\uFF08pip \u4E0E GitHub \u4E0B\u8F7D\uFF09\u8D70\u4EE3\u7406\u3002",
57
+ proxyScopeSync: "\u540C\u6B65",
58
+ 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",
49
59
  tabSync: "\u540C\u6B65",
50
- 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\u4EC5\u5728\u70B9\u51FB\u6309\u94AE\u65F6\u4F20\u8F93\uFF0C\u4E0D\u505A\u81EA\u52A8\u540C\u6B65\u3002",
60
+ tabDebug: "\u8C03\u8BD5",
61
+ 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",
51
62
  syncUrlPlaceholder: "https://dav.jianguoyun.com/dav/",
52
63
  syncDir: "\u4E0A\u4F20\u76EE\u5F55",
53
64
  syncDirPlaceholder: "zcode-pro",
@@ -55,7 +66,7 @@
55
66
  syncPassword: "\u5BC6\u7801",
56
67
  syncSave: "\u4FDD\u5B58",
57
68
  syncSaved: "\u540C\u6B65\u914D\u7F6E\u5DF2\u4FDD\u5B58",
58
- 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\u4E0B\u7684 zcodepro.json \u4E0E model.json\u3002",
69
+ 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",
59
70
  syncRecLink: "\u575A\u679C\u4E91 WebDAV",
60
71
  syncRecSuffix: "\uFF1A\u767B\u5F55\u4E0E\u5BC6\u7801\u586B\u201C\u5E94\u7528\u5BC6\u7801\u201D\uFF0C\u4E0D\u662F\u8D26\u53F7\u5BC6\u7801\u3002",
61
72
  syncIncludeZcp: "ZCode Pro \u8BBE\u7F6E",
@@ -65,16 +76,44 @@
65
76
  syncUpload: "\u4E0A\u4F20\u5230\u4E91\u7AEF",
66
77
  syncDownload: "\u4ECE\u4E91\u7AEF\u4E0B\u8F7D",
67
78
  syncUploading: "\u4E0A\u4F20\u4E2D\u2026",
68
- syncDownloading: "\u4E0B\u8F7D\u4E2D\u2026",
69
79
  syncUploaded: "\u5DF2\u4E0A\u4F20\u5230\u4E91\u7AEF",
70
80
  syncDownloaded: "\u5DF2\u4ECE\u4E91\u7AEF\u6062\u590D",
71
81
  syncLastPush: "\u4E0A\u6B21\u4E0A\u4F20",
72
82
  syncLastPull: "\u4E0A\u6B21\u4E0B\u8F7D",
73
83
  syncNever: "\u65E0",
74
84
  syncConfirmTitle: "\u4ECE\u4E91\u7AEF\u4E0B\u8F7D",
75
- syncConfirmDesc: "\u5C06\u7528\u4E91\u7AEF\u6570\u636E\u8986\u76D6\u672C\u673A\u52FE\u9009\u7684\u540C\u6B65\u5185\u5BB9\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\u4E0B\u8F7D\uFF1F",
76
- syncConfirmGo: "\u786E\u8BA4\u4E0B\u8F7D",
85
+ 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",
86
+ syncConfirmGo: "\u786E\u8BA4\u5BFC\u5165",
87
+ 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",
88
+ 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",
77
89
  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",
90
+ syncTest: "\u6D4B\u8BD5",
91
+ syncTesting: "\u6D4B\u8BD5\u4E2D\u2026",
92
+ syncTestEmpty: "\u8BF7\u5148\u586B\u5199 URL",
93
+ syncTestFailed: "\u6D4B\u8BD5\u5931\u8D25",
94
+ syncTestStepConnect: "\u8FDE\u63A5",
95
+ syncTestStepDir: "\u76EE\u5F55",
96
+ syncTestStepList: "\u6587\u4EF6",
97
+ syncTestFilesFound: "\u4E91\u7AEF\u5DF2\u6709 {n} \u4E2A\u5907\u4EFD\u6587\u4EF6",
98
+ syncPickTitle: "\u8BF7\u9009\u62E9\u4E00\u4E2A\u6587\u4EF6",
99
+ syncPickLoading: "\u6B63\u5728\u8BFB\u53D6\u4E91\u7AEF\u6587\u4EF6\u5217\u8868\u2026",
100
+ syncPickEmpty: "\u4E91\u7AEF\u8FD8\u6CA1\u6709\u5907\u4EFD\u6587\u4EF6\uFF0C\u8BF7\u5148\u5728\u5176\u4ED6\u7535\u8111\u4E0A\u4F20",
101
+ syncVerImport: "\u5BFC\u5165",
102
+ syncVerImporting: "\u5BFC\u5165\u4E2D\u2026",
103
+ syncVerDelete: "\u5220\u9664",
104
+ syncVerSave: "\u4FDD\u5B58\u5230\u78C1\u76D8",
105
+ syncVerSaving: "\u4FDD\u5B58\u4E2D\u2026",
106
+ syncVerLatest: "\u6700\u65B0",
107
+ syncVerDeleteTitle: "\u5220\u9664\u4E91\u7AEF\u6587\u4EF6",
108
+ syncVerDeleteDesc: "\u5C06\u5220\u9664\u4E91\u7AEF\u7684\u8FD9\u4EFD\u5907\u4EFD\uFF0C\u4E0D\u53EF\u6062\u590D\uFF1A",
109
+ syncVerDeleted: "\u5DF2\u5220\u9664\u4E91\u7AEF\u5907\u4EFD",
110
+ syncVerSaved: "\u5DF2\u4FDD\u5B58\u5230",
111
+ devMode: "\u5F00\u53D1\u6A21\u5F0F",
112
+ 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",
113
+ 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",
114
+ devReload: "\u91CD\u8F7D ZCode Pro",
115
+ devReloading: "\u91CD\u8F7D\u4E2D\u2026",
116
+ 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",
78
117
  rowGapName: "\u6BB5\u843D\u95F4\u8DDD",
79
118
  rowGapDesc: "\u4F1A\u8BDD\u4E2D\u6BB5\u843D\u7B49\u6587\u672C\u5757\u4E4B\u95F4\u7684\u5782\u76F4\u95F4\u8DDD\uFF08\u56DE\u5408\u4E4B\u95F4\u3001\u7B54\u6848\u5185\u90E8\uFF09\u3002",
80
119
  listSpacingName: "\u5217\u8868\u4E0A\u4E0B\u8FB9\u8DDD",
@@ -91,6 +130,8 @@
91
130
  tableCellPaddingDesc: "\u8868\u683C\u5355\u5143\u683C\u5185\u7684\u7559\u767D\uFF1B\u5DE6\u8F93\u5165\u6846\u4E3A\u4E0A\u4E0B\u3001\u53F3\u4E3A\u5DE6\u53F3\u3002",
92
131
  lineHeightName: "\u56DE\u7B54\u884C\u9AD8",
93
132
  lineHeightDesc: "\u56DE\u7B54\u6B63\u6587\u7684\u884C\u9AD8\uFF08\u500D\u6570\uFF09\u3002",
133
+ thinkingLineHeightName: "\u601D\u8003\u884C\u9AD8",
134
+ 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",
94
135
  userLineHeightName: "\u63D0\u95EE\u884C\u9AD8",
95
136
  userLineHeightDesc: "\u63D0\u95EE\u5185\u5BB9\u7684\u884C\u9AD8\uFF08\u500D\u6570\uFF09\u3002",
96
137
  contentWidthName: "\u5185\u5BB9\u5BBD\u5EA6",
@@ -154,7 +195,7 @@
154
195
  featurePinnedExpand: "\u7F6E\u9876\u4F1A\u8BDD\u4FDD\u6301\u9879\u76EE\u6298\u53E0\uFF08\u5B9E\u9A8C\u6027\uFF09",
155
196
  featurePinnedExpandDesc: "\u70B9\u51FB\u6298\u53E0\u9879\u76EE\u7684\u7F6E\u9876\u4F1A\u8BDD\u540E\u5C06\u5176\u4FDD\u6301\u6298\u53E0\u3002\u53D7\u9650\u4E8E\u5E94\u7528\u673A\u5236\uFF0C\u9879\u76EE\u4F1A\u5148\u77ED\u6682\u5C55\u5F00\u518D\u7F29\u8D77\u3002",
156
197
  featureAutoUpdatePlugins: "\u81EA\u52A8\u66F4\u65B0 zcode-plugins \u63D2\u4EF6",
157
- featureAutoUpdatePluginsDesc: "\u542F\u52A8\u65F6\u81EA\u52A8\u68C0\u67E5\u5E76\u66F4\u65B0\u5DF2\u88C5\u7684 zcode-plugins \u63D2\u4EF6\uFF08\u542B\u5B89\u88C5\u5E02\u573A\u91CC\u65B0\u589E\u7684\u63D2\u4EF6\uFF09\uFF0C\u66F4\u65B0\u5728\u65B0\u4F1A\u8BDD\u751F\u6548\u3002",
198
+ featureAutoUpdatePluginsDesc: "\u542F\u52A8\u65F6\u81EA\u52A8\u68C0\u67E5\u5E76\u66F4\u65B0\u5DF2\u5B89\u88C5\u7684 zcode-plugins \u63D2\u4EF6\uFF08\u53EA\u5347\u7EA7\u4E0D\u5B89\u88C5\uFF0C\u5378\u8F7D\u4E0D\u4F1A\u88AB\u64A4\u9500\uFF09\uFF0C\u66F4\u65B0\u5728\u65B0\u4F1A\u8BDD\u751F\u6548\u3002",
158
199
  pluginsUpdateTitle: "\u63D2\u4EF6\u66F4\u65B0 \xB7 zcode-plugins",
159
200
  pluginsCheckNow: "\u68C0\u67E5\u5E76\u66F4\u65B0",
160
201
  pluginsUpdating: "\u6B63\u5728\u66F4\u65B0\u63D2\u4EF6\u2026",
@@ -166,7 +207,7 @@
166
207
  pluginsAutoUpdated: "zcode-plugins \u63D2\u4EF6\u5DF2\u66F4\u65B0",
167
208
  tabVision: "Vision",
168
209
  visionDesc: "\u4E3B\u6A21\u578B\u4E0D\u652F\u6301\u56FE\u7247\u8F93\u5165\u65F6\uFF08\u5982 glm-5.3\uFF09\uFF0C\u81EA\u52A8\u628A\u6D88\u606F\u91CC\u7684\u56FE\u7247\u4EA4\u7ED9\u4E0B\u9762\u7684\u89C6\u89C9\u6A21\u578B\u8BC6\u522B\uFF0C\u628A\u63CF\u8FF0\u5E26\u7ED9\u4E3B\u6A21\u578B\uFF08zcode-vision \u63D2\u4EF6\uFF1B/vision-* \u547D\u4EE4\u7F16\u8F91\u540C\u4E00\u914D\u7F6E\uFF09\u3002\u6539\u52A8\u81EA\u52A8\u4FDD\u5B58\u3002",
169
- visionEnabled: "\u542F\u7528\u56FE\u7247\u8BC6\u522B",
210
+ visionEnabled: "\u542F\u7528",
170
211
  visionForceIntercept: "\u4E3B\u6A21\u578B\u80FD\u770B\u56FE\u4E5F\u62E6\u622A",
171
212
  visionMode: "\u94FE\u6A21\u5F0F",
172
213
  visionModeFallback: "\u4F9D\u6B21\u5C1D\u8BD5",
@@ -191,21 +232,21 @@
191
232
  visionResetChainConfirm: "\u5C06\u94FE\u6A21\u5F0F\u4E0E\u4EE3\u7406\u5217\u8868\u6062\u590D\u4E3A\u9ED8\u8BA4\u4E24\u7EA7\uFF08glm-session \u2192 glm-flash\uFF09\uFF0C\u5F53\u524D\u5BF9\u4EE3\u7406\u7684\u4FEE\u6539\u4F1A\u4E22\u5931\u3002\u7EE7\u7EED\uFF1F",
192
233
  visionBaseUrl: "\u63A5\u53E3\u5730\u5740 baseUrl",
193
234
  visionModel: "\u6A21\u578B",
194
- visionFormat: "\u683C\u5F0F",
195
235
  visionApiKey: "API Key",
196
236
  visionApiKeyHint: "\u7559\u7A7A\u81EA\u52A8\u53D6 GLM \u8BA2\u9605 key",
197
237
  visionPrompt: "\u8BC6\u522B\u63D0\u793A\u8BCD\uFF08pipeline \u540E\u7EED\u6B65\u53EF\u7528 {prev} \u5F15\u7528\u4E0A\u4E00\u6B65\uFF09",
198
238
  visionTest: "\u6D4B\u8BD5",
199
239
  visionTesting: "\u6D4B\u8BD5\u4E2D\u2026",
200
240
  visionTestFailed: "\u89C6\u89C9\u4EE3\u7406\u6D4B\u8BD5\u5931\u8D25",
201
- visionTestOutput: "\u6D4B\u8BD5\u8F93\u51FA",
202
241
  visionCompressKB: "\u538B\u7F29\u9608\u503C KB",
203
242
  visionCompressKBHint: "\u8D85\u8FC7\u8BE5\u5927\u5C0F\u7684\u56FE\u5148\u538B\u7F29\u518D\u8BC6\u522B\uFF08\u6700\u957F\u8FB9 2000\u3001JPEG\uFF09\uFF1B0 = \u4E0D\u538B\u7F29",
204
243
  visionLoadFailed: "\u8BFB\u53D6\u89C6\u89C9\u4EE3\u7406\u914D\u7F6E\u5931\u8D25",
205
244
  tabRtk: "rtk",
206
245
  rtkDesc: "rtk \u63D2\u4EF6\uFF1A\u628A\u5E38\u89C1\u5F00\u53D1\u547D\u4EE4\u7684\u8F93\u51FA\u538B\u7F29 60-90% \u518D\u8FDB\u5165\u4E0A\u4E0B\u6587\uFF08/rtk-* \u547D\u4EE4\u7F16\u8F91\u540C\u4E00\u914D\u7F6E\uFF09\u3002\u6539\u52A8\u81EA\u52A8\u4FDD\u5B58\u5E76\u7ACB\u5373\u751F\u6548\u3002",
207
- rtkEnabled: "\u542F\u7528 rtk \u538B\u7F29",
208
- rtkNotInstalled: "\u672A\u5B89\u88C5 rtk\uFF0C\u4EE5\u4E0B\u914D\u7F6E\u5C06\u5728\u5B89\u88C5\u540E\u751F\u6548\uFF08\u4F1A\u8BDD\u91CC\u53EF\u7528 /rtk-setup \u5B89\u88C5\uFF09",
246
+ rtkEnabled: "\u542F\u7528",
247
+ rtkNotInstalled: "\u672A\u5B89\u88C5 rtk \u672C\u4F53\uFF1B\u5B89\u88C5\u540E\u4EE5\u4E0B\u8BBE\u7F6E\u751F\u6548\uFF08\u4F1A\u8BDD\u91CC\u6267\u884C /rtk-setup \u4E5F\u53EF\u4EE5\uFF09",
248
+ rtkInstallBin: "\u5B89\u88C5 rtk \u672C\u4F53",
249
+ rtkBinInstalled: "rtk \u672C\u4F53\u5DF2\u5B89\u88C5\uFF1BPATH \u9700\u6CE8\u9500\u91CD\u767B\u540E\u5BF9\u4F1A\u8BDD\u751F\u6548\uFF0C\u63D2\u4EF6\u94A9\u5B50\u5373\u65F6\u751F\u6548",
209
250
  rtkWhitelistTitle: "\u767D\u540D\u5355\uFF08\u4E0D\u538B\u7F29\u76F4\u63A5\u653E\u884C\uFF09",
210
251
  rtkBuiltinLabel: "\u5185\u7F6E\u653E\u884C\uFF08\u53EA\u8BFB\uFF0C\u968F\u63D2\u4EF6\u66F4\u65B0\uFF09\uFF1A",
211
252
  rtkWhitelistDesc: "\u6BCF\u6761 name\uFF08\u5982 docker\uFF09\u6216 git:name\uFF08\u5982 git:clone\uFF09\u3002\u8F93\u51FA\u6781\u5C0F\u6216\u7EAF\u526F\u4F5C\u7528\u7684\u547D\u4EE4\u65E0\u9700\u538B\u7F29\uFF0C\u52A0\u5165\u540E\u4E0D\u518D\u538B\u7F29\uFF1B\u5185\u7F6E\u7684 git add/commit/push\u3001mkdir/cp \u7B49\u4E0D\u53EF\u79FB\u9664\u3002\u5220\u9664\u672C\u6587\u4EF6\u5373\u6062\u590D\u9ED8\u8BA4\u3002",
@@ -218,12 +259,25 @@
218
259
  tabHeadroom: "Headroom",
219
260
  headroomDesc: "Headroom \u63D2\u4EF6\uFF1A\u672C\u5730\u538B\u7F29\u4EE3\u7406\u9A7B\u7559 127.0.0.1\uFF08\u9ED8\u8BA4 8787 \u7AEF\u53E3\uFF09\uFF0C\u538B\u7F29\u53D1\u5F80\u6A21\u578B\u7684\u4E0A\u4E0B\u6587\u4EE5\u7701 token\uFF08/hr-* \u547D\u4EE4\u7F16\u8F91\u540C\u4E00\u914D\u7F6E\uFF09\u3002\u538B\u7F29\u8BBE\u5907\u4E0E\u7701\u7535\u5207\u6362\u4FDD\u5B58\u540E\u7ACB\u5373\u751F\u6548\uFF1B\u5207\u6362\u8BBE\u5907\u4F1A\u91CD\u542F\u4EE3\u7406\uFF0C\u6B63\u5728\u5904\u7406\u7684\u8BF7\u6C42\u53EF\u80FD\u4E2D\u65AD\u4E00\u6B21\u3002",
220
261
  headroomBinMissing: "\u672A\u627E\u5230 headroom \u7A0B\u5E8F\uFF0C\u4EE3\u7406\u65E0\u6CD5\u542F\u52A8\uFF1B\u5B89\u88C5 headroom \u540E\u4EE5\u4E0B\u8BBE\u7F6E\u81EA\u52A8\u751F\u6548",
262
+ hrInstallBin: "\u5B89\u88C5 headroom \u672C\u4F53",
263
+ hrBinInstalled: "headroom \u672C\u4F53\u5DF2\u5B89\u88C5\uFF1B\u91CD\u65B0\u6253\u5F00\u4F1A\u8BDD\u540E\u4EE3\u7406\u53EF\u7528\uFF08SessionStart \u94A9\u5B50\u81EA\u52A8\u62C9\u8D77\uFF09",
221
264
  headroomInstallTitle: "\u672A\u5B89\u88C5 headroom \u63D2\u4EF6",
222
265
  headroomInstallDesc: "\u4ECE zcode-plugins \u5E02\u573A\u5B89\u88C5\u540E\u5373\u53EF\u5728\u6B64\u7BA1\u7406\u538B\u7F29\u4EE3\u7406\uFF1B\u5EFA\u8BAE\u5B89\u88C5\u540E\u91CD\u542F\u4F1A\u8BDD\u751F\u6548\u3002",
223
266
  headroomInstall: "\u5B89\u88C5",
224
267
  headroomInstalling: "\u5B89\u88C5\u4E2D\u2026",
225
268
  headroomInstalled: "\u5DF2\u5B89\u88C5 headroom \u63D2\u4EF6",
226
269
  headroomInstallRetry: "\u5B89\u88C5\u672A\u751F\u6548\uFF0C\u8BF7\u91CD\u8BD5\u6216\u624B\u52A8\u5728\u63D2\u4EF6\u5E02\u573A\u5B89\u88C5",
270
+ pluginInstallCardTitle: "\u672A\u5B89\u88C5 {name} \u63D2\u4EF6",
271
+ pluginInstallCardDesc: "\u4ECE zcode-plugins \u5E02\u573A\u5B89\u88C5\u540E\u5373\u53EF\u5728\u6B64\u7BA1\u7406\uFF1B\u5EFA\u8BAE\u5B89\u88C5\u540E\u91CD\u542F\u4F1A\u8BDD\u751F\u6548\u3002",
272
+ pluginInstallDone: "\u5DF2\u5B89\u88C5 {name} \u63D2\u4EF6",
273
+ pluginEnable: "\u542F\u7528",
274
+ hrStarting: "\u6B63\u5728\u542F\u52A8\u538B\u7F29\u4EE3\u7406\u2026",
275
+ hrStarted: "\u538B\u7F29\u4EE3\u7406\u5DF2\u542F\u52A8",
276
+ hrStartTimeout: "\u4EE3\u7406\u542F\u52A8\u8D85\u65F6\uFF0890 \u79D2\uFF09\uFF0C\u8BF7\u68C0\u67E5 ~/.zcode-headroom-proxy.log",
277
+ hrStopping: "\u6B63\u5728\u505C\u6B62\u538B\u7F29\u4EE3\u7406\u2026",
278
+ hrRestarting: "\u6B63\u5728\u91CD\u542F\u538B\u7F29\u4EE3\u7406\u2026",
279
+ hrStopped: "\u538B\u7F29\u4EE3\u7406\u5DF2\u505C\u6B62",
280
+ hrUsageNone: "\u5F53\u524D\u6CA1\u6709\u8D70 headroom \u7684\u4F9B\u5E94\u5546\uFF0C\u672A\u5207\u6362",
227
281
  headroomLoadFailed: "\u8BFB\u53D6 Headroom \u914D\u7F6E\u5931\u8D25",
228
282
  headroomStatusTitle: "\u8FD0\u884C\u72B6\u6001",
229
283
  headroomStatusLoadFailed: "\u8BFB\u53D6\u8FD0\u884C\u72B6\u6001\u5931\u8D25",
@@ -249,7 +303,7 @@
249
303
  headroomRestart: "\u91CD\u542F",
250
304
  headroomStopAction: "\u505C\u6B62",
251
305
  headroomStopTitle: "\u505C\u6B62 Headroom \u4EE3\u7406",
252
- headroomStopDesc: "\u505C\u6B62\u540E\uFF0CZCode \u4E2D\u6307\u5411\u8BE5\u4EE3\u7406\uFF08127.0.0.1\uFF09\u7684\u4F9B\u5E94\u5546\u5C06\u65E0\u6CD5\u8FDE\u63A5\uFF0C\u76F4\u81F3\u91CD\u65B0\u542F\u52A8\u4EE3\u7406\u3002\u786E\u8BA4\u505C\u6B62\uFF1F",
306
+ headroomStopDesc: "\u505C\u6B62\u540E\uFF0CZCode \u53CA\u5176\u4ED6\u7A0B\u5E8F\u4E2D\u6307\u5411\u8BE5\u4EE3\u7406\uFF08127.0.0.1\uFF09\u7684\u8BF7\u6C42\u90FD\u4F1A\u5931\u8D25\uFF0C\u76F4\u81F3\u91CD\u65B0\u542F\u52A8\u4EE3\u7406\u3002headroom \u662F\u5171\u4EAB\u7684\uFF0C\u5176\u4ED6\u5DE5\u5177\u53EF\u80FD\u6B63\u5728\u4F7F\u7528\u3002\u786E\u8BA4\u505C\u6B62\uFF1F",
253
307
  headroomStoppedWarn: "\u4EE3\u7406\u5DF2\u505C\u6B62\uFF1B\u6307\u5411 127.0.0.1 \u7684\u4F9B\u5E94\u5546\u5C06\u65E0\u6CD5\u8FDE\u63A5",
254
308
  headroomBackend: "\u538B\u7F29\u8BBE\u5907",
255
309
  headroomBackendAuto: "\u81EA\u52A8 \xB7 \u663E\u5361\u4F18\u5148",
@@ -267,8 +321,6 @@
267
321
  headroomInterval: "\u76D1\u89C6\u95F4\u9694",
268
322
  headroomIntervalDesc: "\u7701\u7535\u76D1\u89C6\u5668\u8F6E\u8BE2\u7535\u6E90\u72B6\u6001\u7684\u95F4\u9694\uFF0810\u20133600 \u79D2\uFF0C\u9ED8\u8BA4 60\uFF09\uFF1B\u4FDD\u5B58\u540E\u4ECE\u4E0B\u4E00\u8F6E\u5DE1\u68C0\u8D77\u751F\u6548\uFF0C\u65E0\u9700\u91CD\u542F\u3002",
269
323
  headroomSecUnit: "\u79D2",
270
- headroomVersionPlugin: "\u63D2\u4EF6",
271
- headroomVersionBin: "\u7A0B\u5E8F",
272
324
  upgradeCheck: "\u68C0\u67E5\u66F4\u65B0",
273
325
  upgradeChecking: "\u68C0\u67E5\u4E2D\u2026",
274
326
  upgradeUpToDate: "\u5DF2\u662F\u6700\u65B0\uFF08{v}\uFF09",
@@ -330,16 +382,24 @@
330
382
  agentsSave: "Save",
331
383
  agentsSaved: "Global prompt saved.",
332
384
  agentsLoadFailed: "Failed to load the global prompt",
333
- proxyDesc: "Set an HTTP proxy for network access made by the local helper: plugin marketplace updates and installs, headroom update checks and upgrades. Does not affect the ZCode app itself or model requests. Applies immediately.",
385
+ 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 \u2014 pick the scopes with the toggles below. Does not affect the ZCode app itself or model requests. Applies immediately.",
334
386
  proxyPlaceholder: "http://127.0.0.1:7890",
335
387
  proxySave: "Save",
336
388
  proxySaved: "Proxy settings saved.",
337
389
  proxyHint: "Save empty to clear the proxy.",
338
390
  proxyTest: "Test",
339
391
  proxyTesting: "Testing\u2026",
392
+ proxyTestEmpty: "Enter a proxy address first",
340
393
  proxyTestFailed: "Test failed",
394
+ proxyScopePlugins: "Plugin marketplace",
395
+ proxyScopePluginsDesc: "Marketplace checks, installs and uninstalls (zcode CLI git operations) go through the proxy.",
396
+ proxyScopeDeps: "Plugin dependencies",
397
+ proxyScopeDepsDesc: "headroom and rtk installs, update checks and upgrades (pip and GitHub downloads) go through the proxy.",
398
+ proxyScopeSync: "Sync",
399
+ proxyScopeSyncDesc: "WebDAV upload and download go through the proxy. Off by default: domestic WebDAV providers work fine direct, and a stopped proxy breaks sync.",
341
400
  tabSync: "Sync",
342
- 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; data moves only when you click, never automatically.",
401
+ tabDebug: "Debug",
402
+ 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.",
343
403
  syncUrlPlaceholder: "https://dav.example.com/dav/",
344
404
  syncDir: "\u4E0A\u4F20\u76EE\u5F55",
345
405
  syncDirPlaceholder: "zcode-pro",
@@ -347,7 +407,7 @@
347
407
  syncPassword: "Password",
348
408
  syncSave: "Save",
349
409
  syncSaved: "Sync settings saved.",
350
- syncFormHint: "Sync files go to zcodepro.json and model.json inside the folder (default zcode-pro, multi-level allowed, created automatically).",
410
+ 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.",
351
411
  syncRecLink: "Jianguoyun WebDAV",
352
412
  syncRecSuffix: " \u2014 use the app password, not your account password.",
353
413
  syncIncludeZcp: "ZCode Pro settings",
@@ -357,16 +417,44 @@
357
417
  syncUpload: "Upload",
358
418
  syncDownload: "Download",
359
419
  syncUploading: "Uploading\u2026",
360
- syncDownloading: "Downloading\u2026",
361
420
  syncUploaded: "Uploaded to the server.",
362
421
  syncDownloaded: "Restored from the server.",
363
422
  syncLastPush: "Last upload",
364
423
  syncLastPull: "Last download",
365
424
  syncNever: "never",
366
425
  syncConfirmTitle: "Download from the server",
367
- syncConfirmDesc: "Cloud data will overwrite the selected sections locally (a .zcodepro-backup copy is kept next to each file); your local sync config and credentials stay untouched. Restarting ZCode is recommended after restoring model settings. Download now?",
368
- syncConfirmGo: "Download",
426
+ syncConfirmDesc: "The selected file will overwrite the matching local section (a .zcodepro-backup copy is kept next to each file); your local sync config and credentials stay untouched. Restarting ZCode is recommended after restoring model settings. Import now?",
427
+ syncConfirmGo: "Import",
428
+ syncPickDesc: "Fixed names are always the latest version; timestamped files are the history each upload leaves behind. Import overwrites the matching local section.",
429
+ syncVerDeleteLatestNote: 'Note: this is the "latest" file that older zcode-pro installs download from; deleting it breaks their download. Timestamped history versions are not affected.',
369
430
  syncHint: "Sync credentials are stored locally in zcodepro.json only; model settings contain the API keys of custom providers and go to your own WebDAV only.",
431
+ syncTest: "Test",
432
+ syncTesting: "Testing\u2026",
433
+ syncTestEmpty: "Enter the WebDAV URL first",
434
+ syncTestFailed: "Test failed",
435
+ syncTestStepConnect: "Connect",
436
+ syncTestStepDir: "Folder",
437
+ syncTestStepList: "Files",
438
+ syncTestFilesFound: "{n} backup file(s) on the server",
439
+ syncPickTitle: "Select a file",
440
+ syncPickLoading: "Loading files from the server\u2026",
441
+ syncPickEmpty: "No backups on the server yet. Upload from a computer first.",
442
+ syncVerImport: "Import",
443
+ syncVerImporting: "Importing\u2026",
444
+ syncVerDelete: "Delete",
445
+ syncVerSave: "Save to disk",
446
+ syncVerSaving: "Saving\u2026",
447
+ syncVerLatest: "Latest",
448
+ syncVerDeleteTitle: "Delete server file",
449
+ syncVerDeleteDesc: "This backup will be deleted from the server and cannot be recovered: ",
450
+ syncVerDeleted: "Backup deleted from the server.",
451
+ syncVerSaved: "Saved to",
452
+ devMode: "Developer mode",
453
+ devModeRowDesc: 'Shows the "Reload ZCode Pro" button; can also be enabled with the ZCODEPRO_DEV=1 environment variable. This tab is visible only while developer mode is active.',
454
+ devModeDesc: 'For development: after changing zcode-pro code, click "Reload ZCode Pro" instead of restarting ZCode. It builds automatically (when running from the source repo), restarts the ZCode Pro launcher process (fresh helper and injected script), and every ZCode window refreshes and re-injects. The ZCode app itself and all sessions stay untouched.',
455
+ devReload: "Reload ZCode Pro",
456
+ devReloading: "Reloading\u2026",
457
+ devReloadDesc: "Every ZCode window refreshes automatically within 2-3 seconds to load the new script.",
370
458
  rowGapName: "Paragraph spacing",
371
459
  rowGapDesc: "Vertical spacing between text blocks (turns, paragraphs inside answers).",
372
460
  listSpacingName: "List spacing",
@@ -383,6 +471,8 @@
383
471
  tableCellPaddingDesc: "Padding inside table cells; left input is vertical, right is horizontal.",
384
472
  lineHeightName: "Answer line height",
385
473
  lineHeightDesc: "Line height of answer text (multiplier).",
474
+ thinkingLineHeightName: "Work-log line height",
475
+ thinkingLineHeightDesc: "Line height of the \u201Cworked for\u2026\u201D history panel (tool entries and reasoning, multiplier).",
386
476
  userLineHeightName: "Question line height",
387
477
  userLineHeightDesc: "Line height of question text (multiplier).",
388
478
  contentWidthName: "Content width",
@@ -446,7 +536,7 @@
446
536
  featurePinnedExpand: "Keep projects collapsed for pinned sessions (experimental)",
447
537
  featurePinnedExpandDesc: "Keeps the project collapsed after clicking a pinned session. Note: it briefly expands first, then collapses.",
448
538
  featureAutoUpdatePlugins: "Auto-update zcode-plugins plugins",
449
- featureAutoUpdatePluginsDesc: "Checks and updates installed zcode-plugins plugins on startup (including newly added ones). Updates apply to new sessions.",
539
+ featureAutoUpdatePluginsDesc: "Checks and updates installed zcode-plugins plugins on startup (updates only \u2014 never installs; uninstalled plugins stay uninstalled). Updates apply to new sessions.",
450
540
  pluginsUpdateTitle: "Plugin updates \xB7 zcode-plugins",
451
541
  pluginsCheckNow: "Check & update",
452
542
  pluginsUpdating: "Updating plugins\u2026",
@@ -458,7 +548,7 @@
458
548
  pluginsAutoUpdated: "zcode-plugins updated",
459
549
  tabVision: "Vision",
460
550
  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.",
461
- visionEnabled: "Enable image recognition",
551
+ visionEnabled: "Enable",
462
552
  visionForceIntercept: "Intercept even if the main model sees images",
463
553
  visionMode: "Chain mode",
464
554
  visionModeFallback: "Fallback",
@@ -483,21 +573,21 @@
483
573
  visionResetChainConfirm: "Reset chain mode and the proxy list to the default 2-level chain (glm-session \u2192 glm-flash)? Current proxy edits will be lost. Continue?",
484
574
  visionBaseUrl: "Base URL",
485
575
  visionModel: "Model",
486
- visionFormat: "Format",
487
576
  visionApiKey: "API Key",
488
577
  visionApiKeyHint: "Leave empty to reuse the GLM subscription key",
489
578
  visionPrompt: "Recognition prompt ({prev} = previous step in pipeline)",
490
579
  visionTest: "Test",
491
580
  visionTesting: "Testing\u2026",
492
581
  visionTestFailed: "Vision proxy test failed",
493
- visionTestOutput: "Test output",
494
582
  visionCompressKB: "Compress threshold KB",
495
583
  visionCompressKBHint: "Images above this size are compressed first (max edge 2000, JPEG); 0 = never",
496
584
  visionLoadFailed: "Failed to load the vision config",
497
585
  tabRtk: "rtk",
498
586
  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.",
499
- rtkEnabled: "Enable rtk compression",
587
+ rtkEnabled: "Enable",
500
588
  rtkNotInstalled: "rtk is not installed. The settings below take effect after installation (/rtk-setup in a session).",
589
+ rtkInstallBin: "Install rtk binary",
590
+ rtkBinInstalled: "rtk installed; PATH takes effect for sessions after signing out and back in \u2014 the plugin hook works immediately",
501
591
  rtkWhitelistTitle: "Whitelist (pass through without compression)",
502
592
  rtkBuiltinLabel: "Built-in exemptions (read-only, follow plugin updates):",
503
593
  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, \u2026) cannot be removed. Delete the file to reset.",
@@ -510,12 +600,25 @@
510
600
  tabHeadroom: "Headroom",
511
601
  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.",
512
602
  headroomBinMissing: "The headroom program was not found, so the proxy cannot start. The settings below take effect once it is installed.",
603
+ hrInstallBin: "Install headroom binary",
604
+ hrBinInstalled: "headroom installed; reopen a session and the hook will start the proxy automatically",
513
605
  headroomInstallTitle: "headroom plugin not installed",
514
606
  headroomInstallDesc: "Install it from the zcode-plugins marketplace to manage the compression proxy here; restarting the session afterwards is recommended.",
515
607
  headroomInstall: "Install",
516
608
  headroomInstalling: "Installing\u2026",
517
609
  headroomInstalled: "headroom plugin installed",
518
610
  headroomInstallRetry: "Installation did not take effect; retry or install it from the marketplace manually",
611
+ pluginInstallCardTitle: "{name} plugin not installed",
612
+ pluginInstallCardDesc: "Install it from the zcode-plugins marketplace to manage it here; restarting the session afterwards is recommended.",
613
+ pluginInstallDone: "{name} plugin installed",
614
+ pluginEnable: "Enable",
615
+ hrStarting: "Starting compression proxy\u2026",
616
+ hrStarted: "Compression proxy started",
617
+ hrStartTimeout: "Proxy start timed out (90s); check ~/.zcode-headroom-proxy.log",
618
+ hrStopping: "Stopping compression proxy\u2026",
619
+ hrRestarting: "Restarting compression proxy\u2026",
620
+ hrStopped: "Compression proxy stopped",
621
+ hrUsageNone: "No providers route through headroom; nothing changed",
519
622
  headroomLoadFailed: "Failed to load the Headroom config",
520
623
  headroomStatusTitle: "Status",
521
624
  headroomStatusLoadFailed: "Failed to load the status",
@@ -541,7 +644,7 @@
541
644
  headroomRestart: "Restart",
542
645
  headroomStopAction: "Stop",
543
646
  headroomStopTitle: "Stop the Headroom proxy",
544
- headroomStopDesc: "Providers in ZCode that point at this proxy (127.0.0.1) will fail to connect until it is started again. Stop it?",
647
+ headroomStopDesc: "Requests from ZCode and other apps pointing at this proxy (127.0.0.1) will fail until it is started again. headroom is shared \u2014 other tools may be using it. Stop it?",
545
648
  headroomStoppedWarn: "Proxy stopped; providers pointing at 127.0.0.1 cannot connect.",
546
649
  headroomBackend: "Compression device",
547
650
  headroomBackendAuto: "Auto \xB7 GPU first",
@@ -559,8 +662,6 @@
559
662
  headroomInterval: "Watch interval",
560
663
  headroomIntervalDesc: "Seconds between power checks (10\u20133600, default 60); applies from the next round, no restart needed.",
561
664
  headroomSecUnit: "s",
562
- headroomVersionPlugin: "plugin",
563
- headroomVersionBin: "program",
564
665
  upgradeCheck: "Check update",
565
666
  upgradeChecking: "Checking\u2026",
566
667
  upgradeUpToDate: "Up to date ({v})",
@@ -678,7 +779,9 @@
678
779
  "sync-remote-missing": "No synced data on the server yet; upload from another computer first",
679
780
  "sync-remote-invalid": "The URL already holds a different file; use another file path",
680
781
  "sync-request": "The WebDAV request failed",
681
- "sync-run": "Sync failed"
782
+ "sync-run": "Sync failed",
783
+ "sync-file-invalid": "Invalid backup file name",
784
+ "sync-export": "Failed to save the backup to disk"
682
785
  };
683
786
  function errText(res) {
684
787
  if (!res) return "";
@@ -753,8 +856,9 @@
753
856
  "div",
754
857
  {
755
858
  id: "__zcodepro_toast__",
756
- class: "fixed bottom-6 right-6 z-[100] flex max-w-md items-center gap-2 rounded-lg border px-4 py-3 text-ui-sm shadow-lg " + (kind === "error" ? "border-destructive/40 bg-menu text-foreground" : "border-border bg-menu text-foreground"),
757
- style: "animation:zcodepro-fade-in .18s ease"
859
+ class: "fixed bottom-6 z-[100] flex max-w-md items-center gap-2 rounded-lg border px-4 py-3 text-ui-sm shadow-lg " + (kind === "error" ? "border-destructive/40 bg-menu text-foreground" : "border-border bg-menu text-foreground"),
860
+ // left 用内联样式:ZCode 的 Tailwind 未编译 left-6/right-6 工具类(bottom-6 有)
861
+ style: "animation:zcodepro-fade-in .18s ease; left: 1.5rem;"
758
862
  },
759
863
  h("span", { class: "truncate" }, text)
760
864
  );
@@ -2361,6 +2465,8 @@
2361
2465
  // 单元格左右边距(应用默认 3px)
2362
2466
  lineHeight: 1.75,
2363
2467
  // 回答行高(leading-[1.75],挂在答案内容容器上)
2468
+ thinkingLineHeight: 1.5,
2469
+ // 思考行高:“已工作/工作中”过程视图的行距(实测应用默认 1.5;范围见 buildCss 注释)
2364
2470
  userLineHeight: 1.5,
2365
2471
  // 提问行高(用户消息文本容器,默认 normal=1.5)
2366
2472
  contentWidth: null,
@@ -2457,6 +2563,12 @@
2457
2563
  if (typeof lh === "number" && Number.isFinite(lh) && lh >= 0.8) {
2458
2564
  parts.push(`${CONV} .space-y-4{line-height:${lh} !important;}`);
2459
2565
  }
2566
+ const tlh = styles.thinkingLineHeight;
2567
+ if (typeof tlh === "number" && Number.isFinite(tlh) && tlh >= 0.8) {
2568
+ parts.push(
2569
+ `[data-testid^="chat-assistant-history-content"]{line-height:${tlh} !important;}`
2570
+ );
2571
+ }
2460
2572
  const ulh = styles.userLineHeight;
2461
2573
  if (typeof ulh === "number" && Number.isFinite(ulh) && ulh >= 0.8) {
2462
2574
  parts.push(`${CONV} [class*="user-row"] .whitespace-pre-wrap{line-height:${ulh} !important;}`);
@@ -2553,7 +2665,10 @@
2553
2665
  tableCellPaddingV: `${CONV} td, ${CONV} th`,
2554
2666
  tableCellPaddingH: `${CONV} td, ${CONV} th`,
2555
2667
  codeLineHeight: `${CONV} diffs-container, ${CONV} pre`,
2556
- lineHeight: `${CONV} .space-y-4`,
2668
+ lineHeight: `${CONV} .space-y-4:not([data-reasoning-content] *)`,
2669
+ // 思考行高作用在“已工作/工作中”过程视图容器上;视图折叠未挂载时不标
2670
+ // (触发行不属此设置范围,不额外描边)
2671
+ thinkingLineHeight: `[data-testid^="chat-assistant-history-content"]`,
2557
2672
  userLineHeight: `${CONV} [class*="user-row"] .whitespace-pre-wrap`,
2558
2673
  contentWidth: "[data-v4-timeline-content-column]",
2559
2674
  sidebarProjectSpacing: '[data-testid^="workspace-item-"]',
@@ -2600,11 +2715,13 @@
2600
2715
  } catch {
2601
2716
  }
2602
2717
  }
2603
- function makeUpgradeControls({ endpoint, metaRefresh }) {
2718
+ function makeUpgradeControls({ endpoint, metaRefresh, idleText }) {
2604
2719
  const L = t();
2605
2720
  const state = h("span", { class: "min-w-0 flex-1 truncate text-left text-ui-xs/relaxed text-foreground-subtle" });
2721
+ const hasIdle = () => typeof idleText === "function";
2606
2722
  const setState = (text, kind = "") => {
2607
- state.textContent = text;
2723
+ const shown = text || (hasIdle() ? idleText() || "" : "");
2724
+ state.textContent = shown ? kind === "error" ? shown : ` \xB7 ${shown}` : "";
2608
2725
  state.className = "min-w-0 flex-1 truncate text-left text-ui-xs/relaxed " + (kind === "error" ? "text-destructive" : "text-foreground-subtle");
2609
2726
  };
2610
2727
  let latest = null;
@@ -2722,6 +2839,7 @@
2722
2839
  const res = await rpc(endpoint + "/upgrade");
2723
2840
  if (res.ok && res.upgrade && res.upgrade.running) pollStart();
2724
2841
  })();
2842
+ setState("");
2725
2843
  return { btn, state, stop: stopPoll };
2726
2844
  }
2727
2845
  function openSettingsDialog() {
@@ -2930,7 +3048,7 @@
2930
3048
  const runPluginsUpdate = async () => {
2931
3049
  pluginsBtn.disabled = true;
2932
3050
  pluginsStatusLine.textContent = L.pluginsUpdating;
2933
- const res = await rpc("/plugins/update", { method: "POST", body: { installMissing: true } });
3051
+ const res = await rpc("/plugins/update", { method: "POST", body: {} });
2934
3052
  pluginsBtn.disabled = false;
2935
3053
  if (!res.ok) {
2936
3054
  pluginsStatusLine.textContent = "";
@@ -2991,7 +3109,8 @@
2991
3109
  const paneVision = h("div", { role: "tabpanel", class: "mt-4", style: "display:none" });
2992
3110
  const paneRtk = h("div", { role: "tabpanel", class: "mt-4", style: "display:none" });
2993
3111
  const paneHeadroom = h("div", { role: "tabpanel", class: "mt-4", style: "display:none" });
2994
- const panes = { features: paneFeatures, styles: paneStyles, perf: panePerf, agents: paneAgents, proxy: paneProxy, sync: paneSync, vision: paneVision, headroom: paneHeadroom, rtk: paneRtk };
3112
+ const paneDebug = h("div", { role: "tabpanel", class: "mt-4", style: "display:none" });
3113
+ const panes = { features: paneFeatures, styles: paneStyles, perf: panePerf, agents: paneAgents, proxy: paneProxy, sync: paneSync, vision: paneVision, headroom: paneHeadroom, rtk: paneRtk, debug: paneDebug };
2995
3114
  const tabDefs = [
2996
3115
  ["features", L.tabFeatures],
2997
3116
  ["styles", L.tabStyles],
@@ -3001,7 +3120,10 @@
3001
3120
  ["sync", L.tabSync],
3002
3121
  ["vision", L.tabVision],
3003
3122
  ["headroom", L.tabHeadroom],
3004
- ["rtk", L.tabRtk]
3123
+ ["rtk", L.tabRtk],
3124
+ // “调试”标签页只对开发模式生效的用户显示(配置开关或 ZCODEPRO_DEV=1),
3125
+ // 普通用户不可见
3126
+ ...config.devMode === true || config.devActive === true ? [["debug", L.tabDebug]] : []
3005
3127
  ];
3006
3128
  const tablist = h("div", { role: "tablist", "aria-orientation": "horizontal", class: "zcodepro-tablist mt-4" });
3007
3129
  const renderTabs = () => tablist.replaceChildren(...tabDefs.map(([id, label]) => h("button", {
@@ -3119,6 +3241,7 @@
3119
3241
  styleCell(L.rowGapName, L.rowGapDesc, "rowGap"),
3120
3242
  styleCell(L.userLineHeightName, L.userLineHeightDesc, "userLineHeight", { min: 1, max: 3, step: 0.05, unit: "x" }),
3121
3243
  styleCell(L.lineHeightName, L.lineHeightDesc, "lineHeight", { min: 1, max: 3, step: 0.05, unit: "x" }),
3244
+ styleCell(L.thinkingLineHeightName, L.thinkingLineHeightDesc, "thinkingLineHeight", { min: 1, max: 3, step: 0.05, unit: "x" }),
3122
3245
  styleCell(L.codeLineHeightName, L.codeLineHeightDesc, "codeLineHeight", { min: 1, max: 3, step: 0.05, unit: "x" }),
3123
3246
  styleCell(L.listSpacingName, L.listSpacingDesc, "listSpacing"),
3124
3247
  styleCell(L.listItemSpacingName, L.listItemSpacingDesc, "listItemSpacing"),
@@ -3278,6 +3401,11 @@
3278
3401
  }, "shrink-0");
3279
3402
  const runProxyTest = async () => {
3280
3403
  if (proxyTesting) return;
3404
+ if (!proxyInput.value.trim()) {
3405
+ proxyTestState.className = "mt-2 text-ui-xs/relaxed text-amber-500";
3406
+ proxyTestState.textContent = L.proxyTestEmpty;
3407
+ return;
3408
+ }
3281
3409
  proxyTesting = true;
3282
3410
  proxyTestBtn.disabled = true;
3283
3411
  proxyTestBtn.textContent = L.proxyTesting;
@@ -3297,6 +3425,37 @@
3297
3425
  proxyTestState.className = "mt-2 text-ui-xs/relaxed " + (allOk ? "text-foreground-subtle" : "text-amber-500");
3298
3426
  proxyTestState.textContent = `${res.proxy}\uFF1A${parts.join(" \xB7 ")}`;
3299
3427
  };
3428
+ const persistProxyScope = async (partial) => {
3429
+ const res = await rpc("/config", { method: "POST", body: { proxyScopes: partial } });
3430
+ if (!res.ok) {
3431
+ showToast(L.failed + ": " + errText(res), "error");
3432
+ return false;
3433
+ }
3434
+ if (res.config && res.config.proxyScopes) config.proxyScopes = res.config.proxyScopes;
3435
+ return true;
3436
+ };
3437
+ const proxyScopeWrap = h("div");
3438
+ const renderProxyScopes = () => {
3439
+ const sc = config.proxyScopes || {};
3440
+ proxyScopeWrap.replaceChildren(
3441
+ settingRow(L.proxyScopePlugins, L.proxyScopePluginsDesc, sc.plugins !== false, async () => {
3442
+ const next = !(sc.plugins !== false);
3443
+ if (await persistProxyScope({ plugins: next })) sc.plugins = next;
3444
+ renderProxyScopes();
3445
+ }),
3446
+ settingRow(L.proxyScopeDeps, L.proxyScopeDepsDesc, sc.deps !== false, async () => {
3447
+ const next = !(sc.deps !== false);
3448
+ if (await persistProxyScope({ deps: next })) sc.deps = next;
3449
+ renderProxyScopes();
3450
+ }),
3451
+ settingRow(L.proxyScopeSync, L.proxyScopeSyncDesc, sc.sync === true, async () => {
3452
+ const next = !(sc.sync === true);
3453
+ if (await persistProxyScope({ sync: next })) sc.sync = next;
3454
+ renderProxyScopes();
3455
+ })
3456
+ );
3457
+ };
3458
+ renderProxyScopes();
3300
3459
  paneProxy.append(
3301
3460
  h("p", { class: "text-ui-sm/relaxed text-foreground-subtle" }, L.proxyDesc),
3302
3461
  h(
@@ -3307,7 +3466,8 @@
3307
3466
  proxyTestBtn
3308
3467
  ),
3309
3468
  h("p", { class: "mt-2 text-ui-xs/relaxed text-foreground-subtle" }, L.proxyHint),
3310
- proxyTestState
3469
+ proxyTestState,
3470
+ h("div", { class: "mt-3 divide-y divide-border rounded-xl border border-border" }, proxyScopeWrap)
3311
3471
  );
3312
3472
  const perfToggleWrap = h("div");
3313
3473
  const renderPerfToggle = () => {
@@ -3414,6 +3574,43 @@
3414
3574
  syncCheckDirty();
3415
3575
  }
3416
3576
  };
3577
+ const syncTestState = h("p", { class: "mt-2 text-ui-xs/relaxed text-foreground-subtle" });
3578
+ let syncTesting = false;
3579
+ const syncTestBtn = btnSmall(L.syncTest, () => {
3580
+ void runSyncTest();
3581
+ }, "shrink-0");
3582
+ const runSyncTest = async () => {
3583
+ if (syncTesting) return;
3584
+ if (!syncUrlInput.value.trim()) {
3585
+ syncTestState.className = "mt-2 text-ui-xs/relaxed text-amber-500";
3586
+ syncTestState.textContent = L.syncTestEmpty;
3587
+ return;
3588
+ }
3589
+ syncTesting = true;
3590
+ syncTestBtn.disabled = true;
3591
+ syncTestBtn.textContent = L.syncTesting;
3592
+ syncTestState.textContent = "";
3593
+ syncTestState.className = "mt-2 text-ui-xs/relaxed text-foreground-subtle";
3594
+ const res = await rpc("/sync/test", { method: "POST", body: { sync: syncFormValues() } });
3595
+ syncTesting = false;
3596
+ syncTestBtn.disabled = false;
3597
+ syncTestBtn.textContent = L.syncTest;
3598
+ if (!res.ok) {
3599
+ syncTestState.className = "mt-2 text-ui-xs/relaxed text-destructive";
3600
+ syncTestState.textContent = L.syncTestFailed + ": " + errText(res);
3601
+ return;
3602
+ }
3603
+ const stepName = { connect: L.syncTestStepConnect, dir: L.syncTestStepDir, list: L.syncTestStepList };
3604
+ const parts = (res.targets || []).map((t2) => `${stepName[t2.step] || t2.step} ${t2.ok ? "\u2713" + (t2.ms != null ? " " + t2.ms + "ms" : "") : "\u2717 " + (t2.error || t2.httpCode || "")}`.trim());
3605
+ const allOk = (res.targets || []).length > 0 && (res.targets || []).every((t2) => t2.ok);
3606
+ let host = syncUrlInput.value.trim();
3607
+ try {
3608
+ host = new URL(host).host;
3609
+ } catch {
3610
+ }
3611
+ syncTestState.className = "mt-2 text-ui-xs/relaxed " + (allOk ? "text-foreground-subtle" : "text-amber-500");
3612
+ syncTestState.textContent = `${host}\uFF1A${parts.join(" \xB7 ")}` + (allOk && res.fileCount != null ? `\uFF08${L.syncTestFilesFound.replace("{n}", res.fileCount)}\uFF09` : "");
3613
+ };
3417
3614
  const JIANGUO_HELP = "https://help.jianguoyun.com/?p=2064";
3418
3615
  const syncRecLine = h("p", { class: "mt-1 text-ui-xs/relaxed text-foreground-subtle" });
3419
3616
  if (typeof window.zcode?.openExternal === "function") {
@@ -3457,56 +3654,216 @@
3457
3654
  };
3458
3655
  renderSyncStatus(savedSync);
3459
3656
  const syncUploadBtn = btnSmall(L.syncUpload, () => {
3460
- void runSyncDirection("push");
3657
+ void runSyncPush();
3461
3658
  }, "shrink-0", "primary");
3462
3659
  const syncDownloadBtn = btnSmall(L.syncDownload, () => {
3463
- confirmSyncDownload();
3660
+ openSyncVersionPicker();
3464
3661
  }, "shrink-0");
3465
- const confirmSyncDownload = () => {
3662
+ const openSyncVersionPicker = () => {
3466
3663
  openDialog({
3467
- title: L.syncConfirmTitle,
3468
- description: L.syncConfirmDesc,
3469
- width: "sm:max-w-md",
3470
- onMount: ({ body: confirmBody, close: closeConfirm }) => {
3471
- confirmBody.append(
3472
- dialogFooter(
3473
- btnSecondary(L.cancel, () => closeConfirm()),
3474
- btnPrimary(L.syncConfirmGo, () => {
3475
- closeConfirm();
3476
- void runSyncDirection("pull");
3477
- }, "min-w-24")
3478
- )
3479
- );
3664
+ title: L.syncPickTitle,
3665
+ description: L.syncPickDesc,
3666
+ width: "max-w-2xl",
3667
+ dismissOnOutside: false,
3668
+ onMount: async ({ body: pickBody, content: content2, close: closePicker }) => {
3669
+ const titleEl2 = content2 && content2.firstElementChild;
3670
+ if (titleEl2 && titleEl2.tagName === "H2") {
3671
+ titleEl2.classList.add("flex", "w-full", "items-center", "justify-between");
3672
+ titleEl2.replaceChildren(
3673
+ h("span", { class: "flex min-w-0 items-center" }, L.syncPickTitle),
3674
+ h("button", {
3675
+ type: "button",
3676
+ class: "zcodepro-close",
3677
+ title: L.close,
3678
+ "aria-label": L.close,
3679
+ onMousedown: (e) => {
3680
+ e.stopPropagation();
3681
+ },
3682
+ onClick: () => {
3683
+ closePicker();
3684
+ }
3685
+ }, "\u2715")
3686
+ );
3687
+ }
3688
+ const listWrap = h("div", { class: "space-y-1" });
3689
+ pickBody.append(listWrap);
3690
+ const fmtSize = (n) => {
3691
+ if (!Number.isFinite(n) || n <= 0) return "";
3692
+ return n >= 1048576 ? (n / 1048576).toFixed(2) + " MB" : Math.max(1, Math.round(n / 1024)) + " KB";
3693
+ };
3694
+ const setStatus = (el) => {
3695
+ listWrap.replaceChildren(el);
3696
+ };
3697
+ const setRowBusy = (btns, busy) => {
3698
+ for (const b of btns) b.disabled = busy;
3699
+ };
3700
+ const versionRow = (f) => {
3701
+ const isLatest = !/-\d{4}-\d{2}-\d{2}T/.test(f.name);
3702
+ const importBtn = btnSmall(L.syncVerImport, () => {
3703
+ confirmImport(f, rowBtns, importBtn);
3704
+ }, "shrink-0");
3705
+ const deleteBtn = btnSmall(L.syncVerDelete, () => {
3706
+ confirmDelete(f, rowBtns, isLatest);
3707
+ }, "shrink-0");
3708
+ const saveBtn = btnSmall(L.syncVerSave, () => {
3709
+ void exportVersion(f, rowBtns, saveBtn);
3710
+ }, "shrink-0");
3711
+ const rowBtns = [importBtn, deleteBtn, saveBtn];
3712
+ return h(
3713
+ "div",
3714
+ { class: "flex items-center gap-3 rounded-xl border border-border px-3 py-2" },
3715
+ h(
3716
+ "div",
3717
+ { class: "min-w-0 flex-1" },
3718
+ h(
3719
+ "div",
3720
+ { class: "flex min-w-0 items-center gap-1.5" },
3721
+ h("span", { class: "truncate font-mono text-ui-xs text-foreground", title: f.name }, f.name),
3722
+ ...isLatest ? [h("span", { class: "shrink-0 rounded border border-border px-1.5 text-ui-xs text-foreground-subtle" }, L.syncVerLatest)] : []
3723
+ ),
3724
+ h(
3725
+ "div",
3726
+ { class: "mt-0.5 text-ui-xs/relaxed text-foreground-subtle" },
3727
+ [fmtSize(f.size), f.lastModified ? new Date(f.lastModified).toLocaleString() : ""].filter(Boolean).join(" \xB7 ")
3728
+ )
3729
+ ),
3730
+ h("div", { class: "flex shrink-0 items-center gap-1.5" }, importBtn, deleteBtn, saveBtn)
3731
+ );
3732
+ };
3733
+ const loadList = async () => {
3734
+ setStatus(h("p", { class: "text-ui-sm/relaxed text-foreground-subtle" }, L.syncPickLoading));
3735
+ const res = await rpc("/sync/versions", { method: "POST", body: {} });
3736
+ if (!res.ok) {
3737
+ setStatus(h("p", { class: "text-ui-sm text-destructive" }, L.failed + ": " + errText(res)));
3738
+ return;
3739
+ }
3740
+ const files = res.files || [];
3741
+ if (!files.length) {
3742
+ setStatus(h("p", { class: "text-ui-sm/relaxed text-foreground-subtle" }, L.syncPickEmpty));
3743
+ return;
3744
+ }
3745
+ const parts = [];
3746
+ for (const [prefix, label] of [["zcodepro", L.syncIncludeZcp], ["model", L.syncIncludeModel]]) {
3747
+ const group = files.filter((f) => f.name === `${prefix}.json` || f.name.startsWith(`${prefix}-`));
3748
+ if (!group.length) continue;
3749
+ const latest = group.find((f) => f.name === `${prefix}.json`);
3750
+ const history = group.filter((f) => f !== latest).sort((a, b) => (b.lastModified || 0) - (a.lastModified || 0) || b.name.localeCompare(a.name));
3751
+ parts.push(h(
3752
+ "div",
3753
+ { class: "px-1 pt-2 text-ui-xs font-medium text-foreground-subtle" },
3754
+ `${label}${isZhLocale() ? "\uFF08" : " ("}${group.length}${isZhLocale() ? "\uFF09" : ")"}`
3755
+ ));
3756
+ for (const f of [latest, ...history]) {
3757
+ if (f) parts.push(versionRow(f));
3758
+ }
3759
+ }
3760
+ listWrap.replaceChildren(...parts);
3761
+ };
3762
+ const confirmImport = (f, rowBtns, importBtn) => {
3763
+ openDialog({
3764
+ title: L.syncConfirmTitle,
3765
+ width: "sm:max-w-md",
3766
+ onMount: ({ body: confirmBody, close: closeConfirm }) => {
3767
+ confirmBody.append(
3768
+ h("p", { class: "mb-2 truncate font-mono text-ui-xs text-foreground-subtle", title: f.name }, f.name),
3769
+ h("p", { class: "text-ui-sm/relaxed text-foreground-subtle" }, L.syncConfirmDesc),
3770
+ dialogFooter(
3771
+ btnSecondary(L.cancel, () => closeConfirm()),
3772
+ btnPrimary(L.syncConfirmGo, () => {
3773
+ closeConfirm();
3774
+ void importVersion(f, rowBtns, importBtn);
3775
+ }, "min-w-24")
3776
+ )
3777
+ );
3778
+ }
3779
+ });
3780
+ };
3781
+ const importVersion = async (f, rowBtns, importBtn) => {
3782
+ setRowBusy(rowBtns, true);
3783
+ importBtn.textContent = L.syncVerImporting;
3784
+ const res = await rpc("/sync/version/apply", { method: "POST", body: { file: f.name } });
3785
+ setRowBusy(rowBtns, false);
3786
+ importBtn.textContent = L.syncVerImport;
3787
+ if (!res.ok) {
3788
+ showToast(L.failed + ": " + errText(res), "error");
3789
+ return;
3790
+ }
3791
+ closePicker();
3792
+ renderSyncStatus(res.sync || {});
3793
+ clearConfigCache();
3794
+ window.dispatchEvent(new CustomEvent("zcodepro:config-changed"));
3795
+ Object.assign(config, await getConfig(true));
3796
+ refreshRows();
3797
+ renderToolbarSwitch();
3798
+ const names = (res.applied || []).map((k) => k === "model" ? L.syncIncludeModel : L.syncIncludeZcp).join(isZhLocale() ? "\u3001" : ", ");
3799
+ showToast(names ? `${L.syncDownloaded}\uFF08${names}\uFF09` : L.syncDownloaded, "success");
3800
+ };
3801
+ const confirmDelete = (f, rowBtns, isLatest) => {
3802
+ openDialog({
3803
+ title: L.syncVerDeleteTitle,
3804
+ width: "sm:max-w-md",
3805
+ onMount: ({ body: confirmBody, close: closeConfirm }) => {
3806
+ confirmBody.append(
3807
+ h(
3808
+ "p",
3809
+ { class: "text-ui-sm/relaxed text-foreground-subtle" },
3810
+ L.syncVerDeleteDesc,
3811
+ h("span", { class: "font-mono" }, f.name)
3812
+ ),
3813
+ ...isLatest ? [h("p", { class: "mt-2 text-ui-xs/relaxed text-amber-500" }, L.syncVerDeleteLatestNote)] : [],
3814
+ dialogFooter(
3815
+ btnSecondary(L.cancel, () => closeConfirm()),
3816
+ btnPrimary(L.syncVerDelete, () => {
3817
+ closeConfirm();
3818
+ void deleteVersion(f, rowBtns);
3819
+ }, "min-w-24")
3820
+ )
3821
+ );
3822
+ }
3823
+ });
3824
+ };
3825
+ const deleteVersion = async (f, rowBtns) => {
3826
+ setRowBusy(rowBtns, true);
3827
+ const res = await rpc("/sync/version/delete", { method: "POST", body: { file: f.name } });
3828
+ setRowBusy(rowBtns, false);
3829
+ if (!res.ok) {
3830
+ showToast(L.failed + ": " + errText(res), "error");
3831
+ return;
3832
+ }
3833
+ showToast(L.syncVerDeleted, "success");
3834
+ void loadList();
3835
+ };
3836
+ const exportVersion = async (f, rowBtns, saveBtn) => {
3837
+ setRowBusy(rowBtns, true);
3838
+ saveBtn.textContent = L.syncVerSaving;
3839
+ const res = await rpc("/sync/version/export", { method: "POST", body: { file: f.name } });
3840
+ setRowBusy(rowBtns, false);
3841
+ saveBtn.textContent = L.syncVerSave;
3842
+ if (!res.ok) {
3843
+ if (res.code !== "export-cancelled") showToast(L.failed + ": " + errText(res), "error");
3844
+ return;
3845
+ }
3846
+ showToast(`${L.syncVerSaved} ${res.path}`, "success");
3847
+ };
3848
+ void loadList();
3480
3849
  }
3481
3850
  });
3482
3851
  };
3483
- const runSyncDirection = async (direction) => {
3852
+ const runSyncPush = async () => {
3484
3853
  syncUploadBtn.disabled = true;
3485
3854
  syncDownloadBtn.disabled = true;
3486
- const btn = direction === "push" ? syncUploadBtn : syncDownloadBtn;
3487
- const idleText = btn.textContent;
3488
- btn.textContent = direction === "push" ? L.syncUploading : L.syncDownloading;
3489
- const res = await rpc("/sync/run", { method: "POST", body: { direction } });
3855
+ const idleText = syncUploadBtn.textContent;
3856
+ syncUploadBtn.textContent = L.syncUploading;
3857
+ const res = await rpc("/sync/run", { method: "POST", body: { direction: "push" } });
3490
3858
  syncUploadBtn.disabled = false;
3491
3859
  syncDownloadBtn.disabled = false;
3492
- btn.textContent = idleText;
3860
+ syncUploadBtn.textContent = idleText;
3493
3861
  if (!res.ok) {
3494
3862
  showToast(L.failed + ": " + errText(res), "error");
3495
3863
  return;
3496
3864
  }
3497
3865
  renderSyncStatus(res.sync || {});
3498
- if (direction === "pull") {
3499
- clearConfigCache();
3500
- window.dispatchEvent(new CustomEvent("zcodepro:config-changed"));
3501
- Object.assign(config, await getConfig(true));
3502
- refreshRows();
3503
- renderToolbarSwitch();
3504
- }
3505
- if (direction === "push") showToast(L.syncUploaded, "success");
3506
- else {
3507
- const names = (res.applied || []).map((k) => k === "model" ? L.syncIncludeModel : L.syncIncludeZcp).join(isZhLocale() ? "\u3001" : ", ");
3508
- showToast(names ? `${L.syncDownloaded}\uFF08${names}\uFF09` : L.syncDownloaded, "success");
3509
- }
3866
+ showToast(L.syncUploaded, "success");
3510
3867
  };
3511
3868
  paneSync.append(
3512
3869
  h("p", { class: "text-ui-sm/relaxed text-foreground-subtle" }, L.syncDesc),
@@ -3526,9 +3883,10 @@
3526
3883
  h("p", { class: "text-ui-xs/relaxed text-foreground-subtle" }, L.syncFormHint),
3527
3884
  syncRecLine
3528
3885
  ),
3529
- syncSaveBtn
3886
+ h("div", { class: "flex shrink-0 items-center gap-1.5" }, syncSaveBtn, syncTestBtn)
3530
3887
  )
3531
3888
  ),
3889
+ syncTestState,
3532
3890
  h("div", { class: "mt-2 divide-y divide-border rounded-xl border border-border" }, syncIncludeWrap),
3533
3891
  h(
3534
3892
  "div",
@@ -3538,6 +3896,59 @@
3538
3896
  ),
3539
3897
  h("p", { class: "mt-2 text-ui-xs/relaxed text-foreground-subtle" }, L.syncHint)
3540
3898
  );
3899
+ const persistDevMode = async (v) => {
3900
+ const res = await rpc("/config", { method: "POST", body: { devMode: v } });
3901
+ if (!res.ok) {
3902
+ showToast(L.failed + ": " + errText(res), "error");
3903
+ return false;
3904
+ }
3905
+ if (res.config) config.devMode = res.config.devMode;
3906
+ return true;
3907
+ };
3908
+ const devReloadBtn = btnSmall(L.devReload, () => {
3909
+ void runDevReload();
3910
+ }, "shrink-0", "primary");
3911
+ const runDevReload = async () => {
3912
+ devReloadBtn.disabled = true;
3913
+ devReloadBtn.textContent = L.devReloading;
3914
+ try {
3915
+ await rpc("/dev/reload", { method: "POST", body: {} });
3916
+ } catch {
3917
+ }
3918
+ setTimeout(() => {
3919
+ try {
3920
+ location.reload();
3921
+ } catch {
3922
+ }
3923
+ }, 2e3);
3924
+ };
3925
+ const devModeWrap = h("div");
3926
+ const renderDevPane = () => {
3927
+ const dev = config.devMode === true;
3928
+ devModeWrap.replaceChildren(
3929
+ h("p", { class: "text-ui-sm/relaxed text-foreground-subtle" }, L.devModeDesc),
3930
+ h(
3931
+ "div",
3932
+ { class: "mt-3 divide-y divide-border rounded-xl border border-border" },
3933
+ settingRow(L.devMode, L.devModeRowDesc, dev, async () => {
3934
+ if (await persistDevMode(!dev)) renderDevPane();
3935
+ }),
3936
+ ...dev ? [h(
3937
+ "div",
3938
+ { class: "flex items-center justify-between gap-2 px-2 py-1.5" },
3939
+ h(
3940
+ "div",
3941
+ { class: "min-w-0" },
3942
+ h("div", { class: "text-ui-sm font-medium text-foreground" }, L.devReload),
3943
+ h("div", { class: "mt-0.5 text-ui-xs/relaxed text-foreground-subtle" }, L.devReloadDesc)
3944
+ ),
3945
+ devReloadBtn
3946
+ )] : []
3947
+ )
3948
+ );
3949
+ };
3950
+ renderDevPane();
3951
+ paneDebug.append(devModeWrap);
3541
3952
  const VISION_DEFAULT_PROMPT = "\u8BF7\u8BE6\u7EC6\u63CF\u8FF0\u8FD9\u5F20\u56FE\u7247\u7684\u5168\u90E8\u5185\u5BB9\u3002\u82E5\u662F\u754C\u9762\u6216\u56FE\u8868\u622A\u56FE\uFF0C\u8BF7\u5148\u628A\u6240\u6709\u9519\u8BEF\u3001\u8B66\u544A\u3001\u5F02\u5E38\u72B6\u6001\u9010\u5B57\u5F15\u7528\u51FA\u6765\uFF08\u542B\u5B8C\u6574\u539F\u6587\uFF09\uFF0C\u518D\u63CF\u8FF0\u6574\u4F53\u5E03\u5C40\u3001\u6587\u5B57\u4E0E\u5173\u952E\u6570\u636E\u3002";
3542
3953
  const VISION_DEFAULT_CFG = {
3543
3954
  enabled: true,
@@ -3575,7 +3986,57 @@
3575
3986
  style: "display:none"
3576
3987
  });
3577
3988
  const visionInputCls = "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";
3989
+ const pluginInstallCard = (pluginName, onInstalled) => {
3990
+ const installBtn = btnSmall(L.headroomInstall, () => {
3991
+ void runInstall();
3992
+ }, "shrink-0");
3993
+ const installState = h("div", { class: "mt-0.5 truncate text-ui-xs/relaxed text-foreground-subtle" });
3994
+ const runInstall = async () => {
3995
+ installBtn.disabled = true;
3996
+ installBtn.textContent = L.headroomInstalling;
3997
+ installState.textContent = "";
3998
+ const res = await rpc("/plugins/update", { method: "POST", body: { name: pluginName } });
3999
+ if (!res.ok) {
4000
+ installBtn.disabled = false;
4001
+ installBtn.textContent = L.headroomInstall;
4002
+ showToast(L.pluginsUpdateFailed + ": " + errText(res), "error");
4003
+ return;
4004
+ }
4005
+ if (onInstalled) await onInstalled();
4006
+ showToast(L.pluginInstallDone.replaceAll("{name}", pluginName), "success");
4007
+ };
4008
+ return h(
4009
+ "div",
4010
+ { class: "mt-3 flex items-center justify-between gap-2 rounded-xl border border-border p-2" },
4011
+ h(
4012
+ "div",
4013
+ { class: "min-w-0" },
4014
+ h("div", { class: "text-ui-sm font-medium text-foreground" }, L.pluginInstallCardTitle.replaceAll("{name}", pluginName)),
4015
+ h("div", { class: "mt-0.5 text-ui-xs/relaxed text-foreground-subtle" }, L.pluginInstallCardDesc),
4016
+ installState
4017
+ ),
4018
+ installBtn
4019
+ );
4020
+ };
3578
4021
  const renderVision = () => {
4022
+ if (!visionRes.ok) {
4023
+ paneVision.replaceChildren(
4024
+ h("p", { class: "text-ui-sm/relaxed text-foreground-subtle" }, L.visionDesc),
4025
+ h("p", { class: "mt-2 text-ui-sm text-destructive" }, L.visionLoadFailed + ": " + errText(visionRes))
4026
+ );
4027
+ return;
4028
+ }
4029
+ if (!visionRes.hook) {
4030
+ paneVision.replaceChildren(
4031
+ h("p", { class: "text-ui-sm/relaxed text-foreground-subtle" }, L.visionDesc),
4032
+ pluginInstallCard("zcode-vision", async () => {
4033
+ const meta = await rpc("/vision");
4034
+ if (meta.ok) Object.assign(visionRes, meta);
4035
+ renderVision();
4036
+ })
4037
+ );
4038
+ return;
4039
+ }
3579
4040
  if (!visionCfg) {
3580
4041
  const initBtn = btnSmall(L.visionAddProxy, async () => {
3581
4042
  const res = await rpc("/vision", { method: "POST", body: { config: VISION_DEFAULT_CFG } });
@@ -3587,6 +4048,16 @@
3587
4048
  paneVision.replaceChildren(
3588
4049
  h("p", { class: "text-ui-sm/relaxed text-foreground-subtle" }, L.visionDesc),
3589
4050
  ...visionRes.ok ? [] : [h("p", { class: "mt-2 text-ui-sm text-destructive" }, L.visionLoadFailed + ": " + errText(visionRes))],
4051
+ // 信息卡(仅版本行;配置未建,启用开关随配置出现)
4052
+ h(
4053
+ "div",
4054
+ { class: "mt-3 rounded-xl border border-border p-2" },
4055
+ h(
4056
+ "div",
4057
+ { class: "flex flex-wrap items-center gap-2" },
4058
+ h("span", { class: "shrink-0 whitespace-nowrap text-ui-xs/relaxed text-foreground-subtle" }, `\u63D2\u4EF6 ${visionRes.pluginVersion || "\u2014"}`)
4059
+ )
4060
+ ),
3590
4061
  h("div", { class: "mt-3 flex justify-end" }, initBtn)
3591
4062
  );
3592
4063
  return;
@@ -3759,7 +4230,26 @@
3759
4230
  });
3760
4231
  paneVision.replaceChildren(
3761
4232
  h("p", { class: "text-ui-sm/relaxed text-foreground-subtle" }, L.visionDesc),
3762
- // 链模式 + 两个开关合到一行(顶部已有简介,开关不再单占一块、不带描述)
4233
+ // 信息卡:插件版本 + 启用图片识别开关(与 rtk/headroom 面板同款式)
4234
+ h(
4235
+ "div",
4236
+ { class: "mt-3 rounded-xl border border-border p-2" },
4237
+ h(
4238
+ "div",
4239
+ { class: "flex flex-wrap items-center gap-2" },
4240
+ h("span", { class: "shrink-0 whitespace-nowrap text-ui-xs/relaxed text-foreground-subtle" }, `\u63D2\u4EF6 ${visionRes.pluginVersion || "\u2014"}`),
4241
+ h(
4242
+ "div",
4243
+ { class: "ml-auto" },
4244
+ settingRow(L.visionEnabled, "", visionCfg.enabled !== false, async () => {
4245
+ visionCfg.enabled = !(visionCfg.enabled !== false);
4246
+ await persistVision();
4247
+ renderVision();
4248
+ })
4249
+ )
4250
+ )
4251
+ ),
4252
+ // 链模式 + 强制拦截开关合到一行(启用开关已上移信息卡)
3763
4253
  h(
3764
4254
  "div",
3765
4255
  { class: "mt-3 flex flex-wrap items-center gap-x-4 gap-y-1.5 rounded-xl border border-border p-1" },
@@ -3769,11 +4259,6 @@
3769
4259
  h(
3770
4260
  "div",
3771
4261
  { class: "ml-auto flex items-center gap-4" },
3772
- settingRow(L.visionEnabled, "", visionCfg.enabled !== false, async () => {
3773
- visionCfg.enabled = !(visionCfg.enabled !== false);
3774
- await persistVision();
3775
- renderVision();
3776
- }),
3777
4262
  settingRow(L.visionForceIntercept, "", visionCfg.forceIntercept !== false, async () => {
3778
4263
  visionCfg.forceIntercept = !(visionCfg.forceIntercept !== false);
3779
4264
  await persistVision();
@@ -3855,13 +4340,16 @@
3855
4340
  };
3856
4341
  const rtkInputCls = "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";
3857
4342
  const rtkVersionLine = h("span", { class: "shrink-0 whitespace-nowrap text-ui-xs/relaxed text-foreground-subtle" });
4343
+ let rtkLocText = "";
3858
4344
  const renderRtkVersion = (meta) => {
3859
- const v = meta.version || "rtk";
3860
- rtkVersionLine.textContent = meta.binPath ? `${v} \xB7 ${meta.binPath}` : v;
4345
+ const v = String(meta.version || "").replace(/^rtk\s+/i, "").trim();
4346
+ rtkVersionLine.textContent = `\u63D2\u4EF6 ${meta.pluginVersion || "\u2014"}${v ? ` \xB7 rtk ${v}` : ""}`;
4347
+ rtkLocText = meta.binPath || "";
3861
4348
  };
3862
4349
  renderRtkVersion(rtkRes);
3863
4350
  const rtkUp = makeUpgradeControls({
3864
4351
  endpoint: "/rtk",
4352
+ idleText: () => rtkLocText,
3865
4353
  metaRefresh: async () => {
3866
4354
  const meta = await rpc("/rtk");
3867
4355
  if (meta.ok) {
@@ -3874,19 +4362,49 @@
3874
4362
  const rtkSwitchRow = h("div");
3875
4363
  const rtkVersionCard = h(
3876
4364
  "div",
3877
- { class: "mt-3 flex items-center gap-2 rounded-xl border border-border p-2" },
4365
+ { class: "mt-3 flex flex-wrap items-center gap-2 rounded-xl border border-border p-2" },
3878
4366
  rtkVersionLine,
3879
4367
  rtkUp.state,
3880
4368
  rtkUp.btn,
3881
- rtkSwitchRow
4369
+ h("div", { class: "ml-auto shrink-0" }, rtkSwitchRow)
3882
4370
  );
3883
4371
  const rtkBody = h("div");
4372
+ const rtkPluginRow = h("div");
4373
+ const renderRtkPluginRow = () => {
4374
+ rtkPluginRow.replaceChildren(...rtkRes.hook ? [] : [
4375
+ pluginInstallCard("rtk", async () => {
4376
+ const meta = await rpc("/rtk");
4377
+ if (meta.ok) {
4378
+ Object.assign(rtkRes, meta);
4379
+ renderRtkVersion(meta);
4380
+ rtkCfg = {
4381
+ installed: meta.installed !== false,
4382
+ version: meta.version || "",
4383
+ binPath: meta.binPath || "",
4384
+ mode: meta.mode === "off" ? "off" : "hint",
4385
+ whitelist: Array.isArray(meta.whitelist) ? [...meta.whitelist] : [],
4386
+ builtinGit: Array.isArray(meta.builtinGit) ? [...meta.builtinGit] : [],
4387
+ builtinPlain: Array.isArray(meta.builtinPlain) ? [...meta.builtinPlain] : []
4388
+ };
4389
+ }
4390
+ renderRtkPluginRow();
4391
+ renderRtk();
4392
+ })
4393
+ ]);
4394
+ };
4395
+ renderRtkPluginRow();
3884
4396
  paneRtk.append(
3885
4397
  h("p", { class: "text-ui-sm/relaxed text-foreground-subtle" }, L.rtkDesc),
4398
+ rtkPluginRow,
3886
4399
  rtkVersionCard,
3887
4400
  rtkBody
3888
4401
  );
3889
4402
  const renderRtk = () => {
4403
+ if (rtkRes.ok && !rtkRes.hook) {
4404
+ rtkVersionCard.style.display = "none";
4405
+ rtkBody.replaceChildren();
4406
+ return;
4407
+ }
3890
4408
  rtkVersionCard.style.display = rtkCfg && rtkCfg.installed ? "" : "none";
3891
4409
  if (!rtkCfg) {
3892
4410
  rtkBody.replaceChildren(
@@ -3932,7 +4450,55 @@
3932
4450
  })
3933
4451
  );
3934
4452
  rtkBody.replaceChildren(
3935
- ...rtkCfg.installed ? [] : [h("p", { class: "text-ui-sm text-amber-500" }, L.rtkNotInstalled)],
4453
+ ...rtkCfg.installed ? [] : [h(
4454
+ "div",
4455
+ { class: "flex items-center gap-2" },
4456
+ h("p", { class: "min-w-0 flex-1 text-ui-sm text-amber-500" }, L.rtkNotInstalled),
4457
+ // 一键安装本体:与“检查更新”同一条 helper 升级管线(未装则装到 ~/.local/bin)
4458
+ (() => {
4459
+ const b = btnSmall(L.rtkInstallBin, () => {
4460
+ void run();
4461
+ }, "shrink-0");
4462
+ const run = async () => {
4463
+ b.disabled = true;
4464
+ b.textContent = L.headroomInstalling;
4465
+ const res = await rpc("/rtk", { method: "POST", body: { upgrade: true } });
4466
+ if (!res.ok) {
4467
+ b.disabled = false;
4468
+ b.textContent = L.rtkInstallBin;
4469
+ showToast(L.failed + ": " + errText(res), "error");
4470
+ return;
4471
+ }
4472
+ const done = await new Promise((resolve) => {
4473
+ const iv = setInterval(async () => {
4474
+ const s = await rpc("/rtk/upgrade");
4475
+ if (s.ok && s.upgrade && !s.upgrade.running) {
4476
+ clearInterval(iv);
4477
+ resolve(s.upgrade);
4478
+ }
4479
+ }, 1200);
4480
+ setTimeout(() => {
4481
+ clearInterval(iv);
4482
+ resolve(null);
4483
+ }, 10 * 60 * 1e3);
4484
+ });
4485
+ if (done && !done.error && done.version) {
4486
+ const meta = await rpc("/rtk");
4487
+ if (meta.ok) {
4488
+ Object.assign(rtkRes, meta);
4489
+ rtkCfg = { ...rtkCfg, installed: meta.installed !== false, version: meta.version || "", binPath: meta.binPath || "" };
4490
+ }
4491
+ showToast(L.rtkBinInstalled, "success");
4492
+ renderRtk();
4493
+ } else {
4494
+ b.disabled = false;
4495
+ b.textContent = L.rtkInstallBin;
4496
+ showToast(done && done.error || L.headroomInstallRetry, "error");
4497
+ }
4498
+ };
4499
+ return b;
4500
+ })()
4501
+ )],
3936
4502
  h(
3937
4503
  "div",
3938
4504
  { class: "mt-2 rounded-xl border border-border p-2" },
@@ -3989,15 +4555,16 @@
3989
4555
  };
3990
4556
  renderRtk();
3991
4557
  const hrVersionLine = h("span", { class: "shrink-0 whitespace-nowrap text-ui-xs/relaxed text-foreground-subtle" });
4558
+ let hrLocText = "";
4559
+ const hrSwitchRow = h("div");
3992
4560
  const renderHrVersion = (meta) => {
3993
- const parts = [];
3994
- if (meta.pluginVersion) parts.push(`${L.headroomVersionPlugin} ${meta.pluginVersion}`);
3995
- if (meta.version) parts.push(`${L.headroomVersionBin} ${meta.version}`);
3996
- hrVersionLine.textContent = parts.length ? parts.join(" \xB7 ") : `${L.headroomVersionPlugin} \u2014`;
4561
+ hrVersionLine.textContent = `\u63D2\u4EF6 ${meta.pluginVersion || "\u2014"}${meta.version ? ` \xB7 headroom ${meta.version}` : ""}`;
4562
+ hrLocText = meta.binPath || "";
3997
4563
  };
3998
4564
  renderHrVersion(headroomRes);
3999
4565
  const hrUp = makeUpgradeControls({
4000
4566
  endpoint: "/headroom",
4567
+ idleText: () => hrLocText,
4001
4568
  metaRefresh: async () => {
4002
4569
  const meta = await rpc("/headroom");
4003
4570
  if (meta.ok) {
@@ -4025,7 +4592,7 @@
4025
4592
  installBtn.disabled = true;
4026
4593
  installBtn.textContent = L.headroomInstalling;
4027
4594
  installState.textContent = "";
4028
- const res = await rpc("/plugins/update", { method: "POST", body: { name: "headroom", installMissing: true } });
4595
+ const res = await rpc("/plugins/update", { method: "POST", body: { name: "headroom" } });
4029
4596
  if (!res.ok) {
4030
4597
  installBtn.disabled = false;
4031
4598
  installBtn.textContent = L.headroomInstall;
@@ -4036,6 +4603,7 @@
4036
4603
  if (meta.ok) Object.assign(headroomRes, meta);
4037
4604
  if (headroomRes.hook) {
4038
4605
  showToast(L.headroomInstalled, "success");
4606
+ renderHrVersion(headroomRes);
4039
4607
  renderHeadroomPane();
4040
4608
  } else {
4041
4609
  installBtn.disabled = false;
@@ -4065,17 +4633,60 @@
4065
4633
  const hrRun = async (reqBody, warn) => {
4066
4634
  if (hrBusy) return false;
4067
4635
  hrBusy = true;
4068
- const res = await rpc("/headroom", { method: "POST", body: reqBody });
4069
- hrBusy = false;
4636
+ const action = reqBody && reqBody.action;
4637
+ const isStart = action === "start" || action === "restart";
4638
+ const isStop = action === "stop";
4639
+ const rpcDone = rpc("/headroom", { method: "POST", body: reqBody });
4640
+ let timerIv = null;
4641
+ if (isStart || isStop) {
4642
+ const wantUp = isStart;
4643
+ const msg = action === "restart" ? L.hrRestarting : isStart ? L.hrStarting : L.hrStopping;
4644
+ const doneMsg = isStart ? L.hrStarted : L.hrStopped;
4645
+ const timeoutMs = isStart ? 90 * 1e3 : 30 * 1e3;
4646
+ const t0 = Date.now();
4647
+ const timerSpan = h("span", { class: "text-ui-sm text-foreground-subtle/70" }, msg + " (0s)");
4648
+ hrStatusLine.replaceChildren(timerSpan);
4649
+ timerIv = setInterval(() => {
4650
+ timerSpan.textContent = msg + ` (${Math.round((Date.now() - t0) / 1e3)}s)`;
4651
+ }, 1e3);
4652
+ void (async () => {
4653
+ const kick = await rpcDone;
4654
+ if (!kick.ok) return;
4655
+ while (Date.now() - t0 < timeoutMs) {
4656
+ await new Promise((r) => setTimeout(r, 2e3));
4657
+ const st = await rpc("/headroom/status");
4658
+ if (!st.ok) continue;
4659
+ const up = !!(st.status && st.status.up);
4660
+ if (up === wantUp) {
4661
+ clearInterval(timerIv);
4662
+ hrBusy = false;
4663
+ await hrRefresh();
4664
+ showToast(doneMsg, isStart ? "success" : void 0);
4665
+ return;
4666
+ }
4667
+ }
4668
+ clearInterval(timerIv);
4669
+ hrBusy = false;
4670
+ if (isStart) showToast(L.hrStartTimeout, "error");
4671
+ void hrRefresh();
4672
+ })();
4673
+ }
4674
+ const res = await rpcDone;
4070
4675
  if (!res.ok) {
4676
+ if (timerIv) clearInterval(timerIv);
4677
+ hrBusy = false;
4071
4678
  showToast(L.failed + ": " + errText(res), "error");
4679
+ void hrRefresh();
4072
4680
  return false;
4073
4681
  }
4074
4682
  if (res.config) Object.assign(hrCfg, res.config);
4075
4683
  if (warn) showToast(warn);
4076
- setTimeout(() => {
4077
- void hrRefresh();
4078
- }, 1200);
4684
+ if (!isStart && !isStop) {
4685
+ hrBusy = false;
4686
+ setTimeout(() => {
4687
+ void hrRefresh();
4688
+ }, 1200);
4689
+ }
4079
4690
  return true;
4080
4691
  };
4081
4692
  const hrDevLabel = (v) => v == null ? "\u2014" : v === "auto" ? L.hrValAuto : v === "cpu" ? "CPU" : v;
@@ -4120,6 +4731,23 @@
4120
4731
  hrHintLine.textContent = s.saverHint || "";
4121
4732
  hrHintLine.style.display = s.saverHint ? "" : "none";
4122
4733
  };
4734
+ const renderHrUsageSwitch = () => {
4735
+ const usageOn = !!(headroomRes.usage && headroomRes.usage.enabled);
4736
+ hrSwitchRow.replaceChildren(
4737
+ settingRow(L.pluginEnable, "", usageOn, async () => {
4738
+ const next = !usageOn;
4739
+ const res = await rpc("/headroom", { method: "POST", body: { usage: next } });
4740
+ if (!res.ok) {
4741
+ showToast(L.failed + ": " + errText(res), "error");
4742
+ return;
4743
+ }
4744
+ if (res.usage) headroomRes.usage = res.usage;
4745
+ renderHrUsageSwitch();
4746
+ if (next && !res.changed && !(res.usage && res.usage.enabled)) showToast(L.hrUsageNone);
4747
+ })
4748
+ );
4749
+ };
4750
+ renderHrUsageSwitch();
4123
4751
  const hrRefreshBtn = btnSmall(L.headroomRefresh, () => {
4124
4752
  void hrRefresh();
4125
4753
  });
@@ -4212,14 +4840,69 @@
4212
4840
  hrPwrHint.style.display = hrCfg.kompressBackend === "cpu" ? "" : "none";
4213
4841
  };
4214
4842
  updateHrPwrHint();
4843
+ const hrInstallBinBtn = (() => {
4844
+ const b = btnSmall(L.hrInstallBin, () => {
4845
+ void run();
4846
+ }, "shrink-0");
4847
+ let busy = false;
4848
+ const run = async () => {
4849
+ if (busy) return;
4850
+ busy = true;
4851
+ b.disabled = true;
4852
+ b.textContent = L.headroomInstalling;
4853
+ const res = await rpc("/headroom", { method: "POST", body: { upgrade: true } });
4854
+ if (!res.ok) {
4855
+ b.disabled = false;
4856
+ b.textContent = L.hrInstallBin;
4857
+ showToast(L.failed + ": " + errText(res), "error");
4858
+ busy = false;
4859
+ return;
4860
+ }
4861
+ const done = await new Promise((resolve) => {
4862
+ const iv = setInterval(async () => {
4863
+ const s = await rpc("/headroom/upgrade");
4864
+ if (s.ok && s.upgrade && !s.upgrade.running) {
4865
+ clearInterval(iv);
4866
+ resolve(s.upgrade);
4867
+ }
4868
+ }, 2e3);
4869
+ setTimeout(() => {
4870
+ clearInterval(iv);
4871
+ resolve(null);
4872
+ }, 12 * 60 * 1e3);
4873
+ });
4874
+ busy = false;
4875
+ if (done && !done.error && done.version) {
4876
+ const meta = await rpc("/headroom");
4877
+ if (meta.ok) {
4878
+ renderHrVersion(meta);
4879
+ Object.assign(headroomRes, meta);
4880
+ }
4881
+ showToast(L.hrBinInstalled, "success");
4882
+ renderHeadroomPane();
4883
+ } else {
4884
+ b.disabled = false;
4885
+ b.textContent = L.hrInstallBin;
4886
+ showToast(done && done.error || L.headroomInstallRetry, "error");
4887
+ }
4888
+ };
4889
+ return b;
4890
+ })();
4215
4891
  paneHeadroom.append(
4216
4892
  h("p", { class: "text-ui-sm/relaxed text-foreground-subtle" }, L.headroomDesc),
4217
- ...headroomRes.installed === false ? [h("p", { class: "mt-2 text-ui-sm text-amber-500" }, L.headroomBinMissing)] : [],
4893
+ ...headroomRes.installed === false ? [h(
4894
+ "div",
4895
+ { class: "mt-2 flex items-center gap-2" },
4896
+ h("p", { class: "min-w-0 flex-1 text-ui-sm text-amber-500" }, L.headroomBinMissing),
4897
+ hrInstallBinBtn
4898
+ )] : [],
4218
4899
  h(
4219
4900
  "div",
4220
- { class: "mt-3 flex items-center justify-between gap-2 rounded-xl border border-border p-2" },
4221
- h("div", { class: "flex min-w-0 items-center gap-2" }, hrVersionLine, hrUp.state),
4222
- hrUp.btn
4901
+ { class: "mt-3 flex flex-wrap items-center gap-2 rounded-xl border border-border p-2" },
4902
+ hrVersionLine,
4903
+ hrUp.state,
4904
+ hrUp.btn,
4905
+ h("div", { class: "ml-auto shrink-0" }, hrSwitchRow)
4223
4906
  ),
4224
4907
  h(
4225
4908
  "div",
@@ -4273,7 +4956,8 @@
4273
4956
  paneSync,
4274
4957
  paneVision,
4275
4958
  paneHeadroom,
4276
- paneRtk
4959
+ paneRtk,
4960
+ paneDebug
4277
4961
  );
4278
4962
  }
4279
4963
  });
@@ -5736,7 +6420,7 @@
5736
6420
  if (status.updates.length === 0) return;
5737
6421
  const config = await getConfig();
5738
6422
  if ((config.features || {}).autoUpdatePlugins === true) {
5739
- const res = await rpc("/plugins/update", { method: "POST", body: { installMissing: true } });
6423
+ const res = await rpc("/plugins/update", { method: "POST", body: {} });
5740
6424
  if (res.ok) showToast(L.pluginsAutoUpdated, "success");
5741
6425
  else showToast(L.pluginsUpdateFailed + ": " + (res.error || ""), "error");
5742
6426
  return;
@@ -5758,8 +6442,14 @@
5758
6442
  // src/inject/index.js
5759
6443
  (function zcodeproInject() {
5760
6444
  if (typeof window === "undefined") return;
5761
- if (window.__zcodeproInjected) return;
6445
+ if (window.__zcodeproInjected) {
6446
+ const boot = window.__ZCODEPRO__ || {};
6447
+ const sameGeneration = window.__zcodeproBootedToken && boot.token && window.__zcodeproBootedToken === boot.token;
6448
+ if (!sameGeneration) location.reload();
6449
+ return;
6450
+ }
5762
6451
  window.__zcodeproInjected = true;
6452
+ window.__zcodeproBootedToken = (window.__ZCODEPRO__ || {}).token || "";
5763
6453
  try {
5764
6454
  startLocaleResolver();
5765
6455
  } catch {