/** * 易变注入的请求尾部 (`dynamicTail`) — 把「每轮都可能变」的上下文搬到 * `event.messages` 的最后一条,而不是塞进 `event.system`。 * * 为什么:system 前缀是 provider 前缀缓存最值钱的部分。状态卡每轮都变(账本 * 动一下、窗口 bucket 跳一格、重试指令出现/消失),它一改,system 里它之后的 * 每一个 token 都失去缓存命中——一份 ≤300 token 的卡片能让几万 token 的 system * 前缀全部作废。把它挪到请求末尾的一条 user 消息,system 就保持逐字节稳定。 * * 形状: * - 生产者(状态卡 / 窗口 / 重试指令 / 拆分建议)`set(sid, key, text)` 写进 * 进程内 sink;每个 key 每会话只有一格(后者覆盖前者)。 * - 本层注册**最后**一个 `session.hook("context")`(在 prune 之后,所以它追加 * 的消息不会被同一请求的修剪看见):`take(sid)` 取出并清空,按 * state → window → retry → advice 的顺序渲染成**一条**消息: * `{ id: "team-dynamic", role: "user", content: [{ type: "text", text }] }`。 * - 宿主会跨请求复用 `event.messages`,所以追加前先剥掉以 `[Team 动态上下文]` * 开头的旧尾部——否则每轮多一条,最后挤掉真正的工作。 * * **默认 off(2026-10-22 实测)。** 宿主 2.1.x 会把这条例 `role:"user"` 消息持久化进 * 会话历史,并在下一次请求里把它当作「最后一条用户消息」回放(`synthetic:true` 也拦不住), * 所以本层默认不注册;功能保留,只在确认宿主提供非唤醒通道后手动 `dynamicTail: "on"`。 * * `dynamicTail: "off"` 时本层根本不注册,四个生产者各自走原来的 `event.system` * 路径,逐字节与改动前一致(这是回滚路径,也是套件里钉住的对照)。 */ import type { TeamScope } from "./v2-scope.js"; import type { V2Context, V2Registration } from "./v2-types.js"; /** 尾部消息的标记:`event.messages` 里首段文本以它开头的那条就是我们的。 */ export declare const DYNAMIC_TAIL_MARKER = "[Team \u52A8\u6001\u4E0A\u4E0B\u6587]"; /** 一条尾部消息的 id——便于宿主/测试认出它,不是能力令牌。 */ export declare const DYNAMIC_TAIL_MESSAGE_ID = "team-dynamic"; /** 生产者可写的段。 顺序即渲染顺序,见 `DYNAMIC_SECTION_ORDER`。 */ export type DynamicSection = "state" | "window" | "retry" | "advice"; /** * 渲染顺序是**声明**的,不是 Map 的插入顺序:四个生产者按各自 hook 的注册 * 顺序写入,若靠插入顺序,重排注册就会重排消息体(一条会被前缀缓存的内容)。 */ export declare const DYNAMIC_SECTION_ORDER: readonly DynamicSection[]; export interface DynamicSink { /** 记下一段易变上下文。空文本被忽略(没有内容就不占位)。 */ set(sessionID: string, key: DynamicSection, text: string): void; /** 取出并清空该会话的全部段。没有内容时返回空 Map(不是 undefined)。 */ take(sessionID: string): Map; /** 只读当前值(同一请求内 hook 被重放时的幂等判定);不清空。 */ peek(sessionID: string, key: DynamicSection): string | undefined; } /** * 进程内 sink:`Map>`。 * * 不做 TTL:`take` 在每个请求的尾部层里清空自己,sink 的生命周期就是「两个 * hook 之间」——一个会话的残留最多活到它下一次组装请求,且只影响它自己。 */ export declare function createDynamicSink(): DynamicSink; /** 一条消息的首段文本(`content[]` 是 v2 的形状,`parts[]` 是旧形状;两者都认)。 */ export declare function firstTextOf(message: unknown): string; /** 是我们上一轮追加的尾部消息吗?marker 前缀或我们铸造的 id,任一即可。 */ export declare function isDynamicTailMessage(message: unknown): boolean; /** 渲染一条尾部消息的正文:首行 marker,每段前一行 `### <段名>`。 */ export declare function renderDynamicTail(sections: Map): string; /** `dynamicTail: "off"`(以及旧的假值拼写)关闭;注册表默认 off,只有显式 on 才走尾部注入。 */ export declare function resolveDynamicTailConfig(config?: { dynamicTail?: string; }): { enabled: boolean; }; export interface DynamicTailReport { enabled: boolean; /** 真的追加了一条尾部消息的请求数。 */ messages: number; /** 这些尾部消息的字符数合计(与 `estimateTokens` 同一口径)。 */ bytes: number; /** 被剥掉的旧尾部消息条数——大于 0 就是「宿主复用了消息数组」的证据。 */ stripped: number; /** sink 为空的请求数(没有易变上下文,一个字节都没追加)。 */ empty: number; /** 是 ours 但请求没带消息数组:sink 内容留着给下一次,不在这里丢。 */ noMessages: number; /** scope 判定非 ours 的请求数(一个字节都不动)。 */ foreignSkipped: number; /** hook 内部抛错(永不外溢,只计数)。 */ threw: number; } export interface DynamicTailInput { scope?: TeamScope; /** `dynamicTail: "off"` → 不注册该层(旧行为逐字节一致)。 */ enabled?: boolean; sink: DynamicSink; /** 每个真实注入一行轨迹(`step_id:"v2-tail"` 由人格侧提供)。 */ onEvent?: (row: Record) => void; } export interface DynamicTailLayer { registrations: V2Registration[]; report: DynamicTailReport; } /** * 注册请求尾部层。**必须是最后一个 `session.hook("context")`**:四个生产者都在 * 它之前写入 sink,prune 也在它之前看过消息数组,它负责把 sink 的内容收成一条 * 消息并在同一次请求里剥掉旧尾部。 */ export declare function applyV2DynamicTail(ctx: V2Context, input: DynamicTailInput): Promise; //# sourceMappingURL=v2-tail.d.ts.map