@morlay/session-rdb 0.0.21-alpha.8 → 0.0.21-alpha.9

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
@@ -7,8 +7,8 @@ SQLite 或 PostgreSQL 后端。表结构、原样存储与各条流程的设计
7
7
 
8
8
  ## 配置
9
9
 
10
- 配置**就是这一行的 config**(`cordis.patch.yml` 的 `session-rdb` 行,或 profile patch /
11
- 设置页改的那份 entry config):
10
+ 配置**就是这一行的 config**(聚合层 `@morlay/better-session` 的 `session-rdb` 行、profile patch,
11
+ 或设置页改的那份 entry config):
12
12
 
13
13
  ```yaml
14
14
  - id: session-rdb
@@ -54,6 +54,18 @@ type Config =
54
54
  };
55
55
  ```
56
56
 
57
+ ### 设置页上的这一行
58
+
59
+ `Config` **整段标了 `.volatile()`**(判别式 union 只能整段标:schemastery 不许在 union 分支内部标 volatile),
60
+ 所以 host 会描述这一行,插件管理页里按 `type` 展开当前后端的字段——与上面 YAML 里的字段一一对应。
61
+
62
+ - **切后端**:`type` 是各分支的判别标签,页面上那一行给的是一个**切换控件**(`"sqlite"` ↔ `"postgres"`);选另一支
63
+ 就换成那一套字段(`path` ↔ `connectionString` / `schema`),页面结构与 host 侧校验的选支依据同一份。
64
+ - **生效方式**:settings 写完 → Loader 重装这一行(`reconcileProfilePatches`)→ 用新 config 重新建连接。所以这里
65
+ 装配时取一次快照(`this.config = config.get()`),没有运行期重连逻辑;换后端 = 换库,旧数据留在旧后端。
66
+ - 默认值都在 schema 上(`journalMode: wal`、`busyTimeout`、`schema: public`、`projectionCache` 的节流),页面上
67
+ 清空即回到这些默认。
68
+
57
69
  ## 分支能力(session-branch 闭环)
58
70
 
59
71
  除 `ctx.sessionPersistence` 外,本包还实现 `@morlay/session-branch` 的 provider
@@ -1,3 +1,3 @@
1
- import { C as usageRowOf, 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-BbleqD1M.mjs";
1
+ import { C as usageRowOf, 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-CUjKQbOR.mjs";
2
2
  import { SESSION_IMPORT_PATH, SESSION_LOG_ARTIFACT_FILENAME, parseImportZip, parseJsonlArtifact, persistImport, registerSessionImport } from "./import.mjs";
3
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,3 +1,3 @@
1
- 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-DTcvsMz7.mjs";
1
+ 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-BWVFYAh5.mjs";
2
2
  import { SESSION_IMPORT_PATH, SESSION_LOG_ARTIFACT_FILENAME, parseImportZip, parseJsonlArtifact, persistImport, registerSessionImport } from "./import.mjs";
3
3
  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,4 +1,4 @@
1
- import { d as repairRequestHeaders, h as scanRows, i as normalizeToCurrentShape, m as rowToMeta } from "./log-DTcvsMz7.mjs";
1
+ import { d as repairRequestHeaders, h as scanRows, i as normalizeToCurrentShape, m as rowToMeta } from "./log-BWVFYAh5.mjs";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import { SESSION_FORMAT_VERSION, SessionLogOffset } from "@deepseek-ai/dsh-session";
4
4
  import { createSessionFormatCatalogWithChildren } from "@deepseek-ai/dsh-session-format-catalog";
@@ -1,4 +1,4 @@
1
- import { L as SessionPersistenceRdb } from "./log-BbleqD1M.mjs";
1
+ import { tt as SessionPersistenceRdb } from "./log-CUjKQbOR.mjs";
2
2
  import { Context } from "@deepseek-ai/cordis";
3
3
  //#region src/deletion.d.ts
4
4
  declare const SESSION_DELETE_PATH = "/api/session.delete";
package/dist/deletion.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { a as registerSessionDeletion, i as SESSION_DELETE_PATH } from "./src-BWqePRA7.mjs";
1
+ import { a as registerSessionDeletion, i as SESSION_DELETE_PATH } from "./src-BZ1tffd5.mjs";
2
2
  export { SESSION_DELETE_PATH, registerSessionDeletion };
@@ -93,7 +93,7 @@ function encode(decoded) {
93
93
  return writer.flush();
94
94
  }
95
95
  //#endregion
96
- //#region ../../../node_modules/.pnpm/magic-string@1.4.1/node_modules/magic-string/dist/index.mjs
96
+ //#region ../../../node_modules/.pnpm/magic-string@1.4.2/node_modules/magic-string/dist/index.mjs
97
97
  var BitSet = class BitSet {
98
98
  constructor(arg) {
99
99
  this.bits = arg instanceof BitSet ? arg.bits.slice() : [];
@@ -187,7 +187,7 @@ var Chunk = class Chunk {
187
187
  this.outro = "";
188
188
  this.end = index;
189
189
  if (this.edited) {
190
- newChunk.edit("", false);
190
+ newChunk.edit("", false, true);
191
191
  this.content = "";
192
192
  } else this.content = originalBefore;
193
193
  newChunk.next = this.next;
@@ -1149,7 +1149,7 @@ var MagicString = class MagicString {
1149
1149
  * Content appended or prepended at positions strictly inside the range is removed with it, while
1150
1150
  * content attached at `start` or `end` is preserved — use `s.overwrite(start, end, '')` to remove
1151
1151
  * the range including its edge inserts.
1152
- * Removing the same content twice, or making removals that partially overlap, will cause an error.
1152
+ * Repeated removals and removals with partially overlapping ranges are allowed.
1153
1153
  */
1154
1154
  remove(start, end) {
1155
1155
  start = start + this.offset;
@@ -1431,10 +1431,16 @@ var MagicString = class MagicString {
1431
1431
  * @internal
1432
1432
  */
1433
1433
  _hasRemovedContent(start, end) {
1434
- let chunk = this.firstChunk;
1435
- while (chunk) {
1436
- if (chunk.content === "" && chunk.start < end && chunk.end > start) return true;
1437
- chunk = chunk.next;
1434
+ let chunk = this.byStart.get(start);
1435
+ if (!chunk) {
1436
+ chunk = this.lastSearchedChunk;
1437
+ const searchForward = start >= chunk.end;
1438
+ while (chunk && !chunk.contains(start)) chunk = searchForward ? this.byStart.get(chunk.end) : this.byEnd.get(chunk.start);
1439
+ if (chunk) this.lastSearchedChunk = chunk;
1440
+ }
1441
+ while (chunk && chunk.start < end) {
1442
+ if (chunk.content === "") return true;
1443
+ chunk = this.byStart.get(chunk.end);
1438
1444
  }
1439
1445
  return false;
1440
1446
  }
@@ -1498,6 +1504,7 @@ var MagicString = class MagicString {
1498
1504
  const stringLength = string.length;
1499
1505
  if (stringLength === 0) {
1500
1506
  for (let index = 0; index <= original.length; index += 1) {
1507
+ if (this._hasRemovedContent(index, index)) continue;
1501
1508
  const _replacement = typeof replacement === "function" ? replacement("", index, original) : expandReplacement(replacement, "", index, original, [], void 0);
1502
1509
  if (_replacement !== "") this.appendRight(index, _replacement);
1503
1510
  }
@@ -0,0 +1,8 @@
1
+ import { tt as SessionPersistenceRdb } from "./log-CUjKQbOR.mjs";
2
+ import { Context } from "@deepseek-ai/cordis";
3
+ //#region src/export.d.ts
4
+ declare const SESSION_EXPORT_PATH = "/api/session.export";
5
+ /** 导出通道:直接把会话日志打成 zip 响应体,与导入通道读同一份 artifact。 */
6
+ declare function registerSessionExport(ctx: Context, persistence: SessionPersistenceRdb): void;
7
+ //#endregion
8
+ export { SESSION_EXPORT_PATH, registerSessionExport };
@@ -0,0 +1,76 @@
1
+ import { strToU8, zip } from "fflate";
2
+ //#region src/export.ts
3
+ const SESSION_EXPORT_PATH = "/api/session.export";
4
+ /** 文件名只保留安全字符:会话 id 来自请求体,会写进 Content-Disposition。 */
5
+ function safeFilename(sessionId) {
6
+ return sessionId.replace(/[^A-Za-z0-9._-]/gu, "_");
7
+ }
8
+ /** fflate 只给回调式异步 API(同步变体被 node/no-sync 禁止)。 */
9
+ function zipBytes(files) {
10
+ return new Promise((resolve, reject) => {
11
+ zip(files, (error, data) => {
12
+ if (error === null) resolve(data);
13
+ else reject(error);
14
+ });
15
+ });
16
+ }
17
+ /** 导出通道:直接把会话日志打成 zip 响应体,与导入通道读同一份 artifact。 */
18
+ function registerSessionExport(ctx, persistence) {
19
+ ctx.inject(["webServer", "connection"], (webCtx) => {
20
+ const webServer = webCtx.webServer;
21
+ const connection = webCtx.get("connection");
22
+ return webCtx.effect(() => webServer.register({
23
+ kind: "exact",
24
+ path: SESSION_EXPORT_PATH,
25
+ handler: async (req, res) => {
26
+ const rejection = connection.requestRejection(req);
27
+ if (rejection !== void 0) {
28
+ res.writeHead(rejection);
29
+ res.end(rejection === 401 ? "unauthorized" : "forbidden");
30
+ return;
31
+ }
32
+ if (req.method !== "POST") {
33
+ res.writeHead(405, { "content-type": "application/json" });
34
+ res.end(JSON.stringify({ error: "method not allowed" }));
35
+ return;
36
+ }
37
+ const chunks = [];
38
+ for await (const chunk of req) chunks.push(chunk);
39
+ let envelope;
40
+ try {
41
+ envelope = JSON.parse(Buffer.concat(chunks).toString("utf8"));
42
+ } catch {
43
+ res.writeHead(400, { "content-type": "application/json" });
44
+ res.end(JSON.stringify({ error: "request body is not JSON" }));
45
+ return;
46
+ }
47
+ if (typeof envelope.sessionId !== "string" || envelope.sessionId === "") {
48
+ res.writeHead(400, { "content-type": "application/json" });
49
+ res.end(JSON.stringify({ error: "missing sessionId field" }));
50
+ return;
51
+ }
52
+ const sessionId = envelope.sessionId;
53
+ try {
54
+ const raw = await persistence.readRaw(sessionId);
55
+ if (raw === void 0) {
56
+ res.writeHead(404, { "content-type": "application/json" });
57
+ res.end(JSON.stringify({ error: `session "${sessionId}" not found` }));
58
+ return;
59
+ }
60
+ const zipFile = await zipBytes({ [raw.filename]: strToU8(raw.content) });
61
+ res.writeHead(200, {
62
+ "content-type": "application/zip",
63
+ "content-length": String(zipFile.byteLength),
64
+ "content-disposition": `attachment; filename="${safeFilename(sessionId)}.zip"`
65
+ });
66
+ res.end(Buffer.from(zipFile));
67
+ } catch (error) {
68
+ res.writeHead(500, { "content-type": "application/json" });
69
+ res.end(JSON.stringify({ error: error instanceof Error ? error.message : "session export failed" }));
70
+ }
71
+ }
72
+ }), `session-rdb: ${SESSION_EXPORT_PATH} route`);
73
+ });
74
+ }
75
+ //#endregion
76
+ export { SESSION_EXPORT_PATH, registerSessionExport };
package/dist/gc.d.mts ADDED
@@ -0,0 +1,8 @@
1
+ import { tt as SessionPersistenceRdb } from "./log-CUjKQbOR.mjs";
2
+ import { Context } from "@deepseek-ai/cordis";
3
+ //#region src/gc.d.ts
4
+ declare const SESSION_GC_PATH = "/api/session.gc";
5
+ /** GC 通道:停 agent → 回收孤儿事件行 → VACUUM,一次请求完成。 */
6
+ declare function registerSessionGc(ctx: Context, persistence: SessionPersistenceRdb): void;
7
+ //#endregion
8
+ export { SESSION_GC_PATH, registerSessionGc };
package/dist/gc.mjs ADDED
@@ -0,0 +1,51 @@
1
+ //#region src/gc.ts
2
+ const SESSION_GC_PATH = "/api/session.gc";
3
+ /** 让所有运行中的 agent 先退场:GC 要重写事件表,运行中的写路径必须停下来。 */
4
+ async function stopRunningAgents(ctx) {
5
+ const running = ctx.get("agents")?.list() ?? [];
6
+ for (const agent of running) agent.cancel?.({ kind: "user" }, { keepInbox: true });
7
+ await Promise.all(running.map((agent) => agent.whenIdle()));
8
+ return running.length;
9
+ }
10
+ /** GC 通道:停 agent → 回收孤儿事件行 → VACUUM,一次请求完成。 */
11
+ function registerSessionGc(ctx, persistence) {
12
+ ctx.inject(["webServer", "connection"], (webCtx) => {
13
+ const webServer = webCtx.webServer;
14
+ const connection = webCtx.get("connection");
15
+ return webCtx.effect(() => webServer.register({
16
+ kind: "exact",
17
+ path: SESSION_GC_PATH,
18
+ handler: async (req, res) => {
19
+ const rejection = connection.requestRejection(req);
20
+ if (rejection !== void 0) {
21
+ res.writeHead(rejection);
22
+ res.end(rejection === 401 ? "unauthorized" : "forbidden");
23
+ return;
24
+ }
25
+ if (req.method !== "POST") {
26
+ res.writeHead(405, { "content-type": "application/json" });
27
+ res.end(JSON.stringify({ error: "method not allowed" }));
28
+ return;
29
+ }
30
+ try {
31
+ const stoppedAgents = await stopRunningAgents(webCtx);
32
+ const orphanSessions = await persistence.collectOrphanSessions();
33
+ const orphanEvents = await persistence.collectOrphans();
34
+ await persistence.vacuum();
35
+ res.writeHead(200, { "content-type": "application/json" });
36
+ res.end(JSON.stringify({
37
+ orphanSessions,
38
+ orphanEvents,
39
+ stoppedAgents,
40
+ vacuumed: true
41
+ }));
42
+ } catch (error) {
43
+ res.writeHead(500, { "content-type": "application/json" });
44
+ res.end(JSON.stringify({ error: error instanceof Error ? error.message : "gc failed" }));
45
+ }
46
+ }
47
+ }), `session-rdb: ${SESSION_GC_PATH} route`);
48
+ });
49
+ }
50
+ //#endregion
51
+ export { SESSION_GC_PATH, registerSessionGc };
package/dist/import.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { L as SessionPersistenceRdb } from "./log-BbleqD1M.mjs";
1
+ import { tt as SessionPersistenceRdb } from "./log-CUjKQbOR.mjs";
2
2
  import { SessionStorageMetadata } from "@deepseek-ai/dsh-session-persistence";
3
3
  import { Session, SessionEvent, SessionId } from "@deepseek-ai/dsh-session";
4
4
  import { Context } from "@deepseek-ai/cordis";
package/dist/import.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { i as replaceLiveSessionLog, l as restoreCatalog } from "./branch-7eOh_tbO.mjs";
1
+ import { i as replaceLiveSessionLog, l as restoreCatalog } from "./branch-D4nVzQQM.mjs";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import { SESSION_FORMAT_VERSION, SessionLogOffset } from "@deepseek-ai/dsh-session";
4
4
  import { parseSessionFormatLogFilename } from "@deepseek-ai/dsh-session-format";
package/dist/index.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { B as SessionBranchRdbProvider, F as SessionDeletionError, G as SessionRowsValue, I as SessionDeletionErrorCode, L as SessionPersistenceRdb, N as Config, P as ProjectionCacheOptions, R as SessionPersistenceRdbInternals, U as SESSION_ROWS_PATH, V as locateTurnEnd, W as SessionRowsItem, Z as SCHEMA_VERSION, z as SessionBranchRdb } from "./log-BbleqD1M.mjs";
2
- export { Config, ProjectionCacheOptions, SCHEMA_VERSION, SESSION_ROWS_PATH, SessionBranchRdb, SessionBranchRdbProvider, SessionDeletionError, SessionDeletionErrorCode, SessionPersistenceRdb, SessionPersistenceRdb as default, SessionPersistenceRdbInternals, type SessionRowsItem, type SessionRowsValue, locateTurnEnd };
1
+ import { $ as SessionDeletionError, Q as ResolvedConfig, X as Config, Z as ProjectionCacheOptions, at as locateTurnEnd, ct as SessionRowsItem, et as SessionDeletionErrorCode, ht as SCHEMA_VERSION, it as SessionBranchRdbProvider, lt as SessionRowsValue, nt as SessionPersistenceRdbInternals, rt as SessionBranchRdb, st as SESSION_ROWS_PATH, tt as SessionPersistenceRdb } from "./log-CUjKQbOR.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 };
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { o as SCHEMA_VERSION } from "./sqlite-ChM_sHAG.mjs";
2
- import { n as SessionPersistenceRdb, r as SESSION_ROWS_PATH, t as SessionDeletionError } from "./src-BWqePRA7.mjs";
3
- import { n as SessionBranchRdbProvider, r as locateTurnEnd, t as SessionBranchRdb } from "./branch-7eOh_tbO.mjs";
1
+ import { o as SCHEMA_VERSION } from "./sqlite-SCiSnwaz.mjs";
2
+ import { n as SessionPersistenceRdb, r as SESSION_ROWS_PATH, t as SessionDeletionError } from "./src-BZ1tffd5.mjs";
3
+ import { n as SessionBranchRdbProvider, r as locateTurnEnd, t as SessionBranchRdb } from "./branch-D4nVzQQM.mjs";
4
4
  export { SCHEMA_VERSION, SESSION_ROWS_PATH, SessionBranchRdb, SessionBranchRdbProvider, SessionDeletionError, SessionPersistenceRdb, SessionPersistenceRdb as default, locateTurnEnd };
@@ -1,156 +1,6 @@
1
+ import { localDayKey } from "./usage.mjs";
1
2
  import { SessionSeq } from "@deepseek-ai/dsh-session";
2
3
  import { sessionFormatCatalog } from "@deepseek-ai/dsh-session-format-catalog";
3
- //#region src/usage.ts
4
- const SESSION_USAGE_PATH = "/api/session.usage";
5
- const ROLLING_DAYS = {
6
- "7d": 7,
7
- "30d": 30,
8
- "90d": 90
9
- };
10
- function localDayStart(now) {
11
- const start = new Date(now);
12
- start.setHours(0, 0, 0, 0);
13
- return start;
14
- }
15
- /**
16
- * 范围起点(含),不限时为 undefined。**所有起点都对齐到 host 本地时区的零点**——`day` / `week` 是
17
- * 今天 / 本周一的零点,`7d` / `30d` / `90d` 是「今天零点往前 N-1 天」(含今天共 N 个自然日)。
18
- * 对齐的意义:事件级表按毫秒过滤、会话汇总表按本地日过滤,两者因此严格等价。
19
- * @param range - 语义键。
20
- * @param now - 当前时刻。
21
- * @returns 起点毫秒时间戳,或 undefined。
22
- */
23
- function resolveUsageSince(range, now) {
24
- switch (range) {
25
- case "all": return;
26
- case "day": return localDayStart(now).getTime();
27
- case "week": {
28
- const start = localDayStart(now);
29
- start.setDate(start.getDate() - (start.getDay() + 6) % 7);
30
- return start.getTime();
31
- }
32
- default: {
33
- const start = localDayStart(now);
34
- start.setDate(start.getDate() - (ROLLING_DAYS[range] - 1));
35
- return start.getTime();
36
- }
37
- }
38
- }
39
- function parseUsageRange(value) {
40
- return value === "day" || value === "week" || value === "7d" || value === "30d" || value === "90d" ? value : "all";
41
- }
42
- /** 计入活动计数的事件类型(轮次 / 步骤 / 用户输入 / 工具调用)。 */
43
- const COUNTED_EVENT_TYPES = [
44
- "turn/start",
45
- "step/start",
46
- "user/message",
47
- "tool/call"
48
- ];
49
- /** 按事件类型把计数累加到活动指标上(表里按类型存,读的时候折成四项)。 */
50
- function addActivityCount(target, type, count) {
51
- switch (type) {
52
- case "turn/start":
53
- target.turns += count;
54
- return target;
55
- case "step/start":
56
- target.steps += count;
57
- return target;
58
- case "user/message":
59
- target.userInputs += count;
60
- return target;
61
- case "tool/call":
62
- target.toolCalls += count;
63
- return target;
64
- default: return target;
65
- }
66
- }
67
- /** 毫秒时间戳 → host 本地日(`YYYY-MM-DD`,与 SQL 的 localtime 口径一致)。 */
68
- function localDayKey(ms) {
69
- const date = new Date(ms);
70
- const pad = (value) => String(value).padStart(2, "0");
71
- return `${String(date.getFullYear())}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
72
- }
73
- function emptyTotals() {
74
- return {
75
- turns: 0,
76
- steps: 0,
77
- userInputs: 0,
78
- toolCalls: 0,
79
- inputTokens: 0,
80
- outputTokens: 0,
81
- cacheReadTokens: 0,
82
- reasoningTokens: 0,
83
- totalTokens: 0
84
- };
85
- }
86
- function addTokenTotals(target, row) {
87
- target.inputTokens += row.inputTokens;
88
- target.outputTokens += row.outputTokens;
89
- target.cacheReadTokens += row.cacheReadTokens;
90
- target.reasoningTokens += row.reasoningTokens;
91
- target.totalTokens += row.totalTokens;
92
- return target;
93
- }
94
- function addActivityTotals(target, row) {
95
- target.turns += row.turns;
96
- target.steps += row.steps;
97
- target.userInputs += row.userInputs;
98
- target.toolCalls += row.toolCalls;
99
- return target;
100
- }
101
- /** 把一行的整套指标累加进目标(后端合并「是否子代理」两组时用)。 */
102
- function addTotals(target, row) {
103
- addActivityTotals(target, row);
104
- addTokenTotals(target, row);
105
- return target;
106
- }
107
- /**
108
- * 用量统计通道:一次请求回报总览(含 subagent 拆分)、按天 × 模型的桶与按会话的行。
109
- * 聚合只算被会话引用的事件行——fork 共享行因此只计一次,已删会话留下的孤儿行不计。
110
- */
111
- function registerSessionUsage(ctx, persistence) {
112
- ctx.inject(["webServer", "connection"], (webCtx) => {
113
- const webServer = webCtx.webServer;
114
- const connection = webCtx.get("connection");
115
- return webCtx.effect(() => webServer.register({
116
- kind: "exact",
117
- path: SESSION_USAGE_PATH,
118
- handler: async (req, res) => {
119
- const rejection = connection.requestRejection(req);
120
- if (rejection !== void 0) {
121
- res.writeHead(rejection);
122
- res.end(rejection === 401 ? "unauthorized" : "forbidden");
123
- return;
124
- }
125
- if (req.method !== "POST") {
126
- res.writeHead(405, { "content-type": "application/json" });
127
- res.end(JSON.stringify({ error: "method not allowed" }));
128
- return;
129
- }
130
- const chunks = [];
131
- for await (const chunk of req) chunks.push(chunk);
132
- const raw = Buffer.concat(chunks).toString("utf8");
133
- let envelope = {};
134
- if (raw !== "") try {
135
- envelope = JSON.parse(raw);
136
- } catch {
137
- res.writeHead(400, { "content-type": "application/json" });
138
- res.end(JSON.stringify({ error: "request body is not JSON" }));
139
- return;
140
- }
141
- try {
142
- const report = await persistence.usageReport(resolveUsageSince(parseUsageRange(envelope.range), Date.now()));
143
- res.writeHead(200, { "content-type": "application/json" });
144
- res.end(JSON.stringify(report));
145
- } catch (error) {
146
- res.writeHead(500, { "content-type": "application/json" });
147
- res.end(JSON.stringify({ error: error instanceof Error ? error.message : "usage report failed" }));
148
- }
149
- }
150
- }), `session-rdb: ${SESSION_USAGE_PATH} route`);
151
- });
152
- }
153
- //#endregion
154
4
  //#region src/log.ts
155
5
  function normalizeSurfaceOp(value) {
156
6
  if (typeof value !== "object" || value === null || Array.isArray(value)) return value;
@@ -760,4 +610,4 @@ function usageRowOf(event, subagent) {
760
610
  };
761
611
  }
762
612
  //#endregion
763
- export { registerSessionUsage as A, COUNTED_EVENT_TYPES as C, addTotals as D, addTokenTotals as E, emptyTotals as O, usageRowOf as S, addActivityTotals as T, 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, localDayKey as k, 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, addActivityCount as w, toJsonlArtifact as x, syncMeteringRanges as y };
613
+ 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 };
@@ -5,6 +5,7 @@ import { BranchAnchorMode, BranchBoundary, ForkFromOptions, SessionBranch, Sessi
5
5
  import { KvUnit } from "@deepseek-ai/dsh-storage";
6
6
  import { Context, Service } from "@deepseek-ai/cordis";
7
7
  import { SessionId as SessionId$1 } from "@deepseek-ai/dsh-session/types";
8
+ import { Volatile } from "@deepseek-ai/cosmokit";
8
9
  //#region ../../../node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/core/json-schema.d.cts
9
10
  type _JSONSchema = boolean | JSONSchema;
10
11
  type SchemaType = "object" | "array" | "string" | "number" | "boolean" | "null" | "integer";
@@ -3285,16 +3286,15 @@ declare class WorkspaceRegistry extends Service {
3285
3286
  * Initialize the default Workspace only while both the registry and Session
3286
3287
  * history are empty. Repeated requests reuse its durable identity; deleting
3287
3288
  * that registration permanently disables automatic creation.
3288
- * @param resolveDirectory - resolve the absolute directory and initial title;
3289
- * called only for eligible creation, inside the registry mutation queue.
3290
- * Missing directories are created recursively before registration.
3289
+ * @param resolveDirectory - resolve the absolute directory; called only for
3290
+ * eligible creation, inside the registry mutation queue. Missing directories
3291
+ * are created recursively before registration, and the initial title is the
3292
+ * requested directory's own final segment — not the canonical one, so a
3293
+ * symlink at that path does not retitle the Workspace after its target.
3291
3294
  * After resolution, caller cancellation does not roll back creation or registration.
3292
3295
  * @returns the initialized Workspace, or undefined when automatic creation is ineligible.
3293
3296
  */
3294
- initializeDefault(resolveDirectory: () => Promise<{
3295
- path: string;
3296
- title: string;
3297
- }>): Promise<Workspace | undefined>;
3297
+ initializeDefault(resolveDirectory: () => Promise<string>): Promise<Workspace | undefined>;
3298
3298
  /**
3299
3299
  * Look up a workspace by id.
3300
3300
  * @param id - Workspace id.
@@ -3610,15 +3610,17 @@ interface ProjectionCacheOptions {
3610
3610
  type Config$1 = {
3611
3611
  type: "sqlite";
3612
3612
  path: string;
3613
- journalMode?: JournalMode;
3614
- busyTimeout?: number;
3615
- projectionCache?: ProjectionCacheOptions;
3613
+ journalMode?: JournalMode | undefined;
3614
+ busyTimeout?: number | undefined;
3615
+ projectionCache?: ProjectionCacheOptions | undefined;
3616
3616
  } | {
3617
3617
  type: "postgres";
3618
3618
  connectionString: string;
3619
- schema?: string;
3620
- projectionCache?: ProjectionCacheOptions;
3619
+ schema?: string | undefined;
3620
+ projectionCache?: ProjectionCacheOptions | undefined;
3621
3621
  };
3622
+ /** schema 解析之后的形状:整段 Config 是 **volatile 稳定引用**(页面可编辑),读它要过 `.get()`。 */
3623
+ type ResolvedConfig = Volatile<Config$1>;
3622
3624
  type SessionDeletionErrorCode = "SESSION_NOT_FOUND" | "SESSION_NOT_ARCHIVED" | "SESSION_LIVE";
3623
3625
  declare class SessionDeletionError extends Error {
3624
3626
  readonly code: SessionDeletionErrorCode;
@@ -3692,9 +3694,8 @@ declare class RdbSessionHandle implements SessionHandle {
3692
3694
  static readonly LIVE_WRITE_BATCH_MAX_DELAY_MS = 200;
3693
3695
  }
3694
3696
  declare class SessionPersistenceRdb extends SessionPersistence {
3695
- config: Config$1;
3696
3697
  static inject: string[];
3697
- static Config: z<Config$1>;
3698
+ static Config: z<Config$1, ResolvedConfig>;
3698
3699
  readonly name = "session-rdb";
3699
3700
  readonly tracker: RdbBackendTracker;
3700
3701
  private readonly backend;
@@ -3704,7 +3705,9 @@ declare class SessionPersistenceRdb extends SessionPersistence {
3704
3705
  private readonly reuseEventIds;
3705
3706
  private readonly liveBuffers;
3706
3707
  private readonly liveReady;
3707
- constructor(ctx: Context, config: Config$1, injectedBackend?: Backend);
3708
+ /** 本次装配用的配置快照(整段 Config 是 volatile,重挂会给新的)。 */
3709
+ readonly config: Config$1;
3710
+ constructor(ctx: Context, config: ResolvedConfig, injectedBackend?: Backend);
3708
3711
  private init;
3709
3712
  create(header: SessionHeader, options?: SessionPersistenceCreateOptions): Promise<SessionHandle>;
3710
3713
  open(id: SessionId, access: SessionAccess, options?: SessionPersistenceOpenOptions): Promise<SessionHandle>;
@@ -3781,6 +3784,21 @@ declare class SessionPersistenceRdb extends SessionPersistence {
3781
3784
  }
3782
3785
  //#endregion
3783
3786
  //#region src/usage.d.ts
3787
+ declare const SESSION_USAGE_PATH = "/api/session.usage";
3788
+ /**
3789
+ * 时间范围的语义键:`all` 不限;`day` / `week` 是**本地自然日 / 自然周**(周一起算);
3790
+ * `7d` / `30d` / `90d` 是**最近 N 个自然日**(含今天)。
3791
+ */
3792
+ type UsageRangeKey = "all" | "day" | "week" | "7d" | "30d" | "90d";
3793
+ /**
3794
+ * 范围起点(含),不限时为 undefined。**所有起点都对齐到 host 本地时区的零点**——`day` / `week` 是
3795
+ * 今天 / 本周一的零点,`7d` / `30d` / `90d` 是「今天零点往前 N-1 天」(含今天共 N 个自然日)。
3796
+ * 对齐的意义:事件级表按毫秒过滤、会话汇总表按本地日过滤,两者因此严格等价。
3797
+ * @param range - 语义键。
3798
+ * @param now - 当前时刻。
3799
+ * @returns 起点毫秒时间戳,或 undefined。
3800
+ */
3801
+ declare function resolveUsageSince(range: UsageRangeKey, now: number): number | undefined;
3784
3802
  /** 一段 token 用量(字段直接取事件里模型报的 usage)。 */
3785
3803
  interface UsageTokenTotals {
3786
3804
  inputTokens: number;
@@ -3826,6 +3844,24 @@ interface UsageAggregate {
3826
3844
  buckets: UsageBucket[];
3827
3845
  sessions: UsageSessionRow[];
3828
3846
  }
3847
+ /** 一次统计请求的完整回报。 */
3848
+ type SessionUsageReport = UsageAggregate;
3849
+ /** 计入活动计数的事件类型(轮次 / 步骤 / 用户输入 / 工具调用)。 */
3850
+ declare const COUNTED_EVENT_TYPES: readonly ["turn/start", "step/start", "user/message", "tool/call"];
3851
+ /** 按事件类型把计数累加到活动指标上(表里按类型存,读的时候折成四项)。 */
3852
+ declare function addActivityCount(target: UsageActivityTotals, type: string, count: number): UsageActivityTotals;
3853
+ /** 毫秒时间戳 → host 本地日(`YYYY-MM-DD`,与 SQL 的 localtime 口径一致)。 */
3854
+ declare function localDayKey(ms: number): string;
3855
+ declare function emptyTotals(): UsageTotals;
3856
+ declare function addTokenTotals(target: UsageTokenTotals, row: UsageTokenTotals): UsageTokenTotals;
3857
+ declare function addActivityTotals(target: UsageActivityTotals, row: UsageActivityTotals): UsageActivityTotals;
3858
+ /** 把一行的整套指标累加进目标(后端合并「是否子代理」两组时用)。 */
3859
+ declare function addTotals(target: UsageTotals, row: UsageTotals): UsageTotals;
3860
+ /**
3861
+ * 用量统计通道:一次请求回报总览(含 subagent 拆分)、按天 × 模型的桶与按会话的行。
3862
+ * 聚合只算被会话引用的事件行——fork 共享行因此只计一次,已删会话留下的孤儿行不计。
3863
+ */
3864
+ declare function registerSessionUsage(ctx: Context, persistence: SessionPersistenceRdb): void;
3829
3865
  //#endregion
3830
3866
  //#region src/backend.d.ts
3831
3867
  interface SessionRow {
@@ -4117,4 +4153,4 @@ declare function usageRowOf(event: {
4117
4153
  fData: string;
4118
4154
  }, subagent: boolean): EventUsageRow | undefined;
4119
4155
  //#endregion
4120
- export { eventDimensions as $, SessionRow as A, SessionBranchRdbProvider as B, usageRowOf as C, SessionCountBucket as D, EventRow as E, SessionDeletionError as F, SessionRowsValue as G, truncateLiveSession as H, SessionDeletionErrorCode as I, EventKind as J, DEFAULT_BUSY_TIMEOUT_MS as K, SessionPersistenceRdb as L, UsageAggregate as M, Config$1 as N, SessionListRowsPage as O, ProjectionCacheOptions as P, SESSION_PERSISTENCE_SQLITE_APPLICATION_ID as Q, SessionPersistenceRdbInternals as R, toJsonlArtifact as S, BackendTx as T, SESSION_ROWS_PATH as U, locateTurnEnd as V, SessionRowsItem as W, JournalMode as X, EventRole as Y, SCHEMA_VERSION as Z, sealOwnEvents as _, normalizeToCurrentShape as a, tSessionCounts as at, syncMeteringRanges as b, renameLegacyPtcEvents as c, tSessionUsage as ct, repairReadView as d, tWorkspaceSessions as dt, eventKind as et, repairRequestHeaders as f, tWorkspaceState as ft, scanRows as g, rowToMeta as h, StorageRepository as ht, needsShapeAdoption as i, tSchemaMeta as it, SessionUsageBucket as j, SessionListRowsQuery as k, repairAssistantSettlement as l, tSessions as lt, rowToEvent as m, WriteGuard as mt, OWN_EVENT_TYPES as n, tEvents as nt, orphanInboxSpliceSeqs as o, tSessionEvents as ot, repairSurfaceOps as p, tWorkspaces as pt, EVENT_ENCODING as q, findSurfaceRepairs as r, tPersistenceState as rt, recomputeReplaceProvenance as s, tSessionProjcacheRows as st, EventUsageRow as t, tEventUsage as tt, repairOrphanInboxSplices as u, tStorageUnits as ut, sessionConflictRow as v, Backend as w, titleOfEventData as x, sessionInsertRow as y, SessionBranchRdb as z };
4156
+ export { SessionDeletionError as $, SessionRow as A, tWorkspaceState as At, UsageTokenTotals as B, usageRowOf as C, tSessionCounts as Ct, SessionCountBucket as D, tSessions as Dt, EventRow as E, tSessionUsage as Et, UsageActivityTotals as F, addTotals as G, addActivityCount as H, UsageAggregate as I, registerSessionUsage as J, emptyTotals as K, UsageBucket as L, COUNTED_EVENT_TYPES as M, WriteGuard as Mt, SESSION_USAGE_PATH as N, StorageRepository as Nt, SessionListRowsPage as O, tStorageUnits as Ot, SessionUsageReport as P, ResolvedConfig as Q, UsageRangeKey as R, toJsonlArtifact as S, tSchemaMeta as St, BackendTx as T, tSessionProjcacheRows as Tt, addActivityTotals as U, UsageTotals as V, addTokenTotals as W, Config$1 as X, resolveUsageSince as Y, ProjectionCacheOptions as Z, sealOwnEvents as _, eventDimensions as _t, normalizeToCurrentShape as a, locateTurnEnd as at, syncMeteringRanges as b, tEvents as bt, renameLegacyPtcEvents as c, SessionRowsItem as ct, repairReadView as d, EVENT_ENCODING as dt, SessionDeletionErrorCode as et, repairRequestHeaders as f, EventKind as ft, scanRows as g, SESSION_PERSISTENCE_SQLITE_APPLICATION_ID as gt, rowToMeta as h, SCHEMA_VERSION as ht, needsShapeAdoption as i, SessionBranchRdbProvider as it, SessionUsageBucket as j, tWorkspaces as jt, SessionListRowsQuery as k, tWorkspaceSessions as kt, repairAssistantSettlement as l, SessionRowsValue as lt, rowToEvent as m, JournalMode as mt, OWN_EVENT_TYPES as n, SessionPersistenceRdbInternals as nt, orphanInboxSpliceSeqs as o, truncateLiveSession as ot, repairSurfaceOps as p, EventRole as pt, localDayKey as q, findSurfaceRepairs as r, SessionBranchRdb as rt, recomputeReplaceProvenance as s, SESSION_ROWS_PATH as st, EventUsageRow as t, SessionPersistenceRdb as tt, repairOrphanInboxSplices as u, DEFAULT_BUSY_TIMEOUT_MS as ut, sessionConflictRow as v, eventKind as vt, Backend as w, tSessionEvents as wt, titleOfEventData as x, tPersistenceState as xt, sessionInsertRow as y, tEventUsage as yt, UsageSessionRow as z };
@@ -1,4 +1,5 @@
1
- import { C as COUNTED_EVENT_TYPES, D as addTotals, E as addTokenTotals, O as emptyTotals, T as addActivityTotals, _ as sessionConflictRow, b as titleOfEventData, k as localDayKey, v as sessionInsertRow } from "./log-DTcvsMz7.mjs";
1
+ import { COUNTED_EVENT_TYPES, addActivityTotals, addTokenTotals, addTotals, emptyTotals, localDayKey } from "./usage.mjs";
2
+ import { _ as sessionConflictRow, b as titleOfEventData, v as sessionInsertRow } from "./log-BWVFYAh5.mjs";
2
3
  import { randomUUID } from "node:crypto";
3
4
  import { SESSION_FORMAT_VERSION, SessionId, SessionLogOffset, SessionSeq } from "@deepseek-ai/dsh-session";
4
5
  import { and, desc, eq, gte, inArray, isNotNull, lt, notInArray, sql } from "drizzle-orm";