@chaoset/session-archive 0.1.3 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,25 @@
1
+ # Changelog
2
+
3
+ ## [0.1.3](https://github.com/winliyou/dsh-plugins) (2026-08-18)
4
+
5
+ ### Bug Fixes
6
+
7
+ * confirm-delete button hover: red text on red background was unreadable; changed to red background with white text
8
+
9
+ ## [0.1.2](https://github.com/winliyou/dsh-plugins) (2026-08-18)
10
+
11
+ ### Bug Fixes
12
+
13
+ * keep ghost archived id after deleting archived sessions so in-memory sessions don't reappear in the sidebar
14
+
15
+ ## [0.1.1](https://github.com/winliyou/dsh-plugins) (2026-08-17)
16
+
17
+ ### Bug Fixes
18
+
19
+ * archived sessions no longer mislabeled as running; restored selectable deletion
20
+
21
+ ## [0.1.0](https://github.com/winliyou/dsh-plugins) (2026-08-17)
22
+
23
+ ### Features
24
+
25
+ * initial session archive plugin: archived session list, read-only detail, batch restore and permanent delete
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 dsh-plugins contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -53,11 +53,11 @@ dsh plugin --profile web add @chaoset/session-archive
53
53
  | `list()` | — | `{ items: ArchiveRow[] }` |
54
54
  | `detail(sessionId)` | 会话 id | `{ sessionId, header, title, messageCount, messages, live }` |
55
55
  | `delete(sessionIds[])` | id 数组 | `{ deleted, failed, removedFromArchive }` |
56
+ | `unarchive(sessionIds[])` | id 数组 | `{ restored, removedFromArchive }` |
56
57
 
57
58
  > `delete` 的 `removedFromArchive` 恒为 0:删除保留归档 ghost id(见上),
58
59
  > 侧边栏不会重新显示已删除的会话;`unarchive` 的 `removedFromArchive` 为
59
60
  > 实际从归档集合移除的 id 数。
60
- | `unarchive(sessionIds[])` | id 数组 | `{ restored, removedFromArchive }` |
61
61
 
62
62
  `ArchiveRow`:`{ sessionId, title, cwd, createdAt, updatedAt, size, live }`。
63
63
 
package/client/client.cjs CHANGED
@@ -3,15 +3,52 @@ window.__ModuleLoader__.load({
3
3
  factory: (require) => {
4
4
  var module = { exports: {} };
5
5
  var exports = module.exports;
6
- let react = require("react");
6
+ "use strict";
7
+ var __create = Object.create;
8
+ var __defProp = Object.defineProperty;
9
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
10
+ var __getOwnPropNames = Object.getOwnPropertyNames;
11
+ var __getProtoOf = Object.getPrototypeOf;
12
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
13
+ var __export = (target, all) => {
14
+ for (var name in all)
15
+ __defProp(target, name, { get: all[name], enumerable: true });
16
+ };
17
+ var __copyProps = (to, from, except, desc) => {
18
+ if (from && typeof from === "object" || typeof from === "function") {
19
+ for (let key of __getOwnPropNames(from))
20
+ if (!__hasOwnProp.call(to, key) && key !== except)
21
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
22
+ }
23
+ return to;
24
+ };
25
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
26
+ // If the importer is in node compatibility mode or this is not an ESM
27
+ // file that has been converted to a CommonJS file using a Babel-
28
+ // compatible transform (i.e. "__esModule" has not been set), then set
29
+ // "default" to the CommonJS "module.exports" for node compatibility.
30
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
31
+ mod
32
+ ));
33
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
7
34
 
8
- // ── 样式(注入 style 标签,复用 DSH 设计变量)────────────────────────
35
+ // client/index.tsx
36
+ var index_exports = {};
37
+ __export(index_exports, {
38
+ apply: () => apply,
39
+ inject: () => inject
40
+ });
41
+ module.exports = __toCommonJS(index_exports);
42
+ var React = __toESM(require("react"), 1);
9
43
  var css = [
10
44
  ".sa_badge{width:100%;height:49px;color:var(--dsw-alias-label-primary);cursor:pointer;background:0 0;border:none;border-radius:12px;align-items:center;gap:8px;padding:0 8px 0 6px;font-family:inherit;font-size:14px;display:inline-flex;overflow:hidden}",
11
45
  ".sa_badge:hover{background:var(--dsw-alias-interactive-bg-hover)}",
12
- ".sa_badgeIcon{flex:none;font-size:15px;line-height:20px}",
46
+ ".sa_badgeIcon{flex:none;display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;color:var(--dsw-alias-label-primary)}",
47
+ ".sa_badgeIcon svg{width:18px;height:18px;display:block}",
13
48
  ".sa_badgeLabel{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}",
14
49
  ".sa_badgeCount{color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums;flex:none;margin-left:auto;font-size:12px;line-height:16px}",
50
+ ".sa_badge--collapsed{width:auto;justify-content:center;padding:0}",
51
+ ".sa_badge--collapsed .sa_badgeLabel,.sa_badge--collapsed .sa_badgeCount{display:none}",
15
52
  ".sa_panel{z-index:30;border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-base);width:440px;max-width:calc(100vw - 24px);max-height:62vh;box-shadow:var(--dsw-shadow-lv2);--dsh-scrollbar-thumb:var(--dsw-alias-scrollbar-bg-l2);--dsh-scrollbar-thumb-hover:var(--dsw-alias-scrollbar-hover-l2);border-radius:12px;flex-direction:column;display:flex;position:fixed;bottom:128px;left:12px;overflow:hidden}",
16
53
  ".sa_header{box-sizing:border-box;border-bottom:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-base);flex:none;justify-content:space-between;align-items:center;min-height:44px;padding:8px 12px;display:flex}",
17
54
  ".sa_title{color:var(--dsw-alias-label-primary);font-size:13px;font-weight:500;line-height:20px}",
@@ -55,49 +92,48 @@ window.__ModuleLoader__.load({
55
92
  ".sa_busy{opacity:.55;pointer-events:none}"
56
93
  ].join("");
57
94
  var tagId = "@chaoset/session-archive/client.css";
58
- if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=\"" + tagId + "\"]") === null) {
59
- var tag = document.createElement("style");
95
+ if (typeof document !== "undefined" && document.querySelector('style[data-plugin-css="' + tagId + '"]') === null) {
96
+ tag = document.createElement("style");
60
97
  tag.dataset.plugin = "@chaoset/session-archive";
61
98
  tag.dataset.pluginCss = tagId;
62
99
  tag.textContent = css;
63
100
  document.head.appendChild(tag);
64
101
  }
65
-
66
- // ── 字典 ─────────────────────────────────────────────────────────────
67
- const NS = "sidebar.sessionArchive";
68
- const zh = {
69
- badge: "归档",
70
- panelTitle: "归档会话",
71
- refresh: "刷新",
72
- close: "关闭",
73
- empty: "暂无归档会话。在会话列表的更多菜单中归档会话后会出现在这里。",
74
- loadFailed: "加载归档列表失败",
75
- selectAll: "全选",
76
- selected: "已选 {n} 项",
77
- restore: "恢复所选",
78
- restoreDone: "已恢复 {n} 个会话",
79
- restoreFailed: "恢复失败",
80
- delete: "删除所选",
81
- deleteConfirm: "再次点击确认删除",
82
- deleteDone: "已删除 {n} 个会话",
83
- deleteFailed: "部分删除失败:{n} ",
84
- confirmAll: "确认删除全部 {n} 个?",
85
- noSelection: "请先勾选会话",
86
- view: "查看",
87
- collapse: "收起",
88
- live: "运行中",
89
- detailLoadFailed: "读取会话内容失败",
90
- noneTitle: "(无标题)",
91
- user: "用户",
92
- assistant: "助手",
93
- messages: "共 {n} 条消息",
94
- noMessages: "(无文本消息)",
102
+ var tag;
103
+ var NS = "sidebar.sessionArchive";
104
+ var zh = {
105
+ badge: "\u5F52\u6863",
106
+ panelTitle: "\u5F52\u6863\u4F1A\u8BDD",
107
+ refresh: "\u5237\u65B0",
108
+ close: "\u5173\u95ED",
109
+ empty: "\u6682\u65E0\u5F52\u6863\u4F1A\u8BDD\u3002\u5728\u4F1A\u8BDD\u5217\u8868\u7684\u66F4\u591A\u83DC\u5355\u4E2D\u5F52\u6863\u4F1A\u8BDD\u540E\u4F1A\u51FA\u73B0\u5728\u8FD9\u91CC\u3002",
110
+ loadFailed: "\u52A0\u8F7D\u5F52\u6863\u5217\u8868\u5931\u8D25",
111
+ selectAll: "\u5168\u9009",
112
+ selected: "\u5DF2\u9009 {n} \u9879",
113
+ restore: "\u6062\u590D\u6240\u9009",
114
+ restoreDone: "\u5DF2\u6062\u590D {n} \u4E2A\u5F52\u6863\u4F1A\u8BDD",
115
+ restoreFailed: "\u6062\u590D\u5931\u8D25",
116
+ delete: "\u5220\u9664\u6240\u9009",
117
+ deleteConfirm: "\u518D\u6B21\u70B9\u51FB\u786E\u8BA4\u5220\u9664",
118
+ deleteDone: "\u5DF2\u5220\u9664 {n} \u4E2A\u5F52\u6863\u4F1A\u8BDD",
119
+ deleteFailed: "\u90E8\u5206\u5220\u9664\u5931\u8D25\uFF1A{n} \u4E2A",
120
+ confirmAll: "\u786E\u8BA4\u5220\u9664\u5168\u90E8 {n} \u4E2A\uFF1F",
121
+ noSelection: "\u8BF7\u5148\u52FE\u9009\u4F1A\u8BDD",
122
+ view: "\u67E5\u770B",
123
+ collapse: "\u6536\u8D77",
124
+ live: "\u8FD0\u884C\u4E2D",
125
+ detailLoadFailed: "\u8BFB\u53D6\u4F1A\u8BDD\u5185\u5BB9\u5931\u8D25",
126
+ noneTitle: "\uFF08\u65E0\u6807\u9898\uFF09",
127
+ user: "\u7528\u6237",
128
+ assistant: "\u52A9\u624B",
129
+ messages: "\u5171 {n} \u6761\u6D88\u606F",
130
+ noMessages: "\uFF08\u65E0\u6587\u672C\u6D88\u606F\uFF09",
95
131
  sizeBytes: "{n} B",
96
132
  sizeKB: "{n} KB",
97
133
  sizeMB: "{n} MB",
98
- runningHint: "运行中的会话不能删除,请先停止"
134
+ runningHint: "\u8FD0\u884C\u4E2D\u7684\u4F1A\u8BDD\u4E0D\u80FD\u5220\u9664\uFF0C\u8BF7\u5148\u505C\u6B62"
99
135
  };
100
- const en = {
136
+ var en = {
101
137
  badge: "Archive",
102
138
  panelTitle: "Archived sessions",
103
139
  refresh: "Refresh",
@@ -107,11 +143,11 @@ window.__ModuleLoader__.load({
107
143
  selectAll: "Select all",
108
144
  selected: "{n} selected",
109
145
  restore: "Restore",
110
- restoreDone: "Restored {n} sessions",
146
+ restoreDone: "Restored {n} archived sessions",
111
147
  restoreFailed: "Restore failed",
112
148
  delete: "Delete",
113
149
  deleteConfirm: "Click again to confirm delete",
114
- deleteDone: "Deleted {n} sessions",
150
+ deleteDone: "Deleted {n} archived sessions",
115
151
  deleteFailed: "Some deletions failed: {n}",
116
152
  confirmAll: "Delete all {n}?",
117
153
  noSelection: "Select sessions first",
@@ -129,38 +165,54 @@ window.__ModuleLoader__.load({
129
165
  sizeMB: "{n} MB",
130
166
  runningHint: "Running sessions cannot be deleted; stop them first"
131
167
  };
132
-
133
- // ── 工具函数 ─────────────────────────────────────────────────────────
134
168
  function formatBytes(bytes, t) {
135
169
  if (bytes < 1024) return t("sizeBytes").replace("{n}", String(bytes));
136
170
  if (bytes < 1024 * 1024) return t("sizeKB").replace("{n}", (bytes / 1024).toFixed(1));
137
171
  return t("sizeMB").replace("{n}", (bytes / (1024 * 1024)).toFixed(1));
138
172
  }
139
173
  function formatTime(ms) {
140
- try { return new Date(ms).toLocaleString(); } catch { return String(ms); }
174
+ try {
175
+ return new Date(ms).toLocaleString();
176
+ } catch {
177
+ return String(ms);
178
+ }
141
179
  }
142
180
  function shortId(id) {
143
- return id.length > 12 ? id.slice(0, 12) + "" : id;
181
+ return id.length > 12 ? id.slice(0, 12) + "\u2026" : id;
144
182
  }
145
-
146
- // ── 归档面板 ─────────────────────────────────────────────────────────
147
183
  function ArchivePanel(props) {
148
184
  const t = props.t;
149
185
  const call = props.call;
150
- const rootRef = react.useRef(null);
151
- const [open, setOpen] = react.useState(false);
152
- const [items, setItems] = react.useState([]);
153
- const [loading, setLoading] = react.useState(false);
154
- const [error, setError] = react.useState(null);
155
- const [selected, setSelected] = react.useState(new Set());
156
- const [expanded, setExpanded] = react.useState(null);
157
- const [details, setDetails] = react.useState(new Map());
158
- const [detailLoading, setDetailLoading] = react.useState(new Set());
159
- const [busy, setBusy] = react.useState(false);
160
- const [confirmingDelete, setConfirmingDelete] = react.useState(false);
161
- const [notice, setNotice] = react.useState(null);
162
-
163
- const load = react.useCallback(async () => {
186
+ const wideExplicit = typeof props.wide === "boolean" ? props.wide : void 0;
187
+ const [narrow, setNarrow] = React.useState(false);
188
+ const collapsed = wideExplicit === void 0 ? narrow : !wideExplicit;
189
+ const badgeRef = React.useRef(null);
190
+ React.useEffect(() => {
191
+ const el = badgeRef.current?.parentElement;
192
+ if (el === void 0 || el === null || typeof globalThis.ResizeObserver === "undefined") return;
193
+ const ro = new globalThis.ResizeObserver((entries) => {
194
+ for (const entry of entries) {
195
+ const w = entry.contentRect?.width ?? el.clientWidth;
196
+ setNarrow(w > 0 && w < 80);
197
+ }
198
+ });
199
+ ro.observe(el);
200
+ return () => ro.disconnect();
201
+ }, []);
202
+ const iconOnly = collapsed;
203
+ const rootRef = React.useRef(null);
204
+ const [open, setOpen] = React.useState(false);
205
+ const [items, setItems] = React.useState([]);
206
+ const [loading, setLoading] = React.useState(false);
207
+ const [error, setError] = React.useState(null);
208
+ const [selected, setSelected] = React.useState(/* @__PURE__ */ new Set());
209
+ const [expanded, setExpanded] = React.useState(null);
210
+ const [details, setDetails] = React.useState(/* @__PURE__ */ new Map());
211
+ const [detailLoading, setDetailLoading] = React.useState(/* @__PURE__ */ new Set());
212
+ const [busy, setBusy] = React.useState(false);
213
+ const [confirmingDelete, setConfirmingDelete] = React.useState(false);
214
+ const [notice, setNotice] = React.useState(null);
215
+ const load = React.useCallback(async () => {
164
216
  setLoading(true);
165
217
  setError(null);
166
218
  try {
@@ -172,12 +224,38 @@ window.__ModuleLoader__.load({
172
224
  setLoading(false);
173
225
  }
174
226
  }, [call, t]);
175
-
176
- react.useEffect(() => {
177
- if (open) load();
227
+ React.useEffect(() => {
228
+ if (open) {
229
+ setNotice(null);
230
+ load();
231
+ }
178
232
  }, [open, load]);
179
-
180
- react.useEffect(() => {
233
+ const refreshSilently = React.useCallback(async () => {
234
+ try {
235
+ const result = await call("list");
236
+ setItems(Array.isArray(result.items) ? result.items : []);
237
+ } catch {
238
+ }
239
+ }, [call]);
240
+ React.useEffect(() => {
241
+ if (open) return;
242
+ refreshSilently();
243
+ const id = globalThis.setInterval(refreshSilently, 5e3);
244
+ const onVis = () => {
245
+ if (document.visibilityState === "visible") refreshSilently();
246
+ };
247
+ document.addEventListener("visibilitychange", onVis);
248
+ return () => {
249
+ clearInterval(id);
250
+ document.removeEventListener("visibilitychange", onVis);
251
+ };
252
+ }, [open, refreshSilently]);
253
+ React.useEffect(() => {
254
+ if (notice === null) return;
255
+ const id = globalThis.setTimeout(() => setNotice(null), 4e3);
256
+ return () => clearTimeout(id);
257
+ }, [notice]);
258
+ React.useEffect(() => {
181
259
  if (!open) return;
182
260
  const onPointerDown = (event) => {
183
261
  if (rootRef.current !== null && !rootRef.current.contains(event.target)) {
@@ -187,25 +265,23 @@ window.__ModuleLoader__.load({
187
265
  document.addEventListener("pointerdown", onPointerDown);
188
266
  return () => document.removeEventListener("pointerdown", onPointerDown);
189
267
  }, [open]);
190
-
191
268
  const selectable = items.filter((item) => !item.live);
192
269
  const allSelected = selectable.length > 0 && selectable.every((item) => selected.has(item.sessionId));
193
-
194
270
  const toggleAll = (checked) => {
195
271
  if (checked) {
196
272
  setSelected(new Set(selectable.map((item) => item.sessionId)));
197
273
  } else {
198
- setSelected(new Set());
274
+ setSelected(/* @__PURE__ */ new Set());
199
275
  }
200
276
  };
201
277
  const toggleOne = (sessionId, checked) => {
202
278
  setSelected((current) => {
203
279
  const next = new Set(current);
204
- if (checked) next.add(sessionId); else next.delete(sessionId);
280
+ if (checked) next.add(sessionId);
281
+ else next.delete(sessionId);
205
282
  return next;
206
283
  });
207
284
  };
208
-
209
285
  const toggleDetail = (item) => {
210
286
  if (expanded === item.sessionId) {
211
287
  setExpanded(null);
@@ -229,7 +305,6 @@ window.__ModuleLoader__.load({
229
305
  });
230
306
  }
231
307
  };
232
-
233
308
  const runBatch = async (action, doneKey, failKey) => {
234
309
  const ids = [...selected];
235
310
  if (ids.length === 0) {
@@ -251,7 +326,7 @@ window.__ModuleLoader__.load({
251
326
  const done = new Set(doneIds);
252
327
  setItems((current) => current.filter((item) => !done.has(item.sessionId)));
253
328
  }
254
- setSelected(new Set());
329
+ setSelected(/* @__PURE__ */ new Set());
255
330
  setConfirmingDelete(false);
256
331
  await load();
257
332
  } catch (actionError) {
@@ -264,49 +339,60 @@ window.__ModuleLoader__.load({
264
339
  const deleteSelected = () => {
265
340
  if (!confirmingDelete) {
266
341
  setConfirmingDelete(true);
267
- window.setTimeout(() => setConfirmingDelete(false), 4000);
342
+ window.setTimeout(() => setConfirmingDelete(false), 4e3);
268
343
  return;
269
344
  }
270
345
  runBatch("delete", "deleteDone", "deleteFailed");
271
346
  };
272
-
273
347
  const hasSelection = selected.size > 0;
274
-
275
- return react.createElement(
348
+ return React.createElement(
276
349
  "div",
277
350
  { className: "sa_root", ref: rootRef },
278
- react.createElement(
351
+ React.createElement(
279
352
  "button",
280
353
  {
281
- className: "sa_badge",
354
+ ref: badgeRef,
355
+ className: "sa_badge" + (iconOnly ? " sa_badge--collapsed" : ""),
282
356
  type: "button",
283
357
  onClick: () => setOpen(!open),
284
358
  "aria-expanded": open,
285
359
  title: t("badge")
286
360
  },
287
- react.createElement("span", { className: "sa_badgeIcon", "aria-hidden": true }, "🗂"),
288
- react.createElement("span", { className: "sa_badgeLabel" }, t("badge")),
289
- react.createElement("span", { className: "sa_badgeCount" }, String(items.length))
361
+ React.createElement(
362
+ "span",
363
+ { className: "sa_badgeIcon", "aria-hidden": true },
364
+ React.createElement(
365
+ "svg",
366
+ { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round" },
367
+ React.createElement("rect", { x: 3, y: 4, width: 18, height: 4, rx: 1 }),
368
+ React.createElement("path", { d: "M5 8v11a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V8" }),
369
+ React.createElement("path", { d: "M10 12h4" })
370
+ )
371
+ ),
372
+ iconOnly ? null : React.createElement("span", { className: "sa_badgeLabel" }, t("badge")),
373
+ iconOnly ? null : React.createElement("span", { className: "sa_badgeCount" }, String(items.length))
290
374
  ),
291
- open ? react.createElement(
375
+ open ? React.createElement(
292
376
  "div",
293
377
  { className: "sa_panel" },
294
- react.createElement(
378
+ React.createElement(
295
379
  "div",
296
380
  { className: "sa_header" },
297
- react.createElement("span", { className: "sa_title" }, t("panelTitle")),
298
- react.createElement(
381
+ React.createElement("span", { className: "sa_title" }, t("panelTitle")),
382
+ React.createElement(
299
383
  "span",
300
384
  { style: { display: "inline-flex", gap: "6px", alignItems: "center" } },
301
- react.createElement("button", { className: "sa_refresh", type: "button", title: t("refresh"), disabled: busy || loading, onClick: load }, t("refresh")),
302
- react.createElement("button", { className: "sa_iconBtn", type: "button", title: t("close"), disabled: busy, onClick: () => setOpen(false) }, "")
385
+ React.createElement("button", { className: "sa_refresh", type: "button", title: t("refresh"), disabled: busy || loading, onClick: load }, t("refresh")),
386
+ React.createElement("button", { className: "sa_iconBtn", type: "button", title: t("close"), disabled: busy, onClick: () => setOpen(false) }, "\u2715")
303
387
  )
304
388
  ),
305
- react.createElement(
389
+ React.createElement(
306
390
  "div",
307
391
  { className: "sa_toolbar" },
308
- react.createElement("label", { className: "sa_toolLabel" },
309
- react.createElement("input", {
392
+ React.createElement(
393
+ "label",
394
+ { className: "sa_toolLabel" },
395
+ React.createElement("input", {
310
396
  className: "sa_check",
311
397
  type: "checkbox",
312
398
  checked: allSelected,
@@ -315,41 +401,41 @@ window.__ModuleLoader__.load({
315
401
  }),
316
402
  t("selectAll")
317
403
  ),
318
- react.createElement("span", { className: "sa_count" }, t("selected").replace("{n}", String(selected.size))),
319
- react.createElement("button", {
404
+ React.createElement("span", { className: "sa_count" }, t("selected").replace("{n}", String(selected.size))),
405
+ React.createElement("button", {
320
406
  className: "sa_action",
321
407
  type: "button",
322
408
  disabled: busy || !hasSelection,
323
409
  onClick: restoreSelected
324
410
  }, t("restore")),
325
- react.createElement("button", {
411
+ React.createElement("button", {
326
412
  className: "sa_action " + (confirmingDelete ? "sa_actionDanger sa_confirm" : "sa_actionDanger"),
327
413
  type: "button",
328
414
  disabled: busy || !hasSelection,
329
415
  onClick: deleteSelected
330
416
  }, confirmingDelete ? t("deleteConfirm") : t("delete"))
331
417
  ),
332
- react.createElement(
418
+ React.createElement(
333
419
  "div",
334
420
  { className: "sa_body" },
335
- notice !== null ? react.createElement("p", { className: "sa_error" + (notice.kind === "ok" ? "" : ""), role: "status" }, notice.text) : null,
336
- error !== null ? react.createElement("p", { className: "sa_error", role: "alert" }, error) : null,
337
- loading && items.length === 0 ? react.createElement("p", { className: "sa_empty" }, "") : null,
338
- !loading && items.length === 0 && error === null ? react.createElement("p", { className: "sa_empty" }, t("empty")) : null,
339
- items.length > 0 ? react.createElement(
421
+ notice !== null ? React.createElement("p", { className: "sa_error" + (notice.kind === "ok" ? "" : ""), role: "status" }, notice.text) : null,
422
+ error !== null ? React.createElement("p", { className: "sa_error", role: "alert" }, error) : null,
423
+ loading && items.length === 0 ? React.createElement("p", { className: "sa_empty" }, "\u2026") : null,
424
+ !loading && items.length === 0 && error === null ? React.createElement("p", { className: "sa_empty" }, t("empty")) : null,
425
+ items.length > 0 ? React.createElement(
340
426
  "ul",
341
427
  { className: "sa_rows" },
342
428
  items.map((item) => {
343
429
  const isExpanded = expanded === item.sessionId;
344
430
  const detail = details.get(item.sessionId);
345
431
  const detailPending = detailLoading.has(item.sessionId);
346
- return react.createElement(
432
+ return React.createElement(
347
433
  "li",
348
434
  { className: "sa_row" + (busy ? " sa_busy" : ""), key: item.sessionId },
349
- react.createElement(
435
+ React.createElement(
350
436
  "div",
351
437
  { className: "sa_rowHead" },
352
- react.createElement("input", {
438
+ React.createElement("input", {
353
439
  className: "sa_check",
354
440
  type: "checkbox",
355
441
  checked: selected.has(item.sessionId),
@@ -357,31 +443,33 @@ window.__ModuleLoader__.load({
357
443
  title: item.live ? t("runningHint") : void 0,
358
444
  onChange: (e) => toggleOne(item.sessionId, e.target.checked)
359
445
  }),
360
- react.createElement(
446
+ React.createElement(
361
447
  "span",
362
448
  { className: "sa_rowTitle", onClick: () => toggleDetail(item), title: t("view") },
363
449
  item.title !== null && item.title !== void 0 && item.title !== "" ? item.title : t("noneTitle")
364
450
  ),
365
- item.live ? react.createElement("span", { className: "sa_live" }, t("live")) : null
451
+ item.live ? React.createElement("span", { className: "sa_live" }, t("live")) : null
366
452
  ),
367
- react.createElement("div", { className: "sa_rowMeta" },
368
- react.createElement("span", null, formatTime(item.updatedAt)),
369
- item.cwd !== null && item.cwd !== void 0 ? react.createElement("span", null, " · ", react.createElement("code", null, item.cwd)) : null,
370
- react.createElement("span", null, " · ", formatBytes(item.size, t))
453
+ React.createElement(
454
+ "div",
455
+ { className: "sa_rowMeta" },
456
+ React.createElement("span", null, formatTime(item.updatedAt)),
457
+ item.cwd !== null && item.cwd !== void 0 ? React.createElement("span", null, " \xB7 ", React.createElement("code", null, item.cwd)) : null,
458
+ React.createElement("span", null, " \xB7 ", formatBytes(item.size, t))
371
459
  ),
372
- react.createElement(
460
+ React.createElement(
373
461
  "div",
374
462
  { className: "sa_rowFoot" },
375
- react.createElement("span", { className: "sa_rowMeta" },
376
- react.createElement("code", null, shortId(item.sessionId)),
377
- detail !== void 0 && !detail.error && detail.messageCount !== void 0
378
- ? " · " + t("messages").replace("{n}", String(detail.messageCount))
379
- : null
463
+ React.createElement(
464
+ "span",
465
+ { className: "sa_rowMeta" },
466
+ React.createElement("code", null, shortId(item.sessionId)),
467
+ detail !== void 0 && !detail.error && detail.messageCount !== void 0 ? " \xB7 " + t("messages").replace("{n}", String(detail.messageCount)) : null
380
468
  ),
381
- react.createElement(
469
+ React.createElement(
382
470
  "div",
383
471
  { className: "sa_rowActions" },
384
- react.createElement("button", {
472
+ React.createElement("button", {
385
473
  className: "sa_action",
386
474
  type: "button",
387
475
  disabled: busy || detailPending,
@@ -389,19 +477,16 @@ window.__ModuleLoader__.load({
389
477
  }, isExpanded ? t("collapse") : t("view"))
390
478
  )
391
479
  ),
392
- isExpanded ? react.createElement(
480
+ isExpanded ? React.createElement(
393
481
  "div",
394
482
  { className: "sa_detail" },
395
- detailPending ? react.createElement("p", { className: "sa_empty" }, "") : null,
396
- detail === void 0 ? null :
397
- detail.error !== void 0 ? react.createElement("p", { className: "sa_error" }, detail.error) :
398
- detail.messages.length === 0 ? react.createElement("p", { className: "sa_empty" }, t("noMessages")) :
399
- detail.messages.map((message, index) => react.createElement(
400
- "div",
401
- { className: "sa_msg", key: index },
402
- react.createElement("span", { className: "sa_msgRole" }, message.role === "user" ? t("user") : t("assistant") + " · " + formatTime(message.time)),
403
- react.createElement("span", { className: "sa_msgText" + (message.role === "user" ? " sa_msgTextUser" : "") }, message.text)
404
- ))
483
+ detailPending ? React.createElement("p", { className: "sa_empty" }, "\u2026") : null,
484
+ detail === void 0 ? null : detail.error !== void 0 ? React.createElement("p", { className: "sa_error" }, detail.error) : detail.messages.length === 0 ? React.createElement("p", { className: "sa_empty" }, t("noMessages")) : detail.messages.map((message, index) => React.createElement(
485
+ "div",
486
+ { className: "sa_msg", key: index },
487
+ React.createElement("span", { className: "sa_msgRole" }, message.role === "user" ? t("user") : t("assistant") + " \xB7 " + formatTime(message.time)),
488
+ React.createElement("span", { className: "sa_msgText" + (message.role === "user" ? " sa_msgTextUser" : "") }, message.text)
489
+ ))
405
490
  ) : null
406
491
  );
407
492
  })
@@ -410,13 +495,9 @@ window.__ModuleLoader__.load({
410
495
  ) : null
411
496
  );
412
497
  }
413
-
414
- // ── 插件 apply ───────────────────────────────────────────────────────
415
- // DSH 客户端的 remote.<ns> 服务不会自动生成:必须由客户端代码用
416
- // ctx.remote.$mount(contribution) 显式挂载(官方 dsh-api-remotes 即如此)。
417
- const inject = ["slots", "locale", "remote"];
418
- const passthroughSchema = { parse: (value) => value };
419
- const REMOTE_CONTRIBUTION = {
498
+ var inject = ["slots", "locale", "remote"];
499
+ var passthroughSchema = { parse: (value) => value };
500
+ var REMOTE_CONTRIBUTION = {
420
501
  package: "@chaoset/session-archive",
421
502
  descriptors: [
422
503
  { id: "@chaoset/session-archive#sessionArchive/list", service: "sessionArchive", namespace: "sessionArchive", method: "list", invocation: { kind: "direct" }, parameters: [], result: { mode: "strict", typeSymbol: "sessionArchive/list:result", schema: passthroughSchema } },
@@ -444,8 +525,6 @@ window.__ModuleLoader__.load({
444
525
  }, ArchivePanel));
445
526
  }
446
527
 
447
- exports.apply = apply;
448
- exports.inject = inject;
449
528
  return module.exports;
450
529
  }
451
530
  });