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.
- {kolega_code-0.5.0 → kolega_code-0.5.1}/PKG-INFO +1 -1
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/__init__.py +1 -1
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/__init__.py +1 -1
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/coder.py +0 -1
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/generalagent.py +0 -1
- kolega_code-0.5.1/kolega_code/agent/tests/services/test_sandbox_terminal_input.py +129 -0
- kolega_code-0.5.1/kolega_code/agent/tests/services/test_terminal.py +139 -0
- kolega_code-0.5.1/kolega_code/agent/tests/services/test_terminal_buffer.py +71 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/test_agent_tools_inventory.py +2 -2
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/test_permissions.py +6 -6
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/test_tool_registry.py +1 -1
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/test_tools.py +9 -8
- kolega_code-0.5.1/kolega_code/agent/tests/tool_backend/test_terminal_tool.py +244 -0
- kolega_code-0.5.1/kolega_code/agent/tool_backend/terminal_tool.py +395 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tools.py +70 -204
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/cli/tests/test_app.py +1 -1
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/permissions.py +1 -2
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/sandbox/terminal.py +157 -1
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/services/base.py +66 -88
- kolega_code-0.5.1/kolega_code/services/terminal.py +495 -0
- kolega_code-0.5.1/kolega_code/services/terminal_buffer.py +164 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/pyproject.toml +1 -1
- {kolega_code-0.5.0 → kolega_code-0.5.1}/uv.lock +2 -2
- kolega_code-0.5.0/kolega_code/agent/tests/services/test_sandbox_terminal_input.py +0 -98
- kolega_code-0.5.0/kolega_code/agent/tests/services/test_terminal.py +0 -154
- kolega_code-0.5.0/kolega_code/agent/tests/services/test_terminal_command_tracking.py +0 -385
- kolega_code-0.5.0/kolega_code/agent/tests/tool_backend/test_terminal_tool.py +0 -482
- kolega_code-0.5.0/kolega_code/agent/tool_backend/terminal_tool.py +0 -643
- kolega_code-0.5.0/kolega_code/services/terminal.py +0 -903
- {kolega_code-0.5.0 → kolega_code-0.5.1}/.env.example +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/.github/workflows/ci.yml +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/.github/workflows/docs.yml +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/.github/workflows/release.yml +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/.gitignore +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/CONTRIBUTING.md +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/LICENSE +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/README.md +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/RELEASING.md +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/SECURITY.md +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/conftest.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/docs/.gitignore +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/docs/README.md +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/docs/astro.config.mjs +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/docs/package-lock.json +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/docs/package.json +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/docs/public/favicon.svg +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/docs/src/assets/kolega-dark.svg +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/docs/src/assets/kolega-light.svg +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/docs/src/content/docs/cli/ask.md +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/docs/src/content/docs/cli/doctor.md +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/docs/src/content/docs/cli/overview.md +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/docs/src/content/docs/cli/sessions.md +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/docs/src/content/docs/concepts/agents.md +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/docs/src/content/docs/concepts/how-it-works.md +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/docs/src/content/docs/concepts/tools.md +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/docs/src/content/docs/configuration/environment-variables.md +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/docs/src/content/docs/configuration/providers-and-models.mdx +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/docs/src/content/docs/configuration/settings-and-api-keys.mdx +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/docs/src/content/docs/getting-started/installation.mdx +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/docs/src/content/docs/getting-started/quick-start.mdx +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/docs/src/content/docs/gigacode.mdx +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/docs/src/content/docs/hooks.md +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/docs/src/content/docs/index.mdx +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/docs/src/content/docs/skills.mdx +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/docs/src/content/docs/tui/composer.md +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/docs/src/content/docs/tui/interface.md +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/docs/src/content/docs/tui/modes.md +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/docs/src/content/docs/tui/sessions-and-resume.md +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/docs/src/content/docs/tui/slash-commands.md +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/docs/src/content.config.ts +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/docs/src/styles/brand.css +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/docs/tsconfig.json +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/baseagent.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/browseragent.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/common.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/compression.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/context.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/conversation.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/investigationagent.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/orchestration/__init__.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/orchestration/budget.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/orchestration/errors.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/orchestration/executor.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/orchestration/guide.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/orchestration/journal.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/orchestration/runtime.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/orchestration/tests/__init__.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/orchestration/tests/test_orchestration.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/orchestration/types.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/planningagent.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/prompt_provider.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/prompt_templates/auxiliary/compression/summary.system.md +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/prompt_templates/auxiliary/compression/summary.user.md.j2 +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/prompt_templates/auxiliary/terminal/output_summary.system.md +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/prompt_templates/auxiliary/terminal/safety.system.md +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/prompt_templates/auxiliary/tools/think_hard.system.md +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/prompt_templates/extensions/cli/planning_questions.md +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/prompt_templates/extensions/cli/shared_task_list.md +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/prompt_templates/extensions/skills/catalog.md.j2 +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/prompt_templates/system/agents/browser.md.j2 +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/prompt_templates/system/agents/coder_cli.md.j2 +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/prompt_templates/system/agents/general.md.j2 +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/prompt_templates/system/agents/investigation.md.j2 +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/prompt_templates/system/agents/planning.md.j2 +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/prompt_templates/system/includes/agents_md_instructions.md +0 -0
- {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
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/prompt_templates/system/includes/frontend_guidance.md +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/prompt_templates/system/includes/starter_templates/expo.md +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/prompt_templates/system/includes/starter_templates/html_website.md +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/prompt_templates/system/includes/starter_templates/mern_stack.md +0 -0
- {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
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/prompt_templates/user_tasks/cli/implement_plan.md.j2 +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/prompt_templates/user_tasks/cli/init_agents.md.j2 +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/prompts.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/__init__.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/llm/__init__.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/llm/test_anthropic_token_counting.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/llm/test_billing_openai_cache.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/llm/test_client.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/llm/test_dashscope_mapping.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/llm/test_error_boundary.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/llm/test_exceptions.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/llm/test_google_thought_signature.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/llm/test_google_tool_signature_live.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/llm/test_instrumented_client.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/llm/test_instrumented_client_integration.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/llm/test_langfuse_normalization.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/llm/test_live_providers.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/llm/test_model_specs.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/llm/test_openai_cached_tokens.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/llm/test_openai_cached_tokens_stream.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/llm/test_openai_message_conversion.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/llm/test_openai_token_counting.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/llm/test_thinking_effort.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/llm/test_tool_execution_ids.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/services/__init__.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/services/test_browser.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/services/test_browser_parity.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/services/test_file_system.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/services/test_terminal_state_serializer.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/test_base_agent.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/test_coder_attachments.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/test_coder_prompt_extensions.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/test_commands.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/test_duplicate_tool_results.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/test_empty_message_handling.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/test_general_agent.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/test_gigacode_gating.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/test_hooks_integration.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/test_html.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/test_parallel_tool_calls.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/test_planning_agent.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/test_prompt_provider.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/test_prompts.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/tool_backend/__init__.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/tool_backend/test_agent_tool.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/tool_backend/test_base_tool.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/tool_backend/test_browser_tool.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/tool_backend/test_build_tool.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/tool_backend/test_create_file_tool.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/tool_backend/test_glob_tool.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/tool_backend/test_glob_tool_sandbox_parity.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/tool_backend/test_list_directory_tool.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/tool_backend/test_read_file_tool.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/tool_backend/test_replace_entire_file_tool.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/tool_backend/test_replace_lines_tool.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/tool_backend/test_search_and_replace_tool.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/tool_backend/test_search_codebase_tool.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/tool_backend/test_think_hard_integration.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/tool_backend/test_think_hard_streaming.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/tool_backend/test_web_fetch_tool.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/tool_backend/test_workflow_tool.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tool_backend/agent_tool.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tool_backend/apply_patch_tool.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tool_backend/base_tool.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tool_backend/browser_tool.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tool_backend/build_tool.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tool_backend/create_file_tool.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tool_backend/glob_tool.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tool_backend/list_directory_tool.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tool_backend/memory_tool.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tool_backend/read_file_tool.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tool_backend/replace_entire_file_tool.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tool_backend/replace_lines_tool.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tool_backend/search_and_replace_tool.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tool_backend/search_codebase_tool.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tool_backend/streaming_tool.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tool_backend/think_hard_tool.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tool_backend/web_fetch_tool.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tool_backend/workflow_tool.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/utils/commands.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/cli/__init__.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/cli/app.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/cli/config.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/cli/connection.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/cli/file_index.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/cli/main.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/cli/mentions.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/cli/messages.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/cli/provider_registry.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/cli/session_store.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/cli/settings.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/cli/skills.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/cli/slash_commands.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/cli/tests/__init__.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/cli/tests/test_cli_config.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/cli/tests/test_connection.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/cli/tests/test_file_index.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/cli/tests/test_main.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/cli/tests/test_mentions.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/cli/tests/test_session_store.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/cli/tests/test_settings.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/cli/tests/test_skills.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/cli/tests/test_slash_commands.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/cli/tests/test_themes.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/cli/tests/test_updater.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/cli/theme.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/cli/updater.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/config.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/events.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/hooks/__init__.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/hooks/backends.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/hooks/config.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/hooks/dispatcher.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/hooks/events.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/hooks/matcher.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/hooks/outcome.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/hooks/tests/__init__.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/hooks/tests/test_hooks.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/llm/client.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/llm/exceptions.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/llm/instrumented_client.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/llm/models.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/llm/providers/__init__.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/llm/providers/anthropic.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/llm/providers/base.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/llm/providers/google.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/llm/providers/models.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/llm/providers/openai.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/llm/ratelimit.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/llm/specs.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/llm/tool_execution_ids.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/models/__init__.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/models/sandbox_terminal_state.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/runtime.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/sandbox/README.md +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/sandbox/__init__.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/sandbox/async_filesystem.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/sandbox/base.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/sandbox/browser.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/sandbox/event_loop.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/sandbox/filesystem.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/sandbox/local.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/sandbox/serializer.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/sandbox/utils.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/services/browser.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/services/file_system.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/services/html.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/tools/__init__.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/tools/core.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/tools/definitions.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/tools/registry.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/tools/tests/__init__.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/tools/tests/test_definitions.py +0 -0
- {kolega_code-0.5.0 → kolega_code-0.5.1}/run_tests.sh +0 -0
- {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.
|
|
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/
|
|
@@ -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
|
{kolega_code-0.5.0 → kolega_code-0.5.1}/kolega_code/agent/tests/test_agent_tools_inventory.py
RENAMED
|
@@ -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 "
|
|
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 "
|
|
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
|
-
"
|
|
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("
|
|
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="
|
|
104
|
-
definition=ToolDefinition(name="
|
|
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="
|
|
131
|
-
input={"
|
|
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("
|
|
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.
|
|
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
|
|
218
|
-
expected_response = "
|
|
219
|
-
tool_collection.terminal_tool.
|
|
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.
|
|
221
|
+
result = await tool_collection.write_stdin("s_1", "Ada\n")
|
|
222
222
|
|
|
223
223
|
assert result == expected_response
|
|
224
|
-
tool_collection.terminal_tool.
|
|
225
|
-
"
|
|
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 "
|
|
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
|
|