feat: Docker 容器化、Cookie 鉴权、Coordinator 编排、FTS5 搜索与 P1-P3 全面收尾

Docker 容器化部署
  - 提供 Mode A (Alpine musl, ~23MB) 和 Mode B (Distroless glibc, ~87MB)
    两种镜像,Docker Compose 一键启动
  - build.rs 支持 SKIP_DASHBOARD_BUILD 跳过前端构建
  - 国内镜像加速 (npm/apt/apk) 通过 USE_MIRRORS build-arg 控制

  安全:Cookie-Based 鉴权系统
  - HttpOnly/SameSite=Strict Cookie 会话管理(24h 过期自动清理)
  - 登录/登出/验证接口 + 中间件注入
  - 前端登录页面 + 退出按钮
  - 三层 CORS:localhost 鉴权 / 全放通 bookmarklet / 受保护路由
  - 书签脚本 fetch 添加 credentials:'include'

  Coordinator 模式 (P2)
  - 4 个 meta-tool (delegate_task/check_task/task_stop/synthesize)
  - WorkerPool + Semaphore 并发控制 + 超时保护
  - 前端协调者模式开关

  Hook 系统:UserPromptSubmit 事件 (P2)
  - 第 13 个生命周期事件,fire-and-forget 审计

  FTS5 全文搜索 (P3)
  - agent_sessions_fts + agent_messages_fts 虚拟表
  - search_history Agent 工具 + /api/search/history HTTP 接口
  - 前端防抖搜索框 + 仅当前会话筛选

  工具加载优化 (P3)
  - defer_loading 延迟加载 (7 个重型工具)
  - is_readonly 只读标记 (9 个查询工具)
  - classifier_summary 工具目录供 LLM 按需判断

  模型回退策略 (P3)
  - LLM_FALLBACK_MODEL 优先回退 + LLM_FALLBACK_CHAIN 链式轮换
  - LlmClient model 改为 Arc<RwLock> 支持运行时切换
  - 连续 3 次过载后自动切换

  压缩记忆桥接 (P3)
  - 压缩丢弃消息 → 子代理提取持久记忆 (extract_memories_from_compaction)

  git2 依赖修复
  - 切换到 vendored-libgit2,消除 OpenSSL 系统依赖
This commit is contained in:
fmq
2026-06-23 20:22:06 +08:00
parent 698d007f39
commit cec4b8cf7b
58 changed files with 3366 additions and 345 deletions
+58
View File
@@ -311,6 +311,50 @@ sequenceDiagram
- `main_agent_saved_this_session == true`(主代理已通过 `save_memory` 工具写入)
- 子代理工具集为受限集(仅 4 个只读工具 + `save_memory`),不能执行 bash、不能搜索论文
## 压缩记忆桥接 (`compact.rs`, P3)
上下文压缩(compaction)丢弃旧消息时,其中可能包含有长期价值的科研结论和用户偏好。`extract_memories_from_compaction()` 在压缩后自动从丢弃的消息中提取持久记忆,将"即将消失的上下文"转化为"跨会话可用的记忆"。
```mermaid
sequenceDiagram
participant RT as AgentRuntime
participant Cmp as compact.rs
participant Ext as extract_memories_from_compaction
participant Sub as SubAgentRunner
participant Mgr as MemoryManager
RT->>Cmp: compress_context_with_hooks_and_log(messages, ...)
Cmp-->>RT: 压缩完成,旧消息将从上下文中移除
RT->>Ext: extract_memories_from_compaction(before_messages, sid, mgr, app_state)
Ext->>Ext: 检查 EXTRACT_MEMORY_ENABLED → false 则 return
Ext->>Ext: 过滤非系统消息 → 每条截断 400 字符 → 拼接
alt 拼接文本 < 300 字符
Ext->>Ext: return (太少不值得提取)
end
Ext->>Mgr: lock() → build_manifest_preview() 获取现有记忆清单
Ext->>Ext: 构建受限 ToolRegistry (仅 SaveMemoryTool)
Ext->>Sub: SubAgentRunner.run(EXTRACTION_SYSTEM_PROMPT, prompt, max_steps=3)
Note over Ext,Sub: tokio::spawn (fire-and-forget, 不阻塞压缩/ReAct 循环)
Sub-->>Mgr: save_memory() 写入提取结果
```
**与 `run_extraction()` 的区别**
| 维度 | `run_extraction()` (会话结束) | `extract_memories_from_compaction()` (压缩桥接) |
|:---|:---|:---|
| 触发时机 | `finalize_turn()` 会话收尾 | `compress_and_restore()` 压缩完成后 |
| 数据来源 | 完整对话历史(已持久化到 DB) | 压缩前即将被丢弃的消息快照 |
| 消息范围 | 系统 + 用户 + 助手消息 | 仅用户 + 助手 + 工具(去除 system |
| 节流机制 | `ExtractionTracker` 轮次计数 | 仅靠 `EXTRACT_MEMORY_ENABLED` 开关 |
| 阻塞行为 | fire-and-forget | fire-and-forget |
| 子代理工具集 | 4 只读工具 + `save_memory` | 仅 `save_memory`(最小化成本) |
| 最小内容阈值 | 无显式阈值 | 拼接文本 < 300 字符跳过 |
**设计原理**
- 压缩是最佳记忆提取时机——此时旧消息尚未被物理删除但即将从上下文中移除,记忆系统可以在"内容消失前最后一刻"将其抢救出来
- 截断每条消息到 400 字符平衡了信息完整性和子代理 prompt 长度
- fire-and-forget 确保压缩不因 LLM 调用延迟而阻塞主 ReAct 循环
## 时效性与衰减系统
**指数时间衰减** (`decay.rs`)
@@ -403,6 +447,20 @@ score = e^(-λ × days_old)
→ mark_main_agent_wrote()
→ 返回: 操作状态 + 质量警告 + 重复提示 + manifest 清单
会话中(压缩时) ← P3 桥接
compact.rs: compress_context_with_hooks_and_log(messages, ...)
→ 压缩快照前捕获消息副本 (pre_compact_snapshot)
→ 执行压缩(snip/auto/aggro_micro
→ extract_memories_from_compaction(snapshot, sid, mgr, app_state)
→ 检查 EXTRACT_MEMORY_ENABLED → false 则 return
→ 过滤非系统消息 + 截断 400 字符 + 拼接
→ 拼接文本 < 300 字符 → return (太少不值得提取)
→ tokio::spawn(子代理)
→ build_manifest_preview() 获取现有清单
→ SubAgentRunner(仅 SaveMemoryTool, max_steps=3)
→ save_memory() 写入提取的记忆
→ 恢复文件缓存(不等待子代理完成)
会话结束
finalize.rs: finalize_turn()
→ 更新 agent_sessions (turn_count, metrics)