diff --git a/CLAUDE.md b/CLAUDE.md
index 8e40406..64c05e2 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -16,5 +16,10 @@
- 边界对齐意志卡 forbidden:发版/部署/重启线上、git push、生产数据操作、对外发消息,攒齐等拍板。
- 本机工具/脚本的行为变更属 report_after 级(干完通报、可回滚),不属确认级。
+## 命名与代码风格(2026-10-10 用户重申)
+
+- **变量/方法名:精简 + 准确**——一眼看出「是什么/做什么」且不超必要长度。忌把实现细节写进名字(`..._after_tool_run`)、忌无信息缩写(`tmp2`/`h`)、忌同概念多叫法。名字过长通常说明该拆函数或把说明下沉到注释(注释承载「为什么」,名字只承载「是什么」)。
+- **DRY · 简洁 · 扩展性 · 高内聚低耦合**(同期重申):新增能力先找共用底座(如定时消息与定时任务共用一张 `ai_outbox` + 一个调度器),不造双轨;同一事实只留**单一真值来源**(如 token 估值口径)。
+
## 其他规范
- 架构设计文档在 `docs/02-架构设计/`;当前活跃专项:无人值守可靠性(B1-B4)+ 异步任务会话(13-17),设计见 `temp/` 与 docs。
diff --git a/docs/02-架构设计/INDEX.md b/docs/02-架构设计/INDEX.md
index 1d6e31b..2221dbe 100644
--- a/docs/02-架构设计/INDEX.md
+++ b/docs/02-架构设计/INDEX.md
@@ -4,7 +4,7 @@
> 上级索引:[../INDEX.md](../INDEX.md)
> 命名规范:见本目录 [文档记录规范-2026-06-14.md](./滚动规范/文档记录规范-2026-06-14.md) §九「文档命名规范」
-本目录是 DevFlow 的**架构设计 / 设计决策 / 设计方案 / 构想**文档集合(不含实现流水,流水走 `PROGRESS.md`;不含审查报告,审查走 `05-代码审查/`)。
+本目录是 DevFlow 的**架构设计 / 设计决策 / 设计方案 / 构想**文档集合(不含实现流水,流水走 [`../07-项目管理/PROGRESS.md`](../07-项目管理/PROGRESS.md);不含审查报告,审查走 `05-代码审查/`)。
按**性质**分四类,每类一个子目录:
@@ -23,7 +23,7 @@
| 文档 | 状态 | 核心内容 |
|---|---|---|
-| [业务系统设计-2026-06-12.md](./滚动规范/业务系统设计-2026-06-12.md) | 🏗 ARCHITECTURE 载体 | 项目无独立 ARCHITECTURE.md,本文档为实质载体:产品定位 / crate 结构 / 数据模型 / Phase 规划 |
+| [业务系统设计-2026-06-12.md](./滚动规范/业务系统设计-2026-06-12.md) | 📐 设计 | 产品定位 / crate 结构 / 数据模型 / Phase 规划。项目架构总纲见根 [ARCHITECTURE.md](../../ARCHITECTURE.md) |
| [Phase1架构决策-2026-06-12.md](./滚动规范/Phase1架构决策-2026-06-12.md) | 📐 ADR | Phase 1 引擎骨架阶段架构级选型(ADR-001 引擎不绑定业务 等) |
| [功能决策记录-2026-06-14.md](./滚动规范/功能决策记录-2026-06-14.md) | 📋 滚动 | 功能**需求规格 + 设计决策规格**(为什么这么定 / 要做什么)。最高频引用(67 入链) |
| [功能决策记录-归档-2026-06-14.md](./滚动规范/功能决策记录-归档-2026-06-14.md) | 🗄 归档只读 | 从主文档归档:纯流水 / 老 Sprint 决策 / UX 微调 / 已被取代细节 |
@@ -92,6 +92,33 @@
| [测试落地实施方案-2026-07-01.md](./专项设计/测试落地实施方案-2026-07-01.md) | 📐 测试 | 各测试用例的落地方式(Rust 单元/集成/vitest/E2E 手动)、文件位置、Mock 策略、CI 自动化路径 |
| [devflow集成三方IM-飞书企微钉钉Web-规划设计-2026-10-04.md](./专项设计/devflow集成三方IM-飞书企微钉钉Web-规划设计-2026-10-04.md) | 📐 规划(待评审) | 飞书/企微/钉钉/自定义Web 远程接入 AI Chat:新增 df-im-gateway(WS Commander)复用 u-relay+df-tunnel,桌面端零改动。含必要性分析(§三A)、破坏性分析(§三B)、演进预留(§十 S1-S4:多人/引擎上云/新端)。P0网关→P1 Web→P2飞书→P3钉钉→P4企微 |
| [三方IM-Rust接入与SDK调研-2026-10-04.md](./专项设计/三方IM-Rust接入与SDK调研-2026-10-04.md) | ✅ 调研完成 | 三平台 Rust SDK 选型:飞书 larksuite-oapi-sdk-rs / 钉钉 dingtalk-stream / 企微 wecom-aibot-rust-sdk。**重大发现:企微智能机器人走 WS 长连接免公网回调,三平台统一形态**。含统一 adapter 映射表与 5 项落地风险清单 |
+| [上下文管理演进-任务技术设计-2026-07-20.md](./专项设计/上下文管理演进-任务技术设计-2026-07-20.md) | 📐 设计 | 上下文管理演进任务技术设计 |
+| [上下文管理演进-执行计划-2026-07-20.md](./专项设计/上下文管理演进-执行计划-2026-07-20.md) | 📐 计划 | 上下文管理演进执行计划 |
+| [上下文管理演进与发散思考-2026-07-20.md](./专项设计/上下文管理演进与发散思考-2026-07-20.md) | 📐 思考 | 上下文管理演进与发散思考 |
+| [aichat-plan-driven-设计-2026-08-01.md](./专项设计/aichat-plan-driven-设计-2026-08-01.md) | 📐 设计 | AI Chat plan-driven 模式设计 |
+| [devflow集成u-claw-可行性研究-2026-09-28.md](./专项设计/devflow集成u-claw-可行性研究-2026-09-28.md) | 📐 研究 | devflow 集成 u-claw 可行性研究 |
+| [跨会话记忆四层架构-设计-2026-09-29.md](./专项设计/跨会话记忆四层架构-设计-2026-09-29.md) | 📐 设计 | 跨会话记忆四层架构 |
+| [aichat上下文管理-派工与决策单-2026-09-29.md](./专项设计/aichat上下文管理-派工与决策单-2026-09-29.md) | 📋 决策单 | aichat 上下文管理派工与决策 |
+| [DRY技术债走查报告-2026-10-01.md](./专项设计/DRY技术债走查报告-2026-10-01.md) | 📋 报告 | 全库 DRY 技术债走查 |
+| [并行化与异步唤醒-设计-2026-10-04.md](./专项设计/并行化与异步唤醒-设计-2026-10-04.md) | 📐 设计 | 并行化与异步唤醒 |
+| [无人值守长任务-设计-2026-10-04.md](./专项设计/无人值守长任务-设计-2026-10-04.md) | 📐 设计 | 无人值守长任务 |
+| [上下文三层记忆-设计-2026-10-04.md](./专项设计/上下文三层记忆-设计-2026-10-04.md) | 📐 设计 | 上下文三层记忆 |
+| [跨会话召回最小闭环-设计-2026-10-04.md](./专项设计/跨会话召回最小闭环-设计-2026-10-04.md) | 📐 设计 | 跨会话召回最小闭环 |
+| [context-helpers拆分备勘-设计-2026-10-04.md](./专项设计/context-helpers拆分备勘-设计-2026-10-04.md) | 📐 设计 | context-helpers 拆分备勘 |
+| [namespace淘汰治理-设计-2026-10-04.md](./专项设计/namespace淘汰治理-设计-2026-10-04.md) | 📐 设计 | namespace 淘汰治理 |
+| [子代理完成卡已消化降级-设计-2026-10-04.md](./专项设计/子代理完成卡已消化降级-设计-2026-10-04.md) | 📐 设计 | 子代理完成卡消化降级 |
+| [uclaw两侧勘察补全-2026-10-04.md](./专项设计/uclaw两侧勘察补全-2026-10-04.md) | 📋 勘察 | u-claw 两侧勘察补全 |
+| [u-relay-wire-protocol-2026-10-04.md](./专项设计/u-relay-wire-protocol-2026-10-04.md) | 📐 协议 | u-relay 线协议规范 |
+| [数据存储架构-定稿-2026-10-05.md](./专项设计/数据存储架构-定稿-2026-10-05.md) | 📐 定稿 | 数据存储架构定稿 |
+| [知识库模型重审-精细化设计-2026-10-05.md](./专项设计/知识库模型重审-精细化设计-2026-10-05.md) | 📐 设计 | 知识库模型精细化重审 |
+| [飞书直连接入-形态修正与问题记录-2026-10-05.md](./专项设计/飞书直连接入-形态修正与问题记录-2026-10-05.md) | 📋 记录 | 飞书直连接入形态修正与问题记录 |
+| [版本更新与自动升级-设计-2026-10-09.md](./专项设计/版本更新与自动升级-设计-2026-10-09.md) | 📐 设计 | 版本更新与自动升级 |
+| [u-relay-运维手册-2026-10-10.md](./专项设计/u-relay-运维手册-2026-10-10.md) | 📖 手册 | u-relay 运维手册 |
+| [上下文分层组装器-落地方案-2026-10-10.md](./专项设计/上下文分层组装器-落地方案-2026-10-10.md) | 📐 方案 | 上下文分层组装器落地方案 |
+| [定时消息与待发箱持久化-设计-2026-10-10.md](./专项设计/定时消息与待发箱持久化-设计-2026-10-10.md) | 📐 设计 | 定时消息与待发箱持久化 |
+| [对话内定时延时任务-设计-2026-10-10.md](./专项设计/对话内定时延时任务-设计-2026-10-10.md) | 📐 设计 | 对话内定时延时任务 |
+| [消息渲染管线重构-设计-2026-10-10.md](./专项设计/消息渲染管线重构-设计-2026-10-10.md) | 📐 设计 | 消息渲染管线重构 |
+| [输入框列表预览与自动编号-设计-2026-10-10.md](./专项设计/输入框列表预览与自动编号-设计-2026-10-10.md) | 📐 设计 | 输入框列表预览与自动编号 |
---
diff --git a/docs/02-架构设计/专项设计/FileTree大目录-方案-2026-10-10.md b/docs/02-架构设计/专项设计/FileTree大目录-方案-2026-10-10.md
new file mode 100644
index 0000000..117d673
--- /dev/null
+++ b/docs/02-架构设计/专项设计/FileTree大目录-方案-2026-10-10.md
@@ -0,0 +1,131 @@
+# FileTree 大目录卡顿 — 可实施方案
+
+> 日期: 2026-10-10 | 状态: 设计稿(待拍板) | 勘察: 只读实测锚点(Wave Q)
+> 定位: 「单个目录上千条合法条目」的卡顿风险可实施方案。本文只写方案,不改码。
+> 触发: Wave Q 实测 — 后端 `get_module_file_tree` 不封顶、前端 FileTree 全量 `v-for`,当前靠三重缓解兜底。
+> 结论预告: 推荐 **方案 B(后端 offset/limit 分页 + 前端「加载更多」)**,先做期 1 最小可用;虚拟滚动本期否决(见 §二·A 的实证疤痕)。
+
+---
+
+## 一、现状与证据(带行号)
+
+### 1.1 后端:列目录不封顶
+
+`src-tauri/src/commands/module.rs` 命令 `get_module_file_tree`(707-834):
+
+- 774-777 `std::fs::read_dir(&target_dir)` 遍历目标目录**全部**条目;
+- 781-791 过滤噪音目录(`is_noise_entry`,FileTree 档)+ 隐藏文件(`.` 开头);
+- 810-818 每条 push 进 `items`;
+- 820-824 排序(目录优先 + 字母序,稳定可预期);
+- 830-833 一次性返回 `{ path, entries }`。
+
+**无任何条数上限**。对照同语义的另一档位:AI 侧 `list_dir_recursive`(`src-tauri/src/commands/ai/tool_registry.rs:923-965`)有 `max_entries` 封顶(调用处 `tools/file.rs:347` 传 `1000`,达上限返 `truncated`)。即"列目录"这一语义,**AI 上下文档有封顶、文件浏览器档没有** —— 这是 Wave Q 要补的缺口。
+
+### 1.2 前端:单层全量 v-for
+
+`src/components/project/FileTree.vue`:
+
+- 25-33 `
` —— 当前层全部条目**一次性渲染**;
+- 95-106 `FileTree` 自引用递归:展开的子目录再加一层同构 ``;
+- 157-205 `entries` 为该层全量(命中 `loadedChildren` 缓存则整层直赋)。
+
+滚动容器是**共享**的 `.explorer-tree`(`FileExplorer.vue:806-813`,`overflow-y: auto`),**不是每层各自滚动** —— 这决定了虚拟滚动若要落地,需先把递归树拍平成"单一带缩进的可见行列表"(见 §二·A)。
+
+### 1.3 三重缓解(既定兜底,也是本次要复用的底座)
+
+1. **按层懒加载**:FileTree 仅在某目录被展开时才拉该层(`loadEntries` 174-205;`toggleDir` 208-237),从不整树加载。
+2. **噪音 / 隐藏过滤**:后端 `is_noise_entry`(FileTree 档,`tool_registry.rs:1045-1056`)+ `.` 隐藏文件(`module.rs:789`)剔除最大宗条目(`.git` 数千项、`node_modules`/`target`/`dist`/`__pycache__` 等)。
+3. **KeepAlive 保活**:`src/views/ProjectDetail.vue:83-85` `` 包 `FileExplorer`,切 tab 不销毁、不重挂载、不重拉。
+
+附随机制:`loadedChildren` 缓存(`FileExplorer.vue:280`)+ 在途去重 / 请求序号(`loadSeq`/`inflightEntries`,`FileTree.vue:170-171`)使重复展开秒开、并发请求去重。
+
+**残留风险**:三重缓解把"上千条"压缩成"**单层**上千条",但对**一个真实含上千条且非噪音**的目录(如日志目录、生成代码目录、扁平资源目录),后端仍全量返回 + 前端仍全量 DOM。
+
+---
+
+## 二、方案对比
+
+| 维度 | A 前端虚拟滚动 | B 后端 offset/limit + 前端「加载更多」 | C 两者结合 |
+|------|---------------|--------------------------------------|-----------|
+| 机制 | 只渲染可视窗口(+ overscan) | 后端分页切片,前端累积 | B 分页 + A 窗口化 |
+| 后端改动 | 无 | `get_module_file_tree` 加 offset/limit/total | 同 B |
+| 前端改动 | FileTree 重构为扁平窗口化列表 | FileTree 累积 entries + 尾部按钮 | B + 窗口化 |
+| 依赖 | 需新库(项目现有依赖无)或自研 | 无 | 需新库 |
+| 与现有架构 | **冲突**(递归树需拍平;共享滚动容器需重排) | **天然契合**(每层本就独立请求) | 中 |
+| 风险 | 高(高度测量/展开态/选中态同步;项目有自研虚拟滚动翻车疤痕) | 低(改动集中、可回退) | 高 |
+| 适配量级 | 上万条 | **上千条(本场景)** | 上万条 |
+
+### A 的风险:项目实证疤痕
+
+自研虚拟滚动曾落地于消息列表(自研 IntersectionObserver + sentinel + ResizeObserver),因 **IO/RO 时序竞态致消息重叠**,2026-06-18 **彻底移除**(见 `docs/待决策.md:132-136`、`docs/02-架构设计/滚动规范/功能决策记录-2026-06-14.md:362-363`;`MessageList.vue` 至今标注"虚拟滚动已移除,恒渲染")。
+
+并且 `package.json` 现有依赖**无任何虚拟滚动库**(`@vueuse/core` 也未安装)。再自研 = 重蹈覆辙;引库 = 新增依赖 + 仍需适配递归树拍平 —— 成本与"上千条"的收益不成比例。
+
+### 推荐:B(最小可用)
+
+理由:
+
+1. **零架构冲突**:现有模型就是"每层一个独立请求 + `loadedChildren` 缓存",把"整层"切片成"整层分批"是该模型的自然延伸,不引入新范式。
+2. **最小 diff、低风险、可回退**(见 §三)。
+3. **量级匹配**:上千条 DOM 在现代浏览器渲染在数十 ms 量级;首屏限 200~300 条后交互即流畅,足以消除卡顿。无需为"上千条"上虚拟滚动的复杂度(A/C 属 YAGNI,仅当实测 >5000 条再评估)。
+4. **给用户明确信号**:「加载更多 (已加载/总数)」比"悄悄截断"更符合用户预期。
+
+---
+
+## 三、方案 B 改动面 / 风险 / 回退 / 验收
+
+### 3.1 改动面(期 1 最小可用)
+
+| 层 | 文件 | 改动 |
+|----|------|------|
+| 后端 | `src-tauri/src/commands/module.rs` | `get_module_file_tree` 加 `offset: Option`、`limit: Option` 参数;返回加 `total`。**先完整收集 + 过滤 + 排序,再切片**(切片点放在 820-824 排序之后),保证分页稳定。`limit` 缺省 = `None` → 返回全量(保持旧行为)。 |
+| 前端 API | `src/api/module.ts` | `getModuleFileTree` 加 `offset`/`limit` 参数;`FileTreeResult` 加 `total?: number`。 |
+| 前端组件 | `src/components/project/FileTree.vue` | `entries` 改为"累积注入":首屏拉 `limit` 条,`loadMore()` 按 `offset=已加载数` 追加,直到 `total`;层尾渲染「加载更多 (N/M)」;分页状态并入 `loadedChildren` 缓存(**建议其值由 `FileTreeEntry[]` 扩展为 `{ entries, total }` 或新增 `loadedTotals` map**,否则重展开丢失"还有更多"状态)。 |
+| i18n | `src/i18n/zh-CN/fileExplorer.ts`、`src/i18n/en/fileExplorer.ts` | 新增 `loadMore`(「加载更多」/「Load more」)、可选 `allLoaded`。 |
+
+不改 `FileExplorer.vue`(缓存语义、展开 / 选中逻辑不变)。改动集中、职责单一。
+
+### 3.2 风险
+
+- **offset 错位**:目录内容在分页期间变化 → 可能重复 / 漏项。自用场景 + `refresh` 清缓存重拉(`resetTreeState`,`FileExplorer.vue:330-336`)已覆盖,可接受。
+- **多次请求 git status**:每个分片请求都重跑 `git_status_map_cached`,但有缓存(10s TTL),开销可控。
+- **`has_children` 开销**:现每目录一次 `read_dir` 短路统计;分页后只对返回的前 N 条目录计算,反而**降**开销。
+- **`loadMore` 与首屏请求竞争**:需让 `loadMore` 复用同一 inflight 去重键或使用独立键,避免与 `onMounted` 首屏请求互相覆盖(机制见 `FileTree.vue:185-188`)。
+
+### 3.3 回退
+
+- 后端 `limit`/`offset` 缺省 = `None` = 全量;前端不传 `limit` 即旧行为。
+- 或前端恒定不渲染"加载更多"(首屏即全量)。
+- 改动集中在 3 个源文件 + 2 个 i18n 文件,单次 revert 即可回滚。
+
+### 3.4 验收口径
+
+- **造数**:批量子项造 1500 条目录(如 1500 个空文件)。
+- 首屏 DOM 行数 ≤ `limit`(如 200);层尾显「加载更多 (200/1500)」;点击逐次追加 200 条直到 1500;加载完按钮隐藏。
+- **回归**:选中文件高亮、git 状态标、空目录标、展开子目录、切工程、刷新,均正常。
+- **命令**:`cargo check -p devflow` EXIT 0;分页切片纯函数加单测(`module.rs:1574` 已有 `mod tests` 模块可承接);`bunx vue-tsc --noEmit` EXIT 0;`node scripts/check-i18n.cjs` 通过。
+
+---
+
+## 四、分期(最小可用优先)
+
+- **期 1(MVP,必做)**:后端点分页 + 前端「加载更多」按钮。
+- **期 2(体验,可选)**:触底自动加载(IO sentinel)。**注意**:滚动容器是共享 `.explorer-tree`(非每层独立滚动),触底自动加载需定位"当前层尾部的 sentinel",实现比按钮复杂;且只做"触底追加"、**不做"卸载裁剪"**,以规避历史 IO 竞态。可延后。
+- **期 3(仅当实测触发)**:若实测 >5000 条仍卡,评估虚拟滚动,且**用成熟库**(如 `@vueuse/core` `useVirtualList`)而非自研,并先把递归树拍平为单一带缩进列表。
+
+---
+
+## 五、与既有能力的复用
+
+- **懒加载缓存**:`loadedChildren`(`FileExplorer.vue:280`,`FileTree.vue:180/197/228`)→ 分页后仍存"累积 entries",展开秒开语义不变。
+- **噪音过滤**:`is_noise_entry` FileTree 档(`tool_registry.rs:1045-1056`)→ 分页在**过滤之后**切片,过滤语义不变(不会因分页漏滤)。
+- **KeepAlive**:`ProjectDetail.vue:83-85` → 分页游标随组件状态在 tab 切换间保留,回来不重拉。
+- **在途去重 / 序号守卫**:`FileTree.vue:170-171/187` → `loadMore` 复用,避免并发覆盖。
+
+---
+
+## 六、拍板点
+
+1. **分页粒度**:首屏 `limit` 与每次追加步长各取多少?(建议各 200 —— 兼顾首屏速度与点击次数)
+2. **交互形态**:MVP 用「加载更多」按钮(推荐:简单、可测、与共享滚动容器不冲突)vs 触底自动加载(期 2 再评估)?
+3. **是否本期引入虚拟滚动**:建议否决(YAGNI + 自研疤痕),仅当实测 >5000 条再以成熟库评估 —— 是否认可?
diff --git a/docs/02-架构设计/专项设计/u-relay-wire-protocol-2026-10-04.md b/docs/02-架构设计/专项设计/u-relay-wire-protocol-2026-10-04.md
index bbaec50..240c213 100644
--- a/docs/02-架构设计/专项设计/u-relay-wire-protocol-2026-10-04.md
+++ b/docs/02-架构设计/专项设计/u-relay-wire-protocol-2026-10-04.md
@@ -170,6 +170,24 @@ Commander(控制端) ──wss──> nginx(:443 TLS 终结) ──ws──> u-r
| **Commander** | 用 pong 更新 `lastPongTime`,看门狗超时则重连 |
| **Agent**(跑桌面/移动网络) | 用 pong 重置读死线,死线超时走既有重连 |
+### 4.4 心跳间隔约束
+
+心跳间隔不是自由值,**链路各层空闲超时中最小的一层决定其上界**:
+
+| 层 | 空闲超时 | 说明 |
+|---|---|---|
+| nginx 反代 | `proxy_read_timeout`(样例 300s;生产实时配置可能更大) | 超时即静默断开 UPSTREAM WS,无任何错误帧 |
+| NAT / 防火墙 | 数十秒 ~ 数分钟(设备相关) | 空闲连接被回收,后续帧永久不可达 |
+
+**约定**:间隔须**显著小于最短超时**并留足余量(容忍连续数次心跳丢失 / 网络抖动仍不断链),**推荐 30–60s**。低于 30s 徒增无效流量,超过 60s 在 300s 反代下余量偏薄。
+
+| 端 | 保活帧 | 间隔 |
+|---|---|---|
+| Commander 参考(u-ppt) | 应用层 `{control_kind:"ping"}` | 30s |
+| Agent(df-tunnel) | WS 协议层 Ping 帧(对端协议栈自动回 Pong) | 30s |
+
+> Agent 侧用协议层 Ping 即可保活——WS Ping/Pong 由对端协议栈(tungstenite/axum)自动应答,同样刷新反代空闲计时;应用层 `control_kind:"ping"` 主要用于需要显式 pong 观测的控制端看门狗。两端节拍取同值,避免一方看门狗口径与另一方保活周期错配导致误重连。
+
---
## 5. 部署配置
diff --git a/docs/02-架构设计/专项设计/u-relay-运维手册-2026-10-10.md b/docs/02-架构设计/专项设计/u-relay-运维手册-2026-10-10.md
new file mode 100644
index 0000000..2498a1a
--- /dev/null
+++ b/docs/02-架构设计/专项设计/u-relay-运维手册-2026-10-10.md
@@ -0,0 +1,122 @@
+# u-relay 运维手册(token 轮换 · 一通道一 agent · 心跳约束)
+
+> 适用:u-relay 跨端中继的日常运维纪律。协议细节见
+> [u-relay Wire 协议规格](./u-relay-wire-protocol-2026-10-04.md),本手册只讲**运维动作与约束**。
+> 来源:协议评估遗留三项(单共享 token 可冒充 / 心跳间隔未定值 / 同 channel 多 agent 全投递)。
+> 本手册**不含**任何 token 值、主机地址等敏感信息——真值只在部署机 `/etc/u-relay.env` 与各客户端本地配置里。
+
+---
+
+## 1. 心跳间隔约束
+
+心跳间隔须显著小于**链路最短空闲超时**(反代 `proxy_read_timeout`,配置样例 300s;生产实时值可能更大),
+以容忍连续数次心跳丢失仍不断链。**约定 30–60s**,各方实现见协议规格 §4.4:
+
+- Agent(df-tunnel):`HEARTBEAT_INTERVAL` 常量,30s(WS 协议层 Ping)
+- Commander 参考(u-ppt):30s(应用层 `control_kind:"ping"`)
+
+> 改反代 `proxy_read_timeout` 时须同步评估心跳间隔:若把超时压到 300s 以下,间隔也要相应收窄并保持「超时 / 间隔 ≥ 5」量级的余量。
+
+---
+
+## 2. 单共享 token 轮换 Runbook
+
+### 2.1 现状与风险
+
+u-relay 鉴权是**单一共享 token**(env `U_RELAY_TOKEN`,服务端常量时间比较),**不做 per-channel 隔离**:
+任何持有该 token 的客户端可声明**任意 `channel_id`** 冒充任一通道。故 token 属敏感凭据,
+泄露即等于整条中继失守;轮换是唯一的止损手段。
+
+### 2.2 涉及方与各自持有位置
+
+token 值散落在「服务端 + 多个客户端」,**没有集中下发通道**:
+
+| 方 | 角色 | token 持有位置 |
+|---|---|---|
+| u-relay 服务端 | 鉴权源 | 部署机 `/etc/u-relay.env`(`U_RELAY_TOKEN`,权限 600)或 systemd unit 的 `Environment=` |
+| devflow 桌面端 | Agent | `relay_token` 设置项(运行时读,缺省回退硬编码默认值) |
+| devflow 小程序 | Commander | `apps/df-miniapp/src/config.ts` 缺省 + 设置页可改 |
+| devflow ask_uclaw | Commander | 设置页 `uclaw_token`(与 relay token 同源) |
+| u-claw | Agent/Runner | u-claw 侧配置 |
+| u-ppt | Commander | `relay.ts` 配置 |
+
+> 新增任何接入端都要登记到本表——漏一端的后果见下节。
+
+### 2.3 锁步成本(务必先读)
+
+因为**服务端只认一个 token、客户端各自缓存**,轮换天然是一个**锁步切换**:
+
+- **无重叠窗口**:服务端不能同时接受新旧两个 token,故不存在「灰度换端」的过渡期。
+- **顺序无解**:先改服务端 → 全部旧客户端立即 `auth_failed` 掉线;先改客户端 → 全部客户端在服务端切换前拿新 token 一样 `auth_failed`。两条路都有中断窗口。
+- **漏端即静默失联**:任一客户端未同步换 token,会持续 `auth_failed` 被拒连,表现为**该端静默离线**(无业务错误、仅 relay 日志有 `auth_failed`),排查成本高。
+- **不可逐端滚动**:无法像多 token 系统那样「先切一半、观察、再切一半」。
+
+结论:**轮换是一次计划内的协调停机**,中断窗口 = 从「首个客户端改值」到「服务端重启加载新值」的间隔。执行要点是**把客户端改值的动作压到最短、紧接着重启服务端**。
+
+### 2.4 执行步骤
+
+前置:准备好新 token,确认上表**每一端**都有人能改、改完可验证。
+
+1. **写服务端新值(先不重启)**:在部署机把 `/etc/u-relay.env`(或 unit `Environment=`)的 `U_RELAY_TOKEN` 改为新值,保持服务仍在跑旧值。此时新值尚未生效。
+2. **集中改用各客户端**:将上表每一端改到新值。此步开始后各端会 `auth_failed`,**中断窗口从这里起算**。
+3. **重启服务端加载新值**:`systemctl restart u-relay`。重启后已改端的客户端重连即通。
+4. **验证**:见 2.5。
+5. **未改端收尾**:对仍 `auth_failed` 的端补改(此时只有漏改者受影响,不再影响大局)。
+
+> 若某些端的 token 是**编译期硬编码**(无运行时配置项),须提前把「新 token + 重新打包」的产物备好,否则第 2 步无法即时完成,窗口会被拉长。
+
+### 2.5 验证
+
+- 服务端:`systemctl status u-relay` 正常,`ss -tlnp | grep 9180` 在听。
+- 连接:拿新 token 走一次握手冒烟(`/ws/agent` 或 `/ws/commander`,收 `hello_ack` 即通)。
+- 日志:确认无 `auth_failed` 残留——有则说明还有端没换到新 token。
+
+### 2.6 回滚
+
+轮换失败(如某端无法改)时,**把服务端 env 回退到旧 token 并再重启一次**,回到轮换前状态;客户端已改的端随之 `auth_failed`,需一并回退。回滚与轮换同样是锁步中断。
+
+### 2.7 根治方向
+
+根治是**去掉单共享 token**:服务端改为 per-channel / per-client token(客户端拿自己的凭据只能绑自己的 channel),
+届时任一端轮换互不影响,锁步成本消失。在此之前,本 runbook 的锁步纪律是必须遵守的现状约束。
+
+---
+
+## 3. 运维纪律:一通道一 agent
+
+### 3.1 现象与根因
+
+u-relay 路由按 `channel_id` 配对,且**同 channel_id 允许多个 agent 并存、命令全投递**(Vec 索引,无去重、无选举)。
+即:同 `channel_id` 上跑两个 agent 实例(如两个 devflow 桌面端、或 devflow + u-claw runner 用了同一通道),
+一条 commander 指令会被**两端各执行一次**——写文件、跑命令、调外部接口全部重复,且两端事件回传互相交错。
+
+### 3.2 危害
+
+- 指令重复执行:副作用翻倍(重复落库 / 重复发消息 / 重复改文件),且难以察觉。
+- 事件串台:commander 收到两份进度/结果,流式 UI 错乱;两 agent 的上下文互相污染。
+- 隐性化:两端都「看起来正常工作」,问题只在副作用层面暴露,事后才追溯得到。
+
+### 3.3 纪律
+
+- **一通道一 agent**:每个 `channel_id` 在任一时刻**只允许一个 active agent**。通道不是「实例池」。
+- **启动前先查**:起第二个 agent 前,确认目标 `channel_id` 上没有在跑的 agent(换一个独立 `channel_id`,或用不同通道前缀)。
+- **换装/重启要收尾**:旧 agent 若未干净退出(进程被杀但连接残留),WS 连接可能存活到对端超时,期间新旧两连接同通道并存 = 短暂双投递。换装脚本须确认旧连接已断、或等旧连接超时后再起新实例。
+- **命名隔离**:不同用途用不同 `channel_id`(如按机器 / 按项目 / 按用户拆通道),从命名上杜绝复用。
+
+### 3.4 排查手法
+
+怀疑重复执行时:
+
+- 看 relay 日志:同一 `channel_id` 是否同时有 >1 条 agent 连接。
+- 看 commander 侧:同一 `request_id` 是否收到两份 `result` / 事件重复。
+- 看副作用:文件 mtime、DB 记录、外部调用日志是否成对出现。
+
+确认后,断开多余 agent 连接(停掉重复实例)即可恢复单投递。
+
+---
+
+## 附:关联
+
+- 协议全貌:[u-relay Wire 协议规格](./u-relay-wire-protocol-2026-10-04.md)(§4 心跳、§5 部署、§3.3 路由)
+- u-relay 仓 README / 部署技能:中继二进制与 systemd 发布流程
+- 看板登记:`docs/todo.md`「新能力引入评估纪律」下三项 P3
diff --git a/docs/02-架构设计/专项设计/上下文分层组装器-落地方案-2026-10-10.md b/docs/02-架构设计/专项设计/上下文分层组装器-落地方案-2026-10-10.md
new file mode 100644
index 0000000..3e25dd5
--- /dev/null
+++ b/docs/02-架构设计/专项设计/上下文分层组装器-落地方案-2026-10-10.md
@@ -0,0 +1,265 @@
+# 上下文分层组装器 — 落地方案
+
+> 日期: 2026-10-10 | 状态: 设计稿(待拍板) | 勘察: 只读实测锚点
+> 定位: **《上下文根治设计-20261006.md》§一(L1/L2/L3)/§二(P1 瘦身)/§九(v1 锁定)/§十(检索面统筹)的落地细化**,不另立设计。
+> 覆盖 Wave 上下文走查实测五病灶:① `history_tokens` 与真实体量脱钩;② 每轮全量深克隆+展开 O(n²);③ 预算恒 128K;④ 裁剪后前缀缓存整体失配;⑤ L1/L2/L3 未落地、窗口外历史无召回。
+> 前置提醒: `crates/df-ai/**` 正由另一写者改动(P1 工具结果外置 WIP,见 §六),本方案明确与其重叠面与对接协议。
+
+---
+
+## 一、现状勘察(锚点实测)
+
+### 1.1 发送链路的真实数据流(五病灶的公共底座)
+
+每一轮 LLM 请求的消息来源分四步,散落在两处:
+
+| 步 | 锚点 | 动作 |
+|----|------|------|
+| ① 裁剪 | `crates/df-ai/src/context/mod.rs:185-273` `build_for_request(sys_tokens)` | 按 `history_tokens` 判预算,超则从**头部**淘汰轮次单元 |
+| ② 展开 | `src-tauri/src/commands/ai/agentic/mod.rs:1927-1944` | 逐条把 `namespace://` 引用**展开回真实内容** |
+| ③ 拼装 | `agentic/mod.rs:1945-1952` | 前置 system_prompt,尾部 `merge_volatile_tail`(`agentic/stream_relay.rs:789`) |
+| ④ 瘦身 | `agentic/mod.rs:1968-1988` → `stall_detector.rs:284-385` `summarize_tool_results` | view-only 摘要压缩大 tool_result(`extract_key_info`) |
+
+### 1.2 五病灶逐条锚点
+
+**① `history_tokens` 与真实体量脱钩(根因,最重)**
+
+- 工具结果在 push 前先被引用化:`audit/mod.rs:1184-1191`,`should_use_namespace`(`namespace_store.rs:47-53`)命中 → `store()`(`namespace_store.rs:124-131`)入内存,**消息里只留短串 `namespace://tool/{hash}`**。
+- `ContextManager::push`(`context/mod.rs:101-143`)对这条短串跑 `estimate_message`(`context_helpers.rs:62-105`),`history_tokens += tokens` 在 `:108-110`。
+- 但**发送时**才在 `agentic/mod.rs:1927-1944` 展开成真实体量(单条 max 150KB,见 P1 注释 `context_helpers.rs:427-432`)。
+- 结果:`history_tokens` 记的是引用串(~30 字符),真实发送是全文。预算门 `context/mod.rs:198`(`history_tokens <= available`)与压缩门 `context_lifecycle.rs:133-135`(`available*6/10 < history_tokens`)**双双恒不触发** → 每轮全量重发 → 223:1。
+
+**② 每轮 O(n) 全量克隆+展开+压缩 → 会话累计 O(n²)**
+
+- 未超预算分支 `context/mod.rs:198-205` 走 `all_messages_clone()`(`:294-296`,逐条深克隆);
+- 展开 `agentic/mod.rs:1927-1944` 对**全部**历史逐条 `read_only` + `String` 克隆;
+- 压缩 `summarize_tool_results`(`stall_detector.rs:313-385`)再遍历全部消息做内容变换。
+- 三者每轮各 O(n),会话累计 O(n²)。**根因与①同源**:门失效 → 窗口永不收敛 → n 随会话单调涨。
+
+**③ 预算恒 128K,与模型真实窗口无关**
+
+- 主会话 `PerConvState::new`(`commands/ai/mod.rs:1241`)= `ContextManager::new(ContextConfig::default())`,`max_tokens=128_000`(`context_helpers.rs:128-136`),`budget_limit` 由它算(`:138-143`)。
+- 真实窗口在 `ModelConfig.context_window`(`crates/df-ai-core/src/model.rs:138-139`),但只在 router 用,而代码自陈该维度"恒过、失效"(`agentic/mod.rs:2012` 注释)。
+- 子代理同病:`sub_agent.rs:119` 也是固定 128_000。
+
+**④ 裁剪后前缀缓存整体失配**
+
+- 超预算时 `context/mod.rs:239-246` 切 `self.messages[trim_end..]`——头部被切,整个请求前缀位移,provider 自动前缀缓存(DeepSeek/Anthropic)整段失效。
+- 已有部分缓解:淘汰摘要改尾部追加(`context/mod.rs:259-261`)、`push_system_anchor` 避让悬空头(`:582-595`)、`tool_view_decisions` 冻结压缩决策防跨轮翻转(`stall_detector.rs:352-363`)。但**基础裁剪本身**仍位移前缀,且**淘汰摘要内容逐轮漂移**(被淘汰区单调增长,`:259` 每轮即时重生成)。
+
+**⑤ L1/L2/L3 未落地 + 窗口外历史无召回**
+
+- 组装是隐式的:system(L1 内容)由 `build_runtime_prompt`(`agentic/mod.rs:3042`)拼,历史由 `build_for_request` 拼,易变尾由 `merge_volatile_tail` 拼,无统一"组装器"对象、无可观测统计。
+- 检索仅覆盖知识库:`hybrid_search`(`knowledge_inject.rs:421`)→ `build_knowledge_context`(`:508`)→ `build_knowledge_context_for_conv`(`:587`);**窗口外对话历史与压缩摘要无处可召回**。
+
+### 1.3 与既有设计的衔接
+
+- 本方案 = 根治设计 §一 的接口化落地;`上下文三层记忆-设计-2026-10-04.md` 的"记忆卡"(C-m1)是 L3 的**记忆卡来源**,本方案不重复其产出逻辑,只提供召回入口。
+- §九 v1 锁定范围(P1 瘦身+P2 页池+P3 压缩常规化+P4 评估)不变;本方案细化 **P2**,并把 §九"唯一存储/唯一调度器/唯一召回入口"三收敛落到具体接口。
+
+---
+
+## 二、目标与验收口径
+
+**目标**
+1. **账实相符**:预算/压缩门依据的 token 数 = 实际发送视图的体量(单一真值来源)。
+2. **组装显式化**:L1 常驻 / L2 滑动窗口 / L3 检索召回 三段各有明确产出与稳定合并顺序;附带可观测统计。
+3. **前缀稳定**:稳态下请求前缀只追加、不位移;裁剪以批量滞后发生。
+4. **预算随模型**:上下文预算取自当前模型的 `context_window`,非恒定 128K。
+5. **可召回**:窗口外历史/记忆卡经统一检索面按需注入,复用 `knowledge_inject` 管线。
+
+**验收口径**
+- 账实相符:`history_tokens` 与"发送视图 `estimate_message` 求和"偏差 ≤ 5%(构造含大 `namespace://` 引用的会话实测)。
+- 门生效:构造超预算会话,`context_trimmed`(`context/mod.rs:263`)按真实体量触发;压缩门 `context_lifecycle.rs:133` 在正确水位触发。
+- 前缀稳定:稳态连续 N 轮,请求消息数组的公共前缀长度 = 全量(无裁剪)或 = system 段(裁剪后仅在再裁剪时才变);provider 缓存命中率不再掉到个位数。
+- 预算随模型:`context_window=256K` 的模型下 `budget_limit` = 256K×0.85−8K 量级,不再恒 128K。
+- 可召回:窗口外某历史事实,仅凭关键词在下一轮被召回注入(带来源标注)。
+- 不倒退:单轮/短会话(未超预算)行为与改造前逐字节一致;`df-ctx-*` 开关全关 = 旧路径。
+
+---
+
+## 三、设计方案
+
+### 3.0 核心判断
+
+五病灶**同源于一个断裂**:"保留形态"与"发送形态"两套尺寸,门只看了前者。因此**不新增存储、不另立调度**——本方案只做三件事:
+1. 把"门所依据的尺寸"与"发送形态"对齐(**记账基点 = 发送视图尺寸**);
+2. 把隐式拼装收敛为一个显式**组装器**(三段产出 + 稳定顺序 + 统计);
+3. 把检索面从"仅知识库"扩为**注册表**(知识库 + 历史页 + 记忆卡同入口),落到 `build_knowledge_context_for_conv` 单点。
+
+### 3.1 单一真值来源的 token 估值(治 ①,兼解 ②)
+
+**两法对比**
+
+| 法 | 做法 | 评价 |
+|----|------|------|
+| **A 记账点算"发送视图尺寸"** | 在内容产生点(及 reload)按 `view_form(msg)` 估 token——大工具结果按 `extract_key_info` 后的摘要+句柄尺寸入账 | O(1)/条、纯函数、与发送形态**同函数**(单一真值)、不依赖 namespace LRU 状态 |
+| B 估值器识别 ref 查真实大小 | `estimate_message` 遇 `namespace://` 前缀 → 查 store 取真实内容再估 | 破坏 `TokenEstimator` 纯函数性(需注入 store)、每轮 O(n) 查+扫(加重②)、引用被淘汰 / 重启后查不到 → 退回串长仍脱钩 |
+
+**取 A**。理由:A 让"记账"与"发送"共用同一个纯函数,是真值来源唯一化的最短路径;B 把复杂度塞进每轮热路径且对淘汰态脆弱。A 单点落在估值函数,正是 `context_helpers.rs` 的职责域(与既有 `should_summarize_tool_result`/`extract_key_info` 同居一文件,DRY)。
+
+**接口形态(新增,minimal)**
+
+```rust
+// crates/df-ai/src/context_helpers.rs — 与 extract_key_info 同域,共享同一压缩逻辑
+/// 发送视图尺寸(记账唯一口径):与发送路径"展开→summarize_tool_results"后的形态对齐。
+/// 大 tool_result → extract_key_info 结果的 token;其余 → 内容 token。
+pub fn view_form_tokens(estimator: &TokenEstimator, content: &str, tool_name: Option<&str>) -> u32;
+
+// crates/df-ai/src/context/mod.rs — push 的带尺寸覆盖入口(记账用)
+impl ContextManager {
+ /// 同 push,但 token_count 用调用方给定的发送视图尺寸(工具名在产生点可得)。
+ pub fn push_with_tokens(&mut self, message: ChatMessage, tokens: u32);
+}
+```
+
+**改动点**
+- 产生点 `audit/mod.rs:1184-1191`:此处已握有真实 `content` 与 `draft.name`,算 `view_form_tokens(...)` 后走 `push_with_tokens`,取代 `push` 的 content 估值。删掉 push 的隐式 ref 估值这条唯一失真源。
+- 其余重估入口(`replace_tool_result_content` `context/mod.rs:332`、`replace_last_active_user_content` `:418`)同改走 `view_form_tokens`,保证单一口径。
+- **reload 一致性**:DB 存的是展开后的真实内容(`namespace_store.rs:3-4` 注释 / `agentic/mod.rs:1922`),故 `restore_from_messages`(`context/mod.rs:310`)须同样按 `view_form_tokens` 记账,否则"新会话(视图尺寸)≠ 重载(真实尺寸)"两态不一致。工具名从历史重建(`build_id_to_name`,`stall_detector.rs:300`)取得(见拍板点 2)。
+- **② 的化解**:① 修好后门下移到位 → 裁剪按真实体量发生 → 发送窗口收敛为常数 W → 每轮克隆/展开/压缩从 O(n) 降到 O(W),会话累计从 O(n²) 降到 O(n·W)。②无需单独"优化",随①自愈。
+
+### 3.2 分层组装的接口形态(治 ②④⑤)
+
+**现状**:拼接散在 `context/mod.rs:185-273`(窗口裁剪)与 `agentic/mod.rs:1945-1952`(system+尾)。改为一个**显式组装器**,产出结构化的三段 + 易变尾 + 统计:
+
+```rust
+// src-tauri/src/commands/ai/agentic/assembler.rs (新文件;或收敛进现有 helpers.rs)
+/// 组装器产出:有序三段 + 易变尾 + 可观测统计。
+pub struct AssembledContext {
+ pub resident: Vec, // L1:system 段(见 3.2.1)
+ pub window: Vec, // L2:滑动窗口(三元组对齐的发送视图)
+ pub recall: Vec, // L3:检索召回(user 角色,易变,固定置尾)
+ pub volatile: Option, // 既有易变尾(日期/清单/knowledge)
+ pub trimmed: bool,
+ pub stats: AssemblyStats, // 页命中/召回数/换出量/system 估算
+}
+
+/// 合并顺序固定:resident → window → volatile → recall,全部只追加不位移前缀。
+pub fn assemble(ctx: &ContextManager, sys_tokens: u32, recall: Vec, volatile: Option)
+ -> AssembledContext;
+```
+
+**合并顺序与缓存友好的三条不变式**
+1. **L1 resident 最前且为 loop 不变量**:`build_runtime_prompt`(`agentic/mod.rs:3042`)产出,pinned_goals/工作上下文/技能/DAG 均在此,整个 loop 文本不变 → 稳定缓存前缀最大块。
+2. **L2 window 追加为主**:`build_for_request` 的裁剪保留区(`context/mod.rs:243-246`)即窗口。三元组原子性沿用 `build_eviction_units`(`:612-642`)的单元边界,窗口切片必落单元边界。稳态只 push 追加,前缀不变。
+3. **L3 recall + volatile 严格置尾(role=user)**:召回复用 `merge_volatile_tail`(`stream_relay.rs:789`)的"末尾 user 承载"范式——易变内容不进缓存前缀。**召回绝不插进 window**(避免劈三元组、位移前缀)。
+
+**与 `build_for_request` 的关系**:`build_for_request` 保留为 L2 的**纯函数内核**(签名与三元组逻辑不动),组装器在其上"包一层":调 `build_for_request(sys_tokens)` 取窗口 → 前置 resident → 追尾 volatile/recall → 填 `AssembledContext`。即"内核不改行为,组装器负责编排"。调用点 `agentic/mod.rs:1910-1953` 从内联拼装改为 `assemble(...)` 单调用。
+
+**前缀稳定性(治④)**
+- **滞后水位批量裁剪**:trim 触发用高水位(`high = available`),一次裁到低水位(`low = available × k`,建议 k≈0.6),使裁剪**稀疏发生**;两次裁剪之间窗口 append-only,前缀稳定。替代现行"每轮才够就裁一单元"(`context/mod.rs:211-219`)。
+- **淘汰摘要冻结**:现行摘要每轮重生成致漂移(`:259`)。改为与 `tool_view_decisions` 同款**按内容哈希冻结**(首次生成后沿用),前缀稳定。
+- **随①自愈**:① 使门在正确水位触发,裁剪频次本就下降。
+
+### 3.3 检索召回复用 knowledge_inject 管线(治⑤,兼§十统筹)
+
+**现状单入口**:两个调用点(`agentic/helpers.rs:150`、`commands/chat.rs:398`)都经 `build_knowledge_context_for_conv`(`knowledge_inject.rs:587`),内部 `hybrid_search`(`:421`)+ `merge_hybrid_results`(`:468`)排序。
+
+**改为注册表(§十"召回入口统一"的落地)**
+
+```rust
+// src-tauri/src/commands/ai/knowledge_inject.rs — 扩为召回面
+type RecallItem = (df_storage::models::KnowledgeRecord, RecallKind); // 复用既有 record 形态
+enum RecallKind { Knowledge, HistoryPage, MemoryCard }
+
+/// 各源实现同一检索签名;合并复用 merge_hybrid_results 的排序/去重/limit 语义。
+async fn recall(query, limit, ...) -> Vec // 内部:知识库(既有) + 历史页(新) + 记忆卡(新)
+```
+
+- **入口不动**:`build_knowledge_context_for_conv`(`:587`)与 `inject_knowledge_into_prompt`(`:633`)保持签名,内部从"只查知识库"扩为"查注册表全部源",输出**按源标注**(`[知识]`/`[历史]`/`[记忆卡]`)。调用点零变更。
+- **HistoryPageSource(v1 当前会话)**:query 复用末条 active user 文本(`knowledge_inject.rs:592-601` 已取);扫描当前 `conv.messages` 中 **!is_active(compressed/archived)** 与**窗口外**消息,关键词重叠排序取 top-K。**零新存储**——数据已在 `ContextManager.messages` 全量保留(`context/mod.rs:104-107`)。
+- **MemoryCardSource**:压缩摘要(`ChatMessage::system(&summary.nl_summary)`,`context_lifecycle.rs:294` 尾部追加)与 `上下文三层记忆` C-m1 的记忆卡,作为可召回条目注册。v1 可与 HistoryPageSource 合并实现。
+- **配额闸(§十原则4)**:召回页每轮 ≤K(建议 5),配额独立于知识注入既有上限,防信息炸弹。
+
+### 3.4 预算随模型窗口(治③)
+
+- 预算来源改为当前模型的 `ModelConfig.context_window`(`df-ai-core/src/model.rs:138-139`)——该值已在 `AiProviderRecord.model_configs`(`df-storage/src/models.rs:306+`,经 `deserialize_model_configs` 解析)可得,loop 顶部已持 `provider_config`。
+- 落地:`ContextManager` 增 `set_max_tokens` 或在派发前用模型窗口重建 `ContextConfig`;`sys_tokens` 估算点(`agentic/mod.rs:1115`)不变。
+- 缺省回退:当前模型无 `context_window`/为默认值时,沿用 128K(零回归)。
+
+### 3.5 边界总览(三段职责)
+
+```
+L1 resident ← build_runtime_prompt (agentic/mod.rs:3042) 常驻,loop 不变量,缓存前缀
+L2 window ← build_for_request (context/mod.rs:185) 滑动,三元组对齐,记账=view_form
+L3 recall ← recall registry (knowledge_inject.rs:587 扩) 按需,置尾,配额闸
+```
+
+---
+
+## 四、分期(每期独立可上线、可回退)
+
+### P2.1 — 真值 token 估值(治①,兼解②)【S,最先】
+- 新增 `view_form_tokens`(`context_helpers.rs`)+ `push_with_tokens`(`context/mod.rs`)。
+- 改 `audit/mod.rs:1191` push 站点与重估入口(`context/mod.rs:332`/`:418`)、reload 路径(`:310`)。
+- 开关 `df-ctx-accurate-tokens`(默认 **true**;false = 旧 content-based 记账,逐字节回退)。
+- 验收:账实相符 + 门生效两条。**此期不动发送内容,零行为变化。**
+
+### P2.2 — 组装器结构化(治④,为 P2.3 铺路)【M】
+- 新增 `AssembledContext` + `assemble()`;`agentic/mod.rs:1910-1953` 内联拼装改单调用;加 `AssemblyStats` 日志/事件。
+- trim 滞后水位 + 淘汰摘要冻结。
+- 行为等价重构(除 trim 时机稀疏化,可单开关 `df-ctx-trim-hysteresis` 默认 true)。
+- 验收:前缀稳定 + 不倒退两条。
+
+### P2.3 — L3 检索召回(治⑤)【M】
+- `knowledge_inject.rs` 扩为召回注册表 + HistoryPageSource(v1 当前会话)+ 源标注;配额闸 ≤K。
+- 开关 `df-ctx-recall`(默认 **off**,择机灰度开)。
+- 验收:可召回一条。
+
+### P2.4 — 预算随模型窗口(治③)【S】
+- 接 `ModelConfig.context_window`;缺省回退 128K。开关 `df-ctx-model-window`(默认 true)。
+- 验收:预算随模型一条。
+
+### P2.5 — 页池统一【北极星,触发制,不在本方案实施】
+- 触发条件:单会话版实测后仍有跨会话/内存痛点(§九问题2);或将 namespace_store(内存)与 tool_offload(磁盘)合流为唯一内容寻址页池(§九问题1 收敛)。
+- 本方案只**预留接口边界**(见 §六),不实施。
+
+**顺序**:P2.1(最大杠杆、最小改动)→ P2.2(结构)→ P2.4(廉、高值,可与 P2.2 并行)→ P2.3。
+
+---
+
+## 五、影响面(五维)
+
+- **兼容**:P2.1 只改记账,不改发送内容;老会话 reload 后按 `view_form_tokens` 重算(与新建会话口径一致,依赖 reload 路径同改)。P2.2 行为等价重构。P2.3/P2.4 均带开关,关=旧路径。无 schema 变更。
+- **破坏**:裁剪切点由"即时"变"滞后批量"(P2.2)——行为变更但语义等价(仍保三元组、仍有淘汰摘要);开关可退。无不可逆操作。
+- **安全**:检索召回仅扩"只读召回面",注入内容受既有配额闸约束;不引入新命令执行面。召回的历史页来自本会话已保留内容,无跨权限面(v1 单会话)。
+- **资源**:① 修正后 `history_tokens` 上升到真实量级,裁剪更积极 → **发送 token 下降**(目标 §一)。召回新增每轮 ≤K 页注入(可控)。无新持久化。
+- **回退开关**:`df-ctx-accurate-tokens` / `df-ctx-trim-hysteresis` / `df-ctx-recall` / `df-ctx-model-window`(均 KV 热读,默认值见各期)。观测:`AssemblyStats`(召回命中/换出量/window token/trim 次数)+ 既有 `context_trimmed` 日志。
+
+---
+
+## 六、与 P1 工具结果瘦身(另一写者)的边界与对接
+
+**P1 现状(WIP,`git diff`)**:`crates/df-ai/src/lib.rs` 加 `pub mod tool_offload;`;`context_helpers.rs` 加 `READ_FILE_OFFLOAD_BYTES:432` / `TOOL_OFFLOAD_HANDLE_MIN_BYTES:436`,重构 `extract_key_info`(`:472`) 为"小文件豁免 + 超阈值压缩并落盘句柄"。消费点 `stall_detector.rs:342-351`、`sub_agent.rs`。
+
+**职责二分(不双实现)**
+| 归属 | 内容 |
+|------|------|
+| **P1(内容变换)** | 大工具结果**如何变小**:`extract_key_info` / `offload_with_handle`(`tool_offload.rs:46`)/ `namespace_store` 引用化 |
+| **本方案(记账+组装)** | 变小后**占多少**、**发什么**、**如何召回**:`view_form_tokens` 记账基点、组装器、召回面 |
+
+**对接协议(冻结接口)**
+1. **单一压缩函数**:本方案的 `view_form_tokens` 内部**复用 P1 的 `extract_key_info`**(同域同 crate),不另写压缩逻辑。P1 若改阈值/句柄格式,本方案自动跟随——**一处定义**。
+2. **单一存储形态(§九问题1)**:`namespace_store`(内存)与 `tool_offload`(磁盘)是**同一原文的两种介质**,构成双实现隐患。**本方案 v1 不碰二者**,只在 §3.3 召回面与 §二验收中把"结果页"抽象为内容哈希寻址(handle 与 ref key 均已是内容哈希:`namespace_store.rs:353` / `tool_offload.rs:37`)——为 P2.5 合流预留,不在 v1 制造第三套。
+3. **文件重叠**:`context_helpers.rs` 双方都动(P1 改 `extract_key_info`/常量;本方案加 `view_form_tokens`)。约定:本方案新增函数**不改 P1 既有函数签名**;`view_form_tokens` 以只读方式调用 `extract_key_info`。**串行合并**:P1 先合(其 `extract_key_info` 形态是本方案记账的输入),本方案在 P1 之上加记账点。
+4. **执行序**:P2.2/P2.3/P2.4 与 P1 无重叠文件(改 src-tauri,除消费 P1 常量),可并行;P2.1 与 P1 同文件,**待 P1 合入后落**。
+
+---
+
+## 七、拍板点清单
+
+| # | 决策 | 选项 | 建议 |
+|---|------|------|------|
+| 1 | 记账基点(单一真值来源) | A 发送视图尺寸(view_form,与发送同函数);B 保留内容/引用串尺寸 | **A**。B 无法消除账实脱钩;B 是当前病灶本身 |
+| 2 | reload 路径口径 | A 重建 id→name,reload 亦按 view_form(精确一致);B reload 按真实内容保守估(实现简,与新建会话口径不完全一致) | **A**。一致性是"单一真值"的前提;`build_id_to_name` 已存在可复用 |
+| 3 | 组装器落地形态 | A 新增 `AssembledContext` 结构 + `assemble()` 单入口;B 保留 `build_for_request` 返回 tuple,拼接仍分散 | **A**。B 延续"拼接分散",统计/顺序无处安放 |
+| 4 | L3 召回 v1 半径 | A 仅当前会话窗口外页(in-memory 关键词,零新存储);B 纳入跨会话/向量检索 | **A**。跨会话/向量是 §九 v2+ 北极星(触发制),v1 零新存储先证价值 |
+
+---
+
+## 八、与既有文档关系
+
+- 本方案 = 《上下文根治设计-20261006.md》§一/§二/§九/§十的**接口细化与实施序**,不改其 v1 范围与北极星定位。
+- `上下文三层记忆-设计-2026-10-04.md`:其 C-m1 记忆卡是 L3 的 `MemoryCardSource` 数据源,本方案只提召回入口(§3.3),不重复卡片产出。
+- `namespace淘汰治理-设计-2026-10-04.md`:namespace_store 的 pin/EvictionLog/owner 治理不变;P2.5 页池统一时作为 RAM 层纳入。
diff --git a/docs/02-架构设计/专项设计/定时消息与待发箱持久化-设计-2026-10-10.md b/docs/02-架构设计/专项设计/定时消息与待发箱持久化-设计-2026-10-10.md
new file mode 100644
index 0000000..1a12d80
--- /dev/null
+++ b/docs/02-架构设计/专项设计/定时消息与待发箱持久化-设计-2026-10-10.md
@@ -0,0 +1,284 @@
+# 定时消息与待发箱持久化 — 设计
+
+> 日期: 2026-10-10 | 状态: 设计稿(待拍板) | 勘察: 只读实测锚点
+> 覆盖需求: todo **935**(定时消息多条 + 统一「定时列表」)与 todo **931**(发送队列网络超时/重启不丢)。
+> 关联: todo **937**(对话内定时/延时任务)共用本设计调度器,仅预留扩展点,不在本设计实施。
+> 目标(用户原话转译): 消息可设为定时发送、可设多条,有一个统一「定时列表」可查看/改期/取消;发队列遇网络超时、杀进程重启都不能丢。
+
+---
+
+## 一、现状勘察(锚点实测)
+
+### 1.1 发送队列(931 病灶)
+
+| 件 | 锚点 | 现状 |
+|----|------|------|
+| 队列数据结构 | `src/stores/ai.ts:104`(类型)、`:136`(`queue: []` 初始化) | **纯内存模块级数组**,字段 `{ text, skill?, enqueuedAt, parts?, spans?, conversationId? }`,无任何持久化 |
+| 入队 | `src/composables/ai/useAiSend.ts:392-404` | `sendMessage` 查 `ai_is_generating` 得 `backendGenerating` → 忙则 `state.queue.push(...)`;上限 `QUEUE_LIMIT=10`(`:53`),超时 `QUEUE_TIMEOUT_MS=30_000`(`:56`) |
+| 续发派发 | `useAiSend.ts:308-353` `drainQueue`;监听器 `:607-612` `initDrainQueueListener` | **前端自驱**:`AiCompleted` → `useAiEvents` 发 `ai-drain-queue` → `drainQueue` 取出队首 `sendMessage` |
+| 显式清队 | `useAiSend.ts:193-196`(regenerate)、`:269-272`(editMessage)、`:488-495` `clearQueue` | 重生成/编辑属用户显式动作,清本会话队列合理;`notifyQueueCleared`(`aiShared.ts:772`)toast 提示 |
+| 队列 UI | `src/components/AiChat.vue:78-113`;`activeQueue` 计算 `:489` | 按 `activeConversationId` 过滤渲染当前会话队列,含 强制发送/清空/编辑/回填/立即发送/取消 |
+
+**病灶(与 todo 931 勘察一致)**:①队列内存态,杀进程即丢;②后端 busy 判定 + 后续 `AiCompleted` 续发全依赖前端活着并收到事件——网络超时/事件竞态丢失则队列永久卡住或静默丢;③`state.queue` 无启动恢复入口。修法须从「内存 + 前端自驱」下沉到「持久化 + 后端自驱」。
+
+### 1.2 后端发送受理链(定时复用的正典入口)
+
+| 件 | 锚点 | 语义 |
+|----|------|------|
+| `ai_chat_send` IPC | `src-tauri/src/commands/ai/commands/chat.rs:636` | 拦截 `/status` → 解析前导技能 → 委派 `spawn_agentic_session` |
+| `spawn_agentic_session` | `chat.rs:796`(`pub(crate)`) | **发送正典核心**:解析 provider → 目标 conv 入参优先/缺省 active/都无则懒建 → `can_accept_request` 门禁(仅 Idle\|Error 可接,拒 Generating/Stopping/Compressed)→ 写 stop_flag/language/iteration/model_override → spawn `run_agentic_loop`。`pin_active` 决定是否劫持用户活跃面板 |
+| generating 判定 | `chat.rs:471` `ai_is_generating` | 读无锁 `state.conv_states.is_active(conv)` |
+| 完成出口(单点) | `src-tauri/src/commands/ai/agentic/mod.rs:2882` `emit_ai_completed_once` → 调 `unattended::notify_batch_done(conv_id)`(`:2917`) | **全部正常收尾汇聚于此单点**,是「会话转空闲」的唯一可靠信号 |
+
+结论:`spawn_agentic_session` 是后端可无前端直接调用的发送入口(无人值守引擎已这么用),定时到点可直接走它,无需前端在场。
+
+### 1.3 后端常驻调度器先例(可直接抄形态)
+
+`src-tauri/src/commands/ai/unattended.rs`:
+- `start(app)`(`:505` 起)用 `tauri::async_runtime::spawn` + `tokio::time::interval` + `tokio::select!{ interval.tick(), notify.notified() }` 起常驻循环;周期/开关/范围全部**从 `Settings KV` 每轮热读**(`df-unattended-poll-secs` 等)。
+- 派发走 `spawn_agentic_session(..., Some(new_id()), ..., false)`(`:847-857`):**独立后台会话 + `pin_active=false`**,不劫持用户活跃面板。
+- `notify_batch_done`(`:483`)在完成出口清在途标志并 `notify_one` 唤醒轮询器。
+- 单会话单在途双保险:内部 `in_flight` 标志 + `can_accept_request` 兜底清残留(异常退出漏钩子时不永久卡死)。
+- 挂载点 `src-tauri/src/lib.rs:84` `commands::ai::unattended::start(app.handle().clone())`。
+- 通知出口:托盘 `notify_session_event`(`commands/ai/notify.rs:47`,`NotifyKind::Completed/Failed/NeedAction`);飞书 `feishu_notify::push_card/push_monitor_alert`,目标 KV `df-monitor-chat-id`(`unattended.rs:52`)。
+
+### 1.4 生命周期(「应用关闭后是否生效」的事实)
+
+- `lib.rs:51` `.setup()`:开库+迁移(`AppState::init`)→ `app.manage` → 各 `spawn`(feishu、`unattended::start`、mcp、proxy gateway、tray)。
+- `lib.rs:614-658` `on_window_event`:`CloseRequested` → **`api.prevent_close()` + `window.hide()` 托盘化,进程不退出**;真正退出只走托盘菜单「退出」(或系统关机)。
+- 无 `RunEvent::Exit` 级关闭钩子(默认 `.run()`)。
+
+**关键事实**:窗口关闭 → 托盘常驻 → **进程仍在,后端 tokio 调度器照常跑**;只有进程真退出(托盘退出/关机)调度器才停。故「应用关闭后是否生效」的精确答案是:**关窗(托盘化)期间生效;进程退出期间不生效,重启后按追赶策略补发**——这是必须写进验收口径的边界。
+
+### 1.5 存储与迁移底座
+
+- 迁移版本链 `crates/df-storage/src/migrations.rs`,当前 **V50**;`migrations_helpers::{column_exists, table_exists}` 提供幂等探测。
+- Repo 两种范式:`impl_repo!` 宏(`crates/df-storage/src/crud/mod.rs`,生成 insert/get_by_id/list_all/update_full,适合固定 CRUD);`SettingsRepo`(`crud/settings.rs`)手写 KV(`app_settings` 表,key/value JSON),适合标量偏好。
+- 会话记录 `AiConversationRecord`(`crates/df-storage/src/models.rs:372`)已有 `loop_state`(V49)/`status`(V50)等生命周期列先例。
+- 前端会话 CRUD:`ai_conversation_create`(`src/api/ai.ts:318`)、`ai_conversation_list`(`:323`)——定时目标会话可在 composer 处先建。
+
+---
+
+## 二、目标与验收口径
+
+**功能目标**
+1. Composer 旁「定时」入口:把当前输入(文本/技能/图片)设定为**某绝对时间**发送;可连续设多条。
+2. 统一「定时列表」:跨会话查看全部定时项,支持 查看/改期/取消/编辑文本;到点自动发送。
+3. 待发队列**持久化**:入队/出队/取消即落库;网络超时不清队列(仅标记该条可重试);杀进程重启后队列仍在且可续发。
+4. 定时到点时会话忙 → 顺延到会话空闲再发(不丢、不打断当前生成)。
+5. 失败可见:连续失败到上限 → 标失败 + 托盘通知(可选飞书),用户可在列表重试/取消。
+
+**验收口径**
+- 断网/超时:构造后端生成中入队 → 断网触发错误 → 队列项仍在(标记可重试),恢复网络后续发。
+- 重启:入队 N 条 → 杀进程 → 重启 → 队列恢复 N 条且自动续发;`sending` 残留被复位为 `pending`。
+- 定时:设 1 分钟后发送 → 关窗托盘化 → 到点仍在后台发出(session 落库可见);设 1 分钟后发送 → 立刻进程退出 → 重启 → 按追赶策略补发或标记过期。
+- 多会话:定时项按目标会话独立排队,不误发到当前活跃会话;到点会话忙则顺延。
+- 不倒退:无定时/无队列时,普通发送链路(L0/L1/L2)行为与改造前逐字节一致。
+
+---
+
+## 三、设计方案
+
+### 3.0 核心判断:统一为后端「待发箱(outbox)」
+
+931 队列与 935 定时消息本质是**同一实体、不同触发条件**:队列 = 「会话空闲即发」,定时 = 「到点且空闲即发」。因此不建两套调度,统一落一张表 + 一个后端调度器:
+
+- **触发条件统一为 `due_at`**:队列项 `due_at = NULL`(尽快);定时项 `due_at = epoch_ms`。调度器每次取「`due_at` 已到 且 目标会话空闲」的项派发。
+- **单一真相源**:表是真相,前端 `state.queue` 退化为该会话 outbox 的**只读视图**,前后端各一套派发逻辑的「草率聚合」不再存在。
+- **调度落点 = 后端 tokio 常驻**(抄 unattended 形态),复用 `spawn_agentic_session` 正典链 + `emit_ai_completed_once` 空闲钩子。
+
+### 3.1 数据结构(新表 `ai_outbox`,迁移 V51)
+
+选**独立表**而非 KV:`app_settings` 是标量偏好,读改写整块 JSON 无索引、并发改期/取消会互相覆盖;outbox 需按 `status+due_at` 索引扫描、按 id 逐条 CAS 更新,表是对的解。
+
+```sql
+CREATE TABLE IF NOT EXISTS ai_outbox (
+ id TEXT PRIMARY KEY,
+ conversation_id TEXT NOT NULL, -- 目标会话(composer 处确定;无则前端先建)
+ kind TEXT NOT NULL DEFAULT 'queue', -- 'queue'(尽快) | 'scheduled'(定时);预留 'task'(937)
+ text TEXT NOT NULL,
+ skill TEXT, -- /技能名
+ model_override TEXT, -- 用户指定模型(null=路由)
+ parts_json TEXT, -- ContentPart[] JSON(图片;空/NULL=纯文本)
+ spans_json TEXT, -- MentionSpan[] JSON
+ due_at INTEGER, -- epoch ms;NULL=尽快(queue)
+ status TEXT NOT NULL DEFAULT 'pending', -- pending|sending|sent|failed|canceled
+ attempts INTEGER NOT NULL DEFAULT 0,
+ last_error TEXT,
+ created_at INTEGER NOT NULL,
+ updated_at INTEGER NOT NULL,
+ sent_at INTEGER
+);
+CREATE INDEX IF NOT EXISTS idx_ai_outbox_due ON ai_outbox(status, due_at);
+CREATE INDEX IF NOT EXISTS idx_ai_outbox_conv ON ai_outbox(conversation_id, status);
+```
+
+迁移:V51 = 建表 + 两索引(`table_exists` 幂等守卫)。**可逆**:DOWN 等价 = `DROP TABLE ai_outbox`(纯新增表,不影响旧数据;回退即弃表 + 关调度器开关)。**务必按项逐条落状态标记,禁依赖全局内存推算**(V21 教训:`docs/todo.md:860` 全局 COUNT 幂等窗口中途崩溃致永久残缺)。
+
+Rust 记录(加 `crates/df-storage/src/models.rs`):
+
+```rust
+pub struct AiOutboxRecord {
+ pub id: String,
+ pub conversation_id: String,
+ pub kind: String,
+ pub text: String,
+ pub skill: Option,
+ pub model_override: Option,
+ pub parts_json: Option,
+ pub spans_json: Option,
+ pub due_at: Option,
+ pub status: String,
+ pub attempts: i64,
+ pub last_error: Option,
+ pub created_at: i64,
+ pub updated_at: i64,
+ pub sent_at: Option,
+}
+```
+
+### 3.2 存储层 Repo(手写,不走 `impl_repo!`)
+
+`crates/df-storage/src/crud/outbox_repo.rs`(新)。因需状态 CAS 与条件更新,手写(同 `SettingsRepo` 范式):
+
+- `insert(&record) -> id`
+- `list_by_conv(conv_id, include_done: bool) -> Vec`
+- `list_due(now_ms, limit) -> Vec`(status='pending' AND (due_at IS NULL OR due_at<=now) ORDER BY created_at)
+- `claim(id) -> bool`:CAS `UPDATE ... SET status='sending', updated_at=? WHERE id=? AND status='pending'`(changes==1 才 true,防并发双取)
+- `mark_sent(id)` / `mark_failed(id, err)` / `revert_pending(id, attempts, err)`
+- `reschedule(id, due_at)` / `cancel(id)` / `update_text(id, text)` / `clear(conv_id?)`
+- `reset_stale_sending()`:启动把残留 `sending` 复位为 `pending`(崩溃半途,镜像 unattended 的 `in_flight` 泄漏处置)
+
+AppState 增 `pub outbox: OutboxRepo`(`state.rs`,`AppState::init` 内 `OutboxRepo::new(&db)`),与 `settings`/`conv_states` 并列。
+
+### 3.3 后端调度器 + IPC(新文件 `src-tauri/src/commands/ai/outbox.rs`)
+
+**挂载**:`lib.rs` setup 增 `commands::ai::outbox::start(app.handle().clone())`(与 `unattended::start` 并列);启动即先 `outbox.reset_stale_sending()`。
+
+**循环**(抄 unattended 形态):
+
+```
+start(app): spawn loop {
+ select!{ interval.tick(), notify.notified() } // 周期兜底 + 事件即时唤醒
+ if !enabled(KV) continue
+ for item in list_due(now, 32) { // 按 created_at 排序
+ if dispatched_this_tick(conv) continue // 同会话同轮只派一条(串行保序)
+ if !conv_states.can_accept_request(conv) continue // 忙 → 本轮跳过,下次再取(不丢)
+ if !claim(item.id) continue // CAS 失败(他轮已取)
+ match spawn_agentic_session(app, state, item.text, None, item.skill,
+ item.model_override, Some(conv), parts, spans, false) {
+ Ok(_) => { mark_sent(item.id); emit("ai-outbox-sent", {id, conv}); mark_dispatched(conv) }
+ Err(e) => { // 仅 spawn 前置失败(provider未配/会话忙竞态)
+ if permanent(e) { mark_failed(item.id, e); notify(...) }
+ else { revert_pending(item.id); emit("ai-outbox-changed"); }
+ }
+ }
+ }
+}
+```
+
+**关键语义**
+- `pin_active=false`:定时/队列的自动发送不劫持用户活跃面板(同无人值守);用户切到该会话仍能看到实时流(AiTextDelta 已按 conv 路由)。
+- **「完成即唤醒」钩子**:`agentic/mod.rs:2917` 现有 `unattended::notify_batch_done(conv_id)` 旁并列加 `outbox::notify_conv_idle(conv_id)`——会话一空闲就唤醒调度器续发,替代前端 `ai-drain-queue` 自驱。
+- **失败面界定**:`spawn_agentic_session` 返回 `Ok` 即「受理成功」,outbox 立即标 `sent`;LLM 轮次内的失败(流断/工具错)由既有 loop 自愈+错误气泡+看门狗处置,**不回流 outbox**(避免与既有韧性机制重叠)。outbox 的 `failed` 只覆盖**受理失败**(provider 未配、永久 4xx 特征串如 404/额度耗尽——复用 `classifySendError` 同源判据的 Rust 版)。
+- 重试:`revert_pending` 保留下轮再试(忙场景天然重试);`attempts` 达 `df-outbox-max-attempts`(默认 3)记 `failed` + 通知。
+- **过期追赶**:调度器只认「`due_at <= now`」,故进程退出期间到期的项在重启后立即命中(补发)。是否对「迟到过久」的项改标过期见拍板点 4。
+
+**IPC 面**(注册进 `lib.rs` `invoke_handler`):
+- `ai_outbox_enqueue(conversation_id, text, skill, model_override, parts, spans, due_at?) -> id`(`due_at` 空=队列项)
+- `ai_outbox_list(conversation_id?, include_done?) -> Vec`
+- `ai_outbox_reschedule(id, due_at)` / `ai_outbox_cancel(id)` / `ai_outbox_update_text(id, text)`
+- `ai_outbox_send_now(id)`(插队=force;后端 CAS claim 后立即派发)
+- `ai_outbox_clear(conversation_id?)`
+
+**事件面**:`ai-outbox-changed`(任何变更后前端刷新视图)、`ai-outbox-sent { id, conversation_id }`、`ai-outbox-failed { id, conversation_id, error, permanent }`。
+
+**通知出口**:失败经 `notify_session_event(app, NotifyKind::Failed/NeedAction, conv, ...)` 托盘补位;需要飞书时复用 `feishu_notify::push_card` + KV `df-monitor-chat-id`(与 unattended 同款,零新通道)。
+
+**KV 配置**(`app_settings`,热读):`df-outbox-enabled`(默认 true,回退开关)、`df-outbox-poll-secs`(默认 10,下限钳 5)、`df-outbox-max-attempts`(默认 3)、`df-outbox-notify-feishu`(默认 false)。调度器另用 `Notify` 由 enqueue/完成钩子即时唤醒,周期 tick 仅兜底 `due_at` 到点。
+
+### 3.4 前端改造(队列视图化 + 定时入口)
+
+**`src/composables/ai/useAiSend.ts`**
+- `sendMessage` L1 分支(`:392-404`):`state.queue.push(...)` → `await aiApi.outboxEnqueue(...)`,随后刷新本会话视图。
+- 删前端派发:`drainQueue`(`:308`)、`initDrainQueueListener`(`:607`)、`ai-drain-queue` 监听改由 `ai-outbox-changed`/`ai-outbox-sent` 驱动的**视图刷新**;`useAiEvents` 的 `AiCompleted` 不再发 `ai-drain-queue`。
+- 保留 `cancelQueued/clearQueue/editQueued/sendQueuedNow/tryForceSend`,后端代理为对应 IPC。
+- regenerate/editMessage 的「清本会话队列」改为 `ai_outbox_clear(convId)`(语义不变:用户显式动作允许清)。
+
+**新增 `src/composables/ai/useAiOutbox.ts`**:`state.queue` 保持为「本会话待发视图」数组(shape 兼容 `AiChat.vue`),由 `ai_outbox_list` 拉取 + 事件增量刷新;提供 `scheduleMessage(convId, text, ..., dueAt)`、`listAll()`、`reschedule/cancel` 等。
+
+**`src/stores/ai.ts`**:`queue` 字段类型不变(视图契约零破裂),来源改为 composable 写入。
+
+**Composer 入口 `src/components/ai/ChatInput.vue`**:发送按钮区(`:107-108`)旁加「定时」图标按钮 → 弹轻量时间选择(预设 5/30/60 分钟 + 自选时间)→ 调 `scheduleMessage`。空文本仅选时间时提示需输入内容。
+
+**列表 UI `src/components/AiChat.vue`**:
+- 现有内联队列条(`:78-113`)保留渲染本会话待发(含定时项可加 `🕐 时间` 角标 + 「改期」按钮)。
+- 新增**「定时列表」抽屉**:跨会话展示全部 `scheduled`(+可选 queue)项,列 时间/会话/摘要/状态,操作 查看/改期/取消/编辑。可复用 TaskDrawer 抽屉范式(`temp/S4-TaskDrawer跳转勘察-2026-10-03.md` 已有跳转勘察)。
+
+**启动恢复**:`useAiOutbox` 初始化时拉 `ai_outbox_list`;若存在恢复项,`ai-toast` 提示「已恢复 N 条待发」(参考 `notifyQueueCleared` 文案范式)。
+
+### 3.5 UI 形态示意(文字稿)
+
+```
+┌ ChatInput ─────────────────────────────┐
+│ [图片预览行] │
+│ ┌──────────────────────────┐ ⏰ ➤ │ ⏰=定时;➤=立即
+│ └──────────────────────────┘ │
+└────────────────────────────────────────┘
+ 点 ⏰ → 小面板: [5分钟] [30分钟] [1小时] [自选时间 ▾] (确认后入 outbox kind=scheduled)
+
+┌ 待发/定时列表(抽屉) ───────────────────┐
+│ 定时 (2) │
+│ 🕐 14:30 会话A 「跑一下构建…」 改期 ✕ │
+│ 🕐 明天9:00 会话B 「/status」 改期 ✕ │
+│ 待发 (1) │
+│ ⏳ 会话A 「继续」 立即 ✕ │
+└─────────────────────────────────────────┘
+```
+
+---
+
+## 四、与 todo 931 的共用关系(同源方案)
+
+- **同一持久层**:931 队列项与 935 定时项**同表 `ai_outbox`**,仅 `kind`/`due_at` 区分。931 不再单独造 KV/表。
+- **同一调度器**:队列=「`due_at` 为空、空闲即发」,定时=「`due_at` 到时、空闲即发」,共用 `outbox.rs` 派发循环与 `spawn_agentic_session`。
+- **共用修复点**:网络超时/重启不丢(§3.3 reconcile + §3.4 视图化)同时治 931;「错误不清队列」由「outbox 项仅 `sending→pending` 回退,不删」天然满足。
+- 分期上允许 **P0 先落 `scheduled`(935)**,`queue`(931 迁移)随后并入同表——同表即刻共用,队列侧前端派发下线放在 P1,降低单批爆炸半径。
+
+---
+
+## 五、分期与最小可用
+
+### P0 — 后端待发箱 + 定时(最小可用,独立可验收)
+- 新增:表 V51 + `models::AiOutboxRecord` + `crud/outbox_repo.rs`;`commands/ai/outbox.rs`(调度器+IPC+事件);`lib.rs` 挂载 `outbox::start` + `agentic/mod.rs:2917` 加 `outbox::notify_conv_idle` 钩子;`state.rs` 加 `outbox`。
+- 前端:`useAiOutbox.ts` + ChatInput ⏰ 入口 + 定时列表抽屉 + `api/ai.ts` 包装 + i18n。
+- 验收:§二「定时」两条 + 「多会话」+「不倒退」。此期**只服务 `kind='scheduled'`**;`queue` 仍走旧内存路径(未有回归)。
+
+### P1 — 队列并入 outbox(治 931)
+- 前端 `sendMessage` L1 改走 outbox;下线 `drainQueue`/`initDrainQueueListener`/`ai-drain-queue`;regenerate/edit/clear 接 IPC 清队。
+- 验收:§二「断网/超时」「重启」两条 + 既有队列 UI 交互(编辑/回填/立即发送/取消/超时强发)零弱化。
+
+### P2 — 进阶
+- 飞书通知(`df-outbox-notify-feishu`);过期窗口策略(拍板点 4);`kind='task'` 扩展位预留供 937(定时任务/工作流经同一调度器,派发目标改 task/workflow 而非 `spawn_agentic_session`)。
+
+---
+
+## 六、影响面(新能力引入评估纪律五维,`docs/todo.md:446`)
+
+- **兼容性**:纯新增表 + 新增 IPC/事件,旧库由 V51 幂等建表向上迁移,老数据零触碰;前端 `state.queue` shape 不变(视图契约)。迁移可断点续跑(逐条状态标记,无全局 COUNT 推算)、可逆(`DROP TABLE`+关开关)。旧 `ai-drain-queue` 事件在 P1 下线时保留一个版本的空发兜底,防旧前端混跑。
+- **破坏性**:不可逆操作仅「迁移 V51 建表」(可 `DROP` 回退);P1 下线上前端派发属行为变更,以开关 `df-outbox-enabled=false` 可回退旧路径(过渡期保留旧内存队列代码)。
+- **安全面**:无新端点/命令执行面。outbox 项若触发工具调用,仍走既有 `audit`/`approval`/边界模型闸门,**定时不等于放行**(与无人值守 L1 同级,不自动升档)。落盘内容(文本/图片 base64)属本地库既有信任域(与 `ai_messages` 同级)。改期/取消为本地 IPC。
+- **资源面**:DB 增长——`sent/canceled` 行建议保留(胜于删除,便于审计)但**须有上限**:图片 base64 项加体积上限(沿用前端 `PARTS_SIZE_BUDGET` 50MB 量级口径,超限入队即拒并提示);可加「已完成项保留 N 天」的清理点(默认可不做,登记为观测)。调度器 tick 默认 10s,配合 `Notify` 即时唤醒,常驻开销可忽略(低于 unattended 300s 轮询)。
+- **回退开关**:`df-outbox-enabled`(默认 true,关=调度停=项只入不发,可观测);`df-outbox-poll-secs`/`df-outbox-max-attempts` 热读。灰度顺序:P0 scheduled 先在本机自用验证 → P1 队列迁移 → P2 通知/937。观测指标:outbox 积压条数、`sending` 残留数、失败率、`due→sent` 延迟。
+
+---
+
+## 七、拍板点清单
+
+| # | 决策 | 选项 | 建议 |
+|---|------|------|------|
+| 1 | 调度落点与「进程退出」语义 | A 后端 tokio 常驻(关窗托盘化期间生效;进程退出期间不生效,重启追赶补发);B 前端定时器(仅界面活着时生效);C 注册 OS 级计划任务(进程死也触发) | **A**。与无人值守同形态、复用 `spawn_agentic_session`;明确「进程退出=不触发,重启补发」为本设计边界,C 作远期非目标(跨平台成本高,偏移本设计) |
+| 2 | 存储形态 | A 新表 `ai_outbox`;B 复用 `app_settings` KV | **A**。需按 `status+due_at` 索引扫描 + 逐条 CAS 改期/取消,KV 整块 JSON 读改写有并发覆盖风险且不可索引 |
+| 3 | 队列与定时是否统一实体 | A 统一并入 outbox(`kind`/`due_at` 区分),前端队列视图化; B 队列保前端内存 + 独立后端定时器 | **A**。931 根因就是内存+前端自驱,B 会留双调度(草率聚合);A 一次治两需求 |
+| 4 | 进程退出期间到期项的追赶策略 | A 全部到点即补发(迟到多久都补);B 迟到超阈值(如 >12h)标「已过期」+通知不补发;A+B 折衷 | **B(阈值建议 12h,值可调)**。补发全部易在长关机后制造批量突发发送;过期标记保证「不静默丢」(用户可见可手动重发),符合不静默丢失纪律 |
+| 5 | 定时消息是否允许携带图片 | A 允许(限体积);B 定时项禁图片仅文本 | **A(B 作兜底)**。图片 base64 落库有膨胀,但用户「设好图片定时发」是合理诉求;用体积上限而非一刀切禁用 |
diff --git a/docs/02-架构设计/专项设计/对话内定时延时任务-设计-2026-10-10.md b/docs/02-架构设计/专项设计/对话内定时延时任务-设计-2026-10-10.md
new file mode 100644
index 0000000..6511255
--- /dev/null
+++ b/docs/02-架构设计/专项设计/对话内定时延时任务-设计-2026-10-10.md
@@ -0,0 +1,254 @@
+# 对话内定时/延时任务 — 设计
+
+> 日期: 2026-10-10 | 状态: 设计稿(待拍板) | 需求锚点: `docs/todo.md:703` 条目 937 | 勘察: 只读实测(文件:行已核)
+> 目标一句话: 在对话里直接说「1 小时后跑 X / 明早 9 点把任务 A 推进到审查」,系统登记一条**持久化的 `kind='task'` 待发箱项**,到点自动执行(注入发起会话续跑),任务卡显示「定时中/已触发」,并可在统一「定时列表」里查看/改期/取消。
+> 边界: 仅设计,不改码。
+> **与 935 的关系(要害)**: 937 **不新造调度底座**。表 / 调度器 / IPC / 事件 / KV / 「定时列表」抽屉**全部复用** [`定时消息与待发箱持久化-设计-2026-10-10.md`](./定时消息与待发箱持久化-设计-2026-10-10.md)(该设计已覆盖 todo 935 定时消息 + 931 队列,并在其 §3.1 明确预留 `kind='task'` 给 937、§五 P2 交棒)。**本设计只定义 `kind='task'` 这一分支的语义 + AI 工具面 + 触发执行 + 权限边界**。
+> 关联: 并行化与异步唤醒-设计(§二 wake delay 型 / §6.3 挂机)/无人值守长任务-设计(TaskBoundary 边界)。
+
+---
+
+## 一、现状勘察(锚点实测)
+
+### 1.1 AI 工具面(新工具的正确落点)
+
+| 件 | 锚点 | 现状 |
+|----|------|------|
+| 注册宏 | `crates/df-ai/src/ai_tools_decl.rs` `declare_tool!` | 纯 `macro_rules!`,零新依赖;新工具**只加一个 `tools/<域>.rs` 文件** + 在对应 `register_*` 接一行 |
+| 注册总入口 | `src-tauri/src/commands/ai/tool_registry.rs:541` `build_ai_tool_registry` | `register_data_tools`(:735) 内 `register_task_tools`(:737)/`register_workflow_tools`(:739);二者已 thin 委托 `tools::task::register`(:767)/`tools::workflow::register`(:813) |
+| 任务工具 | `src-tauri/src/commands/ai/tools/task.rs` | 6 个:list_tasks(:40)/create_task(:72)/advance_task(:185,状态机推进唯一合法路径)/delete_task/get_task_count。**无任何「延后」维度** |
+| 工作流工具 | `src-tauri/src/commands/ai/tools/workflow.rs:35` `run_workflow` | High 风险;handler 是防御兜底(返 Err),**真正执行经 `ai_approve` → `execute_run_workflow_for_tool`**(`src-tauri/src/commands/ai/mod.rs:1367`)→ `run_workflow_inner`(`src-tauri/src/commands/workflow.rs:127`);空 dag + target_status 时按 `template_for`(`crates/df-nodes/src/task_workflow_templates.rs:28`)自动选模板 |
+| conv_id 透传先例 | `execute_run_workflow_for_tool`(`mod.rs:1367`) | AI 工具 handler 无 `State`,**发起会话 id 经调用方(task-local/参数)注入**——`schedule_task` 记录发起会话照此 |
+| 基线守卫 | `tool_registry.rs` 测试 `test_build_ai_tool_registry_baseline_tool_count` | 钉死工具名集合;**新增工具须同步该断言(+1)** |
+
+### 1.2 任务体系与推进链(被定时触发的目标能力,均已就位)
+
+| 件 | 锚点 | 现状 |
+|----|------|------|
+| 状态机 | `crates/df-nodes/src/task_state_machine.rs` | 8 态合法转换纯函数;done 唯一终态 |
+| 原子推进 | `crates/df-nodes/src/task_advance_node.rs:50` `advance_task_atomic` / `:335` `advance_task_with_parent` | 状态机校验 + CAS + review_rounds + 父聚合,已与 IPC/AI 工具三方同源 |
+
+结论: 「到点后要做的事」(推进任务/跑工作流/执行指令)**能力齐备**,缺的只是**外部时间驱动源**与**工具面的延时表达**。
+
+### 1.3 后端常驻调度器先例(unattended;935 已定 outbox 抄其形态)
+
+| 件 | 锚点 | 现状 |
+|----|------|------|
+| 常驻轮询 | `src-tauri/src/commands/ai/unattended.rs:508` `start`;`lib.rs:84` 挂载 | `async_runtime::spawn` + `interval.tick()` / `notify.notified()` `select!` |
+| 共享态/唤醒 | `unattended.rs:459` `EngineShared{notify: Notify, in_flight,…}`;`:483` `notify_batch_done` | 单在途 + 完成钩子即时唤醒 |
+| 开工链路 | `unattended.rs:847` `spawn_agentic_session`(`chat.rs:796`,10 参,`pin_active=false`) | 后台会话派发正典入口 |
+| 生命周期事实 | `lib.rs:614` `on_window_event` `prevent_close`+`hide` 托盘化 | **关窗=进程仍在=调度器照跑;进程退出=停,重启补发** |
+
+**关键缺口(937 视角)**: 引擎作业源是看板文件,**不落库、不看时间点**;真正解决「持久化 + 到点」的是 935 设计的 `ai_outbox`(见下节),937 直接搭车。
+
+### 1.4 存储 / 恢复 / 事件 / UI 样板(937 复用链)
+
+| 件 | 锚点 | 现状 / 复用点 |
+|----|------|--------------|
+| 迁移框架 | `crates/df-storage/src/migrations.rs:9` `run`;调度数组 `:108-121`(止于 `(50, migrate_v50)` @ `:1483`) | 追加 `(N, migrate_vN)` 即挂;**935 占 V51(建 `ai_outbox`),937 用 V52 加列** |
+| 加列样板 | `migrations.rs:1366` `migrate_v45` | `column_exists` 守卫 + `ALTER TABLE ADD COLUMN`,幂等可重跑;可 `DROP COLUMN` 回退 |
+| 启动恢复样板 | `audit/restore.rs:26` `restore_pending_approvals` | 从表重建内存态;outbox 的 `reset_stale_sending` 同款 |
+| 会话来源 | `models.rs:389` `AiConversationRecord.origin`(现仅 `"sub_agent"`) | 定时触发落后台会话时可用新值 `"schedule"` |
+| 事件 | `mod.rs:134` `AiChatEvent`(`#[serde(tag="type")]`) | 935 定的 `ai-outbox-changed/sent/failed` 直接复用 |
+| 卡片范式 | `src/components/ai/TaskParkedCard.vue` | 「定时任务卡」可照此(状态徽标 + 行为后更新) |
+
+### 1.5 缺口清单(937 专属,底座缺口由 935 覆盖)
+
+1. **工具面无延时维度**:LLM 无法表达「1 小时后」(`advance_task`/`run_workflow`/`create_task` 均即时)。
+2. **无 `kind='task'` 分支**:935 的 `ai_outbox` 表/调度器只服务消息派发,`kind='task'` 的**载荷字段**(结构化指令/任务关联/边界快照)与**派发目标**(注入会话 or 直推任务)**尚未定义**。
+3. **无触发执行的落点语义**:到点「谁来跑、跑哪个会话、结果给谁看」未定。
+4. **无对话内定时卡片**:工具卡如何呈现「定时中/已触发」未定。
+
+---
+
+## 二、目标与验收口径
+
+| 目标 | 验收口径(可实测) |
+|------|-------------------|
+| G1 对话内创建 | LLM 调 `schedule_task(instruction, delay_secs)`,对话出现**定时任务卡**(指令摘要 + 触发时刻/倒计时 + 「定时中」徽标);`ai_outbox` 落一行 `kind='task'`, status=pending |
+| G2 到点触发 | 到 `due_at` → 调度器把指令注入**发起会话**续跑(等价一条 user 消息注入),卡片变「已触发」,新 assistant 回复出现在原会话 |
+| G3 持久存活 | **应用关闭跨过 due_at**:关窗托盘化期间照常触发;进程真退出则重启后按 935 追赶策略(§四)补触发或标过期;卡片状态经重启不丢 |
+| G4 管理面 | 统一「定时列表」(935 抽屉,937 项并列显示,类型标「任务」)可查看/改期/取消;改期/取消即时写库刷新卡片 |
+| G5 边界合规 | 创建 = Medium 风险(走既有审批);**触发执行零旁路**——写/High 风险仍在触发会话内走既有 `audit`/`path_auth`;黑名单永不放行 |
+| G6 不打架 | 触发时发起会话正生成 → 轮界排队(不打断);会话已删/不可用 → 回落独立后台会话(origin=`schedule`) |
+| G7 零回归 | 既有 advance_task/run_workflow/create_task 行为不变;无 task 作业时调度器空转零副作用;935 的 `queue`/`scheduled` 分支不受影响 |
+
+---
+
+## 三、设计方案
+
+### 3.1 总体判断(先说结论)
+
+1. **新工具 `schedule_task`,不复用 `advance_task` 加调度字段。** `advance_task` 的契约是「此刻」推进状态机(CAS + `applied.matched_target` 断言,`tools/task.rs:185`),loop 收敛判定依赖它;改成「可能延后」会污染该断言并让「任务状态」与「调度时点」耦合。调度是正交能力:指令里可以*提到*任务,但调度本身不是状态迁移。
+2. **底座全复用 935 的 `ai_outbox` + `outbox.rs` 调度器,937 只加 `kind='task'` 分支。** 不新造表/不新造 loop,与需求「与 935 共用调度器」一致;亦避免「双调度器」的草率聚合。
+3. **触发执行的通用形态 = 注入发起会话(把指令当一条用户消息续跑)**,由触发后的 agentic loop 统一消化(它本就会 `advance_task`/`run_workflow`);确定性「直推任务状态」作 P2 快路径(§3.6、拍板点 4)。
+
+### 3.2 数据形态:复用 `ai_outbox` + 迁移 V52 增列
+
+935 已定义 `ai_outbox`(`定时消息…设计` §3.1,V51),其列 `text / skill / model_override / parts_json / spans_json / due_at / status / attempts` 面向消息。937 复用其 `text`(= 指令原文,供统一列表渲染摘要)、`due_at`(= 触发时刻)、`status`、`attempts`,并**新增三列**承载 task 专属载荷:
+
+```sql
+-- 迁移 V52(937):幂等 ALTER(照 migrate_v45 样板),可 DROP COLUMN 回退
+ALTER TABLE ai_outbox ADD COLUMN title TEXT; -- 卡片/列表标题(缺省取指令前 N 字)
+ALTER TABLE ai_outbox ADD COLUMN payload_json TEXT; -- 结构化载荷 {instruction, task_id?, target_status?}
+ALTER TABLE ai_outbox ADD COLUMN boundary_json TEXT; -- 触发执行继承的边界快照(可空=审批模式)
+```
+
+- `kind='task'` 行:`text`=指令人读文本(列表摘要用);`payload_json`=`{instruction, task_id?, target_status?}`(instruction 冗余存一份便于派发时直接取;task_id/target_status 供 P2 直推快路径)。
+- **不新增独立表**。`AiOutboxRecord`(`定时消息…设计` §3.1)对应加三个 `Option` 字段;`outbox_repo.rs` 的 insert/读方法带这三列(其余复用)。
+- 迁移编号:935 占 V51,937 占 V52;若两需求同批落地,可合入一个迁移函数一次到位(建表 + 增列),但保持「V51 建表 / V52 增列」两段亦可(幂等,顺序无关)。
+
+### 3.3 工具面:新增 `schedule_task`(`declare_tool!`,Medium)
+
+新文件 `src-tauri/src/commands/ai/tools/schedule.rs`,仅一个工具;`tool_registry.rs:737` 旁接线(`register_schedule_tools`),基线测试 +1。
+
+```rust
+declare_tool!(
+ registry, db: Arc,
+ "schedule_task",
+ "登记一个定时/延时执行的任务作业:到点后在发起会话里自动执行 instruction(等价到点时我把这句话重新对你说一遍)。\
+ 用 delay_secs 表达相对延时(如「1 小时后」=3600),或用 at 表达绝对时刻(RFC3339 或 epoch ms)。二者至少给一个,同时给以 at 为准。\
+ 到点执行受既有审批/路径授权约束,不绕过任何安全闸。返回 job_id 与触发时刻。",
+ RiskLevel::Medium, // 登记未来自动执行,须人工知情(对齐 advance_task 的 Medium)
+ meta: ToolMeta {
+ entity_action: Some(("schedule", "create")),
+ approval_hint: Some(("创建定时任务:{}", &["instruction"])),
+ display_verb: Some("创建定时任务"),
+ ..Default::default()
+ },
+ schema: object_schema(vec![
+ ("instruction", "string", true), // 到点要执行的自然语言任务(必填)
+ ("delay_secs", "integer", false), // 相对延时(秒),与 at 至少一个
+ ("at", "string", false), // 绝对时刻 RFC3339;优先于 delay_secs
+ ("title", "string", false), // 卡片标题(缺省取 instruction 前 N 字)
+ ]),
+ args => {
+ // 1. 解析 instruction(必填)/delay_secs/at/title → 折算 due_at(epoch ms)
+ // 2. 发起 conv_id:handler 无 State → 经调用方注入(task-local,对齐 execute_run_workflow_for_tool mod.rs:1367)
+ // 3. outbox_repo::insert(kind="task", text=instruction, payload_json={instruction}, title, due_at, status=pending)
+ // 4. 唤醒调度器 notify → emit("ai-outbox-changed") 前端插卡
+ // 5. 返回 { job_id, due_at }
+ }
+);
+```
+
+- **相对 `delay_secs` 为主**(免时区/解析歧义);`at` 为便利补充(LLM 依 system prompt 里的当前时间折算)。
+- **conv_id**:AI 工具 handler 是无 `State` 闭包;发起会话 id 沿用 workflow 工具先例经 task-local 注入(`execute_run_workflow_for_tool` 由 `ai_approve` 带 `conv_id` 调,`mod.rs:1367`)。`ai_outbox.conversation_id` 为 `NOT NULL`,而 AI 工具场景必在会话内,故恒可填;极少数拿不到 conv_id 的路径退回「前端/列表入口创建」(935 §3.4 composer 入口)。
+
+### 3.4 调度器:复用 935 `outbox.rs`,加 `kind='task'` 派发分支
+
+935 的调度循环(`定时消息…设计` §3.3)按 `kind` 分派。937 **只加一个分支**:
+
+```
+// outbox.rs list_due 取项后的派发(schema 由 935 定义,此处补 kind 分支)
+match item.kind.as_str() {
+ "queue" | "scheduled" => { /* 935 既有:spawn_agentic_session(text,...) */ }
+ "task" => fire_task_job(app, state, &item), // ← 937 新增(§3.5)
+ other => { warn!(unknown kind); }
+}
+```
+
+- **CAS/并发/在途/唤醒钩子全部复用** 935:同会话忙(`can_accept_request=false`)→ 本轮跳过下次再取(不丢);`notify_conv_idle` 完成钩子即时续发;`claim` CAS 防双取;`reset_stale_sending` 启动复位。
+- **`df-outbox-enabled` 复用**:关 = 所有 kind 冻结(937 的 task 也冻结),一致。
+
+### 3.5 触发执行语义(`fire_task_job`,937 核心)
+
+1. **注入文本**:`[定时任务触发] {instruction}`(固定前缀族,便于前端识别 + 完成卡归属)。
+2. **落点选择**:
+ - 发起会话存在且 `can_accept_request` → **注入发起会话**:走 `spawn_agentic_session(text=<前缀+instruction>, …, Some(conv), …, pin_active=false)`(与 935 派发同源,`chat.rs:796`)。
+ - 发起会话正生成 → **轮界排队**:`revert_pending`(复用 935 的忙回退),下一 tick 重试(不打断流)。
+ - 发起会话已删/不存在 → **回落独立后台会话**:`Some(new_id())` + `origin="schedule"`,不静默丢。
+3. **落库**:`mark_sent`(复用 935 语义:`spawn` 返回 Ok 即受理成功)+ `emit("ai-outbox-sent", {id, conv})`。
+4. **失败**:spawn 前置失败(provider 未配/永久 4xx)→ `mark_failed`(`permament(error)` 复用 935 判据)+ 通知;非永久 → `revert_pending` 下轮重试。**LLM 轮次内的失败不回流**(与 935 同界定,交既有 loop 自愈)。
+
+### 3.6 关闭/重启行为(复用 935 追赶策略,937 不另立规则)
+
+- 迁移/恢复:复用 935 的 `reset_stale_sending`(启动把残留 `sending` → `pending`);调度器只认 `due_at <= now`,故**进程退出期间到期的 task 项重启后立即命中**。
+- **迟到追赶策略 = 复用 935 拍板点 4(阈值 12h,建议值)**:迟到 ≤ 阈值 → 补触发;迟到 > 阈值 → 标过期(`status='expired'`? 935 用 `failed`/`canceled`,建议新增 `expired` 或用 `failed`+`last_error="missed"`)+ 通知,列表可见可手动重排。**937 不引入 24h 另一套阈值**,统一走 `df-outbox-*` 配置(如需 task 专属阈值,加 KV `df-outbox-task-missed-grace-secs`,默认同 12h)。
+- 有界性:任何 task 作业按 `due_at + 宽限` 有界,不无限滞留。
+
+### 3.7 权限与边界(与既有 audit/boundary 链的关系)
+
+- **创建**:`schedule_task` Medium → 走既有 `audit/approval`(三档/边界会话按现行规则);**创建 ≠ 授权执行**。
+- **触发执行**:走**同一** pipeline。`fire_task_job` 只把一句话送进会话,此后 agentic loop 里每个工具照常过 `audit`(写/High 审批)、`path_auth`(路径授权)、黑名单拒绝。**不新增任何旁路,不预授权**(红线)。
+- **边界快照 `boundary_json`(可选)**:若发起会话已声明 `TaskBoundary`(`boundary.rs`,risk_ceiling≤Medium,落 `ai_conversations.boundary_json` V45),创建时可快照;触发时注入触发会话 → 边界内自动放行、边界外照旧拒绝。未声明 → 不写快照 → 触发走普通审批(首个写操作停靠等人工 + 通知)。
+- **黑名单/破坏性命令**:永不放行(同 all 档/子代理先例)。
+- **MCP 面(独立表面,首期不接)**:`crates/df-mcp/src/tools.rs:101` `all_tools()` 是**独立硬编码清单**(非 AI registry 派生);首期不把 `schedule_task` 加入 MCP(定时依赖本机 GUI 进程 + 调度 loop,远程场景语义不闭合),MCP 侧零改动。
+
+### 3.8 UI 形态(复用 935 抽屉,937 只加卡片)
+
+- **对话内定时任务卡(工具卡变体)**:`useToolCard.ts:513` 登记 `schedule_task` formatter;卡面 = 标题 + 指令摘要 + 「⏰ 触发于 MM-DD HH:mm(还有 X 分钟)」+ 状态徽标(`定时中`/`已触发`/`已取消`/`已过期`/`失败`)。**状态随 `ai-outbox-changed/sent/failed` 事件更新**(到点晚于工具返回,不能靠静态 tool_result)。形态照 `TaskParkedCard.vue`(per-conv 卡,行为后摘/更新)。
+- **统一「定时列表」抽屉 = 复用 935**(`定时消息…设计` §3.4):937 的 task 项与 935 的 scheduled/queue 项**同列同抽屉**,仅多一列「类型=任务/消息」+ task 项显示关联任务(若有)。改期/取消复用 935 的 `ai_outbox_reschedule/cancel` IPC。
+- **可选 task 专属入口**:ChatInput ⏰ 入口(935 已加,`ChatInput.vue:107`)+ 若要在列表里「手动新增定时任务」,复用同一 IPC(`ai_outbox_enqueue` 加 `kind='task'`+payload,或 937 加 `ai_schedule_task` 薄封装,见拍板点)。
+- 设置页/TopBar 徽标:复用 935 的 `df-outbox-*` 区块与状态查询,不另开。
+
+### 3.9 与既有链路的关系
+
+- **worktree / 评审**:触发执行 = 一次普通 agentic run,worktree/review/run_workflow 全照旧;指令若含「推进任务/跑工作流」,由触发后的 loop 调 `advance_task`/`run_workflow`(工作流仍走 High 审批)。
+- **无人值守引擎**:两者独立 loop、独立作业源;挂机在途(`in_flight`)与 outbox 派发各管各 conv,不冲突。
+- **P2 快路径(可选)**:确定性作业「到点直推任务状态」(`payload_json.task_id`+`target_status`)可**不起 LLM**,直接 `advance_task_with_parent`(`task_advance_node.rs:335`)——省 token、免 LLM(状态机推进本身非破坏);须在创建时校验状态机合法性。见拍板点 4。
+
+---
+
+## 四、分期与最小可用
+
+| 期 | 范围 | 交付 | 依赖 |
+|----|------|------|------|
+| **P1(最小可用)** | 工具面 + task 分支闭环 | V52 增列 + `AiOutboxRecord` 三字段 + repo + `schedule_task` 工具 + `fire_task_job` 注入发起会话 + 启动追赶快照 + 工具卡徽标 | **935 P0 底座(`ai_outbox` 表 + `outbox.rs` 调度器 + 列表抽屉)** |
+| **P2(管理/快路径)** | 列表类型区分 + 直推 | 列表 task 项类型标 + 关联任务显示;可选确定性直推快路径 + KV 阈值 | P1 稳定 |
+| **P3** | 边界快照 / 与挂机联动 | `boundary_json` 快照注入触发执行;挂机自领定时任务(可选) | P2 + 边界模型落地 |
+
+**最小可用定义**: P1 完成即达标——「对话里说 1 小时后跑 X → 卡片定时中 → 到点(含重启补触发)在原会话自动执行」。
+**关键依赖**: 937 的 `kind='task'` 分支**不能脱离 935 的 `ai_outbox` 底座独立交付**;两需求应**同批或先后紧邻落地**,先 935 P0(表+调度器)后接 937 分支。这是本设计对编排的硬约束。
+
+---
+
+## 五、影响面(新能力引入评估纪律 · 五维)
+
+| 维度 | 判定 |
+|------|------|
+| **兼容性** | 复用 `ai_outbox`(V51)+ 937 加三列(V52,`column_exists` 幂等,可 `DROP` 回退);既有工具/任务/工作流零改动。**基线测试 `test_build_ai_tool_registry_baseline_tool_count` 须 +1(schedule_task)**。935 的 `queue`/`scheduled` 分支不受影响(仅多一个 `match` 分支)。**PASS(带迁移)** |
+| **破坏性** | 无破坏性数据操作;不改 advance_task/run_workflow 语义;触发=发消息/起会话,可逆(取消/停止)。**PASS(低)** |
+| **安全面** | 新攻击面 = 「未来自动执行 LLM/用户给定指令」。缓解:①创建 Medium 须知情审批 ②触发执行**零旁路**,写/High/路径/黑名单全走既有闸(G5/G6)③边界快照仅在会话已声明边界时生效(不放大权限)④可随时取消。**红线**:不得为「定时」开任何 approval/path 旁路。**PASS(受控)** |
+| **资源面** | 复用 935 表与 10s tick loop;937 只多三列 + 一个 match 分支,资源增量≈0。task 项随 outbox 统一设上限(935 已定 sent/canceled 有界策略)。**PASS** |
+| **回退开关** | 复用 `df-outbox-enabled`(置 false → task 项只入不发,冻结);V52 可 `DROP COLUMN` 回退。KV 热读免重启。**PASS(带开关)** |
+
+---
+
+## 六、拍板点(需人定,附建议)
+
+1. **触发执行落点**:注入**发起会话**(结果可见、上下文连续,贴「对话内创建」语义)【建议默认】 vs 独立后台会话(不打扰但结果另起)。建议前者,发起会话忙则轮界排队、不可用则回落后台会话(§3.5 两全)。
+2. **与 935 的落地顺序**:937 的 `kind='task'` 分支**依附 935 的 `ai_outbox` 底座**——先 935 P0 后接 937【建议】 vs 937 自建一套表/调度器(违反「共用调度器」且制造双调度,否决) vs 两设计合并为一次实施(978/935/937 同批)。建议前者,把「共用底座」写死为编排约束。
+3. **触发执行授权**:默认**普通审批模式**(未声明边界的发起会话 → 触发后首个写操作停靠等人工并通知)【建议,最安全】 vs 创建即固化「完全信任」(攻击面大) vs 仅当发起会话已声明 `TaskBoundary` 时携带边界快照(边界内免打扰)。建议:默认普通审批 + 「有边界则快照」并存。
+4. **快路径**:起步只做 `instruction`(通用,触发后 loop 消化一切)【建议】 vs 起步即带 `task_id`+`target_status` 确定性直推(省 LLM/免审批,但限死用途、需额外状态机校验)。建议 instruction 起步,直推作 P2(§3.9)。
+5. **迟到追赶阈值**:复用 935 拍板点 4 的统一阈值(建议 12h)【建议】 vs 为 task 单设更短阈值(定时任务含副作用,可能希望更保守)。建议统一阈值起步,观测后如需再拆 KV。
+
+---
+
+## 七、明确不做
+
+- 不做 cron 表达式/周期重复任务(首期只做**一次性**定时;周期任务=挂机模式职责,另议)。
+- 不做跨设备/云端定时(复用 935 边界:关窗托盘化期间生效,进程退出期间不触发,重启补发)。
+- 不做定时执行结果的独立推送通道(复用 `notify_session_event` + 飞书,与 935 同出口)。
+- 不为定时开任何 approval/path 旁路(红线)。
+- 不新造调度底座(表/循环/IPC/事件/抽屉全复用 935)。
+
+---
+
+## 附:实施落点清单(供实施会话直接取用;底座项见 935 设计,此处只列 937 增量)
+
+| 文件 | 改动 |
+|------|------|
+| `crates/df-storage/src/migrations.rs` | 增 `migrate_v52`(3 个 `ALTER TABLE ai_outbox ADD COLUMN`,幂等);调度数组追加 `(52, migrate_v52)`;补幂等单测。**注意:V51 由 935 占,顺序不得换** |
+| `crates/df-storage/src/models.rs` | `AiOutboxRecord`(**935 已加**)再补 `title/payload_json/boundary_json: Option` |
+| `crates/df-storage/src/crud/outbox_repo.rs`(**935 已建**) | insert/读方法带三新列;`list_by_status`/`list_due` 复用 |
+| `src-tauri/src/commands/ai/tools/schedule.rs`(新) | `schedule_task` 工具(`declare_tool!`,Medium);`tool_registry.rs:737` 旁接线 + 基线测试 +1 |
+| `src-tauri/src/commands/ai/outbox.rs`(**935 已建**) | 调度派发 `match kind` 加 `"task" => fire_task_job(...)`;`fire_task_job`(§3.5)+ 追赶快照 |
+| `src-tauri/src/commands/ai/mod.rs` | 事件复用 `ai-outbox-changed/sent/failed`(935 已定);如卡片需状态细分可加可选字段,不新事件名 |
+| `src-tauri/src/lib.rs` | `outbox::start` 由 935 挂载;937 无需另挂 |
+| `src/composables/ai/useToolCard.ts` | `:513` 映射增 `schedule_task` formatter(定时任务卡) |
+| `src/components/ai/` | 定时任务卡(仿 `TaskParkedCard.vue`);列表抽屉 task 项类型标(复用 935 抽屉) |
+| `src/api/ai.ts` | 复用 935 `ai_outbox_*` 封装;若新增「手动建定时任务」入口再加薄封装 |
+| `src/i18n/zh-CN/*` + `en/*` | 卡片状态词(`定时中`/`已触发`/`已过期`…)/类型「任务」 |
+
+> 参照档: `定时消息与待发箱持久化-设计-2026-10-10.md`(935/931,**共用底座,必读**)· `无人值守长任务-设计-2026-10-04.md`(结构/详略)· `并行化与异步唤醒-设计-2026-10-04.md`(§二 wake delay 型/§6.3 挂机)· `输入框列表预览与自动编号-设计-2026-10-10.md`(同目录同日写法/五维自评)。
diff --git a/docs/02-架构设计/专项设计/消息渲染管线重构-设计-2026-10-10.md b/docs/02-架构设计/专项设计/消息渲染管线重构-设计-2026-10-10.md
new file mode 100644
index 0000000..15d6cb1
--- /dev/null
+++ b/docs/02-架构设计/专项设计/消息渲染管线重构-设计-2026-10-10.md
@@ -0,0 +1,245 @@
+# 消息渲染管线重构 — 设计
+
+> 日期: 2026-10-10 | 状态: 设计稿(待拍板) | 勘察: 只读实测锚点(全仓零 `v-memo`,实测 `grep -c v-memo = 0`)
+> 对象: `src/components/ai/MessageList.vue`(3041→实测 **3054 行** God 组件)
+> 目标(用户原话转译): 长会话流式掉帧根治 —— 拆分 God 组件、渲染记忆化、引入 `v-memo`、去 `JSON.stringify` 指纹、流式帧降载。
+> 说明: 本专题为**只读走查 + 设计**,不含代码实施;所有行号均为实测锚点(文件以 `E:/wk-lab/devflow` 为根)。
+
+---
+
+## 一、现状勘察(锚点实测)
+
+### 1.1 文件构成(3054 行 = script 1476 + template 460 + style 1117)
+
+| 段 | 行区间 | 行数 | 备注 |
+|----|--------|------|------|
+| `