@michengai/dsh-archive-manager 1.0.19 → 1.1.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.
@@ -1,5 +1,6 @@
1
1
  import { symbols } from "@deepseek-ai/cordis";
2
2
  import { record } from "./contracts.js";
3
+ import { guardReleaseAgeExclude, mergeMinimumReleaseAgeExclude, recordReleaseAgeExclude } from "./release-age.js";
3
4
  import { spawn } from "node:child_process";
4
5
  import { existsSync, readFileSync } from "node:fs";
5
6
  import { readFile } from "node:fs/promises";
@@ -305,15 +306,26 @@ async function runPluginManagerInstall(target, packageSpec) {
305
306
  throw new Error(result.error?.message || result.packageResult?.output || "\u66F4\u65B0\u5931\u8D25\uFF0C\u8BF7\u67E5\u770B\u670D\u52A1\u7AEF\u65E5\u5FD7\u3002");
306
307
  }
307
308
  async function install(target, packageSpec) {
309
+ await guardReleaseAgeExclude(target.profileDir, packageSpec);
308
310
  if (target.desktopPnpm !== void 0) {
309
311
  const handle = target.desktopPnpm.runPlugin(["add", "--config.minimumReleaseAge=0", packageSpec, "--registry=https://registry.npmjs.org/"], target.profileDir);
310
312
  const result = await handle.done;
311
313
  if (result.exitCode !== 0) throw new Error(`\u66F4\u65B0\u8FDB\u7A0B\u9000\u51FA\u7801 ${String(result.exitCode)}\u3002`);
314
+ await recordReleaseAgeExclude(target.profileDir, packageSpec);
312
315
  return;
313
316
  }
314
- if (target.packageManager !== void 0) return runPackageManagerInstall(target, packageSpec);
315
- if (target.pluginManager !== void 0) return runPluginManagerInstall(target, packageSpec);
316
- return runCliInstall(target, packageSpec);
317
+ if (target.packageManager !== void 0) {
318
+ await runPackageManagerInstall(target, packageSpec);
319
+ await recordReleaseAgeExclude(target.profileDir, packageSpec);
320
+ return;
321
+ }
322
+ if (target.pluginManager !== void 0) {
323
+ await runPluginManagerInstall(target, packageSpec);
324
+ await recordReleaseAgeExclude(target.profileDir, packageSpec);
325
+ return;
326
+ }
327
+ await runCliInstall(target, packageSpec);
328
+ await recordReleaseAgeExclude(target.profileDir, packageSpec);
317
329
  }
318
330
  function json(response, statusCode, value) {
319
331
  response.writeHead(statusCode, { "content-type": "application/json; charset=utf-8", "cache-control": "no-store" });
@@ -417,6 +429,8 @@ export {
417
429
  isDshCliEntry,
418
430
  isNewerVersion,
419
431
  isTrustedUpdateRequest,
432
+ mergeMinimumReleaseAgeExclude,
433
+ recordReleaseAgeExclude,
420
434
  registerPluginUpdater,
421
435
  resolveUpdateRuntime,
422
436
  shouldNotifyParent
package/lib/projcache.js CHANGED
@@ -222,10 +222,6 @@ var ArchiveProjectionCache = class extends CacheBase {
222
222
  await super.write(session);
223
223
  if (this.deletedSessionIds.has(session.id)) await this.requireTable().delete(session.id);
224
224
  }
225
- /**
226
- * 守住所有底层写入。rc.2 的冷读经 `putSoft -> put`,alpha.2 则
227
- * 直接经 `put`;把墓碑与 whenIdle 跟踪放在这里可同时兼容两代实现。
228
- */
229
225
  put(id, identity, rows) {
230
226
  if (this.deletedSessionIds.has(id)) return Promise.resolve();
231
227
  const task = this.putCore(id, identity, rows);
@@ -236,16 +232,6 @@ var ArchiveProjectionCache = class extends CacheBase {
236
232
  await super.put(id, identity, rows);
237
233
  if (this.deletedSessionIds.has(id)) await this.requireTable().delete(id);
238
234
  }
239
- /** rc.2 公开的 fail-soft 辅助在 alpha.2 已移除;保留同形兼容入口。 */
240
- async putSoft(id, identity, rows, what) {
241
- const upstream = CacheBase.prototype.putSoft;
242
- if (typeof upstream === "function") return upstream.call(this, id, identity, rows, what);
243
- try {
244
- await this.put(id, identity, rows);
245
- } catch (error) {
246
- this.ctx.logger.warn(`session projection cache: ${what} for "${id}" failed (cache stays stale): ${String(error)}`);
247
- }
248
- }
249
235
  /**
250
236
  * 全部被跟踪的在途写入落定后 resolve(含失败)。空闲缓存上调用立即返回。
251
237
  * @returns 跟踪写入落定后的 resolution。
@@ -0,0 +1,164 @@
1
+ import { existsSync } from "node:fs";
2
+ import { readFile, rename, unlink, writeFile } from "node:fs/promises";
3
+ import { resolve } from "node:path";
4
+ function releaseAgeSpec(packageSpec) {
5
+ const at = packageSpec.lastIndexOf("@");
6
+ if (at <= 0) return void 0;
7
+ const name = packageSpec.slice(0, at);
8
+ const version = packageSpec.slice(at + 1);
9
+ if (!isPackageName(name) || !isReleaseVersion(version)) return void 0;
10
+ return { name, version };
11
+ }
12
+ function isPackageName(name) {
13
+ return /^@[^/\s]+\/[^/\s]+$/.test(name) || /^[^@/\s]+$/.test(name);
14
+ }
15
+ function isReleaseVersion(version) {
16
+ return /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(version);
17
+ }
18
+ function unreadableReleaseAgeExclude() {
19
+ return new Error("\u53D1\u5E03\u5E74\u9F84\u767D\u540D\u5355\u65E0\u6CD5\u5B89\u5168\u5408\u5E76\uFF0C\u5DF2\u505C\u6B62\u66F4\u65B0\uFF0C\u907F\u514D\u8FD9\u4E2A profile \u4E4B\u540E\u65E0\u6CD5\u5B89\u88C5\u63D2\u4EF6\u3002");
20
+ }
21
+ function excludeEntry(indent, name, versions, comment = "") {
22
+ const entry = `${indent}- '${name}@${versions.join(" || ")}'`;
23
+ return comment === "" ? entry : `${entry} ${comment}`;
24
+ }
25
+ function parseExcludeLine(line) {
26
+ const dash = line.indexOf("-");
27
+ if (!/^\s+-/.test(line) || dash < 0) return void 0;
28
+ const indent = line.slice(0, dash);
29
+ const raw = line.slice(dash + 1).trim();
30
+ let body = raw;
31
+ let comment = "";
32
+ if (raw.startsWith("'") || raw.startsWith('"')) {
33
+ const quote = raw[0] ?? "'";
34
+ const end = raw.lastIndexOf(quote);
35
+ if (end <= 0) return void 0;
36
+ const rest = raw.slice(end + 1).trim();
37
+ if (rest !== "" && !rest.startsWith("#")) return void 0;
38
+ if (rest.startsWith("#")) comment = rest;
39
+ body = raw.slice(1, end);
40
+ } else if (raw.includes("#")) return void 0;
41
+ if (body.includes("#")) return void 0;
42
+ const versionAt = body.lastIndexOf("@");
43
+ if (versionAt > 0) {
44
+ const name = body.slice(0, versionAt);
45
+ const versions = body.slice(versionAt + 1).split(" || ");
46
+ if (isPackageName(name) && versions.every(isReleaseVersion)) return { indent, name, versions, comment };
47
+ return void 0;
48
+ }
49
+ if (isPackageName(body) || /^@[^/\s]+\/\*$/.test(body)) return { indent, name: body, versions: void 0, comment };
50
+ return void 0;
51
+ }
52
+ function mergeMinimumReleaseAgeExclude(yaml, packageSpec) {
53
+ const spec = releaseAgeSpec(packageSpec);
54
+ if (spec === void 0) throw new Error("\u66F4\u65B0\u7248\u672C\u65E0\u6CD5\u5199\u5165\u53D1\u5E03\u5E74\u9F84\u767D\u540D\u5355\uFF0C\u5DF2\u505C\u6B62\u66F4\u65B0\u3002");
55
+ const eol = yaml.includes("\r\n") ? "\r\n" : "\n";
56
+ const text = yaml.replaceAll("\r\n", "\n");
57
+ const next = mergeReleaseAgeText(text, spec.name, spec.version);
58
+ return eol === "\r\n" ? next.replaceAll("\n", "\r\n") : next;
59
+ }
60
+ function mergeReleaseAgeText(yaml, name, version) {
61
+ if (/minimumReleaseAgeExclude:[^\n\r]/.test(yaml)) throw unreadableReleaseAgeExclude();
62
+ const marker = /(?:^|\n)minimumReleaseAgeExclude:[ \t]*(?:\n|$)/.exec(yaml);
63
+ if (marker === null) {
64
+ const base = yaml.endsWith("\n") || yaml.length === 0 ? yaml : `${yaml}
65
+ `;
66
+ const gap = base.length === 0 || base.endsWith("\n\n") ? "" : "\n";
67
+ return `${base}${gap}minimumReleaseAgeExclude:
68
+ - '${name}@${version}'
69
+ `;
70
+ }
71
+ const keyAt = (marker.index ?? 0) + (marker[0].startsWith("\n") ? 1 : 0);
72
+ let cursor = keyAt + "minimumReleaseAgeExclude:".length;
73
+ while (cursor < yaml.length && (yaml[cursor] === " " || yaml[cursor] === " ")) cursor += 1;
74
+ if (yaml[cursor] !== "\n") throw unreadableReleaseAgeExclude();
75
+ const bodyStart = cursor + 1;
76
+ const lines = [];
77
+ let bodyEnd = bodyStart;
78
+ while (bodyEnd <= yaml.length) {
79
+ const nextNl = yaml.indexOf("\n", bodyEnd);
80
+ const lineEnd = nextNl === -1 ? yaml.length : nextNl;
81
+ const line = yaml.slice(bodyEnd, lineEnd);
82
+ const ended = line.trim() !== "" && !/^[ \t]*#/.test(line) && !/^[ \t]/.test(line);
83
+ if (ended) break;
84
+ if (line.trim() !== "" && !/^[ \t]*#/.test(line) && !/^[ \t]+-/.test(line)) throw unreadableReleaseAgeExclude();
85
+ lines.push(line);
86
+ if (nextNl === -1) {
87
+ bodyEnd = yaml.length;
88
+ break;
89
+ }
90
+ bodyEnd = nextNl + 1;
91
+ if (bodyEnd === yaml.length) break;
92
+ }
93
+ let ours;
94
+ const rewritten = lines.map((line, index) => {
95
+ if (line.trim() === "" || /^[ \t]*#/.test(line)) return line;
96
+ const parsed = parseExcludeLine(line);
97
+ if (parsed === void 0) throw unreadableReleaseAgeExclude();
98
+ if (parsed.name !== name) return line;
99
+ if (ours === void 0) {
100
+ ours = { index, indent: parsed.indent, versions: parsed.versions, comment: parsed.comment };
101
+ return line;
102
+ }
103
+ if (ours.versions !== void 0 && parsed.versions !== void 0) ours.versions = [...ours.versions, ...parsed.versions.filter((item) => !ours?.versions?.includes(item))];
104
+ return "";
105
+ });
106
+ if (ours?.versions === void 0 && ours !== void 0) return yaml;
107
+ const indent = ours?.indent ?? lines.find((line) => /^[ \t]+-/.test(line))?.match(/^([ \t]+)/)?.[1] ?? " ";
108
+ const versions = ours?.versions === void 0 ? [version] : ours.versions.includes(version) ? ours.versions : [...ours.versions, version];
109
+ const entry = excludeEntry(indent, name, versions, ours?.comment ?? "");
110
+ if (ours === void 0) rewritten.push(entry);
111
+ else rewritten[ours.index] = entry;
112
+ const body = rewritten.filter((line) => line !== "").join("\n");
113
+ const prefix = yaml.slice(0, bodyStart);
114
+ const suffix = yaml.slice(bodyEnd);
115
+ const joined = `${prefix}${body}${body.length === 0 || body.endsWith("\n") ? "" : "\n"}${suffix}`;
116
+ return joined.endsWith("\n") ? joined : `${joined}
117
+ `;
118
+ }
119
+ async function guardReleaseAgeExclude(profileDir, packageSpec) {
120
+ const file = resolve(profileDir, "pnpm-workspace.yaml");
121
+ const current = existsSync(file) ? await readFile(file, "utf8") : "packages:\n - .\n";
122
+ mergeMinimumReleaseAgeExclude(current, packageSpec);
123
+ }
124
+ async function replaceTextFile(file, next) {
125
+ const tmp = `${file}.${process.pid}.tmp`;
126
+ const backup = `${file}.${process.pid}.bak`;
127
+ await writeFile(tmp, next, "utf8");
128
+ try {
129
+ await rename(tmp, file);
130
+ return;
131
+ } catch (error) {
132
+ const code = error && typeof error === "object" && "code" in error ? String(error.code) : "";
133
+ if (code !== "EEXIST" && code !== "EPERM" && code !== "EACCES") {
134
+ await unlink(tmp).catch(() => {
135
+ });
136
+ throw error;
137
+ }
138
+ }
139
+ await unlink(backup).catch(() => {
140
+ });
141
+ await rename(file, backup);
142
+ try {
143
+ await rename(tmp, file);
144
+ } catch (error) {
145
+ await rename(backup, file).catch(() => {
146
+ });
147
+ await unlink(tmp).catch(() => {
148
+ });
149
+ throw error;
150
+ }
151
+ await unlink(backup).catch(() => {
152
+ });
153
+ }
154
+ async function recordReleaseAgeExclude(profileDir, packageSpec) {
155
+ const file = resolve(profileDir, "pnpm-workspace.yaml");
156
+ const current = existsSync(file) ? await readFile(file, "utf8") : "packages:\n - .\n";
157
+ const next = mergeMinimumReleaseAgeExclude(current, packageSpec);
158
+ if (next !== current) await replaceTextFile(file, next);
159
+ }
160
+ export {
161
+ guardReleaseAgeExclude,
162
+ mergeMinimumReleaseAgeExclude,
163
+ recordReleaseAgeExclude
164
+ };
package/lib/workspace.js CHANGED
@@ -44,6 +44,24 @@ function jsonlSessionDirectory(persistence, header, location) {
44
44
  if (location.path !== join(directory, basename(location.path))) return;
45
45
  return directory;
46
46
  }
47
+ async function pathExists(path) {
48
+ try {
49
+ await lstat(path);
50
+ return true;
51
+ } catch (error) {
52
+ if (errorCode(error) === "ENOENT") return false;
53
+ throw error;
54
+ }
55
+ }
56
+ class PersistenceStillPresentError extends Error {
57
+ constructor(sessionId) {
58
+ super(`session "${sessionId}" is still present in persistence after artifact removal`);
59
+ this.name = "PersistenceStillPresentError";
60
+ }
61
+ }
62
+ function deletionFailureMessage(error) {
63
+ return errorMessage(error).slice(0, 400);
64
+ }
47
65
  function strictCodec(typeSymbol, schema) {
48
66
  return {
49
67
  mode: "strict",
@@ -295,12 +313,8 @@ async function activeSessionErrorType() {
295
313
  activeSessionError = typeof workspace.WorkspaceActiveSessionError === "function" ? workspace.WorkspaceActiveSessionError : null;
296
314
  return activeSessionError ?? void 0;
297
315
  }
298
- function snapshotUsesProjectionKeys(cache) {
299
- return cache.cachedSnapshot.length === 2;
300
- }
301
- function projectionSnapshot(cache, header, inheritedEventCount) {
302
- if (snapshotUsesProjectionKeys(cache)) return cache.cachedSnapshot(header);
303
- return cache.cachedSnapshot(header, inheritedEventCount);
316
+ function projectionSnapshot(cache, header) {
317
+ return cache.cachedSnapshot(header);
304
318
  }
305
319
  function hostEvent(ctx, name) {
306
320
  const caller = ctx[name];
@@ -364,7 +378,7 @@ var ArchiveWorkspaceRegistry = class extends WorkspaceRegistry {
364
378
  const live = this.ctx.get("sessions")?.get(sessionId);
365
379
  if (typeof live?.snapshotEvents === "function") return live.snapshotEvents();
366
380
  const persistence = this.ctx.get("sessionPersistence");
367
- if (!persistence || typeof persistence.readFrom !== "function" && typeof persistence.open !== "function") throw new Error("\u5F53\u524D\u5BBF\u4E3B\u4E0D\u652F\u6301\u53EA\u8BFB\u4F1A\u8BDD\uFF0C\u8BF7\u6253\u5F00\u5B8C\u6574\u4F1A\u8BDD\u67E5\u770B");
381
+ if (!persistence || typeof persistence.open !== "function") throw new Error("\u5F53\u524D\u5BBF\u4E3B\u4E0D\u652F\u6301\u53EA\u8BFB\u4F1A\u8BDD\uFF0C\u8BF7\u6253\u5F00\u5B8C\u6574\u4F1A\u8BDD\u67E5\u770B");
368
382
  return (await this.readStoredProjectionSource(persistence, sessionId)).events;
369
383
  }
370
384
  /** 只读取当前宿主已知会话;路径由宿主定位,不根据错误文本或 ID 拼接。 */
@@ -615,11 +629,17 @@ var ArchiveWorkspaceRegistry = class extends WorkspaceRegistry {
615
629
  if (await this.coldReuseKnown(sessionId)) kept.push(sessionId);
616
630
  continue;
617
631
  }
618
- if (this.headers.has(sessionId) || listed.has(sessionId)) {
632
+ if (listed.has(sessionId)) {
619
633
  kept.push(sessionId);
620
634
  continue;
621
635
  }
622
- if (!await this.sessionArtifactMissing(sessionId)) kept.push(sessionId);
636
+ if (await this.sessionArtifactMissing(sessionId)) {
637
+ const header = this.headers.get(sessionId);
638
+ this.forgetIndexedSession(sessionId);
639
+ if (header !== void 0) this.deletedIdentities.set(sessionId, headerIdentity(header));
640
+ continue;
641
+ }
642
+ kept.push(sessionId);
623
643
  }
624
644
  if (kept.length === state.archivedSessionIds.length && kept.every((id, index) => id === state.archivedSessionIds[index]))
625
645
  return kept;
@@ -662,10 +682,10 @@ var ArchiveWorkspaceRegistry = class extends WorkspaceRegistry {
662
682
  const projections = this.ctx.get("sessionProjections");
663
683
  if (cache === void 0 || typeof cache.cachedSnapshot !== "function" || typeof cache.put !== "function")
664
684
  return false;
665
- if (persistence === void 0 || typeof persistence.readFrom !== "function" && typeof persistence.open !== "function" || projections === void 0 || typeof projections.restore !== "function")
685
+ if (persistence === void 0 || typeof persistence.open !== "function" || projections === void 0 || typeof projections.restore !== "function")
666
686
  return false;
667
687
  try {
668
- if (!header.isSeeded && projectionSnapshot(cache, header, 0) !== void 0)
688
+ if (!header.isSeeded && projectionSnapshot(cache, header) !== void 0)
669
689
  return false;
670
690
  const stored = await this.readStoredProjectionSource(persistence, header.id);
671
691
  const meta = stored.meta ?? header;
@@ -682,7 +702,7 @@ var ArchiveWorkspaceRegistry = class extends WorkspaceRegistry {
682
702
  );
683
703
  return false;
684
704
  }
685
- if (projectionSnapshot(cache, meta, inheritedEventCount) !== void 0) return false;
705
+ if (projectionSnapshot(cache, meta) !== void 0) return false;
686
706
  const restored = projections.restore(
687
707
  {},
688
708
  stored.events,
@@ -712,10 +732,8 @@ var ArchiveWorkspaceRegistry = class extends WorkspaceRegistry {
712
732
  return false;
713
733
  }
714
734
  }
715
- /** 新版读句柄必须关闭;旧版仍沿用 readFrom,避免激活 Agent 或写入会话日志。 */
735
+ /** 只读句柄必须关闭,避免激活 Agent 或写入会话日志。 */
716
736
  async readStoredProjectionSource(persistence, sessionId) {
717
- if (typeof persistence.readFrom === "function")
718
- return persistence.readFrom(sessionId, 0);
719
737
  const handle = await persistence.open(sessionId, "read");
720
738
  try {
721
739
  const { events } = await handle.read(0);
@@ -753,7 +771,7 @@ var ArchiveWorkspaceRegistry = class extends WorkspaceRegistry {
753
771
  if (stopActivity && ActiveSession !== void 0) await this.stopSessionActivity(sessionId);
754
772
  });
755
773
  }
756
- /** 0.1.7 起宿主用 waterfall 报告仍在运行的回合、子代理、任务和提醒。旧宿主没有该事件。 */
774
+ /** 宿主用 waterfall 报告仍在运行的回合、子代理、任务和提醒。 */
757
775
  async sessionActivity(sessionId) {
758
776
  const waterfall = hostEvent(this.ctx, "waterfall");
759
777
  if (waterfall === void 0) return [];
@@ -845,7 +863,7 @@ var ArchiveWorkspaceRegistry = class extends WorkspaceRegistry {
845
863
  if (result?.skipped) skippedSessionIds.push(sessionId);
846
864
  else deletedSessionIds.push(sessionId);
847
865
  } catch (error) {
848
- failures.push({ sessionId, message: String(error) });
866
+ failures.push({ sessionId, message: deletionFailureMessage(error) });
849
867
  }
850
868
  }
851
869
  return {
@@ -1036,14 +1054,12 @@ var ArchiveWorkspaceRegistry = class extends WorkspaceRegistry {
1036
1054
  if (this.deletedSessionIds.has(header.id)) return;
1037
1055
  return super.indexHeader(header);
1038
1056
  }
1039
- /** 统一旧版头部数组与 0.1.3 的持久化快照,供父类索引和本插件枚举共用。 */
1057
+ /** 持久化列表返回快照,这里只取出头部。 */
1040
1058
  async listStoredHeaders() {
1041
- return (await this.ctx.sessionPersistence.list()).map(
1042
- (item) => "header" in item && item.header ? item.header : item
1043
- );
1059
+ return (await this.ctx.sessionPersistence.list()).map((item) => item.header);
1044
1060
  }
1045
1061
  async indexHeaders(items) {
1046
- for (const item of items) await this.indexHeader("header" in item && item.header ? item.header : item);
1062
+ for (const item of items) await this.indexHeader(item);
1047
1063
  }
1048
1064
  /** 为未处于实时状态的持久化会话发布相同的移除事件。 */
1049
1065
  async publishColdSessionRemoval(sessionId, sessions) {
@@ -1073,7 +1089,7 @@ var ArchiveWorkspaceRegistry = class extends WorkspaceRegistry {
1073
1089
  `cannot delete session "${sessionId}": the session persistence backend does not expose locate() to resolve its transcript artifact`
1074
1090
  );
1075
1091
  }
1076
- const header = await this.readSessionHeader(sessionId);
1092
+ const header = this.headers?.get(sessionId) ?? await this.readSessionHeader(sessionId);
1077
1093
  const location = persistence.locate(header);
1078
1094
  if (location === void 0 || typeof location.path !== "string") {
1079
1095
  throw new Error(
@@ -1103,11 +1119,26 @@ var ArchiveWorkspaceRegistry = class extends WorkspaceRegistry {
1103
1119
  `archive-manager: session "${sessionId}": JSONL directory ownership could not be verified; falling back to artifact-only deletion at "${location.path}" (parent directory retained)`
1104
1120
  );
1105
1121
  }
1122
+ const existed = await pathExists(target.path);
1106
1123
  await rm(target.path, { recursive: true, force: true });
1107
- if (typeof persistence.stat === "function" && await persistence.stat(sessionId) !== void 0) {
1108
- throw new Error(
1109
- `session "${sessionId}" is still present in persistence after artifact removal`
1110
- );
1124
+ if (typeof persistence.stat === "function") {
1125
+ try {
1126
+ const postStat = await persistence.stat(sessionId);
1127
+ if (postStat !== void 0) {
1128
+ throw new PersistenceStillPresentError(sessionId);
1129
+ }
1130
+ } catch (error) {
1131
+ if (error instanceof PersistenceStillPresentError) throw error;
1132
+ const gone = !await pathExists(target.path);
1133
+ if (!existed || !gone) {
1134
+ throw new Error(
1135
+ `cannot verify removal of session "${sessionId}": stat failed and "${target.path}" was not a present artifact removed by this deletion`
1136
+ );
1137
+ }
1138
+ this.ctx.logger.warn(
1139
+ `archive-manager: session "${sessionId}" stat check after artifact removal failed; removal confirmed because "${target.path}" existed before rm and is gone now: ${String(error)}`
1140
+ );
1141
+ }
1111
1142
  }
1112
1143
  } catch (error) {
1113
1144
  const message = `cannot delete session "${sessionId}": cleanup of ${target.kind} "${target.path}" failed; bookkeeping retained for retry`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@michengai/dsh-archive-manager",
3
- "version": "1.0.19",
3
+ "version": "1.1.0",
4
4
  "description": "归档、恢复、收藏和永久删除会话,并诊断损坏的会话日志。 · Archive, restore, favorite, and permanently delete sessions, and diagnose damaged session logs.",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@11.22.0",
@@ -34,7 +34,7 @@
34
34
  "test": "pnpm build && node --import tsx --test test/*.test.mjs",
35
35
  "test:matrix": "pnpm build && node scripts/test-version-matrix.mjs",
36
36
  "test:latest": "pnpm build && node scripts/test-latest-compatibility.mjs",
37
- "test:compat": "pnpm build && node scripts/test-legacy-compatibility.mjs && pnpm test:matrix",
37
+ "test:compat": "pnpm build && pnpm test:matrix",
38
38
  "pack:check": "pnpm pack --dry-run",
39
39
  "verify": "pnpm test && pnpm test:compat && pnpm pack:check && node scripts/check-generated.mjs",
40
40
  "release:preflight": "pwsh -NoProfile -File scripts/release-preflight.ps1",
@@ -43,63 +43,58 @@
43
43
  "typecheck": "tsc --noEmit"
44
44
  },
45
45
  "peerDependencies": {
46
- "@deepseek-ai/cordis": ">=4.0.1 <5.0.0 || 4.0.5-alpha.1 || 4.0.5-alpha.2",
47
- "@deepseek-ai/dsh-client-connection": "0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-alpha.2 || 0.2.1-alpha.3",
48
- "@deepseek-ai/dsh-client-locale": "0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-alpha.2 || 0.2.1-alpha.3",
49
- "@deepseek-ai/dsh-client-store": "0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-alpha.2 || 0.2.1-alpha.3",
50
- "@deepseek-ai/dsh-client-ui-conversation": "0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-alpha.2 || 0.2.1-alpha.3",
51
- "@deepseek-ai/dsh-client-ui-primitives": "0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-alpha.2 || 0.2.1-alpha.3",
52
- "@deepseek-ai/dsh-client-ui-sidebar": "0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-alpha.2 || 0.2.1-alpha.3",
53
- "@deepseek-ai/dsh-client-ui-workspace": "0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-alpha.2 || 0.2.1-alpha.3",
54
- "@deepseek-ai/dsh-host-webserver": "0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-alpha.2 || 0.2.1-alpha.3",
55
- "@deepseek-ai/dsh-session": "0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-alpha.2 || 0.2.1-alpha.3",
56
- "@deepseek-ai/dsh-session-format-catalog": "0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-alpha.2 || 0.2.1-alpha.3",
57
- "@deepseek-ai/dsh-session-projection-cache": "0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-alpha.2 || 0.2.1-alpha.3",
58
- "@deepseek-ai/dsh-spill-local": "0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-alpha.2 || 0.2.1-alpha.3",
59
- "@deepseek-ai/dsh-storage-domain": "0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-alpha.2 || 0.2.1-alpha.3",
60
- "@deepseek-ai/dsh-typert-protocol": "0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-alpha.2 || 0.2.1-alpha.3",
61
- "@deepseek-ai/dsh-typert-registry": "0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-alpha.2 || 0.2.1-alpha.3",
62
- "@deepseek-ai/dsh-workspace": "0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-alpha.2 || 0.2.1-alpha.3"
46
+ "@deepseek-ai/cordis": ">=4.0.1 <5.0.0",
47
+ "@deepseek-ai/dsh-client-connection": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2",
48
+ "@deepseek-ai/dsh-client-locale": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2",
49
+ "@deepseek-ai/dsh-client-store": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2",
50
+ "@deepseek-ai/dsh-client-ui-conversation": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2",
51
+ "@deepseek-ai/dsh-client-ui-primitives": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2",
52
+ "@deepseek-ai/dsh-client-ui-sidebar": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2",
53
+ "@deepseek-ai/dsh-client-ui-workspace": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2",
54
+ "@deepseek-ai/dsh-host-webserver": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2",
55
+ "@deepseek-ai/dsh-session": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2",
56
+ "@deepseek-ai/dsh-session-format-catalog": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2",
57
+ "@deepseek-ai/dsh-session-projection-cache": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2",
58
+ "@deepseek-ai/dsh-spill-local": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2",
59
+ "@deepseek-ai/dsh-storage-domain": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2",
60
+ "@deepseek-ai/dsh-typert-protocol": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2",
61
+ "@deepseek-ai/dsh-typert-registry": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2",
62
+ "@deepseek-ai/dsh-workspace": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2"
63
63
  },
64
64
  "peerDependenciesMeta": {
65
65
  "@deepseek-ai/dsh-session-format-catalog": {
66
66
  "optional": true
67
- },
68
- "@deepseek-ai/dsh-client-store": {
69
- "optional": true
70
67
  }
71
68
  },
72
69
  "devDependencies": {
73
- "@deepseek-ai/cordis": "4.0.5-alpha.2",
74
- "@deepseek-ai/dsh-api-gateway": "0.2.1-alpha.3",
75
- "@deepseek-ai/dsh-client-connection": "0.2.1-alpha.3",
76
- "@deepseek-ai/dsh-client-locale": "0.2.1-alpha.3",
77
- "@deepseek-ai/dsh-client-store": "0.2.1-alpha.3",
78
- "@deepseek-ai/dsh-client-ui-conversation": "0.2.1-alpha.3",
79
- "@deepseek-ai/dsh-client-ui-primitives": "0.2.1-alpha.3",
80
- "@deepseek-ai/dsh-client-ui-renderer": "0.2.1-alpha.3",
81
- "@deepseek-ai/dsh-client-ui-sidebar": "0.2.1-alpha.3",
82
- "@deepseek-ai/dsh-client-ui-slots": "0.2.1-alpha.3",
83
- "@deepseek-ai/dsh-client-ui-workspace": "0.2.1-alpha.3",
84
- "@deepseek-ai/dsh-host-webserver": "0.2.1-alpha.3",
85
- "@deepseek-ai/dsh-scope": "0.2.1-alpha.3",
86
- "@deepseek-ai/dsh-session": "0.2.1-alpha.3",
87
- "@deepseek-ai/dsh-session-format-catalog": "0.2.1-alpha.3",
88
- "@deepseek-ai/dsh-session-persistence": "0.2.1-alpha.3",
89
- "@deepseek-ai/dsh-session-projection": "0.2.1-alpha.3",
90
- "@deepseek-ai/dsh-session-projection-cache": "0.2.1-alpha.3",
91
- "@deepseek-ai/dsh-spill": "0.2.1-alpha.3",
92
- "@deepseek-ai/dsh-spill-local": "0.2.1-alpha.3",
93
- "@deepseek-ai/dsh-storage": "0.2.1-alpha.3",
94
- "@deepseek-ai/dsh-storage-domain": "0.2.1-alpha.3",
95
- "@deepseek-ai/dsh-typert-protocol": "0.2.1-alpha.3",
96
- "@deepseek-ai/dsh-typert-registry": "0.2.1-alpha.3",
97
- "@deepseek-ai/dsh-workspace": "0.2.1-alpha.3",
70
+ "@deepseek-ai/cordis": "4.0.5",
71
+ "@deepseek-ai/dsh-api-gateway": "0.2.1-rc.2",
72
+ "@deepseek-ai/dsh-client-connection": "0.2.1-rc.2",
73
+ "@deepseek-ai/dsh-client-locale": "0.2.1-rc.2",
74
+ "@deepseek-ai/dsh-client-store": "0.2.1-rc.2",
75
+ "@deepseek-ai/dsh-client-ui-conversation": "0.2.1-rc.2",
76
+ "@deepseek-ai/dsh-client-ui-primitives": "0.2.1-rc.2",
77
+ "@deepseek-ai/dsh-client-ui-renderer": "0.2.1-rc.2",
78
+ "@deepseek-ai/dsh-client-ui-sidebar": "0.2.1-rc.2",
79
+ "@deepseek-ai/dsh-client-ui-slots": "0.2.1-rc.2",
80
+ "@deepseek-ai/dsh-client-ui-workspace": "0.2.1-rc.2",
81
+ "@deepseek-ai/dsh-host-webserver": "0.2.1-rc.2",
82
+ "@deepseek-ai/dsh-scope": "0.2.1-rc.2",
83
+ "@deepseek-ai/dsh-session": "0.2.1-rc.2",
84
+ "@deepseek-ai/dsh-session-format-catalog": "0.2.1-rc.2",
85
+ "@deepseek-ai/dsh-session-persistence": "0.2.1-rc.2",
86
+ "@deepseek-ai/dsh-session-projection": "0.2.1-rc.2",
87
+ "@deepseek-ai/dsh-session-projection-cache": "0.2.1-rc.2",
88
+ "@deepseek-ai/dsh-spill": "0.2.1-rc.2",
89
+ "@deepseek-ai/dsh-spill-local": "0.2.1-rc.2",
90
+ "@deepseek-ai/dsh-storage": "0.2.1-rc.2",
91
+ "@deepseek-ai/dsh-storage-domain": "0.2.1-rc.2",
92
+ "@deepseek-ai/dsh-typert-protocol": "0.2.1-rc.2",
93
+ "@deepseek-ai/dsh-typert-registry": "0.2.1-rc.2",
94
+ "@deepseek-ai/dsh-workspace": "0.2.1-rc.2",
98
95
  "@types/node": "^22.20.4",
99
96
  "@types/react": "^18.3.31",
100
97
  "@types/react-dom": "^18.0.0",
101
- "antd": "6.6.5",
102
- "dayjs": "1.11.23",
103
98
  "esbuild": "0.25.0",
104
99
  "immer": "10.1.1",
105
100
  "react": "18.3.1",