dsh-group-status 0.2.2 → 0.4.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.
Files changed (110) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/README.en.md +42 -24
  3. package/README.md +45 -25
  4. package/lib/client.js +1781 -483
  5. package/lib/client.js.map +1 -1
  6. package/lib/index.js +22 -9
  7. package/lib/types/client/BadgeSettingsCard.d.ts +24 -0
  8. package/lib/types/client/BadgeSettingsCard.d.ts.map +1 -0
  9. package/lib/types/client/BadgeSettingsCard.js +73 -0
  10. package/lib/types/client/BadgeSettingsCard.js.map +1 -0
  11. package/lib/types/client/badge-field.d.ts +44 -0
  12. package/lib/types/client/badge-field.d.ts.map +1 -0
  13. package/lib/types/client/badge-field.js +77 -0
  14. package/lib/types/client/badge-field.js.map +1 -0
  15. package/lib/types/client/badge-labels.d.ts +92 -0
  16. package/lib/types/client/badge-labels.d.ts.map +1 -0
  17. package/lib/types/client/badge-labels.js +150 -0
  18. package/lib/types/client/badge-labels.js.map +1 -0
  19. package/lib/types/client/badge-settings.d.ts +31 -3
  20. package/lib/types/client/badge-settings.d.ts.map +1 -1
  21. package/lib/types/client/badge-settings.js +50 -6
  22. package/lib/types/client/badge-settings.js.map +1 -1
  23. package/lib/types/client/contract/slots.d.ts +39 -21
  24. package/lib/types/client/contract/slots.d.ts.map +1 -1
  25. package/lib/types/client/contract/slots.js.map +1 -1
  26. package/lib/types/client/contract/toast.d.ts +67 -0
  27. package/lib/types/client/contract/toast.d.ts.map +1 -0
  28. package/lib/types/client/contract/toast.js +2 -0
  29. package/lib/types/client/contract/toast.js.map +1 -0
  30. package/lib/types/client/facet-store.d.ts +46 -0
  31. package/lib/types/client/facet-store.d.ts.map +1 -0
  32. package/lib/types/client/facet-store.js +123 -0
  33. package/lib/types/client/facet-store.js.map +1 -0
  34. package/lib/types/client/facet-visibility.d.ts +17 -0
  35. package/lib/types/client/facet-visibility.d.ts.map +1 -0
  36. package/lib/types/client/facet-visibility.js +18 -0
  37. package/lib/types/client/facet-visibility.js.map +1 -0
  38. package/lib/types/client/facets.d.ts +265 -0
  39. package/lib/types/client/facets.d.ts.map +1 -0
  40. package/lib/types/client/facets.js +209 -0
  41. package/lib/types/client/facets.js.map +1 -0
  42. package/lib/types/client/group-status.d.ts +97 -45
  43. package/lib/types/client/group-status.d.ts.map +1 -1
  44. package/lib/types/client/group-status.js +148 -85
  45. package/lib/types/client/group-status.js.map +1 -1
  46. package/lib/types/client/index.d.ts +31 -3
  47. package/lib/types/client/index.d.ts.map +1 -1
  48. package/lib/types/client/index.js +107 -14
  49. package/lib/types/client/index.js.map +1 -1
  50. package/lib/types/client/locales.d.ts +60 -4
  51. package/lib/types/client/locales.d.ts.map +1 -1
  52. package/lib/types/client/locales.js +60 -4
  53. package/lib/types/client/locales.js.map +1 -1
  54. package/lib/types/client/navigation.d.ts +45 -12
  55. package/lib/types/client/navigation.d.ts.map +1 -1
  56. package/lib/types/client/navigation.js +102 -26
  57. package/lib/types/client/navigation.js.map +1 -1
  58. package/lib/types/client/rows/GroupBadge.d.ts +19 -25
  59. package/lib/types/client/rows/GroupBadge.d.ts.map +1 -1
  60. package/lib/types/client/rows/GroupBadge.js +45 -106
  61. package/lib/types/client/rows/GroupBadge.js.map +1 -1
  62. package/lib/types/client/rows/Rows.d.ts +7 -3
  63. package/lib/types/client/rows/Rows.d.ts.map +1 -1
  64. package/lib/types/client/rows/Rows.js +9 -9
  65. package/lib/types/client/rows/Rows.js.map +1 -1
  66. package/lib/types/client/rows/WorkspaceBrowser.d.ts +1 -1
  67. package/lib/types/client/rows/WorkspaceBrowser.d.ts.map +1 -1
  68. package/lib/types/client/rows/WorkspaceBrowser.js +75 -71
  69. package/lib/types/client/rows/WorkspaceBrowser.js.map +1 -1
  70. package/lib/types/client/session-actions/ArchiveSession.js +1 -1
  71. package/lib/types/client/session-actions/ArchiveSession.js.map +1 -1
  72. package/lib/types/client/session-actions/ForkSession.d.ts +5 -4
  73. package/lib/types/client/session-actions/ForkSession.d.ts.map +1 -1
  74. package/lib/types/client/session-actions/ForkSession.js +7 -4
  75. package/lib/types/client/session-actions/ForkSession.js.map +1 -1
  76. package/lib/types/client/session-actions/PinSession.js +1 -1
  77. package/lib/types/client/session-actions/PinSession.js.map +1 -1
  78. package/lib/types/client/session-actions/RowActionToast.d.ts +5 -8
  79. package/lib/types/client/session-actions/RowActionToast.d.ts.map +1 -1
  80. package/lib/types/client/session-actions/RowActionToast.js +10 -49
  81. package/lib/types/client/session-actions/RowActionToast.js.map +1 -1
  82. package/lib/types/client/session-actions/SessionId.d.ts +8 -0
  83. package/lib/types/client/session-actions/SessionId.d.ts.map +1 -0
  84. package/lib/types/client/session-actions/SessionId.js +16 -0
  85. package/lib/types/client/session-actions/SessionId.js.map +1 -0
  86. package/lib/types/client/session-watermarks.d.ts +30 -0
  87. package/lib/types/client/session-watermarks.d.ts.map +1 -0
  88. package/lib/types/client/session-watermarks.js +54 -0
  89. package/lib/types/client/session-watermarks.js.map +1 -0
  90. package/lib/types/client/shortcuts.d.ts +2 -7
  91. package/lib/types/client/shortcuts.d.ts.map +1 -1
  92. package/lib/types/client/shortcuts.js +7 -11
  93. package/lib/types/client/shortcuts.js.map +1 -1
  94. package/lib/types/client/toast/Toast.d.ts +41 -0
  95. package/lib/types/client/toast/Toast.d.ts.map +1 -0
  96. package/lib/types/client/toast/Toast.js +44 -0
  97. package/lib/types/client/toast/Toast.js.map +1 -0
  98. package/lib/types/client/toast/register.d.ts +11 -0
  99. package/lib/types/client/toast/register.d.ts.map +1 -0
  100. package/lib/types/client/toast/register.js +162 -0
  101. package/lib/types/client/toast/register.js.map +1 -0
  102. package/lib/types/client/tree.d.ts +2 -5
  103. package/lib/types/client/tree.d.ts.map +1 -1
  104. package/lib/types/client/tree.js +0 -5
  105. package/lib/types/client/tree.js.map +1 -1
  106. package/lib/types/index.d.ts +31 -1
  107. package/lib/types/index.d.ts.map +1 -1
  108. package/lib/types/index.js +13 -1
  109. package/lib/types/index.js.map +1 -1
  110. package/package.json +33 -34
package/lib/client.js CHANGED
@@ -5,8 +5,8 @@ window.__ModuleLoader__.load({
5
5
  var exports = module.exports;
6
6
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
7
  let _deepseek_ai_dsh_client_store = require("@deepseek-ai/dsh-client-store");
8
- let react = require("react");
9
8
  let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
9
+ let react = require("react");
10
10
  let react_jsx_runtime = require("react/jsx-runtime");
11
11
  let _deepseek_ai_cordis = require("@deepseek-ai/cordis");
12
12
  //#region src/client/contract/slots.ts
@@ -30,10 +30,8 @@ window.__ModuleLoader__.load({
30
30
  searchRequest: 0,
31
31
  addRequested: false,
32
32
  directoryBusy: false,
33
- renameTarget: null,
34
- forkError: null
33
+ renameTarget: null
35
34
  });
36
- let forkErrorSeq = 0;
37
35
  return {
38
36
  state,
39
37
  search: () => {
@@ -74,22 +72,6 @@ window.__ModuleLoader__.load({
74
72
  ...state.getSnapshot(),
75
73
  renameTarget: null
76
74
  });
77
- },
78
- forkFailed: (reason) => {
79
- forkErrorSeq += 1;
80
- state.set({
81
- ...state.getSnapshot(),
82
- forkError: {
83
- reason,
84
- seq: forkErrorSeq
85
- }
86
- });
87
- },
88
- dismissForkError: () => {
89
- state.set({
90
- ...state.getSnapshot(),
91
- forkError: null
92
- });
93
75
  }
94
76
  };
95
77
  }
@@ -100,8 +82,9 @@ window.__ModuleLoader__.load({
100
82
  * @param navigation - session creation and forking from the pointer controls' navigation service.
101
83
  * @param controls - browser-owned opening requests.
102
84
  * @param archiveSession - shared archive action, including running-work confirmation and notices.
85
+ * @param reportForkFailure - publish a classified failure through the frame-wide notice channel.
103
86
  */
104
- function installWorkspaceShortcuts(ctx, navigation, controls, archiveSession) {
87
+ function installWorkspaceShortcuts(ctx, navigation, controls, archiveSession, reportForkFailure) {
105
88
  const t = ctx.locale.bind("workspace");
106
89
  const current = () => Object.values(ctx.sessions.list.getSnapshot().byId).find((row) => (row.retainedBy.mainView ?? 0) > 0);
107
90
  const addReason = () => ctx.slots.entries("sidebar.workspaces.directoryFlow").length === 0 ? t("shortcut.noPicker") : controls.state.getSnapshot().directoryBusy ? t("shortcut.directoryBusy") : null;
@@ -183,8 +166,8 @@ window.__ModuleLoader__.load({
183
166
  status: "handled",
184
167
  run: () => {
185
168
  navigation.forkSession(target.id).catch((error) => {
186
- const unavailable = error instanceof Error && error.name === "SessionForkError" && error.rpcError.code === "session/fork-unavailable";
187
- controls.forkFailed(unavailable ? "unavailable" : "failed");
169
+ const unavailable = (error instanceof Error && error.name === "SessionForkError" ? error : void 0)?.rpcError.code === "session/fork-unavailable";
170
+ reportForkFailure(unavailable ? "unavailable" : "failed");
188
171
  if (!unavailable) console.warn("session fork rejected:", error);
189
172
  });
190
173
  }
@@ -228,6 +211,17 @@ window.__ModuleLoader__.load({
228
211
  */
229
212
  /** Loader 条目 id —— 设置页的寻址键,也是宿主 `describe()` 的 ns。 */
230
213
  const ENTRY_ID = "dsh-group-status";
214
+ /**
215
+ * 一个 facet 是否被设置关掉。
216
+ *
217
+ * - 没有 `settingKey` → 始终参与(缺省是"显示",与其余开关的默认值同源)。
218
+ * - 声明了但不是布尔开关键 → 同样按显示处理:拼错的键不该让一个状态整块消失。
219
+ */
220
+ function facetVisibleBySettings(settings, settingKey) {
221
+ if (settingKey === void 0) return true;
222
+ const value = settings[settingKey];
223
+ return typeof value === "boolean" ? value : true;
224
+ }
231
225
  /** 默认值:与 `Config` 各字段的 schema 默认同源(全部显示,数字阈值 2 即 count=1 时省略)。 */
232
226
  const DEFAULT_BADGE_SETTINGS = Object.freeze({
233
227
  showCompletedInBadge: true,
@@ -298,6 +292,288 @@ window.__ModuleLoader__.load({
298
292
  };
299
293
  }
300
294
  //#endregion
295
+ //#region src/client/facets.ts
296
+ /** 本协议文件的协议版本。provider 必须原样填这个常量。 */
297
+ const GROUP_STATUS_FACET_PROTOCOL_VERSION = 1;
298
+ /** tone 之间的优先级(越大越紧急),用于多层合并时取更高档。 */
299
+ const FACET_TONE_RANK = Object.freeze({
300
+ info: 0,
301
+ warn: 1,
302
+ error: 2
303
+ });
304
+ /**
305
+ * 同 id 冲突时**由来源决胜,与注册顺序无关**。
306
+ *
307
+ * 宿主不保证插件 apply 的先后。过渡期内"本插件代 provider 写的适配器"与"provider
308
+ * 自己注册的 facet"会撞同一个 id:若靠顺序决胜,同一份部署今天显示、明天不显示。
309
+ * `provider` 恒胜 `legacy`:所有者一接入,代管方立刻让位,且不重复计数。迁移因此
310
+ * 不需要协商发布顺序。
311
+ */
312
+ const FACET_ORIGIN_RANK = Object.freeze({
313
+ legacy: 0,
314
+ provider: 1
315
+ });
316
+ /** 一个 facet 的来源;缺省按 `provider`(最保守的假设:别人主动注册就是所有者)。 */
317
+ function facetOrigin(facet) {
318
+ return facet.origin === "legacy" ? "legacy" : "provider";
319
+ }
320
+ /**
321
+ * 检查一个 facet 是否符合协议 v1。
322
+ * @returns 拒绝原因;`undefined` 表示可用。
323
+ */
324
+ function facetRejection(facet) {
325
+ if (typeof facet !== "object" || facet === null) return "not-an-object";
326
+ const candidate = facet;
327
+ if (candidate.protocol !== 1) return "protocol-mismatch";
328
+ if (typeof candidate.id !== "string" || candidate.id === "") return "missing-id";
329
+ if (typeof candidate.subscribe !== "function" || typeof candidate.getSnapshot !== "function" || typeof candidate.fold !== "function") return "missing-methods";
330
+ }
331
+ /**
332
+ * 缺省折果合并:数量相加、色档取更紧急的一档、脉冲取或。
333
+ *
334
+ * ⚠️ 它**不**合并 `data`(本插件不知道 `data` 是什么)。需要合并原始事实的 facet
335
+ * 必须自己实现 {@link GroupStatusFacet.merge}。
336
+ */
337
+ function defaultMergeFold(a, b) {
338
+ const toneA = FACET_TONE_RANK[a.tone] ?? -1;
339
+ const toneB = FACET_TONE_RANK[b.tone] ?? -1;
340
+ return {
341
+ count: a.count + b.count,
342
+ tone: toneB > toneA ? b.tone : a.tone,
343
+ pulse: a.pulse || b.pulse
344
+ };
345
+ }
346
+ /** 创建一张登记表面(测试用;生产走模块级单例 {@link registry})。 */
347
+ function createFacetRegistry() {
348
+ const entries = /* @__PURE__ */ new Map();
349
+ const listeners = /* @__PURE__ */ new Set();
350
+ let sequence = 0;
351
+ let sorted = [];
352
+ const resort = () => {
353
+ sorted = [...entries.values()].sort((x, y) => {
354
+ const declaredX = x.facet.order !== void 0;
355
+ const declaredY = y.facet.order !== void 0;
356
+ if (declaredX !== declaredY) return declaredX ? -1 : 1;
357
+ if (declaredX && declaredY) return x.facet.order - y.facet.order;
358
+ return x.sequence - y.sequence;
359
+ });
360
+ };
361
+ let notifying = false;
362
+ let pending = false;
363
+ /**
364
+ * 通知订阅者,并把**重入合并成一轮补跑**。
365
+ *
366
+ * 订阅者在自己的回调里登记/注销 facet 是完全合法的(消费方常在收到变更后立即
367
+ * 补登记一个派生来源)。直接递归会有两个问题:嵌套深度由订阅者数量决定(两个互相
368
+ * 触发的订阅者就是无界递归),而且中间那轮通知看到的登记面是**改到一半**的。
369
+ * 所以内层调用只置脏标志,外层跑完一轮后再补一轮,直到没有新的改动 —— 每个订阅者
370
+ * 都看得到最终状态,递归深度恒为 1。
371
+ */
372
+ const notify = () => {
373
+ if (notifying) {
374
+ pending = true;
375
+ return;
376
+ }
377
+ notifying = true;
378
+ try {
379
+ do {
380
+ pending = false;
381
+ for (const listener of [...listeners]) try {
382
+ listener();
383
+ } catch {}
384
+ } while (pending);
385
+ } finally {
386
+ notifying = false;
387
+ pending = false;
388
+ }
389
+ };
390
+ return {
391
+ register: (facet) => {
392
+ const reason = facetRejection(facet);
393
+ const id = typeof facet?.id === "string" ? facet.id : "";
394
+ if (reason !== void 0) return {
395
+ ok: false,
396
+ id,
397
+ reason
398
+ };
399
+ const existing = entries.get(id);
400
+ const nextRank = FACET_ORIGIN_RANK[facetOrigin(facet)];
401
+ if (existing !== void 0 && FACET_ORIGIN_RANK[facetOrigin(existing.facet)] > nextRank) return {
402
+ ok: false,
403
+ id,
404
+ reason: "superseded"
405
+ };
406
+ sequence += 1;
407
+ entries.set(id, {
408
+ facet,
409
+ sequence
410
+ });
411
+ resort();
412
+ notify();
413
+ return {
414
+ ok: true,
415
+ id
416
+ };
417
+ },
418
+ remove: (id, facet) => {
419
+ const current = entries.get(id);
420
+ if (current === void 0) return false;
421
+ if (facet !== void 0 && current.facet !== facet) return false;
422
+ entries.delete(id);
423
+ resort();
424
+ notify();
425
+ return true;
426
+ },
427
+ list: () => sorted,
428
+ subscribe: (listener) => {
429
+ listeners.add(listener);
430
+ return () => {
431
+ listeners.delete(listener);
432
+ };
433
+ }
434
+ };
435
+ }
436
+ /**
437
+ * 生产用的模块级单例登记表面。
438
+ *
439
+ * 与 {@link createFacetRegistry} 分开,是为了让测试能对着一张干净的表断言协议行为,
440
+ * 而不会被别的用例或别的插件登记的 facet 污染。
441
+ */
442
+ const registry = createFacetRegistry();
443
+ /**
444
+ * 本插件 client 入口对外的登记函数(provider 侧唯一需要认识的符号)。
445
+ *
446
+ * @param facet - 待登记的 facet。
447
+ * @returns 注销函数;**注册失败时返回一个空操作**,同时把原因写进 console——
448
+ * 返回值形状保持稳定,provider 的 dispose 路径不必区分成败。
449
+ */
450
+ function registerGroupStatusFacet(facet) {
451
+ const result = registry.register(facet);
452
+ if (!result.ok) {
453
+ if (!(result.reason === "superseded" && facetOrigin(facet) === "legacy")) reportRejection(result);
454
+ return () => void 0;
455
+ }
456
+ return () => {
457
+ registry.remove(result.id, facet);
458
+ };
459
+ }
460
+ /**
461
+ * 把注册失败写成一条可诊断的日志。
462
+ *
463
+ * 刻意不抛错:状态显示是增强功能,一个插件协议填错不该把宿主界面渲染打断;
464
+ * 但也不静默——"注册了却没显示"必须能查出原因。
465
+ */
466
+ function reportRejection(rejection) {
467
+ const detail = rejection.reason === "protocol-mismatch" ? `本插件只接受 protocol: 1` : rejection.reason === "missing-methods" ? "subscribe / getSnapshot / fold 三个方法必须齐备" : rejection.reason === "missing-id" ? "id 必须是非空字符串,建议 '<包名>.<状态名>'" : rejection.reason === "superseded" ? "同 id 上已有更高来源的登记;两个不同插件用了同一个 id,请各自换名" : "facet 必须是一个对象";
468
+ console.warn(`dsh-group-status: 拒绝登记状态 facet "${rejection.id}"(${rejection.reason}):${detail}`);
469
+ }
470
+ //#endregion
471
+ //#region src/client/facet-store.ts
472
+ /**
473
+ * facet 的**消费侧**:把「登记表面 + 每个 facet 自己的外部存储」压成一份 React 可订阅的
474
+ * 单一存储。
475
+ *
476
+ * ## 为什么必须是一个存储,而不是逐个 hook
477
+ *
478
+ * 见 `./facets.ts` 的文件头:hook 数量在同一个组件里必须逐次渲染恒定,而 facet 是运行
479
+ * 时注册/注销的。所以这里只暴露一个**版本号**(数字,引用天然稳定)给
480
+ * `useSyncExternalStore`,实际的快照读取与折叠都发生在渲染期。
481
+ *
482
+ * ## 快照读取为什么放在渲染期
483
+ *
484
+ * `getSnapshot` 必须返回稳定引用,而「每个 facet 的快照」是一个对象图:为它造一个
485
+ * 缓存就需要判定失效,而判定失效又要读快照本身。改成「版本号变 → 重新渲染 → 现读现折」
486
+ * 之后,缓存问题消失,代价是一次 O(facet × 组内会话) 的纯计算,量级与原有实现相同。
487
+ *
488
+ * @module dsh-group-status/client/facet-store
489
+ */
490
+ /**
491
+ * 创建 facet 存储。
492
+ *
493
+ * 对登记面做一次订阅,并在登记变化时**重新接线**各 facet 的 `subscribe`:provider 热替换
494
+ * 实现(同 id 再注册)或运行中启用/停用插件,都只会走到这里一次,不会留下野订阅。
495
+ *
496
+ * @param source - 登记表面;缺省用模块级单例,测试传入自建实例以隔离。
497
+ */
498
+ function createFacetStore(source = registry) {
499
+ const listeners = /* @__PURE__ */ new Set();
500
+ const links = /* @__PURE__ */ new Map();
501
+ let version = 0;
502
+ let disposed = false;
503
+ /** 任一来源变化都要让消费方重新渲染,因此统一 bump。 */
504
+ const bump = () => {
505
+ if (disposed) return;
506
+ version += 1;
507
+ for (const listener of [...listeners]) try {
508
+ listener();
509
+ } catch {}
510
+ };
511
+ /** 把 links 对齐到当前登记项:新增接线、移除退订、替换重接。 */
512
+ const rewire = () => {
513
+ if (disposed) return;
514
+ const current = new Map(source.list().map((entry) => [entry.facet.id, entry.facet]));
515
+ for (const [id, link] of [...links]) {
516
+ if (current.get(id) === link.facet) continue;
517
+ try {
518
+ link.unsubscribe();
519
+ } catch {}
520
+ links.delete(id);
521
+ }
522
+ for (const [id, facet] of current) {
523
+ if (links.has(id)) continue;
524
+ links.set(id, {
525
+ facet,
526
+ unsubscribe: facet.subscribe(bump)
527
+ });
528
+ }
529
+ };
530
+ const unsubscribeRegistry = source.subscribe(() => {
531
+ rewire();
532
+ bump();
533
+ });
534
+ rewire();
535
+ return {
536
+ subscribe: (listener) => {
537
+ listeners.add(listener);
538
+ return () => {
539
+ listeners.delete(listener);
540
+ };
541
+ },
542
+ getVersion: () => version,
543
+ getServerVersion: () => 0,
544
+ list: () => source.list(),
545
+ dispose: () => {
546
+ if (disposed) return;
547
+ disposed = true;
548
+ for (const link of links.values()) try {
549
+ link.unsubscribe();
550
+ } catch {}
551
+ links.clear();
552
+ unsubscribeRegistry();
553
+ listeners.clear();
554
+ }
555
+ };
556
+ }
557
+ /**
558
+ * 生产用的单例存储:消费侧(`WorkspaceBrowser`)订阅它,
559
+ * `apply` 的 effect 结束时用 {@link disposeFacetStore} 释放全部接线。
560
+ *
561
+ * 单例而非每次渲染新建,是因为它持有**对每个 facet 存储的订阅**;随渲染重建会把
562
+ * provider 的 store 拖进无谓的退订/重订,也让"快照在两个文件夹行之间读数不同"成为可能。
563
+ */
564
+ let singleton;
565
+ /** 取(并首次创建)单例存储。 */
566
+ function facetStore() {
567
+ if (singleton === void 0) singleton = createFacetStore();
568
+ return singleton;
569
+ }
570
+ /** 释放单例存储(插件卸载时调用;下次 {@link facetStore} 会重建)。 */
571
+ function disposeFacetStore() {
572
+ if (singleton === void 0) return;
573
+ singleton.dispose();
574
+ singleton = void 0;
575
+ }
576
+ //#endregion
301
577
  //#region src/client/badge-field.ts
302
578
  /** 卡片字段名(与 `src/index.ts` 的 `Config` 同名同义)。 */
303
579
  const BADGE_FIELDS = {
@@ -365,8 +641,8 @@ window.__ModuleLoader__.load({
365
641
  }
366
642
  ];
367
643
  //#endregion
368
- //#region \0dsh-css:/home/wujue/workspace/dsh-plugins/dsh-group-status/src/client/BadgeSettingsCard.module.css.mjs
369
- const css$4 = ".EgC0Ja_section{min-width:0;padding:16px 0}.EgC0Ja_section:last-child{padding-bottom:0}.EgC0Ja_heading{color:var(--dsw-alias-label-primary);margin:0;font-size:13px;font-weight:600;line-height:1.5}.EgC0Ja_badgeField{gap:6px;padding:12px 0;display:grid}.EgC0Ja_badgeToggleRow{color:var(--dsw-alias-label-primary);justify-content:space-between;align-items:flex-start;gap:16px;font-size:13px;line-height:1.5;display:flex}.EgC0Ja_badgeToggleLabel{flex:1;min-width:0}.EgC0Ja_badgeNumberControl{align-items:center;gap:4px;display:flex}.EgC0Ja_badgeNumberButton{width:28px;height:28px;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-elevated,#2a2a2a);border:1px solid var(--dsw-alias-border-primary,#555);cursor:pointer;user-select:none;border-radius:6px;justify-content:center;align-items:center;padding:0;font-size:16px;font-weight:600;line-height:1;display:flex}.EgC0Ja_badgeNumberButton:hover:not(:disabled){border-color:var(--dsw-alias-border-focus,#0078d4);background:var(--dsw-alias-bg-hover,#3a3a3a)}.EgC0Ja_badgeNumberButton:active:not(:disabled){background:var(--dsw-alias-bg-active,#4a4a4a)}.EgC0Ja_badgeNumberButton:disabled{opacity:.3;cursor:not-allowed}.EgC0Ja_badgeNumberValue{text-align:center;min-width:24px;color:var(--dsw-alias-label-primary);font-size:13px;font-weight:500}.EgC0Ja_hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}";
644
+ //#region \0dsh-css:src/client/BadgeSettingsCard.module.css.mjs
645
+ const css$4 = ".dhp54G_section{min-width:0;padding:16px 0}.dhp54G_section:last-child{padding-bottom:0}.dhp54G_heading{color:var(--dsw-alias-label-primary);margin:0;font-size:13px;font-weight:600;line-height:1.5}.dhp54G_badgeField{gap:6px;padding:12px 0;display:grid}.dhp54G_badgeToggleRow{color:var(--dsw-alias-label-primary);justify-content:space-between;align-items:flex-start;gap:16px;font-size:13px;line-height:1.5;display:flex}.dhp54G_badgeToggleLabel{flex:1;min-width:0}.dhp54G_badgeNumberControl{align-items:center;gap:4px;display:flex}.dhp54G_badgeNumberButton{width:28px;height:28px;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-elevated,#2a2a2a);border:1px solid var(--dsw-alias-border-primary,#555);cursor:pointer;user-select:none;border-radius:6px;justify-content:center;align-items:center;padding:0;font-size:16px;font-weight:600;line-height:1;display:flex}.dhp54G_badgeNumberButton:hover:not(:disabled){border-color:var(--dsw-alias-border-focus,#0078d4);background:var(--dsw-alias-bg-hover,#3a3a3a)}.dhp54G_badgeNumberButton:active:not(:disabled){background:var(--dsw-alias-bg-active,#4a4a4a)}.dhp54G_badgeNumberButton:disabled{opacity:.3;cursor:not-allowed}.dhp54G_badgeNumberValue{text-align:center;min-width:24px;color:var(--dsw-alias-label-primary);font-size:13px;font-weight:500}.dhp54G_hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}";
370
646
  const tagId$4 = "dsh-group-status/BadgeSettingsCard.module.css";
371
647
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$4) + "]") === null) {
372
648
  const tag = document.createElement("style");
@@ -376,15 +652,15 @@ window.__ModuleLoader__.load({
376
652
  document.head.appendChild(tag);
377
653
  }
378
654
  var BadgeSettingsCard_module_css_default = {
379
- "badgeField": "EgC0Ja_badgeField",
380
- "badgeNumberButton": "EgC0Ja_badgeNumberButton",
381
- "badgeNumberControl": "EgC0Ja_badgeNumberControl",
382
- "badgeNumberValue": "EgC0Ja_badgeNumberValue",
383
- "badgeToggleLabel": "EgC0Ja_badgeToggleLabel",
384
- "badgeToggleRow": "EgC0Ja_badgeToggleRow",
385
- "heading": "EgC0Ja_heading",
386
- "hint": "EgC0Ja_hint",
387
- "section": "EgC0Ja_section"
655
+ "badgeField": "dhp54G_badgeField",
656
+ "badgeNumberButton": "dhp54G_badgeNumberButton",
657
+ "badgeNumberControl": "dhp54G_badgeNumberControl",
658
+ "badgeNumberValue": "dhp54G_badgeNumberValue",
659
+ "badgeToggleLabel": "dhp54G_badgeToggleLabel",
660
+ "badgeToggleRow": "dhp54G_badgeToggleRow",
661
+ "heading": "dhp54G_heading",
662
+ "hint": "dhp54G_hint",
663
+ "section": "dhp54G_section"
388
664
  };
389
665
  //#endregion
390
666
  //#region src/client/BadgeSettingsCard.tsx
@@ -609,7 +885,838 @@ window.__ModuleLoader__.load({
609
885
  });
610
886
  }
611
887
  //#endregion
612
- //#region node_modules/.pnpm/@deepseek-ai+dsh-util-values@0.2.0-rc.2_@deepseek-ai+cordis@4.0.4/node_modules/@deepseek-ai/dsh-util-values/lib/index.js
888
+ //#region src/client/toast/Toast.tsx
889
+ /** Shared Workspace notice presentation and keyed business adapters. */
890
+ /**
891
+ * Render one notice using the shared Factory.
892
+ * @param props - keyed payload, injected display mapping, locale, and Factory renderer.
893
+ * @returns the shared Toast occurrence.
894
+ */
895
+ function ToastEntry(props) {
896
+ const { dismissToast, display, t, renderFactorySlot, seq } = props;
897
+ return renderFactorySlot("workspace.toast.content", {
898
+ ...display({ ...props }, t, dismissToast),
899
+ seq,
900
+ onDone: dismissToast
901
+ });
902
+ }
903
+ /**
904
+ * Keep the archive filter action reactive without subscribing unrelated notices.
905
+ * @param props - archive notice, display mapping, and the existing viewing store.
906
+ * @returns the shared Toast occurrence with its current actions.
907
+ */
908
+ function ArchiveToastEntry(props) {
909
+ const { dismissToast, display, filterAction, useStore, t, renderFactorySlot, seq } = props;
910
+ const archivedVisible = useStore((state) => (state.archivedFilter ?? "default") !== "default");
911
+ const fields = display({ ...props }, t, dismissToast);
912
+ return renderFactorySlot("workspace.toast.content", {
913
+ ...fields,
914
+ actions: [...fields.actions ?? [], ...archivedVisible ? [] : [filterAction(t, dismissToast)]],
915
+ seq,
916
+ onDone: dismissToast
917
+ });
918
+ }
919
+ /**
920
+ * Present plain display data with the shared primitive's timing and portal.
921
+ * @param props - localized display fields, display identity, and dismissal.
922
+ * @returns one Toast, remounted only for a new display identity.
923
+ */
924
+ function WorkspaceToast({ seq, text, tone, holdMs, actions, onDone }) {
925
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Toast, {
926
+ text,
927
+ onDone,
928
+ ...tone === "success" ? { tone: "success" } : {},
929
+ ...tone === "warning" ? { icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconWarningOutlineRegular, {}) } : {},
930
+ ...holdMs === void 0 ? {} : { holdMs },
931
+ ...actions === void 0 ? {} : { actions }
932
+ }, seq);
933
+ }
934
+ //#endregion
935
+ //#region src/client/toast/register.tsx
936
+ /** Wrap a business display mapping without making the slot component generic. */
937
+ function plainToast(display) {
938
+ return () => ({ display });
939
+ }
940
+ /**
941
+ * Register notice presentations with their business-owned display fields and actions.
942
+ * @param ctx - workspace plugin context.
943
+ * @param viewStore - viewing store, read only by archive notices.
944
+ * @param actions - archive and source-opening callbacks.
945
+ */
946
+ function registerToasts(ctx, viewStore, actions) {
947
+ const toastEntry = ToastEntry;
948
+ const archiveToastEntry = ArchiveToastEntry;
949
+ const archiveFilterAction = (t, dismiss) => ({
950
+ prefix: t("toast.archivedOr"),
951
+ label: t("toast.archivedFilter"),
952
+ onClick: () => {
953
+ dismiss();
954
+ actions.showArchived();
955
+ }
956
+ });
957
+ ctx.slots.registerFactory({
958
+ name: "workspace.toast.content",
959
+ scope: "root"
960
+ }, WorkspaceToast);
961
+ ctx.slots.inject("workspace.toast", function* () {
962
+ yield ctx.slots.register({
963
+ name: "workspace.toast",
964
+ key: "sessionIdCopied",
965
+ locale: "workspace",
966
+ inject: plainToast((_notice, t) => ({
967
+ text: t("toast.sessionIdCopied"),
968
+ tone: "success"
969
+ }))
970
+ }, toastEntry);
971
+ yield ctx.slots.register({
972
+ name: "workspace.toast",
973
+ key: "sessionIdCopyFailed",
974
+ locale: "workspace",
975
+ inject: plainToast((_notice, t) => ({
976
+ text: t("toast.sessionIdCopyFailed"),
977
+ tone: "warning"
978
+ }))
979
+ }, toastEntry);
980
+ yield ctx.slots.register({
981
+ name: "workspace.toast",
982
+ key: "pinFailed",
983
+ locale: "workspace",
984
+ inject: plainToast((_notice, t) => ({
985
+ text: t("toast.pinFailed"),
986
+ tone: "warning"
987
+ }))
988
+ }, toastEntry);
989
+ yield ctx.slots.register({
990
+ name: "workspace.toast",
991
+ key: "unpinFailed",
992
+ locale: "workspace",
993
+ inject: plainToast((_notice, t) => ({
994
+ text: t("toast.unpinFailed"),
995
+ tone: "warning"
996
+ }))
997
+ }, toastEntry);
998
+ yield ctx.slots.register({
999
+ name: "workspace.toast",
1000
+ key: "archivedNotOpenable",
1001
+ locale: "workspace",
1002
+ inject: plainToast((_notice, t) => ({
1003
+ text: t("toast.archivedNotOpenable"),
1004
+ tone: "warning"
1005
+ }))
1006
+ }, toastEntry);
1007
+ yield ctx.slots.register({
1008
+ name: "workspace.toast",
1009
+ key: "defaultWorkspaceFailed",
1010
+ locale: "workspace",
1011
+ inject: plainToast((_notice, t) => ({
1012
+ text: t("defaultWorkspace.failed"),
1013
+ tone: "warning"
1014
+ }))
1015
+ }, toastEntry);
1016
+ yield ctx.slots.register({
1017
+ name: "workspace.toast",
1018
+ key: "shortcutForkFailed",
1019
+ locale: "workspace",
1020
+ inject: plainToast((notice, t) => ({
1021
+ text: t(notice.reason === "unavailable" ? "shortcut.noCompletedTurn" : "shortcut.forkFailed"),
1022
+ tone: "plain"
1023
+ }))
1024
+ }, toastEntry);
1025
+ yield ctx.slots.register({
1026
+ name: "workspace.toast",
1027
+ key: "createFailed",
1028
+ locale: "workspace",
1029
+ inject: plainToast((notice, t) => ({
1030
+ text: t("toast.createFailed", { message: notice.message }),
1031
+ tone: "warning",
1032
+ holdMs: 6e3
1033
+ }))
1034
+ }, toastEntry);
1035
+ yield ctx.slots.register({
1036
+ name: "workspace.toast",
1037
+ key: "forkRequiresOpen",
1038
+ locale: "workspace",
1039
+ inject: plainToast((notice, t, dismiss) => ({
1040
+ text: t("toast.forkRequiresOpen"),
1041
+ tone: "warning",
1042
+ holdMs: 6e3,
1043
+ actions: [{
1044
+ label: t("toast.openForkSource"),
1045
+ onClick: () => {
1046
+ dismiss();
1047
+ actions.openForkSource(notice.sessionId);
1048
+ }
1049
+ }]
1050
+ }))
1051
+ }, toastEntry);
1052
+ yield ctx.slots.register({
1053
+ name: "workspace.toast",
1054
+ key: "archived",
1055
+ locale: "workspace",
1056
+ store: viewStore,
1057
+ inject: () => ({
1058
+ display: (notice, t, dismiss) => ({
1059
+ text: t("toast.archived"),
1060
+ tone: "success",
1061
+ holdMs: 6e3,
1062
+ actions: [{
1063
+ label: t("toast.archivedUndo"),
1064
+ onClick: () => {
1065
+ dismiss();
1066
+ actions.undoArchive(notice.sessionId);
1067
+ }
1068
+ }]
1069
+ }),
1070
+ filterAction: archiveFilterAction
1071
+ })
1072
+ }, archiveToastEntry);
1073
+ yield ctx.slots.register({
1074
+ name: "workspace.toast",
1075
+ key: "stoppedAndArchived",
1076
+ locale: "workspace",
1077
+ store: viewStore,
1078
+ inject: () => ({
1079
+ display: (notice, t, dismiss) => ({
1080
+ text: t("toast.stoppedAndArchived"),
1081
+ tone: "success",
1082
+ holdMs: 6e3,
1083
+ actions: [{
1084
+ label: t("toast.archivedUndo"),
1085
+ onClick: () => {
1086
+ dismiss();
1087
+ actions.undoArchive(notice.sessionId);
1088
+ }
1089
+ }]
1090
+ }),
1091
+ filterAction: archiveFilterAction
1092
+ })
1093
+ }, archiveToastEntry);
1094
+ });
1095
+ }
1096
+ //#endregion
1097
+ //#region node_modules/.pnpm/@deepseek-ai+dsh-util-values@0.2.1-rc.2_@deepseek-ai+cordis@4.0.5/node_modules/@deepseek-ai/dsh-util-values/lib/index.js
1098
+ /**
1099
+ * Lazily scanned view of one JSON object's top-level fields, built from text
1100
+ * that may still be streaming or from an already parsed object. Nothing is
1101
+ * scanned until a reader asks; the view remembers every question it answered
1102
+ * and reports changed answers when the owner refreshes for publication.
1103
+ * Used for model tool-call arguments: a row reads the fields it
1104
+ * cares about at whatever granularity it displays, at every stage of the call.
1105
+ * @module @deepseek-ai/dsh-util-values/src/partial-json
1106
+ */
1107
+ const SIMPLE_ESCAPES = {
1108
+ "\"": "\"",
1109
+ "\\": "\\",
1110
+ "/": "/",
1111
+ b: "\b",
1112
+ f: "\f",
1113
+ n: "\n",
1114
+ r: "\r",
1115
+ t: " "
1116
+ };
1117
+ const CONTENT_ESCAPE = /[\\\u0000-\u001f]/u;
1118
+ function isWhitespace(c) {
1119
+ return c === " " || c === "\n" || c === "\r" || c === " ";
1120
+ }
1121
+ function isHex(c) {
1122
+ return c >= "0" && c <= "9" || c >= "a" && c <= "f" || c >= "A" && c <= "F";
1123
+ }
1124
+ (class PartialArguments {
1125
+ /** The view of a call with no arguments available. */
1126
+ static EMPTY = PartialArguments.fromObject({});
1127
+ /**
1128
+ * View finished argument text without scanning it until a reader asks.
1129
+ * @param text - the complete argument JSON text.
1130
+ * @returns a sealed view.
1131
+ */
1132
+ static fromText(text) {
1133
+ const view = new PartialArguments();
1134
+ view.append(text);
1135
+ view.sealed = true;
1136
+ return view;
1137
+ }
1138
+ /**
1139
+ * View an already parsed argument payload, such as a PTC dispatch object.
1140
+ * @param value - the parsed argument value.
1141
+ * @returns a sealed view; a non-object payload has no fields.
1142
+ */
1143
+ static fromObject(value) {
1144
+ const view = new PartialArguments();
1145
+ view.object = typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
1146
+ view.sealed = true;
1147
+ return view;
1148
+ }
1149
+ /**
1150
+ * The source: text so far or a parsed object, plus whether it can still grow.
1151
+ * These are the only enumerable fields, so two views over the same source
1152
+ * compare equal structurally however far each has been read.
1153
+ */
1154
+ chunks = [];
1155
+ object;
1156
+ sealed = false;
1157
+ #ends = [];
1158
+ #size = 0;
1159
+ #consumed = 0;
1160
+ #mode = "root";
1161
+ #escape = false;
1162
+ #keyStart = 0;
1163
+ #keyEscaped = false;
1164
+ #key = "";
1165
+ #current = null;
1166
+ #nestedEnds = [];
1167
+ #nestedInString = false;
1168
+ #invalidAt;
1169
+ #invalidValue = false;
1170
+ #entries = /* @__PURE__ */ new Map();
1171
+ #order = [];
1172
+ #reads = /* @__PURE__ */ new Map();
1173
+ /** Whether this view rejects further appends; does not scan text or register reads. */
1174
+ get isSealed() {
1175
+ return this.sealed;
1176
+ }
1177
+ /** Whether indexing or a content read found invalid JSON; unread value contents are not validated. */
1178
+ get invalid() {
1179
+ this.scan();
1180
+ return this.#mode === "invalid" || this.#invalidValue;
1181
+ }
1182
+ /**
1183
+ * Retain streamed argument text without scanning or comparing observed answers.
1184
+ * @param fragment - the text following every fragment appended before.
1185
+ */
1186
+ append(fragment) {
1187
+ if (this.sealed) throw new Error("PartialArguments: cannot append to a sealed view");
1188
+ if (fragment.length === 0) return;
1189
+ this.chunks.push(fragment);
1190
+ this.#size += fragment.length;
1191
+ this.#ends.push(this.#size);
1192
+ }
1193
+ /**
1194
+ * Reconcile a streamed prefix with authoritative complete text without joining the fragments.
1195
+ * @param text - the final argument text, which replaces missing or conflicting deltas.
1196
+ * @returns this view sealed with its caches retained when every character matches; otherwise a new sealed view.
1197
+ */
1198
+ settle(text) {
1199
+ if (this.object !== void 0 || text.length !== this.#size) return PartialArguments.fromText(text);
1200
+ let offset = 0;
1201
+ for (const chunk of this.chunks) {
1202
+ if (!text.startsWith(chunk, offset)) return PartialArguments.fromText(text);
1203
+ offset += chunk.length;
1204
+ }
1205
+ this.chunks = text.length === 0 ? [] : [text];
1206
+ this.#ends = text.length === 0 ? [] : [text.length];
1207
+ this.sealed = true;
1208
+ return this;
1209
+ }
1210
+ /**
1211
+ * Compare observed answers and advance their publication baseline. Unread views remain unscanned.
1212
+ * @returns whether any observed answer changed since its first read or the preceding refresh.
1213
+ */
1214
+ refresh() {
1215
+ if (this.#reads.size === 0) return false;
1216
+ this.scan();
1217
+ let changed = false;
1218
+ let completions = false;
1219
+ for (const read of this.#reads.values()) {
1220
+ if (read.completion) {
1221
+ completions = true;
1222
+ continue;
1223
+ }
1224
+ changed = this.refreshRead(read) || changed;
1225
+ }
1226
+ if (completions) {
1227
+ for (const read of this.#reads.values()) if (read.completion) changed = this.refreshRead(read) || changed;
1228
+ }
1229
+ if (this.sealed) this.#reads.clear();
1230
+ return changed;
1231
+ }
1232
+ refreshRead(read) {
1233
+ const now = read.answer();
1234
+ if (Object.is(now, read.last)) return false;
1235
+ read.last = now;
1236
+ return true;
1237
+ }
1238
+ /**
1239
+ * Check whether no further fields can arrive.
1240
+ * @returns whether the outer object closed, indexing failed, or the view is sealed; unread values are not validated.
1241
+ */
1242
+ closed() {
1243
+ return this.remember("closed", "", () => this.closedNow());
1244
+ }
1245
+ /**
1246
+ * List discovered fields in first-appearance order.
1247
+ * @returns top-level keys seen so far, in first-appearance order.
1248
+ */
1249
+ keys() {
1250
+ return this.remember("keys", "", () => this.keysNow(), (keys) => keys.length);
1251
+ }
1252
+ /**
1253
+ * Check whether a top-level field has appeared.
1254
+ * @param key - argument name.
1255
+ * @returns whether the field has appeared (a string opened or another value began).
1256
+ */
1257
+ has(key) {
1258
+ return this.remember("has", key, () => this.hasNow(key));
1259
+ }
1260
+ /**
1261
+ * Check whether a field's closing delimiter has arrived, without validating its contents.
1262
+ * @param key - argument name.
1263
+ * @returns whether its delimiter arrived and no content reader has reported an error for this value.
1264
+ */
1265
+ complete(key) {
1266
+ return this.remember("complete", key, () => this.completeNow(key));
1267
+ }
1268
+ /**
1269
+ * Read string length without materializing its text.
1270
+ * @param key - argument name.
1271
+ * @param options - change granularity for a streaming string.
1272
+ * @returns decoded UTF-16 length of the string field so far; undefined when absent or not a string.
1273
+ */
1274
+ stringLength(key, options) {
1275
+ const step = Math.max(1, Math.floor(options?.step ?? 1));
1276
+ const offset = options?.offset ?? 0;
1277
+ return this.remember(`length:${step}:${offset}`, key, () => this.lengthNow(key), (length) => length === void 0 ? void 0 : Math.ceil((length + offset) / step));
1278
+ }
1279
+ /**
1280
+ * Check a string against a decoded UTF-16 length limit without materializing it.
1281
+ * @param key - argument name.
1282
+ * @param maxLength - decoded UTF-16 limit, floored to at least zero.
1283
+ * @returns whether the string is longer than the limit; false when absent or not a string.
1284
+ */
1285
+ stringExceeds(key, maxLength) {
1286
+ const limit = Math.max(0, Math.floor(maxLength));
1287
+ return this.remember(`exceeds:${limit}`, key, () => (this.lengthNow(key, limit + 1) ?? 0) > limit);
1288
+ }
1289
+ /**
1290
+ * Read a decoded string, including a streaming prefix.
1291
+ * @param key - argument name.
1292
+ * @returns the string field's decoded text so far; undefined when absent or not a string.
1293
+ */
1294
+ text(key) {
1295
+ return this.remember("text", key, () => this.textNow(key));
1296
+ }
1297
+ /**
1298
+ * Read at most the first decoded UTF-16 units of a string.
1299
+ * @param key - argument name.
1300
+ * @param maxLength - maximum decoded UTF-16 length, floored to at least one.
1301
+ * @returns the bounded string prefix; undefined when absent or not a string.
1302
+ */
1303
+ textPrefix(key, maxLength) {
1304
+ const limit = Math.max(1, Math.floor(maxLength));
1305
+ return this.remember(`prefix:${limit}`, key, () => this.textPrefixNow(key, limit));
1306
+ }
1307
+ /**
1308
+ * Read a completed non-string argument.
1309
+ * @param key - argument name.
1310
+ * @returns the parsed non-string value once it closed; undefined while open, absent, or a string.
1311
+ */
1312
+ value(key) {
1313
+ return this.remember("value", key, () => this.valueNow(key));
1314
+ }
1315
+ /** Answer a question and, on a streaming view, remember it for change detection. */
1316
+ remember(kind, key, read, comparison) {
1317
+ this.scan();
1318
+ const result = read();
1319
+ if (!this.sealed) {
1320
+ const id = `${kind}/${key}`;
1321
+ if (!this.#reads.has(id)) this.#reads.set(id, {
1322
+ completion: kind === "complete",
1323
+ answer: comparison === void 0 ? read : () => comparison(read()),
1324
+ last: comparison === void 0 ? result : comparison(result)
1325
+ });
1326
+ }
1327
+ return result;
1328
+ }
1329
+ closedNow() {
1330
+ return this.sealed || this.#mode === "closed" || this.#mode === "invalid";
1331
+ }
1332
+ keysNow() {
1333
+ return this.object === void 0 ? this.#order : Object.keys(this.object);
1334
+ }
1335
+ hasNow(key) {
1336
+ return this.object === void 0 ? this.#entries.has(key) : Object.hasOwn(this.object, key);
1337
+ }
1338
+ completeNow(key) {
1339
+ if (this.object !== void 0) return Object.hasOwn(this.object, key);
1340
+ const entry = this.#entries.get(key);
1341
+ return entry !== void 0 && entry.end >= 0 && (entry.kind === "string" ? entry.invalidAt === void 0 : !entry.invalid);
1342
+ }
1343
+ lengthNow(key, limit = Number.POSITIVE_INFINITY) {
1344
+ if (this.object !== void 0) {
1345
+ const field = Object.hasOwn(this.object, key) ? this.object[key] : void 0;
1346
+ return typeof field === "string" ? field.length : void 0;
1347
+ }
1348
+ const entry = this.#entries.get(key);
1349
+ if (entry?.kind !== "string") return void 0;
1350
+ if (entry.text !== void 0 && entry.text.at === entry.end) return entry.text.length;
1351
+ const read = entry.length ??= {
1352
+ at: entry.start,
1353
+ length: 0,
1354
+ text: ""
1355
+ };
1356
+ this.readString(entry, read, limit, false);
1357
+ return read.length;
1358
+ }
1359
+ textNow(key) {
1360
+ if (this.object !== void 0) {
1361
+ const field = Object.hasOwn(this.object, key) ? this.object[key] : void 0;
1362
+ return typeof field === "string" ? field : void 0;
1363
+ }
1364
+ const entry = this.#entries.get(key);
1365
+ if (entry?.kind !== "string") return void 0;
1366
+ if (entry.text === void 0 && entry.end >= 0 && entry.needsDecoding && entry.invalidAt === void 0) {
1367
+ let text;
1368
+ try {
1369
+ text = JSON.parse(`"${this.slice(entry.start, entry.end)}"`);
1370
+ } catch (_error) {}
1371
+ if (text !== void 0) entry.text = {
1372
+ at: entry.end,
1373
+ length: text.length,
1374
+ text
1375
+ };
1376
+ }
1377
+ const read = entry.text ??= {
1378
+ at: entry.start,
1379
+ length: 0,
1380
+ text: ""
1381
+ };
1382
+ this.readString(entry, read, Number.POSITIVE_INFINITY, true);
1383
+ return read.text;
1384
+ }
1385
+ textPrefixNow(key, maxLength) {
1386
+ if (this.object !== void 0) {
1387
+ const field = Object.hasOwn(this.object, key) ? this.object[key] : void 0;
1388
+ return typeof field === "string" ? field.slice(0, maxLength) : void 0;
1389
+ }
1390
+ const entry = this.#entries.get(key);
1391
+ if (entry?.kind !== "string") return void 0;
1392
+ const prefixes = entry.prefixes ??= /* @__PURE__ */ new Map();
1393
+ let read = prefixes.get(maxLength);
1394
+ if (read === void 0) {
1395
+ read = {
1396
+ at: entry.start,
1397
+ length: 0,
1398
+ text: ""
1399
+ };
1400
+ prefixes.set(maxLength, read);
1401
+ }
1402
+ this.readString(entry, read, maxLength, true);
1403
+ return read.text;
1404
+ }
1405
+ valueNow(key) {
1406
+ if (this.object !== void 0) {
1407
+ if (!Object.hasOwn(this.object, key)) return void 0;
1408
+ const field = this.object[key];
1409
+ return typeof field === "string" ? void 0 : field;
1410
+ }
1411
+ const entry = this.#entries.get(key);
1412
+ if (entry?.kind !== "value" || entry.end < 0 || entry.invalid) return void 0;
1413
+ if (entry.parsed === void 0) try {
1414
+ entry.parsed = JSON.parse(this.slice(entry.start, entry.end));
1415
+ } catch (_error) {
1416
+ entry.invalid = true;
1417
+ this.#invalidValue = true;
1418
+ }
1419
+ return entry.parsed;
1420
+ }
1421
+ chunkAt(at) {
1422
+ let low = 0;
1423
+ let high = this.#ends.length;
1424
+ while (low < high) {
1425
+ const mid = low + high >>> 1;
1426
+ if (this.#ends[mid] <= at) low = mid + 1;
1427
+ else high = mid;
1428
+ }
1429
+ return low;
1430
+ }
1431
+ /** Materialize only a requested range, never the cumulative source. */
1432
+ slice(start, end) {
1433
+ if (start >= end) return "";
1434
+ const first = this.chunkAt(start);
1435
+ const last = this.chunkAt(end - 1);
1436
+ const base = first === 0 ? 0 : this.#ends[first - 1];
1437
+ if (first === last) return this.chunks[first].slice(start - base, end - base);
1438
+ const parts = [this.chunks[first].slice(start - base)];
1439
+ for (let i = first + 1; i < last; i++) parts.push(this.chunks[i]);
1440
+ parts.push(this.chunks[last].slice(0, end - this.#ends[last - 1]));
1441
+ return parts.join("");
1442
+ }
1443
+ readString(entry, read, limit, materialize) {
1444
+ const end = Math.min(entry.end < 0 ? this.#consumed : entry.end, entry.invalidAt ?? Number.POSITIVE_INFINITY, this.#invalidAt ?? Number.POSITIVE_INFINITY);
1445
+ if (!entry.needsDecoding) {
1446
+ const length = Math.min(end - read.at, limit - read.length);
1447
+ if (length <= 0) return;
1448
+ if (materialize) read.text += this.slice(read.at, read.at + length);
1449
+ read.at += length;
1450
+ read.length += length;
1451
+ return;
1452
+ }
1453
+ let chunkIndex = this.chunkAt(read.at);
1454
+ while (read.at < end && read.length < limit) {
1455
+ const base = chunkIndex === 0 ? 0 : this.#ends[chunkIndex - 1];
1456
+ const chunk = this.chunks[chunkIndex];
1457
+ const remaining = chunk.slice(read.at - base, Math.min(chunk.length, end - base));
1458
+ const boundary = remaining.search(CONTENT_ESCAPE);
1459
+ const length = Math.min(boundary < 0 ? remaining.length : boundary, limit - read.length);
1460
+ if (length > 0) {
1461
+ if (materialize) read.text += remaining.slice(0, length);
1462
+ read.at += length;
1463
+ read.length += length;
1464
+ if (read.at === base + chunk.length) chunkIndex++;
1465
+ continue;
1466
+ }
1467
+ const type = remaining.length > 1 ? remaining[1] : read.at + 1 < end ? this.chunks[chunkIndex + 1][0] : void 0;
1468
+ let decoded;
1469
+ let width = 2;
1470
+ if (remaining[0] === "\\" && type === void 0 && entry.end < 0) return;
1471
+ if (remaining[0] === "\\" && type === "u") {
1472
+ const hex = this.slice(read.at + 2, Math.min(end, read.at + 6));
1473
+ let valid = true;
1474
+ for (let i = 0; i < hex.length; i++) if (!isHex(hex[i])) valid = false;
1475
+ if (valid) {
1476
+ if (hex.length < 4 && entry.end < 0) return;
1477
+ if (hex.length === 4) decoded = String.fromCharCode(Number.parseInt(hex, 16));
1478
+ }
1479
+ width = 6;
1480
+ } else if (remaining[0] === "\\" && type !== void 0) decoded = SIMPLE_ESCAPES[type];
1481
+ if (decoded === void 0) {
1482
+ entry.invalidAt = read.at;
1483
+ this.#invalidValue = true;
1484
+ return;
1485
+ }
1486
+ if (materialize) read.text += decoded;
1487
+ read.length++;
1488
+ read.at += width;
1489
+ while (chunkIndex < this.chunks.length && read.at >= this.#ends[chunkIndex]) chunkIndex++;
1490
+ }
1491
+ }
1492
+ /** Locate new field ranges without decoding or parsing their contents. */
1493
+ scan() {
1494
+ if (this.object !== void 0 || this.#consumed === this.#size) return;
1495
+ for (let i = this.chunkAt(this.#consumed); i < this.chunks.length && this.#invalidAt === void 0; i++) {
1496
+ const pending = this.chunks[i];
1497
+ const base = i === 0 ? 0 : this.#ends[i - 1];
1498
+ for (let index = this.#consumed - base; index < pending.length && this.#mode !== "invalid"; index++) {
1499
+ if (this.#mode === "string" || this.#mode === "nested" && this.#nestedInString) {
1500
+ const end = this.stringBoundary(pending, index);
1501
+ this.#consumed += end - index;
1502
+ index = end;
1503
+ if (index === pending.length) break;
1504
+ }
1505
+ this.step(pending[index], this.#consumed);
1506
+ this.#consumed++;
1507
+ }
1508
+ }
1509
+ }
1510
+ /** Only raw quotes and their preceding backslash runs can terminate a string. */
1511
+ stringBoundary(fragment, start) {
1512
+ let at = start;
1513
+ while (true) {
1514
+ const quote = fragment.indexOf("\"", at);
1515
+ const end = quote < 0 ? fragment.length : quote;
1516
+ if (this.#mode === "string") {
1517
+ const entry = this.#current;
1518
+ if (!entry.needsDecoding && CONTENT_ESCAPE.test(fragment.slice(at, end))) entry.needsDecoding = true;
1519
+ }
1520
+ let slashStart = end;
1521
+ while (slashStart > at && fragment[slashStart - 1] === "\\") slashStart--;
1522
+ const escaped = (end - slashStart) % 2 === 1 !== (slashStart === at && this.#escape);
1523
+ this.#escape = quote < 0 && escaped;
1524
+ if (quote < 0 || !escaped) return end;
1525
+ at = quote + 1;
1526
+ }
1527
+ }
1528
+ step(c, at) {
1529
+ switch (this.#mode) {
1530
+ case "root":
1531
+ if (isWhitespace(c)) return;
1532
+ if (c === "{") {
1533
+ this.#mode = "key-or-end";
1534
+ return;
1535
+ }
1536
+ this.fail();
1537
+ return;
1538
+ case "key-or-end":
1539
+ if (isWhitespace(c)) return;
1540
+ if (c === "}") {
1541
+ this.#mode = "closed";
1542
+ return;
1543
+ }
1544
+ if (c === "\"") {
1545
+ this.beginKey(at);
1546
+ return;
1547
+ }
1548
+ this.fail();
1549
+ return;
1550
+ case "key-only":
1551
+ if (isWhitespace(c)) return;
1552
+ if (c === "\"") {
1553
+ this.beginKey(at);
1554
+ return;
1555
+ }
1556
+ this.fail();
1557
+ return;
1558
+ case "key":
1559
+ this.stepKey(c, at);
1560
+ return;
1561
+ case "colon":
1562
+ if (isWhitespace(c)) return;
1563
+ if (c === ":") {
1564
+ this.#mode = "value";
1565
+ return;
1566
+ }
1567
+ this.fail();
1568
+ return;
1569
+ case "value":
1570
+ this.beginValue(c, at);
1571
+ return;
1572
+ case "string": {
1573
+ const entry = this.#current;
1574
+ entry.end = at;
1575
+ this.#current = null;
1576
+ this.#mode = "comma-or-end";
1577
+ return;
1578
+ }
1579
+ case "scalar":
1580
+ this.stepScalar(c, at);
1581
+ return;
1582
+ case "nested":
1583
+ this.stepNested(c, at);
1584
+ return;
1585
+ case "comma-or-end":
1586
+ if (isWhitespace(c)) return;
1587
+ if (c === ",") {
1588
+ this.#mode = "key-only";
1589
+ return;
1590
+ }
1591
+ if (c === "}") {
1592
+ this.#mode = "closed";
1593
+ return;
1594
+ }
1595
+ this.fail();
1596
+ return;
1597
+ case "closed":
1598
+ if (isWhitespace(c)) return;
1599
+ this.fail();
1600
+ return;
1601
+ /* v8 ignore next 2 -- scan() stops stepping once the view is invalid. */
1602
+ case "invalid": return;
1603
+ /* v8 ignore next 2 -- Every scanner mode has a handler above. */
1604
+ default: assertNever$1(this.#mode);
1605
+ }
1606
+ }
1607
+ fail() {
1608
+ this.#invalidAt = this.#consumed;
1609
+ this.#mode = "invalid";
1610
+ this.#current = null;
1611
+ }
1612
+ beginKey(at) {
1613
+ this.#mode = "key";
1614
+ this.#keyStart = at + 1;
1615
+ this.#keyEscaped = false;
1616
+ this.#escape = false;
1617
+ }
1618
+ stepKey(c, at) {
1619
+ if (c < " ") {
1620
+ this.fail();
1621
+ return;
1622
+ }
1623
+ if (this.#escape) {
1624
+ this.#escape = false;
1625
+ return;
1626
+ }
1627
+ if (c === "\\") {
1628
+ this.#escape = true;
1629
+ this.#keyEscaped = true;
1630
+ return;
1631
+ }
1632
+ if (c !== "\"") return;
1633
+ const raw = this.slice(this.#keyStart, at);
1634
+ if (this.#keyEscaped) try {
1635
+ this.#key = JSON.parse(`"${raw}"`);
1636
+ } catch (_error) {
1637
+ this.fail();
1638
+ return;
1639
+ }
1640
+ else this.#key = raw;
1641
+ this.#mode = "colon";
1642
+ }
1643
+ open(entry) {
1644
+ if (!this.#entries.has(this.#key)) this.#order.push(this.#key);
1645
+ this.#entries.set(this.#key, entry);
1646
+ this.#current = entry;
1647
+ }
1648
+ beginValue(c, at) {
1649
+ if (isWhitespace(c)) return;
1650
+ if (c === "\"") {
1651
+ this.open({
1652
+ kind: "string",
1653
+ start: at + 1,
1654
+ end: -1,
1655
+ needsDecoding: false,
1656
+ invalidAt: void 0,
1657
+ length: void 0,
1658
+ text: void 0,
1659
+ prefixes: void 0
1660
+ });
1661
+ this.#escape = false;
1662
+ this.#mode = "string";
1663
+ return;
1664
+ }
1665
+ if (c === "}" || c === "," || c === ":" || c === "]") {
1666
+ this.fail();
1667
+ return;
1668
+ }
1669
+ this.open({
1670
+ kind: "value",
1671
+ start: at,
1672
+ end: -1,
1673
+ parsed: void 0,
1674
+ invalid: false
1675
+ });
1676
+ if (c === "{" || c === "[") {
1677
+ this.#mode = "nested";
1678
+ this.#nestedEnds = [c === "{" ? "}" : "]"];
1679
+ this.#nestedInString = false;
1680
+ this.#escape = false;
1681
+ return;
1682
+ }
1683
+ this.#mode = "scalar";
1684
+ }
1685
+ stepScalar(c, at) {
1686
+ if (c !== "," && c !== "}" && !isWhitespace(c)) return;
1687
+ this.closeValue(at);
1688
+ this.#mode = c === "," ? "key-only" : c === "}" ? "closed" : "comma-or-end";
1689
+ }
1690
+ stepNested(c, at) {
1691
+ if (this.#nestedInString) {
1692
+ this.#nestedInString = false;
1693
+ return;
1694
+ }
1695
+ if (c === "\"") {
1696
+ this.#nestedInString = true;
1697
+ return;
1698
+ }
1699
+ if (c === "{" || c === "[") {
1700
+ this.#nestedEnds.push(c === "{" ? "}" : "]");
1701
+ return;
1702
+ }
1703
+ if (c === "}" || c === "]") {
1704
+ if (this.#nestedEnds.pop() !== c) {
1705
+ this.fail();
1706
+ return;
1707
+ }
1708
+ if (this.#nestedEnds.length === 0) {
1709
+ this.closeValue(at + 1);
1710
+ this.#mode = "comma-or-end";
1711
+ }
1712
+ }
1713
+ }
1714
+ closeValue(end) {
1715
+ const entry = this.#current;
1716
+ entry.end = end;
1717
+ this.#current = null;
1718
+ }
1719
+ });
613
1720
  /** Duplicate-install-safe JSON and immutable-value helpers. @module @deepseek-ai/dsh-util-values */
614
1721
  /**
615
1722
  * Mark an unreachable closed-union branch.
@@ -622,7 +1729,7 @@ window.__ModuleLoader__.load({
622
1729
  throw new Error(`unreachable variant${context ? ` in ${context}` : ""}: ${rendered}`);
623
1730
  }
624
1731
  //#endregion
625
- //#region node_modules/.pnpm/@deepseek-ai+dsh-util-workspace-path@0.2.0-rc.2_@deepseek-ai+cordis@4.0.4/node_modules/@deepseek-ai/dsh-util-workspace-path/lib/index.js
1732
+ //#region node_modules/.pnpm/@deepseek-ai+dsh-util-workspace-path@0.2.1-rc.2_@deepseek-ai+cordis@4.0.5/node_modules/@deepseek-ai/dsh-util-workspace-path/lib/index.js
626
1733
  /**
627
1734
  * Browser-safe Workspace path and display helpers.
628
1735
  * @module @deepseek-ai/dsh-util-workspace-path
@@ -889,8 +1996,6 @@ window.__ModuleLoader__.load({
889
1996
  * @param rowState - registry-global pin and archive sets plus the archived filter.
890
1997
  * @param statuses - unified UI status by Session.
891
1998
  * @param view - local expansion arrays.
892
- * @param waitingReviewSummary - dsh-gitea-dispatch waiting map (session id → waiting entry).
893
- * @param scheduleSummary - dsh-later schedule map (session id → schedule entry).
894
1999
  * @returns group sections in render order.
895
2000
  */
896
2001
  function deriveGroups(list, workspaces, rowState, statuses, view) {
@@ -1199,46 +2304,90 @@ window.__ModuleLoader__.load({
1199
2304
  };
1200
2305
  }, "ui-workspace: Workspace navigation policy");
1201
2306
  }
1202
- async connectWorkspace(workspaceId) {
2307
+ connectWorkspace(workspaceId, options = {}) {
2308
+ return this.connectBlank(workspaceId, options);
2309
+ }
2310
+ /**
2311
+ * @param reuse - preset for the reused or created blank; absent creates a fresh Session.
2312
+ */
2313
+ async connectBlank(workspaceId, reuse) {
1203
2314
  const workspace = this.workspaces.list.getSnapshot().items.find((item) => item.workspaceId === workspaceId);
1204
2315
  if (workspace === void 0) throw new Error(`uiWorkspace.connectWorkspace: unknown workspace ${workspaceId}`);
2316
+ if (reuse === void 0) return this.sessions.create({ workspaceId });
1205
2317
  const inflight = this.connecting.get(workspaceId);
1206
- if (inflight !== void 0) return inflight;
1207
- const attempt = this.reuseOrCreateBlank(workspace).finally(() => {
1208
- this.connecting.delete(workspaceId);
2318
+ if (inflight !== void 0 && inflight.agentPreset === reuse.agentPreset) return inflight.attempt;
2319
+ const attempt = this.createInWorkspace(workspace, reuse).finally(() => {
2320
+ if (this.connecting.get(workspaceId)?.attempt === attempt) this.connecting.delete(workspaceId);
2321
+ });
2322
+ this.connecting.set(workspaceId, {
2323
+ agentPreset: reuse.agentPreset,
2324
+ attempt
1209
2325
  });
1210
- this.connecting.set(workspaceId, attempt);
1211
2326
  return attempt;
1212
2327
  }
1213
- reuseOrCreateBlank(workspace) {
2328
+ createInWorkspace(workspace, options) {
1214
2329
  const archived = this.workspaces.list.getSnapshot().archivedSessionIds;
1215
2330
  const sessions = this.sessions.list.getSnapshot();
1216
- for (const id of sessions.ids) {
2331
+ const blank = sessions.ids.find((id) => {
1217
2332
  const summary = sessions.byId[id];
1218
- if (summary === void 0 || !summary.blank || summary.cwd !== workspace.path || !workspace.sessionIds.includes(id) || archived.includes(id)) continue;
1219
- return this.reuseBlank(workspace.workspaceId, id);
1220
- }
1221
- return this.sessions.create({ workspaceId: workspace.workspaceId });
2333
+ return summary !== void 0 && summary.blank && summary.cwd === workspace.path && workspace.sessionIds.includes(id) && !archived.includes(id);
2334
+ });
2335
+ return this.createNamed({
2336
+ workspaceId: workspace.workspaceId,
2337
+ ...blank === void 0 ? {} : { sessionId: blank },
2338
+ ...options.agentPreset === void 0 ? {} : { agentPreset: options.agentPreset }
2339
+ });
2340
+ }
2341
+ /**
2342
+ * Create through the Host. A named blank the Host declined may still read as
2343
+ * blank from stale cached list metadata, which hides it from the Session tree;
2344
+ * its projections are reloaded so the row reflects its log.
2345
+ */
2346
+ async createNamed(request) {
2347
+ const sessionId = await this.sessions.create(request);
2348
+ const named = request.sessionId;
2349
+ if (named !== void 0 && named !== sessionId) this.sessions.refreshProjections(named).catch((reason) => {
2350
+ console.warn("declined blank Session projection reload failed:", reason);
2351
+ });
2352
+ return sessionId;
1222
2353
  }
1223
- async reuseBlank(workspaceId, sessionId) {
2354
+ /**
2355
+ * Reopen a saved blank Session under the preset its log recorded, or under
2356
+ * the Host default once that preset no longer activates. Listed values are
2357
+ * cached hints that can lag the log after an unclean Host exit, so the
2358
+ * Session's projections are reloaded before the preset is read; a Session
2359
+ * that still awaits a format migration reloads only those cached hints.
2360
+ */
2361
+ async restoreBlank(workspaceId, sessionId) {
2362
+ await this.sessions.refreshProjections(sessionId);
2363
+ const recorded = this.sessions.list.getSnapshot().byId[sessionId]?.projectionValues?.agentPreset;
2364
+ const request = {
2365
+ workspaceId,
2366
+ sessionId
2367
+ };
2368
+ if (typeof recorded !== "string") return this.createNamed(request);
1224
2369
  try {
1225
- return await this.sessions.create({
1226
- workspaceId,
1227
- sessionId
2370
+ return await this.createNamed({
2371
+ ...request,
2372
+ agentPreset: recorded
1228
2373
  });
1229
2374
  } catch (error) {
1230
- if (sessionCreateErrorOf(error)?.rpcError.code !== "session/writer-held") throw error;
1231
- return this.sessions.create({ workspaceId });
2375
+ const code = sessionCreateErrorOf(error)?.rpcError.code;
2376
+ if (code !== "agent-preset/not-found" && code !== "agent-preset/invalid") throw error;
2377
+ return this.createNamed(request);
1232
2378
  }
1233
2379
  }
1234
2380
  openSession(target) {
1235
2381
  this.replaceMain(target, this.lifetime.signal, "reveal");
1236
2382
  }
1237
- async openWorkspace(workspaceId, beforeOpen) {
2383
+ openWorkspace(workspaceId, beforeOpen, options = {}) {
2384
+ return this.openWorkspaceSession(workspaceId, beforeOpen, options);
2385
+ }
2386
+ async openWorkspaceSession(workspaceId, beforeOpen, reuse) {
1238
2387
  const navigation = AbortSignal.any([this.ctx.layout.beginNavigation(), this.lifetime.signal]);
1239
2388
  let sessionId;
1240
2389
  try {
1241
- sessionId = await this.connectWorkspace(workspaceId);
2390
+ sessionId = await this.connectBlank(workspaceId, reuse);
1242
2391
  } catch (error) {
1243
2392
  if (!navigation.aborted) this.notify({
1244
2393
  kind: "createFailed",
@@ -1249,14 +2398,18 @@ window.__ModuleLoader__.load({
1249
2398
  if (navigation.aborted) return;
1250
2399
  this.replaceMain(sessionId, navigation, "reveal", beforeOpen);
1251
2400
  }
1252
- async forkSession(sessionId, onCreated) {
2401
+ forkSession(sessionId, onCreated, options) {
1253
2402
  return this.sessions.fork({
1254
2403
  sessionId,
1255
2404
  increaseTitle: true,
1256
- ...onCreated === void 0 ? {} : { onCreated }
2405
+ ...onCreated === void 0 ? {} : { onCreated },
2406
+ ...options?.allowMigration === void 0 ? {} : { allowMigration: options.allowMigration }
1257
2407
  });
1258
2408
  }
1259
- startSession(workspaceId) {
2409
+ startSession(workspaceId, options) {
2410
+ const { agentPreset, ...draft } = options ?? {};
2411
+ const draftOptions = options === void 0 ? void 0 : draft;
2412
+ const initializeDraft = draftOptions !== void 0 && (draftOptions.prompt !== void 0 || draftOptions.clearPreviousDraft === true);
1260
2413
  const workspace = this.workspaces.list.getSnapshot();
1261
2414
  const sessions = this.sessions.list.getSnapshot();
1262
2415
  const current = this.mainReference?.sessionId;
@@ -1264,13 +2417,34 @@ window.__ModuleLoader__.load({
1264
2417
  const recent = workspace.phase === "ready" && sessions.phase === "ready" ? recentWorkspace(workspace.items, sessions.byId) : void 0;
1265
2418
  const target = workspaceId ?? currentWorkspaceId ?? recent;
1266
2419
  if (target === void 0) {
2420
+ if (initializeDraft) {
2421
+ this.notify({
2422
+ kind: "createFailed",
2423
+ message: this.ctx.locale.bind("workspace")("draft.workspaceRequired")
2424
+ });
2425
+ return;
2426
+ }
1267
2427
  this.clearMain();
1268
2428
  return;
1269
2429
  }
1270
- this.openWorkspace(target).catch((reason) => {
2430
+ (draftOptions === void 0 ? this.openWorkspaceSession(target, void 0, void 0) : this.openWorkspace(target, initializeDraft ? (sessionId) => {
2431
+ const binding = this.sessions.binding(sessionId) ?? this.draftPreparationFailed();
2432
+ this.prepareDraft(binding, draftOptions);
2433
+ } : void 0, agentPreset === void 0 ? {} : { agentPreset })).catch((reason) => {
1271
2434
  console.warn("new session failed:", reason);
1272
2435
  });
1273
2436
  }
2437
+ prepareDraft(binding, options) {
2438
+ if ((this.ctx.get("conversation") ?? this.draftPreparationFailed()).input.requestDraftInitialization(binding, options) === "blocked") this.draftPreparationFailed();
2439
+ }
2440
+ draftPreparationFailed() {
2441
+ const message = this.ctx.locale.bind("workspace")("draft.initializationFailed");
2442
+ this.notify({
2443
+ kind: "createFailed",
2444
+ message
2445
+ });
2446
+ throw new Error(message);
2447
+ }
1274
2448
  async archiveSession(sessionId, options = {}) {
1275
2449
  await this.workspaces.archiveSession(sessionId, options);
1276
2450
  if (this.mainReference?.sessionId === sessionId) this.clearMain();
@@ -1349,7 +2523,7 @@ window.__ModuleLoader__.load({
1349
2523
  }
1350
2524
  const navigation = AbortSignal.any([this.ctx.layout.beginNavigation(), this.lifetime.signal]);
1351
2525
  let sessionId;
1352
- if (summary !== void 0 && workspace !== void 0 && summary.cwd === workspace.path && !workspaces.archivedSessionIds.includes(summary.id)) sessionId = await this.reuseBlank(workspace.workspaceId, summary.id);
2526
+ if (summary !== void 0 && workspace !== void 0 && summary.cwd === workspace.path && !workspaces.archivedSessionIds.includes(summary.id)) sessionId = await this.restoreBlank(workspace.workspaceId, summary.id);
1353
2527
  let target = workspace?.workspaceId ?? recentWorkspace(workspaces.items, sessions.byId);
1354
2528
  if (target === void 0 && workspaces.items.length === 0 && sessions.ids.length === 0) {
1355
2529
  const prepared = await this.initializeDefaultWorkspace(navigation);
@@ -1469,15 +2643,12 @@ window.__ModuleLoader__.load({
1469
2643
  function workspaceDisplayTitle(title, localizedDefault) {
1470
2644
  return title === "default-workspace" ? localizedDefault : title;
1471
2645
  }
1472
- //#endregion
1473
- //#region src/client/group-status.ts
1474
2646
  /**
1475
2647
  * Zero-valued sentinel for {@link GroupLiveStatus}.
1476
2648
  *
1477
- * Used when a group is expanded (badge is hidden, so aggregation is wasted
1478
- * work) or when no cross-plugin data is available. Kept as a frozen constant
1479
- * so the reference is stable across renders — `Object.is` comparisons in
1480
- * React selectors never trigger spurious re-renders on it.
2649
+ * Used when a group has nothing to report. Kept as a frozen constant so the
2650
+ * reference is stable across renders — `Object.is` comparisons in React selectors
2651
+ * never trigger spurious re-renders on it.
1481
2652
  */
1482
2653
  const EMPTY_LIVE_STATUS = Object.freeze({
1483
2654
  running: 0,
@@ -1486,24 +2657,15 @@ window.__ModuleLoader__.load({
1486
2657
  question: 0,
1487
2658
  done: 0,
1488
2659
  subagents: 0,
1489
- scheduledSessions: 0,
1490
- scheduledCount: 0,
1491
- scheduleState: void 0,
1492
- waitingReviewCount: 0,
1493
- waitingReviewState: void 0
2660
+ facets: /* @__PURE__ */ new Map()
1494
2661
  });
1495
2662
  /**
1496
- * Merge two {@link GroupLiveStatus} objects by summing counters and taking the
1497
- * highest-priority state (overdue > urgent > scheduled for schedule; overdue > waiting
1498
- * for waiting-review).
2663
+ * 合并两份分组折果:本插件自己的计数相加,外部状态来源**按 facet 各自合并**。
1499
2664
  *
1500
- * Used for multi-level aggregation: when a parent group is collapsed, its badge
1501
- * must reflect all descendant sessions, not just direct members.
2665
+ * 用于多层聚合:折叠的父文件夹要覆盖整棵子树,而子树里每一层都已各自折过一遍。
2666
+ * `FacetFold.data` 怎么合归该 facet 自己决定,本模块从不解释。
1502
2667
  */
1503
2668
  function mergeLiveStatus(a, b) {
1504
- const priorityOf = (s) => s === "overdue" ? 3 : s === "urgent" ? 2 : s === "scheduled" ? 1 : 0;
1505
- const scheduleState = priorityOf(a.scheduleState) >= priorityOf(b.scheduleState) ? a.scheduleState : b.scheduleState;
1506
- const waitingReviewState = (a.waitingReviewState === "overdue" ? 2 : a.waitingReviewState === "waiting" ? 1 : 0) >= (b.waitingReviewState === "overdue" ? 2 : b.waitingReviewState === "waiting" ? 1 : 0) ? a.waitingReviewState : b.waitingReviewState;
1507
2669
  return {
1508
2670
  running: a.running + b.running,
1509
2671
  approval: a.approval + b.approval,
@@ -1511,35 +2673,36 @@ window.__ModuleLoader__.load({
1511
2673
  question: a.question + b.question,
1512
2674
  done: a.done + b.done,
1513
2675
  subagents: a.subagents + b.subagents,
1514
- scheduledSessions: a.scheduledSessions + b.scheduledSessions,
1515
- scheduledCount: a.scheduledCount + b.scheduledCount,
1516
- scheduleState,
1517
- waitingReviewCount: a.waitingReviewCount + b.waitingReviewCount,
1518
- waitingReviewState
2676
+ facets: mergeFacets(a.facets, b.facets)
1519
2677
  };
1520
2678
  }
1521
- /**
1522
- * Strip the `session-` / `webhook-` prefix from a Session id.
1523
- *
1524
- * dsh-gitea-dispatch publishes its waiting map keyed by the host SessionId, but
1525
- * its HTTP endpoints key by this normalized form. Accepting both here keeps the
1526
- * badge working across that publisher's conventions without knowing which is in
1527
- * play.
1528
- */
1529
- function stripSessionPrefix(id) {
1530
- return id.replace(/^(?:session|webhook)-/, "");
2679
+ /** 按 facet id 合并两侧折果;只有一侧有的来源原样带过。 */
2680
+ function mergeFacets(a, b) {
2681
+ if (a.size === 0) return b;
2682
+ if (b.size === 0) return a;
2683
+ const merged = new Map(a);
2684
+ for (const [id, entry] of b) {
2685
+ const own = merged.get(id);
2686
+ merged.set(id, own === void 0 ? entry : {
2687
+ facet: own.facet,
2688
+ fold: own.facet.merge?.(own.fold, entry.fold) ?? defaultMergeFold(own.fold, entry.fold)
2689
+ });
2690
+ }
2691
+ return merged;
1531
2692
  }
1532
2693
  /**
1533
2694
  * Fold every member row of a group into counts so a collapsed folder can hint at
1534
2695
  * hidden live sessions. Mirrors the session-row status priority (pending
1535
2696
  * outranks running outranks completed) at the counting level; labels resolve with
1536
2697
  * locale at render time.
2698
+ *
1537
2699
  * @param nodes - member rows of the group.
1538
- * @param sessionIds - the same members' Session ids (schedule and waiting facts are keyed by id).
1539
- * @param waitingReviewSummary - dsh-gitea-dispatch waiting map, when that plugin is loaded.
1540
- * @param scheduleSummary - dsh-later schedule map, when that plugin is loaded.
2700
+ * @param sessionIds - the same members' Session ids (facets address their own data by id).
2701
+ * @param facets - the registered status sources to fold over this group.
2702
+ * @param now - one clock reading for the whole pass, so sibling facets cannot disagree
2703
+ * about which deadlines are still ahead.
1541
2704
  */
1542
- function summarizeGroupStatus(nodes, sessionIds, waitingReviewSummary, scheduleSummary) {
2705
+ function summarizeGroupStatus(nodes, sessionIds, facets = [], now = Date.now()) {
1543
2706
  let running = 0;
1544
2707
  let approval = 0;
1545
2708
  let planReview = 0;
@@ -1548,50 +2711,48 @@ window.__ModuleLoader__.load({
1548
2711
  let subagents = 0;
1549
2712
  for (const n of nodes) {
1550
2713
  if (n.blank || n.archived) continue;
2714
+ const hasSubagents = n.runningSubagentCount > 0;
1551
2715
  if (n.pendingInteraction === "approval") {
1552
2716
  approval += 1;
2717
+ subagents += n.runningSubagentCount;
1553
2718
  continue;
1554
2719
  }
1555
2720
  if (n.pendingInteraction === "plan-review") {
1556
2721
  planReview += 1;
2722
+ subagents += n.runningSubagentCount;
1557
2723
  continue;
1558
2724
  }
1559
2725
  if (n.pendingInteraction === "question") {
1560
2726
  question += 1;
2727
+ subagents += n.runningSubagentCount;
1561
2728
  continue;
1562
2729
  }
1563
- if (n.running) running += 1;
1564
- else if (n.completed) done += 1;
1565
- subagents += n.runningSubagentCount;
1566
- }
1567
- let scheduledSessions = 0;
1568
- let scheduledCount = 0;
1569
- let scheduleState = void 0;
1570
- if (scheduleSummary && sessionIds.length > 0) {
1571
- let totalSessions = 0;
1572
- let totalCount = 0;
1573
- let highestPriority = 0;
1574
- for (const id of sessionIds) {
1575
- const entry = scheduleSummary.get(id);
1576
- if (entry) {
1577
- totalSessions += 1;
1578
- totalCount += entry.count;
1579
- const priority = entry.state === "overdue" ? 3 : entry.state === "urgent" ? 2 : 1;
1580
- if (priority > highestPriority) highestPriority = priority;
1581
- }
2730
+ if (n.running) {
2731
+ running += 1;
2732
+ subagents += n.runningSubagentCount;
2733
+ continue;
2734
+ }
2735
+ if (hasSubagents) {
2736
+ subagents += n.runningSubagentCount;
2737
+ continue;
1582
2738
  }
1583
- scheduledSessions = totalSessions;
1584
- scheduledCount = totalCount;
1585
- scheduleState = highestPriority === 3 ? "overdue" : highestPriority === 2 ? "urgent" : highestPriority === 1 ? "scheduled" : void 0;
2739
+ if (n.completed) done += 1;
1586
2740
  }
1587
- let waitingReviewCount = 0;
1588
- let waitingReviewState = void 0;
1589
- if (waitingReviewSummary && sessionIds.length > 0) for (const id of sessionIds) {
1590
- const entry = waitingReviewSummary.get(id) ?? waitingReviewSummary.get(stripSessionPrefix(id));
1591
- if (entry === void 0) continue;
1592
- waitingReviewCount += 1;
1593
- if (entry.state === "overdue") waitingReviewState = "overdue";
1594
- else if (waitingReviewState === void 0) waitingReviewState = "waiting";
2741
+ const folded = /* @__PURE__ */ new Map();
2742
+ for (const { facet } of facets) {
2743
+ let snapshot;
2744
+ try {
2745
+ snapshot = facet.getSnapshot();
2746
+ } catch {
2747
+ snapshot = void 0;
2748
+ }
2749
+ try {
2750
+ const result = facet.fold(snapshot, sessionIds, now);
2751
+ if (result !== void 0 && result.count > 0) folded.set(facet.id, {
2752
+ facet,
2753
+ fold: result
2754
+ });
2755
+ } catch {}
1595
2756
  }
1596
2757
  return {
1597
2758
  running,
@@ -1600,11 +2761,7 @@ window.__ModuleLoader__.load({
1600
2761
  question,
1601
2762
  done,
1602
2763
  subagents,
1603
- scheduledSessions,
1604
- scheduledCount,
1605
- scheduleState,
1606
- waitingReviewCount,
1607
- waitingReviewState
2764
+ facets: folded
1608
2765
  };
1609
2766
  }
1610
2767
  /**
@@ -1618,12 +2775,12 @@ window.__ModuleLoader__.load({
1618
2775
  * every collapsed parent undercount by the size of its expanded descendants.
1619
2776
  *
1620
2777
  * @param groups - derived groups, each with its member rows already populated.
1621
- * @param waitingReviewSummary - dsh-gitea-dispatch waiting map, when that plugin is loaded.
1622
- * @param scheduleSummary - dsh-later schedule map, when that plugin is loaded.
2778
+ * @param facets - registered sources, already filtered by the visibility settings.
2779
+ * @param now - one clock reading shared by every group in the pass.
1623
2780
  */
1624
- function directStatusesForGroups(groups, waitingReviewSummary, scheduleSummary) {
2781
+ function directStatusesForGroups(groups, facets = [], now = Date.now()) {
1625
2782
  const map = /* @__PURE__ */ new Map();
1626
- for (const group of groups) map.set(group.key, summarizeGroupStatus(group.sessions, group.sessions.map((s) => s.id), waitingReviewSummary, scheduleSummary));
2783
+ for (const group of groups) map.set(group.key, summarizeGroupStatus(group.sessions, group.sessions.map((s) => s.id), facets, now));
1627
2784
  return map;
1628
2785
  }
1629
2786
  /**
@@ -1672,67 +2829,115 @@ window.__ModuleLoader__.load({
1672
2829
  liveStatus: aggregated.get(group.key) ?? EMPTY_LIVE_STATUS
1673
2830
  }));
1674
2831
  }
2832
+ /** The blank row plus the first `watermark` non-blank rows, in list order. */
2833
+ function watermarkRows(sessions, watermark) {
2834
+ let count = 0;
2835
+ const rows = sessions.filter((session) => {
2836
+ if (session.blank) return true;
2837
+ count += 1;
2838
+ return count <= watermark;
2839
+ });
2840
+ return {
2841
+ rows,
2842
+ hiddenCount: sessions.length - rows.length
2843
+ };
2844
+ }
2845
+ /**
2846
+ * Smallest watermark that covers every non-blank row within the automatic maximum that is running, has a running
2847
+ * child, awaits the user, carries an unread completion, or is current, and the reveal target at any position.
2848
+ * @param sessions - one group's ordered Session rows.
2849
+ * @param current - Session retained by the main view, if the sidebar is the active surface.
2850
+ * @param revealSessionId - Session selected from search and required to be visible.
2851
+ * @returns the group's baseline non-blank row count.
2852
+ */
2853
+ function requiredWatermark(sessions, current, revealSessionId) {
2854
+ let index = 0;
2855
+ let required = 0;
2856
+ for (const session of sessions) {
2857
+ if (session.blank) continue;
2858
+ index += 1;
2859
+ if (index <= 50 && (session.running || session.runningSubagentCount > 0 || session.pendingInteraction !== void 0 || session.completed || session.id === current) || session.id === revealSessionId) required = index;
2860
+ }
2861
+ return required;
2862
+ }
2863
+ /**
2864
+ * Raise each listed group's watermark to its requirement; returns `marks` itself when nothing rises.
2865
+ * @param marks - watermarks stored by the current browser tree.
2866
+ * @param required - required baselines, in group order.
2867
+ * @returns either the unchanged input or a new object containing only raised groups.
2868
+ */
2869
+ function raiseWatermarks(marks, required) {
2870
+ let raised = marks;
2871
+ for (const [key, watermark] of required) if (watermark > (raised[key] ?? 5)) raised = {
2872
+ ...raised,
2873
+ [key]: watermark
2874
+ };
2875
+ return raised;
2876
+ }
1675
2877
  //#endregion
1676
- //#region \0dsh-css:/home/wujue/workspace/dsh-plugins/dsh-group-status/src/client/rows/Rows.module.css.mjs
1677
- const css$3 = ".eCE9Ta_projectRow,.eCE9Ta_sessionRow{border-radius:var(--dsw-radius-md);padding:0 8px;cursor:pointer;user-select:none;color:var(--dsw-alias-label-primary);align-items:center;gap:6px;padding-inline-start:calc(8px + var(--dsh-workspace-indent,0px));display:flex}.eCE9Ta_projectRow:hover,.eCE9Ta_sessionRow:hover,.eCE9Ta_sessionRow.eCE9Ta_selected{background:var(--dsw-alias-interactive-bg-hover)}.eCE9Ta_searchResultRow{box-sizing:border-box;border-radius:var(--dsw-radius-lg);cursor:pointer;text-align:left;width:100%;min-height:48px;color:var(--dsw-alias-label-primary);background:0 0;border:none;flex-direction:column;align-items:stretch;padding:4px 8px;display:flex}.eCE9Ta_searchResultRow:hover,.eCE9Ta_searchResultRow.eCE9Ta_selected{background:var(--dsw-alias-interactive-bg-hover)}.eCE9Ta_searchResultHeading{align-items:center;min-width:0;display:flex}.eCE9Ta_searchResultTitle{text-overflow:ellipsis;white-space:nowrap;flex:0 auto;min-width:0;margin-left:4px;font-size:14px;line-height:20px;overflow:hidden}.eCE9Ta_searchResultMeta{align-items:center;gap:6px;min-width:0;margin-left:20px;display:flex}.eCE9Ta_searchResultWorkspace,.eCE9Ta_searchResultSnippet{text-overflow:ellipsis;white-space:nowrap;font-size:12px;line-height:17px;overflow:hidden}.eCE9Ta_searchResultWorkspace{max-width:40%;color:var(--dsw-alias-label-tertiary);flex:none}.eCE9Ta_searchResultWorkspace:only-child{max-width:100%}.eCE9Ta_searchResultSnippet{min-width:0;color:var(--dsw-alias-label-secondary);flex:1}.eCE9Ta_projectRow{box-sizing:border-box;align-items:center;height:34px}.eCE9Ta_projectRow .eCE9Ta_rowActions{height:20px}.eCE9Ta_sessionRow{gap:0;height:32px}.eCE9Ta_sessionRow .eCE9Ta_title{margin:0 6px 0 4px}.eCE9Ta_slot{width:16px;height:20px;color:var(--dsw-alias-label-tertiary);flex:none;justify-content:center;align-items:center;display:inline-flex}.eCE9Ta_visuallyHidden{clip:rect(0 0 0 0);white-space:nowrap;width:1px;height:1px;position:absolute;overflow:hidden}.eCE9Ta_folderActive{color:var(--dsw-alias-state-business-primary)}.eCE9Ta_projectRow .eCE9Ta_chevron{display:none}.eCE9Ta_projectRow:hover .eCE9Ta_chevron{display:inline-flex}.eCE9Ta_projectRow:hover .eCE9Ta_folder{display:none}.eCE9Ta_arrow{transition:transform .15s var(--ds-ease-in-out)}.eCE9Ta_arrowOpen{transform:rotate(90deg)}.eCE9Ta_projectText{flex-direction:column;flex:1;gap:2px;min-width:0;display:flex}.eCE9Ta_title{text-overflow:ellipsis;white-space:nowrap;min-width:0;font-size:14px;line-height:20px;overflow:hidden}.eCE9Ta_renameInput{border:.5px solid var(--dsw-alias-border-l4);border-radius:var(--dsw-radius-sm);background:var(--dsw-alias-button-elevated-fill);min-width:0;color:inherit;outline:none;padding:0 2px;font-size:14px;line-height:20px}.eCE9Ta_sessionRow .eCE9Ta_title{flex:1}.eCE9Ta_sessionRow .eCE9Ta_title[data-scrolled]{mask-image:linear-gradient(90deg,#0000,#000 12px)}.eCE9Ta_sessionRow .eCE9Ta_title[data-clipped]{mask-image:linear-gradient(270deg,#0000,#000 12px)}.eCE9Ta_sessionRow .eCE9Ta_title[data-scrolled][data-clipped]{mask-image:linear-gradient(90deg,#0000,#000 12px calc(100% - 12px),#0000)}.eCE9Ta_meta{text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:20px;overflow:hidden}.eCE9Ta_time{color:var(--dsw-alias-label-tertiary);flex:none;font-size:10px;line-height:16px}.eCE9Ta_pinIndicator{width:16px;height:20px;color:var(--dsw-alias-label-caption);flex:none;justify-content:center;align-items:center;margin-left:6px;display:inline-flex}.eCE9Ta_sessionRow.eCE9Ta_archived .eCE9Ta_title,.eCE9Ta_searchResultRow.eCE9Ta_archived .eCE9Ta_searchResultTitle,.eCE9Ta_searchResultRow.eCE9Ta_archived .eCE9Ta_searchResultWorkspace,.eCE9Ta_searchResultRow.eCE9Ta_archived .eCE9Ta_searchResultSnippet{color:var(--dsw-alias-label-caption)}.eCE9Ta_dot{flex:none}.eCE9Ta_rowActions{flex:none;align-items:center;gap:10px;display:none}.eCE9Ta_projectRow:hover .eCE9Ta_rowActions,.eCE9Ta_sessionRow:hover .eCE9Ta_rowActions,.eCE9Ta_searchResultRow:hover .eCE9Ta_rowActions,.eCE9Ta_projectRow.eCE9Ta_menuOpen .eCE9Ta_rowActions,.eCE9Ta_sessionRow.eCE9Ta_menuOpen .eCE9Ta_rowActions{display:inline-flex}.eCE9Ta_searchResultHeading .eCE9Ta_rowActions{margin-left:auto}.eCE9Ta_sessionRow:hover .eCE9Ta_time,.eCE9Ta_sessionRow.eCE9Ta_menuOpen .eCE9Ta_time,.eCE9Ta_sessionRow:hover .eCE9Ta_pinIndicator,.eCE9Ta_sessionRow.eCE9Ta_menuOpen .eCE9Ta_pinIndicator{display:none}@media (hover:hover){.eCE9Ta_sessionRow:hover .eCE9Ta_title,.eCE9Ta_sessionRow.eCE9Ta_menuOpen .eCE9Ta_title{text-overflow:clip}}.eCE9Ta_projectRow.eCE9Ta_menuOpen,.eCE9Ta_sessionRow.eCE9Ta_menuOpen{background:var(--dsw-alias-interactive-bg-hover)}.eCE9Ta_sessionRow.eCE9Ta_dropBefore,.eCE9Ta_sessionRow.eCE9Ta_dropAfter{position:relative}.eCE9Ta_sessionRow.eCE9Ta_dropBefore:before,.eCE9Ta_sessionRow.eCE9Ta_dropAfter:after{content:\"\";z-index:1;background:linear-gradient(55deg, transparent calc(50% - 1px), var(--dsw-alias-state-business-primary) calc(50% - 1px) calc(50% + 1px), transparent calc(50% + 1px)) 0 0 / 5px 7px no-repeat, linear-gradient(125deg, transparent calc(50% - 1px), var(--dsw-alias-state-business-primary) calc(50% - 1px) calc(50% + 1px), transparent calc(50% + 1px)) 0 5px / 5px 7px no-repeat, linear-gradient(var(--dsw-alias-state-business-primary) 0 0) 4px 5px / calc(100% - 4px) 2px no-repeat;pointer-events:none;height:12px;position:absolute;left:0;right:4px}.eCE9Ta_sessionRow.eCE9Ta_dropBefore:before{top:-7px}.eCE9Ta_sessionRow.eCE9Ta_dropAfter:after{bottom:-7px}.eCE9Ta_hoverContent{flex-direction:column;gap:8px;display:flex}.eCE9Ta_hoverTitle{color:var(--dsw-alias-label-primary);overflow-wrap:break-word;font-size:14px;line-height:20px}.eCE9Ta_hoverPath{color:var(--dsw-alias-label-secondary);word-break:break-all;font-size:12px;line-height:16px}.eCE9Ta_hoverTime{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:16px}.eCE9Ta_hoverStatus{color:var(--dsw-alias-label-tertiary);align-items:center;gap:8px;font-size:12px;line-height:20px;display:flex}.eCE9Ta_groupBadge{flex:none;align-items:center;gap:3px;margin-inline-start:4px;display:inline-flex}.eCE9Ta_groupBadgeCount{color:var(--dsw-alias-label-tertiary);border:1px solid;border-radius:6px;padding:0 3px;font-size:9px;line-height:12px}@keyframes eCE9Ta_groupBadgePulse{0%,to{opacity:1}50%{opacity:.6}}.eCE9Ta_groupBadgeUrgent{animation:2s ease-in-out infinite eCE9Ta_groupBadgePulse}.eCE9Ta_hoverArchived svg{flex-shrink:0;margin:0 -4px}.eCE9Ta_iconButton{border-radius:var(--dsw-radius-xs);cursor:pointer;width:16px;height:16px;color:var(--dsw-alias-label-tertiary);background:0 0;border:none;flex:none;justify-content:center;align-items:center;padding:0;display:inline-flex}.eCE9Ta_iconButton:hover{color:var(--dsw-alias-label-primary)}.eCE9Ta_chevron{color:var(--dsw-alias-label-caption)}@media (prefers-reduced-motion:reduce){.eCE9Ta_arrow{transition:none;animation:none}}";
1678
- const tagId$3 = "dsh-group-status/Rows.module.css";
1679
- if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$3) + "]") === null) {
1680
- const tag = document.createElement("style");
1681
- tag.dataset.plugin = "dsh-group-status";
1682
- tag.dataset.pluginCss = tagId$3;
1683
- tag.textContent = css$3;
1684
- document.head.appendChild(tag);
2878
+ //#region src/client/facet-visibility.ts
2879
+ /**
2880
+ * 按用户设置筛掉不参与折叠的状态来源。
2881
+ *
2882
+ * 单独成文是因为它属于**徽标自身**的可见性规则,而不是某个来源的适配:来源只在
2883
+ * `facet.settingKey` 上声明"我受哪个开关管",开关的语义与过滤时机归本插件。
2884
+ *
2885
+ * 过滤发生在**折叠之前**而不是渲染阶段:徽标数字、图标、`aria-label`、悬停明细与
2886
+ * "要不要画徽标"因此天然同源。放到渲染阶段会出现"label 里有、计数里没有",而那正是
2887
+ * 徽标最容易被怀疑撒谎的地方。
2888
+ *
2889
+ * @module dsh-group-status/client/facet-visibility
2890
+ */
2891
+ /** 筛出设置允许显示的登记项;顺序保持登记面的顺序(已按 `order` 排好)。 */
2892
+ function visibleFacets(entries, settings) {
2893
+ return entries.filter((entry) => facetVisibleBySettings(settings, entry.facet.settingKey));
1685
2894
  }
1686
- var Rows_module_css_default = {
1687
- "archived": "eCE9Ta_archived",
1688
- "arrow": "eCE9Ta_arrow",
1689
- "arrowOpen": "eCE9Ta_arrowOpen",
1690
- "chevron": "eCE9Ta_chevron",
1691
- "dot": "eCE9Ta_dot",
1692
- "dropAfter": "eCE9Ta_dropAfter",
1693
- "dropBefore": "eCE9Ta_dropBefore",
1694
- "folder": "eCE9Ta_folder",
1695
- "folderActive": "eCE9Ta_folderActive",
1696
- "groupBadge": "eCE9Ta_groupBadge",
1697
- "groupBadgeCount": "eCE9Ta_groupBadgeCount",
1698
- "groupBadgePulse": "eCE9Ta_groupBadgePulse",
1699
- "groupBadgeUrgent": "eCE9Ta_groupBadgeUrgent",
1700
- "hoverArchived": "eCE9Ta_hoverArchived",
1701
- "hoverContent": "eCE9Ta_hoverContent",
1702
- "hoverPath": "eCE9Ta_hoverPath",
1703
- "hoverStatus": "eCE9Ta_hoverStatus",
1704
- "hoverTime": "eCE9Ta_hoverTime",
1705
- "hoverTitle": "eCE9Ta_hoverTitle",
1706
- "iconButton": "eCE9Ta_iconButton",
1707
- "menuOpen": "eCE9Ta_menuOpen",
1708
- "meta": "eCE9Ta_meta",
1709
- "pinIndicator": "eCE9Ta_pinIndicator",
1710
- "projectRow": "eCE9Ta_projectRow",
1711
- "projectText": "eCE9Ta_projectText",
1712
- "renameInput": "eCE9Ta_renameInput",
1713
- "rowActions": "eCE9Ta_rowActions",
1714
- "searchResultHeading": "eCE9Ta_searchResultHeading",
1715
- "searchResultMeta": "eCE9Ta_searchResultMeta",
1716
- "searchResultRow": "eCE9Ta_searchResultRow",
1717
- "searchResultSnippet": "eCE9Ta_searchResultSnippet",
1718
- "searchResultTitle": "eCE9Ta_searchResultTitle",
1719
- "searchResultWorkspace": "eCE9Ta_searchResultWorkspace",
1720
- "selected": "eCE9Ta_selected",
1721
- "sessionRow": "eCE9Ta_sessionRow",
1722
- "slot": "eCE9Ta_slot",
1723
- "time": "eCE9Ta_time",
1724
- "title": "eCE9Ta_title",
1725
- "visuallyHidden": "eCE9Ta_visuallyHidden"
1726
- };
1727
2895
  //#endregion
1728
- //#region src/client/rows/GroupBadge.tsx
2896
+ //#region src/client/badge-labels.ts
2897
+ /**
2898
+ * 把侧栏行的翻译位交给 facet。
2899
+ *
2900
+ * 这里有一次**故意的放宽**:协议的 `t` 收任意字符串键,而行位只收本插件 `workspace`
2901
+ * 命名空间的键。放宽是安全的——宿主 locale 服务对未注册的键一律**原样回显键名**(不是
2902
+ * 抛错),所以外来 facet 传了自己命名空间的键,最坏读出的是那串键名而不是崩渲染。
2903
+ * 要正确本地化,provider 应当闭包自己的翻译函数、忽略这里传入的 `t`。
2904
+ */
2905
+ function asFacetTranslate(t) {
2906
+ return (key, options) => t(key, options);
2907
+ }
2908
+ /** 该 facet 贡献进徽标数字的条数。 */
2909
+ function facetCountTotal(s) {
2910
+ let total = 0;
2911
+ for (const entry of s.facets.values()) total += entry.fold.count;
2912
+ return total;
2913
+ }
2914
+ /**
2915
+ * 每个外部状态来源的那句话,按登记顺序。
2916
+ *
2917
+ * facet 没实现 `label` 时**不能**退化成 `2 dsh-later.schedule` 这种读法,所以给一句
2918
+ * 通用措辞兜底:只说有几个来源在响,具体语义等该来源自己补文案。
2919
+ */
2920
+ function facetLines(s, t, now) {
2921
+ const translate = asFacetTranslate(t);
2922
+ const lines = [];
2923
+ for (const entry of s.facets.values()) {
2924
+ const text = entry.facet.label?.(entry.fold, {
2925
+ now,
2926
+ t: translate
2927
+ });
2928
+ lines.push(text === void 0 || text === "" ? `${entry.fold.count} ${t("status.needsAttention")}` : text);
2929
+ }
2930
+ return lines;
2931
+ }
1729
2932
  /**
1730
2933
  * Accessible label for a folded group's aggregated status, reusing the
1731
2934
  * session-row locale keys so both surfaces read identically.
1732
2935
  *
1733
- * 按设置过滤:关掉的来源不出现在标签里(aria-label 与徽标视觉一致)。
2936
+ * 按设置过滤:**关掉的外部来源根本不在折果里**(`WorkspaceBrowser` 折叠前就按
2937
+ * `facet.settingKey` 过滤了登记面),所以这里不必再判设置,`aria-label` 与徽标视觉
2938
+ * 天然一致;宿主的行级状态仍按设置逐项过滤。
1734
2939
  */
1735
- function groupLiveLabel(s, t, settings) {
2940
+ function groupLiveLabel(s, t, settings, now = Date.now()) {
1736
2941
  const parts = [];
1737
2942
  if (settings.showPendingInBadge) {
1738
2943
  if (s.approval > 0) parts.push(`${s.approval} ${t("status.waitingApproval")}`);
@@ -1743,80 +2948,50 @@ window.__ModuleLoader__.load({
1743
2948
  if (s.running > 0) parts.push(`${s.running} ${t("status.running")}`);
1744
2949
  if (s.subagents > 0) parts.push(t(s.subagents === 1 ? "status.subagentsRunning.one" : "status.subagentsRunning.other", { n: s.subagents }));
1745
2950
  }
1746
- if (settings.showWaitingReviewInBadge && s.waitingReviewCount > 0) parts.push(`${s.waitingReviewCount} ${t("status.waitingReview")}`);
1747
- if (settings.showScheduledInBadge && s.scheduledSessions > 0) parts.push(`${s.scheduledSessions} ${t("status.scheduled")}`);
2951
+ parts.push(...facetLines(s, t, now));
1748
2952
  if (settings.showCompletedInBadge && s.done > 0) parts.push(`${s.done} ${t("status.completed")}`);
1749
2953
  return parts.join(" · ");
1750
2954
  }
1751
2955
  /**
1752
- * Aggregated live-status badge for a folded folder row. Dots reuse StateDot so
1753
- * colors match session rows (warning = pending, ongoing = running, done = completed).
1754
- * Renders nothing while expanded or when nothing is pending/active/completed/scheduled/waiting.
2956
+ * 每个外部状态来源的明细行,**带它自己的图标**(#24)。
1755
2957
  *
1756
- * Count matches the dots: pending + active + completed + waiting-review + scheduled sessions.
2958
+ * 徽标侧画的是 provider 交出的、已含色档的元素(`rows/GroupBadge.tsx` 的 `facetGlyph`),
2959
+ * 明细侧此前只画一个统一的 `StateDot`,于是同一条状态、同一次悬停里,徽标是红色沙漏、
2960
+ * 明细是黄点——同一行的两个视图各说一套。宿主 `StateDot` 只有 warning / ongoing / done
2961
+ * 三档、没有 error 档,所以色档**只能**由来源自己的图标表达,这也是本函数存在的理由。
1757
2962
  *
1758
- * `settings` 按来源过滤:关掉的来源不画点、不计数、不进 aria-label、不参与「是否显示徽标」
1759
- * 的判断。会话行自身的状态不受影响:那是行级状态,不是分组提示。
2963
+ * provider 没交图标时退回通用点(与徽标侧同一套兜底),陌生来源仍然可见。
2964
+ *
2965
+ * @returns 明细行;`glyph` 为 `undefined` 表示该行用 `StateDot` 渲染。
1760
2966
  */
1761
- function groupLiveBadge(group, t, settings) {
1762
- const s = group.liveStatus;
1763
- if (group.expanded || s === void 0) return null;
1764
- const pending = settings.showPendingInBadge ? s.approval + s.planReview + s.question : 0;
1765
- const active = settings.showRunningInBadge ? s.running + s.subagents : 0;
1766
- const scheduled = settings.showScheduledInBadge ? s.scheduledSessions : 0;
1767
- const hasWaitingReview = settings.showWaitingReviewInBadge && s.waitingReviewCount > 0;
1768
- const completed = settings.showCompletedInBadge ? s.done : 0;
1769
- if (pending === 0 && active === 0 && completed === 0 && scheduled === 0 && !hasWaitingReview) return null;
1770
- const label = groupLiveLabel(s, t, settings);
1771
- const dots = [];
1772
- if (pending > 0) dots.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state: "warning" }, "warn"));
1773
- if (active > 0) dots.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state: "ongoing" }, "run"));
1774
- if (completed > 0) dots.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state: "done" }, "done"));
1775
- if (scheduled > 0) {
1776
- const stateColor = s.scheduleState === "overdue" ? "var(--dsw-alias-state-error-primary)" : s.scheduleState === "urgent" ? "var(--dsw-alias-state-warn-secondary)" : "#8b5cf6";
1777
- dots.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1778
- style: {
1779
- color: stateColor,
1780
- display: "inline-flex",
1781
- alignItems: "center"
1782
- },
1783
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconClockOutlineRegular, { size: 14 })
1784
- }, "schedule"));
1785
- }
1786
- if (hasWaitingReview) {
1787
- const stateColor = s.waitingReviewState === "overdue" ? "var(--dsw-alias-state-error-primary)" : "var(--dsw-alias-state-warn-secondary)";
1788
- dots.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1789
- style: {
1790
- color: stateColor,
1791
- display: "inline-flex",
1792
- alignItems: "center"
1793
- },
1794
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconListPenOutlineRegular, { size: 14 })
1795
- }, "waiting-review"));
2967
+ function facetDetailLines(s, t, now) {
2968
+ const translate = asFacetTranslate(t);
2969
+ const lines = [];
2970
+ for (const entry of s.facets.values()) {
2971
+ const raw = entry.facet.label?.(entry.fold, {
2972
+ now,
2973
+ t: translate
2974
+ });
2975
+ const text = raw === void 0 || raw === "" ? `${entry.fold.count} ${t("status.needsAttention")}` : raw;
2976
+ lines.push({
2977
+ state: "warning",
2978
+ text,
2979
+ glyph: facetGlyph(entry, t, now)
2980
+ });
1796
2981
  }
1797
- const count = pending + active + completed + scheduled + (hasWaitingReview ? s.waitingReviewCount : 0);
1798
- const countLabel = count > 99 ? "99+" : count >= settings.countThreshold ? String(count) : "";
1799
- const badgeClass = s.scheduleState === "overdue" || s.scheduleState === "urgent" || s.waitingReviewState === "overdue" ? `${Rows_module_css_default.groupBadge} ${Rows_module_css_default.groupBadgeUrgent}` : Rows_module_css_default.groupBadge;
1800
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1801
- className: badgeClass,
1802
- role: "img",
1803
- "aria-label": label,
1804
- title: label,
1805
- children: [dots, countLabel && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1806
- className: Rows_module_css_default.groupBadgeCount,
1807
- children: countLabel
1808
- })]
1809
- });
2982
+ return lines;
1810
2983
  }
1811
2984
  /**
1812
2985
  * Build the status detail lines for the workspace hover card.
1813
2986
  *
1814
- * Extracted so `WorkspaceHoverContent` in `Rows.tsx` only needs to map the
1815
- * returned array — the fork-specific status→line logic lives here.
2987
+ * Extracted so `WorkspaceHoverContent` in `Rows.tsx` only needs to map the returned
2988
+ * array — the fork-specific status→line logic lives here.
1816
2989
  *
1817
2990
  * 按设置过滤:关掉的来源不出现在明细行里;`showHoverDetails=false` 时整段返回空。
2991
+ * 明细行的顺序与徽标**完全一致**(外部来源排在会话状态之后、已完成之前):两处顺序
2992
+ * 不一样会让人怀疑它们是两套事实。
1818
2993
  */
1819
- function hoverStatusLines(status, t, settings) {
2994
+ function hoverStatusLines(status, t, settings, now = Date.now()) {
1820
2995
  const lines = [];
1821
2996
  if (status === void 0 || !settings.showHoverDetails) return lines;
1822
2997
  if (settings.showPendingInBadge) {
@@ -1843,20 +3018,146 @@ window.__ModuleLoader__.load({
1843
3018
  text: t(status.subagents === 1 ? "status.subagentsRunning.one" : "status.subagentsRunning.other", { n: status.subagents })
1844
3019
  });
1845
3020
  }
1846
- if (settings.showWaitingReviewInBadge && status.waitingReviewCount > 0) lines.push({
1847
- state: "warning",
1848
- text: `${status.waitingReviewCount} ${t("status.waitingReview")}`
1849
- });
1850
- if (settings.showScheduledInBadge && status.scheduledSessions > 0) lines.push({
1851
- state: "warning",
1852
- text: `${status.scheduledSessions} ${t("status.scheduled")}`
1853
- });
3021
+ lines.push(...facetDetailLines(status, t, now));
1854
3022
  if (settings.showCompletedInBadge && status.done > 0) lines.push({
1855
3023
  state: "done",
1856
3024
  text: `${status.done} ${t("status.completed")}`
1857
3025
  });
1858
3026
  return lines;
1859
3027
  }
3028
+ /**
3029
+ * 某一条外部来源的元素该带哪只 class(#22)。
3030
+ *
3031
+ * 脉冲判据仍由各 facet 自己声明(`FacetFold.pulse`),但**作用对象必须下沉到声明它的那个
3032
+ * 元素**。此前 `groupBadgeIsUrgent()` 只返回一个整徽标的布尔值、class 加在容器上,于是同组
3033
+ * 里任何一家声明 `pulse: true` 都会让别家本该静止的图标跟着呼吸:`pulse: false` 是否生效,
3034
+ * 取决于同组里恰好有没有别人开了闪。判断交出去了,作用对象没交出去——这是修过一次的回归
3035
+ * (见下)。
3036
+ *
3037
+ * 保留这条历史的原因写在所有者那边:dsh-gitea-dispatch 把 `overdue` 画成**静止**(其
3038
+ * `lib/waiting-glyph.js`:「超时后停动画:静止即不再期待」),且它只代表 CI 窗口到期、
3039
+ * 不代表评审失败。同一条等待在行内静止、在父级闪动,就是同一事实的两种说法。
3040
+ */
3041
+ function facetPulseClass(entry, urgentClass) {
3042
+ return entry.fold.pulse ? urgentClass : void 0;
3043
+ }
3044
+ /**
3045
+ * 一个来源的图标。
3046
+ *
3047
+ * 归 provider 画(`facet.renderIcon`):「这个状态长什么样」是它的知识。没实现时本插件
3048
+ * 按色档退回一个通用点(见 `rows/GroupBadge.tsx`),保证陌生来源仍然可见、只是朴素。
3049
+ */
3050
+ function facetGlyph(entry, t, now) {
3051
+ return entry.facet.renderIcon?.(entry.fold, {
3052
+ now,
3053
+ t: asFacetTranslate(t)
3054
+ });
3055
+ }
3056
+ //#endregion
3057
+ //#region \0dsh-css:src/client/rows/Rows.module.css.mjs
3058
+ const css$3 = "._1Jb0BW_projectRow,._1Jb0BW_sessionRow{border-radius:var(--dsw-radius-md);padding:0 8px;cursor:pointer;user-select:none;color:var(--dsw-alias-label-primary);align-items:center;gap:6px;padding-inline-start:calc(8px + var(--dsh-workspace-indent,0px));display:flex}._1Jb0BW_projectRow:hover,._1Jb0BW_sessionRow:hover,._1Jb0BW_sessionRow._1Jb0BW_selected{background:var(--dsw-alias-interactive-bg-hover)}._1Jb0BW_searchResultRow{box-sizing:border-box;border-radius:var(--dsw-radius-lg);cursor:pointer;text-align:left;width:100%;min-height:48px;color:var(--dsw-alias-label-primary);background:0 0;border:none;flex-direction:column;align-items:stretch;padding:4px 8px;display:flex}._1Jb0BW_searchResultRow:hover,._1Jb0BW_searchResultRow._1Jb0BW_selected{background:var(--dsw-alias-interactive-bg-hover)}._1Jb0BW_searchResultHeading{align-items:center;min-width:0;display:flex}._1Jb0BW_searchResultTitle{text-overflow:ellipsis;white-space:nowrap;flex:0 auto;min-width:0;margin-left:4px;font-size:14px;line-height:20px;overflow:hidden}._1Jb0BW_searchResultMeta{align-items:center;gap:6px;min-width:0;margin-left:20px;display:flex}._1Jb0BW_searchResultWorkspace,._1Jb0BW_searchResultSnippet{text-overflow:ellipsis;white-space:nowrap;font-size:12px;line-height:17px;overflow:hidden}._1Jb0BW_searchResultWorkspace{max-width:40%;color:var(--dsw-alias-label-tertiary);flex:none}._1Jb0BW_searchResultWorkspace:only-child{max-width:100%}._1Jb0BW_searchResultSnippet{min-width:0;color:var(--dsw-alias-label-secondary);flex:1}._1Jb0BW_projectRow{box-sizing:border-box;align-items:center;height:34px}._1Jb0BW_projectRow ._1Jb0BW_rowActions{height:20px}._1Jb0BW_sessionRow{gap:0;height:32px}._1Jb0BW_sessionRow ._1Jb0BW_title{margin:0 6px 0 4px}._1Jb0BW_slot{width:16px;height:20px;color:var(--dsw-alias-label-tertiary);flex:none;justify-content:center;align-items:center;display:inline-flex}._1Jb0BW_visuallyHidden{clip:rect(0 0 0 0);white-space:nowrap;width:1px;height:1px;position:absolute;overflow:hidden}._1Jb0BW_folderActive{color:var(--dsw-alias-state-business-primary)}._1Jb0BW_projectRow ._1Jb0BW_chevron{display:none}._1Jb0BW_projectRow:hover ._1Jb0BW_chevron{display:inline-flex}._1Jb0BW_projectRow:hover ._1Jb0BW_folder{display:none}._1Jb0BW_arrow{transition:transform .15s var(--ds-ease-in-out)}._1Jb0BW_arrowOpen{transform:rotate(90deg)}._1Jb0BW_projectText{flex-direction:column;flex:1;gap:2px;min-width:0;display:flex}._1Jb0BW_title{text-overflow:ellipsis;white-space:nowrap;min-width:0;font-size:14px;line-height:20px;overflow:hidden}._1Jb0BW_renameInput{border:.5px solid var(--dsw-alias-border-l4);border-radius:var(--dsw-radius-sm);background:var(--dsw-alias-button-elevated-fill);min-width:0;color:inherit;outline:none;padding:0 2px;font-size:14px;line-height:20px}._1Jb0BW_sessionRow ._1Jb0BW_title{flex:1}._1Jb0BW_sessionRow ._1Jb0BW_title[data-scrolled]{mask-image:linear-gradient(90deg,#0000,#000 12px)}._1Jb0BW_sessionRow ._1Jb0BW_title[data-clipped]{mask-image:linear-gradient(270deg,#0000,#000 12px)}._1Jb0BW_sessionRow ._1Jb0BW_title[data-scrolled][data-clipped]{mask-image:linear-gradient(90deg,#0000,#000 12px calc(100% - 12px),#0000)}._1Jb0BW_meta{text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:20px;overflow:hidden}._1Jb0BW_time{color:var(--dsw-alias-label-tertiary);flex:none;font-size:10px;line-height:16px}._1Jb0BW_pinIndicator{width:16px;height:20px;color:var(--dsw-alias-label-caption);flex:none;justify-content:center;align-items:center;margin-left:6px;display:inline-flex}._1Jb0BW_sessionRow._1Jb0BW_archived ._1Jb0BW_title,._1Jb0BW_searchResultRow._1Jb0BW_archived ._1Jb0BW_searchResultTitle,._1Jb0BW_searchResultRow._1Jb0BW_archived ._1Jb0BW_searchResultWorkspace,._1Jb0BW_searchResultRow._1Jb0BW_archived ._1Jb0BW_searchResultSnippet{color:var(--dsw-alias-label-caption)}._1Jb0BW_dot{flex:none}._1Jb0BW_rowActions{flex:none;align-items:center;gap:10px;display:none}._1Jb0BW_projectRow:hover ._1Jb0BW_rowActions,._1Jb0BW_sessionRow:hover ._1Jb0BW_rowActions,._1Jb0BW_searchResultRow:hover ._1Jb0BW_rowActions,._1Jb0BW_projectRow._1Jb0BW_menuOpen ._1Jb0BW_rowActions,._1Jb0BW_sessionRow._1Jb0BW_menuOpen ._1Jb0BW_rowActions{display:inline-flex}._1Jb0BW_searchResultHeading ._1Jb0BW_rowActions{margin-left:auto}._1Jb0BW_sessionRow:hover ._1Jb0BW_time,._1Jb0BW_sessionRow._1Jb0BW_menuOpen ._1Jb0BW_time,._1Jb0BW_sessionRow:hover ._1Jb0BW_pinIndicator,._1Jb0BW_sessionRow._1Jb0BW_menuOpen ._1Jb0BW_pinIndicator{display:none}@media (hover:hover){._1Jb0BW_sessionRow:hover ._1Jb0BW_title,._1Jb0BW_sessionRow._1Jb0BW_menuOpen ._1Jb0BW_title{text-overflow:clip}}._1Jb0BW_projectRow._1Jb0BW_menuOpen,._1Jb0BW_sessionRow._1Jb0BW_menuOpen{background:var(--dsw-alias-interactive-bg-hover)}._1Jb0BW_sessionRow._1Jb0BW_dropBefore,._1Jb0BW_sessionRow._1Jb0BW_dropAfter{position:relative}._1Jb0BW_sessionRow._1Jb0BW_dropBefore:before,._1Jb0BW_sessionRow._1Jb0BW_dropAfter:after{content:\"\";z-index:1;background:linear-gradient(55deg, transparent calc(50% - 1px), var(--dsw-alias-state-business-primary) calc(50% - 1px) calc(50% + 1px), transparent calc(50% + 1px)) 0 0 / 5px 7px no-repeat, linear-gradient(125deg, transparent calc(50% - 1px), var(--dsw-alias-state-business-primary) calc(50% - 1px) calc(50% + 1px), transparent calc(50% + 1px)) 0 5px / 5px 7px no-repeat, linear-gradient(var(--dsw-alias-state-business-primary) 0 0) 4px 5px / calc(100% - 4px) 2px no-repeat;pointer-events:none;height:12px;position:absolute;left:0;right:4px}._1Jb0BW_sessionRow._1Jb0BW_dropBefore:before{top:-7px}._1Jb0BW_sessionRow._1Jb0BW_dropAfter:after{bottom:-7px}._1Jb0BW_hoverContent{flex-direction:column;gap:8px;display:flex}._1Jb0BW_hoverTitle{color:var(--dsw-alias-label-primary);overflow-wrap:break-word;font-size:14px;line-height:20px}._1Jb0BW_hoverPath{color:var(--dsw-alias-label-secondary);word-break:break-all;font-size:12px;line-height:16px}._1Jb0BW_hoverTime{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:16px}._1Jb0BW_hoverStatus{color:var(--dsw-alias-label-tertiary);align-items:center;gap:8px;font-size:12px;line-height:20px;display:flex}._1Jb0BW_groupBadge{flex:none;align-items:center;gap:3px;margin-inline-start:4px;display:inline-flex}._1Jb0BW_groupBadgeCount{color:var(--dsw-alias-label-tertiary);border:1px solid;border-radius:6px;padding:0 3px;font-size:9px;line-height:12px}@keyframes _1Jb0BW_groupBadgePulse{0%,to{opacity:1}50%{opacity:.6}}._1Jb0BW_groupBadgeUrgent{animation:2s ease-in-out infinite _1Jb0BW_groupBadgePulse}._1Jb0BW_hoverArchived svg{flex-shrink:0;margin:0 -4px}._1Jb0BW_iconButton{border-radius:var(--dsw-radius-xs);cursor:pointer;width:16px;height:16px;color:var(--dsw-alias-label-tertiary);background:0 0;border:none;flex:none;justify-content:center;align-items:center;padding:0;display:inline-flex}._1Jb0BW_iconButton:hover{color:var(--dsw-alias-label-primary)}._1Jb0BW_chevron{color:var(--dsw-alias-label-caption)}@media (prefers-reduced-motion:reduce){._1Jb0BW_arrow{transition:none;animation:none}}";
3059
+ const tagId$3 = "dsh-group-status/Rows.module.css";
3060
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$3) + "]") === null) {
3061
+ const tag = document.createElement("style");
3062
+ tag.dataset.plugin = "dsh-group-status";
3063
+ tag.dataset.pluginCss = tagId$3;
3064
+ tag.textContent = css$3;
3065
+ document.head.appendChild(tag);
3066
+ }
3067
+ var Rows_module_css_default = {
3068
+ "archived": "_1Jb0BW_archived",
3069
+ "arrow": "_1Jb0BW_arrow",
3070
+ "arrowOpen": "_1Jb0BW_arrowOpen",
3071
+ "chevron": "_1Jb0BW_chevron",
3072
+ "dot": "_1Jb0BW_dot",
3073
+ "dropAfter": "_1Jb0BW_dropAfter",
3074
+ "dropBefore": "_1Jb0BW_dropBefore",
3075
+ "folder": "_1Jb0BW_folder",
3076
+ "folderActive": "_1Jb0BW_folderActive",
3077
+ "groupBadge": "_1Jb0BW_groupBadge",
3078
+ "groupBadgeCount": "_1Jb0BW_groupBadgeCount",
3079
+ "groupBadgePulse": "_1Jb0BW_groupBadgePulse",
3080
+ "groupBadgeUrgent": "_1Jb0BW_groupBadgeUrgent",
3081
+ "hoverArchived": "_1Jb0BW_hoverArchived",
3082
+ "hoverContent": "_1Jb0BW_hoverContent",
3083
+ "hoverPath": "_1Jb0BW_hoverPath",
3084
+ "hoverStatus": "_1Jb0BW_hoverStatus",
3085
+ "hoverTime": "_1Jb0BW_hoverTime",
3086
+ "hoverTitle": "_1Jb0BW_hoverTitle",
3087
+ "iconButton": "_1Jb0BW_iconButton",
3088
+ "menuOpen": "_1Jb0BW_menuOpen",
3089
+ "meta": "_1Jb0BW_meta",
3090
+ "pinIndicator": "_1Jb0BW_pinIndicator",
3091
+ "projectRow": "_1Jb0BW_projectRow",
3092
+ "projectText": "_1Jb0BW_projectText",
3093
+ "renameInput": "_1Jb0BW_renameInput",
3094
+ "rowActions": "_1Jb0BW_rowActions",
3095
+ "searchResultHeading": "_1Jb0BW_searchResultHeading",
3096
+ "searchResultMeta": "_1Jb0BW_searchResultMeta",
3097
+ "searchResultRow": "_1Jb0BW_searchResultRow",
3098
+ "searchResultSnippet": "_1Jb0BW_searchResultSnippet",
3099
+ "searchResultTitle": "_1Jb0BW_searchResultTitle",
3100
+ "searchResultWorkspace": "_1Jb0BW_searchResultWorkspace",
3101
+ "selected": "_1Jb0BW_selected",
3102
+ "sessionRow": "_1Jb0BW_sessionRow",
3103
+ "slot": "_1Jb0BW_slot",
3104
+ "time": "_1Jb0BW_time",
3105
+ "title": "_1Jb0BW_title",
3106
+ "visuallyHidden": "_1Jb0BW_visuallyHidden"
3107
+ };
3108
+ //#endregion
3109
+ //#region src/client/rows/GroupBadge.tsx
3110
+ /**
3111
+ * Aggregated live-status badge for a folded folder row. Dots reuse StateDot so
3112
+ * colors match session rows (warning = pending, ongoing = running, done = completed).
3113
+ * Renders nothing while expanded or when nothing is pending/active/completed or any
3114
+ * status source reports a contribution.
3115
+ *
3116
+ * Count matches the dots: host-side sessions plus every registered facet's own count.
3117
+ *
3118
+ * `settings` 按来源过滤:关掉的来源不画点、不计数、不进 aria-label、不参与「是否显示
3119
+ * 徽标」的判断。会话行自身的状态点不受影响:那是行级状态,不是分组提示。**外部**来源的
3120
+ * 过滤发生在折叠之前(`WorkspaceBrowser` 按 `facet.settingKey` 筛登记面),所以到这里
3121
+ * 的折果已经是"该显示的",label 与视觉不可能各说一套。
3122
+ *
3123
+ * `now` 缺省取渲染时刻(与 `WorkspaceBrowser` 为相对时间读的时钟同一姿态):文案里的
3124
+ * 剩余时长以此计算,只在提供方 store 通知时随渲染更新。
3125
+ */
3126
+ function groupLiveBadge(group, t, settings, now = Date.now()) {
3127
+ const s = group.liveStatus;
3128
+ if (group.expanded || s === void 0) return null;
3129
+ const pending = settings.showPendingInBadge ? s.approval + s.planReview + s.question : 0;
3130
+ const active = settings.showRunningInBadge ? s.running + s.subagents : 0;
3131
+ const completed = settings.showCompletedInBadge ? s.done : 0;
3132
+ const facetTotal = facetCountTotal(s);
3133
+ if (pending === 0 && active === 0 && completed === 0 && facetTotal === 0) return null;
3134
+ const label = groupLiveLabel(s, t, settings, now);
3135
+ const dots = [];
3136
+ if (pending > 0) dots.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state: "warning" }, "warn"));
3137
+ if (active > 0) dots.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state: "ongoing" }, "run"));
3138
+ const urgentClass = Rows_module_css_default.groupBadgeUrgent ?? "";
3139
+ for (const entry of s.facets.values()) {
3140
+ const glyph = facetGlyph(entry, t, now);
3141
+ const pulseClass = facetPulseClass(entry, urgentClass) || void 0;
3142
+ dots.push(glyph === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state: entry.fold.tone === "info" ? "ongoing" : "warning" }, entry.facet.id) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3143
+ className: pulseClass,
3144
+ children: glyph
3145
+ }, entry.facet.id));
3146
+ }
3147
+ if (completed > 0) dots.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state: "done" }, "done"));
3148
+ const count = pending + active + completed + facetTotal;
3149
+ const countLabel = count > 99 ? "99+" : count >= settings.countThreshold ? String(count) : "";
3150
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3151
+ className: Rows_module_css_default.groupBadge,
3152
+ role: "img",
3153
+ "aria-label": label,
3154
+ title: label,
3155
+ children: [dots, countLabel && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3156
+ className: Rows_module_css_default.groupBadgeCount,
3157
+ children: countLabel
3158
+ })]
3159
+ });
3160
+ }
1860
3161
  //#endregion
1861
3162
  //#region src/client/rows/Rows.tsx
1862
3163
  /**
@@ -1982,8 +3283,8 @@ window.__ModuleLoader__.load({
1982
3283
  })} ${pad2(d.getHours())}:${pad2(d.getMinutes())}` });
1983
3284
  }
1984
3285
  /** Hover-card body: workspace title, display directory path, absolute creation time. */
1985
- function WorkspaceHoverContent({ label, cwd, createdAt, status, settings, t }) {
1986
- const lines = hoverStatusLines(status, t, settings);
3286
+ function WorkspaceHoverContent({ label, cwd, createdAt, status, settings, now, t }) {
3287
+ const lines = hoverStatusLines(status, t, settings, now);
1987
3288
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1988
3289
  className: Rows_module_css_default.hoverContent,
1989
3290
  children: [
@@ -2001,7 +3302,7 @@ window.__ModuleLoader__.load({
2001
3302
  }),
2002
3303
  lines.map((line) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2003
3304
  className: Rows_module_css_default.hoverStatus,
2004
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state: line.state }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: line.text })]
3305
+ children: [line.glyph ?? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state: line.state }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: line.text })]
2005
3306
  }, line.text))
2006
3307
  ]
2007
3308
  });
@@ -2025,7 +3326,7 @@ window.__ModuleLoader__.load({
2025
3326
  * @param props.t - the browser root's locale seat.
2026
3327
  * @returns the row element.
2027
3328
  */
2028
- function ProjectRowItem({ group, containsCurrentDescendant = false, onToggle, onCreate, actions, drag, home, newShortcut, settings, t }) {
3329
+ function ProjectRowItem({ group, containsCurrentDescendant = false, onToggle, onCreate, actions, drag, home, newShortcut, settings, now, t }) {
2029
3330
  const row = group;
2030
3331
  const label = row.workspaceId === void 0 ? t("group.ungrouped") : row.label;
2031
3332
  const active = containsCurrentDescendant || group.expanded && group.containsCurrent;
@@ -2069,7 +3370,7 @@ window.__ModuleLoader__.load({
2069
3370
  children: label
2070
3371
  })
2071
3372
  }),
2072
- groupLiveBadge(group, t, settings),
3373
+ groupLiveBadge(group, t, settings, now),
2073
3374
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2074
3375
  className: Rows_module_css_default.rowActions,
2075
3376
  children: [actions !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
@@ -2103,6 +3404,7 @@ window.__ModuleLoader__.load({
2103
3404
  side: "bottom",
2104
3405
  align: "end",
2105
3406
  delayMs: 500,
3407
+ portal: true,
2106
3408
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2107
3409
  type: "button",
2108
3410
  className: Rows_module_css_default.iconButton,
@@ -2127,9 +3429,11 @@ window.__ModuleLoader__.load({
2127
3429
  createdAt: row.createdAt,
2128
3430
  status: row.expanded ? void 0 : row.liveStatus,
2129
3431
  settings,
3432
+ now: now ?? Date.now(),
2130
3433
  t
2131
3434
  }),
2132
3435
  openDelayMs: 800,
3436
+ grouped: true,
2133
3437
  disabled: menuOpen,
2134
3438
  copyText: row.cwd,
2135
3439
  copyLabel: t("copy"),
@@ -2284,6 +3588,7 @@ window.__ModuleLoader__.load({
2284
3588
  side: "bottom",
2285
3589
  align: "end",
2286
3590
  delayMs: 500,
3591
+ portal: true,
2287
3592
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2288
3593
  type: "button",
2289
3594
  className: Rows_module_css_default.iconButton,
@@ -2438,6 +3743,7 @@ window.__ModuleLoader__.load({
2438
3743
  t
2439
3744
  }),
2440
3745
  openDelayMs: 800,
3746
+ grouped: true,
2441
3747
  disabled: menuOpen || drag?.active === true,
2442
3748
  copyText: row.blank || row.title === "" ? void 0 : row.title,
2443
3749
  copyLabel: t("copy"),
@@ -2445,8 +3751,8 @@ window.__ModuleLoader__.load({
2445
3751
  });
2446
3752
  }
2447
3753
  //#endregion
2448
- //#region \0dsh-css:/home/wujue/workspace/dsh-plugins/dsh-group-status/src/client/rows/AnimatedRows.module.css.mjs
2449
- const css$2 = ".dYnUQG_exits{contain:strict;pointer-events:none;position:absolute;inset:0;overflow:clip}";
3754
+ //#region \0dsh-css:src/client/rows/AnimatedRows.module.css.mjs
3755
+ const css$2 = "._8TnAsq_exits{contain:strict;pointer-events:none;position:absolute;inset:0;overflow:clip}";
2450
3756
  const tagId$2 = "dsh-group-status/AnimatedRows.module.css";
2451
3757
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$2) + "]") === null) {
2452
3758
  const tag = document.createElement("style");
@@ -2455,7 +3761,7 @@ window.__ModuleLoader__.load({
2455
3761
  tag.textContent = css$2;
2456
3762
  document.head.appendChild(tag);
2457
3763
  }
2458
- var AnimatedRows_module_css_default = { "exits": "dYnUQG_exits" };
3764
+ var AnimatedRows_module_css_default = { "exits": "_8TnAsq_exits" };
2459
3765
  //#endregion
2460
3766
  //#region src/client/rows/AnimatedRows.tsx
2461
3767
  /** React-commit-driven movement and entry/exit fades for the sidebar's keyed rows. */
@@ -2621,8 +3927,8 @@ window.__ModuleLoader__.load({
2621
3927
  }
2622
3928
  };
2623
3929
  //#endregion
2624
- //#region \0dsh-css:/home/wujue/workspace/dsh-plugins/dsh-group-status/src/client/WorkspacePicker.module.css.mjs
2625
- const css$1 = ".ff8vyq_modalAction{min-width:72px}.ff8vyq_modalError,.ff8vyq_menuStatus{margin-top:8px;font-size:12px;line-height:18px}.ff8vyq_modalError{color:var(--dsw-alias-state-error-primary)}.ff8vyq_menuStatus{color:var(--dsw-alias-label-secondary)}";
3930
+ //#region \0dsh-css:src/client/WorkspacePicker.module.css.mjs
3931
+ const css$1 = ".S_JYgq_modalAction{min-width:72px}.S_JYgq_modalError,.S_JYgq_menuStatus{margin-top:8px;font-size:12px;line-height:18px}.S_JYgq_modalError{color:var(--dsw-alias-state-error-primary)}.S_JYgq_menuStatus{color:var(--dsw-alias-label-secondary)}";
2626
3932
  const tagId$1 = "dsh-group-status/WorkspacePicker.module.css";
2627
3933
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
2628
3934
  const tag = document.createElement("style");
@@ -2632,9 +3938,9 @@ window.__ModuleLoader__.load({
2632
3938
  document.head.appendChild(tag);
2633
3939
  }
2634
3940
  var WorkspacePicker_module_css_default = {
2635
- "menuStatus": "ff8vyq_menuStatus",
2636
- "modalAction": "ff8vyq_modalAction",
2637
- "modalError": "ff8vyq_modalError"
3941
+ "menuStatus": "S_JYgq_menuStatus",
3942
+ "modalAction": "S_JYgq_modalAction",
3943
+ "modalError": "S_JYgq_modalError"
2638
3944
  };
2639
3945
  //#endregion
2640
3946
  //#region src/client/WorkspacePicker.tsx
@@ -2794,8 +4100,8 @@ window.__ModuleLoader__.load({
2794
4100
  });
2795
4101
  }
2796
4102
  //#endregion
2797
- //#region \0dsh-css:/home/wujue/workspace/dsh-plugins/dsh-group-status/src/client/rows/WorkspaceBrowser.module.css.mjs
2798
- const css = ".q9WRdW_root{--dsh-session-list-edge-inset:var(--dsh-sidebar-inline-padding);--dsh-session-list-scrollbar-width:5px;--dsh-session-list-scrollbar-offset:2px;box-sizing:border-box;min-height:0;padding-right:var(--dsh-session-list-edge-inset);flex-direction:column;flex:1;display:flex}.q9WRdW_root.q9WRdW_rail{padding-right:0}.q9WRdW_iconButton{border-radius:var(--dsw-radius-sm);cursor:pointer;width:28px;height:28px;color:var(--dsw-alias-label-secondary);background:0 0;border:none;flex:none;justify-content:center;align-items:center;padding:0;display:inline-flex}.q9WRdW_iconButton:focus-visible,.q9WRdW_searchButton:focus-visible,.q9WRdW_clearButton:focus-visible{outline:var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary));outline-offset:-2px}.q9WRdW_iconButton:hover{background:var(--dsw-alias-interactive-bg-hover)}.q9WRdW_viewOptionsMenu{min-width:200px}.q9WRdW_sectionHeader{box-sizing:border-box;border-radius:var(--dsw-radius-md);height:36px;color:var(--dsw-alias-label-tertiary);flex:none;justify-content:flex-end;align-items:center;gap:4px;margin-bottom:4px;padding-left:4px;display:flex;overflow:hidden}.q9WRdW_root:not(.q9WRdW_rail) .q9WRdW_sectionHeader{margin-top:2px;margin-right:-4px}.q9WRdW_sectionLabel{white-space:nowrap;opacity:1;visibility:visible;min-width:0;max-width:45%;transition:max-width .18s var(--ds-ease-in-out), margin-right .18s var(--ds-ease-in-out), opacity .12s var(--ds-ease-in-out), transform .18s var(--ds-ease-in-out), visibility 0s linear;flex:none;line-height:20px;overflow:hidden}.q9WRdW_sectionLabelHidden{opacity:0;visibility:hidden;max-width:0;margin-right:-4px;transition-delay:0s,0s,0s,0s,.18s;transform:translate(-4px)}.q9WRdW_searchSlot{box-sizing:border-box;min-width:0;max-width:28px;transition:max-width .18s var(--ds-ease-in-out), padding-left .18s var(--ds-ease-in-out);flex:1;align-items:center;margin-left:auto;padding-left:0;display:flex}.q9WRdW_searchSlotExpanded{max-width:100%;padding-left:0}.q9WRdW_headerActions{opacity:1;visibility:visible;max-width:60px;transition:max-width .18s var(--ds-ease-in-out), opacity .12s var(--ds-ease-in-out), transform .18s var(--ds-ease-in-out), visibility 0s linear;flex:none;align-items:center;gap:4px;display:flex;overflow:hidden}.q9WRdW_headerActionsHidden{opacity:0;visibility:hidden;pointer-events:none;max-width:0;transition-delay:0s,0s,0s,.18s;transform:translate(4px)}.q9WRdW_search{box-sizing:border-box;border-radius:var(--dsw-radius-sm);cursor:text;width:100%;height:28px;color:var(--dsw-alias-label-secondary);transition:width .18s var(--ds-ease-in-out), padding .18s var(--ds-ease-in-out), border-color .18s var(--ds-ease-in-out), background-color .18s var(--ds-ease-in-out);background:0 0;border:none;flex:none;align-items:center;gap:0;margin:0;padding:0;display:flex;overflow:hidden}.q9WRdW_searchExpanded{border:.5px solid var(--dsw-alias-border-l4);border-radius:var(--dsw-radius-md);width:calc(100% + 4px);height:30px;color:var(--dsw-alias-label-caption);background:0 0;margin-inline:-2px;padding:0 4px 0 0}.q9WRdW_searchButton{border-radius:var(--dsw-radius-sm);cursor:pointer;width:28px;height:28px;color:inherit;background:0 0;border:none;flex:none;justify-content:center;align-items:center;padding:0;display:inline-flex}.q9WRdW_searchExpanded .q9WRdW_searchButton{width:28px;height:30px}.q9WRdW_searchButton:hover{background:var(--dsw-alias-interactive-bg-hover)}.q9WRdW_searchExpanded .q9WRdW_searchButton:hover{background:0 0}.q9WRdW_searchInput{opacity:0;pointer-events:none;width:0;min-width:0;color:var(--dsw-alias-label-primary);transition:opacity .12s var(--ds-ease-in-out);background:0 0;border:none;outline:none;flex:1;font-size:13px;line-height:18px}.q9WRdW_searchExpanded .q9WRdW_searchInput{opacity:1;pointer-events:auto;margin-left:-2px}.q9WRdW_searchInput::placeholder{color:var(--dsw-alias-label-tertiary)}.q9WRdW_clearButton{border-radius:var(--dsw-radius-sm);cursor:pointer;width:24px;height:24px;color:var(--dsw-alias-label-secondary);background:0 0;border:none;flex:none;justify-content:center;align-items:center;padding:0;display:inline-flex}.q9WRdW_clearButton:hover{background:var(--dsw-alias-interactive-bg-hover)}.q9WRdW_rail .q9WRdW_sectionHeader{justify-content:flex-start;gap:0;margin-bottom:12px;padding-left:0}.q9WRdW_rail .q9WRdW_headerActions{max-width:none}.q9WRdW_rail .q9WRdW_iconButton{border-radius:var(--dsw-radius-md);width:36px;height:36px;color:var(--dsw-alias-label-primary)}.q9WRdW_rail .q9WRdW_search{border-radius:var(--dsw-radius-md);background:0 0;border-color:#0000;gap:0;width:36px;height:36px;margin:0 0 12px;padding:0}.q9WRdW_rail .q9WRdW_searchButton{border-radius:var(--dsw-radius-md);width:36px;height:36px;color:var(--dsw-alias-label-primary)}.q9WRdW_rail .q9WRdW_searchButton:hover{background:var(--dsw-alias-interactive-bg-hover)}.q9WRdW_listArea{min-height:0;margin-left:-4px;margin-right:calc(-1 * var(--dsh-session-list-edge-inset));flex-direction:column;flex:1;padding-left:4px;display:flex;overflow:visible}.q9WRdW_rail .q9WRdW_listArea{margin-left:0;margin-right:0;padding-left:0}.q9WRdW_treeBody{flex-direction:column;flex:1;min-height:0;display:flex;position:relative}.q9WRdW_fade{left:0;right:var(--dsh-session-list-edge-inset);background:linear-gradient(to bottom, transparent, var(--dsw-specific-sidebar-fill));pointer-events:none;height:24px;position:absolute;bottom:0}[data-platform=darwin] .q9WRdW_fade{display:none}.q9WRdW_wide{animation:q9WRdW_wide-in .2s var(--ds-ease-in-out)}@keyframes q9WRdW_wide-in{0%{opacity:0}}.q9WRdW_list{min-height:0;margin-left:-4px;margin-right:var(--dsh-session-list-scrollbar-offset);padding-left:4px;padding-right:calc(var(--dsh-session-list-edge-inset) - var(--dsh-session-list-scrollbar-width) - var(--dsh-session-list-scrollbar-offset));scrollbar-gutter:stable;flex:1;padding-bottom:16px;overflow-y:auto}.q9WRdW_flatList>*+*,.q9WRdW_searchTree>[role=treeitem]+[role=treeitem],.q9WRdW_groupSection>*+*{margin-top:2px}.q9WRdW_searchStatus{color:var(--dsw-alias-label-tertiary);padding:10px 12px;font-size:12px;line-height:18px}.q9WRdW_skeletonRow{box-sizing:border-box;align-items:flex-start;gap:8px;min-height:48px;padding:6px 8px 7px;display:flex}.q9WRdW_skeletonDot{corner-shape:round;border-radius:50%;flex:none;width:16px;height:16px}.q9WRdW_skeletonBars{flex-direction:column;flex:1;gap:6px;min-width:0;display:flex}.q9WRdW_skeletonDot,.q9WRdW_skeletonBar{background:var(--dsw-alias-bg-skeleton);animation:2s cubic-bezier(.36,0,.64,1) infinite q9WRdW_search-skeleton}.q9WRdW_skeletonBar{border-radius:var(--dsw-radius-xs);width:65%;height:16px}.q9WRdW_skeletonBarWide{width:90%;height:13px}@keyframes q9WRdW_search-skeleton{0%{opacity:1}40%{opacity:.6}80%,to{opacity:1}}.q9WRdW_groupSection{position:relative}.q9WRdW_groupSection+.q9WRdW_groupSection{margin-top:4px}.q9WRdW_listTopDropIndicator,.q9WRdW_workspaceDropBefore:before,.q9WRdW_workspaceDropAfter:after{content:\"\";z-index:1;background:linear-gradient(55deg, transparent calc(50% - 1px), var(--dsw-alias-state-business-primary) calc(50% - 1px) calc(50% + 1px), transparent calc(50% + 1px)) 0 0 / 5px 7px no-repeat, linear-gradient(125deg, transparent calc(50% - 1px), var(--dsw-alias-state-business-primary) calc(50% - 1px) calc(50% + 1px), transparent calc(50% + 1px)) 0 5px / 5px 7px no-repeat, linear-gradient(var(--dsw-alias-state-business-primary) 0 0) 4px 5px / calc(100% - 4px) 2px no-repeat;pointer-events:none;height:12px;position:absolute;left:0;right:0}.q9WRdW_listTopDropIndicator{top:-8px;left:0;right:var(--dsh-session-list-edge-inset)}.q9WRdW_listTopDropActive>.q9WRdW_workspaceDropBefore:first-child:before{display:none}.q9WRdW_workspaceDropBefore:before{top:-8px}.q9WRdW_workspaceDropAfter:after{bottom:-8px}.q9WRdW_sessionOverflowButton{border-radius:var(--dsw-radius-sm);width:100%;height:28px;padding:0 12px 0 calc(28px + var(--dsh-workspace-indent,0px));cursor:pointer;text-align:left;color:var(--dsw-alias-label-tertiary);background:0 0;border:none;font-size:12px}.q9WRdW_groupSection>.q9WRdW_sessionOverflowButton{margin-top:0}.q9WRdW_sessionOverflowButton:hover{color:var(--dsw-alias-label-secondary);background:0 0}.q9WRdW_empty{color:var(--dsw-alias-label-tertiary);padding:16px 12px;font-size:13px}.q9WRdW_emptyState{color:var(--dsw-alias-label-tertiary);flex-direction:column;align-items:center;gap:8px;margin-top:80px;padding:0 12px;font-size:13px;line-height:20px;display:flex}.q9WRdW_emptyState>svg{color:var(--dsw-alias-label-caption);margin-bottom:4px}.q9WRdW_emptyAction{cursor:pointer;color:var(--dsw-alias-link);background:0 0;border:none;padding:0;font-size:13px;line-height:20px}.q9WRdW_renameInput{box-sizing:border-box;border:.5px solid var(--dsw-alias-border-l4);border-radius:var(--dsw-radius-lg);width:100%;height:44px;color:var(--dsw-alias-label-primary);background:0 0;outline:none;padding:7px 14px;font-size:14px;font-weight:400;line-height:22px}.q9WRdW_renameInput:disabled{color:var(--dsw-alias-label-dimmed)}.q9WRdW_renameError{color:var(--dsw-alias-state-error-primary);margin-top:8px;font-size:12px;line-height:18px}.q9WRdW_deleteAction:not(:disabled){color:var(--dsw-alias-state-error-primary)}.q9WRdW_deleteStatus{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}.q9WRdW_archiveActivity{color:var(--dsw-alias-label-primary);margin:0 0 8px;padding-left:18px;font-size:13px;line-height:20px}.q9WRdW_archiveActivity li{overflow-wrap:anywhere}@media (prefers-reduced-motion:reduce){.q9WRdW_wide,.q9WRdW_skeletonDot,.q9WRdW_skeletonBar{animation:none}.q9WRdW_search,.q9WRdW_sectionLabel,.q9WRdW_searchSlot,.q9WRdW_searchInput,.q9WRdW_headerActions{transition:none}}";
4103
+ //#region \0dsh-css:src/client/rows/WorkspaceBrowser.module.css.mjs
4104
+ const css = ".QAYkWG_root{--dsh-session-list-edge-inset:var(--dsh-sidebar-inline-padding);--dsh-session-list-scrollbar-width:5px;--dsh-session-list-scrollbar-offset:2px;box-sizing:border-box;min-height:0;padding-right:var(--dsh-session-list-edge-inset);flex-direction:column;flex:1;display:flex}.QAYkWG_root.QAYkWG_rail{padding-right:0}.QAYkWG_iconButton{border-radius:var(--dsw-radius-sm);cursor:pointer;width:28px;height:28px;color:var(--dsw-alias-label-secondary);background:0 0;border:none;flex:none;justify-content:center;align-items:center;padding:0;display:inline-flex}.QAYkWG_iconButton:focus-visible,.QAYkWG_searchButton:focus-visible,.QAYkWG_clearButton:focus-visible{outline:var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary));outline-offset:-2px}.QAYkWG_iconButton:hover{background:var(--dsw-alias-interactive-bg-hover)}.QAYkWG_viewOptionsMenu{min-width:200px}.QAYkWG_sectionHeader{box-sizing:border-box;border-radius:var(--dsw-radius-md);height:36px;color:var(--dsw-alias-label-tertiary);flex:none;justify-content:flex-end;align-items:center;gap:4px;margin-bottom:4px;padding-left:4px;display:flex;overflow:hidden}.QAYkWG_root:not(.QAYkWG_rail) .QAYkWG_sectionHeader{margin-top:2px;margin-right:-4px}.QAYkWG_sectionLabel{white-space:nowrap;opacity:1;visibility:visible;min-width:0;max-width:45%;transition:max-width .18s var(--ds-ease-in-out), margin-right .18s var(--ds-ease-in-out), opacity .12s var(--ds-ease-in-out), transform .18s var(--ds-ease-in-out), visibility 0s linear;flex:none;line-height:20px;overflow:hidden}.QAYkWG_sectionLabelHidden{opacity:0;visibility:hidden;max-width:0;margin-right:-4px;transition-delay:0s,0s,0s,0s,.18s;transform:translate(-4px)}.QAYkWG_searchSlot{box-sizing:border-box;min-width:0;max-width:28px;transition:max-width .18s var(--ds-ease-in-out), padding-left .18s var(--ds-ease-in-out);flex:1;align-items:center;margin-left:auto;padding-left:0;display:flex}.QAYkWG_searchSlotExpanded{max-width:100%;padding-left:0}.QAYkWG_headerActions{opacity:1;visibility:visible;max-width:60px;transition:max-width .18s var(--ds-ease-in-out), opacity .12s var(--ds-ease-in-out), transform .18s var(--ds-ease-in-out), visibility 0s linear;flex:none;align-items:center;gap:4px;display:flex;overflow:hidden}.QAYkWG_headerActionsHidden{opacity:0;visibility:hidden;pointer-events:none;max-width:0;transition-delay:0s,0s,0s,.18s;transform:translate(4px)}.QAYkWG_search{box-sizing:border-box;border-radius:var(--dsw-radius-sm);cursor:text;width:100%;height:28px;color:var(--dsw-alias-label-secondary);transition:width .18s var(--ds-ease-in-out), padding .18s var(--ds-ease-in-out), border-color .18s var(--ds-ease-in-out), background-color .18s var(--ds-ease-in-out);background:0 0;border:none;flex:none;align-items:center;gap:0;margin:0;padding:0;display:flex;overflow:hidden}.QAYkWG_searchExpanded{border:.5px solid var(--dsw-alias-border-l4);border-radius:var(--dsw-radius-md);width:calc(100% + 4px);height:30px;color:var(--dsw-alias-label-caption);background:0 0;margin-inline:-2px;padding:0 4px 0 0}.QAYkWG_searchButton{border-radius:var(--dsw-radius-sm);cursor:pointer;width:28px;height:28px;color:inherit;background:0 0;border:none;flex:none;justify-content:center;align-items:center;padding:0;display:inline-flex}.QAYkWG_searchExpanded .QAYkWG_searchButton{width:28px;height:30px}.QAYkWG_searchButton:hover{background:var(--dsw-alias-interactive-bg-hover)}.QAYkWG_searchExpanded .QAYkWG_searchButton:hover{background:0 0}.QAYkWG_searchInput{opacity:0;pointer-events:none;width:0;min-width:0;color:var(--dsw-alias-label-primary);transition:opacity .12s var(--ds-ease-in-out);background:0 0;border:none;outline:none;flex:1;font-size:13px;line-height:18px}.QAYkWG_searchExpanded .QAYkWG_searchInput{opacity:1;pointer-events:auto;margin-left:-2px}.QAYkWG_searchInput::placeholder{color:var(--dsw-alias-label-tertiary)}.QAYkWG_clearButton{border-radius:var(--dsw-radius-sm);cursor:pointer;width:24px;height:24px;color:var(--dsw-alias-label-secondary);background:0 0;border:none;flex:none;justify-content:center;align-items:center;padding:0;display:inline-flex}.QAYkWG_clearButton:hover{background:var(--dsw-alias-interactive-bg-hover)}.QAYkWG_rail .QAYkWG_sectionHeader{justify-content:flex-start;gap:0;margin-bottom:12px;padding-left:0}.QAYkWG_rail .QAYkWG_headerActions{max-width:none}.QAYkWG_rail .QAYkWG_iconButton{border-radius:var(--dsw-radius-md);width:36px;height:36px;color:var(--dsw-alias-label-primary)}.QAYkWG_rail .QAYkWG_search{border-radius:var(--dsw-radius-md);background:0 0;border-color:#0000;gap:0;width:36px;height:36px;margin:0 0 12px;padding:0}.QAYkWG_rail .QAYkWG_searchButton{border-radius:var(--dsw-radius-md);width:36px;height:36px;color:var(--dsw-alias-label-primary)}.QAYkWG_rail .QAYkWG_searchButton:hover{background:var(--dsw-alias-interactive-bg-hover)}.QAYkWG_listArea{min-height:0;margin-left:-4px;margin-right:calc(-1 * var(--dsh-session-list-edge-inset));flex-direction:column;flex:1;padding-left:4px;display:flex;overflow:visible}.QAYkWG_rail .QAYkWG_listArea{margin-left:0;margin-right:0;padding-left:0}.QAYkWG_treeBody{flex-direction:column;flex:1;min-height:0;display:flex;position:relative}.QAYkWG_fade{left:0;right:var(--dsh-session-list-edge-inset);background:linear-gradient(to bottom, transparent, var(--dsw-specific-sidebar-fill));pointer-events:none;height:24px;position:absolute;bottom:0}[data-platform=darwin] .QAYkWG_fade{display:none}.QAYkWG_wide{animation:QAYkWG_wide-in .2s var(--ds-ease-in-out)}@keyframes QAYkWG_wide-in{0%{opacity:0}}.QAYkWG_list{min-height:0;margin-left:-4px;margin-right:var(--dsh-session-list-scrollbar-offset);padding-left:4px;padding-right:calc(var(--dsh-session-list-edge-inset) - var(--dsh-session-list-scrollbar-width) - var(--dsh-session-list-scrollbar-offset));scrollbar-gutter:stable;flex:1;padding-bottom:16px;overflow-y:auto}.QAYkWG_flatList>*+*,.QAYkWG_searchTree>[role=treeitem]+[role=treeitem],.QAYkWG_groupSection>*+*{margin-top:2px}.QAYkWG_searchStatus{color:var(--dsw-alias-label-tertiary);padding:10px 12px;font-size:12px;line-height:18px}.QAYkWG_skeletonRow{box-sizing:border-box;align-items:flex-start;gap:8px;min-height:48px;padding:6px 8px 7px;display:flex}.QAYkWG_skeletonDot{corner-shape:round;border-radius:50%;flex:none;width:16px;height:16px}.QAYkWG_skeletonBars{flex-direction:column;flex:1;gap:6px;min-width:0;display:flex}.QAYkWG_skeletonDot,.QAYkWG_skeletonBar{background:var(--dsw-alias-bg-skeleton);animation:2s cubic-bezier(.36,0,.64,1) infinite QAYkWG_search-skeleton}.QAYkWG_skeletonBar{border-radius:var(--dsw-radius-xs);width:65%;height:16px}.QAYkWG_skeletonBarWide{width:90%;height:13px}@keyframes QAYkWG_search-skeleton{0%{opacity:1}40%{opacity:.6}80%,to{opacity:1}}.QAYkWG_groupSection{position:relative}.QAYkWG_groupSection+.QAYkWG_groupSection{margin-top:4px}.QAYkWG_listTopDropIndicator,.QAYkWG_workspaceDropBefore:before,.QAYkWG_workspaceDropAfter:after{content:\"\";z-index:1;background:linear-gradient(55deg, transparent calc(50% - 1px), var(--dsw-alias-state-business-primary) calc(50% - 1px) calc(50% + 1px), transparent calc(50% + 1px)) 0 0 / 5px 7px no-repeat, linear-gradient(125deg, transparent calc(50% - 1px), var(--dsw-alias-state-business-primary) calc(50% - 1px) calc(50% + 1px), transparent calc(50% + 1px)) 0 5px / 5px 7px no-repeat, linear-gradient(var(--dsw-alias-state-business-primary) 0 0) 4px 5px / calc(100% - 4px) 2px no-repeat;pointer-events:none;height:12px;position:absolute;left:0;right:0}.QAYkWG_listTopDropIndicator{top:-8px;left:0;right:var(--dsh-session-list-edge-inset)}.QAYkWG_listTopDropActive>.QAYkWG_workspaceDropBefore:first-child:before{display:none}.QAYkWG_workspaceDropBefore:before{top:-8px}.QAYkWG_workspaceDropAfter:after{bottom:-8px}.QAYkWG_sessionOverflowButton{border-radius:var(--dsw-radius-sm);width:100%;height:28px;padding:0 12px 0 calc(28px + var(--dsh-workspace-indent,0px));cursor:pointer;text-align:left;color:var(--dsw-alias-label-tertiary);background:0 0;border:none;font-size:12px}.QAYkWG_groupSection>.QAYkWG_sessionOverflowButton{margin-top:0}.QAYkWG_sessionOverflowButton:hover{color:var(--dsw-alias-label-secondary);background:0 0}.QAYkWG_empty{color:var(--dsw-alias-label-tertiary);padding:16px 12px;font-size:13px}.QAYkWG_emptyState{color:var(--dsw-alias-label-tertiary);flex-direction:column;align-items:center;gap:8px;margin-top:80px;padding:0 12px;font-size:13px;line-height:20px;display:flex}.QAYkWG_emptyState>svg{color:var(--dsw-alias-label-caption);margin-bottom:4px}.QAYkWG_emptyAction{cursor:pointer;color:var(--dsw-alias-link);background:0 0;border:none;padding:0;font-size:13px;line-height:20px}.QAYkWG_renameInput{box-sizing:border-box;border:.5px solid var(--dsw-alias-border-l4);border-radius:var(--dsw-radius-lg);width:100%;height:44px;color:var(--dsw-alias-label-primary);background:0 0;outline:none;padding:7px 14px;font-size:14px;font-weight:400;line-height:22px}.QAYkWG_renameInput:disabled{color:var(--dsw-alias-label-dimmed)}.QAYkWG_renameError{color:var(--dsw-alias-state-error-primary);margin-top:8px;font-size:12px;line-height:18px}.QAYkWG_deleteAction:not(:disabled){color:var(--dsw-alias-state-error-primary)}.QAYkWG_deleteStatus{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}.QAYkWG_archiveActivity{color:var(--dsw-alias-label-primary);margin:0 0 8px;padding-left:18px;font-size:13px;line-height:20px}.QAYkWG_archiveActivity li{overflow-wrap:anywhere}@media (prefers-reduced-motion:reduce){.QAYkWG_wide,.QAYkWG_skeletonDot,.QAYkWG_skeletonBar{animation:none}.QAYkWG_search,.QAYkWG_sectionLabel,.QAYkWG_searchSlot,.QAYkWG_searchInput,.QAYkWG_headerActions{transition:none}}";
2799
4105
  const tagId = "dsh-group-status/WorkspaceBrowser.module.css";
2800
4106
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
2801
4107
  const tag = document.createElement("style");
@@ -2805,51 +4111,51 @@ window.__ModuleLoader__.load({
2805
4111
  document.head.appendChild(tag);
2806
4112
  }
2807
4113
  var WorkspaceBrowser_module_css_default = {
2808
- "archiveActivity": "q9WRdW_archiveActivity",
2809
- "clearButton": "q9WRdW_clearButton",
2810
- "deleteAction": "q9WRdW_deleteAction",
2811
- "deleteStatus": "q9WRdW_deleteStatus",
2812
- "empty": "q9WRdW_empty",
2813
- "emptyAction": "q9WRdW_emptyAction",
2814
- "emptyState": "q9WRdW_emptyState",
2815
- "fade": "q9WRdW_fade",
2816
- "flatList": "q9WRdW_flatList",
2817
- "groupSection": "q9WRdW_groupSection",
2818
- "headerActions": "q9WRdW_headerActions",
2819
- "headerActionsHidden": "q9WRdW_headerActionsHidden",
2820
- "iconButton": "q9WRdW_iconButton",
2821
- "list": "q9WRdW_list",
2822
- "listArea": "q9WRdW_listArea",
2823
- "listTopDropActive": "q9WRdW_listTopDropActive",
2824
- "listTopDropIndicator": "q9WRdW_listTopDropIndicator",
2825
- "rail": "q9WRdW_rail",
2826
- "renameError": "q9WRdW_renameError",
2827
- "renameInput": "q9WRdW_renameInput",
2828
- "root": "q9WRdW_root",
2829
- "search": "q9WRdW_search",
2830
- "search-skeleton": "q9WRdW_search-skeleton",
2831
- "searchButton": "q9WRdW_searchButton",
2832
- "searchExpanded": "q9WRdW_searchExpanded",
2833
- "searchInput": "q9WRdW_searchInput",
2834
- "searchSlot": "q9WRdW_searchSlot",
2835
- "searchSlotExpanded": "q9WRdW_searchSlotExpanded",
2836
- "searchStatus": "q9WRdW_searchStatus",
2837
- "searchTree": "q9WRdW_searchTree",
2838
- "sectionHeader": "q9WRdW_sectionHeader",
2839
- "sectionLabel": "q9WRdW_sectionLabel",
2840
- "sectionLabelHidden": "q9WRdW_sectionLabelHidden",
2841
- "sessionOverflowButton": "q9WRdW_sessionOverflowButton",
2842
- "skeletonBar": "q9WRdW_skeletonBar",
2843
- "skeletonBarWide": "q9WRdW_skeletonBarWide",
2844
- "skeletonBars": "q9WRdW_skeletonBars",
2845
- "skeletonDot": "q9WRdW_skeletonDot",
2846
- "skeletonRow": "q9WRdW_skeletonRow",
2847
- "treeBody": "q9WRdW_treeBody",
2848
- "viewOptionsMenu": "q9WRdW_viewOptionsMenu",
2849
- "wide": "q9WRdW_wide",
2850
- "wide-in": "q9WRdW_wide-in",
2851
- "workspaceDropAfter": "q9WRdW_workspaceDropAfter",
2852
- "workspaceDropBefore": "q9WRdW_workspaceDropBefore"
4114
+ "archiveActivity": "QAYkWG_archiveActivity",
4115
+ "clearButton": "QAYkWG_clearButton",
4116
+ "deleteAction": "QAYkWG_deleteAction",
4117
+ "deleteStatus": "QAYkWG_deleteStatus",
4118
+ "empty": "QAYkWG_empty",
4119
+ "emptyAction": "QAYkWG_emptyAction",
4120
+ "emptyState": "QAYkWG_emptyState",
4121
+ "fade": "QAYkWG_fade",
4122
+ "flatList": "QAYkWG_flatList",
4123
+ "groupSection": "QAYkWG_groupSection",
4124
+ "headerActions": "QAYkWG_headerActions",
4125
+ "headerActionsHidden": "QAYkWG_headerActionsHidden",
4126
+ "iconButton": "QAYkWG_iconButton",
4127
+ "list": "QAYkWG_list",
4128
+ "listArea": "QAYkWG_listArea",
4129
+ "listTopDropActive": "QAYkWG_listTopDropActive",
4130
+ "listTopDropIndicator": "QAYkWG_listTopDropIndicator",
4131
+ "rail": "QAYkWG_rail",
4132
+ "renameError": "QAYkWG_renameError",
4133
+ "renameInput": "QAYkWG_renameInput",
4134
+ "root": "QAYkWG_root",
4135
+ "search": "QAYkWG_search",
4136
+ "search-skeleton": "QAYkWG_search-skeleton",
4137
+ "searchButton": "QAYkWG_searchButton",
4138
+ "searchExpanded": "QAYkWG_searchExpanded",
4139
+ "searchInput": "QAYkWG_searchInput",
4140
+ "searchSlot": "QAYkWG_searchSlot",
4141
+ "searchSlotExpanded": "QAYkWG_searchSlotExpanded",
4142
+ "searchStatus": "QAYkWG_searchStatus",
4143
+ "searchTree": "QAYkWG_searchTree",
4144
+ "sectionHeader": "QAYkWG_sectionHeader",
4145
+ "sectionLabel": "QAYkWG_sectionLabel",
4146
+ "sectionLabelHidden": "QAYkWG_sectionLabelHidden",
4147
+ "sessionOverflowButton": "QAYkWG_sessionOverflowButton",
4148
+ "skeletonBar": "QAYkWG_skeletonBar",
4149
+ "skeletonBarWide": "QAYkWG_skeletonBarWide",
4150
+ "skeletonBars": "QAYkWG_skeletonBars",
4151
+ "skeletonDot": "QAYkWG_skeletonDot",
4152
+ "skeletonRow": "QAYkWG_skeletonRow",
4153
+ "treeBody": "QAYkWG_treeBody",
4154
+ "viewOptionsMenu": "QAYkWG_viewOptionsMenu",
4155
+ "wide": "QAYkWG_wide",
4156
+ "wide-in": "QAYkWG_wide-in",
4157
+ "workspaceDropAfter": "QAYkWG_workspaceDropAfter",
4158
+ "workspaceDropBefore": "QAYkWG_workspaceDropBefore"
2853
4159
  };
2854
4160
  //#endregion
2855
4161
  //#region src/client/rows/WorkspaceBrowser.tsx
@@ -2869,14 +4175,7 @@ window.__ModuleLoader__.load({
2869
4175
  * are slot entries with their own behavior, so this component threads no
2870
4176
  * action callbacks and hosts no action surface.
2871
4177
  */
2872
- let useScheduleSummaryHook;
2873
- try {
2874
- useScheduleSummaryHook = require("dsh-later/client")?.useScheduleSummary;
2875
- } catch {}
2876
- let useWaitingSummaryHook;
2877
- try {
2878
- useWaitingSummaryHook = require("dsh-gitea-dispatch/client")?.useWaitingSummary;
2879
- } catch {}
4178
+ const facetsStore = facetStore();
2880
4179
  /**
2881
4180
  * Column slide length (--ds-transition-duration-slow): rail-search focus waits it out —
2882
4181
  * focus() forces a synchronous layout and would jank the slide.
@@ -2886,22 +4185,6 @@ window.__ModuleLoader__.load({
2886
4185
  const SEARCH_DEBOUNCE_MS = 250;
2887
4186
  /** `session.search` wire bound, measured in JavaScript UTF-16 code units. */
2888
4187
  const SEARCH_QUERY_MAX_CODE_UNITS = 500;
2889
- /** Idle Session rows visible per Workspace before the local overflow control. */
2890
- const COLLAPSED_SESSION_LIMIT = 5;
2891
- /** Keep provisional and running rows outside the idle-session quota, including parents with running children. */
2892
- function collapsedSessionRows(sessions, limit = COLLAPSED_SESSION_LIMIT) {
2893
- let idleCount = 0;
2894
- const rows = sessions.filter((session) => {
2895
- if (session.blank || session.running || session.runningSubagentCount > 0) return true;
2896
- if (idleCount >= limit) return false;
2897
- idleCount += 1;
2898
- return true;
2899
- });
2900
- return {
2901
- rows,
2902
- hiddenCount: sessions.length - rows.length
2903
- };
2904
- }
2905
4188
  /** Keep controlled input and RPC payload inside the session.search wire contract. */
2906
4189
  function sanitizeSearchQuery(value) {
2907
4190
  const withoutNul = value.replaceAll("\0", "");
@@ -3087,11 +4370,12 @@ window.__ModuleLoader__.load({
3087
4370
  const panelActive = usePanelInfo((info) => info.activePanelId !== null);
3088
4371
  const badgeSettings = useBadgeSettings((settings) => settings);
3089
4372
  const statuses = useSessionStatus((s) => s);
3090
- const scheduleSummary = useScheduleSummaryHook?.();
3091
- const waitingReviewSummary = useWaitingSummaryHook?.();
4373
+ (0, react.useSyncExternalStore)(facetsStore.subscribe, facetsStore.getVersion, facetsStore.getServerVersion);
4374
+ const facets = (0, react.useMemo)(() => visibleFacets(facetsStore.list(), badgeSettings), [badgeSettings]);
3092
4375
  const current = panelActive ? void 0 : Object.values(list.byId).find((session) => (session.retainedBy.mainView ?? 0) > 0)?.id;
3093
4376
  const revealGroup = revealSessionId === void 0 || !workspaceReady ? void 0 : owningGroupKey(workspaces, revealSessionId);
3094
- const [sessionLimits, setSessionLimits] = (0, react.useState)({});
4377
+ const [storedWatermarks, setStoredWatermarks] = (0, react.useState)({});
4378
+ const [watermarkMotionKey, setWatermarkMotionKey] = (0, react.useState)(0);
3095
4379
  const [drag, setDrag] = (0, react.useState)(null);
3096
4380
  const sessionDropCommitted = (0, react.useRef)(false);
3097
4381
  const [workspaceDrag, setWorkspaceDrag] = (0, react.useState)(null);
@@ -3149,20 +4433,24 @@ window.__ModuleLoader__.load({
3149
4433
  revealGroup,
3150
4434
  setGroupExpanded
3151
4435
  ]);
3152
- (0, react.useEffect)(() => {
3153
- if (revealSessionId === void 0 || revealGroup === void 0) return;
3154
- const group = groups.find((candidate) => candidate.key === revealGroup);
3155
- if (group === void 0 || !group.expanded || !group.sessions.some((row) => row.id === revealSessionId)) return;
3156
- if (collapsedSessionRows(group.sessions).rows.some((row) => row.id === revealSessionId)) return;
3157
- setSessionLimits((limits) => limits[revealGroup] === Infinity ? limits : {
3158
- ...limits,
3159
- [revealGroup]: Infinity
3160
- });
3161
- }, [
3162
- groups,
3163
- revealGroup,
3164
- revealSessionId
3165
- ]);
4436
+ const baselines = new Map(groups.map((group) => [group.key, Math.max(5, requiredWatermark(group.expanded ? group.sessions : [], current, group.key === revealGroup ? revealSessionId : void 0))]));
4437
+ const foldedKeys = new Set(groups.filter((group) => !group.expanded).map((group) => group.key));
4438
+ const underFoldedAncestor = (key) => {
4439
+ for (let owner = parents.get(key); owner !== void 0; owner = parents.get(owner)) if (foldedKeys.has(owner)) return true;
4440
+ return false;
4441
+ };
4442
+ const requiredWatermarks = new Map([...baselines].filter(([key]) => !underFoldedAncestor(key)));
4443
+ const sessionWatermarks = raiseWatermarks(storedWatermarks, requiredWatermarks);
4444
+ if (sessionWatermarks !== storedWatermarks) setStoredWatermarks((marks) => raiseWatermarks(marks, requiredWatermarks));
4445
+ const dropWatermarks = (drop) => {
4446
+ if (!Object.keys(storedWatermarks).some(drop)) return;
4447
+ setStoredWatermarks((marks) => Object.fromEntries(Object.entries(marks).filter(([key]) => !drop(key))));
4448
+ setWatermarkMotionKey((key) => key + 1);
4449
+ };
4450
+ const withinGroup = (key, root) => {
4451
+ for (let owner = key; owner !== void 0; owner = parents.get(owner)) if (owner === root) return true;
4452
+ return false;
4453
+ };
3166
4454
  const now = Date.now();
3167
4455
  const commitSessionDrag = (activeDrag, over) => {
3168
4456
  if (sessionDropCommitted.current) return;
@@ -3173,7 +4461,7 @@ window.__ModuleLoader__.load({
3173
4461
  if (over.id === activeDrag.sessionId) return;
3174
4462
  const accountSessionIds = activeDrag.accountKey === "" ? ungroupedSessionIds : workspaces.find((workspace) => workspace.workspaceId === activeDrag.accountKey)?.sessionIds;
3175
4463
  if (accountSessionIds === void 0) return;
3176
- const renderedSessions = collapsedSessionRows(group.sessions, sessionLimits[group.key]).rows;
4464
+ const renderedSessions = watermarkRows(group.sessions, sessionWatermarks[group.key] ?? 5).rows;
3177
4465
  const nextOrder = sessionDragOrder(accountSessionIds, renderedSessions, activeDrag, over);
3178
4466
  if (nextOrder !== void 0) setSessionOrder(activeDrag.accountKey, nextOrder);
3179
4467
  };
@@ -3206,11 +4494,7 @@ window.__ModuleLoader__.load({
3206
4494
  }
3207
4495
  return children;
3208
4496
  }, [groups, parents]);
3209
- const directStatusByGroup = (0, react.useMemo)(() => directStatusesForGroups(groups, waitingReviewSummary, scheduleSummary), [
3210
- groups,
3211
- waitingReviewSummary,
3212
- scheduleSummary
3213
- ]);
4497
+ const directStatusByGroup = (0, react.useMemo)(() => directStatusesForGroups(groups, facets, now), [groups, facets]);
3214
4498
  const groupsWithAggregatedStatus = (0, react.useMemo)(() => withAggregatedStatus(groups, directStatusByGroup, childrenByParentForAggregation), [
3215
4499
  groups,
3216
4500
  directStatusByGroup,
@@ -3235,8 +4519,9 @@ window.__ModuleLoader__.load({
3235
4519
  const workspaceId = group.workspaceId;
3236
4520
  const children = childrenByParent.get(group.key) ?? [];
3237
4521
  const compatibleDrag = workspaceDrag !== null && parents.get(workspaceDrag.workspaceId) === parents.get(group.key);
3238
- const collapsed = collapsedSessionRows(group.sessions);
3239
- const visible = collapsedSessionRows(group.sessions, sessionLimits[group.key]);
4522
+ const watermark = sessionWatermarks[group.key] ?? 5;
4523
+ const collapsed = watermarkRows(group.sessions, baselines.get(group.key) ?? 5);
4524
+ const visible = watermarkRows(group.sessions, watermark);
3240
4525
  const sessionsExpanded = visible.hiddenCount === 0;
3241
4526
  rowKeys.push(`workspace:${group.key}`);
3242
4527
  const childRows = group.expanded ? children.map((child) => renderGroup(child, depth + 1)) : [];
@@ -3307,12 +4592,10 @@ window.__ModuleLoader__.load({
3307
4592
  containsCurrentDescendant: currentAncestors.has(group.key),
3308
4593
  home,
3309
4594
  settings: badgeSettings,
4595
+ now,
3310
4596
  t,
3311
4597
  onToggle: () => {
3312
- if (group.expanded) setSessionLimits((limits) => ({
3313
- ...limits,
3314
- [group.key]: COLLAPSED_SESSION_LIMIT
3315
- }));
4598
+ if (group.expanded) dropWatermarks((key) => withinGroup(key, group.key));
3316
4599
  setGroupExpanded(group.key, !group.expanded);
3317
4600
  },
3318
4601
  onCreate: () => {
@@ -3397,10 +4680,15 @@ window.__ModuleLoader__.load({
3397
4680
  "data-row-key": `overflow:${group.key}`,
3398
4681
  "aria-expanded": sessionsExpanded,
3399
4682
  onClick: () => {
3400
- setSessionLimits((limits) => ({
3401
- ...limits,
3402
- [group.key]: sessionsExpanded ? COLLAPSED_SESSION_LIMIT : visible.hiddenCount <= COLLAPSED_SESSION_LIMIT ? Infinity : (limits[group.key] ?? COLLAPSED_SESSION_LIMIT) + COLLAPSED_SESSION_LIMIT
4683
+ if (sessionsExpanded) {
4684
+ dropWatermarks((key) => key === group.key);
4685
+ return;
4686
+ }
4687
+ setStoredWatermarks((marks) => ({
4688
+ ...marks,
4689
+ [group.key]: visible.hiddenCount <= 5 ? Infinity : watermark + 5
3403
4690
  }));
4691
+ setWatermarkMotionKey((key) => key + 1);
3404
4692
  },
3405
4693
  children: sessionsExpanded ? t("sessions.collapse") : t("sessions.expand", { n: visible.hiddenCount })
3406
4694
  })
@@ -3408,7 +4696,7 @@ window.__ModuleLoader__.load({
3408
4696
  }, group.key);
3409
4697
  };
3410
4698
  const groupRows = rootGroups.map((group) => renderGroup(group, 0));
3411
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4699
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.HoverCardGroup, {
3412
4700
  className: clsx(WorkspaceBrowser_module_css_default.treeBody, WorkspaceBrowser_module_css_default.wide),
3413
4701
  children: [
3414
4702
  workspaceDropAtListStart && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
@@ -3420,7 +4708,7 @@ window.__ModuleLoader__.load({
3420
4708
  label: t("section.sessions"),
3421
4709
  rowKeys,
3422
4710
  ready: list.phase === "ready" && workspaceReady && !nativeDragActive,
3423
- resetKey: animationResetKey,
4711
+ resetKey: JSON.stringify([animationResetKey, watermarkMotionKey]),
3424
4712
  children: [groupsWithAggregatedStatus.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmptySessions, {
3425
4713
  rowState,
3426
4714
  onLeaveArchivedOnly,
@@ -3453,7 +4741,7 @@ window.__ModuleLoader__.load({
3453
4741
  if (nextOrder !== void 0) setSessionOrder(FLAT_SESSION_ORDER_KEY, nextOrder);
3454
4742
  };
3455
4743
  const now = Date.now();
3456
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4744
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.HoverCardGroup, {
3457
4745
  className: clsx(WorkspaceBrowser_module_css_default.treeBody, WorkspaceBrowser_module_css_default.wide),
3458
4746
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(AnimatedRows, {
3459
4747
  className: clsx(WorkspaceBrowser_module_css_default.list, WorkspaceBrowser_module_css_default.flatList),
@@ -3586,7 +4874,7 @@ window.__ModuleLoader__.load({
3586
4874
  * @param props - composed slot props (shell owner share + store + injected actions).
3587
4875
  * @returns the region element tree.
3588
4876
  */
3589
- function WorkspaceBrowser({ wide, usePanelInfo, expandSidebar, useSessions, useSessionStatus, useWorkspaces, useStore, actions, startSession, open, requestSessionRename, notifyArchivedNotOpenable, renameWorkspace, deleteWorkspace, insertWorkspaceBefore, unarchiveSession, createWorkspace, searchSessions, searchResultLimit, useDirectoryFlow, useHostInfo, useShortcuts, useWorkspaceShortcuts, useBadgeSettings, requestSearch, requestAddWorkspace, closeAddWorkspace, setDirectoryBusy, dismissForkError, renderSlot, t }) {
4877
+ function WorkspaceBrowser({ wide, usePanelInfo, expandSidebar, useSessions, useSessionStatus, useWorkspaces, useStore, actions, startSession, open, requestSessionRename, notifyArchivedNotOpenable, renameWorkspace, deleteWorkspace, insertWorkspaceBefore, unarchiveSession, createWorkspace, searchSessions, searchResultLimit, useDirectoryFlow, useHostInfo, useShortcuts, useWorkspaceShortcuts, useBadgeSettings, requestSearch, requestAddWorkspace, closeAddWorkspace, setDirectoryBusy, renderSlot, t }) {
3590
4878
  const home = useHostInfo((info) => info.home);
3591
4879
  const shortcuts = useShortcuts((rows) => rows);
3592
4880
  const searchShortcut = shortcuts.find((row) => row.id === "session.search");
@@ -4015,7 +5303,7 @@ window.__ModuleLoader__.load({
4015
5303
  side: "right",
4016
5304
  onPick: (workspaceId) => {
4017
5305
  closeAddWorkspace();
4018
- startSession(workspaceId);
5306
+ startSession(workspaceId, { clearPreviousDraft: false });
4019
5307
  },
4020
5308
  onClose: () => {
4021
5309
  closeAddWorkspace();
@@ -4195,11 +5483,7 @@ window.__ModuleLoader__.load({
4195
5483
  role: "alert",
4196
5484
  children: deleteError
4197
5485
  })]
4198
- }),
4199
- shortcutState.forkError !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Toast, {
4200
- text: t(shortcutState.forkError.reason === "unavailable" ? "shortcut.noCompletedTurn" : "shortcut.forkFailed"),
4201
- onDone: dismissForkError
4202
- }, shortcutState.forkError.seq)
5486
+ })
4203
5487
  ]
4204
5488
  });
4205
5489
  }
@@ -4244,6 +5528,7 @@ window.__ModuleLoader__.load({
4244
5528
  side: "bottom",
4245
5529
  align: "end",
4246
5530
  delayMs: 500,
5531
+ portal: true,
4247
5532
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4248
5533
  type: "button",
4249
5534
  className: Rows_module_css_default.iconButton,
@@ -4389,13 +5674,15 @@ window.__ModuleLoader__.load({
4389
5674
  /** The fork action: one `sidebar.workspaces.session.menu.item` row. */
4390
5675
  /**
4391
5676
  * Menu row (order 300): fork at the Session's last completed turn; the child
4392
- * arrives through the Host list beside its source.
4393
- * @param props - owner share, menu open state, and the fork share.
4394
- * @returns the row.
5677
+ * arrives through the Host list beside its source. Absent when the access
5678
+ * snapshot excludes `session/fork` for this Session.
5679
+ * @param props - owner share, menu open state, access Hook, and the fork share.
5680
+ * @returns the row, or nothing when the caller may not fork this Session.
4395
5681
  */
4396
- function ForkSessionMenuItem({ sessionId, useMenuOpenState, useShortcuts, forkSession, t }) {
5682
+ function ForkSessionMenuItem({ sessionId, useMenuOpenState, useShortcuts, useSessionAccess, forkSession, t }) {
4397
5683
  const [, setMenuOpen] = useMenuOpenState();
4398
5684
  const shortcut = useShortcuts((rows) => rows.find((row) => row.id === "session.fork"));
5685
+ if (!useSessionAccess(sessionId, "session/fork")) return null;
4399
5686
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.MenuItemButton, {
4400
5687
  shortcut,
4401
5688
  icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconBranchOutlineRegular, {}),
@@ -4456,6 +5743,7 @@ window.__ModuleLoader__.load({
4456
5743
  side: "bottom",
4457
5744
  align: "end",
4458
5745
  delayMs: 500,
5746
+ portal: true,
4459
5747
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4460
5748
  type: "button",
4461
5749
  className: Rows_module_css_default.iconButton,
@@ -4585,75 +5873,38 @@ window.__ModuleLoader__.load({
4585
5873
  });
4586
5874
  }
4587
5875
  //#endregion
4588
- //#region src/client/session-actions/RowActionToast.tsx
4589
- /**
4590
- * The `shell.overlay` entry for Workspace and Session notices.
4591
- * One notice is visible at a time; a parent rerender does not extend its hold.
4592
- */
5876
+ //#region src/client/session-actions/SessionId.tsx
5877
+ /** The copy Session ID action: one `sidebar.workspaces.session.menu.item` row. */
4593
5878
  /**
4594
- * Hold for the notices that take longer to read than a one-line warning: the
4595
- * actionable archived notice (two buttons to react to) and a refused Session
4596
- * creation, which quotes the Host's reason.
5879
+ * Copy the complete ID after dismissing the row menu.
5880
+ * @param props - row identity, menu dismissal, clipboard action, and locale.
5881
+ * @returns the final built-in menu item.
4597
5882
  */
4598
- const LONG_TOAST_HOLD_MS = 6e3;
5883
+ function SessionIdMenuItem({ sessionId, useMenuOpenState, copySessionId, t }) {
5884
+ const [, setMenuOpen] = useMenuOpenState();
5885
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.MenuItemButton, {
5886
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCopyOutlineRegular, {}),
5887
+ onSelect: () => {
5888
+ setMenuOpen(false);
5889
+ copySessionId(sessionId);
5890
+ },
5891
+ children: t("menu.sessionId")
5892
+ });
5893
+ }
5894
+ //#endregion
5895
+ //#region src/client/session-actions/RowActionToast.tsx
4599
5896
  /**
4600
- * Render the current notice: the archived and stopped-and-archived notices
4601
- * with their undo action — plus the show-archived action while archived rows
4602
- * are hidden — on a 6 s hold, a refused Session creation with the Host's
4603
- * reason on the same hold, or a plain warning for a failed pin, an archived
4604
- * row that was clicked, or default Workspace creation.
4605
- * @param props - the notice hook, the shared viewing store, the notice dismissal, the two archived-notice actions, and the locale seat.
4606
- * @returns the notice on display, or null.
5897
+ * Dispatch the current notice without interpreting its business payload.
5898
+ * @param props - Current notice, dismissal, and the declared keyed renderer.
5899
+ * @returns the selected notice presentation, or nothing.
4607
5900
  */
4608
- function RowActionToast({ useToast, useStore, dismissToast, undoArchive, showArchived, t }) {
4609
- const toast = useToast((current) => current);
4610
- const archivedRowsVisible = useStore((state) => (state.archivedFilter ?? "default") !== "default");
4611
- if (toast === null) return null;
4612
- if (toast.kind === "archived" || toast.kind === "stoppedAndArchived") {
4613
- const { sessionId } = toast;
4614
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Toast, {
4615
- text: t(toast.kind === "archived" ? "toast.archived" : "toast.stoppedAndArchived"),
4616
- tone: "success",
4617
- holdMs: LONG_TOAST_HOLD_MS,
4618
- actions: [{
4619
- label: t("toast.archivedUndo"),
4620
- onClick: () => {
4621
- dismissToast();
4622
- undoArchive(sessionId);
4623
- }
4624
- }, ...archivedRowsVisible ? [] : [{
4625
- prefix: t("toast.archivedOr"),
4626
- label: t("toast.archivedFilter"),
4627
- onClick: () => {
4628
- dismissToast();
4629
- showArchived();
4630
- }
4631
- }]],
4632
- onDone: dismissToast
4633
- }, `toast-${String(toast.seq)}`);
4634
- }
4635
- if (toast.kind === "createFailed") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Toast, {
4636
- text: t("toast.createFailed", { message: toast.message }),
4637
- icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconWarningOutlineRegular, {}),
4638
- holdMs: LONG_TOAST_HOLD_MS,
4639
- onDone: dismissToast
4640
- }, `toast-${String(toast.seq)}`);
4641
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Toast, {
4642
- text: plainNoticeText(toast, t),
4643
- icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconWarningOutlineRegular, {}),
4644
- onDone: dismissToast
4645
- }, `toast-${String(toast.seq)}`);
4646
- }
4647
- /** The copy of one plain warning, keyed by the notice kind the union closes over. */
4648
- function plainNoticeText(toast, t) {
4649
- switch (toast.kind) {
4650
- case "pinFailed": return t("toast.pinFailed");
4651
- case "unpinFailed": return t("toast.unpinFailed");
4652
- case "defaultWorkspaceFailed": return t("defaultWorkspace.failed");
4653
- case "archivedNotOpenable": return t("toast.archivedNotOpenable");
4654
- /* v8 ignore next 2 -- closed-union backstop; only reached if a notice kind is forged */
4655
- default: return assertNever$1(toast);
4656
- }
5901
+ function RowActionToast({ useToast, dismissToast, renderSlot }) {
5902
+ const notice = useToast((current) => current);
5903
+ if (notice === null) return null;
5904
+ return renderSlot("workspace.toast", {
5905
+ ...notice,
5906
+ dismissToast
5907
+ }, { entryKey: notice.kind });
4657
5908
  }
4658
5909
  //#endregion
4659
5910
  //#region src/client/locales.ts
@@ -4665,6 +5916,8 @@ window.__ModuleLoader__.load({
4665
5916
  /** Simplified Chinese dictionary (the key-set source of truth). */
4666
5917
  const zh = {
4667
5918
  "defaultWorkspace.failed": "无法创建默认工作区,请通过“选择工作区”选择文件夹",
5919
+ "draft.workspaceRequired": "请先选择工作区",
5920
+ "draft.initializationFailed": "暂时无法填入草稿,请稍后重试",
4668
5921
  "group.ungrouped": "未分组",
4669
5922
  "session.new": "新会话",
4670
5923
  "session.untitled": "未命名",
@@ -4719,6 +5972,9 @@ window.__ModuleLoader__.load({
4719
5972
  "menu.unarchiveSession": "取消归档",
4720
5973
  "menu.pinSession": "置顶会话",
4721
5974
  "menu.unpinSession": "取消置顶",
5975
+ "menu.sessionId": "复制 Session ID",
5976
+ "toast.sessionIdCopied": "Session ID 已复制",
5977
+ "toast.sessionIdCopyFailed": "复制失败,请重试",
4722
5978
  "row.archived": "已归档",
4723
5979
  "row.pinned": "已置顶",
4724
5980
  "toast.archivedNotOpenable": "已归档对话暂时无法查看,请取消归档后查看",
@@ -4745,6 +6001,8 @@ window.__ModuleLoader__.load({
4745
6001
  "toast.pinFailed": "置顶失败,请稍后重试",
4746
6002
  "toast.unpinFailed": "取消置顶失败,请稍后重试",
4747
6003
  "toast.createFailed": "新建会话失败:{message}",
6004
+ "toast.forkRequiresOpen": "请先打开原会话完成迁移,再创建分支",
6005
+ "toast.openForkSource": "打开原会话",
4748
6006
  "sessions.count.one": "{n} 个会话",
4749
6007
  "sessions.count.other": "{n} 个会话",
4750
6008
  "actions.workspace.aria": "工作区“{name}”的操作",
@@ -4765,8 +6023,7 @@ window.__ModuleLoader__.load({
4765
6023
  "status.compact.approval": "待审批",
4766
6024
  "status.compact.planReview": "计划待审",
4767
6025
  "status.compact.answer": "待回答",
4768
- "status.waitingReview": "等待评审",
4769
- "status.scheduled": "定时任务",
6026
+ "status.needsAttention": "需要关注",
4770
6027
  "status.completed": "已完成",
4771
6028
  "hover.created": "创建于 {time}",
4772
6029
  "hover.copied": "已复制",
@@ -4787,8 +6044,8 @@ window.__ModuleLoader__.load({
4787
6044
  "settings.badge.showCompletedHint": "关闭后,「已完成」的会话不再画绿点,也不再计入折叠徽标的数字与悬停明细。会话行自身的绿点不受影响。",
4788
6045
  "settings.badge.showScheduled": "显示「定时任务」时钟",
4789
6046
  "settings.badge.showScheduledHint": "关闭后,带定时提醒的会话(来自 dsh-later)不再画时钟图标,也不再计入折叠徽标的数字与悬停明细。",
4790
- "settings.badge.showWaitingReview": "显示「等待评审」列表笔",
4791
- "settings.badge.showWaitingReviewHint": "关闭后,等待代码评审的会话(来自 dsh-gitea-dispatch)不再画列表笔图标,也不再计入折叠徽标的数字与悬停明细。",
6047
+ "settings.badge.showWaitingReview": "显示「等待评审」沙漏",
6048
+ "settings.badge.showWaitingReviewHint": "关闭后,等待代码评审的会话(来自 dsh-gitea-dispatch)不再画沙漏图标,也不再计入折叠徽标的数字与悬停明细。",
4792
6049
  "settings.hover.section": "悬停明细",
4793
6050
  "settings.hover.showDetails": "显示状态明细行",
4794
6051
  "settings.hover.showDetailsHint": "关闭后,悬停卡只显示工作区标题、路径和创建时间——不再显示聚合状态明细行。",
@@ -4804,6 +6061,8 @@ window.__ModuleLoader__.load({
4804
6061
  /** English dictionary, checked complete against the zh key set. */
4805
6062
  const en = {
4806
6063
  "defaultWorkspace.failed": "Unable to create default workspace. Use Choose workspace to select a folder.",
6064
+ "draft.workspaceRequired": "Choose a workspace first.",
6065
+ "draft.initializationFailed": "Could not fill the draft. Please try again shortly.",
4807
6066
  "group.ungrouped": "Ungrouped",
4808
6067
  "session.new": "New Session",
4809
6068
  "session.untitled": "Untitled",
@@ -4858,6 +6117,9 @@ window.__ModuleLoader__.load({
4858
6117
  "menu.unarchiveSession": "Unarchive session",
4859
6118
  "menu.pinSession": "Pin session",
4860
6119
  "menu.unpinSession": "Unpin session",
6120
+ "menu.sessionId": "Copy Session ID",
6121
+ "toast.sessionIdCopied": "Session ID copied",
6122
+ "toast.sessionIdCopyFailed": "Copy failed. Try again.",
4861
6123
  "row.archived": "Archived",
4862
6124
  "row.pinned": "Pinned",
4863
6125
  "toast.archivedNotOpenable": "Archived sessions cannot be opened. Unarchive it to view.",
@@ -4884,6 +6146,8 @@ window.__ModuleLoader__.load({
4884
6146
  "toast.pinFailed": "Pin failed. Try again later.",
4885
6147
  "toast.unpinFailed": "Unpin failed. Try again later.",
4886
6148
  "toast.createFailed": "New session failed: {message}",
6149
+ "toast.forkRequiresOpen": "Open the original session to complete migration before creating a branch.",
6150
+ "toast.openForkSource": "Open original session",
4887
6151
  "sessions.count.one": "{n} session",
4888
6152
  "sessions.count.other": "{n} sessions",
4889
6153
  "actions.workspace.aria": "Workspace actions for {name}",
@@ -4904,8 +6168,7 @@ window.__ModuleLoader__.load({
4904
6168
  "status.compact.approval": "Approval",
4905
6169
  "status.compact.planReview": "Plan review",
4906
6170
  "status.compact.answer": "Answer",
4907
- "status.waitingReview": "Waiting for review",
4908
- "status.scheduled": "Scheduled",
6171
+ "status.needsAttention": "Needs attention",
4909
6172
  "status.completed": "Completed",
4910
6173
  "hover.created": "Created {time}",
4911
6174
  "hover.copied": "Copied",
@@ -4927,7 +6190,7 @@ window.__ModuleLoader__.load({
4927
6190
  "settings.badge.showScheduled": "Show the scheduled icon",
4928
6191
  "settings.badge.showScheduledHint": "When off, Sessions with scheduled reminders (from dsh-later) stop drawing the clock icon and drop out of the collapsed badge count and its hover details.",
4929
6192
  "settings.badge.showWaitingReview": "Show the waiting-for-review icon",
4930
- "settings.badge.showWaitingReviewHint": "When off, Sessions waiting for code review (from dsh-gitea-dispatch) stop drawing the list-pen icon and drop out of the collapsed badge count and its hover details.",
6193
+ "settings.badge.showWaitingReviewHint": "When off, Sessions waiting for code review (from dsh-gitea-dispatch) stop drawing the hourglass icon and drop out of the collapsed badge count and its hover details.",
4931
6194
  "settings.hover.section": "Hover details",
4932
6195
  "settings.hover.showDetails": "Show status detail lines",
4933
6196
  "settings.hover.showDetailsHint": "When off, the hover card shows only the workspace title, path, and creation time — no aggregated status lines.",
@@ -4951,14 +6214,6 @@ window.__ModuleLoader__.load({
4951
6214
  * 本包的补丁把行 id 也写成同名,但两者是不同概念,别改成 ENTRY_ID 的引用。
4952
6215
  */
4953
6216
  const CONFIG_CARD_KEY = "dsh-group-status";
4954
- /**
4955
- * Required services (cordis fiber inject). The target slots are declared by
4956
- * the ui-sidebar / ui-conversation applies, whose activation order relative
4957
- * to this one is NOT constrained: dsh.client.inject edges are informational
4958
- * (loading/prefetch metadata, never apply sequencing) and neither owner
4959
- * provides a waitable service. apply therefore depends on each slot
4960
- * declaration through `slots.inject()` instead of assuming order.
4961
- */
4962
6217
  const inject = [
4963
6218
  "slots",
4964
6219
  "sessions",
@@ -4967,7 +6222,8 @@ window.__ModuleLoader__.load({
4967
6222
  "remote",
4968
6223
  "remote.directoryPicker",
4969
6224
  "layout",
4970
- "shortcuts"
6225
+ "shortcuts",
6226
+ "remoteAccess"
4971
6227
  ];
4972
6228
  /**
4973
6229
  * Register the browser and picker once their slot declarations are on the
@@ -4993,11 +6249,19 @@ window.__ModuleLoader__.load({
4993
6249
  });
4994
6250
  };
4995
6251
  const uiWorkspace = new UiWorkspaceService(ctx, ctx.remote.directoryPicker, workspaces, sessions, viewInstance.actions, notify);
4996
- ctx.slots.provideRoot({ hooks: { workspaces: workspaces.list } });
6252
+ const denyWorkspaceAccess = {
6253
+ getSnapshot: () => false,
6254
+ subscribe: () => () => {}
6255
+ };
6256
+ ctx.slots.provideRoot({
6257
+ hooks: { workspaces: workspaces.list },
6258
+ keyedHooks: { workspaceAccess: (workspaceId, action) => action === void 0 ? denyWorkspaceAccess : ctx.remoteAccess.source("workspace", workspaceId, action) }
6259
+ });
4997
6260
  ctx.effect(() => ctx.locale.register(NS, {
4998
6261
  zh,
4999
6262
  en
5000
6263
  }), "ui-workspace: dictionaries");
6264
+ ctx.effect(() => () => disposeFacetStore(), "ui-workspace: status facets");
5001
6265
  const shortcutControls = createWorkspaceShortcutControls();
5002
6266
  const badgeSettings = createBadgeSettingsSource();
5003
6267
  ctx.slots.provideRoot({ hooks: { badgeSettings } });
@@ -5091,7 +6355,12 @@ window.__ModuleLoader__.load({
5091
6355
  },
5092
6356
  unarchiveSession
5093
6357
  });
5094
- installWorkspaceShortcuts(ctx, uiWorkspace, shortcutControls, archiveInjected().archiveSession);
6358
+ installWorkspaceShortcuts(ctx, uiWorkspace, shortcutControls, archiveInjected().archiveSession, (reason) => {
6359
+ notify({
6360
+ kind: "shortcutForkFailed",
6361
+ reason
6362
+ });
6363
+ });
5095
6364
  const archiveConfirmInjected = () => ({
5096
6365
  hooks: { archiveRequest },
5097
6366
  settleSessionArchive: () => {
@@ -5112,7 +6381,12 @@ window.__ModuleLoader__.load({
5112
6381
  parent_session_id: sessionId,
5113
6382
  click_position: "sidebar"
5114
6383
  });
5115
- }).catch(() => {});
6384
+ }, { allowMigration: false }).catch((reason) => {
6385
+ if ((reason instanceof Error && reason.name === "SessionForkError" ? reason : void 0)?.rpcError?.code === "session/migration-required") notify({
6386
+ kind: "forkRequiresOpen",
6387
+ sessionId
6388
+ });
6389
+ });
5116
6390
  } });
5117
6391
  const renameInjected = () => ({ requestSessionRename });
5118
6392
  const renameDialogInjected = () => ({
@@ -5120,19 +6394,31 @@ window.__ModuleLoader__.load({
5120
6394
  settleSessionRename: shortcutControls.closeRename,
5121
6395
  renameSession
5122
6396
  });
6397
+ const sessionIdInjected = () => ({ copySessionId: async (id) => {
6398
+ notify({ kind: await (0, _deepseek_ai_dsh_client_ui_primitives.writeClipboard)(id) ? "sessionIdCopied" : "sessionIdCopyFailed" });
6399
+ } });
6400
+ registerToasts(ctx, viewStore, {
6401
+ undoArchive: unarchiveSession,
6402
+ showArchived: () => {
6403
+ viewInstance.actions.setArchivedFilter("show");
6404
+ },
6405
+ openForkSource: (id) => {
6406
+ if (workspaces.list.getSnapshot().archivedSessionIds.includes(id)) {
6407
+ notify({ kind: "archivedNotOpenable" });
6408
+ return;
6409
+ }
6410
+ openSession(id);
6411
+ }
6412
+ });
5123
6413
  const rowToastInjected = () => ({
5124
6414
  hooks: { toast: rowToast },
5125
6415
  dismissToast: () => {
5126
6416
  rowToast.set(null);
5127
- },
5128
- undoArchive: unarchiveSession,
5129
- showArchived: () => {
5130
- viewInstance.actions.setArchivedFilter("show");
5131
6417
  }
5132
6418
  });
5133
6419
  const browserInjected = () => ({
5134
- startSession: (workspaceId) => {
5135
- uiWorkspace.startSession(workspaceId);
6420
+ startSession: (workspaceId, options) => {
6421
+ uiWorkspace.startSession(workspaceId, options);
5136
6422
  },
5137
6423
  open: openSession,
5138
6424
  searchSessions,
@@ -5158,7 +6444,6 @@ window.__ModuleLoader__.load({
5158
6444
  requestAddWorkspace: shortcutControls.add,
5159
6445
  closeAddWorkspace: shortcutControls.closeAdd,
5160
6446
  setDirectoryBusy: shortcutControls.directoryBusy,
5161
- dismissForkError: shortcutControls.dismissForkError,
5162
6447
  hooks: {
5163
6448
  directoryFlow: browserFlowSource,
5164
6449
  hostInfo,
@@ -5231,6 +6516,13 @@ window.__ModuleLoader__.load({
5231
6516
  locale: NS,
5232
6517
  inject: archiveInjected
5233
6518
  }, ArchiveSessionMenuItem);
6519
+ yield ctx.slots.register({
6520
+ name: "sidebar.workspaces.session.menu.item",
6521
+ id: "session-id",
6522
+ order: 450,
6523
+ locale: NS,
6524
+ inject: sessionIdInjected
6525
+ }, SessionIdMenuItem);
5234
6526
  });
5235
6527
  ctx.slots.inject("sidebar.workspaces.session.row.action", function* () {
5236
6528
  yield ctx.slots.register({
@@ -5264,9 +6556,11 @@ window.__ModuleLoader__.load({
5264
6556
  yield ctx.slots.register({
5265
6557
  name: "shell.overlay",
5266
6558
  id: "workspace.row-toast",
5267
- locale: NS,
5268
- store: viewStore,
5269
- inject: rowToastInjected
6559
+ inject: rowToastInjected,
6560
+ children: { "workspace.toast": {
6561
+ kind: "keyed",
6562
+ scope: "root"
6563
+ } }
5270
6564
  }, RowActionToast);
5271
6565
  });
5272
6566
  ctx.slots.inject("conversation.hero.workspace", () => ctx.slots.register({
@@ -5290,8 +6584,12 @@ window.__ModuleLoader__.load({
5290
6584
  return rpcError.code === "workspace/session-active" ? rpcError.details.activity : void 0;
5291
6585
  }
5292
6586
  //#endregion
6587
+ exports.FACET_TONE_RANK = FACET_TONE_RANK;
6588
+ exports.GROUP_STATUS_FACET_PROTOCOL_VERSION = GROUP_STATUS_FACET_PROTOCOL_VERSION;
5293
6589
  exports.apply = apply;
6590
+ exports.groupStatusFacetRegistry = registry;
5294
6591
  exports.inject = inject;
6592
+ exports.registerGroupStatusFacet = registerGroupStatusFacet;
5295
6593
  return module.exports;
5296
6594
  }
5297
6595
  });