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,41 @@
1
+ """
2
+ bash_runtime_contract.py — Bash 跨平台运行时冻结契约(只读常量)
3
+
4
+ 职责:
5
+ 集中记录 Shell 环境变量解析顺序、cwd 双轨策略、Windows 持久会话默认值等
6
+ **与 CC/exec-plan 对齐的冻结项**,供 `shell_locator`、`BashBackend` 与 CR 对齐;
7
+ 避免散落在多文件中的口头约定。
8
+
9
+ 链路位置:
10
+ 不参与 BashBackend 热路径;`shell_locator` 导入 **SHELL_ENV_PRECEDENCE**;
11
+ `BashBackend.__init__` 的 win32 持久会话策略与本模块 **WIN32_DEFAULT_USE_PERSISTENT_SESSION**
12
+ 注释一致。
13
+
14
+ 当前裁剪范围:
15
+ 不包含 Shell 解析、Popen、cwd 文件或 fd3 实现;不包含对 CC 源码的导入。
16
+
17
+ 设计 SSOT:
18
+ docs/design-docs/tool-design/bash-tool-cross-platform.md(§1.1–§1.4、§3.0)
19
+
20
+ CC 对照(实施前阅读,路径相对 CC 检出 src/):
21
+ utils/Shell.ts — findSuitableShell、spawn 与 SHELL 环境
22
+ utils/shell/bashProvider.ts — shellCwdFilePath / cwdFilePath 双轨
23
+ """
24
+
25
+ from __future__ import annotations
26
+
27
+ #: 显式覆盖 POSIX shell 可执行文件时,环境变量尝试顺序(先声明者优先;逐项校验见 SSOT §1.2)。
28
+ SHELL_ENV_PRECEDENCE: tuple[str, ...] = (
29
+ "LANGCHAIN_AGENTX_SHELL",
30
+ "CLAUDE_CODE_SHELL",
31
+ "SHELL",
32
+ )
33
+
34
+ #: Linux/Unix 上 cwd 回传冻结策略(须保持 fd3 + pass_fds,不得未经 RFC 改为仅文件)。
35
+ CWD_STRATEGY_POSIX = "fd3_pass_fds"
36
+
37
+ #: Windows 上 cwd 回传目标策略(临时文件 + pwd -P;见 `cwd_reporter.py` / exec-plan)。
38
+ CWD_STRATEGY_WIN32 = "temp_file_pwd_p"
39
+
40
+ #: Windows 上 **默认** 关闭真持久 bash 会话(exec-plan:直至持久会话冒烟通过后再评估默认值)。
41
+ WIN32_DEFAULT_USE_PERSISTENT_SESSION = False
@@ -0,0 +1,95 @@
1
+ """
2
+ cwd_reporter.py — Bash 执行后 cwd 回传(Unix fd3 / Windows 临时文件)
3
+
4
+ 职责:
5
+ 为 `BashBackend._execute_raw` / `_aexecute_raw` 生成包装脚本:Unix 用 **fd3 + pass_fds**;
6
+ Windows 用 **临时文件 + `pwd -P`**(禁止 pass_fds),与 CC `bashProvider` 双轨语义对齐。
7
+
8
+ 链路位置:
9
+ 仅被 `backend.py` 调用;不读工具入参、不碰权限链。
10
+
11
+ 当前裁剪范围:
12
+ 不处理流式 `_stream_execute_raw_iter`;不处理沙箱 backend 内部;UNC 临时目录尽力而为
13
+ (`native_path_for_bash_redirect` 覆盖常见 `//server/share` 形态)。
14
+
15
+ 设计 SSOT:
16
+ docs/design-docs/tool-design/bash-tool-cross-platform.md(Phase 2)
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ import os
22
+ from pathlib import Path
23
+
24
+
25
+ def bash_single_quoted(s: str) -> str:
26
+ """Bash 单引号字面量(可嵌入 `pwd -P >| ...` 等)。"""
27
+ return "'" + s.replace("'", "'\\''") + "'"
28
+
29
+
30
+ def native_path_for_bash_redirect(win_native: str) -> str:
31
+ """
32
+ 将 **本机绝对路径** 转为 Git Bash / MSYS 下用于重定向的 POSIX 形态。
33
+
34
+ - `C:\\Users\\x` → `/c/Users/x`
35
+ - `\\\\server\\share\\a` → `//server/share/a`
36
+ - 长路径前缀 `\\\\?\\` 会剥离后再转换(尽力而为)。
37
+ """
38
+ p = Path(win_native).resolve()
39
+ s = os.fspath(p)
40
+ norm = s.replace("/", "\\")
41
+ if norm.startswith("\\\\?\\"):
42
+ norm = norm[4:]
43
+ if norm.upper().startswith("UNC\\"):
44
+ norm = "\\" + norm[3:]
45
+ if norm.startswith("\\\\"):
46
+ body = norm[2:].replace("\\", "/").strip("/")
47
+ return "//" + body if body else "//"
48
+ if len(norm) >= 2 and norm[1] == ":":
49
+ letter = norm[0].lower()
50
+ tail = norm[2:].replace("\\", "/")
51
+ if not tail.startswith("/"):
52
+ tail = "/" + tail.lstrip("/")
53
+ return f"/{letter}{tail}"
54
+ return norm.replace("\\", "/")
55
+
56
+
57
+ def read_cwd_file(native_path: str) -> str | None:
58
+ """读取 `pwd -P` 写入的临时文件,返回最后一行非空 cwd,失败为 None。"""
59
+ try:
60
+ raw = Path(native_path).read_text(encoding="utf-8", errors="replace").strip()
61
+ except OSError:
62
+ return None
63
+ if not raw:
64
+ return None
65
+ lines = [ln.strip() for ln in raw.splitlines() if ln.strip()]
66
+ return lines[-1] if lines else None
67
+
68
+
69
+ class BashCwdReporter:
70
+ """构造「执行用户命令 + 回写 cwd」的包装脚本(双平台)。"""
71
+
72
+ __slots__ = ()
73
+
74
+ @staticmethod
75
+ def wrap_with_fd3(command: str, write_fd: int) -> str:
76
+ """Unix:通过 fd3 写 `$PWD`(与既有 `backend._execute_raw` 一致)。"""
77
+ return (
78
+ f"exec 3>&{write_fd}\n"
79
+ f"{command}\n"
80
+ f'__EC=$?; echo "$PWD" >&3; exit $__EC'
81
+ )
82
+
83
+ @staticmethod
84
+ def wrap_with_cwd_file(command: str, posix_target_single_quoted: str) -> str:
85
+ """
86
+ Windows:用户命令在同一 shell 内执行后 `pwd -P` 覆写到目标文件。
87
+
88
+ `posix_target_single_quoted` 须已用 `bash_single_quoted(...)` 包一层。
89
+ """
90
+ return (
91
+ f"{command}\n"
92
+ f"__EC=$?\n"
93
+ f"pwd -P >| {posix_target_single_quoted}\n"
94
+ f"exit $__EC\n"
95
+ )
@@ -0,0 +1,71 @@
1
+ """
2
+ tools/bash/limits.py — BashRuntimeTool 上限配置
3
+
4
+ 对应 CC prompt.ts 中的 getDefaultBashTimeoutMs() / getMaxBashTimeoutMs()
5
+ 以及 BashTool.tsx 中的 maxResultSizeChars=30_000。
6
+
7
+ 全部支持环境变量覆盖,便于测试 mock 和生产调优。
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import os
13
+
14
+ # ---------------------------------------------------------------------------
15
+ # 超时配置
16
+ # ---------------------------------------------------------------------------
17
+
18
+ DEFAULT_TIMEOUT_SEC = int(os.getenv("BASH_TOOL_DEFAULT_TIMEOUT", "120"))
19
+ """默认超时秒数(对应 CC getDefaultBashTimeoutMs() = 120000ms)。"""
20
+
21
+ MAX_TIMEOUT_SEC = int(os.getenv("BASH_TOOL_MAX_TIMEOUT", "600"))
22
+ """最大超时秒数(对应 CC getMaxBashTimeoutMs() = 600000ms / 10min)。"""
23
+
24
+ # ---------------------------------------------------------------------------
25
+ # 输出限制
26
+ # ---------------------------------------------------------------------------
27
+
28
+ MAX_OUTPUT_CHARS = int(os.getenv("BASH_TOOL_MAX_OUTPUT_CHARS", str(30_000)))
29
+ """单次输出字符上限(对应 CC maxResultSizeChars=30_000)。"""
30
+
31
+ # ---------------------------------------------------------------------------
32
+ # 安全阈值
33
+ # ---------------------------------------------------------------------------
34
+
35
+ SLEEP_BLOCK_THRESHOLD_SEC = int(os.getenv("BASH_TOOL_SLEEP_BLOCK_SEC", "30"))
36
+ """sleep 命令阻断阈值(秒)。超过此值提示使用 run_in_background(对应 CC detectBlockedSleepPattern)。"""
37
+
38
+ # ---------------------------------------------------------------------------
39
+ # 后台任务
40
+ # ---------------------------------------------------------------------------
41
+
42
+ BACKGROUND_TASK_OUTPUT_DIR = os.path.expanduser(
43
+ os.getenv(
44
+ "BASH_TOOL_BACKGROUND_OUTPUT_DIR",
45
+ "~/.cache/langchain_agentx/tasks",
46
+ )
47
+ )
48
+ """后台任务输出目录。"""
49
+
50
+ AUTO_BACKGROUND_ENABLED = os.getenv("BASH_TOOL_AUTO_BACKGROUND_ENABLED", "1") == "1"
51
+ """是否开启自动后台化策略。"""
52
+
53
+ AUTO_BACKGROUND_TIMEOUT_SEC = int(
54
+ os.getenv("BASH_TOOL_AUTO_BACKGROUND_TIMEOUT_SEC", "180")
55
+ )
56
+ """触发自动后台化的 timeout 阈值(秒)。"""
57
+
58
+
59
+ def get_bash_limits() -> dict:
60
+ """
61
+ 返回当前有效配置(动态读取,支持运行时环境变量覆盖)。
62
+ """
63
+ return {
64
+ "default_timeout_sec": DEFAULT_TIMEOUT_SEC,
65
+ "max_timeout_sec": MAX_TIMEOUT_SEC,
66
+ "max_output_chars": MAX_OUTPUT_CHARS,
67
+ "sleep_block_threshold_sec": SLEEP_BLOCK_THRESHOLD_SEC,
68
+ "background_task_output_dir": BACKGROUND_TASK_OUTPUT_DIR,
69
+ "auto_background_enabled": AUTO_BACKGROUND_ENABLED,
70
+ "auto_background_timeout_sec": AUTO_BACKGROUND_TIMEOUT_SEC,
71
+ }
@@ -0,0 +1,282 @@
1
+ """
2
+ tools/bash/mode_validation.py — BashRuntimeTool:mode-aware 权限分流(P2 细粒度版)
3
+
4
+ 职责:
5
+ 与 CC `modeValidation.ts` 对齐方向,在命令级基础上增加:
6
+ - 子模式 / 参数语义(acceptEdits 何时可自动放行)
7
+ - 可解释结论(reason_code + message,便于归因与降误判)
8
+
9
+ 当前实现:
10
+ - `acceptEdits`:按命令 + flag + 参数形态分流;显式拒绝高危 rm/chmod/chown 递归等
11
+ - `dontAsk`:将 ask 降级为 deny
12
+ - `bypassPermissions`:直接 allow(仅跳过权限层,不影响 validate_input)
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import re
18
+ import shlex
19
+ from dataclasses import dataclass
20
+ from typing import Literal
21
+
22
+ from langchain_agentx.tool_runtime.models import AuthorizationDecision, ToolExecutionContext
23
+
24
+
25
+ BashPermissionMode = Literal["default", "acceptEdits", "dontAsk", "bypassPermissions"]
26
+
27
+
28
+ @dataclass(frozen=True)
29
+ class BashModeDecision:
30
+ behavior: Literal["passthrough", "allow", "deny"]
31
+ message: str | None = None
32
+ policy_id: str | None = None
33
+
34
+
35
+ @dataclass(frozen=True)
36
+ class AcceptEditsEvaluation:
37
+ """acceptEdits 细粒度评估结果(可对接日志 / 策略归因)。"""
38
+
39
+ allowed: bool
40
+ reason_code: str
41
+ message: str
42
+
43
+
44
+ class BashPermissionModeValidator:
45
+ """模式级权限协作者(P2:参数语义 + 场景约束)。"""
46
+
47
+ _ACCEPT_EDITS_ALLOWED_COMMANDS = frozenset({
48
+ "mkdir", "touch", "rm", "rmdir", "mv", "cp", "sed", "ln", "install",
49
+ "chmod", "chown",
50
+ })
51
+
52
+ _CHMOD_DENY_RECURSIVE = frozenset({"-R", "-r", "--recursive"})
53
+ _CHOWN_DENY_RECURSIVE = frozenset({"-R", "-r", "--recursive"})
54
+ _CP_DENY_FLAGS = frozenset({
55
+ "--reflink=always", "--parents", "--link", "-l", "--symbolic-link", "-s",
56
+ "--backup", "--remove-destination",
57
+ })
58
+ _MV_DENY_FLAGS = frozenset({"-f", "--force"})
59
+ _MKDIR_DENY_FLAGS = frozenset({"-Z", "--context"})
60
+ _INSTALL_DENY_FLAGS = frozenset({"-D", "--strip", "-s", "-S", "--strip-program"})
61
+ _CHOWN_DENY_FLAGS = frozenset({"--reference", "--from"})
62
+
63
+ def get_mode(self, ctx: ToolExecutionContext) -> BashPermissionMode:
64
+ tool_flags = ctx.tool_flags or {}
65
+ raw_mode = tool_flags.get("permission_mode", "default")
66
+ if raw_mode in {"acceptEdits", "dontAsk", "bypassPermissions"}:
67
+ return raw_mode
68
+ return "default"
69
+
70
+ def check_preflight(
71
+ self,
72
+ command: str,
73
+ ctx: ToolExecutionContext,
74
+ ) -> BashModeDecision:
75
+ mode = self.get_mode(ctx)
76
+ if mode == "bypassPermissions":
77
+ return BashModeDecision(
78
+ behavior="allow",
79
+ message="Permission checks bypassed by current permission mode.",
80
+ policy_id="permission_mode",
81
+ )
82
+ return BashModeDecision(behavior="passthrough")
83
+
84
+ def evaluate_accept_edits(self, command: str) -> AcceptEditsEvaluation:
85
+ """显式评估 acceptEdits 是否允许自动放行(供调试与扩展)。"""
86
+ base = self._base_command(command)
87
+ if base not in self._ACCEPT_EDITS_ALLOWED_COMMANDS:
88
+ return AcceptEditsEvaluation(
89
+ allowed=False,
90
+ reason_code="command_not_in_accept_edits_set",
91
+ message=f"Command `{base}` is not eligible for acceptEdits auto-allow.",
92
+ )
93
+ ok, code, msg = self._accept_edits_semantics(command, base)
94
+ return AcceptEditsEvaluation(allowed=ok, reason_code=code, message=msg)
95
+
96
+ def should_auto_allow_after_policy(
97
+ self,
98
+ command: str,
99
+ ctx: ToolExecutionContext,
100
+ ) -> bool:
101
+ if self.get_mode(ctx) != "acceptEdits":
102
+ return False
103
+ return self.evaluate_accept_edits(command).allowed
104
+
105
+ def finalize_decision(
106
+ self,
107
+ *,
108
+ command: str,
109
+ decision: AuthorizationDecision,
110
+ ctx: ToolExecutionContext,
111
+ ) -> AuthorizationDecision:
112
+ mode = self.get_mode(ctx)
113
+ if mode == "dontAsk" and decision.behavior == "ask":
114
+ detail = self.evaluate_accept_edits(command)
115
+ suffix = f" [acceptEdits_context: {detail.reason_code}]" if detail.reason_code else ""
116
+ message = (decision.message or f"Command requires approval in dontAsk mode: {command}") + suffix
117
+ return AuthorizationDecision(
118
+ behavior="deny",
119
+ message=message,
120
+ policy_id=decision.policy_id or "permission_mode",
121
+ )
122
+ return decision
123
+
124
+ def _accept_edits_semantics(self, command: str, base: str) -> tuple[bool, str, str]:
125
+ """返回 (allowed, reason_code, message)。"""
126
+ try:
127
+ tokens = shlex.split(command)
128
+ except ValueError:
129
+ tokens = command.split()
130
+ args = tokens[1:] if len(tokens) > 1 else []
131
+
132
+ if base == "rm":
133
+ risky = {"-r", "-R", "-rf", "-fr", "--recursive", "--force"}
134
+ if any(a in risky for a in args):
135
+ return (False, "rm_recursive_or_force", "Recursive or force rm is not auto-allowed under acceptEdits.")
136
+ return (True, "rm_safe", "Single-file or small-arg rm eligible for acceptEdits.")
137
+
138
+ if base == "sed":
139
+ if any(arg.startswith("-i") or arg == "--in-place" for arg in args):
140
+ if self._sed_inplace_shell_chain(command):
141
+ return (False, "sed_inplace_shell_chain", "sed -i in compound shell chain not auto-allowed.")
142
+ return (True, "sed_inplace", "In-place sed eligible for acceptEdits.")
143
+ return (False, "sed_not_inplace", "Only in-place sed is auto-allowed under acceptEdits.")
144
+
145
+ if base == "cp":
146
+ if self._args_contain_token_or_flag(args, self._CP_DENY_FLAGS):
147
+ return (False, "cp_submode_denied", "cp with link/backup/parents/reflink semantics not auto-allowed.")
148
+ if self._cp_short_bundle_risky(args):
149
+ return (False, "cp_short_link_or_symlink", "cp -l/-s (含合并短选项) not auto-allowed.")
150
+ if self._cp_sparse_or_preserve_root(args):
151
+ return (False, "cp_sparse_or_preserve_root", "cp --sparse=always or --preserve=root not auto-allowed.")
152
+ return (True, "cp_default", "cp eligible for acceptEdits.")
153
+
154
+ if base == "mv":
155
+ if self._args_contain_token_or_flag(args, self._MV_DENY_FLAGS):
156
+ return (False, "mv_force", "mv -f/--force not auto-allowed under acceptEdits.")
157
+ if "--strip-trailing-slashes" in args:
158
+ return (False, "mv_strip_slashes", "mv --strip-trailing-slashes not auto-allowed.")
159
+ return (True, "mv_default", "mv eligible for acceptEdits.")
160
+
161
+ if base == "mkdir":
162
+ if self._args_contain_token_or_flag(args, self._MKDIR_DENY_FLAGS):
163
+ return (False, "mkdir_selinux_context", "mkdir -Z/--context not auto-allowed.")
164
+ if "-m" in args or "--mode" in args:
165
+ return (True, "mkdir_with_mode", "mkdir with explicit mode still eligible.")
166
+ return (True, "mkdir_default", "mkdir eligible for acceptEdits.")
167
+
168
+ if base == "touch":
169
+ return (True, "touch_default", "touch eligible for acceptEdits.")
170
+
171
+ if base == "rmdir":
172
+ if "--ignore-fail-on-non-empty" in args:
173
+ return (False, "rmdir_ignore_nonempty", "rmdir --ignore-fail-on-non-empty not auto-allowed.")
174
+ return (True, "rmdir_default", "rmdir eligible for acceptEdits.")
175
+
176
+ if base == "ln":
177
+ if "-r" in args or "--relative" in args:
178
+ return (False, "ln_relative", "ln --relative not auto-allowed.")
179
+ return (True, "ln_default", "ln eligible for acceptEdits.")
180
+
181
+ if base == "install":
182
+ if self._args_contain_token_or_flag(args, self._INSTALL_DENY_FLAGS):
183
+ return (False, "install_submode_denied", "install -D/strip or similar not auto-allowed.")
184
+ if "-d" in args or "--directory" in args:
185
+ return (True, "install_dirs", "install -d eligible.")
186
+ return (True, "install_default", "install file copy eligible for acceptEdits.")
187
+
188
+ if base == "chmod":
189
+ if any(a in self._CHMOD_DENY_RECURSIVE for a in args):
190
+ return (False, "chmod_recursive", "Recursive chmod not auto-allowed under acceptEdits.")
191
+ mode, paths = self._chmod_mode_and_paths(args)
192
+ if mode and self._chmod_mode_caps_setuid(mode):
193
+ return (False, "chmod_setuid_or_special", "chmod with setuid/setgid/sticky not auto-allowed.")
194
+ return (True, "chmod_non_recursive", "Non-recursive chmod eligible for acceptEdits.")
195
+
196
+ if base == "chown":
197
+ if any(a in self._CHOWN_DENY_RECURSIVE for a in args):
198
+ return (False, "chown_recursive", "Recursive chown not auto-allowed under acceptEdits.")
199
+ if self._args_contain_token_or_flag(args, self._CHOWN_DENY_FLAGS):
200
+ return (False, "chown_reference_or_from", "chown --reference/--from not auto-allowed.")
201
+ return (True, "chown_non_recursive", "Non-recursive chown eligible for acceptEdits.")
202
+
203
+ return (True, "generic_ok", f"{base} eligible for acceptEdits.")
204
+
205
+ @staticmethod
206
+ def _sed_inplace_shell_chain(command: str) -> bool:
207
+ """与 `&&`、`|` 组合的 in-place sed 不自动放行(子模式边界)。"""
208
+ return "&&" in command or "|" in command
209
+
210
+ @staticmethod
211
+ def _args_contain_token_or_flag(args: list[str], needles: frozenset[str]) -> bool:
212
+ for a in args:
213
+ if a in needles:
214
+ return True
215
+ if a.startswith("--") and "=" in a:
216
+ opt = a.split("=", 1)[0]
217
+ if opt in needles or a in needles:
218
+ return True
219
+ return False
220
+
221
+ @staticmethod
222
+ def _cp_sparse_or_preserve_root(args: list[str]) -> bool:
223
+ for a in args:
224
+ if a.startswith("--sparse=") and a != "--sparse=never":
225
+ return True
226
+ if a == "--preserve=root":
227
+ return True
228
+ return False
229
+
230
+ @staticmethod
231
+ def _cp_short_bundle_risky(args: list[str]) -> bool:
232
+ for a in args:
233
+ if not a.startswith("-") or a.startswith("--") or len(a) < 2:
234
+ continue
235
+ body = a.lstrip("-")
236
+ if "l" in body or "s" in body:
237
+ return True
238
+ return False
239
+
240
+ @staticmethod
241
+ def _chmod_mode_and_paths(args: list[str]) -> tuple[str | None, list[str]]:
242
+ """从 chmod 参数中取出 mode 字串(若存在)与路径候选(启发式)。"""
243
+ mode: str | None = None
244
+ paths: list[str] = []
245
+ i = 0
246
+ while i < len(args):
247
+ a = args[i]
248
+ if a in ("-f", "--silent", "--quiet", "-v", "--verbose", "-c", "--changes", "-R", "-r", "--recursive"):
249
+ i += 1
250
+ continue
251
+ if a.startswith("-"):
252
+ i += 1
253
+ continue
254
+ if mode is None and re.match(r"^[ugoa]*[-+=]?[rwxXst,0-7]+$", a):
255
+ mode = a
256
+ i += 1
257
+ continue
258
+ paths.append(a)
259
+ i += 1
260
+ return mode, paths
261
+
262
+ @staticmethod
263
+ def _chmod_mode_caps_setuid(mode: str) -> bool:
264
+ if re.fullmatch(r"[0-7]{3,4}", mode):
265
+ try:
266
+ val = int(mode, 8)
267
+ except ValueError:
268
+ return False
269
+ return (val & 0o6000) != 0
270
+ if any(x in mode for x in ("s", "t", "T")) and ("+" in mode or "-" in mode or "=" in mode):
271
+ return True
272
+ return False
273
+
274
+ @staticmethod
275
+ def _base_command(command: str) -> str:
276
+ try:
277
+ tokens = shlex.split(command)
278
+ except ValueError:
279
+ tokens = command.split()
280
+ if not tokens:
281
+ return ""
282
+ return tokens[0].rsplit("/", 1)[-1]
@@ -0,0 +1,131 @@
1
+ """
2
+ tools/bash/models.py — BashRuntimeTool 输入输出数据模型
3
+
4
+ 对应 CC BashTool.tsx 的 inputSchema / outputSchema。
5
+ 纯 Pydantic 数据模型,无 I/O,无框架依赖。
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import Any
11
+
12
+ from pydantic import BaseModel, Field
13
+
14
+
15
+ class BashToolInput(BaseModel):
16
+ """
17
+ BashRuntimeTool 输入参数。
18
+
19
+ 对应 CC inputSchema。
20
+ """
21
+
22
+ command: str = Field(
23
+ ...,
24
+ description="The bash command to execute",
25
+ )
26
+ timeout: int | None = Field(
27
+ None,
28
+ gt=0,
29
+ description=(
30
+ "Optional timeout in seconds (max 600). "
31
+ "Defaults to 120 seconds if not specified."
32
+ ),
33
+ )
34
+ description: str | None = Field(
35
+ None,
36
+ description=(
37
+ "Clear, concise description of what this command does in active voice. "
38
+ "For simple commands keep it brief (5-10 words). "
39
+ "For complex or piped commands add enough context to clarify intent."
40
+ ),
41
+ )
42
+ run_in_background: bool = Field(
43
+ False,
44
+ description=(
45
+ "Set to true to run this command in the background. "
46
+ "Use this when you don't need the result immediately. "
47
+ "A task_id is returned; output is written to a file you can read later."
48
+ ),
49
+ )
50
+ dangerously_disable_sandbox: bool = Field(
51
+ False,
52
+ description=(
53
+ "Request to bypass sandbox execution for this command. "
54
+ "This only takes effect when the runtime explicitly allows unsandboxed execution."
55
+ ),
56
+ )
57
+
58
+
59
+ class BashToolOutput(BaseModel):
60
+ """
61
+ BashRuntimeTool 内部执行结果(对应 CC outputSchema)。
62
+
63
+ 由 invoke() 返回,传递给 after_invoke() 和 present()。
64
+ """
65
+
66
+ stdout: str
67
+ """命令输出(stderr 已合并进 stdout)。"""
68
+
69
+ interrupted: bool
70
+ """是否因超时或外部中断而提前终止。"""
71
+
72
+ exit_code: int
73
+ """命令退出码。"""
74
+
75
+ background_task_id: str | None = None
76
+ """后台任务 ID(仅 run_in_background=True 时有值)。"""
77
+
78
+ background_output_path: str | None = None
79
+ """后台任务输出文件路径(模型可用 Read 工具读取)。"""
80
+
81
+ task_mode: str = "foreground"
82
+ """任务模式:foreground / background。"""
83
+
84
+ task_status: str = "completed"
85
+ """任务状态:running / completed / failed / interrupted。"""
86
+
87
+ task_exit_code: int | None = None
88
+ """统一任务协议退出码(前后台共用)。"""
89
+
90
+ auto_backgrounded: bool = False
91
+ """是否由自动后台化策略触发。"""
92
+
93
+ return_code_interpretation: str | None = None
94
+ """
95
+ 特殊退出码的语义解释。
96
+ 例如 grep 返回 1 = 无匹配(非错误),会在此字段给出说明。
97
+ 对应 CC returnCodeInterpretation。
98
+ """
99
+
100
+ no_output_expected: bool = False
101
+ """
102
+ 命令预期不产生输出(mv/cp/rm 等),成功时 present() 显示 "Done"。
103
+ 对应 CC noOutputExpected。
104
+ """
105
+
106
+ destructive_warning: str | None = None
107
+ """
108
+ 危险命令警告文本(非阻断)。
109
+ detect_destructive_patterns() 检测到危险模式时设置,在 present() 中注入到 hints。
110
+ """
111
+
112
+ sandboxed: bool = False
113
+ """本次命令是否在沙箱模式下执行。"""
114
+
115
+ sandbox_bypass_reason: str | None = None
116
+ """未走沙箱时的原因说明(如 excluded command / explicit override)。"""
117
+
118
+ sandbox_temp_dir: str | None = None
119
+ """沙箱注入的 TMPDIR 路径(基础版沙箱使用独立临时目录)。"""
120
+
121
+ sandbox_violation_message: str | None = None
122
+ """沙箱执行返回的结构化 violation 信息。"""
123
+
124
+ overflow_file: str | None = None
125
+ """超大 stdout 落盘路径(完整内容),与 output_truncated 配合使用。"""
126
+
127
+ output_truncated: bool = False
128
+ """stdout 是否已截断,完整内容见 overflow_file。"""
129
+
130
+ observability: dict[str, Any] | None = None
131
+ """结构化可观测数据(事件摘要/决策归因),供 UI/CLI/日志多端消费。"""