kolega-code 0.5.0__tar.gz → 0.5.1__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 (266) hide show
  1. {kolega_code-0.5.0 → kolega_code-0.5.1}/PKG-INFO +1 -1
  2. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/__init__.py +1 -1
  3. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/__init__.py +1 -1
  4. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/coder.py +0 -1
  5. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/generalagent.py +0 -1
  6. kolega_code-0.5.1/kolega_code/agent/tests/services/test_sandbox_terminal_input.py +129 -0
  7. kolega_code-0.5.1/kolega_code/agent/tests/services/test_terminal.py +139 -0
  8. kolega_code-0.5.1/kolega_code/agent/tests/services/test_terminal_buffer.py +71 -0
  9. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/test_agent_tools_inventory.py +2 -2
  10. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/test_permissions.py +6 -6
  11. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/test_tool_registry.py +1 -1
  12. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/test_tools.py +9 -8
  13. kolega_code-0.5.1/kolega_code/agent/tests/tool_backend/test_terminal_tool.py +244 -0
  14. kolega_code-0.5.1/kolega_code/agent/tool_backend/terminal_tool.py +395 -0
  15. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tools.py +70 -204
  16. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/cli/tests/test_app.py +1 -1
  17. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/permissions.py +1 -2
  18. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/sandbox/terminal.py +157 -1
  19. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/services/base.py +66 -88
  20. kolega_code-0.5.1/kolega_code/services/terminal.py +495 -0
  21. kolega_code-0.5.1/kolega_code/services/terminal_buffer.py +164 -0
  22. {kolega_code-0.5.0 → kolega_code-0.5.1}/pyproject.toml +1 -1
  23. {kolega_code-0.5.0 → kolega_code-0.5.1}/uv.lock +2 -2
  24. kolega_code-0.5.0/kolega_code/agent/tests/services/test_sandbox_terminal_input.py +0 -98
  25. kolega_code-0.5.0/kolega_code/agent/tests/services/test_terminal.py +0 -154
  26. kolega_code-0.5.0/kolega_code/agent/tests/services/test_terminal_command_tracking.py +0 -385
  27. kolega_code-0.5.0/kolega_code/agent/tests/tool_backend/test_terminal_tool.py +0 -482
  28. kolega_code-0.5.0/kolega_code/agent/tool_backend/terminal_tool.py +0 -643
  29. kolega_code-0.5.0/kolega_code/services/terminal.py +0 -903
  30. {kolega_code-0.5.0 → kolega_code-0.5.1}/.env.example +0 -0
  31. {kolega_code-0.5.0 → kolega_code-0.5.1}/.github/workflows/ci.yml +0 -0
  32. {kolega_code-0.5.0 → kolega_code-0.5.1}/.github/workflows/docs.yml +0 -0
  33. {kolega_code-0.5.0 → kolega_code-0.5.1}/.github/workflows/release.yml +0 -0
  34. {kolega_code-0.5.0 → kolega_code-0.5.1}/.gitignore +0 -0
  35. {kolega_code-0.5.0 → kolega_code-0.5.1}/CONTRIBUTING.md +0 -0
  36. {kolega_code-0.5.0 → kolega_code-0.5.1}/LICENSE +0 -0
  37. {kolega_code-0.5.0 → kolega_code-0.5.1}/README.md +0 -0
  38. {kolega_code-0.5.0 → kolega_code-0.5.1}/RELEASING.md +0 -0
  39. {kolega_code-0.5.0 → kolega_code-0.5.1}/SECURITY.md +0 -0
  40. {kolega_code-0.5.0 → kolega_code-0.5.1}/conftest.py +0 -0
  41. {kolega_code-0.5.0 → kolega_code-0.5.1}/docs/.gitignore +0 -0
  42. {kolega_code-0.5.0 → kolega_code-0.5.1}/docs/README.md +0 -0
  43. {kolega_code-0.5.0 → kolega_code-0.5.1}/docs/astro.config.mjs +0 -0
  44. {kolega_code-0.5.0 → kolega_code-0.5.1}/docs/package-lock.json +0 -0
  45. {kolega_code-0.5.0 → kolega_code-0.5.1}/docs/package.json +0 -0
  46. {kolega_code-0.5.0 → kolega_code-0.5.1}/docs/public/favicon.svg +0 -0
  47. {kolega_code-0.5.0 → kolega_code-0.5.1}/docs/src/assets/kolega-dark.svg +0 -0
  48. {kolega_code-0.5.0 → kolega_code-0.5.1}/docs/src/assets/kolega-light.svg +0 -0
  49. {kolega_code-0.5.0 → kolega_code-0.5.1}/docs/src/content/docs/cli/ask.md +0 -0
  50. {kolega_code-0.5.0 → kolega_code-0.5.1}/docs/src/content/docs/cli/doctor.md +0 -0
  51. {kolega_code-0.5.0 → kolega_code-0.5.1}/docs/src/content/docs/cli/overview.md +0 -0
  52. {kolega_code-0.5.0 → kolega_code-0.5.1}/docs/src/content/docs/cli/sessions.md +0 -0
  53. {kolega_code-0.5.0 → kolega_code-0.5.1}/docs/src/content/docs/concepts/agents.md +0 -0
  54. {kolega_code-0.5.0 → kolega_code-0.5.1}/docs/src/content/docs/concepts/how-it-works.md +0 -0
  55. {kolega_code-0.5.0 → kolega_code-0.5.1}/docs/src/content/docs/concepts/tools.md +0 -0
  56. {kolega_code-0.5.0 → kolega_code-0.5.1}/docs/src/content/docs/configuration/environment-variables.md +0 -0
  57. {kolega_code-0.5.0 → kolega_code-0.5.1}/docs/src/content/docs/configuration/providers-and-models.mdx +0 -0
  58. {kolega_code-0.5.0 → kolega_code-0.5.1}/docs/src/content/docs/configuration/settings-and-api-keys.mdx +0 -0
  59. {kolega_code-0.5.0 → kolega_code-0.5.1}/docs/src/content/docs/getting-started/installation.mdx +0 -0
  60. {kolega_code-0.5.0 → kolega_code-0.5.1}/docs/src/content/docs/getting-started/quick-start.mdx +0 -0
  61. {kolega_code-0.5.0 → kolega_code-0.5.1}/docs/src/content/docs/gigacode.mdx +0 -0
  62. {kolega_code-0.5.0 → kolega_code-0.5.1}/docs/src/content/docs/hooks.md +0 -0
  63. {kolega_code-0.5.0 → kolega_code-0.5.1}/docs/src/content/docs/index.mdx +0 -0
  64. {kolega_code-0.5.0 → kolega_code-0.5.1}/docs/src/content/docs/skills.mdx +0 -0
  65. {kolega_code-0.5.0 → kolega_code-0.5.1}/docs/src/content/docs/tui/composer.md +0 -0
  66. {kolega_code-0.5.0 → kolega_code-0.5.1}/docs/src/content/docs/tui/interface.md +0 -0
  67. {kolega_code-0.5.0 → kolega_code-0.5.1}/docs/src/content/docs/tui/modes.md +0 -0
  68. {kolega_code-0.5.0 → kolega_code-0.5.1}/docs/src/content/docs/tui/sessions-and-resume.md +0 -0
  69. {kolega_code-0.5.0 → kolega_code-0.5.1}/docs/src/content/docs/tui/slash-commands.md +0 -0
  70. {kolega_code-0.5.0 → kolega_code-0.5.1}/docs/src/content.config.ts +0 -0
  71. {kolega_code-0.5.0 → kolega_code-0.5.1}/docs/src/styles/brand.css +0 -0
  72. {kolega_code-0.5.0 → kolega_code-0.5.1}/docs/tsconfig.json +0 -0
  73. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/baseagent.py +0 -0
  74. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/browseragent.py +0 -0
  75. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/common.py +0 -0
  76. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/compression.py +0 -0
  77. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/context.py +0 -0
  78. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/conversation.py +0 -0
  79. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/investigationagent.py +0 -0
  80. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/orchestration/__init__.py +0 -0
  81. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/orchestration/budget.py +0 -0
  82. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/orchestration/errors.py +0 -0
  83. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/orchestration/executor.py +0 -0
  84. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/orchestration/guide.py +0 -0
  85. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/orchestration/journal.py +0 -0
  86. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/orchestration/runtime.py +0 -0
  87. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/orchestration/tests/__init__.py +0 -0
  88. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/orchestration/tests/test_orchestration.py +0 -0
  89. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/orchestration/types.py +0 -0
  90. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/planningagent.py +0 -0
  91. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/prompt_provider.py +0 -0
  92. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/prompt_templates/auxiliary/compression/summary.system.md +0 -0
  93. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/prompt_templates/auxiliary/compression/summary.user.md.j2 +0 -0
  94. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/prompt_templates/auxiliary/terminal/output_summary.system.md +0 -0
  95. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/prompt_templates/auxiliary/terminal/safety.system.md +0 -0
  96. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/prompt_templates/auxiliary/tools/think_hard.system.md +0 -0
  97. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/prompt_templates/extensions/cli/planning_questions.md +0 -0
  98. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/prompt_templates/extensions/cli/shared_task_list.md +0 -0
  99. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/prompt_templates/extensions/skills/catalog.md.j2 +0 -0
  100. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/prompt_templates/system/agents/browser.md.j2 +0 -0
  101. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/prompt_templates/system/agents/coder_cli.md.j2 +0 -0
  102. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/prompt_templates/system/agents/general.md.j2 +0 -0
  103. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/prompt_templates/system/agents/investigation.md.j2 +0 -0
  104. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/prompt_templates/system/agents/planning.md.j2 +0 -0
  105. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/prompt_templates/system/includes/agents_md_instructions.md +0 -0
  106. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/prompt_templates/system/includes/environment_variables/workspace_env_vars.md +0 -0
  107. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/prompt_templates/system/includes/frontend_guidance.md +0 -0
  108. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/prompt_templates/system/includes/starter_templates/expo.md +0 -0
  109. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/prompt_templates/system/includes/starter_templates/html_website.md +0 -0
  110. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/prompt_templates/system/includes/starter_templates/mern_stack.md +0 -0
  111. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/prompt_templates/system/includes/starter_templates/react_vite_shadcdn.md +0 -0
  112. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/prompt_templates/user_tasks/cli/implement_plan.md.j2 +0 -0
  113. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/prompt_templates/user_tasks/cli/init_agents.md.j2 +0 -0
  114. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/prompts.py +0 -0
  115. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/__init__.py +0 -0
  116. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/llm/__init__.py +0 -0
  117. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/llm/test_anthropic_token_counting.py +0 -0
  118. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/llm/test_billing_openai_cache.py +0 -0
  119. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/llm/test_client.py +0 -0
  120. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/llm/test_dashscope_mapping.py +0 -0
  121. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/llm/test_error_boundary.py +0 -0
  122. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/llm/test_exceptions.py +0 -0
  123. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/llm/test_google_thought_signature.py +0 -0
  124. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/llm/test_google_tool_signature_live.py +0 -0
  125. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/llm/test_instrumented_client.py +0 -0
  126. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/llm/test_instrumented_client_integration.py +0 -0
  127. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/llm/test_langfuse_normalization.py +0 -0
  128. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/llm/test_live_providers.py +0 -0
  129. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/llm/test_model_specs.py +0 -0
  130. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/llm/test_openai_cached_tokens.py +0 -0
  131. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/llm/test_openai_cached_tokens_stream.py +0 -0
  132. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/llm/test_openai_message_conversion.py +0 -0
  133. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/llm/test_openai_token_counting.py +0 -0
  134. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/llm/test_thinking_effort.py +0 -0
  135. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/llm/test_tool_execution_ids.py +0 -0
  136. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/services/__init__.py +0 -0
  137. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/services/test_browser.py +0 -0
  138. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/services/test_browser_parity.py +0 -0
  139. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/services/test_file_system.py +0 -0
  140. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/services/test_terminal_state_serializer.py +0 -0
  141. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/test_base_agent.py +0 -0
  142. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/test_coder_attachments.py +0 -0
  143. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/test_coder_prompt_extensions.py +0 -0
  144. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/test_commands.py +0 -0
  145. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/test_duplicate_tool_results.py +0 -0
  146. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/test_empty_message_handling.py +0 -0
  147. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/test_general_agent.py +0 -0
  148. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/test_gigacode_gating.py +0 -0
  149. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/test_hooks_integration.py +0 -0
  150. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/test_html.py +0 -0
  151. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/test_parallel_tool_calls.py +0 -0
  152. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/test_planning_agent.py +0 -0
  153. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/test_prompt_provider.py +0 -0
  154. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/test_prompts.py +0 -0
  155. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/tool_backend/__init__.py +0 -0
  156. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/tool_backend/test_agent_tool.py +0 -0
  157. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/tool_backend/test_base_tool.py +0 -0
  158. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/tool_backend/test_browser_tool.py +0 -0
  159. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/tool_backend/test_build_tool.py +0 -0
  160. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/tool_backend/test_create_file_tool.py +0 -0
  161. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/tool_backend/test_glob_tool.py +0 -0
  162. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/tool_backend/test_glob_tool_sandbox_parity.py +0 -0
  163. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/tool_backend/test_list_directory_tool.py +0 -0
  164. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/tool_backend/test_read_file_tool.py +0 -0
  165. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/tool_backend/test_replace_entire_file_tool.py +0 -0
  166. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/tool_backend/test_replace_lines_tool.py +0 -0
  167. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/tool_backend/test_search_and_replace_tool.py +0 -0
  168. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/tool_backend/test_search_codebase_tool.py +0 -0
  169. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/tool_backend/test_think_hard_integration.py +0 -0
  170. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/tool_backend/test_think_hard_streaming.py +0 -0
  171. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/tool_backend/test_web_fetch_tool.py +0 -0
  172. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/tool_backend/test_workflow_tool.py +0 -0
  173. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tool_backend/agent_tool.py +0 -0
  174. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tool_backend/apply_patch_tool.py +0 -0
  175. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tool_backend/base_tool.py +0 -0
  176. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tool_backend/browser_tool.py +0 -0
  177. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tool_backend/build_tool.py +0 -0
  178. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tool_backend/create_file_tool.py +0 -0
  179. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tool_backend/glob_tool.py +0 -0
  180. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tool_backend/list_directory_tool.py +0 -0
  181. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tool_backend/memory_tool.py +0 -0
  182. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tool_backend/read_file_tool.py +0 -0
  183. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tool_backend/replace_entire_file_tool.py +0 -0
  184. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tool_backend/replace_lines_tool.py +0 -0
  185. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tool_backend/search_and_replace_tool.py +0 -0
  186. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tool_backend/search_codebase_tool.py +0 -0
  187. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tool_backend/streaming_tool.py +0 -0
  188. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tool_backend/think_hard_tool.py +0 -0
  189. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tool_backend/web_fetch_tool.py +0 -0
  190. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tool_backend/workflow_tool.py +0 -0
  191. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/utils/commands.py +0 -0
  192. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/cli/__init__.py +0 -0
  193. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/cli/app.py +0 -0
  194. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/cli/config.py +0 -0
  195. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/cli/connection.py +0 -0
  196. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/cli/file_index.py +0 -0
  197. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/cli/main.py +0 -0
  198. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/cli/mentions.py +0 -0
  199. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/cli/messages.py +0 -0
  200. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/cli/provider_registry.py +0 -0
  201. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/cli/session_store.py +0 -0
  202. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/cli/settings.py +0 -0
  203. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/cli/skills.py +0 -0
  204. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/cli/slash_commands.py +0 -0
  205. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/cli/tests/__init__.py +0 -0
  206. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/cli/tests/test_cli_config.py +0 -0
  207. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/cli/tests/test_connection.py +0 -0
  208. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/cli/tests/test_file_index.py +0 -0
  209. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/cli/tests/test_main.py +0 -0
  210. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/cli/tests/test_mentions.py +0 -0
  211. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/cli/tests/test_session_store.py +0 -0
  212. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/cli/tests/test_settings.py +0 -0
  213. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/cli/tests/test_skills.py +0 -0
  214. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/cli/tests/test_slash_commands.py +0 -0
  215. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/cli/tests/test_themes.py +0 -0
  216. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/cli/tests/test_updater.py +0 -0
  217. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/cli/theme.py +0 -0
  218. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/cli/updater.py +0 -0
  219. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/config.py +0 -0
  220. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/events.py +0 -0
  221. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/hooks/__init__.py +0 -0
  222. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/hooks/backends.py +0 -0
  223. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/hooks/config.py +0 -0
  224. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/hooks/dispatcher.py +0 -0
  225. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/hooks/events.py +0 -0
  226. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/hooks/matcher.py +0 -0
  227. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/hooks/outcome.py +0 -0
  228. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/hooks/tests/__init__.py +0 -0
  229. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/hooks/tests/test_hooks.py +0 -0
  230. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/llm/client.py +0 -0
  231. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/llm/exceptions.py +0 -0
  232. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/llm/instrumented_client.py +0 -0
  233. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/llm/models.py +0 -0
  234. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/llm/providers/__init__.py +0 -0
  235. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/llm/providers/anthropic.py +0 -0
  236. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/llm/providers/base.py +0 -0
  237. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/llm/providers/google.py +0 -0
  238. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/llm/providers/models.py +0 -0
  239. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/llm/providers/openai.py +0 -0
  240. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/llm/ratelimit.py +0 -0
  241. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/llm/specs.py +0 -0
  242. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/llm/tool_execution_ids.py +0 -0
  243. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/models/__init__.py +0 -0
  244. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/models/sandbox_terminal_state.py +0 -0
  245. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/runtime.py +0 -0
  246. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/sandbox/README.md +0 -0
  247. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/sandbox/__init__.py +0 -0
  248. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/sandbox/async_filesystem.py +0 -0
  249. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/sandbox/base.py +0 -0
  250. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/sandbox/browser.py +0 -0
  251. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/sandbox/event_loop.py +0 -0
  252. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/sandbox/filesystem.py +0 -0
  253. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/sandbox/local.py +0 -0
  254. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/sandbox/serializer.py +0 -0
  255. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/sandbox/utils.py +0 -0
  256. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/services/browser.py +0 -0
  257. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/services/file_system.py +0 -0
  258. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/services/html.py +0 -0
  259. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/tools/__init__.py +0 -0
  260. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/tools/core.py +0 -0
  261. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/tools/definitions.py +0 -0
  262. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/tools/registry.py +0 -0
  263. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/tools/tests/__init__.py +0 -0
  264. {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/tools/tests/test_definitions.py +0 -0
  265. {kolega_code-0.5.0 → kolega_code-0.5.1}/run_tests.sh +0 -0
  266. {kolega_code-0.5.0 → kolega_code-0.5.1}/scripts/install-kolega-code.sh +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kolega-code
3
- Version: 0.5.0
3
+ Version: 0.5.1
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/
@@ -148,4 +148,4 @@ __all__ = [
148
148
  "parse_git_status_output",
149
149
  ]
150
150
 
151
- __version__ = "0.5.0"
151
+ __version__ = "0.5.1"
@@ -39,4 +39,4 @@ __all__ = [
39
39
  "ToolExtension",
40
40
  ]
41
41
 
42
- __version__ = "0.5.0"
42
+ __version__ = "0.5.1"
@@ -116,7 +116,6 @@ class CoderAgent(BaseAgent, LogMixin):
116
116
  "get_tool_list",
117
117
  "log_error",
118
118
  "log_info",
119
- "run_command", # Disabled: unreliable completion detection, use run_command_tracked instead
120
119
  # Exclude task-specific dispatch tools since coder shouldn't call itself or other agents
121
120
  "dispatch_coding_agent",
122
121
  ]
@@ -112,7 +112,6 @@ class GeneralAgent(BaseAgent):
112
112
  "get_tool_list",
113
113
  "log_error",
114
114
  "log_info",
115
- "run_command", # Disabled: unreliable completion detection, use run_command_tracked instead
116
115
  # Recursion guard: a general sub-agent may not spawn further sub-agents
117
116
  *ToolCollection.agent_dispatch_tools,
118
117
  ]
@@ -0,0 +1,129 @@
1
+ import asyncio
2
+ from types import SimpleNamespace
3
+
4
+ import pytest
5
+
6
+ from kolega_code.sandbox.terminal import SandboxTerminalManager
7
+
8
+
9
+ class FakeCommandHandle:
10
+ def __init__(self, pid: int = 123, exit_code: int = 0):
11
+ self.pid = pid
12
+ self._done = asyncio.Event()
13
+ self._exit_code = exit_code
14
+ self.killed = False
15
+
16
+ async def wait(self):
17
+ await self._done.wait()
18
+ return SimpleNamespace(exit_code=self._exit_code)
19
+
20
+ def complete(self) -> None:
21
+ self._done.set()
22
+
23
+ async def kill(self) -> None:
24
+ self.killed = True
25
+ self._exit_code = 137
26
+ self._done.set()
27
+
28
+
29
+ class FakeCommands:
30
+ def __init__(self):
31
+ self.handle = FakeCommandHandle()
32
+ self.run_calls = []
33
+ self.send_stdin_calls = []
34
+ self.started = asyncio.Event()
35
+
36
+ async def run(self, command: str, **kwargs):
37
+ if command.startswith("test -d") or command.startswith("mkdir"):
38
+ return SimpleNamespace(exit_code=0)
39
+
40
+ self.run_calls.append((command, kwargs))
41
+ self.started.set()
42
+ on_stdout = kwargs.get("on_stdout")
43
+ if on_stdout:
44
+ await on_stdout("line1\n")
45
+ return self.handle
46
+
47
+ async def send_stdin(self, pid: int, data: str):
48
+ self.send_stdin_calls.append((pid, data))
49
+
50
+
51
+ class FakeSandbox:
52
+ def __init__(self):
53
+ self.commands = FakeCommands()
54
+
55
+
56
+ @pytest.mark.asyncio
57
+ async def test_exec_command_runs_background_with_stdin():
58
+ sandbox = FakeSandbox()
59
+ manager = SandboxTerminalManager(sandbox, "workspace", "thread")
60
+
61
+ result = await manager.exec_command("python prompt.py", yield_time_ms=300)
62
+
63
+ assert sandbox.commands.run_calls
64
+ _, kwargs = sandbox.commands.run_calls[0]
65
+ assert kwargs["background"] is True
66
+ assert kwargs["stdin"] is True
67
+ # Still waiting on input -> reported as a running session.
68
+ assert result.status == "running"
69
+ assert result.session_id is not None
70
+ assert "line1" in result.output
71
+
72
+
73
+ @pytest.mark.asyncio
74
+ async def test_write_stdin_sends_raw_input_and_reports_exit():
75
+ sandbox = FakeSandbox()
76
+ manager = SandboxTerminalManager(sandbox, "workspace", "thread")
77
+
78
+ result = await manager.exec_command("python prompt.py", yield_time_ms=300)
79
+ session_id = result.session_id
80
+
81
+ running = await manager.write_stdin(session_id, "Ada\n", yield_time_ms=200)
82
+ assert (123, "Ada\n") in sandbox.commands.send_stdin_calls
83
+ assert running.status == "running"
84
+
85
+ sandbox.commands.handle.complete()
86
+ final = await manager.write_stdin(session_id, "", yield_time_ms=2000)
87
+ assert final.status == "exited"
88
+ assert final.exit_code == 0
89
+
90
+
91
+ @pytest.mark.asyncio
92
+ async def test_kill_session_kills_handle():
93
+ sandbox = FakeSandbox()
94
+ manager = SandboxTerminalManager(sandbox, "workspace", "thread")
95
+
96
+ result = await manager.exec_command("sleep 100", yield_time_ms=200)
97
+ assert result.status == "running"
98
+
99
+ killed = await manager.kill_session(result.session_id, "TERM")
100
+ assert sandbox.commands.handle.killed is True
101
+ assert killed.status == "exited"
102
+
103
+
104
+ @pytest.mark.asyncio
105
+ async def test_kill_session_interrupt_sends_ctrl_c():
106
+ sandbox = FakeSandbox()
107
+ manager = SandboxTerminalManager(sandbox, "workspace", "thread")
108
+
109
+ result = await manager.exec_command("sleep 100", yield_time_ms=200)
110
+ await manager.kill_session(result.session_id, "INT")
111
+ assert (123, "\x03") in sandbox.commands.send_stdin_calls
112
+
113
+
114
+ @pytest.mark.asyncio
115
+ async def test_list_sessions_reports_running():
116
+ sandbox = FakeSandbox()
117
+ manager = SandboxTerminalManager(sandbox, "workspace", "thread")
118
+
119
+ result = await manager.exec_command("sleep 100", yield_time_ms=200)
120
+ sessions = await manager.list_sessions()
121
+ assert result.session_id in sessions
122
+ assert sessions[result.session_id]["running"] is True
123
+
124
+
125
+ @pytest.mark.asyncio
126
+ async def test_write_stdin_unknown_session_raises():
127
+ manager = SandboxTerminalManager(FakeSandbox(), "workspace", "thread")
128
+ with pytest.raises(KeyError):
129
+ await manager.write_stdin("nope", "x")
@@ -0,0 +1,139 @@
1
+ import pytest
2
+
3
+ from kolega_code.services.terminal import LocalTerminalManager
4
+
5
+
6
+ @pytest.fixture
7
+ def manager():
8
+ # connection_manager=None: output broadcasting is skipped in tests.
9
+ return LocalTerminalManager("workspace", "thread", None)
10
+
11
+
12
+ @pytest.mark.asyncio
13
+ async def test_exec_command_success(manager):
14
+ result = await manager.exec_command("echo hello world", yield_time_ms=5000)
15
+ assert result.status == "exited"
16
+ assert result.exit_code == 0
17
+ assert "hello world" in result.output
18
+ assert result.session_id is None
19
+
20
+
21
+ @pytest.mark.asyncio
22
+ async def test_exec_command_nonzero_exit_code(manager):
23
+ result = await manager.exec_command("exit 7", yield_time_ms=5000)
24
+ assert result.status == "exited"
25
+ assert result.exit_code == 7
26
+
27
+
28
+ @pytest.mark.asyncio
29
+ async def test_exec_command_failing_command_is_nonzero(manager):
30
+ result = await manager.exec_command("ls /this_path_does_not_exist_xyz", yield_time_ms=5000)
31
+ assert result.status == "exited"
32
+ assert result.exit_code != 0
33
+
34
+
35
+ @pytest.mark.asyncio
36
+ async def test_long_running_returns_session_then_completes(manager):
37
+ result = await manager.exec_command("echo start; sleep 1; echo done", yield_time_ms=250)
38
+ assert result.status == "running"
39
+ assert result.session_id is not None
40
+ session_id = result.session_id
41
+
42
+ for _ in range(40):
43
+ result = await manager.write_stdin(session_id, "", yield_time_ms=2000)
44
+ if result.status == "exited":
45
+ break
46
+ assert result.status == "exited"
47
+ assert result.exit_code == 0
48
+ assert "done" in result.output
49
+
50
+
51
+ @pytest.mark.asyncio
52
+ async def test_interactive_stdin(manager):
53
+ result = await manager.exec_command('printf "P> "; read x; echo got=$x', yield_time_ms=400)
54
+ assert result.status == "running"
55
+ assert "P>" in result.output
56
+
57
+ result = await manager.write_stdin(result.session_id, "ada\n", yield_time_ms=3000)
58
+ assert result.status == "exited"
59
+ assert "got=ada" in result.output
60
+
61
+
62
+ @pytest.mark.asyncio
63
+ async def test_kill_session_interrupt_reports_130(manager):
64
+ result = await manager.exec_command("sleep 30", yield_time_ms=300)
65
+ assert result.status == "running"
66
+ killed = await manager.kill_session(result.session_id, "INT")
67
+ assert killed.status == "exited"
68
+ assert killed.exit_code == 130
69
+
70
+
71
+ @pytest.mark.asyncio
72
+ async def test_kill_session_term(manager):
73
+ result = await manager.exec_command("sleep 30", yield_time_ms=300)
74
+ killed = await manager.kill_session(result.session_id, "TERM")
75
+ assert killed.status == "exited"
76
+ # SIGTERM -> 143, or SIGKILL fallback -> 137
77
+ assert killed.exit_code in (143, 137)
78
+
79
+
80
+ @pytest.mark.asyncio
81
+ async def test_list_sessions_tracks_running_and_clears(manager):
82
+ result = await manager.exec_command("sleep 5", yield_time_ms=200)
83
+ sessions = await manager.list_sessions()
84
+ assert result.session_id in sessions
85
+ assert sessions[result.session_id]["running"] is True
86
+
87
+ await manager.kill_session(result.session_id, "TERM")
88
+ assert result.session_id not in await manager.list_sessions()
89
+
90
+
91
+ @pytest.mark.asyncio
92
+ async def test_write_stdin_unknown_session_raises(manager):
93
+ with pytest.raises(KeyError):
94
+ await manager.write_stdin("does_not_exist")
95
+
96
+
97
+ @pytest.mark.asyncio
98
+ async def test_kill_unknown_session_raises(manager):
99
+ with pytest.raises(KeyError):
100
+ await manager.kill_session("does_not_exist")
101
+
102
+
103
+ @pytest.mark.asyncio
104
+ async def test_run_command_convenience_accumulates_output(manager):
105
+ output = await manager.run_command("echo a; echo b; echo c")
106
+ assert "a" in output and "b" in output and "c" in output
107
+
108
+
109
+ @pytest.mark.asyncio
110
+ async def test_workdir_is_respected(manager, tmp_path):
111
+ result = await manager.exec_command("pwd", workdir=str(tmp_path), yield_time_ms=3000)
112
+ assert result.status == "exited"
113
+ # macOS resolves symlinks (/var -> /private/var); the leaf dir is enough.
114
+ assert result.output.strip().endswith(tmp_path.name)
115
+
116
+
117
+ @pytest.mark.asyncio
118
+ async def test_no_cwd_persistence_between_calls(manager, tmp_path):
119
+ sub = tmp_path / "sub"
120
+ sub.mkdir()
121
+ # cd in one call must NOT affect the next (fresh process per exec).
122
+ await manager.exec_command(f"cd {sub}", workdir=str(tmp_path), yield_time_ms=3000)
123
+ result = await manager.exec_command("pwd", workdir=str(tmp_path), yield_time_ms=3000)
124
+ assert result.output.strip().endswith(tmp_path.name)
125
+
126
+
127
+ @pytest.mark.asyncio
128
+ async def test_clean_env_overlay(manager):
129
+ result = await manager.exec_command("echo $NO_COLOR-$TERM-$PAGER", yield_time_ms=3000)
130
+ assert "1-dumb-cat" in result.output
131
+
132
+
133
+ @pytest.mark.asyncio
134
+ async def test_close_all_terminates_sessions(manager):
135
+ await manager.exec_command("sleep 30", yield_time_ms=200)
136
+ await manager.exec_command("sleep 30", yield_time_ms=200)
137
+ assert len(manager.sessions) == 2
138
+ await manager.close_all()
139
+ assert len(manager.sessions) == 0
@@ -0,0 +1,71 @@
1
+ from kolega_code.services.terminal_buffer import (
2
+ MAX_POLL_MS,
3
+ MAX_YIELD_MS,
4
+ MIN_POLL_MS,
5
+ MIN_YIELD_MS,
6
+ HeadTailBuffer,
7
+ cap_tokens,
8
+ clamp_yield,
9
+ )
10
+
11
+
12
+ def test_headtail_no_truncation():
13
+ buffer = HeadTailBuffer(head_bytes=10, tail_bytes=10)
14
+ buffer.append(b"hello")
15
+ assert buffer.text() == "hello"
16
+ assert buffer.omitted_bytes == 0
17
+
18
+
19
+ def test_headtail_truncates_middle_with_marker():
20
+ buffer = HeadTailBuffer(head_bytes=5, tail_bytes=5)
21
+ buffer.append(b"A" * 5 + b"B" * 100 + b"C" * 5)
22
+ text = buffer.text()
23
+ assert text.startswith("AAAAA")
24
+ assert text.endswith("CCCCC")
25
+ assert "omitted" in text
26
+ assert buffer.omitted_bytes == 100
27
+
28
+
29
+ def test_headtail_append_in_chunks():
30
+ buffer = HeadTailBuffer(head_bytes=4, tail_bytes=4)
31
+ for chunk in (b"12", b"34", b"56", b"78", b"90"):
32
+ buffer.append(chunk)
33
+ text = buffer.text()
34
+ assert text.startswith("1234")
35
+ assert text.endswith("7890")
36
+ assert buffer.total_bytes == 10
37
+ assert buffer.omitted_bytes == 2
38
+
39
+
40
+ def test_headtail_reset():
41
+ buffer = HeadTailBuffer()
42
+ buffer.append(b"x")
43
+ buffer.reset()
44
+ assert buffer.text() == ""
45
+ assert len(buffer) == 0
46
+
47
+
48
+ def test_cap_tokens_under_budget():
49
+ out = cap_tokens("short text", 1000)
50
+ assert out.truncated is False
51
+ assert out.text == "short text"
52
+
53
+
54
+ def test_cap_tokens_over_budget_truncates_middle():
55
+ text = "word " * 5000
56
+ out = cap_tokens(text, 50)
57
+ assert out.truncated is True
58
+ assert out.original_token_count > 50
59
+ assert "truncated to fit" in out.text
60
+ assert len(out.text) < len(text)
61
+
62
+
63
+ def test_clamp_yield_write():
64
+ assert clamp_yield(10, poll=False) == MIN_YIELD_MS
65
+ assert clamp_yield(999999, poll=False) == MAX_YIELD_MS
66
+ assert clamp_yield(None, poll=False) == 10000
67
+
68
+
69
+ def test_clamp_yield_poll():
70
+ assert clamp_yield(10, poll=True) == MIN_POLL_MS
71
+ assert clamp_yield(999999, poll=True) == MAX_POLL_MS
@@ -195,7 +195,7 @@ def test_general_agent_tool_inventory(project_path, mock_connection_manager, age
195
195
  assert "search_codebase" in tool_names
196
196
  assert "create_file" in tool_names
197
197
  assert "replace_entire_file" in tool_names
198
- assert "run_command_tracked" in tool_names
198
+ assert "exec_command" in tool_names
199
199
  # Recursion guard: no dispatch tools at all
200
200
  assert not any(name.startswith("dispatch_") for name in tool_names)
201
201
 
@@ -237,7 +237,7 @@ def test_planning_agent_exposes_read_only_and_planning_tools(project_path, mock_
237
237
  assert "create_file" not in tool_names
238
238
  assert "replace_entire_file" not in tool_names
239
239
  assert "apply_patch" not in tool_names
240
- assert "run_command_tracked" not in tool_names
240
+ assert "exec_command" not in tool_names
241
241
  assert tool_names - expected_planning_tools <= set(agent.tool_collection.read_only_tools)
242
242
 
243
243
 
@@ -46,7 +46,7 @@ def agent_config():
46
46
 
47
47
  def test_permission_store_matches_command_rules(tmp_path):
48
48
  request = permission_request_for_tool(
49
- "run_command_tracked",
49
+ "exec_command",
50
50
  {"command": "npm run test -- --watch=false"},
51
51
  )
52
52
  assert request is not None
@@ -67,7 +67,7 @@ def test_permission_store_matches_command_rules(tmp_path):
67
67
 
68
68
 
69
69
  def test_allow_rule_options_for_command_include_exact_prefix_and_executable():
70
- request = permission_request_for_tool("run_command_tracked", {"command": "npm run test"})
70
+ request = permission_request_for_tool("exec_command", {"command": "npm run test"})
71
71
  assert request is not None
72
72
 
73
73
  options = allow_rule_options(request)
@@ -100,8 +100,8 @@ async def test_execute_single_tool_denies_gated_tool_before_dispatch(tmp_path, a
100
100
  return ToolRegistry(
101
101
  [
102
102
  Tool(
103
- name="run_command_tracked",
104
- definition=ToolDefinition(name="run_command_tracked", description="", parameters=[]),
103
+ name="exec_command",
104
+ definition=ToolDefinition(name="exec_command", description="", parameters=[]),
105
105
  handler=handler,
106
106
  )
107
107
  ]
@@ -127,8 +127,8 @@ async def test_execute_single_tool_denies_gated_tool_before_dispatch(tmp_path, a
127
127
  result = await agent.execute_single_tool(
128
128
  ToolCall(
129
129
  id="tool_1",
130
- name="run_command_tracked",
131
- input={"terminal_id": "term", "command": "npm run test", "purpose": "test"},
130
+ name="exec_command",
131
+ input={"command": "npm run test"},
132
132
  execution_id="exec_1",
133
133
  )
134
134
  )
@@ -82,7 +82,7 @@ class TestToolCollectionRegistry:
82
82
  assert registry.get("read_entire_file").parallel_safe
83
83
  assert registry.get("search_codebase").parallel_safe
84
84
  assert not registry.get("create_file").parallel_safe
85
- assert not registry.get("run_command_tracked").parallel_safe
85
+ assert not registry.get("exec_command").parallel_safe
86
86
 
87
87
  def test_get_tool_list_matches_registry_definitions(self, tmp_path):
88
88
  from unittest.mock import Mock
@@ -71,7 +71,7 @@ def tool_collection(
71
71
  collection.search_codebase_tool.search_codebase = AsyncMock()
72
72
  collection.glob_tool.find_files_by_pattern = AsyncMock()
73
73
  collection.web_fetch_tool.web_fetch = AsyncMock()
74
- collection.terminal_tool.send_terminal_input = AsyncMock()
74
+ collection.terminal_tool.write_stdin = AsyncMock()
75
75
 
76
76
  return collection
77
77
 
@@ -214,15 +214,15 @@ class TestToolCollection:
214
214
  assert result == expected_response
215
215
  tool_collection.terminal_tool.execute_terminal_command.assert_called_once_with(command)
216
216
 
217
- async def test_send_terminal_input(self, tool_collection: AsyncMock) -> None:
218
- expected_response = "Sent input"
219
- tool_collection.terminal_tool.send_terminal_input.return_value = expected_response
217
+ async def test_write_stdin(self, tool_collection: AsyncMock) -> None:
218
+ expected_response = '{"status": "running"}'
219
+ tool_collection.terminal_tool.write_stdin.return_value = expected_response
220
220
 
221
- result = await tool_collection.send_terminal_input("terminal_1", "Ada", submit=True, command_id="cmd_1")
221
+ result = await tool_collection.write_stdin("s_1", "Ada\n")
222
222
 
223
223
  assert result == expected_response
224
- tool_collection.terminal_tool.send_terminal_input.assert_called_once_with(
225
- "terminal_1", "Ada", submit=True, command_id="cmd_1"
224
+ tool_collection.terminal_tool.write_stdin.assert_called_once_with(
225
+ "s_1", "Ada\n", yield_time_ms=10000, max_output_tokens=10000
226
226
  )
227
227
 
228
228
  async def test_read_entire_file(self, tool_collection: AsyncMock) -> None:
@@ -457,7 +457,8 @@ class TestToolCollection:
457
457
  # Check that excluded tools are not in the list
458
458
  excluded_tools = tool_collection.tool_exclusions
459
459
  tool_names = [tool.name for tool in tool_list]
460
- assert "send_terminal_input" in tool_names
460
+ assert "exec_command" in tool_names
461
+ assert "write_stdin" in tool_names
461
462
  for excluded_tool in excluded_tools:
462
463
  assert excluded_tool not in tool_names
463
464