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.
Files changed (354) hide show
  1. langchain_agentx/__init__.py +46 -0
  2. langchain_agentx/command/__init__.py +28 -0
  3. langchain_agentx/command/builtin/__init__.py +25 -0
  4. langchain_agentx/command/builtin/clear.py +33 -0
  5. langchain_agentx/command/builtin/compact.py +33 -0
  6. langchain_agentx/command/builtin/memory.py +37 -0
  7. langchain_agentx/command/builtin/reload_plugins.py +42 -0
  8. langchain_agentx/command/context.py +30 -0
  9. langchain_agentx/command/dispatcher.py +183 -0
  10. langchain_agentx/command/registry.py +110 -0
  11. langchain_agentx/command/result.py +25 -0
  12. langchain_agentx/command/types.py +41 -0
  13. langchain_agentx/config/__init__.py +14 -0
  14. langchain_agentx/loop/__init__.py +47 -0
  15. langchain_agentx/loop/config/__init__.py +20 -0
  16. langchain_agentx/loop/config/agent_config.py +66 -0
  17. langchain_agentx/loop/config/agent_loop_config.py +72 -0
  18. langchain_agentx/loop/config/model_context_resolver.py +105 -0
  19. langchain_agentx/loop/config/runtime_settings.py +50 -0
  20. langchain_agentx/loop/config/token_estimator.py +133 -0
  21. langchain_agentx/loop/context/__init__.py +66 -0
  22. langchain_agentx/loop/context/blocking_guard.py +97 -0
  23. langchain_agentx/loop/context/compaction_service.py +60 -0
  24. langchain_agentx/loop/context/message_utils.py +56 -0
  25. langchain_agentx/loop/context/pipeline.py +127 -0
  26. langchain_agentx/loop/context/settings.py +103 -0
  27. langchain_agentx/loop/context/stages/__init__.py +29 -0
  28. langchain_agentx/loop/context/stages/autocompact.py +140 -0
  29. langchain_agentx/loop/context/stages/base.py +32 -0
  30. langchain_agentx/loop/context/stages/collapse.py +76 -0
  31. langchain_agentx/loop/context/stages/microcompact.py +76 -0
  32. langchain_agentx/loop/context/stages/noop.py +33 -0
  33. langchain_agentx/loop/context/stages/snip.py +71 -0
  34. langchain_agentx/loop/context/stages/tool_result_budget.py +69 -0
  35. langchain_agentx/loop/context/types.py +79 -0
  36. langchain_agentx/loop/exit/__init__.py +1 -0
  37. langchain_agentx/loop/exit/exit_logic.py +320 -0
  38. langchain_agentx/loop/exit/reason_codes.py +39 -0
  39. langchain_agentx/loop/graph/__init__.py +5 -0
  40. langchain_agentx/loop/graph/builtin_loop_control.py +197 -0
  41. langchain_agentx/loop/graph/factory.py +1409 -0
  42. langchain_agentx/loop/graph/graph_edges.py +820 -0
  43. langchain_agentx/loop/hook/__init__.py +48 -0
  44. langchain_agentx/loop/hook/async_hook_runner.py +62 -0
  45. langchain_agentx/loop/hook/config.py +280 -0
  46. langchain_agentx/loop/hook/engine.py +321 -0
  47. langchain_agentx/loop/hook/executors/__init__.py +9 -0
  48. langchain_agentx/loop/hook/executors/agent.py +107 -0
  49. langchain_agentx/loop/hook/executors/command.py +230 -0
  50. langchain_agentx/loop/hook/executors/http.py +114 -0
  51. langchain_agentx/loop/hook/executors/prompt.py +92 -0
  52. langchain_agentx/loop/hook/graph_wiring.py +134 -0
  53. langchain_agentx/loop/hook/registry.py +262 -0
  54. langchain_agentx/loop/hook/trust.py +43 -0
  55. langchain_agentx/loop/hook/types.py +110 -0
  56. langchain_agentx/loop/injection/__init__.py +13 -0
  57. langchain_agentx/loop/injection/dedup.py +74 -0
  58. langchain_agentx/loop/loop_abort.py +36 -0
  59. langchain_agentx/loop/model/__init__.py +1 -0
  60. langchain_agentx/loop/model/model_node.py +648 -0
  61. langchain_agentx/loop/model/model_nodes.py +661 -0
  62. langchain_agentx/loop/model/orphan_tool_results.py +38 -0
  63. langchain_agentx/loop/model/retrier.py +307 -0
  64. langchain_agentx/loop/model/retry_bridge.py +58 -0
  65. langchain_agentx/loop/model/retry_events.py +35 -0
  66. langchain_agentx/loop/model/retry_policy.py +56 -0
  67. langchain_agentx/loop/model/schema_and_format.py +153 -0
  68. langchain_agentx/loop/model/tool_and_model_binding.py +227 -0
  69. langchain_agentx/loop/model/tool_call_degradation_corrector.py +443 -0
  70. langchain_agentx/loop/model/tool_transcript_guard.py +225 -0
  71. langchain_agentx/loop/prompt/__init__.py +95 -0
  72. langchain_agentx/loop/prompt/builder.py +61 -0
  73. langchain_agentx/loop/prompt/builtin.py +218 -0
  74. langchain_agentx/loop/prompt/compact.py +408 -0
  75. langchain_agentx/loop/prompt/sections.py +120 -0
  76. langchain_agentx/loop/runtime/__init__.py +19 -0
  77. langchain_agentx/loop/runtime/context.py +34 -0
  78. langchain_agentx/loop/runtime/context_factory.py +107 -0
  79. langchain_agentx/loop/runtime/subagent_execution_paths.py +68 -0
  80. langchain_agentx/loop/subagent/__init__.py +53 -0
  81. langchain_agentx/loop/subagent/async_runner.py +215 -0
  82. langchain_agentx/loop/subagent/context.py +209 -0
  83. langchain_agentx/loop/subagent/fork_worktree_notice.py +25 -0
  84. langchain_agentx/loop/subagent/graph.py +72 -0
  85. langchain_agentx/loop/subagent/orchestrator.py +391 -0
  86. langchain_agentx/loop/subagent/progress.py +30 -0
  87. langchain_agentx/loop/subagent/prompt.py +52 -0
  88. langchain_agentx/loop/subagent/runner.py +504 -0
  89. langchain_agentx/loop/subagent/transcript.py +172 -0
  90. langchain_agentx/memory/__init__.py +2 -0
  91. langchain_agentx/memory/instruction/__init__.py +12 -0
  92. langchain_agentx/memory/instruction/loader.py +325 -0
  93. langchain_agentx/memory/instruction/resolver.py +24 -0
  94. langchain_agentx/memory/instruction/runtime.py +83 -0
  95. langchain_agentx/memory/instruction/sections.py +83 -0
  96. langchain_agentx/memory/instruction/types.py +59 -0
  97. langchain_agentx/memory/memdir/__init__.py +77 -0
  98. langchain_agentx/memory/memdir/age.py +36 -0
  99. langchain_agentx/memory/memdir/agent_memory.py +380 -0
  100. langchain_agentx/memory/memdir/extractor.py +309 -0
  101. langchain_agentx/memory/memdir/loader.py +187 -0
  102. langchain_agentx/memory/memdir/paths.py +63 -0
  103. langchain_agentx/memory/memdir/recall.py +45 -0
  104. langchain_agentx/memory/memdir/runtime.py +43 -0
  105. langchain_agentx/memory/memdir/scan.py +135 -0
  106. langchain_agentx/memory/memdir/types.py +104 -0
  107. langchain_agentx/memory/session/__init__.py +76 -0
  108. langchain_agentx/memory/session/compact_bridge.py +208 -0
  109. langchain_agentx/memory/session/prompts.py +172 -0
  110. langchain_agentx/memory/session/session_memory.py +282 -0
  111. langchain_agentx/observability/__init__.py +67 -0
  112. langchain_agentx/observability/evaluation/__init__.py +17 -0
  113. langchain_agentx/observability/evaluation/checkers/__init__.py +18 -0
  114. langchain_agentx/observability/evaluation/checkers/base.py +34 -0
  115. langchain_agentx/observability/evaluation/checkers/compaction.py +38 -0
  116. langchain_agentx/observability/evaluation/checkers/degradation.py +50 -0
  117. langchain_agentx/observability/evaluation/checkers/exit_quality.py +42 -0
  118. langchain_agentx/observability/evaluation/checkers/session_memory.py +45 -0
  119. langchain_agentx/observability/evaluation/checkers/tool_behavior.py +53 -0
  120. langchain_agentx/observability/evaluation/retention_scheduler.py +67 -0
  121. langchain_agentx/observability/evaluation/service.py +102 -0
  122. langchain_agentx/observability/evaluation/state.py +32 -0
  123. langchain_agentx/observability/evaluation/store.py +258 -0
  124. langchain_agentx/observability/events/__init__.py +15 -0
  125. langchain_agentx/observability/events/langchain_agentx_event_adapter.py +832 -0
  126. langchain_agentx/observability/logging/__init__.py +15 -0
  127. langchain_agentx/observability/logging/debug_burst.py +95 -0
  128. langchain_agentx/observability/logging/logging_config.py +178 -0
  129. langchain_agentx/observability/logging/logging_contract.py +65 -0
  130. langchain_agentx/observability/replay/__init__.py +35 -0
  131. langchain_agentx/observability/replay/cli.py +91 -0
  132. langchain_agentx/observability/replay/service.py +83 -0
  133. langchain_agentx/observability/replay/store.py +278 -0
  134. langchain_agentx/observability/replay/ui.py +47 -0
  135. langchain_agentx/observability/trace/__init__.py +25 -0
  136. langchain_agentx/observability/trace/collector.py +560 -0
  137. langchain_agentx/observability/trace/event_emitter.py +183 -0
  138. langchain_agentx/observability/trace/hook_event_emitter.py +49 -0
  139. langchain_agentx/observability/trace/models.py +144 -0
  140. langchain_agentx/observability/trace/sqlite_store.py +873 -0
  141. langchain_agentx/observability/trace/trace_callback.py +295 -0
  142. langchain_agentx/observability/trace/trace_lifecycle_collector.py +114 -0
  143. langchain_agentx/plugin/__init__.py +26 -0
  144. langchain_agentx/plugin/builtin.py +53 -0
  145. langchain_agentx/plugin/config.py +113 -0
  146. langchain_agentx/plugin/loader.py +386 -0
  147. langchain_agentx/plugin/manifest.py +154 -0
  148. langchain_agentx/plugin/registries.py +211 -0
  149. langchain_agentx/plugin/types.py +142 -0
  150. langchain_agentx/provider/__init__.py +27 -0
  151. langchain_agentx/provider/anthropic.py +121 -0
  152. langchain_agentx/provider/compatible_chat_openai.py +86 -0
  153. langchain_agentx/provider/env.py +45 -0
  154. langchain_agentx/provider/model_profile.py +156 -0
  155. langchain_agentx/provider/openai.py +89 -0
  156. langchain_agentx/session/__init__.py +17 -0
  157. langchain_agentx/session/agent_session.py +320 -0
  158. langchain_agentx/session/conversation_factory.py +87 -0
  159. langchain_agentx/session/conversation_recovery.py +156 -0
  160. langchain_agentx/session/conversation_session.py +198 -0
  161. langchain_agentx/session/factory.py +143 -0
  162. langchain_agentx/session/protocol.py +25 -0
  163. langchain_agentx/task_runtime/__init__.py +113 -0
  164. langchain_agentx/task_runtime/core/__init__.py +51 -0
  165. langchain_agentx/task_runtime/core/ids.py +33 -0
  166. langchain_agentx/task_runtime/core/interfaces.py +115 -0
  167. langchain_agentx/task_runtime/core/notification_priority.py +19 -0
  168. langchain_agentx/task_runtime/core/types.py +136 -0
  169. langchain_agentx/task_runtime/integrations/__init__.py +33 -0
  170. langchain_agentx/task_runtime/integrations/loop_adapter.py +91 -0
  171. langchain_agentx/task_runtime/integrations/loop_integration.py +61 -0
  172. langchain_agentx/task_runtime/integrations/prefetch_providers.py +108 -0
  173. langchain_agentx/task_runtime/integrations/provider_factory.py +103 -0
  174. langchain_agentx/task_runtime/integrations/queued_command_provider.py +184 -0
  175. langchain_agentx/task_runtime/integrations/sqlite_queued_command_provider.py +338 -0
  176. langchain_agentx/task_runtime/integrations/tool_use_summary_provider.py +254 -0
  177. langchain_agentx/task_runtime/orchestrator/__init__.py +5 -0
  178. langchain_agentx/task_runtime/orchestrator/runtime.py +386 -0
  179. langchain_agentx/task_runtime/output/__init__.py +5 -0
  180. langchain_agentx/task_runtime/output/sink.py +64 -0
  181. langchain_agentx/task_runtime/policy/__init__.py +11 -0
  182. langchain_agentx/task_runtime/policy/withhold_visibility.py +32 -0
  183. langchain_agentx/task_runtime/queue/__init__.py +5 -0
  184. langchain_agentx/task_runtime/queue/in_memory.py +55 -0
  185. langchain_agentx/task_runtime/skill_prefetch/__init__.py +4 -0
  186. langchain_agentx/task_runtime/skill_prefetch/attachments.py +46 -0
  187. langchain_agentx/task_runtime/skill_prefetch/models.py +37 -0
  188. langchain_agentx/task_runtime/skill_prefetch/provider.py +344 -0
  189. langchain_agentx/task_runtime/store/__init__.py +6 -0
  190. langchain_agentx/task_runtime/store/in_memory.py +81 -0
  191. langchain_agentx/task_runtime/store/sqlite_store.py +281 -0
  192. langchain_agentx/task_runtime/tasks/__init__.py +76 -0
  193. langchain_agentx/task_runtime/tasks/ai_analysis/__init__.py +15 -0
  194. langchain_agentx/task_runtime/tasks/ai_analysis/base.py +41 -0
  195. langchain_agentx/task_runtime/tasks/ai_analysis/evaluation.py +67 -0
  196. langchain_agentx/task_runtime/tasks/ai_analysis/registry.py +36 -0
  197. langchain_agentx/task_runtime/tasks/ai_analysis/scheduler.py +70 -0
  198. langchain_agentx/task_runtime/tasks/base/__init__.py +6 -0
  199. langchain_agentx/task_runtime/tasks/base/contracts.py +24 -0
  200. langchain_agentx/task_runtime/tasks/custom/__init__.py +7 -0
  201. langchain_agentx/task_runtime/tasks/custom/executor.py +60 -0
  202. langchain_agentx/task_runtime/tasks/custom/notification.py +7 -0
  203. langchain_agentx/task_runtime/tasks/custom/semantics.py +13 -0
  204. langchain_agentx/task_runtime/tasks/custom/spec.py +33 -0
  205. langchain_agentx/task_runtime/tasks/dream_task/__init__.py +15 -0
  206. langchain_agentx/task_runtime/tasks/dream_task/executor.py +61 -0
  207. langchain_agentx/task_runtime/tasks/dream_task/notification.py +19 -0
  208. langchain_agentx/task_runtime/tasks/dream_task/semantics.py +13 -0
  209. langchain_agentx/task_runtime/tasks/dream_task/spec.py +35 -0
  210. langchain_agentx/task_runtime/tasks/dream_task/state.py +17 -0
  211. langchain_agentx/task_runtime/tasks/in_process_teammate/__init__.py +12 -0
  212. langchain_agentx/task_runtime/tasks/in_process_teammate/executor.py +36 -0
  213. langchain_agentx/task_runtime/tasks/in_process_teammate/notification.py +25 -0
  214. langchain_agentx/task_runtime/tasks/in_process_teammate/semantics.py +13 -0
  215. langchain_agentx/task_runtime/tasks/in_process_teammate/spec.py +63 -0
  216. langchain_agentx/task_runtime/tasks/local_agent/__init__.py +14 -0
  217. langchain_agentx/task_runtime/tasks/local_agent/executor.py +33 -0
  218. langchain_agentx/task_runtime/tasks/local_agent/notification.py +21 -0
  219. langchain_agentx/task_runtime/tasks/local_agent/runner.py +43 -0
  220. langchain_agentx/task_runtime/tasks/local_agent/semantics.py +13 -0
  221. langchain_agentx/task_runtime/tasks/local_agent/spec.py +31 -0
  222. langchain_agentx/task_runtime/tasks/local_bash/__init__.py +13 -0
  223. langchain_agentx/task_runtime/tasks/local_bash/executor.py +95 -0
  224. langchain_agentx/task_runtime/tasks/local_bash/notification.py +22 -0
  225. langchain_agentx/task_runtime/tasks/local_bash/semantics.py +13 -0
  226. langchain_agentx/task_runtime/tasks/local_bash/spec.py +55 -0
  227. langchain_agentx/task_runtime/tasks/remote_agent/__init__.py +19 -0
  228. langchain_agentx/task_runtime/tasks/remote_agent/backend.py +76 -0
  229. langchain_agentx/task_runtime/tasks/remote_agent/executor.py +37 -0
  230. langchain_agentx/task_runtime/tasks/remote_agent/notification.py +22 -0
  231. langchain_agentx/task_runtime/tasks/remote_agent/semantics.py +13 -0
  232. langchain_agentx/task_runtime/tasks/remote_agent/spec.py +34 -0
  233. langchain_agentx/task_runtime/tasks/trace_cleanup/__init__.py +19 -0
  234. langchain_agentx/task_runtime/tasks/trace_cleanup/bootstrap.py +95 -0
  235. langchain_agentx/task_runtime/tasks/trace_cleanup/executor.py +66 -0
  236. langchain_agentx/task_runtime/tasks/trace_cleanup/scheduler.py +169 -0
  237. langchain_agentx/tool_runtime/__init__.py +90 -0
  238. langchain_agentx/tool_runtime/adapter.py +365 -0
  239. langchain_agentx/tool_runtime/base.py +319 -0
  240. langchain_agentx/tool_runtime/errors.py +190 -0
  241. langchain_agentx/tool_runtime/identical_call_cache.py +110 -0
  242. langchain_agentx/tool_runtime/loader.py +195 -0
  243. langchain_agentx/tool_runtime/models.py +260 -0
  244. langchain_agentx/tool_runtime/permission_context.py +78 -0
  245. langchain_agentx/tool_runtime/pipeline.py +621 -0
  246. langchain_agentx/tool_runtime/policy.py +447 -0
  247. langchain_agentx/tool_runtime/registry.py +81 -0
  248. langchain_agentx/tool_runtime/resolvers/__init__.py +27 -0
  249. langchain_agentx/tool_runtime/resolvers/agent_session.py +125 -0
  250. langchain_agentx/tool_runtime/resolvers/background.py +32 -0
  251. langchain_agentx/tool_runtime/resolvers/base.py +20 -0
  252. langchain_agentx/tool_runtime/resolvers/conversation.py +22 -0
  253. langchain_agentx/tool_runtime/resolvers/workflow.py +73 -0
  254. langchain_agentx/tool_runtime/session_store.py +132 -0
  255. langchain_agentx/tool_runtime/smoke_test_runtime.py +294 -0
  256. langchain_agentx/tool_runtime/state_bridge.py +164 -0
  257. langchain_agentx/tools/__init__.py +26 -0
  258. langchain_agentx/tools/agent/__init__.py +9 -0
  259. langchain_agentx/tools/agent/backend.py +53 -0
  260. langchain_agentx/tools/agent/built_in/__init__.py +19 -0
  261. langchain_agentx/tools/agent/built_in/agentx_guide.py +65 -0
  262. langchain_agentx/tools/agent/built_in/explore.py +80 -0
  263. langchain_agentx/tools/agent/built_in/general.py +57 -0
  264. langchain_agentx/tools/agent/built_in/plan.py +89 -0
  265. langchain_agentx/tools/agent/built_in/statusline_setup.py +64 -0
  266. langchain_agentx/tools/agent/built_in/verification.py +120 -0
  267. langchain_agentx/tools/agent/builtin_subagent_loader.py +89 -0
  268. langchain_agentx/tools/agent/cwd_resolution.py +119 -0
  269. langchain_agentx/tools/agent/limits.py +26 -0
  270. langchain_agentx/tools/agent/loader.py +270 -0
  271. langchain_agentx/tools/agent/models.py +85 -0
  272. langchain_agentx/tools/agent/prompt.py +120 -0
  273. langchain_agentx/tools/agent/registry/__init__.py +18 -0
  274. langchain_agentx/tools/agent/registry/config.py +29 -0
  275. langchain_agentx/tools/agent/registry/registry.py +47 -0
  276. langchain_agentx/tools/agent/scope.py +137 -0
  277. langchain_agentx/tools/agent/tool.py +256 -0
  278. langchain_agentx/tools/bash/__init__.py +9 -0
  279. langchain_agentx/tools/bash/ast_security.py +571 -0
  280. langchain_agentx/tools/bash/backend.py +1447 -0
  281. langchain_agentx/tools/bash/bash_hardening.py +734 -0
  282. langchain_agentx/tools/bash/bash_runtime_contract.py +41 -0
  283. langchain_agentx/tools/bash/cwd_reporter.py +95 -0
  284. langchain_agentx/tools/bash/limits.py +71 -0
  285. langchain_agentx/tools/bash/mode_validation.py +282 -0
  286. langchain_agentx/tools/bash/models.py +131 -0
  287. langchain_agentx/tools/bash/observability.py +148 -0
  288. langchain_agentx/tools/bash/output_utils.py +200 -0
  289. langchain_agentx/tools/bash/path_security.py +2429 -0
  290. langchain_agentx/tools/bash/prompt.py +68 -0
  291. langchain_agentx/tools/bash/read_only_validation.py +589 -0
  292. langchain_agentx/tools/bash/result_presenter.py +324 -0
  293. langchain_agentx/tools/bash/sandbox_decision.py +133 -0
  294. langchain_agentx/tools/bash/security.py +311 -0
  295. langchain_agentx/tools/bash/sed_edit_parser.py +243 -0
  296. langchain_agentx/tools/bash/sed_validation.py +163 -0
  297. langchain_agentx/tools/bash/semantics.py +111 -0
  298. langchain_agentx/tools/bash/session_manager.py +205 -0
  299. langchain_agentx/tools/bash/session_runtime.py +290 -0
  300. langchain_agentx/tools/bash/shell_locator.py +191 -0
  301. langchain_agentx/tools/bash/task_runtime.py +91 -0
  302. langchain_agentx/tools/bash/tool.py +939 -0
  303. langchain_agentx/tools/bash/windows_shell_quoting.py +45 -0
  304. langchain_agentx/tools/glob/__init__.py +9 -0
  305. langchain_agentx/tools/glob/models.py +57 -0
  306. langchain_agentx/tools/glob/pagination.py +30 -0
  307. langchain_agentx/tools/glob/prompt.py +24 -0
  308. langchain_agentx/tools/glob/rg_list_backend.py +139 -0
  309. langchain_agentx/tools/glob/rg_pattern.py +44 -0
  310. langchain_agentx/tools/glob/tool.py +327 -0
  311. langchain_agentx/tools/grep/__init__.py +7 -0
  312. langchain_agentx/tools/grep/backend.py +375 -0
  313. langchain_agentx/tools/grep/models.py +127 -0
  314. langchain_agentx/tools/grep/prompt.py +30 -0
  315. langchain_agentx/tools/grep/rg_subprocess_controller.py +114 -0
  316. langchain_agentx/tools/grep/tool.py +475 -0
  317. langchain_agentx/tools/read/__init__.py +9 -0
  318. langchain_agentx/tools/read/backend.py +415 -0
  319. langchain_agentx/tools/read/limits.py +67 -0
  320. langchain_agentx/tools/read/models.py +156 -0
  321. langchain_agentx/tools/read/prompt.py +73 -0
  322. langchain_agentx/tools/read/tool.py +494 -0
  323. langchain_agentx/tools/ripgrep_plugin_exclusions.py +137 -0
  324. langchain_agentx/tools/skill/__init__.py +4 -0
  325. langchain_agentx/tools/skill/argument_substitution.py +80 -0
  326. langchain_agentx/tools/skill/loader.py +196 -0
  327. langchain_agentx/tools/skill/models.py +88 -0
  328. langchain_agentx/tools/skill/policy.py +80 -0
  329. langchain_agentx/tools/skill/prompt.py +35 -0
  330. langchain_agentx/tools/skill/tool.py +222 -0
  331. langchain_agentx/utils/__init__.py +0 -0
  332. langchain_agentx/utils/cwd.py +124 -0
  333. langchain_agentx/utils/host_platform.py +112 -0
  334. langchain_agentx/utils/path_hierarchy.py +48 -0
  335. langchain_agentx/utils/path_user_input.py +66 -0
  336. langchain_agentx/utils/rg_executable.py +18 -0
  337. langchain_agentx/utils/subprocess_text.py +101 -0
  338. langchain_agentx/utils/temp_paths.py +77 -0
  339. langchain_agentx/utils/unc_path.py +25 -0
  340. langchain_agentx/utils/win_reserved_paths.py +51 -0
  341. langchain_agentx/workflow/__init__.py +7 -0
  342. langchain_agentx/workflow/base.py +97 -0
  343. langchain_agentx/workflow/batch.py +55 -0
  344. langchain_agentx/workflow/dag.py +54 -0
  345. langchain_agentx/workspace/__init__.py +13 -0
  346. langchain_agentx/workspace/config.py +140 -0
  347. langchain_agentx/workspace/path_key_normalizer.py +30 -0
  348. langchain_agentx/workspace/resolver.py +74 -0
  349. langchain_agentx/workspace/validators.py +41 -0
  350. langchain_agentx_python-0.1.dist-info/LICENSE +201 -0
  351. langchain_agentx_python-0.1.dist-info/METADATA +513 -0
  352. langchain_agentx_python-0.1.dist-info/RECORD +354 -0
  353. langchain_agentx_python-0.1.dist-info/WHEEL +5 -0
  354. 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
+ ]