@morlay/session-rdb 0.0.21-alpha.7 → 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 +31 -12
- package/dist/artifact.d.mts +2 -101
- package/dist/artifact.mjs +2 -2
- package/dist/{branch-BjUR6wfe.mjs → branch-D4nVzQQM.mjs} +3 -3
- package/dist/deletion.d.mts +1 -1
- package/dist/deletion.mjs +1 -1
- package/dist/{dist-vIVO6bA-.mjs → dist-DHer1cql.mjs} +14 -7
- package/dist/export.d.mts +8 -0
- package/dist/export.mjs +76 -0
- package/dist/gc.d.mts +8 -0
- package/dist/gc.mjs +51 -0
- package/dist/import.d.mts +1 -1
- package/dist/import.mjs +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +3 -3
- package/dist/{log-qCNkDjh5.mjs → log-BWVFYAh5.mjs} +39 -12
- package/dist/{backend-E0bmTXpz.d.mts → log-CUjKQbOR.d.mts} +222 -39
- package/dist/{sqlite-zcXVyMxr.mjs → sqlite-SCiSnwaz.mjs} +321 -259
- package/dist/{src-B9X8BKyh.mjs → src-BZ1tffd5.mjs} +342 -310
- package/dist/storage.d.mts +20 -7
- package/dist/storage.mjs +2 -2
- package/dist/testing.d.mts +1 -1
- package/dist/testing.mjs +48 -29
- package/dist/usage.d.mts +2 -0
- package/dist/usage.mjs +152 -0
- package/drizzle/postgres/20260923120000_v3_usage_materialized/migration.sql +48 -0
- package/drizzle/sqlite/20260923120000_v3_usage_materialized/migration.sql +48 -0
- package/package.json +25 -30
- package/src/backend.ts +41 -17
- package/src/branch.ts +4 -4
- package/src/drizzle/postgres-v3.ts +2 -0
- package/src/drizzle/sqlite-v3.ts +2 -0
- package/src/entities/v3/event-usage.ts +8 -1
- package/src/entities/v3/index.ts +8 -0
- package/src/entities/v3/session-counts.ts +23 -0
- package/src/entities/v3/session-usage.ts +29 -0
- package/src/index.ts +230 -88
- package/src/log.ts +58 -17
- package/src/postgres.ts +248 -149
- package/src/rows.ts +2 -1
- package/src/schema.ts +4 -0
- package/src/sqlite.ts +263 -124
- package/src/storage-takeover/repository.ts +1 -4
- package/src/usage.ts +10 -6
- package/cordis.patch.yml +0 -13
package/README.md
CHANGED
|
@@ -7,8 +7,8 @@ SQLite 或 PostgreSQL 后端。表结构、原样存储与各条流程的设计
|
|
|
7
7
|
|
|
8
8
|
## 配置
|
|
9
9
|
|
|
10
|
-
配置**就是这一行的 config
|
|
11
|
-
|
|
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
|
|
@@ -80,10 +92,10 @@ web 模式经 `POST /api/session.delete`(body `{ sessionId }`)暴露,状
|
|
|
80
92
|
|
|
81
93
|
## 会话列表(两条路)
|
|
82
94
|
|
|
83
|
-
| 用途
|
|
84
|
-
|
|
|
85
|
-
| 官方 UI(上游 `ui
|
|
86
|
-
| 我们自己的管理面
|
|
95
|
+
| 用途 | 接缝 | 内容 |
|
|
96
|
+
| ---------------------- | -------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
|
|
97
|
+
| 官方 UI(上游 `ui-*`) | `session/list` → `ctx.sessionQuery.listSessions`(本包接管,**默认排除已归档**) | 未归档的可见语料,标题走投影(`projections.title`) |
|
|
98
|
+
| 我们自己的管理面 | `POST /api/session.rows`(本包路由) | **完整语料(含归档)** + 标题 + 最后活动时间 + 工作区归属,**搜索 / 子代理过滤 / 分页都在后端** |
|
|
87
99
|
|
|
88
100
|
排除归档是本部署的策略:归档是「收起来」的会话,不该混进日常列表与搜索。管理动作(取消归档 / 删除 /
|
|
89
101
|
导入)需要完整集合,所以另给一条自有路由,两条路不混。决策与取舍见
|
|
@@ -118,13 +130,20 @@ subagent 会话**(`origin = 'subagent'`、父不在表里;有 open handle /
|
|
|
118
130
|
## 用量统计
|
|
119
131
|
|
|
120
132
|
`POST /api/session.usage` 一次回报三份数据:`totals`(含 `subagent` / `human` 拆分)、按
|
|
121
|
-
天 × provider/model × subagent
|
|
133
|
+
天 × provider/model × subagent 的桶、按会话的行(活动计数:轮次 / 步骤 / 用户输入 / 工具调用)。
|
|
134
|
+
|
|
135
|
+
统计读侧只读三张**可销毁重建的派生表**,读侧维度(会话 / 模型 / 本地日 / 是否被引用 / 是否子代理)
|
|
136
|
+
全部在写入时物化,查询不做 `EXISTS`、也不回连事件表:
|
|
137
|
+
|
|
138
|
+
- `t_event_usage`——一条 `assistant/message` 事件行一行(去重口径的总量与「天 × 模型」桶读它);
|
|
139
|
+
- `t_session_usage`——会话 × 本地日 × 模型的 token 汇总(按会话的行读它,含 fork 继承前缀);
|
|
140
|
+
- `t_session_counts`——会话 × 本地日的四项活动计数。
|
|
122
141
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
[ADR
|
|
142
|
+
写入时在事务内落用量行、事务后**旁路累加**两张汇总表;rewind / fork / 删除会话之后按会话重算并全量重算
|
|
143
|
+
引用标记;表为空(首次 / 迁移后 / 手动清空)在打开库时全量回填——迁移对这三张表先删后建。真实库实测
|
|
144
|
+
单次统计从 **1.6s → ~140ms → ~30ms**(口径不变,旧口径逐项交叉核对一致),升级后首次打开付一次回填
|
|
145
|
+
(2.4s / 3.09 万用量行,此后不再发生)。口径与取舍见
|
|
146
|
+
[ADR-统计衍生表物化归属与汇总](.agents/adrs/20260923-统计衍生表物化归属与汇总.md)。
|
|
128
147
|
|
|
129
148
|
## 查询接管(`ctx.sessionQuery`)
|
|
130
149
|
|
package/dist/artifact.d.mts
CHANGED
|
@@ -1,102 +1,3 @@
|
|
|
1
|
-
import { i as
|
|
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
|
-
|
|
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
|
|
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
|
-
export {
|
|
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-
|
|
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";
|
|
@@ -217,7 +217,7 @@ var SessionBranchRdbProvider = class {
|
|
|
217
217
|
internals.dropReuseEventIds(childId);
|
|
218
218
|
throw error;
|
|
219
219
|
}
|
|
220
|
-
await this.persistence.
|
|
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.
|
|
293
|
+
await this.persistence.rebuildSessionStats(id);
|
|
294
294
|
return {
|
|
295
295
|
header: rowToMeta(row),
|
|
296
296
|
revision: await internals.readStoredRevision(id)
|
package/dist/deletion.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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-
|
|
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.
|
|
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
|
-
*
|
|
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.
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
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 };
|
package/dist/export.mjs
ADDED
|
@@ -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 {
|
|
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-
|
|
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 {
|
|
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 {
|
|
2
|
-
import { n as SessionPersistenceRdb, r as SESSION_ROWS_PATH, t as SessionDeletionError } from "./src-
|
|
3
|
-
import { n as SessionBranchRdbProvider, r as locateTurnEnd, t as SessionBranchRdb } from "./branch-
|
|
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,3 +1,4 @@
|
|
|
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
4
|
//#region src/log.ts
|
|
@@ -354,15 +355,15 @@ function renameLegacyPtcEvents(events) {
|
|
|
354
355
|
* (v4 起 system 消息只认这个 kind);
|
|
355
356
|
* - `tool/result` 的消息:v3 是 user 角色、结果块包在 `content[0]`;v4 是 `role: 'tool'` + 顶层
|
|
356
357
|
* `toolCallId` + `content` 就是结果块;
|
|
357
|
-
* -
|
|
358
|
-
*
|
|
358
|
+
* - **本仓库自造、上游不认识的事件类型**(见 `OWN_EVENT_TYPES`:历史遗留的与我们当前在写的)标
|
|
359
|
+
* `ignorable: true`——v4 的校验对未知类型只接受可忽略信封。
|
|
359
360
|
*
|
|
360
361
|
* 只认这张白名单:**其它未知类型保持 fail loud**(那是「数据来自更新的 harness」的信号,不能吞掉)。
|
|
361
362
|
*/
|
|
362
363
|
function normalizeToCurrentShape(events) {
|
|
363
364
|
for (const event of events) {
|
|
364
365
|
const type = event.type;
|
|
365
|
-
if (
|
|
366
|
+
if (OWN_EVENT_TYPES.has(type)) {
|
|
366
367
|
event.ignorable = true;
|
|
367
368
|
continue;
|
|
368
369
|
}
|
|
@@ -405,16 +406,36 @@ function liftToolResultMessage(message) {
|
|
|
405
406
|
};
|
|
406
407
|
}
|
|
407
408
|
/**
|
|
408
|
-
*
|
|
409
|
-
*
|
|
409
|
+
* 落库前给**本仓库自造的事件类型**补上 `ignorable: true` 信封。
|
|
410
|
+
*
|
|
411
|
+
* 上游的持久化校验(`validateStoredEvents`)只放行「已知类型」或「带可忽略信封的未知类型」,我们的类型
|
|
412
|
+
* 按构造就在已知表之外——不补的话连**写入**都会被拒(`appendBatch` 里那道校验)。上游 `Session.append`
|
|
413
|
+
* 没有承载这个信封的口子(第三个参数只服务 surface 事件),所以这一笔由持久化层补:canonical log 里的
|
|
414
|
+
* 形状不变,落库的那一份带上信封。
|
|
415
|
+
* @param events - 即将落库的批次(`materializeAppendBatch` 的 JSON 快照副本,可写)。
|
|
416
|
+
*/
|
|
417
|
+
function sealOwnEvents(events) {
|
|
418
|
+
for (const event of events) if (OWN_EVENT_TYPES.has(event.type)) event.ignorable = true;
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* 本仓库自造、上游不认识的事件类型:读的时候标 `ignorable: true`,v4 的持久化校验才肯跳过它们
|
|
422
|
+
* (`validateStoredEvents` 只放行「已知类型」或「带可忽略信封的未知类型」)。
|
|
423
|
+
*
|
|
424
|
+
* 两类都在这张表里:
|
|
425
|
+
* - **历史遗留**:写过、后来连同机制一起删除的(`session-branch/version`);
|
|
426
|
+
* - **当前在写**:本仓库的插件自己声明的事件类型(`session-mode/selected`)——上游的
|
|
427
|
+
* `KNOWN_SESSION_EVENT_TYPES` 由上游仓库的声明生成,外部插件的事件按构造就在它之外。
|
|
428
|
+
*
|
|
429
|
+
* 新增自造事件类型时**必须**登记到这里,否则读路径 fail loud(真回归:`session-mode/selected` 漏登记,
|
|
430
|
+
* 会话打不开)。**不要**用「所有未知类型」代替(那会掩盖更新版本的未知事件——那种必须拒读)。
|
|
410
431
|
*/
|
|
411
|
-
const
|
|
412
|
-
/**
|
|
413
|
-
*
|
|
414
|
-
function
|
|
432
|
+
const OWN_EVENT_TYPES = /* @__PURE__ */ new Set(["session-branch/version", "session-mode/selected"]);
|
|
433
|
+
/** 这批事件是否需要按当前形状归一(旧代消息形状,或尚未标 `ignorable` 的自造事件类型)——写路径曾把回退
|
|
434
|
+
* 视图的结果以当前版本号落库,所以**版本号不可信**,读之前要按内容判一次。 */
|
|
435
|
+
function needsShapeAdoption(events) {
|
|
415
436
|
for (const event of events) {
|
|
416
437
|
const type = event.type;
|
|
417
|
-
if (
|
|
438
|
+
if (OWN_EVENT_TYPES.has(type)) return true;
|
|
418
439
|
if (type === "system/message") {
|
|
419
440
|
if ((event.data["message"]?.["source"])?.["kind"] === "plugin") return true;
|
|
420
441
|
continue;
|
|
@@ -555,10 +576,13 @@ function textField(value) {
|
|
|
555
576
|
/**
|
|
556
577
|
* 一条事件行的用量(只有带 `usage` 的 `assistant/message` 才有):写路径据此记录
|
|
557
578
|
* `t_event_usage`,统计因此不必逐行解析 JSON。两种 `f_data` 结构(带信封 / 老格式)都认。
|
|
579
|
+
* 读侧维度一起物化:本地日由事件时间算,`f_referenced` 直接 1(这一批事件行与桥接行同事务,
|
|
580
|
+
* 提交即被引用),`f_subagent` 取写入它的会话是否子代理。
|
|
558
581
|
* @param event - 待写入的事件行。
|
|
582
|
+
* @param subagent - 写入它的会话是否 `origin === 'subagent'`。
|
|
559
583
|
* @returns 用量行,或该事件没有用量时的 undefined。
|
|
560
584
|
*/
|
|
561
|
-
function usageRowOf(event) {
|
|
585
|
+
function usageRowOf(event, subagent) {
|
|
562
586
|
if (event.fType !== "assistant/message") return void 0;
|
|
563
587
|
let parsed;
|
|
564
588
|
try {
|
|
@@ -573,8 +597,11 @@ function usageRowOf(event) {
|
|
|
573
597
|
return {
|
|
574
598
|
fEventId: event.fEventId,
|
|
575
599
|
fCreatedAt: event.fCreatedAt,
|
|
600
|
+
fDay: localDayKey(event.fCreatedAt),
|
|
576
601
|
fProvider: textField(source?.["provider"]),
|
|
577
602
|
fModel: textField(source?.["model"]),
|
|
603
|
+
fReferenced: 1,
|
|
604
|
+
fSubagent: subagent ? 1 : 0,
|
|
578
605
|
fInputTokens: numericField(usage["inputTokens"]),
|
|
579
606
|
fOutputTokens: numericField(usage["outputTokens"]),
|
|
580
607
|
fCacheReadTokens: numericField(usage["cacheReadTokens"]),
|
|
@@ -583,4 +610,4 @@ function usageRowOf(event) {
|
|
|
583
610
|
};
|
|
584
611
|
}
|
|
585
612
|
//#endregion
|
|
586
|
-
export {
|
|
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 };
|