dsh-sessions-manager 3.4.2 → 3.5.1
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/README.en.md +6 -2
- package/README.md +6 -2
- package/lib/client.js +78 -21
- package/lib/client.js.map +2 -2
- package/lib/index.js +272 -85
- package/lib/index.js.map +4 -4
- package/package.json +1 -1
- package/src/client/index.jsx +58 -14
- package/src/client/logic.js +8 -0
- package/src/compat/capabilities.js +34 -0
- package/src/compat/persistence.js +72 -0
- package/src/index.js +113 -60
- package/src/zstd-frame.js +68 -34
package/lib/index.js
CHANGED
|
@@ -1,32 +1,67 @@
|
|
|
1
1
|
// src/index.js
|
|
2
|
-
import { mkdir as mkdir4, realpath, rename as rename4, stat, unlink, writeFile as writeFile4 } from "node:fs/promises";
|
|
2
|
+
import { mkdir as mkdir4, readFile as readFile2, realpath, rename as rename4, stat, unlink, writeFile as writeFile4 } from "node:fs/promises";
|
|
3
3
|
import { basename, dirname as dirname2, isAbsolute, join as join4 } from "node:path";
|
|
4
|
-
import { readFileSync as
|
|
4
|
+
import { readFileSync as readFileSync3 } from "node:fs";
|
|
5
5
|
import { homedir as homedir3 } from "node:os";
|
|
6
6
|
|
|
7
7
|
// src/zstd-frame.js
|
|
8
8
|
import zlib from "node:zlib";
|
|
9
|
-
import { readFileSync, writeFileSync } from "node:fs";
|
|
10
9
|
var ZSTD_MAGIC = 4247762216;
|
|
11
10
|
var CHECKSUM_OPTS = { params: { [zlib.constants.ZSTD_c_checksumFlag]: 1 } };
|
|
12
|
-
function
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
11
|
+
function scanZstdFrames(buf, maxFrames = Number.POSITIVE_INFINITY) {
|
|
12
|
+
const frames = [];
|
|
13
|
+
let offset = 0;
|
|
14
|
+
while (offset < buf.length) {
|
|
15
|
+
const start = offset;
|
|
16
|
+
if (buf.length - offset < 4) return { frames, tornStart: start };
|
|
17
|
+
if (buf.readUInt32LE(offset) !== ZSTD_MAGIC) {
|
|
18
|
+
throw new Error(`\u4F1A\u8BDD\u65E5\u5FD7\u683C\u5F0F\u5F02\u5E38\uFF08\u5B57\u8282 ${offset} \u7684 zstd magic \u65E0\u6548\uFF09`);
|
|
19
|
+
}
|
|
20
|
+
offset += 4;
|
|
21
|
+
if (offset === buf.length) return { frames, tornStart: start };
|
|
22
|
+
const descriptor = buf.readUInt8(offset++);
|
|
23
|
+
if ((descriptor & 24) !== 0) throw new Error(`\u4F1A\u8BDD\u65E5\u5FD7\u683C\u5F0F\u5F02\u5E38\uFF08\u5B57\u8282 ${offset - 1} \u4F7F\u7528\u4FDD\u7559\u5E27\u5934\u4F4D\uFF09`);
|
|
24
|
+
const contentSizeFlag = descriptor >>> 6;
|
|
25
|
+
const singleSegment = (descriptor & 32) !== 0;
|
|
26
|
+
const checksum = (descriptor & 4) !== 0;
|
|
27
|
+
const dictionaryFlag = descriptor & 3;
|
|
28
|
+
const dictionaryBytes = dictionaryFlag === 3 ? 4 : dictionaryFlag;
|
|
29
|
+
const contentSizeBytes = contentSizeFlag === 0 ? singleSegment ? 1 : 0 : 1 << contentSizeFlag;
|
|
30
|
+
const remainingHeaderBytes = (singleSegment ? 0 : 1) + dictionaryBytes + contentSizeBytes;
|
|
31
|
+
if (buf.length - offset < remainingHeaderBytes) return { frames, tornStart: start };
|
|
32
|
+
offset += remainingHeaderBytes;
|
|
33
|
+
for (; ; ) {
|
|
34
|
+
if (buf.length - offset < 3) return { frames, tornStart: start };
|
|
35
|
+
const blockHeader = buf.readUIntLE(offset, 3);
|
|
36
|
+
offset += 3;
|
|
37
|
+
const lastBlock = (blockHeader & 1) !== 0;
|
|
38
|
+
const blockType = blockHeader >>> 1 & 3;
|
|
39
|
+
const blockSize = blockHeader >>> 3;
|
|
40
|
+
if (blockType === 3) throw new Error(`\u4F1A\u8BDD\u65E5\u5FD7\u683C\u5F0F\u5F02\u5E38\uFF08\u5B57\u8282 ${offset - 3} \u4F7F\u7528\u4FDD\u7559\u5757\u7C7B\u578B\uFF09`);
|
|
41
|
+
const payloadBytes = blockType === 1 ? 1 : blockSize;
|
|
42
|
+
if (buf.length - offset < payloadBytes) return { frames, tornStart: start };
|
|
43
|
+
offset += payloadBytes;
|
|
44
|
+
if (lastBlock) break;
|
|
45
|
+
}
|
|
46
|
+
if (checksum) {
|
|
47
|
+
if (buf.length - offset < 4) return { frames, tornStart: start };
|
|
48
|
+
offset += 4;
|
|
20
49
|
}
|
|
50
|
+
frames.push({ start, end: offset });
|
|
51
|
+
if (frames.length === maxFrames) return { frames };
|
|
21
52
|
}
|
|
22
|
-
return
|
|
53
|
+
return { frames };
|
|
23
54
|
}
|
|
24
|
-
function
|
|
25
|
-
const
|
|
26
|
-
const
|
|
27
|
-
if (
|
|
28
|
-
|
|
29
|
-
|
|
55
|
+
function firstFrame(buf) {
|
|
56
|
+
const scan = scanZstdFrames(buf, 1);
|
|
57
|
+
const frame = scan.frames[0];
|
|
58
|
+
if (!frame) throw new Error("\u4F1A\u8BDD\u65E5\u5FD7\u683C\u5F0F\u5F02\u5E38\uFF08\u65E0\u5B8C\u6574 zstd \u5E27\uFF09");
|
|
59
|
+
return frame;
|
|
60
|
+
}
|
|
61
|
+
function rewriteFrame0CwdInMemory(buf, newCwd) {
|
|
62
|
+
const frame = firstFrame(buf);
|
|
63
|
+
const end0 = frame.end;
|
|
64
|
+
const frame0 = buf.subarray(frame.start, end0);
|
|
30
65
|
const text = zlib.zstdDecompressSync(frame0).toString("utf8");
|
|
31
66
|
const nl = text.indexOf("\n");
|
|
32
67
|
const line = nl >= 0 ? text.slice(0, nl) : text;
|
|
@@ -34,11 +69,10 @@ function rewriteFrame0Cwd(filePath, newCwd) {
|
|
|
34
69
|
if (obj.type !== "session") {
|
|
35
70
|
throw new Error(`\u4F1A\u8BDD\u65E5\u5FD7\u683C\u5F0F\u5F02\u5E38\uFF08\u5E270 \u4E0D\u662F session header\uFF0C\u5B9E\u9645 type=${obj.type}\uFF09`);
|
|
36
71
|
}
|
|
37
|
-
if (obj.cwd === newCwd) return;
|
|
38
72
|
obj.cwd = newCwd;
|
|
39
73
|
const newFrame0 = zlib.zstdCompressSync(JSON.stringify(obj) + "\n", CHECKSUM_OPTS);
|
|
40
74
|
const rest = buf.subarray(end0);
|
|
41
|
-
|
|
75
|
+
return Buffer.concat([newFrame0, rest]);
|
|
42
76
|
}
|
|
43
77
|
|
|
44
78
|
// src/markdown.js
|
|
@@ -182,7 +216,7 @@ function renderSessionMarkdown(meta, events, options = {}) {
|
|
|
182
216
|
|
|
183
217
|
// src/star-index.js
|
|
184
218
|
import { mkdir, rename, writeFile } from "node:fs/promises";
|
|
185
|
-
import { readFileSync
|
|
219
|
+
import { readFileSync } from "node:fs";
|
|
186
220
|
import { homedir } from "node:os";
|
|
187
221
|
import { join } from "node:path";
|
|
188
222
|
var STAR_SCHEMA_VERSION = 3;
|
|
@@ -210,7 +244,7 @@ function createStarIndex(options = {}) {
|
|
|
210
244
|
let mutation = Promise.resolve();
|
|
211
245
|
async function read() {
|
|
212
246
|
try {
|
|
213
|
-
return normalizeStarStore(JSON.parse(
|
|
247
|
+
return normalizeStarStore(JSON.parse(readFileSync(indexPath, "utf8")));
|
|
214
248
|
} catch {
|
|
215
249
|
return normalizeStarStore(null);
|
|
216
250
|
}
|
|
@@ -303,7 +337,7 @@ function aggregateStorage(items, options = {}) {
|
|
|
303
337
|
|
|
304
338
|
// src/auto-archive.js
|
|
305
339
|
import { mkdir as mkdir2, rename as rename2, writeFile as writeFile2 } from "node:fs/promises";
|
|
306
|
-
import { readFileSync as
|
|
340
|
+
import { readFileSync as readFileSync2 } from "node:fs";
|
|
307
341
|
import { homedir as homedir2 } from "node:os";
|
|
308
342
|
import { join as join2 } from "node:path";
|
|
309
343
|
var AUTO_ARCHIVE_SCHEMA_VERSION = 4;
|
|
@@ -359,7 +393,7 @@ function createAutoArchiveStore(options = {}) {
|
|
|
359
393
|
let mutation = Promise.resolve();
|
|
360
394
|
async function read() {
|
|
361
395
|
try {
|
|
362
|
-
return normalizeAutoArchiveStore(JSON.parse(
|
|
396
|
+
return normalizeAutoArchiveStore(JSON.parse(readFileSync2(indexPath, "utf8")));
|
|
363
397
|
} catch {
|
|
364
398
|
return normalizeAutoArchiveStore(null);
|
|
365
399
|
}
|
|
@@ -604,6 +638,101 @@ function createTitleIndexStore({ dir, file }) {
|
|
|
604
638
|
};
|
|
605
639
|
}
|
|
606
640
|
|
|
641
|
+
// src/compat/persistence.js
|
|
642
|
+
function asHeader(value) {
|
|
643
|
+
if (!value || typeof value !== "object") return null;
|
|
644
|
+
const candidate = value.header && typeof value.header === "object" ? value.header : value;
|
|
645
|
+
return candidate.id == null ? null : candidate;
|
|
646
|
+
}
|
|
647
|
+
function normalizePersistenceEntry(value) {
|
|
648
|
+
const header = asHeader(value);
|
|
649
|
+
if (!header) return null;
|
|
650
|
+
const snapshot = value && value.header === header ? value : null;
|
|
651
|
+
return {
|
|
652
|
+
header,
|
|
653
|
+
snapshot,
|
|
654
|
+
id: String(header.id),
|
|
655
|
+
sizeBytes: snapshot && Number.isFinite(snapshot.sizeBytes) ? Number(snapshot.sizeBytes) : null,
|
|
656
|
+
eventCount: snapshot && Number.isSafeInteger(snapshot.eventCount) ? snapshot.eventCount : null,
|
|
657
|
+
revision: snapshot ? snapshot.revision : null
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
function normalizePersistenceList(values) {
|
|
661
|
+
if (!Array.isArray(values)) return [];
|
|
662
|
+
return values.map(normalizePersistenceEntry).filter(Boolean);
|
|
663
|
+
}
|
|
664
|
+
function createPersistenceAdapter(service) {
|
|
665
|
+
if (!service || typeof service.list !== "function") throw new TypeError("sessionPersistence.list is required");
|
|
666
|
+
async function listEntries(options) {
|
|
667
|
+
return normalizePersistenceList(await service.list(options));
|
|
668
|
+
}
|
|
669
|
+
async function readSession(id, offset = 0) {
|
|
670
|
+
if (typeof service.readFrom === "function") {
|
|
671
|
+
const result = await service.readFrom(id, offset);
|
|
672
|
+
return {
|
|
673
|
+
meta: result && result.meta ? result.meta : null,
|
|
674
|
+
inheritedEventCount: result && Number.isSafeInteger(result.inheritedEventCount) ? result.inheritedEventCount : 0,
|
|
675
|
+
events: result && Array.isArray(result.events) ? result.events : []
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
if (typeof service.open !== "function") throw new Error("\u5F53\u524D DSH \u6301\u4E45\u5316\u670D\u52A1\u4E0D\u652F\u6301\u8BFB\u53D6\u4F1A\u8BDD");
|
|
679
|
+
const handle = await service.open(id, "read");
|
|
680
|
+
if (!handle || typeof handle.read !== "function" || typeof handle.close !== "function") {
|
|
681
|
+
try {
|
|
682
|
+
if (handle && typeof handle.close === "function") await handle.close();
|
|
683
|
+
} catch {
|
|
684
|
+
}
|
|
685
|
+
throw new Error("DSH \u8FD4\u56DE\u4E86\u65E0\u6548\u7684 SessionHandle");
|
|
686
|
+
}
|
|
687
|
+
try {
|
|
688
|
+
const events = await handle.read(offset);
|
|
689
|
+
return {
|
|
690
|
+
meta: handle.header || handle.meta || null,
|
|
691
|
+
inheritedEventCount: Number.isSafeInteger(handle.inheritedEventCount) ? handle.inheritedEventCount : 0,
|
|
692
|
+
events: Array.isArray(events) ? events : [...events || []]
|
|
693
|
+
};
|
|
694
|
+
} finally {
|
|
695
|
+
await handle.close();
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
function locate(header) {
|
|
699
|
+
if (typeof service.locate === "function") return service.locate(header);
|
|
700
|
+
return null;
|
|
701
|
+
}
|
|
702
|
+
const kind = typeof service.open === "function" ? "session-handle" : "legacy";
|
|
703
|
+
return { kind, listEntries, readSession, locate };
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// src/compat/capabilities.js
|
|
707
|
+
function action(available, reason = null) {
|
|
708
|
+
return { available: !!available, reason: available ? null : reason };
|
|
709
|
+
}
|
|
710
|
+
function detectCapabilities({ persistence, workspaceRegistry }) {
|
|
711
|
+
const handleApi = !!(persistence && typeof persistence.open === "function");
|
|
712
|
+
const legacyRead = !!(persistence && typeof persistence.readFrom === "function");
|
|
713
|
+
const legacyLocate = !!(persistence && (typeof persistence.locate === "function" || persistence.backend && typeof persistence.backend.locate === "function"));
|
|
714
|
+
const workspaceInternals = !!(workspaceRegistry && workspaceRegistry.headers && workspaceRegistry.sessionPaths && typeof workspaceRegistry.replaceHeaderIndex === "function");
|
|
715
|
+
return {
|
|
716
|
+
persistence: handleApi ? "session-handle" : "legacy",
|
|
717
|
+
actions: {
|
|
718
|
+
read: action(legacyRead || handleApi, "\u5F53\u524D DSH \u672A\u63D0\u4F9B\u53EF\u8BC6\u522B\u7684\u4F1A\u8BDD\u8BFB\u53D6\u63A5\u53E3"),
|
|
719
|
+
archive: action(!!(workspaceRegistry && typeof workspaceRegistry.archiveSession === "function"), "\u5F53\u524D DSH \u672A\u63D0\u4F9B\u5F52\u6863\u63A5\u53E3"),
|
|
720
|
+
trash: action(legacyRead || handleApi, "\u5F53\u524D DSH \u65E0\u6CD5\u8BFB\u53D6\u4F1A\u8BDD\uFF0C\u4E0D\u80FD\u5B89\u5168\u79FB\u5165\u56DE\u6536\u7AD9"),
|
|
721
|
+
restoreTrash: action(true),
|
|
722
|
+
purge: action(!handleApi && legacyLocate, "\u5F53\u524D DSH \u7248\u672C\u5C1A\u672A\u63D0\u4F9B\u7ECF\u8FC7\u9A8C\u8BC1\u7684\u5B89\u5168\u6C38\u4E45\u5220\u9664\u80FD\u529B"),
|
|
723
|
+
move: action(!handleApi && legacyRead && legacyLocate && workspaceInternals, "\u5F53\u524D DSH \u7248\u672C\u5C1A\u672A\u63D0\u4F9B\u7ECF\u8FC7\u9A8C\u8BC1\u7684\u8DE8\u5DE5\u4F5C\u533A\u8FC1\u79FB\u80FD\u529B")
|
|
724
|
+
}
|
|
725
|
+
};
|
|
726
|
+
}
|
|
727
|
+
function requireCapability(capabilities, name2) {
|
|
728
|
+
const value = capabilities && capabilities.actions && capabilities.actions[name2];
|
|
729
|
+
if (value && value.available) return;
|
|
730
|
+
const error = new Error(value && value.reason || `\u5F53\u524D\u73AF\u5883\u4E0D\u652F\u6301 ${name2}`);
|
|
731
|
+
error.status = 409;
|
|
732
|
+
error.code = "DSM_CAPABILITY_UNAVAILABLE";
|
|
733
|
+
throw error;
|
|
734
|
+
}
|
|
735
|
+
|
|
607
736
|
// src/index.js
|
|
608
737
|
var name = "dsh-sessions-manager";
|
|
609
738
|
var inject = ["webServer", "workspaceRegistry", "sessionPersistence", "sessionQuery", "storageDomain"];
|
|
@@ -690,6 +819,8 @@ function foldTitle(events) {
|
|
|
690
819
|
function apply(ctx) {
|
|
691
820
|
const w = ctx.workspaceRegistry;
|
|
692
821
|
const sp = ctx.sessionPersistence;
|
|
822
|
+
const persistence = createPersistenceAdapter(sp);
|
|
823
|
+
const capabilities = detectCapabilities({ persistence: sp, workspaceRegistry: w });
|
|
693
824
|
const sq = ctx.sessionQuery;
|
|
694
825
|
const dom = () => ctx.storageDomain.get("workspace");
|
|
695
826
|
const authorityTitleCache = /* @__PURE__ */ new Map();
|
|
@@ -814,7 +945,7 @@ function apply(ctx) {
|
|
|
814
945
|
}
|
|
815
946
|
if (!meta.title || !meta.cwd) {
|
|
816
947
|
try {
|
|
817
|
-
const r = await
|
|
948
|
+
const r = await persistence.readSession(id, 0);
|
|
818
949
|
if (r.meta) {
|
|
819
950
|
if (!meta.cwd) meta.cwd = r.meta.cwd || null;
|
|
820
951
|
if (!meta.createdAt) meta.createdAt = r.meta.createdAt || null;
|
|
@@ -830,23 +961,25 @@ function apply(ctx) {
|
|
|
830
961
|
async function collectUsage(preloadedHeaders) {
|
|
831
962
|
const sizeById = /* @__PURE__ */ new Map();
|
|
832
963
|
const mtimeById = /* @__PURE__ */ new Map();
|
|
833
|
-
let
|
|
834
|
-
if (Array.isArray(preloadedHeaders))
|
|
964
|
+
let entries = null;
|
|
965
|
+
if (Array.isArray(preloadedHeaders)) entries = preloadedHeaders;
|
|
835
966
|
else {
|
|
836
967
|
try {
|
|
837
|
-
|
|
968
|
+
entries = await persistence.listEntries();
|
|
838
969
|
} catch (e) {
|
|
839
|
-
|
|
970
|
+
entries = [];
|
|
840
971
|
}
|
|
841
972
|
}
|
|
842
|
-
if (!Array.isArray(
|
|
973
|
+
if (!Array.isArray(entries)) entries = [];
|
|
843
974
|
const CHUNK = 8;
|
|
844
|
-
for (let i = 0; i <
|
|
845
|
-
await Promise.all(
|
|
846
|
-
const
|
|
975
|
+
for (let i = 0; i < entries.length; i += CHUNK) {
|
|
976
|
+
await Promise.all(entries.slice(i, i + CHUNK).map(async (entry) => {
|
|
977
|
+
const header = entry && entry.header ? entry.header : entry;
|
|
978
|
+
const id = entry && entry.id != null ? String(entry.id) : header && header.id != null ? String(header.id) : null;
|
|
847
979
|
if (!id) return;
|
|
980
|
+
if (entry && Number.isFinite(entry.sizeBytes)) sizeById.set(id, Number(entry.sizeBytes));
|
|
848
981
|
try {
|
|
849
|
-
const loc =
|
|
982
|
+
const loc = persistence.locate(header);
|
|
850
983
|
if (!loc || typeof loc.path !== "string" || !loc.path) return;
|
|
851
984
|
const st = await stat(loc.path);
|
|
852
985
|
if (!st) return;
|
|
@@ -876,7 +1009,7 @@ function apply(ctx) {
|
|
|
876
1009
|
}
|
|
877
1010
|
async function readTrashStore() {
|
|
878
1011
|
try {
|
|
879
|
-
return normalizeTrashStore(JSON.parse(
|
|
1012
|
+
return normalizeTrashStore(JSON.parse(readFileSync3(TRASH_INDEX, "utf8")));
|
|
880
1013
|
} catch (e) {
|
|
881
1014
|
return normalizeTrashStore(null);
|
|
882
1015
|
}
|
|
@@ -918,17 +1051,20 @@ function apply(ctx) {
|
|
|
918
1051
|
let cwd = null;
|
|
919
1052
|
let title = null;
|
|
920
1053
|
let removedPath = null;
|
|
1054
|
+
let persistenceEntry = null;
|
|
921
1055
|
try {
|
|
922
|
-
const
|
|
923
|
-
|
|
1056
|
+
const entries = await persistence.listEntries();
|
|
1057
|
+
const found = entries.find((entry) => entry.id === sid) || null;
|
|
1058
|
+
persistenceEntry = found;
|
|
1059
|
+
header = found ? found.header : null;
|
|
924
1060
|
if (header) {
|
|
925
|
-
const loc =
|
|
1061
|
+
const loc = persistence.locate(header);
|
|
926
1062
|
if (loc && typeof loc.path === "string") removedPath = loc.path;
|
|
927
1063
|
cwd = header.cwd || null;
|
|
928
1064
|
title = header.title || header.meta && header.meta.title || null;
|
|
929
1065
|
}
|
|
930
1066
|
if (!title) {
|
|
931
|
-
const r = await
|
|
1067
|
+
const r = await persistence.readSession(sid, 0);
|
|
932
1068
|
if (r && r.meta) {
|
|
933
1069
|
if (!cwd) cwd = r.meta.cwd;
|
|
934
1070
|
title = foldTitle(r.events);
|
|
@@ -949,7 +1085,7 @@ function apply(ctx) {
|
|
|
949
1085
|
cwd: cwd || null,
|
|
950
1086
|
header: header || null,
|
|
951
1087
|
originalPath: removedPath || null,
|
|
952
|
-
sizeBytes:
|
|
1088
|
+
sizeBytes: persistenceEntry && Number.isFinite(persistenceEntry.sizeBytes) ? persistenceEntry.sizeBytes : null,
|
|
953
1089
|
wasArchived: archived,
|
|
954
1090
|
deletedAt: Date.now()
|
|
955
1091
|
};
|
|
@@ -977,6 +1113,7 @@ function apply(ctx) {
|
|
|
977
1113
|
}
|
|
978
1114
|
async function purgeFromTrash(sid) {
|
|
979
1115
|
requireSessionId(sid);
|
|
1116
|
+
requireCapability(capabilities, "purge");
|
|
980
1117
|
let purged = false;
|
|
981
1118
|
await mutateTrash(async (store) => {
|
|
982
1119
|
const entry = store.items.find((t) => String(t.sessionId) === sid);
|
|
@@ -987,13 +1124,18 @@ function apply(ctx) {
|
|
|
987
1124
|
}
|
|
988
1125
|
let target = null;
|
|
989
1126
|
try {
|
|
990
|
-
const
|
|
991
|
-
const current =
|
|
992
|
-
const located = current &&
|
|
1127
|
+
const entries = await persistence.listEntries();
|
|
1128
|
+
const current = entries.find((entry2) => entry2.id === sid);
|
|
1129
|
+
const located = current && persistence.locate(current.header);
|
|
993
1130
|
if (located && typeof located.path === "string") target = located.path;
|
|
994
1131
|
} catch (e) {
|
|
995
1132
|
}
|
|
996
1133
|
if (!target && typeof entry.originalPath === "string") target = entry.originalPath;
|
|
1134
|
+
if (!target) {
|
|
1135
|
+
const error = new Error("\u65E0\u6CD5\u786E\u8BA4\u8BE5\u4F1A\u8BDD\u7684\u7269\u7406\u65E5\u5FD7\u4F4D\u7F6E\uFF0C\u5DF2\u505C\u6B62\u6C38\u4E45\u5220\u9664");
|
|
1136
|
+
error.status = 409;
|
|
1137
|
+
throw error;
|
|
1138
|
+
}
|
|
997
1139
|
const targetOwnsSession = target && (basename(dirname2(target)) === sid || basename(target).includes(sid));
|
|
998
1140
|
if (target && !targetOwnsSession) {
|
|
999
1141
|
const error = new Error("\u65E5\u5FD7\u8DEF\u5F84\u4E0E\u4F1A\u8BDD ID \u4E0D\u5339\u914D\uFF0C\u5DF2\u505C\u6B62\u6C38\u4E45\u5220\u9664");
|
|
@@ -1069,6 +1211,7 @@ function apply(ctx) {
|
|
|
1069
1211
|
return (await readTrashStore()).settings;
|
|
1070
1212
|
}
|
|
1071
1213
|
async function cleanupExpiredTrash() {
|
|
1214
|
+
if (!capabilities.actions.purge.available) return 0;
|
|
1072
1215
|
const store = await readTrashStore();
|
|
1073
1216
|
const days = store.settings.retentionDays;
|
|
1074
1217
|
if (!days) return 0;
|
|
@@ -1102,11 +1245,12 @@ function apply(ctx) {
|
|
|
1102
1245
|
return { canonical, entity: await w.create(canonical, basename(canonical) || "workspace") };
|
|
1103
1246
|
}
|
|
1104
1247
|
async function moveOne(sid, targetPath) {
|
|
1248
|
+
requireCapability(capabilities, "move");
|
|
1105
1249
|
const activeId = getActiveSessionId(ctx);
|
|
1106
1250
|
if (activeId != null && String(activeId) === String(sid)) {
|
|
1107
1251
|
throw new Error("\u8BE5\u4F1A\u8BDD\u5F53\u524D\u5904\u4E8E\u6253\u5F00\u72B6\u6001\uFF0C\u8BF7\u5148\u5207\u6362\u5230\u522B\u7684\u4F1A\u8BDD\u518D\u79FB\u52A8\u3002");
|
|
1108
1252
|
}
|
|
1109
|
-
const r = await
|
|
1253
|
+
const r = await persistence.readSession(sid, 0);
|
|
1110
1254
|
if (!r || !r.meta) throw new Error("\u65E0\u6CD5\u8BFB\u53D6\u8BE5\u4F1A\u8BDD\u7684\u65E5\u5FD7");
|
|
1111
1255
|
const meta = r.meta;
|
|
1112
1256
|
const events = r.events;
|
|
@@ -1133,12 +1277,41 @@ function apply(ctx) {
|
|
|
1133
1277
|
const newPath = locatePath(newHeaderObj);
|
|
1134
1278
|
if (!oldPath || !newPath || oldPath === newPath) return false;
|
|
1135
1279
|
const backupPath = `${oldPath}.move-backup-${Date.now()}`;
|
|
1280
|
+
const stagedPath = `${newPath}.move-stage-${process.pid}-${Date.now()}`;
|
|
1281
|
+
let destinationInstalled = false;
|
|
1136
1282
|
try {
|
|
1137
1283
|
await mkdir4(dirname2(newPath), { recursive: true });
|
|
1284
|
+
try {
|
|
1285
|
+
await stat(newPath);
|
|
1286
|
+
throw new Error("\u79FB\u52A8\u5931\u8D25\uFF1A\u76EE\u6807\u4F4D\u7F6E\u5DF2\u5B58\u5728\u540C\u540D\u4F1A\u8BDD\u65E5\u5FD7");
|
|
1287
|
+
} catch (e) {
|
|
1288
|
+
if (e && e.code !== "ENOENT") throw e;
|
|
1289
|
+
}
|
|
1138
1290
|
await rename4(oldPath, backupPath);
|
|
1139
|
-
await
|
|
1140
|
-
|
|
1291
|
+
const original = await readFile2(backupPath);
|
|
1292
|
+
const originalFrames = scanZstdFrames(original).frames;
|
|
1293
|
+
if (originalFrames.length === 0) throw new Error("\u79FB\u52A8\u524D\u6821\u9A8C\u5931\u8D25\uFF1A\u4F1A\u8BDD\u65E5\u5FD7\u6CA1\u6709\u5B8C\u6574 zstd \u5E27");
|
|
1294
|
+
const rewritten = rewriteFrame0CwdInMemory(original, canonical);
|
|
1295
|
+
const rewrittenFrames = scanZstdFrames(rewritten).frames;
|
|
1296
|
+
if (rewrittenFrames.length !== originalFrames.length) throw new Error("\u79FB\u52A8\u540E\u6821\u9A8C\u5931\u8D25\uFF1A\u4F1A\u8BDD\u65E5\u5FD7\u5E27\u6570\u53D1\u751F\u53D8\u5316");
|
|
1297
|
+
const originalTail = original.subarray(originalFrames[0].end);
|
|
1298
|
+
const rewrittenTail = rewritten.subarray(rewrittenFrames[0].end);
|
|
1299
|
+
if (!originalTail.equals(rewrittenTail)) throw new Error("\u79FB\u52A8\u540E\u6821\u9A8C\u5931\u8D25\uFF1A\u4F1A\u8BDD\u4E8B\u4EF6\u5185\u5BB9\u53D1\u751F\u53D8\u5316");
|
|
1300
|
+
await writeFile4(stagedPath, rewritten, { mode: 384 });
|
|
1301
|
+
await rename4(stagedPath, newPath);
|
|
1302
|
+
destinationInstalled = true;
|
|
1303
|
+
await unlink(backupPath);
|
|
1141
1304
|
} catch (e) {
|
|
1305
|
+
try {
|
|
1306
|
+
await unlink(stagedPath);
|
|
1307
|
+
} catch (_) {
|
|
1308
|
+
}
|
|
1309
|
+
if (destinationInstalled) {
|
|
1310
|
+
try {
|
|
1311
|
+
await unlink(newPath);
|
|
1312
|
+
} catch (_) {
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1142
1315
|
try {
|
|
1143
1316
|
await rename4(backupPath, oldPath);
|
|
1144
1317
|
} catch (_) {
|
|
@@ -1165,7 +1338,7 @@ function apply(ctx) {
|
|
|
1165
1338
|
return null;
|
|
1166
1339
|
};
|
|
1167
1340
|
if (isOpen) {
|
|
1168
|
-
await relocateLog(meta, newHeader);
|
|
1341
|
+
if (!await relocateLog(meta, newHeader)) throw new Error("\u79FB\u52A8\u5931\u8D25\uFF1A\u65E0\u6CD5\u786E\u8BA4\u4F1A\u8BDD\u65E5\u5FD7\u5DF2\u8FC1\u79FB\u5230\u76EE\u6807\u5DE5\u4F5C\u533A");
|
|
1169
1342
|
try {
|
|
1170
1343
|
const st = sp.states && sp.states.get && sp.states.get(sid);
|
|
1171
1344
|
if (st && st.meta) st.meta = Object.assign({}, st.meta, { cwd: canonical });
|
|
@@ -1181,7 +1354,7 @@ function apply(ctx) {
|
|
|
1181
1354
|
oldPath = null;
|
|
1182
1355
|
}
|
|
1183
1356
|
if (typeof sp.create !== "function" || typeof sp.append !== "function") {
|
|
1184
|
-
await relocateLog(meta, newHeader);
|
|
1357
|
+
if (!await relocateLog(meta, newHeader)) throw new Error("\u79FB\u52A8\u5931\u8D25\uFF1A\u65E0\u6CD5\u786E\u8BA4\u4F1A\u8BDD\u65E5\u5FD7\u5DF2\u8FC1\u79FB\u5230\u76EE\u6807\u5DE5\u4F5C\u533A");
|
|
1185
1358
|
} else {
|
|
1186
1359
|
const backupPath = oldPath ? `${oldPath}.move-backup-${Date.now()}` : null;
|
|
1187
1360
|
if (backupPath) {
|
|
@@ -1202,7 +1375,7 @@ function apply(ctx) {
|
|
|
1202
1375
|
try {
|
|
1203
1376
|
await sp.create(newHeader);
|
|
1204
1377
|
await sp.append(sid, events);
|
|
1205
|
-
const check = await
|
|
1378
|
+
const check = await persistence.readSession(sid, 0);
|
|
1206
1379
|
if (!check || !check.meta || check.meta.cwd !== canonical) {
|
|
1207
1380
|
throw new Error("\u79FB\u52A8\u540E\u6821\u9A8C\u5931\u8D25\uFF1A\u4F1A\u8BDD\u5DE5\u4F5C\u76EE\u5F55\u672A\u6B63\u786E\u66F4\u65B0");
|
|
1208
1381
|
}
|
|
@@ -1215,7 +1388,7 @@ function apply(ctx) {
|
|
|
1215
1388
|
} catch (e) {
|
|
1216
1389
|
if (ALREADY_EXISTS_RE.test(String(e && e.message || e))) {
|
|
1217
1390
|
await restore();
|
|
1218
|
-
await relocateLog(meta, newHeader);
|
|
1391
|
+
if (!await relocateLog(meta, newHeader)) throw new Error("\u79FB\u52A8\u5931\u8D25\uFF1A\u65E0\u6CD5\u786E\u8BA4\u4F1A\u8BDD\u65E5\u5FD7\u5DF2\u8FC1\u79FB\u5230\u76EE\u6807\u5DE5\u4F5C\u533A");
|
|
1219
1392
|
} else {
|
|
1220
1393
|
await restore();
|
|
1221
1394
|
throw new Error("\u79FB\u52A8\u4F1A\u8BDD\u65E5\u5FD7\u5931\u8D25\uFF1A" + String(e && e.message || e));
|
|
@@ -1261,14 +1434,14 @@ function apply(ctx) {
|
|
|
1261
1434
|
async function reindexRegistry() {
|
|
1262
1435
|
const reg = w;
|
|
1263
1436
|
if (!reg || typeof reg.replaceHeaderIndex !== "function") return false;
|
|
1264
|
-
let
|
|
1437
|
+
let entries = null;
|
|
1265
1438
|
try {
|
|
1266
|
-
|
|
1439
|
+
entries = await persistence.listEntries();
|
|
1267
1440
|
} catch (e) {
|
|
1268
|
-
|
|
1441
|
+
entries = null;
|
|
1269
1442
|
}
|
|
1270
|
-
if (!
|
|
1271
|
-
await reg.replaceHeaderIndex(
|
|
1443
|
+
if (!entries || !Array.isArray(entries)) return false;
|
|
1444
|
+
await reg.replaceHeaderIndex(entries.map((entry) => entry.header));
|
|
1272
1445
|
if (typeof reg.rebuildEntities === "function") reg.rebuildEntities();
|
|
1273
1446
|
return true;
|
|
1274
1447
|
}
|
|
@@ -1304,17 +1477,17 @@ function apply(ctx) {
|
|
|
1304
1477
|
return out;
|
|
1305
1478
|
}
|
|
1306
1479
|
async function allSessionItems(opts = {}) {
|
|
1307
|
-
let
|
|
1480
|
+
let entries = [];
|
|
1308
1481
|
let headersOk = false;
|
|
1309
1482
|
try {
|
|
1310
|
-
|
|
1311
|
-
headersOk = Array.isArray(
|
|
1312
|
-
if (!headersOk)
|
|
1483
|
+
entries = await persistence.listEntries();
|
|
1484
|
+
headersOk = Array.isArray(entries);
|
|
1485
|
+
if (!headersOk) entries = [];
|
|
1313
1486
|
} catch (e) {
|
|
1314
|
-
|
|
1487
|
+
entries = [];
|
|
1315
1488
|
}
|
|
1316
1489
|
let live = ctx.get("sessions");
|
|
1317
|
-
const ids =
|
|
1490
|
+
const ids = entries.map((entry) => entry.id);
|
|
1318
1491
|
if (live) {
|
|
1319
1492
|
try {
|
|
1320
1493
|
live.list().forEach((s) => {
|
|
@@ -1339,7 +1512,7 @@ function apply(ctx) {
|
|
|
1339
1512
|
}
|
|
1340
1513
|
const currentArchived = new Set((await archivedState().catch(() => ({ archivedSessionIds: [] }))).archivedSessionIds || []);
|
|
1341
1514
|
const items = [];
|
|
1342
|
-
const usage = await collectUsage(
|
|
1515
|
+
const usage = await collectUsage(entries);
|
|
1343
1516
|
const statsById = new Map(visibleIds.map((id) => [id, { mtimeMs: usage.mtimeById.get(id), size: usage.sizeById.get(id) }]));
|
|
1344
1517
|
const { cached, missing } = metaCache.partition(visibleIds, statsById);
|
|
1345
1518
|
const persisted = await hydrateFromPersist(missing, statsById);
|
|
@@ -1405,14 +1578,14 @@ function apply(ctx) {
|
|
|
1405
1578
|
}
|
|
1406
1579
|
async function sidebarAuthority() {
|
|
1407
1580
|
const ids = [];
|
|
1408
|
-
let
|
|
1581
|
+
let entries = [];
|
|
1409
1582
|
try {
|
|
1410
|
-
|
|
1583
|
+
entries = await persistence.listEntries();
|
|
1411
1584
|
} catch (e) {
|
|
1412
|
-
|
|
1585
|
+
entries = [];
|
|
1413
1586
|
}
|
|
1414
|
-
if (!Array.isArray(
|
|
1415
|
-
for (const
|
|
1587
|
+
if (!Array.isArray(entries)) entries = [];
|
|
1588
|
+
for (const entry of entries) ids.push(entry.id);
|
|
1416
1589
|
const sessions = ctx.get("sessions");
|
|
1417
1590
|
try {
|
|
1418
1591
|
if (sessions) sessions.list().forEach((session) => {
|
|
@@ -1423,7 +1596,7 @@ function apply(ctx) {
|
|
|
1423
1596
|
}
|
|
1424
1597
|
const store = await readTrashStore();
|
|
1425
1598
|
if (ids.length) {
|
|
1426
|
-
const usage = await collectUsage(
|
|
1599
|
+
const usage = await collectUsage(entries);
|
|
1427
1600
|
const statsById = new Map(ids.map((id) => [id, { mtimeMs: usage.mtimeById.get(id), size: usage.sizeById.get(id) }]));
|
|
1428
1601
|
const { cached, missing } = metaCache.partition(ids, statsById);
|
|
1429
1602
|
const persisted = await hydrateFromPersist(missing, statsById);
|
|
@@ -1466,14 +1639,14 @@ function apply(ctx) {
|
|
|
1466
1639
|
events = [];
|
|
1467
1640
|
}
|
|
1468
1641
|
} else {
|
|
1469
|
-
const r = await
|
|
1642
|
+
const r = await persistence.readSession(sid, 0);
|
|
1470
1643
|
if (!r || !r.meta) throw new Error("\u627E\u4E0D\u5230\u8BE5\u4F1A\u8BDD\u7684\u8BB0\u5F55\uFF08\u4F1A\u8BDD\u4E0D\u5B58\u5728\uFF09");
|
|
1471
1644
|
meta = r.meta;
|
|
1472
1645
|
events = Array.isArray(r.events) ? r.events : [];
|
|
1473
1646
|
}
|
|
1474
1647
|
let sizeBytes = null;
|
|
1475
1648
|
try {
|
|
1476
|
-
const loc =
|
|
1649
|
+
const loc = persistence.locate(meta);
|
|
1477
1650
|
if (loc && typeof loc.path === "string" && loc.path) {
|
|
1478
1651
|
const st = await stat(loc.path);
|
|
1479
1652
|
if (st && typeof st.size === "number") sizeBytes = st.size;
|
|
@@ -1558,7 +1731,8 @@ function apply(ctx) {
|
|
|
1558
1731
|
const subagentSet = /* @__PURE__ */ new Set();
|
|
1559
1732
|
try {
|
|
1560
1733
|
if (typeof sp.list === "function") {
|
|
1561
|
-
for (const
|
|
1734
|
+
for (const entry of await persistence.listEntries()) {
|
|
1735
|
+
const h = entry.header;
|
|
1562
1736
|
if (String(h.parentSession) !== String(sid)) continue;
|
|
1563
1737
|
if (h.origin === "subagent") subagentSet.add(h.id);
|
|
1564
1738
|
else childrenSet.add(h.id);
|
|
@@ -1595,6 +1769,11 @@ function apply(ctx) {
|
|
|
1595
1769
|
authorityTitleCache.set(String(session.id), event.data.title);
|
|
1596
1770
|
}
|
|
1597
1771
|
}));
|
|
1772
|
+
disposers.push(ctx.webServer.register({
|
|
1773
|
+
kind: "exact",
|
|
1774
|
+
path: "/archived-sessions/capabilities",
|
|
1775
|
+
handler: async (req, res) => json(res, capabilities)
|
|
1776
|
+
}));
|
|
1598
1777
|
disposers.push(ctx.webServer.register({
|
|
1599
1778
|
kind: "exact",
|
|
1600
1779
|
path: "/archived-sessions/list",
|
|
@@ -1605,8 +1784,8 @@ function apply(ctx) {
|
|
|
1605
1784
|
let materialized = /* @__PURE__ */ new Set();
|
|
1606
1785
|
let live = ctx.get("sessions");
|
|
1607
1786
|
try {
|
|
1608
|
-
const
|
|
1609
|
-
materialized = new Set(
|
|
1787
|
+
const entries = await persistence.listEntries();
|
|
1788
|
+
materialized = new Set(entries.map((entry) => entry.id));
|
|
1610
1789
|
} catch (e) {
|
|
1611
1790
|
}
|
|
1612
1791
|
const trashStore = await readTrashStore();
|
|
@@ -1657,12 +1836,12 @@ function apply(ctx) {
|
|
|
1657
1836
|
try {
|
|
1658
1837
|
results.push({ sessionId: sid, ok: true, ...await restoreOne(sid) });
|
|
1659
1838
|
} catch (e) {
|
|
1660
|
-
results.push({ sessionId: sid, ok: false, error: String(e && e.message || e) });
|
|
1839
|
+
results.push({ sessionId: sid, ok: false, code: e && e.code, error: String(e && e.message || e) });
|
|
1661
1840
|
}
|
|
1662
1841
|
}
|
|
1663
1842
|
json(res, { ok: true, restored: results.filter((r) => r.ok).length, results });
|
|
1664
1843
|
} catch (e) {
|
|
1665
|
-
json(res, { ok: false, error: String(e && e.message || e) },
|
|
1844
|
+
json(res, { ok: false, code: e && e.code, error: String(e && e.message || e) }, errorStatus(e));
|
|
1666
1845
|
}
|
|
1667
1846
|
}
|
|
1668
1847
|
}));
|
|
@@ -1701,7 +1880,7 @@ function apply(ctx) {
|
|
|
1701
1880
|
}
|
|
1702
1881
|
json(res, { ok: true, deleted: results.filter((r) => r.ok).length, results });
|
|
1703
1882
|
} catch (e) {
|
|
1704
|
-
json(res, { ok: false, error: String(e && e.message || e) },
|
|
1883
|
+
json(res, { ok: false, code: e && e.code, error: String(e && e.message || e) }, errorStatus(e));
|
|
1705
1884
|
}
|
|
1706
1885
|
}
|
|
1707
1886
|
}));
|
|
@@ -1740,11 +1919,19 @@ function apply(ctx) {
|
|
|
1740
1919
|
try {
|
|
1741
1920
|
const items = await readTrash();
|
|
1742
1921
|
const results = await Promise.all(items.map(async (item) => {
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1922
|
+
if (typeof item.originalPath !== "string" || !item.originalPath) {
|
|
1923
|
+
return { sessionId: item.sessionId, status: "unverified", originalPath: null };
|
|
1924
|
+
}
|
|
1925
|
+
const exists = await stat(item.originalPath).then(() => true).catch(() => false);
|
|
1926
|
+
return { sessionId: item.sessionId, status: exists ? "ok" : "missing", originalPath: item.originalPath };
|
|
1746
1927
|
}));
|
|
1747
|
-
json(res, {
|
|
1928
|
+
json(res, {
|
|
1929
|
+
ok: true,
|
|
1930
|
+
healthy: results.filter((r) => r.status === "ok").length,
|
|
1931
|
+
missing: results.filter((r) => r.status === "missing").length,
|
|
1932
|
+
unverified: results.filter((r) => r.status === "unverified").length,
|
|
1933
|
+
results
|
|
1934
|
+
});
|
|
1748
1935
|
} catch (e) {
|
|
1749
1936
|
json(res, { ok: false, error: String(e && e.message || e) }, errorStatus(e));
|
|
1750
1937
|
}
|
|
@@ -1762,7 +1949,7 @@ function apply(ctx) {
|
|
|
1762
1949
|
metaCache.invalidate(sid);
|
|
1763
1950
|
json(res, out);
|
|
1764
1951
|
} catch (e) {
|
|
1765
|
-
json(res, { ok: false, error: String(e && e.message || e) },
|
|
1952
|
+
json(res, { ok: false, code: e && e.code, error: String(e && e.message || e) }, errorStatus(e));
|
|
1766
1953
|
}
|
|
1767
1954
|
}
|
|
1768
1955
|
}));
|
|
@@ -1780,7 +1967,7 @@ function apply(ctx) {
|
|
|
1780
1967
|
});
|
|
1781
1968
|
json(res, out);
|
|
1782
1969
|
} catch (e) {
|
|
1783
|
-
json(res, { ok: false, error: String(e && e.message || e) },
|
|
1970
|
+
json(res, { ok: false, code: e && e.code, error: String(e && e.message || e) }, errorStatus(e));
|
|
1784
1971
|
}
|
|
1785
1972
|
}
|
|
1786
1973
|
}));
|
|
@@ -1805,7 +1992,7 @@ function apply(ctx) {
|
|
|
1805
1992
|
}
|
|
1806
1993
|
json(res, { ok: true, purged: results.filter((r) => r.ok).length, results });
|
|
1807
1994
|
} catch (e) {
|
|
1808
|
-
json(res, { ok: false, error: String(e && e.message || e) },
|
|
1995
|
+
json(res, { ok: false, code: e && e.code, error: String(e && e.message || e) }, errorStatus(e));
|
|
1809
1996
|
}
|
|
1810
1997
|
}
|
|
1811
1998
|
}));
|
|
@@ -1847,7 +2034,7 @@ function apply(ctx) {
|
|
|
1847
2034
|
const url = new URL(req.url, "http://localhost");
|
|
1848
2035
|
const sid = url.searchParams.get("sessionId");
|
|
1849
2036
|
requireSessionId(sid);
|
|
1850
|
-
const r = await
|
|
2037
|
+
const r = await persistence.readSession(sid, 0);
|
|
1851
2038
|
if (!r || !r.meta) {
|
|
1852
2039
|
const error = new Error("\u65E0\u6CD5\u8BFB\u53D6\u8BE5\u4F1A\u8BDD\u7684\u65E5\u5FD7");
|
|
1853
2040
|
error.status = 404;
|
|
@@ -1905,7 +2092,7 @@ function apply(ctx) {
|
|
|
1905
2092
|
}
|
|
1906
2093
|
json(res, { sessionId: sid, ...moved });
|
|
1907
2094
|
} catch (e) {
|
|
1908
|
-
json(res, { ok: false, error: String(e && e.message || e) },
|
|
2095
|
+
json(res, { ok: false, code: e && e.code, error: String(e && e.message || e) }, errorStatus(e));
|
|
1909
2096
|
}
|
|
1910
2097
|
}
|
|
1911
2098
|
}));
|