@morlay/ui-conversation-message-actions 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -29
- package/dist/client.cjs +12 -47
- package/dist/client.d.cts +3 -4
- package/dist/client.d.mts +3 -4
- package/dist/index.d.cts +6 -6
- package/dist/index.d.mts +6 -6
- package/dist/index.mjs +22 -13
- package/dist/invariant.d.cts +4 -5
- package/dist/invariant.d.mts +4 -5
- package/dist/{plan-BKgGPZdD.d.mts → plan-CRswvwkb.d.mts} +2 -1
- package/dist/{plan-BUTplrpz.d.cts → plan-DeCB5_cR.d.cts} +2 -1
- package/dist/plan.d.cts +2 -2
- package/dist/plan.d.mts +2 -2
- package/dist/plan.mjs +13 -1
- package/dist/rolldown-runtime-BMI-E3GI.mjs +44 -0
- package/dist/shared-DG86bLrc.mjs +11 -0
- package/dist/testing.d.cts +2041 -93
- package/dist/testing.d.mts +2040 -93
- package/dist/testing.mjs +92 -290
- package/package.json +6 -6
- package/src/client/chat-node/MessageItem.tsx +2 -4
- package/src/client/composer-stats/StatsPills.tsx +25 -50
- package/src/client/composer-stats/token-format.ts +2 -3
- package/src/client/controller.ts +15 -32
- package/src/index.ts +29 -15
- package/src/plan.ts +28 -0
- package/src/shared.ts +12 -1
- package/src/testing.ts +3 -2
- package/dist/shared-BBbOTwy4.mjs +0 -4
package/dist/testing.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { t as
|
|
1
|
+
import { a as __toCommonJS, i as __require, n as __esmMin, o as __toESM, r as __exportAll, t as __commonJSMin } from "./rolldown-runtime-BMI-E3GI.mjs";
|
|
2
|
+
import { t as SESSION_EDITOR_PATHS } from "./shared-DG86bLrc.mjs";
|
|
2
3
|
import { SessionEditor } from "./index.mjs";
|
|
3
|
-
import { createRequire } from "node:module";
|
|
4
4
|
import { Context, Service } from "@deepseek-ai/cordis";
|
|
5
5
|
import { KNOWN_SESSION_EVENT_TYPES, SESSION_FORMAT_VERSION, Session, Session as Session$1, SessionId, SessionId as SessionIdBrand, SessionLogOffset, SessionSeq, SessionSeq as SessionSeq$1, SessionStore, foldSurface, interruptedTurnClosers, isSurfaceEvent, snapshotSessionEvent } from "@deepseek-ai/dsh-session";
|
|
6
6
|
import { SessionBranch, SessionBranchError, balanceRewindPrefix, rewindKeepLength } from "@morlay/session-branch";
|
|
@@ -15,49 +15,7 @@ import { mkdir, open, readdir, stat } from "node:fs/promises";
|
|
|
15
15
|
import { fileURLToPath } from "node:url";
|
|
16
16
|
import { DatabaseSync } from "node:sqlite";
|
|
17
17
|
import fs, { existsSync, readdirSync } from "node:fs";
|
|
18
|
-
import { createRequire
|
|
19
|
-
//#region \0rolldown/runtime.js
|
|
20
|
-
var __create = Object.create;
|
|
21
|
-
var __defProp = Object.defineProperty;
|
|
22
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
23
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
24
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
25
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
26
|
-
var __esmMin = (fn, res, err) => () => {
|
|
27
|
-
if (err) throw err[0];
|
|
28
|
-
try {
|
|
29
|
-
return fn && (res = fn(fn = 0)), res;
|
|
30
|
-
} catch (e) {
|
|
31
|
-
throw err = [e], e;
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
|
-
var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
|
|
35
|
-
var __exportAll = (all, no_symbols) => {
|
|
36
|
-
let target = {};
|
|
37
|
-
for (var name in all) __defProp(target, name, {
|
|
38
|
-
get: all[name],
|
|
39
|
-
enumerable: true
|
|
40
|
-
});
|
|
41
|
-
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
42
|
-
return target;
|
|
43
|
-
};
|
|
44
|
-
var __copyProps = (to, from, except, desc) => {
|
|
45
|
-
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
46
|
-
key = keys[i];
|
|
47
|
-
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
48
|
-
get: ((k) => from[k]).bind(null, key),
|
|
49
|
-
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
return to;
|
|
53
|
-
};
|
|
54
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
|
|
55
|
-
value: mod,
|
|
56
|
-
enumerable: true
|
|
57
|
-
}) : target, mod));
|
|
58
|
-
var __toCommonJS = (mod) => __hasOwnProp.call(mod, "module.exports") ? mod["module.exports"] : __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
59
|
-
var __require = /* #__PURE__ */ (() => createRequire(import.meta.url))();
|
|
60
|
-
//#endregion
|
|
18
|
+
import { createRequire } from "module";
|
|
61
19
|
//#region ../../../vendor/deepseek-harness/vendor/cosmokit/lib/index.js
|
|
62
20
|
/** Return true when a value is `null` or `undefined`. */
|
|
63
21
|
function isNullable(value) {
|
|
@@ -14656,7 +14614,6 @@ var WriteGuard = class {
|
|
|
14656
14614
|
confirmHead(id, head) {
|
|
14657
14615
|
this.headSeqs.set(id, head);
|
|
14658
14616
|
}
|
|
14659
|
-
/** 本实例有没有这条会话的 head 记录(迁移重写用它决定是"登记基准"还是"严格比较")。 */
|
|
14660
14617
|
has(id) {
|
|
14661
14618
|
return this.headSeqs.has(id);
|
|
14662
14619
|
}
|
|
@@ -20514,8 +20471,16 @@ createSessionFormatCatalog({
|
|
|
20514
20471
|
}
|
|
20515
20472
|
});
|
|
20516
20473
|
//#endregion
|
|
20474
|
+
//#region ../session-rdb/src/routes.ts
|
|
20475
|
+
const MORLAY_SESSION_API_PREFIX = "/api/morlay/v1/session";
|
|
20476
|
+
const SESSION_ROWS_PATH = `${MORLAY_SESSION_API_PREFIX}/rows`;
|
|
20477
|
+
const SESSION_EXPORT_PATH = `${MORLAY_SESSION_API_PREFIX}/export`;
|
|
20478
|
+
const SESSION_IMPORT_PATH = `${MORLAY_SESSION_API_PREFIX}/import`;
|
|
20479
|
+
const SESSION_DELETE_PATH = `${MORLAY_SESSION_API_PREFIX}/delete`;
|
|
20480
|
+
const SESSION_GC_PATH = `${MORLAY_SESSION_API_PREFIX}/gc`;
|
|
20481
|
+
const SESSION_USAGE_PATH = `${MORLAY_SESSION_API_PREFIX}/usage`;
|
|
20482
|
+
//#endregion
|
|
20517
20483
|
//#region ../session-rdb/src/usage.ts
|
|
20518
|
-
const SESSION_USAGE_PATH = "/api/session.usage";
|
|
20519
20484
|
const ROLLING_DAYS = {
|
|
20520
20485
|
"7d": 7,
|
|
20521
20486
|
"30d": 30,
|
|
@@ -20526,14 +20491,6 @@ function localDayStart(now) {
|
|
|
20526
20491
|
start.setHours(0, 0, 0, 0);
|
|
20527
20492
|
return start;
|
|
20528
20493
|
}
|
|
20529
|
-
/**
|
|
20530
|
-
* 范围起点(含),不限时为 undefined。**所有起点都对齐到 host 本地时区的零点**——`day` / `week` 是
|
|
20531
|
-
* 今天 / 本周一的零点,`7d` / `30d` / `90d` 是「今天零点往前 N-1 天」(含今天共 N 个自然日)。
|
|
20532
|
-
* 对齐的意义:事件级表按毫秒过滤、会话汇总表按本地日过滤,两者因此严格等价。
|
|
20533
|
-
* @param range - 语义键。
|
|
20534
|
-
* @param now - 当前时刻。
|
|
20535
|
-
* @returns 起点毫秒时间戳,或 undefined。
|
|
20536
|
-
*/
|
|
20537
20494
|
function resolveUsageSince(range, now) {
|
|
20538
20495
|
switch (range) {
|
|
20539
20496
|
case "all": return;
|
|
@@ -20553,14 +20510,12 @@ function resolveUsageSince(range, now) {
|
|
|
20553
20510
|
function parseUsageRange(value) {
|
|
20554
20511
|
return value === "day" || value === "week" || value === "7d" || value === "30d" || value === "90d" ? value : "all";
|
|
20555
20512
|
}
|
|
20556
|
-
/** 计入活动计数的事件类型(轮次 / 步骤 / 用户输入 / 工具调用)。 */
|
|
20557
20513
|
const COUNTED_EVENT_TYPES = [
|
|
20558
20514
|
"turn/start",
|
|
20559
20515
|
"step/start",
|
|
20560
20516
|
"user/message",
|
|
20561
20517
|
"tool/call"
|
|
20562
20518
|
];
|
|
20563
|
-
/** 按事件类型把计数累加到活动指标上(表里按类型存,读的时候折成四项)。 */
|
|
20564
20519
|
function addActivityCount(target, type, count) {
|
|
20565
20520
|
switch (type) {
|
|
20566
20521
|
case "turn/start":
|
|
@@ -20578,7 +20533,6 @@ function addActivityCount(target, type, count) {
|
|
|
20578
20533
|
default: return target;
|
|
20579
20534
|
}
|
|
20580
20535
|
}
|
|
20581
|
-
/** 毫秒时间戳 → host 本地日(`YYYY-MM-DD`,与 SQL 的 localtime 口径一致)。 */
|
|
20582
20536
|
function localDayKey(ms) {
|
|
20583
20537
|
const date = new Date(ms);
|
|
20584
20538
|
const pad = (value) => String(value).padStart(2, "0");
|
|
@@ -20612,16 +20566,11 @@ function addActivityTotals(target, row) {
|
|
|
20612
20566
|
target.toolCalls += row.toolCalls;
|
|
20613
20567
|
return target;
|
|
20614
20568
|
}
|
|
20615
|
-
/** 把一行的整套指标累加进目标(后端合并「是否子代理」两组时用)。 */
|
|
20616
20569
|
function addTotals(target, row) {
|
|
20617
20570
|
addActivityTotals(target, row);
|
|
20618
20571
|
addTokenTotals(target, row);
|
|
20619
20572
|
return target;
|
|
20620
20573
|
}
|
|
20621
|
-
/**
|
|
20622
|
-
* 用量统计通道:一次请求回报总览(含 subagent 拆分)、按天 × 模型的桶与按会话的行。
|
|
20623
|
-
* 聚合只算被会话引用的事件行——fork 共享行因此只计一次,已删会话留下的孤儿行不计。
|
|
20624
|
-
*/
|
|
20625
20574
|
function registerSessionUsage(ctx, persistence) {
|
|
20626
20575
|
ctx.inject(["webServer", "connection"], (webCtx) => {
|
|
20627
20576
|
const webServer = webCtx.webServer;
|
|
@@ -20743,22 +20692,17 @@ function rowToEvent(row) {
|
|
|
20743
20692
|
}
|
|
20744
20693
|
const SURFACE_EVENT_TYPES = /* @__PURE__ */ new Set([
|
|
20745
20694
|
"system/message",
|
|
20695
|
+
"developer/message",
|
|
20746
20696
|
"user/message",
|
|
20747
20697
|
"assistant/message",
|
|
20748
20698
|
"tool/result"
|
|
20749
20699
|
]);
|
|
20750
20700
|
const METERING_EVENT_TYPES = /* @__PURE__ */ new Set(["compaction/summary", "compaction/prune"]);
|
|
20751
|
-
/**
|
|
20752
|
-
* 升序的 surface 事件 seq 索引:读视图修复里的溯源(replace 的 `sourceEventSeqs` 与
|
|
20753
|
-
* metering 的 `shadowedSeqs`)共用这一份,避免每个 replace 各扫一遍全量事件。
|
|
20754
|
-
* 前提是输入按 seq 有序(`readLog` 与 live snapshot 都如此)。
|
|
20755
|
-
*/
|
|
20756
20701
|
function surfaceSeqIndex(events) {
|
|
20757
20702
|
const seqs = [];
|
|
20758
20703
|
for (const event of events) if (SURFACE_EVENT_TYPES.has(event.type)) seqs.push(event.seq);
|
|
20759
20704
|
return seqs;
|
|
20760
20705
|
}
|
|
20761
|
-
/** `[startSeq, endSeq]` 内的 surface seq(含端点),二分定位后切片。 */
|
|
20762
20706
|
function surfaceSeqsInRange(seqs, startSeq, endSeq) {
|
|
20763
20707
|
let low = 0;
|
|
20764
20708
|
let high = seqs.length;
|
|
@@ -21010,20 +20954,6 @@ function renameLegacyPtcEvents(events) {
|
|
|
21010
20954
|
}
|
|
21011
20955
|
}
|
|
21012
20956
|
}
|
|
21013
|
-
/**
|
|
21014
|
-
* 把**旧代(v3 及更早)的消息形状**归一到当前格式(v4):迁移链(严格)拒绝那些形状时,读路径仍要
|
|
21015
|
-
* 能把它们读出来——只动字段与标记,不改坐标、不改内容。
|
|
21016
|
-
*
|
|
21017
|
-
* 三件事:
|
|
21018
|
-
* - `system/message` 的 source:`{ kind: 'plugin', plugin: '@deepseek-ai/dsh-system-prompt' }` → `{ kind: 'system-prompt' }`
|
|
21019
|
-
* (v4 起 system 消息只认这个 kind);
|
|
21020
|
-
* - `tool/result` 的消息:v3 是 user 角色、结果块包在 `content[0]`;v4 是 `role: 'tool'` + 顶层
|
|
21021
|
-
* `toolCallId` + `content` 就是结果块;
|
|
21022
|
-
* - **本仓库自造、上游不认识的事件类型**(见 `OWN_EVENT_TYPES`:历史遗留的与我们当前在写的)标
|
|
21023
|
-
* `ignorable: true`——v4 的校验对未知类型只接受可忽略信封。
|
|
21024
|
-
*
|
|
21025
|
-
* 只认这张白名单:**其它未知类型保持 fail loud**(那是「数据来自更新的 harness」的信号,不能吞掉)。
|
|
21026
|
-
*/
|
|
21027
20957
|
function normalizeToCurrentShape(events) {
|
|
21028
20958
|
for (const event of events) {
|
|
21029
20959
|
const type = event.type;
|
|
@@ -21069,33 +20999,10 @@ function liftToolResultMessage(message) {
|
|
|
21069
20999
|
callId
|
|
21070
21000
|
};
|
|
21071
21001
|
}
|
|
21072
|
-
/**
|
|
21073
|
-
* 落库前给**本仓库自造的事件类型**补上 `ignorable: true` 信封。
|
|
21074
|
-
*
|
|
21075
|
-
* 上游的持久化校验(`validateStoredEvents`)只放行「已知类型」或「带可忽略信封的未知类型」,我们的类型
|
|
21076
|
-
* 按构造就在已知表之外——不补的话连**写入**都会被拒(`appendBatch` 里那道校验)。上游 `Session.append`
|
|
21077
|
-
* 没有承载这个信封的口子(第三个参数只服务 surface 事件),所以这一笔由持久化层补:canonical log 里的
|
|
21078
|
-
* 形状不变,落库的那一份带上信封。
|
|
21079
|
-
* @param events - 即将落库的批次(`materializeAppendBatch` 的 JSON 快照副本,可写)。
|
|
21080
|
-
*/
|
|
21081
21002
|
function sealOwnEvents(events) {
|
|
21082
21003
|
for (const event of events) if (OWN_EVENT_TYPES.has(event.type)) event.ignorable = true;
|
|
21083
21004
|
}
|
|
21084
|
-
/**
|
|
21085
|
-
* 本仓库自造、上游不认识的事件类型:读的时候标 `ignorable: true`,v4 的持久化校验才肯跳过它们
|
|
21086
|
-
* (`validateStoredEvents` 只放行「已知类型」或「带可忽略信封的未知类型」)。
|
|
21087
|
-
*
|
|
21088
|
-
* 两类都在这张表里:
|
|
21089
|
-
* - **历史遗留**:写过、后来连同机制一起删除的(`session-branch/version`);
|
|
21090
|
-
* - **当前在写**:本仓库的插件自己声明的事件类型(`session-mode/selected`)——上游的
|
|
21091
|
-
* `KNOWN_SESSION_EVENT_TYPES` 由上游仓库的声明生成,外部插件的事件按构造就在它之外。
|
|
21092
|
-
*
|
|
21093
|
-
* 新增自造事件类型时**必须**登记到这里,否则读路径 fail loud(真回归:`session-mode/selected` 漏登记,
|
|
21094
|
-
* 会话打不开)。**不要**用「所有未知类型」代替(那会掩盖更新版本的未知事件——那种必须拒读)。
|
|
21095
|
-
*/
|
|
21096
21005
|
const OWN_EVENT_TYPES = /* @__PURE__ */ new Set(["session-branch/version", "session-mode/selected"]);
|
|
21097
|
-
/** 这批事件是否需要按当前形状归一(旧代消息形状,或尚未标 `ignorable` 的自造事件类型)——写路径曾把回退
|
|
21098
|
-
* 视图的结果以当前版本号落库,所以**版本号不可信**,读之前要按内容判一次。 */
|
|
21099
21006
|
function needsShapeAdoption(events) {
|
|
21100
21007
|
for (const event of events) {
|
|
21101
21008
|
const type = event.type;
|
|
@@ -21237,15 +21144,6 @@ function numericField(value) {
|
|
|
21237
21144
|
function textField(value) {
|
|
21238
21145
|
return typeof value === "string" && value !== "" ? value : null;
|
|
21239
21146
|
}
|
|
21240
|
-
/**
|
|
21241
|
-
* 一条事件行的用量(只有带 `usage` 的 `assistant/message` 才有):写路径据此记录
|
|
21242
|
-
* `t_event_usage`,统计因此不必逐行解析 JSON。两种 `f_data` 结构(带信封 / 老格式)都认。
|
|
21243
|
-
* 读侧维度一起物化:本地日由事件时间算,`f_referenced` 直接 1(这一批事件行与桥接行同事务,
|
|
21244
|
-
* 提交即被引用),`f_subagent` 取写入它的会话是否子代理。
|
|
21245
|
-
* @param event - 待写入的事件行。
|
|
21246
|
-
* @param subagent - 写入它的会话是否 `origin === 'subagent'`。
|
|
21247
|
-
* @returns 用量行,或该事件没有用量时的 undefined。
|
|
21248
|
-
*/
|
|
21249
21147
|
function usageRowOf(event, subagent) {
|
|
21250
21148
|
if (event.fType !== "assistant/message") return void 0;
|
|
21251
21149
|
let parsed;
|
|
@@ -25200,12 +25098,6 @@ const sessionProjcacheRows = {
|
|
|
25200
25098
|
};
|
|
25201
25099
|
//#endregion
|
|
25202
25100
|
//#region ../session-rdb/src/entities/v3/event-usage.ts
|
|
25203
|
-
/**
|
|
25204
|
-
* 用量日志:一条 `assistant/message` 事件一行(按事件行唯一,fork 共享行不重复)。
|
|
25205
|
-
* 读侧维度全部物化在行上——本地日、是否被会话引用、是否被子代理会话引用——统计因此不做
|
|
25206
|
-
* `EXISTS` 判定、也不回连事件表;事件行本身仍留在 `t_events`。
|
|
25207
|
-
* 取舍见 ADR-统计衍生表物化归属与汇总。
|
|
25208
|
-
*/
|
|
25209
25101
|
const eventUsage = {
|
|
25210
25102
|
name: "t_event_usage",
|
|
25211
25103
|
columns: {
|
|
@@ -25268,11 +25160,6 @@ const eventUsage = {
|
|
|
25268
25160
|
};
|
|
25269
25161
|
//#endregion
|
|
25270
25162
|
//#region ../session-rdb/src/entities/v3/session-usage.ts
|
|
25271
|
-
/**
|
|
25272
|
-
* 会话 × 本地日 × 模型的 token 汇总(派生统计表):**按会话的行**读它,因此保住
|
|
25273
|
-
* 「fork 子会话含继承前缀」的口径(各会话行之和 ≥ 总量)而不回连事件表。
|
|
25274
|
-
* 旁路累加、可按会话重建,读侧只读;取舍见 ADR-统计衍生表物化归属与汇总。
|
|
25275
|
-
*/
|
|
25276
25163
|
const sessionUsage = {
|
|
25277
25164
|
name: "t_session_usage",
|
|
25278
25165
|
columns: {
|
|
@@ -25334,11 +25221,6 @@ const sessionUsage = {
|
|
|
25334
25221
|
};
|
|
25335
25222
|
//#endregion
|
|
25336
25223
|
//#region ../session-rdb/src/entities/v3/session-counts.ts
|
|
25337
|
-
/**
|
|
25338
|
-
* 会话 × 本地日的活动计数(派生统计表):轮次 / 步骤 / 用户输入 / 工具调用四列直接读,
|
|
25339
|
-
* 不再存事件类型、也不在读取时折。旁路累加、可按会话重建,读侧只读;
|
|
25340
|
-
* 取舍见 ADR-统计衍生表物化归属与汇总。
|
|
25341
|
-
*/
|
|
25342
25224
|
const sessionCounts = {
|
|
25343
25225
|
name: "t_session_counts",
|
|
25344
25226
|
columns: {
|
|
@@ -25980,22 +25862,16 @@ function createStorageRepository(host) {
|
|
|
25980
25862
|
}
|
|
25981
25863
|
//#endregion
|
|
25982
25864
|
//#region ../session-rdb/src/sqlite.ts
|
|
25983
|
-
/** 用量行的「被引用」物化表达式(`%EVENT%` 换成事件 id 列)。 */
|
|
25984
25865
|
const EVENT_REFERENCED_SQL = `EXISTS (SELECT 1 FROM t_session_events rb
|
|
25985
25866
|
WHERE rb.f_event_id = %EVENT%)`;
|
|
25986
|
-
/** 用量行的「被 subagent 会话引用」物化表达式(`%EVENT%` 换成事件 id 列)。 */
|
|
25987
25867
|
const EVENT_SUBAGENT_SQL = `EXISTS (SELECT 1 FROM t_session_events sb
|
|
25988
25868
|
JOIN t_sessions ss ON ss.f_session_id = sb.f_session_id
|
|
25989
25869
|
WHERE sb.f_event_id = %EVENT% AND ss.f_origin = 'subagent')`;
|
|
25990
|
-
/** `IN (...)` 用的类型字面量(与 `COUNTED_EVENT_TYPES` 同源)。 */
|
|
25991
25870
|
const COUNTED_EVENT_TYPE_SQL$1 = COUNTED_EVENT_TYPES.map((type) => `'${type}'`).join(", ");
|
|
25871
|
+
const REFRESH_FLAG_CHUNK$1 = 400;
|
|
25992
25872
|
function withEvent(expression, column) {
|
|
25993
25873
|
return expression.replace("%EVENT%", column);
|
|
25994
25874
|
}
|
|
25995
|
-
/**
|
|
25996
|
-
* 派生统计表的回填:任一表为空即按事件表全量重算(幂等;迁移删表重建后、手动清空后都靠它自愈)。
|
|
25997
|
-
* 顺序有依赖:用量行(带物化列)先落,两张会话汇总表再从它 / 事件表重算。
|
|
25998
|
-
*/
|
|
25999
25875
|
function backfillUsageTables(db) {
|
|
26000
25876
|
backfillEventUsage(db);
|
|
26001
25877
|
backfillSessionUsage(db);
|
|
@@ -26004,7 +25880,6 @@ function backfillUsageTables(db) {
|
|
|
26004
25880
|
function tableIsEmpty(db, table) {
|
|
26005
25881
|
return db.prepare(`SELECT count(*) AS n FROM ${table}`).get().n === 0;
|
|
26006
25882
|
}
|
|
26007
|
-
/** 用量行:一条 `assistant/message` 事件行一行,物化本地日与两个归属标记。 */
|
|
26008
25883
|
function backfillEventUsage(db) {
|
|
26009
25884
|
if (!tableIsEmpty(db, "t_event_usage")) return;
|
|
26010
25885
|
db.exec(`INSERT OR IGNORE INTO t_event_usage (
|
|
@@ -26033,7 +25908,6 @@ function backfillEventUsage(db) {
|
|
|
26033
25908
|
AND (json_extract(e.f_data, '$.data.usage') IS NOT NULL
|
|
26034
25909
|
OR json_extract(e.f_data, '$.usage') IS NOT NULL)`);
|
|
26035
25910
|
}
|
|
26036
|
-
/** 会话 × 本地日 × 模型的 token 汇总:从桥接行 × 用量行重算(含 fork 继承前缀)。 */
|
|
26037
25911
|
function backfillSessionUsage(db) {
|
|
26038
25912
|
if (!tableIsEmpty(db, "t_session_usage")) return;
|
|
26039
25913
|
db.exec(`INSERT OR IGNORE INTO t_session_usage (
|
|
@@ -26046,7 +25920,6 @@ function backfillSessionUsage(db) {
|
|
|
26046
25920
|
JOIN t_event_usage u ON u.f_event_id = b.f_event_id
|
|
26047
25921
|
GROUP BY b.f_session_id, u.f_day, coalesce(u.f_provider, ''), coalesce(u.f_model, '')`);
|
|
26048
25922
|
}
|
|
26049
|
-
/** 会话 × 本地日的活动计数:从桥接行 × 事件类型重算。 */
|
|
26050
25923
|
function backfillSessionCounts(db) {
|
|
26051
25924
|
if (!tableIsEmpty(db, "t_session_counts")) return;
|
|
26052
25925
|
db.exec(`INSERT OR IGNORE INTO t_session_counts (
|
|
@@ -26075,7 +25948,6 @@ function sessionListRowOf(row) {
|
|
|
26075
25948
|
workspace: typeof row["workspace"] === "string" ? row["workspace"] : null
|
|
26076
25949
|
};
|
|
26077
25950
|
}
|
|
26078
|
-
/** SQL 的缺失求和是 NULL:没有 usage 字段的行使该字段计 0。 */
|
|
26079
25951
|
function tokenTotalsOf$1(row) {
|
|
26080
25952
|
return {
|
|
26081
25953
|
inputTokens: row.input_tokens ?? 0,
|
|
@@ -26085,7 +25957,6 @@ function tokenTotalsOf$1(row) {
|
|
|
26085
25957
|
totalTokens: row.total_tokens ?? 0
|
|
26086
25958
|
};
|
|
26087
25959
|
}
|
|
26088
|
-
/** `t_session_counts` 的原始行(列名是 SQL 别名)折成四项活动计数。 */
|
|
26089
25960
|
function activityTotalsOf$1(row) {
|
|
26090
25961
|
return {
|
|
26091
25962
|
turns: row.turns ?? 0,
|
|
@@ -26094,7 +25965,6 @@ function activityTotalsOf$1(row) {
|
|
|
26094
25965
|
toolCalls: row.tool_calls ?? 0
|
|
26095
25966
|
};
|
|
26096
25967
|
}
|
|
26097
|
-
/** 一行的整套指标:token 用量 + 活动计数(轮次 / 步骤 / 用户输入 / 工具调用)。 */
|
|
26098
25968
|
function emptyActivity$1() {
|
|
26099
25969
|
return {
|
|
26100
25970
|
turns: 0,
|
|
@@ -26103,7 +25973,6 @@ function emptyActivity$1() {
|
|
|
26103
25973
|
toolCalls: 0
|
|
26104
25974
|
};
|
|
26105
25975
|
}
|
|
26106
|
-
/** 有 token 用量的会话:活动计数与列表都限定在这一批会话里,口径一致(读汇总表,几百行)。 */
|
|
26107
25976
|
function hasUsageSession(column) {
|
|
26108
25977
|
return `EXISTS (SELECT 1 FROM t_session_usage ub WHERE ub.f_session_id = ${column})`;
|
|
26109
25978
|
}
|
|
@@ -26366,7 +26235,7 @@ var SqliteBackend = class SqliteBackend {
|
|
|
26366
26235
|
}).where(eq(tSessions.fSessionId, id)).run();
|
|
26367
26236
|
}
|
|
26368
26237
|
async deleteBridgeTail(id, fromSequence) {
|
|
26369
|
-
this.db.
|
|
26238
|
+
return this.db.$client.prepare("DELETE FROM t_session_events WHERE f_session_id = ? AND f_sequence >= ? RETURNING f_event_id").all(id, fromSequence).map((row) => row.f_event_id);
|
|
26370
26239
|
}
|
|
26371
26240
|
async getPrevBridge(id, sequence) {
|
|
26372
26241
|
return this.db.select({
|
|
@@ -26375,19 +26244,18 @@ var SqliteBackend = class SqliteBackend {
|
|
|
26375
26244
|
}).from(tSessionEvents).where(and(eq(tSessionEvents.fSessionId, id), eq(tSessionEvents.fSequence, sequence))).get();
|
|
26376
26245
|
}
|
|
26377
26246
|
async deleteSession(id) {
|
|
26378
|
-
this.db.
|
|
26247
|
+
const rows = this.db.$client.prepare("DELETE FROM t_session_events WHERE f_session_id = ? RETURNING f_event_id").all(id);
|
|
26379
26248
|
this.db.delete(tWorkspaceSessions).where(eq(tWorkspaceSessions.fSessionId, id)).run();
|
|
26380
26249
|
this.db.delete(tSessionProjcacheRows).where(eq(tSessionProjcacheRows.fSessionId, id)).run();
|
|
26381
26250
|
this.db.delete(tSessions).where(eq(tSessions.fSessionId, id)).run();
|
|
26251
|
+
return rows.map((row) => row.f_event_id);
|
|
26382
26252
|
}
|
|
26383
|
-
/** 事件行可能被多个会话共享(fork 派生),所以孤儿只能在全库范围内判定。 */
|
|
26384
26253
|
async collectOrphans() {
|
|
26385
26254
|
const referenced = this.db.select({ fEventId: tSessionEvents.fEventId }).from(tSessionEvents);
|
|
26386
26255
|
const info = this.db.delete(tEvents).where(notInArray(tEvents.fEventId, referenced)).run();
|
|
26387
26256
|
this.db.delete(tEventUsage).where(notInArray(tEventUsage.fEventId, this.db.select({ fEventId: tEvents.fEventId }).from(tEvents))).run();
|
|
26388
26257
|
return Number(info.changes);
|
|
26389
26258
|
}
|
|
26390
|
-
/** 父会话被删后留下的 subagent 会话:父已不在表里,或本来就没有父。 */
|
|
26391
26259
|
async listOrphanSubagentSessions() {
|
|
26392
26260
|
return this.db.$client.prepare(`SELECT f_session_id AS id FROM t_sessions
|
|
26393
26261
|
WHERE f_origin = 'subagent'
|
|
@@ -26395,18 +26263,20 @@ var SqliteBackend = class SqliteBackend {
|
|
|
26395
26263
|
OR f_parent_session NOT IN (SELECT f_session_id FROM t_sessions))`).all().map((row) => row.id);
|
|
26396
26264
|
}
|
|
26397
26265
|
async deleteSessions(ids) {
|
|
26398
|
-
if (ids.length === 0) return
|
|
26399
|
-
|
|
26266
|
+
if (ids.length === 0) return {
|
|
26267
|
+
deleted: 0,
|
|
26268
|
+
eventIds: []
|
|
26269
|
+
};
|
|
26270
|
+
const placeholders = ids.map(() => "?").join(", ");
|
|
26271
|
+
const rows = this.db.$client.prepare(`DELETE FROM t_session_events WHERE f_session_id IN (${placeholders}) RETURNING f_event_id`).all(...ids);
|
|
26400
26272
|
this.db.delete(tWorkspaceSessions).where(inArray(tWorkspaceSessions.fSessionId, ids)).run();
|
|
26401
26273
|
this.db.delete(tSessionProjcacheRows).where(inArray(tSessionProjcacheRows.fSessionId, ids)).run();
|
|
26402
26274
|
const info = this.db.delete(tSessions).where(inArray(tSessions.fSessionId, ids)).run();
|
|
26403
|
-
return
|
|
26275
|
+
return {
|
|
26276
|
+
deleted: Number(info.changes),
|
|
26277
|
+
eventIds: rows.map((row) => row.f_event_id)
|
|
26278
|
+
};
|
|
26404
26279
|
}
|
|
26405
|
-
/**
|
|
26406
|
-
* 用量聚合:token 用量沿 `t_event_usage` 的用量行,活动计数(轮次 / 步骤 / 用户输入 / 工具调用)
|
|
26407
|
-
* 沿 `t_events` 的事件类型数——两者限定在**有 token 用量的会话**里,时间范围各自按自己的
|
|
26408
|
-
* `f_created_at`(同一时刻写入,口径一致)。
|
|
26409
|
-
*/
|
|
26410
26280
|
async listSessionRows(query = {}) {
|
|
26411
26281
|
const needle = query.query?.trim().toLowerCase() ?? "";
|
|
26412
26282
|
const like = `%${needle}%`;
|
|
@@ -26446,12 +26316,6 @@ var SqliteBackend = class SqliteBackend {
|
|
|
26446
26316
|
total: Number(counted.n)
|
|
26447
26317
|
};
|
|
26448
26318
|
}
|
|
26449
|
-
/**
|
|
26450
|
-
* 用量聚合:只读三张派生统计表——事件级用量行给去重口径的总量与「天 × 模型」桶,
|
|
26451
|
-
* `t_session_usage` / `t_session_counts` 给按会话的行(含 fork 继承前缀)。
|
|
26452
|
-
* 归属、本地日与「是否被引用」都是物化列,这里不再现算、也不回连事件表。
|
|
26453
|
-
* 事件级按毫秒时间戳过滤、汇总表按本地日过滤——`sinceMs` 对齐到本地零点时两者等价。
|
|
26454
|
-
*/
|
|
26455
26319
|
async usageReport(sinceMs) {
|
|
26456
26320
|
const usageSince = sinceMs === void 0 ? "" : " AND f_created_at >= ?";
|
|
26457
26321
|
const params = sinceMs === void 0 ? [] : [sinceMs];
|
|
@@ -26537,7 +26401,6 @@ var SqliteBackend = class SqliteBackend {
|
|
|
26537
26401
|
}))
|
|
26538
26402
|
};
|
|
26539
26403
|
}
|
|
26540
|
-
/** token 用量旁路累加:派生表 `t_session_usage`,不在写事务里(失败可丢,表可销毁重建)。 */
|
|
26541
26404
|
async incrementSessionUsage(id, buckets) {
|
|
26542
26405
|
if (buckets.length === 0) return;
|
|
26543
26406
|
const statement = this.db.$client.prepare(`INSERT INTO t_session_usage (f_session_id, f_day, f_provider, f_model,
|
|
@@ -26551,7 +26414,6 @@ var SqliteBackend = class SqliteBackend {
|
|
|
26551
26414
|
f_total_tokens = f_total_tokens + excluded.f_total_tokens`);
|
|
26552
26415
|
for (const bucket of buckets) statement.run(id, bucket.day, bucket.provider ?? "", bucket.model ?? "", bucket.inputTokens, bucket.outputTokens, bucket.cacheReadTokens, bucket.reasoningTokens, bucket.totalTokens);
|
|
26553
26416
|
}
|
|
26554
|
-
/** 活动计数旁路累加:派生表 `t_session_counts`,同上(四项计数一次落)。 */
|
|
26555
26417
|
async incrementSessionCounts(id, buckets) {
|
|
26556
26418
|
if (buckets.length === 0) return;
|
|
26557
26419
|
const statement = this.db.$client.prepare(`INSERT INTO t_session_counts (f_session_id, f_day, f_turns, f_steps, f_user_inputs, f_tool_calls)
|
|
@@ -26563,7 +26425,6 @@ var SqliteBackend = class SqliteBackend {
|
|
|
26563
26425
|
f_tool_calls = f_tool_calls + excluded.f_tool_calls`);
|
|
26564
26426
|
for (const bucket of buckets) statement.run(id, bucket.day, bucket.turns, bucket.steps, bucket.userInputs, bucket.toolCalls);
|
|
26565
26427
|
}
|
|
26566
|
-
/** 按会话重算两张汇总表(rewind / fork 之后):先删该会话的行,再从事件表重算。 */
|
|
26567
26428
|
async rebuildSessionStats(id) {
|
|
26568
26429
|
this.db.$client.prepare("DELETE FROM t_session_usage WHERE f_session_id = ?").run(id);
|
|
26569
26430
|
this.db.$client.prepare(`INSERT INTO t_session_usage (
|
|
@@ -26590,15 +26451,15 @@ var SqliteBackend = class SqliteBackend {
|
|
|
26590
26451
|
WHERE b.f_session_id = ? AND e.f_type IN (${COUNTED_EVENT_TYPE_SQL$1})
|
|
26591
26452
|
GROUP BY b.f_session_id, 2`).run(id);
|
|
26592
26453
|
}
|
|
26593
|
-
|
|
26594
|
-
|
|
26595
|
-
|
|
26596
|
-
|
|
26597
|
-
|
|
26598
|
-
|
|
26599
|
-
|
|
26600
|
-
|
|
26601
|
-
|
|
26454
|
+
async refreshEventUsageFlags(eventIds) {
|
|
26455
|
+
for (let at = 0; at < eventIds.length; at += REFRESH_FLAG_CHUNK$1) {
|
|
26456
|
+
const chunk = eventIds.slice(at, at + REFRESH_FLAG_CHUNK$1);
|
|
26457
|
+
const placeholders = chunk.map(() => "?").join(", ");
|
|
26458
|
+
this.db.$client.prepare(`UPDATE t_event_usage SET
|
|
26459
|
+
f_referenced = ${withEvent(EVENT_REFERENCED_SQL, "f_event_id")},
|
|
26460
|
+
f_subagent = ${withEvent(EVENT_SUBAGENT_SQL, "f_event_id")}
|
|
26461
|
+
WHERE f_event_id IN (${placeholders})`).run(...chunk);
|
|
26462
|
+
}
|
|
26602
26463
|
}
|
|
26603
26464
|
async deleteSessionStats(id) {
|
|
26604
26465
|
this.db.$client.prepare("DELETE FROM t_session_usage WHERE f_session_id = ?").run(id);
|
|
@@ -26633,22 +26494,19 @@ async function migrate(db, config) {
|
|
|
26633
26494
|
//#region ../session-rdb/src/postgres.ts
|
|
26634
26495
|
const postgresMigrationsDir = fileURLToPath(new URL("../drizzle/postgres/", import.meta.url));
|
|
26635
26496
|
const pgWriteQueues = /* @__PURE__ */ new Map();
|
|
26636
|
-
|
|
26497
|
+
const REFRESH_FLAG_CHUNK = 400;
|
|
26637
26498
|
function enqueuePgWrite(key, fn) {
|
|
26638
26499
|
const run = (pgWriteQueues.get(key) ?? Promise.resolve()).then(fn);
|
|
26639
26500
|
pgWriteQueues.set(key, run.then(() => void 0, () => void 0));
|
|
26640
26501
|
return run;
|
|
26641
26502
|
}
|
|
26642
|
-
/** pg 的 text 列窄化:非字符串(含 null)都不当作文本。 */
|
|
26643
26503
|
function text(value) {
|
|
26644
26504
|
return typeof value === "string" ? value : null;
|
|
26645
26505
|
}
|
|
26646
|
-
/** pg 的 count/sum 回落成字符串:统一转成有限数。 */
|
|
26647
26506
|
function numeric(value) {
|
|
26648
26507
|
const parsed = typeof value === "number" ? value : Number(value ?? 0);
|
|
26649
26508
|
return Number.isFinite(parsed) ? parsed : 0;
|
|
26650
26509
|
}
|
|
26651
|
-
/** SQL 的缺失求和是 NULL:没有 usage 字段的行使该字段计 0。 */
|
|
26652
26510
|
function tokenTotalsOf(row) {
|
|
26653
26511
|
return {
|
|
26654
26512
|
inputTokens: numeric(row["input_tokens"]),
|
|
@@ -26666,7 +26524,6 @@ function emptyActivity() {
|
|
|
26666
26524
|
toolCalls: 0
|
|
26667
26525
|
};
|
|
26668
26526
|
}
|
|
26669
|
-
/** `t_session_counts` 的四项计数折成活动计数(pg 的 sum 回落成字符串)。 */
|
|
26670
26527
|
function activityTotalsOf(row) {
|
|
26671
26528
|
return {
|
|
26672
26529
|
turns: numeric(row["turns"]),
|
|
@@ -26675,7 +26532,6 @@ function activityTotalsOf(row) {
|
|
|
26675
26532
|
toolCalls: numeric(row["tool_calls"])
|
|
26676
26533
|
};
|
|
26677
26534
|
}
|
|
26678
|
-
/** `IN (...)` 用的类型字面量(与 `COUNTED_EVENT_TYPES` 同源)。 */
|
|
26679
26535
|
const COUNTED_EVENT_TYPE_SQL = COUNTED_EVENT_TYPES.map((type) => `'${type}'`).join(", ");
|
|
26680
26536
|
var PostgresBackend = class PostgresBackend {
|
|
26681
26537
|
db;
|
|
@@ -26862,15 +26718,21 @@ var PostgresBackend = class PostgresBackend {
|
|
|
26862
26718
|
}).where(eq(this.tables["t_sessions"].fSessionId, id)).execute();
|
|
26863
26719
|
}
|
|
26864
26720
|
async deleteBridgeTail(exec, id, fromSequence) {
|
|
26865
|
-
|
|
26721
|
+
const table = this.tables["t_session_events"];
|
|
26722
|
+
return (await exec.execute(sql`
|
|
26723
|
+
DELETE FROM ${table} WHERE f_session_id = ${id} AND f_sequence >= ${fromSequence}
|
|
26724
|
+
RETURNING f_event_id
|
|
26725
|
+
`)).rows.map((row) => row.f_event_id);
|
|
26866
26726
|
}
|
|
26867
26727
|
async deleteSession(exec, id) {
|
|
26868
|
-
await exec.
|
|
26728
|
+
const result = await exec.execute(sql`
|
|
26729
|
+
DELETE FROM ${this.tables["t_session_events"]} WHERE f_session_id = ${id} RETURNING f_event_id
|
|
26730
|
+
`);
|
|
26869
26731
|
await exec.delete(this.tables["t_workspace_sessions"]).where(eq(this.tables["t_workspace_sessions"].fSessionId, id)).execute();
|
|
26870
26732
|
await exec.delete(this.tables["t_session_projcache_row"]).where(eq(this.tables["t_session_projcache_row"].fSessionId, id)).execute();
|
|
26871
26733
|
await exec.delete(this.tables["t_sessions"]).where(eq(this.tables["t_sessions"].fSessionId, id)).execute();
|
|
26734
|
+
return result.rows.map((row) => row.f_event_id);
|
|
26872
26735
|
}
|
|
26873
|
-
/** 事件行可能被多个会话共享(fork 派生),所以孤儿只能在全库范围内判定。 */
|
|
26874
26736
|
async collectOrphans() {
|
|
26875
26737
|
const tEvents = this.tables["t_events"];
|
|
26876
26738
|
const tEventUsage = this.tables["t_event_usage"];
|
|
@@ -26879,7 +26741,6 @@ var PostgresBackend = class PostgresBackend {
|
|
|
26879
26741
|
await this.db.delete(tEventUsage).where(notInArray(tEventUsage.fEventId, this.db.select({ fEventId: tEvents.fEventId }).from(tEvents))).execute();
|
|
26880
26742
|
return result.rowCount ?? 0;
|
|
26881
26743
|
}
|
|
26882
|
-
/** 父会话被删后留下的 subagent 会话:父已不在表里,或本来就没有父。 */
|
|
26883
26744
|
async listOrphanSubagentSessions() {
|
|
26884
26745
|
const tSessions = this.tables["t_sessions"];
|
|
26885
26746
|
return (await this.db.execute(sql`
|
|
@@ -26890,16 +26751,26 @@ var PostgresBackend = class PostgresBackend {
|
|
|
26890
26751
|
`)).rows.map((row) => row.id);
|
|
26891
26752
|
}
|
|
26892
26753
|
async deleteSessions(exec, ids) {
|
|
26893
|
-
if (ids.length === 0) return
|
|
26894
|
-
|
|
26754
|
+
if (ids.length === 0) return {
|
|
26755
|
+
deleted: 0,
|
|
26756
|
+
eventIds: []
|
|
26757
|
+
};
|
|
26758
|
+
const removed = await exec.execute(sql`
|
|
26759
|
+
DELETE FROM ${this.tables["t_session_events"]}
|
|
26760
|
+
WHERE f_session_id IN (${sql.join(ids.map((id) => sql`${id}`), sql`, `)})
|
|
26761
|
+
RETURNING f_event_id
|
|
26762
|
+
`);
|
|
26895
26763
|
await exec.delete(this.tables["t_workspace_sessions"]).where(inArray(this.tables["t_workspace_sessions"].fSessionId, ids)).execute();
|
|
26896
26764
|
await exec.delete(this.tables["t_session_projcache_row"]).where(inArray(this.tables["t_session_projcache_row"].fSessionId, ids)).execute();
|
|
26897
|
-
|
|
26765
|
+
const result = await exec.delete(this.tables["t_sessions"]).where(inArray(this.tables["t_sessions"].fSessionId, ids)).execute();
|
|
26766
|
+
return {
|
|
26767
|
+
deleted: Number(result.rowCount ?? 0),
|
|
26768
|
+
eventIds: removed.rows.map((row) => row.f_event_id)
|
|
26769
|
+
};
|
|
26898
26770
|
}
|
|
26899
26771
|
async vacuum() {
|
|
26900
26772
|
await this.db.execute(sql`VACUUM ANALYZE`);
|
|
26901
26773
|
}
|
|
26902
|
-
/** 派生统计表的回填:任一表为空即按事件表全量重算(幂等,迁移删表重建后自愈)。 */
|
|
26903
26774
|
async backfillUsageTables() {
|
|
26904
26775
|
await this.backfillEventUsage();
|
|
26905
26776
|
await this.backfillSessionUsage();
|
|
@@ -26909,10 +26780,6 @@ var PostgresBackend = class PostgresBackend {
|
|
|
26909
26780
|
const existing = await this.db.execute(sql.raw(`SELECT count(*) AS n FROM ${table}`));
|
|
26910
26781
|
return Number(existing.rows[0]?.["n"] ?? 0) === 0;
|
|
26911
26782
|
}
|
|
26912
|
-
/**
|
|
26913
|
-
* 用量行:一条 `assistant/message` 事件行一行,物化本地日与两个归属标记。
|
|
26914
|
-
* 本地日按数据库会话时区渲染(部署前提是它与 host 时区一致,`localDayKey` 同口径)。
|
|
26915
|
-
*/
|
|
26916
26783
|
async backfillEventUsage() {
|
|
26917
26784
|
if (!await this.tableIsEmpty(this.qualifiedTable("t_event_usage"))) return;
|
|
26918
26785
|
const tEventUsage = this.tables["t_event_usage"];
|
|
@@ -26951,7 +26818,6 @@ var PostgresBackend = class PostgresBackend {
|
|
|
26951
26818
|
ON CONFLICT DO NOTHING
|
|
26952
26819
|
`);
|
|
26953
26820
|
}
|
|
26954
|
-
/** 会话 × 本地日 × 模型的 token 汇总:从桥接行 × 用量行重算(含 fork 继承前缀)。 */
|
|
26955
26821
|
async backfillSessionUsage() {
|
|
26956
26822
|
if (!await this.tableIsEmpty(this.qualifiedTable("t_session_usage"))) return;
|
|
26957
26823
|
const tSessionUsage = this.tables["t_session_usage"];
|
|
@@ -26970,7 +26836,6 @@ var PostgresBackend = class PostgresBackend {
|
|
|
26970
26836
|
ON CONFLICT DO NOTHING
|
|
26971
26837
|
`);
|
|
26972
26838
|
}
|
|
26973
|
-
/** 会话 × 本地日的活动计数:从桥接行 × 事件类型重算。 */
|
|
26974
26839
|
async backfillSessionCounts() {
|
|
26975
26840
|
if (!await this.tableIsEmpty(this.qualifiedTable("t_session_counts"))) return;
|
|
26976
26841
|
const tSessionCounts = this.tables["t_session_counts"];
|
|
@@ -26992,7 +26857,6 @@ var PostgresBackend = class PostgresBackend {
|
|
|
26992
26857
|
ON CONFLICT DO NOTHING
|
|
26993
26858
|
`);
|
|
26994
26859
|
}
|
|
26995
|
-
/** 用量聚合:与 SQLite 侧同形,读 `t_event_usage`,数值列回来是字符串。 */
|
|
26996
26860
|
async listSessionRows(query = {}) {
|
|
26997
26861
|
const needle = query.query?.trim().toLowerCase() ?? "";
|
|
26998
26862
|
const like = `%${needle}%`;
|
|
@@ -27042,10 +26906,6 @@ var PostgresBackend = class PostgresBackend {
|
|
|
27042
26906
|
total: numeric(counted.rows[0]?.["n"] ?? 0)
|
|
27043
26907
|
};
|
|
27044
26908
|
}
|
|
27045
|
-
/**
|
|
27046
|
-
* 用量聚合:与 SQLite 侧同形——只读三张派生统计表(用量行给去重口径的总量与「天 × 模型」桶,
|
|
27047
|
-
* 两张会话汇总表给按会话的行),数值列回来是字符串。物化列让这里不做 `EXISTS`、不回连事件表。
|
|
27048
|
-
*/
|
|
27049
26909
|
async usageReport(sinceMs) {
|
|
27050
26910
|
const usageSince = sinceMs === void 0 ? sql`` : sql` AND f_created_at >= ${sinceMs}`;
|
|
27051
26911
|
const dayFilter = (column) => sinceMs === void 0 ? sql`` : sql` AND ${sql.raw(column)} >= ${localDayKey(sinceMs)}`;
|
|
@@ -27135,12 +26995,10 @@ var PostgresBackend = class PostgresBackend {
|
|
|
27135
26995
|
}))
|
|
27136
26996
|
};
|
|
27137
26997
|
}
|
|
27138
|
-
/** 表名限定到配置的 schema。 */
|
|
27139
26998
|
qualifiedTable(name) {
|
|
27140
26999
|
const schema = this.options.schema ?? "public";
|
|
27141
27000
|
return schema === "public" ? name : `"${schema}".${name}`;
|
|
27142
27001
|
}
|
|
27143
|
-
/** token 用量旁路累加:派生表 `t_session_usage`,不在写事务里(失败可丢,表可销毁重建)。 */
|
|
27144
27002
|
async incrementSessionUsage(id, buckets) {
|
|
27145
27003
|
if (buckets.length === 0) return;
|
|
27146
27004
|
const tSessionUsage = this.tables["t_session_usage"];
|
|
@@ -27159,7 +27017,6 @@ var PostgresBackend = class PostgresBackend {
|
|
|
27159
27017
|
f_total_tokens = ${tSessionUsage}.f_total_tokens + EXCLUDED.f_total_tokens
|
|
27160
27018
|
`);
|
|
27161
27019
|
}
|
|
27162
|
-
/** 活动计数旁路累加:派生表 `t_session_counts`,同上(四项计数一次落)。 */
|
|
27163
27020
|
async incrementSessionCounts(id, buckets) {
|
|
27164
27021
|
if (buckets.length === 0) return;
|
|
27165
27022
|
const tSessionCounts = this.tables["t_session_counts"];
|
|
@@ -27175,7 +27032,6 @@ var PostgresBackend = class PostgresBackend {
|
|
|
27175
27032
|
f_tool_calls = ${tSessionCounts}.f_tool_calls + EXCLUDED.f_tool_calls
|
|
27176
27033
|
`);
|
|
27177
27034
|
}
|
|
27178
|
-
/** 按会话重算两张汇总表(rewind / fork 之后):先删该会话的行,再从事件表重算。 */
|
|
27179
27035
|
async rebuildSessionStats(id) {
|
|
27180
27036
|
const tSessionUsage = this.tables["t_session_usage"];
|
|
27181
27037
|
const tSessionCounts = this.tables["t_session_counts"];
|
|
@@ -27211,20 +27067,23 @@ var PostgresBackend = class PostgresBackend {
|
|
|
27211
27067
|
GROUP BY 1, 2
|
|
27212
27068
|
`);
|
|
27213
27069
|
}
|
|
27214
|
-
|
|
27215
|
-
async refreshEventUsageFlags() {
|
|
27070
|
+
async refreshEventUsageFlags(eventIds) {
|
|
27216
27071
|
const tEventUsage = this.tables["t_event_usage"];
|
|
27217
27072
|
const tSessionEvents = this.tables["t_session_events"];
|
|
27218
27073
|
const tSessions = this.tables["t_sessions"];
|
|
27219
|
-
|
|
27220
|
-
|
|
27221
|
-
|
|
27222
|
-
|
|
27223
|
-
|
|
27224
|
-
|
|
27225
|
-
|
|
27226
|
-
|
|
27227
|
-
|
|
27074
|
+
for (let at = 0; at < eventIds.length; at += REFRESH_FLAG_CHUNK) {
|
|
27075
|
+
const chunk = eventIds.slice(at, at + REFRESH_FLAG_CHUNK);
|
|
27076
|
+
await this.db.execute(sql`
|
|
27077
|
+
UPDATE ${tEventUsage} SET
|
|
27078
|
+
f_referenced = (EXISTS (SELECT 1 FROM ${tSessionEvents} rb
|
|
27079
|
+
WHERE rb.f_event_id = ${tEventUsage}.f_event_id))::int,
|
|
27080
|
+
f_subagent = (EXISTS (SELECT 1 FROM ${tSessionEvents} sb
|
|
27081
|
+
JOIN ${tSessions} ss ON ss.f_session_id = sb.f_session_id
|
|
27082
|
+
WHERE sb.f_event_id = ${tEventUsage}.f_event_id
|
|
27083
|
+
AND ss.f_origin = 'subagent'))::int
|
|
27084
|
+
WHERE f_event_id IN (${sql.join(chunk.map((id) => sql`${id}`), sql`, `)})
|
|
27085
|
+
`);
|
|
27086
|
+
}
|
|
27228
27087
|
}
|
|
27229
27088
|
async deleteSessionStats(id) {
|
|
27230
27089
|
await this.db.execute(sql`DELETE FROM ${this.tables["t_session_usage"]} WHERE f_session_id = ${id}`);
|
|
@@ -27284,11 +27143,6 @@ function physicalEvent(row) {
|
|
|
27284
27143
|
...row.fSurfaceOp !== null ? { surfaceOp: JSON.parse(row.fSurfaceOp) } : {}
|
|
27285
27144
|
};
|
|
27286
27145
|
}
|
|
27287
|
-
/**
|
|
27288
|
-
* 读旧格式用的 catalog:v3→v4 这条边要求显式子会话证据(上游 0.1.7 起),本仓库声明
|
|
27289
|
-
* **无子会话证据**——v3 的目录事实由父会话自己在成功路径上写成 `subagent/catalog` 事件,
|
|
27290
|
-
* 迁移的 child evidence 只是补偿入口;我们没有更权威的子集合来源可给(见 ADR-跟随上游session-format-v4)。
|
|
27291
|
-
*/
|
|
27292
27146
|
const restoreCatalog = createSessionFormatCatalogWithChildren([]);
|
|
27293
27147
|
function convertLegacyRows(row, eventRows) {
|
|
27294
27148
|
const restore = restoreCatalog.createRestore(physicalHeader(row), {
|
|
@@ -27466,7 +27320,6 @@ var SessionBranchRdbProvider = class {
|
|
|
27466
27320
|
internals.dropReuseEventIds(childId);
|
|
27467
27321
|
throw error;
|
|
27468
27322
|
}
|
|
27469
|
-
await this.persistence.rebuildSessionStats(childId);
|
|
27470
27323
|
return childId;
|
|
27471
27324
|
}
|
|
27472
27325
|
async rewind(id, toBoundary, signal) {
|
|
@@ -27539,7 +27392,6 @@ var SessionBranchRdbProvider = class {
|
|
|
27539
27392
|
headSeq: keepLength - 1,
|
|
27540
27393
|
snapshotEvents: () => []
|
|
27541
27394
|
});
|
|
27542
|
-
await this.persistence.rebuildSessionStats(id);
|
|
27543
27395
|
return {
|
|
27544
27396
|
header: rowToMeta(row),
|
|
27545
27397
|
revision: await internals.readStoredRevision(id)
|
|
@@ -27645,7 +27497,7 @@ var SessionBranchRdb = class extends SessionBranch {
|
|
|
27645
27497
|
};
|
|
27646
27498
|
//#endregion
|
|
27647
27499
|
//#region ../../../node_modules/.pnpm/fflate@0.8.3/node_modules/fflate/esm/index.mjs
|
|
27648
|
-
var require$1 = createRequire
|
|
27500
|
+
var require$1 = createRequire("/");
|
|
27649
27501
|
var _a$2;
|
|
27650
27502
|
var Worker;
|
|
27651
27503
|
var isMarkedAsUntransferable;
|
|
@@ -28814,7 +28666,6 @@ function unzip(data, opts, cb) {
|
|
|
28814
28666
|
//#endregion
|
|
28815
28667
|
//#region ../session-rdb/src/import.ts
|
|
28816
28668
|
const SESSION_LOG_ARTIFACT_FILENAME = "session.jsonl";
|
|
28817
|
-
const SESSION_IMPORT_PATH = "/api/session.import";
|
|
28818
28669
|
const MAX_IMPORT_ZIP_BYTES = 67108864;
|
|
28819
28670
|
function parseJsonlArtifact(content) {
|
|
28820
28671
|
const lines = content.split("\n");
|
|
@@ -28875,11 +28726,6 @@ function parseJsonlArtifact(content) {
|
|
|
28875
28726
|
events
|
|
28876
28727
|
};
|
|
28877
28728
|
}
|
|
28878
|
-
/**
|
|
28879
|
-
* fflate 的 `unzip` 是回调式异步 API(node 侧内部走 worker_threads,浏览器侧走 Web Worker),
|
|
28880
|
-
* 这里桥成 Promise。它与 `unzipSync` 的差异只在同步/异步:非法 zip 走的是同一段校验代码,
|
|
28881
|
-
* 拒绝的错误对象与同步版一致,因此上层文案无需区分。
|
|
28882
|
-
*/
|
|
28883
28729
|
function unzipAsync(data) {
|
|
28884
28730
|
return new Promise((resolve, reject) => {
|
|
28885
28731
|
unzip(data, (error, entries) => {
|
|
@@ -29020,7 +28866,6 @@ function registerSessionImport(ctx, persistence) {
|
|
|
29020
28866
|
}
|
|
29021
28867
|
//#endregion
|
|
29022
28868
|
//#region ../session-rdb/src/deletion.ts
|
|
29023
|
-
const SESSION_DELETE_PATH = "/api/session.delete";
|
|
29024
28869
|
const STATUS_OF_DELETION_ERROR = {
|
|
29025
28870
|
SESSION_NOT_FOUND: 404,
|
|
29026
28871
|
SESSION_NOT_ARCHIVED: 409,
|
|
@@ -29083,12 +28928,9 @@ function registerSessionDeletion(ctx, persistence) {
|
|
|
29083
28928
|
}
|
|
29084
28929
|
//#endregion
|
|
29085
28930
|
//#region ../session-rdb/src/export.ts
|
|
29086
|
-
const SESSION_EXPORT_PATH = "/api/session.export";
|
|
29087
|
-
/** 文件名只保留安全字符:会话 id 来自请求体,会写进 Content-Disposition。 */
|
|
29088
28931
|
function safeFilename(sessionId) {
|
|
29089
28932
|
return sessionId.replace(/[^A-Za-z0-9._-]/gu, "_");
|
|
29090
28933
|
}
|
|
29091
|
-
/** fflate 只给回调式异步 API(同步变体被 node/no-sync 禁止)。 */
|
|
29092
28934
|
function zipBytes(files) {
|
|
29093
28935
|
return new Promise((resolve, reject) => {
|
|
29094
28936
|
zip(files, (error, data) => {
|
|
@@ -29097,7 +28939,6 @@ function zipBytes(files) {
|
|
|
29097
28939
|
});
|
|
29098
28940
|
});
|
|
29099
28941
|
}
|
|
29100
|
-
/** 导出通道:直接把会话日志打成 zip 响应体,与导入通道读同一份 artifact。 */
|
|
29101
28942
|
function registerSessionExport(ctx, persistence) {
|
|
29102
28943
|
ctx.inject(["webServer", "connection"], (webCtx) => {
|
|
29103
28944
|
const webServer = webCtx.webServer;
|
|
@@ -29157,15 +28998,12 @@ function registerSessionExport(ctx, persistence) {
|
|
|
29157
28998
|
}
|
|
29158
28999
|
//#endregion
|
|
29159
29000
|
//#region ../session-rdb/src/gc.ts
|
|
29160
|
-
const SESSION_GC_PATH = "/api/session.gc";
|
|
29161
|
-
/** 让所有运行中的 agent 先退场:GC 要重写事件表,运行中的写路径必须停下来。 */
|
|
29162
29001
|
async function stopRunningAgents(ctx) {
|
|
29163
29002
|
const running = ctx.get("agents")?.list() ?? [];
|
|
29164
29003
|
for (const agent of running) agent.cancel?.({ kind: "user" }, { keepInbox: true });
|
|
29165
29004
|
await Promise.all(running.map((agent) => agent.whenIdle()));
|
|
29166
29005
|
return running.length;
|
|
29167
29006
|
}
|
|
29168
|
-
/** GC 通道:停 agent → 回收孤儿事件行 → VACUUM,一次请求完成。 */
|
|
29169
29007
|
function registerSessionGc(ctx, persistence) {
|
|
29170
29008
|
ctx.inject(["webServer", "connection"], (webCtx) => {
|
|
29171
29009
|
const webServer = webCtx.webServer;
|
|
@@ -29207,15 +29045,6 @@ function registerSessionGc(ctx, persistence) {
|
|
|
29207
29045
|
}
|
|
29208
29046
|
//#endregion
|
|
29209
29047
|
//#region ../session-rdb/src/rows.ts
|
|
29210
|
-
/**
|
|
29211
|
-
* 管理面的会话列表:**我们自己的**一条路由(完整语料,含归档),与官方 `session/list` 分开。
|
|
29212
|
-
*
|
|
29213
|
-
* 官方那条(`session/list` → `ctx.sessionQuery.listSessions`)按部署策略默认排除已归档——那是给上游
|
|
29214
|
-
* UI 用的。归档集的管理动作要有完整列表,所以这里单独给一条:标题直接从会话列取,最后活动时间用
|
|
29215
|
-
* 该会话最后一个事件的时间。
|
|
29216
|
-
*/
|
|
29217
|
-
const SESSION_ROWS_PATH = "/api/session.rows";
|
|
29218
|
-
/** 一页的条数上限:超过就按上限截断(这个接口给页面用,不是导出通道)。 */
|
|
29219
29048
|
const MAX_PAGE_SIZE = 200;
|
|
29220
29049
|
const DEFAULT_PAGE_SIZE = 20;
|
|
29221
29050
|
function registerSessionRows(ctx, backend) {
|
|
@@ -36201,19 +36030,12 @@ var SessionQueryRdb = class extends SessionQueryEngine {
|
|
|
36201
36030
|
constructor(ctx, config = {}) {
|
|
36202
36031
|
super(ctx, config);
|
|
36203
36032
|
}
|
|
36204
|
-
/**
|
|
36205
|
-
* 官方列表(`session/list`)**默认不含已归档**:归档是「收起来」的会话,列表与搜索都不该把它混进
|
|
36206
|
-
* 日常集合(上游 client 再用 workspace registry 的归档集做「只看归档」的派生)。
|
|
36207
|
-
*
|
|
36208
|
-
* 完整语料(含归档)走我们自己的管理面路由 `SESSION_ROWS_PATH`——两条路分开,不在一处混。
|
|
36209
|
-
*/
|
|
36210
36033
|
async listSessions(signal) {
|
|
36211
36034
|
const records = await super.listSessions(signal);
|
|
36212
36035
|
const archived = this.archivedSessionIds();
|
|
36213
36036
|
if (archived.size === 0) return records;
|
|
36214
36037
|
return records.filter((record) => !archived.has(String(record.header.id)));
|
|
36215
36038
|
}
|
|
36216
|
-
/** 归档集合来自 workspace registry;服务未就绪时保守返回空集(宁可多给,不可漏掉可见会话)。 */
|
|
36217
36039
|
archivedSessionIds() {
|
|
36218
36040
|
const registry = this.ctx.get("workspaceRegistry");
|
|
36219
36041
|
return new Set(registry?.archivedSessionIds ?? []);
|
|
@@ -36303,10 +36125,6 @@ var SessionProjectionCacheRdb = class extends Service {
|
|
|
36303
36125
|
if (record === void 0) return void 0;
|
|
36304
36126
|
return identityMatches(record.identity, expected) ? record : void 0;
|
|
36305
36127
|
}
|
|
36306
|
-
/**
|
|
36307
|
-
* header-only 读(会话列表这类):上游 0.1.7 起只给 header 与 keys——它要求的是**生命周期**身份,
|
|
36308
|
-
* 不再要求调用方提供 inherited cut(那个 cut 只有写路径与 hydration 用得上)。
|
|
36309
|
-
*/
|
|
36310
36128
|
cachedSnapshot(meta, keys) {
|
|
36311
36129
|
const record = this.recordForLifecycle(meta);
|
|
36312
36130
|
const snapshot = record === void 0 ? void 0 : this.viewRecord(record, keys);
|
|
@@ -36449,7 +36267,6 @@ function detachJson(rows) {
|
|
|
36449
36267
|
if (text === void 0) throw new TypeError("projection checkpoint is not losslessly JSON-serializable");
|
|
36450
36268
|
return JSON.parse(text);
|
|
36451
36269
|
}
|
|
36452
|
-
/** header 能证明的生命周期身份(不含 inherited cut):header-only 读只比对它。 */
|
|
36453
36270
|
function lifecycleIdentityOf(header) {
|
|
36454
36271
|
return {
|
|
36455
36272
|
formatVersion: header.version,
|
|
@@ -36622,7 +36439,6 @@ function installStorageTakeover(ctx, options) {
|
|
|
36622
36439
|
//#endregion
|
|
36623
36440
|
//#region ../session-rdb/src/index.ts
|
|
36624
36441
|
init_esm();
|
|
36625
|
-
/** 一批事件里的最大时间:写路径据此推进会话行的「最后活动时间」。 */
|
|
36626
36442
|
function maxEventTime(events) {
|
|
36627
36443
|
let max;
|
|
36628
36444
|
for (const event of events) {
|
|
@@ -36890,12 +36706,7 @@ var RdbSessionHandle = class RdbSessionHandle {
|
|
|
36890
36706
|
}
|
|
36891
36707
|
static LIVE_WRITE_BATCH_MAX_DELAY_MS = 200;
|
|
36892
36708
|
};
|
|
36893
|
-
/**
|
|
36894
|
-
* 本地化说明:`description()` 的类型签名只声明 `string`,而 meta 本身接受 `Dict<string>`
|
|
36895
|
-
* (`vendor/schemastery/src/index.ts` 的 `mergeDesc` 就是按字典合并的),所以这里只做一次类型放行。
|
|
36896
|
-
*/
|
|
36897
36709
|
const localized = (text) => text;
|
|
36898
|
-
/** 投影 checkpoint 的写回节流:两个分支共用。 */
|
|
36899
36710
|
const projectionCacheSchema = Schema.object({
|
|
36900
36711
|
writeEveryEvents: Schema.natural().min(1).default(DEFAULT_PROJECTION_WRITE_EVERY_EVENTS).description(localized({
|
|
36901
36712
|
zh: "每累计这么多事件写回一次投影 checkpoint。",
|
|
@@ -36955,7 +36766,6 @@ var SessionPersistenceRdb = class extends SessionPersistence {
|
|
|
36955
36766
|
reuseEventIds = /* @__PURE__ */ new Map();
|
|
36956
36767
|
liveBuffers = /* @__PURE__ */ new Map();
|
|
36957
36768
|
liveReady = /* @__PURE__ */ new Map();
|
|
36958
|
-
/** 本次装配用的配置快照(整段 Config 是 volatile,重挂会给新的)。 */
|
|
36959
36769
|
config;
|
|
36960
36770
|
constructor(ctx, config, injectedBackend) {
|
|
36961
36771
|
super(ctx);
|
|
@@ -37098,27 +36908,24 @@ var SessionPersistenceRdb = class extends SessionPersistence {
|
|
|
37098
36908
|
if (row.fArchivedAt === null) throw new SessionDeletionError(`session "${id}" is not archived; only archived sessions can be deleted`, "SESSION_NOT_ARCHIVED");
|
|
37099
36909
|
if (this.tracker.hasPending(id) || this.tracker.hasOpenHandle(id)) throw new SessionDeletionError(`session "${id}" is live; stop it before deleting`, "SESSION_LIVE");
|
|
37100
36910
|
await this.unarchiveBeforeDeletion(id);
|
|
37101
|
-
await this.backend.transaction(
|
|
37102
|
-
await tx.deleteSession(id);
|
|
37103
|
-
});
|
|
36911
|
+
const referenced = await this.backend.transaction((tx) => tx.deleteSession(id));
|
|
37104
36912
|
this.liveBuffers.delete(id);
|
|
37105
36913
|
this.liveReady.delete(id);
|
|
37106
36914
|
this.liveFailures.delete(id);
|
|
37107
36915
|
this.liveDropWarned.delete(id);
|
|
37108
36916
|
this.reuseEventIds.delete(id);
|
|
37109
36917
|
await this.dropSessionStats(id);
|
|
36918
|
+
await this.refreshEventUsageFlags(referenced);
|
|
37110
36919
|
}
|
|
37111
|
-
/** GC 通道:回收已无桥接行引用的事件行(孤儿),返回删除行数。 */
|
|
37112
36920
|
async collectOrphans() {
|
|
37113
36921
|
await this.ready;
|
|
37114
36922
|
return this.backend.collectOrphans();
|
|
37115
36923
|
}
|
|
37116
|
-
/** GC 通道:回收父已不存在的 subagent 会话(live 的跳过),返回删除的会话数。 */
|
|
37117
36924
|
async collectOrphanSessions() {
|
|
37118
36925
|
await this.ready;
|
|
37119
36926
|
const deletable = (await this.backend.listOrphanSubagentSessions()).filter((id) => !this.tracker.hasPending(id) && !this.tracker.hasOpenHandle(id));
|
|
37120
36927
|
if (deletable.length === 0) return 0;
|
|
37121
|
-
const deleted = await this.backend.transaction((tx) => tx.deleteSessions(deletable));
|
|
36928
|
+
const { deleted, eventIds } = await this.backend.transaction((tx) => tx.deleteSessions(deletable));
|
|
37122
36929
|
for (const id of deletable) {
|
|
37123
36930
|
this.liveBuffers.delete(id);
|
|
37124
36931
|
this.liveReady.delete(id);
|
|
@@ -37127,14 +36934,13 @@ var SessionPersistenceRdb = class extends SessionPersistence {
|
|
|
37127
36934
|
this.reuseEventIds.delete(id);
|
|
37128
36935
|
await this.dropSessionStats(id);
|
|
37129
36936
|
}
|
|
36937
|
+
await this.refreshEventUsageFlags(eventIds);
|
|
37130
36938
|
return deleted;
|
|
37131
36939
|
}
|
|
37132
|
-
/** 用量统计:SQL 聚合的按天 × 模型桶与按会话行(事件行去重、排除孤儿行)。 */
|
|
37133
36940
|
async usageReport(sinceMs) {
|
|
37134
36941
|
await this.ready;
|
|
37135
36942
|
return this.backend.usageReport(sinceMs);
|
|
37136
36943
|
}
|
|
37137
|
-
/** GC 通道:VACUUM;调用方需先停止运行中的写路径(见 `registerSessionGc`)。 */
|
|
37138
36944
|
async vacuum() {
|
|
37139
36945
|
await this.ready;
|
|
37140
36946
|
await this.backend.vacuum();
|
|
@@ -37268,11 +37074,6 @@ var SessionPersistenceRdb = class extends SessionPersistence {
|
|
|
37268
37074
|
...tornFrom !== void 0 ? { tornFrom } : {}
|
|
37269
37075
|
};
|
|
37270
37076
|
}
|
|
37271
|
-
/**
|
|
37272
|
-
* 统计衍生表的**旁路累加**:不在写事务里、失败只 warn——两张会话汇总表都是可销毁重建的
|
|
37273
|
-
* 派生表,丢几次累加不影响可用性(需要时用 `rebuildSessionStats` 重算)。
|
|
37274
|
-
* token 走写路径已折好的用量行,活动计数走本批事件类型。
|
|
37275
|
-
*/
|
|
37276
37077
|
async recordSessionStats(id, events, usageRows) {
|
|
37277
37078
|
const usageBuckets = sessionUsageBuckets(usageRows);
|
|
37278
37079
|
if (usageBuckets.length > 0) try {
|
|
@@ -37287,33 +37088,36 @@ var SessionPersistenceRdb = class extends SessionPersistence {
|
|
|
37287
37088
|
this.ctx.logger.warn(`session-rdb: activity counts for "${id}" not updated (${describeError(error)})`);
|
|
37288
37089
|
}
|
|
37289
37090
|
}
|
|
37290
|
-
/**
|
|
37291
|
-
* 重算一个会话的统计(rewind / fork 之后;best-effort,同 `recordSessionStats`):
|
|
37292
|
-
* 两张汇总表按会话重算,用量行的引用标记全量重算(引用可能跨会话消失)。
|
|
37293
|
-
*/
|
|
37294
37091
|
async rebuildSessionStats(id) {
|
|
37295
37092
|
try {
|
|
37296
37093
|
await this.backend.rebuildSessionStats(id);
|
|
37297
|
-
await this.backend.refreshEventUsageFlags();
|
|
37298
37094
|
} catch (error) {
|
|
37299
37095
|
this.ctx.logger.warn(`session-rdb: session stats for "${id}" not rebuilt (${describeError(error)})`);
|
|
37300
37096
|
}
|
|
37301
37097
|
}
|
|
37302
|
-
|
|
37098
|
+
async refreshEventUsageFlags(eventIds) {
|
|
37099
|
+
const unique = [...new Set(eventIds)];
|
|
37100
|
+
if (unique.length === 0) return;
|
|
37101
|
+
try {
|
|
37102
|
+
await this.backend.refreshEventUsageFlags(unique);
|
|
37103
|
+
} catch (error) {
|
|
37104
|
+
this.ctx.logger.warn(`session-rdb: usage reference flags not refreshed (${describeError(error)})`);
|
|
37105
|
+
}
|
|
37106
|
+
}
|
|
37303
37107
|
async dropSessionStats(id) {
|
|
37304
37108
|
try {
|
|
37305
37109
|
await this.backend.deleteSessionStats(id);
|
|
37306
|
-
await this.backend.refreshEventUsageFlags();
|
|
37307
37110
|
} catch (error) {
|
|
37308
37111
|
this.ctx.logger.warn(`session-rdb: session stats for "${id}" not deleted (${describeError(error)})`);
|
|
37309
37112
|
}
|
|
37310
37113
|
}
|
|
37311
37114
|
async rewriteMigratedLog(id, log) {
|
|
37115
|
+
let removed = [];
|
|
37312
37116
|
await this.backend.transaction(async (tx) => {
|
|
37313
37117
|
const head = await tx.getHead(id);
|
|
37314
37118
|
if (!this.writeGuard.has(id)) this.writeGuard.confirmHead(id, head.fHeadSequence);
|
|
37315
37119
|
this.writeGuard.assertNoConcurrentWriter(id, head.fHeadSequence);
|
|
37316
|
-
await tx.deleteBridgeTail(id, 0);
|
|
37120
|
+
removed = await tx.deleteBridgeTail(id, 0);
|
|
37317
37121
|
await tx.upsertSession({
|
|
37318
37122
|
meta: log.meta,
|
|
37319
37123
|
inheritedEventCount: SessionLogOffset(log.inheritedEventCount)
|
|
@@ -37326,6 +37130,7 @@ var SessionPersistenceRdb = class extends SessionPersistence {
|
|
|
37326
37130
|
await tx.bumpRevision(id, maxEventTime(log.events));
|
|
37327
37131
|
});
|
|
37328
37132
|
await this.rebuildSessionStats(id);
|
|
37133
|
+
await this.refreshEventUsageFlags(removed);
|
|
37329
37134
|
}
|
|
37330
37135
|
async listSnapshots(signal) {
|
|
37331
37136
|
signal?.throwIfAborted();
|
|
@@ -37415,7 +37220,6 @@ var SessionPersistenceRdb = class extends SessionPersistence {
|
|
|
37415
37220
|
registerReuseEventIds(childId, map) {
|
|
37416
37221
|
this.reuseEventIds.set(childId, new Map(map));
|
|
37417
37222
|
}
|
|
37418
|
-
/** 丢弃一个还没被消费的复用映射(fork 失败时):留着会让同一个 childId 之后的落写复用父会话的事件行。 */
|
|
37419
37223
|
dropReuseEventIds(childId) {
|
|
37420
37224
|
this.reuseEventIds.delete(childId);
|
|
37421
37225
|
}
|
|
@@ -37555,7 +37359,6 @@ function seedCoversPrefix(seed, prefix) {
|
|
|
37555
37359
|
return seedEvent !== void 0 && JSON.stringify(seedEvent) === JSON.stringify(event);
|
|
37556
37360
|
});
|
|
37557
37361
|
}
|
|
37558
|
-
/** 一批事件折成活动计数的桶:只数四种类型,按本地日聚合成四项。 */
|
|
37559
37362
|
function sessionCountBuckets(events) {
|
|
37560
37363
|
const counted = /* @__PURE__ */ new Map();
|
|
37561
37364
|
for (const event of events) {
|
|
@@ -37573,7 +37376,6 @@ function sessionCountBuckets(events) {
|
|
|
37573
37376
|
}
|
|
37574
37377
|
return [...counted.values()];
|
|
37575
37378
|
}
|
|
37576
|
-
/** 一批用量行折成 token 桶:按「本地日 × 模型」聚合(模型未知落空串,与表口径一致)。 */
|
|
37577
37379
|
function sessionUsageBuckets(usageRows) {
|
|
37578
37380
|
const counted = /* @__PURE__ */ new Map();
|
|
37579
37381
|
for (const row of usageRows) {
|
|
@@ -37927,4 +37729,4 @@ function turnLog(base, turn, opts = {}) {
|
|
|
37927
37729
|
return events;
|
|
37928
37730
|
}
|
|
37929
37731
|
//#endregion
|
|
37930
|
-
export {
|
|
37732
|
+
export { SESSION_EDITOR_PATHS, Session, SessionIdBrand, SessionSeq, SessionStore, TokenMeter, assistantMessage, createPersisted, harness, meta, oneTurnLog, parseJsonlArtifact, turnLog, twoTurnLog, userMessage };
|