langchain-agentx-python 0.1__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.
- langchain_agentx/__init__.py +46 -0
- langchain_agentx/command/__init__.py +28 -0
- langchain_agentx/command/builtin/__init__.py +25 -0
- langchain_agentx/command/builtin/clear.py +33 -0
- langchain_agentx/command/builtin/compact.py +33 -0
- langchain_agentx/command/builtin/memory.py +37 -0
- langchain_agentx/command/builtin/reload_plugins.py +42 -0
- langchain_agentx/command/context.py +30 -0
- langchain_agentx/command/dispatcher.py +183 -0
- langchain_agentx/command/registry.py +110 -0
- langchain_agentx/command/result.py +25 -0
- langchain_agentx/command/types.py +41 -0
- langchain_agentx/config/__init__.py +14 -0
- langchain_agentx/loop/__init__.py +47 -0
- langchain_agentx/loop/config/__init__.py +20 -0
- langchain_agentx/loop/config/agent_config.py +66 -0
- langchain_agentx/loop/config/agent_loop_config.py +72 -0
- langchain_agentx/loop/config/model_context_resolver.py +105 -0
- langchain_agentx/loop/config/runtime_settings.py +50 -0
- langchain_agentx/loop/config/token_estimator.py +133 -0
- langchain_agentx/loop/context/__init__.py +66 -0
- langchain_agentx/loop/context/blocking_guard.py +97 -0
- langchain_agentx/loop/context/compaction_service.py +60 -0
- langchain_agentx/loop/context/message_utils.py +56 -0
- langchain_agentx/loop/context/pipeline.py +127 -0
- langchain_agentx/loop/context/settings.py +103 -0
- langchain_agentx/loop/context/stages/__init__.py +29 -0
- langchain_agentx/loop/context/stages/autocompact.py +140 -0
- langchain_agentx/loop/context/stages/base.py +32 -0
- langchain_agentx/loop/context/stages/collapse.py +76 -0
- langchain_agentx/loop/context/stages/microcompact.py +76 -0
- langchain_agentx/loop/context/stages/noop.py +33 -0
- langchain_agentx/loop/context/stages/snip.py +71 -0
- langchain_agentx/loop/context/stages/tool_result_budget.py +69 -0
- langchain_agentx/loop/context/types.py +79 -0
- langchain_agentx/loop/exit/__init__.py +1 -0
- langchain_agentx/loop/exit/exit_logic.py +320 -0
- langchain_agentx/loop/exit/reason_codes.py +39 -0
- langchain_agentx/loop/graph/__init__.py +5 -0
- langchain_agentx/loop/graph/builtin_loop_control.py +197 -0
- langchain_agentx/loop/graph/factory.py +1409 -0
- langchain_agentx/loop/graph/graph_edges.py +820 -0
- langchain_agentx/loop/hook/__init__.py +48 -0
- langchain_agentx/loop/hook/async_hook_runner.py +62 -0
- langchain_agentx/loop/hook/config.py +280 -0
- langchain_agentx/loop/hook/engine.py +321 -0
- langchain_agentx/loop/hook/executors/__init__.py +9 -0
- langchain_agentx/loop/hook/executors/agent.py +107 -0
- langchain_agentx/loop/hook/executors/command.py +230 -0
- langchain_agentx/loop/hook/executors/http.py +114 -0
- langchain_agentx/loop/hook/executors/prompt.py +92 -0
- langchain_agentx/loop/hook/graph_wiring.py +134 -0
- langchain_agentx/loop/hook/registry.py +262 -0
- langchain_agentx/loop/hook/trust.py +43 -0
- langchain_agentx/loop/hook/types.py +110 -0
- langchain_agentx/loop/injection/__init__.py +13 -0
- langchain_agentx/loop/injection/dedup.py +74 -0
- langchain_agentx/loop/loop_abort.py +36 -0
- langchain_agentx/loop/model/__init__.py +1 -0
- langchain_agentx/loop/model/model_node.py +648 -0
- langchain_agentx/loop/model/model_nodes.py +661 -0
- langchain_agentx/loop/model/orphan_tool_results.py +38 -0
- langchain_agentx/loop/model/retrier.py +307 -0
- langchain_agentx/loop/model/retry_bridge.py +58 -0
- langchain_agentx/loop/model/retry_events.py +35 -0
- langchain_agentx/loop/model/retry_policy.py +56 -0
- langchain_agentx/loop/model/schema_and_format.py +153 -0
- langchain_agentx/loop/model/tool_and_model_binding.py +227 -0
- langchain_agentx/loop/model/tool_call_degradation_corrector.py +443 -0
- langchain_agentx/loop/model/tool_transcript_guard.py +225 -0
- langchain_agentx/loop/prompt/__init__.py +95 -0
- langchain_agentx/loop/prompt/builder.py +61 -0
- langchain_agentx/loop/prompt/builtin.py +218 -0
- langchain_agentx/loop/prompt/compact.py +408 -0
- langchain_agentx/loop/prompt/sections.py +120 -0
- langchain_agentx/loop/runtime/__init__.py +19 -0
- langchain_agentx/loop/runtime/context.py +34 -0
- langchain_agentx/loop/runtime/context_factory.py +107 -0
- langchain_agentx/loop/runtime/subagent_execution_paths.py +68 -0
- langchain_agentx/loop/subagent/__init__.py +53 -0
- langchain_agentx/loop/subagent/async_runner.py +215 -0
- langchain_agentx/loop/subagent/context.py +209 -0
- langchain_agentx/loop/subagent/fork_worktree_notice.py +25 -0
- langchain_agentx/loop/subagent/graph.py +72 -0
- langchain_agentx/loop/subagent/orchestrator.py +391 -0
- langchain_agentx/loop/subagent/progress.py +30 -0
- langchain_agentx/loop/subagent/prompt.py +52 -0
- langchain_agentx/loop/subagent/runner.py +504 -0
- langchain_agentx/loop/subagent/transcript.py +172 -0
- langchain_agentx/memory/__init__.py +2 -0
- langchain_agentx/memory/instruction/__init__.py +12 -0
- langchain_agentx/memory/instruction/loader.py +325 -0
- langchain_agentx/memory/instruction/resolver.py +24 -0
- langchain_agentx/memory/instruction/runtime.py +83 -0
- langchain_agentx/memory/instruction/sections.py +83 -0
- langchain_agentx/memory/instruction/types.py +59 -0
- langchain_agentx/memory/memdir/__init__.py +77 -0
- langchain_agentx/memory/memdir/age.py +36 -0
- langchain_agentx/memory/memdir/agent_memory.py +380 -0
- langchain_agentx/memory/memdir/extractor.py +309 -0
- langchain_agentx/memory/memdir/loader.py +187 -0
- langchain_agentx/memory/memdir/paths.py +63 -0
- langchain_agentx/memory/memdir/recall.py +45 -0
- langchain_agentx/memory/memdir/runtime.py +43 -0
- langchain_agentx/memory/memdir/scan.py +135 -0
- langchain_agentx/memory/memdir/types.py +104 -0
- langchain_agentx/memory/session/__init__.py +76 -0
- langchain_agentx/memory/session/compact_bridge.py +208 -0
- langchain_agentx/memory/session/prompts.py +172 -0
- langchain_agentx/memory/session/session_memory.py +282 -0
- langchain_agentx/observability/__init__.py +67 -0
- langchain_agentx/observability/evaluation/__init__.py +17 -0
- langchain_agentx/observability/evaluation/checkers/__init__.py +18 -0
- langchain_agentx/observability/evaluation/checkers/base.py +34 -0
- langchain_agentx/observability/evaluation/checkers/compaction.py +38 -0
- langchain_agentx/observability/evaluation/checkers/degradation.py +50 -0
- langchain_agentx/observability/evaluation/checkers/exit_quality.py +42 -0
- langchain_agentx/observability/evaluation/checkers/session_memory.py +45 -0
- langchain_agentx/observability/evaluation/checkers/tool_behavior.py +53 -0
- langchain_agentx/observability/evaluation/retention_scheduler.py +67 -0
- langchain_agentx/observability/evaluation/service.py +102 -0
- langchain_agentx/observability/evaluation/state.py +32 -0
- langchain_agentx/observability/evaluation/store.py +258 -0
- langchain_agentx/observability/events/__init__.py +15 -0
- langchain_agentx/observability/events/langchain_agentx_event_adapter.py +832 -0
- langchain_agentx/observability/logging/__init__.py +15 -0
- langchain_agentx/observability/logging/debug_burst.py +95 -0
- langchain_agentx/observability/logging/logging_config.py +178 -0
- langchain_agentx/observability/logging/logging_contract.py +65 -0
- langchain_agentx/observability/replay/__init__.py +35 -0
- langchain_agentx/observability/replay/cli.py +91 -0
- langchain_agentx/observability/replay/service.py +83 -0
- langchain_agentx/observability/replay/store.py +278 -0
- langchain_agentx/observability/replay/ui.py +47 -0
- langchain_agentx/observability/trace/__init__.py +25 -0
- langchain_agentx/observability/trace/collector.py +560 -0
- langchain_agentx/observability/trace/event_emitter.py +183 -0
- langchain_agentx/observability/trace/hook_event_emitter.py +49 -0
- langchain_agentx/observability/trace/models.py +144 -0
- langchain_agentx/observability/trace/sqlite_store.py +873 -0
- langchain_agentx/observability/trace/trace_callback.py +295 -0
- langchain_agentx/observability/trace/trace_lifecycle_collector.py +114 -0
- langchain_agentx/plugin/__init__.py +26 -0
- langchain_agentx/plugin/builtin.py +53 -0
- langchain_agentx/plugin/config.py +113 -0
- langchain_agentx/plugin/loader.py +386 -0
- langchain_agentx/plugin/manifest.py +154 -0
- langchain_agentx/plugin/registries.py +211 -0
- langchain_agentx/plugin/types.py +142 -0
- langchain_agentx/provider/__init__.py +27 -0
- langchain_agentx/provider/anthropic.py +121 -0
- langchain_agentx/provider/compatible_chat_openai.py +86 -0
- langchain_agentx/provider/env.py +45 -0
- langchain_agentx/provider/model_profile.py +156 -0
- langchain_agentx/provider/openai.py +89 -0
- langchain_agentx/session/__init__.py +17 -0
- langchain_agentx/session/agent_session.py +320 -0
- langchain_agentx/session/conversation_factory.py +87 -0
- langchain_agentx/session/conversation_recovery.py +156 -0
- langchain_agentx/session/conversation_session.py +198 -0
- langchain_agentx/session/factory.py +143 -0
- langchain_agentx/session/protocol.py +25 -0
- langchain_agentx/task_runtime/__init__.py +113 -0
- langchain_agentx/task_runtime/core/__init__.py +51 -0
- langchain_agentx/task_runtime/core/ids.py +33 -0
- langchain_agentx/task_runtime/core/interfaces.py +115 -0
- langchain_agentx/task_runtime/core/notification_priority.py +19 -0
- langchain_agentx/task_runtime/core/types.py +136 -0
- langchain_agentx/task_runtime/integrations/__init__.py +33 -0
- langchain_agentx/task_runtime/integrations/loop_adapter.py +91 -0
- langchain_agentx/task_runtime/integrations/loop_integration.py +61 -0
- langchain_agentx/task_runtime/integrations/prefetch_providers.py +108 -0
- langchain_agentx/task_runtime/integrations/provider_factory.py +103 -0
- langchain_agentx/task_runtime/integrations/queued_command_provider.py +184 -0
- langchain_agentx/task_runtime/integrations/sqlite_queued_command_provider.py +338 -0
- langchain_agentx/task_runtime/integrations/tool_use_summary_provider.py +254 -0
- langchain_agentx/task_runtime/orchestrator/__init__.py +5 -0
- langchain_agentx/task_runtime/orchestrator/runtime.py +386 -0
- langchain_agentx/task_runtime/output/__init__.py +5 -0
- langchain_agentx/task_runtime/output/sink.py +64 -0
- langchain_agentx/task_runtime/policy/__init__.py +11 -0
- langchain_agentx/task_runtime/policy/withhold_visibility.py +32 -0
- langchain_agentx/task_runtime/queue/__init__.py +5 -0
- langchain_agentx/task_runtime/queue/in_memory.py +55 -0
- langchain_agentx/task_runtime/skill_prefetch/__init__.py +4 -0
- langchain_agentx/task_runtime/skill_prefetch/attachments.py +46 -0
- langchain_agentx/task_runtime/skill_prefetch/models.py +37 -0
- langchain_agentx/task_runtime/skill_prefetch/provider.py +344 -0
- langchain_agentx/task_runtime/store/__init__.py +6 -0
- langchain_agentx/task_runtime/store/in_memory.py +81 -0
- langchain_agentx/task_runtime/store/sqlite_store.py +281 -0
- langchain_agentx/task_runtime/tasks/__init__.py +76 -0
- langchain_agentx/task_runtime/tasks/ai_analysis/__init__.py +15 -0
- langchain_agentx/task_runtime/tasks/ai_analysis/base.py +41 -0
- langchain_agentx/task_runtime/tasks/ai_analysis/evaluation.py +67 -0
- langchain_agentx/task_runtime/tasks/ai_analysis/registry.py +36 -0
- langchain_agentx/task_runtime/tasks/ai_analysis/scheduler.py +70 -0
- langchain_agentx/task_runtime/tasks/base/__init__.py +6 -0
- langchain_agentx/task_runtime/tasks/base/contracts.py +24 -0
- langchain_agentx/task_runtime/tasks/custom/__init__.py +7 -0
- langchain_agentx/task_runtime/tasks/custom/executor.py +60 -0
- langchain_agentx/task_runtime/tasks/custom/notification.py +7 -0
- langchain_agentx/task_runtime/tasks/custom/semantics.py +13 -0
- langchain_agentx/task_runtime/tasks/custom/spec.py +33 -0
- langchain_agentx/task_runtime/tasks/dream_task/__init__.py +15 -0
- langchain_agentx/task_runtime/tasks/dream_task/executor.py +61 -0
- langchain_agentx/task_runtime/tasks/dream_task/notification.py +19 -0
- langchain_agentx/task_runtime/tasks/dream_task/semantics.py +13 -0
- langchain_agentx/task_runtime/tasks/dream_task/spec.py +35 -0
- langchain_agentx/task_runtime/tasks/dream_task/state.py +17 -0
- langchain_agentx/task_runtime/tasks/in_process_teammate/__init__.py +12 -0
- langchain_agentx/task_runtime/tasks/in_process_teammate/executor.py +36 -0
- langchain_agentx/task_runtime/tasks/in_process_teammate/notification.py +25 -0
- langchain_agentx/task_runtime/tasks/in_process_teammate/semantics.py +13 -0
- langchain_agentx/task_runtime/tasks/in_process_teammate/spec.py +63 -0
- langchain_agentx/task_runtime/tasks/local_agent/__init__.py +14 -0
- langchain_agentx/task_runtime/tasks/local_agent/executor.py +33 -0
- langchain_agentx/task_runtime/tasks/local_agent/notification.py +21 -0
- langchain_agentx/task_runtime/tasks/local_agent/runner.py +43 -0
- langchain_agentx/task_runtime/tasks/local_agent/semantics.py +13 -0
- langchain_agentx/task_runtime/tasks/local_agent/spec.py +31 -0
- langchain_agentx/task_runtime/tasks/local_bash/__init__.py +13 -0
- langchain_agentx/task_runtime/tasks/local_bash/executor.py +95 -0
- langchain_agentx/task_runtime/tasks/local_bash/notification.py +22 -0
- langchain_agentx/task_runtime/tasks/local_bash/semantics.py +13 -0
- langchain_agentx/task_runtime/tasks/local_bash/spec.py +55 -0
- langchain_agentx/task_runtime/tasks/remote_agent/__init__.py +19 -0
- langchain_agentx/task_runtime/tasks/remote_agent/backend.py +76 -0
- langchain_agentx/task_runtime/tasks/remote_agent/executor.py +37 -0
- langchain_agentx/task_runtime/tasks/remote_agent/notification.py +22 -0
- langchain_agentx/task_runtime/tasks/remote_agent/semantics.py +13 -0
- langchain_agentx/task_runtime/tasks/remote_agent/spec.py +34 -0
- langchain_agentx/task_runtime/tasks/trace_cleanup/__init__.py +19 -0
- langchain_agentx/task_runtime/tasks/trace_cleanup/bootstrap.py +95 -0
- langchain_agentx/task_runtime/tasks/trace_cleanup/executor.py +66 -0
- langchain_agentx/task_runtime/tasks/trace_cleanup/scheduler.py +169 -0
- langchain_agentx/tool_runtime/__init__.py +90 -0
- langchain_agentx/tool_runtime/adapter.py +365 -0
- langchain_agentx/tool_runtime/base.py +319 -0
- langchain_agentx/tool_runtime/errors.py +190 -0
- langchain_agentx/tool_runtime/identical_call_cache.py +110 -0
- langchain_agentx/tool_runtime/loader.py +195 -0
- langchain_agentx/tool_runtime/models.py +260 -0
- langchain_agentx/tool_runtime/permission_context.py +78 -0
- langchain_agentx/tool_runtime/pipeline.py +621 -0
- langchain_agentx/tool_runtime/policy.py +447 -0
- langchain_agentx/tool_runtime/registry.py +81 -0
- langchain_agentx/tool_runtime/resolvers/__init__.py +27 -0
- langchain_agentx/tool_runtime/resolvers/agent_session.py +125 -0
- langchain_agentx/tool_runtime/resolvers/background.py +32 -0
- langchain_agentx/tool_runtime/resolvers/base.py +20 -0
- langchain_agentx/tool_runtime/resolvers/conversation.py +22 -0
- langchain_agentx/tool_runtime/resolvers/workflow.py +73 -0
- langchain_agentx/tool_runtime/session_store.py +132 -0
- langchain_agentx/tool_runtime/smoke_test_runtime.py +294 -0
- langchain_agentx/tool_runtime/state_bridge.py +164 -0
- langchain_agentx/tools/__init__.py +26 -0
- langchain_agentx/tools/agent/__init__.py +9 -0
- langchain_agentx/tools/agent/backend.py +53 -0
- langchain_agentx/tools/agent/built_in/__init__.py +19 -0
- langchain_agentx/tools/agent/built_in/agentx_guide.py +65 -0
- langchain_agentx/tools/agent/built_in/explore.py +80 -0
- langchain_agentx/tools/agent/built_in/general.py +57 -0
- langchain_agentx/tools/agent/built_in/plan.py +89 -0
- langchain_agentx/tools/agent/built_in/statusline_setup.py +64 -0
- langchain_agentx/tools/agent/built_in/verification.py +120 -0
- langchain_agentx/tools/agent/builtin_subagent_loader.py +89 -0
- langchain_agentx/tools/agent/cwd_resolution.py +119 -0
- langchain_agentx/tools/agent/limits.py +26 -0
- langchain_agentx/tools/agent/loader.py +270 -0
- langchain_agentx/tools/agent/models.py +85 -0
- langchain_agentx/tools/agent/prompt.py +120 -0
- langchain_agentx/tools/agent/registry/__init__.py +18 -0
- langchain_agentx/tools/agent/registry/config.py +29 -0
- langchain_agentx/tools/agent/registry/registry.py +47 -0
- langchain_agentx/tools/agent/scope.py +137 -0
- langchain_agentx/tools/agent/tool.py +256 -0
- langchain_agentx/tools/bash/__init__.py +9 -0
- langchain_agentx/tools/bash/ast_security.py +571 -0
- langchain_agentx/tools/bash/backend.py +1447 -0
- langchain_agentx/tools/bash/bash_hardening.py +734 -0
- langchain_agentx/tools/bash/bash_runtime_contract.py +41 -0
- langchain_agentx/tools/bash/cwd_reporter.py +95 -0
- langchain_agentx/tools/bash/limits.py +71 -0
- langchain_agentx/tools/bash/mode_validation.py +282 -0
- langchain_agentx/tools/bash/models.py +131 -0
- langchain_agentx/tools/bash/observability.py +148 -0
- langchain_agentx/tools/bash/output_utils.py +200 -0
- langchain_agentx/tools/bash/path_security.py +2429 -0
- langchain_agentx/tools/bash/prompt.py +68 -0
- langchain_agentx/tools/bash/read_only_validation.py +589 -0
- langchain_agentx/tools/bash/result_presenter.py +324 -0
- langchain_agentx/tools/bash/sandbox_decision.py +133 -0
- langchain_agentx/tools/bash/security.py +311 -0
- langchain_agentx/tools/bash/sed_edit_parser.py +243 -0
- langchain_agentx/tools/bash/sed_validation.py +163 -0
- langchain_agentx/tools/bash/semantics.py +111 -0
- langchain_agentx/tools/bash/session_manager.py +205 -0
- langchain_agentx/tools/bash/session_runtime.py +290 -0
- langchain_agentx/tools/bash/shell_locator.py +191 -0
- langchain_agentx/tools/bash/task_runtime.py +91 -0
- langchain_agentx/tools/bash/tool.py +939 -0
- langchain_agentx/tools/bash/windows_shell_quoting.py +45 -0
- langchain_agentx/tools/glob/__init__.py +9 -0
- langchain_agentx/tools/glob/models.py +57 -0
- langchain_agentx/tools/glob/pagination.py +30 -0
- langchain_agentx/tools/glob/prompt.py +24 -0
- langchain_agentx/tools/glob/rg_list_backend.py +139 -0
- langchain_agentx/tools/glob/rg_pattern.py +44 -0
- langchain_agentx/tools/glob/tool.py +327 -0
- langchain_agentx/tools/grep/__init__.py +7 -0
- langchain_agentx/tools/grep/backend.py +375 -0
- langchain_agentx/tools/grep/models.py +127 -0
- langchain_agentx/tools/grep/prompt.py +30 -0
- langchain_agentx/tools/grep/rg_subprocess_controller.py +114 -0
- langchain_agentx/tools/grep/tool.py +475 -0
- langchain_agentx/tools/read/__init__.py +9 -0
- langchain_agentx/tools/read/backend.py +415 -0
- langchain_agentx/tools/read/limits.py +67 -0
- langchain_agentx/tools/read/models.py +156 -0
- langchain_agentx/tools/read/prompt.py +73 -0
- langchain_agentx/tools/read/tool.py +494 -0
- langchain_agentx/tools/ripgrep_plugin_exclusions.py +137 -0
- langchain_agentx/tools/skill/__init__.py +4 -0
- langchain_agentx/tools/skill/argument_substitution.py +80 -0
- langchain_agentx/tools/skill/loader.py +196 -0
- langchain_agentx/tools/skill/models.py +88 -0
- langchain_agentx/tools/skill/policy.py +80 -0
- langchain_agentx/tools/skill/prompt.py +35 -0
- langchain_agentx/tools/skill/tool.py +222 -0
- langchain_agentx/utils/__init__.py +0 -0
- langchain_agentx/utils/cwd.py +124 -0
- langchain_agentx/utils/host_platform.py +112 -0
- langchain_agentx/utils/path_hierarchy.py +48 -0
- langchain_agentx/utils/path_user_input.py +66 -0
- langchain_agentx/utils/rg_executable.py +18 -0
- langchain_agentx/utils/subprocess_text.py +101 -0
- langchain_agentx/utils/temp_paths.py +77 -0
- langchain_agentx/utils/unc_path.py +25 -0
- langchain_agentx/utils/win_reserved_paths.py +51 -0
- langchain_agentx/workflow/__init__.py +7 -0
- langchain_agentx/workflow/base.py +97 -0
- langchain_agentx/workflow/batch.py +55 -0
- langchain_agentx/workflow/dag.py +54 -0
- langchain_agentx/workspace/__init__.py +13 -0
- langchain_agentx/workspace/config.py +140 -0
- langchain_agentx/workspace/path_key_normalizer.py +30 -0
- langchain_agentx/workspace/resolver.py +74 -0
- langchain_agentx/workspace/validators.py +41 -0
- langchain_agentx_python-0.1.dist-info/LICENSE +201 -0
- langchain_agentx_python-0.1.dist-info/METADATA +513 -0
- langchain_agentx_python-0.1.dist-info/RECORD +354 -0
- langchain_agentx_python-0.1.dist-info/WHEEL +5 -0
- langchain_agentx_python-0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""
|
|
2
|
+
tool_runtime/resolvers/background.py — 后台任务权限 resolver
|
|
3
|
+
|
|
4
|
+
职责:
|
|
5
|
+
后台/批处理场景中拒绝 ask 请求并记录审计日志。
|
|
6
|
+
|
|
7
|
+
链路位置:
|
|
8
|
+
ToolExecutorPipeline ask 分支调用,避免任务挂起等待人工输入。
|
|
9
|
+
|
|
10
|
+
当前裁剪范围:
|
|
11
|
+
仅记录基础 warning 日志,不接入统一审计事件总线。
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import logging
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class BackgroundPermissionResolver:
|
|
20
|
+
def __init__(self, logger: logging.Logger | None = None) -> None:
|
|
21
|
+
self._log = logger or logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
async def __call__(self, tool_name: str, ask_prompt: str | None) -> bool:
|
|
24
|
+
self._log.warning(
|
|
25
|
+
"background_permission_auto_deny tool=%s prompt=%s",
|
|
26
|
+
tool_name,
|
|
27
|
+
ask_prompt,
|
|
28
|
+
)
|
|
29
|
+
return False
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
__all__ = ["BackgroundPermissionResolver"]
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""
|
|
2
|
+
tool_runtime/resolvers/base.py — PermissionResolver 协议定义
|
|
3
|
+
|
|
4
|
+
职责:
|
|
5
|
+
提供 ask 分支统一回调协议,隔离 ToolExecutorPipeline 与 UI/会话实现细节。
|
|
6
|
+
|
|
7
|
+
链路位置:
|
|
8
|
+
ToolExecutorPipeline 在 auth.behavior=="ask" 时 await PermissionResolver。
|
|
9
|
+
|
|
10
|
+
当前裁剪范围:
|
|
11
|
+
仅定义类型协议,不引入具体会话/交互实现。
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from collections.abc import Awaitable, Callable
|
|
17
|
+
|
|
18
|
+
PermissionResolver = Callable[[str, str | None], Awaitable[bool]]
|
|
19
|
+
|
|
20
|
+
__all__ = ["PermissionResolver"]
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""
|
|
2
|
+
tool_runtime/resolvers/conversation.py — 对话场景权限 resolver
|
|
3
|
+
|
|
4
|
+
职责:
|
|
5
|
+
对话(Web/IM)场景下不阻塞当前轮,ask 分支统一返回拒绝。
|
|
6
|
+
|
|
7
|
+
链路位置:
|
|
8
|
+
ToolExecutorPipeline ask 分支调用;拒绝后由模型在下一轮解释并重试。
|
|
9
|
+
|
|
10
|
+
当前裁剪范围:
|
|
11
|
+
仅返回 False,不包含外部通知/消息回传能力。
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ConversationPermissionResolver:
|
|
18
|
+
async def __call__(self, tool_name: str, ask_prompt: str | None) -> bool:
|
|
19
|
+
return False
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
__all__ = ["ConversationPermissionResolver"]
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""
|
|
2
|
+
tool_runtime/resolvers/workflow.py — 工作流场景权限 resolver
|
|
3
|
+
|
|
4
|
+
职责:
|
|
5
|
+
为 workflow 节点提供同进程可批准/拒绝的等待点(Future 协调)。
|
|
6
|
+
|
|
7
|
+
链路位置:
|
|
8
|
+
ToolExecutorPipeline ask 分支 await;审批侧通过 approve/reject 回填。
|
|
9
|
+
|
|
10
|
+
当前裁剪范围:
|
|
11
|
+
仅支持基于 tool_name 的单键等待,不含跨会话持久化审批。
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import asyncio
|
|
17
|
+
from collections import deque
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class WorkflowPermissionResolver:
|
|
21
|
+
def __init__(self, *, timeout: float = 60.0) -> None:
|
|
22
|
+
self._pending: dict[str, deque[asyncio.Future[bool]]] = {}
|
|
23
|
+
self._timeout = timeout
|
|
24
|
+
self._lock = asyncio.Lock()
|
|
25
|
+
|
|
26
|
+
async def __call__(self, tool_name: str, ask_prompt: str | None) -> bool:
|
|
27
|
+
_ = ask_prompt
|
|
28
|
+
loop = asyncio.get_running_loop()
|
|
29
|
+
future: asyncio.Future[bool] = loop.create_future()
|
|
30
|
+
async with self._lock:
|
|
31
|
+
queue = self._pending.setdefault(tool_name, deque())
|
|
32
|
+
queue.append(future)
|
|
33
|
+
try:
|
|
34
|
+
return await asyncio.wait_for(future, timeout=self._timeout)
|
|
35
|
+
except asyncio.TimeoutError:
|
|
36
|
+
return False
|
|
37
|
+
finally:
|
|
38
|
+
async with self._lock:
|
|
39
|
+
queue = self._pending.get(tool_name)
|
|
40
|
+
if queue is not None:
|
|
41
|
+
try:
|
|
42
|
+
queue.remove(future)
|
|
43
|
+
except ValueError:
|
|
44
|
+
pass
|
|
45
|
+
if not queue:
|
|
46
|
+
self._pending.pop(tool_name, None)
|
|
47
|
+
|
|
48
|
+
async def approve(self, tool_name: str) -> bool:
|
|
49
|
+
future = await self._pop_next_pending(tool_name)
|
|
50
|
+
if future and not future.done():
|
|
51
|
+
future.set_result(True)
|
|
52
|
+
return True
|
|
53
|
+
return False
|
|
54
|
+
|
|
55
|
+
async def reject(self, tool_name: str) -> bool:
|
|
56
|
+
future = await self._pop_next_pending(tool_name)
|
|
57
|
+
if future and not future.done():
|
|
58
|
+
future.set_result(False)
|
|
59
|
+
return True
|
|
60
|
+
return False
|
|
61
|
+
|
|
62
|
+
async def _pop_next_pending(self, tool_name: str) -> asyncio.Future[bool] | None:
|
|
63
|
+
async with self._lock:
|
|
64
|
+
queue = self._pending.get(tool_name)
|
|
65
|
+
if not queue:
|
|
66
|
+
return None
|
|
67
|
+
future = queue.popleft()
|
|
68
|
+
if not queue:
|
|
69
|
+
self._pending.pop(tool_name, None)
|
|
70
|
+
return future
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
__all__ = ["WorkflowPermissionResolver"]
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"""
|
|
2
|
+
runtime/session_store.py — 会话级状态存储
|
|
3
|
+
|
|
4
|
+
职责:
|
|
5
|
+
AgentSessionStore 是 session 级别的状态对象,生命周期与一次 Agent 会话对齐。
|
|
6
|
+
它独立于 LangGraph graph state(turn 级)和 ToolExecutionContext(call 级),
|
|
7
|
+
负责跨 turn 持久化的会话状态:
|
|
8
|
+
|
|
9
|
+
file_read_state — 文件读取记录(为 read-before-write 约束服务)
|
|
10
|
+
file_history — 文件写入/编辑历史
|
|
11
|
+
audit_log — 工具调用审计日志
|
|
12
|
+
permissions_cache — 权限决策缓存(避免重复询问)
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import time
|
|
18
|
+
import uuid
|
|
19
|
+
from dataclasses import dataclass
|
|
20
|
+
from typing import Any
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class FileReadRecord:
|
|
25
|
+
path: str
|
|
26
|
+
tool_call_id: str | None
|
|
27
|
+
ts: float
|
|
28
|
+
line_start: int | None = None
|
|
29
|
+
line_end: int | None = None
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class FileWriteRecord:
|
|
34
|
+
path: str
|
|
35
|
+
operation: str
|
|
36
|
+
tool_call_id: str | None
|
|
37
|
+
ts: float
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class AgentSessionStore:
|
|
41
|
+
CONFIGURABLE_KEY = "session_store"
|
|
42
|
+
|
|
43
|
+
def __init__(self, session_id: str | None = None) -> None:
|
|
44
|
+
self.session_id: str = session_id or str(uuid.uuid4())
|
|
45
|
+
self._created_at: float = time.time()
|
|
46
|
+
self._file_read_state: dict[str, FileReadRecord] = {}
|
|
47
|
+
self._file_history: list[FileWriteRecord] = []
|
|
48
|
+
self._audit_log: list[dict[str, Any]] = []
|
|
49
|
+
self._permissions_cache: dict[str, Any] = {}
|
|
50
|
+
|
|
51
|
+
def record_file_read(
|
|
52
|
+
self,
|
|
53
|
+
path: str,
|
|
54
|
+
*,
|
|
55
|
+
tool_call_id: str | None = None,
|
|
56
|
+
ts: float | None = None,
|
|
57
|
+
line_start: int | None = None,
|
|
58
|
+
line_end: int | None = None,
|
|
59
|
+
) -> None:
|
|
60
|
+
self._file_read_state[path] = FileReadRecord(
|
|
61
|
+
path=path,
|
|
62
|
+
tool_call_id=tool_call_id,
|
|
63
|
+
ts=ts if ts is not None else time.time(),
|
|
64
|
+
line_start=line_start,
|
|
65
|
+
line_end=line_end,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
def has_been_read(self, path: str) -> bool:
|
|
69
|
+
return path in self._file_read_state
|
|
70
|
+
|
|
71
|
+
def get_file_read_record(self, path: str) -> FileReadRecord | None:
|
|
72
|
+
return self._file_read_state.get(path)
|
|
73
|
+
|
|
74
|
+
def get_all_read_paths(self) -> list[str]:
|
|
75
|
+
return list(self._file_read_state.keys())
|
|
76
|
+
|
|
77
|
+
def record_file_write(
|
|
78
|
+
self,
|
|
79
|
+
path: str,
|
|
80
|
+
operation: str,
|
|
81
|
+
*,
|
|
82
|
+
tool_call_id: str | None = None,
|
|
83
|
+
ts: float | None = None,
|
|
84
|
+
) -> None:
|
|
85
|
+
self._file_history.append(
|
|
86
|
+
FileWriteRecord(
|
|
87
|
+
path=path,
|
|
88
|
+
operation=operation,
|
|
89
|
+
tool_call_id=tool_call_id,
|
|
90
|
+
ts=ts if ts is not None else time.time(),
|
|
91
|
+
)
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
def get_file_history(self) -> list[FileWriteRecord]:
|
|
95
|
+
return list(self._file_history)
|
|
96
|
+
|
|
97
|
+
def append_audit(self, record: dict[str, Any]) -> None:
|
|
98
|
+
self._audit_log.append(record)
|
|
99
|
+
|
|
100
|
+
def get_audit_log(self) -> list[dict[str, Any]]:
|
|
101
|
+
return list(self._audit_log)
|
|
102
|
+
|
|
103
|
+
def export_audit_log(self) -> list[dict[str, Any]]:
|
|
104
|
+
"""语义化导出出口;等价于 get_audit_log()."""
|
|
105
|
+
return self.get_audit_log()
|
|
106
|
+
|
|
107
|
+
def cache_permission(self, key: str, decision: Any) -> None:
|
|
108
|
+
self._permissions_cache[key] = decision
|
|
109
|
+
|
|
110
|
+
def get_cached_permission(self, key: str) -> Any | None:
|
|
111
|
+
return self._permissions_cache.get(key)
|
|
112
|
+
|
|
113
|
+
@property
|
|
114
|
+
def created_at(self) -> float:
|
|
115
|
+
return self._created_at
|
|
116
|
+
|
|
117
|
+
def summary(self) -> dict[str, Any]:
|
|
118
|
+
return {
|
|
119
|
+
"session_id": self.session_id,
|
|
120
|
+
"created_at": self._created_at,
|
|
121
|
+
"files_read": len(self._file_read_state),
|
|
122
|
+
"file_writes": len(self._file_history),
|
|
123
|
+
"audit_entries": len(self._audit_log),
|
|
124
|
+
"cached_permissions": len(self._permissions_cache),
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
def __repr__(self) -> str:
|
|
128
|
+
return (
|
|
129
|
+
f"<AgentSessionStore session_id={self.session_id!r} "
|
|
130
|
+
f"files_read={len(self._file_read_state)} "
|
|
131
|
+
f"audit_entries={len(self._audit_log)}>"
|
|
132
|
+
)
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
"""
|
|
2
|
+
runtime 框架端到端冒烟测试(smoke_test_runtime.py)
|
|
3
|
+
|
|
4
|
+
用途:
|
|
5
|
+
快速验证 runtime 框架的完整执行路径,无需完整环境,
|
|
6
|
+
可直接 `python smoke_test_runtime.py` 运行(不依赖 pytest)。
|
|
7
|
+
|
|
8
|
+
覆盖场景:
|
|
9
|
+
1. 正常工具流转(EchoTool 10 步 pipeline)
|
|
10
|
+
2. adapter.envelope_to_tool_output 输出格式
|
|
11
|
+
3. validate_input 失败 → error envelope 短路
|
|
12
|
+
4. check_permissions deny → blocked envelope 短路
|
|
13
|
+
5. ToolOutputManager 超限截断 + overflow_file
|
|
14
|
+
6. registry + DefaultPolicyEngine 无路径字段放行
|
|
15
|
+
7. StateBridge record_read / has_been_read / append_audit
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import os
|
|
21
|
+
import sys
|
|
22
|
+
import tempfile
|
|
23
|
+
import types
|
|
24
|
+
import pathlib
|
|
25
|
+
import importlib.util
|
|
26
|
+
|
|
27
|
+
# ---------------------------------------------------------------------------
|
|
28
|
+
# 绕过顶层 langchain_agentx/__init__.py(该文件依赖尚未安装的包)
|
|
29
|
+
# 手动注册父包 stub,直接加载 runtime 子模块
|
|
30
|
+
# ---------------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
def _stub_pkg(name: str, path_hint: str) -> None:
|
|
33
|
+
m = types.ModuleType(name)
|
|
34
|
+
m.__path__ = [path_hint]
|
|
35
|
+
m.__package__ = name
|
|
36
|
+
sys.modules[name] = m
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# 本文件位于 langchain_agentx/tool_runtime/,parents[2] = 仓库根(含 langchain_agentx 包目录)
|
|
40
|
+
_ROOT = pathlib.Path(__file__).resolve().parents[2]
|
|
41
|
+
|
|
42
|
+
_stub_pkg("langchain_agentx", str(_ROOT / "langchain_agentx"))
|
|
43
|
+
_stub_pkg("langchain_agentx.tool_runtime", str(_ROOT / "langchain_agentx/tool_runtime"))
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _load_module(sub: str) -> None:
|
|
47
|
+
mod_name = f"langchain_agentx.tool_runtime.{sub}"
|
|
48
|
+
path = _ROOT / "langchain_agentx/tool_runtime" / f"{sub}.py"
|
|
49
|
+
spec = importlib.util.spec_from_file_location(mod_name, path)
|
|
50
|
+
mod = importlib.util.module_from_spec(spec)
|
|
51
|
+
sys.modules[mod_name] = mod
|
|
52
|
+
spec.loader.exec_module(mod)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
for _sub in ["models", "errors", "base", "policy", "state_bridge", "pipeline", "adapter", "registry"]:
|
|
56
|
+
_load_module(_sub)
|
|
57
|
+
|
|
58
|
+
# ---------------------------------------------------------------------------
|
|
59
|
+
# 导入
|
|
60
|
+
# ---------------------------------------------------------------------------
|
|
61
|
+
|
|
62
|
+
from langchain_agentx.tool_runtime.models import ToolExecutionContext, ToolResultEnvelope, ValidationResult, AuthorizationDecision # noqa: E402
|
|
63
|
+
from langchain_agentx.tool_runtime.base import RuntimeTool # noqa: E402
|
|
64
|
+
from langchain_agentx.tool_runtime.pipeline import ToolExecutorPipeline # noqa: E402
|
|
65
|
+
from langchain_agentx.tool_runtime.adapter import LangChainAdapter # noqa: E402
|
|
66
|
+
from langchain_agentx.tool_runtime.registry import RuntimeToolRegistry # noqa: E402
|
|
67
|
+
from langchain_agentx.tool_runtime.policy import ToolPolicyConfig, DefaultPolicyEngine # noqa: E402
|
|
68
|
+
from langchain_agentx.tool_runtime.state_bridge import ToolStateBridge # noqa: E402
|
|
69
|
+
from pydantic import BaseModel # noqa: E402
|
|
70
|
+
|
|
71
|
+
# ---------------------------------------------------------------------------
|
|
72
|
+
# 最小工具定义(所有测试共用)
|
|
73
|
+
# ---------------------------------------------------------------------------
|
|
74
|
+
|
|
75
|
+
class EchoInput(BaseModel):
|
|
76
|
+
message: str
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class EchoTool(RuntimeTool):
|
|
80
|
+
name = "echo"
|
|
81
|
+
description = "回显输入消息"
|
|
82
|
+
input_model = EchoInput
|
|
83
|
+
is_read_only = True
|
|
84
|
+
|
|
85
|
+
def invoke(self, data, ctx):
|
|
86
|
+
return data["message"]
|
|
87
|
+
|
|
88
|
+
def present(self, data, result, ctx):
|
|
89
|
+
return ToolResultEnvelope(
|
|
90
|
+
status="ok",
|
|
91
|
+
tool_name=self.name,
|
|
92
|
+
summary=f"Echo: {result}",
|
|
93
|
+
payload=result,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
# ---------------------------------------------------------------------------
|
|
98
|
+
# 辅助
|
|
99
|
+
# ---------------------------------------------------------------------------
|
|
100
|
+
|
|
101
|
+
def _make_ctx(state=None) -> ToolExecutionContext:
|
|
102
|
+
return ToolExecutionContext(
|
|
103
|
+
tool_name="echo",
|
|
104
|
+
tool_call_id="call_001",
|
|
105
|
+
input_args={"message": "hello"},
|
|
106
|
+
state=state if state is not None else {},
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _pass(label: str) -> None:
|
|
111
|
+
print(f" ✅ {label}")
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _section(title: str) -> None:
|
|
115
|
+
print(f"\n=== {title} ===")
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
# ---------------------------------------------------------------------------
|
|
119
|
+
# Smoke tests
|
|
120
|
+
# ---------------------------------------------------------------------------
|
|
121
|
+
|
|
122
|
+
def test_normal_flow() -> None:
|
|
123
|
+
_section("Test 1: 正常工具流转")
|
|
124
|
+
pipeline = ToolExecutorPipeline()
|
|
125
|
+
ctx = _make_ctx()
|
|
126
|
+
env = pipeline.run(tool=EchoTool(), raw_input={"message": "hello runtime"}, ctx=ctx)
|
|
127
|
+
assert env.status == "ok", f"got {env.status}"
|
|
128
|
+
assert "hello runtime" in env.summary
|
|
129
|
+
_pass(f"status=ok, summary={env.summary!r}")
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def test_adapter_output() -> None:
|
|
133
|
+
_section("Test 2: adapter.envelope_to_tool_output")
|
|
134
|
+
pipeline = ToolExecutorPipeline()
|
|
135
|
+
adapter = LangChainAdapter()
|
|
136
|
+
ctx = _make_ctx()
|
|
137
|
+
env = pipeline.run(tool=EchoTool(), raw_input={"message": "hello"}, ctx=ctx)
|
|
138
|
+
output = adapter.envelope_to_tool_output(env)
|
|
139
|
+
assert "hello" in output
|
|
140
|
+
_pass(f"output={output!r}")
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def test_validate_input_short_circuit() -> None:
|
|
144
|
+
_section("Test 3: validate_input 失败 → error 短路")
|
|
145
|
+
|
|
146
|
+
class StrictEcho(EchoTool):
|
|
147
|
+
name = "strict_echo"
|
|
148
|
+
def validate_input(self, data, ctx):
|
|
149
|
+
if len(data["message"]) > 5:
|
|
150
|
+
return ValidationResult(ok=False, message="message too long")
|
|
151
|
+
return ValidationResult(ok=True)
|
|
152
|
+
|
|
153
|
+
pipeline = ToolExecutorPipeline()
|
|
154
|
+
ctx = _make_ctx()
|
|
155
|
+
env = pipeline.run(tool=StrictEcho(), raw_input={"message": "too long message"}, ctx=ctx)
|
|
156
|
+
assert env.status == "error"
|
|
157
|
+
assert "too long" in env.summary
|
|
158
|
+
_pass(f"status=error, summary={env.summary!r}")
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def test_check_permissions_blocked() -> None:
|
|
162
|
+
_section("Test 4: check_permissions deny → blocked 短路")
|
|
163
|
+
|
|
164
|
+
class DeniedEcho(EchoTool):
|
|
165
|
+
name = "denied_echo"
|
|
166
|
+
def check_permissions(self, data, ctx):
|
|
167
|
+
return AuthorizationDecision(behavior="deny", message="not allowed here")
|
|
168
|
+
|
|
169
|
+
pipeline = ToolExecutorPipeline()
|
|
170
|
+
ctx = _make_ctx()
|
|
171
|
+
env = pipeline.run(tool=DeniedEcho(), raw_input={"message": "hi"}, ctx=ctx)
|
|
172
|
+
assert env.status == "blocked"
|
|
173
|
+
assert "not allowed" in env.summary
|
|
174
|
+
_pass(f"status=blocked, summary={env.summary!r}")
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def test_output_manager_truncation() -> None:
|
|
178
|
+
_section("Test 5: ToolOutputManager 超限截断")
|
|
179
|
+
|
|
180
|
+
class TinyEcho(EchoTool):
|
|
181
|
+
name = "tiny_echo"
|
|
182
|
+
max_result_size_chars = 5
|
|
183
|
+
|
|
184
|
+
pipeline = ToolExecutorPipeline()
|
|
185
|
+
ctx = _make_ctx()
|
|
186
|
+
env = pipeline.run(tool=TinyEcho(), raw_input={"message": "hello runtime 1234"}, ctx=ctx)
|
|
187
|
+
assert env.truncated is True
|
|
188
|
+
assert env.overflow_file is not None
|
|
189
|
+
assert os.path.exists(env.overflow_file)
|
|
190
|
+
_pass(f"truncated=True, overflow_file={env.overflow_file}")
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def test_registry_and_policy_no_path_field() -> None:
|
|
194
|
+
_section("Test 6: registry + DefaultPolicyEngine(无路径字段 → allow)")
|
|
195
|
+
tmpdir = tempfile.mkdtemp()
|
|
196
|
+
policy = DefaultPolicyEngine(
|
|
197
|
+
ToolPolicyConfig(read_roots=[tmpdir], write_roots=[tmpdir])
|
|
198
|
+
)
|
|
199
|
+
pipeline = ToolExecutorPipeline()
|
|
200
|
+
adapter = LangChainAdapter()
|
|
201
|
+
registry = RuntimeToolRegistry(pipeline=pipeline, adapter=adapter)
|
|
202
|
+
registry.register(EchoTool(policy=policy))
|
|
203
|
+
|
|
204
|
+
assert "echo" in registry
|
|
205
|
+
lc_tools = registry.to_langchain_tools()
|
|
206
|
+
assert len(lc_tools) == 1
|
|
207
|
+
assert lc_tools[0].name == "echo"
|
|
208
|
+
|
|
209
|
+
ctx = _make_ctx()
|
|
210
|
+
env = pipeline.run(tool=registry.get("echo"), raw_input={"message": "ok"}, ctx=ctx)
|
|
211
|
+
assert env.status == "ok"
|
|
212
|
+
_pass("registry.register + to_langchain_tools OK, policy allow (no path field)")
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def test_state_bridge() -> None:
|
|
216
|
+
_section("Test 7: ToolStateBridge record_read / has_been_read / append_audit")
|
|
217
|
+
bridge = ToolStateBridge()
|
|
218
|
+
state = {}
|
|
219
|
+
ctx = ToolExecutionContext(
|
|
220
|
+
tool_name="echo", tool_call_id="c1", input_args={}, state=state
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
_td = pathlib.Path(tempfile.gettempdir())
|
|
224
|
+
foo = str(_td / "langchain_agentx_smoke" / "foo.txt")
|
|
225
|
+
bar = str(_td / "langchain_agentx_smoke" / "bar.txt")
|
|
226
|
+
bridge.record_read(ctx, foo, line_start=1, line_end=10)
|
|
227
|
+
assert bridge.has_been_read(ctx, foo)
|
|
228
|
+
assert not bridge.has_been_read(ctx, bar)
|
|
229
|
+
|
|
230
|
+
bridge.append_audit(ctx, {"tool": "echo", "ts": 9999})
|
|
231
|
+
log = bridge.get_audit_log(ctx)
|
|
232
|
+
assert len(log) == 1
|
|
233
|
+
assert log[0]["tool"] == "echo"
|
|
234
|
+
_pass("record_read / has_been_read / audit OK")
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def test_exception_captured_as_error_envelope() -> None:
|
|
238
|
+
_section("Test 8: invoke 抛异常 → pipeline 捕获为 error envelope")
|
|
239
|
+
|
|
240
|
+
class BrokenEcho(EchoTool):
|
|
241
|
+
name = "broken_echo"
|
|
242
|
+
def invoke(self, data, ctx):
|
|
243
|
+
raise ValueError("something went wrong")
|
|
244
|
+
|
|
245
|
+
pipeline = ToolExecutorPipeline()
|
|
246
|
+
ctx = _make_ctx()
|
|
247
|
+
env = pipeline.run(tool=BrokenEcho(), raw_input={"message": "hi"}, ctx=ctx)
|
|
248
|
+
assert env.status == "error"
|
|
249
|
+
assert "went wrong" in env.summary
|
|
250
|
+
assert env.meta is not None and "traceback" in env.meta
|
|
251
|
+
_pass(f"status=error, summary={env.summary!r}, traceback in meta")
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def test_schema_parse_failure() -> None:
|
|
255
|
+
_section("Test 9: Pydantic schema 解析失败 → error 短路")
|
|
256
|
+
pipeline = ToolExecutorPipeline()
|
|
257
|
+
ctx = _make_ctx()
|
|
258
|
+
# 传入错误类型(缺少必填 message 字段)
|
|
259
|
+
env = pipeline.run(tool=EchoTool(), raw_input={}, ctx=ctx)
|
|
260
|
+
assert env.status == "error"
|
|
261
|
+
assert "schema" in env.summary.lower() or "message" in env.summary.lower()
|
|
262
|
+
_pass(f"status=error on missing field: {env.summary!r}")
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
# ---------------------------------------------------------------------------
|
|
266
|
+
# Main
|
|
267
|
+
# ---------------------------------------------------------------------------
|
|
268
|
+
|
|
269
|
+
if __name__ == "__main__":
|
|
270
|
+
tests = [
|
|
271
|
+
test_normal_flow,
|
|
272
|
+
test_adapter_output,
|
|
273
|
+
test_validate_input_short_circuit,
|
|
274
|
+
test_check_permissions_blocked,
|
|
275
|
+
test_output_manager_truncation,
|
|
276
|
+
test_registry_and_policy_no_path_field,
|
|
277
|
+
test_state_bridge,
|
|
278
|
+
test_exception_captured_as_error_envelope,
|
|
279
|
+
test_schema_parse_failure,
|
|
280
|
+
]
|
|
281
|
+
failed = []
|
|
282
|
+
for fn in tests:
|
|
283
|
+
try:
|
|
284
|
+
fn()
|
|
285
|
+
except Exception as exc:
|
|
286
|
+
print(f" ❌ FAILED: {exc}")
|
|
287
|
+
failed.append(fn.__name__)
|
|
288
|
+
|
|
289
|
+
print()
|
|
290
|
+
if failed:
|
|
291
|
+
print(f"❌ {len(failed)} test(s) failed: {failed}")
|
|
292
|
+
sys.exit(1)
|
|
293
|
+
else:
|
|
294
|
+
print(f"✅ All {len(tests)} smoke tests passed")
|