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

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 (37) hide show
  1. package/README.md +17 -10
  2. package/dist/artifact.d.mts +2 -101
  3. package/dist/artifact.mjs +2 -2
  4. package/dist/{branch-BjUR6wfe.mjs → branch-7eOh_tbO.mjs} +3 -3
  5. package/dist/deletion.d.mts +1 -1
  6. package/dist/deletion.mjs +1 -1
  7. package/dist/import.d.mts +1 -1
  8. package/dist/import.mjs +1 -1
  9. package/dist/index.d.mts +1 -1
  10. package/dist/index.mjs +3 -3
  11. package/dist/{backend-E0bmTXpz.d.mts → log-BbleqD1M.d.mts} +171 -24
  12. package/dist/{log-qCNkDjh5.mjs → log-DTcvsMz7.mjs} +189 -12
  13. package/dist/{sqlite-zcXVyMxr.mjs → sqlite-ChM_sHAG.mjs} +320 -259
  14. package/dist/{src-B9X8BKyh.mjs → src-BWqePRA7.mjs} +290 -162
  15. package/dist/storage.d.mts +20 -7
  16. package/dist/storage.mjs +2 -2
  17. package/dist/testing.d.mts +1 -1
  18. package/dist/testing.mjs +1 -1
  19. package/drizzle/postgres/20260923120000_v3_usage_materialized/migration.sql +48 -0
  20. package/drizzle/sqlite/20260923120000_v3_usage_materialized/migration.sql +48 -0
  21. package/package.json +20 -20
  22. package/src/backend.ts +41 -17
  23. package/src/branch.ts +4 -4
  24. package/src/drizzle/postgres-v3.ts +2 -0
  25. package/src/drizzle/sqlite-v3.ts +2 -0
  26. package/src/entities/v3/event-usage.ts +8 -1
  27. package/src/entities/v3/index.ts +8 -0
  28. package/src/entities/v3/session-counts.ts +23 -0
  29. package/src/entities/v3/session-usage.ts +29 -0
  30. package/src/index.ts +109 -43
  31. package/src/log.ts +58 -17
  32. package/src/postgres.ts +248 -149
  33. package/src/rows.ts +2 -1
  34. package/src/schema.ts +4 -0
  35. package/src/sqlite.ts +263 -124
  36. package/src/storage-takeover/repository.ts +1 -4
  37. package/src/usage.ts +10 -6
package/README.md CHANGED
@@ -80,10 +80,10 @@ web 模式经 `POST /api/session.delete`(body `{ sessionId }`)暴露,状
80
80
 
81
81
  ## 会话列表(两条路)
82
82
 
83
- | 用途 | 接缝 | 内容 |
84
- | --------------------- | --------------------------------------------------------------------------------------------------------------- | ------------------------------------------------ |
85
- | 官方 UI(上游 `ui-*`)| `session/list` → `ctx.sessionQuery.listSessions`(本包接管,**默认排除已归档**) | 未归档的可见语料,标题走投影(`projections.title`) |
86
- | 我们自己的管理面 | `POST /api/session.rows`(本包路由) | **完整语料(含归档)** + 标题 + 最后活动时间 + 工作区归属,**搜索 / 子代理过滤 / 分页都在后端** |
83
+ | 用途 | 接缝 | 内容 |
84
+ | ---------------------- | -------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
85
+ | 官方 UI(上游 `ui-*`) | `session/list` → `ctx.sessionQuery.listSessions`(本包接管,**默认排除已归档**) | 未归档的可见语料,标题走投影(`projections.title`) |
86
+ | 我们自己的管理面 | `POST /api/session.rows`(本包路由) | **完整语料(含归档)** + 标题 + 最后活动时间 + 工作区归属,**搜索 / 子代理过滤 / 分页都在后端** |
87
87
 
88
88
  排除归档是本部署的策略:归档是「收起来」的会话,不该混进日常列表与搜索。管理动作(取消归档 / 删除 /
89
89
  导入)需要完整集合,所以另给一条自有路由,两条路不混。决策与取舍见
@@ -118,13 +118,20 @@ subagent 会话**(`origin = 'subagent'`、父不在表里;有 open handle /
118
118
  ## 用量统计
119
119
 
120
120
  `POST /api/session.usage` 一次回报三份数据:`totals`(含 `subagent` / `human` 拆分)、按
121
- 天 × provider/model × subagent 的桶、按会话的行。
121
+ 天 × provider/model × subagent 的桶、按会话的行(活动计数:轮次 / 步骤 / 用户输入 / 工具调用)。
122
122
 
123
- 用量写在**专用表 `t_event_usage`**(写路径顺带记录,一条 `assistant/message` 事件行一行),统计只读它,
124
- 不再逐行解析事件 JSON——真实库实测 **1.6s → ~140ms**。老库在首次打开时**一次性回填**(表为空才跑,
125
- 约 2.2s / 1.99 万行),两种 `f_data` 结构都认。口径不变:只算被会话引用的事件行(fork 共享行计一次、
126
- 孤儿行不计),GC 顺带回收不再有事件行的用量行。口径与取舍见
127
- [ADR-用量统计走专用用量日志表](.agents/adrs/20260918-用量统计走专用用量日志表.md)。
123
+ 统计读侧只读三张**可销毁重建的派生表**,读侧维度(会话 / 模型 / 本地日 / 是否被引用 / 是否子代理)
124
+ 全部在写入时物化,查询不做 `EXISTS`、也不回连事件表:
125
+
126
+ - `t_event_usage`——一条 `assistant/message` 事件行一行(去重口径的总量与「天 × 模型」桶读它);
127
+ - `t_session_usage`——会话 × 本地日 × 模型的 token 汇总(按会话的行读它,含 fork 继承前缀);
128
+ - `t_session_counts`——会话 × 本地日的四项活动计数。
129
+
130
+ 写入时在事务内落用量行、事务后**旁路累加**两张汇总表;rewind / fork / 删除会话之后按会话重算并全量重算
131
+ 引用标记;表为空(首次 / 迁移后 / 手动清空)在打开库时全量回填——迁移对这三张表先删后建。真实库实测
132
+ 单次统计从 **1.6s → ~140ms → ~30ms**(口径不变,旧口径逐项交叉核对一致),升级后首次打开付一次回填
133
+ (2.4s / 3.09 万用量行,此后不再发生)。口径与取舍见
134
+ [ADR-统计衍生表物化归属与汇总](.agents/adrs/20260923-统计衍生表物化归属与汇总.md)。
128
135
 
129
136
  ## 查询接管(`ctx.sessionQuery`)
130
137
 
@@ -1,102 +1,3 @@
1
- import { i as EventRow, s as SessionRow } from "./backend-E0bmTXpz.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-BbleqD1M.mjs";
2
2
  import { SESSION_IMPORT_PATH, SESSION_LOG_ARTIFACT_FILENAME, parseImportZip, parseJsonlArtifact, persistImport, registerSessionImport } from "./import.mjs";
3
- import { SessionStorageMetadata } from "@deepseek-ai/dsh-session-persistence";
4
- import { SessionEvent, SessionHeader } from "@deepseek-ai/dsh-session";
5
- //#region src/log.d.ts
6
- declare function rowToMeta(row: SessionRow): SessionHeader;
7
- declare function sessionInsertRow(storage: SessionStorageMetadata, incarnation: string): {
8
- fSessionId: string;
9
- fHeadEventId: string;
10
- fHeadSequence: number;
11
- fVersion: number;
12
- fCreatedAt: number;
13
- fCwd: string | null;
14
- fParentSession: string | null;
15
- fSeedLength: number | null;
16
- fOrigin: string | null;
17
- fDelegationDepth: number | null;
18
- fAgentPreset: string | null;
19
- fIncarnation: string;
20
- fRevision: number;
21
- };
22
- declare function sessionConflictRow(storage: SessionStorageMetadata): {
23
- fVersion: number;
24
- fCreatedAt: number;
25
- fCwd: string | null;
26
- fParentSession: string | null;
27
- fSeedLength: number | null;
28
- fOrigin: string | null;
29
- fDelegationDepth: number | null;
30
- fAgentPreset: string | null;
31
- };
32
- declare function rowToEvent(row: EventRow): SessionEvent;
33
- declare function recomputeReplaceProvenance(events: SessionEvent[]): void;
34
- declare function findSurfaceRepairs(events: readonly SessionEvent[]): {
35
- degradeToAppend: Set<number>;
36
- addAppendMarker: Set<number>;
37
- clearSurfaceOp: Set<number>;
38
- clampEnd: Map<number, number>;
39
- };
40
- declare function repairSurfaceOps(events: SessionEvent[]): void;
41
- declare function repairAssistantSettlement(events: SessionEvent[]): void;
42
- declare function repairRequestHeaders(events: SessionEvent[]): void;
43
- declare function syncMeteringRanges(events: SessionEvent[]): void;
44
- declare function renameLegacyPtcEvents(events: SessionEvent[]): void;
45
- /**
46
- * 把**旧代(v3 及更早)的消息形状**归一到当前格式(v4):迁移链(严格)拒绝那些形状时,读路径仍要
47
- * 能把它们读出来——只动字段与标记,不改坐标、不改内容。
48
- *
49
- * 三件事:
50
- * - `system/message` 的 source:`{ kind: 'plugin', plugin: '@deepseek-ai/dsh-system-prompt' }` → `{ kind: 'system-prompt' }`
51
- * (v4 起 system 消息只认这个 kind);
52
- * - `tool/result` 的消息:v3 是 user 角色、结果块包在 `content[0]`;v4 是 `role: 'tool'` + 顶层
53
- * `toolCallId` + `content` 就是结果块;
54
- * - 本仓库自己写过、上游已删除的事件类型(见 `LEGACY_OWN_EVENT_TYPES`)标 `ignorable: true`——v4 的
55
- * 校验对未知类型只接受可忽略信封。
56
- *
57
- * 只认这张白名单:**其它未知类型保持 fail loud**(那是「数据来自更新的 harness」的信号,不能吞掉)。
58
- */
59
- declare function normalizeToCurrentShape(events: SessionEvent[]): void;
60
- /**
61
- * 本仓库写过、后来连同机制一起删除的事件类型:读的时候标 `ignorable: true` 让校验跳过它们。
62
- * 新增历史类型时登记到这里——**不要**用「所有未知类型」代替(那会掩盖更新版本的未知事件)。
63
- */
64
- declare const LEGACY_OWN_EVENT_TYPES: ReadonlySet<string>;
65
- /** 行里是否还带着旧代消息形状(`session-branch/version` 这类自造事件也算)——写路径曾把回退视图的结果
66
- * 以当前版本号落库,所以**版本号不可信**,读之前要按内容判一次。 */
67
- declare function hasLegacyShape(events: readonly SessionEvent[]): boolean;
68
- declare function repairReadView(events: SessionEvent[]): void;
69
- declare function orphanInboxSpliceSeqs(events: readonly SessionEvent[]): Set<number>;
70
- declare function repairOrphanInboxSplices(events: SessionEvent[]): void;
71
- declare function scanRows(rows: readonly EventRow[], base?: number): {
72
- preserved: SessionEvent[];
73
- tornFrom?: number;
74
- };
75
- declare function toJsonlArtifact(meta: SessionHeader, inheritedEventCount: number, events: readonly SessionEvent[]): string;
76
- declare function titleOfEventData(data: string): string | undefined;
77
- /** 用量行的列(`t_event_usage`)。 */
78
- interface EventUsageRow {
79
- fEventId: string;
80
- fCreatedAt: number;
81
- fProvider: string | null;
82
- fModel: string | null;
83
- fInputTokens: number;
84
- fOutputTokens: number;
85
- fCacheReadTokens: number;
86
- fReasoningTokens: number;
87
- fTotalTokens: number;
88
- }
89
- /**
90
- * 一条事件行的用量(只有带 `usage` 的 `assistant/message` 才有):写路径据此记录
91
- * `t_event_usage`,统计因此不必逐行解析 JSON。两种 `f_data` 结构(带信封 / 老格式)都认。
92
- * @param event - 待写入的事件行。
93
- * @returns 用量行,或该事件没有用量时的 undefined。
94
- */
95
- declare function usageRowOf(event: {
96
- fEventId: string;
97
- fCreatedAt: number;
98
- fType: string;
99
- fData: string;
100
- }): EventUsageRow | undefined;
101
- //#endregion
102
- export { EventUsageRow, LEGACY_OWN_EVENT_TYPES, SESSION_IMPORT_PATH, SESSION_LOG_ARTIFACT_FILENAME, findSurfaceRepairs, hasLegacyShape, normalizeToCurrentShape, orphanInboxSpliceSeqs, parseImportZip, parseJsonlArtifact, persistImport, recomputeReplaceProvenance, registerSessionImport, renameLegacyPtcEvents, repairAssistantSettlement, repairOrphanInboxSplices, repairReadView, repairRequestHeaders, repairSurfaceOps, rowToEvent, rowToMeta, scanRows, sessionConflictRow, sessionInsertRow, syncMeteringRanges, titleOfEventData, toJsonlArtifact, usageRowOf };
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 { _ as sessionInsertRow, a as orphanInboxSpliceSeqs, b as toJsonlArtifact, c as repairAssistantSettlement, d as repairRequestHeaders, f as repairSurfaceOps, g as sessionConflictRow, h as scanRows, i as normalizeToCurrentShape, l as repairOrphanInboxSplices, m as rowToMeta, n as findSurfaceRepairs, o as recomputeReplaceProvenance, p as rowToEvent, r as hasLegacyShape, s as renameLegacyPtcEvents, t as LEGACY_OWN_EVENT_TYPES, u as repairReadView, v as syncMeteringRanges, x as usageRowOf, y as titleOfEventData } from "./log-qCNkDjh5.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-DTcvsMz7.mjs";
2
2
  import { SESSION_IMPORT_PATH, SESSION_LOG_ARTIFACT_FILENAME, parseImportZip, parseJsonlArtifact, persistImport, registerSessionImport } from "./import.mjs";
3
- export { LEGACY_OWN_EVENT_TYPES, SESSION_IMPORT_PATH, SESSION_LOG_ARTIFACT_FILENAME, findSurfaceRepairs, hasLegacyShape, normalizeToCurrentShape, orphanInboxSpliceSeqs, parseImportZip, parseJsonlArtifact, persistImport, recomputeReplaceProvenance, registerSessionImport, renameLegacyPtcEvents, repairAssistantSettlement, repairOrphanInboxSplices, repairReadView, repairRequestHeaders, repairSurfaceOps, rowToEvent, rowToMeta, scanRows, sessionConflictRow, sessionInsertRow, syncMeteringRanges, titleOfEventData, toJsonlArtifact, usageRowOf };
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-qCNkDjh5.mjs";
1
+ import { d as repairRequestHeaders, h as scanRows, i as normalizeToCurrentShape, m as rowToMeta } from "./log-DTcvsMz7.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";
@@ -217,7 +217,7 @@ var SessionBranchRdbProvider = class {
217
217
  internals.dropReuseEventIds(childId);
218
218
  throw error;
219
219
  }
220
- await this.persistence.rebuildEventCounts(childId);
220
+ await this.persistence.rebuildSessionStats(childId);
221
221
  return childId;
222
222
  }
223
223
  async rewind(id, toBoundary, signal) {
@@ -290,7 +290,7 @@ var SessionBranchRdbProvider = class {
290
290
  headSeq: keepLength - 1,
291
291
  snapshotEvents: () => []
292
292
  });
293
- await this.persistence.rebuildEventCounts(id);
293
+ await this.persistence.rebuildSessionStats(id);
294
294
  return {
295
295
  header: rowToMeta(row),
296
296
  revision: await internals.readStoredRevision(id)
@@ -1,4 +1,4 @@
1
- import { p as SessionPersistenceRdb } from "./backend-E0bmTXpz.mjs";
1
+ import { L as SessionPersistenceRdb } from "./log-BbleqD1M.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-B9X8BKyh.mjs";
1
+ import { a as registerSessionDeletion, i as SESSION_DELETE_PATH } from "./src-BWqePRA7.mjs";
2
2
  export { SESSION_DELETE_PATH, registerSessionDeletion };
package/dist/import.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { p as SessionPersistenceRdb } from "./backend-E0bmTXpz.mjs";
1
+ import { L as SessionPersistenceRdb } from "./log-BbleqD1M.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-BjUR6wfe.mjs";
1
+ import { i as replaceLiveSessionLog, l as restoreCatalog } from "./branch-7eOh_tbO.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 { D as SCHEMA_VERSION, _ as locateTurnEnd, b as SessionRowsItem, d as SessionDeletionError, f as SessionDeletionErrorCode, g as SessionBranchRdbProvider, h as SessionBranchRdb, l as Config, m as SessionPersistenceRdbInternals, p as SessionPersistenceRdb, u as ProjectionCacheOptions, x as SessionRowsValue, y as SESSION_ROWS_PATH } from "./backend-E0bmTXpz.mjs";
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
2
  export { Config, ProjectionCacheOptions, 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 { p as SCHEMA_VERSION } from "./sqlite-zcXVyMxr.mjs";
2
- import { n as SessionPersistenceRdb, r as SESSION_ROWS_PATH, t as SessionDeletionError } from "./src-B9X8BKyh.mjs";
3
- import { n as SessionBranchRdbProvider, r as locateTurnEnd, t as SessionBranchRdb } from "./branch-BjUR6wfe.mjs";
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";
4
4
  export { SCHEMA_VERSION, SESSION_ROWS_PATH, SessionBranchRdb, SessionBranchRdbProvider, SessionDeletionError, SessionPersistenceRdb, SessionPersistenceRdb as default, locateTurnEnd };
@@ -3478,6 +3478,8 @@ declare const tWorkspaceSessions: any;
3478
3478
  declare const tWorkspaceState: any;
3479
3479
  declare const tSessionProjcacheRows: any;
3480
3480
  declare const tEventUsage: any;
3481
+ declare const tSessionUsage: any;
3482
+ declare const tSessionCounts: any;
3481
3483
  type JournalMode = "wal" | "delete" | "truncate" | "persist";
3482
3484
  declare const DEFAULT_BUSY_TIMEOUT_MS = 5000;
3483
3485
  type EventKind = "message" | "thinking" | "turn" | "tool" | "request" | "config" | "audit" | "lifecycle" | "inbox" | "compaction" | "llm" | "subagent" | "team" | "workflow" | "goal" | "schedule" | "todo" | "web";
@@ -3740,14 +3742,18 @@ declare class SessionPersistenceRdb extends SessionPersistence {
3740
3742
  migrated: boolean;
3741
3743
  } | undefined>;
3742
3744
  /**
3743
- * 活动计数**旁路累加**:不在写事务里、失败只 warn——`t_event_counts` 是可销毁重建的派生表,
3744
- * 丢几次累加不影响可用性(需要时用 `rebuildEventCounts` 重算)。
3745
+ * 统计衍生表的**旁路累加**:不在写事务里、失败只 warn——两张会话汇总表都是可销毁重建的
3746
+ * 派生表,丢几次累加不影响可用性(需要时用 `rebuildSessionStats` 重算)。
3747
+ * token 走写路径已折好的用量行,活动计数走本批事件类型。
3745
3748
  */
3746
- private recordEventCounts;
3747
- /** 重算一个会话的活动计数(rewind / fork 之后;best-effort,同 `recordEventCounts`)。 */
3748
- rebuildEventCounts(id: SessionId): Promise<void>;
3749
- /** 会话删除时清掉它的活动计数行(best-effort)。 */
3750
- private deleteEventCounts;
3749
+ private recordSessionStats;
3750
+ /**
3751
+ * 重算一个会话的统计(rewind / fork 之后;best-effort,同 `recordSessionStats`):
3752
+ * 两张汇总表按会话重算,用量行的引用标记全量重算(引用可能跨会话消失)。
3753
+ */
3754
+ rebuildSessionStats(id: SessionId): Promise<void>;
3755
+ /** 会话删除时清掉它的汇总行,并重算用量行的引用标记(被删会话引用的行要立刻退出统计)。 */
3756
+ private dropSessionStats;
3751
3757
  private rewriteMigratedLog;
3752
3758
  listSnapshots(signal?: AbortSignal): Promise<Array<SessionPersistenceSnapshot & {
3753
3759
  inheritedEventCount: number;
@@ -3870,7 +3876,11 @@ interface BackendTx {
3870
3876
  getSeedLength(id: SessionId): Promise<number | null>;
3871
3877
  updateSeedLength(id: SessionId, seedLength: number): Promise<void>;
3872
3878
  refreshTitle(id: SessionId): Promise<void>;
3873
- insertEvents(events: EventInsert[]): Promise<void>;
3879
+ /**
3880
+ * 插入事件行与它们的用量行(`usageRows` 由写路径一次折好,读侧维度已物化在行上)。
3881
+ * 这一批事件与桥接行同事务,提交即「被引用」。
3882
+ */
3883
+ insertEvents(events: EventInsert[], usageRows: readonly EventUsageRow[]): Promise<void>;
3874
3884
  insertBridges(rows: Array<{
3875
3885
  fSessionId: SessionId;
3876
3886
  fEventId: string;
@@ -3922,11 +3932,15 @@ interface SessionListRowsPage {
3922
3932
  /** 过滤后的总数(分页前的),前端据此算页数。 */
3923
3933
  total: number;
3924
3934
  }
3925
- /** 活动计数的一个桶:本地日 + 事件类型 + 计数。 */
3926
- interface EventCountBucket {
3935
+ /** 活动计数的一个桶:本地日 + 四项计数(轮次 / 步骤 / 用户输入 / 工具调用)。 */
3936
+ interface SessionCountBucket extends UsageActivityTotals {
3927
3937
  day: string;
3928
- type: string;
3929
- count: number;
3938
+ }
3939
+ /** 用量累加的一个桶:本地日 + 模型归属 + 五个 token 列。 */
3940
+ interface SessionUsageBucket extends UsageTokenTotals {
3941
+ day: string;
3942
+ provider: string | null;
3943
+ model: string | null;
3930
3944
  }
3931
3945
  interface Backend {
3932
3946
  readonly kind: "sqlite" | "postgres";
@@ -3944,30 +3958,163 @@ interface Backend {
3944
3958
  /** 父会话已不存在(或没有父)的 subagent 会话:父被删后它们成了孤儿。 */
3945
3959
  listOrphanSubagentSessions(): Promise<SessionId[]>;
3946
3960
  /**
3947
- * 用量统计的原始聚合:token 用量读 `t_event_usage`、活动计数读派生表 `t_event_counts`,
3948
- * 加按天×模型的桶与按会话的行(事件行去重、排除孤儿)。
3949
- * @param sinceMs - 只算该时刻(含)之后的事件行;省略即全量。
3961
+ * 用量统计的原始聚合:读三张派生统计表(`t_event_usage` + 两张会话汇总表),
3962
+ * 给出总量 / subagent 拆分、按天×模型的桶与按会话的行(事件行去重、排除孤儿)。
3963
+ * @param sinceMs - 只算该时刻(含)之后的用量;省略即全量。会话汇总表按本地日过滤,
3964
+ * 与毫秒过滤等价的前提是调用方把起点对齐到本地零点(见 `resolveUsageSince`)。
3950
3965
  */
3951
3966
  usageReport(sinceMs?: number): Promise<UsageAggregate>;
3952
3967
  /** 管理面的会话行列表:完整语料(含归档)+ 标题 + 最后活动时间,按活动倒序分页。 */
3953
3968
  listSessionRows(query?: SessionListRowsQuery): Promise<SessionListRowsPage>;
3954
3969
  /**
3955
- * 活动计数**旁路累加**(派生表 `t_event_counts`,不参与写事务、失败可丢——表可销毁重建):
3956
- * 每个 (会话, 本地日, 事件类型) 记一行计数。
3970
+ * token 用量**旁路累加**(派生表 `t_session_usage`,不参与写事务、失败可丢——表可销毁重建):
3971
+ * 每个 (会话, 本地日, 模型) 记一行累加。
3972
+ * @param id - 会话 id。
3973
+ * @param buckets - 本批要累加的 (day, provider, model, tokens)。
3974
+ */
3975
+ incrementSessionUsage(id: SessionId, buckets: readonly SessionUsageBucket[]): Promise<void>;
3976
+ /**
3977
+ * 活动计数**旁路累加**(派生表 `t_session_counts`,同上):每个 (会话, 本地日) 累加四项计数。
3957
3978
  * @param id - 会话 id。
3958
- * @param buckets - 本批要累加的 (day, type, count)。
3979
+ * @param buckets - 本批要累加的 (day, turns, steps, userInputs, toolCalls)。
3959
3980
  */
3960
- incrementEventCounts(id: SessionId, buckets: readonly EventCountBucket[]): Promise<void>;
3981
+ incrementSessionCounts(id: SessionId, buckets: readonly SessionCountBucket[]): Promise<void>;
3961
3982
  /**
3962
- * 按会话从事件表重算活动计数(rewind / fork 之后调用):先删该会话的行再重算。
3983
+ * 按会话从事件表重算两张会话汇总表(rewind / fork 之后调用):先删该会话的行再重算。
3963
3984
  * @param id - 会话 id。
3964
3985
  */
3965
- rebuildEventCounts(id: SessionId): Promise<void>;
3966
- /** 删掉一个会话的活动计数行(会话删除时)。 */
3967
- deleteEventCounts(id: SessionId): Promise<void>;
3986
+ rebuildSessionStats(id: SessionId): Promise<void>;
3987
+ /**
3988
+ * 全量重算 `t_event_usage` 的引用标记(`f_referenced` / `f_subagent`):rewind / fork / 会话删除之后调用。
3989
+ * 引用可能跨会话消失(截断、删除),只按本会话判定不够,故这里是全量。
3990
+ */
3991
+ refreshEventUsageFlags(): Promise<void>;
3992
+ /** 删掉一个会话的汇总行(会话删除时;外键 CASCADE 之外再显式清一次)。 */
3993
+ deleteSessionStats(id: SessionId): Promise<void>;
3968
3994
  /** 回收空间与统计(含 VACUUM);不得在事务内执行。 */
3969
3995
  vacuum(): Promise<void>;
3970
3996
  close(): Promise<void>;
3971
3997
  }
3972
3998
  //#endregion
3973
- export { eventKind as A, tWorkspaceState as B, EVENT_ENCODING as C, SCHEMA_VERSION as D, JournalMode as E, tSessionEvents as F, WriteGuard as H, tSessionProjcacheRows as I, tSessions as L, tEvents as M, tPersistenceState as N, SESSION_PERSISTENCE_SQLITE_APPLICATION_ID as O, tSchemaMeta as P, tStorageUnits as R, DEFAULT_BUSY_TIMEOUT_MS as S, EventRole as T, StorageRepository as U, tWorkspaces as V, locateTurnEnd as _, SessionListRowsPage as a, SessionRowsItem as b, UsageAggregate as c, SessionDeletionError as d, SessionDeletionErrorCode as f, SessionBranchRdbProvider as g, SessionBranchRdb as h, EventRow as i, tEventUsage as j, eventDimensions as k, Config$1 as l, SessionPersistenceRdbInternals as m, BackendTx as n, SessionListRowsQuery as o, SessionPersistenceRdb as p, EventCountBucket as r, SessionRow as s, Backend as t, ProjectionCacheOptions as u, truncateLiveSession as v, EventKind as w, SessionRowsValue as x, SESSION_ROWS_PATH as y, tWorkspaceSessions as z };
3999
+ //#region src/log.d.ts
4000
+ declare function rowToMeta(row: SessionRow): SessionHeader;
4001
+ declare function sessionInsertRow(storage: SessionStorageMetadata, incarnation: string): {
4002
+ fSessionId: string;
4003
+ fHeadEventId: string;
4004
+ fHeadSequence: number;
4005
+ fVersion: number;
4006
+ fCreatedAt: number;
4007
+ fCwd: string | null;
4008
+ fParentSession: string | null;
4009
+ fSeedLength: number | null;
4010
+ fOrigin: string | null;
4011
+ fDelegationDepth: number | null;
4012
+ fAgentPreset: string | null;
4013
+ fIncarnation: string;
4014
+ fRevision: number;
4015
+ };
4016
+ declare function sessionConflictRow(storage: SessionStorageMetadata): {
4017
+ fVersion: number;
4018
+ fCreatedAt: number;
4019
+ fCwd: string | null;
4020
+ fParentSession: string | null;
4021
+ fSeedLength: number | null;
4022
+ fOrigin: string | null;
4023
+ fDelegationDepth: number | null;
4024
+ fAgentPreset: string | null;
4025
+ };
4026
+ declare function rowToEvent(row: EventRow): SessionEvent;
4027
+ declare function recomputeReplaceProvenance(events: SessionEvent[]): void;
4028
+ declare function findSurfaceRepairs(events: readonly SessionEvent[]): {
4029
+ degradeToAppend: Set<number>;
4030
+ addAppendMarker: Set<number>;
4031
+ clearSurfaceOp: Set<number>;
4032
+ clampEnd: Map<number, number>;
4033
+ };
4034
+ declare function repairSurfaceOps(events: SessionEvent[]): void;
4035
+ declare function repairAssistantSettlement(events: SessionEvent[]): void;
4036
+ declare function repairRequestHeaders(events: SessionEvent[]): void;
4037
+ declare function syncMeteringRanges(events: SessionEvent[]): void;
4038
+ declare function renameLegacyPtcEvents(events: SessionEvent[]): void;
4039
+ /**
4040
+ * 把**旧代(v3 及更早)的消息形状**归一到当前格式(v4):迁移链(严格)拒绝那些形状时,读路径仍要
4041
+ * 能把它们读出来——只动字段与标记,不改坐标、不改内容。
4042
+ *
4043
+ * 三件事:
4044
+ * - `system/message` 的 source:`{ kind: 'plugin', plugin: '@deepseek-ai/dsh-system-prompt' }` → `{ kind: 'system-prompt' }`
4045
+ * (v4 起 system 消息只认这个 kind);
4046
+ * - `tool/result` 的消息:v3 是 user 角色、结果块包在 `content[0]`;v4 是 `role: 'tool'` + 顶层
4047
+ * `toolCallId` + `content` 就是结果块;
4048
+ * - **本仓库自造、上游不认识的事件类型**(见 `OWN_EVENT_TYPES`:历史遗留的与我们当前在写的)标
4049
+ * `ignorable: true`——v4 的校验对未知类型只接受可忽略信封。
4050
+ *
4051
+ * 只认这张白名单:**其它未知类型保持 fail loud**(那是「数据来自更新的 harness」的信号,不能吞掉)。
4052
+ */
4053
+ declare function normalizeToCurrentShape(events: SessionEvent[]): void;
4054
+ /**
4055
+ * 落库前给**本仓库自造的事件类型**补上 `ignorable: true` 信封。
4056
+ *
4057
+ * 上游的持久化校验(`validateStoredEvents`)只放行「已知类型」或「带可忽略信封的未知类型」,我们的类型
4058
+ * 按构造就在已知表之外——不补的话连**写入**都会被拒(`appendBatch` 里那道校验)。上游 `Session.append`
4059
+ * 没有承载这个信封的口子(第三个参数只服务 surface 事件),所以这一笔由持久化层补:canonical log 里的
4060
+ * 形状不变,落库的那一份带上信封。
4061
+ * @param events - 即将落库的批次(`materializeAppendBatch` 的 JSON 快照副本,可写)。
4062
+ */
4063
+ declare function sealOwnEvents(events: readonly SessionEvent[]): void;
4064
+ /**
4065
+ * 本仓库自造、上游不认识的事件类型:读的时候标 `ignorable: true`,v4 的持久化校验才肯跳过它们
4066
+ * (`validateStoredEvents` 只放行「已知类型」或「带可忽略信封的未知类型」)。
4067
+ *
4068
+ * 两类都在这张表里:
4069
+ * - **历史遗留**:写过、后来连同机制一起删除的(`session-branch/version`);
4070
+ * - **当前在写**:本仓库的插件自己声明的事件类型(`session-mode/selected`)——上游的
4071
+ * `KNOWN_SESSION_EVENT_TYPES` 由上游仓库的声明生成,外部插件的事件按构造就在它之外。
4072
+ *
4073
+ * 新增自造事件类型时**必须**登记到这里,否则读路径 fail loud(真回归:`session-mode/selected` 漏登记,
4074
+ * 会话打不开)。**不要**用「所有未知类型」代替(那会掩盖更新版本的未知事件——那种必须拒读)。
4075
+ */
4076
+ declare const OWN_EVENT_TYPES: ReadonlySet<string>;
4077
+ /** 这批事件是否需要按当前形状归一(旧代消息形状,或尚未标 `ignorable` 的自造事件类型)——写路径曾把回退
4078
+ * 视图的结果以当前版本号落库,所以**版本号不可信**,读之前要按内容判一次。 */
4079
+ declare function needsShapeAdoption(events: readonly SessionEvent[]): boolean;
4080
+ declare function repairReadView(events: SessionEvent[]): void;
4081
+ declare function orphanInboxSpliceSeqs(events: readonly SessionEvent[]): Set<number>;
4082
+ declare function repairOrphanInboxSplices(events: SessionEvent[]): void;
4083
+ declare function scanRows(rows: readonly EventRow[], base?: number): {
4084
+ preserved: SessionEvent[];
4085
+ tornFrom?: number;
4086
+ };
4087
+ declare function toJsonlArtifact(meta: SessionHeader, inheritedEventCount: number, events: readonly SessionEvent[]): string;
4088
+ declare function titleOfEventData(data: string): string | undefined;
4089
+ /** 用量行的列(`t_event_usage`):读侧维度(本地日与两个归属标记)在写入时一起物化。 */
4090
+ interface EventUsageRow {
4091
+ fEventId: string;
4092
+ fCreatedAt: number;
4093
+ fDay: string;
4094
+ fProvider: string | null;
4095
+ fModel: string | null;
4096
+ fReferenced: number;
4097
+ fSubagent: number;
4098
+ fInputTokens: number;
4099
+ fOutputTokens: number;
4100
+ fCacheReadTokens: number;
4101
+ fReasoningTokens: number;
4102
+ fTotalTokens: number;
4103
+ }
4104
+ /**
4105
+ * 一条事件行的用量(只有带 `usage` 的 `assistant/message` 才有):写路径据此记录
4106
+ * `t_event_usage`,统计因此不必逐行解析 JSON。两种 `f_data` 结构(带信封 / 老格式)都认。
4107
+ * 读侧维度一起物化:本地日由事件时间算,`f_referenced` 直接 1(这一批事件行与桥接行同事务,
4108
+ * 提交即被引用),`f_subagent` 取写入它的会话是否子代理。
4109
+ * @param event - 待写入的事件行。
4110
+ * @param subagent - 写入它的会话是否 `origin === 'subagent'`。
4111
+ * @returns 用量行,或该事件没有用量时的 undefined。
4112
+ */
4113
+ declare function usageRowOf(event: {
4114
+ fEventId: string;
4115
+ fCreatedAt: number;
4116
+ fType: string;
4117
+ fData: string;
4118
+ }, subagent: boolean): EventUsageRow | undefined;
4119
+ //#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 };