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,222 @@
|
|
|
1
|
+
"""
|
|
2
|
+
tools/skill/tool.py — SkillRuntimeTool 主编排器
|
|
3
|
+
|
|
4
|
+
职责:
|
|
5
|
+
在 RuntimeTool 生命周期内执行 skill:
|
|
6
|
+
normalize_input -> validate_input -> check_permissions -> invoke -> present
|
|
7
|
+
仅负责 orchestration,不承载 frontmatter 解析/展开细节。
|
|
8
|
+
|
|
9
|
+
链路位置:
|
|
10
|
+
ToolExecutorPipeline.run()
|
|
11
|
+
-> SkillRuntimeTool hooks
|
|
12
|
+
-> ToolResultEnvelope
|
|
13
|
+
|
|
14
|
+
CC 对照:
|
|
15
|
+
对齐 SkillTool 的 command lookup + args expand + metadata 透传语义,
|
|
16
|
+
但保持本工程 RuntimeTool 契约(标准 ToolMessage 路径)。
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from typing import Any
|
|
23
|
+
|
|
24
|
+
from langchain_agentx.tool_runtime.base import RuntimeTool
|
|
25
|
+
from langchain_agentx.tool_runtime.models import (
|
|
26
|
+
AuthorizationDecision,
|
|
27
|
+
ToolExecutionContext,
|
|
28
|
+
ToolResultEnvelope,
|
|
29
|
+
ValidationResult,
|
|
30
|
+
)
|
|
31
|
+
from langchain_agentx.workspace import resolve_agent_workspace_config
|
|
32
|
+
|
|
33
|
+
from .loader import SkillCommandRepository, SkillContentExpander, normalize_skill_name
|
|
34
|
+
from .models import SkillToolInput, SkillToolOutput, _frontmatter_bool
|
|
35
|
+
from .policy import SkillConstraintEvaluator
|
|
36
|
+
from .prompt import COMMAND_NAME_TAG, DESCRIPTION, TOOL_NAME
|
|
37
|
+
from ..agent.scope import resolve_effective_tools
|
|
38
|
+
from ...tool_runtime.permission_context import set_pending_auto_approved_tools
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class SkillRuntimeTool(RuntimeTool):
|
|
42
|
+
name: str = TOOL_NAME
|
|
43
|
+
description: str = DESCRIPTION
|
|
44
|
+
input_model = SkillToolInput
|
|
45
|
+
output_model = SkillToolOutput
|
|
46
|
+
|
|
47
|
+
is_read_only: bool = True
|
|
48
|
+
is_concurrency_safe: bool = True
|
|
49
|
+
never_truncate: bool = False
|
|
50
|
+
max_result_size_chars: int = 60_000
|
|
51
|
+
|
|
52
|
+
def __init__(
|
|
53
|
+
self,
|
|
54
|
+
*,
|
|
55
|
+
workspace_root: str,
|
|
56
|
+
agent_home: str = ".langchain_agentx",
|
|
57
|
+
skills_root: str | None = None,
|
|
58
|
+
policy: Any | None = None,
|
|
59
|
+
state_bridge: Any | None = None,
|
|
60
|
+
repository: SkillCommandRepository | None = None,
|
|
61
|
+
expander: SkillContentExpander | None = None,
|
|
62
|
+
constraint_evaluator: SkillConstraintEvaluator | None = None,
|
|
63
|
+
) -> None:
|
|
64
|
+
super().__init__(policy=policy, state_bridge=state_bridge)
|
|
65
|
+
workspace_cfg = resolve_agent_workspace_config(
|
|
66
|
+
workspace_root=workspace_root,
|
|
67
|
+
agent_home=agent_home,
|
|
68
|
+
)
|
|
69
|
+
self._workspace_root = workspace_cfg.workspace_root
|
|
70
|
+
self._agent_home = agent_home
|
|
71
|
+
self._skills_root = (
|
|
72
|
+
Path(skills_root).expanduser().resolve()
|
|
73
|
+
if skills_root
|
|
74
|
+
else workspace_cfg.skills_dir
|
|
75
|
+
)
|
|
76
|
+
self._repository = repository or SkillCommandRepository(self._skills_root)
|
|
77
|
+
self._expander = expander or SkillContentExpander()
|
|
78
|
+
self._constraint_evaluator = constraint_evaluator or SkillConstraintEvaluator(self._workspace_root)
|
|
79
|
+
|
|
80
|
+
def normalize_input(self, raw: dict[str, Any], ctx: ToolExecutionContext) -> dict[str, Any]:
|
|
81
|
+
data = dict(raw)
|
|
82
|
+
data["skill_name"] = normalize_skill_name(str(data.get("skill_name", "")))
|
|
83
|
+
return data
|
|
84
|
+
|
|
85
|
+
def validate_input(self, data: dict[str, Any], ctx: ToolExecutionContext) -> ValidationResult:
|
|
86
|
+
inp = SkillToolInput.model_validate(data)
|
|
87
|
+
if not inp.skill_name:
|
|
88
|
+
return ValidationResult(ok=False, message="skill_name cannot be empty")
|
|
89
|
+
cmd = self._repository.find_by_name(inp.skill_name)
|
|
90
|
+
if cmd is None:
|
|
91
|
+
return ValidationResult(
|
|
92
|
+
ok=False,
|
|
93
|
+
message=f"Unknown skill: {inp.skill_name}",
|
|
94
|
+
code="SKILL_NOT_FOUND",
|
|
95
|
+
)
|
|
96
|
+
if _frontmatter_bool(
|
|
97
|
+
cmd.frontmatter.get("disable-model-invocation")
|
|
98
|
+
or cmd.frontmatter.get("disable_model_invocation")
|
|
99
|
+
):
|
|
100
|
+
return ValidationResult(
|
|
101
|
+
ok=False,
|
|
102
|
+
message=(
|
|
103
|
+
f"Skill {inp.skill_name} cannot be used with {self.name} tool "
|
|
104
|
+
"due to disable-model-invocation"
|
|
105
|
+
),
|
|
106
|
+
code="SKILL_DISABLE_MODEL_INVOCATION",
|
|
107
|
+
)
|
|
108
|
+
return ValidationResult(ok=True)
|
|
109
|
+
|
|
110
|
+
def check_permissions(self, data: dict[str, Any], ctx: ToolExecutionContext) -> AuthorizationDecision:
|
|
111
|
+
# 先走 skill frontmatter 约束,再委托全局 PolicyEngine。
|
|
112
|
+
inp = SkillToolInput.model_validate(data)
|
|
113
|
+
cmd = self._repository.find_by_name(inp.skill_name)
|
|
114
|
+
if cmd is None:
|
|
115
|
+
return AuthorizationDecision(
|
|
116
|
+
behavior="deny",
|
|
117
|
+
message=f"Permission denied: unknown skill {inp.skill_name}",
|
|
118
|
+
policy_id="skill_not_found",
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
local = self._constraint_evaluator.evaluate(cmd.frontmatter)
|
|
122
|
+
if local.behavior != "allow":
|
|
123
|
+
return local
|
|
124
|
+
|
|
125
|
+
return super().check_permissions(data, ctx)
|
|
126
|
+
|
|
127
|
+
def invoke(self, data: dict[str, Any], ctx: ToolExecutionContext) -> SkillToolOutput:
|
|
128
|
+
inp = SkillToolInput.model_validate(data)
|
|
129
|
+
cmd = self._repository.find_by_name(inp.skill_name)
|
|
130
|
+
if cmd is None:
|
|
131
|
+
raise ValueError(f"Unknown skill: {inp.skill_name}")
|
|
132
|
+
|
|
133
|
+
content = self._expander.expand(cmd, inp.args)
|
|
134
|
+
frontmatter = cmd.frontmatter
|
|
135
|
+
allowed_tools = frontmatter.get("allowed-tools")
|
|
136
|
+
model_override = frontmatter.get("model")
|
|
137
|
+
constraints_applied = {
|
|
138
|
+
"has_allowed_tools": isinstance(allowed_tools, list) and len(allowed_tools) > 0,
|
|
139
|
+
"has_paths": isinstance(frontmatter.get("paths"), list) and len(frontmatter.get("paths")) > 0,
|
|
140
|
+
"has_model_override": isinstance(model_override, str) and bool(model_override.strip()),
|
|
141
|
+
}
|
|
142
|
+
# Tool scope 计算入口:先做纯计算并透出 explain,后续阶段再接 permission context 写回。
|
|
143
|
+
resolved_scope = resolve_effective_tools(
|
|
144
|
+
candidate_tools=list(getattr(ctx, "available_tools", []) or []),
|
|
145
|
+
agent_config=None,
|
|
146
|
+
skill_allowed_tools=[str(x) for x in allowed_tools] if isinstance(allowed_tools, list) else [],
|
|
147
|
+
parent_auto_approved=None,
|
|
148
|
+
global_deny=None,
|
|
149
|
+
)
|
|
150
|
+
constraints_applied["tool_scope_resolution"] = {
|
|
151
|
+
"auto_approved": sorted(resolved_scope.auto_approved),
|
|
152
|
+
"final_allowed": sorted(resolved_scope.final_allowed),
|
|
153
|
+
"explain": list(resolved_scope.explain),
|
|
154
|
+
}
|
|
155
|
+
set_pending_auto_approved_tools(ctx.session_store, resolved_scope.auto_approved)
|
|
156
|
+
return SkillToolOutput(
|
|
157
|
+
skill_name=cmd.name,
|
|
158
|
+
content=content,
|
|
159
|
+
source=cmd.source,
|
|
160
|
+
skill_path=str(cmd.path),
|
|
161
|
+
command_name_tag=f"<{COMMAND_NAME_TAG}>{cmd.name}</{COMMAND_NAME_TAG}>",
|
|
162
|
+
allowed_tools=[str(x) for x in allowed_tools] if isinstance(allowed_tools, list) else [],
|
|
163
|
+
model_override=str(model_override) if isinstance(model_override, str) else None,
|
|
164
|
+
constraints_applied=constraints_applied,
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
def present(self, data: dict[str, Any], result: Any, ctx: ToolExecutionContext) -> ToolResultEnvelope:
|
|
168
|
+
if not isinstance(result, SkillToolOutput):
|
|
169
|
+
return ToolResultEnvelope(
|
|
170
|
+
status="ok",
|
|
171
|
+
tool_name=self.name,
|
|
172
|
+
summary=f"Loaded skill {data.get('skill_name', '')}",
|
|
173
|
+
payload=str(result),
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
summary = f"Loaded skill: {result.skill_name}"
|
|
177
|
+
payload = (
|
|
178
|
+
f"{result.command_name_tag or ''}\n"
|
|
179
|
+
f"[skill:{result.skill_name}]\n\n"
|
|
180
|
+
f"{result.content}"
|
|
181
|
+
).strip()
|
|
182
|
+
return ToolResultEnvelope(
|
|
183
|
+
status="ok",
|
|
184
|
+
tool_name=self.name,
|
|
185
|
+
summary=summary,
|
|
186
|
+
payload=payload,
|
|
187
|
+
meta={
|
|
188
|
+
"skill_name": result.skill_name,
|
|
189
|
+
"source": result.source,
|
|
190
|
+
"skill_path": result.skill_path,
|
|
191
|
+
"allowed_tools": result.allowed_tools,
|
|
192
|
+
"model_override": result.model_override,
|
|
193
|
+
"constraints_applied": result.constraints_applied,
|
|
194
|
+
"observability": {
|
|
195
|
+
"schema_version": "v1",
|
|
196
|
+
"scenario": "skill_tool_scope_resolution",
|
|
197
|
+
"events": [
|
|
198
|
+
{
|
|
199
|
+
"event": "skill_loaded",
|
|
200
|
+
"skill_name": result.skill_name,
|
|
201
|
+
"source": result.source,
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
"event": "tool_scope_resolved",
|
|
205
|
+
"auto_approved": (
|
|
206
|
+
result.constraints_applied.get("tool_scope_resolution", {})
|
|
207
|
+
.get("auto_approved", [])
|
|
208
|
+
),
|
|
209
|
+
"final_allowed": (
|
|
210
|
+
result.constraints_applied.get("tool_scope_resolution", {})
|
|
211
|
+
.get("final_allowed", [])
|
|
212
|
+
),
|
|
213
|
+
"explain": (
|
|
214
|
+
result.constraints_applied.get("tool_scope_resolution", {})
|
|
215
|
+
.get("explain", [])
|
|
216
|
+
),
|
|
217
|
+
},
|
|
218
|
+
],
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
)
|
|
222
|
+
|
|
File without changes
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"""
|
|
2
|
+
utils/cwd.py — 工作目录单点管理
|
|
3
|
+
|
|
4
|
+
职责:CwdContext 封装 ContextVar 与进程 cwd;模块级 get_cwd / run_with_cwd_override /
|
|
5
|
+
expand_path / to_relative_path 为薄封装(稳定 ABI)。
|
|
6
|
+
在整体链路中的位置:所有工具 normalize_input → expand_path(path, ctx.cwd);
|
|
7
|
+
SubagentOrchestrator.invoke_subagent → run_with_cwd_override(effective_cwd)
|
|
8
|
+
CC 对照:utils/cwd.ts getCwd() / runWithCwdOverride();utils/path.ts expandPath() / toRelativePath()
|
|
9
|
+
当前裁剪范围:v1 全量;ContextVar 替代 AsyncLocalStorage,语义完全对齐。
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import os
|
|
15
|
+
from contextlib import contextmanager
|
|
16
|
+
from contextvars import ContextVar
|
|
17
|
+
from typing import Generator
|
|
18
|
+
|
|
19
|
+
# 与历史模块级实现一致:在**导入时**固定进程 cwd,供默认 CwdContext 使用。
|
|
20
|
+
_MODULE_PROCESS_CWD: str = os.path.realpath(os.getcwd())
|
|
21
|
+
|
|
22
|
+
_default_ctx: CwdContext | None = None
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class CwdContext:
|
|
26
|
+
"""单 Agent / 子任务链的 cwd 覆盖与路径展开(CC cwd.ts + path.ts 子集)。"""
|
|
27
|
+
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
*,
|
|
31
|
+
process_cwd: str | None = None,
|
|
32
|
+
cwd_var: ContextVar[str | None] | None = None,
|
|
33
|
+
) -> None:
|
|
34
|
+
self._cwd_var = cwd_var or ContextVar("cwd_override", default=None)
|
|
35
|
+
self._process_cwd = (
|
|
36
|
+
process_cwd if process_cwd is not None else os.path.realpath(os.getcwd())
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
def get_cwd(self) -> str:
|
|
40
|
+
"""有 override → 返回 override;否则返回构造时固定的进程 cwd。"""
|
|
41
|
+
return self._cwd_var.get() or self._process_cwd
|
|
42
|
+
|
|
43
|
+
@contextmanager
|
|
44
|
+
def run_with_cwd_override(self, cwd: str) -> Generator[None, None, None]:
|
|
45
|
+
"""在当前异步上下文中覆盖 cwd,退出后自动恢复。"""
|
|
46
|
+
token = self._cwd_var.set(os.path.realpath(cwd))
|
|
47
|
+
try:
|
|
48
|
+
yield
|
|
49
|
+
finally:
|
|
50
|
+
self._cwd_var.reset(token)
|
|
51
|
+
|
|
52
|
+
def expand_path(self, path: str, base_dir: str | None = None) -> str:
|
|
53
|
+
"""将路径展开为绝对路径,基准为 base_dir 或 get_cwd()。"""
|
|
54
|
+
actual_base = base_dir or self.get_cwd()
|
|
55
|
+
expanded = os.path.expanduser(path.strip())
|
|
56
|
+
if os.path.isabs(expanded):
|
|
57
|
+
return os.path.normpath(expanded)
|
|
58
|
+
return os.path.normpath(os.path.join(actual_base, expanded))
|
|
59
|
+
|
|
60
|
+
def to_relative_path(self, absolute_path: str) -> str:
|
|
61
|
+
"""将绝对路径转为相对于 get_cwd() 的路径;若会 ``..`` 溢出则保持绝对。"""
|
|
62
|
+
rel = os.path.relpath(absolute_path, self.get_cwd())
|
|
63
|
+
return absolute_path if rel.startswith("..") else rel
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def default_cwd_context() -> CwdContext:
|
|
67
|
+
"""返回进程内共享单例(懒创建,``process_cwd`` 与导入时模块行为一致)。"""
|
|
68
|
+
global _default_ctx
|
|
69
|
+
if _default_ctx is None:
|
|
70
|
+
_default_ctx = CwdContext(process_cwd=_MODULE_PROCESS_CWD)
|
|
71
|
+
return _default_ctx
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def get_cwd() -> str:
|
|
75
|
+
"""获取当前异步上下文的工作目录。
|
|
76
|
+
|
|
77
|
+
有 override → 返回 override;否则返回进程启动时的 cwd。
|
|
78
|
+
CC 对照:getCwd() = AsyncLocalStorage.getStore() ?? getCwdState()
|
|
79
|
+
"""
|
|
80
|
+
return default_cwd_context().get_cwd()
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@contextmanager
|
|
84
|
+
def run_with_cwd_override(cwd: str) -> Generator[None, None, None]:
|
|
85
|
+
"""在当前异步上下文中覆盖 cwd,退出后自动恢复。
|
|
86
|
+
|
|
87
|
+
ContextVar 在 asyncio 中自动传播到所有子任务(Task/coroutine),
|
|
88
|
+
因此子代理整个执行链内的 get_cwd() 都返回 cwd,互不干扰。
|
|
89
|
+
CC 对照:runWithCwdOverride(cwd, fn)
|
|
90
|
+
"""
|
|
91
|
+
with default_cwd_context().run_with_cwd_override(cwd):
|
|
92
|
+
yield
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def expand_path(path: str, base_dir: str | None = None) -> str:
|
|
96
|
+
"""将路径展开为绝对路径,基准为 base_dir 或 get_cwd()。
|
|
97
|
+
|
|
98
|
+
处理顺序:
|
|
99
|
+
1. 展开 ~ / ~/...
|
|
100
|
+
2. 绝对路径直接 normpath 返回
|
|
101
|
+
3. 相对路径基于 actual_base 解析
|
|
102
|
+
|
|
103
|
+
CC 对照:utils/path.ts expandPath(path, baseDir?)
|
|
104
|
+
"""
|
|
105
|
+
return default_cwd_context().expand_path(path, base_dir=base_dir)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def to_relative_path(absolute_path: str) -> str:
|
|
109
|
+
"""将绝对路径转为相对于 get_cwd() 的路径,节省 token。
|
|
110
|
+
|
|
111
|
+
若相对路径会超出 cwd(以 .. 开头),保持绝对路径不变。
|
|
112
|
+
CC 对照:utils/path.ts toRelativePath(absolutePath)
|
|
113
|
+
"""
|
|
114
|
+
return default_cwd_context().to_relative_path(absolute_path)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
__all__ = [
|
|
118
|
+
"CwdContext",
|
|
119
|
+
"default_cwd_context",
|
|
120
|
+
"expand_path",
|
|
121
|
+
"get_cwd",
|
|
122
|
+
"run_with_cwd_override",
|
|
123
|
+
"to_relative_path",
|
|
124
|
+
]
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""utils/host_platform.py — 宿主 OS / WSL 分类(L1)
|
|
2
|
+
|
|
3
|
+
职责:HostPlatformDetector 集中分类逻辑;可注入 getter/reader 供单测。
|
|
4
|
+
链路位置:仅被需要「宿主类别」的模块引用;不解析业务路径。
|
|
5
|
+
当前裁剪:不含 Cygwin/MSYS2;proc 版本读失败或空串时回退 LINUX。
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
import sys
|
|
12
|
+
from enum import StrEnum
|
|
13
|
+
from typing import Protocol
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class HostPlatform(StrEnum):
|
|
19
|
+
"""宿主平台枚举(字符串值与 CC / 遥测对齐)。"""
|
|
20
|
+
|
|
21
|
+
MACOS = "macos"
|
|
22
|
+
WINDOWS = "windows"
|
|
23
|
+
WSL = "wsl"
|
|
24
|
+
LINUX = "linux"
|
|
25
|
+
UNKNOWN = "unknown"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class SysPlatformGetter(Protocol):
|
|
29
|
+
def __call__(self) -> str: ...
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ProcVersionReader(Protocol):
|
|
33
|
+
def __call__(self) -> str: ...
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _default_sys_platform() -> str:
|
|
37
|
+
return sys.platform
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _default_read_proc_version() -> str:
|
|
41
|
+
with open("/proc/version", encoding="utf-8") as f:
|
|
42
|
+
return f.read()
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class HostPlatformDetector:
|
|
46
|
+
"""进程级 memo:同一实例上 ``current()`` 结果缓存至 ``invalidate()``。"""
|
|
47
|
+
|
|
48
|
+
def __init__(
|
|
49
|
+
self,
|
|
50
|
+
*,
|
|
51
|
+
sys_platform_getter: SysPlatformGetter | None = None,
|
|
52
|
+
proc_version_reader: ProcVersionReader | None = None,
|
|
53
|
+
) -> None:
|
|
54
|
+
self._sys_platform_getter = sys_platform_getter or _default_sys_platform
|
|
55
|
+
self._proc_version_reader = proc_version_reader or _default_read_proc_version
|
|
56
|
+
self._cache: HostPlatform | None = None
|
|
57
|
+
|
|
58
|
+
def current(self) -> HostPlatform:
|
|
59
|
+
if self._cache is None:
|
|
60
|
+
self._cache = self._compute()
|
|
61
|
+
return self._cache
|
|
62
|
+
|
|
63
|
+
def invalidate(self) -> None:
|
|
64
|
+
self._cache = None
|
|
65
|
+
|
|
66
|
+
def _compute(self) -> HostPlatform:
|
|
67
|
+
plat = self._sys_platform_getter().lower()
|
|
68
|
+
if plat == "darwin":
|
|
69
|
+
return HostPlatform.MACOS
|
|
70
|
+
if plat == "win32":
|
|
71
|
+
return HostPlatform.WINDOWS
|
|
72
|
+
if plat == "linux":
|
|
73
|
+
try:
|
|
74
|
+
ver_raw = self._proc_version_reader()
|
|
75
|
+
except Exception:
|
|
76
|
+
logger.debug("proc_version read failed", exc_info=True)
|
|
77
|
+
return HostPlatform.LINUX
|
|
78
|
+
ver = ver_raw.lower()
|
|
79
|
+
if not ver:
|
|
80
|
+
logger.debug("proc_version empty; treating as non-WSL linux")
|
|
81
|
+
return HostPlatform.LINUX
|
|
82
|
+
if "microsoft" in ver or "wsl" in ver:
|
|
83
|
+
logger.info("Detected WSL host environment")
|
|
84
|
+
return HostPlatform.WSL
|
|
85
|
+
return HostPlatform.LINUX
|
|
86
|
+
return HostPlatform.UNKNOWN
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
_singleton: HostPlatformDetector | None = None
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def default_host_platform_detector() -> HostPlatformDetector:
|
|
93
|
+
"""返回进程内共享单例(懒创建)。"""
|
|
94
|
+
global _singleton
|
|
95
|
+
if _singleton is None:
|
|
96
|
+
_singleton = HostPlatformDetector()
|
|
97
|
+
return _singleton
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def get_host_platform() -> HostPlatform:
|
|
101
|
+
"""等价于 ``default_host_platform_detector().current()``。"""
|
|
102
|
+
return default_host_platform_detector().current()
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
__all__ = [
|
|
106
|
+
"HostPlatform",
|
|
107
|
+
"HostPlatformDetector",
|
|
108
|
+
"ProcVersionReader",
|
|
109
|
+
"SysPlatformGetter",
|
|
110
|
+
"default_host_platform_detector",
|
|
111
|
+
"get_host_platform",
|
|
112
|
+
]
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""utils/path_hierarchy.py — 路径层级原语(跨平台)。
|
|
2
|
+
|
|
3
|
+
职责:
|
|
4
|
+
判断 Path 是否已到达当前卷/文件系统的根,供「沿父目录向上遍历」类逻辑安全退出。
|
|
5
|
+
核心逻辑在 FilesystemRootProbe;模块级 is_filesystem_root 为薄封装。
|
|
6
|
+
|
|
7
|
+
链路位置:
|
|
8
|
+
memory/instruction/loader.py:ProjectRuleCollector.collect 等。
|
|
9
|
+
|
|
10
|
+
当前裁剪范围:
|
|
11
|
+
仅提供根判定;不包含路径规范化业务规则。
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class FilesystemRootProbe:
|
|
20
|
+
"""封装 ``path.parent == path`` 根判定(与 CC git 向上遍历终止思想对齐)。"""
|
|
21
|
+
|
|
22
|
+
def is_root(self, path: Path) -> bool:
|
|
23
|
+
"""若 *path* 已无更上层父目录则返回 True。"""
|
|
24
|
+
return path.parent == path
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
_default_probe = FilesystemRootProbe()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def default_filesystem_root_probe() -> FilesystemRootProbe:
|
|
31
|
+
"""返回进程内共享的默认探测实例。"""
|
|
32
|
+
return _default_probe
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def is_filesystem_root(path: Path) -> bool:
|
|
36
|
+
"""若 *path* 已无更上层父目录则返回 True。
|
|
37
|
+
|
|
38
|
+
使用 pathlib 约定:根目录满足 ``path.parent == path``(POSIX ``/``、
|
|
39
|
+
Windows 盘符根如 ``C:\\``、UNC 卷根等均为此类)。
|
|
40
|
+
"""
|
|
41
|
+
return _default_probe.is_root(path)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
__all__ = [
|
|
45
|
+
"FilesystemRootProbe",
|
|
46
|
+
"default_filesystem_root_probe",
|
|
47
|
+
"is_filesystem_root",
|
|
48
|
+
]
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""utils/path_user_input.py — 用户路径串规范化(N 层 / L2)
|
|
2
|
+
|
|
3
|
+
职责:UserPathInputNormalizer 在 Windows 上将 /c/... 转为原生盘符路径;不校验 workspace。
|
|
4
|
+
链路位置:expand_path 与 policy 之前;依赖 HostPlatformDetector 注入。
|
|
5
|
+
当前裁剪:无 URI、无 NFC。
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import re
|
|
11
|
+
|
|
12
|
+
from langchain_agentx.utils.cwd import expand_path
|
|
13
|
+
from langchain_agentx.utils.host_platform import (
|
|
14
|
+
HostPlatform,
|
|
15
|
+
HostPlatformDetector,
|
|
16
|
+
default_host_platform_detector,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
# Git Bash 风格:/c/Users/... → C:\Users\...
|
|
20
|
+
_POSIX_WINDOWS_DRIVE = re.compile(r"^/([a-zA-Z])(?:/|\\|$)")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class UserPathInputNormalizer:
|
|
24
|
+
"""Windows 上窄匹配 POSIX 盘符形态;其它宿主原样透传。"""
|
|
25
|
+
|
|
26
|
+
def __init__(self, host_detector: HostPlatformDetector | None = None) -> None:
|
|
27
|
+
self._host = host_detector or default_host_platform_detector()
|
|
28
|
+
|
|
29
|
+
def normalize_for_expand(self, path: str) -> str:
|
|
30
|
+
stripped = path.strip()
|
|
31
|
+
if stripped.startswith("~"):
|
|
32
|
+
return path
|
|
33
|
+
if self._host.current() is not HostPlatform.WINDOWS:
|
|
34
|
+
return path
|
|
35
|
+
m = _POSIX_WINDOWS_DRIVE.match(stripped)
|
|
36
|
+
if not m:
|
|
37
|
+
return path
|
|
38
|
+
drive = m.group(1).upper()
|
|
39
|
+
rest = stripped[m.end() :]
|
|
40
|
+
try:
|
|
41
|
+
if rest:
|
|
42
|
+
native = f"{drive}:\\" + rest.replace("/", "\\")
|
|
43
|
+
else:
|
|
44
|
+
native = f"{drive}:\\"
|
|
45
|
+
return native
|
|
46
|
+
except Exception:
|
|
47
|
+
return path
|
|
48
|
+
|
|
49
|
+
def normalize_and_expand(self, path: str, base_dir: str | None = None) -> str:
|
|
50
|
+
return expand_path(self.normalize_for_expand(path), base_dir=base_dir)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
_default_normalizer: UserPathInputNormalizer | None = None
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def normalize_user_path_string_for_expand(path: str) -> str:
|
|
57
|
+
global _default_normalizer
|
|
58
|
+
if _default_normalizer is None:
|
|
59
|
+
_default_normalizer = UserPathInputNormalizer()
|
|
60
|
+
return _default_normalizer.normalize_for_expand(path)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
__all__ = [
|
|
64
|
+
"UserPathInputNormalizer",
|
|
65
|
+
"normalize_user_path_string_for_expand",
|
|
66
|
+
]
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""
|
|
2
|
+
utils/rg_executable.py — 系统 ripgrep 可执行文件名
|
|
3
|
+
|
|
4
|
+
职责:为子进程提供跨平台的默认 rg 命令名(本 SDK 不打包 vendor rg)。
|
|
5
|
+
链路:GrepRuntimeTool / GlobRuntimeTool -> default_rg_executable()。
|
|
6
|
+
|
|
7
|
+
约定:
|
|
8
|
+
- Windows:使用「rg.exe」(与 MSVC 安装及 PATH 常见形态一致)
|
|
9
|
+
- 非 Windows:使用「rg」
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import sys
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def default_rg_executable() -> str:
|
|
18
|
+
return "rg.exe" if sys.platform == "win32" else "rg"
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""utils/subprocess_text.py — 文本子进程默认编码(L4)
|
|
2
|
+
|
|
3
|
+
职责:TextSubprocessRunner 封装默认 utf-8 + errors=replace;可注入 subprocess.run 测行为。
|
|
4
|
+
链路位置:Bash/grep/git 等;不替代 BashRuntimeTool 安全语义。
|
|
5
|
+
当前裁剪:不封装长生命周期 Popen;不默认 shell=True;禁止 shell=True。
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import subprocess
|
|
11
|
+
from collections.abc import Callable, Sequence
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
RunProc = Callable[..., subprocess.CompletedProcess[Any]]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class TextSubprocessRunner:
|
|
18
|
+
"""统一捕获文本子进程的 ``encoding`` / ``errors`` 默认值(方案 B)。"""
|
|
19
|
+
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
*,
|
|
23
|
+
default_encoding: str = "utf-8",
|
|
24
|
+
default_errors: str = "replace",
|
|
25
|
+
subprocess_run: RunProc = subprocess.run,
|
|
26
|
+
) -> None:
|
|
27
|
+
self._default_encoding = default_encoding
|
|
28
|
+
self._default_errors = default_errors
|
|
29
|
+
self._subprocess_run = subprocess_run
|
|
30
|
+
|
|
31
|
+
def run(
|
|
32
|
+
self,
|
|
33
|
+
args: Sequence[str] | str,
|
|
34
|
+
*,
|
|
35
|
+
encoding: str | None = None,
|
|
36
|
+
errors: str | None = None,
|
|
37
|
+
timeout: float | None = None,
|
|
38
|
+
text: bool | None = None,
|
|
39
|
+
capture_output: bool = False,
|
|
40
|
+
**kwargs: Any,
|
|
41
|
+
) -> subprocess.CompletedProcess[Any]:
|
|
42
|
+
shell = kwargs.get("shell", False)
|
|
43
|
+
if shell is True:
|
|
44
|
+
raise ValueError(
|
|
45
|
+
"TextSubprocessRunner rejects shell=True; use argv list or a dedicated shell wrapper."
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
stdout = kwargs.get("stdout")
|
|
49
|
+
stderr = kwargs.get("stderr")
|
|
50
|
+
|
|
51
|
+
if text is False:
|
|
52
|
+
effective_text = False
|
|
53
|
+
elif text is True:
|
|
54
|
+
effective_text = True
|
|
55
|
+
else:
|
|
56
|
+
if capture_output or stdout is subprocess.PIPE or stderr is subprocess.PIPE:
|
|
57
|
+
effective_text = True
|
|
58
|
+
else:
|
|
59
|
+
effective_text = False
|
|
60
|
+
|
|
61
|
+
run_kw: dict[str, Any] = dict(kwargs)
|
|
62
|
+
run_kw["capture_output"] = capture_output
|
|
63
|
+
if timeout is not None:
|
|
64
|
+
run_kw["timeout"] = timeout
|
|
65
|
+
|
|
66
|
+
if effective_text:
|
|
67
|
+
enc = self._default_encoding if encoding is None else encoding
|
|
68
|
+
err = self._default_errors if errors is None else errors
|
|
69
|
+
run_kw["text"] = True
|
|
70
|
+
run_kw["encoding"] = enc
|
|
71
|
+
run_kw["errors"] = err
|
|
72
|
+
else:
|
|
73
|
+
run_kw["text"] = False
|
|
74
|
+
run_kw.pop("encoding", None)
|
|
75
|
+
run_kw.pop("errors", None)
|
|
76
|
+
|
|
77
|
+
return self._subprocess_run(args, **run_kw)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
_runner_singleton: TextSubprocessRunner | None = None
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def default_text_subprocess_runner() -> TextSubprocessRunner:
|
|
84
|
+
global _runner_singleton
|
|
85
|
+
if _runner_singleton is None:
|
|
86
|
+
_runner_singleton = TextSubprocessRunner()
|
|
87
|
+
return _runner_singleton
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def run_text(
|
|
91
|
+
args: Sequence[str] | str,
|
|
92
|
+
**kwargs: Any,
|
|
93
|
+
) -> subprocess.CompletedProcess[Any]:
|
|
94
|
+
return default_text_subprocess_runner().run(args, **kwargs)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
__all__ = [
|
|
98
|
+
"TextSubprocessRunner",
|
|
99
|
+
"default_text_subprocess_runner",
|
|
100
|
+
"run_text",
|
|
101
|
+
]
|