dsh-sessions-manager 3.2.2 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +6 -1
- package/README.md +6 -1
- package/lib/client.js +99 -5
- package/lib/client.js.map +2 -2
- package/lib/index.js +299 -24
- package/lib/index.js.map +4 -4
- package/package.json +1 -1
- package/src/client/index.jsx +90 -6
- package/src/client/logic.js +6 -0
- package/src/index.js +77 -0
- package/src/markdown.js +175 -0
- package/src/star-index.js +109 -0
package/lib/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
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
|
|
5
|
-
import { homedir } from "node:os";
|
|
2
|
+
import { mkdir as mkdir2, realpath, rename as rename2, stat, unlink, writeFile as writeFile2 } from "node:fs/promises";
|
|
3
|
+
import { basename, dirname, isAbsolute, join as join2 } from "node:path";
|
|
4
|
+
import { readFileSync as readFileSync3 } from "node:fs";
|
|
5
|
+
import { homedir as homedir2 } from "node:os";
|
|
6
6
|
|
|
7
7
|
// src/zstd-frame.js
|
|
8
8
|
import zlib from "node:zlib";
|
|
@@ -41,12 +41,221 @@ function rewriteFrame0Cwd(filePath, newCwd) {
|
|
|
41
41
|
writeFileSync(filePath, Buffer.concat([newFrame0, rest]));
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
// src/markdown.js
|
|
45
|
+
var MAX_TOOL_ARG = 200;
|
|
46
|
+
function isoTime(value) {
|
|
47
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) return null;
|
|
48
|
+
try {
|
|
49
|
+
return new Date(value).toISOString();
|
|
50
|
+
} catch {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function yamlString(value) {
|
|
55
|
+
return `"${String(value).replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\r?\n/g, "\\n")}"`;
|
|
56
|
+
}
|
|
57
|
+
function blocksOf(value) {
|
|
58
|
+
return Array.isArray(value) ? value.filter((b) => b && typeof b === "object") : [];
|
|
59
|
+
}
|
|
60
|
+
function textFromBlocks(blocks) {
|
|
61
|
+
const parts = [];
|
|
62
|
+
for (const block of blocks) {
|
|
63
|
+
if (block.type === "text" && typeof block.text === "string") parts.push(block.text);
|
|
64
|
+
}
|
|
65
|
+
return parts.join("\n\n").trim();
|
|
66
|
+
}
|
|
67
|
+
function imageCountOf(blocks) {
|
|
68
|
+
let count = 0;
|
|
69
|
+
for (const block of blocks) if (block.type === "image") count++;
|
|
70
|
+
return count;
|
|
71
|
+
}
|
|
72
|
+
function reasoningFromBlocks(blocks) {
|
|
73
|
+
const parts = [];
|
|
74
|
+
for (const block of blocks) {
|
|
75
|
+
if (block.type === "reasoning" && typeof block.text === "string" && block.text.trim()) parts.push(block.text.trim());
|
|
76
|
+
}
|
|
77
|
+
return parts.join("\n\n");
|
|
78
|
+
}
|
|
79
|
+
function summarizeToolArguments(name2, rawArguments) {
|
|
80
|
+
let parsed = null;
|
|
81
|
+
if (typeof rawArguments === "string") {
|
|
82
|
+
try {
|
|
83
|
+
parsed = JSON.parse(rawArguments);
|
|
84
|
+
} catch {
|
|
85
|
+
parsed = null;
|
|
86
|
+
}
|
|
87
|
+
} else if (rawArguments && typeof rawArguments === "object") {
|
|
88
|
+
parsed = rawArguments;
|
|
89
|
+
}
|
|
90
|
+
if (parsed === null) return typeof rawArguments === "string" ? rawArguments.slice(0, MAX_TOOL_ARG) : "";
|
|
91
|
+
if (typeof parsed !== "object") return String(parsed).slice(0, MAX_TOOL_ARG);
|
|
92
|
+
const preferred = ["command", "file_path", "path", "query", "url", "pattern"];
|
|
93
|
+
for (const key of preferred) {
|
|
94
|
+
if (typeof parsed[key] === "string" && parsed[key].trim()) return parsed[key];
|
|
95
|
+
}
|
|
96
|
+
const keys = Object.keys(parsed);
|
|
97
|
+
if (keys.length === 0) return "";
|
|
98
|
+
const rest = {};
|
|
99
|
+
for (const key of keys.slice(0, 6)) {
|
|
100
|
+
const value = parsed[key];
|
|
101
|
+
rest[key] = typeof value === "string" ? value : JSON.stringify(value);
|
|
102
|
+
}
|
|
103
|
+
return JSON.stringify(rest).slice(0, MAX_TOOL_ARG);
|
|
104
|
+
}
|
|
105
|
+
function renderSessionMarkdown(meta, events, options = {}) {
|
|
106
|
+
const includeReasoning = options.includeReasoning === true;
|
|
107
|
+
const includeToolResults = options.includeToolResults === true;
|
|
108
|
+
const header = meta && typeof meta === "object" ? meta : {};
|
|
109
|
+
const list = Array.isArray(events) ? events : [];
|
|
110
|
+
let title = typeof header.title === "string" && header.title.trim() ? header.title.trim() : null;
|
|
111
|
+
for (const ev of list) {
|
|
112
|
+
const data = ev && ev.data;
|
|
113
|
+
if (ev && ev.type === "session/title" && data && typeof data.title === "string" && data.title.trim()) {
|
|
114
|
+
title = data.title.trim();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
const front = ["---"];
|
|
118
|
+
if (title) front.push(`title: ${yamlString(title)}`);
|
|
119
|
+
if (typeof header.id === "string" && header.id) front.push(`sessionId: ${yamlString(header.id)}`);
|
|
120
|
+
if (typeof header.cwd === "string" && header.cwd) front.push(`cwd: ${yamlString(header.cwd)}`);
|
|
121
|
+
const created = isoTime(header.createdAt);
|
|
122
|
+
if (created) front.push(`createdAt: ${created}`);
|
|
123
|
+
const exported = isoTime(options.exportedAt);
|
|
124
|
+
if (exported) front.push(`exportedAt: ${exported}`);
|
|
125
|
+
front.push("---");
|
|
126
|
+
const out = [front.join("\n")];
|
|
127
|
+
if (title) out.push("", `# ${title}`);
|
|
128
|
+
let turn = null;
|
|
129
|
+
for (const ev of list) {
|
|
130
|
+
if (!ev || typeof ev !== "object") continue;
|
|
131
|
+
const data = ev.data && typeof ev.data === "object" ? ev.data : {};
|
|
132
|
+
const type = ev.type;
|
|
133
|
+
if (type === "turn/start") {
|
|
134
|
+
const next = Number.isInteger(data.turn) ? data.turn : null;
|
|
135
|
+
if (next !== null && next !== turn) {
|
|
136
|
+
turn = next;
|
|
137
|
+
out.push("", `## \u7B2C ${turn} \u8F6E`);
|
|
138
|
+
}
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
if (type === "user/message") {
|
|
142
|
+
const blocks = blocksOf(data.content);
|
|
143
|
+
const text = textFromBlocks(blocks);
|
|
144
|
+
const images = imageCountOf(blocks);
|
|
145
|
+
if (!text && images === 0) continue;
|
|
146
|
+
out.push("", "### \u7528\u6237", "");
|
|
147
|
+
if (text) out.push(text);
|
|
148
|
+
for (let i = 0; i < images; i++) out.push("", ``);
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
if (type === "assistant/message") {
|
|
152
|
+
const message = data.message && typeof data.message === "object" ? data.message : {};
|
|
153
|
+
const blocks = blocksOf(message.content);
|
|
154
|
+
const text = textFromBlocks(blocks);
|
|
155
|
+
const reasoning = includeReasoning ? reasoningFromBlocks(blocks) : "";
|
|
156
|
+
if (!text && !reasoning) continue;
|
|
157
|
+
out.push("", "### \u52A9\u624B", "");
|
|
158
|
+
if (reasoning) out.push("> \u601D\u8003\uFF1A" + reasoning.split("\n").join("\n> "), "");
|
|
159
|
+
if (text) out.push(text);
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
if (type === "tool/call") {
|
|
163
|
+
const name2 = typeof data.name === "string" && data.name ? data.name : "tool";
|
|
164
|
+
const summary = summarizeToolArguments(name2, data.arguments);
|
|
165
|
+
out.push("", `### \u5DE5\u5177\u8C03\u7528\uFF1A\`${name2}\``, "");
|
|
166
|
+
out.push(summary ? "```\n" + summary + "\n```" : "\uFF08\u65E0\u53C2\u6570\uFF09");
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
if (type === "tool/result" && includeToolResults) {
|
|
170
|
+
const message = data.message && typeof data.message === "object" ? data.message : {};
|
|
171
|
+
const blocks = blocksOf(message.content);
|
|
172
|
+
let text = "";
|
|
173
|
+
for (const block of blocks) {
|
|
174
|
+
if (block.type === "tool-result") text = textFromBlocks(blocksOf(block.content));
|
|
175
|
+
}
|
|
176
|
+
if (text) out.push("", "<details><summary>\u5DE5\u5177\u7ED3\u679C</summary>", "", "```\n" + text.slice(0, 2e3) + "\n```", "", "</details>");
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
out.push("");
|
|
180
|
+
return out.join("\n");
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// src/star-index.js
|
|
184
|
+
import { mkdir, rename, writeFile } from "node:fs/promises";
|
|
185
|
+
import { readFileSync as readFileSync2 } from "node:fs";
|
|
186
|
+
import { homedir } from "node:os";
|
|
187
|
+
import { join } from "node:path";
|
|
188
|
+
var STAR_SCHEMA_VERSION = 3;
|
|
189
|
+
var DEFAULT_STAR_DIR = join(homedir(), ".dsh", "sessions-manager");
|
|
190
|
+
function isSafeSessionId(value) {
|
|
191
|
+
return typeof value === "string" && value.length > 0 && value.length <= 200 && !/[\\/\0]/.test(value) && value !== "." && value !== "..";
|
|
192
|
+
}
|
|
193
|
+
function normalizeStarStore(raw) {
|
|
194
|
+
const legacy = Array.isArray(raw) ? raw : null;
|
|
195
|
+
const source = legacy || (raw && typeof raw === "object" ? raw : null);
|
|
196
|
+
const ids = source && Array.isArray(source.starredSessionIds) ? source.starredSessionIds : legacy || [];
|
|
197
|
+
const clean = [];
|
|
198
|
+
const seen = /* @__PURE__ */ new Set();
|
|
199
|
+
for (const id of ids) {
|
|
200
|
+
if (!isSafeSessionId(id)) continue;
|
|
201
|
+
if (seen.has(id)) continue;
|
|
202
|
+
seen.add(id);
|
|
203
|
+
clean.push(id);
|
|
204
|
+
}
|
|
205
|
+
return { schemaVersion: STAR_SCHEMA_VERSION, starredSessionIds: clean };
|
|
206
|
+
}
|
|
207
|
+
function createStarIndex(options = {}) {
|
|
208
|
+
const dir = options.dir || process.env.DSH_SESSIONS_MANAGER_STAR_DIR || DEFAULT_STAR_DIR;
|
|
209
|
+
const indexPath = options.indexPath || join(dir, "star.json");
|
|
210
|
+
let mutation = Promise.resolve();
|
|
211
|
+
async function read() {
|
|
212
|
+
try {
|
|
213
|
+
return normalizeStarStore(JSON.parse(readFileSync2(indexPath, "utf8")));
|
|
214
|
+
} catch {
|
|
215
|
+
return normalizeStarStore(null);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
async function write(store) {
|
|
219
|
+
await mkdir(dir, { recursive: true });
|
|
220
|
+
const tmp = join(dir, `.star-${process.pid}-${Date.now()}.tmp`);
|
|
221
|
+
await writeFile(tmp, JSON.stringify(normalizeStarStore(store), null, 2), { encoding: "utf8", mode: 384 });
|
|
222
|
+
await rename(tmp, indexPath);
|
|
223
|
+
}
|
|
224
|
+
function mutate(mutator) {
|
|
225
|
+
const operation = mutation.then(async () => {
|
|
226
|
+
const store = await read();
|
|
227
|
+
const result = await mutator(store);
|
|
228
|
+
await write(store);
|
|
229
|
+
return result;
|
|
230
|
+
});
|
|
231
|
+
mutation = operation.catch(() => {
|
|
232
|
+
});
|
|
233
|
+
return operation;
|
|
234
|
+
}
|
|
235
|
+
function setStarred(ids, starred) {
|
|
236
|
+
const wanted = (Array.isArray(ids) ? ids : []).filter(isSafeSessionId).map(String);
|
|
237
|
+
return mutate((store) => {
|
|
238
|
+
const set = new Set(store.starredSessionIds);
|
|
239
|
+
for (const id of wanted) {
|
|
240
|
+
if (starred) set.add(id);
|
|
241
|
+
else set.delete(id);
|
|
242
|
+
}
|
|
243
|
+
store.starredSessionIds = [...set];
|
|
244
|
+
return store.starredSessionIds;
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
function removeIds(ids) {
|
|
248
|
+
return setStarred(ids, false);
|
|
249
|
+
}
|
|
250
|
+
return { read, write, mutate, setStarred, removeIds, indexPath, dir };
|
|
251
|
+
}
|
|
252
|
+
|
|
44
253
|
// src/index.js
|
|
45
254
|
var name = "dsh-sessions-manager";
|
|
46
255
|
var inject = ["webServer", "workspaceRegistry", "sessionPersistence", "sessionQuery", "storageDomain"];
|
|
47
256
|
var MAX_TITLE = 80;
|
|
48
|
-
var TRASH_DIR = process.env.DSH_SESSIONS_MANAGER_TRASH_DIR ||
|
|
49
|
-
var TRASH_INDEX =
|
|
257
|
+
var TRASH_DIR = process.env.DSH_SESSIONS_MANAGER_TRASH_DIR || join2(homedir2(), ".dsh", "sessions-manager-trash");
|
|
258
|
+
var TRASH_INDEX = join2(TRASH_DIR, "index.json");
|
|
50
259
|
var TRASH_SCHEMA_VERSION = 2;
|
|
51
260
|
var DEFAULT_TRASH_SETTINGS = Object.freeze({ retentionDays: 0 });
|
|
52
261
|
var FETCH_TOOL_RE = /search|fetch|download|browse/i;
|
|
@@ -77,14 +286,14 @@ function parseIds(body) {
|
|
|
77
286
|
const raw = body && body.sessionIds;
|
|
78
287
|
if (!Array.isArray(raw)) return null;
|
|
79
288
|
const ids = [];
|
|
80
|
-
for (const v of raw) if (typeof v === "string" &&
|
|
289
|
+
for (const v of raw) if (typeof v === "string" && isSafeSessionId2(v)) ids.push(v);
|
|
81
290
|
return ids;
|
|
82
291
|
}
|
|
83
|
-
function
|
|
292
|
+
function isSafeSessionId2(value) {
|
|
84
293
|
return typeof value === "string" && value.length > 0 && value.length <= 200 && !/[\\/\0]/.test(value) && value !== "." && value !== "..";
|
|
85
294
|
}
|
|
86
295
|
function requireSessionId(value) {
|
|
87
|
-
if (!
|
|
296
|
+
if (!isSafeSessionId2(value)) {
|
|
88
297
|
const error = new Error("\u65E0\u6548\u7684 sessionId");
|
|
89
298
|
error.status = 400;
|
|
90
299
|
throw error;
|
|
@@ -213,12 +422,12 @@ function apply(ctx) {
|
|
|
213
422
|
schemaVersion: TRASH_SCHEMA_VERSION,
|
|
214
423
|
settings: { retentionDays },
|
|
215
424
|
items: raw && Array.isArray(raw.items) ? raw.items : [],
|
|
216
|
-
purgedSessionIds: raw && Array.isArray(raw.purgedSessionIds) ? [...new Set(raw.purgedSessionIds.filter(
|
|
425
|
+
purgedSessionIds: raw && Array.isArray(raw.purgedSessionIds) ? [...new Set(raw.purgedSessionIds.filter(isSafeSessionId2).map(String))] : []
|
|
217
426
|
};
|
|
218
427
|
}
|
|
219
428
|
async function readTrashStore() {
|
|
220
429
|
try {
|
|
221
|
-
return normalizeTrashStore(JSON.parse(
|
|
430
|
+
return normalizeTrashStore(JSON.parse(readFileSync3(TRASH_INDEX, "utf8")));
|
|
222
431
|
} catch (e) {
|
|
223
432
|
return normalizeTrashStore(null);
|
|
224
433
|
}
|
|
@@ -227,10 +436,10 @@ function apply(ctx) {
|
|
|
227
436
|
return (await readTrashStore()).items;
|
|
228
437
|
}
|
|
229
438
|
async function writeTrashStore(store) {
|
|
230
|
-
await
|
|
231
|
-
const tmp =
|
|
232
|
-
await
|
|
233
|
-
await
|
|
439
|
+
await mkdir2(TRASH_DIR, { recursive: true });
|
|
440
|
+
const tmp = join2(TRASH_DIR, `.index-${process.pid}-${Date.now()}.tmp`);
|
|
441
|
+
await writeFile2(tmp, JSON.stringify(normalizeTrashStore(store), null, 2), { encoding: "utf8", mode: 384 });
|
|
442
|
+
await rename2(tmp, TRASH_INDEX);
|
|
234
443
|
}
|
|
235
444
|
function mutateTrash(mutator) {
|
|
236
445
|
const operation = trashMutation.then(async () => {
|
|
@@ -243,6 +452,16 @@ function apply(ctx) {
|
|
|
243
452
|
});
|
|
244
453
|
return operation;
|
|
245
454
|
}
|
|
455
|
+
const stars = createStarIndex();
|
|
456
|
+
async function gcStars(validIds) {
|
|
457
|
+
try {
|
|
458
|
+
const store = await stars.read();
|
|
459
|
+
const valid = new Set(validIds.map(String));
|
|
460
|
+
const gone = store.starredSessionIds.filter((id) => !valid.has(id));
|
|
461
|
+
if (gone.length) await stars.removeIds(gone);
|
|
462
|
+
} catch (e) {
|
|
463
|
+
}
|
|
464
|
+
}
|
|
246
465
|
async function deleteOne(sid) {
|
|
247
466
|
requireSessionId(sid);
|
|
248
467
|
let header = null;
|
|
@@ -382,6 +601,8 @@ function apply(ctx) {
|
|
|
382
601
|
purged = true;
|
|
383
602
|
});
|
|
384
603
|
if (!purged) throw new Error("\u5F7B\u5E95\u5220\u9664\u5931\u8D25");
|
|
604
|
+
stars.removeIds([sid]).catch(() => {
|
|
605
|
+
});
|
|
385
606
|
return { ok: true, purged: true };
|
|
386
607
|
}
|
|
387
608
|
async function trashSettings(next) {
|
|
@@ -416,8 +637,8 @@ function apply(ctx) {
|
|
|
416
637
|
async function moveTargetWorkspace(rawPath) {
|
|
417
638
|
if (typeof rawPath !== "string" || !rawPath.trim()) throw new Error("\u7F3A\u5C11\u76EE\u6807\u5DE5\u4F5C\u533A\u8DEF\u5F84");
|
|
418
639
|
let p = String(rawPath).trim();
|
|
419
|
-
if (p.startsWith("~/")) p =
|
|
420
|
-
if (!isAbsolute(p)) p =
|
|
640
|
+
if (p.startsWith("~/")) p = join2(homedir2(), p.slice(2));
|
|
641
|
+
if (!isAbsolute(p)) p = join2(homedir2(), p);
|
|
421
642
|
let canonical = null;
|
|
422
643
|
try {
|
|
423
644
|
canonical = await realpath(p);
|
|
@@ -425,7 +646,7 @@ function apply(ctx) {
|
|
|
425
646
|
canonical = null;
|
|
426
647
|
}
|
|
427
648
|
if (canonical === null) {
|
|
428
|
-
await
|
|
649
|
+
await mkdir2(p, { recursive: true });
|
|
429
650
|
canonical = await realpath(p);
|
|
430
651
|
}
|
|
431
652
|
return { canonical, entity: await w.create(canonical, basename(canonical) || "workspace") };
|
|
@@ -463,13 +684,13 @@ function apply(ctx) {
|
|
|
463
684
|
if (!oldPath || !newPath || oldPath === newPath) return false;
|
|
464
685
|
const backupPath = `${oldPath}.move-backup-${Date.now()}`;
|
|
465
686
|
try {
|
|
466
|
-
await
|
|
467
|
-
await
|
|
687
|
+
await mkdir2(dirname(newPath), { recursive: true });
|
|
688
|
+
await rename2(oldPath, backupPath);
|
|
468
689
|
await rewriteFrame0Cwd(backupPath, canonical);
|
|
469
|
-
await
|
|
690
|
+
await rename2(backupPath, newPath);
|
|
470
691
|
} catch (e) {
|
|
471
692
|
try {
|
|
472
|
-
await
|
|
693
|
+
await rename2(backupPath, oldPath);
|
|
473
694
|
} catch (_) {
|
|
474
695
|
}
|
|
475
696
|
if (e && e.code !== "ENOENT") throw e;
|
|
@@ -515,7 +736,7 @@ function apply(ctx) {
|
|
|
515
736
|
const backupPath = oldPath ? `${oldPath}.move-backup-${Date.now()}` : null;
|
|
516
737
|
if (backupPath) {
|
|
517
738
|
try {
|
|
518
|
-
await
|
|
739
|
+
await rename2(oldPath, backupPath);
|
|
519
740
|
} catch (e) {
|
|
520
741
|
if (e && e.code !== "ENOENT") throw new Error("\u79FB\u52A8\u5931\u8D25\uFF1A\u65E0\u6CD5\u5907\u4EFD\u65E7\u7684\u4F1A\u8BDD\u65E5\u5FD7");
|
|
521
742
|
}
|
|
@@ -523,7 +744,7 @@ function apply(ctx) {
|
|
|
523
744
|
const restore = async () => {
|
|
524
745
|
if (backupPath) {
|
|
525
746
|
try {
|
|
526
|
-
await
|
|
747
|
+
await rename2(backupPath, oldPath);
|
|
527
748
|
} catch (_) {
|
|
528
749
|
}
|
|
529
750
|
}
|
|
@@ -620,9 +841,11 @@ function apply(ctx) {
|
|
|
620
841
|
async function allSessionItems() {
|
|
621
842
|
let materialized = /* @__PURE__ */ new Set();
|
|
622
843
|
let live = ctx.get("sessions");
|
|
844
|
+
let headersOk = false;
|
|
623
845
|
try {
|
|
624
846
|
const headers = await sp.list();
|
|
625
847
|
materialized = new Set(headers.map((h) => String(h.id)));
|
|
848
|
+
headersOk = true;
|
|
626
849
|
} catch (e) {
|
|
627
850
|
}
|
|
628
851
|
const ids = [];
|
|
@@ -658,6 +881,13 @@ function apply(ctx) {
|
|
|
658
881
|
const res2 = await Promise.all(visibleIds.slice(i, i + CHUNK).map(resolveOne));
|
|
659
882
|
for (const it of res2) items.push({ ...it, archived: currentArchived.has(it.sessionId) });
|
|
660
883
|
}
|
|
884
|
+
let starredSet = /* @__PURE__ */ new Set();
|
|
885
|
+
try {
|
|
886
|
+
starredSet = new Set((await stars.read()).starredSessionIds);
|
|
887
|
+
} catch (e) {
|
|
888
|
+
}
|
|
889
|
+
for (const it of items) it.starred = starredSet.has(String(it.sessionId));
|
|
890
|
+
if (headersOk) await gcStars(ids);
|
|
661
891
|
return items;
|
|
662
892
|
}
|
|
663
893
|
async function sidebarAuthority() {
|
|
@@ -1053,6 +1283,51 @@ function apply(ctx) {
|
|
|
1053
1283
|
}
|
|
1054
1284
|
}
|
|
1055
1285
|
}));
|
|
1286
|
+
disposers.push(ctx.webServer.register({
|
|
1287
|
+
kind: "exact",
|
|
1288
|
+
path: "/archived-sessions/star/set",
|
|
1289
|
+
handler: async (req, res) => {
|
|
1290
|
+
try {
|
|
1291
|
+
const body = await readJsonBody(req);
|
|
1292
|
+
const starred = !!(body && body.starred);
|
|
1293
|
+
let ids = parseIds(body);
|
|
1294
|
+
if ((!ids || ids.length === 0) && body && typeof body.sessionId === "string") {
|
|
1295
|
+
ids = isSafeSessionId2(body.sessionId) ? [body.sessionId] : null;
|
|
1296
|
+
}
|
|
1297
|
+
if (!ids || ids.length === 0) return json(res, { ok: false, error: "missing sessionId" }, 400);
|
|
1298
|
+
const starredSessionIds = await stars.setStarred(ids, starred);
|
|
1299
|
+
json(res, { ok: true, starredSessionIds });
|
|
1300
|
+
} catch (e) {
|
|
1301
|
+
json(res, { ok: false, error: String(e && e.message || e) }, errorStatus(e));
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
}));
|
|
1305
|
+
disposers.push(ctx.webServer.register({
|
|
1306
|
+
kind: "exact",
|
|
1307
|
+
path: "/archived-sessions/export-md",
|
|
1308
|
+
handler: async (req, res) => {
|
|
1309
|
+
try {
|
|
1310
|
+
const url = new URL(req.url, "http://localhost");
|
|
1311
|
+
const sid = url.searchParams.get("sessionId");
|
|
1312
|
+
requireSessionId(sid);
|
|
1313
|
+
const r = await sp.readFrom(sid, 0);
|
|
1314
|
+
if (!r || !r.meta) {
|
|
1315
|
+
const error = new Error("\u65E0\u6CD5\u8BFB\u53D6\u8BE5\u4F1A\u8BDD\u7684\u65E5\u5FD7");
|
|
1316
|
+
error.status = 404;
|
|
1317
|
+
throw error;
|
|
1318
|
+
}
|
|
1319
|
+
const md = renderSessionMarkdown({ ...r.meta, id: sid }, r.events || []);
|
|
1320
|
+
res.writeHead(200, {
|
|
1321
|
+
"content-type": "text/markdown; charset=utf-8",
|
|
1322
|
+
"content-disposition": `attachment; filename="dsh-session-${sid}.md"`,
|
|
1323
|
+
"cache-control": "no-store"
|
|
1324
|
+
});
|
|
1325
|
+
res.end(md);
|
|
1326
|
+
} catch (e) {
|
|
1327
|
+
json(res, { error: String(e && e.message || e) }, errorStatus(e));
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
}));
|
|
1056
1331
|
disposers.push(ctx.webServer.register({
|
|
1057
1332
|
kind: "exact",
|
|
1058
1333
|
path: "/archived-sessions/sidebar-state",
|