agentic-cli 0.5.0__tar.gz → 0.5.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.
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/CHANGELOG.md +38 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/CLAUDE.md +7 -3
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/PKG-INFO +46 -36
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/README.md +43 -35
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/examples/research_demo/agents.py +1 -1
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/pyproject.toml +3 -1
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/__init__.py +1 -1
- agentic_cli-0.5.2/src/agentic_cli/knowledge_base/_bm25_backends.py +137 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/knowledge_base/concepts.py +43 -1
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/knowledge_base/manager.py +61 -18
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/tools/__init__.py +13 -8
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/tools/adk/state_tools.py +6 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/tools/arxiv_tools.py +7 -4
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/tools/execution_tools.py +2 -2
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/tools/executor.py +35 -8
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/tools/factories.py +87 -87
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/tools/file_read.py +3 -5
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/tools/file_write.py +3 -4
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/tools/glob_tool.py +3 -5
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/tools/grep_tool.py +2 -4
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/tools/interaction_tools.py +2 -2
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/tools/knowledge_tools.py +279 -119
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/tools/langgraph/state_tools.py +6 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/tools/memory_tools.py +87 -48
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/tools/reflection_tools.py +3 -2
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/tools/registry.py +49 -25
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/tools/sandbox/__init__.py +3 -2
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/tools/search.py +2 -2
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/tools/shell/executor.py +3 -3
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/tools/shell/sandbox.py +33 -1
- agentic_cli-0.5.2/src/agentic_cli/tools/webfetch/fetcher.py +236 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/tools/webfetch_tool.py +3 -2
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/workflow/adk/__init__.py +1 -1
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/workflow/adk/manager.py +3 -2
- agentic_cli-0.5.2/src/agentic_cli/workflow/adk/permission_plugin.py +72 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/workflow/adk/plugins.py +1 -97
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/workflow/base_manager.py +23 -5
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/workflow/langgraph/graph_builder.py +2 -45
- agentic_cli-0.5.2/src/agentic_cli/workflow/langgraph/permission_wrap.py +58 -0
- agentic_cli-0.5.2/src/agentic_cli/workflow/permissions/__init__.py +37 -0
- agentic_cli-0.5.2/src/agentic_cli/workflow/permissions/capabilities.py +44 -0
- agentic_cli-0.5.2/src/agentic_cli/workflow/permissions/engine.py +226 -0
- agentic_cli-0.5.2/src/agentic_cli/workflow/permissions/matchers.py +193 -0
- agentic_cli-0.5.2/src/agentic_cli/workflow/permissions/prompt.py +66 -0
- agentic_cli-0.5.2/src/agentic_cli/workflow/permissions/rules.py +39 -0
- agentic_cli-0.5.2/src/agentic_cli/workflow/permissions/store.py +114 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/workflow/service_registry.py +4 -3
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/workflow/settings.py +26 -6
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/workflow/tool_summaries.py +3 -1
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/tests/integration/test_kb_research_flow.py +2 -2
- agentic_cli-0.5.2/tests/integration/test_permission_adk.py +141 -0
- agentic_cli-0.5.2/tests/integration/test_permission_langgraph.py +110 -0
- agentic_cli-0.5.2/tests/permissions/test_capabilities.py +47 -0
- agentic_cli-0.5.2/tests/permissions/test_engine.py +435 -0
- agentic_cli-0.5.2/tests/permissions/test_matchers.py +175 -0
- agentic_cli-0.5.2/tests/permissions/test_prompt.py +92 -0
- agentic_cli-0.5.2/tests/permissions/test_rules.py +47 -0
- agentic_cli-0.5.2/tests/permissions/test_store.py +179 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/tests/test_adk_plugins.py +4 -228
- agentic_cli-0.5.2/tests/test_bm25_index.py +188 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/tests/test_concept_store.py +37 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/tests/test_config.py +0 -4
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/tests/test_kb_sidecar.py +75 -4
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/tests/test_kb_tool_renames.py +25 -5
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/tests/test_knowledge_tools.py +77 -24
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/tests/test_memory.py +83 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/tests/test_tool_summaries.py +5 -2
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/tests/test_tools.py +34 -89
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/tests/test_webfetch.py +102 -14
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/tests/tools/test_factories.py +6 -4
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/tests/tools/test_os_sandbox.py +54 -18
- agentic_cli-0.5.2/tests/tools/test_registry.py +66 -0
- agentic_cli-0.5.2/tests/workflow/__init__.py +0 -0
- agentic_cli-0.5.2/tests/workflow/test_backend_isolation.py +56 -0
- agentic_cli-0.5.2/tests/workflow/test_base_manager_permissions.py +27 -0
- agentic_cli-0.5.2/tests/workflow/test_settings_permissions.py +37 -0
- agentic_cli-0.5.0/src/agentic_cli/tools/hitl_tools.py +0 -81
- agentic_cli-0.5.0/src/agentic_cli/tools/webfetch/fetcher.py +0 -203
- agentic_cli-0.5.0/tests/test_bm25_index.py +0 -67
- agentic_cli-0.5.0/tests/test_hitl.py +0 -61
- agentic_cli-0.5.0/tests/test_langgraph_confirmation.py +0 -134
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/.gitignore +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/LICENSE +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/environment.yml +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/examples/arxiv_demo.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/examples/fileops_demo.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/examples/hello_agent.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/examples/hello_langgraph.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/examples/memory_demo.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/examples/research_demo/__init__.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/examples/research_demo/__main__.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/examples/research_demo/app.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/examples/research_demo/commands.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/examples/research_demo/settings.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/examples/shell_demo.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/examples/webfetch_demo.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/examples/websearch_demo.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/requirements-dev.txt +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/requirements.txt +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/cli/__init__.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/cli/app.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/cli/builtin_commands.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/cli/commands.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/cli/message_processor.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/cli/settings_command.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/cli/settings_introspection.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/cli/usage_tracker.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/cli/workflow_controller.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/config.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/constants.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/file_utils.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/knowledge_base/__init__.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/knowledge_base/_mock_bm25.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/knowledge_base/_mocks.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/knowledge_base/bm25_index.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/knowledge_base/embeddings.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/knowledge_base/models.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/knowledge_base/sidecar.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/knowledge_base/sources.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/knowledge_base/vector_store.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/logging.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/persistence/__init__.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/persistence/session.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/settings_mixins.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/settings_persistence.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/tools/_core/__init__.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/tools/_core/planning.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/tools/_core/tasks.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/tools/adk/__init__.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/tools/arxiv_source.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/tools/langgraph/__init__.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/tools/pdf_utils.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/tools/sandbox/backends/__init__.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/tools/sandbox/backends/base.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/tools/sandbox/backends/jupyter_local.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/tools/sandbox/manager.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/tools/sandbox/models.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/tools/shell/__init__.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/tools/shell/audit.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/tools/shell/classifier.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/tools/shell/config.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/tools/shell/models.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/tools/shell/os_sandbox/__init__.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/tools/shell/os_sandbox/base.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/tools/shell/os_sandbox/bubblewrap.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/tools/shell/os_sandbox/detect.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/tools/shell/os_sandbox/policy.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/tools/shell/os_sandbox/seatbelt.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/tools/shell/os_sandbox/seatbelt_profile.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/tools/shell/path_analyzer.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/tools/shell/preprocessor.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/tools/shell/risk_assessor.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/tools/shell/tokenizer.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/tools/webfetch/__init__.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/tools/webfetch/converter.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/tools/webfetch/robots.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/tools/webfetch/summarizer.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/tools/webfetch/validator.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/workflow/__init__.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/workflow/adk/event_processor.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/workflow/adk/task_progress_plugin.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/workflow/config.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/workflow/events.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/workflow/factory.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/workflow/langgraph/__init__.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/workflow/langgraph/manager.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/workflow/langgraph/persistence/__init__.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/workflow/langgraph/persistence/checkpointers.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/workflow/langgraph/persistence/stores.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/workflow/langgraph/state.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/workflow/models.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/src/agentic_cli/workflow/retry.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/tests/__init__.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/tests/conftest.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/tests/integration/__init__.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/tests/integration/conftest.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/tests/integration/helpers.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/tests/integration/test_adk_integration.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/tests/integration/test_langgraph_integration.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/tests/integration/test_live.py +0 -0
- {agentic_cli-0.5.0/tests/workflow → agentic_cli-0.5.2/tests/permissions}/__init__.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/tests/test_adk_event_processor.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/tests/test_arxiv_tools.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/tests/test_commands.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/tests/test_concurrent_stores.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/tests/test_context_trimming.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/tests/test_context_window.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/tests/test_dual_thinking_boxes.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/tests/test_embeddings.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/tests/test_grep_tool_cache.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/tests/test_input_callback.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/tests/test_kb_concepts.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/tests/test_kb_helpers.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/tests/test_kb_index_md.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/tests/test_kb_ingest_log.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/tests/test_knowledge_base.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/tests/test_langgraph.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/tests/test_model_registry.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/tests/test_persistence.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/tests/test_planning.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/tests/test_reflection.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/tests/test_session_save_resume.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/tests/test_settings_persistence.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/tests/test_task_tools.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/tests/test_token_caching.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/tests/test_update_setting.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/tests/test_usage_tracker.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/tests/test_vector_store.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/tests/test_workflow.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/tests/test_workflow_controller.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/tests/tools/__init__.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/tests/tools/test_adk_state_tools.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/tests/tools/test_bubblewrap.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/tests/tools/test_core_planning.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/tests/tools/test_core_tasks.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/tests/tools/test_langgraph_state_tools.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/tests/tools/test_sandbox.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/tests/tools/test_seatbelt_profile.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/tests/tools/test_shell_security.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/tests/workflow/test_task_progress_plugin.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.2}/tests/workflow/test_tool_assembly.py +0 -0
|
@@ -5,6 +5,44 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.5.2] - 2026-05-01
|
|
9
|
+
|
|
10
|
+
### Security
|
|
11
|
+
- **`kb_ingest` split into `kb_ingest_text` / `kb_ingest_file` / `kb_ingest_url`**: the previous unified tool declared only `kb.write` (auto-allowed by the builtin `kb.*` rule) while internally reading arbitrary local paths and fetching arbitrary URLs. The split lets the permission engine gate `filesystem.read` and `http.read` at the right entry point, and the URL path now flows through the hardened `ContentFetcher` (URLValidator + manual redirect revalidation + DNS-rebinding mitigation).
|
|
12
|
+
- **Concept-page slug traversal blocked** (`ConceptStore`): explicit slugs are validated against a strict `[a-z0-9-]+` allowlist, and `_concept_path` asserts the resolved path stays under `base_dir`. A slug like `../../etc/passwd` is now rejected before any filesystem write.
|
|
13
|
+
- **Webfetch redirect handling rewritten**: `ContentFetcher.fetch` no longer follows redirects via httpx. Each `Location` is revalidated through `URLValidator` *before* the next GET, cross-host redirects are reported without contacting the new origin, same-host redirects re-check `robots.txt`, and the redirect chain is capped at 5.
|
|
14
|
+
- **`os_sandbox_enabled=True` now fails closed**: when shell or Python execution is requested with sandboxing on, both executors refuse to run if the resolved sandbox is `NoOpSandbox` or wrap fails — instead of silently dropping back to plain subprocess/ulimit.
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
- **`kb_ingest` tool removed** in favor of the three split entry points; `KB_WRITER_TOOLS` and `make_kb_tools` updated. Internal helper renamed: `_ingest_document_with_kb` → `_ingest_text_with_kb` / `_ingest_file_with_kb` / `_ingest_url_with_kb`.
|
|
18
|
+
- **research_demo agent guide** updated to direct ingestion at `ingest_arxiv_paper` for arXiv papers and at the typed `kb_ingest_*` tools for everything else.
|
|
19
|
+
|
|
20
|
+
## [0.5.1] - 2026-04-18
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
- **Capability-based permission engine** (PR #72): Framework-independent engine that replaces the ConfirmationPlugin-based HITL. Tools declare capabilities (e.g. `filesystem.write(path=...)`, `http.read`, `shell.exec`) via `@register_tool(capabilities=...)`. Rules are evaluated from four sources — builtin defaults, user `~/.{app_name}/settings.json`, project `./.{app_name}/settings.json`, and in-memory session. When no rule matches, the user is prompted with `Allow once / for session / always (save to project) / Deny`; "always" grants persist into project settings.
|
|
24
|
+
- **Matchers**: `PathMatcher` (with `**` glob), `URLMatcher`, `ShellMatcher`, and `StringGlobMatcher`.
|
|
25
|
+
- **ADK `PermissionPlugin`**: capability gating for ADK agents.
|
|
26
|
+
- **LangGraph `wrap_tool_for_permission`**: per-tool permission wrapping for LangGraph `ToolNode`.
|
|
27
|
+
- **`permissions` and `permissions_enabled` settings** for runtime control.
|
|
28
|
+
- **`EXEMPT` sentinel**: opts a tool out of the permission engine (used for backend state tools and ADK `transfer_to_agent`).
|
|
29
|
+
- **Real BM25 backends**: `bm25s` (preferred) and `rank_bm25` fallback.
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
- **`@register_tool`** now requires the `capabilities=` kwarg.
|
|
33
|
+
- **Default grants broadened**: `filesystem.*` auto-extends to the parent directory; `memory.*` and `kb.*` are allowed by default.
|
|
34
|
+
- **Ask prompt** now shows the directory-scope target.
|
|
35
|
+
- **Workflow init**: `_ensure_managers_initialized` runs in a worker thread to avoid blocking the event loop.
|
|
36
|
+
- **HITL confirmation** extracted into a backend-neutral module during the permissions migration.
|
|
37
|
+
- **Memory tools**: deduped registry-bound and factory-bound entry points.
|
|
38
|
+
|
|
39
|
+
### Fixed
|
|
40
|
+
- Matcher canonicalize/matches preserves the `*` wildcard.
|
|
41
|
+
- `KnowledgeBaseManager` concurrency contract tightened.
|
|
42
|
+
|
|
43
|
+
### Removed
|
|
44
|
+
- **`PermissionLevel`** (SAFE / CAUTION / DANGEROUS), `ConfirmationPlugin`, `_wrap_for_confirmation`, `hitl_tools`, and the `hitl_enabled` setting — superseded by the permission engine.
|
|
45
|
+
|
|
8
46
|
## [0.5.0] - 2026-04-17
|
|
9
47
|
|
|
10
48
|
### Added
|
|
@@ -47,7 +47,7 @@ agentic-cli/
|
|
|
47
47
|
│ │ ├── persistence/ # Checkpointers, stores
|
|
48
48
|
│ │ └── tools/ # LangChain-compatible wrappers
|
|
49
49
|
│ ├── tools/
|
|
50
|
-
│ │ ├── registry.py # ToolRegistry, @register_tool, ToolCategory
|
|
50
|
+
│ │ ├── registry.py # ToolRegistry, @register_tool, ToolCategory
|
|
51
51
|
│ │ ├── executor.py # SafePythonExecutor
|
|
52
52
|
│ │ ├── knowledge_tools.py # kb_search, kb_ingest, kb_list, kb_read
|
|
53
53
|
│ │ ├── arxiv_tools.py # search_arxiv, fetch_arxiv_paper, analyze_arxiv_paper
|
|
@@ -62,7 +62,7 @@ agentic-cli/
|
|
|
62
62
|
│ │ ├── memory_tools.py # save_memory, search_memory + MemoryStore
|
|
63
63
|
│ │ ├── planning_tools.py # save_plan, get_plan + PlanStore
|
|
64
64
|
│ │ ├── task_tools.py # save_tasks, get_tasks + TaskStore
|
|
65
|
-
│ │ ├──
|
|
65
|
+
│ │ ├── reflection_tools.py # save_reflection + ToolReflectionStore
|
|
66
66
|
│ │ ├── shell/ # 8-layer shell security
|
|
67
67
|
│ │ └── webfetch/ # Fetcher, converter, validator, robots
|
|
68
68
|
│ ├── knowledge_base/
|
|
@@ -114,6 +114,9 @@ Workflow:
|
|
|
114
114
|
2. For features: create `feature/<name>` (or `fix/<name>` or `refactor/<name>`) from `develop`, work there, merge back to `develop`
|
|
115
115
|
3. When ready to release: merge `develop` → `main` and tag the release
|
|
116
116
|
|
|
117
|
+
### What NOT to commit
|
|
118
|
+
- `docs/` is gitignored on purpose (see `.gitignore`). It is a scratchpad for review notes, plans, and internal analysis. **Never `git add docs/…` or suggest committing anything under `docs/`.** If a document belongs in the repo, it lives elsewhere (README, CHANGELOG, top-level `*.md`).
|
|
119
|
+
|
|
117
120
|
## Development Principles
|
|
118
121
|
|
|
119
122
|
### Code Style
|
|
@@ -130,7 +133,8 @@ Workflow:
|
|
|
130
133
|
|
|
131
134
|
### Key Design Patterns
|
|
132
135
|
- **Tool error handling**: All tools return `{"success": bool, ...}` dicts. Never raise `ToolError`.
|
|
133
|
-
- **Tool registration**: Use `@register_tool(category=...,
|
|
136
|
+
- **Tool registration**: Use `@register_tool(category=..., capabilities=..., description=...)` decorator. `capabilities=` is required — pass `EXEMPT` for tools that need no permission check or a list of `Capability(name, target_arg=...)` tuples the engine matches against rules. Tools are auto-discovered via the global `ToolRegistry`.
|
|
137
|
+
- **Permissions**: `workflow/permissions/` holds a framework-independent engine that evaluates declared capabilities against rules from four sources (builtin, user `~/.{app_name}/settings.json`, project `./.{app_name}/settings.json`, in-memory session). ADK + LangGraph gate tool calls via `workflow/adk/permission_plugin.py::PermissionPlugin` and `workflow/langgraph/permission_wrap.py::wrap_tool_for_permission`. See `docs/superpowers/specs/2026-04-18-permissions-system-design.md`.
|
|
134
138
|
- **Service registry**: Tools access services and shared state via `get_service(key)` from `workflow.service_registry`. A single ContextVar holds a `dict[str, Any]` set by the workflow manager during processing. Complex services (KBManager, SandboxManager, MemoryStore) are lazily created; simple state (plan string, task list) lives directly in the registry dict.
|
|
135
139
|
- **Manager detection**: Tools decorated with `@requires("kb_manager")` etc. are scanned by `BaseWorkflowManager._detect_required_managers()` which lazily creates only the needed services.
|
|
136
140
|
- **Atomic writes**: Use `atomic_write_json`/`atomic_write_text` from `persistence/_utils.py` for file persistence.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentic-cli
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.2
|
|
4
4
|
Summary: A framework for building domain-specific agentic CLI applications
|
|
5
5
|
Project-URL: Homepage, https://github.com/shoom1/agentic-cli
|
|
6
6
|
Project-URL: Repository, https://github.com/shoom1/agentic-cli
|
|
@@ -32,7 +32,9 @@ Requires-Dist: pytest-asyncio>=0.24.0; extra == 'dev'
|
|
|
32
32
|
Requires-Dist: pytest-cov>=6.0.0; extra == 'dev'
|
|
33
33
|
Requires-Dist: pytest>=8.0.0; extra == 'dev'
|
|
34
34
|
Provides-Extra: kb
|
|
35
|
+
Requires-Dist: bm25s>=0.2.0; extra == 'kb'
|
|
35
36
|
Requires-Dist: faiss-cpu>=1.7.0; extra == 'kb'
|
|
37
|
+
Requires-Dist: rank-bm25>=0.2.2; extra == 'kb'
|
|
36
38
|
Requires-Dist: sentence-transformers<3.0.0,>=2.0.0; extra == 'kb'
|
|
37
39
|
Requires-Dist: torch>=2.2.0; extra == 'kb'
|
|
38
40
|
Provides-Extra: langgraph
|
|
@@ -84,7 +86,7 @@ Agentic CLI provides the core infrastructure for building interactive CLI applic
|
|
|
84
86
|
├─────────────────────────────┬───────────────────────────────────────┤
|
|
85
87
|
│ GoogleADKWorkflowManager │ LangGraphWorkflowManager │
|
|
86
88
|
│ (default) │ (optional: langgraph extra) │
|
|
87
|
-
│ +
|
|
89
|
+
│ + PermissionPlugin │ + wrap_tool_for_permission │
|
|
88
90
|
│ + LLMLoggingPlugin │ + native ToolNode │
|
|
89
91
|
│ + TaskProgressPlugin │ │
|
|
90
92
|
└─────────────────────────────┴───────────────────────────────────────┘
|
|
@@ -191,7 +193,7 @@ manager = GoogleADKWorkflowManager(
|
|
|
191
193
|
```
|
|
192
194
|
|
|
193
195
|
ADK integrations:
|
|
194
|
-
- **
|
|
196
|
+
- **PermissionPlugin** — evaluates each tool's declared capabilities against the permission engine and prompts the user when no rule matches
|
|
195
197
|
- **LLMLoggingPlugin** — structured logging of LLM requests/responses
|
|
196
198
|
- **TaskProgressPlugin** — streams the task checklist into its own thinking box
|
|
197
199
|
|
|
@@ -217,7 +219,7 @@ Features:
|
|
|
217
219
|
- **Explicit provider support**: Uses `langchain-google-genai` for Gemini (not VertexAI)
|
|
218
220
|
- **Thinking mode**: Native support for Claude and Gemini thinking/reasoning
|
|
219
221
|
- **Retry policies**: Automatic retry with exponential backoff
|
|
220
|
-
- **
|
|
222
|
+
- **Permission wrapper**: Wraps each tool to gate execution through the permission engine
|
|
221
223
|
- **Event streaming**: Real-time workflow events via `WorkflowEvent`
|
|
222
224
|
|
|
223
225
|
Requires: `pip install agentic-cli[langgraph]`
|
|
@@ -232,7 +234,7 @@ Requires: `pip install agentic-cli[langgraph]`
|
|
|
232
234
|
| State persistence | In-memory | Memory, PostgreSQL, or SQLite |
|
|
233
235
|
| Thinking support | Native (Gemini) | Native (Claude & Gemini) |
|
|
234
236
|
| Retry handling | Built-in | Built-in with backoff |
|
|
235
|
-
|
|
|
237
|
+
| Permission gate | PermissionPlugin | wrap_tool_for_permission |
|
|
236
238
|
| Context trimming | Native | Native |
|
|
237
239
|
|
|
238
240
|
### Auto-selection via Settings
|
|
@@ -366,11 +368,12 @@ configs = [coordinator, researcher, analyst]
|
|
|
366
368
|
Tools are regular Python functions with type hints and docstrings. **All tools return `{"success": bool, ...}` dicts** — never raise exceptions.
|
|
367
369
|
|
|
368
370
|
```python
|
|
369
|
-
from agentic_cli.tools import register_tool, ToolCategory
|
|
371
|
+
from agentic_cli.tools import register_tool, ToolCategory
|
|
372
|
+
from agentic_cli.workflow.permissions import Capability, EXEMPT
|
|
370
373
|
|
|
371
374
|
@register_tool(
|
|
372
|
-
category=ToolCategory.
|
|
373
|
-
|
|
375
|
+
category=ToolCategory.NETWORK,
|
|
376
|
+
capabilities=[Capability("http.read")],
|
|
374
377
|
description="Search the database for matching records.",
|
|
375
378
|
)
|
|
376
379
|
def search_database(query: str, limit: int = 10) -> dict:
|
|
@@ -387,15 +390,14 @@ def search_database(query: str, limit: int = 10) -> dict:
|
|
|
387
390
|
return {"success": True, "results": results, "count": len(results)}
|
|
388
391
|
```
|
|
389
392
|
|
|
390
|
-
Registering via `@register_tool` is optional — you can pass raw callables into `AgentConfig.tools`. Registering gives the tool metadata for the registry,
|
|
393
|
+
Registering via `@register_tool` is optional — you can pass raw callables into `AgentConfig.tools`. Registering gives the tool metadata for the registry, capability-aware permission gating, and tool-summary formatting.
|
|
391
394
|
|
|
392
|
-
###
|
|
395
|
+
### Capabilities
|
|
393
396
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
| `DANGEROUS` | Intercepted by HITL — user must approve before execution |
|
|
397
|
+
Tool access is gated by the **permission engine** (see the HITL section below). Each registered tool declares what it touches via `capabilities=`:
|
|
398
|
+
|
|
399
|
+
- **`Capability("namespace.action", target_arg="...")`** — e.g. `Capability("filesystem.write", target_arg="path")`, `Capability("http.read", target_arg="url")`, `Capability("shell.exec", target_arg="command")`. The engine resolves `target_arg` against the actual tool-call arguments and matches against rules.
|
|
400
|
+
- **`EXEMPT`** — opts the tool out of the engine entirely. Reserved for backend-internal tools (e.g. ADK `transfer_to_agent`, backend state tools).
|
|
399
401
|
|
|
400
402
|
### Built-in Tools
|
|
401
403
|
|
|
@@ -458,7 +460,7 @@ results = search_arxiv("transformer attention", max_results=10, categories=["cs.
|
|
|
458
460
|
paper = fetch_arxiv_paper("1706.03762") # "Attention Is All You Need"
|
|
459
461
|
```
|
|
460
462
|
|
|
461
|
-
The `ArxivSearchSource` (in `knowledge_base/sources.py`) is cached via the service registry and reused across tools,
|
|
463
|
+
The `ArxivSearchSource` (in `knowledge_base/sources.py`) is cached via the service registry and reused across tools. To pull a paper into the KB, use `ingest_arxiv_paper(arxiv_id)` — it composes the arxiv source with `kb_manager`.
|
|
462
464
|
|
|
463
465
|
#### File Operations
|
|
464
466
|
|
|
@@ -476,7 +478,7 @@ list_dir("src/", include_hidden=False)
|
|
|
476
478
|
diff_compare(source_a="old.txt", source_b="new.txt")
|
|
477
479
|
```
|
|
478
480
|
|
|
479
|
-
**WRITE tools
|
|
481
|
+
**WRITE tools**
|
|
480
482
|
|
|
481
483
|
```python
|
|
482
484
|
from agentic_cli.tools import write_file, edit_file
|
|
@@ -511,7 +513,8 @@ Renamed tools and a new concept-pages subsystem:
|
|
|
511
513
|
|
|
512
514
|
```python
|
|
513
515
|
from agentic_cli.tools import (
|
|
514
|
-
kb_search,
|
|
516
|
+
kb_search, kb_ingest_text, kb_ingest_file, kb_ingest_url,
|
|
517
|
+
kb_read, kb_list,
|
|
515
518
|
kb_write_concept, kb_search_concepts,
|
|
516
519
|
KB_READER_TOOLS, KB_WRITER_TOOLS,
|
|
517
520
|
)
|
|
@@ -520,12 +523,16 @@ from agentic_cli.tools import (
|
|
|
520
523
|
| Tool | Purpose |
|
|
521
524
|
|------|---------|
|
|
522
525
|
| `kb_search` | Hybrid BM25 + vector search with RRF fusion, filters by source/date |
|
|
523
|
-
| `
|
|
526
|
+
| `kb_ingest_text` | Ingest in-memory text content (no FS or network access) |
|
|
527
|
+
| `kb_ingest_file` | Ingest a local file; declares `filesystem.read(path)` for the permission engine |
|
|
528
|
+
| `kb_ingest_url` | Ingest content from an http(s) URL; routed through the hardened `ContentFetcher` and declares `http.read(url)` |
|
|
524
529
|
| `kb_read` | Return the per-document markdown sidecar (lazy-generated on first read) |
|
|
525
530
|
| `kb_list` | List documents, optionally filtered |
|
|
526
531
|
| `kb_write_concept` | Agent-authored concept/summary page (slugged, merged on overwrite) |
|
|
527
532
|
| `kb_search_concepts` | Search concept pages (title-weighted, case-insensitive) |
|
|
528
533
|
|
|
534
|
+
The single `kb_ingest` tool was split in 0.5.2 so each entry point declares the right capability — text-only stays under `kb.write`, while file and URL ingestion correctly trip `filesystem.read` / `http.read` checks. For arXiv papers, prefer `ingest_arxiv_paper(arxiv_id)`.
|
|
535
|
+
|
|
529
536
|
Bundle convenience:
|
|
530
537
|
|
|
531
538
|
```python
|
|
@@ -580,15 +587,9 @@ Each tool keeps at most N reflections (FIFO eviction). Reflections can be inject
|
|
|
580
587
|
|
|
581
588
|
#### HITL (Human-in-the-Loop)
|
|
582
589
|
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
1. **Automatic confirmation for `DANGEROUS` tools** — handled by ADK's `ConfirmationPlugin` and LangGraph's tool wrapper. No code changes needed; just mark the tool `DANGEROUS`.
|
|
586
|
-
2. **Explicit `request_approval` tool** — for domain-level checkpoints:
|
|
590
|
+
Tool calls are gated by the **permission engine** (`workflow/permissions/`). Each tool declares a list of capabilities (e.g. `filesystem.write(path=...)`); the engine evaluates them against rules from four sources (builtin defaults, user `~/.{app_name}/settings.json`, project `./.{app_name}/settings.json`, in-memory session). When no rule matches, the user is prompted with `Allow once / Allow for session / Allow always (save to project) / Deny`. Always-grants persist into the project settings file so the next run picks them up automatically.
|
|
587
591
|
|
|
588
|
-
|
|
589
|
-
from agentic_cli.tools import hitl_tools
|
|
590
|
-
# request_approval(message, options) -> {"success": True, "choice": "..."}
|
|
591
|
-
```
|
|
592
|
+
See `docs/superpowers/specs/2026-04-18-permissions-system-design.md` for the full design.
|
|
592
593
|
|
|
593
594
|
## CLI Commands
|
|
594
595
|
|
|
@@ -764,22 +765,32 @@ agentic-cli/
|
|
|
764
765
|
│ │ ├── adk/
|
|
765
766
|
│ │ │ ├── manager.py # GoogleADKWorkflowManager
|
|
766
767
|
│ │ │ ├── event_processor.py
|
|
767
|
-
│ │ │ ├── plugins.py #
|
|
768
|
+
│ │ │ ├── plugins.py # LLMLoggingPlugin
|
|
769
|
+
│ │ │ ├── permission_plugin.py # PermissionPlugin (capability gating)
|
|
768
770
|
│ │ │ └── task_progress_plugin.py
|
|
769
|
-
│ │
|
|
770
|
-
│ │
|
|
771
|
-
│ │
|
|
772
|
-
│ │
|
|
773
|
-
│ │
|
|
771
|
+
│ │ ├── langgraph/
|
|
772
|
+
│ │ │ ├── manager.py # LangGraphWorkflowManager
|
|
773
|
+
│ │ │ ├── graph_builder.py
|
|
774
|
+
│ │ │ ├── permission_wrap.py # wrap_tool_for_permission
|
|
775
|
+
│ │ │ ├── state.py
|
|
776
|
+
│ │ │ └── persistence/ # Checkpointers and stores
|
|
777
|
+
│ │ └── permissions/ # Framework-independent permission engine
|
|
778
|
+
│ │ ├── capabilities.py # Capability, ResolvedCapability, EXEMPT
|
|
779
|
+
│ │ ├── rules.py # Rule, Effect, RuleSource, CheckResult, AskScope
|
|
780
|
+
│ │ ├── matchers.py # Path/URL/Shell/StringGlob matchers
|
|
781
|
+
│ │ ├── store.py # PermissionContext, BUILTIN_RULES, JSON load/save
|
|
782
|
+
│ │ ├── prompt.py # build_request + parse_response
|
|
783
|
+
│ │ └── engine.py # PermissionEngine
|
|
774
784
|
│ ├── tools/
|
|
775
|
-
│ │ ├── registry.py # ToolRegistry, ToolCategory,
|
|
785
|
+
│ │ ├── registry.py # ToolRegistry, ToolCategory, register_tool
|
|
776
786
|
│ │ ├── factories.py # Backend-aware tool factories
|
|
777
787
|
│ │ ├── executor.py # SafePythonExecutor
|
|
778
788
|
│ │ ├── arxiv_tools.py # search_arxiv, fetch_arxiv_paper, ingest_arxiv_paper
|
|
779
789
|
│ │ ├── arxiv_source.py # ArxivSearchSource (service-registered)
|
|
780
790
|
│ │ ├── execution_tools.py # execute_python
|
|
781
791
|
│ │ ├── interaction_tools.py # ask_clarification
|
|
782
|
-
│ │ ├── knowledge_tools.py # kb_search /
|
|
792
|
+
│ │ ├── knowledge_tools.py # kb_search / kb_ingest_text / kb_ingest_file /
|
|
793
|
+
│ │ │ # kb_ingest_url / kb_read / kb_list /
|
|
783
794
|
│ │ │ # kb_write_concept / kb_search_concepts
|
|
784
795
|
│ │ ├── file_read.py # read_file, diff_compare
|
|
785
796
|
│ │ ├── file_write.py # write_file, edit_file (atomic)
|
|
@@ -790,7 +801,6 @@ agentic-cli/
|
|
|
790
801
|
│ │ ├── pdf_utils.py # PDF text extraction helpers
|
|
791
802
|
│ │ ├── memory_tools.py # save/search/update/delete + MemoryStore
|
|
792
803
|
│ │ ├── reflection_tools.py # save_reflection + ToolReflectionStore
|
|
793
|
-
│ │ ├── hitl_tools.py # request_approval
|
|
794
804
|
│ │ ├── _core/ # Shared planning/task logic
|
|
795
805
|
│ │ │ ├── planning.py
|
|
796
806
|
│ │ │ └── tasks.py
|
|
@@ -38,7 +38,7 @@ Agentic CLI provides the core infrastructure for building interactive CLI applic
|
|
|
38
38
|
├─────────────────────────────┬───────────────────────────────────────┤
|
|
39
39
|
│ GoogleADKWorkflowManager │ LangGraphWorkflowManager │
|
|
40
40
|
│ (default) │ (optional: langgraph extra) │
|
|
41
|
-
│ +
|
|
41
|
+
│ + PermissionPlugin │ + wrap_tool_for_permission │
|
|
42
42
|
│ + LLMLoggingPlugin │ + native ToolNode │
|
|
43
43
|
│ + TaskProgressPlugin │ │
|
|
44
44
|
└─────────────────────────────┴───────────────────────────────────────┘
|
|
@@ -145,7 +145,7 @@ manager = GoogleADKWorkflowManager(
|
|
|
145
145
|
```
|
|
146
146
|
|
|
147
147
|
ADK integrations:
|
|
148
|
-
- **
|
|
148
|
+
- **PermissionPlugin** — evaluates each tool's declared capabilities against the permission engine and prompts the user when no rule matches
|
|
149
149
|
- **LLMLoggingPlugin** — structured logging of LLM requests/responses
|
|
150
150
|
- **TaskProgressPlugin** — streams the task checklist into its own thinking box
|
|
151
151
|
|
|
@@ -171,7 +171,7 @@ Features:
|
|
|
171
171
|
- **Explicit provider support**: Uses `langchain-google-genai` for Gemini (not VertexAI)
|
|
172
172
|
- **Thinking mode**: Native support for Claude and Gemini thinking/reasoning
|
|
173
173
|
- **Retry policies**: Automatic retry with exponential backoff
|
|
174
|
-
- **
|
|
174
|
+
- **Permission wrapper**: Wraps each tool to gate execution through the permission engine
|
|
175
175
|
- **Event streaming**: Real-time workflow events via `WorkflowEvent`
|
|
176
176
|
|
|
177
177
|
Requires: `pip install agentic-cli[langgraph]`
|
|
@@ -186,7 +186,7 @@ Requires: `pip install agentic-cli[langgraph]`
|
|
|
186
186
|
| State persistence | In-memory | Memory, PostgreSQL, or SQLite |
|
|
187
187
|
| Thinking support | Native (Gemini) | Native (Claude & Gemini) |
|
|
188
188
|
| Retry handling | Built-in | Built-in with backoff |
|
|
189
|
-
|
|
|
189
|
+
| Permission gate | PermissionPlugin | wrap_tool_for_permission |
|
|
190
190
|
| Context trimming | Native | Native |
|
|
191
191
|
|
|
192
192
|
### Auto-selection via Settings
|
|
@@ -320,11 +320,12 @@ configs = [coordinator, researcher, analyst]
|
|
|
320
320
|
Tools are regular Python functions with type hints and docstrings. **All tools return `{"success": bool, ...}` dicts** — never raise exceptions.
|
|
321
321
|
|
|
322
322
|
```python
|
|
323
|
-
from agentic_cli.tools import register_tool, ToolCategory
|
|
323
|
+
from agentic_cli.tools import register_tool, ToolCategory
|
|
324
|
+
from agentic_cli.workflow.permissions import Capability, EXEMPT
|
|
324
325
|
|
|
325
326
|
@register_tool(
|
|
326
|
-
category=ToolCategory.
|
|
327
|
-
|
|
327
|
+
category=ToolCategory.NETWORK,
|
|
328
|
+
capabilities=[Capability("http.read")],
|
|
328
329
|
description="Search the database for matching records.",
|
|
329
330
|
)
|
|
330
331
|
def search_database(query: str, limit: int = 10) -> dict:
|
|
@@ -341,15 +342,14 @@ def search_database(query: str, limit: int = 10) -> dict:
|
|
|
341
342
|
return {"success": True, "results": results, "count": len(results)}
|
|
342
343
|
```
|
|
343
344
|
|
|
344
|
-
Registering via `@register_tool` is optional — you can pass raw callables into `AgentConfig.tools`. Registering gives the tool metadata for the registry,
|
|
345
|
+
Registering via `@register_tool` is optional — you can pass raw callables into `AgentConfig.tools`. Registering gives the tool metadata for the registry, capability-aware permission gating, and tool-summary formatting.
|
|
345
346
|
|
|
346
|
-
###
|
|
347
|
+
### Capabilities
|
|
347
348
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
| `DANGEROUS` | Intercepted by HITL — user must approve before execution |
|
|
349
|
+
Tool access is gated by the **permission engine** (see the HITL section below). Each registered tool declares what it touches via `capabilities=`:
|
|
350
|
+
|
|
351
|
+
- **`Capability("namespace.action", target_arg="...")`** — e.g. `Capability("filesystem.write", target_arg="path")`, `Capability("http.read", target_arg="url")`, `Capability("shell.exec", target_arg="command")`. The engine resolves `target_arg` against the actual tool-call arguments and matches against rules.
|
|
352
|
+
- **`EXEMPT`** — opts the tool out of the engine entirely. Reserved for backend-internal tools (e.g. ADK `transfer_to_agent`, backend state tools).
|
|
353
353
|
|
|
354
354
|
### Built-in Tools
|
|
355
355
|
|
|
@@ -412,7 +412,7 @@ results = search_arxiv("transformer attention", max_results=10, categories=["cs.
|
|
|
412
412
|
paper = fetch_arxiv_paper("1706.03762") # "Attention Is All You Need"
|
|
413
413
|
```
|
|
414
414
|
|
|
415
|
-
The `ArxivSearchSource` (in `knowledge_base/sources.py`) is cached via the service registry and reused across tools,
|
|
415
|
+
The `ArxivSearchSource` (in `knowledge_base/sources.py`) is cached via the service registry and reused across tools. To pull a paper into the KB, use `ingest_arxiv_paper(arxiv_id)` — it composes the arxiv source with `kb_manager`.
|
|
416
416
|
|
|
417
417
|
#### File Operations
|
|
418
418
|
|
|
@@ -430,7 +430,7 @@ list_dir("src/", include_hidden=False)
|
|
|
430
430
|
diff_compare(source_a="old.txt", source_b="new.txt")
|
|
431
431
|
```
|
|
432
432
|
|
|
433
|
-
**WRITE tools
|
|
433
|
+
**WRITE tools**
|
|
434
434
|
|
|
435
435
|
```python
|
|
436
436
|
from agentic_cli.tools import write_file, edit_file
|
|
@@ -465,7 +465,8 @@ Renamed tools and a new concept-pages subsystem:
|
|
|
465
465
|
|
|
466
466
|
```python
|
|
467
467
|
from agentic_cli.tools import (
|
|
468
|
-
kb_search,
|
|
468
|
+
kb_search, kb_ingest_text, kb_ingest_file, kb_ingest_url,
|
|
469
|
+
kb_read, kb_list,
|
|
469
470
|
kb_write_concept, kb_search_concepts,
|
|
470
471
|
KB_READER_TOOLS, KB_WRITER_TOOLS,
|
|
471
472
|
)
|
|
@@ -474,12 +475,16 @@ from agentic_cli.tools import (
|
|
|
474
475
|
| Tool | Purpose |
|
|
475
476
|
|------|---------|
|
|
476
477
|
| `kb_search` | Hybrid BM25 + vector search with RRF fusion, filters by source/date |
|
|
477
|
-
| `
|
|
478
|
+
| `kb_ingest_text` | Ingest in-memory text content (no FS or network access) |
|
|
479
|
+
| `kb_ingest_file` | Ingest a local file; declares `filesystem.read(path)` for the permission engine |
|
|
480
|
+
| `kb_ingest_url` | Ingest content from an http(s) URL; routed through the hardened `ContentFetcher` and declares `http.read(url)` |
|
|
478
481
|
| `kb_read` | Return the per-document markdown sidecar (lazy-generated on first read) |
|
|
479
482
|
| `kb_list` | List documents, optionally filtered |
|
|
480
483
|
| `kb_write_concept` | Agent-authored concept/summary page (slugged, merged on overwrite) |
|
|
481
484
|
| `kb_search_concepts` | Search concept pages (title-weighted, case-insensitive) |
|
|
482
485
|
|
|
486
|
+
The single `kb_ingest` tool was split in 0.5.2 so each entry point declares the right capability — text-only stays under `kb.write`, while file and URL ingestion correctly trip `filesystem.read` / `http.read` checks. For arXiv papers, prefer `ingest_arxiv_paper(arxiv_id)`.
|
|
487
|
+
|
|
483
488
|
Bundle convenience:
|
|
484
489
|
|
|
485
490
|
```python
|
|
@@ -534,15 +539,9 @@ Each tool keeps at most N reflections (FIFO eviction). Reflections can be inject
|
|
|
534
539
|
|
|
535
540
|
#### HITL (Human-in-the-Loop)
|
|
536
541
|
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
1. **Automatic confirmation for `DANGEROUS` tools** — handled by ADK's `ConfirmationPlugin` and LangGraph's tool wrapper. No code changes needed; just mark the tool `DANGEROUS`.
|
|
540
|
-
2. **Explicit `request_approval` tool** — for domain-level checkpoints:
|
|
542
|
+
Tool calls are gated by the **permission engine** (`workflow/permissions/`). Each tool declares a list of capabilities (e.g. `filesystem.write(path=...)`); the engine evaluates them against rules from four sources (builtin defaults, user `~/.{app_name}/settings.json`, project `./.{app_name}/settings.json`, in-memory session). When no rule matches, the user is prompted with `Allow once / Allow for session / Allow always (save to project) / Deny`. Always-grants persist into the project settings file so the next run picks them up automatically.
|
|
541
543
|
|
|
542
|
-
|
|
543
|
-
from agentic_cli.tools import hitl_tools
|
|
544
|
-
# request_approval(message, options) -> {"success": True, "choice": "..."}
|
|
545
|
-
```
|
|
544
|
+
See `docs/superpowers/specs/2026-04-18-permissions-system-design.md` for the full design.
|
|
546
545
|
|
|
547
546
|
## CLI Commands
|
|
548
547
|
|
|
@@ -718,22 +717,32 @@ agentic-cli/
|
|
|
718
717
|
│ │ ├── adk/
|
|
719
718
|
│ │ │ ├── manager.py # GoogleADKWorkflowManager
|
|
720
719
|
│ │ │ ├── event_processor.py
|
|
721
|
-
│ │ │ ├── plugins.py #
|
|
720
|
+
│ │ │ ├── plugins.py # LLMLoggingPlugin
|
|
721
|
+
│ │ │ ├── permission_plugin.py # PermissionPlugin (capability gating)
|
|
722
722
|
│ │ │ └── task_progress_plugin.py
|
|
723
|
-
│ │
|
|
724
|
-
│ │
|
|
725
|
-
│ │
|
|
726
|
-
│ │
|
|
727
|
-
│ │
|
|
723
|
+
│ │ ├── langgraph/
|
|
724
|
+
│ │ │ ├── manager.py # LangGraphWorkflowManager
|
|
725
|
+
│ │ │ ├── graph_builder.py
|
|
726
|
+
│ │ │ ├── permission_wrap.py # wrap_tool_for_permission
|
|
727
|
+
│ │ │ ├── state.py
|
|
728
|
+
│ │ │ └── persistence/ # Checkpointers and stores
|
|
729
|
+
│ │ └── permissions/ # Framework-independent permission engine
|
|
730
|
+
│ │ ├── capabilities.py # Capability, ResolvedCapability, EXEMPT
|
|
731
|
+
│ │ ├── rules.py # Rule, Effect, RuleSource, CheckResult, AskScope
|
|
732
|
+
│ │ ├── matchers.py # Path/URL/Shell/StringGlob matchers
|
|
733
|
+
│ │ ├── store.py # PermissionContext, BUILTIN_RULES, JSON load/save
|
|
734
|
+
│ │ ├── prompt.py # build_request + parse_response
|
|
735
|
+
│ │ └── engine.py # PermissionEngine
|
|
728
736
|
│ ├── tools/
|
|
729
|
-
│ │ ├── registry.py # ToolRegistry, ToolCategory,
|
|
737
|
+
│ │ ├── registry.py # ToolRegistry, ToolCategory, register_tool
|
|
730
738
|
│ │ ├── factories.py # Backend-aware tool factories
|
|
731
739
|
│ │ ├── executor.py # SafePythonExecutor
|
|
732
740
|
│ │ ├── arxiv_tools.py # search_arxiv, fetch_arxiv_paper, ingest_arxiv_paper
|
|
733
741
|
│ │ ├── arxiv_source.py # ArxivSearchSource (service-registered)
|
|
734
742
|
│ │ ├── execution_tools.py # execute_python
|
|
735
743
|
│ │ ├── interaction_tools.py # ask_clarification
|
|
736
|
-
│ │ ├── knowledge_tools.py # kb_search /
|
|
744
|
+
│ │ ├── knowledge_tools.py # kb_search / kb_ingest_text / kb_ingest_file /
|
|
745
|
+
│ │ │ # kb_ingest_url / kb_read / kb_list /
|
|
737
746
|
│ │ │ # kb_write_concept / kb_search_concepts
|
|
738
747
|
│ │ ├── file_read.py # read_file, diff_compare
|
|
739
748
|
│ │ ├── file_write.py # write_file, edit_file (atomic)
|
|
@@ -744,7 +753,6 @@ agentic-cli/
|
|
|
744
753
|
│ │ ├── pdf_utils.py # PDF text extraction helpers
|
|
745
754
|
│ │ ├── memory_tools.py # save/search/update/delete + MemoryStore
|
|
746
755
|
│ │ ├── reflection_tools.py # save_reflection + ToolReflectionStore
|
|
747
|
-
│ │ ├── hitl_tools.py # request_approval
|
|
748
756
|
│ │ ├── _core/ # Shared planning/task logic
|
|
749
757
|
│ │ │ ├── planning.py
|
|
750
758
|
│ │ │ └── tasks.py
|
|
@@ -50,7 +50,7 @@ When asked to research papers on a topic:
|
|
|
50
50
|
1. Check if a relevant concept page already exists with `kb_search_concepts(topic)`. If one is relevant, read it first — it already synthesizes prior work and lists the source `doc_ids` you can follow.
|
|
51
51
|
2. Search arXiv for relevant papers. If a search returns `success: false`, report the error instead of retrying with simpler queries.
|
|
52
52
|
3. Fetch metadata for papers of interest.
|
|
53
|
-
4. Ingest papers into the knowledge base
|
|
53
|
+
4. Ingest papers into the knowledge base. For arXiv papers, call `ingest_arxiv_paper(arxiv_id)` — it handles metadata, PDF download, and rate limiting. For other web sources use `kb_ingest_url(url)`; for a local PDF use `kb_ingest_file(path)`; for raw text you already hold use `kb_ingest_text(content)`. Each returns a `document_id` and the LLM-generated `summary`.
|
|
54
54
|
5. Read each paper's sidecar with `kb_read(doc_id)` to plan the per-paper analysis.
|
|
55
55
|
6. Pull full text with `kb_read(doc_id, full=True)` for any sections where the sidecar is insufficient (specific numbers, quotes, methodology details).
|
|
56
56
|
7. Save detailed per-paper analyses via `write_file`.
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "agentic-cli"
|
|
7
|
-
version = "0.5.
|
|
7
|
+
version = "0.5.2"
|
|
8
8
|
description = "A framework for building domain-specific agentic CLI applications"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -46,6 +46,8 @@ kb = [
|
|
|
46
46
|
"torch>=2.2.0",
|
|
47
47
|
"sentence-transformers>=2.0.0,<3.0.0",
|
|
48
48
|
"faiss-cpu>=1.7.0",
|
|
49
|
+
"bm25s>=0.2.0",
|
|
50
|
+
"rank-bm25>=0.2.2",
|
|
49
51
|
]
|
|
50
52
|
langgraph = [
|
|
51
53
|
"langgraph>=0.2.0",
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""Real BM25 index backends (bm25s, rank_bm25).
|
|
2
|
+
|
|
3
|
+
Both implement the same interface as MockBM25Index so create_bm25_index()
|
|
4
|
+
can return any of them interchangeably. Tokenization is lowercase whitespace
|
|
5
|
+
split to match MockBM25Index; the scoring model is the library's own BM25.
|
|
6
|
+
|
|
7
|
+
Neither underlying library supports true incremental add/remove, so we keep
|
|
8
|
+
tokenized docs + chunk_ids in memory and rebuild the model lazily on search.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
from agentic_cli.file_utils import atomic_write_json
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _tokenize(text: str) -> list[str]:
|
|
20
|
+
return text.lower().split()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class _BM25BackendBase:
|
|
24
|
+
_INDEX_FILE: str = ""
|
|
25
|
+
|
|
26
|
+
def __init__(self):
|
|
27
|
+
self._chunk_ids: list[str] = []
|
|
28
|
+
self._tokenized: list[list[str]] = []
|
|
29
|
+
self._model = None
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def size(self) -> int:
|
|
33
|
+
return len(self._chunk_ids)
|
|
34
|
+
|
|
35
|
+
def add_documents(self, chunk_ids: list[str], texts: list[str]) -> None:
|
|
36
|
+
for cid, text in zip(chunk_ids, texts):
|
|
37
|
+
self._chunk_ids.append(cid)
|
|
38
|
+
self._tokenized.append(_tokenize(text))
|
|
39
|
+
self._model = None
|
|
40
|
+
|
|
41
|
+
def remove_documents(self, chunk_ids: list[str]) -> None:
|
|
42
|
+
remove_set = set(chunk_ids)
|
|
43
|
+
keep = [
|
|
44
|
+
(cid, toks)
|
|
45
|
+
for cid, toks in zip(self._chunk_ids, self._tokenized)
|
|
46
|
+
if cid not in remove_set
|
|
47
|
+
]
|
|
48
|
+
if keep:
|
|
49
|
+
self._chunk_ids, self._tokenized = map(list, zip(*keep))
|
|
50
|
+
else:
|
|
51
|
+
self._chunk_ids, self._tokenized = [], []
|
|
52
|
+
self._model = None
|
|
53
|
+
|
|
54
|
+
def rebuild(self, chunk_ids: list[str], texts: list[str]) -> None:
|
|
55
|
+
self._chunk_ids = []
|
|
56
|
+
self._tokenized = []
|
|
57
|
+
self._model = None
|
|
58
|
+
self.add_documents(chunk_ids, texts)
|
|
59
|
+
|
|
60
|
+
def save(self, path: Path) -> None:
|
|
61
|
+
path.mkdir(parents=True, exist_ok=True)
|
|
62
|
+
atomic_write_json(
|
|
63
|
+
path / self._INDEX_FILE,
|
|
64
|
+
{"chunk_ids": self._chunk_ids, "tokenized": self._tokenized},
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
def load(self, path: Path) -> None:
|
|
68
|
+
index_path = path / self._INDEX_FILE
|
|
69
|
+
if not index_path.exists():
|
|
70
|
+
return
|
|
71
|
+
data = json.loads(index_path.read_text())
|
|
72
|
+
self._chunk_ids = data["chunk_ids"]
|
|
73
|
+
self._tokenized = data["tokenized"]
|
|
74
|
+
self._model = None
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class RankBM25Index(_BM25BackendBase):
|
|
78
|
+
"""BM25 backed by rank_bm25.BM25Plus (pure Python).
|
|
79
|
+
|
|
80
|
+
BM25Plus rather than BM25Okapi because Okapi's IDF can go zero or
|
|
81
|
+
negative when a term appears in most of the corpus; BM25Plus adds a
|
|
82
|
+
delta offset that guarantees positive contributions on real matches.
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
_INDEX_FILE = "bm25_rank.json"
|
|
86
|
+
|
|
87
|
+
def search(self, query: str, top_k: int = 10) -> list[tuple[str, float]]:
|
|
88
|
+
if not self._chunk_ids:
|
|
89
|
+
return []
|
|
90
|
+
query_tokens = _tokenize(query)
|
|
91
|
+
if not query_tokens:
|
|
92
|
+
return []
|
|
93
|
+
query_set = set(query_tokens)
|
|
94
|
+
if self._model is None:
|
|
95
|
+
from rank_bm25 import BM25Plus
|
|
96
|
+
|
|
97
|
+
self._model = BM25Plus(self._tokenized)
|
|
98
|
+
scores = self._model.get_scores(query_tokens)
|
|
99
|
+
scored: list[tuple[str, float]] = []
|
|
100
|
+
for cid, doc_tokens, score in zip(
|
|
101
|
+
self._chunk_ids, self._tokenized, scores
|
|
102
|
+
):
|
|
103
|
+
if query_set.intersection(doc_tokens):
|
|
104
|
+
scored.append((cid, float(score)))
|
|
105
|
+
scored.sort(key=lambda x: x[1], reverse=True)
|
|
106
|
+
return scored[:top_k]
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class BM25sIndex(_BM25BackendBase):
|
|
110
|
+
"""BM25 backed by bm25s (NumPy/C-accelerated)."""
|
|
111
|
+
|
|
112
|
+
_INDEX_FILE = "bm25s_sidecar.json"
|
|
113
|
+
|
|
114
|
+
def _build_model(self) -> None:
|
|
115
|
+
import bm25s
|
|
116
|
+
|
|
117
|
+
model = bm25s.BM25()
|
|
118
|
+
model.index(self._tokenized, show_progress=False)
|
|
119
|
+
self._model = model
|
|
120
|
+
|
|
121
|
+
def search(self, query: str, top_k: int = 10) -> list[tuple[str, float]]:
|
|
122
|
+
if not self._chunk_ids:
|
|
123
|
+
return []
|
|
124
|
+
query_tokens = _tokenize(query)
|
|
125
|
+
if not query_tokens:
|
|
126
|
+
return []
|
|
127
|
+
if self._model is None:
|
|
128
|
+
self._build_model()
|
|
129
|
+
k = min(top_k, len(self._chunk_ids))
|
|
130
|
+
docs, scores = self._model.retrieve(
|
|
131
|
+
[query_tokens], k=k, show_progress=False
|
|
132
|
+
)
|
|
133
|
+
results: list[tuple[str, float]] = []
|
|
134
|
+
for idx, score in zip(docs[0], scores[0]):
|
|
135
|
+
if score > 0:
|
|
136
|
+
results.append((self._chunk_ids[int(idx)], float(score)))
|
|
137
|
+
return results
|