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,64 @@
1
+ """
2
+ task_runtime/output/sink.py — 任务持续输出(§10.4 TaskOutputSink)
3
+
4
+ 职责:
5
+ 提供与终态 notification 正交的 **append-only 输出缓冲**,支持按 task_id 追加与
6
+ 按字节 offset 续读,供排障与(未来)CLI 回放。
7
+
8
+ 与 CC 对比:
9
+ CC 将任务 stdout 等落盘到 workspace 路径;本仓库先提供进程内 **InMemory** 实现,
10
+ 上限策略与 ``never_truncate`` 类语义对齐为「超上限丢弃头部、保留尾部」。
11
+
12
+ 边界:
13
+ - 不负责持久化跨进程;跨重启持久化由调用方换用基于文件/SQLite 的 Sink 实现(未来)。
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ from threading import RLock
19
+ from typing import Optional, Protocol, runtime_checkable
20
+
21
+
22
+ @runtime_checkable
23
+ class TaskOutputSink(Protocol):
24
+ """Append-only task output; ``offset`` in :meth:`read_from` is **byte** offset."""
25
+
26
+ def append(self, task_id: str, chunk: str) -> None: ...
27
+
28
+ def read_from(self, task_id: str, offset: int = 0) -> tuple[str, int]:
29
+ """Return ``(decoded_text_slice, next_byte_offset)`` for the full buffer length."""
30
+ ...
31
+
32
+
33
+ class InMemoryTaskOutputSink:
34
+ """进程内输出缓冲:UTF-8 字节存储;超上限时丢弃最旧字节(保留尾部)。"""
35
+
36
+ def __init__(self, *, max_bytes_per_task: Optional[int] = 1_048_576) -> None:
37
+ self._max_bytes_per_task = max_bytes_per_task
38
+ self._lock = RLock()
39
+ self._buffers: dict[str, bytearray] = {}
40
+
41
+ def append(self, task_id: str, chunk: str) -> None:
42
+ if not chunk:
43
+ return
44
+ raw = chunk.encode("utf-8")
45
+ with self._lock:
46
+ buf = self._buffers.setdefault(task_id, bytearray())
47
+ buf.extend(raw)
48
+ max_b = self._max_bytes_per_task
49
+ if max_b is not None and len(buf) > max_b:
50
+ del buf[: len(buf) - max_b]
51
+
52
+ def read_from(self, task_id: str, offset: int = 0) -> tuple[str, int]:
53
+ with self._lock:
54
+ buf = self._buffers.get(task_id)
55
+ if not buf:
56
+ return "", 0
57
+ data = bytes(buf)
58
+ total = len(data)
59
+ if offset < 0:
60
+ offset = 0
61
+ if offset >= total:
62
+ return "", total
63
+ tail = data[offset:]
64
+ return tail.decode("utf-8", errors="replace"), total
@@ -0,0 +1,11 @@
1
+ """Runtime policies (withhold visibility, etc.)."""
2
+
3
+ from .withhold_visibility import (
4
+ DEFAULT_WITHHOLD_TASK_EVENT_POLICY,
5
+ WithholdTaskEventPolicy,
6
+ )
7
+
8
+ __all__ = [
9
+ "DEFAULT_WITHHOLD_TASK_EVENT_POLICY",
10
+ "WithholdTaskEventPolicy",
11
+ ]
@@ -0,0 +1,32 @@
1
+ """
2
+ task_runtime/policy/withhold_visibility.py — withhold 与 task 事件可见性(§10.6)
3
+
4
+ 职责:
5
+ 冻结 **可测试的策略占位**:withhold(输出截断窗口)期间,task 通知与输出 sink
6
+ 与 loop 恢复的相对顺序。默认行为保守:不延迟终态通知、输出始终写入 sink。
7
+
8
+ 与 loop 的关系:
9
+ 具体注入顺序由 ``create_loop_agent`` / controller 实现;本模块仅提供策略值对象,
10
+ 供未来 loop 与 adapter 读取,避免魔法布尔散落在多处。
11
+
12
+ 边界:
13
+ - 本阶段不修改 loop 默认行为;未传入策略时等价于默认实例。
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ from dataclasses import dataclass
19
+
20
+
21
+ @dataclass(frozen=True)
22
+ class WithholdTaskEventPolicy:
23
+ """withhold 窗口内 task 相关事件的可见性策略(§10.6 契约占位)。"""
24
+
25
+ delay_terminal_task_notification: bool = False
26
+ """若为 True,task 终态通知延迟到 withhold 解除后合并注入(未来)。"""
27
+
28
+ write_task_output_sink_during_withhold: bool = True
29
+ """若为 True,withhold 期间仍向 §10.4 sink 追加(便于排障)。"""
30
+
31
+
32
+ DEFAULT_WITHHOLD_TASK_EVENT_POLICY = WithholdTaskEventPolicy()
@@ -0,0 +1,5 @@
1
+ """Task notification queue implementations."""
2
+
3
+ from .in_memory import InMemoryTaskCommandQueue
4
+
5
+ __all__ = ["InMemoryTaskCommandQueue"]
@@ -0,0 +1,55 @@
1
+ """
2
+ task_runtime/queue/in_memory.py — TaskCommandQueue 内存实现
3
+
4
+ 职责:
5
+ 基于 list 的任务通知队列;采用 RLock 保证基础并发安全。
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from threading import RLock
11
+
12
+ from ..core.interfaces import TaskCommandQueue
13
+ from ..core.types import (
14
+ QueuePriority,
15
+ TaskNotificationEnvelope,
16
+ TaskScope,
17
+ )
18
+
19
+
20
+ _PRIORITY_ORDER = {
21
+ QueuePriority.NOW: 0,
22
+ QueuePriority.NEXT: 1,
23
+ QueuePriority.LATER: 2,
24
+ }
25
+
26
+
27
+ class InMemoryTaskCommandQueue(TaskCommandQueue):
28
+ def __init__(self) -> None:
29
+ self._lock = RLock()
30
+ self._queue: list[TaskNotificationEnvelope] = []
31
+
32
+ def enqueue(self, env: TaskNotificationEnvelope) -> None:
33
+ with self._lock:
34
+ self._queue.append(env)
35
+
36
+ def peek_for_scope(
37
+ self, scope: TaskScope, max_priority: QueuePriority
38
+ ) -> list[TaskNotificationEnvelope]:
39
+ threshold = _PRIORITY_ORDER[max_priority]
40
+ with self._lock:
41
+ filtered = [
42
+ item
43
+ for item in self._queue
44
+ if item.scope.agent_id == scope.agent_id
45
+ and _PRIORITY_ORDER[item.priority] <= threshold
46
+ ]
47
+ filtered.sort(key=lambda item: _PRIORITY_ORDER[item.priority])
48
+ return filtered
49
+
50
+ def remove(self, command_ids: list[str]) -> None:
51
+ if not command_ids:
52
+ return
53
+ ids = set(command_ids)
54
+ with self._lock:
55
+ self._queue = [item for item in self._queue if item.command_id not in ids]
@@ -0,0 +1,4 @@
1
+ from .provider import SkillPrefetchProvider
2
+
3
+ __all__ = ["SkillPrefetchProvider"]
4
+
@@ -0,0 +1,46 @@
1
+ """
2
+ task_runtime/skill_prefetch/attachments.py — Skill 附件构造器
3
+
4
+ 职责:
5
+ 将 `SkillHint` 领域对象映射为 loop 可注入的 attachment message(dict)。
6
+
7
+ 对照:
8
+ 对齐 CC 中 skill_listing / dynamic_skill attachment 的角色,
9
+ 但复用本工程 `task_event_reserve` + dedup 契约。
10
+
11
+ 实现约束:
12
+ - 采用 OOP 构造器,不在外层散落函数;
13
+ - 兼容现有 dedup:复用 `metadata.task_notification.command_id`。
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ from typing import Any
19
+
20
+ from .models import SkillHint
21
+
22
+
23
+ class SkillAttachmentBuilder:
24
+ def build(self, hint: SkillHint) -> dict[str, Any]:
25
+ return {
26
+ "role": "user",
27
+ "content": [{"type": "text", "text": self._build_text(hint)}],
28
+ "metadata": {
29
+ # Reuse task_notification.command_id for existing dedup filter.
30
+ "task_notification": {"command_id": hint.command_id},
31
+ "skill_notification": {
32
+ "command_id": hint.command_id,
33
+ "kind": hint.kind,
34
+ "skill_name": hint.payload.get("skill_name"),
35
+ "source": hint.payload.get("source", "prefetch"),
36
+ "provider": "skill_prefetch",
37
+ "turn_index": hint.payload.get("turn_index"),
38
+ },
39
+ },
40
+ }
41
+
42
+ def _build_text(self, hint: SkillHint) -> str:
43
+ if hint.kind == "skill_listing":
44
+ return f"[skill_listing] {hint.summary}"
45
+ return f"[dynamic_skill] {hint.summary}"
46
+
@@ -0,0 +1,37 @@
1
+ """
2
+ tools/task_runtime/skill_prefetch/models.py — Skill 预取领域模型
3
+
4
+ 职责:
5
+ 承载 Skill 预取阶段的稳定数据对象(hint / command 元信息)。
6
+
7
+ 对照:
8
+ 对齐 CC skill discovery 的“轻量列表提示 + 动态候选提示”语义,
9
+ 用 dataclass 固化本工程 provider 内部契约。
10
+
11
+ 实现约束:
12
+ - 仅定义数据模型,不承载队列/注入/渲染逻辑;
13
+ - 字段命名保持与 loop 注入 metadata 对齐(kind/command_id/source)。
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import time
19
+ from dataclasses import dataclass, field
20
+ from typing import Any, Literal
21
+
22
+ from ..core.types import QueuePriority, TaskScope
23
+
24
+ SkillHintKind = Literal["skill_listing", "dynamic_skill"]
25
+
26
+
27
+ @dataclass(frozen=True)
28
+ class SkillHint:
29
+ kind: SkillHintKind
30
+ command_id: str
31
+ summary: str
32
+ scope: TaskScope
33
+ priority: QueuePriority = QueuePriority.NEXT
34
+ payload: dict[str, Any] = field(default_factory=dict)
35
+ dedup_key: str | None = None
36
+ created_at: float = field(default_factory=time.time)
37
+
@@ -0,0 +1,344 @@
1
+ """
2
+ task_runtime/skill_prefetch/provider.py — Skill 预取队列提供者
3
+
4
+ 职责:
5
+ 在 loop 注入链中提供 skill 可见性附件(skill_listing / dynamic_skill)。
6
+
7
+ 对照:
8
+ 对齐 CC “loop 提供可见性,SkillTool 负责执行”的分层;
9
+ 本类只输出附件批次,不执行任何 skill 内容。
10
+
11
+ 实现约束:
12
+ - OOP 协作:技能发现、附件渲染、队列批次管理职责分离;
13
+ - 与 `QueuedCommandProvider` 契约对齐(build/ack/nack/has_pending/ingest);
14
+ - 默认提供首轮可见性的 listing 注入能力。
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import os
20
+ from pathlib import Path
21
+ from threading import RLock
22
+ from typing import TYPE_CHECKING
23
+ from uuid import uuid4
24
+
25
+ from langchain_agentx.workspace import resolve_agent_workspace_config
26
+
27
+ from ..core.types import QueuePriority, TaskScope
28
+ from ..integrations.loop_adapter import LoopInjectionBatch
29
+ from .attachments import SkillAttachmentBuilder
30
+ from .models import SkillHint
31
+
32
+ if TYPE_CHECKING:
33
+ from langchain_agentx.tools.skill.loader import SkillCommandRepository
34
+
35
+ _PRIORITY_ORDER = {
36
+ QueuePriority.NOW: 0,
37
+ QueuePriority.NEXT: 1,
38
+ QueuePriority.LATER: 2,
39
+ }
40
+
41
+ # 对齐 CC SkillTool prompt 的 listing 预算/截断语义。
42
+ # - 总预算:listing 最多占上下文的一小部分,避免吞噬主对话 token。
43
+ # - 单条上限:每个 skill 的描述做硬截断,避免个别长描述“挤掉”其它技能。
44
+ SKILL_BUDGET_CONTEXT_PERCENT = 0.01
45
+ CHARS_PER_TOKEN = 4
46
+ DEFAULT_CHAR_BUDGET = 8_000
47
+ MAX_LISTING_DESC_CHARS = 250
48
+ MIN_DESC_LENGTH = 20
49
+
50
+
51
+ class SkillPrefetchProvider:
52
+ def __init__(
53
+ self,
54
+ *,
55
+ workspace_root: str | Path = ".",
56
+ agent_home: str = ".langchain_agentx",
57
+ skills_root: str | Path | None = None,
58
+ repository: "SkillCommandRepository | None" = None,
59
+ attachment_builder: SkillAttachmentBuilder | None = None,
60
+ max_queue_size: int | None = None,
61
+ ttl_sec: float | None = None,
62
+ ) -> None:
63
+ del max_queue_size, ttl_sec
64
+ workspace_cfg = resolve_agent_workspace_config(
65
+ workspace_root=workspace_root,
66
+ agent_home=agent_home,
67
+ )
68
+ skills_dir = (
69
+ Path(skills_root).expanduser().resolve()
70
+ if skills_root
71
+ else workspace_cfg.skills_dir
72
+ )
73
+ if repository is None:
74
+ # 延迟导入,避免 task_runtime <-> tools 初始化循环依赖。
75
+ from langchain_agentx.tools.skill.loader import SkillCommandRepository
76
+
77
+ repository = SkillCommandRepository(skills_dir)
78
+ self._repository = repository
79
+ self._attachment_builder = attachment_builder or SkillAttachmentBuilder()
80
+ self._lock = RLock()
81
+ self._queue: list[SkillHint] = []
82
+ self._reserved_batches: dict[str, list[SkillHint]] = {}
83
+ # 可见 listing 的 (name, description, when_to_use) 签名,用于幂等刷新
84
+ self._listing_signature_by_scope: dict[str, tuple[tuple[str, str, str], ...]] = {}
85
+
86
+ def ingest_skill_items(
87
+ self,
88
+ *,
89
+ scope: TaskScope,
90
+ items: list[dict],
91
+ priority: QueuePriority = QueuePriority.NEXT,
92
+ ) -> int:
93
+ inserted = 0
94
+ for idx, item in enumerate(items):
95
+ skill_id = str(item.get("skill_id") or f"skill_{idx}")
96
+ summary = str(item.get("summary") or "").strip() or f"skill {skill_id}"
97
+ hint = SkillHint(
98
+ kind="dynamic_skill",
99
+ command_id=f"skill_{skill_id}",
100
+ summary=summary,
101
+ scope=scope,
102
+ priority=priority,
103
+ payload=dict(item),
104
+ dedup_key=f"skill:{scope.agent_id or 'main'}:{skill_id}",
105
+ )
106
+ self.enqueue_hint(hint)
107
+ inserted += 1
108
+ return inserted
109
+
110
+ def ingest_from_skill_metadata(
111
+ self,
112
+ *,
113
+ scope: TaskScope,
114
+ skills_metadata: list[dict],
115
+ priority: QueuePriority = QueuePriority.NEXT,
116
+ ) -> int:
117
+ items: list[dict] = []
118
+ for skill in skills_metadata:
119
+ skill_id = str(skill.get("name") or "").strip()
120
+ if not skill_id:
121
+ continue
122
+ items.append(
123
+ {
124
+ "skill_id": skill_id,
125
+ "summary": str(skill.get("description") or "").strip() or f"skill {skill_id}",
126
+ "path": skill.get("path"),
127
+ "source": skill.get("source", "prefetch"),
128
+ }
129
+ )
130
+ return self.ingest_skill_items(scope=scope, items=items, priority=priority)
131
+
132
+ def enqueue_hint(self, hint: SkillHint) -> None:
133
+ with self._lock:
134
+ if hint.dedup_key:
135
+ scope_id = self._scope_id(hint.scope)
136
+ self._queue = [
137
+ item
138
+ for item in self._queue
139
+ if not (
140
+ self._scope_id(item.scope) == scope_id
141
+ and item.dedup_key == hint.dedup_key
142
+ )
143
+ ]
144
+ self._queue.append(hint)
145
+
146
+ def build_batch(
147
+ self,
148
+ scope: TaskScope,
149
+ max_priority: QueuePriority = QueuePriority.NEXT,
150
+ *,
151
+ limit: int = 8,
152
+ ) -> LoopInjectionBatch:
153
+ # 仅把“当前 scope 且优先级满足阈值”的 hint 打包给 loop。
154
+ # 被选中的 hint 会先从队列移除并进入 reserved,等待 ack/nack。
155
+ threshold = _PRIORITY_ORDER[max_priority]
156
+ if limit <= 0:
157
+ return LoopInjectionBatch(batch_id="", command_ids=[], attachment_messages=[])
158
+ with self._lock:
159
+ selected = [
160
+ item
161
+ for item in self._queue
162
+ if self._scope_id(item.scope) == self._scope_id(scope)
163
+ and _PRIORITY_ORDER[item.priority] <= threshold
164
+ ]
165
+ selected.sort(key=lambda item: _PRIORITY_ORDER[item.priority])
166
+ selected = selected[:limit]
167
+ if not selected:
168
+ return LoopInjectionBatch(batch_id="", command_ids=[], attachment_messages=[])
169
+
170
+ selected_ids = {item.command_id for item in selected}
171
+ self._queue = [item for item in self._queue if item.command_id not in selected_ids]
172
+ batch_id = f"spbatch_{uuid4().hex[:10]}"
173
+ self._reserved_batches[batch_id] = selected
174
+ return LoopInjectionBatch(
175
+ batch_id=batch_id,
176
+ command_ids=[item.command_id for item in selected],
177
+ attachment_messages=[
178
+ self._attachment_builder.build(item) for item in selected
179
+ ],
180
+ )
181
+
182
+ def ack_batch(self, batch: LoopInjectionBatch) -> None:
183
+ if not batch.batch_id:
184
+ return
185
+ with self._lock:
186
+ self._reserved_batches.pop(batch.batch_id, None)
187
+
188
+ def nack_batch(self, batch: LoopInjectionBatch, *, requeue: bool = True) -> None:
189
+ if not batch.batch_id:
190
+ return
191
+ with self._lock:
192
+ reserved = self._reserved_batches.pop(batch.batch_id, None)
193
+ if reserved and requeue:
194
+ self._queue.extend(reserved)
195
+
196
+ def has_pending(
197
+ self, scope: TaskScope, max_priority: QueuePriority = QueuePriority.NEXT
198
+ ) -> bool:
199
+ threshold = _PRIORITY_ORDER[max_priority]
200
+ with self._lock:
201
+ return any(
202
+ self._scope_id(item.scope) == self._scope_id(scope)
203
+ and _PRIORITY_ORDER[item.priority] <= threshold
204
+ for item in self._queue
205
+ )
206
+
207
+ def ingest_from_messages(self, *, scope: TaskScope, messages: list) -> int:
208
+ # v1:仅触发 listing 刷新(幂等),不从 messages 抽取动态候选。
209
+ # v2 特性:基于 messages 做 dynamic_skill 候选提取,待专项设计后启用。
210
+ del messages
211
+ hint = self._build_listing_hint(scope=scope)
212
+ if hint is None:
213
+ return 0
214
+ self.enqueue_hint(hint)
215
+ return 1
216
+
217
+ def _build_listing_hint(self, *, scope: TaskScope) -> SkillHint | None:
218
+ # 1) 从仓库读取所有命令并做可见性过滤(description/when_to_use/disable-model-invocation)。
219
+ # 2) 通过签名判断是否变化,未变化则不重复注入(减少上下文噪声)。
220
+ # 3) 生成“仅 metadata”的 listing 文本,正文由 skill 工具按需加载。
221
+ commands = self._repository.list_commands()
222
+ if not commands:
223
+ return None
224
+ ordered = sorted((c for c in commands if c.name), key=lambda c: c.name)
225
+ visible = [c for c in ordered if c.visible_in_slash_command_tool_listing()]
226
+ if not visible:
227
+ return None
228
+ signature = tuple(
229
+ (c.name, (c.description or "").strip(), (c.when_to_use or "").strip()) for c in visible
230
+ )
231
+ scope_id = self._scope_id(scope)
232
+ if self._listing_signature_by_scope.get(scope_id) == signature:
233
+ return None
234
+ self._listing_signature_by_scope[scope_id] = signature
235
+
236
+ # CC 对齐:listing 仅暴露 metadata,正文通过 skill 工具按需加载;
237
+ # 同时按上下文预算进行长度控制,避免 listing 吃掉过多上下文。
238
+ header = (
239
+ "Available skills (metadata only — call the `skill` tool with `skill_name` "
240
+ "to load full SKILL.md):"
241
+ )
242
+ summary = self._format_listing_within_budget(visible, header=header)
243
+
244
+ return SkillHint(
245
+ kind="skill_listing",
246
+ command_id=f"skill_listing_{scope_id}_{len(visible)}",
247
+ summary=summary,
248
+ scope=scope,
249
+ priority=QueuePriority.NEXT,
250
+ payload={"skill_count": len(visible), "source": "prefetch"},
251
+ dedup_key=f"skill_listing:{scope_id}",
252
+ )
253
+
254
+ @staticmethod
255
+ def _scope_id(scope: TaskScope) -> str:
256
+ return scope.agent_id or "main"
257
+
258
+ def _get_char_budget(self, context_window_tokens: int | None = None) -> int:
259
+ # 预算优先级:
260
+ # 1) 显式环境变量(便于压测/灰度);
261
+ # 2) 模型上下文窗口推导;
262
+ # 3) 默认常量(与 CC 默认一致量级)。
263
+ raw = os.getenv("SLASH_COMMAND_TOOL_CHAR_BUDGET")
264
+ if raw:
265
+ try:
266
+ value = int(raw)
267
+ except ValueError:
268
+ value = 0
269
+ if value > 0:
270
+ return value
271
+ if context_window_tokens and context_window_tokens > 0:
272
+ return int(context_window_tokens * CHARS_PER_TOKEN * SKILL_BUDGET_CONTEXT_PERCENT)
273
+ return DEFAULT_CHAR_BUDGET
274
+
275
+ @staticmethod
276
+ def _truncate(text: str, max_len: int) -> str:
277
+ if max_len <= 0:
278
+ return ""
279
+ if len(text) <= max_len:
280
+ return text
281
+ if max_len == 1:
282
+ return "…"
283
+ return text[: max_len - 1] + "…"
284
+
285
+ def _command_description(self, cmd) -> str:
286
+ # CC 语义:展示 description + when_to_use(若同时存在),再做单条硬截断。
287
+ desc = (cmd.description or "").strip().replace("\n", " ")
288
+ wtu = (cmd.when_to_use or "").strip().replace("\n", " ")
289
+ merged = f"{desc} - {wtu}" if desc and wtu else (desc or wtu)
290
+ return self._truncate(merged, MAX_LISTING_DESC_CHARS)
291
+
292
+ def _format_command_line(self, cmd, *, desc_max_len: int | None = None) -> str:
293
+ desc = self._command_description(cmd)
294
+ if desc_max_len is not None:
295
+ desc = self._truncate(desc, desc_max_len)
296
+ if desc:
297
+ return f"- `/{cmd.name}`: {desc}"
298
+ return f"- `/{cmd.name}`"
299
+
300
+ def _format_listing_within_budget(self, commands: list, *, header: str) -> str:
301
+ """
302
+ 在给定字符预算内渲染 skill listing。
303
+
304
+ 逐级降级策略(对齐 CC 思路):
305
+ 1) 先尝试完整条目(每条已做 MAX_LISTING_DESC_CHARS 截断);
306
+ 2) 超预算时按“均摊描述长度”二次截断;
307
+ 3) 若均摊长度过小,退化为 names-only;
308
+ 4) 最后兜底:从尾部裁剪条目,保证绝不超预算。
309
+ """
310
+ if not commands:
311
+ return header
312
+ budget = max(self._get_char_budget(), len(header) + 1)
313
+ full_entries = [self._format_command_line(cmd) for cmd in commands]
314
+ full_text = header + "\n" + "\n".join(full_entries)
315
+ if len(full_text) <= budget:
316
+ return full_text
317
+
318
+ # 仅名称+固定前缀开销:`- `/{name}`: `
319
+ name_overhead = sum(len(f"- `/{cmd.name}`: ") for cmd in commands)
320
+ newline_overhead = max(len(commands) - 1, 0)
321
+ available_for_desc = budget - len(header) - 1 - name_overhead - newline_overhead
322
+ max_desc_len = available_for_desc // len(commands)
323
+
324
+ if max_desc_len < MIN_DESC_LENGTH:
325
+ names_only = [f"- `/{cmd.name}`" for cmd in commands]
326
+ return header + "\n" + "\n".join(names_only)
327
+
328
+ trimmed_entries = [
329
+ self._format_command_line(cmd, desc_max_len=max_desc_len) for cmd in commands
330
+ ]
331
+ trimmed_text = header + "\n" + "\n".join(trimmed_entries)
332
+ if len(trimmed_text) <= budget:
333
+ return trimmed_text
334
+
335
+ # 兜底:从末尾移除,保证不超预算
336
+ kept: list[str] = []
337
+ running_len = len(header)
338
+ for entry in trimmed_entries:
339
+ extra = 1 + len(entry)
340
+ if running_len + extra > budget:
341
+ break
342
+ kept.append(entry)
343
+ running_len += extra
344
+ return header + ("\n" + "\n".join(kept) if kept else "")
@@ -0,0 +1,6 @@
1
+ """Task record persistence implementations."""
2
+
3
+ from .in_memory import InMemoryTaskStore
4
+ from .sqlite_store import SqliteTaskStore
5
+
6
+ __all__ = ["InMemoryTaskStore", "SqliteTaskStore"]