kon-coding-agent 0.3.9__tar.gz → 0.3.10__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 (175) hide show
  1. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/CHANGELOG.md +17 -0
  2. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/PKG-INFO +1 -2
  3. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/pyproject.toml +1 -2
  4. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/ui/app.py +22 -7
  5. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/ui/blocks.py +9 -27
  6. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/ui/input.py +17 -5
  7. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/ui/styles.py +27 -28
  8. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/ui/widgets.py +1 -1
  9. kon_coding_agent-0.3.10/tests/tools/test_read_image_resize.py +39 -0
  10. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/tests/ui/test_tool_output_expansion.py +22 -0
  11. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/uv.lock +1 -21
  12. kon_coding_agent-0.3.9/src/kon/ui/terminal_image.py +0 -34
  13. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/.github/workflows/test.yml +0 -0
  14. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/.gitignore +0 -0
  15. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/.kon/skills/kon-release-publish/SKILL.md +0 -0
  16. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/.kon/skills/kon-tmux-test/SKILL.md +0 -0
  17. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/.kon/skills/kon-tmux-test/run-e2e-tests.sh +0 -0
  18. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/.kon/skills/kon-tmux-test/setup-test-project.sh +0 -0
  19. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/.python-version +0 -0
  20. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/AGENTS.md +0 -0
  21. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/LICENSE +0 -0
  22. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/README.md +0 -0
  23. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/docs/e2e-test-coverage-review.md +0 -0
  24. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/docs/images/kon-screenshot.png +0 -0
  25. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/docs/local-models.md +0 -0
  26. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/scripts/show_themes.py +0 -0
  27. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/__init__.py +0 -0
  28. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/async_utils.py +0 -0
  29. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/builtin_skills/init/SKILL.md +0 -0
  30. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/config.py +0 -0
  31. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/context/__init__.py +0 -0
  32. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/context/_xml.py +0 -0
  33. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/context/agent_mds.py +0 -0
  34. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/context/git.py +0 -0
  35. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/context/loader.py +0 -0
  36. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/context/skills.py +0 -0
  37. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/core/__init__.py +0 -0
  38. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/core/compaction.py +0 -0
  39. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/core/handoff.py +0 -0
  40. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/core/types.py +0 -0
  41. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/defaults/__init__.py +0 -0
  42. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/defaults/config.toml +0 -0
  43. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/events.py +0 -0
  44. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/git_branch.py +0 -0
  45. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/llm/__init__.py +0 -0
  46. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/llm/base.py +0 -0
  47. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/llm/models.py +0 -0
  48. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/llm/oauth/__init__.py +0 -0
  49. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/llm/oauth/copilot.py +0 -0
  50. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/llm/oauth/openai.py +0 -0
  51. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/llm/providers/__init__.py +0 -0
  52. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/llm/providers/anthropic.py +0 -0
  53. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/llm/providers/azure_ai_foundry.py +0 -0
  54. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/llm/providers/copilot.py +0 -0
  55. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/llm/providers/copilot_anthropic.py +0 -0
  56. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/llm/providers/github_copilot_headers.py +0 -0
  57. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/llm/providers/mock.py +0 -0
  58. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/llm/providers/openai_codex_responses.py +0 -0
  59. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/llm/providers/openai_compat.py +0 -0
  60. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/llm/providers/openai_completions.py +0 -0
  61. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/llm/providers/openai_responses.py +0 -0
  62. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/llm/providers/sanitize.py +0 -0
  63. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/loop.py +0 -0
  64. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/notify.py +0 -0
  65. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/permissions.py +0 -0
  66. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/py.typed +0 -0
  67. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/runtime.py +0 -0
  68. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/session.py +0 -0
  69. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/sounds/completion.wav +0 -0
  70. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/sounds/error.wav +0 -0
  71. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/sounds/permission.wav +0 -0
  72. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/themes.py +0 -0
  73. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/tools/__init__.py +0 -0
  74. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/tools/_read_image.py +0 -0
  75. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/tools/_tool_utils.py +0 -0
  76. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/tools/base.py +0 -0
  77. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/tools/bash.py +0 -0
  78. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/tools/edit.py +0 -0
  79. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/tools/find.py +0 -0
  80. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/tools/grep.py +0 -0
  81. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/tools/read.py +0 -0
  82. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/tools/web_fetch.py +0 -0
  83. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/tools/web_search.py +0 -0
  84. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/tools/write.py +0 -0
  85. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/tools_manager.py +0 -0
  86. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/turn.py +0 -0
  87. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/ui/__init__.py +0 -0
  88. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/ui/app_protocol.py +0 -0
  89. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/ui/autocomplete.py +0 -0
  90. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/ui/chat.py +0 -0
  91. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/ui/clipboard.py +0 -0
  92. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/ui/commands.py +0 -0
  93. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/ui/export.py +0 -0
  94. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/ui/floating_list.py +0 -0
  95. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/ui/formatting.py +0 -0
  96. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/ui/latex.py +0 -0
  97. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/ui/path_complete.py +0 -0
  98. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/ui/prompt_history.py +0 -0
  99. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/ui/selection_mode.py +0 -0
  100. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/ui/session_ui.py +0 -0
  101. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/ui/tool_output.py +0 -0
  102. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/ui/tree.py +0 -0
  103. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/ui/welcome.py +0 -0
  104. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/src/kon/update_check.py +0 -0
  105. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/tests/conftest.py +0 -0
  106. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/tests/context/test_agents.py +0 -0
  107. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/tests/context/test_skills.py +0 -0
  108. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/tests/llm/__init__.py +0 -0
  109. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/tests/llm/test_anthropic_provider.py +0 -0
  110. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/tests/llm/test_azure_ai_foundry_provider.py +0 -0
  111. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/tests/llm/test_mock_provider.py +0 -0
  112. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/tests/llm/test_openai_codex_provider_errors.py +0 -0
  113. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/tests/llm/test_openai_oauth.py +0 -0
  114. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/tests/test_agentic_loop.py +0 -0
  115. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/tests/test_cli_auth_flags.py +0 -0
  116. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/tests/test_cli_provider_resolution.py +0 -0
  117. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/tests/test_compaction.py +0 -0
  118. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/tests/test_config_binaries.py +0 -0
  119. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/tests/test_config_error_fallback.py +0 -0
  120. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/tests/test_config_injection.py +0 -0
  121. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/tests/test_config_migration.py +0 -0
  122. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/tests/test_git_branch.py +0 -0
  123. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/tests/test_handoff.py +0 -0
  124. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/tests/test_handoff_link_interrupt.py +0 -0
  125. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/tests/test_launch_warnings.py +0 -0
  126. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/tests/test_llm_lazy_imports.py +0 -0
  127. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/tests/test_local_auth_config.py +0 -0
  128. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/tests/test_model_provider_resolution.py +0 -0
  129. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/tests/test_notifications_config.py +0 -0
  130. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/tests/test_notify.py +0 -0
  131. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/tests/test_openai_compat.py +0 -0
  132. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/tests/test_permissions.py +0 -0
  133. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/tests/test_runtime_switch_model.py +0 -0
  134. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/tests/test_session_persistence.py +0 -0
  135. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/tests/test_session_queries.py +0 -0
  136. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/tests/test_session_resume.py +0 -0
  137. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/tests/test_session_tree.py +0 -0
  138. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/tests/test_system_prompt.py +0 -0
  139. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/tests/test_system_prompt_git_context.py +0 -0
  140. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/tests/test_themes.py +0 -0
  141. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/tests/test_tools_manager.py +0 -0
  142. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/tests/test_ui_notifications.py +0 -0
  143. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/tests/test_update_check.py +0 -0
  144. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/tests/test_update_notice_behavior.py +0 -0
  145. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/tests/tools/test_bash_truncation.py +0 -0
  146. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/tests/tools/test_diff.py +0 -0
  147. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/tests/tools/test_edit.py +0 -0
  148. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/tests/tools/test_edit_display.py +0 -0
  149. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/tests/tools/test_read.py +0 -0
  150. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/tests/tools/test_read_image.py +0 -0
  151. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/tests/tools/test_read_image_integration.py +0 -0
  152. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/tests/tools/test_subprocess_cancellation.py +0 -0
  153. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/tests/tools/test_web_fetch.py +0 -0
  154. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/tests/tools/test_write.py +0 -0
  155. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/tests/ui/test_app_approval_keys.py +0 -0
  156. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/tests/ui/test_autocomplete.py +0 -0
  157. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/tests/ui/test_floating_list.py +0 -0
  158. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/tests/ui/test_info_bar_clicks.py +0 -0
  159. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/tests/ui/test_info_bar_permissions.py +0 -0
  160. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/tests/ui/test_input_approval_submit.py +0 -0
  161. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/tests/ui/test_input_cursor_theme.py +0 -0
  162. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/tests/ui/test_input_handoff.py +0 -0
  163. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/tests/ui/test_input_paste.py +0 -0
  164. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/tests/ui/test_input_shell_style.py +0 -0
  165. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/tests/ui/test_keybindings.py +0 -0
  166. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/tests/ui/test_latex.py +0 -0
  167. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/tests/ui/test_permission_selection_status.py +0 -0
  168. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/tests/ui/test_permissions_command.py +0 -0
  169. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/tests/ui/test_prompt_history.py +0 -0
  170. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/tests/ui/test_queue_editing.py +0 -0
  171. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/tests/ui/test_shell_command_detection.py +0 -0
  172. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/tests/ui/test_status_line.py +0 -0
  173. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/tests/ui/test_streaming_blocks.py +0 -0
  174. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/tests/ui/test_styles.py +0 -0
  175. {kon_coding_agent-0.3.9 → kon_coding_agent-0.3.10}/tests/ui/test_thinking_notifications_commands.py +0 -0
@@ -6,6 +6,23 @@ All notable changes to this project will be documented in this file.
6
6
 
7
7
  - No changes yet.
8
8
 
9
+ ## 0.3.10 - 2026-05-21
10
+
11
+ ### Added
12
+
13
+ - Increased completion popup rows from 5 to 8.
14
+
15
+ ### Changed
16
+
17
+ - Redesigned the editor to match user message styling with panel user background and prefix.
18
+ - Reordered the info bar to show provider, model, and thinking state.
19
+
20
+ ### Fixed
21
+
22
+ - Stabilized completion popup scrolling.
23
+ - Reverted textual image display to avoid freezing issues.
24
+ - Corrected the image resize test expectation.
25
+
9
26
  ## 0.3.9 - 2026-05-20
10
27
 
11
28
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kon-coding-agent
3
- Version: 0.3.9
3
+ Version: 0.3.10
4
4
  Summary: Minimal coding agent
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
@@ -15,7 +15,6 @@ Requires-Dist: pillow>=12.1.1
15
15
  Requires-Dist: pydantic>=2.12.5
16
16
  Requires-Dist: readability-lxml>=0.8.4
17
17
  Requires-Dist: rich>=14.3.2
18
- Requires-Dist: textual-image[textual]>=0.12.0
19
18
  Requires-Dist: textual>=8.0.0
20
19
  Description-Content-Type: text/markdown
21
20
 
@@ -14,7 +14,7 @@ default = true
14
14
 
15
15
  [project]
16
16
  name = "kon-coding-agent"
17
- version = "0.3.9"
17
+ version = "0.3.10"
18
18
  description = "Minimal coding agent"
19
19
  readme = "README.md"
20
20
  requires-python = ">=3.12"
@@ -31,7 +31,6 @@ dependencies = [
31
31
  "readability-lxml>=0.8.4",
32
32
  "rich>=14.3.2",
33
33
  "textual>=8.0.0",
34
- "textual-image[textual]>=0.12.0",
35
34
  ]
36
35
 
37
36
  [dependency-groups]
@@ -23,7 +23,7 @@ from ..context.skills import (
23
23
  merge_registered_skills,
24
24
  render_skill_prompt,
25
25
  )
26
- from ..core.types import ImageContent, StopReason
26
+ from ..core.types import StopReason
27
27
  from ..events import (
28
28
  AgentEndEvent,
29
29
  AgentStartEvent,
@@ -86,7 +86,7 @@ _CHANGELOG_URL = "https://github.com/0xku/kon/blob/main/CHANGELOG.md"
86
86
  try:
87
87
  VERSION = version(_PYPI_PACKAGE_NAME)
88
88
  except PackageNotFoundError:
89
- VERSION = "0.3.9"
89
+ VERSION = "0.3.10"
90
90
 
91
91
  _NOTIFY_EVENTS = (AgentEndEvent, ToolApprovalEvent)
92
92
  _GIT_BRANCH_REFRESH_INTERVAL_SECONDS = 1.0
@@ -214,7 +214,7 @@ class Kon(CommandsMixin, SessionUIMixin, App[None]):
214
214
  yield QueueDisplay(id="queue-display")
215
215
  yield StatusLine(id="status-line")
216
216
  yield InputBox(cwd=self._cwd, id="input-box")
217
- yield FloatingList(window_size=5, label_width=12, id="completion-list")
217
+ yield FloatingList(window_size=8, label_width=12, id="completion-list")
218
218
  yield TreeSelector(id="tree-selector")
219
219
  yield InfoBar(
220
220
  cwd=self._cwd,
@@ -490,21 +490,38 @@ class Kon(CommandsMixin, SessionUIMixin, App[None]):
490
490
  # Completion message handlers
491
491
  # -------------------------------------------------------------------------
492
492
 
493
+ def _is_chat_at_bottom(self) -> bool:
494
+ chat = self.query_one("#chat-log", ChatLog)
495
+ return abs(chat.max_scroll_y - chat.scroll_y) < 1
496
+
497
+ def _restore_chat_scroll_if_needed(self, was_at_bottom: bool) -> None:
498
+ # The completion list is a normal grid row, not a true overlay. Showing or
499
+ # hiding it changes the available height for ChatLog. When the chat is
500
+ # already bottom-aligned, that resize can leave the viewport briefly at an
501
+ # intermediate scroll offset and cause a visible flicker. Restore the
502
+ # bottom scroll position after Textual has applied the layout change.
503
+ if was_at_bottom:
504
+ chat = self.query_one("#chat-log", ChatLog)
505
+ chat.scroll_end(animate=False)
506
+
493
507
  @on(InputBox.CompletionUpdate)
494
508
  def on_completion_update(self, event: InputBox.CompletionUpdate) -> None:
495
509
  if self._selection_mode is not None:
496
510
  return
497
511
 
498
512
  completion_list = self.query_one("#completion-list", FloatingList)
513
+ was_at_bottom = self._is_chat_at_bottom()
499
514
  if completion_list.is_visible:
500
515
  completion_list.update_items(event.items)
501
516
  else:
502
517
  completion_list.show(event.items)
518
+ self.call_after_refresh(lambda: self._restore_chat_scroll_if_needed(was_at_bottom))
503
519
 
504
520
  @on(InputBox.CompletionHide)
505
521
  def on_completion_hide(self, event: InputBox.CompletionHide) -> None:
506
522
  completion_list = self.query_one("#completion-list", FloatingList)
507
523
  input_box = self.query_one("#input-box", InputBox)
524
+ was_at_bottom = self._is_chat_at_bottom()
508
525
 
509
526
  with self.batch_update():
510
527
  completion_list.hide()
@@ -523,6 +540,8 @@ class Kon(CommandsMixin, SessionUIMixin, App[None]):
523
540
 
524
541
  input_box.set_completing(False)
525
542
 
543
+ self.call_after_refresh(lambda: self._restore_chat_scroll_if_needed(was_at_bottom))
544
+
526
545
  @on(InputBox.CompletionSelect)
527
546
  def on_completion_select(self, event: InputBox.CompletionSelect) -> None:
528
547
  input_box = self.query_one("#input-box", InputBox)
@@ -1176,9 +1195,6 @@ class Kon(CommandsMixin, SessionUIMixin, App[None]):
1176
1195
  if ui_summary is None and ui_details is None and r.content:
1177
1196
  ui_details, ui_details_full = self._format_tool_result_text(r)
1178
1197
  success = not r.is_error
1179
- images = [
1180
- part for part in r.content if isinstance(part, ImageContent)
1181
- ]
1182
1198
  chat.set_tool_result(
1183
1199
  id,
1184
1200
  ui_summary,
@@ -1186,7 +1202,6 @@ class Kon(CommandsMixin, SessionUIMixin, App[None]):
1186
1202
  success,
1187
1203
  markup=markup,
1188
1204
  ui_details_full=ui_details_full,
1189
- images=images or None,
1190
1205
  )
1191
1206
  if fc:
1192
1207
  info_bar.update_file_changes(fc.path, fc.added, fc.removed)
@@ -14,7 +14,6 @@ from kon.core.types import ImageContent
14
14
  from kon.permissions import ApprovalResponse
15
15
 
16
16
  from .formatting import format_markdown, strip_markdown_for_collapsed_text
17
- from .terminal_image import image_content_to_pil, image_fallback
18
17
 
19
18
  _UPDATE_COMMAND = "uv tool upgrade kon-coding-agent"
20
19
 
@@ -277,7 +276,6 @@ class ToolBlock(Static):
277
276
  def compose(self) -> ComposeResult:
278
277
  yield Label(self._format_header(), id="tool-header")
279
278
  yield Label("", id="tool-output", classes="tool-output -hidden")
280
- yield Static(id="tool-images", classes="tool-images -hidden")
281
279
 
282
280
  def _format_header(self, truncate: bool = True) -> Text:
283
281
  colors = config.ui.colors
@@ -453,7 +451,6 @@ class ToolBlock(Static):
453
451
  self._awaiting_approval = False
454
452
  self._set_state(success)
455
453
  self._render_result_output()
456
- self._render_images()
457
454
  self.query_one("#tool-header", Label).update(self._format_header())
458
455
 
459
456
  def set_expanded(self, expanded: bool) -> None:
@@ -478,35 +475,21 @@ class ToolBlock(Static):
478
475
  output.remove_class("-hidden")
479
476
  output.remove_class("-details")
480
477
  output.update(rendered)
478
+ elif self._images:
479
+ image_count = len(self._images)
480
+ image_label = "image" if image_count == 1 else "images"
481
+ rendered = Text(f"Attached {image_count} {image_label}", style=config.ui.colors.dim)
482
+ self.remove_class("-compact")
483
+ self.add_class("-with-details")
484
+ output.remove_class("-hidden")
485
+ output.remove_class("-details")
486
+ output.update(rendered)
481
487
  else:
482
488
  output.update(Text(""))
483
489
  self.remove_class("-with-details")
484
490
  output.remove_class("-details")
485
491
  output.add_class("-hidden")
486
492
 
487
- def _render_images(self) -> None:
488
- container = self.query_one("#tool-images", Static)
489
- container.remove_children()
490
-
491
- if not self._images:
492
- container.add_class("-hidden")
493
- return
494
-
495
- from textual_image.widget import Image as TextualImage
496
-
497
- self.remove_class("-compact")
498
- self.add_class("-with-details")
499
- container.remove_class("-hidden")
500
-
501
- for image in self._images:
502
- try:
503
- widget = TextualImage(image_content_to_pil(image), classes="tool-image")
504
- widget.styles.width = 60
505
- widget.styles.height = "auto"
506
- container.mount(widget)
507
- except Exception:
508
- container.mount(Label(image_fallback(image), classes="tool-image-fallback"))
509
-
510
493
 
511
494
  class UserBlock(Static):
512
495
  ALLOW_SELECT = True
@@ -526,7 +509,6 @@ class UserBlock(Static):
526
509
  text.append(self._content)
527
510
  stylize_badge_markers(text, [f"[{self._highlighted_skill}]", "[query]"])
528
511
  else:
529
- text.append("> ", style="bold")
530
512
  text.append(self._content)
531
513
 
532
514
  yield Label(text)
@@ -11,9 +11,9 @@ from textual import events
11
11
  from textual._ansi_sequences import ANSI_SEQUENCES_KEYS
12
12
  from textual.app import ComposeResult
13
13
  from textual.binding import Binding
14
- from textual.containers import Vertical
14
+ from textual.containers import Horizontal, Vertical
15
15
  from textual.message import Message
16
- from textual.widgets import TextArea
16
+ from textual.widgets import Label, TextArea
17
17
  from textual.widgets.text_area import TextAreaTheme
18
18
 
19
19
  from kon import config
@@ -131,14 +131,24 @@ class InputBox(Vertical):
131
131
  height: auto;
132
132
  min-height: 3;
133
133
  max-height: 30vh;
134
- border-top: solid grey;
135
- border-bottom: solid grey;
134
+ border-top: solid transparent;
135
+ border-bottom: solid transparent;
136
136
  border-title-align: left;
137
137
  border-subtitle-align: left;
138
138
  border-title-color: grey;
139
139
  border-subtitle-color: grey;
140
140
  }
141
141
 
142
+ #input-row {
143
+ height: auto;
144
+ }
145
+
146
+ #input-prefix {
147
+ width: auto;
148
+ padding: 0 0 0 1;
149
+ text-style: bold;
150
+ }
151
+
142
152
  InputBox .input-textarea {
143
153
  width: 1fr;
144
154
  height: auto;
@@ -186,7 +196,9 @@ class InputBox(Vertical):
186
196
  self._selected_skill_commands: list[str] = []
187
197
 
188
198
  def compose(self) -> ComposeResult:
189
- yield Kon(self._transform_paste, id="input-textarea", classes="input-textarea")
199
+ with Horizontal(id="input-row"):
200
+ yield Label("\u203a", id="input-prefix")
201
+ yield Kon(self._transform_paste, id="input-textarea", classes="input-textarea")
190
202
 
191
203
  def on_mount(self) -> None:
192
204
  textarea = self.query_one("#input-textarea", TextArea)
@@ -17,6 +17,10 @@ def _blend_hex(base: str, overlay: str, overlay_weight: float) -> str:
17
17
  def get_styles() -> str:
18
18
  colors = config.ui.colors
19
19
  approval_bg = _blend_hex(colors.bg, colors.accent, overlay_weight=0.05)
20
+ shell_bg = _blend_hex(colors.panel_user, colors.success, overlay_weight=0.15)
21
+ thinking_medium_bg = _blend_hex(colors.panel_user, colors.accent, overlay_weight=0.08)
22
+ thinking_high_bg = _blend_hex(colors.panel_user, colors.accent, overlay_weight=0.18)
23
+ thinking_xhigh_bg = _blend_hex(colors.panel_user, colors.badge.label, overlay_weight=0.18)
20
24
 
21
25
  return f"""
22
26
  Screen {{
@@ -147,22 +151,6 @@ Screen {{
147
151
  padding: 0 0 0 2;
148
152
  }}
149
153
 
150
- #tool-images,
151
- .tool-images {{
152
- color: {colors.dim};
153
- padding: 1 0 0 0;
154
- }}
155
-
156
- #tool-images.-hidden {{
157
- display: none;
158
- height: 0;
159
- }}
160
-
161
- .tool-image {{
162
- width: 60;
163
- height: auto;
164
- }}
165
-
166
154
  .tool-block.-with-details {{
167
155
  padding: 0 1;
168
156
  }}
@@ -240,37 +228,48 @@ Screen {{
240
228
 
241
229
  /* Input area */
242
230
  #input-box {{
243
- border-top: solid {colors.border};
244
- border-bottom: solid {colors.border};
231
+ background: {colors.panel_user};
232
+ border-top: solid {colors.panel_user};
233
+ border-bottom: solid {colors.panel_user};
245
234
  border-title-color: {colors.dim};
246
235
  border-subtitle-color: {colors.dim};
247
236
  }}
248
237
 
238
+ #input-prefix {{
239
+ color: {colors.fg};
240
+ text-style: bold;
241
+ }}
242
+
249
243
  #input-box.-thinking-none,
250
244
  #input-box.-thinking-minimal,
251
245
  #input-box.-thinking-low {{
252
- border-top: solid {colors.border};
253
- border-bottom: solid {colors.border};
246
+ background: {colors.panel_user};
247
+ border-top: solid {colors.panel_user};
248
+ border-bottom: solid {colors.panel_user};
254
249
  }}
255
250
 
256
251
  #input-box.-thinking-medium {{
257
- border-top: solid {colors.dim};
258
- border-bottom: solid {colors.dim};
252
+ background: {thinking_medium_bg};
253
+ border-top: solid {thinking_medium_bg};
254
+ border-bottom: solid {thinking_medium_bg};
259
255
  }}
260
256
 
261
257
  #input-box.-thinking-high {{
262
- border-top: solid {colors.accent};
263
- border-bottom: solid {colors.accent};
258
+ background: {thinking_high_bg};
259
+ border-top: solid {thinking_high_bg};
260
+ border-bottom: solid {thinking_high_bg};
264
261
  }}
265
262
 
266
263
  #input-box.-thinking-xhigh {{
267
- border-top: solid {colors.badge.label};
268
- border-bottom: solid {colors.badge.label};
264
+ background: {thinking_xhigh_bg};
265
+ border-top: solid {thinking_xhigh_bg};
266
+ border-bottom: solid {thinking_xhigh_bg};
269
267
  }}
270
268
 
271
269
  #input-box.-shell-command {{
272
- border-top: solid {colors.success};
273
- border-bottom: solid {colors.success};
270
+ background: {shell_bg};
271
+ border-top: solid {shell_bg};
272
+ border-bottom: solid {shell_bg};
274
273
  }}
275
274
 
276
275
  #input-box.-shell-command .input-textarea {{
@@ -241,7 +241,7 @@ class InfoBar(Vertical):
241
241
  def _format_row2_right(self) -> Text:
242
242
  model_text = self._model
243
243
  if self._model_provider:
244
- model_text = f"{self._model} ({self._model_provider})"
244
+ model_text = f"({self._model_provider}) {self._model}"
245
245
  result = Text(model_text)
246
246
  result.append(f" • {self._thinking_level}")
247
247
  return result
@@ -0,0 +1,39 @@
1
+ import base64
2
+ import io
3
+
4
+ from PIL import Image
5
+
6
+ from kon.tools._read_image import MAX_DIMENSION, read_and_process_image
7
+
8
+
9
+ def _write_image(path, size: tuple[int, int], color: str = "red") -> None:
10
+ img = Image.new("RGB", size, color=color)
11
+ img.save(path)
12
+
13
+
14
+ def _decoded_size(base64_data: str) -> tuple[int, int]:
15
+ data = base64.b64decode(base64_data)
16
+ with Image.open(io.BytesIO(data)) as img:
17
+ return img.size
18
+
19
+
20
+ def test_read_image_downsizes_large_images(tmp_path):
21
+ image_path = tmp_path / "large.png"
22
+ _write_image(image_path, (2520, 842))
23
+
24
+ base64_data, mime_type, resize_note = read_and_process_image(str(image_path))
25
+
26
+ assert mime_type == "image/png"
27
+ assert _decoded_size(base64_data) == (MAX_DIMENSION, 668)
28
+ assert resize_note == f"[{MAX_DIMENSION}x668, resized from 2520x842]"
29
+
30
+
31
+ def test_read_image_keeps_small_images(tmp_path):
32
+ image_path = tmp_path / "small.png"
33
+ _write_image(image_path, (100, 100))
34
+
35
+ base64_data, mime_type, resize_note = read_and_process_image(str(image_path))
36
+
37
+ assert mime_type == "image/png"
38
+ assert _decoded_size(base64_data) == (100, 100)
39
+ assert resize_note == "[100x100]"
@@ -3,6 +3,7 @@ from rich.text import Text
3
3
  from textual.app import App, ComposeResult
4
4
  from textual.widgets import Label
5
5
 
6
+ from kon.core.types import ImageContent
6
7
  from kon.ui.blocks import ToolBlock
7
8
  from kon.ui.chat import ChatLog
8
9
  from kon.ui.tool_output import format_expand_hint, truncate_tool_output_text
@@ -65,3 +66,24 @@ async def test_start_tool_uses_expanded_state_before_mount():
65
66
 
66
67
  assert block._expanded is True
67
68
  assert block.query_one("#tool-output", Label)
69
+
70
+
71
+ @pytest.mark.asyncio
72
+ async def test_tool_result_with_images_renders_textual_attachment_notice():
73
+ async with ToolExpansionTestApp().run_test() as pilot:
74
+ chat = pilot.app.query_one("#chat-log", ChatLog)
75
+ block = chat.start_tool("read", "tool-1", "~/image.png")
76
+ await pilot.pause()
77
+
78
+ chat.set_tool_result(
79
+ "tool-1",
80
+ "[dim]Read image[/dim]",
81
+ None,
82
+ True,
83
+ images=[ImageContent(data="base64data", mime_type="image/png")],
84
+ )
85
+ await pilot.pause()
86
+
87
+ output = block.query_one("#tool-output", Label)
88
+ assert str(output.render()) == "Attached 1 image"
89
+ assert not output.has_class("-hidden")
@@ -759,7 +759,7 @@ wheels = [
759
759
 
760
760
  [[package]]
761
761
  name = "kon-coding-agent"
762
- version = "0.3.9"
762
+ version = "0.3.10"
763
763
  source = { editable = "." }
764
764
  dependencies = [
765
765
  { name = "aiofiles" },
@@ -774,7 +774,6 @@ dependencies = [
774
774
  { name = "readability-lxml" },
775
775
  { name = "rich" },
776
776
  { name = "textual" },
777
- { name = "textual-image", extra = ["textual"] },
778
777
  ]
779
778
 
780
779
  [package.dev-dependencies]
@@ -800,7 +799,6 @@ requires-dist = [
800
799
  { name = "readability-lxml", specifier = ">=0.8.4" },
801
800
  { name = "rich", specifier = ">=14.3.2" },
802
801
  { name = "textual", specifier = ">=8.0.0" },
803
- { name = "textual-image", extras = ["textual"], specifier = ">=0.12.0" },
804
802
  ]
805
803
 
806
804
  [package.metadata.requires-dev]
@@ -1643,24 +1641,6 @@ wheels = [
1643
1641
  { url = "https://files.pythonhosted.org/packages/d3/be/e191c2a15da20530fde03564564e3e4b4220eb9d687d4014957e5c6a5e85/textual-8.0.0-py3-none-any.whl", hash = "sha256:8908f4ebe93a6b4f77ca7262197784a52162bc88b05f4ecf50ac93a92d49bb8f", size = 718904, upload-time = "2026-02-16T17:12:11.962Z" },
1644
1642
  ]
1645
1643
 
1646
- [[package]]
1647
- name = "textual-image"
1648
- version = "0.12.0"
1649
- source = { registry = "https://pypi.org/simple" }
1650
- dependencies = [
1651
- { name = "pillow" },
1652
- { name = "rich" },
1653
- ]
1654
- sdist = { url = "https://files.pythonhosted.org/packages/c2/e7/c82ea0604874b6d51d5717a0911061ae5810e36dad2e4d2b11fa7d54cdaa/textual_image-0.12.0.tar.gz", hash = "sha256:fdd0b5ff9c8a99740bc360a99ce014d563fa97d07a5b49b472470809f57c0a74", size = 116403, upload-time = "2026-04-12T17:37:44.696Z" }
1655
- wheels = [
1656
- { url = "https://files.pythonhosted.org/packages/67/2c/38ac586a3834a3bd8cbcf0c8ea1ae23bf68b9cc13b96a67f47a825479dd3/textual_image-0.12.0-py3-none-any.whl", hash = "sha256:0b13f62fe6a29f4ed6dfd641f2779ffe92dde41f16163fa1de69158477aa50aa", size = 115626, upload-time = "2026-04-12T17:37:41.238Z" },
1657
- ]
1658
-
1659
- [package.optional-dependencies]
1660
- textual = [
1661
- { name = "textual" },
1662
- ]
1663
-
1664
1644
  [[package]]
1665
1645
  name = "tqdm"
1666
1646
  version = "4.67.3"
@@ -1,34 +0,0 @@
1
- import base64
2
- import io
3
- from dataclasses import dataclass
4
-
5
- from PIL import Image as PILImage
6
-
7
- from ..core.types import ImageContent
8
-
9
-
10
- @dataclass(frozen=True)
11
- class ImageDimensions:
12
- width: int
13
- height: int
14
-
15
-
16
- def image_content_to_pil(image: ImageContent) -> PILImage.Image:
17
- data = base64.b64decode(image.data)
18
- return PILImage.open(io.BytesIO(data))
19
-
20
-
21
- def get_image_dimensions(image: ImageContent) -> ImageDimensions | None:
22
- try:
23
- with image_content_to_pil(image) as img:
24
- width, height = img.size
25
- return ImageDimensions(width=width, height=height)
26
- except Exception:
27
- return None
28
-
29
-
30
- def image_fallback(image: ImageContent) -> str:
31
- dimensions = get_image_dimensions(image)
32
- if dimensions is None:
33
- return f"[Image: {image.mime_type}]"
34
- return f"[Image: {image.mime_type} {dimensions.width}x{dimensions.height}]"