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/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 readFileSync4 } from "node:fs";
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 findZstdFrameStarts(buf) {
13
- const starts = [];
14
- for (let i = 0; i + 4 <= buf.length; i++) {
15
- if (buf.readUInt32LE(i) !== ZSTD_MAGIC) continue;
16
- try {
17
- const out = zlib.zstdDecompressSync(buf.subarray(i, i + Math.min(buf.length - i, 1e6)));
18
- if (out.length > 0) starts.push(i);
19
- } catch (_) {
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 starts;
53
+ return { frames };
23
54
  }
24
- function rewriteFrame0Cwd(filePath, newCwd) {
25
- const buf = readFileSync(filePath);
26
- const starts = findZstdFrameStarts(buf);
27
- if (starts.length === 0) throw new Error("\u4F1A\u8BDD\u65E5\u5FD7\u683C\u5F0F\u5F02\u5E38\uFF08\u65E0 zstd \u5E27\uFF09");
28
- const end0 = starts.length > 1 ? starts[1] : buf.length;
29
- const frame0 = buf.subarray(starts[0], end0);
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
- writeFileSync(filePath, Buffer.concat([newFrame0, rest]));
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 as readFileSync2 } from "node:fs";
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(readFileSync2(indexPath, "utf8")));
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 readFileSync3 } from "node:fs";
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(readFileSync3(indexPath, "utf8")));
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 sp.readFrom(id, 0);
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 headers = null;
834
- if (Array.isArray(preloadedHeaders)) headers = preloadedHeaders;
964
+ let entries = null;
965
+ if (Array.isArray(preloadedHeaders)) entries = preloadedHeaders;
835
966
  else {
836
967
  try {
837
- headers = await sp.list();
968
+ entries = await persistence.listEntries();
838
969
  } catch (e) {
839
- headers = [];
970
+ entries = [];
840
971
  }
841
972
  }
842
- if (!Array.isArray(headers)) headers = [];
973
+ if (!Array.isArray(entries)) entries = [];
843
974
  const CHUNK = 8;
844
- for (let i = 0; i < headers.length; i += CHUNK) {
845
- await Promise.all(headers.slice(i, i + CHUNK).map(async (header) => {
846
- const id = header && header.id != null ? String(header.id) : null;
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 = sp.locate(header);
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(readFileSync4(TRASH_INDEX, "utf8")));
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 headers = await sp.list();
923
- header = headers.find((h) => String(h.id) === sid) || null;
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 = sp.locate(header);
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 sp.readFrom(sid, 0);
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: header && typeof header.size === "number" ? header.size : null,
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 headers = await sp.list();
991
- const current = headers.find((h) => String(h.id) === sid);
992
- const located = current && sp.locate(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 sp.readFrom(sid, 0);
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 rewriteFrame0Cwd(backupPath, canonical);
1140
- await rename4(backupPath, newPath);
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 sp.readFrom(sid, 0);
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 headers = null;
1437
+ let entries = null;
1265
1438
  try {
1266
- headers = await sp.list();
1439
+ entries = await persistence.listEntries();
1267
1440
  } catch (e) {
1268
- headers = null;
1441
+ entries = null;
1269
1442
  }
1270
- if (!headers || !Array.isArray(headers)) return false;
1271
- await reg.replaceHeaderIndex(headers);
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 headers = [];
1480
+ let entries = [];
1308
1481
  let headersOk = false;
1309
1482
  try {
1310
- headers = await sp.list();
1311
- headersOk = Array.isArray(headers);
1312
- if (!headersOk) headers = [];
1483
+ entries = await persistence.listEntries();
1484
+ headersOk = Array.isArray(entries);
1485
+ if (!headersOk) entries = [];
1313
1486
  } catch (e) {
1314
- headers = [];
1487
+ entries = [];
1315
1488
  }
1316
1489
  let live = ctx.get("sessions");
1317
- const ids = headers.map((h) => String(h.id));
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(headers);
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 headers = [];
1581
+ let entries = [];
1409
1582
  try {
1410
- headers = await sp.list();
1583
+ entries = await persistence.listEntries();
1411
1584
  } catch (e) {
1412
- headers = [];
1585
+ entries = [];
1413
1586
  }
1414
- if (!Array.isArray(headers)) headers = [];
1415
- for (const header of headers) ids.push(String(header.id));
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(headers);
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 sp.readFrom(sid, 0);
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 = sp.locate(meta);
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 h of await sp.list()) {
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 headers = await sp.list();
1609
- materialized = new Set(headers.map((h) => String(h.id)));
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) }, 500);
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) }, 500);
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
- let exists = false;
1744
- if (typeof item.originalPath === "string") exists = await stat(item.originalPath).then(() => true).catch(() => false);
1745
- return { sessionId: item.sessionId, status: exists ? "ok" : "missing", originalPath: item.originalPath || null };
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, { ok: true, healthy: results.filter((r) => r.status === "ok").length, missing: results.filter((r) => r.status === "missing").length, results });
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) }, 500);
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) }, 500);
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) }, 500);
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 sp.readFrom(sid, 0);
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) }, 500);
2095
+ json(res, { ok: false, code: e && e.code, error: String(e && e.message || e) }, errorStatus(e));
1909
2096
  }
1910
2097
  }
1911
2098
  }));