zcode-pro 1.0.0 → 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,18 @@
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
+ };
33
45
  function zcodeAppVersion() {
34
46
  const v = liveBoot().zcodeVersion;
35
47
  return typeof v === "string" && v ? v : "";
@@ -178,7 +190,7 @@
178
190
  featureTaskOrder: "\u4FA7\u8FB9\u680F\u4F1A\u8BDD\u62D6\u52A8\u6392\u5E8F",
179
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",
180
192
  featureSessionSwitch: "\u4F1A\u8BDD\u5FEB\u6377\u5207\u6362",
181
- 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",
182
194
  featureTitleLock: "\u91CD\u547D\u540D\u4F1A\u8BDD\u540D\u9501\u5B9A",
183
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",
184
196
  tabTitleGen: "\u4EFB\u52A1\u540D\u63D0\u793A\u8BCD",
@@ -206,7 +218,7 @@
206
218
  titleGenTestOk: "\u751F\u6210\u7ED3\u679C\uFF1A",
207
219
  titleGenTestFailed: "\u751F\u6210\u5931\u8D25",
208
220
  featureWsRunning: "\u6298\u53E0\u65F6\u4EFB\u52A1\u8FD0\u884C\u4E2D\u63D0\u793A",
209
- 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\uFF1B3.16.1 \u4E4B\u524D\u6BCF 60 \u79D2\u4F1A\u5728\u540E\u53F0\u77ED\u6682\u5C55\u5F00\u9879\u76EE\u590D\u6838\u518D\u6536\u8D77\u3002",
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",
210
222
  tabPerformance: "\u6027\u80FD",
211
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",
212
224
  featureIdleReclaim: "\u7A7A\u95F2\u9879\u76EE\u5185\u5B58\u56DE\u6536",
@@ -234,10 +246,40 @@
234
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",
235
247
  assistantFontName: "\u56DE\u7B54\u5B57\u4F53",
236
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",
237
- 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",
238
250
  switcherCurrent: "\u5F53\u524D",
239
251
  switcherEmpty: "\u6682\u65E0\u4E0A\u6B21\u4F1A\u8BDD\uFF08\u5207\u6362\u8FC7\u4F1A\u8BDD\u540E\u53EF\u7528\uFF09",
240
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",
241
283
  featureFileActions: "\u6587\u4EF6\u83DC\u5355\u589E\u5F3A",
242
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",
243
285
  featureImageCopy: "\u56FE\u7247\u53F3\u952E\u590D\u5236",
@@ -362,7 +404,7 @@
362
404
  headroomStoppedWarn: "\u4EE3\u7406\u5DF2\u505C\u6B62\uFF1B\u6307\u5411 127.0.0.1 \u7684\u4F9B\u5E94\u5546\u5C06\u65E0\u6CD5\u8FDE\u63A5",
363
405
  headroomBackend: "\u538B\u7F29\u8BBE\u5907",
364
406
  headroomBackendAuto: "\u81EA\u52A8 \xB7 \u663E\u5361\u4F18\u5148",
365
- headroomBackendCpu: "CPU \xB7 \u7701\u7535",
407
+ headroomBackendCpu: "\u7701\u7535 \xB7 CPU \u4F18\u5148",
366
408
  headroomBackendNative: "\uFF08\u539F\u751F\u503C\uFF09",
367
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",
368
410
  headroomPower: "\u7701\u7535\u81EA\u52A8\u5207\u6362",
@@ -400,7 +442,7 @@
400
442
  aliasSaved: "\u522B\u540D\u5DF2\u66F4\u65B0",
401
443
  aliasCleared: "\u5DF2\u6062\u590D\u771F\u5B9E\u540D\u79F0",
402
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",
403
- featureProjectMenuDescNew: "\u9879\u76EE\u201C\u66F4\u591A\u201D\u83DC\u5355\u4E2D\u7684\u5207\u6362\u6587\u4EF6\u5939\u3001\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",
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",
404
446
  aliasMigrateTitle: "\u8FC1\u79FB\u9879\u76EE\u522B\u540D",
405
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",
406
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",
@@ -568,7 +610,7 @@
568
610
  featureTaskOrder: "Sidebar session drag ordering",
569
611
  featureTaskOrderDesc: "Makes session drags in Pinned, Projects and Groups persist across refreshes.",
570
612
  featureSessionSwitch: "Session quick switch",
571
- 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)",
572
614
  featureTitleLock: "Keep renamed session titles",
573
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.",
574
616
  tabTitleGen: "Task name prompt",
@@ -596,7 +638,7 @@
596
638
  titleGenTestOk: "Result: ",
597
639
  titleGenTestFailed: "Generation failed",
598
640
  featureWsRunning: "Running indicator when collapsed",
599
- 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. Before ZCode 3.16.1 the project is briefly re-expanded in the background every 60 s to re-check.",
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.",
600
642
  tabPerformance: "Performance",
601
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.",
602
644
  featureIdleReclaim: "Idle project memory reclaim",
@@ -624,10 +666,40 @@
624
666
  userFontDesc: 'Font of question text in conversations; "default" follows the UI font.',
625
667
  assistantFontName: "Answer font",
626
668
  assistantFontDesc: 'Font of answer text in conversations (code blocks stay monospaced); "default" follows the UI font.',
627
- 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",
628
670
  switcherCurrent: "current",
629
671
  switcherEmpty: "No previous session yet (available after you switch sessions)",
630
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",
631
703
  featureFileActions: "File menu actions",
632
704
  featureFileActionsDesc: 'Adds "Open with default app" and "Reveal in file manager" to the right-click menu of file links in chat.',
633
705
  featureImageCopy: "Image right-click copy",
@@ -752,7 +824,7 @@
752
824
  headroomStoppedWarn: "Proxy stopped; providers pointing at 127.0.0.1 cannot connect.",
753
825
  headroomBackend: "Compression device",
754
826
  headroomBackendAuto: "Auto \xB7 GPU first",
755
- headroomBackendCpu: "CPU \xB7 power saving",
827
+ headroomBackendCpu: "Power saving \xB7 CPU first",
756
828
  headroomBackendNative: " (native)",
757
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.",
758
830
  headroomPower: "Power-save auto switch",
@@ -790,7 +862,7 @@
790
862
  aliasSaved: "Alias updated.",
791
863
  aliasCleared: "Real name restored.",
792
864
  aliasHint: "The directory name on disk is unchanged: terminals, file managers and other path-based UI still show the real name.",
793
- featureProjectMenuDescNew: 'Switch folder, 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.',
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.',
794
866
  aliasMigrateTitle: "Migrate project aliases",
795
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.',
796
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.',
@@ -1601,6 +1673,20 @@
1601
1673
  background-color: color-mix(in oklab, var(--color-primary, #111) 12%, transparent);
1602
1674
  color: var(--color-foreground-subtle, #666);
1603
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
+ }
1604
1690
  .zcodepro-switcher-ws {
1605
1691
  font-size: 11px;
1606
1692
  color: var(--color-muted-foreground, #888);
@@ -3845,6 +3931,32 @@
3845
3931
  }
3846
3932
  pluginsStatusLine.textContent = n > 0 ? L.pluginsUpdatesFound.replaceAll("{n}", String(n)) : L.pluginsUpToDate;
3847
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
+ };
3848
3960
  const refreshRows = () => {
3849
3961
  const f = config.features || {};
3850
3962
  rows.replaceChildren(
@@ -3860,7 +3972,7 @@
3860
3972
  if (await setFeature("taskOrder", next)) f.taskOrder = next;
3861
3973
  refreshRows();
3862
3974
  }),
3863
- settingRow(L.featureSessionSwitch, L.featureSessionSwitchDesc, f.sessionSwitch !== false, async () => {
3975
+ settingRow(L.featureSessionSwitch, sessionSwitchDesc(), f.sessionSwitch !== false, async () => {
3864
3976
  const next = !(f.sessionSwitch !== false);
3865
3977
  if (await setFeature("sessionSwitch", next)) f.sessionSwitch = next;
3866
3978
  refreshRows();
@@ -3972,6 +4084,127 @@
3972
4084
  h("div", {}, rows),
3973
4085
  communityCards
3974
4086
  );
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
+ );
3975
4208
  const paneStyles = h("div", { role: "tabpanel", class: "mt-4", style: "display:none" });
3976
4209
  const panePerf = h("div", { role: "tabpanel", class: "mt-4", style: "display:none" });
3977
4210
  const paneAgents = h("div", { role: "tabpanel", class: "mt-2", style: "display:none" });
@@ -3982,9 +4215,10 @@
3982
4215
  const paneHeadroom = h("div", { role: "tabpanel", class: "mt-4", style: "display:none" });
3983
4216
  const paneTitleGen = h("div", { role: "tabpanel", class: "mt-4", style: "display:none" });
3984
4217
  const paneDebug = h("div", { role: "tabpanel", class: "mt-4", style: "display:none" });
3985
- const panes = { features: paneFeatures, styles: paneStyles, perf: panePerf, agents: paneAgents, proxy: paneProxy, sync: paneSync, vision: paneVision, headroom: paneHeadroom, rtk: paneRtk, debug: paneDebug };
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 };
3986
4219
  const tabDefs = [
3987
4220
  ["features", L.tabFeatures],
4221
+ ["shortcuts", L.tabShortcuts],
3988
4222
  ["agents", L.tabBehavior],
3989
4223
  ["styles", L.tabStyles],
3990
4224
  ["perf", L.tabPerformance],
@@ -4008,6 +4242,7 @@
4008
4242
  }, label)));
4009
4243
  const switchTab = (name) => {
4010
4244
  activeTab = name;
4245
+ if (name !== "shortcuts" && hkRecording) stopHkRecording();
4011
4246
  for (const [id, pane] of Object.entries(panes)) pane.style.display = id === name ? "" : "none";
4012
4247
  renderTabs();
4013
4248
  };
@@ -6077,6 +6312,7 @@
6077
6312
  statusLine,
6078
6313
  tablist,
6079
6314
  paneFeatures,
6315
+ paneShortcuts,
6080
6316
  paneAgents,
6081
6317
  paneStyles,
6082
6318
  panePerf,
@@ -6169,1318 +6405,1449 @@
6169
6405
  }
6170
6406
  }
6171
6407
 
6172
- // src/inject/features/session-switch.js
6173
- var MRU_LIMIT = 50;
6408
+ // src/inject/features/ws-running.js
6409
+ var installed2 = false;
6410
+ var enabled2 = true;
6174
6411
  var SCAN_DEBOUNCE_MS = 150;
6175
6412
  var SCAN_FALLBACK_MS = 5e3;
6176
- var mru = [];
6177
- var current = null;
6178
- var titles = /* @__PURE__ */ new Map();
6179
- var popupEl = null;
6180
- var aliasesCache = {};
6181
- var highlight = 0;
6182
- var scanDebounce = 0;
6183
- function firstLineOf(item) {
6184
- const tEl = item.querySelector('[class*="truncate"]');
6185
- 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
+ }
6186
6548
  }
6187
6549
  function workspaceOf(key) {
6188
6550
  const i = key.indexOf(":sess_");
6189
6551
  return i > 0 ? key.slice(0, i) : key;
6190
6552
  }
6191
- function findCurrent() {
6192
- 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)) {
6193
6561
  if ((el.className + "").includes("bg-selected")) return el;
6194
6562
  }
6195
6563
  return null;
6196
6564
  }
6197
- function record(key) {
6198
- if (key && key !== current) {
6199
- current = key;
6200
- mru = mru.filter((k) => k !== key);
6201
- mru.unshift(key);
6202
- 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 {
6203
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);
6204
6578
  }
6205
6579
  function scan() {
6206
- const cur = findCurrent();
6207
- if (cur) record(cur.getAttribute("data-task-item-key"));
6208
- for (const el of document.querySelectorAll("[data-task-item-key]")) {
6209
- titles.set(el.getAttribute("data-task-item-key"), firstLineOf(el));
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);
6592
+ }
6593
+ set.add(key);
6594
+ } else if (set) {
6595
+ set.delete(key);
6596
+ }
6210
6597
  }
6598
+ syncPeeks();
6599
+ apply();
6211
6600
  }
6212
- var rowSel2 = "[data-task-item-key]";
6213
- var inTaskRow = (node) => node instanceof Element && !!node.closest && !!node.closest(rowSel2);
6214
- function taskRowMutations(muts) {
6601
+ function handleRemovals(muts) {
6602
+ const removed = [];
6215
6603
  for (const m of muts) {
6216
- if (m.type === "attributes") {
6217
- if (inTaskRow(m.target)) return true;
6218
- } else if (m.type === "characterData") {
6219
- if (m.target.parentElement && inTaskRow(m.target.parentElement)) return true;
6220
- } else if (inTaskRow(m.target)) {
6221
- return true;
6222
- } else {
6223
- for (const n of m.addedNodes) {
6224
- if (n instanceof Element && (n.matches(rowSel2) || !!n.querySelector(rowSel2))) return true;
6225
- }
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);
6226
6609
  }
6227
6610
  }
6228
- return false;
6229
- }
6230
- function scheduleScan() {
6231
- if (scanDebounce) return;
6232
- scanDebounce = setTimeout(() => {
6233
- scanDebounce = 0;
6234
- scan();
6235
- }, SCAN_DEBOUNCE_MS);
6236
- }
6237
- async function switchTo(key) {
6238
- const find = () => document.querySelector(`[data-task-item-key="${CSS.escape(key)}"]`);
6239
- let item = find();
6240
- if (!item) {
6611
+ if (!removed.length) return;
6612
+ const infos = removed.map((el) => {
6613
+ const key = el.getAttribute("data-task-item-key") || "";
6241
6614
  const ws = workspaceOf(key);
6242
- const header = findProjectRow(ws);
6243
- const expander = header && header.querySelector("[aria-expanded]");
6244
- if (expander && expander.getAttribute("aria-expanded") === "false") {
6245
- expander.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true }));
6246
- for (let i = 0; i < 20 && !(item = find()); i++) await new Promise((r) => setTimeout(r, 100));
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);
6628
+ }
6629
+ set.add(info.key);
6630
+ } else if (set) {
6631
+ set.delete(info.key);
6632
+ }
6247
6633
  }
6248
- }
6249
- if (!item) return false;
6250
- item.scrollIntoView({ block: "nearest" });
6251
- item.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true }));
6252
- return true;
6634
+ syncPeeks();
6635
+ apply();
6636
+ }, 0);
6253
6637
  }
6254
- function closePopup(commit) {
6255
- if (!popupEl) return;
6256
- const idx = highlight;
6257
- popupEl.remove();
6258
- popupEl = null;
6259
- if (commit && idx > 0 && mru[idx] && mru[idx] !== current) {
6260
- void switchTo(mru[idx]).then((ok) => {
6261
- if (!ok) showToast(t().switcherFailed, "error");
6262
- });
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);
6263
6652
  }
6653
+ applySectionSpin();
6654
+ for (const ws of [...runningByWs.keys()]) if (!seen.has(ws)) runningByWs.delete(ws);
6264
6655
  }
6265
- function findTitle(key) {
6266
- const item = document.querySelector(`[data-task-item-key="${CSS.escape(key)}"]`);
6267
- if (item) titles.set(key, firstLineOf(item));
6268
- const id = key.slice(key.indexOf(":sess_") + 1);
6269
- return titles.get(key) || `sess ${id.slice(0, 8)}`;
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;
6270
6664
  }
6271
- function projLabelOf(key, aliases2) {
6272
- const ws = workspaceOf(key);
6273
- if (!ws) return "";
6274
- return aliases2[ws] || ws.replace(/[\\/]+$/, "").split(/[\\/]/).pop();
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);
6275
6683
  }
6276
- function renderPopup() {
6277
- if (!popupEl) return;
6278
- const L = t();
6279
- const list = popupEl.querySelector("[data-zcodepro-switcher-list]");
6280
- list.replaceChildren(...mru.map((key, i) => {
6281
- const proj = projLabelOf(key, aliasesCache);
6282
- return h(
6283
- "div",
6284
- {
6285
- class: "zcodepro-switcher-row",
6286
- "data-active": i === highlight ? "" : void 0,
6287
- onClick: () => {
6288
- highlight = i;
6289
- closePopup(true);
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
+ }
6290
6712
  }
6291
- },
6292
- // 标题文本包 span:截断省略作用在文本节点上(flex 容器自身 ellipsis 无效)
6293
- h(
6294
- "div",
6295
- { class: "zcodepro-switcher-title" },
6296
- i === 0 ? h("span", { class: "zcodepro-switcher-tag" }, L.switcherCurrent) : null,
6297
- h("span", null, findTitle(key))
6298
- ),
6299
- // 无项目归属(如置顶)时不渲染空行
6300
- proj ? h("div", { class: "zcodepro-switcher-ws" }, proj) : null
6301
- );
6302
- }));
6303
- }
6304
- async function openPopup() {
6305
- if (popupEl) return;
6306
- const cfg = await getConfig().catch(() => null);
6307
- aliasesCache = cfg && cfg.aliases && typeof cfg.aliases === "object" ? cfg.aliases : {};
6308
- ensureStyle();
6309
- const L = t();
6310
- highlight = 0;
6311
- popupEl = h(
6312
- "div",
6313
- {
6314
- class: "zcodepro-switcher-overlay",
6315
- // 点弹窗外 = 取消(不切)
6316
- onMousedown: (e) => {
6317
- if (e.target === popupEl) closePopup(false);
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;
6318
6767
  }
6319
- },
6320
- h(
6321
- "div",
6322
- { class: "zcodepro-switcher" },
6323
- h("div", { class: "zcodepro-switcher-hint" }, L.switcherHint),
6324
- h("div", { "data-zcodepro-switcher-list": "1", class: "zcodepro-switcher-list" })
6325
- )
6326
- );
6327
- document.body.append(popupEl);
6328
- renderPopup();
6329
- }
6330
- function moveHighlight(delta) {
6331
- if (!popupEl || mru.length < 2) return;
6332
- highlight = (highlight + delta + mru.length) % mru.length;
6333
- renderPopup();
6334
- const row = popupEl.querySelector("[data-active]");
6335
- if (row) row.scrollIntoView({ block: "nearest" });
6768
+ } catch {
6769
+ }
6770
+ if (hit !== null) anyActiveCache = { at: Date.now(), value: hit, busy: false };
6771
+ else anyActiveCache.busy = false;
6772
+ apply();
6773
+ })();
6774
+ return anyActiveCache.value === true;
6336
6775
  }
6337
- function startSessionSwitch() {
6338
- scan();
6339
- new MutationObserver((muts) => {
6340
- if (taskRowMutations(muts)) scheduleScan();
6341
- }).observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ["class"] });
6342
- setInterval(() => {
6343
- if (!document.hidden) scan();
6344
- }, SCAN_FALLBACK_MS);
6345
- document.addEventListener("visibilitychange", () => {
6346
- if (!document.hidden) scan();
6347
- });
6348
- document.addEventListener("click", (e) => {
6349
- const item = e.target instanceof Element && e.target.closest("[data-task-item-key]");
6350
- if (item) record(item.getAttribute("data-task-item-key"));
6351
- }, true);
6352
- document.addEventListener("keydown", (e) => {
6353
- if (e.key === "Escape" && popupEl) {
6354
- e.preventDefault();
6355
- e.stopImmediatePropagation();
6356
- closePopup(false);
6357
- return;
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;
6358
6788
  }
6359
- if (!e.altKey || e.ctrlKey || e.metaKey || e.isComposing) return;
6360
- if (document.querySelector(".zcodepro-overlay")) return;
6361
- const k = e.key.toLowerCase();
6362
- if (k !== "z" && k !== "x" && k !== "c") return;
6363
- e.preventDefault();
6364
- e.stopImmediatePropagation();
6365
- void (async () => {
6366
- const cfg = await getConfig();
6367
- if (cfg.features && cfg.features.sessionSwitch === false) return;
6368
- if (k === "z") {
6369
- if (e.repeat) return;
6370
- closePopup(false);
6371
- const live = findCurrent();
6372
- if (live) record(live.getAttribute("data-task-item-key"));
6373
- const target = live ? mru[0] === current ? mru[1] : mru.find((mk) => mk !== current) : mru[0];
6374
- if (!target) {
6375
- showToast(t().switcherEmpty);
6376
- return;
6377
- }
6378
- void switchTo(target).then((ok) => {
6379
- if (!ok) showToast(t().switcherFailed, "error");
6380
- });
6381
- return;
6382
- }
6383
- if (mru.length < 2) {
6384
- showToast(t().switcherEmpty);
6385
- return;
6386
- }
6387
- if (!popupEl) await openPopup();
6388
- moveHighlight(k === "x" ? -1 : 1);
6389
- })();
6390
- }, true);
6391
- document.addEventListener("keyup", (e) => {
6392
- if (e.key === "Alt" && popupEl) closePopup(true);
6393
- }, true);
6394
- window.addEventListener("blur", () => closePopup(false));
6395
- }
6396
-
6397
- // src/inject/features/title-lock.js
6398
- var TASK_SEL = "[data-task-item-key]";
6399
- var SCAN_DEBOUNCE_MS2 = 150;
6400
- var SCAN_FALLBACK_MS2 = 5e3;
6401
- var BOOT_RETRY_MS = 5e3;
6402
- var RECHECK_DELAY_MS = 1e3;
6403
- var REVERIFY_MS = 3e3;
6404
- var MAP_REFRESH_MS = 6e4;
6405
- var installed2 = false;
6406
- var seenRows = /* @__PURE__ */ new WeakSet();
6407
- var enabled2 = true;
6408
- var pinned = null;
6409
- var resolved = false;
6410
- var fetching = null;
6411
- var pendingKeys = /* @__PURE__ */ new Set();
6412
- function sessIdOf(key) {
6413
- const i = key.indexOf(":sess_");
6414
- return i > 0 ? key.slice(i + 1) : key;
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
+ }
6415
6802
  }
6416
- function titleElsOf(item) {
6417
- const copies = item.querySelectorAll("[data-task-title-copy]");
6418
- if (copies.length) return [...copies];
6419
- const t2 = item.querySelector('[class*="truncate"]');
6420
- return t2 ? [t2] : [];
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));
6421
6809
  }
6422
- function findRows(key) {
6423
- return [...document.querySelectorAll(`${TASK_SEL}[data-task-item-key="${CSS.escape(key)}"]`)];
6810
+ function clearPeek(ws) {
6811
+ const t2 = peekTimers.get(ws);
6812
+ if (t2) {
6813
+ clearTimeout(t2);
6814
+ peekTimers.delete(ws);
6815
+ }
6424
6816
  }
6425
- async function fetchPinned() {
6426
- if (fetching) return fetching;
6427
- const p = (async () => {
6428
- const res = await rpc("/pinned-titles");
6429
- if (!res || !res.ok) return null;
6430
- resolved = true;
6431
- enabled2 = res.enabled !== false;
6432
- if (!enabled2) {
6433
- pinned = null;
6434
- return null;
6817
+ function syncPeeks() {
6818
+ for (const [ws, set] of runningByWs) {
6819
+ if (!enabled2 || !set.size) {
6820
+ clearPeek(ws);
6821
+ continue;
6435
6822
  }
6436
- pinned = new Map(Object.entries(res.titles || {}));
6437
- return pinned;
6438
- })();
6439
- fetching = p;
6440
- try {
6441
- return await p;
6442
- } finally {
6443
- if (fetching === p) fetching = null;
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);
6444
6830
  }
6445
6831
  }
6446
- function isMismatch(item) {
6447
- if (!enabled2 || !pinned) return null;
6448
- const key = item.getAttribute("data-task-item-key") || "";
6449
- const want = pinned.get(sessIdOf(key));
6450
- if (want === void 0) return null;
6451
- const els = titleElsOf(item);
6452
- if (!els.length) return null;
6453
- if ((els[0].textContent || "").trim() === String(want).trim()) return null;
6454
- if (item.querySelector('input, textarea, [contenteditable="true"]')) return null;
6455
- return key;
6456
- }
6457
- function writeTitle(el, text) {
6458
- const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
6459
- const first = walker.nextNode();
6460
- if (first && !walker.nextNode()) first.nodeValue = text;
6461
- else el.textContent = text;
6462
- }
6463
- function scheduleEnforce(key, { immediate = false } = {}) {
6464
- if (immediate) {
6465
- if (enabled2 && pinned) {
6466
- const want = pinned.get(sessIdOf(key));
6467
- if (want !== void 0) {
6468
- for (const item of findRows(key)) {
6469
- for (const el of titleElsOf(item)) writeTitle(el, want);
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);
6470
6844
  }
6845
+ set.add(ws + ":active");
6846
+ schedulePeek(ws, PEEK_REPEAT_MS);
6471
6847
  }
6848
+ apply();
6849
+ return true;
6472
6850
  }
6473
6851
  }
6474
- if (pendingKeys.has(key)) return;
6475
- pendingKeys.add(key);
6476
- setTimeout(() => {
6477
- pendingKeys.delete(key);
6478
- void (async () => {
6479
- const map = await fetchPinned();
6480
- if (!map) return;
6481
- const want = map.get(sessIdOf(key));
6482
- if (want === void 0) return;
6483
- let wrote = false;
6484
- for (const item of findRows(key)) {
6485
- if (isMismatch(item) !== key) continue;
6486
- for (const el of titleElsOf(item)) writeTitle(el, want);
6487
- wrote = true;
6488
- }
6489
- if (wrote) setTimeout(() => {
6490
- void enforceKey(key);
6491
- }, REVERIFY_MS);
6492
- })();
6493
- }, RECHECK_DELAY_MS);
6852
+ const run = () => peekInner(ws, opts);
6853
+ const p = peekChain.then(run, run);
6854
+ peekChain = p.catch(() => {
6855
+ });
6856
+ return p;
6494
6857
  }
6495
- async function enforceKey(key) {
6496
- const map = await fetchPinned();
6497
- if (!map) return;
6498
- const want = map.get(sessIdOf(key));
6499
- if (want === void 0) return;
6500
- for (const item of findRows(key)) {
6501
- if (isMismatch(item) !== key) continue;
6502
- for (const el of titleElsOf(item)) writeTitle(el, want);
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;
6503
6866
  }
6504
- }
6505
- function scan2() {
6506
- if (!enabled2 || !pinned || pinned.size === 0) return;
6507
- for (const item of document.querySelectorAll(TASK_SEL)) {
6508
- const key = isMismatch(item);
6509
- if (!key) {
6510
- seenRows.add(item);
6511
- continue;
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);
6512
6888
  }
6513
- const fresh = !seenRows.has(item);
6514
- seenRows.add(item);
6515
- scheduleEnforce(key, { immediate: fresh });
6889
+ } finally {
6890
+ removePeekHider(hider);
6891
+ row.style.visibility = "";
6892
+ frozen.remove();
6893
+ activePeeks.delete(ws);
6516
6894
  }
6895
+ apply();
6896
+ const set = runningByWs.get(ws);
6897
+ if (set && set.size && enabled2) schedulePeek(ws, PEEK_REPEAT_MS);
6898
+ return readOk;
6517
6899
  }
6518
- var inTaskRow2 = (node) => !!(node instanceof Element && node.closest && node.closest(TASK_SEL));
6519
- function taskRowMutations2(muts) {
6520
- for (const m of muts) {
6521
- if (m.type === "attributes") {
6522
- if (inTaskRow2(m.target)) return true;
6523
- } else if (m.type === "characterData") {
6524
- if (m.target.parentElement && inTaskRow2(m.target.parentElement)) return true;
6525
- } else if (inTaskRow2(m.target)) {
6526
- return true;
6527
- } else {
6528
- for (const n of m.addedNodes) {
6529
- if (n instanceof Element && (n.matches(TASK_SEL) || !!n.querySelector(TASK_SEL))) return true;
6530
- }
6531
- }
6532
- }
6533
- return false;
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
+ };
6534
6906
  }
6535
- var scanDebounce2 = 0;
6536
- function scheduleScan2() {
6537
- if (scanDebounce2) return;
6538
- scanDebounce2 = setTimeout(() => {
6539
- scanDebounce2 = 0;
6540
- scan2();
6541
- }, SCAN_DEBOUNCE_MS2);
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;
6542
6911
  }
6543
- var mapRefreshTimer = 0;
6544
- function refreshMapSoon() {
6545
- if (mapRefreshTimer) return;
6546
- const tick2 = () => {
6547
- if (document.hidden) {
6548
- mapRefreshTimer = setTimeout(tick2, 1e4);
6549
- return;
6550
- }
6551
- mapRefreshTimer = 0;
6552
- if (enabled2 && resolved) void fetchPinned().then((m) => {
6553
- if (m) scan2();
6554
- });
6555
- };
6556
- mapRefreshTimer = setTimeout(tick2, MAP_REFRESH_MS);
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;
6921
+ }
6922
+ let verified = false;
6923
+ try {
6924
+ verified = await peek(ws, { manual: true }) === true;
6925
+ } catch {
6926
+ }
6927
+ if (!verified) return false;
6928
+ const set = runningByWs.get(ws);
6929
+ return !(set && set.size);
6557
6930
  }
6558
- var editingKey = null;
6559
- var bootDelay = 0;
6560
- function scheduleBootRetry() {
6561
- bootDelay = bootDelay ? Math.min(bootDelay * 2, MAP_REFRESH_MS) : BOOT_RETRY_MS;
6562
- setTimeout(() => {
6563
- if (resolved) return;
6564
- if (!document.hidden) void bootstrap();
6565
- scheduleBootRetry();
6566
- }, bootDelay);
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);
6963
+ });
6567
6964
  }
6568
- async function bootstrap() {
6569
- const map = await fetchPinned();
6570
- if (map) scan2();
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 };
6571
6974
  }
6572
- function setRowTitle(key, text) {
6573
- for (const item of findRows(key)) {
6574
- for (const el of titleElsOf(item)) writeTitle(el, text);
6575
- }
6975
+ function removePeekHider(hider) {
6976
+ hider.style.remove();
6977
+ for (const r of document.querySelectorAll("." + PEEK_EXEMPT_CLASS)) r.classList.remove(PEEK_EXEMPT_CLASS);
6576
6978
  }
6577
- function refreshPinnedTitles() {
6578
- return fetchPinned().then((m) => {
6579
- if (m) scan2();
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);
6580
6989
  });
6581
6990
  }
6582
- var RENAME_CTX_VALID_MS = 15e3;
6583
- var renameCtx = null;
6584
- var renamePrefilled = /* @__PURE__ */ new WeakSet();
6585
- function setNativeValue(el, text) {
6586
- if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) {
6587
- Object.getOwnPropertyDescriptor(el.constructor.prototype, "value").set.call(el, text);
6588
- } else {
6589
- el.textContent = text;
6590
- }
6591
- el.dispatchEvent(new Event("input", { bubbles: true }));
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;
6592
6996
  }
6593
- function prefillRenameDialog() {
6594
- if (!renameCtx || !renameCtx.armed || Date.now() - renameCtx.at > RENAME_CTX_VALID_MS) {
6595
- renameCtx = null;
6596
- return;
6997
+ function inSel(node, sel) {
6998
+ return !!(node instanceof Element && node.closest && node.closest(sel));
6999
+ }
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
+ }
6597
7009
  }
6598
- if (!enabled2 || !pinned) return;
6599
- const want = pinned.get(sessIdOf(renameCtx.key));
6600
- if (want === void 0) return;
6601
- for (const el of document.querySelectorAll('[role="dialog"] input, [role="dialog"] textarea, [role="dialog"] [contenteditable="true"]')) {
6602
- if (renamePrefilled.has(el) || !el.getClientRects().length) continue;
6603
- if (el.closest(TASK_SEL) || el.closest(".zcodepro-overlay")) continue;
6604
- const cur = String(el.value ?? el.textContent ?? "").trim();
6605
- if (!cur) continue;
6606
- renamePrefilled.add(el);
6607
- if (cur !== String(want).trim()) setNativeValue(el, String(want));
6608
- renameCtx = null;
6609
- return;
7010
+ return false;
7011
+ }
7012
+ var pollMs = CONFIG_POLL_MIN_MS;
7013
+ async function refreshConfig() {
7014
+ try {
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;
7025
+ }
7026
+ }
7027
+ } catch {
6610
7028
  }
7029
+ return false;
6611
7030
  }
6612
- function startTitleLock() {
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() {
6613
7040
  if (installed2 || typeof document === "undefined") return;
6614
7041
  installed2 = true;
6615
- void bootstrap();
6616
- new MutationObserver((muts) => {
6617
- if (renameCtx) prefillRenameDialog();
6618
- if (!taskRowMutations2(muts)) return;
6619
- refreshMapSoon();
6620
- scheduleScan2();
6621
- }).observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ["class"], characterData: true });
6622
- document.addEventListener("contextmenu", (e) => {
6623
- const row = e.target instanceof Element && e.target.closest(TASK_SEL);
6624
- renameCtx = row ? { key: row.getAttribute("data-task-item-key") || "", at: 0, armed: false } : null;
6625
- }, true);
6626
- document.addEventListener("click", (e) => {
6627
- if (!renameCtx || renameCtx.armed) return;
6628
- const item = e.target instanceof Element && e.target.closest('[role="menuitem"], [data-radix-collection-item]');
6629
- if (item && /重命名|rename/i.test(item.textContent || "")) {
6630
- renameCtx.armed = true;
6631
- renameCtx.at = Date.now();
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);
6632
7052
  }
6633
- }, true);
6634
- scheduleBootRetry();
7053
+ });
7054
+ observer2.observe(document.body, { childList: true, subtree: true });
7055
+ scan();
6635
7056
  setInterval(() => {
6636
- if (!document.hidden) scan2();
6637
- }, SCAN_FALLBACK_MS2);
7057
+ if (!document.hidden && enabled2) scan();
7058
+ }, SCAN_FALLBACK_MS);
6638
7059
  document.addEventListener("visibilitychange", () => {
6639
- if (!document.hidden) scan2();
6640
- });
6641
- window.addEventListener("zcodepro:config-changed", () => {
6642
- void bootstrap();
6643
- });
6644
- document.addEventListener("focusin", (e) => {
6645
- const el = e.target;
6646
- if (!(el instanceof Element)) return;
6647
- if (!el.matches('input, textarea, [contenteditable="true"]')) return;
6648
- const row = el.closest(TASK_SEL);
6649
- editingKey = row ? row.getAttribute("data-task-item-key") || "" : null;
6650
- });
6651
- document.addEventListener("focusout", () => {
6652
- if (editingKey === null) return;
6653
- editingKey = null;
6654
- void fetchPinned().then((m) => {
6655
- if (m) scan2();
6656
- });
7060
+ if (!document.hidden) scan();
6657
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);
6658
7067
  }
6659
7068
 
6660
- // src/inject/features/title-gen.js
6661
- var CONV2 = '[class*="@md/conversation"]';
6662
- var USER_ROW = '[class*="user-row"]';
6663
- var SCAN_DEBOUNCE_MS3 = 300;
6664
- var SCAN_FALLBACK_MS3 = 5e3;
6665
- var SELECT_WAIT_MS = 2e3;
6666
- var MSG_WAIT_MS = 1e4;
6667
- var QUIET_FALLBACK_MS = 12e4;
6668
- var COLLECT_MAX_MS = 6e5;
6669
- var installed3 = false;
6670
- var knownKeys = /* @__PURE__ */ new Set();
6671
- var attempted = /* @__PURE__ */ new Set();
6672
- var sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
6673
- function sessIdOf2(key) {
6674
- const i = key.indexOf(":sess_");
6675
- return i > 0 ? key.slice(i + 1) : "";
6676
- }
6677
- function currentKey() {
6678
- for (const el of document.querySelectorAll("[data-task-item-key]")) {
6679
- if ((el.className + "").includes("bg-selected")) return el.getAttribute("data-task-item-key") || "";
6680
- }
6681
- return "";
6682
- }
6683
- function userTexts() {
6684
- return [...document.querySelectorAll(`${CONV2} ${USER_ROW}`)].map((r) => {
6685
- const el = r.querySelector(".whitespace-pre-wrap");
6686
- return ((el ? el.textContent : r.innerText) || "").trim();
6687
- }).filter(Boolean);
6688
- }
6689
- var URL_RE = /https?:\/\/[^\s\u4e00-\u9fff\u3000-\u303f\uff00-\uffef]+/g;
6690
- function assistantText() {
6691
- const els = [...document.querySelectorAll('[class*="@md/conversation"] [class*="assistant-row"]')].filter((e) => !e.querySelector("[data-is-streaming]"));
6692
- const last = els[els.length - 1];
6693
- if (!last) return "";
6694
- const text = (last.innerText || "").replace(URL_RE, "").trim();
6695
- return text.slice(0, 600);
6696
- }
6697
- function linkDominant(t2) {
6698
- const raw = t2.replace(/\s+/g, "");
6699
- if (!raw) return true;
6700
- const stripped = raw.replace(URL_RE, "");
6701
- return stripped.length < raw.length / 3;
6702
- }
6703
- async function onNewTaskRow(key) {
6704
- const selDeadline = Date.now() + SELECT_WAIT_MS;
6705
- while (currentKey() !== key && Date.now() < selDeadline) await sleep2(200);
6706
- if (currentKey() !== key) return;
6707
- const cfg = await getConfig().catch(() => null);
6708
- if (!cfg || !cfg.titleGen || cfg.titleGen.enabled !== true) return;
6709
- const sessId = sessIdOf2(key);
6710
- if (!sessId || attempted.has(sessId)) return;
6711
- attempted.add(sessId);
6712
- let texts = [];
6713
- const msgDeadline = Date.now() + MSG_WAIT_MS;
6714
- while (Date.now() < msgDeadline) {
6715
- texts = userTexts();
6716
- if (texts.length) break;
6717
- await sleep2(400);
6718
- }
6719
- if (!texts.length) return;
6720
- let assistantSeen = false;
6721
- const convObserver = new MutationObserver((muts) => {
6722
- if (currentKey() !== key) return;
6723
- for (const m of muts) {
6724
- const t2 = m.type === "characterData" ? m.target.parentElement : m.target;
6725
- if (t2 instanceof Element && t2.closest(USER_ROW)) continue;
6726
- for (const n of [t2, ...m.addedNodes]) {
6727
- if (n instanceof Element && n.closest && n.closest(CONV2)) {
6728
- assistantSeen = true;
6729
- return;
6730
- }
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];
6731
7095
  }
6732
7096
  }
7097
+ }).catch(() => {
6733
7098
  });
6734
- convObserver.observe(document.body, { childList: true, subtree: true, characterData: true });
6735
- const startAt = Date.now();
6736
- let lastCount = texts.length;
6737
- let lastChange = Date.now();
6738
- try {
6739
- while (Date.now() - startAt < COLLECT_MAX_MS) {
6740
- await sleep2(500);
6741
- if (currentKey() !== key) {
6742
- lastChange = Date.now();
6743
- continue;
6744
- }
6745
- texts = userTexts();
6746
- if (texts.length !== lastCount) {
6747
- lastCount = texts.length;
6748
- lastChange = Date.now();
6749
- }
6750
- if (assistantSeen) break;
6751
- if (Date.now() - lastChange >= QUIET_FALLBACK_MS) break;
6752
- }
6753
- } finally {
6754
- convObserver.disconnect();
6755
- }
6756
- if (!texts.length) return;
6757
- if (linkDominant(texts.join("\n"))) {
6758
- const deadline = Date.now() + 6e4;
6759
- while (Date.now() < deadline) {
6760
- await sleep2(2e3);
6761
- if (currentKey() !== key) continue;
6762
- const a = assistantText();
6763
- if (a && a.length > 80) {
6764
- texts.push(`\uFF08\u9996\u8F6E\u56DE\u7B54\u6458\u8981\uFF09${a}`);
6765
- break;
6766
- }
6767
- }
6768
- }
6769
- const callTitleGen = () => rpc("/title-gen/run", { method: "POST", body: { sessId, text: texts.join("\n") } }).catch(() => null);
6770
- let res = await callTitleGen();
6771
- for (const delay of [5e3, 2e4]) {
6772
- if (res && res.ok) break;
6773
- await sleep2(delay);
6774
- res = await callTitleGen();
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;
6775
7111
  }
6776
- if (res && res.ok && res.written && res.title) {
6777
- setRowTitle(key, res.title);
6778
- 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;
6779
7121
  }
6780
7122
  }
6781
- function scanForNewRows() {
7123
+ function scan2() {
7124
+ const cur = findCurrent();
7125
+ if (cur) record(cur.getAttribute("data-task-item-key"));
6782
7126
  for (const el of document.querySelectorAll("[data-task-item-key]")) {
6783
- const key = el.getAttribute("data-task-item-key") || "";
6784
- if (!key || knownKeys.has(key)) continue;
6785
- knownKeys.add(key);
6786
- void onNewTaskRow(key);
7127
+ titles.set(el.getAttribute("data-task-item-key"), firstLineOf(el));
6787
7128
  }
6788
7129
  }
6789
- var TASK_SEL2 = "[data-task-item-key]";
6790
- var inTaskRow3 = (node) => !!(node instanceof Element && node.closest && node.closest(TASK_SEL2));
6791
- 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) {
6792
7133
  for (const m of muts) {
6793
7134
  if (m.type === "attributes") {
6794
- if (inTaskRow3(m.target)) return true;
7135
+ if (inTaskRow(m.target)) return true;
6795
7136
  } else if (m.type === "characterData") {
6796
- if (m.target.parentElement && inTaskRow3(m.target.parentElement)) return true;
6797
- } else if (inTaskRow3(m.target)) {
7137
+ if (m.target.parentElement && inTaskRow(m.target.parentElement)) return true;
7138
+ } else if (inTaskRow(m.target)) {
6798
7139
  return true;
6799
7140
  } else {
6800
7141
  for (const n of m.addedNodes) {
6801
- 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;
6802
7143
  }
6803
7144
  }
6804
7145
  }
6805
7146
  return false;
6806
7147
  }
6807
- var scanDebounce3 = 0;
6808
- function scheduleScan3() {
6809
- if (scanDebounce3) return;
6810
- scanDebounce3 = setTimeout(() => {
6811
- scanDebounce3 = 0;
6812
- scanForNewRows();
6813
- }, SCAN_DEBOUNCE_MS3);
6814
- }
6815
- function startTitleGen() {
6816
- if (installed3 || typeof document === "undefined") return;
6817
- installed3 = true;
6818
- scanForNewRows();
6819
- new MutationObserver((muts) => {
6820
- if (taskRowMutations3(muts)) scheduleScan3();
6821
- }).observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ["class"], characterData: true });
6822
- setInterval(() => {
6823
- if (!document.hidden) scanForNewRows();
6824
- }, SCAN_FALLBACK_MS3);
6825
- document.addEventListener("visibilitychange", () => {
6826
- if (!document.hidden) scanForNewRows();
6827
- });
7148
+ function scheduleScan() {
7149
+ if (scanDebounce2) return;
7150
+ scanDebounce2 = setTimeout(() => {
7151
+ scanDebounce2 = 0;
7152
+ scan2();
7153
+ }, SCAN_DEBOUNCE_MS2);
6828
7154
  }
6829
-
6830
- // src/inject/features/ws-running.js
6831
- var installed4 = false;
6832
- var enabled3 = true;
6833
- var SCAN_DEBOUNCE_MS4 = 150;
6834
- var SCAN_FALLBACK_MS4 = 5e3;
6835
- var PEEK_FIRST_MS = 2e4;
6836
- var PEEK_REPEAT_MS = 6e4;
6837
- var PEEK_SETTLE_MS = 160;
6838
- var PEEK_MAX_MS = 1500;
6839
- var PEEK_UNMOUNT_WAIT_MS = 800;
6840
- var CONFIG_POLL_MIN_MS = 5e3;
6841
- var CONFIG_POLL_MAX_MS = 6e4;
6842
- var TASK_SEL3 = "[data-task-item-key]";
6843
- var WS_SEL = () => projectRowSelector();
6844
- function findWsRow(ws) {
6845
- return findProjectRow(ws);
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;
6846
7171
  }
6847
- var LOADER_FALLBACK_PATHS = [
6848
- "M12 2v4",
6849
- "m16.2 7.8 2.9-2.9",
6850
- "M18 12h4",
6851
- "m19.8 16.2-2.9 2.9",
6852
- "M12 18v4",
6853
- "m7.8 19.8-2.9-2.9",
6854
- "M6 12H2",
6855
- "m4.2 7.8 2.9-2.9"
6856
- ];
6857
- function makeLoaderSvg(baseClass) {
6858
- const ns = "http://www.w3.org/2000/svg";
6859
- const proto = document.querySelector("svg.lucide-loader");
6860
- if (proto) {
6861
- const clone = proto.cloneNode(true);
6862
- const origClass = proto.getAttribute("class") || "";
6863
- const keep = origClass.split(/\s+/).filter((c) => /^(size-|text-)/.test(c)).join(" ");
6864
- clone.setAttribute("class", ((keep || baseClass) + " animate-spin zcodepro-ws-loader").trim());
6865
- return clone;
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
+ }
6866
7181
  }
6867
- const svg = document.createElementNS(ns, "svg");
6868
- svg.setAttribute("viewBox", "0 0 24 24");
6869
- svg.setAttribute("fill", "none");
6870
- svg.setAttribute("stroke", "currentColor");
6871
- svg.setAttribute("stroke-width", "2");
6872
- svg.setAttribute("stroke-linecap", "round");
6873
- svg.setAttribute("stroke-linejoin", "round");
6874
- svg.setAttribute("class", (baseClass + " animate-spin zcodepro-ws-loader").trim());
6875
- for (const d of LOADER_FALLBACK_PATHS) {
6876
- const path = document.createElementNS(ns, "path");
6877
- path.setAttribute("d", d);
6878
- svg.append(path);
7182
+ const tail = [];
7183
+ for (const k of run) if (!listed.has(k)) {
7184
+ tail.push(k);
7185
+ listed.add(k);
6879
7186
  }
6880
- return svg;
6881
- }
6882
- function setRunningIcon(holder, on) {
6883
- if (!holder) return;
6884
- if (on) {
6885
- if (holder.querySelector(":scope > svg.zcodepro-ws-loader")) return;
6886
- const orig0 = holder.querySelector(":scope > svg:not(.zcodepro-ws-loader)");
6887
- const base = (orig0 ? orig0.getAttribute("class") || "" : "size-4").replace(/lucide-[a-z-]*/g, "").trim() || "size-4";
6888
- holder.append(makeLoaderSvg(base));
6889
- } else {
6890
- holder.querySelectorAll(":scope > svg.zcodepro-ws-loader").forEach((el) => el.remove());
7187
+ for (const k of unseen) if (!listed.has(k)) {
7188
+ tail.push(k);
7189
+ listed.add(k);
6891
7190
  }
7191
+ return [...head, ...tail];
6892
7192
  }
6893
- function iconHolderOf(row) {
6894
- if (row.hasAttribute && row.hasAttribute("data-project-id")) {
6895
- const toggle2 = row.querySelector('[data-testid="virtual-project-toggle"]');
6896
- return toggle2 ? toggle2.querySelector(":scope > span") : null;
6897
- }
6898
- return row.firstElementChild && row.firstElementChild.querySelector(":scope > span");
7193
+ function activeList() {
7194
+ return popupMode === "attention" ? attentionList() : mru;
6899
7195
  }
6900
- function isRunningRow(el) {
6901
- const scope = el.firstElementChild;
6902
- if (!scope) return false;
6903
- const svg = scope.querySelector('svg[class*="lucide-loader"]');
6904
- return !!svg;
6905
- }
6906
- var runningByWs = /* @__PURE__ */ new Map();
6907
- var peekTimers = /* @__PURE__ */ new Map();
6908
- var activePeeks = /* @__PURE__ */ new Set();
6909
- var peekedWs = /* @__PURE__ */ new Set();
6910
- var wsWithRows = /* @__PURE__ */ new Set();
6911
- var servicesRef = null;
6912
- function findServices() {
6913
- if (servicesRef) return servicesRef;
6914
- try {
6915
- const rootEl = document.getElementById("root");
6916
- const containerKey = rootEl && Object.keys(rootEl).find((k) => k.startsWith("__reactContainer$"));
6917
- if (!containerKey) return null;
6918
- const seen = /* @__PURE__ */ new Set();
6919
- let found = null;
6920
- const scanObj = (o) => {
6921
- if (!o || typeof o !== "object" || seen.has(o) || found) return;
6922
- seen.add(o);
6923
- const ts = o.zcodeTaskService;
6924
- if (ts && typeof ts === "object" && typeof ts.releaseWorkspacePreparation === "function") found = o;
6925
- };
6926
- let visited = 0;
6927
- const visit = (f) => {
6928
- if (!f || found || visited > 6e4) return;
6929
- visited++;
6930
- for (const holder of [f.memoizedProps, f.memoizedState]) {
6931
- if (!holder || typeof holder !== "object") continue;
6932
- scanObj(holder);
6933
- for (const v of Object.values(holder)) {
6934
- if (v && typeof v === "object") scanObj(v);
6935
- }
6936
- }
6937
- visit(f.child);
6938
- visit(f.sibling);
6939
- };
6940
- visit(rootEl[containerKey]);
6941
- if (found) servicesRef = found;
6942
- return found;
6943
- } catch {
6944
- return null;
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
+ });
6945
7206
  }
6946
7207
  }
6947
- async function authoritativeIdle(ws) {
6948
- const services = findServices();
6949
- const ts = services && services.zcodeTaskService;
6950
- if (!ts || typeof ts.listActiveTaskIds !== "function") return null;
6951
- try {
6952
- const base = String(ws).replace(/[\\/]+$/, "");
6953
- const a = await ts.listActiveTaskIds({ workspacePath: base });
6954
- const b = await ts.listActiveTaskIds({ workspacePath: base + "/" });
6955
- const ids = [...Array.isArray(a) ? a : [], ...Array.isArray(b) ? b : []].map(String);
6956
- if (!Array.isArray(a) || !Array.isArray(b)) return null;
6957
- if (!ids.length) return true;
6958
- const st = await rpc("/task-statuses?ids=" + encodeURIComponent(ids.join(","))).catch(() => null);
6959
- const TERMINAL = /* @__PURE__ */ new Set(["completed", "error", "interrupted"]);
6960
- const live = (id) => {
6961
- const rec = st && st.ok && st.available ? st.statuses[id] : void 0;
6962
- if (rec === void 0) return true;
6963
- if (!TERMINAL.has(rec.status)) return true;
6964
- return Date.now() - rec.updatedAt < 3e5;
6965
- };
6966
- return !ids.some(live);
6967
- } catch {
6968
- return null;
6969
- }
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)}`;
6970
7213
  }
6971
- function workspaceOf2(key) {
6972
- const i = key.indexOf(":sess_");
6973
- return i > 0 ? key.slice(0, i) : key;
7214
+ function projLabelOf(key, aliases2) {
7215
+ const ws = workspaceOf2(key);
7216
+ if (!ws) return "";
7217
+ return aliases2[ws] || ws.replace(/[\\/]+$/, "").split(/[\\/]/).pop();
6974
7218
  }
6975
- function wsHead(row) {
6976
- return row.matches("[aria-expanded]") ? row : row.querySelector("[aria-expanded]");
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
+ }
6977
7258
  }
6978
- function domRowsOf(ws) {
6979
- return [...document.querySelectorAll(TASK_SEL3)].filter((e) => workspaceOf2(e.getAttribute("data-task-item-key") || "") === ws);
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();
6980
7292
  }
6981
- function currentTaskRow() {
6982
- for (const el of document.querySelectorAll(TASK_SEL3)) {
6983
- if ((el.className + "").includes("bg-selected")) return el;
6984
- }
6985
- return null;
7293
+ function scrollHighlight() {
7294
+ const row = popupEl && popupEl.querySelector("[data-active]");
7295
+ if (row) row.scrollIntoView({ block: "nearest" });
6986
7296
  }
6987
- var synthClick = (el) => {
6988
- try {
6989
- el.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true }));
6990
- } catch {
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;
6991
7316
  }
6992
- };
6993
- function toggleNavSafe(head) {
6994
- const cur = currentTaskRow();
6995
- const backKey = cur ? cur.getAttribute("data-task-item-key") : "";
6996
- synthClick(head);
6997
- if (!backKey) return;
6998
- const back = cur && cur.isConnected ? cur : document.querySelector(`${TASK_SEL3}[data-task-item-key="${CSS.escape(backKey)}"]`);
6999
- if (back) synthClick(back);
7317
+ movedSinceOpen = true;
7318
+ renderPopup();
7319
+ scrollHighlight();
7000
7320
  }
7001
- function scan3() {
7002
- wsWithRows = /* @__PURE__ */ new Set();
7003
- for (const el of document.querySelectorAll(TASK_SEL3)) {
7004
- const key = el.getAttribute("data-task-item-key") || "";
7005
- const ws = workspaceOf2(key);
7006
- if (!ws) continue;
7007
- wsWithRows.add(ws);
7008
- const run = isRunningRow(el);
7009
- let set = runningByWs.get(ws);
7010
- if (run) {
7011
- if (!set) {
7012
- set = /* @__PURE__ */ new Set();
7013
- runningByWs.set(ws, set);
7321
+ function startSessionSwitch() {
7322
+ scan2();
7323
+ void loadHotkeys();
7324
+ window.addEventListener("zcodepro:config-changed", () => {
7325
+ void loadHotkeys();
7326
+ });
7327
+ new MutationObserver((muts) => {
7328
+ if (taskRowMutations(muts)) scheduleScan();
7329
+ }).observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ["class"] });
7330
+ setInterval(() => {
7331
+ if (!document.hidden) scan2();
7332
+ }, SCAN_FALLBACK_MS2);
7333
+ document.addEventListener("visibilitychange", () => {
7334
+ if (!document.hidden) scan2();
7335
+ });
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);
7014
7342
  }
7015
- set.add(key);
7016
- } else if (set) {
7017
- set.delete(key);
7018
7343
  }
7019
- }
7020
- syncPeeks();
7021
- apply();
7022
- }
7023
- function handleRemovals(muts) {
7024
- const removed = [];
7025
- for (const m of muts) {
7026
- if (m.type !== "childList") continue;
7027
- for (const n of m.removedNodes) {
7028
- if (!(n instanceof Element)) continue;
7029
- if (n.matches(TASK_SEL3)) removed.push(n);
7030
- else for (const r of n.querySelectorAll(TASK_SEL3)) removed.push(r);
7344
+ lastRunKeys = now;
7345
+ if (unseen.size > UNSEEN_LIMIT) unseen = new Set([...unseen].slice(-UNSEEN_LIMIT));
7346
+ }, 1e4);
7347
+ document.addEventListener("click", (e) => {
7348
+ const item = e.target instanceof Element && e.target.closest("[data-task-item-key]");
7349
+ if (item) record(item.getAttribute("data-task-item-key"));
7350
+ }, true);
7351
+ document.addEventListener("keydown", (e) => {
7352
+ if (e.key === "Escape" && popupEl) {
7353
+ e.preventDefault();
7354
+ e.stopImmediatePropagation();
7355
+ closePopup(false);
7356
+ return;
7031
7357
  }
7032
- }
7033
- if (!removed.length) return;
7034
- const infos = removed.map((el) => {
7035
- const key = el.getAttribute("data-task-item-key") || "";
7036
- const ws = workspaceOf2(key);
7037
- return key && ws ? { key, ws, run: isRunningRow(el) } : null;
7038
- }).filter(Boolean);
7039
- if (!infos.length) return;
7040
- setTimeout(() => {
7041
- for (const info of infos) {
7042
- const row = findWsRow(info.ws);
7043
- const head = row && wsHead(row);
7044
- const collapsed = !row || !head || head.getAttribute("aria-expanded") === "false";
7045
- let set = runningByWs.get(info.ws);
7046
- if (collapsed && info.run) {
7047
- if (!set) {
7048
- set = /* @__PURE__ */ new Set();
7049
- runningByWs.set(info.ws, set);
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;
7365
+ }
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;
7050
7385
  }
7051
- set.add(info.key);
7052
- } else if (set) {
7053
- set.delete(info.key);
7386
+ void switchTo(target).then((ok) => {
7387
+ if (!ok) showToast(t().switcherFailed, "error");
7388
+ });
7389
+ return;
7054
7390
  }
7055
- }
7056
- syncPeeks();
7057
- apply();
7058
- }, 0);
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));
7059
7418
  }
7060
- function apply() {
7061
- const seen = /* @__PURE__ */ new Set();
7062
- for (const row of document.querySelectorAll(WS_SEL())) {
7063
- const path = rowPath(row);
7064
- if (!path) continue;
7065
- seen.add(path);
7066
- const head = wsHead(row);
7067
- const collapsed = !head || head.getAttribute("aria-expanded") === "false";
7068
- const domRunning = !!(runningByWs.get(path) && runningByWs.get(path).size);
7069
- const normP = (v) => String(v || "").replace(/[\\/]+$/, "");
7070
- const running = enabled3 && collapsed && (activeProjectPaths.size ? [...activeProjectPaths].some((p2) => normP(p2) === normP(path)) : domRunning);
7071
- const icon = iconHolderOf(row);
7072
- icon?.classList.toggle("zcodepro-ws-running", running);
7073
- setRunningIcon(icon, running);
7074
- }
7075
- applySectionSpin();
7076
- for (const ws of [...runningByWs.keys()]) if (!seen.has(ws)) runningByWs.delete(ws);
7419
+
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();
7430
+ var enabled3 = true;
7431
+ var pinned = null;
7432
+ var resolved = false;
7433
+ var fetching = null;
7434
+ var pendingKeys = /* @__PURE__ */ new Set();
7435
+ function sessIdOf(key) {
7436
+ const i = key.indexOf(":sess_");
7437
+ return i > 0 ? key.slice(i + 1) : key;
7077
7438
  }
7078
- var headInfoCache = /* @__PURE__ */ new WeakMap();
7079
- var PROJECT_HEAD_TEXTS = /* @__PURE__ */ new Set(["\u9879\u76EE", "projects"]);
7080
- var TASK_HEAD_TEXTS = /* @__PURE__ */ new Set(["\u4EFB\u52A1", "tasks"]);
7081
- function kindFromHeadText(head) {
7082
- const text = (head.textContent || "").trim().toLowerCase();
7083
- if (PROJECT_HEAD_TEXTS.has(text)) return "project";
7084
- if (TASK_HEAD_TEXTS.has(text)) return "task";
7085
- return null;
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] : [];
7086
7444
  }
7087
- function sectionHeads() {
7088
- const heads = [];
7089
- for (const sv of document.querySelectorAll("svg[data-purpose-section-chevron]")) {
7090
- const btn = sv.closest("button");
7091
- if (btn) heads.push(btn);
7445
+ function findRows(key) {
7446
+ return [...document.querySelectorAll(`${TASK_SEL2}[data-task-item-key="${CSS.escape(key)}"]`)];
7447
+ }
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;
7463
+ try {
7464
+ return await p;
7465
+ } finally {
7466
+ if (fetching === p) fetching = null;
7092
7467
  }
7093
- for (const h3 of document.querySelectorAll("h3")) heads.push(h3);
7094
- return heads.map((head) => {
7095
- const cached = headInfoCache.get(head);
7096
- if (cached) return { head, cont: cached.cont, kind: cached.kind };
7097
- const textKind = head.tagName === "BUTTON" ? kindFromHeadText(head) : "task";
7098
- let cont = head.parentElement;
7099
- while (cont && !cont.querySelector("li[data-task-item-key]") && cont !== document.body) cont = cont.parentElement;
7100
- const contOk = cont && cont !== document.body;
7101
- const kind = textKind || (contOk ? cont.querySelector("section[data-project-id]") ? "project" : "task" : null);
7102
- headInfoCache.set(head, { cont: contOk ? cont : null, kind });
7103
- return { head, cont: contOk ? cont : null, kind };
7104
- }).filter((x) => x.head);
7105
7468
  }
7106
- var SEC_SPIN_CLASS = "zcodepro-ws-sec-spin";
7107
- var anyActiveCache = { at: 0, value: null, busy: false };
7108
- var sectionActive = { task: false, project: false };
7109
- var activeProjectPaths = /* @__PURE__ */ new Set();
7110
- setInterval(() => {
7111
- if (!anyActiveCache.busy && Date.now() - anyActiveCache.at >= 1e4) globalAnyActive();
7112
- }, 1e4);
7113
- function globalAnyActive() {
7114
- if (anyActiveCache.value !== null && Date.now() - anyActiveCache.at < 1e4) return anyActiveCache.value;
7115
- if (anyActiveCache.busy) return anyActiveCache.value === true;
7116
- anyActiveCache.busy = true;
7117
- sectionActive.task = false;
7118
- sectionActive.project = false;
7119
- activeProjectPaths = /* @__PURE__ */ new Set();
7120
- void (async () => {
7121
- let hit = null;
7122
- try {
7123
- const res = await rpc("/projects-v2").catch(() => null);
7124
- const services = findServices();
7125
- const ts = services && services.zcodeTaskService;
7126
- if (res && res.ok && res.available && ts && typeof ts.listActiveTaskIds === "function") {
7127
- hit = false;
7128
- const projPaths = /* @__PURE__ */ new Set();
7129
- for (const proj of res.projects || []) {
7130
- for (const r of proj.roots || []) {
7131
- const b = String(r.path || "").replace(/[\\/]+$/, "");
7132
- if (b) projPaths.add(b);
7133
- }
7134
- }
7135
- const taskSidePaths = /* @__PURE__ */ new Set();
7136
- for (const el of document.querySelectorAll("[data-task-item-key]")) {
7137
- const sec = el.closest("section[data-project-id]");
7138
- if (sec) continue;
7139
- const k = el.getAttribute("data-task-item-key") || "";
7140
- const cut = k.lastIndexOf(":");
7141
- if (cut > 1) taskSidePaths.add(k.slice(0, cut).replace(/[\\/]+$/, ""));
7142
- }
7143
- const scanPaths = /* @__PURE__ */ new Set([...projPaths, ...taskSidePaths]);
7144
- const byKind = { task: /* @__PURE__ */ new Set(), project: /* @__PURE__ */ new Set() };
7145
- const byKindByPath = /* @__PURE__ */ new Map();
7146
- const rootToProject = /* @__PURE__ */ new Map();
7147
- const projectPaths = /* @__PURE__ */ new Map();
7148
- for (const proj of res.projects || []) {
7149
- const names = [];
7150
- for (const r of proj.roots || []) {
7151
- const b = String(r.path || "").replace(/[\\/]+$/, "");
7152
- if (b) {
7153
- rootToProject.set(b, proj.name);
7154
- names.push(b);
7155
- }
7156
- }
7157
- projectPaths.set(proj.name, names);
7158
- }
7159
- for (const base of scanPaths) {
7160
- const a = await ts.listActiveTaskIds({ workspacePath: base });
7161
- const b = await ts.listActiveTaskIds({ workspacePath: base + "/" });
7162
- const ids = [...Array.isArray(a) ? a : [], ...Array.isArray(b) ? b : []].map(String);
7163
- if (!ids.length) continue;
7164
- byKindByPath.set(base, ids);
7165
- const kind = projPaths.has(base) ? "project" : "task";
7166
- for (const id of ids) byKind[kind].add(id);
7167
- }
7168
- const allIds = [...byKind.task, ...byKind.project];
7169
- const st = allIds.length ? await rpc("/task-statuses?ids=" + encodeURIComponent(allIds.join(","))).catch(() => null) : null;
7170
- const TERMINAL = /* @__PURE__ */ new Set(["completed", "error", "interrupted"]);
7171
- const live = (id) => {
7172
- const rec = st && st.ok && st.available ? st.statuses[id] : void 0;
7173
- if (rec === void 0) return true;
7174
- if (!TERMINAL.has(rec.status)) return true;
7175
- return Date.now() - rec.updatedAt < 3e5;
7176
- };
7177
- activeProjectPaths = /* @__PURE__ */ new Set();
7178
- for (const [base, ids] of byKindByPath) {
7179
- if (!projPaths.has(base)) continue;
7180
- if (ids.some(live)) {
7181
- const projName = rootToProject.get(base);
7182
- if (projName) for (const p2 of projectPaths.get(projName) || []) activeProjectPaths.add(p2);
7183
- }
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;
7479
+ }
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);
7184
7493
  }
7185
- const taskDomRunning = !!document.querySelector('[data-task-item-key]:not(section[data-project-id] *) svg[class*="lucide-loader"]');
7186
- if (taskDomRunning && [...byKind.task].some(live)) sectionActive.task = true;
7187
- if ([...byKind.project].some(live)) sectionActive.project = true;
7188
- hit = sectionActive.task || sectionActive.project;
7189
7494
  }
7190
- } catch {
7191
7495
  }
7192
- if (hit !== null) anyActiveCache = { at: Date.now(), value: hit, busy: false };
7193
- else anyActiveCache.busy = false;
7194
- apply();
7195
- })();
7196
- return anyActiveCache.value === true;
7496
+ }
7497
+ if (pendingKeys.has(key)) return;
7498
+ pendingKeys.add(key);
7499
+ setTimeout(() => {
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;
7511
+ }
7512
+ if (wrote) setTimeout(() => {
7513
+ void enforceKey(key);
7514
+ }, REVERIFY_MS);
7515
+ })();
7516
+ }, RECHECK_DELAY_MS);
7197
7517
  }
7198
- function applySectionSpin() {
7199
- if (!enabled3) {
7200
- document.querySelectorAll("." + SEC_SPIN_CLASS).forEach((el) => el.remove());
7201
- return;
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);
7202
7526
  }
7203
- for (const { head, cont, kind } of sectionHeads()) {
7204
- const running = !!(cont && cont.querySelector('li[data-task-item-key] svg[class*="lucide-loader"]'));
7205
- let expanded = true;
7206
- if (head.tagName === "BUTTON") expanded = head.getAttribute("aria-expanded") !== "false";
7207
- else {
7208
- const list = head.nextElementSibling;
7209
- expanded = !list || list.offsetHeight > 0;
7527
+ }
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);
7534
+ continue;
7210
7535
  }
7211
- if (!kind) return;
7212
- const scoped = kind === "project" ? sectionActive.project : sectionActive.task;
7213
- const want = !expanded && scoped;
7214
- const existing = head.querySelector(":scope > ." + SEC_SPIN_CLASS);
7215
- if (want && !existing) {
7216
- const span = document.createElement("span");
7217
- span.className = "flex size-4 shrink-0 items-center justify-center " + SEC_SPIN_CLASS;
7218
- span.append(makeLoaderSvg("size-4"));
7219
- head.prepend(span);
7220
- } else if (!want && existing) {
7221
- existing.remove();
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
+ }
7222
7554
  }
7223
7555
  }
7556
+ return false;
7224
7557
  }
7225
- function schedulePeek(ws, delay) {
7226
- clearTimeout(peekTimers.get(ws));
7227
- peekTimers.set(ws, setTimeout(() => {
7228
- peekTimers.delete(ws);
7229
- void peek(ws);
7230
- }, delay));
7558
+ var scanDebounce3 = 0;
7559
+ function scheduleScan2() {
7560
+ if (scanDebounce3) return;
7561
+ scanDebounce3 = setTimeout(() => {
7562
+ scanDebounce3 = 0;
7563
+ scan3();
7564
+ }, SCAN_DEBOUNCE_MS3);
7231
7565
  }
7232
- function clearPeek(ws) {
7233
- const t2 = peekTimers.get(ws);
7234
- if (t2) {
7235
- clearTimeout(t2);
7236
- peekTimers.delete(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;
7573
+ }
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);
7237
7598
  }
7238
7599
  }
7239
- function syncPeeks() {
7240
- for (const [ws, set] of runningByWs) {
7241
- if (!enabled3 || !set.size) {
7242
- clearPeek(ws);
7243
- continue;
7244
- }
7245
- const row = findWsRow(ws);
7246
- const head = row && wsHead(row);
7247
- if (!row || !head || head.getAttribute("aria-expanded") !== "false" || wsWithRows.has(ws)) {
7248
- clearPeek(ws);
7249
- continue;
7600
+ function refreshPinnedTitles() {
7601
+ return fetchPinned().then((m) => {
7602
+ if (m) scan3();
7603
+ });
7604
+ }
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;
7613
+ }
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;
7633
+ }
7634
+ }
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();
7250
7655
  }
7251
- if (!peekTimers.has(ws) && !activePeeks.has(ws)) schedulePeek(ws, peekedWs.has(ws) ? PEEK_REPEAT_MS : PEEK_FIRST_MS);
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
+ });
7680
+ });
7681
+ }
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) : "";
7699
+ }
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") || "";
7252
7703
  }
7704
+ return "";
7253
7705
  }
7254
- var peekChain = Promise.resolve();
7255
- async function peek(ws, opts) {
7256
- if (!opts?.manual) {
7257
- const auth = await authoritativeIdle(ws);
7258
- if (auth !== null) {
7259
- if (auth) {
7260
- runningByWs.delete(ws);
7261
- } else {
7262
- let set = runningByWs.get(ws);
7263
- if (!set) {
7264
- set = /* @__PURE__ */ new Set();
7265
- runningByWs.set(ws, set);
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);
7711
+ }
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);
7719
+ }
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;
7725
+ }
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;
7266
7753
  }
7267
- set.add(ws + ":active");
7268
- schedulePeek(ws, PEEK_REPEAT_MS);
7269
7754
  }
7270
- apply();
7271
- return true;
7272
7755
  }
7273
- }
7274
- const run = () => peekInner(ws, opts);
7275
- const p = peekChain.then(run, run);
7276
- peekChain = p.catch(() => {
7277
7756
  });
7278
- return p;
7279
- }
7280
- async function peekInner(ws, opts) {
7281
- if (!opts?.manual && !enabled3 || document.hidden || activePeeks.has(ws)) return false;
7282
- const row = findWsRow(ws);
7283
- const head = row && wsHead(row);
7284
- if (!row || !head || head.getAttribute("aria-expanded") !== "false") return false;
7285
- if (!currentTaskRow()) {
7286
- schedulePeek(ws, PEEK_REPEAT_MS);
7287
- return false;
7288
- }
7289
- activePeeks.add(ws);
7290
- peekedWs.add(ws);
7291
- const rect = row.getBoundingClientRect();
7292
- const frozen = row.cloneNode(true);
7293
- frozen.classList.add("zcodepro-ws-frozen");
7294
- frozen.style.left = rect.left + "px";
7295
- frozen.style.top = rect.top + "px";
7296
- frozen.style.width = rect.width + "px";
7297
- frozen.style.height = rect.height + "px";
7298
- document.body.append(frozen);
7299
- row.style.visibility = "hidden";
7300
- const hider = installPeekHider(ws);
7301
- let readOk = false;
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();
7302
7761
  try {
7303
- toggleNavSafe(head);
7304
- readOk = await settleAndRead(ws);
7305
- const rowNow = findWsRow(ws) || row;
7306
- const headNow = wsHead(rowNow);
7307
- if (headNow && headNow.getAttribute("aria-expanded") === "true" && !userTouched(rowNow)) {
7308
- toggleNavSafe(headNow);
7309
- await waitForRowsGone(ws);
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;
7310
7775
  }
7311
7776
  } finally {
7312
- removePeekHider(hider);
7313
- row.style.visibility = "";
7314
- frozen.remove();
7315
- activePeeks.delete(ws);
7777
+ convObserver.disconnect();
7316
7778
  }
7317
- apply();
7318
- const set = runningByWs.get(ws);
7319
- if (set && set.size && enabled3) schedulePeek(ws, PEEK_REPEAT_MS);
7320
- return readOk;
7321
- }
7322
- function wsRunningSnapshot() {
7323
- const curKey = currentTaskRow()?.getAttribute("data-task-item-key") || "";
7324
- return {
7325
- running: [...runningByWs.entries()].filter(([, set]) => set.size > 0).map(([ws]) => ws),
7326
- current: curKey ? workspaceOf2(curKey) || null : null
7327
- };
7328
- }
7329
- async function verifyWorkspaceNotRunning(ws) {
7330
- if (!ws || document.hidden) return false;
7331
- const row = findWsRow(ws);
7332
- const head = row && wsHead(row);
7333
- if (!row || !head) return false;
7334
- if (head.getAttribute("aria-expanded") !== "false") {
7335
- let run = false;
7336
- for (const el of domRowsOf(ws)) if (isRunningRow(el)) run = true;
7337
- return !run;
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
+ }
7338
7791
  }
7339
- let verified = false;
7340
- try {
7341
- verified = await peek(ws, { manual: true }) === true;
7342
- } catch {
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();
7343
7802
  }
7344
- if (!verified) return false;
7345
- const set = runningByWs.get(ws);
7346
- return !(set && set.size);
7347
- }
7348
- function settleAndRead(ws) {
7349
- return new Promise((resolve) => {
7350
- const t0 = Date.now();
7351
- let lastCount = -1;
7352
- let stableAt = 0;
7353
- let zeroRows = false;
7354
- const timer = setInterval(() => {
7355
- const rows = domRowsOf(ws);
7356
- const elapsed = Date.now() - t0;
7357
- if (rows.length !== lastCount) {
7358
- lastCount = rows.length;
7359
- stableAt = Date.now();
7360
- }
7361
- const settled = rows.length > 0 && rows.length === lastCount && Date.now() - stableAt >= PEEK_SETTLE_MS;
7362
- if (!settled && elapsed < PEEK_MAX_MS) return;
7363
- clearInterval(timer);
7364
- if (lastCount === 0) {
7365
- zeroRows = true;
7366
- } else {
7367
- let set = runningByWs.get(ws);
7368
- if (!set) {
7369
- set = /* @__PURE__ */ new Set();
7370
- runningByWs.set(ws, set);
7371
- }
7372
- for (const el of rows) {
7373
- const key = el.getAttribute("data-task-item-key") || "";
7374
- if (isRunningRow(el)) set.add(key);
7375
- else set.delete(key);
7376
- }
7377
- }
7378
- resolve(!zeroRows);
7379
- }, 80);
7380
- });
7381
- }
7382
- var PEEK_EXEMPT_CLASS = "zcodepro-peek-exempt";
7383
- var cssAttr = (v) => String(v).replace(/\\/g, "\\\\").replace(/"/g, '\\"');
7384
- function installPeekHider(ws) {
7385
- const rowSel3 = `${TASK_SEL3}[data-task-item-key^="${cssAttr(ws + ":")}"]`;
7386
- for (const r of document.querySelectorAll(rowSel3)) r.classList.add(PEEK_EXEMPT_CLASS);
7387
- const style = document.createElement("style");
7388
- style.textContent = `${rowSel3}:not(.${PEEK_EXEMPT_CLASS}){display:none!important}`;
7389
- document.head.append(style);
7390
- return { style, sel: rowSel3 };
7391
- }
7392
- function removePeekHider(hider) {
7393
- hider.style.remove();
7394
- for (const r of document.querySelectorAll("." + PEEK_EXEMPT_CLASS)) r.classList.remove(PEEK_EXEMPT_CLASS);
7395
- }
7396
- function waitForRowsGone(ws) {
7397
- const sel = `${TASK_SEL3}[data-task-item-key^="${cssAttr(ws + ":")}"]:not(.${PEEK_EXEMPT_CLASS})`;
7398
- return new Promise((resolve) => {
7399
- const t0 = Date.now();
7400
- const timer = setInterval(() => {
7401
- if (!document.querySelector(sel) || Date.now() - t0 >= PEEK_UNMOUNT_WAIT_MS) {
7402
- clearInterval(timer);
7403
- resolve();
7404
- }
7405
- }, 40);
7406
- });
7407
- }
7408
- var TOUCH_WINDOW_MS = 800;
7409
- var wsTouchAt = /* @__PURE__ */ new WeakMap();
7410
- function userTouched(row) {
7411
- const at = wsTouchAt.get(row);
7412
- return !!at && Date.now() - at < TOUCH_WINDOW_MS;
7413
- }
7414
- function inSel(node, sel) {
7415
- return !!(node instanceof Element && node.closest && node.closest(sel));
7416
7803
  }
7417
- function relevant(muts) {
7418
- for (const m of muts) {
7419
- if (inSel(m.target, TASK_SEL3) || inSel(m.target, WS_SEL())) return true;
7420
- for (const n of m.addedNodes) {
7421
- if (n instanceof Element && (n.matches(TASK_SEL3) || n.matches(WS_SEL()) || n.querySelector(TASK_SEL3) || n.querySelector(WS_SEL()))) return true;
7422
- }
7423
- for (const n of m.removedNodes) {
7424
- if (n instanceof Element && (n.matches(TASK_SEL3) || n.matches(WS_SEL()) || n.querySelector(TASK_SEL3))) return true;
7425
- }
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);
7426
7810
  }
7427
- return false;
7428
7811
  }
7429
- var pollMs = CONFIG_POLL_MIN_MS;
7430
- async function refreshConfig() {
7431
- try {
7432
- const res = await rpc("/config");
7433
- if (res && res.ok && res.config && res.config.features) {
7434
- const next = res.config.features.wsRunningSpin !== false;
7435
- if (next !== enabled3) {
7436
- enabled3 = next;
7437
- if (!next) {
7438
- for (const ws of [...peekTimers.keys()]) clearPeek(ws);
7439
- apply();
7440
- }
7441
- return true;
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;
7442
7825
  }
7443
7826
  }
7444
- } catch {
7445
7827
  }
7446
7828
  return false;
7447
7829
  }
7448
- function pollConfig() {
7449
- setTimeout(async () => {
7450
- if (document.hidden) pollMs = CONFIG_POLL_MAX_MS;
7451
- else pollMs = await refreshConfig() ? CONFIG_POLL_MIN_MS : Math.min(Math.round(pollMs * 1.5), CONFIG_POLL_MAX_MS);
7452
- pollConfig();
7453
- }, pollMs);
7454
- }
7455
7830
  var scanDebounce4 = 0;
7456
- function startWsRunningSpin() {
7831
+ function scheduleScan3() {
7832
+ if (scanDebounce4) return;
7833
+ scanDebounce4 = setTimeout(() => {
7834
+ scanDebounce4 = 0;
7835
+ scanForNewRows();
7836
+ }, SCAN_DEBOUNCE_MS4);
7837
+ }
7838
+ function startTitleGen() {
7457
7839
  if (installed4 || typeof document === "undefined") return;
7458
7840
  installed4 = true;
7459
- void refreshConfig();
7460
- pollConfig();
7461
- const observer2 = new MutationObserver((muts) => {
7462
- if (!relevant(muts)) return;
7463
- handleRemovals(muts);
7464
- if (!scanDebounce4) {
7465
- scanDebounce4 = setTimeout(() => {
7466
- scanDebounce4 = 0;
7467
- scan3();
7468
- }, SCAN_DEBOUNCE_MS4);
7469
- }
7470
- });
7471
- observer2.observe(document.body, { childList: true, subtree: true });
7472
- scan3();
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 });
7473
7845
  setInterval(() => {
7474
- if (!document.hidden && enabled3) scan3();
7846
+ if (!document.hidden) scanForNewRows();
7475
7847
  }, SCAN_FALLBACK_MS4);
7476
7848
  document.addEventListener("visibilitychange", () => {
7477
- if (!document.hidden) scan3();
7849
+ if (!document.hidden) scanForNewRows();
7478
7850
  });
7479
- document.addEventListener("click", (e) => {
7480
- if (!e.isTrusted) return;
7481
- const row = e.target instanceof Element && e.target.closest(WS_SEL());
7482
- if (row) wsTouchAt.set(row, Date.now());
7483
- }, true);
7484
7851
  }
7485
7852
 
7486
7853
  // src/inject/features/idle-reclaim.js