dsh-archived-chats 1.3.2 → 1.3.3
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.zh-CN.md +6 -6
- package/docs/ARCHITECTURE.md +14 -12
- package/lib/client.js +25 -98
- package/lib/index.js +4 -182
- package/lib/insights.js +9 -6
- package/lib/recycle.js +26 -0
- package/lib/types/client/index.d.ts +3 -56
- package/lib/types/index.d.ts +1 -55
- package/package.json +1 -4
- package/lib/history-restore.js +0 -297
- package/lib/history.js +0 -364
- package/lib/legacy-recycle.js +0 -411
package/lib/index.js
CHANGED
|
@@ -88,14 +88,6 @@ import { createRetentionStore } from './retention.js';
|
|
|
88
88
|
import { startRetentionScheduler } from './auto-retention.js';
|
|
89
89
|
import { createRetentionService } from './retention-service.js';
|
|
90
90
|
import { projectLineage } from './lineage.js';
|
|
91
|
-
import { createHistoryService } from './history.js';
|
|
92
|
-
import { createHistoryRestoreService } from './history-restore.js';
|
|
93
|
-
import {
|
|
94
|
-
createLegacyRecycleService,
|
|
95
|
-
createLegacyRecycleStore,
|
|
96
|
-
createRecycleHub,
|
|
97
|
-
createUnifiedTrashStore,
|
|
98
|
-
} from './legacy-recycle.js';
|
|
99
91
|
import { createWorkspaceBulkArchiveService } from './workspace-bulk-archive.js';
|
|
100
92
|
import { resolvePersistenceCompat } from './persistence-compat.js';
|
|
101
93
|
import { atomicWriteFile } from './durable.js';
|
|
@@ -820,7 +812,7 @@ async function deleteSession(ctx, registry, persistence, id, titleCache, statsSe
|
|
|
820
812
|
//#endregion
|
|
821
813
|
|
|
822
814
|
//#region routes
|
|
823
|
-
function registerRoutes(ctx, webServer, registry, persistence, titleCache, metadataStore, statsService, pendingStore, trashStore, snapshotStore, retentionStore,
|
|
815
|
+
function registerRoutes(ctx, webServer, registry, persistence, titleCache, metadataStore, statsService, pendingStore, trashStore, snapshotStore, retentionStore, lifecycle, importTokens, restoreAdapter) {
|
|
824
816
|
const projectedMessages = createProjectedMessageCache((id) => persistence.inspect(id));
|
|
825
817
|
const listInsightSessions = async () => {
|
|
826
818
|
const pending = await pendingStore.load();
|
|
@@ -849,15 +841,6 @@ function registerRoutes(ctx, webServer, registry, persistence, titleCache, metad
|
|
|
849
841
|
snapshotStore,
|
|
850
842
|
listSessions: listInsightSessions,
|
|
851
843
|
});
|
|
852
|
-
const historyService = createHistoryService({
|
|
853
|
-
registry,
|
|
854
|
-
persistence,
|
|
855
|
-
sessions: ctx.get('sessions'),
|
|
856
|
-
metadataStore,
|
|
857
|
-
trashStore,
|
|
858
|
-
snapshotStore,
|
|
859
|
-
lifecycle,
|
|
860
|
-
});
|
|
861
844
|
let workspaceBulkArchiveService = null;
|
|
862
845
|
let workspaceArchiveUnavailableLogged = false;
|
|
863
846
|
const workspaceArchiveSecret = randomUUID();
|
|
@@ -899,9 +882,8 @@ function registerRoutes(ctx, webServer, registry, persistence, titleCache, metad
|
|
|
899
882
|
statsService.invalidate(ids);
|
|
900
883
|
projectedMessages.invalidate(ids);
|
|
901
884
|
insightsService.invalidate();
|
|
902
|
-
historyService.invalidate();
|
|
903
885
|
};
|
|
904
|
-
const
|
|
886
|
+
const recycleService = createRecycleService({
|
|
905
887
|
registry,
|
|
906
888
|
persistence,
|
|
907
889
|
attachments: ctx.get('attachments'),
|
|
@@ -935,50 +917,15 @@ function registerRoutes(ctx, webServer, registry, persistence, titleCache, metad
|
|
|
935
917
|
invalidate: invalidateRecycleCaches,
|
|
936
918
|
logger: ctx.logger,
|
|
937
919
|
});
|
|
938
|
-
const historyRestoreService = createHistoryRestoreService({
|
|
939
|
-
snapshotStore,
|
|
940
|
-
persistence,
|
|
941
|
-
attachments: ctx.get('attachments'),
|
|
942
|
-
registry,
|
|
943
|
-
metadataStore,
|
|
944
|
-
lifecycle,
|
|
945
|
-
invalidate: (restoredId, sourceId) => {
|
|
946
|
-
titleCache.delete(restoredId);
|
|
947
|
-
statsService.invalidate([restoredId]);
|
|
948
|
-
projectedMessages.invalidate([restoredId, sourceId]);
|
|
949
|
-
insightsService.invalidate();
|
|
950
|
-
historyService.invalidate();
|
|
951
|
-
},
|
|
952
|
-
logger: ctx.logger,
|
|
953
|
-
});
|
|
954
|
-
const legacyRecycleService = createLegacyRecycleService({
|
|
955
|
-
store: legacyRecycleStore,
|
|
956
|
-
historyService,
|
|
957
|
-
historyRestoreService,
|
|
958
|
-
snapshotStore,
|
|
959
|
-
trashStore,
|
|
960
|
-
lifecycle,
|
|
961
|
-
logger: ctx.logger,
|
|
962
|
-
});
|
|
963
|
-
const recycleService = createRecycleHub({
|
|
964
|
-
recycleService: regularRecycleService,
|
|
965
|
-
legacyRecycleService,
|
|
966
|
-
});
|
|
967
|
-
const unifiedTrashStore = createUnifiedTrashStore({ trashStore, legacyRecycleService });
|
|
968
920
|
const retentionService = createRetentionService({
|
|
969
921
|
insightsService,
|
|
970
922
|
retentionStore,
|
|
971
|
-
trashStore
|
|
923
|
+
trashStore,
|
|
972
924
|
snapshotStore,
|
|
973
925
|
recycleService,
|
|
974
926
|
lifecycle,
|
|
975
927
|
});
|
|
976
928
|
|
|
977
|
-
const historyError = (res, error, fallback) => {
|
|
978
|
-
const status = error?.status ?? (error instanceof SyntaxError || error instanceof TypeError ? 400 : 500);
|
|
979
|
-
send(res, status, { ok: false, error: error?.code ?? fallback });
|
|
980
|
-
};
|
|
981
|
-
|
|
982
929
|
const workspaceArchiveError = (res, error, fallback) => {
|
|
983
930
|
const status = error?.status ?? (error instanceof SyntaxError || error instanceof TypeError ? 400 : 500);
|
|
984
931
|
send(res, status, { ok: false, error: error?.code ?? fallback });
|
|
@@ -1036,130 +983,6 @@ function registerRoutes(ctx, webServer, registry, persistence, titleCache, metad
|
|
|
1036
983
|
},
|
|
1037
984
|
}), 'archived-chats: workspace archive apply route');
|
|
1038
985
|
|
|
1039
|
-
ctx.effect(() => webServer.register({
|
|
1040
|
-
kind: 'exact',
|
|
1041
|
-
path: `${ROUTE_PREFIX}/history/capture`,
|
|
1042
|
-
handler: async (req, res) => {
|
|
1043
|
-
if (!guard(req, res)) return;
|
|
1044
|
-
send(res, 410, { ok: false, error: 'history-capture-retired' });
|
|
1045
|
-
},
|
|
1046
|
-
}), 'archived-chats: history capture route');
|
|
1047
|
-
|
|
1048
|
-
ctx.effect(() => webServer.register({
|
|
1049
|
-
kind: 'exact',
|
|
1050
|
-
path: `${ROUTE_PREFIX}/history`,
|
|
1051
|
-
handler: async (req, res) => {
|
|
1052
|
-
if (req.method !== 'GET') { send(res, 405, { ok: false, error: 'method-not-allowed' }); return; }
|
|
1053
|
-
try { send(res, 200, { ok: true, ...(await historyService.list()) }); }
|
|
1054
|
-
catch (error) { historyError(res, error, 'history-list-failed'); }
|
|
1055
|
-
},
|
|
1056
|
-
}), 'archived-chats: history route');
|
|
1057
|
-
|
|
1058
|
-
ctx.effect(() => webServer.register({
|
|
1059
|
-
kind: 'exact',
|
|
1060
|
-
path: `${ROUTE_PREFIX}/history/delete`,
|
|
1061
|
-
handler: async (req, res) => {
|
|
1062
|
-
if (!guard(req, res)) return;
|
|
1063
|
-
try {
|
|
1064
|
-
const body = await readBody(req, 64 * 1024);
|
|
1065
|
-
if (!exactBody(body, ['snapshotId']) || typeof body.snapshotId !== 'string' || body.snapshotId === '') {
|
|
1066
|
-
send(res, 400, { ok: false, error: 'history-delete-invalid' });
|
|
1067
|
-
return;
|
|
1068
|
-
}
|
|
1069
|
-
const result = await historyService.deleteVersion(body.snapshotId);
|
|
1070
|
-
insightsService.invalidate();
|
|
1071
|
-
send(res, 200, { ok: true, ...result });
|
|
1072
|
-
} catch (error) { historyError(res, error, 'history-delete-failed'); }
|
|
1073
|
-
},
|
|
1074
|
-
}), 'archived-chats: history delete route');
|
|
1075
|
-
|
|
1076
|
-
ctx.effect(() => webServer.register({
|
|
1077
|
-
kind: 'exact',
|
|
1078
|
-
path: `${ROUTE_PREFIX}/history/delete-all`,
|
|
1079
|
-
handler: async (req, res) => {
|
|
1080
|
-
if (!guard(req, res)) return;
|
|
1081
|
-
try {
|
|
1082
|
-
const body = await readBody(req, 64 * 1024);
|
|
1083
|
-
if (!exactBody(body, [])) {
|
|
1084
|
-
send(res, 400, { ok: false, error: 'history-delete-all-invalid' });
|
|
1085
|
-
return;
|
|
1086
|
-
}
|
|
1087
|
-
const result = await historyService.clear();
|
|
1088
|
-
if (result.deleted.length > 0) insightsService.invalidate();
|
|
1089
|
-
const status = result.failed.length > 0 && result.deleted.length === 0 ? 409 : 200;
|
|
1090
|
-
send(res, status, { ok: result.failed.length === 0, ...result });
|
|
1091
|
-
} catch (error) { historyError(res, error, 'history-delete-all-failed'); }
|
|
1092
|
-
},
|
|
1093
|
-
}), 'archived-chats: history delete all route');
|
|
1094
|
-
|
|
1095
|
-
ctx.effect(() => webServer.register({
|
|
1096
|
-
kind: 'exact',
|
|
1097
|
-
path: `${ROUTE_PREFIX}/history/preview`,
|
|
1098
|
-
handler: async (req, res) => {
|
|
1099
|
-
if (!guard(req, res)) return;
|
|
1100
|
-
try {
|
|
1101
|
-
const body = await readBody(req, 64 * 1024);
|
|
1102
|
-
if (!exactBody(body, ['snapshotId', 'offset', 'limit']) || typeof body.snapshotId !== 'string') {
|
|
1103
|
-
send(res, 400, { ok: false, error: 'history-preview-invalid' });
|
|
1104
|
-
return;
|
|
1105
|
-
}
|
|
1106
|
-
send(res, 200, { ok: true, ...(await historyService.preview(body.snapshotId, { offset: body.offset, limit: body.limit })) });
|
|
1107
|
-
} catch (error) { historyError(res, error, 'history-preview-failed'); }
|
|
1108
|
-
},
|
|
1109
|
-
}), 'archived-chats: history preview route');
|
|
1110
|
-
|
|
1111
|
-
ctx.effect(() => webServer.register({
|
|
1112
|
-
kind: 'exact',
|
|
1113
|
-
path: `${ROUTE_PREFIX}/history/preview/image`,
|
|
1114
|
-
handler: async (req, res) => {
|
|
1115
|
-
if (!guard(req, res)) return;
|
|
1116
|
-
const request = requestAbort(req);
|
|
1117
|
-
try {
|
|
1118
|
-
const body = await readBody(req, 64 * 1024);
|
|
1119
|
-
if (!exactBody(body, ['snapshotId', 'attachment']) || typeof body.snapshotId !== 'string'
|
|
1120
|
-
|| body.attachment === null || typeof body.attachment !== 'object' || Array.isArray(body.attachment)) {
|
|
1121
|
-
send(res, 400, { ok: false, error: 'history-preview-image-invalid' });
|
|
1122
|
-
return;
|
|
1123
|
-
}
|
|
1124
|
-
const image = await historyService.readImage(body.snapshotId, body.attachment, request.signal);
|
|
1125
|
-
sendImage(res, { data: image.data, ref: { mediaType: image.mediaType } });
|
|
1126
|
-
} catch (error) {
|
|
1127
|
-
if (!request.signal.aborted) historyError(res, error, 'history-preview-image-failed');
|
|
1128
|
-
} finally { request.dispose(); }
|
|
1129
|
-
},
|
|
1130
|
-
}), 'archived-chats: history preview image route');
|
|
1131
|
-
|
|
1132
|
-
ctx.effect(() => webServer.register({
|
|
1133
|
-
kind: 'exact',
|
|
1134
|
-
path: `${ROUTE_PREFIX}/history/restore/preview`,
|
|
1135
|
-
handler: async (req, res) => {
|
|
1136
|
-
if (!guard(req, res)) return;
|
|
1137
|
-
try {
|
|
1138
|
-
const body = await readBody(req, 64 * 1024);
|
|
1139
|
-
if (!exactBody(body, ['snapshotId']) || typeof body.snapshotId !== 'string' || body.snapshotId === '') {
|
|
1140
|
-
send(res, 400, { ok: false, error: 'history-restore-preview-invalid' });
|
|
1141
|
-
return;
|
|
1142
|
-
}
|
|
1143
|
-
send(res, 200, { ok: true, ...(await historyRestoreService.prepare(body.snapshotId)) });
|
|
1144
|
-
} catch (error) { historyError(res, error, 'history-restore-preview-failed'); }
|
|
1145
|
-
},
|
|
1146
|
-
}), 'archived-chats: history restore preview route');
|
|
1147
|
-
|
|
1148
|
-
ctx.effect(() => webServer.register({
|
|
1149
|
-
kind: 'exact',
|
|
1150
|
-
path: `${ROUTE_PREFIX}/history/restore`,
|
|
1151
|
-
handler: async (req, res) => {
|
|
1152
|
-
if (!guard(req, res)) return;
|
|
1153
|
-
try {
|
|
1154
|
-
const body = await readBody(req, 64 * 1024);
|
|
1155
|
-
if (!exactBody(body, ['token', 'nonce']) || typeof body.token !== 'string' || typeof body.nonce !== 'string') {
|
|
1156
|
-
send(res, 400, { ok: false, error: 'history-restore-confirmation-invalid' });
|
|
1157
|
-
return;
|
|
1158
|
-
}
|
|
1159
|
-
send(res, 200, { ok: true, ...(await historyRestoreService.restore(body.token, body.nonce)) });
|
|
1160
|
-
} catch (error) { historyError(res, error, 'history-restore-failed'); }
|
|
1161
|
-
},
|
|
1162
|
-
}), 'archived-chats: history restore route');
|
|
1163
986
|
ctx.effect(() => webServer.register({
|
|
1164
987
|
kind: 'exact',
|
|
1165
988
|
path: `${ROUTE_PREFIX}/state`,
|
|
@@ -1805,7 +1628,6 @@ export function apply(ctx) {
|
|
|
1805
1628
|
const pendingStore = createPendingStore();
|
|
1806
1629
|
const trashStore = createTrashStore({ path: join(pluginDataRoot, 'trash.json') });
|
|
1807
1630
|
const retentionStore = createRetentionStore({ path: join(pluginDataRoot, 'retention.json') });
|
|
1808
|
-
const legacyRecycleStore = createLegacyRecycleStore({ path: join(pluginDataRoot, 'legacy-recycle.json') });
|
|
1809
1631
|
const importTokens = createImportTokenStore();
|
|
1810
1632
|
const lifecycle = createLifecycleQueue();
|
|
1811
1633
|
let statsService;
|
|
@@ -1830,7 +1652,7 @@ export function apply(ctx) {
|
|
|
1830
1652
|
metadataStore,
|
|
1831
1653
|
tempRoot: join(process.env.DSH_HOME ?? join(homedir(), '.dsh'), 'plugin-data', 'archived-chats', 'imports'),
|
|
1832
1654
|
});
|
|
1833
|
-
const { recycleService, retentionService } = registerRoutes(ctx, webServer, registry, persistence, titleCache, metadataStore, statsService, pendingStore, trashStore, snapshotStore, retentionStore,
|
|
1655
|
+
const { recycleService, retentionService } = registerRoutes(ctx, webServer, registry, persistence, titleCache, metadataStore, statsService, pendingStore, trashStore, snapshotStore, retentionStore, lifecycle, importTokens, restoreAdapter);
|
|
1834
1656
|
ctx.effect(() => startRetentionScheduler({
|
|
1835
1657
|
recover: () => recycleService.recoverStartup({ legacyPendingPath: pendingFilePath() }),
|
|
1836
1658
|
run: (options) => retentionService.runAutomatic(options),
|
package/lib/insights.js
CHANGED
|
@@ -102,7 +102,9 @@ export function createInsightsService({
|
|
|
102
102
|
const snapshots = [];
|
|
103
103
|
for (const item of inventory.valid) {
|
|
104
104
|
if (item === null || typeof item !== 'object'
|
|
105
|
-
|| typeof item.snapshotId !== 'string' || item.snapshotId === ''
|
|
105
|
+
|| typeof item.snapshotId !== 'string' || item.snapshotId === '') throw authorityUnavailable();
|
|
106
|
+
if (!activeSnapshotIds.has(item.snapshotId)) continue;
|
|
107
|
+
if (item === null || typeof item !== 'object'
|
|
106
108
|
|| typeof item.sessionId !== 'string' || item.sessionId === ''
|
|
107
109
|
|| typeof item.createdAt !== 'string'
|
|
108
110
|
|| !Number.isSafeInteger(item.totalBytes) || item.totalBytes < 0
|
|
@@ -125,18 +127,19 @@ export function createInsightsService({
|
|
|
125
127
|
sessionBytes: item.sessionBytes,
|
|
126
128
|
attachmentCount: item.attachmentCount,
|
|
127
129
|
status: 'ready',
|
|
128
|
-
active:
|
|
130
|
+
active: true,
|
|
129
131
|
});
|
|
130
132
|
}
|
|
131
133
|
for (const item of inventory.degraded) {
|
|
132
134
|
if (item === null || typeof item !== 'object'
|
|
133
|
-
|| typeof item.snapshotId !== 'string' || item.snapshotId === ''
|
|
134
|
-
|
|
135
|
+
|| typeof item.snapshotId !== 'string' || item.snapshotId === '') throw authorityUnavailable();
|
|
136
|
+
if (!activeSnapshotIds.has(item.snapshotId)) continue;
|
|
137
|
+
if (typeof item.code !== 'string' || item.code === '') throw authorityUnavailable();
|
|
135
138
|
snapshots.push({
|
|
136
139
|
snapshotId: item.snapshotId,
|
|
137
140
|
status: 'degraded',
|
|
138
141
|
code: item.code,
|
|
139
|
-
active:
|
|
142
|
+
active: true,
|
|
140
143
|
});
|
|
141
144
|
}
|
|
142
145
|
|
|
@@ -152,7 +155,7 @@ export function createInsightsService({
|
|
|
152
155
|
totalMeasuredBytes: sessionBytes + snapshotBytes,
|
|
153
156
|
duplicateSnapshotBytes,
|
|
154
157
|
sessionUnavailableCount: sessions.filter((row) => row.status === 'unavailable').length,
|
|
155
|
-
degradedSnapshotCount:
|
|
158
|
+
degradedSnapshotCount: snapshots.filter((row) => row.status === 'degraded').length,
|
|
156
159
|
},
|
|
157
160
|
sessions,
|
|
158
161
|
snapshots,
|
package/lib/recycle.js
CHANGED
|
@@ -544,6 +544,32 @@ export function createRecycleService({
|
|
|
544
544
|
}
|
|
545
545
|
}
|
|
546
546
|
}
|
|
547
|
+
|
|
548
|
+
// Historical versions could leave published snapshots that were never
|
|
549
|
+
// referenced by the durable recycle catalog. Re-read both authorities
|
|
550
|
+
// after pending migration because migration may create a protection
|
|
551
|
+
// snapshot which must survive this startup pass.
|
|
552
|
+
if (typeof snapshotStore.inventory === 'function') {
|
|
553
|
+
await lifecycle.run(async () => {
|
|
554
|
+
const currentRecords = await loadedTrash();
|
|
555
|
+
const protectedIds = new Set(
|
|
556
|
+
[...currentRecords.values()]
|
|
557
|
+
.map((record) => record?.snapshotId)
|
|
558
|
+
.filter((snapshotId) => typeof snapshotId === 'string' && snapshotId !== ''),
|
|
559
|
+
);
|
|
560
|
+
const inventory = await snapshotStore.inventory();
|
|
561
|
+
const candidates = [...(inventory?.valid ?? []), ...(inventory?.degraded ?? [])]
|
|
562
|
+
.map((item) => item?.snapshotId)
|
|
563
|
+
.filter((snapshotId) => typeof snapshotId === 'string' && snapshotId !== '' && !protectedIds.has(snapshotId));
|
|
564
|
+
for (const snapshotId of new Set(candidates)) {
|
|
565
|
+
try {
|
|
566
|
+
await snapshotStore.remove(snapshotId);
|
|
567
|
+
} catch (cause) {
|
|
568
|
+
logger?.warn?.('archived-chats: legacy snapshot cleanup failed: ' + stableCode(cause, 'snapshot-cleanup-failed'));
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
});
|
|
572
|
+
}
|
|
547
573
|
return { status: 'ready' };
|
|
548
574
|
}
|
|
549
575
|
|
|
@@ -3,10 +3,9 @@
|
|
|
3
3
|
* chooser and one-confirmation bulk archive flow. The flow stays inside the
|
|
4
4
|
* plugin-owned Settings surface and requires no workspace-row extension slot.
|
|
5
5
|
* The searchable manager includes Archived, Recycle Bin, Storage & Retention,
|
|
6
|
-
* and Origins & Branches views.
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* view provides scoped read-only previews, original/snapshot restore, guarded
|
|
6
|
+
* and Origins & Branches views. Removing an archived chat moves it to
|
|
7
|
+
* recoverable trash and exposes immediate Undo. The recycle view provides
|
|
8
|
+
* scoped read-only previews, original/snapshot restore, guarded
|
|
10
9
|
* permanent purge and empty operations, degraded-state warnings, responsive
|
|
11
10
|
* rows, and accessible confirmation dialogs, all localized in English and 中文.
|
|
12
11
|
*/
|
|
@@ -15,10 +14,6 @@ export type RecycleLiveDisposition = 'cold' | 'disposed' | 'parked';
|
|
|
15
14
|
|
|
16
15
|
export interface RecycleSessionRow {
|
|
17
16
|
sessionId: string;
|
|
18
|
-
sourceKind?: 'legacy-snapshot';
|
|
19
|
-
legacySnapshotId?: string;
|
|
20
|
-
sourceSessionId?: string | null;
|
|
21
|
-
restorable?: boolean;
|
|
22
17
|
state: RecycleRecordState;
|
|
23
18
|
trashedAt: string;
|
|
24
19
|
purgeRequestedAt: string | null;
|
|
@@ -52,45 +47,6 @@ export interface RetentionPolicy {
|
|
|
52
47
|
recycleAutoDelete: boolean;
|
|
53
48
|
}
|
|
54
49
|
|
|
55
|
-
export type HistoryScope = 'archived' | 'recycled' | 'history-only';
|
|
56
|
-
export type HistoryVersionState = 'history' | 'recycle-protection';
|
|
57
|
-
|
|
58
|
-
export interface HistoryVersion {
|
|
59
|
-
snapshotId: string;
|
|
60
|
-
createdAt: string;
|
|
61
|
-
totalBytes: number;
|
|
62
|
-
attachmentCount: number;
|
|
63
|
-
state: HistoryVersionState;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export interface HistorySession {
|
|
67
|
-
sessionId: string;
|
|
68
|
-
title: string | null;
|
|
69
|
-
workspace: { id: string | null; title: string | null } | null;
|
|
70
|
-
scope: HistoryScope;
|
|
71
|
-
versions: HistoryVersion[];
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export interface HistoryResponse {
|
|
75
|
-
generatedAt: string;
|
|
76
|
-
sessions: HistorySession[];
|
|
77
|
-
degraded: Array<{ snapshotId: string; code: string }>;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export interface HistoryRestoreResult {
|
|
81
|
-
restored: string[];
|
|
82
|
-
sourceSessionId: string;
|
|
83
|
-
snapshotId: string;
|
|
84
|
-
warnings: Array<{ id: string; reason: string }>;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
export interface HistoryDeleteResult {
|
|
88
|
-
deleted: string[];
|
|
89
|
-
freedBytes: number;
|
|
90
|
-
skipped?: Array<{ snapshotId: string; reason: string }>;
|
|
91
|
-
failed?: Array<{ snapshotId: string; reason: string }>;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
50
|
export interface StorageInsightsSummary {
|
|
95
51
|
sessionBytes: number;
|
|
96
52
|
snapshotBytes: number;
|
|
@@ -140,15 +96,6 @@ export interface StorageInsights {
|
|
|
140
96
|
}
|
|
141
97
|
|
|
142
98
|
export type RetentionCandidate =
|
|
143
|
-
| {
|
|
144
|
-
key: string;
|
|
145
|
-
action: 'delete-snapshot';
|
|
146
|
-
reason: 'history-count' | 'snapshot-age' | 'snapshot-quota';
|
|
147
|
-
snapshotId: string;
|
|
148
|
-
sessionId: string;
|
|
149
|
-
createdAt: string;
|
|
150
|
-
bytes: number;
|
|
151
|
-
}
|
|
152
99
|
| {
|
|
153
100
|
key: string;
|
|
154
101
|
action: 'purge-trash';
|
package/lib/types/index.d.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Host loader entry — registers the `/plugins/dsh-archived-chats/*` routes
|
|
3
3
|
* (state, stats, insights, retention/policy, retention/preview, retention/apply,
|
|
4
|
-
* lineage,
|
|
5
|
-
* history/restore/preview, history/restore, history/delete, history/delete-all,
|
|
4
|
+
* lineage,
|
|
6
5
|
* workspace-archive/workspaces, workspace-archive/preview,
|
|
7
6
|
* workspace-archive/apply,
|
|
8
7
|
* preview, preview/image, search,
|
|
@@ -38,10 +37,6 @@ export type RecycleLiveDisposition = 'cold' | 'disposed' | 'parked';
|
|
|
38
37
|
|
|
39
38
|
export interface RecycleSessionRow {
|
|
40
39
|
sessionId: string;
|
|
41
|
-
sourceKind?: 'legacy-snapshot';
|
|
42
|
-
legacySnapshotId?: string;
|
|
43
|
-
sourceSessionId?: string | null;
|
|
44
|
-
restorable?: boolean;
|
|
45
40
|
state: RecycleRecordState;
|
|
46
41
|
trashedAt: string;
|
|
47
42
|
purgeRequestedAt: string | null;
|
|
@@ -75,46 +70,6 @@ export interface RetentionPolicy {
|
|
|
75
70
|
recycleAutoDelete: boolean;
|
|
76
71
|
}
|
|
77
72
|
|
|
78
|
-
export type HistoryScope = 'archived' | 'recycled' | 'history-only';
|
|
79
|
-
export type HistoryVersionState = 'history' | 'recycle-protection';
|
|
80
|
-
|
|
81
|
-
export interface HistoryVersion {
|
|
82
|
-
snapshotId: string;
|
|
83
|
-
createdAt: string;
|
|
84
|
-
totalBytes: number;
|
|
85
|
-
attachmentCount: number;
|
|
86
|
-
state: HistoryVersionState;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export interface HistorySession {
|
|
90
|
-
sessionId: string;
|
|
91
|
-
title: string | null;
|
|
92
|
-
workspace: { id: string | null; title: string | null } | null;
|
|
93
|
-
scope: HistoryScope;
|
|
94
|
-
versions: HistoryVersion[];
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
export interface HistoryResponse {
|
|
98
|
-
generatedAt: string;
|
|
99
|
-
sessions: HistorySession[];
|
|
100
|
-
degraded: Array<{ snapshotId: string; code: string }>;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
export interface HistoryRestoreResult {
|
|
104
|
-
restored: string[];
|
|
105
|
-
sourceSessionId: string;
|
|
106
|
-
snapshotId: string;
|
|
107
|
-
warnings: Array<{ id: string; reason: string }>;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/** A degraded version carries no readable bytes but can still be reclaimed. */
|
|
111
|
-
export interface HistoryDeleteResult {
|
|
112
|
-
deleted: string[];
|
|
113
|
-
freedBytes: number;
|
|
114
|
-
skipped?: Array<{ snapshotId: string; reason: string }>;
|
|
115
|
-
failed?: Array<{ snapshotId: string; reason: string }>;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
73
|
export interface StorageInsightsSummary {
|
|
119
74
|
sessionBytes: number;
|
|
120
75
|
snapshotBytes: number;
|
|
@@ -164,15 +119,6 @@ export interface StorageInsights {
|
|
|
164
119
|
}
|
|
165
120
|
|
|
166
121
|
export type RetentionCandidate =
|
|
167
|
-
| {
|
|
168
|
-
key: string;
|
|
169
|
-
action: 'delete-snapshot';
|
|
170
|
-
reason: 'history-count' | 'snapshot-age' | 'snapshot-quota';
|
|
171
|
-
snapshotId: string;
|
|
172
|
-
sessionId: string;
|
|
173
|
-
createdAt: string;
|
|
174
|
-
bytes: number;
|
|
175
|
-
}
|
|
176
122
|
| {
|
|
177
123
|
key: string;
|
|
178
124
|
action: 'purge-trash';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-archived-chats",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.3",
|
|
4
4
|
"description": "DeepSeek Harness 会话档案:从设置中安全归档工作区会话,按工作区浏览和全文搜索归档聊天,原生只读预览对话、工具活动和已存储图片,管理标签备注与 ZIP 备份恢复;提供统一回收站、可选自动清理、空间分账和来源与分支,已有快照直接进入回收站。所有数据留在本机。 Session Archive for DeepSeek Harness: safely archive workspace chats from Settings; browse and full-text search archived chats; preview conversations, tool activity, and stored images read-only; manage tags, notes, and ZIP backup restore; provide a unified Recycle Bin, optional automatic cleanup, storage accounting, and Origins & Branches, with existing snapshots shown directly in the Recycle Bin. All data stays local.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Ultronen",
|
|
@@ -63,9 +63,6 @@
|
|
|
63
63
|
"lib/auto-retention.js",
|
|
64
64
|
"lib/retention-service.js",
|
|
65
65
|
"lib/lineage.js",
|
|
66
|
-
"lib/history.js",
|
|
67
|
-
"lib/history-restore.js",
|
|
68
|
-
"lib/legacy-recycle.js",
|
|
69
66
|
"lib/workspace-bulk-archive.js",
|
|
70
67
|
"lib/persistence-compat.js",
|
|
71
68
|
"lib/search.js",
|