@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.
- package/CHANGELOG.md +16 -1
- package/CHANGELOG.zh-CN.md +16 -1
- package/README.md +3 -3
- package/README.ru.md +3 -3
- package/README.zh-CN.md +3 -3
- package/cordis.patch.yml +1 -1
- package/lib/archive-discovery.js +16 -2
- package/lib/archive-experience.js +4 -6
- package/lib/client.js +5 -1063
- package/lib/plugin-updater.js +17 -3
- package/lib/projcache.js +0 -14
- package/lib/release-age.js +164 -0
- package/lib/workspace.js +58 -27
- package/package.json +44 -49
package/lib/plugin-updater.js
CHANGED
|
@@ -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)
|
|
315
|
-
|
|
316
|
-
|
|
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
|
|
299
|
-
return cache.cachedSnapshot
|
|
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.
|
|
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 (
|
|
632
|
+
if (listed.has(sessionId)) {
|
|
619
633
|
kept.push(sessionId);
|
|
620
634
|
continue;
|
|
621
635
|
}
|
|
622
|
-
if (
|
|
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.
|
|
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
|
|
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
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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:
|
|
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
|
-
/**
|
|
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(
|
|
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"
|
|
1108
|
-
|
|
1109
|
-
|
|
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
|
|
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 &&
|
|
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
|
|
47
|
-
"@deepseek-ai/dsh-client-connection": "0.
|
|
48
|
-
"@deepseek-ai/dsh-client-locale": "0.
|
|
49
|
-
"@deepseek-ai/dsh-client-store": "0.
|
|
50
|
-
"@deepseek-ai/dsh-client-ui-conversation": "0.
|
|
51
|
-
"@deepseek-ai/dsh-client-ui-primitives": "0.
|
|
52
|
-
"@deepseek-ai/dsh-client-ui-sidebar": "0.
|
|
53
|
-
"@deepseek-ai/dsh-client-ui-workspace": "0.
|
|
54
|
-
"@deepseek-ai/dsh-host-webserver": "0.
|
|
55
|
-
"@deepseek-ai/dsh-session": "0.
|
|
56
|
-
"@deepseek-ai/dsh-session-format-catalog": "0.
|
|
57
|
-
"@deepseek-ai/dsh-session-projection-cache": "0.
|
|
58
|
-
"@deepseek-ai/dsh-spill-local": "0.
|
|
59
|
-
"@deepseek-ai/dsh-storage-domain": "0.
|
|
60
|
-
"@deepseek-ai/dsh-typert-protocol": "0.
|
|
61
|
-
"@deepseek-ai/dsh-typert-registry": "0.
|
|
62
|
-
"@deepseek-ai/dsh-workspace": "0.
|
|
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
|
|
74
|
-
"@deepseek-ai/dsh-api-gateway": "0.2.1-
|
|
75
|
-
"@deepseek-ai/dsh-client-connection": "0.2.1-
|
|
76
|
-
"@deepseek-ai/dsh-client-locale": "0.2.1-
|
|
77
|
-
"@deepseek-ai/dsh-client-store": "0.2.1-
|
|
78
|
-
"@deepseek-ai/dsh-client-ui-conversation": "0.2.1-
|
|
79
|
-
"@deepseek-ai/dsh-client-ui-primitives": "0.2.1-
|
|
80
|
-
"@deepseek-ai/dsh-client-ui-renderer": "0.2.1-
|
|
81
|
-
"@deepseek-ai/dsh-client-ui-sidebar": "0.2.1-
|
|
82
|
-
"@deepseek-ai/dsh-client-ui-slots": "0.2.1-
|
|
83
|
-
"@deepseek-ai/dsh-client-ui-workspace": "0.2.1-
|
|
84
|
-
"@deepseek-ai/dsh-host-webserver": "0.2.1-
|
|
85
|
-
"@deepseek-ai/dsh-scope": "0.2.1-
|
|
86
|
-
"@deepseek-ai/dsh-session": "0.2.1-
|
|
87
|
-
"@deepseek-ai/dsh-session-format-catalog": "0.2.1-
|
|
88
|
-
"@deepseek-ai/dsh-session-persistence": "0.2.1-
|
|
89
|
-
"@deepseek-ai/dsh-session-projection": "0.2.1-
|
|
90
|
-
"@deepseek-ai/dsh-session-projection-cache": "0.2.1-
|
|
91
|
-
"@deepseek-ai/dsh-spill": "0.2.1-
|
|
92
|
-
"@deepseek-ai/dsh-spill-local": "0.2.1-
|
|
93
|
-
"@deepseek-ai/dsh-storage": "0.2.1-
|
|
94
|
-
"@deepseek-ai/dsh-storage-domain": "0.2.1-
|
|
95
|
-
"@deepseek-ai/dsh-typert-protocol": "0.2.1-
|
|
96
|
-
"@deepseek-ai/dsh-typert-registry": "0.2.1-
|
|
97
|
-
"@deepseek-ai/dsh-workspace": "0.2.1-
|
|
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",
|