/** * 会话级任务清单店车道([ref];黑板 [ref] F2 症状 → [ref] 定谳)。 * * core 的 task-list 家族(TaskCreate/TaskGet/TaskUpdate/TaskList)把持久化做成了注入缝: * `assembleCodeTools({ taskList: true, taskListStore })` —— **不传** `taskListStore` 时 core 落 * `store ?? createMemoryTaskListStore()` 的私有臂。而本仓的场景工厂是**每请求**调用的 * (`boot/resolve-spec.ts` 的 `selectScenario(...)(body, …)`;`http/route-ctx.ts` 逐字「resume 会用 * 同一条 resolveSpec 重解析场景」),于是修前每个 turn 都铸一只新私店:同一会话的 TaskList 每 * turn 清空、TaskCreate 从 #1 重新铸号(已经发给用户的任务回执从此指向别的任务)。 * * 本文件就是那条缝的属主 —— **一个 sessionId ↦ 一只店**的解析器,boot 期构造一次,注入 * `ScenarioDeps.taskListStoreFor`。 * * ## 分区语义:listKey = 会话 * 任务清单是**会话级待办**:同 session 跨 turn / 跨 resume 连续,不同 session 互不可见。 * ([ref] §6 D2 的「团队共享清单」是同一张表的另一族分区键 —— 见 {@link taskListKeyFor} * 的 `session:` 命名空间位。) * * ## 后端三态(与 roster / mailbox / background-agent 同姿势) * - `pg` / `tidb`:对应的 SQL twin(`plugins/task-list-store-sql.ts`)。跨副本、跨重启都真续得上; * 高水位(next_id)与插入序(next_sort)都由 meta 行的事务计数器持有,`mutate` 是真后端事务。 * - `memory`:无 SQL 后端(local/file 形、纯内存 dev)⇒ **进程级** per-session 内存店。跨 turn * 存活(这就是 F2 的修点),但**跨进程重启丢失**,且 LRU 溢出(> `maxSessions`)时最老的会话 * 会被逐出=那条会话的待办清空。诚实登记在 USAGE(「local 形重启丢待办」),不自造持久性: * 这与 local 形其余会话态设施(perSessionCwd / TtlSessionStore)是同一姿势。 * * ## 为什么 SQL 形也进同一只缓存 * SQL twin 是池上的无状态薄壳,每 turn 新铸一只在**行**的层面完全等价(真源在表里,`[ref]` * 的 T2 三条就钉这个)。仍然缓存实例的理由是 core 的另一半契约:`createTaskListTools` 对 * **同一只 store 实例**维护一条 per-store promise 链来串行 read-modify-write。同副本内复用实例 * ⇒ 本进程内的串行化真生效,跨副本仍由 SQL twin 自己的分区锁兜底;缓存被逐出也只是回到「靠 * 数据库锁」这一层,没有正确性损失。 */ import type { Pool as MySqlPool } from "mysql2/promise"; import type { Pool as PgPool } from "pg"; import { type TaskListStore } from "@sema-agent/core"; import type { Logger } from "../observability/logger.js"; /** 同时活着的会话清单实例上限(LRU,逐最老)。与 perSessionCwd/perSessionShellEnv 同量级。 */ export declare const MAX_TASK_LIST_SESSIONS = 4096; /** 车道实际落到哪个后端(启动日志 + 测试对表)。 */ export type TaskListLaneBackend = "tidb" | "pg" | "memory"; export interface TaskListLane { /** `sessionId ↦ 该会话的任务清单店`。同 sessionId 恒解析到同一份清单(SQL 形 = 同一批行; * memory 形 = 同一只实例)。 */ storeFor: (sessionId: string) => TaskListStore; /** * 会话删除级联(E21;codex R1-[high] 验真后加)。抹掉该会话的全部任务行**与高水位 meta 行**, * 并逐出进程内缓存条目。 * * 🔴 为什么必须有:会话 id 由调用方自选、会话删除后可被**别人** `register` 重新登记(security.ts * 的 claim-create),而本车道的 listKey 是 `sessionId` 的确定性派生 ⇒ 不级联的话,新化身第一次 * 打开清单就读到上一位主人的任务标题/描述,`TaskCreate` 还会从上一位的高水位续号(「这里以前 * 建过 47 条」本身也是外泄)。同族腿(anchors / approval-exemption / session-policy / attachment / * snapshot / scratchpad)全部走同一条级联,本条与它们同 scoping 理由:路由已在 DELETE 门证过会话 * 所有权,故按 sessionId 单键删。 * * 幂等(没写过任何任务的会话删 0 行);错误如实上抛 —— E21 的次序契约要求子腿失败就中止,让 * session_meta 留着、幂等重试收敛(session-faces.ts 顶注)。 * * 📌 **残余,如实登记**(codex R2-[high] 判真、**射程外**):删除进行中若有一次并发 `storeFor`, * 它会拿到一只新铸的可写店,写入可能落在 purge 事务提交**之后** ⇒ 行被重建、留给下一个化身。 * 这不是本腿独有的窗口,而是**整条 E21 级联共有的**残余(anchors / exemption / policy / attachment / * snapshot / task_list 全是按 session_id 单键删、且各自开始时刻不同),`session-faces.ts` 的 purge * 协调器里那段「残余,如实登记」逐字记着它与它的两条收口候选。真正消灭它要么让整条级联化身原子 * (单事务横跨十余张表 + 对象存储),要么引入**跨副本 durable 的会话化身号**并让每次写校验它 —— * 两者都是设计级改动(属主裁决 + 三问),给 task-list 单独发明一套围栏只会在同一语义面上多一个 * 写者。⇒ 归入既有的那条后续件,不在本批自作主张。 */ deleteBySession: (sessionId: string) => Promise; backend: TaskListLaneBackend; } export { taskListKeyFor } from "../plugins/task-list-store-sql.js"; export interface TaskListLaneOpts { /** MySQL 协议池(`StoreBackend.mysqlPool()`)。与 `pgPool` 互斥——两者都在时 PG 赢(与 roster/ * mailbox/background-agent 三处的既有分支序逐字一致,不新造第二种优先序)。 */ mysqlPool?: MySqlPool | undefined; pgPool?: PgPool | undefined; /** LRU 上限(默认 {@link MAX_TASK_LIST_SESSIONS});测试用小值钉逐出语义。 */ maxSessions?: number; logger?: Logger | undefined; } /** * 车道构造(boot 期一次)。 * * ⚠️ **建表不在这里**:`ensure{TiDB,Pg}TaskListSchema` 由 `boot/stores.ts` 在构造之前 await * (与 roster/mailbox/background-agent/usage-window 四族逐字同姿势)。理由不是风格—— * `test/schema-baseline-generated.test.ts` 判据 ③b 拿「`boot/stores.ts` 里真被 await 的 ensure 名」 * 与「生成器驱动的清单」做**集合相等**对账,那是消灭「两条方言同时漏一族表」这个对称盲区的唯一 * 不对称真源(`usage_window` 曾整族漏在基线外)。DDL 藏在本文件里 = 那道门看不见它。 */ export declare function createTaskListLane(opts?: TaskListLaneOpts): TaskListLane; //# sourceMappingURL=task-list-lane.d.ts.map