dsh-sessions-manager 3.4.1 → 3.5.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/README.en.md +6 -0
- package/README.md +6 -0
- package/lib/client.js +60 -17
- package/lib/client.js.map +2 -2
- package/lib/index.js +375 -82
- package/lib/index.js.map +4 -4
- package/package.json +1 -1
- package/src/client/index.jsx +42 -7
- package/src/compat/capabilities.js +34 -0
- package/src/compat/persistence.js +72 -0
- package/src/index.js +167 -63
- package/src/title-persist-index.js +137 -0
package/lib/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/index.js
|
|
2
|
-
import { mkdir as
|
|
3
|
-
import { basename, dirname, isAbsolute, join as
|
|
2
|
+
import { mkdir as mkdir4, realpath, rename as rename4, stat, unlink, writeFile as writeFile4 } from "node:fs/promises";
|
|
3
|
+
import { basename, dirname as dirname2, isAbsolute, join as join4 } from "node:path";
|
|
4
4
|
import { readFileSync as readFileSync4 } from "node:fs";
|
|
5
5
|
import { homedir as homedir3 } from "node:os";
|
|
6
6
|
|
|
@@ -500,12 +500,211 @@ function createSessionMetaCache(opts = {}) {
|
|
|
500
500
|
};
|
|
501
501
|
}
|
|
502
502
|
|
|
503
|
+
// src/title-persist-index.js
|
|
504
|
+
import { mkdir as mkdir3, readFile, rename as rename3, writeFile as writeFile3 } from "node:fs/promises";
|
|
505
|
+
import { dirname, join as join3 } from "node:path";
|
|
506
|
+
var TITLE_INDEX_SCHEMA_VERSION = 1;
|
|
507
|
+
var MAX_ENTRIES = 2e4;
|
|
508
|
+
function normalizeEntry(raw) {
|
|
509
|
+
if (!raw || typeof raw !== "object") return null;
|
|
510
|
+
const title = typeof raw.title === "string" ? raw.title : null;
|
|
511
|
+
const cwd = typeof raw.cwd === "string" ? raw.cwd : null;
|
|
512
|
+
const createdAt = typeof raw.createdAt === "number" ? raw.createdAt : null;
|
|
513
|
+
const fingerprint = typeof raw.fingerprint === "string" && raw.fingerprint ? raw.fingerprint : null;
|
|
514
|
+
const updatedAt = typeof raw.updatedAt === "number" ? raw.updatedAt : 0;
|
|
515
|
+
if (!fingerprint || !title && !cwd) return null;
|
|
516
|
+
return { title, cwd, createdAt, fingerprint, updatedAt };
|
|
517
|
+
}
|
|
518
|
+
function normalizeTitleIndex(raw) {
|
|
519
|
+
const entries = {};
|
|
520
|
+
if (raw && typeof raw === "object" && raw.entries && typeof raw.entries === "object") {
|
|
521
|
+
for (const [id, entry] of Object.entries(raw.entries)) {
|
|
522
|
+
if (typeof id !== "string" || !id || id.length > 200) continue;
|
|
523
|
+
const normalized = normalizeEntry(entry);
|
|
524
|
+
if (normalized) entries[id] = normalized;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
return { schemaVersion: TITLE_INDEX_SCHEMA_VERSION, entries };
|
|
528
|
+
}
|
|
529
|
+
function mergeEntries(left, right) {
|
|
530
|
+
const merged = { ...left };
|
|
531
|
+
for (const [id, entry] of Object.entries(right)) merged[id] = entry;
|
|
532
|
+
const ids = Object.keys(merged);
|
|
533
|
+
if (ids.length > MAX_ENTRIES) {
|
|
534
|
+
ids.sort((a, b) => (merged[a].updatedAt || 0) - (merged[b].updatedAt || 0));
|
|
535
|
+
for (const id of ids.slice(0, ids.length - MAX_ENTRIES)) delete merged[id];
|
|
536
|
+
}
|
|
537
|
+
return merged;
|
|
538
|
+
}
|
|
539
|
+
function createTitleIndexStore({ dir, file }) {
|
|
540
|
+
let cache = null;
|
|
541
|
+
let chain = Promise.resolve();
|
|
542
|
+
const path = file || join3(dir, "title-index.json");
|
|
543
|
+
async function readRaw() {
|
|
544
|
+
try {
|
|
545
|
+
return normalizeTitleIndex(JSON.parse(await readFile(path, "utf8")));
|
|
546
|
+
} catch (e) {
|
|
547
|
+
return normalizeTitleIndex(null);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
function enqueue(mutator) {
|
|
551
|
+
const operation = chain.then(async () => {
|
|
552
|
+
const store = cache || (cache = (await readRaw()).entries);
|
|
553
|
+
await mutator(store);
|
|
554
|
+
return store;
|
|
555
|
+
});
|
|
556
|
+
chain = operation.catch(() => {
|
|
557
|
+
});
|
|
558
|
+
return operation;
|
|
559
|
+
}
|
|
560
|
+
return {
|
|
561
|
+
// 只读:内存优先,未加载过才落盘一次。绝不抛错。
|
|
562
|
+
async entries() {
|
|
563
|
+
if (cache) return cache;
|
|
564
|
+
cache = (await readRaw()).entries;
|
|
565
|
+
return cache;
|
|
566
|
+
},
|
|
567
|
+
// 批量合并写入(原子替换)。失败静默:索引只是加速器,坏了下次重解码。
|
|
568
|
+
async merge(batch) {
|
|
569
|
+
const right = {};
|
|
570
|
+
for (const [id, entry] of Object.entries(batch || {})) {
|
|
571
|
+
const normalized = normalizeEntry(entry);
|
|
572
|
+
if (normalized) right[String(id)] = normalized;
|
|
573
|
+
}
|
|
574
|
+
if (!Object.keys(right).length) return false;
|
|
575
|
+
await enqueue(async (store) => {
|
|
576
|
+
const next = mergeEntries(store, right);
|
|
577
|
+
await mkdir3(dirname(path), { recursive: true });
|
|
578
|
+
const tmp = join3(dirname(path), `.title-index-${process.pid}-${Date.now()}.tmp`);
|
|
579
|
+
await writeFile3(tmp, JSON.stringify({ schemaVersion: TITLE_INDEX_SCHEMA_VERSION, entries: next }), { encoding: "utf8", mode: 384 });
|
|
580
|
+
await rename3(tmp, path);
|
|
581
|
+
cache = next;
|
|
582
|
+
});
|
|
583
|
+
return true;
|
|
584
|
+
},
|
|
585
|
+
async remove(ids) {
|
|
586
|
+
const wanted = new Set((ids || []).map(String));
|
|
587
|
+
if (!wanted.size) return false;
|
|
588
|
+
await enqueue(async (store) => {
|
|
589
|
+
let changed = false;
|
|
590
|
+
for (const id of wanted) {
|
|
591
|
+
if (id in store) {
|
|
592
|
+
delete store[id];
|
|
593
|
+
changed = true;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
if (!changed) return;
|
|
597
|
+
await mkdir3(dirname(path), { recursive: true });
|
|
598
|
+
const tmp = join3(dirname(path), `.title-index-${process.pid}-${Date.now()}.tmp`);
|
|
599
|
+
await writeFile3(tmp, JSON.stringify({ schemaVersion: TITLE_INDEX_SCHEMA_VERSION, entries: store }), { encoding: "utf8", mode: 384 });
|
|
600
|
+
await rename3(tmp, path);
|
|
601
|
+
});
|
|
602
|
+
return true;
|
|
603
|
+
}
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
// src/compat/persistence.js
|
|
608
|
+
function asHeader(value) {
|
|
609
|
+
if (!value || typeof value !== "object") return null;
|
|
610
|
+
const candidate = value.header && typeof value.header === "object" ? value.header : value;
|
|
611
|
+
return candidate.id == null ? null : candidate;
|
|
612
|
+
}
|
|
613
|
+
function normalizePersistenceEntry(value) {
|
|
614
|
+
const header = asHeader(value);
|
|
615
|
+
if (!header) return null;
|
|
616
|
+
const snapshot = value && value.header === header ? value : null;
|
|
617
|
+
return {
|
|
618
|
+
header,
|
|
619
|
+
snapshot,
|
|
620
|
+
id: String(header.id),
|
|
621
|
+
sizeBytes: snapshot && Number.isFinite(snapshot.sizeBytes) ? Number(snapshot.sizeBytes) : null,
|
|
622
|
+
eventCount: snapshot && Number.isSafeInteger(snapshot.eventCount) ? snapshot.eventCount : null,
|
|
623
|
+
revision: snapshot ? snapshot.revision : null
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
function normalizePersistenceList(values) {
|
|
627
|
+
if (!Array.isArray(values)) return [];
|
|
628
|
+
return values.map(normalizePersistenceEntry).filter(Boolean);
|
|
629
|
+
}
|
|
630
|
+
function createPersistenceAdapter(service) {
|
|
631
|
+
if (!service || typeof service.list !== "function") throw new TypeError("sessionPersistence.list is required");
|
|
632
|
+
async function listEntries(options) {
|
|
633
|
+
return normalizePersistenceList(await service.list(options));
|
|
634
|
+
}
|
|
635
|
+
async function readSession(id, offset = 0) {
|
|
636
|
+
if (typeof service.readFrom === "function") {
|
|
637
|
+
const result = await service.readFrom(id, offset);
|
|
638
|
+
return {
|
|
639
|
+
meta: result && result.meta ? result.meta : null,
|
|
640
|
+
inheritedEventCount: result && Number.isSafeInteger(result.inheritedEventCount) ? result.inheritedEventCount : 0,
|
|
641
|
+
events: result && Array.isArray(result.events) ? result.events : []
|
|
642
|
+
};
|
|
643
|
+
}
|
|
644
|
+
if (typeof service.open !== "function") throw new Error("\u5F53\u524D DSH \u6301\u4E45\u5316\u670D\u52A1\u4E0D\u652F\u6301\u8BFB\u53D6\u4F1A\u8BDD");
|
|
645
|
+
const handle = await service.open(id, "read");
|
|
646
|
+
if (!handle || typeof handle.read !== "function" || typeof handle.close !== "function") {
|
|
647
|
+
try {
|
|
648
|
+
if (handle && typeof handle.close === "function") await handle.close();
|
|
649
|
+
} catch {
|
|
650
|
+
}
|
|
651
|
+
throw new Error("DSH \u8FD4\u56DE\u4E86\u65E0\u6548\u7684 SessionHandle");
|
|
652
|
+
}
|
|
653
|
+
try {
|
|
654
|
+
const events = await handle.read(offset);
|
|
655
|
+
return {
|
|
656
|
+
meta: handle.header || handle.meta || null,
|
|
657
|
+
inheritedEventCount: Number.isSafeInteger(handle.inheritedEventCount) ? handle.inheritedEventCount : 0,
|
|
658
|
+
events: Array.isArray(events) ? events : [...events || []]
|
|
659
|
+
};
|
|
660
|
+
} finally {
|
|
661
|
+
await handle.close();
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
function locate(header) {
|
|
665
|
+
if (typeof service.locate === "function") return service.locate(header);
|
|
666
|
+
return null;
|
|
667
|
+
}
|
|
668
|
+
const kind = typeof service.open === "function" ? "session-handle" : "legacy";
|
|
669
|
+
return { kind, listEntries, readSession, locate };
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// src/compat/capabilities.js
|
|
673
|
+
function action(available, reason = null) {
|
|
674
|
+
return { available: !!available, reason: available ? null : reason };
|
|
675
|
+
}
|
|
676
|
+
function detectCapabilities({ persistence, workspaceRegistry }) {
|
|
677
|
+
const handleApi = !!(persistence && typeof persistence.open === "function");
|
|
678
|
+
const legacyRead = !!(persistence && typeof persistence.readFrom === "function");
|
|
679
|
+
const legacyLocate = !!(persistence && (typeof persistence.locate === "function" || persistence.backend && typeof persistence.backend.locate === "function"));
|
|
680
|
+
const workspaceInternals = !!(workspaceRegistry && workspaceRegistry.headers && workspaceRegistry.sessionPaths && typeof workspaceRegistry.replaceHeaderIndex === "function");
|
|
681
|
+
return {
|
|
682
|
+
persistence: handleApi ? "session-handle" : "legacy",
|
|
683
|
+
actions: {
|
|
684
|
+
read: action(legacyRead || handleApi, "\u5F53\u524D DSH \u672A\u63D0\u4F9B\u53EF\u8BC6\u522B\u7684\u4F1A\u8BDD\u8BFB\u53D6\u63A5\u53E3"),
|
|
685
|
+
archive: action(!!(workspaceRegistry && typeof workspaceRegistry.archiveSession === "function"), "\u5F53\u524D DSH \u672A\u63D0\u4F9B\u5F52\u6863\u63A5\u53E3"),
|
|
686
|
+
trash: action(legacyRead || handleApi, "\u5F53\u524D DSH \u65E0\u6CD5\u8BFB\u53D6\u4F1A\u8BDD\uFF0C\u4E0D\u80FD\u5B89\u5168\u79FB\u5165\u56DE\u6536\u7AD9"),
|
|
687
|
+
restoreTrash: action(true),
|
|
688
|
+
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"),
|
|
689
|
+
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")
|
|
690
|
+
}
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
function requireCapability(capabilities, name2) {
|
|
694
|
+
const value = capabilities && capabilities.actions && capabilities.actions[name2];
|
|
695
|
+
if (value && value.available) return;
|
|
696
|
+
const error = new Error(value && value.reason || `\u5F53\u524D\u73AF\u5883\u4E0D\u652F\u6301 ${name2}`);
|
|
697
|
+
error.status = 409;
|
|
698
|
+
error.code = "DSM_CAPABILITY_UNAVAILABLE";
|
|
699
|
+
throw error;
|
|
700
|
+
}
|
|
701
|
+
|
|
503
702
|
// src/index.js
|
|
504
703
|
var name = "dsh-sessions-manager";
|
|
505
704
|
var inject = ["webServer", "workspaceRegistry", "sessionPersistence", "sessionQuery", "storageDomain"];
|
|
506
705
|
var MAX_TITLE = 80;
|
|
507
|
-
var TRASH_DIR = process.env.DSH_SESSIONS_MANAGER_TRASH_DIR ||
|
|
508
|
-
var TRASH_INDEX =
|
|
706
|
+
var TRASH_DIR = process.env.DSH_SESSIONS_MANAGER_TRASH_DIR || join4(homedir3(), ".dsh", "sessions-manager-trash");
|
|
707
|
+
var TRASH_INDEX = join4(TRASH_DIR, "index.json");
|
|
509
708
|
var TRASH_SCHEMA_VERSION = 2;
|
|
510
709
|
var DEFAULT_TRASH_SETTINGS = Object.freeze({ retentionDays: 0 });
|
|
511
710
|
var FETCH_TOOL_RE = /search|fetch|download|browse/i;
|
|
@@ -586,10 +785,46 @@ function foldTitle(events) {
|
|
|
586
785
|
function apply(ctx) {
|
|
587
786
|
const w = ctx.workspaceRegistry;
|
|
588
787
|
const sp = ctx.sessionPersistence;
|
|
788
|
+
const persistence = createPersistenceAdapter(sp);
|
|
789
|
+
const capabilities = detectCapabilities({ persistence: sp, workspaceRegistry: w });
|
|
589
790
|
const sq = ctx.sessionQuery;
|
|
590
791
|
const dom = () => ctx.storageDomain.get("workspace");
|
|
591
792
|
const authorityTitleCache = /* @__PURE__ */ new Map();
|
|
592
793
|
const metaCache = createSessionMetaCache();
|
|
794
|
+
const titleIndex = createTitleIndexStore({ dir: TRASH_DIR, file: join4(TRASH_DIR, "title-index.json") });
|
|
795
|
+
async function hydrateFromPersist(ids, statsById) {
|
|
796
|
+
const hits = /* @__PURE__ */ new Map();
|
|
797
|
+
if (!ids || !ids.length) return hits;
|
|
798
|
+
let store;
|
|
799
|
+
try {
|
|
800
|
+
store = await titleIndex.entries();
|
|
801
|
+
} catch (e) {
|
|
802
|
+
return hits;
|
|
803
|
+
}
|
|
804
|
+
for (const id of ids) {
|
|
805
|
+
const stat2 = statsById.get(id);
|
|
806
|
+
const entry = store && store[id];
|
|
807
|
+
if (!stat2 || !entry) continue;
|
|
808
|
+
const fp = fingerprintOf(stat2);
|
|
809
|
+
if (fp && entry.fingerprint === fp) {
|
|
810
|
+
hits.set(id, { title: entry.title, cwd: entry.cwd, createdAt: entry.createdAt });
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
return hits;
|
|
814
|
+
}
|
|
815
|
+
function persistDecoded(decoded, statsById) {
|
|
816
|
+
if (!decoded || !decoded.size) return;
|
|
817
|
+
const batch = {};
|
|
818
|
+
const now = Date.now();
|
|
819
|
+
for (const [id, meta] of decoded) {
|
|
820
|
+
const fp = fingerprintOf(statsById.get(id));
|
|
821
|
+
if (!fp) continue;
|
|
822
|
+
batch[id] = { title: meta.title, cwd: meta.cwd, createdAt: meta.createdAt, fingerprint: fp, updatedAt: now };
|
|
823
|
+
}
|
|
824
|
+
if (!Object.keys(batch).length) return;
|
|
825
|
+
titleIndex.merge(batch).catch(() => {
|
|
826
|
+
});
|
|
827
|
+
}
|
|
593
828
|
function metaFromSnapshot(o) {
|
|
594
829
|
let title = null, createdAt = null, cwd = null;
|
|
595
830
|
if (o) {
|
|
@@ -676,7 +911,7 @@ function apply(ctx) {
|
|
|
676
911
|
}
|
|
677
912
|
if (!meta.title || !meta.cwd) {
|
|
678
913
|
try {
|
|
679
|
-
const r = await
|
|
914
|
+
const r = await persistence.readSession(id, 0);
|
|
680
915
|
if (r.meta) {
|
|
681
916
|
if (!meta.cwd) meta.cwd = r.meta.cwd || null;
|
|
682
917
|
if (!meta.createdAt) meta.createdAt = r.meta.createdAt || null;
|
|
@@ -686,28 +921,31 @@ function apply(ctx) {
|
|
|
686
921
|
}
|
|
687
922
|
}
|
|
688
923
|
metaCache.set(key, statInfo, meta);
|
|
924
|
+
if (opts.collectDecoded && statInfo) opts.collectDecoded(key, meta);
|
|
689
925
|
return buildItem(key, meta, usage, opts.exposeUsage);
|
|
690
926
|
}
|
|
691
927
|
async function collectUsage(preloadedHeaders) {
|
|
692
928
|
const sizeById = /* @__PURE__ */ new Map();
|
|
693
929
|
const mtimeById = /* @__PURE__ */ new Map();
|
|
694
|
-
let
|
|
695
|
-
if (Array.isArray(preloadedHeaders))
|
|
930
|
+
let entries = null;
|
|
931
|
+
if (Array.isArray(preloadedHeaders)) entries = preloadedHeaders;
|
|
696
932
|
else {
|
|
697
933
|
try {
|
|
698
|
-
|
|
934
|
+
entries = await persistence.listEntries();
|
|
699
935
|
} catch (e) {
|
|
700
|
-
|
|
936
|
+
entries = [];
|
|
701
937
|
}
|
|
702
938
|
}
|
|
703
|
-
if (!Array.isArray(
|
|
939
|
+
if (!Array.isArray(entries)) entries = [];
|
|
704
940
|
const CHUNK = 8;
|
|
705
|
-
for (let i = 0; i <
|
|
706
|
-
await Promise.all(
|
|
707
|
-
const
|
|
941
|
+
for (let i = 0; i < entries.length; i += CHUNK) {
|
|
942
|
+
await Promise.all(entries.slice(i, i + CHUNK).map(async (entry) => {
|
|
943
|
+
const header = entry && entry.header ? entry.header : entry;
|
|
944
|
+
const id = entry && entry.id != null ? String(entry.id) : header && header.id != null ? String(header.id) : null;
|
|
708
945
|
if (!id) return;
|
|
946
|
+
if (entry && Number.isFinite(entry.sizeBytes)) sizeById.set(id, Number(entry.sizeBytes));
|
|
709
947
|
try {
|
|
710
|
-
const loc =
|
|
948
|
+
const loc = persistence.locate(header);
|
|
711
949
|
if (!loc || typeof loc.path !== "string" || !loc.path) return;
|
|
712
950
|
const st = await stat(loc.path);
|
|
713
951
|
if (!st) return;
|
|
@@ -746,10 +984,10 @@ function apply(ctx) {
|
|
|
746
984
|
return (await readTrashStore()).items;
|
|
747
985
|
}
|
|
748
986
|
async function writeTrashStore(store) {
|
|
749
|
-
await
|
|
750
|
-
const tmp =
|
|
751
|
-
await
|
|
752
|
-
await
|
|
987
|
+
await mkdir4(TRASH_DIR, { recursive: true });
|
|
988
|
+
const tmp = join4(TRASH_DIR, `.index-${process.pid}-${Date.now()}.tmp`);
|
|
989
|
+
await writeFile4(tmp, JSON.stringify(normalizeTrashStore(store), null, 2), { encoding: "utf8", mode: 384 });
|
|
990
|
+
await rename4(tmp, TRASH_INDEX);
|
|
753
991
|
}
|
|
754
992
|
function mutateTrash(mutator) {
|
|
755
993
|
const operation = trashMutation.then(async () => {
|
|
@@ -779,17 +1017,20 @@ function apply(ctx) {
|
|
|
779
1017
|
let cwd = null;
|
|
780
1018
|
let title = null;
|
|
781
1019
|
let removedPath = null;
|
|
1020
|
+
let persistenceEntry = null;
|
|
782
1021
|
try {
|
|
783
|
-
const
|
|
784
|
-
|
|
1022
|
+
const entries = await persistence.listEntries();
|
|
1023
|
+
const found = entries.find((entry) => entry.id === sid) || null;
|
|
1024
|
+
persistenceEntry = found;
|
|
1025
|
+
header = found ? found.header : null;
|
|
785
1026
|
if (header) {
|
|
786
|
-
const loc =
|
|
1027
|
+
const loc = persistence.locate(header);
|
|
787
1028
|
if (loc && typeof loc.path === "string") removedPath = loc.path;
|
|
788
1029
|
cwd = header.cwd || null;
|
|
789
1030
|
title = header.title || header.meta && header.meta.title || null;
|
|
790
1031
|
}
|
|
791
1032
|
if (!title) {
|
|
792
|
-
const r = await
|
|
1033
|
+
const r = await persistence.readSession(sid, 0);
|
|
793
1034
|
if (r && r.meta) {
|
|
794
1035
|
if (!cwd) cwd = r.meta.cwd;
|
|
795
1036
|
title = foldTitle(r.events);
|
|
@@ -810,7 +1051,7 @@ function apply(ctx) {
|
|
|
810
1051
|
cwd: cwd || null,
|
|
811
1052
|
header: header || null,
|
|
812
1053
|
originalPath: removedPath || null,
|
|
813
|
-
sizeBytes:
|
|
1054
|
+
sizeBytes: persistenceEntry && Number.isFinite(persistenceEntry.sizeBytes) ? persistenceEntry.sizeBytes : null,
|
|
814
1055
|
wasArchived: archived,
|
|
815
1056
|
deletedAt: Date.now()
|
|
816
1057
|
};
|
|
@@ -838,6 +1079,7 @@ function apply(ctx) {
|
|
|
838
1079
|
}
|
|
839
1080
|
async function purgeFromTrash(sid) {
|
|
840
1081
|
requireSessionId(sid);
|
|
1082
|
+
requireCapability(capabilities, "purge");
|
|
841
1083
|
let purged = false;
|
|
842
1084
|
await mutateTrash(async (store) => {
|
|
843
1085
|
const entry = store.items.find((t) => String(t.sessionId) === sid);
|
|
@@ -848,14 +1090,19 @@ function apply(ctx) {
|
|
|
848
1090
|
}
|
|
849
1091
|
let target = null;
|
|
850
1092
|
try {
|
|
851
|
-
const
|
|
852
|
-
const current =
|
|
853
|
-
const located = current &&
|
|
1093
|
+
const entries = await persistence.listEntries();
|
|
1094
|
+
const current = entries.find((entry2) => entry2.id === sid);
|
|
1095
|
+
const located = current && persistence.locate(current.header);
|
|
854
1096
|
if (located && typeof located.path === "string") target = located.path;
|
|
855
1097
|
} catch (e) {
|
|
856
1098
|
}
|
|
857
1099
|
if (!target && typeof entry.originalPath === "string") target = entry.originalPath;
|
|
858
|
-
|
|
1100
|
+
if (!target) {
|
|
1101
|
+
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");
|
|
1102
|
+
error.status = 409;
|
|
1103
|
+
throw error;
|
|
1104
|
+
}
|
|
1105
|
+
const targetOwnsSession = target && (basename(dirname2(target)) === sid || basename(target).includes(sid));
|
|
859
1106
|
if (target && !targetOwnsSession) {
|
|
860
1107
|
const error = new Error("\u65E5\u5FD7\u8DEF\u5F84\u4E0E\u4F1A\u8BDD ID \u4E0D\u5339\u914D\uFF0C\u5DF2\u505C\u6B62\u6C38\u4E45\u5220\u9664");
|
|
861
1108
|
error.status = 409;
|
|
@@ -930,6 +1177,7 @@ function apply(ctx) {
|
|
|
930
1177
|
return (await readTrashStore()).settings;
|
|
931
1178
|
}
|
|
932
1179
|
async function cleanupExpiredTrash() {
|
|
1180
|
+
if (!capabilities.actions.purge.available) return 0;
|
|
933
1181
|
const store = await readTrashStore();
|
|
934
1182
|
const days = store.settings.retentionDays;
|
|
935
1183
|
if (!days) return 0;
|
|
@@ -948,8 +1196,8 @@ function apply(ctx) {
|
|
|
948
1196
|
async function moveTargetWorkspace(rawPath) {
|
|
949
1197
|
if (typeof rawPath !== "string" || !rawPath.trim()) throw new Error("\u7F3A\u5C11\u76EE\u6807\u5DE5\u4F5C\u533A\u8DEF\u5F84");
|
|
950
1198
|
let p = String(rawPath).trim();
|
|
951
|
-
if (p.startsWith("~/")) p =
|
|
952
|
-
if (!isAbsolute(p)) p =
|
|
1199
|
+
if (p.startsWith("~/")) p = join4(homedir3(), p.slice(2));
|
|
1200
|
+
if (!isAbsolute(p)) p = join4(homedir3(), p);
|
|
953
1201
|
let canonical = null;
|
|
954
1202
|
try {
|
|
955
1203
|
canonical = await realpath(p);
|
|
@@ -957,17 +1205,18 @@ function apply(ctx) {
|
|
|
957
1205
|
canonical = null;
|
|
958
1206
|
}
|
|
959
1207
|
if (canonical === null) {
|
|
960
|
-
await
|
|
1208
|
+
await mkdir4(p, { recursive: true });
|
|
961
1209
|
canonical = await realpath(p);
|
|
962
1210
|
}
|
|
963
1211
|
return { canonical, entity: await w.create(canonical, basename(canonical) || "workspace") };
|
|
964
1212
|
}
|
|
965
1213
|
async function moveOne(sid, targetPath) {
|
|
1214
|
+
requireCapability(capabilities, "move");
|
|
966
1215
|
const activeId = getActiveSessionId(ctx);
|
|
967
1216
|
if (activeId != null && String(activeId) === String(sid)) {
|
|
968
1217
|
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");
|
|
969
1218
|
}
|
|
970
|
-
const r = await
|
|
1219
|
+
const r = await persistence.readSession(sid, 0);
|
|
971
1220
|
if (!r || !r.meta) throw new Error("\u65E0\u6CD5\u8BFB\u53D6\u8BE5\u4F1A\u8BDD\u7684\u65E5\u5FD7");
|
|
972
1221
|
const meta = r.meta;
|
|
973
1222
|
const events = r.events;
|
|
@@ -995,13 +1244,13 @@ function apply(ctx) {
|
|
|
995
1244
|
if (!oldPath || !newPath || oldPath === newPath) return false;
|
|
996
1245
|
const backupPath = `${oldPath}.move-backup-${Date.now()}`;
|
|
997
1246
|
try {
|
|
998
|
-
await
|
|
999
|
-
await
|
|
1247
|
+
await mkdir4(dirname2(newPath), { recursive: true });
|
|
1248
|
+
await rename4(oldPath, backupPath);
|
|
1000
1249
|
await rewriteFrame0Cwd(backupPath, canonical);
|
|
1001
|
-
await
|
|
1250
|
+
await rename4(backupPath, newPath);
|
|
1002
1251
|
} catch (e) {
|
|
1003
1252
|
try {
|
|
1004
|
-
await
|
|
1253
|
+
await rename4(backupPath, oldPath);
|
|
1005
1254
|
} catch (_) {
|
|
1006
1255
|
}
|
|
1007
1256
|
if (e && e.code !== "ENOENT") throw e;
|
|
@@ -1026,7 +1275,7 @@ function apply(ctx) {
|
|
|
1026
1275
|
return null;
|
|
1027
1276
|
};
|
|
1028
1277
|
if (isOpen) {
|
|
1029
|
-
await relocateLog(meta, newHeader);
|
|
1278
|
+
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");
|
|
1030
1279
|
try {
|
|
1031
1280
|
const st = sp.states && sp.states.get && sp.states.get(sid);
|
|
1032
1281
|
if (st && st.meta) st.meta = Object.assign({}, st.meta, { cwd: canonical });
|
|
@@ -1042,12 +1291,12 @@ function apply(ctx) {
|
|
|
1042
1291
|
oldPath = null;
|
|
1043
1292
|
}
|
|
1044
1293
|
if (typeof sp.create !== "function" || typeof sp.append !== "function") {
|
|
1045
|
-
await relocateLog(meta, newHeader);
|
|
1294
|
+
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");
|
|
1046
1295
|
} else {
|
|
1047
1296
|
const backupPath = oldPath ? `${oldPath}.move-backup-${Date.now()}` : null;
|
|
1048
1297
|
if (backupPath) {
|
|
1049
1298
|
try {
|
|
1050
|
-
await
|
|
1299
|
+
await rename4(oldPath, backupPath);
|
|
1051
1300
|
} catch (e) {
|
|
1052
1301
|
if (e && e.code !== "ENOENT") throw new Error("\u79FB\u52A8\u5931\u8D25\uFF1A\u65E0\u6CD5\u5907\u4EFD\u65E7\u7684\u4F1A\u8BDD\u65E5\u5FD7");
|
|
1053
1302
|
}
|
|
@@ -1055,7 +1304,7 @@ function apply(ctx) {
|
|
|
1055
1304
|
const restore = async () => {
|
|
1056
1305
|
if (backupPath) {
|
|
1057
1306
|
try {
|
|
1058
|
-
await
|
|
1307
|
+
await rename4(backupPath, oldPath);
|
|
1059
1308
|
} catch (_) {
|
|
1060
1309
|
}
|
|
1061
1310
|
}
|
|
@@ -1063,7 +1312,7 @@ function apply(ctx) {
|
|
|
1063
1312
|
try {
|
|
1064
1313
|
await sp.create(newHeader);
|
|
1065
1314
|
await sp.append(sid, events);
|
|
1066
|
-
const check = await
|
|
1315
|
+
const check = await persistence.readSession(sid, 0);
|
|
1067
1316
|
if (!check || !check.meta || check.meta.cwd !== canonical) {
|
|
1068
1317
|
throw new Error("\u79FB\u52A8\u540E\u6821\u9A8C\u5931\u8D25\uFF1A\u4F1A\u8BDD\u5DE5\u4F5C\u76EE\u5F55\u672A\u6B63\u786E\u66F4\u65B0");
|
|
1069
1318
|
}
|
|
@@ -1076,7 +1325,7 @@ function apply(ctx) {
|
|
|
1076
1325
|
} catch (e) {
|
|
1077
1326
|
if (ALREADY_EXISTS_RE.test(String(e && e.message || e))) {
|
|
1078
1327
|
await restore();
|
|
1079
|
-
await relocateLog(meta, newHeader);
|
|
1328
|
+
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");
|
|
1080
1329
|
} else {
|
|
1081
1330
|
await restore();
|
|
1082
1331
|
throw new Error("\u79FB\u52A8\u4F1A\u8BDD\u65E5\u5FD7\u5931\u8D25\uFF1A" + String(e && e.message || e));
|
|
@@ -1122,14 +1371,14 @@ function apply(ctx) {
|
|
|
1122
1371
|
async function reindexRegistry() {
|
|
1123
1372
|
const reg = w;
|
|
1124
1373
|
if (!reg || typeof reg.replaceHeaderIndex !== "function") return false;
|
|
1125
|
-
let
|
|
1374
|
+
let entries = null;
|
|
1126
1375
|
try {
|
|
1127
|
-
|
|
1376
|
+
entries = await persistence.listEntries();
|
|
1128
1377
|
} catch (e) {
|
|
1129
|
-
|
|
1378
|
+
entries = null;
|
|
1130
1379
|
}
|
|
1131
|
-
if (!
|
|
1132
|
-
await reg.replaceHeaderIndex(
|
|
1380
|
+
if (!entries || !Array.isArray(entries)) return false;
|
|
1381
|
+
await reg.replaceHeaderIndex(entries.map((entry) => entry.header));
|
|
1133
1382
|
if (typeof reg.rebuildEntities === "function") reg.rebuildEntities();
|
|
1134
1383
|
return true;
|
|
1135
1384
|
}
|
|
@@ -1165,17 +1414,17 @@ function apply(ctx) {
|
|
|
1165
1414
|
return out;
|
|
1166
1415
|
}
|
|
1167
1416
|
async function allSessionItems(opts = {}) {
|
|
1168
|
-
let
|
|
1417
|
+
let entries = [];
|
|
1169
1418
|
let headersOk = false;
|
|
1170
1419
|
try {
|
|
1171
|
-
|
|
1172
|
-
headersOk = Array.isArray(
|
|
1173
|
-
if (!headersOk)
|
|
1420
|
+
entries = await persistence.listEntries();
|
|
1421
|
+
headersOk = Array.isArray(entries);
|
|
1422
|
+
if (!headersOk) entries = [];
|
|
1174
1423
|
} catch (e) {
|
|
1175
|
-
|
|
1424
|
+
entries = [];
|
|
1176
1425
|
}
|
|
1177
1426
|
let live = ctx.get("sessions");
|
|
1178
|
-
const ids =
|
|
1427
|
+
const ids = entries.map((entry) => entry.id);
|
|
1179
1428
|
if (live) {
|
|
1180
1429
|
try {
|
|
1181
1430
|
live.list().forEach((s) => {
|
|
@@ -1200,18 +1449,27 @@ function apply(ctx) {
|
|
|
1200
1449
|
}
|
|
1201
1450
|
const currentArchived = new Set((await archivedState().catch(() => ({ archivedSessionIds: [] }))).archivedSessionIds || []);
|
|
1202
1451
|
const items = [];
|
|
1203
|
-
const usage = await collectUsage(
|
|
1452
|
+
const usage = await collectUsage(entries);
|
|
1204
1453
|
const statsById = new Map(visibleIds.map((id) => [id, { mtimeMs: usage.mtimeById.get(id), size: usage.sizeById.get(id) }]));
|
|
1205
1454
|
const { cached, missing } = metaCache.partition(visibleIds, statsById);
|
|
1206
|
-
const
|
|
1455
|
+
const persisted = await hydrateFromPersist(missing, statsById);
|
|
1456
|
+
for (const [id, meta] of persisted) metaCache.set(id, statsById.get(id), meta);
|
|
1457
|
+
const stillMissing = missing.filter((id) => !persisted.has(id));
|
|
1458
|
+
const snapshotById = await projectTitles(stillMissing);
|
|
1459
|
+
const decoded = /* @__PURE__ */ new Map();
|
|
1460
|
+
const collectDecoded = (id, meta) => {
|
|
1461
|
+
decoded.set(id, meta);
|
|
1462
|
+
};
|
|
1207
1463
|
const CHUNK = 6;
|
|
1208
1464
|
for (let i = 0; i < visibleIds.length; i += CHUNK) {
|
|
1209
1465
|
const res2 = await Promise.all(visibleIds.slice(i, i + CHUNK).map((id) => resolveOne(id, usage, {
|
|
1210
1466
|
exposeUsage: !!(opts && opts.usage),
|
|
1211
|
-
preloaded: snapshotById.has(id) ? snapshotById.get(id) : void 0
|
|
1467
|
+
preloaded: snapshotById.has(id) ? snapshotById.get(id) : void 0,
|
|
1468
|
+
collectDecoded
|
|
1212
1469
|
})));
|
|
1213
1470
|
for (const it of res2) items.push({ ...it, archived: currentArchived.has(it.sessionId) });
|
|
1214
1471
|
}
|
|
1472
|
+
persistDecoded(decoded, statsById);
|
|
1215
1473
|
let starredSet = /* @__PURE__ */ new Set();
|
|
1216
1474
|
try {
|
|
1217
1475
|
starredSet = new Set((await stars.read()).starredSessionIds);
|
|
@@ -1257,14 +1515,14 @@ function apply(ctx) {
|
|
|
1257
1515
|
}
|
|
1258
1516
|
async function sidebarAuthority() {
|
|
1259
1517
|
const ids = [];
|
|
1260
|
-
let
|
|
1518
|
+
let entries = [];
|
|
1261
1519
|
try {
|
|
1262
|
-
|
|
1520
|
+
entries = await persistence.listEntries();
|
|
1263
1521
|
} catch (e) {
|
|
1264
|
-
|
|
1522
|
+
entries = [];
|
|
1265
1523
|
}
|
|
1266
|
-
if (!Array.isArray(
|
|
1267
|
-
for (const
|
|
1524
|
+
if (!Array.isArray(entries)) entries = [];
|
|
1525
|
+
for (const entry of entries) ids.push(entry.id);
|
|
1268
1526
|
const sessions = ctx.get("sessions");
|
|
1269
1527
|
try {
|
|
1270
1528
|
if (sessions) sessions.list().forEach((session) => {
|
|
@@ -1275,20 +1533,29 @@ function apply(ctx) {
|
|
|
1275
1533
|
}
|
|
1276
1534
|
const store = await readTrashStore();
|
|
1277
1535
|
if (ids.length) {
|
|
1278
|
-
const usage = await collectUsage(
|
|
1536
|
+
const usage = await collectUsage(entries);
|
|
1279
1537
|
const statsById = new Map(ids.map((id) => [id, { mtimeMs: usage.mtimeById.get(id), size: usage.sizeById.get(id) }]));
|
|
1280
1538
|
const { cached, missing } = metaCache.partition(ids, statsById);
|
|
1281
|
-
const
|
|
1539
|
+
const persisted = await hydrateFromPersist(missing, statsById);
|
|
1540
|
+
for (const [id, meta] of persisted) metaCache.set(id, statsById.get(id), meta);
|
|
1541
|
+
const rest = missing.filter((id) => !persisted.has(id));
|
|
1542
|
+
const snapshotById = await projectTitles(rest);
|
|
1543
|
+
const decoded = /* @__PURE__ */ new Map();
|
|
1544
|
+
const collectDecoded = (id, meta) => {
|
|
1545
|
+
decoded.set(id, meta);
|
|
1546
|
+
};
|
|
1282
1547
|
for (const id of ids) {
|
|
1283
|
-
let meta = cached.get(id) || null;
|
|
1548
|
+
let meta = cached.get(id) || persisted.get(id) || null;
|
|
1284
1549
|
if (!meta) {
|
|
1285
1550
|
const snapshot = snapshotById.has(id) ? snapshotById.get(id) : typeof sq.readTitleSnapshot === "function" ? await sq.readTitleSnapshot(id).catch(() => null) : null;
|
|
1286
1551
|
const next = metaFromSnapshot(snapshot);
|
|
1287
1552
|
metaCache.set(id, statsById.get(id), next);
|
|
1553
|
+
if (statsById.get(id)) collectDecoded(id, next);
|
|
1288
1554
|
meta = next;
|
|
1289
1555
|
}
|
|
1290
1556
|
if (meta && meta.title) authorityTitleCache.set(id, String(meta.title));
|
|
1291
1557
|
}
|
|
1558
|
+
persistDecoded(decoded, statsById);
|
|
1292
1559
|
}
|
|
1293
1560
|
return {
|
|
1294
1561
|
titles: Object.fromEntries(authorityTitleCache),
|
|
@@ -1309,14 +1576,14 @@ function apply(ctx) {
|
|
|
1309
1576
|
events = [];
|
|
1310
1577
|
}
|
|
1311
1578
|
} else {
|
|
1312
|
-
const r = await
|
|
1579
|
+
const r = await persistence.readSession(sid, 0);
|
|
1313
1580
|
if (!r || !r.meta) throw new Error("\u627E\u4E0D\u5230\u8BE5\u4F1A\u8BDD\u7684\u8BB0\u5F55\uFF08\u4F1A\u8BDD\u4E0D\u5B58\u5728\uFF09");
|
|
1314
1581
|
meta = r.meta;
|
|
1315
1582
|
events = Array.isArray(r.events) ? r.events : [];
|
|
1316
1583
|
}
|
|
1317
1584
|
let sizeBytes = null;
|
|
1318
1585
|
try {
|
|
1319
|
-
const loc =
|
|
1586
|
+
const loc = persistence.locate(meta);
|
|
1320
1587
|
if (loc && typeof loc.path === "string" && loc.path) {
|
|
1321
1588
|
const st = await stat(loc.path);
|
|
1322
1589
|
if (st && typeof st.size === "number") sizeBytes = st.size;
|
|
@@ -1401,7 +1668,8 @@ function apply(ctx) {
|
|
|
1401
1668
|
const subagentSet = /* @__PURE__ */ new Set();
|
|
1402
1669
|
try {
|
|
1403
1670
|
if (typeof sp.list === "function") {
|
|
1404
|
-
for (const
|
|
1671
|
+
for (const entry of await persistence.listEntries()) {
|
|
1672
|
+
const h = entry.header;
|
|
1405
1673
|
if (String(h.parentSession) !== String(sid)) continue;
|
|
1406
1674
|
if (h.origin === "subagent") subagentSet.add(h.id);
|
|
1407
1675
|
else childrenSet.add(h.id);
|
|
@@ -1438,6 +1706,11 @@ function apply(ctx) {
|
|
|
1438
1706
|
authorityTitleCache.set(String(session.id), event.data.title);
|
|
1439
1707
|
}
|
|
1440
1708
|
}));
|
|
1709
|
+
disposers.push(ctx.webServer.register({
|
|
1710
|
+
kind: "exact",
|
|
1711
|
+
path: "/archived-sessions/capabilities",
|
|
1712
|
+
handler: async (req, res) => json(res, capabilities)
|
|
1713
|
+
}));
|
|
1441
1714
|
disposers.push(ctx.webServer.register({
|
|
1442
1715
|
kind: "exact",
|
|
1443
1716
|
path: "/archived-sessions/list",
|
|
@@ -1448,8 +1721,8 @@ function apply(ctx) {
|
|
|
1448
1721
|
let materialized = /* @__PURE__ */ new Set();
|
|
1449
1722
|
let live = ctx.get("sessions");
|
|
1450
1723
|
try {
|
|
1451
|
-
const
|
|
1452
|
-
materialized = new Set(
|
|
1724
|
+
const entries = await persistence.listEntries();
|
|
1725
|
+
materialized = new Set(entries.map((entry) => entry.id));
|
|
1453
1726
|
} catch (e) {
|
|
1454
1727
|
}
|
|
1455
1728
|
const trashStore = await readTrashStore();
|
|
@@ -1500,12 +1773,12 @@ function apply(ctx) {
|
|
|
1500
1773
|
try {
|
|
1501
1774
|
results.push({ sessionId: sid, ok: true, ...await restoreOne(sid) });
|
|
1502
1775
|
} catch (e) {
|
|
1503
|
-
results.push({ sessionId: sid, ok: false, error: String(e && e.message || e) });
|
|
1776
|
+
results.push({ sessionId: sid, ok: false, code: e && e.code, error: String(e && e.message || e) });
|
|
1504
1777
|
}
|
|
1505
1778
|
}
|
|
1506
1779
|
json(res, { ok: true, restored: results.filter((r) => r.ok).length, results });
|
|
1507
1780
|
} catch (e) {
|
|
1508
|
-
json(res, { ok: false, error: String(e && e.message || e) },
|
|
1781
|
+
json(res, { ok: false, code: e && e.code, error: String(e && e.message || e) }, errorStatus(e));
|
|
1509
1782
|
}
|
|
1510
1783
|
}
|
|
1511
1784
|
}));
|
|
@@ -1544,7 +1817,7 @@ function apply(ctx) {
|
|
|
1544
1817
|
}
|
|
1545
1818
|
json(res, { ok: true, deleted: results.filter((r) => r.ok).length, results });
|
|
1546
1819
|
} catch (e) {
|
|
1547
|
-
json(res, { ok: false, error: String(e && e.message || e) },
|
|
1820
|
+
json(res, { ok: false, code: e && e.code, error: String(e && e.message || e) }, errorStatus(e));
|
|
1548
1821
|
}
|
|
1549
1822
|
}
|
|
1550
1823
|
}));
|
|
@@ -1583,11 +1856,19 @@ function apply(ctx) {
|
|
|
1583
1856
|
try {
|
|
1584
1857
|
const items = await readTrash();
|
|
1585
1858
|
const results = await Promise.all(items.map(async (item) => {
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1859
|
+
if (typeof item.originalPath !== "string" || !item.originalPath) {
|
|
1860
|
+
return { sessionId: item.sessionId, status: "unverified", originalPath: null };
|
|
1861
|
+
}
|
|
1862
|
+
const exists = await stat(item.originalPath).then(() => true).catch(() => false);
|
|
1863
|
+
return { sessionId: item.sessionId, status: exists ? "ok" : "missing", originalPath: item.originalPath };
|
|
1589
1864
|
}));
|
|
1590
|
-
json(res, {
|
|
1865
|
+
json(res, {
|
|
1866
|
+
ok: true,
|
|
1867
|
+
healthy: results.filter((r) => r.status === "ok").length,
|
|
1868
|
+
missing: results.filter((r) => r.status === "missing").length,
|
|
1869
|
+
unverified: results.filter((r) => r.status === "unverified").length,
|
|
1870
|
+
results
|
|
1871
|
+
});
|
|
1591
1872
|
} catch (e) {
|
|
1592
1873
|
json(res, { ok: false, error: String(e && e.message || e) }, errorStatus(e));
|
|
1593
1874
|
}
|
|
@@ -1605,7 +1886,7 @@ function apply(ctx) {
|
|
|
1605
1886
|
metaCache.invalidate(sid);
|
|
1606
1887
|
json(res, out);
|
|
1607
1888
|
} catch (e) {
|
|
1608
|
-
json(res, { ok: false, error: String(e && e.message || e) },
|
|
1889
|
+
json(res, { ok: false, code: e && e.code, error: String(e && e.message || e) }, errorStatus(e));
|
|
1609
1890
|
}
|
|
1610
1891
|
}
|
|
1611
1892
|
}));
|
|
@@ -1619,9 +1900,11 @@ function apply(ctx) {
|
|
|
1619
1900
|
if (!sid) return json(res, { ok: false, error: "missing sessionId" }, 400);
|
|
1620
1901
|
const out = await purgeFromTrash(sid);
|
|
1621
1902
|
metaCache.invalidate(sid);
|
|
1903
|
+
titleIndex.remove([sid]).catch(() => {
|
|
1904
|
+
});
|
|
1622
1905
|
json(res, out);
|
|
1623
1906
|
} catch (e) {
|
|
1624
|
-
json(res, { ok: false, error: String(e && e.message || e) },
|
|
1907
|
+
json(res, { ok: false, code: e && e.code, error: String(e && e.message || e) }, errorStatus(e));
|
|
1625
1908
|
}
|
|
1626
1909
|
}
|
|
1627
1910
|
}));
|
|
@@ -1638,13 +1921,15 @@ function apply(ctx) {
|
|
|
1638
1921
|
try {
|
|
1639
1922
|
results.push({ sessionId: sid, ok: true, ...await purgeFromTrash(sid) });
|
|
1640
1923
|
metaCache.invalidate(sid);
|
|
1924
|
+
titleIndex.remove([sid]).catch(() => {
|
|
1925
|
+
});
|
|
1641
1926
|
} catch (e) {
|
|
1642
1927
|
results.push({ sessionId: sid, ok: false, error: String(e && e.message || e) });
|
|
1643
1928
|
}
|
|
1644
1929
|
}
|
|
1645
1930
|
json(res, { ok: true, purged: results.filter((r) => r.ok).length, results });
|
|
1646
1931
|
} catch (e) {
|
|
1647
|
-
json(res, { ok: false, error: String(e && e.message || e) },
|
|
1932
|
+
json(res, { ok: false, code: e && e.code, error: String(e && e.message || e) }, errorStatus(e));
|
|
1648
1933
|
}
|
|
1649
1934
|
}
|
|
1650
1935
|
}));
|
|
@@ -1686,7 +1971,7 @@ function apply(ctx) {
|
|
|
1686
1971
|
const url = new URL(req.url, "http://localhost");
|
|
1687
1972
|
const sid = url.searchParams.get("sessionId");
|
|
1688
1973
|
requireSessionId(sid);
|
|
1689
|
-
const r = await
|
|
1974
|
+
const r = await persistence.readSession(sid, 0);
|
|
1690
1975
|
if (!r || !r.meta) {
|
|
1691
1976
|
const error = new Error("\u65E0\u6CD5\u8BFB\u53D6\u8BE5\u4F1A\u8BDD\u7684\u65E5\u5FD7");
|
|
1692
1977
|
error.status = 404;
|
|
@@ -1744,7 +2029,7 @@ function apply(ctx) {
|
|
|
1744
2029
|
}
|
|
1745
2030
|
json(res, { sessionId: sid, ...moved });
|
|
1746
2031
|
} catch (e) {
|
|
1747
|
-
json(res, { ok: false, error: String(e && e.message || e) },
|
|
2032
|
+
json(res, { ok: false, code: e && e.code, error: String(e && e.message || e) }, errorStatus(e));
|
|
1748
2033
|
}
|
|
1749
2034
|
}
|
|
1750
2035
|
}));
|
|
@@ -1819,8 +2104,16 @@ function apply(ctx) {
|
|
|
1819
2104
|
const patch = {};
|
|
1820
2105
|
if (body && Object.prototype.hasOwnProperty.call(body, "inactiveDays")) patch.inactiveDays = body.inactiveDays;
|
|
1821
2106
|
if (body && Object.prototype.hasOwnProperty.call(body, "skipStarred")) patch.skipStarred = body.skipStarred;
|
|
1822
|
-
const
|
|
1823
|
-
const
|
|
2107
|
+
const isPatch = Object.keys(patch).length > 0;
|
|
2108
|
+
const settings = isPatch ? await autoArchive.update(patch) : (await autoArchive.read()).settings;
|
|
2109
|
+
let sweep;
|
|
2110
|
+
if (isPatch) {
|
|
2111
|
+
sweep = await autoArchiveSweep();
|
|
2112
|
+
} else {
|
|
2113
|
+
void autoArchiveSweep().catch(() => {
|
|
2114
|
+
});
|
|
2115
|
+
sweep = { triggered: true };
|
|
2116
|
+
}
|
|
1824
2117
|
const store = await autoArchive.read();
|
|
1825
2118
|
json(res, {
|
|
1826
2119
|
ok: true,
|