Commit Graph
59 Commits
Author SHA1 Message Date
qing e43aeeaf44 fix(aibox): Batch L - round-6 audit low-risk fixes (R1~R3)
- R1: /memos WS command tolerates malformed reminder_time (J1 gap)

- R2: handle_interrupt no longer cancels global CLI executor

- R3: unregister closes websocket + cancels in-flight processing tasks

- tests: 6 new cases in test_l_fixes.py, 493 passed on Windows

- docs: round-6 audit report section 16, README sync (all 72 findings fixed)
2026-08-20 11:46:47 +08:00
qing 457bb900fb fix(aibox): round-5 audit batch K - max_turns clamp, task input validation, XSS hardening, atomic writes, broadcast timeout
第五轮审计(J1~J16)全量修复 + 子代理 max_turns 钳制:
- 子代理 max_turns 钳制(0/None 回退默认 10,负数钳制 1)+ 14 项回归测试
- J1 任务系统投毒:add_memo 入库校验(时间/间隔/类型)、调度器预检与触发事务
  防御、展示路径兜底、register 移入 try、add_user_task 补 repeat_end_time 透传
- J2/J13 前端 XSS:三页面 marked 输出经 DOMPurify 净化、entry1 系统消息/任务
  分隔条改 textContent、CDN 脚本补 SRI
- J3 FTS 删除换序(先 fts 后内容表,索引不再残留)
- J5 原子写推广:新增 common/atomic.py(mkstemp+fsync+os.replace),config/prompt/
  history 全部接入;损坏配置备份后回退
- J6 单例初始化加锁(PromptManager/ConfigManager 并发首例不双重加载)
- J7 broadcast 并发发送 + wait_for 超时回收僵死连接
- J4/J8/J9~J12/J14~J16:验证异常脱敏、交互输入容错、MemoDatabase WAL+busy_timeout、
  memo 批量删除、/new 竞态、skills 引号转义、voice_client 清理、死代码移除、
  skill_manager 同名键/help 截断
2026-08-19 07:01:13 +08:00
qing db54921066 fix(aibox): batch I audit fixes - prompt write cap, light-subagent cancel propagation, atomic history writes
Remaining-item sweep (audit report §12):
- N9: cap prompt file writes (identity/soul/status, 10k chars) to bound
  persistent-injection surface
- F5: run_light_subagent switches to stream_chat + parent cancel_event
  (mid-stream interrupt); ExpressEmotionTool injects parent event
- Q4: busy_timeout=10s on mark_as_triggered (multi-process DB contention)
- Q5: malformed reminder_time tasks returned as due (were silently dropped
  by SQL-level string filter, which is now Python-side authoritative check)
- Q7b: load_conversation realpath prefix check (../ traversal defense)
- Q8: atomic JSON writes (tmp+os.replace) for index/current/archive
- F6: dedicated ws command executor for /new, /reload_prompts
- F9: --once hard-exit backstop after soft timeout
- PromptManager singleton lock; limit=0 semantics (None = no LIMIT)
2026-08-18 03:09:05 +00:00
qing d3acccd146 fix(aibox): batch H audit fixes - subagent cancel propagation, scheduler timeout, exec process-group reaping
Fourth audit round (see docs/reports/aibox_audit_20260817.md §11):
- H1: shared parent cancel event no longer cleared on subagent first think
  (N5 regression); parent-already-cancelled skips subagent start
- M1: /reload_prompts moved off event loop (run_in_executor, N3 sibling)
- M2: skill --help scan prewarmed before serving connections
- M3: scheduler task-level timeout (scheduler.task_timeout, default 600s)
- M4: get_recent_memories(None) capped instead of full-table load
- M5: memo delete now physically removes rows (was complete-only)
- M6: PromptManager.reload builds new list then atomically swaps
- L1-L12: read_file arg coercion, proxy save failure reported, SSE
  utf-8 tolerance, persona realpath check, truncation char boundary,
  compressor div0, skill path quoting, ask_user required, POSIX
  process-group kill (pgid captured at Popen), web scheme whitelist,
  registry redact substring match, Bearer redaction in failover logs
2026-08-18 02:33:17 +00:00
qing cccc946660 fix(aibox): batch G audit fixes - llm hardening, thread lifecycle, cancel propagation, db transactions
P-series (llm_client): truncate non-transient HTTP error body to 200 chars, cap Retry-After at 3600s, validate fallback profiles (missing model skipped with warning), dedupe fallback list, remove dead _cancelled flag, document FailoverClient concurrency contract.

N-series (aibox/registry): batch-level drain thread with try/finally reclamation (N2), subagent cancel propagation via shared parent event + watcher thread (N5), /new reset moved off event loop (N3), streaming forward bounded by TOOL_MAX_RESULT_LENGTH (N8), persona path boundary check (N12), broadcast except narrowed + discard (N13), per-connection task set registration (N14).

db/history: FTS5 quote escaping (N7), mark_as_triggered single BEGIN IMMEDIATE transaction (N10), LIMIT parameterization (L1), archive filename collision suffix (N16).

Tests: +20 cases in test_g_fixes.py, full suite 375 passed.
2026-08-18 07:21:29 +08:00
qing d29bb5504a feat(aibox): L-batch profile polish (display name, prefix escape, temperature, reasoning aliases)
L1 display name: client.name now resolves to the profile's
display_name field, falling back to the profile key — the dialogue
context no longer labels Kimi/Qwen sessions "DeepSeek". The legacy
flat api section keeps "DeepSeek" (zero breakage).
_resolve_model_profile now returns (profile, model, profile_name).

L2 prefix escape: "model:NAME" forces bare-model semantics even when
a profile happens to be named NAME; "profile:NAME" forces profile
semantics and raises a clear error (listing available profiles) when
missing, instead of silently falling back to bare-name behavior.
Empty prefixes are rejected. Wired through --model / create_llm_client
/ subagent model params.

L3 profile temperature: new profile field "temperature" becomes the
client's default_temperature. chat_completion/stream_chat treat
temperature=None as "use profile default, else the old hardcoded
0.3/0.7", so explicit caller values still win. DeepSeekChat and
run_light_subagent resolve CLI > profile > constants.DEFAULT_TEMPERATURE.

L5 reasoning field aliases: _parse_delta recognizes
reasoning_content / reasoning / thinking / thinking_content (first
non-empty wins), so non-DeepSeek vendors' thinking streams are no
longer silently dropped.

L4 (cross-profile failover) intentionally deferred: streaming
failover needs request-replay semantics and is feature-level work.

Tests: new TestLBatch (16 cases); 7 existing unpack sites updated to
the 3-tuple. Full suite 315 passed.
2026-08-18 00:06:23 +08:00
qing a583f44d5d refactor(aibox): make profile reasoning_enabled a constructor param (M3)
Profile-level reasoning_enabled was attached as an instance attribute
after construction only when a profile matched; the base class never
declared it and consumers probed with double getattr. Any code that
accidentally set client.reasoning_enabled would silently override the
global switch.

The flag is now an explicit LLMClient constructor parameter with
three-state semantics: True/False (profile-level explicit, or the
global api.reasoning_enabled value concretized when the profile omits
the field) and None (no profile -> consumer falls back to global).
create_llm_client passes it at construction and no longer mutates the
client afterwards; DeepSeekChat._reasoning_enabled simplifies to a
single getattr probe that still tolerates clients without the attr.

Tests: TestM3ReasoningConstructor (4 cases) covering profile True,
profile-omitted -> global concretized, base default None, and the
None -> global fallback at the consumer; one legacy assertion updated
to the new contract (attribute exists, value None). Full suite 299
passed.
2026-08-17 23:58:45 +08:00
qing 5f3ca9a246 fix(aibox): sync compressor and snapshots on client swap via set_client (M1)
run_full_subagent replaced sub_chat.client by direct assignment after
construction, leaving ConversationCompressor (bound to the original
client in DeepSeekChat.__init__) and the api_key/name snapshots
pointing at the default model — compression requests silently kept
using the default endpoint/model while the dialogue itself used the
requested one.

DeepSeekChat gains set_client(): swaps self.client and refreshes
api_key, name, and compressor.llm_client in one place. run_full_subagent
now routes model overrides through it.

Tests: new TestM1SetClient (2 cases: full reference refresh on a real
DeepSeekChat instance, and run_full_subagent calling set_client with
the new client); full suite 295 passed.
2026-08-17 23:48:40 +08:00
qing 77262f8c3a fix(aibox): inject available model profile names into create_subagent (M2)
The LLM had no way to know which profile names exist in the models
config section; a hallucinated name fell through to bare-name semantics
(default endpoint + bogus model) and failed at request time with API
400.

SubAgentTool now builds the model hint dynamically from config via
_model_names_hint(): description and the model parameter's JSON Schema
description both list the profile names, with the default profile
(api.default_model) marked. Empty or malformed models sections keep
the original wording untouched (zero breakage); exceptions degrade to
no hint.

README: tool table note, test count 293, new v3.4 changelog entry
summarizing the H1/H2/H3/M2 profile-hardening batch.

Tests: new TestM2SubAgentModelHint (5 cases); full suite 293 passed.
2026-08-17 23:42:04 +08:00
qing 84a3f6dd5e fix(aibox): make --model effective in WebSocket server mode (H3)
--model was accepted by parse_args but only consumed inside
handle_once_mode, so `python aibox.py --model kimi` (server mode)
silently ignored the override.

The model now flows as a process-wide override:
main() -> EnhancedAIManager(model=...) -> DeepSeekChat(model=...) in
all three AI creation paths: add_ai (main dialogue),
WebSocketHandler._create_session_ai (per-connection sessions), and
TaskScheduler._create_task_ai (scheduled tasks). Manager logs
"CLI 模型覆盖已启用" at startup for observability. --once mode
plumbing is unchanged; --model=None keeps config-driven behavior.

Tests: new TestH3ModelPlumbing (6 cases) mocking DeepSeekChat to
assert the constructor kwarg across manager/session/task paths plus
parse_args; full suite 288 passed.
2026-08-17 23:37:59 +08:00
qing d03dbc7f91 feat(aibox): batch D multi-model profiles + docs
- models 配置节:多 OpenAI Chat Completions 兼容端点按档案名切换,默认空 dict 零破坏回退旧 api 节
- --model 双语义:档案名(命中 models 节完整采用)/ 裸模型名(同端点换模型)
- resolve_api_key 档案级 env 三级语义:未配置回退 api.api_key_env / 显式名称 / 空串免密(Ollama/vLLM)
- 档案级 reasoning_enabled 附加 client 属性,DeepSeekChat._reasoning_enabled 优先读取
- llm_client 增加 openai_compatible/openai provider 别名
- README 更新:aibox/README 六处(特性/配置/多模型档案专节/CLI 参数/更新日志 v3.2/测试计数 264)+ 根 README aibox 小节
- 单测 17 项新增(test_models_profile.py),全套 264 项全过
2026-08-17 16:58:33 +08:00
qing a5af4bb404 feat(aibox): batch C once mode hardening
- SQLite WAL:subsystems/db.py 建库时 PRAGMA journal_mode=WAL,缓解多 CLI 进程并发写锁
- --worker-only CLI 参数:纯执行面模式(tool_profile=worker),注入精简 system prompt 避免人格/身份提示词污染
- --model / --temperature 覆盖参数:DeepSeekChat 构造透传,create_llm_client 与 stream_chat 两处生效
- 子代理工具失败透传:run_full_subagent(outcome) → SubAgentTool 打 _SUBAGENT_TOOL_FAILED_MARKER → _handle_tool_calls 检测置 orchestrator stats.tool_failed 并剥离标记
- Batch C 单测 5 项:worker profile 工具集、model/temperature 构造、子代理失败标记透传与剥离
2026-08-17 16:32:22 +08:00
qing 9e0d39c912 feat(aibox): batch B once mode refinements
- parse_args 新增 --no-save-memory / --show-reasoning
- DeepSeekChat.__init__ 增加 enable_memory 参数,干净禁用 MemoryTool(注册层不加入 schema)
- 修复 _think_and_respond_impl 压缩提示 print 污染 stdout:改走 output_callback(line)
- handle_once_mode 接入新参数:reasoning 默认静默 + show_reasoning 显式开启 + quiet 最高优先级
- 退出码 1 覆盖 orchestrator 工具异常(stats.tool_failed);worker 子代理非零码透传留 Batch C
- 新增 tests/test_once_mode.py(10 项:enable_memory 开关/回调分流/退出码状态/压缩提示不污染 stdout)
2026-08-17 16:00:45 +08:00
qing ce3fb7fc74 feat(aibox): add CLI once mode (--once/--quiet/--once-timeout)
- parse_args 新增 --once/--quiet/--once-timeout 参数
- 新增 handle_once_mode:stdout chunk 流式、stderr 过程行、SIGINT 三层取消、超时 Timer
- 退出码:0 成功 / 2 API 失败 / 3 中断 / 4 超时;与 WebSocket 零回归
- 配套开发文档:docs/reports/aibox_cli_once_mode_dev_doc.md
- 附带修复:memdb/test_memdb.py 并发测试硬编码端口
2026-08-17 15:45:06 +08:00
qing 8a9a061662 fix(aibox): 子代理返回截断修复——收尾报告自生成替换外部摘要器 2026-08-15 14:27:27 +08:00
qing 7f21b65428 fix(aibox): FTS5 中文检索 + Token 压缩阈值生效——Compressor 抽离解耦
- db.py: FTS5 改用 trigram 分词(中文可检索),旧 unicode61 表自动迁移回灌;search_memories 增加 LIKE 兜底(%/_ 转义、MATCH 异常防护)
- conversation_compressor.py: should_compress 补齐 Token 阈值判断(原为死配置),Token 触发时按比例压缩早期消息
- ConversationCompressor 抽离至 subsystems,注入 llm_client 打破与 DeepSeekChat 循环引用;aibox.py 3517→3018 行
- 新增 21 项测试(中文检索/旧表迁移/LIKE 转义/token 估算/压缩流程/失败回退),全量 232 项通过
2026-08-15 09:51:59 +08:00
qing 85133868f3 refactor(aibox): 抽离ConversationHistory至subsystems(Wave2阶段1)
- 新增 subsystems/conversation_history.py: 迁移对话历史管理器(318行),MemoryDatabase用TYPE_CHECKING注解避免循环导入
- subsystems/__init__.py: 导出ConversationHistory,更新docstring含Wave2
- aibox.py: 移除ConversationHistory类定义(3706->3388行),仅保留import与类型注解
- 新增 tests/test_conversation_history.py: 41项单测,覆盖构造/当前对话/归档/历史加载/memory_db集成
- 测试基线: 171->212项全过(12.13s),无回归
2026-08-13 16:54:32 +08:00
qing 791fac0efd refactor(aibox): 抽离B批12个工具类至tools包,附58个单测
B批(tools/):迁移依赖子系统的12个工具类(CommandLineTool/SearchSkills/ReloadSkills/InstallSkill/CreateSkill/MemoryTool/MemoTool/UpdateIdentity/UpdateSoul/SaveMemoryAndEndConversation/SetProxy/GetProxy/AskUser),aibox.py减少~1739行

修复MemoryDatabase.get_recent_memories(limit=None)的SQL错误(既有bug,None传给LIMIT ?导致datatype mismatch)

171个单测全绿,循环依赖障碍消除:B批工具直接依赖subsystems而非反向依赖aibox.py
2026-08-13 16:09:31 +08:00
qing eadba81c26 refactor(aibox): 抽离工具类与子系统,附单测基线
A 批(tools/basic_tools.py):迁移 7 个无外部依赖工具类(FileToolBase/ReadFileTool/WriteFileTool/ListFilesTool/SimpleWebCrawlerTool/GetCurrentTimeTool/CalculatorTool)

Wave1(subsystems/):迁移 5 个叶子子系统(CommandExecutor/SubprocessCommandExecutor/PromptManager/MemoryDatabase/MemoDatabase/SkillManager),aibox.py 减少 ~1440 行

新增 tests/ 单测 113 个用例全绿,依赖方向维持 common -> subsystems -> tools -> aibox 单向
2026-08-13 15:48:55 +08:00
qing 0cfb3a7581 refactor(aibox): extract hardcoded prompts to template files
- Move all default prompt content from PromptManager._ensure_* methods into aibox/prompts/ template files
- Add 8 new template files: 00_IDENTITY, 100_STATUS, 1_memory_search, 2_memory_save, 5_memo_management, 8_identity_management, 9_language_instruction, 11_emotion_management
- Refactor _ensure_* methods to copy from template directory via _ensure_from_template()
- _ensure_skill_files now auto-discovers N_*.md files instead of hardcoded list
- Update README.md with complete file manifest
- Single source of truth: aibox/prompts/ is the only prompt template source
2026-08-13 08:19:53 +08:00
qing 62fab2bc2c feat(aibox): strengthen proactive ask_user and express_emotion guidance
- Rewrite ORCHESTRATOR_TURN_GUIDANCE: remove conservative '宁缺毋滥' principle,
  add 5 trigger scenarios each for ask_user and express_emotion
- Rewrite conversation start guidance: split into 3 steps with concrete examples
- Improve AskUserTool description: from passive 'encountered a problem' to
  proactive 'actively care about user, clarify needs, confirm intent'
- Improve ExpressEmotionTool description: add 'like caring for a friend' tone
  and 4 usage scenarios
- Update 99_SOUL.md: add behavior rules 9 (proactive questioning) and 10
  (emotional expression), expand workflow from 7 to 9 steps adding emotion
  interaction and follow-up questioning
2026-08-13 07:59:53 +08:00
qing e9463a28fb feat(aibox): 每轮临时注入互动风格引导(情绪化表达/适时询问用户)
编排者每轮请求在 enhanced_messages 末尾临时拼接互动风格提醒
(ORCHESTRATOR_TURN_GUIDANCE),引导用 express_emotion/ask_user
保持有温度的对话,并带适度约束;不写入对话历史,工具调用递归续跑
时跳过,worker 子代理不受影响。
2026-08-13 04:04:08 +08:00
qing f623294af6 fix(aibox): 拦截编排者误调执行面工具并清理旧版 run_command 引导
主代理未注册 run_command 等执行面工具,但内置默认提示词与
search_skills/create_skill/install_skill 文案仍引导其直接调用。
新增 WORKER_ONLY_TOOLS 拦截:编排者误调时返回纠正信息引导改用
create_subagent;同步 3/4/6/7 号默认提示词与默认 SOUL 为编排者口径。
2026-08-13 03:56:19 +08:00
qing 45bc019bfc feat(aibox): 子代理输出转发开关 subagent_stream_output(默认开启)
将上一轮的「子代理实时输出可见化」固化为可配置开关,避免在生产环境刷屏或泄露中间过程。

- common/config.py: 默认配置新增 subagent_stream_output: true
- aibox.py run_full_subagent: 读取该开关;关闭时 _effective_output_callback 置 None,
  子代理在后台静默执行(主对话侧栏不显示其思考/工具调用/回答),开启时按原逻辑
  转发 line/chunk/reasoning。
- 仅影响主对话侧栏可见性,不影响子代理执行与最终返回摘要;老用户配置缺该键默认 True(兼容)。
2026-08-13 03:38:20 +08:00
qing c9eaf988b3 feat(aibox): 子代理实时输出可见化(转发 line/chunk 到主对话侧栏)
此前子代理(create_subagent / run_full_subagent)的输出回调只转发 reasoning_* 三类思考消息,
导致子代理真正的回答文本(chunk)与工具执行过程(line,含命令输出/工具调用)被丢弃,用户看不到。

改动:
1. run_full_subagent 的 sub_think_callback 白名单放开,新增转发 line / chunk;
2. SubAgentTool._subagent_output 处理 line(🔹 [子代理] 前缀)与 chunk;
3. chunk 文本先累积于 _subagent_chunk_buf,遇非 chunk 消息或 execute 结束时由
   _flush_subagent_answer 统一冲刷(🤖 [子代理回答]),避免侧栏逐字断行。
仅主对话侧可见,worker 执行子代理本身不受影响。
2026-08-13 03:36:32 +08:00
qing 23a10c37c7 feat(aibox): 对话开始阶段注入引导提示词(查阅记忆/询问用户/情绪化)
在 DeepSeekChat._init_system_prompts 末尾(连接初始化、/new、/reload_prompts 均会重跑)注入一段仅对主对话(编排者)生效的系统提示词,引导智能体在对话开始时主动:
1. 用 memory 工具查阅历史记忆、保持上下文连贯;
2. 主动关心/澄清意图,必要时用 ask_user 工具向用户提问;
3. 以情绪化方式开场,适时用 express_emotion 工具表达情绪。
worker 子代理不注入,避免执行面被人格/情绪干扰。
2026-08-13 03:29:37 +08:00
qing 518c8b2b90 feat(aibox): v2.9 编排者-执行者工具分层(tool_profile)
主对话仅保留记忆/情绪/调度/子代理派生等管理面工具,不再直接持有命令行;
子代理(worker)仅注册 7 个执行面工具,无记忆/人格/派生/提问能力,
修复子代理可改写主人格(update_soul)与递归派生的隐患。
附 v2.9 配套运行期提示词模板(aibox/prompts/)与实施方案文档。
2026-08-13 03:05:47 +08:00
qing 14db64f7a2 refactor(aibox): 工具基类 Tool/ToolRegistry 抽离至 tools 包
延续 common 包的拆分模式,形成 common <- tools <- aibox 单向依赖,
为后续工具子类逐步迁移铺路;aibox.py 瘦身约 230 行,工具子类与注册逻辑零改动。
2026-08-13 02:50:20 +08:00
qing 6db0cf017e fix(aibox): 补全 common/logger 缺失的 logging 导入并固化 common 包抽取
- common/logger.py 漏导 logging / RotatingFileHandler,导致 main() 在
  Logger.init_file_logging() 处抛 NameError;现已补回(抽取自 aibox.py 时
  未携带该 import)
- 固化 aibox 模块化重构成果:common/ 包(i18n/config/constants/logger
  + __init__ 重导出)已落地,aibox.py 删除内联 I18n 改从 common 导入
- requirements.txt 新增 PySocks 依赖
2026-08-12 09:41:33 +08:00
qing fc351f569a fix(aibox): 禁用 websockets 服务端 20s 协议层 ping 超时断连 (ping_interval=None) 2026-08-06 16:16:12 +08:00
qing d680dcd058 fix: 修复 WebSocket 长连接空闲断线问题
三处协同修复:
1. 前端 aibox.js 添加 25s 心跳(ping),防止 gateway 30s 空闲超时
2. gateway libhttp doWsRead 对 WS 代理连接跳过空闲超时
3. aibox.py _handle_tool_calls 启动 drain 线程实时排空输出队列,
   避免 SubAgent 等长时间工具执行期间前端收不到数据
2026-08-05 01:58:19 +08:00
qing d71f526af4 feat(aibox): 子代理思考过程实时输出到主对话侧栏
- run_full_subagent 新增 output_callback 参数,转发子代理 reasoning 事件
- SubAgentTool 新增 _subagent_output 回调,将子代理思考过程以 line 流式输出到前端
2026-08-05 00:16:34 +08:00
qing 09556c9ef4 feat(aibox): run_command 强制流式输出;子代理升级为完整工具代理;补充情绪应对策略
- run_command 移除 stream 参数,删除非流式 subprocess.run 执行路径,统一走流式输出
- 子代理 (create_subagent) 从轻量无工具升级为拥有全部工具的完整代理,支持多轮工具调用与 max_turns 限制,结束后自动总结
- 系统提示词补充子代理使用建议与情绪应对策略
2026-08-05 00:09:25 +08:00
qing dc52ec13ad fix(aibox): 工具结果超长仅返回提示,隔离各会话命令执行器
- 工具结果超过 max_result_length 时不再保留截断内容,仅返回提示信息,避免超大结果污染 AI 上下文

- 每个任务/WebSocket 连接使用独立 SubprocessCommandExecutor,避免 cancel() 误杀其他任务/用户正在运行的子进程
2026-08-04 13:11:37 +08:00
qing d921f84b83 feat(aibox): WebSocket连接用户独立AI实例,支持多用户并发对话
- 每个连接创建独立DeepSeekChat实例,拥有独立messages/工具注册表/消息锁
- 对话状态与并发控制从全局改为会话级,多用户可同时与AI对话互不干扰
- 中断/重置/重载提示词等命令改为针对当前连接的会话AI
- 技能管理器懒加载共享,避免每个连接重复扫描技能
2026-08-04 11:44:57 +08:00
qing 69a8eee192 feat(aibox): 调度器任务间上下文隔离
- 每个定时任务创建独立DeepSeekChat实例,拥有独立messages/工具注册表/消息锁
- 任务与WebSocket主对话互不干扰,消除并发冲突与上下文污染根因
- 共享技能管理器懒加载一次供所有任务复用,避免重复扫描技能
- DeepSeekChat支持传入skill_manager复用,任务实例history_manager=None不污染历史
2026-08-04 09:23:51 +08:00
qing fa9679b7a6 fix(aibox): 修复并发线程导致对话历史污染引发HTTP 400错误
- 添加线程锁(RLock)保护 self.messages,防止WebSocket与调度器线程并发修改
- 重写 _repair_tool_pairs 方法,支持穿透system消息查找tool响应、去重、清理孤立tool消息
- 新增 _validate_enhanced_messages API调用前最终校验,就地修复消息结构
- 调度器执行定时任务前先cancel+wait锁释放,避免与WebSocket对话冲突
- /new命令和global_exit_handler改用锁保护的reset_conversation,不再裸改messages
- 新增 is_busy() 非阻塞方法供外部判断AI是否正在处理中
2026-08-04 09:11:31 +08:00
qing 53ddcb364f feat(aibox): 创建/安装技能工具统一生成 .md 扩展名 + 下线旧版 smtp.py
- CreateSkillTool/InstallSkillTool 改为生成带 .md 扩展名的技能文件,与人格技能文件规范一致
- 同步更新 7_extension_skills.md 模板与工具描述
- 删除旧版 smtp.py(P0-1)并同步审计文档与 README
2026-08-04 08:26:57 +08:00
qing 02904f711e fix(aibox): 修复工具调用配对缺失导致的 insufficient tool messages 400
当 assistant 携带 tool_calls 但对应 tool 响应缺失(工具被中断/压缩丢弃)时,
下一次请求报 400。新增 _repair_tool_pairs 在构建请求前补齐缺失的 tool 响应。
2026-08-03 23:05:28 +08:00
qing d24066369b refactor(aibox): 解耦大模型调用,新增 llm_client 抽象层以支持多厂商切换
将 think_and_respond 与 _compress_via_llm 中的直接 DeepSeek API 调用
收敛到 LLMClient 抽象层(DeepSeekClient 实现 + create_llm_client 工厂),
统一流式事件格式,为子代理/多模型切换预留扩展点;配置新增 api.provider。
2026-08-03 23:03:03 +08:00
qing 417fda5479 refactor(aibox): 文件/网络操作工具改为返回命令行指引并移除beautifulsoup4依赖 2026-08-03 22:52:12 +08:00
qing 52052e51a8 feat(aibox): 在pre_prompt中新增情绪感知提醒
每轮首轮对话的用户输入前,新增【情绪感知提醒】段落,引导AI:
- 识别用户情绪状态并共情回应
- 按情绪状态调整回应策略(安抚/分享/道歉)
- 用户要求特定情绪模式时搜索对应技能
2026-08-02 14:30:20 +08:00
qing ffbe076a32 feat: add execute method to ExpressEmotionTool 2026-08-02 14:17:33 +08:00
qing 92820799fa 补全情绪化工具 2026-08-02 14:02:51 +08:00
qing 054f65f858 增加智能体生气的模板 2026-08-02 13:54:58 +08:00
qing 918f3ea32a fix(aibox): reasoning展示严格遵循配置文件reasoning_enabled开关
之前即使reasoning_enabled=false,模型自发返回的reasoning_content
仍会被动态展示——配置只管了API请求参数,不管展示逻辑。
现在添加constants.REASONING_ENABLED门控,关着时静默忽略。
2026-08-02 13:26:34 +08:00
qing b216535757 comment 2026-08-02 13:21:45 +08:00
qing af5cd02663 fix(aibox/botops): 思考区域按时间顺序分布,不再全部堆在顶部
问题根因:
1. ChatView.end_reasoning() 不清空 _reasoning_widget 引用,导致
   多轮工具调用时 start_reasoning() 直接 return,所有轮次的思考
   内容都被追加到第一个思考区域(在聊天区最顶部)。
2. AI widget 在工具调用期间不重置,导致后续轮次的思考区域被
   挂载在AI widget中间,顺序混乱。

修复:
1. end_reasoning()/abort_reasoning() 更新标签后清除引用(widget
   保留在DOM显示),下一轮 reasoning_start 创建新的思考区域。
2. 新增 ai_break 协议信号:服务端在工具调用执行前发送,前端
   break_ai() 清除AI widget引用(保留已显示内容),后续chunk
   创建新的AI widget,确保思考区域按时间顺序插入在两段AI文本之间。
3. ai_break 前flush chunk_buffer,防止工具调用前的最后文本片段丢失。
2026-08-01 21:10:42 +08:00
qing f3d06252bb fix(aibox/botops): 修复两处重复输出根因
1. 侧栏重复:_handle_tool_calls 中 msg_type in ("line", "complete")
   将流式工具末尾的 complete 信号也当作 line 转发,导致所有支持
   流式输出的工具(get_current_time、run_command、read_file等20+个)
   结果在侧栏显示两次。改为仅转发 "line" 类型。

2. 聊天区Markdown列表重复:Textual 的 Markdown.append() 在 chunk
   边界切断 Markdown 语法(如列表项 "- "被拆成两个chunk)时,增量
   解析可能导致内容重复渲染。改为累积完整文本后调用 update() 整体
   重新解析,避免增量解析的边界问题。
2026-08-01 21:01:31 +08:00
qing 27ab50ae68 fix(aibox/botops): 修复重复输出问题
1. 服务端:is_continuation(工具调用后递归继续)时不再重复发送
   "思考中"提示和reasoning_start信号,避免多轮工具调用时侧栏
   出现多条冗余"思考中"消息
2. BotOps:line消息过滤前缀从5个扩展到9个,过滤掉"分析工具结果"
   "需要调用工具"等被后续具体信息覆盖的过渡提示
3. BotOps:移除危险的fallback分支(未知消息类型直接append到AI
   聊天区),改为写入侧栏日志,防止非AI内容污染回复
2026-08-01 20:53:40 +08:00