@xopcai/xopc 0.0.30 → 0.0.31
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/dist/extensions/telegram/xopc.extension.json +1 -1
- package/dist/gateway/static/root/assets/agents-3u63Fw2Y.js +216 -0
- package/dist/gateway/static/root/assets/agents-3u63Fw2Y.js.map +1 -0
- package/dist/gateway/static/root/assets/{apps-page-CTChHQAu.js → apps-page-CWegY6Kp.js} +2 -2
- package/dist/gateway/static/root/assets/{apps-page-CTChHQAu.js.map → apps-page-CWegY6Kp.js.map} +1 -1
- package/dist/gateway/static/root/assets/channels-settings-CiyeXcTK.js +9 -0
- package/dist/gateway/static/root/assets/channels-settings-CiyeXcTK.js.map +1 -0
- package/dist/gateway/static/root/assets/cron-api-_j_79Zf5.js +3 -0
- package/dist/gateway/static/root/assets/cron-api-_j_79Zf5.js.map +1 -0
- package/dist/gateway/static/root/assets/cron-page-S86YNTtI.js +2 -0
- package/dist/gateway/static/root/assets/cron-page-S86YNTtI.js.map +1 -0
- package/dist/gateway/static/root/assets/dist-D0jxbvuz.js +2 -0
- package/dist/gateway/static/root/assets/{dist-UWGUW3x8.js.map → dist-D0jxbvuz.js.map} +1 -1
- package/dist/gateway/static/root/assets/{extension-debug-page-BwB4a4cK.js → extension-debug-page-DB630cW8.js} +2 -2
- package/dist/gateway/static/root/assets/{extension-debug-page-BwB4a4cK.js.map → extension-debug-page-DB630cW8.js.map} +1 -1
- package/dist/gateway/static/root/assets/{extension-page-CSWu2PHZ.js → extension-page-CnoPUBul.js} +2 -2
- package/dist/gateway/static/root/assets/{extension-page-CSWu2PHZ.js.map → extension-page-CnoPUBul.js.map} +1 -1
- package/dist/gateway/static/root/assets/{extension-settings-page-B12K2a13.js → extension-settings-page-BsiOkvBe.js} +2 -2
- package/dist/gateway/static/root/assets/{extension-settings-page-B12K2a13.js.map → extension-settings-page-BsiOkvBe.js.map} +1 -1
- package/dist/gateway/static/root/assets/{index-D0pFZ0OE.js → index-DHLmAIQl.js} +81 -81
- package/dist/gateway/static/root/assets/{index-D0pFZ0OE.js.map → index-DHLmAIQl.js.map} +1 -1
- package/dist/gateway/static/root/assets/index-DoPwy4aU.css +1 -0
- package/dist/gateway/static/root/assets/logs-page-Bndhenn2.js +2 -0
- package/dist/gateway/static/root/assets/logs-page-Bndhenn2.js.map +1 -0
- package/dist/gateway/static/root/assets/sessions-page-Q201-_lP.js +2 -0
- package/dist/gateway/static/root/assets/{sessions-page-DJkuWpOT.js.map → sessions-page-Q201-_lP.js.map} +1 -1
- package/dist/gateway/static/root/assets/settings-page-Cw75fpc6.js +2 -0
- package/dist/gateway/static/root/assets/settings-page-Cw75fpc6.js.map +1 -0
- package/dist/gateway/static/root/assets/skills-page-CVwEzD_J.js +3 -0
- package/dist/gateway/static/root/assets/skills-page-CVwEzD_J.js.map +1 -0
- package/dist/gateway/static/root/index.html +2 -2
- package/dist/package.js +1 -1
- package/dist/src/agent/orchestration/agent-orchestrator.js +1 -1
- package/dist/src/agent/service/process-direct-streaming.js +12 -1
- package/dist/src/agent/service/process-direct-streaming.js.map +1 -1
- package/dist/src/agent/service.d.ts +4 -0
- package/dist/src/agent/service.js +7 -1
- package/dist/src/agent/service.js.map +1 -1
- package/dist/src/agent/skills/marketplace/resolve-adapter.js +1 -1
- package/dist/src/agent/skills/marketplace/resolve-adapter.js.map +1 -1
- package/dist/src/config/schema.js +1 -0
- package/dist/src/config/schema.js.map +1 -1
- package/dist/src/cron/validation.js +1 -1
- package/dist/src/cron/validation.js.map +1 -1
- package/dist/src/gateway/hono/routes/sessions.js +124 -2
- package/dist/src/gateway/hono/routes/sessions.js.map +1 -1
- package/dist/src/gateway/hono/sse.js +9 -2
- package/dist/src/gateway/hono/sse.js.map +1 -1
- package/dist/src/gateway/service/run-gateway-agent.d.ts +1 -0
- package/dist/src/gateway/service/run-gateway-agent.js +18 -10
- package/dist/src/gateway/service/run-gateway-agent.js.map +1 -1
- package/dist/src/gateway/service.d.ts +23 -1
- package/dist/src/gateway/service.js +47 -3
- package/dist/src/gateway/service.js.map +1 -1
- package/dist/src/session/abort-cutoff.d.ts +6 -0
- package/dist/src/session/abort-cutoff.js +10 -0
- package/dist/src/session/abort-cutoff.js.map +1 -0
- package/dist/src/session/compaction-checkpoints.d.ts +8 -0
- package/dist/src/session/compaction-checkpoints.js +21 -0
- package/dist/src/session/compaction-checkpoints.js.map +1 -0
- package/dist/src/session/index.d.ts +8 -1
- package/dist/src/session/index.js +7 -1
- package/dist/src/session/manager.d.ts +26 -1
- package/dist/src/session/manager.js +39 -2
- package/dist/src/session/manager.js.map +1 -1
- package/dist/src/session/patch-metadata.d.ts +12 -0
- package/dist/src/session/patch-metadata.js +23 -0
- package/dist/src/session/patch-metadata.js.map +1 -0
- package/dist/src/session/search-index.d.ts +2 -0
- package/dist/src/session/search-index.js +30 -2
- package/dist/src/session/search-index.js.map +1 -1
- package/dist/src/session/session-context-for-llm.d.ts +32 -0
- package/dist/src/session/session-context-for-llm.js +60 -0
- package/dist/src/session/session-context-for-llm.js.map +1 -0
- package/dist/src/session/store.d.ts +36 -2
- package/dist/src/session/store.js +200 -28
- package/dist/src/session/store.js.map +1 -1
- package/dist/src/session/strip-webchat-early-save.d.ts +5 -0
- package/dist/src/session/strip-webchat-early-save.js +17 -0
- package/dist/src/session/strip-webchat-early-save.js.map +1 -0
- package/dist/src/session/transcript-format.d.ts +46 -0
- package/dist/src/session/transcript-format.js +88 -0
- package/dist/src/session/transcript-format.js.map +1 -0
- package/dist/src/session/types.d.ts +37 -0
- package/dist/src/session/types.js.map +1 -1
- package/dist/src/utils/logger/log-store.js +4 -3
- package/dist/src/utils/logger/log-store.js.map +1 -1
- package/package.json +1 -1
- package/dist/gateway/static/root/assets/agents-BfwtJOPK.js +0 -216
- package/dist/gateway/static/root/assets/agents-BfwtJOPK.js.map +0 -1
- package/dist/gateway/static/root/assets/channels-settings-BpwVOvvf.js +0 -9
- package/dist/gateway/static/root/assets/channels-settings-BpwVOvvf.js.map +0 -1
- package/dist/gateway/static/root/assets/cron-page-C_6AbVRf.js +0 -2
- package/dist/gateway/static/root/assets/cron-page-C_6AbVRf.js.map +0 -1
- package/dist/gateway/static/root/assets/cron-utils-DZ7pabh5.js +0 -3
- package/dist/gateway/static/root/assets/cron-utils-DZ7pabh5.js.map +0 -1
- package/dist/gateway/static/root/assets/dist-UWGUW3x8.js +0 -2
- package/dist/gateway/static/root/assets/index-C6itMrqR.css +0 -1
- package/dist/gateway/static/root/assets/logs-page-BXqha2gI.js +0 -2
- package/dist/gateway/static/root/assets/logs-page-BXqha2gI.js.map +0 -1
- package/dist/gateway/static/root/assets/sessions-page-DJkuWpOT.js +0 -2
- package/dist/gateway/static/root/assets/settings-page-CleZrGHy.js +0 -2
- package/dist/gateway/static/root/assets/settings-page-CleZrGHy.js.map +0 -1
- package/dist/gateway/static/root/assets/skills-page-D7NiIOzA.js +0 -3
- package/dist/gateway/static/root/assets/skills-page-D7NiIOzA.js.map +0 -1
|
@@ -4,12 +4,14 @@ import { createLogger } from "../utils/logger/index.js";
|
|
|
4
4
|
import { init_logger } from "../utils/logger.js";
|
|
5
5
|
import { FILENAMES, init_paths, resolveSessionsDir } from "../config/paths.js";
|
|
6
6
|
import { init_write_file_atomic, writeTextAtomic } from "../infra/write-file-atomic.js";
|
|
7
|
+
import { buildSessionContextForLlm, isTranscriptContextEntry, mergeLlmMessagesPreservingContextRows } from "./session-context-for-llm.js";
|
|
8
|
+
import { buildTranscriptEnvelope, parseStoredTranscriptJson } from "./transcript-format.js";
|
|
7
9
|
import { invalidateSessionSearchIndexCache } from "./search-index-cache.js";
|
|
8
10
|
import { resolveSessionShardRelativePath } from "./shard-path.js";
|
|
9
11
|
import "./types.js";
|
|
10
12
|
import { SessionCompactor } from "../agent/memory/compaction.js";
|
|
11
13
|
import { SlidingWindow } from "../agent/memory/window.js";
|
|
12
|
-
import {
|
|
14
|
+
import { normalizeCompactionCheckpointId } from "./compaction-checkpoints.js";
|
|
13
15
|
import { basename, join } from "path";
|
|
14
16
|
import { existsSync } from "fs";
|
|
15
17
|
import { copyFile, mkdir, readFile, readdir, stat, unlink } from "fs/promises";
|
|
@@ -255,10 +257,18 @@ var SessionStore = class {
|
|
|
255
257
|
return out;
|
|
256
258
|
}
|
|
257
259
|
async scanSessionFile(key) {
|
|
258
|
-
const messages = await this.loadMessages(key);
|
|
259
|
-
if (messages.length === 0) return null;
|
|
260
260
|
const { jsonPath } = this.sessionPathsForKey(key);
|
|
261
|
+
let raw;
|
|
262
|
+
try {
|
|
263
|
+
raw = await readFile(jsonPath, "utf-8");
|
|
264
|
+
} catch {
|
|
265
|
+
return null;
|
|
266
|
+
}
|
|
267
|
+
const { messages, envelope } = parseStoredTranscriptJson(raw);
|
|
268
|
+
if (messages.length === 0) return null;
|
|
261
269
|
const stats = await stat(jsonPath);
|
|
270
|
+
const sessionStartedAt = envelope?.createdAt ?? stats.birthtime.toISOString();
|
|
271
|
+
const lastInteractionAt = envelope?.updatedAt ?? stats.mtime.toISOString();
|
|
262
272
|
const { channel, chatId } = this.parseSessionKey(key);
|
|
263
273
|
const routing = this.extractRoutingFromKey(key, channel);
|
|
264
274
|
const isCronSession = channel === "cron";
|
|
@@ -275,6 +285,9 @@ var SessionStore = class {
|
|
|
275
285
|
compactedCount: 0,
|
|
276
286
|
sourceChannel: channel,
|
|
277
287
|
sourceChatId: chatId,
|
|
288
|
+
...envelope?.id ? { transcriptId: envelope.id } : {},
|
|
289
|
+
sessionStartedAt,
|
|
290
|
+
lastInteractionAt,
|
|
278
291
|
routing,
|
|
279
292
|
...isCronSession ? {
|
|
280
293
|
sessionType: "cron",
|
|
@@ -352,15 +365,39 @@ var SessionStore = class {
|
|
|
352
365
|
hasMore: offset + limit < total
|
|
353
366
|
};
|
|
354
367
|
}
|
|
355
|
-
async get(key) {
|
|
368
|
+
async get(key, options) {
|
|
356
369
|
const metadata = await this.getMetadata(key);
|
|
357
370
|
if (!metadata) return null;
|
|
358
371
|
const messages = await this.loadMessages(key);
|
|
372
|
+
let transcriptSummary;
|
|
373
|
+
if (options?.includeTranscriptSummary) {
|
|
374
|
+
const env = await this.loadTranscriptDocument(key);
|
|
375
|
+
if (env) transcriptSummary = {
|
|
376
|
+
id: env.id,
|
|
377
|
+
version: env.version,
|
|
378
|
+
createdAt: env.createdAt,
|
|
379
|
+
updatedAt: env.updatedAt,
|
|
380
|
+
compactionCount: env.compactions?.length ?? 0
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
let transcriptRows;
|
|
384
|
+
if (options?.includeTranscriptRows) transcriptRows = await this.loadTranscriptRows(key);
|
|
359
385
|
return {
|
|
360
386
|
...metadata,
|
|
361
|
-
messages: this.convertMessages(messages)
|
|
387
|
+
messages: this.convertMessages(messages),
|
|
388
|
+
...transcriptSummary ? { transcriptSummary } : {},
|
|
389
|
+
...transcriptRows !== void 0 ? { transcriptRows } : {}
|
|
362
390
|
};
|
|
363
391
|
}
|
|
392
|
+
/** Full on-disk transcript rows (LLM messages + optional `kind: 'context'`). */
|
|
393
|
+
async loadTranscriptRows(key) {
|
|
394
|
+
const { jsonPath } = this.sessionPathsForKey(key);
|
|
395
|
+
try {
|
|
396
|
+
return parseStoredTranscriptJson(await readFile(jsonPath, "utf-8")).rows;
|
|
397
|
+
} catch {
|
|
398
|
+
return [];
|
|
399
|
+
}
|
|
400
|
+
}
|
|
364
401
|
async getMetadata(key) {
|
|
365
402
|
const metadata = (await this.loadIndex()).sessions.find((s) => s.key === key);
|
|
366
403
|
if (!metadata) {
|
|
@@ -458,17 +495,7 @@ var SessionStore = class {
|
|
|
458
495
|
const primary = this.sessionPathsForKey(key);
|
|
459
496
|
const readAndNormalize = async (path) => {
|
|
460
497
|
try {
|
|
461
|
-
const
|
|
462
|
-
const messages = JSON.parse(data);
|
|
463
|
-
if (hasProblematicMessages(messages)) {
|
|
464
|
-
const cleaned = cleanTrailingErrors(messages);
|
|
465
|
-
if (cleaned.length !== messages.length) log.info({
|
|
466
|
-
key,
|
|
467
|
-
original: messages.length,
|
|
468
|
-
cleaned: cleaned.length
|
|
469
|
-
}, "Cleaned problematic messages on load");
|
|
470
|
-
return cleaned;
|
|
471
|
-
}
|
|
498
|
+
const { messages } = parseStoredTranscriptJson(await readFile(path, "utf-8"));
|
|
472
499
|
return messages;
|
|
473
500
|
} catch {
|
|
474
501
|
return null;
|
|
@@ -484,6 +511,17 @@ var SessionStore = class {
|
|
|
484
511
|
return [];
|
|
485
512
|
}
|
|
486
513
|
/**
|
|
514
|
+
* Load the versioned transcript document (stable id, compaction history), or null if missing/invalid.
|
|
515
|
+
*/
|
|
516
|
+
async loadTranscriptDocument(key) {
|
|
517
|
+
const { jsonPath } = this.sessionPathsForKey(key);
|
|
518
|
+
try {
|
|
519
|
+
return parseStoredTranscriptJson(await readFile(jsonPath, "utf-8")).envelope;
|
|
520
|
+
} catch {
|
|
521
|
+
return null;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
487
525
|
* Find the most recent archived session file for a given key.
|
|
488
526
|
* Archived files have format: {safeKey}.{timestamp}.json
|
|
489
527
|
*/
|
|
@@ -505,11 +543,22 @@ var SessionStore = class {
|
|
|
505
543
|
}
|
|
506
544
|
/**
|
|
507
545
|
* Persist transcript JSON + merge session row into the index. Caller must hold {@link runStoreMutation} (or be nested under it).
|
|
546
|
+
* Transcript is stored as a versioned document (pi-style header) with stable {@link XopcSessionTranscriptV1.id}.
|
|
508
547
|
*/
|
|
509
|
-
async
|
|
548
|
+
async writeSessionTranscriptFromStoredRows(key, storedRows, options) {
|
|
510
549
|
const { dir, jsonPath } = this.sessionPathsForKey(key);
|
|
511
550
|
await mkdir(dir, { recursive: true });
|
|
512
|
-
|
|
551
|
+
let previous = null;
|
|
552
|
+
try {
|
|
553
|
+
previous = parseStoredTranscriptJson(await readFile(jsonPath, "utf-8")).envelope;
|
|
554
|
+
} catch {}
|
|
555
|
+
const doc = buildTranscriptEnvelope({
|
|
556
|
+
storedRows,
|
|
557
|
+
previous,
|
|
558
|
+
appendCompaction: options?.appendCompaction
|
|
559
|
+
});
|
|
560
|
+
await writeTextAtomic(jsonPath, JSON.stringify(doc, null, 2));
|
|
561
|
+
const llmMessages = buildSessionContextForLlm(storedRows);
|
|
513
562
|
const index = await this.loadIndex();
|
|
514
563
|
const existingIdx = index.sessions.findIndex((s) => s.key === key);
|
|
515
564
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -523,10 +572,13 @@ var SessionStore = class {
|
|
|
523
572
|
...prev,
|
|
524
573
|
sourceChannel: channel,
|
|
525
574
|
sourceChatId: chatId,
|
|
526
|
-
messageCount:
|
|
527
|
-
estimatedTokens: this.estimateTokens(
|
|
575
|
+
messageCount: llmMessages.length,
|
|
576
|
+
estimatedTokens: this.estimateTokens(llmMessages),
|
|
528
577
|
updatedAt: now,
|
|
529
578
|
lastAccessedAt: now,
|
|
579
|
+
transcriptId: doc.id,
|
|
580
|
+
sessionStartedAt: prev.sessionStartedAt ?? doc.createdAt,
|
|
581
|
+
lastInteractionAt: now,
|
|
530
582
|
routing: routing || prev.routing,
|
|
531
583
|
...isCronSession ? {
|
|
532
584
|
sessionType: "cron",
|
|
@@ -544,8 +596,8 @@ var SessionStore = class {
|
|
|
544
596
|
} : {},
|
|
545
597
|
stats: {
|
|
546
598
|
...prev.stats,
|
|
547
|
-
messageCount:
|
|
548
|
-
tokenCount: this.estimateTokens(
|
|
599
|
+
messageCount: llmMessages.length,
|
|
600
|
+
tokenCount: this.estimateTokens(llmMessages),
|
|
549
601
|
lastTurnAt: Date.now()
|
|
550
602
|
}
|
|
551
603
|
};
|
|
@@ -556,8 +608,11 @@ var SessionStore = class {
|
|
|
556
608
|
createdAt: now,
|
|
557
609
|
updatedAt: now,
|
|
558
610
|
lastAccessedAt: now,
|
|
559
|
-
|
|
560
|
-
|
|
611
|
+
transcriptId: doc.id,
|
|
612
|
+
sessionStartedAt: doc.createdAt,
|
|
613
|
+
lastInteractionAt: now,
|
|
614
|
+
messageCount: llmMessages.length,
|
|
615
|
+
estimatedTokens: this.estimateTokens(llmMessages),
|
|
561
616
|
compactedCount: 0,
|
|
562
617
|
sourceChannel: channel,
|
|
563
618
|
sourceChatId: chatId,
|
|
@@ -571,8 +626,8 @@ var SessionStore = class {
|
|
|
571
626
|
customData: { heartbeatTarget: chatId }
|
|
572
627
|
} : {},
|
|
573
628
|
stats: {
|
|
574
|
-
messageCount:
|
|
575
|
-
tokenCount: this.estimateTokens(
|
|
629
|
+
messageCount: llmMessages.length,
|
|
630
|
+
tokenCount: this.estimateTokens(llmMessages),
|
|
576
631
|
lastTurnAt: Date.now()
|
|
577
632
|
}
|
|
578
633
|
});
|
|
@@ -580,6 +635,37 @@ var SessionStore = class {
|
|
|
580
635
|
await this.saveIndex();
|
|
581
636
|
invalidateSessionSearchIndexCache();
|
|
582
637
|
}
|
|
638
|
+
async writeSessionTranscriptAndUpdateIndex(key, messages, options) {
|
|
639
|
+
const { jsonPath } = this.sessionPathsForKey(key);
|
|
640
|
+
let storedRows = messages;
|
|
641
|
+
try {
|
|
642
|
+
const parsed = parseStoredTranscriptJson(await readFile(jsonPath, "utf-8"));
|
|
643
|
+
if (parsed.rows.some((r) => isTranscriptContextEntry(r))) storedRows = mergeLlmMessagesPreservingContextRows(parsed.rows, messages);
|
|
644
|
+
} catch {}
|
|
645
|
+
await this.writeSessionTranscriptFromStoredRows(key, storedRows, options);
|
|
646
|
+
}
|
|
647
|
+
/**
|
|
648
|
+
* Append a persisted-only transcript row (`kind: 'context'`), visible on disk and in session search
|
|
649
|
+
* after stripping, but never returned from {@link loadMessages}.
|
|
650
|
+
*/
|
|
651
|
+
async appendTranscriptContextEntry(key, entry) {
|
|
652
|
+
return this.runStoreMutation(async () => {
|
|
653
|
+
this.invalidateIndexCache();
|
|
654
|
+
const { jsonPath } = this.sessionPathsForKey(key);
|
|
655
|
+
let rows = [];
|
|
656
|
+
try {
|
|
657
|
+
rows = parseStoredTranscriptJson(await readFile(jsonPath, "utf-8")).rows;
|
|
658
|
+
} catch {}
|
|
659
|
+
const row = {
|
|
660
|
+
kind: "context",
|
|
661
|
+
id: typeof entry.id === "string" ? entry.id : void 0,
|
|
662
|
+
text: typeof entry.text === "string" ? entry.text : void 0,
|
|
663
|
+
data: entry.data,
|
|
664
|
+
createdAt: entry.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
665
|
+
};
|
|
666
|
+
await this.writeSessionTranscriptFromStoredRows(key, [...rows, row], {});
|
|
667
|
+
});
|
|
668
|
+
}
|
|
583
669
|
async saveMessages(key, messages) {
|
|
584
670
|
return this.runStoreMutation(async () => {
|
|
585
671
|
this.invalidateIndexCache();
|
|
@@ -618,7 +704,13 @@ var SessionStore = class {
|
|
|
618
704
|
this.invalidateIndexCache();
|
|
619
705
|
const { jsonPath } = this.sessionPathsForKey(key);
|
|
620
706
|
await this.captureCompactionCheckpointIfExists(key, jsonPath);
|
|
621
|
-
await this.writeSessionTranscriptAndUpdateIndex(key, compacted
|
|
707
|
+
await this.writeSessionTranscriptAndUpdateIndex(key, compacted, { appendCompaction: {
|
|
708
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
709
|
+
summary: result.summary,
|
|
710
|
+
firstKeptIndex: result.firstKeptIndex,
|
|
711
|
+
tokensBefore: result.tokensBefore,
|
|
712
|
+
tokensAfter: result.tokensAfter
|
|
713
|
+
} });
|
|
622
714
|
const metadata = await this.getMetadata(key);
|
|
623
715
|
if (metadata) await this.updateMetadata(key, { compactedCount: metadata.compactedCount + 1 });
|
|
624
716
|
log.info({
|
|
@@ -651,6 +743,81 @@ var SessionStore = class {
|
|
|
651
743
|
lastCompactionAt: void 0
|
|
652
744
|
};
|
|
653
745
|
}
|
|
746
|
+
/**
|
|
747
|
+
* List pre-compaction transcript snapshots for a session (newest first).
|
|
748
|
+
*/
|
|
749
|
+
async listCompactionCheckpoints(key) {
|
|
750
|
+
const safeKey = this.sanitizeKey(key);
|
|
751
|
+
const dir = join(this.sessionsDir, resolveSessionShardRelativePath(key));
|
|
752
|
+
const prefix = this.checkpointBasenamePrefix(safeKey);
|
|
753
|
+
let names;
|
|
754
|
+
try {
|
|
755
|
+
names = await readdir(dir);
|
|
756
|
+
} catch {
|
|
757
|
+
return [];
|
|
758
|
+
}
|
|
759
|
+
const files = names.filter((n) => n.startsWith(prefix) && n.endsWith(".json"));
|
|
760
|
+
const valid = (await Promise.all(files.map(async (name) => {
|
|
761
|
+
const p = join(dir, name);
|
|
762
|
+
try {
|
|
763
|
+
const s = await stat(p);
|
|
764
|
+
const id = name.slice(prefix.length, -5);
|
|
765
|
+
if (!normalizeCompactionCheckpointId(id)) return null;
|
|
766
|
+
return {
|
|
767
|
+
id: normalizeCompactionCheckpointId(id),
|
|
768
|
+
sizeBytes: s.size,
|
|
769
|
+
modifiedAt: new Date(s.mtimeMs).toISOString()
|
|
770
|
+
};
|
|
771
|
+
} catch {
|
|
772
|
+
return null;
|
|
773
|
+
}
|
|
774
|
+
}))).filter((r) => r !== null);
|
|
775
|
+
valid.sort((a, b) => b.modifiedAt.localeCompare(a.modifiedAt));
|
|
776
|
+
return valid;
|
|
777
|
+
}
|
|
778
|
+
/**
|
|
779
|
+
* Metadata for a single compaction checkpoint file.
|
|
780
|
+
*/
|
|
781
|
+
async getCompactionCheckpointDetail(key, checkpointId) {
|
|
782
|
+
const id = normalizeCompactionCheckpointId(checkpointId);
|
|
783
|
+
if (!id) return null;
|
|
784
|
+
const safeKey = this.sanitizeKey(key);
|
|
785
|
+
const cpPath = join(join(this.sessionsDir, resolveSessionShardRelativePath(key)), `${this.checkpointBasenamePrefix(safeKey)}${id}.json`);
|
|
786
|
+
if (!existsSync(cpPath)) return null;
|
|
787
|
+
try {
|
|
788
|
+
const { messages } = parseStoredTranscriptJson(await readFile(cpPath, "utf-8"));
|
|
789
|
+
const s = await stat(cpPath);
|
|
790
|
+
return {
|
|
791
|
+
id,
|
|
792
|
+
sizeBytes: s.size,
|
|
793
|
+
modifiedAt: new Date(s.mtimeMs).toISOString(),
|
|
794
|
+
messageCount: messages.length
|
|
795
|
+
};
|
|
796
|
+
} catch {
|
|
797
|
+
return null;
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
/**
|
|
801
|
+
* Restore main transcript from a pre-compaction snapshot (then re-wrap + index sync).
|
|
802
|
+
* Does not delete the checkpoint file.
|
|
803
|
+
*/
|
|
804
|
+
async restoreCompactionCheckpoint(key, checkpointId) {
|
|
805
|
+
const id = normalizeCompactionCheckpointId(checkpointId);
|
|
806
|
+
if (!id) throw new Error("Invalid checkpoint id");
|
|
807
|
+
return this.runStoreMutation(async () => {
|
|
808
|
+
const safeKey = this.sanitizeKey(key);
|
|
809
|
+
const cpPath = join(join(this.sessionsDir, resolveSessionShardRelativePath(key)), `${this.checkpointBasenamePrefix(safeKey)}${id}.json`);
|
|
810
|
+
if (!existsSync(cpPath)) throw new Error(`Checkpoint not found: ${id}`);
|
|
811
|
+
const { jsonPath } = this.sessionPathsForKey(key);
|
|
812
|
+
await copyFile(cpPath, jsonPath);
|
|
813
|
+
const messages = await this.loadMessages(key);
|
|
814
|
+
await this.writeSessionTranscriptAndUpdateIndex(key, messages);
|
|
815
|
+
log.info({
|
|
816
|
+
key,
|
|
817
|
+
checkpointId: id
|
|
818
|
+
}, "Session transcript restored from compaction checkpoint");
|
|
819
|
+
});
|
|
820
|
+
}
|
|
654
821
|
/** Alias for delete */
|
|
655
822
|
async deleteSession(key) {
|
|
656
823
|
return this.delete(key);
|
|
@@ -674,15 +841,20 @@ var SessionStore = class {
|
|
|
674
841
|
return this.extractTextContent(m.content).toLowerCase().includes(keywordLower);
|
|
675
842
|
}));
|
|
676
843
|
}
|
|
844
|
+
/**
|
|
845
|
+
* JSON export includes API-shaped `messages` (LLM-only) plus `transcriptRows` (full on-disk order).
|
|
846
|
+
*/
|
|
677
847
|
async exportSession(key, format) {
|
|
678
848
|
const detail = await this.get(key);
|
|
679
849
|
if (!detail) throw new Error(`Session not found: ${key}`);
|
|
680
850
|
if (format === "json") {
|
|
851
|
+
const transcriptRows = await this.loadTranscriptRows(key);
|
|
681
852
|
const exportData = {
|
|
682
853
|
version: INDEX_VERSION,
|
|
683
854
|
exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
684
855
|
metadata: detail,
|
|
685
|
-
messages: detail.messages
|
|
856
|
+
messages: detail.messages,
|
|
857
|
+
transcriptRows
|
|
686
858
|
};
|
|
687
859
|
return JSON.stringify(exportData, null, 2);
|
|
688
860
|
} else {
|