nexforge-cli 0.3.0__tar.gz → 0.4.0__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.0}/PKG-INFO +7 -8
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/README.md +7 -8
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/config.example.toml +12 -1
- nexforge_cli-0.4.0/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.3.0 → nexforge_cli-0.4.0}/docs/decisions/README.md +1 -0
- nexforge_cli-0.4.0/docs/evidence/release-0.3.0.log +65 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/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.0}/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.0}/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.0}/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.0}/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.0}/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.0}/manual/README.md +1 -1
- nexforge_cli-0.4.0/plugin_speech_summary/README.md +127 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/plugin_speech_summary/install_plugin.py +46 -17
- nexforge_cli-0.4.0/plugin_speech_summary/speech_summary.py +690 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/pyproject.toml +1 -1
- nexforge_cli-0.4.0/scripts/gates/eof_newline.py +106 -0
- nexforge_cli-0.4.0/scripts/gates/example_config.py +100 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/__init__.py +1 -1
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/config.py +48 -3
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/i18n.py +9 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/provider/__init__.py +2 -2
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/ui/screens/chat.py +15 -1
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/ui/slash_handler.py +30 -3
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/tests/test_check_gates.py +112 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/tests/test_config.py +135 -0
- nexforge_cli-0.4.0/tests/test_plugin_example_tts.py +640 -0
- nexforge_cli-0.4.0/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_tui.py +0 -56
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/.gitignore +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/INSTALL.md +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/STATS_API.md +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/assets/icons/agent.svg +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/assets/icons/cost.svg +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/assets/icons/edit.svg +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/assets/icons/err.svg +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/assets/icons/file.svg +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/assets/icons/model.svg +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/assets/icons/ok.svg +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/assets/icons/read.svg +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/assets/icons/running.svg +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/assets/icons/search.svg +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/assets/icons/shell.svg +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/assets/icons/thinking.svg +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/assets/icons/web.svg +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/assets/icons/write.svg +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/devlog/2026-07-07.md +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/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.0}/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.0}/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.0}/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.0}/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.0}/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.0}/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.0}/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.0}/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.0}/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.0}/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.0}/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.0}/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.0}/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.0}/docs/evidence/D10_acceptance.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/docs/evidence/D10_full_test.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/docs/evidence/D11_a4.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/docs/evidence/D11_full_test.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/docs/evidence/D12_audit.md +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/docs/evidence/D12_gap.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/docs/evidence/D13_break.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/docs/evidence/D13_e2e.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/docs/evidence/D13_full_test.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/docs/evidence/D13_gaps.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/docs/evidence/D14_audit.md +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/docs/evidence/D14_full_test.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/docs/evidence/D14_recheck.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/docs/evidence/D15_audit.md +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/docs/evidence/D4_fast.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/docs/evidence/D4_full_test.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/docs/evidence/D4_hook.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/docs/evidence/D4_scene_c.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/docs/evidence/D4_scene_d.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/docs/evidence/D4_scene_e.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/docs/evidence/D5_audit.md +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/docs/evidence/D5_b2_plugin.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/docs/evidence/D5_full_test.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/docs/evidence/D6_b3_a.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/docs/evidence/D6_b3_b.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/docs/evidence/D6_b3_c.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/docs/evidence/D6_b4_provider.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/docs/evidence/D6_full_test.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/docs/evidence/D7_dedup.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/docs/evidence/D7_full_test.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/docs/evidence/D8_audit.md +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/docs/evidence/D8_invariant.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/docs/evidence/D9_after.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/docs/evidence/D9_before.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/docs/evidence/D9_decision.md +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/docs/evidence/D9_full_test.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/docs/evidence/D9_rebuild.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/docs/evidence/E_cap_fulltest.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/docs/evidence/E_capabilities.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/docs/evidence/E_engines.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/docs/evidence/E_engines_fulltest.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/docs/evidence/E_tts_availability.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/docs/evidence/E_tts_fulltest.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/docs/evidence/E_tts_installer.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/docs/evidence/E_vision_channels.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/docs/evidence/E_vision_fulltest.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/docs/evidence/F_decisions_fulltest.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/docs/evidence/deps_fulltest.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/docs/evidence/deps_reverse.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/docs/evidence/exit-cost.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/docs/evidence/final_check_fulltest.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/docs/evidence/final_check_pypi.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/docs/evidence/final_check_report.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/docs/evidence/fix_fulltest.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/docs/evidence/health_fulltest.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/docs/evidence/health_report.md +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/docs/evidence/health_scan.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/docs/evidence/model-sidebar-dot.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/docs/evidence/model_rename_check.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/docs/evidence/provider-family-check.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/docs/evidence/release_0218_fulltest.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/docs/evidence/release_0218_pypi.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/docs/evidence/simplify_tier_fulltest.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/docs/evidence/terminal-leak-conditions.log +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/docs/session-invariant.md +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/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.0}/docs/submission/nexforge.md +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/docs/submission/nexforge.zh-CN.md +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/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.0}/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.0}/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.0}/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.0}/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.0}/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.0}/plugin_gui_auto/README.md +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/plugin_gui_auto/guiauto/__init__.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/plugin_gui_auto/guiauto/action.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/plugin_gui_auto/guiauto/base.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/plugin_gui_auto/guiauto/tools.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/plugin_gui_auto/guiauto/vision.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/plugin_gui_auto/guiauto/window.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/plugin_gui_auto/install_plugin.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/plugin_gui_auto/requirements.txt +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/plugin_gui_auto/smoke_safe.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/plugin_gui_auto/verify_final.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/plugin_gui_auto/verify_host.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/plugin_gui_auto/verify_plugin.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/scripts/__init__.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/scripts/check.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/scripts/exit_cost_probe.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/scripts/exit_smoke.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/scripts/gates/__init__.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/scripts/gates/base.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/scripts/gates/hygiene.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/scripts/gates/manual_consistency.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/scripts/gates/registry_consistency.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/scripts/gates/version_sync.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/scripts/install_hooks.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/scripts/publish.sh +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/scripts/smoke_provider.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/scripts/smoke_tui.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/__main__.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/app.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/cli.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/core/__init__.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/core/agent.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/core/capability.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/core/context.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/core/federation.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/core/goal_manager.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/core/jobs.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/core/knowledge_base.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/core/memory_engine.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/core/model_catalog.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/core/permissions.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/core/plugin.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/core/project_mapper.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/core/provider.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/core/session.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/core/summarizer.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/core/system_inject.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/core/tool_manifest.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/core/types.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/core/update_check.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/features/__init__.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/features/auto_skills.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/features/base.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/features/federation_feature.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/features/kb_feature.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/features/map_feature.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/features/memory_feature.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/features/mode_features.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/features/plan_feature.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/features/registry.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/features/session_feature.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/features/slash_feature.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/features/stats_feature.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/features/tool_manifest_feature.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/icons.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/mcp/__init__.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/provider/_retry.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/provider/anthropic.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/provider/balance.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/provider/deepseek.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/provider/openai.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/provider/qianwen.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/skills/loader.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/stats/__init__.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/stats/heartbeat.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/store/__init__.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/store/director_store.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/store/plan_store.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/store/session_store.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/terminal_guard.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/theme.tcss +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/tools/__init__.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/tools/_paths.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/tools/background.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/tools/base.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/tools/data_tools.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/tools/edit_file.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/tools/federation.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/tools/fs_ops.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/tools/git.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/tools/image_tools.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/tools/kb_tools.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/tools/list_dir.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/tools/multi_edit.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/tools/ops_tools.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/tools/read_file.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/tools/registry.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/tools/screenshot_tools.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/tools/search.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/tools/search_content.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/tools/search_files.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/tools/shell.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/tools/web.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/tools/write_file.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/ui/__init__.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/ui/clipboard_image.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/ui/screen_api.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/ui/screens/__init__.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/ui/screens/chat_actions.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/ui/screens/splash.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/ui/widgets/__init__.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/ui/widgets/apikey_modal.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/ui/widgets/banner.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/ui/widgets/confirm_modal.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/ui/widgets/diff_modal.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/ui/widgets/file_browser.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/ui/widgets/file_menu.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/ui/widgets/image_sidebar.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/ui/widgets/messages.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/ui/widgets/mode_warning.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/ui/widgets/model_sidebar.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/ui/widgets/multiline_modal.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/ui/widgets/multiline_prompt.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/ui/widgets/plan_sidebar.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/ui/widgets/sidebar.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/ui/widgets/statusbar.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/utils/__init__.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/utils/logging.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/web/__init__.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/web/server.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/src/nexforge/web/static/index.html +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/tests/conftest.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/tests/test_app_mouse.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/tests/test_approval.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/tests/test_archive_integrity.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/tests/test_capability.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/tests/test_cross_platform.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/tests/test_deepseek_provider.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/tests/test_deps.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/tests/test_director_loop.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/tests/test_director_store.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/tests/test_dual_panel.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/tests/test_exit_smoke.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/tests/test_federation.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/tests/test_file_browser.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/tests/test_goal_confirm.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/tests/test_i18n.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/tests/test_icons.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/tests/test_lang_cmd.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/tests/test_m2_tools.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/tests/test_manual_consistency.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/tests/test_model_catalog.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/tests/test_model_sidebar_dot.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/tests/test_p2_core.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/tests/test_plugin.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/tests/test_project_map_prune.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/tests/test_provider_errors.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/tests/test_quit_paths.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/tests/test_retry_policy.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/tests/test_sanitize.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/tests/test_screenshot_tools.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/tests/test_session_invariant.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/tests/test_session_repair.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/tests/test_session_save_safety.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/tests/test_session_store.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/tests/test_sidebar.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/tests/test_sidebar_actions.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/tests/test_sidebar_align.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/tests/test_splash_modal.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/tests/test_statusbar.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/tests/test_system_inject.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/tests/test_terminal_guard.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/tests/test_tool_iters.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/tests/test_tools.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/tests/test_update_check.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/tests/test_vision_capability.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/tests/test_vision_model.py +0 -0
- {nexforge_cli-0.3.0 → nexforge_cli-0.4.0}/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.0
|
|
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,13 @@ Description-Content-Type: text/markdown
|
|
|
51
51
|
|
|
52
52
|
NexForgeCLI 是一个运行在终端里的智能体助手,既支持 DeepSeek/千问等多模型,也是**插件宿主**(plugins 目录即插即用)。既能作为全屏 TUI 交互式对话/编码,也能作为 one-shot 命令行工具嵌进脚本和管道。
|
|
53
53
|
|
|
54
|
-
> 当前版本:**v0.
|
|
54
|
+
> 当前版本:**v0.4.0** · PyPI: `pip install nexforge-cli`
|
|
55
55
|
|
|
56
|
-
**v0.
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
两层重试放大修复(429 时由最多 12 次请求降为 `1 + max_retries` 次)。
|
|
56
|
+
**v0.4.0 要点**:语音播报接入**本机 TTS**(如本地部署的 CosyVoice)——本机优先、
|
|
57
|
+
失败自动回退微软 Windows 语音;用 nf 时让本机模型**常驻显存**、nf 全部退出即释放
|
|
58
|
+
(租约机制,被强杀也能自愈回收)。新增 `/voice full|brief` 切换播报范围(默认**全量播报**)。
|
|
59
|
+
另修:配置里的残留值(失效 provider / 旧档位)自动收敛并给出可定位告警,
|
|
60
|
+
不再让启动失败或报假警。
|
|
62
61
|
|
|
63
62
|
---
|
|
64
63
|
|
|
@@ -8,14 +8,13 @@
|
|
|
8
8
|
|
|
9
9
|
NexForgeCLI 是一个运行在终端里的智能体助手,既支持 DeepSeek/千问等多模型,也是**插件宿主**(plugins 目录即插即用)。既能作为全屏 TUI 交互式对话/编码,也能作为 one-shot 命令行工具嵌进脚本和管道。
|
|
10
10
|
|
|
11
|
-
> 当前版本:**v0.
|
|
12
|
-
|
|
13
|
-
**v0.
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
两层重试放大修复(429 时由最多 12 次请求降为 `1 + max_retries` 次)。
|
|
11
|
+
> 当前版本:**v0.4.0** · PyPI: `pip install nexforge-cli`
|
|
12
|
+
|
|
13
|
+
**v0.4.0 要点**:语音播报接入**本机 TTS**(如本地部署的 CosyVoice)——本机优先、
|
|
14
|
+
失败自动回退微软 Windows 语音;用 nf 时让本机模型**常驻显存**、nf 全部退出即释放
|
|
15
|
+
(租约机制,被强杀也能自愈回收)。新增 `/voice full|brief` 切换播报范围(默认**全量播报**)。
|
|
16
|
+
另修:配置里的残留值(失效 provider / 旧档位)自动收敛并给出可定位告警,
|
|
17
|
+
不再让启动失败或报假警。
|
|
19
18
|
|
|
20
19
|
---
|
|
21
20
|
|
|
@@ -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
|
+
# 发布 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`)
|
|
@@ -82,6 +82,7 @@
|
|
|
82
82
|
| `/provider [<name>]` | 无参数列出提供者;带参数切换 |
|
|
83
83
|
| `/model flash` | 切换模型档位(仅 DeepSeek) |
|
|
84
84
|
| `/think on|off` | 开关思考模式(仅 DeepSeek) |
|
|
85
|
+
| `/voice full|brief` | 语音播报范围(full=**全量播报**最终报告, 默认; brief=只播报第一段) |
|
|
85
86
|
| `/balance` | 查询账户余额(仅 DeepSeek) |
|
|
86
87
|
| `/cost` | 显示累计费用 |
|
|
87
88
|
|
|
@@ -54,11 +54,17 @@ base_url = "https://dashscope.aliyuncs.com/compatible-mode/v1" # 可选, 图片
|
|
|
54
54
|
global_path = "~/.nexforge/kb" # 全局知识库位置(可自定义到其他硬盘, 如 D:/NexKB)
|
|
55
55
|
|
|
56
56
|
[tts]
|
|
57
|
+
# 语音播报(需安装 speech_summary 插件): 默认 auto = 本机 CosyVoice 优先 → 微软 Windows 语音回退
|
|
57
58
|
api_key = "" # 千问平台 TTS key(留空回退 [image])
|
|
58
59
|
model = "qwen-audio-3.0-tts-flash" # 千问 TTS 模型
|
|
59
|
-
voice = "loongmary" #
|
|
60
|
-
engine = "auto" # auto |
|
|
61
|
-
|
|
60
|
+
voice = "loongmary" # 千问音色(flash 用 loong 系列: loongmary/loongeva/loongJohn 等)
|
|
61
|
+
engine = "auto" # auto | cosyvoice | local(微软, 别名 microsoft) | qianwen
|
|
62
|
+
cosyvoice_url = "http://127.0.0.1:9880" # 本机 CosyVoice 服务(见其 local_deploy/service)
|
|
63
|
+
cosyvoice_voice = "default" # 本机服务音色 id(GET /voices 可查)
|
|
64
|
+
cosyvoice_timeout_s = 90 # 本机服务超时(空闲卸载后冷启动约 20s, 留足余量)
|
|
65
|
+
cosyvoice_resident = true # nf 运行期间让本机模型常驻显存(退出后服务端立即卸载)
|
|
66
|
+
max_chars = 0 # 播报文本上限(0=不限, 默认; 配合全量播报)
|
|
67
|
+
mode = "full" # 播报范围: full=全量播报最终报告(默认) | brief=只播第一段
|
|
62
68
|
enabled = true # 语音播报总开关
|
|
63
69
|
|
|
64
70
|
[search]
|
|
@@ -34,6 +34,16 @@ DeepSeek:https://platform.deepseek.com/api_keys
|
|
|
34
34
|
|
|
35
35
|
## 模型与连接
|
|
36
36
|
|
|
37
|
+
**Q:启动后提示「provider 'xxx' 未在配置中定义」/「未初始化,无法对话」?**
|
|
38
|
+
|
|
39
|
+
`config.toml` 的 `[provider] default` 指向的 provider 已不在 `models.toml` 里(被删除或改名,
|
|
40
|
+
例如早期版本的 `deepseek-v41`)。这类**悬空值**会让初始化失败,并连带报出误导性的"缺少 API Key"。
|
|
41
|
+
|
|
42
|
+
- **当前版本会自动回退**到内置 `deepseek`,并打印一条 `[配置警告]`(不阻断启动)。
|
|
43
|
+
- 若你仍看到"初始化失败",说明 nf 是旧版本 → `pip install -U nexforge-cli`。
|
|
44
|
+
- 想继续用某个 provider:把 `[provider] default` 改成它在 `models.toml` 中的 `name`;
|
|
45
|
+
用 `F6` 侧栏点选 provider 也会自动写回。
|
|
46
|
+
|
|
37
47
|
**Q:切换模型后没有反应?**
|
|
38
48
|
|
|
39
49
|
1. 先按 F6,在侧边栏点击「验证」测试连通性
|
|
@@ -87,6 +97,29 @@ DeepSeek:https://platform.deepseek.com/api_keys
|
|
|
87
97
|
|
|
88
98
|
浏览器打开 http://localhost:8899,可查看会话/计划/消息、发送消息(仅本机可访问)。
|
|
89
99
|
|
|
100
|
+
## 语音播报
|
|
101
|
+
|
|
102
|
+
**Q:播报变成微软的声音了(或干脆没声音)?**
|
|
103
|
+
|
|
104
|
+
语音默认走 **本机 CosyVoice 服务优先 → 失败自动回退微软 Windows 语音**(`[tts] engine = "auto"`)。
|
|
105
|
+
播报成微软的声音,说明本机服务当时没响应。排查顺序:
|
|
106
|
+
|
|
107
|
+
```bat
|
|
108
|
+
:: 1) 服务在不在(该"服务"是常驻守护进程, 模型按需加载)
|
|
109
|
+
D:\DevelopProject\CosyVoice\local_deploy\service\service.bat status
|
|
110
|
+
D:\DevelopProject\CosyVoice\local_deploy\service\service.bat start
|
|
111
|
+
|
|
112
|
+
:: 2) 看 nf 里的实时状态与原因(会主动探测本机服务)
|
|
113
|
+
/capabilities
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
- **服务空闲会自动卸载模型**(默认 300s,见 CosyVoice 的 `service/config.json` 里 `idle_timeout_s`)。
|
|
117
|
+
下一句播报会**冷启动约 20s**:这段时间没声音是正常的(nf 的超时给了 90s,会等它)。
|
|
118
|
+
想避免冷启动:把 `idle_timeout_s` 设为 `0`(常驻)或调大。
|
|
119
|
+
- **固定用某个引擎**:`[tts] engine = "cosyvoice"`(只用本机,不静默换声音)、
|
|
120
|
+
`"local"`(只用微软)、`"qianwen"`(云端,需 dashscope 与 Key)。
|
|
121
|
+
- 本机服务不可用而回退微软时,**首次会提示一次**(带原因),不会悄悄换声音。
|
|
122
|
+
|
|
90
123
|
## 终端
|
|
91
124
|
|
|
92
125
|
**Q:终端标签显示乱码?**
|
|
@@ -71,8 +71,9 @@ def register(api):
|
|
|
71
71
|
源码在仓库 **`plugin_speech_summary/`**(含 `install_plugin.py` 安装器与自检),
|
|
72
72
|
安装后位于 `~/.nexforge/plugins/speech_summary.py` —— 每轮任务结束用 TTS 播报简报:
|
|
73
73
|
|
|
74
|
-
-
|
|
75
|
-
- **配置**(config.toml `[tts]` 段):`engine=auto|
|
|
74
|
+
- **引擎**:**本机 CosyVoice 服务优先**(HTTP `127.0.0.1:9880`,见 CosyVoice 项目的 `local_deploy/service`),失败自动回退微软 Windows 本地语音(SAPI);千问 TTS 保留为显式选项(`engine="qianwen"`)
|
|
75
|
+
- **配置**(config.toml `[tts]` 段):`engine=auto|cosyvoice|local(别名 microsoft)|qianwen`、`cosyvoice_url`/`cosyvoice_voice`/`cosyvoice_timeout_s`/`cosyvoice_resident`、`voice`、`api_key`(留空用 [image] 按量 key)、`ws_url`、`max_chars`(0=不限,默认)、`mode`(`full`=全量播报最终报告,默认 / `brief`=只播第一段)、`enabled`
|
|
76
|
+
- **模型常驻**(`cosyvoice_resident = true`,默认开):nf 启动即向本机 TTS 服务登记"租约",模型随预热加载并**常驻显存**(首句播报不用等约 20s 冷启动);**所有 nf 退出后服务端立即卸载**。客户端被强杀也不会把显存占住——服务端按 PID 存活 + 心跳超时回收租约。
|
|
76
77
|
- **不阻塞**:合成+播放在后台线程,回合结束流程零延迟
|
|
77
78
|
- **简报内容**:项目名 + 工具调用次数 + AI 最终总结摘要(自动清理 Markdown 符号)
|
|
78
79
|
|
|
@@ -27,6 +27,8 @@ python scripts/check.py --only version-sync # 只跑一项
|
|
|
27
27
|
| `manual-consistency` | 手册与代码一致性(工具、命令、快捷键、配置字段、引用路径、链接)| 具体用例名与断言消息 |
|
|
28
28
|
| `registry-consistency` | 工具 / 斜杠命令 / 快捷键 与手册**双向**匹配 | 缺失项与幽灵项(手册写了但代码没有)|
|
|
29
29
|
| `hygiene` | 临时残留目录、敏感文件、异常大文件 | 路径与命中规则名 |
|
|
30
|
+
| `eof-newline` | 跟踪的文本文件必须以换行结尾(防「黏行」损坏)| 缺换行的文件路径 + 补换行提示 |
|
|
31
|
+
| `example-config` | `config.example.toml` 的每个值必须等于代码默认值 | 偏离字段(示例值 vs 默认值)|
|
|
30
32
|
|
|
31
33
|
## 端到端冒烟(需真实 API Key)
|
|
32
34
|
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# NexForgeCLI 插件:任务简报语音播报
|
|
2
|
+
|
|
3
|
+
每轮任务结束后,用 TTS 播报一句简报(项目名 + 工具调用次数 + AI 总结摘要)。
|
|
4
|
+
|
|
5
|
+
> 这是 NexForgeCLI 的**内置示例插件**,源码随仓库提供,可作插件开发的参考实现。
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 安装
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# 方式 A:用安装脚本(推荐)
|
|
13
|
+
python install_plugin.py # 装到用户级 ~/.nexforge/plugins/
|
|
14
|
+
python install_plugin.py --project # 装到当前项目 .nexforge/plugins/
|
|
15
|
+
python install_plugin.py --check # 只自检,不安装
|
|
16
|
+
python install_plugin.py --uninstall # 卸载
|
|
17
|
+
|
|
18
|
+
# 方式 B:手动复制
|
|
19
|
+
cp speech_summary.py ~/.nexforge/plugins/
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
装好后**重启 nf** 生效 —— 本插件注册的是 **UI 特性**(`register/on_mount` 生命周期钩子),
|
|
23
|
+
而 `/plugins reload` 只重载**工具/技能**(nf 自己也会提示「新特性需重启」)。
|
|
24
|
+
`/plugins` 可查看插件加载状态。
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## 配置(`config.toml` 的 `[tts]` 段)
|
|
29
|
+
|
|
30
|
+
```toml
|
|
31
|
+
[tts]
|
|
32
|
+
enabled = true # 总开关
|
|
33
|
+
engine = "auto" # auto | cosyvoice | local(微软, 别名 microsoft) | qianwen
|
|
34
|
+
# —— 本机 CosyVoice 服务(推荐; 见 CosyVoice 项目的 local_deploy/service) ——
|
|
35
|
+
cosyvoice_url = "http://127.0.0.1:9880" # 仅回环, 无需鉴权
|
|
36
|
+
cosyvoice_voice = "default" # 音色 id(GET /voices 可查)
|
|
37
|
+
cosyvoice_timeout_s = 90 # 空闲会卸载模型 → 冷启动约 20s, 留足余量
|
|
38
|
+
cosyvoice_resident = true # nf 在跑就让模型常驻; nf 全退出后服务端立即卸载
|
|
39
|
+
# —— 千问 TTS(可选) ——
|
|
40
|
+
voice = "loongmary" # 千问音色(loong 系列)
|
|
41
|
+
model = "qwen-audio-3.0-tts-flash"
|
|
42
|
+
api_key = "" # 留空回退 [image] 段 / DASHSCOPE_API_KEY / dashscope.key
|
|
43
|
+
ws_url = "" # 留空用官方默认 WebSocket 端点
|
|
44
|
+
max_chars = 0 # 播报文本上限(0=不限, 默认)
|
|
45
|
+
mode = "full" # 播报范围: full=全量播报最终报告 | brief=只播第一段
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## 引擎
|
|
49
|
+
|
|
50
|
+
| engine | 行为 | 依赖 |
|
|
51
|
+
|---|---|---|
|
|
52
|
+
| `cosyvoice` | **本机 CosyVoice 服务**(HTTP `POST /tts`)| 本机服务在跑(`service.bat start`)|
|
|
53
|
+
| `local`(别名 `microsoft`) | 微软 Windows 语音(SAPI,优先选中文音色)| Windows |
|
|
54
|
+
| `qianwen` | 千问语音合成(WebSocket)| `pip install dashscope` + 控制台开通 qwen-audio-3.0-tts-flash |
|
|
55
|
+
| `auto`(默认) | **本机 CosyVoice 优先 → 失败回退微软 Windows 语音** | 见上 |
|
|
56
|
+
|
|
57
|
+
> 优先级按"本机优先"设计:本机服务可用时不出网、不产生云端费用;它没起来时自动退到
|
|
58
|
+
> 微软语音,**并在首次回退时提示一次**(否则用户只会觉得"声音变了"却查不出原因)。
|
|
59
|
+
> 千问不在 auto 链里 —— 它是要花钱的云端服务,需显式 `engine = "qianwen"` 才用。
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## 播报范围(`/voice` 切换)
|
|
64
|
+
|
|
65
|
+
| mode | 播报内容 |
|
|
66
|
+
|---|---|
|
|
67
|
+
| `full`(默认) | **全量播报**最终报告 —— 本机合成本来就不花 token,没必要截断 |
|
|
68
|
+
| `brief` | 只播报最终报告的**第一段**(若首段是代码块,自动跳到下一段,避免念代码)|
|
|
69
|
+
|
|
70
|
+
两种模式都会带上"项目名 + 本轮调用工具次数"的前缀;`max_chars`(默认 0=不限)可作兜底上限。
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
/voice # 查看当前范围
|
|
74
|
+
/voice full # 全量播报(默认)
|
|
75
|
+
/voice brief # 只播第一段
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
> 在 nf 里直接输入 `/voice brief` 即可,切换会**写回项目配置**(重启后仍生效)。
|
|
79
|
+
|
|
80
|
+
**两条容易踩的细节**(都已按「尊重你的显式操作」实现):
|
|
81
|
+
|
|
82
|
+
- **只有启动时预热一次**,之后只续租(`warm=false`)。所以你手动
|
|
83
|
+
`service.bat unload` 释放显存后,模型**不会被 nf 抢着加载回来** ——
|
|
84
|
+
下次真正播报时按需加载,之后照常常驻。
|
|
85
|
+
- **重复挂载也安全**:起续租线程前先问服务端「我这个 pid 已在租约里了吗」,
|
|
86
|
+
已登记就直接返回 —— 不会出现多个线程重复续租。
|
|
87
|
+
|
|
88
|
+
## 模型常驻(租约)
|
|
89
|
+
|
|
90
|
+
默认 **`cosyvoice_resident = true`**:nf 一启动就向本机服务登记一个**租约**,模型随即在后台加载并**常驻显存**,
|
|
91
|
+
于是播报不用每次等 20s 冷启动;**nf 全部退出后服务端立即卸载**(不等空闲超时)。
|
|
92
|
+
|
|
93
|
+
- 为什么这么做:用 nf 的时候基本不跑别的 GPU 活,常驻换来"随叫随到";不用了立刻还显存。
|
|
94
|
+
- **强杀也不怕**:nf 被 `taskkill /F` / 崩溃时不会自己注销租约,服务端按 **PID 存活 + 心跳超时**
|
|
95
|
+
自愈回收(否则 3.7GB 显存会被无声占住)。
|
|
96
|
+
- 其他软件调用本机服务的行为**不变**:没租约时仍是"按需加载 + 空闲卸载"。
|
|
97
|
+
- 不想要常驻:`cosyvoice_resident = false`;想立刻释放:`service.bat unload`。
|
|
98
|
+
- 查看租约:`service.bat leases` 或 `status` 里的 `leases` / `resident` 字段。
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## 设计要点
|
|
103
|
+
|
|
104
|
+
1. **不阻塞**:合成 + 播放在后台线程,回合结束流程零延迟。
|
|
105
|
+
2. **不可用原因可见**(E 审计改进):引擎不可用 / 缺 SDK / 平台不支持时,会明确告知原因
|
|
106
|
+
(如「未安装 dashscope(pip install dashscope)」),**同一原因只提示一次**(防刷屏)。
|
|
107
|
+
仅"播报过程内"的异常才静默——不影响主流程。
|
|
108
|
+
3. **目标模式跳过**:多轮循环下单轮结束 ≠ 任务完成,播报会误导,故跳过。
|
|
109
|
+
4. **简报内容**:自动清理 Markdown 符号(代码块/加粗/链接等),只留可朗读正文。
|
|
110
|
+
5. **能力登记**:向主程序登记 `tts` 能力,`/capabilities` 可查看其状态与不可用原因
|
|
111
|
+
(旧版主程序没有该模块时会自动跳过,不影响插件本身)。
|
|
112
|
+
6. **回退要说话**(本机 TTS 接入新增):auto 模式里本机服务不可用而退到微软语音时,
|
|
113
|
+
**首次会提示一次**(带原因)——否则用户只会觉得“声音变了”却查不出原因。
|
|
114
|
+
7. **只认真正的 wav**:本机服务返回的响应必须带 `RIFF` 魔数才拿去播,
|
|
115
|
+
避免把错误页/空响应当音频播出去。
|
|
116
|
+
8. **探测有缓存、上报则现探**:每轮播报前的就绪检查走 30s 缓存(不卡 UI),
|
|
117
|
+
而 `/capabilities` 每次都实时探测 —— 避免“上报说可用、实际跑不通”。
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## 自检
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
python install_plugin.py --check
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
会报告:dashscope SDK 是否安装、当前平台、各 engine 的可用性判定。
|
|
@@ -46,25 +46,49 @@ def uninstall() -> None:
|
|
|
46
46
|
|
|
47
47
|
|
|
48
48
|
def check() -> bool:
|
|
49
|
-
"""环境自检: SDK / 平台 /
|
|
49
|
+
"""环境自检: 本机 TTS 服务 / SDK / 平台 / 引擎链可用性。"""
|
|
50
50
|
print("== 环境自检 ==")
|
|
51
51
|
ok = True
|
|
52
52
|
|
|
53
|
+
# 本机 CosyVoice 服务(auto 链的首选引擎)
|
|
54
|
+
sys.path.insert(0, str(HERE))
|
|
55
|
+
try:
|
|
56
|
+
import speech_summary as ss
|
|
57
|
+
|
|
58
|
+
cfg_tts = None
|
|
59
|
+
try:
|
|
60
|
+
from nexforge.config import load_config
|
|
61
|
+
|
|
62
|
+
cfg_tts = load_config().tts
|
|
63
|
+
except Exception:
|
|
64
|
+
pass
|
|
65
|
+
if cfg_tts is not None:
|
|
66
|
+
url = ss._cosy_url(cfg_tts)
|
|
67
|
+
if ss._cosyvoice_ready(cfg_tts, timeout=2.5):
|
|
68
|
+
print(f" 本机 TTS 服务: 在跑({url}) → auto 将优先用它")
|
|
69
|
+
else:
|
|
70
|
+
print(f" 本机 TTS 服务: 未响应({url})")
|
|
71
|
+
print(" 启动: CosyVoice\\local_deploy\\service\\service.bat start")
|
|
72
|
+
print(" (未启动时 auto 会回退微软 Windows 语音)")
|
|
73
|
+
plugin_import_ok = True
|
|
74
|
+
except Exception as exc:
|
|
75
|
+
print(f" 插件导入: 失败! {exc}")
|
|
76
|
+
ok = False
|
|
77
|
+
plugin_import_ok = False
|
|
78
|
+
|
|
53
79
|
try:
|
|
54
80
|
import dashscope # noqa: F401
|
|
55
81
|
|
|
56
|
-
print(" dashscope SDK: 已安装(千问 TTS
|
|
82
|
+
print(" dashscope SDK: 已安装(千问 TTS 可用, 需显式 engine=\"qianwen\")")
|
|
57
83
|
except ImportError:
|
|
58
|
-
print(" dashscope SDK: 未安装 →
|
|
84
|
+
print(" dashscope SDK: 未安装 → 千问引擎不可用(auto 链不依赖它)")
|
|
59
85
|
print(" 需要时: pip install dashscope")
|
|
60
86
|
|
|
61
87
|
if os.name == "nt":
|
|
62
|
-
print(" 平台: Windows(
|
|
88
|
+
print(" 平台: Windows(微软 SAPI 语音可用, 作 auto 的回退)")
|
|
63
89
|
else:
|
|
64
|
-
print(f" 平台: {os.name}(
|
|
65
|
-
|
|
66
|
-
import dashscope # noqa: F401
|
|
67
|
-
except ImportError:
|
|
90
|
+
print(f" 平台: {os.name}(微软 SAPI 仅 Windows 支持)")
|
|
91
|
+
if not _local_service_ok(cfg_tts):
|
|
68
92
|
ok = False
|
|
69
93
|
|
|
70
94
|
key_file = Path.home() / ".nexforge" / "dashscope.key"
|
|
@@ -72,22 +96,27 @@ def check() -> bool:
|
|
|
72
96
|
if has_env or key_file.exists():
|
|
73
97
|
print(" DashScope Key: 已检测到(环境变量或 dashscope.key)")
|
|
74
98
|
else:
|
|
75
|
-
print(" DashScope Key: 未检测到(可写 config.toml [tts]/[image] 段 api_key)")
|
|
76
|
-
|
|
77
|
-
# 试导入插件本体
|
|
78
|
-
sys.path.insert(0, str(HERE))
|
|
79
|
-
try:
|
|
80
|
-
import speech_summary as ss # noqa: F401
|
|
99
|
+
print(" DashScope Key: 未检测到(仅千问引擎需要; 可写 config.toml [tts]/[image] 段 api_key)")
|
|
81
100
|
|
|
101
|
+
if plugin_import_ok:
|
|
82
102
|
print(" 插件导入: OK(register 已加载)")
|
|
83
|
-
except Exception as exc:
|
|
84
|
-
print(f" 插件导入: 失败! {exc}")
|
|
85
|
-
ok = False
|
|
86
103
|
|
|
87
104
|
print("== 自检通过 ==" if ok else "== 自检未通过, 按提示修复 ==")
|
|
88
105
|
return ok
|
|
89
106
|
|
|
90
107
|
|
|
108
|
+
def _local_service_ok(cfg_tts) -> bool: # noqa: ANN001
|
|
109
|
+
"""非 Windows 平台: 只有本机服务可用时 auto 才成立。"""
|
|
110
|
+
if cfg_tts is None:
|
|
111
|
+
return False
|
|
112
|
+
try:
|
|
113
|
+
import speech_summary as ss
|
|
114
|
+
|
|
115
|
+
return ss._cosyvoice_ready(cfg_tts, timeout=2.5)
|
|
116
|
+
except Exception:
|
|
117
|
+
return False
|
|
118
|
+
|
|
119
|
+
|
|
91
120
|
def main() -> None:
|
|
92
121
|
ap = argparse.ArgumentParser(description="语音播报插件安装器")
|
|
93
122
|
ap.add_argument("--project", action="store_true", help="安装到项目级(默认用户级)")
|