import type { StoreApi } from 'zustand/vanilla'; import type { ChatStore } from '../store/chat-store.js'; import type { MessageAttachment } from '../types.js'; import type { GatewayClient } from './gateway-client.js'; /** 一条排队中的用户消息。`frame` 与直发路径同形(`client_msg_id === messageId`),附件已在入队前上传。 */ export interface QueuedEntry { messageId: string; conversationId: string; /** 放出时气泡的正文;取消回填也用它(= composeOutgoing 之后的原文)。 */ content: string; attachments?: MessageAttachment[]; frame: Record; enqueuedAt: number; } /** 每会话队列上限(🅳5)。 */ export declare const QUEUE_LIMIT = 5; /** 选择器只读 store 的这几片(hooks 可直接把 `useStore` 的 state 传进来)。 */ export type OutboundSelectorState = Pick; /** * 新消息是否必须入队(plan v1.2 裁定 5)。在途项受 Δ10 门控:本 session 从未见过带 * `clientMsgId` 的 finish(老 gateway)时忽略原始账本 —— 否则 id 永不删除 ⇒ 空闲也入队。 */ export declare function selectMustQueue(s: OutboundSelectorState, conv: string): boolean; /** hook 暴露的在途数(Δ10 门控后的值;停止键 / 逃生口 busy 都读它)。 */ export declare function selectOutstandingCount(s: OutboundSelectorState, conv: string): number; /** * 「这个会话的队列会不会自己放」(Δ12:⛔ 含 `turnCmidSupported` 合取项 —— 新 session 首个 * 回合里入队的条目**会**被首条带 cmid 的 finish 放出)。四个合取项各挡一种「标签撒谎」: * 账空(结构上已无边沿)/ 抑制位在场(下一个边沿只删不放)/ 他会话在忙(边沿不放)/ * 本会话既不忙也不活跃(幻影账:服务端其实没在跑)。 */ export declare function selectWillAutoDrain(s: OutboundSelectorState, conv: string): boolean; export interface OutboundQueueDeps { store: StoreApi; /** 出网;= GatewayClient.send 的布尔值(false = 未连通 / 宿主 onBeforeSend 拦截)。 */ send: (frame: Record) => boolean; now: () => number; } export declare class OutboundQueue { private readonly deps; /** 已观察到回合终态(R2 的 `∈` 删)、或被屏障丢弃(`onMessageDropped` 出账时记)的 id,每会话 LRU —— 只拦「已终结 id 被 outbox 重投复活」。 */ private readonly terminatedIds; /** `track()` 过的 id → 会话。R1 / E3 只认它们;任何出账都**不**从这里移除。 */ private readonly tracked; constructor(deps: OutboundQueueDeps); private st; mustQueue(conv: string): boolean; list(conv: string): readonly QueuedEntry[]; outstandingCount(conv: string): number; willAutoDrain(conv: string): boolean; /** 追加到队尾(I6);满 ⇒ `'queue_full'`、队列不变。 */ enqueue(entry: QueuedEntry): 'queued' | 'queue_full'; /** 按 id 出队(任意位置);不在队里 ⇒ false。 */ cancel(id: string): boolean; /** * 用户「立即发送」:放出**该条**(不必是队头),不看谓词。起新回合(放出前本会话空闲)才写 * `lastRelease`(Δ6 第②写点)—— 空闲判定**必须在放出之前快照**:真 provider 里 `send()` * 入 outbox 后同步派发、经 `onMessageDispatched` 置 `isThinking`,放出后再判恒为「不空闲」。 */ sendNow(id: string): boolean; track(id: string, conv: string): void; /** R1:`send()` 入 outbox 时与每次 dispatch 各调一次 ⇒ 幂等(已在账不重复追加)。 */ onMessageAccepted(id: string, conv: string): void; /** outbox `failed`:四种 cause 出账(⛔ 记 terminatedIds —— 该消息仍可能被 retry 重投;⛔ 评估)。 */ onDeliveryFailed(id: string, cause?: string): void; onServerEvent(event: unknown): void; /** 逃生口(`settleStuckTurn(C,'escape_hatch')` 的队列腿):清账、⛔ 评估、⛔ 动抑制位。 */ onSettle(conv: string): void; /** 会话删除 / 重置:清该会话的三张 store 表与两张私有表。 */ onConversationGone(conv: string): void; /** `startNewSession`:清全部(含 `turnCmidSupported`)。 */ onSessionReset(): void; /** * Δ17 / R2 / R3 / R5 / Δ14。唯一的自动放帧边沿:带 `finish.clientMsgId` 的 finish,该 id ∈ 本会话 * 在途账,删除使账**恰好变空**,且两道守卫都不成立。 */ private onFinish; /** * gw#2824:gateway 说这条消息被中止屏障丢弃了(它不会有自己的 finish)。在本会话账里 ⇒ 出账并记 terminatedIds; * 删完账恰好变空且抑制位在场 ⇒ 消费抑制位(与 `finish{abort}` 同一个 R5 语义)。⛔ 任何情况下都不评估(abort 终态不放队列, * 同守卫②);⛔ 不翻 `turnCmidSupported`、不走 `dropPreFlip`。不在账里 ⇒ 什么都不做。 */ private onMessageDropped; /** * Δ14:`turnCmidSupported` 由假翻真时丢弃翻转前的账(⛔ 记 terminatedIds)。触发会话只丢排在 * `cmid` 之前入账的(之后入账的是真在途);`cmid` 不在触发会话账里 ⇒ 该会话一条不丢; * 其它会话全部丢弃。返回「触发会话里确有 id 被丢」。 */ private dropPreFlip; private onError; /** Δ13 的 billing_error 腿:只认 outbox 按 transient 重试的那类(= delivery-outbox `billingIsTransient`)。 */ private onBillingError; /** * `session_ready`:**先 E3、后 Δ16**,都 ⛔ 评估。E3 只补 pending、Δ16 只删 delivered,正确实现下 * 作用对象不相交;定死先后是为了让「Δ16 连 pending 也删」这种错写法不被 E3 当场补回而隐身。 */ private onSessionReady; /** * canDrain:队列非空 ∧ 账空 ∧ 本会话不在 processing ∧ 他会话都不在 processing ∧ 本会话没有 * tracked 且仍 pending 的消息(Δ13 配套:正在 outbox 重试的消息不在账里,⛔ 被越过)。 */ private canDrain; /** 评估:只评估触发边沿的那个会话,最多放一条(队头);放出即写 lastRelease(Δ6 第①写点)。 */ private evaluate; /** * 放出一条:`addMessage` → `send(structuredClone(frame))`(disarmed 分支会原地剥 cmid)→ 返 false 撤回留队。 * 克隆或出网抛错(如 `metadata` 里有函数 ⇒ `DataCloneError`)同「返 false」:撤回气泡、留队、⛔ 抛出 * (自动边沿跑在 provider 的 `onEvent` 里,抛出会逃出 `ws.onmessage` 并留下气泡 + 队列条目两份)。 */ private releaseOne; private writeLastRelease; private findQueued; private dequeue; /** 按 id 在所有会话的账里出账(⛔ 记 terminatedIds、⛔ 评估)。 */ private dropFromAnyLedger; private hasTrackedPending; private isTerminated; private markTerminated; } /** * provider 用:`getClient` 惰性(读 `clientRef.current`)—— 队列在 `useMemo` 里先于 * `new GatewayClient` 创建;client 为 null 时 `send` 退化成「发不出去」(放出失败 ⇒ 撤回留队)。 */ export declare function createOutboundQueue(store: StoreApi, getClient: () => Pick | null): OutboundQueue; //# sourceMappingURL=outbound-queue.d.ts.map