nexforge-cli 0.2.18__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.
Files changed (322) hide show
  1. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/PKG-INFO +7 -5
  2. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/README.md +6 -4
  3. nexforge_cli-0.4.0/config.example.toml +41 -0
  4. nexforge_cli-0.4.0/docs/decisions/2026-09-11-deepseek/346/241/243/344/275/215/347/256/200/345/214/226.md +42 -0
  5. 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 +90 -0
  6. 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 +91 -0
  7. 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
  8. 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 +62 -0
  9. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/docs/decisions/README.md +5 -0
  10. nexforge_cli-0.4.0/docs/evidence/exit-cost.log +9 -0
  11. nexforge_cli-0.4.0/docs/evidence/final_check_fulltest.log +7 -0
  12. nexforge_cli-0.4.0/docs/evidence/final_check_pypi.log +11 -0
  13. nexforge_cli-0.4.0/docs/evidence/final_check_report.log +22 -0
  14. nexforge_cli-0.4.0/docs/evidence/model-sidebar-dot.log +54 -0
  15. nexforge_cli-0.4.0/docs/evidence/model_rename_check.log +52 -0
  16. nexforge_cli-0.4.0/docs/evidence/provider-family-check.log +42 -0
  17. nexforge_cli-0.4.0/docs/evidence/release-0.3.0.log +65 -0
  18. nexforge_cli-0.4.0/docs/evidence/release_0218_pypi.log +9 -0
  19. nexforge_cli-0.4.0/docs/evidence/simplify_tier_fulltest.log +7 -0
  20. nexforge_cli-0.4.0/docs/evidence/terminal-leak-conditions.log +11 -0
  21. {nexforge_cli-0.2.18 → 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
  22. {nexforge_cli-0.2.18 → 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 +3 -2
  23. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/manual/03-/346/250/241/345/236/213/351/205/215/347/275/256.md +43 -24
  24. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/manual/05-/346/225/260/346/215/256/344/270/216/346/220/234/347/264/242.md +2 -2
  25. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/manual/07-/344/274/232/350/257/235/344/270/216/350/256/260/345/277/206.md +4 -0
  26. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/manual/08-/350/207/252/345/256/232/344/271/211/351/205/215/347/275/256.md +10 -4
  27. nexforge_cli-0.4.0/manual/09-/345/270/270/350/247/201/351/227/256/351/242/230.md +153 -0
  28. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/manual/10-/346/217/222/344/273/266/345/274/200/345/217/221.md +3 -2
  29. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/manual/11-/345/274/200/345/217/221/346/240/241/351/252/214.md +19 -0
  30. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/manual/README.md +1 -2
  31. {nexforge_cli-0.2.18 → 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 +1 -1
  32. nexforge_cli-0.4.0/plugin_speech_summary/README.md +127 -0
  33. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/plugin_speech_summary/install_plugin.py +46 -17
  34. nexforge_cli-0.4.0/plugin_speech_summary/speech_summary.py +690 -0
  35. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/pyproject.toml +1 -1
  36. nexforge_cli-0.4.0/scripts/exit_cost_probe.py +76 -0
  37. nexforge_cli-0.4.0/scripts/exit_smoke.py +113 -0
  38. nexforge_cli-0.4.0/scripts/gates/eof_newline.py +106 -0
  39. nexforge_cli-0.4.0/scripts/gates/example_config.py +100 -0
  40. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/scripts/publish.sh +41 -36
  41. nexforge_cli-0.4.0/scripts/smoke_tui.py +102 -0
  42. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/__init__.py +1 -1
  43. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/app.py +41 -1
  44. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/cli.py +29 -5
  45. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/config.py +103 -37
  46. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/core/agent.py +22 -6
  47. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/features/session_feature.py +26 -0
  48. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/i18n.py +11 -0
  49. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/provider/__init__.py +2 -2
  50. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/provider/anthropic.py +1 -1
  51. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/provider/deepseek.py +32 -6
  52. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/provider/openai.py +7 -2
  53. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/provider/qianwen.py +7 -2
  54. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/store/session_store.py +41 -11
  55. nexforge_cli-0.4.0/src/nexforge/terminal_guard.py +281 -0
  56. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/tools/image_tools.py +53 -27
  57. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/ui/screens/chat.py +34 -23
  58. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/ui/slash_handler.py +33 -6
  59. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/ui/widgets/model_sidebar.py +87 -25
  60. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/ui/widgets/plan_sidebar.py +2 -0
  61. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/ui/widgets/sidebar.py +2 -0
  62. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/ui/widgets/statusbar.py +1 -1
  63. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/web/server.py +1 -1
  64. nexforge_cli-0.4.0/tests/test_archive_integrity.py +176 -0
  65. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/tests/test_check_gates.py +112 -0
  66. nexforge_cli-0.4.0/tests/test_config.py +488 -0
  67. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/tests/test_deepseek_provider.py +4 -5
  68. nexforge_cli-0.4.0/tests/test_exit_smoke.py +121 -0
  69. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/tests/test_manual_consistency.py +2 -2
  70. nexforge_cli-0.4.0/tests/test_model_sidebar_dot.py +254 -0
  71. nexforge_cli-0.4.0/tests/test_plugin_example_tts.py +640 -0
  72. nexforge_cli-0.4.0/tests/test_quit_paths.py +109 -0
  73. nexforge_cli-0.4.0/tests/test_retry_policy.py +180 -0
  74. nexforge_cli-0.4.0/tests/test_session_save_safety.py +146 -0
  75. nexforge_cli-0.4.0/tests/test_sidebar_align.py +39 -0
  76. nexforge_cli-0.4.0/tests/test_terminal_guard.py +326 -0
  77. nexforge_cli-0.4.0/tests/test_tui.py +147 -0
  78. nexforge_cli-0.4.0/tests/test_vision_capability.py +86 -0
  79. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/tests/test_vision_model.py +40 -11
  80. nexforge_cli-0.2.18/config.example.toml +0 -42
  81. nexforge_cli-0.2.18/manual/09-/345/270/270/350/247/201/351/227/256/351/242/230.md +0 -87
  82. nexforge_cli-0.2.18/plugin_speech_summary/README.md +0 -68
  83. nexforge_cli-0.2.18/plugin_speech_summary/speech_summary.py +0 -374
  84. nexforge_cli-0.2.18/scripts/smoke_tui.py +0 -45
  85. nexforge_cli-0.2.18/src/nexforge/core/router.py +0 -62
  86. nexforge_cli-0.2.18/tests/test_config.py +0 -275
  87. nexforge_cli-0.2.18/tests/test_plugin_example_tts.py +0 -144
  88. nexforge_cli-0.2.18/tests/test_tui.py +0 -50
  89. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/.gitignore +0 -0
  90. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/INSTALL.md +0 -0
  91. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/STATS_API.md +0 -0
  92. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/assets/icons/agent.svg +0 -0
  93. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/assets/icons/cost.svg +0 -0
  94. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/assets/icons/edit.svg +0 -0
  95. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/assets/icons/err.svg +0 -0
  96. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/assets/icons/file.svg +0 -0
  97. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/assets/icons/model.svg +0 -0
  98. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/assets/icons/ok.svg +0 -0
  99. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/assets/icons/read.svg +0 -0
  100. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/assets/icons/running.svg +0 -0
  101. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/assets/icons/search.svg +0 -0
  102. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/assets/icons/shell.svg +0 -0
  103. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/assets/icons/thinking.svg +0 -0
  104. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/assets/icons/web.svg +0 -0
  105. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/assets/icons/write.svg +0 -0
  106. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/devlog/2026-07-07.md +0 -0
  107. {nexforge_cli-0.2.18 → 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
  108. {nexforge_cli-0.2.18 → 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
  109. {nexforge_cli-0.2.18 → 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
  110. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/docs/decisions/2026-09-10-fails-loud/345/210/206/347/272/247.md" +0 -0
  111. {nexforge_cli-0.2.18 → 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
  112. {nexforge_cli-0.2.18 → 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
  113. {nexforge_cli-0.2.18 → 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
  114. {nexforge_cli-0.2.18 → 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
  115. {nexforge_cli-0.2.18 → 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
  116. {nexforge_cli-0.2.18 → 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
  117. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/docs/evidence/D10_acceptance.log +0 -0
  118. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/docs/evidence/D10_full_test.log +0 -0
  119. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/docs/evidence/D11_a4.log +0 -0
  120. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/docs/evidence/D11_full_test.log +0 -0
  121. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/docs/evidence/D12_audit.md +0 -0
  122. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/docs/evidence/D12_gap.log +0 -0
  123. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/docs/evidence/D13_break.log +0 -0
  124. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/docs/evidence/D13_e2e.log +0 -0
  125. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/docs/evidence/D13_full_test.log +0 -0
  126. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/docs/evidence/D13_gaps.log +0 -0
  127. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/docs/evidence/D14_audit.md +0 -0
  128. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/docs/evidence/D14_full_test.log +0 -0
  129. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/docs/evidence/D14_recheck.log +0 -0
  130. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/docs/evidence/D15_audit.md +0 -0
  131. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/docs/evidence/D4_fast.log +0 -0
  132. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/docs/evidence/D4_full_test.log +0 -0
  133. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/docs/evidence/D4_hook.log +0 -0
  134. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/docs/evidence/D4_scene_c.log +0 -0
  135. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/docs/evidence/D4_scene_d.log +0 -0
  136. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/docs/evidence/D4_scene_e.log +0 -0
  137. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/docs/evidence/D5_audit.md +0 -0
  138. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/docs/evidence/D5_b2_plugin.log +0 -0
  139. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/docs/evidence/D5_full_test.log +0 -0
  140. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/docs/evidence/D6_b3_a.log +0 -0
  141. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/docs/evidence/D6_b3_b.log +0 -0
  142. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/docs/evidence/D6_b3_c.log +0 -0
  143. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/docs/evidence/D6_b4_provider.log +0 -0
  144. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/docs/evidence/D6_full_test.log +0 -0
  145. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/docs/evidence/D7_dedup.log +0 -0
  146. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/docs/evidence/D7_full_test.log +0 -0
  147. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/docs/evidence/D8_audit.md +0 -0
  148. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/docs/evidence/D8_invariant.log +0 -0
  149. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/docs/evidence/D9_after.log +0 -0
  150. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/docs/evidence/D9_before.log +0 -0
  151. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/docs/evidence/D9_decision.md +0 -0
  152. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/docs/evidence/D9_full_test.log +0 -0
  153. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/docs/evidence/D9_rebuild.log +0 -0
  154. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/docs/evidence/E_cap_fulltest.log +0 -0
  155. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/docs/evidence/E_capabilities.log +0 -0
  156. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/docs/evidence/E_engines.log +0 -0
  157. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/docs/evidence/E_engines_fulltest.log +0 -0
  158. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/docs/evidence/E_tts_availability.log +0 -0
  159. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/docs/evidence/E_tts_fulltest.log +0 -0
  160. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/docs/evidence/E_tts_installer.log +0 -0
  161. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/docs/evidence/E_vision_channels.log +0 -0
  162. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/docs/evidence/E_vision_fulltest.log +0 -0
  163. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/docs/evidence/F_decisions_fulltest.log +0 -0
  164. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/docs/evidence/deps_fulltest.log +0 -0
  165. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/docs/evidence/deps_reverse.log +0 -0
  166. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/docs/evidence/fix_fulltest.log +0 -0
  167. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/docs/evidence/health_fulltest.log +0 -0
  168. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/docs/evidence/health_report.md +0 -0
  169. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/docs/evidence/health_scan.log +0 -0
  170. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/docs/evidence/release_0218_fulltest.log +0 -0
  171. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/docs/session-invariant.md +0 -0
  172. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/docs/submission/README-/346/212/225/347/250/277/350/257/264/346/230/216.md" +0 -0
  173. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/docs/submission/nexforge.md +0 -0
  174. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/docs/submission/nexforge.zh-CN.md +0 -0
  175. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/manual/04-/345/267/245/345/205/267/345/217/202/350/200/203.md" +0 -0
  176. {nexforge_cli-0.2.18 → 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
  177. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/plugin_gui_auto/README.md +0 -0
  178. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/plugin_gui_auto/guiauto/__init__.py +0 -0
  179. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/plugin_gui_auto/guiauto/action.py +0 -0
  180. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/plugin_gui_auto/guiauto/base.py +0 -0
  181. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/plugin_gui_auto/guiauto/tools.py +0 -0
  182. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/plugin_gui_auto/guiauto/vision.py +0 -0
  183. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/plugin_gui_auto/guiauto/window.py +0 -0
  184. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/plugin_gui_auto/install_plugin.py +0 -0
  185. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/plugin_gui_auto/requirements.txt +0 -0
  186. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/plugin_gui_auto/smoke_safe.py +0 -0
  187. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/plugin_gui_auto/verify_final.py +0 -0
  188. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/plugin_gui_auto/verify_host.py +0 -0
  189. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/plugin_gui_auto/verify_plugin.py +0 -0
  190. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/scripts/__init__.py +0 -0
  191. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/scripts/check.py +0 -0
  192. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/scripts/gates/__init__.py +0 -0
  193. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/scripts/gates/base.py +0 -0
  194. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/scripts/gates/hygiene.py +0 -0
  195. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/scripts/gates/manual_consistency.py +0 -0
  196. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/scripts/gates/registry_consistency.py +0 -0
  197. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/scripts/gates/version_sync.py +0 -0
  198. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/scripts/install_hooks.py +0 -0
  199. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/scripts/smoke_provider.py +0 -0
  200. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/__main__.py +0 -0
  201. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/core/__init__.py +0 -0
  202. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/core/capability.py +0 -0
  203. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/core/context.py +0 -0
  204. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/core/federation.py +0 -0
  205. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/core/goal_manager.py +0 -0
  206. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/core/jobs.py +0 -0
  207. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/core/knowledge_base.py +0 -0
  208. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/core/memory_engine.py +0 -0
  209. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/core/model_catalog.py +0 -0
  210. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/core/permissions.py +0 -0
  211. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/core/plugin.py +0 -0
  212. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/core/project_mapper.py +0 -0
  213. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/core/provider.py +0 -0
  214. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/core/session.py +0 -0
  215. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/core/summarizer.py +0 -0
  216. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/core/system_inject.py +0 -0
  217. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/core/tool_manifest.py +0 -0
  218. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/core/types.py +0 -0
  219. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/core/update_check.py +0 -0
  220. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/features/__init__.py +0 -0
  221. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/features/auto_skills.py +0 -0
  222. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/features/base.py +0 -0
  223. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/features/federation_feature.py +0 -0
  224. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/features/kb_feature.py +0 -0
  225. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/features/map_feature.py +0 -0
  226. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/features/memory_feature.py +0 -0
  227. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/features/mode_features.py +0 -0
  228. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/features/plan_feature.py +0 -0
  229. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/features/registry.py +0 -0
  230. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/features/slash_feature.py +0 -0
  231. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/features/stats_feature.py +0 -0
  232. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/features/tool_manifest_feature.py +0 -0
  233. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/icons.py +0 -0
  234. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/mcp/__init__.py +0 -0
  235. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/provider/_retry.py +0 -0
  236. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/provider/balance.py +0 -0
  237. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/skills/loader.py +0 -0
  238. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/stats/__init__.py +0 -0
  239. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/stats/heartbeat.py +0 -0
  240. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/store/__init__.py +0 -0
  241. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/store/director_store.py +0 -0
  242. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/store/plan_store.py +0 -0
  243. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/theme.tcss +0 -0
  244. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/tools/__init__.py +0 -0
  245. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/tools/_paths.py +0 -0
  246. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/tools/background.py +0 -0
  247. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/tools/base.py +0 -0
  248. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/tools/data_tools.py +0 -0
  249. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/tools/edit_file.py +0 -0
  250. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/tools/federation.py +0 -0
  251. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/tools/fs_ops.py +0 -0
  252. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/tools/git.py +0 -0
  253. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/tools/kb_tools.py +0 -0
  254. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/tools/list_dir.py +0 -0
  255. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/tools/multi_edit.py +0 -0
  256. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/tools/ops_tools.py +0 -0
  257. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/tools/read_file.py +0 -0
  258. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/tools/registry.py +0 -0
  259. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/tools/screenshot_tools.py +0 -0
  260. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/tools/search.py +0 -0
  261. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/tools/search_content.py +0 -0
  262. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/tools/search_files.py +0 -0
  263. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/tools/shell.py +0 -0
  264. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/tools/web.py +0 -0
  265. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/tools/write_file.py +0 -0
  266. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/ui/__init__.py +0 -0
  267. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/ui/clipboard_image.py +0 -0
  268. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/ui/screen_api.py +0 -0
  269. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/ui/screens/__init__.py +0 -0
  270. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/ui/screens/chat_actions.py +0 -0
  271. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/ui/screens/splash.py +0 -0
  272. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/ui/widgets/__init__.py +0 -0
  273. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/ui/widgets/apikey_modal.py +0 -0
  274. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/ui/widgets/banner.py +0 -0
  275. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/ui/widgets/confirm_modal.py +0 -0
  276. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/ui/widgets/diff_modal.py +0 -0
  277. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/ui/widgets/file_browser.py +0 -0
  278. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/ui/widgets/file_menu.py +0 -0
  279. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/ui/widgets/image_sidebar.py +0 -0
  280. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/ui/widgets/messages.py +0 -0
  281. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/ui/widgets/mode_warning.py +0 -0
  282. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/ui/widgets/multiline_modal.py +0 -0
  283. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/ui/widgets/multiline_prompt.py +0 -0
  284. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/utils/__init__.py +0 -0
  285. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/utils/logging.py +0 -0
  286. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/web/__init__.py +0 -0
  287. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/src/nexforge/web/static/index.html +0 -0
  288. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/tests/conftest.py +0 -0
  289. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/tests/test_app_mouse.py +0 -0
  290. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/tests/test_approval.py +0 -0
  291. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/tests/test_capability.py +0 -0
  292. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/tests/test_cross_platform.py +0 -0
  293. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/tests/test_deps.py +0 -0
  294. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/tests/test_director_loop.py +0 -0
  295. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/tests/test_director_store.py +0 -0
  296. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/tests/test_dual_panel.py +0 -0
  297. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/tests/test_federation.py +0 -0
  298. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/tests/test_file_browser.py +0 -0
  299. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/tests/test_goal_confirm.py +0 -0
  300. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/tests/test_i18n.py +0 -0
  301. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/tests/test_icons.py +0 -0
  302. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/tests/test_lang_cmd.py +0 -0
  303. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/tests/test_m2_tools.py +0 -0
  304. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/tests/test_model_catalog.py +0 -0
  305. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/tests/test_p2_core.py +0 -0
  306. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/tests/test_plugin.py +0 -0
  307. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/tests/test_project_map_prune.py +0 -0
  308. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/tests/test_provider_errors.py +0 -0
  309. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/tests/test_sanitize.py +0 -0
  310. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/tests/test_screenshot_tools.py +0 -0
  311. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/tests/test_session_invariant.py +0 -0
  312. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/tests/test_session_repair.py +0 -0
  313. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/tests/test_session_store.py +0 -0
  314. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/tests/test_sidebar.py +0 -0
  315. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/tests/test_sidebar_actions.py +0 -0
  316. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/tests/test_splash_modal.py +0 -0
  317. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/tests/test_statusbar.py +0 -0
  318. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/tests/test_system_inject.py +0 -0
  319. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/tests/test_tool_iters.py +0 -0
  320. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/tests/test_tools.py +0 -0
  321. {nexforge_cli-0.2.18 → nexforge_cli-0.4.0}/tests/test_update_check.py +0 -0
  322. {nexforge_cli-0.2.18 → 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.2.18
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,11 +51,13 @@ Description-Content-Type: text/markdown
51
51
 
52
52
  NexForgeCLI 是一个运行在终端里的智能体助手,既支持 DeepSeek/千问等多模型,也是**插件宿主**(plugins 目录即插即用)。既能作为全屏 TUI 交互式对话/编码,也能作为 one-shot 命令行工具嵌进脚本和管道。
53
53
 
54
- > 当前版本:**v0.2.18** · PyPI: `pip install nexforge-cli`
54
+ > 当前版本:**v0.4.0** · PyPI: `pip install nexforge-cli`
55
55
 
56
- **v0.2.18 要点**:新增 `/capabilities`(一眼看清各能力是否可用、为何不可用);
57
- 会话可重建(恢复/切换后模型看到的历史与当初一致,工具链不再丢失);
58
- 插件与配置的静默失败治理(出错必可见);敏感文件硬拦截;push 前质量门禁。
56
+ **v0.4.0 要点**:语音播报接入**本机 TTS**(如本地部署的 CosyVoice)——本机优先、
57
+ 失败自动回退微软 Windows 语音;用 nf 时让本机模型**常驻显存**、nf 全部退出即释放
58
+ (租约机制,被强杀也能自愈回收)。新增 `/voice full|brief` 切换播报范围(默认**全量播报**)。
59
+ 另修:配置里的残留值(失效 provider / 旧档位)自动收敛并给出可定位告警,
60
+ 不再让启动失败或报假警。
59
61
 
60
62
  ---
61
63
 
@@ -8,11 +8,13 @@
8
8
 
9
9
  NexForgeCLI 是一个运行在终端里的智能体助手,既支持 DeepSeek/千问等多模型,也是**插件宿主**(plugins 目录即插即用)。既能作为全屏 TUI 交互式对话/编码,也能作为 one-shot 命令行工具嵌进脚本和管道。
10
10
 
11
- > 当前版本:**v0.2.18** · PyPI: `pip install nexforge-cli`
11
+ > 当前版本:**v0.4.0** · PyPI: `pip install nexforge-cli`
12
12
 
13
- **v0.2.18 要点**:新增 `/capabilities`(一眼看清各能力是否可用、为何不可用);
14
- 会话可重建(恢复/切换后模型看到的历史与当初一致,工具链不再丢失);
15
- 插件与配置的静默失败治理(出错必可见);敏感文件硬拦截;push 前质量门禁。
13
+ **v0.4.0 要点**:语音播报接入**本机 TTS**(如本地部署的 CosyVoice)——本机优先、
14
+ 失败自动回退微软 Windows 语音;用 nf 时让本机模型**常驻显存**、nf 全部退出即释放
15
+ (租约机制,被强杀也能自愈回收)。新增 `/voice full|brief` 切换播报范围(默认**全量播报**)。
16
+ 另修:配置里的残留值(失效 provider / 旧档位)自动收敛并给出可定位告警,
17
+ 不再让启动失败或报假警。
16
18
 
17
19
  ---
18
20
 
@@ -0,0 +1,41 @@
1
+ # NexForgeCLI 配置示例。
2
+ # 复制到 ~/.nexforge/config.toml 后按需修改;所有字段都有内置默认值,可只写要覆盖的项。
3
+ # DeepSeek V4 参数已核实自 api-docs.deepseek.com(2026-04)。
4
+
5
+ [provider]
6
+ base_url = "https://api.deepseek.com" # OpenAI 格式;Anthropic 格式加 /anthropic
7
+ api_key_env = "DEEPSEEK_API_KEY" # 优先从此环境变量读 key
8
+ # api_key = "sk-..." # 次选:直接写这里(不建议入库)
9
+ timeout = 120.0
10
+ max_retries = 2
11
+
12
+ [models]
13
+ flash = "deepseek-flash" # 原生多模态(文本+读图), 取代原 v4-flash + vision 双档位
14
+ default_tier = "flash" # 档位只剩 flash(pro/auto/vision 均已移除)
15
+ thinking = true # 思考模式,默认开
16
+ reasoning_effort = "high" # high | max
17
+ max_output_tokens = 32768 # 含思考 token; 别写 8192(思考会吃满 → 正文截断秒停)
18
+
19
+ # 定价(每百万 token,USD)—— 已核实
20
+ [models.pricing.flash]
21
+ input_cache_hit = 0.0028
22
+ input_cache_miss = 0.14
23
+ output = 0.28
24
+
25
+ [ui]
26
+ theme = "dark" # dark | light
27
+ show_thinking = true
28
+ icons = "auto" # auto | image | glyph | ascii
29
+ accent = "#6D5EF5"
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,42 @@
1
+ # 决策:DeepSeek 档位简化为 flash / vision(移除 pro 与 auto)
2
+
3
+ > 日期:2026-09-11 · 状态:生效 · **破坏性变更**
4
+
5
+ ## 背景
6
+
7
+ DeepSeek 调整模型阵容后,`pro` 档基本无用;而 `auto` 档的唯一作用是"命中升级信号时自动升到 pro"。
8
+ 用户要求简化 DS 的模型列表("只保留 flash,包括 pro 在内全部删掉")。
9
+
10
+ 同期实测(`docs/evidence/model_rename_check.log`):官方 `GET /models` 只剩 `deepseek-flash` 与 `deepseek-v4-pro`。
11
+
12
+ ## 决策
13
+
14
+ 1. `Tier` 从 `flash / pro / auto / vision` 简化为 **`flash / vision`**。
15
+ 2. 删 `ModelsConfig.pro` 与其计价项。
16
+ 3. **删除 `RouterConfig` 与整个 `core/router.py`**(`Router` 类)——
17
+ `agent.py` 改为直接按用户选定档位构造 `RouteDecision`。
18
+ 4. `vision` **保留**:识图与贴图直达模型的必需档位。
19
+
20
+ ## 理由
21
+
22
+ - **pro 已无用**(用户判断 + 实测支持),保留只会增加配置面与认知负担。
23
+ - **auto 是死逻辑**:它存在的意义是"升级到 pro",没有 pro 就无从升级。
24
+ - **否决"保留 auto 但降级为 flash"**:那等于给一个不存在的选择留按钮,纯误导。
25
+ - **否决"保留 pro 但改指向别的模型"**:用户诉求就是删掉它。
26
+
27
+ ## 代价与边界
28
+
29
+ - **破坏性**:`/model pro` 与 `/model auto` 不再可用(会提示未知档位);
30
+ 配置文件里残留的 `[router]` 段与 `models.pro` 将被"未知字段"检查提示(见 B 工程)。
31
+ 已写入的 `models.default_tier = "pro"/"auto"` 会自动迁移为 `flash` 并告警(见下一条 ADR)。
32
+ - **恢复成本**:若 DeepSeek 未来重启 pro/推理档,需要重新加档位 + 计价,
33
+ 并视情况恢复路由机制。
34
+ (数据载体 `RouteDecision` 仍在,但已内联到 `core/agent.py` 并退化为"固定档位"记录;
35
+ `Router` 与 `core/router.py` 已随单档化删除 —— 恢复路由需重写,不是改开关。)
36
+ - `reasoning_effort` / `thinking` 仍由 `[models]` 控制,效果不变——**只是不再自动切换**。
37
+
38
+ ## 关联
39
+
40
+ `src/nexforge/config.py`、`src/nexforge/core/agent.py`(`router.py` 已删)、
41
+ `ui/widgets/model_sidebar.py`、`ui/widgets/statusbar.py`、`ui/slash_handler.py`、`cli.py`、`web/server.py`、
42
+ `manual/02`、`manual/03`、`manual/08`、`config.example.toml`
@@ -0,0 +1,90 @@
1
+ # ADR: 档位简化为单档 flash
2
+
3
+ - 日期: 2026-09-11
4
+ - 状态: 已采纳
5
+ - 类型: **破坏性变更**
6
+
7
+ ## 背景
8
+
9
+ DeepSeek 于 2026-09 调整模型阵容:
10
+
11
+ 1. `deepseek-v4-pro` 处于更新前的尴尬期,实际可用性差 → 此前(2026-09)已删除 `pro` 与 `auto` 档位。
12
+ 2. 当时保留 `vision` 档位(模型 `deepseek-v4-flash-vision-exp`)用于识图。
13
+ 3. 现在官方新模型 **`deepseek-flash`** 已**原生多模态**(实测可准确读图),而原 flash 档位模型 `deepseek-v4-flash` **不支持读图**。
14
+
15
+ 实测证据(`docs/evidence/model_rename_check.log`):
16
+
17
+ | 模型 | 文本 | 读图 |
18
+ |---|---|---|
19
+ | `deepseek-v4-flash-vision-exp` | OK | 蓝圆 + 红三角 |
20
+ | `deepseek-flash` | OK | 蓝圆、红 |
21
+ | `deepseek-v4-flash` | OK | 空(不支持) |
22
+
23
+ ## 决策
24
+
25
+ **档位从 `flash / vision` 简化为单一 `flash`**,且 `flash` 档位模型由 `deepseek-v4-flash` 改为 `deepseek-flash`。
26
+
27
+ 具体改动:
28
+
29
+ 1. `Tier = Literal["flash"]`;删除 `ModelsConfig.vision` 与 `pricing.vision`。
30
+ 2. `models.flash` 默认值 → `deepseek-flash`(原生多模态)。
31
+ 3. 删除 F6 侧边栏的档位子按钮渲染与 `ms-tier-*` 分支(只剩单档,子选项失去意义)。
32
+ 4. 识图能力探测(`VisionCapability`)与 `_provider_vision_channel` 不再回退 `cfg.models.vision`,改为当前档位模型。
33
+ 5. CLI / 斜杠命令 / web 的档位白名单只保留 `flash`。
34
+
35
+ ## 理由
36
+
37
+ - **避免静默失败**(核心):若只删 vision 档位而不改 flash 模型名,用户切到 flash 后贴图会因 `deepseek-v4-flash` 不支持读图而**静默失败**——正是本项目持续根治的病(见 ADR-2026-09-10-fails-loud分级)。
38
+ - **消除冗余**:`deepseek-flash` 一档同时覆盖文本与读图,独立视觉档位不再必要。
39
+ - **代价可控**:旧档位名(`pro`/`vision`)写错会被明确拒绝,而非静默降级到错误模型。
40
+
41
+ ## 复盘:改名漏改一处判定,贴图静默退化(2026-09-11 第五轮复核)
42
+
43
+ **症状**:默认档位下贴图只发出 `[图片附件: 路径]` **文本占位符**,模型根本看不到图;
44
+ 识图工具通道报"模型 deepseek-flash 不支持图片输入"(与实测相反);而 `/capabilities`
45
+ **又报"可用"** —— 三者互相矛盾。
46
+
47
+ **根因**:多模态判定只有两条规则 —— ①模型名含 `"vision"`;②`models.toml` 里该
48
+ provider 显式 `multimodal = true`。而内置 deepseek **没有 pdef**(不在 `providers` 表里),
49
+ 新档位模型 `deepseek-flash` 名字里**也没有 "vision"**(旧档位模型
50
+ `deepseek-v4-flash-vision-exp` 靠名字就能过)→ 判定为纯文本。
51
+ `/capabilities` 之所以说"可用",是因为它的判据里有个 `or cfg.model_id()`,而
52
+ `model_id()` 恒非空 —— 于是"上报与实际"分叉。
53
+
54
+ **修法**:
55
+ 1. `provider/deepseek.py` 增加显式名单 `NATIVE_MULTIMODAL_MODELS = {"deepseek-flash"}`
56
+ (与既有 `"vision"` 名字规则并列);
57
+ 2. 抽出 `image_tools.vision_channel(cfg)` 作为**唯一**判定,供贴图通道、识图工具
58
+ 与 `VisionCapability` 三处共用(消除"上报说可用、实际跑不通")。
59
+
60
+ **为什么不用"配置开关"代替名单**:若让 `models.flash` 一律视为多模态,用户把它改成
61
+ 纯文本模型(如 `deepseek-v4-flash`)时会被错误放行 → 又把图片发给看不懂的模型。
62
+ 名单式规则与既有 `"vision"` 规则同构,代价小。
63
+
64
+ **维护纪律**:**DeepSeek 再次改名时,`NATIVE_MULTIMODAL_MODELS` 必须与本文同步更新。**
65
+ 这也解释了为什么"档位模型名"不是一个纯配置项 —— 它同时是多模态判定的输入。
66
+
67
+ ## 代价与边界
68
+
69
+ - **破坏性**:`/model pro`、`/model auto`、`/model vision` 不再可用。
70
+ - 用户 `config.toml` 若残留 `models.vision` 或 `[models.pricing.vision]`,启动时会有"未知字段"提示(这是 B 工程配置校验在提醒),删掉即可。
71
+ - **升级残留的档位值会自动迁移**(2026-09-11 三轮复核补充):旧版本的 `/model pro`
72
+ 会把 `default_tier = "pro"` **写进 config.toml**(`slash_handler` 的持久化),
73
+ 若不处理,升级后会在校验阶段直接中止启动(exit 2)——用户被挡在门外、只能手改 TOML。
74
+ 现按既有 `mode: review → supervised` 的迁移先例处理:`pro`/`auto`/`vision` 三个**已知被移除**
75
+ 的档位名 → 自动改为 `flash` 并**发出可见告警**;真正的拼写错误(如 `flsh`)仍照常报错中止。
76
+ 实现在 `config.py:_migrate_removed_tier()`(用户级与项目级两条路径都覆盖)。
77
+ - `supports_model_multimodal` 仍保留 `"vision" in 模型名` 判定——用于**兼容外部显式传入的 vision 系模型名**(如用户自挂的 exp 模型),不属档位概念。
78
+
79
+ ## 备选方案(未采纳)
80
+
81
+ | 方案 | 未采纳原因 |
82
+ |---|---|
83
+ | 保留 vision 档位,仅改 flash 模型名 | 两档模型实际相同(都是多模态),用户需无谓切换 |
84
+ | flash 仍用 `deepseek-v4-flash` | 该模型不支持读图,会引入静默失败 |
85
+ | 用 `deepseek-v4-flash-vision-exp` 作 flash | exp 为实验版、名字带过期语义,非长期选择 |
86
+
87
+ ## 相关
88
+
89
+ - ADR-2026-09-11-deepseek档位简化(本轮,pro/auto 删除)
90
+ - ADR-2026-09-10-能力可用性上报(vision 能力探测同源)
@@ -0,0 +1,91 @@
1
+ # 决策:终端状态兜底恢复 + 退出看门狗
2
+
3
+ > 日期:2026-09-11 · 状态:生效
4
+
5
+ ## 背景
6
+
7
+ **现象**:nf TUI 非正常结束后,命令行被持续刷屏——移动鼠标就喷出 `[555;x;yM`
8
+ 之类的原始转义序列,终端看起来"坏掉了"。
9
+
10
+ **已查证的事实**(源码级 + 本机实测):
11
+
12
+ 1. 那串字符是**鼠标追踪的 SGR 上报**(`ESC[<b;x;yM`)。终端一旦停在"鼠标追踪开启"
13
+ 状态,鼠标一动就会把上报序列当普通字符打进命令行。
14
+ 2. Textual 只在**正常退出 / 被捕获的异常**路径恢复终端
15
+ (`textual/app.py:3495-3514` 的 `finally: driver.stop_application_mode()`)。
16
+ 3. **硬杀无人收尾**:Windows `CTRL_BREAK_EVENT` → Python `SIGBREAK` 默认动作是直接
17
+ 终止进程;`taskkill /F`、直接关闭控制台窗口同理。Textual 在 Windows 下
18
+ **不安装任何信号处理器**(只给 Linux 驱动装了 SIGTSTP/SIGCONT/SIGWINCH)。
19
+ 4. 本机 `WT_SESSION` 存在(Windows Terminal)→ `_mouse_enabled()` 为 True
20
+ → 鼠标追踪**确实处于开启状态**,泄漏条件成立
21
+ (`docs/evidence/terminal-leak-conditions.log`)。
22
+
23
+ **直接触发**:一次复现脚本用 `CTRL_BREAK_EVENT` 结束子进程,却没有建独立进程组 ——
24
+ Windows 控制台事件是**按进程组**投递的,于是这次 CTRL_BREAK 同时打中了宿主 TUI 自己,
25
+ TUI 被硬杀,终端留在鼠标追踪模式。
26
+
27
+ **被实测否掉的假设**(不是推测,每条都有数据):
28
+
29
+ | 假设 | 实测结论 | 证据 |
30
+ |---|---|---|
31
+ | 余额轮询卡在网络 → 退出卡住 | **否**:挂起定时器(可取消 / 不可取消)与基线**同量级**(0.6~1.5s,随负载浮动;关键是相对结论) | `docs/evidence/exit-cost.log` |
32
+ | `Timer._stop_all` 会等待定时器回调 | 会 `await timer._task`,但 `cancel()` 立即打断 await(线程在后台继续) | `scripts/exit_cost_probe.py` |
33
+ | `Ctrl+Q`(App 级 priority 绑定)绕过清理 | **否**:`on_unmount` 兜底已覆盖该路径 | 两条退出路径的清理钩子均被调用 |
34
+ | 正常退出会漏清理 | **否**:作业/会话/隧道清理都执行了 | 同上 |
35
+
36
+ 结论:**不存在"退出卡住"缺陷**;真实缺口是"非 Python 异常路径缺少终端兜底恢复"。
37
+
38
+ ## 决策
39
+
40
+ 新增 `src/nexforge/terminal_guard.py`,提供三层保护:
41
+
42
+ 1. `restore_terminal()`:幂等写入"关鼠标(4 种模式)/ 关括号粘贴 / 关 kitty 键盘协议 /
43
+ 退备用屏 / 显示光标 / 复位 SGR",与 Textual 的 `stop_application_mode()` 对齐;
44
+ 非 TTY 默认不写,`force=True` 无条件写,且**绝不抛异常**。
45
+ 2. `install_signal_guard()`:Windows 下接管 `SIGBREAK`,恢复终端后再退出
46
+ (这正是本次事故缺的那一环)。
47
+ 3. **退出看门狗**:`run_tui` 运行期开启闸门;出现退出请求(`App.exit()`)即开始计时,
48
+ 超时(默认 8s,`NEXFORGE_EXIT_TIMEOUT` 可调)则恢复终端并 `os._exit(0)` ——
49
+ 把"卡退"从"用户只能强杀、终端报废"降级为"最多等 8 秒、终端自动恢复"。
50
+ 4. **能力缺失不静默**:`SIGBREAK` 接管失败时向 stderr 显式告警(含逃生舱指引),
51
+ 不允许"用户以为有保护、实际没有" —— 直接适用
52
+ `ADR-2026-09-10-fails-loud分级` 第 4 条。
53
+
54
+ `run_tui` 另加:崩溃路径 `force` 恢复后**继续抛出原异常**(不吞错);返回路径兜底恢复一次。
55
+
56
+ ## 理由
57
+
58
+ - 只靠 Textual 的 `finally` 覆盖不到硬杀,而用户遇到的正是硬杀后的坏终端。
59
+ - 三层里任一层单独都不够:看门狗管"退不掉",信号兜底管"被打断",
60
+ 返回路径兜底管"异常/正常都补一刀"。
61
+ - 全部标准库实现,非 Windows 平台自动降级为空操作。
62
+
63
+ ## 代价与边界
64
+
65
+ - **闸门是必须的**:无头测试 / 嵌入式调用 / 探针脚本都不走 `run_tui`,若 `App.exit()`
66
+ 无条件武装看门狗,就没人取消它 → 8 秒后 `os._exit()` 会把该进程直接带走。
67
+ 这个坑是**真踩到的**:`scripts/exit_cost_probe.py` 曾在打印结论前被截断。
68
+ 现在闸门关闭时 `arm_exit_watchdog()` 完全不动进程(有回归测试锁定)。
69
+ - **不可恢复场景**:`taskkill /F`(TerminateProcess)无法在进程内做任何事,
70
+ 只能靠终端自身;逃生舱为 `NEXFORGE_MOUSE=0`(跨平台强制关闭鼠标追踪)。
71
+ - 看门狗兜底 8 秒是宽限值;正常退出实测 0.6~1.5s(随机器负载浮动),留着充足余量。
72
+ - 兜底序列与 Textual 自身的恢复序列一致;在**未开启 VT 处理**的老式 conhost 上
73
+ 它们可能显示为可见字符(与 Textual 行为同源)。不过那种终端本来就不会启用鼠标
74
+ 追踪(`_mouse_enabled()` 已按此判定),所以实际影响可忽略。
75
+
76
+ ## 纪律(写给后续)
77
+
78
+ > **在 Windows 上向其他进程发控制台事件(CTRL_C / CTRL_BREAK)之前,必须
79
+ > `CREATE_NEW_PROCESS_GROUP`;若要独立控制台再加 `CREATE_NEW_CONSOLE`。
80
+ > 否则事件按进程组投递,会打中调用者自己。**
81
+
82
+ `scripts/exit_smoke.py` 里对这两个标志做了断言式自检,作为可执行的纪律。
83
+
84
+ ## 相关
85
+
86
+ - `src/nexforge/terminal_guard.py`、`src/nexforge/app.py`(`run_tui` / `NexForgeApp.exit`)
87
+ - ADR-2026-09-10-fails-loud分级(本模块的静默/可见边界按其裁定)
88
+ - `tests/test_terminal_guard.py`(19 项:恢复序列 / 看门狗 / 闸门 / 信号 / 可见降级 / run_tui 集成)
89
+ - `scripts/exit_cost_probe.py`(退出耗时对照探针)、`scripts/exit_smoke.py`(真实进程退出冒烟)
90
+ - 证据:`docs/evidence/exit-cost.log`、`docs/evidence/terminal-leak-conditions.log`
91
+ - 既有相关修复:`ad1fac6`(conhost 禁用鼠标追踪)、`25304a4`(VS Code 终端保留鼠标)
@@ -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,62 @@
1
+ # 决策:重试只归一层(包装器负责,SDK 归零)
2
+
3
+ > 日期:2026-09-11 · 状态:生效
4
+
5
+ ## 背景
6
+
7
+ `provider/_retry.py` 的 `retry_on_rate_limit()` 包住 `chat.completions.create`,
8
+ 同时 openai SDK 客户端构造时也配了 `max_retries`(来自 `config.provider.max_retries`,
9
+ 默认 2)。两层各自重试,于是**放大**:
10
+
11
+ 用 `httpx.MockTransport` 实测(持续 429,退避打桩为瞬时):
12
+
13
+ | 层 | 次数 |
14
+ |---|---|
15
+ | 包装器 `retry_on_rate_limit`(调用点未传参 → 默认 3) | 4 次调用 |
16
+ | 每次调用内 SDK 自己重试(`max_retries=2`) | 3 次尝试 |
17
+ | **合计 HTTP 请求** | **12** |
18
+
19
+ 副作用:
20
+ 1. **配额与限流**:一次用户提问在 429 下打出 12 次请求 —— 既浪费,也可能加重限流;
21
+ 2. **配置失真**:`provider.max_retries` 只影响 SDK 层,对包装器**无效**,
22
+ 用户调它看不到预期效果;
23
+ 3. **最坏等待**:若每次都耗到 `timeout`(120s),上限是 12×120s(远超合理范围)。
24
+
25
+ ## 决策
26
+
27
+ **重试只保留一层,且以我们的包装器为准**:
28
+
29
+ 1. 包装器内用 `self._client.with_options(max_retries=0)` 调 SDK,
30
+ **关掉 SDK 的内层重试**;
31
+ 2. 包装器显式接收 `pdef.max_retries or config.provider.max_retries` ——
32
+ 配置项成为唯一权威;
33
+ 3. 三个走包装器的 provider(deepseek / openai / qianwen)一致处理。
34
+ (anthropic 不走该包装器,仍由自身 SDK 重试,不在本次范围。)
35
+
36
+ 修后实测:**3 次请求**(= 1 + `max_retries`),配置改动可观察。
37
+
38
+ ## 理由
39
+
40
+ - **单一职责**:重试是策略,属于我们可观测、可配置、可测试的那一层;
41
+ 两层叠加等价于"把重试次数相乘",而没人会期望这个乘法。
42
+ - **可测**:MockTransport 计数让"到底打了几次 API"变成可断言的事实,
43
+ 而不是靠读代码推断(本决策的证据即由此而来)。
44
+ - **否决"保留两层、只把配置传下去"**:那只是让乘法变得可配置,放大依旧。
45
+ - **否决"关掉包装器、只信 SDK"**:包装器提供了显式的 429/5xx 集合与
46
+ 统一的错误包装(`ProviderError` 便于上层显示),且我们能在其中加观测。
47
+
48
+ ## 代价与边界
49
+
50
+ - SDK 层原生的 `Retry-After` 处理随之失效 —— 退避改由包装器的
51
+ 指数退避 + 抖动(上限 15s)承担。若将来需要尊重 `Retry-After`,
52
+ 应加在包装器里(并在测试中覆盖)。
53
+ - 非流式调用(`models.list` 等)不走包装器,仍用 SDK 重试 —— 这属于
54
+ "另一条路径",本次未动;若要统一,需把这些调用也纳入包装器。
55
+ - 退避等待用 `await asyncio.sleep`(**可取消**),因此"停止/退出"不会被
56
+ 退避拖住 —— 与 `terminal_guard` 的退出兜底同源关切。
57
+
58
+ ## 验证
59
+
60
+ - `tests/test_retry_policy.py`(12 项):可重试/不可重试状态码、
61
+ `1 + max_retries` 次数、配置权威性、**两层不放大**(实测计数)、退避可取消。
62
+ - 复盘脚本证据:12 次 → 3 次。
@@ -61,3 +61,8 @@
61
61
  | 2026-09-10 | [能力可用性上报](2026-09-10-能力可用性上报.md) | 生效 |
62
62
  | 2026-09-10 | [审计以调用链为准](2026-09-10-审计以调用链为准.md) | 生效 |
63
63
  | 2026-09-10 | [依赖一致性](2026-09-10-依赖一致性.md) | 生效 |
64
+ | 2026-09-11 | [DeepSeek 档位简化(删除 pro/auto)](2026-09-11-deepseek档位简化.md) | 已取代 → 见下行 |
65
+ | 2026-09-11 | [档位简化为单档 flash](2026-09-11-档位简化为单档flash.md) | 生效 · 破坏性 |
66
+ | 2026-09-11 | [终端状态兜底恢复 + 退出看门狗](2026-09-11-终端状态兜底恢复.md) | 生效 |
67
+ | 2026-09-11 | [重试只归一层](2026-09-11-重试归一层.md) | 生效 |
68
+ | 2026-09-11 | [配置残留值治理(自动收敛 + 必须可见)](2026-09-11-配置残留值治理.md) | 生效 |
@@ -0,0 +1,9 @@
1
+ === TUI exit cost probe (headless) ===
2
+ [A baseline] quit->shutdown = 1.32s
3
+ [B cancellable hang] quit->shutdown = 1.49s
4
+ [C blocking call] quit->shutdown = 1.06s
5
+
6
+ A baseline : 1.32s
7
+ B cancellable hang: 1.49s (delay +0.17s)
8
+ C blocking call : 1.06s (delay -0.26s)
9
+ >>> 无拖慢: 退出耗时与是否挂起无关
@@ -0,0 +1,7 @@
1
+ ........................................................................ [ 16%]
2
+ ................sss..................................................... [ 33%]
3
+ ........................................................................ [ 50%]
4
+ ........................................................................ [ 66%]
5
+ ........................................................................ [ 83%]
6
+ ...................................................................... [100%]
7
+ 427 passed, 3 skipped in 107.39s (0:01:47)
@@ -0,0 +1,11 @@
1
+ === 发布产物可用性验证 ===
2
+ === 11) 从 PyPI 真实安装 nexforge-cli==0.2.18 ===
3
+ pip 退出码: 0
4
+ 安装顶层条目: ['bin', 'nexforge', 'nexforge_cli-0.2.18.dist-info']
5
+ nexforge.__version__ = 0.2.18
6
+ === 12) 本版新增模块可用性 ===
7
+ capability OK
8
+ engines: ['bing', 'duckduckgo', 'google', 'searxng']
9
+ config-issue API OK
10
+ (临时安装目录已清理)
11
+ exit=0
@@ -0,0 +1,22 @@
1
+ === 发布后全量检查 ===
2
+ === 7) 静默捕获扫描(AST) ===
3
+ 裸 except: pass = 94 处 (体检时为 101 处)
4
+ 注: 经体检分类, 其中绝大多数为合理防御(清理/关闭/探测/UI 元素查找)
5
+
6
+ === 8) 文档索引完整性 ===
7
+ ADR 文件数: 7
8
+ 索引失效链接: 无
9
+ 未登记 ADR: 无
10
+
11
+ === 9) 其他文档 ===
12
+ OK docs/session-invariant.md
13
+ OK manual/11-开发校验.md
14
+ OK plugin_speech_summary/README.md
15
+
16
+ === 10) 发布产物抽检(whl) ===
17
+ 文件数: 128
18
+ OK nexforge/core/capability.py
19
+ OK nexforge/tools/search.py
20
+ OK nexforge/store/session_store.py
21
+ 插件示例是否误入包: False (应为 False)
22
+ exit=0
@@ -0,0 +1,54 @@
1
+ ============================================================
2
+ 真实配置 / 激活 deepseek [激活 provider = deepseek]
3
+ ============================================================
4
+ | deepseek |
5
+ | ● deepseek-flash | <-- 绿点
6
+ | kimi ⚪ |
7
+ | 🔍 验证 |
8
+ | kimi-k3 |
9
+ | 🔄 刷新模型 |
10
+ | local ⚪ |
11
+ | 🔍 验证 |
12
+ | D:\LocalModels\models\qwen2.5-14b-instruct-q4_k_m.gguf |
13
+ | 🔄 刷新模型 |
14
+ | 验证全部 |
15
+ 合计绿点 = 1
16
+
17
+ ============================================================
18
+ 真实配置 / 激活 kimi [激活 provider = kimi]
19
+ ============================================================
20
+ | deepseek |
21
+ | deepseek-flash |
22
+ | kimi ⚪ |
23
+ | 🔍 验证 |
24
+ | ● kimi-k3 | <-- 绿点
25
+ | 🔄 刷新模型 |
26
+ | local ⚪ |
27
+ | 🔍 验证 |
28
+ | D:\LocalModels\models\qwen2.5-14b-instruct-q4_k_m.gguf |
29
+ | 🔄 刷新模型 |
30
+ | 验证全部 |
31
+ 合计绿点 = 1
32
+
33
+ ============================================================
34
+ 合成配置 / 激活 anthropic 类 provider [激活 provider = claude]
35
+ ============================================================
36
+ | deepseek |
37
+ | deepseek-flash |
38
+ | claude ⚪ |
39
+ | 🔍 验证 |
40
+ | ● claude-sonnet-4 | <-- 绿点
41
+ | 验证全部 |
42
+ 合计绿点 = 1
43
+
44
+ ============================================================
45
+ 合成配置 / deepseek 激活, anthropic 未激活 [激活 provider = deepseek]
46
+ ============================================================
47
+ | deepseek |
48
+ | ● deepseek-flash | <-- 绿点
49
+ | claude ⚪ |
50
+ | 🔍 验证 |
51
+ | claude-sonnet-4 |
52
+ | 验证全部 |
53
+ 合计绿点 = 1
54
+
@@ -0,0 +1,52 @@
1
+ === DeepSeek 模型名实测 ===
2
+ === DeepSeek 模型名可用性实测 ===
3
+ [不可用] deepseek-v4.1-flash HTTP 400: The supported API model names are deepseek-flash, deepseek-v4-pro, but you passed deepseek-v4.1-flas
4
+ [可用] deepseek-v4.1-flash-expires-on-0910 -> 回复: ''
5
+ exit=0
6
+ === DeepSeek 模型名全面探测 ===
7
+ === 1) GET /models (官方可用模型列表, 最权威) ===
8
+ HTTP 200
9
+ - deepseek-flash
10
+ - deepseek-v4-pro
11
+
12
+ === 2) 逐个 chat 实测(max_tokens=64 避免空回复) ===
13
+ [200] deepseek-flash content='好的' finish=stop
14
+ [200] deepseek-v4-pro content='好的' finish=stop
15
+ [400] deepseek-v4.1-flash The supported API model names are deepseek-flash, deepseek-v4-pro, but you passed deepseek
16
+ [200] deepseek-v4.1-flash-expires-on-0910 content='好的' finish=stop
17
+ [200] deepseek-v4-flash content='好的' finish=stop
18
+ exit=0
19
+
20
+ === deepseek-flash 能力验证 ===
21
+ === 1) 文本能力 ===
22
+ HTTP 200 content=''
23
+
24
+ === 2) 读图能力(贴 base64 图片, 问它看到什么) ===
25
+ HTTP 200
26
+ 回答: '这张图是纯色背景,'
27
+ => 支持读图: True
28
+ exit=0
29
+
30
+ === 改名后端到端验证 ===
31
+ Traceback (most recent call last):
32
+ File "D:\DevelopProject\NexForge\NexForgeCLI\.nexforge\_v41_e2e.py", line 11, in <module>
33
+ from nexforge.core.provider import ChunkKind # noqa: E402
34
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
35
+ ImportError: cannot import name 'ChunkKind' from 'nexforge.core.provider' (D:\DevelopProject\NexForge\NexForgeCLI\src\nexforge\core\provider.py)
36
+ exit=1
37
+
38
+ === 改名后端到端验证 ===
39
+ === 1) 配置加载 ===
40
+ provider name = deepseek-v41
41
+ models = ['deepseek-flash']
42
+ default_model = deepseek-flash
43
+ multimodal = True
44
+
45
+ === 2) provider 实例化 ===
46
+ 类型 = DeepSeekProvider
47
+ 支持读图 = True
48
+
49
+ === 3) 真实调用 ===
50
+ 回复 = '就绪'
51
+ => 端到端可用: True
52
+ exit=0