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.
- 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
- ReadFileTool: replace readlines() with on-demand range reading; add reverse block reading for large file tail; limit single line to 10KB; hard-cap max_lines to 500
- SubprocessCommandExecutor: replace capture_output with line-by-line Popen reading; enforce 1MB output limit with immediate process termination; merge stdout/stderr counting for both sync and streaming modes
- Raise TOOL_MAX_RESULT_LENGTH default from 10KB to 1MB as final safety net
- Add SECURITY_AUDIT.md documenting vulnerabilities and remediation roadmap
- Update README with memory safety features and v2.3 changelog
Prompt system bugs and improvements:
1. **SKILL_PREFIX bug fixed**: SKILL_PREFIX = "1_" only matched files starting
with "1_", causing 2_ through 10_ skill prompt files to be misclassified
as "other" files and sorted in wrong order (e.g., 10_task_handling.md
sorted before 2_memory_save.md due to lexicographic ordering). Replaced
with regex _SKILL_FILE_RE = re.compile(r'^(\d+)_') and _skill_sort_key()
that extracts the numeric prefix for proper numeric sorting.
2. **_init_system_prompts used unsorted list**: get_prompts_list() returned
prompts in os.listdir() lexicographic order, which put 100_STATUS.md
before 1_memory_search.md and 10_task_handling.md before 1_memory_search.md.
Now uses get_sorted_prompts_list() for correct ordering:
identity → skill prompts (1-10 by number) → soul → status.
3. **Verbose pre_prompt injected every turn (~30 lines)**: The pre_prompt
contained detailed workflow instructions (search_skills first, memory
search, ask_user, etc.) that duplicated what system skill prompts
already say, wasting ~300+ tokens per user message. Worse, the same
verbose prompt was injected on recursive think_and_respond calls after
tool execution, confusing the model with repeated "第一步搜索技能"
instructions when it was already mid-conversation with tool results.
- Reduced pre_prompt to 5 concise bullet points as a reminder
- Added is_continuation parameter: recursive calls after tool execution
skip pre_prompt entirely and just pass "请基于以上工具执行结果继续回答。"
- Removed post_prompt (save_memory/update_soul instructions already in
2_memory_save.md and 8_identity_management.md)
4. **Unreasonable large-file thresholds**: 3_file_operations.md default
said >50KB or >200 lines is "too large" — too conservative for code
files. Raised to 500KB/2000 lines; added tail=true and binary file
guidance.
5. **Overly rigid memory search instruction**: 1_memory_search.md said
"每次都要做!" (must do every time), causing unnecessary memory searches
for simple greetings/acknowledgments. Softened to "根据话题相关性判断",
with explicit examples of when to skip (greetings, confirmations).
New/changed methods:
- PromptManager._skill_sort_key(cls, filename) - extract numeric prefix
- PromptManager._prompt_sort_key(self, p) - tuple-based sort key
- PromptManager.get_sorted_prompts_list() - returns correctly ordered list
- PromptManager.get_skill_prompts() - uses regex instead of startswith("1_")
- DeepSeekChat.think_and_respond() - added is_continuation parameter