@scemoon/cdh 1.0.4 → 1.0.6

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 (269) hide show
  1. package/ai-dlc-skill/SKILL.md +4 -2
  2. package/ai-dlc-skill/agents/master.md +6 -0
  3. package/ai-dlc-skill/agents/plan-agent.md +2 -2
  4. package/ai-dlc-skill/agents/understand-agent.md +4 -4
  5. package/ai-dlc-skill/agents/verify-agent.md +1 -1
  6. package/ai-dlc-skill/architecture/project-structure.md +10 -10
  7. package/ai-dlc-skill/brownfield/README.md +2 -2
  8. package/ai-dlc-skill/brownfield/generate-context.sh +3 -3
  9. package/ai-dlc-skill/brownfield/scripts/extract-api.sh +5 -5
  10. package/ai-dlc-skill/components/backend.md +1 -1
  11. package/ai-dlc-skill/components/web.md +2 -2
  12. package/ai-dlc-skill/contracts/README.md +3 -3
  13. package/ai-dlc-skill/cross-tool/cline/export.sh +1 -1
  14. package/ai-dlc-skill/cross-tool/copilot/export.sh +1 -1
  15. package/ai-dlc-skill/cross-tool/onecode/export-skills.sh +7 -3
  16. package/ai-dlc-skill/cross-tool/onecode/export.sh +14 -8
  17. package/ai-dlc-skill/phases/deliver/lifecycle.md +6 -6
  18. package/ai-dlc-skill/phases/deliver/prompt.md +2 -2
  19. package/ai-dlc-skill/phases/deliver/rules.md +1 -1
  20. package/ai-dlc-skill/phases/plan/lifecycle.md +11 -11
  21. package/ai-dlc-skill/phases/plan/prompt.md +6 -6
  22. package/ai-dlc-skill/phases/plan/rules.md +1 -1
  23. package/ai-dlc-skill/phases/understand/entry.md +1 -1
  24. package/ai-dlc-skill/phases/understand/lifecycle.md +7 -7
  25. package/ai-dlc-skill/phases/understand/prompt.md +8 -8
  26. package/ai-dlc-skill/phases/understand/rules.md +5 -5
  27. package/ai-dlc-skill/phases/verify/lifecycle.md +24 -24
  28. package/ai-dlc-skill/phases/verify/prompt.md +8 -8
  29. package/ai-dlc-skill/phases/verify/rules.md +9 -9
  30. package/ai-dlc-skill/providers/README.md +2 -2
  31. package/ai-dlc-skill/providers/aliyun/preview.yaml +2 -2
  32. package/ai-dlc-skill/providers/tcb/preview.yaml +2 -2
  33. package/ai-dlc-skill/requirements.md +7 -7
  34. package/ai-dlc-skill/skill.yaml +4 -4
  35. package/ai-dlc-skill/templates/integration/CHANGELOG.md +2 -2
  36. package/ai-dlc-skill/templates/integration/contract-diff.md +7 -7
  37. package/ai-dlc-skill/templates/integration/contract-spec.md +7 -7
  38. package/ai-dlc-skill/templates/integration/runtime-contract.yaml +1 -1
  39. package/ai-dlc-skill/templates/project/README.md +18 -19
  40. package/ai-dlc-skill/templates/project/template.md +21 -21
  41. package/ai-dlc-skill/walkthrough/collect.sh +1 -1
  42. package/ai-dlc-skill/walkthrough/template.md +2 -2
  43. package/ai-dlc-skill/workflows/ai-dlc.yaml +12 -12
  44. package/ai-dlc-skill/workflows/deployment.yaml +4 -4
  45. package/cdh/__pycache__/cdh_mcp_injector.cpython-314.pyc +0 -0
  46. package/cdh/__pycache__/cdh_mcp_loader.cpython-314.pyc +0 -0
  47. package/cdh/__pycache__/cdh_mcp_manager.cpython-314.pyc +0 -0
  48. package/cdh/__pycache__/cdh_session_aggregator.cpython-314.pyc +0 -0
  49. package/cdh/__pycache__/cdh_skill_loader.cpython-314.pyc +0 -0
  50. package/cdh/__pycache__/cdh_skill_manager.cpython-314.pyc +0 -0
  51. package/cdh/__pycache__/cli.cpython-313.pyc +0 -0
  52. package/cdh/__pycache__/cli.cpython-314.pyc +0 -0
  53. package/cdh/__pycache__/scaffold.cpython-314.pyc +0 -0
  54. package/cdh/cdh_mcp_injector.py +115 -0
  55. package/cdh/cdh_mcp_loader.py +67 -0
  56. package/cdh/cdh_mcp_manager.py +73 -0
  57. package/cdh/cdh_session_aggregator.py +258 -0
  58. package/cdh/cdh_skill_manager.py +105 -0
  59. package/cdh/cli.py +64 -4
  60. package/cdh/scaffold.py +192 -41
  61. package/onecode/__init__.py +1 -1
  62. package/onecode/__pycache__/cli.cpython-314.pyc +0 -0
  63. package/onecode/__pycache__/commands.cpython-314.pyc +0 -0
  64. package/onecode/__pycache__/config.cpython-314.pyc +0 -0
  65. package/onecode/__pycache__/config_screen.cpython-314.pyc +0 -0
  66. package/onecode/__pycache__/migrate.cpython-314.pyc +0 -0
  67. package/onecode/agent/__pycache__/cdh_loader.cpython-314.pyc +0 -0
  68. package/onecode/agent/__pycache__/context.cpython-314.pyc +0 -0
  69. package/onecode/agent/__pycache__/engine.cpython-314.pyc +0 -0
  70. package/onecode/agent/__pycache__/hooks.cpython-314.pyc +0 -0
  71. package/onecode/agent/__pycache__/onecode_agent_acp.cpython-314.pyc +0 -0
  72. package/onecode/agent/__pycache__/permissions.cpython-314.pyc +0 -0
  73. package/onecode/agent/__pycache__/project_doc.cpython-314.pyc +0 -0
  74. package/onecode/agent/__pycache__/session.cpython-314.pyc +0 -0
  75. package/onecode/agent/agents/__pycache__/types.cpython-314.pyc +0 -0
  76. package/onecode/agent/agents/types.py +47 -164
  77. package/onecode/agent/attachments.py +0 -1
  78. package/onecode/agent/cdh_loader.py +17 -25
  79. package/onecode/agent/context.py +8 -3
  80. package/onecode/agent/engine.py +835 -220
  81. package/onecode/agent/hooks.py +1 -1
  82. package/onecode/agent/onecode_agent_acp.py +460 -47
  83. package/onecode/agent/onecode_agent_acp.py.bak +2447 -0
  84. package/onecode/agent/permissions.py +0 -1
  85. package/onecode/agent/project_doc.py +19 -0
  86. package/onecode/agent/session.py +17 -3
  87. package/onecode/agent/snapshot.py +3 -4
  88. package/onecode/agent/tools/__pycache__/agent_tools.cpython-314.pyc +0 -0
  89. package/onecode/agent/tools/__pycache__/apply_patch_tool.cpython-314.pyc +0 -0
  90. package/onecode/agent/tools/__pycache__/bash_tool.cpython-314.pyc +0 -0
  91. package/onecode/agent/tools/__pycache__/communication_tools.cpython-314.pyc +0 -0
  92. package/onecode/agent/tools/__pycache__/config_tool.cpython-314.pyc +0 -0
  93. package/onecode/agent/tools/__pycache__/cron_tools.cpython-314.pyc +0 -0
  94. package/onecode/agent/tools/__pycache__/file_ops.cpython-314.pyc +0 -0
  95. package/onecode/agent/tools/__pycache__/file_tools.cpython-314.pyc +0 -0
  96. package/onecode/agent/tools/__pycache__/git_tools.cpython-314.pyc +0 -0
  97. package/onecode/agent/tools/__pycache__/lsp_tools.cpython-314.pyc +0 -0
  98. package/onecode/agent/tools/__pycache__/mcp_tools.cpython-314.pyc +0 -0
  99. package/onecode/agent/tools/__pycache__/permission_handler.cpython-314.pyc +0 -0
  100. package/onecode/agent/tools/__pycache__/protocol.cpython-314.pyc +0 -0
  101. package/onecode/agent/tools/__pycache__/registry.cpython-314.pyc +0 -0
  102. package/onecode/agent/tools/__pycache__/sandbox.cpython-314.pyc +0 -0
  103. package/onecode/agent/tools/__pycache__/skill_tools.cpython-314.pyc +0 -0
  104. package/onecode/agent/tools/__pycache__/todo_tools.cpython-314.pyc +0 -0
  105. package/onecode/agent/tools/__pycache__/web_tools.cpython-314.pyc +0 -0
  106. package/onecode/agent/tools/agent_tools.py +4 -6
  107. package/onecode/agent/tools/apply_patch_tool.py +2 -8
  108. package/onecode/agent/tools/bash_tool.py +32 -12
  109. package/onecode/agent/tools/communication_tools.py +4 -5
  110. package/onecode/agent/tools/config_tool.py +3 -3
  111. package/onecode/agent/tools/cron_tools.py +4 -5
  112. package/onecode/agent/tools/file_ops.py +37 -3
  113. package/onecode/agent/tools/file_tools.py +10 -11
  114. package/onecode/agent/tools/git_tools.py +2 -2
  115. package/onecode/agent/tools/lsp_tools.py +2 -2
  116. package/onecode/agent/tools/mcp_tools.py +3 -5
  117. package/onecode/agent/tools/permission_handler.py +1 -1
  118. package/onecode/agent/tools/protocol.py +1 -1
  119. package/onecode/agent/tools/registry.py +69 -2
  120. package/onecode/agent/tools/sandbox.py +110 -16
  121. package/onecode/agent/tools/skill_tools.py +2 -2
  122. package/onecode/agent/tools/todo_tools.py +38 -19
  123. package/onecode/agent/tools/web_tools.py +6 -7
  124. package/onecode/builtin_skills/agent-browser/SKILL.md +212 -0
  125. package/onecode/builtin_skills/agent-browser/skill.yaml +8 -0
  126. package/onecode/builtin_skills/skill-creator/SKILL.md +106 -0
  127. package/onecode/builtin_skills/skill-creator/skill.yaml +8 -0
  128. package/onecode/cli.py +186 -6
  129. package/onecode/codebase/__pycache__/chunker.cpython-314.pyc +0 -0
  130. package/onecode/codebase/__pycache__/indexer.cpython-314.pyc +0 -0
  131. package/onecode/codebase/__pycache__/retriever.cpython-314.pyc +0 -0
  132. package/onecode/codebase/__pycache__/storage.cpython-314.pyc +0 -0
  133. package/onecode/codebase/chunker.py +0 -5
  134. package/onecode/codebase/indexer.py +14 -1
  135. package/onecode/codebase/retriever.py +13 -2
  136. package/onecode/codebase/storage.py +5 -4
  137. package/onecode/commands.py +81 -6
  138. package/onecode/config.py +41 -12
  139. package/onecode/config_screen.py +7 -3
  140. package/onecode/mcp/__pycache__/manager.cpython-314.pyc +0 -0
  141. package/onecode/mcp/manager.py +2 -3
  142. package/onecode/memory/__init__.py +7 -53
  143. package/onecode/memory/__pycache__/__init__.cpython-314.pyc +0 -0
  144. package/onecode/memory/__pycache__/backend.cpython-314.pyc +0 -0
  145. package/onecode/memory/__pycache__/offload.cpython-314.pyc +0 -0
  146. package/onecode/memory/__pycache__/pyramid.cpython-314.pyc +0 -0
  147. package/onecode/memory/__pycache__/recall.cpython-314.pyc +0 -0
  148. package/onecode/memory/backend.py +13 -39
  149. package/onecode/memory/pyramid.py +2 -71
  150. package/onecode/memory/recall.py +38 -3
  151. package/onecode/migrate.py +122 -0
  152. package/onecode/models/__pycache__/messages.cpython-314.pyc +0 -0
  153. package/onecode/models/__pycache__/provider.cpython-314.pyc +0 -0
  154. package/onecode/models/messages.py +58 -0
  155. package/onecode/models/provider.py +20 -2
  156. package/onecode/models/providers/__pycache__/anthropic_provider.cpython-314.pyc +0 -0
  157. package/onecode/models/providers/__pycache__/glm_provider.cpython-314.pyc +0 -0
  158. package/onecode/models/providers/__pycache__/minimax_provider.cpython-314.pyc +0 -0
  159. package/onecode/models/providers/__pycache__/minimaxi_provider.cpython-314.pyc +0 -0
  160. package/onecode/models/providers/__pycache__/ollama_provider.cpython-314.pyc +0 -0
  161. package/onecode/models/providers/anthropic_provider.py +59 -12
  162. package/onecode/models/providers/glm_provider.py +40 -0
  163. package/onecode/models/providers/minimax_provider.py +3 -0
  164. package/onecode/models/providers/minimaxi_provider.py +3 -0
  165. package/onecode/models/providers/ollama_provider.py +3 -0
  166. package/onecode/server/app.py +1 -1
  167. package/onecode/skills/__pycache__/argument_substitution.cpython-314.pyc +0 -0
  168. package/onecode/skills/__pycache__/bootstrap.cpython-314.pyc +0 -0
  169. package/onecode/skills/__pycache__/loader.cpython-314.pyc +0 -0
  170. package/onecode/skills/__pycache__/manager.cpython-314.pyc +0 -0
  171. package/onecode/skills/argument_substitution.py +0 -1
  172. package/onecode/skills/bootstrap.py +124 -0
  173. package/onecode/skills/loader.py +35 -53
  174. package/onecode/skills/manager.py +2 -2
  175. package/onecode/storage/activity.py +3 -4
  176. package/onecode/storage/session.py +3 -4
  177. package/onecode/tasks/manager.py +23 -10
  178. package/onecode/trace/tracer.py +2 -3
  179. package/package.json +1 -1
  180. package/pyproject.toml +5 -1
  181. package/run.js +68 -14
  182. package/tui/__pycache__/about.cpython-314.pyc +0 -0
  183. package/tui/__pycache__/app.cpython-313.pyc +0 -0
  184. package/tui/__pycache__/app.cpython-314.pyc +0 -0
  185. package/tui/__pycache__/danger.cpython-314.pyc +0 -0
  186. package/tui/__pycache__/directory_suggester.cpython-314.pyc +0 -0
  187. package/tui/__pycache__/messages.cpython-314.pyc +0 -0
  188. package/tui/__pycache__/path_complete.cpython-314.pyc +0 -0
  189. package/tui/__pycache__/paths.cpython-314.pyc +0 -0
  190. package/tui/__pycache__/session_tracker.cpython-314.pyc +0 -0
  191. package/tui/__pycache__/settings_schema.cpython-314.pyc +0 -0
  192. package/tui/acp/__pycache__/agent.cpython-314.pyc +0 -0
  193. package/tui/acp/__pycache__/api.cpython-314.pyc +0 -0
  194. package/tui/acp/__pycache__/messages.cpython-314.pyc +0 -0
  195. package/tui/acp/__pycache__/prompt.cpython-314.pyc +0 -0
  196. package/tui/acp/__pycache__/protocol.cpython-314.pyc +0 -0
  197. package/tui/acp/agent.py +125 -58
  198. package/tui/acp/api.py +12 -0
  199. package/tui/acp/messages.py +3 -1
  200. package/tui/acp/prompt.py +0 -57
  201. package/tui/acp/protocol.py +6 -0
  202. package/tui/app.py +11 -33
  203. package/tui/messages.py +2 -0
  204. package/tui/path_complete.py +1 -1
  205. package/tui/paths.py +16 -0
  206. package/tui/screens/__pycache__/agent_modal.cpython-314.pyc +0 -0
  207. package/tui/screens/__pycache__/component_picker.cpython-314.pyc +0 -0
  208. package/tui/screens/__pycache__/diff_screen.cpython-314.pyc +0 -0
  209. package/tui/screens/__pycache__/log.cpython-314.pyc +0 -0
  210. package/tui/screens/__pycache__/main.cpython-313.pyc +0 -0
  211. package/tui/screens/__pycache__/main.cpython-314.pyc +0 -0
  212. package/tui/screens/__pycache__/permissions.cpython-314.pyc +0 -0
  213. package/tui/screens/__pycache__/projects_app.cpython-314.pyc +0 -0
  214. package/tui/screens/__pycache__/projects_screen.cpython-314.pyc +0 -0
  215. package/tui/screens/__pycache__/sessions.cpython-314.pyc +0 -0
  216. package/tui/screens/__pycache__/store.cpython-314.pyc +0 -0
  217. package/tui/screens/__pycache__/tool_content_screen.cpython-314.pyc +0 -0
  218. package/tui/screens/component_picker.py +35 -15
  219. package/tui/screens/diff_screen.py +84 -0
  220. package/tui/screens/log.py +25 -25
  221. package/tui/screens/main.py +48 -26
  222. package/tui/screens/projects.tcss +5 -1
  223. package/tui/screens/projects_app.py +6 -6
  224. package/tui/screens/projects_screen.py +19 -39
  225. package/tui/screens/sessions.py +3 -5
  226. package/tui/session_tracker.py +3 -0
  227. package/tui/settings_schema.py +1 -1
  228. package/tui/visuals/__pycache__/columns.cpython-314.pyc +0 -0
  229. package/tui/visuals/columns.py +1 -2
  230. package/tui/widgets/__pycache__/acp_content.cpython-314.pyc +0 -0
  231. package/tui/widgets/__pycache__/agent_response.cpython-314.pyc +0 -0
  232. package/tui/widgets/__pycache__/agent_thought.cpython-314.pyc +0 -0
  233. package/tui/widgets/__pycache__/conversation.cpython-314.pyc +0 -0
  234. package/tui/widgets/__pycache__/diff_view.cpython-314.pyc +0 -0
  235. package/tui/widgets/__pycache__/directory_input.cpython-314.pyc +0 -0
  236. package/tui/widgets/__pycache__/future_text.cpython-314.pyc +0 -0
  237. package/tui/widgets/__pycache__/grid_select.cpython-314.pyc +0 -0
  238. package/tui/widgets/__pycache__/load_more.cpython-314.pyc +0 -0
  239. package/tui/widgets/__pycache__/mandelbrot.cpython-314.pyc +0 -0
  240. package/tui/widgets/__pycache__/markdown_note.cpython-314.pyc +0 -0
  241. package/tui/widgets/__pycache__/modified_files.cpython-314.pyc +0 -0
  242. package/tui/widgets/__pycache__/plan.cpython-314.pyc +0 -0
  243. package/tui/widgets/__pycache__/project_grid_select.cpython-314.pyc +0 -0
  244. package/tui/widgets/__pycache__/project_summary.cpython-314.pyc +0 -0
  245. package/tui/widgets/__pycache__/session_grid_select.cpython-314.pyc +0 -0
  246. package/tui/widgets/__pycache__/session_summary.cpython-314.pyc +0 -0
  247. package/tui/widgets/__pycache__/shell_result.cpython-314.pyc +0 -0
  248. package/tui/widgets/__pycache__/side_bar.cpython-314.pyc +0 -0
  249. package/tui/widgets/__pycache__/subagent.cpython-314.pyc +0 -0
  250. package/tui/widgets/__pycache__/subagent_screen.cpython-314.pyc +0 -0
  251. package/tui/widgets/__pycache__/terminal.cpython-314.pyc +0 -0
  252. package/tui/widgets/__pycache__/tool_call.cpython-314.pyc +0 -0
  253. package/tui/widgets/agent_response.py +0 -1
  254. package/tui/widgets/agent_thought.py +0 -1
  255. package/tui/widgets/conversation.py +157 -14
  256. package/tui/widgets/diff_view.py +10 -0
  257. package/tui/widgets/load_more.py +24 -0
  258. package/tui/widgets/modified_files.py +86 -12
  259. package/tui/widgets/plan.py +1 -1
  260. package/tui/widgets/project_grid_select.py +9 -2
  261. package/tui/widgets/session_grid_select.py +6 -0
  262. package/tui/widgets/side_bar.py +10 -0
  263. package/tui/widgets/subagent.py +92 -58
  264. package/tui/widgets/subagent_screen.py +638 -115
  265. package/tui/widgets/terminal.py +1 -2
  266. package/tui/widgets/terminal_tool.py +11 -2
  267. package/tui/widgets/tool_call.py +35 -2
  268. package/onecode/memory/offload.py +0 -114
  269. package/onecode/memory/symbolic.py +0 -164
@@ -105,6 +105,7 @@ TOOL_CATEGORY_MAP: dict[str, ToolCategory] = {
105
105
  "TodoUpdate": ToolCategory.TODO_MGMT,
106
106
  "TodoOutput": ToolCategory.TODO_MGMT,
107
107
  "TodoStop": ToolCategory.TODO_MGMT,
108
+ "TodoClear": ToolCategory.TODO_MGMT,
108
109
  "AskUser": ToolCategory.INTERACTION,
109
110
  }
110
111
 
@@ -280,6 +281,11 @@ class StreamEventType(str, Enum):
280
281
  SUBAGENT_CHUNK = "subagent_chunk"
281
282
  SUBAGENT_THINKING = "subagent_thinking"
282
283
  SUBAGENT_END = "subagent_end"
284
+ # A tool invoked *inside* a subagent, forwarded to the parent so the TUI
285
+ # can render it as a real ToolCall card inside the SubAgent widget (same
286
+ # style as main-conversation tools) instead of a plain "[Tool: X]" line.
287
+ SUBAGENT_TOOL_CALL = "subagent_tool_call"
288
+ SUBAGENT_TOOL_RESULT = "subagent_tool_result"
283
289
 
284
290
 
285
291
  @dataclass
@@ -327,6 +333,10 @@ class StreamEvent:
327
333
  subagent_thinking_text: str = ""
328
334
  subagent_status: str = ""
329
335
  subagent_error: str = ""
336
+ # SUBAGENT_TOOL_CALL fields ("start" | "complete"; reuses tool_id/name/
337
+ # category/args). SUBAGENT_TOOL_RESULT reuses tool_id/result_* and needs
338
+ # tool_name for display when no prior TOOL_CALL_COMPLETE was captured.
339
+ subagent_tool_phase: str = ""
330
340
 
331
341
  @classmethod
332
342
  def text_delta(cls, text: str) -> "StreamEvent":
@@ -448,6 +458,54 @@ class StreamEvent:
448
458
  subagent_error=error,
449
459
  )
450
460
 
461
+ @classmethod
462
+ def subagent_tool_call(
463
+ cls,
464
+ subagent_id: str,
465
+ phase: str,
466
+ tool_id: str,
467
+ tool_name: str,
468
+ tool_category: ToolCategory = ToolCategory.UNKNOWN,
469
+ tool_args: dict[str, Any] | None = None,
470
+ ) -> "StreamEvent":
471
+ """A tool invocation *inside* a subagent.
472
+
473
+ ``phase`` is ``"start"`` (model began the call) or ``"complete"``
474
+ (args fully streamed). Carries the parent Spawn ``subagent_id`` so
475
+ the ACP can tag the emitted ``tool_call`` sessionUpdate and the TUI
476
+ can mount the card inside the owning SubAgent widget.
477
+ """
478
+ return cls(
479
+ type=StreamEventType.SUBAGENT_TOOL_CALL,
480
+ subagent_id=subagent_id,
481
+ subagent_tool_phase=phase,
482
+ tool_id=tool_id,
483
+ tool_name=tool_name,
484
+ tool_category=tool_category,
485
+ tool_args=tool_args or {},
486
+ )
487
+
488
+ @classmethod
489
+ def subagent_tool_result(
490
+ cls,
491
+ subagent_id: str,
492
+ tool_id: str,
493
+ tool_name: str,
494
+ result_content: str,
495
+ is_error: bool = False,
496
+ category: ToolCategory = ToolCategory.UNKNOWN,
497
+ ) -> "StreamEvent":
498
+ """Result of a tool invoked *inside* a subagent."""
499
+ return cls(
500
+ type=StreamEventType.SUBAGENT_TOOL_RESULT,
501
+ subagent_id=subagent_id,
502
+ tool_id=tool_id,
503
+ tool_name=tool_name,
504
+ result_content=result_content,
505
+ result_is_error=is_error,
506
+ result_category=category,
507
+ )
508
+
451
509
  @staticmethod
452
510
  def is_text(event: "StreamEvent") -> bool:
453
511
  return event.type in (StreamEventType.TEXT_DELTA, StreamEventType.THINKING)
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import asyncio
5
6
  import logging
6
7
  from abc import ABC, abstractmethod
7
8
  from dataclasses import dataclass, field
@@ -592,6 +593,15 @@ class Provider(ABC):
592
593
  """Whether this provider uses Anthropic-style 'system' kwarg."""
593
594
  return False
594
595
 
596
+ def supports_native_tools(self) -> bool:
597
+ """Whether this provider accepts structured tools param in API calls.
598
+
599
+ When True, TOOL_DESCRIPTIONS prose in the system prompt is skipped
600
+ because the provider's native tool schema (passed via the ``tools``
601
+ kwarg to ``chat_stream_response``) provides equivalent information.
602
+ """
603
+ return True
604
+
595
605
  async def chat_stream_response(
596
606
  self,
597
607
  messages: list[Message],
@@ -605,7 +615,15 @@ class Provider(ABC):
605
615
  Default implementation: falls back to chat() for providers that
606
616
  don't implement structured streaming.
607
617
  """
608
- response = await self.chat(messages, model=model, **kwargs)
618
+ logging.getLogger("onecode.models.provider").warning(
619
+ "[PROVIDER-STREAM] %s does not override chat_stream_response — "
620
+ "falling back to non-streaming chat() (on_text_chunk will NOT fire)",
621
+ type(self).__name__,
622
+ )
623
+ response = await asyncio.wait_for(
624
+ self.chat(messages, model=model, **kwargs),
625
+ timeout=300,
626
+ )
609
627
  return ChatResponse(
610
628
  content=response.get_text(),
611
629
  tool_uses=[
@@ -625,7 +643,7 @@ class Provider(ABC):
625
643
  body: str,
626
644
  *,
627
645
  retry_after: Optional[float] = None,
628
- ) -> "ProviderError":
646
+ ) -> "ProviderError": # noqa: F821
629
647
  """Convert an upstream HTTP error into the most specific
630
648
  :class:`ProviderError` subclass.
631
649
 
@@ -58,6 +58,25 @@ class AnthropicProvider(Provider):
58
58
  model=model,
59
59
  )
60
60
  async with httpx.AsyncClient(timeout=120) as client:
61
+ prepared = self.prepare_messages(messages)
62
+ system_content = None
63
+ filtered_messages = []
64
+ for msg in prepared:
65
+ if msg.get("role") == "system":
66
+ system_content = msg["content"]
67
+ else:
68
+ filtered_messages.append(msg)
69
+ api_body = {
70
+ "model": model,
71
+ "max_tokens": kwargs.get("max_tokens", 4096),
72
+ "messages": filtered_messages,
73
+ }
74
+ if system_content:
75
+ api_body["system"] = [{
76
+ "type": "text",
77
+ "text": system_content,
78
+ "cache_control": {"type": "ephemeral"},
79
+ }]
61
80
  resp = await client.post(
62
81
  f"{self._endpoint}/messages",
63
82
  headers={
@@ -65,11 +84,7 @@ class AnthropicProvider(Provider):
65
84
  "anthropic-version": "2023-06-01",
66
85
  "content-type": "application/json",
67
86
  },
68
- json={
69
- "model": model,
70
- "max_tokens": kwargs.get("max_tokens", 4096),
71
- "messages": self.prepare_messages(messages),
72
- },
87
+ json=api_body,
73
88
  )
74
89
  data = resp.json()
75
90
  return ModelResponse(
@@ -87,6 +102,26 @@ class AnthropicProvider(Provider):
87
102
  yield "API key not configured."
88
103
  return
89
104
  async with httpx.AsyncClient(timeout=300) as client:
105
+ prepared_cs = self.prepare_messages(messages)
106
+ system_content_cs = None
107
+ filtered_cs = []
108
+ for msg in prepared_cs:
109
+ if msg.get("role") == "system":
110
+ system_content_cs = msg["content"]
111
+ else:
112
+ filtered_cs.append(msg)
113
+ body_cs = {
114
+ "model": model,
115
+ "max_tokens": kwargs.get("max_tokens", 4096),
116
+ "messages": filtered_cs,
117
+ "stream": True,
118
+ }
119
+ if system_content_cs:
120
+ body_cs["system"] = [{
121
+ "type": "text",
122
+ "text": system_content_cs,
123
+ "cache_control": {"type": "ephemeral"},
124
+ }]
90
125
  async with client.stream(
91
126
  "POST",
92
127
  f"{self._endpoint}/messages",
@@ -95,12 +130,7 @@ class AnthropicProvider(Provider):
95
130
  "anthropic-version": "2023-06-01",
96
131
  "content-type": "application/json",
97
132
  },
98
- json={
99
- "model": model,
100
- "max_tokens": kwargs.get("max_tokens", 4096),
101
- "messages": self.prepare_messages(messages),
102
- "stream": True,
103
- },
133
+ json=body_cs,
104
134
  ) as resp:
105
135
  async for line in resp.aiter_lines():
106
136
  if line.startswith("data: ") and line[6:] != "[DONE]":
@@ -131,12 +161,29 @@ class AnthropicProvider(Provider):
131
161
 
132
162
  try:
133
163
  async with httpx.AsyncClient(timeout=300) as client:
164
+ # Extract system message for Anthropic's separate ``system``
165
+ # parameter (enables prompt caching).
166
+ prepared = self.prepare_messages(messages)
167
+ system_content = None
168
+ filtered_messages = []
169
+ for msg in prepared:
170
+ if msg.get("role") == "system":
171
+ system_content = msg["content"]
172
+ else:
173
+ filtered_messages.append(msg)
174
+
134
175
  body = {
135
176
  "model": model,
136
177
  "max_tokens": kwargs.get("max_tokens", 4096),
137
- "messages": self.prepare_messages(messages),
178
+ "messages": filtered_messages,
138
179
  "stream": True,
139
180
  }
181
+ if system_content:
182
+ body["system"] = [{
183
+ "type": "text",
184
+ "text": system_content,
185
+ "cache_control": {"type": "ephemeral"},
186
+ }]
140
187
  tools = kwargs.get("tools")
141
188
  if tools:
142
189
  body["tools"] = tools
@@ -1,6 +1,8 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import json
4
+ import logging
5
+ import time
4
6
  from typing import AsyncIterator, Callable, Optional
5
7
 
6
8
  import httpx
@@ -8,6 +10,8 @@ import httpx
8
10
  from onecode.models.errors import safe_error_msg
9
11
  from onecode.models.provider import ChatResponse, Message, ModelResponse, Provider, ProviderRegistry
10
12
 
13
+ logger = logging.getLogger("onecode.models.providers.glm")
14
+
11
15
 
12
16
  class GLMProvider(Provider):
13
17
  name = "glm"
@@ -92,6 +96,17 @@ class GLMProvider(Provider):
92
96
  return ChatResponse(content="API key not configured.")
93
97
  content_parts: list[str] = []
94
98
  stream_tool_calls: dict[int, dict] = {}
99
+ _n_msgs = len(messages)
100
+ _t0 = time.monotonic()
101
+ _first_byte_t: float | None = None
102
+ _chunk_n = 0
103
+ _text_n = 0
104
+ _bytes = 0
105
+ _on_text_cb = bool(on_text_chunk)
106
+ logger.debug(
107
+ "[GLM-STREAM] open model=%s msgs=%d on_text_chunk_cb=%s",
108
+ model, _n_msgs, _on_text_cb,
109
+ )
95
110
  try:
96
111
  async with httpx.AsyncClient(timeout=300) as client:
97
112
  async with client.stream(
@@ -108,12 +123,27 @@ class GLMProvider(Provider):
108
123
  **kwargs,
109
124
  },
110
125
  ) as resp:
126
+ logger.debug(
127
+ "[GLM-STREAM] stream opened status=%d t=%.2fs",
128
+ resp.status_code, time.monotonic() - _t0,
129
+ )
111
130
  async for line in resp.aiter_lines():
131
+ if not line:
132
+ continue
133
+ _chunk_n += 1
112
134
  if line.startswith("data: ") and line[6:] != "[DONE]":
113
135
  chunk = json.loads(line[6:])
114
136
  delta = chunk.get("choices", [{}])[0].get("delta", {})
115
137
  text = delta.get("content", "")
116
138
  if text:
139
+ if _first_byte_t is None:
140
+ _first_byte_t = time.monotonic() - _t0
141
+ logger.debug(
142
+ "[GLM-STREAM] first-byte t=%.2fs chunk#%d",
143
+ _first_byte_t, _chunk_n,
144
+ )
145
+ _text_n += 1
146
+ _bytes += len(text)
117
147
  content_parts.append(text)
118
148
  if on_text_chunk:
119
149
  on_text_chunk(text)
@@ -132,7 +162,17 @@ class GLMProvider(Provider):
132
162
  slot["arguments"] += args_delta
133
163
  if on_tool_call_delta:
134
164
  on_tool_call_delta(slot.get("id", ""), slot.get("name", ""), args_delta)
165
+ logger.debug(
166
+ "[GLM-STREAM] stream done chunks=%d text_chunks=%d "
167
+ "bytes=%d on_text_cb=%s t=%.2fs",
168
+ _chunk_n, _text_n, _bytes, _on_text_cb,
169
+ time.monotonic() - _t0,
170
+ )
135
171
  except Exception as e:
172
+ logger.exception(
173
+ "[GLM-STREAM] EXCEPTION t=%.2fs chunks=%d text_chunks=%d: %s",
174
+ time.monotonic() - _t0, _chunk_n, _text_n, e,
175
+ )
136
176
  return ChatResponse(content=f"Error: {safe_error_msg(e)}")
137
177
  tool_uses = []
138
178
  for slot in stream_tool_calls.values():
@@ -19,6 +19,9 @@ class MiniMaxProvider(Provider):
19
19
  def is_anthropic_style(self) -> bool:
20
20
  return False
21
21
 
22
+ def supports_native_tools(self) -> bool:
23
+ return False
24
+
22
25
  async def chat(
23
26
  self, messages: list[Message], model: str = "MiniMax-M2.7", **kwargs
24
27
  ) -> ModelResponse:
@@ -128,6 +128,9 @@ class MiniMaxiProvider(Provider):
128
128
  def is_anthropic_style(self) -> bool:
129
129
  return False
130
130
 
131
+ def supports_native_tools(self) -> bool:
132
+ return False
133
+
131
134
  async def chat(
132
135
  self, messages: list[Message], model: str = "", **kwargs
133
136
  ) -> ModelResponse:
@@ -18,6 +18,9 @@ class OllamaProvider(Provider):
18
18
  def is_anthropic_style(self) -> bool:
19
19
  return False
20
20
 
21
+ def supports_native_tools(self) -> bool:
22
+ return False
23
+
21
24
  async def chat(
22
25
  self, messages: list[Message], model: str = "llama2", **kwargs
23
26
  ) -> ModelResponse:
@@ -3,7 +3,7 @@ from __future__ import annotations
3
3
  import asyncio
4
4
  import json
5
5
  import logging
6
- from typing import Any, Optional
6
+ from typing import Optional
7
7
 
8
8
  from starlette.applications import Starlette
9
9
  from starlette.responses import JSONResponse, Response
@@ -1,7 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import re
4
- from typing import Any
5
4
 
6
5
 
7
6
  _PATH_RE = re.compile(r'\$path')
@@ -0,0 +1,124 @@
1
+ """onecode bootstrap — ensures ai-dlc-skill is installed in the cdh platform pool.
2
+
3
+ Called at onecode startup (or cdh launch) to sync the ai-dlc-skill version
4
+ from the repository source (ai-dlc-skill/) to ~/.cdh/skills/ai-dlc-skill/.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import logging
10
+ from pathlib import Path
11
+ from typing import Optional
12
+
13
+ import yaml
14
+
15
+ from cdh.cdh_skill_manager import CdhSkillManager
16
+
17
+ logger = logging.getLogger("onecode.skills.bootstrap")
18
+
19
+ SKILL_SOURCE_NAME = "ai-dlc-skill"
20
+ DEFAULT_VERSION = "4.0.0"
21
+
22
+
23
+ def get_source_skill_dir(workspace_root: Path) -> Optional[Path]:
24
+ """Walk up from workspace_root to find ai-dlc-skill/ directory."""
25
+ current = workspace_root.resolve()
26
+ # Check current directory
27
+ candidate = current / SKILL_SOURCE_NAME
28
+ if candidate.is_dir() and (candidate / "skill.yaml").exists():
29
+ return candidate
30
+ # Check parent (if in a subdirectory)
31
+ candidate = current.parent / SKILL_SOURCE_NAME
32
+ if candidate.is_dir() and (candidate / "skill.yaml").exists():
33
+ return candidate
34
+ # Check git root
35
+ try:
36
+ git_root = next(
37
+ p for p in current.parents if (p / ".git").exists()
38
+ )
39
+ candidate = git_root / SKILL_SOURCE_NAME
40
+ if candidate.is_dir() and (candidate / "skill.yaml").exists():
41
+ return candidate
42
+ except StopIteration:
43
+ pass
44
+ return None
45
+
46
+
47
+ def get_source_version(source_dir: Path) -> str:
48
+ """Read metadata.version from source skill.yaml."""
49
+ try:
50
+ skill_yaml = source_dir / "skill.yaml"
51
+ data = yaml.safe_load(skill_yaml.read_text(encoding="utf-8")) or {}
52
+ return data.get("metadata", {}).get("version", DEFAULT_VERSION)
53
+ except Exception:
54
+ return DEFAULT_VERSION
55
+
56
+
57
+ def ensure_onecode_default_skills() -> None:
58
+ """Install built-in skills (git, shell) to ~/.onecode/skills/ on first run."""
59
+ from onecode.config import ONECODE_DIR
60
+
61
+ target_dir = ONECODE_DIR / "skills"
62
+ target_dir.mkdir(parents=True, exist_ok=True)
63
+
64
+ builtin_root = Path(__file__).resolve().parent.parent / "builtin_skills"
65
+ if not builtin_root.exists():
66
+ return
67
+
68
+ mgr = CdhSkillManager(skills_dir=target_dir)
69
+
70
+ for d in sorted(builtin_root.iterdir()):
71
+ if not d.is_dir():
72
+ continue
73
+ if not (d / "skill.yaml").exists():
74
+ continue
75
+ name = d.name
76
+ if mgr.get_installed_version(name) is not None:
77
+ continue
78
+ err = mgr.install(d)
79
+ if err:
80
+ logger.warning("default skill %s install failed: %s", name, err)
81
+ else:
82
+ logger.info("default skill %s installed \u2192 %s/%s", name, target_dir, name)
83
+
84
+
85
+ def ensure_ai_dlc_skill(workspace_root: Path | None = None) -> None:
86
+ """Bootstrap ai-dlc-skill into cdh platform pool.
87
+
88
+ Compares source version with installed version. If missing or outdated,
89
+ installs via CdhSkillManager. Return value: None (logs warnings on failure).
90
+ """
91
+ root = workspace_root or Path.cwd()
92
+ mgr = CdhSkillManager()
93
+
94
+ # Check if already installed and up-to-date
95
+ installed_version = mgr.get_installed_version(SKILL_SOURCE_NAME)
96
+
97
+ # Find source directory
98
+ source_dir = get_source_skill_dir(root)
99
+ if source_dir is None:
100
+ if installed_version is None:
101
+ logger.warning(
102
+ "ai-dlc-skill source not found at %s/ai-dlc-skill/ and not installed; "
103
+ "install from repository root to enable AI-DLC methodology.",
104
+ root,
105
+ )
106
+ return
107
+
108
+ source_version = get_source_version(source_dir)
109
+
110
+ if installed_version == source_version:
111
+ logger.debug("ai-dlc-skill v%s is current, skipping install.", source_version)
112
+ return
113
+
114
+ # Install or update
115
+ err = mgr.install(source_dir)
116
+ if err:
117
+ logger.warning("ai-dlc-skill install failed: %s", err)
118
+ else:
119
+ logger.info(
120
+ "ai-dlc-skill installed v%s → %s/%s",
121
+ source_version,
122
+ mgr.skills_dir,
123
+ SKILL_SOURCE_NAME,
124
+ )
@@ -1,6 +1,5 @@
1
1
  from __future__ import annotations
2
2
 
3
- import re
4
3
  from pathlib import Path
5
4
  from typing import Optional
6
5
 
@@ -9,18 +8,10 @@ import yaml
9
8
  from onecode.skills.frontmatter import parse_frontmatter
10
9
  from onecode.skills.model import Skill
11
10
 
12
- from onecode.config import CLOUD_DEV_HARNESS_DIR
11
+ from onecode.config import ONECODE_DIR
13
12
 
14
13
 
15
- USER_SKILLS_DIR = CLOUD_DEV_HARNESS_DIR / "skills"
16
-
17
-
18
- SKILL_LOCATIONS = [
19
- ("user", USER_SKILLS_DIR),
20
- (".opencode", ".opencode/skills"),
21
- (".claude", ".claude/skills"),
22
- (".agents", ".agents/skills"),
23
- ]
14
+ USER_SKILLS_DIR = ONECODE_DIR / "skills"
24
15
 
25
16
 
26
17
  class SkillLoader:
@@ -31,50 +22,9 @@ class SkillLoader:
31
22
  def _get_search_dirs(self) -> list[tuple[str, Path]]:
32
23
  dirs = []
33
24
  if USER_SKILLS_DIR.exists():
34
- dirs.append(("user", USER_SKILLS_DIR))
35
-
36
- # Built-in skills packaged with onecode
37
- builtin_dir = Path(__file__).resolve().parent.parent / "builtin_skills"
38
- if builtin_dir.exists():
39
- dirs.append(("builtin", builtin_dir))
40
-
41
- # Project root-level directories containing SKILL.md
42
- try:
43
- for d in sorted(self._workspace_root.iterdir()):
44
- if d.is_dir() and (d / "SKILL.md").exists():
45
- dirs.append(("project", self._workspace_root))
46
- break
47
- except Exception:
48
- pass
49
-
50
- search_paths = [
51
- (".opencode", ".claude", ".agents"),
52
- ]
53
- for group in search_paths:
54
- for name in group:
55
- pattern = f"{name}/skills"
56
- for parent in self._walk_up_parents():
57
- candidate = parent / pattern
58
- if candidate.exists():
59
- dirs.append((f"{name}/skills", candidate))
60
- break
61
-
25
+ dirs.append(("onecode", USER_SKILLS_DIR))
62
26
  return dirs
63
27
 
64
- def _walk_up_parents(self):
65
- current = self._workspace_root.resolve()
66
- yield current
67
- try:
68
- git_root = next(
69
- (p for p in current.parents if (p / ".git").exists() or (p / ".hg").exists()),
70
- None,
71
- )
72
- if git_root:
73
- yield git_root
74
- except StopIteration:
75
- pass
76
- yield Path.home()
77
-
78
28
  def _discover(self) -> dict[str, Skill]:
79
29
  if self._cache is not None:
80
30
  return self._cache
@@ -153,5 +103,37 @@ class SkillLoader:
153
103
  if s.path and str(s.path).startswith(source_prefix)
154
104
  ]
155
105
 
106
+ def search(self, keyword: str) -> list[Skill]:
107
+ kw = keyword.lower()
108
+ results: list[tuple[Skill, int]] = []
109
+
110
+ for skill in self._discover().values():
111
+ score = 0
112
+ name_lower = skill.name.lower()
113
+ desc_lower = skill.description.lower()
114
+
115
+ if kw == name_lower:
116
+ score += 100
117
+ elif kw in name_lower:
118
+ score += 50
119
+ if kw in desc_lower:
120
+ score += 30
121
+ if desc_lower.startswith(kw):
122
+ score += 10
123
+ for trigger in skill.triggers:
124
+ if kw in trigger.lower():
125
+ score += 20
126
+ break
127
+ for phase in skill.phases:
128
+ if kw in phase.lower():
129
+ score += 10
130
+ break
131
+
132
+ if score > 0:
133
+ results.append((skill, score))
134
+
135
+ results.sort(key=lambda x: x[1], reverse=True)
136
+ return [s for s, _ in results]
137
+
156
138
  def invalidate_cache(self) -> None:
157
139
  self._cache = None
@@ -5,12 +5,12 @@ from typing import Optional
5
5
 
6
6
  import yaml
7
7
 
8
- from onecode.config import CLOUD_DEV_HARNESS_DIR
8
+ from onecode.config import ONECODE_DIR
9
9
 
10
10
 
11
11
  class SkillManager:
12
12
  def __init__(self):
13
- self.skills_dir = CLOUD_DEV_HARNESS_DIR / "skills"
13
+ self.skills_dir = ONECODE_DIR / "skills"
14
14
  self.skills_dir.mkdir(parents=True, exist_ok=True)
15
15
  self._skills: dict[str, dict] = {}
16
16