nexforge-cli 0.5.0__tar.gz → 0.7.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 (340) hide show
  1. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/PKG-INFO +9 -8
  2. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/README.md +9 -8
  3. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/config.example.toml +18 -0
  4. nexforge_cli-0.7.0/docs/decisions/2026-09-12-/347/255/211/345/276/205/345/267/245/345/205/267/344/270/216/350/266/205/346/227/266/345/217/257/350/261/201/345/205/215.md +127 -0
  5. nexforge_cli-0.7.0/docs/decisions/2026-09-12-/350/277/233/347/250/213/346/240/221/347/273/210/346/255/242.md +65 -0
  6. nexforge_cli-0.7.0/docs/decisions/2026-09-13-MCP/346/216/245/345/205/245.md +74 -0
  7. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/decisions/README.md +3 -0
  8. nexforge_cli-0.7.0/docs/evidence/release-0.5.0.log +76 -0
  9. nexforge_cli-0.7.0/docs/evidence/wait-tool.log +144 -0
  10. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/submission/README-/346/212/225/347/250/277/350/257/264/346/230/216.md +1 -1
  11. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/submission/nexforge.zh-CN.md +1 -1
  12. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/manual/01-/345/256/211/350/243/205/344/270/216/351/205/215/347/275/256.md +1 -1
  13. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/manual/02-/347/225/214/351/235/242/344/270/216/345/277/253/346/215/267/351/224/256.md +1 -0
  14. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/manual/04-/345/267/245/345/205/267/345/217/202/350/200/203.md +21 -2
  15. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/manual/08-/350/207/252/345/256/232/344/271/211/351/205/215/347/275/256.md +3 -1
  16. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/manual/09-/345/270/270/350/247/201/351/227/256/351/242/230.md +26 -0
  17. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/manual/10-/346/217/222/344/273/266/345/274/200/345/217/221.md +10 -1
  18. nexforge_cli-0.7.0/manual/12-MCP/346/216/245/345/205/245.md +107 -0
  19. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/manual/README.md +4 -3
  20. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/manual//351/231/204/345/275/225-/345/257/271/346/257/224/345/210/206/346/236/220.md +1 -1
  21. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/plugin_speech_summary/speech_summary.py +328 -15
  22. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/pyproject.toml +11 -1
  23. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/scripts/gates/registry_consistency.py +2 -0
  24. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/__init__.py +1 -1
  25. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/config.py +117 -2
  26. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/core/agent.py +43 -5
  27. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/core/goal_manager.py +3 -0
  28. nexforge_cli-0.7.0/src/nexforge/core/jobs.py +200 -0
  29. nexforge_cli-0.7.0/src/nexforge/core/permissions.py +108 -0
  30. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/core/session.py +2 -0
  31. nexforge_cli-0.7.0/src/nexforge/core/waits.py +78 -0
  32. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/features/session_feature.py +5 -0
  33. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/i18n.py +14 -0
  34. nexforge_cli-0.7.0/src/nexforge/mcp/__init__.py +336 -0
  35. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/tools/background.py +127 -111
  36. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/tools/base.py +12 -0
  37. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/tools/registry.py +3 -0
  38. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/tools/screenshot_tools.py +9 -0
  39. nexforge_cli-0.7.0/src/nexforge/tools/wait_tools.py +379 -0
  40. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/ui/screens/chat.py +81 -1
  41. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/ui/screens/chat_actions.py +23 -0
  42. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/ui/slash_handler.py +47 -0
  43. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/web/server.py +7 -0
  44. nexforge_cli-0.7.0/tests/fixtures/fake_mcp_server.py +59 -0
  45. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/tests/test_config.py +37 -0
  46. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/tests/test_cross_platform.py +2 -0
  47. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/tests/test_file_browser.py +4 -2
  48. nexforge_cli-0.7.0/tests/test_jobs_lifecycle.py +84 -0
  49. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/tests/test_manual_consistency.py +4 -2
  50. nexforge_cli-0.7.0/tests/test_mcp_integration.py +210 -0
  51. nexforge_cli-0.7.0/tests/test_mcp_lifecycle.py +114 -0
  52. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/tests/test_plugin_example_tts.py +176 -10
  53. nexforge_cli-0.7.0/tests/test_wait_tools.py +489 -0
  54. nexforge_cli-0.5.0/src/nexforge/core/jobs.py +0 -120
  55. nexforge_cli-0.5.0/src/nexforge/core/permissions.py +0 -73
  56. nexforge_cli-0.5.0/src/nexforge/mcp/__init__.py +0 -168
  57. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/.gitignore +0 -0
  58. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/INSTALL.md +0 -0
  59. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/STATS_API.md +0 -0
  60. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/assets/icons/agent.svg +0 -0
  61. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/assets/icons/cost.svg +0 -0
  62. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/assets/icons/edit.svg +0 -0
  63. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/assets/icons/err.svg +0 -0
  64. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/assets/icons/file.svg +0 -0
  65. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/assets/icons/model.svg +0 -0
  66. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/assets/icons/ok.svg +0 -0
  67. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/assets/icons/read.svg +0 -0
  68. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/assets/icons/running.svg +0 -0
  69. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/assets/icons/search.svg +0 -0
  70. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/assets/icons/shell.svg +0 -0
  71. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/assets/icons/thinking.svg +0 -0
  72. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/assets/icons/web.svg +0 -0
  73. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/assets/icons/write.svg +0 -0
  74. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/devlog/2026-07-07.md +0 -0
  75. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/doc//344/273/243/347/240/201/344/274/230/345/214/226/350/256/241/345/210/222.md" +0 -0
  76. {nexforge_cli-0.5.0 → nexforge_cli-0.7.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
  77. {nexforge_cli-0.5.0 → nexforge_cli-0.7.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
  78. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/decisions/2026-09-10-fails-loud/345/210/206/347/272/247.md" +0 -0
  79. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/decisions/2026-09-10-/344/274/232/350/257/235/344/270/215/345/217/230/351/207/217.md" +0 -0
  80. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/decisions/2026-09-10-/344/276/235/350/265/226/344/270/200/350/207/264/346/200/247.md" +0 -0
  81. {nexforge_cli-0.5.0 → nexforge_cli-0.7.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
  82. {nexforge_cli-0.5.0 → nexforge_cli-0.7.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
  83. {nexforge_cli-0.5.0 → nexforge_cli-0.7.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
  84. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/decisions/2026-09-10-/350/264/250/351/207/217gate/344/275/223/347/263/273.md" +0 -0
  85. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/decisions/2026-09-11-deepseek/346/241/243/344/275/215/347/256/200/345/214/226.md" +0 -0
  86. {nexforge_cli-0.5.0 → nexforge_cli-0.7.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
  87. {nexforge_cli-0.5.0 → nexforge_cli-0.7.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
  88. {nexforge_cli-0.5.0 → nexforge_cli-0.7.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" +0 -0
  89. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/decisions/2026-09-11-/351/207/215/350/257/225/345/275/222/344/270/200/345/261/202.md" +0 -0
  90. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/decisions/2026-09-12-/345/206/205/345/256/271/351/207/214/347/232/204/346/240/207/350/256/260/344/270/215/345/276/227/345/275/223/346/210/220/346/266/210/346/201/257/350/276/271/347/225/214.md" +0 -0
  91. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/decisions/2026-09-12-/347/233/256/346/240/207/346/250/241/345/274/217/345/244/261/350/264/245/345/217/257/350/247/201/344/270/216/345/216/206/345/217/262/344/277/235/345/205/250.md" +0 -0
  92. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/evidence/D10_acceptance.log +0 -0
  93. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/evidence/D10_full_test.log +0 -0
  94. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/evidence/D11_a4.log +0 -0
  95. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/evidence/D11_full_test.log +0 -0
  96. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/evidence/D12_audit.md +0 -0
  97. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/evidence/D12_gap.log +0 -0
  98. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/evidence/D13_break.log +0 -0
  99. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/evidence/D13_e2e.log +0 -0
  100. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/evidence/D13_full_test.log +0 -0
  101. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/evidence/D13_gaps.log +0 -0
  102. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/evidence/D14_audit.md +0 -0
  103. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/evidence/D14_full_test.log +0 -0
  104. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/evidence/D14_recheck.log +0 -0
  105. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/evidence/D15_audit.md +0 -0
  106. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/evidence/D4_fast.log +0 -0
  107. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/evidence/D4_full_test.log +0 -0
  108. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/evidence/D4_hook.log +0 -0
  109. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/evidence/D4_scene_c.log +0 -0
  110. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/evidence/D4_scene_d.log +0 -0
  111. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/evidence/D4_scene_e.log +0 -0
  112. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/evidence/D5_audit.md +0 -0
  113. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/evidence/D5_b2_plugin.log +0 -0
  114. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/evidence/D5_full_test.log +0 -0
  115. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/evidence/D6_b3_a.log +0 -0
  116. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/evidence/D6_b3_b.log +0 -0
  117. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/evidence/D6_b3_c.log +0 -0
  118. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/evidence/D6_b4_provider.log +0 -0
  119. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/evidence/D6_full_test.log +0 -0
  120. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/evidence/D7_dedup.log +0 -0
  121. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/evidence/D7_full_test.log +0 -0
  122. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/evidence/D8_audit.md +0 -0
  123. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/evidence/D8_invariant.log +0 -0
  124. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/evidence/D9_after.log +0 -0
  125. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/evidence/D9_before.log +0 -0
  126. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/evidence/D9_decision.md +0 -0
  127. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/evidence/D9_full_test.log +0 -0
  128. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/evidence/D9_rebuild.log +0 -0
  129. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/evidence/E_cap_fulltest.log +0 -0
  130. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/evidence/E_capabilities.log +0 -0
  131. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/evidence/E_engines.log +0 -0
  132. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/evidence/E_engines_fulltest.log +0 -0
  133. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/evidence/E_tts_availability.log +0 -0
  134. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/evidence/E_tts_fulltest.log +0 -0
  135. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/evidence/E_tts_installer.log +0 -0
  136. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/evidence/E_vision_channels.log +0 -0
  137. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/evidence/E_vision_fulltest.log +0 -0
  138. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/evidence/F_decisions_fulltest.log +0 -0
  139. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/evidence/deps_fulltest.log +0 -0
  140. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/evidence/deps_reverse.log +0 -0
  141. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/evidence/exit-cost.log +0 -0
  142. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/evidence/final_check_fulltest.log +0 -0
  143. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/evidence/final_check_pypi.log +0 -0
  144. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/evidence/final_check_report.log +0 -0
  145. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/evidence/fix_fulltest.log +0 -0
  146. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/evidence/health_fulltest.log +0 -0
  147. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/evidence/health_report.md +0 -0
  148. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/evidence/health_scan.log +0 -0
  149. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/evidence/model-sidebar-dot.log +0 -0
  150. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/evidence/model_rename_check.log +0 -0
  151. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/evidence/provider-family-check.log +0 -0
  152. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/evidence/release-0.3.0.log +0 -0
  153. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/evidence/release-0.4.0.log +0 -0
  154. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/evidence/release-0.4.1.log +0 -0
  155. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/evidence/release_0218_fulltest.log +0 -0
  156. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/evidence/release_0218_pypi.log +0 -0
  157. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/evidence/session-marker-boundary.log +0 -0
  158. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/evidence/session-marker-boundary_fulltest.log +0 -0
  159. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/evidence/session-marker-boundary_live.log +0 -0
  160. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/evidence/session-marker-boundary_report.md +0 -0
  161. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/evidence/simplify_tier_fulltest.log +0 -0
  162. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/evidence/terminal-leak-conditions.log +0 -0
  163. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/session-invariant.md +0 -0
  164. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/docs/submission/nexforge.md +0 -0
  165. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/manual/03-/346/250/241/345/236/213/351/205/215/347/275/256.md" +0 -0
  166. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/manual/05-/346/225/260/346/215/256/344/270/216/346/220/234/347/264/242.md" +0 -0
  167. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/manual/06-/346/235/203/351/231/220/344/270/216/346/250/241/345/274/217.md" +0 -0
  168. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/manual/07-/344/274/232/350/257/235/344/270/216/350/256/260/345/277/206.md" +0 -0
  169. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/manual/11-/345/274/200/345/217/221/346/240/241/351/252/214.md" +0 -0
  170. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/plugin_gui_auto/README.md +0 -0
  171. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/plugin_gui_auto/guiauto/__init__.py +0 -0
  172. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/plugin_gui_auto/guiauto/action.py +0 -0
  173. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/plugin_gui_auto/guiauto/base.py +0 -0
  174. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/plugin_gui_auto/guiauto/tools.py +0 -0
  175. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/plugin_gui_auto/guiauto/vision.py +0 -0
  176. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/plugin_gui_auto/guiauto/window.py +0 -0
  177. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/plugin_gui_auto/install_plugin.py +0 -0
  178. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/plugin_gui_auto/requirements.txt +0 -0
  179. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/plugin_gui_auto/smoke_safe.py +0 -0
  180. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/plugin_gui_auto/verify_final.py +0 -0
  181. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/plugin_gui_auto/verify_host.py +0 -0
  182. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/plugin_gui_auto/verify_plugin.py +0 -0
  183. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/plugin_speech_summary/README.md +0 -0
  184. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/plugin_speech_summary/install_plugin.py +0 -0
  185. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/scripts/__init__.py +0 -0
  186. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/scripts/check.py +0 -0
  187. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/scripts/exit_cost_probe.py +0 -0
  188. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/scripts/exit_smoke.py +0 -0
  189. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/scripts/gates/__init__.py +0 -0
  190. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/scripts/gates/base.py +0 -0
  191. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/scripts/gates/eof_newline.py +0 -0
  192. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/scripts/gates/example_config.py +0 -0
  193. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/scripts/gates/hygiene.py +0 -0
  194. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/scripts/gates/manual_consistency.py +0 -0
  195. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/scripts/gates/version_sync.py +0 -0
  196. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/scripts/install_hooks.py +0 -0
  197. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/scripts/publish.sh +0 -0
  198. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/scripts/smoke_provider.py +0 -0
  199. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/scripts/smoke_tui.py +0 -0
  200. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/__main__.py +0 -0
  201. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/app.py +0 -0
  202. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/cli.py +0 -0
  203. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/core/__init__.py +0 -0
  204. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/core/capability.py +0 -0
  205. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/core/context.py +0 -0
  206. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/core/federation.py +0 -0
  207. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/core/knowledge_base.py +0 -0
  208. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/core/memory_engine.py +0 -0
  209. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/core/model_catalog.py +0 -0
  210. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/core/model_window.py +0 -0
  211. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/core/plugin.py +0 -0
  212. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/core/project_mapper.py +0 -0
  213. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/core/provider.py +0 -0
  214. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/core/summarizer.py +0 -0
  215. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/core/system_inject.py +0 -0
  216. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/core/tool_manifest.py +0 -0
  217. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/core/types.py +0 -0
  218. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/core/update_check.py +0 -0
  219. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/features/__init__.py +0 -0
  220. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/features/auto_skills.py +0 -0
  221. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/features/base.py +0 -0
  222. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/features/federation_feature.py +0 -0
  223. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/features/kb_feature.py +0 -0
  224. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/features/map_feature.py +0 -0
  225. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/features/memory_feature.py +0 -0
  226. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/features/mode_features.py +0 -0
  227. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/features/plan_feature.py +0 -0
  228. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/features/registry.py +0 -0
  229. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/features/slash_feature.py +0 -0
  230. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/features/stats_feature.py +0 -0
  231. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/features/tool_manifest_feature.py +0 -0
  232. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/icons.py +0 -0
  233. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/provider/__init__.py +0 -0
  234. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/provider/_retry.py +0 -0
  235. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/provider/anthropic.py +0 -0
  236. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/provider/balance.py +0 -0
  237. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/provider/deepseek.py +0 -0
  238. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/provider/openai.py +0 -0
  239. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/provider/qianwen.py +0 -0
  240. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/skills/loader.py +0 -0
  241. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/stats/__init__.py +0 -0
  242. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/stats/heartbeat.py +0 -0
  243. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/store/__init__.py +0 -0
  244. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/store/director_store.py +0 -0
  245. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/store/plan_store.py +0 -0
  246. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/store/session_store.py +0 -0
  247. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/terminal_guard.py +0 -0
  248. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/theme.tcss +0 -0
  249. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/tools/__init__.py +0 -0
  250. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/tools/_paths.py +0 -0
  251. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/tools/data_tools.py +0 -0
  252. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/tools/edit_file.py +0 -0
  253. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/tools/federation.py +0 -0
  254. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/tools/fs_ops.py +0 -0
  255. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/tools/git.py +0 -0
  256. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/tools/image_tools.py +0 -0
  257. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/tools/kb_tools.py +0 -0
  258. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/tools/list_dir.py +0 -0
  259. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/tools/multi_edit.py +0 -0
  260. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/tools/ops_tools.py +0 -0
  261. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/tools/read_file.py +0 -0
  262. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/tools/search.py +0 -0
  263. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/tools/search_content.py +0 -0
  264. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/tools/search_files.py +0 -0
  265. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/tools/shell.py +0 -0
  266. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/tools/web.py +0 -0
  267. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/tools/write_file.py +0 -0
  268. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/ui/__init__.py +0 -0
  269. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/ui/clipboard_image.py +0 -0
  270. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/ui/screen_api.py +0 -0
  271. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/ui/screens/__init__.py +0 -0
  272. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/ui/screens/splash.py +0 -0
  273. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/ui/widgets/__init__.py +0 -0
  274. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/ui/widgets/apikey_modal.py +0 -0
  275. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/ui/widgets/banner.py +0 -0
  276. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/ui/widgets/confirm_modal.py +0 -0
  277. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/ui/widgets/diff_modal.py +0 -0
  278. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/ui/widgets/file_browser.py +0 -0
  279. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/ui/widgets/file_menu.py +0 -0
  280. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/ui/widgets/image_sidebar.py +0 -0
  281. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/ui/widgets/messages.py +0 -0
  282. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/ui/widgets/mode_warning.py +0 -0
  283. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/ui/widgets/model_sidebar.py +0 -0
  284. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/ui/widgets/multiline_modal.py +0 -0
  285. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/ui/widgets/multiline_prompt.py +0 -0
  286. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/ui/widgets/plan_sidebar.py +0 -0
  287. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/ui/widgets/sidebar.py +0 -0
  288. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/ui/widgets/statusbar.py +0 -0
  289. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/utils/__init__.py +0 -0
  290. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/utils/logging.py +0 -0
  291. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/web/__init__.py +0 -0
  292. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/src/nexforge/web/static/index.html +0 -0
  293. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/tests/conftest.py +0 -0
  294. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/tests/test_app_mouse.py +0 -0
  295. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/tests/test_approval.py +0 -0
  296. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/tests/test_archive_integrity.py +0 -0
  297. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/tests/test_capability.py +0 -0
  298. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/tests/test_check_gates.py +0 -0
  299. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/tests/test_deepseek_provider.py +0 -0
  300. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/tests/test_deps.py +0 -0
  301. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/tests/test_director_loop.py +0 -0
  302. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/tests/test_director_store.py +0 -0
  303. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/tests/test_dual_panel.py +0 -0
  304. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/tests/test_exit_smoke.py +0 -0
  305. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/tests/test_federation.py +0 -0
  306. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/tests/test_goal_confirm.py +0 -0
  307. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/tests/test_goal_stability.py +0 -0
  308. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/tests/test_i18n.py +0 -0
  309. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/tests/test_icons.py +0 -0
  310. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/tests/test_lang_cmd.py +0 -0
  311. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/tests/test_m2_tools.py +0 -0
  312. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/tests/test_model_catalog.py +0 -0
  313. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/tests/test_model_sidebar_dot.py +0 -0
  314. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/tests/test_model_window.py +0 -0
  315. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/tests/test_p2_core.py +0 -0
  316. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/tests/test_plugin.py +0 -0
  317. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/tests/test_project_map_prune.py +0 -0
  318. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/tests/test_provider_errors.py +0 -0
  319. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/tests/test_quit_paths.py +0 -0
  320. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/tests/test_retry_policy.py +0 -0
  321. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/tests/test_sanitize.py +0 -0
  322. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/tests/test_screenshot_tools.py +0 -0
  323. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/tests/test_session_invariant.py +0 -0
  324. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/tests/test_session_repair.py +0 -0
  325. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/tests/test_session_save_safety.py +0 -0
  326. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/tests/test_session_store.py +0 -0
  327. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/tests/test_sidebar.py +0 -0
  328. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/tests/test_sidebar_actions.py +0 -0
  329. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/tests/test_sidebar_align.py +0 -0
  330. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/tests/test_splash_modal.py +0 -0
  331. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/tests/test_statusbar.py +0 -0
  332. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/tests/test_system_inject.py +0 -0
  333. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/tests/test_terminal_guard.py +0 -0
  334. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/tests/test_tool_iters.py +0 -0
  335. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/tests/test_tools.py +0 -0
  336. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/tests/test_tui.py +0 -0
  337. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/tests/test_update_check.py +0 -0
  338. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/tests/test_vision_capability.py +0 -0
  339. {nexforge_cli-0.5.0 → nexforge_cli-0.7.0}/tests/test_vision_model.py +0 -0
  340. {nexforge_cli-0.5.0 → nexforge_cli-0.7.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.5.0
3
+ Version: 0.7.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,15 @@ Description-Content-Type: text/markdown
51
51
 
52
52
  NexForgeCLI 是一个运行在终端里的智能体助手,既支持 DeepSeek/千问等多模型,也是**插件宿主**(plugins 目录即插即用)。既能作为全屏 TUI 交互式对话/编码,也能作为 one-shot 命令行工具嵌进脚本和管道。
53
53
 
54
- > 当前版本:**v0.5.0** · PyPI: `pip install nexforge-cli`
54
+ > 当前版本:**v0.7.0** · PyPI: `pip install nexforge-cli`
55
55
 
56
- **v0.4.1 要点(重要修复)**:会话重建时,**内容里出现的 `<!-- nf-msg -->` 不再被当成消息边界**。
57
- 此前若工具结果 dump 过会话文件(或粘贴过会话内容),加载时那些标记会被解码成额外消息、
58
- 插进工具块中间,导致**每轮请求 400、项目再也打不开**。
59
- 修法:读侧标记识别改为**行首锚定**,写侧对内容里的标记前缀做转义;同时把工具序列修复
60
- 从"只修尾部"升级为"修全序列"(中段违规也能纠正)。
61
- 另含 0.4.0 推出的:本机 TTS 优先/回退、模型随 nf 常驻、`/voice full|brief`、配置残留值自动收敛。
56
+ **v0.7.0 要点(MCP 外部工具接入)**:可以用 `[[mcp.servers]]` 挂载 MCP server,
57
+ 把第三方工具(文件系统/GitHub/数据库等)接进 AI 工具箱。外部工具统一注册为
58
+ `mcp__<server>__<tool>` —— **无法覆盖内置工具**(外部进程不能改写能力面), 也便于按来源授权;
59
+ 默认需审批(`require_approval`), 并提供 `/mcp` 查看连接状态与失败原因。工具数有闸门
60
+ (`[mcp] max_tools` + server 级 `tools` 白名单), 防止上下文被几十个外部工具挤爆。
61
+ (上一版 0.6.0 要点: 新增 `wait` / `wait_until` 精确等待与条件轮询, 并按参数豁免 60 秒
62
+ 单工具超时上限; `stop_job` 与退出清理改为终止整个进程树; 内置工具 39 → 41。)
62
63
 
63
64
  ---
64
65
 
@@ -8,14 +8,15 @@
8
8
 
9
9
  NexForgeCLI 是一个运行在终端里的智能体助手,既支持 DeepSeek/千问等多模型,也是**插件宿主**(plugins 目录即插即用)。既能作为全屏 TUI 交互式对话/编码,也能作为 one-shot 命令行工具嵌进脚本和管道。
10
10
 
11
- > 当前版本:**v0.5.0** · PyPI: `pip install nexforge-cli`
12
-
13
- **v0.4.1 要点(重要修复)**:会话重建时,**内容里出现的 `<!-- nf-msg -->` 不再被当成消息边界**。
14
- 此前若工具结果 dump 过会话文件(或粘贴过会话内容),加载时那些标记会被解码成额外消息、
15
- 插进工具块中间,导致**每轮请求 400、项目再也打不开**。
16
- 修法:读侧标记识别改为**行首锚定**,写侧对内容里的标记前缀做转义;同时把工具序列修复
17
- 从"只修尾部"升级为"修全序列"(中段违规也能纠正)。
18
- 另含 0.4.0 推出的:本机 TTS 优先/回退、模型随 nf 常驻、`/voice full|brief`、配置残留值自动收敛。
11
+ > 当前版本:**v0.7.0** · PyPI: `pip install nexforge-cli`
12
+
13
+ **v0.7.0 要点(MCP 外部工具接入)**:可以用 `[[mcp.servers]]` 挂载 MCP server,
14
+ 把第三方工具(文件系统/GitHub/数据库等)接进 AI 工具箱。外部工具统一注册为
15
+ `mcp__<server>__<tool>` —— **无法覆盖内置工具**(外部进程不能改写能力面), 也便于按来源授权;
16
+ 默认需审批(`require_approval`), 并提供 `/mcp` 查看连接状态与失败原因。工具数有闸门
17
+ (`[mcp] max_tools` + server 级 `tools` 白名单), 防止上下文被几十个外部工具挤爆。
18
+ (上一版 0.6.0 要点: 新增 `wait` / `wait_until` 精确等待与条件轮询, 并按参数豁免 60 秒
19
+ 单工具超时上限; `stop_job` 与退出清理改为终止整个进程树; 内置工具 39 → 41。)
19
20
 
20
21
  ---
21
22
 
@@ -45,5 +45,23 @@ cosyvoice_url = "http://127.0.0.1:9880" # 本机 CosyVoice 常驻服务(其 l
45
45
  cosyvoice_voice = "default" # 本机服务音色 id
46
46
  cosyvoice_timeout_s = 90 # 冷启动约 20s, 留足余量
47
47
  cosyvoice_resident = true # nf 在跑就让模型常驻显存; nf 全退出后由服务立即卸载
48
+ stream = true # 流式播报(首块约 2s 就开口); false = 整句合成完再播
49
+ instruct = "" # 风格指令(如 "用自然亲切的语气说"); 空 = 不用指令
48
50
  max_chars = 0 # 播报文本上限(0=不限; 配合下面的 mode)
49
51
  mode = "full" # 播报范围: full=全量播报最终报告(默认) | brief=只播第一段(/voice 切换)
52
+
53
+ # MCP 外部工具服务(默认不启用)。下面是配置模板, 取消注释并按需修改;
54
+ # 完整说明(命名/权限/排错/成本)见 manual/12-MCP接入.md。
55
+ #
56
+ # [[mcp.servers]]
57
+ # name = "filesystem" # 唯一标识, 同时是工具名前缀(mcp__<name>__<tool>)
58
+ # command = "npx" # stdio: 可执行文件(当前仅支持 stdio)
59
+ # args = ["-y", "@modelcontextprotocol/server-filesystem", "D:/work"]
60
+ # env = { SOME_TOKEN = "..." } # 可选: 追加环境变量
61
+ # tools = ["read_file", "list_directory"] # 可选: 工具白名单(空 = 该 server 的全部工具)
62
+ # require_approval = true # 默认 true: 每次调用需审批(外部进程与 run_background 同级)
63
+ # enabled = true
64
+ #
65
+ # [mcp]
66
+ # max_tools = 40 # 注入工具 schema 的 MCP 工具总数上限(超出截断并提示)
67
+ # connect_timeout = 15.0 # 单个 server 连接/握手超时(秒)
@@ -0,0 +1,127 @@
1
+ # 决策:等待用专门的工具 + 可声明超时,不再靠 Shell 拼命令
2
+
3
+ > 日期:2026-09-12 · 状态:生效
4
+
5
+ ## 背景
6
+
7
+ 实际使用中"等一会儿再执行"是高频诉求(等 dev server 起来、等构建产物落盘、
8
+ 等接口限流恢复、等 CI 回包)。但加这两个工具之前,**没有任何一种等待手段是可靠的**,
9
+ 模型只能即兴发挥,于是方法五花八门、结果时好时坏。实测(详见
10
+ `docs/evidence/wait-tool.log`):
11
+
12
+ | 它常试的办法 | 实测结果 |
13
+ |---|---|
14
+ | `run_shell("sleep 30")` | cmd 无 `sleep` 命令,直接报错 |
15
+ | `run_shell("timeout /t 30")` | 无交互控制台 → **0.03s 就返回**(以为等了 30s,实际 0s,且 rc=1) |
16
+ | `run_shell("ping -n 30 127.0.0.1 >nul")` | 能等,但仅 Windows、1 秒粒度、污染输出 |
17
+ | `run_shell("powershell Start-Sleep -Seconds 30")` | 能用,但更慢且带 profile 告警噪音 |
18
+ | 反复 `job_output` / `health_check` 轮询 | 烧工具轮数(上限 300)与 token,且只能等"命令退出" |
19
+
20
+ 三处结构性障碍让"等待"注定失败:
21
+
22
+ 1. **全局 `tools.tool_timeout=60` 硬顶**(`core/agent.py` 的 `_exec_tool` 用
23
+ `asyncio.wait_for` 包住整个工具)→ 任何 >60s 的等待必然判"工具执行超时"。
24
+ 而等待 5 分钟是正当需求(编译、镜像构建、限流恢复)。
25
+ 2. **超时后线程仍在跑**:`asyncio.to_thread` 里的线程不可取消(实测:asyncio 1.00s
26
+ 放弃等待,工作线程继续跑完)。于是"停止/Esc/退出"对等待无效,用户以为停了,
27
+ 线程还在睡。
28
+ 3. **语义错配**:真正的需求大多是"等到条件成立"(端口可连 / 文件出现 / HTTP 200 /
29
+ 日志出现关键字),而当时唯一沾边的 `wait_for_job` 只能等"作业退出" —— 常驻的
30
+ dev server 永不退出,必然超时失败。
31
+
32
+ ## 决策
33
+
34
+ 1. **新增 `wait` 与 `wait_until` 两个工具**(`tools/wait_tools.py`),纯 Python 实现,
35
+ **不起子进程** → 跨平台一致、无编码坑:
36
+ - `wait(seconds, reason)`:精确计时(支持小数秒);
37
+ - `wait_until(until, target, expect, timeout, interval)`:条件轮询,
38
+ `until ∈ {port, file, http, log}`,超时返回**最后一次探测证据**。
39
+ 2. **等待可中断**:新建 `core/waits.py`(进程级 `threading.Event` + `sleep()`),
40
+ 每 0.05s 检查一次;UI 的停止 / Esc / 退出 / 切会话 / Web 面板停止 /
41
+ 总监目标模式停止、删除会话(`session_feature.py`),**七处**取消路径统一调用 `waits.interrupt()`。
42
+ 同时把 `jobs.wait()` 的 `time.sleep(0.1)` 轮询改为可中断睡眠,
43
+ **顺手修掉 `wait_for_job` 同款"停止无效"的暗坑**。
44
+ 3. **超时可声明,但有硬顶**:`BaseTool.timeout_for(args) -> float | None`(默认 `None`),
45
+ 上限 = `min(max(全局 tool_timeout, 声明值), MAX_TOOL_TIMEOUT=3600s)` —— 硬顶防的是
46
+ “第三方/插件工具声明天文数字 → 一次调用把会话钉死”。
47
+ 两个等待工具按 `等待秒数 + 15s 余量` 声明;`wait_for_job` 同步补上。
48
+ 4. **单次等待上限 600 秒 + 明确拒绝**:超限直接返回错误("单次等待上限 600 秒"),
49
+ **不做静默截断** —— 静默截断会让模型以为自己等了 1 小时。
50
+ 5. **新一轮复位**:`AgentLoop.run_turn` 开头 `waits.reset()`,否则一次取消会永久
51
+ 毒化后续所有等待(`wait` 永远立刻返回"已中断")。
52
+ 6. `wait` / `wait_until` 归**只读**(`permissions.py`),等待不需要审批;
53
+ 并加入总监只读白名单 —— 它改不了仓库、也起不了进程。
54
+ 7. **让模型用得上**:`DEFAULT_SYSTEM` 补一句需要等待/延时用 wait 或 wait_until、
55
+ 不要用 sleep/timeout/ping/Start-Sleep 拼命令 —— 只在工具 description 里写不够,
56
+ 系统提示才是行为纪律的落点(与“修改文件用 edit_file”同源)。
57
+
58
+ ## 理由
59
+
60
+ - **为什么不做成"只是 sleep 一下"**:计时精确从来不是瓶颈(`time.sleep` 本身就准),
61
+ 瓶颈是"等待被 60s 上限砍死"和"等待不可中断"。只加 sleep 工具不改机制,等于白做。
62
+ - **为什么用进程级 Event 而不是 asyncio 事件**:工具跑在 `to_thread` 的工作线程里,
63
+ 与事件循环不同线程;线程不可取消,只能主动查询一个线程安全的标记。
64
+ - **为什么不让 wait 工具自己接受 `timeout` 参数**(即读取全局上限):全局上限是
65
+ "防单点卡死"的产品设定,不该被单个工具绕过;用声明式钩子把"放宽多少"交回工具,
66
+ Agent 只做 `max` 合并,审计点唯一(`_exec_tool` 一处)。
67
+
68
+ **否决的备选**:
69
+
70
+ | 备选 | 否决理由 |
71
+ |---|---|
72
+ | 继续用 `run_shell` 拼 sleep/timeout/ping | 实测全不可靠(上表);且 `run_shell` 属 danger 类,每次等待都要用户点审批 |
73
+ | 调大全局 `tool_timeout` | 会一并削弱网络类工具的快速失败保护,风险外溢到所有工具 |
74
+ | 等待超 60s 就分段返回"请再调一次" | 轮数与 token 翻倍,且模型可能忘记继续(或把它当成失败) |
75
+ | 扩展 `wait_for_job` 支持任意条件 | 名字与语义不符,会模糊"作业"概念、伤已有用例 |
76
+ | 靠 provider 重试层解决 | "等 CI / 等端口"是 agent 层语义,不属于 provider 的失败重试范畴 |
77
+ | 插件 / MCP 外挂实现 | 等待是高频核心能力,不该外挂;且增加工具计数与管理成本 |
78
+
79
+ ## 代价与边界
80
+
81
+ - **`tool_timeout = 0` = 不限额**(2026-09-12 统一语义):此前传 0 会让
82
+ `asyncio.wait_for(..., 0)` 立刻判超时 —— 一个看起来像"不限额"的配置, 实际让所有工具
83
+ 一调就失败。现在 0 转成 `timeout=None`(无限等待),与 `turn_timeout = 0` 的约定一致;
84
+ 代价是关掉了工具级超时保护,属用户显式选择(默认仍是 60s)。
85
+ - **Token 成本**:工具 schema 从 39 → 41 个(约 +0.2k tokens/次请求)。在"工具 schema
86
+ 已 ~7.7k tokens"的现状下可接受,但**不再轻易加工具**这条纪律应当继续。
87
+ - **600s 上限是产品选择**:需要更久的等待应拆成多次调用,或改用 `run_background`
88
+ 起任务 + `wait_for_job`。上限值集中定义在 `wait_tools.MAX_WAIT_SECONDS`。
89
+ - **`wait_until` 会产生真实探测请求**(连端口 / 发 HTTP GET / 读文件):
90
+ 在受限网络下 `http` 探测会慢(每次 5s 超时),此时应改用 `log` 或 `file` 观察本地信号。
91
+ - **Esc 路径下模型看到的是会话修复层的回执**("已取消(中断/超时)。"),而不是 wait 的
92
+ 中断文案 —— 因为 Esc 会同时取消 worker,工具结果被丢弃(`_repair_tool_sequence` 补一条
93
+ 合法响应防 400)。`waits.interrupt()` 的价值是**让等待线程立刻退出**,不是改文案。
94
+ 真机结论见 `docs/evidence/wait-tool.log` 的 E 节。
95
+ - **探测自身的耗时计入 timeout 预算**(2026-09-12 真机检查后修):`port` / `http` 探测
96
+ 本身也要耗时(本机对未监听端口的 connect 是 **2 秒超时**而非立即拒绝),不计入预算
97
+ 会让 `timeout=3` 的请求实测用 **5 秒**(语义失真)。现把剩余预算交给探测函数
98
+ (`port` ≤2s、`http` ≤5s,且不超预算),并由 `test_wait_until_respects_timeout_budget`
99
+ 钉住"实际耗时 ≤ timeout + 容差"。
100
+ - **不引入新的导入负担**:`wait_tools` 顶层**不** import httpx(只有 `until=http`
101
+ 时才延迟导入,与 `image_tools` 同做法)。httpx 的导入链在 Windows 上会拉 click
102
+ (硬依赖 `ctypes.windll`)—— 让“等文件/等端口”这类纯本地条件付出无谓代价,也会
103
+ 让“无 windll 环境”的导入检查失败(已由 `test_cross_platform.py` 的模块清单钉住)。
104
+ - **中断是"协作式"的**:只对走 `core/waits.py` 的等待生效。`run_shell` 里正在跑的长
105
+ 命令仍然不可中断(它阻塞在 `subprocess.run`)—— 这是本次**未解决**的边界,
106
+ 遇到需要中断的长命令,应改用 `run_background` + 停止。
107
+ - 若将来把"回合内取消"升级为硬取消(如杀进程组),`waits` 应随之收敛为唯一入口。
108
+ - 顺手校正了一处**既有文档漂移**:手册/投稿文档写"内置 35 个工具",而
109
+ `default_registry()` 实际已 39 个(`tests/test_manual_consistency.py` 钉的是 39)。
110
+ 本次统一为真实的 41。
111
+
112
+ ## 关联
113
+
114
+ - 代码:`src/nexforge/tools/wait_tools.py`、`src/nexforge/core/waits.py`、
115
+ `src/nexforge/tools/base.py`(`timeout_for`)、`src/nexforge/core/agent.py`
116
+ (`_exec_tool` 上限合并、`run_turn` 复位)、`src/nexforge/core/jobs.py`(可中断等待)、`src/nexforge/core/session.py`(`DEFAULT_SYSTEM` 引导)、
117
+ `src/nexforge/core/permissions.py`、`src/nexforge/core/goal_manager.py`、
118
+ `src/nexforge/ui/screens/chat.py`、`chat_actions.py`、`ui/slash_handler.py`、`features/session_feature.py`、
119
+ `web/server.py`
120
+ - 测试:`tests/test_wait_tools.py`(21 项)
121
+ - 证据:`docs/evidence/wait-tool.log`
122
+ - 手册:`manual/04-工具参考.md`(含"等待与条件轮询"小节)、`manual/09-常见问题.md`
123
+ - 相关决策:[终端状态兜底恢复](2026-09-11-终端状态兜底恢复.md)(同样是"取消/退出 vs
124
+ 线程与等待"的关切)、[Fails Loud 分级](2026-09-10-fails-loud分级.md)
125
+ (超限明确拒绝而非静默截断)
126
+ - 后续:[杀进程必须杀整棵树](2026-09-12-进程树终止.md) —— 本次检查暴露的
127
+ "僵尸端口误报就绪"由它修复
@@ -0,0 +1,65 @@
1
+ # 决策:杀进程必须杀整棵树(`shell=True` 的孙进程会变孤儿)
2
+
3
+ > 日期:2026-09-12 · 状态:生效
4
+
5
+ ## 背景
6
+
7
+ `run_background` 起的长任务(dev server / 监听 / 下载)在 Windows 上是经
8
+ `cmd.exe /c <cmd>` 启动的,真正的服务是 cmd 的**子进程**。原来的停止/退出清理用
9
+ `Popen.terminate()`,只终结 cmd —— 真机实测:
10
+
11
+ ```
12
+ run_background("python -m http.server 18234")
13
+ stop_job(1) → 作业显示"已退出(code 1)"
14
+ netstat -ano | findstr 18234
15
+ TCP 127.0.0.1:18234 LISTENING 22088 ← python.exe 还活着
16
+ ```
17
+
18
+ 影响面:
19
+
20
+ 1. dev server "停不掉",端口被占,下次启动报端口冲突;
21
+ 2. 退出 nf 后进程累积(每起停一次泄漏一个);
22
+ 3. **`wait_until(port=...)` 会对"僵尸端口"误报"服务已就绪"** —— 等到的其实是上一轮的
23
+ 进程,给出错误结论。这一条是 2026-09-12 加等待工具后暴露出来的,也是本次决策的触发点。
24
+
25
+ ## 决策
26
+
27
+ `core/jobs.py` 的两条停止路径(`stop()` 与 `shutdown_all()`)统一走 `_terminate_tree()`:
28
+
29
+ - **Windows**:`taskkill /F /T /PID <pid>`(`/T` 递归杀子孙),带 `CREATE_NO_WINDOW`
30
+ (无控制台父进程 spawn 控制台程序会闪黑窗);先杀树,再 `wait(3s)`,超时重杀一次。
31
+ - **POSIX**:启动时 `start_new_session=True` 建独立进程组,停止时 `os.killpg(SIGTERM)`,
32
+ 超时 `SIGKILL`。
33
+ - **绝不杀死自己的进程组**:`_killpg` 先比较 `os.getpgid(pid) == os.getpgid(0)`,相等即放弃
34
+ 整组杀(退回单进程终止)—— 旧作业对象或异常状态下,整组杀会**连带杀死 nf 自己**。
35
+ - 守护测试:`tests/test_jobs_lifecycle.py`(起真实 `http.server` → 停止 → 断言端口释放)。
36
+
37
+ ## 理由
38
+
39
+ **否决的备选**:
40
+
41
+ | 备选 | 否决理由 |
42
+ |---|---|
43
+ | 继续用 `terminate()` + `kill()` | 就是本次要修的缺陷:杀不到孙进程 |
44
+ | `CREATE_NEW_PROCESS_GROUP` + `CTRL_BREAK_EVENT` | 需要子进程自行处理控制台事件,对任意命令不可靠(`python -m http.server` 不会因此优雅退出);且与 POSIX 语义不一致 |
45
+ | Windows Job Object(`CreateJobObject` + `AssignProcessToJobObject`) | 语义最正(内核级整树管理),但要手写 ctypes 结构体 + 句柄生命周期,复杂度远超收益;将来需要"整树资源限制"时再评估 |
46
+ | 记录子进程 PID 后逐个杀 | 孙进程可能再生孙进程(构建工具链),且 PID 记录天然不完整 |
47
+ | 只在退出时清理 | `stop_job` 是用户显式动作,必须立即生效(否则端口一直被占) |
48
+
49
+ ## 代价与边界
50
+
51
+ - `taskkill /F` 是**强制**终止:目标进程没有优雅退出机会(不 flush 缓冲)。对 dev server
52
+ 这类进程可接受;若某长任务需要优雅落盘,应让它自带 graceful 停机命令。
53
+ - `/T` 递归杀整棵子树 —— 若某作业被设计成"启动后脱离父进程独立运行"(如 `start /b`),
54
+ 也会被一并杀掉。这是我们要的语义,但值得知道。
55
+ - POSIX 的"整组杀"依赖 `start_new_session=True`;本项目启动的作业都有,但**外部注入的
56
+ Popen 对象不保证** —— 所以保留"组相同则不整组杀"的防御。
57
+ - 极简环境里没有 `taskkill` 时会失败,退回 `proc.kill()`(杀不到孙进程),属已知降级。
58
+
59
+ ## 关联
60
+
61
+ - 代码:`src/nexforge/core/jobs.py`(`_terminate_tree` / `_taskkill` / `_killpg` / `start`)
62
+ - 测试:`tests/test_jobs_lifecycle.py`
63
+ - 证据:`docs/evidence/wait-tool.log`(E 节"顺带发现的既有缺陷")
64
+ - 相关:[等待用专门的工具 + 可声明超时](2026-09-12-等待工具与超时可豁免.md)
65
+ ——"僵尸端口误报就绪"是本次的触发场景
@@ -0,0 +1,74 @@
1
+ # 决策:MCP 接入 —— 配置建模 + 前缀命名 + 权限自报
2
+
3
+ > 日期:2026-09-13 · 状态:生效
4
+
5
+ ## 背景
6
+
7
+ MCP 客户端(`McpClient` / `McpTool`)在 M1 就写好了,但**从未接线**:全项目没有任何
8
+ 实例化点,配置只从 `config._raw_toml["mcp"]` 裸读。于是有三重问题:
9
+
10
+ 1. **配置写错静默无效**:拼错字段 / 漏 `name` / 重名,用户只会看到"没有工具",
11
+ 无从排查 —— 与「配置残留值治理」ADR 的取向直接冲突;
12
+ 2. **调用必然失败**(已单独修):`McpTool.run` 在 `to_thread` 工作线程里调
13
+ `asyncio.get_event_loop()`,工作线程没有事件循环 → 必抛 RuntimeError;
14
+ 3. **无权限归属**:`permissions.check()` 按工具名查硬编码表,而 MCP 工具名是运行时
15
+ 才知道的 → 一律落到 `danger`,用户既无从知晓原因,也无处按来源调整。
16
+
17
+ 它同时暴露了一个**更普遍**的问题:插件工具今天也是"未知 → danger → 每次弹审批"。
18
+
19
+ ## 决策
20
+
21
+ 1. **配置建模**:新增 `MCPConfig` / `McpServerDef`(pydantic)并进 `NexForgeConfig`;
22
+ 结构校验走 `MCPConfig.validate_servers()` → `ConfigIssue`。**不抛 ValidationError**:
23
+ 单个 server 配错不该让整份配置回退默认值(那是 provider 悬空值的代价,这里太大)。
24
+ 2. **命名强制加前缀**:外部工具一律注册为 `mcp__<server>__<tool>`
25
+ (非字母数字替换为 `_`,截断到 64 字符)。
26
+ 3. **权限自报 + 前缀兜底**:
27
+ - `McpTool.permissions_category` 按 server 的 `require_approval` 声明 `danger` / `read`;
28
+ - `permissions.category_of(name, declared)` 优先级:**工具自报 > 精确表 > 前缀规则 > danger**;
29
+ - 前缀表 `PREFIX_RULES`(`mcp__` → `danger`)兜住"没声明"的情形。
30
+ 4. **失败可见**:连接在 `on_mount` 后台进行(不阻塞开局),每个 server 的结果都出提示;
31
+ 新增 `/mcp` 命令展示配置 / 连接状态 / 失败原因(与 `/plugins`、`/capabilities` 同构)。
32
+ 5. **成本闸门**:`[mcp] max_tools`(默认 40)+ server 级 `tools` 白名单;超出截断并提示。
33
+ 6. **生命周期**:优雅路径 `await client.disconnect()`(`on_unmount`,3s 超时兜底),
34
+ 同步退出路径(`_safe_shutdown`)用 `terminate_now()` 尽力终止,避免留下孤儿 server。
35
+
36
+ ## 理由
37
+
38
+ - **否决"继续裸读 `_raw_toml`"**:用户无从知道自己配错了 —— "静默无效"正是本次要消灭的问题类别。
39
+ - **否决"裸名注册"**:外部 server 声明一个 `read_file` 就能**覆盖内置工具**,
40
+ 这不是命名偏好而是安全边界。
41
+ - **否决"把 MCP 工具名写进 `CATEGORIES`"**:名字运行时才知道,写不进去。
42
+ - **否决"为 MCP 单独开后门"**:动态工具授权是通用问题(插件同样遇到),
43
+ 用"工具自报 + 前缀兜底"一次解决,且不改动既有 41 个内置工具的分类。
44
+ - **否决"用 capability 上报 MCP 可用性"**:「能力可用性上报」ADR 要求探测
45
+ **只读、无副作用(不发网络)**,而 MCP 的"可用"只有真实连接才知道 → 改用 `/mcp` 命令。
46
+ - **否决"自动重连 / 守护进程"**:会引入看不见的后台重试,与"失败必须可见"冲突。
47
+ - **默认 `require_approval = true`**:外部进程与 `run_background` 同级,先保守;
48
+ 用户显式设为 `false` 才算授权。
49
+
50
+ ## 代价与边界
51
+
52
+ - **token 成本**:每个 MCP 工具都进**每一轮**请求的 schema(内置 41 个约 7.9k tokens),
53
+ 上游 server 动辄几十个工具 → 靠 `max_tools` + 白名单收敛;**没有做"按需注入"**
54
+ (那需要相关度筛选,属另一件事)。
55
+ - **仅支持 stdio**:sse / streamable-http 配了会被明确跳过并提示(不静默)。
56
+ - **无自动重连**:server 中途挂掉需重启 nf(后续可加 `/mcp reload`)。
57
+ - **MCP 工具不得在事件循环线程里同步调用**:`run_coroutine_threadsafe(...).result()`
58
+ 在 loop **自己的线程**里会自锁(loop 被这个同步调用占住,提交进去的协程永远轮不到
59
+ 执行)。现检测到该情形直接报错(有测试钉住),Agent 的 `to_thread` 路径不受影响。
60
+ - **审批语义**:`require_approval = false` 时 MCP 工具归"只读",
61
+ 但外部 server 实际仍可能写文件 —— 这是**用户显式承担**的选择,手册已注明。
62
+ - **插件工具仍是"未知 → danger"**:本次提供了 `permissions_category` 机制但没批量改造插件;
63
+ 有需要的插件可直接声明该属性。
64
+ - 配置校验只覆盖结构问题(缺 name / 重名 / 缺 command / transport 未实现),
65
+ **不验证外部命令是否真实存在** —— 那要跑起来才知道,属连接阶段的错误(会提示)。
66
+
67
+ ## 关联
68
+
69
+ - 代码:`config.py`、`mcp/__init__.py`、`core/permissions.py`、`core/agent.py`、
70
+ `ui/screens/chat.py`、`ui/slash_handler.py`、`ui/screens/chat_actions.py`、`i18n.py`
71
+ - 测试:`tests/test_mcp_lifecycle.py`(链路)、`tests/test_mcp_integration.py`(配置→注册→权限)
72
+ - 手册:`manual/12-MCP接入.md`、`manual/02-界面与快捷键.md`(`/mcp`)
73
+ - 相关:[等待工具与超时可豁免](2026-09-12-等待工具与超时可豁免.md)(同一批引入的
74
+ `timeout_for` 机制让 MCP 也能声明超时)、[能力可用性上报](2026-09-10-能力可用性上报.md)(否决的备选)
@@ -68,3 +68,6 @@
68
68
  | 2026-09-11 | [配置残留值治理(自动收敛 + 必须可见)](2026-09-11-配置残留值治理.md) | 生效 |
69
69
  | 2026-09-12 | [内容里的标记不得被当成消息边界](2026-09-12-内容里的标记不得当成消息边界.md) | 生效 |
70
70
  | 2026-09-12 | [目标模式的失败必须可见,历史必须保全](2026-09-12-目标模式失败可见与历史保全.md) | 生效 |
71
+ | 2026-09-12 | [等待用专门的工具 + 可声明超时](2026-09-12-等待工具与超时可豁免.md) | 生效 |
72
+ | 2026-09-12 | [杀进程必须杀整棵树](2026-09-12-进程树终止.md) | 生效 |
73
+ | 2026-09-13 | [MCP 接入(配置建模 + 前缀命名 + 权限自报)](2026-09-13-MCP接入.md) | 生效 |
@@ -0,0 +1,76 @@
1
+ # 发布 0.5.0 记录(2026-09-12)
2
+
3
+ ## 为什么是 minor(0.4.1 → 0.5.0)
4
+
5
+ 本版既含**新能力**(目标模式: 总监只读核验 / 核验折叠区 / 归档查看清理; 上下文窗口自学习),
6
+ 也含**行为修复**(停止不再删历史、失败不再静默挂起)。按语义化版本, 新增能力 → minor。
7
+
8
+ 兼容性: **无破坏性变更**。配置、会话文件格式、CLI 命令均保持兼容; 目标模式新增
9
+ `.nexforge/goal_archive/` 目录(新产物), 不迁移、不回溯旧会话(下次保存自动收敛)。
10
+
11
+ ## 内容(相对 0.4.1, 6 个提交)
12
+
13
+ | 提交 | 主题 |
14
+ |---|---|
15
+ | `41d9d42` | feat(context): 上下文窗口自学习(压缩预算跟随当前模型)+ 会话陈旧分段清理 |
16
+ | `2396571` | fix(goal): 目标模式失败路径治理 + 历史归档 + 验收客观证据 |
17
+ | `224f176` | feat(goal): 总监只读工具(自主核验)+ max_iters 不被目标模式放大 |
18
+ | `77c9641` | fix(session): 会话分段容量 200→500 行 + 切分改用真实行数(压会话文件数) |
19
+ | `707d49c` | feat(goal): 总监核验折叠区(核验了什么/读到了什么, 默认收起可展开) |
20
+ | `ef44ff6` | feat(goal): 归档查看/清理入口(/goal archive [clean N]) |
21
+
22
+ ### 用户可感知的变化
23
+
24
+ - **失败不再静默挂起**: 开发者回合异常(报错/超时/取消)→ 补失败回执交总监决策;
25
+ 总监回合失败 → 重试一次 → 仍失败则暂停(保留目标/方案/轮次, 回一句话续跑);
26
+ 余额耗尽改为数值判定(修掉整元余额 ¥100.00 被误判为耗尽)。
27
+ - **停止不再删历史**: 目标/方案/轮次归档到 `.nexforge/goal_archive/`;
28
+ 新增 `/goal archive`(列出)与 `/goal archive clean [N]`(带确认的清理)。
29
+ - **验收有客观证据**: 复盘自动附 git(未提交改动 + 最近提交)+ 文件树快照的改动摘要,
30
+ 并明确"与开发者报告冲突时以客观证据为准"; 总监可自主调用 9 个**只读**工具核验,
31
+ 界面出现可展开的「总监核验」折叠区(`[ui] director_tools = false` 可关, 省 token)。
32
+ - **会话更省文件**: 同一会话的分段文件数实测 111 → 43(-61%), 旧会话下次保存自动收敛。
33
+ - **上下文预算跟随当前模型**(不再写死 1M): 上游报错时学到真实窗口并立刻压缩, 换模型下轮生效。
34
+
35
+ ### 实测依据(真机驱动, 非推测)
36
+
37
+ 见 ADR `docs/decisions/2026-09-12-目标模式失败可见与历史保全.md`, 关键三条:
38
+
39
+ 1. 目标模式跑的测试项目**不是 git 仓库** → 只靠 git 的"客观证据"是空话, 改为 git + 文件树快照;
40
+ 2. 总监会话 28 条消息 = 请求体量 11.4 万字符 → **按条数裁剪(30)永不触发**, 改为体量预算;
41
+ 3. 会话文件 53.2 万字符 ≠ 请求体量(结构标记 base64 膨胀) → 判据落到 `director_load` 实测。
42
+
43
+ ## 发布流程与结果
44
+
45
+ | 步骤 | 结果 |
46
+ |---|---|
47
+ | 版本 | 0.4.1 → **0.5.0**(5 处同步: 代码 / 打包元数据 / README / 手册索引 / 手册第 1 章) |
48
+ | 质量 gate | **6/6 PASS**(eof-newline / example-config / hygiene / manual-consistency / registry-consistency / version-sync) |
49
+ | 全量测试 | **681 passed, 3 skipped** |
50
+ | 构建 | whl 316 KB + tar.gz 519 KB |
51
+ | 包检查 | `twine check` **PASSED**(whl + tar.gz) |
52
+ | 标签 | `v0.5.0` |
53
+ | 上传 | PyPI 上传成功: whl 323,240 B + tar.gz 531,203 B(upload 2026-09-12T13:05:10Z) |
54
+
55
+ ### 上传与核验细节
56
+
57
+ - 上传走 `~/.pypirc` 的 `[pypi]` token(环境变量 `NEXFORGE_PYPI_TOKEN` 在本机 shell 里是空的;
58
+ 用 `bash -c "... --password \"$VAR\""` 时 `$` 被 cmd 层转义过一次, 会变成**字面量密码** →
59
+ 表现为"进度跑满 100% 后 403 Forbidden", 这个假象值得记住: 403 不一定是 token 失效)。
60
+ - PyPI JSON API 核验(绕开 pip 索引缓存):
61
+ `https://pypi.org/pypi/nexforge-cli/0.5.0/json` → 200, `version=0.5.0`, 两个产物 size 与本地一致。
62
+ 注意 `pip index versions` 在发布后一段时间仍显示旧版本(PyPI 索引/本地缓存延迟) —— 不要据此判定失败。
63
+
64
+ ### 一处未跟上的元数据(留待下次发版)
65
+
66
+ README 的"要点"段落(PyPI 页面描述)在 0.5.0 上传时仍是 v0.4.1 要点 —— 上传后才发现。
67
+ 已在本地 README 更新为 0.5.0 要点, 但**元数据要下次发版才会刷新到 PyPI 页面**
68
+ (同版本号不可重传)。影响面: PyPI 项目页顶部的"当前版本"已正确显示 v0.5.0, 仅要点段落滞后一版。
69
+
70
+
71
+ ## 已知边界(本版有意不做, 已写入 ADR)
72
+
73
+ - 目标文件**无跨进程互斥锁**(写入已原子化, 读不到半截; 同一项目同时跑两个目标模式会互相覆盖);
74
+ - 界面验收不强制截图(改以客观证据 + 提示为准; 无 GUI 会话下强制截图会卡死流程);
75
+ - 会话分段不做增量写(只解决文件数, 不优化 IO —— 收益/复杂度不成比例);
76
+ - 归档目录只增不减**除了**用户主动 `/goal archive clean`。
@@ -0,0 +1,144 @@
1
+ # wait / wait_until 实测证据
2
+
3
+ > 采集:2026-09-12 · Windows 10.0.26200 · Python 3.14.3
4
+ > 用途:在"要不要加等待工具"这件事上先拿数据,而不是凭印象决策。
5
+
6
+ ## A. 现存各种"等待写法"的真实行为
7
+
8
+ 测量方式:在 `run_shell` 的真实执行环境(`subprocess.run(shell=True, stdin=DEVNULL,
9
+ capture_output=True)`)里跑命令,用 `time.time()` 取差值。
10
+
11
+ | 命令 | 实测耗时 | 退出码 | 结论 |
12
+ |---|---|---|---|
13
+ | `timeout /t 2` | **0.03s** | 1 | **静默失败**:stderr `Input redirection is not supported`。以为等了 2 秒,实际 0 秒 |
14
+ | `sleep 2` | — | 1 | cmd 无此命令:`'sleep' is not recognized as an internal or external command` |
15
+ | `ping -n 3 127.0.0.1 >nul` | 2.03s | 0 | 能等,但仅 Windows、1 秒粒度、污染输出 |
16
+ | `bash -c "sleep 2"` | **10.59s** | 0 | PATH 上是 WSL 存根(`WindowsApps\bash.EXE`),冷启动多花 8.5 秒 |
17
+ | `powershell -Command Start-Sleep -Seconds 2` | 2.30s | 0 | 能用,但慢 15%,且 stderr 有 profile 加载告警 |
18
+ | `python -c "time.sleep(2)"`(进程内) | **2.00s** | 0 | 精确 —— 等待工具据此实现(不起子进程,无编码/平台差异) |
19
+
20
+ ## B. 为什么"等待"必须可中断
21
+
22
+ asyncio 的超时/取消只能解除 `await`,**杀不掉 `to_thread` 里的线程**:
23
+
24
+ ```
25
+ asyncio.wait_for(asyncio.to_thread(work), 1) # work 内 time.sleep(3)
26
+ -> "asyncio 在 1.00s 放弃等待(工具被判失败)"
27
+ -> 此后线程池线程仍在跑,直到自己的 sleep 结束
28
+ ```
29
+
30
+ 含义:不可中断的长等待会让"用户按了停止"变成假象,也会在退出时拖住进程。
31
+ 因此等待走 `core/waits.py` 的进程级 Event(每 0.05s 轮询一次)。
32
+
33
+ 同时这也是**旧 `wait_for_job` 的暗坑**:它用 `time.sleep(0.1)` 轮询作业状态,
34
+ 取消对它无效。本次一并改为可中断睡眠。
35
+
36
+ ## C. 新实现的自测(`tests/test_wait_tools.py`,26 项全绿,7.19s)
37
+
38
+ - `wait(0.3)` 实测 **0.30s**(误差 < 10ms)
39
+ - `wait(30)` 在 0.3s 后 `interrupt()` → **0.3s 内返回**,`ok=False`、"等待被用户中断"
40
+ - `wait(601)` → **明确拒绝**("单次等待上限 600 秒"),不静默截断
41
+ - `wait` 漏传 `seconds` → 报“参数错误”(而非静默返回“已等待 0 秒”)
42
+ - `job_manager.wait()`(等作业)在 0.5s 后 `interrupt()` → **2s 内返回**(修前要等满 20s)
43
+ - Agent 上限 = `min(max(全局 tool_timeout, 工具声明的 timeout_for), 3600s)`:
44
+ - 全局压到 0.5s + 工具声明 2s(实睡 1s)→ **成功**(豁免生效)
45
+ - 不声明的工具(实睡 1s)→ **判超时**,报错含实际上限 `0.5`
46
+ - 假工具声明 1e9 → 被硬顶截到 **3600s**(防乱声明钉死会话)
47
+ - `wait_until`:
48
+ - `file`:目标 0.4s 后出现 → 命中
49
+ - `log`:0.3s 后写入 `BUILD SUCCESS` → 命中(缺 `expect` 时明确拒绝)
50
+ - `port`:监听中 → 命中;关闭后 → 超时并给出"不可连接"证据
51
+ - `http`:状态码匹配规则(空=2xx/3xx、`404`、`200,204`、`5xx`)单测覆盖
52
+ - 权限:`wait` / `wait_until` 归**只读**(supervised / safe 下均 `allow`,不弹审批)
53
+ - 总监只读白名单允许等待(核验构建产物/端口是合理诉求,且改不了仓库)
54
+ - 系统提示(`DEFAULT_SYSTEM`)含“等待用 wait / wait_until,不要用 sleep 拼命令”
55
+ - 源码断言:5 个文件的每条取消路径都调用 `waits.interrupt()`(防漏改回归)
56
+
57
+ ## D. 检查中发现并修掉的导入链问题
58
+
59
+ 把 `nexforge.tools.wait_tools` 加进 `test_cross_platform.MODULES`(无 windll 环境导入门)
60
+ 后**立刻报错**,栈为:
61
+
62
+ ```
63
+ nexforge/tools/wait_tools.py:36: import httpx
64
+ httpx/__init__.py -> httpx/_main.py:8: import click
65
+ click/_compat.py -> click/_winconsole.py:42: kernel32 = windll.kernel32
66
+ AttributeError: windll not available
67
+ ```
68
+
69
+ 即 httpx 的导入链会拉 click(Windows 分支硬依赖 `ctypes.windll`)。修法:顶层不再
70
+ import httpx,只在 `until=http` 的探测函数内延迟导入 —— “等文件/端口/日志”不再为
71
+ 网络库付代价。修后该门禁通过(70 passed)。
72
+
73
+ ## E. 真机验证(重启后的新进程,2026-09-12)
74
+
75
+ | 调用 | 返回 | 实测耗时 |
76
+ |---|---|---|
77
+ | `wait(2)` | 已等待 2.00 秒 | 2.00s |
78
+ | `wait(65)` | **已等待 65.00 秒** | 65.00s |
79
+ | `wait(601)` | 拒绝(单次等待上限 600 秒) | 即时 |
80
+ | `wait_until(port, 127.0.0.1:18234)` | 命中,1 次探测 | 0.50s |
81
+ | `wait_until(http, expect=200)` | 命中 HTTP 200,1 次探测 | 0.34s |
82
+ | `wait_until(file, 4 秒后才生成)` | 命中 | 4.50s(10 次探测) |
83
+ | `wait_until(file, 不存在的文件)` | 超时 + 末次证据 | 2.00s(8 次探测) |
84
+ | `wait_until(log, expect)` | 命中 | 0.00s |
85
+ | `wait_until(log, 缺 expect)` | 拒绝 | 即时 |
86
+ | `wait_for_job(3, 15)` | 作业已退出(code 0) | 即时 |
87
+ | `wait(1)`(在 Esc 取消之后的下一轮) | 已等待 1.00 秒 —— 说明中断标记已复位 | 1.00s |
88
+
89
+ **`wait(65)` 是超时豁免的决定性证据**:65 秒 > 全局 `tool_timeout`(60),若豁免失效
90
+ 这里必然返回"工具执行超时(60s)"。
91
+
92
+ ### Esc 中断的真实路径(重要:别误判成 bug)
93
+
94
+ 真机按 Esc 取消一个 `wait(60)` 后,**模型看到的是**:
95
+
96
+ ```
97
+ 已取消(中断/超时)。
98
+ ```
99
+
100
+ 这条**不是** wait 工具自己的文案,而是会话修复层补的回执(`core/session.py` 的
101
+ `_repair_tool_sequence`:为没有响应的 tool_call 补一条合法响应,防止下次请求 400)。
102
+
103
+ 原因链条:Esc → `cancel_group("agent")` 取消 asyncio worker → `_exec_tool` 的
104
+ `asyncio.wait_for` 抛 `CancelledError` → 回合终止、**工具结果被丢弃**。
105
+ `waits.interrupt()` 同时被调用,其作用是**让等待线程立刻退出**(不留一个还在睡的线程),
106
+ 而不是改变给模型看的中断文案 —— 后者由会话修复层负责。
107
+
108
+ 因此 `wait` 的"等待被用户中断(已等 X 秒)"文案只在"只 interrupt、不 cancel"的时序
109
+ (如 Web 面板停止的竞态窗口)可见;Esc 路径由修复层兜底。两者互不冲突:
110
+ 真机观察到的用户可见行为是"**立刻停止,不等满**",这正是要保证的。
111
+
112
+ ### 检查中发现的实现缺陷(已修):探测耗时未计入 timeout 预算
113
+
114
+ 真机实测(修复前):
115
+
116
+ | 请求 | 实际耗时 | 探测次数 | 末次探测证据 |
117
+ |---|---|---|---|
118
+ | `wait_until(port, timeout=2, interval=0.4)` | 2.00s | 1 | TimeoutError |
119
+ | `wait_until(port, timeout=3, interval=1)` | **5.00s** | 2 | TimeoutError |
120
+ | `wait_until(port, timeout=3, interval=0.5)` | **4.50s** | 2 | TimeoutError |
121
+
122
+ 根因:`_probe_port` 的 `socket.create_connection(timeout=2)` 耗时会受本机行为影响 ——
123
+ 对**未监听端口**这里是 **2 秒超时**(不是立即 ConnectionRefused),而这段耗时**没有计入**
124
+ timeout 预算,于是"最多等 3 秒"的请求实际跑了 5 秒。
125
+
126
+ 修法:循环里先算 `budget = deadline - now` 并把预算传给探测函数 —— `port` 用
127
+ `min(2.0, max(0.2, budget))`,`http` 用 `min(5.0, max(0.5, budget))`。
128
+
129
+ 修复后(子进程加载新版源码实测):`timeout=3` → **3.00s**(原 5.00s)。
130
+ 守护测试:`test_wait_until_respects_timeout_budget`(断言实际耗时 ≤ timeout + 0.8s)。
131
+
132
+ ### 顺带发现的既有缺陷(均已修)
133
+
134
+ 1. **`stop_job` / 退出清理杀不掉孙进程**(`core/jobs.py`):实测
135
+ `run_background("python -m http.server 18234")` → `stop_job` 后作业显示"退出1",
136
+ 但 `python.exe`(PID 22088) **仍 LISTENING 18234**。因为 `Popen(shell=True)` 在
137
+ Windows 上是 cmd.exe → python.exe,`proc.terminate()` 只终结 cmd.exe,孙进程成孤儿。
138
+ 影响:dev server/长任务场景端口占用、内存泄漏;**`wait_until(port)` 可能对"僵尸端口"
139
+ 误报就绪**。**已修**:`_terminate_tree()` —— Windows `taskkill /F /T`、POSIX
140
+ 独立进程组 `killpg`(见 ADR「杀进程必须杀整棵树」与 `tests/test_jobs_lifecycle.py`)。
141
+ (本次测试产生的孤儿已手动清理。)
142
+ 2. **`web_screenshot` 内部超时 90s > 全局工具上限 60s**(`tools/screenshot_tools.py:315`):
143
+ 复杂页面会在 60s 被判"工具执行超时",而线程/浏览器继续跑到 90s(结果被丢弃、白跑)。
144
+ **已修**:给它加了 `timeout_for()` 返回 95,内外部超时一致。
@@ -35,7 +35,7 @@ docs/nexforge.zh-CN.md ← 内容取自 docs/submission/nexforge.zh-CN.md
35
35
  **README.zh-CN.md**(中文版): 同样位置(在 nanobot 之后、Oh My Pi 之前)插入:
36
36
 
37
37
  ```markdown
38
- | **NexForgeCLI** | 开源终端 AI 编程助手,内置 35 个工具,原生支持 DeepSeek-V4(100 万上下文)、数据分析、网络搜索与总监循环目标模式。 | [指南](./docs/nexforge.zh-CN.md) |
38
+ | **NexForgeCLI** | 开源终端 AI 编程助手,内置 41 个工具,原生支持 DeepSeek-V4(100 万上下文)、数据分析、网络搜索与总监循环目标模式。 | [指南](./docs/nexforge.zh-CN.md) |
39
39
  ```
40
40
 
41
41
  > 注意: 按字母序插入(Ne 在 na 和 Oh 之间), 不要追加到表格末尾, 否则合并冲突概率高。
@@ -2,7 +2,7 @@
2
2
 
3
3
  # 接入 NexForgeCLI
4
4
 
5
- NexForgeCLI 是一款开源的终端 AI 编程助手,内置 35 个工具,覆盖文件编辑、Shell 执行、Git、数据分析(CSV / SQLite / 文本)、网络搜索、图片理解与运维自动化。原生对接 `api.deepseek.com`,支持 DeepSeek-V4-Pro 与 DeepSeek-V4-Flash 全 100 万 token 上下文,也可通过一份 `models.toml` 驱动任意 OpenAI 兼容或 Anthropic 兼容的端点(Kimi、GPT、Claude 等)。
5
+ NexForgeCLI 是一款开源的终端 AI 编程助手,内置 41 个工具,覆盖文件编辑、Shell 执行、Git、数据分析(CSV / SQLite / 文本)、网络搜索、图片理解与运维自动化。原生对接 `api.deepseek.com`,支持 DeepSeek-V4-Pro 与 DeepSeek-V4-Flash 全 100 万 token 上下文,也可通过一份 `models.toml` 驱动任意 OpenAI 兼容或 Anthropic 兼容的端点(Kimi、GPT、Claude 等)。
6
6
 
7
7
  - **仓库:** <https://gitee.com/Lighthorn/nexforgecli>
8
8
  - **PyPI:** <https://pypi.org/project/nexforge-cli/>
@@ -1,6 +1,6 @@
1
1
  # 安装与配置
2
2
 
3
- > NexForgeCLI v0.5.0
3
+ > NexForgeCLI v0.7.0
4
4
 
5
5
  ## 环境要求
6
6
 
@@ -121,6 +121,7 @@
121
121
  | `/map view|check|sync` | 项目地图管理 |
122
122
  | `/kb search|add|list|path` | 知识库管理(检索/添加/列出/设置路径) |
123
123
  | `/plugins [list\|reload]` | 插件管理(列出/重载第三方插件) |
124
+ | `/mcp` | MCP 外部工具服务: 配置与连接状态(详见 [12-MCP接入](12-MCP接入.md)) |
124
125
  | `/web start|stop` | Web 管理面板(端口8899) |
125
126
 
126
127
  **系统**