541 Commits

Author SHA1 Message Date
oudecheng
924017fe7b fix(web_fetch): 加固 SSRF 防护,对齐 http_request 实现
修复 extract_host 的 4 处 host 解析缺口,使 web_fetch 与
http_request 的内网拦截策略完全一致:

- 拒绝 userinfo(authority 中的 "@"):防止 http://evil.com@127.0.0.1/
  以 evil.com 作为表面 host 绕过检查,实际请求发往 127.0.0.1
- 拒绝 IPv6 字面量(authority 以 "[" 开头):防止 http://[::1]/
  被拆分为 "[" 导致 IpAddr::parse 失败而放行
- trim_end_matches('.') 规范化末尾点:防止 http://127.0.0.1./
  因标准库解析失败绕过检查(reqwest 视为 FQDN 解析为 loopback)
- is_private_host 补充 is_broadcast/is_multicast:拦截
  255.255.255.255 与 224.0.0.1 等广播/组播地址

新增 5 个针对性测试覆盖以上场景。
2026-08-04 15:17:52 +08:00
oudecheng
134aa073d5 docs: CHANGELOG 补充 LLM 重试机制条目并修正 commit 计数 2026-08-04 13:10:59 +08:00
oudecheng
949ba650ee chore: 删除临时计划文件 PLAN.md 2026-08-04 12:53:49 +08:00
oudecheng
457f6b2408 feat(retry): 为 LLM 主调用添加可配置重试机制,默认 3 次
- config: ProviderConfig 增加 max_retries 字段(serde default=3,向后兼容)
- config: LLMProviderConfig 透传 max_retries,不进 ProviderRuntimeConfig(保持 provider 构造包纯净)
- agent: AgentRuntimeConfig 增加 max_retries,归属 agent 行为层
- agent_loop: 流式 + summary 两个调用点实现重试循环
  - 指数退避 1s/2s/4s,仅对 429/502/503/504/timeout/connection reset 重试
  - 流式仅在未 emit delta 时重试(AtomicBool 跟踪),避免重复输出
  - 退避 sleep 期间响应 cancel_signal,取消优先
- 前端: ProviderConfig 类型和表单增加 max_retries 字段
- 测试: 7 个单元测试(3 判定 + 4 行为),540 个 lib 测试全绿
2026-08-04 12:53:33 +08:00
oudecheng
6901659849 fix(mcp): 限制 server 名称字符并兜底 tool_name 清洗
OpenAI 要求 function name 匹配 ^[a-zA-Z0-9_-]+$,否则整个请求 400。MCP 工具名由 mcp_{server_key}_{tool_name} 拼成,两个输入源:server_key 用户可控(前端 addMcp/renameMcp 正则校验 + toast),tool_name 由 MCP server 上报(后端 sanitize_tool_name 替换非法字符为 _)。

前端:MCP 卡片头部改用 MapEntryHeader 支持点击重命名,状态指示灯移到上方独立行。顺手修了 MapEntryHeader 进入编辑时 val 未同步当前 name 的 bug。

后端:保留 server_key 和 tool_name 原值用于路由,仅清洗 LLM 可见的 full_name,并在发生清洗时打 warn 日志便于定位。
2026-08-04 11:17:41 +08:00
oudecheng
14d903e067 fix: Agent 执行与显示层解耦,修复锁屏冻结根因
浏览器锁屏导致 WebSocket 半死,ws_sender.send().await 永久阻塞,
级联阻塞 dispatcher → MessageBus → Agent Loop,后端停止执行直到解锁。

基于第一性原理建立"执行-显示解耦"原则:agent 执行只依赖 SQLite
持久化,实时广播是可丢弃的最佳努力通道。

核心改动:
- MessageBus::publish_outbound 由 send().await 改为 try_send(),
  bus 满时丢弃消息并告警,agent 不再被显示层阻塞
- WebSocket writer task 包裹 30s 超时,使用每连接独立的
  CancellationToken(非共享 CliChannel 级 token),避免一个连接
  超时关闭所有连接;writer 退出时 cancel 通知主 loop 退出
- CliChannel::send 由 send().await 改为 try_send(),避免 dispatcher
  单线程被卡住的 writer 阻塞 37s
- 全仓 13 处 publish_outbound 调用统一区分 Dropped(warn)/Closed(error)
- scheduler 3 处 ? 改为 warn,避免 Dropped 触发 misfire 重试风暴
- 前端 WebSocket 添加 25s ping + 指数退避重连(3s→60s封顶)
- 前端 session_list 区分重连恢复/首次连接,重连时保留 messages
  并刷新 topic 列表

经五轮对抗性审查验证,修复了共享 cancel token、dispatcher 阻塞、
load_chat_messages 跨 topic 污染、原 session 删除后状态不一致等
回归问题。

同时升级版本号至 0.3.0 并更新 CHANGELOG。

验证:cargo clippy --all-targets --all-features ✓
      npm run build ✓ | useChat.test.ts 13 passed ✓
2026-08-04 10:55:52 +08:00
oudecheng
1e2d64e28b fix(web_fetch): 禁用 HTTP 重定向,防止 SSRF 重定向绕过
validate_url 只校验初始 URL 的 host,若跟随 302 跳转,攻击者可用公网 URL
重定向到 169.254.169.254(云元数据)或 127.0.0.1 等内网地址,绕过 is_private_host
的 SSRF 防护。与 http_request 工具保持一致,使用 redirect::Policy::none()。
2026-08-04 08:25:57 +08:00
oudecheng
94a0dd0169 ci: 扩大构建与测试覆盖范围,纳入集成测试与二进制入口
cargo build --lib 改为 cargo build,确保 main.rs(gateway 二进制入口)也被编译验证;
cargo test --lib 改为 cargo test,纳入 tests/ 目录的集成测试。
test_request_format.rs 的 8 个序列化测试此前从未在 CI 中运行,现在会实际执行;
test_integration.rs / test_tool_calling.rs 中的 #[ignore] 测试会被编译但跳过执行(需真实 API key)。
2026-08-04 08:09:51 +08:00
oudecheng
b790a39c1f fix(cargo): 修正依赖分类错误,将跨平台依赖移出 windows target 段
rmcp/schemars/http/tower-http/rust-embed 均为跨平台 crate 且在源码中被无条件引用,错放在 [target.'cfg(windows)'.dependencies] 下导致 Linux 构建必然失败。仅 windows-sys 是真正的 Windows 专用依赖,保留在 target 段。
2026-08-04 07:58:51 +08:00
oudecheng
cd314742ad fix(test): 修复 anthropic 错误链嵌套测试为真正的 source 链
对抗性审查发现 test_format_error_chain_nested 是无效测试:
声称测嵌套错误链但实际没构造 source 链,inner 变量被 let _ 抑制。
改用 thiserror 构造真正的 #[source] 嵌套错误,验证 'caused by' 拼接。
2026-08-03 23:47:43 +08:00
oudecheng
e6b2fcfb6d docs+test: 补充架构文档与 anthropic provider 单测
P3 文档:
- 新增 ARCHITECTURE.md,聚焦数据流与 7 个关键设计决策
  (MessageBus 解耦/SessionPool 隔离/AgentLoop 循环/SQLite 池化/重启机制/嵌入静态文件/Safety Guard)
- 不写代码导读,只写'为什么这样设计',代码是唯一真相源

P2 测试:
- src/providers/anthropic.rs 曾零测试(396 行),补 15 个纯函数单测
- 覆盖:data URL 解析、图片过滤逻辑、内部字段过滤、响应反序列化、错误链格式化
- providers 模块测试密度 31% -> 提升,重点补齐 anthropic 空白
2026-08-03 23:45:41 +08:00
oudecheng
c724bbf864 chore(web): prettier 一次性格式化并在 CI 启用 format:check
对 47 个前端文件统一执行 npm run format,消除存量格式差异。
随后在 CI 与 Makefile check 中启用 format:check,确保后续提交强制遵守 prettier 风格。
2026-08-03 23:25:22 +08:00
oudecheng
cda14360af chore: 建立工程化基线(rustfmt + clippy + CI + eslint + prettier)
配置:
- rustfmt.toml: 固化 max_width=100 / 4 空格缩进,cargo fmt 全量格式化
- Cargo.toml: 配置 [lints.rust] 与 [lints.clippy] 渐进式规则
- .github/workflows/ci.yml: Rust(fmt+clippy+test) + 前端(eslint+tsc+test) 双平台 CI
- Makefile: 新增 check/fmt/fix 目标,clippy 对齐 --all-targets --all-features
- web: eslint flat config + prettier 配置 + package.json 脚本与依赖
- src/main.rs: loop→while 修复 clippy::never_loop

对抗性审查发现并修复:
- eslint 缺 caughtErrorsIgnorePattern 导致 catch(_) 误报为 error
- 前端 lint 未接入 CI,现已补上 Lint 步骤
- Makefile 与 CI 的 clippy flags 不一致,已对齐
2026-08-03 23:24:02 +08:00
oudecheng
f37a5ffe6e fix(platform): 收紧 safety guard 的 format 正则并按平台分组
原 \bformat\s+ 正则精度不足,会误伤 dart format、buf format、
pytest --format 等合法命令;Remove-Item 大写正则经 to_lowercase
后永远匹配不到,是潜在 bug。

改动:
- format 正则收紧为 \bformat\s+.*[a-z]:,要求出现盘符才拦截
- 按 Platform 分组注入规则,Unix 不再注入 Windows 专用规则
- Remove-Item 正则改为小写,与 guard_command 的大小写处理一致
- 平台相关测试加 #[cfg] 属性,避免跨平台失败
- 新增 test_format_pattern_precision 覆盖危险命令与误伤场景
- 新增 test_legitimate_format_commands_not_blocked 回归测试

验证:
- cargo test --lib platform::  11/11 通过
- cargo test --lib tools::bash 13/13 通过
2026-08-03 22:45:36 +08:00
oudecheng
c7ee6bb519 fix: 修复并发 sub-agent 持久化时 SQLite database is locked 错误
将 7 个写事务从 BEGIN DEFERRED 改为 BEGIN IMMEDIATE,在事务开始即获取写锁,消除多 sub-agent 并发写入时的死锁路径。同时将 busy_timeout 从 5s 提升至 30s,为并发写者排队提供 100 倍余量。

根因:BEGIN DEFERRED 下多个事务可同时读 MAX(seq) 不持写锁,提交时互相阻塞,5s timeout 耗尽后返回 SQLITE_BUSY。BEGIN IMMEDIATE 强制写者串行排队,顺带消除 MAX(seq)+1 竞态导致的 UNIQUE 约束冲突。
2026-08-03 22:15:13 +08:00
oudecheng
2a5a0277c0 feat(web): 添加话题重命名功能
新增 RenameTopic 命令与 TopicRenamed 协议消息,复用存储层已有的
update_topic_title 方法。后端响应携带刷新后的完整 topic 列表,
前端零额外往返即可同步侧边栏。

前端 TopicList 侧边栏增加内联编辑入口:悬停显示铅笔图标,点击进入
编辑模式,Enter 提交 / Esc 取消 / blur 取消;通过 onMouseDown
preventDefault 防止按钮点击时 input 提前失焦。

包含 5 个单元测试覆盖成功、空标题、不存在、标题未变跳过写入、
can_handle 等场景。
2026-08-03 22:10:25 +08:00
oudecheng
c484a918b5 feat: 添加 build.rs 自动构建前端,cargo build 时自动执行 npm install + npm run build 2026-08-03 17:29:46 +08:00
oudecheng
389e222b11 feat: 默认 max_tool_iterations 调整为 1000 2026-08-03 14:46:49 +08:00
oudecheng
0aa9050b12 fix: 修复 3 个失败测试 - agent_md_template 写入模板内容/StreamingAccumulator BTreeMap 保序/source_order 断言补全 2026-08-03 14:26:20 +08:00
oudecheng
3b0de7d071 refactor: 清理未使用的 dead code 函数与方法 2026-08-03 14:21:01 +08:00
oudecheng
f8e1f3c2eb chore: bump 版本至 0.2.0 并新增 CHANGELOG
- Cargo.toml / Cargo.lock: 0.1.2 → 0.2.0
- 新增 docs/CHANGELOG.md,记录 0.2.0 相对 0.1.2 的 47 个 commit 迭代内容,
  按新增功能/性能优化/修复/重构/内部改进分类,涵盖四大方向:
  CapabilityPolicy 统一策略模型、专家/子代理模型独立配置、
  话题级并发隔离、Agent Loop 性能优化
2026-07-31 08:28:36 +08:00
oudecheng
7f05545488 fix: session pool 锁范围收敛与 replace_todos 原子性
- SessionPool.ensure_session_internal 改为 double-checked locking:
  先短暂持锁检查存在性,释放锁后执行耗时的 session 创建(含配置加载、
  agent 工厂构造),再次持锁插入并处理竞态。避免跨 session_factory.create().await
  持有全局锁导致所有 channel 的 session 访问串行化。
- storage::replace_todos 用 transaction() 包裹 DELETE + INSERT,保证原子性:
  中途失败自动回滚,避免 todos 列表丢失且无法恢复。事务内复用同一连接
  查询返回值,消除 drop(conn) 后重新 pool.get() 的冗余。
2026-07-31 08:23:13 +08:00
oudecheng
92db80dc3f perf(agent-loop): 缓存工具定义与轻量 token 估算,DB 写入 offload 至 blocking 池
- 工具定义在 process() 循环外构建一次(快照语义:单次 process 内 Arc<ToolRegistry> 不变)
- 工具 token 估算循环外预算一次,避免每轮 serde_json::to_string 全量序列化
- 新增 estimate_text_tokens_from_chat_messages 直接基于 ChatMessage 累加字段长度,
  消除中间 text_only_messages: Vec<Message> 构造(N 条消息 × 5 字段双克隆 → 0)
- PersistingEmittedMessageHandler 用 spawn_blocking 包装 SQLite 同步写,
  解除 tokio worker 线程阻塞,不改变失败语义(仅记日志不阻断主流程)
2026-07-31 08:22:58 +08:00
oudecheng
649ea2998d feat(web): 集成 ModelSelector 实现主 agent 模型选择 UI
新建 ModelSelector 组件(参考 ExpertSelector 交互模式): 显示当前生效模型,下拉支持 provider/model 选择,草稿暂存+应用提交,重置为默认。

ChatContainer 用共享 flex 容器并排渲染 ExpertSelector 和 ModelSelector,ExpertSelector 简化外层 wrapper(去除冗余 padding/max-width)。

API 层: 新增 sessionSelectedModel 端点常量和 getSelectedModel 函数; types.ts 为 ModelOptionsResponse 添加 current 字段。

按钮语义: 已覆盖时显示 provider/model,未覆盖时显示 '默认 provider/model'。
2026-07-30 23:25:14 +08:00
oudecheng
a825b10c48 feat(model): 主 agent 支持会话级模型覆盖
新增 ModelSelectionStore 存储 session_id -> (provider, model) 映射,职责单一,仅依赖 std,不引入业务模块耦合。

AgentFactory::create 中按链式覆盖应用模型配置: 专家 frontmatter 覆盖 -> 用户手动选择覆盖(最高优先级)。

HTTP API: POST /api/session/select-model 设置/清除用户模型覆盖(校验 provider/model 存在性); GET /api/session/selected-model 读取当前 session 的用户覆盖(与 experts/selected 对称)。

在 build_session_manager 系列函数中创建并注入 ModelSelectionStore,GatewayState 持有 Arc<ModelSelectionStore> 供 HTTP handler 访问。
2026-07-30 23:24:51 +08:00
oudecheng
5ff1e8455c feat(web,docs): 前端支持模型下拉选择并更新 README 专家系统章节
- 前端类型 ExpertItem/SubagentItem 新增 provider/model 字段,新增 ModelOptionsResponse 类型

- API 层 createExpert/updateExpert/updateSubagent payload 支持 provider/model,新增 listModelOptions

- ConfigPage 专家/子代理模态框新增模型配置 SectionCard(Provider/Model 下拉框,留空继承默认)

- 空字符串转 undefined 传后端,避免 P0 反序列化为 Some(vec![]) 导致全禁

- README 新增第 8 节专家系统,更新子代理 SUBAGENT.md 字段说明(含 provider/model 和子代理黑白名单)

- README 新增 max_nesting_depth 配置项说明,重排后续章节编号
2026-07-30 22:44:28 +08:00
oudecheng
dc9211548a feat(model): 专家和子代理支持独立配置 provider/model
- 新增 ModelResolver 解析器,按 frontmatter 中的 provider/model 名覆盖基础 LLMProviderConfig

- Expert/SubagentDef 数据结构新增 provider/model 字段,frontmatter 解析与渲染支持往返

- AgentFactory 和 DefaultSubAgentRuntime 持有 ModelResolver,在创建 agent 时解析模型覆盖

- HTTP API 新增 /api/model-options 端点,ExpertResponse/Create/Update 和 SubagentUpdateRequest 支持 provider/model

- update_expert/update_subagent 支持 provider/model 字段写回 frontmatter

- 保持架构解耦:ModelResolver 位于 config 底层,不引入新的跨模块依赖
2026-07-30 22:44:10 +08:00
oudecheng
003eab4f21 fix(web): 修复点击设置后白屏(React hooks 顺序违规)
子模态框 ESC 处理的 useEffect 被放在 if(loading)/if(!config) 条件 return 之后,导致首次渲染(loading=true)时不执行该 hook,config 加载后重新渲染才执行 → hooks 数量不一致 → React error #310 白屏崩溃。

修复:将 useEffect 移到所有条件 return 之前,与其他 hooks 放在一起。
2026-07-30 18:56:43 +08:00
oudecheng
951e31aca4 feat(web): 子代理模态框改造并暴露 max_nesting_depth 配置项
对抗审查修复 4 个 P0 问题:
- 子代理 modal handleSave 缺失 allowed_subagents/denied_subagents 字段,导致保存时既有策略被后端 #[serde(default)] 静默清空(数据丢失)
- 子代理 modal 缺失子代理黑白名单 CheckboxList UI
- 子代理 modal 未改造为 ModalHeader/ModalFooter/SectionCard,无 X 关闭按钮与遮罩 onClick
- experts tab 未加载 subagentList,编辑专家时子代理勾选列表显示空

新增改动:
- ui.tsx 新增 ModalHeader/ModalFooter 通用组件
- ConfigPage renderTools 新增最大嵌套深度输入框(后端 TaskConfig.max_nesting_depth 早已存在,默认 2)
- 子代理 modal 子代理选项排除自身避免意外自递归
2026-07-30 17:38:08 +08:00
oudecheng
c27efedb6c feat(capability): 扩展 CapabilityPolicy 支持子代理黑白名单
- domain: CapabilityPolicy 新增 allowed_subagents/denied_subagents 字段及 check_subagent_allowed 方法
- experts: 专家 frontmatter 解析/渲染支持子代理策略字段
- task/runtime: spawn/resume 双路径校验父代理子代理策略;新增 update_subagent 写回 SUBAGENT.md(与 update_expert 对称);新增 SubagentPromptProvider 按专家策略过滤子代理索引
- task/runtime: 子代理自身 capability 作为孙代理的 parent_capability 透传(ToolContext),保持解耦
- traits: ToolContext 新增 parent_capability 字段
- agent_factory: 主 agent 注入 expert_capability 到 ToolContext

安全:策略不通过即拒绝(与 def 不可用即拒绝范式一致),防止 LLM 通过选择被禁子代理绕过限制;max_nesting_depth 兜底防递归不可被 def 覆盖。
2026-07-30 17:37:29 +08:00
oudecheng
9381ed5dd4 feat(web): 专家/子代理 capability 字段改为勾选式编辑
- 专家编辑模态框:4 个 textarea 替换为 CheckboxList,技能/工具按来源分组展示

- 新增子代理编辑模态框:非 builtin 子代理显示编辑按钮,支持描述 + 4 个 capability 勾选字段

- 进入 experts/subagents 标签页时自动拉取技能与工具列表

- allowed_* 为空时传 undefined(后端 None=不限),避免空数组触发白名单空集语义
2026-07-30 16:55:38 +08:00
oudecheng
3ea0c19262 feat(web): 新增工具列表 API 客户端与 CheckboxList 勾选组件
- api/client.ts: 注册 tools 与 subagentsUpdate 端点常量

- api/tools.ts: 新增 listTools() 调用 /api/tools

- api/subagents.ts: 新增 updateSubagent() 调用 PUT /api/subagents/update

- types.ts: 新增 ToolItem / ToolsListResponse 类型

- ui.tsx: 新增 CheckboxList 组件,支持分组展示、legacy 已选值以可移除标签呈现
2026-07-30 16:55:12 +08:00
oudecheng
89c444ad3f feat(gateway): 新增 /api/tools 与 /api/subagents/update 端点
- /api/tools: 只读访问 ToolRegistry,返回 builtin + MCP 工具的 name/description/source

- /api/subagents/update: PUT 接口,调用 SubagentRuntime::update_subagent 写回 SUBAGENT.md frontmatter

- 路由表两处同步注册新端点
2026-07-30 16:54:19 +08:00
oudecheng
12f1094426 feat(capability): 新增 CapabilityPolicy 统一策略模型并集成至专家/子代理/技能
- domain: 定义 CapabilityPolicy(allowed/denied skills/tools)统一策略结构

- experts: 专家支持 capability 字段,新增 update_expert 写回 EXPERT.md

- skills: SkillRuntime 暴露 capability 过滤接口

- tools/task: SubagentDef 增加 capability 字段,SubagentRuntime 新增 update_subagent 写回 SUBAGENT.md,工具集按 capability 过滤

- agent_factory: 按 capability 构建专家/子代理工具集(白名单交集 + 黑名单扣除)
2026-07-30 16:53:30 +08:00
oudecheng
85c021f124 fix(deps): 升级 wechatbot 的 reqwest 至 0.13,消除版本冲突
主 crate 用 reqwest 0.13.2,而 vendored wechatbot 仍用 0.12,
导致 Cargo 同时编译两套 reqwest 及其传递依赖,拖慢编译、增大二进制。

改动:
- vendor/wechatbot/Cargo.toml: reqwest 0.12 -> 0.13,feature rustls-tls -> rustls
  (reqwest 0.13 移除了 rustls-tls feature,改为细粒度的 rustls)
- Cargo.lock: 合并两份 reqwest 为一份 0.13.3,移除 0.12.28 条目

wechatbot 对 reqwest 的使用仅限稳定客户端 API(Client::builder、
get/post/json/body/send、status/text/bytes/headers),0.12 -> 0.13 无破坏性变更,
源码零改动。

验证:
- cargo check 通过
- cargo test --no-run 全部测试二进制编译通过
- cargo test --lib: 494 passed,3 个失败经 stash 对照确认为预存技术债
2026-07-29 17:31:29 +08:00
oudecheng
4baa8e7a6b fix: 内存历史按 topic_id 键化,新增 replace_topic_history 修复 DB 压缩覆盖
- SessionHistory.chat_histories 改为 topic_histories 按 topic_id 键化,消除多话题并发时内存历史互相覆盖的根因

- is_current_turn 改用 current_topic == original_topic_id 直接比较,替代失效的内存最新消息匹配判断

- 新增 ConversationRepository::replace_topic_history 按 topic 删除/插入消息,解决 replace_active_history 删除整个 session 消息的 pre-existing 问题

- compaction 路径改用 replace_topic_history,避免压缩时覆盖其他 topic 的 DB 消息

- switch_topic 不再 remove_history,不同 topic 历史独立存储互斥

- append_persisted_message 仅当 topic_id == current_topic 时更新内存,防止延迟消息污染新话题历史
2026-07-28 15:32:42 +08:00
oudecheng
b042b45ac7 fix: 话题隔离 - topic_id 全程显式传递,修复消息错投与并发阻塞
问题:WS 端一个话题执行中时新建另一话题发消息,新会话无响应
(串行锁按 chat_id 阻塞),且首个话题完成后用户消息错误出现在
旧话题而非新话题(执行路径多次从共享 UI 状态读取 topic_id 产生竞态)。

核心修复(第一性原则):
执行上下文应在消息接收时一次性捕获,全程显式传递,不从共享可变
状态重复读取。

1. processor.rs: process_one 入口捕获 current_topic,传入 handle_message
   和 set_agent_cancel_token
2. session_message_service.rs: handle_message 签名加 topic_id 参数,
   透传给 MessageExecutionRequest
3. execution.rs: MessageExecutionRequest 加 topic_id 字段;
   - 串行锁键改用 topic_id(不同 topic 并发,同 topic 串行)
   - original_topic_id 优先用传入值,消除锁等待期间 topic 切换竞态
   - append_persisted_message 调用传入 original_topic_id
   - create_agent 调用传入 original_topic_id
4. session.rs: append_persisted_message 加 explicit_topic_id 参数;
   create_agent/create_agent_with_provider_config 加 explicit_topic_id;
   set_cancel_receiver/set_agent_cancel_token 加 topic_id 参数;
   pending_cancel_tokens 查找改为优先 topic_id(避免并发 topic 执行时
   cancel token 互相覆盖)

对抗性审查补丁:
- append_persisted_message: 仅当写入 topic 匹配当前活跃 topic 时才更新
  内存历史,避免旧 topic 的排队消息污染已切换到的新 topic 内存历史
- prepare_and_execute_scheduled_task: 锁前一次性捕获 topic_id,锁后
  复用同一值作为 original_topic_id,保证锁键与写入目标一致

已验证:cargo check 通过,gateway 模块 48 个测试通过(1 个预存在的
prompt 模板测试失败,与本次修改无关)。
2026-07-28 14:19:20 +08:00
oudecheng
3a8da51936 refactor: 串行锁按 topic_id 键化,支持多话题并发执行
将 chat_serial_locks 重命名为 topic_serial_locks,串行锁粒度从
chat_id 改为 topic_id。同一 topic 的消息处理仍串行执行,不同
topic 之间互不阻塞,为后续多话题并发修复铺路。

本提交仅重命名锁结构和访问方法,调用点仍用 chat_id 作为锁键
(回退兼容),不影响现有行为。
2026-07-28 14:17:12 +08:00
56612389ae 改用gray_matter解析md头元数据,增加相应的日志 2026-07-21 16:33:55 +08:00
oudecheng
141ffda1ee feat: 实现聊天消息的串行锁,确保同一聊天的消息处理串行执行 2026-07-15 17:47:52 +08:00
oudecheng
303f6d83e3 feat: 添加工具调用序列的前向检查,确保工具消息紧随助手消息后 2026-07-15 16:36:13 +08:00
oudecheng
cde41e32a8 refactor: 优化工具使用说明,简化记忆检索和写入规则 2026-07-13 17:00:40 +08:00
oudecheng
cea4bd3cfb feat: 子代理支持数据驱动的工具过滤(白名单+黑名单),删除内置 explore
SUBAGENT.md frontmatter 新增 denied_tools 黑名单字段,启用已有的 allowed_tools 白名单;新增 ToolRegistry::only 白名单方法;抽取 build_subagent_tools_registry(白名单→黑名单→depth 兜底);安全修复:resume 在 def 失踪时拒绝恢复而非降级为完整工具集(避免权限提升);depth 阈值改为引用 max_nesting_depth 配置;SubagentWithStatus 暴露工具字段供前端只读展示;删除内置 explore 子代理及专属 explore_max_execution_secs 配置(全栈清理);新增 23 个测试覆盖过滤矩阵、frontmatter 解析、状态投影
2026-07-09 14:42:38 +08:00
oudecheng
c276381d6c 增加整理意识,确保同类工作输出到同一文件夹 2026-07-08 16:32:43 +08:00
oudecheng
58f461c953 fix: 修复 SSRF 重定向绕过 + 符号链接路径遍历 + TodoItemSummary 字段缺失
P0: src/tools/http_request.rs SSRF 重定向绕过
  - reqwest::Client 默认跟随最多 10 次重定向,is_private_host 仅检查初始 URL
  - 攻击者可用公网 URL 返回 302 → http://127.0.0.1/http://169.254.169.254/(云元数据端点)绕过防护访问内网
  - 修复:.redirect(reqwest::redirect::Policy::none()) 完全禁用重定向

P1: src/tools/file_read/write/edit.rs 符号链接路径遍历
  - resolve_path 用 starts_with 检查但未 canonicalize
  - 攻击者可在 allowed_dir 内创建指向 /etc/passwd 的符号链接绕过限制
  - 修复:对 resolved 和 allowed 均执行 canonicalize 后比较
  - file_read: 文件必须存在,canonicalize 失败直接报错
  - file_write/edit: 文件可能不存在,降级到父目录 canonicalize

P1: src/protocol/mod.rs + list_todos.rs TodoItemSummary 字段缺失
  - 后端 TodoItemSummary 仅返回 4 字段,前端期望 7 字段
  - 缺失 priority, created_at, updated_at,前端 TodoPanel 无法显示
    优先级和时间戳
  - 修复:struct 补齐 3 字段,list_todos 构造时传递完整字段
2026-07-08 16:23:09 +08:00
oudecheng
53a45ad7c4 修复mcp log 2026-07-08 15:52:36 +08:00
oudecheng
0a8d21fe40 fix: 修复专家发现与前端选择多项问题
后端:
- split_frontmatter 兼容 CRLF/LF 行尾符,修复 Windows 下 EXPERT.md 解析失败
- Config::load_from 移除 fallback 到 cwd/config.json 的逻辑,避免静默加载项目目录配置导致 experts 字段缺失
- ExpertRuntime 新增 update_config 方法,save_config 时同步更新 ExpertRuntime 内部 config,sources 变更即时生效
- source_order 空数组不再兜底返回 [User, Project],尊重用户关闭所有源的意图
- from_config_with_cwd 增加诊断日志,输出 enabled/sources/cwd/discovered

前端:
- ExpertSelector 弹窗改为按钮上方居中展开
- 专家卡片 name 完整显示(break-all),路径显示在卡片底部
- "管理专家"入口仅在无专家时显示
- 每次打开下拉都重新拉取列表和选中状态
- 监听设置弹窗关闭事件刷新已选专家(处理已选专家被禁用的情况)
2026-07-08 14:53:30 +08:00
oudecheng
46a1ca6853 refactor: 修复 3 项 P1 技术债(硬编码 URL / any 类型 / dead_code)
P1-1: 硬编码 wechat URL 抽常量统一两处
  - config/mod.rs: 定义 pub const WECHAT_DEFAULT_BASE_URL
  - cli/init.rs: import 并引用常量,消除字面量重复
  - 消除两处不一致风险(default_wechat_base_url 与 init 向导)

P1-2: ConfigPage any 类型定义具体类型(8 处)
  - types.ts: 新增 FeishuChannelConfig/WechatChannelConfig/ChannelConfig/
    SchedulerJobConfig 类型;jobs?: any[] → SchedulerJobConfig[];
    channels: Record<string, any> → Record<string, ChannelConfig>
  - api/config.ts: 新增 RestartResponse 类型,data: any → RestartResponse
  - ConfigPage.tsx: catch (e: any) ×2 → catch (e: unknown) + 类型守卫;
    as any → as SchedulerConfig['misfire_policy'];
    Record<string, any> → Partial<ChannelConfig>;(ch: any) → (ch: ChannelConfig)

P1-3: dead_code 18 处逐一审查清理
  - 删除 10 处未使用函数/struct(YAGNI 原则):
    context_compressor.rs (into_messages, is_tool_round)
    agent_loop.rs (EmptySkillProvider struct + impl)
    memory_maintenance.rs (run_for_scope)
    session.rs (try_start/finish_background_compaction)
    session_history.rs (try_start/finish_background_compaction)
    tool_registry_factory.rs (shell_session_manager)
    task/runtime.rs (effective_allowed_tools)
  - 保留 8 处 serde 反序列化字段(删除会破坏 JSON 反序列化):
    feishu.rs/openai.rs/anthropic.rs/skills/mod.rs/task/runtime.rs
2026-07-08 14:51:42 +08:00
oudecheng
47a30e87d8 fix: 修复 http.rs 重启信号吞错(P1)
src/gateway/http.rs 的 /api/restart 处理器中:
  let _ = restart_tx.send(true);
静默丢弃了 watch::Sender::send 的错误。

若接收方任务已退出(select! 其他分支先完成),
send 返回 Err 但 HTTP 已返回 success:true,
用户误以为重启成功,实际重启未触发,且无日志可排查。

改为 if let Err(e) = ... { tracing::warn!(...) } 记录错误。
保留 200ms 延迟设计(让 HTTP 响应先返回客户端),
不改变成功路径行为,仅在失败时增加诊断日志。
2026-07-08 14:37:08 +08:00
oudecheng
fef5ae7626 fix: 修复 P0 级技术债(useEffect 依赖项缺失 + prompt 注入吞错)
P0-1: web/src/App.tsx 话题刷新 useEffect 依赖项不完整
  - 原依赖数组仅 [topicRefreshTrigger],但 effect 体内使用了
    status/handleCommand/sendMessage/requestTopicList
  - 导致断线重连后 status 从 disconnected→connected 时
    effect 不会重新运行,话题刷新被静默丢失(stale closure bug)
  - 补全依赖数组为 [topicRefreshTrigger, status, handleCommand,
    sendMessage, requestTopicList],与同文件 200-208 行 effect 风格一致

P0-2: src/gateway/agent_prompt_provider.rs 静默吞错
  - record_injection 中 let _ = repository.mark_agent_prompt_reinjected(...)
    丢弃了 Result 错误
  - 失败时 Agent prompt 注入计数错乱且无任何日志
  - 改为 if let Err(e) = ... { tracing::warn!(...) } 记录 session_id 和 error
2026-07-08 14:33:22 +08:00