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.
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.
--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.
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.
~/.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.
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.
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.
- 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
- 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.
- 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.
- 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
- 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
- Move all per-module security audit reports to docs/security/
- Move code review logs to docs/reviews/, analysis reports to docs/reports/
- Add docs/INDEX.md as single entry point for all project documentation
- Add documentation management conventions section to README.md
- Clean up empty per-module docs/ directories