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,45 @@
|
|
|
1
|
+
"""
|
|
2
|
+
windows_shell_quoting.py — Windows CMD 风格重定向纠偏(POSIX shell 内)
|
|
3
|
+
|
|
4
|
+
职责:
|
|
5
|
+
将误入 Git Bash 的 **`>nul` / `2>nul` / `&>nul`** 等改写为 **`/dev/null`**,避免在当前目录
|
|
6
|
+
生成保留名 **`nul`** 文件(与 CC `utils/bash/shellQuoting.ts::rewriteWindowsNullRedirect` 同思路)。
|
|
7
|
+
|
|
8
|
+
链路位置:
|
|
9
|
+
`BashBackend` 在把用户 `command` 拼进 `-c` 包装脚本 **之前** 调用 **`rewrite_windows_null_redirect`**。
|
|
10
|
+
|
|
11
|
+
当前裁剪范围:
|
|
12
|
+
不解析引号内字面量(与 CC 一致:极少数误伤可接受);非 win32 默认整段 **no-op**。
|
|
13
|
+
|
|
14
|
+
参考:
|
|
15
|
+
anthropics/claude-code 相关 issue(`nul` 保留设备名、`shellQuoting`);设计 SSOT
|
|
16
|
+
docs/design-docs/tool-design/bash-tool-cross-platform.md Phase 3。
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import re
|
|
22
|
+
import sys
|
|
23
|
+
|
|
24
|
+
# 与 CC 描述对齐:可选 fd、`&`、`>` 重复、空白,后跟 **nul** 整词(排除 `null`、`nul.txt` 等)。
|
|
25
|
+
# 前瞻:nul 后须为分隔/行尾/管道等,避免匹配 `nul.txt` 中的 `nul`。
|
|
26
|
+
_NUL_REDIRECT_RE = re.compile(
|
|
27
|
+
r"(\d?&?>+)\s*nul(?=\s|$|[|&;)'\"`]|\\|\n|\r)",
|
|
28
|
+
re.IGNORECASE,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def rewrite_windows_null_redirect(
|
|
33
|
+
command: str,
|
|
34
|
+
*,
|
|
35
|
+
_platform: str | None = None,
|
|
36
|
+
) -> str:
|
|
37
|
+
"""
|
|
38
|
+
将 CMD 风格 `nul` 重定向改为 `/dev/null`。
|
|
39
|
+
|
|
40
|
+
`_platform` 仅供单测注入;默认使用 **`sys.platform`**(仅 **`win32`** 时改写)。
|
|
41
|
+
"""
|
|
42
|
+
plat = sys.platform if _platform is None else _platform
|
|
43
|
+
if plat != "win32":
|
|
44
|
+
return command
|
|
45
|
+
return _NUL_REDIRECT_RE.sub(r"\1/dev/null", command)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""
|
|
2
|
+
tools/glob/models.py — GlobToolInput / GlobToolOutput
|
|
3
|
+
|
|
4
|
+
职责:
|
|
5
|
+
定义 glob 工具面向模型暴露的输入 schema,以及 invoke() 返回的内部输出结构。
|
|
6
|
+
|
|
7
|
+
对应 CC:
|
|
8
|
+
src/tools/GlobTool/GlobTool.ts 中的 inputSchema / outputSchema;
|
|
9
|
+
offset + limit 分页语义对齐 src/utils/glob.ts(v2-C)。
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from pydantic import BaseModel, Field
|
|
15
|
+
|
|
16
|
+
DEFAULT_MAX_RESULTS = 100
|
|
17
|
+
MAX_MAX_RESULTS = 1_000
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class GlobToolInput(BaseModel):
|
|
21
|
+
pattern: str = Field(
|
|
22
|
+
...,
|
|
23
|
+
description=(
|
|
24
|
+
"The glob pattern to match files against. Semantics follow ripgrep --glob "
|
|
25
|
+
'(same family as the grep tool file filter), e.g. "**/*.py", "src/**/*.ts".'
|
|
26
|
+
),
|
|
27
|
+
)
|
|
28
|
+
path: str | None = Field(
|
|
29
|
+
default=None,
|
|
30
|
+
description=(
|
|
31
|
+
"The directory to search in. If omitted, the workspace root is used. "
|
|
32
|
+
'Do not pass "null" or "undefined"; simply omit this field.'
|
|
33
|
+
),
|
|
34
|
+
)
|
|
35
|
+
offset: int = Field(
|
|
36
|
+
default=0,
|
|
37
|
+
ge=0,
|
|
38
|
+
description=(
|
|
39
|
+
"Skip this many matches from the start of the sorted result list "
|
|
40
|
+
"(same semantics as CC globLimits offset; pairs with the tool's max_results limit)."
|
|
41
|
+
),
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class GlobToolOutput(BaseModel):
|
|
46
|
+
"""工具内部结果,供 present() 映射为 ToolResultEnvelope。"""
|
|
47
|
+
|
|
48
|
+
model_config = {"extra": "forbid"}
|
|
49
|
+
|
|
50
|
+
pattern: str
|
|
51
|
+
search_root: str
|
|
52
|
+
filenames: list[str] = Field(default_factory=list)
|
|
53
|
+
num_files: int = 0
|
|
54
|
+
truncated: bool = False
|
|
55
|
+
total_matches: int = 0
|
|
56
|
+
offset: int = 0
|
|
57
|
+
duration_ms: float = 0.0
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""
|
|
2
|
+
tools/glob/pagination.py — 有序路径列表上的 offset/limit 分页
|
|
3
|
+
|
|
4
|
+
职责:
|
|
5
|
+
在已排序的全量路径列表上执行与 CC `utils/glob.ts` 一致的 slice 与 truncated 判定。
|
|
6
|
+
|
|
7
|
+
在整体链路中的位置:
|
|
8
|
+
GlobRuntimeTool.invoke() 在 list_files_via_ripgrep 返回全量列表后调用本模块。
|
|
9
|
+
|
|
10
|
+
当前裁剪范围:
|
|
11
|
+
仅 slice + truncated 布尔;不包含流式截断或磁盘 walk。
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def slice_glob_paths(
|
|
18
|
+
paths: list[str],
|
|
19
|
+
*,
|
|
20
|
+
offset: int,
|
|
21
|
+
limit: int,
|
|
22
|
+
) -> tuple[list[str], int, bool]:
|
|
23
|
+
"""
|
|
24
|
+
与 CC 一致:``truncated = len(paths) > offset + limit``,
|
|
25
|
+
返回 ``paths[offset : offset + limit]``。
|
|
26
|
+
"""
|
|
27
|
+
total = len(paths)
|
|
28
|
+
end = offset + max(1, limit)
|
|
29
|
+
truncated = total > end
|
|
30
|
+
return paths[offset:end], total, truncated
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""
|
|
2
|
+
tools/glob/prompt.py — 工具名与模型可见描述
|
|
3
|
+
|
|
4
|
+
对应 CC:
|
|
5
|
+
src/tools/GlobTool/prompt.ts。
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
TOOL_NAME = "glob"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def get_prompt() -> str:
|
|
14
|
+
return (
|
|
15
|
+
"- Fast file pattern matching tool that works with any codebase size\n"
|
|
16
|
+
'- Supports glob patterns like "**/*.js" or "src/**/*.ts"\n'
|
|
17
|
+
"- Returns matching file paths sorted by modification time\n"
|
|
18
|
+
"- Use this tool when you need to find files by name patterns\n"
|
|
19
|
+
"- When you are doing an open ended search that may require multiple rounds "
|
|
20
|
+
"of globbing and grepping, use the Agent tool instead"
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
DESCRIPTION = get_prompt()
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"""
|
|
2
|
+
tools/glob/rg_list_backend.py — rg --files 列路径(对齐 CC utils/glob.ts)
|
|
3
|
+
|
|
4
|
+
职责:拼装与 CC 一致的 ripgrep 参数并调用 run_ripgrep_lines。
|
|
5
|
+
链路:GlobRuntimeTool.invoke -> list_files_via_ripgrep -> run_ripgrep_lines。
|
|
6
|
+
|
|
7
|
+
裁剪:plugin cache 排除由调用方传入 `plugin_glob_exclusions`(与 CC glob.ts 追加顺序一致)。
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import os
|
|
13
|
+
import threading
|
|
14
|
+
|
|
15
|
+
from langchain_agentx.tools.grep.backend import RipgrepBackend, run_ripgrep_lines
|
|
16
|
+
from langchain_agentx.utils.subprocess_text import (
|
|
17
|
+
TextSubprocessRunner,
|
|
18
|
+
default_text_subprocess_runner,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _read_cc_style_flag(*, langchain_key: str, cc_key: str) -> bool:
|
|
23
|
+
v = os.environ.get(langchain_key)
|
|
24
|
+
if v is None:
|
|
25
|
+
v = os.environ.get(cc_key, "1")
|
|
26
|
+
return v.strip().lower() not in ("", "0", "false", "no", "off")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _ignore_patterns_to_rg_globs(ignore_patterns: list[str] | None) -> list[str]:
|
|
30
|
+
out: list[str] = []
|
|
31
|
+
if not ignore_patterns:
|
|
32
|
+
return out
|
|
33
|
+
for p in ignore_patterns:
|
|
34
|
+
p = str(p).strip()
|
|
35
|
+
if not p:
|
|
36
|
+
continue
|
|
37
|
+
rg_pat = f"!{p}" if p.startswith("/") else f"!**/{p}"
|
|
38
|
+
out.extend(["--glob", rg_pat])
|
|
39
|
+
return out
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def build_rg_files_args(
|
|
43
|
+
relative_glob: str,
|
|
44
|
+
*,
|
|
45
|
+
no_ignore: bool | None = None,
|
|
46
|
+
hidden: bool | None = None,
|
|
47
|
+
ignore_patterns: list[str] | None = None,
|
|
48
|
+
plugin_glob_exclusions: list[str] | None = None,
|
|
49
|
+
) -> list[str]:
|
|
50
|
+
"""
|
|
51
|
+
对齐 CC glob.ts:--files --glob <pattern> --sort=modified,及默认 --no-ignore / --hidden。
|
|
52
|
+
环境变量(与 CC 名称兼容,便于本机已有配置):
|
|
53
|
+
CLAUDE_CODE_GLOB_NO_IGNORE / LANGCHAIN_AGENTX_GLOB_NO_IGNORE
|
|
54
|
+
CLAUDE_CODE_GLOB_HIDDEN / LANGCHAIN_AGENTX_GLOB_HIDDEN
|
|
55
|
+
未设置时默认 true(与 CC 一致)。
|
|
56
|
+
"""
|
|
57
|
+
if no_ignore is None:
|
|
58
|
+
no_ignore = _read_cc_style_flag(
|
|
59
|
+
langchain_key="LANGCHAIN_AGENTX_GLOB_NO_IGNORE",
|
|
60
|
+
cc_key="CLAUDE_CODE_GLOB_NO_IGNORE",
|
|
61
|
+
)
|
|
62
|
+
if hidden is None:
|
|
63
|
+
hidden = _read_cc_style_flag(
|
|
64
|
+
langchain_key="LANGCHAIN_AGENTX_GLOB_HIDDEN",
|
|
65
|
+
cc_key="CLAUDE_CODE_GLOB_HIDDEN",
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
args: list[str] = [
|
|
69
|
+
"--files",
|
|
70
|
+
"--glob",
|
|
71
|
+
relative_glob,
|
|
72
|
+
"--sort=modified",
|
|
73
|
+
]
|
|
74
|
+
if no_ignore:
|
|
75
|
+
args.append("--no-ignore")
|
|
76
|
+
if hidden:
|
|
77
|
+
args.append("--hidden")
|
|
78
|
+
for d in RipgrepBackend.VCS_EXCLUDE_DIRS:
|
|
79
|
+
args.extend(["--glob", f"!{d}"])
|
|
80
|
+
args.extend(_ignore_patterns_to_rg_globs(ignore_patterns))
|
|
81
|
+
if plugin_glob_exclusions:
|
|
82
|
+
for ex in plugin_glob_exclusions:
|
|
83
|
+
if ex:
|
|
84
|
+
args.extend(["--glob", ex])
|
|
85
|
+
return args
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def list_files_via_ripgrep(
|
|
89
|
+
search_root: str,
|
|
90
|
+
relative_glob: str,
|
|
91
|
+
*,
|
|
92
|
+
rg_path: str,
|
|
93
|
+
timeout: int,
|
|
94
|
+
ignore_patterns: list[str] | None = None,
|
|
95
|
+
plugin_glob_exclusions: list[str] | None = None,
|
|
96
|
+
text_runner: TextSubprocessRunner | None = None,
|
|
97
|
+
cancel_event: threading.Event | None = None,
|
|
98
|
+
) -> list[str]:
|
|
99
|
+
"""
|
|
100
|
+
在 search_root 下执行 rg,返回**绝对路径**列表(仅文件),路径已 normpath。
|
|
101
|
+
stdout 行为与 CC 一致:相对 search_root 的相对路径。
|
|
102
|
+
"""
|
|
103
|
+
runner = text_runner or default_text_subprocess_runner()
|
|
104
|
+
args = build_rg_files_args(
|
|
105
|
+
relative_glob,
|
|
106
|
+
ignore_patterns=ignore_patterns,
|
|
107
|
+
plugin_glob_exclusions=plugin_glob_exclusions,
|
|
108
|
+
)
|
|
109
|
+
lines = run_ripgrep_lines(
|
|
110
|
+
rg_path,
|
|
111
|
+
args,
|
|
112
|
+
search_root,
|
|
113
|
+
timeout=timeout,
|
|
114
|
+
text_runner=runner,
|
|
115
|
+
is_retry=False,
|
|
116
|
+
cancel_event=cancel_event,
|
|
117
|
+
)
|
|
118
|
+
root = os.path.realpath(search_root)
|
|
119
|
+
files: list[str] = []
|
|
120
|
+
seen: set[str] = set()
|
|
121
|
+
for ln in lines:
|
|
122
|
+
p = ln.strip()
|
|
123
|
+
if not p:
|
|
124
|
+
continue
|
|
125
|
+
full = os.path.normpath(p if os.path.isabs(p) else os.path.join(root, p))
|
|
126
|
+
if full in seen:
|
|
127
|
+
continue
|
|
128
|
+
if os.path.isfile(full):
|
|
129
|
+
seen.add(full)
|
|
130
|
+
files.append(full)
|
|
131
|
+
# 对齐 CC:--sort=modified 升序;同 mtime 时按路径稳定次序
|
|
132
|
+
def _mtime_key(path: str) -> tuple[float, str]:
|
|
133
|
+
try:
|
|
134
|
+
return (os.path.getmtime(path), path)
|
|
135
|
+
except OSError:
|
|
136
|
+
return (0.0, path)
|
|
137
|
+
|
|
138
|
+
files.sort(key=_mtime_key)
|
|
139
|
+
return files
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""
|
|
2
|
+
tools/glob/rg_pattern.py — 绝对 glob pattern 拆分为搜索根 + 相对 rg --glob
|
|
3
|
+
|
|
4
|
+
职责:移植 CC src/utils/glob.ts 的 extractGlobBaseDirectory,供 Glob 与 rg --glob 对齐。
|
|
5
|
+
链路:GlobRuntimeTool.invoke -> extract_glob_base_directory。
|
|
6
|
+
|
|
7
|
+
裁剪:仅服务于本工具路径语义;不含 CC 的 getPlatform 以外的逻辑。
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import os
|
|
13
|
+
import re
|
|
14
|
+
import sys
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def extract_glob_base_directory(pattern: str) -> tuple[str, str]:
|
|
18
|
+
"""
|
|
19
|
+
返回 (base_dir, relative_pattern)。
|
|
20
|
+
ripgrep 的 --glob 在作为「目录扫描」参数链中需配合「相对 pattern」使用,与 CC 一致。
|
|
21
|
+
"""
|
|
22
|
+
sep = os.sep
|
|
23
|
+
glob_chars = re.compile(r"[*?[{]")
|
|
24
|
+
match = glob_chars.search(pattern)
|
|
25
|
+
if not match:
|
|
26
|
+
d = os.path.dirname(pattern)
|
|
27
|
+
f = os.path.basename(pattern)
|
|
28
|
+
return (d, f)
|
|
29
|
+
|
|
30
|
+
static_prefix = pattern[: match.start()]
|
|
31
|
+
last_sep = max(static_prefix.rfind("/"), static_prefix.rfind("\\"))
|
|
32
|
+
if last_sep < 0:
|
|
33
|
+
return ("", pattern)
|
|
34
|
+
|
|
35
|
+
base_dir = static_prefix[:last_sep]
|
|
36
|
+
relative_pattern = pattern[last_sep + 1 :]
|
|
37
|
+
|
|
38
|
+
if base_dir == "" and last_sep == 0:
|
|
39
|
+
base_dir = "/"
|
|
40
|
+
|
|
41
|
+
if sys.platform == "win32" and len(base_dir) == 2 and base_dir[1] == ":" and base_dir[0].isalpha():
|
|
42
|
+
base_dir = base_dir + sep
|
|
43
|
+
|
|
44
|
+
return (base_dir, relative_pattern)
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
"""
|
|
2
|
+
职责:
|
|
3
|
+
GlobRuntimeTool — 按文件名模式列路径(系统 ripgrep:`rg --files`,对齐 CC utils/glob.ts)。
|
|
4
|
+
实现 RuntimeTool 生命周期;不负责读文件内容(Read)与内容搜索(Grep)。
|
|
5
|
+
|
|
6
|
+
在整体链路中的位置:
|
|
7
|
+
ToolExecutorPipeline -> GlobRuntimeTool.invoke()
|
|
8
|
+
-> rg_list_backend.list_files_via_ripgrep(..., cancel_event=ctx.cancel_event)
|
|
9
|
+
-> GlobToolOutput -> after_invoke(可选写入 ToolStateBridge.last_glob_summary)
|
|
10
|
+
-> present() -> ToolResultEnvelope
|
|
11
|
+
|
|
12
|
+
与 CC 对照:
|
|
13
|
+
对应 CC src/tools/GlobTool/GlobTool.ts + src/utils/glob.ts。
|
|
14
|
+
仅使用系统 PATH 中的 ripgrep:Windows 默认 `rg.exe`,Unix 默认 `rg`(见 utils.rg_executable)。
|
|
15
|
+
validate_input 对 UNC / ``//`` 前缀跳过本地 stat,与 CC 一致(utils.unc_path)。
|
|
16
|
+
不使用 Python 标准库 glob,避免与 ripgrep --glob 两套语法。
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import os
|
|
22
|
+
import time
|
|
23
|
+
from typing import Any
|
|
24
|
+
|
|
25
|
+
from langchain_agentx.tool_runtime.base import RuntimeTool
|
|
26
|
+
from langchain_agentx.tool_runtime.models import (
|
|
27
|
+
AuthorizationDecision,
|
|
28
|
+
ToolExecutionContext,
|
|
29
|
+
ToolHint,
|
|
30
|
+
ToolResultEnvelope,
|
|
31
|
+
ValidationResult,
|
|
32
|
+
)
|
|
33
|
+
from langchain_agentx.tool_runtime.state_bridge import ToolStateBridge
|
|
34
|
+
from langchain_agentx.tools.grep.backend import GrepExecutionError, RipgrepBackend
|
|
35
|
+
from langchain_agentx.tools.ripgrep_plugin_exclusions import PluginCacheGlobExclusions
|
|
36
|
+
from langchain_agentx.utils.path_user_input import UserPathInputNormalizer
|
|
37
|
+
from langchain_agentx.utils.rg_executable import default_rg_executable
|
|
38
|
+
from langchain_agentx.utils.unc_path import is_unc_path_skip_local_stat
|
|
39
|
+
|
|
40
|
+
from .models import DEFAULT_MAX_RESULTS, MAX_MAX_RESULTS, GlobToolInput, GlobToolOutput
|
|
41
|
+
from .pagination import slice_glob_paths
|
|
42
|
+
from .prompt import DESCRIPTION, TOOL_NAME
|
|
43
|
+
from .rg_list_backend import list_files_via_ripgrep
|
|
44
|
+
from .rg_pattern import extract_glob_base_directory
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _has_path_traversal(pattern: str) -> bool:
|
|
48
|
+
normalized = pattern.replace("\\", "/")
|
|
49
|
+
parts = [part for part in normalized.split("/") if part]
|
|
50
|
+
return any(part == ".." for part in parts)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _is_descendant(child: str, parent: str) -> bool:
|
|
54
|
+
try:
|
|
55
|
+
c = os.path.realpath(child)
|
|
56
|
+
p = os.path.realpath(parent)
|
|
57
|
+
return os.path.commonpath([c, p]) == p
|
|
58
|
+
except ValueError:
|
|
59
|
+
return False
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class GlobRuntimeTool(RuntimeTool):
|
|
63
|
+
name: str = TOOL_NAME
|
|
64
|
+
description: str = DESCRIPTION
|
|
65
|
+
input_model = GlobToolInput
|
|
66
|
+
output_model = GlobToolOutput
|
|
67
|
+
|
|
68
|
+
is_read_only: bool = True
|
|
69
|
+
is_concurrency_safe: bool = True
|
|
70
|
+
never_truncate: bool = False
|
|
71
|
+
dedupe_identical_calls: bool = True
|
|
72
|
+
max_result_size_chars: int = 100_000
|
|
73
|
+
|
|
74
|
+
def __init__(
|
|
75
|
+
self,
|
|
76
|
+
*,
|
|
77
|
+
workspace_root: str,
|
|
78
|
+
max_results: int = DEFAULT_MAX_RESULTS,
|
|
79
|
+
policy: Any | None = None,
|
|
80
|
+
state_bridge: Any | None = None,
|
|
81
|
+
path_normalizer: UserPathInputNormalizer | None = None,
|
|
82
|
+
rg_path: str | None = None,
|
|
83
|
+
rg_timeout: int = RipgrepBackend.DEFAULT_TIMEOUT,
|
|
84
|
+
ignore_patterns: list[str] | None = None,
|
|
85
|
+
plugin_cache_glob_exclusions: PluginCacheGlobExclusions | None = None,
|
|
86
|
+
) -> None:
|
|
87
|
+
super().__init__(policy=policy, state_bridge=state_bridge)
|
|
88
|
+
self._workspace_root = os.path.realpath(os.path.expanduser(workspace_root))
|
|
89
|
+
self.max_results = max(1, min(int(max_results), MAX_MAX_RESULTS))
|
|
90
|
+
self._path_normalizer = path_normalizer or UserPathInputNormalizer()
|
|
91
|
+
self._rg_path = rg_path if rg_path is not None else default_rg_executable()
|
|
92
|
+
self._rg_timeout = int(rg_timeout)
|
|
93
|
+
self._ignore_patterns = [p for p in (ignore_patterns or []) if str(p).strip()]
|
|
94
|
+
self._plugin_cache_glob_exclusions = plugin_cache_glob_exclusions
|
|
95
|
+
|
|
96
|
+
def normalize_input(
|
|
97
|
+
self,
|
|
98
|
+
raw: dict[str, Any],
|
|
99
|
+
ctx: ToolExecutionContext,
|
|
100
|
+
) -> dict[str, Any]:
|
|
101
|
+
data = dict(raw)
|
|
102
|
+
pattern = data.get("pattern")
|
|
103
|
+
if isinstance(pattern, str):
|
|
104
|
+
data["pattern"] = pattern.strip()
|
|
105
|
+
|
|
106
|
+
path = data.get("path")
|
|
107
|
+
from langchain_agentx.utils.cwd import get_cwd
|
|
108
|
+
|
|
109
|
+
if isinstance(path, str) and path.strip():
|
|
110
|
+
data["path"] = self._path_normalizer.normalize_and_expand(
|
|
111
|
+
path, base_dir=ctx.cwd
|
|
112
|
+
)
|
|
113
|
+
else:
|
|
114
|
+
data["path"] = ctx.cwd or get_cwd()
|
|
115
|
+
return data
|
|
116
|
+
|
|
117
|
+
def validate_input(
|
|
118
|
+
self,
|
|
119
|
+
data: dict[str, Any],
|
|
120
|
+
ctx: ToolExecutionContext,
|
|
121
|
+
) -> ValidationResult:
|
|
122
|
+
pattern = str(data.get("pattern") or "").strip()
|
|
123
|
+
if not pattern:
|
|
124
|
+
return ValidationResult(
|
|
125
|
+
ok=False,
|
|
126
|
+
message="Pattern cannot be empty",
|
|
127
|
+
code="EMPTY_PATTERN",
|
|
128
|
+
)
|
|
129
|
+
if _has_path_traversal(pattern):
|
|
130
|
+
return ValidationResult(
|
|
131
|
+
ok=False,
|
|
132
|
+
message="Pattern must not contain path traversal sequences",
|
|
133
|
+
code="PATH_TRAVERSAL",
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
search_root = str(data["path"])
|
|
137
|
+
# CC validateInput:UNC / // 前缀跳过本地 stat(与 Grep 一致)
|
|
138
|
+
if is_unc_path_skip_local_stat(search_root):
|
|
139
|
+
return ValidationResult(ok=True)
|
|
140
|
+
if not os.path.exists(search_root):
|
|
141
|
+
return ValidationResult(
|
|
142
|
+
ok=False,
|
|
143
|
+
message=(
|
|
144
|
+
f"Directory does not exist: {search_root}. "
|
|
145
|
+
f"Current workspace: {self._workspace_root}"
|
|
146
|
+
),
|
|
147
|
+
code="DIR_NOT_FOUND",
|
|
148
|
+
)
|
|
149
|
+
if not os.path.isdir(search_root):
|
|
150
|
+
return ValidationResult(
|
|
151
|
+
ok=False,
|
|
152
|
+
message=f"Path is not a directory: {search_root}",
|
|
153
|
+
code="NOT_A_DIRECTORY",
|
|
154
|
+
)
|
|
155
|
+
return ValidationResult(ok=True)
|
|
156
|
+
|
|
157
|
+
def check_permissions(
|
|
158
|
+
self,
|
|
159
|
+
data: dict[str, Any],
|
|
160
|
+
ctx: ToolExecutionContext,
|
|
161
|
+
) -> AuthorizationDecision:
|
|
162
|
+
return super().check_permissions(data, ctx)
|
|
163
|
+
|
|
164
|
+
def after_invoke(
|
|
165
|
+
self,
|
|
166
|
+
data: dict[str, Any],
|
|
167
|
+
result: Any,
|
|
168
|
+
ctx: ToolExecutionContext,
|
|
169
|
+
) -> None:
|
|
170
|
+
"""
|
|
171
|
+
v2-D:若构造时注入 ``ToolStateBridge``,将本次 Glob 摘要写入 ``ctx.state``
|
|
172
|
+
(供 ``get_last_glob_summary`` 读取;**不**参与 PolicyEngine)。
|
|
173
|
+
"""
|
|
174
|
+
if self._state_bridge is None or not isinstance(self._state_bridge, ToolStateBridge):
|
|
175
|
+
return
|
|
176
|
+
if not isinstance(result, GlobToolOutput):
|
|
177
|
+
return
|
|
178
|
+
self._state_bridge.record_last_glob(
|
|
179
|
+
ctx,
|
|
180
|
+
pattern=result.pattern,
|
|
181
|
+
search_root=result.search_root,
|
|
182
|
+
filenames=result.filenames,
|
|
183
|
+
num_files_page=result.num_files,
|
|
184
|
+
total_matches=result.total_matches,
|
|
185
|
+
truncated=result.truncated,
|
|
186
|
+
offset=result.offset,
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
def invoke(
|
|
190
|
+
self,
|
|
191
|
+
data: dict[str, Any],
|
|
192
|
+
ctx: ToolExecutionContext,
|
|
193
|
+
) -> GlobToolOutput:
|
|
194
|
+
pattern = str(data["pattern"])
|
|
195
|
+
search_root = os.path.realpath(str(data["path"]))
|
|
196
|
+
started = time.perf_counter()
|
|
197
|
+
|
|
198
|
+
relative_glob = pattern
|
|
199
|
+
if os.path.isabs(pattern):
|
|
200
|
+
base_dir, relative_glob = extract_glob_base_directory(pattern)
|
|
201
|
+
if base_dir:
|
|
202
|
+
candidate = os.path.realpath(os.path.expanduser(base_dir))
|
|
203
|
+
if not _is_descendant(candidate, self._workspace_root):
|
|
204
|
+
raise GrepExecutionError(
|
|
205
|
+
"Glob pattern resolves to a directory outside the workspace root."
|
|
206
|
+
)
|
|
207
|
+
search_root = candidate
|
|
208
|
+
|
|
209
|
+
plugin_ex: list[str] | None = None
|
|
210
|
+
if self._plugin_cache_glob_exclusions is not None:
|
|
211
|
+
plugin_ex = self._plugin_cache_glob_exclusions.get_exclusions_for_search(
|
|
212
|
+
search_root
|
|
213
|
+
)
|
|
214
|
+
if not plugin_ex:
|
|
215
|
+
plugin_ex = None
|
|
216
|
+
|
|
217
|
+
try:
|
|
218
|
+
files_full = list_files_via_ripgrep(
|
|
219
|
+
search_root,
|
|
220
|
+
relative_glob,
|
|
221
|
+
rg_path=self._rg_path,
|
|
222
|
+
timeout=self._rg_timeout,
|
|
223
|
+
ignore_patterns=self._ignore_patterns or None,
|
|
224
|
+
plugin_glob_exclusions=plugin_ex,
|
|
225
|
+
cancel_event=ctx.cancel_event,
|
|
226
|
+
)
|
|
227
|
+
except GrepExecutionError:
|
|
228
|
+
raise
|
|
229
|
+
except OSError as e:
|
|
230
|
+
raise GrepExecutionError(f"Glob failed while resolving paths: {e}") from e
|
|
231
|
+
|
|
232
|
+
page_offset = int(data["offset"])
|
|
233
|
+
files, total_matches, truncated = slice_glob_paths(
|
|
234
|
+
files_full,
|
|
235
|
+
offset=page_offset,
|
|
236
|
+
limit=self.max_results,
|
|
237
|
+
)
|
|
238
|
+
relative_files = [self._to_relative(path) for path in files]
|
|
239
|
+
|
|
240
|
+
duration_ms = (time.perf_counter() - started) * 1000.0
|
|
241
|
+
return GlobToolOutput(
|
|
242
|
+
pattern=pattern,
|
|
243
|
+
search_root=search_root,
|
|
244
|
+
filenames=relative_files,
|
|
245
|
+
num_files=len(relative_files),
|
|
246
|
+
truncated=truncated,
|
|
247
|
+
total_matches=total_matches,
|
|
248
|
+
offset=page_offset,
|
|
249
|
+
duration_ms=duration_ms,
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
def present(
|
|
253
|
+
self,
|
|
254
|
+
data: dict[str, Any],
|
|
255
|
+
result: Any,
|
|
256
|
+
ctx: ToolExecutionContext,
|
|
257
|
+
) -> ToolResultEnvelope:
|
|
258
|
+
if not isinstance(result, GlobToolOutput):
|
|
259
|
+
return ToolResultEnvelope(
|
|
260
|
+
status="ok",
|
|
261
|
+
tool_name=self.name,
|
|
262
|
+
summary=str(result),
|
|
263
|
+
payload=str(result),
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
meta = {
|
|
267
|
+
"pattern": result.pattern,
|
|
268
|
+
"search_root": result.search_root,
|
|
269
|
+
"num_files": result.num_files,
|
|
270
|
+
"truncated": result.truncated,
|
|
271
|
+
"total_matches": result.total_matches,
|
|
272
|
+
"offset": result.offset,
|
|
273
|
+
"duration_ms": round(result.duration_ms, 2),
|
|
274
|
+
"glob_backend": "ripgrep",
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if result.num_files == 0:
|
|
278
|
+
if result.total_matches == 0:
|
|
279
|
+
summary = "No files found"
|
|
280
|
+
else:
|
|
281
|
+
summary = (
|
|
282
|
+
f"No files in this page (offset {result.offset}, "
|
|
283
|
+
f"{result.total_matches} total match(es))"
|
|
284
|
+
)
|
|
285
|
+
return ToolResultEnvelope(
|
|
286
|
+
status="ok",
|
|
287
|
+
tool_name=self.name,
|
|
288
|
+
summary=summary,
|
|
289
|
+
payload={
|
|
290
|
+
"filenames": [],
|
|
291
|
+
"num_files": 0,
|
|
292
|
+
"truncated": result.truncated,
|
|
293
|
+
"total_matches": result.total_matches,
|
|
294
|
+
"offset": result.offset,
|
|
295
|
+
},
|
|
296
|
+
meta=meta,
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
summary = f"Found {result.num_files} file(s) matching '{result.pattern}'"
|
|
300
|
+
payload_lines = list(result.filenames)
|
|
301
|
+
hints: list[ToolHint] | None = None
|
|
302
|
+
if result.truncated:
|
|
303
|
+
summary += (
|
|
304
|
+
f" (results truncated at {self.max_results}"
|
|
305
|
+
+ (f", offset {result.offset}" if result.offset else "")
|
|
306
|
+
+ ")"
|
|
307
|
+
)
|
|
308
|
+
hint_msg = (
|
|
309
|
+
"Results are truncated. Consider using a more specific path or pattern."
|
|
310
|
+
)
|
|
311
|
+
payload_lines.append(f"({hint_msg})")
|
|
312
|
+
hints = [ToolHint(message=hint_msg)]
|
|
313
|
+
|
|
314
|
+
return ToolResultEnvelope(
|
|
315
|
+
status="ok",
|
|
316
|
+
tool_name=self.name,
|
|
317
|
+
summary=summary,
|
|
318
|
+
payload="\n".join(payload_lines),
|
|
319
|
+
hints=hints,
|
|
320
|
+
meta=meta,
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
def _to_relative(self, path: str) -> str:
|
|
324
|
+
try:
|
|
325
|
+
return os.path.relpath(path, self._workspace_root)
|
|
326
|
+
except ValueError:
|
|
327
|
+
return path
|