@morlay/session-rdb 0.1.8 → 0.1.10-alpha.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.md CHANGED
@@ -18,11 +18,17 @@ provider 面),并接管 `ctx.sessionQuery`(全文检索 disabled)与 `$D
18
18
  path: /absolute/path/to/sessions.sqlite # 自定义路径请用绝对路径;省略则由 bundle patch 的默认值兜底
19
19
  journalMode: wal
20
20
  busyTimeout: 5000
21
+ preparedSessionCacheSize: 1 # 冷会话观察缓存容量(条数,最小 1)
21
22
  ```
22
23
 
23
24
  PostgreSQL 同理:`type: postgres` + `connectionString`(可加 `schema`,默认 `public`)。字段、默认值、装配默认值的
24
25
  来源与「改配置由 Loader 重挂这一行生效」见[设计总览](./.agents/designs/20260917-设计总览.md)的「配置门面」。
25
26
 
27
+ `preparedSessionCacheSize` 透传给上游 `session-query` 的冷会话观察缓存:缓存**按条目数**持有完整 `Session`
28
+ (含整份 append-only log),被活跃观察租约钉住的条目不淘汰;一次读取中途转成 live 时会重试 live 路径,但不会
29
+ 顺手清掉已缓存的 cold 条目——同一个 id 因此可能同时有两份 log 驻留。上游默认 5,**本行默认 1**(本地单用户、
30
+ rdb 后端,冷读复用的收益不值这份内存放大);上游校验下限是 1,不能设 0。
31
+
26
32
  管理面的 HTTP 通道统一挂在 `/api/morlay/v1/session` 前缀下(均为 `POST`,body `{ sessionId }` 或列表请求
27
33
  `{ query?, includeSubagents?, page?, pageSize? }`):`/rows`(完整语料,含归档)、`/export`、`/import`、
28
34
  `/delete`、`/gc`、`/usage`(前缀与上游分家的理由见
@@ -1,4 +1,3 @@
1
- import { C as usageRowOf, Ft as SESSION_IMPORT_PATH, S as toJsonlArtifact, _ as sealOwnEvents, a as normalizeToCurrentShape, b as syncMeteringRanges, c as renameLegacyPtcEvents, d as repairReadView, f as repairRequestHeaders, g as scanRows, h as rowToMeta, i as needsShapeAdoption, l as repairAssistantSettlement, m as rowToEvent, n as OWN_EVENT_TYPES, o as orphanInboxSpliceSeqs, p as repairSurfaceOps, r as findSurfaceRepairs, s as recomputeReplaceProvenance, t as EventUsageRow, u as repairOrphanInboxSplices, v as sessionConflictRow, x as titleOfEventData, y as sessionInsertRow } from "./log-C0q5svm7.mjs";
1
+ import { C as usageRowOf, It as SESSION_IMPORT_PATH, S as toJsonlArtifact, _ as sealOwnEvents, a as normalizeToCurrentShape, b as syncMeteringRanges, c as renameLegacyPtcEvents, d as repairReadView, f as repairRequestHeaders, g as scanRows, h as rowToMeta, i as needsShapeAdoption, l as repairAssistantSettlement, m as rowToEvent, n as OWN_EVENT_TYPES, o as orphanInboxSpliceSeqs, p as repairSurfaceOps, r as findSurfaceRepairs, s as recomputeReplaceProvenance, t as EventUsageRow, u as repairOrphanInboxSplices, v as sessionConflictRow, x as titleOfEventData, y as sessionInsertRow } from "./log-TYgd8Xkr.mjs";
2
2
  import { SESSION_LOG_ARTIFACT_FILENAME, parseImportZip, parseJsonlArtifact, persistImport, registerSessionImport } from "./import.mjs";
3
- //#endregion
4
3
  export { EventUsageRow, OWN_EVENT_TYPES, SESSION_IMPORT_PATH, SESSION_LOG_ARTIFACT_FILENAME, findSurfaceRepairs, needsShapeAdoption, normalizeToCurrentShape, orphanInboxSpliceSeqs, parseImportZip, parseJsonlArtifact, persistImport, recomputeReplaceProvenance, registerSessionImport, renameLegacyPtcEvents, repairAssistantSettlement, repairOrphanInboxSplices, repairReadView, repairRequestHeaders, repairSurfaceOps, rowToEvent, rowToMeta, scanRows, sealOwnEvents, sessionConflictRow, sessionInsertRow, syncMeteringRanges, titleOfEventData, toJsonlArtifact, usageRowOf };
package/dist/artifact.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { i as SESSION_IMPORT_PATH } from "./routes-DGBlBE_I.mjs";
2
- import { S as usageRowOf, _ as sessionConflictRow, a as orphanInboxSpliceSeqs, b as titleOfEventData, c as repairAssistantSettlement, d as repairRequestHeaders, f as repairSurfaceOps, g as sealOwnEvents, h as scanRows, i as normalizeToCurrentShape, l as repairOrphanInboxSplices, m as rowToMeta, n as findSurfaceRepairs, o as recomputeReplaceProvenance, p as rowToEvent, r as needsShapeAdoption, s as renameLegacyPtcEvents, t as OWN_EVENT_TYPES, u as repairReadView, v as sessionInsertRow, x as toJsonlArtifact, y as syncMeteringRanges } from "./log-DYxdjJ-l.mjs";
1
+ import { i as SESSION_IMPORT_PATH } from "./routes-hZjTcX_R.mjs";
2
+ import { S as usageRowOf, _ as sessionConflictRow, a as orphanInboxSpliceSeqs, b as titleOfEventData, c as repairAssistantSettlement, d as repairRequestHeaders, f as repairSurfaceOps, g as sealOwnEvents, h as scanRows, i as normalizeToCurrentShape, l as repairOrphanInboxSplices, m as rowToMeta, n as findSurfaceRepairs, o as recomputeReplaceProvenance, p as rowToEvent, r as needsShapeAdoption, s as renameLegacyPtcEvents, t as OWN_EVENT_TYPES, u as repairReadView, v as sessionInsertRow, x as toJsonlArtifact, y as syncMeteringRanges } from "./log-CcWy46VE.mjs";
3
3
  import { SESSION_LOG_ARTIFACT_FILENAME, parseImportZip, parseJsonlArtifact, persistImport, registerSessionImport } from "./import.mjs";
4
4
  export { OWN_EVENT_TYPES, SESSION_IMPORT_PATH, SESSION_LOG_ARTIFACT_FILENAME, findSurfaceRepairs, needsShapeAdoption, normalizeToCurrentShape, orphanInboxSpliceSeqs, parseImportZip, parseJsonlArtifact, persistImport, recomputeReplaceProvenance, registerSessionImport, renameLegacyPtcEvents, repairAssistantSettlement, repairOrphanInboxSplices, repairReadView, repairRequestHeaders, repairSurfaceOps, rowToEvent, rowToMeta, scanRows, sealOwnEvents, sessionConflictRow, sessionInsertRow, syncMeteringRanges, titleOfEventData, toJsonlArtifact, usageRowOf };
@@ -1,9 +1,9 @@
1
- import { d as repairRequestHeaders, h as scanRows, i as normalizeToCurrentShape, m as rowToMeta } from "./log-DYxdjJ-l.mjs";
1
+ import { d as repairRequestHeaders, h as scanRows, i as normalizeToCurrentShape, m as rowToMeta } from "./log-CcWy46VE.mjs";
2
2
  import { SESSION_FORMAT_VERSION, SessionLogOffset } from "@deepseek-ai/dsh-session";
3
3
  import { createSessionFormatCatalogWithChildren } from "@deepseek-ai/dsh-session-format-catalog";
4
4
  import { randomUUID } from "node:crypto";
5
5
  import { SessionBranch, SessionBranchError, balanceRewindPrefix, rewindKeepLength } from "@morlay/session-branch";
6
- //#region src/legacy.ts
6
+ // src/legacy.ts
7
7
  function physicalHeader(row) {
8
8
  const common = {
9
9
  type: "session",
@@ -66,8 +66,7 @@ function adoptLegacyRows(row, eventRows) {
66
66
  ...tornFrom !== void 0 ? { tornFrom } : {}
67
67
  };
68
68
  }
69
- //#endregion
70
- //#region src/branch.ts
69
+ // src/branch.ts
71
70
  function assertRewindBoundary(id, toBoundary, boundaryType, head) {
72
71
  if (toBoundary === -1) return;
73
72
  if (toBoundary > head) throw new SessionBranchError(`rewind boundary ${toBoundary} is beyond the stored head ${head}`, "INVALID_BOUNDARY");
@@ -113,8 +112,20 @@ function resetSurfaceManager(surfaceManager) {
113
112
  surfaceManager._lastProcessedSeq = surfaceManager.baseSeq - 1;
114
113
  surfaceManager._pendingPlan = void 0;
115
114
  }
115
+ /**
116
+ * 会话**当前内存 log** 的快照(复制一份)。用于"需要完整序列"的操作(首次落库的 seed、rewind / fork 的历史),
117
+ * 不走已弃用的同步历史读 `snapshotEvents()`——那三个读口上游已声明「新调用禁止」,而这里要的是当前内存里
118
+ * 那份完整日志,不是按需分页的历史。
119
+ * @param session - 会话。
120
+ * @returns 事件副本(调用方可以自由遍历)。
121
+ */
122
+ function liveEventsOf(session) {
123
+ return [...session.log];
124
+ }
125
+ /** @returns 截断后日志里最后一个 `turn/start` 的 turn 号(没有则 0)。 */
116
126
  function truncateLiveSession(session, newLength) {
117
127
  const s = session;
128
+ const lastTurn = s.log.slice(0, newLength).findLast((event) => event.type === "turn/start")?.data.turn ?? 0;
118
129
  s.log.length = newLength;
119
130
  s.eventsSnapshot = void 0;
120
131
  s.headerFold = void 0;
@@ -125,6 +136,7 @@ function truncateLiveSession(session, newLength) {
125
136
  s.derivedNodes = 0;
126
137
  s.derivedGeneration = 0;
127
138
  resetSurfaceManager(s.surfaceManager);
139
+ return lastTurn;
128
140
  }
129
141
  function replaceLiveSessionLog(session, events) {
130
142
  const s = session;
@@ -262,13 +274,12 @@ var SessionBranchRdbProvider = class {
262
274
  });
263
275
  internals.writeGuard.confirmHead(id, denseBoundary);
264
276
  if (live !== void 0) {
265
- truncateLiveSession(live, keepLength);
277
+ const lastTurn = truncateLiveSession(live, keepLength);
266
278
  this.live.resetProjections?.(live);
267
279
  this.live.resetTokenMeter?.(live);
268
280
  const agent = this.live.getAgent(id);
269
281
  if (agent !== void 0) {
270
282
  agent.requestHeaderLogged = false;
271
- const lastTurn = live.snapshotEvents().findLast((e) => e.type === "turn/start")?.data.turn ?? 0;
272
283
  const phase = agent.phase;
273
284
  if (phase !== void 0) phase.lastTurn = lastTurn;
274
285
  }
@@ -387,5 +398,4 @@ var SessionBranchRdb = class extends SessionBranch {
387
398
  return this.provider.rewind(id, toBoundary, signal);
388
399
  }
389
400
  };
390
- //#endregion
391
- export { truncateLiveSession as a, isLegacyVersion as c, replaceLiveSessionLog as i, restoreCatalog as l, SessionBranchRdbProvider as n, adoptLegacyRows as o, locateTurnEnd as r, convertLegacyRows as s, SessionBranchRdb as t };
401
+ export { replaceLiveSessionLog as a, convertLegacyRows as c, locateTurnEnd as i, isLegacyVersion as l, SessionBranchRdbProvider as n, truncateLiveSession as o, liveEventsOf as r, adoptLegacyRows as s, SessionBranchRdb as t, restoreCatalog as u };
@@ -1,9 +1,9 @@
1
- import { a as SESSION_ROWS_PATH, t as SESSION_DELETE_PATH } from "./routes-DGBlBE_I.mjs";
1
+ import { a as SESSION_ROWS_PATH, t as SESSION_DELETE_PATH } from "./routes-hZjTcX_R.mjs";
2
2
  import { COUNTED_EVENT_TYPES, addActivityCount, addActivityTotals, addTokenTotals, addTotals, emptyTotals, localDayKey, registerSessionUsage } from "./usage.mjs";
3
- import { S as usageRowOf, _ as sessionConflictRow, b as titleOfEventData, g as sealOwnEvents, h as scanRows, m as rowToMeta, r as needsShapeAdoption, u as repairReadView, v as sessionInsertRow, x as toJsonlArtifact } from "./log-DYxdjJ-l.mjs";
4
- import { c as isLegacyVersion, o as adoptLegacyRows, s as convertLegacyRows, t as SessionBranchRdb } from "./branch-DbFW3rJX.mjs";
3
+ import { S as usageRowOf, _ as sessionConflictRow, b as titleOfEventData, g as sealOwnEvents, h as scanRows, m as rowToMeta, r as needsShapeAdoption, u as repairReadView, v as sessionInsertRow, x as toJsonlArtifact } from "./log-CcWy46VE.mjs";
4
+ import { c as convertLegacyRows, l as isLegacyVersion, r as liveEventsOf, s as adoptLegacyRows, t as SessionBranchRdb } from "./branch-CoPCRKer.mjs";
5
5
  import { registerSessionImport } from "./import.mjs";
6
- import { E as WriteGuard, T as toPostgresSchema, a as EVENT_ENCODING, c as eventDimensions, i as DEFAULT_BUSY_TIMEOUT_MS, r as createStorageRepository, t as SqliteBackend, w as postgresTableDefs } from "./sqlite-DjOnMRh2.mjs";
6
+ import { E as WriteGuard, T as toPostgresSchema, a as EVENT_ENCODING, c as eventDimensions, i as DEFAULT_BUSY_TIMEOUT_MS, r as createStorageRepository, t as SqliteBackend, w as postgresTableDefs } from "./sqlite-BNRd1aqR.mjs";
7
7
  import { registerSessionExport } from "./export.mjs";
8
8
  import { registerSessionGc } from "./gc.mjs";
9
9
  import { SESSION_FORMAT_VERSION, SessionLogOffset } from "@deepseek-ai/dsh-session";
@@ -21,7 +21,7 @@ import { migrate } from "drizzle-orm/node-postgres/migrator";
21
21
  import { SessionQueryEngine, SessionQueryError } from "@deepseek-ai/dsh-session-query";
22
22
  import { StorageError, storageBackendServiceKey } from "@deepseek-ai/dsh-storage";
23
23
  import { Service } from "@deepseek-ai/cordis";
24
- //#region src/postgres.ts
24
+ // src/postgres.ts
25
25
  const postgresMigrationsDir = fileURLToPath(new URL("../drizzle/postgres/", import.meta.url));
26
26
  const pgWriteQueues = /* @__PURE__ */ new Map();
27
27
  const REFRESH_FLAG_CHUNK = 400;
@@ -640,8 +640,7 @@ var PostgresBackend = class PostgresBackend {
640
640
  }).from(this.tables["t_session_events"]).innerJoin(this.tables["t_events"], eq(this.tables["t_session_events"].fEventId, this.tables["t_events"].fEventId));
641
641
  }
642
642
  };
643
- //#endregion
644
- //#region src/rows.ts
643
+ // src/rows.ts
645
644
  const MAX_PAGE_SIZE = 200;
646
645
  const DEFAULT_PAGE_SIZE = 20;
647
646
  function registerSessionRows(ctx, backend) {
@@ -699,21 +698,27 @@ function registerSessionRows(ctx, backend) {
699
698
  }), "session-rdb: session rows route");
700
699
  });
701
700
  }
702
- //#endregion
703
- //#region src/session-query.ts
701
+ // src/session-query.ts
704
702
  var SessionQueryRdb = class extends SessionQueryEngine {
703
+ /**
704
+ * 本次装配用的冷会话观察缓存容量(由 `session-rdb` 行的 `preparedSessionCacheSize` 透传;undefined 表示沿用
705
+ * 上游默认)。它是**按条目数**持有完整 Session 的缓存:条目多一条就多一份完整 append-only log 驻留。
706
+ */
707
+ preparedSessionCacheSize;
705
708
  constructor(ctx, config = {}) {
706
709
  super(ctx, config);
710
+ this.preparedSessionCacheSize = config.preparedSessionCacheSize;
707
711
  }
708
712
  async listSessions(signal) {
709
713
  const records = await super.listSessions(signal);
710
- const archived = this.archivedSessionIds();
714
+ const archived = await this.archivedSessionIds();
711
715
  if (archived.size === 0) return records;
712
716
  return records.filter((record) => !archived.has(String(record.header.id)));
713
717
  }
714
- archivedSessionIds() {
718
+ async archivedSessionIds() {
715
719
  const registry = this.ctx.get("workspaceRegistry");
716
- return new Set(registry?.archivedSessionIds ?? []);
720
+ if (registry !== void 0) return new Set(registry.archivedSessionIds ?? []);
721
+ return await this.ctx.get("sessionPersistence")?.archivedSessionIds?.() ?? /* @__PURE__ */ new Set();
717
722
  }
718
723
  async searchSessions() {
719
724
  throw searchDisabled();
@@ -725,8 +730,12 @@ var SessionQueryRdb = class extends SessionQueryEngine {
725
730
  function searchDisabled() {
726
731
  return new SessionQueryError("session search is disabled: this deployment serves session queries from the rdb backend without a full-text index", "SESSION_QUERY_SEARCH_DISABLED");
727
732
  }
728
- //#endregion
729
- //#region src/storage-takeover/projection-cache.ts
733
+ // src/storage-takeover/projection-cache.ts
734
+ function selects(keys, key) {
735
+ if (keys === void 0) return true;
736
+ if ("has" in keys) return keys.has(key);
737
+ return keys.includes(key);
738
+ }
730
739
  const SESSION_PROJECTION_CACHE_SERVICE = "sessionProjectionCache";
731
740
  const PREDECESSOR_TITLE_KEY = "title";
732
741
  var SessionProjectionCacheRdb = class extends Service {
@@ -770,7 +779,7 @@ var SessionProjectionCacheRdb = class extends Service {
770
779
  return record === void 0 || !identityMatches(record.identity, lifecycleIdentityOf(meta)) ? void 0 : record;
771
780
  }
772
781
  withDirectTitle(id, keys, snapshot) {
773
- if (keys !== void 0 && !keys.includes(PREDECESSOR_TITLE_KEY)) return snapshot;
782
+ if (!selects(keys, PREDECESSOR_TITLE_KEY)) return snapshot;
774
783
  if (snapshot?.values[PREDECESSOR_TITLE_KEY] !== void 0) return snapshot;
775
784
  const direct = this.repository.readSessionTitleDirect?.(id);
776
785
  if (direct === void 0) return snapshot;
@@ -930,8 +939,7 @@ function predecessorIdentityMatches(stored, expected) {
930
939
  function lifecycleIdentityMatches(stored, expected) {
931
940
  return stored.createdAt === expected.createdAt && stored.cwd === expected.cwd && (stored.isSeeded ?? false) === expected.isSeeded;
932
941
  }
933
- //#endregion
934
- //#region src/storage-takeover/storage-backend.ts
942
+ // src/storage-takeover/storage-backend.ts
935
943
  const WORKSPACE_UNIT = "workspace";
936
944
  const WORKSPACE_TABLE = "workspaces";
937
945
  const SCHEDULE_UNIT = "schedule";
@@ -1038,6 +1046,11 @@ var WorkspaceKvUnit = class extends TrackedKvUnit {
1038
1046
  this.assertTable(table);
1039
1047
  await this.track(this.repository.deleteWorkspace(key));
1040
1048
  }
1049
+ async deleteRecords(table, keys) {
1050
+ this.assertOpen();
1051
+ this.assertTable(table);
1052
+ await this.track(this.repository.deleteWorkspaces([...new Set(keys)]));
1053
+ }
1041
1054
  async setGlobal(value) {
1042
1055
  this.assertOpen();
1043
1056
  await this.track(this.repository.writeWorkspaceState(workspaceStateOf(value)));
@@ -1064,6 +1077,11 @@ var ScheduleKvUnit = class extends TrackedKvUnit {
1064
1077
  this.assertTable(table);
1065
1078
  await this.track(this.repository.deleteScheduleTask(key));
1066
1079
  }
1080
+ async deleteRecords(table, keys) {
1081
+ this.assertOpen();
1082
+ this.assertTable(table);
1083
+ await this.track(this.repository.deleteScheduleTasks([...new Set(keys)]));
1084
+ }
1067
1085
  setGlobal(_value) {
1068
1086
  throw new Error(`unit '${this.descriptor.name}' declares no global slot`);
1069
1087
  }
@@ -1095,8 +1113,7 @@ function scheduleTaskOf(value) {
1095
1113
  if (typeof task !== "object" || task === null || typeof task.sessionId !== "string" || typeof task.record !== "object" || task.record === null) throw new TypeError("schedule task does not match the stored shape");
1096
1114
  return task;
1097
1115
  }
1098
- //#endregion
1099
- //#region src/storage-takeover/index.ts
1116
+ // src/storage-takeover/index.ts
1100
1117
  function installStorageTakeover(ctx, options) {
1101
1118
  ctx.inject(["storage"], (storageCtx) => {
1102
1119
  const backend = new RdbStorageBackend(options.repository);
@@ -1115,8 +1132,7 @@ function installStorageTakeover(ctx, options) {
1115
1132
  ready: options.ready
1116
1133
  });
1117
1134
  }
1118
- //#endregion
1119
- //#region src/index.ts
1135
+ // src/index.ts
1120
1136
  function maxEventTime(events) {
1121
1137
  let max;
1122
1138
  for (const event of events) {
@@ -1385,6 +1401,10 @@ var RdbSessionHandle = class RdbSessionHandle {
1385
1401
  static LIVE_WRITE_BATCH_MAX_DELAY_MS = 200;
1386
1402
  };
1387
1403
  const localized = (text) => text;
1404
+ const preparedSessionCacheSchema = z.number().step(1).min(1).default(1).description(localized({
1405
+ zh: "冷会话观察缓存容量(条数,最小 1):每个条目持一份完整会话日志;本部署默认 1,把同 id 两份日志的驻留压到最小。",
1406
+ en: "Cold prepared-Session observation cache size (entries, minimum 1): each entry holds one complete session log. This deployment defaults to 1, minimizing duplicate per-id residency."
1407
+ }));
1388
1408
  const projectionCacheSchema = z.object({
1389
1409
  writeEveryEvents: z.natural().min(1).default(DEFAULT_PROJECTION_WRITE_EVERY_EVENTS).description(localized({
1390
1410
  zh: "每累计这么多事件写回一次投影 checkpoint。",
@@ -1419,7 +1439,8 @@ var SessionPersistenceRdb = class extends SessionPersistence {
1419
1439
  zh: "写锁冲突时的忙等超时(毫秒)。",
1420
1440
  en: "Busy timeout in ms while a write lock is contended."
1421
1441
  })),
1422
- projectionCache: projectionCacheSchema
1442
+ projectionCache: projectionCacheSchema,
1443
+ preparedSessionCacheSize: preparedSessionCacheSchema
1423
1444
  }), z.object({
1424
1445
  type: z.const("postgres"),
1425
1446
  connectionString: z.string().required().description(localized({
@@ -1430,7 +1451,8 @@ var SessionPersistenceRdb = class extends SessionPersistence {
1430
1451
  zh: "库内的 schema 名;默认 public。",
1431
1452
  en: "Schema name inside the database; `public` by default."
1432
1453
  })),
1433
- projectionCache: projectionCacheSchema
1454
+ projectionCache: projectionCacheSchema,
1455
+ preparedSessionCacheSize: preparedSessionCacheSchema
1434
1456
  })]).description(localized({
1435
1457
  zh: "会话持久化后端:改这里的连接参数会在这一行重挂时生效(Loader 会重装被改的行)。",
1436
1458
  en: "Session persistence backend: edits apply when this row is remounted (the Loader re-installs changed rows)."
@@ -1452,7 +1474,8 @@ var SessionPersistenceRdb = class extends SessionPersistence {
1452
1474
  this.ready = this.init();
1453
1475
  this.installLiveRouting(ctx);
1454
1476
  new SessionBranchRdb(this.ctx);
1455
- this.ctx.plugin(SessionQueryRdb, {});
1477
+ const queryConfig = this.config.preparedSessionCacheSize === void 0 ? {} : { preparedSessionCacheSize: this.config.preparedSessionCacheSize };
1478
+ this.ctx.plugin(SessionQueryRdb, queryConfig);
1456
1479
  registerSessionImport(this.ctx, this);
1457
1480
  registerSessionDeletion(this.ctx, this);
1458
1481
  registerSessionExport(this.ctx, this);
@@ -1623,6 +1646,16 @@ var SessionPersistenceRdb = class extends SessionPersistence {
1623
1646
  await this.ready;
1624
1647
  await this.backend.vacuum();
1625
1648
  }
1649
+ /**
1650
+ * 已归档的会话 id(`t_sessions.f_archived_at` 非空)。`session/list` 默认过滤掉它们。
1651
+ * 读的是持久化里的归档标记,不依赖 `workspaceRegistry` 服务的就绪时机。
1652
+ * @returns 归档过的 Session id 集。
1653
+ */
1654
+ async archivedSessionIds() {
1655
+ await this.ready;
1656
+ const state = await this.backend.storage.readWorkspaceState();
1657
+ return new Set((state?.archivedSessionIds ?? []).map(String));
1658
+ }
1626
1659
  async unarchiveBeforeDeletion(id) {
1627
1660
  const registry = this.ctx.get("workspaceRegistry");
1628
1661
  if (registry === void 0) return;
@@ -2011,14 +2044,14 @@ var SessionPersistenceRdb = class extends SessionPersistence {
2011
2044
  let handle;
2012
2045
  if (stored === void 0) {
2013
2046
  handle = await this.create(session.header, { inheritedEventCount: session.inheritedEventCount });
2014
- const seed = session.snapshotEvents();
2047
+ const seed = liveEventsOf(session);
2015
2048
  if (seed.length > 0) await handle.append(seed);
2016
2049
  } else {
2017
2050
  if (stored.meta.cwd !== session.header.cwd) throw new Error(`session "${id}" is already persisted at a different cwd (persisted: ${String(stored.meta.cwd)}, live: ${String(session.header.cwd)}) (id collision)`);
2018
2051
  if (stored.inheritedEventCount !== session.inheritedEventCount) throw new Error(`session "${id}" is already persisted with a different inherited event count (id collision)`);
2019
2052
  assertVersion(stored.meta);
2020
2053
  repairReadView(stored.events);
2021
- const seed = session.snapshotEvents();
2054
+ const seed = liveEventsOf(session);
2022
2055
  if (!seedCoversPrefix(seed, stored.events)) throw new Error(`session "${id}" already has a persisted log on disk that does not match this live session (id collision)`);
2023
2056
  handle = await this.open(id, "write");
2024
2057
  const suffix = seed.slice(stored.events.length);
@@ -2152,8 +2185,7 @@ async function appendEventTail(tx, meta, events, anchor, reuse) {
2152
2185
  usageRows
2153
2186
  };
2154
2187
  }
2155
- //#endregion
2156
- //#region src/deletion.ts
2188
+ // src/deletion.ts
2157
2189
  const STATUS_OF_DELETION_ERROR = {
2158
2190
  SESSION_NOT_FOUND: 404,
2159
2191
  SESSION_NOT_ARCHIVED: 409,
@@ -2214,5 +2246,4 @@ function registerSessionDeletion(ctx, persistence) {
2214
2246
  }));
2215
2247
  });
2216
2248
  }
2217
- //#endregion
2218
2249
  export { SessionDeletionError as n, SessionPersistenceRdb as r, registerSessionDeletion as t };
@@ -1,6 +1,4 @@
1
- import { Mt as SESSION_DELETE_PATH, et as SessionPersistenceRdb } from "./log-C0q5svm7.mjs";
1
+ import { Nt as SESSION_DELETE_PATH, et as SessionPersistenceRdb } from "./log-TYgd8Xkr.mjs";
2
2
  import { Context } from "@deepseek-ai/cordis";
3
- //#region src/deletion.d.ts
4
3
  export declare function registerSessionDeletion(ctx: Context, persistence: SessionPersistenceRdb): void;
5
- //#endregion
6
4
  export { SESSION_DELETE_PATH };
package/dist/deletion.mjs CHANGED
@@ -1,3 +1,3 @@
1
- import { t as SESSION_DELETE_PATH } from "./routes-DGBlBE_I.mjs";
2
- import { t as registerSessionDeletion } from "./deletion-ODZFcbZm.mjs";
1
+ import { t as SESSION_DELETE_PATH } from "./routes-hZjTcX_R.mjs";
2
+ import { t as registerSessionDeletion } from "./deletion-CWl89TIR.mjs";
3
3
  export { SESSION_DELETE_PATH, registerSessionDeletion };
package/dist/export.d.mts CHANGED
@@ -1,6 +1,4 @@
1
- import { Nt as SESSION_EXPORT_PATH, et as SessionPersistenceRdb } from "./log-C0q5svm7.mjs";
1
+ import { Pt as SESSION_EXPORT_PATH, et as SessionPersistenceRdb } from "./log-TYgd8Xkr.mjs";
2
2
  import { Context } from "@deepseek-ai/cordis";
3
- //#region src/export.d.ts
4
3
  export declare function registerSessionExport(ctx: Context, persistence: SessionPersistenceRdb): void;
5
- //#endregion
6
4
  export { SESSION_EXPORT_PATH };
package/dist/export.mjs CHANGED
@@ -1,6 +1,6 @@
1
- import { n as SESSION_EXPORT_PATH } from "./routes-DGBlBE_I.mjs";
1
+ import { n as SESSION_EXPORT_PATH } from "./routes-hZjTcX_R.mjs";
2
2
  import { strToU8, zip } from "fflate";
3
- //#region src/export.ts
3
+ // src/export.ts
4
4
  function safeFilename(sessionId) {
5
5
  return sessionId.replace(/[^A-Za-z0-9._-]/gu, "_");
6
6
  }
@@ -69,5 +69,4 @@ function registerSessionExport(ctx, persistence) {
69
69
  }), `session-rdb: ${SESSION_EXPORT_PATH} route`);
70
70
  });
71
71
  }
72
- //#endregion
73
72
  export { SESSION_EXPORT_PATH, registerSessionExport };
package/dist/gc.d.mts CHANGED
@@ -1,6 +1,4 @@
1
- import { Pt as SESSION_GC_PATH, et as SessionPersistenceRdb } from "./log-C0q5svm7.mjs";
1
+ import { Ft as SESSION_GC_PATH, et as SessionPersistenceRdb } from "./log-TYgd8Xkr.mjs";
2
2
  import { Context } from "@deepseek-ai/cordis";
3
- //#region src/gc.d.ts
4
3
  export declare function registerSessionGc(ctx: Context, persistence: SessionPersistenceRdb): void;
5
- //#endregion
6
4
  export { SESSION_GC_PATH };
package/dist/gc.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { r as SESSION_GC_PATH } from "./routes-DGBlBE_I.mjs";
2
- //#region src/gc.ts
1
+ import { r as SESSION_GC_PATH } from "./routes-hZjTcX_R.mjs";
2
+ // src/gc.ts
3
3
  async function stopRunningAgents(ctx) {
4
4
  const running = ctx.get("agents")?.list() ?? [];
5
5
  for (const agent of running) agent.cancel?.({ kind: "user" }, { keepInbox: true });
@@ -45,5 +45,4 @@ function registerSessionGc(ctx, persistence) {
45
45
  }), `session-rdb: ${SESSION_GC_PATH} route`);
46
46
  });
47
47
  }
48
- //#endregion
49
48
  export { SESSION_GC_PATH, registerSessionGc };
package/dist/import.d.mts CHANGED
@@ -1,8 +1,7 @@
1
- import { Ft as SESSION_IMPORT_PATH, et as SessionPersistenceRdb } from "./log-C0q5svm7.mjs";
1
+ import { It as SESSION_IMPORT_PATH, et as SessionPersistenceRdb } from "./log-TYgd8Xkr.mjs";
2
2
  import { Session, SessionEvent, SessionId } from "@deepseek-ai/dsh-session";
3
3
  import { SessionStorageMetadata } from "@deepseek-ai/dsh-session-persistence";
4
4
  import { Context } from "@deepseek-ai/cordis";
5
- //#region src/import.d.ts
6
5
  export declare const SESSION_LOG_ARTIFACT_FILENAME = "session.jsonl";
7
6
  export declare function parseJsonlArtifact(content: string): SessionStorageMetadata & {
8
7
  events: SessionEvent[];
@@ -19,5 +18,4 @@ export declare function persistImport(persistence: SessionPersistenceRdb, branch
19
18
  get(id: SessionId): Session | undefined;
20
19
  }, stopLoop?: (id: SessionId) => Promise<void>): Promise<SessionId>;
21
20
  export declare function registerSessionImport(ctx: Context, persistence: SessionPersistenceRdb): void;
22
- //#endregion
23
21
  export { SESSION_IMPORT_PATH };
package/dist/import.mjs CHANGED
@@ -1,11 +1,11 @@
1
- import { i as SESSION_IMPORT_PATH } from "./routes-DGBlBE_I.mjs";
2
- import { i as replaceLiveSessionLog, l as restoreCatalog } from "./branch-DbFW3rJX.mjs";
1
+ import { i as SESSION_IMPORT_PATH } from "./routes-hZjTcX_R.mjs";
2
+ import { a as replaceLiveSessionLog, u as restoreCatalog } from "./branch-CoPCRKer.mjs";
3
3
  import { SESSION_FORMAT_VERSION, SessionLogOffset } from "@deepseek-ai/dsh-session";
4
4
  import { randomUUID } from "node:crypto";
5
5
  import { parseSessionFormatLogFilename } from "@deepseek-ai/dsh-session-format";
6
6
  import { balanceRewindPrefix } from "@morlay/session-branch";
7
7
  import { unzip } from "fflate";
8
- //#region src/import.ts
8
+ // src/import.ts
9
9
  const SESSION_LOG_ARTIFACT_FILENAME = "session.jsonl";
10
10
  const MAX_IMPORT_ZIP_BYTES = 67108864;
11
11
  function parseJsonlArtifact(content) {
@@ -205,5 +205,4 @@ function registerSessionImport(ctx, persistence) {
205
205
  }), `session-rdb: ${SESSION_IMPORT_PATH} route`);
206
206
  });
207
207
  }
208
- //#endregion
209
208
  export { SESSION_IMPORT_PATH, SESSION_LOG_ARTIFACT_FILENAME, parseImportZip, parseJsonlArtifact, persistImport, registerSessionImport };
package/dist/index.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { $ as SessionDeletionErrorCode, It as SESSION_ROWS_PATH, Q as SessionDeletionError, X as ProjectionCacheOptions, Y as Config, Z as ResolvedConfig, et as SessionPersistenceRdb, it as locateTurnEnd, nt as SessionBranchRdb, ot as SessionRowsItem, pt as SCHEMA_VERSION, rt as SessionBranchRdbProvider, st as SessionRowsValue, tt as SessionPersistenceRdbInternals } from "./log-C0q5svm7.mjs";
2
- export { Config, ProjectionCacheOptions, ResolvedConfig, SCHEMA_VERSION, SESSION_ROWS_PATH, SessionBranchRdb, SessionBranchRdbProvider, SessionDeletionError, SessionDeletionErrorCode, SessionPersistenceRdb, SessionPersistenceRdb as default, SessionPersistenceRdbInternals, type SessionRowsItem, type SessionRowsValue, locateTurnEnd };
1
+ import { $ as SessionDeletionErrorCode, Lt as SESSION_ROWS_PATH, Q as SessionDeletionError, X as ProjectionCacheOptions, Y as Config, Z as ResolvedConfig, at as locateTurnEnd, ct as SessionRowsValue, et as SessionPersistenceRdb, it as liveEventsOf, mt as SCHEMA_VERSION, nt as SessionBranchRdb, rt as SessionBranchRdbProvider, st as SessionRowsItem, tt as SessionPersistenceRdbInternals } from "./log-TYgd8Xkr.mjs";
2
+ export { Config, ProjectionCacheOptions, ResolvedConfig, SCHEMA_VERSION, SESSION_ROWS_PATH, SessionBranchRdb, SessionBranchRdbProvider, SessionDeletionError, SessionDeletionErrorCode, SessionPersistenceRdb, SessionPersistenceRdb as default, SessionPersistenceRdbInternals, type SessionRowsItem, type SessionRowsValue, liveEventsOf, locateTurnEnd };
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { a as SESSION_ROWS_PATH } from "./routes-DGBlBE_I.mjs";
2
- import { n as SessionBranchRdbProvider, r as locateTurnEnd, t as SessionBranchRdb } from "./branch-DbFW3rJX.mjs";
3
- import { o as SCHEMA_VERSION } from "./sqlite-DjOnMRh2.mjs";
4
- import { n as SessionDeletionError, r as SessionPersistenceRdb } from "./deletion-ODZFcbZm.mjs";
5
- export { SCHEMA_VERSION, SESSION_ROWS_PATH, SessionBranchRdb, SessionBranchRdbProvider, SessionDeletionError, SessionPersistenceRdb, SessionPersistenceRdb as default, locateTurnEnd };
1
+ import { a as SESSION_ROWS_PATH } from "./routes-hZjTcX_R.mjs";
2
+ import { i as locateTurnEnd, n as SessionBranchRdbProvider, r as liveEventsOf, t as SessionBranchRdb } from "./branch-CoPCRKer.mjs";
3
+ import { o as SCHEMA_VERSION } from "./sqlite-BNRd1aqR.mjs";
4
+ import { n as SessionDeletionError, r as SessionPersistenceRdb } from "./deletion-CWl89TIR.mjs";
5
+ export { SCHEMA_VERSION, SESSION_ROWS_PATH, SessionBranchRdb, SessionBranchRdbProvider, SessionDeletionError, SessionPersistenceRdb, SessionPersistenceRdb as default, liveEventsOf, locateTurnEnd };
@@ -1,7 +1,7 @@
1
1
  import { localDayKey } from "./usage.mjs";
2
2
  import { SessionSeq } from "@deepseek-ai/dsh-session";
3
3
  import { sessionFormatCatalog } from "@deepseek-ai/dsh-session-format-catalog";
4
- //#region src/log.ts
4
+ // src/log.ts
5
5
  function normalizeSurfaceOp(value) {
6
6
  if (typeof value !== "object" || value === null || Array.isArray(value)) return value;
7
7
  const record = value;
@@ -63,12 +63,13 @@ function sessionConflictRow(storage) {
63
63
  function rowToEvent(row) {
64
64
  const surfaceOp = row.fSurfaceOp !== null ? normalizeSurfaceOp(JSON.parse(row.fSurfaceOp)) : void 0;
65
65
  const record = JSON.parse(row.fData);
66
- if (typeof record === "object" && record !== null && !Array.isArray(record) && typeof record["type"] === "string" && typeof record["seq"] === "number" && typeof record["time"] === "number" && "data" in record) return {
67
- ...record,
68
- seq: row.fSequence,
69
- time: row.fCreatedAt,
70
- ...surfaceOp === void 0 ? {} : { surfaceOp }
71
- };
66
+ if (typeof record === "object" && record !== null && !Array.isArray(record) && typeof record["type"] === "string" && typeof record["seq"] === "number" && typeof record["time"] === "number" && "data" in record) {
67
+ const event = record;
68
+ event.seq = row.fSequence;
69
+ event.time = row.fCreatedAt;
70
+ if (surfaceOp !== void 0) event.surfaceOp = surfaceOp;
71
+ return event;
72
+ }
72
73
  return {
73
74
  type: row.fType,
74
75
  seq: row.fSequence,
@@ -474,33 +475,29 @@ function repairOrphanInboxSplices(events) {
474
475
  }
475
476
  }
476
477
  function scanRows(rows, base = 0) {
477
- const parsed = rows.map((row) => {
478
- try {
479
- return {
480
- ok: true,
481
- event: rowToEvent(row)
482
- };
483
- } catch {
484
- return { ok: false };
485
- }
486
- });
487
478
  let lastTurnEnd = -1;
488
- for (let i = parsed.length - 1; i >= 0; i--) if (parsed[i]?.ok && rows[i]?.fType === "turn/end") {
489
- lastTurnEnd = i;
490
- break;
479
+ for (let i = rows.length - 1; i >= 0; i--) {
480
+ if (rows[i]?.fType !== "turn/end") continue;
481
+ try {
482
+ rowToEvent(rows[i]);
483
+ lastTurnEnd = i;
484
+ break;
485
+ } catch {}
491
486
  }
492
487
  const preserved = [];
493
488
  for (let i = 0; i < rows.length; i++) {
494
- const p = parsed[i];
495
- if (!p?.ok || p.event === void 0) {
489
+ let event;
490
+ try {
491
+ event = rowToEvent(rows[i]);
492
+ } catch {
496
493
  if (i <= lastTurnEnd) throw new Error(`corrupt session log: unparsable committed event at seq ${rows[i]?.fSequence}`);
497
494
  break;
498
495
  }
499
- if (p.event.seq !== base + i) {
500
- if (i <= lastTurnEnd) throw new Error(`corrupt session log: seq gap in committed region (expected ${base + i}, got ${p.event.seq})`);
496
+ if (event.seq !== base + i) {
497
+ if (i <= lastTurnEnd) throw new Error(`corrupt session log: seq gap in committed region (expected ${base + i}, got ${event.seq})`);
501
498
  break;
502
499
  }
503
- preserved.push(p.event);
500
+ preserved.push(event);
504
501
  }
505
502
  return preserved.length < rows.length ? {
506
503
  preserved,
@@ -558,5 +555,4 @@ function usageRowOf(event, subagent) {
558
555
  fTotalTokens: numericField(usage["totalTokens"])
559
556
  };
560
557
  }
561
- //#endregion
562
558
  export { usageRowOf as S, sessionConflictRow as _, orphanInboxSpliceSeqs as a, titleOfEventData as b, repairAssistantSettlement as c, repairRequestHeaders as d, repairSurfaceOps as f, sealOwnEvents as g, scanRows as h, normalizeToCurrentShape as i, repairOrphanInboxSplices as l, rowToMeta as m, findSurfaceRepairs as n, recomputeReplaceProvenance as o, rowToEvent as p, needsShapeAdoption as r, renameLegacyPtcEvents as s, OWN_EVENT_TYPES as t, repairReadView as u, sessionInsertRow as v, toJsonlArtifact as x, syncMeteringRanges as y };