limbo-code 0.2__tar.gz → 0.2.2__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 (203) hide show
  1. {limbo_code-0.2 → limbo_code-0.2.2}/PKG-INFO +1 -1
  2. {limbo_code-0.2 → limbo_code-0.2.2}/src/limbo/llm/catalog.py +17 -2
  3. {limbo_code-0.2 → limbo_code-0.2.2}/src/limbo/ui/app.tcss +2 -1
  4. {limbo_code-0.2 → limbo_code-0.2.2}/src/limbo/ui/banner.py +24 -0
  5. {limbo_code-0.2 → limbo_code-0.2.2}/src/limbo/ui/contrast.py +4 -0
  6. {limbo_code-0.2 → limbo_code-0.2.2}/src/limbo/ui/theme.py +8 -0
  7. {limbo_code-0.2 → limbo_code-0.2.2}/src/limbo/ui/widgets/chat.py +11 -2
  8. {limbo_code-0.2 → limbo_code-0.2.2}/tests/test_catalog.py +30 -1
  9. limbo_code-0.2.2/tests/ui/__snapshots__/test_snapshots/test_snapshot_thinking_and_running_tool.raw +175 -0
  10. limbo_code-0.2.2/tests/ui/__snapshots__/test_snapshots/test_snapshot_tool_error_and_error_message.raw +176 -0
  11. {limbo_code-0.2 → limbo_code-0.2.2}/tests/ui/test_startup_art.py +4 -1
  12. {limbo_code-0.2 → limbo_code-0.2.2}/tests/ui/test_theme.py +2 -0
  13. limbo_code-0.2/tests/ui/__snapshots__/test_snapshots/test_snapshot_thinking_and_running_tool.raw +0 -174
  14. limbo_code-0.2/tests/ui/__snapshots__/test_snapshots/test_snapshot_tool_error_and_error_message.raw +0 -175
  15. {limbo_code-0.2 → limbo_code-0.2.2}/.agents/skills/grill-with-docs/SKILL.md +0 -0
  16. {limbo_code-0.2 → limbo_code-0.2.2}/.agents/skills/grill-with-docs/agents/openai.yaml +0 -0
  17. {limbo_code-0.2 → limbo_code-0.2.2}/.agents/skills/improve-codebase-architecture/HTML-REPORT.md +0 -0
  18. {limbo_code-0.2 → limbo_code-0.2.2}/.agents/skills/improve-codebase-architecture/SKILL.md +0 -0
  19. {limbo_code-0.2 → limbo_code-0.2.2}/.agents/skills/improve-codebase-architecture/agents/openai.yaml +0 -0
  20. {limbo_code-0.2 → limbo_code-0.2.2}/.agents/skills/tdd/SKILL.md +0 -0
  21. {limbo_code-0.2 → limbo_code-0.2.2}/.agents/skills/tdd/agents/openai.yaml +0 -0
  22. {limbo_code-0.2 → limbo_code-0.2.2}/.agents/skills/tdd/mocking.md +0 -0
  23. {limbo_code-0.2 → limbo_code-0.2.2}/.agents/skills/tdd/tests.md +0 -0
  24. {limbo_code-0.2 → limbo_code-0.2.2}/.github/workflows/publish.yml +0 -0
  25. {limbo_code-0.2 → limbo_code-0.2.2}/.github/workflows/test.yml +0 -0
  26. {limbo_code-0.2 → limbo_code-0.2.2}/.gitignore +0 -0
  27. {limbo_code-0.2 → limbo_code-0.2.2}/AGENTS.md +0 -0
  28. {limbo_code-0.2 → limbo_code-0.2.2}/CONTEXT.md +0 -0
  29. {limbo_code-0.2 → limbo_code-0.2.2}/Makefile +0 -0
  30. {limbo_code-0.2 → limbo_code-0.2.2}/README.md +0 -0
  31. {limbo_code-0.2 → limbo_code-0.2.2}/design/bang-verification.md +0 -0
  32. {limbo_code-0.2 → limbo_code-0.2.2}/design/confirm_view.html +0 -0
  33. {limbo_code-0.2 → limbo_code-0.2.2}/design/limbo-ui-minimal.md +0 -0
  34. {limbo_code-0.2 → limbo_code-0.2.2}/design/limbo-ui-redesign.md +0 -0
  35. {limbo_code-0.2 → limbo_code-0.2.2}/design/prototype-minimal-confirm.png +0 -0
  36. {limbo_code-0.2 → limbo_code-0.2.2}/design/prototype-minimal.html +0 -0
  37. {limbo_code-0.2 → limbo_code-0.2.2}/design/prototype-minimal.png +0 -0
  38. {limbo_code-0.2 → limbo_code-0.2.2}/design/prototype.html +0 -0
  39. {limbo_code-0.2 → limbo_code-0.2.2}/design/prototype.png +0 -0
  40. {limbo_code-0.2 → limbo_code-0.2.2}/design/rfc-bang-command.md +0 -0
  41. {limbo_code-0.2 → limbo_code-0.2.2}/design/rfc-headless-cli.md +0 -0
  42. {limbo_code-0.2 → limbo_code-0.2.2}/docs/assets/limbo-current-ui.png +0 -0
  43. {limbo_code-0.2 → limbo_code-0.2.2}/docs/assets/limbo-new-ui.png +0 -0
  44. {limbo_code-0.2 → limbo_code-0.2.2}/docs/assets/walkthrough/limbo-dark/1_idle.svg +0 -0
  45. {limbo_code-0.2 → limbo_code-0.2.2}/docs/assets/walkthrough/limbo-dark/2_thinking_tool_running.svg +0 -0
  46. {limbo_code-0.2 → limbo_code-0.2.2}/docs/assets/walkthrough/limbo-dark/3_tool_success.svg +0 -0
  47. {limbo_code-0.2 → limbo_code-0.2.2}/docs/assets/walkthrough/limbo-dark/4_tool_error.svg +0 -0
  48. {limbo_code-0.2 → limbo_code-0.2.2}/docs/assets/walkthrough/limbo-dark/5_llm_error.svg +0 -0
  49. {limbo_code-0.2 → limbo_code-0.2.2}/docs/assets/walkthrough/limbo-dark/6_edit_diff.svg +0 -0
  50. {limbo_code-0.2 → limbo_code-0.2.2}/docs/assets/walkthrough/limbo-light/1_idle.svg +0 -0
  51. {limbo_code-0.2 → limbo_code-0.2.2}/docs/assets/walkthrough/limbo-light/2_thinking_tool_running.svg +0 -0
  52. {limbo_code-0.2 → limbo_code-0.2.2}/docs/assets/walkthrough/limbo-light/3_tool_success.svg +0 -0
  53. {limbo_code-0.2 → limbo_code-0.2.2}/docs/assets/walkthrough/limbo-light/4_tool_error.svg +0 -0
  54. {limbo_code-0.2 → limbo_code-0.2.2}/docs/assets/walkthrough/limbo-light/5_llm_error.svg +0 -0
  55. {limbo_code-0.2 → limbo_code-0.2.2}/docs/assets/walkthrough/limbo-light/6_edit_diff.svg +0 -0
  56. {limbo_code-0.2 → limbo_code-0.2.2}/docs/session-management.md +0 -0
  57. {limbo_code-0.2 → limbo_code-0.2.2}/docs/skills.md +0 -0
  58. {limbo_code-0.2 → limbo_code-0.2.2}/docs/ui-redesign-proposal.md +0 -0
  59. {limbo_code-0.2 → limbo_code-0.2.2}/pyproject.toml +0 -0
  60. {limbo_code-0.2 → limbo_code-0.2.2}/scripts/check_contrast.py +0 -0
  61. {limbo_code-0.2 → limbo_code-0.2.2}/scripts/gen_banner.py +0 -0
  62. {limbo_code-0.2 → limbo_code-0.2.2}/scripts/scroll_bench.py +0 -0
  63. {limbo_code-0.2 → limbo_code-0.2.2}/scripts/ui_walkthrough.py +0 -0
  64. {limbo_code-0.2 → limbo_code-0.2.2}/skills-lock.json +0 -0
  65. {limbo_code-0.2 → limbo_code-0.2.2}/src/limbo/__init__.py +0 -0
  66. {limbo_code-0.2 → limbo_code-0.2.2}/src/limbo/__main__.py +0 -0
  67. {limbo_code-0.2 → limbo_code-0.2.2}/src/limbo/agent.py +0 -0
  68. {limbo_code-0.2 → limbo_code-0.2.2}/src/limbo/app.py +0 -0
  69. {limbo_code-0.2 → limbo_code-0.2.2}/src/limbo/attachments.py +0 -0
  70. {limbo_code-0.2 → limbo_code-0.2.2}/src/limbo/btw.py +0 -0
  71. {limbo_code-0.2 → limbo_code-0.2.2}/src/limbo/code_mode.py +0 -0
  72. {limbo_code-0.2 → limbo_code-0.2.2}/src/limbo/compaction.py +0 -0
  73. {limbo_code-0.2 → limbo_code-0.2.2}/src/limbo/config.py +0 -0
  74. {limbo_code-0.2 → limbo_code-0.2.2}/src/limbo/goal.py +0 -0
  75. {limbo_code-0.2 → limbo_code-0.2.2}/src/limbo/headless.py +0 -0
  76. {limbo_code-0.2 → limbo_code-0.2.2}/src/limbo/history.py +0 -0
  77. {limbo_code-0.2 → limbo_code-0.2.2}/src/limbo/integrations/__init__.py +0 -0
  78. {limbo_code-0.2 → limbo_code-0.2.2}/src/limbo/integrations/base.py +0 -0
  79. {limbo_code-0.2 → limbo_code-0.2.2}/src/limbo/integrations/herdr.py +0 -0
  80. {limbo_code-0.2 → limbo_code-0.2.2}/src/limbo/llm/__init__.py +0 -0
  81. {limbo_code-0.2 → limbo_code-0.2.2}/src/limbo/llm/anthropic_client.py +0 -0
  82. {limbo_code-0.2 → limbo_code-0.2.2}/src/limbo/llm/client.py +0 -0
  83. {limbo_code-0.2 → limbo_code-0.2.2}/src/limbo/llm/factory.py +0 -0
  84. {limbo_code-0.2 → limbo_code-0.2.2}/src/limbo/llm/openai_client.py +0 -0
  85. {limbo_code-0.2 → limbo_code-0.2.2}/src/limbo/llm/responses_client.py +0 -0
  86. {limbo_code-0.2 → limbo_code-0.2.2}/src/limbo/llm/retry.py +0 -0
  87. {limbo_code-0.2 → limbo_code-0.2.2}/src/limbo/llm/scaffold.py +0 -0
  88. {limbo_code-0.2 → limbo_code-0.2.2}/src/limbo/llm/sse.py +0 -0
  89. {limbo_code-0.2 → limbo_code-0.2.2}/src/limbo/llm/usage.py +0 -0
  90. {limbo_code-0.2 → limbo_code-0.2.2}/src/limbo/model_switch.py +0 -0
  91. {limbo_code-0.2 → limbo_code-0.2.2}/src/limbo/models.py +0 -0
  92. {limbo_code-0.2 → limbo_code-0.2.2}/src/limbo/prompt.py +0 -0
  93. {limbo_code-0.2 → limbo_code-0.2.2}/src/limbo/pump.py +0 -0
  94. {limbo_code-0.2 → limbo_code-0.2.2}/src/limbo/sessions.py +0 -0
  95. {limbo_code-0.2 → limbo_code-0.2.2}/src/limbo/skills.py +0 -0
  96. {limbo_code-0.2 → limbo_code-0.2.2}/src/limbo/steer.py +0 -0
  97. {limbo_code-0.2 → limbo_code-0.2.2}/src/limbo/tools/__init__.py +0 -0
  98. {limbo_code-0.2 → limbo_code-0.2.2}/src/limbo/tools/base.py +0 -0
  99. {limbo_code-0.2 → limbo_code-0.2.2}/src/limbo/tools/bash.py +0 -0
  100. {limbo_code-0.2 → limbo_code-0.2.2}/src/limbo/tools/edit.py +0 -0
  101. {limbo_code-0.2 → limbo_code-0.2.2}/src/limbo/tools/find.py +0 -0
  102. {limbo_code-0.2 → limbo_code-0.2.2}/src/limbo/tools/grep.py +0 -0
  103. {limbo_code-0.2 → limbo_code-0.2.2}/src/limbo/tools/ignore.py +0 -0
  104. {limbo_code-0.2 → limbo_code-0.2.2}/src/limbo/tools/ls.py +0 -0
  105. {limbo_code-0.2 → limbo_code-0.2.2}/src/limbo/tools/mutation_queue.py +0 -0
  106. {limbo_code-0.2 → limbo_code-0.2.2}/src/limbo/tools/read.py +0 -0
  107. {limbo_code-0.2 → limbo_code-0.2.2}/src/limbo/tools/registry.py +0 -0
  108. {limbo_code-0.2 → limbo_code-0.2.2}/src/limbo/tools/run_code.py +0 -0
  109. {limbo_code-0.2 → limbo_code-0.2.2}/src/limbo/tools/write.py +0 -0
  110. {limbo_code-0.2 → limbo_code-0.2.2}/src/limbo/trace.py +0 -0
  111. {limbo_code-0.2 → limbo_code-0.2.2}/src/limbo/ui/__init__.py +0 -0
  112. {limbo_code-0.2 → limbo_code-0.2.2}/src/limbo/ui/app.py +0 -0
  113. {limbo_code-0.2 → limbo_code-0.2.2}/src/limbo/ui/clipboard.py +0 -0
  114. {limbo_code-0.2 → limbo_code-0.2.2}/src/limbo/ui/commands.py +0 -0
  115. {limbo_code-0.2 → limbo_code-0.2.2}/src/limbo/ui/key_fixes.py +0 -0
  116. {limbo_code-0.2 → limbo_code-0.2.2}/src/limbo/ui/paste_store.py +0 -0
  117. {limbo_code-0.2 → limbo_code-0.2.2}/src/limbo/ui/path_input.py +0 -0
  118. {limbo_code-0.2 → limbo_code-0.2.2}/src/limbo/ui/screens/__init__.py +0 -0
  119. {limbo_code-0.2 → limbo_code-0.2.2}/src/limbo/ui/screens/btw.py +0 -0
  120. {limbo_code-0.2 → limbo_code-0.2.2}/src/limbo/ui/screens/game2048.py +0 -0
  121. {limbo_code-0.2 → limbo_code-0.2.2}/src/limbo/ui/screens/main.py +0 -0
  122. {limbo_code-0.2 → limbo_code-0.2.2}/src/limbo/ui/screens/model_picker.py +0 -0
  123. {limbo_code-0.2 → limbo_code-0.2.2}/src/limbo/ui/screens/session_picker.py +0 -0
  124. {limbo_code-0.2 → limbo_code-0.2.2}/src/limbo/ui/screens/verify_picker.py +0 -0
  125. {limbo_code-0.2 → limbo_code-0.2.2}/src/limbo/ui/syntax.py +0 -0
  126. {limbo_code-0.2 → limbo_code-0.2.2}/src/limbo/ui/widgets/__init__.py +0 -0
  127. {limbo_code-0.2 → limbo_code-0.2.2}/src/limbo/ui/widgets/command_menu.py +0 -0
  128. {limbo_code-0.2 → limbo_code-0.2.2}/src/limbo/ui/widgets/input.py +0 -0
  129. {limbo_code-0.2 → limbo_code-0.2.2}/src/limbo/ui/widgets/status_bar.py +0 -0
  130. {limbo_code-0.2 → limbo_code-0.2.2}/src/limbo/ui/widgets/tool_card.py +0 -0
  131. {limbo_code-0.2 → limbo_code-0.2.2}/tests/conftest.py +0 -0
  132. {limbo_code-0.2 → limbo_code-0.2.2}/tests/test_agent.py +0 -0
  133. {limbo_code-0.2 → limbo_code-0.2.2}/tests/test_anthropic_client.py +0 -0
  134. {limbo_code-0.2 → limbo_code-0.2.2}/tests/test_attachments.py +0 -0
  135. {limbo_code-0.2 → limbo_code-0.2.2}/tests/test_btw.py +0 -0
  136. {limbo_code-0.2 → limbo_code-0.2.2}/tests/test_cli.py +0 -0
  137. {limbo_code-0.2 → limbo_code-0.2.2}/tests/test_clipboard.py +0 -0
  138. {limbo_code-0.2 → limbo_code-0.2.2}/tests/test_code_mode.py +0 -0
  139. {limbo_code-0.2 → limbo_code-0.2.2}/tests/test_code_mode_agent.py +0 -0
  140. {limbo_code-0.2 → limbo_code-0.2.2}/tests/test_compaction.py +0 -0
  141. {limbo_code-0.2 → limbo_code-0.2.2}/tests/test_config.py +0 -0
  142. {limbo_code-0.2 → limbo_code-0.2.2}/tests/test_goal.py +0 -0
  143. {limbo_code-0.2 → limbo_code-0.2.2}/tests/test_headless.py +0 -0
  144. {limbo_code-0.2 → limbo_code-0.2.2}/tests/test_herdr.py +0 -0
  145. {limbo_code-0.2 → limbo_code-0.2.2}/tests/test_history.py +0 -0
  146. {limbo_code-0.2 → limbo_code-0.2.2}/tests/test_integration.py +0 -0
  147. {limbo_code-0.2 → limbo_code-0.2.2}/tests/test_integrations.py +0 -0
  148. {limbo_code-0.2 → limbo_code-0.2.2}/tests/test_interrupt.py +0 -0
  149. {limbo_code-0.2 → limbo_code-0.2.2}/tests/test_llm_client.py +0 -0
  150. {limbo_code-0.2 → limbo_code-0.2.2}/tests/test_llm_scaffold.py +0 -0
  151. {limbo_code-0.2 → limbo_code-0.2.2}/tests/test_model_switch.py +0 -0
  152. {limbo_code-0.2 → limbo_code-0.2.2}/tests/test_models.py +0 -0
  153. {limbo_code-0.2 → limbo_code-0.2.2}/tests/test_prompt.py +0 -0
  154. {limbo_code-0.2 → limbo_code-0.2.2}/tests/test_pump.py +0 -0
  155. {limbo_code-0.2 → limbo_code-0.2.2}/tests/test_responses_client.py +0 -0
  156. {limbo_code-0.2 → limbo_code-0.2.2}/tests/test_retry.py +0 -0
  157. {limbo_code-0.2 → limbo_code-0.2.2}/tests/test_sessions.py +0 -0
  158. {limbo_code-0.2 → limbo_code-0.2.2}/tests/test_skills.py +0 -0
  159. {limbo_code-0.2 → limbo_code-0.2.2}/tests/test_sse.py +0 -0
  160. {limbo_code-0.2 → limbo_code-0.2.2}/tests/test_steer.py +0 -0
  161. {limbo_code-0.2 → limbo_code-0.2.2}/tests/test_trace.py +0 -0
  162. {limbo_code-0.2 → limbo_code-0.2.2}/tests/test_usage.py +0 -0
  163. {limbo_code-0.2 → limbo_code-0.2.2}/tests/tools/test_base.py +0 -0
  164. {limbo_code-0.2 → limbo_code-0.2.2}/tests/tools/test_bash.py +0 -0
  165. {limbo_code-0.2 → limbo_code-0.2.2}/tests/tools/test_edit.py +0 -0
  166. {limbo_code-0.2 → limbo_code-0.2.2}/tests/tools/test_find.py +0 -0
  167. {limbo_code-0.2 → limbo_code-0.2.2}/tests/tools/test_grep.py +0 -0
  168. {limbo_code-0.2 → limbo_code-0.2.2}/tests/tools/test_ignore.py +0 -0
  169. {limbo_code-0.2 → limbo_code-0.2.2}/tests/tools/test_ls.py +0 -0
  170. {limbo_code-0.2 → limbo_code-0.2.2}/tests/tools/test_mutation_queue.py +0 -0
  171. {limbo_code-0.2 → limbo_code-0.2.2}/tests/tools/test_read.py +0 -0
  172. {limbo_code-0.2 → limbo_code-0.2.2}/tests/tools/test_registry.py +0 -0
  173. {limbo_code-0.2 → limbo_code-0.2.2}/tests/tools/test_run_code.py +0 -0
  174. {limbo_code-0.2 → limbo_code-0.2.2}/tests/tools/test_write.py +0 -0
  175. {limbo_code-0.2 → limbo_code-0.2.2}/tests/ui/__snapshots__/test_snapshots/test_snapshot_idle.raw +0 -0
  176. {limbo_code-0.2 → limbo_code-0.2.2}/tests/ui/__snapshots__/test_snapshots/test_snapshot_session_picker.raw +0 -0
  177. {limbo_code-0.2 → limbo_code-0.2.2}/tests/ui/test_app_smoke.py +0 -0
  178. {limbo_code-0.2 → limbo_code-0.2.2}/tests/ui/test_bang_ui.py +0 -0
  179. {limbo_code-0.2 → limbo_code-0.2.2}/tests/ui/test_btw_ui.py +0 -0
  180. {limbo_code-0.2 → limbo_code-0.2.2}/tests/ui/test_code_mode_ui.py +0 -0
  181. {limbo_code-0.2 → limbo_code-0.2.2}/tests/ui/test_command_menu.py +0 -0
  182. {limbo_code-0.2 → limbo_code-0.2.2}/tests/ui/test_commands.py +0 -0
  183. {limbo_code-0.2 → limbo_code-0.2.2}/tests/ui/test_compact_ui.py +0 -0
  184. {limbo_code-0.2 → limbo_code-0.2.2}/tests/ui/test_contrast.py +0 -0
  185. {limbo_code-0.2 → limbo_code-0.2.2}/tests/ui/test_copy.py +0 -0
  186. {limbo_code-0.2 → limbo_code-0.2.2}/tests/ui/test_game2048.py +0 -0
  187. {limbo_code-0.2 → limbo_code-0.2.2}/tests/ui/test_goal_ui.py +0 -0
  188. {limbo_code-0.2 → limbo_code-0.2.2}/tests/ui/test_input_attachments.py +0 -0
  189. {limbo_code-0.2 → limbo_code-0.2.2}/tests/ui/test_input_history.py +0 -0
  190. {limbo_code-0.2 → limbo_code-0.2.2}/tests/ui/test_input_paste.py +0 -0
  191. {limbo_code-0.2 → limbo_code-0.2.2}/tests/ui/test_interrupt_ui.py +0 -0
  192. {limbo_code-0.2 → limbo_code-0.2.2}/tests/ui/test_key_fixes.py +0 -0
  193. {limbo_code-0.2 → limbo_code-0.2.2}/tests/ui/test_main_screen.py +0 -0
  194. {limbo_code-0.2 → limbo_code-0.2.2}/tests/ui/test_model_picker.py +0 -0
  195. {limbo_code-0.2 → limbo_code-0.2.2}/tests/ui/test_paste_store.py +0 -0
  196. {limbo_code-0.2 → limbo_code-0.2.2}/tests/ui/test_path_input.py +0 -0
  197. {limbo_code-0.2 → limbo_code-0.2.2}/tests/ui/test_scroll_follow.py +0 -0
  198. {limbo_code-0.2 → limbo_code-0.2.2}/tests/ui/test_sessions_ui.py +0 -0
  199. {limbo_code-0.2 → limbo_code-0.2.2}/tests/ui/test_skills_ui.py +0 -0
  200. {limbo_code-0.2 → limbo_code-0.2.2}/tests/ui/test_snapshots.py +0 -0
  201. {limbo_code-0.2 → limbo_code-0.2.2}/tests/ui/test_steer_ui.py +0 -0
  202. {limbo_code-0.2 → limbo_code-0.2.2}/tests/ui/test_widgets.py +0 -0
  203. {limbo_code-0.2 → limbo_code-0.2.2}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: limbo-code
3
- Version: 0.2
3
+ Version: 0.2.2
4
4
  Summary: A minimal terminal AI coding agent
5
5
  Requires-Python: >=3.11
6
6
  Requires-Dist: httpx>=0.27
@@ -175,16 +175,19 @@ def _kimi_coding(
175
175
  *,
176
176
  context_window: int = 262_144,
177
177
  max_tokens: int = 32_768,
178
+ vision: bool = True,
178
179
  ) -> ModelSpec:
179
180
  # All Kimi For Coding models reason with Anthropic adaptive thinking
180
181
  # (thinking: {type: adaptive} + output_config.effort); thinking cannot
181
182
  # be disabled. Replayed thinking blocks may carry an empty signature.
183
+ # Every Kimi model is multimodal (accepts image input).
182
184
  return ModelSpec(
183
185
  id=model_id,
184
186
  provider=KIMI_CODING,
185
187
  context_window=context_window,
186
188
  max_tokens=max_tokens,
187
189
  reasoning=True,
190
+ vision=vision,
188
191
  thinking_format="anthropic-adaptive",
189
192
  thinking_levels={"low": "low", "high": "high", "max": "max"},
190
193
  thinking_can_disable=False,
@@ -197,7 +200,9 @@ def _moonshot(
197
200
  context_window: int = 262_144,
198
201
  max_tokens: int = 262_144,
199
202
  reasoning: bool = False,
200
- vision: bool = False,
203
+ # Every Kimi (Moonshot) model is multimodal: image input is supported
204
+ # across the family, so the helper defaults to vision-capable.
205
+ vision: bool = True,
201
206
  thinking_format: str | None = None,
202
207
  thinking_levels: dict[str, str] | None = None,
203
208
  thinking_can_disable: bool = True,
@@ -264,6 +269,16 @@ CATALOG: dict[str, ModelSpec] = {
264
269
  reasoning=True,
265
270
  thinking_format="deepseek",
266
271
  ),
272
+ # Vision-enabled experimental flash variant (accepts image input).
273
+ "deepseek-v4-flash-vision-exp": ModelSpec(
274
+ id="deepseek-v4-flash-vision-exp",
275
+ provider=DEEPSEEK,
276
+ context_window=1_000_000,
277
+ max_tokens=65_536,
278
+ reasoning=True,
279
+ vision=True,
280
+ thinking_format="deepseek",
281
+ ),
267
282
  # -- Kimi (Moonshot AI) -------------------------------------------------
268
283
  "kimi-k2-0711-preview": _moonshot(
269
284
  "kimi-k2-0711-preview", context_window=131_072, max_tokens=16_384
@@ -278,7 +293,7 @@ CATALOG: dict[str, ModelSpec] = {
278
293
  ),
279
294
  # Kimi K2.5 is Moonshot's multimodal generation (accepts image input).
280
295
  "kimi-k2.5": _moonshot(
281
- "kimi-k2.5", reasoning=True, thinking_format="deepseek", vision=True
296
+ "kimi-k2.5", reasoning=True, thinking_format="deepseek"
282
297
  ),
283
298
  "kimi-k2.6": _moonshot(
284
299
  "kimi-k2.6", reasoning=True, thinking_format="deepseek"
@@ -35,7 +35,8 @@ ChatWidget {
35
35
  }
36
36
  .user-message {
37
37
  width: 1fr;
38
- background: $surface;
38
+ background: $user-message-bg;
39
+ color: $user-message-fg;
39
40
  border-left: thick $primary;
40
41
  padding: 0 1;
41
42
  margin-top: 1;
@@ -115,3 +115,27 @@ def startup_art_text() -> Text:
115
115
  elif cb is not None:
116
116
  text.append(LOWER, style=_rgb(cb))
117
117
  return text
118
+
119
+
120
+ def user_icon() -> Text:
121
+ """A compact single-row mascot icon for user messages (LIM-16 v1.1).
122
+
123
+ Half-block compression of the mascot: red shirt on top, yellow pants
124
+ below, dark outline on the sides. One terminal row tall so it lines up
125
+ with the message text ("same size as the font") while keeping the
126
+ brand colors. Prepended before the ``❯`` user-message marker.
127
+
128
+ ``DRRRD`` / ``DYYYD`` -> 2 px rows condensed into 1 terminal row via
129
+ ``▀`` (top pixel as foreground, bottom pixel as background).
130
+ """
131
+ top = "DRRRD"
132
+ bottom = "DYYYD"
133
+ text = Text()
134
+ for ct_key, cb_key in zip(top, bottom):
135
+ ct = _PALETTE[ct_key]
136
+ cb = _PALETTE[cb_key]
137
+ if ct == cb:
138
+ text.append(FULL, style=_rgb(ct))
139
+ else:
140
+ text.append(UPPER, style=f"{_rgb(ct)} on {_rgb(cb)}")
141
+ return text
@@ -33,6 +33,7 @@ _FOREGROUNDS = (
33
33
  "error",
34
34
  "diff-added",
35
35
  "diff-removed",
36
+ "user-message-fg",
36
37
  )
37
38
 
38
39
  # §6.3 规范中实际使用的「文字 on 背景」组合(低于 4.5 即 FAIL)。
@@ -56,6 +57,8 @@ USED_PAIRS = frozenset(
56
57
  # F1:edit diff 的真实渲染路径(pygments Inserted/Deleted on diff 底)
57
58
  ("diff-added", "diff-added-bg"),
58
59
  ("diff-removed", "diff-removed-bg"),
60
+ # 用户消息蓝染底上的文字(新语义色,与 agent 中性前景区分)
61
+ ("user-message-fg", "user-message-bg"),
59
62
  }
60
63
  )
61
64
 
@@ -64,6 +67,7 @@ _EXTRA_PAIRS = (
64
67
  ("error-bright", "error-bg"),
65
68
  ("diff-added", "diff-added-bg"),
66
69
  ("diff-removed", "diff-removed-bg"),
70
+ ("user-message-fg", "user-message-bg"), # 用户消息蓝染底文字
67
71
  )
68
72
 
69
73
 
@@ -39,6 +39,10 @@ LIMBO_DARK = Theme(
39
39
  "text-tertiary": "#7F85A0",
40
40
  "border-subtle": "#2C3044",
41
41
  "border-default": "#3A3F57",
42
+ # 用户消息专属蓝:蓝染底 + 蓝字,与 agent 中性前景拉开辨识度
43
+ # (user-fg on user-bg = 7.26:1,on chat-bg = 7.91:1)
44
+ "user-message-bg": "#202744",
45
+ "user-message-fg": "#96B9EA",
42
46
  # 错误底块上的文字专用(on error-bg = 4.91:1)
43
47
  "error-bright": "#F06661",
44
48
  "error-bg": "#342026",
@@ -69,6 +73,10 @@ LIMBO_LIGHT = Theme(
69
73
  "text-tertiary": "#6B7186",
70
74
  "border-subtle": "#D8DCE8",
71
75
  "border-default": "#C4CADD",
76
+ # 用户消息专属蓝:淡蓝底 + 深蓝字,与 agent 中性前景拉开辨识度
77
+ # (user-fg on user-bg = 6.69:1,on chat-bg = 8.78:1)
78
+ "user-message-bg": "#DCE6F5",
79
+ "user-message-fg": "#1F4E86",
72
80
  "error-bright": "#A93230",
73
81
  "error-bg": "#F3D6D6",
74
82
  "diff-added": "#256B41",
@@ -27,6 +27,7 @@ from textual.css.query import NoMatches
27
27
  from textual.widgets import Markdown, Static
28
28
 
29
29
  from limbo.models import Attachment
30
+ from limbo.ui.banner import user_icon
30
31
  from limbo.ui.widgets.tool_card import ToolCard
31
32
 
32
33
  # How many message widgets stay mounted in the DOM. Older ones are pruned
@@ -144,7 +145,11 @@ class QueuedMessage(Vertical):
144
145
  def compose(self) -> ComposeResult:
145
146
  if self.state == "cancelled":
146
147
  self.add_class("cancelled")
147
- yield Static(f"❯ {self._text}", classes="user-message", markup=False)
148
+ yield Static(
149
+ user_icon() + Text(" ") + Text(f"❯ {self._text}"),
150
+ classes="user-message",
151
+ markup=False,
152
+ )
148
153
  if self._attachments:
149
154
  chips = []
150
155
  for attachment in self._attachments:
@@ -310,7 +315,11 @@ class ChatWidget(VerticalScroll):
310
315
  ) -> None:
311
316
  self._current_assistant = None
312
317
  self._close_thinking()
313
- msg = Static(f"❯ {text}", classes="user-message", markup=False)
318
+ msg = Static(
319
+ user_icon() + Text(" ") + Text(f"❯ {text}"),
320
+ classes="user-message",
321
+ markup=False,
322
+ )
314
323
  self.messages.append(msg)
315
324
  self._mount_and_scroll(msg)
316
325
  if attachments:
@@ -5,6 +5,7 @@ import pytest
5
5
  from limbo.config import Config, ProviderOverride
6
6
  from limbo.llm.anthropic_client import AnthropicMessagesClient
7
7
  from limbo.llm.catalog import (
8
+ CATALOG,
8
9
  DEEPSEEK,
9
10
  DEFAULT_BASE_URL,
10
11
  GENERIC_OPENAI,
@@ -298,13 +299,41 @@ def test_register_client_adds_dialect():
298
299
  def test_vision_defaults_to_false():
299
300
  # Text-only catalog entries stay non-vision (conservative default).
300
301
  assert resolve_model("deepseek-v4-flash").vision is False
301
- assert resolve_model("kimi-k3").vision is False
302
+ assert resolve_model("glm-5.2").vision is False
303
+
304
+
305
+ def test_deepseek_flash_vision_exp_is_vision_capable():
306
+ # Experimental flash variant: accepts image input, same spec as
307
+ # deepseek-v4-flash otherwise.
308
+ spec = resolve_model("deepseek-v4-flash-vision-exp")
309
+ assert spec.provider is DEEPSEEK
310
+ assert spec.vision is True
311
+ assert spec.reasoning is True
312
+ assert spec.thinking_format == "deepseek"
313
+
314
+
315
+ def test_all_kimi_models_are_vision_capable():
316
+ # Every Kimi model (Moonshot API + Kimi For Coding) is multimodal.
317
+ kimi_ids = {
318
+ m for m in CATALOG
319
+ if m.startswith("kimi-") or m in {"k3", "kimi-for-coding", "kimi-for-coding-highspeed"}
320
+ }
321
+ assert kimi_ids, "expected at least one Kimi model in the catalog"
322
+ for model_id in kimi_ids:
323
+ assert resolve_model(model_id).vision is True, model_id
302
324
 
303
325
 
304
326
  def test_kimi_k2_5_is_vision_capable():
305
327
  assert resolve_model("kimi-k2.5").vision is True
306
328
 
307
329
 
330
+ def test_kimi_k3_is_vision_capable():
331
+ # k3 (Kimi For Coding) and kimi-k3 (Moonshot) are the same multimodal
332
+ # model: both accept image input and must pass the vision gate.
333
+ assert resolve_model("kimi-k3").vision is True
334
+ assert resolve_model("k3").vision is True
335
+
336
+
308
337
  def test_unknown_model_vision_defaults_to_false():
309
338
  # GENERIC_OPENAI fallback: image attachments degrade to path references
310
339
  # rather than breaking an unrecognized endpoint with base64 payloads.