Files
app/tauri/README.md
T

192 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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-R01aRuntime 回归。** `npm test` 执行 Kernel 与 Conversation Store 的自动化回归;覆盖合法/非法/未知协议输入、重复 envelope ID、包含式 cursor、本地回显和 sync echo 去重。
- **M0-R02:登录。** 使用 Tailscale AppServer 登录;成功切换到聊天页,错误地址/超时给出可理解提示。
- **M0-R03:历史追平。** 登录后 presence 从“正在同步消息”变为“已同步,等待消息”,已有 Agent 历史可见。
- **M0-R04:本地回显。** 发送普通文本后,用户气泡立即出现,随后显示已发送或发送失败。
- **M0-R05Agent 回复。** Hermes 回复能够在未刷新页面时出现,并按 Markdown 安全渲染。
- **M0-R06:刷新恢复。** 刷新并重新登录后,历史仍可追平,不能重复无限渲染同一条 Agent 消息。
- **M0-R07:安全降级。** 收到未知 `lineup.v1.*` 类型、`ui.*``app.*` 请求时,页面不崩溃、不执行脚本/能力,并给出可见提示。
### M0 完成记录(2026-08-03
M0-01M0-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-02Confirm/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` 模式,声明 112 个具有唯一 `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
# macOSApple 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、登录态或本机特权。