Commit Graph
805 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 7dc6796f3a docs: reorganize aibox audit report - overview table, full conclusions, final state
Add a rounds overview table (baseline HEAD / findings / fix batch /
test baseline per round), extend the overall conclusion to cover all
five rounds, and append a final-state section consolidating high-risk
disposition, maintained/deferred items, and the 490-test baseline.
Fix Batch H item count (13 -> 19) to match the fix table.
2026-08-20 11:19:04 +08:00
qing f7fc06c0ff docs: sync aibox round-5 audit and model_config.py across root docs
Root README updates aibox audit rounds to five (Batch E/F/G/H/I/K,
490 tests) and adds the interactive model_config.py profile manager;
aibox README gains the v3.6 changelog entry (J1~J16 + Batch K waves
1-5); MODULES.md refreshes the doc count and aibox row; ARCHITECTURE
re-verified and deviation list extended.
2026-08-20 11:15:58 +08:00
qing 04e3357dd0 docs: add hot-pluggable service-card frontend dev plan (fsys /api/cards aggregator) 2026-08-19 12:25:11 +08:00
qing 2c6d1e8ce6 docs: record round-5 audit and batch K fixes in aibox report and README
审计报告追加第五轮(§13 发现 J1~J16 + §14 波次 1~5 落地记录),
README 同步测试清单与计数(490 项全过)。
2026-08-19 07:01:22 +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 f4fcf175ec fix(memdb): align _run.sh with CLI-passed args convention
_run.sh hardcoded --instance/--log-level while also forwarding "$@",
so core's services.yaml args produced a duplicate --instance and
boost program_options aborted startup. Now pure "$@" passthrough,
matching db2/ca/captcha2; binary defaults keep manual runs unchanged.
2026-08-19 05:00:41 +08:00
qing d513c5f892 feat(aibox): interactive model profile manager (model_config.py)
Menu-driven list/add/delete/connectivity-test for config.json models
section - no hand-written JSON needed. Key convention preserved: env
var name only, never plaintext (or empty = keyless local endpoint).
Delete auto-cleans dangling fallback refs; default profile via
api.default_model; save failure reported. 17 unit tests, 441 passed.
2026-08-18 03:26:53 +00:00
qing 4cb97318b5 docs: sync aibox audit batches G/H/I in root README and aibox README
- root README: aibox section notes four audit rounds (Batch E/F/G/H/I)
- aibox README: v3.5 changelog (Batch G/H/I), test suite count 424,
  test file listing and aibox.py line count refreshed
2026-08-18 03:15:10 +00:00
qing 31b2a4d6ca docs: batch I audit fixes record in aibox audit report
Batch I (remaining-item sweep): 10 fixes landed, 16 new tests
(test_i_fixes.py), 424 passed. Q7a / M4 / Q9 / F8 kept as-is with
rationales recorded.
2026-08-18 03:09:18 +00: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 9fee9a1478 docs: batch H audit fixes record in aibox audit report
Fourth round (HEAD d1f780e): 3-way parallel review + main-reviewer
verification + Linux PoC 6/6; 13 fixes landed, 26 new tests
(test_h_fixes.py), 408 passed.
2026-08-18 02:33:34 +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 d1f780e381 feat(aibox): verify SOCKS5 connectivity before saving proxy config
- set_proxy 先做完整 SOCKS5 握手 + generate_204 探测(trust_env=False,超时 4s/6s),验证失败不落盘并按类型归因提示(auth/timeout/connect/missing_dependency/probe_failed)

- 异常类型优先分类,消息关键词兜底:Windows 本地化错误消息不影响归因

- 新增 skip_check 逃生口(代理未启动的预配置场景);禁用代理不验证;password_env 指向空环境变量时提前精准提示

- 测试:+9 用例(mock 网络 + 真实异常类型),全量 383 passed 零回归
2026-08-18 07:44:17 +08:00
qing acb6ddbb4c docs: batch G audit fixes record in aibox audit report
Add round-3 audit (section 9): legacy item re-verification (17 items), v3.4 multi-model profile + L4 failover review (P1-P7), architecture compliance matrix. Add batch G fix record (section 10): 17 fixes across 5 waves, 20 new regression tests, full suite 375 passed.
2026-08-18 07:21:34 +08: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 aa15841d73 docs: sync aibox multi-model profile (fallback failover) in root README and ARCHITECTURE 2026-08-18 00:53:51 +08:00
qing 0246943165 feat(aibox): failover observability, timeout budget and Retry-After (L4c)
Observability: FailoverClient.get_stats() exposes aggregate counters
(requests / failovers / cooldown_skips / exhausted, snapshot copy);
new on_failover(from_name, to_name, reason) callback fires on every
failover decision — to_name=None marks chain exhaustion — for future
admin-panel/frontend wiring, wrapped so callback exceptions never
affect the request path.

Timeout budget: profile field "failover_timeout_budget" caps the total
seconds spent walking the chain (default unlimited; values <= 0 are
rejected to None with a warning). Once elapsed, remaining profiles are
skipped — prevents worst-case N x timeout stacking on longer chains.
Exhaustion is counted once per call via a single end-of-loop increment
(an initial double count at the budget branch was caught by the new
budget test and folded into the unified counter).

Retry-After: TransientLLMError now carries retry_after parsed from the
numeric Retry-After response header (non-numeric values like HTTP-dates
are ignored); FailoverClient._mark_fail uses it to extend that
profile's cooldown beyond the default 60s, respecting server-side
throttling on 429s. _fail_at entries became (fail_time, cooldown)
tuples; three existing tests that poke the internal map were updated.

Config: create_llm_client passes profile.failover_timeout_budget into
the FailoverClient constructor.

Tests: new TestL4cObservabilityAndBudget (13 cases: stats on
success/exhaustion/cooldown-skip, snapshot copy, hook called with
chain-tail None, hook exception safety, budget stops chain via mocked
monotonic, invalid budget ignored, config field plumbing, default
no-budget regression, Retry-After parse/ignore/extend-cooldown). Full
suite 355 passed.
2026-08-18 00:52:39 +08:00
qing d53eba77f0 feat(aibox): stream_chat first-event failover + cancel-flag guard (L4b)
FailoverClient.stream_chat now walks the chain but only fails over
while no event has been produced yet — replay before the first
reasoning/content/tool_call event is invisible to the user. Once any
event is out, failures propagate directly (replay would duplicate
frontend output and corrupt tool-call accumulation).

DeepSeekClient.stream_chat classifies non-200 responses: 429/5xx raise
TransientLLMError (failover-eligible), auth/param 4xx keep raising
RuntimeError. Chain exhaustion re-raises the last exception so the
existing rollback/error handling in think_and_respond catches it; an
all-cooling chain raises RuntimeError.

Cancel-flag guard: LLMClient.cancel() sets _cancelled because the
RequestException raised by force-closing the connection is
indistinguishable from a real network fault. FailoverClient.cancel()
is now defined explicitly (overriding __getattr__ delegation),
broadcasts to every client in the chain and sets the wrapper flag;
stream_chat/chat_completion reset it per call and suppress failover
when it is set — otherwise interrupting would make the system hop to
another vendor and keep generating against the user's intent.

Tests: new TestL4bStreamFailover (12 cases: failover before first
event on TransientLLMError/ConnectionError, no failover after first
event, cancel never fails over on both paths, cancel broadcast, chain
exhaustion, all-cooling, cooldown skip, success isolation, transient/
auth status classification). Full suite 342 passed.
2026-08-18 00:46:23 +08:00
qing 3d7f5ca3cb feat(aibox): chat_completion cross-profile failover via fallback chain (L4a)
When a profile declares "fallback": ["kimi", "local"], create_llm_client
now returns a FailoverClient wrapper holding the ordered chain. For the
idempotent non-streaming path (chat_completion — conversation
compression, light subagents):

- Fail over on transient errors only: HTTP 429/5xx (now raised as
  TransientLLMError by DeepSeekClient) and connection errors/timeouts
  (previously swallowed into None by chat_completion's catch-all, now
  re-raised). Auth/param 4xx failures keep returning None with no
  failover — config problems stay loud, consistent with the H1/H2
  error-early philosophy.
- Failed profiles enter a 60s in-process cooldown and are skipped
  until it expires; chain exhaustion returns None per the existing
  failure contract.
- Config structure errors fail at construction (non-list fallback,
  unknown profile name, self-reference — H2-style). Environmental
  problems (e.g. fallback profile missing its API key) skip that
  fallback with a warning instead of blocking the primary.
- Unspecified attributes delegate to the primary client via
  __getattr__ (name/model/api_key/cancel), so DeepSeekChat and
  set_client keep working unchanged. stream_chat still delegates to
  the primary directly; first-token failover is L4b.

create_llm_client refactored: single-profile construction extracted
into _construct_profile_client, shared by the main path and the
fallback chain (flat-api path untouched).

Tests: new TestL4aChatCompletionFailover (15 cases: chain build,
validation, 429/5xx/connection failover, 401 no-failover, exhaustion,
cooldown skip/expiry, missing-key skip with warning, transient raise,
4xx None regression, stream delegation); full suite 330 passed.
2026-08-18 00:42:57 +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 0e9efe4895 fix(aibox): validate model profile schema at config source (H2)
Profiles previously had no validation, so config errors surfaced far
from their origin and cryptically:
- explicit profile missing `model` fell back to the profile NAME as the
  model (a profile named "local" would send "model": "local")
- default profile missing `model` with no model param produced
  "model": null -> API 400
- missing `api_url` surfaced as requests InvalidURL

_resolve_model_profile now runs _validate_profile (dict type + api_url)
before reading fields and _require_model on the effective model,
raising ValueErrors that name the profile, the missing field, and the
config.json path to fix. Bare model names still legally substitute for
a default profile's missing model field.

Tests: new TestH2SchemaValidation (6 cases: missing model/api_url,
null-model via default profile, bare-name substitution, non-dict
profile, e2e create_llm_client); full suite 282 passed.
2026-08-17 23:31:06 +08:00
qing 4480bce684 fix(aibox): never fall back to deepseek.key for model profile endpoints (H1)
~/.cert/deepseek.key is a DeepSeek-only credential; when a models profile
specified its own api_key_env (e.g. MOONSHOT_API_KEY) but the env var was
unset, resolve_api_key silently read that file and sent the DeepSeek key
as Bearer to a third-party endpoint (cross-vendor credential leak).

resolve_api_key gains allow_key_file (default True for the legacy flat
api section); create_llm_client passes allow_key_file=profile is None so
any profile path raises a clear RuntimeError instead, pointing users to
set the env var or use "" for local keyless endpoints.

Tests: new TestH1KeyIsolation (4 cases) with a mocked readable
deepseek.key proving the profile path errors out while the flat api
section still reads the file; full suite 276 passed.
2026-08-17 23:28:25 +08:00
qing 58725c8a12 docs: sync async inference loop into module docs
pybot --loop HEF 后端异步闭环(执行上一步动作与 NPU 推理并行)与 actpred predict_async 流水线落地后未同步文档,本次补齐:

- pybot/README:--loop 补异步推理路径说明(滞后一拍,~16ms→~12ms)

- actpred/README:Hailo 节新增 predict_async 小节(缓冲语义/2.6× 实测)

- 根 README、MODULES.md、ARCHITECTURE.md:pybot 行与机器人数据链同步

- MODULES.md:docs md 计数 70→76;ARCHITECTURE §9 挂 panel 设计稿规划项
2026-08-17 22:56:30 +08:00
qing 90f5544673 docs(aibox): changelog F2 (registry timeout cleanup + reader finalization)
Batch F bullet now split into F1/F2 entries; F2 covers unified
timeout-path cleanup of tool streaming attributes and reader-thread
finalization with the CPython stream-lock correction; test count 272.
2026-08-17 22:49:45 +08:00
qing 5bcf22f9a7 fix(aibox): registry timeout cleanup + reader thread finalization (F2/N6+N4)
N6: both streaming-forward timeout paths (deadline hit, join(5)
expiry) previously returned early and skipped the
tool.streaming_output / tool.output_queue cleanup, leaving the daemon
thread writing through the instance attribute — zombie output could
leak into the next session's queue once the attribute got reassigned.
Now unified through a timed_out flag with cleanup at the single exit;
stream_tool_timeout becomes an instance attribute (3600s default,
test-tunable).

N4: reader-thread finalization after timeout/grace break. First
implementation followed the audit suggestion (close stdout + join),
but PoC regression proved close() blocks: CPython stream objects hold
an internal lock, so closing while readline is blocked parks until
readline returns (PoC-1 regressed 3s -> 15.2s, exactly the
Start-Sleep duration). Corrected strategy: when reader already got
EOF, close stdout to release the pipe fd; when reader is still
blocked, only join(timeout=2) then give up (daemon-bounded, thread
ends naturally when the grandchild exits and EOF arrives), keeping
the main flow prompt. Audit report updated with this correction.

Tests: new tests/test_f2_fixes.py (5 cases), PoC regression 5/5
(PoC-1/4 back to ~5s), full suite 272 passed.
2026-08-17 22:47:37 +08:00
qing 6ef5d11d9d docs(aibox): sync README with Batch E/F security fixes
- feature list & tool table: set_proxy password semantics (memory-only,
  password_env for persistence)
- config example: fix stale flat proxy section to actual proxy.socks5
  structure with password_env; add plaintext-not-persisted note
- changelog: new v3.3 section covering Batch E (command exec protection
  chain) and Batch F (credential protection), test count 267
2026-08-17 22:41:24 +08:00
qing eb88a77238 fix(aibox): never persist SOCKS5 proxy password to disk (F1/N1, +N11)
- config.set_proxy gains password_env (aligned with api_key_env
  convention): config file persists only the env var name; plaintext
  password lives in memory for the current session only
- _save_config deep-copies and nulls proxy.socks5.password before
  json.dump, so every save path is sanitized (defense in depth)
- get_proxy resolves password as in-memory plaintext first, then via
  password_env from os.environ; downstream consumers (llm_client,
  web tool curl args) unchanged
- legacy migration on load: plaintext found on disk is kept in memory
  for the session, file is immediately rewritten sanitized
- SetProxyTool exposes password_env and reports password source
- registry debug logs mask password/api_key/token/secret/authorization
  kwargs (N11)

Tests: new tests/test_config.py (3 cases: no plaintext on save,
password_env resolution, legacy migration); full suite 267 passed.
Audit report updated with Batch F progress.
2026-08-17 22:37:30 +08:00
qing a833cd0d5f fix(memdb): expire semantics + Boost 1.83 portability + backlog & test harness
- expire 改为 update+refresh_ttl:键不存在返回 false,与 del/update 约定一致
- _require_string 适配 Boost 1.83(boost::json::string 已非 std::string)
- 新增 --backlog(默认 128,libmsg 默认 10 短连接突刺易丢连接)
- Makefile 补 HOST/PORT 默认值,test-memdb 用 PID 文件替代失效的 kill %1
- 补测试:复制构造/源销毁后副本有效/refresh 延长 TTL/expire 不存在键
2026-08-17 14:29:41 +00:00
qing 207137c0dd fix(libmemdb): deep-copy semantics so indices point into the copy's own list
默认复制只拷贝容器,keys/time_touch 中的迭代器仍指向源对象的 list 节点,
源销毁或改动后副本悬垂(UB)。改为保留时间戳的深拷贝构造 + copy-and-swap 赋值。
2026-08-17 14:29:39 +00:00
qing b2ecb8e12b fix(libmsg): close fd-reuse race silently killing short-lived connections
worker 收 EOF 失败后过早清除 busy 标记,事件循环对同一 fd 重复投递任务,
迟到的失败标记经 fd 复用落在新连接上被 _reap_failed 误杀(客户端收到空 EOF)。
失败路径改由 _reap_failed 统一清除 busy 并关闭;_handle_client 增加防重复投递守卫。
memdb 短连接压力测试错误率从 5-9% 降至 0。
2026-08-17 14:29:37 +00:00
qing 910ab8aa38 feat(pybot): 闭环异步推理——执行上一步动作与 NPU 推理并行
run_loop 自动检测 predictor.predict_async 走异步路径:提交推理后并行
执行上一步动作(滞后一拍,10Hz 下 100ms),隐藏真实 I2C 执行耗时;
同步路径不变。pybot 97 测试全过(新增异步路径 + 滞后一拍执行验证)。
2026-08-17 21:59:25 +08:00
qing 8de7f6696c feat(actpred): HailoPredictor 异步推理接口——run_async 流水线 2.6× 吞吐
新增 predict_async()(4 缓冲池 + 完成回调 + _AsyncJob.result),与同步
predict 语义/输出完全一致(重构公共 _prepare_inputs/_fill_bindings/
_postprocess)。真实 HEF 实测:连续吞吐 6.0ms vs 同步 15.5ms;扩展测试
10 seed 随机/60 任务乱序对应/混合调用/缓冲压力/300 次稳定性/4 线程并发
全部 0 差异,190 单测 OK。
2026-08-17 21:46:26 +08:00
qing 1348660cd8 docs(aibox): second-round audit re-review at HEAD 177a9ac
- Batch E fixes re-verified: H1/H3/M1 solid, H2 solid but with two
  residuals (reader thread leak, subagent timeout blind spot), M2
  incomplete (timeout paths skip tool attribute cleanup)
- Legacy M3/M4/L1/L3 confirmed unfixed; L1 found second instance at
  db.py:459 (get_immediate_memos)
- New findings: 1 high (SOCKS5 proxy password persisted in plaintext),
  9 medium (drain thread leak, event-loop blocking RLock, FTS5 keyword
  injection, streaming bypass of result length cap, persistent prompt
  injection storage, TOCTOU on mark_as_triggered, etc.), 7 low
- Updated compliance matrix and proposed Batch F fix plan (F1-F6)
2026-08-17 17:51:43 +08:00
qing 177a9ac9a6 fix(aibox): close audit findings H1-H3/M1-M2 (command exec protection chain)
- H1: CommandLineTool.execute_streaming now purely delegates to injected
  executor; removes 150-line duplicate Popen loop that bypassed 1MB cap,
  cancel() and timeout protections
- H2: thread the blocking readline into a daemon reader thread; main loop
  polls queue with 0.1s ticks so timeout/1MB checks fire even for silent
  commands (verified: 3s timeout on 15s sleep, was 15s+ before)
- H3: stdin=DEVNULL on both Popen sites; stdin-waiting commands now hit
  EOF instead of deadlocking the tool thread forever
- M1: heredoc delimiter uniquification in write_file guidance (content
  line equal to AIBOX_EOF no longer truncates into shell injection)
- M2: 3600s total deadline on registry streaming forward loop

Verification: PoC regression 5/5 PASS (timeout/1MB/cancel/delegation),
full unit suite 264 passed. Audit report updated with Batch E results.
2026-08-17 17:28:41 +08:00
qing ce142fe32f docs(aibox): sync once-mode docs (MODULES/ARCHITECTURE/dev-doc)
- MODULES.md/ARCHITECTURE.md:aibox 条目补 --once CLI 与 models 多模型档案(三处同步惯例)
- 开发文档新增第 7 节实施状态:四批提交映射、与计划的 4 处偏差(--once-timeout 改名/models 默认空/worker-only 精简 prompt/退出码 1 边界)、验证结果
- 原 7/8 节顺延为 8/9
2026-08-17 17:12:15 +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 2b133f1531 docs: cv2.LUT 优化验收测试报告 + 方案文档实测数据更新
200 次均值端到端 16.1ms(3.3×),分段 3.0/12.5/0.6ms——瓶颈已转移至
NPU 本身;一致性 7 类输入 + 8 seed 随机全 0 差异;10Hz 预算占用 16%。
2026-08-17 15:28:15 +08:00
qing 40523f6f8a feat(actpred): HailoPredictor cv2.LUT 快路径——归一化+量化合并,端到端 3.3×
帧预处理从 26.5ms 降至 1.7ms(15.8×),端到端 52.4 → 16.0ms。惰性导入 cv2、
无 cv2 自动回退 float 路径;非 uint8/非 3 通道/尺寸不匹配输入走 PIL 转换
与 float 路径语义一致(7 类输入实测 max_diff=0)。OpenCV 5.0 仅支持单通道
LUT,逐通道应用后 merge。依赖加入 requirements.txt。
2026-08-17 15:10:40 +08:00
qing 8a5162c912 docs: 修订 Hailo 预处理优化方案——查表法实测证伪,改合并乘加为主
原型实测:numpy 高级索引查表无加速(26.6 vs 28.5ms),预期 18-20ms 不成立。
主方案改为合并系数乘加(-5ms,0 LSB 等价),cv2.LUT 列为可选激进路径;
预期收益修正为 ~47ms(纯 numpy)/ ~25-30ms(cv2)。
2026-08-17 14:48:26 +08:00
qing 116d6b63bd docs: actpred Hailo 预处理优化方案——查表法合并归一化+量化
新 640×480 数据集实测端到端 52.4ms,预处理 35.9ms 占 68%(float 归一化
27.2ms + 量化绑定 9.1ms,存在中间 float 冗余)。方案:256 项查表合并
归一化与量化(数值等价),预计预处理降至 3-6ms、端到端 ~18-20ms。
2026-08-17 14:41:15 +08:00
qing 0fa32cb5e7 feat(actpred): HailoPredictor 适配 v4 onehot HEF——1864 logits 解码 + 固定列覆盖
v4 onehot 模型 HEF 输出 (1,1,1864),原回归假设(输出=action_dim)导致
输入分类与维度校验失败。_classify_inputs 改为 pred_token 先判(s[1]==1)、
其余 3 维归 history;_derive_dims 识别 TOTAL_BINS 输出;predict 经 bins.decode
期望值解码并固定权重 0 列(与 predict.py 同语义),新增 column_weights 参数。
_npu_test.py 指向 v4 并增加 onehot 参考分支,NPU 实测 4 角 PASS、
HEF vs torch max_diff 0.5°。
2026-08-17 14:29:21 +08:00
qing 63c29680ce docs: panel 微服务开发文档——背景视图聚合控件片段
设计稿:panel:9201 提供背景视图,各服务以 /panel-widget/<id>
暴露前端片段,面板拉取聚合后以 iframe 沙箱插入;含 postMessage
协议、BFF 转发、CSP 分层隔离与 M1-M4 实现阶段划分
2026-08-17 00:43:02 +00:00