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,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,9 @@
1
+ """
2
+ tools/glob — GlobRuntimeTool 包
3
+
4
+ GlobRuntimeTool 是工具体系 P2 只读搜索工具,负责按文件名模式匹配路径。
5
+ """
6
+
7
+ from .tool import GlobRuntimeTool
8
+
9
+ __all__ = ["GlobRuntimeTool"]
@@ -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
@@ -0,0 +1,7 @@
1
+ """GrepRuntimeTool — 基于 ripgrep 的内容搜索。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .tool import GrepRuntimeTool
6
+
7
+ __all__ = ["GrepRuntimeTool"]