@lijian-ui/dsh-session-cleaner 0.1.1 → 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.
Files changed (3) hide show
  1. package/lib/client.js +151 -62
  2. package/lib/index.js +44 -6
  3. package/package.json +8 -8
package/lib/client.js CHANGED
@@ -27,12 +27,12 @@ window.__ModuleLoader__.load({
27
27
  justifyContent: "space-between",
28
28
  padding: "14px 0",
29
29
  flexShrink: 0,
30
- borderBottom: "1px solid var(--dsw-alias-border-l2, #eee)"
30
+ borderBottom: "1px solid var(--dsw-alias-border-l2)"
31
31
  };
32
32
  const title = {
33
33
  fontSize: 17,
34
34
  fontWeight: 500,
35
- color: "var(--dsw-alias-label-primary, inherit)",
35
+ color: "var(--dsw-alias-label-primary)",
36
36
  margin: 0,
37
37
  whiteSpace: "nowrap"
38
38
  };
@@ -44,8 +44,8 @@ window.__ModuleLoader__.load({
44
44
  height: 22,
45
45
  padding: "0 7px",
46
46
  borderRadius: 999,
47
- background: "var(--dsw-alias-bg-layer-3, #eee)",
48
- color: "var(--dsw-alias-label-secondary, #555)",
47
+ background: "var(--dsw-alias-bg-layer-3)",
48
+ color: "var(--dsw-alias-label-secondary)",
49
49
  fontSize: 11,
50
50
  fontWeight: 500,
51
51
  marginLeft: 8
@@ -62,8 +62,8 @@ window.__ModuleLoader__.load({
62
62
  height: 30,
63
63
  padding: "0 12px",
64
64
  background: "transparent",
65
- color: "var(--dsw-alias-label-primary, inherit)",
66
- border: "1px solid var(--dsw-alias-border-l2, #ddd)",
65
+ color: "var(--dsw-alias-label-primary)",
66
+ border: "1px solid var(--dsw-alias-border-l2)",
67
67
  borderRadius: 6,
68
68
  fontSize: 12,
69
69
  cursor: "pointer"
@@ -75,8 +75,8 @@ window.__ModuleLoader__.load({
75
75
  height: 26,
76
76
  padding: "0 10px",
77
77
  background: "transparent",
78
- color: "var(--dsw-alias-state-error-primary, #e24b4a)",
79
- border: "1px solid var(--dsw-alias-state-error-primary, #e24b4a)",
78
+ color: "var(--dsw-alias-state-error-primary)",
79
+ border: "1px solid var(--dsw-alias-state-error-primary)",
80
80
  borderRadius: 6,
81
81
  fontSize: 12,
82
82
  cursor: "pointer"
@@ -87,8 +87,8 @@ window.__ModuleLoader__.load({
87
87
  gap: 6,
88
88
  height: 30,
89
89
  padding: "0 14px",
90
- background: "var(--dsw-alias-state-error-primary, #e24b4a)",
91
- color: "#fff",
90
+ background: "var(--dsw-alias-state-error-primary)",
91
+ color: "var(--dsw-alias-label-primary-foreground)",
92
92
  border: "none",
93
93
  borderRadius: 6,
94
94
  fontSize: 12,
@@ -98,13 +98,13 @@ window.__ModuleLoader__.load({
98
98
  const tabs = {
99
99
  display: "flex",
100
100
  gap: 4,
101
- borderBottom: "1px solid var(--dsw-alias-border-l2, #eee)",
101
+ borderBottom: "1px solid var(--dsw-alias-border-l2)",
102
102
  paddingBottom: 0
103
103
  };
104
104
  const tabStyle = {
105
105
  padding: "8px 14px",
106
106
  fontSize: 13,
107
- color: "var(--dsw-alias-label-secondary, #666)",
107
+ color: "var(--dsw-alias-label-secondary)",
108
108
  background: "transparent",
109
109
  border: "none",
110
110
  borderBottom: "2px solid transparent",
@@ -112,8 +112,8 @@ window.__ModuleLoader__.load({
112
112
  };
113
113
  const tabStyleActive = {
114
114
  ...tabStyle,
115
- color: "var(--dsw-alias-state-business-primary, #185FA5)",
116
- borderBottomColor: "var(--dsw-alias-state-business-primary, #185FA5)",
115
+ color: "var(--dsw-alias-state-business-primary)",
116
+ borderBottomColor: "var(--dsw-alias-state-business-primary)",
117
117
  fontWeight: 500
118
118
  };
119
119
  const list = {
@@ -127,8 +127,8 @@ window.__ModuleLoader__.load({
127
127
  gap: 12,
128
128
  padding: "10px 12px",
129
129
  borderRadius: 8,
130
- background: "var(--dsw-alias-bg-layer-2, #fafafa)",
131
- border: "1px solid var(--dsw-alias-border-l3, #f0f0f0)",
130
+ background: "var(--dsw-alias-bg-layer-2)",
131
+ border: "1px solid var(--dsw-alias-border-l3)",
132
132
  cursor: "pointer"
133
133
  };
134
134
  const rowMain = {
@@ -141,14 +141,14 @@ window.__ModuleLoader__.load({
141
141
  const rowTitle = {
142
142
  fontSize: 13,
143
143
  fontWeight: 500,
144
- color: "var(--dsw-alias-label-primary, inherit)",
144
+ color: "var(--dsw-alias-label-primary)",
145
145
  overflow: "hidden",
146
146
  textOverflow: "ellipsis",
147
147
  whiteSpace: "nowrap"
148
148
  };
149
149
  const rowMeta = {
150
150
  fontSize: 11,
151
- color: "var(--dsw-alias-label-secondary, #888)"
151
+ color: "var(--dsw-alias-label-secondary)"
152
152
  };
153
153
  const rowActions = {
154
154
  display: "flex",
@@ -162,18 +162,18 @@ window.__ModuleLoader__.load({
162
162
  padding: "1px 8px",
163
163
  borderRadius: 999,
164
164
  fontSize: 10,
165
- background: "var(--dsw-alias-bg-layer-3, #eee)",
166
- color: "var(--dsw-alias-label-secondary, #777)"
165
+ background: "var(--dsw-alias-bg-layer-3)",
166
+ color: "var(--dsw-alias-label-secondary)"
167
167
  };
168
168
  const badgeArchived = {
169
169
  ...badge,
170
- color: "var(--dsw-alias-state-warning-primary, #b8860b)",
171
- background: "rgba(184,134,11,0.12)"
170
+ color: "var(--dsw-alias-state-warning-primary)",
171
+ background: "color-mix(in srgb, var(--dsw-alias-state-warning-primary) 12%, transparent)"
172
172
  };
173
173
  const badgeStray = {
174
174
  ...badge,
175
- color: "var(--dsw-alias-state-error-primary, #e24b4a)",
176
- background: "rgba(226,75,74,0.10)"
175
+ color: "var(--dsw-alias-state-error-primary)",
176
+ background: "color-mix(in srgb, var(--dsw-alias-state-error-primary) 10%, transparent)"
177
177
  };
178
178
  const empty = {
179
179
  display: "flex",
@@ -182,35 +182,35 @@ window.__ModuleLoader__.load({
182
182
  justifyContent: "center",
183
183
  gap: 6,
184
184
  padding: "40px 24px",
185
- border: "1px dashed var(--dsw-alias-border-l3, #ccc)",
185
+ border: "1px dashed var(--dsw-alias-border-l3)",
186
186
  borderRadius: 10,
187
- background: "var(--dsw-alias-bg-layer-1, transparent)",
187
+ background: "var(--dsw-alias-bg-layer-1)",
188
188
  textAlign: "center"
189
189
  };
190
190
  const emptyText = {
191
191
  margin: 0,
192
192
  fontSize: 13,
193
- color: "var(--dsw-alias-label-secondary, #555)"
193
+ color: "var(--dsw-alias-label-secondary)"
194
194
  };
195
195
  const errorStyle = {
196
196
  fontSize: 12,
197
197
  lineHeight: 1.5,
198
- color: "var(--dsw-alias-state-error-primary, #e24b4a)",
198
+ color: "var(--dsw-alias-state-error-primary)",
199
199
  padding: "10px 14px",
200
- background: "rgba(226,75,74,0.08)",
201
- border: "1px solid rgba(226,75,74,0.35)",
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
202
  borderRadius: 8,
203
203
  whiteSpace: "pre-wrap"
204
204
  };
205
205
  const hint = {
206
206
  fontSize: 12,
207
- color: "var(--dsw-alias-label-secondary, #888)",
207
+ color: "var(--dsw-alias-label-secondary)",
208
208
  lineHeight: 1.6
209
209
  };
210
210
  const modalOverlay = {
211
211
  position: "fixed",
212
212
  inset: 0,
213
- background: "rgba(0,0,0,0.45)",
213
+ background: "var(--dsw-alias-bg-mask-1)",
214
214
  display: "flex",
215
215
  alignItems: "center",
216
216
  justifyContent: "center",
@@ -219,10 +219,10 @@ window.__ModuleLoader__.load({
219
219
  const modalCard = {
220
220
  width: 420,
221
221
  maxWidth: "86vw",
222
- background: "var(--dsw-alias-bg-layer-3, #fff)",
223
- color: "var(--dsw-alias-label-primary, #222)",
222
+ background: "var(--dsw-alias-bg-layer-3)",
223
+ color: "var(--dsw-alias-label-primary)",
224
224
  borderRadius: 12,
225
- boxShadow: "0 8px 32px rgba(0,0,0,0.25)",
225
+ boxShadow: "var(--dsw-shadow-lv2)",
226
226
  padding: "20px 22px",
227
227
  display: "flex",
228
228
  flexDirection: "column",
@@ -236,12 +236,12 @@ window.__ModuleLoader__.load({
236
236
  const modalBody = {
237
237
  fontSize: 13,
238
238
  lineHeight: 1.6,
239
- color: "var(--dsw-alias-label-secondary, #555)"
239
+ color: "var(--dsw-alias-label-secondary)"
240
240
  };
241
241
  const modalBodyStrong = {
242
242
  fontSize: 13,
243
243
  lineHeight: 1.6,
244
- color: "var(--dsw-alias-state-error-primary, #e24b4a)",
244
+ color: "var(--dsw-alias-state-error-primary)",
245
245
  fontWeight: 500
246
246
  };
247
247
  const modalActions = {
@@ -279,18 +279,18 @@ window.__ModuleLoader__.load({
279
279
  const msgUser = {
280
280
  ...msgBubble,
281
281
  alignSelf: "flex-end",
282
- background: "var(--dsw-alias-state-business-primary, #185FA5)",
283
- color: "#fff"
282
+ background: "var(--dsw-alias-state-business-primary)",
283
+ color: "var(--dsw-alias-label-primary-foreground)"
284
284
  };
285
285
  const msgAssistant = {
286
286
  ...msgBubble,
287
287
  alignSelf: "flex-start",
288
- background: "var(--dsw-alias-bg-layer-3, #f0f0f0)",
289
- color: "var(--dsw-alias-label-primary, #222)"
288
+ background: "var(--dsw-alias-bg-layer-3)",
289
+ color: "var(--dsw-alias-label-primary)"
290
290
  };
291
291
  const previewMeta = {
292
292
  fontSize: 11,
293
- color: "var(--dsw-alias-label-secondary, #888)"
293
+ color: "var(--dsw-alias-label-secondary)"
294
294
  };
295
295
  function fmtBytes(n) {
296
296
  if (n < 1024) return `${n} B`;
@@ -315,7 +315,7 @@ window.__ModuleLoader__.load({
315
315
  return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}`;
316
316
  }
317
317
  function SessionManagerSection(props) {
318
- const { api } = props;
318
+ const { api, t } = props;
319
319
  const [sessions, setSessions] = (0, react.useState)([]);
320
320
  const [loading, setLoading] = (0, react.useState)(true);
321
321
  const [error, setError] = (0, react.useState)("");
@@ -333,7 +333,7 @@ window.__ModuleLoader__.load({
333
333
  if (!mountedRef.current) return;
334
334
  setLoading(false);
335
335
  if (!res.ok) {
336
- setError(`加载会话失败:${res.message ?? res.code ?? "未知错误"}`);
336
+ setError(t("error.loadFailed", { message: res.message ?? res.code ?? "" }));
337
337
  return;
338
338
  }
339
339
  setSessions(res.value ?? []);
@@ -362,7 +362,7 @@ window.__ModuleLoader__.load({
362
362
  const res = await fn();
363
363
  setBusy(false);
364
364
  if (!res.ok) {
365
- setError(`操作失败:${res.message ?? "未知错误"}`);
365
+ setError(t("error.operationFailed", { message: res.message ?? "" }));
366
366
  return;
367
367
  }
368
368
  await refresh();
@@ -374,7 +374,7 @@ window.__ModuleLoader__.load({
374
374
  const res = await api.preview(s.id);
375
375
  setPreviewLoading(false);
376
376
  if (res.ok) setPreviewData(res.value ?? null);
377
- else setError(`加载预览失败:${res.message ?? "未知错误"}`);
377
+ else setError(t("error.previewFailed", { message: res.message ?? "" }));
378
378
  };
379
379
  const handleDelete = (s) => {
380
380
  setConfirmTarget(s);
@@ -394,7 +394,7 @@ window.__ModuleLoader__.load({
394
394
  setError("");
395
395
  const ok = await archiveSession(s.id);
396
396
  setBusy(false);
397
- if (!ok) setError("归档失败(官方 API 不可用,请重试或重启 dsh)");
397
+ if (!ok) setError(t("error.archiveFailed"));
398
398
  await refresh();
399
399
  })();
400
400
  return;
@@ -405,11 +405,14 @@ window.__ModuleLoader__.load({
405
405
  run(() => api.unarchive(s.id));
406
406
  };
407
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 }, "会话管理", (0, react.createElement)("span", { style: countBadge }, `${sessions.length} 个会话 · ${fmtBytes(totalBytes)}`)), (0, react.createElement)("div", { style: toolbar }, (0, react.createElement)("button", {
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", {
409
412
  style: btnGhost,
410
413
  onClick: () => void refresh(),
411
414
  disabled: loading
412
- }, loading ? "加载中…" : "刷新"))), (0, react.createElement)("div", { style: tabs }, [
415
+ }, loading ? t("btn.loading") : t("btn.refresh")))), (0, react.createElement)("div", { style: tabs }, [
413
416
  "all",
414
417
  "archived",
415
418
  "stray"
@@ -417,58 +420,62 @@ window.__ModuleLoader__.load({
417
420
  key: k,
418
421
  style: tab === k ? tabStyleActive : tabStyle,
419
422
  onClick: () => setTab(k)
420
- }, k === "all" ? `全部 (${counts.all})` : k === "archived" ? `已归档 (${counts.archived})` : `游离 (${counts.stray})`))), error ? (0, react.createElement)("div", { style: errorStyle }, error) : null, (0, react.createElement)("div", { style: hint }, "会话以文件形式保存在 ~/.dsh/sessions。归档会从官方列表隐藏(与官方归档打通),删除为永久删除,不可恢复。"), (0, react.createElement)("div", { style: list }, filtered.map((s) => (0, react.createElement)("div", {
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", {
421
424
  key: s.id,
422
425
  style: row,
423
426
  onClick: () => void openPreview(s)
424
- }, (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 }, "已归档") : !s.active ? (0, react.createElement)("span", { style: badgeStray }, "游离") : null, (0, react.createElement)("div", {
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", {
425
428
  style: rowActions,
426
429
  onClick: (e) => e.stopPropagation()
427
430
  }, s.archived ? (0, react.createElement)("button", {
428
431
  style: btnGhost,
429
432
  onClick: () => handleUnarchive(s),
430
433
  disabled: busy
431
- }, "恢复") : (0, react.createElement)("button", {
434
+ }, t("btn.unarchive")) : (0, react.createElement)("button", {
432
435
  style: btnGhost,
433
436
  onClick: () => handleArchive(s),
434
437
  disabled: busy
435
- }, "归档"), (0, react.createElement)("button", {
438
+ }, t("btn.archive")), (0, react.createElement)("button", {
436
439
  style: btnDanger,
437
440
  onClick: () => handleDelete(s),
438
441
  disabled: busy
439
- }, "删除"))))), !loading && filtered.length === 0 ? (0, react.createElement)("div", { style: empty }, (0, react.createElement)("p", { style: emptyText }, tab === "archived" ? "没有已归档的会话" : tab === "stray" ? "没有游离会话" : "没有会话")) : null, confirmTarget ? (0, react.createElement)("div", {
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", {
440
443
  style: modalOverlay,
441
444
  onClick: () => setConfirmTarget(null)
442
445
  }, (0, react.createElement)("div", {
443
446
  style: modalCard,
444
447
  onClick: (e) => e.stopPropagation()
445
- }, (0, react.createElement)("h3", { style: modalTitle }, "删除会话?"), (0, react.createElement)("div", { style: modalBody }, `确定要删除会话「${fmtSessionName(confirmTarget)}」吗?`), (0, react.createElement)("div", { style: modalBodyStrong }, "此操作将永久删除该会话及其日志文件,无法恢复(不会进入回收站)。"), (0, react.createElement)("div", { style: modalActions }, (0, react.createElement)("button", {
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", {
446
449
  style: btnGhost,
447
450
  onClick: () => setConfirmTarget(null),
448
451
  disabled: busy
449
- }, "取消"), (0, react.createElement)("button", {
452
+ }, t("btn.cancel")), (0, react.createElement)("button", {
450
453
  style: btnDangerSolid,
451
454
  onClick: () => void confirmDelete(),
452
455
  disabled: busy
453
- }, busy ? "删除中…" : "永久删除")))) : null, previewTarget ? (0, react.createElement)("div", {
456
+ }, busy ? t("btn.deleting") : t("btn.permanentDelete"))))) : null, previewTarget ? (0, react.createElement)("div", {
454
457
  style: modalOverlay,
455
458
  onClick: () => setPreviewTarget(null)
456
459
  }, (0, react.createElement)("div", {
457
460
  style: previewModalCard,
458
461
  onClick: (e) => e.stopPropagation()
459
- }, (0, react.createElement)("h3", { style: modalTitle }, fmtSessionName(previewTarget)), (0, react.createElement)("div", { style: previewMeta }, `ID: ${previewTarget.id} · 创建: ${fmtDateTime(previewData?.createdAt)} · ${fmtBytes(previewTarget.sizeBytes)}`), (0, react.createElement)("div", { style: previewBody }, previewLoading ? (0, react.createElement)("p", { style: emptyText }, "加载中…") : previewData && previewData.messages.length > 0 ? previewData.messages.map((m, i) => (0, react.createElement)("div", {
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", {
460
467
  key: i,
461
468
  style: m.role === "user" ? msgUser : msgAssistant
462
- }, m.text)) : (0, react.createElement)("p", { style: emptyText }, "(该会话暂无文本消息)")), (0, react.createElement)("div", { style: modalActions }, (0, react.createElement)("button", {
469
+ }, m.text)) : (0, react.createElement)("p", { style: emptyText }, t("preview.empty"))), (0, react.createElement)("div", { style: modalActions }, (0, react.createElement)("button", {
463
470
  style: btnDanger,
464
471
  onClick: () => {
465
472
  setPreviewTarget(null);
466
473
  handleDelete(previewTarget);
467
474
  }
468
- }, "删除此会话"), (0, react.createElement)("button", {
475
+ }, t("btn.deleteSession")), (0, react.createElement)("button", {
469
476
  style: btnGhost,
470
477
  onClick: () => setPreviewTarget(null)
471
- }, "关闭")))) : null);
478
+ }, t("btn.close"))))) : null);
472
479
  }
473
480
  //#endregion
474
481
  //#region src/client/config-api.ts
@@ -595,11 +602,87 @@ window.__ModuleLoader__.load({
595
602
  };
596
603
  }
597
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
598
679
  //#region src/client/index.ts
599
680
  /** Section identity in the Settings left-nav rail. */
600
681
  const SECTION_ID = "session-cleaner";
601
682
  /** Sits below Models (10) and IM 通道 (20). */
602
683
  const SECTION_ORDER = 30;
684
+ /** i18n namespace — matches the plugin package name for disambiguation. */
685
+ const I18N_NS = "session-cleaner";
603
686
  /** Services this browser plugin requires (cordis fiber inject). */
604
687
  const inject = [
605
688
  "slots",
@@ -613,6 +696,11 @@ window.__ModuleLoader__.load({
613
696
  */
614
697
  function apply(ctx) {
615
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);
616
704
  let officialArchive = null;
617
705
  ctx.inject(["workspaces"], (sctx) => {
618
706
  officialArchive = async (sessionId) => {
@@ -627,9 +715,10 @@ window.__ModuleLoader__.load({
627
715
  name: "settings.section",
628
716
  id: SECTION_ID,
629
717
  order: SECTION_ORDER,
630
- label: () => "会话管理",
718
+ label: () => t("section.label"),
631
719
  inject: () => ({
632
720
  api,
721
+ t,
633
722
  archiveSession: (sessionId) => officialArchive ? officialArchive(sessionId) : api.archive(sessionId).then((r) => r.ok)
634
723
  })
635
724
  }, SessionManagerSection));
package/lib/index.js CHANGED
@@ -24,6 +24,41 @@ import { zstdDecompressSync } from "node:zlib";
24
24
  * 官方 UI 的隐藏/恢复在 dsh 重启后同步。删除同理(已删除的会话从文件层消失,官方 UI
25
25
  * 重启后不再列出)。
26
26
  */
27
+ /**
28
+ * dsh 会话 ID ↔ 文件系统目录名 编解码(与 dsh-session-persistence-jsonl 的 encodeSegment 对齐)。
29
+ * dsh 把 sessionId 中的非 [A-Za-z0-9._-] 字符编码为 ~XXXX(charCode 的 4 位十六进制大写)。
30
+ * 例如 `im:qq-s5ey:c2c:...` → `im~003Aqq-s5ey~003Ac2c~003A...`。
31
+ * session-cleaner 扫描文件系统得到编码后的目录名,需要解码回原始 ID 才能与 workspace.json 的 sessionIds 匹配。
32
+ */
33
+ function encodeSegment(raw) {
34
+ if (raw.length === 0) return raw;
35
+ if (raw === ".") return "~002E";
36
+ if (raw === "..") return "~002E~002E";
37
+ let out = "";
38
+ for (let i = 0; i < raw.length; i++) {
39
+ const code = raw.charCodeAt(i);
40
+ const ch = String.fromCharCode(code);
41
+ if (ch !== "~" && /^[A-Za-z0-9._-]$/.test(ch)) out += ch;
42
+ else out += "~" + code.toString(16).toUpperCase().padStart(4, "0");
43
+ }
44
+ return out;
45
+ }
46
+ function decodeSegment(encoded) {
47
+ let out = "";
48
+ for (let i = 0; i < encoded.length;) {
49
+ if (encoded[i] === "~" && i + 5 <= encoded.length) {
50
+ const hex = encoded.slice(i + 1, i + 5);
51
+ if (/^[0-9A-Fa-f]{4}$/.test(hex)) {
52
+ out += String.fromCharCode(parseInt(hex, 16));
53
+ i += 5;
54
+ continue;
55
+ }
56
+ }
57
+ out += encoded[i];
58
+ i++;
59
+ }
60
+ return out;
61
+ }
27
62
  /** dsh 用户数据目录(与桌面壳 buildDshEnv 的 DSH_HOME 保持一致) */
28
63
  function dshHome() {
29
64
  return process.env.DSH_HOME ?? path.join(homedir(), ".dsh");
@@ -106,13 +141,13 @@ function listSessions() {
106
141
  continue;
107
142
  }
108
143
  out.push(stripUndefined({
109
- id: entry.name,
144
+ id: decodeSegment(entry.name),
110
145
  workspaceDir: workspaceDir.name,
111
146
  dir,
112
147
  sizeBytes,
113
148
  mtimeMs,
114
- archived: archived.has(entry.name),
115
- active: activeIds.has(entry.name),
149
+ archived: archived.has(decodeSegment(entry.name)),
150
+ active: activeIds.has(decodeSegment(entry.name)),
116
151
  title: readSessionTitle(logFile)
117
152
  }));
118
153
  }
@@ -136,11 +171,12 @@ function removeFromProjcache(id) {
136
171
  if (!fs.existsSync(file)) return;
137
172
  const data = readJson(file, null);
138
173
  if (data === null) return;
174
+ const encodedId = encodeSegment(id);
139
175
  const walk = (o) => {
140
176
  if (!o || typeof o !== "object") return false;
141
177
  let changed = false;
142
178
  for (const k of Object.keys(o)) {
143
- if (String(k).includes(id)) {
179
+ if (String(k).includes(id) || String(k).includes(encodedId)) {
144
180
  delete o[k];
145
181
  changed = true;
146
182
  continue;
@@ -158,10 +194,11 @@ function removeFromProjcache(id) {
158
194
  */
159
195
  function deleteSession(id) {
160
196
  const root = sessionsRoot();
197
+ const encodedId = encodeSegment(id);
161
198
  let target = null;
162
199
  if (fs.existsSync(root)) for (const workspaceDir of fs.readdirSync(root, { withFileTypes: true })) {
163
200
  if (!workspaceDir.isDirectory()) continue;
164
- const dir = path.join(root, workspaceDir.name, id);
201
+ const dir = path.join(root, workspaceDir.name, encodedId);
165
202
  if (fs.existsSync(path.join(dir, "session.jsonl.zstd"))) {
166
203
  target = dir;
167
204
  break;
@@ -292,10 +329,11 @@ function readSessionTitle(logFile) {
292
329
  */
293
330
  function previewSession(id, limit = 12) {
294
331
  const root = sessionsRoot();
332
+ const encodedId = encodeSegment(id);
295
333
  let logFile = null;
296
334
  if (fs.existsSync(root)) for (const workspaceDir of fs.readdirSync(root, { withFileTypes: true })) {
297
335
  if (!workspaceDir.isDirectory()) continue;
298
- const candidate = path.join(root, workspaceDir.name, id, "session.jsonl.zstd");
336
+ const candidate = path.join(root, workspaceDir.name, encodedId, "session.jsonl.zstd");
299
337
  if (fs.existsSync(candidate)) {
300
338
  logFile = candidate;
301
339
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lijian-ui/dsh-session-cleaner",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Session management plugin for DeepSeek Harness (dsh): list / delete / archive & restore sessions, with a built-in \"Archived\" group and a delete confirm dialog (real delete, not trash). 为 DeepSeek Harness 提供会话管理:列表 / 真删除(二次确认)/ 归档分组与恢复。",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -61,23 +61,23 @@
61
61
  },
62
62
  "peerDependencies": {
63
63
  "@deepseek-ai/cordis": "*",
64
- "@deepseek-ai/schemastery": "*",
65
64
  "@deepseek-ai/dsh-settings": "*",
66
- "@deepseek-ai/dsh-typert-protocol": "*"
65
+ "@deepseek-ai/dsh-typert-protocol": "*",
66
+ "@deepseek-ai/schemastery": "*"
67
67
  },
68
68
  "devDependencies": {
69
69
  "@deepseek-ai/cordis": "4.0.1",
70
- "@deepseek-ai/schemastery": "3.18.1",
71
- "@deepseek-ai/dsh-settings": "0.1.0-rc.6",
72
- "@deepseek-ai/dsh-typert-protocol": "0.1.0-rc.6",
70
+ "@deepseek-ai/dsh-api-gateway": "0.1.0-rc.6",
73
71
  "@deepseek-ai/dsh-client-connection": "0.1.0-rc.6",
74
72
  "@deepseek-ai/dsh-client-locale": "0.1.0-rc.6",
75
73
  "@deepseek-ai/dsh-client-runtime": "0.1.0-rc.6",
76
74
  "@deepseek-ai/dsh-client-schema-form": "0.1.0-rc.6",
77
- "@deepseek-ai/dsh-client-ui-slots": "0.1.0-rc.6",
78
75
  "@deepseek-ai/dsh-client-ui-settings": "0.1.0-rc.6",
79
76
  "@deepseek-ai/dsh-client-ui-settings-plugins": "0.1.0-rc.6",
80
- "@deepseek-ai/dsh-api-gateway": "0.1.0-rc.6",
77
+ "@deepseek-ai/dsh-client-ui-slots": "0.1.0-rc.6",
78
+ "@deepseek-ai/dsh-settings": "0.1.0-rc.6",
79
+ "@deepseek-ai/dsh-typert-protocol": "0.1.0-rc.6",
80
+ "@deepseek-ai/schemastery": "3.18.1",
81
81
  "@types/node": "^22.0.0",
82
82
  "@types/react": "^18.3.0",
83
83
  "react": "^18.2.0",