@morlay/session-rdb 0.0.21-alpha.4 → 0.0.21-alpha.6
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 +53 -27
- package/dist/artifact.d.mts +27 -2
- package/dist/artifact.mjs +2 -2
- package/dist/{backend-a0xDrUFG.d.mts → backend-E0bmTXpz.d.mts} +233 -27
- package/dist/{branch-Dlmbkfjj.mjs → branch-BjUR6wfe.mjs} +31 -15
- package/dist/deletion.d.mts +1 -1
- package/dist/deletion.mjs +1 -1
- package/dist/import.d.mts +1 -1
- package/dist/import.mjs +2 -3
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +4 -4
- package/dist/{log-C-rbdFwK.mjs → log-qCNkDjh5.mjs} +93 -22
- package/dist/{sqlite-C3LHLzYW.mjs → sqlite-zcXVyMxr.mjs} +85 -10
- package/dist/{src-Bn0038Sp.mjs → src-B9X8BKyh.mjs} +210 -38
- package/dist/storage.d.mts +2 -1
- package/dist/storage.mjs +1 -1
- package/dist/testing.d.mts +2 -11
- package/dist/testing.mjs +2 -19
- package/drizzle/postgres/20260921075900_v3_agent_preset/migration.sql +1 -0
- package/drizzle/postgres/20260922090001_v3_pinned_seq/migration.sql +2 -0
- package/drizzle/postgres/20260922093001_v3_last_event_at/migration.sql +10 -0
- package/drizzle/sqlite/20260921075855_v3_agent_preset/migration.sql +7 -0
- package/drizzle/sqlite/20260921075855_v3_agent_preset/snapshot.json +1041 -0
- package/drizzle/sqlite/20260922090000_v3_pinned_seq/migration.sql +4 -0
- package/drizzle/sqlite/20260922093000_v3_last_event_at/migration.sql +13 -0
- package/package.json +21 -21
- package/src/backend.ts +46 -1
- package/src/branch.ts +28 -11
- package/src/drizzle/postgres-v3.ts +1 -0
- package/src/drizzle/sqlite-v3.ts +1 -0
- package/src/entities/v3/event-usage.ts +5 -5
- package/src/entities/v3/sessions.ts +5 -0
- package/src/import.ts +3 -3
- package/src/index.ts +56 -22
- package/src/legacy.ts +14 -3
- package/src/log.ts +107 -22
- package/src/postgres.ts +65 -5
- package/src/rows.ts +125 -0
- package/src/schema.ts +3 -2
- package/src/session-query.ts +27 -1
- package/src/sqlite.ts +71 -3
- package/src/storage-takeover/projection-cache.ts +40 -15
- package/src/storage-takeover/repository.ts +34 -0
- package/src/storage-takeover/storage-backend.ts +3 -1
- package/src/testing/coordinator-contract.ts +2 -1
- package/src/testing.ts +0 -1
- package/src/write-guard.ts +5 -0
- package/src/testing/helpers.ts +0 -20
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { A as WriteGuard, O as postgresTableDefs, a as addActivityCount, c as emptyTotals, d as DEFAULT_BUSY_TIMEOUT_MS, f as EVENT_ENCODING, h as eventDimensions, i as COUNTED_EVENT_TYPES, k as toPostgresSchema, l as localDayKey, o as addTokenTotals, r as createStorageRepository, s as addTotals, t as SqliteBackend, u as registerSessionUsage } from "./sqlite-
|
|
2
|
-
import { _ as
|
|
3
|
-
import { c as isLegacyVersion, o as adoptLegacyRows, s as convertLegacyRows, t as SessionBranchRdb } from "./branch-
|
|
1
|
+
import { A as WriteGuard, O as postgresTableDefs, a as addActivityCount, c as emptyTotals, d as DEFAULT_BUSY_TIMEOUT_MS, f as EVENT_ENCODING, h as eventDimensions, i as COUNTED_EVENT_TYPES, k as toPostgresSchema, l as localDayKey, o as addTokenTotals, r as createStorageRepository, s as addTotals, t as SqliteBackend, u as registerSessionUsage } from "./sqlite-zcXVyMxr.mjs";
|
|
2
|
+
import { _ as sessionInsertRow, b as toJsonlArtifact, g as sessionConflictRow, h as scanRows, m as rowToMeta, r as hasLegacyShape, u as repairReadView, x as usageRowOf, y as titleOfEventData } from "./log-qCNkDjh5.mjs";
|
|
3
|
+
import { c as isLegacyVersion, o as adoptLegacyRows, s as convertLegacyRows, t as SessionBranchRdb } from "./branch-BjUR6wfe.mjs";
|
|
4
4
|
import { registerSessionImport } from "./import.mjs";
|
|
5
5
|
import z from "@deepseek-ai/schemastery";
|
|
6
6
|
import { randomUUID } from "node:crypto";
|
|
@@ -173,7 +173,7 @@ var PostgresBackend = class PostgresBackend {
|
|
|
173
173
|
insertEvents: (events) => this.insertEvents(tx, events),
|
|
174
174
|
insertBridges: (rows) => this.insertBridges(tx, rows),
|
|
175
175
|
updateHead: (id, headEventId, headSequence) => this.updateHead(tx, id, headEventId, headSequence),
|
|
176
|
-
bumpRevision: (id) => this.bumpRevision(tx, id),
|
|
176
|
+
bumpRevision: (id, lastEventAt) => this.bumpRevision(tx, id, lastEventAt),
|
|
177
177
|
refreshTitle: (id) => this.refreshTitle(tx, id),
|
|
178
178
|
deleteBridgeTail: (id, fromSequence) => this.deleteBridgeTail(tx, id, fromSequence),
|
|
179
179
|
getPrevBridge: (id, sequence) => this.getPrevBridge(tx, id, sequence),
|
|
@@ -226,8 +226,12 @@ var PostgresBackend = class PostgresBackend {
|
|
|
226
226
|
fHeadSequence: headSequence
|
|
227
227
|
}).where(eq(this.tables["t_sessions"].fSessionId, id)).execute();
|
|
228
228
|
}
|
|
229
|
-
async bumpRevision(exec, id) {
|
|
230
|
-
|
|
229
|
+
async bumpRevision(exec, id, lastEventAt) {
|
|
230
|
+
const sessions = this.tables["t_sessions"];
|
|
231
|
+
await exec.update(sessions).set({
|
|
232
|
+
fRevision: sql`${sessions.fRevision} + 1`,
|
|
233
|
+
...lastEventAt === void 0 ? {} : { fLastEventAt: sql`GREATEST(COALESCE(${sessions.fLastEventAt}, 0), ${lastEventAt})` }
|
|
234
|
+
}).where(eq(sessions.fSessionId, id)).execute();
|
|
231
235
|
}
|
|
232
236
|
async refreshTitle(exec, id) {
|
|
233
237
|
const bridges = this.tables["t_session_events"];
|
|
@@ -313,6 +317,55 @@ var PostgresBackend = class PostgresBackend {
|
|
|
313
317
|
`);
|
|
314
318
|
}
|
|
315
319
|
/** 用量聚合:与 SQLite 侧同形,读 `t_event_usage`,数值列回来是字符串。 */
|
|
320
|
+
async listSessionRows(query = {}) {
|
|
321
|
+
const needle = query.query?.trim().toLowerCase() ?? "";
|
|
322
|
+
const like = `%${needle}%`;
|
|
323
|
+
const includeSubagents = query.includeSubagents === true;
|
|
324
|
+
const limit = query.limit ?? 20;
|
|
325
|
+
const offset = query.offset ?? 0;
|
|
326
|
+
const tSessions = this.tables["t_sessions"];
|
|
327
|
+
const tWorkspaces = this.tables["t_workspaces"];
|
|
328
|
+
const tWorkspaceSessions = this.tables["t_workspace_sessions"];
|
|
329
|
+
const workspaceTitle = sql`(SELECT w.f_title FROM ${tWorkspaceSessions} b
|
|
330
|
+
JOIN ${tWorkspaces} w ON w.f_workspace_id = b.f_workspace_id
|
|
331
|
+
WHERE b.f_session_id = s.f_session_id
|
|
332
|
+
ORDER BY b.f_position LIMIT 1)`;
|
|
333
|
+
const where = sql`WHERE (${needle} = ''
|
|
334
|
+
OR lower(COALESCE(s.f_title, '')) LIKE ${like}
|
|
335
|
+
OR lower(COALESCE(${workspaceTitle}, '')) LIKE ${like})
|
|
336
|
+
AND (${includeSubagents} OR s.f_origin IS DISTINCT FROM 'subagent')`;
|
|
337
|
+
const rows = await this.db.execute(sql`
|
|
338
|
+
SELECT s.f_session_id AS session_id,
|
|
339
|
+
s.f_title AS title,
|
|
340
|
+
s.f_origin AS origin,
|
|
341
|
+
s.f_cwd AS cwd,
|
|
342
|
+
s.f_created_at AS created_at,
|
|
343
|
+
(s.f_archived_at IS NOT NULL) AS archived,
|
|
344
|
+
COALESCE(s.f_last_event_at, s.f_created_at) AS updated_at,
|
|
345
|
+
${workspaceTitle} AS workspace
|
|
346
|
+
FROM ${tSessions} s
|
|
347
|
+
${where}
|
|
348
|
+
ORDER BY updated_at DESC, s.f_session_id
|
|
349
|
+
LIMIT ${limit} OFFSET ${offset}
|
|
350
|
+
`);
|
|
351
|
+
const counted = await this.db.execute(sql`
|
|
352
|
+
SELECT count(*) AS n FROM ${tSessions} s ${where}
|
|
353
|
+
`);
|
|
354
|
+
return {
|
|
355
|
+
items: rows.rows.map((row) => ({
|
|
356
|
+
sessionId: typeof row["session_id"] === "string" ? row["session_id"] : String(row["session_id"]),
|
|
357
|
+
title: typeof row["title"] === "string" ? row["title"] : null,
|
|
358
|
+
origin: typeof row["origin"] === "string" ? row["origin"] : null,
|
|
359
|
+
cwd: typeof row["cwd"] === "string" ? row["cwd"] : null,
|
|
360
|
+
createdAt: numeric(row["created_at"]),
|
|
361
|
+
updatedAt: numeric(row["updated_at"]),
|
|
362
|
+
archived: row["archived"] === true,
|
|
363
|
+
subagent: row["origin"] === "subagent",
|
|
364
|
+
workspace: typeof row["workspace"] === "string" ? row["workspace"] : null
|
|
365
|
+
})),
|
|
366
|
+
total: numeric(counted.rows[0]?.["n"] ?? 0)
|
|
367
|
+
};
|
|
368
|
+
}
|
|
316
369
|
async usageReport(sinceMs) {
|
|
317
370
|
const usageSince = sinceMs === void 0 ? sql`` : sql` AND u.f_created_at >= ${sinceMs}`;
|
|
318
371
|
const daySince = sinceMs === void 0 ? sql`` : sql` AND c.f_day >= ${localDayKey(sinceMs)}`;
|
|
@@ -404,7 +457,7 @@ var PostgresBackend = class PostgresBackend {
|
|
|
404
457
|
...tokenTotalsOf(row)
|
|
405
458
|
})),
|
|
406
459
|
sessions: tokenSessions.rows.map((row) => ({
|
|
407
|
-
sessionId: String(row["session_id"]),
|
|
460
|
+
sessionId: typeof row["session_id"] === "string" ? row["session_id"] : String(row["session_id"]),
|
|
408
461
|
title: text(row["title"]),
|
|
409
462
|
subagent: row["subagent"] === true,
|
|
410
463
|
archived: row["archived"] === true,
|
|
@@ -693,11 +746,96 @@ function registerSessionGc(ctx, persistence) {
|
|
|
693
746
|
});
|
|
694
747
|
}
|
|
695
748
|
//#endregion
|
|
749
|
+
//#region src/rows.ts
|
|
750
|
+
/**
|
|
751
|
+
* 管理面的会话列表:**我们自己的**一条路由(完整语料,含归档),与官方 `session/list` 分开。
|
|
752
|
+
*
|
|
753
|
+
* 官方那条(`session/list` → `ctx.sessionQuery.listSessions`)按部署策略默认排除已归档——那是给上游
|
|
754
|
+
* UI 用的。归档集的管理动作要有完整列表,所以这里单独给一条:标题直接从会话列取,最后活动时间用
|
|
755
|
+
* 该会话最后一个事件的时间。
|
|
756
|
+
*/
|
|
757
|
+
const SESSION_ROWS_PATH = "/api/session.rows";
|
|
758
|
+
/** 一页的条数上限:超过就按上限截断(这个接口给页面用,不是导出通道)。 */
|
|
759
|
+
const MAX_PAGE_SIZE = 200;
|
|
760
|
+
const DEFAULT_PAGE_SIZE = 20;
|
|
761
|
+
function registerSessionRows(ctx, backend) {
|
|
762
|
+
ctx.inject(["webServer", "connection"], (webCtx) => {
|
|
763
|
+
const webServer = webCtx.webServer;
|
|
764
|
+
const connection = webCtx.get("connection");
|
|
765
|
+
return webCtx.effect(() => webServer.register({
|
|
766
|
+
kind: "exact",
|
|
767
|
+
path: SESSION_ROWS_PATH,
|
|
768
|
+
handler: async (req, res) => {
|
|
769
|
+
const rejection = connection.requestRejection(req);
|
|
770
|
+
if (rejection !== void 0) {
|
|
771
|
+
res.writeHead(rejection);
|
|
772
|
+
res.end(rejection === 401 ? "unauthorized" : "forbidden");
|
|
773
|
+
return;
|
|
774
|
+
}
|
|
775
|
+
if (req.method !== "POST") {
|
|
776
|
+
res.writeHead(405, { "content-type": "application/json" });
|
|
777
|
+
res.end(JSON.stringify({ error: "method not allowed" }));
|
|
778
|
+
return;
|
|
779
|
+
}
|
|
780
|
+
const chunks = [];
|
|
781
|
+
for await (const chunk of req) chunks.push(chunk);
|
|
782
|
+
const raw = Buffer.concat(chunks).toString("utf8");
|
|
783
|
+
let envelope = {};
|
|
784
|
+
if (raw !== "") try {
|
|
785
|
+
envelope = JSON.parse(raw);
|
|
786
|
+
} catch {
|
|
787
|
+
res.writeHead(400, { "content-type": "application/json" });
|
|
788
|
+
res.end(JSON.stringify({ error: "request body is not JSON" }));
|
|
789
|
+
return;
|
|
790
|
+
}
|
|
791
|
+
const page = Number.isSafeInteger(envelope.page) && (envelope.page ?? 0) > 0 ? envelope.page : 1;
|
|
792
|
+
const requested = envelope.pageSize;
|
|
793
|
+
const pageSize = Number.isSafeInteger(requested) && (requested ?? 0) > 0 ? Math.min(requested, MAX_PAGE_SIZE) : DEFAULT_PAGE_SIZE;
|
|
794
|
+
try {
|
|
795
|
+
const { items, total } = await backend.listSessionRows({
|
|
796
|
+
...envelope.query === void 0 ? {} : { query: envelope.query },
|
|
797
|
+
...envelope.includeSubagents === void 0 ? {} : { includeSubagents: envelope.includeSubagents },
|
|
798
|
+
limit: pageSize,
|
|
799
|
+
offset: (page - 1) * pageSize
|
|
800
|
+
});
|
|
801
|
+
res.writeHead(200, { "content-type": "application/json" });
|
|
802
|
+
res.end(JSON.stringify({
|
|
803
|
+
items,
|
|
804
|
+
total,
|
|
805
|
+
page,
|
|
806
|
+
pageSize
|
|
807
|
+
}));
|
|
808
|
+
} catch (error) {
|
|
809
|
+
res.writeHead(500, { "content-type": "application/json" });
|
|
810
|
+
res.end(JSON.stringify({ error: error instanceof Error ? error.message : "session rows failed" }));
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
}), "session-rdb: session rows route");
|
|
814
|
+
});
|
|
815
|
+
}
|
|
816
|
+
//#endregion
|
|
696
817
|
//#region src/session-query.ts
|
|
697
818
|
var SessionQueryRdb = class extends SessionQueryEngine {
|
|
698
819
|
constructor(ctx, config = {}) {
|
|
699
820
|
super(ctx, config);
|
|
700
821
|
}
|
|
822
|
+
/**
|
|
823
|
+
* 官方列表(`session/list`)**默认不含已归档**:归档是「收起来」的会话,列表与搜索都不该把它混进
|
|
824
|
+
* 日常集合(上游 client 再用 workspace registry 的归档集做「只看归档」的派生)。
|
|
825
|
+
*
|
|
826
|
+
* 完整语料(含归档)走我们自己的管理面路由 `SESSION_ROWS_PATH`——两条路分开,不在一处混。
|
|
827
|
+
*/
|
|
828
|
+
async listSessions(signal) {
|
|
829
|
+
const records = await super.listSessions(signal);
|
|
830
|
+
const archived = this.archivedSessionIds();
|
|
831
|
+
if (archived.size === 0) return records;
|
|
832
|
+
return records.filter((record) => !archived.has(String(record.header.id)));
|
|
833
|
+
}
|
|
834
|
+
/** 归档集合来自 workspace registry;服务未就绪时保守返回空集(宁可多给,不可漏掉可见会话)。 */
|
|
835
|
+
archivedSessionIds() {
|
|
836
|
+
const registry = this.ctx.get("workspaceRegistry");
|
|
837
|
+
return new Set(registry?.archivedSessionIds ?? []);
|
|
838
|
+
}
|
|
701
839
|
async searchSessions() {
|
|
702
840
|
throw searchDisabled();
|
|
703
841
|
}
|
|
@@ -743,11 +881,19 @@ var SessionProjectionCacheRdb = class extends Service {
|
|
|
743
881
|
if (record === void 0) return void 0;
|
|
744
882
|
return identityMatches(record.identity, expected) ? record : void 0;
|
|
745
883
|
}
|
|
746
|
-
|
|
747
|
-
|
|
884
|
+
/**
|
|
885
|
+
* header-only 读(会话列表这类):上游 0.1.7 起只给 header 与 keys——它要求的是**生命周期**身份,
|
|
886
|
+
* 不再要求调用方提供 inherited cut(那个 cut 只有写路径与 hydration 用得上)。
|
|
887
|
+
*/
|
|
888
|
+
cachedSnapshot(meta, keys) {
|
|
889
|
+
const record = this.recordForLifecycle(meta);
|
|
748
890
|
const snapshot = record === void 0 ? void 0 : this.viewRecord(record, keys);
|
|
749
891
|
return this.withDirectTitle(meta.id, keys, snapshot);
|
|
750
892
|
}
|
|
893
|
+
recordForLifecycle(meta) {
|
|
894
|
+
const record = this.lookup(meta.id);
|
|
895
|
+
return record === void 0 || !identityMatches(record.identity, lifecycleIdentityOf(meta)) ? void 0 : record;
|
|
896
|
+
}
|
|
751
897
|
withDirectTitle(id, keys, snapshot) {
|
|
752
898
|
if (keys !== void 0 && !keys.includes(PREDECESSOR_TITLE_KEY)) return snapshot;
|
|
753
899
|
if (snapshot?.values[PREDECESSOR_TITLE_KEY] !== void 0) return snapshot;
|
|
@@ -762,15 +908,10 @@ var SessionProjectionCacheRdb = class extends Service {
|
|
|
762
908
|
values
|
|
763
909
|
};
|
|
764
910
|
}
|
|
765
|
-
cachedPredecessorTitle(meta
|
|
766
|
-
const expected = identityOf(meta, inheritedEventCount);
|
|
911
|
+
cachedPredecessorTitle(meta) {
|
|
767
912
|
const record = this.lookup(meta.id);
|
|
768
|
-
if (record === void 0 || !predecessorIdentityMatches(record.identity,
|
|
769
|
-
|
|
770
|
-
return title === void 0 ? void 0 : {
|
|
771
|
-
...title,
|
|
772
|
-
asOfSeq: -1
|
|
773
|
-
};
|
|
913
|
+
if (record === void 0 || !predecessorIdentityMatches(record.identity, lifecycleIdentityOf(meta))) return;
|
|
914
|
+
return this.viewRecord(record, [PREDECESSOR_TITLE_KEY]);
|
|
774
915
|
}
|
|
775
916
|
viewRecord(record, keys) {
|
|
776
917
|
const values = this.ctx.sessionProjections.viewCheckpoint(record.rows, keys);
|
|
@@ -886,6 +1027,15 @@ function detachJson(rows) {
|
|
|
886
1027
|
if (text === void 0) throw new TypeError("projection checkpoint is not losslessly JSON-serializable");
|
|
887
1028
|
return JSON.parse(text);
|
|
888
1029
|
}
|
|
1030
|
+
/** header 能证明的生命周期身份(不含 inherited cut):header-only 读只比对它。 */
|
|
1031
|
+
function lifecycleIdentityOf(header) {
|
|
1032
|
+
return {
|
|
1033
|
+
formatVersion: header.version,
|
|
1034
|
+
createdAt: header.createdAt,
|
|
1035
|
+
...header.cwd === void 0 ? {} : { cwd: header.cwd },
|
|
1036
|
+
isSeeded: header.isSeeded
|
|
1037
|
+
};
|
|
1038
|
+
}
|
|
889
1039
|
function identityOf(header, inheritedEventCount) {
|
|
890
1040
|
const cut = SessionLogOffset(inheritedEventCount);
|
|
891
1041
|
if (!header.isSeeded && cut !== 0) throw new Error("unseeded projection-cache identity inherited event count must be 0");
|
|
@@ -904,7 +1054,7 @@ function predecessorIdentityMatches(stored, expected) {
|
|
|
904
1054
|
return (stored.formatVersion === void 0 || stored.formatVersion < expected.formatVersion) && lifecycleIdentityMatches(stored, expected);
|
|
905
1055
|
}
|
|
906
1056
|
function lifecycleIdentityMatches(stored, expected) {
|
|
907
|
-
return stored.createdAt === expected.createdAt && stored.cwd === expected.cwd && (stored.isSeeded ?? false) === expected.isSeeded
|
|
1057
|
+
return stored.createdAt === expected.createdAt && stored.cwd === expected.cwd && (stored.isSeeded ?? false) === expected.isSeeded;
|
|
908
1058
|
}
|
|
909
1059
|
//#endregion
|
|
910
1060
|
//#region src/storage-takeover/storage-backend.ts
|
|
@@ -1018,11 +1168,12 @@ function workspaceRecordOf(value) {
|
|
|
1018
1168
|
}
|
|
1019
1169
|
function workspaceStateOf(value) {
|
|
1020
1170
|
const state = value;
|
|
1021
|
-
if (typeof state !== "object" || state === null || typeof state.initialized !== "boolean" || !Array.isArray(state.workspaceIds) || !Array.isArray(state.archivedSessionIds)) throw new TypeError("workspace registry state does not match the stored shape");
|
|
1171
|
+
if (typeof state !== "object" || state === null || typeof state.initialized !== "boolean" || !Array.isArray(state.workspaceIds) || !Array.isArray(state.archivedSessionIds) || !Array.isArray(state.pinnedSessionIds)) throw new TypeError("workspace registry state does not match the stored shape");
|
|
1022
1172
|
return {
|
|
1023
1173
|
initialized: state.initialized,
|
|
1024
1174
|
workspaceIds: state.workspaceIds.map((id) => id),
|
|
1025
1175
|
archivedSessionIds: state.archivedSessionIds.map((id) => id),
|
|
1176
|
+
pinnedSessionIds: state.pinnedSessionIds.map((id) => id),
|
|
1026
1177
|
...state.pendingMutation === void 0 ? {} : { pendingMutation: state.pendingMutation }
|
|
1027
1178
|
};
|
|
1028
1179
|
}
|
|
@@ -1048,6 +1199,16 @@ function installStorageTakeover(ctx, options) {
|
|
|
1048
1199
|
}
|
|
1049
1200
|
//#endregion
|
|
1050
1201
|
//#region src/index.ts
|
|
1202
|
+
/** 一批事件里的最大时间:写路径据此推进会话行的「最后活动时间」。 */
|
|
1203
|
+
function maxEventTime(events) {
|
|
1204
|
+
let max;
|
|
1205
|
+
for (const event of events) {
|
|
1206
|
+
const time = event.time;
|
|
1207
|
+
if (typeof time !== "number") continue;
|
|
1208
|
+
if (max === void 0 || time > max) max = time;
|
|
1209
|
+
}
|
|
1210
|
+
return max;
|
|
1211
|
+
}
|
|
1051
1212
|
const DEFAULT_PROJECTION_WRITE_EVERY_EVENTS = 200;
|
|
1052
1213
|
const DEFAULT_PROJECTION_WRITE_INTERVAL_MS = 5e3;
|
|
1053
1214
|
var SessionDeletionError = class extends Error {
|
|
@@ -1306,9 +1467,9 @@ var RdbSessionHandle = class RdbSessionHandle {
|
|
|
1306
1467
|
}
|
|
1307
1468
|
static LIVE_WRITE_BATCH_MAX_DELAY_MS = 200;
|
|
1308
1469
|
};
|
|
1309
|
-
var SessionPersistenceRdb = class
|
|
1470
|
+
var SessionPersistenceRdb = class extends SessionPersistence {
|
|
1310
1471
|
config;
|
|
1311
|
-
static inject = ["sessions"
|
|
1472
|
+
static inject = ["sessions"];
|
|
1312
1473
|
static Config = z.union([z.object({
|
|
1313
1474
|
type: z.const("sqlite"),
|
|
1314
1475
|
path: z.string().required(),
|
|
@@ -1338,7 +1499,6 @@ var SessionPersistenceRdb = class SessionPersistenceRdb extends SessionPersisten
|
|
|
1338
1499
|
writeIntervalMs: DEFAULT_PROJECTION_WRITE_INTERVAL_MS
|
|
1339
1500
|
})
|
|
1340
1501
|
})]);
|
|
1341
|
-
static settingsNs = "session-rdb";
|
|
1342
1502
|
name = "session-rdb";
|
|
1343
1503
|
tracker = new RdbBackendTracker(this.name);
|
|
1344
1504
|
backend;
|
|
@@ -1349,19 +1509,9 @@ var SessionPersistenceRdb = class SessionPersistenceRdb extends SessionPersisten
|
|
|
1349
1509
|
liveBuffers = /* @__PURE__ */ new Map();
|
|
1350
1510
|
liveReady = /* @__PURE__ */ new Map();
|
|
1351
1511
|
constructor(ctx, config, injectedBackend) {
|
|
1352
|
-
let resolved = config;
|
|
1353
|
-
const settings = ctx.reflect.get("settings");
|
|
1354
|
-
if (settings !== void 0) {
|
|
1355
|
-
const scope = settings.register(SessionPersistenceRdb.settingsNs, SessionPersistenceRdb.Config, { base: config });
|
|
1356
|
-
resolved = scope.get();
|
|
1357
|
-
scope.watch(() => {
|
|
1358
|
-
ctx.logger.warn("session-rdb: settings changed; restart to apply the new configuration");
|
|
1359
|
-
});
|
|
1360
|
-
}
|
|
1361
1512
|
super(ctx);
|
|
1362
1513
|
this.config = config;
|
|
1363
|
-
this.
|
|
1364
|
-
this.backend = injectedBackend ?? createBackend(resolved);
|
|
1514
|
+
this.backend = injectedBackend ?? createBackend(config);
|
|
1365
1515
|
this.ready = this.init();
|
|
1366
1516
|
this.installLiveRouting(ctx);
|
|
1367
1517
|
new SessionBranchRdb(this.ctx);
|
|
@@ -1371,6 +1521,7 @@ var SessionPersistenceRdb = class SessionPersistenceRdb extends SessionPersisten
|
|
|
1371
1521
|
registerSessionExport(this.ctx, this);
|
|
1372
1522
|
registerSessionGc(this.ctx, this);
|
|
1373
1523
|
registerSessionUsage(this.ctx, this);
|
|
1524
|
+
registerSessionRows(this.ctx, this.backend);
|
|
1374
1525
|
installStorageTakeover(this.ctx, {
|
|
1375
1526
|
repository: this.backend.storage,
|
|
1376
1527
|
ready: this.ready,
|
|
@@ -1578,7 +1729,6 @@ var SessionPersistenceRdb = class SessionPersistenceRdb extends SessionPersisten
|
|
|
1578
1729
|
if (events.length === 0) return false;
|
|
1579
1730
|
validateStoredEvents(meta, [...events]);
|
|
1580
1731
|
const reuse = this.reuseEventIds.get(meta.id);
|
|
1581
|
-
if (reuse !== void 0) this.reuseEventIds.delete(meta.id);
|
|
1582
1732
|
let confirmedHead = -1;
|
|
1583
1733
|
await this.backend.transaction(async (tx) => {
|
|
1584
1734
|
if (tornTruncateTo !== void 0) {
|
|
@@ -1598,9 +1748,10 @@ var SessionPersistenceRdb = class SessionPersistenceRdb extends SessionPersisten
|
|
|
1598
1748
|
nextSeq: head.fHeadSequence + 1
|
|
1599
1749
|
}, reuse);
|
|
1600
1750
|
await tx.updateHead(meta.id, headEventId, headSequence);
|
|
1601
|
-
await tx.bumpRevision(meta.id);
|
|
1751
|
+
await tx.bumpRevision(meta.id, maxEventTime(events));
|
|
1602
1752
|
confirmedHead = headSequence;
|
|
1603
1753
|
});
|
|
1754
|
+
if (reuse !== void 0) this.reuseEventIds.delete(meta.id);
|
|
1604
1755
|
this.writeGuard.confirmHead(meta.id, confirmedHead);
|
|
1605
1756
|
this.tracker.materialized(meta.id);
|
|
1606
1757
|
await this.recordEventCounts(meta.id, events);
|
|
@@ -1641,6 +1792,19 @@ var SessionPersistenceRdb = class SessionPersistenceRdb extends SessionPersisten
|
|
|
1641
1792
|
};
|
|
1642
1793
|
}
|
|
1643
1794
|
const { preserved, tornFrom } = scanRows(eventRows, options.fromSeq ?? 0);
|
|
1795
|
+
if (hasLegacyShape(preserved)) {
|
|
1796
|
+
const adopted = adoptLegacyRows(row, eventRows);
|
|
1797
|
+
return {
|
|
1798
|
+
meta: adopted.meta,
|
|
1799
|
+
inheritedEventCount: adopted.inheritedEventCount,
|
|
1800
|
+
events: adopted.events,
|
|
1801
|
+
incarnation: row.fIncarnation,
|
|
1802
|
+
revision: row.fRevision,
|
|
1803
|
+
storedCount: eventRows.length,
|
|
1804
|
+
migrated: false,
|
|
1805
|
+
...adopted.tornFrom !== void 0 ? { tornFrom: adopted.tornFrom } : {}
|
|
1806
|
+
};
|
|
1807
|
+
}
|
|
1644
1808
|
return {
|
|
1645
1809
|
meta,
|
|
1646
1810
|
inheritedEventCount: row.fSeedLength ?? 0,
|
|
@@ -1683,6 +1847,9 @@ var SessionPersistenceRdb = class SessionPersistenceRdb extends SessionPersisten
|
|
|
1683
1847
|
}
|
|
1684
1848
|
async rewriteMigratedLog(id, log) {
|
|
1685
1849
|
await this.backend.transaction(async (tx) => {
|
|
1850
|
+
const head = await tx.getHead(id);
|
|
1851
|
+
if (!this.writeGuard.has(id)) this.writeGuard.confirmHead(id, head.fHeadSequence);
|
|
1852
|
+
this.writeGuard.assertNoConcurrentWriter(id, head.fHeadSequence);
|
|
1686
1853
|
await tx.deleteBridgeTail(id, 0);
|
|
1687
1854
|
await tx.upsertSession({
|
|
1688
1855
|
meta: log.meta,
|
|
@@ -1693,7 +1860,7 @@ var SessionPersistenceRdb = class SessionPersistenceRdb extends SessionPersisten
|
|
|
1693
1860
|
nextSeq: 0
|
|
1694
1861
|
});
|
|
1695
1862
|
await tx.updateHead(id, headEventId, headSequence);
|
|
1696
|
-
await tx.bumpRevision(id);
|
|
1863
|
+
await tx.bumpRevision(id, maxEventTime(log.events));
|
|
1697
1864
|
});
|
|
1698
1865
|
}
|
|
1699
1866
|
async listSnapshots(signal) {
|
|
@@ -1784,6 +1951,10 @@ var SessionPersistenceRdb = class SessionPersistenceRdb extends SessionPersisten
|
|
|
1784
1951
|
registerReuseEventIds(childId, map) {
|
|
1785
1952
|
this.reuseEventIds.set(childId, new Map(map));
|
|
1786
1953
|
}
|
|
1954
|
+
/** 丢弃一个还没被消费的复用映射(fork 失败时):留着会让同一个 childId 之后的落写复用父会话的事件行。 */
|
|
1955
|
+
dropReuseEventIds(childId) {
|
|
1956
|
+
this.reuseEventIds.delete(childId);
|
|
1957
|
+
}
|
|
1787
1958
|
internals() {
|
|
1788
1959
|
return {
|
|
1789
1960
|
backend: this.backend,
|
|
@@ -1795,7 +1966,8 @@ var SessionPersistenceRdb = class SessionPersistenceRdb extends SessionPersisten
|
|
|
1795
1966
|
readFrom: (id, fromSeq, signal) => this.readFrom(id, fromSeq, signal),
|
|
1796
1967
|
listSnapshots: (signal) => this.listSnapshots(signal),
|
|
1797
1968
|
readStoredRevision: (id, signal) => this.readStoredRevision(id, signal),
|
|
1798
|
-
registerReuseEventIds: (childId, map) => this.registerReuseEventIds(childId, map)
|
|
1969
|
+
registerReuseEventIds: (childId, map) => this.registerReuseEventIds(childId, map),
|
|
1970
|
+
dropReuseEventIds: (childId) => this.dropReuseEventIds(childId)
|
|
1799
1971
|
};
|
|
1800
1972
|
}
|
|
1801
1973
|
rowRevision(row) {
|
|
@@ -2005,4 +2177,4 @@ async function appendEventTail(tx, meta, events, anchor, reuse) {
|
|
|
2005
2177
|
};
|
|
2006
2178
|
}
|
|
2007
2179
|
//#endregion
|
|
2008
|
-
export { registerSessionDeletion as i, SessionPersistenceRdb as n,
|
|
2180
|
+
export { registerSessionDeletion as a, SESSION_DELETE_PATH as i, SessionPersistenceRdb as n, SESSION_ROWS_PATH as r, SessionDeletionError as t };
|
package/dist/storage.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as
|
|
1
|
+
import { A as eventKind, B as tWorkspaceState, C as EVENT_ENCODING, D as SCHEMA_VERSION, E as JournalMode, F as tSessionEvents, H as WriteGuard, I as tSessionProjcacheRows, L as tSessions, M as tEvents, N as tPersistenceState, O as SESSION_PERSISTENCE_SQLITE_APPLICATION_ID, P as tSchemaMeta, R as tStorageUnits, S as DEFAULT_BUSY_TIMEOUT_MS, T as EventRole, U as StorageRepository, V as tWorkspaces, a as SessionListRowsPage, c as UsageAggregate, i as EventRow, j as tEventUsage, k as eventDimensions, n as BackendTx, o as SessionListRowsQuery, r as EventCountBucket, s as SessionRow, t as Backend, w as EventKind, z as tWorkspaceSessions } from "./backend-E0bmTXpz.mjs";
|
|
2
2
|
import { SessionId } from "@deepseek-ai/dsh-session";
|
|
3
3
|
import { DatabaseSync } from "node:sqlite";
|
|
4
4
|
//#region src/sqlite.d.ts
|
|
@@ -52,6 +52,7 @@ declare class SqliteBackend implements Backend {
|
|
|
52
52
|
* 沿 `t_events` 的事件类型数——两者限定在**有 token 用量的会话**里,时间范围各自按自己的
|
|
53
53
|
* `f_created_at`(同一时刻写入,口径一致)。
|
|
54
54
|
*/
|
|
55
|
+
listSessionRows(query?: SessionListRowsQuery): Promise<SessionListRowsPage>;
|
|
55
56
|
usageReport(sinceMs?: number): Promise<UsageAggregate>;
|
|
56
57
|
/** 活动计数旁路累加:派生表 `t_event_counts`,不在写事务里(失败可丢,表可销毁重建)。 */
|
|
57
58
|
incrementEventCounts(id: SessionId, buckets: readonly EventCountBucket[]): Promise<void>;
|
package/dist/storage.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { A as WriteGuard, C as tSessions, D as tWorkspaces, E as tWorkspaceState, S as tSessionProjcacheRows, T as tWorkspaceSessions, _ as tEventUsage, b as tSchemaMeta, d as DEFAULT_BUSY_TIMEOUT_MS, f as EVENT_ENCODING, g as eventKind, h as eventDimensions, m as SESSION_PERSISTENCE_SQLITE_APPLICATION_ID, n as openDatabase, p as SCHEMA_VERSION, t as SqliteBackend, v as tEvents, w as tStorageUnits, x as tSessionEvents, y as tPersistenceState } from "./sqlite-
|
|
1
|
+
import { A as WriteGuard, C as tSessions, D as tWorkspaces, E as tWorkspaceState, S as tSessionProjcacheRows, T as tWorkspaceSessions, _ as tEventUsage, b as tSchemaMeta, d as DEFAULT_BUSY_TIMEOUT_MS, f as EVENT_ENCODING, g as eventKind, h as eventDimensions, m as SESSION_PERSISTENCE_SQLITE_APPLICATION_ID, n as openDatabase, p as SCHEMA_VERSION, t as SqliteBackend, v as tEvents, w as tStorageUnits, x as tSessionEvents, y as tPersistenceState } from "./sqlite-zcXVyMxr.mjs";
|
|
2
2
|
export { DEFAULT_BUSY_TIMEOUT_MS, EVENT_ENCODING, SCHEMA_VERSION, SESSION_PERSISTENCE_SQLITE_APPLICATION_ID, SqliteBackend, WriteGuard, eventDimensions, eventKind, openDatabase, tEventUsage, tEvents, tPersistenceState, tSchemaMeta, tSessionEvents, tSessionProjcacheRows, tSessions, tStorageUnits, tWorkspaceSessions, tWorkspaceState, tWorkspaces };
|
package/dist/testing.d.mts
CHANGED
|
@@ -1,16 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { v as truncateLiveSession } from "./backend-E0bmTXpz.mjs";
|
|
2
2
|
import { SessionPersistence } from "@deepseek-ai/dsh-session-persistence";
|
|
3
3
|
import { Session, SessionEvent, SessionHeader, SessionId } from "@deepseek-ai/dsh-session";
|
|
4
4
|
import { Context, Fiber } from "@deepseek-ai/cordis";
|
|
5
|
-
import { SettingsNamespace, SettingsProvider } from "@deepseek-ai/dsh-settings";
|
|
6
|
-
//#region src/testing/helpers.d.ts
|
|
7
|
-
declare class EmptySettings extends SettingsProvider {
|
|
8
|
-
constructor(ctx: Context);
|
|
9
|
-
get writable(): boolean;
|
|
10
|
-
protected load(): Promise<Record<string, unknown>>;
|
|
11
|
-
protected persist(_ns: SettingsNamespace, _section: Record<string, unknown>): Promise<void>;
|
|
12
|
-
}
|
|
13
|
-
//#endregion
|
|
14
5
|
//#region src/testing/contract.d.ts
|
|
15
6
|
interface ContractBackendInstance {
|
|
16
7
|
persistence: SessionPersistence;
|
|
@@ -33,4 +24,4 @@ interface CoordinatorFixture {
|
|
|
33
24
|
}
|
|
34
25
|
declare function runCoordinatorContract(name: string, makeFixture: () => Promise<CoordinatorFixture>): void;
|
|
35
26
|
//#endregion
|
|
36
|
-
export { type ContractBackend, type CoordinatorFixture,
|
|
27
|
+
export { type ContractBackend, type CoordinatorFixture, appendLog, meta, oneTurnLog, runCoordinatorContract, runPersistenceContract, truncateLiveSession };
|
package/dist/testing.mjs
CHANGED
|
@@ -1,28 +1,11 @@
|
|
|
1
|
-
import { a as truncateLiveSession } from "./branch-
|
|
1
|
+
import { a as truncateLiveSession } from "./branch-BjUR6wfe.mjs";
|
|
2
2
|
import { SessionAlreadyExistsError, SessionAlreadyOwnedError, SessionFormatUnsupportedError, SessionHandleClosedError, SessionPersistenceNotFoundError, SessionReadOnlyError } from "@deepseek-ai/dsh-session-persistence";
|
|
3
3
|
import SessionStore, { SESSION_FORMAT_VERSION, SessionId, SessionLogOffset, SessionSeq } from "@deepseek-ai/dsh-session";
|
|
4
4
|
import { Context } from "@deepseek-ai/cordis";
|
|
5
|
-
import { SettingsProvider } from "@deepseek-ai/dsh-settings";
|
|
6
5
|
import { MessageId, createUserMessage, freezeMessage } from "@deepseek-ai/dsh-llm";
|
|
7
6
|
//#region \0rolldown/runtime.js
|
|
8
7
|
var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
|
|
9
8
|
//#endregion
|
|
10
|
-
//#region src/testing/helpers.ts
|
|
11
|
-
var EmptySettings = class extends SettingsProvider {
|
|
12
|
-
constructor(ctx) {
|
|
13
|
-
super(ctx);
|
|
14
|
-
}
|
|
15
|
-
get writable() {
|
|
16
|
-
return true;
|
|
17
|
-
}
|
|
18
|
-
async load() {
|
|
19
|
-
return {};
|
|
20
|
-
}
|
|
21
|
-
persist(_ns, _section) {
|
|
22
|
-
return Promise.resolve();
|
|
23
|
-
}
|
|
24
|
-
};
|
|
25
|
-
//#endregion
|
|
26
9
|
//#region ../../../node_modules/.pnpm/vitest@5.0.1_@opentelemetry+api@1.9.1_@types+node@26.6.2_happy-dom@20.14.5_jsdom@30.1.0_35ad368e4c4a4b8f279ac875fcf2bfae/node_modules/vitest/dist/chunks/tinyrainbow.Ht9iggcq.js
|
|
27
10
|
var b$1 = {
|
|
28
11
|
reset: [0, 0],
|
|
@@ -15623,4 +15606,4 @@ function runCoordinatorContract(name, makeFixture) {
|
|
|
15623
15606
|
});
|
|
15624
15607
|
}
|
|
15625
15608
|
//#endregion
|
|
15626
|
-
export {
|
|
15609
|
+
export { appendLog, meta, oneTurnLog, runCoordinatorContract, runPersistenceContract, truncateLiveSession };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ALTER TABLE "t_sessions" ADD COLUMN "f_agent_preset" text;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
-- 见 SQLite 侧同名迁移的说明:最后活动时间物化到会话行,写路径维护,这里一次性回填。
|
|
2
|
+
ALTER TABLE "t_sessions" ADD COLUMN "f_last_event_at" bigint;
|
|
3
|
+
--> statement-breakpoint
|
|
4
|
+
UPDATE "t_sessions" s
|
|
5
|
+
SET "f_last_event_at" = (
|
|
6
|
+
SELECT MAX(e.f_created_at)
|
|
7
|
+
FROM "t_session_events" se
|
|
8
|
+
JOIN "t_events" e ON e.f_event_id = se.f_event_id
|
|
9
|
+
WHERE se.f_session_id = s.f_session_id
|
|
10
|
+
);
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
-- 只加一列:`agentPreset` 是会话 header 的创建事实,会话投影 `agentPreset` 的 `init` 只读 header
|
|
2
|
+
-- (缺了就被算成 null 并缓存下来),所以列式 header 必须存它。
|
|
3
|
+
--
|
|
4
|
+
-- 生成器另外还想重建 `t_event_usage`(实体的 tokens 列没有 default,而既有迁移建的表带 DEFAULT 0)。
|
|
5
|
+
-- 那次重建与本次改动无关,且会在几十万行的库上重写整表,所以不在这里应用;实体的 default 差异
|
|
6
|
+
-- 留待单独处理。
|
|
7
|
+
ALTER TABLE `t_sessions` ADD `f_agent_preset` text;
|