kolega-code 0.7.1__tar.gz → 0.8.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (313) hide show
  1. {kolega_code-0.7.1 → kolega_code-0.8.0}/.env.example +1 -0
  2. {kolega_code-0.7.1 → kolega_code-0.8.0}/PKG-INFO +2 -1
  3. {kolega_code-0.7.1 → kolega_code-0.8.0}/RELEASING.md +6 -0
  4. {kolega_code-0.7.1 → kolega_code-0.8.0}/docs/src/content/docs/concepts/agents.md +2 -2
  5. {kolega_code-0.7.1 → kolega_code-0.8.0}/docs/src/content/docs/concepts/tools.md +7 -4
  6. {kolega_code-0.7.1 → kolega_code-0.8.0}/docs/src/content/docs/configuration/environment-variables.md +5 -0
  7. {kolega_code-0.7.1 → kolega_code-0.8.0}/docs/src/content/docs/configuration/providers-and-models.mdx +15 -4
  8. {kolega_code-0.7.1 → kolega_code-0.8.0}/docs/src/content/docs/hooks.md +1 -0
  9. {kolega_code-0.7.1 → kolega_code-0.8.0}/docs/src/content/docs/tui/modes.md +2 -0
  10. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/__init__.py +1 -1
  11. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/__init__.py +1 -1
  12. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/baseagent.py +104 -51
  13. kolega_code-0.8.0/kolega_code/agent/compression.py +146 -0
  14. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/conversation.py +333 -21
  15. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/investigationagent.py +2 -2
  16. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/planningagent.py +3 -1
  17. kolega_code-0.8.0/kolega_code/agent/prompt_templates/auxiliary/compression/summary.system.md +28 -0
  18. kolega_code-0.8.0/kolega_code/agent/prompt_templates/auxiliary/compression/summary.user.md.j2 +17 -0
  19. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/prompt_templates/system/agents/investigation.md.j2 +1 -1
  20. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/prompt_templates/system/agents/planning.md.j2 +5 -2
  21. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/prompts.py +7 -3
  22. kolega_code-0.8.0/kolega_code/agent/tests/compaction_helpers.py +189 -0
  23. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tests/llm/test_chatgpt_oauth_provider.py +60 -0
  24. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tests/llm/test_client.py +143 -0
  25. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tests/llm/test_langfuse_normalization.py +17 -0
  26. kolega_code-0.8.0/kolega_code/agent/tests/llm/test_model_specs.py +51 -0
  27. kolega_code-0.8.0/kolega_code/agent/tests/llm/test_openai_message_conversion.py +140 -0
  28. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tests/llm/test_openai_token_counting.py +25 -13
  29. kolega_code-0.8.0/kolega_code/agent/tests/llm/test_supports_vision.py +56 -0
  30. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tests/llm/test_thinking_effort.py +26 -0
  31. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tests/test_agent_tools_inventory.py +18 -2
  32. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tests/test_base_agent.py +157 -68
  33. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tests/test_coder_attachments.py +175 -12
  34. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tests/test_commands.py +51 -5
  35. kolega_code-0.8.0/kolega_code/agent/tests/test_compaction_integration.py +66 -0
  36. kolega_code-0.8.0/kolega_code/agent/tests/test_compaction_persistence.py +45 -0
  37. kolega_code-0.8.0/kolega_code/agent/tests/test_compaction_subagent.py +26 -0
  38. kolega_code-0.8.0/kolega_code/agent/tests/test_compaction_tui_events.py +98 -0
  39. kolega_code-0.8.0/kolega_code/agent/tests/test_compress_command.py +41 -0
  40. kolega_code-0.8.0/kolega_code/agent/tests/test_compression.py +143 -0
  41. kolega_code-0.8.0/kolega_code/agent/tests/test_conversation_compaction.py +223 -0
  42. kolega_code-0.8.0/kolega_code/agent/tests/test_image_compat.py +420 -0
  43. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tests/test_planning_agent.py +26 -2
  44. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tests/test_prompts.py +3 -1
  45. kolega_code-0.8.0/kolega_code/agent/tests/test_read_image_tool.py +161 -0
  46. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tests/test_tool_registry.py +4 -1
  47. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tests/test_tools.py +7 -0
  48. kolega_code-0.8.0/kolega_code/agent/tool_backend/read_image_tool.py +35 -0
  49. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tool_backend/workflow_tool.py +3 -1
  50. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tools.py +42 -0
  51. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/utils/commands.py +26 -6
  52. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/cli/app.py +568 -52
  53. kolega_code-0.8.0/kolega_code/cli/clipboard_image.py +152 -0
  54. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/cli/main.py +20 -0
  55. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/cli/mentions.py +20 -0
  56. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/cli/messages.py +15 -0
  57. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/cli/provider_registry.py +6 -1
  58. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/cli/session_store.py +10 -2
  59. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/cli/slash_commands.py +2 -0
  60. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/cli/tests/test_app.py +1042 -49
  61. kolega_code-0.8.0/kolega_code/cli/tests/test_app_compaction_restore.py +144 -0
  62. kolega_code-0.8.0/kolega_code/cli/tests/test_app_image_vision_warning.py +563 -0
  63. kolega_code-0.8.0/kolega_code/cli/tests/test_app_model_switch_vision.py +214 -0
  64. kolega_code-0.8.0/kolega_code/cli/tests/test_clipboard_image.py +185 -0
  65. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/cli/tests/test_mentions.py +30 -0
  66. kolega_code-0.8.0/kolega_code/cli/tests/test_provider_registry.py +19 -0
  67. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/cli/tests/test_session_store.py +51 -0
  68. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/events.py +20 -0
  69. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/hooks/events.py +1 -0
  70. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/llm/instrumented_client.py +2 -2
  71. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/llm/models.py +195 -76
  72. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/llm/providers/anthropic.py +1 -1
  73. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/llm/providers/chatgpt_oauth.py +39 -2
  74. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/llm/providers/openai.py +33 -8
  75. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/llm/specs.py +130 -12
  76. kolega_code-0.8.0/kolega_code/utils/__init__.py +1 -0
  77. kolega_code-0.8.0/kolega_code/utils/images.py +140 -0
  78. kolega_code-0.8.0/kolega_code/utils/tests/__init__.py +0 -0
  79. kolega_code-0.8.0/kolega_code/utils/tests/test_images.py +90 -0
  80. kolega_code-0.8.0/kolega_code/utils/tests/test_images_ascii.py +97 -0
  81. {kolega_code-0.7.1 → kolega_code-0.8.0}/pyproject.toml +2 -1
  82. {kolega_code-0.7.1 → kolega_code-0.8.0}/uv.lock +91 -2
  83. kolega_code-0.7.1/kolega_code/agent/compression.py +0 -81
  84. kolega_code-0.7.1/kolega_code/agent/prompt_templates/auxiliary/compression/summary.system.md +0 -59
  85. kolega_code-0.7.1/kolega_code/agent/prompt_templates/auxiliary/compression/summary.user.md.j2 +0 -18
  86. kolega_code-0.7.1/kolega_code/agent/tests/llm/test_model_specs.py +0 -31
  87. kolega_code-0.7.1/kolega_code/agent/tests/llm/test_openai_message_conversion.py +0 -30
  88. {kolega_code-0.7.1 → kolega_code-0.8.0}/.github/workflows/ci.yml +0 -0
  89. {kolega_code-0.7.1 → kolega_code-0.8.0}/.github/workflows/docs.yml +0 -0
  90. {kolega_code-0.7.1 → kolega_code-0.8.0}/.github/workflows/release.yml +0 -0
  91. {kolega_code-0.7.1 → kolega_code-0.8.0}/.gitignore +0 -0
  92. {kolega_code-0.7.1 → kolega_code-0.8.0}/CONTRIBUTING.md +0 -0
  93. {kolega_code-0.7.1 → kolega_code-0.8.0}/LICENSE +0 -0
  94. {kolega_code-0.7.1 → kolega_code-0.8.0}/README.md +0 -0
  95. {kolega_code-0.7.1 → kolega_code-0.8.0}/SECURITY.md +0 -0
  96. {kolega_code-0.7.1 → kolega_code-0.8.0}/conftest.py +0 -0
  97. {kolega_code-0.7.1 → kolega_code-0.8.0}/docs/.gitignore +0 -0
  98. {kolega_code-0.7.1 → kolega_code-0.8.0}/docs/README.md +0 -0
  99. {kolega_code-0.7.1 → kolega_code-0.8.0}/docs/astro.config.mjs +0 -0
  100. {kolega_code-0.7.1 → kolega_code-0.8.0}/docs/package-lock.json +0 -0
  101. {kolega_code-0.7.1 → kolega_code-0.8.0}/docs/package.json +0 -0
  102. {kolega_code-0.7.1 → kolega_code-0.8.0}/docs/public/favicon.svg +0 -0
  103. {kolega_code-0.7.1 → kolega_code-0.8.0}/docs/src/assets/demo.gif +0 -0
  104. {kolega_code-0.7.1 → kolega_code-0.8.0}/docs/src/assets/kolega-dark.svg +0 -0
  105. {kolega_code-0.7.1 → kolega_code-0.8.0}/docs/src/assets/kolega-light.svg +0 -0
  106. {kolega_code-0.7.1 → kolega_code-0.8.0}/docs/src/content/docs/cli/ask.md +0 -0
  107. {kolega_code-0.7.1 → kolega_code-0.8.0}/docs/src/content/docs/cli/doctor.md +0 -0
  108. {kolega_code-0.7.1 → kolega_code-0.8.0}/docs/src/content/docs/cli/overview.md +0 -0
  109. {kolega_code-0.7.1 → kolega_code-0.8.0}/docs/src/content/docs/cli/sessions.md +0 -0
  110. {kolega_code-0.7.1 → kolega_code-0.8.0}/docs/src/content/docs/concepts/how-it-works.md +0 -0
  111. {kolega_code-0.7.1 → kolega_code-0.8.0}/docs/src/content/docs/configuration/settings-and-api-keys.mdx +0 -0
  112. {kolega_code-0.7.1 → kolega_code-0.8.0}/docs/src/content/docs/configuration/sign-in-with-chatgpt.mdx +0 -0
  113. {kolega_code-0.7.1 → kolega_code-0.8.0}/docs/src/content/docs/getting-started/installation.mdx +0 -0
  114. {kolega_code-0.7.1 → kolega_code-0.8.0}/docs/src/content/docs/getting-started/quick-start.mdx +0 -0
  115. {kolega_code-0.7.1 → kolega_code-0.8.0}/docs/src/content/docs/gigacode.mdx +0 -0
  116. {kolega_code-0.7.1 → kolega_code-0.8.0}/docs/src/content/docs/index.mdx +0 -0
  117. {kolega_code-0.7.1 → kolega_code-0.8.0}/docs/src/content/docs/skills.mdx +0 -0
  118. {kolega_code-0.7.1 → kolega_code-0.8.0}/docs/src/content/docs/tui/composer.md +0 -0
  119. {kolega_code-0.7.1 → kolega_code-0.8.0}/docs/src/content/docs/tui/interface.md +0 -0
  120. {kolega_code-0.7.1 → kolega_code-0.8.0}/docs/src/content/docs/tui/sessions-and-resume.md +0 -0
  121. {kolega_code-0.7.1 → kolega_code-0.8.0}/docs/src/content/docs/tui/slash-commands.md +0 -0
  122. {kolega_code-0.7.1 → kolega_code-0.8.0}/docs/src/content.config.ts +0 -0
  123. {kolega_code-0.7.1 → kolega_code-0.8.0}/docs/src/styles/brand.css +0 -0
  124. {kolega_code-0.7.1 → kolega_code-0.8.0}/docs/tsconfig.json +0 -0
  125. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/browseragent.py +0 -0
  126. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/coder.py +0 -0
  127. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/common.py +0 -0
  128. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/context.py +0 -0
  129. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/generalagent.py +0 -0
  130. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/orchestration/__init__.py +0 -0
  131. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/orchestration/budget.py +0 -0
  132. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/orchestration/errors.py +0 -0
  133. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/orchestration/executor.py +0 -0
  134. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/orchestration/guide.py +0 -0
  135. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/orchestration/journal.py +0 -0
  136. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/orchestration/runtime.py +0 -0
  137. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/orchestration/tests/__init__.py +0 -0
  138. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/orchestration/tests/test_orchestration.py +0 -0
  139. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/orchestration/types.py +0 -0
  140. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/prompt_provider.py +0 -0
  141. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/prompt_templates/auxiliary/terminal/output_summary.system.md +0 -0
  142. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/prompt_templates/auxiliary/terminal/safety.system.md +0 -0
  143. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/prompt_templates/auxiliary/tools/think_hard.system.md +0 -0
  144. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/prompt_templates/extensions/cli/planning_questions.md +0 -0
  145. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/prompt_templates/extensions/cli/shared_task_list.md +0 -0
  146. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/prompt_templates/extensions/skills/catalog.md.j2 +0 -0
  147. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/prompt_templates/system/agents/browser.md.j2 +0 -0
  148. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/prompt_templates/system/agents/coder_cli.md.j2 +0 -0
  149. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/prompt_templates/system/agents/general.md.j2 +0 -0
  150. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/prompt_templates/system/includes/agents_md_instructions.md +0 -0
  151. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/prompt_templates/system/includes/environment_variables/workspace_env_vars.md +0 -0
  152. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/prompt_templates/system/includes/frontend_guidance.md +0 -0
  153. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/prompt_templates/system/includes/starter_templates/expo.md +0 -0
  154. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/prompt_templates/system/includes/starter_templates/html_website.md +0 -0
  155. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/prompt_templates/system/includes/starter_templates/mern_stack.md +0 -0
  156. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/prompt_templates/system/includes/starter_templates/react_vite_shadcdn.md +0 -0
  157. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/prompt_templates/user_tasks/cli/implement_plan.md.j2 +0 -0
  158. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/prompt_templates/user_tasks/cli/init_agents.md.j2 +0 -0
  159. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tests/__init__.py +0 -0
  160. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tests/llm/__init__.py +0 -0
  161. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tests/llm/test_anthropic_token_counting.py +0 -0
  162. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tests/llm/test_billing_openai_cache.py +0 -0
  163. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tests/llm/test_chatgpt_live.py +0 -0
  164. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tests/llm/test_dashscope_mapping.py +0 -0
  165. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tests/llm/test_error_boundary.py +0 -0
  166. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tests/llm/test_exceptions.py +0 -0
  167. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tests/llm/test_google_thought_signature.py +0 -0
  168. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tests/llm/test_google_tool_signature_live.py +0 -0
  169. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tests/llm/test_instrumented_client.py +0 -0
  170. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tests/llm/test_instrumented_client_integration.py +0 -0
  171. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tests/llm/test_live_providers.py +0 -0
  172. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tests/llm/test_openai_cached_tokens.py +0 -0
  173. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tests/llm/test_openai_cached_tokens_stream.py +0 -0
  174. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tests/llm/test_tool_execution_ids.py +0 -0
  175. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tests/services/__init__.py +0 -0
  176. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tests/services/test_browser.py +0 -0
  177. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tests/services/test_browser_parity.py +0 -0
  178. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tests/services/test_file_system.py +0 -0
  179. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tests/services/test_sandbox_terminal_input.py +0 -0
  180. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tests/services/test_terminal.py +0 -0
  181. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tests/services/test_terminal_buffer.py +0 -0
  182. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tests/services/test_terminal_state_serializer.py +0 -0
  183. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tests/test_coder_prompt_extensions.py +0 -0
  184. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tests/test_duplicate_tool_results.py +0 -0
  185. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tests/test_empty_message_handling.py +0 -0
  186. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tests/test_event_emitter.py +0 -0
  187. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tests/test_general_agent.py +0 -0
  188. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tests/test_gigacode_gating.py +0 -0
  189. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tests/test_hooks_integration.py +0 -0
  190. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tests/test_html.py +0 -0
  191. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tests/test_parallel_tool_calls.py +0 -0
  192. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tests/test_permissions.py +0 -0
  193. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tests/test_prompt_provider.py +0 -0
  194. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tests/tool_backend/__init__.py +0 -0
  195. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tests/tool_backend/test_agent_tool.py +0 -0
  196. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tests/tool_backend/test_base_tool.py +0 -0
  197. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tests/tool_backend/test_browser_tool.py +0 -0
  198. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tests/tool_backend/test_build_tool.py +0 -0
  199. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tests/tool_backend/test_create_file_tool.py +0 -0
  200. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tests/tool_backend/test_edit_preview.py +0 -0
  201. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tests/tool_backend/test_glob_tool.py +0 -0
  202. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tests/tool_backend/test_glob_tool_sandbox_parity.py +0 -0
  203. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tests/tool_backend/test_list_directory_tool.py +0 -0
  204. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tests/tool_backend/test_read_file_tool.py +0 -0
  205. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tests/tool_backend/test_replace_entire_file_tool.py +0 -0
  206. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tests/tool_backend/test_replace_lines_tool.py +0 -0
  207. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tests/tool_backend/test_search_and_replace_tool.py +0 -0
  208. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tests/tool_backend/test_search_codebase_tool.py +0 -0
  209. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tests/tool_backend/test_terminal_tool.py +0 -0
  210. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tests/tool_backend/test_think_hard_integration.py +0 -0
  211. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tests/tool_backend/test_think_hard_streaming.py +0 -0
  212. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tests/tool_backend/test_web_fetch_tool.py +0 -0
  213. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tests/tool_backend/test_web_search_tool.py +0 -0
  214. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tests/tool_backend/test_workflow_tool.py +0 -0
  215. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tool_backend/agent_tool.py +0 -0
  216. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tool_backend/base_tool.py +0 -0
  217. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tool_backend/browser_tool.py +0 -0
  218. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tool_backend/build_tool.py +0 -0
  219. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tool_backend/create_file_tool.py +0 -0
  220. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tool_backend/edit_preview.py +0 -0
  221. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tool_backend/glob_tool.py +0 -0
  222. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tool_backend/list_directory_tool.py +0 -0
  223. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tool_backend/memory_tool.py +0 -0
  224. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tool_backend/read_file_tool.py +0 -0
  225. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tool_backend/replace_entire_file_tool.py +0 -0
  226. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tool_backend/replace_lines_tool.py +0 -0
  227. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tool_backend/search_and_replace_tool.py +0 -0
  228. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tool_backend/search_backends/__init__.py +0 -0
  229. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tool_backend/search_backends/base.py +0 -0
  230. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tool_backend/search_backends/duckduckgo.py +0 -0
  231. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tool_backend/search_backends/errors.py +0 -0
  232. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tool_backend/search_backends/firecrawl.py +0 -0
  233. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tool_backend/search_backends/models.py +0 -0
  234. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tool_backend/search_backends/searxng.py +0 -0
  235. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tool_backend/search_backends/tavily.py +0 -0
  236. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tool_backend/search_codebase_tool.py +0 -0
  237. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tool_backend/streaming_tool.py +0 -0
  238. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tool_backend/terminal_tool.py +0 -0
  239. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tool_backend/think_hard_tool.py +0 -0
  240. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tool_backend/web_fetch_tool.py +0 -0
  241. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/agent/tool_backend/web_search_tool.py +0 -0
  242. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/auth/__init__.py +0 -0
  243. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/auth/_jwt.py +0 -0
  244. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/auth/chatgpt_oauth.py +0 -0
  245. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/auth/constants.py +0 -0
  246. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/auth/tests/__init__.py +0 -0
  247. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/auth/tests/test_chatgpt_oauth.py +0 -0
  248. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/auth/tests/test_tokens.py +0 -0
  249. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/auth/tokens.py +0 -0
  250. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/cli/__init__.py +0 -0
  251. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/cli/config.py +0 -0
  252. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/cli/connection.py +0 -0
  253. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/cli/file_index.py +0 -0
  254. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/cli/settings.py +0 -0
  255. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/cli/skills.py +0 -0
  256. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/cli/tests/__init__.py +0 -0
  257. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/cli/tests/test_cli_config.py +0 -0
  258. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/cli/tests/test_connection.py +0 -0
  259. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/cli/tests/test_file_index.py +0 -0
  260. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/cli/tests/test_main.py +0 -0
  261. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/cli/tests/test_settings.py +0 -0
  262. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/cli/tests/test_skills.py +0 -0
  263. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/cli/tests/test_slash_commands.py +0 -0
  264. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/cli/tests/test_themes.py +0 -0
  265. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/cli/tests/test_updater.py +0 -0
  266. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/cli/theme.py +0 -0
  267. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/cli/updater.py +0 -0
  268. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/config.py +0 -0
  269. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/hooks/__init__.py +0 -0
  270. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/hooks/backends.py +0 -0
  271. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/hooks/config.py +0 -0
  272. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/hooks/dispatcher.py +0 -0
  273. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/hooks/matcher.py +0 -0
  274. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/hooks/outcome.py +0 -0
  275. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/hooks/tests/__init__.py +0 -0
  276. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/hooks/tests/test_hooks.py +0 -0
  277. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/llm/client.py +0 -0
  278. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/llm/exceptions.py +0 -0
  279. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/llm/providers/__init__.py +0 -0
  280. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/llm/providers/base.py +0 -0
  281. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/llm/providers/google.py +0 -0
  282. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/llm/providers/models.py +0 -0
  283. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/llm/ratelimit.py +0 -0
  284. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/llm/tool_execution_ids.py +0 -0
  285. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/models/__init__.py +0 -0
  286. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/models/sandbox_terminal_state.py +0 -0
  287. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/permissions.py +0 -0
  288. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/runtime.py +0 -0
  289. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/sandbox/README.md +0 -0
  290. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/sandbox/__init__.py +0 -0
  291. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/sandbox/async_filesystem.py +0 -0
  292. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/sandbox/base.py +0 -0
  293. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/sandbox/browser.py +0 -0
  294. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/sandbox/event_loop.py +0 -0
  295. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/sandbox/filesystem.py +0 -0
  296. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/sandbox/local.py +0 -0
  297. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/sandbox/serializer.py +0 -0
  298. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/sandbox/terminal.py +0 -0
  299. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/sandbox/utils.py +0 -0
  300. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/services/base.py +0 -0
  301. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/services/browser.py +0 -0
  302. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/services/file_system.py +0 -0
  303. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/services/html.py +0 -0
  304. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/services/terminal.py +0 -0
  305. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/services/terminal_buffer.py +0 -0
  306. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/tools/__init__.py +0 -0
  307. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/tools/core.py +0 -0
  308. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/tools/definitions.py +0 -0
  309. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/tools/registry.py +0 -0
  310. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/tools/tests/__init__.py +0 -0
  311. {kolega_code-0.7.1 → kolega_code-0.8.0}/kolega_code/tools/tests/test_definitions.py +0 -0
  312. {kolega_code-0.7.1 → kolega_code-0.8.0}/run_tests.sh +0 -0
  313. {kolega_code-0.7.1 → kolega_code-0.8.0}/scripts/install-kolega-code.sh +0 -0
@@ -7,6 +7,7 @@ OPENAI_API_KEY=
7
7
  # (no key needed). Optional override of the OAuth client id, for testing only:
8
8
  # KOLEGA_CODE_CHATGPT_CLIENT_ID=
9
9
  GOOGLE_API_KEY=
10
+ FIREWORKS_API_KEY=
10
11
  MOONSHOT_API_KEY=
11
12
  DEEPSEEK_API_KEY=
12
13
  ZAI_API_KEY=
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kolega-code
3
- Version: 0.7.1
3
+ Version: 0.8.0
4
4
  Summary: Local-first AI coding agent for the terminal
5
5
  Project-URL: Homepage, https://kolega-ai.github.io/kolega-code/
6
6
  Project-URL: Documentation, https://kolega-ai.github.io/kolega-code/
@@ -54,6 +54,7 @@ Requires-Dist: nest-asyncio==1.6.0
54
54
  Requires-Dist: openai==1.109.1
55
55
  Requires-Dist: packaging==25.0
56
56
  Requires-Dist: pathspec==1.1.1
57
+ Requires-Dist: pillow==12.2.0
57
58
  Requires-Dist: playwright==1.59.0
58
59
  Requires-Dist: psutil==7.2.2
59
60
  Requires-Dist: pydantic==2.13.4
@@ -20,6 +20,12 @@ release branch.
20
20
  2. Update `pyproject.toml`, `uv.lock`, and package `__version__` values to the
21
21
  release version.
22
22
 
23
+ Running `uv lock` advances the `exclude-newer` cutoff recorded in `uv.lock`,
24
+ because `pyproject.toml` sets `exclude-newer = "1 week"` as a relative
25
+ duration. The cutoff timestamp moving forward, and any dependency versions
26
+ released within the new one-week window being pulled in, is expected and
27
+ acceptable. Do not revert these changes.
28
+
23
29
  3. Run the fast test suite:
24
30
 
25
31
  ```bash
@@ -12,10 +12,10 @@ agent can hand off to the others when a task benefits from focus.
12
12
  | Agent | Specialty | Can edit files? |
13
13
  | --- | --- | --- |
14
14
  | **Coder** | The main, general-purpose coding agent. Full toolset. | Yes |
15
- | **Investigation** | Read-only exploration of the codebase. | No |
15
+ | **Investigation** | Read-only exploration of the codebase. Can run investigative commands. | No |
16
16
  | **Browser** | Web navigation and interaction via Playwright. | No (browser tools only) |
17
17
  | **General** | A flexible agent that can read and dispatch other agents. | Limited |
18
- | **Planning** | Drives [Plan mode](../../tui/modes/): investigates and writes a plan, read-only. | No |
18
+ | **Planning** | Drives [Plan mode](../../tui/modes/): investigates and writes a plan. Can run commands but cannot edit files. | No |
19
19
 
20
20
  ## Dispatching sub-agents
21
21
 
@@ -61,13 +61,16 @@ and `dispatch_general_agent`.
61
61
  ## Read-only vs. full access
62
62
 
63
63
  Tools are gated by mode. In a read-only context — like [Plan mode](../../tui/modes/)
64
- or an investigation sub-agent — only non-mutating tools are available
64
+ or an investigation sub-agent — the agent can read and search the codebase
65
65
  (`list_directory`, `read_entire_file`, `read_file_section`, `search_codebase`,
66
66
  `find_files_by_pattern`, `web_search`, `web_fetch`, `think_hard`, and reading
67
- memory). Editing files and running commands require Build mode's full toolset.
67
+ memory) **and** run shell commands to investigate. Editing files still requires
68
+ Build mode's full toolset.
68
69
 
69
- This separation is what makes Plan mode safe to run against any codebase: the
70
- planning agent can look but not touch.
70
+ This separation is what keeps Plan mode safe to run against any codebase: the
71
+ planning agent can look and run investigative commands, but it has no file-edit
72
+ tools. Shell commands are further gated by the active permission mode — in `ask`
73
+ they prompt before running.
71
74
 
72
75
  In the Textual TUI, Build mode defaults to `ask` permission mode. Shell commands
73
76
  and file edits must be approved before they run unless you switch to `auto` or
@@ -118,6 +118,7 @@ starting point:
118
118
  # Pick one provider's key (or several)
119
119
  MOONSHOT_API_KEY=
120
120
  DEEPSEEK_API_KEY=
121
+ FIREWORKS_API_KEY=
121
122
  ANTHROPIC_API_KEY=
122
123
 
123
124
  # Optional: choose models per role
@@ -125,6 +126,10 @@ KOLEGA_CODE_PROVIDER=moonshot
125
126
  KOLEGA_CODE_MODEL=kimi-k2.7-code
126
127
  KOLEGA_CODE_THINKING_EFFORT=auto
127
128
 
129
+ # Fireworks example:
130
+ # KOLEGA_CODE_PROVIDER=fireworks
131
+ # KOLEGA_CODE_MODEL=accounts/fireworks/models/glm-5p2
132
+
128
133
  # Optional: web search
129
134
  KOLEGA_CODE_WEB_SEARCH_BACKEND=duckduckgo
130
135
 
@@ -45,11 +45,21 @@ with thinking effort `auto` (enabled) requests route to **K2.7 Code**, and with
45
45
  (disabled) they route to **K2.6**. Set `KIMI_CODING_API_KEY` to your Kimi Coding Plan key.
46
46
  </Aside>
47
47
 
48
+ <Aside type="tip">
49
+ **Fireworks.ai.** The `fireworks` provider targets Fireworks' OpenAI-compatible
50
+ serverless endpoint (`https://api.fireworks.ai/inference/v1`) with `FIREWORKS_API_KEY`.
51
+ Fireworks model IDs use resource names such as `accounts/fireworks/models/glm-5p2`.
52
+ Kolega Code exposes GLM-5.2, GLM-5.1, Kimi K2.7 Code, DeepSeek V4 Pro/Flash,
53
+ MiniMax M3, and Qwen 3.7 Plus through the Settings UI and CLI. Fireworks reasoning
54
+ models return `reasoning_content`; Kolega Code exposes `none`, `low`, `medium`,
55
+ `high`, and `max` as `reasoning_effort` values for these models.
56
+ </Aside>
57
+
48
58
  <Aside type="note">
49
- The **Settings tab** in the TUI currently offers a curated shortlist
50
- **Moonshot (Kimi K2.7 Code and Kimi K2.6)** and **DeepSeek (DeepSeek V4 Pro)**.
51
- The full provider list above is available through environment variables and CLI
52
- flags. See [Settings & API Keys](../settings-and-api-keys/).
59
+ The **Settings tab** in the TUI is generated from Kolega Code's model registry, so
60
+ new entries in the supported model catalog appear there automatically. You can also
61
+ select any supported provider/model through environment variables and CLI flags.
62
+ See [Settings & API Keys](../settings-and-api-keys/).
53
63
  </Aside>
54
64
 
55
65
  ## Model roles
@@ -153,6 +163,7 @@ models in the TUI.
153
163
  | Moonshot `kimi-k2.6` | `auto`, `none` | `auto` |
154
164
  | Z.AI `glm-5.2` | `high`, `max` | `max` |
155
165
  | Z.AI `glm-5.1` | `auto`, `none` | `auto` |
166
+ | Fireworks serverless reasoning models | `none`, `low`, `medium`, `high`, `max` | `medium` |
156
167
  | Google `gemini-2.5-pro` | `auto`, `low`, `medium`, `high` | `medium` |
157
168
  | OpenAI reasoning models | `low`, `medium`, `high` | `medium` |
158
169
 
@@ -63,6 +63,7 @@ Both files use the same shape:
63
63
  | `PreToolUse` | Before a tool runs (after the permission gate) | Denies the tool; the reason is returned to the agent as a tool error, so it can adjust |
64
64
  | `PostToolUse` | After a tool succeeds (and on failure) | Ends the turn; the reason is shown as a warning |
65
65
  | `PreCompact` | Before the conversation is compacted | Advisory |
66
+ | `PostCompact` | After the conversation is compacted | Advisory; payload reports before/after token counts |
66
67
  | `Stop` | The agent is about to finish its turn | Keeps the agent working; the reason becomes its next instruction |
67
68
  | `SubagentStop` | A dispatched sub-agent finished | Advisory; can annotate the result the parent sees |
68
69
  | `Notification` | A permission prompt is about to be shown | Advisory (desktop notifications, sounds) |
@@ -35,6 +35,8 @@ produces a plan.
35
35
  While planning, the agent can:
36
36
 
37
37
  - Read and search the codebase.
38
+ - Run shell commands to investigate (e.g. `git log`, `grep`, running tests) —
39
+ subject to the current permission mode, so commands prompt for approval in `ask`.
38
40
  - Maintain a shared **task list** (visible in the Planning tab).
39
41
  - Write a structured plan, which appears in the **Planning** tab.
40
42
 
@@ -148,4 +148,4 @@ __all__ = [
148
148
  "parse_git_status_output",
149
149
  ]
150
150
 
151
- __version__ = "0.7.1"
151
+ __version__ = "0.8.0"
@@ -39,4 +39,4 @@ __all__ = [
39
39
  "ToolExtension",
40
40
  ]
41
41
 
42
- __version__ = "0.7.1"
42
+ __version__ = "0.8.0"
@@ -11,11 +11,11 @@ from pathlib import Path
11
11
  from typing import Any, AsyncGenerator, Dict, List, Optional
12
12
 
13
13
  from .common import LogMixin
14
- from .compression import HistoryCompressor
14
+ from .compression import CompactionResult, HistoryCompressor
15
15
  from kolega_code.config import AgentConfig, ModelProvider
16
16
  from kolega_code.events import AgentConnectionManager
17
17
  from .context import AgentContext, AgentServices, Telemetry, WorkspaceInfo
18
- from .conversation import Conversation
18
+ from .conversation import Conversation, adapt_history_for_provider
19
19
  from kolega_code.events import AgentEventEmitter
20
20
  from kolega_code.hooks import (
21
21
  NO_OP_DISPATCHER,
@@ -34,7 +34,7 @@ from kolega_code.llm.exceptions import (
34
34
  )
35
35
  from kolega_code.llm.models import ImageBlock, Message, MessageHistory, TextBlock, ToolCall, ToolResult
36
36
  from kolega_code.llm.providers.models import TokenCount
37
- from kolega_code.llm.specs import get_model_specs
37
+ from kolega_code.llm.specs import get_model_specs, supports_vision as model_supports_vision
38
38
  from kolega_code.permissions import (
39
39
  PermissionDecision,
40
40
  PermissionMode,
@@ -90,10 +90,6 @@ class BaseAgent(LogMixin):
90
90
  long_content_tool_calls = ["create_file", "replace_entire_file"]
91
91
  max_tool_result_chars_in_history = 100_000
92
92
  skill_content_pattern = re.compile(r'<skill_content name="[^"]+">')
93
- deepseek_image_unsupported_message = (
94
- "DeepSeek V4 Pro does not support image input via the DeepSeek API. "
95
- "Remove the image or switch to a vision-capable model for this request."
96
- )
97
93
 
98
94
  def __init__(
99
95
  self,
@@ -270,6 +266,11 @@ class BaseAgent(LogMixin):
270
266
  self.model_context_length = model_specs["context_length"]
271
267
  self.model_completion_tokens = model_specs["max_completion_tokens"]
272
268
  self.model_default_temperature = model_specs.get("default_temperature", 1.0)
269
+ # Whether this agent's primary model can accept image input. Read by the
270
+ # ToolCollection read_image tool gate (so non-vision models never see the
271
+ # tool) and used by _unsupported_attachment_message to reject image
272
+ # attachments for non-vision models with a clear message.
273
+ self.supports_vision = bool(model_specs.get("supports_vision", False))
273
274
 
274
275
  self.llm = context.create_llm_client(agent_name=self.agent_name)
275
276
 
@@ -365,6 +366,26 @@ class BaseAgent(LogMixin):
365
366
  """
366
367
  return self.conversation.repaired(messages)
367
368
 
369
+ def _history_for_llm(self) -> MessageHistory:
370
+ """Build the message history to send to the LLM for this turn.
371
+
372
+ Compaction-aware and tool-call-repaired. For non-vision models, any
373
+ ``ImageBlock`` carried over from earlier turns (user attachments or
374
+ ``read_image`` tool results) is replaced with a text placeholder on this
375
+ request copy only — the stored history is never mutated, so switching
376
+ back to a vision-capable model restores the images.
377
+ """
378
+ effective = self.get_effective_history_for_llm()
379
+ fixed = self.fix_incomplete_tool_calls(list(effective))
380
+ provider = getattr(self.primary_model_config.provider, "value", self.primary_model_config.provider)
381
+ fixed = adapt_history_for_provider(
382
+ fixed,
383
+ target_provider=str(provider),
384
+ target_model=self.primary_model_config.model,
385
+ supports_vision=self.supports_vision,
386
+ )
387
+ return MessageHistory(fixed)
388
+
368
389
  def mark_cache_checkpoint(self) -> None:
369
390
  """
370
391
  Mark the last message in history for caching and remove cache_control from all other messages.
@@ -398,6 +419,14 @@ class BaseAgent(LogMixin):
398
419
  """Restores the message history from a list of dictionaries using custom methods."""
399
420
  self.conversation.restore(serialized_history)
400
421
 
422
+ def dump_compaction_state(self) -> Dict[str, Any]:
423
+ """Serialize the compaction boundary (summary + how many leading messages it folds)."""
424
+ return self.conversation.dump_compaction()
425
+
426
+ def restore_compaction_state(self, data: Optional[Dict[str, Any]]) -> None:
427
+ """Restore the compaction boundary; must be called after restore_message_history."""
428
+ self.conversation.restore_compaction(data)
429
+
401
430
  def _sanitize_oversized_tool_results(self) -> int:
402
431
  return self.conversation.sanitize_oversized_tool_results()
403
432
 
@@ -472,18 +501,22 @@ class BaseAgent(LogMixin):
472
501
  # ------------------------------------------------------------------
473
502
 
474
503
  def _unsupported_attachment_message(self, attachments: Optional[List[Dict[str, Any]]]) -> Optional[str]:
504
+ if not any(attachment.get("type") == "image" for attachment in attachments or []):
505
+ return None
506
+
475
507
  provider = getattr(
476
508
  self.primary_model_config.provider,
477
509
  "value",
478
510
  self.primary_model_config.provider,
479
511
  )
480
- if provider != ModelProvider.DEEPSEEK.value:
512
+ if model_supports_vision(provider, self.primary_model_config.model):
481
513
  return None
482
514
 
483
- if any(attachment.get("type") == "image" for attachment in attachments or []):
484
- return self.deepseek_image_unsupported_message
485
-
486
- return None
515
+ return (
516
+ f"{self.primary_model_config.model} does not support image input. "
517
+ "Your message was not sent to the model. "
518
+ "Remove the image attachment or switch to a vision-capable model with /model."
519
+ )
487
520
 
488
521
  def _attachment_blocks(self, attachments: Optional[List[Dict[str, Any]]]) -> List[Any]:
489
522
  """Convert attachment payloads into content blocks for a user message."""
@@ -510,9 +543,9 @@ class BaseAgent(LogMixin):
510
543
 
511
544
  async def count_current_context(self) -> TokenCount:
512
545
  self._sanitize_oversized_tool_results()
513
- # Fix history before counting to get accurate count for what LLM will see
514
- effective = self.get_effective_history_for_llm()
515
- fixed_history = MessageHistory(self.fix_incomplete_tool_calls(list(effective)))
546
+ # History sent to the LLM (and to token counting): tool-call-repaired and,
547
+ # for non-vision models, stripped of image blocks from earlier turns.
548
+ fixed_history = self._history_for_llm()
516
549
  token_count = await self.llm.count_tokens(
517
550
  system=self.system_prompt,
518
551
  messages=fixed_history,
@@ -549,9 +582,12 @@ class BaseAgent(LogMixin):
549
582
  message=message,
550
583
  )
551
584
 
552
- async def compress_history(self) -> None:
585
+ async def compress_history(self) -> CompactionResult:
553
586
  """
554
- Non-destructively summarize the current history and mark a compression boundary.
587
+ Non-destructively summarize the current history, keeping recent turns
588
+ verbatim. Emits a compaction_status event around the work so the UI can
589
+ show progress, then recounts + emits a context_update so the gauge
590
+ refreshes. Returns the structured outcome.
555
591
  """
556
592
 
557
593
  async def on_info(message: str) -> None:
@@ -560,16 +596,34 @@ class BaseAgent(LogMixin):
560
596
  async def on_error(message: str) -> None:
561
597
  await self.log_error(message, sender=self.agent_name)
562
598
 
563
- await self.compressor.summarize(
564
- self.conversation,
565
- llm=self.llm,
566
- model=self.primary_model_config.model,
567
- max_completion_tokens=self.model_completion_tokens,
568
- temperature=self.model_default_temperature,
569
- thinking=self.primary_model_config.thinking_effort,
570
- on_info=on_info,
571
- on_error=on_error,
599
+ await self.emitter.compaction_status("started", "Compacting conversation…")
600
+ try:
601
+ result = await self.compressor.summarize(
602
+ self.conversation,
603
+ llm=self.llm,
604
+ model=self.primary_model_config.model,
605
+ temperature=self.model_default_temperature,
606
+ # No extended thinking: a bounded summary doesn't need it, and the
607
+ # thinking budget could otherwise exceed the small summary max_tokens.
608
+ thinking=None,
609
+ on_info=on_info,
610
+ on_error=on_error,
611
+ )
612
+ finally:
613
+ # Recount + emit so the context gauge reflects post-compaction reality
614
+ # (even on a no-op the UI may have been stale).
615
+ await self.count_current_context()
616
+
617
+ phase = "finished" if result.ok else "error"
618
+ summary_text = (
619
+ self.conversation.summary.get_text_content() if result.ok and self.conversation.summary else ""
572
620
  )
621
+ await self.emitter.compaction_status(phase, result.message, summary=summary_text)
622
+ return result
623
+
624
+ def clear_history(self) -> None:
625
+ """Drop all history and reset compaction state."""
626
+ self.conversation.clear()
573
627
 
574
628
  # ------------------------------------------------------------------
575
629
  # Tool execution
@@ -1136,9 +1190,8 @@ class BaseAgent(LogMixin):
1136
1190
  #
1137
1191
  # process_message_stream is the single canonical loop shared by every
1138
1192
  # agent. Subclasses customize behavior through the hook methods below
1139
- # (build_user_content, apply_compression_fallback, on_tool_use_start,
1140
- # should_stop_after_tools, recap_agent_outcome) rather than overriding
1141
- # the loop itself.
1193
+ # (build_user_content, on_tool_use_start, should_stop_after_tools,
1194
+ # recap_agent_outcome) rather than overriding the loop itself.
1142
1195
  # ------------------------------------------------------------------
1143
1196
 
1144
1197
  completion_log_message = "Processing complete"
@@ -1166,21 +1219,6 @@ class BaseAgent(LogMixin):
1166
1219
 
1167
1220
  return content_blocks
1168
1221
 
1169
- def apply_compression_fallback(self) -> None:
1170
- """
1171
- Hard-truncate history when compression alone could not get under budget.
1172
-
1173
- Default: keep the first message (the original task) plus any protected
1174
- skill-content messages.
1175
- """
1176
- first_message = self.history[0]
1177
- protected = [
1178
- message
1179
- for message in self.history
1180
- if message is not first_message and self._is_protected_skill_content(message)
1181
- ]
1182
- self.history = MessageHistory(protected + [first_message])
1183
-
1184
1222
  async def on_tool_use_start(self, tool_call_delta: Dict[str, Any]) -> None:
1185
1223
  """
1186
1224
  Called when the provider streams a tool_use_start event (Anthropic only).
@@ -1252,20 +1290,35 @@ class BaseAgent(LogMixin):
1252
1290
  logger.debug("Input token count: %s", token_count)
1253
1291
 
1254
1292
  if self.compressor.over_budget(token_count.input_tokens, self.model_context_length):
1293
+ before_tokens = token_count.input_tokens
1255
1294
  # PreCompact hooks (advisory): observe before history is compacted.
1256
1295
  await self.fire_hook(
1257
1296
  HookEvent.PRE_COMPACT,
1258
1297
  {
1259
1298
  "trigger": "auto",
1260
- "input_tokens": token_count.input_tokens,
1299
+ "input_tokens": before_tokens,
1261
1300
  "model_context_length": self.model_context_length,
1262
1301
  },
1263
1302
  )
1264
- await self.compress_history()
1303
+ result = await self.compress_history()
1265
1304
  token_count = await self.count_current_context()
1266
1305
 
1267
- if self.compressor.over_budget(token_count.input_tokens, self.model_context_length):
1268
- self.apply_compression_fallback()
1306
+ # PostCompact hooks (advisory): observe the outcome. There is no
1307
+ # destructive fallback — a bounded summary plus capped tool results
1308
+ # and a small verbatim tail keep us under budget; if somehow not, we
1309
+ # send as-is rather than wipe history.
1310
+ await self.fire_hook(
1311
+ HookEvent.POST_COMPACT,
1312
+ {
1313
+ "trigger": "auto",
1314
+ "ok": result.ok,
1315
+ "reason": result.reason,
1316
+ "summarized_messages": result.summarized_messages,
1317
+ "input_tokens_before": before_tokens,
1318
+ "input_tokens_after": token_count.input_tokens,
1319
+ "model_context_length": self.model_context_length,
1320
+ },
1321
+ )
1269
1322
 
1270
1323
  self.mark_cache_checkpoint()
1271
1324
 
@@ -1276,9 +1329,9 @@ class BaseAgent(LogMixin):
1276
1329
  response_uuid = str(uuid.uuid4())
1277
1330
  thinking_uuid = str(uuid.uuid4())
1278
1331
 
1279
- # Fix history before sending to LLM to ensure valid tool call sequences
1280
- effective = self.get_effective_history_for_llm()
1281
- fixed_history = MessageHistory(self.fix_incomplete_tool_calls(list(effective)))
1332
+ # History sent to the LLM: tool-call-repaired and, for non-vision
1333
+ # models, stripped of image blocks carried over from earlier turns.
1334
+ fixed_history = self._history_for_llm()
1282
1335
 
1283
1336
  async with await self.llm.stream(
1284
1337
  system=self.system_prompt,
@@ -0,0 +1,146 @@
1
+ """History compression: summarize a conversation when it outgrows the context window."""
2
+
3
+ import logging
4
+ from dataclasses import dataclass
5
+ from typing import Awaitable, Callable, Optional
6
+
7
+ from .conversation import Conversation
8
+ from kolega_code.llm.models import Message, MessageHistory, TextBlock
9
+ from .prompts import (
10
+ build_compression_summary_user_prompt,
11
+ COMPRESSION_SUMMARY_SYSTEM_PROMPT,
12
+ )
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+ LogCallback = Callable[[str], Awaitable[None]]
17
+
18
+
19
+ @dataclass(frozen=True)
20
+ class CompactionResult:
21
+ """Outcome of a compaction attempt, surfaced to callers and the UI.
22
+
23
+ ``reason`` is a machine tag: "ok" | "too_few" | "nothing_to_summarize" | "llm_error".
24
+ ``message`` is a human-readable line for the command output / logs.
25
+ """
26
+
27
+ ok: bool
28
+ reason: str
29
+ summarized_messages: int = 0
30
+ message: str = ""
31
+
32
+
33
+ class HistoryCompressor:
34
+ """Summarizes a conversation non-destructively when it crosses the budget threshold."""
35
+
36
+ MIN_MESSAGES_TO_COMPRESS = 5
37
+ # How many of the most recent messages to keep verbatim after the summary.
38
+ KEEP_RECENT_MESSAGES = 6
39
+ # Don't bother summarizing a trivially short prefix.
40
+ MIN_PREFIX_TO_SUMMARIZE = 3
41
+ # Cap the summary length: the prompt targets ~600 words (~900 tokens), so a
42
+ # small ceiling keeps it tight and avoids the model's full completion budget.
43
+ SUMMARY_MAX_TOKENS = 2048
44
+
45
+ def __init__(self, threshold: float = 0.8) -> None:
46
+ # Fraction of the model context window above which compression kicks in
47
+ self.threshold = threshold
48
+
49
+ def over_budget(self, input_tokens: int, model_context_length: int) -> bool:
50
+ return input_tokens > model_context_length * self.threshold
51
+
52
+ async def summarize(
53
+ self,
54
+ conversation: Conversation,
55
+ *,
56
+ llm,
57
+ model: str,
58
+ temperature: float,
59
+ thinking,
60
+ on_info: Optional[LogCallback] = None,
61
+ on_error: Optional[LogCallback] = None,
62
+ ) -> CompactionResult:
63
+ """
64
+ Non-destructively summarize the aged-out prefix while keeping the most
65
+ recent turns verbatim, and move the compaction boundary.
66
+
67
+ Incremental: only the messages that have aged out since the previous
68
+ boundary are summarized, with the prior summary folded in for continuity.
69
+ Returns a CompactionResult describing what happened — never a silent no-op.
70
+ """
71
+ history = conversation.history
72
+ if not history or len(history) < self.MIN_MESSAGES_TO_COMPRESS:
73
+ return CompactionResult(
74
+ ok=False,
75
+ reason="too_few",
76
+ message=(
77
+ f"Nothing to compress yet ({len(history)} message(s); "
78
+ f"need at least {self.MIN_MESSAGES_TO_COMPRESS})."
79
+ ),
80
+ )
81
+
82
+ split = conversation.compaction_split_point(
83
+ keep_recent=self.KEEP_RECENT_MESSAGES, min_prefix=self.MIN_PREFIX_TO_SUMMARIZE
84
+ )
85
+ prior_through = conversation.compacted_through if conversation.summary is not None else 0
86
+ if split is None or split <= prior_through:
87
+ return CompactionResult(
88
+ ok=False,
89
+ reason="nothing_to_summarize",
90
+ message="Already compact — no older messages to summarize.",
91
+ )
92
+
93
+ if on_info:
94
+ await on_info("Compressing message history...")
95
+
96
+ try:
97
+ prefix = MessageHistory(list(history[prior_through:split]))
98
+ prefix_markdown = prefix.get_markdown_conversation()
99
+ previous_summary = conversation.summary.get_text_content() if conversation.summary is not None else None
100
+ user_prompt_filled = build_compression_summary_user_prompt(
101
+ prefix_markdown, previous_summary=previous_summary
102
+ )
103
+
104
+ messages = MessageHistory([Message(role="user", content=[TextBlock(text=user_prompt_filled)])])
105
+ system_message = Message(role="system", content=[TextBlock(text=COMPRESSION_SUMMARY_SYSTEM_PROMPT)])
106
+
107
+ # Stream and drain rather than calling generate(): the Anthropic SDK
108
+ # rejects non-streaming requests whose max_tokens is large enough to risk
109
+ # a >10-minute response, which the model's full completion budget triggers.
110
+ async with await llm.stream(
111
+ messages=messages,
112
+ system=system_message,
113
+ temperature=temperature,
114
+ model=model,
115
+ max_completion_tokens=self.SUMMARY_MAX_TOKENS,
116
+ thinking=thinking,
117
+ ) as stream:
118
+ async for _event in stream:
119
+ pass
120
+ response = await stream.get_final_message()
121
+
122
+ summary_text = response.get_text_content()
123
+ if not summary_text or not summary_text.strip():
124
+ msg = "Compression produced an empty summary; history left unchanged."
125
+ if on_error:
126
+ await on_error(msg)
127
+ else:
128
+ logger.error(msg)
129
+ return CompactionResult(ok=False, reason="llm_error", message=msg)
130
+
131
+ folded = split - prior_through
132
+ conversation.apply_compaction(summary_text, split)
133
+
134
+ done = f"Compressed {folded} older message(s) into a summary; kept the latest turns verbatim."
135
+ if on_info:
136
+ await on_info(done)
137
+ return CompactionResult(ok=True, reason="ok", summarized_messages=folded, message=done)
138
+
139
+ except Exception as e:
140
+ # Never swallow into a fake success: log the full traceback and surface
141
+ # the real error to the caller.
142
+ logger.exception("Failed to compress message history")
143
+ msg = f"Failed to compress message history: {type(e).__name__}: {e}"
144
+ if on_error:
145
+ await on_error(msg)
146
+ return CompactionResult(ok=False, reason="llm_error", message=msg)