nexforge-cli 0.3.0__tar.gz → 0.4.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/PKG-INFO +8 -8
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/README.md +8 -8
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/config.example.toml +12 -1
- nexforge_cli-0.4.1/docs/decisions/2026-09-11-/351/205/215/347/275/256/346/256/213/347/225/231/345/200/274/346/262/273/347/220/206.md +90 -0
- nexforge_cli-0.4.1/docs/decisions/2026-09-12-/345/206/205/345/256/271/351/207/214/347/232/204/346/240/207/350/256/260/344/270/215/345/276/227/345/275/223/346/210/220/346/266/210/346/201/257/350/276/271/347/225/214.md +65 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/decisions/README.md +2 -0
- nexforge_cli-0.4.1/docs/evidence/release-0.3.0.log +65 -0
- nexforge_cli-0.4.1/docs/evidence/release-0.4.0.log +46 -0
- nexforge_cli-0.4.1/docs/evidence/session-marker-boundary.log +101 -0
- nexforge_cli-0.4.1/docs/evidence/session-marker-boundary_fulltest.log +4 -0
- nexforge_cli-0.4.1/docs/evidence/session-marker-boundary_live.log +84 -0
- nexforge_cli-0.4.1/docs/evidence/session-marker-boundary_report.md +77 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/manual/01-/345/256/211/350/243/205/344/270/216/351/205/215/347/275/256.md +1 -1
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/manual/02-/347/225/214/351/235/242/344/270/216/345/277/253/346/215/267/351/224/256.md +1 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/manual/08-/350/207/252/345/256/232/344/271/211/351/205/215/347/275/256.md +9 -3
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/manual/09-/345/270/270/350/247/201/351/227/256/351/242/230.md +33 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/manual/10-/346/217/222/344/273/266/345/274/200/345/217/221.md +3 -2
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/manual/11-/345/274/200/345/217/221/346/240/241/351/252/214.md +2 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/manual/README.md +1 -1
- nexforge_cli-0.4.1/plugin_speech_summary/README.md +127 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/plugin_speech_summary/install_plugin.py +46 -17
- nexforge_cli-0.4.1/plugin_speech_summary/speech_summary.py +690 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/pyproject.toml +1 -1
- nexforge_cli-0.4.1/scripts/gates/eof_newline.py +106 -0
- nexforge_cli-0.4.1/scripts/gates/example_config.py +100 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/__init__.py +1 -1
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/config.py +48 -3
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/core/agent.py +16 -11
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/core/session.py +67 -48
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/i18n.py +9 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/provider/__init__.py +2 -2
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/store/session_store.py +39 -3
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/ui/screens/chat.py +19 -4
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/ui/slash_handler.py +30 -3
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/tests/test_check_gates.py +112 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/tests/test_config.py +135 -0
- nexforge_cli-0.4.1/tests/test_plugin_example_tts.py +640 -0
- nexforge_cli-0.4.1/tests/test_session_repair.py +301 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/tests/test_session_store.py +56 -0
- nexforge_cli-0.4.1/tests/test_tui.py +147 -0
- nexforge_cli-0.3.0/plugin_speech_summary/README.md +0 -68
- nexforge_cli-0.3.0/plugin_speech_summary/speech_summary.py +0 -374
- nexforge_cli-0.3.0/tests/test_plugin_example_tts.py +0 -144
- nexforge_cli-0.3.0/tests/test_session_repair.py +0 -141
- nexforge_cli-0.3.0/tests/test_tui.py +0 -56
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/.gitignore +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/INSTALL.md +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/STATS_API.md +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/assets/icons/agent.svg +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/assets/icons/cost.svg +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/assets/icons/edit.svg +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/assets/icons/err.svg +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/assets/icons/file.svg +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/assets/icons/model.svg +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/assets/icons/ok.svg +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/assets/icons/read.svg +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/assets/icons/running.svg +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/assets/icons/search.svg +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/assets/icons/shell.svg +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/assets/icons/thinking.svg +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/assets/icons/web.svg +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/assets/icons/write.svg +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/devlog/2026-07-07.md +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/doc//344/273/243/347/240/201/344/274/230/345/214/226/350/256/241/345/210/222.md" +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/doc//347/211/271/346/200/247/346/250/241/345/235/227/346/263/250/345/206/214/345/210/266-/345/256/236/346/226/275/350/256/241/345/210/222.md" +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/doc//351/241/271/347/233/256/346/200/273/347/233/221/345/276/252/347/216/257-/345/274/200/345/217/221/350/256/241/345/210/222.md" +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/decisions/2026-09-10-fails-loud/345/210/206/347/272/247.md" +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/decisions/2026-09-10-/344/274/232/350/257/235/344/270/215/345/217/230/351/207/217.md" +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/decisions/2026-09-10-/344/276/235/350/265/226/344/270/200/350/207/264/346/200/247.md" +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/decisions/2026-09-10-/345/233/236/346/224/276/350/214/203/345/233/264/351/207/215/345/256/232/344/271/211.md" +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/decisions/2026-09-10-/345/256/241/350/256/241/344/273/245/350/260/203/347/224/250/351/223/276/344/270/272/345/207/206.md" +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/decisions/2026-09-10-/350/203/275/345/212/233/345/217/257/347/224/250/346/200/247/344/270/212/346/212/245.md" +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/decisions/2026-09-10-/350/264/250/351/207/217gate/344/275/223/347/263/273.md" +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/decisions/2026-09-11-deepseek/346/241/243/344/275/215/347/256/200/345/214/226.md" +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/decisions/2026-09-11-/346/241/243/344/275/215/347/256/200/345/214/226/344/270/272/345/215/225/346/241/243flash.md" +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/decisions/2026-09-11-/347/273/210/347/253/257/347/212/266/346/200/201/345/205/234/345/272/225/346/201/242/345/244/215.md" +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/decisions/2026-09-11-/351/207/215/350/257/225/345/275/222/344/270/200/345/261/202.md" +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/evidence/D10_acceptance.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/evidence/D10_full_test.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/evidence/D11_a4.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/evidence/D11_full_test.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/evidence/D12_audit.md +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/evidence/D12_gap.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/evidence/D13_break.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/evidence/D13_e2e.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/evidence/D13_full_test.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/evidence/D13_gaps.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/evidence/D14_audit.md +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/evidence/D14_full_test.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/evidence/D14_recheck.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/evidence/D15_audit.md +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/evidence/D4_fast.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/evidence/D4_full_test.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/evidence/D4_hook.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/evidence/D4_scene_c.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/evidence/D4_scene_d.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/evidence/D4_scene_e.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/evidence/D5_audit.md +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/evidence/D5_b2_plugin.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/evidence/D5_full_test.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/evidence/D6_b3_a.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/evidence/D6_b3_b.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/evidence/D6_b3_c.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/evidence/D6_b4_provider.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/evidence/D6_full_test.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/evidence/D7_dedup.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/evidence/D7_full_test.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/evidence/D8_audit.md +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/evidence/D8_invariant.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/evidence/D9_after.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/evidence/D9_before.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/evidence/D9_decision.md +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/evidence/D9_full_test.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/evidence/D9_rebuild.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/evidence/E_cap_fulltest.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/evidence/E_capabilities.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/evidence/E_engines.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/evidence/E_engines_fulltest.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/evidence/E_tts_availability.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/evidence/E_tts_fulltest.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/evidence/E_tts_installer.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/evidence/E_vision_channels.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/evidence/E_vision_fulltest.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/evidence/F_decisions_fulltest.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/evidence/deps_fulltest.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/evidence/deps_reverse.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/evidence/exit-cost.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/evidence/final_check_fulltest.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/evidence/final_check_pypi.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/evidence/final_check_report.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/evidence/fix_fulltest.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/evidence/health_fulltest.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/evidence/health_report.md +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/evidence/health_scan.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/evidence/model-sidebar-dot.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/evidence/model_rename_check.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/evidence/provider-family-check.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/evidence/release_0218_fulltest.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/evidence/release_0218_pypi.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/evidence/simplify_tier_fulltest.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/evidence/terminal-leak-conditions.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/session-invariant.md +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/submission/README-/346/212/225/347/250/277/350/257/264/346/230/216.md" +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/submission/nexforge.md +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/docs/submission/nexforge.zh-CN.md +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/manual/03-/346/250/241/345/236/213/351/205/215/347/275/256.md" +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/manual/04-/345/267/245/345/205/267/345/217/202/350/200/203.md" +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/manual/05-/346/225/260/346/215/256/344/270/216/346/220/234/347/264/242.md" +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/manual/06-/346/235/203/351/231/220/344/270/216/346/250/241/345/274/217.md" +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/manual/07-/344/274/232/350/257/235/344/270/216/350/256/260/345/277/206.md" +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/manual//351/231/204/345/275/225-/345/257/271/346/257/224/345/210/206/346/236/220.md" +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/plugin_gui_auto/README.md +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/plugin_gui_auto/guiauto/__init__.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/plugin_gui_auto/guiauto/action.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/plugin_gui_auto/guiauto/base.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/plugin_gui_auto/guiauto/tools.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/plugin_gui_auto/guiauto/vision.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/plugin_gui_auto/guiauto/window.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/plugin_gui_auto/install_plugin.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/plugin_gui_auto/requirements.txt +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/plugin_gui_auto/smoke_safe.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/plugin_gui_auto/verify_final.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/plugin_gui_auto/verify_host.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/plugin_gui_auto/verify_plugin.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/scripts/__init__.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/scripts/check.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/scripts/exit_cost_probe.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/scripts/exit_smoke.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/scripts/gates/__init__.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/scripts/gates/base.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/scripts/gates/hygiene.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/scripts/gates/manual_consistency.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/scripts/gates/registry_consistency.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/scripts/gates/version_sync.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/scripts/install_hooks.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/scripts/publish.sh +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/scripts/smoke_provider.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/scripts/smoke_tui.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/__main__.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/app.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/cli.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/core/__init__.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/core/capability.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/core/context.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/core/federation.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/core/goal_manager.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/core/jobs.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/core/knowledge_base.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/core/memory_engine.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/core/model_catalog.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/core/permissions.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/core/plugin.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/core/project_mapper.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/core/provider.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/core/summarizer.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/core/system_inject.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/core/tool_manifest.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/core/types.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/core/update_check.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/features/__init__.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/features/auto_skills.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/features/base.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/features/federation_feature.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/features/kb_feature.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/features/map_feature.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/features/memory_feature.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/features/mode_features.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/features/plan_feature.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/features/registry.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/features/session_feature.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/features/slash_feature.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/features/stats_feature.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/features/tool_manifest_feature.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/icons.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/mcp/__init__.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/provider/_retry.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/provider/anthropic.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/provider/balance.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/provider/deepseek.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/provider/openai.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/provider/qianwen.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/skills/loader.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/stats/__init__.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/stats/heartbeat.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/store/__init__.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/store/director_store.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/store/plan_store.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/terminal_guard.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/theme.tcss +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/tools/__init__.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/tools/_paths.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/tools/background.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/tools/base.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/tools/data_tools.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/tools/edit_file.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/tools/federation.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/tools/fs_ops.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/tools/git.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/tools/image_tools.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/tools/kb_tools.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/tools/list_dir.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/tools/multi_edit.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/tools/ops_tools.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/tools/read_file.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/tools/registry.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/tools/screenshot_tools.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/tools/search.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/tools/search_content.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/tools/search_files.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/tools/shell.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/tools/web.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/tools/write_file.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/ui/__init__.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/ui/clipboard_image.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/ui/screen_api.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/ui/screens/__init__.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/ui/screens/chat_actions.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/ui/screens/splash.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/ui/widgets/__init__.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/ui/widgets/apikey_modal.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/ui/widgets/banner.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/ui/widgets/confirm_modal.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/ui/widgets/diff_modal.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/ui/widgets/file_browser.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/ui/widgets/file_menu.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/ui/widgets/image_sidebar.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/ui/widgets/messages.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/ui/widgets/mode_warning.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/ui/widgets/model_sidebar.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/ui/widgets/multiline_modal.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/ui/widgets/multiline_prompt.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/ui/widgets/plan_sidebar.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/ui/widgets/sidebar.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/ui/widgets/statusbar.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/utils/__init__.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/utils/logging.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/web/__init__.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/web/server.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/src/nexforge/web/static/index.html +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/tests/conftest.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/tests/test_app_mouse.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/tests/test_approval.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/tests/test_archive_integrity.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/tests/test_capability.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/tests/test_cross_platform.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/tests/test_deepseek_provider.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/tests/test_deps.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/tests/test_director_loop.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/tests/test_director_store.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/tests/test_dual_panel.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/tests/test_exit_smoke.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/tests/test_federation.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/tests/test_file_browser.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/tests/test_goal_confirm.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/tests/test_i18n.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/tests/test_icons.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/tests/test_lang_cmd.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/tests/test_m2_tools.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/tests/test_manual_consistency.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/tests/test_model_catalog.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/tests/test_model_sidebar_dot.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/tests/test_p2_core.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/tests/test_plugin.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/tests/test_project_map_prune.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/tests/test_provider_errors.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/tests/test_quit_paths.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/tests/test_retry_policy.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/tests/test_sanitize.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/tests/test_screenshot_tools.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/tests/test_session_invariant.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/tests/test_session_save_safety.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/tests/test_sidebar.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/tests/test_sidebar_actions.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/tests/test_sidebar_align.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/tests/test_splash_modal.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/tests/test_statusbar.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/tests/test_system_inject.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/tests/test_terminal_guard.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/tests/test_tool_iters.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/tests/test_tools.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/tests/test_update_check.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/tests/test_vision_capability.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/tests/test_vision_model.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.1}/tests/test_vision_tool_retention.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: nexforge-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.1
|
|
4
4
|
Summary: NexForgeCLI — 一个面向 DeepSeek V4 的终端智能体(TUI + CLI 双模式)
|
|
5
5
|
Project-URL: Homepage, https://gitee.com/Lighthorn/nexforgecli
|
|
6
6
|
Author: NexForge
|
|
@@ -51,14 +51,14 @@ Description-Content-Type: text/markdown
|
|
|
51
51
|
|
|
52
52
|
NexForgeCLI 是一个运行在终端里的智能体助手,既支持 DeepSeek/千问等多模型,也是**插件宿主**(plugins 目录即插即用)。既能作为全屏 TUI 交互式对话/编码,也能作为 one-shot 命令行工具嵌进脚本和管道。
|
|
53
53
|
|
|
54
|
-
> 当前版本:**v0.
|
|
54
|
+
> 当前版本:**v0.4.1** · PyPI: `pip install nexforge-cli`
|
|
55
55
|
|
|
56
|
-
**v0.
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
56
|
+
**v0.4.1 要点(重要修复)**:会话重建时,**内容里出现的 `<!-- nf-msg … -->` 不再被当成消息边界**。
|
|
57
|
+
此前若工具结果 dump 过会话文件(或粘贴过会话内容),加载时那些标记会被解码成额外消息、
|
|
58
|
+
插进工具块中间,导致**每轮请求 400、项目再也打不开**。
|
|
59
|
+
修法:读侧标记识别改为**行首锚定**,写侧对内容里的标记前缀做转义;同时把工具序列修复
|
|
60
|
+
从"只修尾部"升级为"修全序列"(中段违规也能纠正)。
|
|
61
|
+
另含 0.4.0 推出的:本机 TTS 优先/回退、模型随 nf 常驻、`/voice full|brief`、配置残留值自动收敛。
|
|
62
62
|
|
|
63
63
|
---
|
|
64
64
|
|
|
@@ -8,14 +8,14 @@
|
|
|
8
8
|
|
|
9
9
|
NexForgeCLI 是一个运行在终端里的智能体助手,既支持 DeepSeek/千问等多模型,也是**插件宿主**(plugins 目录即插即用)。既能作为全屏 TUI 交互式对话/编码,也能作为 one-shot 命令行工具嵌进脚本和管道。
|
|
10
10
|
|
|
11
|
-
> 当前版本:**v0.
|
|
12
|
-
|
|
13
|
-
**v0.
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
11
|
+
> 当前版本:**v0.4.1** · PyPI: `pip install nexforge-cli`
|
|
12
|
+
|
|
13
|
+
**v0.4.1 要点(重要修复)**:会话重建时,**内容里出现的 `<!-- nf-msg … -->` 不再被当成消息边界**。
|
|
14
|
+
此前若工具结果 dump 过会话文件(或粘贴过会话内容),加载时那些标记会被解码成额外消息、
|
|
15
|
+
插进工具块中间,导致**每轮请求 400、项目再也打不开**。
|
|
16
|
+
修法:读侧标记识别改为**行首锚定**,写侧对内容里的标记前缀做转义;同时把工具序列修复
|
|
17
|
+
从"只修尾部"升级为"修全序列"(中段违规也能纠正)。
|
|
18
|
+
另含 0.4.0 推出的:本机 TTS 优先/回退、模型随 nf 常驻、`/voice full|brief`、配置残留值自动收敛。
|
|
19
19
|
|
|
20
20
|
---
|
|
21
21
|
|
|
@@ -14,7 +14,7 @@ flash = "deepseek-flash" # 原生多模态(文本+读图), 取
|
|
|
14
14
|
default_tier = "flash" # 档位只剩 flash(pro/auto/vision 均已移除)
|
|
15
15
|
thinking = true # 思考模式,默认开
|
|
16
16
|
reasoning_effort = "high" # high | max
|
|
17
|
-
max_output_tokens =
|
|
17
|
+
max_output_tokens = 32768 # 含思考 token; 别写 8192(思考会吃满 → 正文截断秒停)
|
|
18
18
|
|
|
19
19
|
# 定价(每百万 token,USD)—— 已核实
|
|
20
20
|
[models.pricing.flash]
|
|
@@ -28,3 +28,14 @@ show_thinking = true
|
|
|
28
28
|
icons = "auto" # auto | image | glyph | ascii
|
|
29
29
|
accent = "#6D5EF5"
|
|
30
30
|
accent2 = "#22D3EE"
|
|
31
|
+
|
|
32
|
+
# 语音播报(需安装内置示例插件 speech_summary)
|
|
33
|
+
[tts]
|
|
34
|
+
enabled = true # 总开关
|
|
35
|
+
engine = "auto" # auto(本机 TTS 优先→微软回退) | cosyvoice | local | qianwen
|
|
36
|
+
cosyvoice_url = "http://127.0.0.1:9880" # 本机 CosyVoice 常驻服务(其 local_deploy/service)
|
|
37
|
+
cosyvoice_voice = "default" # 本机服务音色 id
|
|
38
|
+
cosyvoice_timeout_s = 90 # 冷启动约 20s, 留足余量
|
|
39
|
+
cosyvoice_resident = true # nf 在跑就让模型常驻显存; nf 全退出后由服务立即卸载
|
|
40
|
+
max_chars = 0 # 播报文本上限(0=不限; 配合下面的 mode)
|
|
41
|
+
mode = "full" # 播报范围: full=全量播报最终报告(默认) | brief=只播第一段(/voice 切换)
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# 决策:配置残留值治理(自动收敛 + 必须可见)
|
|
2
|
+
|
|
3
|
+
> 日期:2026-09-11 · 状态:生效
|
|
4
|
+
|
|
5
|
+
## 背景
|
|
6
|
+
|
|
7
|
+
这个软件会**把自己的状态写回配置**:`save_config()` 会把 UI 模式、当前 provider、
|
|
8
|
+
档位、已验证名单等写进 `config.toml`/项目级配置。于是当**代码侧删掉或改名一个取值**
|
|
9
|
+
(档位、provider、字段)后,用户机器上那份由旧版本写下的值就成了**残留值**——
|
|
10
|
+
它在下一次启动时会被读到,然后:
|
|
11
|
+
|
|
12
|
+
- 要么**让启动直接失败**(用户被挡在门外,只能手改 TOML),
|
|
13
|
+
- 要么被**静默忽略**(配置写了却没效果,是"最难查"的那类困惑),
|
|
14
|
+
- 要么**让软件对自己的文件报假警**(检查器不认识自己写的字段)。
|
|
15
|
+
|
|
16
|
+
2026-09-11 一天内实际踩到三类,都不是理论推演:
|
|
17
|
+
|
|
18
|
+
| # | 残留值 | 现场后果 | 证据 |
|
|
19
|
+
|---|---|---|---|
|
|
20
|
+
| 1 | `models.default_tier = "pro"`(旧版 `/model pro` 持久化) | **启动中止**(exit 2):`Input should be 'flash'` | 第三轮复核;实验 exit 2 → 修复后 exit 0 |
|
|
21
|
+
| 2 | `provider.default = "deepseek-v41"`(该 provider 已从 models.toml 移除) | TUI `初始化失败`,并**连带报出误导性的"缺少 API Key"** | 用户截图 + 本轮复盘 |
|
|
22
|
+
| 3 | `[provider] verified = [...]`(nf 自己写的字段) | 未知字段检查**误报**:"未知字段 'verified'(已忽略, 请检查拼写)" | 用户启动告警 |
|
|
23
|
+
|
|
24
|
+
## 决策
|
|
25
|
+
|
|
26
|
+
**残留值一律"自动收敛 + 可见告警",并按下述判据分级;静默迁移不再允许。**
|
|
27
|
+
|
|
28
|
+
### 判据(新增一条收敛规则前先回答这两个问题)
|
|
29
|
+
|
|
30
|
+
1. **这个值的意图是否唯一可知?**
|
|
31
|
+
- 唯一可知(已移除的档位只剩 `flash`;不存在的 provider → 回退内置 `deepseek`)
|
|
32
|
+
→ **自动收敛**为唯一合理值,并给 `warning`(**不阻断启动**)。
|
|
33
|
+
- 不可知(拼写错误 `flsh`、类型错误、TOML 损坏)
|
|
34
|
+
→ **仍然 `error`**,照旧中止启动。**不许猜**。
|
|
35
|
+
2. **用户看到告警后能否知道去哪儿改?**
|
|
36
|
+
告警的 `source` 必须是**真正写了该值的文件路径**(用户级 vs 项目级要分清),
|
|
37
|
+
文案要给出下一步动作(改哪个键 / 用哪条命令)。
|
|
38
|
+
|
|
39
|
+
### 三类处置(现有实现,作为范式)
|
|
40
|
+
|
|
41
|
+
| 类别 | 例子 | 处置 | 实现位置 |
|
|
42
|
+
|---|---|---|---|
|
|
43
|
+
| 已知移除的**取值** | `default_tier = "pro"/"auto"/"vision"` | 收敛为 `flash` + warning | `config.py:_migrate_removed_tier()`(`REMOVED_TIERS`)|
|
|
44
|
+
| **悬空引用** | `provider.default` 不在 provider 列表里 | 收敛为内置 `deepseek` + warning(指名文件)| `config.py:load_config()` 收尾校验 |
|
|
45
|
+
| **未知字段/段** | `defualt`、`[router]` | warning + 忽略(**保留**:可能只是拼错,也可能是新版本字段)| `config.py:_check_unknown_fields()` |
|
|
46
|
+
| 不可知错误 | TOML 损坏、枚举/类型不合法 | `error` + 中止(exit 2)| `load_config()` / `cli.py:has_config_errors()` |
|
|
47
|
+
|
|
48
|
+
### 纪律
|
|
49
|
+
|
|
50
|
+
- **禁止静默迁移**。历史上的 `ui.mode: review → supervised` 是无告警迁移,属遗留;
|
|
51
|
+
新增迁移一律带 `ConfigIssue("warning", ...)`。(判据:用户必须能知道"我写的值被改了"。)
|
|
52
|
+
- **软件自己写的字段,必须在 schema 里声明**。写入方(`save_config`)与
|
|
53
|
+
校验器(`_check_unknown_fields`)之间的契约靠 `model_fields` 维系;
|
|
54
|
+
漏声明就会"自己对自己报假警"(上表 #3)。
|
|
55
|
+
- **错误文案不许误导**。例:agent 建不起来时,有 Key 要说"provider 初始化失败",
|
|
56
|
+
只有确实没 Key 才说"缺少 API Key"(`ChatScreen._not_initialized_message()`)。
|
|
57
|
+
- **每条收敛规则都要成对测试**:正例(收敛 + 告警存在 + 不阻断)与
|
|
58
|
+
反例(真错误仍然报错 / 合法值不被改动)。
|
|
59
|
+
|
|
60
|
+
## 理由
|
|
61
|
+
|
|
62
|
+
- **不让用户被挡在门外**:残留值是"旧版本写下的合法值",用户无从预知;让它中止启动
|
|
63
|
+
等于把升级成本转嫁给用户,而语义其实唯一(只能回退内置/唯一档位)。
|
|
64
|
+
- **但也不能悄悄改**:静默收敛会让用户"以为配置生效了",与
|
|
65
|
+
`ADR-2026-09-10-fails-loud分级` 冲突——那里只允许**清理/关闭类**失败静默,
|
|
66
|
+
而配置变更属于"用户期待的功能"。
|
|
67
|
+
- **否决"只报错、不收敛"**:三种残留都是**旧版本自动写入**的,惩罚用户不合理。
|
|
68
|
+
- **否决"只收敛、不告警"**:用户无法察觉配置被改,也无法修正源头。
|
|
69
|
+
- **否决"把 unknown field 也自动收敛"**:拼写错误与"未来字段"无法区分,猜错更糟。
|
|
70
|
+
|
|
71
|
+
## 代价与边界
|
|
72
|
+
|
|
73
|
+
- 收敛会让"用户显式写的无效值"被覆盖(如 `default_tier = "flsh"` 仍报错,
|
|
74
|
+
但 `"pro"` 被改成 `"flash"`)。判据是**语义唯一**:仅对"代码侧已知移除/改名"的值生效,
|
|
75
|
+
清单必须显式维护(`REMOVED_TIERS`),不能靠模糊匹配。
|
|
76
|
+
- 悬空 `provider.default` 收敛到内置 `deepseek` 后,用户若本意是用某个自定义 provider,
|
|
77
|
+
需自己改回(告警已指名文件与键名)。
|
|
78
|
+
- 项目级配置与用户级配置都可能持有残留值:**分别检查、分别归属**(告警要指出是哪一个)。
|
|
79
|
+
- 每次删除/改名一个"会被写回配置"的取值时,**必须同日补一条收敛规则 + 测试**,
|
|
80
|
+
否则下一位用户就会以"启动失败"或"假警报"的形式替你发现它。
|
|
81
|
+
|
|
82
|
+
## 相关
|
|
83
|
+
|
|
84
|
+
- `src/nexforge/config.py`:`REMOVED_TIERS`/`_migrate_removed_tier()`、`load_config()` 收尾校验、
|
|
85
|
+
`_check_unknown_fields()`、`save_config()`(写入方)、`ProviderConfig.verified`(自己写的字段)
|
|
86
|
+
- `src/nexforge/ui/screens/chat.py`:`_not_initialized_message()`(文案不许误导)
|
|
87
|
+
- `src/nexforge/provider/__init__.py`:`ProviderError` 的修复指引
|
|
88
|
+
- 测试:`tests/test_config.py`(档位迁移/悬空 provider/verified 已知字段/反例)
|
|
89
|
+
- ADR-2026-09-10-fails-loud分级(可见性分级的上位规则)
|
|
90
|
+
- ADR-2026-09-11-档位简化为单档flash、ADR-2026-09-11-deepseek档位简化(残留值的来源)
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# 决策:内容里的 `<!-- nf-msg … -->` 不得被当成消息边界
|
|
2
|
+
|
|
3
|
+
> 日期:2026-09-12 · 状态:生效
|
|
4
|
+
|
|
5
|
+
## 背景
|
|
6
|
+
|
|
7
|
+
会话重建的权威是每行消息末尾的结构化标记 `<!-- nf-msg <base64> -->`(`docs/session-invariant.md` §4.1)。
|
|
8
|
+
但解析用的是**全文 `findall`**,于是"内容里出现的标记"与"真正的消息边界"无法区分。
|
|
9
|
+
|
|
10
|
+
触发链(实测,NexForgeCLI 自己的 `session-122357`):
|
|
11
|
+
|
|
12
|
+
1. 工具结果 dump 了某个会话 `.md` 文件(`read_file .nexforge/sessions/session-…md`),
|
|
13
|
+
被 dump 的标记行作为**内容**落进新会话文件;
|
|
14
|
+
2. 下次加载时,这些"内容里的标记"被解码成**额外消息**,且插在
|
|
15
|
+
`assistant(tool_calls)` 与它的 `tool` 响应之间(实测多出 10 份重复助手消息);
|
|
16
|
+
3. 请求违反 OpenAI/DeepSeek 规则——"带 tool_calls 的 assistant 后必须紧跟覆盖全部
|
|
17
|
+
tool_call_id 的 tool 消息"——返回 400
|
|
18
|
+
`insufficient tool messages following tool_calls message`;
|
|
19
|
+
4. `_repair_incomplete_tail` 只修**尾部**,看不见中段违规 → 每轮请求都 400。
|
|
20
|
+
用户侧现象:**"项目再也打不开"**(打开后说什么都报错)。
|
|
21
|
+
|
|
22
|
+
## 决策
|
|
23
|
+
|
|
24
|
+
三层一起上(缺任一层都有残留口子):
|
|
25
|
+
|
|
26
|
+
1. **写侧转义**(`_escape_marker_prefix`):人读部分里出现的 `<!-- nf-msg` 一律插入零宽空格
|
|
27
|
+
`\u200b` 转义。标记内的 base64 负载不受影响 → 重建权威无损
|
|
28
|
+
(§4.1 本就允许人读部分变形/截断)。
|
|
29
|
+
2. **读侧锚定**(`_MARKER_RE` 加 `re.M` 与行首 `^`):只有**独立成行**的标记才是边界。
|
|
30
|
+
真标记必然在行首(所有人类可读块都以换行结尾),全仓库历史会话文件实测零反例。
|
|
31
|
+
3. **全序列修复**(`_repair_incomplete_tail` → `_repair_tool_sequence`,旧名保留为别名):
|
|
32
|
+
修复**整条序列**而不是尾部 —— 夹层消息**保序后移**、缺响应的 tool_call 补"已取消"、
|
|
33
|
+
重复响应与无主孤儿 tool 删除。修复后的序列对 API **恒合法**。
|
|
34
|
+
配套:`agent.py` 的防死循环提示改为在整块工具响应**之后**追加,不再插进 tool 之间。
|
|
35
|
+
|
|
36
|
+
## 理由
|
|
37
|
+
|
|
38
|
+
- **只修尾部不够**:违规可能在中段(本次实测),尾部修复完全看不到。
|
|
39
|
+
- **只加读侧锚定不够**:不带行号前缀的内容(用户把会话文件整段粘进对话、shell 直接
|
|
40
|
+
`type` 出文件)仍能伪造出行首标记。
|
|
41
|
+
- **只加写侧转义不够**:修复前落盘的历史文件里已有的镶嵌标记仍会被解码 → 需要读侧 +
|
|
42
|
+
全序列修复兜底(已用真机 `session-122357` 验证:修复后非法点 0)。
|
|
43
|
+
- **否决"按人读格式严格比对,再判定某个标记是否可信"**:人读格式一旦演进,旧文件会被
|
|
44
|
+
整段判成伪标记 → **静默丢历史**,代价远高于收益(不变量只禁止一件事:无声地丢失)。
|
|
45
|
+
|
|
46
|
+
## 代价与边界
|
|
47
|
+
|
|
48
|
+
- 人读部分与原始内容有肉眼不可见的差异(零宽空格);工具结果原文仍由标记负载精确重建。
|
|
49
|
+
- 夹层消息只"后移"不删除:已积累的重复消息仍占上下文(必要时人工清理)。
|
|
50
|
+
- **复用注意**:`_repair_tool_sequence` 为兼容旧行为会弹出**尾部未配对 user**;因此它之后必须
|
|
51
|
+
紧跟 `add_user`(app 的 `run_turn` 顺序)。若单独调用后直接发请求,列表会以 assistant 结尾,
|
|
52
|
+
thinking 模式下会触发另一条 400 `reasoning_content ... must be passed back`
|
|
53
|
+
(见 `docs/evidence/session-marker-boundary_report.md` §5 坑 1)。
|
|
54
|
+
- **已知未覆盖(本次不改,另行决策)**:`save_session` 不清理"陈旧分段"(列表变短时,
|
|
55
|
+
旧的高编号分段文件仍会被 `load_session` 追加 → 陈旧内容混入 + 可能非法序列)。
|
|
56
|
+
机制已在证据脚本里复现(3 条被加载成 130 条);全序列修复能兜住 400,但内容冗余仍在。
|
|
57
|
+
|
|
58
|
+
## 关联
|
|
59
|
+
|
|
60
|
+
- 模块:`store/session_store.py`(`_MARKER_RE` / `_escape_marker_prefix` / `_format_message`)、
|
|
61
|
+
`core/session.py`(`_repair_tool_sequence`)、`core/agent.py`(提示位置)
|
|
62
|
+
- 文档:`docs/session-invariant.md` §4.1
|
|
63
|
+
- 测试:`tests/test_session_store.py`(往返)、`tests/test_session_repair.py`(全序列)、
|
|
64
|
+
`tests/test_session_invariant.py`
|
|
65
|
+
- 证据:`docs/evidence/session-marker-boundary_report.md` / `.log`
|
|
@@ -65,3 +65,5 @@
|
|
|
65
65
|
| 2026-09-11 | [档位简化为单档 flash](2026-09-11-档位简化为单档flash.md) | 生效 · 破坏性 |
|
|
66
66
|
| 2026-09-11 | [终端状态兜底恢复 + 退出看门狗](2026-09-11-终端状态兜底恢复.md) | 生效 |
|
|
67
67
|
| 2026-09-11 | [重试只归一层](2026-09-11-重试归一层.md) | 生效 |
|
|
68
|
+
| 2026-09-11 | [配置残留值治理(自动收敛 + 必须可见)](2026-09-11-配置残留值治理.md) | 生效 |
|
|
69
|
+
| 2026-09-12 | [内容里的标记不得被当成消息边界](2026-09-12-内容里的标记不得当成消息边界.md) | 生效 |
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# 发布 0.3.0 记录(2026-09-11)
|
|
2
|
+
|
|
3
|
+
## 时间线
|
|
4
|
+
|
|
5
|
+
| 步骤 | 结果 |
|
|
6
|
+
|---|---|
|
|
7
|
+
| 版本升级 | 0.2.18 → **0.3.0**(5 处同步: pyproject / `__version__` / README / 手册索引 / 手册第 1 章) |
|
|
8
|
+
| 全量测试 | **518 passed, 3 skipped** |
|
|
9
|
+
| 质量 gate | **4/4 PASS**(含 version-sync) |
|
|
10
|
+
| 构建 | `python -m build` → `nexforge_cli-0.3.0-py3-none-any.whl`(290 KB) + `.tar.gz`(435 KB) |
|
|
11
|
+
| 产物自检 | `twine check` → **两个包 PASSED** |
|
|
12
|
+
| 本地预装 | 干净目录 `pip install --no-deps` → rc=0; `__version__ = 0.3.0`; 新模块可导入 |
|
|
13
|
+
| 上传 | `twine upload`(带 `PYTHONIOENCODING=utf-8`)→ 成功 |
|
|
14
|
+
| PyPI URL | https://pypi.org/project/nexforge-cli/0.3.0/ |
|
|
15
|
+
|
|
16
|
+
## 上传后验证(记录一个反复出现的坑)
|
|
17
|
+
|
|
18
|
+
twine 报成功后, **立刻**查 PyPI API 仍是 `最新版本: 0.2.18`、`0.3.0 文件数: 0`,
|
|
19
|
+
`pip install nexforge-cli==0.3.0` 也报 `No matching distribution found` —— 这是
|
|
20
|
+
**PyPI 的 CDN 传播延迟**(0.2.18 发布时遇到过同样现象), 不是上传失败。
|
|
21
|
+
|
|
22
|
+
约 40 秒后复验:
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
0.3.0 文件数 = 2
|
|
26
|
+
pip install(从 PyPI) rc = 0
|
|
27
|
+
导入路径: pypi_inst_030 ← 确认真的是从 PyPI 安装的目录, 不是本机 editable 包
|
|
28
|
+
__version__ = 0.3.0
|
|
29
|
+
terminal_guard: True
|
|
30
|
+
vision_channel ok
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**教训(第二次踩)**: 上传后的验证必须
|
|
34
|
+
1) 看**顶层** `/pypi/<name>/json` 的 `releases`(单版本 URL 会 404);
|
|
35
|
+
2) **等传播**(几十秒到几分钟), 期间不要判定失败;
|
|
36
|
+
3) 安装验证要打印**导入路径**, 否则可能误用本机已装的同名包而得出假结论
|
|
37
|
+
(本次第一次检查就犯了这个错, 已修正)。
|
|
38
|
+
|
|
39
|
+
## 发布后深度核验(独立 venv + 逐文件比对)
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
独立 venv 从 PyPI 全量安装(含依赖) rc=0
|
|
43
|
+
nf --version → NexForgeCLI 0.3.0 ← 真实控制台脚本可用(不只是 import)
|
|
44
|
+
nf --help → "-m, --model MODEL 模型档位(flash)…"
|
|
45
|
+
"--resume 兼容参数:启动时已自动恢复最近一次会话…"
|
|
46
|
+
源码逐文件比对 → 共同文件 108 个, 内容不一致 0 个, 无缺失/无多余
|
|
47
|
+
⇒ 发出去的包 == 仓库 HEAD(审过的就是发出的)
|
|
48
|
+
升级模拟 → 旧配置 default_tier="pro": 退出码 0 + 可见迁移告警
|
|
49
|
+
"[models] 档位 'pro' 已移除(现只有 flash), 已自动改用 flash"
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## 发布卫生: 补打 v0.3.0 标签
|
|
53
|
+
|
|
54
|
+
仓库原有轻量标签 `v0.2.12` / `v0.2.13`,之后 0.2.14–0.2.18 与本次 0.3.0 **均未打标签**
|
|
55
|
+
(约定中途断了)。本次补打 **v0.3.0 → 56d314e**(版本提交;构建用的树与它一致,
|
|
56
|
+
之后只加过 docs/evidence,不入包)。历史版本不追溯补打,避免猜测各自对应的提交。
|
|
57
|
+
|
|
58
|
+
## 本版内容(摘要)
|
|
59
|
+
- **破坏性**: 档位简化为单档 `flash`(`/model pro|auto|vision` 移除; 旧配置值自动迁移并告警)
|
|
60
|
+
- 模型名更新为官方 `deepseek-flash`(原生多模态, 文本+读图一档)
|
|
61
|
+
- 终端状态兜底恢复 + 退出看门狗(SIGBREAK 接管 / 超时强制退出)
|
|
62
|
+
- 模型侧边栏绿点恒为一个; 侧栏列表左对齐修复
|
|
63
|
+
- 会话落盘原子写; 历史归档不再静默丢批次; 归档/保存失败均可见
|
|
64
|
+
- 识图通道判定修复(默认档位贴图真的发图, 能力上报与实际同源)
|
|
65
|
+
- 重试归一层(429 时由最多 12 次请求降为 `1 + max_retries`)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# 发布 0.4.0 记录(2026-09-11)
|
|
2
|
+
|
|
3
|
+
## 内容(相对 0.3.0)
|
|
4
|
+
|
|
5
|
+
0.3.0 之后共 **10 个提交 / 23 文件(+1827 / −125)**:
|
|
6
|
+
|
|
7
|
+
- **feat** 语音播报接入**本机 TTS**(如本地部署的 CosyVoice):本机优先、失败自动回退
|
|
8
|
+
微软 Windows 语音(`122481f`);
|
|
9
|
+
- **feat** 用 nf 时让本机模型**常驻显存**、nf 全部退出即释放 —— 租约机制 + PID 存活/
|
|
10
|
+
心跳超时自愈,被强杀也能归还显存(`c4f033a`、`67b33bc`);
|
|
11
|
+
- **feat** 新增 `/voice full|brief` 切换播报范围,默认**全量播报**最终报告
|
|
12
|
+
(`max_chars` 默认 100 → 0)(`0d62b3d`);
|
|
13
|
+
- **fix** 配置残留值治理:失效 provider 自动回退内置 deepseek(不再让 TUI 初始化失败、
|
|
14
|
+
不再连带报误导性的"缺少 API Key");`[provider] verified` 不再被当成未知字段误报
|
|
15
|
+
(`3f95873`、`660e27e`);
|
|
16
|
+
- **test** 新增两条门禁:`eof-newline`(防"黏行"损坏)、`example-config`
|
|
17
|
+
(示例配置必须等于代码默认值 —— 它当场抓出两处漂移,其中 `max_output_tokens=8192`
|
|
18
|
+
会误导用户复现"正文截断")(`84a7f5f`、`efad92c`);
|
|
19
|
+
- **docs** ADR《配置残留值治理》+ 插件/服务文档修正(`4001050`、`d99e3e5`)。
|
|
20
|
+
|
|
21
|
+
## 发布流程与结果
|
|
22
|
+
|
|
23
|
+
| 步骤 | 结果 |
|
|
24
|
+
|---|---|
|
|
25
|
+
| 版本 | 0.3.0 → **0.4.0**(5 处同步: pyproject / `__version__` / README / 手册索引 / 手册第 1 章) |
|
|
26
|
+
| 全量测试 | **567 passed, 3 skipped** |
|
|
27
|
+
| 质量 gate | **6/6 PASS**(含 version-sync、example-config、eof-newline) |
|
|
28
|
+
| 构建 | whl 292 KB + tar.gz 461 KB |
|
|
29
|
+
| 产物自检 | `twine check` → **两个包 PASSED** |
|
|
30
|
+
| 上传前本地安装 | rc=0;新装环境版本 0.4.0、`tts.mode/resident/max_chars` 齐全、`/voice` 在包内 |
|
|
31
|
+
| 上传 | `twine upload`(带 `PYTHONIOENCODING=utf-8`)→ 成功 |
|
|
32
|
+
| PyPI | https://pypi.org/project/nexforge-cli/0.4.0/ |
|
|
33
|
+
|
|
34
|
+
## 又一次踩到"传播延迟",且这次是**两个不同的缓存**
|
|
35
|
+
|
|
36
|
+
1. 上传后立刻查**顶层 JSON API** → 先显示 `最新版本: 0.3.0`,**约 40 秒后**变为 0.4.0、
|
|
37
|
+
文件数 2 ✔;
|
|
38
|
+
2. 但 **pip 用的 `/simple` 索引是另一个缓存**:JSON 已经更新时,`pip install
|
|
39
|
+
nexforge-cli==0.4.0` 仍报 `No matching distribution found (available: … 0.3.0)`,
|
|
40
|
+
**约 20 秒后**才可见。
|
|
41
|
+
|
|
42
|
+
**差点产生的假绿**:pip 失败后,我的校验脚本 `import nexforge` 回退到了**本地仓库包**,
|
|
43
|
+
却照样打印出 `0.4.0`(因为仓库里就是 0.4.0)—— 是"**打印导入路径**"这一步把它揭穿的
|
|
44
|
+
(显示 `src` 而不是安装目录)。**教训**:发布后验证必须
|
|
45
|
+
(1) 分别等待 JSON 与 `/simple` 两个缓存的传播,
|
|
46
|
+
(2) 打印导入路径确认来源。
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# 2026-09-12 会话标记边界修复 — 原始证据(诊断脚本输出, 未编辑)
|
|
2
|
+
|
|
3
|
+
复现命令(诊断脚本为临时脚本, 逻辑就是"用项目自己的 load_session 加载 + 按 OpenAI/DeepSeek
|
|
4
|
+
规则校验工具序列"):
|
|
5
|
+
|
|
6
|
+
python verify_real.py D:\DevelopProject\NexForge\NexForgeCLI session-122357
|
|
7
|
+
python before_after.py # 旧行为 vs 修复后
|
|
8
|
+
python stale_seg_sim.py # 遗留风险(陈旧分段)模拟
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## [1] 内容里的标记 → 幽灵消息: 旧行为 vs 修复后(`before_after.py`)
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
【修复后】消息数 = 3 期望 = 3
|
|
16
|
+
【修复后】序列: [('user', None), ('assistant', None), ('tool', 'c1')]
|
|
17
|
+
|
|
18
|
+
【旧行为】消息数 = 4
|
|
19
|
+
【旧行为】序列: [('user', None), ('assistant', None), ('assistant', None), ('tool', 'c1')]
|
|
20
|
+
|
|
21
|
+
修复后 API 合法: True
|
|
22
|
+
旧行为 API 合法: False ← False 即 DeepSeek 400
|
|
23
|
+
结论: 旧行为凭空多出消息且序列非法(400); 修复后逐项一致。
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
要点: 工具结果内容里嵌一行真标记形状, 旧行为会把它解码成**一条幽灵 assistant 消息**,
|
|
27
|
+
且插在 `assistant(tool_calls)` 与 `tool` 响应之间 → 序列非法(400)。
|
|
28
|
+
|
|
29
|
+
## [2] 真机会话 `session-122357`: 加载 → 全序列修复 → 校验(`verify_real.py`)
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
=== D:\DevelopProject\NexForge\NexForgeCLI :: session-122357 ===
|
|
33
|
+
加载消息数: 3785 角色分布: {'system': 1, 'user': 44, 'assistant': 2673, 'tool': 1067}
|
|
34
|
+
idx=1724 第2个 tool_call 后不是其响应 → 实际下一条 role=assistant
|
|
35
|
+
idx=1724 第3个 tool_call 后不是其响应 → 实际下一条 role=assistant
|
|
36
|
+
idx=1729 孤儿 tool(无前置 assistant) → 实际下一条 role=call_01_ntfoM5j9cu4Ee5Yx0Tyq5025
|
|
37
|
+
idx=1730 孤儿 tool(无前置 assistant) → 实际下一条 role=call_02_UWDVr1XVkUCnkmPX0HcO6758
|
|
38
|
+
修复后消息数: 3785 (净变化 +0) 非法点: 0
|
|
39
|
+
非 tool 消息丢失数: 0 (必须为 0)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
说明:
|
|
43
|
+
|
|
44
|
+
- 违规点在 **idx=1724(全长 3785 的中段)** —— 这正是旧 `_repair_incomplete_tail`(只修尾部)
|
|
45
|
+
兜不住、导致"每轮都 400"的原因;
|
|
46
|
+
- 修复后非法点 0, 且**非 tool 消息一条不丢**(夹层消息只后移, 不删除);
|
|
47
|
+
- 修复后加载条数比修复前少 1(3786 → 3785): 少的正是"内容里的标记"伪造出的幽灵消息,
|
|
48
|
+
被读侧行首锚定挡住。
|
|
49
|
+
|
|
50
|
+
## [3] 遗留风险模拟: 陈旧分段残留(`stale_seg_sim.py`, 本次未修, 已记录)
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
第一次保存(60 轮)分段文件: 3
|
|
54
|
+
第二次保存(3 条)后分段文件: 3 (旧文件仍在 → 陈旧残留)
|
|
55
|
+
|
|
56
|
+
加载结果: 130 条(期望 3)
|
|
57
|
+
加载后非法点: 0
|
|
58
|
+
全序列修复后消息数: 130 非法点: 0
|
|
59
|
+
→ 修复兜住了 400, 但陈旧内容仍混入上下文(冗余/重复)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
说明: `save_session` 写完新分段后不清理"超出新段数的旧分段文件", 而 `load_session`
|
|
63
|
+
按序号**连续**读 → 列表变短(上下文压缩等)后, 旧分段会被追加回来。
|
|
64
|
+
本次修复能让请求保持合法(不再永久 400), 但内容冗余仍在; 建议后续用"文件头声明分段数"
|
|
65
|
+
收口(见 ADR `docs/decisions/2026-09-12-内容里的标记不得当成消息边界.md` 的"代价与边界")。
|
|
66
|
+
|
|
67
|
+
## [4] 全量测试与本机巡检
|
|
68
|
+
|
|
69
|
+
- 全量 `pytest -q`: 见 `session-marker-boundary_fulltest.log`(**577 passed / 3 skipped**);
|
|
70
|
+
注: 直接把 pytest 输出重定向到文件时, `sys.__stderr__` 走 GBK 会把中文断言串编坏,
|
|
71
|
+
`test_session_save_safety` / `test_terminal_guard` 两条会假 FAIL —— 加
|
|
72
|
+
`PYTHONIOENCODING=utf-8` 即全绿(与本次改动无关, 供后来者参考);
|
|
73
|
+
- 本机所有会话巡检(只读): 仅 `NexForgeCLI/.nexforge/sessions/session-122357.md` 命中非法序列,
|
|
74
|
+
修复后 0; `CosyVoice` 两个会话本就合法; 未发现真实存在的陈旧分段文件。
|
|
75
|
+
|
|
76
|
+
## [5] 真机 API 验证(唯一联网步骤; 目标是用户自己配置的 deepseek)
|
|
77
|
+
|
|
78
|
+
顺序复刻 app 的 `run_turn`: **修复 → add_user → 发请求**。原始输出见
|
|
79
|
+
`session-marker-boundary_live.log`, 摘要:
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
--- 合成最小复现 ---
|
|
83
|
+
[400 ] 修复前(只修尾部 → 中段违规保留): 400 "must be followed by tool messages responding ..."
|
|
84
|
+
[OK ] 修复后(全序列修复): 回复='我没法执行 git 命令——我目前只有 read_file ...'
|
|
85
|
+
|
|
86
|
+
--- 用户真机会话切片(idx 1715..1735, 含 10 份重复消息) ---
|
|
87
|
+
[400 ] 修复前: 400 "must be followed by tool messages responding ..."(与截图同款)
|
|
88
|
+
[OK ] 修复后: 回复='先做两件事并举:把截图切块放大精读…'
|
|
89
|
+
|
|
90
|
+
--- 用户会话全部 3785 条端到端 ---
|
|
91
|
+
[OK ] 加载 3785 条; content 字符 896103 → 回复='好'
|
|
92
|
+
usage: prompt=980288 completion=2
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
附带发现(见报告 §5): 若自行构造的请求**以 assistant 结尾**(脱离 `run_turn` 的
|
|
96
|
+
"修复 → add_user"顺序), thinking 模式会报另一条 400
|
|
97
|
+
`The reasoning_content in the thinking mode must be passed back to the API.`
|
|
98
|
+
app 的真实路径永远以 user 结尾, 不受影响; 排查时先看请求最后一条的 role。
|
|
99
|
+
|
|
100
|
+
体量标定: 该会话 ≈ **980k prompt tokens**(按真机 usage 实测) —— 已逼近模型窗口,
|
|
101
|
+
建议该项目后续工作改开新会话(详见报告 §6)。
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
..........................................................................................................................sss........................................................................................................... [ 40%]
|
|
2
|
+
........................................................................................................................................................................................................................................ [ 80%]
|
|
3
|
+
.................................................................................................................... [100%]
|
|
4
|
+
577 passed, 3 skipped in 166.00s (0:02:46)
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# 2026-09-12 真机 API 验证 — 原始输出
|
|
2
|
+
# 说明: 唯一联网的部分。调用的是用户自己配置的 DeepSeek(极少量 token)。
|
|
3
|
+
# 顺序复刻 app: run_turn = 修复(全序列) → add_user → 发请求。
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
======================================================================
|
|
7
|
+
[1] 合成最小复现 + 用户真机切片(修复前 400 / 修复后 OK)
|
|
8
|
+
======================================================================
|
|
9
|
+
|
|
10
|
+
--- 合成最小复现 (合成, 6 条) ---
|
|
11
|
+
[400 ] 合成最小复现: 修复前(只修尾部 → 中段违规保留)
|
|
12
|
+
尾部形态=[('system', ''), ('user', ''), ('assistant', ''), ('tool', 'call_a'), ('assistant', ''), ('tool', 'call_b'), ('user', '')]
|
|
13
|
+
DeepSeek 请求失败:Error code: 400 - {'error': {'message': "An assistant message with 'tool_calls' must be followed by tool messages responding to each 'tool_call_id'. (insuff
|
|
14
|
+
[OK ] 合成最小复现: 修复后(全序列修复)
|
|
15
|
+
尾部形态=[('system', ''), ('user', ''), ('assistant', ''), ('tool', 'call_a'), ('tool', 'call_b'), ('assistant', ''), ('user', '')]
|
|
16
|
+
回复='我没法执行 git 命令——我目前只有 `read_file'
|
|
17
|
+
|
|
18
|
+
--- 用户真机切片 (真机会话 idx 1715..1735, 20 条) ---
|
|
19
|
+
[400 ] 用户真机切片: 修复前(只修尾部 → 中段违规保留)
|
|
20
|
+
尾部形态=[('assistant', ''), ('assistant', ''), ('assistant', ''), ('assistant', ''), ('tool', 'call_00_KUQ0QE4uHYFZR9vECKHs8585'), ('tool', 'call_01_vPwNaGPVtYL6pjDSz1Jp1970'), ('assistant', ''), ('user', '')]
|
|
21
|
+
DeepSeek 请求失败:Error code: 400 - {'error': {'message': "An assistant message with 'tool_calls' must be followed by tool messages responding to each 'tool_call_id'. (insuff
|
|
22
|
+
[OK ] 用户真机切片: 修复后(全序列修复)
|
|
23
|
+
尾部形态=[('assistant', ''), ('assistant', ''), ('tool', 'call_00_KUQ0QE4uHYFZR9vECKHs8585'), ('tool', 'call_01_vPwNaGPVtYL6pjDSz1Jp1970'), ('assistant', ''), ('tool', 'call_00_lGLB89578wxkRZEHAc7h3860'), ('tool', 'call_01_PSu2XcMwXvYpfFx5HyeH0236'), ('user', '')]
|
|
24
|
+
回复='先做两件事并举:把截图切块放大精读(上次裁剪被中断了),同时'
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
======================================================================
|
|
29
|
+
[2] 用户会话**全部 3785 条**端到端(修复后 → OK, 980k prompt tokens)
|
|
30
|
+
======================================================================
|
|
31
|
+
加载 3785 条; content 字符 896103
|
|
32
|
+
OK 回复='好'
|
|
33
|
+
usage: prompt=980288 completion=2 cache_hit=0 cache_miss=980288
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
======================================================================
|
|
38
|
+
[3] token 折算标定(用于估算会话体量)
|
|
39
|
+
======================================================================
|
|
40
|
+
样本: 10 条, content 字符 2677, 实测 prompt_tokens 1809
|
|
41
|
+
折算比 ≈ 1.48 字符/token(含消息包装开销, 偏保守)
|
|
42
|
+
全会话 content 字符 896103 → 估算 ≈ 605,547 tokens
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
======================================================================
|
|
47
|
+
[4] 排查过程中的一个反例(见报告 §5 解释: 请求以 assistant 结尾会触发另一条 400)
|
|
48
|
+
======================================================================
|
|
49
|
+
A(直接构造) 哈希: 032cb06ac43097bb [('system', '', False, False), ('user', '', False, False), ('assistant', '', True, True), ('tool', 'call_a', False, False), ('tool', 'call_b', False, False), ('assistant', '', False, False), ('user', '', False, False)]
|
|
50
|
+
B(违规→修复) 哈希: 155c87cb5fdfbe12 [('system', '', False, False), ('user', '', False, False), ('assistant', '', True, True), ('tool', 'call_a', False, False), ('tool', 'call_b', False, False), ('assistant', '', False, False)]
|
|
51
|
+
两者规范形态一致: False
|
|
52
|
+
A#1 OK: '我没法直接运行 git 命令'
|
|
53
|
+
A#2 OK: '我没法真正“检查 git 提'
|
|
54
|
+
A#3 OK: '好'
|
|
55
|
+
B#1 400(reasoning): DeepSeek 请求失败:Error code: 400 - {'error': {'message': 'The `reasoning_content` in the thinking mode
|
|
56
|
+
B#2 400(reasoning): DeepSeek 请求失败:Error code: 400 - {'error': {'message': 'The `reasoning_content` in the thinking mode
|
|
57
|
+
B#3 400(reasoning): DeepSeek 请求失败:Error code: 400 - {'error': {'message': 'The `reasoning_content` in the thinking mode
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
======================================================================
|
|
62
|
+
[5] 同一请求连发 6 次(reasoning_content 报错与请求形态相关, 非随机)
|
|
63
|
+
======================================================================
|
|
64
|
+
第1次 OK: ''
|
|
65
|
+
第2次 OK: '我手上只有读文件的能力,'
|
|
66
|
+
第3次 OK: '好'
|
|
67
|
+
第4次 OK: '好'
|
|
68
|
+
第5次 OK: ''
|
|
69
|
+
第6次 OK: '先说清楚我这边的情况,免'
|
|
70
|
+
|
|
71
|
+
统计: OK=6 reasoning_content 400=0 其它 400=0
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
======================================================================
|
|
75
|
+
[6] 上下文窗口探针(免费/必然超长 → 用于确认窗口大小)
|
|
76
|
+
======================================================================
|
|
77
|
+
探针: 竟然通过(窗口 > 1.2M tokens)
|
|
78
|
+
|
|
79
|
+
DEFAULT_SYSTEM 字符数: 2041
|
|
80
|
+
工具 schema 字符数: 20993 (47 个工具)
|
|
81
|
+
合计附加字符数 ≈ 23034 → 约 7,678 tokens
|
|
82
|
+
会话本体(实测) = 980,288 tokens; max_output_tokens = 32768
|
|
83
|
+
预估请求总量 ≈ 1,020,734 tokens
|
|
84
|
+
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# 会话"再也打不开"根因报告 — 内容里的标记被当成消息边界(2026-09-12)
|
|
2
|
+
|
|
3
|
+
> 现象: nf 启动后说什么都报 `DeepSeek 400 ... insufficient tool messages following tool_calls message`(截图同款),
|
|
4
|
+
> 即用户所说"项目打不开了"。本文记录完整证据链、修复与验证;**结论均有原始输出支撑**(见文末索引)。
|
|
5
|
+
|
|
6
|
+
## 1. 结论(一句话)
|
|
7
|
+
|
|
8
|
+
工具结果 dump 会话 `.md` 文件时, 被 dump 的 `<!-- nf-msg … -->` 标记行作为**内容**落进新会话文件;
|
|
9
|
+
解析时被全文 `findall` 解码成**额外消息**, 插进 `assistant(tool_calls)` 与它的 `tool` 响应之间 →
|
|
10
|
+
请求违反 API 的"tool 响应必须紧跟且覆盖全部 tool_call_id"→ 每轮 400。
|
|
11
|
+
而旧的修复函数 `_repair_incomplete_tail` **只修尾部**, 看不到中段违规 → 永久失败。
|
|
12
|
+
|
|
13
|
+
## 2. 定位过程(证据链)
|
|
14
|
+
|
|
15
|
+
| 步骤 | 做法 | 结果 |
|
|
16
|
+
|---|---|---|
|
|
17
|
+
| 1 | 解出截图报错原文 | `An assistant message with 'tool_calls' must be followed by tool messages responding to each 'tool_call_id'. (insufficient tool messages following tool_calls message)` |
|
|
18
|
+
| 2 | 扫描本机各项目会话, 按 API 规则校验工具序列(只读) | 只有 `NexForgeCLI/.nexforge/sessions/session-122357.md` 命中: **中段 idx=1724 违规 4 例**; CosyVoice 两个会话合法 |
|
|
19
|
+
| 3 | 打印违规点上下文 | `assistant(4 个 tool_calls)` 的响应被 4 条重复助手消息插开 |
|
|
20
|
+
| 4 | 查重复消息来源 | 文件里存在"内容里的标记": 工具结果 `read_file .nexforge/sessions/session-…md` 的 dump 里带着真标记行 |
|
|
21
|
+
| 5 | 统计幽灵消息 | 该会话多出 **10 份重复助手消息**; 修复后加载条数 3786 → 3785(少的那条正是幽灵) |
|
|
22
|
+
| 6 | 用真机 API 复现 | 修复前的形态 → **400(与截图完全一致)**; 修复后 → 200 正常回复 |
|
|
23
|
+
|
|
24
|
+
关键点: 违规在**中段**——这是"只修尾部"兜不住的原因, 也解释了为什么"历史会话能正常显示, 一说话就炸"。
|
|
25
|
+
|
|
26
|
+
## 3. 修复
|
|
27
|
+
|
|
28
|
+
| # | 位置 | 改动 |
|
|
29
|
+
|---|---|---|
|
|
30
|
+
| 1 | `store/session_store.py` `_escape_marker_prefix` | **写侧转义**: 人读部分里的 `<!-- nf-msg` 插零宽空格 → 内容不再具备标记形状(标记负载无损) |
|
|
31
|
+
| 2 | `store/session_store.py` `_MARKER_RE` | **读侧锚定行首**(`re.M`): 只有独立成行的标记才是边界(dump 出来的标记带 ` 12 | ` 前缀, 旧正则也匹配) |
|
|
32
|
+
| 3 | `core/session.py` `_repair_tool_sequence` | **全序列修复**(旧名保留为别名): 夹层消息保序后移、缺响应补"已取消"、重复/孤儿 tool 删除 → 修复后序列对 API **恒合法**; 调用点 `add_user` / `run_turn` / 回合结束 |
|
|
33
|
+
| 4 | `core/agent.py` | 防死循环提示改为整块工具响应**之后**追加(原先会插进 tool 之间, 同类违规) |
|
|
34
|
+
|
|
35
|
+
设计取舍与边界见 ADR: `docs/decisions/2026-09-12-内容里的标记不得当成消息边界.md`。
|
|
36
|
+
|
|
37
|
+
## 4. 验证(三档)
|
|
38
|
+
|
|
39
|
+
| 档 | 内容 | 结果 |
|
|
40
|
+
|---|---|---|
|
|
41
|
+
| 单元回归 | 新增/更新 20 项: 内容里的标记往返、锚定读入、中段违规修复、幂等、合法序列不动、add_user 恢复后合法 | `tests/test_session_store.py` `tests/test_session_repair.py` `tests/test_session_invariant.py` 全绿 |
|
|
42
|
+
| 全量 + 门禁 | `pytest -q` 与 `scripts/check.py` | **577 passed / 3 skipped**;gate **6/6 PASS** |
|
|
43
|
+
| 真机 API | 合成最小复现 + **用户真机会话切片** + **整会话 3785 条端到端** | 修复前 400(截图同款) → 修复后 OK(见 `session-marker-boundary_live.log`) |
|
|
44
|
+
|
|
45
|
+
真机端到端最有力的一条: 用户会话**全部 3785 条**(体积 ≈ 980k prompt tokens)在修复后能正常得到回复,
|
|
46
|
+
说明既不是"尾部侥幸合法", 也不是"切片合法、整体不合法"。
|
|
47
|
+
|
|
48
|
+
**用户侧确认(2026-09-12)**: 该项目会话已恢复正常, 可继续对话(修复后首个回合由用户实机跑通)。
|
|
49
|
+
|
|
50
|
+
## 5. 排查中的两个坑(留给后来者)
|
|
51
|
+
|
|
52
|
+
1. **请求不能以 assistant 消息结尾**: thinking 模式下 DeepSeek 会报
|
|
53
|
+
`The reasoning_content in the thinking mode must be passed back to the API.`
|
|
54
|
+
`_repair_tool_sequence` 为兼容旧行为会**弹出尾部未配对 user**; 若在"修复后"自行构造请求
|
|
55
|
+
(而不是走 `run_turn` 的 `修复 → add_user` 顺序), 就会得到一个以 assistant 结尾的列表 →
|
|
56
|
+
触发这条 400, 看起来像"修复没生效"。**app 的真实路径永远以 user 结尾**, 不受影响。
|
|
57
|
+
排查时若看到这条错, 先看请求最后一条是不是 assistant。
|
|
58
|
+
2. **同一请求重复 6 次全 OK**: 上面的 reasoning 报错与请求形态强相关(见坑 1), 不是服务端随机抖动;
|
|
59
|
+
排查"偶发"问题时先做 A/B 并打印规范形态哈希, 别急着下"服务端不稳定"的结论。
|
|
60
|
+
|
|
61
|
+
## 6. 遗留与建议(本次未改)
|
|
62
|
+
|
|
63
|
+
| 项 | 现状 | 建议 |
|
|
64
|
+
|---|---|---|
|
|
65
|
+
| 会话里已积累的 10 份重复助手消息 | 修复只**后移**不删除(不变量: 不允许无声丢失); 请求合法但占上下文 | 首次成功回合后 app 会重写会话文件; 重复内容如需清理请人工处理 |
|
|
66
|
+
| 会话体量 ≈ 980k tokens | 实测: 会话本体 980,288 tokens; 加上 `DEFAULT_SYSTEM` + 47 个工具 schema(≈7.7k)+ `max_output_tokens`(32,768)≈ **1.02M**; 上下文窗口探针(1.25M tokens 的超长请求)通过 ⇒ 目前仍能发出, 但**已无余量** | 上下文治理只按"轮数 > 60"压缩、没有按 token 的裁剪 → 优先补"按 token 裁剪/强制压缩"; 短期内该项目建议 `/new` 开新会话 |
|
|
67
|
+
| 失败的回合会丢弃那条用户消息 | 观测: 会话文件在 01:16 被(带本修复的)nf 重写, 序列已合法(非法点 0), 但尾部**没有**新用户消息与回复 → 说明那一轮以失败/中断收尾, 且 `_repair_tool_sequence` 的"弹尾部未配对 user"(沿用旧行为)把该用户消息从会话里移除了 | 若是"请求已发出但没成", `_repair_tool_sequence` 弹 user 这一步可考虑改为不弹(API 允许连续 user 消息); 需产品决策, 本次未改 |
|
|
68
|
+
| 陈旧分段残留 | `save_session` 不清理"超出新段数的旧分段", 而 `load_session` 按序号连续读 → 列表变短(压缩)后旧内容被追加(已复现: 3 条被加载成 130 条)。全序列修复能兜住 400, 但内容冗余仍在 | 会话文件头声明分段数(`> 分段 N`), 加载按声明数读取, 超出的不读也不删 |
|
|
69
|
+
|
|
70
|
+
## 7. 证据索引
|
|
71
|
+
|
|
72
|
+
- `docs/evidence/session-marker-boundary.log` — 脚本级原始输出(旧行为 vs 修复后、真机会话校验、陈旧分段模拟)
|
|
73
|
+
- `docs/evidence/session-marker-boundary_live.log` — 真机 API 原始输出(含 980k tokens 端到端)
|
|
74
|
+
- `docs/evidence/session-marker-boundary_fulltest.log` — 全量测试输出
|
|
75
|
+
- 回归测试: `tests/test_session_store.py::test_roundtrip_does_not_revive_marker_inside_content`、
|
|
76
|
+
`tests/test_session_store.py::test_load_ignores_marker_line_with_dump_prefix`、
|
|
77
|
+
`tests/test_session_repair.py::test_repair_mid_list_violation_keeps_responses_adjacent`
|