dsh-sessions-manager 3.2.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 ADDED
@@ -0,0 +1,1159 @@
1
+ // src/index.js
2
+ import { mkdir, realpath, rename, stat, unlink, writeFile } from "node:fs/promises";
3
+ import { basename, dirname, isAbsolute, join } from "node:path";
4
+ import { readFileSync as readFileSync2 } from "node:fs";
5
+ import { homedir } from "node:os";
6
+
7
+ // src/zstd-frame.js
8
+ import zlib from "node:zlib";
9
+ import { readFileSync, writeFileSync } from "node:fs";
10
+ var ZSTD_MAGIC = 4247762216;
11
+ 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 (_) {
20
+ }
21
+ }
22
+ return starts;
23
+ }
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);
30
+ const text = zlib.zstdDecompressSync(frame0).toString("utf8");
31
+ const nl = text.indexOf("\n");
32
+ const line = nl >= 0 ? text.slice(0, nl) : text;
33
+ const obj = JSON.parse(line);
34
+ if (obj.type !== "session") {
35
+ 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
+ }
37
+ if (obj.cwd === newCwd) return;
38
+ obj.cwd = newCwd;
39
+ const newFrame0 = zlib.zstdCompressSync(JSON.stringify(obj) + "\n", CHECKSUM_OPTS);
40
+ const rest = buf.subarray(end0);
41
+ writeFileSync(filePath, Buffer.concat([newFrame0, rest]));
42
+ }
43
+
44
+ // src/index.js
45
+ var name = "dsh-sessions-manager";
46
+ var inject = ["webServer", "workspaceRegistry", "sessionPersistence", "sessionQuery", "storageDomain"];
47
+ var MAX_TITLE = 80;
48
+ var TRASH_DIR = process.env.DSH_SESSIONS_MANAGER_TRASH_DIR || join(homedir(), ".dsh", "sessions-manager-trash");
49
+ var TRASH_INDEX = join(TRASH_DIR, "index.json");
50
+ var TRASH_SCHEMA_VERSION = 2;
51
+ var DEFAULT_TRASH_SETTINGS = Object.freeze({ retentionDays: 0 });
52
+ var FETCH_TOOL_RE = /search|fetch|download|browse/i;
53
+ var MAX_FETCHES = 12;
54
+ var MAX_FILES = 20;
55
+ function json(res, value, status = 200) {
56
+ res.writeHead(status, { "content-type": "application/json; charset=utf-8", "cache-control": "no-store" });
57
+ res.end(JSON.stringify(value));
58
+ }
59
+ function errorStatus(error) {
60
+ return error && Number.isInteger(error.status) ? error.status : 500;
61
+ }
62
+ async function readJsonBody(req) {
63
+ const chunks = [];
64
+ let total = 0;
65
+ for await (const chunk of req) {
66
+ chunks.push(chunk);
67
+ total += chunk.length;
68
+ if (total > 1 << 20) return null;
69
+ }
70
+ try {
71
+ return JSON.parse(Buffer.concat(chunks).toString("utf8"));
72
+ } catch {
73
+ return null;
74
+ }
75
+ }
76
+ function parseIds(body) {
77
+ const raw = body && body.sessionIds;
78
+ if (!Array.isArray(raw)) return null;
79
+ const ids = [];
80
+ for (const v of raw) if (typeof v === "string" && isSafeSessionId(v)) ids.push(v);
81
+ return ids;
82
+ }
83
+ function isSafeSessionId(value) {
84
+ return typeof value === "string" && value.length > 0 && value.length <= 200 && !/[\\/\0]/.test(value) && value !== "." && value !== "..";
85
+ }
86
+ function requireSessionId(value) {
87
+ if (!isSafeSessionId(value)) {
88
+ const error = new Error("\u65E0\u6548\u7684 sessionId");
89
+ error.status = 400;
90
+ throw error;
91
+ }
92
+ return value;
93
+ }
94
+ function getActiveSessionId(context) {
95
+ try {
96
+ const a = context.get("activeSession");
97
+ if (a != null) return a && a.id != null ? a.id : typeof a === "string" ? a : null;
98
+ } catch (e) {
99
+ }
100
+ try {
101
+ const c = context.get("currentSession");
102
+ if (c != null) return c && c.id != null ? c.id : typeof c === "string" ? c : null;
103
+ } catch (e) {
104
+ }
105
+ try {
106
+ const store = context.get("sessions");
107
+ if (store && store.active && store.active.id != null) return store.active.id;
108
+ } catch (e) {
109
+ }
110
+ return null;
111
+ }
112
+ function foldTitle(events) {
113
+ let found = null;
114
+ let firstUser = null;
115
+ for (const ev of events) {
116
+ if (ev.type === "session/title" && ev.data && typeof ev.data.title === "string" && ev.data.title.length) {
117
+ found = ev.data.title;
118
+ }
119
+ if (firstUser === null && ev.type === "user/message" && ev.data && Array.isArray(ev.data.content)) {
120
+ const txt = ev.data.content.filter((b) => b && b.type === "text").map((b) => b.text).filter(Boolean).join(" ").trim();
121
+ if (txt) firstUser = txt;
122
+ }
123
+ }
124
+ return found || firstUser || null;
125
+ }
126
+ function apply(ctx) {
127
+ const w = ctx.workspaceRegistry;
128
+ const sp = ctx.sessionPersistence;
129
+ const sq = ctx.sessionQuery;
130
+ const dom = () => ctx.storageDomain.get("workspace");
131
+ const authorityTitleCache = /* @__PURE__ */ new Map();
132
+ let authorityTitlesLoaded = false;
133
+ async function archivedState() {
134
+ const d = dom();
135
+ if (!d) throw new Error("workspace domain is not open");
136
+ return d.global.get();
137
+ }
138
+ async function writeArchived(nextIds) {
139
+ const d = dom();
140
+ if (!d) throw new Error("workspace domain is not open");
141
+ const cur = d.global.get();
142
+ const next = Object.assign({}, cur, { archivedSessionIds: nextIds });
143
+ await d.global.set(next);
144
+ if (w && "state" in w) {
145
+ try {
146
+ w.state = next;
147
+ } catch (e) {
148
+ }
149
+ }
150
+ return next;
151
+ }
152
+ let archiveMutation = Promise.resolve();
153
+ function mutateArchived(mutator) {
154
+ const operation = archiveMutation.then(async () => {
155
+ const state = await archivedState();
156
+ const list = (state.archivedSessionIds || []).map(String);
157
+ const result = await mutator(list);
158
+ if (result.next) await writeArchived(result.next);
159
+ return result.value;
160
+ });
161
+ archiveMutation = operation.catch(() => {
162
+ });
163
+ return operation;
164
+ }
165
+ let wsByPath = {};
166
+ async function resolveOne(id) {
167
+ let title = null, createdAt = null, cwd = null;
168
+ try {
169
+ const o = await sq.readTitleSnapshot(id);
170
+ if (o) {
171
+ if (o.title && o.title.title) title = String(o.title.title);
172
+ if (o.session) {
173
+ cwd = o.session.cwd || null;
174
+ createdAt = o.session.createdAt || null;
175
+ }
176
+ }
177
+ } catch (e) {
178
+ }
179
+ if (!title || !cwd) {
180
+ try {
181
+ const r = await sp.readFrom(id, 0);
182
+ if (r.meta) {
183
+ if (!cwd) cwd = r.meta.cwd || null;
184
+ if (!createdAt) createdAt = r.meta.createdAt || null;
185
+ }
186
+ if (!title && Array.isArray(r.events)) title = foldTitle(r.events);
187
+ } catch (e2) {
188
+ }
189
+ }
190
+ const ws = cwd ? wsByPath[cwd] : void 0;
191
+ const workspaceGone = !!(cwd && !ws);
192
+ const display = title ? String(title).length > MAX_TITLE ? String(title).slice(0, MAX_TITLE) + "\u2026" : String(title) : null;
193
+ return {
194
+ sessionId: id,
195
+ title: display,
196
+ createdAt: createdAt || null,
197
+ workspacePath: cwd || null,
198
+ workspaceTitle: ws && ws.title ? ws.title : null,
199
+ workspaceGone: workspaceGone ? true : false,
200
+ hasWorkspace: !!cwd
201
+ };
202
+ }
203
+ async function restoreOne(sid) {
204
+ requireSessionId(sid);
205
+ return mutateArchived((list) => list.includes(sid) ? { next: list.filter((x) => x !== sid), value: { ok: true, restored: true } } : { next: null, value: { ok: true, restored: false } });
206
+ }
207
+ let trashMutation = Promise.resolve();
208
+ function normalizeTrashStore(raw) {
209
+ if (Array.isArray(raw)) return { schemaVersion: TRASH_SCHEMA_VERSION, settings: { ...DEFAULT_TRASH_SETTINGS }, items: raw, purgedSessionIds: [] };
210
+ const settings = raw && typeof raw.settings === "object" ? raw.settings : {};
211
+ const retentionDays = Number.isInteger(settings.retentionDays) && settings.retentionDays >= 0 ? settings.retentionDays : 0;
212
+ return {
213
+ schemaVersion: TRASH_SCHEMA_VERSION,
214
+ settings: { retentionDays },
215
+ items: raw && Array.isArray(raw.items) ? raw.items : [],
216
+ purgedSessionIds: raw && Array.isArray(raw.purgedSessionIds) ? [...new Set(raw.purgedSessionIds.filter(isSafeSessionId).map(String))] : []
217
+ };
218
+ }
219
+ async function readTrashStore() {
220
+ try {
221
+ return normalizeTrashStore(JSON.parse(readFileSync2(TRASH_INDEX, "utf8")));
222
+ } catch (e) {
223
+ return normalizeTrashStore(null);
224
+ }
225
+ }
226
+ async function readTrash() {
227
+ return (await readTrashStore()).items;
228
+ }
229
+ async function writeTrashStore(store) {
230
+ await mkdir(TRASH_DIR, { recursive: true });
231
+ const tmp = join(TRASH_DIR, `.index-${process.pid}-${Date.now()}.tmp`);
232
+ await writeFile(tmp, JSON.stringify(normalizeTrashStore(store), null, 2), { encoding: "utf8", mode: 384 });
233
+ await rename(tmp, TRASH_INDEX);
234
+ }
235
+ function mutateTrash(mutator) {
236
+ const operation = trashMutation.then(async () => {
237
+ const store = await readTrashStore();
238
+ const result = await mutator(store);
239
+ await writeTrashStore(store);
240
+ return result;
241
+ });
242
+ trashMutation = operation.catch(() => {
243
+ });
244
+ return operation;
245
+ }
246
+ async function deleteOne(sid) {
247
+ requireSessionId(sid);
248
+ let header = null;
249
+ let cwd = null;
250
+ let title = null;
251
+ let removedPath = null;
252
+ try {
253
+ const headers = await sp.list();
254
+ header = headers.find((h) => String(h.id) === sid) || null;
255
+ if (header) {
256
+ const loc = sp.locate(header);
257
+ if (loc && typeof loc.path === "string") removedPath = loc.path;
258
+ cwd = header.cwd || null;
259
+ title = header.title || header.meta && header.meta.title || null;
260
+ }
261
+ if (!title) {
262
+ const r = await sp.readFrom(sid, 0);
263
+ if (r && r.meta) {
264
+ if (!cwd) cwd = r.meta.cwd;
265
+ title = foldTitle(r.events);
266
+ }
267
+ }
268
+ } catch (e) {
269
+ }
270
+ if (!header && !removedPath) {
271
+ const error = new Error("\u627E\u4E0D\u5230\u8BE5\u4F1A\u8BDD");
272
+ error.status = 404;
273
+ throw error;
274
+ }
275
+ const archived = await mutateArchived((list) => ({ next: null, value: list.includes(sid) })).catch(() => false);
276
+ await mutateTrash((store) => {
277
+ const entry = {
278
+ sessionId: sid,
279
+ title: title || cwd || sid,
280
+ cwd: cwd || null,
281
+ header: header || null,
282
+ originalPath: removedPath || null,
283
+ sizeBytes: header && typeof header.size === "number" ? header.size : null,
284
+ wasArchived: archived,
285
+ deletedAt: Date.now()
286
+ };
287
+ const at = store.items.findIndex((t) => String(t.sessionId) === sid);
288
+ if (at >= 0) store.items[at] = entry;
289
+ else store.items.push(entry);
290
+ store.purgedSessionIds = store.purgedSessionIds.filter((id) => id !== sid);
291
+ });
292
+ return { ok: true, trashed: true };
293
+ }
294
+ async function restoreFromTrash(sid) {
295
+ requireSessionId(sid);
296
+ await mutateTrash(async (store) => {
297
+ const entry = store.items.find((t) => String(t.sessionId) === sid);
298
+ if (!entry) {
299
+ const error = new Error("\u56DE\u6536\u7AD9\u4E2D\u627E\u4E0D\u5230\u8BE5\u4F1A\u8BDD");
300
+ error.status = 404;
301
+ throw error;
302
+ }
303
+ if (entry.wasArchived === false) await restoreOne(sid);
304
+ store.items = store.items.filter((t) => String(t.sessionId) !== sid);
305
+ store.purgedSessionIds = store.purgedSessionIds.filter((id) => id !== sid);
306
+ });
307
+ return { ok: true, restored: true };
308
+ }
309
+ async function purgeFromTrash(sid) {
310
+ requireSessionId(sid);
311
+ let purged = false;
312
+ await mutateTrash(async (store) => {
313
+ const entry = store.items.find((t) => String(t.sessionId) === sid);
314
+ if (!entry) {
315
+ const error = new Error("\u56DE\u6536\u7AD9\u4E2D\u627E\u4E0D\u5230\u8BE5\u4F1A\u8BDD");
316
+ error.status = 404;
317
+ throw error;
318
+ }
319
+ let target = null;
320
+ try {
321
+ const headers = await sp.list();
322
+ const current = headers.find((h) => String(h.id) === sid);
323
+ const located = current && sp.locate(current);
324
+ if (located && typeof located.path === "string") target = located.path;
325
+ } catch (e) {
326
+ }
327
+ if (!target && typeof entry.originalPath === "string") target = entry.originalPath;
328
+ const targetOwnsSession = target && (basename(dirname(target)) === sid || basename(target).includes(sid));
329
+ if (target && !targetOwnsSession) {
330
+ const error = new Error("\u65E5\u5FD7\u8DEF\u5F84\u4E0E\u4F1A\u8BDD ID \u4E0D\u5339\u914D\uFF0C\u5DF2\u505C\u6B62\u6C38\u4E45\u5220\u9664");
331
+ error.status = 409;
332
+ throw error;
333
+ }
334
+ if (!store.purgedSessionIds.includes(sid)) store.purgedSessionIds.push(sid);
335
+ await writeTrashStore(store);
336
+ try {
337
+ const sessions = ctx.get("sessions");
338
+ const liveSession = sessions && sessions.get && sessions.get(sid);
339
+ if (liveSession && typeof sessions.flush === "function") await sessions.flush(liveSession);
340
+ const entered = sessions && sessions.store && sessions.store.get && sessions.store.get(sid);
341
+ if (liveSession && (!entered || typeof entered.detach !== "function")) throw new Error("\u5BBF\u4E3B\u672A\u63D0\u4F9B live Session detach \u80FD\u529B");
342
+ if (entered && typeof entered.detach === "function") entered.detach();
343
+ const retirement = sp && sp.retirements && sp.retirements.get && sp.retirements.get(sid);
344
+ if (retirement && typeof retirement.then === "function") await retirement;
345
+ } catch (e) {
346
+ const error = new Error("\u65E0\u6CD5\u4ECE\u5BBF\u4E3B\u5185\u5B58\u79FB\u9664\u4F1A\u8BDD\uFF0C\u5DF2\u505C\u6B62\u6C38\u4E45\u5220\u9664\uFF1A" + String(e && e.message || e));
347
+ error.status = 409;
348
+ throw error;
349
+ }
350
+ if (target) {
351
+ try {
352
+ await unlink(target);
353
+ } catch (e) {
354
+ if (e && e.code !== "ENOENT") throw new Error("\u5220\u9664\u6587\u4EF6\u5931\u8D25\uFF1A" + String(e && e.message || e));
355
+ }
356
+ }
357
+ try {
358
+ for (const ent of w.list()) {
359
+ if (ent.sessionIds.includes(sid)) {
360
+ try {
361
+ await ent.detachSession(sid);
362
+ } catch (e) {
363
+ }
364
+ }
365
+ }
366
+ } catch (e) {
367
+ }
368
+ try {
369
+ if (w.sessionPaths && w.sessionPaths.delete) w.sessionPaths.delete(sid);
370
+ } catch (e) {
371
+ }
372
+ try {
373
+ if (w.headers && w.headers.delete) w.headers.delete(sid);
374
+ } catch (e) {
375
+ }
376
+ await restoreOne(sid);
377
+ try {
378
+ await reindexRegistry();
379
+ } catch (e) {
380
+ }
381
+ store.items = store.items.filter((t) => String(t.sessionId) !== sid);
382
+ purged = true;
383
+ });
384
+ if (!purged) throw new Error("\u5F7B\u5E95\u5220\u9664\u5931\u8D25");
385
+ return { ok: true, purged: true };
386
+ }
387
+ async function trashSettings(next) {
388
+ if (next === void 0) return (await readTrashStore()).settings;
389
+ const days = Number(next.retentionDays);
390
+ if (!Number.isInteger(days) || ![0, 7, 30, 90].includes(days)) {
391
+ const error = new Error("retentionDays \u4EC5\u652F\u6301 0\u30017\u300130\u300190");
392
+ error.status = 400;
393
+ throw error;
394
+ }
395
+ await mutateTrash((store) => {
396
+ store.settings = { retentionDays: days };
397
+ });
398
+ return (await readTrashStore()).settings;
399
+ }
400
+ async function cleanupExpiredTrash() {
401
+ const store = await readTrashStore();
402
+ const days = store.settings.retentionDays;
403
+ if (!days) return 0;
404
+ const cutoff = Date.now() - days * 864e5;
405
+ const ids = store.items.filter((item) => Number(item.deletedAt) > 0 && Number(item.deletedAt) < cutoff).map((item) => String(item.sessionId));
406
+ let count = 0;
407
+ for (const sid of ids) {
408
+ try {
409
+ await purgeFromTrash(sid);
410
+ count++;
411
+ } catch (e) {
412
+ }
413
+ }
414
+ return count;
415
+ }
416
+ async function moveTargetWorkspace(rawPath) {
417
+ if (typeof rawPath !== "string" || !rawPath.trim()) throw new Error("\u7F3A\u5C11\u76EE\u6807\u5DE5\u4F5C\u533A\u8DEF\u5F84");
418
+ let p = String(rawPath).trim();
419
+ if (p.startsWith("~/")) p = join(homedir(), p.slice(2));
420
+ if (!isAbsolute(p)) p = join(homedir(), p);
421
+ let canonical = null;
422
+ try {
423
+ canonical = await realpath(p);
424
+ } catch (e) {
425
+ canonical = null;
426
+ }
427
+ if (canonical === null) {
428
+ await mkdir(p, { recursive: true });
429
+ canonical = await realpath(p);
430
+ }
431
+ return { canonical, entity: await w.create(canonical, basename(canonical) || "workspace") };
432
+ }
433
+ async function moveOne(sid, targetPath) {
434
+ const activeId = getActiveSessionId(ctx);
435
+ if (activeId != null && String(activeId) === String(sid)) {
436
+ 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");
437
+ }
438
+ const r = await sp.readFrom(sid, 0);
439
+ if (!r || !r.meta) throw new Error("\u65E0\u6CD5\u8BFB\u53D6\u8BE5\u4F1A\u8BDD\u7684\u65E5\u5FD7");
440
+ const meta = r.meta;
441
+ const events = r.events;
442
+ const oldCwd = meta.cwd || null;
443
+ const { canonical, entity: target } = await moveTargetWorkspace(targetPath);
444
+ if (oldCwd) {
445
+ let oldCanon = null;
446
+ try {
447
+ oldCanon = await realpath(oldCwd);
448
+ } catch (e) {
449
+ oldCanon = null;
450
+ }
451
+ if (oldCanon === canonical) {
452
+ return { ok: true, already: true, workspaceId: target.id, workspaceTitle: target.title };
453
+ }
454
+ }
455
+ const newHeader = Object.assign({}, meta, { cwd: canonical });
456
+ const live = ctx.get("sessions");
457
+ const liveObj = live && live.get && live.get(sid);
458
+ const isOpen = !!liveObj;
459
+ const ALREADY_EXISTS_RE = /already exists in this backend/i;
460
+ const relocateLog = async (header, newHeaderObj) => {
461
+ const oldPath = locatePath(header);
462
+ const newPath = locatePath(newHeaderObj);
463
+ if (!oldPath || !newPath || oldPath === newPath) return false;
464
+ const backupPath = `${oldPath}.move-backup-${Date.now()}`;
465
+ try {
466
+ await mkdir(dirname(newPath), { recursive: true });
467
+ await rename(oldPath, backupPath);
468
+ await rewriteFrame0Cwd(backupPath, canonical);
469
+ await rename(backupPath, newPath);
470
+ } catch (e) {
471
+ try {
472
+ await rename(backupPath, oldPath);
473
+ } catch (_) {
474
+ }
475
+ if (e && e.code !== "ENOENT") throw e;
476
+ return false;
477
+ }
478
+ return true;
479
+ };
480
+ const locatePath = (header) => {
481
+ let fn = null;
482
+ try {
483
+ if (typeof sp.locate === "function") fn = sp.locate.bind(sp);
484
+ } catch (e) {
485
+ }
486
+ if (!fn && sp.backend && typeof sp.backend.locate === "function") fn = sp.backend.locate.bind(sp.backend);
487
+ if (!fn) return null;
488
+ try {
489
+ const loc = fn(header);
490
+ if (loc && typeof loc.path === "string") return loc.path;
491
+ if (typeof loc === "string") return loc;
492
+ } catch (e) {
493
+ }
494
+ return null;
495
+ };
496
+ if (isOpen) {
497
+ await relocateLog(meta, newHeader);
498
+ try {
499
+ const st = sp.states && sp.states.get && sp.states.get(sid);
500
+ if (st && st.meta) st.meta = Object.assign({}, st.meta, { cwd: canonical });
501
+ } catch (e) {
502
+ }
503
+ } else {
504
+ let oldPath = null;
505
+ try {
506
+ const loc = locatePath(meta);
507
+ if (loc && typeof loc === "string") oldPath = loc;
508
+ else if (loc && loc.path) oldPath = loc.path;
509
+ } catch (e) {
510
+ oldPath = null;
511
+ }
512
+ if (typeof sp.create !== "function" || typeof sp.append !== "function") {
513
+ await relocateLog(meta, newHeader);
514
+ } else {
515
+ const backupPath = oldPath ? `${oldPath}.move-backup-${Date.now()}` : null;
516
+ if (backupPath) {
517
+ try {
518
+ await rename(oldPath, backupPath);
519
+ } catch (e) {
520
+ if (e && e.code !== "ENOENT") throw new Error("\u79FB\u52A8\u5931\u8D25\uFF1A\u65E0\u6CD5\u5907\u4EFD\u65E7\u7684\u4F1A\u8BDD\u65E5\u5FD7");
521
+ }
522
+ }
523
+ const restore = async () => {
524
+ if (backupPath) {
525
+ try {
526
+ await rename(backupPath, oldPath);
527
+ } catch (_) {
528
+ }
529
+ }
530
+ };
531
+ try {
532
+ await sp.create(newHeader);
533
+ await sp.append(sid, events);
534
+ const check = await sp.readFrom(sid, 0);
535
+ if (!check || !check.meta || check.meta.cwd !== canonical) {
536
+ throw new Error("\u79FB\u52A8\u540E\u6821\u9A8C\u5931\u8D25\uFF1A\u4F1A\u8BDD\u5DE5\u4F5C\u76EE\u5F55\u672A\u6B63\u786E\u66F4\u65B0");
537
+ }
538
+ if (backupPath) {
539
+ try {
540
+ await unlink(backupPath);
541
+ } catch (e) {
542
+ }
543
+ }
544
+ } catch (e) {
545
+ if (ALREADY_EXISTS_RE.test(String(e && e.message || e))) {
546
+ await restore();
547
+ await relocateLog(meta, newHeader);
548
+ } else {
549
+ await restore();
550
+ throw new Error("\u79FB\u52A8\u4F1A\u8BDD\u65E5\u5FD7\u5931\u8D25\uFF1A" + String(e && e.message || e));
551
+ }
552
+ }
553
+ }
554
+ }
555
+ try {
556
+ if (liveObj) {
557
+ if ("header" in liveObj) liveObj.header = newHeader;
558
+ if ("cwd" in liveObj) liveObj.cwd = canonical;
559
+ if ("meta" in liveObj) liveObj.meta = newHeader;
560
+ }
561
+ } catch (e) {
562
+ }
563
+ for (const ent of w.list()) {
564
+ try {
565
+ await ent.detachSession(sid);
566
+ } catch (e) {
567
+ }
568
+ }
569
+ if (w.headers && typeof w.headers.set === "function") w.headers.set(sid, newHeader);
570
+ if (w.sessionPaths && typeof w.sessionPaths.set === "function") w.sessionPaths.set(sid, canonical);
571
+ await target.attachSession(sid);
572
+ const verified = (() => {
573
+ try {
574
+ return target.sessionIds.includes(sid);
575
+ } catch (e) {
576
+ return false;
577
+ }
578
+ })();
579
+ if (!verified) {
580
+ throw new Error("\u79FB\u52A8\u540E\u6821\u9A8C\u5931\u8D25\uFF1A\u4F1A\u8BDD\u672A\u51FA\u73B0\u5728\u76EE\u6807\u5DE5\u4F5C\u533A\uFF0C\u8BF7\u91CD\u8BD5\u6216\u91CD\u542F DSH\u3002");
581
+ }
582
+ return {
583
+ ok: true,
584
+ moved: true,
585
+ workspaceId: target.id,
586
+ workspaceTitle: target.title,
587
+ workspacePath: canonical
588
+ };
589
+ }
590
+ async function reindexRegistry() {
591
+ const reg = w;
592
+ if (!reg || typeof reg.replaceHeaderIndex !== "function") return false;
593
+ let headers = null;
594
+ try {
595
+ headers = await sp.list();
596
+ } catch (e) {
597
+ headers = null;
598
+ }
599
+ if (!headers || !Array.isArray(headers)) return false;
600
+ await reg.replaceHeaderIndex(headers);
601
+ if (typeof reg.rebuildEntities === "function") reg.rebuildEntities();
602
+ return true;
603
+ }
604
+ async function listWorkspaces() {
605
+ const out = [];
606
+ try {
607
+ for (const ent of w.list()) out.push({ workspaceId: ent.id, title: ent.title, path: ent.path });
608
+ } catch (e) {
609
+ }
610
+ return out;
611
+ }
612
+ async function archiveOne(sid) {
613
+ requireSessionId(sid);
614
+ return mutateArchived(async (list) => {
615
+ if (list.includes(sid)) return { next: null, value: { ok: true, archived: false } };
616
+ await w.archiveSession(sid);
617
+ return { next: null, value: { ok: true, archived: true } };
618
+ });
619
+ }
620
+ async function allSessionItems() {
621
+ let materialized = /* @__PURE__ */ new Set();
622
+ let live = ctx.get("sessions");
623
+ try {
624
+ const headers = await sp.list();
625
+ materialized = new Set(headers.map((h) => String(h.id)));
626
+ } catch (e) {
627
+ }
628
+ const ids = [];
629
+ try {
630
+ for (const header of await sp.list()) ids.push(String(header.id));
631
+ } catch (e) {
632
+ }
633
+ if (live) {
634
+ try {
635
+ live.list().forEach((s) => {
636
+ if (!ids.includes(String(s.id))) ids.push(String(s.id));
637
+ });
638
+ } catch (e) {
639
+ }
640
+ }
641
+ let hiddenIds = /* @__PURE__ */ new Set();
642
+ try {
643
+ const store = await readTrashStore();
644
+ hiddenIds = /* @__PURE__ */ new Set([...store.items.map((t) => String(t.sessionId)), ...store.purgedSessionIds.map(String)]);
645
+ } catch (e) {
646
+ }
647
+ const visibleIds = ids.filter((id) => !hiddenIds.has(id));
648
+ wsByPath = {};
649
+ try {
650
+ for (const ent of w.list()) wsByPath[ent.path] = ent;
651
+ } catch (e) {
652
+ wsByPath = {};
653
+ }
654
+ const currentArchived = new Set((await archivedState().catch(() => ({ archivedSessionIds: [] }))).archivedSessionIds || []);
655
+ const items = [];
656
+ const CHUNK = 6;
657
+ for (let i = 0; i < visibleIds.length; i += CHUNK) {
658
+ const res2 = await Promise.all(visibleIds.slice(i, i + CHUNK).map(resolveOne));
659
+ for (const it of res2) items.push({ ...it, archived: currentArchived.has(it.sessionId) });
660
+ }
661
+ return items;
662
+ }
663
+ async function sidebarAuthority() {
664
+ const ids = [];
665
+ try {
666
+ for (const header of await sp.list()) ids.push(String(header.id));
667
+ } catch (e) {
668
+ }
669
+ const sessions = ctx.get("sessions");
670
+ try {
671
+ if (sessions) sessions.list().forEach((session) => {
672
+ if (!ids.includes(String(session.id))) ids.push(String(session.id));
673
+ });
674
+ } catch (e) {
675
+ }
676
+ const store = await readTrashStore();
677
+ if (!authorityTitlesLoaded && ids.length && typeof sq.readTitleSnapshots === "function") {
678
+ const results = await sq.readTitleSnapshots(ids);
679
+ results.forEach((result, index) => {
680
+ if (result && result.status === "fulfilled" && result.value && result.value.title && typeof result.value.title.title === "string") {
681
+ authorityTitleCache.set(ids[index], result.value.title.title);
682
+ }
683
+ });
684
+ authorityTitlesLoaded = true;
685
+ } else if (!authorityTitlesLoaded) {
686
+ for (const sid of ids) {
687
+ try {
688
+ const snapshot = await sq.readTitleSnapshot(sid);
689
+ if (snapshot && snapshot.title && typeof snapshot.title.title === "string") authorityTitleCache.set(sid, snapshot.title.title);
690
+ } catch (e) {
691
+ }
692
+ }
693
+ authorityTitlesLoaded = true;
694
+ }
695
+ return {
696
+ titles: Object.fromEntries(authorityTitleCache),
697
+ trashedSessionIds: store.items.map((item) => String(item.sessionId)),
698
+ purgedSessionIds: store.purgedSessionIds.map(String)
699
+ };
700
+ }
701
+ async function buildDetails(sid) {
702
+ const sessions = ctx.get("sessions");
703
+ const live = sessions && sessions.get(sid);
704
+ let meta = null;
705
+ let events = [];
706
+ if (live !== void 0) {
707
+ meta = live && live.header || null;
708
+ try {
709
+ events = Array.isArray(live.events) ? [...live.events] : [];
710
+ } catch (e) {
711
+ events = [];
712
+ }
713
+ } else {
714
+ const r = await sp.readFrom(sid, 0);
715
+ if (!r || !r.meta) throw new Error("\u627E\u4E0D\u5230\u8BE5\u4F1A\u8BDD\u7684\u8BB0\u5F55\uFF08\u4F1A\u8BDD\u4E0D\u5B58\u5728\uFF09");
716
+ meta = r.meta;
717
+ events = Array.isArray(r.events) ? r.events : [];
718
+ }
719
+ let sizeBytes = null;
720
+ try {
721
+ const loc = sp.locate(meta);
722
+ if (loc && typeof loc.path === "string" && loc.path) {
723
+ const st = await stat(loc.path);
724
+ if (st && typeof st.size === "number") sizeBytes = st.size;
725
+ }
726
+ } catch (e) {
727
+ sizeBytes = null;
728
+ }
729
+ let lastTime = typeof meta.createdAt === "number" ? meta.createdAt : 0;
730
+ const fileSet = /* @__PURE__ */ new Map();
731
+ const stats = {
732
+ turns: 0,
733
+ steps: 0,
734
+ userMessages: 0,
735
+ assistantMessages: 0,
736
+ toolCalls: 0,
737
+ attachments: 0,
738
+ toolCounts: {},
739
+ fetches: []
740
+ };
741
+ const turnSeen = /* @__PURE__ */ new Set();
742
+ const stepSeen = /* @__PURE__ */ new Set();
743
+ for (const ev of events) {
744
+ if (ev && typeof ev.time === "number" && ev.time > lastTime) lastTime = ev.time;
745
+ const d = ev && ev.data && typeof ev.data === "object" ? ev.data : {};
746
+ const type = ev && ev.type;
747
+ switch (type) {
748
+ case "turn/start":
749
+ if (typeof d.turn === "number") turnSeen.add(d.turn);
750
+ break;
751
+ case "step/start":
752
+ if (typeof d.step === "number") stepSeen.add(d.step);
753
+ break;
754
+ case "user/message":
755
+ stats.userMessages++;
756
+ if (Array.isArray(d.content)) {
757
+ for (const b of d.content) if (b && b.type === "image") stats.attachments++;
758
+ }
759
+ break;
760
+ case "assistant/message":
761
+ stats.assistantMessages++;
762
+ break;
763
+ case "tool/call": {
764
+ stats.toolCalls++;
765
+ const tn = typeof d.name === "string" && d.name ? d.name : "tool";
766
+ stats.toolCounts[tn] = (stats.toolCounts[tn] || 0) + 1;
767
+ if (FETCH_TOOL_RE.test(tn)) {
768
+ let query;
769
+ try {
770
+ const a = typeof d.arguments === "string" ? JSON.parse(d.arguments) : d.arguments;
771
+ query = typeof a?.query === "string" ? a.query : typeof a?.url === "string" ? a.url : typeof a?.q === "string" ? a.q : void 0;
772
+ } catch (e) {
773
+ query = void 0;
774
+ }
775
+ stats.fetches.push({ tool: tn, ...query && query !== "" ? { query } : {} });
776
+ }
777
+ if (tn === "write" || tn === "edit") {
778
+ let argsJ;
779
+ try {
780
+ argsJ = typeof d.arguments === "string" ? JSON.parse(d.arguments) : d.arguments;
781
+ } catch (e) {
782
+ break;
783
+ }
784
+ const fp = argsJ && typeof argsJ.file_path === "string" && argsJ.file_path ? argsJ.file_path : void 0;
785
+ if (fp !== void 0 && !fileSet.has(fp)) fileSet.set(fp, tn);
786
+ }
787
+ break;
788
+ }
789
+ }
790
+ }
791
+ stats.turns = turnSeen.size;
792
+ stats.steps = stepSeen.size;
793
+ if (stats.fetches.length > MAX_FETCHES) stats.fetches = stats.fetches.slice(0, MAX_FETCHES);
794
+ const fileEntries = [...fileSet.entries()].slice(0, MAX_FILES * 2);
795
+ const exists = await Promise.all(fileEntries.map(([p]) => stat(p).then(() => true).catch(() => false)));
796
+ const files = fileEntries.filter((_, i) => exists[i]).map(([path, tool]) => ({ path, tool })).slice(0, MAX_FILES);
797
+ const lineage = {
798
+ parentSessionId: meta && typeof meta.parentSession === "string" ? meta.parentSession : null,
799
+ children: [],
800
+ subagents: []
801
+ };
802
+ const childrenSet = /* @__PURE__ */ new Set();
803
+ const subagentSet = /* @__PURE__ */ new Set();
804
+ try {
805
+ if (typeof sp.list === "function") {
806
+ for (const h of await sp.list()) {
807
+ if (String(h.parentSession) !== String(sid)) continue;
808
+ if (h.origin === "subagent") subagentSet.add(h.id);
809
+ else childrenSet.add(h.id);
810
+ }
811
+ }
812
+ } catch (e) {
813
+ }
814
+ if (sessions) {
815
+ try {
816
+ sessions.list().forEach((s) => {
817
+ if (String(s.header.parentSession) !== String(sid)) return;
818
+ if (s.header.origin === "subagent") subagentSet.add(s.id);
819
+ else childrenSet.add(s.id);
820
+ });
821
+ } catch (e) {
822
+ }
823
+ }
824
+ lineage.children = [...childrenSet];
825
+ lineage.subagents = [...subagentSet];
826
+ return {
827
+ sessionId: sid,
828
+ sizeBytes,
829
+ createdAt: meta && typeof meta.createdAt === "number" ? meta.createdAt : null,
830
+ updatedAt: lastTime || null,
831
+ files,
832
+ stats,
833
+ lineage
834
+ };
835
+ }
836
+ ctx.effect(() => {
837
+ const disposers = [];
838
+ if (typeof ctx.on === "function") disposers.push(ctx.on("session/event", (session, event) => {
839
+ if (event && event.type === "session/title" && event.data && typeof event.data.title === "string") {
840
+ authorityTitleCache.set(String(session.id), event.data.title);
841
+ }
842
+ }));
843
+ disposers.push(ctx.webServer.register({
844
+ kind: "exact",
845
+ path: "/archived-sessions/list",
846
+ handler: async (req, res) => {
847
+ try {
848
+ const state = await archivedState();
849
+ const ids = state.archivedSessionIds || [];
850
+ let materialized = /* @__PURE__ */ new Set();
851
+ let live = ctx.get("sessions");
852
+ try {
853
+ const headers = await sp.list();
854
+ materialized = new Set(headers.map((h) => String(h.id)));
855
+ } catch (e) {
856
+ }
857
+ const trashStore = await readTrashStore();
858
+ const hidden = /* @__PURE__ */ new Set([...trashStore.items.map((item) => String(item.sessionId)), ...trashStore.purgedSessionIds.map(String)]);
859
+ const idStrs = ids.map(String).filter((id) => !hidden.has(id) && (materialized.has(id) || live && live.get(id)));
860
+ wsByPath = {};
861
+ try {
862
+ for (const ent of w.list()) wsByPath[ent.path] = ent;
863
+ } catch (e) {
864
+ wsByPath = {};
865
+ }
866
+ const items = [];
867
+ const CHUNK = 6;
868
+ for (let i = 0; i < idStrs.length; i += CHUNK) {
869
+ const res2 = await Promise.all(idStrs.slice(i, i + CHUNK).map(resolveOne));
870
+ items.push.apply(items, res2);
871
+ }
872
+ json(res, { items });
873
+ } catch (e) {
874
+ json(res, { error: String(e && e.message || e) }, 500);
875
+ }
876
+ }
877
+ }));
878
+ disposers.push(ctx.webServer.register({
879
+ kind: "exact",
880
+ path: "/archived-sessions/restore",
881
+ handler: async (req, res) => {
882
+ try {
883
+ const body = await readJsonBody(req);
884
+ const sid = body && typeof body.sessionId === "string" ? body.sessionId : null;
885
+ if (!sid) return json(res, { ok: false, error: "missing sessionId" }, 400);
886
+ json(res, await restoreOne(sid));
887
+ } catch (e) {
888
+ json(res, { ok: false, error: String(e && e.message || e) }, errorStatus(e));
889
+ }
890
+ }
891
+ }));
892
+ disposers.push(ctx.webServer.register({
893
+ kind: "exact",
894
+ path: "/archived-sessions/restore-many",
895
+ handler: async (req, res) => {
896
+ try {
897
+ const body = await readJsonBody(req);
898
+ const ids = parseIds(body);
899
+ if (!ids || ids.length === 0) return json(res, { ok: false, error: "missing sessionIds" }, 400);
900
+ const results = [];
901
+ for (const sid of ids) {
902
+ try {
903
+ results.push({ sessionId: sid, ok: true, ...await restoreOne(sid) });
904
+ } catch (e) {
905
+ results.push({ sessionId: sid, ok: false, error: String(e && e.message || e) });
906
+ }
907
+ }
908
+ json(res, { ok: true, restored: results.filter((r) => r.ok).length, results });
909
+ } catch (e) {
910
+ json(res, { ok: false, error: String(e && e.message || e) }, 500);
911
+ }
912
+ }
913
+ }));
914
+ disposers.push(ctx.webServer.register({
915
+ kind: "exact",
916
+ path: "/archived-sessions/delete",
917
+ handler: async (req, res) => {
918
+ try {
919
+ const body = await readJsonBody(req);
920
+ const sid = body && typeof body.sessionId === "string" ? body.sessionId : null;
921
+ if (!sid) return json(res, { ok: false, error: "missing sessionId" }, 400);
922
+ json(res, await deleteOne(sid));
923
+ } catch (e) {
924
+ json(res, { ok: false, error: String(e && e.message || e) }, errorStatus(e));
925
+ }
926
+ }
927
+ }));
928
+ disposers.push(ctx.webServer.register({
929
+ kind: "exact",
930
+ path: "/archived-sessions/delete-many",
931
+ handler: async (req, res) => {
932
+ try {
933
+ const body = await readJsonBody(req);
934
+ const ids = parseIds(body);
935
+ if (!ids || ids.length === 0) return json(res, { ok: false, error: "missing sessionIds" }, 400);
936
+ const results = [];
937
+ for (const sid of ids) {
938
+ try {
939
+ results.push({ sessionId: sid, ok: true, ...await deleteOne(sid) });
940
+ } catch (e) {
941
+ results.push({ sessionId: sid, ok: false, error: String(e && e.message || e) });
942
+ }
943
+ }
944
+ json(res, { ok: true, deleted: results.filter((r) => r.ok).length, results });
945
+ } catch (e) {
946
+ json(res, { ok: false, error: String(e && e.message || e) }, 500);
947
+ }
948
+ }
949
+ }));
950
+ disposers.push(ctx.webServer.register({
951
+ kind: "exact",
952
+ path: "/archived-sessions/trash/list",
953
+ handler: async (req, res) => {
954
+ try {
955
+ await cleanupExpiredTrash();
956
+ const list = await readTrash();
957
+ list.sort((a, b) => (b.deletedAt || 0) - (a.deletedAt || 0));
958
+ const store = await readTrashStore();
959
+ json(res, { schemaVersion: store.schemaVersion, settings: store.settings, purgedSessionIds: store.purgedSessionIds, items: list });
960
+ } catch (e) {
961
+ json(res, { ok: false, error: String(e && e.message || e) }, errorStatus(e));
962
+ }
963
+ }
964
+ }));
965
+ disposers.push(ctx.webServer.register({
966
+ kind: "exact",
967
+ path: "/archived-sessions/trash/settings",
968
+ handler: async (req, res) => {
969
+ try {
970
+ const body = await readJsonBody(req);
971
+ const settings = body && Object.prototype.hasOwnProperty.call(body, "retentionDays") ? await trashSettings({ retentionDays: body.retentionDays }) : await trashSettings();
972
+ json(res, { ok: true, settings });
973
+ } catch (e) {
974
+ json(res, { ok: false, error: String(e && e.message || e) }, errorStatus(e));
975
+ }
976
+ }
977
+ }));
978
+ disposers.push(ctx.webServer.register({
979
+ kind: "exact",
980
+ path: "/archived-sessions/trash/verify",
981
+ handler: async (req, res) => {
982
+ try {
983
+ const items = await readTrash();
984
+ const results = await Promise.all(items.map(async (item) => {
985
+ let exists = false;
986
+ if (typeof item.originalPath === "string") exists = await stat(item.originalPath).then(() => true).catch(() => false);
987
+ return { sessionId: item.sessionId, status: exists ? "ok" : "missing", originalPath: item.originalPath || null };
988
+ }));
989
+ json(res, { ok: true, healthy: results.filter((r) => r.status === "ok").length, missing: results.filter((r) => r.status === "missing").length, results });
990
+ } catch (e) {
991
+ json(res, { ok: false, error: String(e && e.message || e) }, errorStatus(e));
992
+ }
993
+ }
994
+ }));
995
+ disposers.push(ctx.webServer.register({
996
+ kind: "exact",
997
+ path: "/archived-sessions/trash/restore",
998
+ handler: async (req, res) => {
999
+ try {
1000
+ const body = await readJsonBody(req);
1001
+ const sid = body && typeof body.sessionId === "string" ? body.sessionId : null;
1002
+ if (!sid) return json(res, { ok: false, error: "missing sessionId" }, 400);
1003
+ json(res, await restoreFromTrash(sid));
1004
+ } catch (e) {
1005
+ json(res, { ok: false, error: String(e && e.message || e) }, 500);
1006
+ }
1007
+ }
1008
+ }));
1009
+ disposers.push(ctx.webServer.register({
1010
+ kind: "exact",
1011
+ path: "/archived-sessions/trash/purge",
1012
+ handler: async (req, res) => {
1013
+ try {
1014
+ const body = await readJsonBody(req);
1015
+ const sid = body && typeof body.sessionId === "string" ? body.sessionId : null;
1016
+ if (!sid) return json(res, { ok: false, error: "missing sessionId" }, 400);
1017
+ json(res, await purgeFromTrash(sid));
1018
+ } catch (e) {
1019
+ json(res, { ok: false, error: String(e && e.message || e) }, 500);
1020
+ }
1021
+ }
1022
+ }));
1023
+ disposers.push(ctx.webServer.register({
1024
+ kind: "exact",
1025
+ path: "/archived-sessions/trash/purge-many",
1026
+ handler: async (req, res) => {
1027
+ try {
1028
+ const body = await readJsonBody(req);
1029
+ const ids = parseIds(body);
1030
+ if (!ids || ids.length === 0) return json(res, { ok: false, error: "missing sessionIds" }, 400);
1031
+ const results = [];
1032
+ for (const sid of ids) {
1033
+ try {
1034
+ results.push({ sessionId: sid, ok: true, ...await purgeFromTrash(sid) });
1035
+ } catch (e) {
1036
+ results.push({ sessionId: sid, ok: false, error: String(e && e.message || e) });
1037
+ }
1038
+ }
1039
+ json(res, { ok: true, purged: results.filter((r) => r.ok).length, results });
1040
+ } catch (e) {
1041
+ json(res, { ok: false, error: String(e && e.message || e) }, 500);
1042
+ }
1043
+ }
1044
+ }));
1045
+ disposers.push(ctx.webServer.register({
1046
+ kind: "exact",
1047
+ path: "/archived-sessions/sessions",
1048
+ handler: async (req, res) => {
1049
+ try {
1050
+ json(res, { items: await allSessionItems() });
1051
+ } catch (e) {
1052
+ json(res, { error: String(e && e.message || e) }, 500);
1053
+ }
1054
+ }
1055
+ }));
1056
+ disposers.push(ctx.webServer.register({
1057
+ kind: "exact",
1058
+ path: "/archived-sessions/sidebar-state",
1059
+ handler: async (req, res) => {
1060
+ try {
1061
+ json(res, await sidebarAuthority());
1062
+ } catch (e) {
1063
+ json(res, { error: String(e && e.message || e) }, errorStatus(e));
1064
+ }
1065
+ }
1066
+ }));
1067
+ disposers.push(ctx.webServer.register({
1068
+ kind: "exact",
1069
+ path: "/archived-sessions/workspaces",
1070
+ handler: async (req, res) => {
1071
+ try {
1072
+ json(res, { items: await listWorkspaces() });
1073
+ } catch (e) {
1074
+ json(res, { error: String(e && e.message || e) }, 500);
1075
+ }
1076
+ }
1077
+ }));
1078
+ disposers.push(ctx.webServer.register({
1079
+ kind: "exact",
1080
+ path: "/archived-sessions/move",
1081
+ handler: async (req, res) => {
1082
+ try {
1083
+ const body = await readJsonBody(req);
1084
+ const sid = body && typeof body.sessionId === "string" ? body.sessionId : null;
1085
+ const target = body && typeof body.targetPath === "string" ? body.targetPath : null;
1086
+ if (!sid) return json(res, { ok: false, error: "missing sessionId" }, 400);
1087
+ if (!target) return json(res, { ok: false, error: "missing targetPath" }, 400);
1088
+ const moved = await moveOne(sid, target);
1089
+ try {
1090
+ await reindexRegistry();
1091
+ } catch (e) {
1092
+ }
1093
+ json(res, { sessionId: sid, ...moved });
1094
+ } catch (e) {
1095
+ json(res, { ok: false, error: String(e && e.message || e) }, 500);
1096
+ }
1097
+ }
1098
+ }));
1099
+ disposers.push(ctx.webServer.register({
1100
+ kind: "exact",
1101
+ path: "/archived-sessions/archive",
1102
+ handler: async (req, res) => {
1103
+ try {
1104
+ const body = await readJsonBody(req);
1105
+ const sid = body && typeof body.sessionId === "string" ? body.sessionId : null;
1106
+ if (!sid) return json(res, { ok: false, error: "missing sessionId" }, 400);
1107
+ json(res, { sessionId: sid, ...await archiveOne(sid) });
1108
+ } catch (e) {
1109
+ json(res, { ok: false, error: String(e && e.message || e) }, 500);
1110
+ }
1111
+ }
1112
+ }));
1113
+ disposers.push(ctx.webServer.register({
1114
+ kind: "exact",
1115
+ path: "/archived-sessions/archive-many",
1116
+ handler: async (req, res) => {
1117
+ try {
1118
+ const body = await readJsonBody(req);
1119
+ const ids = parseIds(body);
1120
+ if (!ids || ids.length === 0) return json(res, { ok: false, error: "missing sessionIds" }, 400);
1121
+ const results = [];
1122
+ for (const sid of ids) {
1123
+ try {
1124
+ results.push({ sessionId: sid, ok: true, ...await archiveOne(sid) });
1125
+ } catch (e) {
1126
+ results.push({ sessionId: sid, ok: false, error: String(e && e.message || e) });
1127
+ }
1128
+ }
1129
+ json(res, { ok: true, archived: results.filter((r) => r.ok).length, results });
1130
+ } catch (e) {
1131
+ json(res, { ok: false, error: String(e && e.message || e) }, 500);
1132
+ }
1133
+ }
1134
+ }));
1135
+ disposers.push(ctx.webServer.register({
1136
+ kind: "exact",
1137
+ path: "/archived-sessions/details",
1138
+ handler: async (req, res) => {
1139
+ try {
1140
+ const body = await readJsonBody(req);
1141
+ const sid = body && typeof body.sessionId === "string" ? body.sessionId : null;
1142
+ if (!sid) return json(res, { ok: false, error: "missing sessionId" }, 400);
1143
+ json(res, await buildDetails(sid));
1144
+ } catch (e) {
1145
+ json(res, { error: String(e && e.message || e) }, e && e.status ? e.status : 500);
1146
+ }
1147
+ }
1148
+ }));
1149
+ return () => {
1150
+ for (const d of disposers) d();
1151
+ };
1152
+ }, "dsh-sessions-manager: routes");
1153
+ }
1154
+ export {
1155
+ apply,
1156
+ inject,
1157
+ name
1158
+ };
1159
+ //# sourceMappingURL=index.js.map