@scemoon/cdh 1.0.0

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 (528) hide show
  1. package/ai-dlc-skill/SKILL.md +75 -0
  2. package/ai-dlc-skill/agents/deliver-agent.md +24 -0
  3. package/ai-dlc-skill/agents/master.md +44 -0
  4. package/ai-dlc-skill/agents/plan-agent.md +23 -0
  5. package/ai-dlc-skill/agents/understand-agent.md +23 -0
  6. package/ai-dlc-skill/agents/verify-agent.md +27 -0
  7. package/ai-dlc-skill/architecture/project-structure.md +78 -0
  8. package/ai-dlc-skill/brownfield/README.md +30 -0
  9. package/ai-dlc-skill/brownfield/generate-context.sh +64 -0
  10. package/ai-dlc-skill/brownfield/scripts/deps.sh +27 -0
  11. package/ai-dlc-skill/brownfield/scripts/discover.sh +34 -0
  12. package/ai-dlc-skill/brownfield/scripts/extract-api.sh +45 -0
  13. package/ai-dlc-skill/components/backend.md +34 -0
  14. package/ai-dlc-skill/components/desktop.md +30 -0
  15. package/ai-dlc-skill/components/mya.md +29 -0
  16. package/ai-dlc-skill/components/native.md +30 -0
  17. package/ai-dlc-skill/components/tta.md +29 -0
  18. package/ai-dlc-skill/components/web.md +31 -0
  19. package/ai-dlc-skill/components/wxa.md +29 -0
  20. package/ai-dlc-skill/contracts/README.md +25 -0
  21. package/ai-dlc-skill/core/adaptive-flow.md +36 -0
  22. package/ai-dlc-skill/core/security.md +29 -0
  23. package/ai-dlc-skill/cross-tool/cline/export.sh +31 -0
  24. package/ai-dlc-skill/cross-tool/copilot/export.sh +47 -0
  25. package/ai-dlc-skill/cross-tool/cursor/export.sh +32 -0
  26. package/ai-dlc-skill/cross-tool/export.sh +22 -0
  27. package/ai-dlc-skill/cross-tool/onecode/config.yaml.tpl +17 -0
  28. package/ai-dlc-skill/cross-tool/onecode/export-skills.sh +31 -0
  29. package/ai-dlc-skill/cross-tool/onecode/export.sh +48 -0
  30. package/ai-dlc-skill/phases/deliver/entry.md +26 -0
  31. package/ai-dlc-skill/phases/deliver/lifecycle.md +185 -0
  32. package/ai-dlc-skill/phases/deliver/prompt.md +40 -0
  33. package/ai-dlc-skill/phases/deliver/rules.md +61 -0
  34. package/ai-dlc-skill/phases/plan/entry.md +25 -0
  35. package/ai-dlc-skill/phases/plan/lifecycle.md +204 -0
  36. package/ai-dlc-skill/phases/plan/prompt.md +38 -0
  37. package/ai-dlc-skill/phases/plan/rules.md +25 -0
  38. package/ai-dlc-skill/phases/understand/entry.md +26 -0
  39. package/ai-dlc-skill/phases/understand/lifecycle.md +184 -0
  40. package/ai-dlc-skill/phases/understand/prompt.md +41 -0
  41. package/ai-dlc-skill/phases/understand/rules.md +37 -0
  42. package/ai-dlc-skill/phases/verify/entry.md +26 -0
  43. package/ai-dlc-skill/phases/verify/lifecycle.md +192 -0
  44. package/ai-dlc-skill/phases/verify/prompt.md +43 -0
  45. package/ai-dlc-skill/phases/verify/rules.md +73 -0
  46. package/ai-dlc-skill/practices/bdd.md +91 -0
  47. package/ai-dlc-skill/practices/sdd.md +84 -0
  48. package/ai-dlc-skill/practices/tdd.md +95 -0
  49. package/ai-dlc-skill/providers/README.md +114 -0
  50. package/ai-dlc-skill/providers/aliyun/deployment.yaml +66 -0
  51. package/ai-dlc-skill/providers/aliyun/preview.yaml +84 -0
  52. package/ai-dlc-skill/providers/aliyun/provider.yaml +160 -0
  53. package/ai-dlc-skill/providers/tcb/deployment.yaml +69 -0
  54. package/ai-dlc-skill/providers/tcb/preview.yaml +83 -0
  55. package/ai-dlc-skill/providers/tcb/provider.yaml +172 -0
  56. package/ai-dlc-skill/requirements.md +140 -0
  57. package/ai-dlc-skill/skill.yaml +147 -0
  58. package/ai-dlc-skill/templates/integration/CHANGELOG.md +41 -0
  59. package/ai-dlc-skill/templates/integration/asyncapi.yaml +43 -0
  60. package/ai-dlc-skill/templates/integration/contract-diff.md +63 -0
  61. package/ai-dlc-skill/templates/integration/contract-spec.md +49 -0
  62. package/ai-dlc-skill/templates/integration/openapi.yaml +18 -0
  63. package/ai-dlc-skill/templates/integration/runtime-contract.yaml +36 -0
  64. package/ai-dlc-skill/templates/plan/design.md +49 -0
  65. package/ai-dlc-skill/templates/plan/task-list.md +32 -0
  66. package/ai-dlc-skill/templates/project/README.md +127 -0
  67. package/ai-dlc-skill/templates/project/template.md +119 -0
  68. package/ai-dlc-skill/templates/understand/feature.feature +23 -0
  69. package/ai-dlc-skill/templates/understand/intent.md +29 -0
  70. package/ai-dlc-skill/templates/understand/spec-delta.md +47 -0
  71. package/ai-dlc-skill/templates/verify/test-unit.py +27 -0
  72. package/ai-dlc-skill/walkthrough/collect.sh +50 -0
  73. package/ai-dlc-skill/walkthrough/template.md +54 -0
  74. package/ai-dlc-skill/workflows/ai-dlc.yaml +110 -0
  75. package/ai-dlc-skill/workflows/deployment.yaml +199 -0
  76. package/cdh/__init__.py +0 -0
  77. package/cdh/__main__.py +4 -0
  78. package/cdh/__pycache__/__init__.cpython-313.pyc +0 -0
  79. package/cdh/__pycache__/__init__.cpython-314.pyc +0 -0
  80. package/cdh/__pycache__/cli.cpython-314.pyc +0 -0
  81. package/cdh/__pycache__/scaffold.cpython-313.pyc +0 -0
  82. package/cdh/__pycache__/scaffold.cpython-314.pyc +0 -0
  83. package/cdh/cli.py +361 -0
  84. package/cdh/scaffold.py +312 -0
  85. package/cli.js +2 -0
  86. package/onecode/__init__.py +1 -0
  87. package/onecode/__main__.py +4 -0
  88. package/onecode/__pycache__/__init__.cpython-314.pyc +0 -0
  89. package/onecode/__pycache__/cli.cpython-314.pyc +0 -0
  90. package/onecode/__pycache__/commands.cpython-314.pyc +0 -0
  91. package/onecode/__pycache__/config.cpython-314.pyc +0 -0
  92. package/onecode/__pycache__/config_screen.cpython-314.pyc +0 -0
  93. package/onecode/__pycache__/mock_data.cpython-314.pyc +0 -0
  94. package/onecode/agent/__init__.py +0 -0
  95. package/onecode/agent/__pycache__/__init__.cpython-314.pyc +0 -0
  96. package/onecode/agent/__pycache__/cdh_agent_acp.cpython-313.pyc +0 -0
  97. package/onecode/agent/__pycache__/cdh_agent_acp.cpython-314.pyc +0 -0
  98. package/onecode/agent/__pycache__/cdh_loader.cpython-314.pyc +0 -0
  99. package/onecode/agent/__pycache__/context.cpython-314.pyc +0 -0
  100. package/onecode/agent/__pycache__/engine.cpython-314.pyc +0 -0
  101. package/onecode/agent/__pycache__/harness_skill.cpython-314.pyc +0 -0
  102. package/onecode/agent/__pycache__/hooks.cpython-314.pyc +0 -0
  103. package/onecode/agent/__pycache__/onecode_agent_acp.cpython-314.pyc +0 -0
  104. package/onecode/agent/__pycache__/permissions.cpython-314.pyc +0 -0
  105. package/onecode/agent/__pycache__/permissions_store.cpython-314.pyc +0 -0
  106. package/onecode/agent/__pycache__/session.cpython-314.pyc +0 -0
  107. package/onecode/agent/agents/__init__.py +29 -0
  108. package/onecode/agent/agents/__pycache__/__init__.cpython-314.pyc +0 -0
  109. package/onecode/agent/agents/__pycache__/types.cpython-314.pyc +0 -0
  110. package/onecode/agent/agents/types.py +662 -0
  111. package/onecode/agent/attachments.py +113 -0
  112. package/onecode/agent/cdh_loader.py +262 -0
  113. package/onecode/agent/context.py +338 -0
  114. package/onecode/agent/engine.py +1894 -0
  115. package/onecode/agent/hooks.py +86 -0
  116. package/onecode/agent/onecode_agent_acp.py +2203 -0
  117. package/onecode/agent/permissions.py +151 -0
  118. package/onecode/agent/permissions_store.py +62 -0
  119. package/onecode/agent/session.py +186 -0
  120. package/onecode/agent/snapshot.py +145 -0
  121. package/onecode/agent/tools/__init__.py +17 -0
  122. package/onecode/agent/tools/__pycache__/__init__.cpython-314.pyc +0 -0
  123. package/onecode/agent/tools/__pycache__/agent_tools.cpython-314.pyc +0 -0
  124. package/onecode/agent/tools/__pycache__/apply_patch_tool.cpython-314.pyc +0 -0
  125. package/onecode/agent/tools/__pycache__/bash_tool.cpython-314.pyc +0 -0
  126. package/onecode/agent/tools/__pycache__/communication_tools.cpython-314.pyc +0 -0
  127. package/onecode/agent/tools/__pycache__/config_tool.cpython-314.pyc +0 -0
  128. package/onecode/agent/tools/__pycache__/cron_tools.cpython-314.pyc +0 -0
  129. package/onecode/agent/tools/__pycache__/errors.cpython-314.pyc +0 -0
  130. package/onecode/agent/tools/__pycache__/file_ops.cpython-314.pyc +0 -0
  131. package/onecode/agent/tools/__pycache__/file_tools.cpython-314.pyc +0 -0
  132. package/onecode/agent/tools/__pycache__/git_tools.cpython-314.pyc +0 -0
  133. package/onecode/agent/tools/__pycache__/lsp_tools.cpython-314.pyc +0 -0
  134. package/onecode/agent/tools/__pycache__/mcp_tools.cpython-314.pyc +0 -0
  135. package/onecode/agent/tools/__pycache__/permission_handler.cpython-314.pyc +0 -0
  136. package/onecode/agent/tools/__pycache__/permissions.cpython-314.pyc +0 -0
  137. package/onecode/agent/tools/__pycache__/protocol.cpython-314.pyc +0 -0
  138. package/onecode/agent/tools/__pycache__/registry.cpython-314.pyc +0 -0
  139. package/onecode/agent/tools/__pycache__/sandbox.cpython-314.pyc +0 -0
  140. package/onecode/agent/tools/__pycache__/schema_validation.cpython-314.pyc +0 -0
  141. package/onecode/agent/tools/__pycache__/skill_tools.cpython-314.pyc +0 -0
  142. package/onecode/agent/tools/__pycache__/task_tools.cpython-314.pyc +0 -0
  143. package/onecode/agent/tools/__pycache__/web_tools.cpython-314.pyc +0 -0
  144. package/onecode/agent/tools/agent_tools.py +134 -0
  145. package/onecode/agent/tools/apply_patch_tool.py +144 -0
  146. package/onecode/agent/tools/bash_tool.py +91 -0
  147. package/onecode/agent/tools/communication_tools.py +149 -0
  148. package/onecode/agent/tools/config_tool.py +130 -0
  149. package/onecode/agent/tools/cron_tools.py +179 -0
  150. package/onecode/agent/tools/errors.py +17 -0
  151. package/onecode/agent/tools/file_ops.py +187 -0
  152. package/onecode/agent/tools/file_tools.py +226 -0
  153. package/onecode/agent/tools/git_tools.py +88 -0
  154. package/onecode/agent/tools/lsp_tools.py +404 -0
  155. package/onecode/agent/tools/mcp_tools.py +129 -0
  156. package/onecode/agent/tools/permission_handler.py +46 -0
  157. package/onecode/agent/tools/permissions.py +77 -0
  158. package/onecode/agent/tools/protocol.py +20 -0
  159. package/onecode/agent/tools/registry.py +134 -0
  160. package/onecode/agent/tools/sandbox.py +264 -0
  161. package/onecode/agent/tools/schema_validation.py +100 -0
  162. package/onecode/agent/tools/skill_tools.py +52 -0
  163. package/onecode/agent/tools/task_tools.py +249 -0
  164. package/onecode/agent/tools/web_tools.py +170 -0
  165. package/onecode/builtin_skills/git/SKILL.md +64 -0
  166. package/onecode/builtin_skills/git/skill.yaml +7 -0
  167. package/onecode/builtin_skills/shell/SKILL.md +58 -0
  168. package/onecode/builtin_skills/shell/skill.yaml +8 -0
  169. package/onecode/cli.py +755 -0
  170. package/onecode/cloud/__init__.py +0 -0
  171. package/onecode/cloud/base.py +20 -0
  172. package/onecode/codebase/__init__.py +69 -0
  173. package/onecode/codebase/__pycache__/__init__.cpython-314.pyc +0 -0
  174. package/onecode/codebase/__pycache__/chunker.cpython-314.pyc +0 -0
  175. package/onecode/codebase/__pycache__/config.cpython-314.pyc +0 -0
  176. package/onecode/codebase/__pycache__/indexer.cpython-314.pyc +0 -0
  177. package/onecode/codebase/__pycache__/retriever.cpython-314.pyc +0 -0
  178. package/onecode/codebase/__pycache__/storage.cpython-314.pyc +0 -0
  179. package/onecode/codebase/__pycache__/tools.cpython-314.pyc +0 -0
  180. package/onecode/codebase/chunker.py +130 -0
  181. package/onecode/codebase/indexer.py +136 -0
  182. package/onecode/codebase/retriever.py +192 -0
  183. package/onecode/codebase/storage.py +130 -0
  184. package/onecode/codebase/tools.py +59 -0
  185. package/onecode/commands.py +276 -0
  186. package/onecode/config.py +285 -0
  187. package/onecode/config_screen.py +594 -0
  188. package/onecode/cron/__init__.py +3 -0
  189. package/onecode/lsp/__init__.py +3 -0
  190. package/onecode/mcp/__init__.py +4 -0
  191. package/onecode/mcp/__pycache__/__init__.cpython-314.pyc +0 -0
  192. package/onecode/mcp/__pycache__/client.cpython-314.pyc +0 -0
  193. package/onecode/mcp/__pycache__/manager.cpython-314.pyc +0 -0
  194. package/onecode/mcp/client.py +193 -0
  195. package/onecode/mcp/manager.py +191 -0
  196. package/onecode/memory/__init__.py +95 -0
  197. package/onecode/memory/__pycache__/__init__.cpython-314.pyc +0 -0
  198. package/onecode/memory/__pycache__/backend.cpython-314.pyc +0 -0
  199. package/onecode/memory/__pycache__/offload.cpython-314.pyc +0 -0
  200. package/onecode/memory/__pycache__/pyramid.cpython-314.pyc +0 -0
  201. package/onecode/memory/__pycache__/recall.cpython-314.pyc +0 -0
  202. package/onecode/memory/__pycache__/symbolic.cpython-314.pyc +0 -0
  203. package/onecode/memory/backend.py +185 -0
  204. package/onecode/memory/offload.py +114 -0
  205. package/onecode/memory/pyramid.py +202 -0
  206. package/onecode/memory/recall.py +168 -0
  207. package/onecode/memory/symbolic.py +164 -0
  208. package/onecode/models/__init__.py +4 -0
  209. package/onecode/models/__pycache__/__init__.cpython-314.pyc +0 -0
  210. package/onecode/models/__pycache__/errors.cpython-314.pyc +0 -0
  211. package/onecode/models/__pycache__/messages.cpython-314.pyc +0 -0
  212. package/onecode/models/__pycache__/provider.cpython-314.pyc +0 -0
  213. package/onecode/models/__pycache__/registry.cpython-314.pyc +0 -0
  214. package/onecode/models/errors.py +94 -0
  215. package/onecode/models/messages.py +662 -0
  216. package/onecode/models/provider.py +450 -0
  217. package/onecode/models/providers/__init__.py +17 -0
  218. package/onecode/models/providers/__pycache__/__init__.cpython-314.pyc +0 -0
  219. package/onecode/models/providers/__pycache__/anthropic_provider.cpython-314.pyc +0 -0
  220. package/onecode/models/providers/__pycache__/deepseek_provider.cpython-314.pyc +0 -0
  221. package/onecode/models/providers/__pycache__/glm_provider.cpython-314.pyc +0 -0
  222. package/onecode/models/providers/__pycache__/minimax_provider.cpython-314.pyc +0 -0
  223. package/onecode/models/providers/__pycache__/minimaxi_provider.cpython-314.pyc +0 -0
  224. package/onecode/models/providers/__pycache__/mock_provider.cpython-314.pyc +0 -0
  225. package/onecode/models/providers/__pycache__/ollama_provider.cpython-314.pyc +0 -0
  226. package/onecode/models/providers/__pycache__/openai_provider.cpython-314.pyc +0 -0
  227. package/onecode/models/providers/anthropic_provider.py +224 -0
  228. package/onecode/models/providers/deepseek_provider.py +210 -0
  229. package/onecode/models/providers/glm_provider.py +154 -0
  230. package/onecode/models/providers/minimax_provider.py +154 -0
  231. package/onecode/models/providers/minimaxi_provider.py +363 -0
  232. package/onecode/models/providers/ollama_provider.py +96 -0
  233. package/onecode/models/providers/openai_provider.py +233 -0
  234. package/onecode/models/registry.py +212 -0
  235. package/onecode/server/__init__.py +3 -0
  236. package/onecode/server/app.py +224 -0
  237. package/onecode/skills/__init__.py +11 -0
  238. package/onecode/skills/__pycache__/__init__.cpython-314.pyc +0 -0
  239. package/onecode/skills/__pycache__/argument_substitution.cpython-314.pyc +0 -0
  240. package/onecode/skills/__pycache__/create.cpython-314.pyc +0 -0
  241. package/onecode/skills/__pycache__/frontmatter.cpython-314.pyc +0 -0
  242. package/onecode/skills/__pycache__/loader.cpython-314.pyc +0 -0
  243. package/onecode/skills/__pycache__/manager.cpython-314.pyc +0 -0
  244. package/onecode/skills/__pycache__/model.cpython-314.pyc +0 -0
  245. package/onecode/skills/argument_substitution.py +39 -0
  246. package/onecode/skills/create.py +56 -0
  247. package/onecode/skills/frontmatter.py +33 -0
  248. package/onecode/skills/loader.py +157 -0
  249. package/onecode/skills/manager.py +75 -0
  250. package/onecode/skills/model.py +43 -0
  251. package/onecode/storage/__init__.py +0 -0
  252. package/onecode/storage/activity.py +64 -0
  253. package/onecode/storage/project.py +54 -0
  254. package/onecode/storage/session.py +113 -0
  255. package/onecode/tasks/__init__.py +26 -0
  256. package/onecode/tasks/manager.py +219 -0
  257. package/onecode/tasks/models.py +240 -0
  258. package/onecode/trace/__init__.py +0 -0
  259. package/onecode/trace/tracer.py +174 -0
  260. package/onecode/utils/__init__.py +0 -0
  261. package/onecode/utils/helpers.py +20 -0
  262. package/onecode-cli.js +2 -0
  263. package/package.json +32 -0
  264. package/pyproject.toml +60 -0
  265. package/run.js +86 -0
  266. package/tui/__init__.py +46 -0
  267. package/tui/__main__.py +4 -0
  268. package/tui/__pycache__/__init__.cpython-314.pyc +0 -0
  269. package/tui/__pycache__/_loop.cpython-314.pyc +0 -0
  270. package/tui/__pycache__/_path_fuzzy_search.cpython-314.pyc +0 -0
  271. package/tui/__pycache__/_path_match.cpython-314.pyc +0 -0
  272. package/tui/__pycache__/agent.cpython-314.pyc +0 -0
  273. package/tui/__pycache__/agent_schema.cpython-314.pyc +0 -0
  274. package/tui/__pycache__/agents.cpython-314.pyc +0 -0
  275. package/tui/__pycache__/answer.cpython-314.pyc +0 -0
  276. package/tui/__pycache__/app.cpython-314.pyc +0 -0
  277. package/tui/__pycache__/atomic.cpython-314.pyc +0 -0
  278. package/tui/__pycache__/complete.cpython-314.pyc +0 -0
  279. package/tui/__pycache__/constants.cpython-314.pyc +0 -0
  280. package/tui/__pycache__/conversation_markdown.cpython-314.pyc +0 -0
  281. package/tui/__pycache__/danger.cpython-314.pyc +0 -0
  282. package/tui/__pycache__/db.cpython-314.pyc +0 -0
  283. package/tui/__pycache__/dec.cpython-314.pyc +0 -0
  284. package/tui/__pycache__/directory.cpython-314.pyc +0 -0
  285. package/tui/__pycache__/directory_suggester.cpython-314.pyc +0 -0
  286. package/tui/__pycache__/directory_watcher.cpython-314.pyc +0 -0
  287. package/tui/__pycache__/format_path.cpython-314.pyc +0 -0
  288. package/tui/__pycache__/fuzzy.cpython-314.pyc +0 -0
  289. package/tui/__pycache__/fuzzy_index.cpython-314.pyc +0 -0
  290. package/tui/__pycache__/history.cpython-314.pyc +0 -0
  291. package/tui/__pycache__/jsonrpc.cpython-314.pyc +0 -0
  292. package/tui/__pycache__/menus.cpython-314.pyc +0 -0
  293. package/tui/__pycache__/message_log.cpython-314.pyc +0 -0
  294. package/tui/__pycache__/messages.cpython-314.pyc +0 -0
  295. package/tui/__pycache__/path_complete.cpython-314.pyc +0 -0
  296. package/tui/__pycache__/path_filter.cpython-314.pyc +0 -0
  297. package/tui/__pycache__/paths.cpython-314.pyc +0 -0
  298. package/tui/__pycache__/pill.cpython-314.pyc +0 -0
  299. package/tui/__pycache__/platform_commands.cpython-314.pyc +0 -0
  300. package/tui/__pycache__/protocol.cpython-314.pyc +0 -0
  301. package/tui/__pycache__/session_tracker.cpython-314.pyc +0 -0
  302. package/tui/__pycache__/settings.cpython-314.pyc +0 -0
  303. package/tui/__pycache__/settings_schema.cpython-314.pyc +0 -0
  304. package/tui/__pycache__/shell.cpython-314.pyc +0 -0
  305. package/tui/__pycache__/shell_read.cpython-314.pyc +0 -0
  306. package/tui/__pycache__/slash_command.cpython-314.pyc +0 -0
  307. package/tui/__pycache__/version.cpython-314.pyc +0 -0
  308. package/tui/_loop.py +86 -0
  309. package/tui/_path_fuzzy_search.py +48 -0
  310. package/tui/_path_match.py +141 -0
  311. package/tui/about.py +95 -0
  312. package/tui/acp/__pycache__/agent.cpython-314.pyc +0 -0
  313. package/tui/acp/__pycache__/api.cpython-314.pyc +0 -0
  314. package/tui/acp/__pycache__/encode_tool_call_id.cpython-314.pyc +0 -0
  315. package/tui/acp/__pycache__/messages.cpython-314.pyc +0 -0
  316. package/tui/acp/__pycache__/prompt.cpython-314.pyc +0 -0
  317. package/tui/acp/__pycache__/protocol.cpython-314.pyc +0 -0
  318. package/tui/acp/agent.py +886 -0
  319. package/tui/acp/api.py +55 -0
  320. package/tui/acp/encode_tool_call_id.py +12 -0
  321. package/tui/acp/messages.py +190 -0
  322. package/tui/acp/prompt.py +110 -0
  323. package/tui/acp/protocol.py +462 -0
  324. package/tui/agent.py +70 -0
  325. package/tui/agent_schema.py +76 -0
  326. package/tui/agents.py +45 -0
  327. package/tui/ansi/__init__.py +1 -0
  328. package/tui/ansi/__pycache__/__init__.cpython-314.pyc +0 -0
  329. package/tui/ansi/__pycache__/_ansi.cpython-314.pyc +0 -0
  330. package/tui/ansi/__pycache__/_ansi_colors.cpython-314.pyc +0 -0
  331. package/tui/ansi/__pycache__/_control_codes.cpython-314.pyc +0 -0
  332. package/tui/ansi/__pycache__/_keys.cpython-314.pyc +0 -0
  333. package/tui/ansi/__pycache__/_sgr_styles.cpython-314.pyc +0 -0
  334. package/tui/ansi/__pycache__/_stream_parser.cpython-314.pyc +0 -0
  335. package/tui/ansi/_ansi.py +1689 -0
  336. package/tui/ansi/_ansi_colors.py +264 -0
  337. package/tui/ansi/_control_codes.py +37 -0
  338. package/tui/ansi/_keys.py +251 -0
  339. package/tui/ansi/_sgr_styles.py +64 -0
  340. package/tui/ansi/_stream_parser.py +418 -0
  341. package/tui/answer.py +12 -0
  342. package/tui/app.py +1236 -0
  343. package/tui/atomic.py +37 -0
  344. package/tui/cli.py +313 -0
  345. package/tui/code_analyze.py +27 -0
  346. package/tui/complete.py +38 -0
  347. package/tui/constants.py +61 -0
  348. package/tui/conversation_markdown.py +30 -0
  349. package/tui/danger.py +372 -0
  350. package/tui/data/agents/ampcode.com.toml +51 -0
  351. package/tui/data/agents/augmentcode.com.toml +40 -0
  352. package/tui/data/agents/cdh.cloud-dev-harness.toml +22 -0
  353. package/tui/data/agents/claude.com.toml +41 -0
  354. package/tui/data/agents/copilot.github.com.toml +29 -0
  355. package/tui/data/agents/crowai.dev.toml +35 -0
  356. package/tui/data/agents/cursor.com.toml +38 -0
  357. package/tui/data/agents/deepagents.langchain.com +33 -0
  358. package/tui/data/agents/docker.com.toml +59 -0
  359. package/tui/data/agents/geminicli.com.toml +28 -0
  360. package/tui/data/agents/goose.ai.toml +51 -0
  361. package/tui/data/agents/inference.huggingface.co.toml +41 -0
  362. package/tui/data/agents/kimi.com.toml +35 -0
  363. package/tui/data/agents/kiro.dev.toml +42 -0
  364. package/tui/data/agents/openai.com.toml +53 -0
  365. package/tui/data/agents/openclaw.ai.toml +37 -0
  366. package/tui/data/agents/opencode.ai.toml +39 -0
  367. package/tui/data/agents/openhands.dev.toml +45 -0
  368. package/tui/data/agents/stakpak.dev.toml +61 -0
  369. package/tui/data/agents/vibe.mistral.ai.toml +30 -0
  370. package/tui/data/agents/vtcode.dev.toml +62 -0
  371. package/tui/data/images/frog.png +0 -0
  372. package/tui/data/sounds/question.wav +0 -0
  373. package/tui/data/sounds/turn-over.wav +0 -0
  374. package/tui/db.py +190 -0
  375. package/tui/dec.py +332 -0
  376. package/tui/directory.py +234 -0
  377. package/tui/directory_suggester.py +93 -0
  378. package/tui/directory_watcher.py +197 -0
  379. package/tui/format_path.py +25 -0
  380. package/tui/fuzzy.py +140 -0
  381. package/tui/fuzzy_index.py +275 -0
  382. package/tui/gist.py +33 -0
  383. package/tui/history.py +138 -0
  384. package/tui/jsonrpc.py +578 -0
  385. package/tui/menus.py +14 -0
  386. package/tui/message_log.py +69 -0
  387. package/tui/messages.py +139 -0
  388. package/tui/option_content.py +51 -0
  389. package/tui/os.py +0 -0
  390. package/tui/path_complete.py +217 -0
  391. package/tui/path_filter.py +124 -0
  392. package/tui/paths.py +71 -0
  393. package/tui/pill.py +23 -0
  394. package/tui/platform_commands.py +52 -0
  395. package/tui/prompt/__pycache__/extract.cpython-314.pyc +0 -0
  396. package/tui/prompt/__pycache__/resource.cpython-314.pyc +0 -0
  397. package/tui/prompt/extract.py +19 -0
  398. package/tui/prompt/resource.py +68 -0
  399. package/tui/protocol.py +28 -0
  400. package/tui/screens/__init__.py +0 -0
  401. package/tui/screens/__pycache__/__init__.cpython-314.pyc +0 -0
  402. package/tui/screens/__pycache__/log.cpython-314.pyc +0 -0
  403. package/tui/screens/__pycache__/main.cpython-314.pyc +0 -0
  404. package/tui/screens/__pycache__/projects_screen.cpython-314.pyc +0 -0
  405. package/tui/screens/__pycache__/sessions.cpython-314.pyc +0 -0
  406. package/tui/screens/__pycache__/settings.cpython-314.pyc +0 -0
  407. package/tui/screens/__pycache__/store.cpython-314.pyc +0 -0
  408. package/tui/screens/action_modal.py +94 -0
  409. package/tui/screens/agent_modal.py +175 -0
  410. package/tui/screens/command_edit_modal.py +74 -0
  411. package/tui/screens/log.py +553 -0
  412. package/tui/screens/main.py +411 -0
  413. package/tui/screens/main.tcss +655 -0
  414. package/tui/screens/permissions.py +404 -0
  415. package/tui/screens/permissions.tcss +72 -0
  416. package/tui/screens/projects.tcss +66 -0
  417. package/tui/screens/projects_app.py +319 -0
  418. package/tui/screens/projects_screen.py +142 -0
  419. package/tui/screens/session_resume_modal.py +172 -0
  420. package/tui/screens/session_resume_modal.tcss +35 -0
  421. package/tui/screens/sessions.py +166 -0
  422. package/tui/screens/sessions.tcss +119 -0
  423. package/tui/screens/settings.py +254 -0
  424. package/tui/screens/settings.tcss +101 -0
  425. package/tui/screens/store.py +500 -0
  426. package/tui/screens/store.tcss +274 -0
  427. package/tui/screens/tool_content_screen.py +59 -0
  428. package/tui/session_tracker.py +120 -0
  429. package/tui/settings.py +353 -0
  430. package/tui/settings_schema.py +431 -0
  431. package/tui/shell.py +304 -0
  432. package/tui/shell_read.py +42 -0
  433. package/tui/slash_command.py +34 -0
  434. package/tui/tui.tcss +246 -0
  435. package/tui/twitter.py +38 -0
  436. package/tui/version.py +80 -0
  437. package/tui/visuals/__init__.py +0 -0
  438. package/tui/visuals/__pycache__/__init__.cpython-314.pyc +0 -0
  439. package/tui/visuals/__pycache__/columns.cpython-314.pyc +0 -0
  440. package/tui/visuals/columns.py +273 -0
  441. package/tui/widgets/__init__.py +0 -0
  442. package/tui/widgets/__pycache__/__init__.cpython-314.pyc +0 -0
  443. package/tui/widgets/__pycache__/acp_content.cpython-314.pyc +0 -0
  444. package/tui/widgets/__pycache__/agent_response.cpython-314.pyc +0 -0
  445. package/tui/widgets/__pycache__/agent_thought.cpython-314.pyc +0 -0
  446. package/tui/widgets/__pycache__/ask_user.cpython-314.pyc +0 -0
  447. package/tui/widgets/__pycache__/condensed_path.cpython-314.pyc +0 -0
  448. package/tui/widgets/__pycache__/context_stats.cpython-314.pyc +0 -0
  449. package/tui/widgets/__pycache__/conversation.cpython-313.pyc +0 -0
  450. package/tui/widgets/__pycache__/conversation.cpython-314.pyc +0 -0
  451. package/tui/widgets/__pycache__/diff_view.cpython-314.pyc +0 -0
  452. package/tui/widgets/__pycache__/directory_input.cpython-314.pyc +0 -0
  453. package/tui/widgets/__pycache__/flash.cpython-314.pyc +0 -0
  454. package/tui/widgets/__pycache__/future_text.cpython-314.pyc +0 -0
  455. package/tui/widgets/__pycache__/grid_select.cpython-314.pyc +0 -0
  456. package/tui/widgets/__pycache__/highlighted_textarea.cpython-314.pyc +0 -0
  457. package/tui/widgets/__pycache__/mandelbrot.cpython-314.pyc +0 -0
  458. package/tui/widgets/__pycache__/markdown_note.cpython-314.pyc +0 -0
  459. package/tui/widgets/__pycache__/menu.cpython-314.pyc +0 -0
  460. package/tui/widgets/__pycache__/modified_files.cpython-314.pyc +0 -0
  461. package/tui/widgets/__pycache__/non_selectable_label.cpython-314.pyc +0 -0
  462. package/tui/widgets/__pycache__/note.cpython-314.pyc +0 -0
  463. package/tui/widgets/__pycache__/path_search.cpython-314.pyc +0 -0
  464. package/tui/widgets/__pycache__/plan.cpython-314.pyc +0 -0
  465. package/tui/widgets/__pycache__/project_directory_tree.cpython-314.pyc +0 -0
  466. package/tui/widgets/__pycache__/project_grid_select.cpython-314.pyc +0 -0
  467. package/tui/widgets/__pycache__/project_summary.cpython-314.pyc +0 -0
  468. package/tui/widgets/__pycache__/prompt.cpython-314.pyc +0 -0
  469. package/tui/widgets/__pycache__/question.cpython-314.pyc +0 -0
  470. package/tui/widgets/__pycache__/session_grid_select.cpython-314.pyc +0 -0
  471. package/tui/widgets/__pycache__/session_summary.cpython-314.pyc +0 -0
  472. package/tui/widgets/__pycache__/session_tabs.cpython-314.pyc +0 -0
  473. package/tui/widgets/__pycache__/shell_result.cpython-314.pyc +0 -0
  474. package/tui/widgets/__pycache__/shell_terminal.cpython-314.pyc +0 -0
  475. package/tui/widgets/__pycache__/side_bar.cpython-314.pyc +0 -0
  476. package/tui/widgets/__pycache__/slash_complete.cpython-314.pyc +0 -0
  477. package/tui/widgets/__pycache__/strike_text.cpython-314.pyc +0 -0
  478. package/tui/widgets/__pycache__/subagent.cpython-314.pyc +0 -0
  479. package/tui/widgets/__pycache__/terminal.cpython-313.pyc +0 -0
  480. package/tui/widgets/__pycache__/terminal.cpython-314.pyc +0 -0
  481. package/tui/widgets/__pycache__/throbber.cpython-314.pyc +0 -0
  482. package/tui/widgets/__pycache__/tool_call.cpython-313.pyc +0 -0
  483. package/tui/widgets/__pycache__/tool_call.cpython-314.pyc +0 -0
  484. package/tui/widgets/__pycache__/user_input.cpython-314.pyc +0 -0
  485. package/tui/widgets/acp_content.py +70 -0
  486. package/tui/widgets/agent_response.py +77 -0
  487. package/tui/widgets/agent_thought.py +168 -0
  488. package/tui/widgets/ask_user.py +264 -0
  489. package/tui/widgets/command_pane.py +223 -0
  490. package/tui/widgets/condensed_path.py +115 -0
  491. package/tui/widgets/context_stats.py +66 -0
  492. package/tui/widgets/conversation.py +2441 -0
  493. package/tui/widgets/danger_warning.py +65 -0
  494. package/tui/widgets/diff_view.py +59 -0
  495. package/tui/widgets/directory_input.py +8 -0
  496. package/tui/widgets/flash.py +81 -0
  497. package/tui/widgets/future_text.py +126 -0
  498. package/tui/widgets/grid_select.py +234 -0
  499. package/tui/widgets/highlighted_textarea.py +180 -0
  500. package/tui/widgets/mandelbrot.py +294 -0
  501. package/tui/widgets/markdown_note.py +13 -0
  502. package/tui/widgets/menu.py +147 -0
  503. package/tui/widgets/modified_files.py +106 -0
  504. package/tui/widgets/non_selectable_label.py +5 -0
  505. package/tui/widgets/note.py +18 -0
  506. package/tui/widgets/path_search.py +492 -0
  507. package/tui/widgets/plan.py +263 -0
  508. package/tui/widgets/project_directory_tree.py +90 -0
  509. package/tui/widgets/project_grid_select.py +35 -0
  510. package/tui/widgets/project_summary.py +33 -0
  511. package/tui/widgets/prompt.py +795 -0
  512. package/tui/widgets/question.py +360 -0
  513. package/tui/widgets/session_grid_select.py +224 -0
  514. package/tui/widgets/session_summary.py +115 -0
  515. package/tui/widgets/session_tabs.py +188 -0
  516. package/tui/widgets/shell_result.py +35 -0
  517. package/tui/widgets/shell_terminal.py +40 -0
  518. package/tui/widgets/side_bar.py +114 -0
  519. package/tui/widgets/slash_complete.py +235 -0
  520. package/tui/widgets/strike_text.py +70 -0
  521. package/tui/widgets/subagent.py +87 -0
  522. package/tui/widgets/terminal.py +577 -0
  523. package/tui/widgets/terminal_tool.py +351 -0
  524. package/tui/widgets/throbber.py +100 -0
  525. package/tui/widgets/tool_call.py +368 -0
  526. package/tui/widgets/user_input.py +23 -0
  527. package/tui/widgets/version.py +5 -0
  528. package/tui/widgets/welcome.py +31 -0
@@ -0,0 +1,77 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from enum import Enum
5
+ from pathlib import Path
6
+ from typing import Optional
7
+
8
+ from onecode.agent.tools.errors import ToolPermissionError
9
+
10
+
11
+ class PermissionResult(str, Enum):
12
+ ALLOW = "allow"
13
+ DENY = "deny"
14
+ ASK = "ask"
15
+
16
+
17
+ def _resolve_path(p: str | Path) -> Path:
18
+ return Path(p).expanduser().resolve()
19
+
20
+
21
+ def _is_within(child: Path, parent: Path) -> bool:
22
+ try:
23
+ child.relative_to(parent)
24
+ return True
25
+ except ValueError:
26
+ return False
27
+
28
+
29
+ @dataclass
30
+ class ToolPermissionContext:
31
+ deny_names: frozenset[str] = field(default_factory=frozenset)
32
+ deny_prefixes: tuple[str, ...] = ()
33
+ workspace_root: Optional[Path] = None
34
+ additional_working_directories: tuple[Path, ...] = ()
35
+
36
+ @classmethod
37
+ def from_iterables(
38
+ cls,
39
+ deny_names: list[str] | None = None,
40
+ deny_prefixes: list[str] | None = None,
41
+ *,
42
+ workspace_root: str | Path | None = None,
43
+ additional_working_directories: list[str | Path] | None = None,
44
+ ) -> ToolPermissionContext:
45
+ return cls(
46
+ deny_names=frozenset(name.lower() for name in (deny_names or [])),
47
+ deny_prefixes=tuple(prefix.lower() for prefix in (deny_prefixes or [])),
48
+ workspace_root=_resolve_path(workspace_root) if workspace_root else None,
49
+ additional_working_directories=tuple(
50
+ _resolve_path(p) for p in (additional_working_directories or [])
51
+ ),
52
+ )
53
+
54
+ def blocks_tool(self, tool_name: str) -> bool:
55
+ lowered = tool_name.lower()
56
+ return lowered in self.deny_names or any(
57
+ lowered.startswith(prefix) for prefix in self.deny_prefixes
58
+ )
59
+
60
+ def allowed_roots(self) -> tuple[Path, ...]:
61
+ roots: list[Path] = []
62
+ if self.workspace_root is not None:
63
+ roots.append(self.workspace_root)
64
+ roots.extend(self.additional_working_directories)
65
+ return tuple(roots)
66
+
67
+ def ensure_path_allowed(self, path: str | Path) -> Path:
68
+ resolved = _resolve_path(path)
69
+ roots = self.allowed_roots()
70
+ if not roots:
71
+ return resolved
72
+ if any(_is_within(resolved, root) for root in roots):
73
+ return resolved
74
+ roots_str = ", ".join(str(r) for r in roots)
75
+ raise ToolPermissionError(
76
+ f"path is outside allowed working directories: {resolved} (allowed: {roots_str})"
77
+ )
@@ -0,0 +1,20 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from typing import Any, Literal, Optional
5
+
6
+
7
+ @dataclass(frozen=True)
8
+ class ToolCall:
9
+ name: str
10
+ input: dict[str, Any]
11
+ tool_use_id: Optional[str] = None
12
+
13
+
14
+ @dataclass(frozen=True)
15
+ class ToolResult:
16
+ name: str
17
+ output: Any
18
+ is_error: bool = False
19
+ tool_use_id: Optional[str] = None
20
+ content_type: Literal["text", "json"] = "json"
@@ -0,0 +1,134 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Any, Iterable, Mapping, Optional, Protocol
5
+
6
+ from onecode.agent.tools.errors import ToolInputError, ToolPermissionError
7
+ from onecode.agent.tools.protocol import ToolCall, ToolResult
8
+ from onecode.agent.tools.schema_validation import validate_json_schema
9
+
10
+
11
+ @dataclass(frozen=True)
12
+ class ToolSpec:
13
+ name: str
14
+ description: str
15
+ input_schema: Mapping[str, Any]
16
+ aliases: tuple[str, ...] = ()
17
+ is_read_only: bool = False
18
+ is_destructive: bool = False
19
+ max_result_size_chars: int = 20_000
20
+
21
+
22
+ class Tool(Protocol):
23
+ def spec(self) -> ToolSpec: ...
24
+
25
+ def run(self, tool_input: dict[str, Any]) -> ToolResult: ...
26
+
27
+
28
+ class ToolRegistry:
29
+ def __init__(
30
+ self,
31
+ tools: Iterable[Tool] | None = None,
32
+ ) -> None:
33
+ self._tools: list[Tool] = []
34
+ self._by_name: dict[str, Tool] = {}
35
+ if tools:
36
+ for tool in tools:
37
+ self.register(tool)
38
+
39
+ def register(self, tool: Tool) -> None:
40
+ spec = tool.spec()
41
+ key = spec.name.lower()
42
+ if key in self._by_name:
43
+ raise ValueError(f"duplicate tool name: {spec.name}")
44
+ self._tools.append(tool)
45
+ self._by_name[key] = tool
46
+ for alias in spec.aliases:
47
+ alias_key = alias.lower()
48
+ if alias_key in self._by_name:
49
+ raise ValueError(f"duplicate tool alias: {alias}")
50
+ self._by_name[alias_key] = tool
51
+
52
+ def list_specs(self) -> list[ToolSpec]:
53
+ return [tool.spec() for tool in self._tools]
54
+
55
+ def get(self, name: str) -> Tool | None:
56
+ return self._by_name.get(name.lower())
57
+
58
+ def make_openai_schemas(self) -> list[dict]:
59
+ schemas = []
60
+ for spec in self.list_specs():
61
+ schemas.append({
62
+ "type": "function",
63
+ "function": {
64
+ "name": spec.name,
65
+ "description": spec.description,
66
+ "parameters": dict(spec.input_schema),
67
+ },
68
+ })
69
+ return schemas
70
+
71
+ def dispatch(
72
+ self,
73
+ call: ToolCall,
74
+ ) -> ToolResult:
75
+ tool = self.get(call.name)
76
+ if tool is None:
77
+ return ToolResult(
78
+ name=call.name,
79
+ output={"error": f"unknown tool: {call.name}"},
80
+ is_error=True,
81
+ tool_use_id=call.tool_use_id,
82
+ )
83
+ try:
84
+ spec = tool.spec()
85
+
86
+ # Schema validation
87
+ validate_json_schema(call.input, spec.input_schema, tool_name=call.name)
88
+
89
+ # Execute
90
+ result = tool.run(call.input)
91
+
92
+ # Enforce max result size — truncate oversized output so the
93
+ # context window is not flooded by a single tool result.
94
+ max_chars = spec.max_result_size_chars
95
+ if max_chars > 0 and isinstance(result.output, str) and len(result.output) > max_chars:
96
+ result = ToolResult(
97
+ name=result.name,
98
+ output=result.output[:max_chars]
99
+ + f"\n\n[Output truncated to {max_chars} characters]",
100
+ is_error=result.is_error,
101
+ tool_use_id=result.tool_use_id or call.tool_use_id,
102
+ content_type=result.content_type,
103
+ )
104
+
105
+ if result.tool_use_id is None:
106
+ return ToolResult(
107
+ name=result.name,
108
+ output=result.output,
109
+ is_error=result.is_error,
110
+ tool_use_id=call.tool_use_id,
111
+ content_type=result.content_type,
112
+ )
113
+ return result
114
+ except ToolInputError as e:
115
+ return ToolResult(
116
+ name=call.name,
117
+ output={"error": str(e)},
118
+ is_error=True,
119
+ tool_use_id=call.tool_use_id,
120
+ )
121
+ except ToolPermissionError as e:
122
+ return ToolResult(
123
+ name=call.name,
124
+ output={"error": str(e)},
125
+ is_error=True,
126
+ tool_use_id=call.tool_use_id,
127
+ )
128
+ except Exception as e:
129
+ return ToolResult(
130
+ name=call.name,
131
+ output={"error": str(e)},
132
+ is_error=True,
133
+ tool_use_id=call.tool_use_id,
134
+ )
@@ -0,0 +1,264 @@
1
+ from __future__ import annotations
2
+
3
+ import enum
4
+ import os
5
+ import resource
6
+ import subprocess
7
+ from dataclasses import dataclass, field
8
+ from pathlib import Path
9
+ from typing import Optional
10
+
11
+
12
+ class SandboxMode(enum.Enum):
13
+ NONE = "none"
14
+ BUBBLEWRAP = "bwrap"
15
+ DOCKER = "docker"
16
+
17
+
18
+ @dataclass
19
+ class ResourceLimits:
20
+ cpu_time: int = 30
21
+ memory_mb: int = 512
22
+ max_procs: int = 10
23
+ max_open_files: int = 100
24
+ max_pseudo_terminals: int = 0
25
+
26
+
27
+ @dataclass
28
+ class SandboxConfig:
29
+ workspace_root: Path
30
+ mode: SandboxMode = SandboxMode.NONE
31
+ resource_limits: ResourceLimits = field(default_factory=ResourceLimits)
32
+ read_only_bindings: tuple[str, ...] = ("/usr", "/bin", "/lib", "/lib64", "/opt", "/tmp")
33
+ network_enabled: bool = False
34
+ env_whitelist: tuple[str, ...] = ("PATH", "HOME", "USER", "TERM", "LANG", "LC_*")
35
+
36
+
37
+ class SandboxError(Exception):
38
+ pass
39
+
40
+
41
+ class Sandbox:
42
+ def __init__(self, config: SandboxConfig):
43
+ self.config = config
44
+ self._bwrap_available: Optional[bool] = None
45
+
46
+ def _check_bwrap_available(self) -> bool:
47
+ if self._bwrap_available is not None:
48
+ return self._bwrap_available
49
+ try:
50
+ subprocess.run(["bwrap", "--version"], capture_output=True, check=True)
51
+ self._bwrap_available = True
52
+ except (subprocess.CalledProcessError, FileNotFoundError):
53
+ self._bwrap_available = False
54
+ return self._bwrap_available
55
+
56
+ def _build_bwrap_cmd(self, cmd: str) -> list[str]:
57
+ ws = str(self.config.workspace_root.resolve())
58
+
59
+ bwrap_args = ["bwrap"]
60
+
61
+ bwrap_args.extend(["--dev", "/dev"])
62
+ bwrap_args.extend(["--proc", "/proc"])
63
+
64
+ bwrap_args.extend(["--bind", ws, "/workspace"])
65
+
66
+ for ro_path in self.config.read_only_bindings:
67
+ p = Path(ro_path)
68
+ if p.exists():
69
+ bwrap_args.extend(["--ro-bind", str(p), str(p)])
70
+
71
+ bwrap_args.extend(["--tmpfs", "/tmp"])
72
+ bwrap_args.extend(["--tmpfs", "/var/tmp"])
73
+
74
+ if not self.config.network_enabled:
75
+ bwrap_args.append("--unshare-net")
76
+
77
+ bwrap_args.extend(["--uid", str(os.getuid())])
78
+ bwrap_args.extend(["--gid", str(os.getgid())])
79
+
80
+ env = " ".join(f"{k}={v}" for k, v in os.environ.items() if k in self.config.env_whitelist or k.startswith("LC_"))
81
+ bwrap_args.extend(["--setenv", "HOME", "/workspace"])
82
+ bwrap_args.extend(["--setenv", "PATH", "/usr/local/sbin:/usr/local/bin:/usr/bin:/bin"])
83
+
84
+ bwrap_args.extend(["--chdir", "/workspace"])
85
+
86
+ bwrap_args.extend(["--", "sh", "-c", cmd])
87
+
88
+ return bwrap_args
89
+
90
+ def _set_resource_limits(self) -> None:
91
+ """Deprecated: applied rlimits to the parent process which broke
92
+ ``subprocess.run``. Kept as a no-op for backward compatibility —
93
+ use :meth:`_exec_direct` (which now applies the limits in a
94
+ ``preexec_fn``) instead.
95
+ """
96
+ return
97
+
98
+ def exec(self, cmd: str, timeout: int = 60) -> dict:
99
+ """Public entry point — dispatches to the right backend for the mode."""
100
+ mode = self.config.mode
101
+
102
+ if mode == SandboxMode.NONE:
103
+ return self._exec_direct(cmd, timeout)
104
+ if mode == SandboxMode.BUBBLEWRAP:
105
+ if self._check_bwrap_available():
106
+ return self._exec_bwrap(cmd, timeout)
107
+ return self._exec_direct(cmd, timeout)
108
+ if mode == SandboxMode.DOCKER:
109
+ return self._exec_docker(cmd, timeout)
110
+ return self._exec_direct(cmd, timeout)
111
+
112
+ def _exec_direct(self, cmd: str, timeout: int) -> dict:
113
+ # Apply resource limits in the *child* process only — calling
114
+ # ``setrlimit`` on the parent (this Python interpreter) shrinks
115
+ # its own address space, which then fails with ``EAGAIN`` on the
116
+ # very next ``fork``/``execve`` that ``subprocess.run`` performs.
117
+ limits = self.config.resource_limits
118
+
119
+ def _preexec_apply_rlimits() -> None: # pragma: no cover (runs in child)
120
+ try:
121
+ resource.setrlimit(
122
+ resource.RLIMIT_CPU, (limits.cpu_time, limits.cpu_time + 5)
123
+ )
124
+ except (ValueError, OSError, resource.error):
125
+ pass
126
+ try:
127
+ mem_bytes = limits.memory_mb * 1024 * 1024
128
+ resource.setrlimit(
129
+ resource.RLIMIT_AS, (mem_bytes, mem_bytes)
130
+ )
131
+ except (ValueError, OSError, resource.error):
132
+ pass
133
+ try:
134
+ resource.setrlimit(
135
+ resource.RLIMIT_NPROC, (limits.max_procs, limits.max_procs + 5)
136
+ )
137
+ except (ValueError, OSError, resource.error):
138
+ pass
139
+ try:
140
+ resource.setrlimit(
141
+ resource.RLIMIT_NOFILE,
142
+ (limits.max_open_files, limits.max_open_files + 10),
143
+ )
144
+ except (ValueError, OSError, resource.error):
145
+ pass
146
+
147
+ try:
148
+ result = subprocess.run(
149
+ cmd,
150
+ shell=True,
151
+ capture_output=True,
152
+ text=True,
153
+ cwd=str(self.config.workspace_root),
154
+ timeout=timeout,
155
+ preexec_fn=_preexec_apply_rlimits,
156
+ )
157
+ return {
158
+ "success": result.returncode == 0,
159
+ "stdout": result.stdout,
160
+ "stderr": result.stderr,
161
+ "returncode": result.returncode,
162
+ "sandbox": "none",
163
+ }
164
+ except subprocess.TimeoutExpired:
165
+ return {"success": False, "error": "Command timed out", "sandbox": "none"}
166
+ except Exception as e:
167
+ return {"success": False, "error": str(e), "sandbox": "none"}
168
+
169
+ def _exec_bwrap(self, cmd: str, timeout: int) -> dict:
170
+ bwrap_cmd = self._build_bwrap_cmd(cmd)
171
+
172
+ try:
173
+ result = subprocess.run(
174
+ bwrap_cmd,
175
+ capture_output=True,
176
+ text=True,
177
+ cwd=str(self.config.workspace_root),
178
+ timeout=timeout,
179
+ )
180
+ return {
181
+ "success": result.returncode == 0,
182
+ "stdout": result.stdout,
183
+ "stderr": result.stderr,
184
+ "returncode": result.returncode,
185
+ "sandbox": "bwrap",
186
+ }
187
+ except subprocess.TimeoutExpired:
188
+ return {"success": False, "error": "Command timed out", "sandbox": "bwrap"}
189
+ except FileNotFoundError:
190
+ return {"success": False, "error": "bwrap not installed", "sandbox": "bwrap"}
191
+ except Exception as e:
192
+ return {"success": False, "error": str(e), "sandbox": "bwrap"}
193
+
194
+ def _exec_docker(self, cmd: str, timeout: int) -> dict:
195
+ import docker
196
+
197
+ ws = str(self.config.workspace_root.resolve())
198
+
199
+ docker_cmd = [
200
+ "sh", "-c",
201
+ f"cd /workspace && {cmd}"
202
+ ]
203
+
204
+ try:
205
+ client = docker.from_env()
206
+ container = client.containers.run(
207
+ "alpine:latest",
208
+ docker_cmd,
209
+ detach=True,
210
+ mem_limit=f"{self.config.resource_limits.memory_mb}m",
211
+ cpu_period=100000,
212
+ cpu_quota=self.config.resource_limits.cpu_time * 1000,
213
+ network_mode="none" if not self.config.network_enabled else "bridge",
214
+ volumes={ws: {"bind": "/workspace", "mode": "rw"}},
215
+ working_dir="/workspace",
216
+ user=f"{os.getuid()}:{os.getgid()}",
217
+ remove=True,
218
+ )
219
+ result = container.wait(timeout=timeout)
220
+ logs = container.logs().decode("utf-8", errors="replace")
221
+ return {
222
+ "success": result["StatusCode"] == 0,
223
+ "stdout": logs,
224
+ "stderr": "",
225
+ "returncode": result["StatusCode"],
226
+ "sandbox": "docker",
227
+ }
228
+ except docker.errors.DockerException:
229
+ return {"success": False, "error": "Docker not available", "sandbox": "docker"}
230
+ except docker.errors.TimeoutError:
231
+ return {"success": False, "error": "Container timed out", "sandbox": "docker"}
232
+ except Exception as e:
233
+ return {"success": False, "error": str(e), "sandbox": "docker"}
234
+
235
+
236
+ def create_sandbox(
237
+ workspace_root: Path,
238
+ mode: str = "auto",
239
+ **kwargs,
240
+ ) -> Sandbox:
241
+ if mode == "auto":
242
+ sandbox_mode = SandboxMode.NONE
243
+ if Path("/usr/bin/bwrap").exists():
244
+ sandbox_mode = SandboxMode.BUBBLEWRAP
245
+ elif Path("/var/run/docker.sock").exists():
246
+ try:
247
+ subprocess.run(["docker", "info"], capture_output=True, check=True)
248
+ sandbox_mode = SandboxMode.DOCKER
249
+ except (subprocess.CalledProcessError, FileNotFoundError):
250
+ pass
251
+ elif mode == "bwrap":
252
+ sandbox_mode = SandboxMode.BUBBLEWRAP
253
+ elif mode == "docker":
254
+ sandbox_mode = SandboxMode.DOCKER
255
+ else:
256
+ sandbox_mode = SandboxMode.NONE
257
+
258
+ config = SandboxConfig(
259
+ workspace_root=workspace_root,
260
+ mode=sandbox_mode,
261
+ resource_limits=ResourceLimits(**kwargs) if kwargs else ResourceLimits(),
262
+ )
263
+
264
+ return Sandbox(config)
@@ -0,0 +1,100 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Mapping
4
+
5
+ from onecode.agent.tools.errors import ToolInputError
6
+
7
+
8
+ def validate_json_schema(
9
+ input_data: dict[str, Any],
10
+ schema: Mapping[str, Any],
11
+ tool_name: str = "",
12
+ ) -> None:
13
+ """Validate input against JSON Schema.
14
+
15
+ Supports: required fields, type checks, string patterns, enums,
16
+ nested objects, and arrays.
17
+ """
18
+ if not schema:
19
+ return
20
+
21
+ errors: list[str] = []
22
+ props = schema.get("properties", {})
23
+ required = set(schema.get("required", []))
24
+
25
+ # Check required fields
26
+ for field in required:
27
+ if field not in input_data or input_data[field] is None:
28
+ errors.append(f"missing required field: '{field}'")
29
+
30
+ # Validate each present field
31
+ for field, value in input_data.items():
32
+ prop_schema = props.get(field)
33
+ if prop_schema is None:
34
+ continue
35
+ _validate_field(field, value, prop_schema, errors, input_data)
36
+
37
+ if errors:
38
+ prefix = f" in tool '{tool_name}'" if tool_name else ""
39
+ msg = f"input validation failed{prefix}:\n" + "\n".join(f" - {e}" for e in errors)
40
+ raise ToolInputError(msg)
41
+
42
+
43
+ def _validate_field(
44
+ field: str,
45
+ value: Any,
46
+ schema: dict[str, Any],
47
+ errors: list[str],
48
+ root_input: dict[str, Any],
49
+ ) -> None:
50
+ json_type = schema.get("type", "")
51
+
52
+ if json_type == "string":
53
+ if not isinstance(value, str):
54
+ errors.append(f"'{field}': expected string, got {type(value).__name__}")
55
+ else:
56
+ pattern = schema.get("pattern")
57
+ if pattern:
58
+ import re
59
+ if not re.match(pattern, value):
60
+ errors.append(f"'{field}': does not match pattern {pattern!r}")
61
+
62
+ elif json_type == "integer":
63
+ if not isinstance(value, int) or isinstance(value, bool):
64
+ errors.append(f"'{field}': expected integer, got {type(value).__name__}")
65
+
66
+ elif json_type == "number":
67
+ if not isinstance(value, (int, float)) or isinstance(value, bool):
68
+ errors.append(f"'{field}': expected number, got {type(value).__name__}")
69
+
70
+ elif json_type == "boolean":
71
+ if not isinstance(value, bool):
72
+ errors.append(f"'{field}': expected boolean, got {type(value).__name__}")
73
+
74
+ elif json_type == "array":
75
+ if not isinstance(value, list):
76
+ errors.append(f"'{field}': expected array, got {type(value).__name__}")
77
+ else:
78
+ items_schema = schema.get("items")
79
+ if items_schema:
80
+ for i, item in enumerate(value):
81
+ _validate_field(f"{field}[{i}]", item, items_schema, errors, root_input)
82
+
83
+ elif json_type == "object":
84
+ if not isinstance(value, dict):
85
+ errors.append(f"'{field}': expected object, got {type(value).__name__}")
86
+ else:
87
+ sub_required = set(schema.get("required", []))
88
+ sub_props = schema.get("properties", {})
89
+ for sub_field in sub_required:
90
+ if sub_field not in value:
91
+ errors.append(f"'{field}.{sub_field}': missing required field")
92
+ for sub_field, sub_value in value.items():
93
+ sub_schema = sub_props.get(sub_field)
94
+ if sub_schema:
95
+ _validate_field(f"{field}.{sub_field}", sub_value, sub_schema, errors, root_input)
96
+
97
+ # Check enum values
98
+ enum_values = schema.get("enum")
99
+ if enum_values is not None and value not in enum_values:
100
+ errors.append(f"'{field}': must be one of {enum_values}, got {value!r}")
@@ -0,0 +1,52 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ from onecode.agent.tools.protocol import ToolResult
6
+ from onecode.agent.tools.registry import ToolSpec
7
+ from onecode.skills.loader import SkillLoader
8
+ from onecode.skills.argument_substitution import substitute_arguments
9
+
10
+
11
+ class SkillTool:
12
+ """Run a SKILL.md skill (Clawd-Code pattern)."""
13
+
14
+ def __init__(self, loader: SkillLoader):
15
+ self._loader = loader
16
+
17
+ def spec(self) -> ToolSpec:
18
+ return ToolSpec(
19
+ name="Skill",
20
+ description="Run a registered skill by name with optional arguments. Skills are markdown-based instruction sets with YAML frontmatter.",
21
+ input_schema={
22
+ "type": "object",
23
+ "properties": {
24
+ "name": {"type": "string", "description": "Skill name to run"},
25
+ "arguments": {
26
+ "type": "array",
27
+ "items": {"type": "string"},
28
+ "description": "Optional positional arguments ($0, $1, $path, $ARGUMENTS)",
29
+ },
30
+ },
31
+ "required": ["name"],
32
+ },
33
+ )
34
+
35
+ def run(self, tool_input: dict[str, Any]) -> ToolResult:
36
+ name = tool_input.get("name", "")
37
+ args = tool_input.get("arguments") or []
38
+ skill = self._loader.get(name)
39
+ if not skill:
40
+ return ToolResult(
41
+ name="Skill",
42
+ output={"error": f"skill '{name}' not found"},
43
+ is_error=True,
44
+ )
45
+ content = skill.content
46
+ if args:
47
+ content = substitute_arguments(content, args)
48
+ return ToolResult(
49
+ name="Skill",
50
+ output={"name": name, "content": content, "description": skill.description},
51
+ )
52
+