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.
This commit is contained in:
qing committed 2026-08-18 07:21:29 +08:00
1 parent aa15841d73
commit cccc946660
6 files changed
+690 -184

No files matched your search

+23 -2
View File
@@ -11,7 +11,7 @@ import threading
import time
from typing import Dict, List, Optional, Tuple
from common import Logger, i18n
from common import Logger, constants, i18n
from .base import Tool
@@ -110,6 +110,11 @@ class ToolRegistry:
deadline = time.time() + tool_timeout
final_result = None
timed_out = False
# N8:流式转发同样受 TOOL_MAX_RESULT_LENGTH 约束——此前仅非流式
# 返回值受限,超长内容(如记忆全文)经流式通道可完整推到前端
line_budget = constants.TOOL_MAX_RESULT_LENGTH
forwarded = 0
truncation_noted = False
while thread.is_alive() or not output_queue.empty():
if time.time() > deadline:
self.logger.warning(
@@ -119,9 +124,25 @@ class ToolRegistry:
try:
msg_type, content = output_queue.get(timeout=0.1)
if msg_type == "line":
piece = str(content) if content is not None else ""
if forwarded >= line_budget:
# 预算耗尽:停止转发,仅提示一次
if not truncation_noted:
truncation_noted = True
if self.current_output_queue:
self.current_output_queue.put(
("line", f"\n⚠️ 流式输出超过 {line_budget} 字符上限,已停止转发剩余内容\n"))
continue
remaining = line_budget - forwarded
if len(piece) > remaining:
piece = piece[:remaining] + \
f"\n⚠️ 流式输出超过 {line_budget} 字符上限,已截断"
forwarded = line_budget
else:
forwarded += len(piece)
# 发送到当前队列(会通过WebSocket发送)
if self.current_output_queue:
self.current_output_queue.put(("line", content))
self.current_output_queue.put(("line", piece))
elif msg_type == "complete":
final_result = content
# 也发送完整结果