dsh-sessions-manager 3.6.2 → 3.7.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 +21 -10
- package/README.md +23 -12
- package/lib/client.js +990 -32
- package/lib/client.js.map +2 -2
- package/lib/index.js +921 -151
- package/lib/index.js.map +4 -4
- package/package.json +1 -1
- package/src/client/index.jsx +816 -33
- package/src/client/logic.js +285 -0
- package/src/empty-scan-index.js +118 -0
- package/src/index.js +340 -79
- package/src/lineage.js +18 -14
- package/src/move-notices.js +129 -0
- package/src/saved-filters.js +208 -0
- package/src/tag-index.js +308 -0
package/lib/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// src/index.js
|
|
2
|
-
import { mkdir as
|
|
3
|
-
import { basename as basename3, dirname as
|
|
4
|
-
import { readFileSync as
|
|
5
|
-
import { homedir as
|
|
2
|
+
import { mkdir as mkdir10, readFile as readFile4, readdir as readdir4, realpath, rename as rename9, rm as rm3, stat as stat4, unlink, writeFile as writeFile10 } from "node:fs/promises";
|
|
3
|
+
import { basename as basename3, dirname as dirname4, isAbsolute, join as join12 } from "node:path";
|
|
4
|
+
import { readFileSync as readFileSync7 } from "node:fs";
|
|
5
|
+
import { homedir as homedir7 } from "node:os";
|
|
6
6
|
|
|
7
7
|
// src/zstd-frame.js
|
|
8
8
|
import zlib from "node:zlib";
|
|
@@ -303,13 +303,358 @@ function createStarIndex(options = {}) {
|
|
|
303
303
|
return { read, write, mutate, setStarred, removeIds, indexPath, dir };
|
|
304
304
|
}
|
|
305
305
|
|
|
306
|
-
// src/
|
|
306
|
+
// src/tag-index.js
|
|
307
|
+
import { mkdir as mkdir2, rename as fsRename, writeFile as writeFile2 } from "node:fs/promises";
|
|
307
308
|
import { readFileSync as readFileSync2 } from "node:fs";
|
|
308
|
-
import {
|
|
309
|
+
import { randomBytes } from "node:crypto";
|
|
309
310
|
import { homedir as homedir2 } from "node:os";
|
|
310
311
|
import { join as join2 } from "node:path";
|
|
312
|
+
var TAG_SCHEMA_VERSION = 4;
|
|
313
|
+
var MAX_TAGS = 200;
|
|
314
|
+
var MAX_TAGS_PER_SESSION = 10;
|
|
315
|
+
var MAX_TAG_NAME = 24;
|
|
316
|
+
var DEFAULT_TAG_DIR = join2(homedir2(), ".dsh", "sessions-manager");
|
|
317
|
+
var TAG_ID_ALPHABET = "abcdefghijklmnopqrstuvwxyz0123456789";
|
|
318
|
+
function randomSuffix() {
|
|
319
|
+
const bytes = randomBytes(8);
|
|
320
|
+
let out = "";
|
|
321
|
+
for (const b of bytes) out += TAG_ID_ALPHABET[b % TAG_ID_ALPHABET.length];
|
|
322
|
+
return out;
|
|
323
|
+
}
|
|
324
|
+
function isSafeSessionId2(value) {
|
|
325
|
+
return typeof value === "string" && value.length > 0 && value.length <= 200 && !/[\\/\0]/.test(value) && value !== "." && value !== "..";
|
|
326
|
+
}
|
|
327
|
+
function isSafeTagId(value) {
|
|
328
|
+
return typeof value === "string" && value.length > 0 && value.length <= 64 && !/[\\/\0]/.test(value);
|
|
329
|
+
}
|
|
330
|
+
function tagError(message, status, code) {
|
|
331
|
+
const error = new Error(message);
|
|
332
|
+
error.status = status;
|
|
333
|
+
if (code) error.code = code;
|
|
334
|
+
throw error;
|
|
335
|
+
}
|
|
336
|
+
function normalizeTagName(value) {
|
|
337
|
+
if (typeof value !== "string") return null;
|
|
338
|
+
const name2 = value.trim();
|
|
339
|
+
if (!name2 || /[\\/\0]/.test(name2)) return null;
|
|
340
|
+
if (Array.from(name2).length > MAX_TAG_NAME) return null;
|
|
341
|
+
return name2;
|
|
342
|
+
}
|
|
343
|
+
function nameKey(name2) {
|
|
344
|
+
return name2.toLocaleLowerCase();
|
|
345
|
+
}
|
|
346
|
+
function normalizeTagStore(raw) {
|
|
347
|
+
const source = raw && typeof raw === "object" && !Array.isArray(raw) ? raw : null;
|
|
348
|
+
const tags = [];
|
|
349
|
+
const ids = /* @__PURE__ */ new Set();
|
|
350
|
+
const names = /* @__PURE__ */ new Set();
|
|
351
|
+
for (const item of source && Array.isArray(source.tags) ? source.tags : []) {
|
|
352
|
+
if (!item || typeof item !== "object") continue;
|
|
353
|
+
if (!isSafeTagId(item.id) || ids.has(item.id)) continue;
|
|
354
|
+
const name2 = normalizeTagName(item.name);
|
|
355
|
+
if (name2 === null) continue;
|
|
356
|
+
const key = nameKey(name2);
|
|
357
|
+
if (names.has(key)) continue;
|
|
358
|
+
ids.add(item.id);
|
|
359
|
+
names.add(key);
|
|
360
|
+
const createdAt = Number.isFinite(Number(item.createdAt)) ? Number(item.createdAt) : 0;
|
|
361
|
+
tags.push({ id: item.id, name: name2, createdAt });
|
|
362
|
+
if (tags.length >= MAX_TAGS) break;
|
|
363
|
+
}
|
|
364
|
+
const assignments = {};
|
|
365
|
+
const rawAssignments = source && source.assignments && typeof source.assignments === "object" && !Array.isArray(source.assignments) ? source.assignments : null;
|
|
366
|
+
if (rawAssignments) {
|
|
367
|
+
for (const [sid, value] of Object.entries(rawAssignments)) {
|
|
368
|
+
if (!isSafeSessionId2(sid) || !Array.isArray(value)) continue;
|
|
369
|
+
const kept = [];
|
|
370
|
+
const seen = /* @__PURE__ */ new Set();
|
|
371
|
+
for (const tagId of value) {
|
|
372
|
+
if (!ids.has(tagId) || seen.has(tagId)) continue;
|
|
373
|
+
seen.add(tagId);
|
|
374
|
+
kept.push(tagId);
|
|
375
|
+
if (kept.length >= MAX_TAGS_PER_SESSION) break;
|
|
376
|
+
}
|
|
377
|
+
if (kept.length) assignments[sid] = kept;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
return { schemaVersion: TAG_SCHEMA_VERSION, tags, assignments };
|
|
381
|
+
}
|
|
382
|
+
function createTagIndex(options = {}) {
|
|
383
|
+
const dir = options.dir || process.env.DSH_SESSIONS_MANAGER_STAR_DIR || DEFAULT_TAG_DIR;
|
|
384
|
+
const indexPath = options.indexPath || join2(dir, "tags.json");
|
|
385
|
+
let mutation = Promise.resolve();
|
|
386
|
+
async function read() {
|
|
387
|
+
try {
|
|
388
|
+
return normalizeTagStore(JSON.parse(readFileSync2(indexPath, "utf8")));
|
|
389
|
+
} catch {
|
|
390
|
+
return normalizeTagStore(null);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
async function write(store) {
|
|
394
|
+
await mkdir2(dir, { recursive: true });
|
|
395
|
+
const tmp = join2(dir, `.tags-${process.pid}-${Date.now()}.tmp`);
|
|
396
|
+
await writeFile2(tmp, JSON.stringify(normalizeTagStore(store), null, 2), { encoding: "utf8", mode: 384 });
|
|
397
|
+
await fsRename(tmp, indexPath);
|
|
398
|
+
}
|
|
399
|
+
function mutate(mutator) {
|
|
400
|
+
const operation = mutation.then(async () => {
|
|
401
|
+
const store = await read();
|
|
402
|
+
const result = await mutator(store);
|
|
403
|
+
await write(store);
|
|
404
|
+
return result;
|
|
405
|
+
});
|
|
406
|
+
mutation = operation.catch(() => {
|
|
407
|
+
});
|
|
408
|
+
return operation;
|
|
409
|
+
}
|
|
410
|
+
function newTagId(store) {
|
|
411
|
+
for (let i = 0; i < 16; i++) {
|
|
412
|
+
const id = `t_${randomSuffix()}`;
|
|
413
|
+
if (!store.tags.some((t) => t.id === id)) return id;
|
|
414
|
+
}
|
|
415
|
+
return tagError("\u65E0\u6CD5\u751F\u6210\u6807\u7B7E id\uFF08\u968F\u673A\u78B0\u649E\u5F02\u5E38\uFF09", 500, "DSM_TAG_ID_COLLISION");
|
|
416
|
+
}
|
|
417
|
+
async function create(rawName) {
|
|
418
|
+
const name2 = normalizeTagName(rawName);
|
|
419
|
+
if (name2 === null) tagError(`\u6807\u7B7E\u540D\u65E0\u6548\uFF08\u975E\u7A7A\u3001\u4E0D\u542B\u659C\u6760\u3001\u4E0D\u8D85\u8FC7 ${MAX_TAG_NAME} \u4E2A\u5B57\u7B26\uFF09`, 400, "DSM_TAG_NAME_INVALID");
|
|
420
|
+
const key = nameKey(name2);
|
|
421
|
+
return mutate((store) => {
|
|
422
|
+
if (store.tags.some((t) => nameKey(t.name) === key)) tagError("\u540C\u540D\u6807\u7B7E\u5DF2\u5B58\u5728", 409, "DSM_TAG_EXISTS");
|
|
423
|
+
if (store.tags.length >= MAX_TAGS) tagError(`\u6807\u7B7E\u603B\u6570\u5DF2\u8FBE\u4E0A\u9650\uFF08${MAX_TAGS}\uFF09`, 409, "DSM_TAG_LIMIT");
|
|
424
|
+
const tag = { id: newTagId(store), name: name2, createdAt: Date.now() };
|
|
425
|
+
store.tags.push(tag);
|
|
426
|
+
return tag;
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
async function rename10(id, rawName) {
|
|
430
|
+
const name2 = normalizeTagName(rawName);
|
|
431
|
+
if (name2 === null) tagError(`\u6807\u7B7E\u540D\u65E0\u6548\uFF08\u975E\u7A7A\u3001\u4E0D\u542B\u659C\u6760\u3001\u4E0D\u8D85\u8FC7 ${MAX_TAG_NAME} \u4E2A\u5B57\u7B26\uFF09`, 400, "DSM_TAG_NAME_INVALID");
|
|
432
|
+
const key = nameKey(name2);
|
|
433
|
+
return mutate((store) => {
|
|
434
|
+
const tag = store.tags.find((t) => t.id === id);
|
|
435
|
+
if (!tag) tagError("\u6807\u7B7E\u4E0D\u5B58\u5728", 404, "DSM_TAG_NOT_FOUND");
|
|
436
|
+
if (store.tags.some((t) => t.id !== tag.id && nameKey(t.name) === key)) tagError("\u540C\u540D\u6807\u7B7E\u5DF2\u5B58\u5728", 409, "DSM_TAG_EXISTS");
|
|
437
|
+
tag.name = name2;
|
|
438
|
+
return tag;
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
async function merge(fromId, toId) {
|
|
442
|
+
if (!isSafeTagId(fromId) || !isSafeTagId(toId) || fromId === toId) {
|
|
443
|
+
tagError("\u5408\u5E76\u7684\u6E90/\u76EE\u6807\u6807\u7B7E\u65E0\u6548\u6216\u76F8\u540C", 400, "DSM_TAG_INVALID");
|
|
444
|
+
}
|
|
445
|
+
return mutate((store) => {
|
|
446
|
+
if (!store.tags.some((t) => t.id === fromId) || !store.tags.some((t) => t.id === toId)) {
|
|
447
|
+
tagError("\u6807\u7B7E\u4E0D\u5B58\u5728", 404, "DSM_TAG_NOT_FOUND");
|
|
448
|
+
}
|
|
449
|
+
for (const [sid, list2] of Object.entries(store.assignments)) {
|
|
450
|
+
if (!list2.includes(fromId)) continue;
|
|
451
|
+
const merged = [...new Set(list2.filter((x) => x !== fromId).concat(toId))];
|
|
452
|
+
store.assignments[sid] = merged.slice(0, MAX_TAGS_PER_SESSION);
|
|
453
|
+
}
|
|
454
|
+
store.tags = store.tags.filter((t) => t.id !== fromId);
|
|
455
|
+
return { merged: true };
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
function removeTag(id) {
|
|
459
|
+
return mutate((store) => {
|
|
460
|
+
if (isSafeTagId(id)) {
|
|
461
|
+
store.tags = store.tags.filter((t) => t.id !== id);
|
|
462
|
+
for (const [sid, list2] of Object.entries(store.assignments)) {
|
|
463
|
+
const kept = list2.filter((x) => x !== id);
|
|
464
|
+
if (kept.length) store.assignments[sid] = kept;
|
|
465
|
+
else delete store.assignments[sid];
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
return { removed: true };
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
async function setTags(sessionId, tagIds) {
|
|
472
|
+
if (!isSafeSessionId2(sessionId)) tagError("\u65E0\u6548\u7684 sessionId", 400, "DSM_TAG_SESSION_INVALID");
|
|
473
|
+
if (!Array.isArray(tagIds)) tagError("tagIds \u5FC5\u987B\u662F\u6570\u7EC4", 400, "DSM_TAG_IDS_INVALID");
|
|
474
|
+
const seen = /* @__PURE__ */ new Set();
|
|
475
|
+
const wanted = [];
|
|
476
|
+
for (const v of tagIds) {
|
|
477
|
+
if (!isSafeTagId(v)) tagError("\u65E0\u6548\u7684\u6807\u7B7E id", 400, "DSM_TAG_UNKNOWN");
|
|
478
|
+
if (seen.has(v)) continue;
|
|
479
|
+
seen.add(v);
|
|
480
|
+
wanted.push(v);
|
|
481
|
+
}
|
|
482
|
+
if (wanted.length > MAX_TAGS_PER_SESSION) tagError(`\u5355\u4E2A\u4F1A\u8BDD\u6700\u591A ${MAX_TAGS_PER_SESSION} \u4E2A\u6807\u7B7E`, 409, "DSM_TAG_LIMIT");
|
|
483
|
+
return mutate((store) => {
|
|
484
|
+
const known = new Set(store.tags.map((t) => t.id));
|
|
485
|
+
const unknown = wanted.filter((id) => !known.has(id));
|
|
486
|
+
if (unknown.length) tagError(`\u672A\u77E5\u7684\u6807\u7B7E id\uFF1A${unknown.join("\u3001")}`, 400, "DSM_TAG_UNKNOWN");
|
|
487
|
+
if (wanted.length) store.assignments[sessionId] = [...wanted];
|
|
488
|
+
else delete store.assignments[sessionId];
|
|
489
|
+
return store.assignments;
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
function removeIds(ids) {
|
|
493
|
+
const wanted = (Array.isArray(ids) ? ids : []).filter(isSafeSessionId2);
|
|
494
|
+
return mutate((store) => {
|
|
495
|
+
for (const sid of wanted) delete store.assignments[sid];
|
|
496
|
+
return store.assignments;
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
async function list() {
|
|
500
|
+
const store = await read();
|
|
501
|
+
return { tags: store.tags, assignments: store.assignments };
|
|
502
|
+
}
|
|
503
|
+
return { read, write, mutate, list, create, rename: rename10, merge, removeTag, setTags, removeIds, indexPath, dir };
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// src/saved-filters.js
|
|
507
|
+
import { mkdir as mkdir3, rename as rename2, writeFile as writeFile3 } from "node:fs/promises";
|
|
508
|
+
import { readFileSync as readFileSync3 } from "node:fs";
|
|
509
|
+
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
510
|
+
import { homedir as homedir3 } from "node:os";
|
|
511
|
+
import { join as join3 } from "node:path";
|
|
512
|
+
var FILTER_SCHEMA_VERSION = 1;
|
|
513
|
+
var MAX_FILTERS = 20;
|
|
514
|
+
var MAX_FILTER_NAME = 40;
|
|
515
|
+
var MAX_FILTER_JSON = 2048;
|
|
516
|
+
var DEFAULT_FILTER_DIR = join3(homedir3(), ".dsh", "sessions-manager");
|
|
517
|
+
var FILTER_ID_ALPHABET = "abcdefghijklmnopqrstuvwxyz0123456789";
|
|
518
|
+
function randomSuffix2() {
|
|
519
|
+
const bytes = randomBytes2(8);
|
|
520
|
+
let out = "";
|
|
521
|
+
for (const b of bytes) out += FILTER_ID_ALPHABET[b % FILTER_ID_ALPHABET.length];
|
|
522
|
+
return out;
|
|
523
|
+
}
|
|
524
|
+
function isSafeFilterId(value) {
|
|
525
|
+
return typeof value === "string" && value.length > 0 && value.length <= 64 && !/[\\/\0]/.test(value);
|
|
526
|
+
}
|
|
527
|
+
function filterError(message, status, code) {
|
|
528
|
+
const error = new Error(message);
|
|
529
|
+
error.status = status;
|
|
530
|
+
if (code) error.code = code;
|
|
531
|
+
throw error;
|
|
532
|
+
}
|
|
533
|
+
function normalizeFilterName(value) {
|
|
534
|
+
if (typeof value !== "string") return null;
|
|
535
|
+
const name2 = value.trim();
|
|
536
|
+
if (!name2 || /[\\/\0]/.test(name2)) return null;
|
|
537
|
+
if (Array.from(name2).length > MAX_FILTER_NAME) return null;
|
|
538
|
+
return name2;
|
|
539
|
+
}
|
|
540
|
+
function nameKey2(name2) {
|
|
541
|
+
return name2.toLocaleLowerCase();
|
|
542
|
+
}
|
|
543
|
+
function normalizeFiltersPayload(value) {
|
|
544
|
+
let serialized;
|
|
545
|
+
try {
|
|
546
|
+
serialized = JSON.stringify(value);
|
|
547
|
+
} catch {
|
|
548
|
+
return null;
|
|
549
|
+
}
|
|
550
|
+
if (typeof serialized !== "string" || serialized.length > MAX_FILTER_JSON) return null;
|
|
551
|
+
try {
|
|
552
|
+
return JSON.parse(serialized);
|
|
553
|
+
} catch {
|
|
554
|
+
return null;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
function normalizeFilterStore(raw) {
|
|
558
|
+
const source = raw && typeof raw === "object" && !Array.isArray(raw) ? raw : null;
|
|
559
|
+
const items = [];
|
|
560
|
+
const ids = /* @__PURE__ */ new Set();
|
|
561
|
+
const names = /* @__PURE__ */ new Set();
|
|
562
|
+
for (const item of source && Array.isArray(source.items) ? source.items : []) {
|
|
563
|
+
if (!item || typeof item !== "object") continue;
|
|
564
|
+
if (!isSafeFilterId(item.id) || ids.has(item.id)) continue;
|
|
565
|
+
const name2 = normalizeFilterName(item.name);
|
|
566
|
+
if (name2 === null) continue;
|
|
567
|
+
const key = nameKey2(name2);
|
|
568
|
+
if (names.has(key)) continue;
|
|
569
|
+
const filters = normalizeFiltersPayload(item.filters);
|
|
570
|
+
if (filters === null) continue;
|
|
571
|
+
ids.add(item.id);
|
|
572
|
+
names.add(key);
|
|
573
|
+
const createdAt = Number.isFinite(Number(item.createdAt)) ? Number(item.createdAt) : 0;
|
|
574
|
+
items.push({ id: item.id, name: name2, filters, createdAt });
|
|
575
|
+
if (items.length >= MAX_FILTERS) break;
|
|
576
|
+
}
|
|
577
|
+
return { schemaVersion: FILTER_SCHEMA_VERSION, items };
|
|
578
|
+
}
|
|
579
|
+
function createSavedFilters(options = {}) {
|
|
580
|
+
const dir = options.dir || process.env.DSH_SESSIONS_MANAGER_STAR_DIR || DEFAULT_FILTER_DIR;
|
|
581
|
+
const indexPath = options.indexPath || join3(dir, "saved-filters.json");
|
|
582
|
+
let mutation = Promise.resolve();
|
|
583
|
+
async function read() {
|
|
584
|
+
try {
|
|
585
|
+
return normalizeFilterStore(JSON.parse(readFileSync3(indexPath, "utf8")));
|
|
586
|
+
} catch {
|
|
587
|
+
return normalizeFilterStore(null);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
async function write(store) {
|
|
591
|
+
await mkdir3(dir, { recursive: true });
|
|
592
|
+
const tmp = join3(dir, `.saved-filters-${process.pid}-${Date.now()}.tmp`);
|
|
593
|
+
await writeFile3(tmp, JSON.stringify(normalizeFilterStore(store), null, 2), { encoding: "utf8", mode: 384 });
|
|
594
|
+
await rename2(tmp, indexPath);
|
|
595
|
+
}
|
|
596
|
+
function mutate(mutator) {
|
|
597
|
+
const operation = mutation.then(async () => {
|
|
598
|
+
const store = await read();
|
|
599
|
+
const result = await mutator(store);
|
|
600
|
+
await write(store);
|
|
601
|
+
return result;
|
|
602
|
+
});
|
|
603
|
+
mutation = operation.catch(() => {
|
|
604
|
+
});
|
|
605
|
+
return operation;
|
|
606
|
+
}
|
|
607
|
+
function newFilterId(store) {
|
|
608
|
+
for (let i = 0; i < 16; i++) {
|
|
609
|
+
const id = `f_${randomSuffix2()}`;
|
|
610
|
+
if (!store.items.some((t) => t.id === id)) return id;
|
|
611
|
+
}
|
|
612
|
+
return filterError("\u65E0\u6CD5\u751F\u6210\u7B5B\u9009 id\uFF08\u968F\u673A\u78B0\u649E\u5F02\u5E38\uFF09", 500, "DSM_FILTER_ID_COLLISION");
|
|
613
|
+
}
|
|
614
|
+
async function save(rawName, filters) {
|
|
615
|
+
const name2 = normalizeFilterName(rawName);
|
|
616
|
+
if (name2 === null) filterError(`\u7B5B\u9009\u540D\u65E0\u6548\uFF08\u975E\u7A7A\u3001\u4E0D\u542B\u659C\u6760\u3001\u4E0D\u8D85\u8FC7 ${MAX_FILTER_NAME} \u4E2A\u5B57\u7B26\uFF09`, 400, "DSM_FILTER_NAME_INVALID");
|
|
617
|
+
let serialized;
|
|
618
|
+
try {
|
|
619
|
+
serialized = JSON.stringify(filters);
|
|
620
|
+
} catch {
|
|
621
|
+
serialized = void 0;
|
|
622
|
+
}
|
|
623
|
+
if (typeof serialized !== "string") filterError("\u7B5B\u9009\u6761\u4EF6\u5FC5\u987B\u662F\u53EF JSON \u5E8F\u5217\u5316\u7684\u6570\u636E", 400, "DSM_FILTER_INVALID");
|
|
624
|
+
if (serialized === "null") filterError("\u7B5B\u9009\u6761\u4EF6\u4E0D\u80FD\u4E3A null", 400, "DSM_FILTER_INVALID");
|
|
625
|
+
if (serialized.length > MAX_FILTER_JSON) filterError(`\u7B5B\u9009\u6761\u4EF6\u8FC7\u5927\uFF08\u5E8F\u5217\u5316\u540E\u6700\u591A ${MAX_FILTER_JSON} \u5B57\u7B26\uFF09`, 400, "DSM_FILTER_TOO_LARGE");
|
|
626
|
+
const payload = JSON.parse(serialized);
|
|
627
|
+
const key = nameKey2(name2);
|
|
628
|
+
return mutate((store) => {
|
|
629
|
+
if (store.items.some((t) => nameKey2(t.name) === key)) filterError("\u540C\u540D\u7B5B\u9009\u5DF2\u5B58\u5728", 409, "DSM_FILTER_EXISTS");
|
|
630
|
+
if (store.items.length >= MAX_FILTERS) filterError(`\u4FDD\u5B58\u7684\u7B5B\u9009\u5DF2\u8FBE\u4E0A\u9650\uFF08${MAX_FILTERS}\uFF09`, 409, "DSM_FILTER_LIMIT");
|
|
631
|
+
const item = { id: newFilterId(store), name: name2, filters: payload, createdAt: Date.now() };
|
|
632
|
+
store.items.push(item);
|
|
633
|
+
return item;
|
|
634
|
+
});
|
|
635
|
+
}
|
|
636
|
+
function remove(ids) {
|
|
637
|
+
const wanted = new Set((Array.isArray(ids) ? ids : []).filter(isSafeFilterId).map(String));
|
|
638
|
+
return mutate((store) => {
|
|
639
|
+
const before = store.items.length;
|
|
640
|
+
store.items = store.items.filter((t) => !wanted.has(t.id));
|
|
641
|
+
return before - store.items.length;
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
async function list() {
|
|
645
|
+
const store = await read();
|
|
646
|
+
return store.items;
|
|
647
|
+
}
|
|
648
|
+
return { read, write, mutate, list, save, remove, indexPath, dir };
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
// src/pending-moves.js
|
|
652
|
+
import { readFileSync as readFileSync4 } from "node:fs";
|
|
653
|
+
import { mkdir as mkdir4, rename as rename3, writeFile as writeFile4 } from "node:fs/promises";
|
|
654
|
+
import { homedir as homedir4 } from "node:os";
|
|
655
|
+
import { join as join4 } from "node:path";
|
|
311
656
|
var SCHEMA_VERSION = 1;
|
|
312
|
-
var DEFAULT_DIR = process.env.DSH_SESSIONS_MANAGER_PENDING_DIR || process.env.DSH_SESSIONS_MANAGER_STAR_DIR ||
|
|
657
|
+
var DEFAULT_DIR = process.env.DSH_SESSIONS_MANAGER_PENDING_DIR || process.env.DSH_SESSIONS_MANAGER_STAR_DIR || join4(homedir4(), ".dsh", "sessions-manager");
|
|
313
658
|
var MAX_ITEMS = 50;
|
|
314
659
|
var MAX_ATTEMPTS = 5;
|
|
315
660
|
function isSafeId(value) {
|
|
@@ -335,20 +680,20 @@ function normalizeStore(raw) {
|
|
|
335
680
|
}
|
|
336
681
|
function createPendingMoveStore(options = {}) {
|
|
337
682
|
const dir = options.dir || DEFAULT_DIR;
|
|
338
|
-
const indexPath = options.indexPath ||
|
|
683
|
+
const indexPath = options.indexPath || join4(dir, "pending-moves.json");
|
|
339
684
|
let mutation = Promise.resolve();
|
|
340
685
|
async function read() {
|
|
341
686
|
try {
|
|
342
|
-
return normalizeStore(JSON.parse(
|
|
687
|
+
return normalizeStore(JSON.parse(readFileSync4(indexPath, "utf8")));
|
|
343
688
|
} catch (e) {
|
|
344
689
|
return normalizeStore(null);
|
|
345
690
|
}
|
|
346
691
|
}
|
|
347
692
|
async function write(store) {
|
|
348
|
-
await
|
|
349
|
-
const tmp =
|
|
350
|
-
await
|
|
351
|
-
await
|
|
693
|
+
await mkdir4(dir, { recursive: true });
|
|
694
|
+
const tmp = join4(dir, `.pending-moves-${process.pid}-${Date.now()}.tmp`);
|
|
695
|
+
await writeFile4(tmp, JSON.stringify(normalizeStore(store), null, 2), { encoding: "utf8", mode: 384 });
|
|
696
|
+
await rename3(tmp, indexPath);
|
|
352
697
|
}
|
|
353
698
|
function mutate(mutator) {
|
|
354
699
|
const operation = mutation.then(async () => {
|
|
@@ -398,9 +743,106 @@ function createPendingMoveStore(options = {}) {
|
|
|
398
743
|
};
|
|
399
744
|
}
|
|
400
745
|
|
|
746
|
+
// src/move-notices.js
|
|
747
|
+
import { readFileSync as readFileSync5 } from "node:fs";
|
|
748
|
+
import { mkdir as mkdir5, rename as rename4, writeFile as writeFile5 } from "node:fs/promises";
|
|
749
|
+
import { homedir as homedir5 } from "node:os";
|
|
750
|
+
import { join as join5 } from "node:path";
|
|
751
|
+
var SCHEMA_VERSION2 = 1;
|
|
752
|
+
var MAX_NOTICES = 20;
|
|
753
|
+
var NOTICE_MAX_AGE_MS = 7 * 24 * 3600 * 1e3;
|
|
754
|
+
var KINDS = /* @__PURE__ */ new Set(["moved", "abandoned"]);
|
|
755
|
+
function isSafeId2(value) {
|
|
756
|
+
return typeof value === "string" && value.length > 0 && value.length <= 200;
|
|
757
|
+
}
|
|
758
|
+
function str(v, max) {
|
|
759
|
+
return typeof v === "string" && v ? v.slice(0, max) : null;
|
|
760
|
+
}
|
|
761
|
+
function normalizeNotice(raw) {
|
|
762
|
+
if (!raw || typeof raw !== "object") return null;
|
|
763
|
+
if (!KINDS.has(raw.kind) || !isSafeId2(raw.sessionId)) return null;
|
|
764
|
+
const at = Number.isFinite(raw.at) && raw.at > 0 ? Math.floor(raw.at) : 0;
|
|
765
|
+
if (!at) return null;
|
|
766
|
+
const sessionId = String(raw.sessionId);
|
|
767
|
+
return {
|
|
768
|
+
id: str(raw.id, 400) || `${sessionId}:${raw.kind}:${at}`,
|
|
769
|
+
kind: raw.kind,
|
|
770
|
+
sessionId,
|
|
771
|
+
targetPath: str(raw.targetPath, 500),
|
|
772
|
+
attempts: Number.isSafeInteger(raw.attempts) && raw.attempts >= 0 ? raw.attempts : null,
|
|
773
|
+
reason: str(raw.reason, 300),
|
|
774
|
+
at
|
|
775
|
+
};
|
|
776
|
+
}
|
|
777
|
+
function normalizeStore2(raw) {
|
|
778
|
+
const source = raw && Array.isArray(raw.items) ? raw.items : [];
|
|
779
|
+
const seen = /* @__PURE__ */ new Set();
|
|
780
|
+
const items = [];
|
|
781
|
+
for (const entry of source) {
|
|
782
|
+
const n = normalizeNotice(entry);
|
|
783
|
+
if (!n || seen.has(n.id)) continue;
|
|
784
|
+
seen.add(n.id);
|
|
785
|
+
items.push(n);
|
|
786
|
+
}
|
|
787
|
+
items.sort((a, b) => a.at - b.at);
|
|
788
|
+
const trimmed = items.length > MAX_NOTICES ? items.slice(items.length - MAX_NOTICES) : items;
|
|
789
|
+
return { schemaVersion: SCHEMA_VERSION2, items: trimmed };
|
|
790
|
+
}
|
|
791
|
+
function createMoveNoticeStore(options = {}) {
|
|
792
|
+
const dir = options.dir || process.env.DSH_SESSIONS_MANAGER_PENDING_DIR || process.env.DSH_SESSIONS_MANAGER_STAR_DIR || join5(homedir5(), ".dsh", "sessions-manager");
|
|
793
|
+
const indexPath = options.indexPath || join5(dir, "move-notices.json");
|
|
794
|
+
let mutation = Promise.resolve();
|
|
795
|
+
function read() {
|
|
796
|
+
try {
|
|
797
|
+
return normalizeStore2(JSON.parse(readFileSync5(indexPath, "utf8")));
|
|
798
|
+
} catch (e) {
|
|
799
|
+
return normalizeStore2(null);
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
async function write(store) {
|
|
803
|
+
await mkdir5(dir, { recursive: true });
|
|
804
|
+
const tmp = join5(dir, `.move-notices-${process.pid}-${Date.now()}.tmp`);
|
|
805
|
+
await writeFile5(tmp, JSON.stringify(normalizeStore2(store), null, 2), { encoding: "utf8", mode: 384 });
|
|
806
|
+
await rename4(tmp, indexPath);
|
|
807
|
+
}
|
|
808
|
+
function mutate(mutator) {
|
|
809
|
+
const operation = mutation.then(async () => {
|
|
810
|
+
const store = read();
|
|
811
|
+
const result = await mutator(store);
|
|
812
|
+
await write(store);
|
|
813
|
+
return result;
|
|
814
|
+
});
|
|
815
|
+
mutation = operation.catch(() => {
|
|
816
|
+
});
|
|
817
|
+
return operation;
|
|
818
|
+
}
|
|
819
|
+
return {
|
|
820
|
+
indexPath,
|
|
821
|
+
// 终局落一条通知;同 id(会话+类型+时刻)幂等去重。
|
|
822
|
+
append: (notice) => mutate((store) => {
|
|
823
|
+
const n = normalizeNotice(notice);
|
|
824
|
+
if (!n) return null;
|
|
825
|
+
if (store.items.some((x) => x.id === n.id)) return n;
|
|
826
|
+
store.items.push(n);
|
|
827
|
+
return n;
|
|
828
|
+
}),
|
|
829
|
+
// 只读列出未过期、未 ack 的通知(按时间升序)。过期项在下次写入时随归一化清掉。
|
|
830
|
+
async list() {
|
|
831
|
+
const now = Date.now();
|
|
832
|
+
return read().items.filter((n) => now - n.at <= NOTICE_MAX_AGE_MS);
|
|
833
|
+
},
|
|
834
|
+
ack: (ids) => mutate((store) => {
|
|
835
|
+
const drop = new Set((Array.isArray(ids) ? ids : []).filter((v) => typeof v === "string" && v.length <= 400).map(String));
|
|
836
|
+
const before = store.items.length;
|
|
837
|
+
store.items = store.items.filter((n) => !drop.has(n.id));
|
|
838
|
+
return before - store.items.length;
|
|
839
|
+
})
|
|
840
|
+
};
|
|
841
|
+
}
|
|
842
|
+
|
|
401
843
|
// src/state-temp-sweep.js
|
|
402
844
|
import { readdir, rm, stat } from "node:fs/promises";
|
|
403
|
-
import { join as
|
|
845
|
+
import { join as join6 } from "node:path";
|
|
404
846
|
var TEMP_NAME_RE = /^\..+-\d+-\d+\.tmp$/;
|
|
405
847
|
async function sweepStaleStateTemps(dirs, options = {}) {
|
|
406
848
|
const maxAgeMs = Number.isSafeInteger(options.maxAgeMs) && options.maxAgeMs >= 0 ? options.maxAgeMs : 36e5;
|
|
@@ -416,7 +858,7 @@ async function sweepStaleStateTemps(dirs, options = {}) {
|
|
|
416
858
|
}
|
|
417
859
|
for (const name2 of entries) {
|
|
418
860
|
if (!TEMP_NAME_RE.test(name2)) continue;
|
|
419
|
-
const path =
|
|
861
|
+
const path = join6(dir, name2);
|
|
420
862
|
let info;
|
|
421
863
|
try {
|
|
422
864
|
info = await stat(path);
|
|
@@ -487,15 +929,15 @@ function aggregateStorage(items, options = {}) {
|
|
|
487
929
|
}
|
|
488
930
|
|
|
489
931
|
// src/auto-archive.js
|
|
490
|
-
import { mkdir as
|
|
491
|
-
import { readFileSync as
|
|
492
|
-
import { homedir as
|
|
493
|
-
import { join as
|
|
932
|
+
import { mkdir as mkdir6, rename as rename5, writeFile as writeFile6 } from "node:fs/promises";
|
|
933
|
+
import { readFileSync as readFileSync6 } from "node:fs";
|
|
934
|
+
import { homedir as homedir6 } from "node:os";
|
|
935
|
+
import { join as join7 } from "node:path";
|
|
494
936
|
var AUTO_ARCHIVE_SCHEMA_VERSION = 4;
|
|
495
937
|
var INACTIVE_DAY_OPTIONS = Object.freeze([0, 30, 60, 90]);
|
|
496
938
|
var DAY_MS = 864e5;
|
|
497
939
|
var RUN_INTERVAL_MS = DAY_MS;
|
|
498
|
-
var DEFAULT_DIR2 =
|
|
940
|
+
var DEFAULT_DIR2 = join7(homedir6(), ".dsh", "sessions-manager");
|
|
499
941
|
function normalizeAutoArchiveStore(raw) {
|
|
500
942
|
const source = raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
|
|
501
943
|
const settings = source.settings && typeof source.settings === "object" ? source.settings : {};
|
|
@@ -540,20 +982,20 @@ function pickInactiveCandidates(items, options = {}) {
|
|
|
540
982
|
}
|
|
541
983
|
function createAutoArchiveStore(options = {}) {
|
|
542
984
|
const dir = options.dir || process.env.DSH_SESSIONS_MANAGER_AUTO_ARCHIVE_DIR || DEFAULT_DIR2;
|
|
543
|
-
const indexPath = options.indexPath ||
|
|
985
|
+
const indexPath = options.indexPath || join7(dir, "auto-archive.json");
|
|
544
986
|
let mutation = Promise.resolve();
|
|
545
987
|
async function read() {
|
|
546
988
|
try {
|
|
547
|
-
return normalizeAutoArchiveStore(JSON.parse(
|
|
989
|
+
return normalizeAutoArchiveStore(JSON.parse(readFileSync6(indexPath, "utf8")));
|
|
548
990
|
} catch {
|
|
549
991
|
return normalizeAutoArchiveStore(null);
|
|
550
992
|
}
|
|
551
993
|
}
|
|
552
994
|
async function write(store) {
|
|
553
|
-
await
|
|
554
|
-
const tmp =
|
|
555
|
-
await
|
|
556
|
-
await
|
|
995
|
+
await mkdir6(dir, { recursive: true });
|
|
996
|
+
const tmp = join7(dir, `.auto-archive-${process.pid}-${Date.now()}.tmp`);
|
|
997
|
+
await writeFile6(tmp, JSON.stringify(normalizeAutoArchiveStore(store), null, 2), { encoding: "utf8", mode: 384 });
|
|
998
|
+
await rename5(tmp, indexPath);
|
|
557
999
|
}
|
|
558
1000
|
function mutate(mutator) {
|
|
559
1001
|
const operation = mutation.then(async () => {
|
|
@@ -700,8 +1142,8 @@ function createSessionMetaCache(opts = {}) {
|
|
|
700
1142
|
}
|
|
701
1143
|
|
|
702
1144
|
// src/title-persist-index.js
|
|
703
|
-
import { mkdir as
|
|
704
|
-
import { dirname, join as
|
|
1145
|
+
import { mkdir as mkdir7, readFile, rename as rename6, writeFile as writeFile7 } from "node:fs/promises";
|
|
1146
|
+
import { dirname, join as join8 } from "node:path";
|
|
705
1147
|
var TITLE_INDEX_SCHEMA_VERSION = 2;
|
|
706
1148
|
var MAX_ENTRIES = 2e4;
|
|
707
1149
|
function normalizeEntry(raw) {
|
|
@@ -743,7 +1185,7 @@ function mergeEntries(left, right) {
|
|
|
743
1185
|
function createTitleIndexStore({ dir, file }) {
|
|
744
1186
|
let cache = null;
|
|
745
1187
|
let chain = Promise.resolve();
|
|
746
|
-
const path = file ||
|
|
1188
|
+
const path = file || join8(dir, "title-index.json");
|
|
747
1189
|
async function readRaw() {
|
|
748
1190
|
try {
|
|
749
1191
|
return normalizeTitleIndex(JSON.parse(await readFile(path, "utf8")));
|
|
@@ -778,10 +1220,10 @@ function createTitleIndexStore({ dir, file }) {
|
|
|
778
1220
|
if (!Object.keys(right).length) return false;
|
|
779
1221
|
await enqueue(async (store) => {
|
|
780
1222
|
const next = mergeEntries(store, right);
|
|
781
|
-
await
|
|
782
|
-
const tmp =
|
|
783
|
-
await
|
|
784
|
-
await
|
|
1223
|
+
await mkdir7(dirname(path), { recursive: true });
|
|
1224
|
+
const tmp = join8(dirname(path), `.title-index-${process.pid}-${Date.now()}.tmp`);
|
|
1225
|
+
await writeFile7(tmp, JSON.stringify({ schemaVersion: TITLE_INDEX_SCHEMA_VERSION, entries: next }), { encoding: "utf8", mode: 384 });
|
|
1226
|
+
await rename6(tmp, path);
|
|
785
1227
|
cache = next;
|
|
786
1228
|
});
|
|
787
1229
|
return true;
|
|
@@ -798,10 +1240,110 @@ function createTitleIndexStore({ dir, file }) {
|
|
|
798
1240
|
}
|
|
799
1241
|
}
|
|
800
1242
|
if (!changed) return;
|
|
801
|
-
await
|
|
802
|
-
const tmp =
|
|
803
|
-
await
|
|
804
|
-
await
|
|
1243
|
+
await mkdir7(dirname(path), { recursive: true });
|
|
1244
|
+
const tmp = join8(dirname(path), `.title-index-${process.pid}-${Date.now()}.tmp`);
|
|
1245
|
+
await writeFile7(tmp, JSON.stringify({ schemaVersion: TITLE_INDEX_SCHEMA_VERSION, entries: store }), { encoding: "utf8", mode: 384 });
|
|
1246
|
+
await rename6(tmp, path);
|
|
1247
|
+
});
|
|
1248
|
+
return true;
|
|
1249
|
+
}
|
|
1250
|
+
};
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
// src/empty-scan-index.js
|
|
1254
|
+
import { mkdir as mkdir8, readFile as readFile2, rename as rename7, writeFile as writeFile8 } from "node:fs/promises";
|
|
1255
|
+
import { dirname as dirname2, join as join9 } from "node:path";
|
|
1256
|
+
var EMPTY_SCAN_SCHEMA_VERSION = 1;
|
|
1257
|
+
var MAX_ENTRIES2 = 2e4;
|
|
1258
|
+
function normalizeEntry2(raw) {
|
|
1259
|
+
if (!raw || typeof raw !== "object") return null;
|
|
1260
|
+
const empty = raw.empty === 1 || raw.empty === true ? 1 : raw.empty === 0 || raw.empty === false ? 0 : null;
|
|
1261
|
+
const fingerprint = typeof raw.fingerprint === "string" && raw.fingerprint ? raw.fingerprint : null;
|
|
1262
|
+
const updatedAt = typeof raw.updatedAt === "number" ? raw.updatedAt : 0;
|
|
1263
|
+
if (empty === null || !fingerprint || fingerprint.startsWith("rev:")) return null;
|
|
1264
|
+
return { empty, fingerprint, updatedAt };
|
|
1265
|
+
}
|
|
1266
|
+
function normalizeEmptyScanIndex(raw) {
|
|
1267
|
+
const entries = {};
|
|
1268
|
+
if (raw && typeof raw === "object" && raw.entries && typeof raw.entries === "object") {
|
|
1269
|
+
for (const [id, entry] of Object.entries(raw.entries)) {
|
|
1270
|
+
if (typeof id !== "string" || !id || id.length > 200) continue;
|
|
1271
|
+
const normalized = normalizeEntry2(entry);
|
|
1272
|
+
if (normalized) entries[id] = normalized;
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
return { schemaVersion: EMPTY_SCAN_SCHEMA_VERSION, entries };
|
|
1276
|
+
}
|
|
1277
|
+
function mergeEntries2(left, right) {
|
|
1278
|
+
const merged = { ...left };
|
|
1279
|
+
for (const [id, entry] of Object.entries(right)) merged[id] = entry;
|
|
1280
|
+
const ids = Object.keys(merged);
|
|
1281
|
+
if (ids.length > MAX_ENTRIES2) {
|
|
1282
|
+
ids.sort((a, b) => (merged[a].updatedAt || 0) - (merged[b].updatedAt || 0));
|
|
1283
|
+
for (const id of ids.slice(0, ids.length - MAX_ENTRIES2)) delete merged[id];
|
|
1284
|
+
}
|
|
1285
|
+
return merged;
|
|
1286
|
+
}
|
|
1287
|
+
function createEmptyScanStore({ dir, file }) {
|
|
1288
|
+
let cache = null;
|
|
1289
|
+
let chain = Promise.resolve();
|
|
1290
|
+
const path = file || join9(dir, "empty-scan.json");
|
|
1291
|
+
async function readRaw() {
|
|
1292
|
+
try {
|
|
1293
|
+
return normalizeEmptyScanIndex(JSON.parse(await readFile2(path, "utf8")));
|
|
1294
|
+
} catch (e) {
|
|
1295
|
+
return normalizeEmptyScanIndex(null);
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
function enqueue(mutator) {
|
|
1299
|
+
const operation = chain.then(async () => {
|
|
1300
|
+
const store = cache || (cache = (await readRaw()).entries);
|
|
1301
|
+
await mutator(store);
|
|
1302
|
+
return store;
|
|
1303
|
+
});
|
|
1304
|
+
chain = operation.catch(() => {
|
|
1305
|
+
});
|
|
1306
|
+
return operation;
|
|
1307
|
+
}
|
|
1308
|
+
return {
|
|
1309
|
+
async entries() {
|
|
1310
|
+
if (cache) return cache;
|
|
1311
|
+
cache = (await readRaw()).entries;
|
|
1312
|
+
return cache;
|
|
1313
|
+
},
|
|
1314
|
+
async merge(batch) {
|
|
1315
|
+
const right = {};
|
|
1316
|
+
for (const [id, entry] of Object.entries(batch || {})) {
|
|
1317
|
+
const normalized = normalizeEntry2(entry);
|
|
1318
|
+
if (normalized) right[String(id)] = normalized;
|
|
1319
|
+
}
|
|
1320
|
+
if (!Object.keys(right).length) return false;
|
|
1321
|
+
await enqueue(async (store) => {
|
|
1322
|
+
const next = mergeEntries2(store, right);
|
|
1323
|
+
await mkdir8(dirname2(path), { recursive: true });
|
|
1324
|
+
const tmp = join9(dirname2(path), `.empty-scan-${process.pid}-${Date.now()}.tmp`);
|
|
1325
|
+
await writeFile8(tmp, JSON.stringify({ schemaVersion: EMPTY_SCAN_SCHEMA_VERSION, entries: next }), { encoding: "utf8", mode: 384 });
|
|
1326
|
+
await rename7(tmp, path);
|
|
1327
|
+
cache = next;
|
|
1328
|
+
});
|
|
1329
|
+
return true;
|
|
1330
|
+
},
|
|
1331
|
+
async remove(ids) {
|
|
1332
|
+
const wanted = new Set((ids || []).map(String));
|
|
1333
|
+
if (!wanted.size) return false;
|
|
1334
|
+
await enqueue(async (store) => {
|
|
1335
|
+
let changed = false;
|
|
1336
|
+
for (const id of wanted) {
|
|
1337
|
+
if (id in store) {
|
|
1338
|
+
delete store[id];
|
|
1339
|
+
changed = true;
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
if (!changed) return;
|
|
1343
|
+
await mkdir8(dirname2(path), { recursive: true });
|
|
1344
|
+
const tmp = join9(dirname2(path), `.empty-scan-${process.pid}-${Date.now()}.tmp`);
|
|
1345
|
+
await writeFile8(tmp, JSON.stringify({ schemaVersion: EMPTY_SCAN_SCHEMA_VERSION, entries: store }), { encoding: "utf8", mode: 384 });
|
|
1346
|
+
await rename7(tmp, path);
|
|
805
1347
|
});
|
|
806
1348
|
return true;
|
|
807
1349
|
}
|
|
@@ -810,7 +1352,7 @@ function createTitleIndexStore({ dir, file }) {
|
|
|
810
1352
|
|
|
811
1353
|
// src/handle-era-paths.js
|
|
812
1354
|
import { readdir as readdir2, stat as stat2 } from "node:fs/promises";
|
|
813
|
-
import { basename, join as
|
|
1355
|
+
import { basename, join as join10 } from "node:path";
|
|
814
1356
|
|
|
815
1357
|
// src/path-guard.js
|
|
816
1358
|
function splitSegments(target) {
|
|
@@ -883,8 +1425,8 @@ function resolveSessionRoot(sp) {
|
|
|
883
1425
|
return typeof root === "string" && root.length > 0 ? root : null;
|
|
884
1426
|
}
|
|
885
1427
|
function deriveSessionDir(root, cwd, id) {
|
|
886
|
-
const project = cwd === void 0 || cwd === null || cwd === "" ?
|
|
887
|
-
return
|
|
1428
|
+
const project = cwd === void 0 || cwd === null || cwd === "" ? join10(root, "_no-cwd") : join10(root, projectKeyFor(cwd));
|
|
1429
|
+
return join10(project, encodeSegmentFor(id));
|
|
888
1430
|
}
|
|
889
1431
|
function generationVersionOf(name2) {
|
|
890
1432
|
return Number((String(name2).match(/^session\.v(\d+)\./) || [])[1] || 0);
|
|
@@ -911,11 +1453,11 @@ async function locateSessionArtifacts(sp, header) {
|
|
|
911
1453
|
const generationFiles = entries.filter((name2) => GENERATION_LOG_RE.test(name2));
|
|
912
1454
|
if (generationFiles.length === 0) return null;
|
|
913
1455
|
generationFiles.sort((a, b) => generationVersionOf(b) - generationVersionOf(a));
|
|
914
|
-
const logPath =
|
|
1456
|
+
const logPath = join10(sessionDir, generationFiles[0]);
|
|
915
1457
|
if (!pathOwnsSession(logPath, sid)) return null;
|
|
916
1458
|
return {
|
|
917
1459
|
root,
|
|
918
|
-
projectDir:
|
|
1460
|
+
projectDir: join10(root, header.cwd === void 0 || header.cwd === null || header.cwd === "" ? "_no-cwd" : projectKeyFor(header.cwd)),
|
|
919
1461
|
sessionDir,
|
|
920
1462
|
logPath,
|
|
921
1463
|
generationFiles,
|
|
@@ -1125,8 +1667,8 @@ function requireCapability(capabilities, name2) {
|
|
|
1125
1667
|
}
|
|
1126
1668
|
|
|
1127
1669
|
// src/handle-era-ops.js
|
|
1128
|
-
import { mkdir as
|
|
1129
|
-
import { basename as basename2, dirname as
|
|
1670
|
+
import { mkdir as mkdir9, readFile as readFile3, readdir as readdir3, rename as rename8, rm as rm2, stat as stat3, writeFile as writeFile9 } from "node:fs/promises";
|
|
1671
|
+
import { basename as basename2, dirname as dirname3, join as join11 } from "node:path";
|
|
1130
1672
|
var MOVE_BATCH = 400;
|
|
1131
1673
|
var REAL_LOG_RE = /^session.*\.jsonl(\.zst(d)?)?$/;
|
|
1132
1674
|
var MOVE_TEMP_RE = /\.move-(backup|stage)-/;
|
|
@@ -1149,7 +1691,7 @@ async function cleanSiblingCopies(root, sid, keepDir, keepVersion) {
|
|
|
1149
1691
|
const real = [];
|
|
1150
1692
|
for (const ent of projects) {
|
|
1151
1693
|
if (!ent.isDirectory()) continue;
|
|
1152
|
-
const candidate =
|
|
1694
|
+
const candidate = join11(root, ent.name, segment);
|
|
1153
1695
|
if (keepDir && candidate === keepDir) continue;
|
|
1154
1696
|
let entries;
|
|
1155
1697
|
try {
|
|
@@ -1264,7 +1806,7 @@ async function purgeSessionArtifacts(sp, sid, header) {
|
|
|
1264
1806
|
return artifacts;
|
|
1265
1807
|
}
|
|
1266
1808
|
async function relocateRewrittenBackup({ sid, canonical, backupLogPath, artifacts }) {
|
|
1267
|
-
const original = await
|
|
1809
|
+
const original = await readFile3(backupLogPath);
|
|
1268
1810
|
const frames = scanZstdFrames(original).frames;
|
|
1269
1811
|
if (frames.length === 0) throw new Error("\u79FB\u52A8\u524D\u6821\u9A8C\u5931\u8D25\uFF1A\u4F1A\u8BDD\u65E5\u5FD7\u6CA1\u6709\u5B8C\u6574 zstd \u5E27");
|
|
1270
1812
|
const rewritten = rewriteFrame0CwdInMemory(original, canonical);
|
|
@@ -1274,10 +1816,10 @@ async function relocateRewrittenBackup({ sid, canonical, backupLogPath, artifact
|
|
|
1274
1816
|
throw new Error("\u79FB\u52A8\u540E\u6821\u9A8C\u5931\u8D25\uFF1A\u4F1A\u8BDD\u4E8B\u4EF6\u5185\u5BB9\u53D1\u751F\u53D8\u5316");
|
|
1275
1817
|
}
|
|
1276
1818
|
const targetDir = deriveSessionDir(artifacts.root, canonical, sid);
|
|
1277
|
-
await
|
|
1278
|
-
const staged =
|
|
1279
|
-
await
|
|
1280
|
-
await
|
|
1819
|
+
await mkdir9(targetDir, { recursive: true });
|
|
1820
|
+
const staged = join11(targetDir, `.move-stage-${process.pid}-${Date.now()}`);
|
|
1821
|
+
await writeFile9(staged, rewritten, { mode: 384 });
|
|
1822
|
+
await rename8(staged, join11(targetDir, artifacts.generationFiles[0]));
|
|
1281
1823
|
return targetDir;
|
|
1282
1824
|
}
|
|
1283
1825
|
async function moveSessionToCwd({ sp, sid, header, canonical, events = [], inheritedEventCount = 0 }) {
|
|
@@ -1329,11 +1871,11 @@ async function moveSessionToCwd({ sp, sid, header, canonical, events = [], inher
|
|
|
1329
1871
|
const firstSeq = events.length ? Number(events[0].seq) : 0;
|
|
1330
1872
|
const replay = firstSeq !== 0 ? events.map((event, index) => ({ ...event, seq: index })) : events;
|
|
1331
1873
|
const createOptions = header.isSeeded && Number.isSafeInteger(inheritedEventCount) && inheritedEventCount > 0 ? { inheritedEventCount } : void 0;
|
|
1332
|
-
const backupRoot =
|
|
1333
|
-
const backupDir =
|
|
1334
|
-
await
|
|
1335
|
-
const backupLogPath =
|
|
1336
|
-
await
|
|
1874
|
+
const backupRoot = join11(dirname3(artifacts.root), "sessions-manager-move-backup");
|
|
1875
|
+
const backupDir = join11(backupRoot, `${encodeSegmentFor(sid)}-${process.pid}-${Date.now()}`);
|
|
1876
|
+
await mkdir9(backupRoot, { recursive: true });
|
|
1877
|
+
const backupLogPath = join11(backupDir, basename2(artifacts.logPath));
|
|
1878
|
+
await rename8(artifacts.sessionDir, backupDir);
|
|
1337
1879
|
let writer = null;
|
|
1338
1880
|
try {
|
|
1339
1881
|
try {
|
|
@@ -1366,11 +1908,11 @@ async function moveSessionToCwd({ sp, sid, header, canonical, events = [], inher
|
|
|
1366
1908
|
} catch (_) {
|
|
1367
1909
|
}
|
|
1368
1910
|
try {
|
|
1369
|
-
await
|
|
1911
|
+
await mkdir9(dirname3(artifacts.sessionDir), { recursive: true });
|
|
1370
1912
|
} catch (_) {
|
|
1371
1913
|
}
|
|
1372
1914
|
try {
|
|
1373
|
-
await
|
|
1915
|
+
await rename8(backupDir, artifacts.sessionDir);
|
|
1374
1916
|
} catch (_) {
|
|
1375
1917
|
}
|
|
1376
1918
|
if (e && e.status) throw e;
|
|
@@ -1387,12 +1929,6 @@ async function moveSessionToCwd({ sp, sid, header, canonical, events = [], inher
|
|
|
1387
1929
|
}
|
|
1388
1930
|
|
|
1389
1931
|
// src/lineage.js
|
|
1390
|
-
var EMPTY_BASE = 190;
|
|
1391
|
-
function isEmptyLogSize(sizeBytes, cwd) {
|
|
1392
|
-
if (!Number.isFinite(sizeBytes) || sizeBytes < 0) return false;
|
|
1393
|
-
const cwdLen = typeof cwd === "string" ? cwd.length : 0;
|
|
1394
|
-
return sizeBytes <= EMPTY_BASE + cwdLen;
|
|
1395
|
-
}
|
|
1396
1932
|
var EMPTY_DECODE_LIMIT = 8192;
|
|
1397
1933
|
var LIFECYCLE_EVENT_TYPES = /* @__PURE__ */ new Set([
|
|
1398
1934
|
"session",
|
|
@@ -1407,24 +1943,26 @@ function isEmptyEventTypes(types) {
|
|
|
1407
1943
|
}
|
|
1408
1944
|
return true;
|
|
1409
1945
|
}
|
|
1410
|
-
function classifyLineage(header
|
|
1946
|
+
function classifyLineage(header) {
|
|
1411
1947
|
if (!header || typeof header !== "object") return null;
|
|
1412
1948
|
const origin = header.origin === "subagent" ? "subagent" : null;
|
|
1413
1949
|
const parentSession = typeof header.parentSession === "string" && header.parentSession ? header.parentSession : null;
|
|
1414
1950
|
const delegationDepth = Number.isSafeInteger(header.delegationDepth) && header.delegationDepth > 0 ? header.delegationDepth : 0;
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1951
|
+
if (!origin && !parentSession) return null;
|
|
1952
|
+
return { origin, parentSession, delegationDepth, empty: null };
|
|
1953
|
+
}
|
|
1954
|
+
function emptyScanCandidate(sizeBytes) {
|
|
1955
|
+
return Number.isFinite(sizeBytes) && sizeBytes <= EMPTY_DECODE_LIMIT;
|
|
1418
1956
|
}
|
|
1419
1957
|
|
|
1420
1958
|
// src/index.js
|
|
1421
|
-
var BUILD_STAMP = true ? "3.
|
|
1959
|
+
var BUILD_STAMP = true ? "3.7.0+95376789" : "dev";
|
|
1422
1960
|
var name = "dsh-sessions-manager";
|
|
1423
1961
|
var inject = ["webServer", "workspaceRegistry", "sessionPersistence", "sessionQuery", "storageDomain"];
|
|
1424
1962
|
var MAX_TITLE = 80;
|
|
1425
|
-
var STATE_DIR = process.env.DSH_SESSIONS_MANAGER_STAR_DIR ||
|
|
1426
|
-
var TRASH_DIR = process.env.DSH_SESSIONS_MANAGER_TRASH_DIR ||
|
|
1427
|
-
var TRASH_INDEX =
|
|
1963
|
+
var STATE_DIR = process.env.DSH_SESSIONS_MANAGER_STAR_DIR || join12(homedir7(), ".dsh", "sessions-manager");
|
|
1964
|
+
var TRASH_DIR = process.env.DSH_SESSIONS_MANAGER_TRASH_DIR || join12(homedir7(), ".dsh", "sessions-manager-trash");
|
|
1965
|
+
var TRASH_INDEX = join12(TRASH_DIR, "index.json");
|
|
1428
1966
|
var TRASH_SCHEMA_VERSION = 2;
|
|
1429
1967
|
var DEFAULT_TRASH_SETTINGS = Object.freeze({ retentionDays: 0 });
|
|
1430
1968
|
var FETCH_TOOL_RE = /search|fetch|download|browse/i;
|
|
@@ -1471,14 +2009,14 @@ function parseIds(body) {
|
|
|
1471
2009
|
const raw = body && body.sessionIds;
|
|
1472
2010
|
if (!Array.isArray(raw)) return null;
|
|
1473
2011
|
const ids = [];
|
|
1474
|
-
for (const v of raw) if (typeof v === "string" &&
|
|
2012
|
+
for (const v of raw) if (typeof v === "string" && isSafeSessionId3(v)) ids.push(v);
|
|
1475
2013
|
return ids;
|
|
1476
2014
|
}
|
|
1477
|
-
function
|
|
2015
|
+
function isSafeSessionId3(value) {
|
|
1478
2016
|
return typeof value === "string" && value.length > 0 && value.length <= 200 && !/[\\/\0]/.test(value) && value !== "." && value !== "..";
|
|
1479
2017
|
}
|
|
1480
2018
|
function requireSessionId(value) {
|
|
1481
|
-
if (!
|
|
2019
|
+
if (!isSafeSessionId3(value)) {
|
|
1482
2020
|
const error = new Error("\u65E0\u6548\u7684 sessionId");
|
|
1483
2021
|
error.status = 400;
|
|
1484
2022
|
throw error;
|
|
@@ -1512,7 +2050,9 @@ function apply(ctx) {
|
|
|
1512
2050
|
const dom = () => ctx.storageDomain.get("workspace");
|
|
1513
2051
|
const authorityTitleCache = /* @__PURE__ */ new Map();
|
|
1514
2052
|
const metaCache = createSessionMetaCache();
|
|
1515
|
-
const titleIndex = createTitleIndexStore({ dir: TRASH_DIR, file:
|
|
2053
|
+
const titleIndex = createTitleIndexStore({ dir: TRASH_DIR, file: join12(TRASH_DIR, "title-index.json") });
|
|
2054
|
+
const emptyIndex = createEmptyScanStore({ dir: TRASH_DIR });
|
|
2055
|
+
const EMPTY_VERDICT_TTL_MS = 14 * 24 * 3600 * 1e3;
|
|
1516
2056
|
async function hydrateFromPersist(ids, statsById) {
|
|
1517
2057
|
const hits = /* @__PURE__ */ new Map();
|
|
1518
2058
|
if (!ids || !ids.length) return hits;
|
|
@@ -1824,12 +2364,12 @@ function apply(ctx) {
|
|
|
1824
2364
|
schemaVersion: TRASH_SCHEMA_VERSION,
|
|
1825
2365
|
settings: { retentionDays },
|
|
1826
2366
|
items: raw && Array.isArray(raw.items) ? raw.items : [],
|
|
1827
|
-
purgedSessionIds: raw && Array.isArray(raw.purgedSessionIds) ? [...new Set(raw.purgedSessionIds.filter(
|
|
2367
|
+
purgedSessionIds: raw && Array.isArray(raw.purgedSessionIds) ? [...new Set(raw.purgedSessionIds.filter(isSafeSessionId3).map(String))] : []
|
|
1828
2368
|
};
|
|
1829
2369
|
}
|
|
1830
2370
|
async function readTrashStore() {
|
|
1831
2371
|
try {
|
|
1832
|
-
return normalizeTrashStore(JSON.parse(
|
|
2372
|
+
return normalizeTrashStore(JSON.parse(readFileSync7(TRASH_INDEX, "utf8")));
|
|
1833
2373
|
} catch (e) {
|
|
1834
2374
|
return normalizeTrashStore(null);
|
|
1835
2375
|
}
|
|
@@ -1838,10 +2378,10 @@ function apply(ctx) {
|
|
|
1838
2378
|
return (await readTrashStore()).items;
|
|
1839
2379
|
}
|
|
1840
2380
|
async function writeTrashStore(store) {
|
|
1841
|
-
await
|
|
1842
|
-
const tmp =
|
|
1843
|
-
await
|
|
1844
|
-
await
|
|
2381
|
+
await mkdir10(TRASH_DIR, { recursive: true });
|
|
2382
|
+
const tmp = join12(TRASH_DIR, `.index-${process.pid}-${Date.now()}.tmp`);
|
|
2383
|
+
await writeFile10(tmp, JSON.stringify(normalizeTrashStore(store), null, 2), { encoding: "utf8", mode: 384 });
|
|
2384
|
+
await rename9(tmp, TRASH_INDEX);
|
|
1845
2385
|
}
|
|
1846
2386
|
function mutateTrash(mutator) {
|
|
1847
2387
|
const operation = trashMutation.then(async () => {
|
|
@@ -1855,7 +2395,10 @@ function apply(ctx) {
|
|
|
1855
2395
|
return operation;
|
|
1856
2396
|
}
|
|
1857
2397
|
const stars = createStarIndex();
|
|
2398
|
+
const tags = createTagIndex();
|
|
2399
|
+
const savedFilters = createSavedFilters();
|
|
1858
2400
|
const pendingMoves = createPendingMoveStore();
|
|
2401
|
+
const moveNotices = createMoveNoticeStore();
|
|
1859
2402
|
const autoArchive = createAutoArchiveStore();
|
|
1860
2403
|
sweepStaleStateTemps([STATE_DIR, TRASH_DIR], {}).catch(() => {
|
|
1861
2404
|
});
|
|
@@ -1868,6 +2411,15 @@ function apply(ctx) {
|
|
|
1868
2411
|
} catch (e) {
|
|
1869
2412
|
}
|
|
1870
2413
|
}
|
|
2414
|
+
async function gcTags(validIds) {
|
|
2415
|
+
try {
|
|
2416
|
+
const store = await tags.read();
|
|
2417
|
+
const valid = new Set(validIds.map(String));
|
|
2418
|
+
const gone = Object.keys(store.assignments).filter((id) => !valid.has(id));
|
|
2419
|
+
if (gone.length) await tags.removeIds(gone);
|
|
2420
|
+
} catch (e) {
|
|
2421
|
+
}
|
|
2422
|
+
}
|
|
1871
2423
|
async function deleteOne(sid) {
|
|
1872
2424
|
requireSessionId(sid);
|
|
1873
2425
|
let header = null;
|
|
@@ -2120,6 +2672,10 @@ function apply(ctx) {
|
|
|
2120
2672
|
await stars.removeIds([sid]);
|
|
2121
2673
|
} catch (e) {
|
|
2122
2674
|
}
|
|
2675
|
+
try {
|
|
2676
|
+
await tags.removeIds([sid]);
|
|
2677
|
+
} catch (e) {
|
|
2678
|
+
}
|
|
2123
2679
|
return { ok: true, purged: true };
|
|
2124
2680
|
}
|
|
2125
2681
|
async function trashSettings(next) {
|
|
@@ -2155,8 +2711,8 @@ function apply(ctx) {
|
|
|
2155
2711
|
async function moveTargetWorkspace(rawPath) {
|
|
2156
2712
|
if (typeof rawPath !== "string" || !rawPath.trim()) throw new Error("\u7F3A\u5C11\u76EE\u6807\u5DE5\u4F5C\u533A\u8DEF\u5F84");
|
|
2157
2713
|
let p = String(rawPath).trim();
|
|
2158
|
-
if (p.startsWith("~/")) p =
|
|
2159
|
-
if (!isAbsolute(p)) p =
|
|
2714
|
+
if (p.startsWith("~/")) p = join12(homedir7(), p.slice(2));
|
|
2715
|
+
if (!isAbsolute(p)) p = join12(homedir7(), p);
|
|
2160
2716
|
let canonical = null;
|
|
2161
2717
|
try {
|
|
2162
2718
|
canonical = await realpath(p);
|
|
@@ -2164,7 +2720,7 @@ function apply(ctx) {
|
|
|
2164
2720
|
canonical = null;
|
|
2165
2721
|
}
|
|
2166
2722
|
if (canonical === null) {
|
|
2167
|
-
await
|
|
2723
|
+
await mkdir10(p, { recursive: true });
|
|
2168
2724
|
canonical = await realpath(p);
|
|
2169
2725
|
}
|
|
2170
2726
|
return { canonical, entity: await w.create(canonical, basename3(canonical) || "workspace") };
|
|
@@ -2209,15 +2765,15 @@ function apply(ctx) {
|
|
|
2209
2765
|
const stagedPath = `${newPath}.move-stage-${process.pid}-${Date.now()}`;
|
|
2210
2766
|
let destinationInstalled = false;
|
|
2211
2767
|
try {
|
|
2212
|
-
await
|
|
2768
|
+
await mkdir10(dirname4(newPath), { recursive: true });
|
|
2213
2769
|
try {
|
|
2214
2770
|
await stat4(newPath);
|
|
2215
2771
|
throw new Error("\u79FB\u52A8\u5931\u8D25\uFF1A\u76EE\u6807\u4F4D\u7F6E\u5DF2\u5B58\u5728\u540C\u540D\u4F1A\u8BDD\u65E5\u5FD7");
|
|
2216
2772
|
} catch (e) {
|
|
2217
2773
|
if (e && e.code !== "ENOENT") throw e;
|
|
2218
2774
|
}
|
|
2219
|
-
await
|
|
2220
|
-
const original = await
|
|
2775
|
+
await rename9(oldPath, backupPath);
|
|
2776
|
+
const original = await readFile4(backupPath);
|
|
2221
2777
|
const originalFrames = scanZstdFrames(original).frames;
|
|
2222
2778
|
if (originalFrames.length === 0) throw new Error("\u79FB\u52A8\u524D\u6821\u9A8C\u5931\u8D25\uFF1A\u4F1A\u8BDD\u65E5\u5FD7\u6CA1\u6709\u5B8C\u6574 zstd \u5E27");
|
|
2223
2779
|
const rewritten = rewriteFrame0CwdInMemory(original, canonical);
|
|
@@ -2226,11 +2782,11 @@ function apply(ctx) {
|
|
|
2226
2782
|
const originalTail = original.subarray(originalFrames[0].end);
|
|
2227
2783
|
const rewrittenTail = rewritten.subarray(rewrittenFrames[0].end);
|
|
2228
2784
|
if (!originalTail.equals(rewrittenTail)) throw new Error("\u79FB\u52A8\u540E\u6821\u9A8C\u5931\u8D25\uFF1A\u4F1A\u8BDD\u4E8B\u4EF6\u5185\u5BB9\u53D1\u751F\u53D8\u5316");
|
|
2229
|
-
await
|
|
2230
|
-
await
|
|
2785
|
+
await writeFile10(stagedPath, rewritten, { mode: 384 });
|
|
2786
|
+
await rename9(stagedPath, newPath);
|
|
2231
2787
|
destinationInstalled = true;
|
|
2232
2788
|
await unlink(backupPath);
|
|
2233
|
-
const keptInSource = await cleanupMovedSourceDir(
|
|
2789
|
+
const keptInSource = await cleanupMovedSourceDir(dirname4(oldPath));
|
|
2234
2790
|
if (keptInSource.length > 0) moveNotes.push(`\u6E90\u76EE\u5F55\u672A\u80FD\u6E05\u7406\u5E72\u51C0\uFF08\u6B8B\u7559 ${keptInSource.join("\u3001")}\uFF09\uFF0C\u82E5\u540E\u7EED\u79FB\u52A8\u62A5\u201Cduplicate\u201D\u8BF7\u624B\u52A8\u6E05\u7A7A\u8BE5\u76EE\u5F55\u3002`);
|
|
2235
2791
|
} catch (e) {
|
|
2236
2792
|
try {
|
|
@@ -2244,7 +2800,7 @@ function apply(ctx) {
|
|
|
2244
2800
|
}
|
|
2245
2801
|
}
|
|
2246
2802
|
try {
|
|
2247
|
-
await
|
|
2803
|
+
await rename9(backupPath, oldPath);
|
|
2248
2804
|
} catch (_) {
|
|
2249
2805
|
}
|
|
2250
2806
|
if (e && e.code !== "ENOENT") throw e;
|
|
@@ -2324,7 +2880,7 @@ function apply(ctx) {
|
|
|
2324
2880
|
const backupPath = oldPath ? `${oldPath}.move-backup-${Date.now()}` : null;
|
|
2325
2881
|
if (backupPath) {
|
|
2326
2882
|
try {
|
|
2327
|
-
await
|
|
2883
|
+
await rename9(oldPath, backupPath);
|
|
2328
2884
|
} catch (e) {
|
|
2329
2885
|
if (e && e.code !== "ENOENT") throw new Error("\u79FB\u52A8\u5931\u8D25\uFF1A\u65E0\u6CD5\u5907\u4EFD\u65E7\u7684\u4F1A\u8BDD\u65E5\u5FD7");
|
|
2330
2886
|
}
|
|
@@ -2332,7 +2888,7 @@ function apply(ctx) {
|
|
|
2332
2888
|
const restore = async () => {
|
|
2333
2889
|
if (backupPath) {
|
|
2334
2890
|
try {
|
|
2335
|
-
await
|
|
2891
|
+
await rename9(backupPath, oldPath);
|
|
2336
2892
|
} catch (_) {
|
|
2337
2893
|
}
|
|
2338
2894
|
}
|
|
@@ -2351,7 +2907,7 @@ function apply(ctx) {
|
|
|
2351
2907
|
}
|
|
2352
2908
|
}
|
|
2353
2909
|
if (oldPath) {
|
|
2354
|
-
const keptInSource = await cleanupMovedSourceDir(
|
|
2910
|
+
const keptInSource = await cleanupMovedSourceDir(dirname4(oldPath));
|
|
2355
2911
|
if (keptInSource.length > 0) moveNotes.push(`\u6E90\u76EE\u5F55\u672A\u80FD\u6E05\u7406\u5E72\u51C0\uFF08\u6B8B\u7559 ${keptInSource.join("\u3001")}\uFF09\uFF0C\u82E5\u540E\u7EED\u79FB\u52A8\u62A5\u201Cduplicate\u201D\u8BF7\u624B\u52A8\u6E05\u7A7A\u8BE5\u76EE\u5F55\u3002`);
|
|
2356
2912
|
}
|
|
2357
2913
|
} catch (e) {
|
|
@@ -2434,11 +2990,21 @@ function apply(ctx) {
|
|
|
2434
2990
|
metaCache.invalidate(item.sessionId);
|
|
2435
2991
|
moved++;
|
|
2436
2992
|
notes.push(`\u6392\u961F\u4E2D\u7684\u79FB\u52A8\u5DF2\u5B8C\u6210\uFF1A${String(item.sessionId).slice(0, 18)}\u2026`);
|
|
2993
|
+
try {
|
|
2994
|
+
await moveNotices.append({ kind: "moved", sessionId: item.sessionId, targetPath: item.targetPath, at: Date.now() });
|
|
2995
|
+
} catch (e) {
|
|
2996
|
+
}
|
|
2437
2997
|
} catch (e) {
|
|
2438
2998
|
if (isBusyError(e)) continue;
|
|
2439
2999
|
if (Date.now() - bootedAt < BOOT_WARMUP_MS) continue;
|
|
2440
3000
|
const bumped = await pendingMoves.bumpAttempts(item.sessionId);
|
|
2441
|
-
if (bumped && bumped.dropped)
|
|
3001
|
+
if (bumped && bumped.dropped) {
|
|
3002
|
+
notes.push(`\u6392\u961F\u4E2D\u7684\u79FB\u52A8\u591A\u6B21\u5931\u8D25\u5DF2\u653E\u5F03\uFF1A${String(item.sessionId).slice(0, 18)}\u2026\uFF08${String(e && e.message || e)}\uFF09`);
|
|
3003
|
+
try {
|
|
3004
|
+
await moveNotices.append({ kind: "abandoned", sessionId: item.sessionId, targetPath: item.targetPath, at: Date.now(), attempts: bumped.attempts, reason: String(e && e.message || e).split("\n")[0] });
|
|
3005
|
+
} catch (err) {
|
|
3006
|
+
}
|
|
3007
|
+
}
|
|
2442
3008
|
}
|
|
2443
3009
|
}
|
|
2444
3010
|
if (moved > 0) {
|
|
@@ -2657,12 +3223,23 @@ function apply(ctx) {
|
|
|
2657
3223
|
return { items: archivedItems, usage };
|
|
2658
3224
|
}
|
|
2659
3225
|
let starredSet = /* @__PURE__ */ new Set();
|
|
3226
|
+
let tagIdsBySession = /* @__PURE__ */ new Map();
|
|
2660
3227
|
try {
|
|
2661
3228
|
starredSet = new Set((await stars.read()).starredSessionIds);
|
|
3229
|
+
const tagStore = await tags.read();
|
|
3230
|
+
const knownTagIds = new Set(tagStore.tags.map((t) => t.id));
|
|
3231
|
+
for (const [sid, list] of Object.entries(tagStore.assignments)) {
|
|
3232
|
+
const kept = list.filter((id) => knownTagIds.has(id));
|
|
3233
|
+
if (kept.length) tagIdsBySession.set(sid, kept);
|
|
3234
|
+
}
|
|
2662
3235
|
} catch (e) {
|
|
2663
3236
|
}
|
|
2664
|
-
for (const it of items)
|
|
3237
|
+
for (const it of items) {
|
|
3238
|
+
it.starred = starredSet.has(String(it.sessionId));
|
|
3239
|
+
it.tags = tagIdsBySession.get(String(it.sessionId)) || [];
|
|
3240
|
+
}
|
|
2665
3241
|
if (headersOk) await gcStars(ids);
|
|
3242
|
+
if (headersOk) await gcTags(ids);
|
|
2666
3243
|
return { items, usage };
|
|
2667
3244
|
}
|
|
2668
3245
|
async function allSessionItems(opts = {}) {
|
|
@@ -2757,69 +3334,121 @@ function apply(ctx) {
|
|
|
2757
3334
|
}
|
|
2758
3335
|
}
|
|
2759
3336
|
const lineage = {};
|
|
3337
|
+
const refineWanted = [];
|
|
3338
|
+
let emptyStore = null;
|
|
3339
|
+
try {
|
|
3340
|
+
emptyStore = await emptyIndex.entries();
|
|
3341
|
+
} catch (e) {
|
|
3342
|
+
emptyStore = null;
|
|
3343
|
+
}
|
|
3344
|
+
const nowTs = Date.now();
|
|
2760
3345
|
for (const entry of entries) {
|
|
2761
|
-
const
|
|
2762
|
-
|
|
3346
|
+
const id = String(entry.id);
|
|
3347
|
+
const size = entry && Number.isFinite(entry.sizeBytes) ? entry.sizeBytes : null;
|
|
3348
|
+
const candidate = emptyScanCandidate(size);
|
|
3349
|
+
let verdict = null;
|
|
3350
|
+
if (!candidate) {
|
|
3351
|
+
if (size !== null) verdict = false;
|
|
3352
|
+
} else {
|
|
3353
|
+
const mem = emptyScanCache.get(id);
|
|
3354
|
+
if (mem && mem.sizeBytes === size) {
|
|
3355
|
+
verdict = !!mem.empty;
|
|
3356
|
+
} else {
|
|
3357
|
+
const rec = emptyStore && emptyStore[id];
|
|
3358
|
+
if (rec && rec.fingerprint === `sz:${size}` && nowTs - (rec.updatedAt || 0) <= EMPTY_VERDICT_TTL_MS) {
|
|
3359
|
+
verdict = rec.empty === 1;
|
|
3360
|
+
emptyScanCache.set(id, { sizeBytes: size, empty: verdict });
|
|
3361
|
+
}
|
|
3362
|
+
}
|
|
3363
|
+
if (verdict === null && refineCapable() && !refineQueue.has(id)) refineWanted.push({ id, sizeBytes: size });
|
|
3364
|
+
}
|
|
3365
|
+
const info = classifyLineage(entry.header);
|
|
3366
|
+
if (info) lineage[id] = { origin: info.origin, parentSession: info.parentSession, delegationDepth: info.delegationDepth, empty: verdict };
|
|
3367
|
+
else if (verdict === true) lineage[id] = { origin: null, parentSession: null, delegationDepth: 0, empty: true };
|
|
2763
3368
|
}
|
|
2764
|
-
|
|
2765
|
-
|
|
3369
|
+
enqueueRefine(refineWanted);
|
|
3370
|
+
for (const key of emptyScanCache.keys()) {
|
|
3371
|
+
const rec = emptyScanCache.get(key);
|
|
3372
|
+
if (!rec || !emptyScanCandidate(rec.sizeBytes)) emptyScanCache.delete(key);
|
|
3373
|
+
}
|
|
3374
|
+
const payload = {
|
|
2766
3375
|
titles: Object.fromEntries(authorityTitleCache),
|
|
2767
3376
|
trashedSessionIds: store.items.map((item) => String(item.sessionId)),
|
|
2768
3377
|
purgedSessionIds: activeTombstones,
|
|
2769
3378
|
lineage,
|
|
2770
3379
|
// v3.6.2:还有预热/退避重试在途 → client 缩短轮询节拍,预热一完成就把
|
|
2771
3380
|
// 补齐的标题送回(含面板自动刷新)。无预热 API 时恒 false,绝不假忙。
|
|
2772
|
-
warmPending: warmPendingNow()
|
|
3381
|
+
warmPending: warmPendingNow(),
|
|
3382
|
+
// T1:空白精判后台队列是否还有活。无精判能力(旧 runtime 无 inspect 通道)
|
|
3383
|
+
// 时恒 false——永久 busy 会让 client 疯轮询,这是唯一现实的假忙陷阱。
|
|
3384
|
+
refinePending: refinePendingNow()
|
|
2773
3385
|
};
|
|
3386
|
+
try {
|
|
3387
|
+
const noticeRows = await moveNotices.list();
|
|
3388
|
+
if (noticeRows.length) payload.moveNotices = noticeRows;
|
|
3389
|
+
} catch (e) {
|
|
3390
|
+
}
|
|
3391
|
+
return payload;
|
|
2774
3392
|
}
|
|
2775
3393
|
const emptyScanCache = /* @__PURE__ */ new Map();
|
|
2776
|
-
const
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
}
|
|
2800
|
-
if (todo.length < REFINE_BUDGET) todo.push({ id, size });
|
|
3394
|
+
const REFINE_CHUNK = 2;
|
|
3395
|
+
const refineQueue = /* @__PURE__ */ new Map();
|
|
3396
|
+
let refineRunning = false;
|
|
3397
|
+
let refineKickTimer = null;
|
|
3398
|
+
function refineCapable() {
|
|
3399
|
+
return !!persistence && typeof persistence.inspectSession === "function";
|
|
3400
|
+
}
|
|
3401
|
+
function refinePendingNow() {
|
|
3402
|
+
return !!(refineRunning || refineQueue.size);
|
|
3403
|
+
}
|
|
3404
|
+
function scheduleRefine() {
|
|
3405
|
+
if (refineKickTimer || refineRunning || !refineQueue.size) return;
|
|
3406
|
+
refineKickTimer = setTimeout(() => {
|
|
3407
|
+
refineKickTimer = null;
|
|
3408
|
+
runRefine();
|
|
3409
|
+
}, 25);
|
|
3410
|
+
if (typeof refineKickTimer.unref === "function") refineKickTimer.unref();
|
|
3411
|
+
}
|
|
3412
|
+
function enqueueRefine(items) {
|
|
3413
|
+
if (!items || !items.length || !refineCapable()) return;
|
|
3414
|
+
for (const { id, sizeBytes } of items) {
|
|
3415
|
+
if (emptyScanCache.has(id) && emptyScanCache.get(id).sizeBytes === sizeBytes) continue;
|
|
3416
|
+
refineQueue.set(id, { sizeBytes });
|
|
2801
3417
|
}
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
3418
|
+
scheduleRefine();
|
|
3419
|
+
}
|
|
3420
|
+
async function runRefine() {
|
|
3421
|
+
if (refineRunning) return;
|
|
3422
|
+
refineRunning = true;
|
|
3423
|
+
try {
|
|
3424
|
+
while (refineQueue.size) {
|
|
3425
|
+
const batch = [...refineQueue.entries()].slice(0, REFINE_CHUNK);
|
|
3426
|
+
for (const [id] of batch) refineQueue.delete(id);
|
|
3427
|
+
const persistBatch = {};
|
|
3428
|
+
for (const [id, desc] of batch) {
|
|
3429
|
+
try {
|
|
3430
|
+
const types = [];
|
|
3431
|
+
await persistence.inspectSession(id, { onEvents: (b) => {
|
|
3432
|
+
for (const ev of b || []) if (types.length < 64) types.push(ev && ev.type);
|
|
3433
|
+
} });
|
|
3434
|
+
const isEmpty = isEmptyEventTypes(types);
|
|
3435
|
+
emptyScanCache.set(id, { sizeBytes: desc.sizeBytes, empty: isEmpty });
|
|
3436
|
+
persistBatch[id] = { empty: isEmpty ? 1 : 0, fingerprint: `sz:${desc.sizeBytes}`, updatedAt: Date.now() };
|
|
3437
|
+
} catch (e) {
|
|
3438
|
+
emptyScanCache.set(id, { sizeBytes: desc.sizeBytes, empty: false });
|
|
3439
|
+
}
|
|
3440
|
+
}
|
|
3441
|
+
if (Object.keys(persistBatch).length) {
|
|
3442
|
+
try {
|
|
3443
|
+
await emptyIndex.merge(persistBatch);
|
|
3444
|
+
} catch (e) {
|
|
3445
|
+
}
|
|
3446
|
+
}
|
|
3447
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
2812
3448
|
}
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
const live = /* @__PURE__ */ new Set();
|
|
2817
|
-
for (const entry of entries) {
|
|
2818
|
-
const size = entry && Number.isFinite(entry.sizeBytes) ? entry.sizeBytes : null;
|
|
2819
|
-
if (size !== null && size <= EMPTY_DECODE_LIMIT) live.add(String(entry.id));
|
|
2820
|
-
}
|
|
2821
|
-
for (const key of emptyScanCache.keys()) {
|
|
2822
|
-
if (!live.has(key)) emptyScanCache.delete(key);
|
|
3449
|
+
} finally {
|
|
3450
|
+
refineRunning = false;
|
|
3451
|
+
if (refineQueue.size) scheduleRefine();
|
|
2823
3452
|
}
|
|
2824
3453
|
}
|
|
2825
3454
|
async function buildDetails(sid, signal) {
|
|
@@ -3175,6 +3804,10 @@ function apply(ctx) {
|
|
|
3175
3804
|
await titleIndex.remove([sid]);
|
|
3176
3805
|
} catch (e) {
|
|
3177
3806
|
}
|
|
3807
|
+
try {
|
|
3808
|
+
await emptyIndex.remove([sid]);
|
|
3809
|
+
} catch (e) {
|
|
3810
|
+
}
|
|
3178
3811
|
json(res, out);
|
|
3179
3812
|
} catch (e) {
|
|
3180
3813
|
json(res, { ok: false, code: e && e.code, error: String(e && e.message || e) }, errorStatus(e));
|
|
@@ -3196,6 +3829,8 @@ function apply(ctx) {
|
|
|
3196
3829
|
metaCache.invalidate(sid);
|
|
3197
3830
|
await titleIndex.remove([sid]).catch(() => {
|
|
3198
3831
|
});
|
|
3832
|
+
await emptyIndex.remove([sid]).catch(() => {
|
|
3833
|
+
});
|
|
3199
3834
|
} catch (e) {
|
|
3200
3835
|
results.push({ sessionId: sid, ok: false, error: String(e && e.message || e) });
|
|
3201
3836
|
}
|
|
@@ -3211,7 +3846,7 @@ function apply(ctx) {
|
|
|
3211
3846
|
path: "/archived-sessions/sessions",
|
|
3212
3847
|
handler: async (req, res) => {
|
|
3213
3848
|
try {
|
|
3214
|
-
json(res, { items: await allSessionItems(), warmPending: warmPendingNow() });
|
|
3849
|
+
json(res, { items: await allSessionItems({ usage: true }), warmPending: warmPendingNow() });
|
|
3215
3850
|
} catch (e) {
|
|
3216
3851
|
json(res, { error: String(e && e.message || e) }, 500);
|
|
3217
3852
|
}
|
|
@@ -3226,13 +3861,133 @@ function apply(ctx) {
|
|
|
3226
3861
|
const starred = !!(body && body.starred);
|
|
3227
3862
|
let ids = parseIds(body);
|
|
3228
3863
|
if ((!ids || ids.length === 0) && body && typeof body.sessionId === "string") {
|
|
3229
|
-
ids =
|
|
3864
|
+
ids = isSafeSessionId3(body.sessionId) ? [body.sessionId] : null;
|
|
3230
3865
|
}
|
|
3231
|
-
if (!ids || ids.length === 0) return json(res, { ok: false, error: "missing sessionId" }, 400);
|
|
3866
|
+
if (!ids || ids.length === 0) return json(res, { ok: false, code: "DSM_STAR_SESSION_INVALID", error: "missing sessionId" }, 400);
|
|
3232
3867
|
const starredSessionIds = await stars.setStarred(ids, starred);
|
|
3233
3868
|
json(res, { ok: true, starredSessionIds });
|
|
3234
3869
|
} catch (e) {
|
|
3235
|
-
json(res, { ok: false, error: String(e && e.message || e) }, errorStatus(e));
|
|
3870
|
+
json(res, { ok: false, code: e && e.code, error: String(e && e.message || e) }, errorStatus(e));
|
|
3871
|
+
}
|
|
3872
|
+
}
|
|
3873
|
+
}));
|
|
3874
|
+
disposers.push(ctx.webServer.register({
|
|
3875
|
+
kind: "exact",
|
|
3876
|
+
path: "/archived-sessions/tags/list",
|
|
3877
|
+
handler: async (req, res) => {
|
|
3878
|
+
try {
|
|
3879
|
+
const { tags: tagList, assignments } = await tags.list();
|
|
3880
|
+
json(res, { ok: true, tags: tagList, assignments });
|
|
3881
|
+
} catch (e) {
|
|
3882
|
+
json(res, { ok: false, code: e && e.code, error: String(e && e.message || e) }, errorStatus(e));
|
|
3883
|
+
}
|
|
3884
|
+
}
|
|
3885
|
+
}));
|
|
3886
|
+
disposers.push(ctx.webServer.register({
|
|
3887
|
+
kind: "exact",
|
|
3888
|
+
path: "/archived-sessions/tags/create",
|
|
3889
|
+
handler: async (req, res) => {
|
|
3890
|
+
try {
|
|
3891
|
+
const body = await readJsonBody(req);
|
|
3892
|
+
const tag = await tags.create(body && body.name);
|
|
3893
|
+
json(res, { ok: true, tag });
|
|
3894
|
+
} catch (e) {
|
|
3895
|
+
json(res, { ok: false, code: e && e.code, error: String(e && e.message || e) }, errorStatus(e));
|
|
3896
|
+
}
|
|
3897
|
+
}
|
|
3898
|
+
}));
|
|
3899
|
+
disposers.push(ctx.webServer.register({
|
|
3900
|
+
kind: "exact",
|
|
3901
|
+
path: "/archived-sessions/tags/rename",
|
|
3902
|
+
handler: async (req, res) => {
|
|
3903
|
+
try {
|
|
3904
|
+
const body = await readJsonBody(req);
|
|
3905
|
+
await tags.rename(body && body.id, body && body.name);
|
|
3906
|
+
json(res, { ok: true });
|
|
3907
|
+
} catch (e) {
|
|
3908
|
+
json(res, { ok: false, code: e && e.code, error: String(e && e.message || e) }, errorStatus(e));
|
|
3909
|
+
}
|
|
3910
|
+
}
|
|
3911
|
+
}));
|
|
3912
|
+
disposers.push(ctx.webServer.register({
|
|
3913
|
+
kind: "exact",
|
|
3914
|
+
path: "/archived-sessions/tags/merge",
|
|
3915
|
+
handler: async (req, res) => {
|
|
3916
|
+
try {
|
|
3917
|
+
const body = await readJsonBody(req);
|
|
3918
|
+
await tags.merge(body && body.fromId, body && body.toId);
|
|
3919
|
+
json(res, { ok: true });
|
|
3920
|
+
} catch (e) {
|
|
3921
|
+
json(res, { ok: false, code: e && e.code, error: String(e && e.message || e) }, errorStatus(e));
|
|
3922
|
+
}
|
|
3923
|
+
}
|
|
3924
|
+
}));
|
|
3925
|
+
disposers.push(ctx.webServer.register({
|
|
3926
|
+
kind: "exact",
|
|
3927
|
+
path: "/archived-sessions/tags/delete",
|
|
3928
|
+
handler: async (req, res) => {
|
|
3929
|
+
try {
|
|
3930
|
+
const body = await readJsonBody(req);
|
|
3931
|
+
await tags.removeTag(body && body.id);
|
|
3932
|
+
json(res, { ok: true });
|
|
3933
|
+
} catch (e) {
|
|
3934
|
+
json(res, { ok: false, code: e && e.code, error: String(e && e.message || e) }, errorStatus(e));
|
|
3935
|
+
}
|
|
3936
|
+
}
|
|
3937
|
+
}));
|
|
3938
|
+
disposers.push(ctx.webServer.register({
|
|
3939
|
+
kind: "exact",
|
|
3940
|
+
path: "/archived-sessions/tags/set",
|
|
3941
|
+
handler: async (req, res) => {
|
|
3942
|
+
try {
|
|
3943
|
+
const body = await readJsonBody(req);
|
|
3944
|
+
const sid = body && body.sessionId;
|
|
3945
|
+
if (!isSafeSessionId3(sid)) {
|
|
3946
|
+
return json(res, { ok: false, code: "DSM_TAG_SESSION_INVALID", error: "missing/invalid sessionId" }, 400);
|
|
3947
|
+
}
|
|
3948
|
+
const assignments = await tags.setTags(sid, body && body.tagIds);
|
|
3949
|
+
json(res, { ok: true, assignments });
|
|
3950
|
+
} catch (e) {
|
|
3951
|
+
json(res, { ok: false, code: e && e.code, error: String(e && e.message || e) }, errorStatus(e));
|
|
3952
|
+
}
|
|
3953
|
+
}
|
|
3954
|
+
}));
|
|
3955
|
+
disposers.push(ctx.webServer.register({
|
|
3956
|
+
kind: "exact",
|
|
3957
|
+
path: "/archived-sessions/filters/list",
|
|
3958
|
+
handler: async (req, res) => {
|
|
3959
|
+
try {
|
|
3960
|
+
json(res, { ok: true, items: await savedFilters.list() });
|
|
3961
|
+
} catch (e) {
|
|
3962
|
+
json(res, { ok: false, code: e && e.code, error: String(e && e.message || e) }, errorStatus(e));
|
|
3963
|
+
}
|
|
3964
|
+
}
|
|
3965
|
+
}));
|
|
3966
|
+
disposers.push(ctx.webServer.register({
|
|
3967
|
+
kind: "exact",
|
|
3968
|
+
path: "/archived-sessions/filters/save",
|
|
3969
|
+
handler: async (req, res) => {
|
|
3970
|
+
try {
|
|
3971
|
+
const body = await readJsonBody(req);
|
|
3972
|
+
const item = await savedFilters.save(body && body.name, body && body.filters);
|
|
3973
|
+
json(res, { ok: true, item });
|
|
3974
|
+
} catch (e) {
|
|
3975
|
+
json(res, { ok: false, code: e && e.code, error: String(e && e.message || e) }, errorStatus(e));
|
|
3976
|
+
}
|
|
3977
|
+
}
|
|
3978
|
+
}));
|
|
3979
|
+
disposers.push(ctx.webServer.register({
|
|
3980
|
+
kind: "exact",
|
|
3981
|
+
path: "/archived-sessions/filters/delete",
|
|
3982
|
+
handler: async (req, res) => {
|
|
3983
|
+
try {
|
|
3984
|
+
const body = await readJsonBody(req);
|
|
3985
|
+
const ids = body && Array.isArray(body.ids) ? body.ids : null;
|
|
3986
|
+
if (!ids || ids.length === 0) return json(res, { ok: false, code: "DSM_FILTER_IDS_INVALID", error: "missing ids" }, 400);
|
|
3987
|
+
const removed = await savedFilters.remove(ids);
|
|
3988
|
+
json(res, { ok: true, removed });
|
|
3989
|
+
} catch (e) {
|
|
3990
|
+
json(res, { ok: false, code: e && e.code, error: String(e && e.message || e) }, errorStatus(e));
|
|
3236
3991
|
}
|
|
3237
3992
|
}
|
|
3238
3993
|
}));
|
|
@@ -3291,7 +4046,7 @@ function apply(ctx) {
|
|
|
3291
4046
|
try {
|
|
3292
4047
|
const body = await readJsonBody(req);
|
|
3293
4048
|
const rootId = body && typeof body.sessionId === "string" ? body.sessionId : "";
|
|
3294
|
-
if (!rootId || !
|
|
4049
|
+
if (!rootId || !isSafeSessionId3(rootId)) return json(res, { error: "missing sessionId" }, 400);
|
|
3295
4050
|
const headerById = /* @__PURE__ */ new Map();
|
|
3296
4051
|
const sizeById = /* @__PURE__ */ new Map();
|
|
3297
4052
|
const trashedIds = /* @__PURE__ */ new Set();
|
|
@@ -3503,6 +4258,21 @@ function apply(ctx) {
|
|
|
3503
4258
|
}
|
|
3504
4259
|
}
|
|
3505
4260
|
}));
|
|
4261
|
+
disposers.push(ctx.webServer.register({
|
|
4262
|
+
kind: "exact",
|
|
4263
|
+
path: "/archived-sessions/pending-moves/notices/ack",
|
|
4264
|
+
handler: async (req, res) => {
|
|
4265
|
+
try {
|
|
4266
|
+
const body = await readJsonBody(req);
|
|
4267
|
+
const ids = body && Array.isArray(body.ids) ? body.ids : null;
|
|
4268
|
+
if (!ids || !ids.length) return json(res, { ok: false, error: "missing ids" }, 400);
|
|
4269
|
+
const removed = await moveNotices.ack(ids);
|
|
4270
|
+
json(res, { ok: true, removed });
|
|
4271
|
+
} catch (e) {
|
|
4272
|
+
json(res, { ok: false, error: String(e && e.message || e) }, errorStatus(e));
|
|
4273
|
+
}
|
|
4274
|
+
}
|
|
4275
|
+
}));
|
|
3506
4276
|
disposers.push(ctx.webServer.register({
|
|
3507
4277
|
kind: "exact",
|
|
3508
4278
|
path: "/archived-sessions/archive",
|