agentpool 2.1.9__py3-none-any.whl
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.
Potentially problematic release.
This version of agentpool might be problematic. Click here for more details.
- acp/README.md +64 -0
- acp/__init__.py +172 -0
- acp/__main__.py +10 -0
- acp/acp_requests.py +285 -0
- acp/agent/__init__.py +6 -0
- acp/agent/connection.py +256 -0
- acp/agent/implementations/__init__.py +6 -0
- acp/agent/implementations/debug_server/__init__.py +1 -0
- acp/agent/implementations/debug_server/cli.py +79 -0
- acp/agent/implementations/debug_server/debug.html +234 -0
- acp/agent/implementations/debug_server/debug_server.py +496 -0
- acp/agent/implementations/testing.py +91 -0
- acp/agent/protocol.py +65 -0
- acp/bridge/README.md +162 -0
- acp/bridge/__init__.py +6 -0
- acp/bridge/__main__.py +91 -0
- acp/bridge/bridge.py +246 -0
- acp/bridge/py.typed +0 -0
- acp/bridge/settings.py +15 -0
- acp/client/__init__.py +7 -0
- acp/client/connection.py +251 -0
- acp/client/implementations/__init__.py +7 -0
- acp/client/implementations/default_client.py +185 -0
- acp/client/implementations/headless_client.py +266 -0
- acp/client/implementations/noop_client.py +110 -0
- acp/client/protocol.py +61 -0
- acp/connection.py +280 -0
- acp/exceptions.py +46 -0
- acp/filesystem.py +524 -0
- acp/notifications.py +832 -0
- acp/py.typed +0 -0
- acp/schema/__init__.py +265 -0
- acp/schema/agent_plan.py +30 -0
- acp/schema/agent_requests.py +126 -0
- acp/schema/agent_responses.py +256 -0
- acp/schema/base.py +39 -0
- acp/schema/capabilities.py +230 -0
- acp/schema/client_requests.py +247 -0
- acp/schema/client_responses.py +96 -0
- acp/schema/common.py +81 -0
- acp/schema/content_blocks.py +188 -0
- acp/schema/mcp.py +82 -0
- acp/schema/messages.py +171 -0
- acp/schema/notifications.py +82 -0
- acp/schema/protocol_stuff.md +3 -0
- acp/schema/session_state.py +160 -0
- acp/schema/session_updates.py +419 -0
- acp/schema/slash_commands.py +51 -0
- acp/schema/terminal.py +15 -0
- acp/schema/tool_call.py +347 -0
- acp/stdio.py +250 -0
- acp/task/__init__.py +53 -0
- acp/task/debug.py +197 -0
- acp/task/dispatcher.py +93 -0
- acp/task/queue.py +69 -0
- acp/task/sender.py +82 -0
- acp/task/state.py +87 -0
- acp/task/supervisor.py +93 -0
- acp/terminal_handle.py +30 -0
- acp/tool_call_reporter.py +199 -0
- acp/tool_call_state.py +178 -0
- acp/transports.py +104 -0
- acp/utils.py +240 -0
- agentpool/__init__.py +63 -0
- agentpool/__main__.py +7 -0
- agentpool/agents/__init__.py +30 -0
- agentpool/agents/acp_agent/__init__.py +5 -0
- agentpool/agents/acp_agent/acp_agent.py +837 -0
- agentpool/agents/acp_agent/acp_converters.py +294 -0
- agentpool/agents/acp_agent/client_handler.py +317 -0
- agentpool/agents/acp_agent/session_state.py +44 -0
- agentpool/agents/agent.py +1264 -0
- agentpool/agents/agui_agent/__init__.py +19 -0
- agentpool/agents/agui_agent/agui_agent.py +677 -0
- agentpool/agents/agui_agent/agui_converters.py +423 -0
- agentpool/agents/agui_agent/chunk_transformer.py +204 -0
- agentpool/agents/agui_agent/event_types.py +83 -0
- agentpool/agents/agui_agent/helpers.py +192 -0
- agentpool/agents/architect.py +71 -0
- agentpool/agents/base_agent.py +177 -0
- agentpool/agents/claude_code_agent/__init__.py +11 -0
- agentpool/agents/claude_code_agent/claude_code_agent.py +1021 -0
- agentpool/agents/claude_code_agent/converters.py +243 -0
- agentpool/agents/context.py +105 -0
- agentpool/agents/events/__init__.py +61 -0
- agentpool/agents/events/builtin_handlers.py +129 -0
- agentpool/agents/events/event_emitter.py +320 -0
- agentpool/agents/events/events.py +561 -0
- agentpool/agents/events/tts_handlers.py +186 -0
- agentpool/agents/interactions.py +419 -0
- agentpool/agents/slashed_agent.py +244 -0
- agentpool/agents/sys_prompts.py +178 -0
- agentpool/agents/tool_wrapping.py +184 -0
- agentpool/base_provider.py +28 -0
- agentpool/common_types.py +226 -0
- agentpool/config_resources/__init__.py +16 -0
- agentpool/config_resources/acp_assistant.yml +24 -0
- agentpool/config_resources/agents.yml +109 -0
- agentpool/config_resources/agents_template.yml +18 -0
- agentpool/config_resources/agui_test.yml +18 -0
- agentpool/config_resources/claude_code_agent.yml +16 -0
- agentpool/config_resources/claude_style_subagent.md +30 -0
- agentpool/config_resources/external_acp_agents.yml +77 -0
- agentpool/config_resources/opencode_style_subagent.md +19 -0
- agentpool/config_resources/tts_test_agents.yml +78 -0
- agentpool/delegation/__init__.py +8 -0
- agentpool/delegation/base_team.py +504 -0
- agentpool/delegation/message_flow_tracker.py +39 -0
- agentpool/delegation/pool.py +1129 -0
- agentpool/delegation/team.py +325 -0
- agentpool/delegation/teamrun.py +343 -0
- agentpool/docs/__init__.py +5 -0
- agentpool/docs/gen_examples.py +42 -0
- agentpool/docs/utils.py +370 -0
- agentpool/functional/__init__.py +20 -0
- agentpool/functional/py.typed +0 -0
- agentpool/functional/run.py +80 -0
- agentpool/functional/structure.py +136 -0
- agentpool/hooks/__init__.py +20 -0
- agentpool/hooks/agent_hooks.py +247 -0
- agentpool/hooks/base.py +119 -0
- agentpool/hooks/callable.py +140 -0
- agentpool/hooks/command.py +180 -0
- agentpool/hooks/prompt.py +122 -0
- agentpool/jinja_filters.py +132 -0
- agentpool/log.py +224 -0
- agentpool/mcp_server/__init__.py +17 -0
- agentpool/mcp_server/client.py +429 -0
- agentpool/mcp_server/constants.py +32 -0
- agentpool/mcp_server/conversions.py +172 -0
- agentpool/mcp_server/helpers.py +47 -0
- agentpool/mcp_server/manager.py +232 -0
- agentpool/mcp_server/message_handler.py +164 -0
- agentpool/mcp_server/registries/__init__.py +1 -0
- agentpool/mcp_server/registries/official_registry_client.py +345 -0
- agentpool/mcp_server/registries/pulsemcp_client.py +88 -0
- agentpool/mcp_server/tool_bridge.py +548 -0
- agentpool/messaging/__init__.py +58 -0
- agentpool/messaging/compaction.py +928 -0
- agentpool/messaging/connection_manager.py +319 -0
- agentpool/messaging/context.py +66 -0
- agentpool/messaging/event_manager.py +426 -0
- agentpool/messaging/events.py +39 -0
- agentpool/messaging/message_container.py +209 -0
- agentpool/messaging/message_history.py +491 -0
- agentpool/messaging/messagenode.py +377 -0
- agentpool/messaging/messages.py +655 -0
- agentpool/messaging/processing.py +76 -0
- agentpool/mime_utils.py +95 -0
- agentpool/models/__init__.py +21 -0
- agentpool/models/acp_agents/__init__.py +22 -0
- agentpool/models/acp_agents/base.py +308 -0
- agentpool/models/acp_agents/mcp_capable.py +790 -0
- agentpool/models/acp_agents/non_mcp.py +842 -0
- agentpool/models/agents.py +450 -0
- agentpool/models/agui_agents.py +89 -0
- agentpool/models/claude_code_agents.py +238 -0
- agentpool/models/file_agents.py +116 -0
- agentpool/models/file_parsing.py +367 -0
- agentpool/models/manifest.py +658 -0
- agentpool/observability/__init__.py +9 -0
- agentpool/observability/observability_registry.py +97 -0
- agentpool/prompts/__init__.py +1 -0
- agentpool/prompts/base.py +27 -0
- agentpool/prompts/builtin_provider.py +75 -0
- agentpool/prompts/conversion_manager.py +95 -0
- agentpool/prompts/convert.py +96 -0
- agentpool/prompts/manager.py +204 -0
- agentpool/prompts/parts/zed.md +33 -0
- agentpool/prompts/prompts.py +581 -0
- agentpool/py.typed +0 -0
- agentpool/queries/tree-sitter-language-pack/README.md +7 -0
- agentpool/queries/tree-sitter-language-pack/arduino-tags.scm +5 -0
- agentpool/queries/tree-sitter-language-pack/c-tags.scm +9 -0
- agentpool/queries/tree-sitter-language-pack/chatito-tags.scm +16 -0
- agentpool/queries/tree-sitter-language-pack/clojure-tags.scm +7 -0
- agentpool/queries/tree-sitter-language-pack/commonlisp-tags.scm +122 -0
- agentpool/queries/tree-sitter-language-pack/cpp-tags.scm +15 -0
- agentpool/queries/tree-sitter-language-pack/csharp-tags.scm +26 -0
- agentpool/queries/tree-sitter-language-pack/d-tags.scm +26 -0
- agentpool/queries/tree-sitter-language-pack/dart-tags.scm +92 -0
- agentpool/queries/tree-sitter-language-pack/elisp-tags.scm +5 -0
- agentpool/queries/tree-sitter-language-pack/elixir-tags.scm +54 -0
- agentpool/queries/tree-sitter-language-pack/elm-tags.scm +19 -0
- agentpool/queries/tree-sitter-language-pack/gleam-tags.scm +41 -0
- agentpool/queries/tree-sitter-language-pack/go-tags.scm +42 -0
- agentpool/queries/tree-sitter-language-pack/java-tags.scm +20 -0
- agentpool/queries/tree-sitter-language-pack/javascript-tags.scm +88 -0
- agentpool/queries/tree-sitter-language-pack/lua-tags.scm +34 -0
- agentpool/queries/tree-sitter-language-pack/matlab-tags.scm +10 -0
- agentpool/queries/tree-sitter-language-pack/ocaml-tags.scm +115 -0
- agentpool/queries/tree-sitter-language-pack/ocaml_interface-tags.scm +98 -0
- agentpool/queries/tree-sitter-language-pack/pony-tags.scm +39 -0
- agentpool/queries/tree-sitter-language-pack/properties-tags.scm +5 -0
- agentpool/queries/tree-sitter-language-pack/python-tags.scm +14 -0
- agentpool/queries/tree-sitter-language-pack/r-tags.scm +21 -0
- agentpool/queries/tree-sitter-language-pack/racket-tags.scm +12 -0
- agentpool/queries/tree-sitter-language-pack/ruby-tags.scm +64 -0
- agentpool/queries/tree-sitter-language-pack/rust-tags.scm +60 -0
- agentpool/queries/tree-sitter-language-pack/solidity-tags.scm +43 -0
- agentpool/queries/tree-sitter-language-pack/swift-tags.scm +51 -0
- agentpool/queries/tree-sitter-language-pack/udev-tags.scm +20 -0
- agentpool/queries/tree-sitter-languages/README.md +24 -0
- agentpool/queries/tree-sitter-languages/c-tags.scm +9 -0
- agentpool/queries/tree-sitter-languages/c_sharp-tags.scm +46 -0
- agentpool/queries/tree-sitter-languages/cpp-tags.scm +15 -0
- agentpool/queries/tree-sitter-languages/dart-tags.scm +91 -0
- agentpool/queries/tree-sitter-languages/elisp-tags.scm +8 -0
- agentpool/queries/tree-sitter-languages/elixir-tags.scm +54 -0
- agentpool/queries/tree-sitter-languages/elm-tags.scm +19 -0
- agentpool/queries/tree-sitter-languages/fortran-tags.scm +15 -0
- agentpool/queries/tree-sitter-languages/go-tags.scm +30 -0
- agentpool/queries/tree-sitter-languages/haskell-tags.scm +3 -0
- agentpool/queries/tree-sitter-languages/hcl-tags.scm +77 -0
- agentpool/queries/tree-sitter-languages/java-tags.scm +20 -0
- agentpool/queries/tree-sitter-languages/javascript-tags.scm +88 -0
- agentpool/queries/tree-sitter-languages/julia-tags.scm +60 -0
- agentpool/queries/tree-sitter-languages/kotlin-tags.scm +27 -0
- agentpool/queries/tree-sitter-languages/matlab-tags.scm +10 -0
- agentpool/queries/tree-sitter-languages/ocaml-tags.scm +115 -0
- agentpool/queries/tree-sitter-languages/ocaml_interface-tags.scm +98 -0
- agentpool/queries/tree-sitter-languages/php-tags.scm +26 -0
- agentpool/queries/tree-sitter-languages/python-tags.scm +12 -0
- agentpool/queries/tree-sitter-languages/ql-tags.scm +26 -0
- agentpool/queries/tree-sitter-languages/ruby-tags.scm +64 -0
- agentpool/queries/tree-sitter-languages/rust-tags.scm +60 -0
- agentpool/queries/tree-sitter-languages/scala-tags.scm +65 -0
- agentpool/queries/tree-sitter-languages/typescript-tags.scm +41 -0
- agentpool/queries/tree-sitter-languages/zig-tags.scm +3 -0
- agentpool/repomap.py +1231 -0
- agentpool/resource_providers/__init__.py +17 -0
- agentpool/resource_providers/aggregating.py +54 -0
- agentpool/resource_providers/base.py +172 -0
- agentpool/resource_providers/codemode/__init__.py +9 -0
- agentpool/resource_providers/codemode/code_executor.py +215 -0
- agentpool/resource_providers/codemode/default_prompt.py +19 -0
- agentpool/resource_providers/codemode/helpers.py +83 -0
- agentpool/resource_providers/codemode/progress_executor.py +212 -0
- agentpool/resource_providers/codemode/provider.py +150 -0
- agentpool/resource_providers/codemode/remote_mcp_execution.py +143 -0
- agentpool/resource_providers/codemode/remote_provider.py +171 -0
- agentpool/resource_providers/filtering.py +42 -0
- agentpool/resource_providers/mcp_provider.py +246 -0
- agentpool/resource_providers/plan_provider.py +196 -0
- agentpool/resource_providers/pool.py +69 -0
- agentpool/resource_providers/static.py +289 -0
- agentpool/running/__init__.py +20 -0
- agentpool/running/decorators.py +56 -0
- agentpool/running/discovery.py +101 -0
- agentpool/running/executor.py +284 -0
- agentpool/running/injection.py +111 -0
- agentpool/running/py.typed +0 -0
- agentpool/running/run_nodes.py +87 -0
- agentpool/server.py +122 -0
- agentpool/sessions/__init__.py +13 -0
- agentpool/sessions/manager.py +302 -0
- agentpool/sessions/models.py +71 -0
- agentpool/sessions/session.py +239 -0
- agentpool/sessions/store.py +163 -0
- agentpool/skills/__init__.py +5 -0
- agentpool/skills/manager.py +120 -0
- agentpool/skills/registry.py +210 -0
- agentpool/skills/skill.py +36 -0
- agentpool/storage/__init__.py +17 -0
- agentpool/storage/manager.py +419 -0
- agentpool/storage/serialization.py +136 -0
- agentpool/talk/__init__.py +13 -0
- agentpool/talk/registry.py +128 -0
- agentpool/talk/stats.py +159 -0
- agentpool/talk/talk.py +604 -0
- agentpool/tasks/__init__.py +20 -0
- agentpool/tasks/exceptions.py +25 -0
- agentpool/tasks/registry.py +33 -0
- agentpool/testing.py +129 -0
- agentpool/text_templates/__init__.py +39 -0
- agentpool/text_templates/system_prompt.jinja +30 -0
- agentpool/text_templates/tool_call_default.jinja +13 -0
- agentpool/text_templates/tool_call_markdown.jinja +25 -0
- agentpool/text_templates/tool_call_simple.jinja +5 -0
- agentpool/tools/__init__.py +16 -0
- agentpool/tools/base.py +269 -0
- agentpool/tools/exceptions.py +9 -0
- agentpool/tools/manager.py +255 -0
- agentpool/tools/tool_call_info.py +87 -0
- agentpool/ui/__init__.py +2 -0
- agentpool/ui/base.py +89 -0
- agentpool/ui/mock_provider.py +81 -0
- agentpool/ui/stdlib_provider.py +150 -0
- agentpool/utils/__init__.py +44 -0
- agentpool/utils/baseregistry.py +185 -0
- agentpool/utils/count_tokens.py +62 -0
- agentpool/utils/dag.py +184 -0
- agentpool/utils/importing.py +206 -0
- agentpool/utils/inspection.py +334 -0
- agentpool/utils/model_capabilities.py +25 -0
- agentpool/utils/network.py +28 -0
- agentpool/utils/now.py +22 -0
- agentpool/utils/parse_time.py +87 -0
- agentpool/utils/result_utils.py +35 -0
- agentpool/utils/signatures.py +305 -0
- agentpool/utils/streams.py +112 -0
- agentpool/utils/tasks.py +186 -0
- agentpool/vfs_registry.py +250 -0
- agentpool-2.1.9.dist-info/METADATA +336 -0
- agentpool-2.1.9.dist-info/RECORD +474 -0
- agentpool-2.1.9.dist-info/WHEEL +4 -0
- agentpool-2.1.9.dist-info/entry_points.txt +14 -0
- agentpool-2.1.9.dist-info/licenses/LICENSE +22 -0
- agentpool_cli/__init__.py +34 -0
- agentpool_cli/__main__.py +66 -0
- agentpool_cli/agent.py +175 -0
- agentpool_cli/cli_types.py +23 -0
- agentpool_cli/common.py +163 -0
- agentpool_cli/create.py +175 -0
- agentpool_cli/history.py +217 -0
- agentpool_cli/log.py +78 -0
- agentpool_cli/py.typed +0 -0
- agentpool_cli/run.py +84 -0
- agentpool_cli/serve_acp.py +177 -0
- agentpool_cli/serve_api.py +69 -0
- agentpool_cli/serve_mcp.py +74 -0
- agentpool_cli/serve_vercel.py +233 -0
- agentpool_cli/store.py +171 -0
- agentpool_cli/task.py +84 -0
- agentpool_cli/utils.py +104 -0
- agentpool_cli/watch.py +54 -0
- agentpool_commands/__init__.py +180 -0
- agentpool_commands/agents.py +199 -0
- agentpool_commands/base.py +45 -0
- agentpool_commands/commands.py +58 -0
- agentpool_commands/completers.py +110 -0
- agentpool_commands/connections.py +175 -0
- agentpool_commands/markdown_utils.py +31 -0
- agentpool_commands/models.py +62 -0
- agentpool_commands/prompts.py +78 -0
- agentpool_commands/py.typed +0 -0
- agentpool_commands/read.py +77 -0
- agentpool_commands/resources.py +210 -0
- agentpool_commands/session.py +48 -0
- agentpool_commands/tools.py +269 -0
- agentpool_commands/utils.py +189 -0
- agentpool_commands/workers.py +163 -0
- agentpool_config/__init__.py +53 -0
- agentpool_config/builtin_tools.py +265 -0
- agentpool_config/commands.py +237 -0
- agentpool_config/conditions.py +301 -0
- agentpool_config/converters.py +30 -0
- agentpool_config/durable.py +331 -0
- agentpool_config/event_handlers.py +600 -0
- agentpool_config/events.py +153 -0
- agentpool_config/forward_targets.py +251 -0
- agentpool_config/hook_conditions.py +331 -0
- agentpool_config/hooks.py +241 -0
- agentpool_config/jinja.py +206 -0
- agentpool_config/knowledge.py +41 -0
- agentpool_config/loaders.py +350 -0
- agentpool_config/mcp_server.py +243 -0
- agentpool_config/nodes.py +202 -0
- agentpool_config/observability.py +191 -0
- agentpool_config/output_types.py +55 -0
- agentpool_config/pool_server.py +267 -0
- agentpool_config/prompt_hubs.py +105 -0
- agentpool_config/prompts.py +185 -0
- agentpool_config/py.typed +0 -0
- agentpool_config/resources.py +33 -0
- agentpool_config/session.py +119 -0
- agentpool_config/skills.py +17 -0
- agentpool_config/storage.py +288 -0
- agentpool_config/system_prompts.py +190 -0
- agentpool_config/task.py +162 -0
- agentpool_config/teams.py +52 -0
- agentpool_config/tools.py +112 -0
- agentpool_config/toolsets.py +1033 -0
- agentpool_config/workers.py +86 -0
- agentpool_prompts/__init__.py +1 -0
- agentpool_prompts/braintrust_hub.py +235 -0
- agentpool_prompts/fabric.py +75 -0
- agentpool_prompts/langfuse_hub.py +79 -0
- agentpool_prompts/promptlayer_provider.py +59 -0
- agentpool_prompts/py.typed +0 -0
- agentpool_server/__init__.py +9 -0
- agentpool_server/a2a_server/__init__.py +5 -0
- agentpool_server/a2a_server/a2a_types.py +41 -0
- agentpool_server/a2a_server/server.py +190 -0
- agentpool_server/a2a_server/storage.py +81 -0
- agentpool_server/acp_server/__init__.py +22 -0
- agentpool_server/acp_server/acp_agent.py +786 -0
- agentpool_server/acp_server/acp_tools.py +43 -0
- agentpool_server/acp_server/commands/__init__.py +18 -0
- agentpool_server/acp_server/commands/acp_commands.py +594 -0
- agentpool_server/acp_server/commands/debug_commands.py +376 -0
- agentpool_server/acp_server/commands/docs_commands/__init__.py +39 -0
- agentpool_server/acp_server/commands/docs_commands/fetch_repo.py +169 -0
- agentpool_server/acp_server/commands/docs_commands/get_schema.py +176 -0
- agentpool_server/acp_server/commands/docs_commands/get_source.py +110 -0
- agentpool_server/acp_server/commands/docs_commands/git_diff.py +111 -0
- agentpool_server/acp_server/commands/docs_commands/helpers.py +33 -0
- agentpool_server/acp_server/commands/docs_commands/url_to_markdown.py +90 -0
- agentpool_server/acp_server/commands/spawn.py +210 -0
- agentpool_server/acp_server/converters.py +235 -0
- agentpool_server/acp_server/input_provider.py +338 -0
- agentpool_server/acp_server/server.py +288 -0
- agentpool_server/acp_server/session.py +969 -0
- agentpool_server/acp_server/session_manager.py +313 -0
- agentpool_server/acp_server/syntax_detection.py +250 -0
- agentpool_server/acp_server/zed_tools.md +90 -0
- agentpool_server/aggregating_server.py +309 -0
- agentpool_server/agui_server/__init__.py +11 -0
- agentpool_server/agui_server/server.py +128 -0
- agentpool_server/base.py +189 -0
- agentpool_server/http_server.py +164 -0
- agentpool_server/mcp_server/__init__.py +6 -0
- agentpool_server/mcp_server/server.py +314 -0
- agentpool_server/mcp_server/zed_wrapper.py +110 -0
- agentpool_server/openai_api_server/__init__.py +5 -0
- agentpool_server/openai_api_server/completions/__init__.py +1 -0
- agentpool_server/openai_api_server/completions/helpers.py +81 -0
- agentpool_server/openai_api_server/completions/models.py +98 -0
- agentpool_server/openai_api_server/responses/__init__.py +1 -0
- agentpool_server/openai_api_server/responses/helpers.py +74 -0
- agentpool_server/openai_api_server/responses/models.py +96 -0
- agentpool_server/openai_api_server/server.py +242 -0
- agentpool_server/py.typed +0 -0
- agentpool_storage/__init__.py +9 -0
- agentpool_storage/base.py +310 -0
- agentpool_storage/file_provider.py +378 -0
- agentpool_storage/formatters.py +129 -0
- agentpool_storage/memory_provider.py +396 -0
- agentpool_storage/models.py +108 -0
- agentpool_storage/py.typed +0 -0
- agentpool_storage/session_store.py +262 -0
- agentpool_storage/sql_provider/__init__.py +21 -0
- agentpool_storage/sql_provider/cli.py +146 -0
- agentpool_storage/sql_provider/models.py +249 -0
- agentpool_storage/sql_provider/queries.py +15 -0
- agentpool_storage/sql_provider/sql_provider.py +444 -0
- agentpool_storage/sql_provider/utils.py +234 -0
- agentpool_storage/text_log_provider.py +275 -0
- agentpool_toolsets/__init__.py +15 -0
- agentpool_toolsets/builtin/__init__.py +33 -0
- agentpool_toolsets/builtin/agent_management.py +239 -0
- agentpool_toolsets/builtin/chain.py +288 -0
- agentpool_toolsets/builtin/code.py +398 -0
- agentpool_toolsets/builtin/debug.py +291 -0
- agentpool_toolsets/builtin/execution_environment.py +381 -0
- agentpool_toolsets/builtin/file_edit/__init__.py +11 -0
- agentpool_toolsets/builtin/file_edit/file_edit.py +747 -0
- agentpool_toolsets/builtin/file_edit/fuzzy_matcher/__init__.py +5 -0
- agentpool_toolsets/builtin/file_edit/fuzzy_matcher/example_usage.py +311 -0
- agentpool_toolsets/builtin/file_edit/fuzzy_matcher/streaming_fuzzy_matcher.py +443 -0
- agentpool_toolsets/builtin/history.py +36 -0
- agentpool_toolsets/builtin/integration.py +85 -0
- agentpool_toolsets/builtin/skills.py +77 -0
- agentpool_toolsets/builtin/subagent_tools.py +324 -0
- agentpool_toolsets/builtin/tool_management.py +90 -0
- agentpool_toolsets/builtin/user_interaction.py +52 -0
- agentpool_toolsets/builtin/workers.py +128 -0
- agentpool_toolsets/composio_toolset.py +96 -0
- agentpool_toolsets/config_creation.py +192 -0
- agentpool_toolsets/entry_points.py +47 -0
- agentpool_toolsets/fsspec_toolset/__init__.py +7 -0
- agentpool_toolsets/fsspec_toolset/diagnostics.py +115 -0
- agentpool_toolsets/fsspec_toolset/grep.py +450 -0
- agentpool_toolsets/fsspec_toolset/helpers.py +631 -0
- agentpool_toolsets/fsspec_toolset/streaming_diff_parser.py +249 -0
- agentpool_toolsets/fsspec_toolset/toolset.py +1384 -0
- agentpool_toolsets/mcp_run_toolset.py +61 -0
- agentpool_toolsets/notifications.py +146 -0
- agentpool_toolsets/openapi.py +118 -0
- agentpool_toolsets/py.typed +0 -0
- agentpool_toolsets/search_toolset.py +202 -0
- agentpool_toolsets/semantic_memory_toolset.py +536 -0
- agentpool_toolsets/streaming_tools.py +265 -0
- agentpool_toolsets/vfs_toolset.py +124 -0
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
"""AgentHooks - Runtime hook container for agent lifecycle events."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from typing import TYPE_CHECKING, Any
|
|
8
|
+
|
|
9
|
+
from agentpool.hooks.base import HookResult
|
|
10
|
+
from agentpool.log import get_logger
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from agentpool.hooks.base import Hook, HookInput
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
logger = get_logger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class AgentHooks:
|
|
22
|
+
"""Runtime container for agent lifecycle hooks.
|
|
23
|
+
|
|
24
|
+
Holds instantiated hooks organized by event type and provides
|
|
25
|
+
methods to execute them with proper input/output handling.
|
|
26
|
+
|
|
27
|
+
Attributes:
|
|
28
|
+
pre_run: Hooks executed before agent.run() processes a prompt.
|
|
29
|
+
post_run: Hooks executed after agent.run() completes.
|
|
30
|
+
pre_tool_use: Hooks executed before a tool is called.
|
|
31
|
+
post_tool_use: Hooks executed after a tool completes.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
pre_run: list[Hook] = field(default_factory=list)
|
|
35
|
+
post_run: list[Hook] = field(default_factory=list)
|
|
36
|
+
pre_tool_use: list[Hook] = field(default_factory=list)
|
|
37
|
+
post_tool_use: list[Hook] = field(default_factory=list)
|
|
38
|
+
|
|
39
|
+
def has_hooks(self) -> bool:
|
|
40
|
+
"""Check if any hooks are configured."""
|
|
41
|
+
return bool(self.pre_run or self.post_run or self.pre_tool_use or self.post_tool_use)
|
|
42
|
+
|
|
43
|
+
async def run_pre_run_hooks(
|
|
44
|
+
self,
|
|
45
|
+
*,
|
|
46
|
+
agent_name: str,
|
|
47
|
+
prompt: str,
|
|
48
|
+
conversation_id: str | None = None,
|
|
49
|
+
) -> HookResult:
|
|
50
|
+
"""Execute pre-run hooks.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
agent_name: Name of the agent.
|
|
54
|
+
prompt: The prompt being processed.
|
|
55
|
+
conversation_id: Optional conversation identifier.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
Combined hook result. If any hook denies, the run should be blocked.
|
|
59
|
+
"""
|
|
60
|
+
input_data: HookInput = {
|
|
61
|
+
"event": "pre_run",
|
|
62
|
+
"agent_name": agent_name,
|
|
63
|
+
"prompt": prompt,
|
|
64
|
+
"conversation_id": conversation_id,
|
|
65
|
+
}
|
|
66
|
+
return await self._run_hooks(self.pre_run, input_data)
|
|
67
|
+
|
|
68
|
+
async def run_post_run_hooks(
|
|
69
|
+
self,
|
|
70
|
+
*,
|
|
71
|
+
agent_name: str,
|
|
72
|
+
prompt: str,
|
|
73
|
+
result: Any,
|
|
74
|
+
conversation_id: str | None = None,
|
|
75
|
+
) -> HookResult:
|
|
76
|
+
"""Execute post-run hooks.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
agent_name: Name of the agent.
|
|
80
|
+
prompt: The prompt that was processed.
|
|
81
|
+
result: The result from the run.
|
|
82
|
+
conversation_id: Optional conversation identifier.
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
Combined hook result.
|
|
86
|
+
"""
|
|
87
|
+
input_data: HookInput = {
|
|
88
|
+
"event": "post_run",
|
|
89
|
+
"agent_name": agent_name,
|
|
90
|
+
"prompt": prompt,
|
|
91
|
+
"result": result,
|
|
92
|
+
"conversation_id": conversation_id,
|
|
93
|
+
}
|
|
94
|
+
return await self._run_hooks(self.post_run, input_data)
|
|
95
|
+
|
|
96
|
+
async def run_pre_tool_hooks(
|
|
97
|
+
self,
|
|
98
|
+
*,
|
|
99
|
+
agent_name: str,
|
|
100
|
+
tool_name: str,
|
|
101
|
+
tool_input: dict[str, Any],
|
|
102
|
+
conversation_id: str | None = None,
|
|
103
|
+
) -> HookResult:
|
|
104
|
+
"""Execute pre-tool-use hooks.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
agent_name: Name of the agent.
|
|
108
|
+
tool_name: Name of the tool being called.
|
|
109
|
+
tool_input: Input arguments for the tool.
|
|
110
|
+
conversation_id: Optional conversation identifier.
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
Combined hook result. If any hook denies, the tool call should be blocked.
|
|
114
|
+
May include modified_input to change tool arguments.
|
|
115
|
+
"""
|
|
116
|
+
input_data: HookInput = {
|
|
117
|
+
"event": "pre_tool_use",
|
|
118
|
+
"agent_name": agent_name,
|
|
119
|
+
"tool_name": tool_name,
|
|
120
|
+
"tool_input": tool_input,
|
|
121
|
+
"conversation_id": conversation_id,
|
|
122
|
+
}
|
|
123
|
+
return await self._run_hooks(self.pre_tool_use, input_data)
|
|
124
|
+
|
|
125
|
+
async def run_post_tool_hooks(
|
|
126
|
+
self,
|
|
127
|
+
*,
|
|
128
|
+
agent_name: str,
|
|
129
|
+
tool_name: str,
|
|
130
|
+
tool_input: dict[str, Any],
|
|
131
|
+
tool_output: Any,
|
|
132
|
+
duration_ms: float,
|
|
133
|
+
conversation_id: str | None = None,
|
|
134
|
+
) -> HookResult:
|
|
135
|
+
"""Execute post-tool-use hooks.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
agent_name: Name of the agent.
|
|
139
|
+
tool_name: Name of the tool that was called.
|
|
140
|
+
tool_input: Input arguments that were passed to the tool.
|
|
141
|
+
tool_output: Output from the tool.
|
|
142
|
+
duration_ms: How long the tool took to execute.
|
|
143
|
+
conversation_id: Optional conversation identifier.
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
Combined hook result. May include additional_context to inject.
|
|
147
|
+
"""
|
|
148
|
+
input_data: HookInput = {
|
|
149
|
+
"event": "post_tool_use",
|
|
150
|
+
"agent_name": agent_name,
|
|
151
|
+
"tool_name": tool_name,
|
|
152
|
+
"tool_input": tool_input,
|
|
153
|
+
"tool_output": tool_output,
|
|
154
|
+
"duration_ms": duration_ms,
|
|
155
|
+
"conversation_id": conversation_id,
|
|
156
|
+
}
|
|
157
|
+
return await self._run_hooks(self.post_tool_use, input_data)
|
|
158
|
+
|
|
159
|
+
@staticmethod
|
|
160
|
+
async def _run_hooks(hooks: list[Hook], input_data: HookInput) -> HookResult:
|
|
161
|
+
"""Run a list of hooks and combine their results.
|
|
162
|
+
|
|
163
|
+
Hooks are run in parallel. Results are combined:
|
|
164
|
+
- If any hook returns "deny", the combined result is "deny"
|
|
165
|
+
- If any hook returns "ask", the combined result is "ask" (unless denied)
|
|
166
|
+
- Reasons are concatenated
|
|
167
|
+
- modified_input values are merged (later hooks override earlier)
|
|
168
|
+
- additional_context values are concatenated
|
|
169
|
+
- continue_ is False if any hook sets it False
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
hooks: List of hooks to execute.
|
|
173
|
+
input_data: Input data for the hooks.
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
Combined hook result.
|
|
177
|
+
"""
|
|
178
|
+
if not hooks:
|
|
179
|
+
return HookResult(decision="allow")
|
|
180
|
+
|
|
181
|
+
# Filter to matching hooks
|
|
182
|
+
matching = [h for h in hooks if h.matches(input_data)]
|
|
183
|
+
if not matching:
|
|
184
|
+
return HookResult(decision="allow")
|
|
185
|
+
|
|
186
|
+
# Run all matching hooks in parallel
|
|
187
|
+
raw_results = await asyncio.gather(
|
|
188
|
+
*(hook.execute(input_data) for hook in matching),
|
|
189
|
+
return_exceptions=True,
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
# Combine results
|
|
193
|
+
combined: HookResult = {"decision": "allow"}
|
|
194
|
+
reasons: list[str] = []
|
|
195
|
+
contexts: list[str] = []
|
|
196
|
+
|
|
197
|
+
for raw_result in raw_results:
|
|
198
|
+
if isinstance(raw_result, BaseException):
|
|
199
|
+
logger.warning("Hook execution failed", error=str(raw_result))
|
|
200
|
+
continue
|
|
201
|
+
|
|
202
|
+
result: HookResult = raw_result
|
|
203
|
+
|
|
204
|
+
# Decision priority: deny > ask > allow
|
|
205
|
+
if result.get("decision") == "deny":
|
|
206
|
+
combined["decision"] = "deny"
|
|
207
|
+
elif result.get("decision") == "ask" and combined.get("decision") != "deny":
|
|
208
|
+
combined["decision"] = "ask"
|
|
209
|
+
|
|
210
|
+
# Collect reasons
|
|
211
|
+
if reason := result.get("reason"):
|
|
212
|
+
reasons.append(reason)
|
|
213
|
+
|
|
214
|
+
# Merge modified_input (later overrides earlier)
|
|
215
|
+
if modified := result.get("modified_input"):
|
|
216
|
+
if "modified_input" not in combined:
|
|
217
|
+
combined["modified_input"] = {}
|
|
218
|
+
combined["modified_input"].update(modified)
|
|
219
|
+
|
|
220
|
+
# Collect additional context
|
|
221
|
+
if ctx := result.get("additional_context"):
|
|
222
|
+
contexts.append(ctx)
|
|
223
|
+
|
|
224
|
+
# continue_ is False if any hook sets it False
|
|
225
|
+
if result.get("continue_") is False:
|
|
226
|
+
combined["continue_"] = False
|
|
227
|
+
|
|
228
|
+
# Combine collected values
|
|
229
|
+
if reasons:
|
|
230
|
+
combined["reason"] = "; ".join(reasons)
|
|
231
|
+
if contexts:
|
|
232
|
+
combined["additional_context"] = "\n".join(contexts)
|
|
233
|
+
|
|
234
|
+
return combined
|
|
235
|
+
|
|
236
|
+
def __repr__(self) -> str:
|
|
237
|
+
counts = {
|
|
238
|
+
"pre_run": len(self.pre_run),
|
|
239
|
+
"post_run": len(self.post_run),
|
|
240
|
+
"pre_tool_use": len(self.pre_tool_use),
|
|
241
|
+
"post_tool_use": len(self.post_tool_use),
|
|
242
|
+
}
|
|
243
|
+
non_empty = {k: v for k, v in counts.items() if v > 0}
|
|
244
|
+
if not non_empty:
|
|
245
|
+
return "AgentHooks(empty)"
|
|
246
|
+
parts = ", ".join(f"{k}={v}" for k, v in non_empty.items())
|
|
247
|
+
return f"AgentHooks({parts})"
|
agentpool/hooks/base.py
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""Base hook classes and types."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from abc import ABC, abstractmethod
|
|
6
|
+
import re
|
|
7
|
+
from typing import Any, Literal, TypedDict
|
|
8
|
+
|
|
9
|
+
from agentpool.log import get_logger
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
logger = get_logger(__name__)
|
|
13
|
+
|
|
14
|
+
HookEvent = Literal["pre_run", "post_run", "pre_tool_use", "post_tool_use"]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class HookInput(TypedDict, total=False):
|
|
18
|
+
"""Input data passed to hooks."""
|
|
19
|
+
|
|
20
|
+
# Common fields
|
|
21
|
+
event: HookEvent
|
|
22
|
+
agent_name: str
|
|
23
|
+
conversation_id: str | None
|
|
24
|
+
|
|
25
|
+
# Tool-related fields (pre_tool_use, post_tool_use)
|
|
26
|
+
tool_name: str
|
|
27
|
+
tool_input: dict[str, Any]
|
|
28
|
+
tool_output: Any
|
|
29
|
+
duration_ms: float
|
|
30
|
+
|
|
31
|
+
# Run-related fields (pre_run, post_run)
|
|
32
|
+
prompt: str
|
|
33
|
+
result: Any
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class HookResult(TypedDict, total=False):
|
|
37
|
+
"""Result returned from hook execution."""
|
|
38
|
+
|
|
39
|
+
decision: Literal["allow", "deny", "ask"]
|
|
40
|
+
"""Decision for pre_* hooks: allow, deny, or ask user."""
|
|
41
|
+
|
|
42
|
+
reason: str
|
|
43
|
+
"""Explanation for the decision."""
|
|
44
|
+
|
|
45
|
+
modified_input: dict[str, Any]
|
|
46
|
+
"""Modified input for pre_* hooks (e.g., modified tool_input)."""
|
|
47
|
+
|
|
48
|
+
additional_context: str
|
|
49
|
+
"""Context to inject into conversation."""
|
|
50
|
+
|
|
51
|
+
continue_: bool
|
|
52
|
+
"""Whether to continue execution. False = stop."""
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class Hook(ABC):
|
|
56
|
+
"""Base class for runtime hooks."""
|
|
57
|
+
|
|
58
|
+
def __init__(
|
|
59
|
+
self,
|
|
60
|
+
event: HookEvent,
|
|
61
|
+
matcher: str | None = None,
|
|
62
|
+
timeout: float = 60.0,
|
|
63
|
+
enabled: bool = True,
|
|
64
|
+
) -> None:
|
|
65
|
+
"""Initialize hook.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
event: The lifecycle event this hook handles.
|
|
69
|
+
matcher: Regex pattern for matching (e.g., tool names). None matches all.
|
|
70
|
+
timeout: Maximum execution time in seconds.
|
|
71
|
+
enabled: Whether this hook is active.
|
|
72
|
+
"""
|
|
73
|
+
self.event = event
|
|
74
|
+
self.matcher = matcher
|
|
75
|
+
self.timeout = timeout
|
|
76
|
+
self.enabled = enabled
|
|
77
|
+
self._pattern = re.compile(matcher) if matcher and matcher != "*" else None
|
|
78
|
+
|
|
79
|
+
def matches(self, input_data: HookInput) -> bool:
|
|
80
|
+
"""Check if this hook should run for the given input.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
input_data: The hook input data.
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
True if the hook should execute.
|
|
87
|
+
"""
|
|
88
|
+
if not self.enabled:
|
|
89
|
+
return False
|
|
90
|
+
|
|
91
|
+
# No pattern means match all
|
|
92
|
+
if self._pattern is None:
|
|
93
|
+
return True
|
|
94
|
+
|
|
95
|
+
# For tool events, match against tool_name
|
|
96
|
+
if self.event in ("pre_tool_use", "post_tool_use"):
|
|
97
|
+
tool_name = input_data.get("tool_name", "")
|
|
98
|
+
return bool(self._pattern.search(tool_name))
|
|
99
|
+
|
|
100
|
+
# For run events, pattern matching doesn't apply (no tool name to match)
|
|
101
|
+
return True
|
|
102
|
+
|
|
103
|
+
@abstractmethod
|
|
104
|
+
async def execute(self, input_data: HookInput) -> HookResult:
|
|
105
|
+
"""Execute the hook.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
input_data: The hook input data.
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
Hook result with decision and optional modifications.
|
|
112
|
+
"""
|
|
113
|
+
...
|
|
114
|
+
|
|
115
|
+
def __repr__(self) -> str:
|
|
116
|
+
return (
|
|
117
|
+
f"{self.__class__.__name__}("
|
|
118
|
+
f"event={self.event!r}, matcher={self.matcher!r}, enabled={self.enabled})"
|
|
119
|
+
)
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""Callable hook implementation."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
from typing import TYPE_CHECKING, Any
|
|
7
|
+
|
|
8
|
+
from agentpool.hooks.base import Hook, HookResult
|
|
9
|
+
from agentpool.log import get_logger
|
|
10
|
+
from agentpool.utils.importing import import_callable
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from collections.abc import Callable
|
|
15
|
+
|
|
16
|
+
from agentpool.hooks.base import HookEvent, HookInput
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
logger = get_logger(__name__)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class CallableHook(Hook):
|
|
23
|
+
"""Hook that executes a Python callable.
|
|
24
|
+
|
|
25
|
+
The callable receives hook input as a dictionary and should return
|
|
26
|
+
a HookResult dictionary or None.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
event: HookEvent,
|
|
32
|
+
fn: Callable[..., HookResult | None] | str,
|
|
33
|
+
matcher: str | None = None,
|
|
34
|
+
timeout: float = 60.0,
|
|
35
|
+
enabled: bool = True,
|
|
36
|
+
arguments: dict[str, Any] | None = None,
|
|
37
|
+
):
|
|
38
|
+
"""Initialize callable hook.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
event: The lifecycle event this hook handles.
|
|
42
|
+
fn: The callable to execute, or import path string.
|
|
43
|
+
matcher: Regex pattern for matching.
|
|
44
|
+
timeout: Maximum execution time in seconds.
|
|
45
|
+
enabled: Whether this hook is active.
|
|
46
|
+
arguments: Additional keyword arguments for the callable.
|
|
47
|
+
"""
|
|
48
|
+
super().__init__(event=event, matcher=matcher, timeout=timeout, enabled=enabled)
|
|
49
|
+
self._callable: Callable[..., HookResult | None] | None = None
|
|
50
|
+
self._import_path: str | None = None
|
|
51
|
+
|
|
52
|
+
if isinstance(fn, str):
|
|
53
|
+
self._import_path = fn
|
|
54
|
+
else:
|
|
55
|
+
self._callable = fn
|
|
56
|
+
|
|
57
|
+
self.arguments = arguments or {}
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def callable(self) -> Callable[..., HookResult | None]:
|
|
61
|
+
"""Get the callable, importing lazily if needed."""
|
|
62
|
+
if self._callable is None:
|
|
63
|
+
if self._import_path is None:
|
|
64
|
+
msg = "No callable or import path provided"
|
|
65
|
+
raise ValueError(msg)
|
|
66
|
+
self._callable = import_callable(self._import_path)
|
|
67
|
+
return self._callable
|
|
68
|
+
|
|
69
|
+
async def execute(self, input_data: HookInput) -> HookResult:
|
|
70
|
+
"""Execute the callable.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
input_data: The hook input data.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
Hook result from callable.
|
|
77
|
+
"""
|
|
78
|
+
try:
|
|
79
|
+
fn = self.callable
|
|
80
|
+
# Merge input data with additional arguments
|
|
81
|
+
kwargs = {**dict(input_data), **self.arguments}
|
|
82
|
+
# Execute with timeout
|
|
83
|
+
if asyncio.iscoroutinefunction(fn):
|
|
84
|
+
result = await asyncio.wait_for(fn(**kwargs), timeout=self.timeout) # ty: ignore
|
|
85
|
+
else:
|
|
86
|
+
# Run sync function in executor
|
|
87
|
+
loop = asyncio.get_event_loop()
|
|
88
|
+
result = await asyncio.wait_for(
|
|
89
|
+
loop.run_in_executor(None, lambda: fn(**kwargs)),
|
|
90
|
+
timeout=self.timeout,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
# Normalize result
|
|
94
|
+
if result is None:
|
|
95
|
+
return HookResult(decision="allow")
|
|
96
|
+
|
|
97
|
+
return _normalize_result(result)
|
|
98
|
+
|
|
99
|
+
except TimeoutError:
|
|
100
|
+
fn_path = self._import_path or str(self._callable)
|
|
101
|
+
logger.exception("Hook callable timed out", timeout=self.timeout, callable=fn_path)
|
|
102
|
+
return HookResult(decision="allow")
|
|
103
|
+
except Exception as e:
|
|
104
|
+
fn_path = self._import_path or str(self._callable)
|
|
105
|
+
logger.exception("Hook callable failed", callable=fn_path)
|
|
106
|
+
return HookResult(decision="allow", reason=str(e))
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _normalize_result(result: Any) -> HookResult:
|
|
110
|
+
"""Normalize callable result to HookResult.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
result: Result from callable.
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
Normalized hook result.
|
|
117
|
+
"""
|
|
118
|
+
if isinstance(result, dict):
|
|
119
|
+
# Already a dict, ensure proper typing
|
|
120
|
+
normalized: HookResult = {}
|
|
121
|
+
if "decision" in result:
|
|
122
|
+
normalized["decision"] = result["decision"]
|
|
123
|
+
if "reason" in result:
|
|
124
|
+
normalized["reason"] = result["reason"]
|
|
125
|
+
if "modified_input" in result:
|
|
126
|
+
normalized["modified_input"] = result["modified_input"]
|
|
127
|
+
if "additional_context" in result:
|
|
128
|
+
normalized["additional_context"] = result["additional_context"]
|
|
129
|
+
if "continue_" in result:
|
|
130
|
+
normalized["continue_"] = result["continue_"]
|
|
131
|
+
return normalized
|
|
132
|
+
|
|
133
|
+
# String result treated as additional context
|
|
134
|
+
if isinstance(result, str):
|
|
135
|
+
return HookResult(decision="allow", additional_context=result)
|
|
136
|
+
# Bool result treated as allow/deny
|
|
137
|
+
if isinstance(result, bool):
|
|
138
|
+
return HookResult(decision="allow" if result else "deny")
|
|
139
|
+
# Unknown type, allow by default
|
|
140
|
+
return HookResult(decision="allow")
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"""Command hook implementation."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import TYPE_CHECKING, Any
|
|
10
|
+
|
|
11
|
+
from agentpool.hooks.base import Hook, HookResult
|
|
12
|
+
from agentpool.log import get_logger
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from agentpool.hooks.base import HookEvent, HookInput
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
logger = get_logger(__name__)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class CommandHook(Hook):
|
|
23
|
+
"""Hook that executes a shell command.
|
|
24
|
+
|
|
25
|
+
The command receives hook input as JSON via stdin and should return
|
|
26
|
+
JSON output via stdout.
|
|
27
|
+
|
|
28
|
+
Exit codes:
|
|
29
|
+
- 0: Success, stdout parsed as JSON for result
|
|
30
|
+
- 2: Block/deny, stderr used as reason
|
|
31
|
+
- Other: Non-blocking error, logged but execution continues
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(
|
|
35
|
+
self,
|
|
36
|
+
event: HookEvent,
|
|
37
|
+
command: str,
|
|
38
|
+
matcher: str | None = None,
|
|
39
|
+
timeout: float = 60.0,
|
|
40
|
+
enabled: bool = True,
|
|
41
|
+
env: dict[str, str] | None = None,
|
|
42
|
+
):
|
|
43
|
+
"""Initialize command hook.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
event: The lifecycle event this hook handles.
|
|
47
|
+
command: Shell command to execute.
|
|
48
|
+
matcher: Regex pattern for matching.
|
|
49
|
+
timeout: Maximum execution time in seconds.
|
|
50
|
+
enabled: Whether this hook is active.
|
|
51
|
+
env: Additional environment variables.
|
|
52
|
+
"""
|
|
53
|
+
super().__init__(event=event, matcher=matcher, timeout=timeout, enabled=enabled)
|
|
54
|
+
self.command = command
|
|
55
|
+
self.env = env or {}
|
|
56
|
+
|
|
57
|
+
async def execute(self, input_data: HookInput) -> HookResult:
|
|
58
|
+
"""Execute the shell command.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
input_data: The hook input data, passed as JSON to stdin.
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
Hook result parsed from command output.
|
|
65
|
+
"""
|
|
66
|
+
# Prepare environment
|
|
67
|
+
env = os.environ.copy()
|
|
68
|
+
env.update(self.env)
|
|
69
|
+
|
|
70
|
+
# Expand $PROJECT_DIR if present
|
|
71
|
+
command = self.command
|
|
72
|
+
if "$PROJECT_DIR" in command:
|
|
73
|
+
project_dir = env.get("PROJECT_DIR", Path.cwd())
|
|
74
|
+
command = command.replace("$PROJECT_DIR", str(project_dir))
|
|
75
|
+
|
|
76
|
+
# Serialize input
|
|
77
|
+
input_json = json.dumps(dict(input_data))
|
|
78
|
+
|
|
79
|
+
try:
|
|
80
|
+
proc = await asyncio.create_subprocess_shell(
|
|
81
|
+
command,
|
|
82
|
+
stdin=asyncio.subprocess.PIPE,
|
|
83
|
+
stdout=asyncio.subprocess.PIPE,
|
|
84
|
+
stderr=asyncio.subprocess.PIPE,
|
|
85
|
+
env=env,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
stdout, stderr = await asyncio.wait_for(
|
|
89
|
+
proc.communicate(input_json.encode()),
|
|
90
|
+
timeout=self.timeout,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
stdout_str = stdout.decode().strip()
|
|
94
|
+
stderr_str = stderr.decode().strip()
|
|
95
|
+
|
|
96
|
+
# Handle exit codes
|
|
97
|
+
if proc.returncode == 0:
|
|
98
|
+
return _parse_success_output(stdout_str)
|
|
99
|
+
if proc.returncode == 2: # noqa: PLR2004
|
|
100
|
+
# Blocking error
|
|
101
|
+
reason = stderr_str or "Hook denied the operation"
|
|
102
|
+
return HookResult(decision="deny", reason=reason)
|
|
103
|
+
# Non-blocking error
|
|
104
|
+
logger.warning("Hook command failed", returncode=proc.returncode, stderr=stderr_str)
|
|
105
|
+
return HookResult(decision="allow")
|
|
106
|
+
|
|
107
|
+
except TimeoutError:
|
|
108
|
+
logger.exception("Hook command timed out", timeout=self.timeout, command=command)
|
|
109
|
+
return HookResult(decision="allow")
|
|
110
|
+
except Exception as e:
|
|
111
|
+
logger.exception("Hook command failed", command=command)
|
|
112
|
+
return HookResult(decision="allow", reason=str(e))
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _parse_success_output(stdout: str) -> HookResult:
|
|
116
|
+
"""Parse successful command output.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
stdout: Command stdout.
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
Parsed hook result.
|
|
123
|
+
"""
|
|
124
|
+
if not stdout:
|
|
125
|
+
return HookResult(decision="allow")
|
|
126
|
+
|
|
127
|
+
try:
|
|
128
|
+
data = json.loads(stdout)
|
|
129
|
+
return _normalize_result(data)
|
|
130
|
+
except json.JSONDecodeError:
|
|
131
|
+
# Plain text output treated as additional context
|
|
132
|
+
return HookResult(decision="allow", additional_context=stdout)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _normalize_result(data: dict[str, Any]) -> HookResult:
|
|
136
|
+
"""Normalize command output to HookResult.
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
data: Parsed JSON data.
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
Normalized hook result.
|
|
143
|
+
"""
|
|
144
|
+
result: HookResult = {}
|
|
145
|
+
|
|
146
|
+
# Handle decision field (support various naming conventions)
|
|
147
|
+
decision = data.get("decision") or data.get("permissionDecision")
|
|
148
|
+
if decision:
|
|
149
|
+
# Normalize decision values
|
|
150
|
+
if decision in ("approve", "allow"):
|
|
151
|
+
result["decision"] = "allow"
|
|
152
|
+
elif decision in ("block", "deny"):
|
|
153
|
+
result["decision"] = "deny"
|
|
154
|
+
elif decision == "ask":
|
|
155
|
+
result["decision"] = "ask"
|
|
156
|
+
|
|
157
|
+
# Handle reason field
|
|
158
|
+
reason = data.get("reason") or data.get("permissionDecisionReason")
|
|
159
|
+
if reason:
|
|
160
|
+
result["reason"] = reason
|
|
161
|
+
|
|
162
|
+
# Handle modified input
|
|
163
|
+
if "modified_input" in data:
|
|
164
|
+
result["modified_input"] = data["modified_input"]
|
|
165
|
+
elif "updatedInput" in data:
|
|
166
|
+
result["modified_input"] = data["updatedInput"]
|
|
167
|
+
|
|
168
|
+
# Handle additional context
|
|
169
|
+
if "additional_context" in data:
|
|
170
|
+
result["additional_context"] = data["additional_context"]
|
|
171
|
+
elif "additionalContext" in data:
|
|
172
|
+
result["additional_context"] = data["additionalContext"]
|
|
173
|
+
|
|
174
|
+
# Handle continue flag
|
|
175
|
+
if "continue" in data:
|
|
176
|
+
result["continue_"] = data["continue"]
|
|
177
|
+
elif "continue_" in data:
|
|
178
|
+
result["continue_"] = data["continue_"]
|
|
179
|
+
|
|
180
|
+
return result
|