@michengai/dsh-codex-ui 0.2.100 → 0.2.102

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.
@@ -297,7 +297,7 @@ window.__ModuleLoader__.load({
297
297
  running: row.running === true
298
298
  };
299
299
  }
300
- function parseChannelGroups(payload) {
300
+ function parseChannelGroups(payload, fallbackLabel = "") {
301
301
  const root = payload !== null && typeof payload === "object" ? payload : {};
302
302
  return (Array.isArray(root.groups) ? root.groups : Array.isArray(payload) ? payload : []).flatMap((item, index) => {
303
303
  if (item === null || typeof item !== "object") return [];
@@ -308,13 +308,13 @@ window.__ModuleLoader__.load({
308
308
  }) : [];
309
309
  return [{
310
310
  id: text(group.id, `channel-${index}`),
311
- label: text(group.label, text(group.title, "频道")),
311
+ label: text(group.label, text(group.title, fallbackLabel)),
312
312
  sessions
313
313
  }];
314
314
  });
315
315
  }
316
316
  /** 读取 IM 频道分组;失败时交给界面显示空态或错误。 */
317
- async function loadChannelGroups(signal) {
317
+ async function loadChannelGroups(signal, fallbackLabel = "") {
318
318
  const response = await fetch(CHANNELS_ENDPOINT, {
319
319
  cache: "no-store",
320
320
  signal
@@ -328,66 +328,244 @@ window.__ModuleLoader__.load({
328
328
  const root = payload !== null && typeof payload === "object" ? payload : {};
329
329
  if (!response.ok) throw new Error(text(root.error, "无法读取频道会话"));
330
330
  if (root.ok === false) throw new Error(text(root.error, "无法读取频道会话"));
331
- return parseChannelGroups(payload);
331
+ return parseChannelGroups(payload, fallbackLabel);
332
332
  }
333
333
  //#endregion
334
- //#region src/client/session-manager.ts
335
- const SESSION_PINS_STORAGE_KEY = "dsh.session-pins.v1";
336
- const SESSION_UNREAD_STORAGE_KEY = "dsh.session-unread.v1";
337
- function normalizeSessionIds(ids) {
338
- return [...new Set(ids.filter((id) => id.trim() !== ""))];
339
- }
340
- function toggleSessionId(ids, sessionId) {
341
- return ids.includes(sessionId) ? ids.filter((id) => id !== sessionId) : [sessionId, ...ids.filter((id) => id !== sessionId)];
342
- }
343
- function readSessionIds(storage, key) {
344
- if (storage === void 0) return [];
345
- try {
346
- const value = JSON.parse(storage.getItem(key) ?? "{\"version\":1,\"ids\":[]}");
347
- if (value === null || typeof value !== "object") return [];
348
- const ids = value.ids;
349
- return Array.isArray(ids) && ids.every((id) => typeof id === "string") ? normalizeSessionIds(ids) : [];
350
- } catch {
351
- return [];
352
- }
353
- }
354
- function writeSessionIds(storage, key, ids) {
355
- if (storage === void 0) return;
356
- try {
357
- storage.setItem(key, JSON.stringify({
358
- version: 1,
359
- ids: normalizeSessionIds(ids)
360
- }));
361
- } catch {}
362
- }
363
- function sessionDeepLink(base, sessionId) {
364
- const url = new URL(base);
365
- url.searchParams.set("session", sessionId);
366
- return url.toString();
367
- }
334
+ //#region node_modules/.pnpm/lucide-react@1.39.0_react@18.3.1/node_modules/lucide-react/dist/esm/shared/src/utils/mergeClasses.mjs
335
+ /**
336
+ * @license lucide-react v1.39.0 - ISC
337
+ *
338
+ * This source code is licensed under the ISC license.
339
+ * See the LICENSE file in the root directory of this source tree.
340
+ */
341
+ const mergeClasses = (...classes) => classes.filter((className, index, array) => {
342
+ return Boolean(className) && className.trim() !== "" && array.indexOf(className) === index;
343
+ }).join(" ").trim();
344
+ //#endregion
345
+ //#region node_modules/.pnpm/lucide-react@1.39.0_react@18.3.1/node_modules/lucide-react/dist/esm/shared/src/utils/toKebabCase.mjs
346
+ /**
347
+ * @license lucide-react v1.39.0 - ISC
348
+ *
349
+ * This source code is licensed under the ISC license.
350
+ * See the LICENSE file in the root directory of this source tree.
351
+ */
352
+ const toKebabCase = (string) => string.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
353
+ //#endregion
354
+ //#region node_modules/.pnpm/lucide-react@1.39.0_react@18.3.1/node_modules/lucide-react/dist/esm/shared/src/utils/toCamelCase.mjs
355
+ /**
356
+ * @license lucide-react v1.39.0 - ISC
357
+ *
358
+ * This source code is licensed under the ISC license.
359
+ * See the LICENSE file in the root directory of this source tree.
360
+ */
361
+ const toCamelCase = (string) => string.replace(/^([A-Z])|[\s-_]+(\w)/g, (match, p1, p2) => p2 ? p2.toUpperCase() : p1.toLowerCase());
362
+ //#endregion
363
+ //#region node_modules/.pnpm/lucide-react@1.39.0_react@18.3.1/node_modules/lucide-react/dist/esm/shared/src/utils/toPascalCase.mjs
364
+ /**
365
+ * @license lucide-react v1.39.0 - ISC
366
+ *
367
+ * This source code is licensed under the ISC license.
368
+ * See the LICENSE file in the root directory of this source tree.
369
+ */
370
+ const toPascalCase = (string) => {
371
+ const camelCase = toCamelCase(string);
372
+ return camelCase.charAt(0).toUpperCase() + camelCase.slice(1);
373
+ };
374
+ //#endregion
375
+ //#region node_modules/.pnpm/lucide-react@1.39.0_react@18.3.1/node_modules/lucide-react/dist/esm/defaultAttributes.mjs
376
+ /**
377
+ * @license lucide-react v1.39.0 - ISC
378
+ *
379
+ * This source code is licensed under the ISC license.
380
+ * See the LICENSE file in the root directory of this source tree.
381
+ */
382
+ var defaultAttributes = {
383
+ xmlns: "http://www.w3.org/2000/svg",
384
+ width: 24,
385
+ height: 24,
386
+ viewBox: "0 0 24 24",
387
+ fill: "none",
388
+ stroke: "currentColor",
389
+ strokeWidth: 2,
390
+ strokeLinecap: "round",
391
+ strokeLinejoin: "round"
392
+ };
393
+ //#endregion
394
+ //#region node_modules/.pnpm/lucide-react@1.39.0_react@18.3.1/node_modules/lucide-react/dist/esm/shared/src/utils/hasA11yProp.mjs
395
+ /**
396
+ * @license lucide-react v1.39.0 - ISC
397
+ *
398
+ * This source code is licensed under the ISC license.
399
+ * See the LICENSE file in the root directory of this source tree.
400
+ */
401
+ const hasA11yProp = (props) => {
402
+ for (const prop in props) if (prop.startsWith("aria-") || prop === "role" || prop === "title") return true;
403
+ return false;
404
+ };
405
+ //#endregion
406
+ //#region node_modules/.pnpm/lucide-react@1.39.0_react@18.3.1/node_modules/lucide-react/dist/esm/context.mjs
407
+ /**
408
+ * @license lucide-react v1.39.0 - ISC
409
+ *
410
+ * This source code is licensed under the ISC license.
411
+ * See the LICENSE file in the root directory of this source tree.
412
+ */
413
+ const LucideContext = (0, react.createContext)({});
414
+ const useLucideContext = () => (0, react.useContext)(LucideContext);
415
+ //#endregion
416
+ //#region node_modules/.pnpm/lucide-react@1.39.0_react@18.3.1/node_modules/lucide-react/dist/esm/Icon.mjs
417
+ /**
418
+ * @license lucide-react v1.39.0 - ISC
419
+ *
420
+ * This source code is licensed under the ISC license.
421
+ * See the LICENSE file in the root directory of this source tree.
422
+ */
423
+ const Icon = (0, react.forwardRef)(({ color, size, strokeWidth, absoluteStrokeWidth, className = "", children, iconNode, ...rest }, ref) => {
424
+ const { size: contextSize = 24, strokeWidth: contextStrokeWidth = 2, absoluteStrokeWidth: contextAbsoluteStrokeWidth = false, color: contextColor = "currentColor", className: contextClass = "" } = useLucideContext() ?? {};
425
+ const calculatedStrokeWidth = absoluteStrokeWidth ?? contextAbsoluteStrokeWidth ? Number(strokeWidth ?? contextStrokeWidth) * 24 / Number(size ?? contextSize) : strokeWidth ?? contextStrokeWidth;
426
+ return (0, react.createElement)("svg", {
427
+ ref,
428
+ ...defaultAttributes,
429
+ width: size ?? contextSize ?? defaultAttributes.width,
430
+ height: size ?? contextSize ?? defaultAttributes.height,
431
+ stroke: color ?? contextColor,
432
+ strokeWidth: calculatedStrokeWidth,
433
+ className: mergeClasses("lucide", contextClass, className),
434
+ ...!children && !hasA11yProp(rest) && { "aria-hidden": "true" },
435
+ ...rest
436
+ }, [...iconNode.map(([tag, attrs]) => (0, react.createElement)(tag, attrs)), ...Array.isArray(children) ? children : [children]]);
437
+ });
438
+ //#endregion
439
+ //#region node_modules/.pnpm/lucide-react@1.39.0_react@18.3.1/node_modules/lucide-react/dist/esm/createLucideIcon.mjs
440
+ /**
441
+ * @license lucide-react v1.39.0 - ISC
442
+ *
443
+ * This source code is licensed under the ISC license.
444
+ * See the LICENSE file in the root directory of this source tree.
445
+ */
446
+ const createLucideIcon = (iconName, iconNode) => {
447
+ const Component = (0, react.forwardRef)(({ className, ...props }, ref) => (0, react.createElement)(Icon, {
448
+ ref,
449
+ iconNode,
450
+ className: mergeClasses(`lucide-${toKebabCase(toPascalCase(iconName))}`, `lucide-${iconName}`, className),
451
+ ...props
452
+ }));
453
+ Component.displayName = toPascalCase(iconName);
454
+ return Component;
455
+ };
456
+ /**
457
+ * @license lucide-react v1.39.0 - ISC
458
+ *
459
+ * This source code is licensed under the ISC license.
460
+ * See the LICENSE file in the root directory of this source tree.
461
+ */
462
+ const Archive = createLucideIcon("archive", [
463
+ ["rect", {
464
+ width: "20",
465
+ height: "5",
466
+ x: "2",
467
+ y: "3",
468
+ rx: "1",
469
+ key: "1wp1u1"
470
+ }],
471
+ ["path", {
472
+ d: "M4 8v11a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8",
473
+ key: "1s80jp"
474
+ }],
475
+ ["path", {
476
+ d: "M10 12h4",
477
+ key: "a56b0p"
478
+ }]
479
+ ]);
480
+ /**
481
+ * @license lucide-react v1.39.0 - ISC
482
+ *
483
+ * This source code is licensed under the ISC license.
484
+ * See the LICENSE file in the root directory of this source tree.
485
+ */
486
+ const EyeOff = createLucideIcon("eye-off", [
487
+ ["path", {
488
+ d: "M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49",
489
+ key: "ct8e1f"
490
+ }],
491
+ ["path", {
492
+ d: "M14.084 14.158a3 3 0 0 1-4.242-4.242",
493
+ key: "151rxh"
494
+ }],
495
+ ["path", {
496
+ d: "M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143",
497
+ key: "13bj9a"
498
+ }],
499
+ ["path", {
500
+ d: "m2 2 20 20",
501
+ key: "1ooewy"
502
+ }]
503
+ ]);
504
+ /**
505
+ * @license lucide-react v1.39.0 - ISC
506
+ *
507
+ * This source code is licensed under the ISC license.
508
+ * See the LICENSE file in the root directory of this source tree.
509
+ */
510
+ const Eye = createLucideIcon("eye", [["path", {
511
+ d: "M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0",
512
+ key: "1nclc0"
513
+ }], ["circle", {
514
+ cx: "12",
515
+ cy: "12",
516
+ r: "3",
517
+ key: "1v7zrd"
518
+ }]]);
519
+ /**
520
+ * @license lucide-react v1.39.0 - ISC
521
+ *
522
+ * This source code is licensed under the ISC license.
523
+ * See the LICENSE file in the root directory of this source tree.
524
+ */
525
+ const MessageCircle = createLucideIcon("message-circle", [["path", {
526
+ d: "M2.992 16.342a2 2 0 0 1 .094 1.167l-1.065 3.29a1 1 0 0 0 1.236 1.168l3.413-.998a2 2 0 0 1 1.099.092 10 10 0 1 0-4.777-4.719",
527
+ key: "1sd12s"
528
+ }]]);
529
+ /**
530
+ * @license lucide-react v1.39.0 - ISC
531
+ *
532
+ * This source code is licensed under the ISC license.
533
+ * See the LICENSE file in the root directory of this source tree.
534
+ */
535
+ const Pin = createLucideIcon("pin", [["path", {
536
+ d: "M12 17v5",
537
+ key: "bb1du9"
538
+ }], ["path", {
539
+ d: "M9 10.76a2 2 0 0 1-1.11 1.79l-1.78.9A2 2 0 0 0 5 15.24V16a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-.76a2 2 0 0 0-1.11-1.79l-1.78-.9A2 2 0 0 1 15 10.76V7a1 1 0 0 1 1-1 2 2 0 0 0 0-4H8a2 2 0 0 0 0 4 1 1 0 0 1 1 1z",
540
+ key: "1nkz8b"
541
+ }]]);
542
+ /**
543
+ * @license lucide-react v1.39.0 - ISC
544
+ *
545
+ * This source code is licensed under the ISC license.
546
+ * See the LICENSE file in the root directory of this source tree.
547
+ */
548
+ const SquarePen = createLucideIcon("square-pen", [["path", {
549
+ d: "M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7",
550
+ key: "1m0v6g"
551
+ }], ["path", {
552
+ d: "M18.375 2.625a1 1 0 0 1 3 3l-9.013 9.014a2 2 0 0 1-.853.505l-2.873.84a.5.5 0 0 1-.62-.62l.84-2.873a2 2 0 0 1 .506-.852z",
553
+ key: "ohrbg2"
554
+ }]]);
368
555
  //#endregion
369
556
  //#region src/client/session-tree.tsx
370
557
  function PinIcon() {
371
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
372
- viewBox: "0 0 16 16",
373
- width: 16,
374
- height: 16,
558
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Pin, {
375
559
  "aria-hidden": "true",
376
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
377
- d: "M5.6 9.7 3.2 14M10.9 2.4c.9.9 1.1 2.2.5 3.3L10 7.6l2.3 2.3c.3.3.3.8 0 1.1l-.5.5c-.3.3-.8.3-1.1 0L8.4 9.2 6.5 10.6c-1.1.6-2.4.4-3.3-.5",
378
- fill: "none",
379
- stroke: "currentColor",
380
- strokeWidth: "1.35",
381
- strokeLinecap: "round",
382
- strokeLinejoin: "round"
383
- })
560
+ size: 16,
561
+ strokeWidth: 1.5
384
562
  });
385
563
  }
386
- function PinMark() {
387
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
388
- viewBox: "0 0 16 16",
564
+ function QuickArchiveIcon() {
565
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Archive, {
389
566
  "aria-hidden": "true",
390
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M5 2.5h6M5 2.5v2L3.75 6v1h8.5V6L11 4.5v-2M8 7v6" })
567
+ size: 16,
568
+ strokeWidth: 1.5
391
569
  });
392
570
  }
393
571
  function SessionHoverCard({ tip, onEnter, onLeave }) {
@@ -402,7 +580,10 @@ window.__ModuleLoader__.load({
402
580
  children: [
403
581
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
404
582
  className: "dcu-wb-tip-title",
405
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: tip.title }), tip.time !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
583
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
584
+ className: "dcu-wb-tip-title-main",
585
+ children: tip.title
586
+ }), tip.time !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
406
587
  className: "dcu-wb-tip-time",
407
588
  children: tip.time
408
589
  })]
@@ -431,15 +612,18 @@ window.__ModuleLoader__.load({
431
612
  label: t("sessions.rename"),
432
613
  icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEditOutline16, { size: 16 })
433
614
  },
434
- {
435
- id: "pin",
436
- label: t(options.pinned ? "sessions.unpin" : "sessions.pin"),
437
- icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PinIcon, {})
438
- },
439
615
  {
440
616
  id: "unread",
441
617
  label: t(options.unread ? "sessions.markRead" : "sessions.markUnread"),
442
- icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "dcu-wb-unread" })
618
+ icon: options.unread ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EyeOff, {
619
+ "aria-hidden": "true",
620
+ size: 16,
621
+ strokeWidth: 1.5
622
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Eye, {
623
+ "aria-hidden": "true",
624
+ size: 16,
625
+ strokeWidth: 1.5
626
+ })
443
627
  },
444
628
  {
445
629
  id: "archive",
@@ -454,10 +638,6 @@ window.__ModuleLoader__.load({
454
638
  id: "fork",
455
639
  label: t("sessions.fork"),
456
640
  icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconBranchOutline16, { size: 16 })
457
- },
458
- {
459
- type: "separator",
460
- id: "copy-separator"
461
641
  }
462
642
  ];
463
643
  if (options.includePath === true) items.push({
@@ -465,25 +645,35 @@ window.__ModuleLoader__.load({
465
645
  label: t("sessions.openPath"),
466
646
  icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderOpenOutline16, { size: 16 }),
467
647
  disabled: options.path === void 0
468
- }, {
469
- id: "copyPath",
470
- label: t("sessions.copyPath"),
471
- icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCopyOutline16, { size: 16 }),
472
- disabled: options.path === void 0
648
+ });
649
+ if (options.moveTargets !== void 0) items.push({
650
+ id: "moveWorkspace",
651
+ label: t("sessions.moveWorkspace"),
652
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderClose16, { size: 16 }),
653
+ disabled: options.moveTargets.length === 0,
654
+ submenu: options.moveTargets.map((target) => ({
655
+ id: target.id,
656
+ label: target.label,
657
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderClose16, { size: 16 })
658
+ }))
473
659
  });
474
660
  items.push({
475
- id: "copyTitle",
476
- label: t("sessions.copyTitle"),
477
- icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCopyOutline16, { size: 16 })
661
+ type: "separator",
662
+ id: "copy-separator"
478
663
  }, {
479
664
  id: "copyId",
480
665
  label: t("sessions.copyId"),
481
666
  icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconLinkOutline16, { size: 16 })
482
667
  }, {
483
- id: "copyLink",
484
- label: t("sessions.copyLink"),
485
- icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconShareOutline16, { size: 16 })
486
- }, {
668
+ id: "copyTitle",
669
+ label: t("sessions.copyTitle"),
670
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCopyOutline16, { size: 16 })
671
+ }, ...options.includePath === true ? [{
672
+ id: "copyPath",
673
+ label: t("sessions.copyPath"),
674
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCopyOutline16, { size: 16 }),
675
+ disabled: options.path === void 0
676
+ }] : [], {
487
677
  type: "separator",
488
678
  id: "delete-separator"
489
679
  }, {
@@ -494,9 +684,6 @@ window.__ModuleLoader__.load({
494
684
  });
495
685
  return items;
496
686
  }
497
- function copySessionLink(sessionId) {
498
- return sessionDeepLink(typeof window === "undefined" || window.location.origin === "null" ? "http://dsh.internal/" : `${window.location.origin}/`, sessionId);
499
- }
500
687
  /** 把右键落点收成 Menu 的锚点矩形,让列表贴着指针打开。 */
501
688
  function pointerMenuRect(x, y) {
502
689
  return new DOMRect(x, y, 0, 0);
@@ -535,7 +722,7 @@ window.__ModuleLoader__.load({
535
722
  });
536
723
  return null;
537
724
  }
538
- function SessionRow({ id, title, selected, menuOpen, pinned, unread, running, pendingInteraction, t, menuItems, onOpen, onMenuChange, onSelectAction, onPin, onArchive, onHover, onLeave, onContextMenu, menuPoint, subtitle, flat, draggable, dropActive, onDragStart, onDragEnd, onDragOver, onDrop }) {
725
+ function SessionRow({ id, title, selected, menuOpen, unread, running, pendingInteraction, t, menuItems, onOpen, onMenuChange, onSelectAction, onArchive, onHover, onLeave, onContextMenu, menuPoint, subtitle, flat, draggable, dropActive, onDragStart, onDragEnd, onDragOver, onDrop }) {
539
726
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
540
727
  className: `dcu-wb-session${selected ? " dcu-wb-selected" : ""}${menuOpen ? " dcu-wb-menu-open" : ""}${dropActive === true ? " dcu-wb-drop" : ""}${flat === true ? " dcu-wb-session-flat" : ""}`,
541
728
  role: "treeitem",
@@ -563,29 +750,15 @@ window.__ModuleLoader__.load({
563
750
  className: "dcu-wb-session-title",
564
751
  children: title.split(/\r?\n/)[0] ?? title
565
752
  }),
566
- pinned && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
567
- className: "dcu-wb-pin",
568
- "aria-label": t("sessions.pinned"),
569
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PinMark, {})
570
- }),
571
753
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionState, {
572
754
  pendingInteraction,
573
755
  unread,
574
756
  running,
575
757
  t
576
758
  }),
577
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
759
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
578
760
  className: "dcu-wb-quick-actions",
579
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
580
- type: "button",
581
- className: "dcu-wb-more dcu-wb-quick-pin",
582
- "aria-label": t(pinned ? "sessions.unpin" : "sessions.pin"),
583
- onClick: (event) => {
584
- event.stopPropagation();
585
- onPin();
586
- },
587
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PinMark, {})
588
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
761
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
589
762
  type: "button",
590
763
  className: "dcu-wb-more",
591
764
  "aria-label": t("sessions.archive"),
@@ -593,8 +766,8 @@ window.__ModuleLoader__.load({
593
766
  event.stopPropagation();
594
767
  onArchive();
595
768
  },
596
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconArchiveOutline20, { size: 16 })
597
- })]
769
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(QuickArchiveIcon, {})
770
+ })
598
771
  }),
599
772
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
600
773
  className: "dcu-wb-actions",
@@ -655,6 +828,10 @@ window.__ModuleLoader__.load({
655
828
  }
656
829
  //#endregion
657
830
  //#region src/client/session-pending.ts
831
+ const EMPTY_PENDING_INTERACTIONS = /* @__PURE__ */ new Map();
832
+ function useEmptySessionPendingInteraction(selector) {
833
+ return selector(EMPTY_PENDING_INTERACTIONS);
834
+ }
658
835
  function visiblePendingKind(kind) {
659
836
  switch (kind) {
660
837
  case "approval":
@@ -663,10 +840,151 @@ window.__ModuleLoader__.load({
663
840
  default: return;
664
841
  }
665
842
  }
843
+ /** 旧版 SessionSummary 的兼容读取;alpha.5 已将该状态迁移到独立 Store。 */
844
+ function legacyPendingInteraction(summary) {
845
+ if (typeof summary !== "object" || summary === null || !("pendingInteraction" in summary)) return void 0;
846
+ return summary.pendingInteraction;
847
+ }
848
+ function pendingInteractionForSession(sessionId, pendingInteractions, summaryKind) {
849
+ return visiblePendingKind(summaryKind) ?? visiblePendingKind(pendingInteractions.get(sessionId)?.kind);
850
+ }
851
+ const WORKSPACE_GROUP_ERROR_MESSAGES = {
852
+ "group-invalid": "分组信息无效。",
853
+ "workspace-invalid": "项目标识无效。",
854
+ "group-missing": "目标分组不存在。",
855
+ "order-anchor-missing": "排序锚点不存在。"
856
+ };
857
+ /** 以稳定错误码承载分组业务失败,诊断文案不参与界面控制逻辑。 */
858
+ var WorkspaceGroupError = class extends Error {
859
+ code;
860
+ name = "WorkspaceGroupError";
861
+ constructor(code, message = WORKSPACE_GROUP_ERROR_MESSAGES[code]) {
862
+ super(message);
863
+ this.code = code;
864
+ }
865
+ };
866
+ /** 对持久化分组做严格校验,避免一个项目同时出现在多个分组。 */
867
+ function parseWorkspaceGroups(value) {
868
+ if (!Array.isArray(value) || value.length > 100) return void 0;
869
+ const groupIds = /* @__PURE__ */ new Set();
870
+ const groupTitles = /* @__PURE__ */ new Set();
871
+ const workspaceIds = /* @__PURE__ */ new Set();
872
+ const groups = [];
873
+ for (const valueGroup of value) {
874
+ if (valueGroup === null || typeof valueGroup !== "object") return void 0;
875
+ const group = valueGroup;
876
+ const id = typeof group.id === "string" ? group.id.trim() : "";
877
+ const title = typeof group.title === "string" ? group.title.trim() : "";
878
+ if (id === "" || id.length > 128 || title === "" || title.length > 80) return void 0;
879
+ if (groupIds.has(id) || groupTitles.has(title.toLocaleLowerCase())) return void 0;
880
+ if (!Array.isArray(group.workspaceIds) || group.workspaceIds.length > 1e3) return void 0;
881
+ const normalizedIds = [...new Set(group.workspaceIds)];
882
+ if (!normalizedIds.every((workspaceId) => typeof workspaceId === "string" && workspaceId.trim() !== "" && workspaceId.length <= 256)) return void 0;
883
+ if (normalizedIds.some((workspaceId) => workspaceIds.has(workspaceId))) return void 0;
884
+ groupIds.add(id);
885
+ groupTitles.add(title.toLocaleLowerCase());
886
+ normalizedIds.forEach((workspaceId) => workspaceIds.add(workspaceId));
887
+ groups.push({
888
+ id,
889
+ title,
890
+ workspaceIds: normalizedIds
891
+ });
892
+ }
893
+ return groups;
894
+ }
895
+ /** 新建空分组;项目只有被显式移动后才会进入其中。 */
896
+ function createWorkspaceGroup(groups, group) {
897
+ const next = parseWorkspaceGroups([...groups, {
898
+ ...group,
899
+ workspaceIds: []
900
+ }]);
901
+ if (next === void 0) throw new WorkspaceGroupError("group-invalid");
902
+ return next;
903
+ }
904
+ /** 将项目放入指定分组;未传分组时退回未分组区。 */
905
+ function assignWorkspaceToGroup(groups, workspaceId, groupId) {
906
+ if (workspaceId.trim() === "" || workspaceId.length > 256) throw new WorkspaceGroupError("workspace-invalid");
907
+ if (groupId !== void 0 && !groups.some((group) => group.id === groupId)) throw new WorkspaceGroupError("group-missing");
908
+ return groups.map((group) => ({
909
+ ...group,
910
+ workspaceIds: group.id === groupId ? [...group.workspaceIds.filter((id) => id !== workspaceId), workspaceId] : group.workspaceIds.filter((id) => id !== workspaceId)
911
+ }));
912
+ }
913
+ /** 将项目插入目标分组的指定位置,同时从原分组移除。 */
914
+ function placeWorkspaceInGroup(groups, workspaceId, groupId, beforeId) {
915
+ if (workspaceId.trim() === "" || workspaceId.length > 256) throw new WorkspaceGroupError("workspace-invalid");
916
+ const target = groups.find((group) => group.id === groupId);
917
+ if (target === void 0) throw new WorkspaceGroupError("group-missing");
918
+ const targetIds = target.workspaceIds.filter((id) => id !== workspaceId);
919
+ const index = beforeId === void 0 ? targetIds.length : targetIds.indexOf(beforeId);
920
+ if (index < 0) throw new WorkspaceGroupError("order-anchor-missing");
921
+ targetIds.splice(index, 0, workspaceId);
922
+ return groups.map((group) => ({
923
+ ...group,
924
+ workspaceIds: group.id === groupId ? targetIds : group.workspaceIds.filter((id) => id !== workspaceId)
925
+ }));
926
+ }
927
+ /** 在同一分组内移动项目;省略锚点时放到分组末尾。 */
928
+ function moveWorkspaceGroupMember(groups, workspaceId, groupId, beforeId) {
929
+ const target = groups.find((group) => group.id === groupId);
930
+ if (target === void 0) throw new WorkspaceGroupError("group-missing");
931
+ if (!target.workspaceIds.includes(workspaceId)) return groups.map((group) => ({
932
+ ...group,
933
+ workspaceIds: [...group.workspaceIds]
934
+ }));
935
+ const remaining = target.workspaceIds.filter((id) => id !== workspaceId);
936
+ const index = beforeId === void 0 ? remaining.length : remaining.indexOf(beforeId);
937
+ if (index < 0) throw new WorkspaceGroupError("order-anchor-missing");
938
+ const workspaceIds = [...remaining];
939
+ workspaceIds.splice(index, 0, workspaceId);
940
+ return groups.map((group) => group.id === groupId ? {
941
+ ...group,
942
+ workspaceIds
943
+ } : {
944
+ ...group,
945
+ workspaceIds: [...group.workspaceIds]
946
+ });
947
+ }
948
+ /** 调整自定义分组顺序;省略锚点时移动到所有自定义分组末尾。 */
949
+ function moveWorkspaceGroup(groups, groupId, beforeGroupId) {
950
+ const moved = groups.find((group) => group.id === groupId);
951
+ if (moved === void 0) throw new WorkspaceGroupError("group-missing");
952
+ if (beforeGroupId === groupId) return groups.map((group) => ({
953
+ ...group,
954
+ workspaceIds: [...group.workspaceIds]
955
+ }));
956
+ const remaining = groups.filter((group) => group.id !== groupId);
957
+ const index = beforeGroupId === void 0 ? remaining.length : remaining.findIndex((group) => group.id === beforeGroupId);
958
+ if (index < 0) throw new WorkspaceGroupError("order-anchor-missing");
959
+ remaining.splice(index, 0, moved);
960
+ return remaining.map((group) => ({
961
+ ...group,
962
+ workspaceIds: [...group.workspaceIds]
963
+ }));
964
+ }
965
+ /** 删除分组时仅解除归属,不影响工作区本身。 */
966
+ function deleteWorkspaceGroup(groups, groupId) {
967
+ return groups.filter((group) => group.id !== groupId).map((group) => ({
968
+ ...group,
969
+ workspaceIds: [...group.workspaceIds]
970
+ }));
971
+ }
972
+ /** 工作区清单变化后清理失效归属,但保留空分组供用户继续使用。 */
973
+ function pruneWorkspaceGroups(groups, validIds) {
974
+ const valid = new Set(validIds);
975
+ return groups.map((group) => ({
976
+ ...group,
977
+ workspaceIds: group.workspaceIds.filter((id) => valid.has(id))
978
+ }));
979
+ }
980
+ function groupedWorkspaceIds(groups) {
981
+ return groups.flatMap((group) => group.workspaceIds);
982
+ }
666
983
  //#endregion
667
984
  //#region src/client/pinned-workspaces.ts
668
- /** 浏览器本地持久化键;置顶只影响本插件中的工作区分区。 */
985
+ /** 浏览器本地持久化键;用于工作区偏好的首帧恢复与 Host 故障兜底。 */
669
986
  const PINNED_WORKSPACES_STORAGE_KEY = "dsh-codex-ui.pinned-workspace-ids";
987
+ const WORKSPACE_GROUPS_STORAGE_KEY = "dsh-codex-ui.workspace-groups.v1";
670
988
  const WORKSPACE_PREFERENCES_ENDPOINT = "/api/michengai/codex-ui/preferences";
671
989
  /** 清理无效或重复的工作区标识。 */
672
990
  function normalizePinnedWorkspaceIds(ids) {
@@ -678,23 +996,29 @@ window.__ModuleLoader__.load({
678
996
  const next = ids.filter((id) => valid.has(id));
679
997
  return next.length === ids.length ? [...ids] : next;
680
998
  }
681
- /** Host 数据优先;首次升级没有 Host 文件时迁移旧 origin 的 localStorage;读取期间的用户操作永远优先。 */
682
- function resolvePinnedWorkspaceHydration(localIds, host, dirtyIds) {
683
- if (dirtyIds !== void 0) return {
684
- ids: normalizePinnedWorkspaceIds(dirtyIds),
999
+ /** Host 数据优先;旧 Host 或待同步分组使用本地缓存,读取期间的用户操作始终优先。 */
1000
+ function resolveWorkspacePreferencesHydration(local, host, dirty, localWorkspaceGroupsPendingHostSync = false) {
1001
+ if (dirty !== void 0) return {
1002
+ pinnedWorkspaceIds: normalizePinnedWorkspaceIds(dirty.pinnedWorkspaceIds),
1003
+ workspaceGroups: dirty.workspaceGroups,
685
1004
  writeHost: true
686
1005
  };
687
- if (host.exists) return {
688
- ids: normalizePinnedWorkspaceIds(host.pinnedWorkspaceIds),
689
- writeHost: false
690
- };
691
- const ids = normalizePinnedWorkspaceIds(localIds);
1006
+ if (host.exists) {
1007
+ const preserveLocalGroups = host.workspaceGroupsSupported === false || localWorkspaceGroupsPendingHostSync;
1008
+ return {
1009
+ pinnedWorkspaceIds: normalizePinnedWorkspaceIds(host.pinnedWorkspaceIds),
1010
+ workspaceGroups: preserveLocalGroups ? local.workspaceGroups : host.workspaceGroups,
1011
+ writeHost: localWorkspaceGroupsPendingHostSync && host.workspaceGroupsSupported !== false
1012
+ };
1013
+ }
1014
+ const pinnedWorkspaceIds = normalizePinnedWorkspaceIds(local.pinnedWorkspaceIds);
692
1015
  return {
693
- ids,
694
- writeHost: ids.length > 0
1016
+ pinnedWorkspaceIds,
1017
+ workspaceGroups: local.workspaceGroups,
1018
+ writeHost: pinnedWorkspaceIds.length > 0 || local.workspaceGroups.length > 0
695
1019
  };
696
1020
  }
697
- async function readHostPinnedWorkspaceIds(fetcher = fetch) {
1021
+ async function readHostWorkspacePreferences(fetcher = fetch) {
698
1022
  const response = await fetcher(WORKSPACE_PREFERENCES_ENDPOINT, {
699
1023
  method: "GET",
700
1024
  cache: "no-store",
@@ -704,22 +1028,35 @@ window.__ModuleLoader__.load({
704
1028
  const payload = await response.json();
705
1029
  if (payload === null || typeof payload !== "object") throw new Error("置顶偏好响应格式无效。");
706
1030
  const record = payload;
707
- if (typeof record.exists !== "boolean" || !Array.isArray(record.pinnedWorkspaceIds) || !record.pinnedWorkspaceIds.every((id) => typeof id === "string")) throw new Error("置顶偏好响应格式无效。");
1031
+ const workspaceGroupsSupported = Object.prototype.hasOwnProperty.call(record, "workspaceGroups");
1032
+ const workspaceGroups = workspaceGroupsSupported ? parseWorkspaceGroups(record.workspaceGroups) : [];
1033
+ if (typeof record.exists !== "boolean" || !Array.isArray(record.pinnedWorkspaceIds) || !record.pinnedWorkspaceIds.every((id) => typeof id === "string") || workspaceGroups === void 0) throw new Error("置顶偏好响应格式无效。");
708
1034
  return {
709
1035
  exists: record.exists,
710
- pinnedWorkspaceIds: normalizePinnedWorkspaceIds(record.pinnedWorkspaceIds)
1036
+ pinnedWorkspaceIds: normalizePinnedWorkspaceIds(record.pinnedWorkspaceIds),
1037
+ workspaceGroups,
1038
+ workspaceGroupsSupported
711
1039
  };
712
1040
  }
713
- async function writeHostPinnedWorkspaceIds(ids, fetcher = fetch) {
1041
+ async function writeHostWorkspacePreferences(pinnedWorkspaceIds, workspaceGroups, fetcher = fetch) {
1042
+ const groups = parseWorkspaceGroups([...workspaceGroups]);
1043
+ if (groups === void 0) throw new Error("工作区分组数据无效。");
714
1044
  const response = await fetcher(WORKSPACE_PREFERENCES_ENDPOINT, {
715
1045
  method: "PUT",
716
1046
  cache: "no-store",
717
1047
  headers: { "content-type": "application/json" },
718
- body: JSON.stringify({ pinnedWorkspaceIds: normalizePinnedWorkspaceIds(ids) }),
1048
+ body: JSON.stringify({
1049
+ pinnedWorkspaceIds: normalizePinnedWorkspaceIds(pinnedWorkspaceIds),
1050
+ workspaceGroups: groups
1051
+ }),
719
1052
  signal: AbortSignal.timeout(5e3)
720
1053
  });
721
1054
  if (!response.ok) throw new Error(`保存置顶偏好失败:HTTP ${response.status}`);
722
1055
  }
1056
+ /** 兼容旧调用方;新代码应同时写入分组。 */
1057
+ async function readHostPinnedWorkspaceIds(fetcher = fetch) {
1058
+ return readHostWorkspacePreferences(fetcher);
1059
+ }
723
1060
  /** 在置顶列表中切换一个工作区。 */
724
1061
  /** 把工作区插入置顶列表的指定位置;省略锚点时追加到末尾。 */
725
1062
  function insertPinnedWorkspace(ids, id, beforeId) {
@@ -750,6 +1087,80 @@ window.__ModuleLoader__.load({
750
1087
  storage.setItem(PINNED_WORKSPACES_STORAGE_KEY, JSON.stringify(normalizePinnedWorkspaceIds(ids)));
751
1088
  } catch {}
752
1089
  }
1090
+ /** 分组缓存用于首帧恢复和 Host 暂时不可用时兜底,并记录尚未完成的 Host 迁移。 */
1091
+ function readWorkspaceGroupsCache(storage) {
1092
+ if (storage === void 0) return {
1093
+ workspaceGroups: [],
1094
+ pendingHostSync: false
1095
+ };
1096
+ try {
1097
+ const value = JSON.parse(storage.getItem("dsh-codex-ui.workspace-groups.v1") ?? "{\"version\":1,\"workspaceGroups\":[],\"pendingHostSync\":false}");
1098
+ if (value === null || typeof value !== "object") return {
1099
+ workspaceGroups: [],
1100
+ pendingHostSync: false
1101
+ };
1102
+ const record = value;
1103
+ const workspaceGroups = record.version === 1 ? parseWorkspaceGroups(record.workspaceGroups) : void 0;
1104
+ if (workspaceGroups === void 0 || typeof record.pendingHostSync !== "boolean") return {
1105
+ workspaceGroups: [],
1106
+ pendingHostSync: false
1107
+ };
1108
+ return {
1109
+ workspaceGroups,
1110
+ pendingHostSync: record.pendingHostSync
1111
+ };
1112
+ } catch {
1113
+ return {
1114
+ workspaceGroups: [],
1115
+ pendingHostSync: false
1116
+ };
1117
+ }
1118
+ }
1119
+ function saveWorkspaceGroupsCache(storage, groups, pendingHostSync) {
1120
+ if (storage === void 0) return;
1121
+ const workspaceGroups = parseWorkspaceGroups([...groups]);
1122
+ if (workspaceGroups === void 0) return;
1123
+ try {
1124
+ storage.setItem(WORKSPACE_GROUPS_STORAGE_KEY, JSON.stringify({
1125
+ version: 1,
1126
+ workspaceGroups,
1127
+ pendingHostSync
1128
+ }));
1129
+ } catch {}
1130
+ }
1131
+ //#endregion
1132
+ //#region src/client/session-manager.ts
1133
+ const SESSION_UNREAD_STORAGE_KEY = "dsh.session-unread.v1";
1134
+ function normalizeSessionIds(ids) {
1135
+ return [...new Set(ids.filter((id) => id.trim() !== ""))];
1136
+ }
1137
+ function toggleSessionId(ids, sessionId) {
1138
+ return ids.includes(sessionId) ? ids.filter((id) => id !== sessionId) : [sessionId, ...ids.filter((id) => id !== sessionId)];
1139
+ }
1140
+ /** 返回本次刷新中结束、且不在当前会话中的后台会话。 */
1141
+ function completedBackgroundSessionIds(previous, current, currentSessionId) {
1142
+ return Object.entries(current).flatMap(([id, running]) => previous[id] === true && running !== true && id !== currentSessionId ? [id] : []);
1143
+ }
1144
+ function readSessionIds(storage, key) {
1145
+ if (storage === void 0) return [];
1146
+ try {
1147
+ const value = JSON.parse(storage.getItem(key) ?? "{\"version\":1,\"ids\":[]}");
1148
+ if (value === null || typeof value !== "object") return [];
1149
+ const ids = value.ids;
1150
+ return Array.isArray(ids) && ids.every((id) => typeof id === "string") ? normalizeSessionIds(ids) : [];
1151
+ } catch {
1152
+ return [];
1153
+ }
1154
+ }
1155
+ function writeSessionIds(storage, key, ids) {
1156
+ if (storage === void 0) return;
1157
+ try {
1158
+ storage.setItem(key, JSON.stringify({
1159
+ version: 1,
1160
+ ids: normalizeSessionIds(ids)
1161
+ }));
1162
+ } catch {}
1163
+ }
753
1164
  //#endregion
754
1165
  //#region src/client/hover-tip.ts
755
1166
  /** 把悬停卡片限制在视口内,避免贴边裁切。 */
@@ -768,12 +1179,12 @@ window.__ModuleLoader__.load({
768
1179
  };
769
1180
  }
770
1181
  /** Codex 会话卡片右上角的紧凑相对时间。 */
771
- function formatHoverTime(updatedAt, now = Date.now()) {
1182
+ function formatHoverTime(updatedAt, t, now = Date.now()) {
772
1183
  const seconds = Math.max(0, Math.round((now - updatedAt) / 1e3));
773
- if (seconds < 60) return "刚刚";
774
- if (seconds < 3600) return `${Math.floor(seconds / 60)}分`;
775
- if (seconds < 86400) return `${Math.floor(seconds / 3600)}小时`;
776
- return `${Math.floor(seconds / 86400)}天`;
1184
+ if (seconds < 60) return t("time.justNow");
1185
+ if (seconds < 3600) return t("time.minutes", { count: Math.floor(seconds / 60) });
1186
+ if (seconds < 86400) return t("time.hours", { count: Math.floor(seconds / 3600) });
1187
+ return t("time.days", { count: Math.floor(seconds / 86400) });
777
1188
  }
778
1189
  //#endregion
779
1190
  //#region src/client/conversation-bubbles.ts
@@ -1016,6 +1427,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1016
1427
  //#endregion
1017
1428
  //#region src/client/hover-shell.tsx
1018
1429
  const HOVER_TIP_SHOW_DELAY_MS = 1e3;
1430
+ const WORKSPACE_HOVER_CARD_ESTIMATED_HEIGHT = 152;
1019
1431
  const HoverDispatchContext = (0, react.createContext)({
1020
1432
  showTip: () => {},
1021
1433
  hideTip: () => {},
@@ -1036,7 +1448,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1036
1448
  tipRef.current = hoverTip;
1037
1449
  const place = (tip) => ({
1038
1450
  ...tip,
1039
- ...clampHoverCardPosition(tip.left, tip.top, 248, 148, window.innerWidth, window.innerHeight)
1451
+ ...clampHoverCardPosition(tip.left, tip.top, tip.kind === "workspace" ? 316 : 248, tip.kind === "workspace" ? WORKSPACE_HOVER_CARD_ESTIMATED_HEIGHT : 148, window.innerWidth, window.innerHeight)
1040
1452
  });
1041
1453
  const dispatch = (0, react.useMemo)(() => ({
1042
1454
  showTip: (tip, options) => {
@@ -1114,7 +1526,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1114
1526
  //#endregion
1115
1527
  //#region src/client/workspace-hover-card.tsx
1116
1528
  /** 只订阅悬停值,避免工作区树随鼠标移动整树重绘。 */
1117
- function WorkspaceHoverCard({ t, onEditWorkspace }) {
1529
+ function WorkspaceHoverCard({ t, onEditWorkspace, onToggleWorkspacePin }) {
1118
1530
  const hoverTip = useHoverValue();
1119
1531
  const { keepTip, hideTip, dismissTip } = useHoverDispatch();
1120
1532
  (0, react.useEffect)(() => {
@@ -1131,8 +1543,14 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1131
1543
  };
1132
1544
  }, [hoverTip, dismissTip]);
1133
1545
  if (hoverTip === void 0) return null;
1546
+ const workspace = hoverTip.kind === "workspace";
1547
+ const workspaceId = workspace ? hoverTip.id : void 0;
1548
+ const taskSummary = hoverTip.unreadCount !== void 0 && hoverTip.unreadCount > 0 ? t("workspace.taskSummary", {
1549
+ count: hoverTip.count ?? 0,
1550
+ unreadCount: hoverTip.unreadCount
1551
+ }) : t("workspace.taskCount", { count: hoverTip.count ?? 0 });
1134
1552
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1135
- className: "dcu-wb-tip",
1553
+ className: `dcu-wb-tip${workspace ? " dcu-wb-tip-workspace" : ""}`,
1136
1554
  style: {
1137
1555
  left: hoverTip.left,
1138
1556
  top: hoverTip.top
@@ -1143,59 +1561,69 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1143
1561
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1144
1562
  className: "dcu-wb-tip-title",
1145
1563
  children: [
1146
- hoverTip.kind === "workspace" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1564
+ workspace && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1147
1565
  className: "dcu-wb-folder",
1148
1566
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderClose16, { size: 16 })
1149
1567
  }),
1150
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: hoverTip.title }),
1151
- hoverTip.time !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1568
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1569
+ className: "dcu-wb-tip-title-main",
1570
+ children: hoverTip.title
1571
+ }),
1572
+ workspaceId !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1573
+ type: "button",
1574
+ className: "dcu-wb-tip-pin",
1575
+ "aria-label": t(hoverTip.pinned === true ? "workspace.unpin" : "workspace.pin"),
1576
+ onClick: () => {
1577
+ onToggleWorkspacePin(workspaceId);
1578
+ dismissTip();
1579
+ },
1580
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PinIcon, {})
1581
+ }) : hoverTip.time !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1152
1582
  className: "dcu-wb-tip-time",
1153
1583
  children: hoverTip.time
1154
1584
  })
1155
1585
  ]
1156
1586
  }),
1157
- hoverTip.kind === "workspace" && hoverTip.count !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1587
+ workspace && hoverTip.count !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1158
1588
  className: "dcu-wb-tip-meta",
1159
- children: t("workspace.taskCount", { count: hoverTip.count })
1589
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MessageCircle, {
1590
+ "aria-hidden": "true",
1591
+ size: 16,
1592
+ strokeWidth: 1.5
1593
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: taskSummary })]
1160
1594
  }),
1161
- hoverTip.kind === "workspace" && hoverTip.path !== void 0 && hoverTip.path !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1162
- className: "dcu-wb-tip-row",
1595
+ workspace && hoverTip.path !== void 0 && hoverTip.path !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1596
+ className: "dcu-wb-tip-row dcu-wb-tip-path",
1163
1597
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1164
1598
  className: "dcu-wb-folder",
1165
1599
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderClose16, { size: 16 })
1166
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: hoverTip.path })]
1600
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1601
+ className: "dcu-wb-tip-path-copy",
1602
+ children: hoverTip.path
1603
+ })]
1167
1604
  }),
1168
- hoverTip.kind === "session" && hoverTip.project !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1605
+ !workspace && hoverTip.project !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1169
1606
  className: "dcu-wb-tip-row",
1170
1607
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1171
1608
  className: "dcu-wb-folder",
1172
1609
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderClose16, { size: 16 })
1173
1610
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: hoverTip.project })]
1174
1611
  }),
1175
- hoverTip.kind === "session" && hoverTip.branch !== void 0 && hoverTip.branch !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1612
+ !workspace && hoverTip.branch !== void 0 && hoverTip.branch !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1176
1613
  className: "dcu-wb-tip-row",
1177
1614
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1178
1615
  className: "dcu-wb-folder",
1179
1616
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconBranchOutline16, { size: 16 })
1180
1617
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: hoverTip.branch })]
1181
1618
  }),
1182
- hoverTip.kind === "workspace" && hoverTip.id !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "dcu-wb-tip-sep" }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1619
+ workspaceId !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "dcu-wb-tip-sep" }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1183
1620
  type: "button",
1184
1621
  className: "dcu-wb-tip-edit",
1185
1622
  onClick: () => {
1186
- onEditWorkspace(hoverTip.id ?? "", hoverTip.title);
1623
+ onEditWorkspace(workspaceId, hoverTip.title);
1187
1624
  dismissTip();
1188
1625
  },
1189
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
1190
- viewBox: "0 0 16 16",
1191
- width: 16,
1192
- height: 16,
1193
- "aria-hidden": "true",
1194
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1195
- fill: "currentColor",
1196
- d: "M8 1.4A6.6 6.6 0 1 0 8 14.6 6.6 6.6 0 0 0 8 1.4Zm0 1.4a5.2 5.2 0 1 1 0 10.4A5.2 5.2 0 0 1 8 2.8Zm-.7 2.3h1.4v3.05l2.2 1.3-.7 1.18L7.3 9.1V5.1Z"
1197
- })
1198
- }), t("workspace.edit")]
1626
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSettingsOutline16, { size: 16 }), t("workspace.edit")]
1199
1627
  })] })
1200
1628
  ]
1201
1629
  });
@@ -1211,7 +1639,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1211
1639
  return name !== void 0 && name !== "" ? name : title.trim();
1212
1640
  }
1213
1641
  /** 把定时运行会话按任务名收成和任务树一样的分组。 */
1214
- function groupScheduleSessions(items) {
1642
+ function groupScheduleSessions(items, locale = "zh-CN") {
1215
1643
  const groups = /* @__PURE__ */ new Map();
1216
1644
  for (const item of items) {
1217
1645
  if (!isScheduleSession(item.id, item.title)) continue;
@@ -1227,7 +1655,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1227
1655
  return [...groups.values()].map((group) => ({
1228
1656
  ...group,
1229
1657
  sessions: [...group.sessions].sort((left, right) => (right.updatedAt ?? 0) - (left.updatedAt ?? 0))
1230
- })).sort((left, right) => left.label.localeCompare(right.label, "zh-CN", {
1658
+ })).sort((left, right) => left.label.localeCompare(right.label, locale, {
1231
1659
  numeric: true,
1232
1660
  sensitivity: "base"
1233
1661
  }));
@@ -1296,10 +1724,39 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1296
1724
  return item === void 0 ? [] : [item];
1297
1725
  });
1298
1726
  }
1727
+ /** 展开移动目标的全部父级,保证页面重载并选中会话后该行仍然可见。 */
1728
+ function expandedForSessionMove(current, target) {
1729
+ const next = { ...current };
1730
+ if (target.pinned) {
1731
+ next["section:pinned"] = true;
1732
+ next[`pin:${target.workspaceId}`] = true;
1733
+ return next;
1734
+ }
1735
+ next["section:projects"] = true;
1736
+ next[target.workspaceId] = true;
1737
+ if (target.groupId !== void 0) next[`workspace-group:${target.groupId}`] = true;
1738
+ else if (target.hasGroups) next["workspace-group:ungrouped"] = true;
1739
+ return next;
1740
+ }
1741
+ /** 仅提供真正改变归属的目标;未分组目标固定排在自定义分组之后。 */
1742
+ function workspaceGroupMoveTargets(groups, workspaceId) {
1743
+ const currentGroupId = groups.find((group) => group.workspaceIds.includes(workspaceId))?.id;
1744
+ const targets = groups.filter((group) => group.id !== currentGroupId).map((group) => ({
1745
+ groupId: group.id,
1746
+ title: group.title
1747
+ }));
1748
+ if (currentGroupId !== void 0) targets.push({
1749
+ groupId: void 0,
1750
+ title: void 0
1751
+ });
1752
+ return targets;
1753
+ }
1299
1754
  const SESSION_DRAG_TYPE = "application/x-dcu-session";
1300
1755
  const WORKSPACE_DRAG_TYPE = "application/x-dcu-workspace";
1756
+ const WORKSPACE_GROUP_DRAG_TYPE = "application/x-dcu-workspace-group";
1301
1757
  const SESSION_DRAG_PREFIX = "dcu-session:";
1302
1758
  const WORKSPACE_DRAG_PREFIX = "dcu-workspace:";
1759
+ const WORKSPACE_GROUP_DRAG_PREFIX = "dcu-workspace-group:";
1303
1760
  function textPayload(data) {
1304
1761
  try {
1305
1762
  return data?.getData("text/plain") ?? "";
@@ -1318,6 +1775,11 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1318
1775
  data.setData("text/plain", `${WORKSPACE_DRAG_PREFIX}${workspaceId}`);
1319
1776
  data.setData(WORKSPACE_DRAG_TYPE, workspaceId);
1320
1777
  }
1778
+ function writeWorkspaceGroupDrag(data, groupId, title) {
1779
+ data.effectAllowed = "move";
1780
+ data.setData("text/plain", `${WORKSPACE_GROUP_DRAG_PREFIX}${groupId}`);
1781
+ data.setData(WORKSPACE_GROUP_DRAG_TYPE, groupId);
1782
+ }
1321
1783
  function readSessionDrag(data, fallback) {
1322
1784
  try {
1323
1785
  const typed = data?.getData(SESSION_DRAG_TYPE);
@@ -1327,9 +1789,23 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1327
1789
  if (text.startsWith(SESSION_DRAG_PREFIX)) return text.slice(12);
1328
1790
  return fallback !== void 0 && fallback.trim() !== "" ? fallback : void 0;
1329
1791
  }
1792
+ function sessionDropAction(sourceWorkspaceId, targetWorkspaceId) {
1793
+ return sourceWorkspaceId !== void 0 && sourceWorkspaceId === targetWorkspaceId ? "reorder" : "move";
1794
+ }
1795
+ function readWorkspaceGroupDrag(data, fallback) {
1796
+ if (readSessionDrag(data) !== void 0) return void 0;
1797
+ try {
1798
+ const typed = data?.getData(WORKSPACE_GROUP_DRAG_TYPE);
1799
+ if (typed !== void 0 && typed.trim() !== "") return typed;
1800
+ } catch {}
1801
+ const text = textPayload(data);
1802
+ if (text.startsWith(WORKSPACE_GROUP_DRAG_PREFIX)) return text.slice(20);
1803
+ return fallback !== void 0 && fallback.trim() !== "" ? fallback : void 0;
1804
+ }
1330
1805
  /** 会话拖拽优先;有会话载荷时不得再把父项目置顶。 */
1331
1806
  function readWorkspaceDrag(data, fallback) {
1332
1807
  if (readSessionDrag(data) !== void 0) return void 0;
1808
+ if (readWorkspaceGroupDrag(data) !== void 0) return void 0;
1333
1809
  try {
1334
1810
  const typed = data?.getData(WORKSPACE_DRAG_TYPE);
1335
1811
  if (typed !== void 0 && typed.trim() !== "") return typed;
@@ -1373,60 +1849,364 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1373
1849
  } catch {}
1374
1850
  }
1375
1851
  //#endregion
1376
- //#region src/client/CodexWorkspaceBrowser.tsx
1377
- const stylesheet$4 = `
1378
- .dcu-wb{--dcu-wb-inset:4px;display:flex;flex:1;min-height:0;flex-direction:column;padding:4px var(--dcu-wb-inset) 8px;color:var(--dcu-sidebar-primary)}
1379
- .dcu-wb *{box-sizing:border-box}
1380
- .dcu-wb-tree{flex:1;min-height:0;overflow-y:auto;padding-bottom:16px;scrollbar-gutter:stable;user-select:none;-webkit-user-select:none}
1381
- .dcu-wb-section+.dcu-wb-section{margin-top:12px}
1382
- .dcu-wb-section-head{position:relative;display:flex;align-items:center;min-height:24px;padding:0 4px;border-radius:6px}.dcu-wb-section-head:hover .dcu-wb-actions{display:flex}.dcu-wb-section-label{display:flex;align-items:center;gap:4px;flex:1;min-width:0;min-height:24px;border:0;padding:2px 4px;background:transparent;color:var(--dcu-sidebar-tertiary);font:13px/20px var(--dcu-font,inherit);font-weight:400;letter-spacing:.02em;text-align:left;cursor:pointer}.dcu-wb-section-caret{display:grid;place-items:center;flex:none;width:12px;height:12px;color:currentColor;opacity:0;transform:rotate(0deg);transform-origin:50% 50%;transition:opacity 120ms ease,transform 160ms ease}.dcu-wb-section-caret svg{display:block}.dcu-wb-section-head:hover .dcu-wb-section-caret,.dcu-wb-section-label:focus-visible .dcu-wb-section-caret{opacity:.78}.dcu-wb-section-label[aria-expanded=true] .dcu-wb-section-caret{transform:rotate(90deg)}.dcu-wb-section-body{display:block}.dcu-wb-section-body[data-open=false]{display:none}.dcu-wb-section-body>div{min-height:0}.dcu-wb-section-body[data-open=true]>div{animation:dcu-wb-section-in 140ms cubic-bezier(.16,1,.3,1)}@keyframes dcu-wb-section-in{from{opacity:0;transform:translateY(-3px)}to{opacity:1;transform:none}}@media (prefers-reduced-motion:reduce){.dcu-wb-section-caret{transition:none}.dcu-wb-section-body[data-open=true]>div{animation:none}}
1383
- .dcu-wb-project{position:relative}
1384
- .dcu-wb-project-head,.dcu-wb-session{position:relative;display:flex;align-items:center;gap:6px;width:100%;border-radius:8px;padding:0 8px;color:var(--dcu-sidebar-primary);cursor:pointer}
1385
- .dcu-wb-project-head{height:28px;background:transparent;font:inherit;text-align:left}
1386
- .dcu-wb-project-head:hover,.dcu-wb-project-head.dcu-wb-menu-open,.dcu-wb-session:hover,.dcu-wb-session.dcu-wb-selected,.dcu-wb-session.dcu-wb-menu-open{background:var(--dcu-sidebar-hover)}.dcu-wb-project-head+.dcu-wb-session,.dcu-wb-project-head+.dcu-wb-nochat{margin-top:0}.dcu-wb-session+.dcu-wb-session{margin-top:2px}
1387
- .dcu-wb-project-head[draggable=true],.dcu-wb-session[draggable=true]{cursor:grab}
1388
- .dcu-wb-project-head[draggable=true]:active,.dcu-wb-session[draggable=true]:active{cursor:grabbing}
1389
- .dcu-wb-section,.dcu-wb-section:focus,.dcu-wb-section:focus-visible,.dcu-wb-project-head:focus,.dcu-wb-session:focus{outline:0}.dcu-wb-pin-end,.dcu-wb-pin-start{position:relative;height:8px}.dcu-wb-project.dcu-wb-drop::before,.dcu-wb-session.dcu-wb-drop::before,.dcu-wb-pin-end.dcu-wb-drop::before,.dcu-wb-pin-start.dcu-wb-drop::before{content:"";position:absolute;z-index:2;left:8px;right:8px;top:-4px;height:8px;pointer-events:none;background:radial-gradient(circle at 4px 50%,var(--dsw-alias-state-business-primary) 3.25px,transparent 3.45px),linear-gradient(var(--dsw-alias-state-business-primary),var(--dsw-alias-state-business-primary)) 10px 50%/calc(100% - 10px) 2px no-repeat}.dcu-wb-pin-start.dcu-wb-drop::before,.dcu-wb-project.dcu-wb-drop::before,.dcu-wb-session.dcu-wb-drop::before{top:0}.dcu-wb-project.dcu-wb-drop-after::before,.dcu-wb-session.dcu-wb-drop-after::before{top:auto;bottom:0}.dcu-wb-dragging{opacity:.28}.dcu-wb-drag-ghost{position:fixed;top:-120px;left:-240px;z-index:10040;max-width:220px;height:32px;padding:0 10px;border:1px solid var(--dcu-sidebar-border);border-radius:8px;background:var(--dcu-sidebar-hover);color:var(--dcu-sidebar-primary);font:13px/32px var(--dcu-font,inherit);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;pointer-events:none;opacity:0}
1390
- .dcu-wb-folder{display:grid;place-items:center;flex:none;width:16px;height:20px;color:var(--dcu-sidebar-icon)}.dcu-wb-brand{display:block;width:16px;height:16px}
1391
- .dcu-wb-project-current .dcu-wb-folder{color:var(--dsw-alias-state-business-primary)}
1392
- .dcu-wb-project-title,.dcu-wb-session-title{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:14px;line-height:20px}
1393
- .dcu-wb-project-title{flex:1;font-weight:400;color:var(--dcu-sidebar-primary)}
1394
- .dcu-wb-session{position:relative;min-height:32px;gap:0;padding-left:28px}
1395
- .dcu-wb-session-title{flex:1;margin-left:0}.dcu-wb-session-copy{flex:1;min-width:0;display:flex;flex-direction:column;justify-content:center}.dcu-wb-session-sub{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dcu-sidebar-tertiary);font-size:12px;line-height:16px}.dcu-wb-session-flat,.dcu-wb-session-top{padding-left:8px}
1396
- .dcu-wb-pin{display:grid;place-items:center;flex:none;width:16px;height:20px;margin-left:6px;color:var(--dcu-sidebar-tertiary)}
1397
- .dcu-wb-pin svg,.dcu-wb-quick-pin svg{width:13px;height:13px;fill:none;stroke:currentColor;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.4}
1398
- .dcu-wb-actions{display:none;align-items:center;gap:8px;flex:none}
1399
- .dcu-wb-quick-actions{position:absolute;right:8px;top:50%;display:flex;align-items:center;gap:2px;opacity:0;pointer-events:none;transform:translateY(-50%);background:var(--dcu-sidebar-hover);border-radius:6px}
1400
- .dcu-wb-project-head:hover .dcu-wb-actions,.dcu-wb-project-head.dcu-wb-menu-open .dcu-wb-actions,.dcu-wb-session.dcu-wb-menu-open .dcu-wb-actions{display:flex}
1401
- .dcu-wb-session:hover .dcu-wb-quick-actions,.dcu-wb-session.dcu-wb-menu-open .dcu-wb-quick-actions{opacity:1;pointer-events:auto}
1402
- .dcu-wb-more{display:grid;place-items:center;width:20px;height:20px;border:0;border-radius:4px;padding:0;background:transparent;color:var(--dcu-sidebar-tertiary);cursor:pointer}
1403
- .dcu-wb-more:hover{color:var(--dcu-sidebar-primary)}
1404
- .dcu-wb-context-anchor{opacity:0;pointer-events:none}
1405
- .dcu-wb-tip{position:fixed;z-index:10050;min-width:220px;max-width:280px;padding:10px 12px;border:1px solid var(--dcu-sidebar-border);border-radius:12px;background:var(--dcu-tip-bg,#fff);box-shadow:var(--dcu-tip-shadow,0 8px 28px rgba(31,39,36,.16));color:var(--dcu-sidebar-primary);animation:dcu-tip-in 180ms cubic-bezier(.16,1,.3,1)}@keyframes dcu-tip-in{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}@media (prefers-reduced-motion:reduce){.dcu-wb-tip{animation:none}}.dcu-wb-tip-title{display:flex;align-items:center;justify-content:space-between;gap:12px;min-width:0;font-size:14px;line-height:20px;font-weight:500}.dcu-wb-tip-time{flex:none;color:var(--dcu-sidebar-tertiary);font-size:12px;line-height:18px;font-weight:400}.dcu-wb-tip-title>span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dcu-wb-tip-meta{margin-top:6px;color:var(--dcu-sidebar-secondary);font-size:12px;line-height:18px}.dcu-wb-tip-row{display:flex;align-items:center;gap:6px;min-width:0;margin-top:4px;color:var(--dcu-sidebar-secondary);font-size:12px;line-height:18px}.dcu-wb-tip-row>span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dcu-wb-tip-sep{height:1px;margin:8px 0;background:var(--dcu-sidebar-border)}.dcu-wb-tip-edit{display:flex;align-items:center;gap:8px;width:100%;border:0;padding:4px 0;background:transparent;color:var(--dcu-sidebar-primary);font:12px/18px var(--dcu-font,inherit);cursor:pointer}
1406
- .dcu-wb-unread{flex:none;width:7px;height:7px;margin-left:8px;margin-right:8px;border-radius:50%;background:var(--dsw-alias-state-business-primary)}
1407
- .dcu-wb-pending{display:flex;align-items:center;gap:4px;flex:none;max-width:88px;margin-left:8px;margin-right:8px;color:var(--dsw-alias-state-warn-label,#b45309);font-size:11px;line-height:16px;white-space:nowrap}.dcu-wb-pending-dot{flex:none;width:7px;height:7px;border-radius:50%;background:var(--dsw-alias-state-warn-primary,#f59e0b)}.dcu-wb-pending-label{min-width:0;overflow:hidden;text-overflow:ellipsis}
1408
- .dcu-wb-empty{padding:14px 8px;color:var(--dcu-sidebar-tertiary);font-size:13px}
1409
- .dcu-wb-error{margin:4px 0;padding:6px 8px;border-radius:6px;background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 12%,transparent);color:var(--dsw-alias-state-error-primary);font-size:12px}
1410
- .dcu-wb-rail{display:none}
1411
- .dcu-wb-rename-actions{display:flex;justify-content:flex-end;gap:8px}
1852
+ //#region src/client/workspace-compat.ts
1853
+ function hasStartSession(value) {
1854
+ return value !== null && typeof value === "object" && "startSession" in value && typeof value.startSession === "function";
1855
+ }
1856
+ function hasConnectWorkspace(value) {
1857
+ return value !== null && typeof value === "object" && "connectWorkspace" in value && typeof value.connectWorkspace === "function";
1858
+ }
1859
+ /** 与 DSH 官方最近工作区策略一致;时间相同时保留 Host 工作区顺序。 */
1860
+ function recentWorkspaceId(workspaces, sessions) {
1861
+ let selected;
1862
+ let selectedTime = Number.NEGATIVE_INFINITY;
1863
+ for (const workspace of workspaces) {
1864
+ let latest = Number.NEGATIVE_INFINITY;
1865
+ for (const sessionId of workspace.sessionIds) {
1866
+ const session = sessions[sessionId];
1867
+ if (session !== void 0) latest = Math.max(latest, session.updatedAt);
1868
+ }
1869
+ if (latest === Number.NEGATIVE_INFINITY) latest = Date.parse(workspace.createdAt);
1870
+ if (selected === void 0 || latest > selectedTime) {
1871
+ selected = workspace.workspaceId;
1872
+ selectedTime = latest;
1873
+ }
1874
+ }
1875
+ return selected;
1876
+ }
1877
+ /** 旧版提供聚合字段;alpha.5 起由工作区和会话两个 Store 分别报告基线阶段。 */
1878
+ function workspaceBaselinesReady(workspaces, sessions) {
1879
+ if ("baselinesReady" in workspaces) return workspaces.baselinesReady === true;
1880
+ return "phase" in workspaces && workspaces.phase === "ready" && "phase" in sessions && sessions.phase === "ready";
1881
+ }
1882
+ //#endregion
1883
+ //#region src/client/user-error.ts
1884
+ const WORKSPACE_GROUP_ERROR_KEYS = {
1885
+ "group-invalid": "errors.groupInvalid",
1886
+ "workspace-invalid": "errors.workspaceInvalid",
1887
+ "group-missing": "errors.groupMissing",
1888
+ "order-anchor-missing": "errors.orderAnchorMissing"
1889
+ };
1890
+ const HOST_ACTION_DEFAULT_KEYS = {
1891
+ rename: "sessions.renameFailed",
1892
+ delete: "sessions.deleteFailed",
1893
+ archive: "sessions.archiveFailed",
1894
+ fork: "sessions.forkFailed"
1895
+ };
1896
+ const HOST_ACTION_ERROR_KEYS = {
1897
+ rename: {
1898
+ "session/not-found": "sessions.unknown",
1899
+ "session/title-invalid": "sessions.renameInvalid"
1900
+ },
1901
+ delete: { "session/not-found": "sessions.unknown" },
1902
+ archive: { "session/not-found": "sessions.unknown" },
1903
+ fork: {
1904
+ "session/not-found": "sessions.unknown",
1905
+ "session/fork-unavailable": "sessions.forkUnavailable"
1906
+ }
1907
+ };
1908
+ const INSTALL_ERROR_KEYS = [
1909
+ [/没有进入当前 Profile|did not enter this profile/i, "about.installUnchanged"],
1910
+ [/完全退出桌面端|无法覆盖正在运行的插件文件|running plugin files|quit DSH Desktop/i, "about.installExitDesktop"],
1911
+ [/pnpm 仓库不一致|pnpm store do not match/i, "about.installStoreMismatch"],
1912
+ [/构建脚本策略|build-script policy/i, "about.installBuildPolicy"],
1913
+ [/找不到 pnpm|pnpm (?:is )?not (?:available|found)/i, "about.installPnpmMissing"],
1914
+ [/安装超时|installation timed out/i, "about.installTimeout"],
1915
+ [/服务尚未就绪|Profile 信息无效|dependency service is not ready/i, "about.installServiceUnavailable"]
1916
+ ];
1917
+ /** 仅显式构造的本地化错误允许原样进入界面。 */
1918
+ var UserFacingError = class extends Error {};
1919
+ /** 保留 Host 结构化失败及操作语境,界面只消费受控映射。 */
1920
+ var HostActionError = class extends Error {
1921
+ action;
1922
+ reason;
1923
+ name = "HostActionError";
1924
+ constructor(action, reason) {
1925
+ super(`Host ${action} 操作失败`);
1926
+ this.action = action;
1927
+ this.reason = reason;
1928
+ }
1929
+ };
1930
+ function objectRecord(value) {
1931
+ return value !== null && typeof value === "object" ? value : void 0;
1932
+ }
1933
+ /** DSH 明确要求跨 bundle 按结构标记识别 RemoteFailure,不能使用 instanceof。 */
1934
+ function remoteFailureCode(value) {
1935
+ const record = objectRecord(value);
1936
+ if (record === void 0) return void 0;
1937
+ if (record.isDSHRemoteError === true && typeof record.code === "string") return record.code;
1938
+ return record.rpcError === value ? void 0 : remoteFailureCode(record.rpcError);
1939
+ }
1940
+ function diagnosticMessage(value) {
1941
+ if (value instanceof Error) return value.message;
1942
+ const record = objectRecord(value);
1943
+ return typeof record?.message === "string" ? record.message : void 0;
1944
+ }
1945
+ function hostActionErrorKey(error) {
1946
+ const code = remoteFailureCode(error.reason);
1947
+ if (code !== void 0) {
1948
+ const key = HOST_ACTION_ERROR_KEYS[error.action][code];
1949
+ if (key !== void 0) return key;
1950
+ }
1951
+ if (error.action === "delete" && diagnosticMessage(error.reason)?.includes("UNKNOWN_SESSION") === true) return "sessions.unknown";
1952
+ return HOST_ACTION_DEFAULT_KEYS[error.action];
1953
+ }
1954
+ /** 将业务错误映射为当前语言,未知底层错误统一脱敏。 */
1955
+ function userErrorText(error, t) {
1956
+ if (error instanceof UserFacingError) return error.message;
1957
+ if (error instanceof WorkspaceGroupError) return t(WORKSPACE_GROUP_ERROR_KEYS[error.code]);
1958
+ if (error instanceof HostActionError) return t(hostActionErrorKey(error));
1959
+ return t("errors.generic");
1960
+ }
1961
+ /** 安装错误保留用户可执行的处理建议,但不直接展示 Host 原文。 */
1962
+ function installErrorText(error, t) {
1963
+ const message = error instanceof Error ? error.message : String(error);
1964
+ return t(INSTALL_ERROR_KEYS.find(([pattern]) => pattern.test(message))?.[1] ?? "about.installFailed");
1965
+ }
1966
+ //#endregion
1967
+ //#region src/client/session-move.ts
1968
+ const SESSION_MOVE_ENDPOINT = "/api/michengai/codex-ui/session-move";
1969
+ const MOVE_ACTION_PREFIX = "move-session:";
1970
+ const SESSION_MOVE_ERROR_KEYS = {
1971
+ "session-move/unavailable": "sessions.moveUnavailable",
1972
+ "session-move/service-unavailable": "sessions.moveUnavailable",
1973
+ "session-move/session-not-found": "sessions.moveNotFound",
1974
+ "session-move/workspace-not-found": "sessions.moveNotFound",
1975
+ "session-move/subagent-unsupported": "sessions.moveSubagent",
1976
+ "session-move/busy": "sessions.moveBusy",
1977
+ "session-move/rollback-failed": "sessions.moveRollbackFailed"
1978
+ };
1979
+ var SessionMoveRequestError = class extends Error {
1980
+ code;
1981
+ constructor(code) {
1982
+ super(code);
1983
+ this.code = code;
1984
+ this.name = "SessionMoveRequestError";
1985
+ }
1986
+ };
1987
+ /** 将 Host 公开错误码映射为受控 i18n 键,未知错误统一使用安全兜底。 */
1988
+ function sessionMoveErrorKey(code) {
1989
+ return SESSION_MOVE_ERROR_KEYS[code] ?? "sessions.moveFailed";
1990
+ }
1991
+ function moveSessionActionId(workspaceId) {
1992
+ return `${MOVE_ACTION_PREFIX}${encodeURIComponent(workspaceId)}`;
1993
+ }
1994
+ function parseMoveSessionActionId(actionId) {
1995
+ if (!actionId.startsWith(MOVE_ACTION_PREFIX)) return void 0;
1996
+ try {
1997
+ const workspaceId = decodeURIComponent(actionId.slice(13));
1998
+ return workspaceId === "" ? void 0 : workspaceId;
1999
+ } catch {
2000
+ return;
2001
+ }
2002
+ }
2003
+ /** 按 Host 项目顺序生成目标列表,并排除会话当前所属项目。 */
2004
+ function sessionMoveTargets(workspaces, sessionId) {
2005
+ return workspaces.filter((workspace) => !workspace.sessionIds.includes(sessionId)).map((workspace) => ({
2006
+ id: workspace.workspaceId,
2007
+ label: workspace.title || workspace.workspaceId
2008
+ }));
2009
+ }
2010
+ async function requestSessionMove(sessionId, targetWorkspaceId, fetcher = fetch) {
2011
+ let response;
2012
+ try {
2013
+ response = await fetcher(SESSION_MOVE_ENDPOINT, {
2014
+ method: "POST",
2015
+ cache: "no-store",
2016
+ headers: { "content-type": "application/json" },
2017
+ body: JSON.stringify({
2018
+ sessionId,
2019
+ targetWorkspaceId
2020
+ }),
2021
+ signal: AbortSignal.timeout(3e4)
2022
+ });
2023
+ } catch {
2024
+ throw new SessionMoveRequestError("session-move/unavailable");
2025
+ }
2026
+ let payload;
2027
+ try {
2028
+ payload = await response.json();
2029
+ } catch {
2030
+ throw new SessionMoveRequestError("session-move/unavailable");
2031
+ }
2032
+ const code = payload !== null && typeof payload === "object" && typeof payload.code === "string" ? payload.code : void 0;
2033
+ if (!response.ok || payload === null || typeof payload !== "object" || payload.ok !== true) throw new SessionMoveRequestError(code ?? "session-move/failed");
2034
+ }
2035
+ /** 通过现有深链入口选中移动后的会话,同时用页面重建清除 DSH 保留的只读会话对象。 */
2036
+ function finishSessionMove({ sessionId, currentUrl, navigate }) {
2037
+ const targetUrl = new URL(currentUrl);
2038
+ targetUrl.searchParams.set("session", sessionId);
2039
+ navigate(targetUrl.toString());
2040
+ }
2041
+ //#endregion
2042
+ //#region src/client/workspace-archive.ts
2043
+ /** 按项目顺序归档会话;失败后停止,交由调用方显示并允许用户重试。 */
2044
+ async function archiveWorkspaceSessions(sessionIds, archiveSession) {
2045
+ for (const sessionId of new Set(sessionIds)) await archiveSession(sessionId);
2046
+ }
2047
+ //#endregion
2048
+ //#region src/client/CodexWorkspaceBrowser.tsx
2049
+ const DISCLOSURE_EXIT_MS = 180;
2050
+ function NewSessionIcon() {
2051
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SquarePen, {
2052
+ "aria-hidden": "true",
2053
+ size: 16,
2054
+ strokeWidth: 1.5
2055
+ });
2056
+ }
2057
+ /** 使用独立节点生成稳定的拖拽预览,避免浏览器把目标高亮和操作按钮截入默认快照。 */
2058
+ function setDragPreview(dataTransfer, title, icon) {
2059
+ const preview = document.createElement("div");
2060
+ preview.className = "dcu-wb-drag-ghost";
2061
+ if (icon !== void 0 && icon !== null) {
2062
+ const previewIcon = icon.cloneNode(true);
2063
+ previewIcon.className = "dcu-wb-drag-ghost-icon";
2064
+ previewIcon.setAttribute("aria-hidden", "true");
2065
+ preview.appendChild(previewIcon);
2066
+ }
2067
+ const previewTitle = document.createElement("span");
2068
+ previewTitle.className = "dcu-wb-drag-ghost-title";
2069
+ previewTitle.textContent = title.split(/\r?\n/)[0] ?? title;
2070
+ preview.appendChild(previewTitle);
2071
+ document.body.appendChild(preview);
2072
+ preview.offsetWidth;
2073
+ dataTransfer.setDragImage(preview, 16, 15);
2074
+ window.requestAnimationFrame(() => {
2075
+ preview.remove();
2076
+ });
2077
+ }
2078
+ function animateDisclosure(body, wasOpen, open) {
2079
+ if (wasOpen === open || window.matchMedia("(prefers-reduced-motion: reduce)").matches) return void 0;
2080
+ const height = `${body.scrollHeight}px`;
2081
+ const from = wasOpen ? {
2082
+ height,
2083
+ opacity: 1,
2084
+ transform: "translateY(0)"
2085
+ } : {
2086
+ height: "0px",
2087
+ opacity: 0,
2088
+ transform: "translateY(-2px)"
2089
+ };
2090
+ const to = open ? {
2091
+ height,
2092
+ opacity: 1,
2093
+ transform: "translateY(0)"
2094
+ } : {
2095
+ height: "0px",
2096
+ opacity: 0,
2097
+ transform: "translateY(-2px)"
2098
+ };
2099
+ return body.animate([from, to], {
2100
+ duration: DISCLOSURE_EXIT_MS,
2101
+ easing: "cubic-bezier(.16, 1, .3, 1)",
2102
+ fill: "none"
2103
+ });
2104
+ }
2105
+ /** 所有可折叠层级使用同一套原生高度动画,兼容宿主内核对 auto 高度过渡的差异。 */
2106
+ function DisclosureBody({ className, children, open }) {
2107
+ const bodyRef = (0, react.useRef)(null);
2108
+ const previousOpen = (0, react.useRef)(open);
2109
+ (0, react.useLayoutEffect)(() => {
2110
+ const body = bodyRef.current;
2111
+ if (body === null) return;
2112
+ body.inert = !open;
2113
+ const animation = animateDisclosure(body, previousOpen.current, open);
2114
+ previousOpen.current = open;
2115
+ return () => {
2116
+ animation?.cancel();
2117
+ };
2118
+ }, [open]);
2119
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2120
+ ref: bodyRef,
2121
+ className,
2122
+ "data-open": open,
2123
+ "aria-hidden": !open,
2124
+ children
2125
+ });
2126
+ }
2127
+ /** 顶层分区使用已有结构,通过统一监听器复用与项目、分组一致的原生动画。 */
2128
+ function useSectionDisclosureMotion(rootRef) {
2129
+ const previousOpen = (0, react.useRef)(/* @__PURE__ */ new WeakMap());
2130
+ (0, react.useLayoutEffect)(() => {
2131
+ const bodies = rootRef.current?.querySelectorAll(".dcu-wb-section-body") ?? [];
2132
+ for (const body of bodies) {
2133
+ const open = body.dataset.open === "true";
2134
+ const previous = previousOpen.current.get(body);
2135
+ previousOpen.current.set(body, open);
2136
+ body.inert = !open;
2137
+ if (previous !== void 0) animateDisclosure(body, previous, open);
2138
+ }
2139
+ });
2140
+ }
2141
+ function sameWorkspaceDropTarget(left, right) {
2142
+ if (left === void 0 || right === void 0) return left === right;
2143
+ if (left.zone !== right.zone) return false;
2144
+ if (left.zone === "group" && right.zone === "group") return left.groupId === right.groupId && left.beforeId === right.beforeId && left.ontoGroup === right.ontoGroup && left.crossGroup === right.crossGroup;
2145
+ if (left.zone === "ungrouped" && right.zone === "ungrouped") return left.beforeId === right.beforeId && left.ontoSection === right.ontoSection && left.crossGroup === right.crossGroup;
2146
+ return "beforeId" in left && "beforeId" in right && left.beforeId === right.beforeId;
2147
+ }
2148
+ function sameWorkspaceGroups(left, right) {
2149
+ return left.length === right.length && left.every((group, index) => group.id === right[index]?.id && group.title === right[index]?.title && sameIds(group.workspaceIds, right[index]?.workspaceIds ?? []));
2150
+ }
2151
+ function newWorkspaceGroupId() {
2152
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") return `group-${crypto.randomUUID()}`;
2153
+ return `group-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
2154
+ }
2155
+ function workspaceGroupMoveActionId(groupId) {
2156
+ return `moveWorkspaceToGroup:${groupId ?? ""}`;
2157
+ }
2158
+ const stylesheet$4 = `
2159
+ .dcu-wb{--dcu-wb-inset:8px;display:flex;flex:1;min-height:0;flex-direction:column;padding:4px var(--dcu-wb-inset) 8px;color:var(--dcu-sidebar-primary)}
2160
+ .dcu-wb *{box-sizing:border-box}
2161
+ .dcu-wb-tree{flex:1;min-height:0;overflow-x:hidden;overflow-y:auto;padding-bottom:16px;scrollbar-gutter:auto;user-select:none;-webkit-user-select:none}
2162
+ .dcu-wb-section+.dcu-wb-section{margin-top:12px}
2163
+ .dcu-wb-section-head{position:relative;display:flex;align-items:center;min-height:24px;padding:0;border-radius:6px}.dcu-wb-section-head:hover .dcu-wb-actions{display:flex}.dcu-wb-section-label{position:relative;display:flex;align-items:center;gap:4px;flex:1;min-width:0;min-height:24px;border:0;padding:2px 4px 2px 12px;background:transparent;color:var(--dcu-sidebar-tertiary);font:13px/20px var(--dcu-font,inherit);font-weight:400;letter-spacing:0;text-align:left;cursor:pointer}.dcu-wb-section-caret{display:grid;place-items:center;flex:none;width:12px;height:12px;color:currentColor;opacity:0;transform:rotate(0deg);transform-origin:50% 50%;transition:opacity 120ms ease,transform 160ms ease}.dcu-wb-section-head .dcu-wb-section-caret{position:absolute;left:0;top:6px}.dcu-wb-section-caret svg{display:block}.dcu-wb-section-head:hover .dcu-wb-section-caret,.dcu-wb-section-label:focus-visible .dcu-wb-section-caret{opacity:.78}.dcu-wb-section-label[aria-expanded=true] .dcu-wb-section-caret{transform:rotate(90deg)}.dcu-wb-section-body{display:block}.dcu-wb-section-body[data-open=false]{display:none}.dcu-wb-section-body>div{min-height:0}.dcu-wb-section-body[data-open=true]>div{animation:dcu-wb-section-in 140ms cubic-bezier(.16,1,.3,1)}@keyframes dcu-wb-section-in{from{opacity:0;transform:translateY(-3px)}to{opacity:1;transform:none}}@media (prefers-reduced-motion:reduce){.dcu-wb-section-caret{transition:none}.dcu-wb-section-body[data-open=true]>div{animation:none}}
2164
+ .dcu-wb-project{position:relative;min-width:0}
2165
+ .dcu-wb-collections{display:grid;min-width:0;gap:4px}.dcu-wb-collection,.dcu-wb-ungrouped{position:relative;min-width:0;overflow:visible}.dcu-wb-collection-head{display:flex;align-items:center;min-height:28px;padding:0 4px;border-radius:6px;transition:background 120ms ease}.dcu-wb-collection-head:hover,.dcu-wb-collection-head:focus-within,.dcu-wb-collection-head.dcu-wb-group-drop{background:var(--dcu-sidebar-hover)}.dcu-wb-collection-head.dcu-wb-group-drop{outline:1px solid var(--dsw-alias-state-business-primary)}.dcu-wb-collection-head[draggable=true],.dcu-wb-collection-head[draggable=true] .dcu-wb-collection-label{cursor:grab}.dcu-wb-collection-head[draggable=true]:active,.dcu-wb-collection-head[draggable=true]:active .dcu-wb-collection-label{cursor:grabbing}.dcu-wb-collection-label{display:flex;align-items:center;gap:5px;min-width:0;flex:1;border:0;padding:4px;background:transparent;color:var(--dcu-sidebar-primary);font:13px/20px var(--dcu-font,inherit);text-align:left;cursor:pointer}.dcu-wb-collection-label .dcu-wb-section-caret{position:static;order:-1;opacity:.78}.dcu-wb-collection-label[aria-expanded=true] .dcu-wb-section-caret{transform:rotate(90deg)}.dcu-wb-collection-count{margin-left:auto;color:var(--dcu-sidebar-secondary);font-variant-numeric:tabular-nums}.dcu-wb-collection-body{position:relative;min-width:0;padding-left:12px}.dcu-wb-collection-body::before{content:"";position:absolute;left:31px;top:0;bottom:8px;width:1px;background:var(--dcu-sidebar-border)}.dcu-wb-group-member{position:relative}.dcu-wb-group-member.dcu-wb-drop::before,.dcu-wb-collection.dcu-wb-group-order-drop::before,.dcu-wb-ungrouped.dcu-wb-group-order-drop::before{content:"";position:absolute;z-index:2;left:7px;right:8px;top:-8px;height:8px;pointer-events:none;background:radial-gradient(circle at 4px 50%,transparent 1.75px,var(--dsw-alias-state-business-primary) 2px 3.75px,transparent 4px),linear-gradient(var(--dsw-alias-state-business-primary),var(--dsw-alias-state-business-primary)) 10px 50%/calc(100% - 10px) 2px no-repeat}.dcu-wb-group-member.dcu-wb-drop-after::before{top:auto;bottom:-8px}.dcu-wb-collection-body .dcu-wb-project-head{padding-left:12px}
2166
+ .dcu-wb-collection.dcu-wb-workspace-move-drop,.dcu-wb-ungrouped.dcu-wb-workspace-move-drop{border-radius:8px;background:color-mix(in srgb,var(--dsw-alias-state-business-primary) 9%,transparent);outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:-2px}
2167
+ .dcu-wb-project-head,.dcu-wb-session{position:relative;display:flex;align-items:center;gap:6px;width:100%;min-width:0;border-radius:10px;padding:0 8px;color:var(--dcu-sidebar-primary);cursor:pointer}
2168
+ .dcu-wb-collection-body::before{left:calc(4px + 4px + 6px)}
2169
+ .dcu-wb-project-head{height:30px;background:transparent;font:inherit;text-align:left}
2170
+ .dcu-wb-project-head:hover,.dcu-wb-project-head.dcu-wb-menu-open,.dcu-wb-session:hover,.dcu-wb-session.dcu-wb-selected,.dcu-wb-session.dcu-wb-menu-open{background:var(--dcu-sidebar-hover)}.dcu-wb-project.dcu-wb-session-drop>.dcu-wb-project-head{border-radius:8px;background:color-mix(in srgb,var(--dsw-alias-state-business-primary) 9%,transparent);outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:-2px}.dcu-wb-project.dcu-wb-session-move-drop{border-radius:8px;background:color-mix(in srgb,var(--dsw-alias-state-business-primary) 9%,transparent);outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:-2px}.dcu-wb-project.dcu-wb-session-move-drop>.dcu-wb-project-head{background:transparent;outline:0}.dcu-wb-project-body>.dcu-wb-session:first-child,.dcu-wb-project-body>.dcu-wb-nochat:first-child{margin-top:4px}.dcu-wb-session+.dcu-wb-session{margin-top:2px}
2171
+ .dcu-wb-project-head[draggable=true],.dcu-wb-session[draggable=true]{cursor:grab}
2172
+ .dcu-wb-project-head[draggable=true]:active,.dcu-wb-session[draggable=true]:active{cursor:grabbing}
2173
+ .dcu-wb-section,.dcu-wb-section:focus,.dcu-wb-section:focus-visible,.dcu-wb-project-head:focus,.dcu-wb-session:focus{outline:0}.dcu-wb-pin-end,.dcu-wb-pin-start{position:relative;height:8px}.dcu-wb-project.dcu-wb-drop::before,.dcu-wb-session.dcu-wb-drop::before,.dcu-wb-pin-end.dcu-wb-drop::before,.dcu-wb-pin-start.dcu-wb-drop::before{content:"";position:absolute;z-index:2;left:7px;right:8px;top:-8px;height:8px;pointer-events:none;background:radial-gradient(circle at 4px 50%,transparent 1.75px,var(--dsw-alias-state-business-primary) 2px 3.75px,transparent 4px),linear-gradient(var(--dsw-alias-state-business-primary),var(--dsw-alias-state-business-primary)) 10px 50%/calc(100% - 10px) 2px no-repeat}.dcu-wb-pin-start.dcu-wb-drop::before{top:0}.dcu-wb-pinned-list>.dcu-wb-project:first-child.dcu-wb-drop::before{top:-4px}.dcu-wb-project.dcu-wb-drop-after::before,.dcu-wb-session.dcu-wb-drop-after::before{top:auto;bottom:-8px}.dcu-wb-dragging{opacity:.28}.dcu-wb-drag-ghost{position:fixed;top:0;left:0;transform:translate(-200vw,-200vh);z-index:10040;display:flex;align-items:center;gap:8px;max-width:220px;height:30px;padding:0 10px;border:1px solid var(--dcu-sidebar-border);border-radius:8px;background:var(--dcu-sidebar-hover);box-shadow:0 4px 12px rgba(0,0,0,.24);color:var(--dcu-sidebar-primary);font:14px/20px var(--dcu-font,inherit);white-space:nowrap;pointer-events:none}.dcu-wb-drag-ghost-icon{display:grid;place-items:center;flex:none;width:16px;height:16px;color:var(--dcu-sidebar-icon)}.dcu-wb-drag-ghost-icon svg{display:block;width:16px;height:16px}.dcu-wb-drag-ghost-title{min-width:0;overflow:hidden;text-overflow:ellipsis}
2174
+ .dcu-wb-folder{display:grid;place-items:center;flex:none;width:16px;height:20px;color:var(--dcu-sidebar-icon)}.dcu-wb-brand{display:block;width:16px;height:16px}
2175
+ .dcu-wb-project-title,.dcu-wb-session-title{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:14px;line-height:20px}
2176
+ .dcu-wb-project-title{flex:1;font-weight:400;color:var(--dcu-sidebar-primary)}
2177
+ .dcu-wb-session{position:relative;min-width:0;min-height:30px;gap:0;overflow:hidden;padding-left:28px;padding-right:28px}
2178
+ .dcu-wb-session-title{flex:1;margin-left:0}.dcu-wb-session-copy{flex:1;min-width:0;display:flex;flex-direction:column;justify-content:center}.dcu-wb-session-sub{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dcu-sidebar-tertiary);font-size:12px;line-height:16px}.dcu-wb-session-flat,.dcu-wb-session-top{padding-left:8px}
2179
+ .dcu-wb-actions{display:none;align-items:center;gap:8px;flex:none}
2180
+ .dcu-wb-quick-actions{position:absolute;right:8px;top:50%;display:flex;align-items:center;gap:2px;opacity:0;pointer-events:none;transform:translateY(-50%);background:var(--dcu-sidebar-hover);border-radius:6px}
2181
+ .dcu-wb-project-head:hover .dcu-wb-actions,.dcu-wb-project-head.dcu-wb-menu-open .dcu-wb-actions,.dcu-wb-session.dcu-wb-menu-open .dcu-wb-actions{display:flex}
2182
+ .dcu-wb-session:hover .dcu-wb-quick-actions,.dcu-wb-session.dcu-wb-menu-open .dcu-wb-quick-actions{opacity:1;pointer-events:auto}.dcu-wb-session:hover .dcu-wb-running,.dcu-wb-session.dcu-wb-menu-open .dcu-wb-running,.dcu-wb-session:hover .dcu-wb-pending,.dcu-wb-session.dcu-wb-menu-open .dcu-wb-pending,.dcu-wb-session:hover .dcu-wb-unread,.dcu-wb-session.dcu-wb-menu-open .dcu-wb-unread{visibility:hidden}
2183
+ .dcu-wb-more{display:grid;place-items:center;width:20px;height:20px;border:0;border-radius:4px;padding:0;background:transparent;color:var(--dcu-sidebar-tertiary);cursor:pointer}
2184
+ .dcu-wb-more:hover{color:var(--dcu-sidebar-primary)}
2185
+ .dcu-wb-context-anchor{opacity:0;pointer-events:none}
2186
+ .dcu-wb-tip{position:fixed;z-index:10050;min-width:220px;max-width:280px;padding:10px 12px;border:1px solid var(--dcu-sidebar-border);border-radius:12px;background:var(--dcu-tip-bg,#fff);box-shadow:var(--dcu-tip-shadow,0 8px 28px rgba(31,39,36,.16));color:var(--dcu-sidebar-primary);animation:dcu-tip-in 180ms cubic-bezier(.16,1,.3,1)}.dcu-wb-tip-workspace{width:316px;min-width:0;max-width:calc(100vw - 16px);padding:10px 8px 8px}@keyframes dcu-tip-in{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}@media (prefers-reduced-motion:reduce){.dcu-wb-tip{animation:none}}.dcu-wb-tip-title{display:flex;align-items:center;gap:6px;min-width:0;min-height:20px;font-size:14px;line-height:20px;font-weight:500}.dcu-wb-tip-title-main{min-width:0;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dcu-wb-tip-time{flex:none;margin-left:auto;color:var(--dcu-sidebar-tertiary);font-size:12px;line-height:18px;font-weight:400}.dcu-wb-tip-pin{display:grid;place-items:center;flex:none;width:20px;height:20px;border:0;border-radius:4px;padding:0;background:transparent;color:var(--dcu-sidebar-tertiary);cursor:pointer}.dcu-wb-tip-pin:hover{background:var(--dcu-sidebar-hover);color:var(--dcu-sidebar-primary)}.dcu-wb-tip-meta,.dcu-wb-tip-row{display:flex;align-items:center;gap:6px;min-width:0;margin-top:4px;color:var(--dcu-sidebar-secondary);font-size:14px;line-height:20px}.dcu-wb-tip-meta svg,.dcu-wb-tip-row svg{flex:none}.dcu-wb-tip-row>span:not(.dcu-wb-folder){min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dcu-wb-tip-path{align-items:flex-start;margin-top:8px}.dcu-wb-tip-path-copy{min-width:0;overflow-wrap:anywhere;white-space:normal}.dcu-wb-tip-sep{height:1px;margin:8px 0 4px;background:var(--dcu-sidebar-border)}.dcu-wb-tip-edit{display:flex;align-items:center;gap:6px;width:100%;min-height:24px;border:0;padding:2px 0;background:transparent;color:var(--dcu-sidebar-primary);font:14px/20px var(--dcu-font,inherit);text-align:left;cursor:pointer}.dcu-wb-tip-edit svg{flex:none;color:var(--dcu-sidebar-secondary)}
2187
+ .dcu-wb-session:has(.dcu-wb-pending){padding-right:112px}.dcu-wb-unread{position:absolute;right:13px;top:50%;width:7px;height:7px;margin:-3.5px 0 0;border-radius:50%;background:var(--dsw-alias-state-business-primary)}
2188
+ .dcu-wb-pending{position:absolute;right:10px;top:50%;display:flex;align-items:center;gap:4px;max-width:88px;margin:0;transform:translateY(-50%);color:var(--dsw-alias-state-warn-label,#b45309);font-size:11px;line-height:16px;white-space:nowrap}.dcu-wb-pending-dot{flex:none;width:7px;height:7px;border-radius:50%;background:var(--dsw-alias-state-warn-primary,#f59e0b)}.dcu-wb-pending-label{min-width:0;overflow:hidden;text-overflow:ellipsis}
2189
+ .dcu-wb-empty{padding:14px 8px;color:var(--dcu-sidebar-tertiary);font-size:13px}
2190
+ .dcu-wb-error{margin:4px 0;padding:6px 8px;border-radius:6px;background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 12%,transparent);color:var(--dsw-alias-state-error-primary);font-size:12px}
2191
+ .dcu-wb-rail{display:none}
2192
+ .dcu-wb-rename-actions{display:flex;justify-content:flex-end;gap:8px}
1412
2193
  .dcu-wb-delete-button{color:var(--dsw-alias-state-error-primary)!important}
1413
2194
  .dcu-wb-delete-copy{margin:0;color:var(--dcu-sidebar-secondary);font-size:13px;line-height:20px}
2195
+ .dcu-wb-move-target{display:flex;min-width:0;align-items:center;gap:8px;margin-top:4px;color:var(--dcu-sidebar-primary);font:14px/20px var(--dcu-font,inherit)}.dcu-wb-move-target svg{flex:none;color:var(--dcu-sidebar-secondary)}.dcu-wb-move-target-copy{min-width:0}.dcu-wb-move-project,.dcu-wb-move-path{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dcu-wb-move-path{color:var(--dcu-sidebar-tertiary);font-size:12px}
1414
2196
  .dcu-wb-rename-input{box-sizing:border-box;width:100%;height:36px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:7px 10px;outline:0;background:var(--dsw-alias-background-secondary);color:var(--dsw-alias-label-primary);font:14px/20px var(--dsw-font-family,inherit);caret-color:var(--dsw-alias-state-business-primary);transition:border-color 120ms ease,box-shadow 120ms ease}
1415
2197
  .dcu-wb-rename-input:hover{border-color:color-mix(in srgb,var(--dsw-alias-label-primary) 32%,var(--dsw-alias-border-l2))}
1416
2198
  .dcu-wb-rename-input:focus{border-color:var(--dsw-alias-state-business-primary);box-shadow:0 0 0 1px color-mix(in srgb,var(--dsw-alias-state-business-primary) 28%,transparent)}
1417
2199
  `;
1418
- const runningStyles = `.dcu-wb-running{flex:none;width:12px;height:12px;border:2px solid color-mix(in srgb,var(--dcu-sidebar-secondary) 25%,transparent);border-top-color:var(--dcu-sidebar-secondary);border-radius:50%;animation:dcu-wb-spin .8s linear infinite}@keyframes dcu-wb-spin{to{transform:rotate(360deg)}}@media (prefers-reduced-motion:reduce){.dcu-wb-running{animation:none}}`;
1419
- const typographyStyles = `.dcu-wb{font:14px/20px var(--dcu-font,var(--dsw-font-family))}.dcu-wb-section-label{color:var(--dcu-sidebar-tertiary);font-size:13px;font-weight:400;letter-spacing:.02em}.dcu-wb-project-title{font-size:14px;line-height:20px;font-weight:400;color:var(--dcu-sidebar-primary)}.dcu-wb-session-title{font-size:14px;line-height:20px;font-weight:400;color:var(--dcu-sidebar-secondary)}.dcu-wb-session.dcu-wb-selected .dcu-wb-session-title,.dcu-wb-session:hover .dcu-wb-session-title{color:var(--dcu-sidebar-primary)}.dcu-wb-empty{color:var(--dcu-sidebar-tertiary);font-size:13px;line-height:18px}.dcu-wb-nochat{padding:0 8px 4px 28px;color:var(--dcu-sidebar-tertiary);font-size:14px;line-height:20px}`;
1420
- const WORKSPACE_TREE_STYLE = stylesheet$4 + runningStyles + typographyStyles;
2200
+ const runningStyles = `.dcu-wb-running{position:absolute;right:10px;top:50%;flex:none;width:12px;height:12px;margin-top:-6px;border:2px solid color-mix(in srgb,var(--dcu-sidebar-secondary) 25%,transparent);border-top-color:var(--dcu-sidebar-secondary);border-radius:50%;animation:dcu-wb-spin .8s linear infinite}@keyframes dcu-wb-spin{to{transform:rotate(360deg)}}@media (prefers-reduced-motion:reduce){.dcu-wb-running{animation:none}}`;
2201
+ const typographyStyles = `.dcu-wb{--dcu-wb-disclosure-duration:180ms;--dcu-wb-disclosure-ease:cubic-bezier(.16,1,.3,1);font:14px/20px var(--dcu-font,var(--dsw-font-family))}.dcu-wb-section-label{color:var(--dcu-sidebar-secondary);font:13px/20px var(--dcu-font,var(--dsw-font-family));font-weight:400;letter-spacing:0;padding-left:0}.dcu-wb-section-caret{transition:opacity var(--dcu-wb-disclosure-duration) var(--dcu-wb-disclosure-ease),transform var(--dcu-wb-disclosure-duration) var(--dcu-wb-disclosure-ease)}.dcu-wb-section-head .dcu-wb-section-caret{position:static;left:auto;top:auto;opacity:.78}.dcu-wb-section-body,.dcu-wb-project-body,.dcu-wb-collection-body{display:block;min-height:0;height:auto;overflow:clip;opacity:1;transform:none;visibility:visible}.dcu-wb-section-body[data-open=false],.dcu-wb-project-body[data-open=false],.dcu-wb-collection-body[data-open=false]{display:block;height:0;opacity:0;transform:translateY(-2px);pointer-events:none}.dcu-wb-section-body[data-open=true]:has(.dcu-wb-drop),.dcu-wb-project-body[data-open=true]:has(.dcu-wb-drop),.dcu-wb-collection-body[data-open=true]:has(.dcu-wb-drop){overflow:visible}.dcu-wb-section-body[data-open=true]>div{animation:none}@media (prefers-reduced-motion:reduce){.dcu-wb-section-caret{transition:none}}.dcu-wb-project-title{font-size:14px;line-height:20px;font-weight:400;color:var(--dcu-sidebar-primary)}.dcu-wb-session-title{font-size:14px;line-height:20px;font-weight:400;color:var(--dcu-sidebar-secondary)}.dcu-wb-session.dcu-wb-selected .dcu-wb-session-title,.dcu-wb-session:hover .dcu-wb-session-title{color:var(--dcu-sidebar-primary)}.dcu-wb-empty{color:var(--dcu-sidebar-tertiary);font-size:13px;line-height:18px}.dcu-wb-nochat{padding:0 8px 4px 28px;color:var(--dcu-sidebar-tertiary);font-size:14px;line-height:20px}`;
2202
+ const collectionLayoutStyles = `.dcu-wb-collection-body{padding-left:0}.dcu-wb-collection-body .dcu-wb-project-head{padding-left:8px}.dcu-wb-collection-body::before,.dcu-wb-group-member::after{display:none}.dcu-wb-collection-head,.dcu-wb-collection-head:focus-within{background:color-mix(in srgb,var(--dcu-sidebar-hover) 28%,transparent)}.dcu-wb-collection-head:hover{background:var(--dcu-sidebar-hover)}.dcu-wb-collection-head.dcu-wb-group-drop{background:var(--dcu-sidebar-hover)}.dcu-wb-workspace-move-drop>.dcu-wb-collection-head{background:transparent}`;
2203
+ const WORKSPACE_TREE_STYLE = stylesheet$4 + runningStyles + typographyStyles + collectionLayoutStyles;
1421
2204
  function storage$1() {
1422
2205
  return browserStorage();
1423
2206
  }
1424
2207
  function sameIds(left, right) {
1425
2208
  return left.length === right.length && left.every((id, index) => id === right[index]);
1426
2209
  }
1427
- function browserBase() {
1428
- return typeof window === "undefined" || window.location.origin === "null" ? "http://dsh.internal/" : `${window.location.origin}/`;
1429
- }
1430
2210
  function optionalText(value, key) {
1431
2211
  if (!(key in value)) return void 0;
1432
2212
  const next = value[key];
@@ -1436,13 +2216,21 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1436
2216
  function CodexWorkspaceBrowser(props) {
1437
2217
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HoverShell, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CodexWorkspaceTree, { ...props }) });
1438
2218
  }
1439
- function CodexWorkspaceTree({ wide, useSessions, useWorkspaces, t, archiveSession, deleteSession, deleteWorkspace, forkSession, insertSessionBefore, insertWorkspaceBefore, openPath, openSession, renameSession, renameWorkspace, startSession }) {
2219
+ function CodexWorkspaceTree({ wide, useSessions, useSessionPendingInteraction, useWorkspaces, t, archiveSession, deleteSession, deleteWorkspace, forkSession, insertSessionBefore, insertWorkspaceBefore, moveSession, openPath, openSession, renameSession, renameWorkspace, startSession }) {
1440
2220
  const sessions = useSessions((state) => state);
2221
+ const pendingInteractions = (useSessionPendingInteraction ?? useEmptySessionPendingInteraction)((state) => state);
1441
2222
  const workspaces = useWorkspaces((state) => state);
2223
+ const baselinesReady = workspaceBaselinesReady(workspaces, sessions);
1442
2224
  const [expanded, setExpanded] = (0, react.useState)(() => readTreeExpansionState(storage$1(), WORKSPACE_EXPANSION_STORAGE_KEY));
1443
2225
  const [pinnedWorkspaceIds, setPinnedWorkspaceIdsState] = (0, react.useState)(() => readPinnedWorkspaceIds(storage$1()));
2226
+ const initialWorkspaceGroups = (0, react.useMemo)(() => readWorkspaceGroupsCache(storage$1()), []);
2227
+ const [workspaceGroups, setWorkspaceGroupsState] = (0, react.useState)(() => initialWorkspaceGroups.workspaceGroups);
1444
2228
  const pinnedWorkspaceIdsRef = (0, react.useRef)(pinnedWorkspaceIds);
1445
2229
  pinnedWorkspaceIdsRef.current = pinnedWorkspaceIds;
2230
+ const workspaceGroupsRef = (0, react.useRef)(workspaceGroups);
2231
+ workspaceGroupsRef.current = workspaceGroups;
2232
+ const workspaceGroupsPendingHostSyncRef = (0, react.useRef)(initialWorkspaceGroups.pendingHostSync);
2233
+ const pinnedHostSupportsWorkspaceGroupsRef = (0, react.useRef)(false);
1446
2234
  const pinnedHostHydratedRef = (0, react.useRef)(false);
1447
2235
  const pinnedHostDirtyRef = (0, react.useRef)(false);
1448
2236
  const pinnedHostSkipWriteRef = (0, react.useRef)();
@@ -1452,12 +2240,24 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1452
2240
  validIds: []
1453
2241
  });
1454
2242
  workspaceBaselineRef.current = {
1455
- ready: workspaces.baselinesReady === true,
2243
+ ready: baselinesReady,
1456
2244
  validIds: workspaces.items.map((workspace) => String(workspace.workspaceId))
1457
2245
  };
1458
- const queuePinnedHostWrite = (ids) => {
1459
- const snapshot = [...ids];
1460
- const pending = pinnedHostWriteRef.current.then(() => writeHostPinnedWorkspaceIds(snapshot));
2246
+ const queuePinnedHostWrite = (ids, groups) => {
2247
+ const snapshot = {
2248
+ pinnedWorkspaceIds: [...ids],
2249
+ workspaceGroups: groups.map((group) => ({
2250
+ ...group,
2251
+ workspaceIds: [...group.workspaceIds]
2252
+ }))
2253
+ };
2254
+ const pending = pinnedHostWriteRef.current.then(async () => {
2255
+ await writeHostWorkspacePreferences(snapshot.pinnedWorkspaceIds, snapshot.workspaceGroups);
2256
+ if (!pinnedHostSupportsWorkspaceGroupsRef.current) return;
2257
+ if (!sameIds(pinnedWorkspaceIdsRef.current, snapshot.pinnedWorkspaceIds) || !sameWorkspaceGroups(workspaceGroupsRef.current, snapshot.workspaceGroups)) return;
2258
+ workspaceGroupsPendingHostSyncRef.current = false;
2259
+ saveWorkspaceGroupsCache(storage$1(), snapshot.workspaceGroups, false);
2260
+ });
1461
2261
  pinnedHostWriteRef.current = pending.catch(() => void 0);
1462
2262
  };
1463
2263
  const setPinnedWorkspaceIds = (update) => {
@@ -1467,11 +2267,21 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1467
2267
  pinnedWorkspaceIdsRef.current = next;
1468
2268
  setPinnedWorkspaceIdsState(next);
1469
2269
  };
1470
- const [pinnedSessionIds, setPinnedSessionIds] = (0, react.useState)(() => readSessionIds(storage$1(), SESSION_PINS_STORAGE_KEY));
2270
+ const setWorkspaceGroups = (update) => {
2271
+ pinnedHostDirtyRef.current = true;
2272
+ workspaceGroupsPendingHostSyncRef.current = true;
2273
+ const current = workspaceGroupsRef.current;
2274
+ const next = typeof update === "function" ? update(current) : update;
2275
+ workspaceGroupsRef.current = next;
2276
+ setWorkspaceGroupsState(next);
2277
+ };
1471
2278
  const [unreadSessionIds, setUnreadSessionIds] = (0, react.useState)(() => readSessionIds(storage$1(), SESSION_UNREAD_STORAGE_KEY));
2279
+ const previousSessionRunningRef = (0, react.useRef)();
1472
2280
  const [menu, setMenu] = (0, react.useState)();
1473
2281
  const [renameTarget, setRenameTarget] = (0, react.useState)();
1474
2282
  const [deleteTarget, setDeleteTarget] = (0, react.useState)();
2283
+ const [archiveWorkspaceTarget, setArchiveWorkspaceTarget] = (0, react.useState)();
2284
+ const [sessionMoveTarget, setSessionMoveTarget] = (0, react.useState)();
1475
2285
  const { showTip: publishTip, hideTip, dismissTip, isShowing } = useHoverDispatch();
1476
2286
  const showTip = (tip, options) => {
1477
2287
  if (menu !== void 0) return;
@@ -1483,8 +2293,17 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1483
2293
  const [error, setError] = (0, react.useState)();
1484
2294
  const [workspaceDragId, setWorkspaceDragId] = (0, react.useState)();
1485
2295
  const [workspaceDropTarget, setWorkspaceDropTargetState] = (0, react.useState)();
2296
+ const [workspaceGroupDragId, setWorkspaceGroupDragId] = (0, react.useState)();
2297
+ const [workspaceGroupDropTarget, setWorkspaceGroupDropTargetState] = (0, react.useState)();
2298
+ const setWorkspaceGroupDropTarget = (target) => {
2299
+ setWorkspaceGroupDropTargetState((current) => {
2300
+ if (current === void 0 || target === void 0) return current === target ? current : target;
2301
+ return current.beforeId === target.beforeId ? current : target;
2302
+ });
2303
+ };
1486
2304
  const workspaceDropTargetRef = (0, react.useRef)();
1487
2305
  const setWorkspaceDropTarget = (target) => {
2306
+ if (sameWorkspaceDropTarget(workspaceDropTargetRef.current, target)) return;
1488
2307
  workspaceDropTargetRef.current = target;
1489
2308
  setWorkspaceDropTargetState(target);
1490
2309
  };
@@ -1494,75 +2313,108 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1494
2313
  const headerMenuPointerAt = (0, react.useRef)(0);
1495
2314
  const [sessionDrag, setSessionDrag] = (0, react.useState)();
1496
2315
  const [sessionDropTarget, setSessionDropTarget] = (0, react.useState)();
2316
+ const [createGroupOpen, setCreateGroupOpen] = (0, react.useState)(false);
2317
+ const [groupTitleDraft, setGroupTitleDraft] = (0, react.useState)("");
2318
+ const [deleteGroupId, setDeleteGroupId] = (0, react.useState)();
2319
+ const [groupMenuOpen, setGroupMenuOpen] = (0, react.useState)(false);
1497
2320
  (0, react.useEffect)(() => {
1498
2321
  writeTreeExpansionState(storage$1(), WORKSPACE_EXPANSION_STORAGE_KEY, expanded);
1499
2322
  }, [expanded]);
1500
2323
  (0, react.useEffect)(() => {
1501
2324
  savePinnedWorkspaceIds(storage$1(), pinnedWorkspaceIds);
2325
+ saveWorkspaceGroupsCache(storage$1(), workspaceGroups, workspaceGroupsPendingHostSyncRef.current);
1502
2326
  if (!pinnedHostHydratedRef.current) return;
1503
2327
  const skippedHydration = pinnedHostSkipWriteRef.current;
1504
2328
  pinnedHostSkipWriteRef.current = void 0;
1505
- if (skippedHydration !== void 0 && sameIds(skippedHydration, pinnedWorkspaceIds)) return;
1506
- queuePinnedHostWrite(pinnedWorkspaceIds);
1507
- }, [pinnedWorkspaceIds]);
2329
+ if (skippedHydration !== void 0 && sameIds(skippedHydration.pinnedWorkspaceIds, pinnedWorkspaceIds) && sameWorkspaceGroups(skippedHydration.workspaceGroups, workspaceGroups)) return;
2330
+ queuePinnedHostWrite(pinnedWorkspaceIds, workspaceGroups);
2331
+ }, [pinnedWorkspaceIds, workspaceGroups]);
1508
2332
  (0, react.useEffect)(() => {
1509
2333
  let alive = true;
1510
- const localIds = [...pinnedWorkspaceIdsRef.current];
2334
+ const local = {
2335
+ pinnedWorkspaceIds: [...pinnedWorkspaceIdsRef.current],
2336
+ workspaceGroups: workspaceGroupsRef.current
2337
+ };
1511
2338
  readHostPinnedWorkspaceIds().then((host) => {
1512
2339
  if (!alive) return;
1513
- const hydration = resolvePinnedWorkspaceHydration(localIds, host, pinnedHostDirtyRef.current ? pinnedWorkspaceIdsRef.current : void 0);
2340
+ const dirty = pinnedHostDirtyRef.current ? {
2341
+ pinnedWorkspaceIds: pinnedWorkspaceIdsRef.current,
2342
+ workspaceGroups: workspaceGroupsRef.current
2343
+ } : void 0;
2344
+ const hydration = resolveWorkspacePreferencesHydration(local, host, dirty, workspaceGroupsPendingHostSyncRef.current);
1514
2345
  const baseline = workspaceBaselineRef.current;
1515
- const ids = baseline.ready ? prunePinnedWorkspaceIds(hydration.ids, baseline.validIds) : hydration.ids;
1516
- const writeHost = hydration.writeHost || !sameIds(ids, hydration.ids);
2346
+ const ids = baseline.ready ? prunePinnedWorkspaceIds(hydration.pinnedWorkspaceIds, baseline.validIds) : hydration.pinnedWorkspaceIds;
2347
+ const groups = baseline.ready ? pruneWorkspaceGroups(hydration.workspaceGroups, baseline.validIds) : hydration.workspaceGroups;
2348
+ const writeHost = hydration.writeHost || !sameIds(ids, hydration.pinnedWorkspaceIds) || !sameWorkspaceGroups(groups, hydration.workspaceGroups);
2349
+ pinnedHostSupportsWorkspaceGroupsRef.current = host.workspaceGroupsSupported !== false;
2350
+ workspaceGroupsPendingHostSyncRef.current = workspaceGroupsPendingHostSyncRef.current || host.workspaceGroupsSupported === false && groups.length > 0 || !host.exists && local.workspaceGroups.length > 0;
1517
2351
  pinnedHostHydratedRef.current = true;
1518
- if (!sameIds(pinnedWorkspaceIdsRef.current, ids)) {
1519
- pinnedHostSkipWriteRef.current = [...ids];
2352
+ if (!sameIds(pinnedWorkspaceIdsRef.current, ids) || !sameWorkspaceGroups(workspaceGroupsRef.current, groups)) {
2353
+ pinnedHostSkipWriteRef.current = {
2354
+ pinnedWorkspaceIds: [...ids],
2355
+ workspaceGroups: groups
2356
+ };
1520
2357
  pinnedWorkspaceIdsRef.current = ids;
2358
+ workspaceGroupsRef.current = groups;
1521
2359
  setPinnedWorkspaceIdsState(ids);
2360
+ setWorkspaceGroupsState(groups);
1522
2361
  }
1523
2362
  savePinnedWorkspaceIds(storage$1(), ids);
1524
- if (writeHost) queuePinnedHostWrite(ids);
1525
- }).catch(() => {});
2363
+ if (writeHost) queuePinnedHostWrite(ids, groups);
2364
+ }).catch(() => {
2365
+ pinnedHostHydratedRef.current = true;
2366
+ });
1526
2367
  return () => {
1527
2368
  alive = false;
1528
2369
  };
1529
2370
  }, []);
1530
- (0, react.useEffect)(() => {
1531
- writeSessionIds(storage$1(), SESSION_PINS_STORAGE_KEY, pinnedSessionIds);
1532
- }, [pinnedSessionIds]);
1533
2371
  (0, react.useEffect)(() => {
1534
2372
  writeSessionIds(storage$1(), SESSION_UNREAD_STORAGE_KEY, unreadSessionIds);
1535
2373
  }, [unreadSessionIds]);
1536
2374
  (0, react.useEffect)(() => {
1537
- if (workspaces.baselinesReady !== true) return;
1538
- const next = prunePinnedWorkspaceIds(pinnedWorkspaceIdsRef.current, workspaces.items.map((workspace) => String(workspace.workspaceId)));
1539
- if (sameIds(pinnedWorkspaceIdsRef.current, next)) return;
1540
- pinnedWorkspaceIdsRef.current = next;
1541
- setPinnedWorkspaceIdsState(next);
1542
- }, [workspaces.baselinesReady, workspaces.items]);
2375
+ if (!baselinesReady) return;
2376
+ const validIds = workspaces.items.map((workspace) => String(workspace.workspaceId));
2377
+ const nextPinned = prunePinnedWorkspaceIds(pinnedWorkspaceIdsRef.current, validIds);
2378
+ const nextGroups = pruneWorkspaceGroups(workspaceGroupsRef.current, validIds);
2379
+ if (!sameIds(pinnedWorkspaceIdsRef.current, nextPinned)) {
2380
+ pinnedWorkspaceIdsRef.current = nextPinned;
2381
+ setPinnedWorkspaceIdsState(nextPinned);
2382
+ }
2383
+ if (!sameWorkspaceGroups(workspaceGroupsRef.current, nextGroups)) {
2384
+ workspaceGroupsRef.current = nextGroups;
2385
+ setWorkspaceGroupsState(nextGroups);
2386
+ }
2387
+ }, [baselinesReady, workspaces.items]);
1543
2388
  (0, react.useEffect)(() => {
1544
2389
  const current = sessions.current;
1545
2390
  if (current !== void 0) setUnreadSessionIds((ids) => ids.filter((id) => id !== current));
1546
2391
  }, [sessions.current]);
2392
+ (0, react.useEffect)(() => {
2393
+ const next = Object.fromEntries(Object.entries(sessions.byId).map(([id, session]) => [id, session.running === true]));
2394
+ const previous = previousSessionRunningRef.current;
2395
+ previousSessionRunningRef.current = next;
2396
+ if (previous === void 0) return;
2397
+ const completed = completedBackgroundSessionIds(previous, next, sessions.current);
2398
+ if (completed.length > 0) setUnreadSessionIds((ids) => [...completed, ...ids.filter((id) => !completed.includes(id))]);
2399
+ }, [sessions.byId, sessions.current]);
1547
2400
  const groups = (0, react.useMemo)(() => {
1548
2401
  const archived = workspaces.archivedSessionIds;
1549
- const visible = (ids) => visibleSessionIds(ids, sessions.byId, archived).sort((left, right) => {
1550
- const leftPinned = pinnedSessionIds.indexOf(left);
1551
- const rightPinned = pinnedSessionIds.indexOf(right);
1552
- if (leftPinned !== -1 || rightPinned !== -1) return (leftPinned === -1 ? Number.MAX_SAFE_INTEGER : leftPinned) - (rightPinned === -1 ? Number.MAX_SAFE_INTEGER : rightPinned);
1553
- return 0;
1554
- });
2402
+ const visible = (ids) => visibleSessionIds(ids, sessions.byId, archived);
1555
2403
  return { items: workspaces.items.map((workspace) => ({
1556
2404
  ...workspace,
1557
2405
  visibleIds: visible(workspace.sessionIds)
1558
2406
  })) };
1559
2407
  }, [
1560
- pinnedSessionIds,
1561
2408
  sessions.byId,
1562
2409
  sessions.ids,
1563
2410
  workspaces.archivedSessionIds,
1564
2411
  workspaces.items
1565
2412
  ]);
2413
+ const sourceWorkspaceIdForSession = (sessionId) => {
2414
+ if (sessionDrag?.sessionId === sessionId && sessionDrag.workspaceId !== "") return sessionDrag.workspaceId;
2415
+ const source = groups.items.find((workspace) => workspace.sessionIds.some((id) => String(id) === sessionId));
2416
+ return source === void 0 ? void 0 : String(source.workspaceId);
2417
+ };
1566
2418
  (0, react.useEffect)(() => {
1567
2419
  const onPointerDown = () => {
1568
2420
  if (headerMenuRef.current !== void 0) headerMenuPointerAt.current = performance.now();
@@ -1588,6 +2440,8 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1588
2440
  title: workspace.title,
1589
2441
  path: workspace.path,
1590
2442
  count: workspace.visibleIds.length,
2443
+ unreadCount: workspace.visibleIds.filter((id) => unreadSessionIds.includes(String(id))).length,
2444
+ pinned: pinnedWorkspaceIds.includes(String(workspace.workspaceId)),
1591
2445
  left: detail.left,
1592
2446
  top: detail.top
1593
2447
  }, { immediate: true });
@@ -1616,7 +2470,9 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1616
2470
  }, [
1617
2471
  groups.items,
1618
2472
  sessions.current,
1619
- menu
2473
+ menu,
2474
+ pinnedWorkspaceIds,
2475
+ unreadSessionIds
1620
2476
  ]);
1621
2477
  const projectPinned = (id) => pinnedWorkspaceIds.includes(String(id));
1622
2478
  const pinnedGroups = orderByIds(groups.items, pinnedWorkspaceIds, (workspace) => String(workspace.workspaceId));
@@ -1624,6 +2480,11 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1624
2480
  const pinDragActive = workspaceDragId !== void 0;
1625
2481
  const pinnedGroupIds = pinnedGroups.map((workspace) => String(workspace.workspaceId));
1626
2482
  const regularGroupIds = regularGroups.map((workspace) => String(workspace.workspaceId));
2483
+ const groupedIds = new Set(groupedWorkspaceIds(workspaceGroups));
2484
+ const workspaceGroupIds = workspaceGroups.map((group) => group.id);
2485
+ const ungroupedGroups = regularGroups.filter((workspace) => !groupedIds.has(String(workspace.workspaceId)));
2486
+ const ungroupedGroupIds = ungroupedGroups.map((workspace) => String(workspace.workspaceId));
2487
+ const workspaceById = new Map(regularGroups.map((workspace) => [String(workspace.workspaceId), workspace]));
1627
2488
  const pinnedHeaderDrop = workspaceDropTarget?.zone === "pinned" && workspaceDropTarget.beforeId === pinnedGroupIds[0] ? pinnedHeaderDropIndicator(pinnedGroupIds) : void 0;
1628
2489
  const assignedIds = workspaces.items.flatMap((workspace) => workspace.sessionIds.map((id) => String(id)));
1629
2490
  const recentIds = ungroupedSessionIds(sessions.ids ?? Object.keys(sessions.byId), sessions.byId, assignedIds, workspaces.archivedSessionIds).sort((left, right) => (sessions.byId[right]?.updatedAt ?? 0) - (sessions.byId[left]?.updatedAt ?? 0));
@@ -1636,7 +2497,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1636
2497
  await action();
1637
2498
  setMenu(void 0);
1638
2499
  } catch (reason) {
1639
- setError(reason instanceof Error ? reason.message : String(reason));
2500
+ setError(userErrorText(reason, t));
1640
2501
  } finally {
1641
2502
  busyRef.current = void 0;
1642
2503
  setBusy(void 0);
@@ -1666,7 +2527,6 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1666
2527
  run(operation, async () => {
1667
2528
  if (target.kind === "session") {
1668
2529
  await deleteSession(target.id);
1669
- setPinnedSessionIds((ids) => ids.filter((id) => id !== target.id));
1670
2530
  setUnreadSessionIds((ids) => ids.filter((id) => id !== target.id));
1671
2531
  } else {
1672
2532
  await deleteWorkspace(target.id);
@@ -1675,6 +2535,15 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1675
2535
  setDeleteTarget(void 0);
1676
2536
  });
1677
2537
  };
2538
+ const submitArchiveWorkspace = () => {
2539
+ if (archiveWorkspaceTarget === void 0) return;
2540
+ const target = archiveWorkspaceTarget;
2541
+ run("archive-workspace", async () => {
2542
+ await archiveWorkspaceSessions(target.sessionIds, (sessionId) => archiveSession(sessionId));
2543
+ setUnreadSessionIds((ids) => ids.filter((id) => !target.sessionIds.includes(id)));
2544
+ setArchiveWorkspaceTarget(void 0);
2545
+ });
2546
+ };
1678
2547
  const copy = (value) => {
1679
2548
  if (value === void 0 || value === "") return;
1680
2549
  run("copy", async () => {
@@ -1692,6 +2561,19 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1692
2561
  [key]: !(current[key] ?? defaultOpen)
1693
2562
  }));
1694
2563
  };
2564
+ const submitCreateGroup = () => {
2565
+ if (groupTitleDraft.trim() === "") return;
2566
+ try {
2567
+ setWorkspaceGroups((current) => createWorkspaceGroup(current, {
2568
+ id: newWorkspaceGroupId(),
2569
+ title: groupTitleDraft
2570
+ }));
2571
+ setCreateGroupOpen(false);
2572
+ setGroupTitleDraft("");
2573
+ } catch (reason) {
2574
+ setError(userErrorText(reason, t));
2575
+ }
2576
+ };
1695
2577
  const sectionOpen = (id) => expanded[`section:${id}`] ?? true;
1696
2578
  const toggleSection = (id) => {
1697
2579
  setExpanded((current) => ({
@@ -1699,44 +2581,150 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1699
2581
  [`section:${id}`]: !(current[`section:${id}`] ?? true)
1700
2582
  }));
1701
2583
  };
1702
- const projectMenu = (workspace) => [
1703
- {
1704
- id: "new",
1705
- label: t("workspace.newSession"),
1706
- icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPlusOutline16, { size: 16 })
1707
- },
1708
- {
1709
- id: "rename",
1710
- label: t("workspace.rename"),
1711
- icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEditOutline16, { size: 16 })
1712
- },
1713
- {
1714
- id: "pin",
1715
- label: t(projectPinned(workspace.workspaceId) ? "workspace.unpin" : "workspace.pin"),
1716
- icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PinIcon, {})
1717
- },
1718
- {
1719
- id: "openPath",
1720
- label: t("workspace.openPath"),
1721
- icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderOpenOutline16, { size: 16 })
1722
- },
1723
- {
1724
- type: "separator",
1725
- id: "project-separator"
1726
- },
1727
- {
1728
- id: "delete",
1729
- label: t("workspace.delete"),
1730
- icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, { size: 16 }),
1731
- danger: true
2584
+ const workspaceGroupIdFor = (workspaceId) => workspaceGroups.find((group) => group.workspaceIds.includes(workspaceId))?.id;
2585
+ const moveWorkspaceToGroup = (workspaceId, groupId) => {
2586
+ setWorkspaceGroups((current) => assignWorkspaceToGroup(current, workspaceId, groupId));
2587
+ if (pinnedWorkspaceIdsRef.current.includes(workspaceId)) setPinnedWorkspaceIds((ids) => ids.filter((id) => id !== workspaceId));
2588
+ };
2589
+ const projectMenu = (workspace) => {
2590
+ const workspaceId = String(workspace.workspaceId);
2591
+ const moveTargets = workspaceGroupMoveTargets(workspaceGroups, workspaceId).map((target) => ({
2592
+ id: workspaceGroupMoveActionId(target.groupId),
2593
+ label: target.title ?? t("workspace.removeFromGroup"),
2594
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderClose16, { size: 16 })
2595
+ }));
2596
+ return [
2597
+ {
2598
+ id: "pin",
2599
+ label: t(projectPinned(workspace.workspaceId) ? "workspace.unpin" : "workspace.pin"),
2600
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PinIcon, {})
2601
+ },
2602
+ {
2603
+ id: "rename",
2604
+ label: t("workspace.rename"),
2605
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEditOutline16, { size: 16 })
2606
+ },
2607
+ {
2608
+ type: "separator",
2609
+ id: "project-main-separator"
2610
+ },
2611
+ {
2612
+ id: "moveToGroup",
2613
+ label: t("workspace.moveToGroup"),
2614
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderClose16, { size: 16 }),
2615
+ disabled: moveTargets.length === 0,
2616
+ submenu: moveTargets
2617
+ },
2618
+ {
2619
+ id: "openPath",
2620
+ label: t("workspace.openPath"),
2621
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderOpenOutline16, { size: 16 })
2622
+ },
2623
+ {
2624
+ type: "separator",
2625
+ id: "project-archive-separator"
2626
+ },
2627
+ {
2628
+ id: "archiveWorkspace",
2629
+ label: t("workspace.archive"),
2630
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconArchiveOutline20, { size: 16 }),
2631
+ disabled: workspace.visibleIds.length === 0
2632
+ },
2633
+ {
2634
+ type: "separator",
2635
+ id: "project-delete-separator"
2636
+ },
2637
+ {
2638
+ id: "delete",
2639
+ label: t("workspace.delete"),
2640
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, { size: 16 }),
2641
+ danger: true
2642
+ }
2643
+ ];
2644
+ };
2645
+ const handleProjectMenuAction = (workspace, id) => {
2646
+ if (busy !== void 0) return;
2647
+ const workspaceId = String(workspace.workspaceId);
2648
+ const targetGroup = workspaceGroups.find((group) => id === workspaceGroupMoveActionId(group.id));
2649
+ if (targetGroup !== void 0 || id === workspaceGroupMoveActionId()) {
2650
+ moveWorkspaceToGroup(workspaceId, targetGroup?.id);
2651
+ setMenu(void 0);
2652
+ return;
1732
2653
  }
1733
- ];
1734
- const sessionMenu = (sessionId, _title, path) => sessionMenuItems(t, {
1735
- pinned: pinnedSessionIds.includes(sessionId),
2654
+ if (id === "rename") beginRename("workspace", workspace.workspaceId, workspace.title);
2655
+ if (id === "pin") {
2656
+ setPinnedWorkspaceIds((ids) => togglePinnedWorkspace(ids, workspace.workspaceId));
2657
+ setMenu(void 0);
2658
+ }
2659
+ if (id === "archiveWorkspace") {
2660
+ setArchiveWorkspaceTarget({
2661
+ id: workspaceId,
2662
+ title: workspace.title,
2663
+ sessionIds: [...workspace.visibleIds]
2664
+ });
2665
+ setError(void 0);
2666
+ setMenu(void 0);
2667
+ }
2668
+ if (id === "openPath") openPathImmediately(workspace.path);
2669
+ if (id === "delete") {
2670
+ setDeleteTarget({
2671
+ id: workspace.workspaceId,
2672
+ kind: "workspace",
2673
+ title: workspace.title
2674
+ });
2675
+ setError(void 0);
2676
+ setMenu(void 0);
2677
+ }
2678
+ };
2679
+ const sessionMenu = (sessionId, path) => sessionMenuItems(t, {
1736
2680
  unread: unreadSessionIds.includes(sessionId),
1737
2681
  path,
1738
- includePath: true
2682
+ includePath: true,
2683
+ moveTargets: sessionMoveTargets(groups.items, sessionId).map((target) => ({
2684
+ ...target,
2685
+ id: moveSessionActionId(target.id)
2686
+ }))
1739
2687
  });
2688
+ const requestSessionMove = (sessionId, targetWorkspaceId) => {
2689
+ const targetWorkspace = groups.items.find((workspace) => String(workspace.workspaceId) === targetWorkspaceId);
2690
+ const session = sessions.byId[sessionId];
2691
+ if (targetWorkspace === void 0 || session === void 0) {
2692
+ setError(t("sessions.moveNotFound"));
2693
+ return;
2694
+ }
2695
+ setMenu(void 0);
2696
+ setHeaderMenu(void 0);
2697
+ setError(void 0);
2698
+ setSessionMoveTarget({
2699
+ sessionId,
2700
+ sessionTitle: session.displayTitle,
2701
+ targetWorkspaceId,
2702
+ targetWorkspaceTitle: targetWorkspace.title,
2703
+ targetPath: targetWorkspace.path
2704
+ });
2705
+ };
2706
+ const runSessionMoveAction = (action, sessionId) => {
2707
+ const targetWorkspaceId = parseMoveSessionActionId(action);
2708
+ if (targetWorkspaceId === void 0) return false;
2709
+ requestSessionMove(sessionId, targetWorkspaceId);
2710
+ return true;
2711
+ };
2712
+ const submitSessionMove = () => {
2713
+ const target = sessionMoveTarget;
2714
+ if (target === void 0) return;
2715
+ run("session-move", async () => {
2716
+ const nextExpanded = expandedForSessionMove(expanded, {
2717
+ workspaceId: target.targetWorkspaceId,
2718
+ pinned: projectPinned(target.targetWorkspaceId),
2719
+ groupId: workspaceGroupIdFor(target.targetWorkspaceId),
2720
+ hasGroups: workspaceGroups.length > 0
2721
+ });
2722
+ setExpanded(nextExpanded);
2723
+ writeTreeExpansionState(storage$1(), WORKSPACE_EXPANSION_STORAGE_KEY, nextExpanded);
2724
+ await moveSession(target.sessionId, target.targetWorkspaceId);
2725
+ setSessionMoveTarget(void 0);
2726
+ });
2727
+ };
1740
2728
  const pinWorkspaceAt = (id, beforeId) => {
1741
2729
  setPinnedWorkspaceIds((ids) => insertPinnedWorkspace(ids, id, beforeId));
1742
2730
  };
@@ -1749,207 +2737,222 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1749
2737
  x: menu.x,
1750
2738
  y: menu.y
1751
2739
  } : void 0;
1752
- const isCurrentWorkspace = sessions.current !== void 0 && workspace.visibleIds.includes(sessions.current);
1753
2740
  const isPinnedHeaderDrop = zone === "pinned" && pinnedHeaderDrop?.kind === "workspace" && pinnedHeaderDrop.workspaceId === String(workspace.workspaceId);
1754
2741
  const workspaceId = String(workspace.workspaceId);
1755
2742
  const zoneIds = zone === "pinned" ? pinnedGroupIds : regularGroupIds;
1756
2743
  const dropsBefore = workspaceDropTarget?.zone === zone && workspaceDropTarget.beforeId === workspaceId;
1757
2744
  const dropsAfterLast = zone === "projects" && workspaceDropTarget?.zone === zone && workspaceDropTarget.beforeId === void 0 && zoneIds[zoneIds.length - 1] === workspaceId;
1758
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1759
- className: `dcu-wb-project${isCurrentWorkspace ? " dcu-wb-project-current" : ""}${isPinnedHeaderDrop || dropsBefore ? " dcu-wb-drop" : ""}${dropsAfterLast ? " dcu-wb-drop dcu-wb-drop-after" : ""}`,
1760
- onDragOver: (event) => {
1761
- if (workspaceDragId === void 0) return;
2745
+ const projectSessionDrop = sessionDropTarget?.workspaceId === workspaceId && sessionDropTarget.ontoProject === true;
2746
+ const projectSessionMoveDrop = projectSessionDrop && sessionDropTarget.crossWorkspace === true;
2747
+ const handleProjectDragOver = (event) => {
2748
+ const draggedWorkspace = readWorkspaceDrag(event.dataTransfer, workspaceDragId);
2749
+ if (draggedWorkspace !== void 0) {
1762
2750
  event.preventDefault();
1763
2751
  event.stopPropagation();
1764
2752
  event.dataTransfer.dropEffect = "move";
1765
2753
  const rect = event.currentTarget.getBoundingClientRect();
1766
- const after = event.clientY > rect.top + rect.height / 2;
1767
- const beforeId = reorderDropBeforeId(zoneIds, workspaceDragId, workspaceId, after);
2754
+ const beforeId = reorderDropBeforeId(zoneIds, draggedWorkspace, workspaceId, event.clientY > rect.top + rect.height / 2);
2755
+ setSessionDropTarget(void 0);
1768
2756
  setWorkspaceDropTarget(beforeId === null ? void 0 : {
1769
2757
  zone,
1770
2758
  beforeId
1771
2759
  });
1772
- },
1773
- onDragLeave: (event) => {
1774
- if (event.currentTarget.contains(event.relatedTarget)) return;
1775
- setWorkspaceDropTarget(void 0);
1776
- },
1777
- onDrop: (event) => {
2760
+ return;
2761
+ }
2762
+ const draggedSessionId = readSessionDrag(event.dataTransfer, sessionDrag?.sessionId);
2763
+ if (draggedSessionId === void 0) return;
2764
+ event.preventDefault();
2765
+ event.stopPropagation();
2766
+ event.dataTransfer.dropEffect = "move";
2767
+ const action = sessionDropAction(sourceWorkspaceIdForSession(draggedSessionId), workspaceId);
2768
+ setWorkspaceDropTarget(void 0);
2769
+ setSessionDropTarget({
2770
+ workspaceId,
2771
+ beforeId: action === "reorder" ? shownIds[0] : void 0,
2772
+ ontoProject: true,
2773
+ crossWorkspace: action === "move"
2774
+ });
2775
+ };
2776
+ const handleProjectDrop = (event) => {
2777
+ const draggedWorkspace = readWorkspaceDrag(event.dataTransfer, workspaceDragId);
2778
+ if (draggedWorkspace !== void 0) {
1778
2779
  event.preventDefault();
1779
2780
  event.stopPropagation();
1780
- const dragged = readWorkspaceDrag(event.dataTransfer, workspaceDragId);
1781
2781
  const rect = event.currentTarget.getBoundingClientRect();
1782
- const after = event.clientY > rect.top + rect.height / 2;
2782
+ const beforeId = reorderDropBeforeId(zoneIds, draggedWorkspace, workspaceId, event.clientY > rect.top + rect.height / 2);
1783
2783
  setWorkspaceDragId(void 0);
1784
2784
  setWorkspaceDropTarget(void 0);
1785
- if (dragged === void 0) return;
1786
- const beforeId = reorderDropBeforeId(zoneIds, dragged, workspaceId, after);
1787
2785
  if (beforeId === null) return;
1788
2786
  if (zone === "pinned") {
1789
- pinWorkspaceAt(dragged, beforeId);
2787
+ pinWorkspaceAt(draggedWorkspace, beforeId);
1790
2788
  return;
1791
2789
  }
1792
- setPinnedWorkspaceIds((ids) => ids.filter((id) => id !== dragged));
1793
2790
  const hostIds = groups.items.map((item) => String(item.workspaceId));
1794
- if (moveBefore(hostIds, dragged, beforeId).some((id, index) => id !== hostIds[index])) run("workspace-order", () => insertWorkspaceBefore(dragged, beforeId));
2791
+ if (moveBefore(hostIds, draggedWorkspace, beforeId).some((id, index) => id !== hostIds[index])) run("workspace-order", () => insertWorkspaceBefore(draggedWorkspace, beforeId));
2792
+ return;
2793
+ }
2794
+ const draggedSessionId = readSessionDrag(event.dataTransfer, sessionDrag?.sessionId);
2795
+ if (draggedSessionId === void 0) return;
2796
+ event.preventDefault();
2797
+ event.stopPropagation();
2798
+ const action = sessionDropAction(sourceWorkspaceIdForSession(draggedSessionId), workspaceId);
2799
+ setSessionDrag(void 0);
2800
+ setSessionDropTarget(void 0);
2801
+ setWorkspaceDropTarget(void 0);
2802
+ if (action === "move") {
2803
+ requestSessionMove(draggedSessionId, workspaceId);
2804
+ return;
2805
+ }
2806
+ run("session-order", () => insertSessionBefore(workspace.workspaceId, draggedSessionId, shownIds[0]));
2807
+ };
2808
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2809
+ className: `dcu-wb-project${isPinnedHeaderDrop || dropsBefore ? " dcu-wb-drop" : ""}${dropsAfterLast ? " dcu-wb-drop dcu-wb-drop-after" : ""}${projectSessionDrop ? " dcu-wb-session-drop" : ""}${projectSessionMoveDrop ? " dcu-wb-session-move-drop" : ""}`,
2810
+ onDragOver: handleProjectDragOver,
2811
+ onDragLeave: (event) => {
2812
+ if (event.currentTarget.contains(event.relatedTarget)) return;
2813
+ setWorkspaceDropTarget(void 0);
2814
+ setSessionDropTarget(void 0);
1795
2815
  },
1796
- children: [
1797
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1798
- className: `dcu-wb-project-head${menuOpen ? " dcu-wb-menu-open" : ""}${workspaceDragId === workspaceId ? " dcu-wb-dragging" : ""}`,
1799
- role: "treeitem",
1800
- "aria-expanded": isExpanded,
1801
- tabIndex: 0,
1802
- draggable: true,
1803
- onDragStart: (event) => {
1804
- event.stopPropagation();
1805
- setSessionDrag(void 0);
1806
- setSessionDropTarget(void 0);
1807
- writeWorkspaceDrag(event.dataTransfer, workspaceId, workspace.title);
1808
- const preview = document.createElement("div");
1809
- preview.className = "dcu-wb-drag-ghost";
1810
- preview.textContent = workspace.title;
1811
- document.body.appendChild(preview);
1812
- event.dataTransfer.setDragImage(preview, 16, 18);
1813
- window.requestAnimationFrame(() => {
1814
- preview.remove();
1815
- });
1816
- setWorkspaceDragId(workspaceId);
1817
- },
1818
- onDragEnd: () => {
1819
- setWorkspaceDragId(void 0);
1820
- setWorkspaceDropTarget(void 0);
1821
- },
1822
- onClick: () => {
1823
- toggleGroup(expandKey, true);
1824
- },
1825
- onContextMenu: (event) => {
2816
+ onDrop: handleProjectDrop,
2817
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2818
+ className: `dcu-wb-project-head${menuOpen ? " dcu-wb-menu-open" : ""}${workspaceDragId === workspaceId ? " dcu-wb-dragging" : ""}`,
2819
+ role: "treeitem",
2820
+ "aria-expanded": isExpanded,
2821
+ tabIndex: 0,
2822
+ draggable: true,
2823
+ onDragStart: (event) => {
2824
+ event.stopPropagation();
2825
+ setSessionDrag(void 0);
2826
+ setSessionDropTarget(void 0);
2827
+ setWorkspaceGroupDragId(void 0);
2828
+ setWorkspaceGroupDropTarget(void 0);
2829
+ writeWorkspaceDrag(event.dataTransfer, workspaceId, workspace.title);
2830
+ setDragPreview(event.dataTransfer, workspace.title, event.currentTarget.querySelector(".dcu-wb-folder"));
2831
+ setWorkspaceDragId(workspaceId);
2832
+ },
2833
+ onDragEnd: () => {
2834
+ setWorkspaceDragId(void 0);
2835
+ setWorkspaceDropTarget(void 0);
2836
+ setWorkspaceGroupDropTarget(void 0);
2837
+ },
2838
+ onClick: () => {
2839
+ toggleGroup(expandKey, true);
2840
+ },
2841
+ onContextMenu: (event) => {
2842
+ event.preventDefault();
2843
+ event.stopPropagation();
2844
+ dismissTip();
2845
+ setMenu({
2846
+ id: workspace.workspaceId,
2847
+ type: "workspace",
2848
+ x: event.clientX,
2849
+ y: event.clientY
2850
+ });
2851
+ },
2852
+ onMouseEnter: (event) => {
2853
+ const box = hoverCardAnchor(event.currentTarget.getBoundingClientRect());
2854
+ showTip({
2855
+ kind: "workspace",
2856
+ id: workspace.workspaceId,
2857
+ title: workspace.title,
2858
+ path: workspace.path,
2859
+ count: workspace.visibleIds.length,
2860
+ unreadCount: workspace.visibleIds.filter((id) => unreadSessionIds.includes(String(id))).length,
2861
+ pinned: projectPinned(workspace.workspaceId),
2862
+ left: box.left,
2863
+ top: box.top
2864
+ });
2865
+ },
2866
+ onMouseLeave: hideTip,
2867
+ onKeyDown: (event) => {
2868
+ if (event.key === "Enter" || event.key === " ") {
1826
2869
  event.preventDefault();
1827
- event.stopPropagation();
1828
- dismissTip();
1829
- setMenu({
1830
- id: workspace.workspaceId,
1831
- type: "workspace",
1832
- x: event.clientX,
1833
- y: event.clientY
1834
- });
1835
- },
1836
- onMouseEnter: (event) => {
1837
- const box = hoverCardAnchor(event.currentTarget.getBoundingClientRect());
1838
- showTip({
1839
- kind: "workspace",
1840
- id: workspace.workspaceId,
1841
- title: workspace.title,
1842
- path: workspace.path,
1843
- count: workspace.visibleIds.length,
1844
- left: box.left,
1845
- top: box.top
1846
- });
1847
- },
1848
- onMouseLeave: hideTip,
1849
- onKeyDown: (event) => {
1850
- if (event.key === "Enter" || event.key === " ") {
1851
- event.preventDefault();
1852
- toggleGroup(expandKey, true);
1853
- }
1854
- },
1855
- children: [
1856
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1857
- className: "dcu-wb-folder",
1858
- onClick: (event) => {
1859
- event.stopPropagation();
1860
- const box = hoverCardAnchor(event.currentTarget.getBoundingClientRect());
1861
- if (isShowing("workspace", workspace.workspaceId)) {
1862
- dismissTip();
1863
- return;
1864
- }
1865
- showTip({
1866
- kind: "workspace",
1867
- id: workspace.workspaceId,
1868
- title: workspace.title,
1869
- path: workspace.path,
1870
- count: workspace.visibleIds.length,
1871
- left: box.left,
1872
- top: box.top
1873
- }, { immediate: true });
2870
+ toggleGroup(expandKey, true);
2871
+ }
2872
+ },
2873
+ children: [
2874
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2875
+ className: "dcu-wb-folder",
2876
+ onClick: (event) => {
2877
+ event.stopPropagation();
2878
+ const box = hoverCardAnchor(event.currentTarget.getBoundingClientRect());
2879
+ if (isShowing("workspace", workspace.workspaceId)) {
2880
+ dismissTip();
2881
+ return;
2882
+ }
2883
+ showTip({
2884
+ kind: "workspace",
2885
+ id: workspace.workspaceId,
2886
+ title: workspace.title,
2887
+ path: workspace.path,
2888
+ count: workspace.visibleIds.length,
2889
+ unreadCount: workspace.visibleIds.filter((id) => unreadSessionIds.includes(String(id))).length,
2890
+ pinned: projectPinned(workspace.workspaceId),
2891
+ left: box.left,
2892
+ top: box.top
2893
+ }, { immediate: true });
2894
+ },
2895
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderClose16, { size: 16 })
2896
+ }),
2897
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2898
+ className: "dcu-wb-project-title",
2899
+ children: workspace.title
2900
+ }),
2901
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2902
+ className: "dcu-wb-actions",
2903
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
2904
+ open: menuOpen,
2905
+ onClose: () => {
2906
+ setMenu(void 0);
1874
2907
  },
1875
- children: isExpanded ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderOpenOutline16, { size: 16 }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderClose16, { size: 16 })
1876
- }),
1877
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1878
- className: "dcu-wb-project-title",
1879
- children: workspace.title
1880
- }),
1881
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1882
- className: "dcu-wb-actions",
1883
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
1884
- open: menuOpen,
1885
- onClose: () => {
1886
- setMenu(void 0);
1887
- },
1888
- items: projectMenu(workspace),
1889
- onSelect: (id) => {
1890
- if (busy !== void 0) return;
1891
- if (id === "new") {
1892
- startSession(workspace.workspaceId);
1893
- setMenu(void 0);
1894
- }
1895
- if (id === "rename") beginRename("workspace", workspace.workspaceId, workspace.title);
1896
- if (id === "pin") {
1897
- setPinnedWorkspaceIds((ids) => togglePinnedWorkspace(ids, workspace.workspaceId));
1898
- setMenu(void 0);
1899
- }
1900
- if (id === "openPath") openPathImmediately(workspace.path);
1901
- if (id === "delete") {
1902
- setDeleteTarget({
1903
- id: workspace.workspaceId,
1904
- kind: "workspace",
1905
- title: workspace.title
1906
- });
1907
- setError(void 0);
1908
- setMenu(void 0);
1909
- }
1910
- },
1911
- portal: true,
1912
- dense: true,
1913
- compact: true,
1914
- getAnchorRect: menuAt === void 0 ? void 0 : () => pointerMenuRect(menuAt.x, menuAt.y),
1915
- anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1916
- type: "button",
1917
- className: "dcu-wb-more",
1918
- "aria-label": t("workspace.actions", { name: workspace.title }),
1919
- onClick: (event) => {
1920
- event.stopPropagation();
1921
- setMenu((current) => current?.id === workspace.workspaceId && current.type === "workspace" ? void 0 : {
1922
- id: workspace.workspaceId,
1923
- type: "workspace"
1924
- });
1925
- },
1926
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEllipsisOutline16, { size: 16 })
1927
- })
1928
- })
1929
- }),
1930
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1931
- className: "dcu-wb-actions",
1932
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2908
+ items: projectMenu(workspace),
2909
+ onSelect: (id) => {
2910
+ handleProjectMenuAction(workspace, id);
2911
+ },
2912
+ portal: true,
2913
+ dense: true,
2914
+ compact: true,
2915
+ getAnchorRect: menuAt === void 0 ? void 0 : () => pointerMenuRect(menuAt.x, menuAt.y),
2916
+ anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1933
2917
  type: "button",
1934
2918
  className: "dcu-wb-more",
1935
- "aria-label": t("workspace.newSession"),
2919
+ "aria-label": t("workspace.actions", { name: workspace.title }),
1936
2920
  onClick: (event) => {
1937
2921
  event.stopPropagation();
1938
- startSession(workspace.workspaceId);
2922
+ dismissTip();
2923
+ setMenu((current) => current?.id === workspace.workspaceId && current?.type === "workspace" ? void 0 : {
2924
+ id: workspace.workspaceId,
2925
+ type: "workspace"
2926
+ });
1939
2927
  },
1940
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPlusOutline16, { size: 16 })
2928
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEllipsisOutline16, { size: 16 })
1941
2929
  })
1942
2930
  })
1943
- ]
1944
- }),
1945
- isExpanded && shownIds.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2931
+ }),
2932
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2933
+ className: "dcu-wb-actions",
2934
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2935
+ type: "button",
2936
+ className: "dcu-wb-more",
2937
+ "aria-label": t("workspace.newSession"),
2938
+ onClick: (event) => {
2939
+ event.stopPropagation();
2940
+ startSession(workspace.workspaceId);
2941
+ },
2942
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NewSessionIcon, {})
2943
+ })
2944
+ })
2945
+ ]
2946
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(DisclosureBody, {
2947
+ className: "dcu-wb-project-body",
2948
+ open: isExpanded,
2949
+ children: [shownIds.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1946
2950
  className: "dcu-wb-nochat",
1947
2951
  children: t("workspace.noChat")
1948
- }),
1949
- isExpanded && shownIds.map((id) => {
2952
+ }), shownIds.map((id) => {
1950
2953
  const session = sessions.byId[id];
1951
2954
  if (session === void 0) return null;
1952
- const pendingInteraction = visiblePendingKind(session.pendingInteraction);
2955
+ const pendingInteraction = pendingInteractionForSession(id, pendingInteractions, legacyPendingInteraction(session));
1953
2956
  const path = session.cwd ?? workspace.path;
1954
2957
  const selected = sessions.current === id;
1955
2958
  const sessionMenuOpen = menu?.type === "session" && menu.id === id;
@@ -1957,8 +2960,8 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1957
2960
  x: menu.x,
1958
2961
  y: menu.y
1959
2962
  } : void 0;
1960
- const dropsBeforeSession = sessionDropTarget?.workspaceId === workspaceId && sessionDropTarget.beforeId === id;
1961
- const dropsAfterLastSession = sessionDropTarget?.workspaceId === workspaceId && sessionDropTarget.beforeId === void 0 && shownIds[shownIds.length - 1] === id;
2963
+ const dropsBeforeSession = sessionDropTarget?.workspaceId === workspaceId && sessionDropTarget.ontoProject !== true && sessionDropTarget.beforeId === id;
2964
+ const dropsAfterLastSession = sessionDropTarget?.workspaceId === workspaceId && sessionDropTarget.ontoProject !== true && sessionDropTarget.beforeId === void 0 && shownIds[shownIds.length - 1] === id;
1962
2965
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1963
2966
  className: `dcu-wb-session${selected ? " dcu-wb-selected" : ""}${sessionMenuOpen ? " dcu-wb-menu-open" : ""}${dropsBeforeSession ? " dcu-wb-drop" : ""}${dropsAfterLastSession ? " dcu-wb-drop dcu-wb-drop-after" : ""}`,
1964
2967
  role: "treeitem",
@@ -1968,15 +2971,10 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1968
2971
  event.stopPropagation();
1969
2972
  setWorkspaceDragId(void 0);
1970
2973
  setWorkspaceDropTarget(void 0);
2974
+ setWorkspaceGroupDragId(void 0);
2975
+ setWorkspaceGroupDropTarget(void 0);
1971
2976
  writeSessionDrag(event.dataTransfer, id, session.displayTitle);
1972
- const preview = document.createElement("div");
1973
- preview.className = "dcu-wb-drag-ghost";
1974
- preview.textContent = session.displayTitle;
1975
- document.body.appendChild(preview);
1976
- event.dataTransfer.setDragImage(preview, 16, 18);
1977
- window.requestAnimationFrame(() => {
1978
- preview.remove();
1979
- });
2977
+ setDragPreview(event.dataTransfer, session.displayTitle);
1980
2978
  setSessionDrag({
1981
2979
  sessionId: id,
1982
2980
  workspaceId
@@ -1985,14 +2983,16 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1985
2983
  onDragEnd: () => {
1986
2984
  setSessionDrag(void 0);
1987
2985
  setSessionDropTarget(void 0);
2986
+ setWorkspaceGroupDropTarget(void 0);
1988
2987
  },
1989
2988
  onDragOver: (event) => {
1990
- if (sessionDrag?.workspaceId !== workspaceId) return;
2989
+ const drag = sessionDrag;
2990
+ if (drag === void 0 || drag.workspaceId !== workspaceId) return;
1991
2991
  event.preventDefault();
1992
2992
  event.stopPropagation();
1993
2993
  event.dataTransfer.dropEffect = "move";
1994
2994
  const rect = event.currentTarget.getBoundingClientRect();
1995
- const beforeId = reorderDropBeforeId(shownIds, sessionDrag.sessionId, id, event.clientY > rect.top + rect.height / 2);
2995
+ const beforeId = reorderDropBeforeId(shownIds, drag.sessionId, id, event.clientY > rect.top + rect.height / 2);
1996
2996
  setSessionDropTarget(beforeId === null ? void 0 : {
1997
2997
  workspaceId,
1998
2998
  beforeId
@@ -2003,13 +3003,13 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2003
3003
  setSessionDropTarget(void 0);
2004
3004
  },
2005
3005
  onDrop: (event) => {
3006
+ const drag = sessionDrag;
3007
+ if (drag === void 0 || drag.workspaceId !== workspaceId) return;
2006
3008
  event.preventDefault();
2007
3009
  event.stopPropagation();
2008
- const drag = sessionDrag;
2009
3010
  const rect = event.currentTarget.getBoundingClientRect();
2010
3011
  setSessionDrag(void 0);
2011
3012
  setSessionDropTarget(void 0);
2012
- if (drag === void 0 || drag.workspaceId !== workspaceId) return;
2013
3013
  const beforeId = reorderDropBeforeId(shownIds, drag.sessionId, id, event.clientY > rect.top + rect.height / 2);
2014
3014
  if (beforeId !== null) run("session-order", () => insertSessionBefore(workspace.workspaceId, drag.sessionId, beforeId));
2015
3015
  },
@@ -2038,7 +3038,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2038
3038
  project: workspace.title,
2039
3039
  path,
2040
3040
  branch,
2041
- time: formatHoverTime(session.updatedAt),
3041
+ time: formatHoverTime(session.updatedAt, t),
2042
3042
  left: box.left,
2043
3043
  top: box.top
2044
3044
  });
@@ -2049,37 +3049,15 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2049
3049
  className: "dcu-wb-session-title",
2050
3050
  children: session.displayTitle.split(/\r?\n/)[0] ?? session.displayTitle
2051
3051
  }),
2052
- pinnedSessionIds.includes(id) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2053
- className: "dcu-wb-pin",
2054
- "aria-label": t("sessions.pinned"),
2055
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
2056
- viewBox: "0 0 16 16",
2057
- "aria-hidden": "true",
2058
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M5 2.5h6M5 2.5v2L3.75 6v1h8.5V6L11 4.5v-2M8 7v6" })
2059
- })
2060
- }),
2061
3052
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionState, {
2062
3053
  pendingInteraction,
2063
3054
  unread: unreadSessionIds.includes(id),
2064
3055
  running: session.running === true,
2065
3056
  t
2066
3057
  }),
2067
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3058
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2068
3059
  className: "dcu-wb-quick-actions",
2069
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2070
- type: "button",
2071
- className: "dcu-wb-more dcu-wb-quick-pin",
2072
- "aria-label": t(pinnedSessionIds.includes(id) ? "sessions.unpin" : "sessions.pin"),
2073
- onClick: (event) => {
2074
- event.stopPropagation();
2075
- setPinnedSessionIds((ids) => toggleSessionId(ids, id));
2076
- },
2077
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
2078
- viewBox: "0 0 16 16",
2079
- "aria-hidden": "true",
2080
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M5 2.5h6M5 2.5v2L3.75 6v1h8.5V6L11 4.5v-2M8 7v6" })
2081
- })
2082
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3060
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2083
3061
  type: "button",
2084
3062
  className: "dcu-wb-more",
2085
3063
  "aria-label": t("sessions.archive"),
@@ -2087,8 +3065,8 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2087
3065
  event.stopPropagation();
2088
3066
  run("archive", () => archiveSession(id));
2089
3067
  },
2090
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconArchiveOutline20, { size: 16 })
2091
- })]
3068
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(QuickArchiveIcon, {})
3069
+ })
2092
3070
  }),
2093
3071
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2094
3072
  className: "dcu-wb-actions",
@@ -2097,15 +3075,10 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2097
3075
  onClose: () => {
2098
3076
  setMenu(void 0);
2099
3077
  },
2100
- items: sessionMenu(id, session.displayTitle, path),
3078
+ items: sessionMenu(id, path),
2101
3079
  onSelect: (action) => {
2102
- if (busy !== void 0) return;
2103
- const link = sessionDeepLink(browserBase(), id);
3080
+ if (busy !== void 0 || runSessionMoveAction(action, id)) return;
2104
3081
  if (action === "rename") beginRename("session", id, session.displayTitle);
2105
- if (action === "pin") {
2106
- setPinnedSessionIds((ids) => toggleSessionId(ids, id));
2107
- setMenu(void 0);
2108
- }
2109
3082
  if (action === "unread") {
2110
3083
  setUnreadSessionIds((ids) => toggleSessionId(ids, id));
2111
3084
  setMenu(void 0);
@@ -2125,7 +3098,6 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2125
3098
  if (action === "copyPath") copy(path);
2126
3099
  if (action === "copyTitle") copy(session.displayTitle);
2127
3100
  if (action === "copyId") copy(id);
2128
- if (action === "copyLink") copy(link);
2129
3101
  },
2130
3102
  portal: true,
2131
3103
  dense: true,
@@ -2137,7 +3109,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2137
3109
  "aria-label": t("sessions.actions", { name: session.displayTitle }),
2138
3110
  onClick: (event) => {
2139
3111
  event.stopPropagation();
2140
- setMenu((current) => current?.id === id && current.type === "session" ? void 0 : {
3112
+ setMenu((current) => current?.id === id && current?.type === "session" ? void 0 : {
2141
3113
  id,
2142
3114
  type: "session"
2143
3115
  });
@@ -2148,10 +3120,326 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2148
3120
  })
2149
3121
  ]
2150
3122
  }, id);
2151
- })
2152
- ]
3123
+ })]
3124
+ })]
2153
3125
  }, workspace.workspaceId);
2154
3126
  };
3127
+ const renderWorkspaceCollection = (group) => {
3128
+ const groupId = group.id;
3129
+ const expandKey = `workspace-group:${group.id}`;
3130
+ const isExpanded = expanded[expandKey] ?? true;
3131
+ const members = group.workspaceIds.flatMap((id) => {
3132
+ const workspace = workspaceById.get(id);
3133
+ return workspace === void 0 ? [] : [workspace];
3134
+ });
3135
+ const memberIds = members.map((workspace) => String(workspace.workspaceId));
3136
+ const groupWorkspaceMoveDrop = workspaceDropTarget?.zone === "group" && workspaceDropTarget.groupId === group.id && workspaceDropTarget.crossGroup === true;
3137
+ const groupOrderDrop = workspaceGroupDropTarget?.beforeId === groupId;
3138
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3139
+ className: `dcu-wb-collection${groupOrderDrop ? " dcu-wb-group-order-drop" : ""}${groupWorkspaceMoveDrop ? " dcu-wb-workspace-move-drop" : ""}`,
3140
+ onDragOver: (event) => {
3141
+ const draggedGroupId = readWorkspaceGroupDrag(event.dataTransfer, workspaceGroupDragId);
3142
+ if (draggedGroupId !== void 0) {
3143
+ event.preventDefault();
3144
+ event.stopPropagation();
3145
+ event.dataTransfer.dropEffect = "move";
3146
+ setWorkspaceDropTarget(void 0);
3147
+ const rect = event.currentTarget.querySelector(":scope > .dcu-wb-collection-head")?.getBoundingClientRect() ?? event.currentTarget.getBoundingClientRect();
3148
+ const beforeId = reorderDropBeforeId(workspaceGroupIds, draggedGroupId, groupId, event.clientY > rect.top + rect.height / 2);
3149
+ setWorkspaceGroupDropTarget(beforeId === null ? void 0 : { beforeId });
3150
+ return;
3151
+ }
3152
+ const dragged = readWorkspaceDrag(event.dataTransfer, workspaceDragId);
3153
+ if (dragged === void 0) return;
3154
+ event.preventDefault();
3155
+ event.stopPropagation();
3156
+ event.dataTransfer.dropEffect = "move";
3157
+ setWorkspaceGroupDropTarget(void 0);
3158
+ if (workspaceGroupIdFor(dragged) === groupId) {
3159
+ const lastId = memberIds[memberIds.length - 1];
3160
+ const beforeId = lastId === void 0 ? null : reorderDropBeforeId(memberIds, dragged, lastId, true);
3161
+ setWorkspaceDropTarget(beforeId === null ? void 0 : {
3162
+ zone: "group",
3163
+ groupId,
3164
+ beforeId
3165
+ });
3166
+ return;
3167
+ }
3168
+ setWorkspaceDropTarget({
3169
+ zone: "group",
3170
+ groupId,
3171
+ ontoGroup: true,
3172
+ crossGroup: true
3173
+ });
3174
+ },
3175
+ onDragLeave: (event) => {
3176
+ if (event.currentTarget.contains(event.relatedTarget)) return;
3177
+ setWorkspaceDropTarget(void 0);
3178
+ setWorkspaceGroupDropTarget(void 0);
3179
+ },
3180
+ onDrop: (event) => {
3181
+ const draggedGroupId = readWorkspaceGroupDrag(event.dataTransfer, workspaceGroupDragId);
3182
+ if (draggedGroupId !== void 0) {
3183
+ event.preventDefault();
3184
+ event.stopPropagation();
3185
+ const rect = event.currentTarget.querySelector(":scope > .dcu-wb-collection-head")?.getBoundingClientRect() ?? event.currentTarget.getBoundingClientRect();
3186
+ const beforeId = reorderDropBeforeId(workspaceGroupIds, draggedGroupId, groupId, event.clientY > rect.top + rect.height / 2);
3187
+ setWorkspaceGroupDragId(void 0);
3188
+ setWorkspaceGroupDropTarget(void 0);
3189
+ if (beforeId !== null) setWorkspaceGroups((current) => moveWorkspaceGroup(current, draggedGroupId, beforeId));
3190
+ return;
3191
+ }
3192
+ const dragged = readWorkspaceDrag(event.dataTransfer, workspaceDragId);
3193
+ if (dragged === void 0) return;
3194
+ event.preventDefault();
3195
+ event.stopPropagation();
3196
+ setWorkspaceDragId(void 0);
3197
+ setWorkspaceDropTarget(void 0);
3198
+ setWorkspaceGroups((current) => current.find((candidate) => candidate.workspaceIds.includes(dragged))?.id === groupId ? moveWorkspaceGroupMember(current, dragged, groupId) : assignWorkspaceToGroup(current, dragged, groupId));
3199
+ },
3200
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3201
+ className: `dcu-wb-collection-head${workspaceGroupDragId === groupId ? " dcu-wb-dragging" : ""}`,
3202
+ draggable: true,
3203
+ onDragStart: (event) => {
3204
+ event.stopPropagation();
3205
+ setSessionDrag(void 0);
3206
+ setSessionDropTarget(void 0);
3207
+ setWorkspaceDragId(void 0);
3208
+ setWorkspaceDropTarget(void 0);
3209
+ setWorkspaceGroupDropTarget(void 0);
3210
+ writeWorkspaceGroupDrag(event.dataTransfer, groupId, group.title);
3211
+ setDragPreview(event.dataTransfer, group.title);
3212
+ setWorkspaceGroupDragId(groupId);
3213
+ },
3214
+ onDragEnd: () => {
3215
+ setWorkspaceGroupDragId(void 0);
3216
+ setWorkspaceGroupDropTarget(void 0);
3217
+ },
3218
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
3219
+ type: "button",
3220
+ className: "dcu-wb-collection-label",
3221
+ "aria-expanded": isExpanded,
3222
+ onClick: () => {
3223
+ toggleGroup(expandKey, true);
3224
+ },
3225
+ children: [
3226
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3227
+ className: "dcu-wb-section-caret",
3228
+ "aria-hidden": "true",
3229
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
3230
+ viewBox: "0 0 16 16",
3231
+ width: "12",
3232
+ height: "12",
3233
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
3234
+ d: "M6.25 4.25 10.25 8 6.25 11.75",
3235
+ fill: "none",
3236
+ stroke: "currentColor",
3237
+ strokeWidth: "1.5",
3238
+ strokeLinecap: "round",
3239
+ strokeLinejoin: "round"
3240
+ })
3241
+ })
3242
+ }),
3243
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: group.title }),
3244
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3245
+ className: "dcu-wb-collection-count",
3246
+ children: members.length
3247
+ })
3248
+ ]
3249
+ })
3250
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(DisclosureBody, {
3251
+ className: "dcu-wb-collection-body",
3252
+ open: isExpanded,
3253
+ children: [members.map((workspace) => {
3254
+ const workspaceId = String(workspace.workspaceId);
3255
+ const preciseTarget = workspaceDropTarget?.zone === "group" && workspaceDropTarget.groupId === groupId && workspaceDropTarget.ontoGroup !== true && workspaceDropTarget.crossGroup !== true;
3256
+ const orderDrop = preciseTarget && workspaceDropTarget.beforeId === workspaceId;
3257
+ const orderDropAfter = preciseTarget && workspaceDropTarget.beforeId === void 0 && memberIds[memberIds.length - 1] === workspaceId;
3258
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3259
+ "data-dcu-group-member": true,
3260
+ className: `dcu-wb-group-member${orderDrop ? " dcu-wb-drop" : ""}${orderDropAfter ? " dcu-wb-drop dcu-wb-drop-after" : ""}`,
3261
+ onDragOverCapture: (event) => {
3262
+ const dragged = readWorkspaceDrag(event.dataTransfer, workspaceDragId);
3263
+ if (dragged === void 0) return;
3264
+ event.preventDefault();
3265
+ event.stopPropagation();
3266
+ event.dataTransfer.dropEffect = "move";
3267
+ const rect = event.currentTarget.getBoundingClientRect();
3268
+ const beforeId = reorderDropBeforeId(memberIds, dragged, workspaceId, event.clientY > rect.top + rect.height / 2);
3269
+ setWorkspaceDropTarget(beforeId === null ? void 0 : {
3270
+ zone: "group",
3271
+ groupId,
3272
+ beforeId,
3273
+ crossGroup: workspaceGroupIdFor(dragged) !== groupId
3274
+ });
3275
+ },
3276
+ onDropCapture: (event) => {
3277
+ const dragged = readWorkspaceDrag(event.dataTransfer, workspaceDragId);
3278
+ if (dragged === void 0) return;
3279
+ event.preventDefault();
3280
+ event.stopPropagation();
3281
+ const rect = event.currentTarget.getBoundingClientRect();
3282
+ const beforeId = reorderDropBeforeId(memberIds, dragged, workspaceId, event.clientY > rect.top + rect.height / 2);
3283
+ setWorkspaceDragId(void 0);
3284
+ setWorkspaceDropTarget(void 0);
3285
+ if (beforeId === null) return;
3286
+ setWorkspaceGroups((current) => current.find((candidate) => candidate.workspaceIds.includes(dragged))?.id === groupId ? moveWorkspaceGroupMember(current, dragged, groupId, beforeId) : placeWorkspaceInGroup(current, dragged, groupId, beforeId));
3287
+ },
3288
+ children: renderGroup(workspace, "projects")
3289
+ }, workspaceId);
3290
+ }), members.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3291
+ className: "dcu-wb-empty",
3292
+ children: t("workspace.empty")
3293
+ })]
3294
+ })]
3295
+ }, group.id);
3296
+ };
3297
+ const renderUngroupedCollection = () => {
3298
+ const ungroupedExpandKey = "workspace-group:ungrouped";
3299
+ const ungroupedExpanded = expanded[ungroupedExpandKey] ?? true;
3300
+ const ungroupedWorkspaceMoveDrop = workspaceDropTarget?.zone === "ungrouped" && workspaceDropTarget.crossGroup === true;
3301
+ const groupOrderDrop = workspaceGroupDropTarget !== void 0 && workspaceGroupDropTarget.beforeId === void 0;
3302
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3303
+ className: `dcu-wb-ungrouped${groupOrderDrop ? " dcu-wb-group-order-drop" : ""}${ungroupedWorkspaceMoveDrop ? " dcu-wb-workspace-move-drop" : ""}`,
3304
+ onDragOver: (event) => {
3305
+ const draggedGroupId = readWorkspaceGroupDrag(event.dataTransfer, workspaceGroupDragId);
3306
+ if (draggedGroupId !== void 0) {
3307
+ event.preventDefault();
3308
+ event.stopPropagation();
3309
+ event.dataTransfer.dropEffect = "move";
3310
+ setWorkspaceDropTarget(void 0);
3311
+ const lastGroupId = workspaceGroupIds[workspaceGroupIds.length - 1];
3312
+ const beforeId = lastGroupId === void 0 ? null : reorderDropBeforeId(workspaceGroupIds, draggedGroupId, lastGroupId, true);
3313
+ setWorkspaceGroupDropTarget(beforeId === null ? void 0 : { beforeId: void 0 });
3314
+ return;
3315
+ }
3316
+ const dragged = readWorkspaceDrag(event.dataTransfer, workspaceDragId);
3317
+ if (dragged === void 0) return;
3318
+ event.preventDefault();
3319
+ event.stopPropagation();
3320
+ event.dataTransfer.dropEffect = "move";
3321
+ setWorkspaceGroupDropTarget(void 0);
3322
+ if (workspaceGroupIdFor(dragged) === void 0) {
3323
+ const lastId = ungroupedGroupIds[ungroupedGroupIds.length - 1];
3324
+ const beforeId = lastId === void 0 ? null : reorderDropBeforeId(ungroupedGroupIds, dragged, lastId, true);
3325
+ setWorkspaceDropTarget(beforeId === null ? void 0 : {
3326
+ zone: "ungrouped",
3327
+ beforeId
3328
+ });
3329
+ return;
3330
+ }
3331
+ setWorkspaceDropTarget({
3332
+ zone: "ungrouped",
3333
+ ontoSection: true,
3334
+ crossGroup: true
3335
+ });
3336
+ },
3337
+ onDragLeave: (event) => {
3338
+ if (event.currentTarget.contains(event.relatedTarget)) return;
3339
+ setWorkspaceDropTarget(void 0);
3340
+ setWorkspaceGroupDropTarget(void 0);
3341
+ },
3342
+ onDrop: (event) => {
3343
+ const draggedGroupId = readWorkspaceGroupDrag(event.dataTransfer, workspaceGroupDragId);
3344
+ if (draggedGroupId !== void 0) {
3345
+ event.preventDefault();
3346
+ event.stopPropagation();
3347
+ const lastGroupId = workspaceGroupIds[workspaceGroupIds.length - 1];
3348
+ const beforeId = lastGroupId === void 0 ? null : reorderDropBeforeId(workspaceGroupIds, draggedGroupId, lastGroupId, true);
3349
+ setWorkspaceGroupDragId(void 0);
3350
+ setWorkspaceGroupDropTarget(void 0);
3351
+ if (beforeId !== null) setWorkspaceGroups((current) => moveWorkspaceGroup(current, draggedGroupId));
3352
+ return;
3353
+ }
3354
+ const dragged = readWorkspaceDrag(event.dataTransfer, workspaceDragId);
3355
+ if (dragged === void 0) return;
3356
+ event.preventDefault();
3357
+ event.stopPropagation();
3358
+ setWorkspaceDragId(void 0);
3359
+ setWorkspaceDropTarget(void 0);
3360
+ setWorkspaceGroups((current) => assignWorkspaceToGroup(current, dragged));
3361
+ run("workspace-order", () => insertWorkspaceBefore(dragged));
3362
+ },
3363
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3364
+ className: "dcu-wb-collection-head",
3365
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
3366
+ type: "button",
3367
+ className: "dcu-wb-collection-label",
3368
+ "aria-expanded": ungroupedExpanded,
3369
+ onClick: () => {
3370
+ toggleGroup(ungroupedExpandKey, true);
3371
+ },
3372
+ children: [
3373
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3374
+ className: "dcu-wb-section-caret",
3375
+ "aria-hidden": "true",
3376
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
3377
+ viewBox: "0 0 16 16",
3378
+ width: "12",
3379
+ height: "12",
3380
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
3381
+ d: "M6.25 4.25 10.25 8 6.25 11.75",
3382
+ fill: "none",
3383
+ stroke: "currentColor",
3384
+ strokeWidth: "1.5",
3385
+ strokeLinecap: "round",
3386
+ strokeLinejoin: "round"
3387
+ })
3388
+ })
3389
+ }),
3390
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("workspace.ungrouped") }),
3391
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3392
+ className: "dcu-wb-collection-count",
3393
+ children: ungroupedGroups.length
3394
+ })
3395
+ ]
3396
+ })
3397
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DisclosureBody, {
3398
+ className: "dcu-wb-collection-body",
3399
+ open: ungroupedExpanded,
3400
+ children: ungroupedGroups.map((workspace) => {
3401
+ const workspaceId = String(workspace.workspaceId);
3402
+ const preciseTarget = workspaceDropTarget?.zone === "ungrouped" && workspaceDropTarget.ontoSection !== true && workspaceDropTarget.crossGroup !== true;
3403
+ const orderDrop = preciseTarget && workspaceDropTarget.beforeId === workspaceId;
3404
+ const orderDropAfter = preciseTarget && workspaceDropTarget.beforeId === void 0 && ungroupedGroupIds[ungroupedGroupIds.length - 1] === workspaceId;
3405
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3406
+ "data-dcu-ungrouped-member": true,
3407
+ className: `dcu-wb-group-member dcu-wb-ungrouped-member${orderDrop ? " dcu-wb-drop" : ""}${orderDropAfter ? " dcu-wb-drop dcu-wb-drop-after" : ""}`,
3408
+ onDragOverCapture: (event) => {
3409
+ const dragged = readWorkspaceDrag(event.dataTransfer, workspaceDragId);
3410
+ if (dragged === void 0) return;
3411
+ event.preventDefault();
3412
+ event.stopPropagation();
3413
+ event.dataTransfer.dropEffect = "move";
3414
+ const rect = event.currentTarget.getBoundingClientRect();
3415
+ const beforeId = reorderDropBeforeId(ungroupedGroupIds, dragged, workspaceId, event.clientY > rect.top + rect.height / 2);
3416
+ setWorkspaceDropTarget(beforeId === null ? void 0 : {
3417
+ zone: "ungrouped",
3418
+ beforeId,
3419
+ crossGroup: workspaceGroupIdFor(dragged) !== void 0
3420
+ });
3421
+ },
3422
+ onDropCapture: (event) => {
3423
+ const dragged = readWorkspaceDrag(event.dataTransfer, workspaceDragId);
3424
+ if (dragged === void 0) return;
3425
+ event.preventDefault();
3426
+ event.stopPropagation();
3427
+ const rect = event.currentTarget.getBoundingClientRect();
3428
+ const beforeId = reorderDropBeforeId(ungroupedGroupIds, dragged, workspaceId, event.clientY > rect.top + rect.height / 2);
3429
+ setWorkspaceDragId(void 0);
3430
+ setWorkspaceDropTarget(void 0);
3431
+ if (beforeId === null) return;
3432
+ if (groupedIds.has(dragged)) setWorkspaceGroups((current) => assignWorkspaceToGroup(current, dragged));
3433
+ run("workspace-order", () => insertWorkspaceBefore(dragged, beforeId));
3434
+ },
3435
+ children: renderGroup(workspace, "projects")
3436
+ }, workspaceId);
3437
+ })
3438
+ })]
3439
+ });
3440
+ };
3441
+ const treeRef = (0, react.useRef)(null);
3442
+ useSectionDisclosureMotion(treeRef);
2155
3443
  if (!wide) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2156
3444
  className: "dcu-wb dcu-wb-rail",
2157
3445
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: stylesheet$4 })
@@ -2163,7 +3451,8 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2163
3451
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("style", { children: [
2164
3452
  stylesheet$4,
2165
3453
  runningStyles,
2166
- typographyStyles
3454
+ typographyStyles,
3455
+ collectionLayoutStyles
2167
3456
  ] }),
2168
3457
  error !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2169
3458
  className: "dcu-wb-error",
@@ -2171,6 +3460,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2171
3460
  children: t("sessions.failed", { message: error })
2172
3461
  }),
2173
3462
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3463
+ ref: treeRef,
2174
3464
  className: "dcu-wb-tree",
2175
3465
  role: "tree",
2176
3466
  children: [
@@ -2181,7 +3471,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2181
3471
  if (!pinDragActive) return;
2182
3472
  event.preventDefault();
2183
3473
  event.dataTransfer.dropEffect = "move";
2184
- if (event.target === event.currentTarget || event.target instanceof Element && event.target.closest(".dcu-wb-section-head") !== null) {
3474
+ if (pinnedGroups.length === 0 || event.target === event.currentTarget || event.target instanceof Element && event.target.closest(".dcu-wb-section-head") !== null) {
2185
3475
  const firstId = pinnedGroupIds[0];
2186
3476
  const beforeId = firstId === void 0 ? void 0 : reorderDropBeforeId(pinnedGroupIds, workspaceDragId, firstId, false);
2187
3477
  setWorkspaceDropTarget(beforeId === null ? void 0 : {
@@ -2232,46 +3522,53 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2232
3522
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2233
3523
  className: "dcu-wb-section-body",
2234
3524
  "data-open": sectionOpen("pinned"),
2235
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
2236
- pinDragActive && pinnedGroups.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2237
- className: `dcu-wb-pin-start${pinnedHeaderDrop?.kind === "empty" ? " dcu-wb-drop" : ""}`,
2238
- onDragOver: (event) => {
2239
- event.preventDefault();
2240
- event.stopPropagation();
2241
- event.dataTransfer.dropEffect = "move";
2242
- setWorkspaceDropTarget({
2243
- zone: "pinned",
2244
- beforeId: void 0
2245
- });
2246
- }
2247
- }),
2248
- pinnedGroups.length === 0 && !(workspaceDropTarget?.zone === "pinned" && workspaceDropTarget.beforeId === void 0) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2249
- className: "dcu-wb-empty",
2250
- children: t("workspace.pinnedEmpty")
2251
- }),
2252
- pinnedGroups.map((workspace) => renderGroup(workspace, "pinned")),
2253
- pinDragActive && pinnedGroups.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2254
- className: `dcu-wb-pin-end${workspaceDropTarget?.zone === "pinned" && workspaceDropTarget.beforeId === void 0 ? " dcu-wb-drop" : ""}`,
2255
- onDragOver: (event) => {
2256
- if (!pinDragActive) return;
2257
- event.preventDefault();
2258
- event.stopPropagation();
2259
- event.dataTransfer.dropEffect = "move";
2260
- const lastId = pinnedGroupIds[pinnedGroupIds.length - 1];
2261
- if (lastId === void 0 || workspaceDragId === void 0) return;
2262
- const beforeId = reorderDropBeforeId(pinnedGroupIds, workspaceDragId, lastId, true);
2263
- setWorkspaceDropTarget(beforeId === null ? void 0 : {
2264
- zone: "pinned",
2265
- beforeId
2266
- });
2267
- }
2268
- })
2269
- ] })
3525
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3526
+ className: "dcu-wb-pinned-list",
3527
+ children: [
3528
+ pinDragActive && pinnedGroups.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3529
+ className: `dcu-wb-pin-start${pinnedHeaderDrop?.kind === "empty" ? " dcu-wb-drop" : ""}`,
3530
+ onDragOver: (event) => {
3531
+ event.preventDefault();
3532
+ event.stopPropagation();
3533
+ event.dataTransfer.dropEffect = "move";
3534
+ setWorkspaceDropTarget({
3535
+ zone: "pinned",
3536
+ beforeId: void 0
3537
+ });
3538
+ }
3539
+ }),
3540
+ pinnedGroups.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3541
+ className: "dcu-wb-empty",
3542
+ children: t("workspace.pinnedEmpty")
3543
+ }),
3544
+ pinnedGroups.map((workspace) => renderGroup(workspace, "pinned")),
3545
+ pinDragActive && pinnedGroups.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3546
+ className: `dcu-wb-pin-end${workspaceDropTarget?.zone === "pinned" && workspaceDropTarget.beforeId === void 0 ? " dcu-wb-drop" : ""}`,
3547
+ onDragOver: (event) => {
3548
+ if (!pinDragActive) return;
3549
+ event.preventDefault();
3550
+ event.stopPropagation();
3551
+ event.dataTransfer.dropEffect = "move";
3552
+ const lastId = pinnedGroupIds[pinnedGroupIds.length - 1];
3553
+ if (lastId === void 0 || workspaceDragId === void 0) return;
3554
+ const beforeId = reorderDropBeforeId(pinnedGroupIds, workspaceDragId, lastId, true);
3555
+ setWorkspaceDropTarget(beforeId === null ? void 0 : {
3556
+ zone: "pinned",
3557
+ beforeId
3558
+ });
3559
+ }
3560
+ })
3561
+ ]
3562
+ })
2270
3563
  })]
2271
3564
  }),
2272
3565
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2273
3566
  className: "dcu-wb-section",
2274
3567
  "aria-label": t("workspace.projects"),
3568
+ onDropCapture: (event) => {
3569
+ const dragged = readWorkspaceDrag(event.dataTransfer, workspaceDragId);
3570
+ if (dragged !== void 0 && projectPinned(dragged)) setPinnedWorkspaceIds((ids) => ids.filter((id) => id !== dragged));
3571
+ },
2275
3572
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2276
3573
  className: "dcu-wb-section-head",
2277
3574
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
@@ -2298,26 +3595,66 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2298
3595
  })
2299
3596
  })
2300
3597
  })]
2301
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3598
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2302
3599
  className: "dcu-wb-actions",
2303
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3600
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2304
3601
  type: "button",
2305
3602
  className: "dcu-wb-more",
2306
- "aria-label": t("workspace.newSession"),
3603
+ "aria-label": t("workspace.createGroup"),
3604
+ title: t("workspace.createGroup"),
2307
3605
  onClick: (event) => {
2308
3606
  event.stopPropagation();
2309
- startSession();
3607
+ setGroupTitleDraft("");
3608
+ setError(void 0);
3609
+ setCreateGroupOpen(true);
2310
3610
  },
2311
3611
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPlusOutline16, { size: 16 })
2312
- })
3612
+ }), workspaceGroups.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
3613
+ open: groupMenuOpen,
3614
+ onClose: () => {
3615
+ setGroupMenuOpen(false);
3616
+ },
3617
+ items: workspaceGroups.map((group) => ({
3618
+ id: group.id,
3619
+ label: t("workspace.deleteGroup", { name: group.title }),
3620
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, { size: 16 }),
3621
+ danger: true
3622
+ })),
3623
+ onSelect: (id) => {
3624
+ setDeleteGroupId(id);
3625
+ setGroupMenuOpen(false);
3626
+ },
3627
+ portal: true,
3628
+ dense: true,
3629
+ compact: true,
3630
+ anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3631
+ type: "button",
3632
+ className: "dcu-wb-more",
3633
+ "aria-label": t("workspace.manageGroups"),
3634
+ title: t("workspace.manageGroups"),
3635
+ onClick: (event) => {
3636
+ event.stopPropagation();
3637
+ setGroupMenuOpen((current) => !current);
3638
+ },
3639
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEllipsisOutline16, { size: 16 })
3640
+ })
3641
+ })]
2313
3642
  })]
2314
3643
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2315
3644
  className: "dcu-wb-section-body",
2316
3645
  "data-open": sectionOpen("projects"),
2317
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [regularGroups.map((workspace) => renderGroup(workspace, "projects")), regularGroups.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2318
- className: "dcu-wb-empty",
2319
- children: t("workspace.empty")
2320
- })] })
3646
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3647
+ className: "dcu-wb-collections",
3648
+ children: [
3649
+ workspaceGroups.map(renderWorkspaceCollection),
3650
+ workspaceGroups.length > 0 && renderUngroupedCollection(),
3651
+ workspaceGroups.length === 0 && ungroupedGroups.map((workspace) => renderGroup(workspace, "projects")),
3652
+ workspaceGroups.length === 0 && ungroupedGroups.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3653
+ className: "dcu-wb-empty",
3654
+ children: t("workspace.empty")
3655
+ })
3656
+ ]
3657
+ })
2321
3658
  })]
2322
3659
  }),
2323
3660
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
@@ -2360,34 +3697,27 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2360
3697
  const session = sessions.byId[id];
2361
3698
  if (session === void 0) return null;
2362
3699
  const title = session.displayTitle;
2363
- const pinned = pinnedSessionIds.includes(id);
2364
3700
  const unread = unreadSessionIds.includes(id);
2365
- const pendingInteraction = visiblePendingKind(session.pendingInteraction);
3701
+ const pendingInteraction = pendingInteractionForSession(id, pendingInteractions, legacyPendingInteraction(session));
2366
3702
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionRow, {
2367
3703
  id,
2368
3704
  title,
2369
3705
  selected: sessions.current === id,
2370
3706
  menuOpen: menu?.type === "session" && menu.id === id,
2371
- pinned,
2372
3707
  unread,
2373
3708
  running: session.running === true,
2374
3709
  pendingInteraction,
2375
3710
  t,
2376
- menuItems: sessionMenu(id, title, session.cwd),
3711
+ menuItems: sessionMenu(id, session.cwd),
2377
3712
  draggable: true,
2378
3713
  onDragStart: (event) => {
2379
3714
  event.stopPropagation();
2380
3715
  setWorkspaceDragId(void 0);
2381
3716
  setWorkspaceDropTarget(void 0);
3717
+ setWorkspaceGroupDragId(void 0);
3718
+ setWorkspaceGroupDropTarget(void 0);
2382
3719
  writeSessionDrag(event.dataTransfer, id, title);
2383
- const preview = document.createElement("div");
2384
- preview.className = "dcu-wb-drag-ghost";
2385
- preview.textContent = title;
2386
- document.body.appendChild(preview);
2387
- event.dataTransfer.setDragImage(preview, 16, 18);
2388
- window.requestAnimationFrame(() => {
2389
- preview.remove();
2390
- });
3720
+ setDragPreview(event.dataTransfer, title);
2391
3721
  setSessionDrag({
2392
3722
  sessionId: id,
2393
3723
  workspaceId: ""
@@ -2397,6 +3727,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2397
3727
  setSessionDrag(void 0);
2398
3728
  setSessionDropTarget(void 0);
2399
3729
  setWorkspaceDropTarget(void 0);
3730
+ setWorkspaceGroupDropTarget(void 0);
2400
3731
  },
2401
3732
  menuPoint: menu?.type === "session" && menu.id === id && menu.x !== void 0 && menu.y !== void 0 ? {
2402
3733
  x: menu.x,
@@ -2412,9 +3743,6 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2412
3743
  type: "session"
2413
3744
  } : void 0);
2414
3745
  },
2415
- onPin: () => {
2416
- setPinnedSessionIds((ids) => toggleSessionId(ids, id));
2417
- },
2418
3746
  onArchive: () => {
2419
3747
  run("archive", () => archiveSession(id));
2420
3748
  },
@@ -2424,7 +3752,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2424
3752
  kind: "session",
2425
3753
  id,
2426
3754
  title,
2427
- time: formatHoverTime(session.updatedAt),
3755
+ time: formatHoverTime(session.updatedAt, t),
2428
3756
  left: box.left,
2429
3757
  top: box.top
2430
3758
  });
@@ -2442,12 +3770,8 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2442
3770
  });
2443
3771
  },
2444
3772
  onSelectAction: (action) => {
2445
- if (busy !== void 0) return;
3773
+ if (busy !== void 0 || runSessionMoveAction(action, id)) return;
2446
3774
  if (action === "rename") beginRename("session", id, title);
2447
- if (action === "pin") {
2448
- setPinnedSessionIds((ids) => toggleSessionId(ids, id));
2449
- setMenu(void 0);
2450
- }
2451
3775
  if (action === "unread") {
2452
3776
  setUnreadSessionIds((ids) => toggleSessionId(ids, id));
2453
3777
  setMenu(void 0);
@@ -2467,7 +3791,6 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2467
3791
  if (action === "copyPath") copy(session.cwd);
2468
3792
  if (action === "copyTitle") copy(title);
2469
3793
  if (action === "copyId") copy(id);
2470
- if (action === "copyLink") copy(sessionDeepLink(browserBase(), id));
2471
3794
  }
2472
3795
  }, id);
2473
3796
  })] })
@@ -2480,15 +3803,13 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2480
3803
  onClose: () => {
2481
3804
  setHeaderMenu(void 0);
2482
3805
  },
2483
- items: sessionMenu(headerMenu.id, sessions.byId[headerMenu.id].displayTitle, sessions.byId[headerMenu.id].cwd ?? groups.items.find((item) => item.visibleIds.includes(headerMenu.id))?.path),
3806
+ items: sessionMenu(headerMenu.id, sessions.byId[headerMenu.id].cwd ?? groups.items.find((item) => item.visibleIds.includes(headerMenu.id))?.path),
2484
3807
  onSelect: (action) => {
2485
3808
  const id = headerMenu.id;
2486
3809
  const session = sessions.byId[id];
2487
- if (session === void 0 || busy !== void 0) return;
3810
+ if (session === void 0 || busy !== void 0 || runSessionMoveAction(action, id)) return;
2488
3811
  const path = session.cwd ?? groups.items.find((item) => item.visibleIds.includes(id))?.path;
2489
- const link = sessionDeepLink(browserBase(), id);
2490
3812
  if (action === "rename") beginRename("session", id, session.displayTitle);
2491
- if (action === "pin") setPinnedSessionIds((ids) => toggleSessionId(ids, id));
2492
3813
  if (action === "unread") setUnreadSessionIds((ids) => toggleSessionId(ids, id));
2493
3814
  if (action === "archive") run("archive", () => archiveSession(id));
2494
3815
  if (action === "delete") {
@@ -2504,7 +3825,6 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2504
3825
  if (action === "copyPath") copy(path);
2505
3826
  if (action === "copyTitle") copy(session.displayTitle);
2506
3827
  if (action === "copyId") copy(id);
2507
- if (action === "copyLink") copy(link);
2508
3828
  setHeaderMenu(void 0);
2509
3829
  },
2510
3830
  portal: true,
@@ -2519,8 +3839,89 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2519
3839
  t,
2520
3840
  onEditWorkspace: (id, title) => {
2521
3841
  beginRename("workspace", id, title);
3842
+ },
3843
+ onToggleWorkspacePin: (id) => {
3844
+ setPinnedWorkspaceIds((ids) => togglePinnedWorkspace(ids, id));
2522
3845
  }
2523
3846
  }),
3847
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
3848
+ open: createGroupOpen,
3849
+ onClose: () => {
3850
+ setCreateGroupOpen(false);
3851
+ setGroupTitleDraft("");
3852
+ setError(void 0);
3853
+ },
3854
+ closeLabel: t("sessions.close"),
3855
+ title: t("workspace.createGroup"),
3856
+ description: t("workspace.createGroupDescription"),
3857
+ footer: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3858
+ className: "dcu-wb-rename-actions",
3859
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
3860
+ variant: "outline",
3861
+ onClick: () => {
3862
+ setCreateGroupOpen(false);
3863
+ setGroupTitleDraft("");
3864
+ setError(void 0);
3865
+ },
3866
+ children: t("sessions.cancel")
3867
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
3868
+ variant: "primary",
3869
+ disabled: groupTitleDraft.trim() === "",
3870
+ onClick: submitCreateGroup,
3871
+ children: t("sessions.save")
3872
+ })]
3873
+ }),
3874
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
3875
+ className: "dcu-wb-rename-input",
3876
+ "aria-label": t("workspace.groupName"),
3877
+ value: groupTitleDraft,
3878
+ autoFocus: true,
3879
+ onChange: (event) => {
3880
+ setGroupTitleDraft(event.target.value);
3881
+ setError(void 0);
3882
+ },
3883
+ onKeyDown: (event) => {
3884
+ if (event.key === "Enter") {
3885
+ event.preventDefault();
3886
+ submitCreateGroup();
3887
+ }
3888
+ }
3889
+ }), error !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3890
+ className: "dcu-wb-error",
3891
+ role: "alert",
3892
+ children: t("sessions.failed", { message: error })
3893
+ })]
3894
+ }),
3895
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
3896
+ open: deleteGroupId !== void 0,
3897
+ onClose: () => {
3898
+ setDeleteGroupId(void 0);
3899
+ },
3900
+ closeLabel: t("sessions.close"),
3901
+ title: t("workspace.deleteGroup", { name: workspaceGroups.find((group) => group.id === deleteGroupId)?.title ?? "" }),
3902
+ footer: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3903
+ className: "dcu-wb-rename-actions",
3904
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
3905
+ variant: "outline",
3906
+ onClick: () => {
3907
+ setDeleteGroupId(void 0);
3908
+ },
3909
+ children: t("sessions.cancel")
3910
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
3911
+ variant: "outline",
3912
+ className: "dcu-wb-delete-button",
3913
+ onClick: () => {
3914
+ if (deleteGroupId !== void 0) setWorkspaceGroups((current) => deleteWorkspaceGroup(current, deleteGroupId));
3915
+ setDeleteGroupId(void 0);
3916
+ },
3917
+ children: t("workspace.deleteGroupAction")
3918
+ })]
3919
+ }),
3920
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3921
+ className: "dcu-wb-delete-copy",
3922
+ children: t("workspace.deleteGroupDescription")
3923
+ })
3924
+ }),
2524
3925
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
2525
3926
  open: renameTarget !== void 0,
2526
3927
  onClose: () => {
@@ -2566,6 +3967,98 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2566
3967
  children: t("sessions.failed", { message: error })
2567
3968
  })]
2568
3969
  }),
3970
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
3971
+ open: archiveWorkspaceTarget !== void 0,
3972
+ onClose: () => {
3973
+ if (busy !== "archive-workspace") {
3974
+ setArchiveWorkspaceTarget(void 0);
3975
+ setError(void 0);
3976
+ }
3977
+ },
3978
+ closeLabel: t("sessions.close"),
3979
+ title: t("workspace.archiveTitle", { count: archiveWorkspaceTarget?.sessionIds.length ?? 0 }),
3980
+ description: archiveWorkspaceTarget === void 0 ? void 0 : t("workspace.archiveDescription", { name: archiveWorkspaceTarget.title }),
3981
+ footer: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3982
+ className: "dcu-wb-rename-actions",
3983
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
3984
+ variant: "outline",
3985
+ disabled: busy === "archive-workspace",
3986
+ onClick: () => {
3987
+ setArchiveWorkspaceTarget(void 0);
3988
+ setError(void 0);
3989
+ },
3990
+ children: t("sessions.cancel")
3991
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
3992
+ variant: "primary",
3993
+ disabled: busy === "archive-workspace",
3994
+ onClick: submitArchiveWorkspace,
3995
+ children: t("workspace.archiveConfirm")
3996
+ })]
3997
+ }),
3998
+ children: [busy === "archive-workspace" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3999
+ className: "dcu-wb-error",
4000
+ role: "status",
4001
+ children: t("workspace.archivePending")
4002
+ }), error !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4003
+ className: "dcu-wb-error",
4004
+ role: "alert",
4005
+ children: t("sessions.failed", { message: error })
4006
+ })]
4007
+ }),
4008
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
4009
+ open: sessionMoveTarget !== void 0,
4010
+ onClose: () => {
4011
+ if (busy !== "session-move") {
4012
+ setSessionMoveTarget(void 0);
4013
+ setError(void 0);
4014
+ }
4015
+ },
4016
+ closeLabel: t("sessions.close"),
4017
+ title: t("sessions.moveConfirmTitle", { project: sessionMoveTarget?.targetWorkspaceTitle ?? "" }),
4018
+ description: sessionMoveTarget === void 0 ? void 0 : t("sessions.moveConfirmDescription", { name: sessionMoveTarget.sessionTitle }),
4019
+ footer: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4020
+ className: "dcu-wb-rename-actions",
4021
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
4022
+ variant: "outline",
4023
+ disabled: busy === "session-move",
4024
+ onClick: () => {
4025
+ setSessionMoveTarget(void 0);
4026
+ setError(void 0);
4027
+ },
4028
+ children: t("sessions.cancel")
4029
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
4030
+ variant: "primary",
4031
+ disabled: busy === "session-move",
4032
+ onClick: submitSessionMove,
4033
+ children: t("sessions.moveConfirmAction")
4034
+ })]
4035
+ }),
4036
+ children: [
4037
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4038
+ className: "dcu-wb-move-target",
4039
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderClose16, { size: 16 }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4040
+ className: "dcu-wb-move-target-copy",
4041
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4042
+ className: "dcu-wb-move-project",
4043
+ children: sessionMoveTarget?.targetWorkspaceTitle
4044
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4045
+ className: "dcu-wb-move-path",
4046
+ children: sessionMoveTarget?.targetPath
4047
+ })]
4048
+ })]
4049
+ }),
4050
+ busy === "session-move" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4051
+ className: "dcu-wb-error",
4052
+ role: "status",
4053
+ children: t("sessions.movePending")
4054
+ }),
4055
+ error !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4056
+ className: "dcu-wb-error",
4057
+ role: "alert",
4058
+ children: t("sessions.failed", { message: error })
4059
+ })
4060
+ ]
4061
+ }),
2569
4062
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
2570
4063
  open: deleteTarget !== void 0,
2571
4064
  onClose: () => {
@@ -2624,13 +4117,9 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2624
4117
  function storage() {
2625
4118
  return browserStorage();
2626
4119
  }
2627
- /** 频道/定时共用的本地置顶与未读标记。 */
4120
+ /** 频道/定时共用的本地未读标记。 */
2628
4121
  function useSessionFlags(current) {
2629
- const [pinnedSessionIds, setPinnedSessionIds] = (0, react.useState)(() => readSessionIds(storage(), SESSION_PINS_STORAGE_KEY));
2630
4122
  const [unreadSessionIds, setUnreadSessionIds] = (0, react.useState)(() => readSessionIds(storage(), SESSION_UNREAD_STORAGE_KEY));
2631
- (0, react.useEffect)(() => {
2632
- writeSessionIds(storage(), SESSION_PINS_STORAGE_KEY, pinnedSessionIds);
2633
- }, [pinnedSessionIds]);
2634
4123
  (0, react.useEffect)(() => {
2635
4124
  writeSessionIds(storage(), SESSION_UNREAD_STORAGE_KEY, unreadSessionIds);
2636
4125
  }, [unreadSessionIds]);
@@ -2638,8 +4127,6 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2638
4127
  if (current !== void 0) setUnreadSessionIds((ids) => ids.filter((id) => id !== current));
2639
4128
  }, [current]);
2640
4129
  return {
2641
- pinnedSessionIds,
2642
- setPinnedSessionIds,
2643
4130
  unreadSessionIds,
2644
4131
  setUnreadSessionIds
2645
4132
  };
@@ -2654,7 +4141,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2654
4141
  onLeave: hideTip
2655
4142
  });
2656
4143
  }
2657
- function useBusyAction(onSuccess) {
4144
+ function useBusyAction(t, onSuccess) {
2658
4145
  const [busy, setBusy] = (0, react.useState)();
2659
4146
  const [error, setError] = (0, react.useState)();
2660
4147
  const busyRef = (0, react.useRef)();
@@ -2667,7 +4154,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2667
4154
  await action();
2668
4155
  onSuccess?.();
2669
4156
  } catch (reason) {
2670
- setError(reason instanceof Error ? reason.message : String(reason));
4157
+ setError(userErrorText(reason, t));
2671
4158
  } finally {
2672
4159
  busyRef.current = void 0;
2673
4160
  setBusy(void 0);
@@ -2697,7 +4184,6 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2697
4184
  const target = deleteTarget;
2698
4185
  run("delete", async () => {
2699
4186
  await actions.deleteSession(target.id);
2700
- flags.setPinnedSessionIds((ids) => ids.filter((id) => id !== target.id));
2701
4187
  flags.setUnreadSessionIds((ids) => ids.filter((id) => id !== target.id));
2702
4188
  setDeleteTarget(void 0);
2703
4189
  });
@@ -2711,10 +4197,6 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2711
4197
  setRenameDraft(title);
2712
4198
  closeMenu();
2713
4199
  }
2714
- if (action === "pin") {
2715
- flags.setPinnedSessionIds((ids) => toggleSessionId(ids, id));
2716
- closeMenu();
2717
- }
2718
4200
  if (action === "unread") {
2719
4201
  flags.setUnreadSessionIds((ids) => toggleSessionId(ids, id));
2720
4202
  closeMenu();
@@ -2734,9 +4216,6 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2734
4216
  if (action === "copyId") run("copy", async () => {
2735
4217
  await (0, _deepseek_ai_dsh_client_ui_primitives.writeClipboard)(id);
2736
4218
  });
2737
- if (action === "copyLink") run("copy", async () => {
2738
- await (0, _deepseek_ai_dsh_client_ui_primitives.writeClipboard)(copySessionLink(id));
2739
- });
2740
4219
  };
2741
4220
  return {
2742
4221
  renameTarget,
@@ -2869,14 +4348,16 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2869
4348
  })
2870
4349
  });
2871
4350
  }
2872
- function ChannelBrowserTree({ openSession, archiveSession, deleteSession, forkSession, renameSession, useSessions, t, menu, setMenu }) {
4351
+ function ChannelBrowserTree({ openSession, archiveSession, deleteSession, forkSession, moveSession, renameSession, useSessions, useSessionPendingInteraction, useWorkspaces, t, menu, setMenu }) {
2873
4352
  const sessions = useSessions((state) => state);
4353
+ const workspaces = useWorkspaces?.((state) => state);
4354
+ const pendingInteractions = (useSessionPendingInteraction ?? useEmptySessionPendingInteraction)((state) => state);
2874
4355
  const [groups, setGroups] = (0, react.useState)([]);
2875
4356
  const [pollError, setPollError] = (0, react.useState)();
2876
4357
  const [expanded, setExpanded] = (0, react.useState)(() => readTreeExpansionState(browserStorage(), CHANNEL_EXPANSION_STORAGE_KEY));
2877
4358
  const flags = useSessionFlags(sessions.current);
2878
4359
  const { showTip, hideTip, dismissTip } = useHoverDispatch();
2879
- const { busy, error, setError, run } = useBusyAction(() => {
4360
+ const { busy, error, setError, run } = useBusyAction(t, () => {
2880
4361
  setMenu(void 0);
2881
4362
  });
2882
4363
  const dialogs = useSessionDialogs({
@@ -2901,7 +4382,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2901
4382
  const timeout = window.setTimeout(() => {
2902
4383
  controller.abort();
2903
4384
  }, 8e3);
2904
- loadChannelGroups(controller.signal).then((next) => {
4385
+ loadChannelGroups(controller.signal, t("channel.unknown")).then((next) => {
2905
4386
  if (!disposed) {
2906
4387
  setGroups(next);
2907
4388
  setPollError(void 0);
@@ -2966,22 +4447,24 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2966
4447
  const selected = sessions.current === id;
2967
4448
  const running = session.running || sessions.byId[id]?.running === true;
2968
4449
  const updatedAt = session.updatedAt ?? sessions.byId[id]?.updatedAt;
2969
- const pinned = flags.pinnedSessionIds.includes(id);
2970
4450
  const unread = flags.unreadSessionIds.includes(id);
2971
- const pendingInteraction = visiblePendingKind(sessions.byId[id]?.pendingInteraction);
4451
+ const pendingInteraction = pendingInteractionForSession(id, pendingInteractions, sessions.byId[id]?.pendingInteraction);
4452
+ const moveTargets = moveSession === void 0 || workspaces === void 0 ? void 0 : sessionMoveTargets(workspaces.items, id).map((target) => ({
4453
+ ...target,
4454
+ id: moveSessionActionId(target.id)
4455
+ }));
2972
4456
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionRow, {
2973
4457
  id,
2974
4458
  title,
2975
4459
  selected,
2976
4460
  menuOpen: menu?.id === id,
2977
- pinned,
2978
4461
  unread,
2979
4462
  running,
2980
4463
  pendingInteraction,
2981
4464
  t,
2982
4465
  menuItems: sessionMenuItems(t, {
2983
- pinned,
2984
- unread
4466
+ unread,
4467
+ moveTargets
2985
4468
  }),
2986
4469
  menuPoint: menu?.id === id && menu.x !== void 0 && menu.y !== void 0 ? {
2987
4470
  x: menu.x,
@@ -2994,9 +4477,6 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2994
4477
  onMenuChange: (open) => {
2995
4478
  setMenu(open ? { id } : void 0);
2996
4479
  },
2997
- onPin: () => {
2998
- flags.setPinnedSessionIds((ids) => toggleSessionId(ids, id));
2999
- },
3000
4480
  onArchive: () => {
3001
4481
  run("archive", () => archiveSession(id));
3002
4482
  },
@@ -3005,7 +4485,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
3005
4485
  showTip({
3006
4486
  title,
3007
4487
  project: label,
3008
- time: updatedAt === void 0 ? void 0 : formatHoverTime(updatedAt),
4488
+ time: updatedAt === void 0 ? void 0 : formatHoverTime(updatedAt, t),
3009
4489
  left: box.left,
3010
4490
  top: box.top
3011
4491
  });
@@ -3022,7 +4502,14 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
3022
4502
  });
3023
4503
  },
3024
4504
  onSelectAction: (action) => {
3025
- if (busy === void 0) dialogs.handleAction(action, id, title);
4505
+ if (busy !== void 0) return;
4506
+ const targetWorkspaceId = parseMoveSessionActionId(action);
4507
+ if (targetWorkspaceId !== void 0 && moveSession !== void 0) {
4508
+ setMenu(void 0);
4509
+ run("session-move", () => moveSession(id, targetWorkspaceId));
4510
+ return;
4511
+ }
4512
+ dialogs.handleAction(action, id, title);
3026
4513
  }
3027
4514
  }, id);
3028
4515
  })]
@@ -3125,15 +4612,16 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
3125
4612
  })]
3126
4613
  });
3127
4614
  }
3128
- function ScheduleBrowserTree({ openSession, archiveSession, deleteSession, forkSession, renameSession, useSessions, useWorkspaces, t, openTaskSettings, menu, setMenu }) {
4615
+ function ScheduleBrowserTree({ openSession, archiveSession, deleteSession, forkSession, moveSession, renameSession, useSessions, useSessionPendingInteraction, useWorkspaces, t, openTaskSettings, menu, setMenu }) {
3129
4616
  const sessions = useSessions((state) => state);
4617
+ const pendingInteractions = (useSessionPendingInteraction ?? useEmptySessionPendingInteraction)((state) => state);
3130
4618
  const workspaces = useWorkspaces((state) => state);
3131
4619
  const [expanded, setExpanded] = (0, react.useState)(() => readTreeExpansionState(browserStorage(), SCHEDULE_EXPANSION_STORAGE_KEY));
3132
4620
  const [groupMenu, setGroupMenu] = (0, react.useState)();
3133
4621
  const [archiveGroupTarget, setArchiveGroupTarget] = (0, react.useState)();
3134
4622
  const flags = useSessionFlags(sessions.current);
3135
4623
  const { showTip, hideTip, dismissTip } = useHoverDispatch();
3136
- const { busy, error, setError, run } = useBusyAction(() => {
4624
+ const { busy, error, setError, run } = useBusyAction(t, () => {
3137
4625
  setMenu(void 0);
3138
4626
  });
3139
4627
  const dialogs = useSessionDialogs({
@@ -3176,10 +4664,11 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
3176
4664
  updatedAt: session.updatedAt,
3177
4665
  running: session.running === true
3178
4666
  }];
3179
- }));
4667
+ }), t("meta.locale"));
3180
4668
  }, [
3181
4669
  sessions.byId,
3182
4670
  sessions.ids,
4671
+ t,
3183
4672
  workspaces.archivedSessionIds
3184
4673
  ]);
3185
4674
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
@@ -3257,22 +4746,24 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
3257
4746
  }), isExpanded && group.sessions.map((session) => {
3258
4747
  const id = session.id;
3259
4748
  const title = session.title;
3260
- const pinned = flags.pinnedSessionIds.includes(id);
3261
4749
  const unread = flags.unreadSessionIds.includes(id);
3262
- const pendingInteraction = visiblePendingKind(sessions.byId[id]?.pendingInteraction);
4750
+ const pendingInteraction = pendingInteractionForSession(id, pendingInteractions, sessions.byId[id]?.pendingInteraction);
4751
+ const moveTargets = moveSession === void 0 ? void 0 : sessionMoveTargets(workspaces.items ?? [], id).map((target) => ({
4752
+ ...target,
4753
+ id: moveSessionActionId(target.id)
4754
+ }));
3263
4755
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionRow, {
3264
4756
  id,
3265
4757
  title,
3266
4758
  selected: sessions.current === id,
3267
4759
  menuOpen: menu?.id === id,
3268
- pinned,
3269
4760
  unread,
3270
4761
  running: session.running,
3271
4762
  pendingInteraction,
3272
4763
  t,
3273
4764
  menuItems: sessionMenuItems(t, {
3274
- pinned,
3275
- unread
4765
+ unread,
4766
+ moveTargets
3276
4767
  }),
3277
4768
  menuPoint: menu?.id === id && menu.x !== void 0 && menu.y !== void 0 ? {
3278
4769
  x: menu.x,
@@ -3285,9 +4776,6 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
3285
4776
  onMenuChange: (open) => {
3286
4777
  setMenu(open ? { id } : void 0);
3287
4778
  },
3288
- onPin: () => {
3289
- flags.setPinnedSessionIds((ids) => toggleSessionId(ids, id));
3290
- },
3291
4779
  onArchive: () => {
3292
4780
  run("archive", () => archiveSession(id));
3293
4781
  },
@@ -3296,7 +4784,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
3296
4784
  showTip({
3297
4785
  title,
3298
4786
  project: group.label,
3299
- time: session.updatedAt === void 0 ? void 0 : formatHoverTime(session.updatedAt),
4787
+ time: session.updatedAt === void 0 ? void 0 : formatHoverTime(session.updatedAt, t),
3300
4788
  left: box.left,
3301
4789
  top: box.top
3302
4790
  });
@@ -3313,7 +4801,14 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
3313
4801
  });
3314
4802
  },
3315
4803
  onSelectAction: (action) => {
3316
- if (busy === void 0) dialogs.handleAction(action, id, title);
4804
+ if (busy !== void 0) return;
4805
+ const targetWorkspaceId = parseMoveSessionActionId(action);
4806
+ if (targetWorkspaceId !== void 0 && moveSession !== void 0) {
4807
+ setMenu(void 0);
4808
+ run("session-move", () => moveSession(id, targetWorkspaceId));
4809
+ return;
4810
+ }
4811
+ dialogs.handleAction(action, id, title);
3317
4812
  }
3318
4813
  }, id);
3319
4814
  })]
@@ -3358,14 +4853,13 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
3358
4853
  const target = archiveGroupTarget;
3359
4854
  run("archive-group", async () => {
3360
4855
  const result = await archiveScheduleGroup(target.sessionIds, (id) => archiveSession(id));
3361
- flags.setPinnedSessionIds((ids) => ids.filter((id) => !result.archivedIds.includes(id)));
3362
4856
  flags.setUnreadSessionIds((ids) => ids.filter((id) => !result.archivedIds.includes(id)));
3363
4857
  if (result.failedIds.length > 0) {
3364
4858
  setArchiveGroupTarget({
3365
4859
  ...target,
3366
4860
  sessionIds: result.failedIds
3367
4861
  });
3368
- throw new Error(t("schedule.archiveGroupPartial", {
4862
+ throw new UserFacingError(t("schedule.archiveGroupPartial", {
3369
4863
  archived: result.archivedIds.length,
3370
4864
  failed: result.failedIds.length
3371
4865
  }));
@@ -3399,9 +4893,10 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
3399
4893
  ]
3400
4894
  });
3401
4895
  }
4896
+ const visibleSidebarWidths = /* @__PURE__ */ new WeakMap();
3402
4897
  /** 解析宿主 AppFrame 的 grid-template-columns。 */
3403
4898
  function parseSidebarGrid(value) {
3404
- const match = /^(\d+(?:\.\d+)?)px\s+(minmax\(0,\s*1fr\))\s+(\d+(?:\.\d+)?)px$/.exec(value.trim());
4899
+ const match = /^(\d+(?:\.\d+)?)px\s+(minmax\(0(?:px)?,\s*1fr\))\s+(\d+(?:\.\d+)?)px$/.exec(value.trim());
3405
4900
  if (match === null) return void 0;
3406
4901
  return {
3407
4902
  sidebar: Number(match[1]),
@@ -3409,41 +4904,41 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
3409
4904
  details: Number(match[3])
3410
4905
  };
3411
4906
  }
3412
- /**
3413
- * Translate the DSH host's 264–420px drag range to Codex's measured
3414
- * 240–520px range, preserving the 280px-host / 275px-Codex default anchor.
3415
- */
3416
- function slimedSidebarWidth(hostWidth, collapsed, viewportWidth = Number.POSITIVE_INFINITY) {
3417
- if (collapsed || hostWidth <= 80) return hostWidth;
3418
- const maximum = Math.max(240, Math.min(520, viewportWidth - 240));
3419
- const source = Math.max(264, Math.min(420, hostWidth));
3420
- if (source <= 280) return 240 + (source - 264) * 35 / 16;
3421
- return 275 + (source - 280) * (maximum - 275) / 140;
3422
- }
3423
- function slimedGridTemplate(tracks, collapsed, viewportWidth = Number.POSITIVE_INFINITY) {
3424
- return `${slimedSidebarWidth(tracks.sidebar, collapsed, viewportWidth)}px ${tracks.middle} ${tracks.details}px`;
3425
- }
3426
4907
  function findSidebarFrame(root) {
3427
4908
  const marked = root.querySelector("[data-sidebar-collapsed]");
3428
4909
  if (marked !== null) return marked;
3429
4910
  for (const node of root.querySelectorAll("div")) if (parseSidebarGrid(node.style.gridTemplateColumns) !== void 0) return node;
3430
4911
  }
3431
- function applySlimSidebar(frame) {
3432
- if (frame.hasAttribute("data-dragging")) {
3433
- frame.removeAttribute("data-dcu-codex-sidebar-grid");
3434
- return false;
3435
- }
4912
+ /** 同步侧栏网格和拖拽柄,保证两者始终使用同一个可见宽度。 */
4913
+ function applySidebarWidth(frame, width) {
3436
4914
  const tracks = parseSidebarGrid(frame.style.gridTemplateColumns);
3437
- if (tracks === void 0) return false;
3438
- const collapsed = frame.hasAttribute("data-sidebar-collapsed");
3439
- const next = slimedGridTemplate(tracks, collapsed, frame.getBoundingClientRect().width);
3440
- if (frame.getAttribute("data-dcu-codex-sidebar-grid") === frame.style.gridTemplateColumns) return false;
3441
- if (frame.style.gridTemplateColumns === next) return false;
3442
- frame.style.gridTemplateColumns = next;
3443
- frame.setAttribute("data-dcu-codex-sidebar-grid", next);
4915
+ if (tracks === void 0 || frame.hasAttribute("data-sidebar-collapsed")) return false;
4916
+ visibleSidebarWidths.set(frame, width);
4917
+ const next = `${width}px ${tracks.middle} ${tracks.details}px`;
4918
+ const changed = frame.style.gridTemplateColumns !== next;
4919
+ if (changed) frame.style.gridTemplateColumns = next;
3444
4920
  const handle = frame.querySelector("[data-side=\"sidebar\"]");
3445
- if (handle !== null && !collapsed) handle.style.left = `${slimedSidebarWidth(tracks.sidebar, collapsed, frame.getBoundingClientRect().width)}px`;
3446
- return true;
4921
+ if (handle !== null) handle.style.left = `${width}px`;
4922
+ return changed;
4923
+ }
4924
+ function applySlimSidebar(frame) {
4925
+ if (frame.hasAttribute("data-dragging")) return false;
4926
+ if (parseSidebarGrid(frame.style.gridTemplateColumns) === void 0 || frame.hasAttribute("data-sidebar-collapsed")) return false;
4927
+ const changed = applySidebarWidth(frame, frame.hasAttribute("data-dcu-codex-sidebar-initialized") ? visibleSidebarWidths.get(frame) ?? 240 : 240);
4928
+ frame.setAttribute("data-dcu-codex-sidebar-initialized", "");
4929
+ return changed;
4930
+ }
4931
+ /** 仅在首帧覆盖宿主默认宽度时关闭过渡,避免刷新出现收缩动画。 */
4932
+ function pauseInitialSidebarTransition(frame) {
4933
+ const handle = frame.querySelector("[data-side=\"sidebar\"]");
4934
+ const frameTransition = frame.style.transition;
4935
+ const handleTransition = handle?.style.transition;
4936
+ frame.style.transition = "none";
4937
+ if (handle !== null) handle.style.transition = "none";
4938
+ return () => {
4939
+ frame.style.transition = frameTransition;
4940
+ if (handle !== null && handleTransition !== void 0) handle.style.transition = handleTransition;
4941
+ };
3447
4942
  }
3448
4943
  function observeSlimSidebar() {
3449
4944
  if (typeof document === "undefined" || document.body === null) return () => {};
@@ -3462,7 +4957,8 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
3462
4957
  attributeFilter: [
3463
4958
  "style",
3464
4959
  "data-sidebar-collapsed",
3465
- "data-dragging"
4960
+ "data-dragging",
4961
+ "data-dcu-codex-sidebar-initialized"
3466
4962
  ]
3467
4963
  });
3468
4964
  };
@@ -3471,7 +4967,14 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
3471
4967
  applying = true;
3472
4968
  try {
3473
4969
  if (frame === void 0 || !frame.isConnected) watchFrame(findSidebarFrame(document));
3474
- if (frame !== void 0) applySlimSidebar(frame);
4970
+ if (frame !== void 0) {
4971
+ const restoreTransition = frame.hasAttribute("data-dcu-codex-sidebar-initialized") ? void 0 : pauseInitialSidebarTransition(frame);
4972
+ const changed = applySlimSidebar(frame);
4973
+ if (restoreTransition !== void 0) {
4974
+ if (!changed) restoreTransition();
4975
+ else window.requestAnimationFrame(() => window.requestAnimationFrame(restoreTransition));
4976
+ }
4977
+ }
3475
4978
  } finally {
3476
4979
  applying = false;
3477
4980
  }
@@ -3499,9 +5002,14 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
3499
5002
  }
3500
5003
  //#endregion
3501
5004
  //#region src/client/sidebar-drag.ts
3502
- /** Codex collapses the sidebar when a resize drag would make it narrower than 240px. */
5005
+ const SIDEBAR_MAX_PX = 420;
5006
+ /** 左移超过开始宽度的一半时收起侧栏,避免轻微拖动误触。 */
3503
5007
  function shouldCollapseOnSidebarDrag(startWidth, startX, endX) {
3504
- return startWidth + endX - startX < 240;
5008
+ return startX - endX > startWidth / 2;
5009
+ }
5010
+ /** 直接按可见宽度计算拖拽结果,绕开宿主与 Codex 不一致的最小宽度。 */
5011
+ function sidebarWidthDuringDrag(startWidth, startX, currentX) {
5012
+ return Math.min(SIDEBAR_MAX_PX, Math.max(240, startWidth + currentX - startX));
3505
5013
  }
3506
5014
  function isSidebarDragHandle(target) {
3507
5015
  if (target === null || typeof target !== "object" || !("closest" in target)) return false;
@@ -3544,20 +5052,34 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
3544
5052
  const subscribeEmptyCompanionTabs = () => () => {};
3545
5053
  const getEmptyCompanionTabs = () => EMPTY_COMPANION_TABS;
3546
5054
  const SIDEBAR_COLLAPSE_SETTLE_MS = 140;
5055
+ const SIDEBAR_EXPANSION_STORAGE_KEY = "dsh-codex-ui.sidebar-expansion.v1";
5056
+ const EXTENSIONS_EXPANSION_KEY = "extensions";
5057
+ function readExtensionsOpen() {
5058
+ return readTreeExpansionState(browserStorage(), SIDEBAR_EXPANSION_STORAGE_KEY)[EXTENSIONS_EXPANSION_KEY] !== false;
5059
+ }
5060
+ function writeExtensionsOpen(open) {
5061
+ const storage = browserStorage();
5062
+ const expanded = readTreeExpansionState(storage, SIDEBAR_EXPANSION_STORAGE_KEY);
5063
+ writeTreeExpansionState(storage, SIDEBAR_EXPANSION_STORAGE_KEY, {
5064
+ ...expanded,
5065
+ [EXTENSIONS_EXPANSION_KEY]: open
5066
+ });
5067
+ }
3547
5068
  const stylesheet$3 = `
3548
- .dcu-root{--dcu-font:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI","Microsoft YaHei UI",sans-serif;--dcu-sidebar-wide-width:240px;--dcu-sidebar-primary:#393d3e;--dcu-sidebar-secondary:#676b6c;--dcu-sidebar-tertiary:#9a9f9f;--dcu-sidebar-navigation:#4e5253;--dcu-sidebar-icon:#4e5253;--dcu-sidebar-hover:#dfe8e5;--dcu-sidebar-border:rgba(37,46,41,.10);--dcu-tip-bg:#ffffff;--dcu-tip-shadow:0 10px 32px rgba(31,39,36,.22);height:100%;min-width:0;box-sizing:border-box;display:flex;flex-direction:column;overflow:hidden;background:#eef7f5;color:var(--dcu-sidebar-primary);font:14px/20px var(--dcu-font)}body[data-ds-dark-theme] .dcu-root{background:#1d2120;--dcu-sidebar-primary:#b9bab9;--dcu-sidebar-secondary:#909191;--dcu-sidebar-tertiary:#666867;--dcu-sidebar-navigation:#b9bab9;--dcu-sidebar-icon:#afafaf;--dcu-sidebar-hover:#303432;--dcu-sidebar-border:rgba(255,255,255,.08);--dcu-tip-bg:#2a2e2c;--dcu-tip-shadow:0 10px 30px rgba(0,0,0,.28)}
3549
- .dcu-expanded-shell{display:flex;width:var(--dcu-sidebar-wide-width);min-height:0;flex:1 1 0;overflow:hidden;flex-direction:column;transform-origin:left center;transition:opacity 140ms ease-out,transform 180ms cubic-bezier(.16,1,.3,1);animation:dcu-sidebar-expanded-in 180ms cubic-bezier(.16,1,.3,1)}.dcu-compact-shell{display:none;width:56px}.dcu-root.dcu-collapsing .dcu-expanded-shell{opacity:0;transform:translateX(-6px);pointer-events:none}.dcu-root.dcu-compact .dcu-expanded-shell{display:none}.dcu-root.dcu-compact .dcu-compact-shell{display:flex;min-height:0;flex:1;flex-direction:column;align-items:center;animation:dcu-sidebar-compact-in 140ms ease-out}@keyframes dcu-sidebar-expanded-in{from{opacity:0;transform:translateX(-6px)}to{opacity:1;transform:none}}@keyframes dcu-sidebar-compact-in{from{opacity:0;transform:translateX(-4px)}to{opacity:1;transform:none}}
5069
+ .dcu-root{--dcu-font:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI","Microsoft YaHei UI",sans-serif;--dcu-sidebar-primary:#393d3e;--dcu-sidebar-secondary:#676b6c;--dcu-sidebar-tertiary:#9a9f9f;--dcu-sidebar-navigation:#4e5253;--dcu-sidebar-icon:#4e5253;--dcu-sidebar-hover:#dfe8e5;--dcu-sidebar-border:rgba(37,46,41,.10);--dcu-tip-bg:#ffffff;--dcu-tip-shadow:0 10px 32px rgba(31,39,36,.22);width:100%;height:100%;min-width:0;box-sizing:border-box;display:flex;flex-direction:column;overflow:hidden;background:#eef7f5;color:var(--dcu-sidebar-primary);font:14px/20px var(--dcu-font)}body[data-ds-dark-theme] .dcu-root{background:#1d2120;--dcu-sidebar-primary:#b9bab9;--dcu-sidebar-secondary:#909191;--dcu-sidebar-tertiary:#666867;--dcu-sidebar-navigation:#b9bab9;--dcu-sidebar-icon:#afafaf;--dcu-sidebar-hover:#303432;--dcu-sidebar-border:rgba(255,255,255,.08);--dcu-tip-bg:#2a2a2a;--dcu-tip-shadow:0 10px 30px rgba(0,0,0,.28)}
5070
+ .dcu-expanded-shell{display:flex;width:100%;min-height:0;flex:1 1 0;overflow:hidden;flex-direction:column;transform-origin:left center;transition:opacity 140ms ease-out,transform 180ms cubic-bezier(.16,1,.3,1);animation:dcu-sidebar-expanded-in 180ms cubic-bezier(.16,1,.3,1)}.dcu-compact-shell{display:none;width:56px}.dcu-root.dcu-collapsing .dcu-expanded-shell{opacity:0;transform:translateX(-6px);pointer-events:none}.dcu-root.dcu-compact .dcu-expanded-shell{display:none}.dcu-root.dcu-compact .dcu-compact-shell{display:flex;min-height:0;flex:1;flex-direction:column;align-items:center;animation:dcu-sidebar-compact-in 140ms ease-out}@keyframes dcu-sidebar-expanded-in{from{opacity:0;transform:translateX(-6px)}to{opacity:1;transform:none}}@keyframes dcu-sidebar-compact-in{from{opacity:0;transform:translateX(-4px)}to{opacity:1;transform:none}}
3550
5071
  .dcu-root *{box-sizing:border-box}.dcu-head{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;column-gap:8px;height:60px;padding:8px 8px 8px 12px}.dcu-brand{border:0;background:transparent;color:inherit;padding:0;display:flex;align-items:center;min-width:0;overflow:hidden}.dcu-brand svg{display:block;width:auto;max-width:100%;height:24px;min-width:0}.dcu-head-actions{display:grid;grid-auto-flow:column;grid-auto-columns:28px;align-items:center;column-gap:8px;height:28px}
3551
5072
  .dcu-icon,.dcu-menu button,.dcu-footer-link{appearance:none;border:0;background:transparent;color:inherit;font:inherit;cursor:pointer}.dcu-icon{display:grid;place-items:center;width:36px;height:36px;border-radius:8px;color:var(--dcu-sidebar-icon)}.dcu-head .dcu-icon{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;margin:0;padding:0;border-radius:50%;line-height:0}.dcu-head .dcu-icon svg{display:block;width:16px;height:16px}
3552
5073
  .dcu-icon:hover,.dcu-menu button:hover:not(:disabled),.dcu-footer-link:hover{background:var(--dcu-sidebar-hover);color:var(--dcu-sidebar-primary)}
3553
5074
  .dcu-menu{padding:0 6px 8px;display:grid;gap:2px}.dcu-menu button,.dcu-footer-link{display:grid;grid-template-columns:20px minmax(0,1fr);column-gap:8px;align-items:center;width:100%;min-height:36px;padding:0 4px;border-radius:8px;color:var(--dcu-sidebar-navigation);font-size:14px;line-height:20px;text-align:left;font-weight:400}
3554
5075
  .dcu-menu-icon{display:grid;place-items:center start;width:20px;height:20px}.dcu-menu-icon svg,.dcu-footer-link svg{display:block;width:16px;height:16px;color:var(--dcu-sidebar-icon)}.dcu-menu button:disabled{color:var(--dcu-sidebar-secondary);cursor:default;opacity:1}.dcu-menu button:disabled svg{color:var(--dcu-sidebar-secondary)}
3555
- .dcu-extension-items{display:grid;gap:1px;margin:1px 0 4px 28px}.dcu-extension-items button{min-height:32px;color:var(--dcu-sidebar-secondary);font-size:13px;font-weight:400}
3556
- .dcu-workspaces{display:flex;min-height:0;flex:1;flex-direction:column;margin-top:2px;padding-top:8px;border-top:1px solid var(--dcu-sidebar-border)}.dcu-workspaces.dcu-workspaces-tabs{padding-top:0;border-top:0}.dcu-im-tabs{display:flex;gap:16px;margin:0 8px 12px;padding:0;border-bottom:1px solid var(--dcu-sidebar-border)}.dcu-im-tab{appearance:none;border:0;background:transparent;color:var(--dcu-sidebar-secondary);padding:8px 0 7px;font:14px/22px var(--dcu-font);font-weight:500;cursor:pointer}.dcu-im-tab[data-on=true]{color:var(--dcu-sidebar-primary);font-weight:600;box-shadow:inset 0 -2px 0 currentColor}.dcu-native-workspaces{display:flex;min-height:0;flex:1}.dcu-native-workspaces>*{min-width:0;flex:1}.dcu-native-workspaces .ima-tabs,.dcu-native-workspaces [role=tablist]{display:none!important}.dcu-schedule-browser{display:flex;min-height:0;flex:1;flex-direction:column}.dcu-native-workspaces .dcu-schedule-views{display:flex!important;flex:none;min-height:30px;margin:0 8px 8px;padding:2px;border:1px solid var(--dcu-sidebar-border);border-radius:8px;background:rgba(255,255,255,.025)}.dcu-schedule-views button{appearance:none;flex:1;min-width:0;height:24px;border:0;border-radius:6px;background:transparent;color:var(--dcu-sidebar-secondary);font:600 12px/18px var(--dcu-font);cursor:pointer}.dcu-schedule-views button[aria-selected=true]{background:var(--dcu-sidebar-hover);color:var(--dcu-sidebar-primary);box-shadow:inset 0 0 0 1px var(--dcu-sidebar-border)}.dcu-schedule-pane{display:flex;min-height:0;flex:1}.dcu-schedule-pane>*{min-width:0;flex:1}.dcu-schedule-pane>[data-slot="sidebar.schedule"]{display:flex!important;width:100%;min-width:0;flex:1}.dcu-schedule-pane>[data-slot="sidebar.schedule"]>.dsh-st-rail{width:100%;min-width:0;padding-right:8px;scrollbar-gutter:auto}.dcu-schedule-pane .dsh-st-overview{padding-right:8px}.dcu-foot{display:grid;width:var(--dcu-sidebar-wide-width);gap:4px;padding:8px 6px 12px;border-top:1px solid var(--dcu-sidebar-border);transition:opacity 120ms ease-out,transform 160ms cubic-bezier(.16,1,.3,1)}.dcu-root.dcu-collapsing>.dcu-foot{opacity:0;transform:translateX(-4px);pointer-events:none}.dcu-footer-actions:empty,.dcu-settings-seat:empty{display:none}.dcu-settings-seat>button{width:100%;min-height:36px;padding-left:4px!important;color:var(--dcu-sidebar-navigation);font:14px/20px var(--dcu-font);font-weight:400}.dcu-compact{width:100%;align-items:flex-start;overflow:hidden;padding:10px 0 8px}.dcu-compact-nav{display:flex;flex:1;min-height:0;flex-direction:column;align-items:center;gap:2px;overflow:auto;padding:6px 0}.dcu-compact .dcu-icon{width:36px;height:36px;flex:none}.dcu-compact .dcu-foot{width:36px;margin-top:auto;margin-left:10px;padding:8px 0;border-top:0}.dcu-compact .dcu-settings-seat{width:36px;overflow:hidden}.dcu-compact .dcu-settings-seat>button{display:grid;place-items:center;width:36px;min-height:36px;padding:0!important;font-size:0!important;line-height:0}.dcu-compact .dcu-settings-seat>button svg{width:16px;height:16px}.dcu-compact .dcu-footer-link{display:flex;justify-content:center;width:36px;padding:0;font-size:0}.dcu-compact .dcu-footer-link svg{width:16px;height:16px}
5076
+ .dcu-extensions-group{display:grid}.dcu-extension-leading{position:relative;display:block;width:16px;height:16px}.dcu-extension-leading svg{position:absolute;inset:0;transition:opacity 140ms ease-out,transform 220ms cubic-bezier(.16,1,.3,1)}.dcu-extension-default-icon{opacity:1}.dcu-extension-state-arrow{opacity:0;transform:rotate(0)}.dcu-extensions-group:hover .dcu-extension-default-icon,.dcu-extensions-toggle:focus-visible .dcu-extension-default-icon{opacity:0}.dcu-extensions-group:hover .dcu-extension-state-arrow,.dcu-extensions-toggle:focus-visible .dcu-extension-state-arrow{opacity:1}.dcu-extensions-toggle[aria-expanded=true] .dcu-extension-state-arrow{transform:rotate(90deg)}.dcu-extension-panel{display:grid;grid-template-rows:1fr;opacity:1;transition:grid-template-rows 220ms cubic-bezier(.16,1,.3,1),opacity 160ms ease-out}.dcu-extension-panel[data-open=false]{grid-template-rows:0fr;opacity:0;pointer-events:none}.dcu-extension-panel-inner{position:relative;min-height:0;overflow:hidden}.dcu-extension-items{position:relative;display:grid;gap:1px;margin:1px 0 4px 28px}.dcu-extension-items::before{content:"";position:absolute;left:-16px;top:0;bottom:4px;width:1px;background:var(--dcu-sidebar-border)}.dcu-extension-items button{grid-template-columns:minmax(0,1fr);min-height:32px;color:var(--dcu-sidebar-secondary);font-size:13px;font-weight:400}.dcu-extension-items .dcu-menu-icon{display:none}
5077
+ .dcu-workspaces{display:flex;min-height:0;flex:1;flex-direction:column;margin-top:2px;padding-top:8px;border-top:1px solid var(--dcu-sidebar-border)}.dcu-workspaces.dcu-workspaces-tabs{padding-top:0;border-top:0}.dcu-im-tabs{display:flex;gap:16px;margin:0 8px 12px;padding:0;border-bottom:1px solid var(--dcu-sidebar-border)}.dcu-im-tab{appearance:none;border:0;background:transparent;color:var(--dcu-sidebar-secondary);padding:8px 0 7px;font:14px/22px var(--dcu-font);font-weight:500;cursor:pointer}.dcu-im-tab[data-on=true]{color:var(--dcu-sidebar-primary);font-weight:600;box-shadow:inset 0 -2px 0 currentColor}.dcu-native-workspaces{display:flex;min-height:0;flex:1}.dcu-native-workspaces>*{min-width:0;flex:1}.dcu-native-workspaces .ima-tabs,.dcu-native-workspaces [role=tablist]{display:none!important}.dcu-schedule-browser{display:flex;min-height:0;flex:1;flex-direction:column}.dcu-native-workspaces .dcu-schedule-views{display:flex!important;flex:none;min-height:30px;margin:0 8px 8px;padding:2px;border:1px solid var(--dcu-sidebar-border);border-radius:8px;background:rgba(255,255,255,.025)}.dcu-schedule-views button{appearance:none;flex:1;min-width:0;height:24px;border:0;border-radius:6px;background:transparent;color:var(--dcu-sidebar-secondary);font:600 12px/18px var(--dcu-font);cursor:pointer}.dcu-schedule-views button[aria-selected=true]{background:var(--dcu-sidebar-hover);color:var(--dcu-sidebar-primary);box-shadow:inset 0 0 0 1px var(--dcu-sidebar-border)}.dcu-schedule-pane{display:flex;min-height:0;flex:1}.dcu-schedule-pane>*{min-width:0;flex:1}.dcu-schedule-pane>[data-slot="sidebar.schedule"]{display:flex!important;width:100%;min-width:0;flex:1}.dcu-schedule-pane>[data-slot="sidebar.schedule"]>.dsh-st-rail{width:100%;min-width:0;padding-right:8px;scrollbar-gutter:auto}.dcu-schedule-pane .dsh-st-overview{padding-right:8px}.dcu-foot{display:grid;width:100%;gap:4px;padding:8px 6px 12px;border-top:1px solid var(--dcu-sidebar-border);transition:opacity 120ms ease-out,transform 160ms cubic-bezier(.16,1,.3,1)}.dcu-root.dcu-collapsing>.dcu-foot{opacity:0;transform:translateX(-4px);pointer-events:none}.dcu-footer-actions:empty,.dcu-settings-seat:empty{display:none}.dcu-settings-seat>button{width:100%;min-height:36px;padding-left:4px!important;color:var(--dcu-sidebar-navigation);font:14px/20px var(--dcu-font);font-weight:400}.dcu-compact{width:100%;align-items:flex-start;overflow:hidden;padding:10px 0 8px}.dcu-compact-nav{display:flex;flex:1;min-height:0;flex-direction:column;align-items:center;gap:2px;overflow:auto;padding:6px 0}.dcu-compact .dcu-icon{width:36px;height:36px;flex:none}.dcu-compact .dcu-foot{width:36px;margin-top:auto;margin-left:10px;padding:8px 0;border-top:0}.dcu-compact .dcu-settings-seat{width:36px;overflow:hidden}.dcu-compact .dcu-settings-seat>button{display:grid;place-items:center;width:36px;min-height:36px;padding:0!important;font-size:0!important;line-height:0}.dcu-compact .dcu-settings-seat>button svg{width:16px;height:16px}.dcu-compact .dcu-footer-link{display:flex;justify-content:center;width:36px;padding:0;font-size:0}.dcu-compact .dcu-footer-link svg{width:16px;height:16px}
5078
+ .dcu-expanded-shell,.dcu-foot{width:100%}
3557
5079
  .dcu-native-workspaces{flex-direction:column}.dcu-native-workspaces>[data-mcp-connector-top-mount=true]{flex:none}.dcu-root .mcpConnectorLauncher,.dcu-root [data-mcp-connector-top-mount=true]{display:none!important}
3558
5080
  .dcu-dependency-notice{margin:0 10px 8px;border:1px solid var(--dcu-sidebar-border);border-radius:8px;padding:8px;color:var(--dcu-sidebar-secondary);font-size:12px;line-height:18px}
3559
5081
  [role="dialog"][aria-labelledby]{width:min(1000px,calc(100vw - 48px));max-width:calc(100vw - 48px);height:min(800px,calc(100vh - 48px))}
3560
- .dcu-search-scrim{position:fixed;z-index:10020;inset:0;display:flex;justify-content:center;align-items:flex-start;padding:72px 20px;background:color-mix(in srgb,#000 48%,transparent);animation:dcu-search-scrim-in 140ms ease-out}.dcu-search-dialog{width:min(560px,100%);max-height:min(640px,calc(100vh - 120px));overflow:auto;border:1px solid var(--dcu-sidebar-border);border-radius:16px;padding:10px;background:var(--dsw-specific-menu);box-shadow:var(--dsw-shadow-lv4);animation:dcu-search-dialog-in 180ms cubic-bezier(.16,1,.3,1)}@keyframes dcu-search-scrim-in{from{opacity:0}to{opacity:1}}@keyframes dcu-search-dialog-in{from{opacity:0;transform:translateY(-6px) scale(.985)}to{opacity:1;transform:none}}.dcu-search-input{margin-bottom:8px}.dcu-search-section{padding:6px 0}.dcu-search-title{padding:0 8px 4px;color:var(--dcu-sidebar-tertiary);font-size:12px;font-weight:600}.dcu-search-row{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:12px;width:100%;min-height:34px;border:0;border-radius:8px;padding:6px 8px;background:transparent;color:var(--dcu-sidebar-primary);font:inherit;text-align:left;cursor:pointer}.dcu-search-row:hover,.dcu-search-row[data-active=true]{background:var(--dcu-sidebar-hover)}.dcu-search-main{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dcu-search-detail{max-width:160px;overflow:hidden;color:var(--dcu-sidebar-tertiary);font-size:12px;text-overflow:ellipsis;white-space:nowrap}.dcu-search-empty{padding:18px 8px;color:var(--dcu-sidebar-tertiary);font-size:13px}@media (prefers-reduced-motion:reduce){.dcu-expanded-shell,.dcu-root.dcu-compact .dcu-compact-shell,.dcu-foot,.dcu-search-scrim,.dcu-search-dialog{animation:none;transition:none}}
5082
+ .dcu-search-scrim{position:fixed;z-index:10020;inset:0;display:flex;justify-content:center;align-items:flex-start;padding:72px 20px;background:color-mix(in srgb,#000 48%,transparent);animation:dcu-search-scrim-in 140ms ease-out}.dcu-search-dialog{width:min(560px,100%);max-height:min(640px,calc(100vh - 120px));overflow:auto;border:1px solid var(--dcu-sidebar-border);border-radius:16px;padding:10px;background:var(--dsw-specific-menu);box-shadow:var(--dsw-shadow-lv4);animation:dcu-search-dialog-in 180ms cubic-bezier(.16,1,.3,1)}@keyframes dcu-search-scrim-in{from{opacity:0}to{opacity:1}}@keyframes dcu-search-dialog-in{from{opacity:0;transform:translateY(-6px) scale(.985)}to{opacity:1}}.dcu-search-input{margin-bottom:8px}.dcu-search-section{padding:6px 0}.dcu-search-title{padding:0 8px 4px;color:var(--dcu-sidebar-tertiary);font-size:12px;font-weight:600}.dcu-search-row{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:12px;width:100%;min-height:34px;border:0;border-radius:8px;padding:6px 8px;background:transparent;color:var(--dcu-sidebar-primary);font:inherit;text-align:left;cursor:pointer}.dcu-search-row:hover,.dcu-search-row[data-active=true]{background:var(--dcu-sidebar-hover)}.dcu-search-main{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dcu-search-detail{max-width:160px;overflow:hidden;color:var(--dcu-sidebar-tertiary);font-size:12px;text-overflow:ellipsis;white-space:nowrap}.dcu-search-empty{padding:18px 8px;color:var(--dcu-sidebar-tertiary);font-size:13px}@media (prefers-reduced-motion:reduce){.dcu-expanded-shell,.dcu-root.dcu-compact .dcu-compact-shell,.dcu-foot,.dcu-search-scrim,.dcu-search-dialog,.dcu-extension-panel,.dcu-extension-leading svg{animation:none;transition:none}}
3561
5083
  [data-conversation-scroll]{--dsh-composer-card-max-width:calc(var(--dsh-chat-content-width) + 32px);--dsh-composer-side-clearance:24px}[data-conversation-scroll] [data-composer-card]{min-height:96px;padding-top:10px;border-radius:16px;box-shadow:var(--dsw-shadow-lv3);transition:border-color 180ms ease,box-shadow 180ms ease}[data-conversation-scroll] [data-composer-card]:focus-within{border-color:var(--dsw-alias-button-info-fill);box-shadow:0 0 0 2px var(--dsw-static-deepseek-50),var(--dsw-shadow-lv3)}[data-conversation-scroll] [data-input-mirror]{min-height:44px}@media (prefers-reduced-motion:reduce){[data-conversation-scroll] [data-composer-card]{transition:none}}
3562
5084
  html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:has([data-dcu-official-turn-navigator]) .dcu-turn-navigator{display:none}
3563
5085
  [data-dcu-official-turn-navigator]{right:auto!important;left:calc(12px - (var(--dsh-composer-side-clearance) + 16px))!important}
@@ -3873,12 +5395,10 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
3873
5395
  });
3874
5396
  });
3875
5397
  /** Codex 风格的 DSH 侧栏,只替换导航外观,项目浏览和设置仍由 DSH 官方组件提供。 */
3876
- function CodexSidebar({ collapsed, width, openSession, startSession, toggleSidebar, archiveSession, deleteSession, forkSession, renameSession, openPath, companionSlots, renderSlot, t, useSessions, useWorkspaces }) {
5398
+ function CodexSidebar({ collapsed, width, openSession, startSession, toggleSidebar, archiveSession, deleteSession, forkSession, moveSession, renameSession, openPath, companionSlots, renderSlot, t, useSessions, useSessionPendingInteraction, useWorkspaces }) {
3877
5399
  const compact = collapsed || width < 80;
3878
5400
  const [visualCompact, setVisualCompact] = (0, react.useState)(compact);
3879
5401
  const [collapsing, setCollapsing] = (0, react.useState)(false);
3880
- const lastWideWidth = (0, react.useRef)(width >= 80 ? width : 275);
3881
- if (!compact) lastWideWidth.current = width;
3882
5402
  const settingsSeat = (0, react.useRef)(null);
3883
5403
  const search = (0, react.useRef)(null);
3884
5404
  const toggleSidebarRef = (0, react.useRef)(toggleSidebar);
@@ -3886,7 +5406,7 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
3886
5406
  const expandSidebar = (0, react.useCallback)(() => {
3887
5407
  toggleSidebarRef.current();
3888
5408
  }, []);
3889
- const [extensionsOpen, setExtensionsOpen] = (0, react.useState)(true);
5409
+ const [extensionsOpen, setExtensionsOpen] = (0, react.useState)(readExtensionsOpen);
3890
5410
  const [imTab, setImTab] = (0, react.useState)("tasks");
3891
5411
  const companionTabs = (0, react.useSyncExternalStore)(companionSlots?.subscribe ?? subscribeEmptyCompanionTabs, companionSlots?.getSnapshot ?? getEmptyCompanionTabs, companionSlots?.getSnapshot ?? getEmptyCompanionTabs);
3892
5412
  const showChannels = companionTabs.channels;
@@ -3920,42 +5440,77 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
3920
5440
  ]);
3921
5441
  (0, react.useEffect)(() => {
3922
5442
  let startX = 0;
3923
- let startWidth = 275;
5443
+ let startWidth = 240;
3924
5444
  let dragging = false;
3925
5445
  let pointerId;
5446
+ let frame;
5447
+ let handle;
5448
+ const stopHostDrag = (event) => {
5449
+ event.preventDefault();
5450
+ event.stopPropagation();
5451
+ };
5452
+ const finishDrag = () => {
5453
+ frame?.removeAttribute("data-dragging");
5454
+ handle?.removeAttribute("data-dragging");
5455
+ if (handle !== void 0 && pointerId !== void 0 && handle.hasPointerCapture?.(pointerId)) handle.releasePointerCapture(pointerId);
5456
+ dragging = false;
5457
+ pointerId = void 0;
5458
+ frame = void 0;
5459
+ handle = void 0;
5460
+ };
3926
5461
  const onDown = (event) => {
3927
5462
  if (dragging || !isSidebarDragHandle(event.target)) return;
5463
+ const nextFrame = findSidebarFrame(document);
5464
+ const tracks = nextFrame === void 0 ? void 0 : parseSidebarGrid(nextFrame.style.gridTemplateColumns);
5465
+ const nextHandle = event.target instanceof Element ? event.target.closest("[data-side=\"sidebar\"]") ?? void 0 : void 0;
5466
+ if (nextFrame === void 0 || tracks === void 0 || nextHandle === void 0) return;
5467
+ stopHostDrag(event);
3928
5468
  dragging = true;
3929
5469
  pointerId = event.pointerId;
3930
5470
  startX = event.clientX;
3931
- const match = (document.querySelector("[data-sidebar-collapsed]") ?? [...document.querySelectorAll("div")].find((node) => node.style.gridTemplateColumns.includes("minmax(0, 1fr)")))?.style.gridTemplateColumns.match(/^(\d+(?:\.\d+)?)px\s/);
3932
- startWidth = match === null || match === void 0 ? 275 : Number(match[1]);
5471
+ startWidth = tracks.sidebar;
5472
+ frame = nextFrame;
5473
+ handle = nextHandle;
5474
+ frame.setAttribute("data-dragging", "");
5475
+ handle.setAttribute("data-dragging", "true");
5476
+ try {
5477
+ handle.setPointerCapture(event.pointerId);
5478
+ } catch {}
5479
+ };
5480
+ const onMove = (event) => {
5481
+ if (!dragging || event.pointerId !== pointerId || frame === void 0) return;
5482
+ stopHostDrag(event);
5483
+ applySidebarWidth(frame, sidebarWidthDuringDrag(startWidth, startX, event.clientX));
3933
5484
  };
3934
5485
  const onUp = (event) => {
3935
5486
  if (!dragging || event.pointerId !== pointerId) return;
3936
- dragging = false;
3937
- pointerId = void 0;
3938
- if (!shouldCollapseOnSidebarDrag(startWidth, startX, event.clientX)) return;
3939
- window.requestAnimationFrame(() => {
5487
+ stopHostDrag(event);
5488
+ if (frame !== void 0) applySidebarWidth(frame, sidebarWidthDuringDrag(startWidth, startX, event.clientX));
5489
+ const collapse = shouldCollapseOnSidebarDrag(startWidth, startX, event.clientX);
5490
+ finishDrag();
5491
+ if (collapse) window.requestAnimationFrame(() => {
3940
5492
  window.requestAnimationFrame(() => {
3941
- toggleSidebar();
5493
+ toggleSidebarRef.current();
3942
5494
  });
3943
5495
  });
3944
5496
  };
3945
5497
  const onCancel = (event) => {
3946
5498
  if (event.pointerId !== pointerId) return;
3947
- dragging = false;
3948
- pointerId = void 0;
5499
+ stopHostDrag(event);
5500
+ finishDrag();
3949
5501
  };
3950
5502
  window.addEventListener("pointerdown", onDown, true);
5503
+ window.addEventListener("pointermove", onMove, true);
3951
5504
  window.addEventListener("pointerup", onUp, true);
3952
5505
  window.addEventListener("pointercancel", onCancel, true);
3953
5506
  return () => {
5507
+ finishDrag();
3954
5508
  window.removeEventListener("pointerdown", onDown, true);
5509
+ window.removeEventListener("pointermove", onMove, true);
3955
5510
  window.removeEventListener("pointerup", onUp, true);
3956
5511
  window.removeEventListener("pointercancel", onCancel, true);
3957
5512
  };
3958
- }, [toggleSidebar]);
5513
+ }, []);
3959
5514
  (0, react.useLayoutEffect)(() => {
3960
5515
  if (!compact) {
3961
5516
  setCollapsing(false);
@@ -3991,6 +5546,7 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
3991
5546
  archiveSession,
3992
5547
  deleteSession,
3993
5548
  forkSession,
5549
+ moveSession,
3994
5550
  renameSession,
3995
5551
  openPath,
3996
5552
  useSessions,
@@ -4003,6 +5559,7 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
4003
5559
  deleteSession,
4004
5560
  expandSidebar,
4005
5561
  forkSession,
5562
+ moveSession,
4006
5563
  openPath,
4007
5564
  openSession,
4008
5565
  renameSession,
@@ -4010,10 +5567,8 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
4010
5567
  useSessions,
4011
5568
  useWorkspaces
4012
5569
  ]);
4013
- const sidebarStyle = { "--dcu-sidebar-wide-width": `${slimedSidebarWidth(lastWideWidth.current, false, window.innerWidth)}px` };
4014
5570
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("aside", {
4015
5571
  className: `dcu-root${visualCompact ? " dcu-compact" : ""}${collapsing ? " dcu-collapsing" : ""}`,
4016
- style: sidebarStyle,
4017
5572
  "aria-label": t("sidebar.label"),
4018
5573
  children: [
4019
5574
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: stylesheet$3 }),
@@ -4060,46 +5615,73 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
4060
5615
  },
4061
5616
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MenuIcon, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconNewChatOutline16, { size: 16 }) }), t("sidebar.newTask")]
4062
5617
  }),
4063
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4064
- type: "button",
4065
- "aria-expanded": extensionsOpen,
4066
- onClick: () => {
4067
- setExtensionsOpen((open) => !open);
4068
- },
4069
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MenuIcon, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEnhanceOutline16, { size: 16 }) }), t("sidebar.extensions")]
4070
- }),
4071
- extensionsOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4072
- className: "dcu-extension-items",
4073
- children: [
4074
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4075
- type: "button",
4076
- onClick: () => {
4077
- selectExternalSection(t("sidebar.experts"));
4078
- },
4079
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MenuIcon, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconUserOutline16, { size: 16 }) }), t("sidebar.experts")]
4080
- }),
4081
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4082
- type: "button",
4083
- onClick: () => {
4084
- selectExternalSection(t("sidebar.skills"));
4085
- },
4086
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MenuIcon, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSkillOutline16, { size: 16 }) }), t("sidebar.skills")]
4087
- }),
4088
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4089
- type: "button",
4090
- onClick: () => {
4091
- selectPluginSection();
4092
- },
4093
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MenuIcon, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPersonalizationOutline16, { size: 16 }) }), t("sidebar.plugins")]
4094
- }),
4095
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4096
- type: "button",
4097
- onClick: () => {
4098
- selectSection(t("sidebar.connectors"));
4099
- },
4100
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MenuIcon, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconLinkOutline16, { size: 16 }) }), t("sidebar.connectors")]
5618
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5619
+ className: "dcu-extensions-group",
5620
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
5621
+ type: "button",
5622
+ className: "dcu-extensions-toggle",
5623
+ "aria-expanded": extensionsOpen,
5624
+ "aria-controls": "dcu-extension-items",
5625
+ onClick: () => {
5626
+ setExtensionsOpen((open) => {
5627
+ const next = !open;
5628
+ writeExtensionsOpen(next);
5629
+ return next;
5630
+ });
5631
+ },
5632
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MenuIcon, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
5633
+ className: "dcu-extension-leading",
5634
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEnhanceOutline16, {
5635
+ className: "dcu-extension-default-icon",
5636
+ size: 16
5637
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronRightOutline14, { className: "dcu-extension-state-arrow" })]
5638
+ }) }), t("sidebar.extensions")]
5639
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5640
+ id: "dcu-extension-items",
5641
+ className: "dcu-extension-panel",
5642
+ "data-open": extensionsOpen,
5643
+ "aria-hidden": !extensionsOpen,
5644
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5645
+ className: "dcu-extension-panel-inner",
5646
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5647
+ className: "dcu-extension-items",
5648
+ children: [
5649
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
5650
+ type: "button",
5651
+ tabIndex: extensionsOpen ? 0 : -1,
5652
+ onClick: () => {
5653
+ selectExternalSection(t("sidebar.experts"));
5654
+ },
5655
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MenuIcon, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconUserOutline16, { size: 16 }) }), t("sidebar.experts")]
5656
+ }),
5657
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
5658
+ type: "button",
5659
+ tabIndex: extensionsOpen ? 0 : -1,
5660
+ onClick: () => {
5661
+ selectExternalSection(t("sidebar.skills"));
5662
+ },
5663
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MenuIcon, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSkillOutline16, { size: 16 }) }), t("sidebar.skills")]
5664
+ }),
5665
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
5666
+ type: "button",
5667
+ tabIndex: extensionsOpen ? 0 : -1,
5668
+ onClick: () => {
5669
+ selectPluginSection();
5670
+ },
5671
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MenuIcon, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPersonalizationOutline16, { size: 16 }) }), t("sidebar.plugins")]
5672
+ }),
5673
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
5674
+ type: "button",
5675
+ tabIndex: extensionsOpen ? 0 : -1,
5676
+ onClick: () => {
5677
+ selectSection(t("sidebar.connectors"));
5678
+ },
5679
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MenuIcon, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconLinkOutline16, { size: 16 }) }), t("sidebar.connectors")]
5680
+ })
5681
+ ]
5682
+ })
4101
5683
  })
4102
- ]
5684
+ })]
4103
5685
  }),
4104
5686
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4105
5687
  type: "button",
@@ -4155,8 +5737,11 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
4155
5737
  archiveSession,
4156
5738
  deleteSession,
4157
5739
  forkSession,
5740
+ moveSession,
4158
5741
  renameSession,
4159
5742
  useSessions,
5743
+ useSessionPendingInteraction,
5744
+ useWorkspaces,
4160
5745
  t
4161
5746
  })
4162
5747
  }) : imTab === "schedule" && showSchedule ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
@@ -4166,8 +5751,10 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
4166
5751
  archiveSession,
4167
5752
  deleteSession,
4168
5753
  forkSession,
5754
+ moveSession,
4169
5755
  renameSession,
4170
5756
  useSessions,
5757
+ useSessionPendingInteraction,
4171
5758
  useWorkspaces,
4172
5759
  t,
4173
5760
  overviewContent: scheduleOverviewSlot,
@@ -4344,7 +5931,22 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
4344
5931
  const endpoint = "/api/michengai/codex-ui/dependencies";
4345
5932
  const stylesheet$2 = `
4346
5933
  .dcu-about{color:var(--dsw-alias-label-primary)}.dcu-about h2{margin:0;font-size:20px;line-height:28px}.dcu-about-intro{margin:6px 0 22px;color:var(--dsw-alias-label-secondary);font-size:13px;line-height:20px}.dcu-about h3{margin:24px 0 8px;font-size:14px;line-height:20px}.dcu-about-features{margin:0;padding-left:20px;color:var(--dsw-alias-label-secondary);font-size:13px;line-height:24px}.dcu-about-dependencies-heading{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-top:24px}.dcu-about-dependencies-heading h3{margin:0}.dcu-about-dependencies-heading+.dcu-about-intro{margin-bottom:14px}.dcu-about-dependencies{overflow:hidden;border:1px solid var(--dsw-alias-border-l2);border-radius:10px}.dcu-about-dependency{display:flex;align-items:center;gap:12px;min-height:64px;padding:12px;border-bottom:1px solid var(--dsw-alias-border-l2)}.dcu-about-dependency:last-child{border-bottom:0}.dcu-about-copy{min-width:0;flex:1}.dcu-about-name{font-size:13px;line-height:20px;font-weight:600}.dcu-about-package{overflow:hidden;margin-top:2px;color:var(--dsw-alias-label-tertiary);font-size:12px;text-overflow:ellipsis;white-space:nowrap}.dcu-about-status{display:flex;align-items:center;gap:5px;font-size:12px}.dcu-about-status[data-installed=true]{color:var(--dsw-alias-state-success-primary)}.dcu-about-status[data-installed=false]{color:var(--dsw-alias-state-error-primary)}.dcu-about-status[data-update=true]{color:var(--dsw-alias-state-warning-primary)}.dcu-about-install{display:inline-flex;align-items:center;justify-content:center;gap:5px;min-height:32px;border:1px solid var(--dsw-alias-border-l2);border-radius:7px;padding:6px 9px;background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:12px;cursor:pointer}.dcu-about-install:hover:not(:disabled){background:var(--dsw-specific-menu-item-hover)}.dcu-about-install:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:2px}.dcu-about-install:disabled{cursor:wait;opacity:.65}.dcu-about-update-all{min-width:92px}.dcu-about-message{margin:10px 0 0;border-radius:8px;padding:9px 10px;background:color-mix(in srgb,var(--dsw-alias-state-business-primary) 12%,transparent);color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}.dcu-about-message[data-error=true]{background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 12%,transparent);color:var(--dsw-alias-state-error-primary)}.dcu-about-progress{display:grid;grid-template-columns:auto 1fr auto;gap:8px 10px;align-items:center;margin-top:10px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:9px 10px}.dcu-about-progress code{min-width:0;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dcu-about-progress-pct{color:var(--dsw-alias-label-tertiary);font-size:12px;font-variant-numeric:tabular-nums}.dcu-about-progress-bar{grid-column:1/-1;height:4px;overflow:hidden;border-radius:99px;background:var(--dsw-alias-border-l2)}.dcu-about-progress-fill{height:100%;background:var(--dsw-alias-state-business-primary);transition:width .2s ease}.dcu-about-progress-fill[data-wave=true]{width:28%;animation:dcu-about-progress-wave 1.2s ease-in-out infinite}@keyframes dcu-about-progress-wave{0%{transform:translateX(-60%)}100%{transform:translateX(280%)}}
5934
+ .dcu-about-title-row{display:flex;align-items:center;gap:8px;min-width:0}.dcu-about-links{display:flex;align-items:center;gap:4px}.dcu-about-external-link{display:inline-flex;align-items:center;gap:5px;min-height:28px;padding:0 8px;color:var(--dsw-alias-label-secondary);background:transparent;border:1px solid var(--dsw-alias-border-l2);border-radius:7px;font-size:12px;font-weight:500;line-height:18px;text-decoration:none;white-space:nowrap}.dcu-about-external-link:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}.dcu-about-external-link:focus-visible{outline:2px solid var(--dsw-alias-state-success-primary);outline-offset:2px}.dcu-about-external-link svg{flex:none}@media(max-width:720px){.dcu-about-title-row{flex-wrap:wrap}}
4347
5935
  `;
5936
+ /** 宿主图标库不提供 GitHub 品牌标识,内联后可离线使用并继承当前文字颜色。 */
5937
+ function GithubMark16() {
5938
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
5939
+ viewBox: "0 0 16 16",
5940
+ width: 16,
5941
+ height: 16,
5942
+ "aria-hidden": "true",
5943
+ focusable: "false",
5944
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
5945
+ fill: "currentColor",
5946
+ d: "M8 0a8 8 0 0 0-2.53 15.59c.4.074.547-.173.547-.385 0-.19-.007-.693-.01-1.36-2.226.484-2.695-1.073-2.695-1.073-.364-.924-.89-1.17-.89-1.17-.726-.496.055-.486.055-.486.803.056 1.225.824 1.225.824.714 1.223 1.872.87 2.328.665.072-.517.28-.87.508-1.07-1.777-.202-3.645-.888-3.645-3.956 0-.874.31-1.588.823-2.148-.083-.202-.357-1.017.078-2.12 0 0 .672-.215 2.2.82A7.65 7.65 0 0 1 8 4.8c.68.003 1.365.092 2.004.27 1.527-1.035 2.197-.82 2.197-.82.437 1.103.162 1.918.08 2.12.513.56.822 1.274.822 2.148 0 3.076-1.872 3.752-3.654 3.95.288.248.544.735.544 1.482 0 1.07-.01 1.932-.01 2.195 0 .214.144.463.55.384A8.001 8.001 0 0 0 8 0Z"
5947
+ })
5948
+ });
5949
+ }
4348
5950
  function isDependencyStatus(value) {
4349
5951
  if (value === null || typeof value !== "object") return false;
4350
5952
  const status = value;
@@ -4359,11 +5961,6 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
4359
5961
  const currentPackage = progress.currentPackage;
4360
5962
  return typeof progress.active === "boolean" && typeof progress.target === "string" && typeof progress.seconds === "number" && Number.isInteger(progress.seconds) && progress.seconds >= 0 && typeof progress.lastLine === "string" && (phase === null || typeof phase === "string" && PROGRESS_PHASES.has(phase)) && typeof progress.done === "number" && Number.isInteger(progress.done) && progress.done >= 0 && (total === null || typeof total === "number" && Number.isInteger(total) && total >= 0) && (percent === null || typeof percent === "number" && Number.isFinite(percent) && percent >= 0 && percent <= 100) && (currentPackage === null || typeof currentPackage === "string");
4361
5963
  }
4362
- function installErrorText(error, t) {
4363
- const message = error instanceof Error ? error.message : "";
4364
- if (message.includes("没有进入当前 Profile") || message.includes("did not enter this profile")) return t("about.installUnchanged");
4365
- return message !== "" ? message : t("about.installFailed");
4366
- }
4367
5964
  function progressLabel(progress, t) {
4368
5965
  if (progress.phase !== null) {
4369
5966
  const current = progress.currentPackage === null || progress.currentPackage === "" ? "" : ` · ${progress.currentPackage}`;
@@ -4525,7 +6122,27 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
4525
6122
  "aria-label": t("about.nav"),
4526
6123
  children: [
4527
6124
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: stylesheet$2 }),
4528
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", { children: t("about.title") }),
6125
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6126
+ className: "dcu-about-title-row",
6127
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", { children: t("about.title") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6128
+ className: "dcu-about-links",
6129
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("a", {
6130
+ className: "dcu-about-external-link",
6131
+ href: "https://github.com/MichengAI/dsh-codex-ui",
6132
+ target: "_blank",
6133
+ rel: "noreferrer",
6134
+ "aria-label": t("about.viewProject"),
6135
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(GithubMark16, {}), t("about.viewProject")]
6136
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("a", {
6137
+ className: "dcu-about-external-link",
6138
+ href: "https://github.com/MichengAI/dsh-codex-ui/issues",
6139
+ target: "_blank",
6140
+ rel: "noreferrer",
6141
+ "aria-label": t("about.feedback"),
6142
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconListPenOutline16, {}), t("about.feedback")]
6143
+ })]
6144
+ })]
6145
+ }),
4529
6146
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4530
6147
  className: "dcu-about-intro",
4531
6148
  children: t("about.description")
@@ -4666,7 +6283,7 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
4666
6283
  }
4667
6284
  style.textContent = document.body.hasAttribute("data-ds-dark-theme") ? frameDarkTheme : frameLightTheme;
4668
6285
  }
4669
- function ConnectorMarket({ startPromptSession }) {
6286
+ function ConnectorMarket({ startPromptSession, t }) {
4670
6287
  const frameRef = (0, react.useRef)(null);
4671
6288
  (0, react.useEffect)(() => {
4672
6289
  const onMessage = (event) => {
@@ -4683,9 +6300,9 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
4683
6300
  }, window.location.origin);
4684
6301
  };
4685
6302
  startPromptSession(prompt).then(() => {
4686
- reply(true, "已带入新会话");
6303
+ reply(true, t("connectors.promptReady"));
4687
6304
  }, (error) => {
4688
- reply(false, error instanceof Error ? error.message : String(error));
6305
+ reply(false, userErrorText(error, t));
4689
6306
  });
4690
6307
  };
4691
6308
  const syncTheme = () => {
@@ -4701,12 +6318,12 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
4701
6318
  observer.disconnect();
4702
6319
  window.removeEventListener("message", onMessage);
4703
6320
  };
4704
- }, [startPromptSession]);
6321
+ }, [startPromptSession, t]);
4705
6322
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("iframe", {
4706
6323
  ref: frameRef,
4707
6324
  className: "dcu-connector-frame",
4708
6325
  src: MCP_CONNECTOR_UI,
4709
- title: "MCP连接器",
6326
+ title: t("connectors.title"),
4710
6327
  onLoad: () => {
4711
6328
  syncFrameTheme(frameRef.current);
4712
6329
  }
@@ -4798,7 +6415,10 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
4798
6415
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: stylesheet$1 }), marketAvailable === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4799
6416
  className: "dcu-connector-empty",
4800
6417
  children: t("connectors.loading")
4801
- }) : marketAvailable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConnectorMarket, { startPromptSession }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
6418
+ }) : marketAvailable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConnectorMarket, {
6419
+ startPromptSession,
6420
+ t
6421
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
4802
6422
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", { children: t("connectors.title") }),
4803
6423
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("connectors.description") }),
4804
6424
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NativeConnectorList, {
@@ -4842,6 +6462,7 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
4842
6462
  "channel.wecom": "企业微信",
4843
6463
  "channel.qq": "QQ",
4844
6464
  "channel.telegram": "Telegram",
6465
+ "channel.unknown": "频道",
4845
6466
  "schedule.empty": "还没有定时任务运行记录。",
4846
6467
  "schedule.groupActions": "{name} 的任务操作",
4847
6468
  "schedule.taskSettings": "任务设置",
@@ -4859,30 +6480,43 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
4859
6480
  "workspace.pinned": "置顶",
4860
6481
  "workspace.pinnedEmpty": "拖动项目到此处置顶",
4861
6482
  "workspace.projects": "项目",
6483
+ "workspace.ungrouped": "未分组",
6484
+ "workspace.createGroup": "新建分组",
6485
+ "workspace.createGroupDescription": "输入分组名称,项目不会自动移动。",
6486
+ "workspace.groupName": "分组名称",
6487
+ "workspace.moveToGroup": "移动到分组",
6488
+ "workspace.removeFromGroup": "移至未分组",
6489
+ "workspace.deleteGroup": "删除分组“{name}”",
6490
+ "workspace.deleteGroupAction": "删除分组",
6491
+ "workspace.deleteGroupDescription": "删除分组只会将其中项目移至未分组,不会删除项目或会话。",
6492
+ "workspace.manageGroups": "管理分组",
4862
6493
  "workspace.recent": "最近",
4863
6494
  "workspace.recentEmpty": "无聊天",
4864
6495
  "workspace.noChat": "没有聊天",
4865
- "workspace.empty": "暂无会话",
6496
+ "workspace.empty": "暂无项目",
4866
6497
  "workspace.newSession": "新建会话",
4867
- "workspace.rename": "编辑项目",
6498
+ "workspace.rename": "重命名项目",
4868
6499
  "workspace.pin": "置顶项目",
4869
6500
  "workspace.unpin": "取消置顶",
6501
+ "workspace.archive": "归档所有会话",
6502
+ "workspace.archiveTitle": "归档 {count} 个会话?",
6503
+ "workspace.archiveDescription": "这会归档“{name}”中的全部会话,项目和文件夹仍会保留。",
6504
+ "workspace.archiveConfirm": "全部归档",
6505
+ "workspace.archivePending": "正在归档会话…",
4870
6506
  "workspace.openPath": "在资源管理器中打开",
4871
- "workspace.delete": "删除项目",
6507
+ "workspace.delete": "移除项目",
4872
6508
  "workspace.deleteDescription": "将把“{name}”从项目列表中移除。文件夹和会话记录会保留。",
4873
- "workspace.deletePending": "正在删除项目…",
6509
+ "workspace.deletePending": "正在移除项目…",
4874
6510
  "workspace.actions": "{name} 的项目操作",
4875
6511
  "workspace.taskCount": "{count} 个任务",
4876
- "workspace.edit": "编辑项目",
6512
+ "workspace.taskSummary": "{count} 个任务 · {unreadCount} 条未读",
6513
+ "workspace.edit": "重命名项目",
4877
6514
  "sessions.close": "关闭",
4878
6515
  "sessions.cancel": "取消",
4879
6516
  "sessions.save": "保存",
4880
6517
  "sessions.failed": "操作失败:{message}",
4881
6518
  "sessions.unknown": "找不到该会话。",
4882
6519
  "sessions.actions": "{name} 的会话操作",
4883
- "sessions.pin": "置顶会话",
4884
- "sessions.unpin": "取消置顶",
4885
- "sessions.pinned": "已置顶",
4886
6520
  "sessions.markUnread": "标记为未读",
4887
6521
  "sessions.markRead": "标记为已读",
4888
6522
  "sessions.unread": "未读",
@@ -4891,17 +6525,33 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
4891
6525
  "sessions.planReview": "计划待审",
4892
6526
  "sessions.rename": "重命名会话",
4893
6527
  "sessions.renameDescription": "保持简短且易于识别",
6528
+ "sessions.renameInvalid": "会话名称无效,请修改后重试。",
6529
+ "sessions.renameFailed": "暂时无法重命名该会话,请稍后重试。",
4894
6530
  "sessions.archive": "归档会话",
6531
+ "sessions.archiveFailed": "暂时无法归档该会话,请稍后重试。",
4895
6532
  "sessions.delete": "删除会话",
4896
6533
  "sessions.deleteDescription": "将永久删除会话“{name}”及其全部记录,此操作不可恢复。",
4897
6534
  "sessions.deletePending": "正在删除会话…",
4898
6535
  "sessions.deleteUnavailable": "未安装或无法连接归档管理插件。",
6536
+ "sessions.deleteFailed": "暂时无法删除该会话,请稍后重试。",
4899
6537
  "sessions.fork": "在新会话中继续",
6538
+ "sessions.forkUnavailable": "当前会话暂时无法在新会话中继续,请等待本轮结束后重试。",
6539
+ "sessions.forkFailed": "暂时无法创建新会话,请稍后重试。",
4900
6540
  "sessions.openPath": "在资源管理器中打开",
6541
+ "sessions.moveWorkspace": "项目",
6542
+ "sessions.moveUnavailable": "会话移动服务暂不可用,请重启 DSH 后重试。",
6543
+ "sessions.moveNotFound": "会话或目标项目已不存在,请刷新后重试。",
6544
+ "sessions.moveSubagent": "子代理会话不能移动到其他项目。",
6545
+ "sessions.moveBusy": "该会话正在移动,请稍后重试。",
6546
+ "sessions.moveRollbackFailed": "会话移动失败,自动恢复未完整完成,请查看 DSH 服务端日志。",
6547
+ "sessions.moveFailed": "暂时无法移动该会话,原会话已保留。",
6548
+ "sessions.moveConfirmTitle": "将会话移至“{project}”?",
6549
+ "sessions.moveConfirmDescription": "“{name}”将移动到此项目,工作目录也会随之更改。",
6550
+ "sessions.moveConfirmAction": "移动",
6551
+ "sessions.movePending": "正在移动会话…",
4901
6552
  "sessions.copyPath": "复制工作目录",
4902
6553
  "sessions.copyTitle": "复制会话标题",
4903
6554
  "sessions.copyId": "复制会话 ID",
4904
- "sessions.copyLink": "复制会话链接",
4905
6555
  "connectors.title": "连接器",
4906
6556
  "connectors.description": "显示当前会话可用的 MCP 连接器及其工具,不显示地址、命令或凭证。",
4907
6557
  "connectors.openSession": "请先打开一个会话以读取连接器。",
@@ -4909,8 +6559,17 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
4909
6559
  "connectors.failed": "连接器目录暂不可用,请稍后重试。",
4910
6560
  "connectors.toolCount": "{count} 个工具",
4911
6561
  "connectors.empty": "当前会话没有可用的 MCP 连接器。",
4912
- "about.nav": "关于",
6562
+ "connectors.promptReady": "已带入新会话",
6563
+ "connectors.promptRequired": "Prompt 不能为空。",
6564
+ "connectors.workspacesLoading": "DSH 工作区数据尚未就绪,请稍后重试。",
6565
+ "connectors.workspaceRequired": "请先选择一个工作区,再使用示例 Prompt。",
6566
+ "connectors.workspaceUnavailable": "DSH 工作区服务尚未就绪,请稍后重试。",
6567
+ "connectors.conversationUnavailable": "DSH 对话服务尚未就绪,请稍后重试。",
6568
+ "connectors.sessionPending": "新会话尚未就绪,请稍后重试。",
6569
+ "about.nav": "Codex UI",
4913
6570
  "about.title": "Codex UI",
6571
+ "about.viewProject": "GitHub",
6572
+ "about.feedback": "问题反馈",
4914
6573
  "about.description": "为 DSH 提供 Codex 风格的侧栏、工作区会话管理、搜索与会话轮次导航。",
4915
6574
  "about.features": "基本功能",
4916
6575
  "about.feature.sidebar": "Codex 风格侧栏、全局会话搜索与设置入口",
@@ -4934,6 +6593,12 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
4934
6593
  "about.upToDate": "所有配套插件均已安装并为最新版本。",
4935
6594
  "about.installFailed": "依赖安装失败,请稍后重试。",
4936
6595
  "about.installUnchanged": "安装命令已结束,但插件没有进入当前 Profile。请重试。",
6596
+ "about.installExitDesktop": "请先完全退出 DSH Desktop,再重新打开后更新。",
6597
+ "about.installStoreMismatch": "插件目录与 pnpm 仓库不一致。请完全退出 DSH Desktop 后再更新。",
6598
+ "about.installBuildPolicy": "插件依赖的 pnpm 构建脚本策略尚未确认,请更新 Profile 配置后重试。",
6599
+ "about.installPnpmMissing": "当前环境找不到 pnpm,请安装 pnpm 并重启 DSH 后重试。",
6600
+ "about.installTimeout": "插件安装超时,请检查网络后重试。",
6601
+ "about.installServiceUnavailable": "DSH Desktop 依赖管理服务尚未就绪,请重启后重试。",
4937
6602
  "about.restartRequired": "正在热更新插件,窗口会自动刷新。",
4938
6603
  "about.restartManually": "安装完成。当前 Web 无法自动重启,请重启 DSH Web 后刷新页面。",
4939
6604
  "about.progressHint": "正在安装插件…",
@@ -4953,7 +6618,17 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
4953
6618
  "about.dependency.market": "插件市场",
4954
6619
  "turns.label": "当前会话轮次导航",
4955
6620
  "turns.untitled": "未命名提问",
4956
- "turns.jump": "跳转到第 {index} 轮:{summary}"
6621
+ "turns.jump": "跳转到第 {index} 轮:{summary}",
6622
+ "time.justNow": "刚刚",
6623
+ "time.minutes": "{count}分",
6624
+ "time.hours": "{count}小时",
6625
+ "time.days": "{count}天",
6626
+ "errors.generic": "操作未完成,请重试。",
6627
+ "errors.groupInvalid": "分组信息无效。",
6628
+ "errors.workspaceInvalid": "项目标识无效。",
6629
+ "errors.groupMissing": "目标分组不存在。",
6630
+ "errors.orderAnchorMissing": "排序位置已失效,请重试。",
6631
+ "meta.locale": "zh-CN"
4957
6632
  };
4958
6633
  const en = {
4959
6634
  "sidebar.label": "DSH navigation",
@@ -4985,6 +6660,7 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
4985
6660
  "channel.wecom": "WeCom",
4986
6661
  "channel.qq": "QQ",
4987
6662
  "channel.telegram": "Telegram",
6663
+ "channel.unknown": "Channel",
4988
6664
  "schedule.empty": "No scheduled-task runs yet.",
4989
6665
  "schedule.groupActions": "Task actions for {name}",
4990
6666
  "schedule.taskSettings": "Task settings",
@@ -5000,32 +6676,45 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
5000
6676
  "search.actions": "Quick actions",
5001
6677
  "workspace.label": "Workspace conversations",
5002
6678
  "workspace.projects": "Projects",
6679
+ "workspace.ungrouped": "Ungrouped",
6680
+ "workspace.createGroup": "Create group",
6681
+ "workspace.createGroupDescription": "Enter a group name. Projects will not move automatically.",
6682
+ "workspace.groupName": "Group name",
6683
+ "workspace.moveToGroup": "Move to group",
6684
+ "workspace.removeFromGroup": "Move to ungrouped",
6685
+ "workspace.deleteGroup": "Delete group {name}",
6686
+ "workspace.deleteGroupAction": "Delete group",
6687
+ "workspace.deleteGroupDescription": "Deleting this group moves its projects to Ungrouped. It does not delete projects or conversations.",
6688
+ "workspace.manageGroups": "Manage groups",
5003
6689
  "workspace.recent": "Recents",
5004
6690
  "workspace.recentEmpty": "No chats",
5005
6691
  "workspace.noChat": "No chats",
5006
- "workspace.empty": "No conversations",
6692
+ "workspace.empty": "No projects",
5007
6693
  "workspace.newSession": "New conversation",
5008
- "workspace.rename": "Edit project",
6694
+ "workspace.rename": "Rename project",
5009
6695
  "workspace.pin": "Pin project",
5010
6696
  "workspace.unpin": "Unpin",
6697
+ "workspace.archive": "Archive all conversations",
6698
+ "workspace.archiveTitle": "Archive {count} conversations?",
6699
+ "workspace.archiveDescription": "This archives every conversation in “{name}”. The project and folder remain available.",
6700
+ "workspace.archiveConfirm": "Archive all",
6701
+ "workspace.archivePending": "Archiving conversations…",
5011
6702
  "workspace.pinned": "Pinned",
5012
6703
  "workspace.pinnedEmpty": "Drag a project here to pin it",
5013
6704
  "workspace.openPath": "Open in file explorer",
5014
- "workspace.delete": "Delete project",
6705
+ "workspace.delete": "Remove project",
5015
6706
  "workspace.deleteDescription": "This removes “{name}” from the project list. The folder and conversation records remain.",
5016
- "workspace.deletePending": "Deleting project…",
6707
+ "workspace.deletePending": "Removing project…",
5017
6708
  "workspace.actions": "Project actions for {name}",
5018
6709
  "workspace.taskCount": "{count} tasks",
5019
- "workspace.edit": "Edit project",
6710
+ "workspace.taskSummary": "{count} tasks · {unreadCount} unread",
6711
+ "workspace.edit": "Rename project",
5020
6712
  "sessions.close": "Close",
5021
6713
  "sessions.cancel": "Cancel",
5022
6714
  "sessions.save": "Save",
5023
6715
  "sessions.failed": "Action failed: {message}",
5024
6716
  "sessions.unknown": "Conversation not found.",
5025
6717
  "sessions.actions": "Conversation actions for {name}",
5026
- "sessions.pin": "Pin conversation",
5027
- "sessions.unpin": "Unpin",
5028
- "sessions.pinned": "Pinned",
5029
6718
  "sessions.markUnread": "Mark as unread",
5030
6719
  "sessions.markRead": "Mark as read",
5031
6720
  "sessions.unread": "Unread",
@@ -5034,17 +6723,33 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
5034
6723
  "sessions.planReview": "Plan awaiting review",
5035
6724
  "sessions.rename": "Rename conversation",
5036
6725
  "sessions.renameDescription": "Keep it short and easy to recognize.",
6726
+ "sessions.renameInvalid": "The conversation name is invalid. Change it and try again.",
6727
+ "sessions.renameFailed": "The conversation could not be renamed. Try again later.",
5037
6728
  "sessions.archive": "Archive conversation",
6729
+ "sessions.archiveFailed": "The conversation could not be archived. Try again later.",
5038
6730
  "sessions.delete": "Delete conversation",
5039
6731
  "sessions.deleteDescription": "This permanently deletes “{name}” and all of its records. This cannot be undone.",
5040
6732
  "sessions.deletePending": "Deleting conversation…",
5041
6733
  "sessions.deleteUnavailable": "The archive manager plugin is not installed or unavailable.",
6734
+ "sessions.deleteFailed": "The conversation could not be deleted. Try again later.",
5042
6735
  "sessions.fork": "Continue in new conversation",
6736
+ "sessions.forkUnavailable": "This conversation cannot be continued in a new conversation right now. Try again after the current turn finishes.",
6737
+ "sessions.forkFailed": "A new conversation could not be created. Try again later.",
5043
6738
  "sessions.openPath": "Open in file explorer",
6739
+ "sessions.moveWorkspace": "Project",
6740
+ "sessions.moveUnavailable": "Conversation moving is unavailable. Restart DSH and try again.",
6741
+ "sessions.moveNotFound": "The conversation or target project no longer exists. Refresh and try again.",
6742
+ "sessions.moveSubagent": "Subagent conversations cannot be moved to another project.",
6743
+ "sessions.moveBusy": "This conversation is already being moved. Try again shortly.",
6744
+ "sessions.moveRollbackFailed": "The conversation could not be moved, and automatic recovery did not complete. Check the DSH server logs.",
6745
+ "sessions.moveFailed": "The conversation could not be moved. The original conversation was preserved.",
6746
+ "sessions.moveConfirmTitle": "Move conversation to “{project}”?",
6747
+ "sessions.moveConfirmDescription": "“{name}” will move to this project, and its working directory will change with it.",
6748
+ "sessions.moveConfirmAction": "Move",
6749
+ "sessions.movePending": "Moving conversation…",
5044
6750
  "sessions.copyPath": "Copy working directory",
5045
6751
  "sessions.copyTitle": "Copy conversation title",
5046
6752
  "sessions.copyId": "Copy conversation ID",
5047
- "sessions.copyLink": "Copy conversation link",
5048
6753
  "connectors.title": "Connectors",
5049
6754
  "connectors.description": "Shows MCP connectors and tools available to this session without exposing addresses, commands, or credentials.",
5050
6755
  "connectors.openSession": "Open a conversation to load connectors.",
@@ -5052,8 +6757,17 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
5052
6757
  "connectors.failed": "Connectors are unavailable. Try again shortly.",
5053
6758
  "connectors.toolCount": "{count} tools",
5054
6759
  "connectors.empty": "This conversation has no available MCP connectors.",
5055
- "about.nav": "About",
6760
+ "connectors.promptReady": "Added to a new conversation",
6761
+ "connectors.promptRequired": "The prompt cannot be empty.",
6762
+ "connectors.workspacesLoading": "DSH workspace data is still loading. Try again shortly.",
6763
+ "connectors.workspaceRequired": "Select a workspace before using an example prompt.",
6764
+ "connectors.workspaceUnavailable": "The DSH workspace service is not ready. Try again shortly.",
6765
+ "connectors.conversationUnavailable": "The DSH conversation service is not ready. Try again shortly.",
6766
+ "connectors.sessionPending": "The new conversation is not ready yet. Try again shortly.",
6767
+ "about.nav": "Codex UI",
5056
6768
  "about.title": "Codex UI",
6769
+ "about.viewProject": "GitHub",
6770
+ "about.feedback": "Issues",
5057
6771
  "about.description": "Adds a Codex-style sidebar, workspace conversation management, search, and turn navigation to DSH.",
5058
6772
  "about.features": "Core features",
5059
6773
  "about.feature.sidebar": "Codex-style sidebar, global conversation search, and settings entry points",
@@ -5077,6 +6791,12 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
5077
6791
  "about.upToDate": "All companion plugins are installed and up to date.",
5078
6792
  "about.installFailed": "Dependency installation failed. Try again later.",
5079
6793
  "about.installUnchanged": "The install command finished, but the plugin did not enter this profile. Try again.",
6794
+ "about.installExitDesktop": "Quit DSH Desktop completely, reopen it, and try the update again.",
6795
+ "about.installStoreMismatch": "The plugin directory and pnpm store do not match. Quit DSH Desktop completely and try again.",
6796
+ "about.installBuildPolicy": "The pnpm build-script policy for this plugin is not configured. Update the Profile configuration and try again.",
6797
+ "about.installPnpmMissing": "pnpm is not available. Install pnpm, restart DSH, and try again.",
6798
+ "about.installTimeout": "Plugin installation timed out. Check the network and try again.",
6799
+ "about.installServiceUnavailable": "The DSH Desktop dependency service is not ready. Restart DSH Desktop and try again.",
5080
6800
  "about.restartRequired": "Updating plugins now. The window will refresh automatically.",
5081
6801
  "about.restartManually": "Installation complete. This Web host cannot restart itself; restart DSH Web, then refresh the page.",
5082
6802
  "about.progressHint": "Installing plugin…",
@@ -5096,7 +6816,17 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
5096
6816
  "about.dependency.market": "Plugin Market",
5097
6817
  "turns.label": "Conversation turn navigation",
5098
6818
  "turns.untitled": "Untitled prompt",
5099
- "turns.jump": "Jump to turn {index}: {summary}"
6819
+ "turns.jump": "Jump to turn {index}: {summary}",
6820
+ "time.justNow": "Just now",
6821
+ "time.minutes": "{count}m",
6822
+ "time.hours": "{count}h",
6823
+ "time.days": "{count}d",
6824
+ "errors.generic": "The action could not be completed. Try again.",
6825
+ "errors.groupInvalid": "The group information is invalid.",
6826
+ "errors.workspaceInvalid": "The project identifier is invalid.",
6827
+ "errors.groupMissing": "The target group no longer exists.",
6828
+ "errors.orderAnchorMissing": "The target position is no longer available. Try again.",
6829
+ "meta.locale": "en-US"
5100
6830
  };
5101
6831
  //#endregion
5102
6832
  //#region src/client/host-open-path.ts
@@ -5141,7 +6871,7 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
5141
6871
  [["定时任务", "Scheduled tasks"], "schedule"],
5142
6872
  [["IM助理", "IM Assistant"], "assistant"],
5143
6873
  [["已归档", "Archived"], "archive"],
5144
- [["关于", "About"], "about"]
6874
+ [["Codex UI"], "about"]
5145
6875
  ];
5146
6876
  /** 按设置导航可见文案解析侧栏同款图标;插件市场复用插件图标。 */
5147
6877
  function settingsNavIconId(label) {
@@ -5504,8 +7234,8 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
5504
7234
  }
5505
7235
  function turnLinks(snapshot, fallback) {
5506
7236
  const links = [];
5507
- for (const key of snapshot.chat.order) {
5508
- const node = snapshot.chat.nodes.get(key);
7237
+ for (const key of snapshot.order) {
7238
+ const node = snapshot.nodes.get(key);
5509
7239
  if (node?.kind !== "user") continue;
5510
7240
  links.push({
5511
7241
  key: node.key,
@@ -5517,14 +7247,21 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
5517
7247
  function equalTurns(left, right) {
5518
7248
  return left.length === right.length && left.every((turn, index) => turn.key === right[index]?.key && turn.summary === right[index]?.summary);
5519
7249
  }
7250
+ function legacyTurnLinks(useSession, fallback) {
7251
+ return useSession((snapshot) => {
7252
+ const chat = "chat" in snapshot ? snapshot.chat : void 0;
7253
+ return chat === void 0 ? [] : turnLinks(chat, fallback);
7254
+ }, equalTurns);
7255
+ }
5520
7256
  function railLeftFromSidebar() {
5521
7257
  const sidebar = document.querySelector(".dcu-root");
5522
7258
  if (sidebar === null) return 288;
5523
7259
  return Math.round(sidebar.getBoundingClientRect().right) + 16;
5524
7260
  }
5525
7261
  /** 当前会话的轮次导航;只读取原生聊天锚点并滚动,不改写会话数据或消息视图。 */
5526
- function TurnNavigator({ useSession, t }) {
5527
- const turns = useSession((snapshot) => turnLinks(snapshot, t("turns.untitled")), equalTurns);
7262
+ function TurnNavigator({ useChat, useSession, t }) {
7263
+ const fallback = t("turns.untitled");
7264
+ const turns = useChat === void 0 ? legacyTurnLinks(useSession, fallback) : useChat((snapshot) => turnLinks(snapshot, fallback), equalTurns);
5528
7265
  const [current, setCurrent] = (0, react.useState)(turns[0]?.key ?? null);
5529
7266
  const [hoverAt, setHoverAt] = (0, react.useState)(null);
5530
7267
  const [railLeft, setRailLeft] = (0, react.useState)(288);
@@ -5684,8 +7421,13 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
5684
7421
  function hasDeleteSession(value) {
5685
7422
  return value !== null && typeof value === "object" && "deleteSession" in value && typeof value.deleteSession === "function";
5686
7423
  }
5687
- function hasStartSession(value) {
5688
- return value !== null && typeof value === "object" && "startSession" in value && typeof value.startSession === "function";
7424
+ async function runHostAction(action, execute) {
7425
+ try {
7426
+ return await execute();
7427
+ } catch (reason) {
7428
+ if (reason instanceof UserFacingError || reason instanceof HostActionError) throw reason;
7429
+ throw new HostActionError(action, reason);
7430
+ }
5689
7431
  }
5690
7432
  /** Archive Manager replaces the official ui-workspace row with this optional service. */
5691
7433
  function startWorkspaceSession(ctx, workspaceId) {
@@ -5694,7 +7436,11 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
5694
7436
  uiWorkspace.startSession(workspaceId);
5695
7437
  return;
5696
7438
  }
5697
- ctx.workspaces.startSession(workspaceId);
7439
+ if (hasStartSession(ctx.workspaces)) {
7440
+ ctx.workspaces.startSession(workspaceId);
7441
+ return;
7442
+ }
7443
+ console.warn("DSH 工作空间服务尚未就绪,无法新建会话。");
5698
7444
  }
5699
7445
  /** 替换 DSH 的官方 sidebar 插槽,不修改 DSH 源码或会话数据。 */
5700
7446
  function apply(ctx) {
@@ -5747,9 +7493,10 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
5747
7493
  toggleSidebar: () => {
5748
7494
  ctx.layout.toggleSidebar();
5749
7495
  },
5750
- archiveSession: (sessionId) => ctx.workspaces.archiveSession(sessionId),
7496
+ archiveSession,
5751
7497
  deleteSession,
5752
7498
  forkSession,
7499
+ moveSession,
5753
7500
  renameSession,
5754
7501
  openPath,
5755
7502
  companionSlots
@@ -5762,36 +7509,65 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
5762
7509
  locale: NS
5763
7510
  }, TurnNavigator));
5764
7511
  const forkSession = async (sessionId) => {
5765
- const childId = await ctx.sessions.fork({
5766
- sessionId,
5767
- increaseTitle: true
7512
+ await runHostAction("fork", async () => {
7513
+ const childId = await ctx.sessions.fork({
7514
+ sessionId,
7515
+ increaseTitle: true
7516
+ });
7517
+ ctx.sessions.open(childId);
5768
7518
  });
5769
- ctx.sessions.open(childId);
5770
7519
  };
5771
7520
  const renameSession = async (sessionId, title) => {
5772
7521
  const session = ctx.sessions.binding(sessionId)?.session;
5773
- if (session === void 0) throw new Error(t("sessions.unknown"));
5774
- const result = await session.rename(title);
5775
- if (!result.ok) throw new Error(result.error.message);
7522
+ if (session === void 0) throw new UserFacingError(t("sessions.unknown"));
7523
+ await runHostAction("rename", async () => {
7524
+ const result = await session.rename(title);
7525
+ if (!result.ok) throw result.error;
7526
+ });
5776
7527
  };
5777
7528
  const deleteSession = async (sessionId) => {
5778
7529
  const registry = ctx.get("remote.workspaceRegistry");
5779
- if (!hasDeleteSession(registry)) throw new Error(t("sessions.deleteUnavailable"));
5780
- const result = await registry.deleteSession(sessionId);
5781
- if (!result.ok) throw new Error(result.error?.message ?? t("sessions.deleteUnavailable"));
7530
+ if (!hasDeleteSession(registry)) throw new UserFacingError(t("sessions.deleteUnavailable"));
7531
+ await runHostAction("delete", async () => {
7532
+ const result = await registry.deleteSession(sessionId);
7533
+ if (!result.ok) throw result.error === void 0 ? new UserFacingError(t("sessions.deleteUnavailable")) : result.error;
7534
+ });
7535
+ };
7536
+ const archiveSession = (sessionId) => runHostAction("archive", () => ctx.workspaces.archiveSession(sessionId));
7537
+ const moveSession = async (sessionId, targetWorkspaceId) => {
7538
+ try {
7539
+ await requestSessionMove(sessionId, targetWorkspaceId);
7540
+ } catch (error) {
7541
+ if (!(error instanceof SessionMoveRequestError)) throw error;
7542
+ throw new UserFacingError(t(sessionMoveErrorKey(error.code)));
7543
+ }
7544
+ finishSessionMove({
7545
+ sessionId,
7546
+ currentUrl: window.location.href,
7547
+ navigate: (url) => {
7548
+ window.location.replace(url);
7549
+ }
7550
+ });
5782
7551
  };
5783
7552
  const startConnectorPromptSession = async (promptText) => {
5784
7553
  const prompt = promptText.trim();
5785
- if (prompt === "") throw new Error("Prompt 不能为空");
7554
+ if (prompt === "") throw new UserFacingError(t("connectors.promptRequired"));
5786
7555
  const workspaces = ctx.workspaces.list.getSnapshot();
5787
- const currentSessionId = ctx.sessions.list.getSnapshot().current;
5788
- const targetWorkspaceId = (currentSessionId === void 0 ? void 0 : workspaces.items.find((workspace) => workspace.sessionIds.includes(currentSessionId))?.workspaceId) ?? workspaces.recentWorkspaceId;
5789
- if (targetWorkspaceId === void 0) throw new Error("请先选择一个工作空间,再使用示例 Prompt");
5790
- const sessionId = await ctx.workspaces.connectWorkspace(targetWorkspaceId);
7556
+ const sessionSnapshot = ctx.sessions.list.getSnapshot();
7557
+ const currentSessionId = sessionSnapshot.current;
7558
+ const currentWorkspaceId = currentSessionId === void 0 ? void 0 : workspaces.items.find((workspace) => workspace.sessionIds.includes(currentSessionId))?.workspaceId;
7559
+ const baselinesReady = workspaceBaselinesReady(workspaces, sessionSnapshot);
7560
+ const targetWorkspaceId = currentWorkspaceId ?? (baselinesReady ? recentWorkspaceId(workspaces.items, sessionSnapshot.byId) : void 0);
7561
+ if (targetWorkspaceId === void 0 && !baselinesReady) throw new UserFacingError(t("connectors.workspacesLoading"));
7562
+ if (targetWorkspaceId === void 0) throw new UserFacingError(t("connectors.workspaceRequired"));
7563
+ const uiWorkspace = ctx.get("uiWorkspace");
7564
+ const workspaceNavigation = hasConnectWorkspace(uiWorkspace) ? uiWorkspace : hasConnectWorkspace(ctx.workspaces) ? ctx.workspaces : void 0;
7565
+ if (workspaceNavigation === void 0) throw new UserFacingError(t("connectors.workspaceUnavailable"));
7566
+ const sessionId = await workspaceNavigation.connectWorkspace(targetWorkspaceId);
5791
7567
  const conversation = ctx.get("conversation");
5792
- if (conversation === void 0) throw new Error("DSH 对话服务尚未就绪,请稍后重试");
7568
+ if (conversation === void 0) throw new UserFacingError(t("connectors.conversationUnavailable"));
5793
7569
  const binding = ctx.sessions.binding(sessionId);
5794
- if (binding === void 0) throw new Error("新会话尚未就绪,请稍后重试");
7570
+ if (binding === void 0) throw new UserFacingError(t("connectors.sessionPending"));
5795
7571
  conversation.input.for(binding.ctx).setDraft(prompt);
5796
7572
  ctx.sessions.open(sessionId);
5797
7573
  };
@@ -5800,10 +7576,11 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
5800
7576
  priority: -1,
5801
7577
  locale: NS,
5802
7578
  inject: () => ({
5803
- archiveSession: (sessionId) => ctx.workspaces.archiveSession(sessionId),
7579
+ archiveSession,
5804
7580
  deleteSession,
5805
7581
  deleteWorkspace: (workspaceId) => ctx.workspaces.delete(workspaceId),
5806
7582
  forkSession,
7583
+ moveSession,
5807
7584
  openPath,
5808
7585
  openSession: (sessionId) => {
5809
7586
  ctx.sessions.open(sessionId);