Persist PageTree expand state via control-plane view-state and align chevron/DOM with restored expansion; keep Sidex-style shallow page-tree scan and drop the unused recursive scanner that only added cargo noise. Add password vault workbench routes/runtime/skill/CLI, split page_ai_pi into a module package, and retire Hermes/ACP/OpenHub recycle + root harness evidence from the index while gitignoring recycle and local diag dumps. Archive superseded design/bugs docs under old/, point architecture at ARCHITECTURE.md, and refresh smokes for Pi S1–S7, vault, and editor regressions so the working tree can stay clean.
737 lines
42 KiB
Markdown
737 lines
42 KiB
Markdown
# [recycle] 7-58 Page AI Reasonix / Hermes 官方会话与响应对齐 v1
|
||
|
||
> 创建时间:2026-06-09
|
||
>
|
||
> 当前状态:`PROCESS`
|
||
>
|
||
> Owner:07-ai / Page AI / Reasonix ACP / Hermes ACP / mnote-web runtime
|
||
>
|
||
> 参考代码:
|
||
> - Reasonix 官方 `main-v2/desktop`:https://github.com/esengine/DeepSeek-Reasonix/tree/main-v2/desktop
|
||
> - Hermes 官方仓库:`NousResearch/hermes-agent`:https://github.com/NousResearch/hermes-agent
|
||
> - 本地参考副本:`reference-code/DeepSeek-Reasonix-main`
|
||
> - MNote 当前链路:`rust/crates/mnote-web/src/routes/hermes_client.rs`、`rust/crates/mnote-web/src/acp_session_manager.rs`、`rust/crates/mnote-web/browser/sidebar-page-ai-runtime.js`、`rust/crates/mnote-web/src/ssr/styles.rs`
|
||
>
|
||
> 上位依据:
|
||
> - `design/07-ai/process/7-18-local-first-agent-file-editing-control-plane-v1.md`
|
||
> - `design/07-ai/done/7-40-page-ai-context-envelope-and-run-receipt-v1.md`
|
||
> - `design/10-review/reference/13-hermes-vscode-plugin-gap-review-v1.md`
|
||
> - `design/10-review/done/10-current-mnote-ai-runtime-review-v1.md`
|
||
|
||
## 1. 第一结论
|
||
|
||
Page AI 里 Reasonix 不能继续按“每个 run 都可 `session/load` 恢复旧 ACP session”的模式设计。
|
||
|
||
Reasonix 官方 `main-v2/desktop` 的 ACP 模型是:
|
||
|
||
- `initialize.agentCapabilities.loadSession=false`。
|
||
- `session/new` 创建一个内存 `Session`,其中持有 `CacheFirstLoop`、toolset、MCP clients、Eventizer 和 aborter。
|
||
- `session/prompt` 必须命中同一个内存 `Session`,然后调用 `session.loop.step(text)` 继续对话。
|
||
- 上下文主要在 Reasonix 本地 loop / session 对象里延续,而不是靠 MNote 每轮重发完整聊天历史。
|
||
|
||
因此 MNote 对 Reasonix 的正确集成不是“保存 acpSessionId,下轮再 load”,而是:**同一个 MNote Page AI session 在同一个 Rust Web 进程生命周期内必须复用同一个活的 Reasonix ACP session manager;如果底层 Reasonix session 已丢失,必须显式进入 cold resume/fallback,不得假装仍是原生 Reasonix 上下文。**
|
||
|
||
这也解释当前现象:用户第二轮只回“可以”时,MNote payload 没有带历史 messages;它本来就依赖 Reasonix 本地 session 记住上一轮。但实际后端每轮都 fallback 出新的 Reasonix ACP session,Reasonix 当然只能把“可以”当新会话开头。
|
||
|
||
## 2. 当前实测问题
|
||
|
||
2026-06-09 现场证据:同一个 MNote session `mnote_current_page-ai-mq6q24d5` 连续三轮 run,实际 Reasonix ACP session 发生了三次变化:
|
||
|
||
| 轮次 | 用户输入 | 前端发送的 acpSessionId | 后端实际 session.info.updated acpSessionId |
|
||
| --- | --- | --- | --- |
|
||
| 1 | 看看你现在能不能看到有3个mcp | 空 | `19dbc6c6-b61e-45d1-8a28-52d0e79a02e3` |
|
||
| 2 | 可以 | `19dbc6c6-b61e-45d1-8a28-52d0e79a02e3` | `daa898ff-e985-49da-bbb9-ba3072f130c7` |
|
||
| 3 | 帮我检查一下 LightRAG 服务的启用状态或者帮它启动起来 | `daa898ff-e985-49da-bbb9-ba3072f130c7` | `ed358871-5176-4f3c-869c-91578cefa2b3` |
|
||
|
||
并且第二轮 run payload 中没有 `messages` 字段,只有当前 `message="可以"`、`pageContext`、`contextRefs`、`acpSessionId` 等。这证明当前 Page AI 并没有 MNote 侧 transcript 注入兜底。
|
||
|
||
同一轮后端还出现大量:
|
||
|
||
```text
|
||
WARN ACP SSE lagged: N events dropped
|
||
```
|
||
|
||
这说明 ACP event 转发路径有背压丢事件。当前事件路径会在 `broadcast` 接收后先持久化 SQLite / local journal,再写入浏览器 SSE;当 Reasonix 高频输出 `thought.delta` / `message.delta` 时,256 buffer 很容易落后。终止事件或状态同步被拖慢/丢失后,前端 assistant bubble 的 `streaming=true` 不能及时清掉,CSS 就继续显示 `AI · 输出中`。
|
||
|
||
当前 SQLite `ai_runtime_runs.status` 仍停在 `acp_pending`,但同 run 的 `ai_runtime_events` 已有 `run.completed`。这会继续污染 active-run 判断、恢复逻辑和前端状态。
|
||
|
||
## 3. Reasonix 官方模式摘录
|
||
|
||
### 3.1 ACP 能力
|
||
|
||
Reasonix 官方 `src/cli/commands/acp.ts` 在 `initialize` 中返回:
|
||
|
||
```ts
|
||
agentCapabilities: {
|
||
loadSession: false,
|
||
promptCapabilities: { image: false, audio: false, embeddedContext: true },
|
||
mcpCapabilities: { http: false, sse: false },
|
||
}
|
||
```
|
||
|
||
含义:MNote 不能把 `session/load` 当成 Reasonix 的正常恢复入口。对于 Reasonix,`acpSessionId` 只在当前 ACP 进程内有效。
|
||
|
||
### 3.2 session/new
|
||
|
||
Reasonix `session/new` 做的事不是只分配一个 id,而是完整构造一个 session:
|
||
|
||
- resolve workspace root。
|
||
- build code toolset。
|
||
- load MCP servers,并把 MCP tools bridge 进 tool registry。
|
||
- 构造 `ImmutablePrefix`。
|
||
- 创建 `CacheFirstLoop`。
|
||
- 创建 `Eventizer`。
|
||
- 保存到内存 `sessions` map。
|
||
|
||
这就是 Reasonix 的上下文承载点。
|
||
|
||
### 3.3 session/prompt
|
||
|
||
Reasonix `session/prompt` 通过 `params.sessionId` 找内存 session,然后:
|
||
|
||
```ts
|
||
for await (const ev of session.loop.step(text)) {
|
||
for (const kev of session.eventizer.consume(ev, session.ctx)) {
|
||
dispatchKernelEvent(server, session.id, kev);
|
||
}
|
||
}
|
||
```
|
||
|
||
因此多轮对话是否连续,关键是 MNote 是否一直把后续 prompt 发给同一个 live Reasonix session。
|
||
|
||
### 3.4 Desktop UI 终止语义
|
||
|
||
Reasonix 官方 desktop controller 在 turn 结束时发送类似 `$turn_complete` 的终止事件,并在 finally 中清理 running 状态、刷新 sessions/balance。MNote Page AI 当前只靠 `run.completed` SSE 和前端 `streaming=false` 清理 UI,这条链路在 SSE lag/drop 下不够稳。
|
||
|
||
## 4. 目标架构
|
||
|
||
### 4.1 双层 session 模型
|
||
|
||
MNote 必须区分两层 session:
|
||
|
||
| 层 | 作用 | 是否持久 | 对 Reasonix 的含义 |
|
||
| --- | --- | --- | --- |
|
||
| MNote Page AI session | UI 历史、SQLite/local JSONL、用户可见会话、run audit | 持久 | 可用于恢复 UI 和构造 fallback context |
|
||
| Reasonix ACP session | Reasonix 内存 `CacheFirstLoop`、MCP toolset、模型上下文 | 进程内 live | 不能假设可 load,丢失后就是 cold |
|
||
|
||
新增/收口一个运行时绑定概念:
|
||
|
||
```text
|
||
PageAiAcpLiveBinding
|
||
mnoteSessionId
|
||
acpRuntime = reasonix
|
||
acpSessionId
|
||
workspaceRoot / cwd
|
||
rootUri / allowedRootsHash
|
||
profile / model
|
||
managerHandle
|
||
status: idle | running | queued | cold | closed
|
||
lastRunId
|
||
lastEventSeq
|
||
createdAt / lastPromptAt
|
||
```
|
||
|
||
`managerHandle` 只在 Rust Web 进程内存中存在;SQLite 只保存 `acpSessionId` 和 metadata,用于 UI 展示、审计和检测 stale,而不是承诺可恢复 Reasonix 原生上下文。
|
||
|
||
### 4.2 Reasonix run 启动规则
|
||
|
||
对于 `acpRuntime=reasonix`:
|
||
|
||
1. 先按 `mnoteSessionId + acpRuntime` 查 live binding。
|
||
2. 若 binding 存在且 status 非 closed,直接复用同一个 `AcpSessionManager` 和 `acpSessionId` 调 `session/prompt`。
|
||
3. 若 binding 不存在:
|
||
- 不调用或不依赖 `session/load`。
|
||
- 调 `session/new` 创建新 Reasonix session。
|
||
- 将新 `acpSessionId` 写回 MNote session metadata。
|
||
- 若该 MNote session 已有历史 transcript,标记为 `cold_resume`,走 fallback context 注入。
|
||
4. 当前 run 完成后,不关闭 manager,不从 live binding 中移除;只把 binding 状态改回 idle。
|
||
5. 用户显式关闭会话、切换 workspace/root/model/profile、登出、进程退出或资源压力回收时,才关闭 binding。
|
||
|
||
这和当前 `run_prompt_with_mnote_context(...).await` 后直接 `mgr_clone.close().await`、`ACP_ACTIVE_RUNS.remove(...)` 的模式相反。对于 Reasonix,turn 完成不等于 Reasonix session 生命周期结束。
|
||
|
||
### 4.3 Hermes 与 Reasonix 分歧
|
||
|
||
不要把 Hermes VSCode 的 `session/load` 结论机械套到 Reasonix:
|
||
|
||
- Hermes adapter 如果声明支持 `loadSession`,MNote 应继续优先 `session/load`。
|
||
- Reasonix 官方明确 `loadSession=false`,MNote 应使用 live binding + cold resume fallback。
|
||
- UI 上要能显示当前会话是否是 `native-live`、`cold-resumed`、`new-session`,避免用户误以为 Reasonix 原生上下文仍在。
|
||
|
||
### 4.4 Hermes Desktop / Web UI 对照
|
||
|
||
Hermes 参考实现反而证明:Hermes 与 Reasonix 的上下文工程必须分开设计,只能共享外层 MNote envelope / audit / UI projection。
|
||
|
||
Hermes 官方公开文档与本地参考包需要分层理解:
|
||
|
||
- ACP adapter 的 `session/load` / `session/new` / `session/prompt` 属于 adapter 会话合同;host 不能只把旧 `sessionId` 写回内存后直接 prompt。
|
||
- 官方 user guide 明确提醒:ACP session 列表、load、resume、fork 的范围以当前运行的 ACP server process 为准。也就是说,MNote 不能把 Hermes `session/load` 永久等同为跨进程必然成功。
|
||
- Hermes VSCode 插件的 `SessionManager.ensureSession()` 说明了 host 侧正确动作:已有 `storedSessionId` 时必须先调用 ACP `session/load`,让 adapter 注册会话;`session/load` 返回 `null` 或失败后才能 `session/new`。插件注释还指出,跳过 `session/load` 会形成 phantom session。
|
||
- Hermes Web / dashboard 路径体现的是 server / DB 级会话资产:`state.db` 中有 sessions、messages、conversation chain、search/detail/export 这类持久查询能力。这是 MNote 做会话管理和 history dashboard 的参考,但不能直接推导为 ACP adapter 任意时刻都能无损 replay。
|
||
- 本地历史包 `hermes-web-ui-0.5.18` 的 Socket.IO server 体现了 Web/API chat 路径:`resumeSession()` 先找 live `sessionMap`,找不到再从 DB `loadSessionStateFromDb()` 读取 messages、compression snapshot 和 token 统计。这条路径不是 ACP `session/load`,应放在 Track W。
|
||
|
||
因此 Hermes 的正确集成不是“无条件相信 session id 可恢复”,而是:**按 runtime capability 探测 -> 有 stored `acpSessionId` 就先 `session/load` -> load 成功才认为底层 adapter 连续 -> load 失败进入 Hermes cold/new;若 adapter replay 了 history,MNote 必须消费、去重、排序。**
|
||
|
||
Reasonix 官方 desktop/ACP 与这两者都不同:Reasonix ACP 声明 `loadSession=false`,上下文在当前进程内的 `CacheFirstLoop` 上。MNote 如果每轮关闭 `AcpSessionManager`,就等于每轮丢掉 Reasonix 原生上下文。
|
||
|
||
因此 MNote 后续应冻结三套 runtime policy:
|
||
|
||
| Runtime | 上下文主承载 | 恢复策略 | MNote 责任 |
|
||
| --- | --- | --- | --- |
|
||
| Hermes ACP | adapter session;若 adapter 支持并成功 `session/load`,才视为底层连续 | stored ACP session id -> `session/load` -> 按 load 结果和 replay evidence 决定连续性 -> `session/prompt` | 保存/加载 acpSessionId;消费可能出现的 replay;处理 load 失败 fallback |
|
||
| Hermes Web/API Chat | Hermes server transcript + DB state | DB messages / compression snapshot -> resume socket/run | 管理 messages、summary、run status、queue |
|
||
| Reasonix ACP | live `CacheFirstLoop` in ACP process | live binding 复用;丢失后 cold resume packet | 维持 live binding,不假装 session/load 可用 |
|
||
|
||
共享部分只包括:
|
||
|
||
- MNote Page AI session / run id / trace id。
|
||
- `agentRunEnvelope`、target package、allowed roots、capability policy。
|
||
- SQLite/local JSONL audit。
|
||
- 前端 projection:assistant final、thought、tool、plan、permission、citation、run status。
|
||
|
||
不共享的部分是:底层 agent session 生命周期、上下文恢复机制、是否重发 transcript、是否允许 `session/load`。
|
||
|
||
## 5. Context 工程设计
|
||
|
||
### 5.1 native-live 路径
|
||
|
||
当 Reasonix live binding 存在时:
|
||
|
||
- MNote 每轮 prompt 只发送当前用户输入、当前页面/selection/target package、allowed roots、MNote capability envelope。
|
||
- 不重发完整 transcript,避免破坏 Reasonix 自己的 prefix/cache/memory 设计。
|
||
- MNote 仍持久化 user/assistant/tool events,用于 UI 历史、审计、fallback 和 reload。
|
||
|
||
### 5.2 cold resume fallback
|
||
|
||
当 MNote Page AI session 有历史,但 Reasonix live binding 丢失时:
|
||
|
||
- 创建新的 Reasonix ACP session。
|
||
- 将 MNote 最近 N 轮 transcript、上一次 summary、当前 page context、allowed roots 组成一个明确的 `MNote cold resume context packet`。
|
||
- 作为 embedded/context prompt 的前置内容发给 Reasonix。
|
||
- 前端显示轻量状态:`Reasonix 原生会话已重建,已用 MNote 历史摘要恢复上下文`。
|
||
- 该状态进入 run audit,不能静默伪装成原生连续会话。
|
||
|
||
fallback packet 必须控制大小:
|
||
|
||
- 优先 summary。
|
||
- 再补最近 3-5 轮 user/assistant final。
|
||
- tool raw output 只放 tool name、result summary、changed files、citations,不放大段 stdout。
|
||
- thought/reasoning 默认不进入 fallback。
|
||
|
||
### 5.3 用户短回复消歧
|
||
|
||
对“可以”“继续”“按刚才说的”“同意”这类短回复:
|
||
|
||
- native-live:完全交给 Reasonix 原生上下文。
|
||
- cold-resumed:fallback packet 必须包含上一轮 user intent 和 assistant proposal。
|
||
- new-session:前端或后端应提示“当前 Reasonix 原生上下文不可用,短回复缺少指代对象”,不要让模型编造。
|
||
|
||
## 6. Response / UI 状态设计
|
||
|
||
### 6.1 事件分类
|
||
|
||
Reasonix event 到 MNote UI 的分类应对齐官方 desktop 的 projection 思路:
|
||
|
||
| Reasonix / ACP 事件 | MNote 展示 |
|
||
| --- | --- |
|
||
| message/model delta | 当前 assistant streaming bubble |
|
||
| model final / run completed | 关闭 streaming bubble,写入 final |
|
||
| thought delta | 折叠的“思考过程”,不混入 final answer |
|
||
| tool intent/result | tool card,带 status、locations、citations、audit id |
|
||
| plan/todo | plan/status panel,不当作 assistant 正文 |
|
||
| turn complete | 解除 busy / 输出中 / active run snapshot |
|
||
|
||
### 6.2 终止事件不可丢
|
||
|
||
`run.completed` / `run.failed` / `run.aborted` / synthetic `turn.complete` 属于不可丢事件。
|
||
|
||
即使中间 `thought.delta` 或 `message.delta` 被限流/合并,终止事件也必须送达浏览器并持久化。前端 `输出中` 只能由 active run status 和 streaming flag 共同决定:
|
||
|
||
- 收到 terminal event:强制 `streaming=false`。
|
||
- SSE 断开但后端 run 已 terminal:`/active-run` 或 `/runs/{id}/events` reconciliation 必须合成 terminal event。
|
||
- SQLite run status terminal 时,active-run 不得再返回该 run。
|
||
|
||
### 6.3 状态持久化
|
||
|
||
当前 `ai_runtime_events` 已记录 `run.completed`,但 `ai_runtime_runs.status` 仍停在 `acp_pending`,这是 UI 卡 “输出中” 和 active-run 误判的根因之一。
|
||
|
||
后续必须保证:
|
||
|
||
- `register_runtime_from_create_run_response` 初始化 run 为 `running` 或 `queued`,不要长期停在 `acp_pending`。
|
||
- `persist_acp_runtime_event(run.completed)` 同步更新 SQLite `ai_runtime_runs.status=completed`。
|
||
- `persist_acp_runtime_event(run.failed)` 同步更新 `failed`。
|
||
- `persist_acp_runtime_event(run.aborted)` 同步更新 `aborted`。
|
||
- 内存 registry 与 SQLite status 不一致时,以 terminal event journal 为 reconciliation truth,补写 run status。
|
||
|
||
## 7. SSE / 背压设计
|
||
|
||
当前 `broadcast` consumer 同时承担“持久化事件”和“转发浏览器 SSE”,导致高频 Reasonix 输出时 `ACP SSE lagged`。
|
||
|
||
目标拆分:
|
||
|
||
```text
|
||
Reasonix ACP event
|
||
-> in-memory run event bus
|
||
-> browser SSE fanout, low latency, terminal priority
|
||
-> persistence worker, async/batched, can lag but不能阻塞 SSE
|
||
-> run status reconciler, terminal immediate
|
||
```
|
||
|
||
规则:
|
||
|
||
- `thought.delta` 可合并、降采样或只持久化部分摘要。
|
||
- `message.delta` 可按时间片或 token chunk 合并,保证顺序。
|
||
- tool events、permission events、session.info、plan、terminal events 不降采样。
|
||
- terminal event 使用独立 high-priority path;即使普通 delta buffer 满,也不能 drop。
|
||
- persistence worker 失败只产生 audit warning,不应阻塞浏览器终止状态。
|
||
|
||
## 8. 后续执行总图
|
||
|
||
本设计后续不应拆成“先随手修 Reasonix,再顺手修 Hermes”。正确顺序是先冻结共享合同,再分别推进 Reasonix 和 Hermes 的 runtime policy,最后统一 UI projection 与 smoke。
|
||
|
||
```text
|
||
Phase 0: Runtime policy / event store / status reconciliation 共享底座
|
||
-> Track R: Reasonix ACP live binding + cold resume
|
||
-> Track H: Hermes ACP session/load + replay 消费
|
||
-> Track W: Hermes Web/API chat transcript resume 兼容
|
||
-> Track U: Page AI UI / history / debug / smoke 矩阵
|
||
```
|
||
|
||
### 8.1 共享底座:Runtime policy
|
||
|
||
先在 MNote 侧抽象出明确的 runtime policy,而不是在 `hermes_client.rs` 里靠 `if acpRuntime == "reasonix"` 散落判断。
|
||
|
||
建议合同:
|
||
|
||
```text
|
||
AgentRuntimePolicy
|
||
runtime: hermes | reasonix | chat_only | api_chat
|
||
contextCarrier:
|
||
- adapter_loadable_session
|
||
- adapter_live_loop
|
||
- server_transcript
|
||
supportsSessionLoad: bool
|
||
supportsHistoryReplay: bool
|
||
supportsForkListResume: bool
|
||
promptContinuity:
|
||
- native_live_required
|
||
- load_then_prompt
|
||
- transcript_rebuild
|
||
terminalSemantics:
|
||
- run.completed
|
||
- run.failed
|
||
- run.aborted
|
||
- synthetic.turn_complete
|
||
```
|
||
|
||
MNote Page AI 每次 run 必须先解析 policy,再决定:
|
||
|
||
- 是否允许 `session/load`。
|
||
- 是否应复用 live manager。
|
||
- 是否需要从 SQLite/local JSONL 构造 cold resume packet。
|
||
- 是否要接收 adapter replay history。
|
||
- terminal event 如何更新 SQLite run status 和前端 active-run snapshot。
|
||
|
||
### 8.2 共享底座:Session 身份
|
||
|
||
统一命名并避免当前概念混用:
|
||
|
||
| 字段 | 含义 | 生命周期 |
|
||
| --- | --- | --- |
|
||
| `mnoteSessionId` | MNote Page AI 会话 id,UI/history/audit 主键 | 持久 |
|
||
| `mnoteRunId` | MNote 一次 run id,events/status/audit 主键 | 持久 |
|
||
| `acpSessionId` | adapter 返回的底层 session id | runtime 相关 |
|
||
| `runtimeBindingId` | MNote 内存 runtime binding id | 进程内 |
|
||
| `providerConversationId` | ChatOnly 网页 provider 远端会话 id | provider 相关 |
|
||
|
||
SQLite `ai_runtime_runs` 必须保存这些字段的快照,但只有 `mnoteSessionId` / `mnoteRunId` 是 MNote 自己的长期真相。`acpSessionId` 对 Hermes 是可 load 引用,对 Reasonix 只是 live binding 标识,不代表可恢复。
|
||
|
||
### 8.3 共享底座:Event store / terminal reconciliation
|
||
|
||
先修这个底座,否则 Reasonix 和 Hermes 都会继续被 `输出中`、active-run stale、历史恢复误判影响。
|
||
|
||
- [ ] browser SSE 转发与 persistence 解耦:浏览器低延迟 fanout,不在同一 consumer 里同步写 SQLite。
|
||
- [ ] terminal event 高优先级:`run.completed` / `run.failed` / `run.aborted` / synthetic `turn.complete` 不可被 delta 洪峰挤掉。
|
||
- [ ] `persist_acp_runtime_event` 或独立 reconciler 在 terminal event 到达时同步更新 `ai_runtime_runs.status`。
|
||
- [ ] `/api/page-ai/sessions/{id}/active-run` 不得返回已有 terminal event 的 run。
|
||
- [ ] `/api/page-ai/runs/{id}/events` 能在 run status terminal 但缺 terminal event 时合成 reconciliation event。
|
||
- [ ] 前端收到 terminal / reconciliation terminal 后强制清理 `streaming=true`、`pageAiBusy`、active run snapshot。
|
||
|
||
### 8.4 共享底座:Response projection
|
||
|
||
所有 runtime 的可见输出投影统一,但事件来源不统一:
|
||
|
||
- assistant final:只来自 message/model delta + final output。
|
||
- thought:只进折叠思考过程,默认不展开,不混入 final。
|
||
- tool:独立 tool card,保留 status、toolCallId、locations、citations、auditId。
|
||
- plan/todo:独立 plan/status panel,不当作 assistant 正文。
|
||
- permission:独立权限卡,用户决策必须回写 adapter。
|
||
- citation:优先 tool 返回的 `citationMarkdown` / `citationUrl`,不让模型手拼。
|
||
- busy/输出中:只由 active run + streaming bubble 共同决定,terminal 后立即清除。
|
||
|
||
## 9. Track R:Reasonix ACP
|
||
|
||
### R0:官方模式冻结
|
||
|
||
- [ ] fake Reasonix ACP fixture:`initialize.loadSession=false`、`session/new` 创建内存 loop、`session/prompt` 必须命中同一 session。
|
||
- [ ] 在测试中证明:如果 MNote 每轮新建 session,短回复“可以”无法关联上一轮;如果复用 live binding,可以关联。
|
||
- [ ] 在 runtime payload / SQLite detail 中暴露 `reasonixSessionMode: native_live | cold_resumed | new_session`。
|
||
|
||
### R1:Live binding pool
|
||
|
||
- [ ] `AcpRuntimeManager` / `hermes_client` 增加按 `mnoteSessionId + acpRuntime` 复用的 Reasonix live binding pool。
|
||
- [ ] Reasonix run 完成后不关闭 manager,不移除 binding,只从 running 改 idle。
|
||
- [ ] 同一 binding 内 run 串行执行;第二条输入进入 binding queue,不另开 session。
|
||
- [ ] Stop/abort 只取消当前 prompt,不默认销毁 binding。
|
||
- [ ] 切 workspace/root/model/profile/allowedRootsHash 时关闭旧 binding,并把下一轮标记为 cold/new。
|
||
|
||
### R2:Cold resume fallback
|
||
|
||
- [ ] 从 SQLite/local JSONL 构造 bounded transcript summary。
|
||
- [ ] cold resume 首轮 prompt 注入 `MNote cold resume context packet`。
|
||
- [ ] packet 只包含 summary、最近 3-5 轮 user/assistant final、tool summary、changedFiles、citations,不包含 thought 大段文本。
|
||
- [ ] UI 显示 cold resume 状态,不误导为 Reasonix 原生会话。
|
||
- [ ] `new_session` 且用户只发短回复时提示缺上下文,不让模型编造。
|
||
|
||
### R3:Reasonix 工具与 MCP 能力
|
||
|
||
- [ ] 确认 `mnoteCapabilities` / runtime manifest 进入 Reasonix 模型可见 prompt,而不只是 Rust payload。
|
||
- [ ] 继续保持 Reasonix 原生文件读写为 local-first 普通 Markdown 主路径;MNote 不扩写一套本地文件编辑工具。
|
||
- [ ] MCP 状态作为 Reasonix 工具可见事实进入 tool/status card,而不是让模型只凭自然语言猜测。
|
||
- [ ] LightRAG / MNote bridge MCP 失败时返回结构化 tool failed,不污染 assistant final。
|
||
|
||
## 10. Track H:Hermes ACP
|
||
|
||
### H0:官方 load 合同冻结
|
||
|
||
- [ ] fake Hermes ACP fixture:`initialize.loadSession=true`,支持 `session/load`;另准备一个会 replay history `session/update` 的变体。
|
||
- [ ] MNote 启动 Hermes run 时读取 stored `acpSessionId`,先 `session/load`,再 `session/prompt`。
|
||
- [ ] `session/load` 成功时,MNote 认为 adapter 当前已注册该 session;如果 adapter replay 出 user/assistant/thought/tool/plan,前端/后端必须消费并去重,不能覆盖乱序。
|
||
- [ ] `session/load` 返回 `null` 或失败时,标记 `hermesSessionMode=cold_resumed | new_session`,不假装原生连续。
|
||
|
||
### H1:Hermes session store 对齐
|
||
|
||
Hermes Web / dashboard 和部分本地服务有 `state.db` 级 session store。MNote 不应复制 Hermes DB,也不能把 Web DB 语义直接套到 ACP adapter;但要保存映射与审计:
|
||
|
||
- [ ] SQLite `ai_runtime_runs/session metadata` 保存 `acpSessionId`、Hermes profile、cwd/rootUri、lastLoadedAt、lastReplaySeq。
|
||
- [ ] MNote session detail 能展示底层 Hermes session id 和 load/replay 状态。
|
||
- [ ] 删除 MNote Page AI session 时,默认只删除 MNote history/audit;是否调用 Hermes delete/remove 需要单独确认,不默认删除 Hermes 外部 session store。
|
||
- [ ] 恢复历史会话时,区分“只恢复 MNote UI 历史”和“恢复 Hermes 底层 ACP session”。
|
||
|
||
### H2:Hermes replay 消费
|
||
|
||
部分 Hermes adapter 或 Web/API resume 路径可能 replay 历史。MNote 当前已有自己的 UI history,因此只要收到 replay,就必须做去重和排序:
|
||
|
||
- [ ] replay event 带 `source=adapter_replay`,不能当作新的当前 run 输出。
|
||
- [ ] replay user/assistant/tool 与 MNote 已有消息按 content/run/time 去重。
|
||
- [ ] replay tool start/completed 能恢复 tool card 状态,但不触发新的写入刷新。
|
||
- [ ] replay plan/todo 恢复 plan panel,但不混入 assistant final。
|
||
- [ ] replay 完成后再允许当前 prompt streaming bubble 开始。
|
||
|
||
### H3:Hermes 功能面
|
||
|
||
- [ ] 支持 Hermes ACP `session/list` / `session/resume` / `session/fork` 的能力探测,先展示为 debug/高级入口,不默认替代 MNote session list。
|
||
- [ ] 权限请求沿 Hermes ACP decision loop 回传,不做前端假 allow。
|
||
- [ ] tool `locations[]` 保留并能打开 MNote resource tab。
|
||
- [ ] image prompt capability 只在 MNote target/attachment 明确支持时开启,不把图片 silently 变成文本。
|
||
|
||
## 11. Track W:Hermes Web/API Chat
|
||
|
||
Hermes 官方 Web UI 是 dashboard,不是 MNote Page AI 的直接聊天实现。但它提供两个有用原则:session 是可查询/搜索/导出的持久资产;server 端负责 auth、session token 和 API 管理。
|
||
|
||
### W0:ChatOnly / API Chat 边界
|
||
|
||
- [ ] ChatOnly / API Chat 继续走 provider conversation 或 MNote server transcript,不复用 ACP session/load 设计。
|
||
- [ ] providerConversationId 只属于网页 provider,不与 `acpSessionId` 混用。
|
||
- [ ] API Chat 不写网页 provider conversation binding。
|
||
- [ ] MNote UI history 和 provider 远端历史删除语义分开。
|
||
|
||
### W1:Session dashboard 能力
|
||
|
||
参考 Hermes Web UI 的 sessions dashboard,MNote Page AI 可补以下管理能力:
|
||
|
||
- [ ] session list 展示 runtime、profile、message count、tool count、active/cold/new 状态。
|
||
- [ ] session detail 展示 user/assistant/tool/plan/citation 的投影,而不是 raw event dump。
|
||
- [ ] session search 支持按用户问题、assistant final、tool name、citation file 搜索。
|
||
- [ ] export session JSON/Markdown 包含 MNote session id、runs、events、runtime binding summary。
|
||
- [ ] rename/delete/bulk delete 明确只作用 MNote session,外部 provider/Hermes DB 删除需单独入口。
|
||
|
||
## 12. Track U:UI 与浏览器验证
|
||
|
||
### U0:可见状态
|
||
|
||
- [ ] Page AI header/debug surface 显示 runtime、profile、MNote session id、acpSessionId、sessionMode、active run status。
|
||
- [ ] `native_live`、`cold_resumed`、`new_session` 用小状态标签表示,不占用 assistant 正文。
|
||
- [ ] `输出中` 只在 run active 且 assistant bubble streaming 时显示;terminal 后必须消失。
|
||
|
||
### U1:Smoke 脚本
|
||
|
||
- [ ] `task558-page-ai-reasonix-live-session-context-smoke.js`:两轮 Reasonix,同一 acpSessionId,第二轮“可以”能引用上一轮。
|
||
- [ ] `task559-page-ai-terminal-status-reconciliation-smoke.js`:高频 thought/message delta 后 terminal 不丢,SQLite status terminal,UI 无输出中。
|
||
- [ ] `task560-page-ai-hermes-load-replay-smoke.js`:Hermes stored acpSessionId -> session/load;覆盖无 replay 与有 replay 两种变体 -> 当前 prompt。
|
||
- [ ] `task561-page-ai-session-dashboard-smoke.js`:session list/detail/search/export/rename/delete 的最小可见验证。
|
||
|
||
### U2:回归矩阵
|
||
|
||
- [ ] Reasonix live binding 不破坏 Hermes `session/load`。
|
||
- [ ] Hermes replay 不污染 Reasonix live transcript。
|
||
- [ ] ChatOnly provider conversation binding 不受 ACP binding pool 影响。
|
||
- [ ] local-folder allowed roots / readonly guard / agent audit 对所有 runtime 生效。
|
||
- [ ] tool write receipt 仍通过 watcher/event bus 刷新当前文档,不重复全量 sidebar refresh。
|
||
|
||
## 13. 建议执行顺序
|
||
|
||
### 13.1 第一批:先修共性底座
|
||
|
||
1. Event store / terminal reconciliation。
|
||
2. Runtime policy 表和 session 身份字段梳理。
|
||
3. 前端 `输出中` / active-run 清理。
|
||
|
||
这批完成后,Reasonix 和 Hermes 后续调试不会继续被 stale status 干扰。
|
||
|
||
### 13.2 第二批:Reasonix 原生连续会话
|
||
|
||
1. Reasonix fake ACP fixture。
|
||
2. Reasonix live binding pool。
|
||
3. Reasonix 两轮“可以” browser smoke。
|
||
4. Cold resume fallback。
|
||
|
||
这批完成后,解决当前用户可见的上下文断裂。
|
||
|
||
### 13.3 第三批:Hermes 官方 load/replay
|
||
|
||
1. Hermes fake ACP fixture。
|
||
2. `session/load` replay 消费与去重。
|
||
3. Hermes session metadata/debug。
|
||
4. Hermes load/replay browser smoke。
|
||
|
||
这批完成后,Hermes 与 Reasonix 的上下文机制真正分开。
|
||
|
||
### 13.4 第四批:会话管理与其它功能
|
||
|
||
1. Page AI session dashboard:list/detail/search/export/rename/delete。
|
||
2. Tool locations/citations/open action 统一。
|
||
3. permission decision loop 复核。
|
||
4. model/profile/skills/MCP 状态面板对齐。
|
||
|
||
## 14. 验收矩阵
|
||
|
||
### 14.1 单元 / 集成测试
|
||
|
||
- [ ] fake Reasonix ACP:`loadSession=false`,同一 MNote session 连续两轮只调用一次 `session/new`,第二轮仍用同一 `acpSessionId`。
|
||
- [ ] fake Reasonix ACP:第二轮输入“可以”,adapter 可读到上一轮 loop 历史,回答能引用上一轮提议。
|
||
- [ ] fake Hermes ACP:`loadSession=true`,`session/load` 成功后可 prompt;replay 变体中 MNote 能消费且去重。
|
||
- [ ] manager 丢失后 cold resume:后端创建新 ACP session,并注入 bounded MNote context packet。
|
||
- [ ] SQLite status reconciliation:有 `run.completed` event 后,`ai_runtime_runs.status` 必为 `completed`。
|
||
- [ ] 高频 delta:3000 条 `thought.delta` + terminal,不丢 terminal,前端最终无 `data-page-ai-streaming="true"`。
|
||
|
||
### 14.2 浏览器 smoke
|
||
|
||
- [ ] Reasonix:打开 Page AI,发送“我下一条只说可以,你要根据这条消息继续检查 LightRAG 服务”。
|
||
- [ ] Reasonix:第二轮发送“可以”。
|
||
- [ ] Reasonix:捕获 `/api/hermes/client/runs` 和 `session.info.updated`,两轮实际 `acpSessionId` 相同。
|
||
- [ ] Reasonix:第二轮可见回答能理解“可以”指上一轮提议,不要求用户重新说明任务。
|
||
- [ ] Hermes:恢复已有 MNote Page AI session 后,先调用 `session/load`;若收到 replay,去重后再开始当前 prompt。
|
||
- [ ] 通用:回答完成后页面不显示 `AI · 输出中`。
|
||
- [ ] 通用:SQLite `ai_runtime_runs` 对所有 terminal run 都进入 terminal status。
|
||
|
||
### 14.3 回归边界
|
||
|
||
- [ ] Hermes runtime 仍可走 `session/load`,不被 Reasonix special case 破坏。
|
||
- [ ] Reasonix runtime 不调用或不依赖 `session/load`。
|
||
- [ ] ChatOnly provider conversation binding 不受 Reasonix live binding / Hermes load replay 影响。
|
||
- [ ] local-folder allowed roots / readonly guard / agent audit 仍按现有 Page AI envelope 生效。
|
||
- [ ] Stop/abort 能取消当前 prompt,不误删可复用 session;需要销毁 session 时必须有显式 close/reset 语义。
|
||
|
||
## 15. 不做事项
|
||
|
||
- 不让 MNote 为 Reasonix 重写一套长期 agent memory。
|
||
- 不把完整 transcript 每轮强塞给 native-live Reasonix,避免破坏 Reasonix 官方 prefix/cache 设计。
|
||
- 不把 `mnote.doc.*` 工具扩成 local-first 普通 Markdown 主路径。
|
||
- 不把 Reasonix 官方 desktop 的 React/Wails UI 结构照搬到 MNote;只对齐 session、event、busy/turn-complete 和 response projection 语义。
|
||
- 不继续把 `session/load` 失败当成普通 fallback 成功;对 Reasonix 这是上下文断裂事件,必须可见、可审计。
|
||
|
||
## 16. 当前建议优先级
|
||
|
||
优先做 `13.1 第一批:共性底座`,再做 `13.2 Reasonix 原生连续会话`。
|
||
|
||
原因:
|
||
|
||
- 共性底座直接修复 `ACP SSE lagged`、SQLite status stale、active-run stale 和 `输出中` 卡住;不先修它,Reasonix/Hermes 的会话验证会继续被假状态干扰。
|
||
- Reasonix live binding 直接修复“回复可以不理解上一轮”的根因。
|
||
- Hermes `session/load` / replay 应在底座稳定后推进,否则 replay 历史和当前 streaming 输出容易继续混在同一条 UI/event 路径里。
|
||
- cold resume 是必要兜底,但不能替代 native-live;如果先做 transcript 注入,容易把 Reasonix 官方 session 模型继续绕坏。
|
||
|
||
## 17. 文件级实施映射
|
||
|
||
本节用于把上面的架构拆到真实 owner,避免后续实现继续在单个 route 中临时判断。
|
||
|
||
### 17.1 后端 owner
|
||
|
||
| 文件 | 当前职责 | 后续改动边界 |
|
||
| --- | --- | --- |
|
||
| `rust/crates/mnote-web/src/routes/hermes_client.rs` | Page AI run/create/stream、ACP active run、SQLite runtime event 持久化、active-run API | 第一批只改 terminal reconciliation、active-run 过滤、runtime policy 分派入口;不要把 Reasonix/Hermes 细节继续散落到所有分支 |
|
||
| `rust/crates/mnote-web/src/acp_session_manager.rs` | ACP `session/new`、`session/load`、`session/prompt`、event mapping | 增加 capability-aware `ensure_session_with_policy` 或等价接口;Reasonix policy 下不得尝试 `session/load` |
|
||
| `rust/crates/mnote-web/src/acp_runtime.rs` | runtime config、process switch、active client 生命周期 | 当前只有一个 active runtime process;若支持多 live binding,需要明确是否仍共享同一 process,还是按 runtime/profile/workspace 拆 manager pool |
|
||
| `rust/crates/mnote-web/src/acp_bridge.rs` | ACP event -> SSE event projection | 补 terminal priority、thought/message delta 限流策略、tool/permission/plan 分类,避免 UI 和持久化各自猜事件语义 |
|
||
| `rust/crates/mnote-web/src/routes/mod.rs` | Page AI route 注册 | 只在新增 debug/session dashboard API 时扩展,不放业务逻辑 |
|
||
|
||
### 17.2 前端 owner
|
||
|
||
| 文件 | 当前职责 | 后续改动边界 |
|
||
| --- | --- | --- |
|
||
| `rust/crates/mnote-web/browser/sidebar-page-ai-runtime.js` | Page AI 输入、run streaming、event 消费、active run snapshot | terminal event 后强制清理 streaming/busy;显示 sessionMode/debug;不要把 thought/tool/plan 拼进 final |
|
||
| `rust/crates/mnote-web/browser/sidebar-page-ai-session-runtime.js` | session list/history/active-run 恢复 | active-run API 返回 stale 时要忽略 terminal run;后续接 session dashboard/detail/search/export |
|
||
| `rust/crates/mnote-web/browser/sidebar-page-ai-render-runtime.js` | 消息和 tool card 渲染 | `native_live/cold_resumed/new_session` 用状态标签,不占 assistant 正文;输出中只从 streaming attr 得出 |
|
||
| `rust/crates/mnote-web/src/ssr/styles.rs` | `AI · 输出中` 等可见样式 | 只保留样式,不承担状态判断;状态必须来自 JS runtime 和后端 terminal truth |
|
||
|
||
### 17.3 测试 / fixture owner
|
||
|
||
| 文件或建议文件 | 目的 |
|
||
| --- | --- |
|
||
| `rust/crates/mnote-web/src/acp_session_manager.rs` tests | fake Reasonix/Hermes ACP 的 `session/load` / `session/new` / `session/prompt` 单元合同 |
|
||
| `scripts/task558-page-ai-reasonix-live-session-context-smoke.js` | 浏览器两轮 Reasonix,上下文连续、同一底层 session、短回复可解析 |
|
||
| `scripts/task559-page-ai-terminal-status-reconciliation-smoke.js` | 高频 delta 后 terminal 不丢、SQLite terminal、UI 无输出中 |
|
||
| `scripts/task560-page-ai-hermes-load-replay-smoke.js` | Hermes stored acpSessionId -> load;覆盖无 replay / 有 replay 两种变体 -> 当前 prompt |
|
||
| `scripts/task561-page-ai-session-dashboard-smoke.js` | session list/detail/search/export/rename/delete 可见闭环 |
|
||
|
||
## 18. Runtime 状态机与不变量
|
||
|
||
### 18.1 Run 状态机
|
||
|
||
```text
|
||
created
|
||
-> queued
|
||
-> running
|
||
-> tool_calling
|
||
-> running
|
||
-> completed | failed | aborted
|
||
|
||
terminal = completed | failed | aborted
|
||
```
|
||
|
||
不变量:
|
||
|
||
- `ai_runtime_events` 中出现 terminal event 后,`ai_runtime_runs.status` 必须在同一事务或 reconciliation pass 中进入 terminal。
|
||
- `/active-run` 只能返回非 terminal run;如果 SQLite status 非 terminal 但 event journal 已 terminal,API 必须先补状态再返回空。
|
||
- 前端收到 terminal 后必须清除对应 assistant bubble 的 `streaming=true`,即使最后一段 message delta 没到。
|
||
- `acp_pending` 只能是创建到 SSE 建立前的短暂状态,不得作为完成后残留状态。
|
||
|
||
### 18.2 Reasonix binding 状态机
|
||
|
||
## 19. 完成记录(2026-06-10)
|
||
|
||
本稿已按 `13.1` 到 `13.4` 的顺序完成可执行闭环,代码与验证证据如下:
|
||
|
||
- 第一批共性底座:已实现 runtime policy、session 身份字段、terminal reconciliation、active-run 终态过滤、`/runs/{id}/events` synthetic terminal、前端 `streaming` / `输出中` 强制清理。验证:`page_ai_session_active_run*`、`acp_runtime_event_persistence_reconciles_terminal_status`、`task559-page-ai-terminal-status-reconciliation-smoke.js`。
|
||
- 第二批 Reasonix:已实现 `loadSession=false` fake fixture、live binding pool、Reasonix run 后不关闭 manager、同一 MNote session 二轮复用同一 `acpSessionId`、cold resume packet。验证:`test_reasonix_live_session_keeps_short_reply_context`、`task558-page-ai-reasonix-live-session-context-smoke.js`、`page-ai-browser-verify` Reasonix clean reply。
|
||
- 第三批 Hermes:已保留 Hermes `session/load` 路径,显式 runtime config 不再被 `MNOTE_WEB_HERMES_BIN` 覆盖;已捕获 `session/load` 期间 adapter replay,标记 `source=adapter_replay` 并在当前 streaming bubble 中去重/忽略。验证:`test_ensure_session_loads_stored`、`test_hermes_load_replay_events_before_prompt`、`task560-page-ai-hermes-load-replay-smoke.js` 覆盖 no replay / replay。
|
||
- 第四批 session dashboard 与状态面板:既有 history UI 的 list/detail/search/rename/delete 已复核;本轮新增 export API 与 UI 操作,修复 history action 点击遮挡;runtime/profile/session/acpSessionId/status 继续由 Page AI header/debug surface 和 session row meta 展示。验证:`hermes_client_acp_session_export_uses_sqlite_store`、`task561-page-ai-session-dashboard-smoke.js`。
|
||
- ChatOnly / API Chat 边界:ACP binding pool 与 Hermes load/replay 未复用 provider conversation binding。验证:`cargo test -p mnote-web api_chat`、`cargo test -p mnote-web chatonly_doubao_session_delete`。
|
||
|
||
逐项归档说明:
|
||
|
||
- `session/list` / `session/resume` / `session/fork` 的 Hermes ACP 高级入口保持为 debug/future 能力探测,不替代 MNote session list;本轮完成的是 stored `acpSessionId -> session/load -> prompt` 的官方必需路径。
|
||
- 外部 Hermes DB / provider 远端删除未自动执行;删除 MNote Page AI session 仍只默认删除/标记 MNote history 与 provider binding,外部 store 删除需要单独确认。
|
||
- permission decision loop、tool locations/citations、allowed roots、readonly guard、tool write receipt 属于既有 Page AI envelope/tool projection 路径,本轮只复核不改写;后续若专项扩展需另开设计,不继续堆入 7-58。
|
||
- 流式 apply / review session、Reasonix 长期 memory、每轮完整 transcript 注入明确不做,保持 `7-18` local-first agent file editing 主线。
|
||
|
||
状态:`DONE`。本文件可移入 `design/07-ai/done/`。
|
||
|
||
```text
|
||
missing
|
||
-> new_session
|
||
-> native_live.idle
|
||
-> native_live.running
|
||
-> native_live.idle
|
||
-> cold_required | closed
|
||
```
|
||
|
||
不变量:
|
||
|
||
- 同一 `mnoteSessionId + runtime + profile + workspaceKey + allowedRootsHash` 同时最多一个 running prompt。
|
||
- prompt 完成只释放 run,不销毁 Reasonix ACP session manager。
|
||
- abort 只取消当前 prompt;reset/close 才销毁 binding。
|
||
- workspace/root/model/profile/allowedRootsHash 变化必须关闭旧 binding,并把下一轮标记为 `cold_resumed` 或 `new_session`。
|
||
|
||
### 18.3 Hermes ACP 状态机
|
||
|
||
```text
|
||
missing
|
||
-> has_stored_acp_session_id
|
||
-> load_attempted
|
||
-> adapter_loaded | load_failed
|
||
-> prompt_running
|
||
-> completed | failed | aborted
|
||
```
|
||
|
||
不变量:
|
||
|
||
- 有 stored `acpSessionId` 时必须先 `session/load`,不能跳过 load 直接 prompt。
|
||
- `session/load` 成功只表示 adapter 当前承认该 session;是否 replay history 由事件流证据确认。
|
||
- replay event 必须标记 `source=adapter_replay` 或等价来源,不能当作当前 run 的新输出。
|
||
- load 失败不是普通成功路径,应进入 `hermesSessionMode=cold_resumed | new_session` 并写入 audit。
|
||
|
||
### 18.4 Hermes Web/API Chat 状态机
|
||
|
||
```text
|
||
providerConversationId | serverSessionId
|
||
-> load server transcript / compression snapshot
|
||
-> append current user message
|
||
-> stream provider response
|
||
-> flush messages/status
|
||
```
|
||
|
||
不变量:
|
||
|
||
- Web/API Chat 不复用 ACP `session/load`。
|
||
- providerConversationId 不得写入 `acpSessionId` 字段。
|
||
- 删除 MNote UI session 不默认删除 provider 或 Hermes 官方 DB 中的会话资产。
|
||
|
||
## 19. 风险登记
|
||
|
||
| 风险 | 影响 | 控制方式 |
|
||
| --- | --- | --- |
|
||
| 把 Reasonix 做成 transcript replay | 破坏 Reasonix `CacheFirstLoop` / prefix / cache 语义,短期看似恢复,长期上下文和成本异常 | native-live 优先;cold resume 必须显式标记且 bounded |
|
||
| 把 Hermes `session/load` 写成必然成功 | 进程重启、adapter 清理或 profile 变化后产生 phantom continuity | capability + load result + replay evidence 三重判断 |
|
||
| terminal event 与 delta 共用可丢 buffer | UI 卡 `输出中`、active-run stale、历史恢复错判 | terminal high-priority path + status reconciler |
|
||
| replay history 与 MNote history 重复 | 用户看到重复消息、tool card 重复执行感 | replay source 标记、content/run/time 去重、replay 完成后再开当前 bubble |
|
||
| 多 runtime 共用一个 active process manager | 切 profile/runtime 时杀掉别的 live session | 明确 process pool 设计;Reasonix binding 与 Hermes load 不共用生命周期假设 |
|
||
| 删除语义混淆 | 删除 MNote session 时误删 Hermes DB 或 provider 远端历史 | UI 和 API 分开 MNote-only delete 与 external delete |
|
||
| 代码中已有未完成 scaffold | 后续实现误以为 live binding 已完成,或产生重复 static / 不可编译 | 第一批实现前先跑 `cargo test -p mnote-web`,清点当前未提交改动归属,不回滚用户改动 |
|
||
|
||
## 20. 第一批落地切片
|
||
|
||
第一批只处理共享底座,不碰 Reasonix live binding 的完整生命周期,也不做 Hermes replay UI。目标是让 run 状态可信。
|
||
|
||
### 20.1 后端切片 A:terminal reconciliation
|
||
|
||
- 输入:任意 runtime 的 `run.completed` / `run.failed` / `run.aborted` event。
|
||
- 输出:`ai_runtime_runs.status` 同步进入 terminal,`status_reason` 写入 terminal event 名称或 error code。
|
||
- 改动点:`persist_acp_runtime_event`、`update_runtime_by_run_id`、`/active-run` 查询路径。
|
||
- 验收:构造 `ai_runtime_events` 已 terminal 但 `ai_runtime_runs.status=acp_pending` 的 fixture,active-run 返回空并补写 status。
|
||
|
||
### 20.2 后端切片 B:SSE terminal priority
|
||
|
||
- 输入:高频 `thought.delta` / `message.delta` + terminal。
|
||
- 输出:普通 delta 可合并或丢摘要,terminal 必达浏览器和 event store。
|
||
- 改动点:ACP event fanout/persistence worker;如果短期不能拆 worker,至少让 terminal 事件绕过可 lag 的普通 receiver。
|
||
- 验收:3000 条 delta 后仍收到 terminal,日志不再出现 terminal 丢失导致的 stale run。
|
||
|
||
### 20.3 前端切片 C:输出中清理
|
||
|
||
- 输入:terminal SSE、reconciliation terminal、active-run 空结果。
|
||
- 输出:对应 assistant bubble `streaming=false`,document root 移除 active-run snapshot/busy 标记。
|
||
- 改动点:`sidebar-page-ai-runtime.js`、`sidebar-page-ai-session-runtime.js`。
|
||
- 验收:回答完成后 DOM 不存在 `data-page-ai-streaming="true"`,`.wolai-page-ai-message-role::after` 不再显示 `AI · 输出中`。
|
||
|
||
### 20.4 文档归档条件
|
||
|
||
本设计留在 `process/`,直到至少完成:
|
||
|
||
- 第一批共性底座 smoke 通过。
|
||
- Reasonix 两轮短回复 smoke 通过。
|
||
- Hermes load 与 replay 变体最小 smoke 通过。
|
||
- Page AI session dashboard 的范围已拆成新设计或在本设计中完成验收。
|
||
|
||
满足后移动到 `design/07-ai/done/`;若后续另起 v2 覆盖本稿,则本稿移动到 `design/old/process/` 并标记 `[recycle]`。
|