/** * module: interaction —— 对外类型(编码规范 §10.1;语义出处 OS 02 / 09,AM 07)。 * * 交互的**结构**归基座,**内容**归活动: * 对话流 = 任务的人机时间线、**一切回环输入的唯一入口**; * 视图区 = 只读呈现容器,内容由后端流控推送。 */ import type { ChatMessage, StaffId, TaskId, ViewBlock } from '@contracts'; import type { ControlHolder, InteractionPhase, TemplateViolation } from './internal/domain/control-handover.ts'; export type { ChatMessage, ControlHolder, InteractionPhase, TemplateViolation, ViewBlock }; export declare class TemplateRejectedError extends Error { readonly violations: readonly TemplateViolation[]; constructor(violations: readonly TemplateViolation[]); } /** SSE 推送的信封 —— 前端按目标失效对应缓存(§9.2) */ export type PushEnvelope = { readonly kind: 'chat'; readonly taskId: TaskId; readonly message: ChatMessage; } | { readonly kind: 'view'; readonly taskId: TaskId; readonly blocks: readonly ViewBlock[]; } | { readonly kind: 'view-html'; readonly taskId: TaskId; readonly html: string; } | { readonly kind: 'control'; readonly taskId: TaskId; readonly phase: InteractionPhase; readonly output: ControlHolder; } | { readonly kind: 'invalidate'; readonly target: 'task' | 'workflow' | 'desk'; readonly id: string; }; export type Subscription = { close(): void; }; export type InteractionService = { /** 对话流:任务的人机时间线 —— 与任务记忆同源、不同筛选 */ timeline(taskId: TaskId): Promise; /** * 负责人的自由输入 —— 人事件的第二形态(卡回传之外): * 写入任务记忆,loop 作为观察进入后续窗口;**不驱动 flow 步**。 * 叫停等指令走控制事件,不经自由输入。 */ say(taskId: TaskId, by: StaffId, text: string): Promise; /** 视图流控:活动按局面推结构化块 */ pushView(taskId: TaskId, blocks: readonly ViewBlock[]): Promise; /** 视图流控:活动推 HTML 模版 —— 过净化门,不过即抛 */ /** * 推一帧 HTML。`template` **要一起存下来** —— 件是投影,而投影得能按当下数据**重渲**。 * * 只存渲染好的 HTML,那一帧就成了快照:人在件上改完几格,由数据算出来的东西 * (类徽 A/B/C、分布条、结论横幅那条去向)全停在渲染那一刻 —— * 屏上一半新一半旧,那不是没更新,是界面在撒谎。记住模版名,才重得出来。 */ pushViewHtml(taskId: TaskId, html: string, template?: string): Promise; /** 视图区当前呈现什么 —— 只读投影,刷新即重建(前端零过程状态) */ viewOf(taskId: TaskId): Promise<{ blocks?: readonly ViewBlock[]; html?: string; template?: string; } | undefined>; /** 控制权转交 —— 随引擎的激活 / 完成事件自动发生 */ setPhase(taskId: TaskId, phase: InteractionPhase): Promise; phaseOf(taskId: TaskId): Promise; /** SSE:单连接、按信封目标失效缓存 */ subscribe(staffId: StaffId, onPush: (envelope: PushEnvelope) => void): Subscription; /** * **系统级转接**:装配层把所有推送接出去(转给前端的 SSE 广播),不按人筛。 * * 为什么不复用 `subscribe`:那个要一个收件人,而转接是系统的事 —— * 为它编一个假员工 id 是撒谎。不按人筛也不漏:推送**不带业务载荷** * (只说「哪个任务动了」),谁该看见由前端自己那几把查询决定(§9.2 前端零过程状态)。 * * 没有这个口子的后果实测过:AI 说的每一句、推的每一帧画面都广播给了空气 —— * `subscribe` 一个订阅者都没有,前端只好靠人点刷新。 */ onPush(listener: (envelope: PushEnvelope) => void): Subscription; /** 供上层广播失效信号 */ invalidate(target: 'task' | 'workflow' | 'desk', id: string): void; };