@michengai/dsh-codex-ui 0.2.101 → 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",
@@ -667,13 +840,151 @@ window.__ModuleLoader__.load({
667
840
  default: return;
668
841
  }
669
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
+ }
670
848
  function pendingInteractionForSession(sessionId, pendingInteractions, summaryKind) {
671
849
  return visiblePendingKind(summaryKind) ?? visiblePendingKind(pendingInteractions.get(sessionId)?.kind);
672
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
+ }
673
983
  //#endregion
674
984
  //#region src/client/pinned-workspaces.ts
675
- /** 浏览器本地持久化键;置顶只影响本插件中的工作区分区。 */
985
+ /** 浏览器本地持久化键;用于工作区偏好的首帧恢复与 Host 故障兜底。 */
676
986
  const PINNED_WORKSPACES_STORAGE_KEY = "dsh-codex-ui.pinned-workspace-ids";
987
+ const WORKSPACE_GROUPS_STORAGE_KEY = "dsh-codex-ui.workspace-groups.v1";
677
988
  const WORKSPACE_PREFERENCES_ENDPOINT = "/api/michengai/codex-ui/preferences";
678
989
  /** 清理无效或重复的工作区标识。 */
679
990
  function normalizePinnedWorkspaceIds(ids) {
@@ -685,23 +996,29 @@ window.__ModuleLoader__.load({
685
996
  const next = ids.filter((id) => valid.has(id));
686
997
  return next.length === ids.length ? [...ids] : next;
687
998
  }
688
- /** Host 数据优先;首次升级没有 Host 文件时迁移旧 origin 的 localStorage;读取期间的用户操作永远优先。 */
689
- function resolvePinnedWorkspaceHydration(localIds, host, dirtyIds) {
690
- if (dirtyIds !== void 0) return {
691
- 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,
692
1004
  writeHost: true
693
1005
  };
694
- if (host.exists) return {
695
- ids: normalizePinnedWorkspaceIds(host.pinnedWorkspaceIds),
696
- writeHost: false
697
- };
698
- 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);
699
1015
  return {
700
- ids,
701
- writeHost: ids.length > 0
1016
+ pinnedWorkspaceIds,
1017
+ workspaceGroups: local.workspaceGroups,
1018
+ writeHost: pinnedWorkspaceIds.length > 0 || local.workspaceGroups.length > 0
702
1019
  };
703
1020
  }
704
- async function readHostPinnedWorkspaceIds(fetcher = fetch) {
1021
+ async function readHostWorkspacePreferences(fetcher = fetch) {
705
1022
  const response = await fetcher(WORKSPACE_PREFERENCES_ENDPOINT, {
706
1023
  method: "GET",
707
1024
  cache: "no-store",
@@ -711,22 +1028,35 @@ window.__ModuleLoader__.load({
711
1028
  const payload = await response.json();
712
1029
  if (payload === null || typeof payload !== "object") throw new Error("置顶偏好响应格式无效。");
713
1030
  const record = payload;
714
- 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("置顶偏好响应格式无效。");
715
1034
  return {
716
1035
  exists: record.exists,
717
- pinnedWorkspaceIds: normalizePinnedWorkspaceIds(record.pinnedWorkspaceIds)
1036
+ pinnedWorkspaceIds: normalizePinnedWorkspaceIds(record.pinnedWorkspaceIds),
1037
+ workspaceGroups,
1038
+ workspaceGroupsSupported
718
1039
  };
719
1040
  }
720
- 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("工作区分组数据无效。");
721
1044
  const response = await fetcher(WORKSPACE_PREFERENCES_ENDPOINT, {
722
1045
  method: "PUT",
723
1046
  cache: "no-store",
724
1047
  headers: { "content-type": "application/json" },
725
- body: JSON.stringify({ pinnedWorkspaceIds: normalizePinnedWorkspaceIds(ids) }),
1048
+ body: JSON.stringify({
1049
+ pinnedWorkspaceIds: normalizePinnedWorkspaceIds(pinnedWorkspaceIds),
1050
+ workspaceGroups: groups
1051
+ }),
726
1052
  signal: AbortSignal.timeout(5e3)
727
1053
  });
728
1054
  if (!response.ok) throw new Error(`保存置顶偏好失败:HTTP ${response.status}`);
729
1055
  }
1056
+ /** 兼容旧调用方;新代码应同时写入分组。 */
1057
+ async function readHostPinnedWorkspaceIds(fetcher = fetch) {
1058
+ return readHostWorkspacePreferences(fetcher);
1059
+ }
730
1060
  /** 在置顶列表中切换一个工作区。 */
731
1061
  /** 把工作区插入置顶列表的指定位置;省略锚点时追加到末尾。 */
732
1062
  function insertPinnedWorkspace(ids, id, beforeId) {
@@ -757,6 +1087,80 @@ window.__ModuleLoader__.load({
757
1087
  storage.setItem(PINNED_WORKSPACES_STORAGE_KEY, JSON.stringify(normalizePinnedWorkspaceIds(ids)));
758
1088
  } catch {}
759
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
+ }
760
1164
  //#endregion
761
1165
  //#region src/client/hover-tip.ts
762
1166
  /** 把悬停卡片限制在视口内,避免贴边裁切。 */
@@ -775,12 +1179,12 @@ window.__ModuleLoader__.load({
775
1179
  };
776
1180
  }
777
1181
  /** Codex 会话卡片右上角的紧凑相对时间。 */
778
- function formatHoverTime(updatedAt, now = Date.now()) {
1182
+ function formatHoverTime(updatedAt, t, now = Date.now()) {
779
1183
  const seconds = Math.max(0, Math.round((now - updatedAt) / 1e3));
780
- if (seconds < 60) return "刚刚";
781
- if (seconds < 3600) return `${Math.floor(seconds / 60)}分`;
782
- if (seconds < 86400) return `${Math.floor(seconds / 3600)}小时`;
783
- 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) });
784
1188
  }
785
1189
  //#endregion
786
1190
  //#region src/client/conversation-bubbles.ts
@@ -1023,6 +1427,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1023
1427
  //#endregion
1024
1428
  //#region src/client/hover-shell.tsx
1025
1429
  const HOVER_TIP_SHOW_DELAY_MS = 1e3;
1430
+ const WORKSPACE_HOVER_CARD_ESTIMATED_HEIGHT = 152;
1026
1431
  const HoverDispatchContext = (0, react.createContext)({
1027
1432
  showTip: () => {},
1028
1433
  hideTip: () => {},
@@ -1043,7 +1448,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1043
1448
  tipRef.current = hoverTip;
1044
1449
  const place = (tip) => ({
1045
1450
  ...tip,
1046
- ...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)
1047
1452
  });
1048
1453
  const dispatch = (0, react.useMemo)(() => ({
1049
1454
  showTip: (tip, options) => {
@@ -1121,7 +1526,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1121
1526
  //#endregion
1122
1527
  //#region src/client/workspace-hover-card.tsx
1123
1528
  /** 只订阅悬停值,避免工作区树随鼠标移动整树重绘。 */
1124
- function WorkspaceHoverCard({ t, onEditWorkspace }) {
1529
+ function WorkspaceHoverCard({ t, onEditWorkspace, onToggleWorkspacePin }) {
1125
1530
  const hoverTip = useHoverValue();
1126
1531
  const { keepTip, hideTip, dismissTip } = useHoverDispatch();
1127
1532
  (0, react.useEffect)(() => {
@@ -1138,8 +1543,14 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1138
1543
  };
1139
1544
  }, [hoverTip, dismissTip]);
1140
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 });
1141
1552
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1142
- className: "dcu-wb-tip",
1553
+ className: `dcu-wb-tip${workspace ? " dcu-wb-tip-workspace" : ""}`,
1143
1554
  style: {
1144
1555
  left: hoverTip.left,
1145
1556
  top: hoverTip.top
@@ -1150,59 +1561,69 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1150
1561
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1151
1562
  className: "dcu-wb-tip-title",
1152
1563
  children: [
1153
- hoverTip.kind === "workspace" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1564
+ workspace && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1154
1565
  className: "dcu-wb-folder",
1155
1566
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderClose16, { size: 16 })
1156
1567
  }),
1157
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: hoverTip.title }),
1158
- 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", {
1159
1582
  className: "dcu-wb-tip-time",
1160
1583
  children: hoverTip.time
1161
1584
  })
1162
1585
  ]
1163
1586
  }),
1164
- 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", {
1165
1588
  className: "dcu-wb-tip-meta",
1166
- 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 })]
1167
1594
  }),
1168
- hoverTip.kind === "workspace" && hoverTip.path !== void 0 && hoverTip.path !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1169
- 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",
1170
1597
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1171
1598
  className: "dcu-wb-folder",
1172
1599
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderClose16, { size: 16 })
1173
- }), /* @__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
+ })]
1174
1604
  }),
1175
- 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", {
1176
1606
  className: "dcu-wb-tip-row",
1177
1607
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1178
1608
  className: "dcu-wb-folder",
1179
1609
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderClose16, { size: 16 })
1180
1610
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: hoverTip.project })]
1181
1611
  }),
1182
- 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", {
1183
1613
  className: "dcu-wb-tip-row",
1184
1614
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1185
1615
  className: "dcu-wb-folder",
1186
1616
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconBranchOutline16, { size: 16 })
1187
1617
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: hoverTip.branch })]
1188
1618
  }),
1189
- 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", {
1190
1620
  type: "button",
1191
1621
  className: "dcu-wb-tip-edit",
1192
1622
  onClick: () => {
1193
- onEditWorkspace(hoverTip.id ?? "", hoverTip.title);
1623
+ onEditWorkspace(workspaceId, hoverTip.title);
1194
1624
  dismissTip();
1195
1625
  },
1196
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
1197
- viewBox: "0 0 16 16",
1198
- width: 16,
1199
- height: 16,
1200
- "aria-hidden": "true",
1201
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1202
- fill: "currentColor",
1203
- 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"
1204
- })
1205
- }), t("workspace.edit")]
1626
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSettingsOutline16, { size: 16 }), t("workspace.edit")]
1206
1627
  })] })
1207
1628
  ]
1208
1629
  });
@@ -1218,7 +1639,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1218
1639
  return name !== void 0 && name !== "" ? name : title.trim();
1219
1640
  }
1220
1641
  /** 把定时运行会话按任务名收成和任务树一样的分组。 */
1221
- function groupScheduleSessions(items) {
1642
+ function groupScheduleSessions(items, locale = "zh-CN") {
1222
1643
  const groups = /* @__PURE__ */ new Map();
1223
1644
  for (const item of items) {
1224
1645
  if (!isScheduleSession(item.id, item.title)) continue;
@@ -1234,7 +1655,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1234
1655
  return [...groups.values()].map((group) => ({
1235
1656
  ...group,
1236
1657
  sessions: [...group.sessions].sort((left, right) => (right.updatedAt ?? 0) - (left.updatedAt ?? 0))
1237
- })).sort((left, right) => left.label.localeCompare(right.label, "zh-CN", {
1658
+ })).sort((left, right) => left.label.localeCompare(right.label, locale, {
1238
1659
  numeric: true,
1239
1660
  sensitivity: "base"
1240
1661
  }));
@@ -1303,10 +1724,39 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1303
1724
  return item === void 0 ? [] : [item];
1304
1725
  });
1305
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
+ }
1306
1754
  const SESSION_DRAG_TYPE = "application/x-dcu-session";
1307
1755
  const WORKSPACE_DRAG_TYPE = "application/x-dcu-workspace";
1756
+ const WORKSPACE_GROUP_DRAG_TYPE = "application/x-dcu-workspace-group";
1308
1757
  const SESSION_DRAG_PREFIX = "dcu-session:";
1309
1758
  const WORKSPACE_DRAG_PREFIX = "dcu-workspace:";
1759
+ const WORKSPACE_GROUP_DRAG_PREFIX = "dcu-workspace-group:";
1310
1760
  function textPayload(data) {
1311
1761
  try {
1312
1762
  return data?.getData("text/plain") ?? "";
@@ -1325,6 +1775,11 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1325
1775
  data.setData("text/plain", `${WORKSPACE_DRAG_PREFIX}${workspaceId}`);
1326
1776
  data.setData(WORKSPACE_DRAG_TYPE, workspaceId);
1327
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
+ }
1328
1783
  function readSessionDrag(data, fallback) {
1329
1784
  try {
1330
1785
  const typed = data?.getData(SESSION_DRAG_TYPE);
@@ -1334,9 +1789,23 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1334
1789
  if (text.startsWith(SESSION_DRAG_PREFIX)) return text.slice(12);
1335
1790
  return fallback !== void 0 && fallback.trim() !== "" ? fallback : void 0;
1336
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
+ }
1337
1805
  /** 会话拖拽优先;有会话载荷时不得再把父项目置顶。 */
1338
1806
  function readWorkspaceDrag(data, fallback) {
1339
1807
  if (readSessionDrag(data) !== void 0) return void 0;
1808
+ if (readWorkspaceGroupDrag(data) !== void 0) return void 0;
1340
1809
  try {
1341
1810
  const typed = data?.getData(WORKSPACE_DRAG_TYPE);
1342
1811
  if (typed !== void 0 && typed.trim() !== "") return typed;
@@ -1380,60 +1849,364 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1380
1849
  } catch {}
1381
1850
  }
1382
1851
  //#endregion
1383
- //#region src/client/CodexWorkspaceBrowser.tsx
1384
- const stylesheet$4 = `
1385
- .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)}
1386
- .dcu-wb *{box-sizing:border-box}
1387
- .dcu-wb-tree{flex:1;min-height:0;overflow-y:auto;padding-bottom:16px;scrollbar-gutter:stable;user-select:none;-webkit-user-select:none}
1388
- .dcu-wb-section+.dcu-wb-section{margin-top:12px}
1389
- .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}}
1390
- .dcu-wb-project{position:relative}
1391
- .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}
1392
- .dcu-wb-project-head{height:28px;background:transparent;font:inherit;text-align:left}
1393
- .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}
1394
- .dcu-wb-project-head[draggable=true],.dcu-wb-session[draggable=true]{cursor:grab}
1395
- .dcu-wb-project-head[draggable=true]:active,.dcu-wb-session[draggable=true]:active{cursor:grabbing}
1396
- .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}
1397
- .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}
1398
- .dcu-wb-project-current .dcu-wb-folder{color:var(--dsw-alias-state-business-primary)}
1399
- .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}
1400
- .dcu-wb-project-title{flex:1;font-weight:400;color:var(--dcu-sidebar-primary)}
1401
- .dcu-wb-session{position:relative;min-height:32px;gap:0;padding-left:28px}
1402
- .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}
1403
- .dcu-wb-pin{display:grid;place-items:center;flex:none;width:16px;height:20px;margin-left:6px;color:var(--dcu-sidebar-tertiary)}
1404
- .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}
1405
- .dcu-wb-actions{display:none;align-items:center;gap:8px;flex:none}
1406
- .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}
1407
- .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}
1408
- .dcu-wb-session:hover .dcu-wb-quick-actions,.dcu-wb-session.dcu-wb-menu-open .dcu-wb-quick-actions{opacity:1;pointer-events:auto}
1409
- .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}
1410
- .dcu-wb-more:hover{color:var(--dcu-sidebar-primary)}
1411
- .dcu-wb-context-anchor{opacity:0;pointer-events:none}
1412
- .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}
1413
- .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)}
1414
- .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}
1415
- .dcu-wb-empty{padding:14px 8px;color:var(--dcu-sidebar-tertiary);font-size:13px}
1416
- .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}
1417
- .dcu-wb-rail{display:none}
1418
- .dcu-wb-rename-actions{display:flex;justify-content:flex-end;gap:8px}
1419
- .dcu-wb-delete-button{color:var(--dsw-alias-state-error-primary)!important}
1420
- .dcu-wb-delete-copy{margin:0;color:var(--dcu-sidebar-secondary);font-size:13px;line-height:20px}
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}
2193
+ .dcu-wb-delete-button{color:var(--dsw-alias-state-error-primary)!important}
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}
1421
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}
1422
2197
  .dcu-wb-rename-input:hover{border-color:color-mix(in srgb,var(--dsw-alias-label-primary) 32%,var(--dsw-alias-border-l2))}
1423
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)}
1424
2199
  `;
1425
- 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}}`;
1426
- 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}`;
1427
- 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;
1428
2204
  function storage$1() {
1429
2205
  return browserStorage();
1430
2206
  }
1431
2207
  function sameIds(left, right) {
1432
2208
  return left.length === right.length && left.every((id, index) => id === right[index]);
1433
2209
  }
1434
- function browserBase() {
1435
- return typeof window === "undefined" || window.location.origin === "null" ? "http://dsh.internal/" : `${window.location.origin}/`;
1436
- }
1437
2210
  function optionalText(value, key) {
1438
2211
  if (!(key in value)) return void 0;
1439
2212
  const next = value[key];
@@ -1443,14 +2216,21 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1443
2216
  function CodexWorkspaceBrowser(props) {
1444
2217
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HoverShell, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CodexWorkspaceTree, { ...props }) });
1445
2218
  }
1446
- function CodexWorkspaceTree({ wide, useSessions, useSessionPendingInteraction, 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 }) {
1447
2220
  const sessions = useSessions((state) => state);
1448
2221
  const pendingInteractions = (useSessionPendingInteraction ?? useEmptySessionPendingInteraction)((state) => state);
1449
2222
  const workspaces = useWorkspaces((state) => state);
2223
+ const baselinesReady = workspaceBaselinesReady(workspaces, sessions);
1450
2224
  const [expanded, setExpanded] = (0, react.useState)(() => readTreeExpansionState(storage$1(), WORKSPACE_EXPANSION_STORAGE_KEY));
1451
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);
1452
2228
  const pinnedWorkspaceIdsRef = (0, react.useRef)(pinnedWorkspaceIds);
1453
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);
1454
2234
  const pinnedHostHydratedRef = (0, react.useRef)(false);
1455
2235
  const pinnedHostDirtyRef = (0, react.useRef)(false);
1456
2236
  const pinnedHostSkipWriteRef = (0, react.useRef)();
@@ -1460,12 +2240,24 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1460
2240
  validIds: []
1461
2241
  });
1462
2242
  workspaceBaselineRef.current = {
1463
- ready: workspaces.baselinesReady === true,
2243
+ ready: baselinesReady,
1464
2244
  validIds: workspaces.items.map((workspace) => String(workspace.workspaceId))
1465
2245
  };
1466
- const queuePinnedHostWrite = (ids) => {
1467
- const snapshot = [...ids];
1468
- 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
+ });
1469
2261
  pinnedHostWriteRef.current = pending.catch(() => void 0);
1470
2262
  };
1471
2263
  const setPinnedWorkspaceIds = (update) => {
@@ -1475,11 +2267,21 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1475
2267
  pinnedWorkspaceIdsRef.current = next;
1476
2268
  setPinnedWorkspaceIdsState(next);
1477
2269
  };
1478
- 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
+ };
1479
2278
  const [unreadSessionIds, setUnreadSessionIds] = (0, react.useState)(() => readSessionIds(storage$1(), SESSION_UNREAD_STORAGE_KEY));
2279
+ const previousSessionRunningRef = (0, react.useRef)();
1480
2280
  const [menu, setMenu] = (0, react.useState)();
1481
2281
  const [renameTarget, setRenameTarget] = (0, react.useState)();
1482
2282
  const [deleteTarget, setDeleteTarget] = (0, react.useState)();
2283
+ const [archiveWorkspaceTarget, setArchiveWorkspaceTarget] = (0, react.useState)();
2284
+ const [sessionMoveTarget, setSessionMoveTarget] = (0, react.useState)();
1483
2285
  const { showTip: publishTip, hideTip, dismissTip, isShowing } = useHoverDispatch();
1484
2286
  const showTip = (tip, options) => {
1485
2287
  if (menu !== void 0) return;
@@ -1491,8 +2293,17 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1491
2293
  const [error, setError] = (0, react.useState)();
1492
2294
  const [workspaceDragId, setWorkspaceDragId] = (0, react.useState)();
1493
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
+ };
1494
2304
  const workspaceDropTargetRef = (0, react.useRef)();
1495
2305
  const setWorkspaceDropTarget = (target) => {
2306
+ if (sameWorkspaceDropTarget(workspaceDropTargetRef.current, target)) return;
1496
2307
  workspaceDropTargetRef.current = target;
1497
2308
  setWorkspaceDropTargetState(target);
1498
2309
  };
@@ -1502,75 +2313,108 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1502
2313
  const headerMenuPointerAt = (0, react.useRef)(0);
1503
2314
  const [sessionDrag, setSessionDrag] = (0, react.useState)();
1504
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);
1505
2320
  (0, react.useEffect)(() => {
1506
2321
  writeTreeExpansionState(storage$1(), WORKSPACE_EXPANSION_STORAGE_KEY, expanded);
1507
2322
  }, [expanded]);
1508
2323
  (0, react.useEffect)(() => {
1509
2324
  savePinnedWorkspaceIds(storage$1(), pinnedWorkspaceIds);
2325
+ saveWorkspaceGroupsCache(storage$1(), workspaceGroups, workspaceGroupsPendingHostSyncRef.current);
1510
2326
  if (!pinnedHostHydratedRef.current) return;
1511
2327
  const skippedHydration = pinnedHostSkipWriteRef.current;
1512
2328
  pinnedHostSkipWriteRef.current = void 0;
1513
- if (skippedHydration !== void 0 && sameIds(skippedHydration, pinnedWorkspaceIds)) return;
1514
- queuePinnedHostWrite(pinnedWorkspaceIds);
1515
- }, [pinnedWorkspaceIds]);
2329
+ if (skippedHydration !== void 0 && sameIds(skippedHydration.pinnedWorkspaceIds, pinnedWorkspaceIds) && sameWorkspaceGroups(skippedHydration.workspaceGroups, workspaceGroups)) return;
2330
+ queuePinnedHostWrite(pinnedWorkspaceIds, workspaceGroups);
2331
+ }, [pinnedWorkspaceIds, workspaceGroups]);
1516
2332
  (0, react.useEffect)(() => {
1517
2333
  let alive = true;
1518
- const localIds = [...pinnedWorkspaceIdsRef.current];
2334
+ const local = {
2335
+ pinnedWorkspaceIds: [...pinnedWorkspaceIdsRef.current],
2336
+ workspaceGroups: workspaceGroupsRef.current
2337
+ };
1519
2338
  readHostPinnedWorkspaceIds().then((host) => {
1520
2339
  if (!alive) return;
1521
- 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);
1522
2345
  const baseline = workspaceBaselineRef.current;
1523
- const ids = baseline.ready ? prunePinnedWorkspaceIds(hydration.ids, baseline.validIds) : hydration.ids;
1524
- 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;
1525
2351
  pinnedHostHydratedRef.current = true;
1526
- if (!sameIds(pinnedWorkspaceIdsRef.current, ids)) {
1527
- pinnedHostSkipWriteRef.current = [...ids];
2352
+ if (!sameIds(pinnedWorkspaceIdsRef.current, ids) || !sameWorkspaceGroups(workspaceGroupsRef.current, groups)) {
2353
+ pinnedHostSkipWriteRef.current = {
2354
+ pinnedWorkspaceIds: [...ids],
2355
+ workspaceGroups: groups
2356
+ };
1528
2357
  pinnedWorkspaceIdsRef.current = ids;
2358
+ workspaceGroupsRef.current = groups;
1529
2359
  setPinnedWorkspaceIdsState(ids);
2360
+ setWorkspaceGroupsState(groups);
1530
2361
  }
1531
2362
  savePinnedWorkspaceIds(storage$1(), ids);
1532
- if (writeHost) queuePinnedHostWrite(ids);
1533
- }).catch(() => {});
2363
+ if (writeHost) queuePinnedHostWrite(ids, groups);
2364
+ }).catch(() => {
2365
+ pinnedHostHydratedRef.current = true;
2366
+ });
1534
2367
  return () => {
1535
2368
  alive = false;
1536
2369
  };
1537
2370
  }, []);
1538
- (0, react.useEffect)(() => {
1539
- writeSessionIds(storage$1(), SESSION_PINS_STORAGE_KEY, pinnedSessionIds);
1540
- }, [pinnedSessionIds]);
1541
2371
  (0, react.useEffect)(() => {
1542
2372
  writeSessionIds(storage$1(), SESSION_UNREAD_STORAGE_KEY, unreadSessionIds);
1543
2373
  }, [unreadSessionIds]);
1544
2374
  (0, react.useEffect)(() => {
1545
- if (workspaces.baselinesReady !== true) return;
1546
- const next = prunePinnedWorkspaceIds(pinnedWorkspaceIdsRef.current, workspaces.items.map((workspace) => String(workspace.workspaceId)));
1547
- if (sameIds(pinnedWorkspaceIdsRef.current, next)) return;
1548
- pinnedWorkspaceIdsRef.current = next;
1549
- setPinnedWorkspaceIdsState(next);
1550
- }, [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]);
1551
2388
  (0, react.useEffect)(() => {
1552
2389
  const current = sessions.current;
1553
2390
  if (current !== void 0) setUnreadSessionIds((ids) => ids.filter((id) => id !== current));
1554
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]);
1555
2400
  const groups = (0, react.useMemo)(() => {
1556
2401
  const archived = workspaces.archivedSessionIds;
1557
- const visible = (ids) => visibleSessionIds(ids, sessions.byId, archived).sort((left, right) => {
1558
- const leftPinned = pinnedSessionIds.indexOf(left);
1559
- const rightPinned = pinnedSessionIds.indexOf(right);
1560
- if (leftPinned !== -1 || rightPinned !== -1) return (leftPinned === -1 ? Number.MAX_SAFE_INTEGER : leftPinned) - (rightPinned === -1 ? Number.MAX_SAFE_INTEGER : rightPinned);
1561
- return 0;
1562
- });
2402
+ const visible = (ids) => visibleSessionIds(ids, sessions.byId, archived);
1563
2403
  return { items: workspaces.items.map((workspace) => ({
1564
2404
  ...workspace,
1565
2405
  visibleIds: visible(workspace.sessionIds)
1566
2406
  })) };
1567
2407
  }, [
1568
- pinnedSessionIds,
1569
2408
  sessions.byId,
1570
2409
  sessions.ids,
1571
2410
  workspaces.archivedSessionIds,
1572
2411
  workspaces.items
1573
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
+ };
1574
2418
  (0, react.useEffect)(() => {
1575
2419
  const onPointerDown = () => {
1576
2420
  if (headerMenuRef.current !== void 0) headerMenuPointerAt.current = performance.now();
@@ -1596,6 +2440,8 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1596
2440
  title: workspace.title,
1597
2441
  path: workspace.path,
1598
2442
  count: workspace.visibleIds.length,
2443
+ unreadCount: workspace.visibleIds.filter((id) => unreadSessionIds.includes(String(id))).length,
2444
+ pinned: pinnedWorkspaceIds.includes(String(workspace.workspaceId)),
1599
2445
  left: detail.left,
1600
2446
  top: detail.top
1601
2447
  }, { immediate: true });
@@ -1624,7 +2470,9 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1624
2470
  }, [
1625
2471
  groups.items,
1626
2472
  sessions.current,
1627
- menu
2473
+ menu,
2474
+ pinnedWorkspaceIds,
2475
+ unreadSessionIds
1628
2476
  ]);
1629
2477
  const projectPinned = (id) => pinnedWorkspaceIds.includes(String(id));
1630
2478
  const pinnedGroups = orderByIds(groups.items, pinnedWorkspaceIds, (workspace) => String(workspace.workspaceId));
@@ -1632,6 +2480,11 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1632
2480
  const pinDragActive = workspaceDragId !== void 0;
1633
2481
  const pinnedGroupIds = pinnedGroups.map((workspace) => String(workspace.workspaceId));
1634
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]));
1635
2488
  const pinnedHeaderDrop = workspaceDropTarget?.zone === "pinned" && workspaceDropTarget.beforeId === pinnedGroupIds[0] ? pinnedHeaderDropIndicator(pinnedGroupIds) : void 0;
1636
2489
  const assignedIds = workspaces.items.flatMap((workspace) => workspace.sessionIds.map((id) => String(id)));
1637
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));
@@ -1644,7 +2497,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1644
2497
  await action();
1645
2498
  setMenu(void 0);
1646
2499
  } catch (reason) {
1647
- setError(reason instanceof Error ? reason.message : String(reason));
2500
+ setError(userErrorText(reason, t));
1648
2501
  } finally {
1649
2502
  busyRef.current = void 0;
1650
2503
  setBusy(void 0);
@@ -1674,7 +2527,6 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1674
2527
  run(operation, async () => {
1675
2528
  if (target.kind === "session") {
1676
2529
  await deleteSession(target.id);
1677
- setPinnedSessionIds((ids) => ids.filter((id) => id !== target.id));
1678
2530
  setUnreadSessionIds((ids) => ids.filter((id) => id !== target.id));
1679
2531
  } else {
1680
2532
  await deleteWorkspace(target.id);
@@ -1683,6 +2535,15 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1683
2535
  setDeleteTarget(void 0);
1684
2536
  });
1685
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
+ };
1686
2547
  const copy = (value) => {
1687
2548
  if (value === void 0 || value === "") return;
1688
2549
  run("copy", async () => {
@@ -1700,6 +2561,19 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1700
2561
  [key]: !(current[key] ?? defaultOpen)
1701
2562
  }));
1702
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
+ };
1703
2577
  const sectionOpen = (id) => expanded[`section:${id}`] ?? true;
1704
2578
  const toggleSection = (id) => {
1705
2579
  setExpanded((current) => ({
@@ -1707,44 +2581,150 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1707
2581
  [`section:${id}`]: !(current[`section:${id}`] ?? true)
1708
2582
  }));
1709
2583
  };
1710
- const projectMenu = (workspace) => [
1711
- {
1712
- id: "new",
1713
- label: t("workspace.newSession"),
1714
- icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPlusOutline16, { size: 16 })
1715
- },
1716
- {
1717
- id: "rename",
1718
- label: t("workspace.rename"),
1719
- icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEditOutline16, { size: 16 })
1720
- },
1721
- {
1722
- id: "pin",
1723
- label: t(projectPinned(workspace.workspaceId) ? "workspace.unpin" : "workspace.pin"),
1724
- icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PinIcon, {})
1725
- },
1726
- {
1727
- id: "openPath",
1728
- label: t("workspace.openPath"),
1729
- icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderOpenOutline16, { size: 16 })
1730
- },
1731
- {
1732
- type: "separator",
1733
- id: "project-separator"
1734
- },
1735
- {
1736
- id: "delete",
1737
- label: t("workspace.delete"),
1738
- icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, { size: 16 }),
1739
- 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;
1740
2653
  }
1741
- ];
1742
- const sessionMenu = (sessionId, _title, path) => sessionMenuItems(t, {
1743
- 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, {
1744
2680
  unread: unreadSessionIds.includes(sessionId),
1745
2681
  path,
1746
- includePath: true
2682
+ includePath: true,
2683
+ moveTargets: sessionMoveTargets(groups.items, sessionId).map((target) => ({
2684
+ ...target,
2685
+ id: moveSessionActionId(target.id)
2686
+ }))
1747
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
+ };
1748
2728
  const pinWorkspaceAt = (id, beforeId) => {
1749
2729
  setPinnedWorkspaceIds((ids) => insertPinnedWorkspace(ids, id, beforeId));
1750
2730
  };
@@ -1757,207 +2737,222 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1757
2737
  x: menu.x,
1758
2738
  y: menu.y
1759
2739
  } : void 0;
1760
- const isCurrentWorkspace = sessions.current !== void 0 && workspace.visibleIds.includes(sessions.current);
1761
2740
  const isPinnedHeaderDrop = zone === "pinned" && pinnedHeaderDrop?.kind === "workspace" && pinnedHeaderDrop.workspaceId === String(workspace.workspaceId);
1762
2741
  const workspaceId = String(workspace.workspaceId);
1763
2742
  const zoneIds = zone === "pinned" ? pinnedGroupIds : regularGroupIds;
1764
2743
  const dropsBefore = workspaceDropTarget?.zone === zone && workspaceDropTarget.beforeId === workspaceId;
1765
2744
  const dropsAfterLast = zone === "projects" && workspaceDropTarget?.zone === zone && workspaceDropTarget.beforeId === void 0 && zoneIds[zoneIds.length - 1] === workspaceId;
1766
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1767
- className: `dcu-wb-project${isCurrentWorkspace ? " dcu-wb-project-current" : ""}${isPinnedHeaderDrop || dropsBefore ? " dcu-wb-drop" : ""}${dropsAfterLast ? " dcu-wb-drop dcu-wb-drop-after" : ""}`,
1768
- onDragOver: (event) => {
1769
- 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) {
1770
2750
  event.preventDefault();
1771
2751
  event.stopPropagation();
1772
2752
  event.dataTransfer.dropEffect = "move";
1773
2753
  const rect = event.currentTarget.getBoundingClientRect();
1774
- const after = event.clientY > rect.top + rect.height / 2;
1775
- 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);
1776
2756
  setWorkspaceDropTarget(beforeId === null ? void 0 : {
1777
2757
  zone,
1778
2758
  beforeId
1779
2759
  });
1780
- },
1781
- onDragLeave: (event) => {
1782
- if (event.currentTarget.contains(event.relatedTarget)) return;
1783
- setWorkspaceDropTarget(void 0);
1784
- },
1785
- 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) {
1786
2779
  event.preventDefault();
1787
2780
  event.stopPropagation();
1788
- const dragged = readWorkspaceDrag(event.dataTransfer, workspaceDragId);
1789
2781
  const rect = event.currentTarget.getBoundingClientRect();
1790
- const after = event.clientY > rect.top + rect.height / 2;
2782
+ const beforeId = reorderDropBeforeId(zoneIds, draggedWorkspace, workspaceId, event.clientY > rect.top + rect.height / 2);
1791
2783
  setWorkspaceDragId(void 0);
1792
2784
  setWorkspaceDropTarget(void 0);
1793
- if (dragged === void 0) return;
1794
- const beforeId = reorderDropBeforeId(zoneIds, dragged, workspaceId, after);
1795
2785
  if (beforeId === null) return;
1796
2786
  if (zone === "pinned") {
1797
- pinWorkspaceAt(dragged, beforeId);
2787
+ pinWorkspaceAt(draggedWorkspace, beforeId);
1798
2788
  return;
1799
2789
  }
1800
- setPinnedWorkspaceIds((ids) => ids.filter((id) => id !== dragged));
1801
2790
  const hostIds = groups.items.map((item) => String(item.workspaceId));
1802
- 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);
1803
2815
  },
1804
- children: [
1805
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1806
- className: `dcu-wb-project-head${menuOpen ? " dcu-wb-menu-open" : ""}${workspaceDragId === workspaceId ? " dcu-wb-dragging" : ""}`,
1807
- role: "treeitem",
1808
- "aria-expanded": isExpanded,
1809
- tabIndex: 0,
1810
- draggable: true,
1811
- onDragStart: (event) => {
1812
- event.stopPropagation();
1813
- setSessionDrag(void 0);
1814
- setSessionDropTarget(void 0);
1815
- writeWorkspaceDrag(event.dataTransfer, workspaceId, workspace.title);
1816
- const preview = document.createElement("div");
1817
- preview.className = "dcu-wb-drag-ghost";
1818
- preview.textContent = workspace.title;
1819
- document.body.appendChild(preview);
1820
- event.dataTransfer.setDragImage(preview, 16, 18);
1821
- window.requestAnimationFrame(() => {
1822
- preview.remove();
1823
- });
1824
- setWorkspaceDragId(workspaceId);
1825
- },
1826
- onDragEnd: () => {
1827
- setWorkspaceDragId(void 0);
1828
- setWorkspaceDropTarget(void 0);
1829
- },
1830
- onClick: () => {
1831
- toggleGroup(expandKey, true);
1832
- },
1833
- 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 === " ") {
1834
2869
  event.preventDefault();
1835
- event.stopPropagation();
1836
- dismissTip();
1837
- setMenu({
1838
- id: workspace.workspaceId,
1839
- type: "workspace",
1840
- x: event.clientX,
1841
- y: event.clientY
1842
- });
1843
- },
1844
- onMouseEnter: (event) => {
1845
- const box = hoverCardAnchor(event.currentTarget.getBoundingClientRect());
1846
- showTip({
1847
- kind: "workspace",
1848
- id: workspace.workspaceId,
1849
- title: workspace.title,
1850
- path: workspace.path,
1851
- count: workspace.visibleIds.length,
1852
- left: box.left,
1853
- top: box.top
1854
- });
1855
- },
1856
- onMouseLeave: hideTip,
1857
- onKeyDown: (event) => {
1858
- if (event.key === "Enter" || event.key === " ") {
1859
- event.preventDefault();
1860
- toggleGroup(expandKey, true);
1861
- }
1862
- },
1863
- children: [
1864
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1865
- className: "dcu-wb-folder",
1866
- onClick: (event) => {
1867
- event.stopPropagation();
1868
- const box = hoverCardAnchor(event.currentTarget.getBoundingClientRect());
1869
- if (isShowing("workspace", workspace.workspaceId)) {
1870
- dismissTip();
1871
- return;
1872
- }
1873
- showTip({
1874
- kind: "workspace",
1875
- id: workspace.workspaceId,
1876
- title: workspace.title,
1877
- path: workspace.path,
1878
- count: workspace.visibleIds.length,
1879
- left: box.left,
1880
- top: box.top
1881
- }, { 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);
1882
2907
  },
1883
- 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 })
1884
- }),
1885
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1886
- className: "dcu-wb-project-title",
1887
- children: workspace.title
1888
- }),
1889
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1890
- className: "dcu-wb-actions",
1891
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
1892
- open: menuOpen,
1893
- onClose: () => {
1894
- setMenu(void 0);
1895
- },
1896
- items: projectMenu(workspace),
1897
- onSelect: (id) => {
1898
- if (busy !== void 0) return;
1899
- if (id === "new") {
1900
- startSession(workspace.workspaceId);
1901
- setMenu(void 0);
1902
- }
1903
- if (id === "rename") beginRename("workspace", workspace.workspaceId, workspace.title);
1904
- if (id === "pin") {
1905
- setPinnedWorkspaceIds((ids) => togglePinnedWorkspace(ids, workspace.workspaceId));
1906
- setMenu(void 0);
1907
- }
1908
- if (id === "openPath") openPathImmediately(workspace.path);
1909
- if (id === "delete") {
1910
- setDeleteTarget({
1911
- id: workspace.workspaceId,
1912
- kind: "workspace",
1913
- title: workspace.title
1914
- });
1915
- setError(void 0);
1916
- setMenu(void 0);
1917
- }
1918
- },
1919
- portal: true,
1920
- dense: true,
1921
- compact: true,
1922
- getAnchorRect: menuAt === void 0 ? void 0 : () => pointerMenuRect(menuAt.x, menuAt.y),
1923
- anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1924
- type: "button",
1925
- className: "dcu-wb-more",
1926
- "aria-label": t("workspace.actions", { name: workspace.title }),
1927
- onClick: (event) => {
1928
- event.stopPropagation();
1929
- setMenu((current) => current?.id === workspace.workspaceId && current.type === "workspace" ? void 0 : {
1930
- id: workspace.workspaceId,
1931
- type: "workspace"
1932
- });
1933
- },
1934
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEllipsisOutline16, { size: 16 })
1935
- })
1936
- })
1937
- }),
1938
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1939
- className: "dcu-wb-actions",
1940
- 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", {
1941
2917
  type: "button",
1942
2918
  className: "dcu-wb-more",
1943
- "aria-label": t("workspace.newSession"),
2919
+ "aria-label": t("workspace.actions", { name: workspace.title }),
1944
2920
  onClick: (event) => {
1945
2921
  event.stopPropagation();
1946
- 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
+ });
1947
2927
  },
1948
- 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 })
1949
2929
  })
1950
2930
  })
1951
- ]
1952
- }),
1953
- 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", {
1954
2950
  className: "dcu-wb-nochat",
1955
2951
  children: t("workspace.noChat")
1956
- }),
1957
- isExpanded && shownIds.map((id) => {
2952
+ }), shownIds.map((id) => {
1958
2953
  const session = sessions.byId[id];
1959
2954
  if (session === void 0) return null;
1960
- const pendingInteraction = pendingInteractionForSession(id, pendingInteractions, session.pendingInteraction);
2955
+ const pendingInteraction = pendingInteractionForSession(id, pendingInteractions, legacyPendingInteraction(session));
1961
2956
  const path = session.cwd ?? workspace.path;
1962
2957
  const selected = sessions.current === id;
1963
2958
  const sessionMenuOpen = menu?.type === "session" && menu.id === id;
@@ -1965,8 +2960,8 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1965
2960
  x: menu.x,
1966
2961
  y: menu.y
1967
2962
  } : void 0;
1968
- const dropsBeforeSession = sessionDropTarget?.workspaceId === workspaceId && sessionDropTarget.beforeId === id;
1969
- 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;
1970
2965
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1971
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" : ""}`,
1972
2967
  role: "treeitem",
@@ -1976,15 +2971,10 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1976
2971
  event.stopPropagation();
1977
2972
  setWorkspaceDragId(void 0);
1978
2973
  setWorkspaceDropTarget(void 0);
2974
+ setWorkspaceGroupDragId(void 0);
2975
+ setWorkspaceGroupDropTarget(void 0);
1979
2976
  writeSessionDrag(event.dataTransfer, id, session.displayTitle);
1980
- const preview = document.createElement("div");
1981
- preview.className = "dcu-wb-drag-ghost";
1982
- preview.textContent = session.displayTitle;
1983
- document.body.appendChild(preview);
1984
- event.dataTransfer.setDragImage(preview, 16, 18);
1985
- window.requestAnimationFrame(() => {
1986
- preview.remove();
1987
- });
2977
+ setDragPreview(event.dataTransfer, session.displayTitle);
1988
2978
  setSessionDrag({
1989
2979
  sessionId: id,
1990
2980
  workspaceId
@@ -1993,14 +2983,16 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1993
2983
  onDragEnd: () => {
1994
2984
  setSessionDrag(void 0);
1995
2985
  setSessionDropTarget(void 0);
2986
+ setWorkspaceGroupDropTarget(void 0);
1996
2987
  },
1997
2988
  onDragOver: (event) => {
1998
- if (sessionDrag?.workspaceId !== workspaceId) return;
2989
+ const drag = sessionDrag;
2990
+ if (drag === void 0 || drag.workspaceId !== workspaceId) return;
1999
2991
  event.preventDefault();
2000
2992
  event.stopPropagation();
2001
2993
  event.dataTransfer.dropEffect = "move";
2002
2994
  const rect = event.currentTarget.getBoundingClientRect();
2003
- 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);
2004
2996
  setSessionDropTarget(beforeId === null ? void 0 : {
2005
2997
  workspaceId,
2006
2998
  beforeId
@@ -2011,13 +3003,13 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2011
3003
  setSessionDropTarget(void 0);
2012
3004
  },
2013
3005
  onDrop: (event) => {
3006
+ const drag = sessionDrag;
3007
+ if (drag === void 0 || drag.workspaceId !== workspaceId) return;
2014
3008
  event.preventDefault();
2015
3009
  event.stopPropagation();
2016
- const drag = sessionDrag;
2017
3010
  const rect = event.currentTarget.getBoundingClientRect();
2018
3011
  setSessionDrag(void 0);
2019
3012
  setSessionDropTarget(void 0);
2020
- if (drag === void 0 || drag.workspaceId !== workspaceId) return;
2021
3013
  const beforeId = reorderDropBeforeId(shownIds, drag.sessionId, id, event.clientY > rect.top + rect.height / 2);
2022
3014
  if (beforeId !== null) run("session-order", () => insertSessionBefore(workspace.workspaceId, drag.sessionId, beforeId));
2023
3015
  },
@@ -2046,7 +3038,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2046
3038
  project: workspace.title,
2047
3039
  path,
2048
3040
  branch,
2049
- time: formatHoverTime(session.updatedAt),
3041
+ time: formatHoverTime(session.updatedAt, t),
2050
3042
  left: box.left,
2051
3043
  top: box.top
2052
3044
  });
@@ -2057,37 +3049,15 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2057
3049
  className: "dcu-wb-session-title",
2058
3050
  children: session.displayTitle.split(/\r?\n/)[0] ?? session.displayTitle
2059
3051
  }),
2060
- pinnedSessionIds.includes(id) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2061
- className: "dcu-wb-pin",
2062
- "aria-label": t("sessions.pinned"),
2063
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
2064
- viewBox: "0 0 16 16",
2065
- "aria-hidden": "true",
2066
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M5 2.5h6M5 2.5v2L3.75 6v1h8.5V6L11 4.5v-2M8 7v6" })
2067
- })
2068
- }),
2069
3052
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionState, {
2070
3053
  pendingInteraction,
2071
3054
  unread: unreadSessionIds.includes(id),
2072
3055
  running: session.running === true,
2073
3056
  t
2074
3057
  }),
2075
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3058
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2076
3059
  className: "dcu-wb-quick-actions",
2077
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2078
- type: "button",
2079
- className: "dcu-wb-more dcu-wb-quick-pin",
2080
- "aria-label": t(pinnedSessionIds.includes(id) ? "sessions.unpin" : "sessions.pin"),
2081
- onClick: (event) => {
2082
- event.stopPropagation();
2083
- setPinnedSessionIds((ids) => toggleSessionId(ids, id));
2084
- },
2085
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
2086
- viewBox: "0 0 16 16",
2087
- "aria-hidden": "true",
2088
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M5 2.5h6M5 2.5v2L3.75 6v1h8.5V6L11 4.5v-2M8 7v6" })
2089
- })
2090
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3060
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2091
3061
  type: "button",
2092
3062
  className: "dcu-wb-more",
2093
3063
  "aria-label": t("sessions.archive"),
@@ -2095,8 +3065,8 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2095
3065
  event.stopPropagation();
2096
3066
  run("archive", () => archiveSession(id));
2097
3067
  },
2098
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconArchiveOutline20, { size: 16 })
2099
- })]
3068
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(QuickArchiveIcon, {})
3069
+ })
2100
3070
  }),
2101
3071
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2102
3072
  className: "dcu-wb-actions",
@@ -2105,15 +3075,10 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2105
3075
  onClose: () => {
2106
3076
  setMenu(void 0);
2107
3077
  },
2108
- items: sessionMenu(id, session.displayTitle, path),
3078
+ items: sessionMenu(id, path),
2109
3079
  onSelect: (action) => {
2110
- if (busy !== void 0) return;
2111
- const link = sessionDeepLink(browserBase(), id);
3080
+ if (busy !== void 0 || runSessionMoveAction(action, id)) return;
2112
3081
  if (action === "rename") beginRename("session", id, session.displayTitle);
2113
- if (action === "pin") {
2114
- setPinnedSessionIds((ids) => toggleSessionId(ids, id));
2115
- setMenu(void 0);
2116
- }
2117
3082
  if (action === "unread") {
2118
3083
  setUnreadSessionIds((ids) => toggleSessionId(ids, id));
2119
3084
  setMenu(void 0);
@@ -2133,7 +3098,6 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2133
3098
  if (action === "copyPath") copy(path);
2134
3099
  if (action === "copyTitle") copy(session.displayTitle);
2135
3100
  if (action === "copyId") copy(id);
2136
- if (action === "copyLink") copy(link);
2137
3101
  },
2138
3102
  portal: true,
2139
3103
  dense: true,
@@ -2145,7 +3109,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2145
3109
  "aria-label": t("sessions.actions", { name: session.displayTitle }),
2146
3110
  onClick: (event) => {
2147
3111
  event.stopPropagation();
2148
- setMenu((current) => current?.id === id && current.type === "session" ? void 0 : {
3112
+ setMenu((current) => current?.id === id && current?.type === "session" ? void 0 : {
2149
3113
  id,
2150
3114
  type: "session"
2151
3115
  });
@@ -2156,10 +3120,326 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2156
3120
  })
2157
3121
  ]
2158
3122
  }, id);
2159
- })
2160
- ]
3123
+ })]
3124
+ })]
2161
3125
  }, workspace.workspaceId);
2162
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);
2163
3443
  if (!wide) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2164
3444
  className: "dcu-wb dcu-wb-rail",
2165
3445
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: stylesheet$4 })
@@ -2171,7 +3451,8 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2171
3451
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("style", { children: [
2172
3452
  stylesheet$4,
2173
3453
  runningStyles,
2174
- typographyStyles
3454
+ typographyStyles,
3455
+ collectionLayoutStyles
2175
3456
  ] }),
2176
3457
  error !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2177
3458
  className: "dcu-wb-error",
@@ -2179,6 +3460,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2179
3460
  children: t("sessions.failed", { message: error })
2180
3461
  }),
2181
3462
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3463
+ ref: treeRef,
2182
3464
  className: "dcu-wb-tree",
2183
3465
  role: "tree",
2184
3466
  children: [
@@ -2189,7 +3471,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2189
3471
  if (!pinDragActive) return;
2190
3472
  event.preventDefault();
2191
3473
  event.dataTransfer.dropEffect = "move";
2192
- 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) {
2193
3475
  const firstId = pinnedGroupIds[0];
2194
3476
  const beforeId = firstId === void 0 ? void 0 : reorderDropBeforeId(pinnedGroupIds, workspaceDragId, firstId, false);
2195
3477
  setWorkspaceDropTarget(beforeId === null ? void 0 : {
@@ -2240,46 +3522,53 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2240
3522
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2241
3523
  className: "dcu-wb-section-body",
2242
3524
  "data-open": sectionOpen("pinned"),
2243
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
2244
- pinDragActive && pinnedGroups.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2245
- className: `dcu-wb-pin-start${pinnedHeaderDrop?.kind === "empty" ? " dcu-wb-drop" : ""}`,
2246
- onDragOver: (event) => {
2247
- event.preventDefault();
2248
- event.stopPropagation();
2249
- event.dataTransfer.dropEffect = "move";
2250
- setWorkspaceDropTarget({
2251
- zone: "pinned",
2252
- beforeId: void 0
2253
- });
2254
- }
2255
- }),
2256
- pinnedGroups.length === 0 && !(workspaceDropTarget?.zone === "pinned" && workspaceDropTarget.beforeId === void 0) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2257
- className: "dcu-wb-empty",
2258
- children: t("workspace.pinnedEmpty")
2259
- }),
2260
- pinnedGroups.map((workspace) => renderGroup(workspace, "pinned")),
2261
- pinDragActive && pinnedGroups.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2262
- className: `dcu-wb-pin-end${workspaceDropTarget?.zone === "pinned" && workspaceDropTarget.beforeId === void 0 ? " dcu-wb-drop" : ""}`,
2263
- onDragOver: (event) => {
2264
- if (!pinDragActive) return;
2265
- event.preventDefault();
2266
- event.stopPropagation();
2267
- event.dataTransfer.dropEffect = "move";
2268
- const lastId = pinnedGroupIds[pinnedGroupIds.length - 1];
2269
- if (lastId === void 0 || workspaceDragId === void 0) return;
2270
- const beforeId = reorderDropBeforeId(pinnedGroupIds, workspaceDragId, lastId, true);
2271
- setWorkspaceDropTarget(beforeId === null ? void 0 : {
2272
- zone: "pinned",
2273
- beforeId
2274
- });
2275
- }
2276
- })
2277
- ] })
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
+ })
2278
3563
  })]
2279
3564
  }),
2280
3565
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2281
3566
  className: "dcu-wb-section",
2282
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
+ },
2283
3572
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2284
3573
  className: "dcu-wb-section-head",
2285
3574
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
@@ -2306,26 +3595,66 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2306
3595
  })
2307
3596
  })
2308
3597
  })]
2309
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3598
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2310
3599
  className: "dcu-wb-actions",
2311
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3600
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2312
3601
  type: "button",
2313
3602
  className: "dcu-wb-more",
2314
- "aria-label": t("workspace.newSession"),
3603
+ "aria-label": t("workspace.createGroup"),
3604
+ title: t("workspace.createGroup"),
2315
3605
  onClick: (event) => {
2316
3606
  event.stopPropagation();
2317
- startSession();
3607
+ setGroupTitleDraft("");
3608
+ setError(void 0);
3609
+ setCreateGroupOpen(true);
2318
3610
  },
2319
3611
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPlusOutline16, { size: 16 })
2320
- })
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
+ })]
2321
3642
  })]
2322
3643
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2323
3644
  className: "dcu-wb-section-body",
2324
3645
  "data-open": sectionOpen("projects"),
2325
- 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", {
2326
- className: "dcu-wb-empty",
2327
- children: t("workspace.empty")
2328
- })] })
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
+ })
2329
3658
  })]
2330
3659
  }),
2331
3660
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
@@ -2368,34 +3697,27 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2368
3697
  const session = sessions.byId[id];
2369
3698
  if (session === void 0) return null;
2370
3699
  const title = session.displayTitle;
2371
- const pinned = pinnedSessionIds.includes(id);
2372
3700
  const unread = unreadSessionIds.includes(id);
2373
- const pendingInteraction = pendingInteractionForSession(id, pendingInteractions, session.pendingInteraction);
3701
+ const pendingInteraction = pendingInteractionForSession(id, pendingInteractions, legacyPendingInteraction(session));
2374
3702
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionRow, {
2375
3703
  id,
2376
3704
  title,
2377
3705
  selected: sessions.current === id,
2378
3706
  menuOpen: menu?.type === "session" && menu.id === id,
2379
- pinned,
2380
3707
  unread,
2381
3708
  running: session.running === true,
2382
3709
  pendingInteraction,
2383
3710
  t,
2384
- menuItems: sessionMenu(id, title, session.cwd),
3711
+ menuItems: sessionMenu(id, session.cwd),
2385
3712
  draggable: true,
2386
3713
  onDragStart: (event) => {
2387
3714
  event.stopPropagation();
2388
3715
  setWorkspaceDragId(void 0);
2389
3716
  setWorkspaceDropTarget(void 0);
3717
+ setWorkspaceGroupDragId(void 0);
3718
+ setWorkspaceGroupDropTarget(void 0);
2390
3719
  writeSessionDrag(event.dataTransfer, id, title);
2391
- const preview = document.createElement("div");
2392
- preview.className = "dcu-wb-drag-ghost";
2393
- preview.textContent = title;
2394
- document.body.appendChild(preview);
2395
- event.dataTransfer.setDragImage(preview, 16, 18);
2396
- window.requestAnimationFrame(() => {
2397
- preview.remove();
2398
- });
3720
+ setDragPreview(event.dataTransfer, title);
2399
3721
  setSessionDrag({
2400
3722
  sessionId: id,
2401
3723
  workspaceId: ""
@@ -2405,6 +3727,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2405
3727
  setSessionDrag(void 0);
2406
3728
  setSessionDropTarget(void 0);
2407
3729
  setWorkspaceDropTarget(void 0);
3730
+ setWorkspaceGroupDropTarget(void 0);
2408
3731
  },
2409
3732
  menuPoint: menu?.type === "session" && menu.id === id && menu.x !== void 0 && menu.y !== void 0 ? {
2410
3733
  x: menu.x,
@@ -2420,9 +3743,6 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2420
3743
  type: "session"
2421
3744
  } : void 0);
2422
3745
  },
2423
- onPin: () => {
2424
- setPinnedSessionIds((ids) => toggleSessionId(ids, id));
2425
- },
2426
3746
  onArchive: () => {
2427
3747
  run("archive", () => archiveSession(id));
2428
3748
  },
@@ -2432,7 +3752,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2432
3752
  kind: "session",
2433
3753
  id,
2434
3754
  title,
2435
- time: formatHoverTime(session.updatedAt),
3755
+ time: formatHoverTime(session.updatedAt, t),
2436
3756
  left: box.left,
2437
3757
  top: box.top
2438
3758
  });
@@ -2450,12 +3770,8 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2450
3770
  });
2451
3771
  },
2452
3772
  onSelectAction: (action) => {
2453
- if (busy !== void 0) return;
3773
+ if (busy !== void 0 || runSessionMoveAction(action, id)) return;
2454
3774
  if (action === "rename") beginRename("session", id, title);
2455
- if (action === "pin") {
2456
- setPinnedSessionIds((ids) => toggleSessionId(ids, id));
2457
- setMenu(void 0);
2458
- }
2459
3775
  if (action === "unread") {
2460
3776
  setUnreadSessionIds((ids) => toggleSessionId(ids, id));
2461
3777
  setMenu(void 0);
@@ -2475,7 +3791,6 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2475
3791
  if (action === "copyPath") copy(session.cwd);
2476
3792
  if (action === "copyTitle") copy(title);
2477
3793
  if (action === "copyId") copy(id);
2478
- if (action === "copyLink") copy(sessionDeepLink(browserBase(), id));
2479
3794
  }
2480
3795
  }, id);
2481
3796
  })] })
@@ -2488,15 +3803,13 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2488
3803
  onClose: () => {
2489
3804
  setHeaderMenu(void 0);
2490
3805
  },
2491
- 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),
2492
3807
  onSelect: (action) => {
2493
3808
  const id = headerMenu.id;
2494
3809
  const session = sessions.byId[id];
2495
- if (session === void 0 || busy !== void 0) return;
3810
+ if (session === void 0 || busy !== void 0 || runSessionMoveAction(action, id)) return;
2496
3811
  const path = session.cwd ?? groups.items.find((item) => item.visibleIds.includes(id))?.path;
2497
- const link = sessionDeepLink(browserBase(), id);
2498
3812
  if (action === "rename") beginRename("session", id, session.displayTitle);
2499
- if (action === "pin") setPinnedSessionIds((ids) => toggleSessionId(ids, id));
2500
3813
  if (action === "unread") setUnreadSessionIds((ids) => toggleSessionId(ids, id));
2501
3814
  if (action === "archive") run("archive", () => archiveSession(id));
2502
3815
  if (action === "delete") {
@@ -2512,7 +3825,6 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2512
3825
  if (action === "copyPath") copy(path);
2513
3826
  if (action === "copyTitle") copy(session.displayTitle);
2514
3827
  if (action === "copyId") copy(id);
2515
- if (action === "copyLink") copy(link);
2516
3828
  setHeaderMenu(void 0);
2517
3829
  },
2518
3830
  portal: true,
@@ -2527,8 +3839,89 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2527
3839
  t,
2528
3840
  onEditWorkspace: (id, title) => {
2529
3841
  beginRename("workspace", id, title);
3842
+ },
3843
+ onToggleWorkspacePin: (id) => {
3844
+ setPinnedWorkspaceIds((ids) => togglePinnedWorkspace(ids, id));
2530
3845
  }
2531
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
+ }),
2532
3925
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
2533
3926
  open: renameTarget !== void 0,
2534
3927
  onClose: () => {
@@ -2574,6 +3967,98 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2574
3967
  children: t("sessions.failed", { message: error })
2575
3968
  })]
2576
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
+ }),
2577
4062
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
2578
4063
  open: deleteTarget !== void 0,
2579
4064
  onClose: () => {
@@ -2632,13 +4117,9 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2632
4117
  function storage() {
2633
4118
  return browserStorage();
2634
4119
  }
2635
- /** 频道/定时共用的本地置顶与未读标记。 */
4120
+ /** 频道/定时共用的本地未读标记。 */
2636
4121
  function useSessionFlags(current) {
2637
- const [pinnedSessionIds, setPinnedSessionIds] = (0, react.useState)(() => readSessionIds(storage(), SESSION_PINS_STORAGE_KEY));
2638
4122
  const [unreadSessionIds, setUnreadSessionIds] = (0, react.useState)(() => readSessionIds(storage(), SESSION_UNREAD_STORAGE_KEY));
2639
- (0, react.useEffect)(() => {
2640
- writeSessionIds(storage(), SESSION_PINS_STORAGE_KEY, pinnedSessionIds);
2641
- }, [pinnedSessionIds]);
2642
4123
  (0, react.useEffect)(() => {
2643
4124
  writeSessionIds(storage(), SESSION_UNREAD_STORAGE_KEY, unreadSessionIds);
2644
4125
  }, [unreadSessionIds]);
@@ -2646,8 +4127,6 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2646
4127
  if (current !== void 0) setUnreadSessionIds((ids) => ids.filter((id) => id !== current));
2647
4128
  }, [current]);
2648
4129
  return {
2649
- pinnedSessionIds,
2650
- setPinnedSessionIds,
2651
4130
  unreadSessionIds,
2652
4131
  setUnreadSessionIds
2653
4132
  };
@@ -2662,7 +4141,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2662
4141
  onLeave: hideTip
2663
4142
  });
2664
4143
  }
2665
- function useBusyAction(onSuccess) {
4144
+ function useBusyAction(t, onSuccess) {
2666
4145
  const [busy, setBusy] = (0, react.useState)();
2667
4146
  const [error, setError] = (0, react.useState)();
2668
4147
  const busyRef = (0, react.useRef)();
@@ -2675,7 +4154,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2675
4154
  await action();
2676
4155
  onSuccess?.();
2677
4156
  } catch (reason) {
2678
- setError(reason instanceof Error ? reason.message : String(reason));
4157
+ setError(userErrorText(reason, t));
2679
4158
  } finally {
2680
4159
  busyRef.current = void 0;
2681
4160
  setBusy(void 0);
@@ -2705,7 +4184,6 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2705
4184
  const target = deleteTarget;
2706
4185
  run("delete", async () => {
2707
4186
  await actions.deleteSession(target.id);
2708
- flags.setPinnedSessionIds((ids) => ids.filter((id) => id !== target.id));
2709
4187
  flags.setUnreadSessionIds((ids) => ids.filter((id) => id !== target.id));
2710
4188
  setDeleteTarget(void 0);
2711
4189
  });
@@ -2719,10 +4197,6 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2719
4197
  setRenameDraft(title);
2720
4198
  closeMenu();
2721
4199
  }
2722
- if (action === "pin") {
2723
- flags.setPinnedSessionIds((ids) => toggleSessionId(ids, id));
2724
- closeMenu();
2725
- }
2726
4200
  if (action === "unread") {
2727
4201
  flags.setUnreadSessionIds((ids) => toggleSessionId(ids, id));
2728
4202
  closeMenu();
@@ -2742,9 +4216,6 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2742
4216
  if (action === "copyId") run("copy", async () => {
2743
4217
  await (0, _deepseek_ai_dsh_client_ui_primitives.writeClipboard)(id);
2744
4218
  });
2745
- if (action === "copyLink") run("copy", async () => {
2746
- await (0, _deepseek_ai_dsh_client_ui_primitives.writeClipboard)(copySessionLink(id));
2747
- });
2748
4219
  };
2749
4220
  return {
2750
4221
  renameTarget,
@@ -2877,15 +4348,16 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2877
4348
  })
2878
4349
  });
2879
4350
  }
2880
- function ChannelBrowserTree({ openSession, archiveSession, deleteSession, forkSession, renameSession, useSessions, useSessionPendingInteraction, t, menu, setMenu }) {
4351
+ function ChannelBrowserTree({ openSession, archiveSession, deleteSession, forkSession, moveSession, renameSession, useSessions, useSessionPendingInteraction, useWorkspaces, t, menu, setMenu }) {
2881
4352
  const sessions = useSessions((state) => state);
4353
+ const workspaces = useWorkspaces?.((state) => state);
2882
4354
  const pendingInteractions = (useSessionPendingInteraction ?? useEmptySessionPendingInteraction)((state) => state);
2883
4355
  const [groups, setGroups] = (0, react.useState)([]);
2884
4356
  const [pollError, setPollError] = (0, react.useState)();
2885
4357
  const [expanded, setExpanded] = (0, react.useState)(() => readTreeExpansionState(browserStorage(), CHANNEL_EXPANSION_STORAGE_KEY));
2886
4358
  const flags = useSessionFlags(sessions.current);
2887
4359
  const { showTip, hideTip, dismissTip } = useHoverDispatch();
2888
- const { busy, error, setError, run } = useBusyAction(() => {
4360
+ const { busy, error, setError, run } = useBusyAction(t, () => {
2889
4361
  setMenu(void 0);
2890
4362
  });
2891
4363
  const dialogs = useSessionDialogs({
@@ -2910,7 +4382,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2910
4382
  const timeout = window.setTimeout(() => {
2911
4383
  controller.abort();
2912
4384
  }, 8e3);
2913
- loadChannelGroups(controller.signal).then((next) => {
4385
+ loadChannelGroups(controller.signal, t("channel.unknown")).then((next) => {
2914
4386
  if (!disposed) {
2915
4387
  setGroups(next);
2916
4388
  setPollError(void 0);
@@ -2975,22 +4447,24 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2975
4447
  const selected = sessions.current === id;
2976
4448
  const running = session.running || sessions.byId[id]?.running === true;
2977
4449
  const updatedAt = session.updatedAt ?? sessions.byId[id]?.updatedAt;
2978
- const pinned = flags.pinnedSessionIds.includes(id);
2979
4450
  const unread = flags.unreadSessionIds.includes(id);
2980
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
+ }));
2981
4456
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionRow, {
2982
4457
  id,
2983
4458
  title,
2984
4459
  selected,
2985
4460
  menuOpen: menu?.id === id,
2986
- pinned,
2987
4461
  unread,
2988
4462
  running,
2989
4463
  pendingInteraction,
2990
4464
  t,
2991
4465
  menuItems: sessionMenuItems(t, {
2992
- pinned,
2993
- unread
4466
+ unread,
4467
+ moveTargets
2994
4468
  }),
2995
4469
  menuPoint: menu?.id === id && menu.x !== void 0 && menu.y !== void 0 ? {
2996
4470
  x: menu.x,
@@ -3003,9 +4477,6 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
3003
4477
  onMenuChange: (open) => {
3004
4478
  setMenu(open ? { id } : void 0);
3005
4479
  },
3006
- onPin: () => {
3007
- flags.setPinnedSessionIds((ids) => toggleSessionId(ids, id));
3008
- },
3009
4480
  onArchive: () => {
3010
4481
  run("archive", () => archiveSession(id));
3011
4482
  },
@@ -3014,7 +4485,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
3014
4485
  showTip({
3015
4486
  title,
3016
4487
  project: label,
3017
- time: updatedAt === void 0 ? void 0 : formatHoverTime(updatedAt),
4488
+ time: updatedAt === void 0 ? void 0 : formatHoverTime(updatedAt, t),
3018
4489
  left: box.left,
3019
4490
  top: box.top
3020
4491
  });
@@ -3031,7 +4502,14 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
3031
4502
  });
3032
4503
  },
3033
4504
  onSelectAction: (action) => {
3034
- 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);
3035
4513
  }
3036
4514
  }, id);
3037
4515
  })]
@@ -3134,7 +4612,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
3134
4612
  })]
3135
4613
  });
3136
4614
  }
3137
- function ScheduleBrowserTree({ openSession, archiveSession, deleteSession, forkSession, renameSession, useSessions, useSessionPendingInteraction, useWorkspaces, t, openTaskSettings, menu, setMenu }) {
4615
+ function ScheduleBrowserTree({ openSession, archiveSession, deleteSession, forkSession, moveSession, renameSession, useSessions, useSessionPendingInteraction, useWorkspaces, t, openTaskSettings, menu, setMenu }) {
3138
4616
  const sessions = useSessions((state) => state);
3139
4617
  const pendingInteractions = (useSessionPendingInteraction ?? useEmptySessionPendingInteraction)((state) => state);
3140
4618
  const workspaces = useWorkspaces((state) => state);
@@ -3143,7 +4621,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
3143
4621
  const [archiveGroupTarget, setArchiveGroupTarget] = (0, react.useState)();
3144
4622
  const flags = useSessionFlags(sessions.current);
3145
4623
  const { showTip, hideTip, dismissTip } = useHoverDispatch();
3146
- const { busy, error, setError, run } = useBusyAction(() => {
4624
+ const { busy, error, setError, run } = useBusyAction(t, () => {
3147
4625
  setMenu(void 0);
3148
4626
  });
3149
4627
  const dialogs = useSessionDialogs({
@@ -3186,10 +4664,11 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
3186
4664
  updatedAt: session.updatedAt,
3187
4665
  running: session.running === true
3188
4666
  }];
3189
- }));
4667
+ }), t("meta.locale"));
3190
4668
  }, [
3191
4669
  sessions.byId,
3192
4670
  sessions.ids,
4671
+ t,
3193
4672
  workspaces.archivedSessionIds
3194
4673
  ]);
3195
4674
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
@@ -3267,22 +4746,24 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
3267
4746
  }), isExpanded && group.sessions.map((session) => {
3268
4747
  const id = session.id;
3269
4748
  const title = session.title;
3270
- const pinned = flags.pinnedSessionIds.includes(id);
3271
4749
  const unread = flags.unreadSessionIds.includes(id);
3272
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
+ }));
3273
4755
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionRow, {
3274
4756
  id,
3275
4757
  title,
3276
4758
  selected: sessions.current === id,
3277
4759
  menuOpen: menu?.id === id,
3278
- pinned,
3279
4760
  unread,
3280
4761
  running: session.running,
3281
4762
  pendingInteraction,
3282
4763
  t,
3283
4764
  menuItems: sessionMenuItems(t, {
3284
- pinned,
3285
- unread
4765
+ unread,
4766
+ moveTargets
3286
4767
  }),
3287
4768
  menuPoint: menu?.id === id && menu.x !== void 0 && menu.y !== void 0 ? {
3288
4769
  x: menu.x,
@@ -3295,9 +4776,6 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
3295
4776
  onMenuChange: (open) => {
3296
4777
  setMenu(open ? { id } : void 0);
3297
4778
  },
3298
- onPin: () => {
3299
- flags.setPinnedSessionIds((ids) => toggleSessionId(ids, id));
3300
- },
3301
4779
  onArchive: () => {
3302
4780
  run("archive", () => archiveSession(id));
3303
4781
  },
@@ -3306,7 +4784,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
3306
4784
  showTip({
3307
4785
  title,
3308
4786
  project: group.label,
3309
- time: session.updatedAt === void 0 ? void 0 : formatHoverTime(session.updatedAt),
4787
+ time: session.updatedAt === void 0 ? void 0 : formatHoverTime(session.updatedAt, t),
3310
4788
  left: box.left,
3311
4789
  top: box.top
3312
4790
  });
@@ -3323,7 +4801,14 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
3323
4801
  });
3324
4802
  },
3325
4803
  onSelectAction: (action) => {
3326
- 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);
3327
4812
  }
3328
4813
  }, id);
3329
4814
  })]
@@ -3368,14 +4853,13 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
3368
4853
  const target = archiveGroupTarget;
3369
4854
  run("archive-group", async () => {
3370
4855
  const result = await archiveScheduleGroup(target.sessionIds, (id) => archiveSession(id));
3371
- flags.setPinnedSessionIds((ids) => ids.filter((id) => !result.archivedIds.includes(id)));
3372
4856
  flags.setUnreadSessionIds((ids) => ids.filter((id) => !result.archivedIds.includes(id)));
3373
4857
  if (result.failedIds.length > 0) {
3374
4858
  setArchiveGroupTarget({
3375
4859
  ...target,
3376
4860
  sessionIds: result.failedIds
3377
4861
  });
3378
- throw new Error(t("schedule.archiveGroupPartial", {
4862
+ throw new UserFacingError(t("schedule.archiveGroupPartial", {
3379
4863
  archived: result.archivedIds.length,
3380
4864
  failed: result.failedIds.length
3381
4865
  }));
@@ -3409,9 +4893,10 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
3409
4893
  ]
3410
4894
  });
3411
4895
  }
4896
+ const visibleSidebarWidths = /* @__PURE__ */ new WeakMap();
3412
4897
  /** 解析宿主 AppFrame 的 grid-template-columns。 */
3413
4898
  function parseSidebarGrid(value) {
3414
- 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());
3415
4900
  if (match === null) return void 0;
3416
4901
  return {
3417
4902
  sidebar: Number(match[1]),
@@ -3419,41 +4904,41 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
3419
4904
  details: Number(match[3])
3420
4905
  };
3421
4906
  }
3422
- /**
3423
- * Translate the DSH host's 264–420px drag range to Codex's measured
3424
- * 240–520px range, preserving the 280px-host / 275px-Codex default anchor.
3425
- */
3426
- function slimedSidebarWidth(hostWidth, collapsed, viewportWidth = Number.POSITIVE_INFINITY) {
3427
- if (collapsed || hostWidth <= 80) return hostWidth;
3428
- const maximum = Math.max(240, Math.min(520, viewportWidth - 240));
3429
- const source = Math.max(264, Math.min(420, hostWidth));
3430
- if (source <= 280) return 240 + (source - 264) * 35 / 16;
3431
- return 275 + (source - 280) * (maximum - 275) / 140;
3432
- }
3433
- function slimedGridTemplate(tracks, collapsed, viewportWidth = Number.POSITIVE_INFINITY) {
3434
- return `${slimedSidebarWidth(tracks.sidebar, collapsed, viewportWidth)}px ${tracks.middle} ${tracks.details}px`;
3435
- }
3436
4907
  function findSidebarFrame(root) {
3437
4908
  const marked = root.querySelector("[data-sidebar-collapsed]");
3438
4909
  if (marked !== null) return marked;
3439
4910
  for (const node of root.querySelectorAll("div")) if (parseSidebarGrid(node.style.gridTemplateColumns) !== void 0) return node;
3440
4911
  }
3441
- function applySlimSidebar(frame) {
3442
- if (frame.hasAttribute("data-dragging")) {
3443
- frame.removeAttribute("data-dcu-codex-sidebar-grid");
3444
- return false;
3445
- }
4912
+ /** 同步侧栏网格和拖拽柄,保证两者始终使用同一个可见宽度。 */
4913
+ function applySidebarWidth(frame, width) {
3446
4914
  const tracks = parseSidebarGrid(frame.style.gridTemplateColumns);
3447
- if (tracks === void 0) return false;
3448
- const collapsed = frame.hasAttribute("data-sidebar-collapsed");
3449
- const next = slimedGridTemplate(tracks, collapsed, frame.getBoundingClientRect().width);
3450
- if (frame.getAttribute("data-dcu-codex-sidebar-grid") === frame.style.gridTemplateColumns) return false;
3451
- if (frame.style.gridTemplateColumns === next) return false;
3452
- frame.style.gridTemplateColumns = next;
3453
- 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;
3454
4920
  const handle = frame.querySelector("[data-side=\"sidebar\"]");
3455
- if (handle !== null && !collapsed) handle.style.left = `${slimedSidebarWidth(tracks.sidebar, collapsed, frame.getBoundingClientRect().width)}px`;
3456
- 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
+ };
3457
4942
  }
3458
4943
  function observeSlimSidebar() {
3459
4944
  if (typeof document === "undefined" || document.body === null) return () => {};
@@ -3472,7 +4957,8 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
3472
4957
  attributeFilter: [
3473
4958
  "style",
3474
4959
  "data-sidebar-collapsed",
3475
- "data-dragging"
4960
+ "data-dragging",
4961
+ "data-dcu-codex-sidebar-initialized"
3476
4962
  ]
3477
4963
  });
3478
4964
  };
@@ -3481,7 +4967,14 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
3481
4967
  applying = true;
3482
4968
  try {
3483
4969
  if (frame === void 0 || !frame.isConnected) watchFrame(findSidebarFrame(document));
3484
- 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
+ }
3485
4978
  } finally {
3486
4979
  applying = false;
3487
4980
  }
@@ -3509,9 +5002,14 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
3509
5002
  }
3510
5003
  //#endregion
3511
5004
  //#region src/client/sidebar-drag.ts
3512
- /** Codex collapses the sidebar when a resize drag would make it narrower than 240px. */
5005
+ const SIDEBAR_MAX_PX = 420;
5006
+ /** 左移超过开始宽度的一半时收起侧栏,避免轻微拖动误触。 */
3513
5007
  function shouldCollapseOnSidebarDrag(startWidth, startX, endX) {
3514
- 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));
3515
5013
  }
3516
5014
  function isSidebarDragHandle(target) {
3517
5015
  if (target === null || typeof target !== "object" || !("closest" in target)) return false;
@@ -3554,20 +5052,34 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
3554
5052
  const subscribeEmptyCompanionTabs = () => () => {};
3555
5053
  const getEmptyCompanionTabs = () => EMPTY_COMPANION_TABS;
3556
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
+ }
3557
5068
  const stylesheet$3 = `
3558
- .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)}
3559
- .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}}
3560
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}
3561
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}
3562
5073
  .dcu-icon:hover,.dcu-menu button:hover:not(:disabled),.dcu-footer-link:hover{background:var(--dcu-sidebar-hover);color:var(--dcu-sidebar-primary)}
3563
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}
3564
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)}
3565
- .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}
3566
- .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%}
3567
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}
3568
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}
3569
5081
  [role="dialog"][aria-labelledby]{width:min(1000px,calc(100vw - 48px));max-width:calc(100vw - 48px);height:min(800px,calc(100vh - 48px))}
3570
- .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}}
3571
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}}
3572
5084
  html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:has([data-dcu-official-turn-navigator]) .dcu-turn-navigator{display:none}
3573
5085
  [data-dcu-official-turn-navigator]{right:auto!important;left:calc(12px - (var(--dsh-composer-side-clearance) + 16px))!important}
@@ -3883,12 +5395,10 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
3883
5395
  });
3884
5396
  });
3885
5397
  /** Codex 风格的 DSH 侧栏,只替换导航外观,项目浏览和设置仍由 DSH 官方组件提供。 */
3886
- function CodexSidebar({ collapsed, width, openSession, startSession, toggleSidebar, archiveSession, deleteSession, forkSession, renameSession, openPath, companionSlots, renderSlot, t, useSessions, useSessionPendingInteraction, useWorkspaces }) {
5398
+ function CodexSidebar({ collapsed, width, openSession, startSession, toggleSidebar, archiveSession, deleteSession, forkSession, moveSession, renameSession, openPath, companionSlots, renderSlot, t, useSessions, useSessionPendingInteraction, useWorkspaces }) {
3887
5399
  const compact = collapsed || width < 80;
3888
5400
  const [visualCompact, setVisualCompact] = (0, react.useState)(compact);
3889
5401
  const [collapsing, setCollapsing] = (0, react.useState)(false);
3890
- const lastWideWidth = (0, react.useRef)(width >= 80 ? width : 275);
3891
- if (!compact) lastWideWidth.current = width;
3892
5402
  const settingsSeat = (0, react.useRef)(null);
3893
5403
  const search = (0, react.useRef)(null);
3894
5404
  const toggleSidebarRef = (0, react.useRef)(toggleSidebar);
@@ -3896,7 +5406,7 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
3896
5406
  const expandSidebar = (0, react.useCallback)(() => {
3897
5407
  toggleSidebarRef.current();
3898
5408
  }, []);
3899
- const [extensionsOpen, setExtensionsOpen] = (0, react.useState)(true);
5409
+ const [extensionsOpen, setExtensionsOpen] = (0, react.useState)(readExtensionsOpen);
3900
5410
  const [imTab, setImTab] = (0, react.useState)("tasks");
3901
5411
  const companionTabs = (0, react.useSyncExternalStore)(companionSlots?.subscribe ?? subscribeEmptyCompanionTabs, companionSlots?.getSnapshot ?? getEmptyCompanionTabs, companionSlots?.getSnapshot ?? getEmptyCompanionTabs);
3902
5412
  const showChannels = companionTabs.channels;
@@ -3930,42 +5440,77 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
3930
5440
  ]);
3931
5441
  (0, react.useEffect)(() => {
3932
5442
  let startX = 0;
3933
- let startWidth = 275;
5443
+ let startWidth = 240;
3934
5444
  let dragging = false;
3935
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
+ };
3936
5461
  const onDown = (event) => {
3937
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);
3938
5468
  dragging = true;
3939
5469
  pointerId = event.pointerId;
3940
5470
  startX = event.clientX;
3941
- 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/);
3942
- 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));
3943
5484
  };
3944
5485
  const onUp = (event) => {
3945
5486
  if (!dragging || event.pointerId !== pointerId) return;
3946
- dragging = false;
3947
- pointerId = void 0;
3948
- if (!shouldCollapseOnSidebarDrag(startWidth, startX, event.clientX)) return;
3949
- 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(() => {
3950
5492
  window.requestAnimationFrame(() => {
3951
- toggleSidebar();
5493
+ toggleSidebarRef.current();
3952
5494
  });
3953
5495
  });
3954
5496
  };
3955
5497
  const onCancel = (event) => {
3956
5498
  if (event.pointerId !== pointerId) return;
3957
- dragging = false;
3958
- pointerId = void 0;
5499
+ stopHostDrag(event);
5500
+ finishDrag();
3959
5501
  };
3960
5502
  window.addEventListener("pointerdown", onDown, true);
5503
+ window.addEventListener("pointermove", onMove, true);
3961
5504
  window.addEventListener("pointerup", onUp, true);
3962
5505
  window.addEventListener("pointercancel", onCancel, true);
3963
5506
  return () => {
5507
+ finishDrag();
3964
5508
  window.removeEventListener("pointerdown", onDown, true);
5509
+ window.removeEventListener("pointermove", onMove, true);
3965
5510
  window.removeEventListener("pointerup", onUp, true);
3966
5511
  window.removeEventListener("pointercancel", onCancel, true);
3967
5512
  };
3968
- }, [toggleSidebar]);
5513
+ }, []);
3969
5514
  (0, react.useLayoutEffect)(() => {
3970
5515
  if (!compact) {
3971
5516
  setCollapsing(false);
@@ -4001,6 +5546,7 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
4001
5546
  archiveSession,
4002
5547
  deleteSession,
4003
5548
  forkSession,
5549
+ moveSession,
4004
5550
  renameSession,
4005
5551
  openPath,
4006
5552
  useSessions,
@@ -4013,6 +5559,7 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
4013
5559
  deleteSession,
4014
5560
  expandSidebar,
4015
5561
  forkSession,
5562
+ moveSession,
4016
5563
  openPath,
4017
5564
  openSession,
4018
5565
  renameSession,
@@ -4020,10 +5567,8 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
4020
5567
  useSessions,
4021
5568
  useWorkspaces
4022
5569
  ]);
4023
- const sidebarStyle = { "--dcu-sidebar-wide-width": `${slimedSidebarWidth(lastWideWidth.current, false, window.innerWidth)}px` };
4024
5570
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("aside", {
4025
5571
  className: `dcu-root${visualCompact ? " dcu-compact" : ""}${collapsing ? " dcu-collapsing" : ""}`,
4026
- style: sidebarStyle,
4027
5572
  "aria-label": t("sidebar.label"),
4028
5573
  children: [
4029
5574
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: stylesheet$3 }),
@@ -4070,46 +5615,73 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
4070
5615
  },
4071
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")]
4072
5617
  }),
4073
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4074
- type: "button",
4075
- "aria-expanded": extensionsOpen,
4076
- onClick: () => {
4077
- setExtensionsOpen((open) => !open);
4078
- },
4079
- 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")]
4080
- }),
4081
- extensionsOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4082
- className: "dcu-extension-items",
4083
- children: [
4084
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4085
- type: "button",
4086
- onClick: () => {
4087
- selectExternalSection(t("sidebar.experts"));
4088
- },
4089
- 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")]
4090
- }),
4091
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4092
- type: "button",
4093
- onClick: () => {
4094
- selectExternalSection(t("sidebar.skills"));
4095
- },
4096
- 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")]
4097
- }),
4098
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4099
- type: "button",
4100
- onClick: () => {
4101
- selectPluginSection();
4102
- },
4103
- 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")]
4104
- }),
4105
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4106
- type: "button",
4107
- onClick: () => {
4108
- selectSection(t("sidebar.connectors"));
4109
- },
4110
- 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
+ })
4111
5683
  })
4112
- ]
5684
+ })]
4113
5685
  }),
4114
5686
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4115
5687
  type: "button",
@@ -4165,9 +5737,11 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
4165
5737
  archiveSession,
4166
5738
  deleteSession,
4167
5739
  forkSession,
5740
+ moveSession,
4168
5741
  renameSession,
4169
5742
  useSessions,
4170
5743
  useSessionPendingInteraction,
5744
+ useWorkspaces,
4171
5745
  t
4172
5746
  })
4173
5747
  }) : imTab === "schedule" && showSchedule ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
@@ -4177,6 +5751,7 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
4177
5751
  archiveSession,
4178
5752
  deleteSession,
4179
5753
  forkSession,
5754
+ moveSession,
4180
5755
  renameSession,
4181
5756
  useSessions,
4182
5757
  useSessionPendingInteraction,
@@ -4356,7 +5931,22 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
4356
5931
  const endpoint = "/api/michengai/codex-ui/dependencies";
4357
5932
  const stylesheet$2 = `
4358
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}}
4359
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
+ }
4360
5950
  function isDependencyStatus(value) {
4361
5951
  if (value === null || typeof value !== "object") return false;
4362
5952
  const status = value;
@@ -4371,11 +5961,6 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
4371
5961
  const currentPackage = progress.currentPackage;
4372
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");
4373
5963
  }
4374
- function installErrorText(error, t) {
4375
- const message = error instanceof Error ? error.message : "";
4376
- if (message.includes("没有进入当前 Profile") || message.includes("did not enter this profile")) return t("about.installUnchanged");
4377
- return message !== "" ? message : t("about.installFailed");
4378
- }
4379
5964
  function progressLabel(progress, t) {
4380
5965
  if (progress.phase !== null) {
4381
5966
  const current = progress.currentPackage === null || progress.currentPackage === "" ? "" : ` · ${progress.currentPackage}`;
@@ -4537,7 +6122,27 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
4537
6122
  "aria-label": t("about.nav"),
4538
6123
  children: [
4539
6124
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: stylesheet$2 }),
4540
- /* @__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
+ }),
4541
6146
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4542
6147
  className: "dcu-about-intro",
4543
6148
  children: t("about.description")
@@ -4678,7 +6283,7 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
4678
6283
  }
4679
6284
  style.textContent = document.body.hasAttribute("data-ds-dark-theme") ? frameDarkTheme : frameLightTheme;
4680
6285
  }
4681
- function ConnectorMarket({ startPromptSession }) {
6286
+ function ConnectorMarket({ startPromptSession, t }) {
4682
6287
  const frameRef = (0, react.useRef)(null);
4683
6288
  (0, react.useEffect)(() => {
4684
6289
  const onMessage = (event) => {
@@ -4695,9 +6300,9 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
4695
6300
  }, window.location.origin);
4696
6301
  };
4697
6302
  startPromptSession(prompt).then(() => {
4698
- reply(true, "已带入新会话");
6303
+ reply(true, t("connectors.promptReady"));
4699
6304
  }, (error) => {
4700
- reply(false, error instanceof Error ? error.message : String(error));
6305
+ reply(false, userErrorText(error, t));
4701
6306
  });
4702
6307
  };
4703
6308
  const syncTheme = () => {
@@ -4713,12 +6318,12 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
4713
6318
  observer.disconnect();
4714
6319
  window.removeEventListener("message", onMessage);
4715
6320
  };
4716
- }, [startPromptSession]);
6321
+ }, [startPromptSession, t]);
4717
6322
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("iframe", {
4718
6323
  ref: frameRef,
4719
6324
  className: "dcu-connector-frame",
4720
6325
  src: MCP_CONNECTOR_UI,
4721
- title: "MCP连接器",
6326
+ title: t("connectors.title"),
4722
6327
  onLoad: () => {
4723
6328
  syncFrameTheme(frameRef.current);
4724
6329
  }
@@ -4810,7 +6415,10 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
4810
6415
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: stylesheet$1 }), marketAvailable === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4811
6416
  className: "dcu-connector-empty",
4812
6417
  children: t("connectors.loading")
4813
- }) : 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: [
4814
6422
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", { children: t("connectors.title") }),
4815
6423
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("connectors.description") }),
4816
6424
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NativeConnectorList, {
@@ -4854,6 +6462,7 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
4854
6462
  "channel.wecom": "企业微信",
4855
6463
  "channel.qq": "QQ",
4856
6464
  "channel.telegram": "Telegram",
6465
+ "channel.unknown": "频道",
4857
6466
  "schedule.empty": "还没有定时任务运行记录。",
4858
6467
  "schedule.groupActions": "{name} 的任务操作",
4859
6468
  "schedule.taskSettings": "任务设置",
@@ -4871,30 +6480,43 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
4871
6480
  "workspace.pinned": "置顶",
4872
6481
  "workspace.pinnedEmpty": "拖动项目到此处置顶",
4873
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": "管理分组",
4874
6493
  "workspace.recent": "最近",
4875
6494
  "workspace.recentEmpty": "无聊天",
4876
6495
  "workspace.noChat": "没有聊天",
4877
- "workspace.empty": "暂无会话",
6496
+ "workspace.empty": "暂无项目",
4878
6497
  "workspace.newSession": "新建会话",
4879
- "workspace.rename": "编辑项目",
6498
+ "workspace.rename": "重命名项目",
4880
6499
  "workspace.pin": "置顶项目",
4881
6500
  "workspace.unpin": "取消置顶",
6501
+ "workspace.archive": "归档所有会话",
6502
+ "workspace.archiveTitle": "归档 {count} 个会话?",
6503
+ "workspace.archiveDescription": "这会归档“{name}”中的全部会话,项目和文件夹仍会保留。",
6504
+ "workspace.archiveConfirm": "全部归档",
6505
+ "workspace.archivePending": "正在归档会话…",
4882
6506
  "workspace.openPath": "在资源管理器中打开",
4883
- "workspace.delete": "删除项目",
6507
+ "workspace.delete": "移除项目",
4884
6508
  "workspace.deleteDescription": "将把“{name}”从项目列表中移除。文件夹和会话记录会保留。",
4885
- "workspace.deletePending": "正在删除项目…",
6509
+ "workspace.deletePending": "正在移除项目…",
4886
6510
  "workspace.actions": "{name} 的项目操作",
4887
6511
  "workspace.taskCount": "{count} 个任务",
4888
- "workspace.edit": "编辑项目",
6512
+ "workspace.taskSummary": "{count} 个任务 · {unreadCount} 条未读",
6513
+ "workspace.edit": "重命名项目",
4889
6514
  "sessions.close": "关闭",
4890
6515
  "sessions.cancel": "取消",
4891
6516
  "sessions.save": "保存",
4892
6517
  "sessions.failed": "操作失败:{message}",
4893
6518
  "sessions.unknown": "找不到该会话。",
4894
6519
  "sessions.actions": "{name} 的会话操作",
4895
- "sessions.pin": "置顶会话",
4896
- "sessions.unpin": "取消置顶",
4897
- "sessions.pinned": "已置顶",
4898
6520
  "sessions.markUnread": "标记为未读",
4899
6521
  "sessions.markRead": "标记为已读",
4900
6522
  "sessions.unread": "未读",
@@ -4903,17 +6525,33 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
4903
6525
  "sessions.planReview": "计划待审",
4904
6526
  "sessions.rename": "重命名会话",
4905
6527
  "sessions.renameDescription": "保持简短且易于识别",
6528
+ "sessions.renameInvalid": "会话名称无效,请修改后重试。",
6529
+ "sessions.renameFailed": "暂时无法重命名该会话,请稍后重试。",
4906
6530
  "sessions.archive": "归档会话",
6531
+ "sessions.archiveFailed": "暂时无法归档该会话,请稍后重试。",
4907
6532
  "sessions.delete": "删除会话",
4908
6533
  "sessions.deleteDescription": "将永久删除会话“{name}”及其全部记录,此操作不可恢复。",
4909
6534
  "sessions.deletePending": "正在删除会话…",
4910
6535
  "sessions.deleteUnavailable": "未安装或无法连接归档管理插件。",
6536
+ "sessions.deleteFailed": "暂时无法删除该会话,请稍后重试。",
4911
6537
  "sessions.fork": "在新会话中继续",
6538
+ "sessions.forkUnavailable": "当前会话暂时无法在新会话中继续,请等待本轮结束后重试。",
6539
+ "sessions.forkFailed": "暂时无法创建新会话,请稍后重试。",
4912
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": "正在移动会话…",
4913
6552
  "sessions.copyPath": "复制工作目录",
4914
6553
  "sessions.copyTitle": "复制会话标题",
4915
6554
  "sessions.copyId": "复制会话 ID",
4916
- "sessions.copyLink": "复制会话链接",
4917
6555
  "connectors.title": "连接器",
4918
6556
  "connectors.description": "显示当前会话可用的 MCP 连接器及其工具,不显示地址、命令或凭证。",
4919
6557
  "connectors.openSession": "请先打开一个会话以读取连接器。",
@@ -4921,8 +6559,17 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
4921
6559
  "connectors.failed": "连接器目录暂不可用,请稍后重试。",
4922
6560
  "connectors.toolCount": "{count} 个工具",
4923
6561
  "connectors.empty": "当前会话没有可用的 MCP 连接器。",
4924
- "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",
4925
6570
  "about.title": "Codex UI",
6571
+ "about.viewProject": "GitHub",
6572
+ "about.feedback": "问题反馈",
4926
6573
  "about.description": "为 DSH 提供 Codex 风格的侧栏、工作区会话管理、搜索与会话轮次导航。",
4927
6574
  "about.features": "基本功能",
4928
6575
  "about.feature.sidebar": "Codex 风格侧栏、全局会话搜索与设置入口",
@@ -4946,6 +6593,12 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
4946
6593
  "about.upToDate": "所有配套插件均已安装并为最新版本。",
4947
6594
  "about.installFailed": "依赖安装失败,请稍后重试。",
4948
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 依赖管理服务尚未就绪,请重启后重试。",
4949
6602
  "about.restartRequired": "正在热更新插件,窗口会自动刷新。",
4950
6603
  "about.restartManually": "安装完成。当前 Web 无法自动重启,请重启 DSH Web 后刷新页面。",
4951
6604
  "about.progressHint": "正在安装插件…",
@@ -4965,7 +6618,17 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
4965
6618
  "about.dependency.market": "插件市场",
4966
6619
  "turns.label": "当前会话轮次导航",
4967
6620
  "turns.untitled": "未命名提问",
4968
- "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"
4969
6632
  };
4970
6633
  const en = {
4971
6634
  "sidebar.label": "DSH navigation",
@@ -4997,6 +6660,7 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
4997
6660
  "channel.wecom": "WeCom",
4998
6661
  "channel.qq": "QQ",
4999
6662
  "channel.telegram": "Telegram",
6663
+ "channel.unknown": "Channel",
5000
6664
  "schedule.empty": "No scheduled-task runs yet.",
5001
6665
  "schedule.groupActions": "Task actions for {name}",
5002
6666
  "schedule.taskSettings": "Task settings",
@@ -5012,32 +6676,45 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
5012
6676
  "search.actions": "Quick actions",
5013
6677
  "workspace.label": "Workspace conversations",
5014
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",
5015
6689
  "workspace.recent": "Recents",
5016
6690
  "workspace.recentEmpty": "No chats",
5017
6691
  "workspace.noChat": "No chats",
5018
- "workspace.empty": "No conversations",
6692
+ "workspace.empty": "No projects",
5019
6693
  "workspace.newSession": "New conversation",
5020
- "workspace.rename": "Edit project",
6694
+ "workspace.rename": "Rename project",
5021
6695
  "workspace.pin": "Pin project",
5022
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…",
5023
6702
  "workspace.pinned": "Pinned",
5024
6703
  "workspace.pinnedEmpty": "Drag a project here to pin it",
5025
6704
  "workspace.openPath": "Open in file explorer",
5026
- "workspace.delete": "Delete project",
6705
+ "workspace.delete": "Remove project",
5027
6706
  "workspace.deleteDescription": "This removes “{name}” from the project list. The folder and conversation records remain.",
5028
- "workspace.deletePending": "Deleting project…",
6707
+ "workspace.deletePending": "Removing project…",
5029
6708
  "workspace.actions": "Project actions for {name}",
5030
6709
  "workspace.taskCount": "{count} tasks",
5031
- "workspace.edit": "Edit project",
6710
+ "workspace.taskSummary": "{count} tasks · {unreadCount} unread",
6711
+ "workspace.edit": "Rename project",
5032
6712
  "sessions.close": "Close",
5033
6713
  "sessions.cancel": "Cancel",
5034
6714
  "sessions.save": "Save",
5035
6715
  "sessions.failed": "Action failed: {message}",
5036
6716
  "sessions.unknown": "Conversation not found.",
5037
6717
  "sessions.actions": "Conversation actions for {name}",
5038
- "sessions.pin": "Pin conversation",
5039
- "sessions.unpin": "Unpin",
5040
- "sessions.pinned": "Pinned",
5041
6718
  "sessions.markUnread": "Mark as unread",
5042
6719
  "sessions.markRead": "Mark as read",
5043
6720
  "sessions.unread": "Unread",
@@ -5046,17 +6723,33 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
5046
6723
  "sessions.planReview": "Plan awaiting review",
5047
6724
  "sessions.rename": "Rename conversation",
5048
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.",
5049
6728
  "sessions.archive": "Archive conversation",
6729
+ "sessions.archiveFailed": "The conversation could not be archived. Try again later.",
5050
6730
  "sessions.delete": "Delete conversation",
5051
6731
  "sessions.deleteDescription": "This permanently deletes “{name}” and all of its records. This cannot be undone.",
5052
6732
  "sessions.deletePending": "Deleting conversation…",
5053
6733
  "sessions.deleteUnavailable": "The archive manager plugin is not installed or unavailable.",
6734
+ "sessions.deleteFailed": "The conversation could not be deleted. Try again later.",
5054
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.",
5055
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…",
5056
6750
  "sessions.copyPath": "Copy working directory",
5057
6751
  "sessions.copyTitle": "Copy conversation title",
5058
6752
  "sessions.copyId": "Copy conversation ID",
5059
- "sessions.copyLink": "Copy conversation link",
5060
6753
  "connectors.title": "Connectors",
5061
6754
  "connectors.description": "Shows MCP connectors and tools available to this session without exposing addresses, commands, or credentials.",
5062
6755
  "connectors.openSession": "Open a conversation to load connectors.",
@@ -5064,8 +6757,17 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
5064
6757
  "connectors.failed": "Connectors are unavailable. Try again shortly.",
5065
6758
  "connectors.toolCount": "{count} tools",
5066
6759
  "connectors.empty": "This conversation has no available MCP connectors.",
5067
- "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",
5068
6768
  "about.title": "Codex UI",
6769
+ "about.viewProject": "GitHub",
6770
+ "about.feedback": "Issues",
5069
6771
  "about.description": "Adds a Codex-style sidebar, workspace conversation management, search, and turn navigation to DSH.",
5070
6772
  "about.features": "Core features",
5071
6773
  "about.feature.sidebar": "Codex-style sidebar, global conversation search, and settings entry points",
@@ -5089,6 +6791,12 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
5089
6791
  "about.upToDate": "All companion plugins are installed and up to date.",
5090
6792
  "about.installFailed": "Dependency installation failed. Try again later.",
5091
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.",
5092
6800
  "about.restartRequired": "Updating plugins now. The window will refresh automatically.",
5093
6801
  "about.restartManually": "Installation complete. This Web host cannot restart itself; restart DSH Web, then refresh the page.",
5094
6802
  "about.progressHint": "Installing plugin…",
@@ -5108,7 +6816,17 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
5108
6816
  "about.dependency.market": "Plugin Market",
5109
6817
  "turns.label": "Conversation turn navigation",
5110
6818
  "turns.untitled": "Untitled prompt",
5111
- "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"
5112
6830
  };
5113
6831
  //#endregion
5114
6832
  //#region src/client/host-open-path.ts
@@ -5153,7 +6871,7 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
5153
6871
  [["定时任务", "Scheduled tasks"], "schedule"],
5154
6872
  [["IM助理", "IM Assistant"], "assistant"],
5155
6873
  [["已归档", "Archived"], "archive"],
5156
- [["关于", "About"], "about"]
6874
+ [["Codex UI"], "about"]
5157
6875
  ];
5158
6876
  /** 按设置导航可见文案解析侧栏同款图标;插件市场复用插件图标。 */
5159
6877
  function settingsNavIconId(label) {
@@ -5516,8 +7234,8 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
5516
7234
  }
5517
7235
  function turnLinks(snapshot, fallback) {
5518
7236
  const links = [];
5519
- for (const key of snapshot.chat.order) {
5520
- const node = snapshot.chat.nodes.get(key);
7237
+ for (const key of snapshot.order) {
7238
+ const node = snapshot.nodes.get(key);
5521
7239
  if (node?.kind !== "user") continue;
5522
7240
  links.push({
5523
7241
  key: node.key,
@@ -5529,14 +7247,21 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
5529
7247
  function equalTurns(left, right) {
5530
7248
  return left.length === right.length && left.every((turn, index) => turn.key === right[index]?.key && turn.summary === right[index]?.summary);
5531
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
+ }
5532
7256
  function railLeftFromSidebar() {
5533
7257
  const sidebar = document.querySelector(".dcu-root");
5534
7258
  if (sidebar === null) return 288;
5535
7259
  return Math.round(sidebar.getBoundingClientRect().right) + 16;
5536
7260
  }
5537
7261
  /** 当前会话的轮次导航;只读取原生聊天锚点并滚动,不改写会话数据或消息视图。 */
5538
- function TurnNavigator({ useSession, t }) {
5539
- 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);
5540
7265
  const [current, setCurrent] = (0, react.useState)(turns[0]?.key ?? null);
5541
7266
  const [hoverAt, setHoverAt] = (0, react.useState)(null);
5542
7267
  const [railLeft, setRailLeft] = (0, react.useState)(288);
@@ -5696,8 +7421,13 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
5696
7421
  function hasDeleteSession(value) {
5697
7422
  return value !== null && typeof value === "object" && "deleteSession" in value && typeof value.deleteSession === "function";
5698
7423
  }
5699
- function hasStartSession(value) {
5700
- 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
+ }
5701
7431
  }
5702
7432
  /** Archive Manager replaces the official ui-workspace row with this optional service. */
5703
7433
  function startWorkspaceSession(ctx, workspaceId) {
@@ -5706,7 +7436,11 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
5706
7436
  uiWorkspace.startSession(workspaceId);
5707
7437
  return;
5708
7438
  }
5709
- ctx.workspaces.startSession(workspaceId);
7439
+ if (hasStartSession(ctx.workspaces)) {
7440
+ ctx.workspaces.startSession(workspaceId);
7441
+ return;
7442
+ }
7443
+ console.warn("DSH 工作空间服务尚未就绪,无法新建会话。");
5710
7444
  }
5711
7445
  /** 替换 DSH 的官方 sidebar 插槽,不修改 DSH 源码或会话数据。 */
5712
7446
  function apply(ctx) {
@@ -5759,9 +7493,10 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
5759
7493
  toggleSidebar: () => {
5760
7494
  ctx.layout.toggleSidebar();
5761
7495
  },
5762
- archiveSession: (sessionId) => ctx.workspaces.archiveSession(sessionId),
7496
+ archiveSession,
5763
7497
  deleteSession,
5764
7498
  forkSession,
7499
+ moveSession,
5765
7500
  renameSession,
5766
7501
  openPath,
5767
7502
  companionSlots
@@ -5774,36 +7509,65 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
5774
7509
  locale: NS
5775
7510
  }, TurnNavigator));
5776
7511
  const forkSession = async (sessionId) => {
5777
- const childId = await ctx.sessions.fork({
5778
- sessionId,
5779
- 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);
5780
7518
  });
5781
- ctx.sessions.open(childId);
5782
7519
  };
5783
7520
  const renameSession = async (sessionId, title) => {
5784
7521
  const session = ctx.sessions.binding(sessionId)?.session;
5785
- if (session === void 0) throw new Error(t("sessions.unknown"));
5786
- const result = await session.rename(title);
5787
- 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
+ });
5788
7527
  };
5789
7528
  const deleteSession = async (sessionId) => {
5790
7529
  const registry = ctx.get("remote.workspaceRegistry");
5791
- if (!hasDeleteSession(registry)) throw new Error(t("sessions.deleteUnavailable"));
5792
- const result = await registry.deleteSession(sessionId);
5793
- 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
+ });
5794
7551
  };
5795
7552
  const startConnectorPromptSession = async (promptText) => {
5796
7553
  const prompt = promptText.trim();
5797
- if (prompt === "") throw new Error("Prompt 不能为空");
7554
+ if (prompt === "") throw new UserFacingError(t("connectors.promptRequired"));
5798
7555
  const workspaces = ctx.workspaces.list.getSnapshot();
5799
- const currentSessionId = ctx.sessions.list.getSnapshot().current;
5800
- const targetWorkspaceId = (currentSessionId === void 0 ? void 0 : workspaces.items.find((workspace) => workspace.sessionIds.includes(currentSessionId))?.workspaceId) ?? workspaces.recentWorkspaceId;
5801
- if (targetWorkspaceId === void 0) throw new Error("请先选择一个工作空间,再使用示例 Prompt");
5802
- 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);
5803
7567
  const conversation = ctx.get("conversation");
5804
- if (conversation === void 0) throw new Error("DSH 对话服务尚未就绪,请稍后重试");
7568
+ if (conversation === void 0) throw new UserFacingError(t("connectors.conversationUnavailable"));
5805
7569
  const binding = ctx.sessions.binding(sessionId);
5806
- if (binding === void 0) throw new Error("新会话尚未就绪,请稍后重试");
7570
+ if (binding === void 0) throw new UserFacingError(t("connectors.sessionPending"));
5807
7571
  conversation.input.for(binding.ctx).setDraft(prompt);
5808
7572
  ctx.sessions.open(sessionId);
5809
7573
  };
@@ -5812,10 +7576,11 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
5812
7576
  priority: -1,
5813
7577
  locale: NS,
5814
7578
  inject: () => ({
5815
- archiveSession: (sessionId) => ctx.workspaces.archiveSession(sessionId),
7579
+ archiveSession,
5816
7580
  deleteSession,
5817
7581
  deleteWorkspace: (workspaceId) => ctx.workspaces.delete(workspaceId),
5818
7582
  forkSession,
7583
+ moveSession,
5819
7584
  openPath,
5820
7585
  openSession: (sessionId) => {
5821
7586
  ctx.sessions.open(sessionId);