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,137 @@
1
+ """
2
+ tools/ripgrep_plugin_exclusions.py — Plugin 缓存孤儿版本的 ripgrep --glob 排除
3
+
4
+ 职责:
5
+ 对齐 CC src/utils/plugins/orphanedPluginFilter.ts:在搜索路径与 plugin cache 重叠时,
6
+ 生成 `!**/<relativeVersionDir>/**` 列表,避免 Grep/Glob 命中带 `.orphaned_at` 的旧版本目录。
7
+
8
+ 链路位置:
9
+ ToolRuntimeLoader.register_default_tools -> 构造 PluginCacheGlobExclusions
10
+ -> GrepRuntimeTool / GlobRuntimeTool.invoke -> RipgrepBackend._build_args / build_rg_files_args
11
+
12
+ 当前裁剪范围:
13
+ 仅基于 workspace 的 `plugin_cache_dir`;不实现 CC 的 main.tsx 预热与 /reload-plugins 命令
14
+ (clear_cache 供后续插件热重载接线)。失败时 best-effort 返回空列表,不阻断搜索。
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import os
20
+ import sys
21
+ import threading
22
+
23
+ from langchain_agentx.tools.grep.backend import run_ripgrep_lines
24
+ from langchain_agentx.utils.rg_executable import default_rg_executable
25
+ from langchain_agentx.utils.subprocess_text import (
26
+ TextSubprocessRunner,
27
+ default_text_subprocess_runner,
28
+ )
29
+
30
+ _ORPHANED_AT = ".orphaned_at"
31
+
32
+
33
+ class PluginCacheGlobExclusions:
34
+ """会话级缓存的 plugin cache `--glob` 排除串(与 CC getGlobExclusionsForPluginCache 语义对齐)。"""
35
+
36
+ def __init__(
37
+ self,
38
+ *,
39
+ cache_root: str | None,
40
+ rg_path: str | None = None,
41
+ timeout: int = 20,
42
+ text_runner: TextSubprocessRunner | None = None,
43
+ ) -> None:
44
+ if cache_root:
45
+ self._cache_root = os.path.normpath(os.path.realpath(os.path.expanduser(cache_root)))
46
+ else:
47
+ self._cache_root = None
48
+ self._rg_path = rg_path if rg_path is not None else default_rg_executable()
49
+ self._timeout = int(timeout)
50
+ self._text_runner = text_runner or default_text_subprocess_runner()
51
+ self._cached: list[str] | None = None
52
+ self._lock = threading.Lock()
53
+
54
+ def clear_cache(self) -> None:
55
+ """对齐 CC clearPluginCacheExclusions:插件集重载后应调用以重新扫描 marker。"""
56
+ with self._lock:
57
+ self._cached = None
58
+
59
+ @staticmethod
60
+ def _normalize_for_compare(path: str) -> str:
61
+ n = os.path.normpath(os.path.realpath(os.path.expanduser(path)))
62
+ if sys.platform == "win32":
63
+ return n.lower()
64
+ return n
65
+
66
+ @staticmethod
67
+ def paths_overlap(search_path: str, cache_root: str) -> bool:
68
+ """与 CC orphanedPluginFilter.pathsOverlap 等价(含根路径与前缀关系)。"""
69
+ na = PluginCacheGlobExclusions._normalize_for_compare(search_path)
70
+ nb = PluginCacheGlobExclusions._normalize_for_compare(cache_root)
71
+ sep = os.sep
72
+ return (
73
+ na == nb
74
+ or na == sep
75
+ or nb == sep
76
+ or na.startswith(nb + sep)
77
+ or nb.startswith(na + sep)
78
+ )
79
+
80
+ def get_exclusions_for_search(self, search_path: str) -> list[str]:
81
+ """
82
+ 若 search_path 与 plugin cache 根无路径重叠,返回 [](与 CC 短路一致)。
83
+ 否则返回缓存的排除 glob 列表(首次调用时扫描 `.orphaned_at`)。
84
+ """
85
+ if not self._cache_root:
86
+ return []
87
+ sp = os.path.normpath(os.path.realpath(os.path.expanduser(search_path)))
88
+ if not self.paths_overlap(sp, self._cache_root):
89
+ return []
90
+ with self._lock:
91
+ if self._cached is not None:
92
+ return list(self._cached)
93
+ self._cached = self._compute_exclusions_locked()
94
+ return list(self._cached)
95
+
96
+ def _compute_exclusions_locked(self) -> list[str]:
97
+ try:
98
+ if not os.path.isdir(self._cache_root):
99
+ return []
100
+ args = [
101
+ "--files",
102
+ "--hidden",
103
+ "--no-ignore",
104
+ "--max-depth",
105
+ "4",
106
+ "--glob",
107
+ _ORPHANED_AT,
108
+ ]
109
+ lines = run_ripgrep_lines(
110
+ self._rg_path,
111
+ args,
112
+ self._cache_root,
113
+ timeout=self._timeout,
114
+ text_runner=self._text_runner,
115
+ is_retry=False,
116
+ )
117
+ out: list[str] = []
118
+ cache = self._cache_root
119
+ for marker_path in lines:
120
+ mp = marker_path.strip()
121
+ if not mp:
122
+ continue
123
+ version_dir = os.path.dirname(mp)
124
+ if os.path.isabs(version_dir):
125
+ try:
126
+ rel = os.path.relpath(version_dir, cache)
127
+ except ValueError:
128
+ continue
129
+ else:
130
+ rel = version_dir
131
+ posix_rel = rel.replace("\\", "/")
132
+ if not posix_rel or posix_rel == ".":
133
+ continue
134
+ out.append(f"!**/{posix_rel}/**")
135
+ return out
136
+ except Exception:
137
+ return []
@@ -0,0 +1,4 @@
1
+ from .tool import SkillRuntimeTool
2
+
3
+ __all__ = ["SkillRuntimeTool"]
4
+
@@ -0,0 +1,80 @@
1
+ """
2
+ tools/skill/argument_substitution.py — skill 参数展开(CC argumentSubstitution.ts 对齐子集)
3
+
4
+ 职责:
5
+ ``parse_arguments`` / ``parse_argument_names`` / ``substitute_arguments``,
6
+ 支持 ``$ARGUMENTS``、``$ARGUMENTS[n]``、``$n`` 及 frontmatter ``arguments`` 定义的具名 ``$name``。
7
+
8
+ 链路位置:
9
+ ``SkillContentExpander.expand()`` → ``substitute_arguments()``。
10
+
11
+ CC 对照:
12
+ ``src/utils/argumentSubstitution.ts``;本实现以 ``shlex.split`` 近似 CC 的 shell-quote 解析。
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import re
18
+ import shlex
19
+ from collections.abc import Sequence
20
+ from typing import Any
21
+
22
+
23
+ def parse_arguments(args: str | None) -> list[str]:
24
+ """将参数字符串拆成 token 列表(对齐 CC ``parseArguments``)。"""
25
+ if args is None or not args.strip():
26
+ return []
27
+ try:
28
+ return shlex.split(args, posix=True)
29
+ except ValueError:
30
+ return [p for p in args.split() if p]
31
+
32
+
33
+ def parse_argument_names(argument_names: Any) -> list[str]:
34
+ """解析 frontmatter ``arguments``(对齐 CC ``parseArgumentNames``)。"""
35
+
36
+ def _valid(name: str) -> bool:
37
+ return bool(name.strip()) and not name.strip().isdigit()
38
+
39
+ if not argument_names:
40
+ return []
41
+ if isinstance(argument_names, list):
42
+ return [str(x).strip() for x in argument_names if _valid(str(x))]
43
+ if isinstance(argument_names, str):
44
+ return [p for p in argument_names.split() if _valid(p)]
45
+ return []
46
+
47
+
48
+ def substitute_arguments(
49
+ content: str,
50
+ args: str | None,
51
+ *,
52
+ append_if_no_placeholder: bool = True,
53
+ argument_names: Sequence[str] | None = None,
54
+ ) -> str:
55
+ """替换正文中的参数占位符(对齐 CC ``substituteArguments`` 顺序与语义)。"""
56
+ if args is None:
57
+ return content
58
+
59
+ names = list(argument_names or [])
60
+ parsed = parse_arguments(args)
61
+ original = content
62
+
63
+ for i, name in enumerate(names):
64
+ if not name:
65
+ continue
66
+ pat = re.compile(rf"\${re.escape(name)}(?!\[|\w)")
67
+ rep = parsed[i] if i < len(parsed) else ""
68
+ content = pat.sub(rep, content)
69
+
70
+ def _tok_at(parsed_args: list[str], m: re.Match[str]) -> str:
71
+ idx = int(m.group(1))
72
+ return parsed_args[idx] if 0 <= idx < len(parsed_args) else ""
73
+
74
+ content = re.sub(r"\$ARGUMENTS\[(\d+)\]", lambda m: _tok_at(parsed, m), content)
75
+ content = re.sub(r"\$(\d+)(?!\w)", lambda m: _tok_at(parsed, m), content)
76
+ content = content.replace("$ARGUMENTS", args)
77
+
78
+ if content == original and append_if_no_placeholder and args:
79
+ content = f"{content}\n\nARGUMENTS: {args}"
80
+ return content
@@ -0,0 +1,196 @@
1
+ """
2
+ tools/skill/loader.py — Skill 加载协作者
3
+
4
+ 职责:
5
+ - SkillFrontmatterParser:解析 SKILL.md frontmatter + body
6
+ - SkillCommandRepository:从 skills 根目录加载并查找技能
7
+ - SkillContentExpander:注入 ``Base directory for this skill:`` 前缀后执行参数展开
8
+
9
+ 链路位置:
10
+ SkillRuntimeTool.invoke()
11
+ -> SkillCommandRepository.find_by_name()
12
+ -> SkillContentExpander.expand()
13
+
14
+ CC 对照:
15
+ 对齐 CC SkillTool 的 command lookup + prompt expansion 思路,
16
+ 但实现为本工程 RuntimeTool 的 OOP 协作者,不走 middleware 旁路。
17
+ frontmatter:支持 ``when_to_use`` / ``whenToUse`` 与 ``description`` 的
18
+ 单行值及常见块标量(``|`` / ``>-``,见 loadSkillsDir 的 whenToUse 语义)。
19
+ 块标量限制:``_read_literal_block`` 以顶格 ``word:`` 作为块结束;块正文内若出现
20
+ 顶格 ``key: value`` 行会被误截断(已知限制,复杂 YAML 请用单行或换用完整 YAML 解析)。
21
+ """
22
+
23
+ from __future__ import annotations
24
+
25
+ import re
26
+ from pathlib import Path
27
+ from typing import Any
28
+
29
+ from .argument_substitution import parse_argument_names, substitute_arguments
30
+ from .models import SkillCommand
31
+
32
+ # CC loadSkillsDir:when_to_use / description 常为块标量(| / >-);须在 parser 层取出独立字符串
33
+ _BLOCK_TEXT_KEYS = frozenset({"when_to_use", "whenToUse", "description", "argument-hint", "argument_hint"})
34
+
35
+
36
+ def normalize_skill_name(skill_name: str) -> str:
37
+ return skill_name.strip().lstrip("/")
38
+
39
+
40
+ def _normalize_when_to_use_raw(raw: Any) -> str | None:
41
+ """将 frontmatter 中的 when_to_use 规范为单行或多行字符串(CC whenToUse)。"""
42
+ if isinstance(raw, str) and raw.strip():
43
+ return raw.strip()
44
+ if isinstance(raw, list):
45
+ parts = [str(x).strip() for x in raw if str(x).strip()]
46
+ if parts:
47
+ return "\n".join(parts)
48
+ return None
49
+
50
+
51
+ def _is_block_scalar_header(value: str) -> bool:
52
+ """YAML 块标量起始行:空、``|``、``|-``、``|+``、``>``、``>-`` 等(与 CC SKILL 常见写法一致)。"""
53
+ v = value.strip()
54
+ if not v:
55
+ return True
56
+ return v.startswith("|") or v.startswith(">")
57
+
58
+
59
+ class SkillFrontmatterParser:
60
+ _FRONTMATTER_RE = re.compile(r"^---\s*\n(.*?)\n---\s*\n?", re.DOTALL)
61
+
62
+ def parse(self, text: str) -> tuple[dict[str, Any], str]:
63
+ m = self._FRONTMATTER_RE.match(text)
64
+ if not m:
65
+ return {}, text
66
+ frontmatter_text = m.group(1)
67
+ body = text[m.end():]
68
+ return self._parse_simple_yaml(frontmatter_text), body
69
+
70
+ @staticmethod
71
+ def _read_literal_block(lines: list[str], start: int) -> tuple[str, int]:
72
+ """读取 ``key: |`` 后的块,直到下一个顶格 ``word:``(与 CC 常见 SKILL.md 一致)。
73
+
74
+ 限制:块内不得出现顶格 ``foo: bar`` 示例行,否则会被当作下一个 frontmatter 键而提前结束。
75
+ """
76
+ parts: list[str] = []
77
+ j = start
78
+ while j < len(lines):
79
+ raw = lines[j]
80
+ if re.match(r"^[A-Za-z0-9_-]+:\s*", raw) and not raw[:1].isspace():
81
+ break
82
+ parts.append(raw)
83
+ j += 1
84
+ text = "\n".join(parts)
85
+ return text.strip("\n"), j
86
+
87
+ def _parse_simple_yaml(self, frontmatter_text: str) -> dict[str, Any]:
88
+ data: dict[str, Any] = {}
89
+ lines = frontmatter_text.splitlines()
90
+ i = 0
91
+ while i < len(lines):
92
+ stripped = lines[i].strip()
93
+ i += 1
94
+ if not stripped or stripped.startswith("#"):
95
+ continue
96
+
97
+ m = re.match(r"^([A-Za-z0-9_-]+):\s*(.*)$", stripped)
98
+ if not m:
99
+ continue
100
+
101
+ key, value = m.group(1), m.group(2).strip()
102
+ if key in _BLOCK_TEXT_KEYS and _is_block_scalar_header(value):
103
+ block, i = self._read_literal_block(lines, i)
104
+ if block.strip():
105
+ data[key] = block.strip()
106
+ continue
107
+
108
+ if value:
109
+ data[key] = value
110
+ continue
111
+
112
+ items: list[str] = []
113
+ while i < len(lines):
114
+ nxt = lines[i].strip()
115
+ if not nxt:
116
+ i += 1
117
+ continue
118
+ if nxt.startswith("- "):
119
+ items.append(nxt[2:].strip())
120
+ i += 1
121
+ continue
122
+ break
123
+ data[key] = items
124
+ return data
125
+
126
+
127
+ class SkillCommandRepository:
128
+ def __init__(self, skills_root: Path, parser: SkillFrontmatterParser | None = None) -> None:
129
+ self._skills_root = skills_root
130
+ self._parser = parser or SkillFrontmatterParser()
131
+
132
+ def list_commands(self) -> list[SkillCommand]:
133
+ if not self._skills_root.exists():
134
+ return []
135
+
136
+ commands: list[SkillCommand] = []
137
+ for skill_dir in self._skills_root.iterdir():
138
+ if not skill_dir.is_dir():
139
+ continue
140
+ skill_md = skill_dir / "SKILL.md"
141
+ if not skill_md.exists():
142
+ continue
143
+
144
+ content = skill_md.read_text(encoding="utf-8")
145
+ frontmatter, body = self._parser.parse(content)
146
+ name = str(frontmatter.get("name") or skill_dir.name).strip()
147
+ raw_desc = frontmatter.get("description")
148
+ has_user_desc = isinstance(raw_desc, str) and bool(raw_desc.strip())
149
+ description = raw_desc.strip() if has_user_desc else ""
150
+ raw_wtu = frontmatter.get("when_to_use") or frontmatter.get("whenToUse")
151
+ when_to_use = _normalize_when_to_use_raw(raw_wtu)
152
+ arg_names = parse_argument_names(frontmatter.get("arguments"))
153
+ commands.append(
154
+ SkillCommand(
155
+ name=name,
156
+ description=description,
157
+ content=body.strip(),
158
+ path=skill_md,
159
+ source="local",
160
+ frontmatter=frontmatter,
161
+ has_user_specified_description=has_user_desc,
162
+ when_to_use=when_to_use,
163
+ argument_names=arg_names,
164
+ )
165
+ )
166
+
167
+ dedup: dict[str, SkillCommand] = {}
168
+ for cmd in commands:
169
+ dedup.setdefault(normalize_skill_name(cmd.name), cmd)
170
+ return list(dedup.values())
171
+
172
+ def find_by_name(self, skill_name: str) -> SkillCommand | None:
173
+ normalized = normalize_skill_name(skill_name)
174
+ for cmd in self.list_commands():
175
+ if normalize_skill_name(cmd.name) == normalized:
176
+ return cmd
177
+ return None
178
+
179
+
180
+ class SkillContentExpander:
181
+ def expand(self, command: SkillCommand, args: str | None) -> str:
182
+ # CC createSkillCommand.getPromptForCommand:先注入 skill 根目录,再 substituteArguments。
183
+ body = command.content
184
+ skill_root = command.path.parent
185
+ try:
186
+ skill_root_s = str(skill_root.resolve())
187
+ except OSError:
188
+ skill_root_s = str(skill_root)
189
+ markdown = f"Base directory for this skill: {skill_root_s}\n\n{body}"
190
+ return substitute_arguments(
191
+ markdown,
192
+ args,
193
+ append_if_no_placeholder=True,
194
+ argument_names=command.argument_names,
195
+ )
196
+
@@ -0,0 +1,88 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from pathlib import Path
5
+ from typing import Any, Literal
6
+
7
+ from pydantic import BaseModel, Field, field_validator
8
+
9
+
10
+ class SkillToolInput(BaseModel):
11
+ skill_name: str = Field(
12
+ ...,
13
+ description="Skill name (leading slash is optional).",
14
+ )
15
+ args: str | None = Field(
16
+ default=None,
17
+ description="Optional arguments passed to skill expansion.",
18
+ )
19
+
20
+ @field_validator("skill_name")
21
+ @classmethod
22
+ def _validate_skill_name(cls, value: str) -> str:
23
+ name = value.strip()
24
+ if not name:
25
+ raise ValueError("skill_name cannot be empty")
26
+ return name
27
+
28
+ @field_validator("args")
29
+ @classmethod
30
+ def _validate_args_len(cls, value: str | None) -> str | None:
31
+ if value and len(value) > 2000:
32
+ raise ValueError("args too long (max 2000 chars)")
33
+ return value
34
+
35
+
36
+ class SkillToolOutput(BaseModel):
37
+ skill_name: str
38
+ content: str
39
+ source: Literal["local", "mcp"] = "local"
40
+ skill_path: str | None = None
41
+ command_name_tag: str | None = None
42
+ allowed_tools: list[str] = Field(default_factory=list)
43
+ model_override: str | None = None
44
+ constraints_applied: dict[str, Any] = Field(default_factory=dict)
45
+
46
+
47
+ def _frontmatter_bool(value: Any) -> bool:
48
+ if value is True:
49
+ return True
50
+ if isinstance(value, str) and value.strip().lower() in ("true", "1", "yes", "on"):
51
+ return True
52
+ return False
53
+
54
+
55
+ @dataclass(slots=True)
56
+ class SkillCommand:
57
+ name: str
58
+ description: str
59
+ content: str
60
+ path: Path
61
+ source: Literal["local", "mcp"] = "local"
62
+ frontmatter: dict[str, Any] = field(default_factory=dict)
63
+ # CC getSlashCommandToolSkills:仅当 frontmatter 显式给出非空 description 时为 True(非正文推导)
64
+ has_user_specified_description: bool = False
65
+ # CC whenToUse / frontmatter when_to_use
66
+ when_to_use: str | None = None
67
+ # CC frontmatter ``arguments`` → parseArgumentNames → $name 占位符
68
+ argument_names: list[str] = field(default_factory=list)
69
+
70
+ def visible_in_slash_command_tool_listing(self) -> bool:
71
+ """是否出现在模型可见的 skill listing 中。
72
+
73
+ 与 CC ``commands/init`` / ``SkillTool`` 侧一致:``disable-model-invocation: true`` 表示
74
+ 仅用户侧触发,**不**对模型展示 listing(亦由 ``SkillRuntimeTool.validate_input`` 拦截工具调用)。
75
+ """
76
+ if not (self.name or "").strip():
77
+ return False
78
+ if not (self.has_user_specified_description or bool((self.when_to_use or "").strip())):
79
+ return False
80
+
81
+ disable_mi = _frontmatter_bool(
82
+ self.frontmatter.get("disable-model-invocation")
83
+ or self.frontmatter.get("disable_model_invocation")
84
+ )
85
+ if disable_mi:
86
+ return False
87
+ return self.source in ("local", "mcp")
88
+
@@ -0,0 +1,80 @@
1
+ """
2
+ tools/skill/policy.py — Skill frontmatter 约束协作者
3
+
4
+ 职责:
5
+ 解析并评估 skill frontmatter 中与执行约束相关的字段,
6
+ 当前 v1 支持:
7
+ - allowed-tools
8
+ - paths
9
+
10
+ 链路位置:
11
+ SkillRuntimeTool.check_permissions()
12
+ -> SkillConstraintEvaluator.evaluate()
13
+
14
+ 注意:
15
+ - 本模块不替代全局 PolicyEngine;全局 deny/allow 仍由 RuntimeTool 基类链路兜底。
16
+ - 这里仅做 skill 自身约束判定,返回 AuthorizationDecision。
17
+ - ``paths`` 是否在 workspace 内:用 ``Path.resolve()`` + ``Path.relative_to``,**不用** ``str.startswith``(避免 Windows 大小写/规范化误判)。
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ from pathlib import Path
23
+ from typing import Any
24
+
25
+ from langchain_agentx.tool_runtime.models import AuthorizationDecision
26
+
27
+
28
+ def _path_is_descendant(candidate: Path, ancestor: Path) -> bool:
29
+ """candidate 与 ancestor 均已 resolve;在 ancestor 目录树内(含自身)则 True。"""
30
+ try:
31
+ candidate.relative_to(ancestor)
32
+ return True
33
+ except ValueError:
34
+ return False
35
+
36
+
37
+ class SkillConstraintEvaluator:
38
+ def __init__(self, workspace_root: Path | str) -> None:
39
+ root = Path(workspace_root).expanduser()
40
+ try:
41
+ self._workspace_root = root.resolve()
42
+ except OSError:
43
+ self._workspace_root = root
44
+
45
+ def evaluate(self, frontmatter: dict[str, Any]) -> AuthorizationDecision:
46
+ paths = frontmatter.get("paths")
47
+ if isinstance(paths, list):
48
+ ws = self._workspace_root
49
+ for raw in paths:
50
+ text = str(raw).strip()
51
+ if not text:
52
+ continue
53
+ try:
54
+ candidate = (ws / text).resolve()
55
+ except OSError:
56
+ return AuthorizationDecision(
57
+ behavior="deny",
58
+ message=(
59
+ "Permission denied: skill paths constraint could not "
60
+ f"resolve path entry: {text!r}."
61
+ ),
62
+ policy_id="skill_paths_resolve_error",
63
+ )
64
+ if not _path_is_descendant(candidate, ws):
65
+ return AuthorizationDecision(
66
+ behavior="deny",
67
+ message="Permission denied: skill paths constraint escapes workspace root.",
68
+ policy_id="skill_paths_outside_workspace",
69
+ )
70
+
71
+ allowed_tools = frontmatter.get("allowed-tools")
72
+ if allowed_tools is not None and not isinstance(allowed_tools, list):
73
+ return AuthorizationDecision(
74
+ behavior="deny",
75
+ message="Permission denied: invalid 'allowed-tools' frontmatter format.",
76
+ policy_id="skill_allowed_tools_invalid",
77
+ )
78
+
79
+ return AuthorizationDecision(behavior="allow")
80
+
@@ -0,0 +1,35 @@
1
+ from __future__ import annotations
2
+
3
+ TOOL_NAME = "skill"
4
+ COMMAND_NAME_TAG = "command-name"
5
+
6
+
7
+ def get_prompt() -> str:
8
+ return (
9
+ "Execute a skill within the main conversation.\n\n"
10
+ "When users ask you to perform tasks, check whether any available skill matches.\n"
11
+ "Skills provide specialized capabilities and domain knowledge.\n"
12
+ "If users reference slash-command style requests such as '/commit' or '/review-pr', "
13
+ "treat them as skill invocations.\n\n"
14
+ "How to invoke:\n"
15
+ '- Use this tool with `skill_name` and optional `args`.\n'
16
+ '- Example: `skill_name=\"commit\"`\n'
17
+ '- Example: `skill_name=\"pdf\"`\n'
18
+ '- Example: `skill_name=\"commit\", args=\"-m \'Fix bug\'\"`\n'
19
+ '- Example: `skill_name=\"review-pr\", args=\"123\"`\n'
20
+ '- Example: `skill_name=\"ms-office-suite:pdf\"`\n'
21
+ '- Leading slash is allowed (e.g. `/commit`).\n\n'
22
+ "Important:\n"
23
+ "- Available skills are provided via skill listing reminders/messages in the conversation.\n"
24
+ "- If a matching skill exists, this is a BLOCKING REQUIREMENT: call this tool before "
25
+ "giving any task-specific guidance.\n"
26
+ "- Never mention a skill without calling this tool.\n"
27
+ "- Do not invoke a skill that is already running in the current turn.\n"
28
+ f"- If current turn already contains <{COMMAND_NAME_TAG}>...</{COMMAND_NAME_TAG}>, "
29
+ "the skill is already loaded; follow it directly instead of re-invoking.\n"
30
+ "- Do not use this tool for built-in CLI commands (for example /help, /clear).\n"
31
+ )
32
+
33
+
34
+ DESCRIPTION = get_prompt()
35
+