@morlay/session-rdb 0.1.10-alpha.0 → 0.1.10-alpha.3

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.
Files changed (142) hide show
  1. package/README.md +18 -3
  2. package/dist/artifact.d.mts +118 -2
  3. package/dist/artifact.mjs +5 -3
  4. package/dist/{log-TYgd8Xkr.d.mts → backend-BKtJ5sO5.d.mts} +183 -284
  5. package/dist/backend-rdb-BDCntVDy.mjs +1341 -0
  6. package/dist/branch-CG1PUlO6.mjs +833 -0
  7. package/dist/deletion-SSX7IqU8.mjs +35 -0
  8. package/dist/deletion.d.mts +1 -1
  9. package/dist/deletion.mjs +2 -2
  10. package/dist/export-CfJQjQfr.mjs +123 -0
  11. package/dist/export.d.mts +1 -1
  12. package/dist/export.mjs +31 -55
  13. package/dist/import.d.mts +1 -1
  14. package/dist/import.mjs +31 -76
  15. package/dist/index-CUD2ZpTA.d.mts +71 -0
  16. package/dist/index.d.mts +20 -2
  17. package/dist/index.mjs +1561 -4
  18. package/dist/open-YoyD_kah.mjs +561 -0
  19. package/dist/organize.d.mts +9 -0
  20. package/dist/organize.mjs +30 -0
  21. package/dist/route-DpfY0zq-.mjs +114 -0
  22. package/dist/storage.d.mts +41 -48
  23. package/dist/storage.mjs +3 -1
  24. package/dist/testing.d.mts +9 -1
  25. package/dist/testing.mjs +25 -4
  26. package/dist/usage-row-BQQM-_t6.mjs +36 -0
  27. package/dist/usage.d.mts +1 -1
  28. package/dist/usage.mjs +7 -40
  29. package/dist/write-guard-CYrY58GM.mjs +174 -0
  30. package/package.json +32 -36
  31. package/src/artifact.ts +4 -1
  32. package/src/backend-rdb.ts +331 -0
  33. package/src/backend.ts +32 -15
  34. package/src/{branch.ts → branch/index.ts} +109 -110
  35. package/src/branch/port.ts +62 -0
  36. package/src/branch/semantics.ts +69 -0
  37. package/src/db/open.ts +180 -0
  38. package/src/db/stats.ts +449 -0
  39. package/src/deletion-error.ts +17 -0
  40. package/src/deletion.ts +19 -72
  41. package/src/entities/index.ts +16 -1
  42. package/src/entities/v3/event-usage.ts +3 -3
  43. package/src/entities/v3/events.ts +3 -3
  44. package/src/entities/v3/index.ts +4 -16
  45. package/src/entities/v3/persistence-state.ts +3 -3
  46. package/src/entities/v3/schema-meta.ts +3 -3
  47. package/src/entities/v3/session-counts.ts +3 -3
  48. package/src/entities/v3/session-events.ts +3 -3
  49. package/src/entities/v3/session-projcache-rows.ts +3 -3
  50. package/src/entities/v3/session-usage.ts +3 -3
  51. package/src/entities/v3/sessions.ts +3 -3
  52. package/src/entities/v3/storage-records.ts +17 -0
  53. package/src/entities/v3/storage-units.ts +3 -3
  54. package/src/{schema.ts → event-shape.ts} +0 -46
  55. package/src/export.ts +32 -78
  56. package/src/handles.ts +349 -0
  57. package/src/http/route.ts +178 -0
  58. package/src/import-storages.ts +5 -2
  59. package/src/import.ts +63 -125
  60. package/src/index.ts +15 -1666
  61. package/src/legacy.ts +3 -1
  62. package/src/live-routing.ts +205 -0
  63. package/src/log/export.ts +84 -0
  64. package/src/{log.ts → log/repair.ts} +1 -268
  65. package/src/log/rows.ts +107 -0
  66. package/src/log/usage-row.ts +64 -0
  67. package/src/migrations.ts +196 -0
  68. package/src/organize.ts +38 -0
  69. package/src/read-path.ts +190 -0
  70. package/src/routes.ts +2 -1
  71. package/src/rows.ts +25 -77
  72. package/src/session-ops.ts +444 -0
  73. package/src/session-store.ts +675 -0
  74. package/src/stats-fold.ts +44 -0
  75. package/src/storage-takeover/index.ts +2 -5
  76. package/src/storage-takeover/legacy-domain-migration.ts +118 -0
  77. package/src/storage-takeover/projection-cache.ts +122 -71
  78. package/src/storage-takeover/read-model.ts +189 -0
  79. package/src/storage-takeover/repository.ts +236 -442
  80. package/src/storage-takeover/storage-backend.ts +44 -159
  81. package/src/storage-takeover/types.ts +45 -19
  82. package/src/storage-takeover/workspace-documents.ts +65 -0
  83. package/src/storage.ts +4 -2
  84. package/src/testing.ts +21 -1
  85. package/src/usage.ts +11 -65
  86. package/src/version.ts +13 -0
  87. package/src/write-path.ts +286 -0
  88. package/dist/branch-CoPCRKer.mjs +0 -401
  89. package/dist/deletion-CWl89TIR.mjs +0 -2249
  90. package/dist/gc.d.mts +0 -4
  91. package/dist/gc.mjs +0 -48
  92. package/dist/log-CcWy46VE.mjs +0 -558
  93. package/dist/routes-hZjTcX_R.mjs +0 -9
  94. package/dist/sqlite-BNRd1aqR.mjs +0 -1778
  95. package/drizzle/postgres/20260908072805_v2_initial/migration.sql +0 -58
  96. package/drizzle/postgres/20260908072805_v2_initial/snapshot.json +0 -686
  97. package/drizzle/postgres/20260908072806_v3_drop_original_seq/migration.sql +0 -1
  98. package/drizzle/postgres/20260908072806_v3_drop_original_seq/snapshot.json +0 -673
  99. package/drizzle/postgres/20260910120001_v3_storage_tables/migration.sql +0 -65
  100. package/drizzle/postgres/20260910120001_v3_storage_tables/snapshot.json +0 -1183
  101. package/drizzle/postgres/20260910130001_v3_session_title_backfill/migration.sql +0 -10
  102. package/drizzle/postgres/20260910130001_v3_session_title_backfill/snapshot.json +0 -1183
  103. package/drizzle/postgres/20260918120000_v3_event_usage/migration.sql +0 -14
  104. package/drizzle/postgres/20260918120000_v3_event_usage/snapshot.json +0 -1309
  105. package/drizzle/postgres/20260921075900_v3_agent_preset/migration.sql +0 -1
  106. package/drizzle/postgres/20260922090001_v3_pinned_seq/migration.sql +0 -2
  107. package/drizzle/postgres/20260922093001_v3_last_event_at/migration.sql +0 -10
  108. package/drizzle/postgres/20260923120000_v3_usage_materialized/migration.sql +0 -48
  109. package/drizzle/postgres/20261008103001_v3_schedule_tasks/migration.sql +0 -11
  110. package/drizzle/sqlite/20260908072751_v2_initial/migration.sql +0 -53
  111. package/drizzle/sqlite/20260908072751_v2_initial/snapshot.json +0 -492
  112. package/drizzle/sqlite/20260908072752_v3_drop_original_seq/migration.sql +0 -6
  113. package/drizzle/sqlite/20260908072752_v3_drop_original_seq/snapshot.json +0 -513
  114. package/drizzle/sqlite/20260910120000_v3_storage_tables/migration.sql +0 -66
  115. package/drizzle/sqlite/20260910120000_v3_storage_tables/snapshot.json +0 -905
  116. package/drizzle/sqlite/20260910130000_v3_session_title_backfill/migration.sql +0 -13
  117. package/drizzle/sqlite/20260910130000_v3_session_title_backfill/snapshot.json +0 -905
  118. package/drizzle/sqlite/20260918120000_v3_event_usage/migration.sql +0 -14
  119. package/drizzle/sqlite/20260918120000_v3_event_usage/snapshot.json +0 -1031
  120. package/drizzle/sqlite/20260921075855_v3_agent_preset/migration.sql +0 -7
  121. package/drizzle/sqlite/20260921075855_v3_agent_preset/snapshot.json +0 -1041
  122. package/drizzle/sqlite/20260922090000_v3_pinned_seq/migration.sql +0 -4
  123. package/drizzle/sqlite/20260922093000_v3_last_event_at/migration.sql +0 -13
  124. package/drizzle/sqlite/20260923120000_v3_usage_materialized/migration.sql +0 -48
  125. package/drizzle/sqlite/20261008103000_v3_schedule_tasks/migration.sql +0 -14
  126. package/src/adapters/index.ts +0 -12
  127. package/src/adapters/to-postgres.ts +0 -93
  128. package/src/adapters/to-sqlite.ts +0 -83
  129. package/src/adapters/types.ts +0 -44
  130. package/src/drizzle/postgres-v2.ts +0 -10
  131. package/src/drizzle/postgres-v3.ts +0 -18
  132. package/src/drizzle/sqlite-v2.ts +0 -9
  133. package/src/drizzle/sqlite-v3.ts +0 -18
  134. package/src/entities/v2/index.ts +0 -20
  135. package/src/entities/v2/session-events.ts +0 -10
  136. package/src/entities/v3/schedule-tasks.ts +0 -16
  137. package/src/entities/v3/workspace-sessions.ts +0 -19
  138. package/src/entities/v3/workspace-state.ts +0 -12
  139. package/src/entities/v3/workspaces.ts +0 -14
  140. package/src/gc.ts +0 -77
  141. package/src/postgres.ts +0 -1015
  142. package/src/sqlite.ts +0 -1096
@@ -0,0 +1,35 @@
1
+ import { i as SESSION_DELETE_PATH, n as mountJsonRoute, t as HttpError } from "./route-DpfY0zq-.mjs";
2
+ // src/deletion-error.ts
3
+ var SessionDeletionError = class extends Error {
4
+ code;
5
+ constructor(message, code) {
6
+ super(message);
7
+ this.code = code;
8
+ this.name = "SessionDeletionError";
9
+ }
10
+ };
11
+ // src/deletion.ts
12
+ const STATUS_OF_DELETION_ERROR = {
13
+ SESSION_NOT_FOUND: 404,
14
+ SESSION_NOT_ARCHIVED: 409,
15
+ SESSION_LIVE: 409
16
+ };
17
+ function registerSessionDeletion(ctx, persistence) {
18
+ mountJsonRoute(ctx, {
19
+ path: SESSION_DELETE_PATH,
20
+ handle: async (body) => {
21
+ const { sessionId } = body;
22
+ if (typeof sessionId !== "string" || sessionId === "") throw new HttpError(400, "missing sessionId field");
23
+ await persistence.deleteSession(sessionId);
24
+ return { body: { deleted: sessionId } };
25
+ },
26
+ statusOfError: (error) => error instanceof SessionDeletionError ? {
27
+ status: STATUS_OF_DELETION_ERROR[error.code],
28
+ body: {
29
+ error: error.message,
30
+ code: error.code
31
+ }
32
+ } : void 0
33
+ });
34
+ }
35
+ export { SessionDeletionError as n, registerSessionDeletion as t };
@@ -1,4 +1,4 @@
1
- import { Nt as SESSION_DELETE_PATH, et as SessionPersistenceRdb } from "./log-TYgd8Xkr.mjs";
1
+ import { k as SessionPersistenceRdb, z as SESSION_DELETE_PATH } from "./backend-BKtJ5sO5.mjs";
2
2
  import { Context } from "@deepseek-ai/cordis";
3
3
  export declare function registerSessionDeletion(ctx: Context, persistence: SessionPersistenceRdb): void;
4
4
  export { SESSION_DELETE_PATH };
package/dist/deletion.mjs CHANGED
@@ -1,3 +1,3 @@
1
- import { t as SESSION_DELETE_PATH } from "./routes-hZjTcX_R.mjs";
2
- import { t as registerSessionDeletion } from "./deletion-CWl89TIR.mjs";
1
+ import { i as SESSION_DELETE_PATH } from "./route-DpfY0zq-.mjs";
2
+ import { t as registerSessionDeletion } from "./deletion-SSX7IqU8.mjs";
3
3
  export { SESSION_DELETE_PATH, registerSessionDeletion };
@@ -0,0 +1,123 @@
1
+ import { SessionSeq } from "@deepseek-ai/dsh-session";
2
+ import { sessionFormatCatalog } from "@deepseek-ai/dsh-session-format-catalog";
3
+ // src/log/rows.ts
4
+ function normalizeSurfaceOp(value) {
5
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return value;
6
+ const record = value;
7
+ if (record["op"] !== "replace") return value;
8
+ const startSeq = record["startSeq"] ?? record["start"];
9
+ const endSeq = record["endSeq"] ?? record["end"];
10
+ if (typeof startSeq !== "number" || typeof endSeq !== "number") return value;
11
+ return {
12
+ op: "replace",
13
+ startSeq: SessionSeq(startSeq),
14
+ endSeq: SessionSeq(endSeq)
15
+ };
16
+ }
17
+ function rowToMeta(row) {
18
+ if (!Number.isSafeInteger(row.fCreatedAt) || row.fCreatedAt < 0) throw new Error("stored session createdAt must be a non-negative safe integer");
19
+ return {
20
+ version: row.fVersion,
21
+ id: row.fSessionId,
22
+ createdAt: row.fCreatedAt,
23
+ ...row.fCwd !== null ? { cwd: row.fCwd } : {},
24
+ ...row.fParentSession !== null ? { parentSession: row.fParentSession } : {},
25
+ isSeeded: row.fSeedLength !== null,
26
+ ...row.fOrigin !== null ? { origin: row.fOrigin } : {},
27
+ ...row.fDelegationDepth === null ? {} : { delegationDepth: row.fDelegationDepth },
28
+ ...row.fAgentPreset === null ? {} : { agentPreset: row.fAgentPreset }
29
+ };
30
+ }
31
+ /** 新建会话行(列名即表定义里的列,值全部显式给出)。 */
32
+ function sessionInsertColumns(storage, incarnation) {
33
+ return {
34
+ ...sessionHeaderColumns(storage),
35
+ f_session_id: storage.meta.id,
36
+ f_head_event_id: "",
37
+ f_head_sequence: -1,
38
+ f_incarnation: incarnation,
39
+ f_revision: 0
40
+ };
41
+ }
42
+ /** header 的八列(`upsert` 时「变了的列才写」判据也用它)。 */
43
+ function sessionHeaderColumns(storage) {
44
+ const meta = storage.meta;
45
+ return {
46
+ f_version: meta.version,
47
+ f_created_at: meta.createdAt,
48
+ f_cwd: meta.cwd ?? null,
49
+ f_parent_session: meta.parentSession ?? null,
50
+ f_seed_length: meta.isSeeded ? storage.inheritedEventCount : null,
51
+ f_origin: meta.origin ?? null,
52
+ f_delegation_depth: meta.delegationDepth ?? null,
53
+ f_agent_preset: meta.agentPreset ?? null
54
+ };
55
+ }
56
+ function rowToEvent(row) {
57
+ const surfaceOp = row.fSurfaceOp !== null ? normalizeSurfaceOp(JSON.parse(row.fSurfaceOp)) : void 0;
58
+ const record = JSON.parse(row.fData);
59
+ if (typeof record === "object" && record !== null && !Array.isArray(record) && typeof record["type"] === "string" && typeof record["seq"] === "number" && typeof record["time"] === "number" && "data" in record) {
60
+ const event = record;
61
+ event.seq = row.fSequence;
62
+ event.time = row.fCreatedAt;
63
+ if (surfaceOp !== void 0) event.surfaceOp = surfaceOp;
64
+ return event;
65
+ }
66
+ return {
67
+ type: row.fType,
68
+ seq: row.fSequence,
69
+ time: row.fCreatedAt,
70
+ data: record,
71
+ ...surfaceOp === void 0 ? {} : { surfaceOp }
72
+ };
73
+ }
74
+ // src/log/export.ts
75
+ function scanRows(rows, base = 0) {
76
+ let lastTurnEnd = -1;
77
+ for (let i = rows.length - 1; i >= 0; i--) {
78
+ if (rows[i]?.fType !== "turn/end") continue;
79
+ try {
80
+ rowToEvent(rows[i]);
81
+ lastTurnEnd = i;
82
+ break;
83
+ } catch {}
84
+ }
85
+ const preserved = [];
86
+ for (let i = 0; i < rows.length; i++) {
87
+ let event;
88
+ try {
89
+ event = rowToEvent(rows[i]);
90
+ } catch {
91
+ if (i <= lastTurnEnd) throw new Error(`corrupt session log: unparsable committed event at seq ${rows[i]?.fSequence}`);
92
+ break;
93
+ }
94
+ if (event.seq !== base + i) {
95
+ if (i <= lastTurnEnd) throw new Error(`corrupt session log: seq gap in committed region (expected ${base + i}, got ${event.seq})`);
96
+ break;
97
+ }
98
+ preserved.push(event);
99
+ }
100
+ return preserved.length < rows.length ? {
101
+ preserved,
102
+ tornFrom: base + preserved.length
103
+ } : { preserved };
104
+ }
105
+ function toJsonlArtifact(meta, inheritedEventCount, events) {
106
+ const header = sessionFormatCatalog.encodeCurrentHeader({
107
+ ...meta,
108
+ delegationDepth: meta.delegationDepth ?? 0
109
+ }, inheritedEventCount);
110
+ const lines = [JSON.stringify(header)];
111
+ for (const event of events) lines.push(JSON.stringify(sessionFormatCatalog.encodeCurrentEvent(event)));
112
+ return lines.join("\n");
113
+ }
114
+ function titleOfEventData(data) {
115
+ try {
116
+ const value = JSON.parse(data);
117
+ const title = value.data?.title ?? value.title;
118
+ return typeof title === "string" ? title : void 0;
119
+ } catch {
120
+ return;
121
+ }
122
+ }
123
+ export { rowToMeta as a, rowToEvent as i, titleOfEventData as n, sessionHeaderColumns as o, toJsonlArtifact as r, sessionInsertColumns as s, scanRows as t };
package/dist/export.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { Pt as SESSION_EXPORT_PATH, et as SessionPersistenceRdb } from "./log-TYgd8Xkr.mjs";
1
+ import { B as SESSION_EXPORT_PATH, k as SessionPersistenceRdb } from "./backend-BKtJ5sO5.mjs";
2
2
  import { Context } from "@deepseek-ai/cordis";
3
3
  export declare function registerSessionExport(ctx: Context, persistence: SessionPersistenceRdb): void;
4
4
  export { SESSION_EXPORT_PATH };
package/dist/export.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { n as SESSION_EXPORT_PATH } from "./routes-hZjTcX_R.mjs";
1
+ import { a as SESSION_EXPORT_PATH, r as mountRoute } from "./route-DpfY0zq-.mjs";
2
2
  import { strToU8, zip } from "fflate";
3
3
  // src/export.ts
4
4
  function safeFilename(sessionId) {
@@ -13,60 +13,36 @@ function zipBytes(files) {
13
13
  });
14
14
  }
15
15
  function registerSessionExport(ctx, persistence) {
16
- ctx.inject(["webServer", "connection"], (webCtx) => {
17
- const webServer = webCtx.webServer;
18
- const connection = webCtx.get("connection");
19
- return webCtx.effect(() => webServer.register({
20
- kind: "exact",
21
- path: SESSION_EXPORT_PATH,
22
- handler: async (req, res) => {
23
- const rejection = connection.requestRejection(req);
24
- if (rejection !== void 0) {
25
- res.writeHead(rejection);
26
- res.end(rejection === 401 ? "unauthorized" : "forbidden");
27
- return;
28
- }
29
- if (req.method !== "POST") {
30
- res.writeHead(405, { "content-type": "application/json" });
31
- res.end(JSON.stringify({ error: "method not allowed" }));
32
- return;
33
- }
34
- const chunks = [];
35
- for await (const chunk of req) chunks.push(chunk);
36
- let envelope;
37
- try {
38
- envelope = JSON.parse(Buffer.concat(chunks).toString("utf8"));
39
- } catch {
40
- res.writeHead(400, { "content-type": "application/json" });
41
- res.end(JSON.stringify({ error: "request body is not JSON" }));
42
- return;
43
- }
44
- if (typeof envelope.sessionId !== "string" || envelope.sessionId === "") {
45
- res.writeHead(400, { "content-type": "application/json" });
46
- res.end(JSON.stringify({ error: "missing sessionId field" }));
47
- return;
48
- }
49
- const sessionId = envelope.sessionId;
50
- try {
51
- const raw = await persistence.readRaw(sessionId);
52
- if (raw === void 0) {
53
- res.writeHead(404, { "content-type": "application/json" });
54
- res.end(JSON.stringify({ error: `session "${sessionId}" not found` }));
55
- return;
56
- }
57
- const zipFile = await zipBytes({ [raw.filename]: strToU8(raw.content) });
58
- res.writeHead(200, {
59
- "content-type": "application/zip",
60
- "content-length": String(zipFile.byteLength),
61
- "content-disposition": `attachment; filename="${safeFilename(sessionId)}.zip"`
62
- });
63
- res.end(Buffer.from(zipFile));
64
- } catch (error) {
65
- res.writeHead(500, { "content-type": "application/json" });
66
- res.end(JSON.stringify({ error: error instanceof Error ? error.message : "session export failed" }));
67
- }
68
- }
69
- }), `session-rdb: ${SESSION_EXPORT_PATH} route`);
16
+ mountRoute(ctx, SESSION_EXPORT_PATH, async (req, res) => {
17
+ const chunks = [];
18
+ for await (const chunk of req) chunks.push(chunk);
19
+ let envelope;
20
+ try {
21
+ envelope = JSON.parse(Buffer.concat(chunks).toString("utf8"));
22
+ } catch {
23
+ res.writeHead(400, { "content-type": "application/json" });
24
+ res.end(JSON.stringify({ error: "request body is not JSON" }));
25
+ return;
26
+ }
27
+ if (typeof envelope.sessionId !== "string" || envelope.sessionId === "") {
28
+ res.writeHead(400, { "content-type": "application/json" });
29
+ res.end(JSON.stringify({ error: "missing sessionId field" }));
30
+ return;
31
+ }
32
+ const sessionId = envelope.sessionId;
33
+ const raw = await persistence.readRaw(sessionId);
34
+ if (raw === void 0) {
35
+ res.writeHead(404, { "content-type": "application/json" });
36
+ res.end(JSON.stringify({ error: `session "${sessionId}" not found` }));
37
+ return;
38
+ }
39
+ const zipFile = await zipBytes({ [raw.filename]: strToU8(raw.content) });
40
+ res.writeHead(200, {
41
+ "content-type": "application/zip",
42
+ "content-length": String(zipFile.byteLength),
43
+ "content-disposition": `attachment; filename="${safeFilename(sessionId)}.zip"`
44
+ });
45
+ res.end(Buffer.from(zipFile));
70
46
  });
71
47
  }
72
48
  export { SESSION_EXPORT_PATH, registerSessionExport };
package/dist/import.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { It as SESSION_IMPORT_PATH, et as SessionPersistenceRdb } from "./log-TYgd8Xkr.mjs";
1
+ import { V as SESSION_IMPORT_PATH, k as SessionPersistenceRdb } from "./backend-BKtJ5sO5.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";
package/dist/import.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { i as SESSION_IMPORT_PATH } from "./routes-hZjTcX_R.mjs";
2
- import { a as replaceLiveSessionLog, u as restoreCatalog } from "./branch-CoPCRKer.mjs";
1
+ import { i as replaceLiveSessionLog, u as restoreCatalog } from "./branch-CG1PUlO6.mjs";
2
+ import { n as mountJsonRoute, o as SESSION_IMPORT_PATH, t as HttpError } from "./route-DpfY0zq-.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";
@@ -128,81 +128,36 @@ async function persistImport(persistence, branch, imported, targetId, sessions,
128
128
  return id;
129
129
  }
130
130
  function registerSessionImport(ctx, persistence) {
131
- ctx.inject(["webServer", "connection"], (webCtx) => {
132
- const webServer = webCtx.webServer;
133
- const connection = webCtx.get("connection");
134
- return webCtx.effect(() => webServer.register({
135
- kind: "exact",
136
- path: SESSION_IMPORT_PATH,
137
- handler: async (req, res) => {
138
- const rejection = connection.requestRejection(req);
139
- if (rejection !== void 0) {
140
- res.writeHead(rejection);
141
- res.end(rejection === 401 ? "unauthorized" : "forbidden");
142
- return;
143
- }
144
- const chunks = [];
145
- for await (const chunk of req) chunks.push(chunk);
146
- const body = Buffer.concat(chunks);
147
- let envelope;
148
- try {
149
- envelope = JSON.parse(body.toString("utf8"));
150
- } catch {
151
- res.writeHead(400, { "content-type": "application/json" });
152
- res.end(JSON.stringify({ error: "request body is not JSON" }));
153
- return;
154
- }
155
- if (typeof envelope.zip !== "string" || envelope.zip === "") {
156
- res.writeHead(400, { "content-type": "application/json" });
157
- res.end(JSON.stringify({ error: "missing zip field" }));
158
- return;
159
- }
160
- if (envelope.sessionId !== void 0 && (typeof envelope.sessionId !== "string" || envelope.sessionId === "")) {
161
- res.writeHead(400, { "content-type": "application/json" });
162
- res.end(JSON.stringify({ error: "sessionId must be a non-empty string" }));
163
- return;
164
- }
165
- let zip;
166
- try {
167
- zip = Buffer.from(envelope.zip, "base64");
168
- } catch {
169
- res.writeHead(400, { "content-type": "application/json" });
170
- res.end(JSON.stringify({ error: "zip field is not valid base64" }));
171
- return;
172
- }
173
- if (zip.byteLength > MAX_IMPORT_ZIP_BYTES) {
174
- res.writeHead(413, { "content-type": "application/json" });
175
- res.end(JSON.stringify({ error: "imported zip exceeds the size limit" }));
176
- return;
177
- }
178
- let imported;
179
- try {
180
- imported = await parseImportZip(zip);
181
- } catch (error) {
182
- res.writeHead(400, { "content-type": "application/json" });
183
- res.end(JSON.stringify({ error: error instanceof Error ? error.message : "imported zip is invalid" }));
184
- return;
185
- }
186
- const targetId = typeof envelope.sessionId === "string" ? envelope.sessionId : void 0;
187
- const branch = webCtx.get("sessionBranch");
188
- try {
189
- const sessions = webCtx.get("sessions");
190
- const id = await persistImport(persistence, branch, imported, targetId, sessions, (sessionId) => stopAgentLoop(webCtx, sessionId));
191
- res.writeHead(200, { "content-type": "application/json" });
192
- res.end(JSON.stringify({ sessionId: id }));
193
- } catch (error) {
194
- const message = error instanceof Error ? error.message : String(error);
195
- if (targetId !== void 0 && /not found/i.test(message)) {
196
- res.writeHead(404, { "content-type": "application/json" });
197
- res.end(JSON.stringify({ error: `session "${targetId}" not found` }));
198
- return;
199
- }
200
- res.writeHead(500, { "content-type": "application/json" });
201
- res.end(JSON.stringify({ error: error instanceof Error ? error.message : "import failed" }));
202
- return;
203
- }
131
+ mountJsonRoute(ctx, {
132
+ path: SESSION_IMPORT_PATH,
133
+ handle: async (body, webCtx) => {
134
+ const envelope = body;
135
+ if (typeof envelope.zip !== "string" || envelope.zip === "") throw new HttpError(400, "missing zip field");
136
+ if (envelope.sessionId !== void 0 && (typeof envelope.sessionId !== "string" || envelope.sessionId === "")) throw new HttpError(400, "sessionId must be a non-empty string");
137
+ let zip;
138
+ try {
139
+ zip = Buffer.from(envelope.zip, "base64");
140
+ } catch {
141
+ throw new HttpError(400, "zip field is not valid base64");
142
+ }
143
+ if (zip.byteLength > MAX_IMPORT_ZIP_BYTES) throw new HttpError(413, "imported zip exceeds the size limit");
144
+ let imported;
145
+ try {
146
+ imported = await parseImportZip(zip);
147
+ } catch (error) {
148
+ throw new HttpError(400, error instanceof Error ? error.message : "imported zip is invalid");
204
149
  }
205
- }), `session-rdb: ${SESSION_IMPORT_PATH} route`);
150
+ const targetId = typeof envelope.sessionId === "string" ? envelope.sessionId : void 0;
151
+ const branch = webCtx.get("sessionBranch");
152
+ const sessions = webCtx.get("sessions");
153
+ try {
154
+ return { body: { sessionId: await persistImport(persistence, branch, imported, targetId, sessions, (sessionId) => stopAgentLoop(webCtx, sessionId)) } };
155
+ } catch (error) {
156
+ const message = error instanceof Error ? error.message : String(error);
157
+ if (targetId !== void 0 && /not found/i.test(message)) throw new HttpError(404, `session "${targetId}" not found`);
158
+ throw error;
159
+ }
160
+ }
206
161
  });
207
162
  }
208
163
  export { SESSION_IMPORT_PATH, SESSION_LOG_ARTIFACT_FILENAME, parseImportZip, parseJsonlArtifact, persistImport, registerSessionImport };
@@ -0,0 +1,71 @@
1
+ import { M as BranchPort } from "./backend-BKtJ5sO5.mjs";
2
+ import { Session, SessionEvent, SessionHeader, SessionId, SessionLogOffset } from "@deepseek-ai/dsh-session";
3
+ import { SessionPersistenceSnapshot } from "@deepseek-ai/dsh-session-persistence";
4
+ import { BranchAnchorMode, BranchBoundary, ForkFromOptions, SessionBranch, SessionBranchProvider } from "@morlay/session-branch";
5
+ declare function locateTurnEnd(events: readonly SessionEvent[], atSeq?: number, mode?: BranchAnchorMode): number;
6
+ interface LiveSessionHooks {
7
+ getSession(id: SessionId): Session | undefined;
8
+ getAgent(id: SessionId): LiveAgentLike | undefined;
9
+ flush(session: Session): Promise<boolean>;
10
+ warn?(message: string): void;
11
+ resetProjections?(session: Session): void;
12
+ resetTokenMeter?(session: Session): void;
13
+ refreshProjectionCache?(session: ProjectionCacheSession): Promise<void>;
14
+ }
15
+ interface ProjectionCacheSession {
16
+ readonly id: SessionId;
17
+ readonly header: SessionHeader;
18
+ readonly inheritedEventCount: SessionLogOffset;
19
+ readonly headSeq?: number;
20
+ snapshotEvents(): readonly SessionEvent[];
21
+ }
22
+ interface LiveAgentLike {
23
+ session: Session;
24
+ requestHeaderLogged?: boolean;
25
+ inbox?: {
26
+ clear(): void;
27
+ };
28
+ }
29
+ /**
30
+ * 会话**当前内存 log** 的快照(复制一份)。用于"需要完整序列"的操作(首次落库的 seed、rewind / fork 的历史),
31
+ * 不走已弃用的同步历史读 `snapshotEvents()`——那三个读口上游已声明「新调用禁止」,而这里要的是当前内存里
32
+ * 那份完整日志,不是按需分页的历史。
33
+ * @param session - 会话。
34
+ * @returns 事件副本(调用方可以自由遍历)。
35
+ */
36
+ declare function liveEventsOf(session: Session): readonly SessionEvent[];
37
+ /** @returns 截断后日志里最后一个 `turn/start` 的 turn 号(没有则 0)。 */
38
+ declare function truncateLiveSession(session: Session, newLength: number): number;
39
+ declare class SessionBranchRdbProvider implements SessionBranchProvider {
40
+ private readonly port;
41
+ private readonly live;
42
+ readonly name = "session-rdb";
43
+ constructor(port: BranchPort, live?: LiveSessionHooks);
44
+ readBranchPrefix(id: SessionId, atSeq?: number, mode?: BranchAnchorMode, signal?: AbortSignal): Promise<BranchBoundary>;
45
+ readRawEvents(id: SessionId, signal?: AbortSignal): Promise<{
46
+ meta: SessionHeader;
47
+ events: readonly SessionEvent[];
48
+ }>;
49
+ forkFrom(sourceId: SessionId, options?: ForkFromOptions, signal?: AbortSignal): Promise<SessionId>;
50
+ rewind(id: SessionId, toBoundary: number, signal?: AbortSignal): Promise<SessionPersistenceSnapshot>;
51
+ private rewindKeepLength;
52
+ private refreshProjectionCache;
53
+ }
54
+ declare class SessionBranchRdb extends SessionBranch {
55
+ static inject: string[];
56
+ private readonly warned;
57
+ constructor(ctx: import("@deepseek-ai/cordis").Context);
58
+ resetLiveDerivedState(session: Session): void;
59
+ private warnOnce;
60
+ private resetProjectionCells;
61
+ private resetTokenMeterFold;
62
+ private readonly provider;
63
+ readBranchPrefix(id: SessionId, atSeq?: number, mode?: BranchAnchorMode, signal?: AbortSignal): Promise<BranchBoundary>;
64
+ readRawEvents(id: SessionId, signal?: AbortSignal): Promise<{
65
+ meta: SessionHeader;
66
+ events: readonly SessionEvent[];
67
+ }>;
68
+ forkFrom(sourceId: SessionId, options?: ForkFromOptions, signal?: AbortSignal): Promise<SessionId>;
69
+ rewind(id: SessionId, toBoundary: number, signal?: AbortSignal): Promise<SessionPersistenceSnapshot>;
70
+ }
71
+ export { locateTurnEnd as a, truncateLiveSession as i, SessionBranchRdbProvider as n, liveEventsOf as r, SessionBranchRdb as t };
package/dist/index.d.mts CHANGED
@@ -1,2 +1,20 @@
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 };
1
+ import { A as SessionDeletionError, D as Config, I as SCHEMA_VERSION, O as ResolvedConfig, U as SESSION_ROWS_PATH, j as SessionDeletionErrorCode, k as SessionPersistenceRdb } from "./backend-BKtJ5sO5.mjs";
2
+ import { a as locateTurnEnd, n as SessionBranchRdbProvider, r as liveEventsOf, t as SessionBranchRdb } from "./index-CUD2ZpTA.mjs";
3
+ import { Context } from "@deepseek-ai/cordis";
4
+ interface SessionRowsItem {
5
+ sessionId: string;
6
+ title: string | null;
7
+ origin: string | null;
8
+ cwd: string | null;
9
+ createdAt: number;
10
+ updatedAt: number;
11
+ archived: boolean;
12
+ workspace: string | null;
13
+ }
14
+ interface SessionRowsValue {
15
+ items: SessionRowsItem[];
16
+ total: number;
17
+ page: number;
18
+ pageSize: number;
19
+ }
20
+ export { type Config, type ResolvedConfig, SCHEMA_VERSION, SESSION_ROWS_PATH, SessionBranchRdb, SessionBranchRdbProvider, SessionDeletionError, type SessionDeletionErrorCode, SessionPersistenceRdb, SessionPersistenceRdb as default, type SessionRowsItem, type SessionRowsValue, liveEventsOf, locateTurnEnd };