# LineUp Tauri Reference Host 这是同一套 LineUp Runtime 的两种运行方式:Tauri 2 Desktop Host 和 Web Reference Host。 它们不是两个客户端,也不是两套聊天功能;两者加载相同的 Runtime 和 Chat Core App。 - **Tauri Desktop Host** 是正式桌面应用的外壳。它提供窗口,并在 Runtime 作出授权决定后 调用文件、通知等系统能力。 - **Web Reference Host** 是浏览器中的开发和验证入口。它方便通过 Vite 热更新、 Tailscale 联调和自动化测试,但受浏览器权限限制,不能替代桌面外壳。 无论使用哪种方式,AppServer 通信、消息同步、Store 和 Agent 协议都由 Runtime 管理。 当前不存在并行的独立 Android/Kotlin 或 Wails 客户端路线。 ## 已实现的最小闭环 - 可信 Host 前端:AppServer 地址、手机号/验证码登录、退出; - AppServer 交互:`/login`、`/messages/send`、`/messages/sync`; - 用户消息本地即时回显与送达状态; - HTTP 同步游标:首次 `0`、后续最后序号 `+ 1`; - `lineup.v1.text`、`agent.status`、`agent.progress`、`error` 的基础 Renderer; - `marked + DOMPurify` 的 GFM Markdown 安全渲染; - Tauri 2 Rust Host、capability 文件与桌面窗口配置。 AppServer 已配置只允许 `tauri://localhost`、`https://tauri.localhost`、`http://tauri.localhost` 三个 Tauri Host origin 跨源调用。若产品使用新的 Tauri origin 或独立 Web 客户端,应先在 `lineup-app-server/configs/lineup.yaml` 的 `client.allowedOrigins` 显式加入,不能放开任意 Origin。 macOS 的 `src-tauri/Info.plist` 为当前 Tailscale / 私网 HTTP AppServer 配置了 `NSAllowsArbitraryLoadsInWebContent`。该例外仅作用于 Host 的 WKWebView 内容请求,使其能访问类似 `http://100.x.y.z:8090` 的地址,不放宽 Rust 原生网络层;待所有 AppServer 都迁移到 HTTPS 后应移除。 Surface、Capability、签名 Bundle 与隔离 bridge 已有 M2~M4 的实现和测试基础,但它们不等于已开放的应用市场或通用 Installed App。当前 MVP-R1 的重点是 Runtime 托管 Chat:任何 Surface 或能力请求仍必须经过 Runtime 验证、受限 bridge、Capability Policy 和用户确认,不能在 Host 页面中直接执行 Agent JavaScript 或直接授予设备能力。 ## Milestone 0 当前行为基线 本节记录 Runtime 重构前已经由 Mac 开发态验证的行为。它是 M0 的回归基线,不代表最终模块结构;正式阶段任务和准入门槛见 [App Layer 架构方案 §9.1](../设计/02.正式方案/lineup-app-layer-architecture.md#91-阶段任务清单与硬性准入门槛)。 | 范围 | 当前行为 | 当前实现位置 | 重构后必须保持 | |---|---|---|---| | 登录 | 用户输入 AppServer 地址、手机号、验证码;登录请求 10 秒超时;地址仅保存到 localStorage | `src/main.ts`、`src/runtime/communication/transport-adapter.ts` | 成功登录进入会话;失败原因对用户可见 | | 会话 | 当前只有一个 Agent channel;退出清空页面内消息并停止同步 | `src/main.ts` | 会话状态必须可由 Store 管理,不能依赖 DOM | | 发送 | 用户消息先本地显示,再通过 Transport 调用 `/messages/send`;显示已发送或失败 | `src/main.ts`、`src/runtime/communication/transport-adapter.ts` | 即时反馈、送达状态与失败可见性不退化 | | 历史同步 | 首次从 cursor `0` 连续拉取到空页;之后以 `lastSeq + 1` 每 1.5 秒经 Transport 增量轮询 | `src/main.ts`、`src/runtime/communication/transport-adapter.ts` | 保持包含式 cursor 语义,不能漏消息或无限热循环 | | Agent 文本 | 将 `lineup.v1.text` 解码为 Markdown,使用 `marked + DOMPurify` 渲染 | `src/runtime/protocol/lineup-v1.ts`、`src/core-apps/chat/trusted-dom-renderers.ts` | GFM、安全净化和外链防护不退化 | | Agent 状态 | `thinking` 显示临时指示器;其他状态更新顶栏 presence | `src/runtime/protocol/lineup-v1.ts`、`src/core-apps/chat/trusted-dom-renderers.ts` | 状态应更新已有状态项,而非无限追加气泡 | | 进度和错误 | progress/error 目前以系统提示显示 | `src/runtime/protocol/lineup-v1.ts`、`src/core-apps/chat/trusted-dom-renderers.ts` | 迁移后至少保持可见,M1 再升级为正式卡片 | | 未实现扩展 | `ui.open / patch / close` 与 `app.call` 仅安全降级为提示,绝不执行不可信脚本或原生能力 | `src/runtime/protocol/lineup-v1.ts`、`src/main.ts` | 在 M2/M3 完成前始终保持拒绝执行 | ### M0 回归场景 每次迁移或拆分模块后,至少执行以下场景;其中 `R01` 为构建级检查,其余为 Mac 浏览器访问 Linux 开发 Host 的人工验收,直至 M0 建立自动化测试。 - **M0-R01:构建。** `npm run build` 通过 TypeScript 检查与 Vite 打包。 - **M0-R01a:Runtime 回归。** `npm test` 执行 Kernel 与 Conversation Store 的自动化回归;覆盖合法/非法/未知协议输入、重复 envelope ID、包含式 cursor、本地回显和 sync echo 去重。 - **M0-R02:登录。** 使用 Tailscale AppServer 登录;成功切换到聊天页,错误地址/超时给出可理解提示。 - **M0-R03:历史追平。** 登录后 presence 从“正在同步消息”变为“已同步,等待消息”,已有 Agent 历史可见。 - **M0-R04:本地回显。** 发送普通文本后,用户气泡立即出现,随后显示已发送或发送失败。 - **M0-R05:Agent 回复。** Hermes 回复能够在未刷新页面时出现,并按 Markdown 安全渲染。 - **M0-R06:刷新恢复。** 刷新并重新登录后,历史仍可追平,不能重复无限渲染同一条 Agent 消息。 - **M0-R07:安全降级。** 收到未知 `lineup.v1.*` 类型、`ui.*` 或 `app.*` 请求时,页面不崩溃、不执行脚本/能力,并给出可见提示。 ### M0 完成记录(2026-08-03) M0-01~M0-08 已完成。Mac 经 Linux Tailscale Web Host `http://100.121.118.116:1420` 完成 M0-08 总验收:登录、历史追平、本地回显、Agent 回复、真实 Markdown(标题、加粗、行内代码、围栏 TypeScript 代码块)和刷新重新登录恢复均正常;用户与 Agent 消息不重复,presence 最终为“已同步,等待消息”。同时 `npm test` 的 2 个文件 / 8 个 Runtime 测试与 `npm run build` 均通过。 ### M1-01 Tool Call 状态机(已完成) `src/runtime/protocol/lineup-v1.ts` 现可严格解码 `lineup.v1.tool.call`、`lineup.v1.tool.result`、`lineup.v1.tool.cancel`,只接受 `choice`、`confirm`、`input` 三种声明性请求。`src/runtime/coordination/tool-call-state.ts` 是独立于 UI、Storage、Transport、Tauri 与 Capability 的纯状态机;它以 `call_id` 为唯一关联键,只允许 `pending → submitted → completed / failed / cancelled / expired`,拒绝重复调用及非法状态跃迁。Interaction Kernel 仅投影这些状态,且只有已提交的 call 能接受 result。 这不是可用的交互组件:M1-01 不渲染选项、不提供按钮/表单、不发送 tool result,也不修改 Hermes Adapter。可信 ActionGroup 留给 M1-02,Confirm/Input/Form 留给 M1-03。`npm test` 当前为 3 个文件 / 14 个测试通过,`npm run build` 通过。 ### M1-02 ActionGroup(已完成) `choice` tool call 必须在 `payload.data.action_group` 中使用 `single-choice`、`multi-choice` 或 `button` 模式,声明 1~12 个具有唯一 `id` 的 action。协议层会拒绝未知模式、空列表、重复 id 和超长文本。可信 Renderer 使用原生 DOM 按钮而不解释远端 HTML:单选和按钮动作点击后立即提交,多选在用户显式点击“提交选择”后一次性提交;卡片随即显示“已提交,等待 Agent 确认”并禁用所有控件。过期 call 从一开始即不可操作。 提交仅写入 Interaction Kernel 的 `{ action_ids: [...] }` submission 快照;此阶段没有网络请求、outbox、`tool.result` 回传、刷新恢复或 Hermes Adapter 改动。它们分别留给后续 M1 任务。`npm test` 当前为 3 个文件 / 15 个测试通过,`npm run build` 通过。 ### M1-03 Confirm 与 Input/Form(已完成) `confirm` call 的 `data.confirm` 只接受有限长度的确认/取消文案;`input` call 的 `data.form` 只接受 1~8 个具有唯一 id 的 `text`、`textarea` 或 `number` 字段,并可限定必填、placeholder、长度与正则。协议层拒绝未知字段类型、空/重复字段、非法正则和超限 schema。所有 UI 均为 Host 自己创建的原生 DOM,不解释 Agent HTML。 确认、取消、表单本地校验、提交与超时都更新同一 `call_id` 的状态,并在终态禁用控件。Conversation Store v4 额外持久化 Tool Call projection 及未提交表单草稿:刷新或重新登录时,待处理卡片与草稿会恢复;离线期间到期的 call 会恢复为不可操作的 expired。提交仍不发送网络请求或 `tool.result`,也不处理 Agent echo 或 Hermes Adapter。`npm test` 当前为 3 个文件 / 18 个测试通过,`npm run build` 通过。 ### M1-04 任务与进度聚合(已完成) 已有的不带 `operation_id` 的 `agent.progress` 仍显示为兼容性系统提示;带 `operation_id` 的更新必须使用严格的 task schema,并聚合到同一张可信原生任务卡片。每次进度、状态或 Agent `cancelled` 更新只更新卡片,不会无限新增聊天消息。可取消任务提供“请求取消”入口;本阶段它只记录本地请求并禁用入口,等待后续 Agent 状态确认。 Conversation Store v5 持久化任务快照,刷新或重新登录后仍只显示任务最新状态。尚未创建取消 outbox、尚未发送任何网络事件、尚未实现 result echo 或 Hermes Adapter 契约。`npm test` 当前为 4 个文件 / 22 个测试通过,`npm run build` 通过。 ### M1-05 `tool.result` 回声(已完成) 每张可信 Tool Call 卡片均以 `call_id` 建立展示索引。同步到达的合法 `tool.result` 或 `tool.cancel` 只会更新同一张 choice / confirm / input 卡片的最终状态,并禁用控件;它们不会再被渲染为独立系统消息或“不支持的 Agent 内容”。未关联到本地卡片的 result/cancel 保持无展示副作用。 本项没有接入 Adapter 的实际发送、Agent 侧幂等消费或审计;这些是 M1-06 的唯一范围。`npm test` 当前为 4 个文件 / 22 个测试通过,`npm run build` 通过。 ### 当前结构债务(M0 的改造对象) 该段记录的是 M0 历史结构债务。当前 MVP-R1 已将 Transport、Store、sync loop 与 outbox 收敛至 `src/runtime/coordination/lineup-runtime.ts`;HTTP 实现位于 `src/runtime/communication/transport-adapter.ts`,可信 Chat DOM Renderer 位于 `src/core-apps/chat/trusted-dom-renderers.ts`。现行目录与边界见 [src/README.md](src/README.md)。 ### M0-02 协议模型(已完成) `src/runtime/protocol/lineup-v1.ts` 是目前唯一允许把 Transport 原始 payload 转换成可信表现模型的入口。它定义了 `JsonValue`、`Actor`、严格 `Envelope`、`ConversationItem`、`UserAction`、`DeliveryState` 与 `ProtocolFallback`,并提供: - `parseEnvelope(value)`:校验版本、消息类型、`id`、`conversation_id`、sender/target、timestamp 和 JSON payload; - `parseEnvelopeJSON(raw)`:供不接受历史纯文本的 Transport / Kernel 路径把非法 JSON 明确归为 `invalid_json`; - `decodeConversationItem(raw)`:保留历史纯文本作为 Markdown 的兼容行为;对于非法 LineUp envelope、未知类型和各类非法 payload,返回受控 fallback,绝不触发执行。 该文件不依赖 DOM、`fetch`、Tauri 或本地存储。Surface 与 Capability 在本阶段只校验其基础合约并降级显示;不加载 bundle,也不调用能力。下一项 M0-03 才会将此模型接入 Interaction Kernel 和 Renderer Registry。 ### M0-03 Interaction Kernel 与 Renderer Registry(已完成) `src/runtime/coordination/interaction-kernel.ts` 是 Transport 与表现层之间的唯一入口:它将原始同步/实时 payload 解码为 `ConversationItem`,对有有效 LineUp `id` 的消息实施有界去重,并将 Agent status 投影为可读取的 presence 快照。它不执行 `ui.*`、`app.*` 或用户动作。 `src/core-apps/chat/renderer-registry.ts` 只根据已验证的 `ConversationItem.kind` 分派已注册的可信 renderer;renderer 的输入只有表现项与 View context,不会获得 Transport 或 Capability 对象。当前聊天页已通过该 Registry 渲染同步进入的标准项。历史上 M0-03 完成时具体 DOM renderer 仍在 `main.ts`;该 M0-06 迁移现已完成,当前可信 DOM renderer 位于 `src/core-apps/chat/trusted-dom-renderers.ts`。 ## 本地命令 ```bash cd lineup-app/tauri npm install # 日常开发:启动 Vite 热更新 + 未打包的 Tauri 桌面壳 npm run desktop:dev # 纯 Web 开发:Linux 开发机监听全部网卡,供 Mac 浏览器访问 npm run web:dev # Mac 浏览器访问(经 Tailscale) # http://100.121.118.116:1420 # 仅检查并构建前端静态文件 npm run build # Linux:仅构建 Debian 包,避免为不需要的 AppImage 下载额外运行器 npm run tauri -- build --debug --bundles deb # macOS(Apple Silicon / Intel):生成 .app 与 .dmg npm run tauri build -- --debug ``` `npm run desktop:dev` 不生成 `.app`、`.dmg` 或安装包。前端 TypeScript / CSS / HTML 保存后会由 Vite 自动热更新;改动 `src-tauri/` 中的 Rust Host 代码时,Tauri 会重新编译并重启开发窗口。只有需要交付可安装文件时才执行 `tauri build`。 `npm run web:dev` 是在浏览器里启动同一份可信 Host UI,适合快速检查登录、消息和 Renderer。它监听 `0.0.0.0:1420`;Mac 可经 Tailscale 打开 `http://100.121.118.116:1420`,直接看到 Linux 工作区的 Vite 热更新,无须把源码同步到 Mac。浏览器模式没有 Tauri 的原生能力,也不能用作第三方 Mini Runtime Surface 的安全验收 环境。AppServer 只对固定开发 Origin `http://127.0.0.1:1420` 和 `http://100.121.118.116:1420` 配置 CORS;若改动 Vite 端口或 Host 地址,必须同步更新并 保持收紧 AppServer 的 `client.allowedOrigins`,不得改成允许任意来源的 wildcard。 本项目的 `tauri` 与 `check:rust` npm 脚本会自动识别 Linux x86_64、macOS Apple Silicon、macOS Intel 的 Rust stable toolchain,并加入 `PATH`。因此不要直接执行裸 `tauri build`;统一使用 `npm run tauri build -- --debug`。如 Rust toolchain 安装在其他位置,可临时覆盖: ```bash RUST_TOOLCHAIN_BIN=/path/to/rust/bin npm run tauri build -- --debug ``` ## 本机构建状态(2026-08-03) - `npm run build`:已通过; - Cargo/Tauri Rust crates:已下载并开始编译; - Tauri Linux desktop build:已通过;debug 可执行文件和 Debian 包已生成; - 独立 Android/Kotlin 与 Wails Host:不在当前或后续规划范围内。 Linux 桌面构建依赖以下系统开发库;本机已具备。新环境缺少时可安装: ```bash sudo apt-get update sudo apt-get install -y pkg-config libdbus-1-dev libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev ``` 本机已生成的产物: ```text src-tauri/target/debug/lineup-tauri src-tauri/target/debug/bundle/deb/LineUp_0.1.0_amd64.deb ``` Tauri 构建产物位于 `src-tauri/target/`,已被 Git 忽略。图标源文件是 `src-tauri/icons/icon.svg`;当前交付只面向 Tauri Desktop Host,其他图标派生产物不代表额外客户端路线。 ## 目录边界 ```text tauri/ ├── src/ │ ├── main.ts # 应用启动装配入口:连接 Host、Runtime 与默认 App │ ├── core-apps/chat/ # Chat Shell、可信 Renderer 与样式 │ └── runtime/ # 按通信、协调、存储、应用、Surface、能力等分层 ├── src-tauri/ # Tauri Rust Host 与 capability 配置 ├── vite.config.ts # @/ → src/ 路径别名(Vite / Vitest) └── package.json # Vite、Tauri CLI 与前端依赖 ``` 各 Runtime 子目录、依赖方向与新增模块约定见 [src/README.md](src/README.md)。 第三方 Agent Surface 未来必须运行于独立隔离执行域,只能经 LineUp Mini Runtime 事件桥与 Capability Gateway 通信;不得访问 Tauri `invoke`、Host DOM、登录态或本机特权。