zcode-pro 0.20.3 → 1.1.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
@@ -30,6 +30,47 @@
30
30
  function clearConfigCache() {
31
31
  configCache = { value: null, at: 0 };
32
32
  }
33
+ var DEFAULT_SHORTCUTS = {
34
+ switchLast: "z",
35
+ // 立即切到上次会话
36
+ switchPrev: "x",
37
+ // 弹窗中上移
38
+ switchNext: "c",
39
+ // 弹窗中下移
40
+ switchRecent: "e",
41
+ // 打开最近 10 条
42
+ switchAttention: "r"
43
+ // 打开运行中 / 未查看
44
+ };
45
+ function zcodeAppVersion() {
46
+ const v = liveBoot().zcodeVersion;
47
+ return typeof v === "string" && v ? v : "";
48
+ }
49
+ function zcodeVersionGte(version, major, minor, patch = 0) {
50
+ const m = String(version || "").match(/^(\d+)\.(\d+)\.(\d+)/);
51
+ if (!m) return false;
52
+ const a = Number(m[1]), b = Number(m[2]), c = Number(m[3]);
53
+ return a > major || a === major && (b > minor || b === minor && c >= patch);
54
+ }
55
+ function officialRenameSupported() {
56
+ return zcodeVersionGte(zcodeAppVersion(), 3, 16, 1);
57
+ }
58
+ var newSidebarCached = false;
59
+ function isNewProjectSidebar() {
60
+ if (newSidebarCached) return true;
61
+ if (officialRenameSupported()) {
62
+ newSidebarCached = true;
63
+ return true;
64
+ }
65
+ try {
66
+ if (document.querySelector("section[data-project-id]")) {
67
+ newSidebarCached = true;
68
+ return true;
69
+ }
70
+ } catch {
71
+ }
72
+ return false;
73
+ }
33
74
  var zh = {
34
75
  settingsTitle: "ZCode Pro",
35
76
  tabFeatures: "\u529F\u80FD",
@@ -76,7 +117,7 @@
76
117
  syncRecLink: "\u575A\u679C\u4E91 WebDAV",
77
118
  syncRecSuffix: "\uFF1A\u767B\u5F55\u4E0E\u5BC6\u7801\u586B\u201C\u5E94\u7528\u5BC6\u7801\u201D\uFF0C\u4E0D\u662F\u8D26\u53F7\u5BC6\u7801\u3002",
78
119
  syncIncludeZcp: "ZCode Pro \u8BBE\u7F6E",
79
- syncIncludeZcpDesc: "\u529F\u80FD\u5F00\u5173\u3001\u6837\u5F0F\u8C03\u6574\u3001\u9879\u76EE\u522B\u540D\u4E0E\u4EE3\u7406\u8BBE\u7F6E\uFF1B\u540C\u6B65\u914D\u7F6E\u4E0E\u51ED\u636E\u672C\u8EAB\u4E0D\u53C2\u4E0E\u540C\u6B65\u3002",
120
+ syncIncludeZcpDesc: "\u529F\u80FD\u5F00\u5173\u3001\u6837\u5F0F\u8C03\u6574\u4E0E\u4EE3\u7406\u8BBE\u7F6E\uFF1B\u540C\u6B65\u914D\u7F6E\u4E0E\u51ED\u636E\u672C\u8EAB\u4E0D\u53C2\u4E0E\u540C\u6B65\u3002\u9879\u76EE\u522B\u540D\u4E0D\u540C\u6B65\uFF08\u5B98\u65B9\u5DF2\u652F\u6301\u6539\u9879\u76EE\u540D\uFF09\u3002",
80
121
  syncIncludeModel: "\u6A21\u578B\u8BBE\u7F6E",
81
122
  syncIncludeModelDesc: "\u81EA\u5B9A\u4E49\u4F9B\u5E94\u5546\u4E0E\u6A21\u578B\u914D\u7F6E\uFF08config.json \u4E0E provider_config.json\uFF0C\u542B API Key\uFF09\u3002\u8D26\u53F7\u767B\u5F55\u7684\u51ED\u636E\uFF08\u5982 BigModel \u8BA2\u9605\uFF09\u4E0D\u53C2\u4E0E\u540C\u6B65\uFF0C\u5404\u673A\u5668\u7528\u5404\u81EA\u8D26\u53F7\u767B\u5F55\u3002",
82
123
  syncUpload: "\u4E0A\u4F20\u5230\u4E91\u7AEF",
@@ -149,7 +190,7 @@
149
190
  featureTaskOrder: "\u4FA7\u8FB9\u680F\u4F1A\u8BDD\u62D6\u52A8\u6392\u5E8F",
150
191
  featureTaskOrderDesc: "\u8BA9\u7F6E\u9876\u3001\u9879\u76EE\u4E0E\u5206\u7EC4\u4E2D\u7684\u4F1A\u8BDD\u62D6\u52A8\u540E\u8BB0\u4F4F\u987A\u5E8F\uFF0C\u5237\u65B0\u540E\u4FDD\u6301\u3002",
151
192
  featureSessionSwitch: "\u4F1A\u8BDD\u5FEB\u6377\u5207\u6362",
152
- featureSessionSwitchDesc: "alt+z \u5728\u5F53\u524D\u4E0E\u4E0A\u6B21\u4F1A\u8BDD\u95F4\u6765\u56DE\u5207\u6362\uFF1B\u6309\u4F4F alt \u518D\u6309 x/c \u5F39\u51FA\u6700\u8FD1\u4F1A\u8BDD\u5217\u8868\u524D\u540E\u9009\u62E9\uFF0C\u677E\u5F00 alt \u5207\u6362\uFF08\u7C7B alt+tab\uFF09\u3002",
193
+ featureSessionSwitchDesc: "{last} \u5728\u5F53\u524D\u4E0E\u4E0A\u6B21\u4F1A\u8BDD\u95F4\u6765\u56DE\u5207\u6362\uFF1B\u6309\u4F4F alt \u518D\u6309 {prev}/{next} \u5F39\u51FA\u6700\u8FD1\u4F1A\u8BDD\u5217\u8868\u524D\u540E\u9009\u62E9\uFF08\u521D\u59CB 10 \u6761\uFF0C{next} \u5230\u5E95\u518D\u52A0\u8F7D 5 \u6761\uFF09\uFF0C{recent} \u76F4\u63A5\u6253\u5F00\u6700\u8FD1 10 \u6761\uFF0C{attention} \u6253\u5F00\u8FD0\u884C\u4E2D\u4E0E\u8FD0\u884C\u5B8C\u672A\u67E5\u770B\u7684\u4EFB\u52A1\uFF1B\u65B9\u5411\u952E\u540C\u6837\u53EF\u9009\uFF0CEnter \u6216\u677E\u5F00 alt \u5207\u6362\uFF08\u7C7B alt+tab\uFF09",
153
194
  featureTitleLock: "\u91CD\u547D\u540D\u4F1A\u8BDD\u540D\u9501\u5B9A",
154
195
  featureTitleLockDesc: "\u624B\u52A8\u91CD\u547D\u540D\u8FC7\u7684\u4F1A\u8BDD\u4E0D\u518D\u88AB\u81EA\u52A8\u6807\u9898\u8986\u76D6\uFF1A\u4F1A\u8BDD\u6709\u65B0\u56DE\u7B54\u540E\u754C\u9762\u53EF\u80FD\u6539\u663E\u81EA\u52A8\u6807\u9898\uFF0C\u91CD\u542F\u540E\u53C8\u53D8\u56DE\uFF0C\u672C\u529F\u80FD\u8BA9\u4FA7\u680F\u59CB\u7EC8\u663E\u793A\u91CD\u547D\u540D\u540E\u7684\u540D\u5B57\u3002",
155
196
  tabTitleGen: "\u4EFB\u52A1\u540D\u63D0\u793A\u8BCD",
@@ -176,8 +217,8 @@
176
217
  titleGenTesting: "\u751F\u6210\u4E2D\u2026",
177
218
  titleGenTestOk: "\u751F\u6210\u7ED3\u679C\uFF1A",
178
219
  titleGenTestFailed: "\u751F\u6210\u5931\u8D25",
179
- featureWsRunning: "\u6298\u53E0\u9879\u76EE\u8FD0\u884C\u63D0\u793A\uFF08\u5B9E\u9A8C\u6027\uFF09",
180
- featureWsRunningDesc: "\u6298\u53E0\u7684\u9879\u76EE\u91CC\u4ECD\u6709\u4F1A\u8BDD\u5728\u8FD0\u884C\u65F6\uFF0C\u9879\u76EE\u56FE\u6807\u65CB\u8F6C\u63D0\u793A\uFF1B\u5168\u90E8\u7ED3\u675F\u6216\u5C55\u5F00\u9879\u76EE\u540E\u6062\u590D\u3002\u671F\u95F4\u6BCF 60 \u79D2\u5728\u540E\u53F0\u77ED\u6682\u5C55\u5F00\u8BE5\u9879\u76EE\u590D\u6838\u8FD0\u884C\u72B6\u6001\u518D\u6536\u8D77\uFF0C\u4F1A\u8BDD\u884C\u4E0D\u663E\u793A\uFF0C\u4EC5\u9879\u76EE\u4F4D\u7F6E\u53EF\u80FD\u6709\u7EA6\u56DB\u5206\u4E4B\u4E00\u79D2\u7684\u8F7B\u5FAE\u9AD8\u5EA6\u8D77\u4F0F\u3002",
220
+ featureWsRunning: "\u6298\u53E0\u65F6\u4EFB\u52A1\u8FD0\u884C\u4E2D\u63D0\u793A",
221
+ featureWsRunningDesc: "\u6709\u4F1A\u8BDD\u5728\u8FD0\u884C\u65F6\uFF0C\u6298\u53E0\u7684\u9879\u76EE\u4E0E\u201C\u4EFB\u52A1\u201D\u201C\u5DF2\u7F6E\u9876\u201D\u7B49\u5206\u533A\u5934\u663E\u793A\u4E0E\u4EFB\u52A1\u8FD0\u884C\u4E2D\u540C\u6B3E\u7684\u52A0\u8F7D\u56FE\u6807\uFF0C\u5168\u90E8\u7ED3\u675F\u540E\u6D88\u5931\u3002",
181
222
  tabPerformance: "\u6027\u80FD",
182
223
  perfDesc: "ZCode \u4E3A\u6BCF\u4E2A\u6253\u5F00\u8FC7\u7684\u9879\u76EE\u5E38\u9A7B\u4E00\u4E2A\u4F1A\u8BDD\u8FDB\u7A0B\uFF0C\u5E94\u7528\u81EA\u8EAB\u4E0D\u4F1A\u56DE\u6536\uFF0C\u9879\u76EE\u591A\u4E86\u5185\u5B58\u5360\u7528\u5C31\u9AD8\u3002\u5F00\u542F\u540E\uFF0C\u957F\u65F6\u95F4\u672A\u4F7F\u7528\u4E14\u6CA1\u6709\u4EFB\u52A1\u5728\u8DD1\u7684\u9879\u76EE\u4F1A\u88AB\u81EA\u52A8\u91CA\u653E\uFF1A\u4F1A\u8BDD\u8BB0\u5F55\u4E0D\u53D7\u5F71\u54CD\uFF0C\u518D\u6B21\u6253\u5F00\u65F6\u81EA\u52A8\u6062\u590D\uFF0C\u4EC5\u9996\u6B21\u56DE\u590D\u6709\u51E0\u79D2\u7B49\u5F85\u3002\u6B63\u5728\u4F7F\u7528\u7684\u9879\u76EE\u3001\u8FD0\u884C\u4E2D\u7684\u4F1A\u8BDD\u4E0E\u540E\u53F0\u547D\u4EE4\u3001\u5B9A\u65F6\u4EFB\u52A1\u90FD\u4E0D\u4F1A\u88AB\u56DE\u6536\u3002\u540E\u53F0\u547D\u4EE4\u68C0\u6D4B\u4F9D\u8D56\u7CFB\u7EDF\u8FDB\u7A0B\u4FE1\u606F\uFF08Linux / macOS / Windows \u5404\u8D70\u7CFB\u7EDF\u81EA\u5E26\u5DE5\u5177\uFF09\uFF0C\u68C0\u6D4B\u4E0D\u53EF\u7528\u65F6\u529F\u80FD\u81EA\u52A8\u505C\u7528\u3002",
183
224
  featureIdleReclaim: "\u7A7A\u95F2\u9879\u76EE\u5185\u5B58\u56DE\u6536",
@@ -205,10 +246,40 @@
205
246
  userFontDesc: "\u4F1A\u8BDD\u4E2D\u63D0\u95EE\u5185\u5BB9\u7684\u5B57\u4F53\uFF1B\u9009\u201C\u9ED8\u8BA4\u201D\u65F6\u8DDF\u968F\u754C\u9762\u5B57\u4F53\u3002",
206
247
  assistantFontName: "\u56DE\u7B54\u5B57\u4F53",
207
248
  assistantFontDesc: "\u4F1A\u8BDD\u4E2D\u56DE\u7B54\u6B63\u6587\u7684\u5B57\u4F53\uFF08\u4EE3\u7801\u5757\u4ECD\u7528\u7B49\u5BBD\u5B57\u4F53\uFF09\uFF1B\u9009\u201C\u9ED8\u8BA4\u201D\u65F6\u8DDF\u968F\u754C\u9762\u5B57\u4F53\u3002",
208
- switcherHint: "x / c \u9009\u62E9\uFF0C\u677E\u5F00 alt \u5207\u6362\uFF0CEsc \u53D6\u6D88",
249
+ switcherHint: "{prev} / {next} \u6216 \u2191 / \u2193 \u9009\u62E9\uFF0CEnter \u6216\u677E\u5F00 alt \u5207\u6362\uFF0CEsc \u53D6\u6D88",
209
250
  switcherCurrent: "\u5F53\u524D",
210
251
  switcherEmpty: "\u6682\u65E0\u4E0A\u6B21\u4F1A\u8BDD\uFF08\u5207\u6362\u8FC7\u4F1A\u8BDD\u540E\u53EF\u7528\uFF09",
211
252
  switcherFailed: "\u672A\u80FD\u5207\u6362\u5230\u8BE5\u4F1A\u8BDD\uFF08\u53EF\u80FD\u5DF2\u5220\u9664\uFF09",
253
+ switcherMore: "\u8FD8\u6709 {n} \u6761 \xB7 \u6309 c \u52A0\u8F7D",
254
+ switcherRunning: "\u8FD0\u884C\u4E2D",
255
+ switcherUnseen: "\u672A\u67E5\u770B",
256
+ switcherAttentionHint: "\u8FD0\u884C\u4E2D / \u8FD0\u884C\u5B8C\u672A\u67E5\u770B\u7684\u4EFB\u52A1 \xB7 {prev} / {next} \u6216 \u2191 / \u2193 \u9009\u62E9\uFF0CEnter \u6216\u677E\u5F00 alt \u5207\u6362",
257
+ switcherAttentionEmpty: "\u6682\u65E0\u8FD0\u884C\u4E2D\u6216\u672A\u67E5\u770B\u7684\u4EFB\u52A1",
258
+ tabShortcuts: "\u5FEB\u6377\u952E",
259
+ hkTitle: "\u4F1A\u8BDD\u5FEB\u6377\u5207\u6362",
260
+ hkAltZ: "\u5728\u5F53\u524D\u4E0E\u4E0A\u6B21\u4F7F\u7528\u7684\u4F1A\u8BDD\u95F4\u6765\u56DE\u5207\u6362",
261
+ hkAltX: "\u5F39\u7A97\u4E2D\u4E0A\u79FB\u9AD8\u4EAE\uFF08\u66F4\u65E9\u7684\u4F1A\u8BDD\uFF09",
262
+ hkAltC: "\u5F39\u7A97\u4E2D\u4E0B\u79FB\u9AD8\u4EAE\uFF08\u66F4\u65B0\u7684\u4F1A\u8BDD\uFF09\uFF1B\u5230\u5E95\u4E14\u8FD8\u6709\u66F4\u591A\u65F6\u52A0\u8F7D 5 \u6761\u5E76\u9009\u4E2D\u65B0\u52A0\u8F7D\u7684\u7B2C\u4E00\u6761",
263
+ hkAltE: "\u6253\u5F00\u6700\u8FD1 10 \u6761\u4EFB\u52A1\u5217\u8868",
264
+ hkAltR: "\u6253\u5F00\u6B63\u5728\u8FD0\u884C\u4E0E\u8FD0\u884C\u5B8C\u672A\u67E5\u770B\u7684\u4EFB\u52A1\u5217\u8868",
265
+ hkArrow: "\u5F39\u7A97\u6253\u5F00\u65F6\u79FB\u52A8\u9AD8\u4EAE\uFF08\u4E0E alt + {prev} / alt + {next} \u540C\u89C4\u5219\uFF09",
266
+ hkEnter: "\u786E\u8BA4\u5207\u6362\u5230\u9AD8\u4EAE\u7684\u4F1A\u8BDD",
267
+ hkEsc: "\u53D6\u6D88\u5E76\u5173\u95ED\u5F39\u7A97",
268
+ hkRelease: "\u79FB\u52A8\u8FC7\u9AD8\u4EAE\u65F6\u677E\u5F00\u5373\u5207\u6362\uFF1B\u4ECE\u672A\u79FB\u52A8\u5219\u5F39\u7A97\u4FDD\u6301\u6253\u5F00\uFF0C\u7EE7\u7EED\u9009\u62E9",
269
+ hkReleaseKey: "\u677E\u5F00 alt",
270
+ hkModify: "\u4FEE\u6539",
271
+ hkRecording: "\u6309\u4E0B\u65B0\u952E\u2026",
272
+ hkInvalid: "\u5FEB\u6377\u952E\u53EA\u652F\u6301\u5355\u4E2A\u5B57\u6BCD\u6216\u6570\u5B57",
273
+ hkConflict: "alt+{k} \u5DF2\u7528\u4E8E\u201C{name}\u201D",
274
+ hkSaved: "\u5FEB\u6377\u952E\u5DF2\u66F4\u65B0",
275
+ hkNameLast: "\u5207\u6362\u4E0A\u6B21\u4F1A\u8BDD",
276
+ hkNamePrev: "\u4E0A\u79FB\u9AD8\u4EAE",
277
+ hkNameNext: "\u4E0B\u79FB\u9AD8\u4EAE",
278
+ hkNameRecent: "\u6700\u8FD1 10 \u6761",
279
+ hkNameAttention: "\u8FD0\u884C\u4E2D / \u672A\u67E5\u770B",
280
+ hkEditHint: "\u70B9\u51FB\u201C\u4FEE\u6539\u201D\u540E\u6309\u4E0B\u65B0\u6309\u952E\uFF08\u5B57\u6BCD\u6216\u6570\u5B57\uFF09\uFF0CEsc \u53D6\u6D88",
281
+ hkReset: "\u91CD\u7F6E\u4E3A\u9ED8\u8BA4",
282
+ hkResetDone: "\u5DF2\u6062\u590D\u9ED8\u8BA4\u952E\u4F4D",
212
283
  featureFileActions: "\u6587\u4EF6\u83DC\u5355\u589E\u5F3A",
213
284
  featureFileActionsDesc: "\u5728\u4F1A\u8BDD\u4E2D\u6587\u4EF6\u94FE\u63A5\u7684\u53F3\u952E\u83DC\u5355\u91CC\u65B0\u589E\u201C\u9ED8\u8BA4\u5E94\u7528\u6253\u5F00\u201D\u4E0E\u201C\u6253\u5F00\u6240\u5728\u76EE\u5F55\u201D\u3002",
214
285
  featureImageCopy: "\u56FE\u7247\u53F3\u952E\u590D\u5236",
@@ -217,17 +288,16 @@
217
288
  imageCopied: "\u56FE\u7247\u5DF2\u590D\u5236\u5230\u526A\u8D34\u677F",
218
289
  imageCopyFailed: "\u590D\u5236\u56FE\u7247\u5931\u8D25",
219
290
  openFolderItem: "\u6253\u5F00\u6587\u4EF6\u5939",
291
+ openFolderPrimary: "\u4E3B\u76EE\u5F55",
220
292
  copyPathItem: "\u590D\u5236\u8DEF\u5F84",
221
293
  pathCopied: "\u8DEF\u5F84\u5DF2\u590D\u5236\u5230\u526A\u8D34\u677F",
222
294
  pathCopyFailed: "\u590D\u5236\u8DEF\u5F84\u5931\u8D25",
223
295
  fileOpenDefault: "\u9ED8\u8BA4\u5E94\u7528\u6253\u5F00",
224
296
  fileReveal: "\u6253\u5F00\u6240\u5728\u76EE\u5F55",
225
- featurePinnedExpand: "\u7F6E\u9876\u4F1A\u8BDD\u4FDD\u6301\u9879\u76EE\u6298\u53E0\uFF08\u5B9E\u9A8C\u6027\uFF09",
226
- 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",
227
- featureAutoUpdatePlugins: "\u81EA\u52A8\u66F4\u65B0 zcode-plugins \u63D2\u4EF6",
228
- 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",
229
- pluginsUpdateTitle: "\u63D2\u4EF6\u66F4\u65B0 \xB7 zcode-plugins",
230
- pluginsCheckNow: "\u68C0\u67E5\u5E76\u66F4\u65B0",
297
+ featureAutoUpdatePlugins: "\u81EA\u52A8\u66F4\u65B0",
298
+ featureAutoUpdatePluginsRepo: "duanluan/zcode-plugins",
299
+ featureAutoUpdatePluginsDesc: "",
300
+ pluginsCheckNow: "\u68C0\u67E5\u66F4\u65B0",
231
301
  pluginsUpdating: "\u6B63\u5728\u66F4\u65B0\u63D2\u4EF6\u2026",
232
302
  pluginsUpToDate: "\u63D2\u4EF6\u5DF2\u662F\u6700\u65B0",
233
303
  pluginsUpdatesFound: "{n} \u4E2A\u63D2\u4EF6\u53EF\u66F4\u65B0",
@@ -305,6 +375,8 @@
305
375
  hrUsageNone: "\u5F53\u524D\u6CA1\u6709\u8D70 headroom \u7684\u4F9B\u5E94\u5546\uFF0C\u672A\u5207\u6362",
306
376
  headroomLoadFailed: "\u8BFB\u53D6 Headroom \u914D\u7F6E\u5931\u8D25",
307
377
  headroomStatusTitle: "\u8FD0\u884C\u72B6\u6001",
378
+ hrInstRunning: "\u8FD0\u884C\u4E2D",
379
+ hrInstStopped: "\u672A\u8FD0\u884C",
308
380
  headroomStatusLoadFailed: "\u8BFB\u53D6\u8FD0\u884C\u72B6\u6001\u5931\u8D25",
309
381
  headroomProxyUp: "\u4EE3\u7406\u8FD0\u884C\u4E2D",
310
382
  headroomProxyDown: "\u4EE3\u7406\u672A\u8FD0\u884C",
@@ -332,7 +404,7 @@
332
404
  headroomStoppedWarn: "\u4EE3\u7406\u5DF2\u505C\u6B62\uFF1B\u6307\u5411 127.0.0.1 \u7684\u4F9B\u5E94\u5546\u5C06\u65E0\u6CD5\u8FDE\u63A5",
333
405
  headroomBackend: "\u538B\u7F29\u8BBE\u5907",
334
406
  headroomBackendAuto: "\u81EA\u52A8 \xB7 \u663E\u5361\u4F18\u5148",
335
- headroomBackendCpu: "CPU \xB7 \u7701\u7535",
407
+ headroomBackendCpu: "\u7701\u7535 \xB7 CPU \u4F18\u5148",
336
408
  headroomBackendNative: "\uFF08\u539F\u751F\u503C\uFF09",
337
409
  headroomBackendDesc: "\u81EA\u52A8\uFF1Aheadroom \u9009\u6700\u5FEB\u8BBE\u5907\uFF08CUDA/MPS \u663E\u5361\u4F18\u5148\uFF09\uFF1BCPU\uFF1A\u5F3A\u5236 CPU \u538B\u7F29\uFF0C\u7701\u7535\u4E14\u4E0D\u5360\u663E\u5B58\u3002\u4E5F\u652F\u6301 headroom \u539F\u751F\u503C\uFF08onnx\u3001pytorch_mps \u7B49\uFF09\u3002\u5207\u6362\u4F1A\u91CD\u542F\u4EE3\u7406\u7ACB\u5373\u751F\u6548\u3002",
338
410
  headroomPower: "\u7701\u7535\u81EA\u52A8\u5207\u6362",
@@ -370,6 +442,30 @@
370
442
  aliasSaved: "\u522B\u540D\u5DF2\u66F4\u65B0",
371
443
  aliasCleared: "\u5DF2\u6062\u590D\u771F\u5B9E\u540D\u79F0",
372
444
  aliasHint: "\u78C1\u76D8\u76EE\u5F55\u540D\u4E0D\u53D8\uFF1A\u7EC8\u7AEF\u3001\u6587\u4EF6\u7BA1\u7406\u5668\u4E0E\u5176\u4ED6\u5F15\u7528\u771F\u5B9E\u8DEF\u5F84\u7684\u754C\u9762\u4ECD\u663E\u793A\u539F\u540D\u3002",
445
+ featureProjectMenuDescNew: "\u9879\u76EE\u201C\u66F4\u591A\u201D\u83DC\u5355\u4E2D\u7684\u6253\u5F00\u6587\u4EF6\u5939\u4E0E\u590D\u5236\u8DEF\u5F84\u3002\u5B98\u65B9\u5DF2\u652F\u6301\u4FEE\u6539\u9879\u76EE\u540D\u79F0\uFF08\u7F16\u8F91\u9879\u76EE\uFF09\uFF0C\u81EA\u5B9A\u4E49\u522B\u540D\u5DF2\u505C\u7528\u3002",
446
+ aliasMigrateTitle: "\u8FC1\u79FB\u9879\u76EE\u522B\u540D",
447
+ aliasMigrateDesc: "\u5F53\u524D ZCode \u5DF2\u652F\u6301\u4FEE\u6539\u9879\u76EE\u540D\u79F0\uFF08\u9879\u76EE\u201C\u66F4\u591A\u201D\u83DC\u5355 \u2192 \u7F16\u8F91\u9879\u76EE\uFF09\uFF0C\u81EA\u5B9A\u4E49\u522B\u540D\u529F\u80FD\u5DF2\u505C\u7528\u3002\u53EF\u628A\u5DF2\u4FDD\u5B58\u7684\u522B\u540D\u5199\u5165\u5B98\u65B9\u9879\u76EE\u540D\u79F0\uFF0C\u52FE\u9009\u8981\u8FC1\u79FB\u7684\u9879\u76EE\uFF1B\u8FC1\u79FB\u53EA\u6539\u9879\u76EE\u540D\u79F0\uFF0C\u78C1\u76D8\u76EE\u5F55\u4E0D\u53D7\u5F71\u54CD\u3002",
448
+ aliasMigrateItemHint: "\u8FC1\u79FB\u540E\u5C06\u4F5C\u4E3A\u9879\u76EE\u540D\u79F0\u4FDD\u5B58\uFF08\u53EF\u968F\u65F6\u5728\u201C\u7F16\u8F91\u9879\u76EE\u201D\u4E2D\u518D\u6539\uFF09\u3002\u4EC5\u65E0\u6807\u8BB0\u7684\u6761\u76EE\u53EF\u52FE\u9009\uFF1B\u5DF2\u8FC1\u79FB\u3001\u5DF2\u5E76\u5165\u3001\u9879\u76EE\u5DF2\u5220\u9664\u3001\u672C\u673A\u4E0D\u5B58\u5728\uFF08\u65E7\u673A\u5668\u8DEF\u5F84\uFF09\u4EE5\u53CA\u9879\u76EE\u5DF2\u91CD\u547D\u540D\u7684\u90FD\u4E0D\u53EF\u52FE\u9009\u2014\u2014\u8981\u6539\u8FD9\u4E9B\u9879\u76EE\u7684\u540D\u5B57\uFF0C\u76F4\u63A5\u7528\u9879\u76EE\u201C\u66F4\u591A\u201D\u83DC\u5355\u91CC\u7684\u201C\u7F16\u8F91\u9879\u76EE\u201D\u3002",
449
+ aliasMigrateStateDone: "\u5DF2\u8FC1\u79FB",
450
+ aliasMigrateStateMerged: "\u5DF2\u5E76\u5165\u300C{name}\u300D",
451
+ aliasMigrateStateDeleted: "\u9879\u76EE\u5DF2\u5220\u9664",
452
+ aliasMigrateStateMissing: "\u672C\u673A\u4E0D\u5B58\u5728",
453
+ aliasMigrateStateRenamed: "\u9879\u76EE\u5DF2\u91CD\u547D\u540D",
454
+ aliasMigrateDeadSummary: "\u5DF2\u5931\u6548 {n} \u6761\uFF08\u672C\u673A\u4E0D\u5B58\u5728 / \u9879\u76EE\u5DF2\u5220\u9664\uFF0C\u591A\u4E3A\u8BBE\u7F6E\u540C\u6B65\u5E26\u6765\u7684\u65E7\u673A\u5668\u8DEF\u5F84\uFF09",
455
+ aliasMigrateClearDead: "\u6E05\u9664\u5931\u6548\u6761\u76EE",
456
+ aliasMigrateDeadCleared: "\u5DF2\u6E05\u9664 {n} \u6761\u5931\u6548\u522B\u540D",
457
+ aliasMigrateDeadClearFailed: "\u90E8\u5206\u5931\u6548\u522B\u540D\u672A\u80FD\u6E05\u9664\uFF0C\u8BF7\u91CD\u8BD5",
458
+ aliasMigrateGo: "\u8FC1\u79FB\u6240\u9009",
459
+ aliasMigrateRunning: "\u8FC1\u79FB\u4E2D\u2026",
460
+ aliasMigrateProgress: "\u6B63\u5728\u8FC1\u79FB {i}/{n}\uFF1A{name}",
461
+ aliasMigrateStateFailed: "\u5931\u8D25",
462
+ aliasMigrateSkip: "\u6682\u4E0D\u8FC1\u79FB",
463
+ aliasMigrateDone: "\u5DF2\u8FC1\u79FB {n} \u4E2A\u522B\u540D",
464
+ aliasMigratePartial: "{n} \u4E2A\u5DF2\u8FC1\u79FB\uFF0C{m} \u4E2A\u672A\u6210\u529F\uFF1B\u672A\u6210\u529F\u7684\u53EF\u7A0D\u540E\u5728 ZCode Pro \u8BBE\u7F6E\u4E2D\u91CD\u8BD5",
465
+ aliasMigrateAllFailed: "\u522B\u540D\u8FC1\u79FB\u672A\u6210\u529F\uFF0C\u53EF\u7A0D\u540E\u5728 ZCode Pro \u8BBE\u7F6E\u4E2D\u91CD\u8BD5",
466
+ aliasMigrateCardName: "\u522B\u540D\u8FC1\u79FB",
467
+ aliasMigrateCardDesc: "\u5B98\u65B9\u5DF2\u652F\u6301\u4FEE\u6539\u9879\u76EE\u540D\u79F0\uFF0C\u8FD8\u6709 {n} \u4E2A\u81EA\u5B9A\u4E49\u522B\u540D\u672A\u8FC1\u79FB\u3002",
468
+ aliasMigrateRun: "\u8FC1\u79FB",
373
469
  taskOrderSaved: "\u987A\u5E8F\u5DF2\u66F4\u65B0",
374
470
  qqGroupTitle: "QQ \u7FA4\uFF1A428403354",
375
471
  qqGroupDesc: "\u95EE\u9898\u53CD\u9988\u4E0E\u4EA4\u6D41",
@@ -441,7 +537,7 @@
441
537
  syncRecLink: "Jianguoyun WebDAV",
442
538
  syncRecSuffix: " \u2014 use the app password, not your account password.",
443
539
  syncIncludeZcp: "ZCode Pro settings",
444
- syncIncludeZcpDesc: "Feature toggles, style adjustments, project aliases and the proxy setting; the sync config and credentials themselves are never synced.",
540
+ syncIncludeZcpDesc: "Feature toggles, style adjustments and the proxy setting; the sync config and credentials themselves are never synced. Project aliases are not synced (renaming is built in now).",
445
541
  syncIncludeModel: "Model settings",
446
542
  syncIncludeModelDesc: "Custom providers and model config (config.json and provider_config.json, API keys included). Account sign-in credentials (e.g. the BigModel subscription) are not synced; each computer signs in with its own account.",
447
543
  syncUpload: "Upload",
@@ -514,7 +610,7 @@
514
610
  featureTaskOrder: "Sidebar session drag ordering",
515
611
  featureTaskOrderDesc: "Makes session drags in Pinned, Projects and Groups persist across refreshes.",
516
612
  featureSessionSwitch: "Session quick switch",
517
- featureSessionSwitchDesc: "alt+z toggles between the current and the last session; hold alt and press x/c to move the highlight across recently used sessions, release alt to switch (like alt+tab).",
613
+ featureSessionSwitchDesc: "{last} toggles between the current and the last session; hold alt and press {prev}/{next} to move the highlight across recently used sessions (10 shown initially, pressing {next} at the bottom loads 5 more), {recent} opens the last 10 directly, {attention} opens running / finished-but-unseen tasks; arrow keys also move the highlight, Enter or release alt to switch (like alt+tab)",
518
614
  featureTitleLock: "Keep renamed session titles",
519
615
  featureTitleLockDesc: "Sessions you renamed keep their custom title: after new replies the app may show an auto-generated title instead, which reverts after a restart; this keeps the renamed title in the sidebar.",
520
616
  tabTitleGen: "Task name prompt",
@@ -541,8 +637,8 @@
541
637
  titleGenTesting: "Generating\u2026",
542
638
  titleGenTestOk: "Result: ",
543
639
  titleGenTestFailed: "Generation failed",
544
- featureWsRunning: "Running indicator on collapsed projects (experimental)",
545
- featureWsRunningDesc: "Spins a collapsed project's icon while any of its sessions is still running; stops when they all finish or the project is expanded. While spinning, the project is briefly re-expanded in the background every 60 s to re-check and then collapsed \u2014 rows stay hidden, with only a subtle quarter-second height ripple at the project.",
640
+ featureWsRunning: "Running indicator when collapsed",
641
+ featureWsRunningDesc: "Shows the same spinning loader as running tasks on collapsed projects and on section headers (\u201CTasks\u201D, \u201CPinned\u201D) while any session is running; clears when they all finish.",
546
642
  tabPerformance: "Performance",
547
643
  perfDesc: "ZCode keeps a session process resident for every project you have opened and never reclaims it, so memory grows with projects. When enabled, projects unused for a while with nothing running release their memory automatically: sessions are untouched and reload when reopened, with a few seconds wait on the first reply. The project in use, running sessions, background commands and scheduled tasks are never reclaimed. Background-command detection reads system process info via built-in tools (Linux / macOS / Windows); when unavailable, the feature disables itself.",
548
644
  featureIdleReclaim: "Idle project memory reclaim",
@@ -570,10 +666,40 @@
570
666
  userFontDesc: 'Font of question text in conversations; "default" follows the UI font.',
571
667
  assistantFontName: "Answer font",
572
668
  assistantFontDesc: 'Font of answer text in conversations (code blocks stay monospaced); "default" follows the UI font.',
573
- switcherHint: "x / c to choose, release alt to switch, Esc to cancel",
669
+ switcherHint: "{prev} / {next} or \u2191 / \u2193 to choose, Enter or release alt to switch, Esc to cancel",
574
670
  switcherCurrent: "current",
575
671
  switcherEmpty: "No previous session yet (available after you switch sessions)",
576
672
  switcherFailed: "Failed to switch to that session (it may have been deleted)",
673
+ switcherMore: "{n} more \xB7 press c to load",
674
+ switcherRunning: "running",
675
+ switcherUnseen: "unseen",
676
+ switcherAttentionHint: "Running / finished but unseen tasks \xB7 {prev} / {next} or \u2191 / \u2193 to choose, Enter or release alt to switch",
677
+ switcherAttentionEmpty: "No running or unseen tasks",
678
+ tabShortcuts: "Shortcuts",
679
+ hkTitle: "Session quick switch",
680
+ hkAltZ: "Toggle between the current and the last-used session",
681
+ hkAltX: "Move the highlight up (earlier sessions) in the popup",
682
+ hkAltC: "Move the highlight down (recent sessions); at the bottom it loads 5 more and highlights the first newly loaded entry",
683
+ hkAltE: "Open the last 10 tasks",
684
+ hkAltR: "Open running / finished-but-unseen tasks",
685
+ hkArrow: "Move the highlight while the popup is open (same rules as alt + {prev} / alt + {next})",
686
+ hkEnter: "Switch to the highlighted session",
687
+ hkEsc: "Cancel and close the popup",
688
+ hkRelease: "Releasing alt switches once the highlight has moved; otherwise the popup stays open for further selection",
689
+ hkReleaseKey: "release alt",
690
+ hkModify: "Change",
691
+ hkRecording: "press a key\u2026",
692
+ hkInvalid: "Shortcuts accept a single letter or digit only",
693
+ hkConflict: 'alt+{k} is already used by "{name}"',
694
+ hkSaved: "Shortcut updated",
695
+ hkNameLast: "last session",
696
+ hkNamePrev: "move up",
697
+ hkNameNext: "move down",
698
+ hkNameRecent: "last 10 tasks",
699
+ hkNameAttention: "running / unseen",
700
+ hkEditHint: 'Click "Change", press a new key (letter or digit); Esc cancels',
701
+ hkReset: "Reset to defaults",
702
+ hkResetDone: "Shortcuts reset to defaults",
577
703
  featureFileActions: "File menu actions",
578
704
  featureFileActionsDesc: 'Adds "Open with default app" and "Reveal in file manager" to the right-click menu of file links in chat.',
579
705
  featureImageCopy: "Image right-click copy",
@@ -582,17 +708,16 @@
582
708
  imageCopied: "Image copied to the clipboard.",
583
709
  imageCopyFailed: "Failed to copy the image.",
584
710
  openFolderItem: "Open folder",
711
+ openFolderPrimary: "primary",
585
712
  copyPathItem: "Copy path",
586
713
  pathCopied: "Path copied to the clipboard.",
587
714
  pathCopyFailed: "Failed to copy the path.",
588
715
  fileOpenDefault: "Open with default app",
589
716
  fileReveal: "Reveal in file manager",
590
- featurePinnedExpand: "Keep projects collapsed for pinned sessions (experimental)",
591
- featurePinnedExpandDesc: "Keeps the project collapsed after clicking a pinned session. Note: it briefly expands first, then collapses.",
592
- featureAutoUpdatePlugins: "Auto-update zcode-plugins plugins",
593
- featureAutoUpdatePluginsDesc: "Checks and updates installed zcode-plugins plugins on startup (updates only \u2014 never installs; uninstalled plugins stay uninstalled). Updates apply to new sessions.",
594
- pluginsUpdateTitle: "Plugin updates \xB7 zcode-plugins",
595
- pluginsCheckNow: "Check & update",
717
+ featureAutoUpdatePlugins: "Auto-update",
718
+ featureAutoUpdatePluginsRepo: "duanluan/zcode-plugins",
719
+ featureAutoUpdatePluginsDesc: "",
720
+ pluginsCheckNow: "Check for updates",
596
721
  pluginsUpdating: "Updating plugins\u2026",
597
722
  pluginsUpToDate: "Plugins are up to date",
598
723
  pluginsUpdatesFound: "{n} plugin(s) can be updated",
@@ -670,6 +795,8 @@
670
795
  hrUsageNone: "No providers route through headroom; nothing changed",
671
796
  headroomLoadFailed: "Failed to load the Headroom config",
672
797
  headroomStatusTitle: "Status",
798
+ hrInstRunning: "running",
799
+ hrInstStopped: "down",
673
800
  headroomStatusLoadFailed: "Failed to load the status",
674
801
  headroomProxyUp: "Proxy running",
675
802
  headroomProxyDown: "Proxy down",
@@ -697,7 +824,7 @@
697
824
  headroomStoppedWarn: "Proxy stopped; providers pointing at 127.0.0.1 cannot connect.",
698
825
  headroomBackend: "Compression device",
699
826
  headroomBackendAuto: "Auto \xB7 GPU first",
700
- headroomBackendCpu: "CPU \xB7 power saving",
827
+ headroomBackendCpu: "Power saving \xB7 CPU first",
701
828
  headroomBackendNative: " (native)",
702
829
  headroomBackendDesc: "Auto lets headroom pick the fastest device (CUDA/MPS); CPU forces CPU compression (power saving, no VRAM). Native headroom values (onnx, pytorch_mps, \u2026) also work. Switching restarts the proxy to apply.",
703
830
  headroomPower: "Power-save auto switch",
@@ -735,6 +862,30 @@
735
862
  aliasSaved: "Alias updated.",
736
863
  aliasCleared: "Real name restored.",
737
864
  aliasHint: "The directory name on disk is unchanged: terminals, file managers and other path-based UI still show the real name.",
865
+ featureProjectMenuDescNew: 'Open folder and copy path in the project "More" menu. Renaming is built in since ZCode 3.16.1 ("Edit project"), so the custom alias is retired.',
866
+ aliasMigrateTitle: "Migrate project aliases",
867
+ aliasMigrateDesc: 'This ZCode supports renaming projects (project "More" menu \u2192 Edit project), so the custom alias feature is retired. Write your saved aliases into the official project names by picking the projects below; only the project name changes, folders on disk are untouched.',
868
+ aliasMigrateItemHint: 'The alias becomes the project name (re-editable anytime via "Edit project"). Only unmarked entries can be checked; migrated, merged, deleted, not-on-this-machine and manually-renamed ones cannot \u2014 to rename those projects, use "Edit project" in the project "More" menu directly.',
869
+ aliasMigrateStateDone: "Migrated",
870
+ aliasMigrateStateMerged: 'Merged into "{name}"',
871
+ aliasMigrateStateDeleted: "Project deleted",
872
+ aliasMigrateStateMissing: "Not on this machine",
873
+ aliasMigrateStateRenamed: "Project renamed",
874
+ aliasMigrateDeadSummary: "{n} stale entries (not on this machine / project deleted \u2014 usually old-machine paths from settings sync)",
875
+ aliasMigrateClearDead: "Clear stale entries",
876
+ aliasMigrateDeadCleared: "Removed {n} stale alias(es)",
877
+ aliasMigrateDeadClearFailed: "Some stale aliases could not be removed; please retry",
878
+ aliasMigrateGo: "Migrate selected",
879
+ aliasMigrateRunning: "Migrating\u2026",
880
+ aliasMigrateProgress: "Migrating {i}/{n}: {name}",
881
+ aliasMigrateStateFailed: "Failed",
882
+ aliasMigrateSkip: "Not now",
883
+ aliasMigrateDone: "Migrated {n} alias(es)",
884
+ aliasMigratePartial: "{n} migrated, {m} failed; retry the rest later in ZCode Pro settings",
885
+ aliasMigrateAllFailed: "Alias migration failed; retry later in ZCode Pro settings",
886
+ aliasMigrateCardName: "Alias migration",
887
+ aliasMigrateCardDesc: "Project renaming is built in now; {n} custom alias(es) are not migrated yet.",
888
+ aliasMigrateRun: "Migrate",
738
889
  taskOrderSaved: "Order updated.",
739
890
  qqGroupTitle: "QQ group: 428403354",
740
891
  qqGroupDesc: "Feedback & discussion",
@@ -918,7 +1069,7 @@
918
1069
  var overlayClassBare = "fixed inset-0 isolate z-50 flex items-center justify-center duration-100 p-4 platform-linux-desktop:top-12";
919
1070
  var contentClass = "w-full sm:max-w-md rounded-2xl border-none bg-popover/98 p-5 text-ui-base/relaxed text-foreground ring-border shadow-2xl";
920
1071
  var dialogStack = [];
921
- function openDialog({ title, description, onMount, onClose, width = "sm:max-w-md", overlay = "dim", draggable = false, posKey = "", dismissOnOutside = true, bodyClass = "" }) {
1072
+ function openDialog({ title, description, onMount, onClose, width = "sm:max-w-md", overlay = "dim", draggable = false, posKey = "", dismissOnOutside = true, bodyClass = "", closable = false }) {
922
1073
  const L = t();
923
1074
  const prevActive = document.activeElement;
924
1075
  let cleanupDrag = null;
@@ -958,8 +1109,25 @@
958
1109
  content.style.pointerEvents = "auto";
959
1110
  }
960
1111
  const titleEl = h("h2", { class: "text-lg font-semibold leading-none tracking-tight text-foreground" }, title);
961
- content.append(
1112
+ const titleRow = closable ? h(
1113
+ "div",
1114
+ { class: "flex items-start justify-between gap-2" },
962
1115
  titleEl,
1116
+ h("button", {
1117
+ type: "button",
1118
+ class: "zcodepro-close",
1119
+ title: L.close,
1120
+ "aria-label": L.close,
1121
+ onMousedown: (e) => {
1122
+ e.stopPropagation();
1123
+ },
1124
+ onClick: () => {
1125
+ close();
1126
+ }
1127
+ }, "\u2715")
1128
+ ) : titleEl;
1129
+ content.append(
1130
+ titleRow,
963
1131
  ...description ? [h("p", { class: "mt-2 text-ui-sm/relaxed text-foreground-subtle" }, description)] : []
964
1132
  );
965
1133
  const body = h("div", { class: "zcodepro-dialog-body " + (bodyClass || "mt-4") });
@@ -1154,13 +1322,13 @@
1154
1322
  }
1155
1323
  function unitField({ value = null, fallback = { value: 100, unit: "%" }, step = { px: 10, "%": 1 }, onCommit }) {
1156
1324
  const RANGES = { px: [320, 3840], "%": [20, 100] };
1157
- const norm2 = (v) => {
1325
+ const norm3 = (v) => {
1158
1326
  if (!v || !RANGES[v.unit]) return null;
1159
1327
  const [min, max] = RANGES[v.unit];
1160
1328
  const n = v.unit === "px" ? Math.round(v.value) : Math.round(v.value * 10) / 10;
1161
1329
  return Number.isFinite(n) ? { value: Math.min(max, Math.max(min, n)), unit: v.unit } : null;
1162
1330
  };
1163
- let current2 = norm2(value) || norm2(fallback) || { value: 100, unit: "%" };
1331
+ let current2 = norm3(value) || norm3(fallback) || { value: 100, unit: "%" };
1164
1332
  const input = h("input", {
1165
1333
  type: "text",
1166
1334
  inputmode: "decimal",
@@ -1171,7 +1339,7 @@
1171
1339
  input.value = `${current2.value}${current2.unit}`;
1172
1340
  };
1173
1341
  const commit = (v) => {
1174
- const next = norm2(v);
1342
+ const next = norm3(v);
1175
1343
  if (!next || next.value === current2.value && next.unit === current2.unit) {
1176
1344
  display();
1177
1345
  return;
@@ -1208,7 +1376,7 @@
1208
1376
  return {
1209
1377
  el: input,
1210
1378
  reset() {
1211
- current2 = norm2(fallback) || current2;
1379
+ current2 = norm3(fallback) || current2;
1212
1380
  display();
1213
1381
  },
1214
1382
  get() {
@@ -1247,7 +1415,7 @@
1247
1415
  "div",
1248
1416
  { class: "min-w-0 flex-1" },
1249
1417
  h("div", { class: "text-ui-sm font-medium text-foreground" }, name),
1250
- ...desc ? [h("div", { class: "mt-0.5 text-ui-xs/relaxed text-foreground-subtle" }, desc)] : []
1418
+ h("div", { class: "mt-0.5 text-ui-xs/relaxed text-foreground-subtle", "data-zcodepro-row-desc": "" }, desc ?? "")
1251
1419
  ),
1252
1420
  ...extraEl ? [extraEl] : [],
1253
1421
  h("button", {
@@ -1505,6 +1673,20 @@
1505
1673
  background-color: color-mix(in oklab, var(--color-primary, #111) 12%, transparent);
1506
1674
  color: var(--color-foreground-subtle, #666);
1507
1675
  }
1676
+ /* attention \u5F39\u7A97\uFF08alt+r\uFF09\u7684\u884C\u72B6\u6001\u6807\u7B7E\uFF1A\u8FD0\u884C\u4E2D\u7EFF\u3001\u672A\u67E5\u770B\u7425\u73C0 */
1677
+ .zcodepro-switcher-tag-run {
1678
+ background-color: color-mix(in oklab, var(--color-emerald-500, #10b981) 20%, transparent);
1679
+ }
1680
+ .zcodepro-switcher-tag-new {
1681
+ background-color: color-mix(in oklab, var(--color-amber-500, #f59e0b) 22%, transparent);
1682
+ }
1683
+ /* \u5217\u8868\u5E95\u90E8\u201C\u8FD8\u6709 N \u6761\u201D\u63D0\u793A\u884C */
1684
+ .zcodepro-switcher-more {
1685
+ padding: 6px 12px 2px;
1686
+ font-size: 11px;
1687
+ text-align: center;
1688
+ color: var(--color-muted-foreground, #888);
1689
+ }
1508
1690
  .zcodepro-switcher-ws {
1509
1691
  font-size: 11px;
1510
1692
  color: var(--color-muted-foreground, #888);
@@ -1525,13 +1707,9 @@
1525
1707
  .zcodepro-imgmenu-item:hover {
1526
1708
  background-color: var(--color-accent, color-mix(in oklab, var(--color-foreground, #888) 10%, transparent));
1527
1709
  }
1528
- /* \u6298\u53E0\u9879\u76EE\u8FD0\u884C\u63D0\u793A\uFF08ws-running.js\uFF09\uFF1A\u9879\u76EE\u56FE\u6807\u65CB\u8F6C\u3002\u52A8\u753B\u7C7B\u6302\u5728\u56FE\u6807\u5916\u5C42\u7684
1529
- \u7A33\u5B9A span \u4E0A\uFF0C\u5C55\u5F00/\u6536\u8D77\u65F6\u5E94\u7528\u91CD\u5EFA svg \u4E5F\u4E0D\u53D7\u5F71\u54CD\uFF1B\u65F6\u957F\u4E0E\u5E94\u7528 loader \u4E00\u81F4 */
1530
- @keyframes zcodepro-ws-spin { to { transform: rotate(360deg); } }
1531
- span.zcodepro-ws-running > svg {
1532
- animation: zcodepro-ws-spin 1s linear infinite;
1533
- transform-origin: center;
1534
- }
1710
+ /* \u6298\u53E0\u9879\u76EE\u8FD0\u884C\u63D0\u793A\uFF1Afolder \u56FE\u6807\u8BA9\u4F4D\u7ED9 loader\uFF08loader \u7531\u811A\u672C\u63D2\u5165\uFF0C
1711
+ folder \u53EF\u80FD\u88AB React \u968F\u65F6\u91CD\u5EFA\uFF0C\u9760\u7C7B\u9009\u62E9\u5668\u7EDF\u4E00\u9690\u85CF\uFF0C\u540E\u4EE3\u5199\u6CD5\u517C\u5BB9\u5305\u88C5\u5C42\uFF09 */
1712
+ span.zcodepro-ws-running svg:not(.zcodepro-ws-loader) { display: none !important; }
1535
1713
  /* \u65E0\u611F\u63A2\u67E5\u671F\u95F4\u76D6\u5728\u9879\u76EE\u884C\u4E0A\u7684\u51BB\u7ED3\u514B\u9686\uFF1A\u56FA\u5B9A\u5B9A\u4F4D\u3001\u4E0D\u54CD\u5E94\u6307\u9488 */
1536
1714
  .zcodepro-ws-frozen {
1537
1715
  position: fixed;
@@ -1645,12 +1823,82 @@
1645
1823
  }
1646
1824
  .zcodepro-pin-head:hover .zcodepro-pin-chevron svg { opacity: 1; }
1647
1825
  .zcodepro-pin-collapsed .zcodepro-pin-chevron svg { transform: rotate(-90deg); }
1648
- .zcodepro-pin-collapsed > ul { display: none !important; }
1826
+ .zcodepro-pin-collapsed > :is(ul, div):has(li[data-task-item-key]) { display: none !important; }
1649
1827
  /* \u6298\u53E0\u540E\u53BB\u6389\u6807\u9898\u81EA\u5E26\u7684\u5E95\u90E8\u7559\u767D\uFF0C\u4E0E\u4E0B\u4E00\u5206\u533A\u7684\u95F4\u9694\u548C\u5176\u4ED6\u5206\u533A\u8282\u594F\u4E00\u81F4 */
1650
1828
  .zcodepro-pin-collapsed .zcodepro-pin-head { padding-bottom: 0; }
1651
1829
  `));
1652
1830
  }
1653
1831
 
1832
+ // src/inject/features/copy-path.js
1833
+ async function appendCopyPathItem(menu, anchorItem, project) {
1834
+ if (menu.querySelector('[data-zcodepro-item="copy-path"]')) return;
1835
+ const L = t();
1836
+ const item = anchorItem.cloneNode(true);
1837
+ item.removeAttribute("data-testid");
1838
+ item.removeAttribute("data-highlighted");
1839
+ item.setAttribute("data-zcodepro-item", "copy-path");
1840
+ for (const child of [...item.childNodes]) child.remove();
1841
+ const origIcon = anchorItem.querySelector("svg");
1842
+ const iconClass = origIcon ? origIcon.getAttribute("class") : "h-3.5 w-3.5";
1843
+ item.append(hCopyIcon(iconClass), document.createTextNode(L.copyPathItem));
1844
+ item.addEventListener("mouseenter", () => {
1845
+ for (const el of menu.querySelectorAll('[role="menuitem"]')) el.removeAttribute("data-highlighted");
1846
+ item.setAttribute("data-highlighted", "");
1847
+ });
1848
+ item.addEventListener("mouseleave", () => item.removeAttribute("data-highlighted"));
1849
+ item.addEventListener("click", () => {
1850
+ closeRadixMenu(menu);
1851
+ void copyText(project.path);
1852
+ });
1853
+ anchorItem.before(item);
1854
+ }
1855
+ async function copyText(text) {
1856
+ const L = t();
1857
+ if (await copyToClipboard(text)) showToast(L.pathCopied);
1858
+ else showToast(L.pathCopyFailed, "error");
1859
+ }
1860
+ async function copyToClipboard(text) {
1861
+ try {
1862
+ await navigator.clipboard.writeText(text);
1863
+ return true;
1864
+ } catch {
1865
+ return copyViaHiddenInput(text);
1866
+ }
1867
+ }
1868
+ function copyViaHiddenInput(text) {
1869
+ try {
1870
+ const input = h("input", { type: "text", style: "position:fixed;left:-9999px;top:0;opacity:0" });
1871
+ input.value = text;
1872
+ document.body.append(input);
1873
+ input.select();
1874
+ const ok = document.execCommand("copy");
1875
+ input.remove();
1876
+ return ok;
1877
+ } catch {
1878
+ return false;
1879
+ }
1880
+ }
1881
+ function hCopyIcon(cls) {
1882
+ const ns = "http://www.w3.org/2000/svg";
1883
+ const svg = document.createElementNS(ns, "svg");
1884
+ svg.setAttribute("viewBox", "0 0 24 24");
1885
+ svg.setAttribute("fill", "none");
1886
+ svg.setAttribute("stroke", "currentColor");
1887
+ svg.setAttribute("stroke-width", "2");
1888
+ svg.setAttribute("stroke-linecap", "round");
1889
+ svg.setAttribute("stroke-linejoin", "round");
1890
+ svg.setAttribute("class", cls);
1891
+ for (const d of [
1892
+ "M8 8h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2V10a2 2 0 0 1 2-2Z",
1893
+ "M4 16c-1.1 0-2-.9-2-2V4a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2"
1894
+ ]) {
1895
+ const p = document.createElementNS(ns, "path");
1896
+ p.setAttribute("d", d);
1897
+ svg.append(p);
1898
+ }
1899
+ return svg;
1900
+ }
1901
+
1654
1902
  // src/inject/features/alias.js
1655
1903
  var norm = (p) => String(p || "").replace(/[\\/]+$/, "");
1656
1904
  var basenameOf = (p) => norm(p).split(/[\\/]/).pop() || "";
@@ -1679,6 +1927,7 @@
1679
1927
  return false;
1680
1928
  }
1681
1929
  async function startAliasWatcher() {
1930
+ if (isNewProjectSidebar()) return;
1682
1931
  await syncFromConfig();
1683
1932
  if (observer) return;
1684
1933
  observer = new MutationObserver((muts) => {
@@ -2053,76 +2302,6 @@
2053
2302
  return svg;
2054
2303
  }
2055
2304
 
2056
- // src/inject/features/copy-path.js
2057
- async function appendCopyPathItem(menu, anchorItem, project) {
2058
- if (menu.querySelector('[data-zcodepro-item="copy-path"]')) return;
2059
- const L = t();
2060
- const item = anchorItem.cloneNode(true);
2061
- item.removeAttribute("data-testid");
2062
- item.removeAttribute("data-highlighted");
2063
- item.setAttribute("data-zcodepro-item", "copy-path");
2064
- for (const child of [...item.childNodes]) child.remove();
2065
- const origIcon = anchorItem.querySelector("svg");
2066
- const iconClass = origIcon ? origIcon.getAttribute("class") : "h-3.5 w-3.5";
2067
- item.append(hCopyIcon(iconClass), document.createTextNode(L.copyPathItem));
2068
- item.addEventListener("mouseenter", () => {
2069
- for (const el of menu.querySelectorAll('[role="menuitem"]')) el.removeAttribute("data-highlighted");
2070
- item.setAttribute("data-highlighted", "");
2071
- });
2072
- item.addEventListener("mouseleave", () => item.removeAttribute("data-highlighted"));
2073
- item.addEventListener("click", () => {
2074
- closeRadixMenu(menu);
2075
- void copyText(project.path);
2076
- });
2077
- anchorItem.before(item);
2078
- }
2079
- async function copyText(text) {
2080
- const L = t();
2081
- if (await copyToClipboard(text)) showToast(L.pathCopied);
2082
- else showToast(L.pathCopyFailed, "error");
2083
- }
2084
- async function copyToClipboard(text) {
2085
- try {
2086
- await navigator.clipboard.writeText(text);
2087
- return true;
2088
- } catch {
2089
- return copyViaHiddenInput(text);
2090
- }
2091
- }
2092
- function copyViaHiddenInput(text) {
2093
- try {
2094
- const input = h("input", { type: "text", style: "position:fixed;left:-9999px;top:0;opacity:0" });
2095
- input.value = text;
2096
- document.body.append(input);
2097
- input.select();
2098
- const ok = document.execCommand("copy");
2099
- input.remove();
2100
- return ok;
2101
- } catch {
2102
- return false;
2103
- }
2104
- }
2105
- function hCopyIcon(cls) {
2106
- const ns = "http://www.w3.org/2000/svg";
2107
- const svg = document.createElementNS(ns, "svg");
2108
- svg.setAttribute("viewBox", "0 0 24 24");
2109
- svg.setAttribute("fill", "none");
2110
- svg.setAttribute("stroke", "currentColor");
2111
- svg.setAttribute("stroke-width", "2");
2112
- svg.setAttribute("stroke-linecap", "round");
2113
- svg.setAttribute("stroke-linejoin", "round");
2114
- svg.setAttribute("class", cls);
2115
- for (const d of [
2116
- "M8 8h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2V10a2 2 0 0 1 2-2Z",
2117
- "M4 16c-1.1 0-2-.9-2-2V4a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2"
2118
- ]) {
2119
- const p = document.createElementNS(ns, "path");
2120
- p.setAttribute("d", d);
2121
- svg.append(p);
2122
- }
2123
- return svg;
2124
- }
2125
-
2126
2305
  // src/inject/features/project-menu.js
2127
2306
  var REMOVE_TEXTS = ["\u79FB\u9664", "Remove"];
2128
2307
  function extractPathFromTestId2(testid) {
@@ -2131,37 +2310,213 @@
2131
2310
  m = testid.match(/[-:=,|]([A-Za-z]:\\.*)$/);
2132
2311
  return m ? m[1] : null;
2133
2312
  }
2134
- function basenameOf2(p) {
2135
- return String(p || "").replace(/[\\/]+$/, "").split(/[\\/]/).pop() || "";
2313
+ function openMenuProjectRow() {
2314
+ const trigger = document.querySelector(
2315
+ '[data-testid="virtual-project-menu"][aria-expanded="true"], [data-testid="virtual-project-menu"][data-state="open"], [data-testid="virtual-project-row"][data-state="open"], [data-testid="virtual-project-row"][aria-expanded="true"]'
2316
+ );
2317
+ const row = trigger ? trigger.closest("section[data-project-id]") : null;
2318
+ if (!row) return null;
2319
+ const host = row.getAttribute("data-project-host");
2320
+ return host === null || host === "local" ? row : null;
2136
2321
  }
2137
2322
  async function resolveProjectPath(removeItem) {
2138
- const testid = removeItem.getAttribute("data-testid") || "";
2139
- const extracted = extractPathFromTestId2(testid);
2323
+ const extracted = extractPathFromTestId2(removeItem.getAttribute("data-testid") || "");
2140
2324
  if (!extracted) return null;
2141
2325
  const res = await rpc("/projects");
2142
2326
  if (res.ok && Array.isArray(res.projects)) {
2143
- const norm2 = (p) => String(p).replace(/[\\/]+$/, "");
2144
- const hit = res.projects.find((p) => norm2(p.path) === norm2(extracted));
2327
+ const norm3 = (p) => String(p).replace(/[\\/]+$/, "");
2328
+ const hit = res.projects.find((p) => norm3(p.path) === norm3(extracted));
2145
2329
  if (hit) return hit;
2146
2330
  }
2147
- return { path: extracted, name: basenameOf2(extracted) };
2331
+ return { path: extracted, name: String(extracted).replace(/[\\/]+$/, "").split(/[\\/]/).pop() || "" };
2332
+ }
2333
+ function cloneMenuItem(anchor, testid, label) {
2334
+ const item = anchor.cloneNode(true);
2335
+ item.removeAttribute("data-testid");
2336
+ item.removeAttribute("data-highlighted");
2337
+ item.removeAttribute("id");
2338
+ item.setAttribute("data-zcodepro-item", testid);
2339
+ for (const child of [...item.childNodes]) child.remove();
2340
+ const origIcon = anchor.querySelector("svg");
2341
+ const iconClass = origIcon ? origIcon.getAttribute("class") : "h-3.5 w-3.5";
2342
+ const ns = "http://www.w3.org/2000/svg";
2343
+ const svg = document.createElementNS(ns, "svg");
2344
+ svg.setAttribute("viewBox", "0 0 24 24");
2345
+ svg.setAttribute("fill", "none");
2346
+ svg.setAttribute("stroke", "currentColor");
2347
+ svg.setAttribute("stroke-width", "2");
2348
+ svg.setAttribute("stroke-linecap", "round");
2349
+ svg.setAttribute("stroke-linejoin", "round");
2350
+ svg.setAttribute("class", iconClass);
2351
+ const p = document.createElementNS(ns, "path");
2352
+ p.setAttribute("d", "M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z");
2353
+ svg.append(p);
2354
+ item.append(svg, document.createTextNode(label));
2355
+ return item;
2356
+ }
2357
+ function wireHighlight(menu, item) {
2358
+ item.addEventListener("mouseenter", () => {
2359
+ for (const el of menu.querySelectorAll('[role="menuitem"]')) el.removeAttribute("data-highlighted");
2360
+ item.setAttribute("data-highlighted", "");
2361
+ });
2362
+ item.addEventListener("mouseleave", () => item.removeAttribute("data-highlighted"));
2363
+ }
2364
+ function attachFolderHover(menu, item, roots) {
2365
+ const L = t();
2366
+ let sub = null;
2367
+ let subHovered = false;
2368
+ let hideTimer = 0;
2369
+ let aliveTimer = 0;
2370
+ const removeSub = () => {
2371
+ clearTimeout(hideTimer);
2372
+ clearInterval(aliveTimer);
2373
+ sub?.remove();
2374
+ sub = null;
2375
+ subHovered = false;
2376
+ };
2377
+ const position = () => {
2378
+ const rect = item.getBoundingClientRect();
2379
+ sub.style.left = rect.right + "px";
2380
+ sub.style.top = rect.top + "px";
2381
+ const r = sub.getBoundingClientRect();
2382
+ if (r.right > window.innerWidth - 8) {
2383
+ sub.style.left = "";
2384
+ sub.style.right = window.innerWidth - rect.left + "px";
2385
+ }
2386
+ };
2387
+ const ensureSub = () => {
2388
+ if (sub) {
2389
+ position();
2390
+ return;
2391
+ }
2392
+ sub = document.createElement("div");
2393
+ sub.setAttribute("role", "menu");
2394
+ sub.setAttribute("data-zcodepro-item", "open-folder-sub");
2395
+ sub.className = "zcodepro-card flex flex-col gap-0.5 rounded-lg border border-border bg-menu p-1 text-ui-sm shadow-lg";
2396
+ sub.style.cssText = "position:fixed;z-index:60;min-width:14rem;";
2397
+ for (const root of roots) {
2398
+ const base = String(root.path).replace(/[\\/]+$/, "").split(/[\\/]/).pop() || root.path;
2399
+ const row = document.createElement("div");
2400
+ row.setAttribute("role", "menuitem");
2401
+ row.className = "flex cursor-pointer items-center gap-2 truncate rounded-md px-2 py-1.5 text-foreground transition-colors hover:bg-surface-hover";
2402
+ row.title = root.path;
2403
+ row.append(document.createTextNode(base + (root.primary ? ` \xB7 ${L.openFolderPrimary}` : "")));
2404
+ row.addEventListener("click", () => {
2405
+ try {
2406
+ menu.dispatchEvent(new KeyboardEvent("keydown", { key: "Escape", code: "Escape", bubbles: true, cancelable: true }));
2407
+ } catch {
2408
+ }
2409
+ removeSub();
2410
+ void rpc("/open-folder", { method: "POST", body: { path: root.path } });
2411
+ });
2412
+ sub.append(row);
2413
+ }
2414
+ sub.addEventListener("mouseenter", () => {
2415
+ subHovered = true;
2416
+ clearTimeout(hideTimer);
2417
+ });
2418
+ sub.addEventListener("mouseleave", () => {
2419
+ subHovered = false;
2420
+ scheduleHide();
2421
+ });
2422
+ document.body.append(sub);
2423
+ position();
2424
+ aliveTimer = setInterval(() => {
2425
+ if (!menu.isConnected) removeSub();
2426
+ }, 400);
2427
+ };
2428
+ const scheduleHide = () => {
2429
+ clearTimeout(hideTimer);
2430
+ hideTimer = setTimeout(() => {
2431
+ if (!subHovered) removeSub();
2432
+ }, 300);
2433
+ };
2434
+ item.addEventListener("mouseenter", () => {
2435
+ clearTimeout(hideTimer);
2436
+ ensureSub();
2437
+ });
2438
+ item.addEventListener("mouseleave", scheduleHide);
2439
+ item.addEventListener("click", () => {
2440
+ clearTimeout(hideTimer);
2441
+ ensureSub();
2442
+ });
2443
+ }
2444
+ async function injectNewMenuItems(content, menuRow) {
2445
+ const L = t();
2446
+ const projectId = menuRow.getAttribute("data-project-id");
2447
+ const editItem = content.querySelector('[data-testid="virtual-project-edit"]');
2448
+ if (!editItem || !projectId) return;
2449
+ if (content.querySelector("[data-zcodepro-item]")) return;
2450
+ const config = await getConfig();
2451
+ if (config.features && config.features.projectMenu === false) return;
2452
+ const res = await rpc("/projects-v2");
2453
+ if (!res.ok || !res.available) return;
2454
+ const proj = (res.projects || []).find((p) => p.id === projectId);
2455
+ if (!proj) return;
2456
+ const roots = (proj.roots || []).slice().sort((a, b) => (b.primary ? 1 : 0) - (a.primary ? 1 : 0));
2457
+ if (!roots.length) return;
2458
+ const primary = roots.find((r) => r.primary) || roots[0];
2459
+ const openItem = cloneMenuItem(editItem, "open-folder", L.openFolderItem);
2460
+ wireHighlight(content, openItem);
2461
+ if (roots.length === 1) {
2462
+ openItem.addEventListener("click", () => {
2463
+ try {
2464
+ content.dispatchEvent(new KeyboardEvent("keydown", { key: "Escape", code: "Escape", bubbles: true, cancelable: true }));
2465
+ } catch {
2466
+ }
2467
+ void rpc("/open-folder", { method: "POST", body: { path: primary.path } });
2468
+ });
2469
+ } else {
2470
+ const ns = "http://www.w3.org/2000/svg";
2471
+ const chev = document.createElementNS(ns, "svg");
2472
+ chev.setAttribute("viewBox", "0 0 24 24");
2473
+ chev.setAttribute("fill", "none");
2474
+ chev.setAttribute("stroke", "currentColor");
2475
+ chev.setAttribute("stroke-width", "2");
2476
+ chev.setAttribute("stroke-linecap", "round");
2477
+ chev.setAttribute("stroke-linejoin", "round");
2478
+ chev.setAttribute("class", "ml-auto size-3.5 shrink-0 text-foreground-subtle");
2479
+ const chevPath = document.createElementNS(ns, "path");
2480
+ chevPath.setAttribute("d", "m9 18 6-6-6-6");
2481
+ chev.append(chevPath);
2482
+ openItem.append(chev);
2483
+ attachFolderHover(content, openItem, roots);
2484
+ }
2485
+ editItem.after(openItem);
2486
+ const copyItem = cloneMenuItem(editItem, "copy-path", L.copyPathItem);
2487
+ wireHighlight(content, copyItem);
2488
+ copyItem.addEventListener("click", () => {
2489
+ try {
2490
+ content.dispatchEvent(new KeyboardEvent("keydown", { key: "Escape", code: "Escape", bubbles: true, cancelable: true }));
2491
+ } catch {
2492
+ }
2493
+ void copyToClipboard(primary.path).then((ok) => showToast(ok ? L.pathCopied : L.pathCopyFailed, ok ? void 0 : "error"));
2494
+ });
2495
+ openItem.after(copyItem);
2148
2496
  }
2149
2497
  function handleProjectMenu(content) {
2150
- if (content.querySelector('[data-zcodepro-item="alias"]')) return;
2498
+ if (content.querySelector("[data-zcodepro-item]")) return;
2151
2499
  const items = itemsOf(content);
2152
- const removeItem = items.find(
2500
+ const newUi = isNewProjectSidebar();
2501
+ const menuRow = newUi ? openMenuProjectRow() : null;
2502
+ const anchor = newUi ? items.find((el) => el.getAttribute("data-testid") === "virtual-project-edit") : items.find(
2153
2503
  (el) => el.hasAttribute("data-testid") && REMOVE_TEXTS.some((x) => itemText(el).startsWith(x))
2154
2504
  );
2155
- if (!removeItem) return;
2505
+ if (!anchor) return;
2506
+ if (newUi) {
2507
+ if (!menuRow) return;
2508
+ void injectNewMenuItems(content, menuRow);
2509
+ return;
2510
+ }
2156
2511
  void (async () => {
2157
2512
  const config = await getConfig();
2158
2513
  if (config.features && config.features.projectMenu === false) return;
2159
- const project = await resolveProjectPath(removeItem);
2514
+ const project = await resolveProjectPath(anchor);
2160
2515
  if (!project) return;
2161
- appendAliasItem(content, removeItem, project);
2162
- appendRelocateItem(content, removeItem, project);
2163
- appendOpenFolderItem(content, removeItem, project);
2164
- appendCopyPathItem(content, removeItem, project);
2516
+ appendAliasItem(content, anchor, project);
2517
+ appendRelocateItem(content, anchor, project);
2518
+ appendOpenFolderItem(content, anchor, project);
2519
+ appendCopyPathItem(content, anchor, project);
2165
2520
  })();
2166
2521
  }
2167
2522
 
@@ -2554,40 +2909,523 @@
2554
2909
  return v === "" || v !== "session" && !providers.some((p) => p.id === v);
2555
2910
  }
2556
2911
 
2557
- // src/inject/features/styles.js
2558
- var STYLE_DEFAULTS = {
2559
- rowGap: 20,
2560
- // 段落间距:会话内各块之间的垂直间距
2561
- listSpacing: 12,
2562
- // 列表上下留白(my-3)
2563
- listItemSpacing: 6,
2564
- // 列表项之间的间距(space-y-1.5)
2565
- quoteCodeSpacing: 16,
2566
- // 引用块上下留白(my-4;代码块同规则)
2567
- codeLineHeight: 1.65,
2568
- // 代码块行高(应用默认 12px 字号 × 20px 行盒)
2569
- tableSpacing: 20,
2570
- // 表格上下边距(未设置时跟随段落间距)
2571
- tableCellPaddingV: 3,
2572
- // 单元格上下边距(应用默认 3px)
2573
- tableCellPaddingH: 3,
2574
- // 单元格左右边距(应用默认 3px)
2575
- lineHeight: 1.75,
2576
- // 回答行高(leading-[1.75],挂在答案内容容器上)
2577
- thinkingLineHeight: 1.5,
2578
- // 思考行高:“已工作/工作中”过程视图的行距(实测应用默认 1.5;范围见 buildCss 注释)
2579
- userLineHeight: 1.5,
2580
- // 提问行高(用户消息文本容器,默认 normal=1.5)
2581
- contentWidth: null,
2582
- // 内容宽度:默认 100%(跟随应用,不覆盖)
2583
- sidebarProjectSpacing: 20,
2584
- // 侧栏项目间距:项目行视觉间距(行内留白 12 + 边距 8)
2585
- sidebarTaskSpacing: 10,
2586
- // 侧栏任务间距:行内留白加行间边距(py-1 + space-y-0.5)
2587
- uiFont: null,
2588
- // 界面字体:除终端外所有界面文字(null = 跟随应用默认)
2589
- userFont: null,
2590
- // 提问字体:会话中提问内容
2912
+ // src/inject/features/ws-rows.js
2913
+ var norm2 = (p) => String(p || "").replace(/[\\/]+$/, "");
2914
+ var NEW_ROW_SEL = "section[data-project-id]";
2915
+ var OLD_ROW_SEL = '[data-testid^="workspace-item-"]';
2916
+ var pathByProjectId = /* @__PURE__ */ new Map();
2917
+ function projectRowSelector() {
2918
+ return isNewProjectSidebar() ? NEW_ROW_SEL : OLD_ROW_SEL;
2919
+ }
2920
+ function projectRows(root = document) {
2921
+ return [...root.querySelectorAll(projectRowSelector())];
2922
+ }
2923
+ function rowPath(row) {
2924
+ if (!row) return null;
2925
+ if (row.hasAttribute && row.hasAttribute("data-project-id")) {
2926
+ const host = row.getAttribute("data-project-host");
2927
+ if (host !== null && host !== "local") return null;
2928
+ const id = row.getAttribute("data-project-id");
2929
+ let found = null;
2930
+ for (const el of row.querySelectorAll("[data-task-item-key]")) {
2931
+ const key = el.getAttribute("data-task-item-key") || "";
2932
+ const cut = key.lastIndexOf(":");
2933
+ if (cut > 1) {
2934
+ found = key.slice(0, cut);
2935
+ break;
2936
+ }
2937
+ }
2938
+ if (found) pathByProjectId.set(id, found);
2939
+ return found || pathByProjectId.get(id) || null;
2940
+ }
2941
+ const testid = row.getAttribute && row.getAttribute("data-testid");
2942
+ if (!testid || !testid.startsWith("workspace-item-")) return null;
2943
+ return norm2(testid.slice("workspace-item-".length)) || null;
2944
+ }
2945
+ function findProjectRow(path) {
2946
+ const want = norm2(path);
2947
+ if (!want) return null;
2948
+ for (const row of projectRows()) {
2949
+ const p = rowPath(row);
2950
+ if (p && norm2(p) === want) return row;
2951
+ }
2952
+ return null;
2953
+ }
2954
+
2955
+ // src/inject/features/alias-migration.js
2956
+ var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
2957
+ var basenameOf2 = (p) => String(p || "").replace(/[\\/]+$/, "").split(/[\\/]/).pop() || "";
2958
+ function waitFor(getter, timeoutMs, step) {
2959
+ const start = Date.now();
2960
+ return new Promise((resolve, reject) => {
2961
+ const tick2 = () => {
2962
+ let v = null;
2963
+ try {
2964
+ v = getter();
2965
+ } catch {
2966
+ }
2967
+ if (v) {
2968
+ resolve(v);
2969
+ return;
2970
+ }
2971
+ if (Date.now() - start >= timeoutMs) {
2972
+ reject(new Error(step));
2973
+ return;
2974
+ }
2975
+ setTimeout(tick2, 120);
2976
+ };
2977
+ tick2();
2978
+ });
2979
+ }
2980
+ function setReactInputValue(input, value) {
2981
+ const setter = Object.getOwnPropertyDescriptor(input.constructor.prototype, "value")?.set || Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value")?.set;
2982
+ if (!setter) throw new Error("input-setter");
2983
+ setter.call(input, value);
2984
+ input.dispatchEvent(new Event("input", { bubbles: true }));
2985
+ }
2986
+ function pressEscape() {
2987
+ try {
2988
+ document.dispatchEvent(new KeyboardEvent("keydown", { key: "Escape", code: "Escape", bubbles: true, cancelable: true }));
2989
+ } catch {
2990
+ }
2991
+ }
2992
+ async function markDismissed() {
2993
+ await rpc("/config", { method: "POST", body: { migrations: { aliasRenameDismissed: true } } });
2994
+ clearConfigCache();
2995
+ }
2996
+ var MIGRATE_LOCK_KEY = "zcodepro:alias-migrate-lock";
2997
+ var MIGRATE_LOCK_MS = 5 * 6e4;
2998
+ function migrateLockFresh() {
2999
+ try {
3000
+ const at = Number(localStorage.getItem(MIGRATE_LOCK_KEY));
3001
+ return Number.isFinite(at) && Date.now() - at < MIGRATE_LOCK_MS;
3002
+ } catch {
3003
+ return false;
3004
+ }
3005
+ }
3006
+ function touchMigrateLock() {
3007
+ try {
3008
+ localStorage.setItem(MIGRATE_LOCK_KEY, String(Date.now()));
3009
+ } catch {
3010
+ }
3011
+ }
3012
+ async function migrateOne(path, alias) {
3013
+ if (document.querySelector('[data-testid="virtual-project-name"], [role="menu"]')) {
3014
+ pressEscape();
3015
+ await sleep(300);
3016
+ }
3017
+ let row = findProjectRow(path);
3018
+ if (!row) {
3019
+ const byName = [...document.querySelectorAll("section[data-project-id]")].filter((el) => {
3020
+ const host = el.getAttribute("data-project-host");
3021
+ return (host === null || host === "local") && el.getAttribute("data-project-name") === basenameOf2(path);
3022
+ });
3023
+ if (byName.length === 1) row = byName[0];
3024
+ }
3025
+ if (!row) throw new Error("project-row");
3026
+ const projectId = row.getAttribute("data-project-id");
3027
+ const rowById = () => document.querySelector(`section[data-project-id="${CSS.escape(projectId)}"]`);
3028
+ try {
3029
+ row.scrollIntoView({ block: "center" });
3030
+ } catch {
3031
+ }
3032
+ const trigger = await waitFor(
3033
+ () => rowById()?.querySelector('[data-testid="virtual-project-menu"]'),
3034
+ 4e3,
3035
+ "menu-trigger"
3036
+ );
3037
+ trigger.dispatchEvent(new PointerEvent("pointerdown", { bubbles: true, cancelable: true, pointerId: 1, isPrimary: true, button: 0 }));
3038
+ trigger.dispatchEvent(new PointerEvent("pointerup", { bubbles: true, cancelable: true, pointerId: 1, isPrimary: true, button: 0 }));
3039
+ trigger.click();
3040
+ const menu = await waitFor(() => {
3041
+ for (const m of document.querySelectorAll('[role="menu"]')) {
3042
+ if (m.querySelector('[data-testid="virtual-project-edit"]')) return m;
3043
+ }
3044
+ return null;
3045
+ }, 4e3, "menu-open");
3046
+ menu.querySelector('[data-testid="virtual-project-edit"]').click();
3047
+ const input = await waitFor(
3048
+ () => document.querySelector('[data-testid="virtual-project-name"]'),
3049
+ 5e3,
3050
+ "editor-open"
3051
+ );
3052
+ const wantName = alias.trim();
3053
+ setReactInputValue(input, wantName);
3054
+ const save = await waitFor(
3055
+ () => {
3056
+ const btn = document.querySelector('[data-testid="virtual-project-save"]');
3057
+ return btn && !btn.disabled ? btn : null;
3058
+ },
3059
+ 4e3,
3060
+ "save-ready"
3061
+ );
3062
+ save.click();
3063
+ await waitFor(() => {
3064
+ const r = rowById();
3065
+ return r && r.getAttribute("data-project-name") === wantName ? r : null;
3066
+ }, 8e3, "name-updated");
3067
+ await sleep(250);
3068
+ if (document.querySelector('[data-testid="virtual-project-name"]')) pressEscape();
3069
+ const res = await rpc("/project/alias", { method: "POST", body: { path, alias: "" } });
3070
+ if (!res.ok) throw new Error("alias-clear");
3071
+ }
3072
+ async function classifyEntry(path, alias, db) {
3073
+ const want = norm0(path);
3074
+ if (db) {
3075
+ let alsoDeleted = false;
3076
+ for (const proj of db.projects || []) {
3077
+ const hit = (proj.roots || []).find((r) => norm0(r.path) === want);
3078
+ if (!hit) continue;
3079
+ if (hit.primary) {
3080
+ if (proj.name === alias) return { state: "done", note: "" };
3081
+ return { state: "", note: "", overridden: !!proj.overridden };
3082
+ }
3083
+ alsoDeleted = (db.deletedRoots || []).some((p) => norm0(p) === want);
3084
+ return { state: "merged", note: proj.name, alsoDeleted };
3085
+ }
3086
+ if ((db.deletedRoots || []).some((p) => norm0(p) === want)) return { state: "deleted", note: "" };
3087
+ return { state: "missing", note: "" };
3088
+ }
3089
+ const localRows = [...document.querySelectorAll("section[data-project-id]")].filter((el) => {
3090
+ const host = el.getAttribute("data-project-host");
3091
+ return host === null || host === "local";
3092
+ });
3093
+ const byPath = localRows.find((el) => norm0(rowPath(el)) === want);
3094
+ if (byPath) {
3095
+ return (byPath.getAttribute("data-project-name") || "") === alias ? { state: "done", note: "" } : { state: "", note: "" };
3096
+ }
3097
+ const byName = localRows.filter((el) => el.getAttribute("data-project-name") === alias);
3098
+ if (byName.length === 1) return { state: "done", note: "" };
3099
+ for (const task of document.querySelectorAll("[data-task-item-key]")) {
3100
+ const key = task.getAttribute("data-task-item-key") || "";
3101
+ const cut = key.lastIndexOf(":");
3102
+ if (cut <= 1 || norm0(key.slice(0, cut)) !== want) continue;
3103
+ const owner = task.closest("section[data-project-id]");
3104
+ const ownerName = owner ? owner.getAttribute("data-project-name") || "" : "";
3105
+ if (ownerName) return { state: "merged", note: ownerName };
3106
+ }
3107
+ return { state: "missing", note: "" };
3108
+ }
3109
+ async function fetchProjectDb() {
3110
+ const res = await rpc("/projects-v2");
3111
+ return res.ok && res.available ? res : null;
3112
+ }
3113
+ var norm0 = (p) => String(p || "").replace(/[\\/]+$/, "");
3114
+ function openMigrationDialog() {
3115
+ ensureStyle();
3116
+ const L = t();
3117
+ let running = false;
3118
+ let decided = false;
3119
+ const decide = () => {
3120
+ if (decided) return;
3121
+ decided = true;
3122
+ void markDismissed();
3123
+ };
3124
+ touchMigrateLock();
3125
+ openDialog({
3126
+ title: L.aliasMigrateTitle,
3127
+ description: L.aliasMigrateDesc,
3128
+ width: "sm:max-w-md",
3129
+ // 迁移不可中途撤销且过程可见:点遮罩不关闭(防误触),右上角 ✕ 关闭
3130
+ dismissOnOutside: false,
3131
+ closable: true,
3132
+ onClose: () => {
3133
+ decide();
3134
+ window.dispatchEvent(new CustomEvent("zcodepro:aliases-changed"));
3135
+ },
3136
+ onMount: ({ body, close }) => {
3137
+ void (async () => {
3138
+ const config = await getConfig(true);
3139
+ const db = await fetchProjectDb();
3140
+ const entries = [];
3141
+ for (const [path, aliasRaw] of Object.entries(config.aliases || {})) {
3142
+ const alias = String(aliasRaw || "").trim();
3143
+ if (!alias) continue;
3144
+ const cls = await classifyEntry(path, alias, db);
3145
+ entries.push({
3146
+ path,
3147
+ alias,
3148
+ checked: cls.state === "" && !cls.overridden,
3149
+ state: cls.state,
3150
+ note: cls.note || "",
3151
+ overridden: !!cls.overridden,
3152
+ alsoDeleted: !!cls.alsoDeleted
3153
+ });
3154
+ }
3155
+ if (!entries.length) {
3156
+ close();
3157
+ return;
3158
+ }
3159
+ const badgesOf = (e) => {
3160
+ const out = [];
3161
+ if (e.state === "run") out.push({ text: L.aliasMigrateRunning, cls: "text-amber-500" });
3162
+ if (e.state === "ok") out.push({ text: L.aliasMigrateStateDone, cls: "text-emerald-600" });
3163
+ if (e.state === "fail") out.push({ text: L.aliasMigrateStateFailed, cls: "text-destructive" });
3164
+ if (e.state === "done") out.push({ text: L.aliasMigrateStateDone, cls: "text-emerald-600" });
3165
+ if (e.state === "merged") out.push({ text: L.aliasMigrateStateMerged.replace("{name}", e.note), cls: "text-amber-500" });
3166
+ if (e.state === "deleted" || e.state === "merged" && e.alsoDeleted) out.push({ text: L.aliasMigrateStateDeleted, cls: "text-foreground-subtle/70" });
3167
+ if (e.state === "missing") out.push({ text: L.aliasMigrateStateMissing, cls: "text-foreground-subtle/70" });
3168
+ if (e.state === "" && e.overridden) out.push({ text: L.aliasMigrateStateRenamed, cls: "text-foreground-subtle/70" });
3169
+ return out;
3170
+ };
3171
+ const progressEl = h("div", { class: "mb-1 text-ui-xs text-foreground-subtle", style: "display:none" });
3172
+ const listEl = h("div", { class: "max-h-[50vh] space-y-1.5 overflow-y-auto" });
3173
+ let deadOpen = false;
3174
+ const renderList = () => {
3175
+ const alive = entries.filter((e) => e.state !== "missing" && e.state !== "deleted");
3176
+ const dead = entries.filter((e) => e.state === "missing" || e.state === "deleted");
3177
+ const rowOf = (e, deadRow) => {
3178
+ const cb = h("input", { type: "checkbox" });
3179
+ cb.checked = e.checked;
3180
+ cb.disabled = running || e.state !== "" && e.state !== "fail" || e.overridden;
3181
+ cb.addEventListener("change", () => {
3182
+ e.checked = cb.checked;
3183
+ });
3184
+ const badges = badgesOf(e);
3185
+ const dim = deadRow || e.state !== "" && e.state !== "fail" && e.state !== "run" || e.overridden;
3186
+ return h(
3187
+ "label",
3188
+ { class: "flex cursor-pointer items-start gap-2 rounded-lg p-2 transition-colors hover:bg-surface-hover" + (dim ? " opacity-60" : "") },
3189
+ h("span", { class: "mt-0.5" }, cb),
3190
+ h(
3191
+ "span",
3192
+ { class: "min-w-0 flex-1" },
3193
+ h("span", { class: "block truncate text-ui-sm font-medium text-foreground" }, e.alias),
3194
+ h("span", { class: "block truncate font-mono text-ui-xs text-foreground-subtle" }, e.path)
3195
+ ),
3196
+ ...badges.map((b) => h("span", { class: "shrink-0 self-center text-ui-xs " + b.cls }, b.text))
3197
+ );
3198
+ };
3199
+ const parts = alive.map((e) => rowOf(e, false));
3200
+ if (dead.length) {
3201
+ const deadRows = h(
3202
+ "div",
3203
+ { class: "space-y-1", style: deadOpen ? "" : "display:none" },
3204
+ ...dead.map((e) => rowOf(e, true))
3205
+ );
3206
+ const toggle2 = h("button", {
3207
+ type: "button",
3208
+ class: "flex w-full items-center gap-1 rounded-lg p-2 text-left text-ui-xs text-foreground-subtle transition-colors hover:bg-surface-hover",
3209
+ onClick: () => {
3210
+ deadOpen = !deadOpen;
3211
+ renderList();
3212
+ }
3213
+ }, (deadOpen ? "\u25BE " : "\u25B8 ") + L.aliasMigrateDeadSummary.replace("{n}", String(dead.length)));
3214
+ parts.push(toggle2, deadRows);
3215
+ }
3216
+ listEl.replaceChildren(...parts);
3217
+ };
3218
+ renderList();
3219
+ const clearDead = async () => {
3220
+ if (running) return;
3221
+ const dead = entries.filter((e) => e.state === "missing" || e.state === "deleted");
3222
+ if (!dead.length) return;
3223
+ running = true;
3224
+ goBtn.setAttribute("disabled", "true");
3225
+ let ok = 0;
3226
+ for (const e of dead) {
3227
+ const r = await rpc("/project/alias", { method: "POST", body: { path: e.path, alias: "" } });
3228
+ if (r.ok) {
3229
+ ok++;
3230
+ entries.splice(entries.indexOf(e), 1);
3231
+ }
3232
+ }
3233
+ running = false;
3234
+ goBtn.removeAttribute("disabled");
3235
+ goBtn.textContent = L.aliasMigrateGo;
3236
+ clearConfigCache();
3237
+ renderList();
3238
+ window.dispatchEvent(new CustomEvent("zcodepro:aliases-changed"));
3239
+ if (ok === dead.length) showToast(L.aliasMigrateDeadCleared.replace("{n}", String(ok)));
3240
+ else showToast(L.aliasMigrateDeadClearFailed, "error");
3241
+ };
3242
+ const goBtn = btnPrimary(L.aliasMigrateGo, () => {
3243
+ void run();
3244
+ }, "min-w-24");
3245
+ const run = async () => {
3246
+ if (running) return;
3247
+ const todo = entries.filter((e) => e.checked);
3248
+ if (!todo.length) return;
3249
+ running = true;
3250
+ goBtn.setAttribute("disabled", "true");
3251
+ goBtn.textContent = L.aliasMigrateRunning;
3252
+ let ok = 0;
3253
+ const failed = [];
3254
+ for (let i = 0; i < todo.length; i++) {
3255
+ const e = todo[i];
3256
+ progressEl.style.display = "";
3257
+ progressEl.textContent = L.aliasMigrateProgress.replace("{i}", String(i + 1)).replace("{n}", String(todo.length)).replace("{name}", e.alias);
3258
+ e.state = "run";
3259
+ renderList();
3260
+ try {
3261
+ await migrateOne(e.path, e.alias);
3262
+ e.state = "ok";
3263
+ e.checked = false;
3264
+ ok++;
3265
+ } catch (err) {
3266
+ console.warn("[zcodepro] alias-migrate: " + e.path + " \u5931\u8D25\u4E8E " + (err && err.message));
3267
+ e.state = "fail";
3268
+ failed.push(e);
3269
+ }
3270
+ renderList();
3271
+ }
3272
+ running = false;
3273
+ progressEl.style.display = "none";
3274
+ goBtn.removeAttribute("disabled");
3275
+ goBtn.textContent = L.aliasMigrateGo;
3276
+ decide();
3277
+ close();
3278
+ if (!failed.length) {
3279
+ showToast(L.aliasMigrateDone.replace("{n}", String(ok)), "ok");
3280
+ } else if (ok) {
3281
+ showToast(L.aliasMigratePartial.replace("{n}", String(ok)).replace("{m}", String(failed.length)), "error");
3282
+ } else {
3283
+ showToast(L.aliasMigrateAllFailed, "error");
3284
+ }
3285
+ };
3286
+ body.append(
3287
+ progressEl,
3288
+ listEl,
3289
+ h("p", { class: "text-ui-xs/relaxed text-foreground-subtle" }, L.aliasMigrateItemHint),
3290
+ (() => {
3291
+ const footer = document.createElement("div");
3292
+ footer.className = "mt-6 flex flex-col-reverse gap-2 sm:flex-row sm:justify-end";
3293
+ const hasDead = entries.some((e) => e.state === "missing" || e.state === "deleted");
3294
+ if (hasDead) {
3295
+ const clearBtn = btnSecondary(L.aliasMigrateClearDead, () => {
3296
+ void clearDead().then(() => {
3297
+ if (!entries.some((e) => e.state === "missing" || e.state === "deleted")) clearBtn.style.display = "none";
3298
+ });
3299
+ });
3300
+ footer.append(clearBtn);
3301
+ }
3302
+ footer.append(
3303
+ btnSecondary(L.aliasMigrateSkip, () => {
3304
+ if (running) return;
3305
+ decide();
3306
+ close();
3307
+ }),
3308
+ goBtn
3309
+ );
3310
+ return footer;
3311
+ })()
3312
+ );
3313
+ })();
3314
+ }
3315
+ });
3316
+ }
3317
+ function migrationCard() {
3318
+ const L = t();
3319
+ const card = h("div", {
3320
+ class: "flex items-center gap-2 rounded-lg p-2",
3321
+ "data-zcodepro-migration-card": ""
3322
+ });
3323
+ const render = (count) => {
3324
+ card.replaceChildren(
3325
+ h(
3326
+ "div",
3327
+ { class: "min-w-0 flex-1" },
3328
+ h("div", { class: "text-ui-sm font-medium text-foreground" }, L.aliasMigrateCardName),
3329
+ h(
3330
+ "div",
3331
+ { class: "mt-0.5 text-ui-xs/relaxed text-foreground-subtle" },
3332
+ count === null ? "\u2026" : L.aliasMigrateCardDesc.replace("{n}", String(count))
3333
+ )
3334
+ ),
3335
+ btnSmall(L.aliasMigrateRun, () => {
3336
+ openMigrationDialog();
3337
+ })
3338
+ );
3339
+ };
3340
+ render(null);
3341
+ const refresh = async () => {
3342
+ const config = await getConfig(true);
3343
+ const db = await fetchProjectDb();
3344
+ let count = 0;
3345
+ for (const [p, a] of Object.entries(config.aliases || {})) {
3346
+ if (!String(a || "").trim()) continue;
3347
+ const cls = await classifyEntry(p, String(a).trim(), db);
3348
+ if (cls.state === "" && !cls.overridden) count++;
3349
+ }
3350
+ if (!count) {
3351
+ card.remove();
3352
+ return;
3353
+ }
3354
+ render(count);
3355
+ };
3356
+ const onAliasesChanged = () => {
3357
+ if (!card.isConnected) {
3358
+ window.removeEventListener("zcodepro:aliases-changed", onAliasesChanged);
3359
+ return;
3360
+ }
3361
+ void refresh();
3362
+ };
3363
+ window.addEventListener("zcodepro:aliases-changed", onAliasesChanged);
3364
+ void refresh();
3365
+ return card;
3366
+ }
3367
+ function startAliasMigrationWatcher() {
3368
+ if (!officialRenameSupported() && !isNewProjectSidebar()) return;
3369
+ void (async () => {
3370
+ try {
3371
+ const config = await getConfig();
3372
+ const hasAliases = Object.values(config.aliases || {}).some((a) => String(a || "").trim());
3373
+ if (!hasAliases) return;
3374
+ if (config.migrations && config.migrations.aliasRenameDismissed) return;
3375
+ await waitFor(() => document.querySelector("section[data-project-id]"), 45e3, "sidebar");
3376
+ let focused = false;
3377
+ for (let i = 0; i < 600; i++) {
3378
+ if (document.visibilityState === "visible" && document.hasFocus()) {
3379
+ focused = true;
3380
+ break;
3381
+ }
3382
+ await sleep(500);
3383
+ }
3384
+ if (!focused) return;
3385
+ if (migrateLockFresh()) return;
3386
+ await waitFor(() => document.querySelector(".zcodepro-overlay") ? null : true, 12e4, "dialog-busy");
3387
+ const fresh = await getConfig(true);
3388
+ const still = Object.values(fresh.aliases || {}).some((a) => String(a || "").trim());
3389
+ if (still && !(fresh.migrations && fresh.migrations.aliasRenameDismissed)) openMigrationDialog();
3390
+ } catch {
3391
+ }
3392
+ })();
3393
+ }
3394
+
3395
+ // src/inject/features/styles.js
3396
+ var STYLE_DEFAULTS = {
3397
+ rowGap: 20,
3398
+ // 段落间距:会话内各块之间的垂直间距
3399
+ listSpacing: 12,
3400
+ // 列表上下留白(my-3)
3401
+ listItemSpacing: 6,
3402
+ // 列表项之间的间距(space-y-1.5)
3403
+ quoteCodeSpacing: 16,
3404
+ // 引用块上下留白(my-4;代码块同规则)
3405
+ codeLineHeight: 1.65,
3406
+ // 代码块行高(应用默认 12px 字号 × 20px 行盒)
3407
+ tableSpacing: 20,
3408
+ // 表格上下边距(未设置时跟随段落间距)
3409
+ tableCellPaddingV: 3,
3410
+ // 单元格上下边距(应用默认 3px)
3411
+ tableCellPaddingH: 3,
3412
+ // 单元格左右边距(应用默认 3px)
3413
+ lineHeight: 1.75,
3414
+ // 回答行高(leading-[1.75],挂在答案内容容器上)
3415
+ thinkingLineHeight: 1.5,
3416
+ // 思考行高:“已工作/工作中”过程视图的行距(实测应用默认 1.5;范围见 buildCss 注释)
3417
+ userLineHeight: 1.5,
3418
+ // 提问行高(用户消息文本容器,默认 normal=1.5)
3419
+ contentWidth: null,
3420
+ // 内容宽度:默认 100%(跟随应用,不覆盖)
3421
+ sidebarProjectSpacing: 20,
3422
+ // 侧栏项目间距:项目行视觉间距(行内留白 12 + 边距 8)
3423
+ sidebarTaskSpacing: 10,
3424
+ // 侧栏任务间距:行内留白加行间边距(py-1 + space-y-0.5)
3425
+ uiFont: null,
3426
+ // 界面字体:除终端外所有界面文字(null = 跟随应用默认)
3427
+ userFont: null,
3428
+ // 提问字体:会话中提问内容
2591
3429
  assistantFont: null
2592
3430
  // 回答字体:会话中回答正文(代码块仍用等宽字体)
2593
3431
  };
@@ -2728,7 +3566,10 @@
2728
3566
  `${LIST} [data-testid^="workspace-item-"]{height:auto !important;min-height:${28 + 2 * pad}px !important;padding-block:${pad}px !important;padding-top:${pad}px !important;padding-bottom:${pad}px !important;}`,
2729
3567
  `${LIST} [data-testid^="workspace-item-"] > div:first-child{align-self:stretch !important;}`,
2730
3568
  `${SCROLL} [data-slot="collapsible-trigger"],${SCROLL} .flex.h-7{height:auto !important;}`,
2731
- `${SCROLL} [data-slot="collapsible-trigger"]{min-height:${28 + 2 * pad}px !important;padding-block:${pad}px !important;padding-top:${pad}px !important;padding-bottom:${pad}px !important;}`
3569
+ `${SCROLL} [data-slot="collapsible-trigger"]{min-height:${28 + 2 * pad}px !important;padding-block:${pad}px !important;padding-top:${pad}px !important;padding-bottom:${pad}px !important;}`,
3570
+ // 3.16.1 新结构:项目行(virtual-project-row,固定 h-8)按视觉总量放行内留白;
3571
+ // 分区间距由新侧栏容器自己的布局管理,不动
3572
+ `section[data-project-id] [data-testid="virtual-project-row"]{height:auto !important;min-height:${28 + 2 * pad}px !important;padding-block:${Math.round(pad)}px !important;}`
2732
3573
  );
2733
3574
  }
2734
3575
  const sts = styles.sidebarTaskSpacing;
@@ -2739,7 +3580,10 @@
2739
3580
  `ul.space-y-0\\.5:has(> li[data-task-item-key]) > :not(:last-child){margin-block-end:${m}px !important;margin-bottom:${m}px !important;}`,
2740
3581
  `ul.space-y-0\\.5:has(> li[data-task-item-key]) > li{padding-block:${pad}px !important;padding-top:${pad}px !important;padding-bottom:${pad}px !important;}`,
2741
3582
  `${SCROLL} .flex.flex-col.gap-2{gap:${m}px !important;row-gap:${m}px !important;}`,
2742
- `${SCROLL} div.cursor-pointer[class*="pl-8"]{padding-block:${pad}px !important;padding-top:${pad}px !important;padding-bottom:${pad}px !important;}`
3583
+ `${SCROLL} div.cursor-pointer[class*="pl-8"]{padding-block:${pad}px !important;padding-top:${pad}px !important;padding-bottom:${pad}px !important;}`,
3584
+ // 3.16.1 新结构:任务行(li[data-task-item-key])行内留白 + 相邻行间距
3585
+ `li[data-task-item-key]{padding-block:${Math.max(1, Math.round(pad))}px !important;}`,
3586
+ `li[data-task-item-key] + li[data-task-item-key]{margin-block-start:${m}px !important;}`
2743
3587
  );
2744
3588
  }
2745
3589
  return parts.join("");
@@ -2780,7 +3624,8 @@
2780
3624
  thinkingLineHeight: `[data-testid^="chat-assistant-history-content"]`,
2781
3625
  userLineHeight: `${CONV} [class*="user-row"] .whitespace-pre-wrap`,
2782
3626
  contentWidth: "[data-v4-timeline-content-column]",
2783
- sidebarProjectSpacing: '[data-testid^="workspace-item-"]',
3627
+ // 双结构:新侧栏项目行 / 旧 testid 行
3628
+ sidebarProjectSpacing: 'section[data-project-id] [data-testid="virtual-project-row"], [data-testid^="workspace-item-"]',
2784
3629
  sidebarTaskSpacing: "li[data-task-item-key]"
2785
3630
  };
2786
3631
  var HL_MARGIN = {
@@ -2824,6 +3669,7 @@
2824
3669
  } catch {
2825
3670
  }
2826
3671
  }
3672
+ var PLUGINS_REPO_URL = "https://github.com/duanluan/zcode-plugins";
2827
3673
  function makeUpgradeControls({ endpoint, metaRefresh, idleText }) {
2828
3674
  const L = t();
2829
3675
  const state = h("span", { class: "min-w-0 flex-1 truncate text-left text-ui-xs/relaxed text-foreground-subtle" });
@@ -3050,10 +3896,72 @@
3050
3896
  h("span", { class: "text-foreground-subtle/70" }, `${health.version} \xB7 ${HELPER_URL}`)
3051
3897
  );
3052
3898
  const rows = h("div", { class: "divide-y divide-border rounded-xl border border-border" });
3899
+ const pluginsStatusLine = h("span", { class: "max-w-40 truncate text-ui-xs/relaxed text-foreground-subtle" }, "\u2026");
3900
+ const pluginsBtn = btnSmall(L.pluginsCheckNow, () => {
3901
+ void runPluginsUpdate();
3902
+ });
3903
+ let pluginsUpdating = false;
3904
+ const runPluginsUpdate = async () => {
3905
+ if (pluginsUpdating) return;
3906
+ pluginsUpdating = true;
3907
+ pluginsBtn.disabled = true;
3908
+ pluginsStatusLine.textContent = L.pluginsUpdating;
3909
+ const res = await rpc("/plugins/update", { method: "POST", body: {} });
3910
+ pluginsUpdating = false;
3911
+ pluginsBtn.disabled = false;
3912
+ if (!res.ok) {
3913
+ pluginsStatusLine.textContent = "";
3914
+ showToast(L.pluginsUpdateFailed + ": " + errText(res), "error");
3915
+ return;
3916
+ }
3917
+ pluginsStatusLine.textContent = res.updated > 0 ? L.pluginsUpdatedDone.replaceAll("{n}", String(res.updated)) : L.pluginsUpToDate;
3918
+ showToast(pluginsStatusLine.textContent, "success");
3919
+ };
3920
+ void (async () => {
3921
+ const cfg = await getConfig(true).catch(() => null);
3922
+ const res = await rpc("/plugins/status");
3923
+ if (!res.ok) {
3924
+ pluginsStatusLine.textContent = "";
3925
+ return;
3926
+ }
3927
+ const n = (res.updates || []).length;
3928
+ if (cfg && (cfg.features || {}).autoUpdatePlugins === true && n > 0) {
3929
+ void runPluginsUpdate();
3930
+ return;
3931
+ }
3932
+ pluginsStatusLine.textContent = n > 0 ? L.pluginsUpdatesFound.replaceAll("{n}", String(n)) : L.pluginsUpToDate;
3933
+ })();
3934
+ const HK_ACTIONS = [
3935
+ ["switchLast", "hkAltZ"],
3936
+ ["switchPrev", "hkAltX"],
3937
+ ["switchNext", "hkAltC"],
3938
+ ["switchRecent", "hkAltE"],
3939
+ ["switchAttention", "hkAltR"]
3940
+ ];
3941
+ const hkNames = () => ({
3942
+ switchLast: L.hkNameLast,
3943
+ switchPrev: L.hkNamePrev,
3944
+ switchNext: L.hkNameNext,
3945
+ switchRecent: L.hkNameRecent,
3946
+ switchAttention: L.hkNameAttention
3947
+ });
3948
+ const hkKeys = () => {
3949
+ const sc = config.shortcuts || {};
3950
+ const keys = {};
3951
+ for (const [a] of HK_ACTIONS) {
3952
+ keys[a] = /^[a-z0-9]$/.test(sc[a]) ? sc[a] : DEFAULT_SHORTCUTS[a];
3953
+ }
3954
+ return keys;
3955
+ };
3956
+ const sessionSwitchDesc = () => {
3957
+ const k = hkKeys();
3958
+ return L.featureSessionSwitchDesc.replaceAll("{last}", "alt+" + k.switchLast).replaceAll("{prev}", k.switchPrev).replaceAll("{next}", k.switchNext).replaceAll("{recent}", "alt+" + k.switchRecent).replaceAll("{attention}", "alt+" + k.switchAttention);
3959
+ };
3053
3960
  const refreshRows = () => {
3054
3961
  const f = config.features || {};
3055
3962
  rows.replaceChildren(
3056
- settingRow(L.featureProjectMenu, L.featureProjectMenuDesc, f.projectMenu !== false, async () => {
3963
+ ...isNewProjectSidebar() ? [migrationCard()] : [],
3964
+ settingRow(L.featureProjectMenu, isNewProjectSidebar() ? L.featureProjectMenuDescNew : L.featureProjectMenuDesc, f.projectMenu !== false, async () => {
3057
3965
  const next = !(f.projectMenu !== false);
3058
3966
  if (await setFeature("projectMenu", next)) f.projectMenu = next;
3059
3967
  refreshRows();
@@ -3064,7 +3972,7 @@
3064
3972
  if (await setFeature("taskOrder", next)) f.taskOrder = next;
3065
3973
  refreshRows();
3066
3974
  }),
3067
- settingRow(L.featureSessionSwitch, L.featureSessionSwitchDesc, f.sessionSwitch !== false, async () => {
3975
+ settingRow(L.featureSessionSwitch, sessionSwitchDesc(), f.sessionSwitch !== false, async () => {
3068
3976
  const next = !(f.sessionSwitch !== false);
3069
3977
  if (await setFeature("sessionSwitch", next)) f.sessionSwitch = next;
3070
3978
  refreshRows();
@@ -3094,16 +4002,32 @@
3094
4002
  if (await setFeature("wsRunningSpin", next)) f.wsRunningSpin = next;
3095
4003
  refreshRows();
3096
4004
  }),
3097
- settingRow(L.featurePinnedExpand, L.featurePinnedExpandDesc, f.pinnedKeepCollapsed !== false, async () => {
3098
- const next = !(f.pinnedKeepCollapsed !== false);
3099
- if (await setFeature("pinnedKeepCollapsed", next)) f.pinnedKeepCollapsed = next;
3100
- refreshRows();
3101
- }),
3102
- settingRow(L.featureAutoUpdatePlugins, L.featureAutoUpdatePluginsDesc, f.autoUpdatePlugins === true, async () => {
3103
- const next = !(f.autoUpdatePlugins === true);
3104
- if (await setFeature("autoUpdatePlugins", next)) f.autoUpdatePlugins = next;
3105
- refreshRows();
3106
- })
4005
+ (() => {
4006
+ const repoLink = h("span", {
4007
+ class: "cursor-pointer underline-offset-4 hover:underline",
4008
+ title: PLUGINS_REPO_URL,
4009
+ onClick: (ev) => {
4010
+ ev.stopPropagation();
4011
+ openExternal(PLUGINS_REPO_URL);
4012
+ }
4013
+ }, L.featureAutoUpdatePluginsRepo);
4014
+ const row = settingRow("", "", f.autoUpdatePlugins === true, async () => {
4015
+ const next = !(f.autoUpdatePlugins === true);
4016
+ if (await setFeature("autoUpdatePlugins", next)) f.autoUpdatePlugins = next;
4017
+ refreshRows();
4018
+ }, h("div", { class: "flex items-center gap-2" }, pluginsBtn));
4019
+ const nameEl = row.querySelector(".text-ui-sm.font-medium");
4020
+ if (nameEl) {
4021
+ nameEl.replaceChildren(
4022
+ document.createTextNode(L.featureAutoUpdatePlugins + " "),
4023
+ repoLink,
4024
+ document.createTextNode(" \u63D2\u4EF6")
4025
+ );
4026
+ }
4027
+ const descEl = row.querySelector("[data-zcodepro-row-desc]");
4028
+ if (descEl) descEl.replaceChildren(pluginsStatusLine);
4029
+ return row;
4030
+ })()
3107
4031
  );
3108
4032
  };
3109
4033
  refreshRows();
@@ -3154,89 +4078,157 @@
3154
4078
  )
3155
4079
  );
3156
4080
  let activeTab = "features";
3157
- const pluginsStatusLine = h("span", { class: "text-ui-xs/relaxed text-foreground-subtle" }, "\u2026");
3158
- const pluginsBtn = btnSmall(L.pluginsCheckNow, () => {
3159
- void runPluginsUpdate();
3160
- });
3161
- const runPluginsUpdate = async () => {
3162
- pluginsBtn.disabled = true;
3163
- pluginsStatusLine.textContent = L.pluginsUpdating;
3164
- const res = await rpc("/plugins/update", { method: "POST", body: {} });
3165
- pluginsBtn.disabled = false;
3166
- if (!res.ok) {
3167
- pluginsStatusLine.textContent = "";
3168
- showToast(L.pluginsUpdateFailed + ": " + errText(res), "error");
3169
- return;
3170
- }
3171
- pluginsStatusLine.textContent = res.updated > 0 ? L.pluginsUpdatedDone.replaceAll("{n}", String(res.updated)) : L.pluginsUpToDate;
3172
- showToast(pluginsStatusLine.textContent, "success");
3173
- };
3174
- void (async () => {
3175
- const res = await rpc("/plugins/status");
3176
- if (!res.ok) {
3177
- pluginsStatusLine.textContent = "";
3178
- return;
3179
- }
3180
- pluginsStatusLine.textContent = (res.updates || []).length > 0 ? L.pluginsUpdatesFound.replaceAll("{n}", String(res.updates.length)) : L.pluginsUpToDate;
3181
- })();
3182
- const PLUGINS_REPO_URL = "https://github.com/duanluan/zcode-plugins";
3183
- const repoName = "zcode-plugins";
3184
- const titleParts = L.pluginsUpdateTitle.split(repoName);
3185
- const pluginsTitle = h("div", { class: "truncate text-ui-sm font-medium text-foreground" });
3186
- if (titleParts.length === 2 && typeof window.zcode?.openExternal === "function") {
3187
- pluginsTitle.append(
3188
- titleParts[0],
3189
- h("span", {
3190
- class: "cursor-pointer underline-offset-4 hover:underline",
3191
- title: PLUGINS_REPO_URL,
3192
- onClick: () => openExternal(PLUGINS_REPO_URL)
3193
- }, repoName),
3194
- titleParts[1]
3195
- );
3196
- } else {
3197
- pluginsTitle.append(L.pluginsUpdateTitle);
3198
- }
3199
- const pluginsCard = h(
3200
- "div",
3201
- { class: "mt-3 flex items-center justify-between gap-2 rounded-xl border border-border p-2" },
3202
- h(
3203
- "div",
3204
- { class: "min-w-0" },
3205
- pluginsTitle,
3206
- h("div", { class: "mt-0.5 truncate text-ui-xs/relaxed text-foreground-subtle" }, pluginsStatusLine)
3207
- ),
3208
- pluginsBtn
3209
- );
3210
4081
  const paneFeatures = h(
3211
4082
  "div",
3212
4083
  { role: "tabpanel", class: "mt-4" },
3213
4084
  h("div", {}, rows),
3214
- pluginsCard,
3215
4085
  communityCards
3216
4086
  );
3217
- const paneStyles = h("div", { role: "tabpanel", class: "mt-4", style: "display:none" });
3218
- const panePerf = h("div", { role: "tabpanel", class: "mt-4", style: "display:none" });
3219
- const paneAgents = h("div", { role: "tabpanel", class: "mt-2", style: "display:none" });
3220
- const paneProxy = h("div", { role: "tabpanel", class: "mt-4", style: "display:none" });
3221
- const paneSync = h("div", { role: "tabpanel", class: "mt-4", style: "display:none" });
3222
- const paneVision = h("div", { role: "tabpanel", class: "mt-4", style: "display:none" });
3223
- const paneRtk = h("div", { role: "tabpanel", class: "mt-4", style: "display:none" });
3224
- const paneHeadroom = h("div", { role: "tabpanel", class: "mt-4", style: "display:none" });
3225
- const paneTitleGen = h("div", { role: "tabpanel", class: "mt-4", style: "display:none" });
3226
- const paneDebug = h("div", { role: "tabpanel", class: "mt-4", style: "display:none" });
3227
- const panes = { features: paneFeatures, styles: paneStyles, perf: panePerf, agents: paneAgents, proxy: paneProxy, sync: paneSync, vision: paneVision, headroom: paneHeadroom, rtk: paneRtk, debug: paneDebug };
3228
- const tabDefs = [
3229
- ["features", L.tabFeatures],
3230
- ["agents", L.tabBehavior],
3231
- ["styles", L.tabStyles],
3232
- ["perf", L.tabPerformance],
3233
- ["proxy", L.tabProxy],
3234
- ["sync", L.tabSync],
3235
- ["vision", L.tabVision],
3236
- ["headroom", L.tabHeadroom],
3237
- ["rtk", L.tabRtk],
3238
- // “调试”标签页只对开发模式生效的用户显示(配置开关或 ZCODEPRO_DEV=1),
3239
- // 普通用户不可见
4087
+ let hkRecording = null;
4088
+ let hkKeyHandler = null;
4089
+ const hkCard = h("div", { class: "divide-y divide-border rounded-xl border border-border" });
4090
+ const hkCell = (keyLabel, desc, extra) => h(
4091
+ "div",
4092
+ { class: "flex items-start gap-3 px-3 py-2.5" },
4093
+ h("kbd", {
4094
+ class: "shrink-0 min-w-20 rounded-md border border-border px-2 py-0.5 text-center text-ui-xs text-foreground-subtle",
4095
+ style: "font-family: ui-monospace, SFMono-Regular, Menlo, monospace; line-height: 1.5;"
4096
+ }, keyLabel),
4097
+ h("div", { class: "min-w-0 flex-1 text-ui-sm text-foreground-subtle" }, desc),
4098
+ extra ? h("div", { class: "ml-auto shrink-0 pl-3" }, extra) : null
4099
+ );
4100
+ const renderShortcuts = () => {
4101
+ const keys = hkKeys();
4102
+ const names = hkNames();
4103
+ hkCard.replaceChildren(
4104
+ ...HK_ACTIONS.map(([a, descKey]) => {
4105
+ const recording = hkRecording === a;
4106
+ return hkCell(
4107
+ recording ? L.hkRecording : "alt + " + keys[a],
4108
+ L[descKey],
4109
+ btnSmall(recording ? L.hkRecording : L.hkModify, () => {
4110
+ recording ? stopHkRecording() : startHkRecording(a);
4111
+ })
4112
+ );
4113
+ }),
4114
+ hkCell("\u2191 / \u2193", L.hkArrow.replaceAll("{prev}", keys.switchPrev).replaceAll("{next}", keys.switchNext)),
4115
+ hkCell("Enter", L.hkEnter),
4116
+ hkCell("Esc", L.hkEsc),
4117
+ hkCell(L.hkReleaseKey, L.hkRelease)
4118
+ );
4119
+ };
4120
+ const stopHkRecording = () => {
4121
+ hkRecording = null;
4122
+ if (hkKeyHandler) {
4123
+ document.removeEventListener("keydown", hkKeyHandler, true);
4124
+ hkKeyHandler = null;
4125
+ }
4126
+ renderShortcuts();
4127
+ };
4128
+ const startHkRecording = (action) => {
4129
+ hkRecording = action;
4130
+ if (!hkKeyHandler) {
4131
+ const myOverlay = document.querySelector(".zcodepro-overlay");
4132
+ hkKeyHandler = (e) => {
4133
+ if (!myOverlay || !myOverlay.isConnected) {
4134
+ stopHkRecording();
4135
+ return;
4136
+ }
4137
+ if (e.isComposing) return;
4138
+ e.preventDefault();
4139
+ e.stopImmediatePropagation();
4140
+ if (e.key === "Escape") {
4141
+ stopHkRecording();
4142
+ return;
4143
+ }
4144
+ const k = e.key.toLowerCase();
4145
+ if (!/^[a-z0-9]$/.test(k)) {
4146
+ showToast(L.hkInvalid, "error");
4147
+ return;
4148
+ }
4149
+ const keys = hkKeys();
4150
+ const names = hkNames();
4151
+ const conflict = HK_ACTIONS.find(([a2]) => a2 !== hkRecording && keys[a2] === k);
4152
+ if (conflict) {
4153
+ showToast(L.hkConflict.replaceAll("{k}", k).replaceAll("{name}", names[conflict[0]]), "error");
4154
+ return;
4155
+ }
4156
+ const a = hkRecording;
4157
+ stopHkRecording();
4158
+ void (async () => {
4159
+ const res = await rpc("/config", { method: "POST", body: { shortcuts: { [a]: k } } });
4160
+ clearConfigCache();
4161
+ window.dispatchEvent(new CustomEvent("zcodepro:config-changed"));
4162
+ if (!res.ok) {
4163
+ showToast(L.failed + ": " + errText(res), "error");
4164
+ renderShortcuts();
4165
+ return;
4166
+ }
4167
+ if (res.config && res.config.shortcuts) config.shortcuts = res.config.shortcuts;
4168
+ showToast(L.hkSaved, "success");
4169
+ refreshRows();
4170
+ renderShortcuts();
4171
+ })();
4172
+ };
4173
+ document.addEventListener("keydown", hkKeyHandler, true);
4174
+ }
4175
+ renderShortcuts();
4176
+ };
4177
+ renderShortcuts();
4178
+ const resetShortcuts = async () => {
4179
+ stopHkRecording();
4180
+ const all = {};
4181
+ for (const [a] of HK_ACTIONS) all[a] = DEFAULT_SHORTCUTS[a];
4182
+ const res = await rpc("/config", { method: "POST", body: { shortcuts: all } });
4183
+ clearConfigCache();
4184
+ window.dispatchEvent(new CustomEvent("zcodepro:config-changed"));
4185
+ if (!res.ok) {
4186
+ showToast(L.failed + ": " + errText(res), "error");
4187
+ return;
4188
+ }
4189
+ if (res.config && res.config.shortcuts) config.shortcuts = res.config.shortcuts;
4190
+ showToast(L.hkResetDone, "success");
4191
+ refreshRows();
4192
+ renderShortcuts();
4193
+ };
4194
+ const paneShortcuts = h(
4195
+ "div",
4196
+ { role: "tabpanel", class: "mt-4", style: "display:none" },
4197
+ h("div", { class: "mb-2 text-ui-sm font-medium" }, L.hkTitle),
4198
+ h(
4199
+ "div",
4200
+ { class: "mb-2 flex items-center gap-2" },
4201
+ h("div", { class: "min-w-0 flex-1 text-ui-xs/relaxed text-foreground-subtle" }, L.hkEditHint),
4202
+ h("div", { class: "shrink-0" }, btnSmall(L.hkReset, () => {
4203
+ void resetShortcuts();
4204
+ }))
4205
+ ),
4206
+ hkCard
4207
+ );
4208
+ const paneStyles = h("div", { role: "tabpanel", class: "mt-4", style: "display:none" });
4209
+ const panePerf = h("div", { role: "tabpanel", class: "mt-4", style: "display:none" });
4210
+ const paneAgents = h("div", { role: "tabpanel", class: "mt-2", style: "display:none" });
4211
+ const paneProxy = h("div", { role: "tabpanel", class: "mt-4", style: "display:none" });
4212
+ const paneSync = h("div", { role: "tabpanel", class: "mt-4", style: "display:none" });
4213
+ const paneVision = h("div", { role: "tabpanel", class: "mt-4", style: "display:none" });
4214
+ const paneRtk = h("div", { role: "tabpanel", class: "mt-4", style: "display:none" });
4215
+ const paneHeadroom = h("div", { role: "tabpanel", class: "mt-4", style: "display:none" });
4216
+ const paneTitleGen = h("div", { role: "tabpanel", class: "mt-4", style: "display:none" });
4217
+ const paneDebug = h("div", { role: "tabpanel", class: "mt-4", style: "display:none" });
4218
+ const panes = { features: paneFeatures, shortcuts: paneShortcuts, styles: paneStyles, perf: panePerf, agents: paneAgents, proxy: paneProxy, sync: paneSync, vision: paneVision, headroom: paneHeadroom, rtk: paneRtk, debug: paneDebug };
4219
+ const tabDefs = [
4220
+ ["features", L.tabFeatures],
4221
+ ["shortcuts", L.tabShortcuts],
4222
+ ["agents", L.tabBehavior],
4223
+ ["styles", L.tabStyles],
4224
+ ["perf", L.tabPerformance],
4225
+ ["proxy", L.tabProxy],
4226
+ ["sync", L.tabSync],
4227
+ ["vision", L.tabVision],
4228
+ ["headroom", L.tabHeadroom],
4229
+ ["rtk", L.tabRtk],
4230
+ // “调试”标签页只对开发模式生效的用户显示(配置开关或 ZCODEPRO_DEV=1),
4231
+ // 普通用户不可见
3240
4232
  ...config.devMode === true || config.devActive === true ? [["debug", L.tabDebug]] : []
3241
4233
  ];
3242
4234
  const tablist = h("div", { role: "tablist", "aria-orientation": "horizontal", class: "zcodepro-tablist mt-1" });
@@ -3250,6 +4242,7 @@
3250
4242
  }, label)));
3251
4243
  const switchTab = (name) => {
3252
4244
  activeTab = name;
4245
+ if (name !== "shortcuts" && hkRecording) stopHkRecording();
3253
4246
  for (const [id, pane] of Object.entries(panes)) pane.style.display = id === name ? "" : "none";
3254
4247
  renderTabs();
3255
4248
  };
@@ -3349,6 +4342,7 @@
3349
4342
  )
3350
4343
  };
3351
4344
  const cells = [
4345
+ // 侧栏间距双结构:3.15.x 旧容器/行 + 3.16.1 新项目行(virtual-project-row)与任务行
3352
4346
  styleCell(L.sidebarProjectSpacingName, L.sidebarProjectSpacingDesc, "sidebarProjectSpacing", { max: 24 }),
3353
4347
  styleCell(L.sidebarTaskSpacingName, L.sidebarTaskSpacingDesc, "sidebarTaskSpacing", { max: 24 }),
3354
4348
  widthCell,
@@ -4868,6 +5862,7 @@
4868
5862
  h("span", { class: "text-ui-sm text-foreground-subtle/70" }, "\u2026")
4869
5863
  );
4870
5864
  const hrFacts = h("div", { class: "mt-1.5 grid grid-cols-2 gap-x-4 gap-y-1 sm:grid-cols-3" });
5865
+ const hrInstList = h("div", { class: "mt-1.5 space-y-1", style: "display:none" });
4871
5866
  const hrHintLine = h("p", { class: "mt-1.5 text-ui-xs/relaxed text-amber-500", style: "display:none" });
4872
5867
  const hrRefresh = async () => {
4873
5868
  const res = await rpc("/headroom/status");
@@ -4876,6 +5871,8 @@
4876
5871
  h("span", { class: "text-ui-sm text-destructive" }, L.headroomStatusLoadFailed + ": " + errText(res))
4877
5872
  );
4878
5873
  hrFacts.replaceChildren();
5874
+ hrInstList.replaceChildren();
5875
+ hrInstList.style.display = "none";
4879
5876
  hrHintLine.style.display = "none";
4880
5877
  return;
4881
5878
  }
@@ -4895,9 +5892,23 @@
4895
5892
  fact(L.headroomCurrentBackend, hrDevLabel(s.backend)),
4896
5893
  fact(L.headroomDesiredBackend, hrDevLabel(s.desiredBackend)),
4897
5894
  fact(L.headroomPowerState, HR_PM[s.powerMode] || s.powerMode),
4898
- fact(L.headroomWatcher, s.watcherRunning ? L.headroomWatcherOn : L.headroomWatcherOff),
5895
+ fact(L.headroomWatcher, s.watcherRunning == null ? "\u2014" : s.watcherRunning ? L.headroomWatcherOn : L.headroomWatcherOff),
4899
5896
  fact(L.headroomSaverDetect, HR_DETECT[s.saverDetect] || s.saverDetect)
4900
5897
  );
5898
+ const insts = Array.isArray(s.instances) ? s.instances : [];
5899
+ hrInstList.replaceChildren(...insts.map((it) => h(
5900
+ "div",
5901
+ { class: "flex min-w-0 items-center gap-2" },
5902
+ h("span", { class: "shrink-0 font-mono text-ui-xs text-foreground-subtle" }, `:${it.port}`),
5903
+ h(
5904
+ "span",
5905
+ { class: "min-w-0 flex-1 truncate text-ui-xs text-foreground-subtle", title: `${it.upstream} | ${it.providers}` },
5906
+ `${it.format} \u2192 ${it.upstream}`
5907
+ ),
5908
+ h("span", { class: "max-w-[40%] shrink-0 truncate text-ui-xs text-foreground-subtle/70", title: it.providers }, it.providers),
5909
+ h("span", { class: "shrink-0 text-ui-xs " + (it.up ? "text-emerald-600" : "text-destructive") }, it.up ? L.hrInstRunning : L.hrInstStopped)
5910
+ )));
5911
+ hrInstList.style.display = insts.length ? "" : "none";
4901
5912
  hrHintLine.textContent = s.saverHint || "";
4902
5913
  hrHintLine.style.display = s.saverHint ? "" : "none";
4903
5914
  };
@@ -5085,6 +6096,7 @@
5085
6096
  ),
5086
6097
  hrStatusLine,
5087
6098
  hrFacts,
6099
+ hrInstList,
5088
6100
  hrHintLine
5089
6101
  ),
5090
6102
  h(
@@ -5300,6 +6312,7 @@
5300
6312
  statusLine,
5301
6313
  tablist,
5302
6314
  paneFeatures,
6315
+ paneShortcuts,
5303
6316
  paneAgents,
5304
6317
  paneStyles,
5305
6318
  panePerf,
@@ -5392,1127 +6405,1453 @@
5392
6405
  }
5393
6406
  }
5394
6407
 
5395
- // src/inject/features/session-switch.js
5396
- var MRU_LIMIT = 50;
6408
+ // src/inject/features/ws-running.js
6409
+ var installed2 = false;
6410
+ var enabled2 = true;
5397
6411
  var SCAN_DEBOUNCE_MS = 150;
5398
6412
  var SCAN_FALLBACK_MS = 5e3;
5399
- var mru = [];
5400
- var current = null;
5401
- var titles = /* @__PURE__ */ new Map();
5402
- var popupEl = null;
5403
- var aliasesCache = {};
5404
- var highlight = 0;
5405
- var scanDebounce = 0;
5406
- function firstLineOf(item) {
5407
- const tEl = item.querySelector('[class*="truncate"]');
5408
- return (tEl ? tEl.textContent : (item.innerText || "").split("\n")[0])?.trim() || "";
6413
+ var PEEK_FIRST_MS = 2e4;
6414
+ var PEEK_REPEAT_MS = 6e4;
6415
+ var PEEK_SETTLE_MS = 160;
6416
+ var PEEK_MAX_MS = 1500;
6417
+ var PEEK_UNMOUNT_WAIT_MS = 800;
6418
+ var CONFIG_POLL_MIN_MS = 5e3;
6419
+ var CONFIG_POLL_MAX_MS = 6e4;
6420
+ var TASK_SEL = "[data-task-item-key]";
6421
+ var WS_SEL = () => projectRowSelector();
6422
+ function findWsRow(ws) {
6423
+ return findProjectRow(ws);
6424
+ }
6425
+ var LOADER_FALLBACK_PATHS = [
6426
+ "M12 2v4",
6427
+ "m16.2 7.8 2.9-2.9",
6428
+ "M18 12h4",
6429
+ "m19.8 16.2-2.9 2.9",
6430
+ "M12 18v4",
6431
+ "m7.8 19.8-2.9-2.9",
6432
+ "M6 12H2",
6433
+ "m4.2 7.8 2.9-2.9"
6434
+ ];
6435
+ function makeLoaderSvg(baseClass) {
6436
+ const ns = "http://www.w3.org/2000/svg";
6437
+ const proto = document.querySelector("svg.lucide-loader");
6438
+ if (proto) {
6439
+ const clone = proto.cloneNode(true);
6440
+ const origClass = proto.getAttribute("class") || "";
6441
+ const keep = origClass.split(/\s+/).filter((c) => /^(size-|text-)/.test(c)).join(" ");
6442
+ clone.setAttribute("class", ((keep || baseClass) + " animate-spin zcodepro-ws-loader").trim());
6443
+ return clone;
6444
+ }
6445
+ const svg = document.createElementNS(ns, "svg");
6446
+ svg.setAttribute("viewBox", "0 0 24 24");
6447
+ svg.setAttribute("fill", "none");
6448
+ svg.setAttribute("stroke", "currentColor");
6449
+ svg.setAttribute("stroke-width", "2");
6450
+ svg.setAttribute("stroke-linecap", "round");
6451
+ svg.setAttribute("stroke-linejoin", "round");
6452
+ svg.setAttribute("class", (baseClass + " animate-spin zcodepro-ws-loader").trim());
6453
+ for (const d of LOADER_FALLBACK_PATHS) {
6454
+ const path = document.createElementNS(ns, "path");
6455
+ path.setAttribute("d", d);
6456
+ svg.append(path);
6457
+ }
6458
+ return svg;
6459
+ }
6460
+ function setRunningIcon(holder, on) {
6461
+ if (!holder) return;
6462
+ if (on) {
6463
+ if (holder.querySelector(":scope > svg.zcodepro-ws-loader")) return;
6464
+ const orig0 = holder.querySelector(":scope > svg:not(.zcodepro-ws-loader)");
6465
+ const base = (orig0 ? orig0.getAttribute("class") || "" : "size-4").replace(/lucide-[a-z-]*/g, "").trim() || "size-4";
6466
+ holder.append(makeLoaderSvg(base));
6467
+ } else {
6468
+ holder.querySelectorAll(":scope > svg.zcodepro-ws-loader").forEach((el) => el.remove());
6469
+ }
6470
+ }
6471
+ function iconHolderOf(row) {
6472
+ if (row.hasAttribute && row.hasAttribute("data-project-id")) {
6473
+ const toggle2 = row.querySelector('[data-testid="virtual-project-toggle"]');
6474
+ return toggle2 ? toggle2.querySelector(":scope > span") : null;
6475
+ }
6476
+ return row.firstElementChild && row.firstElementChild.querySelector(":scope > span");
6477
+ }
6478
+ function isRunningRow(el) {
6479
+ const scope = el.firstElementChild;
6480
+ if (!scope) return false;
6481
+ const svg = scope.querySelector('svg[class*="lucide-loader"]');
6482
+ return !!svg;
6483
+ }
6484
+ var runningByWs = /* @__PURE__ */ new Map();
6485
+ var peekTimers = /* @__PURE__ */ new Map();
6486
+ var activePeeks = /* @__PURE__ */ new Set();
6487
+ var peekedWs = /* @__PURE__ */ new Set();
6488
+ var wsWithRows = /* @__PURE__ */ new Set();
6489
+ var servicesRef = null;
6490
+ function findServices() {
6491
+ if (servicesRef) return servicesRef;
6492
+ try {
6493
+ const rootEl = document.getElementById("root");
6494
+ const containerKey = rootEl && Object.keys(rootEl).find((k) => k.startsWith("__reactContainer$"));
6495
+ if (!containerKey) return null;
6496
+ const seen = /* @__PURE__ */ new Set();
6497
+ let found = null;
6498
+ const scanObj = (o) => {
6499
+ if (!o || typeof o !== "object" || seen.has(o) || found) return;
6500
+ seen.add(o);
6501
+ const ts = o.zcodeTaskService;
6502
+ if (ts && typeof ts === "object" && typeof ts.releaseWorkspacePreparation === "function") found = o;
6503
+ };
6504
+ let visited = 0;
6505
+ const visit = (f) => {
6506
+ if (!f || found || visited > 6e4) return;
6507
+ visited++;
6508
+ for (const holder of [f.memoizedProps, f.memoizedState]) {
6509
+ if (!holder || typeof holder !== "object") continue;
6510
+ scanObj(holder);
6511
+ for (const v of Object.values(holder)) {
6512
+ if (v && typeof v === "object") scanObj(v);
6513
+ }
6514
+ }
6515
+ visit(f.child);
6516
+ visit(f.sibling);
6517
+ };
6518
+ visit(rootEl[containerKey]);
6519
+ if (found) servicesRef = found;
6520
+ return found;
6521
+ } catch {
6522
+ return null;
6523
+ }
6524
+ }
6525
+ async function authoritativeIdle(ws) {
6526
+ const services = findServices();
6527
+ const ts = services && services.zcodeTaskService;
6528
+ if (!ts || typeof ts.listActiveTaskIds !== "function") return null;
6529
+ try {
6530
+ const base = String(ws).replace(/[\\/]+$/, "");
6531
+ const a = await ts.listActiveTaskIds({ workspacePath: base });
6532
+ const b = await ts.listActiveTaskIds({ workspacePath: base + "/" });
6533
+ const ids = [...Array.isArray(a) ? a : [], ...Array.isArray(b) ? b : []].map(String);
6534
+ if (!Array.isArray(a) || !Array.isArray(b)) return null;
6535
+ if (!ids.length) return true;
6536
+ const st = await rpc("/task-statuses?ids=" + encodeURIComponent(ids.join(","))).catch(() => null);
6537
+ const TERMINAL = /* @__PURE__ */ new Set(["completed", "error", "interrupted"]);
6538
+ const live = (id) => {
6539
+ const rec = st && st.ok && st.available ? st.statuses[id] : void 0;
6540
+ if (rec === void 0) return true;
6541
+ if (!TERMINAL.has(rec.status)) return true;
6542
+ return Date.now() - rec.updatedAt < 3e5;
6543
+ };
6544
+ return !ids.some(live);
6545
+ } catch {
6546
+ return null;
6547
+ }
5409
6548
  }
5410
6549
  function workspaceOf(key) {
5411
6550
  const i = key.indexOf(":sess_");
5412
6551
  return i > 0 ? key.slice(0, i) : key;
5413
6552
  }
5414
- function findCurrent() {
5415
- for (const el of document.querySelectorAll("[data-task-item-key]")) {
6553
+ function wsHead(row) {
6554
+ return row.matches("[aria-expanded]") ? row : row.querySelector("[aria-expanded]");
6555
+ }
6556
+ function domRowsOf(ws) {
6557
+ return [...document.querySelectorAll(TASK_SEL)].filter((e) => workspaceOf(e.getAttribute("data-task-item-key") || "") === ws);
6558
+ }
6559
+ function currentTaskRow() {
6560
+ for (const el of document.querySelectorAll(TASK_SEL)) {
5416
6561
  if ((el.className + "").includes("bg-selected")) return el;
5417
6562
  }
5418
6563
  return null;
5419
6564
  }
5420
- function record(key) {
5421
- if (key && key !== current) {
5422
- current = key;
5423
- mru = mru.filter((k) => k !== key);
5424
- mru.unshift(key);
5425
- if (mru.length > MRU_LIMIT) mru.length = MRU_LIMIT;
6565
+ var synthClick = (el) => {
6566
+ try {
6567
+ el.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true }));
6568
+ } catch {
5426
6569
  }
6570
+ };
6571
+ function toggleNavSafe(head) {
6572
+ const cur = currentTaskRow();
6573
+ const backKey = cur ? cur.getAttribute("data-task-item-key") : "";
6574
+ synthClick(head);
6575
+ if (!backKey) return;
6576
+ const back = cur && cur.isConnected ? cur : document.querySelector(`${TASK_SEL}[data-task-item-key="${CSS.escape(backKey)}"]`);
6577
+ if (back) synthClick(back);
5427
6578
  }
5428
6579
  function scan() {
5429
- const cur = findCurrent();
5430
- if (cur) record(cur.getAttribute("data-task-item-key"));
5431
- for (const el of document.querySelectorAll("[data-task-item-key]")) {
5432
- titles.set(el.getAttribute("data-task-item-key"), firstLineOf(el));
5433
- }
5434
- }
5435
- var rowSel2 = "[data-task-item-key]";
5436
- var inTaskRow = (node) => node instanceof Element && !!node.closest && !!node.closest(rowSel2);
5437
- function taskRowMutations(muts) {
5438
- for (const m of muts) {
5439
- if (m.type === "attributes") {
5440
- if (inTaskRow(m.target)) return true;
5441
- } else if (m.type === "characterData") {
5442
- if (m.target.parentElement && inTaskRow(m.target.parentElement)) return true;
5443
- } else if (inTaskRow(m.target)) {
5444
- return true;
5445
- } else {
5446
- for (const n of m.addedNodes) {
5447
- if (n instanceof Element && (n.matches(rowSel2) || !!n.querySelector(rowSel2))) return true;
6580
+ wsWithRows = /* @__PURE__ */ new Set();
6581
+ for (const el of document.querySelectorAll(TASK_SEL)) {
6582
+ const key = el.getAttribute("data-task-item-key") || "";
6583
+ const ws = workspaceOf(key);
6584
+ if (!ws) continue;
6585
+ wsWithRows.add(ws);
6586
+ const run = isRunningRow(el);
6587
+ let set = runningByWs.get(ws);
6588
+ if (run) {
6589
+ if (!set) {
6590
+ set = /* @__PURE__ */ new Set();
6591
+ runningByWs.set(ws, set);
5448
6592
  }
6593
+ set.add(key);
6594
+ } else if (set) {
6595
+ set.delete(key);
5449
6596
  }
5450
6597
  }
5451
- return false;
5452
- }
5453
- function scheduleScan() {
5454
- if (scanDebounce) return;
5455
- scanDebounce = setTimeout(() => {
5456
- scanDebounce = 0;
5457
- scan();
5458
- }, SCAN_DEBOUNCE_MS);
6598
+ syncPeeks();
6599
+ apply();
5459
6600
  }
5460
- async function switchTo(key) {
5461
- const find = () => document.querySelector(`[data-task-item-key="${CSS.escape(key)}"]`);
5462
- let item = find();
5463
- if (!item) {
5464
- const ws = workspaceOf(key);
5465
- const header = document.querySelector(`[data-testid="workspace-item-${CSS.escape(ws)}"]`);
5466
- const expander = header && header.querySelector("[aria-expanded]");
5467
- if (expander && expander.getAttribute("aria-expanded") === "false") {
5468
- expander.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true }));
5469
- for (let i = 0; i < 20 && !(item = find()); i++) await new Promise((r) => setTimeout(r, 100));
6601
+ function handleRemovals(muts) {
6602
+ const removed = [];
6603
+ for (const m of muts) {
6604
+ if (m.type !== "childList") continue;
6605
+ for (const n of m.removedNodes) {
6606
+ if (!(n instanceof Element)) continue;
6607
+ if (n.matches(TASK_SEL)) removed.push(n);
6608
+ else for (const r of n.querySelectorAll(TASK_SEL)) removed.push(r);
5470
6609
  }
5471
6610
  }
5472
- if (!item) return false;
5473
- item.scrollIntoView({ block: "nearest" });
5474
- item.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true }));
5475
- return true;
5476
- }
5477
- function closePopup(commit) {
5478
- if (!popupEl) return;
5479
- const idx = highlight;
5480
- popupEl.remove();
5481
- popupEl = null;
5482
- if (commit && idx > 0 && mru[idx] && mru[idx] !== current) {
5483
- void switchTo(mru[idx]).then((ok) => {
5484
- if (!ok) showToast(t().switcherFailed, "error");
5485
- });
5486
- }
5487
- }
5488
- function findTitle(key) {
5489
- const item = document.querySelector(`[data-task-item-key="${CSS.escape(key)}"]`);
5490
- if (item) titles.set(key, firstLineOf(item));
5491
- const id = key.slice(key.indexOf(":sess_") + 1);
5492
- return titles.get(key) || `sess ${id.slice(0, 8)}`;
5493
- }
5494
- function projLabelOf(key, aliases2) {
5495
- const ws = workspaceOf(key);
5496
- if (!ws) return "";
5497
- return aliases2[ws] || ws.replace(/[\\/]+$/, "").split(/[\\/]/).pop();
5498
- }
5499
- function renderPopup() {
5500
- if (!popupEl) return;
5501
- const L = t();
5502
- const list = popupEl.querySelector("[data-zcodepro-switcher-list]");
5503
- list.replaceChildren(...mru.map((key, i) => {
5504
- const proj = projLabelOf(key, aliasesCache);
5505
- return h(
5506
- "div",
5507
- {
5508
- class: "zcodepro-switcher-row",
5509
- "data-active": i === highlight ? "" : void 0,
5510
- onClick: () => {
5511
- highlight = i;
5512
- closePopup(true);
6611
+ if (!removed.length) return;
6612
+ const infos = removed.map((el) => {
6613
+ const key = el.getAttribute("data-task-item-key") || "";
6614
+ const ws = workspaceOf(key);
6615
+ return key && ws ? { key, ws, run: isRunningRow(el) } : null;
6616
+ }).filter(Boolean);
6617
+ if (!infos.length) return;
6618
+ setTimeout(() => {
6619
+ for (const info of infos) {
6620
+ const row = findWsRow(info.ws);
6621
+ const head = row && wsHead(row);
6622
+ const collapsed = !row || !head || head.getAttribute("aria-expanded") === "false";
6623
+ let set = runningByWs.get(info.ws);
6624
+ if (collapsed && info.run) {
6625
+ if (!set) {
6626
+ set = /* @__PURE__ */ new Set();
6627
+ runningByWs.set(info.ws, set);
5513
6628
  }
5514
- },
5515
- // 标题文本包 span:截断省略作用在文本节点上(flex 容器自身 ellipsis 无效)
5516
- h(
5517
- "div",
5518
- { class: "zcodepro-switcher-title" },
5519
- i === 0 ? h("span", { class: "zcodepro-switcher-tag" }, L.switcherCurrent) : null,
5520
- h("span", null, findTitle(key))
5521
- ),
5522
- // 无项目归属(如置顶)时不渲染空行
5523
- proj ? h("div", { class: "zcodepro-switcher-ws" }, proj) : null
5524
- );
5525
- }));
5526
- }
5527
- async function openPopup() {
5528
- if (popupEl) return;
5529
- const cfg = await getConfig().catch(() => null);
5530
- aliasesCache = cfg && cfg.aliases && typeof cfg.aliases === "object" ? cfg.aliases : {};
5531
- ensureStyle();
5532
- const L = t();
5533
- highlight = 0;
5534
- popupEl = h(
5535
- "div",
5536
- {
5537
- class: "zcodepro-switcher-overlay",
5538
- // 点弹窗外 = 取消(不切)
5539
- onMousedown: (e) => {
5540
- if (e.target === popupEl) closePopup(false);
6629
+ set.add(info.key);
6630
+ } else if (set) {
6631
+ set.delete(info.key);
5541
6632
  }
5542
- },
5543
- h(
5544
- "div",
5545
- { class: "zcodepro-switcher" },
5546
- h("div", { class: "zcodepro-switcher-hint" }, L.switcherHint),
5547
- h("div", { "data-zcodepro-switcher-list": "1", class: "zcodepro-switcher-list" })
5548
- )
5549
- );
5550
- document.body.append(popupEl);
5551
- renderPopup();
5552
- }
5553
- function moveHighlight(delta) {
5554
- if (!popupEl || mru.length < 2) return;
5555
- highlight = (highlight + delta + mru.length) % mru.length;
5556
- renderPopup();
5557
- const row = popupEl.querySelector("[data-active]");
5558
- if (row) row.scrollIntoView({ block: "nearest" });
5559
- }
5560
- function startSessionSwitch() {
5561
- scan();
5562
- new MutationObserver((muts) => {
5563
- if (taskRowMutations(muts)) scheduleScan();
5564
- }).observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ["class"] });
5565
- setInterval(() => {
5566
- if (!document.hidden) scan();
5567
- }, SCAN_FALLBACK_MS);
5568
- document.addEventListener("visibilitychange", () => {
5569
- if (!document.hidden) scan();
5570
- });
5571
- document.addEventListener("click", (e) => {
5572
- const item = e.target instanceof Element && e.target.closest("[data-task-item-key]");
5573
- if (item) record(item.getAttribute("data-task-item-key"));
5574
- }, true);
5575
- document.addEventListener("keydown", (e) => {
5576
- if (e.key === "Escape" && popupEl) {
5577
- e.preventDefault();
5578
- e.stopImmediatePropagation();
5579
- closePopup(false);
5580
- return;
5581
6633
  }
5582
- if (!e.altKey || e.ctrlKey || e.metaKey || e.isComposing) return;
5583
- if (document.querySelector(".zcodepro-overlay")) return;
5584
- const k = e.key.toLowerCase();
5585
- if (k !== "z" && k !== "x" && k !== "c") return;
5586
- e.preventDefault();
5587
- e.stopImmediatePropagation();
5588
- void (async () => {
5589
- const cfg = await getConfig();
5590
- if (cfg.features && cfg.features.sessionSwitch === false) return;
5591
- if (k === "z") {
5592
- if (e.repeat) return;
5593
- closePopup(false);
5594
- const live = findCurrent();
5595
- if (live) record(live.getAttribute("data-task-item-key"));
5596
- const target = live ? mru[0] === current ? mru[1] : mru.find((mk) => mk !== current) : mru[0];
5597
- if (!target) {
5598
- showToast(t().switcherEmpty);
5599
- return;
5600
- }
5601
- void switchTo(target).then((ok) => {
5602
- if (!ok) showToast(t().switcherFailed, "error");
5603
- });
5604
- return;
5605
- }
5606
- if (mru.length < 2) {
5607
- showToast(t().switcherEmpty);
5608
- return;
5609
- }
5610
- if (!popupEl) await openPopup();
5611
- moveHighlight(k === "x" ? 1 : -1);
5612
- })();
5613
- }, true);
5614
- document.addEventListener("keyup", (e) => {
5615
- if (e.key === "Alt" && popupEl) closePopup(true);
5616
- }, true);
5617
- window.addEventListener("blur", () => closePopup(false));
5618
- }
5619
-
5620
- // src/inject/features/title-lock.js
5621
- var TASK_SEL = "[data-task-item-key]";
5622
- var SCAN_DEBOUNCE_MS2 = 150;
5623
- var SCAN_FALLBACK_MS2 = 5e3;
5624
- var BOOT_RETRY_MS = 5e3;
5625
- var RECHECK_DELAY_MS = 1e3;
5626
- var REVERIFY_MS = 3e3;
5627
- var MAP_REFRESH_MS = 6e4;
5628
- var installed2 = false;
5629
- var enabled2 = true;
5630
- var pinned = null;
5631
- var resolved = false;
5632
- var fetching = null;
5633
- var pendingKeys = /* @__PURE__ */ new Set();
5634
- function sessIdOf(key) {
5635
- const i = key.indexOf(":sess_");
5636
- return i > 0 ? key.slice(i + 1) : key;
6634
+ syncPeeks();
6635
+ apply();
6636
+ }, 0);
5637
6637
  }
5638
- function titleElsOf(item) {
5639
- const copies = item.querySelectorAll("[data-task-title-copy]");
5640
- if (copies.length) return [...copies];
5641
- const t2 = item.querySelector('[class*="truncate"]');
5642
- return t2 ? [t2] : [];
6638
+ function apply() {
6639
+ const seen = /* @__PURE__ */ new Set();
6640
+ for (const row of document.querySelectorAll(WS_SEL())) {
6641
+ const path = rowPath(row);
6642
+ if (!path) continue;
6643
+ seen.add(path);
6644
+ const head = wsHead(row);
6645
+ const collapsed = !head || head.getAttribute("aria-expanded") === "false";
6646
+ const domRunning = !!(runningByWs.get(path) && runningByWs.get(path).size);
6647
+ const normP = (v) => String(v || "").replace(/[\\/]+$/, "");
6648
+ const running = enabled2 && collapsed && (activeProjectPaths.size ? [...activeProjectPaths].some((p2) => normP(p2) === normP(path)) : domRunning);
6649
+ const icon = iconHolderOf(row);
6650
+ icon?.classList.toggle("zcodepro-ws-running", running);
6651
+ setRunningIcon(icon, running);
6652
+ }
6653
+ applySectionSpin();
6654
+ for (const ws of [...runningByWs.keys()]) if (!seen.has(ws)) runningByWs.delete(ws);
5643
6655
  }
5644
- function findRows(key) {
5645
- return [...document.querySelectorAll(`${TASK_SEL}[data-task-item-key="${CSS.escape(key)}"]`)];
6656
+ var headInfoCache = /* @__PURE__ */ new WeakMap();
6657
+ var PROJECT_HEAD_TEXTS = /* @__PURE__ */ new Set(["\u9879\u76EE", "projects"]);
6658
+ var TASK_HEAD_TEXTS = /* @__PURE__ */ new Set(["\u4EFB\u52A1", "tasks"]);
6659
+ function kindFromHeadText(head) {
6660
+ const text = (head.textContent || "").trim().toLowerCase();
6661
+ if (PROJECT_HEAD_TEXTS.has(text)) return "project";
6662
+ if (TASK_HEAD_TEXTS.has(text)) return "task";
6663
+ return null;
5646
6664
  }
5647
- async function fetchPinned() {
5648
- if (fetching) return fetching;
5649
- const p = (async () => {
5650
- const res = await rpc("/pinned-titles");
5651
- if (!res || !res.ok) return null;
5652
- resolved = true;
5653
- enabled2 = res.enabled !== false;
5654
- if (!enabled2) {
5655
- pinned = null;
5656
- return null;
6665
+ function sectionHeads() {
6666
+ const heads = [];
6667
+ for (const sv of document.querySelectorAll("svg[data-purpose-section-chevron]")) {
6668
+ const btn = sv.closest("button");
6669
+ if (btn) heads.push(btn);
6670
+ }
6671
+ for (const h3 of document.querySelectorAll("h3")) heads.push(h3);
6672
+ return heads.map((head) => {
6673
+ const cached = headInfoCache.get(head);
6674
+ if (cached) return { head, cont: cached.cont, kind: cached.kind };
6675
+ const textKind = head.tagName === "BUTTON" ? kindFromHeadText(head) : "task";
6676
+ let cont = head.parentElement;
6677
+ while (cont && !cont.querySelector("li[data-task-item-key]") && cont !== document.body) cont = cont.parentElement;
6678
+ const contOk = cont && cont !== document.body;
6679
+ const kind = textKind || (contOk ? cont.querySelector("section[data-project-id]") ? "project" : "task" : null);
6680
+ headInfoCache.set(head, { cont: contOk ? cont : null, kind });
6681
+ return { head, cont: contOk ? cont : null, kind };
6682
+ }).filter((x) => x.head);
6683
+ }
6684
+ var SEC_SPIN_CLASS = "zcodepro-ws-sec-spin";
6685
+ var anyActiveCache = { at: 0, value: null, busy: false };
6686
+ var sectionActive = { task: false, project: false };
6687
+ var activeProjectPaths = /* @__PURE__ */ new Set();
6688
+ setInterval(() => {
6689
+ if (!anyActiveCache.busy && Date.now() - anyActiveCache.at >= 1e4) globalAnyActive();
6690
+ }, 1e4);
6691
+ function globalAnyActive() {
6692
+ if (anyActiveCache.value !== null && Date.now() - anyActiveCache.at < 1e4) return anyActiveCache.value;
6693
+ if (anyActiveCache.busy) return anyActiveCache.value === true;
6694
+ anyActiveCache.busy = true;
6695
+ sectionActive.task = false;
6696
+ sectionActive.project = false;
6697
+ activeProjectPaths = /* @__PURE__ */ new Set();
6698
+ void (async () => {
6699
+ let hit = null;
6700
+ try {
6701
+ const res = await rpc("/projects-v2").catch(() => null);
6702
+ const services = findServices();
6703
+ const ts = services && services.zcodeTaskService;
6704
+ if (res && res.ok && res.available && ts && typeof ts.listActiveTaskIds === "function") {
6705
+ hit = false;
6706
+ const projPaths = /* @__PURE__ */ new Set();
6707
+ for (const proj of res.projects || []) {
6708
+ for (const r of proj.roots || []) {
6709
+ const b = String(r.path || "").replace(/[\\/]+$/, "");
6710
+ if (b) projPaths.add(b);
6711
+ }
6712
+ }
6713
+ const taskSidePaths = /* @__PURE__ */ new Set();
6714
+ for (const el of document.querySelectorAll("[data-task-item-key]")) {
6715
+ const sec = el.closest("section[data-project-id]");
6716
+ if (sec) continue;
6717
+ const k = el.getAttribute("data-task-item-key") || "";
6718
+ const cut = k.lastIndexOf(":");
6719
+ if (cut > 1) taskSidePaths.add(k.slice(0, cut).replace(/[\\/]+$/, ""));
6720
+ }
6721
+ const scanPaths = /* @__PURE__ */ new Set([...projPaths, ...taskSidePaths]);
6722
+ const byKind = { task: /* @__PURE__ */ new Set(), project: /* @__PURE__ */ new Set() };
6723
+ const byKindByPath = /* @__PURE__ */ new Map();
6724
+ const rootToProject = /* @__PURE__ */ new Map();
6725
+ const projectPaths = /* @__PURE__ */ new Map();
6726
+ for (const proj of res.projects || []) {
6727
+ const names = [];
6728
+ for (const r of proj.roots || []) {
6729
+ const b = String(r.path || "").replace(/[\\/]+$/, "");
6730
+ if (b) {
6731
+ rootToProject.set(b, proj.name);
6732
+ names.push(b);
6733
+ }
6734
+ }
6735
+ projectPaths.set(proj.name, names);
6736
+ }
6737
+ for (const base of scanPaths) {
6738
+ const a = await ts.listActiveTaskIds({ workspacePath: base });
6739
+ const b = await ts.listActiveTaskIds({ workspacePath: base + "/" });
6740
+ const ids = [...Array.isArray(a) ? a : [], ...Array.isArray(b) ? b : []].map(String);
6741
+ if (!ids.length) continue;
6742
+ byKindByPath.set(base, ids);
6743
+ const kind = projPaths.has(base) ? "project" : "task";
6744
+ for (const id of ids) byKind[kind].add(id);
6745
+ }
6746
+ const allIds = [...byKind.task, ...byKind.project];
6747
+ const st = allIds.length ? await rpc("/task-statuses?ids=" + encodeURIComponent(allIds.join(","))).catch(() => null) : null;
6748
+ const TERMINAL = /* @__PURE__ */ new Set(["completed", "error", "interrupted"]);
6749
+ const live = (id) => {
6750
+ const rec = st && st.ok && st.available ? st.statuses[id] : void 0;
6751
+ if (rec === void 0) return true;
6752
+ if (!TERMINAL.has(rec.status)) return true;
6753
+ return Date.now() - rec.updatedAt < 3e5;
6754
+ };
6755
+ activeProjectPaths = /* @__PURE__ */ new Set();
6756
+ for (const [base, ids] of byKindByPath) {
6757
+ if (!projPaths.has(base)) continue;
6758
+ if (ids.some(live)) {
6759
+ const projName = rootToProject.get(base);
6760
+ if (projName) for (const p2 of projectPaths.get(projName) || []) activeProjectPaths.add(p2);
6761
+ }
6762
+ }
6763
+ const taskDomRunning = !!document.querySelector('[data-task-item-key]:not(section[data-project-id] *) svg[class*="lucide-loader"]');
6764
+ if (taskDomRunning && [...byKind.task].some(live)) sectionActive.task = true;
6765
+ if ([...byKind.project].some(live)) sectionActive.project = true;
6766
+ hit = sectionActive.task || sectionActive.project;
6767
+ }
6768
+ } catch {
5657
6769
  }
5658
- pinned = new Map(Object.entries(res.titles || {}));
5659
- return pinned;
6770
+ if (hit !== null) anyActiveCache = { at: Date.now(), value: hit, busy: false };
6771
+ else anyActiveCache.busy = false;
6772
+ apply();
5660
6773
  })();
5661
- fetching = p;
5662
- try {
5663
- return await p;
5664
- } finally {
5665
- if (fetching === p) fetching = null;
5666
- }
5667
- }
5668
- function isMismatch(item) {
5669
- if (!enabled2 || !pinned) return null;
5670
- const key = item.getAttribute("data-task-item-key") || "";
5671
- const want = pinned.get(sessIdOf(key));
5672
- if (want === void 0) return null;
5673
- const els = titleElsOf(item);
5674
- if (!els.length) return null;
5675
- if ((els[0].textContent || "").trim() === String(want).trim()) return null;
5676
- if (item.querySelector('input, textarea, [contenteditable="true"]')) return null;
5677
- return key;
6774
+ return anyActiveCache.value === true;
5678
6775
  }
5679
- function writeTitle(el, text) {
5680
- const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
5681
- const first = walker.nextNode();
5682
- if (first && !walker.nextNode()) first.nodeValue = text;
5683
- else el.textContent = text;
6776
+ function applySectionSpin() {
6777
+ if (!enabled2) {
6778
+ document.querySelectorAll("." + SEC_SPIN_CLASS).forEach((el) => el.remove());
6779
+ return;
6780
+ }
6781
+ for (const { head, cont, kind } of sectionHeads()) {
6782
+ const running = !!(cont && cont.querySelector('li[data-task-item-key] svg[class*="lucide-loader"]'));
6783
+ let expanded = true;
6784
+ if (head.tagName === "BUTTON") expanded = head.getAttribute("aria-expanded") !== "false";
6785
+ else {
6786
+ const list = head.nextElementSibling;
6787
+ expanded = !list || list.offsetHeight > 0;
6788
+ }
6789
+ if (!kind) return;
6790
+ const scoped = kind === "project" ? sectionActive.project : sectionActive.task;
6791
+ const want = !expanded && scoped;
6792
+ const existing = head.querySelector(":scope > ." + SEC_SPIN_CLASS);
6793
+ if (want && !existing) {
6794
+ const span = document.createElement("span");
6795
+ span.className = "flex size-4 shrink-0 items-center justify-center " + SEC_SPIN_CLASS;
6796
+ span.append(makeLoaderSvg("size-4"));
6797
+ head.prepend(span);
6798
+ } else if (!want && existing) {
6799
+ existing.remove();
6800
+ }
6801
+ }
5684
6802
  }
5685
- function scheduleEnforce(key) {
5686
- if (pendingKeys.has(key)) return;
5687
- pendingKeys.add(key);
5688
- setTimeout(() => {
5689
- pendingKeys.delete(key);
5690
- void (async () => {
5691
- const map = await fetchPinned();
5692
- if (!map) return;
5693
- const want = map.get(sessIdOf(key));
5694
- if (want === void 0) return;
5695
- let wrote = false;
5696
- for (const item of findRows(key)) {
5697
- if (isMismatch(item) !== key) continue;
5698
- for (const el of titleElsOf(item)) writeTitle(el, want);
5699
- wrote = true;
5700
- }
5701
- if (wrote) setTimeout(() => {
5702
- void enforceKey(key);
5703
- }, REVERIFY_MS);
5704
- })();
5705
- }, RECHECK_DELAY_MS);
6803
+ function schedulePeek(ws, delay) {
6804
+ clearTimeout(peekTimers.get(ws));
6805
+ peekTimers.set(ws, setTimeout(() => {
6806
+ peekTimers.delete(ws);
6807
+ void peek(ws);
6808
+ }, delay));
5706
6809
  }
5707
- async function enforceKey(key) {
5708
- const map = await fetchPinned();
5709
- if (!map) return;
5710
- const want = map.get(sessIdOf(key));
5711
- if (want === void 0) return;
5712
- for (const item of findRows(key)) {
5713
- if (isMismatch(item) !== key) continue;
5714
- for (const el of titleElsOf(item)) writeTitle(el, want);
6810
+ function clearPeek(ws) {
6811
+ const t2 = peekTimers.get(ws);
6812
+ if (t2) {
6813
+ clearTimeout(t2);
6814
+ peekTimers.delete(ws);
5715
6815
  }
5716
6816
  }
5717
- function scan2() {
5718
- if (!enabled2 || !pinned || pinned.size === 0) return;
5719
- for (const item of document.querySelectorAll(TASK_SEL)) {
5720
- const key = isMismatch(item);
5721
- if (key) scheduleEnforce(key);
6817
+ function syncPeeks() {
6818
+ for (const [ws, set] of runningByWs) {
6819
+ if (!enabled2 || !set.size) {
6820
+ clearPeek(ws);
6821
+ continue;
6822
+ }
6823
+ const row = findWsRow(ws);
6824
+ const head = row && wsHead(row);
6825
+ if (!row || !head || head.getAttribute("aria-expanded") !== "false" || wsWithRows.has(ws)) {
6826
+ clearPeek(ws);
6827
+ continue;
6828
+ }
6829
+ if (!peekTimers.has(ws) && !activePeeks.has(ws)) schedulePeek(ws, peekedWs.has(ws) ? PEEK_REPEAT_MS : PEEK_FIRST_MS);
5722
6830
  }
5723
6831
  }
5724
- var inTaskRow2 = (node) => !!(node instanceof Element && node.closest && node.closest(TASK_SEL));
5725
- function taskRowMutations2(muts) {
5726
- for (const m of muts) {
5727
- if (m.type === "attributes") {
5728
- if (inTaskRow2(m.target)) return true;
5729
- } else if (m.type === "characterData") {
5730
- if (m.target.parentElement && inTaskRow2(m.target.parentElement)) return true;
5731
- } else if (inTaskRow2(m.target)) {
5732
- return true;
5733
- } else {
5734
- for (const n of m.addedNodes) {
5735
- if (n instanceof Element && (n.matches(TASK_SEL) || !!n.querySelector(TASK_SEL))) return true;
6832
+ var peekChain = Promise.resolve();
6833
+ async function peek(ws, opts) {
6834
+ if (!opts?.manual) {
6835
+ const auth = await authoritativeIdle(ws);
6836
+ if (auth !== null) {
6837
+ if (auth) {
6838
+ runningByWs.delete(ws);
6839
+ } else {
6840
+ let set = runningByWs.get(ws);
6841
+ if (!set) {
6842
+ set = /* @__PURE__ */ new Set();
6843
+ runningByWs.set(ws, set);
6844
+ }
6845
+ set.add(ws + ":active");
6846
+ schedulePeek(ws, PEEK_REPEAT_MS);
5736
6847
  }
6848
+ apply();
6849
+ return true;
5737
6850
  }
5738
6851
  }
5739
- return false;
5740
- }
5741
- var scanDebounce2 = 0;
5742
- function scheduleScan2() {
5743
- if (scanDebounce2) return;
5744
- scanDebounce2 = setTimeout(() => {
5745
- scanDebounce2 = 0;
5746
- scan2();
5747
- }, SCAN_DEBOUNCE_MS2);
5748
- }
5749
- var mapRefreshTimer = 0;
5750
- function refreshMapSoon() {
5751
- if (mapRefreshTimer) return;
5752
- const tick2 = () => {
5753
- if (document.hidden) {
5754
- mapRefreshTimer = setTimeout(tick2, 1e4);
5755
- return;
5756
- }
5757
- mapRefreshTimer = 0;
5758
- if (enabled2 && resolved) void fetchPinned().then((m) => {
5759
- if (m) scan2();
5760
- });
5761
- };
5762
- mapRefreshTimer = setTimeout(tick2, MAP_REFRESH_MS);
5763
- }
5764
- var editingKey = null;
5765
- var bootDelay = 0;
5766
- function scheduleBootRetry() {
5767
- bootDelay = bootDelay ? Math.min(bootDelay * 2, MAP_REFRESH_MS) : BOOT_RETRY_MS;
5768
- setTimeout(() => {
5769
- if (resolved) return;
5770
- if (!document.hidden) void bootstrap();
5771
- scheduleBootRetry();
5772
- }, bootDelay);
5773
- }
5774
- async function bootstrap() {
5775
- const map = await fetchPinned();
5776
- if (map) scan2();
6852
+ const run = () => peekInner(ws, opts);
6853
+ const p = peekChain.then(run, run);
6854
+ peekChain = p.catch(() => {
6855
+ });
6856
+ return p;
5777
6857
  }
5778
- function setRowTitle(key, text) {
5779
- for (const item of findRows(key)) {
5780
- for (const el of titleElsOf(item)) writeTitle(el, text);
6858
+ async function peekInner(ws, opts) {
6859
+ if (!opts?.manual && !enabled2 || document.hidden || activePeeks.has(ws)) return false;
6860
+ const row = findWsRow(ws);
6861
+ const head = row && wsHead(row);
6862
+ if (!row || !head || head.getAttribute("aria-expanded") !== "false") return false;
6863
+ if (!currentTaskRow()) {
6864
+ schedulePeek(ws, PEEK_REPEAT_MS);
6865
+ return false;
6866
+ }
6867
+ activePeeks.add(ws);
6868
+ peekedWs.add(ws);
6869
+ const rect = row.getBoundingClientRect();
6870
+ const frozen = row.cloneNode(true);
6871
+ frozen.classList.add("zcodepro-ws-frozen");
6872
+ frozen.style.left = rect.left + "px";
6873
+ frozen.style.top = rect.top + "px";
6874
+ frozen.style.width = rect.width + "px";
6875
+ frozen.style.height = rect.height + "px";
6876
+ document.body.append(frozen);
6877
+ row.style.visibility = "hidden";
6878
+ const hider = installPeekHider(ws);
6879
+ let readOk = false;
6880
+ try {
6881
+ toggleNavSafe(head);
6882
+ readOk = await settleAndRead(ws);
6883
+ const rowNow = findWsRow(ws) || row;
6884
+ const headNow = wsHead(rowNow);
6885
+ if (headNow && headNow.getAttribute("aria-expanded") === "true" && !userTouched(rowNow)) {
6886
+ toggleNavSafe(headNow);
6887
+ await waitForRowsGone(ws);
6888
+ }
6889
+ } finally {
6890
+ removePeekHider(hider);
6891
+ row.style.visibility = "";
6892
+ frozen.remove();
6893
+ activePeeks.delete(ws);
5781
6894
  }
6895
+ apply();
6896
+ const set = runningByWs.get(ws);
6897
+ if (set && set.size && enabled2) schedulePeek(ws, PEEK_REPEAT_MS);
6898
+ return readOk;
5782
6899
  }
5783
- function refreshPinnedTitles() {
5784
- return fetchPinned().then((m) => {
5785
- if (m) scan2();
5786
- });
6900
+ function wsRunningSnapshot() {
6901
+ const curKey = currentTaskRow()?.getAttribute("data-task-item-key") || "";
6902
+ return {
6903
+ running: [...runningByWs.entries()].filter(([, set]) => set.size > 0).map(([ws]) => ws),
6904
+ current: curKey ? workspaceOf(curKey) || null : null
6905
+ };
5787
6906
  }
5788
- var RENAME_CTX_VALID_MS = 15e3;
5789
- var renameCtx = null;
5790
- var renamePrefilled = /* @__PURE__ */ new WeakSet();
5791
- function setNativeValue(el, text) {
5792
- if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) {
5793
- Object.getOwnPropertyDescriptor(el.constructor.prototype, "value").set.call(el, text);
5794
- } else {
5795
- el.textContent = text;
5796
- }
5797
- el.dispatchEvent(new Event("input", { bubbles: true }));
6907
+ function wsRunningKeys() {
6908
+ const keys = /* @__PURE__ */ new Set();
6909
+ for (const set of runningByWs.values()) for (const k of set) keys.add(k);
6910
+ return keys;
5798
6911
  }
5799
- function prefillRenameDialog() {
5800
- if (!renameCtx || !renameCtx.armed || Date.now() - renameCtx.at > RENAME_CTX_VALID_MS) {
5801
- renameCtx = null;
5802
- return;
6912
+ async function verifyWorkspaceNotRunning(ws) {
6913
+ if (!ws || document.hidden) return false;
6914
+ const row = findWsRow(ws);
6915
+ const head = row && wsHead(row);
6916
+ if (!row || !head) return false;
6917
+ if (head.getAttribute("aria-expanded") !== "false") {
6918
+ let run = false;
6919
+ for (const el of domRowsOf(ws)) if (isRunningRow(el)) run = true;
6920
+ return !run;
5803
6921
  }
5804
- if (!enabled2 || !pinned) return;
5805
- const want = pinned.get(sessIdOf(renameCtx.key));
5806
- if (want === void 0) return;
5807
- for (const el of document.querySelectorAll('[role="dialog"] input, [role="dialog"] textarea, [role="dialog"] [contenteditable="true"]')) {
5808
- if (renamePrefilled.has(el) || !el.getClientRects().length) continue;
5809
- if (el.closest(TASK_SEL) || el.closest(".zcodepro-overlay")) continue;
5810
- const cur = String(el.value ?? el.textContent ?? "").trim();
5811
- if (!cur) continue;
5812
- renamePrefilled.add(el);
5813
- if (cur !== String(want).trim()) setNativeValue(el, String(want));
5814
- renameCtx = null;
5815
- return;
6922
+ let verified = false;
6923
+ try {
6924
+ verified = await peek(ws, { manual: true }) === true;
6925
+ } catch {
5816
6926
  }
6927
+ if (!verified) return false;
6928
+ const set = runningByWs.get(ws);
6929
+ return !(set && set.size);
5817
6930
  }
5818
- function startTitleLock() {
5819
- if (installed2 || typeof document === "undefined") return;
5820
- installed2 = true;
5821
- void bootstrap();
5822
- new MutationObserver((muts) => {
5823
- if (renameCtx) prefillRenameDialog();
5824
- if (!taskRowMutations2(muts)) return;
5825
- refreshMapSoon();
5826
- scheduleScan2();
5827
- }).observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ["class"], characterData: true });
5828
- document.addEventListener("contextmenu", (e) => {
5829
- const row = e.target instanceof Element && e.target.closest(TASK_SEL);
5830
- renameCtx = row ? { key: row.getAttribute("data-task-item-key") || "", at: 0, armed: false } : null;
5831
- }, true);
5832
- document.addEventListener("click", (e) => {
5833
- if (!renameCtx || renameCtx.armed) return;
5834
- const item = e.target instanceof Element && e.target.closest('[role="menuitem"], [data-radix-collection-item]');
5835
- if (item && /重命名|rename/i.test(item.textContent || "")) {
5836
- renameCtx.armed = true;
5837
- renameCtx.at = Date.now();
5838
- }
5839
- }, true);
5840
- scheduleBootRetry();
5841
- setInterval(() => {
5842
- if (!document.hidden) scan2();
5843
- }, SCAN_FALLBACK_MS2);
5844
- document.addEventListener("visibilitychange", () => {
5845
- if (!document.hidden) scan2();
5846
- });
5847
- window.addEventListener("zcodepro:config-changed", () => {
5848
- void bootstrap();
5849
- });
5850
- document.addEventListener("focusin", (e) => {
5851
- const el = e.target;
5852
- if (!(el instanceof Element)) return;
5853
- if (!el.matches('input, textarea, [contenteditable="true"]')) return;
5854
- const row = el.closest(TASK_SEL);
5855
- editingKey = row ? row.getAttribute("data-task-item-key") || "" : null;
5856
- });
5857
- document.addEventListener("focusout", () => {
5858
- if (editingKey === null) return;
5859
- editingKey = null;
5860
- void fetchPinned().then((m) => {
5861
- if (m) scan2();
5862
- });
6931
+ function settleAndRead(ws) {
6932
+ return new Promise((resolve) => {
6933
+ const t0 = Date.now();
6934
+ let lastCount = -1;
6935
+ let stableAt = 0;
6936
+ let zeroRows = false;
6937
+ const timer = setInterval(() => {
6938
+ const rows = domRowsOf(ws);
6939
+ const elapsed = Date.now() - t0;
6940
+ if (rows.length !== lastCount) {
6941
+ lastCount = rows.length;
6942
+ stableAt = Date.now();
6943
+ }
6944
+ const settled = rows.length > 0 && rows.length === lastCount && Date.now() - stableAt >= PEEK_SETTLE_MS;
6945
+ if (!settled && elapsed < PEEK_MAX_MS) return;
6946
+ clearInterval(timer);
6947
+ if (lastCount === 0) {
6948
+ zeroRows = true;
6949
+ } else {
6950
+ let set = runningByWs.get(ws);
6951
+ if (!set) {
6952
+ set = /* @__PURE__ */ new Set();
6953
+ runningByWs.set(ws, set);
6954
+ }
6955
+ for (const el of rows) {
6956
+ const key = el.getAttribute("data-task-item-key") || "";
6957
+ if (isRunningRow(el)) set.add(key);
6958
+ else set.delete(key);
6959
+ }
6960
+ }
6961
+ resolve(!zeroRows);
6962
+ }, 80);
5863
6963
  });
5864
6964
  }
5865
-
5866
- // src/inject/features/title-gen.js
5867
- var CONV2 = '[class*="@md/conversation"]';
5868
- var USER_ROW = '[class*="user-row"]';
5869
- var SCAN_DEBOUNCE_MS3 = 300;
5870
- var SCAN_FALLBACK_MS3 = 5e3;
5871
- var SELECT_WAIT_MS = 2e3;
5872
- var MSG_WAIT_MS = 1e4;
5873
- var QUIET_FALLBACK_MS = 12e4;
5874
- var COLLECT_MAX_MS = 6e5;
5875
- var installed3 = false;
5876
- var knownKeys = /* @__PURE__ */ new Set();
5877
- var attempted = /* @__PURE__ */ new Set();
5878
- var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
5879
- function sessIdOf2(key) {
5880
- const i = key.indexOf(":sess_");
5881
- return i > 0 ? key.slice(i + 1) : "";
6965
+ var PEEK_EXEMPT_CLASS = "zcodepro-peek-exempt";
6966
+ var cssAttr = (v) => String(v).replace(/\\/g, "\\\\").replace(/"/g, '\\"');
6967
+ function installPeekHider(ws) {
6968
+ const rowSel3 = `${TASK_SEL}[data-task-item-key^="${cssAttr(ws + ":")}"]`;
6969
+ for (const r of document.querySelectorAll(rowSel3)) r.classList.add(PEEK_EXEMPT_CLASS);
6970
+ const style = document.createElement("style");
6971
+ style.textContent = `${rowSel3}:not(.${PEEK_EXEMPT_CLASS}){display:none!important}`;
6972
+ document.head.append(style);
6973
+ return { style, sel: rowSel3 };
5882
6974
  }
5883
- function currentKey() {
5884
- for (const el of document.querySelectorAll("[data-task-item-key]")) {
5885
- if ((el.className + "").includes("bg-selected")) return el.getAttribute("data-task-item-key") || "";
5886
- }
5887
- return "";
6975
+ function removePeekHider(hider) {
6976
+ hider.style.remove();
6977
+ for (const r of document.querySelectorAll("." + PEEK_EXEMPT_CLASS)) r.classList.remove(PEEK_EXEMPT_CLASS);
5888
6978
  }
5889
- function userTexts() {
5890
- return [...document.querySelectorAll(`${CONV2} ${USER_ROW}`)].map((r) => {
5891
- const el = r.querySelector(".whitespace-pre-wrap");
5892
- return ((el ? el.textContent : r.innerText) || "").trim();
5893
- }).filter(Boolean);
6979
+ function waitForRowsGone(ws) {
6980
+ const sel = `${TASK_SEL}[data-task-item-key^="${cssAttr(ws + ":")}"]:not(.${PEEK_EXEMPT_CLASS})`;
6981
+ return new Promise((resolve) => {
6982
+ const t0 = Date.now();
6983
+ const timer = setInterval(() => {
6984
+ if (!document.querySelector(sel) || Date.now() - t0 >= PEEK_UNMOUNT_WAIT_MS) {
6985
+ clearInterval(timer);
6986
+ resolve();
6987
+ }
6988
+ }, 40);
6989
+ });
5894
6990
  }
5895
- var URL_RE = /https?:\/\/[^\s\u4e00-\u9fff\u3000-\u303f\uff00-\uffef]+/g;
5896
- function assistantText() {
5897
- const els = [...document.querySelectorAll('[class*="@md/conversation"] [class*="assistant-row"]')].filter((e) => !e.querySelector("[data-is-streaming]"));
5898
- const last = els[els.length - 1];
5899
- if (!last) return "";
5900
- const text = (last.innerText || "").replace(URL_RE, "").trim();
5901
- return text.slice(0, 600);
6991
+ var TOUCH_WINDOW_MS = 800;
6992
+ var wsTouchAt = /* @__PURE__ */ new WeakMap();
6993
+ function userTouched(row) {
6994
+ const at = wsTouchAt.get(row);
6995
+ return !!at && Date.now() - at < TOUCH_WINDOW_MS;
5902
6996
  }
5903
- function linkDominant(t2) {
5904
- const raw = t2.replace(/\s+/g, "");
5905
- if (!raw) return true;
5906
- const stripped = raw.replace(URL_RE, "");
5907
- return stripped.length < raw.length / 3;
6997
+ function inSel(node, sel) {
6998
+ return !!(node instanceof Element && node.closest && node.closest(sel));
5908
6999
  }
5909
- async function onNewTaskRow(key) {
5910
- const selDeadline = Date.now() + SELECT_WAIT_MS;
5911
- while (currentKey() !== key && Date.now() < selDeadline) await sleep(200);
5912
- if (currentKey() !== key) return;
5913
- const cfg = await getConfig().catch(() => null);
5914
- if (!cfg || !cfg.titleGen || cfg.titleGen.enabled !== true) return;
5915
- const sessId = sessIdOf2(key);
5916
- if (!sessId || attempted.has(sessId)) return;
5917
- attempted.add(sessId);
5918
- let texts = [];
5919
- const msgDeadline = Date.now() + MSG_WAIT_MS;
5920
- while (Date.now() < msgDeadline) {
5921
- texts = userTexts();
5922
- if (texts.length) break;
5923
- await sleep(400);
5924
- }
5925
- if (!texts.length) return;
5926
- let assistantSeen = false;
5927
- const convObserver = new MutationObserver((muts) => {
5928
- if (currentKey() !== key) return;
5929
- for (const m of muts) {
5930
- const t2 = m.type === "characterData" ? m.target.parentElement : m.target;
5931
- if (t2 instanceof Element && t2.closest(USER_ROW)) continue;
5932
- for (const n of [t2, ...m.addedNodes]) {
5933
- if (n instanceof Element && n.closest && n.closest(CONV2)) {
5934
- assistantSeen = true;
5935
- return;
5936
- }
5937
- }
5938
- }
5939
- });
5940
- convObserver.observe(document.body, { childList: true, subtree: true, characterData: true });
5941
- const startAt = Date.now();
5942
- let lastCount = texts.length;
5943
- let lastChange = Date.now();
7000
+ function relevant(muts) {
7001
+ for (const m of muts) {
7002
+ if (inSel(m.target, TASK_SEL) || inSel(m.target, WS_SEL())) return true;
7003
+ for (const n of m.addedNodes) {
7004
+ if (n instanceof Element && (n.matches(TASK_SEL) || n.matches(WS_SEL()) || n.querySelector(TASK_SEL) || n.querySelector(WS_SEL()))) return true;
7005
+ }
7006
+ for (const n of m.removedNodes) {
7007
+ if (n instanceof Element && (n.matches(TASK_SEL) || n.matches(WS_SEL()) || n.querySelector(TASK_SEL))) return true;
7008
+ }
7009
+ }
7010
+ return false;
7011
+ }
7012
+ var pollMs = CONFIG_POLL_MIN_MS;
7013
+ async function refreshConfig() {
5944
7014
  try {
5945
- while (Date.now() - startAt < COLLECT_MAX_MS) {
5946
- await sleep(500);
5947
- if (currentKey() !== key) {
5948
- lastChange = Date.now();
5949
- continue;
5950
- }
5951
- texts = userTexts();
5952
- if (texts.length !== lastCount) {
5953
- lastCount = texts.length;
5954
- lastChange = Date.now();
7015
+ const res = await rpc("/config");
7016
+ if (res && res.ok && res.config && res.config.features) {
7017
+ const next = res.config.features.wsRunningSpin !== false;
7018
+ if (next !== enabled2) {
7019
+ enabled2 = next;
7020
+ if (!next) {
7021
+ for (const ws of [...peekTimers.keys()]) clearPeek(ws);
7022
+ apply();
7023
+ }
7024
+ return true;
5955
7025
  }
5956
- if (assistantSeen) break;
5957
- if (Date.now() - lastChange >= QUIET_FALLBACK_MS) break;
5958
7026
  }
5959
- } finally {
5960
- convObserver.disconnect();
7027
+ } catch {
5961
7028
  }
5962
- if (!texts.length) return;
5963
- if (linkDominant(texts.join("\n"))) {
5964
- const deadline = Date.now() + 6e4;
5965
- while (Date.now() < deadline) {
5966
- await sleep(2e3);
5967
- if (currentKey() !== key) continue;
5968
- const a = assistantText();
5969
- if (a && a.length > 80) {
5970
- texts.push(`\uFF08\u9996\u8F6E\u56DE\u7B54\u6458\u8981\uFF09${a}`);
5971
- break;
7029
+ return false;
7030
+ }
7031
+ function pollConfig() {
7032
+ setTimeout(async () => {
7033
+ if (document.hidden) pollMs = CONFIG_POLL_MAX_MS;
7034
+ else pollMs = await refreshConfig() ? CONFIG_POLL_MIN_MS : Math.min(Math.round(pollMs * 1.5), CONFIG_POLL_MAX_MS);
7035
+ pollConfig();
7036
+ }, pollMs);
7037
+ }
7038
+ var scanDebounce = 0;
7039
+ function startWsRunningSpin() {
7040
+ if (installed2 || typeof document === "undefined") return;
7041
+ installed2 = true;
7042
+ void refreshConfig();
7043
+ pollConfig();
7044
+ const observer2 = new MutationObserver((muts) => {
7045
+ if (!relevant(muts)) return;
7046
+ handleRemovals(muts);
7047
+ if (!scanDebounce) {
7048
+ scanDebounce = setTimeout(() => {
7049
+ scanDebounce = 0;
7050
+ scan();
7051
+ }, SCAN_DEBOUNCE_MS);
7052
+ }
7053
+ });
7054
+ observer2.observe(document.body, { childList: true, subtree: true });
7055
+ scan();
7056
+ setInterval(() => {
7057
+ if (!document.hidden && enabled2) scan();
7058
+ }, SCAN_FALLBACK_MS);
7059
+ document.addEventListener("visibilitychange", () => {
7060
+ if (!document.hidden) scan();
7061
+ });
7062
+ document.addEventListener("click", (e) => {
7063
+ if (!e.isTrusted) return;
7064
+ const row = e.target instanceof Element && e.target.closest(WS_SEL());
7065
+ if (row) wsTouchAt.set(row, Date.now());
7066
+ }, true);
7067
+ }
7068
+
7069
+ // src/inject/features/session-switch.js
7070
+ var MRU_LIMIT = 50;
7071
+ var SCAN_DEBOUNCE_MS2 = 150;
7072
+ var SCAN_FALLBACK_MS2 = 5e3;
7073
+ var INITIAL_COUNT = 10;
7074
+ var PAGE_SIZE = 5;
7075
+ var UNSEEN_LIMIT = 50;
7076
+ var mru = [];
7077
+ var current = null;
7078
+ var titles = /* @__PURE__ */ new Map();
7079
+ var popupEl = null;
7080
+ var aliasesCache = {};
7081
+ var highlight = 0;
7082
+ var scanDebounce2 = 0;
7083
+ var popupMode = "mru";
7084
+ var visibleCount = INITIAL_COUNT;
7085
+ var unseen = /* @__PURE__ */ new Set();
7086
+ var lastRunKeys = null;
7087
+ var movedSinceOpen = false;
7088
+ var hotkeys = { ...DEFAULT_SHORTCUTS };
7089
+ function loadHotkeys() {
7090
+ return getConfig(true).then((cfg) => {
7091
+ const sc = cfg && cfg.shortcuts;
7092
+ if (sc && typeof sc === "object") {
7093
+ for (const k of Object.keys(DEFAULT_SHORTCUTS)) {
7094
+ if (typeof sc[k] === "string" && /^[a-z0-9]$/.test(sc[k])) hotkeys[k] = sc[k];
5972
7095
  }
5973
7096
  }
7097
+ }).catch(() => {
7098
+ });
7099
+ }
7100
+ function firstLineOf(item) {
7101
+ const tEl = item.querySelector('[class*="truncate"]');
7102
+ return (tEl ? tEl.textContent : (item.innerText || "").split("\n")[0])?.trim() || "";
7103
+ }
7104
+ function workspaceOf2(key) {
7105
+ const i = key.indexOf(":sess_");
7106
+ return i > 0 ? key.slice(0, i) : key;
7107
+ }
7108
+ function findCurrent() {
7109
+ for (const el of document.querySelectorAll("[data-task-item-key]")) {
7110
+ if ((el.className + "").includes("bg-selected")) return el;
5974
7111
  }
5975
- const callTitleGen = () => rpc("/title-gen/run", { method: "POST", body: { sessId, text: texts.join("\n") } }).catch(() => null);
5976
- let res = await callTitleGen();
5977
- for (const delay of [5e3, 2e4]) {
5978
- if (res && res.ok) break;
5979
- await sleep(delay);
5980
- res = await callTitleGen();
5981
- }
5982
- if (res && res.ok && res.written && res.title) {
5983
- setRowTitle(key, res.title);
5984
- void refreshPinnedTitles();
7112
+ return null;
7113
+ }
7114
+ function record(key) {
7115
+ if (key) unseen.delete(key);
7116
+ if (key && key !== current) {
7117
+ current = key;
7118
+ mru = mru.filter((k) => k !== key);
7119
+ mru.unshift(key);
7120
+ if (mru.length > MRU_LIMIT) mru.length = MRU_LIMIT;
5985
7121
  }
5986
7122
  }
5987
- function scanForNewRows() {
7123
+ function scan2() {
7124
+ const cur = findCurrent();
7125
+ if (cur) record(cur.getAttribute("data-task-item-key"));
5988
7126
  for (const el of document.querySelectorAll("[data-task-item-key]")) {
5989
- const key = el.getAttribute("data-task-item-key") || "";
5990
- if (!key || knownKeys.has(key)) continue;
5991
- knownKeys.add(key);
5992
- void onNewTaskRow(key);
7127
+ titles.set(el.getAttribute("data-task-item-key"), firstLineOf(el));
5993
7128
  }
5994
7129
  }
5995
- var TASK_SEL2 = "[data-task-item-key]";
5996
- var inTaskRow3 = (node) => !!(node instanceof Element && node.closest && node.closest(TASK_SEL2));
5997
- function taskRowMutations3(muts) {
7130
+ var rowSel2 = "[data-task-item-key]";
7131
+ var inTaskRow = (node) => node instanceof Element && !!node.closest && !!node.closest(rowSel2);
7132
+ function taskRowMutations(muts) {
5998
7133
  for (const m of muts) {
5999
7134
  if (m.type === "attributes") {
6000
- if (inTaskRow3(m.target)) return true;
7135
+ if (inTaskRow(m.target)) return true;
6001
7136
  } else if (m.type === "characterData") {
6002
- if (m.target.parentElement && inTaskRow3(m.target.parentElement)) return true;
6003
- } else if (inTaskRow3(m.target)) {
7137
+ if (m.target.parentElement && inTaskRow(m.target.parentElement)) return true;
7138
+ } else if (inTaskRow(m.target)) {
6004
7139
  return true;
6005
7140
  } else {
6006
7141
  for (const n of m.addedNodes) {
6007
- if (n instanceof Element && (n.matches(TASK_SEL2) || !!n.querySelector(TASK_SEL2))) return true;
7142
+ if (n instanceof Element && (n.matches(rowSel2) || !!n.querySelector(rowSel2))) return true;
6008
7143
  }
6009
7144
  }
6010
7145
  }
6011
7146
  return false;
6012
7147
  }
6013
- var scanDebounce3 = 0;
6014
- function scheduleScan3() {
6015
- if (scanDebounce3) return;
6016
- scanDebounce3 = setTimeout(() => {
6017
- scanDebounce3 = 0;
6018
- scanForNewRows();
6019
- }, SCAN_DEBOUNCE_MS3);
7148
+ function scheduleScan() {
7149
+ if (scanDebounce2) return;
7150
+ scanDebounce2 = setTimeout(() => {
7151
+ scanDebounce2 = 0;
7152
+ scan2();
7153
+ }, SCAN_DEBOUNCE_MS2);
6020
7154
  }
6021
- function startTitleGen() {
6022
- if (installed3 || typeof document === "undefined") return;
6023
- installed3 = true;
6024
- scanForNewRows();
7155
+ async function switchTo(key) {
7156
+ const find = () => document.querySelector(`[data-task-item-key="${CSS.escape(key)}"]`);
7157
+ let item = find();
7158
+ if (!item) {
7159
+ const ws = workspaceOf2(key);
7160
+ const header = findProjectRow(ws);
7161
+ const expander = header && header.querySelector("[aria-expanded]");
7162
+ if (expander && expander.getAttribute("aria-expanded") === "false") {
7163
+ expander.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true }));
7164
+ for (let i = 0; i < 20 && !(item = find()); i++) await new Promise((r) => setTimeout(r, 100));
7165
+ }
7166
+ }
7167
+ if (!item) return false;
7168
+ item.scrollIntoView({ block: "nearest" });
7169
+ item.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true }));
7170
+ return true;
7171
+ }
7172
+ function attentionList() {
7173
+ const run = wsRunningKeys();
7174
+ const listed = /* @__PURE__ */ new Set();
7175
+ const head = [];
7176
+ for (const k of mru) {
7177
+ if (run.has(k) || unseen.has(k)) {
7178
+ head.push(k);
7179
+ listed.add(k);
7180
+ }
7181
+ }
7182
+ const tail = [];
7183
+ for (const k of run) if (!listed.has(k)) {
7184
+ tail.push(k);
7185
+ listed.add(k);
7186
+ }
7187
+ for (const k of unseen) if (!listed.has(k)) {
7188
+ tail.push(k);
7189
+ listed.add(k);
7190
+ }
7191
+ return [...head, ...tail];
7192
+ }
7193
+ function activeList() {
7194
+ return popupMode === "attention" ? attentionList() : mru;
7195
+ }
7196
+ function closePopup(commit) {
7197
+ if (!popupEl) return;
7198
+ const idx = highlight;
7199
+ const list = activeList();
7200
+ popupEl.remove();
7201
+ popupEl = null;
7202
+ if (commit && list[idx] && list[idx] !== current) {
7203
+ void switchTo(list[idx]).then((ok) => {
7204
+ if (!ok) showToast(t().switcherFailed, "error");
7205
+ });
7206
+ }
7207
+ }
7208
+ function findTitle(key) {
7209
+ const item = document.querySelector(`[data-task-item-key="${CSS.escape(key)}"]`);
7210
+ if (item) titles.set(key, firstLineOf(item));
7211
+ const id = key.slice(key.indexOf(":sess_") + 1);
7212
+ return titles.get(key) || `sess ${id.slice(0, 8)}`;
7213
+ }
7214
+ function projLabelOf(key, aliases2) {
7215
+ const ws = workspaceOf2(key);
7216
+ if (!ws) return "";
7217
+ return aliases2[ws] || ws.replace(/[\\/]+$/, "").split(/[\\/]/).pop();
7218
+ }
7219
+ function renderPopup() {
7220
+ if (!popupEl) return;
7221
+ const L = t();
7222
+ const list = popupEl.querySelector("[data-zcodepro-switcher-list]");
7223
+ const full = activeList();
7224
+ const shown = full.slice(0, visibleCount);
7225
+ const run = popupMode === "attention" ? wsRunningKeys() : null;
7226
+ list.replaceChildren(...shown.map((key) => {
7227
+ const proj = projLabelOf(key, aliasesCache);
7228
+ return h(
7229
+ "div",
7230
+ {
7231
+ class: "zcodepro-switcher-row",
7232
+ "data-active": full[highlight] === key ? "" : void 0,
7233
+ onClick: () => {
7234
+ highlight = full.indexOf(key);
7235
+ closePopup(true);
7236
+ }
7237
+ },
7238
+ // 标题文本包 span:截断省略作用在文本节点上(flex 容器自身 ellipsis 无效)
7239
+ h(
7240
+ "div",
7241
+ { class: "zcodepro-switcher-title" },
7242
+ key === current ? h("span", { class: "zcodepro-switcher-tag" }, L.switcherCurrent) : null,
7243
+ run && run.has(key) ? h("span", { class: "zcodepro-switcher-tag zcodepro-switcher-tag-run" }, L.switcherRunning) : null,
7244
+ run && !run.has(key) ? h("span", { class: "zcodepro-switcher-tag zcodepro-switcher-tag-new" }, L.switcherUnseen) : null,
7245
+ h("span", null, findTitle(key))
7246
+ ),
7247
+ // 无项目归属(如置顶)时不渲染空行
7248
+ proj ? h("div", { class: "zcodepro-switcher-ws" }, proj) : null
7249
+ );
7250
+ }));
7251
+ if (full.length > shown.length) {
7252
+ list.append(h(
7253
+ "div",
7254
+ { class: "zcodepro-switcher-more" },
7255
+ L.switcherMore.replaceAll("{n}", String(full.length - shown.length))
7256
+ ));
7257
+ }
7258
+ }
7259
+ async function openPopup(mode = "mru") {
7260
+ if (popupEl) return;
7261
+ popupMode = mode;
7262
+ visibleCount = INITIAL_COUNT;
7263
+ highlight = 0;
7264
+ movedSinceOpen = false;
7265
+ const cfg = await getConfig().catch(() => null);
7266
+ aliasesCache = cfg && cfg.aliases && typeof cfg.aliases === "object" ? cfg.aliases : {};
7267
+ ensureStyle();
7268
+ const L = t();
7269
+ popupEl = h(
7270
+ "div",
7271
+ {
7272
+ class: "zcodepro-switcher-overlay",
7273
+ // 点弹窗外 = 取消(不切)
7274
+ onMousedown: (e) => {
7275
+ if (e.target === popupEl) closePopup(false);
7276
+ }
7277
+ },
7278
+ h(
7279
+ "div",
7280
+ { class: "zcodepro-switcher" },
7281
+ // 提示行键位跟随当前配置(改键后新开的弹窗即用新键位)
7282
+ h(
7283
+ "div",
7284
+ { class: "zcodepro-switcher-hint" },
7285
+ (mode === "attention" ? L.switcherAttentionHint : L.switcherHint).replaceAll("{prev}", hotkeys.switchPrev).replaceAll("{next}", hotkeys.switchNext)
7286
+ ),
7287
+ h("div", { "data-zcodepro-switcher-list": "1", class: "zcodepro-switcher-list" })
7288
+ )
7289
+ );
7290
+ document.body.append(popupEl);
7291
+ renderPopup();
7292
+ }
7293
+ function scrollHighlight() {
7294
+ const row = popupEl && popupEl.querySelector("[data-active]");
7295
+ if (row) row.scrollIntoView({ block: "nearest" });
7296
+ }
7297
+ function moveUp() {
7298
+ const len = Math.min(activeList().length, visibleCount);
7299
+ if (len < 2) return;
7300
+ highlight = highlight > 0 ? highlight - 1 : len - 1;
7301
+ movedSinceOpen = true;
7302
+ renderPopup();
7303
+ scrollHighlight();
7304
+ }
7305
+ function moveDown() {
7306
+ const full = activeList();
7307
+ const len = Math.min(full.length, visibleCount);
7308
+ if (len < 2) return;
7309
+ if (highlight < len - 1) {
7310
+ highlight += 1;
7311
+ } else if (visibleCount < full.length) {
7312
+ visibleCount = Math.min(visibleCount + PAGE_SIZE, full.length);
7313
+ highlight = len;
7314
+ } else {
7315
+ highlight = 0;
7316
+ }
7317
+ movedSinceOpen = true;
7318
+ renderPopup();
7319
+ scrollHighlight();
7320
+ }
7321
+ function startSessionSwitch() {
7322
+ scan2();
7323
+ void loadHotkeys();
7324
+ window.addEventListener("zcodepro:config-changed", () => {
7325
+ void loadHotkeys();
7326
+ });
6025
7327
  new MutationObserver((muts) => {
6026
- if (taskRowMutations3(muts)) scheduleScan3();
6027
- }).observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ["class"], characterData: true });
7328
+ if (taskRowMutations(muts)) scheduleScan();
7329
+ }).observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ["class"] });
6028
7330
  setInterval(() => {
6029
- if (!document.hidden) scanForNewRows();
6030
- }, SCAN_FALLBACK_MS3);
7331
+ if (!document.hidden) scan2();
7332
+ }, SCAN_FALLBACK_MS2);
6031
7333
  document.addEventListener("visibilitychange", () => {
6032
- if (!document.hidden) scanForNewRows();
7334
+ if (!document.hidden) scan2();
6033
7335
  });
6034
- }
6035
-
6036
- // src/inject/features/pinned-expand.js
6037
- var installed4 = false;
6038
- var keepCollapsed = false;
6039
- var CONFIG_POLL_MIN_MS = 5e3;
6040
- var CONFIG_POLL_MAX_MS = 6e4;
6041
- var pollMs = CONFIG_POLL_MIN_MS;
6042
- async function refreshConfig() {
6043
- try {
6044
- const res = await rpc("/config");
6045
- if (res && res.ok && res.config && res.config.features) {
6046
- const next = res.config.features.pinnedKeepCollapsed !== false;
6047
- if (next !== keepCollapsed) {
6048
- keepCollapsed = next;
6049
- return true;
7336
+ setInterval(() => {
7337
+ if (document.hidden) return;
7338
+ const now = wsRunningKeys();
7339
+ if (lastRunKeys) {
7340
+ for (const k of lastRunKeys) {
7341
+ if (!now.has(k) && k !== current) unseen.add(k);
6050
7342
  }
6051
7343
  }
6052
- } catch {
6053
- }
6054
- return false;
6055
- }
6056
- function pollConfig() {
6057
- setTimeout(async () => {
6058
- if (document.hidden) pollMs = CONFIG_POLL_MAX_MS;
6059
- else pollMs = await refreshConfig() ? CONFIG_POLL_MIN_MS : Math.min(Math.round(pollMs * 1.5), CONFIG_POLL_MAX_MS);
6060
- pollConfig();
6061
- }, pollMs);
6062
- }
6063
- function startPinnedExpandSuppression() {
6064
- if (installed4 || typeof document === "undefined") return;
6065
- installed4 = true;
6066
- void refreshConfig();
6067
- pollConfig();
6068
- document.addEventListener("visibilitychange", () => {
6069
- if (!document.hidden) void refreshConfig();
6070
- });
6071
- document.addEventListener("click", (e) => {
6072
- if (!keepCollapsed) return;
6073
- const taskRow = e.target.closest && e.target.closest('[data-testid^="task-item-"]');
6074
- if (!taskRow) return;
6075
- const key = taskRow.getAttribute("data-task-item-key") || "";
6076
- const sep = key.lastIndexOf(":");
6077
- if (sep <= 0) return;
6078
- const wsKey = key.slice(0, sep);
6079
- const wsRow = document.querySelector(`[data-testid="workspace-item-${cssEscape(wsKey)}"]`);
6080
- if (!wsRow || wsRow.getAttribute("aria-expanded") !== "false") return;
6081
- const head = wsRow.matches("[aria-expanded]") ? wsRow : wsRow.querySelector("[aria-expanded]");
6082
- if (!head) return;
6083
- collapseAfterContentLoaded(wsRow);
6084
- }, true);
7344
+ lastRunKeys = now;
7345
+ if (unseen.size > UNSEEN_LIMIT) unseen = new Set([...unseen].slice(-UNSEEN_LIMIT));
7346
+ }, 1e4);
6085
7347
  document.addEventListener("click", (e) => {
6086
- const row = e.target.closest && e.target.closest('[data-testid^="workspace-item-"]');
6087
- if (row) row.__zcodeproUserTouched = Date.now();
7348
+ const item = e.target instanceof Element && e.target.closest("[data-task-item-key]");
7349
+ if (item) record(item.getAttribute("data-task-item-key"));
6088
7350
  }, true);
6089
- }
6090
- function currentTaskRow() {
6091
- for (const el of document.querySelectorAll("[data-task-item-key]")) {
6092
- if ((el.className + "").includes("bg-selected")) return el;
6093
- }
6094
- return null;
6095
- }
6096
- function collapseNavSafe(head) {
6097
- const cur = currentTaskRow();
6098
- const backKey = cur ? cur.getAttribute("data-task-item-key") : "";
6099
- head.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true }));
6100
- if (!backKey) return;
6101
- const back = cur && cur.isConnected ? cur : document.querySelector(`[data-task-item-key="${cssEscape(backKey)}"]`);
6102
- if (back) back.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true }));
6103
- }
6104
- function collapseAfterContentLoaded(wsRow) {
6105
- const deadline = Date.now() + 25e3;
6106
- let lastLen = (document.querySelector("main") || document.body).innerText.length;
6107
- let grewAt = 0;
6108
- let settledAt = 0;
6109
- const headOf = () => {
6110
- const h2 = wsRow.matches("[aria-expanded]") ? wsRow : wsRow.querySelector("[aria-expanded]");
6111
- return h2 || null;
6112
- };
6113
- const userTouched2 = () => wsRow.__zcodeproUserTouched && Date.now() - wsRow.__zcodeproUserTouched < 800;
6114
- const tick2 = () => {
6115
- if (!keepCollapsed || Date.now() > deadline || !wsRow.isConnected) return done();
6116
- const len = (document.querySelector("main") || document.body).innerText.length;
6117
- if (len > lastLen) grewAt = Date.now();
6118
- if (grewAt && Date.now() - grewAt >= 1e3) settledAt = settledAt || Date.now();
6119
- lastLen = len;
6120
- const head = headOf();
6121
- if (head && head.getAttribute("aria-expanded") === "true" && !userTouched2()) {
6122
- collapseNavSafe(head);
7351
+ document.addEventListener("keydown", (e) => {
7352
+ if (e.key === "Escape" && popupEl) {
7353
+ e.preventDefault();
7354
+ e.stopImmediatePropagation();
7355
+ closePopup(false);
7356
+ return;
6123
7357
  }
6124
- if (settledAt && Date.now() - settledAt >= 1e3) {
6125
- const h2 = headOf();
6126
- if (h2 && h2.getAttribute("aria-expanded") === "true" && !userTouched2()) {
6127
- collapseNavSafe(h2);
6128
- }
6129
- return done();
7358
+ if (popupEl && (e.key === "ArrowUp" || e.key === "ArrowDown" || e.key === "Enter")) {
7359
+ e.preventDefault();
7360
+ e.stopImmediatePropagation();
7361
+ if (e.key === "ArrowUp") moveUp();
7362
+ else if (e.key === "ArrowDown") moveDown();
7363
+ else if (!e.repeat) closePopup(true);
7364
+ return;
6130
7365
  }
6131
- setTimeout(tick2, 200);
6132
- };
6133
- const done = () => {
6134
- wsRow.__zcodeproSuppending = false;
6135
- };
6136
- wsRow.__zcodeproSuppending = true;
6137
- setTimeout(tick2, 400);
6138
- }
6139
- function cssEscape(s) {
6140
- return String(s).replace(/(["\\\]])/g, "\\$1");
7366
+ if (!e.altKey || e.ctrlKey || e.metaKey || e.isComposing) return;
7367
+ if (document.querySelector(".zcodepro-overlay")) return;
7368
+ const k = e.key.toLowerCase();
7369
+ const isHot = k === hotkeys.switchLast || k === hotkeys.switchPrev || k === hotkeys.switchNext || k === hotkeys.switchRecent || k === hotkeys.switchAttention;
7370
+ if (!isHot) return;
7371
+ e.preventDefault();
7372
+ e.stopImmediatePropagation();
7373
+ void (async () => {
7374
+ const cfg = await getConfig();
7375
+ if (cfg.features && cfg.features.sessionSwitch === false) return;
7376
+ if (k === hotkeys.switchLast) {
7377
+ if (e.repeat) return;
7378
+ closePopup(false);
7379
+ const live = findCurrent();
7380
+ if (live) record(live.getAttribute("data-task-item-key"));
7381
+ const target = live ? mru[0] === current ? mru[1] : mru.find((mk) => mk !== current) : mru[0];
7382
+ if (!target) {
7383
+ showToast(t().switcherEmpty);
7384
+ return;
7385
+ }
7386
+ void switchTo(target).then((ok) => {
7387
+ if (!ok) showToast(t().switcherFailed, "error");
7388
+ });
7389
+ return;
7390
+ }
7391
+ if (k === hotkeys.switchAttention) {
7392
+ if (e.repeat) return;
7393
+ closePopup(false);
7394
+ if (!attentionList().length) {
7395
+ showToast(t().switcherAttentionEmpty);
7396
+ return;
7397
+ }
7398
+ await openPopup("attention");
7399
+ return;
7400
+ }
7401
+ if (mru.length < 2) {
7402
+ showToast(t().switcherEmpty);
7403
+ return;
7404
+ }
7405
+ if (k === hotkeys.switchRecent) {
7406
+ if (!e.repeat && !popupEl) await openPopup("mru");
7407
+ return;
7408
+ }
7409
+ if (!popupEl) await openPopup("mru");
7410
+ if (k === hotkeys.switchPrev) moveUp();
7411
+ else moveDown();
7412
+ })();
7413
+ }, true);
7414
+ document.addEventListener("keyup", (e) => {
7415
+ if (e.key === "Alt" && popupEl && movedSinceOpen) closePopup(true);
7416
+ }, true);
7417
+ window.addEventListener("blur", () => closePopup(false));
6141
7418
  }
6142
7419
 
6143
- // src/inject/features/ws-running.js
6144
- var installed5 = false;
7420
+ // src/inject/features/title-lock.js
7421
+ var TASK_SEL2 = "[data-task-item-key]";
7422
+ var SCAN_DEBOUNCE_MS3 = 150;
7423
+ var SCAN_FALLBACK_MS3 = 5e3;
7424
+ var BOOT_RETRY_MS = 5e3;
7425
+ var RECHECK_DELAY_MS = 1e3;
7426
+ var REVERIFY_MS = 3e3;
7427
+ var MAP_REFRESH_MS = 6e4;
7428
+ var installed3 = false;
7429
+ var seenRows = /* @__PURE__ */ new WeakSet();
6145
7430
  var enabled3 = true;
6146
- var SCAN_DEBOUNCE_MS4 = 150;
6147
- var SCAN_FALLBACK_MS4 = 5e3;
6148
- var PEEK_FIRST_MS = 2e4;
6149
- var PEEK_REPEAT_MS = 6e4;
6150
- var PEEK_SETTLE_MS = 160;
6151
- var PEEK_MAX_MS = 1500;
6152
- var PEEK_UNMOUNT_WAIT_MS = 800;
6153
- var CONFIG_POLL_MIN_MS2 = 5e3;
6154
- var CONFIG_POLL_MAX_MS2 = 6e4;
6155
- var TASK_SEL3 = "[data-task-item-key]";
6156
- var WS_SEL = '[data-testid^="workspace-item-"]';
6157
- var runningByWs = /* @__PURE__ */ new Map();
6158
- var peekTimers = /* @__PURE__ */ new Map();
6159
- var activePeeks = /* @__PURE__ */ new Set();
6160
- var peekedWs = /* @__PURE__ */ new Set();
6161
- var wsWithRows = /* @__PURE__ */ new Set();
6162
- function workspaceOf2(key) {
7431
+ var pinned = null;
7432
+ var resolved = false;
7433
+ var fetching = null;
7434
+ var pendingKeys = /* @__PURE__ */ new Set();
7435
+ function sessIdOf(key) {
6163
7436
  const i = key.indexOf(":sess_");
6164
- return i > 0 ? key.slice(0, i) : key;
6165
- }
6166
- function findWsRow(ws) {
6167
- return document.querySelector(`[data-testid="workspace-item-${CSS.escape(ws)}"]`);
6168
- }
6169
- function wsHead(row) {
6170
- return row.matches("[aria-expanded]") ? row : row.querySelector("[aria-expanded]");
6171
- }
6172
- function isRunningRow(el) {
6173
- const holder = el.firstElementChild && el.firstElementChild.querySelector(":scope > span");
6174
- if (!holder) return false;
6175
- return [...holder.children].some((c) => c.tagName === "svg" && (c.getAttribute("class") || "").includes("lucide-loader"));
7437
+ return i > 0 ? key.slice(i + 1) : key;
6176
7438
  }
6177
- function domRowsOf(ws) {
6178
- return [...document.querySelectorAll(TASK_SEL3)].filter((e) => workspaceOf2(e.getAttribute("data-task-item-key") || "") === ws);
7439
+ function titleElsOf(item) {
7440
+ const copies = item.querySelectorAll("[data-task-title-copy]");
7441
+ if (copies.length) return [...copies];
7442
+ const t2 = item.querySelector('[class*="truncate"]');
7443
+ return t2 ? [t2] : [];
6179
7444
  }
6180
- function currentTaskRow2() {
6181
- for (const el of document.querySelectorAll(TASK_SEL3)) {
6182
- if ((el.className + "").includes("bg-selected")) return el;
6183
- }
6184
- return null;
7445
+ function findRows(key) {
7446
+ return [...document.querySelectorAll(`${TASK_SEL2}[data-task-item-key="${CSS.escape(key)}"]`)];
6185
7447
  }
6186
- var synthClick = (el) => {
7448
+ async function fetchPinned() {
7449
+ if (fetching) return fetching;
7450
+ const p = (async () => {
7451
+ const res = await rpc("/pinned-titles");
7452
+ if (!res || !res.ok) return null;
7453
+ resolved = true;
7454
+ enabled3 = res.enabled !== false;
7455
+ if (!enabled3) {
7456
+ pinned = null;
7457
+ return null;
7458
+ }
7459
+ pinned = new Map(Object.entries(res.titles || {}));
7460
+ return pinned;
7461
+ })();
7462
+ fetching = p;
6187
7463
  try {
6188
- el.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true }));
6189
- } catch {
7464
+ return await p;
7465
+ } finally {
7466
+ if (fetching === p) fetching = null;
6190
7467
  }
6191
- };
6192
- function toggleNavSafe(head) {
6193
- const cur = currentTaskRow2();
6194
- const backKey = cur ? cur.getAttribute("data-task-item-key") : "";
6195
- synthClick(head);
6196
- if (!backKey) return;
6197
- const back = cur && cur.isConnected ? cur : document.querySelector(`${TASK_SEL3}[data-task-item-key="${CSS.escape(backKey)}"]`);
6198
- if (back) synthClick(back);
6199
7468
  }
6200
- function scan3() {
6201
- wsWithRows = /* @__PURE__ */ new Set();
6202
- for (const el of document.querySelectorAll(TASK_SEL3)) {
6203
- const key = el.getAttribute("data-task-item-key") || "";
6204
- const ws = workspaceOf2(key);
6205
- if (!ws) continue;
6206
- wsWithRows.add(ws);
6207
- const run = isRunningRow(el);
6208
- let set = runningByWs.get(ws);
6209
- if (run) {
6210
- if (!set) {
6211
- set = /* @__PURE__ */ new Set();
6212
- runningByWs.set(ws, set);
6213
- }
6214
- set.add(key);
6215
- } else if (set) {
6216
- set.delete(key);
6217
- }
6218
- }
6219
- syncPeeks();
6220
- apply();
7469
+ function isMismatch(item) {
7470
+ if (!enabled3 || !pinned) return null;
7471
+ const key = item.getAttribute("data-task-item-key") || "";
7472
+ const want = pinned.get(sessIdOf(key));
7473
+ if (want === void 0) return null;
7474
+ const els = titleElsOf(item);
7475
+ if (!els.length) return null;
7476
+ if ((els[0].textContent || "").trim() === String(want).trim()) return null;
7477
+ if (item.querySelector('input, textarea, [contenteditable="true"]')) return null;
7478
+ return key;
6221
7479
  }
6222
- function handleRemovals(muts) {
6223
- const removed = [];
6224
- for (const m of muts) {
6225
- if (m.type !== "childList") continue;
6226
- for (const n of m.removedNodes) {
6227
- if (!(n instanceof Element)) continue;
6228
- if (n.matches(TASK_SEL3)) removed.push(n);
6229
- else for (const r of n.querySelectorAll(TASK_SEL3)) removed.push(r);
7480
+ function writeTitle(el, text) {
7481
+ const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
7482
+ const first = walker.nextNode();
7483
+ if (first && !walker.nextNode()) first.nodeValue = text;
7484
+ else el.textContent = text;
7485
+ }
7486
+ function scheduleEnforce(key, { immediate = false } = {}) {
7487
+ if (immediate) {
7488
+ if (enabled3 && pinned) {
7489
+ const want = pinned.get(sessIdOf(key));
7490
+ if (want !== void 0) {
7491
+ for (const item of findRows(key)) {
7492
+ for (const el of titleElsOf(item)) writeTitle(el, want);
7493
+ }
7494
+ }
6230
7495
  }
6231
7496
  }
6232
- if (!removed.length) return;
6233
- const infos = removed.map((el) => {
6234
- const key = el.getAttribute("data-task-item-key") || "";
6235
- const ws = workspaceOf2(key);
6236
- return key && ws ? { key, ws, run: isRunningRow(el) } : null;
6237
- }).filter(Boolean);
6238
- if (!infos.length) return;
7497
+ if (pendingKeys.has(key)) return;
7498
+ pendingKeys.add(key);
6239
7499
  setTimeout(() => {
6240
- for (const info of infos) {
6241
- const row = findWsRow(info.ws);
6242
- const head = row && wsHead(row);
6243
- const collapsed = !row || !head || head.getAttribute("aria-expanded") === "false";
6244
- let set = runningByWs.get(info.ws);
6245
- if (collapsed && info.run) {
6246
- if (!set) {
6247
- set = /* @__PURE__ */ new Set();
6248
- runningByWs.set(info.ws, set);
6249
- }
6250
- set.add(info.key);
6251
- } else if (set) {
6252
- set.delete(info.key);
7500
+ pendingKeys.delete(key);
7501
+ void (async () => {
7502
+ const map = await fetchPinned();
7503
+ if (!map) return;
7504
+ const want = map.get(sessIdOf(key));
7505
+ if (want === void 0) return;
7506
+ let wrote = false;
7507
+ for (const item of findRows(key)) {
7508
+ if (isMismatch(item) !== key) continue;
7509
+ for (const el of titleElsOf(item)) writeTitle(el, want);
7510
+ wrote = true;
6253
7511
  }
6254
- }
6255
- syncPeeks();
6256
- apply();
6257
- }, 0);
6258
- }
6259
- function apply() {
6260
- const seen = /* @__PURE__ */ new Set();
6261
- for (const row of document.querySelectorAll(WS_SEL)) {
6262
- const path = row.getAttribute("data-testid").slice("workspace-item-".length);
6263
- seen.add(path);
6264
- const head = wsHead(row);
6265
- const collapsed = !head || head.getAttribute("aria-expanded") === "false";
6266
- const running = !!(enabled3 && collapsed && runningByWs.get(path) && runningByWs.get(path).size);
6267
- const icon = row.firstElementChild && row.firstElementChild.querySelector(":scope > span");
6268
- if (icon) icon.classList.toggle("zcodepro-ws-running", running);
6269
- }
6270
- for (const ws of [...runningByWs.keys()]) if (!seen.has(ws)) runningByWs.delete(ws);
6271
- }
6272
- function schedulePeek(ws, delay) {
6273
- clearTimeout(peekTimers.get(ws));
6274
- peekTimers.set(ws, setTimeout(() => {
6275
- peekTimers.delete(ws);
6276
- void peek(ws);
6277
- }, delay));
7512
+ if (wrote) setTimeout(() => {
7513
+ void enforceKey(key);
7514
+ }, REVERIFY_MS);
7515
+ })();
7516
+ }, RECHECK_DELAY_MS);
6278
7517
  }
6279
- function clearPeek(ws) {
6280
- const t2 = peekTimers.get(ws);
6281
- if (t2) {
6282
- clearTimeout(t2);
6283
- peekTimers.delete(ws);
7518
+ async function enforceKey(key) {
7519
+ const map = await fetchPinned();
7520
+ if (!map) return;
7521
+ const want = map.get(sessIdOf(key));
7522
+ if (want === void 0) return;
7523
+ for (const item of findRows(key)) {
7524
+ if (isMismatch(item) !== key) continue;
7525
+ for (const el of titleElsOf(item)) writeTitle(el, want);
6284
7526
  }
6285
7527
  }
6286
- function syncPeeks() {
6287
- for (const [ws, set] of runningByWs) {
6288
- if (!enabled3 || !set.size) {
6289
- clearPeek(ws);
7528
+ function scan3() {
7529
+ if (!enabled3 || !pinned || pinned.size === 0) return;
7530
+ for (const item of document.querySelectorAll(TASK_SEL2)) {
7531
+ const key = isMismatch(item);
7532
+ if (!key) {
7533
+ seenRows.add(item);
6290
7534
  continue;
6291
7535
  }
6292
- const row = findWsRow(ws);
6293
- const head = row && wsHead(row);
6294
- if (!row || !head || head.getAttribute("aria-expanded") !== "false" || wsWithRows.has(ws)) {
6295
- clearPeek(ws);
6296
- continue;
7536
+ const fresh = !seenRows.has(item);
7537
+ seenRows.add(item);
7538
+ scheduleEnforce(key, { immediate: fresh });
7539
+ }
7540
+ }
7541
+ var inTaskRow2 = (node) => !!(node instanceof Element && node.closest && node.closest(TASK_SEL2));
7542
+ function taskRowMutations2(muts) {
7543
+ for (const m of muts) {
7544
+ if (m.type === "attributes") {
7545
+ if (inTaskRow2(m.target)) return true;
7546
+ } else if (m.type === "characterData") {
7547
+ if (m.target.parentElement && inTaskRow2(m.target.parentElement)) return true;
7548
+ } else if (inTaskRow2(m.target)) {
7549
+ return true;
7550
+ } else {
7551
+ for (const n of m.addedNodes) {
7552
+ if (n instanceof Element && (n.matches(TASK_SEL2) || !!n.querySelector(TASK_SEL2))) return true;
7553
+ }
6297
7554
  }
6298
- if (!peekTimers.has(ws) && !activePeeks.has(ws)) schedulePeek(ws, peekedWs.has(ws) ? PEEK_REPEAT_MS : PEEK_FIRST_MS);
6299
7555
  }
7556
+ return false;
6300
7557
  }
6301
- var peekChain = Promise.resolve();
6302
- async function peek(ws, opts) {
6303
- const run = () => peekInner(ws, opts);
6304
- const p = peekChain.then(run, run);
6305
- peekChain = p.catch(() => {
6306
- });
6307
- return p;
7558
+ var scanDebounce3 = 0;
7559
+ function scheduleScan2() {
7560
+ if (scanDebounce3) return;
7561
+ scanDebounce3 = setTimeout(() => {
7562
+ scanDebounce3 = 0;
7563
+ scan3();
7564
+ }, SCAN_DEBOUNCE_MS3);
6308
7565
  }
6309
- async function peekInner(ws, opts) {
6310
- if (!opts?.manual && !enabled3 || document.hidden || activePeeks.has(ws)) return false;
6311
- const row = findWsRow(ws);
6312
- const head = row && wsHead(row);
6313
- if (!row || !head || head.getAttribute("aria-expanded") !== "false") return false;
6314
- if (!currentTaskRow2()) {
6315
- schedulePeek(ws, PEEK_REPEAT_MS);
6316
- return false;
6317
- }
6318
- activePeeks.add(ws);
6319
- peekedWs.add(ws);
6320
- const rect = row.getBoundingClientRect();
6321
- const frozen = row.cloneNode(true);
6322
- frozen.classList.add("zcodepro-ws-frozen");
6323
- frozen.style.left = rect.left + "px";
6324
- frozen.style.top = rect.top + "px";
6325
- frozen.style.width = rect.width + "px";
6326
- frozen.style.height = rect.height + "px";
6327
- document.body.append(frozen);
6328
- row.style.visibility = "hidden";
6329
- const hider = installPeekHider(ws);
6330
- try {
6331
- toggleNavSafe(head);
6332
- await settleAndRead(ws);
6333
- const rowNow = findWsRow(ws) || row;
6334
- const headNow = wsHead(rowNow);
6335
- if (headNow && headNow.getAttribute("aria-expanded") === "true" && !userTouched(rowNow)) {
6336
- toggleNavSafe(headNow);
6337
- await waitForRowsGone(ws);
7566
+ var mapRefreshTimer = 0;
7567
+ function refreshMapSoon() {
7568
+ if (mapRefreshTimer) return;
7569
+ const tick2 = () => {
7570
+ if (document.hidden) {
7571
+ mapRefreshTimer = setTimeout(tick2, 1e4);
7572
+ return;
6338
7573
  }
6339
- } finally {
6340
- removePeekHider(hider);
6341
- row.style.visibility = "";
6342
- frozen.remove();
6343
- activePeeks.delete(ws);
7574
+ mapRefreshTimer = 0;
7575
+ if (enabled3 && resolved) void fetchPinned().then((m) => {
7576
+ if (m) scan3();
7577
+ });
7578
+ };
7579
+ mapRefreshTimer = setTimeout(tick2, MAP_REFRESH_MS);
7580
+ }
7581
+ var editingKey = null;
7582
+ var bootDelay = 0;
7583
+ function scheduleBootRetry() {
7584
+ bootDelay = bootDelay ? Math.min(bootDelay * 2, MAP_REFRESH_MS) : BOOT_RETRY_MS;
7585
+ setTimeout(() => {
7586
+ if (resolved) return;
7587
+ if (!document.hidden) void bootstrap();
7588
+ scheduleBootRetry();
7589
+ }, bootDelay);
7590
+ }
7591
+ async function bootstrap() {
7592
+ const map = await fetchPinned();
7593
+ if (map) scan3();
7594
+ }
7595
+ function setRowTitle(key, text) {
7596
+ for (const item of findRows(key)) {
7597
+ for (const el of titleElsOf(item)) writeTitle(el, text);
6344
7598
  }
6345
- apply();
6346
- const set = runningByWs.get(ws);
6347
- if (set && set.size && enabled3) schedulePeek(ws, PEEK_REPEAT_MS);
6348
- return true;
6349
7599
  }
6350
- function wsRunningSnapshot() {
6351
- const curKey = currentTaskRow2()?.getAttribute("data-task-item-key") || "";
6352
- return {
6353
- running: [...runningByWs.entries()].filter(([, set]) => set.size > 0).map(([ws]) => ws),
6354
- current: curKey ? workspaceOf2(curKey) || null : null
6355
- };
7600
+ function refreshPinnedTitles() {
7601
+ return fetchPinned().then((m) => {
7602
+ if (m) scan3();
7603
+ });
6356
7604
  }
6357
- async function verifyWorkspaceNotRunning(ws) {
6358
- if (!ws || document.hidden) return false;
6359
- const row = findWsRow(ws);
6360
- const head = row && wsHead(row);
6361
- if (!row || !head) return false;
6362
- if (head.getAttribute("aria-expanded") !== "false") {
6363
- let run = false;
6364
- for (const el of domRowsOf(ws)) if (isRunningRow(el)) run = true;
6365
- return !run;
7605
+ var RENAME_CTX_VALID_MS = 15e3;
7606
+ var renameCtx = null;
7607
+ var renamePrefilled = /* @__PURE__ */ new WeakSet();
7608
+ function setNativeValue(el, text) {
7609
+ if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) {
7610
+ Object.getOwnPropertyDescriptor(el.constructor.prototype, "value").set.call(el, text);
7611
+ } else {
7612
+ el.textContent = text;
6366
7613
  }
6367
- let verified = false;
6368
- try {
6369
- verified = await peek(ws, { manual: true }) === true;
6370
- } catch {
7614
+ el.dispatchEvent(new Event("input", { bubbles: true }));
7615
+ }
7616
+ function prefillRenameDialog() {
7617
+ if (!renameCtx || !renameCtx.armed || Date.now() - renameCtx.at > RENAME_CTX_VALID_MS) {
7618
+ renameCtx = null;
7619
+ return;
7620
+ }
7621
+ if (!enabled3 || !pinned) return;
7622
+ const want = pinned.get(sessIdOf(renameCtx.key));
7623
+ if (want === void 0) return;
7624
+ for (const el of document.querySelectorAll('[role="dialog"] input, [role="dialog"] textarea, [role="dialog"] [contenteditable="true"]')) {
7625
+ if (renamePrefilled.has(el) || !el.getClientRects().length) continue;
7626
+ if (el.closest(TASK_SEL2) || el.closest(".zcodepro-overlay")) continue;
7627
+ const cur = String(el.value ?? el.textContent ?? "").trim();
7628
+ if (!cur) continue;
7629
+ renamePrefilled.add(el);
7630
+ if (cur !== String(want).trim()) setNativeValue(el, String(want));
7631
+ renameCtx = null;
7632
+ return;
6371
7633
  }
6372
- if (!verified) return false;
6373
- const set = runningByWs.get(ws);
6374
- return !(set && set.size);
6375
7634
  }
6376
- function settleAndRead(ws) {
6377
- return new Promise((resolve) => {
6378
- const t0 = Date.now();
6379
- let lastCount = -1;
6380
- let stableAt = 0;
6381
- const timer = setInterval(() => {
6382
- const rows = domRowsOf(ws);
6383
- const elapsed = Date.now() - t0;
6384
- if (rows.length !== lastCount) {
6385
- lastCount = rows.length;
6386
- stableAt = Date.now();
6387
- }
6388
- const settled = rows.length > 0 && rows.length === lastCount && Date.now() - stableAt >= PEEK_SETTLE_MS;
6389
- if (!settled && elapsed < PEEK_MAX_MS) return;
6390
- clearInterval(timer);
6391
- if (lastCount === 0) {
6392
- runningByWs.delete(ws);
6393
- } else {
6394
- let set = runningByWs.get(ws);
6395
- if (!set) {
6396
- set = /* @__PURE__ */ new Set();
6397
- runningByWs.set(ws, set);
6398
- }
6399
- for (const el of rows) {
6400
- const key = el.getAttribute("data-task-item-key") || "";
6401
- if (isRunningRow(el)) set.add(key);
6402
- else set.delete(key);
6403
- }
6404
- }
6405
- resolve();
6406
- }, 80);
7635
+ function startTitleLock() {
7636
+ if (installed3 || typeof document === "undefined") return;
7637
+ installed3 = true;
7638
+ void bootstrap();
7639
+ new MutationObserver((muts) => {
7640
+ if (renameCtx) prefillRenameDialog();
7641
+ if (!taskRowMutations2(muts)) return;
7642
+ refreshMapSoon();
7643
+ scheduleScan2();
7644
+ }).observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ["class"], characterData: true });
7645
+ document.addEventListener("contextmenu", (e) => {
7646
+ const row = e.target instanceof Element && e.target.closest(TASK_SEL2);
7647
+ renameCtx = row ? { key: row.getAttribute("data-task-item-key") || "", at: 0, armed: false } : null;
7648
+ }, true);
7649
+ document.addEventListener("click", (e) => {
7650
+ if (!renameCtx || renameCtx.armed) return;
7651
+ const item = e.target instanceof Element && e.target.closest('[role="menuitem"], [data-radix-collection-item]');
7652
+ if (item && /重命名|rename/i.test(item.textContent || "")) {
7653
+ renameCtx.armed = true;
7654
+ renameCtx.at = Date.now();
7655
+ }
7656
+ }, true);
7657
+ scheduleBootRetry();
7658
+ setInterval(() => {
7659
+ if (!document.hidden) scan3();
7660
+ }, SCAN_FALLBACK_MS3);
7661
+ document.addEventListener("visibilitychange", () => {
7662
+ if (!document.hidden) scan3();
7663
+ });
7664
+ window.addEventListener("zcodepro:config-changed", () => {
7665
+ void bootstrap();
7666
+ });
7667
+ document.addEventListener("focusin", (e) => {
7668
+ const el = e.target;
7669
+ if (!(el instanceof Element)) return;
7670
+ if (!el.matches('input, textarea, [contenteditable="true"]')) return;
7671
+ const row = el.closest(TASK_SEL2);
7672
+ editingKey = row ? row.getAttribute("data-task-item-key") || "" : null;
7673
+ });
7674
+ document.addEventListener("focusout", () => {
7675
+ if (editingKey === null) return;
7676
+ editingKey = null;
7677
+ void fetchPinned().then((m) => {
7678
+ if (m) scan3();
7679
+ });
6407
7680
  });
6408
7681
  }
6409
- var PEEK_EXEMPT_CLASS = "zcodepro-peek-exempt";
6410
- var cssAttr = (v) => String(v).replace(/\\/g, "\\\\").replace(/"/g, '\\"');
6411
- function installPeekHider(ws) {
6412
- const rowSel3 = `${TASK_SEL3}[data-task-item-key^="${cssAttr(ws + ":")}"]`;
6413
- for (const r of document.querySelectorAll(rowSel3)) r.classList.add(PEEK_EXEMPT_CLASS);
6414
- const style = document.createElement("style");
6415
- style.textContent = `${rowSel3}:not(.${PEEK_EXEMPT_CLASS}){display:none!important}`;
6416
- document.head.append(style);
6417
- return { style, sel: rowSel3 };
7682
+
7683
+ // src/inject/features/title-gen.js
7684
+ var CONV2 = '[class*="@md/conversation"]';
7685
+ var USER_ROW = '[class*="user-row"]';
7686
+ var SCAN_DEBOUNCE_MS4 = 300;
7687
+ var SCAN_FALLBACK_MS4 = 5e3;
7688
+ var SELECT_WAIT_MS = 2e3;
7689
+ var MSG_WAIT_MS = 1e4;
7690
+ var QUIET_FALLBACK_MS = 12e4;
7691
+ var COLLECT_MAX_MS = 6e5;
7692
+ var installed4 = false;
7693
+ var knownKeys = /* @__PURE__ */ new Set();
7694
+ var attempted = /* @__PURE__ */ new Set();
7695
+ var sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
7696
+ function sessIdOf2(key) {
7697
+ const i = key.indexOf(":sess_");
7698
+ return i > 0 ? key.slice(i + 1) : "";
6418
7699
  }
6419
- function removePeekHider(hider) {
6420
- hider.style.remove();
6421
- for (const r of document.querySelectorAll("." + PEEK_EXEMPT_CLASS)) r.classList.remove(PEEK_EXEMPT_CLASS);
7700
+ function currentKey() {
7701
+ for (const el of document.querySelectorAll("[data-task-item-key]")) {
7702
+ if ((el.className + "").includes("bg-selected")) return el.getAttribute("data-task-item-key") || "";
7703
+ }
7704
+ return "";
6422
7705
  }
6423
- function waitForRowsGone(ws) {
6424
- const sel = `${TASK_SEL3}[data-task-item-key^="${cssAttr(ws + ":")}"]:not(.${PEEK_EXEMPT_CLASS})`;
6425
- return new Promise((resolve) => {
6426
- const t0 = Date.now();
6427
- const timer = setInterval(() => {
6428
- if (!document.querySelector(sel) || Date.now() - t0 >= PEEK_UNMOUNT_WAIT_MS) {
6429
- clearInterval(timer);
6430
- resolve();
6431
- }
6432
- }, 40);
6433
- });
7706
+ function userTexts() {
7707
+ return [...document.querySelectorAll(`${CONV2} ${USER_ROW}`)].map((r) => {
7708
+ const el = r.querySelector(".whitespace-pre-wrap");
7709
+ return ((el ? el.textContent : r.innerText) || "").trim();
7710
+ }).filter(Boolean);
6434
7711
  }
6435
- var TOUCH_WINDOW_MS = 800;
6436
- var wsTouchAt = /* @__PURE__ */ new WeakMap();
6437
- function userTouched(row) {
6438
- const at = wsTouchAt.get(row);
6439
- return !!at && Date.now() - at < TOUCH_WINDOW_MS;
7712
+ var URL_RE = /https?:\/\/[^\s\u4e00-\u9fff\u3000-\u303f\uff00-\uffef]+/g;
7713
+ function assistantText() {
7714
+ const els = [...document.querySelectorAll('[class*="@md/conversation"] [class*="assistant-row"]')].filter((e) => !e.querySelector("[data-is-streaming]"));
7715
+ const last = els[els.length - 1];
7716
+ if (!last) return "";
7717
+ const text = (last.innerText || "").replace(URL_RE, "").trim();
7718
+ return text.slice(0, 600);
6440
7719
  }
6441
- function inSel(node, sel) {
6442
- return !!(node instanceof Element && node.closest && node.closest(sel));
7720
+ function linkDominant(t2) {
7721
+ const raw = t2.replace(/\s+/g, "");
7722
+ if (!raw) return true;
7723
+ const stripped = raw.replace(URL_RE, "");
7724
+ return stripped.length < raw.length / 3;
6443
7725
  }
6444
- function relevant(muts) {
6445
- for (const m of muts) {
6446
- if (inSel(m.target, TASK_SEL3) || inSel(m.target, WS_SEL)) return true;
6447
- for (const n of m.addedNodes) {
6448
- if (n instanceof Element && (n.matches(TASK_SEL3) || n.matches(WS_SEL) || n.querySelector(TASK_SEL3) || n.querySelector(WS_SEL))) return true;
7726
+ async function onNewTaskRow(key) {
7727
+ const selDeadline = Date.now() + SELECT_WAIT_MS;
7728
+ while (currentKey() !== key && Date.now() < selDeadline) await sleep2(200);
7729
+ if (currentKey() !== key) return;
7730
+ const cfg = await getConfig().catch(() => null);
7731
+ if (!cfg || !cfg.titleGen || cfg.titleGen.enabled !== true) return;
7732
+ const sessId = sessIdOf2(key);
7733
+ if (!sessId || attempted.has(sessId)) return;
7734
+ attempted.add(sessId);
7735
+ let texts = [];
7736
+ const msgDeadline = Date.now() + MSG_WAIT_MS;
7737
+ while (Date.now() < msgDeadline) {
7738
+ texts = userTexts();
7739
+ if (texts.length) break;
7740
+ await sleep2(400);
7741
+ }
7742
+ if (!texts.length) return;
7743
+ let assistantSeen = false;
7744
+ const convObserver = new MutationObserver((muts) => {
7745
+ if (currentKey() !== key) return;
7746
+ for (const m of muts) {
7747
+ const t2 = m.type === "characterData" ? m.target.parentElement : m.target;
7748
+ if (t2 instanceof Element && t2.closest(USER_ROW)) continue;
7749
+ for (const n of [t2, ...m.addedNodes]) {
7750
+ if (n instanceof Element && n.closest && n.closest(CONV2)) {
7751
+ assistantSeen = true;
7752
+ return;
7753
+ }
7754
+ }
6449
7755
  }
6450
- for (const n of m.removedNodes) {
6451
- if (n instanceof Element && (n.matches(TASK_SEL3) || n.matches(WS_SEL) || n.querySelector(TASK_SEL3))) return true;
7756
+ });
7757
+ convObserver.observe(document.body, { childList: true, subtree: true, characterData: true });
7758
+ const startAt = Date.now();
7759
+ let lastCount = texts.length;
7760
+ let lastChange = Date.now();
7761
+ try {
7762
+ while (Date.now() - startAt < COLLECT_MAX_MS) {
7763
+ await sleep2(500);
7764
+ if (currentKey() !== key) {
7765
+ lastChange = Date.now();
7766
+ continue;
7767
+ }
7768
+ texts = userTexts();
7769
+ if (texts.length !== lastCount) {
7770
+ lastCount = texts.length;
7771
+ lastChange = Date.now();
7772
+ }
7773
+ if (assistantSeen) break;
7774
+ if (Date.now() - lastChange >= QUIET_FALLBACK_MS) break;
6452
7775
  }
7776
+ } finally {
7777
+ convObserver.disconnect();
7778
+ }
7779
+ if (!texts.length) return;
7780
+ if (linkDominant(texts.join("\n"))) {
7781
+ const deadline = Date.now() + 6e4;
7782
+ while (Date.now() < deadline) {
7783
+ await sleep2(2e3);
7784
+ if (currentKey() !== key) continue;
7785
+ const a = assistantText();
7786
+ if (a && a.length > 80) {
7787
+ texts.push(`\uFF08\u9996\u8F6E\u56DE\u7B54\u6458\u8981\uFF09${a}`);
7788
+ break;
7789
+ }
7790
+ }
7791
+ }
7792
+ const callTitleGen = () => rpc("/title-gen/run", { method: "POST", body: { sessId, text: texts.join("\n") } }).catch(() => null);
7793
+ let res = await callTitleGen();
7794
+ for (const delay of [5e3, 2e4]) {
7795
+ if (res && res.ok) break;
7796
+ await sleep2(delay);
7797
+ res = await callTitleGen();
7798
+ }
7799
+ if (res && res.ok && res.written && res.title) {
7800
+ setRowTitle(key, res.title);
7801
+ void refreshPinnedTitles();
6453
7802
  }
6454
- return false;
6455
7803
  }
6456
- var pollMs2 = CONFIG_POLL_MIN_MS2;
6457
- async function refreshConfig2() {
6458
- try {
6459
- const res = await rpc("/config");
6460
- if (res && res.ok && res.config && res.config.features) {
6461
- const next = res.config.features.wsRunningSpin !== false;
6462
- if (next !== enabled3) {
6463
- enabled3 = next;
6464
- if (!next) {
6465
- for (const ws of [...peekTimers.keys()]) clearPeek(ws);
6466
- apply();
6467
- }
6468
- return true;
7804
+ function scanForNewRows() {
7805
+ for (const el of document.querySelectorAll("[data-task-item-key]")) {
7806
+ const key = el.getAttribute("data-task-item-key") || "";
7807
+ if (!key || knownKeys.has(key)) continue;
7808
+ knownKeys.add(key);
7809
+ void onNewTaskRow(key);
7810
+ }
7811
+ }
7812
+ var TASK_SEL3 = "[data-task-item-key]";
7813
+ var inTaskRow3 = (node) => !!(node instanceof Element && node.closest && node.closest(TASK_SEL3));
7814
+ function taskRowMutations3(muts) {
7815
+ for (const m of muts) {
7816
+ if (m.type === "attributes") {
7817
+ if (inTaskRow3(m.target)) return true;
7818
+ } else if (m.type === "characterData") {
7819
+ if (m.target.parentElement && inTaskRow3(m.target.parentElement)) return true;
7820
+ } else if (inTaskRow3(m.target)) {
7821
+ return true;
7822
+ } else {
7823
+ for (const n of m.addedNodes) {
7824
+ if (n instanceof Element && (n.matches(TASK_SEL3) || !!n.querySelector(TASK_SEL3))) return true;
6469
7825
  }
6470
7826
  }
6471
- } catch {
6472
7827
  }
6473
7828
  return false;
6474
7829
  }
6475
- function pollConfig2() {
6476
- setTimeout(async () => {
6477
- if (document.hidden) pollMs2 = CONFIG_POLL_MAX_MS2;
6478
- else pollMs2 = await refreshConfig2() ? CONFIG_POLL_MIN_MS2 : Math.min(Math.round(pollMs2 * 1.5), CONFIG_POLL_MAX_MS2);
6479
- pollConfig2();
6480
- }, pollMs2);
6481
- }
6482
7830
  var scanDebounce4 = 0;
6483
- function startWsRunningSpin() {
6484
- if (installed5 || typeof document === "undefined") return;
6485
- installed5 = true;
6486
- void refreshConfig2();
6487
- pollConfig2();
6488
- const observer2 = new MutationObserver((muts) => {
6489
- if (!relevant(muts)) return;
6490
- handleRemovals(muts);
6491
- if (!scanDebounce4) {
6492
- scanDebounce4 = setTimeout(() => {
6493
- scanDebounce4 = 0;
6494
- scan3();
6495
- }, SCAN_DEBOUNCE_MS4);
6496
- }
6497
- });
6498
- observer2.observe(document.body, { childList: true, subtree: true });
6499
- scan3();
7831
+ function scheduleScan3() {
7832
+ if (scanDebounce4) return;
7833
+ scanDebounce4 = setTimeout(() => {
7834
+ scanDebounce4 = 0;
7835
+ scanForNewRows();
7836
+ }, SCAN_DEBOUNCE_MS4);
7837
+ }
7838
+ function startTitleGen() {
7839
+ if (installed4 || typeof document === "undefined") return;
7840
+ installed4 = true;
7841
+ scanForNewRows();
7842
+ new MutationObserver((muts) => {
7843
+ if (taskRowMutations3(muts)) scheduleScan3();
7844
+ }).observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ["class"], characterData: true });
6500
7845
  setInterval(() => {
6501
- if (!document.hidden && enabled3) scan3();
7846
+ if (!document.hidden) scanForNewRows();
6502
7847
  }, SCAN_FALLBACK_MS4);
6503
7848
  document.addEventListener("visibilitychange", () => {
6504
- if (!document.hidden) scan3();
7849
+ if (!document.hidden) scanForNewRows();
6505
7850
  });
6506
- document.addEventListener("click", (e) => {
6507
- if (!e.isTrusted) return;
6508
- const row = e.target instanceof Element && e.target.closest(WS_SEL);
6509
- if (row) wsTouchAt.set(row, Date.now());
6510
- }, true);
6511
7851
  }
6512
7852
 
6513
7853
  // src/inject/features/idle-reclaim.js
6514
7854
  var CHECK_MS = 3e4;
6515
- var WS_SEL2 = '[data-testid^="workspace-item-"]';
6516
7855
  var MIN_MINUTES = 1;
6517
7856
  var MAX_MINUTES = 1440;
6518
7857
  var idleSince = /* @__PURE__ */ new Map();
@@ -6520,46 +7859,16 @@
6520
7859
  var scanDisabledLogged = false;
6521
7860
  var bootAt = Date.now();
6522
7861
  var startupSweepDone = false;
6523
- var servicesRef = null;
6524
- function findServices() {
6525
- if (servicesRef) return servicesRef;
6526
- try {
6527
- const rootEl = document.getElementById("root");
6528
- const containerKey = rootEl && Object.keys(rootEl).find((k) => k.startsWith("__reactContainer$"));
6529
- if (!containerKey) return null;
6530
- const seen = /* @__PURE__ */ new Set();
6531
- let found = null;
6532
- const scanObj = (o) => {
6533
- if (!o || typeof o !== "object" || seen.has(o) || found) return;
6534
- seen.add(o);
6535
- const ts = o.zcodeTaskService;
6536
- if (ts && typeof ts === "object" && typeof ts.releaseWorkspacePreparation === "function") found = o;
6537
- };
6538
- let visited = 0;
6539
- const visit = (f) => {
6540
- if (!f || found || visited > 6e4) return;
6541
- visited++;
6542
- for (const holder of [f.memoizedProps, f.memoizedState]) {
6543
- if (!holder || typeof holder !== "object") continue;
6544
- scanObj(holder);
6545
- for (const v of Object.values(holder)) {
6546
- if (v && typeof v === "object") scanObj(v);
6547
- }
6548
- }
6549
- visit(f.child);
6550
- visit(f.sibling);
6551
- };
6552
- visit(rootEl[containerKey]);
6553
- if (found) servicesRef = found;
6554
- return found;
6555
- } catch {
6556
- return null;
6557
- }
6558
- }
6559
- function sidebarWorkspaces() {
7862
+ async function sidebarWorkspaces() {
6560
7863
  const out = /* @__PURE__ */ new Set();
6561
- for (const el of document.querySelectorAll(WS_SEL2)) {
6562
- out.add(el.getAttribute("data-testid").slice("workspace-item-".length));
7864
+ const res = await rpc("/projects-v2").catch(() => null);
7865
+ if (res && res.ok && res.available) {
7866
+ for (const proj of res.projects || []) {
7867
+ for (const r of proj.roots || []) {
7868
+ const p = String(r.path || "").replace(/[\/]+$/, "");
7869
+ if (p) out.add(p);
7870
+ }
7871
+ }
6563
7872
  }
6564
7873
  return out;
6565
7874
  }
@@ -6578,8 +7887,14 @@
6578
7887
  }
6579
7888
  }
6580
7889
  async function tryReclaim(ws, cooldownMs) {
6581
- const verifiedIdle = await verifyWorkspaceNotRunning(ws);
6582
- if (!verifiedIdle) {
7890
+ const auth = await authoritativeIdle(ws);
7891
+ if (auth === null) {
7892
+ const verifiedIdle = await verifyWorkspaceNotRunning(ws);
7893
+ if (!verifiedIdle) {
7894
+ blockUntil.set(ws, Date.now() + cooldownMs);
7895
+ return;
7896
+ }
7897
+ } else if (!auth) {
6583
7898
  blockUntil.set(ws, Date.now() + cooldownMs);
6584
7899
  return;
6585
7900
  }
@@ -6609,7 +7924,7 @@
6609
7924
  }
6610
7925
  const busy = new Set(snap.running);
6611
7926
  busy.add(snap.current);
6612
- const workspaces = sidebarWorkspaces();
7927
+ const workspaces = await sidebarWorkspaces();
6613
7928
  for (const ws of [...idleSince.keys(), ...blockUntil.keys()]) {
6614
7929
  if (!workspaces.has(ws)) {
6615
7930
  idleSince.delete(ws);
@@ -6665,24 +7980,34 @@
6665
7980
  var SYNC_DEBOUNCE_MS = 150;
6666
7981
  var SCAN_FALLBACK_MS5 = 5e3;
6667
7982
  var ROW_SEL = "[data-task-item-key]";
6668
- var MENU_SEL = '[data-testid="task-new-button"], [data-testid="automations-open"], [data-testid="plugin-store-sidebar-open"], [data-slot="command"], [data-testid="desktop-top-nav-back"]';
7983
+ var MENU_SEL = '[data-testid="task-new-button"], [data-testid="automations-open"], [data-testid="plugin-store-sidebar-open"], [data-testid="sidebar-search-open"], [data-slot="command"], [data-testid="desktop-top-nav-back"]';
6669
7984
  var enabled4 = true;
6670
7985
  var rowEl = null;
6671
7986
  var origs = null;
6672
7987
  var syncTimer = 0;
6673
7988
  var widthObserver = null;
6674
7989
  var widthObserved = null;
6675
- function findMenu() {
6676
- const nt = document.querySelector('[data-testid="task-new-button"]');
6677
- const menu = nt?.parentElement;
6678
- return menu && menu.querySelector('[data-testid="automations-open"]') ? menu : null;
6679
- }
6680
- function originalsOf(menu) {
6681
- const newTask = menu.querySelector('[data-testid="task-new-button"]');
6682
- const search = newTask && menu.querySelector('button [class*="lucide-search"]')?.closest("button");
6683
- const auto = menu.querySelector('[data-testid="automations-open"]');
6684
- const plugin = menu.querySelector('[data-testid="plugin-store-sidebar-open"]');
6685
- return newTask && search && auto && plugin ? { menu, newTask, search, auto, plugin } : null;
7990
+ function findParts() {
7991
+ const newTask = document.querySelector('[data-testid="task-new-button"]');
7992
+ if (!newTask) return null;
7993
+ if (!isNewProjectSidebar()) {
7994
+ const menu = newTask.parentElement;
7995
+ if (!menu || !menu.querySelector('[data-testid="automations-open"]')) return null;
7996
+ const search2 = menu.querySelector('button [class*="lucide-search"]')?.closest("button");
7997
+ const auto2 = menu.querySelector('[data-testid="automations-open"]');
7998
+ const plugin2 = menu.querySelector('[data-testid="plugin-store-sidebar-open"]');
7999
+ return search2 && auto2 && plugin2 ? { newTask, search: search2, auto: auto2, plugin: plugin2, hideEls: [menu] } : null;
8000
+ }
8001
+ const search = document.querySelector('[data-testid="sidebar-search-open"]');
8002
+ const auto = document.querySelector('[data-testid="automations-open"]');
8003
+ const plugin = document.querySelector('[data-testid="plugin-store-sidebar-open"]');
8004
+ if (!search || !auto || !plugin) return null;
8005
+ const hideEls = [.../* @__PURE__ */ new Set([
8006
+ newTask.closest('[data-testid="sidebar-primary-navigation"]'),
8007
+ auto.closest('[data-testid="sidebar-primary-navigation"]'),
8008
+ search.closest('[data-testid="sidebar-search-toolbar"]')
8009
+ ])].filter(Boolean);
8010
+ return hideEls.length ? { newTask, search, auto, plugin, hideEls } : null;
6686
8011
  }
6687
8012
  function nameOf(orig, nameSel, fallback) {
6688
8013
  let name = nameSel && (orig.querySelector(nameSel)?.textContent || "").trim();
@@ -6708,6 +8033,7 @@
6708
8033
  else btn.removeAttribute("data-active");
6709
8034
  if (kind === "auto" || kind === "plugin") btn.setAttribute("aria-pressed", on ? "true" : "false");
6710
8035
  }
8036
+ var lastSelectedKey = "";
6711
8037
  function syncActive() {
6712
8038
  if (!origs || !rowEl) return;
6713
8039
  const auto = origs.auto.getAttribute("aria-pressed") === "true";
@@ -6717,23 +8043,37 @@
6717
8043
  for (const el of document.querySelectorAll(ROW_SEL)) {
6718
8044
  if ((el.className + "").includes("bg-selected")) {
6719
8045
  selected = true;
8046
+ lastSelectedKey = el.getAttribute("data-task-item-key") || lastSelectedKey;
6720
8047
  break;
6721
8048
  }
6722
8049
  }
6723
- setActive("newTask", !selected && !auto && !plugin);
8050
+ let newTaskOn = false;
8051
+ if (!selected && lastSelectedKey) {
8052
+ const stillMounted = document.querySelector(`${ROW_SEL}[data-task-item-key="${CSS.escape(lastSelectedKey)}"]`);
8053
+ if (stillMounted) {
8054
+ newTaskOn = !auto && !plugin;
8055
+ lastSelectedKey = "";
8056
+ } else {
8057
+ const btn = rowEl?.querySelector('[data-kind="newTask"]');
8058
+ newTaskOn = btn ? btn.getAttribute("data-active") === "1" : false;
8059
+ }
8060
+ } else if (!selected) {
8061
+ newTaskOn = !auto && !plugin;
8062
+ }
8063
+ setActive("newTask", newTaskOn);
6724
8064
  setActive("search", palette);
6725
8065
  setActive("auto", auto);
6726
8066
  setActive("plugin", plugin);
6727
8067
  }
6728
8068
  function rebuild() {
6729
- const menu = findMenu();
6730
- if (!menu) return false;
6731
- const next = originalsOf(menu);
8069
+ const parts = findParts();
6732
8070
  const navRow = document.querySelector('[data-testid="desktop-top-nav-back"]')?.parentElement;
6733
- if (!next || !navRow) return false;
6734
- if (origs && origs.menu !== menu) origs.menu.classList.remove(HIDE_CLASS);
6735
- menu.classList.add(HIDE_CLASS);
6736
- origs = next;
8071
+ if (!parts || !navRow) return false;
8072
+ if (origs && origs.hideEls[0] !== parts.hideEls[0]) {
8073
+ for (const el of origs.hideEls) el.classList.remove(HIDE_CLASS);
8074
+ }
8075
+ for (const el of parts.hideEls) el.classList.add(HIDE_CLASS);
8076
+ origs = parts;
6737
8077
  rowEl?.remove();
6738
8078
  navRow.classList.add("zcodepro-tb-hidden-native");
6739
8079
  const L = t();
@@ -6748,17 +8088,17 @@
6748
8088
  setTimeout(syncActive, 150);
6749
8089
  }
6750
8090
  });
6751
- const newBtn = mk("newTask", next.newTask, "span.truncate", L.tbNewTask);
6752
- const icon = iconOf(next.newTask);
8091
+ const newBtn = mk("newTask", parts.newTask, "span.truncate", L.tbNewTask);
8092
+ const icon = iconOf(parts.newTask);
6753
8093
  if (icon) newBtn.append(icon);
6754
- const searchBtn = mk("search", next.search, "span.truncate", L.tbSearch);
6755
- const sIcon = iconOf(next.search);
8094
+ const searchBtn = mk("search", parts.search, "span.truncate", L.tbSearch);
8095
+ const sIcon = iconOf(parts.search);
6756
8096
  if (sIcon) searchBtn.append(sIcon);
6757
- const autoBtn = mk("auto", next.auto, null, L.tbAutomations);
6758
- const aIcon = iconOf(next.auto);
8097
+ const autoBtn = mk("auto", parts.auto, null, L.tbAutomations);
8098
+ const aIcon = iconOf(parts.auto);
6759
8099
  if (aIcon) autoBtn.append(aIcon);
6760
- const pluginBtn = mk("plugin", next.plugin, null, L.tbPluginStore);
6761
- const pIcon = iconOf(next.plugin);
8100
+ const pluginBtn = mk("plugin", parts.plugin, null, L.tbPluginStore);
8101
+ const pIcon = iconOf(parts.plugin);
6762
8102
  if (pIcon) pluginBtn.append(pIcon);
6763
8103
  rowEl = h("div", { class: "zcodepro-tb-row" }, newBtn, searchBtn, autoBtn, pluginBtn);
6764
8104
  const updBtn = [...navRow.querySelectorAll("button")].find((b) => /bg-(success|emerald)/.test(b.className || ""));
@@ -6773,7 +8113,7 @@
6773
8113
  }
6774
8114
  var NARROW_SIDEBAR_PX = 260;
6775
8115
  function sidebarTooNarrow() {
6776
- const scroll = document.querySelector(".flex.flex-1.min-h-0.flex-col.gap-3.overflow-y-auto");
8116
+ const scroll = document.querySelector(".flex.flex-1.min-h-0.flex-col.gap-3.overflow-y-auto") || document.querySelector("aside .min-h-0.flex-1.overflow-y-auto");
6777
8117
  if (!scroll) return false;
6778
8118
  if (!widthObserver) widthObserver = new ResizeObserver(() => scheduleSync());
6779
8119
  if (widthObserved !== scroll) {
@@ -6785,18 +8125,18 @@
6785
8125
  }
6786
8126
  function sync() {
6787
8127
  if (!enabled4) return;
6788
- const menu = findMenu();
6789
- if (!menu) {
8128
+ const parts = findParts();
8129
+ if (!parts) {
6790
8130
  if (rowEl) rowEl.style.display = "none";
6791
8131
  return;
6792
8132
  }
6793
8133
  if (sidebarCollapsed() || sidebarTooNarrow()) {
6794
8134
  if (rowEl) rowEl.style.display = "none";
6795
- menu.classList.remove(HIDE_CLASS);
8135
+ for (const el of parts.hideEls) el.classList.remove(HIDE_CLASS);
6796
8136
  document.querySelectorAll(".zcodepro-tb-hidden-native").forEach((el) => el.classList.remove("zcodepro-tb-hidden-native"));
6797
8137
  return;
6798
8138
  }
6799
- const alive = origs && origs.menu === menu && origs.newTask.isConnected && origs.search.isConnected && origs.auto.isConnected && origs.plugin.isConnected && rowEl && rowEl.isConnected;
8139
+ const alive = origs && origs.hideEls[0] === parts.hideEls[0] && origs.newTask.isConnected && origs.search.isConnected && origs.auto.isConnected && origs.plugin.isConnected && rowEl && rowEl.isConnected;
6800
8140
  if (!alive && !rebuild()) {
6801
8141
  if (rowEl) rowEl.style.display = "none";
6802
8142
  setTimeout(scheduleSync, 400);
@@ -6805,7 +8145,7 @@
6805
8145
  }
6806
8146
  const navRow = document.querySelector('[data-testid="desktop-top-nav-back"]')?.parentElement;
6807
8147
  if (navRow) navRow.classList.add("zcodepro-tb-hidden-native");
6808
- menu.classList.add(HIDE_CLASS);
8148
+ for (const el of parts.hideEls) el.classList.add(HIDE_CLASS);
6809
8149
  if (rowEl) rowEl.style.display = "";
6810
8150
  syncActive();
6811
8151
  }
@@ -6839,12 +8179,12 @@
6839
8179
  rowEl?.remove();
6840
8180
  rowEl = null;
6841
8181
  if (origs) {
6842
- origs.menu.classList.remove(HIDE_CLASS);
8182
+ for (const el of origs.hideEls) el.classList.remove(HIDE_CLASS);
6843
8183
  document.querySelectorAll(".zcodepro-tb-hidden-native").forEach((el) => el.classList.remove("zcodepro-tb-hidden-native"));
6844
8184
  origs = null;
6845
8185
  }
6846
8186
  }
6847
- async function refreshConfig3() {
8187
+ async function refreshConfig2() {
6848
8188
  const cfg = await getConfig(true).catch(() => null);
6849
8189
  enabled4 = !cfg || !cfg.features || cfg.features.toolbarIcons !== false;
6850
8190
  if (enabled4) sync();
@@ -6853,9 +8193,9 @@
6853
8193
  function startToolbarIcons() {
6854
8194
  ensureStyle();
6855
8195
  window.addEventListener("zcodepro:config-changed", () => {
6856
- void refreshConfig3();
8196
+ void refreshConfig2();
6857
8197
  });
6858
- void refreshConfig3();
8198
+ void refreshConfig2();
6859
8199
  new MutationObserver((muts) => {
6860
8200
  if (relevantMutations(muts)) scheduleSync();
6861
8201
  }).observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ["class", "aria-pressed"] });
@@ -6877,8 +8217,8 @@
6877
8217
  var syncTimer2 = 0;
6878
8218
  function findPinnedHeader() {
6879
8219
  for (const h3 of document.querySelectorAll("h3")) {
6880
- const ul = h3.nextElementSibling;
6881
- if (ul && ul.tagName === "UL" && ul.querySelector("li[data-task-item-key]")) return h3;
8220
+ const list = h3.nextElementSibling;
8221
+ if (list && list.querySelector("li[data-task-item-key]")) return h3;
6882
8222
  }
6883
8223
  return null;
6884
8224
  }
@@ -6974,7 +8314,7 @@
6974
8314
  h3.parentElement?.classList.remove(COLLAPSED_CLASS);
6975
8315
  }
6976
8316
  }
6977
- async function refreshConfig4() {
8317
+ async function refreshConfig3() {
6978
8318
  const cfg = await getConfig(true).catch(() => null);
6979
8319
  enabled5 = !cfg || !cfg.features || cfg.features.pinnedCollapse !== false;
6980
8320
  if (enabled5) sync2();
@@ -6983,9 +8323,9 @@
6983
8323
  function startPinnedCollapse() {
6984
8324
  ensureStyle();
6985
8325
  window.addEventListener("zcodepro:config-changed", () => {
6986
- void refreshConfig4();
8326
+ void refreshConfig3();
6987
8327
  });
6988
- void refreshConfig4();
8328
+ void refreshConfig3();
6989
8329
  new MutationObserver((muts) => {
6990
8330
  if (relevantMutations2(muts)) scheduleSync2();
6991
8331
  }).observe(document.body, { childList: true, subtree: true });
@@ -7060,6 +8400,10 @@
7060
8400
  void startAliasWatcher();
7061
8401
  } catch {
7062
8402
  }
8403
+ try {
8404
+ startAliasMigrationWatcher();
8405
+ } catch {
8406
+ }
7063
8407
  try {
7064
8408
  startTaskOrderWatcher();
7065
8409
  } catch {
@@ -7080,10 +8424,6 @@
7080
8424
  startImageMenu();
7081
8425
  } catch {
7082
8426
  }
7083
- try {
7084
- startPinnedExpandSuppression();
7085
- } catch {
7086
- }
7087
8427
  try {
7088
8428
  startWsRunningSpin();
7089
8429
  } catch {