@michengai/dsh-archive-manager 0.1.21 → 0.1.22

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,14 @@
4
4
 
5
5
  Published release notes are retained below; new versions are added without removing earlier entries.
6
6
 
7
+ ## 0.1.22 — 2026-09-01
8
+
9
+ - 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.
10
+ - Kept selected operations on the existing authoritative host batch path, so stale records, partial deletion failures, and retry behavior match existing project-wide and all-chat actions.
11
+ - Refreshed the client session projection after restoring chats so restored sessions immediately reappear in their original workspace.
12
+
13
+ Published package: [`@michengai/dsh-archive-manager@0.1.22`](https://www.npmjs.com/package/@michengai/dsh-archive-manager/v/0.1.22).
14
+
7
15
  ## 0.1.21 — 2026-08-31
8
16
 
9
17
  - Preserved the projection-cache `put()` Promise contract when a tombstone suppresses a deleted Session write, so DSH `v0.1.2-alpha.2` cold-read write-back remains fail-soft instead of throwing a synchronous `TypeError`.
@@ -4,6 +4,14 @@
4
4
 
5
5
  以下发布说明会持续保留;新增版本时不再删除较早记录。
6
6
 
7
+ ## 0.1.22 — 2026-09-01
8
+
9
+ - 在「设置 → 已归档」新增任意多选:每行复选框、全选当前筛选结果、跨项目保留选择、批量恢复和确认后的批量永久删除。
10
+ - 所选操作复用宿主权威批量路径,陈旧记录清理、部分删除失败和重试行为与项目级及全部操作保持一致。
11
+ - 恢复聊天后刷新客户端会话投影,使其立刻重新出现在原工作区。
12
+
13
+ 已发布包:[`@michengai/dsh-archive-manager@0.1.22`](https://www.npmjs.com/package/@michengai/dsh-archive-manager/v/0.1.22)。
14
+
7
15
  ## 0.1.21 — 2026-08-31
8
16
 
9
17
  - 墓碑拦截已删除会话的投影缓存写入时仍保持 `put()` 的 Promise 契约,使 DSH `v0.1.2-alpha.2` 冷读写回继续按 fail-soft 方式处理,而不会同步抛出 `TypeError`。
package/lib/client.js CHANGED
@@ -49,7 +49,8 @@ window.__ModuleLoader__.load({
49
49
  if (typeof value !== "object" || value === null || Array.isArray(value)) throw new TypeError("target must be an object");
50
50
  if (value.scope === "all" || value.scope === "ungrouped") return value;
51
51
  if (value.scope === "workspace" && typeof value.workspaceId === "string" && value.workspaceId.length > 0) return value;
52
- throw new TypeError("target.scope must be all, ungrouped, or workspace with a non-empty workspaceId");
52
+ if (value.scope === "sessions" && Array.isArray(value.sessionIds) && value.sessionIds.length > 0 && value.sessionIds.every((id) => typeof id === "string" && id.length > 0)) return value;
53
+ throw new TypeError("target.scope must be all, ungrouped, workspace with a non-empty workspaceId, or sessions with non-empty sessionIds");
53
54
  }
54
55
  };
55
56
  const unarchivedBatchSchema = {
@@ -283,6 +284,195 @@ window.__ModuleLoader__.load({
283
284
  }
284
285
  });
285
286
  }
287
+ /**
288
+ * 归档多选原型。保留既有设置页作为兼容回退,原型直接使用相同宿主
289
+ * 批量接口,因此其恢复、删除和并发安全语义与原有项目级操作一致。
290
+ */
291
+ function ArchivedSessionsSectionPrototype({ sessionStore, workspaceStore, unarchiveSession, deleteSession, unarchiveSessions, deleteArchivedSessions, archivedSessionMetadata, t }) {
292
+ const sessions = (0, react.useSyncExternalStore)(sessionStore.subscribe, sessionStore.getSnapshot);
293
+ const workspaceState = (0, react.useSyncExternalStore)(workspaceStore.subscribe, workspaceStore.getSnapshot);
294
+ const [deleteTarget, setDeleteTarget] = (0, react.useState)(null);
295
+ const [busy, setBusy] = (0, react.useState)(false);
296
+ const [error, setError] = (0, react.useState)(null);
297
+ const [notice, setNotice] = (0, react.useState)(null);
298
+ const [query, setQuery] = (0, react.useState)("");
299
+ const [project, setProject] = (0, react.useState)("all");
300
+ const [sortBy, setSortBy] = (0, react.useState)("updated");
301
+ const [createdAtById, setCreatedAtById] = (0, react.useState)({});
302
+ const [unarchivingSessionIds, setUnarchivingSessionIds] = (0, react.useState)(() => /* @__PURE__ */ new Set());
303
+ const unarchivingSessionIdsRef = (0, react.useRef)(/* @__PURE__ */ new Set());
304
+ const [selectedSessionIds, setSelectedSessionIds] = (0, react.useState)([]);
305
+ const groups = (0, react.useMemo)(() => deriveArchivedGroups(sessions.byId, workspaceState.items, workspaceState.archivedSessionIds, t("group.ungrouped")), [sessions.byId, workspaceState, t]);
306
+ const sortedGroups = (0, react.useMemo)(() => sortArchivedGroups(groups, sortBy, createdAtById, t), [groups, sortBy, createdAtById, t]);
307
+ (0, react.useEffect)(() => {
308
+ let cancelled = false;
309
+ archivedSessionMetadata().then((result) => {
310
+ if (!cancelled) setCreatedAtById(Object.fromEntries(result.items.map((item) => [item.sessionId, item.createdAt])));
311
+ }).catch(() => {
312
+ if (!cancelled) setCreatedAtById({});
313
+ });
314
+ return () => {
315
+ cancelled = true;
316
+ };
317
+ }, [archivedSessionMetadata, workspaceState.archivedSessionIds]);
318
+ (0, react.useEffect)(() => {
319
+ if (project !== "all" && !groups.some((group) => group.key === project)) setProject("all");
320
+ }, [groups, project]);
321
+ const filteredGroups = (0, react.useMemo)(() => {
322
+ const normalizedQuery = query.trim().toLocaleLowerCase();
323
+ return sortedGroups.filter((group) => project === "all" || project === group.key).map((group) => ({
324
+ ...group,
325
+ sessions: group.sessions.filter((session) => normalizedQuery === "" || displayTitle(session, t).toLocaleLowerCase().includes(normalizedQuery))
326
+ })).filter((group) => group.sessions.length > 0);
327
+ }, [sortedGroups, project, query, t]);
328
+ const visibleSessionIds = (0, react.useMemo)(() => archivedSessionIdsInGroups(filteredGroups), [filteredGroups]);
329
+ const selectedSessionIdSet = (0, react.useMemo)(() => new Set(selectedSessionIds), [selectedSessionIds]);
330
+ const selectedVisibleCount = visibleSessionIds.filter((sessionId) => selectedSessionIdSet.has(sessionId)).length;
331
+ const allVisibleSelected = visibleSessionIds.length > 0 && selectedVisibleCount === visibleSessionIds.length;
332
+ const allBatchTarget = { scope: "all" };
333
+ const allBatchSessionIds = (0, react.useMemo)(() => deriveArchivedBatchIds(workspaceState.archivedSessionIds, workspaceState.items, allBatchTarget), [workspaceState.archivedSessionIds, workspaceState.items]);
334
+ (0, react.useEffect)(() => {
335
+ setSelectedSessionIds((current) => pruneArchivedSelection(current, workspaceState.archivedSessionIds));
336
+ }, [workspaceState.archivedSessionIds]);
337
+ const toggleSessionSelection = (sessionId, checked) => {
338
+ setSelectedSessionIds((current) => toggleArchivedSelection(current, [sessionId], checked));
339
+ };
340
+ const toggleVisibleSelection = (checked) => {
341
+ setSelectedSessionIds((current) => toggleArchivedSelection(current, visibleSessionIds, checked));
342
+ };
343
+ const onUnarchive = (sessionId) => {
344
+ if (busy || unarchivingSessionIdsRef.current.has(sessionId)) return;
345
+ unarchivingSessionIdsRef.current.add(sessionId);
346
+ setUnarchivingSessionIds(new Set(unarchivingSessionIdsRef.current));
347
+ setError(null);
348
+ setNotice(null);
349
+ unarchiveSession(sessionId).catch((reason) => {
350
+ setError(formatUnarchiveError(reason, t));
351
+ }).finally(() => {
352
+ unarchivingSessionIdsRef.current.delete(sessionId);
353
+ setUnarchivingSessionIds(new Set(unarchivingSessionIdsRef.current));
354
+ });
355
+ };
356
+ const onBatchUnarchive = async (target) => {
357
+ if (busy) return;
358
+ setBusy(true);
359
+ setError(null);
360
+ setNotice(null);
361
+ try {
362
+ const result = await unarchiveSessions(target);
363
+ setNotice(t("archives.restoreSuccess", { n: result.unarchivedSessionIds.length }));
364
+ return result;
365
+ } catch (reason) {
366
+ setError(t("archives.restoreBatchFailed", { detail: reason instanceof Error ? reason.message : String(reason) }));
367
+ } finally {
368
+ setBusy(false);
369
+ }
370
+ };
371
+ const onSelectedUnarchive = async () => {
372
+ if (selectedSessionIds.length === 0) return;
373
+ const result = await onBatchUnarchive({ scope: "sessions", sessionIds: selectedSessionIds });
374
+ if (result !== void 0) {
375
+ const restored = new Set(result.unarchivedSessionIds);
376
+ setSelectedSessionIds((current) => current.filter((sessionId) => !restored.has(sessionId)));
377
+ }
378
+ };
379
+ const closeDelete = () => {
380
+ if (!busy) setDeleteTarget(null);
381
+ };
382
+ (0, react.useEffect)(() => {
383
+ if (deleteTarget === null) return;
384
+ const onKeyDown = (event) => {
385
+ if (event.key !== "Escape") return;
386
+ event.preventDefault();
387
+ event.stopPropagation();
388
+ if (typeof event.stopImmediatePropagation === "function") event.stopImmediatePropagation();
389
+ if (!busy) setDeleteTarget(null);
390
+ };
391
+ window.addEventListener("keydown", onKeyDown, true);
392
+ return () => window.removeEventListener("keydown", onKeyDown, true);
393
+ }, [deleteTarget, busy]);
394
+ const confirmDelete = async () => {
395
+ if (busy || deleteTarget === null) return;
396
+ setBusy(true);
397
+ setError(null);
398
+ setNotice(null);
399
+ try {
400
+ if (deleteTarget.kind === "batch") {
401
+ const result = await deleteArchivedSessions(deleteTarget.target);
402
+ const feedback = archivedDeleteFeedback(result, t);
403
+ if (feedback.kind === "error") setError(feedback.message);
404
+ else setNotice(feedback.message);
405
+ if (deleteTarget.target.scope === "sessions") {
406
+ const completed = new Set([...result.deletedSessionIds, ...result.skippedSessionIds]);
407
+ setSelectedSessionIds((current) => current.filter((sessionId) => !completed.has(sessionId)));
408
+ }
409
+ } else {
410
+ await deleteSession(deleteTarget.session.id);
411
+ }
412
+ setDeleteTarget(null);
413
+ } catch (reason) {
414
+ setError(formatDeleteError(reason, t));
415
+ } finally {
416
+ setBusy(false);
417
+ }
418
+ };
419
+ const batchScope = deleteTarget?.kind === "batch" ? deleteTarget.target.scope : null;
420
+ const deleteDialogTitle = batchScope === "all" ? t("archives.deleteAllTitle") : batchScope === "ungrouped" ? t("archives.deleteUngroupedTitle") : batchScope === "workspace" ? t("archives.deleteProjectTitle", { name: deleteTarget.title }) : batchScope === "sessions" ? t("archives.deleteSelectedTitle") : t("deleteSession.title");
421
+ const deleteDialogDescription = deleteTarget === null ? void 0 : batchScope === "all" ? t("archives.deleteAllDesc", { n: deleteTarget.count }) : batchScope === "ungrouped" ? t("archives.deleteUngroupedDesc", { n: deleteTarget.count }) : batchScope === "workspace" ? t("archives.deleteProjectDesc", { name: deleteTarget.title, n: deleteTarget.count }) : batchScope === "sessions" ? t("archives.deleteSelectedDesc", { n: deleteTarget.count }) : t("deleteSession.desc", { name: displayTitle(deleteTarget.session, t) });
422
+ const deleteConfirmLabel = batchScope === "all" ? t("archives.deleteAll") : batchScope === "ungrouped" ? t("archives.deleteUngroupedConfirm") : batchScope === "workspace" ? t("archives.deleteProjectConfirm") : batchScope === "sessions" ? t("archives.deleteSelectedConfirm") : t("deleteSession.title");
423
+ return (0, react_jsx_runtime.jsxs)("section", {
424
+ className: "dsham_settings",
425
+ "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", {
427
+ className: "dsham_settingsHeader",
428
+ 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") })]
430
+ }), (0, react_jsx_runtime.jsxs)("div", {
431
+ className: "dsham_settingsHeaderActions",
432
+ 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")] })]
433
+ })]
434
+ }), (0, react_jsx_runtime.jsxs)("div", {
435
+ className: "dsham_settingsToolbar",
436
+ children: [(0, react_jsx_runtime.jsxs)("label", {
437
+ className: "dsham_settingsSearch",
438
+ 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") })]
439
+ }), (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") })]
440
+ }), groups.length > 0 && (0, react_jsx_runtime.jsxs)("div", {
441
+ className: "dsham_settingsSelection",
442
+ children: [(0, react_jsx_runtime.jsxs)("label", {
443
+ className: "dsham_settingsSelectionToggle",
444
+ children: [(0, react_jsx_runtime.jsx)(ArchiveSelectionCheckbox, { checked: allVisibleSelected, indeterminate: selectedVisibleCount > 0 && !allVisibleSelected, disabled: busy || visibleSessionIds.length === 0, label: t("archives.selectAllFiltered"), onChange: (event) => toggleVisibleSelection(event.target.checked) }), t("archives.selectAllFiltered")]
445
+ }), (0, react_jsx_runtime.jsx)("span", { className: "dsham_settingsSelectionCount", children: t("archives.selectedCount", { n: selectedSessionIds.length }) }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: "dsham_settingsSelectionAction", disabled: busy || selectedSessionIds.length === 0, onClick: onSelectedUnarchive, children: t("archives.restoreSelected") }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: "dsham_settingsSelectionAction dsham_settingsSelectionDelete", disabled: busy || selectedSessionIds.length === 0, onClick: () => setDeleteTarget({ kind: "batch", target: { scope: "sessions", sessionIds: selectedSessionIds }, count: selectedSessionIds.length }), children: t("archives.deleteSelected") })]
446
+ }), 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) => {
447
+ const target = archivedBatchTargetForGroup(group.key);
448
+ const count = deriveArchivedBatchIds(workspaceState.archivedSessionIds, workspaceState.items, target).length;
449
+ return (0, react_jsx_runtime.jsxs)("section", {
450
+ className: "dsham_settingsGroup",
451
+ children: [(0, react_jsx_runtime.jsxs)("div", {
452
+ className: "dsham_settingsGroupHeading",
453
+ children: [(0, react_jsx_runtime.jsxs)("h3", { className: "dsham_settingsGroupTitle", children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderOpenOutline16, {}), group.title] }), (0, react_jsx_runtime.jsxs)("div", { className: "dsham_settingsGroupMeta", children: [(0, react_jsx_runtime.jsx)("span", { className: "dsham_settingsCount", children: t("archives.sessionCount", { n: count }) }), (0, react_jsx_runtime.jsx)(ArchivedGroupActions, { group, busy, onRestore: () => onBatchUnarchive(target), onDelete: () => setDeleteTarget({ kind: "batch", target, title: group.title, count }), t })] })]
454
+ }), (0, react_jsx_runtime.jsx)("div", {
455
+ className: "dsham_settingsList",
456
+ children: group.sessions.map((session) => (0, react_jsx_runtime.jsxs)("article", {
457
+ className: "dsham_settingsRow",
458
+ children: [(0, react_jsx_runtime.jsx)(ArchiveSelectionCheckbox, { checked: selectedSessionIdSet.has(session.id), disabled: busy, label: t("archives.selectSession", { name: displayTitle(session, t) }), onChange: (event) => toggleSessionSelection(session.id, event.target.checked) }), (0, react_jsx_runtime.jsxs)("div", { className: "dsham_settingsContent", children: [(0, react_jsx_runtime.jsx)("div", { className: "dsham_settingsTitle", children: displayTitle(session, t) }), (0, react_jsx_runtime.jsx)("div", { className: "dsham_settingsMeta", children: archiveTimeLabel(session.updatedAt, t) })] }), (0, react_jsx_runtime.jsxs)("div", {
459
+ className: "dsham_settingsActions",
460
+ children: [(0, react_jsx_runtime.jsx)("button", { type: "button", className: "dsham_settingsAction", disabled: busy || unarchivingSessionIds.has(session.id), onClick: () => onUnarchive(session.id), children: t("menu.unarchive") }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: "dsham_settingsDelete", disabled: busy || unarchivingSessionIds.has(session.id), "aria-label": t("menu.deleteSession"), onClick: () => setDeleteTarget({ kind: "session", session }), children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, {}) })]
461
+ })]
462
+ }, session.id))
463
+ })]
464
+ }, group.key);
465
+ }), error !== null && (0, react_jsx_runtime.jsx)("div", { className: "dsham_settingsError", role: "alert", children: error }), notice !== null && (0, react_jsx_runtime.jsx)("div", { className: "dsham_settingsStatus", role: "status", children: notice }), (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
466
+ open: deleteTarget !== null,
467
+ onClose: closeDelete,
468
+ closeLabel: t("close"),
469
+ title: deleteDialogTitle,
470
+ ...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 })] }),
472
+ children: busy && (0, react_jsx_runtime.jsx)("div", { role: "status", children: deleteTarget?.kind === "batch" ? t("archives.deleteBatchPending") : t("deleteSession.pending") })
473
+ })]
474
+ });
475
+ }
286
476
  //#endregion
287
477
  //#region ../../../node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.mjs
288
478
  function r(e) {
@@ -2622,6 +2812,7 @@ window.__ModuleLoader__.load({
2622
2812
  //#region dsh-archive-manager: settings section
2623
2813
  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}}";
2624
2814
  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}}";
2815
+ 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}}";
2625
2816
  const ARCHIVE_SETTINGS_LAYOUT_OVERRIDE = ".dsham_settings{margin:0 auto!important}@media(max-width:720px){.dsham_settings{margin:0 auto!important}}";
2626
2817
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify("dsh-archive-manager/ArchiveSettings.layout.css") + "]") === null) {
2627
2818
  const tag = document.createElement("style");
@@ -2762,6 +2953,22 @@ window.__ModuleLoader__.load({
2762
2953
  }) : null]
2763
2954
  });
2764
2955
  }
2956
+ /** 原生 checkbox 保留浏览器的正确键盘语义,并显式同步三态显示。 */
2957
+ function ArchiveSelectionCheckbox({ checked, indeterminate = false, disabled = false, label, onChange }) {
2958
+ const inputRef = (0, react.useRef)(null);
2959
+ (0, react.useEffect)(() => {
2960
+ if (inputRef.current !== null) inputRef.current.indeterminate = indeterminate;
2961
+ }, [indeterminate]);
2962
+ return (0, react_jsx_runtime.jsx)("input", {
2963
+ ref: inputRef,
2964
+ type: "checkbox",
2965
+ className: "dsham_settingsCheckbox",
2966
+ checked,
2967
+ disabled,
2968
+ "aria-label": label,
2969
+ onChange
2970
+ });
2971
+ }
2765
2972
  /** 项目标题右侧的批量恢复/删除菜单,复用宿主菜单组件的键盘和焦点行为。 */
2766
2973
  function ArchivedGroupActions({ group, busy, onRestore, onDelete, t }) {
2767
2974
  const [open, setOpen] = (0, react.useState)(false);
@@ -2805,6 +3012,10 @@ window.__ModuleLoader__.load({
2805
3012
  function deriveArchivedBatchIds(archivedSessionIds, items, target) {
2806
3013
  const ids = [...new Set(archivedSessionIds ?? [])];
2807
3014
  if (target.scope === "all") return ids;
3015
+ if (target.scope === "sessions") {
3016
+ const selected = new Set(target.sessionIds ?? []);
3017
+ return ids.filter((id) => selected.has(id));
3018
+ }
2808
3019
  if (target.scope === "workspace") {
2809
3020
  const accounted = new Set(items.find((workspace) => workspace.workspaceId === target.workspaceId)?.sessionIds ?? []);
2810
3021
  return ids.filter((id) => accounted.has(id));
@@ -2812,6 +3023,24 @@ window.__ModuleLoader__.load({
2812
3023
  const accounted = new Set(items.flatMap((workspace) => workspace.sessionIds));
2813
3024
  return ids.filter((id) => !accounted.has(id));
2814
3025
  }
3026
+ /** 当前筛选结果中的可选会话;按列表显示顺序去重。 */
3027
+ function archivedSessionIdsInGroups(groups) {
3028
+ return [...new Set(groups.flatMap((group) => group.sessions.map((session) => session.id)))];
3029
+ }
3030
+ /** 只保留仍属于权威归档集合的选中项,避免对已变化的列表执行旧操作。 */
3031
+ function pruneArchivedSelection(selectedSessionIds, archivedSessionIds) {
3032
+ const archived = new Set(archivedSessionIds);
3033
+ return [...new Set(selectedSessionIds)].filter((id) => archived.has(id));
3034
+ }
3035
+ /** 向当前选择中加入或移除一组可见会话,供“全选筛选结果”复用。 */
3036
+ function toggleArchivedSelection(selectedSessionIds, sessionIds, checked) {
3037
+ const selected = new Set(selectedSessionIds);
3038
+ for (const sessionId of sessionIds) {
3039
+ if (checked) selected.add(sessionId);
3040
+ else selected.delete(sessionId);
3041
+ }
3042
+ return [...selected];
3043
+ }
2815
3044
  /** 把批量删除结果转换为准确区分“已删除”与“陈旧标记已清理”的反馈。 */
2816
3045
  function archivedDeleteFeedback(result, t) {
2817
3046
  const deleted = result.deletedSessionIds.length;
@@ -2898,6 +3127,7 @@ window.__ModuleLoader__.load({
2898
3127
  const [createdAtById, setCreatedAtById] = (0, react.useState)({});
2899
3128
  const [unarchivingSessionIds, setUnarchivingSessionIds] = (0, react.useState)(() => /* @__PURE__ */ new Set());
2900
3129
  const unarchivingSessionIdsRef = (0, react.useRef)(/* @__PURE__ */ new Set());
3130
+ const [selectedSessionIds, setSelectedSessionIds] = (0, react.useState)([]);
2901
3131
  const groups = (0, react.useMemo)(() => deriveArchivedGroups(sessions.byId, workspaceState.items, workspaceState.archivedSessionIds, t("group.ungrouped")), [sessions.byId, workspaceState, t]);
2902
3132
  const sortedGroups = (0, react.useMemo)(() => sortArchivedGroups(groups, sortBy, createdAtById, t), [groups, sortBy, createdAtById, t]);
2903
3133
  (0, react.useEffect)(() => {
@@ -2926,8 +3156,21 @@ window.__ModuleLoader__.load({
2926
3156
  sessions: group.sessions.filter((session) => normalizedQuery === "" || displayTitle(session, t).toLocaleLowerCase().includes(normalizedQuery))
2927
3157
  })).filter((group) => group.sessions.length > 0);
2928
3158
  }, [sortedGroups, project, query, t]);
3159
+ const visibleSessionIds = (0, react.useMemo)(() => archivedSessionIdsInGroups(filteredGroups), [filteredGroups]);
3160
+ const selectedSessionIdSet = (0, react.useMemo)(() => new Set(selectedSessionIds), [selectedSessionIds]);
3161
+ const selectedVisibleCount = visibleSessionIds.filter((sessionId) => selectedSessionIdSet.has(sessionId)).length;
3162
+ const allVisibleSelected = visibleSessionIds.length > 0 && selectedVisibleCount === visibleSessionIds.length;
2929
3163
  const allBatchTarget = { scope: "all" };
2930
3164
  const allBatchSessionIds = (0, react.useMemo)(() => deriveArchivedBatchIds(workspaceState.archivedSessionIds, workspaceState.items, allBatchTarget), [workspaceState.archivedSessionIds, workspaceState.items]);
3165
+ (0, react.useEffect)(() => {
3166
+ setSelectedSessionIds((current) => pruneArchivedSelection(current, workspaceState.archivedSessionIds));
3167
+ }, [workspaceState.archivedSessionIds]);
3168
+ const toggleSessionSelection = (sessionId, checked) => {
3169
+ setSelectedSessionIds((current) => toggleArchivedSelection(current, [sessionId], checked));
3170
+ };
3171
+ const toggleVisibleSelection = (checked) => {
3172
+ setSelectedSessionIds((current) => toggleArchivedSelection(current, visibleSessionIds, checked));
3173
+ };
2931
3174
  const onUnarchive = (sessionId) => {
2932
3175
  if (busy || unarchivingSessionIdsRef.current.has(sessionId)) return;
2933
3176
  unarchivingSessionIdsRef.current.add(sessionId);
@@ -2949,12 +3192,21 @@ window.__ModuleLoader__.load({
2949
3192
  try {
2950
3193
  const result = await unarchiveSessions(target);
2951
3194
  setNotice(t("archives.restoreSuccess", { n: result.unarchivedSessionIds.length }));
3195
+ return result;
2952
3196
  } catch (reason) {
2953
3197
  setError(t("archives.restoreBatchFailed", { detail: reason instanceof Error ? reason.message : String(reason) }));
2954
3198
  } finally {
2955
3199
  setBusy(false);
2956
3200
  }
2957
3201
  };
3202
+ const onSelectedUnarchive = async () => {
3203
+ if (selectedSessionIds.length === 0) return;
3204
+ const result = await onBatchUnarchive({ scope: "sessions", sessionIds: selectedSessionIds });
3205
+ if (result !== void 0) {
3206
+ const restored = new Set(result.unarchivedSessionIds);
3207
+ setSelectedSessionIds((current) => current.filter((sessionId) => !restored.has(sessionId)));
3208
+ }
3209
+ };
2958
3210
  const closeDelete = () => {
2959
3211
  if (!busy) setDeleteTarget(null);
2960
3212
  };
@@ -2981,6 +3233,10 @@ window.__ModuleLoader__.load({
2981
3233
  const feedback = archivedDeleteFeedback(result, t);
2982
3234
  if (feedback.kind === "error") setError(feedback.message);
2983
3235
  else setNotice(feedback.message);
3236
+ if (deleteTarget.target.scope === "sessions") {
3237
+ const completed = new Set([...result.deletedSessionIds, ...result.skippedSessionIds]);
3238
+ setSelectedSessionIds((current) => current.filter((sessionId) => !completed.has(sessionId)));
3239
+ }
2984
3240
  } else {
2985
3241
  await deleteSession(deleteTarget.session.id);
2986
3242
  }
@@ -2992,9 +3248,9 @@ window.__ModuleLoader__.load({
2992
3248
  }
2993
3249
  };
2994
3250
  const batchScope = deleteTarget?.kind === "batch" ? deleteTarget.target.scope : null;
2995
- const deleteDialogTitle = batchScope === "all" ? t("archives.deleteAllTitle") : batchScope === "ungrouped" ? t("archives.deleteUngroupedTitle") : batchScope === "workspace" ? t("archives.deleteProjectTitle", { name: deleteTarget.title }) : t("deleteSession.title");
2996
- const deleteDialogDescription = deleteTarget === null ? void 0 : batchScope === "all" ? t("archives.deleteAllDesc", { n: deleteTarget.count }) : batchScope === "ungrouped" ? t("archives.deleteUngroupedDesc", { n: deleteTarget.count }) : batchScope === "workspace" ? t("archives.deleteProjectDesc", { name: deleteTarget.title, n: deleteTarget.count }) : t("deleteSession.desc", { name: displayTitle(deleteTarget.session, t) });
2997
- const deleteConfirmLabel = batchScope === "all" ? t("archives.deleteAll") : batchScope === "ungrouped" ? t("archives.deleteUngroupedConfirm") : batchScope === "workspace" ? t("archives.deleteProjectConfirm") : t("deleteSession.title");
3251
+ const deleteDialogTitle = batchScope === "all" ? t("archives.deleteAllTitle") : batchScope === "ungrouped" ? t("archives.deleteUngroupedTitle") : batchScope === "workspace" ? t("archives.deleteProjectTitle", { name: deleteTarget.title }) : batchScope === "sessions" ? t("archives.deleteSelectedTitle") : t("deleteSession.title");
3252
+ const deleteDialogDescription = deleteTarget === null ? void 0 : batchScope === "all" ? t("archives.deleteAllDesc", { n: deleteTarget.count }) : batchScope === "ungrouped" ? t("archives.deleteUngroupedDesc", { n: deleteTarget.count }) : batchScope === "workspace" ? t("archives.deleteProjectDesc", { name: deleteTarget.title, n: deleteTarget.count }) : batchScope === "sessions" ? t("archives.deleteSelectedDesc", { n: deleteTarget.count }) : t("deleteSession.desc", { name: displayTitle(deleteTarget.session, t) });
3253
+ const deleteConfirmLabel = batchScope === "all" ? t("archives.deleteAll") : batchScope === "ungrouped" ? t("archives.deleteUngroupedConfirm") : batchScope === "workspace" ? t("archives.deleteProjectConfirm") : batchScope === "sessions" ? t("archives.deleteSelectedConfirm") : t("deleteSession.title");
2998
3254
  return (0, react_jsx_runtime.jsxs)("section", {
2999
3255
  className: "dsham_settings",
3000
3256
  "aria-label": t("archives.title"),
@@ -3075,6 +3331,11 @@ window.__ModuleLoader__.load({
3075
3331
  "archives.projectFilter": "按项目筛选",
3076
3332
  "archives.allProjects": "所有项目",
3077
3333
  "archives.sessionCount": "{n} 个聊天",
3334
+ "archives.selectAllFiltered": "全选当前筛选结果",
3335
+ "archives.selectSession": "选择会话“{name}”",
3336
+ "archives.selectedCount": "已选 {n} 个",
3337
+ "archives.restoreSelected": "恢复所选",
3338
+ "archives.deleteSelected": "删除所选",
3078
3339
  "archives.timestamp": "{date},{time}",
3079
3340
  "archives.restoreAll": "全部恢复",
3080
3341
  "archives.restoreProject": "恢复该项目的全部聊天",
@@ -3088,6 +3349,9 @@ window.__ModuleLoader__.load({
3088
3349
  "archives.deleteAll": "全部删除",
3089
3350
  "archives.deleteAllTitle": "删除全部已归档聊天",
3090
3351
  "archives.deleteAllDesc": "将永久删除全部 {n} 个已归档聊天及其子代理(含正在运行的)和记录,此操作不可恢复。",
3352
+ "archives.deleteSelectedTitle": "删除所选已归档聊天",
3353
+ "archives.deleteSelectedDesc": "将永久删除所选的 {n} 个已归档聊天及其子代理(含正在运行的)和记录。其他聊天不会受影响,此操作不可恢复。",
3354
+ "archives.deleteSelectedConfirm": "删除所选聊天",
3091
3355
  "archives.deleteAllPending": "正在删除已归档聊天…",
3092
3356
  "archives.deleteProjectTitle": "删除“{name}”中的已归档聊天",
3093
3357
  "archives.deleteProjectDesc": "将永久删除“{name}”中的 {n} 个已归档聊天及其子代理和记录。项目目录和未归档聊天不会受影响,此操作不可恢复。",
@@ -3191,6 +3455,11 @@ window.__ModuleLoader__.load({
3191
3455
  "archives.projectFilter": "Filter by project",
3192
3456
  "archives.allProjects": "All projects",
3193
3457
  "archives.sessionCount": "{n} chats",
3458
+ "archives.selectAllFiltered": "Select all results",
3459
+ "archives.selectSession": "Select chat {name}",
3460
+ "archives.selectedCount": "{n} selected",
3461
+ "archives.restoreSelected": "Restore selected",
3462
+ "archives.deleteSelected": "Delete selected",
3194
3463
  "archives.timestamp": "{date}, {time}",
3195
3464
  "archives.restoreAll": "Restore all",
3196
3465
  "archives.restoreProject": "Restore all chats in this project",
@@ -3204,6 +3473,9 @@ window.__ModuleLoader__.load({
3204
3473
  "archives.deleteAll": "Delete all",
3205
3474
  "archives.deleteAllTitle": "Delete all archived chats",
3206
3475
  "archives.deleteAllDesc": "This permanently deletes all {n} archived chats, their child agents (including any that are still running), and their records. This cannot be undone.",
3476
+ "archives.deleteSelectedTitle": "Delete selected archived chats",
3477
+ "archives.deleteSelectedDesc": "This permanently deletes the selected {n} archived chats, their child agents (including any that are still running), and their records. Other chats are not affected. This cannot be undone.",
3478
+ "archives.deleteSelectedConfirm": "Delete selected chats",
3207
3479
  "archives.deleteAllPending": "Deleting archived chats…",
3208
3480
  "archives.deleteProjectTitle": "Delete archived chats in {name}",
3209
3481
  "archives.deleteProjectDesc": "This permanently deletes the {n} archived chats in {name}, their child agents, and their records. The project directory and unarchived chats are not affected. This cannot be undone.",
@@ -3482,11 +3754,20 @@ window.__ModuleLoader__.load({
3482
3754
  });
3483
3755
  const browserFlowSource = flowSource("sidebar.workspaces.directoryFlow");
3484
3756
  const pickerFlowSource = flowSource("conversation.hero.workspace.directoryFlow");
3757
+ const refreshSessionList = async () => {
3758
+ if (typeof ctx.sessions.refresh !== "function") return;
3759
+ try {
3760
+ await ctx.sessions.refresh();
3761
+ } catch (error) {
3762
+ console.warn("archive-manager: restored archived sessions but session list refresh failed:", error);
3763
+ }
3764
+ };
3485
3765
  const unarchiveSession = async (sessionId) => {
3486
3766
  const registry = ctx.get("remote.workspaceRegistry");
3487
3767
  if (registry === void 0) throw new Error("archive-manager remote service is unavailable");
3488
3768
  const result = await registry.unarchiveSession(sessionId);
3489
3769
  if (!result.ok) throw new Error(result.error.message);
3770
+ await refreshSessionList();
3490
3771
  };
3491
3772
  const deleteSession = async (sessionId) => {
3492
3773
  const registry = ctx.get("remote.workspaceRegistry");
@@ -3499,6 +3780,7 @@ window.__ModuleLoader__.load({
3499
3780
  if (registry === void 0) throw new Error("archive-manager remote service is unavailable");
3500
3781
  const result = await registry.unarchiveSessions(target);
3501
3782
  if (!result.ok) throw new Error(result.error.message);
3783
+ await refreshSessionList();
3502
3784
  return result.value;
3503
3785
  };
3504
3786
  const deleteArchivedSessions = async (target) => {
@@ -3605,7 +3887,7 @@ window.__ModuleLoader__.load({
3605
3887
  archivedSessionMetadata,
3606
3888
  t: ctx.locale.bind(NS)
3607
3889
  })
3608
- }, ArchivedSessionsSection));
3890
+ }, ArchivedSessionsSectionPrototype));
3609
3891
  }
3610
3892
  //#endregion
3611
3893
  /** Pure derivation surface for dsh-archive-manager self-tests (no-op for the runtime). */
@@ -3620,6 +3902,9 @@ window.__ModuleLoader__.load({
3620
3902
  deriveArchivedGroups,
3621
3903
  sortArchivedGroups,
3622
3904
  deriveArchivedBatchIds,
3905
+ archivedSessionIdsInGroups,
3906
+ pruneArchivedSelection,
3907
+ toggleArchivedSelection,
3623
3908
  archivedDeleteFeedback,
3624
3909
  createWorkspaceViewStore,
3625
3910
  bindObservable,
package/lib/workspace.js CHANGED
@@ -84,7 +84,8 @@ const archivedBatchTargetSchema = {
84
84
  if (typeof value !== "object" || value === null || Array.isArray(value)) throw new TypeError("target must be an object");
85
85
  if (value.scope === "all" || value.scope === "ungrouped") return value;
86
86
  if (value.scope === "workspace" && typeof value.workspaceId === "string" && value.workspaceId.length > 0) return value;
87
- throw new TypeError("target.scope must be all, ungrouped, or workspace with a non-empty workspaceId");
87
+ if (value.scope === "sessions" && Array.isArray(value.sessionIds) && value.sessionIds.length > 0 && value.sessionIds.every((id) => typeof id === "string" && id.length > 0)) return value;
88
+ throw new TypeError("target.scope must be all, ungrouped, workspace with a non-empty workspaceId, or sessions with non-empty sessionIds");
88
89
  }
89
90
  };
90
91
  const unarchivedBatchSchema = {
@@ -400,6 +401,10 @@ var ArchiveWorkspaceRegistry = class extends WorkspaceRegistry {
400
401
  const state = this.requireState();
401
402
  const archivedSessionIds = [...new Set(state.archivedSessionIds)];
402
403
  if (target.scope === "all") return archivedSessionIds;
404
+ if (target.scope === "sessions") {
405
+ const selected = new Set(target.sessionIds);
406
+ return archivedSessionIds.filter((id) => selected.has(id));
407
+ }
403
408
  if (target.scope === "workspace") {
404
409
  const workspace = this.requireTable().get(target.workspaceId);
405
410
  if (workspace === void 0) throw new Error(`unknown workspace "${target.workspaceId}"`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@michengai/dsh-archive-manager",
3
- "version": "0.1.21",
3
+ "version": "0.1.22",
4
4
  "description": "NPM-installable DSH Web plugin for managing archived sessions.",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@11.21.0",