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,571 @@
1
+ """
2
+ tools/bash/ast_security.py — BashRuntimeTool v2 AST 分析
3
+
4
+ 职责:
5
+ 使用 tree-sitter 对 bash 命令做结构化分析,提供:
6
+ - 顶层子命令拆分(pipeline / list)
7
+ - 命令替换 / 进程替换 / subshell / command group 检测
8
+ - 包装命令归一化(env / command / builtin / nohup)
9
+ - 嵌套 shell 执行检测(bash -c / sh -c / zsh -c)
10
+ - 输出重定向检测(辅助只读/写入判定)
11
+
12
+ 对照 CC:
13
+ - bashSecurity.ts → 结构级危险模式识别
14
+ - bashCommandHelpers.ts → 子命令拆分与分段授权
15
+ - ParsedCommand / treeSitterAnalysis → 结构化命令理解
16
+
17
+ 实现约束:
18
+ - 采用 OOP 风格,避免散落函数主导的实现
19
+ - 保持 fail-open:AST 失败时回退到轻量启发式,而不是阻塞工具
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ import re
25
+ import shlex
26
+ from dataclasses import dataclass
27
+ from functools import lru_cache
28
+
29
+ from tree_sitter import Node
30
+ from tree_sitter_languages import get_parser
31
+
32
+
33
+ @dataclass(frozen=True)
34
+ class BashSimpleCommand:
35
+ """单个 simple command 的结构摘要。"""
36
+
37
+ text: str
38
+ argv: tuple[str, ...]
39
+ base_command: str | None
40
+ normalized_base_command: str | None
41
+
42
+
43
+ @dataclass(frozen=True)
44
+ class BashAstAnalysis:
45
+ """
46
+ Bash AST 分析结果。
47
+
48
+ `permission_segments` 是 v2 最关键字段:用于对顶层 pipeline / list
49
+ 做逐段授权,对齐 CC `checkCommandOperatorPermissions()`。
50
+ """
51
+
52
+ command: str
53
+ parsed: bool
54
+ has_syntax_error: bool
55
+ permission_segments: tuple[str, ...]
56
+ simple_commands: tuple[BashSimpleCommand, ...]
57
+ has_pipeline: bool
58
+ has_logical_list: bool
59
+ has_subshell: bool
60
+ has_command_group: bool
61
+ has_command_substitution: bool
62
+ has_process_substitution: bool
63
+ has_output_redirection: bool
64
+ output_redirection_paths: tuple[str, ...]
65
+ nested_shell_command: str | None = None
66
+
67
+ @property
68
+ def normalized_base_commands(self) -> tuple[str, ...]:
69
+ return tuple(
70
+ cmd.normalized_base_command
71
+ for cmd in self.simple_commands
72
+ if cmd.normalized_base_command
73
+ )
74
+
75
+ @property
76
+ def has_unsafe_compound_structure(self) -> bool:
77
+ return self.has_subshell or self.has_command_group
78
+
79
+
80
+ class BashCommandNameNormalizer:
81
+ """
82
+ 负责将命令文本归一化到“真实基础命令”。
83
+
84
+ 对齐 CC 中 wrapper stripping 的核心思想:
85
+ - 跳过 `FOO=1` 这类环境赋值
86
+ - 跳过 `env` 自身和其 flag/赋值
87
+ - 跳过 `command` / `builtin` / `nohup`
88
+ - 保留真实被执行的主命令
89
+ """
90
+
91
+ _ENV_ASSIGNMENT_RE = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*=.*$")
92
+ _SAFE_WRAPPERS = frozenset({"command", "builtin", "nohup", "time"})
93
+
94
+ def __init__(self, wrapper_stripper: "BashWrapperStripper | None" = None) -> None:
95
+ self._wrapper_stripper = wrapper_stripper or BashWrapperStripper()
96
+
97
+ def normalize(self, command_text: str) -> str | None:
98
+ return self.normalize_argv(self._split(command_text))
99
+
100
+ def normalize_argv(self, tokens: list[str]) -> str | None:
101
+ effective_tokens = list(tokens)
102
+ while effective_tokens and self._is_assignment(effective_tokens[0]):
103
+ effective_tokens = effective_tokens[1:]
104
+ stripped_tokens = self._wrapper_stripper.strip(effective_tokens)
105
+ if not stripped_tokens:
106
+ return None
107
+
108
+ return stripped_tokens[0].rsplit("/", 1)[-1]
109
+
110
+ def _skip_env_arguments(self, tokens: list[str], start_idx: int) -> int:
111
+ idx = start_idx
112
+ while idx < len(tokens):
113
+ candidate = tokens[idx]
114
+ if candidate == "--":
115
+ return idx + 1
116
+ if candidate.startswith("-") or self._is_assignment(candidate):
117
+ idx += 1
118
+ continue
119
+ return idx
120
+ return idx
121
+
122
+ def _is_assignment(self, token: str) -> bool:
123
+ return bool(self._ENV_ASSIGNMENT_RE.match(token))
124
+
125
+ @staticmethod
126
+ def _split(command_text: str) -> list[str]:
127
+ try:
128
+ return shlex.split(command_text)
129
+ except ValueError:
130
+ return command_text.split()
131
+
132
+
133
+ class BashAstNodeHelper:
134
+ """封装 tree-sitter 节点上的常用操作。"""
135
+
136
+ def __init__(self, source: str) -> None:
137
+ self._source = source
138
+ self._source_bytes = source.encode("utf8")
139
+
140
+ def text(self, node: Node) -> str:
141
+ """
142
+ 安全提取 AST 节点文本。
143
+
144
+ 重要:tree-sitter 的 `start_byte/end_byte` 是按 UTF-8 字节偏移计算的,
145
+ 不能直接对 Python `str` 做切片,否则中文等多字节字符会导致偏移错误。
146
+ """
147
+ snippet_bytes = self._source_bytes[node.start_byte:node.end_byte]
148
+ return snippet_bytes.decode("utf8")
149
+
150
+ def contains_node_type(self, node: Node, node_types: set[str]) -> bool:
151
+ if node.type in node_types:
152
+ return True
153
+ return any(self.contains_node_type(child, node_types) for child in node.children)
154
+
155
+ def contains_output_redirection(self, node: Node) -> bool:
156
+ if node.type == "file_redirect":
157
+ redirect_text = self.text(node).lstrip()
158
+ # `>` / `>>` / `1>` / `2>` / `&>` / `<>` 都视为写操作;纯 `<` 不算
159
+ if ">" in redirect_text:
160
+ return True
161
+ return any(self.contains_output_redirection(child) for child in node.children)
162
+
163
+ def command_name_text(self, node: Node) -> str | None:
164
+ for child in node.children:
165
+ if child.type == "command_name":
166
+ return self.text(child).strip().rsplit("/", 1)[-1]
167
+ return None
168
+
169
+ def argument_like_texts(self, node: Node) -> list[str]:
170
+ values: list[str] = []
171
+ for child in node.children:
172
+ if child.type in {
173
+ "command_name",
174
+ "word",
175
+ "string",
176
+ "raw_string",
177
+ "number",
178
+ "concatenation",
179
+ "expansion",
180
+ "simple_expansion",
181
+ "command_substitution",
182
+ "process_substitution",
183
+ }:
184
+ values.append(self.text(child).strip())
185
+ return [value for value in values if value]
186
+
187
+ def collect_output_redirection_paths(self, node: Node) -> list[str]:
188
+ paths: list[str] = []
189
+ self._walk_output_redirections(node, paths)
190
+ return paths
191
+
192
+ def _walk_output_redirections(self, node: Node, paths: list[str]) -> None:
193
+ if node.type == "file_redirect":
194
+ redirect_text = self.text(node).lstrip()
195
+ if ">" in redirect_text:
196
+ named_children = node.named_children
197
+ if named_children:
198
+ path_text = self.text(named_children[-1]).strip()
199
+ if path_text:
200
+ paths.append(path_text)
201
+ for child in node.children:
202
+ self._walk_output_redirections(child, paths)
203
+
204
+
205
+ class BashPermissionSegmenter:
206
+ """
207
+ 提取用于权限检查的顶层命令段。
208
+
209
+ 对齐 CC `checkCommandOperatorPermissions()`:
210
+ - program/list/pipeline 递归展开
211
+ - command/redirected_statement/subshell/compound_statement 保持原段文本
212
+ """
213
+
214
+ def __init__(self, node_helper: BashAstNodeHelper) -> None:
215
+ self._node_helper = node_helper
216
+
217
+ def collect_segments(self, node: Node) -> list[str]:
218
+ if node.type == "program":
219
+ return self._collect_from_container(node)
220
+
221
+ if node.type in {"pipeline", "list"}:
222
+ return self._collect_from_container(node)
223
+
224
+ if node.type in {"command", "redirected_statement", "subshell", "compound_statement"}:
225
+ text = self._node_helper.text(node).strip()
226
+ return [text] if text else []
227
+
228
+ segments = []
229
+ for child in node.named_children:
230
+ segments.extend(self.collect_segments(child))
231
+ if segments:
232
+ return [segment for segment in segments if segment.strip()]
233
+
234
+ text = self._node_helper.text(node).strip()
235
+ return [text] if text else []
236
+
237
+ def _collect_from_container(self, node: Node) -> list[str]:
238
+ segments: list[str] = []
239
+ for child in node.named_children:
240
+ segments.extend(self.collect_segments(child))
241
+ return [segment for segment in segments if segment.strip()]
242
+
243
+
244
+ class BashSimpleCommandCollector:
245
+ """提取 AST 中全部 simple command。"""
246
+
247
+ def __init__(
248
+ self,
249
+ node_helper: BashAstNodeHelper,
250
+ normalizer: BashCommandNameNormalizer,
251
+ argv_extractor: "BashArgvExtractor | None" = None,
252
+ ) -> None:
253
+ self._node_helper = node_helper
254
+ self._normalizer = normalizer
255
+ self._argv_extractor = argv_extractor or BashArgvExtractor(node_helper)
256
+
257
+ def collect(self, node: Node) -> list[BashSimpleCommand]:
258
+ result: list[BashSimpleCommand] = []
259
+ self._walk(node, result)
260
+ return result
261
+
262
+ def _walk(self, node: Node, result: list[BashSimpleCommand]) -> None:
263
+ if node.type == "command":
264
+ text = self._node_helper.text(node).strip()
265
+ argv = tuple(self._argv_extractor.extract(node))
266
+ result.append(
267
+ BashSimpleCommand(
268
+ text=text,
269
+ argv=argv,
270
+ base_command=self._node_helper.command_name_text(node),
271
+ normalized_base_command=self._normalizer.normalize_argv(list(argv)),
272
+ )
273
+ )
274
+ for child in node.children:
275
+ self._walk(child, result)
276
+
277
+
278
+ class BashArgvExtractor:
279
+ """
280
+ 从 AST command 节点直接提取 argv。
281
+
282
+ 对齐 CC validateSinglePathCommandArgv 的思路:优先使用 AST 已解析的参数,
283
+ 避免 shell-quote / shlex 在复杂 quoting 下的歧义。
284
+ """
285
+
286
+ def __init__(self, node_helper: BashAstNodeHelper) -> None:
287
+ self._node_helper = node_helper
288
+
289
+ def extract(self, command_node: Node) -> list[str]:
290
+ return self._node_helper.argument_like_texts(command_node)
291
+
292
+
293
+ class BashWrapperStripper:
294
+ """
295
+ AST argv 级 wrapper stripping。
296
+
297
+ 对照 CC `stripWrappersFromArgv()`,先实现最常见且高价值的 wrappers:
298
+ `time` / `nohup` / `timeout` / `nice` / `stdbuf` / `env` / `command` / `builtin`
299
+ """
300
+
301
+ def strip(self, argv: list[str]) -> list[str]:
302
+ tokens = list(argv)
303
+ while tokens:
304
+ head = tokens[0]
305
+ if head in {"time", "nohup", "command", "builtin"}:
306
+ tokens = tokens[2:] if len(tokens) > 1 and tokens[1] == "--" else tokens[1:]
307
+ continue
308
+ if head == "timeout":
309
+ next_idx = self._skip_timeout(tokens)
310
+ if next_idx <= 0 or next_idx >= len(tokens):
311
+ return tokens
312
+ tokens = tokens[next_idx + 1:]
313
+ continue
314
+ if head == "nice":
315
+ next_idx = self._skip_nice(tokens)
316
+ if next_idx <= 0 or next_idx >= len(tokens):
317
+ return tokens
318
+ tokens = tokens[next_idx:]
319
+ continue
320
+ if head == "stdbuf":
321
+ next_idx = self._skip_stdbuf(tokens)
322
+ if next_idx <= 0 or next_idx >= len(tokens):
323
+ return tokens
324
+ tokens = tokens[next_idx:]
325
+ continue
326
+ if head == "env":
327
+ next_idx = self._skip_env(tokens)
328
+ if next_idx <= 0 or next_idx >= len(tokens):
329
+ return tokens
330
+ tokens = tokens[next_idx:]
331
+ continue
332
+ break
333
+ return tokens
334
+
335
+ def _skip_timeout(self, argv: list[str]) -> int:
336
+ i = 1
337
+ while i < len(argv):
338
+ arg = argv[i]
339
+ next_arg = argv[i + 1] if i + 1 < len(argv) else None
340
+ if arg in {"--foreground", "--preserve-status", "--verbose"}:
341
+ i += 1
342
+ elif arg.startswith("--kill-after=") or arg.startswith("--signal="):
343
+ i += 1
344
+ elif arg in {"--kill-after", "--signal"} and next_arg:
345
+ i += 2
346
+ elif arg == "--":
347
+ i += 1
348
+ break
349
+ elif arg.startswith("--"):
350
+ return -1
351
+ elif arg == "-v":
352
+ i += 1
353
+ elif arg in {"-k", "-s"} and next_arg:
354
+ i += 2
355
+ elif re.match(r"^-[ks][A-Za-z0-9_.+-]+$", arg):
356
+ i += 1
357
+ elif arg.startswith("-"):
358
+ return -1
359
+ else:
360
+ break
361
+ return i
362
+
363
+ def _skip_nice(self, argv: list[str]) -> int:
364
+ if len(argv) >= 3 and argv[1] == "-n" and re.match(r"^-?\d+$", argv[2]):
365
+ return 4 if len(argv) > 3 and argv[3] == "--" else 3
366
+ if len(argv) >= 2 and re.match(r"^-\d+$", argv[1]):
367
+ return 3 if len(argv) > 2 and argv[2] == "--" else 2
368
+ return 2 if len(argv) > 1 and argv[1] == "--" else 1
369
+
370
+ def _skip_stdbuf(self, argv: list[str]) -> int:
371
+ i = 1
372
+ while i < len(argv):
373
+ arg = argv[i]
374
+ if re.match(r"^-[ioe]$", arg) and i + 1 < len(argv):
375
+ i += 2
376
+ elif re.match(r"^-[ioe].", arg):
377
+ i += 1
378
+ elif re.match(r"^--(input|output|error)=", arg):
379
+ i += 1
380
+ elif arg.startswith("-"):
381
+ return -1
382
+ else:
383
+ break
384
+ return i if i > 1 and i < len(argv) else -1
385
+
386
+ def _skip_env(self, argv: list[str]) -> int:
387
+ i = 1
388
+ while i < len(argv):
389
+ arg = argv[i]
390
+ if "=" in arg and not arg.startswith("-"):
391
+ i += 1
392
+ elif arg in {"-i", "-0", "-v"}:
393
+ i += 1
394
+ elif arg == "-u" and i + 1 < len(argv):
395
+ i += 2
396
+ elif arg.startswith("-"):
397
+ return -1
398
+ else:
399
+ break
400
+ return i if i < len(argv) else -1
401
+
402
+
403
+ class BashAstAdvisor:
404
+ """
405
+ 基于 AST 分析结果给出权限建议。
406
+
407
+ 对齐 CC 的思路:复杂 shell 结构优先 ask,而不是盲目依赖字符串规则。
408
+ """
409
+
410
+ def get_approval_reason(self, analysis: BashAstAnalysis) -> str | None:
411
+ if analysis.has_subshell:
412
+ return "This command uses a subshell `(...)`, which requires approval for safety."
413
+ if analysis.has_command_group:
414
+ return "This command uses a command group `{ ...; }`, which requires approval for safety."
415
+ if analysis.has_command_substitution:
416
+ return "This command uses command substitution (`$()` or backticks), which requires approval for safety."
417
+ if analysis.has_process_substitution:
418
+ return "This command uses process substitution (`<()` or `>()`), which requires approval for safety."
419
+ if analysis.nested_shell_command:
420
+ return (
421
+ f"This command launches a nested shell via `{analysis.nested_shell_command} -c`, "
422
+ "which requires approval for safety."
423
+ )
424
+ if analysis.has_syntax_error:
425
+ return "This command has shell syntax errors and requires approval before execution."
426
+
427
+ normalized = analysis.normalized_base_commands
428
+ if sum(1 for command in normalized if command == "cd") > 1:
429
+ return "Multiple directory changes in one command require approval for clarity."
430
+
431
+ if "cd" in normalized and "git" in normalized and len(analysis.permission_segments) > 1:
432
+ return (
433
+ "Compound commands mixing `cd` and `git` across segments require approval "
434
+ "to avoid repository-boundary confusion."
435
+ )
436
+
437
+ return None
438
+
439
+ def requires_write_permissions(self, analysis: BashAstAnalysis) -> bool:
440
+ return analysis.has_output_redirection
441
+
442
+
443
+ class BashAstAnalyzer:
444
+ """
445
+ Bash AST 主分析器。
446
+
447
+ 这是 OOP 入口,供 `BashRuntimeTool` 调用。
448
+ """
449
+
450
+ _SHELL_WRAPPER_COMMANDS = frozenset({"bash", "sh", "zsh", "fish"})
451
+
452
+ def __init__(self) -> None:
453
+ self._normalizer = BashCommandNameNormalizer()
454
+ self._advisor = BashAstAdvisor()
455
+
456
+ def analyze(self, command: str) -> BashAstAnalysis:
457
+ stripped = command.strip()
458
+ if not stripped:
459
+ return BashAstAnalysis(
460
+ command=command,
461
+ parsed=True,
462
+ has_syntax_error=False,
463
+ permission_segments=(),
464
+ simple_commands=(),
465
+ has_pipeline=False,
466
+ has_logical_list=False,
467
+ has_subshell=False,
468
+ has_command_group=False,
469
+ has_command_substitution=False,
470
+ has_process_substitution=False,
471
+ has_output_redirection=False,
472
+ output_redirection_paths=(),
473
+ )
474
+
475
+ try:
476
+ parser = self._get_bash_parser()
477
+ tree = parser.parse(command.encode("utf-8"))
478
+ root = tree.root_node
479
+ except Exception:
480
+ return self._build_fallback_analysis(command)
481
+
482
+ node_helper = BashAstNodeHelper(command)
483
+ segmenter = BashPermissionSegmenter(node_helper)
484
+ collector = BashSimpleCommandCollector(node_helper, self._normalizer)
485
+
486
+ simple_commands = tuple(collector.collect(root))
487
+ permission_segments = tuple(segmenter.collect_segments(root))
488
+
489
+ return BashAstAnalysis(
490
+ command=command,
491
+ parsed=True,
492
+ has_syntax_error=root.has_error,
493
+ permission_segments=permission_segments or ((stripped,) if stripped else ()),
494
+ simple_commands=simple_commands,
495
+ has_pipeline=node_helper.contains_node_type(root, {"pipeline"}),
496
+ has_logical_list=node_helper.contains_node_type(root, {"list"}),
497
+ has_subshell=node_helper.contains_node_type(root, {"subshell"}),
498
+ has_command_group=node_helper.contains_node_type(root, {"compound_statement"}),
499
+ has_command_substitution=node_helper.contains_node_type(root, {"command_substitution"}),
500
+ has_process_substitution=node_helper.contains_node_type(root, {"process_substitution"}),
501
+ has_output_redirection=node_helper.contains_output_redirection(root),
502
+ output_redirection_paths=tuple(node_helper.collect_output_redirection_paths(root)),
503
+ nested_shell_command=self._detect_nested_shell(simple_commands),
504
+ )
505
+
506
+ def get_approval_reason(self, analysis: BashAstAnalysis) -> str | None:
507
+ return self._advisor.get_approval_reason(analysis)
508
+
509
+ def requires_write_permissions(self, analysis: BashAstAnalysis) -> bool:
510
+ return self._advisor.requires_write_permissions(analysis)
511
+
512
+ def _build_fallback_analysis(self, command: str) -> BashAstAnalysis:
513
+ stripped = command.strip()
514
+ return BashAstAnalysis(
515
+ command=command,
516
+ parsed=False,
517
+ has_syntax_error=False,
518
+ permission_segments=(stripped,) if stripped else (),
519
+ simple_commands=(),
520
+ has_pipeline="|" in stripped,
521
+ has_logical_list="&&" in stripped or "||" in stripped or ";" in stripped,
522
+ has_subshell=False,
523
+ has_command_group=False,
524
+ has_command_substitution=False,
525
+ has_process_substitution=False,
526
+ has_output_redirection=">" in stripped,
527
+ output_redirection_paths=(),
528
+ )
529
+
530
+ def _detect_nested_shell(
531
+ self,
532
+ simple_commands: tuple[BashSimpleCommand, ...],
533
+ ) -> str | None:
534
+ for command in simple_commands:
535
+ normalized = command.normalized_base_command
536
+ if normalized not in self._SHELL_WRAPPER_COMMANDS:
537
+ continue
538
+ tokens = self._safe_split(command.text)
539
+ if "-c" in tokens:
540
+ return normalized
541
+ return None
542
+
543
+ @staticmethod
544
+ def _safe_split(command_text: str) -> list[str]:
545
+ try:
546
+ return shlex.split(command_text)
547
+ except ValueError:
548
+ return command_text.split()
549
+
550
+ @staticmethod
551
+ @lru_cache(maxsize=1)
552
+ def _get_bash_parser():
553
+ return get_parser("bash")
554
+
555
+
556
+ _DEFAULT_ANALYZER = BashAstAnalyzer()
557
+
558
+
559
+ def analyze_command_ast(command: str) -> BashAstAnalysis:
560
+ """兼容入口:分析 bash 命令 AST。"""
561
+ return _DEFAULT_ANALYZER.analyze(command)
562
+
563
+
564
+ def get_ast_approval_reason(analysis: BashAstAnalysis) -> str | None:
565
+ """兼容入口:返回 AST 级审批原因。"""
566
+ return _DEFAULT_ANALYZER.get_approval_reason(analysis)
567
+
568
+
569
+ def command_requires_write_permissions(command: str) -> bool:
570
+ """兼容入口:判断命令是否需要写权限。"""
571
+ return _DEFAULT_ANALYZER.requires_write_permissions(analyze_command_ast(command))