kolega-code 0.7.1__tar.gz → 0.7.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {kolega_code-0.7.1 → kolega_code-0.7.2}/PKG-INFO +1 -1
- {kolega_code-0.7.1 → kolega_code-0.7.2}/RELEASING.md +6 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/docs/src/content/docs/hooks.md +1 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/__init__.py +1 -1
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/__init__.py +1 -1
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/baseagent.py +62 -34
- kolega_code-0.7.2/kolega_code/agent/compression.py +146 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/conversation.py +131 -20
- kolega_code-0.7.2/kolega_code/agent/prompt_templates/auxiliary/compression/summary.system.md +28 -0
- kolega_code-0.7.2/kolega_code/agent/prompt_templates/auxiliary/compression/summary.user.md.j2 +17 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/prompts.py +7 -3
- kolega_code-0.7.2/kolega_code/agent/tests/compaction_helpers.py +189 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tests/test_base_agent.py +75 -68
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tests/test_commands.py +51 -5
- kolega_code-0.7.2/kolega_code/agent/tests/test_compaction_integration.py +66 -0
- kolega_code-0.7.2/kolega_code/agent/tests/test_compaction_persistence.py +43 -0
- kolega_code-0.7.2/kolega_code/agent/tests/test_compaction_subagent.py +26 -0
- kolega_code-0.7.2/kolega_code/agent/tests/test_compaction_tui_events.py +98 -0
- kolega_code-0.7.2/kolega_code/agent/tests/test_compress_command.py +41 -0
- kolega_code-0.7.2/kolega_code/agent/tests/test_compression.py +143 -0
- kolega_code-0.7.2/kolega_code/agent/tests/test_conversation_compaction.py +192 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tests/test_prompts.py +1 -1
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/utils/commands.py +26 -6
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/cli/app.py +212 -21
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/cli/main.py +3 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/cli/messages.py +2 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/cli/session_store.py +3 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/cli/tests/test_app.py +761 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/cli/tests/test_session_store.py +27 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/events.py +20 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/hooks/events.py +1 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/llm/specs.py +4 -4
- {kolega_code-0.7.1 → kolega_code-0.7.2}/pyproject.toml +1 -1
- {kolega_code-0.7.1 → kolega_code-0.7.2}/uv.lock +2 -2
- kolega_code-0.7.1/kolega_code/agent/compression.py +0 -81
- kolega_code-0.7.1/kolega_code/agent/prompt_templates/auxiliary/compression/summary.system.md +0 -59
- kolega_code-0.7.1/kolega_code/agent/prompt_templates/auxiliary/compression/summary.user.md.j2 +0 -18
- {kolega_code-0.7.1 → kolega_code-0.7.2}/.env.example +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/.github/workflows/ci.yml +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/.github/workflows/docs.yml +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/.github/workflows/release.yml +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/.gitignore +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/CONTRIBUTING.md +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/LICENSE +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/README.md +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/SECURITY.md +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/conftest.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/docs/.gitignore +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/docs/README.md +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/docs/astro.config.mjs +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/docs/package-lock.json +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/docs/package.json +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/docs/public/favicon.svg +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/docs/src/assets/demo.gif +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/docs/src/assets/kolega-dark.svg +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/docs/src/assets/kolega-light.svg +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/docs/src/content/docs/cli/ask.md +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/docs/src/content/docs/cli/doctor.md +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/docs/src/content/docs/cli/overview.md +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/docs/src/content/docs/cli/sessions.md +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/docs/src/content/docs/concepts/agents.md +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/docs/src/content/docs/concepts/how-it-works.md +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/docs/src/content/docs/concepts/tools.md +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/docs/src/content/docs/configuration/environment-variables.md +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/docs/src/content/docs/configuration/providers-and-models.mdx +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/docs/src/content/docs/configuration/settings-and-api-keys.mdx +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/docs/src/content/docs/configuration/sign-in-with-chatgpt.mdx +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/docs/src/content/docs/getting-started/installation.mdx +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/docs/src/content/docs/getting-started/quick-start.mdx +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/docs/src/content/docs/gigacode.mdx +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/docs/src/content/docs/index.mdx +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/docs/src/content/docs/skills.mdx +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/docs/src/content/docs/tui/composer.md +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/docs/src/content/docs/tui/interface.md +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/docs/src/content/docs/tui/modes.md +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/docs/src/content/docs/tui/sessions-and-resume.md +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/docs/src/content/docs/tui/slash-commands.md +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/docs/src/content.config.ts +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/docs/src/styles/brand.css +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/docs/tsconfig.json +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/browseragent.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/coder.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/common.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/context.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/generalagent.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/investigationagent.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/orchestration/__init__.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/orchestration/budget.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/orchestration/errors.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/orchestration/executor.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/orchestration/guide.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/orchestration/journal.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/orchestration/runtime.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/orchestration/tests/__init__.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/orchestration/tests/test_orchestration.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/orchestration/types.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/planningagent.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/prompt_provider.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/prompt_templates/auxiliary/terminal/output_summary.system.md +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/prompt_templates/auxiliary/terminal/safety.system.md +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/prompt_templates/auxiliary/tools/think_hard.system.md +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/prompt_templates/extensions/cli/planning_questions.md +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/prompt_templates/extensions/cli/shared_task_list.md +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/prompt_templates/extensions/skills/catalog.md.j2 +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/prompt_templates/system/agents/browser.md.j2 +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/prompt_templates/system/agents/coder_cli.md.j2 +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/prompt_templates/system/agents/general.md.j2 +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/prompt_templates/system/agents/investigation.md.j2 +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/prompt_templates/system/agents/planning.md.j2 +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/prompt_templates/system/includes/agents_md_instructions.md +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/prompt_templates/system/includes/environment_variables/workspace_env_vars.md +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/prompt_templates/system/includes/frontend_guidance.md +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/prompt_templates/system/includes/starter_templates/expo.md +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/prompt_templates/system/includes/starter_templates/html_website.md +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/prompt_templates/system/includes/starter_templates/mern_stack.md +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/prompt_templates/system/includes/starter_templates/react_vite_shadcdn.md +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/prompt_templates/user_tasks/cli/implement_plan.md.j2 +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/prompt_templates/user_tasks/cli/init_agents.md.j2 +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tests/__init__.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tests/llm/__init__.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tests/llm/test_anthropic_token_counting.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tests/llm/test_billing_openai_cache.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tests/llm/test_chatgpt_live.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tests/llm/test_chatgpt_oauth_provider.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tests/llm/test_client.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tests/llm/test_dashscope_mapping.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tests/llm/test_error_boundary.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tests/llm/test_exceptions.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tests/llm/test_google_thought_signature.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tests/llm/test_google_tool_signature_live.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tests/llm/test_instrumented_client.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tests/llm/test_instrumented_client_integration.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tests/llm/test_langfuse_normalization.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tests/llm/test_live_providers.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tests/llm/test_model_specs.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tests/llm/test_openai_cached_tokens.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tests/llm/test_openai_cached_tokens_stream.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tests/llm/test_openai_message_conversion.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tests/llm/test_openai_token_counting.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tests/llm/test_thinking_effort.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tests/llm/test_tool_execution_ids.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tests/services/__init__.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tests/services/test_browser.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tests/services/test_browser_parity.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tests/services/test_file_system.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tests/services/test_sandbox_terminal_input.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tests/services/test_terminal.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tests/services/test_terminal_buffer.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tests/services/test_terminal_state_serializer.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tests/test_agent_tools_inventory.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tests/test_coder_attachments.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tests/test_coder_prompt_extensions.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tests/test_duplicate_tool_results.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tests/test_empty_message_handling.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tests/test_event_emitter.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tests/test_general_agent.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tests/test_gigacode_gating.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tests/test_hooks_integration.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tests/test_html.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tests/test_parallel_tool_calls.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tests/test_permissions.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tests/test_planning_agent.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tests/test_prompt_provider.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tests/test_tool_registry.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tests/test_tools.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tests/tool_backend/__init__.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tests/tool_backend/test_agent_tool.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tests/tool_backend/test_base_tool.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tests/tool_backend/test_browser_tool.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tests/tool_backend/test_build_tool.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tests/tool_backend/test_create_file_tool.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tests/tool_backend/test_edit_preview.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tests/tool_backend/test_glob_tool.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tests/tool_backend/test_glob_tool_sandbox_parity.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tests/tool_backend/test_list_directory_tool.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tests/tool_backend/test_read_file_tool.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tests/tool_backend/test_replace_entire_file_tool.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tests/tool_backend/test_replace_lines_tool.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tests/tool_backend/test_search_and_replace_tool.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tests/tool_backend/test_search_codebase_tool.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tests/tool_backend/test_terminal_tool.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tests/tool_backend/test_think_hard_integration.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tests/tool_backend/test_think_hard_streaming.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tests/tool_backend/test_web_fetch_tool.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tests/tool_backend/test_web_search_tool.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tests/tool_backend/test_workflow_tool.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tool_backend/agent_tool.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tool_backend/base_tool.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tool_backend/browser_tool.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tool_backend/build_tool.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tool_backend/create_file_tool.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tool_backend/edit_preview.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tool_backend/glob_tool.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tool_backend/list_directory_tool.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tool_backend/memory_tool.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tool_backend/read_file_tool.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tool_backend/replace_entire_file_tool.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tool_backend/replace_lines_tool.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tool_backend/search_and_replace_tool.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tool_backend/search_backends/__init__.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tool_backend/search_backends/base.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tool_backend/search_backends/duckduckgo.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tool_backend/search_backends/errors.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tool_backend/search_backends/firecrawl.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tool_backend/search_backends/models.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tool_backend/search_backends/searxng.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tool_backend/search_backends/tavily.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tool_backend/search_codebase_tool.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tool_backend/streaming_tool.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tool_backend/terminal_tool.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tool_backend/think_hard_tool.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tool_backend/web_fetch_tool.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tool_backend/web_search_tool.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tool_backend/workflow_tool.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/agent/tools.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/auth/__init__.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/auth/_jwt.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/auth/chatgpt_oauth.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/auth/constants.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/auth/tests/__init__.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/auth/tests/test_chatgpt_oauth.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/auth/tests/test_tokens.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/auth/tokens.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/cli/__init__.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/cli/config.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/cli/connection.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/cli/file_index.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/cli/mentions.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/cli/provider_registry.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/cli/settings.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/cli/skills.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/cli/slash_commands.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/cli/tests/__init__.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/cli/tests/test_cli_config.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/cli/tests/test_connection.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/cli/tests/test_file_index.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/cli/tests/test_main.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/cli/tests/test_mentions.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/cli/tests/test_settings.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/cli/tests/test_skills.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/cli/tests/test_slash_commands.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/cli/tests/test_themes.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/cli/tests/test_updater.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/cli/theme.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/cli/updater.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/config.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/hooks/__init__.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/hooks/backends.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/hooks/config.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/hooks/dispatcher.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/hooks/matcher.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/hooks/outcome.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/hooks/tests/__init__.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/hooks/tests/test_hooks.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/llm/client.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/llm/exceptions.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/llm/instrumented_client.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/llm/models.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/llm/providers/__init__.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/llm/providers/anthropic.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/llm/providers/base.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/llm/providers/chatgpt_oauth.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/llm/providers/google.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/llm/providers/models.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/llm/providers/openai.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/llm/ratelimit.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/llm/tool_execution_ids.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/models/__init__.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/models/sandbox_terminal_state.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/permissions.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/runtime.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/sandbox/README.md +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/sandbox/__init__.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/sandbox/async_filesystem.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/sandbox/base.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/sandbox/browser.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/sandbox/event_loop.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/sandbox/filesystem.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/sandbox/local.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/sandbox/serializer.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/sandbox/terminal.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/sandbox/utils.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/services/base.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/services/browser.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/services/file_system.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/services/html.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/services/terminal.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/services/terminal_buffer.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/tools/__init__.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/tools/core.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/tools/definitions.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/tools/registry.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/tools/tests/__init__.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/kolega_code/tools/tests/test_definitions.py +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/run_tests.sh +0 -0
- {kolega_code-0.7.1 → kolega_code-0.7.2}/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.7.
|
|
3
|
+
Version: 0.7.2
|
|
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/
|
|
@@ -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
|
|
@@ -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) |
|
|
@@ -11,7 +11,7 @@ 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
|
|
@@ -398,6 +398,14 @@ class BaseAgent(LogMixin):
|
|
|
398
398
|
"""Restores the message history from a list of dictionaries using custom methods."""
|
|
399
399
|
self.conversation.restore(serialized_history)
|
|
400
400
|
|
|
401
|
+
def dump_compaction_state(self) -> Dict[str, Any]:
|
|
402
|
+
"""Serialize the compaction boundary (summary + how many leading messages it folds)."""
|
|
403
|
+
return self.conversation.dump_compaction()
|
|
404
|
+
|
|
405
|
+
def restore_compaction_state(self, data: Optional[Dict[str, Any]]) -> None:
|
|
406
|
+
"""Restore the compaction boundary; must be called after restore_message_history."""
|
|
407
|
+
self.conversation.restore_compaction(data)
|
|
408
|
+
|
|
401
409
|
def _sanitize_oversized_tool_results(self) -> int:
|
|
402
410
|
return self.conversation.sanitize_oversized_tool_results()
|
|
403
411
|
|
|
@@ -549,9 +557,12 @@ class BaseAgent(LogMixin):
|
|
|
549
557
|
message=message,
|
|
550
558
|
)
|
|
551
559
|
|
|
552
|
-
async def compress_history(self) ->
|
|
560
|
+
async def compress_history(self) -> CompactionResult:
|
|
553
561
|
"""
|
|
554
|
-
Non-destructively summarize the current history
|
|
562
|
+
Non-destructively summarize the current history, keeping recent turns
|
|
563
|
+
verbatim. Emits a compaction_status event around the work so the UI can
|
|
564
|
+
show progress, then recounts + emits a context_update so the gauge
|
|
565
|
+
refreshes. Returns the structured outcome.
|
|
555
566
|
"""
|
|
556
567
|
|
|
557
568
|
async def on_info(message: str) -> None:
|
|
@@ -560,16 +571,34 @@ class BaseAgent(LogMixin):
|
|
|
560
571
|
async def on_error(message: str) -> None:
|
|
561
572
|
await self.log_error(message, sender=self.agent_name)
|
|
562
573
|
|
|
563
|
-
await self.
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
574
|
+
await self.emitter.compaction_status("started", "Compacting conversation…")
|
|
575
|
+
try:
|
|
576
|
+
result = await self.compressor.summarize(
|
|
577
|
+
self.conversation,
|
|
578
|
+
llm=self.llm,
|
|
579
|
+
model=self.primary_model_config.model,
|
|
580
|
+
temperature=self.model_default_temperature,
|
|
581
|
+
# No extended thinking: a bounded summary doesn't need it, and the
|
|
582
|
+
# thinking budget could otherwise exceed the small summary max_tokens.
|
|
583
|
+
thinking=None,
|
|
584
|
+
on_info=on_info,
|
|
585
|
+
on_error=on_error,
|
|
586
|
+
)
|
|
587
|
+
finally:
|
|
588
|
+
# Recount + emit so the context gauge reflects post-compaction reality
|
|
589
|
+
# (even on a no-op the UI may have been stale).
|
|
590
|
+
await self.count_current_context()
|
|
591
|
+
|
|
592
|
+
phase = "finished" if result.ok else "error"
|
|
593
|
+
summary_text = (
|
|
594
|
+
self.conversation.summary.get_text_content() if result.ok and self.conversation.summary else ""
|
|
572
595
|
)
|
|
596
|
+
await self.emitter.compaction_status(phase, result.message, summary=summary_text)
|
|
597
|
+
return result
|
|
598
|
+
|
|
599
|
+
def clear_history(self) -> None:
|
|
600
|
+
"""Drop all history and reset compaction state."""
|
|
601
|
+
self.conversation.clear()
|
|
573
602
|
|
|
574
603
|
# ------------------------------------------------------------------
|
|
575
604
|
# Tool execution
|
|
@@ -1136,9 +1165,8 @@ class BaseAgent(LogMixin):
|
|
|
1136
1165
|
#
|
|
1137
1166
|
# process_message_stream is the single canonical loop shared by every
|
|
1138
1167
|
# agent. Subclasses customize behavior through the hook methods below
|
|
1139
|
-
# (build_user_content,
|
|
1140
|
-
#
|
|
1141
|
-
# the loop itself.
|
|
1168
|
+
# (build_user_content, on_tool_use_start, should_stop_after_tools,
|
|
1169
|
+
# recap_agent_outcome) rather than overriding the loop itself.
|
|
1142
1170
|
# ------------------------------------------------------------------
|
|
1143
1171
|
|
|
1144
1172
|
completion_log_message = "Processing complete"
|
|
@@ -1166,21 +1194,6 @@ class BaseAgent(LogMixin):
|
|
|
1166
1194
|
|
|
1167
1195
|
return content_blocks
|
|
1168
1196
|
|
|
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
1197
|
async def on_tool_use_start(self, tool_call_delta: Dict[str, Any]) -> None:
|
|
1185
1198
|
"""
|
|
1186
1199
|
Called when the provider streams a tool_use_start event (Anthropic only).
|
|
@@ -1252,20 +1265,35 @@ class BaseAgent(LogMixin):
|
|
|
1252
1265
|
logger.debug("Input token count: %s", token_count)
|
|
1253
1266
|
|
|
1254
1267
|
if self.compressor.over_budget(token_count.input_tokens, self.model_context_length):
|
|
1268
|
+
before_tokens = token_count.input_tokens
|
|
1255
1269
|
# PreCompact hooks (advisory): observe before history is compacted.
|
|
1256
1270
|
await self.fire_hook(
|
|
1257
1271
|
HookEvent.PRE_COMPACT,
|
|
1258
1272
|
{
|
|
1259
1273
|
"trigger": "auto",
|
|
1260
|
-
"input_tokens":
|
|
1274
|
+
"input_tokens": before_tokens,
|
|
1261
1275
|
"model_context_length": self.model_context_length,
|
|
1262
1276
|
},
|
|
1263
1277
|
)
|
|
1264
|
-
await self.compress_history()
|
|
1278
|
+
result = await self.compress_history()
|
|
1265
1279
|
token_count = await self.count_current_context()
|
|
1266
1280
|
|
|
1267
|
-
|
|
1268
|
-
|
|
1281
|
+
# PostCompact hooks (advisory): observe the outcome. There is no
|
|
1282
|
+
# destructive fallback — a bounded summary plus capped tool results
|
|
1283
|
+
# and a small verbatim tail keep us under budget; if somehow not, we
|
|
1284
|
+
# send as-is rather than wipe history.
|
|
1285
|
+
await self.fire_hook(
|
|
1286
|
+
HookEvent.POST_COMPACT,
|
|
1287
|
+
{
|
|
1288
|
+
"trigger": "auto",
|
|
1289
|
+
"ok": result.ok,
|
|
1290
|
+
"reason": result.reason,
|
|
1291
|
+
"summarized_messages": result.summarized_messages,
|
|
1292
|
+
"input_tokens_before": before_tokens,
|
|
1293
|
+
"input_tokens_after": token_count.input_tokens,
|
|
1294
|
+
"model_context_length": self.model_context_length,
|
|
1295
|
+
},
|
|
1296
|
+
)
|
|
1269
1297
|
|
|
1270
1298
|
self.mark_cache_checkpoint()
|
|
1271
1299
|
|
|
@@ -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)
|
|
@@ -27,11 +27,54 @@ class Conversation:
|
|
|
27
27
|
*,
|
|
28
28
|
max_tool_result_chars: int = 100_000,
|
|
29
29
|
) -> None:
|
|
30
|
-
self.
|
|
31
|
-
#
|
|
32
|
-
|
|
30
|
+
self._history = MessageHistory(list(messages) if messages else [])
|
|
31
|
+
# Compaction state: ``summary`` stands in for ``history[:compacted_through]``
|
|
32
|
+
# in the effective view; messages from ``compacted_through`` onward are kept
|
|
33
|
+
# verbatim. ``summary is None`` / ``compacted_through == 0`` means uncompacted.
|
|
34
|
+
self.summary: Optional[Message] = None
|
|
35
|
+
self.compacted_through: int = 0
|
|
33
36
|
self.max_tool_result_chars = max_tool_result_chars
|
|
34
37
|
|
|
38
|
+
# ------------------------------------------------------------------
|
|
39
|
+
# History access (compaction-aware)
|
|
40
|
+
# ------------------------------------------------------------------
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def history(self) -> MessageHistory:
|
|
44
|
+
return self._history
|
|
45
|
+
|
|
46
|
+
@history.setter
|
|
47
|
+
def history(self, value) -> None:
|
|
48
|
+
# Wholesale replacement of the log invalidates any compaction boundary.
|
|
49
|
+
# Internal mutators that must preserve an active summary assign to
|
|
50
|
+
# ``self._history`` directly instead of going through this setter.
|
|
51
|
+
self._history = value if isinstance(value, MessageHistory) else MessageHistory(list(value))
|
|
52
|
+
self.summary = None
|
|
53
|
+
self.compacted_through = 0
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def last_compression_index(self) -> Optional[int]:
|
|
57
|
+
"""Back-compat: index of the last message folded into the summary, or None."""
|
|
58
|
+
if self.summary is not None and self.compacted_through > 0:
|
|
59
|
+
return self.compacted_through - 1
|
|
60
|
+
return None
|
|
61
|
+
|
|
62
|
+
@last_compression_index.setter
|
|
63
|
+
def last_compression_index(self, value: Optional[int]) -> None:
|
|
64
|
+
# Legacy setter. The only meaningful operation is clearing the boundary;
|
|
65
|
+
# a real summary is recorded through ``apply_compaction``/``record_compression``.
|
|
66
|
+
if value is None:
|
|
67
|
+
self.summary = None
|
|
68
|
+
self.compacted_through = 0
|
|
69
|
+
else:
|
|
70
|
+
self.compacted_through = value + 1
|
|
71
|
+
|
|
72
|
+
def clear(self) -> None:
|
|
73
|
+
"""Drop all history and reset compaction state."""
|
|
74
|
+
self._history = MessageHistory([])
|
|
75
|
+
self.summary = None
|
|
76
|
+
self.compacted_through = 0
|
|
77
|
+
|
|
35
78
|
# ------------------------------------------------------------------
|
|
36
79
|
# Appending
|
|
37
80
|
# ------------------------------------------------------------------
|
|
@@ -125,7 +168,9 @@ class Conversation:
|
|
|
125
168
|
def extend(self, messages: List[Message]) -> None:
|
|
126
169
|
"""Extend history with multiple messages, repairing incomplete tool calls in the result."""
|
|
127
170
|
all_messages = list(self.history) + messages
|
|
128
|
-
|
|
171
|
+
# Assign to _history (not the public setter) so an active compaction survives
|
|
172
|
+
# appending live turns.
|
|
173
|
+
self._history = MessageHistory(self.repaired(all_messages))
|
|
129
174
|
|
|
130
175
|
# ------------------------------------------------------------------
|
|
131
176
|
# Views and validity
|
|
@@ -134,20 +179,17 @@ class Conversation:
|
|
|
134
179
|
def effective_history(self) -> MessageHistory:
|
|
135
180
|
"""
|
|
136
181
|
Return the subset of history to send to the LLM:
|
|
137
|
-
- If
|
|
138
|
-
|
|
182
|
+
- If compacted: protected skill content from the folded prefix, then the
|
|
183
|
+
summary, then the most-recent turns kept verbatim.
|
|
184
|
+
- Else: the full history.
|
|
139
185
|
"""
|
|
140
|
-
if self.
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
tail = list(self.history[summary_idx + 1 :]) if summary_idx + 1 < len(self.history) else []
|
|
148
|
-
return MessageHistory(protected + [summary_msg] + tail)
|
|
149
|
-
|
|
150
|
-
return MessageHistory(list(self.history))
|
|
186
|
+
if self.summary is not None and self.compacted_through > 0 and self._history:
|
|
187
|
+
cut = min(self.compacted_through, len(self._history))
|
|
188
|
+
protected = [message for message in self._history[:cut] if self.is_protected(message)]
|
|
189
|
+
tail = list(self._history[cut:])
|
|
190
|
+
return MessageHistory(protected + [self.summary] + tail)
|
|
191
|
+
|
|
192
|
+
return MessageHistory(list(self._history))
|
|
151
193
|
|
|
152
194
|
def is_protected(self, message: Message) -> bool:
|
|
153
195
|
"""True for user messages carrying skill content that must survive compression."""
|
|
@@ -388,9 +430,55 @@ class Conversation:
|
|
|
388
430
|
return sanitized_count
|
|
389
431
|
|
|
390
432
|
def record_compression(self, summary: Message) -> None:
|
|
391
|
-
"""
|
|
392
|
-
|
|
393
|
-
|
|
433
|
+
"""Back-compat shim: fold the entire current history into ``summary``.
|
|
434
|
+
|
|
435
|
+
Newer callers use ``apply_compaction`` with an explicit split point so the
|
|
436
|
+
most recent turns are kept verbatim.
|
|
437
|
+
"""
|
|
438
|
+
self.summary = (
|
|
439
|
+
summary if isinstance(summary, Message) else Message(role="user", content=[TextBlock(text=str(summary))])
|
|
440
|
+
)
|
|
441
|
+
self.compacted_through = len(self._history)
|
|
442
|
+
|
|
443
|
+
def apply_compaction(self, summary_text: str, split_point: int) -> None:
|
|
444
|
+
"""Record ``summary_text`` as standing in for ``history[:split_point]``.
|
|
445
|
+
|
|
446
|
+
Non-destructive: the full history is untouched; only the compaction
|
|
447
|
+
boundary moves. Messages from ``split_point`` onward stay verbatim.
|
|
448
|
+
"""
|
|
449
|
+
self.summary = Message(role="user", content=[TextBlock(text=summary_text)])
|
|
450
|
+
self.compacted_through = max(0, min(split_point, len(self._history)))
|
|
451
|
+
|
|
452
|
+
def compaction_split_point(self, *, keep_recent: int, min_prefix: int) -> Optional[int]:
|
|
453
|
+
"""Index where the verbatim recent tail should begin.
|
|
454
|
+
|
|
455
|
+
Keeps the last ``keep_recent`` messages verbatim, then snaps the cut
|
|
456
|
+
backward so it never lands between an assistant tool_use and its
|
|
457
|
+
tool_result. Returns None when the prefix to summarize would be smaller
|
|
458
|
+
than ``min_prefix``.
|
|
459
|
+
"""
|
|
460
|
+
n = len(self._history)
|
|
461
|
+
if n <= min_prefix:
|
|
462
|
+
return None
|
|
463
|
+
candidate = self._snap_to_safe_boundary(max(0, n - keep_recent))
|
|
464
|
+
if candidate < min_prefix:
|
|
465
|
+
return None
|
|
466
|
+
return candidate
|
|
467
|
+
|
|
468
|
+
def _snap_to_safe_boundary(self, idx: int) -> int:
|
|
469
|
+
"""Move ``idx`` backward past any assistant tool_use so a tool_use/tool_result
|
|
470
|
+
group is never split across the compaction boundary."""
|
|
471
|
+
while idx > 0:
|
|
472
|
+
prev = self._history[idx - 1]
|
|
473
|
+
if (
|
|
474
|
+
prev.role == "assistant"
|
|
475
|
+
and isinstance(prev.content, list)
|
|
476
|
+
and any(isinstance(block, ToolCall) for block in prev.content)
|
|
477
|
+
):
|
|
478
|
+
idx -= 1
|
|
479
|
+
continue
|
|
480
|
+
break
|
|
481
|
+
return idx
|
|
394
482
|
|
|
395
483
|
# ------------------------------------------------------------------
|
|
396
484
|
# Serialization
|
|
@@ -406,3 +494,26 @@ class Conversation:
|
|
|
406
494
|
# Keep history authentic - no fixing here
|
|
407
495
|
self.history = MessageHistory(parsed_messages)
|
|
408
496
|
self.sanitize_oversized_tool_results()
|
|
497
|
+
|
|
498
|
+
def dump_compaction(self) -> Dict[str, Any]:
|
|
499
|
+
"""Serialize the compaction boundary so it survives save/restore."""
|
|
500
|
+
return {
|
|
501
|
+
"summary": self.summary.get_text_content() if self.summary is not None else "",
|
|
502
|
+
"compacted_through": self.compacted_through,
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
def restore_compaction(self, data: Optional[Dict[str, Any]]) -> None:
|
|
506
|
+
"""Restore a compaction boundary saved by ``dump_compaction``.
|
|
507
|
+
|
|
508
|
+
Call AFTER ``restore`` (which resets compaction). Assigns the boundary
|
|
509
|
+
fields directly so it does not trip the history setter's reset.
|
|
510
|
+
"""
|
|
511
|
+
data = data or {}
|
|
512
|
+
text = (data.get("summary") or "").strip()
|
|
513
|
+
through = int(data.get("compacted_through") or 0)
|
|
514
|
+
if text and through > 0:
|
|
515
|
+
self.summary = Message(role="user", content=[TextBlock(text=text)])
|
|
516
|
+
self.compacted_through = min(through, len(self._history))
|
|
517
|
+
else:
|
|
518
|
+
self.summary = None
|
|
519
|
+
self.compacted_through = 0
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
You are compacting an in-progress coding session so the work can continue seamlessly in a smaller context window. Produce a TIGHT continuity briefing — not a transcript.
|
|
2
|
+
|
|
3
|
+
Hard rules:
|
|
4
|
+
- Respond with the summary text ONLY. Do NOT call tools. Do NOT ask questions.
|
|
5
|
+
- Be bounded: aim for under ~600 words. Omit anything not needed to keep working.
|
|
6
|
+
- Quote identifiers EXACTLY: file paths, function/class names, variable names, error strings, commands, and config keys. Do not paraphrase code or paths.
|
|
7
|
+
- Do not invent facts. If something is unknown or unfinished, say so briefly.
|
|
8
|
+
- Preserve any `<skill_content name="...">` instructions referenced earlier — note them by name so they are not lost.
|
|
9
|
+
|
|
10
|
+
Write these sections, each terse. Use `##` headers and keep them in this order:
|
|
11
|
+
|
|
12
|
+
## Goal
|
|
13
|
+
The user's overall objective and any explicit constraints, in 1-3 sentences.
|
|
14
|
+
|
|
15
|
+
## State
|
|
16
|
+
What is true RIGHT NOW: what has been built or changed, what works, what has been verified.
|
|
17
|
+
|
|
18
|
+
## Files Touched
|
|
19
|
+
A bullet list of exact paths read/created/modified, each with a 3-8 word note on what changed or why it matters. No code blocks unless a snippet is load-bearing.
|
|
20
|
+
|
|
21
|
+
## Decisions
|
|
22
|
+
Key technical decisions and their one-line rationale. Note approaches already tried and rejected.
|
|
23
|
+
|
|
24
|
+
## Open Problems
|
|
25
|
+
Exact open errors (quote them), failing tests, or unresolved questions. Write "None" if there are none.
|
|
26
|
+
|
|
27
|
+
## Next Steps
|
|
28
|
+
The concrete next actions to take, in order. Write "None" if the task appears complete.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{% if previous_summary %}
|
|
2
|
+
A prior summary already covers the earliest part of this session. Treat it as established context and fold its facts into your new summary (do not drop anything important from it):
|
|
3
|
+
|
|
4
|
+
<previous_summary>
|
|
5
|
+
{{ previous_summary }}
|
|
6
|
+
</previous_summary>
|
|
7
|
+
|
|
8
|
+
Below are the messages that have happened SINCE that summary and now need to be folded in as well:
|
|
9
|
+
{% else %}
|
|
10
|
+
Summarize the following coding session so far:
|
|
11
|
+
{% endif %}
|
|
12
|
+
|
|
13
|
+
<conversation_history>
|
|
14
|
+
{{ history }}
|
|
15
|
+
</conversation_history>
|
|
16
|
+
|
|
17
|
+
Produce the continuity briefing exactly as specified in your instructions. Summary text only — do not call tools.
|
|
@@ -4,7 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
from functools import lru_cache
|
|
6
6
|
from pathlib import Path
|
|
7
|
-
from typing import Any
|
|
7
|
+
from typing import Any, Optional
|
|
8
8
|
|
|
9
9
|
import jinja2
|
|
10
10
|
|
|
@@ -56,8 +56,12 @@ SKILL_CATALOG_PROMPT_TEMPLATE = prompt_template_source("extensions/skills/catalo
|
|
|
56
56
|
PLANNING_AGENT_SYSTEM_PROMPT_TEMPLATE = prompt_template_source("system/agents/planning.md.j2")
|
|
57
57
|
|
|
58
58
|
|
|
59
|
-
def build_compression_summary_user_prompt(history: str) -> str:
|
|
60
|
-
return render_prompt_template(
|
|
59
|
+
def build_compression_summary_user_prompt(history: str, previous_summary: Optional[str] = None) -> str:
|
|
60
|
+
return render_prompt_template(
|
|
61
|
+
"auxiliary/compression/summary.user.md.j2",
|
|
62
|
+
history=history,
|
|
63
|
+
previous_summary=previous_summary,
|
|
64
|
+
)
|
|
61
65
|
|
|
62
66
|
|
|
63
67
|
def build_implement_plan_prompt(plan: str, gigacode_enabled: bool = False) -> str:
|