551 Commits

Author SHA1 Message Date
oudecheng
508806a408 fix(gateway): 定时任务执行前自动补齐 topic,修复 agent 空历史执行
scheduler/ 虚拟会话从不经用户消息分配 topic,导致定时任务的系统提示词与任务 prompt 只落库、不进内存历史(append_persisted_message 在 topic=None 时跳过内存写入),agent 以空历史执行,任务实际未运行、消息无法送达会话。

- Session 新增 ensure_topic_for_chat:内存无 topic 时从 DB 恢复最近活跃 topic,无则自动创建(先确保持久会话以规避外键约束)

- prepare_and_execute_scheduled_task 捕获 topic 前调用 ensure,保证 original_topic_id 恒为 Some

- append_persisted_message 无 topic 时回退到 chat_id 键内存历史,兜底所有无 topic 路径
2026-08-15 18:16:50 +08:00
oudecheng
73c25e5a20 feat(web): Task 卡片差异显示子代理模型名 + 子代理完成状态转发修复
- Task/tool 卡片头部在子代理模型与主代理不同(model 或 provider 任一不同)时显示模型徽章
- useChat 放行带 subagent_task_id 的 execution_completed 消息,更新卡片 running→completed 状态
2026-08-15 15:35:32 +08:00
oudecheng
53e2fb6dc6 feat(web): per-topic 输入草稿记忆(切换暂存/恢复 + localStorage 持久化 + 删除清理)
- 文本草稿 debounce 持久化到 localStorage(key 按 topicId),附件仅存内存
- 切换话题时暂存当前草稿并恢复目标话题草稿,组件卸载时补齐落盘
- 删除话题时同步清除其持久化草稿,避免孤儿 key
2026-08-15 15:35:07 +08:00
oudecheng
8f59b6b93a feat(web): 话题级模型选择前端(ModelSelector topicId 化 + 竞态防护)+ 专家系统开关隐藏
- ModelSelector 支持按 topicId 读写话题级选择(topic 优先,session 兜底)
- 快速切换话题时用递增令牌丢弃过期响应,防止乱序覆盖
- ChatContainer 透传 topicId,向后端双写新话题默认模型
- ExpertSelector 依据后端 experts_system_enabled 隐藏选择器并清除已选专家
2026-08-15 15:34:38 +08:00
oudecheng
dc693fa80b feat(model): 主代理/子代理模型解析链 topic>session>expert>config + TaskToolResult 附带实际模型
- 主代理解析链新增 topic 级选择(最高优先级),session 级首命中时物化固化为话题选择
- 子代理解析链共享 helper:def frontmatter > 话题级 > session 级 > 全局基础配置
- TaskToolResult 携带子代理实际使用的 provider/model,供前端差异展示
2026-08-15 15:34:18 +08:00
oudecheng
414105d419 feat(model): 话题级模型选择持久化与 API(topics 表加 provider/model 列 + 双写内存缓存)
- topics 表迁移新增 provider/model 列,记录话题级显式模型选择
- storage 层新增 update_topic_model / list_topic_model_selections
- 新增 POST /api/topic/select-model 与 GET /api/topic/selected-model
- 双写 key 取 topic 行自带 session_id(不信任请求体,防污染其他会话)
- 清除时同步清 session 级选择,避免'重置为默认'被物化逻辑重新覆盖
2026-08-15 15:33:58 +08:00
oudecheng
9e503f2672 feat(web): 工具卡片统一格式,子智能体显示 '工具名 · 任务描述' 并移除下方徽章与时间 2026-08-15 09:32:16 +08:00
oudecheng
f705536fb1 feat(web): 通道/会话切换移至设置页 ConnectionTab,修复通道失效回退;连接状态上移 Logo 行并支持窄侧栏 compact 降级 2026-08-15 08:38:12 +08:00
oudecheng
eaee29841d feat(web): 对齐 deepseek-harness 设计语言,重构前端视觉与三栏布局
- 设计 token 重映射为 DeepSeek 调色板(深浅双主题),正文改无衬线字体栈,全面去霓虹化
- 移除顶部 Header,控件迁入左栏(Logo/连接/通道/会话/tabs/底部操作区),左右栏支持拖拽调宽并持久化,折叠为图标 rail
- 对话区对齐 DeepSeek 聊天风:hero/docked 状态机、736px 居中列、用户右对齐柔和气泡、助手无气泡平铺、composer 卡片与反差发送键;子智能体点击导航完整保留
- 全功能区同步换肤:侧栏列表、右栏面板、ConfigPage、弹窗、选择器等
- fix: 修复 virtual-core 3.17.x 陈旧行高导致消息行重叠(宽度变化/滚动停止后强制 resizeItem 重测)
- fix: 修复 ConfigPage/Modal 引用不存在的 fadeIn/scaleIn keyframes 致入场动画失效
2026-08-14 19:15:45 +08:00
oudecheng
4c21add43b chore: 同步 Cargo.lock 版本号至 0.4.0 2026-08-14 16:39:11 +08:00
oudecheng
2dead61697 chore: 升级版本号至 0.4.0 并更新 CHANGELOG 2026-08-13 23:20:44 +08:00
oudecheng
f05f09b636 docs: 异步子代理设计文档
记录 v8 设计方案的三个核心不变量、wait 模式、取消机制与崩溃恢复策略
2026-08-13 22:13:00 +08:00
oudecheng
daf973a7dc fix(web): 子代理状态显示与渲染崩溃修复
MessageBubble:
- taskStatusConfig 补全 running/completed/cancelled/interrupted 状态配色,
  修复未知状态访问 undefined.borderColor 导致的白屏崩溃
- 添加安全回退到 failed 状态

main.tsx:
- 添加 ErrorBoundary 捕获 React 渲染错误,防止白屏并提供错误详情与恢复入口

useMessages:
- 收到子代理 execution_completed 事件后,更新主视图中 task tool result 占位消息的
  status 字段(running->completed/failed/cancelled),消除永久黄色转圈

protocol.ts:
- ExecutionCompleted 接口添加 subagent_status 和 subagent_summary 字段
2026-08-13 22:12:43 +08:00
oudecheng
fc3a95b152 feat: 异步子代理 v8 实现与 /stop 取消流程修复
后端核心实现:
- 新增 wait_coordinator:释放/重获取 serial_lock 的 in-tool waiting 模式,替代旧的 break-exit 方案
- 新增 wait_for_subagents 工具:select! 等待子代理完成/用户消息/超时,支持 try_drain 批量消费
- task 工具异步 spawn 路径:CancellationToken 注册表 + RAII guard + Semaphore 并发限流
- process exit 安全检查:pending 子代理存在时抑制 ExecutionCompleted
- 崩溃恢复:启动时标记 running->interrupted,history 加载时对账占位

/stop 取消流程修复:
- wait_coordinator select! 添加 cancel 分支,完整清理状态
- ToolContext 注入 cancel_rx(watch::Receiver clone)
- agent_loop 工具执行 select! 对 wait 工具跳过竞速,防止 drop coordinator 清理逻辑
- 子代理完成状态通过 execution_completed metadata 传播

存储层:
- pending_subagents 表 + 条件 UPDATE
- mark_all_running_as_interrupted 崩溃恢复
2026-08-13 22:12:20 +08:00
oudecheng
e0313ab8f3 fix: 增强 panic 安全与计算器健壮性
- agent_loop/processor: catch_unwind 隔离 panic,防止消息静默丢失
- calculator: 拒绝 NaN/Infinity 输入,修复阶乘溢出(上限 34),拒绝非有限表达式结果
- message: 修复 sanitize 两阶段删除索引未排序导致的越界 panic
- utils: 新增 panic_payload_message 提取可读 panic 消息
- .gitignore: 忽略 artifacts/ 测试产物目录
2026-08-13 08:38:36 +08:00
oudecheng
f65fb0167f chore: 忽略 artifacts 构建产物目录 2026-08-12 10:27:12 +08:00
oudecheng
a9ce308c05 fix(web): 移除 ChatContainer 的 key remount,修复新建话题列表刷新延迟
话题切换时 key={selectedTopic} 导致整个 ChatContainer 子树卸载重建,
触发 ExpertSelector/ModelSelector 重新挂载并发起 3 次冗余 HTTP 请求
(getSelectedExpert/listModelOptions/模型选择刷新),引入 200-500ms 延迟。

改用 topicId prop 透传话题 ID:
- App.tsx: 移除 key,viewKey 纳入 selectedTopic 保持各话题独立滚动位置
- ChatContainer: 新增 topicId prop 透传给 MessageInput
- MessageInput: 监听 topicId 变化清空草稿,替代原 key remount 重置机制
2026-08-12 09:16:38 +08:00
oudecheng
3910bc324b chore: 升级版本号至 0.3.5 并更新 CHANGELOG 2026-08-12 08:39:40 +08:00
oudecheng
29060315a3 feat(observability): 端到端可观测性整改,修复 trace_id 断链与指标配对
- 传播 trace_id:BusToolCallEmitter/SubAgentEmitter/processor 全链路设置

- AgentEnd 配对:补发 5 个 cancel/defensive 路径,闭合 AgentStart 指标

- LLM 计时修正:attempt_start 移入 retry 循环,排除退避等待时间

- /metrics auth:非 loopback 部署时纳入 Bearer token 校验

- recorder 复用:OnceLock 缓存 PrometheusHandle,热重启后不再返回 503

- 结构化日志:新增 tracing_ctx + JSON 日志格式支持
2026-08-12 08:26:42 +08:00
oudecheng
7cb170e0c2 fix(gateway): 压缩期间释放 session 锁,修复新建话题列表刷新延迟
压缩任务在 LLM 调用(2-5s)期间持有 session 锁,阻塞 create_session
命令获取锁执行 switch_topic,导致新建话题后列表刷新延迟。

将压缩重构为 4 阶段:
1. 短暂持锁:读取 history/compressor/store/session_id/provider_config
2. 释放锁:LLM 压缩调用(2-5s)
3. 释放锁:DB 写入(store 为 Arc,DB 层自带事务保护)
4. 短暂持锁:reload 内存历史

并发安全:同 topic 由调用方的 per-topic serial lock 保证串行;
不同 topic 完全并行不受影响。
2026-08-12 08:24:46 +08:00
oudecheng
77a0eac2c8 fix(mcp): 修复前端空输入重置为不超时,增加超时路径单元测试
P3: 清空输入框时回退到默认值 300 而非 0;新增两个 tokio 测试覆盖有超时和无超时(error pass-through)路径
2026-08-11 23:07:00 +08:00
oudecheng
3e97ed903c feat(mcp): 为 MCP 工具调用增加超时保护,默认 5 分钟
在 McpToolWrapper 适配层用 tokio::time::timeout 包裹 call_tool,防止外部 MCP server 挂起导致 agent loop 无限阻塞。超时时间通过 config.mcp_tool_timeout_secs 配置(默认 300 秒,0=不超时),前端 McpTab 设置页提供输入框。
2026-08-11 21:53:07 +08:00
oudecheng
7de9a8a054 feat(web): 顶栏显示后端版本号
复用现有 /health 端点返回的 CARGO_PKG_VERSION,避免在 package.json 重复维护版本号;挂载时拉取一次,失败则不显示徽标。
2026-08-07 14:52:11 +08:00
oudecheng
f26ec49182 chore: 修复 release 构建的编译器警告
- 为 regex::Captures 补全显式生命周期标注
- 将仅用于 debug 日志的局部变量 (media_count/len/total) 用 cfg(debug_assertions) 条件化
- 为仅 debug 读取的 AnthropicProvider::llm_timeout_secs 字段添加 cfg_attr allow(dead_code)
2026-08-07 14:36:27 +08:00
oudecheng
da62905505 chore: 升级版本号至 0.3.3 并更新 CHANGELOG 2026-08-07 11:55:59 +08:00
oudecheng
79d099a3bc chore(compaction): 调整压缩阈值默认值
工程化压缩阈值 threshold_ratio: 0.7 → 0.5(50%)
LLM 压缩阈值 llm_compaction_threshold_ratio: 0.5 → 0.3(30%)

降低触发门槛使压缩更积极介入,减少上下文溢出风险。
同步更新相关注释、日志信息和前端 hint 文案。
2026-08-07 11:53:54 +08:00
oudecheng
a9429a5657 fix(web): 输入框/发送按钮状态按话题隔离,避免处理中仍可发送
将单一全局 isLoading 布尔值重构为按 topic_id 跟踪的 processingTopicIds
集合,isLoading 派生自当前选中话题是否在集合中。导航响应不再清空处理
状态,切换话题后切回原话题仍能正确禁用输入。重连时通过新增的
/api/executions 端点对账后端权威执行状态,修正断连期间丢失的
execution_completed 信号导致的状态漂移。
2026-08-07 11:45:47 +08:00
oudecheng
feeb9d9c18 chore: 降低 list_todos 日志级别为 debug
前端每次 todo_write 后自动刷新会触发 list_todos 命令,子代理多次更新待办时产生大量 INFO 日志噪音。将 handler 与 gateway 转发两处日志降级为 debug,默认不输出,需要排查时通过 RUST_LOG=debug 开启。
2026-08-07 09:01:33 +08:00
oudecheng
03be2ef5bc chore: remove optimization plan doc from repo 2026-08-07 08:34:39 +08:00
oudecheng
e3e32c725d style: 对存储模块应用 rustfmt 格式化 2026-08-07 08:34:30 +08:00
oudecheng
d3ee430aa9 test(web): MessageList 虚拟化的对抗性 smoke test
对抗性审查 commit 03d8840 的虚拟化实现,新增 5 个组件测试:

- 用例 1: 空消息渲染空状态
- 用例 2: 100 条消息不崩溃(虚拟化 smoke test)
- 用例 3: highlight 指向视口外消息不崩溃
- 用例 4: viewKey 切换不崩溃
- 用例 5: 流式 rerender 不崩溃

发现:jsdom 下 getBoundingClientRect 返回 0 + rAF 同步化,
virtualizer 测量后可见范围可能变空(textContent 不可靠)。
这是测试环境限制,不反映浏览器真实行为(浏览器 rAF 异步、
高度真实)。用例 5 聚焦不崩溃验证,内容正确性由真实布局保证。
2026-08-07 08:34:17 +08:00
oudecheng
2367d87db6 perf(web): 消息列表虚拟化,长对话不再卡顿
原实现 messages.map 渲染所有消息,500 条消息会产生 10000+ DOM
节点(每条 MessageBubble 含 ReactMarkdown 解析),导致首次渲染
卡顿、滚动掉帧、流式输出每 token 都全量 diff。

引入 @tanstack/react-virtual 实现虚拟化:
- 只渲染视口内 + overscan=6 条消息,DOM 节点数恒定
- measureElement 动态测量变高度消息(一行文本 vs 50 行代码块)
- estimateSize=120 提供初始估计,测量后自动校正
- Firefox 特殊处理 measureElement(getBoundingClientRect)

适配现有功能:
- 自动滚到底部:bottomRef.scrollIntoView 改为 virtualizer.scrollToIndex
- viewKey 滚动位置记忆:保留 scrollTop 保存/恢复逻辑
- highlightedMessageId:先 scrollToIndex 渲染目标项,rAF 后加 class
- newMessageCount:流式 delta 不计数(消息条数不变)
- space-y-6 改为每项 pb-6(虚拟化下相邻 margin 不生效)

性能对比(500 条消息):
- DOM 节点:10000+ → ~200(仅可见+overscan)
- 首次渲染:数百 ms → <16ms
- 流式 diff:全量列表 → 仅可见项
2026-08-07 08:34:07 +08:00
oudecheng
934c7aa804 test(web): 补充流式断连的对抗性测试(stream_end 幂等性 + 多周期循环)
对抗性审查 commit 30b7d90 的修复健壮性,新增两个边缘场景测试:

- 用例 18: 验证断连后 stream_end 到达是幂等的(不崩溃、不改变
  已 flush 内容),且后续 assistant_response 能正常按 id 替换
- 用例 19: 验证多次断连-重连循环下 finishStreaming 幂等
  (连续调用不崩溃,消息状态稳定)
2026-08-07 08:33:57 +08:00
oudecheng
c749df4e0a fix(web): WebSocket 断连时清理流式状态,防止重连后消息串流
流式优化引入的回归风险:当 WebSocket 在流式输出中途断连时,
streamingRef(contentChunks/index)不会被重置。重连后新消息的
delta 会通过 s.index >= 0 分支追加到旧消息的错误位置。

修复链路:useWebSocket 的 onDisconnect 回调 → useChat 的
finishStreaming → useMessages 的 finishStreaming(重置 ref +
flush pending chunks)。

- useMessages.ts: 在 UseMessagesReturn 接口和 return 对象暴露
  finishStreaming(原为内部函数)
- useChat.ts: 在返回对象透传 finishStreaming
- App.tsx: useWebSocket 调用新增 onDisconnect 回调触发 finishStreaming
- useChat.test.ts: 新增用例 17 验证断连后新 delta 不污染旧消息
2026-08-07 08:33:48 +08:00
oudecheng
00735395ca fix(web): 修复正在执行的会话消息泄露到定时任务视图
handleSchedulerMessage (Tier 1 路由) 原先无条件吞掉所有可转换的
chat 消息,导致用户打开定时任务历史视图时,当前主会话正在执行
的 stream_delta/assistant_response 等实时消息被错误追加到
schedulerView.messages。

修复:调度器视图是历史会话查看器,只接收不带 topic_id 的历史
加载消息;实时流式消息(stream_delta/stream_end)和带 topic_id
的实时消息 fall through 到 Tier 2/3 路由到主视图,不再泄露。

- useSchedulerView.ts: handleSchedulerMessage 增加流式和 topic_id
  过滤,实时消息 fall through
- useChat.test.ts: 新增用例 16 验证流式和带 topic_id 消息不进入
  schedulerView,无 topic_id 历史消息仍正常进入
2026-08-07 08:33:37 +08:00
oudecheng
f8c984aef4 perf(web): 优化流式 delta 处理,避免每 token 全数组拷贝
用 ref 累加 delta chunks + requestAnimationFrame 批量 flush,
将每秒数十次 O(n) 数组拷贝合并为一帧一次状态更新。

- useMessages.ts: 新增 streamingRef 累加器 + rAF 调度,所有 delta
  只 push 到 ref,由 flushStreaming 批量落盘;用 rafScheduledRef
  boolean 追踪调度状态(避免 rAF handle 为 0 时与 null 检查冲突);
  flushStreaming 基于 prev 实际状态判断创建/更新消息(批处理安全)
- useChat.ts: 5 处 setMessages([]) 改为 clearMessages(),切话题/
  会话/通道时重置流式 ref,避免脏状态残留
- test/setup.ts: 同步化 rAF 并返回递增非零 handle,保持测试同步语义
- useChat.test.ts: 新增对抗性测试(批量 delta 累加、stream_end 后
  assistant_response 替换含 reasoning)

复杂度:O(n²·L) → O(n) 每帧,15 个测试全部通过
2026-08-07 08:33:27 +08:00
oudecheng
510520e08e refactor: 提取公共工具函数,修复飞书正则重复编译和网关路由DRY问题,添加CI安全审计 2026-08-07 08:33:15 +08:00
oudecheng
3abd22ddf4 chore: 升级版本号至 0.3.2 并更新 CHANGELOG
Cargo.toml: 0.3.1 → 0.3.2
CHANGELOG: 新增 0.3.2 条目,涵盖 7 个 commit 的变更:
- 压缩算法关键参数暴露到设置页面
- HTTP API 认证与 CORS 防护
- 上下文压缩重构为两阶段+双阈值+三段保留
- 迁移 parking_lot 锁并优化阻塞 IO
- 修复子智能体任务状态卡死、飞书消息丢失、防御性错误处理
2026-08-06 17:51:15 +08:00
oudecheng
7b459b8ca1 feat(config): 压缩算法关键参数暴露到设置页面
将 4 个压缩参数(threshold_ratio、llm_compaction_threshold_ratio、
truncate_max_tokens、preserve_count)从硬编码提取到 config.json 顶层
compaction 节,前端新增 CompactionTab 供用户调整。

后端:
- config: 新增 CompactionConfig 结构体,Config 新增 compaction 字段
- context_compressor: 4 个参数内聚到 ContextCompressor 实例字段;
  新增 with_compaction_config() 构造函数和 truncate_tool_results() 方法;
  对用户配置做防御性 clamp(ratio ∈ [0.1,1.0],整数 ≥ 1)
- agent_loop: 调用 compressor.truncate_tool_results() 实现零参数耦合
- agent_factory: 新增 build_compressor() 方法,in-loop 与 sync 兜底
  两条压缩路径共用同一套用户配置
- session: with_factories 改用 agent_factory.build_compressor(),修复
  sync 压缩路径忽略用户配置的 P0 问题

前端:
- types: 新增 CompactionConfig 接口,TabId 新增 'compaction'
- 新建 CompactionTab.tsx,4 个 number input 分两组 SectionCard
- constants + ConfigPage: 注册"上下文压缩" Tab(Archive 图标)

清理:删除无调用方的 from_provider_config/from_runtime_config/with_config
方法及 DEFAULT_THRESHOLD_RATIO/LLM_COMPACTION_THRESHOLD_RATIO 常量。

测试:context_compressor 18 + session 31 + agent_loop 42 全通过。
2026-08-06 11:03:56 +08:00
oudecheng
fe7037e4ad fix(feishu,channel): 修正 reaction 语义并增容 bus 队列,避免丢最后一条消息
- feishu.rs: 仅终态消息(AssistantResponse | ErrorNotification)成功发送
  后才移除 reaction,ToolCall 等中间过程不再触碰;发送失败/空内容
  保留 reaction 作为异常信号,解决『reaction 被提前移除掩盖最终响应
  丢失』的语义错位问题
- manager.rs: MessageBus 容量从 100 扩至 256,降低多 topic 并发场景下
  bus 级 try_send 的瞬时积压丢弃概率

与前一提交的 outbound_dispatcher 双队列优先级重构配合:AssistantResponse
/ ErrorNotification 走独立 high 队列必达,终态消息送达后才移除 reaction,
彻底解决『飞书经常丢最后一条消息且 reaction 状态误导』的问题。
2026-08-06 10:08:36 +08:00
oudecheng
e037de88a2 fix(agent,gateway): 替换 unreachable!() 为防御性错误返回
将 run_final_summary 和 send_with_retry 中的 unreachable!() 替换为
错误返回,避免未来重构导致循环体未返回时 panic 终止所有会话。

- agent_loop.rs: run_final_summary 兜底构造错误 AgentProcessResult,
  含 emit_live_tool_call_message 推送,与 Err 不可重试路径一致
- outbound_dispatcher.rs: send_with_retry 兜底返回 ChannelError::SendError,
  覆盖 RETRY_DELAYS_SECS 为空数组的边界情况

同时 outbound_dispatcher.rs 包含优先级队列重构:高优消息使用独立
high 队列和扩展重试策略,保证最终响应不被中间过程挤占丢弃。
2026-08-06 10:01:02 +08:00
oudecheng
bf8c227634 refactor: 迁移 parking_lot 锁并优化阻塞 IO 与内存管理
## 锁迁移:std::sync → parking_lot
消除锁中毒(poison)导致的级联崩溃风险。parking_lot 锁不会中毒,
且性能更优。迁移覆盖全部生产代码:
- experts/mod.rs: 4 RwLock + 13 expect
- skills/mod.rs: 1 RwLock + 11 expect
- tools/registry.rs: 1 RwLock + 2 expect
- gateway/model_selection.rs: 1 RwLock + 2 expect
- tools/task/repository.rs: 1 RwLock + 4 unwrap
- tools/task/runtime.rs: 2 RwLock + 17 expect
- gateway/session.rs + task/runtime.rs: stream_message_id Mutex
- gateway/processor.rs: description_generation_in_flight Mutex
- command/handler.rs + help.rs: metadata Mutex(公开 API)
- mcp/client.rs: stderr_lines Mutex

测试代码中的 std::sync::Mutex(串行化锁 + TestObserver)有意保留,
已通过 unwrap_or_else(|err| err.into_inner()) 做中毒恢复。

## P1: 阻塞 IO 迁移到 spawn_blocking
将 3 处阻塞 async worker 的操作迁移到 blocking 线程池:
- file_read.rs: read_to_string + 行处理 + base64 编码整体包入 spawn_blocking
- agent_loop.rs: 新增 preencode_images_for_request 两阶段预编码
  (顺序分配预算 → 并行 spawn_blocking 编码),build_llm_request 改为 async
- wechat.rs: media_to_send_content 改为 async,std::fs::read 用 spawn_blocking 包裹

## P2: session_history topic_histories 内存上限
新增 MAX_CACHED_TOPICS=32 上限和 evict_inactive_if_needed 方法。
超限时驱逐非活跃 topic(不在 chat_topic_ids、不在 compression_in_flight、
serial_lock 未被持有)。活跃 topic 永不误驱逐。
remove_history 同步清理 topic_serial_locks,防止无限增长。

## P3: 减少 panic 面
agent_loop.rs retry 循环的 response.expect(...) 改为 ok_or_else(...)?
返回 AgentError::Other,逻辑 bug 不再导致整个 agent 崩溃。

## 对抗性审查修复
- preencode_images_for_request: 用 seen HashSet 去重,防止同 path 重复
  编码导致 HashMap entry 覆盖(NoBudget 覆盖 Encoded 等)
- evict_inactive_if_needed: 检查 topic_serial_lock.try_lock(),防止驱逐
  正在 agent 处理中的 topic(original_topic_id 不在 chat_topic_ids 但
  agent 仍持锁)
- remove_history: 清理 topic_serial_locks

## 验证
- cargo check: 通过(仅既有 lifetime 警告)
- cargo test: 559 passed / 3 failed(均为环境/sandbox 权限问题,与本次改动无关)
2026-08-06 08:18:04 +08:00
oudecheng
fa420b713f feat(gateway): 添加 HTTP API 认证与 CORS 防护,修复密钥泄露风险
- 新增 auth_token 配置项:非 loopback 绑定强制认证,loopback 可选加固
- 新增 auth.rs:loopback 判定(IpAddr 解析)、常量时间 token 比较、RFC 6750 Bearer 解析
- 新增 Bearer token 中间件(/api/*)与 WebSocket ?token= 校验
- 新增 CORS layer:loopback 镜像 Origin 防 DNS rebinding,非 loopback permissive
- 修复 .gitignore 遗漏 tests/test.env,补充通用 *.env 规则
- 修复前端 experts/skills/subagents.ts 直接 fetch 不带 token(13 处),统一走 authedFetch
- config.json host 从 0.0.0.0 改回 127.0.0.1
- mask_config/save_config 处理 auth_token 掩码与保留
- ConnectionTab/GatewayTab 增加认证 token 配置入口
2026-08-05 22:41:00 +08:00
oudecheng
6252b600cd refactor(compression): 重构上下文压缩为两阶段+双阈值+三段保留
压缩时机从 process 整体返回后提前到每轮工具调用完成后,更及时控制上下文大小。

两阶段压缩:
- 工程化压缩(70% 阈值触发):截断非子代理 tool 结果到 100 token,仅改内存不影响 DB
- LLM 压缩(50% 阈值触发):工程化压缩后仍超 50% 才调 LLM,避免不必要的 LLM 调用

LLM 压缩三段保留策略:
- 保留最旧 5 个 unit 和最新 5 个 unit 原样
- 中间段用 LLM 生成摘要(heavy prompt)
- SystemGuard 永远保留不计入配额
- ToolRound 原子性由 parse_to_units 保证,切分在 unit 边界

工程化压缩:
- truncate_tool_results_in_place 截断 role="tool" 且 tool_name!="task" 的消息
- 子代理返回(tool_name="task")保持原样
- char_indices 确保 UTF-8 字符边界安全,截断后追加 "...[已截断]" 标记

AgentLoop 集成:
- 新增 CompactionSink trait,LLM 压缩结果通过 sink 回写 DB
- AgentProcessResult 新增 compaction_performed 和 engineering_compaction_applied 标记
- finalize_result 据此决定是否 reload DB 历史(避免重复 append)和跳过兜底压缩

错误恢复:
- LLM 压缩失败降级为仅工程化压缩,不中断 agent loop
- sink.compact 失败记 error 日志继续使用内存压缩结果,DB 下次 process 会重新触发压缩

清理旧的 two-segment 差异化压缩代码(OLDER_BUDGET_RATIO、find_safe_split_point、
build_light_summary_prompt 等),新增 unit_to_messages 辅助函数。
2026-08-05 21:56:41 +08:00
oudecheng
c790ee1609 fix(gateway): 修复已完成子智能体任务卡在"子智能体正在执行..."
TaskStarted 补发逻辑改为基于 tool_result 存在性判断(替代 state==Running),
并在补发事件中携带 tool_call_id 以实现前端精确匹配。

- TaskSession 新增 tool_call_id 字段,runtime.spawn 时从 parent_context 注入
- send_topic_history/send_task_messages 收集历史中已有 tool_result 的 tool_call_id
  集合,仅对没有 tool_result 的任务补发 TaskStarted(不论 Running/Completed)
- 补发时传入 task.tool_call_id,前端可精确匹配 task tool_call 卡片,
  避免多并行子智能体场景下 fallback 串扰
- 移除未使用的 TaskSessionState 导入

修复场景:父智能体启动多个并行子智能体,其中一个完成但另一个仍在运行时
重载 topic,已完成的子智能体因 state!=Running 被跳过,导致其 tool_call
卡片永远收不到 TaskStarted,卡在"子智能体正在执行..."无法变为"查看实时进度"。
2026-08-05 17:34:03 +08:00
oudecheng
bc26c66169 chore: 升级版本号至 0.3.1 并更新 CHANGELOG
收录自 0.3.0 以来 19 个 commit 的变更,涵盖五大方向:
- 历史压缩保留原消息(双视图分离)
- Topic 维度 token 统计
- 前端渲染稳定性修复
- 工具执行可靠性修复
- 网关吞吐与存储查询优化
2026-08-05 12:56:58 +08:00
oudecheng
81fbee4878 feat(storage): 历史压缩保留原消息,前端展示完整对话、LLM 用压缩摘要
将压缩流程从物理删除原消息改为标记位 is_compacted 分离两个视图:
- LLM 视角(load_messages_for_topic):只看 is_compacted=0 的压缩摘要+新消息
- UI 视角(load_messages_for_topic_full):看原消息(含 is_compacted=1)+新消息,排除摘要

新增 compact_topic_history:不删除原消息,仅打标记+插入摘要,
同事务内删除旧摘要避免累积。修复 token 统计因压缩丢失 usage 的问题。
2026-08-05 12:46:43 +08:00
oudecheng
bb2774c6b5 feat(web): token 统计从侧边栏迁移到右侧面板,支持子代理每轮刷新
- 新增 TopicTokenStatsPanel 面板,显示五字段 + 上下文占用进度条

- 移除 TopicList 侧边栏的 token 统计显示

- 后端新增 get_session_token_stats,按 session_id 精确查询子代理 token

- TaskMessagesLoaded 协议扩展 token_stats 字段,ws.rs 转发至前端

- useMessages/useSubAgentView 每轮 assistant_response/execution_completed 触发刷新

- App.tsx 根据当前视图分派 list_topics(主代理)或 load_task_messages(子代理)

- 抽取 utils/tokenStats.ts 共享格式化与占用率计算函数
2026-08-05 12:09:40 +08:00
oudecheng
beb7b581a6 fix(web): 主代理执行结束时刷新 topic 列表以更新 token 统计
LLM usage 在消息持久化后已写入 DB,主代理本次执行结束时触发 bumpTopicRefreshTrigger,list_topics 重新读取最新 token 统计并更新侧边栏。
2026-08-05 11:15:12 +08:00
oudecheng
6fc6d5628f perf(gateway): send_with_retry 对 ChannelFull 不重试,加速队列消费
ws 连接队列满时,sender task 原本重试 3 次浪费 1+2+4=7s,期间 channel 队列堆积。新增 ChannelError::ChannelFull 变体区分队列满错误,CliChannel::send 在 try_send Full 时返回该变体;send_with_retry 遇到 ChannelFull 立即返回不重试,sender task 记 warn(预期背压丢弃)而非 error。

- base.rs: 新增 ChannelFull 变体及 Display 实现
- cli.rs: try_send 错误区分 Full(ChannelFull,不可重试)与 Closed(SendError,可重试)
- outbound_dispatcher.rs: send_with_retry 短路返回 ChannelFull;run_sender_task 区分日志级别;新增 TestChannel.with_channel_full 及单测验证不重试(500ms 阈值内返回 + send 仅调用一次)
2026-08-05 11:14:53 +08:00