@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/index.js CHANGED
@@ -5,18 +5,30 @@ import * as fs from "fs";
5
5
  import * as path from "path";
6
6
  import { homedir } from "os";
7
7
  import { zstdDecompressSync } from "node:zlib";
8
-
9
8
  //#region src/session-store.ts
10
9
  /**
11
-
10
+ * 会话存储层(session-cleaner host half)
11
+ * ------------------------------------------------------------------
12
+ * 直接操作 dsh 官方文件层(无需官方 API):
13
+ *
14
+ * sessions 树: ~/.dsh/sessions/<encoded-workspace>/<sessionId>/session.jsonl.zstd
15
+ * workspace 状态:~/.dsh/storages/workspace.json
16
+ * - global.archivedSessionIds ← 归档状态(与官方 UI 共享)
17
+ * - tables.workspaces[*].sessionIds ← 会话归属
18
+ * projcache: ~/.dsh/storages/session_projcache.json(删除时必须清,否则旧投影残留)
19
+ *
20
+ * 删除 = 三处清理:session 目录 + projcache + workspace.json(sessionIds/archivedSessionIds)。
21
+ * 归档/恢复 = 读写 workspace.json 的 global.archivedSessionIds(单真相源与官方打通)。
22
+ *
23
+ * ⚠️ 已知边界:归档/恢复直接改文件,host 内存态不会热更新——插件页面以文件为准(准确),
24
+ * 官方 UI 的隐藏/恢复在 dsh 重启后同步。删除同理(已删除的会话从文件层消失,官方 UI
25
+ * 重启后不再列出)。
26
+ */
27
+ /**
12
28
  * dsh 会话 ID ↔ 文件系统目录名 编解码(与 dsh-session-persistence-jsonl 的 encodeSegment 对齐)。
13
-
14
29
  * dsh 把 sessionId 中的非 [A-Za-z0-9._-] 字符编码为 ~XXXX(charCode 的 4 位十六进制大写)。
15
-
16
30
  * 例如 `im:qq-s5ey:c2c:...` → `im~003Aqq-s5ey~003Ac2c~003A...`。
17
-
18
31
  * session-cleaner 扫描文件系统得到编码后的目录名,需要解码回原始 ID 才能与 workspace.json 的 sessionIds 匹配。
19
-
20
32
  */
21
33
  function encodeSegment(raw) {
22
34
  if (raw.length === 0) return raw;
@@ -100,11 +112,8 @@ function writeArchived(archived) {
100
112
  writeJsonAtomic(workspaceFile(), ws);
101
113
  }
102
114
  /**
103
-
104
115
  * 扫描全部 session(文件层权威)。
105
-
106
116
  * 遍历 ~/.dsh/sessions/<workspace>/<sessionId>/,结合 workspace.json 标注状态。
107
-
108
117
  */
109
118
  function listSessions() {
110
119
  const root = sessionsRoot();
@@ -112,7 +121,7 @@ function listSessions() {
112
121
  if (!fs.existsSync(root)) return out;
113
122
  const archived = readArchived();
114
123
  const ws = readWorkspace();
115
- const activeIds = new Set();
124
+ const activeIds = /* @__PURE__ */ new Set();
116
125
  for (const w of Object.values(ws.tables?.workspaces ?? {})) for (const id of w.sessionIds ?? []) activeIds.add(id);
117
126
  for (const workspaceDir of fs.readdirSync(root, { withFileTypes: true })) {
118
127
  if (!workspaceDir.isDirectory()) continue;
@@ -179,13 +188,9 @@ function removeFromProjcache(id) {
179
188
  if (walk(data)) writeJsonAtomic(file, data);
180
189
  }
181
190
  /**
182
-
183
191
  * 真删除会话(不可恢复,调用方必须已二次确认):
184
-
185
192
  * ① 删会话目录;② 清 projcache;③ 清 workspace.json(sessionIds + archivedSessionIds)。
186
-
187
193
  * 返回已删除的目录路径。
188
-
189
194
  */
190
195
  function deleteSession(id) {
191
196
  const root = sessionsRoot();
@@ -230,13 +235,9 @@ function unarchiveSession(id) {
230
235
  return { ok: true };
231
236
  }
232
237
  /**
233
-
234
238
  * 多帧 zstd 解码:dsh 的 session 日志每次追加写一个独立 zstd 帧
235
-
236
239
  * (文件里多个 magic 头),Node 的 zstdDecompressSync 只解第一帧——
237
-
238
240
  * 必须按 magic 位置逐帧解压拼接。
239
-
240
241
  */
241
242
  function zstdDecompressAll(input) {
242
243
  const magic = Buffer.from([
@@ -264,8 +265,7 @@ function zstdDecompressAll(input) {
264
265
  function readSessionLines(logFile) {
265
266
  try {
266
267
  if (!fs.existsSync(logFile)) return null;
267
- const raw = zstdDecompressAll(fs.readFileSync(logFile));
268
- return raw.toString("utf8").split("\n").filter(Boolean);
268
+ return zstdDecompressAll(fs.readFileSync(logFile)).toString("utf8").split("\n").filter(Boolean);
269
269
  } catch {
270
270
  return null;
271
271
  }
@@ -292,15 +292,14 @@ function extractHeader(lines) {
292
292
  cwd: header.cwd
293
293
  };
294
294
  } catch {
295
- return void 0;
295
+ return;
296
296
  }
297
297
  }
298
298
  /** 从消息事件提取纯文本(宽容解析各种 content 形态) */
299
299
  function messageText(message) {
300
300
  if (typeof message === "string") return message;
301
301
  if (!message || typeof message !== "object") return "";
302
- const m = message;
303
- const content = m["content"];
302
+ const content = message["content"];
304
303
  if (typeof content === "string") return content;
305
304
  if (Array.isArray(content)) return content.map((part) => {
306
305
  if (typeof part === "string") return part;
@@ -325,11 +324,8 @@ function readSessionTitle(logFile) {
325
324
  return extractTitle(lines);
326
325
  }
327
326
  /**
328
-
329
327
  * 读取会话预览:header 元数据 + 最近 N 条用户/助手消息文本。
330
-
331
328
  * 找不到文件抛错(调用方转 RPC 错误)。
332
-
333
329
  */
334
330
  function previewSession(id, limit = 12) {
335
331
  const root = sessionsRoot();
@@ -373,7 +369,6 @@ function previewSession(id, limit = 12) {
373
369
  messages: messages.slice(-limit)
374
370
  });
375
371
  }
376
-
377
372
  //#endregion
378
373
  //#region src/remote.ts
379
374
  const SERVICE = "sessionCleanerRemote";
@@ -384,11 +379,8 @@ const codec = (typeSymbol) => ({
384
379
  schema: { parse: (v) => v }
385
380
  });
386
381
  /**
387
-
388
382
  * 注册到 `ctx.typert` 的 host 面契约。
389
-
390
383
  * face: 'host' + invocations(client 侧的 $mount 贡献则带 descriptors)。
391
-
392
384
  */
393
385
  const CONTRIBUTION = {
394
386
  package: PACKAGE,
@@ -474,11 +466,8 @@ const CONTRIBUTION = {
474
466
  }
475
467
  };
476
468
  /**
477
-
478
469
  * Remote 服务实现。构造时注册 `sessionCleanerRemote` cordis 服务,
479
-
480
470
  * 上面的贡献让 API gateway 能分发四个方法。
481
-
482
471
  */
483
472
  var SessionCleanerApi = class extends TypertRemoteService {
484
473
  constructor(ctx) {
@@ -510,7 +499,6 @@ function registerSessionCleanerRemote(ctx) {
510
499
  new SessionCleanerApi(ctx);
511
500
  ctx.effect(() => ctx.get("typert").register(CONTRIBUTION), "session-cleaner: typert contribution");
512
501
  }
513
-
514
502
  //#endregion
515
503
  //#region src/index.ts
516
504
  /**
@@ -543,19 +531,17 @@ function apply(ctx) {
543
531
  */
544
532
  function installConsoleLoggerExporter(ctx) {
545
533
  try {
546
- const logger = ctx.root.logger;
547
- logger?.exporter?.({
534
+ ctx.root.logger?.exporter?.({
548
535
  colors: 0,
549
536
  export: (message) => {
550
537
  const { name = "session-cleaner", type = "log", args = [] } = message;
551
- const rendered = args.map((arg) => arg instanceof Error ? arg.stack ?? arg.message : typeof arg === "string" ? arg : (() => {
538
+ const line = `[${name}] ${args.map((arg) => arg instanceof Error ? arg.stack ?? arg.message : typeof arg === "string" ? arg : (() => {
552
539
  try {
553
540
  return JSON.stringify(arg);
554
541
  } catch {
555
542
  return String(arg);
556
543
  }
557
- })()).join(" ");
558
- const line = `[${name}] ${rendered}`;
544
+ })()).join(" ")}`;
559
545
  if (type === "error") console.error(line);
560
546
  else if (type === "warn") console.warn(line);
561
547
  else if (type === "debug") console.debug(line);
@@ -564,6 +550,5 @@ function installConsoleLoggerExporter(ctx) {
564
550
  });
565
551
  } catch {}
566
552
  }
567
-
568
553
  //#endregion
569
- export { Config, apply, inject };
554
+ export { Config, apply, inject };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lijian-ui/dsh-session-cleaner",
3
- "version": "0.1.2",
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",
File without changes