@lijian-ui/dsh-session-cleaner 0.1.2 → 0.1.3

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/lib/client.js CHANGED
@@ -1,751 +1,733 @@
1
- window.__ModuleLoader__.load({ id: "@lijian-ui/dsh-session-cleaner", factory: (require) => {
2
- var module = { exports: {} }; var exports = module.exports;
3
- //#region rolldown:runtime
4
- var __create = Object.create;
5
- var __defProp = Object.defineProperty;
6
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
7
- var __getOwnPropNames = Object.getOwnPropertyNames;
8
- var __getProtoOf = Object.getPrototypeOf;
9
- var __hasOwnProp = Object.prototype.hasOwnProperty;
10
- var __copyProps = (to, from, except, desc) => {
11
- if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
12
- key = keys[i];
13
- if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
14
- get: ((k) => from[k]).bind(null, key),
15
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
16
- });
17
- }
18
- return to;
19
- };
20
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
21
- value: mod,
22
- enumerable: true
23
- }) : target, mod));
24
-
25
- //#endregion
26
- const react = __toESM(require("react"));
27
-
28
- //#region src/client/SessionManagerSection.ts
29
- const page = {
30
- display: "flex",
31
- flexDirection: "column",
32
- gap: 12,
33
- padding: "4px 0 24px",
34
- width: "100%",
35
- minHeight: 400
36
- };
37
- const header = {
38
- display: "flex",
39
- alignItems: "center",
40
- justifyContent: "space-between",
41
- padding: "14px 0",
42
- flexShrink: 0,
43
- borderBottom: "1px solid var(--dsw-alias-border-l2, #eee)"
44
- };
45
- const title = {
46
- fontSize: 17,
47
- fontWeight: 500,
48
- color: "var(--dsw-alias-label-primary, inherit)",
49
- margin: 0,
50
- whiteSpace: "nowrap"
51
- };
52
- const countBadge = {
53
- display: "inline-flex",
54
- alignItems: "center",
55
- justifyContent: "center",
56
- minWidth: 22,
57
- height: 22,
58
- padding: "0 7px",
59
- borderRadius: 999,
60
- background: "var(--dsw-alias-bg-layer-3, #eee)",
61
- color: "var(--dsw-alias-label-secondary, #555)",
62
- fontSize: 11,
63
- fontWeight: 500,
64
- marginLeft: 8
65
- };
66
- const toolbar = {
67
- display: "flex",
68
- alignItems: "center",
69
- gap: 8
70
- };
71
- const btnGhost = {
72
- display: "inline-flex",
73
- alignItems: "center",
74
- gap: 6,
75
- height: 30,
76
- padding: "0 12px",
77
- background: "transparent",
78
- color: "var(--dsw-alias-label-primary, inherit)",
79
- border: "1px solid var(--dsw-alias-border-l2, #ddd)",
80
- borderRadius: 6,
81
- fontSize: 12,
82
- cursor: "pointer"
83
- };
84
- const btnDanger = {
85
- display: "inline-flex",
86
- alignItems: "center",
87
- gap: 6,
88
- height: 26,
89
- padding: "0 10px",
90
- background: "transparent",
91
- color: "var(--dsw-alias-state-error-primary, #e24b4a)",
92
- border: "1px solid var(--dsw-alias-state-error-primary, #e24b4a)",
93
- borderRadius: 6,
94
- fontSize: 12,
95
- cursor: "pointer"
96
- };
97
- const btnDangerSolid = {
98
- display: "inline-flex",
99
- alignItems: "center",
100
- gap: 6,
101
- height: 30,
102
- padding: "0 14px",
103
- background: "var(--dsw-alias-state-error-primary, #e24b4a)",
104
- color: "#fff",
105
- border: "none",
106
- borderRadius: 6,
107
- fontSize: 12,
108
- fontWeight: 500,
109
- cursor: "pointer"
110
- };
111
- const tabs = {
112
- display: "flex",
113
- gap: 4,
114
- borderBottom: "1px solid var(--dsw-alias-border-l2, #eee)",
115
- paddingBottom: 0
116
- };
117
- const tabStyle = {
118
- padding: "8px 14px",
119
- fontSize: 13,
120
- color: "var(--dsw-alias-label-secondary, #666)",
121
- background: "transparent",
122
- border: "none",
123
- borderBottom: "2px solid transparent",
124
- cursor: "pointer"
125
- };
126
- const tabStyleActive = {
127
- ...tabStyle,
128
- color: "var(--dsw-alias-state-business-primary, #185FA5)",
129
- borderBottomColor: "var(--dsw-alias-state-business-primary, #185FA5)",
130
- fontWeight: 500
131
- };
132
- const list = {
133
- display: "flex",
134
- flexDirection: "column",
135
- gap: 6
136
- };
137
- const row = {
138
- display: "flex",
139
- alignItems: "center",
140
- gap: 12,
141
- padding: "10px 12px",
142
- borderRadius: 8,
143
- background: "var(--dsw-alias-bg-layer-2, #fafafa)",
144
- border: "1px solid var(--dsw-alias-border-l3, #f0f0f0)",
145
- cursor: "pointer"
146
- };
147
- const rowMain = {
148
- flex: 1,
149
- minWidth: 0,
150
- display: "flex",
151
- flexDirection: "column",
152
- gap: 2
153
- };
154
- const rowTitle = {
155
- fontSize: 13,
156
- fontWeight: 500,
157
- color: "var(--dsw-alias-label-primary, inherit)",
158
- overflow: "hidden",
159
- textOverflow: "ellipsis",
160
- whiteSpace: "nowrap"
161
- };
162
- const rowMeta = {
163
- fontSize: 11,
164
- color: "var(--dsw-alias-label-secondary, #888)"
165
- };
166
- const rowActions = {
167
- display: "flex",
168
- alignItems: "center",
169
- gap: 6,
170
- flexShrink: 0
171
- };
172
- const badge = {
173
- display: "inline-flex",
174
- alignItems: "center",
175
- padding: "1px 8px",
176
- borderRadius: 999,
177
- fontSize: 10,
178
- background: "var(--dsw-alias-bg-layer-3, #eee)",
179
- color: "var(--dsw-alias-label-secondary, #777)"
180
- };
181
- const badgeArchived = {
182
- ...badge,
183
- color: "var(--dsw-alias-state-warning-primary, #b8860b)",
184
- background: "rgba(184,134,11,0.12)"
185
- };
186
- const badgeStray = {
187
- ...badge,
188
- color: "var(--dsw-alias-state-error-primary, #e24b4a)",
189
- background: "rgba(226,75,74,0.10)"
190
- };
191
- const empty = {
192
- display: "flex",
193
- flexDirection: "column",
194
- alignItems: "center",
195
- justifyContent: "center",
196
- gap: 6,
197
- padding: "40px 24px",
198
- border: "1px dashed var(--dsw-alias-border-l3, #ccc)",
199
- borderRadius: 10,
200
- background: "var(--dsw-alias-bg-layer-1, transparent)",
201
- textAlign: "center"
202
- };
203
- const emptyText = {
204
- margin: 0,
205
- fontSize: 13,
206
- color: "var(--dsw-alias-label-secondary, #555)"
207
- };
208
- const errorStyle = {
209
- fontSize: 12,
210
- lineHeight: 1.5,
211
- color: "var(--dsw-alias-state-error-primary, #e24b4a)",
212
- padding: "10px 14px",
213
- background: "rgba(226,75,74,0.08)",
214
- border: "1px solid rgba(226,75,74,0.35)",
215
- borderRadius: 8,
216
- whiteSpace: "pre-wrap"
217
- };
218
- const hint = {
219
- fontSize: 12,
220
- color: "var(--dsw-alias-label-secondary, #888)",
221
- lineHeight: 1.6
222
- };
223
- const modalOverlay = {
224
- position: "fixed",
225
- inset: 0,
226
- background: "rgba(0,0,0,0.45)",
227
- display: "flex",
228
- alignItems: "center",
229
- justifyContent: "center",
230
- zIndex: 1e3
231
- };
232
- const modalCard = {
233
- width: 420,
234
- maxWidth: "86vw",
235
- background: "var(--dsw-alias-bg-layer-3, #fff)",
236
- color: "var(--dsw-alias-label-primary, #222)",
237
- borderRadius: 12,
238
- boxShadow: "0 8px 32px rgba(0,0,0,0.25)",
239
- padding: "20px 22px",
240
- display: "flex",
241
- flexDirection: "column",
242
- gap: 12
243
- };
244
- const modalTitle = {
245
- margin: 0,
246
- fontSize: 15,
247
- fontWeight: 600
248
- };
249
- const modalBody = {
250
- fontSize: 13,
251
- lineHeight: 1.6,
252
- color: "var(--dsw-alias-label-secondary, #555)"
253
- };
254
- const modalBodyStrong = {
255
- fontSize: 13,
256
- lineHeight: 1.6,
257
- color: "var(--dsw-alias-state-error-primary, #e24b4a)",
258
- fontWeight: 500
259
- };
260
- const modalActions = {
261
- display: "flex",
262
- justifyContent: "flex-end",
263
- gap: 8,
264
- marginTop: 4
265
- };
266
- const previewModalCard = {
267
- ...modalCard,
268
- width: 560,
269
- maxHeight: "70vh",
270
- overflow: "hidden",
271
- display: "flex",
272
- flexDirection: "column"
273
- };
274
- const previewBody = {
275
- flex: 1,
276
- minHeight: 0,
277
- overflowY: "auto",
278
- display: "flex",
279
- flexDirection: "column",
280
- gap: 8,
281
- padding: "4px 2px"
282
- };
283
- const msgBubble = {
284
- maxWidth: "92%",
285
- padding: "8px 12px",
286
- borderRadius: 8,
287
- fontSize: 12.5,
288
- lineHeight: 1.6,
289
- whiteSpace: "pre-wrap",
290
- wordBreak: "break-word"
291
- };
292
- const msgUser = {
293
- ...msgBubble,
294
- alignSelf: "flex-end",
295
- background: "var(--dsw-alias-state-business-primary, #185FA5)",
296
- color: "#fff"
297
- };
298
- const msgAssistant = {
299
- ...msgBubble,
300
- alignSelf: "flex-start",
301
- background: "var(--dsw-alias-bg-layer-3, #f0f0f0)",
302
- color: "var(--dsw-alias-label-primary, #222)"
303
- };
304
- const previewMeta = {
305
- fontSize: 11,
306
- color: "var(--dsw-alias-label-secondary, #888)"
307
- };
308
- function fmtBytes(n) {
309
- if (n < 1024) return `${n} B`;
310
- if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`;
311
- return `${(n / 1024 / 1024).toFixed(1)} MB`;
312
- }
313
- function fmtTime(ms) {
314
- const d = new Date(ms);
315
- const p = (x) => String(x).padStart(2, "0");
316
- return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}`;
317
- }
318
- /** 会话展示名:优先标题,回退 workspace 解码 + id 短前缀 */
319
- function fmtSessionName(s) {
320
- if (s.title) return s.title;
321
- const ws = s.workspaceDir.replace(/~003A/g, ":").replace(/^--/, "").replace(/--$/, "");
322
- const short = s.id.length > 8 ? `${s.id.slice(0, 8)}…` : s.id;
323
- return `${ws} / ${short}`;
324
- }
325
- /** 格式化时间戳 */
326
- function fmtDateTime(ms) {
327
- if (!ms) return "—";
328
- const d = new Date(ms);
329
- const p = (x) => String(x).padStart(2, "0");
330
- return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}`;
331
- }
332
- function SessionManagerSection(props) {
333
- const { api, t } = props;
334
- const [sessions, setSessions] = (0, react.useState)([]);
335
- const [loading, setLoading] = (0, react.useState)(true);
336
- const [error, setError] = (0, react.useState)("");
337
- const [tab, setTab] = (0, react.useState)("all");
338
- const [confirmTarget, setConfirmTarget] = (0, react.useState)(null);
339
- const [busy, setBusy] = (0, react.useState)(false);
340
- const [previewTarget, setPreviewTarget] = (0, react.useState)(null);
341
- const [previewData, setPreviewData] = (0, react.useState)(null);
342
- const [previewLoading, setPreviewLoading] = (0, react.useState)(false);
343
- const mountedRef = (0, react.useRef)(true);
344
- const refresh = async () => {
345
- setLoading(true);
346
- setError("");
347
- const res = await api.list();
348
- if (!mountedRef.current) return;
349
- setLoading(false);
350
- if (!res.ok) {
351
- setError(t("error.loadFailed", { message: res.message ?? res.code ?? "" }));
352
- return;
1
+ window.__ModuleLoader__.load({
2
+ id: "@lijian-ui/dsh-session-cleaner",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ let react = require("react");
8
+ //#region src/client/SessionManagerSection.ts
9
+ /**
10
+ * 会话管理设置页(client half)
11
+ * ------------------------------------------------------------------
12
+ * 分组:全部 / 已归档 / 游离(无 workspace 归属)。
13
+ * 操作:归档(移入已归档分组)、恢复(移出)、删除(真删除 + 二次确认弹窗)。
14
+ * 样式走 dsh 设计 token(var(--dsw-alias-*))自动适配明暗主题。
15
+ */
16
+ const page = {
17
+ display: "flex",
18
+ flexDirection: "column",
19
+ gap: 12,
20
+ padding: "4px 0 24px",
21
+ width: "100%",
22
+ minHeight: 400
23
+ };
24
+ const header = {
25
+ display: "flex",
26
+ alignItems: "center",
27
+ justifyContent: "space-between",
28
+ padding: "14px 0",
29
+ flexShrink: 0,
30
+ borderBottom: "1px solid var(--dsw-alias-border-l2)"
31
+ };
32
+ const title = {
33
+ fontSize: 17,
34
+ fontWeight: 500,
35
+ color: "var(--dsw-alias-label-primary)",
36
+ margin: 0,
37
+ whiteSpace: "nowrap"
38
+ };
39
+ const countBadge = {
40
+ display: "inline-flex",
41
+ alignItems: "center",
42
+ justifyContent: "center",
43
+ minWidth: 22,
44
+ height: 22,
45
+ padding: "0 7px",
46
+ borderRadius: 999,
47
+ background: "var(--dsw-alias-bg-layer-3)",
48
+ color: "var(--dsw-alias-label-secondary)",
49
+ fontSize: 11,
50
+ fontWeight: 500,
51
+ marginLeft: 8
52
+ };
53
+ const toolbar = {
54
+ display: "flex",
55
+ alignItems: "center",
56
+ gap: 8
57
+ };
58
+ const btnGhost = {
59
+ display: "inline-flex",
60
+ alignItems: "center",
61
+ gap: 6,
62
+ height: 30,
63
+ padding: "0 12px",
64
+ background: "transparent",
65
+ color: "var(--dsw-alias-label-primary)",
66
+ border: "1px solid var(--dsw-alias-border-l2)",
67
+ borderRadius: 6,
68
+ fontSize: 12,
69
+ cursor: "pointer"
70
+ };
71
+ const btnDanger = {
72
+ display: "inline-flex",
73
+ alignItems: "center",
74
+ gap: 6,
75
+ height: 26,
76
+ padding: "0 10px",
77
+ background: "transparent",
78
+ color: "var(--dsw-alias-state-error-primary)",
79
+ border: "1px solid var(--dsw-alias-state-error-primary)",
80
+ borderRadius: 6,
81
+ fontSize: 12,
82
+ cursor: "pointer"
83
+ };
84
+ const btnDangerSolid = {
85
+ display: "inline-flex",
86
+ alignItems: "center",
87
+ gap: 6,
88
+ height: 30,
89
+ padding: "0 14px",
90
+ background: "var(--dsw-alias-state-error-primary)",
91
+ color: "var(--dsw-alias-label-primary-foreground)",
92
+ border: "none",
93
+ borderRadius: 6,
94
+ fontSize: 12,
95
+ fontWeight: 500,
96
+ cursor: "pointer"
97
+ };
98
+ const tabs = {
99
+ display: "flex",
100
+ gap: 4,
101
+ borderBottom: "1px solid var(--dsw-alias-border-l2)",
102
+ paddingBottom: 0
103
+ };
104
+ const tabStyle = {
105
+ padding: "8px 14px",
106
+ fontSize: 13,
107
+ color: "var(--dsw-alias-label-secondary)",
108
+ background: "transparent",
109
+ border: "none",
110
+ borderBottom: "2px solid transparent",
111
+ cursor: "pointer"
112
+ };
113
+ const tabStyleActive = {
114
+ ...tabStyle,
115
+ color: "var(--dsw-alias-state-business-primary)",
116
+ borderBottomColor: "var(--dsw-alias-state-business-primary)",
117
+ fontWeight: 500
118
+ };
119
+ const list = {
120
+ display: "flex",
121
+ flexDirection: "column",
122
+ gap: 6
123
+ };
124
+ const row = {
125
+ display: "flex",
126
+ alignItems: "center",
127
+ gap: 12,
128
+ padding: "10px 12px",
129
+ borderRadius: 8,
130
+ background: "var(--dsw-alias-bg-layer-2)",
131
+ border: "1px solid var(--dsw-alias-border-l3)",
132
+ cursor: "pointer"
133
+ };
134
+ const rowMain = {
135
+ flex: 1,
136
+ minWidth: 0,
137
+ display: "flex",
138
+ flexDirection: "column",
139
+ gap: 2
140
+ };
141
+ const rowTitle = {
142
+ fontSize: 13,
143
+ fontWeight: 500,
144
+ color: "var(--dsw-alias-label-primary)",
145
+ overflow: "hidden",
146
+ textOverflow: "ellipsis",
147
+ whiteSpace: "nowrap"
148
+ };
149
+ const rowMeta = {
150
+ fontSize: 11,
151
+ color: "var(--dsw-alias-label-secondary)"
152
+ };
153
+ const rowActions = {
154
+ display: "flex",
155
+ alignItems: "center",
156
+ gap: 6,
157
+ flexShrink: 0
158
+ };
159
+ const badge = {
160
+ display: "inline-flex",
161
+ alignItems: "center",
162
+ padding: "1px 8px",
163
+ borderRadius: 999,
164
+ fontSize: 10,
165
+ background: "var(--dsw-alias-bg-layer-3)",
166
+ color: "var(--dsw-alias-label-secondary)"
167
+ };
168
+ const badgeArchived = {
169
+ ...badge,
170
+ color: "var(--dsw-alias-state-warning-primary)",
171
+ background: "color-mix(in srgb, var(--dsw-alias-state-warning-primary) 12%, transparent)"
172
+ };
173
+ const badgeStray = {
174
+ ...badge,
175
+ color: "var(--dsw-alias-state-error-primary)",
176
+ background: "color-mix(in srgb, var(--dsw-alias-state-error-primary) 10%, transparent)"
177
+ };
178
+ const empty = {
179
+ display: "flex",
180
+ flexDirection: "column",
181
+ alignItems: "center",
182
+ justifyContent: "center",
183
+ gap: 6,
184
+ padding: "40px 24px",
185
+ border: "1px dashed var(--dsw-alias-border-l3)",
186
+ borderRadius: 10,
187
+ background: "var(--dsw-alias-bg-layer-1)",
188
+ textAlign: "center"
189
+ };
190
+ const emptyText = {
191
+ margin: 0,
192
+ fontSize: 13,
193
+ color: "var(--dsw-alias-label-secondary)"
194
+ };
195
+ const errorStyle = {
196
+ fontSize: 12,
197
+ lineHeight: 1.5,
198
+ color: "var(--dsw-alias-state-error-primary)",
199
+ padding: "10px 14px",
200
+ background: "color-mix(in srgb, var(--dsw-alias-state-error-primary) 8%, transparent)",
201
+ border: "1px solid color-mix(in srgb, var(--dsw-alias-state-error-primary) 40%, transparent)",
202
+ borderRadius: 8,
203
+ whiteSpace: "pre-wrap"
204
+ };
205
+ const hint = {
206
+ fontSize: 12,
207
+ color: "var(--dsw-alias-label-secondary)",
208
+ lineHeight: 1.6
209
+ };
210
+ const modalOverlay = {
211
+ position: "fixed",
212
+ inset: 0,
213
+ background: "var(--dsw-alias-bg-mask-1)",
214
+ display: "flex",
215
+ alignItems: "center",
216
+ justifyContent: "center",
217
+ zIndex: 1e3
218
+ };
219
+ const modalCard = {
220
+ width: 420,
221
+ maxWidth: "86vw",
222
+ background: "var(--dsw-alias-bg-layer-3)",
223
+ color: "var(--dsw-alias-label-primary)",
224
+ borderRadius: 12,
225
+ boxShadow: "var(--dsw-shadow-lv2)",
226
+ padding: "20px 22px",
227
+ display: "flex",
228
+ flexDirection: "column",
229
+ gap: 12
230
+ };
231
+ const modalTitle = {
232
+ margin: 0,
233
+ fontSize: 15,
234
+ fontWeight: 600
235
+ };
236
+ const modalBody = {
237
+ fontSize: 13,
238
+ lineHeight: 1.6,
239
+ color: "var(--dsw-alias-label-secondary)"
240
+ };
241
+ const modalBodyStrong = {
242
+ fontSize: 13,
243
+ lineHeight: 1.6,
244
+ color: "var(--dsw-alias-state-error-primary)",
245
+ fontWeight: 500
246
+ };
247
+ const modalActions = {
248
+ display: "flex",
249
+ justifyContent: "flex-end",
250
+ gap: 8,
251
+ marginTop: 4
252
+ };
253
+ const previewModalCard = {
254
+ ...modalCard,
255
+ width: 560,
256
+ maxHeight: "70vh",
257
+ overflow: "hidden",
258
+ display: "flex",
259
+ flexDirection: "column"
260
+ };
261
+ const previewBody = {
262
+ flex: 1,
263
+ minHeight: 0,
264
+ overflowY: "auto",
265
+ display: "flex",
266
+ flexDirection: "column",
267
+ gap: 8,
268
+ padding: "4px 2px"
269
+ };
270
+ const msgBubble = {
271
+ maxWidth: "92%",
272
+ padding: "8px 12px",
273
+ borderRadius: 8,
274
+ fontSize: 12.5,
275
+ lineHeight: 1.6,
276
+ whiteSpace: "pre-wrap",
277
+ wordBreak: "break-word"
278
+ };
279
+ const msgUser = {
280
+ ...msgBubble,
281
+ alignSelf: "flex-end",
282
+ background: "var(--dsw-alias-state-business-primary)",
283
+ color: "var(--dsw-alias-label-primary-foreground)"
284
+ };
285
+ const msgAssistant = {
286
+ ...msgBubble,
287
+ alignSelf: "flex-start",
288
+ background: "var(--dsw-alias-bg-layer-3)",
289
+ color: "var(--dsw-alias-label-primary)"
290
+ };
291
+ const previewMeta = {
292
+ fontSize: 11,
293
+ color: "var(--dsw-alias-label-secondary)"
294
+ };
295
+ function fmtBytes(n) {
296
+ if (n < 1024) return `${n} B`;
297
+ if (n < 1048576) return `${(n / 1024).toFixed(1)} KB`;
298
+ return `${(n / 1024 / 1024).toFixed(1)} MB`;
299
+ }
300
+ function fmtTime(ms) {
301
+ const d = new Date(ms);
302
+ const p = (x) => String(x).padStart(2, "0");
303
+ return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}`;
353
304
  }
354
- setSessions(res.value ?? []);
355
- };
356
- (0, react.useEffect)(() => {
357
- mountedRef.current = true;
358
- refresh();
359
- return () => {
360
- mountedRef.current = false;
361
- };
362
- }, []);
363
- const filtered = (0, react.useMemo)(() => {
364
- if (tab === "archived") return sessions.filter((s) => s.archived);
365
- if (tab === "stray") return sessions.filter((s) => !s.archived && !s.active);
366
- return sessions;
367
- }, [sessions, tab]);
368
- const counts = (0, react.useMemo)(() => ({
369
- all: sessions.length,
370
- archived: sessions.filter((s) => s.archived).length,
371
- stray: sessions.filter((s) => !s.archived && !s.active).length
372
- }), [sessions]);
373
- const run = async (fn) => {
374
- if (busy) return;
375
- setBusy(true);
376
- setError("");
377
- const res = await fn();
378
- setBusy(false);
379
- if (!res.ok) {
380
- setError(t("error.operationFailed", { message: res.message ?? "" }));
381
- return;
305
+ /** 会话展示名:优先标题,回退 workspace 解码 + id 短前缀 */
306
+ function fmtSessionName(s) {
307
+ if (s.title) return s.title;
308
+ return `${s.workspaceDir.replace(/~003A/g, ":").replace(/^--/, "").replace(/--$/, "")} / ${s.id.length > 8 ? `${s.id.slice(0, 8)}…` : s.id}`;
382
309
  }
383
- await refresh();
384
- };
385
- const openPreview = async (s) => {
386
- setPreviewTarget(s);
387
- setPreviewData(null);
388
- setPreviewLoading(true);
389
- const res = await api.preview(s.id);
390
- setPreviewLoading(false);
391
- if (res.ok) setPreviewData(res.value ?? null);
392
- else setError(t("error.previewFailed", { message: res.message ?? "" }));
393
- };
394
- const handleDelete = (s) => {
395
- setConfirmTarget(s);
396
- };
397
- const confirmDelete = async () => {
398
- if (!confirmTarget) return;
399
- const target = confirmTarget;
400
- setConfirmTarget(null);
401
- await run(() => api.remove(target.id));
402
- };
403
- const handleArchive = (s) => {
404
- const { archiveSession } = props;
405
- if (archiveSession) {
406
- (async () => {
310
+ /** 格式化时间戳 */
311
+ function fmtDateTime(ms) {
312
+ if (!ms) return "—";
313
+ const d = new Date(ms);
314
+ const p = (x) => String(x).padStart(2, "0");
315
+ return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}`;
316
+ }
317
+ function SessionManagerSection(props) {
318
+ const { api, t } = props;
319
+ const [sessions, setSessions] = (0, react.useState)([]);
320
+ const [loading, setLoading] = (0, react.useState)(true);
321
+ const [error, setError] = (0, react.useState)("");
322
+ const [tab, setTab] = (0, react.useState)("all");
323
+ const [confirmTarget, setConfirmTarget] = (0, react.useState)(null);
324
+ const [busy, setBusy] = (0, react.useState)(false);
325
+ const [previewTarget, setPreviewTarget] = (0, react.useState)(null);
326
+ const [previewData, setPreviewData] = (0, react.useState)(null);
327
+ const [previewLoading, setPreviewLoading] = (0, react.useState)(false);
328
+ const mountedRef = (0, react.useRef)(true);
329
+ const refresh = async () => {
330
+ setLoading(true);
331
+ setError("");
332
+ const res = await api.list();
333
+ if (!mountedRef.current) return;
334
+ setLoading(false);
335
+ if (!res.ok) {
336
+ setError(t("error.loadFailed", { message: res.message ?? res.code ?? "" }));
337
+ return;
338
+ }
339
+ setSessions(res.value ?? []);
340
+ };
341
+ (0, react.useEffect)(() => {
342
+ mountedRef.current = true;
343
+ refresh();
344
+ return () => {
345
+ mountedRef.current = false;
346
+ };
347
+ }, []);
348
+ const filtered = (0, react.useMemo)(() => {
349
+ if (tab === "archived") return sessions.filter((s) => s.archived);
350
+ if (tab === "stray") return sessions.filter((s) => !s.archived && !s.active);
351
+ return sessions;
352
+ }, [sessions, tab]);
353
+ const counts = (0, react.useMemo)(() => ({
354
+ all: sessions.length,
355
+ archived: sessions.filter((s) => s.archived).length,
356
+ stray: sessions.filter((s) => !s.archived && !s.active).length
357
+ }), [sessions]);
358
+ const run = async (fn) => {
407
359
  if (busy) return;
408
360
  setBusy(true);
409
361
  setError("");
410
- const ok = await archiveSession(s.id);
362
+ const res = await fn();
411
363
  setBusy(false);
412
- if (!ok) setError(t("error.archiveFailed"));
364
+ if (!res.ok) {
365
+ setError(t("error.operationFailed", { message: res.message ?? "" }));
366
+ return;
367
+ }
413
368
  await refresh();
414
- })();
415
- return;
416
- }
417
- run(() => api.archive(s.id));
418
- };
419
- const handleUnarchive = (s) => {
420
- run(() => api.unarchive(s.id));
421
- };
422
- const totalBytes = (0, react.useMemo)(() => sessions.reduce((a, s) => a + s.sizeBytes, 0), [sessions]);
423
- return (0, react.createElement)("div", { style: page }, (0, react.createElement)("div", { style: header }, (0, react.createElement)("h2", { style: title }, t("header.title"), (0, react.createElement)("span", { style: countBadge }, t("header.count", {
424
- count: sessions.length,
425
- size: fmtBytes(totalBytes)
426
- }))), (0, react.createElement)("div", { style: toolbar }, (0, react.createElement)("button", {
427
- style: btnGhost,
428
- onClick: () => void refresh(),
429
- disabled: loading
430
- }, loading ? t("btn.loading") : t("btn.refresh")))), (0, react.createElement)("div", { style: tabs }, [
431
- "all",
432
- "archived",
433
- "stray"
434
- ].map((k) => (0, react.createElement)("button", {
435
- key: k,
436
- style: tab === k ? tabStyleActive : tabStyle,
437
- onClick: () => setTab(k)
438
- }, k === "all" ? t("tab.all", { count: counts.all }) : k === "archived" ? t("tab.archived", { count: counts.archived }) : t("tab.stray", { count: counts.stray })))), error ? (0, react.createElement)("div", { style: errorStyle }, error) : null, (0, react.createElement)("div", { style: hint }, t("hint.text")), (0, react.createElement)("div", { style: list }, filtered.map((s) => (0, react.createElement)("div", {
439
- key: s.id,
440
- style: row,
441
- onClick: () => void openPreview(s)
442
- }, (0, react.createElement)("div", { style: rowMain }, (0, react.createElement)("div", { style: rowTitle }, fmtSessionName(s)), (0, react.createElement)("div", { style: rowMeta }, `${fmtTime(s.mtimeMs)} · ${fmtBytes(s.sizeBytes)} · ${s.id}`)), s.archived ? (0, react.createElement)("span", { style: badgeArchived }, t("badge.archived")) : !s.active ? (0, react.createElement)("span", { style: badgeStray }, t("badge.stray")) : null, (0, react.createElement)("div", {
443
- style: rowActions,
444
- onClick: (e) => e.stopPropagation()
445
- }, s.archived ? (0, react.createElement)("button", {
446
- style: btnGhost,
447
- onClick: () => handleUnarchive(s),
448
- disabled: busy
449
- }, t("btn.unarchive")) : (0, react.createElement)("button", {
450
- style: btnGhost,
451
- onClick: () => handleArchive(s),
452
- disabled: busy
453
- }, t("btn.archive")), (0, react.createElement)("button", {
454
- style: btnDanger,
455
- onClick: () => handleDelete(s),
456
- disabled: busy
457
- }, t("btn.delete")))))), !loading && filtered.length === 0 ? (0, react.createElement)("div", { style: empty }, (0, react.createElement)("p", { style: emptyText }, tab === "archived" ? t("empty.noArchived") : tab === "stray" ? t("empty.noStray") : t("empty.noSessions"))) : null, confirmTarget ? (0, react.createElement)("div", {
458
- style: modalOverlay,
459
- onClick: () => setConfirmTarget(null)
460
- }, (0, react.createElement)("div", {
461
- style: modalCard,
462
- onClick: (e) => e.stopPropagation()
463
- }, (0, react.createElement)("h3", { style: modalTitle }, t("modal.delete.title")), (0, react.createElement)("div", { style: modalBody }, t("modal.delete.body", { name: fmtSessionName(confirmTarget) })), (0, react.createElement)("div", { style: modalBodyStrong }, t("modal.delete.warning")), (0, react.createElement)("div", { style: modalActions }, (0, react.createElement)("button", {
464
- style: btnGhost,
465
- onClick: () => setConfirmTarget(null),
466
- disabled: busy
467
- }, t("btn.cancel")), (0, react.createElement)("button", {
468
- style: btnDangerSolid,
469
- onClick: () => void confirmDelete(),
470
- disabled: busy
471
- }, busy ? t("btn.deleting") : t("btn.permanentDelete"))))) : null, previewTarget ? (0, react.createElement)("div", {
472
- style: modalOverlay,
473
- onClick: () => setPreviewTarget(null)
474
- }, (0, react.createElement)("div", {
475
- style: previewModalCard,
476
- onClick: (e) => e.stopPropagation()
477
- }, (0, react.createElement)("h3", { style: modalTitle }, fmtSessionName(previewTarget)), (0, react.createElement)("div", { style: previewMeta }, t("preview.meta", {
478
- id: previewTarget.id,
479
- createdAt: fmtDateTime(previewData?.createdAt),
480
- size: fmtBytes(previewTarget.sizeBytes)
481
- })), (0, react.createElement)("div", { style: previewBody }, previewLoading ? (0, react.createElement)("p", { style: emptyText }, t("preview.loading")) : previewData && previewData.messages.length > 0 ? previewData.messages.map((m, i) => (0, react.createElement)("div", {
482
- key: i,
483
- style: m.role === "user" ? msgUser : msgAssistant
484
- }, m.text)) : (0, react.createElement)("p", { style: emptyText }, t("preview.empty"))), (0, react.createElement)("div", { style: modalActions }, (0, react.createElement)("button", {
485
- style: btnDanger,
486
- onClick: () => {
487
- setPreviewTarget(null);
488
- handleDelete(previewTarget);
489
- }
490
- }, t("btn.deleteSession")), (0, react.createElement)("button", {
491
- style: btnGhost,
492
- onClick: () => setPreviewTarget(null)
493
- }, t("btn.close"))))) : null);
494
- }
495
-
496
- //#endregion
497
- //#region src/client/config-api.ts
498
- const PACKAGE = "@lijian-ui/dsh-session-cleaner";
499
- const SERVICE = "sessionCleanerRemote";
500
- /** Identity codec — the host manifest owns strict validation. */
501
- const identity = (value) => value;
502
- const codec = (symbol) => ({
503
- mode: "strict",
504
- typeSymbol: symbol,
505
- schema: { parse: identity }
506
- });
507
- /** Wire descriptors mirroring the host contribution (src/remote.ts). */
508
- const CONTRIBUTION = {
509
- package: PACKAGE,
510
- descriptors: [
511
- {
512
- id: `${PACKAGE}#${SERVICE}/listSessions`,
513
- service: SERVICE,
514
- namespace: SERVICE,
515
- method: "listSessions",
516
- invocation: { kind: "direct" },
517
- parameters: [],
518
- result: codec(`${PACKAGE}#SessionList`)
519
- },
520
- {
521
- id: `${PACKAGE}#${SERVICE}/previewSession`,
522
- service: SERVICE,
523
- namespace: SERVICE,
524
- method: "previewSession",
525
- invocation: { kind: "direct" },
526
- parameters: [{
527
- name: "sessionId",
528
- wire: "sessionId",
529
- source: "json",
530
- codec: codec(`${PACKAGE}#SessionId`)
531
- }, {
532
- name: "limit",
533
- wire: "limit",
534
- source: "json",
535
- acceptsUndefined: true,
536
- codec: codec(`${PACKAGE}#Limit`)
537
- }],
538
- result: codec(`${PACKAGE}#SessionPreview`)
539
- },
540
- {
541
- id: `${PACKAGE}#${SERVICE}/deleteSession`,
542
- service: SERVICE,
543
- namespace: SERVICE,
544
- method: "deleteSession",
545
- invocation: { kind: "direct" },
546
- parameters: [{
547
- name: "sessionId",
548
- wire: "sessionId",
549
- source: "json",
550
- codec: codec(`${PACKAGE}#SessionId`)
551
- }],
552
- result: codec(`${PACKAGE}#DeleteResult`)
553
- },
554
- {
555
- id: `${PACKAGE}#${SERVICE}/archiveSession`,
556
- service: SERVICE,
557
- namespace: SERVICE,
558
- method: "archiveSession",
559
- invocation: { kind: "direct" },
560
- parameters: [{
561
- name: "sessionId",
562
- wire: "sessionId",
563
- source: "json",
564
- codec: codec(`${PACKAGE}#SessionId`)
565
- }],
566
- result: codec(`${PACKAGE}#ArchiveResult`)
567
- },
568
- {
569
- id: `${PACKAGE}#${SERVICE}/unarchiveSession`,
570
- service: SERVICE,
571
- namespace: SERVICE,
572
- method: "unarchiveSession",
573
- invocation: { kind: "direct" },
574
- parameters: [{
575
- name: "sessionId",
576
- wire: "sessionId",
577
- source: "json",
578
- codec: codec(`${PACKAGE}#SessionId`)
579
- }],
580
- result: codec(`${PACKAGE}#ArchiveResult`)
581
- }
582
- ]
583
- };
584
- /**
585
- * Create the session api bound to this browser plugin's context.
586
- * Mounts the remote contribution once; every call awaits the mount, then
587
- * invokes the namespace service and unwraps the envelope.
588
- */
589
- function createSessionApi(ctx) {
590
- const mount = ctx.remote.$mount(CONTRIBUTION);
591
- const call = async (method, ...args) => {
592
- try {
593
- await mount;
594
- const remote = ctx.get(`remote.${SERVICE}`);
595
- const result = await remote[method](...args);
596
- if (!result.ok) return {
597
- ok: false,
598
- code: result.error?.code ?? "remote-error",
599
- message: result.error?.message ?? `${SERVICE}.${method} failed`
600
369
  };
601
- return {
602
- ok: true,
603
- value: result.value
370
+ const openPreview = async (s) => {
371
+ setPreviewTarget(s);
372
+ setPreviewData(null);
373
+ setPreviewLoading(true);
374
+ const res = await api.preview(s.id);
375
+ setPreviewLoading(false);
376
+ if (res.ok) setPreviewData(res.value ?? null);
377
+ else setError(t("error.previewFailed", { message: res.message ?? "" }));
378
+ };
379
+ const handleDelete = (s) => {
380
+ setConfirmTarget(s);
381
+ };
382
+ const confirmDelete = async () => {
383
+ if (!confirmTarget) return;
384
+ const target = confirmTarget;
385
+ setConfirmTarget(null);
386
+ await run(() => api.remove(target.id));
387
+ };
388
+ const handleArchive = (s) => {
389
+ const { archiveSession } = props;
390
+ if (archiveSession) {
391
+ (async () => {
392
+ if (busy) return;
393
+ setBusy(true);
394
+ setError("");
395
+ const ok = await archiveSession(s.id);
396
+ setBusy(false);
397
+ if (!ok) setError(t("error.archiveFailed"));
398
+ await refresh();
399
+ })();
400
+ return;
401
+ }
402
+ run(() => api.archive(s.id));
403
+ };
404
+ const handleUnarchive = (s) => {
405
+ run(() => api.unarchive(s.id));
406
+ };
407
+ const totalBytes = (0, react.useMemo)(() => sessions.reduce((a, s) => a + s.sizeBytes, 0), [sessions]);
408
+ return (0, react.createElement)("div", { style: page }, (0, react.createElement)("div", { style: header }, (0, react.createElement)("h2", { style: title }, t("header.title"), (0, react.createElement)("span", { style: countBadge }, t("header.count", {
409
+ count: sessions.length,
410
+ size: fmtBytes(totalBytes)
411
+ }))), (0, react.createElement)("div", { style: toolbar }, (0, react.createElement)("button", {
412
+ style: btnGhost,
413
+ onClick: () => void refresh(),
414
+ disabled: loading
415
+ }, loading ? t("btn.loading") : t("btn.refresh")))), (0, react.createElement)("div", { style: tabs }, [
416
+ "all",
417
+ "archived",
418
+ "stray"
419
+ ].map((k) => (0, react.createElement)("button", {
420
+ key: k,
421
+ style: tab === k ? tabStyleActive : tabStyle,
422
+ onClick: () => setTab(k)
423
+ }, k === "all" ? t("tab.all", { count: counts.all }) : k === "archived" ? t("tab.archived", { count: counts.archived }) : t("tab.stray", { count: counts.stray })))), error ? (0, react.createElement)("div", { style: errorStyle }, error) : null, (0, react.createElement)("div", { style: hint }, t("hint.text")), (0, react.createElement)("div", { style: list }, filtered.map((s) => (0, react.createElement)("div", {
424
+ key: s.id,
425
+ style: row,
426
+ onClick: () => void openPreview(s)
427
+ }, (0, react.createElement)("div", { style: rowMain }, (0, react.createElement)("div", { style: rowTitle }, fmtSessionName(s)), (0, react.createElement)("div", { style: rowMeta }, `${fmtTime(s.mtimeMs)} · ${fmtBytes(s.sizeBytes)} · ${s.id}`)), s.archived ? (0, react.createElement)("span", { style: badgeArchived }, t("badge.archived")) : !s.active ? (0, react.createElement)("span", { style: badgeStray }, t("badge.stray")) : null, (0, react.createElement)("div", {
428
+ style: rowActions,
429
+ onClick: (e) => e.stopPropagation()
430
+ }, s.archived ? (0, react.createElement)("button", {
431
+ style: btnGhost,
432
+ onClick: () => handleUnarchive(s),
433
+ disabled: busy
434
+ }, t("btn.unarchive")) : (0, react.createElement)("button", {
435
+ style: btnGhost,
436
+ onClick: () => handleArchive(s),
437
+ disabled: busy
438
+ }, t("btn.archive")), (0, react.createElement)("button", {
439
+ style: btnDanger,
440
+ onClick: () => handleDelete(s),
441
+ disabled: busy
442
+ }, t("btn.delete")))))), !loading && filtered.length === 0 ? (0, react.createElement)("div", { style: empty }, (0, react.createElement)("p", { style: emptyText }, tab === "archived" ? t("empty.noArchived") : tab === "stray" ? t("empty.noStray") : t("empty.noSessions"))) : null, confirmTarget ? (0, react.createElement)("div", {
443
+ style: modalOverlay,
444
+ onClick: () => setConfirmTarget(null)
445
+ }, (0, react.createElement)("div", {
446
+ style: modalCard,
447
+ onClick: (e) => e.stopPropagation()
448
+ }, (0, react.createElement)("h3", { style: modalTitle }, t("modal.delete.title")), (0, react.createElement)("div", { style: modalBody }, t("modal.delete.body", { name: fmtSessionName(confirmTarget) })), (0, react.createElement)("div", { style: modalBodyStrong }, t("modal.delete.warning")), (0, react.createElement)("div", { style: modalActions }, (0, react.createElement)("button", {
449
+ style: btnGhost,
450
+ onClick: () => setConfirmTarget(null),
451
+ disabled: busy
452
+ }, t("btn.cancel")), (0, react.createElement)("button", {
453
+ style: btnDangerSolid,
454
+ onClick: () => void confirmDelete(),
455
+ disabled: busy
456
+ }, busy ? t("btn.deleting") : t("btn.permanentDelete"))))) : null, previewTarget ? (0, react.createElement)("div", {
457
+ style: modalOverlay,
458
+ onClick: () => setPreviewTarget(null)
459
+ }, (0, react.createElement)("div", {
460
+ style: previewModalCard,
461
+ onClick: (e) => e.stopPropagation()
462
+ }, (0, react.createElement)("h3", { style: modalTitle }, fmtSessionName(previewTarget)), (0, react.createElement)("div", { style: previewMeta }, t("preview.meta", {
463
+ id: previewTarget.id,
464
+ createdAt: fmtDateTime(previewData?.createdAt),
465
+ size: fmtBytes(previewTarget.sizeBytes)
466
+ })), (0, react.createElement)("div", { style: previewBody }, previewLoading ? (0, react.createElement)("p", { style: emptyText }, t("preview.loading")) : previewData && previewData.messages.length > 0 ? previewData.messages.map((m, i) => (0, react.createElement)("div", {
467
+ key: i,
468
+ style: m.role === "user" ? msgUser : msgAssistant
469
+ }, m.text)) : (0, react.createElement)("p", { style: emptyText }, t("preview.empty"))), (0, react.createElement)("div", { style: modalActions }, (0, react.createElement)("button", {
470
+ style: btnDanger,
471
+ onClick: () => {
472
+ setPreviewTarget(null);
473
+ handleDelete(previewTarget);
474
+ }
475
+ }, t("btn.deleteSession")), (0, react.createElement)("button", {
476
+ style: btnGhost,
477
+ onClick: () => setPreviewTarget(null)
478
+ }, t("btn.close"))))) : null);
479
+ }
480
+ //#endregion
481
+ //#region src/client/config-api.ts
482
+ const PACKAGE = "@lijian-ui/dsh-session-cleaner";
483
+ const SERVICE = "sessionCleanerRemote";
484
+ /** Identity codec — the host manifest owns strict validation. */
485
+ const identity = (value) => value;
486
+ const codec = (symbol) => ({
487
+ mode: "strict",
488
+ typeSymbol: symbol,
489
+ schema: { parse: identity }
490
+ });
491
+ /** Wire descriptors mirroring the host contribution (src/remote.ts). */
492
+ const CONTRIBUTION = {
493
+ package: PACKAGE,
494
+ descriptors: [
495
+ {
496
+ id: `${PACKAGE}#${SERVICE}/listSessions`,
497
+ service: SERVICE,
498
+ namespace: SERVICE,
499
+ method: "listSessions",
500
+ invocation: { kind: "direct" },
501
+ parameters: [],
502
+ result: codec(`${PACKAGE}#SessionList`)
503
+ },
504
+ {
505
+ id: `${PACKAGE}#${SERVICE}/previewSession`,
506
+ service: SERVICE,
507
+ namespace: SERVICE,
508
+ method: "previewSession",
509
+ invocation: { kind: "direct" },
510
+ parameters: [{
511
+ name: "sessionId",
512
+ wire: "sessionId",
513
+ source: "json",
514
+ codec: codec(`${PACKAGE}#SessionId`)
515
+ }, {
516
+ name: "limit",
517
+ wire: "limit",
518
+ source: "json",
519
+ acceptsUndefined: true,
520
+ codec: codec(`${PACKAGE}#Limit`)
521
+ }],
522
+ result: codec(`${PACKAGE}#SessionPreview`)
523
+ },
524
+ {
525
+ id: `${PACKAGE}#${SERVICE}/deleteSession`,
526
+ service: SERVICE,
527
+ namespace: SERVICE,
528
+ method: "deleteSession",
529
+ invocation: { kind: "direct" },
530
+ parameters: [{
531
+ name: "sessionId",
532
+ wire: "sessionId",
533
+ source: "json",
534
+ codec: codec(`${PACKAGE}#SessionId`)
535
+ }],
536
+ result: codec(`${PACKAGE}#DeleteResult`)
537
+ },
538
+ {
539
+ id: `${PACKAGE}#${SERVICE}/archiveSession`,
540
+ service: SERVICE,
541
+ namespace: SERVICE,
542
+ method: "archiveSession",
543
+ invocation: { kind: "direct" },
544
+ parameters: [{
545
+ name: "sessionId",
546
+ wire: "sessionId",
547
+ source: "json",
548
+ codec: codec(`${PACKAGE}#SessionId`)
549
+ }],
550
+ result: codec(`${PACKAGE}#ArchiveResult`)
551
+ },
552
+ {
553
+ id: `${PACKAGE}#${SERVICE}/unarchiveSession`,
554
+ service: SERVICE,
555
+ namespace: SERVICE,
556
+ method: "unarchiveSession",
557
+ invocation: { kind: "direct" },
558
+ parameters: [{
559
+ name: "sessionId",
560
+ wire: "sessionId",
561
+ source: "json",
562
+ codec: codec(`${PACKAGE}#SessionId`)
563
+ }],
564
+ result: codec(`${PACKAGE}#ArchiveResult`)
565
+ }
566
+ ]
567
+ };
568
+ /**
569
+ * Create the session api bound to this browser plugin's context.
570
+ * Mounts the remote contribution once; every call awaits the mount, then
571
+ * invokes the namespace service and unwraps the envelope.
572
+ */
573
+ function createSessionApi(ctx) {
574
+ const mount = ctx.remote.$mount(CONTRIBUTION);
575
+ const call = async (method, ...args) => {
576
+ try {
577
+ await mount;
578
+ const result = await ctx.get(`remote.${SERVICE}`)[method](...args);
579
+ if (!result.ok) return {
580
+ ok: false,
581
+ code: result.error?.code ?? "remote-error",
582
+ message: result.error?.message ?? `${SERVICE}.${method} failed`
583
+ };
584
+ return {
585
+ ok: true,
586
+ value: result.value
587
+ };
588
+ } catch (err) {
589
+ return {
590
+ ok: false,
591
+ code: "remote-error",
592
+ message: err instanceof Error ? err.message : String(err)
593
+ };
594
+ }
604
595
  };
605
- } catch (err) {
606
596
  return {
607
- ok: false,
608
- code: "remote-error",
609
- message: err instanceof Error ? err.message : String(err)
597
+ list: () => call("listSessions"),
598
+ preview: (sessionId) => call("previewSession", sessionId, void 0),
599
+ remove: (sessionId) => call("deleteSession", sessionId),
600
+ archive: (sessionId) => call("archiveSession", sessionId),
601
+ unarchive: (sessionId) => call("unarchiveSession", sessionId)
610
602
  };
611
603
  }
612
- };
613
- return {
614
- list: () => call("listSessions"),
615
- preview: (sessionId) => call("previewSession", sessionId, void 0),
616
- remove: (sessionId) => call("deleteSession", sessionId),
617
- archive: (sessionId) => call("archiveSession", sessionId),
618
- unarchive: (sessionId) => call("unarchiveSession", sessionId)
619
- };
620
- }
621
-
622
- //#endregion
623
- //#region src/client/client-i18n.ts
624
- /**
625
- * session-cleaner 客户端翻译字典
626
- * zh 为 key 源,en 与 zh 键集完整对照。
627
- */
628
- const zh = {
629
- "section.label": "会话管理",
630
- "header.title": "会话管理",
631
- "header.count": "{count} 个会话 · {size}",
632
- "btn.refresh": "刷新",
633
- "btn.loading": "加载中…",
634
- "tab.all": "全部 ({count})",
635
- "tab.archived": "已归档 ({count})",
636
- "tab.stray": "游离 ({count})",
637
- "badge.archived": "已归档",
638
- "badge.stray": "游离",
639
- "btn.archive": "归档",
640
- "btn.unarchive": "恢复",
641
- "btn.delete": "删除",
642
- "hint.text": "会话以文件形式保存在 ~/.dsh/sessions。归档会从官方列表隐藏(与官方归档打通),删除为永久删除,不可恢复。",
643
- "empty.noSessions": "没有会话",
644
- "empty.noArchived": "没有已归档的会话",
645
- "empty.noStray": "没有游离会话",
646
- "error.loadFailed": "加载会话失败:{message}",
647
- "error.operationFailed": "操作失败:{message}",
648
- "error.archiveFailed": "归档失败(官方 API 不可用,请重试或重启 dsh)",
649
- "error.previewFailed": "加载预览失败:{message}",
650
- "modal.delete.title": "删除会话?",
651
- "modal.delete.body": "确定要删除会话「{name}」吗?",
652
- "modal.delete.warning": "此操作将永久删除该会话及其日志文件,无法恢复(不会进入回收站)。",
653
- "btn.cancel": "取消",
654
- "btn.deleting": "删除中…",
655
- "btn.permanentDelete": "永久删除",
656
- "preview.meta": "ID: {id} · 创建: {createdAt} · {size}",
657
- "preview.loading": "加载中…",
658
- "preview.empty": "(该会话暂无文本消息)",
659
- "btn.deleteSession": "删除此会话",
660
- "btn.close": "关闭"
661
- };
662
- const en = {
663
- "section.label": "Session Manager",
664
- "header.title": "Session Manager",
665
- "header.count": "{count} sessions · {size}",
666
- "btn.refresh": "Refresh",
667
- "btn.loading": "Loading…",
668
- "tab.all": "All ({count})",
669
- "tab.archived": "Archived ({count})",
670
- "tab.stray": "Stray ({count})",
671
- "badge.archived": "Archived",
672
- "badge.stray": "Stray",
673
- "btn.archive": "Archive",
674
- "btn.unarchive": "Restore",
675
- "btn.delete": "Delete",
676
- "hint.text": "Sessions are stored as files in ~/.dsh/sessions. Archiving hides it from the official list (integrated with official archive). Deletion is permanent and irreversible.",
677
- "empty.noSessions": "No sessions",
678
- "empty.noArchived": "No archived sessions",
679
- "empty.noStray": "No stray sessions",
680
- "error.loadFailed": "Failed to load sessions: {message}",
681
- "error.operationFailed": "Operation failed: {message}",
682
- "error.archiveFailed": "Archive failed (official API unavailable, please retry or restart dsh)",
683
- "error.previewFailed": "Failed to load preview: {message}",
684
- "modal.delete.title": "Delete Session?",
685
- "modal.delete.body": "Are you sure you want to delete \"{name}\"?",
686
- "modal.delete.warning": "This action will permanently delete the session and its log files. This cannot be undone (no trash recovery).",
687
- "btn.cancel": "Cancel",
688
- "btn.deleting": "Deleting…",
689
- "btn.permanentDelete": "Delete Forever",
690
- "preview.meta": "ID: {id} · Created: {createdAt} · {size}",
691
- "preview.loading": "Loading…",
692
- "preview.empty": "(No text messages in this session)",
693
- "btn.deleteSession": "Delete this session",
694
- "btn.close": "Close"
695
- };
696
-
697
- //#endregion
698
- //#region src/client/index.ts
699
- /** Section identity in the Settings left-nav rail. */
700
- const SECTION_ID = "session-cleaner";
701
- /** Sits below Models (10) and IM 通道 (20). */
702
- const SECTION_ORDER = 30;
703
- /** i18n namespace matches the plugin package name for disambiguation. */
704
- const I18N_NS = "session-cleaner";
705
- /** Services this browser plugin requires (cordis fiber inject). */
706
- const inject = [
707
- "slots",
708
- "locale",
709
- "connection",
710
- "remote"
711
- ];
712
- /**
713
- * Mount the session management settings page.
714
- * @param ctx - the browser plugin context (guarded facade).
715
- */
716
- function apply(ctx) {
717
- const api = createSessionApi(ctx);
718
- ctx.effect(() => ctx.locale.register(I18N_NS, {
719
- zh,
720
- en
721
- }), "session-cleaner: client dictionaries");
722
- const t = ctx.locale.bind(I18N_NS);
723
- let officialArchive = null;
724
- ctx.inject(["workspaces"], (sctx) => {
725
- officialArchive = async (sessionId) => {
726
- try {
727
- const res = await sctx.workspaces.archiveSession(sessionId);
728
- return res.ok === true;
729
- } catch {
730
- return false;
731
- }
732
- };
733
- });
734
- ctx.slots.inject("settings.section", () => ctx.slots.register({
735
- name: "settings.section",
736
- id: SECTION_ID,
737
- order: SECTION_ORDER,
738
- label: () => t("section.label"),
739
- inject: () => ({
740
- api,
741
- t,
742
- archiveSession: (sessionId) => officialArchive ? officialArchive(sessionId) : api.archive(sessionId).then((r) => r.ok)
743
- })
744
- }, SessionManagerSection));
745
- }
604
+ //#endregion
605
+ //#region src/client/client-i18n.ts
606
+ /**
607
+ * session-cleaner 客户端翻译字典
608
+ * zh key 源,en 与 zh 键集完整对照。
609
+ */
610
+ const zh = {
611
+ "section.label": "会话管理",
612
+ "header.title": "会话管理",
613
+ "header.count": "{count} 个会话 · {size}",
614
+ "btn.refresh": "刷新",
615
+ "btn.loading": "加载中…",
616
+ "tab.all": "全部 ({count})",
617
+ "tab.archived": "已归档 ({count})",
618
+ "tab.stray": "游离 ({count})",
619
+ "badge.archived": "已归档",
620
+ "badge.stray": "游离",
621
+ "btn.archive": "归档",
622
+ "btn.unarchive": "恢复",
623
+ "btn.delete": "删除",
624
+ "hint.text": "会话以文件形式保存在 ~/.dsh/sessions。归档会从官方列表隐藏(与官方归档打通),删除为永久删除,不可恢复。",
625
+ "empty.noSessions": "没有会话",
626
+ "empty.noArchived": "没有已归档的会话",
627
+ "empty.noStray": "没有游离会话",
628
+ "error.loadFailed": "加载会话失败:{message}",
629
+ "error.operationFailed": "操作失败:{message}",
630
+ "error.archiveFailed": "归档失败(官方 API 不可用,请重试或重启 dsh)",
631
+ "error.previewFailed": "加载预览失败:{message}",
632
+ "modal.delete.title": "删除会话?",
633
+ "modal.delete.body": "确定要删除会话「{name}」吗?",
634
+ "modal.delete.warning": "此操作将永久删除该会话及其日志文件,无法恢复(不会进入回收站)。",
635
+ "btn.cancel": "取消",
636
+ "btn.deleting": "删除中…",
637
+ "btn.permanentDelete": "永久删除",
638
+ "preview.meta": "ID: {id} · 创建: {createdAt} · {size}",
639
+ "preview.loading": "加载中…",
640
+ "preview.empty": "(该会话暂无文本消息)",
641
+ "btn.deleteSession": "删除此会话",
642
+ "btn.close": "关闭"
643
+ };
644
+ const en = {
645
+ "section.label": "Session Manager",
646
+ "header.title": "Session Manager",
647
+ "header.count": "{count} sessions · {size}",
648
+ "btn.refresh": "Refresh",
649
+ "btn.loading": "Loading…",
650
+ "tab.all": "All ({count})",
651
+ "tab.archived": "Archived ({count})",
652
+ "tab.stray": "Stray ({count})",
653
+ "badge.archived": "Archived",
654
+ "badge.stray": "Stray",
655
+ "btn.archive": "Archive",
656
+ "btn.unarchive": "Restore",
657
+ "btn.delete": "Delete",
658
+ "hint.text": "Sessions are stored as files in ~/.dsh/sessions. Archiving hides it from the official list (integrated with official archive). Deletion is permanent and irreversible.",
659
+ "empty.noSessions": "No sessions",
660
+ "empty.noArchived": "No archived sessions",
661
+ "empty.noStray": "No stray sessions",
662
+ "error.loadFailed": "Failed to load sessions: {message}",
663
+ "error.operationFailed": "Operation failed: {message}",
664
+ "error.archiveFailed": "Archive failed (official API unavailable, please retry or restart dsh)",
665
+ "error.previewFailed": "Failed to load preview: {message}",
666
+ "modal.delete.title": "Delete Session?",
667
+ "modal.delete.body": "Are you sure you want to delete \"{name}\"?",
668
+ "modal.delete.warning": "This action will permanently delete the session and its log files. This cannot be undone (no trash recovery).",
669
+ "btn.cancel": "Cancel",
670
+ "btn.deleting": "Deleting…",
671
+ "btn.permanentDelete": "Delete Forever",
672
+ "preview.meta": "ID: {id} · Created: {createdAt} · {size}",
673
+ "preview.loading": "Loading…",
674
+ "preview.empty": "(No text messages in this session)",
675
+ "btn.deleteSession": "Delete this session",
676
+ "btn.close": "Close"
677
+ };
678
+ //#endregion
679
+ //#region src/client/index.ts
680
+ /** Section identity in the Settings left-nav rail. */
681
+ const SECTION_ID = "session-cleaner";
682
+ /** Sits below Models (10) and IM 通道 (20). */
683
+ const SECTION_ORDER = 30;
684
+ /** i18n namespace matches the plugin package name for disambiguation. */
685
+ const I18N_NS = "session-cleaner";
686
+ /** Services this browser plugin requires (cordis fiber inject). */
687
+ const inject = [
688
+ "slots",
689
+ "locale",
690
+ "connection",
691
+ "remote"
692
+ ];
693
+ /**
694
+ * Mount the session management settings page.
695
+ * @param ctx - the browser plugin context (guarded facade).
696
+ */
697
+ function apply(ctx) {
698
+ const api = createSessionApi(ctx);
699
+ ctx.effect(() => ctx.locale.register(I18N_NS, {
700
+ zh,
701
+ en
702
+ }), "session-cleaner: client dictionaries");
703
+ const t = ctx.locale.bind(I18N_NS);
704
+ let officialArchive = null;
705
+ ctx.inject(["workspaces"], (sctx) => {
706
+ officialArchive = async (sessionId) => {
707
+ try {
708
+ return (await sctx.workspaces.archiveSession(sessionId)).ok === true;
709
+ } catch {
710
+ return false;
711
+ }
712
+ };
713
+ });
714
+ ctx.slots.inject("settings.section", () => ctx.slots.register({
715
+ name: "settings.section",
716
+ id: SECTION_ID,
717
+ order: SECTION_ORDER,
718
+ label: () => t("section.label"),
719
+ inject: () => ({
720
+ api,
721
+ t,
722
+ archiveSession: (sessionId) => officialArchive ? officialArchive(sessionId) : api.archive(sessionId).then((r) => r.ok)
723
+ })
724
+ }, SessionManagerSection));
725
+ }
726
+ //#endregion
727
+ exports.apply = apply;
728
+ exports.inject = inject;
729
+ return module.exports;
730
+ }
731
+ });
746
732
 
747
- //#endregion
748
- exports.apply = apply;
749
- exports.inject = inject;
750
- return module.exports; } });
751
733
  //# sourceMappingURL=client.js.map