@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.
- package/README.md +17 -10
- package/dist/artifact.d.mts +2 -101
- package/dist/artifact.mjs +2 -2
- package/dist/{branch-BjUR6wfe.mjs → branch-7eOh_tbO.mjs} +3 -3
- package/dist/deletion.d.mts +1 -1
- package/dist/deletion.mjs +1 -1
- package/dist/import.d.mts +1 -1
- package/dist/import.mjs +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +3 -3
- package/dist/{backend-E0bmTXpz.d.mts → log-BbleqD1M.d.mts} +171 -24
- package/dist/{log-qCNkDjh5.mjs → log-DTcvsMz7.mjs} +189 -12
- package/dist/{sqlite-zcXVyMxr.mjs → sqlite-ChM_sHAG.mjs} +320 -259
- package/dist/{src-B9X8BKyh.mjs → src-BWqePRA7.mjs} +290 -162
- package/dist/storage.d.mts +20 -7
- package/dist/storage.mjs +2 -2
- package/dist/testing.d.mts +1 -1
- package/dist/testing.mjs +1 -1
- 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 +20 -20
- 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 +109 -43
- 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
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { _ as
|
|
3
|
-
import { c as isLegacyVersion, o as adoptLegacyRows, s as convertLegacyRows, t as SessionBranchRdb } from "./branch-
|
|
1
|
+
import { C as postgresTableDefs, T as WriteGuard, a as EVENT_ENCODING, c as eventDimensions, i as DEFAULT_BUSY_TIMEOUT_MS, r as createStorageRepository, t as SqliteBackend, w as toPostgresSchema } from "./sqlite-ChM_sHAG.mjs";
|
|
2
|
+
import { A as registerSessionUsage, C as COUNTED_EVENT_TYPES, D as addTotals, E as addTokenTotals, O as emptyTotals, S as usageRowOf, T as addActivityTotals, _ as sessionConflictRow, b as titleOfEventData, g as sealOwnEvents, h as scanRows, k as localDayKey, m as rowToMeta, r as needsShapeAdoption, u as repairReadView, v as sessionInsertRow, w as addActivityCount, x as toJsonlArtifact } from "./log-DTcvsMz7.mjs";
|
|
3
|
+
import { c as isLegacyVersion, o as adoptLegacyRows, s as convertLegacyRows, t as SessionBranchRdb } from "./branch-7eOh_tbO.mjs";
|
|
4
4
|
import { registerSessionImport } from "./import.mjs";
|
|
5
5
|
import z from "@deepseek-ai/schemastery";
|
|
6
6
|
import { randomUUID } from "node:crypto";
|
|
@@ -54,6 +54,15 @@ function emptyActivity() {
|
|
|
54
54
|
toolCalls: 0
|
|
55
55
|
};
|
|
56
56
|
}
|
|
57
|
+
/** `t_session_counts` 的四项计数折成活动计数(pg 的 sum 回落成字符串)。 */
|
|
58
|
+
function activityTotalsOf(row) {
|
|
59
|
+
return {
|
|
60
|
+
turns: numeric(row["turns"]),
|
|
61
|
+
steps: numeric(row["steps"]),
|
|
62
|
+
userInputs: numeric(row["user_inputs"]),
|
|
63
|
+
toolCalls: numeric(row["tool_calls"])
|
|
64
|
+
};
|
|
65
|
+
}
|
|
57
66
|
/** `IN (...)` 用的类型字面量(与 `COUNTED_EVENT_TYPES` 同源)。 */
|
|
58
67
|
const COUNTED_EVENT_TYPE_SQL = COUNTED_EVENT_TYPES.map((type) => `'${type}'`).join(", ");
|
|
59
68
|
var PostgresBackend = class PostgresBackend {
|
|
@@ -107,9 +116,7 @@ var PostgresBackend = class PostgresBackend {
|
|
|
107
116
|
if (await this.readMeta(this.db, "schema_version") === "2") await this.baselineV2();
|
|
108
117
|
}
|
|
109
118
|
await migrate(this.db, { migrationsFolder: postgresMigrationsDir });
|
|
110
|
-
await this.
|
|
111
|
-
await this.backfillEventCounts();
|
|
112
|
-
await this.backfillEventUsage();
|
|
119
|
+
await this.backfillUsageTables();
|
|
113
120
|
const storeId = await this.db.transaction(async (tx) => {
|
|
114
121
|
await tx.insert(this.tables["t_persistence_state"]).values({
|
|
115
122
|
fSingleton: 1,
|
|
@@ -170,7 +177,7 @@ var PostgresBackend = class PostgresBackend {
|
|
|
170
177
|
getHead: (id) => this.getHead(tx, id),
|
|
171
178
|
getSeedLength: (id) => this.getSeedLength(tx, id),
|
|
172
179
|
updateSeedLength: (id, seedLength) => this.updateSeedLength(tx, id, seedLength),
|
|
173
|
-
insertEvents: (events) => this.insertEvents(tx, events),
|
|
180
|
+
insertEvents: (events, usageRows) => this.insertEvents(tx, events, usageRows),
|
|
174
181
|
insertBridges: (rows) => this.insertBridges(tx, rows),
|
|
175
182
|
updateHead: (id, headEventId, headSequence) => this.updateHead(tx, id, headEventId, headSequence),
|
|
176
183
|
bumpRevision: (id, lastEventAt) => this.bumpRevision(tx, id, lastEventAt),
|
|
@@ -207,14 +214,10 @@ var PostgresBackend = class PostgresBackend {
|
|
|
207
214
|
await exec.update(this.tables["t_sessions"]).set({ fSeedLength: seedLength }).where(eq(this.tables["t_sessions"].fSessionId, id)).execute();
|
|
208
215
|
}
|
|
209
216
|
static INSERT_BATCH_ROWS = 1e3;
|
|
210
|
-
async insertEvents(exec, events) {
|
|
217
|
+
async insertEvents(exec, events, usageRows) {
|
|
211
218
|
if (events.length === 0) return;
|
|
212
219
|
for (let i = 0; i < events.length; i += PostgresBackend.INSERT_BATCH_ROWS) await exec.insert(this.tables["t_events"]).values(events.slice(i, i + PostgresBackend.INSERT_BATCH_ROWS).map((event) => ({ ...event }))).execute();
|
|
213
|
-
|
|
214
|
-
const row = usageRowOf(event);
|
|
215
|
-
return row === void 0 ? [] : [row];
|
|
216
|
-
});
|
|
217
|
-
if (usage.length > 0) await exec.insert(this.tables["t_event_usage"]).values(usage).onConflictDoNothing().execute();
|
|
220
|
+
if (usageRows.length > 0) for (let i = 0; i < usageRows.length; i += PostgresBackend.INSERT_BATCH_ROWS) await exec.insert(this.tables["t_event_usage"]).values(usageRows.slice(i, i + PostgresBackend.INSERT_BATCH_ROWS).map((row) => ({ ...row }))).onConflictDoNothing().execute();
|
|
218
221
|
}
|
|
219
222
|
async insertBridges(exec, rows) {
|
|
220
223
|
if (rows.length === 0) return;
|
|
@@ -284,21 +287,41 @@ var PostgresBackend = class PostgresBackend {
|
|
|
284
287
|
async vacuum() {
|
|
285
288
|
await this.db.execute(sql`VACUUM ANALYZE`);
|
|
286
289
|
}
|
|
287
|
-
/**
|
|
290
|
+
/** 派生统计表的回填:任一表为空即按事件表全量重算(幂等,迁移删表重建后自愈)。 */
|
|
291
|
+
async backfillUsageTables() {
|
|
292
|
+
await this.backfillEventUsage();
|
|
293
|
+
await this.backfillSessionUsage();
|
|
294
|
+
await this.backfillSessionCounts();
|
|
295
|
+
}
|
|
296
|
+
async tableIsEmpty(table) {
|
|
297
|
+
const existing = await this.db.execute(sql.raw(`SELECT count(*) AS n FROM ${table}`));
|
|
298
|
+
return Number(existing.rows[0]?.["n"] ?? 0) === 0;
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* 用量行:一条 `assistant/message` 事件行一行,物化本地日与两个归属标记。
|
|
302
|
+
* 本地日按数据库会话时区渲染(部署前提是它与 host 时区一致,`localDayKey` 同口径)。
|
|
303
|
+
*/
|
|
288
304
|
async backfillEventUsage() {
|
|
305
|
+
if (!await this.tableIsEmpty(this.qualifiedTable("t_event_usage"))) return;
|
|
289
306
|
const tEventUsage = this.tables["t_event_usage"];
|
|
290
307
|
const tEvents = this.tables["t_events"];
|
|
291
|
-
const
|
|
292
|
-
|
|
308
|
+
const tSessionEvents = this.tables["t_session_events"];
|
|
309
|
+
const tSessions = this.tables["t_sessions"];
|
|
293
310
|
await this.db.execute(sql`
|
|
294
311
|
INSERT INTO ${tEventUsage} (
|
|
295
|
-
f_event_id, f_created_at, f_provider, f_model,
|
|
312
|
+
f_event_id, f_created_at, f_day, f_provider, f_model, f_referenced, f_subagent,
|
|
296
313
|
f_input_tokens, f_output_tokens, f_cache_read_tokens, f_reasoning_tokens, f_total_tokens)
|
|
297
314
|
SELECT e.f_event_id, e.f_created_at,
|
|
315
|
+
to_char(to_timestamp(e.f_created_at / 1000.0), 'YYYY-MM-DD'),
|
|
298
316
|
coalesce(e.f_data::json #>> '{data,message,source,provider}',
|
|
299
317
|
e.f_data::json #>> '{message,source,provider}'),
|
|
300
318
|
coalesce(e.f_data::json #>> '{data,message,source,model}',
|
|
301
319
|
e.f_data::json #>> '{message,source,model}'),
|
|
320
|
+
(EXISTS (SELECT 1 FROM ${tSessionEvents} rb
|
|
321
|
+
WHERE rb.f_event_id = e.f_event_id))::int,
|
|
322
|
+
(EXISTS (SELECT 1 FROM ${tSessionEvents} sb
|
|
323
|
+
JOIN ${tSessions} ss ON ss.f_session_id = sb.f_session_id
|
|
324
|
+
WHERE sb.f_event_id = e.f_event_id AND ss.f_origin = 'subagent'))::int,
|
|
302
325
|
coalesce((e.f_data::json #>> '{data,usage,inputTokens}')::integer,
|
|
303
326
|
(e.f_data::json #>> '{usage,inputTokens}')::integer, 0),
|
|
304
327
|
coalesce((e.f_data::json #>> '{data,usage,outputTokens}')::integer,
|
|
@@ -314,6 +337,47 @@ var PostgresBackend = class PostgresBackend {
|
|
|
314
337
|
AND (e.f_data::json #> '{data,usage}' IS NOT NULL
|
|
315
338
|
OR e.f_data::json #> '{usage}' IS NOT NULL)
|
|
316
339
|
ON CONFLICT DO NOTHING
|
|
340
|
+
`);
|
|
341
|
+
}
|
|
342
|
+
/** 会话 × 本地日 × 模型的 token 汇总:从桥接行 × 用量行重算(含 fork 继承前缀)。 */
|
|
343
|
+
async backfillSessionUsage() {
|
|
344
|
+
if (!await this.tableIsEmpty(this.qualifiedTable("t_session_usage"))) return;
|
|
345
|
+
const tSessionUsage = this.tables["t_session_usage"];
|
|
346
|
+
const tSessionEvents = this.tables["t_session_events"];
|
|
347
|
+
const tEventUsage = this.tables["t_event_usage"];
|
|
348
|
+
await this.db.execute(sql`
|
|
349
|
+
INSERT INTO ${tSessionUsage} (
|
|
350
|
+
f_session_id, f_day, f_provider, f_model,
|
|
351
|
+
f_input_tokens, f_output_tokens, f_cache_read_tokens, f_reasoning_tokens, f_total_tokens)
|
|
352
|
+
SELECT b.f_session_id, u.f_day, coalesce(u.f_provider, ''), coalesce(u.f_model, ''),
|
|
353
|
+
sum(u.f_input_tokens), sum(u.f_output_tokens), sum(u.f_cache_read_tokens),
|
|
354
|
+
sum(u.f_reasoning_tokens), sum(u.f_total_tokens)
|
|
355
|
+
FROM ${tSessionEvents} b
|
|
356
|
+
JOIN ${tEventUsage} u ON u.f_event_id = b.f_event_id
|
|
357
|
+
GROUP BY 1, 2, 3, 4
|
|
358
|
+
ON CONFLICT DO NOTHING
|
|
359
|
+
`);
|
|
360
|
+
}
|
|
361
|
+
/** 会话 × 本地日的活动计数:从桥接行 × 事件类型重算。 */
|
|
362
|
+
async backfillSessionCounts() {
|
|
363
|
+
if (!await this.tableIsEmpty(this.qualifiedTable("t_session_counts"))) return;
|
|
364
|
+
const tSessionCounts = this.tables["t_session_counts"];
|
|
365
|
+
const tSessionEvents = this.tables["t_session_events"];
|
|
366
|
+
const tEvents = this.tables["t_events"];
|
|
367
|
+
await this.db.execute(sql`
|
|
368
|
+
INSERT INTO ${tSessionCounts} (
|
|
369
|
+
f_session_id, f_day, f_turns, f_steps, f_user_inputs, f_tool_calls)
|
|
370
|
+
SELECT b.f_session_id,
|
|
371
|
+
to_char(to_timestamp(e.f_created_at / 1000.0), 'YYYY-MM-DD'),
|
|
372
|
+
sum(CASE WHEN e.f_type = 'turn/start' THEN 1 ELSE 0 END),
|
|
373
|
+
sum(CASE WHEN e.f_type = 'step/start' THEN 1 ELSE 0 END),
|
|
374
|
+
sum(CASE WHEN e.f_type = 'user/message' THEN 1 ELSE 0 END),
|
|
375
|
+
sum(CASE WHEN e.f_type = 'tool/call' THEN 1 ELSE 0 END)
|
|
376
|
+
FROM ${tSessionEvents} b
|
|
377
|
+
JOIN ${tEvents} e ON e.f_event_id = b.f_event_id
|
|
378
|
+
WHERE e.f_type IN (${sql.raw(COUNTED_EVENT_TYPE_SQL)})
|
|
379
|
+
GROUP BY 1, 2
|
|
380
|
+
ON CONFLICT DO NOTHING
|
|
317
381
|
`);
|
|
318
382
|
}
|
|
319
383
|
/** 用量聚合:与 SQLite 侧同形,读 `t_event_usage`,数值列回来是字符串。 */
|
|
@@ -366,85 +430,78 @@ var PostgresBackend = class PostgresBackend {
|
|
|
366
430
|
total: numeric(counted.rows[0]?.["n"] ?? 0)
|
|
367
431
|
};
|
|
368
432
|
}
|
|
433
|
+
/**
|
|
434
|
+
* 用量聚合:与 SQLite 侧同形——只读三张派生统计表(用量行给去重口径的总量与「天 × 模型」桶,
|
|
435
|
+
* 两张会话汇总表给按会话的行),数值列回来是字符串。物化列让这里不做 `EXISTS`、不回连事件表。
|
|
436
|
+
*/
|
|
369
437
|
async usageReport(sinceMs) {
|
|
370
|
-
const usageSince = sinceMs === void 0 ? sql`` : sql` AND
|
|
371
|
-
const
|
|
438
|
+
const usageSince = sinceMs === void 0 ? sql`` : sql` AND f_created_at >= ${sinceMs}`;
|
|
439
|
+
const dayFilter = (column) => sinceMs === void 0 ? sql`` : sql` AND ${sql.raw(column)} >= ${localDayKey(sinceMs)}`;
|
|
372
440
|
const tEventUsage = this.tables["t_event_usage"];
|
|
373
|
-
const
|
|
441
|
+
const tSessionUsage = this.tables["t_session_usage"];
|
|
442
|
+
const tSessionCounts = this.tables["t_session_counts"];
|
|
374
443
|
const tSessions = this.tables["t_sessions"];
|
|
375
|
-
const
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
JOIN ${tSessions} ss ON ss.f_session_id = sb.f_session_id
|
|
381
|
-
WHERE sb.f_session_id = c.f_session_id AND ss.f_origin = 'subagent')`;
|
|
382
|
-
const hasUsageSession = (column) => sql`EXISTS (SELECT 1 FROM ${tSessionEvents} ub
|
|
383
|
-
JOIN ${tEventUsage} uu ON uu.f_event_id = ub.f_event_id
|
|
384
|
-
WHERE ub.f_session_id = ${sql.raw(column)})`;
|
|
385
|
-
const tokenColumns = sql.raw(`sum(u.f_input_tokens) AS input_tokens,
|
|
386
|
-
sum(u.f_output_tokens) AS output_tokens,
|
|
387
|
-
sum(u.f_cache_read_tokens) AS cache_read_tokens,
|
|
388
|
-
sum(u.f_reasoning_tokens) AS reasoning_tokens,
|
|
389
|
-
sum(u.f_total_tokens) AS total_tokens`);
|
|
444
|
+
const tokenColumns = (alias) => sql.raw(`sum(${alias}.f_input_tokens) AS input_tokens,
|
|
445
|
+
sum(${alias}.f_output_tokens) AS output_tokens,
|
|
446
|
+
sum(${alias}.f_cache_read_tokens) AS cache_read_tokens,
|
|
447
|
+
sum(${alias}.f_reasoning_tokens) AS reasoning_tokens,
|
|
448
|
+
sum(${alias}.f_total_tokens) AS total_tokens`);
|
|
390
449
|
const buckets = await this.db.execute(sql`
|
|
391
|
-
SELECT
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
${tokenColumns}
|
|
396
|
-
FROM ${tEventUsage}
|
|
397
|
-
WHERE
|
|
450
|
+
SELECT a.f_day AS day,
|
|
451
|
+
a.f_provider AS provider,
|
|
452
|
+
a.f_model AS model,
|
|
453
|
+
(a.f_subagent = 1) AS subagent,
|
|
454
|
+
${tokenColumns("a")}
|
|
455
|
+
FROM ${tEventUsage} a
|
|
456
|
+
WHERE a.f_referenced = 1${usageSince}
|
|
398
457
|
GROUP BY 1, 2, 3, 4
|
|
399
458
|
`);
|
|
400
459
|
const tokenScope = await this.db.execute(sql`
|
|
401
|
-
SELECT
|
|
402
|
-
${tokenColumns}
|
|
403
|
-
FROM ${tEventUsage}
|
|
404
|
-
WHERE
|
|
460
|
+
SELECT (a.f_subagent = 1) AS subagent,
|
|
461
|
+
${tokenColumns("a")}
|
|
462
|
+
FROM ${tEventUsage} a
|
|
463
|
+
WHERE a.f_referenced = 1${usageSince}
|
|
405
464
|
GROUP BY 1
|
|
406
465
|
`);
|
|
407
466
|
const activityScope = await this.db.execute(sql`
|
|
408
|
-
SELECT
|
|
409
|
-
c.
|
|
410
|
-
sum(c.
|
|
411
|
-
|
|
467
|
+
SELECT (s.f_origin = 'subagent') AS subagent,
|
|
468
|
+
sum(c.f_turns) AS turns,
|
|
469
|
+
sum(c.f_steps) AS steps,
|
|
470
|
+
sum(c.f_user_inputs) AS user_inputs,
|
|
471
|
+
sum(c.f_tool_calls) AS tool_calls
|
|
472
|
+
FROM ${tSessionCounts} c
|
|
412
473
|
JOIN ${tSessions} s ON s.f_session_id = c.f_session_id
|
|
413
|
-
WHERE ${
|
|
414
|
-
GROUP BY 1
|
|
474
|
+
WHERE EXISTS (SELECT 1 FROM ${tSessionUsage} ub WHERE ub.f_session_id = c.f_session_id)${dayFilter("c.f_day")}
|
|
475
|
+
GROUP BY 1
|
|
415
476
|
`);
|
|
416
477
|
const tokenSessions = await this.db.execute(sql`
|
|
417
|
-
SELECT
|
|
478
|
+
SELECT u.f_session_id AS session_id,
|
|
418
479
|
s.f_title AS title,
|
|
419
480
|
(s.f_origin = 'subagent') AS subagent,
|
|
420
481
|
(s.f_archived_at IS NOT NULL) AS archived,
|
|
421
|
-
${tokenColumns}
|
|
422
|
-
FROM ${
|
|
423
|
-
JOIN ${
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
GROUP BY b.f_session_id, s.f_title, s.f_origin, s.f_archived_at
|
|
482
|
+
${tokenColumns("u")}
|
|
483
|
+
FROM ${tSessionUsage} u
|
|
484
|
+
JOIN ${tSessions} s ON s.f_session_id = u.f_session_id
|
|
485
|
+
WHERE 1 = 1${dayFilter("u.f_day")}
|
|
486
|
+
GROUP BY u.f_session_id, s.f_title, s.f_origin, s.f_archived_at
|
|
427
487
|
`);
|
|
428
488
|
const activitySessions = await this.db.execute(sql`
|
|
429
489
|
SELECT c.f_session_id AS session_id,
|
|
430
|
-
c.
|
|
431
|
-
sum(c.
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
490
|
+
sum(c.f_turns) AS turns,
|
|
491
|
+
sum(c.f_steps) AS steps,
|
|
492
|
+
sum(c.f_user_inputs) AS user_inputs,
|
|
493
|
+
sum(c.f_tool_calls) AS tool_calls
|
|
494
|
+
FROM ${tSessionCounts} c
|
|
495
|
+
WHERE 1 = 1${dayFilter("c.f_day")}
|
|
496
|
+
GROUP BY c.f_session_id
|
|
435
497
|
`);
|
|
436
498
|
const human = emptyTotals();
|
|
437
499
|
const subagent = emptyTotals();
|
|
438
500
|
for (const row of tokenScope.rows) addTokenTotals(row["subagent"] === true ? subagent : human, tokenTotalsOf(row));
|
|
439
|
-
for (const row of activityScope.rows)
|
|
501
|
+
for (const row of activityScope.rows) addActivityTotals(row["subagent"] === true ? subagent : human, activityTotalsOf(row));
|
|
440
502
|
const totals = addTotals(addTotals(emptyTotals(), human), subagent);
|
|
441
503
|
const activityBySession = /* @__PURE__ */ new Map();
|
|
442
|
-
for (const row of activitySessions.rows)
|
|
443
|
-
const key = String(row["session_id"]);
|
|
444
|
-
const target = activityBySession.get(key) ?? emptyActivity();
|
|
445
|
-
addActivityCount(target, String(row["f_type"]), numeric(row["n"]));
|
|
446
|
-
activityBySession.set(key, target);
|
|
447
|
-
}
|
|
504
|
+
for (const row of activitySessions.rows) activityBySession.set(String(row["session_id"]), activityTotalsOf(row));
|
|
448
505
|
return {
|
|
449
506
|
totals,
|
|
450
507
|
subagent,
|
|
@@ -466,76 +523,100 @@ var PostgresBackend = class PostgresBackend {
|
|
|
466
523
|
}))
|
|
467
524
|
};
|
|
468
525
|
}
|
|
469
|
-
/** 表名限定到配置的 schema
|
|
526
|
+
/** 表名限定到配置的 schema。 */
|
|
470
527
|
qualifiedTable(name) {
|
|
471
528
|
const schema = this.options.schema ?? "public";
|
|
472
529
|
return schema === "public" ? name : `"${schema}".${name}`;
|
|
473
530
|
}
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
const tEvents = this.tables["t_events"];
|
|
493
|
-
await this.db.execute(sql`
|
|
494
|
-
INSERT INTO ${sql.raw(table)} (f_session_id, f_day, f_type, f_count)
|
|
495
|
-
SELECT ${tSessionEvents}.f_session_id,
|
|
496
|
-
to_char(to_timestamp(${tEvents}.f_created_at / 1000.0), 'YYYY-MM-DD'),
|
|
497
|
-
${tEvents}.f_type,
|
|
498
|
-
count(*)
|
|
499
|
-
FROM ${tSessionEvents}
|
|
500
|
-
JOIN ${tEvents} ON ${tEvents}.f_event_id = ${tSessionEvents}.f_event_id
|
|
501
|
-
WHERE ${tEvents}.f_type IN (${sql.raw(COUNTED_EVENT_TYPE_SQL)})
|
|
502
|
-
GROUP BY 1, 2, 3
|
|
503
|
-
ON CONFLICT DO NOTHING
|
|
504
|
-
`);
|
|
531
|
+
/** token 用量旁路累加:派生表 `t_session_usage`,不在写事务里(失败可丢,表可销毁重建)。 */
|
|
532
|
+
async incrementSessionUsage(id, buckets) {
|
|
533
|
+
if (buckets.length === 0) return;
|
|
534
|
+
const tSessionUsage = this.tables["t_session_usage"];
|
|
535
|
+
for (const bucket of buckets) await this.db.execute(sql`
|
|
536
|
+
INSERT INTO ${tSessionUsage} (
|
|
537
|
+
f_session_id, f_day, f_provider, f_model,
|
|
538
|
+
f_input_tokens, f_output_tokens, f_cache_read_tokens, f_reasoning_tokens, f_total_tokens)
|
|
539
|
+
VALUES (${id}, ${bucket.day}, ${bucket.provider ?? ""}, ${bucket.model ?? ""},
|
|
540
|
+
${bucket.inputTokens}, ${bucket.outputTokens}, ${bucket.cacheReadTokens},
|
|
541
|
+
${bucket.reasoningTokens}, ${bucket.totalTokens})
|
|
542
|
+
ON CONFLICT (f_session_id, f_day, f_provider, f_model) DO UPDATE SET
|
|
543
|
+
f_input_tokens = ${tSessionUsage}.f_input_tokens + EXCLUDED.f_input_tokens,
|
|
544
|
+
f_output_tokens = ${tSessionUsage}.f_output_tokens + EXCLUDED.f_output_tokens,
|
|
545
|
+
f_cache_read_tokens = ${tSessionUsage}.f_cache_read_tokens + EXCLUDED.f_cache_read_tokens,
|
|
546
|
+
f_reasoning_tokens = ${tSessionUsage}.f_reasoning_tokens + EXCLUDED.f_reasoning_tokens,
|
|
547
|
+
f_total_tokens = ${tSessionUsage}.f_total_tokens + EXCLUDED.f_total_tokens
|
|
548
|
+
`);
|
|
505
549
|
}
|
|
506
|
-
/**
|
|
507
|
-
async
|
|
550
|
+
/** 活动计数旁路累加:派生表 `t_session_counts`,同上(四项计数一次落)。 */
|
|
551
|
+
async incrementSessionCounts(id, buckets) {
|
|
508
552
|
if (buckets.length === 0) return;
|
|
509
|
-
const
|
|
553
|
+
const tSessionCounts = this.tables["t_session_counts"];
|
|
510
554
|
for (const bucket of buckets) await this.db.execute(sql`
|
|
511
|
-
INSERT INTO ${
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
555
|
+
INSERT INTO ${tSessionCounts} (
|
|
556
|
+
f_session_id, f_day, f_turns, f_steps, f_user_inputs, f_tool_calls)
|
|
557
|
+
VALUES (${id}, ${bucket.day}, ${bucket.turns}, ${bucket.steps},
|
|
558
|
+
${bucket.userInputs}, ${bucket.toolCalls})
|
|
559
|
+
ON CONFLICT (f_session_id, f_day) DO UPDATE SET
|
|
560
|
+
f_turns = ${tSessionCounts}.f_turns + EXCLUDED.f_turns,
|
|
561
|
+
f_steps = ${tSessionCounts}.f_steps + EXCLUDED.f_steps,
|
|
562
|
+
f_user_inputs = ${tSessionCounts}.f_user_inputs + EXCLUDED.f_user_inputs,
|
|
563
|
+
f_tool_calls = ${tSessionCounts}.f_tool_calls + EXCLUDED.f_tool_calls
|
|
515
564
|
`);
|
|
516
565
|
}
|
|
517
|
-
/**
|
|
518
|
-
async
|
|
519
|
-
const
|
|
566
|
+
/** 按会话重算两张汇总表(rewind / fork 之后):先删该会话的行,再从事件表重算。 */
|
|
567
|
+
async rebuildSessionStats(id) {
|
|
568
|
+
const tSessionUsage = this.tables["t_session_usage"];
|
|
569
|
+
const tSessionCounts = this.tables["t_session_counts"];
|
|
520
570
|
const tSessionEvents = this.tables["t_session_events"];
|
|
571
|
+
const tEventUsage = this.tables["t_event_usage"];
|
|
521
572
|
const tEvents = this.tables["t_events"];
|
|
522
|
-
await this.db.execute(sql`DELETE FROM ${
|
|
573
|
+
await this.db.execute(sql`DELETE FROM ${tSessionUsage} WHERE f_session_id = ${id}`);
|
|
523
574
|
await this.db.execute(sql`
|
|
524
|
-
INSERT INTO ${
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
GROUP BY 1, 2, 3
|
|
575
|
+
INSERT INTO ${tSessionUsage} (
|
|
576
|
+
f_session_id, f_day, f_provider, f_model,
|
|
577
|
+
f_input_tokens, f_output_tokens, f_cache_read_tokens, f_reasoning_tokens, f_total_tokens)
|
|
578
|
+
SELECT b.f_session_id, u.f_day, coalesce(u.f_provider, ''), coalesce(u.f_model, ''),
|
|
579
|
+
sum(u.f_input_tokens), sum(u.f_output_tokens), sum(u.f_cache_read_tokens),
|
|
580
|
+
sum(u.f_reasoning_tokens), sum(u.f_total_tokens)
|
|
581
|
+
FROM ${tSessionEvents} b
|
|
582
|
+
JOIN ${tEventUsage} u ON u.f_event_id = b.f_event_id
|
|
583
|
+
WHERE b.f_session_id = ${id}
|
|
584
|
+
GROUP BY 1, 2, 3, 4
|
|
585
|
+
`);
|
|
586
|
+
await this.db.execute(sql`DELETE FROM ${tSessionCounts} WHERE f_session_id = ${id}`);
|
|
587
|
+
await this.db.execute(sql`
|
|
588
|
+
INSERT INTO ${tSessionCounts} (
|
|
589
|
+
f_session_id, f_day, f_turns, f_steps, f_user_inputs, f_tool_calls)
|
|
590
|
+
SELECT b.f_session_id,
|
|
591
|
+
to_char(to_timestamp(e.f_created_at / 1000.0), 'YYYY-MM-DD'),
|
|
592
|
+
sum(CASE WHEN e.f_type = 'turn/start' THEN 1 ELSE 0 END),
|
|
593
|
+
sum(CASE WHEN e.f_type = 'step/start' THEN 1 ELSE 0 END),
|
|
594
|
+
sum(CASE WHEN e.f_type = 'user/message' THEN 1 ELSE 0 END),
|
|
595
|
+
sum(CASE WHEN e.f_type = 'tool/call' THEN 1 ELSE 0 END)
|
|
596
|
+
FROM ${tSessionEvents} b
|
|
597
|
+
JOIN ${tEvents} e ON e.f_event_id = b.f_event_id
|
|
598
|
+
WHERE b.f_session_id = ${id} AND e.f_type IN (${sql.raw(COUNTED_EVENT_TYPE_SQL)})
|
|
599
|
+
GROUP BY 1, 2
|
|
534
600
|
`);
|
|
535
601
|
}
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
602
|
+
/** 全量重算用量行的引用标记(rewind / fork / 会话删除之后)。 */
|
|
603
|
+
async refreshEventUsageFlags() {
|
|
604
|
+
const tEventUsage = this.tables["t_event_usage"];
|
|
605
|
+
const tSessionEvents = this.tables["t_session_events"];
|
|
606
|
+
const tSessions = this.tables["t_sessions"];
|
|
607
|
+
await this.db.execute(sql`
|
|
608
|
+
UPDATE ${tEventUsage} SET
|
|
609
|
+
f_referenced = (EXISTS (SELECT 1 FROM ${tSessionEvents} rb
|
|
610
|
+
WHERE rb.f_event_id = ${tEventUsage}.f_event_id))::int,
|
|
611
|
+
f_subagent = (EXISTS (SELECT 1 FROM ${tSessionEvents} sb
|
|
612
|
+
JOIN ${tSessions} ss ON ss.f_session_id = sb.f_session_id
|
|
613
|
+
WHERE sb.f_event_id = ${tEventUsage}.f_event_id
|
|
614
|
+
AND ss.f_origin = 'subagent'))::int
|
|
615
|
+
`);
|
|
616
|
+
}
|
|
617
|
+
async deleteSessionStats(id) {
|
|
618
|
+
await this.db.execute(sql`DELETE FROM ${this.tables["t_session_usage"]} WHERE f_session_id = ${id}`);
|
|
619
|
+
await this.db.execute(sql`DELETE FROM ${this.tables["t_session_counts"]} WHERE f_session_id = ${id}`);
|
|
539
620
|
}
|
|
540
621
|
async getPrevBridge(exec, id, sequence) {
|
|
541
622
|
return (await exec.select({
|
|
@@ -1657,7 +1738,7 @@ var SessionPersistenceRdb = class extends SessionPersistence {
|
|
|
1657
1738
|
this.liveFailures.delete(id);
|
|
1658
1739
|
this.liveDropWarned.delete(id);
|
|
1659
1740
|
this.reuseEventIds.delete(id);
|
|
1660
|
-
await this.
|
|
1741
|
+
await this.dropSessionStats(id);
|
|
1661
1742
|
}
|
|
1662
1743
|
/** GC 通道:回收已无桥接行引用的事件行(孤儿),返回删除行数。 */
|
|
1663
1744
|
async collectOrphans() {
|
|
@@ -1676,7 +1757,7 @@ var SessionPersistenceRdb = class extends SessionPersistence {
|
|
|
1676
1757
|
this.liveFailures.delete(id);
|
|
1677
1758
|
this.liveDropWarned.delete(id);
|
|
1678
1759
|
this.reuseEventIds.delete(id);
|
|
1679
|
-
await this.
|
|
1760
|
+
await this.dropSessionStats(id);
|
|
1680
1761
|
}
|
|
1681
1762
|
return deleted;
|
|
1682
1763
|
}
|
|
@@ -1727,9 +1808,11 @@ var SessionPersistenceRdb = class extends SessionPersistence {
|
|
|
1727
1808
|
async appendBatch(meta, inheritedEventCount, events, tornTruncateTo) {
|
|
1728
1809
|
await this.ready;
|
|
1729
1810
|
if (events.length === 0) return false;
|
|
1811
|
+
sealOwnEvents(events);
|
|
1730
1812
|
validateStoredEvents(meta, [...events]);
|
|
1731
1813
|
const reuse = this.reuseEventIds.get(meta.id);
|
|
1732
1814
|
let confirmedHead = -1;
|
|
1815
|
+
let usageRows = [];
|
|
1733
1816
|
await this.backend.transaction(async (tx) => {
|
|
1734
1817
|
if (tornTruncateTo !== void 0) {
|
|
1735
1818
|
await tx.deleteBridgeTail(meta.id, tornTruncateTo);
|
|
@@ -1743,18 +1826,19 @@ var SessionPersistenceRdb = class extends SessionPersistence {
|
|
|
1743
1826
|
}, randomUUID());
|
|
1744
1827
|
const head = await tx.getHead(meta.id);
|
|
1745
1828
|
this.writeGuard.assertNoConcurrentWriter(meta.id, head.fHeadSequence);
|
|
1746
|
-
const
|
|
1829
|
+
const appended = await appendEventTail(tx, meta, events, {
|
|
1747
1830
|
parentId: head.fHeadEventId,
|
|
1748
1831
|
nextSeq: head.fHeadSequence + 1
|
|
1749
1832
|
}, reuse);
|
|
1750
|
-
|
|
1833
|
+
usageRows = appended.usageRows;
|
|
1834
|
+
await tx.updateHead(meta.id, appended.headEventId, appended.headSequence);
|
|
1751
1835
|
await tx.bumpRevision(meta.id, maxEventTime(events));
|
|
1752
|
-
confirmedHead = headSequence;
|
|
1836
|
+
confirmedHead = appended.headSequence;
|
|
1753
1837
|
});
|
|
1754
1838
|
if (reuse !== void 0) this.reuseEventIds.delete(meta.id);
|
|
1755
1839
|
this.writeGuard.confirmHead(meta.id, confirmedHead);
|
|
1756
1840
|
this.tracker.materialized(meta.id);
|
|
1757
|
-
await this.
|
|
1841
|
+
await this.recordSessionStats(meta.id, events, usageRows);
|
|
1758
1842
|
return true;
|
|
1759
1843
|
}
|
|
1760
1844
|
async readLog(id, options = {}, signal) {
|
|
@@ -1792,7 +1876,7 @@ var SessionPersistenceRdb = class extends SessionPersistence {
|
|
|
1792
1876
|
};
|
|
1793
1877
|
}
|
|
1794
1878
|
const { preserved, tornFrom } = scanRows(eventRows, options.fromSeq ?? 0);
|
|
1795
|
-
if (
|
|
1879
|
+
if (needsShapeAdoption(preserved)) {
|
|
1796
1880
|
const adopted = adoptLegacyRows(row, eventRows);
|
|
1797
1881
|
return {
|
|
1798
1882
|
meta: adopted.meta,
|
|
@@ -1817,32 +1901,43 @@ var SessionPersistenceRdb = class extends SessionPersistence {
|
|
|
1817
1901
|
};
|
|
1818
1902
|
}
|
|
1819
1903
|
/**
|
|
1820
|
-
*
|
|
1821
|
-
*
|
|
1904
|
+
* 统计衍生表的**旁路累加**:不在写事务里、失败只 warn——两张会话汇总表都是可销毁重建的
|
|
1905
|
+
* 派生表,丢几次累加不影响可用性(需要时用 `rebuildSessionStats` 重算)。
|
|
1906
|
+
* token 走写路径已折好的用量行,活动计数走本批事件类型。
|
|
1822
1907
|
*/
|
|
1823
|
-
async
|
|
1824
|
-
const
|
|
1825
|
-
if (
|
|
1826
|
-
|
|
1827
|
-
|
|
1908
|
+
async recordSessionStats(id, events, usageRows) {
|
|
1909
|
+
const usageBuckets = sessionUsageBuckets(usageRows);
|
|
1910
|
+
if (usageBuckets.length > 0) try {
|
|
1911
|
+
await this.backend.incrementSessionUsage(id, usageBuckets);
|
|
1912
|
+
} catch (error) {
|
|
1913
|
+
this.ctx.logger.warn(`session-rdb: token usage for "${id}" not updated (${describeError(error)})`);
|
|
1914
|
+
}
|
|
1915
|
+
const countBuckets = sessionCountBuckets(events);
|
|
1916
|
+
if (countBuckets.length > 0) try {
|
|
1917
|
+
await this.backend.incrementSessionCounts(id, countBuckets);
|
|
1828
1918
|
} catch (error) {
|
|
1829
1919
|
this.ctx.logger.warn(`session-rdb: activity counts for "${id}" not updated (${describeError(error)})`);
|
|
1830
1920
|
}
|
|
1831
1921
|
}
|
|
1832
|
-
/**
|
|
1833
|
-
|
|
1922
|
+
/**
|
|
1923
|
+
* 重算一个会话的统计(rewind / fork 之后;best-effort,同 `recordSessionStats`):
|
|
1924
|
+
* 两张汇总表按会话重算,用量行的引用标记全量重算(引用可能跨会话消失)。
|
|
1925
|
+
*/
|
|
1926
|
+
async rebuildSessionStats(id) {
|
|
1834
1927
|
try {
|
|
1835
|
-
await this.backend.
|
|
1928
|
+
await this.backend.rebuildSessionStats(id);
|
|
1929
|
+
await this.backend.refreshEventUsageFlags();
|
|
1836
1930
|
} catch (error) {
|
|
1837
|
-
this.ctx.logger.warn(`session-rdb:
|
|
1931
|
+
this.ctx.logger.warn(`session-rdb: session stats for "${id}" not rebuilt (${describeError(error)})`);
|
|
1838
1932
|
}
|
|
1839
1933
|
}
|
|
1840
|
-
/**
|
|
1841
|
-
async
|
|
1934
|
+
/** 会话删除时清掉它的汇总行,并重算用量行的引用标记(被删会话引用的行要立刻退出统计)。 */
|
|
1935
|
+
async dropSessionStats(id) {
|
|
1842
1936
|
try {
|
|
1843
|
-
await this.backend.
|
|
1937
|
+
await this.backend.deleteSessionStats(id);
|
|
1938
|
+
await this.backend.refreshEventUsageFlags();
|
|
1844
1939
|
} catch (error) {
|
|
1845
|
-
this.ctx.logger.warn(`session-rdb:
|
|
1940
|
+
this.ctx.logger.warn(`session-rdb: session stats for "${id}" not deleted (${describeError(error)})`);
|
|
1846
1941
|
}
|
|
1847
1942
|
}
|
|
1848
1943
|
async rewriteMigratedLog(id, log) {
|
|
@@ -1855,13 +1950,14 @@ var SessionPersistenceRdb = class extends SessionPersistence {
|
|
|
1855
1950
|
meta: log.meta,
|
|
1856
1951
|
inheritedEventCount: SessionLogOffset(log.inheritedEventCount)
|
|
1857
1952
|
}, randomUUID());
|
|
1858
|
-
const
|
|
1953
|
+
const appended = await appendEventTail(tx, log.meta, log.events, {
|
|
1859
1954
|
parentId: "",
|
|
1860
1955
|
nextSeq: 0
|
|
1861
1956
|
});
|
|
1862
|
-
await tx.updateHead(id, headEventId, headSequence);
|
|
1957
|
+
await tx.updateHead(id, appended.headEventId, appended.headSequence);
|
|
1863
1958
|
await tx.bumpRevision(id, maxEventTime(log.events));
|
|
1864
1959
|
});
|
|
1960
|
+
await this.rebuildSessionStats(id);
|
|
1865
1961
|
}
|
|
1866
1962
|
async listSnapshots(signal) {
|
|
1867
1963
|
signal?.throwIfAborted();
|
|
@@ -2091,19 +2187,46 @@ function seedCoversPrefix(seed, prefix) {
|
|
|
2091
2187
|
return seedEvent !== void 0 && JSON.stringify(seedEvent) === JSON.stringify(event);
|
|
2092
2188
|
});
|
|
2093
2189
|
}
|
|
2094
|
-
/**
|
|
2095
|
-
function
|
|
2190
|
+
/** 一批事件折成活动计数的桶:只数四种类型,按本地日聚合成四项。 */
|
|
2191
|
+
function sessionCountBuckets(events) {
|
|
2096
2192
|
const counted = /* @__PURE__ */ new Map();
|
|
2097
2193
|
for (const event of events) {
|
|
2098
2194
|
if (!COUNTED_EVENT_TYPES.includes(event.type)) continue;
|
|
2099
2195
|
const day = localDayKey(event.time);
|
|
2100
|
-
const
|
|
2101
|
-
const bucket = counted.get(key) ?? {
|
|
2196
|
+
const bucket = counted.get(day) ?? {
|
|
2102
2197
|
day,
|
|
2103
|
-
|
|
2104
|
-
|
|
2198
|
+
turns: 0,
|
|
2199
|
+
steps: 0,
|
|
2200
|
+
userInputs: 0,
|
|
2201
|
+
toolCalls: 0
|
|
2105
2202
|
};
|
|
2106
|
-
bucket.
|
|
2203
|
+
addActivityCount(bucket, event.type, 1);
|
|
2204
|
+
counted.set(day, bucket);
|
|
2205
|
+
}
|
|
2206
|
+
return [...counted.values()];
|
|
2207
|
+
}
|
|
2208
|
+
/** 一批用量行折成 token 桶:按「本地日 × 模型」聚合(模型未知落空串,与表口径一致)。 */
|
|
2209
|
+
function sessionUsageBuckets(usageRows) {
|
|
2210
|
+
const counted = /* @__PURE__ */ new Map();
|
|
2211
|
+
for (const row of usageRows) {
|
|
2212
|
+
const provider = row.fProvider ?? "";
|
|
2213
|
+
const model = row.fModel ?? "";
|
|
2214
|
+
const key = `${row.fDay}#${provider}#${model}`;
|
|
2215
|
+
const bucket = counted.get(key) ?? {
|
|
2216
|
+
day: row.fDay,
|
|
2217
|
+
provider,
|
|
2218
|
+
model,
|
|
2219
|
+
inputTokens: 0,
|
|
2220
|
+
outputTokens: 0,
|
|
2221
|
+
cacheReadTokens: 0,
|
|
2222
|
+
reasoningTokens: 0,
|
|
2223
|
+
totalTokens: 0
|
|
2224
|
+
};
|
|
2225
|
+
bucket.inputTokens += row.fInputTokens;
|
|
2226
|
+
bucket.outputTokens += row.fOutputTokens;
|
|
2227
|
+
bucket.cacheReadTokens += row.fCacheReadTokens;
|
|
2228
|
+
bucket.reasoningTokens += row.fReasoningTokens;
|
|
2229
|
+
bucket.totalTokens += row.fTotalTokens;
|
|
2107
2230
|
counted.set(key, bucket);
|
|
2108
2231
|
}
|
|
2109
2232
|
return [...counted.values()];
|
|
@@ -2135,6 +2258,7 @@ async function appendEventTail(tx, meta, events, anchor, reuse) {
|
|
|
2135
2258
|
let parentId = anchor.parentId;
|
|
2136
2259
|
let nextSeq = anchor.nextSeq;
|
|
2137
2260
|
const eventRows = [];
|
|
2261
|
+
const usageRows = [];
|
|
2138
2262
|
const bridgeRows = [];
|
|
2139
2263
|
for (const event of events) {
|
|
2140
2264
|
const reusedId = reuse?.get(event.seq);
|
|
@@ -2142,7 +2266,7 @@ async function appendEventTail(tx, meta, events, anchor, reuse) {
|
|
|
2142
2266
|
if (reusedId === void 0) {
|
|
2143
2267
|
const { kind, role, name, actionId } = eventDimensions(event);
|
|
2144
2268
|
const { data, surfaceOp: _surfaceOp, sourceEventSeqs: _sourceEventSeqs, ...envelope } = event;
|
|
2145
|
-
|
|
2269
|
+
const row = {
|
|
2146
2270
|
fEventId: eventId,
|
|
2147
2271
|
fParentId: parentId,
|
|
2148
2272
|
fType: event.type,
|
|
@@ -2156,7 +2280,10 @@ async function appendEventTail(tx, meta, events, anchor, reuse) {
|
|
|
2156
2280
|
data
|
|
2157
2281
|
}),
|
|
2158
2282
|
fCreatedAt: event.time
|
|
2159
|
-
}
|
|
2283
|
+
};
|
|
2284
|
+
eventRows.push(row);
|
|
2285
|
+
const usageRow = usageRowOf(row, meta.origin === "subagent");
|
|
2286
|
+
if (usageRow !== void 0) usageRows.push(usageRow);
|
|
2160
2287
|
}
|
|
2161
2288
|
const surfaceOp = event.surfaceOp === void 0 ? null : JSON.stringify(event.surfaceOp);
|
|
2162
2289
|
bridgeRows.push({
|
|
@@ -2168,12 +2295,13 @@ async function appendEventTail(tx, meta, events, anchor, reuse) {
|
|
|
2168
2295
|
parentId = eventId;
|
|
2169
2296
|
nextSeq++;
|
|
2170
2297
|
}
|
|
2171
|
-
if (eventRows.length > 0) await tx.insertEvents(eventRows);
|
|
2298
|
+
if (eventRows.length > 0) await tx.insertEvents(eventRows, usageRows);
|
|
2172
2299
|
await tx.insertBridges(bridgeRows);
|
|
2173
2300
|
if (events.some((event) => event.type === "session/title")) await tx.refreshTitle(meta.id);
|
|
2174
2301
|
return {
|
|
2175
2302
|
headEventId: parentId,
|
|
2176
|
-
headSequence: nextSeq - 1
|
|
2303
|
+
headSequence: nextSeq - 1,
|
|
2304
|
+
usageRows
|
|
2177
2305
|
};
|
|
2178
2306
|
}
|
|
2179
2307
|
//#endregion
|