sliceagent 0.1.5__tar.gz → 0.1.7__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 (205) hide show
  1. {sliceagent-0.1.5 → sliceagent-0.1.7}/CHANGELOG.md +24 -0
  2. {sliceagent-0.1.5 → sliceagent-0.1.7}/PKG-INFO +1 -1
  3. {sliceagent-0.1.5 → sliceagent-0.1.7}/src/sliceagent/__init__.py +1 -1
  4. {sliceagent-0.1.5 → sliceagent-0.1.7}/src/sliceagent/cli.py +51 -18
  5. {sliceagent-0.1.5 → sliceagent-0.1.7}/src/sliceagent/llm.py +27 -5
  6. {sliceagent-0.1.5 → sliceagent-0.1.7}/src/sliceagent/tui.py +40 -22
  7. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_bughunt_fixes.py +4 -4
  8. sliceagent-0.1.7/tests/test_config_journey.py +119 -0
  9. {sliceagent-0.1.5 → sliceagent-0.1.7}/.dockerignore +0 -0
  10. {sliceagent-0.1.5 → sliceagent-0.1.7}/.env.example +0 -0
  11. {sliceagent-0.1.5 → sliceagent-0.1.7}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  12. {sliceagent-0.1.5 → sliceagent-0.1.7}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  13. {sliceagent-0.1.5 → sliceagent-0.1.7}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  14. {sliceagent-0.1.5 → sliceagent-0.1.7}/.github/dependabot.yml +0 -0
  15. {sliceagent-0.1.5 → sliceagent-0.1.7}/.github/workflows/ci.yml +0 -0
  16. {sliceagent-0.1.5 → sliceagent-0.1.7}/.github/workflows/publish.yml +0 -0
  17. {sliceagent-0.1.5 → sliceagent-0.1.7}/.gitignore +0 -0
  18. {sliceagent-0.1.5 → sliceagent-0.1.7}/CODE_OF_CONDUCT.md +0 -0
  19. {sliceagent-0.1.5 → sliceagent-0.1.7}/CONTRIBUTING.md +0 -0
  20. {sliceagent-0.1.5 → sliceagent-0.1.7}/Dockerfile +0 -0
  21. {sliceagent-0.1.5 → sliceagent-0.1.7}/LICENSE +0 -0
  22. {sliceagent-0.1.5 → sliceagent-0.1.7}/NOTICE +0 -0
  23. {sliceagent-0.1.5 → sliceagent-0.1.7}/QUICKSTART.md +0 -0
  24. {sliceagent-0.1.5 → sliceagent-0.1.7}/README.md +0 -0
  25. {sliceagent-0.1.5 → sliceagent-0.1.7}/SECURITY.md +0 -0
  26. {sliceagent-0.1.5 → sliceagent-0.1.7}/examples/plugins/hello/__init__.py +0 -0
  27. {sliceagent-0.1.5 → sliceagent-0.1.7}/examples/plugins/hello/plugin.toml +0 -0
  28. {sliceagent-0.1.5 → sliceagent-0.1.7}/install.sh +0 -0
  29. {sliceagent-0.1.5 → sliceagent-0.1.7}/pyproject.toml +0 -0
  30. {sliceagent-0.1.5 → sliceagent-0.1.7}/scripts/gen_config_reference.py +0 -0
  31. {sliceagent-0.1.5 → sliceagent-0.1.7}/scripts/run_tests.sh +0 -0
  32. {sliceagent-0.1.5 → sliceagent-0.1.7}/sliceagent.toml.example +0 -0
  33. {sliceagent-0.1.5 → sliceagent-0.1.7}/src/sliceagent/__main__.py +0 -0
  34. {sliceagent-0.1.5 → sliceagent-0.1.7}/src/sliceagent/access.py +0 -0
  35. {sliceagent-0.1.5 → sliceagent-0.1.7}/src/sliceagent/agents.py +0 -0
  36. {sliceagent-0.1.5 → sliceagent-0.1.7}/src/sliceagent/background_review.py +0 -0
  37. {sliceagent-0.1.5 → sliceagent-0.1.7}/src/sliceagent/binsniff.py +0 -0
  38. {sliceagent-0.1.5 → sliceagent-0.1.7}/src/sliceagent/clock.py +0 -0
  39. {sliceagent-0.1.5 → sliceagent-0.1.7}/src/sliceagent/code_grep.py +0 -0
  40. {sliceagent-0.1.5 → sliceagent-0.1.7}/src/sliceagent/code_index.py +0 -0
  41. {sliceagent-0.1.5 → sliceagent-0.1.7}/src/sliceagent/config.py +0 -0
  42. {sliceagent-0.1.5 → sliceagent-0.1.7}/src/sliceagent/context_overflow.py +0 -0
  43. {sliceagent-0.1.5 → sliceagent-0.1.7}/src/sliceagent/envspec.py +0 -0
  44. {sliceagent-0.1.5 → sliceagent-0.1.7}/src/sliceagent/errors.py +0 -0
  45. {sliceagent-0.1.5 → sliceagent-0.1.7}/src/sliceagent/events.py +0 -0
  46. {sliceagent-0.1.5 → sliceagent-0.1.7}/src/sliceagent/finding_types.py +0 -0
  47. {sliceagent-0.1.5 → sliceagent-0.1.7}/src/sliceagent/flags.py +0 -0
  48. {sliceagent-0.1.5 → sliceagent-0.1.7}/src/sliceagent/fuzzy.py +0 -0
  49. {sliceagent-0.1.5 → sliceagent-0.1.7}/src/sliceagent/guardrails.py +0 -0
  50. {sliceagent-0.1.5 → sliceagent-0.1.7}/src/sliceagent/guidance.py +0 -0
  51. {sliceagent-0.1.5 → sliceagent-0.1.7}/src/sliceagent/hippocampus.py +0 -0
  52. {sliceagent-0.1.5 → sliceagent-0.1.7}/src/sliceagent/hooks.py +0 -0
  53. {sliceagent-0.1.5 → sliceagent-0.1.7}/src/sliceagent/interfaces.py +0 -0
  54. {sliceagent-0.1.5 → sliceagent-0.1.7}/src/sliceagent/loop.py +0 -0
  55. {sliceagent-0.1.5 → sliceagent-0.1.7}/src/sliceagent/mcp_client.py +0 -0
  56. {sliceagent-0.1.5 → sliceagent-0.1.7}/src/sliceagent/mcp_security.py +0 -0
  57. {sliceagent-0.1.5 → sliceagent-0.1.7}/src/sliceagent/memory.py +0 -0
  58. {sliceagent-0.1.5 → sliceagent-0.1.7}/src/sliceagent/metrics.py +0 -0
  59. {sliceagent-0.1.5 → sliceagent-0.1.7}/src/sliceagent/model_catalog.py +0 -0
  60. {sliceagent-0.1.5 → sliceagent-0.1.7}/src/sliceagent/monitor.py +0 -0
  61. {sliceagent-0.1.5 → sliceagent-0.1.7}/src/sliceagent/neocortex.py +0 -0
  62. {sliceagent-0.1.5 → sliceagent-0.1.7}/src/sliceagent/onboarding.py +0 -0
  63. {sliceagent-0.1.5 → sliceagent-0.1.7}/src/sliceagent/oracle.py +0 -0
  64. {sliceagent-0.1.5 → sliceagent-0.1.7}/src/sliceagent/pagetable.py +0 -0
  65. {sliceagent-0.1.5 → sliceagent-0.1.7}/src/sliceagent/pfc.py +0 -0
  66. {sliceagent-0.1.5 → sliceagent-0.1.7}/src/sliceagent/plugins.py +0 -0
  67. {sliceagent-0.1.5 → sliceagent-0.1.7}/src/sliceagent/policy.py +0 -0
  68. {sliceagent-0.1.5 → sliceagent-0.1.7}/src/sliceagent/procman.py +0 -0
  69. {sliceagent-0.1.5 → sliceagent-0.1.7}/src/sliceagent/prompt.py +0 -0
  70. {sliceagent-0.1.5 → sliceagent-0.1.7}/src/sliceagent/records.py +0 -0
  71. {sliceagent-0.1.5 → sliceagent-0.1.7}/src/sliceagent/recovery.py +0 -0
  72. {sliceagent-0.1.5 → sliceagent-0.1.7}/src/sliceagent/regions.py +0 -0
  73. {sliceagent-0.1.5 → sliceagent-0.1.7}/src/sliceagent/registry.py +0 -0
  74. {sliceagent-0.1.5 → sliceagent-0.1.7}/src/sliceagent/retriever.py +0 -0
  75. {sliceagent-0.1.5 → sliceagent-0.1.7}/src/sliceagent/safety.py +0 -0
  76. {sliceagent-0.1.5 → sliceagent-0.1.7}/src/sliceagent/sandbox.py +0 -0
  77. {sliceagent-0.1.5 → sliceagent-0.1.7}/src/sliceagent/scheduler.py +0 -0
  78. {sliceagent-0.1.5 → sliceagent-0.1.7}/src/sliceagent/search_index.py +0 -0
  79. {sliceagent-0.1.5 → sliceagent-0.1.7}/src/sliceagent/seed.py +0 -0
  80. {sliceagent-0.1.5 → sliceagent-0.1.7}/src/sliceagent/sensory_cortex.py +0 -0
  81. {sliceagent-0.1.5 → sliceagent-0.1.7}/src/sliceagent/session.py +0 -0
  82. {sliceagent-0.1.5 → sliceagent-0.1.7}/src/sliceagent/skill_provenance.py +0 -0
  83. {sliceagent-0.1.5 → sliceagent-0.1.7}/src/sliceagent/skill_usage.py +0 -0
  84. {sliceagent-0.1.5 → sliceagent-0.1.7}/src/sliceagent/skills.py +0 -0
  85. {sliceagent-0.1.5 → sliceagent-0.1.7}/src/sliceagent/subagent.py +0 -0
  86. {sliceagent-0.1.5 → sliceagent-0.1.7}/src/sliceagent/subdir_hints.py +0 -0
  87. {sliceagent-0.1.5 → sliceagent-0.1.7}/src/sliceagent/swap.py +0 -0
  88. {sliceagent-0.1.5 → sliceagent-0.1.7}/src/sliceagent/taskstate.py +0 -0
  89. {sliceagent-0.1.5 → sliceagent-0.1.7}/src/sliceagent/telemetry.py +0 -0
  90. {sliceagent-0.1.5 → sliceagent-0.1.7}/src/sliceagent/terminal.py +0 -0
  91. {sliceagent-0.1.5 → sliceagent-0.1.7}/src/sliceagent/text_utils.py +0 -0
  92. {sliceagent-0.1.5 → sliceagent-0.1.7}/src/sliceagent/tool_summary.py +0 -0
  93. {sliceagent-0.1.5 → sliceagent-0.1.7}/src/sliceagent/tools.py +0 -0
  94. {sliceagent-0.1.5 → sliceagent-0.1.7}/src/sliceagent/web.py +0 -0
  95. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/README.md +0 -0
  96. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_active_focus.py +0 -0
  97. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_agents.py +0 -0
  98. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_ask_echo.py +0 -0
  99. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_ask_user.py +0 -0
  100. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_background_review.py +0 -0
  101. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_binary_view.py +0 -0
  102. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_bound_is_relevance.py +0 -0
  103. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_breadth_e2e.py +0 -0
  104. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_bugfix_2b.py +0 -0
  105. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_bugfix_breadth_wave.py +0 -0
  106. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_bugfix_current_request.py +0 -0
  107. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_bugfix_linenum.py +0 -0
  108. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_bugfix_loop_wave.py +0 -0
  109. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_bugfix_memory_wave.py +0 -0
  110. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_bugfix_p0a.py +0 -0
  111. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_bugfix_p0b.py +0 -0
  112. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_bugfix_p0c.py +0 -0
  113. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_bugfix_selfreview.py +0 -0
  114. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_bugfix_selfreview_siblings.py +0 -0
  115. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_bugfix_skills_h1.py +0 -0
  116. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_bugfix_slice_wave.py +0 -0
  117. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_bugfix_subagent_wave.py +0 -0
  118. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_bugfix_tools_wave.py +0 -0
  119. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_cache_manifest.py +0 -0
  120. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_checkpoint.py +0 -0
  121. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_cli_smoke.py +0 -0
  122. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_closure.py +0 -0
  123. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_code_grep.py +0 -0
  124. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_code_index.py +0 -0
  125. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_consolidate.py +0 -0
  126. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_context_overflow.py +0 -0
  127. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_conversation.py +0 -0
  128. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_coresidency.py +0 -0
  129. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_echo_before_blocking.py +0 -0
  130. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_episode.py +0 -0
  131. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_errors_backoff.py +0 -0
  132. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_esc_sentinel.py +0 -0
  133. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_exec_env.py +0 -0
  134. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_finding_types.py +0 -0
  135. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_flags.py +0 -0
  136. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_fuzzy.py +0 -0
  137. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_ghost_index.py +0 -0
  138. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_guardrails.py +0 -0
  139. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_guidance.py +0 -0
  140. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_history.py +0 -0
  141. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_invariant_fixes.py +0 -0
  142. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_live_composer.py +0 -0
  143. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_llm_streaming.py +0 -0
  144. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_llm_usage_cache.py +0 -0
  145. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_llm_watchdog.py +0 -0
  146. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_log_redaction.py +0 -0
  147. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_loop_overflow.py +0 -0
  148. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_mcp_output_cap.py +0 -0
  149. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_mcp_runtime.py +0 -0
  150. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_memory.py +0 -0
  151. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_memory_persist.py +0 -0
  152. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_menu_select.py +0 -0
  153. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_metrics.py +0 -0
  154. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_mining.py +0 -0
  155. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_mission_tier.py +0 -0
  156. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_model_catalog.py +0 -0
  157. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_monitor.py +0 -0
  158. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_onboarding.py +0 -0
  159. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_pageout_compaction.py +0 -0
  160. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_permission_patterns.py +0 -0
  161. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_pinned_composer.py +0 -0
  162. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_plan_tier.py +0 -0
  163. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_procman.py +0 -0
  164. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_product_features.py +0 -0
  165. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_progress_status.py +0 -0
  166. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_prompt_ab.py +0 -0
  167. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_provenance.py +0 -0
  168. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_provider_lineup.py +0 -0
  169. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_proxy_interrupt.py +0 -0
  170. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_reach.py +0 -0
  171. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_readonly_subagent.py +0 -0
  172. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_recall_search.py +0 -0
  173. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_records.py +0 -0
  174. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_refault.py +0 -0
  175. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_registry_validation.py +0 -0
  176. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_reliability.py +0 -0
  177. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_repo_map_bounded.py +0 -0
  178. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_requirements.py +0 -0
  179. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_review_fixes.py +0 -0
  180. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_route_lexical.py +0 -0
  181. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_safety.py +0 -0
  182. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_seal_markdown.py +0 -0
  183. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_search_index.py +0 -0
  184. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_skill_meta.py +0 -0
  185. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_skill_metadata.py +0 -0
  186. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_subdir_hints.py +0 -0
  187. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_task_agnostic.py +0 -0
  188. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_task_state_roundtrip.py +0 -0
  189. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_telemetry_convergence.py +0 -0
  190. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_terminal.py +0 -0
  191. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_tool_dedup.py +0 -0
  192. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_tool_result_ok.py +0 -0
  193. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_tools_robust.py +0 -0
  194. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_topic_switch.py +0 -0
  195. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_trajectory.py +0 -0
  196. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_tui.py +0 -0
  197. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_tui_menus.py +0 -0
  198. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_tui_render.py +0 -0
  199. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_tui_widgets.py +0 -0
  200. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_turn_budget.py +0 -0
  201. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_verification_agent.py +0 -0
  202. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_web.py +0 -0
  203. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_workspace.py +0 -0
  204. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_world_region.py +0 -0
  205. {sliceagent-0.1.5 → sliceagent-0.1.7}/tests/test_world_state.py +0 -0
@@ -5,6 +5,30 @@ this project aims for [Semantic Versioning](https://semver.org/).
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [0.1.7] — 2026-07-02
9
+
10
+ ### Fixed
11
+ - `/model`: the current env-configured model row is now labeled `current (env)` instead of the
12
+ provider family name — it could masquerade as a configured provider (e.g. an env `gpt-5.5` on a
13
+ DeepSeek endpoint showed as "deepseek").
14
+
15
+ ## [0.1.6] — 2026-07-02
16
+
17
+ The clear config journey: one wizard, two doors, an honest /model.
18
+
19
+ ### Added
20
+ - **`/config`** — manage LLM providers *inside* sliceagent: the same wizard as first-run onboarding
21
+ (provider → model → key → live test), then the config hot-reloads and the new provider shows up
22
+ in `/model` immediately.
23
+ - **`/model` switches providers for real** — the menu lists ONLY configured providers' models
24
+ (saved model + suggestions, labeled by provider), and picking one switches **model + endpoint +
25
+ key together** (the old menu changed the model string but never the endpoint). The last-picked
26
+ provider is remembered across sessions.
27
+
28
+ ### Changed
29
+ - Typed `/model <name>` stays same-endpoint (documented as such); the mismatch warning now points
30
+ at `/config` + the `/model` menu instead of `config --use`.
31
+
8
32
  ## [0.1.5] — 2026-07-02
9
33
 
10
34
  ### Changed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sliceagent
3
- Version: 0.1.5
3
+ Version: 0.1.7
4
4
  Summary: A coding agent with a new context-engineering framework: bounded, deterministic, reconstructed context (slice architecture) — built for long-horizon work.
5
5
  Author: TT-Wang
6
6
  License-Expression: MIT
@@ -1,3 +1,3 @@
1
1
  """sliceagent — a memory-native coding agent (deterministic, bounded, reconstructed context)."""
2
2
 
3
- __version__ = "0.1.5"
3
+ __version__ = "0.1.7"
@@ -134,8 +134,8 @@ def _reasoning_note(llm) -> str:
134
134
  label = {"openai": "an OpenAI", "deepseek": "a DeepSeek",
135
135
  "moonshot": "a Moonshot", "anthropic": "an Anthropic"}.get(home, f"a {home}")
136
136
  return (f"note: {llm.model} looks like {label} model, but you're connected to {base or '(default)'}"
137
- f" — it will likely fail on your next message. Run `sliceagent init` to add {label} provider, "
138
- f"then `config --use <id>` to switch (or `/model` with no args to see what's configured).")
137
+ f" — it will likely fail on your next message. Add {label} provider with /config, then pick "
138
+ f"its model from the /model menu (the menu switches the endpoint too; typed /model doesn't).")
139
139
  eff = (getattr(llm, "reasoning", "full") or "full").lower()
140
140
  if eff == "full":
141
141
  return ""
@@ -162,10 +162,14 @@ def main() -> None:
162
162
  _load_env()
163
163
  # config-persisted key/endpoint (written by `sliceagent init`) populate the env BEFORE the gate, so a
164
164
  # configured user never has to export anything; ENV still wins for one-off overrides.
165
- from .config import load_config
166
-
167
- def _env_from_config(c) -> None:
168
- for _env, _val in (("LLM_API_KEY", c.api_key), ("LLM_BASE_URL", c.base_url)):
165
+ from .config import load_config, load_prefs
166
+ _boot_prefs = load_prefs() # the last /model choice may pin a PROVIDER (endpoint+key), not just a model
167
+
168
+ def _env_from_config(c, pid: str | None = None) -> None:
169
+ tbl = (c.providers() or {}).get(pid) if pid else None
170
+ key = (tbl or {}).get("api_key") or c.api_key
171
+ base = ((tbl or {}).get("base_url") if tbl and tbl.get("api_key") else None) or c.base_url
172
+ for _env, _val in (("LLM_API_KEY", key), ("LLM_BASE_URL", base)):
169
173
  if not os.environ.get(_env) and _val:
170
174
  os.environ[_env] = _val
171
175
 
@@ -185,7 +189,7 @@ def main() -> None:
185
189
  return run_init() == 0
186
190
 
187
191
  cfg = load_config()
188
- _env_from_config(cfg)
192
+ _env_from_config(cfg, _boot_prefs.get("provider"))
189
193
  if not _key_present() and _first_run_setup("Welcome! No API key configured yet"):
190
194
  cfg = load_config() # the wizard just wrote ~/.sliceagent/config.toml — pick it up
191
195
  _env_from_config(cfg)
@@ -465,12 +469,33 @@ def main() -> None:
465
469
  return {"y": "yes", "yes": "yes", "a": "always", "always": "always"}.get(ans, "no")
466
470
 
467
471
  def _handle_slash(line): # TUI navigation palette — wired to existing session ops
472
+ nonlocal cfg # /config hot-reloads the config after the in-session wizard
468
473
  parts = line.split(maxsplit=1)
469
474
  cmd, arg = parts[0], (parts[1].strip() if len(parts) > 1 else "")
470
475
  if cmd == "/help":
471
- _console.print("commands: /model · /mode · /cwd · /learn · /plan · /cost · /threads · /plugins · /mcp · "
472
- "/help · /exit\n (type / for the menu · /cwd <path> switches workspace · "
473
- "Esc = undo last turn · say \"review my changes\" for code_review · @path pins a file)")
476
+ _console.print("commands: /config · /model · /mode · /cwd · /learn · /plan · /cost · /threads · "
477
+ "/plugins · /mcp · /help · /exit\n (type / for the menu · /config adds LLM "
478
+ "providers · /cwd <path> switches workspace · Esc = undo last turn · "
479
+ "say \"review my changes\" for code_review · @path pins a file)")
480
+ elif cmd == "/config":
481
+ # THE clear user journey: providers are managed INSIDE sliceagent — same wizard as first-run
482
+ # onboarding (provider → model → key → live test), then the new provider shows up in /model.
483
+ if sys.stdin.isatty() and not use_live:
484
+ from .onboarding import run_init
485
+ try:
486
+ rc = run_init()
487
+ except (EOFError, KeyboardInterrupt):
488
+ rc = 1
489
+ if rc == 0:
490
+ from .config import load_config as _reload
491
+ cfg = _reload() # hot-reload: /model now sees the new provider
492
+ provs = cfg.providers()
493
+ _console.print(" providers configured: "
494
+ + (", ".join(f"[bold]{k}[/] ({v.get('model', '?')})"
495
+ for k, v in provs.items()) or "(none)"))
496
+ _console.print(" switch with [bold]/model[/] — it lists your configured providers' models.")
497
+ else:
498
+ _console.print(" /config needs an interactive terminal — run `sliceagent init` instead.")
474
499
  elif cmd == "/plan":
475
500
  s = session.active() if session.active_id else None
476
501
  plan = getattr(s, "plan", None) if s else None
@@ -562,23 +587,31 @@ def main() -> None:
562
587
  from .tui import select_model_reasoning
563
588
  choice = select_model_reasoning(llm, cfg)
564
589
  if choice:
565
- llm.switch(model=choice[0], reasoning=choice[1])
590
+ _model, _reasoning, _pid = choice
591
+ _kw = {}
592
+ if _pid: # a CONFIGURED provider → switch endpoint+key too
593
+ _tbl = (cfg.providers() or {}).get(_pid) or {}
594
+ if _tbl.get("api_key"):
595
+ _kw = {"base_url": _tbl.get("base_url") or "", "api_key": _tbl["api_key"]}
596
+ llm.switch(model=_model, reasoning=_reasoning, **_kw)
566
597
  _stats["model"] = llm.model
567
- save_prefs({"model": llm.model, "reasoning": llm.reasoning})
598
+ save_prefs({"model": llm.model, "reasoning": llm.reasoning,
599
+ **({"provider": _pid} if _pid else {})})
568
600
  note = _reasoning_note(llm)
569
- _console.print(f" ✓ model → [bold]{llm.model}[/] · reasoning [bold]{llm.reasoning}[/] (saved)"
601
+ _console.print(f" ✓ model → [bold]{llm.model}[/]"
602
+ + (f" @ [bold]{_pid}[/]" if _pid else "")
603
+ + f" · reasoning [bold]{llm.reasoning}[/] (saved)"
570
604
  + (f"\n {note}" if note else ""))
571
605
  elif not arg:
572
606
  _console.print(f" model: [bold]{llm.model}[/] · reasoning: [bold]{llm.reasoning}[/]"
573
607
  f" · net: {getattr(llm, 'proxy_used', 'direct')}")
574
- known = ("gpt-5.5", "gpt-5", "gpt-5-mini", "o3", "deepseek-chat",
575
- "kimi-k2-0905-preview", "claude-sonnet-4-6")
576
- _console.print(" switch: /model <name> [fast|full|high|max]")
577
- _console.print(" known: " + ", ".join(known))
608
+ _console.print(" switch: /model <name> [fast|full|high|max] (same endpoint)")
578
609
  provs = cfg.providers()
579
610
  if provs:
580
- _console.print(" providers (use `config --use <id>` to change endpoint): "
611
+ _console.print(" configured providers (pick via the /model menu to switch endpoint too): "
581
612
  + ", ".join(f"{k}={v.get('model', '?')}" for k, v in provs.items()))
613
+ else:
614
+ _console.print(" no providers configured yet — add one with [bold]/config[/]")
582
615
  else:
583
616
  name, *rest = arg.split()
584
617
  eff = rest[0].lower() if rest else None
@@ -220,6 +220,7 @@ class OpenAILLM:
220
220
  # the SDK's own (cleaner) timeout fires first when it can, and SIGALRM only catches the stalls
221
221
  # the read-timeout misses (silent mid-response connections).
222
222
  self._hard_timeout = max(int(timeout) + 15, 30)
223
+ self._timeout = timeout # kept for live endpoint switches (switch())
223
224
  # No built-in default model — the user picks (parallels the CLI's model gate; a silent
224
225
  # fallback here would contradict it for library/embedding callers).
225
226
  self.model = model or os.environ.get("AGENT_MODEL") or ""
@@ -259,11 +260,32 @@ class OpenAILLM:
259
260
  # thereafter reasoning_effort is dropped when tools are present (graceful degrade, no re-400).
260
261
  self._drop_reasoning_effort = False
261
262
 
262
- def switch(self, *, model: str | None = None, reasoning: str | None = None) -> None:
263
- """Live-switch the model id and/or reasoning intent for SUBSEQUENT turns (mutates in place — the
264
- loop passes this same llm object every turn, so the change applies from the next turn on). Resets
265
- the reasoning_effort+tools degrade memory since a different model may support the pairing. Same
266
- endpoint/client; switching to a DIFFERENT PROVIDER (base_url/key) is `sliceagent config --use`."""
263
+ def switch(self, *, model: str | None = None, reasoning: str | None = None,
264
+ base_url: str | None = None, api_key: str | None = None) -> None:
265
+ """Live-switch model / reasoning and, when `base_url`/`api_key` are given, the PROVIDER too:
266
+ the client is rebuilt against the new endpoint (with the proxy re-chosen for it), so /model can
267
+ hop between configured providers in one action. Mutates in place — the loop passes this same llm
268
+ object every turn, so the change applies from the next turn on. base_url semantics: None = keep
269
+ the current endpoint; "" = the SDK's default endpoint (OpenAI). Resets the
270
+ reasoning_effort+tools degrade memory (a different model/provider may support the pairing)."""
271
+ if base_url is not None or api_key:
272
+ import httpx
273
+ from openai import OpenAI
274
+ new_base = self._base_url if base_url is None else base_url
275
+ new_key = api_key or getattr(self.client, "api_key", None)
276
+ proxy = _choose_proxy(new_base, os.environ.get("AGENT_PROXY")
277
+ or os.environ.get("HTTPS_PROXY") or os.environ.get("HTTP_PROXY"))
278
+ use_proxy = bool(proxy) and proxy != "none"
279
+ self.proxy_used = proxy if use_proxy else "direct"
280
+ timeout = getattr(self, "_timeout", 60.0)
281
+ http_client = (httpx.Client(proxy=proxy, timeout=timeout) if use_proxy
282
+ else httpx.Client(timeout=timeout))
283
+ ckw: dict = {"api_key": new_key}
284
+ if new_base:
285
+ ckw["base_url"] = new_base
286
+ self.client = OpenAI(http_client=http_client, timeout=timeout, max_retries=2, **ckw)
287
+ self._base_url = new_base or ""
288
+ self._drop_reasoning_effort = False
267
289
  if model:
268
290
  self.model = model
269
291
  self._drop_reasoning_effort = False
@@ -824,25 +824,41 @@ def run_selector(title, rows, *, current=-1, hint="↑↓ move · Enter select
824
824
 
825
825
 
826
826
  def _model_candidates(llm, cfg):
827
- """Models to offer in the /model menu: the current model + any configured providers' models + a known
828
- set, deduped and grouped by inferred provider family. Returns [(model, family)] sorted by family."""
827
+ """Models to offer in the /model menu CONFIGURED providers only (the user's clear-journey rule:
828
+ the switcher shows what you can actually use). Each configured provider (has an api_key)
829
+ contributes its saved model + its wizard suggestions, labeled with the provider id; picking one
830
+ lets the CLI switch model+endpoint+key together. Returns [(model, group_label, provider_id)].
831
+ provider_id None = the current env-configured model (no provider table to rebind to). Fallback
832
+ when NO providers are configured (pure env setup): current model + a small known set."""
829
833
  from .model_catalog import capability
830
- known = ["gpt-5.5", "gpt-5", "gpt-5-mini", "o3", "deepseek-chat", "kimi-k2-0905-preview", "claude-sonnet-4-6"]
831
- prov = []
832
834
  try:
833
- for tbl in (cfg.providers() or {}).values():
834
- m = tbl.get("model") if isinstance(tbl, dict) else None
835
- if m:
836
- prov.append(m)
835
+ provs = {pid: t for pid, t in (cfg.providers() or {}).items()
836
+ if isinstance(t, dict) and t.get("api_key")}
837
837
  except Exception: # noqa: BLE001 — a malformed providers table must not break the menu
838
- pass
838
+ provs = {}
839
839
  out, seen = [], set()
840
- for m in [llm.model] + prov + known:
841
- if m and m not in seen:
842
- seen.add(m)
843
- base = getattr(llm, "_base_url", "") if m == llm.model else ""
844
- out.append((m, capability(m, base).family))
845
- out.sort(key=lambda mf: (mf[1], mf[0]))
840
+ if provs:
841
+ try:
842
+ from .onboarding import MODEL_SUGGESTIONS
843
+ except Exception: # noqa: BLE001
844
+ MODEL_SUGGESTIONS = {}
845
+ for pid, tbl in provs.items():
846
+ for m in [tbl.get("model")] + list(MODEL_SUGGESTIONS.get(pid, [])):
847
+ if m and (pid, m) not in seen:
848
+ seen.add((pid, m))
849
+ out.append((m, pid, pid))
850
+ out.sort(key=lambda t: (t[1], t[0]))
851
+ if all(m != llm.model for m, _, _ in out): # an env-overridden current model still shows first
852
+ out.insert(0, (llm.model, "current (env)", None)) # not a configured provider — label honestly
853
+ else:
854
+ known = ["gpt-5.5", "gpt-5", "gpt-5-mini", "o3", "deepseek-chat", "kimi-k2-0905-preview",
855
+ "claude-sonnet-5"]
856
+ for m in [llm.model] + known:
857
+ if m and m not in seen:
858
+ seen.add(m)
859
+ base = getattr(llm, "_base_url", "") if m == llm.model else ""
860
+ out.append((m, capability(m, base).family, None))
861
+ out.sort(key=lambda t: (t[1], t[0]))
846
862
  return out
847
863
 
848
864
 
@@ -860,17 +876,18 @@ def _reasoning_levels(model, base_url):
860
876
 
861
877
 
862
878
  def select_model_reasoning(llm, cfg, *, pt_input=None, pt_output=None):
863
- """Two-tier picker: choose a model (grouped by provider) then its reasoning level (only the levels that
864
- model supports). Returns (model, reasoning) to apply, or None if the model step was cancelled."""
879
+ """Two-tier picker: choose a model (from CONFIGURED providers) then its reasoning level (only the
880
+ levels that model supports). Returns (model, reasoning, provider_id) provider_id is the configured
881
+ provider to switch endpoint+key to (None = keep the current endpoint) — or None if cancelled."""
865
882
  cands = _model_candidates(llm, cfg)
866
- rows = [(m, f"provider: {fam}") for m, fam in cands]
867
- cur_idx = next((i for i, (m, _) in enumerate(cands) if m == llm.model), -1)
883
+ rows = [(m, f"provider: {grp}") for m, grp, _pid in cands]
884
+ cur_idx = next((i for i, (m, _, _) in enumerate(cands) if m == llm.model), -1)
868
885
  pick = run_selector("Select model", rows, current=cur_idx,
869
886
  hint="↑↓ move · Enter choose model → reasoning · Esc cancel",
870
887
  pt_input=pt_input, pt_output=pt_output)
871
888
  if pick is None:
872
889
  return None
873
- model = cands[pick][0]
890
+ model, _grp, pid = cands[pick]
874
891
  base = getattr(llm, "_base_url", "") if model == llm.model else ""
875
892
  levels = _reasoning_levels(model, base)
876
893
  lvl_rows = [(name, desc) for name, desc in levels]
@@ -879,12 +896,13 @@ def select_model_reasoning(llm, cfg, *, pt_input=None, pt_output=None):
879
896
  hint="↑↓ move · Enter select · Esc keep current",
880
897
  pt_input=pt_input, pt_output=pt_output)
881
898
  reasoning = levels[lpick][0] if lpick is not None else llm.reasoning # Esc on step 2 = keep current
882
- return (model, reasoning)
899
+ return (model, reasoning, pid)
883
900
 
884
901
 
885
902
  # ── input layer (prompt_toolkit) ─────────────────────────────────────────────────────────────
886
903
  _SLASH = {
887
- "/model": "switch model + reasoning opens a menu (or /model <name> [fast|full|high|max])",
904
+ "/config": "add / update LLM providers (the setup wizard, in-session) — then /model to switch",
905
+ "/model": "switch model + reasoning — menu lists YOUR configured providers (switches endpoint too)",
888
906
  "/mode": "permission mode — opens a menu (baby-sitter · teenager · let-it-go)",
889
907
  "/cwd": "switch workspace root (/cwd <path>) — re-roots repo map, file tools & commands",
890
908
  "/learn": "turn what you just did into a reusable SKILL (/learn [name])",
@@ -816,11 +816,11 @@ def model_menu_is_provider_aware():
816
816
  def providers(self):
817
817
  return {}
818
818
 
819
- cands = t._model_candidates(_LLM(), _CFG())
820
- models = [m for m, _ in cands]
819
+ cands = t._model_candidates(_LLM(), _CFG()) # 3-tuples since the configured-only journey
820
+ models = [m for m, _grp, _pid in cands]
821
821
  assert "deepseek-chat" in models and "gpt-5.5" in models # current + known both present
822
822
  assert len(models) == len(set(models)) # deduped
823
- fams = [fam for _, fam in cands]
823
+ fams = [fam for _, fam, _pid in cands]
824
824
  assert fams == sorted(fams) # grouped (sorted) by provider family
825
825
 
826
826
 
@@ -1337,7 +1337,7 @@ def model_switch_warns_when_the_endpoint_cant_serve_it():
1337
1337
  self.model, self._base_url, self.reasoning = model, base_url, reasoning
1338
1338
 
1339
1339
  note = _reasoning_note(LLM("gpt-5.5", "https://api.deepseek.com/v1", "full"))
1340
- assert "OpenAI" in note and "deepseek" in note.lower() and "init" in note, note
1340
+ assert "OpenAI" in note and "deepseek" in note.lower() and "/config" in note, note
1341
1341
  # same provider, no mismatch → must stay silent (no false positive)
1342
1342
  assert _reasoning_note(LLM("deepseek-chat", "https://api.deepseek.com/v1", "full")) == ""
1343
1343
  # real OpenAI → completely unaffected
@@ -0,0 +1,119 @@
1
+ """The clear config journey (user-designed): /config manages providers in-session, and /model shows
2
+ ONLY configured providers' models — picking one switches model + endpoint + key together.
3
+ Offline: stub objects, no network, no pytest. Run: PYTHONPATH=src python tests/test_config_journey.py
4
+ """
5
+ import os
6
+ import sys
7
+
8
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))
9
+
10
+ from sliceagent.config import Config # noqa: E402
11
+ from sliceagent.llm import OpenAILLM # noqa: E402
12
+ from sliceagent.tui import _SLASH, _model_candidates, select_model_reasoning # noqa: E402
13
+
14
+ CHECKS = []
15
+ def check(fn):
16
+ CHECKS.append(fn)
17
+ return fn
18
+
19
+
20
+ class _Llm:
21
+ model = "deepseek-chat"
22
+ _base_url = "https://api.deepseek.com/v1"
23
+ reasoning = "full"
24
+
25
+
26
+ _CFG = Config({"providers": {
27
+ "deepseek": {"api_key": "k1", "base_url": "https://api.deepseek.com/v1", "model": "deepseek-chat"},
28
+ "openrouter": {"api_key": "k2", "base_url": "https://openrouter.ai/api/v1", "model": "openai/gpt-5.5"},
29
+ "keyless": {"model": "phantom-model"}, # no api_key → NOT usable → must not appear
30
+ }})
31
+
32
+
33
+ @check
34
+ def model_menu_lists_only_configured_providers():
35
+ cands = _model_candidates(_Llm(), _CFG)
36
+ pids = {pid for _, _, pid in cands if pid}
37
+ models = [m for m, _, _ in cands]
38
+ assert pids == {"deepseek", "openrouter"}, f"only keyed providers may appear, got {pids}"
39
+ assert "phantom-model" not in models, "a provider without a key must not contribute models"
40
+ assert "deepseek-chat" in models and "openai/gpt-5.5" in models
41
+ assert "anthropic/claude-sonnet-5" in models, "configured providers contribute their suggestions too"
42
+
43
+
44
+ @check
45
+ def every_candidate_carries_its_provider_for_endpoint_switch():
46
+ for m, _grp, pid in _model_candidates(_Llm(), _CFG):
47
+ assert pid in ("deepseek", "openrouter"), f"{m} must map to a configured provider, got {pid!r}"
48
+
49
+
50
+ @check
51
+ def env_current_model_is_labeled_honestly_not_as_a_provider():
52
+ class _EnvLlm: # env model NOT in any configured provider's list
53
+ model = "gpt-5.5"
54
+ _base_url = "https://api.deepseek.com/v1" # deepseek endpoint must NOT make it say "deepseek"
55
+ reasoning = "full"
56
+ cands = _model_candidates(_EnvLlm(), _CFG)
57
+ m, grp, pid = cands[0]
58
+ assert m == "gpt-5.5" and pid is None
59
+ assert grp == "current (env)", f"env model must not masquerade as a configured provider, got {grp!r}"
60
+
61
+
62
+ @check
63
+ def no_providers_configured_falls_back_to_known_set():
64
+ cands = _model_candidates(_Llm(), Config({}))
65
+ assert cands, "env-only setups still need a menu"
66
+ assert all(pid is None for _, _, pid in cands), "fallback entries have no provider to rebind to"
67
+ assert any(m == "deepseek-chat" for m, _, _ in cands)
68
+
69
+
70
+ @check
71
+ def switch_rebinds_endpoint_and_key():
72
+ saved = {k: os.environ.pop(k, None) for k in ("AGENT_PROXY", "HTTPS_PROXY", "HTTP_PROXY")}
73
+ try:
74
+ llm = OpenAILLM(model="deepseek-chat", api_key="k1", base_url="https://api.deepseek.com/v1")
75
+ assert llm._base_url == "https://api.deepseek.com/v1"
76
+ llm.switch(model="openai/gpt-5.5", base_url="https://openrouter.ai/api/v1", api_key="k2")
77
+ assert llm.model == "openai/gpt-5.5"
78
+ assert llm._base_url == "https://openrouter.ai/api/v1"
79
+ assert str(llm.client.base_url).startswith("https://openrouter.ai/api/v1"), \
80
+ "the OpenAI client itself must be rebuilt against the new endpoint"
81
+ assert llm.client.api_key == "k2", "the key must follow the provider"
82
+ # base_url="" → SDK default endpoint (OpenAI)
83
+ llm.switch(model="gpt-5.5", base_url="", api_key="k3")
84
+ assert llm._base_url == "" and "openrouter" not in str(llm.client.base_url)
85
+ # model-only switch keeps the endpoint (the classic /model <name> path)
86
+ llm.switch(model="gpt-5")
87
+ assert llm.model == "gpt-5" and llm.client.api_key == "k3"
88
+ finally:
89
+ for k, v in saved.items():
90
+ if v is not None:
91
+ os.environ[k] = v
92
+
93
+
94
+ @check
95
+ def selector_returns_the_provider_triple():
96
+ from prompt_toolkit.input.defaults import create_pipe_input
97
+ from prompt_toolkit.output import DummyOutput
98
+ with create_pipe_input() as pinp:
99
+ pinp.send_text("\r\r") # Enter (model) + Enter (reasoning)
100
+ got = select_model_reasoning(_Llm(), _CFG, pt_input=pinp, pt_output=DummyOutput())
101
+ assert got is not None and len(got) == 3, f"expected (model, reasoning, pid), got {got!r}"
102
+ model, _reasoning, pid = got
103
+ assert pid in ("deepseek", "openrouter") and isinstance(model, str)
104
+
105
+
106
+ @check
107
+ def config_command_is_in_the_slash_palette():
108
+ assert "/config" in _SLASH, "/config must be discoverable in the palette"
109
+
110
+
111
+ if __name__ == "__main__":
112
+ ok = 0
113
+ for fn in CHECKS:
114
+ try:
115
+ fn(); ok += 1; print(f"PASS {fn.__name__}")
116
+ except Exception as e: # noqa: BLE001
117
+ print(f"FAIL {fn.__name__}: {type(e).__name__}: {e}")
118
+ print(f"\n{ok}/{len(CHECKS)} passed")
119
+ sys.exit(0 if ok == len(CHECKS) else 1)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes