@michengai/dsh-archive-manager 0.1.22 → 0.1.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,20 @@
4
4
 
5
5
  Published release notes are retained below; new versions are added without removing earlier entries.
6
6
 
7
+ ## 0.1.24 — 2026-09-02
8
+
9
+ - Upgraded the development test stack to DSH `0.1.2-alpha.5` and `@deepseek-ai/cordis` `4.0.2`, while retaining the existing `>=0.1.0-rc.5 <0.2.0` DSH runtime peer declarations.
10
+ - Switched primary client integration tests to the current `dsh-client-store` contract and retained a tested optional `dsh-client-runtime` fallback for older hosts.
11
+ - Added a reproducible pnpm alpha-resolution policy that disables automatic peer installation and records the approved newly published packages.
12
+
13
+ ## 0.1.23 — 2026-09-01
14
+
15
+ - Fixed missing sidebar pending-state indicators on DSH `v0.1.2-alpha.2`: workspace rows now read pending UI interactions from the `ui-session` `pendingInteractions` Map.
16
+ - Whitelisted the official visible pending states (`question`, `approval`, and `plan-review`) before passing them to row rendering, so unknown future states are ignored safely.
17
+ - Kept the legacy `SessionSummary.pendingInteraction` fallback and added regression coverage for grouped, flat, and search result rows.
18
+
19
+ Published package: [`@michengai/dsh-archive-manager@0.1.23`](https://www.npmjs.com/package/@michengai/dsh-archive-manager/v/0.1.23).
20
+
7
21
  ## 0.1.22 — 2026-09-01
8
22
 
9
23
  - Added arbitrary archived-chat selection in **Settings → Archived**, including per-row checkboxes, select-all for the current filtered results, cross-project selection, batch restore, and confirmed batch permanent deletion.
@@ -4,6 +4,20 @@
4
4
 
5
5
  以下发布说明会持续保留;新增版本时不再删除较早记录。
6
6
 
7
+ ## 0.1.24 — 2026-09-02
8
+
9
+ - 将开发测试依赖升级至 DSH `0.1.2-alpha.5` 与 `@deepseek-ai/cordis` `4.0.2`,同时保留原有的 DSH 运行时 peer 声明 `>=0.1.0-rc.5 <0.2.0`。
10
+ - 客户端集成测试以当前 `dsh-client-store` 为主路径,并保留经过测试的可选 `dsh-client-runtime` 回退,兼容旧版宿主。
11
+ - 新增可复现的 pnpm alpha 解析策略:关闭自动安装 peer,并记录已批准的刚发布依赖。
12
+
13
+ ## 0.1.23 — 2026-09-01
14
+
15
+ - 修复 DSH `v0.1.2-alpha.2` 后会话侧栏“等待回答 / 等待审批”状态点不显示的问题:侧栏行现在从 `ui-session` 的 `pendingInteractions` Map 读取待处理交互状态。
16
+ - 对 `question`、`approval`、`plan-review` 三种官方可见状态做白名单透传,未知状态会被忽略,避免触发行渲染断言。
17
+ - 保留旧版 `SessionSummary.pendingInteraction` 兜底,并补充分组、扁平列表和搜索结果三条路径的回归测试。
18
+
19
+ 已发布包:[`@michengai/dsh-archive-manager@0.1.23`](https://www.npmjs.com/package/@michengai/dsh-archive-manager/v/0.1.23)。
20
+
7
21
  ## 0.1.22 — 2026-09-01
8
22
 
9
23
  - 在「设置 → 已归档」新增任意多选:每行复选框、全选当前筛选结果、跨项目保留选择、批量恢复和确认后的批量永久删除。
package/lib/client.js CHANGED
@@ -284,6 +284,10 @@ window.__ModuleLoader__.load({
284
284
  }
285
285
  });
286
286
  }
287
+ /** GitHub 品牌标识未由宿主图标库提供,内联以保持离线可用和主题适配。 */
288
+ function GithubMark16() {
289
+ return (0, react_jsx_runtime.jsx)("svg", { viewBox: "0 0 16 16", width: 16, height: 16, "aria-hidden": true, focusable: "false", children: (0, react_jsx_runtime.jsx)("path", { fill: "currentColor", 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" }) });
290
+ }
287
291
  /**
288
292
  * 归档多选原型。保留既有设置页作为兼容回退,原型直接使用相同宿主
289
293
  * 批量接口,因此其恢复、删除和并发安全语义与原有项目级操作一致。
@@ -423,10 +427,10 @@ window.__ModuleLoader__.load({
423
427
  return (0, react_jsx_runtime.jsxs)("section", {
424
428
  className: "dsham_settings",
425
429
  "aria-label": t("archives.title"),
426
- children: [(0, react_jsx_runtime.jsx)("style", { children: ARCHIVE_SETTINGS_CSS + ARCHIVE_SETTINGS_BATCH_CSS + ARCHIVE_SETTINGS_SELECTION_CSS }), (0, react_jsx_runtime.jsxs)("header", {
430
+ children: [(0, react_jsx_runtime.jsx)("style", { children: ARCHIVE_SETTINGS_CSS + ARCHIVE_SETTINGS_BATCH_CSS + ARCHIVE_SETTINGS_EXTERNAL_LINK_CSS + ARCHIVE_SETTINGS_SELECTION_CSS + ARCHIVE_SETTINGS_DELETE_CONFIRM_CSS }), (0, react_jsx_runtime.jsxs)("header", {
427
431
  className: "dsham_settingsHeader",
428
432
  children: [(0, react_jsx_runtime.jsxs)("div", {
429
- children: [(0, react_jsx_runtime.jsx)("h2", { children: t("archives.title") }), (0, react_jsx_runtime.jsx)("p", { className: "dsham_settingsIntro", children: t("archives.description") })]
433
+ children: [(0, react_jsx_runtime.jsxs)("div", { className: "dsham_settingsTitleRow", children: [(0, react_jsx_runtime.jsx)("h2", { children: t("archives.title") }), (0, react_jsx_runtime.jsxs)("div", { className: "dsham_settingsLinks", children: [(0, react_jsx_runtime.jsxs)("a", { className: "dsham_settingsExternalLink", href: "https://github.com/MichengAI/dsh-archive-manager", target: "_blank", rel: "noreferrer", "aria-label": t("archives.viewProject"), children: [(0, react_jsx_runtime.jsx)(GithubMark16, {}), t("archives.viewProject")] }), (0, react_jsx_runtime.jsxs)("a", { className: "dsham_settingsExternalLink", href: "https://github.com/MichengAI/dsh-archive-manager/issues", target: "_blank", rel: "noreferrer", "aria-label": t("archives.feedback"), children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconListPenOutline16, {}), t("archives.feedback")] })] })] }), (0, react_jsx_runtime.jsx)("p", { className: "dsham_settingsIntro", children: t("archives.description") })]
430
434
  }), (0, react_jsx_runtime.jsxs)("div", {
431
435
  className: "dsham_settingsHeaderActions",
432
436
  children: [(0, react_jsx_runtime.jsx)("button", { type: "button", className: "dsham_settingsRestoreAll", disabled: busy || allBatchSessionIds.length === 0, onClick: () => onBatchUnarchive(allBatchTarget), children: t("archives.restoreAll") }), (0, react_jsx_runtime.jsxs)("button", { type: "button", className: "dsham_settingsDanger", disabled: busy || allBatchSessionIds.length === 0, onClick: () => setDeleteTarget({ kind: "batch", target: allBatchTarget, title: t("archives.allProjects"), count: allBatchSessionIds.length }), children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, {}), t("archives.deleteAll")] })]
@@ -468,7 +472,7 @@ window.__ModuleLoader__.load({
468
472
  closeLabel: t("close"),
469
473
  title: deleteDialogTitle,
470
474
  ...deleteDialogDescription === void 0 ? {} : { description: deleteDialogDescription },
471
- footer: (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, { variant: "outline", disabled: busy, onClick: closeDelete, children: t("cancel") }), (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, { variant: "outline", disabled: busy, onClick: confirmDelete, children: deleteConfirmLabel })] }),
475
+ footer: (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, { variant: "outline", disabled: busy, onClick: closeDelete, children: t("cancel") }), (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, { variant: "outline", className: "dsham_settingsDeleteConfirm", disabled: busy, onClick: confirmDelete, children: deleteConfirmLabel })] }),
472
476
  children: busy && (0, react_jsx_runtime.jsx)("div", { role: "status", children: deleteTarget?.kind === "batch" ? t("archives.deleteBatchPending") : t("deleteSession.pending") })
473
477
  })]
474
478
  });
@@ -496,8 +500,8 @@ window.__ModuleLoader__.load({
496
500
  * @param cwd - directory path, or undefined for the ungrouped bucket.
497
501
  * @returns basename, the raw cwd when it has no basename, or the ungrouped label.
498
502
  */
499
- function workspaceLabel(cwd) {
500
- if (cwd === void 0 || cwd === "") return UNGROUPED_LABEL;
503
+ function workspaceLabel(cwd, ungroupedLabel = UNGROUPED_LABEL) {
504
+ if (cwd === void 0 || cwd === "") return ungroupedLabel;
501
505
  const base = cwd.replace(/[/\\]+$/, "").split(/[/\\]/).pop();
502
506
  return base !== void 0 && base !== "" ? base : cwd;
503
507
  }
@@ -603,7 +607,26 @@ window.__ModuleLoader__.load({
603
607
  if (stray.length > 0) groups.push(buildGroup("", void 0, void 0, void 0, UNGROUPED_LABEL, ungroupedOrder === void 0 ? stray : orderedUngrouped(stray, ungroupedOrder), ungroupedOrder === void 0 ? "recency" : "account"));
604
608
  return groups;
605
609
  }
606
- function sessionNode(s, descendants, archived) {
610
+ /** 只把侧栏已支持的待处理交互类型交给行渲染,避免未知类型触发断言。 */
611
+ function visiblePendingKind(kind) {
612
+ switch (kind) {
613
+ case "approval":
614
+ case "plan-review":
615
+ case "question": return kind;
616
+ default: return;
617
+ }
618
+ }
619
+ function pendingInteractionForSession(session, pendingInteractions) {
620
+ const entry = pendingInteractions.get(session.id);
621
+ const kind = entry === void 0 ? session.pendingInteraction : entry.kind;
622
+ return visiblePendingKind(kind);
623
+ }
624
+ const EMPTY_PENDING_INTERACTIONS = /* @__PURE__ */ new Map();
625
+ function useEmptySessionPendingInteraction(selector) {
626
+ return selector(EMPTY_PENDING_INTERACTIONS);
627
+ }
628
+ function sessionNode(s, descendants, archived, pendingInteractions) {
629
+ const pendingInteraction = pendingInteractionForSession(s, pendingInteractions);
607
630
  return {
608
631
  id: s.id,
609
632
  title: sessionTitle(s),
@@ -613,7 +636,7 @@ window.__ModuleLoader__.load({
613
636
  completed: s.completed === true,
614
637
  updatedAt: s.updatedAt,
615
638
  archived: archived.has(s.id),
616
- ...s.pendingInteraction === void 0 ? {} : { pendingInteraction: s.pendingInteraction }
639
+ ...pendingInteraction === void 0 ? {} : { pendingInteraction }
617
640
  };
618
641
  }
619
642
  /**
@@ -628,10 +651,11 @@ window.__ModuleLoader__.load({
628
651
  * @param list - sessions list snapshot (`current` feeds containsCurrent).
629
652
  * @param workspaces - real workspaces in stable Host order.
630
653
  * @param archivedSessionIds - registry-global archive set.
654
+ * @param pendingInteractions - pending UI interactions by Session.
631
655
  * @param view - local expansion arrays and the show-archived toggle.
632
656
  * @returns group sections in render order.
633
657
  */
634
- function deriveGroups(list, workspaces, archivedSessionIds, view) {
658
+ function deriveGroups(list, workspaces, archivedSessionIds, pendingInteractions, view) {
635
659
  const archived = new Set(archivedSessionIds);
636
660
  const expandedGroups = new Set(view.expandedGroups);
637
661
  const descendants = indexSubagentDescendants(list.byId);
@@ -648,7 +672,7 @@ window.__ModuleLoader__.load({
648
672
  sessionCount: g.sessions.length,
649
673
  expanded,
650
674
  containsCurrent: g.key === currentGroup,
651
- sessions: expanded ? g.sessions.map((session) => sessionNode(session, descendants, archived)) : []
675
+ sessions: expanded ? g.sessions.map((session) => sessionNode(session, descendants, archived, pendingInteractions)) : []
652
676
  });
653
677
  }
654
678
  return groups;
@@ -662,10 +686,11 @@ window.__ModuleLoader__.load({
662
686
  * (see {@link deriveSearchResults}).
663
687
  * @param list - sessions list snapshot.
664
688
  * @param archivedSessionIds - registry-global archive set.
689
+ * @param pendingInteractions - pending UI interactions by Session.
665
690
  * @param showArchived - "show archived" view toggle.
666
691
  * @returns flat rows in render order.
667
692
  */
668
- function deriveFlat(list, archivedSessionIds, showArchived) {
693
+ function deriveFlat(list, archivedSessionIds, pendingInteractions, showArchived) {
669
694
  const archived = new Set(archivedSessionIds);
670
695
  const descendants = indexSubagentDescendants(list.byId);
671
696
  const rows = [];
@@ -675,7 +700,7 @@ window.__ModuleLoader__.load({
675
700
  rows.push(s);
676
701
  }
677
702
  rows.sort(byRecency);
678
- return rows.map((session) => sessionNode(session, descendants, archived));
703
+ return rows.map((session) => sessionNode(session, descendants, archived, pendingInteractions));
679
704
  }
680
705
  /**
681
706
  * Merge immediate title/Workspace substring matches with ranked Host content
@@ -686,12 +711,14 @@ window.__ModuleLoader__.load({
686
711
  * @param workspaces - Workspace membership and display labels.
687
712
  * @param query - caller text; surrounding whitespace is ignored.
688
713
  * @param archivedSessionIds - registry-global archive set.
714
+ * @param pendingInteractions - pending UI interactions by Session.
689
715
  * @param content - ranked Host content-search page.
690
716
  * @param limit - protocol-owned maximum merged row count.
691
717
  * @param showArchived - "show archived" view toggle.
718
+ * @param ungroupedLabel - 未归属工作区会话的本地化回退标签。
692
719
  * @returns bounded deduplicated flat rows and a refine-query hint bit.
693
720
  */
694
- function deriveSearchResults(list, workspaces, query, archivedSessionIds, content, limit, showArchived) {
721
+ function deriveSearchResults(list, workspaces, query, archivedSessionIds, pendingInteractions, content, limit, showArchived, ungroupedLabel = UNGROUPED_LABEL) {
695
722
  const q = query.trim().toLowerCase();
696
723
  if (q === "") return {
697
724
  items: [],
@@ -701,7 +728,7 @@ window.__ModuleLoader__.load({
701
728
  const descendants = indexSubagentDescendants(list.byId);
702
729
  const workspaceBySession = /* @__PURE__ */ new Map();
703
730
  for (const workspace of workspaces) for (const sessionId of workspace.sessionIds) if (!workspaceBySession.has(sessionId)) workspaceBySession.set(sessionId, workspace.title);
704
- const labelOf = (summary) => workspaceBySession.get(summary.id) ?? workspaceLabel(summary.cwd);
731
+ const labelOf = (summary) => workspaceBySession.get(summary.id) ?? workspaceLabel(summary.cwd, ungroupedLabel);
705
732
  const contentBySession = /* @__PURE__ */ new Map();
706
733
  for (const item of content.items) if (!contentBySession.has(item.sessionId)) contentBySession.set(item.sessionId, item);
707
734
  const local = [];
@@ -726,6 +753,7 @@ window.__ModuleLoader__.load({
726
753
  return {
727
754
  items: ordered.slice(0, limit).map((summary) => {
728
755
  const match = contentBySession.get(summary.id);
756
+ const pendingInteraction = pendingInteractionForSession(summary, pendingInteractions);
729
757
  return {
730
758
  id: summary.id,
731
759
  title: sessionTitle(summary),
@@ -733,7 +761,7 @@ window.__ModuleLoader__.load({
733
761
  running: summary.running,
734
762
  runningSubagentCount: descendants.get(summary.id)?.runningCount ?? 0,
735
763
  archived: archived.has(summary.id),
736
- ...summary.pendingInteraction === void 0 ? {} : { pendingInteraction: summary.pendingInteraction },
764
+ ...pendingInteraction === void 0 ? {} : { pendingInteraction },
737
765
  completed: summary.completed === true,
738
766
  ...match === void 0 ? {} : { snippet: match.snippet }
739
767
  };
@@ -1713,8 +1741,9 @@ window.__ModuleLoader__.load({
1713
1741
  return e.clientY < rect.top + rect.height / 2 ? "before" : "after";
1714
1742
  }
1715
1743
  /** The scrolling session tree; unmounting drops the sessions subscription and expand-all state. */
1716
- function SessionTree({ useSessions, startSession, open, forkSession, workspaces, archivedSessionIds, showArchived, onRenameRequest, onDeleteRequest, onSessionRename, onSessionArchive, onSessionUnarchive, onSessionDelete, insertWorkspaceBefore, insertSessionBefore, orderBy, groupExpansion, setGroupExpanded, sessionOrderByAccount, sessionUpdatedAtByAccount, syncSessionOrderAccount, setSessionOrder, t }) {
1744
+ function SessionTree({ useSessions, useSessionPendingInteraction, startSession, open, forkSession, workspaces, archivedSessionIds, showArchived, onRenameRequest, onDeleteRequest, onSessionRename, onSessionArchive, onSessionUnarchive, onSessionDelete, insertWorkspaceBefore, insertSessionBefore, orderBy, groupExpansion, setGroupExpanded, sessionOrderByAccount, sessionUpdatedAtByAccount, syncSessionOrderAccount, setSessionOrder, t }) {
1717
1745
  const list = useSessions((s) => s);
1746
+ const pendingInteractions = useSessionPendingInteraction((s) => s);
1718
1747
  const current = list.current;
1719
1748
  const [expandedSessionGroups, setExpandedSessionGroups] = (0, react.useState)([]);
1720
1749
  const [drag, setDrag] = (0, react.useState)(null);
@@ -1781,7 +1810,7 @@ window.__ModuleLoader__.load({
1781
1810
  });
1782
1811
  }, [sessionOrderByAccount, workspaces]);
1783
1812
  const orderedUngroupedSessionIds = (0, react.useMemo)(() => reconciledSessionOrder(ungroupedSessionIds, sessionOrderByAccount[""]), [sessionOrderByAccount, ungroupedSessionIds]);
1784
- const groups = (0, react.useMemo)(() => deriveGroups(list, orderedWorkspaces, archivedSessionIds, {
1813
+ const groups = (0, react.useMemo)(() => deriveGroups(list, orderedWorkspaces, archivedSessionIds, pendingInteractions, {
1785
1814
  expandedGroups,
1786
1815
  showArchived,
1787
1816
  ...sessionOrderByAccount[""] === void 0 ? {} : { ungroupedOrder: sessionOrderByAccount[""] }
@@ -1789,6 +1818,7 @@ window.__ModuleLoader__.load({
1789
1818
  list,
1790
1819
  orderedWorkspaces,
1791
1820
  archivedSessionIds,
1821
+ pendingInteractions,
1792
1822
  showArchived,
1793
1823
  expandedGroups,
1794
1824
  sessionOrderByAccount
@@ -1990,9 +2020,15 @@ window.__ModuleLoader__.load({
1990
2020
  });
1991
2021
  }
1992
2022
  /** The flat "In one list" body: every session is one draggable top-level row. */
1993
- function FlatList({ useSessions, open, forkSession, onSessionRename, onSessionArchive, onSessionUnarchive, onSessionDelete, archivedSessionIds, showArchived, orderBy, sessionOrderByAccount, sessionUpdatedAtByAccount, syncSessionOrderAccount, setSessionOrder, t }) {
2023
+ function FlatList({ useSessions, useSessionPendingInteraction, open, forkSession, onSessionRename, onSessionArchive, onSessionUnarchive, onSessionDelete, archivedSessionIds, showArchived, orderBy, sessionOrderByAccount, sessionUpdatedAtByAccount, syncSessionOrderAccount, setSessionOrder, t }) {
1994
2024
  const list = useSessions((s) => s);
1995
- const baseRows = (0, react.useMemo)(() => deriveFlat(list, archivedSessionIds, showArchived), [list, archivedSessionIds, showArchived]);
2025
+ const pendingInteractions = useSessionPendingInteraction((s) => s);
2026
+ const baseRows = (0, react.useMemo)(() => deriveFlat(list, archivedSessionIds, pendingInteractions, showArchived), [
2027
+ list,
2028
+ archivedSessionIds,
2029
+ pendingInteractions,
2030
+ showArchived
2031
+ ]);
1996
2032
  const sessionIds = (0, react.useMemo)(() => baseRows.map((row) => row.id), [baseRows]);
1997
2033
  const previousOrderBy = (0, react.useRef)(orderBy);
1998
2034
  (0, react.useEffect)(() => {
@@ -2114,22 +2150,26 @@ window.__ModuleLoader__.load({
2114
2150
  });
2115
2151
  }
2116
2152
  /** Flat search body: local metadata matches plus the current Host result page. */
2117
- function SearchResults({ useSessions, open, workspaces, archivedSessionIds, showArchived, query, remote, resultLimit, t }) {
2153
+ function SearchResults({ useSessions, useSessionPendingInteraction, open, workspaces, archivedSessionIds, showArchived, query, remote, resultLimit, t }) {
2118
2154
  const list = useSessions((s) => s);
2155
+ const pendingInteractions = useSessionPendingInteraction((s) => s);
2156
+ const ungroupedLabel = t("group.ungrouped");
2119
2157
  const currentRemote = remote.query === query ? remote : {
2120
2158
  query,
2121
2159
  status: "loading",
2122
2160
  items: [],
2123
2161
  hasMore: false
2124
2162
  };
2125
- const results = (0, react.useMemo)(() => deriveSearchResults(list, workspaces, query, archivedSessionIds, currentRemote, resultLimit, showArchived), [
2163
+ const results = (0, react.useMemo)(() => deriveSearchResults(list, workspaces, query, archivedSessionIds, pendingInteractions, currentRemote, resultLimit, showArchived, ungroupedLabel), [
2126
2164
  list,
2127
2165
  workspaces,
2128
2166
  query,
2129
2167
  archivedSessionIds,
2168
+ pendingInteractions,
2130
2169
  currentRemote,
2131
2170
  resultLimit,
2132
- showArchived
2171
+ showArchived,
2172
+ ungroupedLabel
2133
2173
  ]);
2134
2174
  const pending = currentRemote.status === "loading";
2135
2175
  const failed = currentRemote.status === "error";
@@ -2176,7 +2216,8 @@ window.__ModuleLoader__.load({
2176
2216
  * @param props - composed slot props (shell owner share + store + injected actions).
2177
2217
  * @returns the region element tree.
2178
2218
  */
2179
- function WorkspaceBrowser({ wide, expandSidebar, useSessions, useWorkspaces, useStore, actions, startSession, open, renameSession, forkSession, renameWorkspace, deleteWorkspace, insertWorkspaceBefore, archiveSession, unarchiveSession, deleteSession, insertSessionBefore, createWorkspace, searchSessions, searchResultLimit, useDirectoryFlow, renderSlot, t }) {
2219
+ function WorkspaceBrowser({ wide, expandSidebar, useSessions, useSessionPendingInteraction, useWorkspaces, useStore, actions, startSession, open, renameSession, forkSession, renameWorkspace, deleteWorkspace, insertWorkspaceBefore, archiveSession, unarchiveSession, deleteSession, insertSessionBefore, createWorkspace, searchSessions, searchResultLimit, useDirectoryFlow, renderSlot, t }) {
2220
+ const useEffectiveSessionPendingInteraction = useSessionPendingInteraction ?? useEmptySessionPendingInteraction;
2180
2221
  const workspaces = useWorkspaces((state) => state.items);
2181
2222
  const workspacePhase = useWorkspaces((state) => state.phase);
2182
2223
  const archivedSessionIds = useWorkspaces((state) => state.archivedSessionIds);
@@ -2571,6 +2612,7 @@ window.__ModuleLoader__.load({
2571
2612
  className: WorkspaceBrowser_module_css_default.listArea,
2572
2613
  children: wide && (normalizedQuery !== "" ? (0, react_jsx_runtime.jsx)(SearchResults, {
2573
2614
  useSessions,
2615
+ useSessionPendingInteraction: useEffectiveSessionPendingInteraction,
2574
2616
  open: guardedOpen,
2575
2617
  workspaces,
2576
2618
  archivedSessionIds,
@@ -2581,6 +2623,7 @@ window.__ModuleLoader__.load({
2581
2623
  t
2582
2624
  }) : groupBy === "flat" ? (0, react_jsx_runtime.jsx)(FlatList, {
2583
2625
  useSessions,
2626
+ useSessionPendingInteraction: useEffectiveSessionPendingInteraction,
2584
2627
  open: guardedOpen,
2585
2628
  forkSession,
2586
2629
  onSessionRename,
@@ -2600,6 +2643,7 @@ window.__ModuleLoader__.load({
2600
2643
  t
2601
2644
  }) : (0, react_jsx_runtime.jsx)(SessionTree, {
2602
2645
  useSessions,
2646
+ useSessionPendingInteraction: useEffectiveSessionPendingInteraction,
2603
2647
  onSessionRename,
2604
2648
  onSessionArchive,
2605
2649
  onSessionUnarchive,
@@ -2812,13 +2856,15 @@ window.__ModuleLoader__.load({
2812
2856
  //#region dsh-archive-manager: settings section
2813
2857
  const ARCHIVE_SETTINGS_CSS = ".dsham_settings{box-sizing:border-box;width:min(100%,760px);margin:0 auto;padding:0 0 32px;color:var(--dsw-alias-label-primary)}.dsham_settingsHeader{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;margin-bottom:12px}.dsham_settings h2{margin:0;font-size:20px;font-weight:650;letter-spacing:-.2px;line-height:28px}.dsham_settingsIntro{margin:4px 0 0;max-width:42em;color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}.dsham_settingsDanger{display:inline-flex;align-items:center;gap:6px;min-height:32px;padding:0 12px;color:var(--dsw-alias-state-error-primary);background:transparent;border:1px solid var(--dsw-alias-state-error-primary);border-radius:8px;cursor:pointer;font:inherit;font-size:13px;font-weight:500}.dsham_settingsDanger:hover{background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 20%,transparent)}.dsham_settingsToolbar{display:flex;gap:8px;margin-bottom:16px}.dsham_settingsSearch{display:flex;align-items:center;gap:8px;min-width:0;flex:1;height:32px;padding:0 12px;color:var(--dsw-alias-label-tertiary);background:var(--dsw-alias-bg-layer-3,var(--dsw-alias-button-elevated-fill));border:1px solid var(--dsw-alias-border-l2);border-radius:8px}.dsham_settingsSearch:focus-within{border-color:var(--dsw-alias-label-tertiary)}.dsham_settingsSearch input{width:100%;min-width:0;padding:0;color:var(--dsw-alias-label-primary);background:transparent;border:0;outline:0;font:inherit;font-size:12px}.dsham_settingsSearch input::placeholder{color:var(--dsw-alias-label-tertiary)}.dsham_settingsFilter{position:relative;min-width:168px;flex:none}.dsham_selectTrigger{box-sizing:border-box;display:flex;align-items:center;justify-content:space-between;gap:8px;width:100%;min-height:32px;padding:0 10px;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-3,var(--dsw-alias-button-elevated-fill));border:1px solid var(--dsw-alias-border-l2);border-radius:8px;cursor:pointer;font:inherit;font-size:13px;line-height:20px;text-align:left}.dsham_selectTrigger:hover{background:var(--dsw-alias-interactive-bg-hover)}.dsham_selectTrigger:focus-visible{outline:2px solid var(--dsw-alias-state-success-primary);outline-offset:2px}.dsham_selectTrigger[aria-expanded='true']{border-color:var(--dsw-alias-state-success-primary)}.dsham_selectValue{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dsham_selectCaret{flex:none;width:12px;height:12px;color:var(--dsw-alias-label-tertiary)}.dsham_selectMenu{position:absolute;top:calc(100% + 4px);left:0;right:0;z-index:30;box-sizing:border-box;min-width:100%;max-height:280px;overflow:auto;padding:4px;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-specific-menu,var(--dsw-alias-bg-layer-2));box-shadow:var(--dsw-shadow-lv3)}.dsham_selectOption{box-sizing:border-box;display:flex;align-items:center;width:100%;min-height:32px;padding:0 10px;border:0;border-radius:8px;background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:13px;line-height:20px;text-align:left;cursor:pointer}.dsham_selectOption:hover,.dsham_selectOption[data-active='true']{background:var(--dsw-alias-interactive-bg-hover)}.dsham_selectOption[aria-selected='true']{color:var(--dsw-alias-state-success-primary)}.dsham_settingsGroup{margin:0 0 20px}.dsham_settingsGroupHeading{display:flex;align-items:center;justify-content:space-between;gap:12px;margin:0 0 14px}.dsham_settingsGroupTitle{display:flex;align-items:center;gap:8px;min-width:0;margin:0;color:var(--dsw-alias-label-primary);font-size:13px;font-weight:600}.dsham_settingsGroupTitle svg{flex:none;color:var(--dsw-alias-label-secondary)}.dsham_settingsCount{flex:none;color:var(--dsw-alias-label-tertiary);font-size:12px}.dsham_settingsList{overflow:hidden;border:1px solid var(--dsw-alias-border-l2);border-radius:12px;background:var(--dsw-alias-bg-layer-2,var(--dsw-alias-button-elevated-fill))}.dsham_settingsRow{display:flex;align-items:center;gap:12px;min-height:60px;padding:10px 16px;border-bottom:1px solid var(--dsw-alias-border-l2)}.dsham_settingsRow:last-child{border-bottom:0}.dsham_settingsContent{min-width:0;flex:1}.dsham_settingsTitle{overflow:hidden;color:var(--dsw-alias-label-primary);font-size:13px;font-weight:600;line-height:18px;text-overflow:ellipsis;white-space:nowrap}.dsham_settingsMeta{margin-top:2px;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:16px}.dsham_settingsActions{display:flex;align-items:center;gap:8px}.dsham_settingsAction{min-height:32px;padding:0 12px;color:var(--dsw-alias-label-primary);background:transparent;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;cursor:pointer;font:inherit;font-size:13px;font-weight:500}.dsham_settingsAction:hover{filter:brightness(1.12)}.dsham_settingsDelete{display:flex;align-items:center;justify-content:center;width:28px;height:28px;color:var(--dsw-alias-label-tertiary);background:transparent;border:0;border-radius:8px;cursor:pointer}.dsham_settingsDelete:hover{color:var(--dsw-alias-state-error-primary);background:var(--dsw-alias-interactive-bg-hover)}.dsham_settingsEmpty{padding:28px 8px;color:var(--dsw-alias-label-secondary);text-align:center}.dsham_settingsError{margin-top:10px;color:var(--dsw-alias-state-error-primary);font-size:12px}@media(max-width:720px){.dsham_settings{width:100%;margin:28px auto 48px;padding:0 16px}.dsham_settingsHeader{margin-bottom:28px}.dsham_settingsToolbar{flex-wrap:wrap;margin-bottom:28px}.dsham_settingsSearch{flex-basis:100%}.dsham_settingsFilter{flex:1;min-width:0}.dsham_settingsGroup{margin-bottom:32px}.dsham_settingsRow{padding:10px 12px}.dsham_settingsActions{gap:4px}}";
2814
2858
  const ARCHIVE_SETTINGS_BATCH_CSS = ".dsham_settingsHeaderActions,.dsham_settingsGroupMeta{display:flex;align-items:center;gap:8px;flex:none}.dsham_settingsRestoreAll{display:inline-flex;align-items:center;min-height:32px;padding:0 12px;color:var(--dsw-alias-label-primary);background:transparent;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;cursor:pointer;font:inherit;font-size:13px;font-weight:500}.dsham_settingsRestoreAll:hover{background:var(--dsw-alias-interactive-bg-hover)}.dsham_settingsRestoreAll:disabled,.dsham_settingsDanger:disabled,.dsham_settingsGroupMenu:disabled{cursor:not-allowed;opacity:.5}.dsham_settingsGroupMenu{display:flex;align-items:center;justify-content:center;width:28px;height:28px;padding:0;color:var(--dsw-alias-label-tertiary);background:transparent;border:0;border-radius:8px;cursor:pointer}.dsham_settingsGroupMenu:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}.dsham_settingsStatus{margin-top:10px;color:var(--dsw-alias-label-secondary);font-size:12px}@media(max-width:720px){.dsham_settingsHeader{flex-direction:column}.dsham_settingsHeaderActions{align-self:flex-end}}";
2859
+ const ARCHIVE_SETTINGS_EXTERNAL_LINK_CSS = ".dsham_settingsTitleRow{display:flex;align-items:center;gap:8px;min-width:0}.dsham_settingsLinks{display:flex;align-items:center;gap:4px}.dsham_settingsExternalLink{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}.dsham_settingsExternalLink:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}.dsham_settingsExternalLink:focus-visible{outline:2px solid var(--dsw-alias-state-success-primary);outline-offset:2px}.dsham_settingsExternalLink svg{flex:none}@media(max-width:720px){.dsham_settingsTitleRow{flex-wrap:wrap}}";
2860
+ const ARCHIVE_SETTINGS_DELETE_CONFIRM_CSS = ".dsham_settingsDeleteConfirm{color:var(--dsw-alias-state-error-primary)!important;background:transparent!important;border-color:var(--dsw-alias-state-error-primary)!important}.dsham_settingsDeleteConfirm:hover:not(:disabled){background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 20%,transparent)!important}.dsham_settingsDeleteConfirm:focus-visible{outline:2px solid var(--dsw-alias-state-error-secondary);outline-offset:2px}.dsham_settingsDeleteConfirm:disabled{cursor:not-allowed;opacity:.5}";
2815
2861
  const ARCHIVE_SETTINGS_SELECTION_CSS = ".dsham_settingsSelection{display:flex;align-items:center;gap:10px;min-height:40px;margin:-8px 0 16px;padding:4px 10px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-2,var(--dsw-alias-button-elevated-fill))}.dsham_settingsSelectionToggle{display:inline-flex;align-items:center;gap:8px;min-width:0;color:var(--dsw-alias-label-primary);font-size:13px;cursor:pointer}.dsham_settingsCheckbox{width:16px;height:16px;margin:0;accent-color:#3b82f6}.dsham_settingsSelectionCount{margin-right:auto;color:var(--dsw-alias-label-tertiary);font-size:12px}.dsham_settingsSelectionAction{min-height:28px;padding:0 10px;color:var(--dsw-alias-label-primary);background:transparent;border:1px solid var(--dsw-alias-border-l2);border-radius:7px;cursor:pointer;font:inherit;font-size:12px;font-weight:500}.dsham_settingsSelectionAction:hover{background:var(--dsw-alias-interactive-bg-hover)}.dsham_settingsSelectionDelete{color:var(--dsw-alias-state-error-primary);border-color:var(--dsw-alias-state-error-primary)}.dsham_settingsSelectionAction:disabled{cursor:not-allowed;opacity:.5}@media(max-width:720px){.dsham_settingsSelection{flex-wrap:wrap}.dsham_settingsSelectionCount{margin-right:0}.dsham_settingsSelectionAction{margin-left:auto}.dsham_settingsSelectionAction+.dsham_settingsSelectionAction{margin-left:-2px}}";
2816
2862
  const ARCHIVE_SETTINGS_LAYOUT_OVERRIDE = ".dsham_settings{margin:0 auto!important}@media(max-width:720px){.dsham_settings{margin:0 auto!important}}";
2817
2863
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify("dsh-archive-manager/ArchiveSettings.layout.css") + "]") === null) {
2818
2864
  const tag = document.createElement("style");
2819
2865
  tag.dataset.plugin = "@michengai/dsh-archive-manager";
2820
2866
  tag.dataset.pluginCss = "dsh-archive-manager/ArchiveSettings.layout.css";
2821
- tag.textContent = ARCHIVE_SETTINGS_LAYOUT_OVERRIDE;
2867
+ tag.textContent = ARCHIVE_SETTINGS_LAYOUT_OVERRIDE + ARCHIVE_SETTINGS_DELETE_CONFIRM_CSS;
2822
2868
  document.head.appendChild(tag);
2823
2869
  }
2824
2870
  /** 设置页筛选/排序:自定义菜单,避免原生 select 弹出系统浅色下拉。 */
@@ -3254,7 +3300,7 @@ window.__ModuleLoader__.load({
3254
3300
  return (0, react_jsx_runtime.jsxs)("section", {
3255
3301
  className: "dsham_settings",
3256
3302
  "aria-label": t("archives.title"),
3257
- children: [(0, react_jsx_runtime.jsx)("style", { children: ARCHIVE_SETTINGS_CSS + ARCHIVE_SETTINGS_BATCH_CSS }), (0, react_jsx_runtime.jsxs)("header", { className: "dsham_settingsHeader", children: [(0, react_jsx_runtime.jsxs)("div", { children: [(0, react_jsx_runtime.jsx)("h2", { children: t("archives.title") }), (0, react_jsx_runtime.jsx)("p", { className: "dsham_settingsIntro", children: t("archives.description") })] }), (0, react_jsx_runtime.jsxs)("div", { className: "dsham_settingsHeaderActions", children: [(0, react_jsx_runtime.jsx)("button", { type: "button", className: "dsham_settingsRestoreAll", disabled: busy || allBatchSessionIds.length === 0, onClick: () => onBatchUnarchive(allBatchTarget), children: t("archives.restoreAll") }), (0, react_jsx_runtime.jsxs)("button", { type: "button", className: "dsham_settingsDanger", disabled: busy || allBatchSessionIds.length === 0, onClick: () => setDeleteTarget({ kind: "batch", target: allBatchTarget, title: t("archives.allProjects"), count: allBatchSessionIds.length }), children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, {}), t("archives.deleteAll")] })] })] }), (0, react_jsx_runtime.jsxs)("div", { className: "dsham_settingsToolbar", children: [(0, react_jsx_runtime.jsxs)("label", { className: "dsham_settingsSearch", children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSearchOutline16, {}), (0, react_jsx_runtime.jsx)("input", { type: "search", value: query, onChange: (event) => setQuery(event.target.value), placeholder: t("archives.searchPlaceholder"), "aria-label": t("archives.searchPlaceholder") })] }), (0, react_jsx_runtime.jsx)(ArchiveProjectSelect, { id: "dsham-sort-filter", value: sortBy, options: [{ value: "updated", label: t("archives.sortUpdated") }, { value: "created", label: t("archives.sortCreated") }, { value: "alphabetical", label: t("archives.sortAlphabetical") }], onChange: setSortBy, "aria-label": t("archives.sortBy") }), (0, react_jsx_runtime.jsx)(ArchiveProjectSelect, { id: "dsham-project-filter", value: project, options: [{ value: "all", label: t("archives.allProjects") }, ...sortedGroups.map((group) => ({ value: group.key, label: group.title }))], onChange: setProject, "aria-label": t("archives.projectFilter") })] }), groups.length === 0 ? (0, react_jsx_runtime.jsx)("div", { className: "dsham_settingsEmpty", children: t("archives.empty") }) : filteredGroups.length === 0 ? (0, react_jsx_runtime.jsx)("div", { className: "dsham_settingsEmpty", children: t("archives.emptyFiltered") }) : filteredGroups.map((group) => {
3303
+ children: [(0, react_jsx_runtime.jsx)("style", { children: ARCHIVE_SETTINGS_CSS + ARCHIVE_SETTINGS_BATCH_CSS + ARCHIVE_SETTINGS_EXTERNAL_LINK_CSS }), (0, react_jsx_runtime.jsxs)("header", { className: "dsham_settingsHeader", children: [(0, react_jsx_runtime.jsxs)("div", { children: [(0, react_jsx_runtime.jsxs)("div", { className: "dsham_settingsTitleRow", children: [(0, react_jsx_runtime.jsx)("h2", { children: t("archives.title") }), (0, react_jsx_runtime.jsxs)("div", { className: "dsham_settingsLinks", children: [(0, react_jsx_runtime.jsxs)("a", { className: "dsham_settingsExternalLink", href: "https://github.com/MichengAI/dsh-archive-manager", target: "_blank", rel: "noreferrer", "aria-label": t("archives.viewProject"), children: [(0, react_jsx_runtime.jsx)(GithubMark16, {}), t("archives.viewProject")] }), (0, react_jsx_runtime.jsxs)("a", { className: "dsham_settingsExternalLink", href: "https://github.com/MichengAI/dsh-archive-manager/issues", target: "_blank", rel: "noreferrer", "aria-label": t("archives.feedback"), children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconListPenOutline16, {}), t("archives.feedback")] })] })] }), (0, react_jsx_runtime.jsx)("p", { className: "dsham_settingsIntro", children: t("archives.description") })] }), (0, react_jsx_runtime.jsxs)("div", { className: "dsham_settingsHeaderActions", children: [(0, react_jsx_runtime.jsx)("button", { type: "button", className: "dsham_settingsRestoreAll", disabled: busy || allBatchSessionIds.length === 0, onClick: () => onBatchUnarchive(allBatchTarget), children: t("archives.restoreAll") }), (0, react_jsx_runtime.jsxs)("button", { type: "button", className: "dsham_settingsDanger", disabled: busy || allBatchSessionIds.length === 0, onClick: () => setDeleteTarget({ kind: "batch", target: allBatchTarget, title: t("archives.allProjects"), count: allBatchSessionIds.length }), children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, {}), t("archives.deleteAll")] })] })] }), (0, react_jsx_runtime.jsxs)("div", { className: "dsham_settingsToolbar", children: [(0, react_jsx_runtime.jsxs)("label", { className: "dsham_settingsSearch", children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSearchOutline16, {}), (0, react_jsx_runtime.jsx)("input", { type: "search", value: query, onChange: (event) => setQuery(event.target.value), placeholder: t("archives.searchPlaceholder"), "aria-label": t("archives.searchPlaceholder") })] }), (0, react_jsx_runtime.jsx)(ArchiveProjectSelect, { id: "dsham-sort-filter", value: sortBy, options: [{ value: "updated", label: t("archives.sortUpdated") }, { value: "created", label: t("archives.sortCreated") }, { value: "alphabetical", label: t("archives.sortAlphabetical") }], onChange: setSortBy, "aria-label": t("archives.sortBy") }), (0, react_jsx_runtime.jsx)(ArchiveProjectSelect, { id: "dsham-project-filter", value: project, options: [{ value: "all", label: t("archives.allProjects") }, ...sortedGroups.map((group) => ({ value: group.key, label: group.title }))], onChange: setProject, "aria-label": t("archives.projectFilter") })] }), groups.length === 0 ? (0, react_jsx_runtime.jsx)("div", { className: "dsham_settingsEmpty", children: t("archives.empty") }) : filteredGroups.length === 0 ? (0, react_jsx_runtime.jsx)("div", { className: "dsham_settingsEmpty", children: t("archives.emptyFiltered") }) : filteredGroups.map((group) => {
3258
3304
  const target = archivedBatchTargetForGroup(group.key);
3259
3305
  const count = deriveArchivedBatchIds(workspaceState.archivedSessionIds, workspaceState.items, target).length;
3260
3306
  return (0, react_jsx_runtime.jsxs)("section", {
@@ -3276,7 +3322,7 @@ window.__ModuleLoader__.load({
3276
3322
  closeLabel: t("close"),
3277
3323
  title: deleteDialogTitle,
3278
3324
  ...deleteDialogDescription === void 0 ? {} : { description: deleteDialogDescription },
3279
- footer: (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, { variant: "outline", disabled: busy, onClick: closeDelete, children: t("cancel") }), (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, { variant: "outline", disabled: busy, onClick: confirmDelete, children: deleteConfirmLabel })] }),
3325
+ footer: (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, { variant: "outline", disabled: busy, onClick: closeDelete, children: t("cancel") }), (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, { variant: "outline", className: "dsham_settingsDeleteConfirm", disabled: busy, onClick: confirmDelete, children: deleteConfirmLabel })] }),
3280
3326
  children: busy && (0, react_jsx_runtime.jsx)("div", { role: "status", children: deleteTarget?.kind === "batch" ? t("archives.deleteBatchPending") : t("deleteSession.pending") })
3281
3327
  })]
3282
3328
  });
@@ -3321,6 +3367,8 @@ window.__ModuleLoader__.load({
3321
3367
  "archived.notOpenable": "已归档,取消归档后可继续对话",
3322
3368
  "archives.title": "已归档的聊天",
3323
3369
  "archives.description": "管理已归档的会话。",
3370
+ "archives.viewProject": "GitHub",
3371
+ "archives.feedback": "问题反馈",
3324
3372
  "archives.empty": "暂无已归档会话",
3325
3373
  "archives.emptyFiltered": "没有匹配的已归档聊天",
3326
3374
  "archives.searchPlaceholder": "搜索已归档聊天",
@@ -3445,6 +3493,8 @@ window.__ModuleLoader__.load({
3445
3493
  "archived.notOpenable": "This session is archived. Unarchive it to continue the conversation.",
3446
3494
  "archives.title": "Archived chats",
3447
3495
  "archives.description": "Manage archived sessions.",
3496
+ "archives.viewProject": "GitHub",
3497
+ "archives.feedback": "Issues",
3448
3498
  "archives.empty": "No archived sessions.",
3449
3499
  "archives.emptyFiltered": "No archived chats match your filters.",
3450
3500
  "archives.searchPlaceholder": "Search archived chats",
package/lib/projcache.js CHANGED
@@ -25,17 +25,23 @@ const storedCheckpointRecord = checkpointRecord.extend({
25
25
  });
26
26
  const safeDomainInput = {
27
27
  name: "session_projcache_archive_manager",
28
- version: 1,
28
+ version: 2,
29
29
  tables: { sessions: domainTable(storedCheckpointRecord) },
30
30
  ...(projectionCacheDomainSpec.layout === "per-record" ? { layout: "per-record" } : {})
31
31
  };
32
32
  const safeProjectionCacheDomainSpec = defineDomain(safeDomainInput);
33
33
 
34
34
  /** The last official whole-file cache format used by DSH rc.2. */
35
+ const legacyCheckpointRecord = checkpointRecord.extend({
36
+ identity: checkpointIdentity.partial({
37
+ isSeeded: true,
38
+ inheritedEventCount: true
39
+ })
40
+ });
35
41
  const legacyProjectionCacheDomainSpec = defineDomain({
36
42
  name: "session_projcache",
37
43
  version: 3,
38
- tables: { sessions: domainTable(checkpointRecord) }
44
+ tables: { sessions: domainTable(legacyCheckpointRecord) }
39
45
  });
40
46
 
41
47
  function projectionCacheStorageKey(sessionId) {
@@ -107,12 +113,42 @@ async function readSourceRecords(ctx, spec, label) {
107
113
  }
108
114
  }
109
115
 
116
+ /**
117
+ * 旧版记录没有完整身份时,只能安全补齐未播种会话的零继承切点。
118
+ * 播种会话无法从头部反推出精确切点,宁可跳过并按需重建。
119
+ */
120
+ function normalizeLegacyRecord(record) {
121
+ if (record === null || typeof record !== "object" || record.identity === null || typeof record.identity !== "object") return void 0;
122
+ const identity = record.identity;
123
+ const inheritedEventCount = identity.inheritedEventCount;
124
+ const validOffset = Number.isSafeInteger(inheritedEventCount) && inheritedEventCount >= 0;
125
+ if (identity.isSeeded === true) {
126
+ if (!validOffset) return void 0;
127
+ return record;
128
+ }
129
+ if (identity.isSeeded !== void 0 && identity.isSeeded !== false) return void 0;
130
+ if (inheritedEventCount !== void 0 && inheritedEventCount !== 0) return void 0;
131
+ return {
132
+ ...record,
133
+ identity: {
134
+ ...identity,
135
+ isSeeded: false,
136
+ inheritedEventCount: 0
137
+ }
138
+ };
139
+ }
140
+
110
141
  async function importMissingRecords(ctx, target, records, label) {
111
142
  let imported = 0;
112
143
  for (const [sessionId, record] of records) {
113
144
  if (target.has(sessionId)) continue;
114
145
  try {
115
- await target.put(sessionId, record);
146
+ const normalized = normalizeLegacyRecord(record);
147
+ if (normalized === void 0) {
148
+ ctx.logger.warn(`archive-manager projcache: ${label} row "${sessionId}" import skipped because its lifecycle identity is incomplete`);
149
+ continue;
150
+ }
151
+ await target.put(sessionId, normalized);
116
152
  imported += 1;
117
153
  } catch (error) {
118
154
  ctx.logger.warn(`archive-manager projcache: ${label} row "${sessionId}" import failed: ${String(error)}`);
@@ -122,22 +158,20 @@ async function importMissingRecords(ctx, target, records, label) {
122
158
  }
123
159
 
124
160
  /**
125
- * Seed the compatibility domain without opening alpha.2's unsafe per-record
126
- * source while the v3 whole-file cache contains rows. An empty v3 source may
127
- * fall through to the current source; source-open failures stay fail-soft.
128
- * Imports are add-only: a newer compatibility-domain checkpoint always wins,
129
- * and a partially completed import resumes on the next startup.
161
+ * 合并官方新版逐会话缓存与旧版整文件缓存。归档管理器自身记录最高优先,
162
+ * 新版记录优先于旧版;任一来源失败时仍继续尝试另一个来源。
130
163
  */
131
164
  async function importPreviousProjectionCache(ctx, target) {
132
165
  const legacy = await readSourceRecords(ctx, legacyProjectionCacheDomainSpec, "legacy v3");
133
- if (!legacy.opened) return 0;
134
- if (legacy.records.length > 0) return importMissingRecords(ctx, target, legacy.records, "legacy v3");
135
166
  const sameSpec = projectionCacheDomainSpec.version === legacyProjectionCacheDomainSpec.version
136
167
  && projectionCacheDomainSpec.layout === legacyProjectionCacheDomainSpec.layout;
137
- if (sameSpec) return 0;
138
- const current = await readSourceRecords(ctx, projectionCacheDomainSpec, "current");
139
- if (!current.opened) return 0;
140
- return importMissingRecords(ctx, target, current.records, "current");
168
+ let imported = 0;
169
+ if (!sameSpec) {
170
+ const current = await readSourceRecords(ctx, projectionCacheDomainSpec, "current");
171
+ if (current.opened) imported += await importMissingRecords(ctx, target, current.records, "current");
172
+ }
173
+ if (legacy.opened) imported += await importMissingRecords(ctx, target, legacy.records, "legacy v3");
174
+ return imported;
141
175
  }
142
176
  //#region lib/types/index.js
143
177
  /**
package/lib/workspace.js CHANGED
@@ -279,14 +279,27 @@ var ArchiveWorkspaceRegistry = class extends WorkspaceRegistry {
279
279
  if (cache === void 0 || typeof cache.cachedSnapshot !== "function" || typeof cache.put !== "function") return false;
280
280
  if (persistence === void 0 || typeof persistence.readFrom !== "function" || projections === void 0 || typeof projections.restore !== "function") return false;
281
281
  try {
282
- if (cache.cachedSnapshot(header) !== void 0) return false;
282
+ // 未播种会话的继承事件数恒为零,先查缓存可避免读取完整会话原文。
283
+ if (!header.isSeeded && cache.cachedSnapshot(header, 0) !== void 0) return false;
283
284
  const stored = await persistence.readFrom(header.id, 0);
284
285
  const meta = stored.meta ?? header;
285
- const restored = projections.restore({}, stored.events, 0, meta);
286
+ if (meta.isSeeded === true && stored.inheritedEventCount === void 0) {
287
+ this.ctx.logger.warn(`archive-manager: projection repair for seeded archived session "${header.id}" skipped because its inherited event count is unavailable`);
288
+ return false;
289
+ }
290
+ const inheritedEventCount = stored.inheritedEventCount ?? 0;
291
+ if (meta.isSeeded !== true && inheritedEventCount !== 0) {
292
+ this.ctx.logger.warn(`archive-manager: projection repair for unseeded archived session "${header.id}" skipped because its inherited event count is not zero`);
293
+ return false;
294
+ }
295
+ if (cache.cachedSnapshot(meta, inheritedEventCount) !== void 0) return false;
296
+ const restored = projections.restore({}, stored.events, 0, meta, inheritedEventCount);
286
297
  if (restored === void 0 || typeof restored !== "object" || restored.checkpoint === void 0) return false;
287
298
  await cache.put(header.id, {
288
299
  createdAt: meta.createdAt,
289
- ...meta.cwd === void 0 ? {} : { cwd: meta.cwd }
300
+ ...(meta.cwd === void 0 ? {} : { cwd: meta.cwd }),
301
+ isSeeded: meta.isSeeded ?? false,
302
+ inheritedEventCount
290
303
  }, restored.checkpoint);
291
304
  return true;
292
305
  } catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@michengai/dsh-archive-manager",
3
- "version": "0.1.22",
3
+ "version": "0.1.24",
4
4
  "description": "NPM-installable DSH Web plugin for managing archived sessions.",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@11.21.0",
@@ -36,14 +36,15 @@
36
36
  "@deepseek-ai/dsh-client-connection": ">=0.1.0-rc.5 <0.2.0",
37
37
  "@deepseek-ai/dsh-client-locale": ">=0.1.0-rc.5 <0.2.0",
38
38
  "@deepseek-ai/dsh-client-runtime": ">=0.1.0-rc.5 <0.2.0",
39
+ "@deepseek-ai/dsh-client-store": ">=0.1.0-rc.5 <0.2.0",
39
40
  "@deepseek-ai/dsh-client-ui-conversation": ">=0.1.0-rc.5 <0.2.0",
40
41
  "@deepseek-ai/dsh-client-ui-primitives": ">=0.1.0-rc.5 <0.2.0",
41
42
  "@deepseek-ai/dsh-client-ui-sidebar": ">=0.1.0-rc.5 <0.2.0",
42
43
  "@deepseek-ai/dsh-client-ui-workspace": ">=0.1.0-rc.5 <0.2.0",
43
44
  "@deepseek-ai/dsh-session": ">=0.1.0-rc.5 <0.2.0",
44
45
  "@deepseek-ai/dsh-session-projection-cache": ">=0.1.0-rc.5 <0.2.0",
45
- "@deepseek-ai/dsh-storage-domain": ">=0.1.0-rc.5 <0.2.0",
46
46
  "@deepseek-ai/dsh-spill-local": ">=0.1.0-rc.5 <0.2.0",
47
+ "@deepseek-ai/dsh-storage-domain": ">=0.1.0-rc.5 <0.2.0",
47
48
  "@deepseek-ai/dsh-typert-protocol": ">=0.1.0-rc.5 <0.2.0",
48
49
  "@deepseek-ai/dsh-typert-registry": ">=0.1.0-rc.5 <0.2.0",
49
50
  "@deepseek-ai/dsh-workspace": ">=0.1.0-rc.5 <0.2.0"
@@ -51,20 +52,35 @@
51
52
  "peerDependenciesMeta": {
52
53
  "@deepseek-ai/dsh-client-runtime": {
53
54
  "optional": true
55
+ },
56
+ "@deepseek-ai/dsh-client-store": {
57
+ "optional": true
54
58
  }
55
59
  },
56
60
  "devDependencies": {
57
- "@deepseek-ai/cordis": "4.0.1",
58
- "@deepseek-ai/dsh-api-gateway": "0.1.0-rc.6",
59
- "@deepseek-ai/dsh-client-runtime": "0.1.0-rc.6",
60
- "@deepseek-ai/dsh-client-ui-slots": "0.1.0-rc.6",
61
- "@deepseek-ai/dsh-client-web-react": "0.1.0-rc.6",
62
- "@deepseek-ai/dsh-session-projection-cache": "0.1.0-rc.6",
63
- "@deepseek-ai/dsh-storage-domain": "0.1.0-rc.6",
64
- "@deepseek-ai/dsh-spill-local": "0.1.0-rc.6",
65
- "@deepseek-ai/dsh-typert-protocol": "0.1.0-rc.6",
66
- "@deepseek-ai/dsh-typert-registry": "0.1.0-rc.6",
67
- "@deepseek-ai/dsh-workspace": "0.1.0-rc.6",
61
+ "@deepseek-ai/cordis": "4.0.2",
62
+ "@deepseek-ai/dsh-api-gateway": "0.1.2-alpha.5",
63
+ "@deepseek-ai/dsh-client-connection": "0.1.2-alpha.5",
64
+ "@deepseek-ai/dsh-client-locale": "0.1.2-alpha.5",
65
+ "@deepseek-ai/dsh-client-store": "0.1.2-alpha.5",
66
+ "@deepseek-ai/dsh-client-ui-conversation": "0.1.2-alpha.5",
67
+ "@deepseek-ai/dsh-client-ui-primitives": "0.1.2-alpha.5",
68
+ "@deepseek-ai/dsh-client-ui-sidebar": "0.1.2-alpha.5",
69
+ "@deepseek-ai/dsh-client-ui-slots": "0.1.2-alpha.5",
70
+ "@deepseek-ai/dsh-client-ui-workspace": "0.1.2-alpha.5",
71
+ "@deepseek-ai/dsh-invariants": "0.1.2-alpha.5",
72
+ "@deepseek-ai/dsh-scope": "0.1.2-alpha.5",
73
+ "@deepseek-ai/dsh-session": "0.1.2-alpha.5",
74
+ "@deepseek-ai/dsh-session-persistence": "0.1.2-alpha.5",
75
+ "@deepseek-ai/dsh-session-projection": "0.1.2-alpha.5",
76
+ "@deepseek-ai/dsh-session-projection-cache": "0.1.2-alpha.5",
77
+ "@deepseek-ai/dsh-spill": "0.1.2-alpha.5",
78
+ "@deepseek-ai/dsh-spill-local": "0.1.2-alpha.5",
79
+ "@deepseek-ai/dsh-storage": "0.1.2-alpha.5",
80
+ "@deepseek-ai/dsh-storage-domain": "0.1.2-alpha.5",
81
+ "@deepseek-ai/dsh-typert-protocol": "0.1.2-alpha.5",
82
+ "@deepseek-ai/dsh-typert-registry": "0.1.2-alpha.5",
83
+ "@deepseek-ai/dsh-workspace": "0.1.2-alpha.5",
68
84
  "react": "18.3.1",
69
85
  "react-dom": "18.3.1"
70
86
  },