@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
package/tui/acp/prompt.py CHANGED
@@ -6,54 +6,6 @@ from tui.prompt.extract import extract_paths_from_prompt
6
6
  from tui.prompt.resource import load_resource, ResourceError
7
7
 
8
8
 
9
- CLOUD_SPEC_SKILL_PATH = Path(__file__).resolve().parent.parent.parent / "cloud-spec-skill" / "SKILL.md"
10
-
11
-
12
- def _get_cloud_spec_content() -> str:
13
- """Load cloud-spec-skill content."""
14
- if CLOUD_SPEC_SKILL_PATH.exists():
15
- return CLOUD_SPEC_SKILL_PATH.read_text(encoding="utf-8")
16
- return ""
17
-
18
-
19
- def cloud_spec_skill_loaded() -> bool:
20
- """Check if cloud-spec-skill SKILL.md exists."""
21
- return CLOUD_SPEC_SKILL_PATH.exists()
22
-
23
-
24
- def ensure_cloud_spec_skill_installed() -> str | None:
25
- """Ensure cloud-spec-skill is accessible to agents.
26
-
27
- Checks if SKILL.md exists in repo and copies it to ~/.cdh/skills/ if needed,
28
- or updates if the repo version is newer.
29
- Returns error message if installation fails, None on success.
30
- """
31
- from onecode.skills.loader import USER_SKILLS_DIR
32
-
33
- if not CLOUD_SPEC_SKILL_PATH.exists():
34
- return None
35
-
36
- skill_dest = USER_SKILLS_DIR / "cloud-spec-skill"
37
- dest_skills_md = skill_dest / "SKILL.md"
38
-
39
- if skill_dest.exists() and dest_skills_md.exists():
40
- repo_mtime = CLOUD_SPEC_SKILL_PATH.stat().st_mtime
41
- dest_mtime = dest_skills_md.stat().st_mtime
42
- if dest_mtime >= repo_mtime:
43
- return None
44
-
45
- try:
46
- import shutil
47
-
48
- if skill_dest.exists():
49
- shutil.rmtree(skill_dest)
50
- skill_dest.parent.mkdir(parents=True, exist_ok=True)
51
- shutil.copytree(CLOUD_SPEC_SKILL_PATH.parent, skill_dest)
52
- return None
53
- except Exception as e:
54
- return str(e)
55
-
56
-
57
9
  def build(project_path: Path, prompt: str) -> list[protocol.ContentBlock]:
58
10
  """Build the prompt structure and extract paths with the @ syntax.
59
11
 
@@ -66,14 +18,6 @@ def build(project_path: Path, prompt: str) -> list[protocol.ContentBlock]:
66
18
  """
67
19
  prompt_content: list[protocol.ContentBlock] = []
68
20
 
69
- # Prepend cloud-spec-skill as system guidance
70
- cloud_spec_content = _get_cloud_spec_content()
71
- if cloud_spec_content:
72
- prompt_content.append({
73
- "type": "text",
74
- "text": f"[System Guidance - Always follow these development standards]\n\n{cloud_spec_content}\n\n---\n\n"
75
- })
76
-
77
21
  prompt_content.append({"type": "text", "text": prompt})
78
22
  for path, _, _ in extract_paths_from_prompt(prompt):
79
23
  if path.endswith("/"):
@@ -81,7 +25,6 @@ def build(project_path: Path, prompt: str) -> list[protocol.ContentBlock]:
81
25
  try:
82
26
  resource = load_resource(project_path, Path(path))
83
27
  except ResourceError:
84
- # TODO: How should this be handled?
85
28
  continue
86
29
  uri = f"file://{resource.path.absolute().resolve()}"
87
30
  if resource.text is not None:
@@ -217,6 +217,11 @@ class ToolCall(SchemaDict, total=False, extra_items=Any):
217
217
  status: ToolCallStatus
218
218
  title: Required[str]
219
219
  toolCallId: Required[ToolCallId]
220
+ # Custom (non-ACP) field: when set, this tool call was invoked *inside* a
221
+ # subagent and the TUI should mount/update the ToolCall card inside the
222
+ # owning SubAgent widget (subagentId = the parent Spawn toolCallId) rather
223
+ # than in the main conversation.
224
+ subagentId: str
220
225
 
221
226
 
222
227
  # https://agentclientprotocol.com/protocol/schema#toolcallupdate
@@ -231,6 +236,7 @@ class ToolCallUpdate(SchemaDict, total=False, extra_items=Any):
231
236
  status: ToolCallStatus | None
232
237
  title: str | None
233
238
  toolCallId: Required[ToolCallId]
239
+ subagentId: str
234
240
 
235
241
 
236
242
  # https://agentclientprotocol.com/protocol/schema#param-tool-call
package/tui/app.py CHANGED
@@ -22,7 +22,6 @@ from textual.timer import Timer
22
22
  from textual.notifications import Notify
23
23
  from textual.screen import Screen
24
24
 
25
- import tui
26
25
  from tui.db import DB
27
26
  from tui.settings import Schema, Settings
28
27
  from tui.agent_schema import Agent as AgentData
@@ -234,12 +233,6 @@ def get_store_screen():
234
233
  return StoreScreen()
235
234
 
236
235
 
237
- def get_projects_screen() -> "ProjectsScreen":
238
- from tui.screens.projects_screen import ProjectsScreen
239
-
240
- return ProjectsScreen()
241
-
242
-
243
236
  class A2TUIApp(App, inherit_bindings=False):
244
237
  """The top level app."""
245
238
 
@@ -249,7 +242,6 @@ class A2TUIApp(App, inherit_bindings=False):
249
242
  "settings": get_settings_screen,
250
243
  "sessions": get_sessions_screen,
251
244
  "store": get_store_screen,
252
- "projects": get_projects_screen,
253
245
  }
254
246
  MODES = {}
255
247
  BINDING_GROUP_TITLE = "System"
@@ -361,9 +353,12 @@ class A2TUIApp(App, inherit_bindings=False):
361
353
  return
362
354
  session_time = monotonic() - conv.session_start_time
363
355
  session_id = getattr(conv.agent, "session_id", None) or conv._agent_session_id or ""
356
+ session_pk = conv._session_pk
357
+ if session_pk is None and conv.agent is not None:
358
+ session_pk = getattr(conv.agent, "session_pk", None)
364
359
  self._exit_metrics = {
365
360
  "session_id": session_id,
366
- "session_pk": conv._session_pk,
361
+ "session_pk": session_pk,
367
362
  "tool_call_total": conv._tool_call_total,
368
363
  "tool_call_success": conv._tool_call_success,
369
364
  "tool_call_failed": conv._tool_call_failed,
@@ -838,15 +833,6 @@ class A2TUIApp(App, inherit_bindings=False):
838
833
  self.capture_event("tui-run")
839
834
  self.anon_id # Created on frst reference
840
835
 
841
- from tui.acp.prompt import ensure_cloud_spec_skill_installed
842
-
843
- if err := ensure_cloud_spec_skill_installed():
844
- self.notify(
845
- f"Failed to install cloud-spec-skill: {err}",
846
- title="cloud-spec-skill",
847
- severity="warning",
848
- )
849
-
850
836
  from pathlib import Path
851
837
 
852
838
  if mode := self._initial_mode:
@@ -1015,7 +1001,7 @@ class A2TUIApp(App, inherit_bindings=False):
1015
1001
  hint = f" Reload with: cdh session load {session_pk}\n"
1016
1002
  elif sid:
1017
1003
  display_id = sid
1018
- hint = ""
1004
+ hint = f" Reload with: cdh session load {sid}\n"
1019
1005
  else:
1020
1006
  display_id = "-"
1021
1007
  hint = ""
@@ -1142,15 +1128,16 @@ class A2TUIApp(App, inherit_bindings=False):
1142
1128
  @work
1143
1129
  async def action_projects(self) -> None:
1144
1130
  from pathlib import Path
1145
- from onecode.config import load_config, save_config, CLOUD_DEV_HARNESS_DIR
1131
+ from onecode.config import load_config, save_config
1146
1132
  from onecode.config_screen import EditFieldScreen
1147
1133
  import yaml
1148
1134
 
1149
- result = await self.push_screen_wait("projects")
1135
+ from tui.screens.projects_screen import ProjectsScreen
1136
+ result = await self.push_screen_wait(ProjectsScreen())
1150
1137
  if result is None:
1151
1138
  cfg = load_config()
1152
1139
  if cfg.current_project:
1153
- projects_dir = CLOUD_DEV_HARNESS_DIR / "projects"
1140
+ projects_dir = Path.home() / ".cdh" / "projects"
1154
1141
  pf = projects_dir / f"{cfg.current_project}.yaml"
1155
1142
  if not pf.exists():
1156
1143
  cfg.current_project = None
@@ -1176,7 +1163,7 @@ class A2TUIApp(App, inherit_bindings=False):
1176
1163
  from cdh.scaffold import scaffold_dlc_project
1177
1164
  scaffold_dlc_project(project_path, name)
1178
1165
  CdhProjectLoader.init_project(project_path, name)
1179
- projects_dir = CLOUD_DEV_HARNESS_DIR / "projects"
1166
+ projects_dir = Path.home() / ".cdh" / "projects"
1180
1167
  projects_dir.mkdir(parents=True, exist_ok=True)
1181
1168
  proj_data = {"name": name, "path": str(project_path), "description": ""}
1182
1169
  (projects_dir / f"{name}.yaml").write_text(yaml.dump(proj_data))
@@ -1191,7 +1178,7 @@ class A2TUIApp(App, inherit_bindings=False):
1191
1178
 
1192
1179
  project_name = result if isinstance(result, str) else getattr(result, 'name', None)
1193
1180
  if project_name:
1194
- projects_dir = CLOUD_DEV_HARNESS_DIR / "projects"
1181
+ projects_dir = Path.home() / ".cdh" / "projects"
1195
1182
  project_path = None
1196
1183
  for ext in ["yaml", "yml", "json"]:
1197
1184
  pf = projects_dir / f"{project_name}.{ext}"
@@ -1216,15 +1203,6 @@ class A2TUIApp(App, inherit_bindings=False):
1216
1203
 
1217
1204
  @on(messages.LaunchAgent)
1218
1205
  def on_launch_agent(self, message: messages.LaunchAgent) -> None:
1219
- from tui.acp.prompt import ensure_cloud_spec_skill_installed
1220
-
1221
- if err := ensure_cloud_spec_skill_installed():
1222
- self.notify(
1223
- f"Failed to install cloud-spec-skill: {err}",
1224
- title="cloud-spec-skill",
1225
- severity="warning",
1226
- )
1227
-
1228
1206
  self.launch_agent(
1229
1207
  message.identity,
1230
1208
  agent_session_id=message.session_id,
package/tui/messages.py CHANGED
@@ -116,6 +116,8 @@ class SessionUpdate(Message):
116
116
  """Project directory path."""
117
117
  state: SessionState | None = None
118
118
  """New session state."""
119
+ session_pk: int | None = None
120
+ """DB primary key of the session."""
119
121
 
120
122
 
121
123
  @dataclass
@@ -2,7 +2,7 @@ import asyncio
2
2
  from itertools import islice
3
3
  import os
4
4
  from pathlib import Path
5
- from typing import Iterable, Literal, Sequence
5
+ from typing import Iterable, Literal
6
6
 
7
7
 
8
8
  def longest_common_prefix(strings: list[str]) -> str:
package/tui/paths.py CHANGED
@@ -69,3 +69,19 @@ def get_log() -> Path:
69
69
  with suppress(OSError):
70
70
  path.mkdir(0o700, exist_ok=True, parents=True)
71
71
  return path
72
+
73
+
74
+ def get_sessions() -> Path:
75
+ """Get the platform-level sessions directory (~/.cdh/sessions/).
76
+
77
+ Session JSON files are stored by the cdh platform, not by individual
78
+ agent engines. The DB index at ~/.cdh/state/tui/tui.db maps each
79
+ session to its owning engine (B mapping mode).
80
+
81
+ Returns:
82
+ Path to the sessions directory.
83
+ """
84
+ path = Path.home() / ".cdh" / "sessions"
85
+ with suppress(OSError):
86
+ path.mkdir(0o700, exist_ok=True, parents=True)
87
+ return path
@@ -32,22 +32,22 @@ class ComponentPickerScreen(ModalScreen[Optional[list[str]]]):
32
32
  width: 78;
33
33
  height: auto;
34
34
  max-height: 90%;
35
- background: #111;
36
- border: solid #555;
35
+ background: $surface;
36
+ border: solid $border;
37
37
  padding: 0 1;
38
38
  }
39
39
 
40
40
  #picker-title {
41
41
  height: 1;
42
- background: #333;
43
- color: #fff;
42
+ background: $panel;
43
+ color: $text;
44
44
  padding: 0 1;
45
45
  text-style: bold;
46
46
  }
47
47
 
48
48
  #picker-subtitle {
49
49
  height: 1;
50
- color: #aaa;
50
+ color: $text-muted;
51
51
  padding: 0 1;
52
52
  margin-bottom: 1;
53
53
  }
@@ -56,43 +56,63 @@ class ComponentPickerScreen(ModalScreen[Optional[list[str]]]):
56
56
  height: auto;
57
57
  max-height: 22;
58
58
  padding: 0 1;
59
+ background: $surface;
59
60
  }
60
61
 
61
62
  .component-row {
62
63
  height: 2;
63
64
  padding: 0 1;
65
+ background: $surface;
64
66
  }
65
67
 
66
68
  .component-row Checkbox {
67
69
  width: 1fr;
70
+ background: $surface;
71
+ color: $text;
72
+ }
73
+
74
+ .component-row Checkbox > .toggle--button {
75
+ background: $panel;
76
+ color: $text-muted;
77
+ }
78
+
79
+ .component-row Checkbox.-on > .toggle--button {
80
+ background: $primary;
81
+ color: $text;
82
+ }
83
+
84
+ .component-row Checkbox > .toggle--label {
85
+ color: $text;
86
+ }
87
+
88
+ Footer {
89
+ background: $panel;
90
+ color: $text-muted;
68
91
  }
69
92
 
70
93
  #picker-buttons {
71
94
  height: 3;
72
95
  align: center middle;
73
- background: #222;
96
+ background: $panel;
74
97
  }
75
98
 
76
99
  #picker-buttons Button {
77
100
  margin: 0 1;
78
- background: #444;
79
- color: #fff;
101
+ background: $panel;
102
+ color: $text;
80
103
  }
81
104
 
82
105
  #picker-buttons Button:hover {
83
- background: #666;
84
- }
85
-
86
- #picker-buttons Button:focus {
87
- background: #555;
106
+ background: $primary-darken-2;
88
107
  }
89
108
 
90
109
  #picker-buttons #picker-confirm {
91
- background: #1a4d8f;
110
+ background: $primary-background;
111
+ color: $text;
92
112
  }
93
113
 
94
114
  #picker-buttons #picker-confirm:hover {
95
- background: #2870c2;
115
+ background: $primary-darken-1;
96
116
  }
97
117
  """
98
118
 
@@ -0,0 +1,84 @@
1
+ from __future__ import annotations
2
+
3
+ from textual.app import ComposeResult
4
+ from textual.binding import Binding
5
+ from textual.containers import VerticalScroll
6
+ from textual.screen import ModalScreen
7
+ from textual.widgets import Static
8
+
9
+ from tui.widgets.diff_view import make_diff
10
+
11
+
12
+ class DiffScreen(ModalScreen[None]):
13
+ """Full-screen view of a file diff.
14
+
15
+ Opened when a file is selected from the ModifiedFiles sidebar. Displays
16
+ the same DiffView widget that ``Conversation.post_diff`` would have mounted
17
+ inline, but as a dedicated modal screen instead of a conversation block.
18
+ """
19
+
20
+ BINDINGS = [
21
+ Binding("escape", "dismiss(None)", "Close", show=False, priority=True),
22
+ Binding("q", "dismiss(None)", "Close", show=False, priority=True),
23
+ ]
24
+
25
+ DEFAULT_CSS = """
26
+ DiffScreen {
27
+ background: $surface;
28
+ }
29
+
30
+ #diff-header {
31
+ dock: top;
32
+ width: 100%;
33
+ padding: 1 2;
34
+ background: $panel;
35
+ color: $text-primary;
36
+ text-style: bold;
37
+ height: 3;
38
+ }
39
+
40
+ #diff-header-meta {
41
+ color: $text-muted;
42
+ text-style: none;
43
+ }
44
+
45
+ #diff-scroll {
46
+ width: 100%;
47
+ height: 1fr;
48
+ padding: 0 1 0 1;
49
+ }
50
+
51
+ #diff-scroll > DiffView {
52
+ width: 100%;
53
+ height: auto;
54
+ }
55
+ """
56
+
57
+ def __init__(
58
+ self,
59
+ path: str,
60
+ before: str | None,
61
+ after: str,
62
+ *,
63
+ name: str | None = None,
64
+ id: str | None = None,
65
+ classes: str | None = None,
66
+ ) -> None:
67
+ super().__init__(name=name, id=id, classes=classes)
68
+ self._path = path
69
+ self._before = before
70
+ self._after = after
71
+
72
+ def compose(self) -> ComposeResult:
73
+ filename = self._path.rsplit("/", 1)[-1] or self._path
74
+ meta = "untracked" if self._before is None else "modified"
75
+ yield Static(
76
+ f"[b]📝 {filename}[/b] [#888888]{meta}[/]",
77
+ id="diff-header",
78
+ markup=True,
79
+ )
80
+ with VerticalScroll(id="diff-scroll"):
81
+ yield make_diff(self._path, self._path, self._before, self._after)
82
+
83
+ def on_mount(self) -> None:
84
+ self.title = f"Diff: {self._path}"
@@ -285,7 +285,7 @@ class LogScreen(ModalScreen[None]):
285
285
  padding: 0 1;
286
286
  }
287
287
 
288
- LogScreen VerticalScroll {
288
+ LogScreen #log-list {
289
289
  height: 1fr;
290
290
  background: $surface;
291
291
  }
@@ -308,7 +308,8 @@ class LogScreen(ModalScreen[None]):
308
308
  yield Static(self._header_text(), id="log-header")
309
309
  yield VerticalScroll(id="log-list")
310
310
 
311
- def on_mount(self) -> None:
311
+ async def on_mount(self) -> None:
312
+ self.query_one("#log-list", VerticalScroll).can_focus = False
312
313
  if self._log_path is None:
313
314
  self._append_system("No log file yet. Start an agent to begin logging.")
314
315
  return
@@ -318,7 +319,7 @@ class LogScreen(ModalScreen[None]):
318
319
  )
319
320
  self._poll_timer = self.set_interval(0.2, self._poll_new_lines)
320
321
  return
321
- self._begin_tailing()
322
+ await self._begin_tailing()
322
323
 
323
324
  # ── Page helpers ───────────────────────────────────────────────
324
325
 
@@ -330,25 +331,24 @@ class LogScreen(ModalScreen[None]):
330
331
  end = min(start + _PAGE_SIZE, len(self._entries))
331
332
  return start, end
332
333
 
333
- def _render_page(self) -> None:
334
+ async def _render_page(self) -> None:
334
335
  scroller = self.query_one("#log-list", VerticalScroll)
335
- scroller.remove_children()
336
+ await scroller.remove_children()
336
337
  start, end = self._page_range()
337
338
  page_entries = self._entries[start:end]
338
- for tag, raw in page_entries:
339
- scroller.mount(LogEntry(tag, raw))
339
+ await scroller.mount(*[LogEntry(tag, raw) for tag, raw in page_entries])
340
340
  self._current_index = 0 if page_entries else -1
341
341
  self._apply_selection()
342
342
  self._refresh_header()
343
343
 
344
- def _navigate_to_page(self, page: int) -> None:
344
+ async def _navigate_to_page(self, page: int) -> None:
345
345
  total = self._total_pages()
346
346
  self._current_page = max(1, min(page, total))
347
- self._render_page()
347
+ await self._render_page()
348
348
 
349
349
  # ── File loading ───────────────────────────────────────────────
350
350
 
351
- def _begin_tailing(self) -> None:
351
+ async def _begin_tailing(self) -> None:
352
352
  """Read entire log file and start polling."""
353
353
  assert self._log_path is not None
354
354
  try:
@@ -360,9 +360,9 @@ class LogScreen(ModalScreen[None]):
360
360
  return
361
361
  self._file_was_present = True
362
362
  self._entries = []
363
- self._ingest(data)
363
+ await self._ingest(data)
364
364
  self._current_page = self._total_pages()
365
- self._render_page()
365
+ await self._render_page()
366
366
  if self._poll_timer is None:
367
367
  self._poll_timer = self.set_interval(0.2, self._poll_new_lines)
368
368
 
@@ -413,14 +413,14 @@ class LogScreen(ModalScreen[None]):
413
413
 
414
414
  # ── Polling for new lines ──────────────────────────────────────
415
415
 
416
- def _poll_new_lines(self) -> None:
416
+ async def _poll_new_lines(self) -> None:
417
417
  if self._log_path is None:
418
418
  return
419
419
  if not self._log_path.exists():
420
420
  return
421
421
  if not self._file_was_present:
422
422
  self._append_system(f"── log file appeared: {self._log_path} ──")
423
- self._begin_tailing()
423
+ await self._begin_tailing()
424
424
  return
425
425
  try:
426
426
  size = self._log_path.stat().st_size
@@ -433,11 +433,11 @@ class LogScreen(ModalScreen[None]):
433
433
  new_text = f.read()
434
434
  self._file_pos = size
435
435
  if new_text:
436
- self._ingest(new_text)
436
+ await self._ingest(new_text)
437
437
  except OSError:
438
438
  pass
439
439
 
440
- def _ingest(self, chunk: str) -> None:
440
+ async def _ingest(self, chunk: str) -> None:
441
441
  was_on_last_page = (
442
442
  len(self._entries) == 0
443
443
  or self._current_page == self._total_pages()
@@ -466,7 +466,7 @@ class LogScreen(ModalScreen[None]):
466
466
  self._entries.append(("error", line))
467
467
  if was_on_last_page:
468
468
  self._current_page = self._total_pages()
469
- self._render_page()
469
+ await self._render_page()
470
470
  else:
471
471
  self._refresh_header()
472
472
 
@@ -479,19 +479,19 @@ class LogScreen(ModalScreen[None]):
479
479
  return child
480
480
  return None
481
481
 
482
- def action_next_page(self) -> None:
482
+ async def action_next_page(self) -> None:
483
483
  if self._current_page < self._total_pages():
484
- self._navigate_to_page(self._current_page + 1)
484
+ await self._navigate_to_page(self._current_page + 1)
485
485
 
486
- def action_prev_page(self) -> None:
486
+ async def action_prev_page(self) -> None:
487
487
  if self._current_page > 1:
488
- self._navigate_to_page(self._current_page - 1)
488
+ await self._navigate_to_page(self._current_page - 1)
489
489
 
490
- def action_first_page(self) -> None:
491
- self._navigate_to_page(1)
490
+ async def action_first_page(self) -> None:
491
+ await self._navigate_to_page(1)
492
492
 
493
- def action_last_page(self) -> None:
494
- self._navigate_to_page(self._total_pages())
493
+ async def action_last_page(self) -> None:
494
+ await self._navigate_to_page(self._total_pages())
495
495
 
496
496
  def action_next_entry(self) -> None:
497
497
  scroller = self.query_one("#log-list", VerticalScroll)