agentic-cli 0.5.0__tar.gz → 0.5.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/CHANGELOG.md +26 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/CLAUDE.md +7 -3
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/PKG-INFO +36 -32
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/README.md +33 -31
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/pyproject.toml +3 -1
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/__init__.py +1 -1
- agentic_cli-0.5.1/src/agentic_cli/knowledge_base/_bm25_backends.py +137 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/knowledge_base/manager.py +61 -18
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/__init__.py +0 -5
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/adk/state_tools.py +6 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/arxiv_tools.py +7 -4
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/execution_tools.py +2 -2
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/factories.py +27 -75
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/file_read.py +3 -5
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/file_write.py +3 -4
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/glob_tool.py +3 -5
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/grep_tool.py +2 -4
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/interaction_tools.py +2 -2
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/knowledge_tools.py +8 -9
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/langgraph/state_tools.py +6 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/memory_tools.py +87 -48
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/reflection_tools.py +3 -2
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/registry.py +49 -25
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/sandbox/__init__.py +3 -2
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/search.py +2 -2
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/shell/executor.py +3 -3
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/webfetch_tool.py +3 -2
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/workflow/adk/__init__.py +1 -1
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/workflow/adk/manager.py +3 -2
- agentic_cli-0.5.1/src/agentic_cli/workflow/adk/permission_plugin.py +72 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/workflow/adk/plugins.py +1 -97
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/workflow/base_manager.py +20 -4
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/workflow/langgraph/graph_builder.py +2 -45
- agentic_cli-0.5.1/src/agentic_cli/workflow/langgraph/permission_wrap.py +58 -0
- agentic_cli-0.5.1/src/agentic_cli/workflow/permissions/__init__.py +37 -0
- agentic_cli-0.5.1/src/agentic_cli/workflow/permissions/capabilities.py +44 -0
- agentic_cli-0.5.1/src/agentic_cli/workflow/permissions/engine.py +226 -0
- agentic_cli-0.5.1/src/agentic_cli/workflow/permissions/matchers.py +193 -0
- agentic_cli-0.5.1/src/agentic_cli/workflow/permissions/prompt.py +66 -0
- agentic_cli-0.5.1/src/agentic_cli/workflow/permissions/rules.py +39 -0
- agentic_cli-0.5.1/src/agentic_cli/workflow/permissions/store.py +114 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/workflow/service_registry.py +4 -3
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/workflow/settings.py +26 -6
- agentic_cli-0.5.1/tests/integration/test_permission_adk.py +141 -0
- agentic_cli-0.5.1/tests/integration/test_permission_langgraph.py +110 -0
- agentic_cli-0.5.1/tests/permissions/test_capabilities.py +47 -0
- agentic_cli-0.5.1/tests/permissions/test_engine.py +435 -0
- agentic_cli-0.5.1/tests/permissions/test_matchers.py +175 -0
- agentic_cli-0.5.1/tests/permissions/test_prompt.py +92 -0
- agentic_cli-0.5.1/tests/permissions/test_rules.py +47 -0
- agentic_cli-0.5.1/tests/permissions/test_store.py +179 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_adk_plugins.py +4 -228
- agentic_cli-0.5.1/tests/test_bm25_index.py +188 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_config.py +0 -4
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_kb_sidecar.py +71 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_memory.py +83 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_tools.py +28 -86
- agentic_cli-0.5.1/tests/tools/test_registry.py +66 -0
- agentic_cli-0.5.1/tests/workflow/__init__.py +0 -0
- agentic_cli-0.5.1/tests/workflow/test_backend_isolation.py +56 -0
- agentic_cli-0.5.1/tests/workflow/test_base_manager_permissions.py +27 -0
- agentic_cli-0.5.1/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/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.1}/.gitignore +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/LICENSE +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/environment.yml +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/examples/arxiv_demo.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/examples/fileops_demo.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/examples/hello_agent.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/examples/hello_langgraph.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/examples/memory_demo.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/examples/research_demo/__init__.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/examples/research_demo/__main__.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/examples/research_demo/agents.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/examples/research_demo/app.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/examples/research_demo/commands.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/examples/research_demo/settings.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/examples/shell_demo.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/examples/webfetch_demo.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/examples/websearch_demo.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/requirements-dev.txt +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/requirements.txt +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/cli/__init__.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/cli/app.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/cli/builtin_commands.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/cli/commands.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/cli/message_processor.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/cli/settings_command.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/cli/settings_introspection.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/cli/usage_tracker.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/cli/workflow_controller.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/config.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/constants.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/file_utils.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/knowledge_base/__init__.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/knowledge_base/_mock_bm25.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/knowledge_base/_mocks.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/knowledge_base/bm25_index.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/knowledge_base/concepts.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/knowledge_base/embeddings.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/knowledge_base/models.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/knowledge_base/sidecar.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/knowledge_base/sources.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/knowledge_base/vector_store.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/logging.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/persistence/__init__.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/persistence/session.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/settings_mixins.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/settings_persistence.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/_core/__init__.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/_core/planning.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/_core/tasks.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/adk/__init__.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/arxiv_source.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/executor.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/langgraph/__init__.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/pdf_utils.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/sandbox/backends/__init__.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/sandbox/backends/base.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/sandbox/backends/jupyter_local.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/sandbox/manager.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/sandbox/models.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/shell/__init__.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/shell/audit.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/shell/classifier.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/shell/config.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/shell/models.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/shell/os_sandbox/__init__.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/shell/os_sandbox/base.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/shell/os_sandbox/bubblewrap.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/shell/os_sandbox/detect.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/shell/os_sandbox/policy.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/shell/os_sandbox/seatbelt.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/shell/os_sandbox/seatbelt_profile.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/shell/path_analyzer.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/shell/preprocessor.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/shell/risk_assessor.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/shell/sandbox.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/shell/tokenizer.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/webfetch/__init__.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/webfetch/converter.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/webfetch/fetcher.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/webfetch/robots.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/webfetch/summarizer.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/webfetch/validator.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/workflow/__init__.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/workflow/adk/event_processor.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/workflow/adk/task_progress_plugin.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/workflow/config.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/workflow/events.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/workflow/factory.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/workflow/langgraph/__init__.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/workflow/langgraph/manager.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/workflow/langgraph/persistence/__init__.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/workflow/langgraph/persistence/checkpointers.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/workflow/langgraph/persistence/stores.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/workflow/langgraph/state.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/workflow/models.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/workflow/retry.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/workflow/tool_summaries.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/__init__.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/conftest.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/integration/__init__.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/integration/conftest.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/integration/helpers.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/integration/test_adk_integration.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/integration/test_kb_research_flow.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/integration/test_langgraph_integration.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/integration/test_live.py +0 -0
- {agentic_cli-0.5.0/tests/workflow → agentic_cli-0.5.1/tests/permissions}/__init__.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_adk_event_processor.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_arxiv_tools.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_commands.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_concept_store.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_concurrent_stores.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_context_trimming.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_context_window.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_dual_thinking_boxes.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_embeddings.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_grep_tool_cache.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_input_callback.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_kb_concepts.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_kb_helpers.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_kb_index_md.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_kb_ingest_log.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_kb_tool_renames.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_knowledge_base.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_knowledge_tools.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_langgraph.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_model_registry.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_persistence.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_planning.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_reflection.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_session_save_resume.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_settings_persistence.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_task_tools.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_token_caching.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_tool_summaries.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_update_setting.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_usage_tracker.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_vector_store.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_webfetch.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_workflow.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_workflow_controller.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/tools/__init__.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/tools/test_adk_state_tools.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/tools/test_bubblewrap.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/tools/test_core_planning.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/tools/test_core_tasks.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/tools/test_factories.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/tools/test_langgraph_state_tools.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/tools/test_os_sandbox.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/tools/test_sandbox.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/tools/test_seatbelt_profile.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/tools/test_shell_security.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/workflow/test_task_progress_plugin.py +0 -0
- {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/workflow/test_tool_assembly.py +0 -0
|
@@ -5,6 +5,32 @@ 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.1] - 2026-04-18
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **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.
|
|
12
|
+
- **Matchers**: `PathMatcher` (with `**` glob), `URLMatcher`, `ShellMatcher`, and `StringGlobMatcher`.
|
|
13
|
+
- **ADK `PermissionPlugin`**: capability gating for ADK agents.
|
|
14
|
+
- **LangGraph `wrap_tool_for_permission`**: per-tool permission wrapping for LangGraph `ToolNode`.
|
|
15
|
+
- **`permissions` and `permissions_enabled` settings** for runtime control.
|
|
16
|
+
- **`EXEMPT` sentinel**: opts a tool out of the permission engine (used for backend state tools and ADK `transfer_to_agent`).
|
|
17
|
+
- **Real BM25 backends**: `bm25s` (preferred) and `rank_bm25` fallback.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
- **`@register_tool`** now requires the `capabilities=` kwarg.
|
|
21
|
+
- **Default grants broadened**: `filesystem.*` auto-extends to the parent directory; `memory.*` and `kb.*` are allowed by default.
|
|
22
|
+
- **Ask prompt** now shows the directory-scope target.
|
|
23
|
+
- **Workflow init**: `_ensure_managers_initialized` runs in a worker thread to avoid blocking the event loop.
|
|
24
|
+
- **HITL confirmation** extracted into a backend-neutral module during the permissions migration.
|
|
25
|
+
- **Memory tools**: deduped registry-bound and factory-bound entry points.
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
- Matcher canonicalize/matches preserves the `*` wildcard.
|
|
29
|
+
- `KnowledgeBaseManager` concurrency contract tightened.
|
|
30
|
+
|
|
31
|
+
### Removed
|
|
32
|
+
- **`PermissionLevel`** (SAFE / CAUTION / DANGEROUS), `ConfirmationPlugin`, `_wrap_for_confirmation`, `hitl_tools`, and the `hitl_enabled` setting — superseded by the permission engine.
|
|
33
|
+
|
|
8
34
|
## [0.5.0] - 2026-04-17
|
|
9
35
|
|
|
10
36
|
### 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.1
|
|
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
|
|
|
@@ -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
|
|
@@ -580,15 +582,9 @@ Each tool keeps at most N reflections (FIFO eviction). Reflections can be inject
|
|
|
580
582
|
|
|
581
583
|
#### HITL (Human-in-the-Loop)
|
|
582
584
|
|
|
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:
|
|
585
|
+
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
586
|
|
|
588
|
-
|
|
589
|
-
from agentic_cli.tools import hitl_tools
|
|
590
|
-
# request_approval(message, options) -> {"success": True, "choice": "..."}
|
|
591
|
-
```
|
|
587
|
+
See `docs/superpowers/specs/2026-04-18-permissions-system-design.md` for the full design.
|
|
592
588
|
|
|
593
589
|
## CLI Commands
|
|
594
590
|
|
|
@@ -764,15 +760,24 @@ agentic-cli/
|
|
|
764
760
|
│ │ ├── adk/
|
|
765
761
|
│ │ │ ├── manager.py # GoogleADKWorkflowManager
|
|
766
762
|
│ │ │ ├── event_processor.py
|
|
767
|
-
│ │ │ ├── plugins.py #
|
|
763
|
+
│ │ │ ├── plugins.py # LLMLoggingPlugin
|
|
764
|
+
│ │ │ ├── permission_plugin.py # PermissionPlugin (capability gating)
|
|
768
765
|
│ │ │ └── task_progress_plugin.py
|
|
769
|
-
│ │
|
|
770
|
-
│ │
|
|
771
|
-
│ │
|
|
772
|
-
│ │
|
|
773
|
-
│ │
|
|
766
|
+
│ │ ├── langgraph/
|
|
767
|
+
│ │ │ ├── manager.py # LangGraphWorkflowManager
|
|
768
|
+
│ │ │ ├── graph_builder.py
|
|
769
|
+
│ │ │ ├── permission_wrap.py # wrap_tool_for_permission
|
|
770
|
+
│ │ │ ├── state.py
|
|
771
|
+
│ │ │ └── persistence/ # Checkpointers and stores
|
|
772
|
+
│ │ └── permissions/ # Framework-independent permission engine
|
|
773
|
+
│ │ ├── capabilities.py # Capability, ResolvedCapability, EXEMPT
|
|
774
|
+
│ │ ├── rules.py # Rule, Effect, RuleSource, CheckResult, AskScope
|
|
775
|
+
│ │ ├── matchers.py # Path/URL/Shell/StringGlob matchers
|
|
776
|
+
│ │ ├── store.py # PermissionContext, BUILTIN_RULES, JSON load/save
|
|
777
|
+
│ │ ├── prompt.py # build_request + parse_response
|
|
778
|
+
│ │ └── engine.py # PermissionEngine
|
|
774
779
|
│ ├── tools/
|
|
775
|
-
│ │ ├── registry.py # ToolRegistry, ToolCategory,
|
|
780
|
+
│ │ ├── registry.py # ToolRegistry, ToolCategory, register_tool
|
|
776
781
|
│ │ ├── factories.py # Backend-aware tool factories
|
|
777
782
|
│ │ ├── executor.py # SafePythonExecutor
|
|
778
783
|
│ │ ├── arxiv_tools.py # search_arxiv, fetch_arxiv_paper, ingest_arxiv_paper
|
|
@@ -790,7 +795,6 @@ agentic-cli/
|
|
|
790
795
|
│ │ ├── pdf_utils.py # PDF text extraction helpers
|
|
791
796
|
│ │ ├── memory_tools.py # save/search/update/delete + MemoryStore
|
|
792
797
|
│ │ ├── reflection_tools.py # save_reflection + ToolReflectionStore
|
|
793
|
-
│ │ ├── hitl_tools.py # request_approval
|
|
794
798
|
│ │ ├── _core/ # Shared planning/task logic
|
|
795
799
|
│ │ │ ├── planning.py
|
|
796
800
|
│ │ │ └── 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
|
|
|
@@ -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
|
|
@@ -534,15 +534,9 @@ Each tool keeps at most N reflections (FIFO eviction). Reflections can be inject
|
|
|
534
534
|
|
|
535
535
|
#### HITL (Human-in-the-Loop)
|
|
536
536
|
|
|
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:
|
|
537
|
+
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
538
|
|
|
542
|
-
|
|
543
|
-
from agentic_cli.tools import hitl_tools
|
|
544
|
-
# request_approval(message, options) -> {"success": True, "choice": "..."}
|
|
545
|
-
```
|
|
539
|
+
See `docs/superpowers/specs/2026-04-18-permissions-system-design.md` for the full design.
|
|
546
540
|
|
|
547
541
|
## CLI Commands
|
|
548
542
|
|
|
@@ -718,15 +712,24 @@ agentic-cli/
|
|
|
718
712
|
│ │ ├── adk/
|
|
719
713
|
│ │ │ ├── manager.py # GoogleADKWorkflowManager
|
|
720
714
|
│ │ │ ├── event_processor.py
|
|
721
|
-
│ │ │ ├── plugins.py #
|
|
715
|
+
│ │ │ ├── plugins.py # LLMLoggingPlugin
|
|
716
|
+
│ │ │ ├── permission_plugin.py # PermissionPlugin (capability gating)
|
|
722
717
|
│ │ │ └── task_progress_plugin.py
|
|
723
|
-
│ │
|
|
724
|
-
│ │
|
|
725
|
-
│ │
|
|
726
|
-
│ │
|
|
727
|
-
│ │
|
|
718
|
+
│ │ ├── langgraph/
|
|
719
|
+
│ │ │ ├── manager.py # LangGraphWorkflowManager
|
|
720
|
+
│ │ │ ├── graph_builder.py
|
|
721
|
+
│ │ │ ├── permission_wrap.py # wrap_tool_for_permission
|
|
722
|
+
│ │ │ ├── state.py
|
|
723
|
+
│ │ │ └── persistence/ # Checkpointers and stores
|
|
724
|
+
│ │ └── permissions/ # Framework-independent permission engine
|
|
725
|
+
│ │ ├── capabilities.py # Capability, ResolvedCapability, EXEMPT
|
|
726
|
+
│ │ ├── rules.py # Rule, Effect, RuleSource, CheckResult, AskScope
|
|
727
|
+
│ │ ├── matchers.py # Path/URL/Shell/StringGlob matchers
|
|
728
|
+
│ │ ├── store.py # PermissionContext, BUILTIN_RULES, JSON load/save
|
|
729
|
+
│ │ ├── prompt.py # build_request + parse_response
|
|
730
|
+
│ │ └── engine.py # PermissionEngine
|
|
728
731
|
│ ├── tools/
|
|
729
|
-
│ │ ├── registry.py # ToolRegistry, ToolCategory,
|
|
732
|
+
│ │ ├── registry.py # ToolRegistry, ToolCategory, register_tool
|
|
730
733
|
│ │ ├── factories.py # Backend-aware tool factories
|
|
731
734
|
│ │ ├── executor.py # SafePythonExecutor
|
|
732
735
|
│ │ ├── arxiv_tools.py # search_arxiv, fetch_arxiv_paper, ingest_arxiv_paper
|
|
@@ -744,7 +747,6 @@ agentic-cli/
|
|
|
744
747
|
│ │ ├── pdf_utils.py # PDF text extraction helpers
|
|
745
748
|
│ │ ├── memory_tools.py # save/search/update/delete + MemoryStore
|
|
746
749
|
│ │ ├── reflection_tools.py # save_reflection + ToolReflectionStore
|
|
747
|
-
│ │ ├── hitl_tools.py # request_approval
|
|
748
750
|
│ │ ├── _core/ # Shared planning/task logic
|
|
749
751
|
│ │ │ ├── planning.py
|
|
750
752
|
│ │ │ └── tasks.py
|
|
@@ -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.1"
|
|
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
|
|
@@ -96,6 +96,26 @@ class KnowledgeBaseManager:
|
|
|
96
96
|
"""Main interface for knowledge base operations.
|
|
97
97
|
|
|
98
98
|
Provides document ingestion, semantic search, and document management.
|
|
99
|
+
|
|
100
|
+
Concurrency model
|
|
101
|
+
-----------------
|
|
102
|
+
Two lock surfaces with different semantics:
|
|
103
|
+
|
|
104
|
+
* ``_lock`` (``threading.Lock``) — guards every mutation to the
|
|
105
|
+
in-memory document/chunk/vector/BM25 state and the check-and-set
|
|
106
|
+
of ``_backfill_running``. The sync mutation API
|
|
107
|
+
(``ingest_document``, ``search``, ``delete_document``, ``clear``)
|
|
108
|
+
is safe to call from any thread.
|
|
109
|
+
* ``_sidecar_locks`` (``dict[str, asyncio.Lock]``) — per-doc locks
|
|
110
|
+
that serialize concurrent LLM-backed sidecar generation for the
|
|
111
|
+
same document. Always obtain these through
|
|
112
|
+
:meth:`get_or_create_sidecar_lock`; never index the dict directly.
|
|
113
|
+
|
|
114
|
+
The async sidecar path (``backfill_sidecars`` and the lazy
|
|
115
|
+
``kb_read`` fallback in ``tools/knowledge_tools``) is safe within a
|
|
116
|
+
single asyncio event loop. Sharing a manager across multiple event
|
|
117
|
+
loops is NOT supported — the per-doc ``asyncio.Lock`` is bound to
|
|
118
|
+
the loop it was created on.
|
|
99
119
|
"""
|
|
100
120
|
|
|
101
121
|
def __init__(
|
|
@@ -302,6 +322,23 @@ class KnowledgeBaseManager:
|
|
|
302
322
|
if path.exists():
|
|
303
323
|
path.unlink()
|
|
304
324
|
|
|
325
|
+
def get_or_create_sidecar_lock(self, doc_id: str) -> asyncio.Lock:
|
|
326
|
+
"""Return the per-doc async lock used to serialize sidecar writes.
|
|
327
|
+
|
|
328
|
+
Same lock is returned for the same ``doc_id`` across repeat calls,
|
|
329
|
+
so ``backfill_sidecars`` and the lazy ``kb_read`` fallback cannot
|
|
330
|
+
double-generate a sidecar for the same document.
|
|
331
|
+
|
|
332
|
+
The dict insert is guarded by ``_lock`` so two threads racing to
|
|
333
|
+
create the lock for a new doc will not produce two distinct
|
|
334
|
+
``asyncio.Lock`` instances.
|
|
335
|
+
"""
|
|
336
|
+
existing = self._sidecar_locks.get(doc_id)
|
|
337
|
+
if existing is not None:
|
|
338
|
+
return existing
|
|
339
|
+
with self._lock:
|
|
340
|
+
return self._sidecar_locks.setdefault(doc_id, asyncio.Lock())
|
|
341
|
+
|
|
305
342
|
def _index_md_path(self) -> Path:
|
|
306
343
|
return self.kb_dir / "index.md"
|
|
307
344
|
|
|
@@ -1028,35 +1065,40 @@ class KnowledgeBaseManager:
|
|
|
1028
1065
|
Returns:
|
|
1029
1066
|
Number of sidecars written this call.
|
|
1030
1067
|
"""
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
# concurrently.
|
|
1068
|
+
# Check-and-set the running flag and snapshot the work list under
|
|
1069
|
+
# one lock acquisition so a second thread cannot slip past the
|
|
1070
|
+
# guard between the read and the write.
|
|
1071
|
+
with self._lock:
|
|
1072
|
+
if self._backfill_running:
|
|
1073
|
+
raise BackfillAlreadyRunning(
|
|
1074
|
+
"backfill_sidecars is already running on this KB"
|
|
1075
|
+
)
|
|
1076
|
+
self._backfill_running = True
|
|
1041
1077
|
todo = [
|
|
1042
1078
|
doc
|
|
1043
|
-
for doc in
|
|
1079
|
+
for doc in self._documents.values()
|
|
1044
1080
|
if not self._sidecar_path(doc.id).exists()
|
|
1045
1081
|
]
|
|
1082
|
+
try:
|
|
1083
|
+
written = 0
|
|
1046
1084
|
total = len(todo)
|
|
1047
1085
|
for i, doc in enumerate(todo):
|
|
1048
1086
|
# Guard against delete-during-backfill — doc may have
|
|
1049
|
-
# been removed since the snapshot was taken.
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1087
|
+
# been removed since the snapshot was taken. Checked
|
|
1088
|
+
# under the lock so a concurrent delete_document cannot
|
|
1089
|
+
# interleave between the check and use.
|
|
1090
|
+
with self._lock:
|
|
1091
|
+
if doc.id not in self._documents:
|
|
1092
|
+
continue
|
|
1093
|
+
lock = self.get_or_create_sidecar_lock(doc.id)
|
|
1053
1094
|
async with lock:
|
|
1054
1095
|
# Double-check inside the lock — another task may
|
|
1055
1096
|
# have written it (e.g. lazy kb_read fallback).
|
|
1056
1097
|
if self._sidecar_path(doc.id).exists():
|
|
1057
1098
|
continue
|
|
1058
|
-
|
|
1059
|
-
|
|
1099
|
+
with self._lock:
|
|
1100
|
+
if doc.id not in self._documents:
|
|
1101
|
+
continue
|
|
1060
1102
|
if progress_cb is not None:
|
|
1061
1103
|
progress_cb(i, total, doc)
|
|
1062
1104
|
payload = await self.generate_sidecar_payload(
|
|
@@ -1066,7 +1108,8 @@ class KnowledgeBaseManager:
|
|
|
1066
1108
|
written += 1
|
|
1067
1109
|
return written
|
|
1068
1110
|
finally:
|
|
1069
|
-
self.
|
|
1111
|
+
with self._lock:
|
|
1112
|
+
self._backfill_running = False
|
|
1070
1113
|
|
|
1071
1114
|
|
|
1072
1115
|
class BackfillAlreadyRunning(RuntimeError):
|