@scemoon/cdh 1.0.4 → 1.0.5

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 (268) 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 +14 -3
  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/tui/__pycache__/about.cpython-314.pyc +0 -0
  182. package/tui/__pycache__/app.cpython-313.pyc +0 -0
  183. package/tui/__pycache__/app.cpython-314.pyc +0 -0
  184. package/tui/__pycache__/danger.cpython-314.pyc +0 -0
  185. package/tui/__pycache__/directory_suggester.cpython-314.pyc +0 -0
  186. package/tui/__pycache__/messages.cpython-314.pyc +0 -0
  187. package/tui/__pycache__/path_complete.cpython-314.pyc +0 -0
  188. package/tui/__pycache__/paths.cpython-314.pyc +0 -0
  189. package/tui/__pycache__/session_tracker.cpython-314.pyc +0 -0
  190. package/tui/__pycache__/settings_schema.cpython-314.pyc +0 -0
  191. package/tui/acp/__pycache__/agent.cpython-314.pyc +0 -0
  192. package/tui/acp/__pycache__/api.cpython-314.pyc +0 -0
  193. package/tui/acp/__pycache__/messages.cpython-314.pyc +0 -0
  194. package/tui/acp/__pycache__/prompt.cpython-314.pyc +0 -0
  195. package/tui/acp/__pycache__/protocol.cpython-314.pyc +0 -0
  196. package/tui/acp/agent.py +125 -58
  197. package/tui/acp/api.py +12 -0
  198. package/tui/acp/messages.py +3 -1
  199. package/tui/acp/prompt.py +0 -57
  200. package/tui/acp/protocol.py +6 -0
  201. package/tui/app.py +11 -33
  202. package/tui/messages.py +2 -0
  203. package/tui/path_complete.py +1 -1
  204. package/tui/paths.py +16 -0
  205. package/tui/screens/__pycache__/agent_modal.cpython-314.pyc +0 -0
  206. package/tui/screens/__pycache__/component_picker.cpython-314.pyc +0 -0
  207. package/tui/screens/__pycache__/diff_screen.cpython-314.pyc +0 -0
  208. package/tui/screens/__pycache__/log.cpython-314.pyc +0 -0
  209. package/tui/screens/__pycache__/main.cpython-313.pyc +0 -0
  210. package/tui/screens/__pycache__/main.cpython-314.pyc +0 -0
  211. package/tui/screens/__pycache__/permissions.cpython-314.pyc +0 -0
  212. package/tui/screens/__pycache__/projects_app.cpython-314.pyc +0 -0
  213. package/tui/screens/__pycache__/projects_screen.cpython-314.pyc +0 -0
  214. package/tui/screens/__pycache__/sessions.cpython-314.pyc +0 -0
  215. package/tui/screens/__pycache__/store.cpython-314.pyc +0 -0
  216. package/tui/screens/__pycache__/tool_content_screen.cpython-314.pyc +0 -0
  217. package/tui/screens/component_picker.py +35 -15
  218. package/tui/screens/diff_screen.py +84 -0
  219. package/tui/screens/log.py +25 -25
  220. package/tui/screens/main.py +48 -26
  221. package/tui/screens/projects.tcss +5 -1
  222. package/tui/screens/projects_app.py +6 -6
  223. package/tui/screens/projects_screen.py +19 -39
  224. package/tui/screens/sessions.py +3 -5
  225. package/tui/session_tracker.py +3 -0
  226. package/tui/settings_schema.py +1 -1
  227. package/tui/visuals/__pycache__/columns.cpython-314.pyc +0 -0
  228. package/tui/visuals/columns.py +1 -2
  229. package/tui/widgets/__pycache__/acp_content.cpython-314.pyc +0 -0
  230. package/tui/widgets/__pycache__/agent_response.cpython-314.pyc +0 -0
  231. package/tui/widgets/__pycache__/agent_thought.cpython-314.pyc +0 -0
  232. package/tui/widgets/__pycache__/conversation.cpython-314.pyc +0 -0
  233. package/tui/widgets/__pycache__/diff_view.cpython-314.pyc +0 -0
  234. package/tui/widgets/__pycache__/directory_input.cpython-314.pyc +0 -0
  235. package/tui/widgets/__pycache__/future_text.cpython-314.pyc +0 -0
  236. package/tui/widgets/__pycache__/grid_select.cpython-314.pyc +0 -0
  237. package/tui/widgets/__pycache__/load_more.cpython-314.pyc +0 -0
  238. package/tui/widgets/__pycache__/mandelbrot.cpython-314.pyc +0 -0
  239. package/tui/widgets/__pycache__/markdown_note.cpython-314.pyc +0 -0
  240. package/tui/widgets/__pycache__/modified_files.cpython-314.pyc +0 -0
  241. package/tui/widgets/__pycache__/plan.cpython-314.pyc +0 -0
  242. package/tui/widgets/__pycache__/project_grid_select.cpython-314.pyc +0 -0
  243. package/tui/widgets/__pycache__/project_summary.cpython-314.pyc +0 -0
  244. package/tui/widgets/__pycache__/session_grid_select.cpython-314.pyc +0 -0
  245. package/tui/widgets/__pycache__/session_summary.cpython-314.pyc +0 -0
  246. package/tui/widgets/__pycache__/shell_result.cpython-314.pyc +0 -0
  247. package/tui/widgets/__pycache__/side_bar.cpython-314.pyc +0 -0
  248. package/tui/widgets/__pycache__/subagent.cpython-314.pyc +0 -0
  249. package/tui/widgets/__pycache__/subagent_screen.cpython-314.pyc +0 -0
  250. package/tui/widgets/__pycache__/terminal.cpython-314.pyc +0 -0
  251. package/tui/widgets/__pycache__/tool_call.cpython-314.pyc +0 -0
  252. package/tui/widgets/agent_response.py +0 -1
  253. package/tui/widgets/agent_thought.py +0 -1
  254. package/tui/widgets/conversation.py +157 -14
  255. package/tui/widgets/diff_view.py +10 -0
  256. package/tui/widgets/load_more.py +24 -0
  257. package/tui/widgets/modified_files.py +86 -12
  258. package/tui/widgets/plan.py +1 -1
  259. package/tui/widgets/project_grid_select.py +9 -2
  260. package/tui/widgets/session_grid_select.py +6 -0
  261. package/tui/widgets/side_bar.py +10 -0
  262. package/tui/widgets/subagent.py +92 -58
  263. package/tui/widgets/subagent_screen.py +638 -115
  264. package/tui/widgets/terminal.py +1 -2
  265. package/tui/widgets/terminal_tool.py +11 -2
  266. package/tui/widgets/tool_call.py +35 -2
  267. package/onecode/memory/offload.py +0 -114
  268. package/onecode/memory/symbolic.py +0 -164
@@ -1,7 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import fnmatch
4
- import re
5
4
  from dataclasses import dataclass, field
6
5
  from enum import Enum
7
6
  from pathlib import Path
@@ -0,0 +1,19 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+
5
+
6
+ def load_project_doc(workspace_root: Path) -> str:
7
+ """Read ``AGENTS.md`` at the project root if present.
8
+
9
+ Convention mirrors ``AGENTS.md`` / ``CLAUDE.md``: exactly one file at
10
+ the workspace root that all AI agents must read. Unlike skills,
11
+ this is not registered — it's injected directly as project context.
12
+ """
13
+ path = workspace_root.resolve() / "AGENTS.md"
14
+ if not path.exists():
15
+ return ""
16
+ try:
17
+ return path.read_text(encoding="utf-8")
18
+ except Exception:
19
+ return ""
@@ -72,8 +72,23 @@ class AgentSession:
72
72
  self._data.id = session_id
73
73
 
74
74
  def _default_storage_path(self) -> Path:
75
- from onecode.config import CLOUD_DEV_HARNESS_DIR
76
- return CLOUD_DEV_HARNESS_DIR / "sessions"
75
+ cdh_sessions = Path.home() / ".cdh" / "sessions"
76
+ # One-time self-heal: move sessions incorrectly saved to ~/.onecode/sessions/
77
+ # back to the cdh platform-level directory (B mapping mode).
78
+ onecode_sessions = Path.home() / ".onecode" / "sessions"
79
+ marker = cdh_sessions / ".migrated_from_onecode"
80
+ if not marker.exists() and onecode_sessions.exists():
81
+ try:
82
+ cdh_sessions.mkdir(parents=True, exist_ok=True)
83
+ for f in list(onecode_sessions.glob("*.json"))[:250]:
84
+ dst = cdh_sessions / f.name
85
+ if not dst.exists():
86
+ import shutil
87
+ shutil.move(str(f), str(dst))
88
+ marker.write_text("migrated", encoding="utf-8")
89
+ except Exception as e:
90
+ logger.warning("Failed to migrate onecode sessions to cdh: %s", e)
91
+ return cdh_sessions
77
92
 
78
93
  @property
79
94
  def id(self) -> str:
@@ -115,7 +130,6 @@ class AgentSession:
115
130
  if len(self._data.messages) <= 2:
116
131
  return
117
132
  system_msgs = [m for m in self._data.messages if m.get("role") == "system"]
118
- other_msgs = [m for m in self._data.messages if m.get("role") != "system"]
119
133
  self._data.messages = system_msgs + [
120
134
  {"role": "system", "content": f"[Previous context summarized]\n{summary}"}
121
135
  ]
@@ -5,9 +5,8 @@ import json
5
5
  import os
6
6
  import shutil
7
7
  import tarfile
8
- import tempfile
9
8
  import uuid
10
- from dataclasses import dataclass, field
9
+ from dataclasses import dataclass
11
10
  from datetime import datetime, timezone
12
11
  from pathlib import Path
13
12
  from typing import Optional
@@ -25,8 +24,8 @@ class SnapshotInfo:
25
24
 
26
25
  class SnapshotManager:
27
26
  def __init__(self, base_path: Optional[Path] = None):
28
- from onecode.config import CLOUD_DEV_HARNESS_DIR
29
- self.base_path = base_path or (CLOUD_DEV_HARNESS_DIR / "snapshots")
27
+ from onecode.config import ONECODE_DIR
28
+ self.base_path = base_path or (ONECODE_DIR / "snapshots")
30
29
  self.base_path.mkdir(parents=True, exist_ok=True)
31
30
 
32
31
  def create(
@@ -1,13 +1,12 @@
1
1
  from __future__ import annotations
2
2
 
3
- import json
4
3
  from typing import Any
5
4
 
6
5
  from onecode.agent.tools.protocol import ToolResult
7
- from onecode.agent.tools.registry import ToolRegistry, ToolSpec
6
+ from onecode.agent.tools.registry import Tool, ToolRegistry, ToolSpec
8
7
 
9
8
 
10
- class AgentTool:
9
+ class AgentTool(Tool):
11
10
  def __init__(self, registry: ToolRegistry, permission_checker=None):
12
11
  self._registry = registry
13
12
  self._permission_checker = permission_checker
@@ -76,7 +75,7 @@ class AgentTool:
76
75
 
77
76
 
78
77
 
79
- class TaskTool:
78
+ class TaskTool(Tool):
80
79
  VALID_AGENT_TYPES: tuple[str, ...] = (
81
80
  "general", "explore", "scout",
82
81
  "build", "plan", "solo",
@@ -88,7 +87,7 @@ class TaskTool:
88
87
  def spec(self) -> ToolSpec:
89
88
  return ToolSpec(
90
89
  name="Spawn",
91
- description="Delegate a subtask to a specialized subagent. Use for large, independent, or specialized work that benefits from a focused sub-agent.",
90
+ description="Delegate a subtask to a specialized subagent. Use for complex multi-step work or work that benefits from an isolated context.",
92
91
  input_schema={
93
92
  "type": "object",
94
93
  "properties": {
@@ -118,7 +117,6 @@ class TaskTool:
118
117
  is_error=True,
119
118
  )
120
119
  import asyncio
121
- import concurrent.futures
122
120
  try:
123
121
  loop = asyncio.get_event_loop()
124
122
  if loop.is_running():
@@ -1,15 +1,14 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import re
4
- from pathlib import Path
5
4
  from typing import Any
6
5
 
7
6
  from onecode.agent.tools.file_ops import FileOps
8
7
  from onecode.agent.tools.protocol import ToolResult
9
- from onecode.agent.tools.registry import ToolSpec
8
+ from onecode.agent.tools.registry import Tool, ToolSpec
10
9
 
11
10
 
12
- class ApplyPatchTool:
11
+ class ApplyPatchTool(Tool):
13
12
  def __init__(self, file_ops: FileOps):
14
13
  self._file_ops = file_ops
15
14
 
@@ -43,7 +42,6 @@ class ApplyPatchTool:
43
42
  results = []
44
43
  current_file: str | None = None
45
44
  current_content: list[str] = []
46
- patch_type = "update"
47
45
 
48
46
  lines = patch.split("\n")
49
47
  i = 0
@@ -61,7 +59,6 @@ class ApplyPatchTool:
61
59
  results.append(result)
62
60
  current_file = add_match.group(1).strip()
63
61
  current_content = []
64
- patch_type = "add"
65
62
 
66
63
  elif update_match:
67
64
  if current_file and current_content:
@@ -69,7 +66,6 @@ class ApplyPatchTool:
69
66
  results.append(result)
70
67
  current_file = update_match.group(1).strip()
71
68
  current_content = []
72
- patch_type = "update"
73
69
 
74
70
  elif move_match:
75
71
  target = move_match.group(1).strip()
@@ -77,7 +73,6 @@ class ApplyPatchTool:
77
73
  result = self._move_file(current_file, target)
78
74
  results.append(result)
79
75
  current_file = target
80
- patch_type = "move"
81
76
 
82
77
  elif delete_match:
83
78
  target = delete_match.group(1).strip()
@@ -85,7 +80,6 @@ class ApplyPatchTool:
85
80
  results.append(result)
86
81
  current_file = None
87
82
  current_content = []
88
- patch_type = "delete"
89
83
 
90
84
  else:
91
85
  if current_file is not None:
@@ -1,12 +1,11 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import re
4
- from pathlib import Path
5
- from typing import Any, Optional
4
+ from typing import Any, Callable
6
5
 
7
6
  from onecode.agent.tools.file_ops import ShellTool
8
7
  from onecode.agent.tools.protocol import ToolResult
9
- from onecode.agent.tools.registry import ToolSpec
8
+ from onecode.agent.tools.registry import Tool, ToolSpec
10
9
 
11
10
 
12
11
  _DANGEROUS_PATTERNS = [
@@ -21,7 +20,7 @@ _DANGEROUS_PATTERNS = [
21
20
  ]
22
21
 
23
22
 
24
- class BashTool:
23
+ class BashTool(Tool):
25
24
  def __init__(self, shell: ShellTool):
26
25
  self._shell = shell
27
26
 
@@ -45,21 +44,42 @@ class BashTool:
45
44
  command = tool_input.get("command", "")
46
45
  timeout = tool_input.get("timeout", 60)
47
46
 
47
+ error = self._validate(command)
48
+ if error:
49
+ return error
50
+
51
+ result = self._shell.exec(command, timeout=timeout)
52
+ return self._to_tool_result(result, tool_input)
53
+
54
+ async def run_async(
55
+ self, tool_input: dict[str, Any],
56
+ cancel_check: Callable[[], bool] | None = None,
57
+ ) -> ToolResult:
58
+ command = tool_input.get("command", "")
59
+ timeout = tool_input.get("timeout", 60)
60
+
61
+ error = self._validate(command)
62
+ if error:
63
+ return error
64
+
65
+ result = await self._shell.exec_async(command, timeout=timeout, cancel_check=cancel_check)
66
+ return self._to_tool_result(result, tool_input)
67
+
68
+ def _validate(self, command: str) -> ToolResult | None:
48
69
  if not isinstance(command, str) or not command.strip():
49
70
  return ToolResult(name="Bash", output="command must be a non-empty string", is_error=True, content_type="text")
50
71
  if "\x00" in command:
51
72
  return ToolResult(name="Bash", output="command contains NUL byte", is_error=True, content_type="text")
52
-
53
73
  for pat in _DANGEROUS_PATTERNS:
54
74
  if pat.search(command):
55
75
  return ToolResult(name="Bash", output="refusing to run potentially dangerous command", is_error=True, content_type="text")
76
+ return None
56
77
 
57
- result = self._shell.exec(command, timeout=timeout)
78
+ def _to_tool_result(self, result: dict, tool_input: dict) -> ToolResult:
58
79
  is_error = not result.get("success", True)
59
80
  stdout = result.get("stdout", "") or ""
60
81
  stderr = result.get("stderr", "") or ""
61
82
  error_msg = result.get("error", "") or ""
62
-
63
83
  formatted = self._format_bash_output(stdout, stderr, error_msg, is_error)
64
84
  return ToolResult(
65
85
  name="Bash",
@@ -75,17 +95,17 @@ class BashTool:
75
95
 
76
96
  Stderr is rendered first when present so failures are visible,
77
97
  the underlying ``error`` string is included for non-zero exits /
78
- transport failures, and both streams are fenced so the TUI
79
- renders them as code blocks.
98
+ transport failures, and the result is fenced so the TUI renders
99
+ it as a code block with bash syntax highlighting.
80
100
  """
81
101
  parts: list[str] = []
82
102
  if error.strip():
83
103
  parts.append(f"[error] {error.strip()}")
84
104
  if stderr.strip():
85
- parts.append(f"```\n[stderr]\n{stderr.rstrip()}\n```")
105
+ parts.append(f"[stderr]\n{stderr.rstrip()}")
86
106
  if stdout.strip():
87
- parts.append(f"```\n{stdout.rstrip()}\n```")
107
+ parts.append(stdout.rstrip())
88
108
  if not parts:
89
109
  return "(no output)" if not is_error else "(failed with no output)"
90
- return "\n\n".join(parts)
110
+ return "```bash\n" + "\n\n".join(parts) + "\n```"
91
111
 
@@ -1,13 +1,12 @@
1
1
  from __future__ import annotations
2
2
 
3
- import json
4
3
  from typing import Any
5
4
 
6
5
  from onecode.agent.tools.protocol import ToolResult
7
- from onecode.agent.tools.registry import ToolRegistry, ToolSpec
6
+ from onecode.agent.tools.registry import Tool, ToolRegistry, ToolSpec
8
7
 
9
8
 
10
- class SendMessageTool:
9
+ class SendMessageTool(Tool):
11
10
  def __init__(self):
12
11
  self.last_message: str = ""
13
12
 
@@ -70,7 +69,7 @@ _QUESTION_SCHEMA = {
70
69
  }
71
70
 
72
71
 
73
- class AskUserTool:
72
+ class AskUserTool(Tool):
74
73
  def spec(self) -> ToolSpec:
75
74
  return ToolSpec(
76
75
  name="AskUser",
@@ -118,7 +117,7 @@ class AskUserTool:
118
117
 
119
118
 
120
119
 
121
- class ToolSearchTool:
120
+ class ToolSearchTool(Tool):
122
121
  def __init__(self, registry: ToolRegistry):
123
122
  self._registry = registry
124
123
 
@@ -4,13 +4,13 @@ import logging
4
4
  from typing import Any
5
5
 
6
6
  from onecode.agent.tools.protocol import ToolResult
7
- from onecode.agent.tools.registry import ToolSpec
7
+ from onecode.agent.tools.registry import Tool, ToolSpec
8
8
  from onecode.config import GlobalConfig
9
9
 
10
10
  logger = logging.getLogger("onecode.tools.config")
11
11
 
12
12
 
13
- class ConfigReadTool:
13
+ class ConfigReadTool(Tool):
14
14
  """Read configuration values accessible to the LLM (Clawd-Code pattern)."""
15
15
 
16
16
  def __init__(self, config: GlobalConfig):
@@ -64,7 +64,7 @@ class ConfigReadTool:
64
64
 
65
65
 
66
66
 
67
- class ConfigWriteTool:
67
+ class ConfigWriteTool(Tool):
68
68
  """Write configuration values accessible to the LLM (Clawd-Code pattern)."""
69
69
 
70
70
  def __init__(self, config: GlobalConfig, save_fn=None):
@@ -1,13 +1,12 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import asyncio
4
- import json
5
4
  from dataclasses import dataclass, field
6
5
  from datetime import datetime, timedelta
7
6
  from typing import Any, Callable
8
7
 
9
8
  from onecode.agent.tools.protocol import ToolResult
10
- from onecode.agent.tools.registry import ToolSpec
9
+ from onecode.agent.tools.registry import Tool, ToolSpec
11
10
 
12
11
 
13
12
  @dataclass
@@ -95,7 +94,7 @@ class CronScheduler:
95
94
  self._task = None
96
95
 
97
96
 
98
- class CronCreateTool:
97
+ class CronCreateTool(Tool):
99
98
  """Create a scheduled cron job (Clawd-Code pattern)."""
100
99
 
101
100
  def __init__(self, scheduler: CronScheduler):
@@ -131,7 +130,7 @@ class CronCreateTool:
131
130
 
132
131
 
133
132
 
134
- class CronListTool:
133
+ class CronListTool(Tool):
135
134
  """List all cron jobs."""
136
135
 
137
136
  def __init__(self, scheduler: CronScheduler):
@@ -152,7 +151,7 @@ class CronListTool:
152
151
 
153
152
 
154
153
 
155
- class CronRemoveTool:
154
+ class CronRemoveTool(Tool):
156
155
  """Remove a cron job."""
157
156
 
158
157
  def __init__(self, scheduler: CronScheduler):
@@ -1,12 +1,12 @@
1
1
  from __future__ import annotations
2
2
 
3
- import os
3
+ import asyncio
4
4
  import subprocess
5
5
  from pathlib import Path
6
- from typing import Optional
6
+ from typing import Callable, Optional
7
7
 
8
8
  from onecode.agent.permissions import PermissionChecker, PermissionSet, create_safe_permission_set
9
- from onecode.agent.tools.sandbox import Sandbox, SandboxConfig, SandboxMode, ResourceLimits, create_sandbox
9
+ from onecode.agent.tools.sandbox import create_sandbox
10
10
 
11
11
 
12
12
  class FileOps:
@@ -116,6 +116,22 @@ class FileOps:
116
116
  except Exception:
117
117
  return []
118
118
 
119
+ async def grep_async(self, pattern: str, include: Optional[str] = None, path: Optional[str] = None) -> list[str]:
120
+ base = self._resolve(path) if path else self.workspace
121
+ cmd = ["grep", "-rn", pattern, str(base)]
122
+ if include:
123
+ cmd.extend(["--include", include])
124
+ try:
125
+ process = await asyncio.create_subprocess_exec(
126
+ *cmd,
127
+ stdout=asyncio.subprocess.PIPE,
128
+ stderr=asyncio.subprocess.PIPE,
129
+ )
130
+ stdout, _ = await asyncio.wait_for(process.communicate(), timeout=30)
131
+ return stdout.decode("utf-8", errors="replace").split("\n")
132
+ except Exception:
133
+ return []
134
+
119
135
  def list(self, path: str = ".") -> list[dict]:
120
136
  p = self._resolve(path)
121
137
  if not p.exists():
@@ -176,6 +192,24 @@ class ShellTool:
176
192
 
177
193
  return self._sandbox.exec(cmd, timeout=timeout)
178
194
 
195
+ async def exec_async(
196
+ self, cmd: str,
197
+ cwd: Optional[str] = None,
198
+ timeout: int = 60,
199
+ cancel_check: Callable[[], bool] | None = None,
200
+ ) -> dict:
201
+ result = self._checker.check_command(cmd)
202
+ if result.value == "deny":
203
+ return {"success": False, "error": "Command not allowed", "requires_approval": False}
204
+ if result.value == "ask":
205
+ return {"success": False, "error": "Command requires approval", "requires_approval": True}
206
+
207
+ work_dir = Path(cwd) if cwd else self.workspace
208
+ if not work_dir.is_relative_to(self.workspace.resolve()):
209
+ return {"success": False, "error": "CWD outside workspace"}
210
+
211
+ return await self._sandbox.exec_async(cmd, timeout=timeout, cancel_check=cancel_check)
212
+
179
213
 
180
214
  class ToolFactory:
181
215
  @staticmethod
@@ -1,14 +1,13 @@
1
1
  from __future__ import annotations
2
2
 
3
- from pathlib import Path
4
- from typing import Any, Optional
3
+ from typing import Any
5
4
 
6
5
  from onecode.agent.tools.file_ops import FileOps
7
6
  from onecode.agent.tools.protocol import ToolResult
8
- from onecode.agent.tools.registry import ToolSpec
7
+ from onecode.agent.tools.registry import Tool, ToolSpec
9
8
 
10
9
 
11
- class ReadTool:
10
+ class ReadTool(Tool):
12
11
  def __init__(self, file_ops: FileOps):
13
12
  self._file_ops = file_ops
14
13
 
@@ -39,7 +38,7 @@ class ReadTool:
39
38
 
40
39
 
41
40
 
42
- class WriteTool:
41
+ class WriteTool(Tool):
43
42
  def __init__(self, file_ops: FileOps):
44
43
  self._file_ops = file_ops
45
44
 
@@ -66,7 +65,7 @@ class WriteTool:
66
65
 
67
66
 
68
67
 
69
- class EditTool:
68
+ class EditTool(Tool):
70
69
  def __init__(self, file_ops: FileOps):
71
70
  self._file_ops = file_ops
72
71
 
@@ -95,7 +94,7 @@ class EditTool:
95
94
 
96
95
 
97
96
 
98
- class InsertTool:
97
+ class InsertTool(Tool):
99
98
  def __init__(self, file_ops: FileOps):
100
99
  self._file_ops = file_ops
101
100
 
@@ -124,7 +123,7 @@ class InsertTool:
124
123
 
125
124
 
126
125
 
127
- class UndoEditTool:
126
+ class UndoEditTool(Tool):
128
127
  def __init__(self, file_ops: FileOps):
129
128
  self._file_ops = file_ops
130
129
 
@@ -149,7 +148,7 @@ class UndoEditTool:
149
148
 
150
149
 
151
150
 
152
- class GlobTool:
151
+ class GlobTool(Tool):
153
152
  def __init__(self, file_ops: FileOps):
154
153
  self._file_ops = file_ops
155
154
 
@@ -174,7 +173,7 @@ class GlobTool:
174
173
 
175
174
 
176
175
 
177
- class GrepTool:
176
+ class GrepTool(Tool):
178
177
  def __init__(self, file_ops: FileOps):
179
178
  self._file_ops = file_ops
180
179
 
@@ -201,7 +200,7 @@ class GrepTool:
201
200
 
202
201
 
203
202
 
204
- class ListTool:
203
+ class ListTool(Tool):
205
204
  def __init__(self, file_ops: FileOps):
206
205
  self._file_ops = file_ops
207
206
 
@@ -5,10 +5,10 @@ from pathlib import Path
5
5
  from typing import Any
6
6
 
7
7
  from onecode.agent.tools.protocol import ToolResult
8
- from onecode.agent.tools.registry import ToolSpec
8
+ from onecode.agent.tools.registry import Tool, ToolSpec
9
9
 
10
10
 
11
- class WorktreeTool:
11
+ class WorktreeTool(Tool):
12
12
  """Manage git worktrees (Clawd-Code pattern)."""
13
13
 
14
14
  def __init__(self, workspace_root: Path | None = None):
@@ -6,7 +6,7 @@ from pathlib import Path
6
6
  from typing import Any
7
7
 
8
8
  from onecode.agent.tools.protocol import ToolResult
9
- from onecode.agent.tools.registry import ToolSpec
9
+ from onecode.agent.tools.registry import Tool, ToolSpec
10
10
 
11
11
 
12
12
  class LSPClient:
@@ -263,7 +263,7 @@ class LSPClient:
263
263
  self._process = None
264
264
 
265
265
 
266
- class LSPTool:
266
+ class LSPTool(Tool):
267
267
  def __init__(self):
268
268
  self._servers: dict[str, LSPClient] = {}
269
269
 
@@ -3,11 +3,11 @@ from __future__ import annotations
3
3
  from typing import Any
4
4
 
5
5
  from onecode.agent.tools.protocol import ToolResult
6
- from onecode.agent.tools.registry import ToolSpec
6
+ from onecode.agent.tools.registry import Tool, ToolSpec
7
7
  from onecode.mcp.manager import MCPManager
8
8
 
9
9
 
10
- class MCPTool:
10
+ class MCPTool(Tool):
11
11
  """Call a tool from a connected MCP server (Clawd-Code pattern)."""
12
12
 
13
13
  def __init__(self, mcp_manager: MCPManager):
@@ -42,7 +42,6 @@ class MCPTool:
42
42
  try:
43
43
  loop = asyncio.get_event_loop()
44
44
  if loop.is_running():
45
- import threading
46
45
  future = asyncio.run_coroutine_threadsafe(
47
46
  self._mcp.call_tool(server, tool_name, args), loop
48
47
  )
@@ -59,7 +58,7 @@ class MCPTool:
59
58
 
60
59
 
61
60
 
62
- class MCPResourcesTool:
61
+ class MCPResourcesTool(Tool):
63
62
  """Access resources from a connected MCP server."""
64
63
 
65
64
  def __init__(self, mcp_manager: MCPManager):
@@ -104,7 +103,6 @@ class MCPResourcesTool:
104
103
  try:
105
104
  loop = asyncio.get_event_loop()
106
105
  if loop.is_running():
107
- import threading
108
106
  if action == "list":
109
107
  future = asyncio.run_coroutine_threadsafe(
110
108
  client._send_request("resources/list", {}), loop
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- from dataclasses import dataclass, field
3
+ from dataclasses import dataclass
4
4
  from typing import Callable, Optional
5
5
 
6
6
  from onecode.agent.tools.permissions import PermissionResult
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- from dataclasses import dataclass, field
3
+ from dataclasses import dataclass
4
4
  from typing import Any, Literal, Optional
5
5
 
6
6