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,120 @@
|
|
|
1
|
+
"""
|
|
2
|
+
tools/agent/built_in/verification.py — Verification 内置类型
|
|
3
|
+
|
|
4
|
+
职责:定义验证型 subagent,用于代码/配置核验,不承载执行逻辑。
|
|
5
|
+
在整体链路中的位置:builtin_subagent_loader 在 LANGCHAIN_AGENTX_BUILTIN_VERIFICATION 时调用 register()。
|
|
6
|
+
CC 对照:src/tools/AgentTool/built-in/verificationAgent.ts。
|
|
7
|
+
当前裁剪范围:v3 对齐“强验证”语义,强调证据驱动与只读边界。
|
|
8
|
+
|
|
9
|
+
典型使用场景:
|
|
10
|
+
1) 非 trivial 改动完成后,需要独立验证正确性与回归风险;
|
|
11
|
+
2) 需要明确 PASS/FAIL/PARTIAL 结论并附命令证据;
|
|
12
|
+
3) 需要尝试边界/异常/对抗输入,而非仅走 happy path。
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from ..registry.config import SubagentConfig
|
|
18
|
+
from ..registry.registry import SubagentRegistry
|
|
19
|
+
|
|
20
|
+
_DISALLOWED = frozenset({"Agent", "agent", "Edit", "Write", "NotebookEdit"})
|
|
21
|
+
|
|
22
|
+
_SYSTEM_PROMPT = """You are a verification specialist. Your job is not to confirm the \
|
|
23
|
+
implementation works — it's to try to break it.
|
|
24
|
+
|
|
25
|
+
You have two documented failure patterns. First, verification avoidance: when faced with \
|
|
26
|
+
a check, you find reasons not to run it — you read code, narrate what you would test, \
|
|
27
|
+
write "PASS," and move on. Second, being seduced by the first 80%: you see passing tests \
|
|
28
|
+
and feel inclined to pass it, not noticing half the paths do nothing or the backend crashes \
|
|
29
|
+
on bad input. Your entire value is in finding the last 20%.
|
|
30
|
+
|
|
31
|
+
=== CRITICAL: DO NOT MODIFY THE PROJECT ===
|
|
32
|
+
You are STRICTLY PROHIBITED from:
|
|
33
|
+
- Creating, modifying, or deleting any files IN THE PROJECT DIRECTORY
|
|
34
|
+
- Installing dependencies or packages
|
|
35
|
+
- Running git write operations (add, commit, push)
|
|
36
|
+
|
|
37
|
+
You MAY write ephemeral test scripts to /tmp when inline commands aren't sufficient. \
|
|
38
|
+
Clean up after yourself.
|
|
39
|
+
|
|
40
|
+
=== VERIFICATION STRATEGY ===
|
|
41
|
+
Adapt your strategy based on what was changed:
|
|
42
|
+
|
|
43
|
+
**Backend/API changes**: Start server → curl/fetch endpoints → verify response shapes \
|
|
44
|
+
against expected values (not just status codes) → test error handling → check edge cases
|
|
45
|
+
**CLI/script changes**: Run with representative inputs → verify stdout/stderr/exit codes \
|
|
46
|
+
→ test edge inputs (empty, malformed, boundary) → verify --help output is accurate
|
|
47
|
+
**Library/package changes**: Build → full test suite → import and exercise the public API \
|
|
48
|
+
as a consumer would → verify exported interfaces match docs
|
|
49
|
+
**Bug fixes**: Reproduce the original bug → verify fix → run regression tests → check \
|
|
50
|
+
related functionality for side effects
|
|
51
|
+
**Refactoring (no behavior change)**: Existing test suite MUST pass unchanged → diff the \
|
|
52
|
+
public API surface → spot-check observable behavior is identical (same inputs → same outputs)
|
|
53
|
+
**Other change types**: (a) figure out how to exercise this change directly, \
|
|
54
|
+
(b) check outputs against expectations, (c) try to break it with inputs the implementer didn't test.
|
|
55
|
+
|
|
56
|
+
=== REQUIRED STEPS (universal baseline) ===
|
|
57
|
+
1. Read CLAUDE.md / README for build/test commands. Check pyproject.toml / Makefile for script names.
|
|
58
|
+
2. Run the build (if applicable). A broken build is an automatic FAIL.
|
|
59
|
+
3. Run the project's test suite. Failing tests are an automatic FAIL.
|
|
60
|
+
4. Run linters/type-checkers if configured (mypy, ruff, etc.).
|
|
61
|
+
5. Check for regressions in related code.
|
|
62
|
+
|
|
63
|
+
Then apply the type-specific strategy above.
|
|
64
|
+
|
|
65
|
+
=== ADVERSARIAL PROBES ===
|
|
66
|
+
Functional tests confirm the happy path. Also try to break it:
|
|
67
|
+
- **Boundary values**: 0, -1, empty string, very long strings, unicode
|
|
68
|
+
- **Idempotency**: same mutating request twice — duplicate created? correct no-op?
|
|
69
|
+
- **Orphan operations**: reference IDs/paths that don't exist
|
|
70
|
+
- **Concurrency** (where applicable): parallel requests to shared state
|
|
71
|
+
|
|
72
|
+
=== RECOGNIZE YOUR OWN RATIONALIZATIONS ===
|
|
73
|
+
- "The code looks correct based on my reading" — reading is not verification. Run it.
|
|
74
|
+
- "The implementer's tests already pass" — verify independently.
|
|
75
|
+
- "This is probably fine" — probably is not verified. Run it.
|
|
76
|
+
If you catch yourself writing an explanation instead of a command, stop. Run the command.
|
|
77
|
+
|
|
78
|
+
=== OUTPUT FORMAT (REQUIRED) ===
|
|
79
|
+
Every check MUST follow this structure:
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
### Check: [what you're verifying]
|
|
83
|
+
**Command run:**
|
|
84
|
+
[exact command you executed]
|
|
85
|
+
**Output observed:**
|
|
86
|
+
[actual terminal output — copy-paste, not paraphrased]
|
|
87
|
+
**Result: PASS** (or FAIL — with Expected vs Actual)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
End with exactly one of:
|
|
91
|
+
VERDICT: PASS
|
|
92
|
+
VERDICT: FAIL
|
|
93
|
+
VERDICT: PARTIAL
|
|
94
|
+
|
|
95
|
+
PARTIAL is for environmental limitations only (tool unavailable, server can't start) — \
|
|
96
|
+
not for "I'm unsure." Use the literal string `VERDICT: ` followed by exactly one of \
|
|
97
|
+
`PASS`, `FAIL`, `PARTIAL`. No markdown, no punctuation, no variation."""
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _verification_tool_filter(all_tools: list) -> list:
|
|
101
|
+
return [t for t in all_tools if getattr(t, "name", "") not in _DISALLOWED]
|
|
102
|
+
|
|
103
|
+
VERIFICATION_AGENT = SubagentConfig(
|
|
104
|
+
name="verification",
|
|
105
|
+
description="Verification-focused agent for tests, assertions, and regression checks.",
|
|
106
|
+
when_to_use=(
|
|
107
|
+
"Use this agent to independently verify non-trivial changes after implementation. "
|
|
108
|
+
"It runs tests, checks regressions, and tries to break the implementation with "
|
|
109
|
+
"boundary/adversarial inputs. Returns a PASS/FAIL/PARTIAL verdict with command evidence."
|
|
110
|
+
),
|
|
111
|
+
system_prompt_factory=lambda: _SYSTEM_PROMPT,
|
|
112
|
+
tool_filter=_verification_tool_filter,
|
|
113
|
+
tools_description="All tools except Agent/Edit/Write/NotebookEdit",
|
|
114
|
+
default_max_steps=None,
|
|
115
|
+
allow_async=False,
|
|
116
|
+
tags={"verification", "testing"},
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
def register(registry: SubagentRegistry) -> None:
|
|
120
|
+
registry.register(VERIFICATION_AGENT)
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""
|
|
2
|
+
tools/agent/builtin_subagent_loader.py — 内置 subagent 的环境驱动装载
|
|
3
|
+
|
|
4
|
+
职责:按环境变量解析是否装载各 built_in 子模块,并写入调用方提供的 SubagentRegistry;
|
|
5
|
+
提供进程内懒单例供 AgentRuntimeTool(registry=None)。
|
|
6
|
+
在整体链路中的位置:AgentRuntimeTool → BuiltinSubagentEnvLoader / DefaultSubagentRegistryProvider;
|
|
7
|
+
built_in/ 仅保留 SubagentConfig 与各模块 register(),不包含装载策略。
|
|
8
|
+
CC 对照:CC 以环境变量控制内置能力;本模块为显式 OOP 装载入口。
|
|
9
|
+
|
|
10
|
+
环境变量(truthy:1/true/yes/on,大小写不敏感):
|
|
11
|
+
- LANGCHAIN_AGENTX_BUILTIN_SUBAGENTS_DISABLED — 总闸:为真时不注册任何 built-in。
|
|
12
|
+
- LANGCHAIN_AGENTX_CLI — CLI 宿主;与 BUILTIN_STATUSLINE 同时为真才注册 statusline-setup。
|
|
13
|
+
- LANGCHAIN_AGENTX_BUILTIN_STATUSLINE / AGENTX_GUIDE / VERIFICATION — 可选增量。
|
|
14
|
+
总闸未关时固定注册:general-purpose、Explore、Plan。
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import os
|
|
20
|
+
import threading
|
|
21
|
+
from collections.abc import Mapping
|
|
22
|
+
from typing import ClassVar
|
|
23
|
+
|
|
24
|
+
from .registry.registry import SubagentRegistry
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class BuiltinSubagentEnvLoader:
|
|
28
|
+
"""读取环境变量,将内置 subagent 注册到已有 SubagentRegistry。"""
|
|
29
|
+
|
|
30
|
+
def __init__(self, environ: Mapping[str, str] | None = None) -> None:
|
|
31
|
+
self._environ: Mapping[str, str] = environ if environ is not None else os.environ
|
|
32
|
+
|
|
33
|
+
@staticmethod
|
|
34
|
+
def _truthy(raw: str | None) -> bool:
|
|
35
|
+
if raw is None:
|
|
36
|
+
return False
|
|
37
|
+
return raw.strip().lower() in {"1", "true", "yes", "on"}
|
|
38
|
+
|
|
39
|
+
def _env_flag(self, name: str) -> bool:
|
|
40
|
+
return self._truthy(self._environ.get(name))
|
|
41
|
+
|
|
42
|
+
def subagents_disabled(self) -> bool:
|
|
43
|
+
return self._env_flag("LANGCHAIN_AGENTX_BUILTIN_SUBAGENTS_DISABLED")
|
|
44
|
+
|
|
45
|
+
def load_into(self, registry: SubagentRegistry) -> None:
|
|
46
|
+
if self.subagents_disabled():
|
|
47
|
+
return
|
|
48
|
+
|
|
49
|
+
from .built_in import explore, general, plan
|
|
50
|
+
|
|
51
|
+
general.register(registry)
|
|
52
|
+
explore.register(registry)
|
|
53
|
+
plan.register(registry)
|
|
54
|
+
|
|
55
|
+
if self._env_flag("LANGCHAIN_AGENTX_BUILTIN_VERIFICATION"):
|
|
56
|
+
from .built_in import verification
|
|
57
|
+
|
|
58
|
+
verification.register(registry)
|
|
59
|
+
if self._env_flag("LANGCHAIN_AGENTX_BUILTIN_AGENTX_GUIDE"):
|
|
60
|
+
from .built_in import agentx_guide
|
|
61
|
+
|
|
62
|
+
agentx_guide.register(registry)
|
|
63
|
+
if self._env_flag("LANGCHAIN_AGENTX_BUILTIN_STATUSLINE") and self._env_flag(
|
|
64
|
+
"LANGCHAIN_AGENTX_CLI"
|
|
65
|
+
):
|
|
66
|
+
from .built_in import statusline_setup
|
|
67
|
+
|
|
68
|
+
statusline_setup.register(registry)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class DefaultSubagentRegistryProvider:
|
|
72
|
+
"""进程内懒单例:首次访问时用 BuiltinSubagentEnvLoader 填充 SubagentRegistry。"""
|
|
73
|
+
|
|
74
|
+
_registry: ClassVar[SubagentRegistry | None] = None
|
|
75
|
+
_lock: ClassVar[threading.Lock] = threading.Lock()
|
|
76
|
+
|
|
77
|
+
@classmethod
|
|
78
|
+
def get(cls) -> SubagentRegistry:
|
|
79
|
+
with cls._lock:
|
|
80
|
+
if cls._registry is None:
|
|
81
|
+
cls._registry = SubagentRegistry()
|
|
82
|
+
BuiltinSubagentEnvLoader().load_into(cls._registry)
|
|
83
|
+
return cls._registry
|
|
84
|
+
|
|
85
|
+
@classmethod
|
|
86
|
+
def reset_for_tests(cls) -> None:
|
|
87
|
+
"""仅测试:释放单例以便 monkeypatch 环境后重新装载。"""
|
|
88
|
+
with cls._lock:
|
|
89
|
+
cls._registry = None
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""
|
|
2
|
+
tools/agent/cwd_resolution.py — Agent 工具 cwd 展开与 workspace 校验
|
|
3
|
+
|
|
4
|
+
职责:
|
|
5
|
+
将可选 ``cwd`` 展开为绝对路径(``normalize_user_path_string_for_expand`` + ``expand_path``),
|
|
6
|
+
并校验落在 ``workspace_root`` 内、目录存在;UNC 不做本地 ``isdir``(与 Grep/Read 一致)。
|
|
7
|
+
链路位置:
|
|
8
|
+
``AgentRuntimeTool.validate_input``(成功时写回 ``data["cwd"]`` 供 ``invoke`` 使用)。
|
|
9
|
+
当前裁剪:
|
|
10
|
+
不替代全局 PolicyEngine;仅语义校验层中文消息。无 ``workspace_root`` 时以 ``get_cwd()`` 为展开基准,
|
|
11
|
+
且不做「必须在 workspace 内」校验(与测试桩及旧编排兼容)。
|
|
12
|
+
CC 对照:
|
|
13
|
+
``AgentTool.tsx`` cwd 描述(Absolute path);``utils/path.ts`` ``expandPath``。
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import os
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from typing import Any
|
|
21
|
+
|
|
22
|
+
from langchain_agentx.tool_runtime.models import ValidationResult
|
|
23
|
+
from langchain_agentx.utils.cwd import expand_path, get_cwd
|
|
24
|
+
from langchain_agentx.utils.path_user_input import normalize_user_path_string_for_expand
|
|
25
|
+
from langchain_agentx.utils.unc_path import is_unc_path_skip_local_stat
|
|
26
|
+
from langchain_agentx.utils.win_reserved_paths import path_uses_windows_reserved_name
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _path_is_descendant(candidate: Path, ancestor: Path) -> bool:
|
|
30
|
+
try:
|
|
31
|
+
candidate.relative_to(ancestor)
|
|
32
|
+
return True
|
|
33
|
+
except ValueError:
|
|
34
|
+
return False
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class AgentToolCwdResolution:
|
|
38
|
+
"""Agent ``cwd`` 展开与校验(单类单责,避免 ``tool.py`` 堆逻辑)。"""
|
|
39
|
+
|
|
40
|
+
@staticmethod
|
|
41
|
+
def expand_to_absolute(raw: str, *, workspace_root: str | None) -> str:
|
|
42
|
+
"""相对路径相对 ``workspace_root``(若缺省则 ``get_cwd()``)展开为绝对路径。"""
|
|
43
|
+
base = workspace_root if workspace_root else get_cwd()
|
|
44
|
+
normalized = normalize_user_path_string_for_expand(raw.strip())
|
|
45
|
+
return expand_path(normalized, base_dir=base)
|
|
46
|
+
|
|
47
|
+
@staticmethod
|
|
48
|
+
def validate_expanded(absolute: str, *, workspace_root: str | None) -> str | None:
|
|
49
|
+
"""返回 ``None`` 表示通过;否则为面向模型的中文错误信息。"""
|
|
50
|
+
if path_uses_windows_reserved_name(absolute):
|
|
51
|
+
return "cwd 路径包含 Windows 保留设备名,请更换为普通目录路径。"
|
|
52
|
+
|
|
53
|
+
if is_unc_path_skip_local_stat(absolute):
|
|
54
|
+
return None
|
|
55
|
+
|
|
56
|
+
if workspace_root:
|
|
57
|
+
try:
|
|
58
|
+
root = Path(workspace_root).expanduser().resolve()
|
|
59
|
+
candidate = Path(absolute).expanduser().resolve()
|
|
60
|
+
except OSError as exc:
|
|
61
|
+
return f"cwd 无法解析到 workspace 内:{exc}"
|
|
62
|
+
if not _path_is_descendant(candidate, root):
|
|
63
|
+
return (
|
|
64
|
+
"cwd 必须位于当前 workspace 根目录之下,"
|
|
65
|
+
"不可使用 ../ 等方式跳出工程根。"
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
try:
|
|
69
|
+
if not os.path.isdir(absolute):
|
|
70
|
+
return "cwd 必须是已存在的目录路径。"
|
|
71
|
+
except OSError as exc:
|
|
72
|
+
return f"cwd 无法访问({exc})。请确认路径存在且可读。"
|
|
73
|
+
|
|
74
|
+
return None
|
|
75
|
+
|
|
76
|
+
@classmethod
|
|
77
|
+
def resolve_and_validate(
|
|
78
|
+
cls,
|
|
79
|
+
raw_cwd: str,
|
|
80
|
+
*,
|
|
81
|
+
ctx: Any,
|
|
82
|
+
) -> tuple[str | None, str | None]:
|
|
83
|
+
"""展开并校验;成功时 ``(absolute, None)``,失败 ``(None, message)``。"""
|
|
84
|
+
ws = getattr(ctx, "workspace_root", None)
|
|
85
|
+
if not isinstance(ws, str):
|
|
86
|
+
ws = None
|
|
87
|
+
if isinstance(ws, str) and not ws.strip():
|
|
88
|
+
ws = None
|
|
89
|
+
try:
|
|
90
|
+
absolute = cls.expand_to_absolute(raw_cwd, workspace_root=ws)
|
|
91
|
+
except (OSError, ValueError) as exc:
|
|
92
|
+
return None, f"cwd 无法展开为有效绝对路径:{exc}"
|
|
93
|
+
msg = cls.validate_expanded(absolute, workspace_root=ws)
|
|
94
|
+
if msg:
|
|
95
|
+
return None, msg
|
|
96
|
+
return absolute, None
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def apply_agent_cwd_to_data(data: dict[str, Any], ctx: Any) -> ValidationResult | None:
|
|
100
|
+
"""若 ``data`` 含非空 ``cwd``,展开并校验后写回绝对路径;无 cwd 或空串返回 ``None``。
|
|
101
|
+
|
|
102
|
+
供 ``validate_input`` 与 ``invoke``(绕过 pipeline 的测试/脚本路径)共用。
|
|
103
|
+
"""
|
|
104
|
+
raw = data.get("cwd")
|
|
105
|
+
if raw is None:
|
|
106
|
+
return None
|
|
107
|
+
if isinstance(raw, str) and not raw.strip():
|
|
108
|
+
return None
|
|
109
|
+
abs_cwd, err = AgentToolCwdResolution.resolve_and_validate(str(raw), ctx=ctx)
|
|
110
|
+
if err:
|
|
111
|
+
return ValidationResult(ok=False, message=err)
|
|
112
|
+
data["cwd"] = abs_cwd
|
|
113
|
+
return None
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
__all__ = [
|
|
117
|
+
"AgentToolCwdResolution",
|
|
118
|
+
"apply_agent_cwd_to_data",
|
|
119
|
+
]
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""
|
|
2
|
+
tools/agent/limits.py — AgentTool 配额常量
|
|
3
|
+
|
|
4
|
+
职责:集中管理 Agent 工具输出上限、嵌套深度与步数限制,不承载执行逻辑。
|
|
5
|
+
在整体链路中的位置:tool.py 的 check_permissions()/invoke() 读取这些阈值。
|
|
6
|
+
CC 对照:src/tools/AgentTool/AgentTool.tsx 的 maxResultSizeChars=100_000 与深度防护语义。
|
|
7
|
+
当前裁剪范围:v1 全量常量;异步输出目录为 v2 预留。
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import os
|
|
13
|
+
|
|
14
|
+
from langchain_agentx.workspace import resolve_agent_workspace_config
|
|
15
|
+
|
|
16
|
+
MAX_OUTPUT_CHARS: int = int(
|
|
17
|
+
os.getenv("LANGCHAIN_AGENTX_AGENT_TOOL_MAX_OUTPUT_CHARS", "100000")
|
|
18
|
+
)
|
|
19
|
+
MAX_AGENT_DEPTH: int = int(os.getenv("LANGCHAIN_AGENTX_AGENT_TOOL_MAX_DEPTH", "3"))
|
|
20
|
+
_WORKSPACE_CFG = resolve_agent_workspace_config()
|
|
21
|
+
BACKGROUND_OUTPUT_DIR: str = os.path.expanduser(
|
|
22
|
+
os.getenv(
|
|
23
|
+
"LANGCHAIN_AGENTX_AGENT_TOOL_BACKGROUND_OUTPUT_DIR",
|
|
24
|
+
str(_WORKSPACE_CFG.tasks_dir),
|
|
25
|
+
)
|
|
26
|
+
)
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
"""
|
|
2
|
+
tools/agent/loader.py — 用户自定义 Agent 加载器
|
|
3
|
+
|
|
4
|
+
职责:从 Markdown 文件({agent_home}/agents/ 下扩展名 ``.md``,大小写不敏感)加载用户自定义 Agent 配置,
|
|
5
|
+
按优先级与内置原语合并后返回 SubagentRegistry。
|
|
6
|
+
在整体链路中的位置:编排层初始化阶段 → AgentDefinitionLoader.load() → SubagentRegistry
|
|
7
|
+
→ AgentRuntimeTool(registry=...)
|
|
8
|
+
CC 对照:src/tools/AgentTool/loadAgentsDir.ts getAgentDefinitionsWithOverrides()
|
|
9
|
+
+ parseAgentFromMarkdown()
|
|
10
|
+
当前裁剪范围:v1 Markdown 解析 + YAML frontmatter + 优先级合并;
|
|
11
|
+
tools/disallowedTools/maxTurns/background 字段支持;
|
|
12
|
+
agents 目录下 ``.md`` 扩展名 **大小写不敏感**(``*.MD`` / ``*.Md`` 与 ``*.md`` 同等);
|
|
13
|
+
v2 memoization / file watcher
|
|
14
|
+
|
|
15
|
+
加载优先级(后者覆盖前者,与 CC getActiveAgentsFromList 一致):
|
|
16
|
+
built-in < userSettings < projectSettings
|
|
17
|
+
|
|
18
|
+
文件格式(与 CC 一致):
|
|
19
|
+
---
|
|
20
|
+
name: my-agent # 必填
|
|
21
|
+
description: Does XYZ # 必填
|
|
22
|
+
tools: [Read, Glob] # 可选,allowlist(省略=全部)
|
|
23
|
+
disallowedTools: [Edit] # 可选,denylist(优先级高于 tools)
|
|
24
|
+
maxTurns: 30 # 可选,default_max_steps
|
|
25
|
+
background: false # 可选,allow_async
|
|
26
|
+
---
|
|
27
|
+
系统提示词 body...
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
from __future__ import annotations
|
|
31
|
+
|
|
32
|
+
import logging
|
|
33
|
+
from dataclasses import dataclass, field
|
|
34
|
+
from pathlib import Path
|
|
35
|
+
from typing import Any
|
|
36
|
+
|
|
37
|
+
from .registry.config import SubagentConfig
|
|
38
|
+
from .registry.registry import SubagentRegistry
|
|
39
|
+
|
|
40
|
+
logger = logging.getLogger(__name__)
|
|
41
|
+
|
|
42
|
+
# 子 Agent 始终禁止调用 AgentTool 本身(与 backend.py 不变量一致)
|
|
43
|
+
_ALWAYS_DISALLOWED = {"Agent", "agent"}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass
|
|
47
|
+
class AgentLoadResult:
|
|
48
|
+
"""加载结果:成功加载的 config 列表 + 失败文件列表。"""
|
|
49
|
+
configs: list[SubagentConfig] = field(default_factory=list)
|
|
50
|
+
failed_files: list[dict[str, Any]] = field(default_factory=list)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class AgentDefinitionLoader:
|
|
54
|
+
"""从 Markdown 文件加载用户自定义 Agent,按优先级合并到 SubagentRegistry。
|
|
55
|
+
|
|
56
|
+
优先级(后加载覆盖先加载):built-in < userSettings < projectSettings
|
|
57
|
+
CC 对照:loadAgentsDir.ts getAgentDefinitionsWithOverrides()
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
def __init__(
|
|
61
|
+
self,
|
|
62
|
+
workspace_config: Any | None = None,
|
|
63
|
+
base_registry: SubagentRegistry | None = None,
|
|
64
|
+
user_agents_dir: str | Path | None = None,
|
|
65
|
+
) -> None:
|
|
66
|
+
"""
|
|
67
|
+
Args:
|
|
68
|
+
workspace_config: AgentWorkspaceConfig 实例(提供 agents_dir 路径)。
|
|
69
|
+
None 时跳过 projectSettings 加载。
|
|
70
|
+
base_registry: 内置能力原语注册表(built-in 层)。
|
|
71
|
+
user_agents_dir: 用户级 agents 目录(~/.config/langchain_agentx/agents/)。
|
|
72
|
+
None 时跳过 userSettings 加载。
|
|
73
|
+
"""
|
|
74
|
+
self._workspace_config = workspace_config
|
|
75
|
+
self._base_registry = base_registry
|
|
76
|
+
self._user_agents_dir = Path(user_agents_dir) if user_agents_dir else None
|
|
77
|
+
|
|
78
|
+
def load(self) -> SubagentRegistry:
|
|
79
|
+
"""加载所有 Agent 定义,按优先级合并,返回合并后的 SubagentRegistry。
|
|
80
|
+
|
|
81
|
+
合并规则:built-in < userSettings < projectSettings(后写覆盖前写)
|
|
82
|
+
CC 对照:getActiveAgentsFromList() 的 agentMap 合并逻辑
|
|
83
|
+
"""
|
|
84
|
+
merged: dict[str, SubagentConfig] = {}
|
|
85
|
+
|
|
86
|
+
# 1. built-in(最低优先级)
|
|
87
|
+
if self._base_registry:
|
|
88
|
+
for cfg in self._base_registry.list_all():
|
|
89
|
+
merged[cfg.name] = cfg
|
|
90
|
+
|
|
91
|
+
# 2. userSettings
|
|
92
|
+
for cfg in self._load_from_dir(self._user_agents_dir):
|
|
93
|
+
merged[cfg.name] = cfg
|
|
94
|
+
|
|
95
|
+
# 3. projectSettings(最高优先级)
|
|
96
|
+
project_dir = self._get_project_agents_dir()
|
|
97
|
+
for cfg in self._load_from_dir(project_dir):
|
|
98
|
+
merged[cfg.name] = cfg
|
|
99
|
+
|
|
100
|
+
registry = SubagentRegistry()
|
|
101
|
+
for cfg in merged.values():
|
|
102
|
+
registry.register(cfg)
|
|
103
|
+
return registry
|
|
104
|
+
|
|
105
|
+
def _get_project_agents_dir(self) -> Path | None:
|
|
106
|
+
"""从 workspace_config 获取 projectSettings agents 目录。"""
|
|
107
|
+
if self._workspace_config is None:
|
|
108
|
+
return None
|
|
109
|
+
agents_dir = getattr(self._workspace_config, "agents_dir", None)
|
|
110
|
+
return Path(agents_dir) if agents_dir else None
|
|
111
|
+
|
|
112
|
+
def _load_from_dir(self, directory: Path | None) -> list[SubagentConfig]:
|
|
113
|
+
"""从目录加载所有 Markdown agent 文件,返回解析成功的 SubagentConfig 列表。
|
|
114
|
+
|
|
115
|
+
扩展名按 **大小写不敏感** 匹配 ``.md``(Linux 上 ``*.MD`` 与 CC 产品预期一致)。
|
|
116
|
+
目录不存在时安静跳过,不抛异常。
|
|
117
|
+
"""
|
|
118
|
+
if directory is None or not directory.exists():
|
|
119
|
+
return []
|
|
120
|
+
configs: list[SubagentConfig] = []
|
|
121
|
+
for md_file in _iter_markdown_agent_files(directory):
|
|
122
|
+
cfg = self._parse_agent_from_markdown(md_file)
|
|
123
|
+
if cfg is not None:
|
|
124
|
+
configs.append(cfg)
|
|
125
|
+
return configs
|
|
126
|
+
|
|
127
|
+
def _parse_agent_from_markdown(self, file_path: Path) -> SubagentConfig | None:
|
|
128
|
+
"""解析单个 Markdown agent 定义文件。
|
|
129
|
+
CC 对照:parseAgentFromMarkdown()
|
|
130
|
+
|
|
131
|
+
必填:name、description;缺失时静默跳过(可能是参考文档)。
|
|
132
|
+
"""
|
|
133
|
+
try:
|
|
134
|
+
raw = file_path.read_text(encoding="utf-8")
|
|
135
|
+
except OSError as exc:
|
|
136
|
+
logger.warning("Cannot read agent file %s: %s", file_path, exc)
|
|
137
|
+
return None
|
|
138
|
+
|
|
139
|
+
frontmatter, body = self._split_frontmatter(raw)
|
|
140
|
+
if frontmatter is None:
|
|
141
|
+
return None # 无 frontmatter,静默跳过
|
|
142
|
+
|
|
143
|
+
name = frontmatter.get("name")
|
|
144
|
+
description = frontmatter.get("description")
|
|
145
|
+
if not name or not isinstance(name, str):
|
|
146
|
+
return None
|
|
147
|
+
if not description or not isinstance(description, str):
|
|
148
|
+
logger.warning("Agent file %s missing 'description', skipping", file_path)
|
|
149
|
+
return None
|
|
150
|
+
|
|
151
|
+
system_prompt = body.strip()
|
|
152
|
+
tools_raw = frontmatter.get("tools")
|
|
153
|
+
disallowed_raw = frontmatter.get("disallowedTools")
|
|
154
|
+
max_turns = _parse_positive_int(frontmatter.get("maxTurns"))
|
|
155
|
+
allow_async = bool(_parse_bool(frontmatter.get("background")))
|
|
156
|
+
tool_filter = self._build_tool_filter(tools_raw, disallowed_raw)
|
|
157
|
+
tools_description = _describe_tools(tools_raw, disallowed_raw)
|
|
158
|
+
|
|
159
|
+
return SubagentConfig(
|
|
160
|
+
name=name.strip(),
|
|
161
|
+
description=description.strip(),
|
|
162
|
+
system_prompt_factory=lambda sp=system_prompt: sp,
|
|
163
|
+
tool_filter=tool_filter,
|
|
164
|
+
tools_description=tools_description,
|
|
165
|
+
default_max_steps=max_turns,
|
|
166
|
+
allow_async=allow_async,
|
|
167
|
+
tags={"custom"},
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
def _split_frontmatter(self, text: str) -> tuple[dict | None, str]:
|
|
171
|
+
"""分离 YAML frontmatter 与 body。
|
|
172
|
+
格式:--- frontmatter --- body
|
|
173
|
+
CC 对照:markdownConfigLoader.ts frontmatter 分割逻辑
|
|
174
|
+
"""
|
|
175
|
+
if not text.startswith("---"):
|
|
176
|
+
return None, text
|
|
177
|
+
parts = text.split("---", 2)
|
|
178
|
+
if len(parts) < 3:
|
|
179
|
+
return None, text
|
|
180
|
+
try:
|
|
181
|
+
import yaml
|
|
182
|
+
fm = yaml.safe_load(parts[1]) or {}
|
|
183
|
+
except Exception as exc:
|
|
184
|
+
logger.warning("YAML parse error in frontmatter: %s", exc)
|
|
185
|
+
return None, text
|
|
186
|
+
return fm, parts[2]
|
|
187
|
+
|
|
188
|
+
def _build_tool_filter(
|
|
189
|
+
self,
|
|
190
|
+
tools_raw: Any,
|
|
191
|
+
disallowed_raw: Any,
|
|
192
|
+
):
|
|
193
|
+
"""根据 tools/disallowedTools 字段构建 tool_filter 函数。
|
|
194
|
+
CC 对照:parseAgentToolsFromFrontmatter() + filterToolsForAgent()
|
|
195
|
+
denylist 优先级高于 allowlist;AgentTool 本身始终在 denylist。
|
|
196
|
+
"""
|
|
197
|
+
# 解析 allowlist
|
|
198
|
+
if tools_raw is None or tools_raw == ["*"]:
|
|
199
|
+
allowlist: set[str] | None = None # None = 全工具集
|
|
200
|
+
elif isinstance(tools_raw, list):
|
|
201
|
+
allowlist = set(tools_raw)
|
|
202
|
+
else:
|
|
203
|
+
allowlist = None
|
|
204
|
+
|
|
205
|
+
# 解析 denylist(合并用户指定 + 始终禁止)
|
|
206
|
+
if isinstance(disallowed_raw, list):
|
|
207
|
+
denylist = set(disallowed_raw) | _ALWAYS_DISALLOWED
|
|
208
|
+
else:
|
|
209
|
+
denylist = set(_ALWAYS_DISALLOWED)
|
|
210
|
+
|
|
211
|
+
if allowlist is None and denylist == _ALWAYS_DISALLOWED:
|
|
212
|
+
return None # 无过滤,全工具集(backend.py 会移除 AgentTool)
|
|
213
|
+
|
|
214
|
+
def _filter(all_tools: list) -> list:
|
|
215
|
+
result = all_tools if allowlist is None else [
|
|
216
|
+
t for t in all_tools if getattr(t, "name", "") in allowlist
|
|
217
|
+
]
|
|
218
|
+
return [t for t in result if getattr(t, "name", "") not in denylist]
|
|
219
|
+
|
|
220
|
+
return _filter
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
# ── 辅助函数 ──────────────────────────────────────────────────────────────────
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def _iter_markdown_agent_files(directory: Path) -> list[Path]:
|
|
227
|
+
"""枚举 ``directory`` 下扩展名为 ``.md`` 的文件(大小写不敏感),按文件名排序。"""
|
|
228
|
+
if not directory.is_dir():
|
|
229
|
+
return []
|
|
230
|
+
paths: list[Path] = []
|
|
231
|
+
try:
|
|
232
|
+
entries = list(directory.iterdir())
|
|
233
|
+
except OSError as exc:
|
|
234
|
+
logger.warning("Cannot list agents directory %s: %s", directory, exc)
|
|
235
|
+
return []
|
|
236
|
+
for p in entries:
|
|
237
|
+
try:
|
|
238
|
+
if p.is_file() and p.suffix.lower() == ".md":
|
|
239
|
+
paths.append(p)
|
|
240
|
+
except OSError:
|
|
241
|
+
continue
|
|
242
|
+
return sorted(paths, key=lambda x: x.name.lower())
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def _parse_positive_int(raw: Any) -> int | None:
|
|
246
|
+
try:
|
|
247
|
+
v = int(raw)
|
|
248
|
+
return v if v > 0 else None
|
|
249
|
+
except (TypeError, ValueError):
|
|
250
|
+
return None
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def _parse_bool(raw: Any) -> bool | None:
|
|
254
|
+
if raw in (True, "true", "True", 1):
|
|
255
|
+
return True
|
|
256
|
+
if raw in (False, "false", "False", 0):
|
|
257
|
+
return False
|
|
258
|
+
return None
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def _describe_tools(tools_raw: Any, disallowed_raw: Any) -> str:
|
|
262
|
+
if tools_raw is None or tools_raw == ["*"]:
|
|
263
|
+
base = "All tools"
|
|
264
|
+
elif isinstance(tools_raw, list):
|
|
265
|
+
base = ", ".join(tools_raw)
|
|
266
|
+
else:
|
|
267
|
+
base = "All tools"
|
|
268
|
+
if disallowed_raw:
|
|
269
|
+
return f"{base} except {', '.join(disallowed_raw)}"
|
|
270
|
+
return base
|