@morlay/ui-conversation-message-actions 0.1.0 → 0.1.2

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/dist/testing.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { t as SESSION_EDITOR_PATH } from "./shared-BBbOTwy4.mjs";
1
+ import { t as SESSION_EDITOR_PATHS } from "./shared-DG86bLrc.mjs";
2
2
  import { SessionEditor } from "./index.mjs";
3
3
  import { createRequire } from "node:module";
4
4
  import { Context, Service } from "@deepseek-ai/cordis";
@@ -13952,12 +13952,12 @@ function assertNever(value, context) {
13952
13952
  const rendered = JSON.stringify(value) ?? String(value);
13953
13953
  throw new Error(`unreachable variant${context ? ` in ${context}` : ""}: ${rendered}`);
13954
13954
  }
13955
- /** Whether a realm-owned intrinsic prototype is backed by its native constructor. */
13955
+ /** Whether a realm-owned intrinsic prototype has a native constructor matching this engine's representation. */
13956
13956
  function hasIntrinsicConstructor(prototype, name) {
13957
13957
  const constructor = Object.getOwnPropertyDescriptor(prototype, "constructor")?.value;
13958
13958
  if (typeof constructor !== "function") return false;
13959
13959
  try {
13960
- return constructor.name === name && constructor.prototype === prototype && Function.prototype.toString.call(constructor) === `function ${name}() { [native code] }`;
13960
+ return constructor.name === name && constructor.prototype === prototype && Function.prototype.toString.call(constructor) === Function.prototype.toString.call(name === "Array" ? Array : Object);
13961
13961
  } catch {
13962
13962
  return false;
13963
13963
  }
@@ -14656,7 +14656,6 @@ var WriteGuard = class {
14656
14656
  confirmHead(id, head) {
14657
14657
  this.headSeqs.set(id, head);
14658
14658
  }
14659
- /** 本实例有没有这条会话的 head 记录(迁移重写用它决定是"登记基准"还是"严格比较")。 */
14660
14659
  has(id) {
14661
14660
  return this.headSeqs.has(id);
14662
14661
  }
@@ -20514,8 +20513,16 @@ createSessionFormatCatalog({
20514
20513
  }
20515
20514
  });
20516
20515
  //#endregion
20516
+ //#region ../session-rdb/src/routes.ts
20517
+ const MORLAY_SESSION_API_PREFIX = "/api/morlay/v1/session";
20518
+ const SESSION_ROWS_PATH = `${MORLAY_SESSION_API_PREFIX}/rows`;
20519
+ const SESSION_EXPORT_PATH = `${MORLAY_SESSION_API_PREFIX}/export`;
20520
+ const SESSION_IMPORT_PATH = `${MORLAY_SESSION_API_PREFIX}/import`;
20521
+ const SESSION_DELETE_PATH = `${MORLAY_SESSION_API_PREFIX}/delete`;
20522
+ const SESSION_GC_PATH = `${MORLAY_SESSION_API_PREFIX}/gc`;
20523
+ const SESSION_USAGE_PATH = `${MORLAY_SESSION_API_PREFIX}/usage`;
20524
+ //#endregion
20517
20525
  //#region ../session-rdb/src/usage.ts
20518
- const SESSION_USAGE_PATH = "/api/session.usage";
20519
20526
  const ROLLING_DAYS = {
20520
20527
  "7d": 7,
20521
20528
  "30d": 30,
@@ -20526,14 +20533,6 @@ function localDayStart(now) {
20526
20533
  start.setHours(0, 0, 0, 0);
20527
20534
  return start;
20528
20535
  }
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
20536
  function resolveUsageSince(range, now) {
20538
20537
  switch (range) {
20539
20538
  case "all": return;
@@ -20553,14 +20552,12 @@ function resolveUsageSince(range, now) {
20553
20552
  function parseUsageRange(value) {
20554
20553
  return value === "day" || value === "week" || value === "7d" || value === "30d" || value === "90d" ? value : "all";
20555
20554
  }
20556
- /** 计入活动计数的事件类型(轮次 / 步骤 / 用户输入 / 工具调用)。 */
20557
20555
  const COUNTED_EVENT_TYPES = [
20558
20556
  "turn/start",
20559
20557
  "step/start",
20560
20558
  "user/message",
20561
20559
  "tool/call"
20562
20560
  ];
20563
- /** 按事件类型把计数累加到活动指标上(表里按类型存,读的时候折成四项)。 */
20564
20561
  function addActivityCount(target, type, count) {
20565
20562
  switch (type) {
20566
20563
  case "turn/start":
@@ -20578,7 +20575,6 @@ function addActivityCount(target, type, count) {
20578
20575
  default: return target;
20579
20576
  }
20580
20577
  }
20581
- /** 毫秒时间戳 → host 本地日(`YYYY-MM-DD`,与 SQL 的 localtime 口径一致)。 */
20582
20578
  function localDayKey(ms) {
20583
20579
  const date = new Date(ms);
20584
20580
  const pad = (value) => String(value).padStart(2, "0");
@@ -20612,16 +20608,11 @@ function addActivityTotals(target, row) {
20612
20608
  target.toolCalls += row.toolCalls;
20613
20609
  return target;
20614
20610
  }
20615
- /** 把一行的整套指标累加进目标(后端合并「是否子代理」两组时用)。 */
20616
20611
  function addTotals(target, row) {
20617
20612
  addActivityTotals(target, row);
20618
20613
  addTokenTotals(target, row);
20619
20614
  return target;
20620
20615
  }
20621
- /**
20622
- * 用量统计通道:一次请求回报总览(含 subagent 拆分)、按天 × 模型的桶与按会话的行。
20623
- * 聚合只算被会话引用的事件行——fork 共享行因此只计一次,已删会话留下的孤儿行不计。
20624
- */
20625
20616
  function registerSessionUsage(ctx, persistence) {
20626
20617
  ctx.inject(["webServer", "connection"], (webCtx) => {
20627
20618
  const webServer = webCtx.webServer;
@@ -20748,17 +20739,11 @@ const SURFACE_EVENT_TYPES = /* @__PURE__ */ new Set([
20748
20739
  "tool/result"
20749
20740
  ]);
20750
20741
  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
20742
  function surfaceSeqIndex(events) {
20757
20743
  const seqs = [];
20758
20744
  for (const event of events) if (SURFACE_EVENT_TYPES.has(event.type)) seqs.push(event.seq);
20759
20745
  return seqs;
20760
20746
  }
20761
- /** `[startSeq, endSeq]` 内的 surface seq(含端点),二分定位后切片。 */
20762
20747
  function surfaceSeqsInRange(seqs, startSeq, endSeq) {
20763
20748
  let low = 0;
20764
20749
  let high = seqs.length;
@@ -21010,20 +20995,6 @@ function renameLegacyPtcEvents(events) {
21010
20995
  }
21011
20996
  }
21012
20997
  }
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
20998
  function normalizeToCurrentShape(events) {
21028
20999
  for (const event of events) {
21029
21000
  const type = event.type;
@@ -21069,33 +21040,10 @@ function liftToolResultMessage(message) {
21069
21040
  callId
21070
21041
  };
21071
21042
  }
21072
- /**
21073
- * 落库前给**本仓库自造的事件类型**补上 `ignorable: true` 信封。
21074
- *
21075
- * 上游的持久化校验(`validateStoredEvents`)只放行「已知类型」或「带可忽略信封的未知类型」,我们的类型
21076
- * 按构造就在已知表之外——不补的话连**写入**都会被拒(`appendBatch` 里那道校验)。上游 `Session.append`
21077
- * 没有承载这个信封的口子(第三个参数只服务 surface 事件),所以这一笔由持久化层补:canonical log 里的
21078
- * 形状不变,落库的那一份带上信封。
21079
- * @param events - 即将落库的批次(`materializeAppendBatch` 的 JSON 快照副本,可写)。
21080
- */
21081
21043
  function sealOwnEvents(events) {
21082
21044
  for (const event of events) if (OWN_EVENT_TYPES.has(event.type)) event.ignorable = true;
21083
21045
  }
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
21046
  const OWN_EVENT_TYPES = /* @__PURE__ */ new Set(["session-branch/version", "session-mode/selected"]);
21097
- /** 这批事件是否需要按当前形状归一(旧代消息形状,或尚未标 `ignorable` 的自造事件类型)——写路径曾把回退
21098
- * 视图的结果以当前版本号落库,所以**版本号不可信**,读之前要按内容判一次。 */
21099
21047
  function needsShapeAdoption(events) {
21100
21048
  for (const event of events) {
21101
21049
  const type = event.type;
@@ -21237,15 +21185,6 @@ function numericField(value) {
21237
21185
  function textField(value) {
21238
21186
  return typeof value === "string" && value !== "" ? value : null;
21239
21187
  }
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
21188
  function usageRowOf(event, subagent) {
21250
21189
  if (event.fType !== "assistant/message") return void 0;
21251
21190
  let parsed;
@@ -25200,12 +25139,6 @@ const sessionProjcacheRows = {
25200
25139
  };
25201
25140
  //#endregion
25202
25141
  //#region ../session-rdb/src/entities/v3/event-usage.ts
25203
- /**
25204
- * 用量日志:一条 `assistant/message` 事件一行(按事件行唯一,fork 共享行不重复)。
25205
- * 读侧维度全部物化在行上——本地日、是否被会话引用、是否被子代理会话引用——统计因此不做
25206
- * `EXISTS` 判定、也不回连事件表;事件行本身仍留在 `t_events`。
25207
- * 取舍见 ADR-统计衍生表物化归属与汇总。
25208
- */
25209
25142
  const eventUsage = {
25210
25143
  name: "t_event_usage",
25211
25144
  columns: {
@@ -25268,11 +25201,6 @@ const eventUsage = {
25268
25201
  };
25269
25202
  //#endregion
25270
25203
  //#region ../session-rdb/src/entities/v3/session-usage.ts
25271
- /**
25272
- * 会话 × 本地日 × 模型的 token 汇总(派生统计表):**按会话的行**读它,因此保住
25273
- * 「fork 子会话含继承前缀」的口径(各会话行之和 ≥ 总量)而不回连事件表。
25274
- * 旁路累加、可按会话重建,读侧只读;取舍见 ADR-统计衍生表物化归属与汇总。
25275
- */
25276
25204
  const sessionUsage = {
25277
25205
  name: "t_session_usage",
25278
25206
  columns: {
@@ -25334,11 +25262,6 @@ const sessionUsage = {
25334
25262
  };
25335
25263
  //#endregion
25336
25264
  //#region ../session-rdb/src/entities/v3/session-counts.ts
25337
- /**
25338
- * 会话 × 本地日的活动计数(派生统计表):轮次 / 步骤 / 用户输入 / 工具调用四列直接读,
25339
- * 不再存事件类型、也不在读取时折。旁路累加、可按会话重建,读侧只读;
25340
- * 取舍见 ADR-统计衍生表物化归属与汇总。
25341
- */
25342
25265
  const sessionCounts = {
25343
25266
  name: "t_session_counts",
25344
25267
  columns: {
@@ -25980,22 +25903,16 @@ function createStorageRepository(host) {
25980
25903
  }
25981
25904
  //#endregion
25982
25905
  //#region ../session-rdb/src/sqlite.ts
25983
- /** 用量行的「被引用」物化表达式(`%EVENT%` 换成事件 id 列)。 */
25984
25906
  const EVENT_REFERENCED_SQL = `EXISTS (SELECT 1 FROM t_session_events rb
25985
25907
  WHERE rb.f_event_id = %EVENT%)`;
25986
- /** 用量行的「被 subagent 会话引用」物化表达式(`%EVENT%` 换成事件 id 列)。 */
25987
25908
  const EVENT_SUBAGENT_SQL = `EXISTS (SELECT 1 FROM t_session_events sb
25988
25909
  JOIN t_sessions ss ON ss.f_session_id = sb.f_session_id
25989
25910
  WHERE sb.f_event_id = %EVENT% AND ss.f_origin = 'subagent')`;
25990
- /** `IN (...)` 用的类型字面量(与 `COUNTED_EVENT_TYPES` 同源)。 */
25991
25911
  const COUNTED_EVENT_TYPE_SQL$1 = COUNTED_EVENT_TYPES.map((type) => `'${type}'`).join(", ");
25912
+ const REFRESH_FLAG_CHUNK$1 = 400;
25992
25913
  function withEvent(expression, column) {
25993
25914
  return expression.replace("%EVENT%", column);
25994
25915
  }
25995
- /**
25996
- * 派生统计表的回填:任一表为空即按事件表全量重算(幂等;迁移删表重建后、手动清空后都靠它自愈)。
25997
- * 顺序有依赖:用量行(带物化列)先落,两张会话汇总表再从它 / 事件表重算。
25998
- */
25999
25916
  function backfillUsageTables(db) {
26000
25917
  backfillEventUsage(db);
26001
25918
  backfillSessionUsage(db);
@@ -26004,7 +25921,6 @@ function backfillUsageTables(db) {
26004
25921
  function tableIsEmpty(db, table) {
26005
25922
  return db.prepare(`SELECT count(*) AS n FROM ${table}`).get().n === 0;
26006
25923
  }
26007
- /** 用量行:一条 `assistant/message` 事件行一行,物化本地日与两个归属标记。 */
26008
25924
  function backfillEventUsage(db) {
26009
25925
  if (!tableIsEmpty(db, "t_event_usage")) return;
26010
25926
  db.exec(`INSERT OR IGNORE INTO t_event_usage (
@@ -26033,7 +25949,6 @@ function backfillEventUsage(db) {
26033
25949
  AND (json_extract(e.f_data, '$.data.usage') IS NOT NULL
26034
25950
  OR json_extract(e.f_data, '$.usage') IS NOT NULL)`);
26035
25951
  }
26036
- /** 会话 × 本地日 × 模型的 token 汇总:从桥接行 × 用量行重算(含 fork 继承前缀)。 */
26037
25952
  function backfillSessionUsage(db) {
26038
25953
  if (!tableIsEmpty(db, "t_session_usage")) return;
26039
25954
  db.exec(`INSERT OR IGNORE INTO t_session_usage (
@@ -26046,7 +25961,6 @@ function backfillSessionUsage(db) {
26046
25961
  JOIN t_event_usage u ON u.f_event_id = b.f_event_id
26047
25962
  GROUP BY b.f_session_id, u.f_day, coalesce(u.f_provider, ''), coalesce(u.f_model, '')`);
26048
25963
  }
26049
- /** 会话 × 本地日的活动计数:从桥接行 × 事件类型重算。 */
26050
25964
  function backfillSessionCounts(db) {
26051
25965
  if (!tableIsEmpty(db, "t_session_counts")) return;
26052
25966
  db.exec(`INSERT OR IGNORE INTO t_session_counts (
@@ -26075,7 +25989,6 @@ function sessionListRowOf(row) {
26075
25989
  workspace: typeof row["workspace"] === "string" ? row["workspace"] : null
26076
25990
  };
26077
25991
  }
26078
- /** SQL 的缺失求和是 NULL:没有 usage 字段的行使该字段计 0。 */
26079
25992
  function tokenTotalsOf$1(row) {
26080
25993
  return {
26081
25994
  inputTokens: row.input_tokens ?? 0,
@@ -26085,7 +25998,6 @@ function tokenTotalsOf$1(row) {
26085
25998
  totalTokens: row.total_tokens ?? 0
26086
25999
  };
26087
26000
  }
26088
- /** `t_session_counts` 的原始行(列名是 SQL 别名)折成四项活动计数。 */
26089
26001
  function activityTotalsOf$1(row) {
26090
26002
  return {
26091
26003
  turns: row.turns ?? 0,
@@ -26094,7 +26006,6 @@ function activityTotalsOf$1(row) {
26094
26006
  toolCalls: row.tool_calls ?? 0
26095
26007
  };
26096
26008
  }
26097
- /** 一行的整套指标:token 用量 + 活动计数(轮次 / 步骤 / 用户输入 / 工具调用)。 */
26098
26009
  function emptyActivity$1() {
26099
26010
  return {
26100
26011
  turns: 0,
@@ -26103,7 +26014,6 @@ function emptyActivity$1() {
26103
26014
  toolCalls: 0
26104
26015
  };
26105
26016
  }
26106
- /** 有 token 用量的会话:活动计数与列表都限定在这一批会话里,口径一致(读汇总表,几百行)。 */
26107
26017
  function hasUsageSession(column) {
26108
26018
  return `EXISTS (SELECT 1 FROM t_session_usage ub WHERE ub.f_session_id = ${column})`;
26109
26019
  }
@@ -26366,7 +26276,7 @@ var SqliteBackend = class SqliteBackend {
26366
26276
  }).where(eq(tSessions.fSessionId, id)).run();
26367
26277
  }
26368
26278
  async deleteBridgeTail(id, fromSequence) {
26369
- this.db.delete(tSessionEvents).where(and(eq(tSessionEvents.fSessionId, id), gte(tSessionEvents.fSequence, fromSequence))).run();
26279
+ 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
26280
  }
26371
26281
  async getPrevBridge(id, sequence) {
26372
26282
  return this.db.select({
@@ -26375,19 +26285,18 @@ var SqliteBackend = class SqliteBackend {
26375
26285
  }).from(tSessionEvents).where(and(eq(tSessionEvents.fSessionId, id), eq(tSessionEvents.fSequence, sequence))).get();
26376
26286
  }
26377
26287
  async deleteSession(id) {
26378
- this.db.delete(tSessionEvents).where(eq(tSessionEvents.fSessionId, id)).run();
26288
+ const rows = this.db.$client.prepare("DELETE FROM t_session_events WHERE f_session_id = ? RETURNING f_event_id").all(id);
26379
26289
  this.db.delete(tWorkspaceSessions).where(eq(tWorkspaceSessions.fSessionId, id)).run();
26380
26290
  this.db.delete(tSessionProjcacheRows).where(eq(tSessionProjcacheRows.fSessionId, id)).run();
26381
26291
  this.db.delete(tSessions).where(eq(tSessions.fSessionId, id)).run();
26292
+ return rows.map((row) => row.f_event_id);
26382
26293
  }
26383
- /** 事件行可能被多个会话共享(fork 派生),所以孤儿只能在全库范围内判定。 */
26384
26294
  async collectOrphans() {
26385
26295
  const referenced = this.db.select({ fEventId: tSessionEvents.fEventId }).from(tSessionEvents);
26386
26296
  const info = this.db.delete(tEvents).where(notInArray(tEvents.fEventId, referenced)).run();
26387
26297
  this.db.delete(tEventUsage).where(notInArray(tEventUsage.fEventId, this.db.select({ fEventId: tEvents.fEventId }).from(tEvents))).run();
26388
26298
  return Number(info.changes);
26389
26299
  }
26390
- /** 父会话被删后留下的 subagent 会话:父已不在表里,或本来就没有父。 */
26391
26300
  async listOrphanSubagentSessions() {
26392
26301
  return this.db.$client.prepare(`SELECT f_session_id AS id FROM t_sessions
26393
26302
  WHERE f_origin = 'subagent'
@@ -26395,18 +26304,20 @@ var SqliteBackend = class SqliteBackend {
26395
26304
  OR f_parent_session NOT IN (SELECT f_session_id FROM t_sessions))`).all().map((row) => row.id);
26396
26305
  }
26397
26306
  async deleteSessions(ids) {
26398
- if (ids.length === 0) return 0;
26399
- this.db.delete(tSessionEvents).where(inArray(tSessionEvents.fSessionId, ids)).run();
26307
+ if (ids.length === 0) return {
26308
+ deleted: 0,
26309
+ eventIds: []
26310
+ };
26311
+ const placeholders = ids.map(() => "?").join(", ");
26312
+ const rows = this.db.$client.prepare(`DELETE FROM t_session_events WHERE f_session_id IN (${placeholders}) RETURNING f_event_id`).all(...ids);
26400
26313
  this.db.delete(tWorkspaceSessions).where(inArray(tWorkspaceSessions.fSessionId, ids)).run();
26401
26314
  this.db.delete(tSessionProjcacheRows).where(inArray(tSessionProjcacheRows.fSessionId, ids)).run();
26402
26315
  const info = this.db.delete(tSessions).where(inArray(tSessions.fSessionId, ids)).run();
26403
- return Number(info.changes);
26316
+ return {
26317
+ deleted: Number(info.changes),
26318
+ eventIds: rows.map((row) => row.f_event_id)
26319
+ };
26404
26320
  }
26405
- /**
26406
- * 用量聚合:token 用量沿 `t_event_usage` 的用量行,活动计数(轮次 / 步骤 / 用户输入 / 工具调用)
26407
- * 沿 `t_events` 的事件类型数——两者限定在**有 token 用量的会话**里,时间范围各自按自己的
26408
- * `f_created_at`(同一时刻写入,口径一致)。
26409
- */
26410
26321
  async listSessionRows(query = {}) {
26411
26322
  const needle = query.query?.trim().toLowerCase() ?? "";
26412
26323
  const like = `%${needle}%`;
@@ -26446,12 +26357,6 @@ var SqliteBackend = class SqliteBackend {
26446
26357
  total: Number(counted.n)
26447
26358
  };
26448
26359
  }
26449
- /**
26450
- * 用量聚合:只读三张派生统计表——事件级用量行给去重口径的总量与「天 × 模型」桶,
26451
- * `t_session_usage` / `t_session_counts` 给按会话的行(含 fork 继承前缀)。
26452
- * 归属、本地日与「是否被引用」都是物化列,这里不再现算、也不回连事件表。
26453
- * 事件级按毫秒时间戳过滤、汇总表按本地日过滤——`sinceMs` 对齐到本地零点时两者等价。
26454
- */
26455
26360
  async usageReport(sinceMs) {
26456
26361
  const usageSince = sinceMs === void 0 ? "" : " AND f_created_at >= ?";
26457
26362
  const params = sinceMs === void 0 ? [] : [sinceMs];
@@ -26537,7 +26442,6 @@ var SqliteBackend = class SqliteBackend {
26537
26442
  }))
26538
26443
  };
26539
26444
  }
26540
- /** token 用量旁路累加:派生表 `t_session_usage`,不在写事务里(失败可丢,表可销毁重建)。 */
26541
26445
  async incrementSessionUsage(id, buckets) {
26542
26446
  if (buckets.length === 0) return;
26543
26447
  const statement = this.db.$client.prepare(`INSERT INTO t_session_usage (f_session_id, f_day, f_provider, f_model,
@@ -26551,7 +26455,6 @@ var SqliteBackend = class SqliteBackend {
26551
26455
  f_total_tokens = f_total_tokens + excluded.f_total_tokens`);
26552
26456
  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
26457
  }
26554
- /** 活动计数旁路累加:派生表 `t_session_counts`,同上(四项计数一次落)。 */
26555
26458
  async incrementSessionCounts(id, buckets) {
26556
26459
  if (buckets.length === 0) return;
26557
26460
  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 +26466,6 @@ var SqliteBackend = class SqliteBackend {
26563
26466
  f_tool_calls = f_tool_calls + excluded.f_tool_calls`);
26564
26467
  for (const bucket of buckets) statement.run(id, bucket.day, bucket.turns, bucket.steps, bucket.userInputs, bucket.toolCalls);
26565
26468
  }
26566
- /** 按会话重算两张汇总表(rewind / fork 之后):先删该会话的行,再从事件表重算。 */
26567
26469
  async rebuildSessionStats(id) {
26568
26470
  this.db.$client.prepare("DELETE FROM t_session_usage WHERE f_session_id = ?").run(id);
26569
26471
  this.db.$client.prepare(`INSERT INTO t_session_usage (
@@ -26590,15 +26492,15 @@ var SqliteBackend = class SqliteBackend {
26590
26492
  WHERE b.f_session_id = ? AND e.f_type IN (${COUNTED_EVENT_TYPE_SQL$1})
26591
26493
  GROUP BY b.f_session_id, 2`).run(id);
26592
26494
  }
26593
- /**
26594
- * 全量重算用量行的引用标记:rewind 截断 / fork 复用 / 会话删除都会让「引用」跨会话变化,
26595
- * 只按本会话判定不够。低频路径,一次全表 UPDATE(3 万行量级)。
26596
- */
26597
- async refreshEventUsageFlags() {
26598
- const eventId = "t_event_usage.f_event_id";
26599
- this.db.$client.exec(`UPDATE t_event_usage SET
26600
- f_referenced = ${withEvent(EVENT_REFERENCED_SQL, eventId)},
26601
- f_subagent = ${withEvent(EVENT_SUBAGENT_SQL, eventId)}`);
26495
+ async refreshEventUsageFlags(eventIds) {
26496
+ for (let at = 0; at < eventIds.length; at += REFRESH_FLAG_CHUNK$1) {
26497
+ const chunk = eventIds.slice(at, at + REFRESH_FLAG_CHUNK$1);
26498
+ const placeholders = chunk.map(() => "?").join(", ");
26499
+ this.db.$client.prepare(`UPDATE t_event_usage SET
26500
+ f_referenced = ${withEvent(EVENT_REFERENCED_SQL, "f_event_id")},
26501
+ f_subagent = ${withEvent(EVENT_SUBAGENT_SQL, "f_event_id")}
26502
+ WHERE f_event_id IN (${placeholders})`).run(...chunk);
26503
+ }
26602
26504
  }
26603
26505
  async deleteSessionStats(id) {
26604
26506
  this.db.$client.prepare("DELETE FROM t_session_usage WHERE f_session_id = ?").run(id);
@@ -26633,22 +26535,19 @@ async function migrate(db, config) {
26633
26535
  //#region ../session-rdb/src/postgres.ts
26634
26536
  const postgresMigrationsDir = fileURLToPath(new URL("../drizzle/postgres/", import.meta.url));
26635
26537
  const pgWriteQueues = /* @__PURE__ */ new Map();
26636
- /** 按介质(连接串 + schema)串行化写事务,与 SQLite 侧的 `enqueueSqliteTx` 同形。 */
26538
+ const REFRESH_FLAG_CHUNK = 400;
26637
26539
  function enqueuePgWrite(key, fn) {
26638
26540
  const run = (pgWriteQueues.get(key) ?? Promise.resolve()).then(fn);
26639
26541
  pgWriteQueues.set(key, run.then(() => void 0, () => void 0));
26640
26542
  return run;
26641
26543
  }
26642
- /** pg 的 text 列窄化:非字符串(含 null)都不当作文本。 */
26643
26544
  function text(value) {
26644
26545
  return typeof value === "string" ? value : null;
26645
26546
  }
26646
- /** pg 的 count/sum 回落成字符串:统一转成有限数。 */
26647
26547
  function numeric(value) {
26648
26548
  const parsed = typeof value === "number" ? value : Number(value ?? 0);
26649
26549
  return Number.isFinite(parsed) ? parsed : 0;
26650
26550
  }
26651
- /** SQL 的缺失求和是 NULL:没有 usage 字段的行使该字段计 0。 */
26652
26551
  function tokenTotalsOf(row) {
26653
26552
  return {
26654
26553
  inputTokens: numeric(row["input_tokens"]),
@@ -26666,7 +26565,6 @@ function emptyActivity() {
26666
26565
  toolCalls: 0
26667
26566
  };
26668
26567
  }
26669
- /** `t_session_counts` 的四项计数折成活动计数(pg 的 sum 回落成字符串)。 */
26670
26568
  function activityTotalsOf(row) {
26671
26569
  return {
26672
26570
  turns: numeric(row["turns"]),
@@ -26675,7 +26573,6 @@ function activityTotalsOf(row) {
26675
26573
  toolCalls: numeric(row["tool_calls"])
26676
26574
  };
26677
26575
  }
26678
- /** `IN (...)` 用的类型字面量(与 `COUNTED_EVENT_TYPES` 同源)。 */
26679
26576
  const COUNTED_EVENT_TYPE_SQL = COUNTED_EVENT_TYPES.map((type) => `'${type}'`).join(", ");
26680
26577
  var PostgresBackend = class PostgresBackend {
26681
26578
  db;
@@ -26862,15 +26759,21 @@ var PostgresBackend = class PostgresBackend {
26862
26759
  }).where(eq(this.tables["t_sessions"].fSessionId, id)).execute();
26863
26760
  }
26864
26761
  async deleteBridgeTail(exec, id, fromSequence) {
26865
- await exec.delete(this.tables["t_session_events"]).where(and(eq(this.tables["t_session_events"].fSessionId, id), gte(this.tables["t_session_events"].fSequence, fromSequence))).execute();
26762
+ const table = this.tables["t_session_events"];
26763
+ return (await exec.execute(sql`
26764
+ DELETE FROM ${table} WHERE f_session_id = ${id} AND f_sequence >= ${fromSequence}
26765
+ RETURNING f_event_id
26766
+ `)).rows.map((row) => row.f_event_id);
26866
26767
  }
26867
26768
  async deleteSession(exec, id) {
26868
- await exec.delete(this.tables["t_session_events"]).where(eq(this.tables["t_session_events"].fSessionId, id)).execute();
26769
+ const result = await exec.execute(sql`
26770
+ DELETE FROM ${this.tables["t_session_events"]} WHERE f_session_id = ${id} RETURNING f_event_id
26771
+ `);
26869
26772
  await exec.delete(this.tables["t_workspace_sessions"]).where(eq(this.tables["t_workspace_sessions"].fSessionId, id)).execute();
26870
26773
  await exec.delete(this.tables["t_session_projcache_row"]).where(eq(this.tables["t_session_projcache_row"].fSessionId, id)).execute();
26871
26774
  await exec.delete(this.tables["t_sessions"]).where(eq(this.tables["t_sessions"].fSessionId, id)).execute();
26775
+ return result.rows.map((row) => row.f_event_id);
26872
26776
  }
26873
- /** 事件行可能被多个会话共享(fork 派生),所以孤儿只能在全库范围内判定。 */
26874
26777
  async collectOrphans() {
26875
26778
  const tEvents = this.tables["t_events"];
26876
26779
  const tEventUsage = this.tables["t_event_usage"];
@@ -26879,7 +26782,6 @@ var PostgresBackend = class PostgresBackend {
26879
26782
  await this.db.delete(tEventUsage).where(notInArray(tEventUsage.fEventId, this.db.select({ fEventId: tEvents.fEventId }).from(tEvents))).execute();
26880
26783
  return result.rowCount ?? 0;
26881
26784
  }
26882
- /** 父会话被删后留下的 subagent 会话:父已不在表里,或本来就没有父。 */
26883
26785
  async listOrphanSubagentSessions() {
26884
26786
  const tSessions = this.tables["t_sessions"];
26885
26787
  return (await this.db.execute(sql`
@@ -26890,16 +26792,26 @@ var PostgresBackend = class PostgresBackend {
26890
26792
  `)).rows.map((row) => row.id);
26891
26793
  }
26892
26794
  async deleteSessions(exec, ids) {
26893
- if (ids.length === 0) return 0;
26894
- await exec.delete(this.tables["t_session_events"]).where(inArray(this.tables["t_session_events"].fSessionId, ids)).execute();
26795
+ if (ids.length === 0) return {
26796
+ deleted: 0,
26797
+ eventIds: []
26798
+ };
26799
+ const removed = await exec.execute(sql`
26800
+ DELETE FROM ${this.tables["t_session_events"]}
26801
+ WHERE f_session_id IN (${sql.join(ids.map((id) => sql`${id}`), sql`, `)})
26802
+ RETURNING f_event_id
26803
+ `);
26895
26804
  await exec.delete(this.tables["t_workspace_sessions"]).where(inArray(this.tables["t_workspace_sessions"].fSessionId, ids)).execute();
26896
26805
  await exec.delete(this.tables["t_session_projcache_row"]).where(inArray(this.tables["t_session_projcache_row"].fSessionId, ids)).execute();
26897
- return (await exec.delete(this.tables["t_sessions"]).where(inArray(this.tables["t_sessions"].fSessionId, ids)).execute()).rowCount ?? 0;
26806
+ const result = await exec.delete(this.tables["t_sessions"]).where(inArray(this.tables["t_sessions"].fSessionId, ids)).execute();
26807
+ return {
26808
+ deleted: Number(result.rowCount ?? 0),
26809
+ eventIds: removed.rows.map((row) => row.f_event_id)
26810
+ };
26898
26811
  }
26899
26812
  async vacuum() {
26900
26813
  await this.db.execute(sql`VACUUM ANALYZE`);
26901
26814
  }
26902
- /** 派生统计表的回填:任一表为空即按事件表全量重算(幂等,迁移删表重建后自愈)。 */
26903
26815
  async backfillUsageTables() {
26904
26816
  await this.backfillEventUsage();
26905
26817
  await this.backfillSessionUsage();
@@ -26909,10 +26821,6 @@ var PostgresBackend = class PostgresBackend {
26909
26821
  const existing = await this.db.execute(sql.raw(`SELECT count(*) AS n FROM ${table}`));
26910
26822
  return Number(existing.rows[0]?.["n"] ?? 0) === 0;
26911
26823
  }
26912
- /**
26913
- * 用量行:一条 `assistant/message` 事件行一行,物化本地日与两个归属标记。
26914
- * 本地日按数据库会话时区渲染(部署前提是它与 host 时区一致,`localDayKey` 同口径)。
26915
- */
26916
26824
  async backfillEventUsage() {
26917
26825
  if (!await this.tableIsEmpty(this.qualifiedTable("t_event_usage"))) return;
26918
26826
  const tEventUsage = this.tables["t_event_usage"];
@@ -26951,7 +26859,6 @@ var PostgresBackend = class PostgresBackend {
26951
26859
  ON CONFLICT DO NOTHING
26952
26860
  `);
26953
26861
  }
26954
- /** 会话 × 本地日 × 模型的 token 汇总:从桥接行 × 用量行重算(含 fork 继承前缀)。 */
26955
26862
  async backfillSessionUsage() {
26956
26863
  if (!await this.tableIsEmpty(this.qualifiedTable("t_session_usage"))) return;
26957
26864
  const tSessionUsage = this.tables["t_session_usage"];
@@ -26970,7 +26877,6 @@ var PostgresBackend = class PostgresBackend {
26970
26877
  ON CONFLICT DO NOTHING
26971
26878
  `);
26972
26879
  }
26973
- /** 会话 × 本地日的活动计数:从桥接行 × 事件类型重算。 */
26974
26880
  async backfillSessionCounts() {
26975
26881
  if (!await this.tableIsEmpty(this.qualifiedTable("t_session_counts"))) return;
26976
26882
  const tSessionCounts = this.tables["t_session_counts"];
@@ -26992,7 +26898,6 @@ var PostgresBackend = class PostgresBackend {
26992
26898
  ON CONFLICT DO NOTHING
26993
26899
  `);
26994
26900
  }
26995
- /** 用量聚合:与 SQLite 侧同形,读 `t_event_usage`,数值列回来是字符串。 */
26996
26901
  async listSessionRows(query = {}) {
26997
26902
  const needle = query.query?.trim().toLowerCase() ?? "";
26998
26903
  const like = `%${needle}%`;
@@ -27042,10 +26947,6 @@ var PostgresBackend = class PostgresBackend {
27042
26947
  total: numeric(counted.rows[0]?.["n"] ?? 0)
27043
26948
  };
27044
26949
  }
27045
- /**
27046
- * 用量聚合:与 SQLite 侧同形——只读三张派生统计表(用量行给去重口径的总量与「天 × 模型」桶,
27047
- * 两张会话汇总表给按会话的行),数值列回来是字符串。物化列让这里不做 `EXISTS`、不回连事件表。
27048
- */
27049
26950
  async usageReport(sinceMs) {
27050
26951
  const usageSince = sinceMs === void 0 ? sql`` : sql` AND f_created_at >= ${sinceMs}`;
27051
26952
  const dayFilter = (column) => sinceMs === void 0 ? sql`` : sql` AND ${sql.raw(column)} >= ${localDayKey(sinceMs)}`;
@@ -27135,12 +27036,10 @@ var PostgresBackend = class PostgresBackend {
27135
27036
  }))
27136
27037
  };
27137
27038
  }
27138
- /** 表名限定到配置的 schema。 */
27139
27039
  qualifiedTable(name) {
27140
27040
  const schema = this.options.schema ?? "public";
27141
27041
  return schema === "public" ? name : `"${schema}".${name}`;
27142
27042
  }
27143
- /** token 用量旁路累加:派生表 `t_session_usage`,不在写事务里(失败可丢,表可销毁重建)。 */
27144
27043
  async incrementSessionUsage(id, buckets) {
27145
27044
  if (buckets.length === 0) return;
27146
27045
  const tSessionUsage = this.tables["t_session_usage"];
@@ -27159,7 +27058,6 @@ var PostgresBackend = class PostgresBackend {
27159
27058
  f_total_tokens = ${tSessionUsage}.f_total_tokens + EXCLUDED.f_total_tokens
27160
27059
  `);
27161
27060
  }
27162
- /** 活动计数旁路累加:派生表 `t_session_counts`,同上(四项计数一次落)。 */
27163
27061
  async incrementSessionCounts(id, buckets) {
27164
27062
  if (buckets.length === 0) return;
27165
27063
  const tSessionCounts = this.tables["t_session_counts"];
@@ -27175,7 +27073,6 @@ var PostgresBackend = class PostgresBackend {
27175
27073
  f_tool_calls = ${tSessionCounts}.f_tool_calls + EXCLUDED.f_tool_calls
27176
27074
  `);
27177
27075
  }
27178
- /** 按会话重算两张汇总表(rewind / fork 之后):先删该会话的行,再从事件表重算。 */
27179
27076
  async rebuildSessionStats(id) {
27180
27077
  const tSessionUsage = this.tables["t_session_usage"];
27181
27078
  const tSessionCounts = this.tables["t_session_counts"];
@@ -27211,20 +27108,23 @@ var PostgresBackend = class PostgresBackend {
27211
27108
  GROUP BY 1, 2
27212
27109
  `);
27213
27110
  }
27214
- /** 全量重算用量行的引用标记(rewind / fork / 会话删除之后)。 */
27215
- async refreshEventUsageFlags() {
27111
+ async refreshEventUsageFlags(eventIds) {
27216
27112
  const tEventUsage = this.tables["t_event_usage"];
27217
27113
  const tSessionEvents = this.tables["t_session_events"];
27218
27114
  const tSessions = this.tables["t_sessions"];
27219
- await this.db.execute(sql`
27220
- UPDATE ${tEventUsage} SET
27221
- f_referenced = (EXISTS (SELECT 1 FROM ${tSessionEvents} rb
27222
- WHERE rb.f_event_id = ${tEventUsage}.f_event_id))::int,
27223
- f_subagent = (EXISTS (SELECT 1 FROM ${tSessionEvents} sb
27224
- JOIN ${tSessions} ss ON ss.f_session_id = sb.f_session_id
27225
- WHERE sb.f_event_id = ${tEventUsage}.f_event_id
27226
- AND ss.f_origin = 'subagent'))::int
27227
- `);
27115
+ for (let at = 0; at < eventIds.length; at += REFRESH_FLAG_CHUNK) {
27116
+ const chunk = eventIds.slice(at, at + REFRESH_FLAG_CHUNK);
27117
+ await this.db.execute(sql`
27118
+ UPDATE ${tEventUsage} SET
27119
+ f_referenced = (EXISTS (SELECT 1 FROM ${tSessionEvents} rb
27120
+ WHERE rb.f_event_id = ${tEventUsage}.f_event_id))::int,
27121
+ f_subagent = (EXISTS (SELECT 1 FROM ${tSessionEvents} sb
27122
+ JOIN ${tSessions} ss ON ss.f_session_id = sb.f_session_id
27123
+ WHERE sb.f_event_id = ${tEventUsage}.f_event_id
27124
+ AND ss.f_origin = 'subagent'))::int
27125
+ WHERE f_event_id IN (${sql.join(chunk.map((id) => sql`${id}`), sql`, `)})
27126
+ `);
27127
+ }
27228
27128
  }
27229
27129
  async deleteSessionStats(id) {
27230
27130
  await this.db.execute(sql`DELETE FROM ${this.tables["t_session_usage"]} WHERE f_session_id = ${id}`);
@@ -27284,11 +27184,6 @@ function physicalEvent(row) {
27284
27184
  ...row.fSurfaceOp !== null ? { surfaceOp: JSON.parse(row.fSurfaceOp) } : {}
27285
27185
  };
27286
27186
  }
27287
- /**
27288
- * 读旧格式用的 catalog:v3→v4 这条边要求显式子会话证据(上游 0.1.7 起),本仓库声明
27289
- * **无子会话证据**——v3 的目录事实由父会话自己在成功路径上写成 `subagent/catalog` 事件,
27290
- * 迁移的 child evidence 只是补偿入口;我们没有更权威的子集合来源可给(见 ADR-跟随上游session-format-v4)。
27291
- */
27292
27187
  const restoreCatalog = createSessionFormatCatalogWithChildren([]);
27293
27188
  function convertLegacyRows(row, eventRows) {
27294
27189
  const restore = restoreCatalog.createRestore(physicalHeader(row), {
@@ -27466,7 +27361,6 @@ var SessionBranchRdbProvider = class {
27466
27361
  internals.dropReuseEventIds(childId);
27467
27362
  throw error;
27468
27363
  }
27469
- await this.persistence.rebuildSessionStats(childId);
27470
27364
  return childId;
27471
27365
  }
27472
27366
  async rewind(id, toBoundary, signal) {
@@ -27539,7 +27433,6 @@ var SessionBranchRdbProvider = class {
27539
27433
  headSeq: keepLength - 1,
27540
27434
  snapshotEvents: () => []
27541
27435
  });
27542
- await this.persistence.rebuildSessionStats(id);
27543
27436
  return {
27544
27437
  header: rowToMeta(row),
27545
27438
  revision: await internals.readStoredRevision(id)
@@ -28814,7 +28707,6 @@ function unzip(data, opts, cb) {
28814
28707
  //#endregion
28815
28708
  //#region ../session-rdb/src/import.ts
28816
28709
  const SESSION_LOG_ARTIFACT_FILENAME = "session.jsonl";
28817
- const SESSION_IMPORT_PATH = "/api/session.import";
28818
28710
  const MAX_IMPORT_ZIP_BYTES = 67108864;
28819
28711
  function parseJsonlArtifact(content) {
28820
28712
  const lines = content.split("\n");
@@ -28875,11 +28767,6 @@ function parseJsonlArtifact(content) {
28875
28767
  events
28876
28768
  };
28877
28769
  }
28878
- /**
28879
- * fflate 的 `unzip` 是回调式异步 API(node 侧内部走 worker_threads,浏览器侧走 Web Worker),
28880
- * 这里桥成 Promise。它与 `unzipSync` 的差异只在同步/异步:非法 zip 走的是同一段校验代码,
28881
- * 拒绝的错误对象与同步版一致,因此上层文案无需区分。
28882
- */
28883
28770
  function unzipAsync(data) {
28884
28771
  return new Promise((resolve, reject) => {
28885
28772
  unzip(data, (error, entries) => {
@@ -29020,7 +28907,6 @@ function registerSessionImport(ctx, persistence) {
29020
28907
  }
29021
28908
  //#endregion
29022
28909
  //#region ../session-rdb/src/deletion.ts
29023
- const SESSION_DELETE_PATH = "/api/session.delete";
29024
28910
  const STATUS_OF_DELETION_ERROR = {
29025
28911
  SESSION_NOT_FOUND: 404,
29026
28912
  SESSION_NOT_ARCHIVED: 409,
@@ -29083,12 +28969,9 @@ function registerSessionDeletion(ctx, persistence) {
29083
28969
  }
29084
28970
  //#endregion
29085
28971
  //#region ../session-rdb/src/export.ts
29086
- const SESSION_EXPORT_PATH = "/api/session.export";
29087
- /** 文件名只保留安全字符:会话 id 来自请求体,会写进 Content-Disposition。 */
29088
28972
  function safeFilename(sessionId) {
29089
28973
  return sessionId.replace(/[^A-Za-z0-9._-]/gu, "_");
29090
28974
  }
29091
- /** fflate 只给回调式异步 API(同步变体被 node/no-sync 禁止)。 */
29092
28975
  function zipBytes(files) {
29093
28976
  return new Promise((resolve, reject) => {
29094
28977
  zip(files, (error, data) => {
@@ -29097,7 +28980,6 @@ function zipBytes(files) {
29097
28980
  });
29098
28981
  });
29099
28982
  }
29100
- /** 导出通道:直接把会话日志打成 zip 响应体,与导入通道读同一份 artifact。 */
29101
28983
  function registerSessionExport(ctx, persistence) {
29102
28984
  ctx.inject(["webServer", "connection"], (webCtx) => {
29103
28985
  const webServer = webCtx.webServer;
@@ -29157,15 +29039,12 @@ function registerSessionExport(ctx, persistence) {
29157
29039
  }
29158
29040
  //#endregion
29159
29041
  //#region ../session-rdb/src/gc.ts
29160
- const SESSION_GC_PATH = "/api/session.gc";
29161
- /** 让所有运行中的 agent 先退场:GC 要重写事件表,运行中的写路径必须停下来。 */
29162
29042
  async function stopRunningAgents(ctx) {
29163
29043
  const running = ctx.get("agents")?.list() ?? [];
29164
29044
  for (const agent of running) agent.cancel?.({ kind: "user" }, { keepInbox: true });
29165
29045
  await Promise.all(running.map((agent) => agent.whenIdle()));
29166
29046
  return running.length;
29167
29047
  }
29168
- /** GC 通道:停 agent → 回收孤儿事件行 → VACUUM,一次请求完成。 */
29169
29048
  function registerSessionGc(ctx, persistence) {
29170
29049
  ctx.inject(["webServer", "connection"], (webCtx) => {
29171
29050
  const webServer = webCtx.webServer;
@@ -29207,15 +29086,6 @@ function registerSessionGc(ctx, persistence) {
29207
29086
  }
29208
29087
  //#endregion
29209
29088
  //#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
29089
  const MAX_PAGE_SIZE = 200;
29220
29090
  const DEFAULT_PAGE_SIZE = 20;
29221
29091
  function registerSessionRows(ctx, backend) {
@@ -36201,19 +36071,12 @@ var SessionQueryRdb = class extends SessionQueryEngine {
36201
36071
  constructor(ctx, config = {}) {
36202
36072
  super(ctx, config);
36203
36073
  }
36204
- /**
36205
- * 官方列表(`session/list`)**默认不含已归档**:归档是「收起来」的会话,列表与搜索都不该把它混进
36206
- * 日常集合(上游 client 再用 workspace registry 的归档集做「只看归档」的派生)。
36207
- *
36208
- * 完整语料(含归档)走我们自己的管理面路由 `SESSION_ROWS_PATH`——两条路分开,不在一处混。
36209
- */
36210
36074
  async listSessions(signal) {
36211
36075
  const records = await super.listSessions(signal);
36212
36076
  const archived = this.archivedSessionIds();
36213
36077
  if (archived.size === 0) return records;
36214
36078
  return records.filter((record) => !archived.has(String(record.header.id)));
36215
36079
  }
36216
- /** 归档集合来自 workspace registry;服务未就绪时保守返回空集(宁可多给,不可漏掉可见会话)。 */
36217
36080
  archivedSessionIds() {
36218
36081
  const registry = this.ctx.get("workspaceRegistry");
36219
36082
  return new Set(registry?.archivedSessionIds ?? []);
@@ -36303,10 +36166,6 @@ var SessionProjectionCacheRdb = class extends Service {
36303
36166
  if (record === void 0) return void 0;
36304
36167
  return identityMatches(record.identity, expected) ? record : void 0;
36305
36168
  }
36306
- /**
36307
- * header-only 读(会话列表这类):上游 0.1.7 起只给 header 与 keys——它要求的是**生命周期**身份,
36308
- * 不再要求调用方提供 inherited cut(那个 cut 只有写路径与 hydration 用得上)。
36309
- */
36310
36169
  cachedSnapshot(meta, keys) {
36311
36170
  const record = this.recordForLifecycle(meta);
36312
36171
  const snapshot = record === void 0 ? void 0 : this.viewRecord(record, keys);
@@ -36449,7 +36308,6 @@ function detachJson(rows) {
36449
36308
  if (text === void 0) throw new TypeError("projection checkpoint is not losslessly JSON-serializable");
36450
36309
  return JSON.parse(text);
36451
36310
  }
36452
- /** header 能证明的生命周期身份(不含 inherited cut):header-only 读只比对它。 */
36453
36311
  function lifecycleIdentityOf(header) {
36454
36312
  return {
36455
36313
  formatVersion: header.version,
@@ -36622,7 +36480,6 @@ function installStorageTakeover(ctx, options) {
36622
36480
  //#endregion
36623
36481
  //#region ../session-rdb/src/index.ts
36624
36482
  init_esm();
36625
- /** 一批事件里的最大时间:写路径据此推进会话行的「最后活动时间」。 */
36626
36483
  function maxEventTime(events) {
36627
36484
  let max;
36628
36485
  for (const event of events) {
@@ -36890,12 +36747,7 @@ var RdbSessionHandle = class RdbSessionHandle {
36890
36747
  }
36891
36748
  static LIVE_WRITE_BATCH_MAX_DELAY_MS = 200;
36892
36749
  };
36893
- /**
36894
- * 本地化说明:`description()` 的类型签名只声明 `string`,而 meta 本身接受 `Dict<string>`
36895
- * (`vendor/schemastery/src/index.ts` 的 `mergeDesc` 就是按字典合并的),所以这里只做一次类型放行。
36896
- */
36897
36750
  const localized = (text) => text;
36898
- /** 投影 checkpoint 的写回节流:两个分支共用。 */
36899
36751
  const projectionCacheSchema = Schema.object({
36900
36752
  writeEveryEvents: Schema.natural().min(1).default(DEFAULT_PROJECTION_WRITE_EVERY_EVENTS).description(localized({
36901
36753
  zh: "每累计这么多事件写回一次投影 checkpoint。",
@@ -36955,7 +36807,6 @@ var SessionPersistenceRdb = class extends SessionPersistence {
36955
36807
  reuseEventIds = /* @__PURE__ */ new Map();
36956
36808
  liveBuffers = /* @__PURE__ */ new Map();
36957
36809
  liveReady = /* @__PURE__ */ new Map();
36958
- /** 本次装配用的配置快照(整段 Config 是 volatile,重挂会给新的)。 */
36959
36810
  config;
36960
36811
  constructor(ctx, config, injectedBackend) {
36961
36812
  super(ctx);
@@ -37098,27 +36949,24 @@ var SessionPersistenceRdb = class extends SessionPersistence {
37098
36949
  if (row.fArchivedAt === null) throw new SessionDeletionError(`session "${id}" is not archived; only archived sessions can be deleted`, "SESSION_NOT_ARCHIVED");
37099
36950
  if (this.tracker.hasPending(id) || this.tracker.hasOpenHandle(id)) throw new SessionDeletionError(`session "${id}" is live; stop it before deleting`, "SESSION_LIVE");
37100
36951
  await this.unarchiveBeforeDeletion(id);
37101
- await this.backend.transaction(async (tx) => {
37102
- await tx.deleteSession(id);
37103
- });
36952
+ const referenced = await this.backend.transaction((tx) => tx.deleteSession(id));
37104
36953
  this.liveBuffers.delete(id);
37105
36954
  this.liveReady.delete(id);
37106
36955
  this.liveFailures.delete(id);
37107
36956
  this.liveDropWarned.delete(id);
37108
36957
  this.reuseEventIds.delete(id);
37109
36958
  await this.dropSessionStats(id);
36959
+ await this.refreshEventUsageFlags(referenced);
37110
36960
  }
37111
- /** GC 通道:回收已无桥接行引用的事件行(孤儿),返回删除行数。 */
37112
36961
  async collectOrphans() {
37113
36962
  await this.ready;
37114
36963
  return this.backend.collectOrphans();
37115
36964
  }
37116
- /** GC 通道:回收父已不存在的 subagent 会话(live 的跳过),返回删除的会话数。 */
37117
36965
  async collectOrphanSessions() {
37118
36966
  await this.ready;
37119
36967
  const deletable = (await this.backend.listOrphanSubagentSessions()).filter((id) => !this.tracker.hasPending(id) && !this.tracker.hasOpenHandle(id));
37120
36968
  if (deletable.length === 0) return 0;
37121
- const deleted = await this.backend.transaction((tx) => tx.deleteSessions(deletable));
36969
+ const { deleted, eventIds } = await this.backend.transaction((tx) => tx.deleteSessions(deletable));
37122
36970
  for (const id of deletable) {
37123
36971
  this.liveBuffers.delete(id);
37124
36972
  this.liveReady.delete(id);
@@ -37127,14 +36975,13 @@ var SessionPersistenceRdb = class extends SessionPersistence {
37127
36975
  this.reuseEventIds.delete(id);
37128
36976
  await this.dropSessionStats(id);
37129
36977
  }
36978
+ await this.refreshEventUsageFlags(eventIds);
37130
36979
  return deleted;
37131
36980
  }
37132
- /** 用量统计:SQL 聚合的按天 × 模型桶与按会话行(事件行去重、排除孤儿行)。 */
37133
36981
  async usageReport(sinceMs) {
37134
36982
  await this.ready;
37135
36983
  return this.backend.usageReport(sinceMs);
37136
36984
  }
37137
- /** GC 通道:VACUUM;调用方需先停止运行中的写路径(见 `registerSessionGc`)。 */
37138
36985
  async vacuum() {
37139
36986
  await this.ready;
37140
36987
  await this.backend.vacuum();
@@ -37268,11 +37115,6 @@ var SessionPersistenceRdb = class extends SessionPersistence {
37268
37115
  ...tornFrom !== void 0 ? { tornFrom } : {}
37269
37116
  };
37270
37117
  }
37271
- /**
37272
- * 统计衍生表的**旁路累加**:不在写事务里、失败只 warn——两张会话汇总表都是可销毁重建的
37273
- * 派生表,丢几次累加不影响可用性(需要时用 `rebuildSessionStats` 重算)。
37274
- * token 走写路径已折好的用量行,活动计数走本批事件类型。
37275
- */
37276
37118
  async recordSessionStats(id, events, usageRows) {
37277
37119
  const usageBuckets = sessionUsageBuckets(usageRows);
37278
37120
  if (usageBuckets.length > 0) try {
@@ -37287,33 +37129,36 @@ var SessionPersistenceRdb = class extends SessionPersistence {
37287
37129
  this.ctx.logger.warn(`session-rdb: activity counts for "${id}" not updated (${describeError(error)})`);
37288
37130
  }
37289
37131
  }
37290
- /**
37291
- * 重算一个会话的统计(rewind / fork 之后;best-effort,同 `recordSessionStats`):
37292
- * 两张汇总表按会话重算,用量行的引用标记全量重算(引用可能跨会话消失)。
37293
- */
37294
37132
  async rebuildSessionStats(id) {
37295
37133
  try {
37296
37134
  await this.backend.rebuildSessionStats(id);
37297
- await this.backend.refreshEventUsageFlags();
37298
37135
  } catch (error) {
37299
37136
  this.ctx.logger.warn(`session-rdb: session stats for "${id}" not rebuilt (${describeError(error)})`);
37300
37137
  }
37301
37138
  }
37302
- /** 会话删除时清掉它的汇总行,并重算用量行的引用标记(被删会话引用的行要立刻退出统计)。 */
37139
+ async refreshEventUsageFlags(eventIds) {
37140
+ const unique = [...new Set(eventIds)];
37141
+ if (unique.length === 0) return;
37142
+ try {
37143
+ await this.backend.refreshEventUsageFlags(unique);
37144
+ } catch (error) {
37145
+ this.ctx.logger.warn(`session-rdb: usage reference flags not refreshed (${describeError(error)})`);
37146
+ }
37147
+ }
37303
37148
  async dropSessionStats(id) {
37304
37149
  try {
37305
37150
  await this.backend.deleteSessionStats(id);
37306
- await this.backend.refreshEventUsageFlags();
37307
37151
  } catch (error) {
37308
37152
  this.ctx.logger.warn(`session-rdb: session stats for "${id}" not deleted (${describeError(error)})`);
37309
37153
  }
37310
37154
  }
37311
37155
  async rewriteMigratedLog(id, log) {
37156
+ let removed = [];
37312
37157
  await this.backend.transaction(async (tx) => {
37313
37158
  const head = await tx.getHead(id);
37314
37159
  if (!this.writeGuard.has(id)) this.writeGuard.confirmHead(id, head.fHeadSequence);
37315
37160
  this.writeGuard.assertNoConcurrentWriter(id, head.fHeadSequence);
37316
- await tx.deleteBridgeTail(id, 0);
37161
+ removed = await tx.deleteBridgeTail(id, 0);
37317
37162
  await tx.upsertSession({
37318
37163
  meta: log.meta,
37319
37164
  inheritedEventCount: SessionLogOffset(log.inheritedEventCount)
@@ -37326,6 +37171,7 @@ var SessionPersistenceRdb = class extends SessionPersistence {
37326
37171
  await tx.bumpRevision(id, maxEventTime(log.events));
37327
37172
  });
37328
37173
  await this.rebuildSessionStats(id);
37174
+ await this.refreshEventUsageFlags(removed);
37329
37175
  }
37330
37176
  async listSnapshots(signal) {
37331
37177
  signal?.throwIfAborted();
@@ -37415,7 +37261,6 @@ var SessionPersistenceRdb = class extends SessionPersistence {
37415
37261
  registerReuseEventIds(childId, map) {
37416
37262
  this.reuseEventIds.set(childId, new Map(map));
37417
37263
  }
37418
- /** 丢弃一个还没被消费的复用映射(fork 失败时):留着会让同一个 childId 之后的落写复用父会话的事件行。 */
37419
37264
  dropReuseEventIds(childId) {
37420
37265
  this.reuseEventIds.delete(childId);
37421
37266
  }
@@ -37555,7 +37400,6 @@ function seedCoversPrefix(seed, prefix) {
37555
37400
  return seedEvent !== void 0 && JSON.stringify(seedEvent) === JSON.stringify(event);
37556
37401
  });
37557
37402
  }
37558
- /** 一批事件折成活动计数的桶:只数四种类型,按本地日聚合成四项。 */
37559
37403
  function sessionCountBuckets(events) {
37560
37404
  const counted = /* @__PURE__ */ new Map();
37561
37405
  for (const event of events) {
@@ -37573,7 +37417,6 @@ function sessionCountBuckets(events) {
37573
37417
  }
37574
37418
  return [...counted.values()];
37575
37419
  }
37576
- /** 一批用量行折成 token 桶:按「本地日 × 模型」聚合(模型未知落空串,与表口径一致)。 */
37577
37420
  function sessionUsageBuckets(usageRows) {
37578
37421
  const counted = /* @__PURE__ */ new Map();
37579
37422
  for (const row of usageRows) {
@@ -37927,4 +37770,4 @@ function turnLog(base, turn, opts = {}) {
37927
37770
  return events;
37928
37771
  }
37929
37772
  //#endregion
37930
- export { SESSION_EDITOR_PATH, Session, SessionIdBrand, SessionSeq, SessionStore, TokenMeter, assistantMessage, createPersisted, harness, meta, oneTurnLog, parseJsonlArtifact, turnLog, twoTurnLog, userMessage };
37773
+ export { SESSION_EDITOR_PATHS, Session, SessionIdBrand, SessionSeq, SessionStore, TokenMeter, assistantMessage, createPersisted, harness, meta, oneTurnLog, parseJsonlArtifact, turnLog, twoTurnLog, userMessage };