@scemoon/cdh 1.0.3 → 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 (294) 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 +462 -49
  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__/agents.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__/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/agents.py +8 -7
  202. package/tui/app.py +11 -33
  203. package/tui/data/agents/ampcode.com.toml +1 -0
  204. package/tui/data/agents/augmentcode.com.toml +1 -0
  205. package/tui/data/agents/cdh.cloud-dev-harness.toml +3 -2
  206. package/tui/data/agents/crowai.dev.toml +1 -0
  207. package/tui/data/agents/deepagents.langchain.com +1 -0
  208. package/tui/data/agents/docker.com.toml +1 -0
  209. package/tui/data/agents/goose.ai.toml +1 -0
  210. package/tui/data/agents/inference.huggingface.co.toml +1 -1
  211. package/tui/data/agents/kiro.dev.toml +1 -0
  212. package/tui/data/agents/onecode.dev.toml +22 -0
  213. package/tui/data/agents/openclaw.ai.toml +1 -1
  214. package/tui/data/agents/openhands.dev.toml +1 -0
  215. package/tui/data/agents/stakpak.dev.toml +1 -0
  216. package/tui/data/agents/vibe.mistral.ai.toml +1 -0
  217. package/tui/data/agents/vtcode.dev.toml +1 -0
  218. package/tui/messages.py +2 -0
  219. package/tui/path_complete.py +1 -1
  220. package/tui/paths.py +16 -0
  221. package/tui/screens/__pycache__/component_picker.cpython-314.pyc +0 -0
  222. package/tui/screens/__pycache__/diff_screen.cpython-314.pyc +0 -0
  223. package/tui/screens/__pycache__/log.cpython-314.pyc +0 -0
  224. package/tui/screens/__pycache__/main.cpython-313.pyc +0 -0
  225. package/tui/screens/__pycache__/main.cpython-314.pyc +0 -0
  226. package/tui/screens/__pycache__/permissions.cpython-314.pyc +0 -0
  227. package/tui/screens/__pycache__/projects_app.cpython-314.pyc +0 -0
  228. package/tui/screens/__pycache__/projects_screen.cpython-314.pyc +0 -0
  229. package/tui/screens/__pycache__/sessions.cpython-314.pyc +0 -0
  230. package/tui/screens/__pycache__/store.cpython-314.pyc +0 -0
  231. package/tui/screens/__pycache__/tool_content_screen.cpython-314.pyc +0 -0
  232. package/tui/screens/component_picker.py +35 -15
  233. package/tui/screens/diff_screen.py +84 -0
  234. package/tui/screens/log.py +25 -25
  235. package/tui/screens/main.py +48 -26
  236. package/tui/screens/projects.tcss +5 -1
  237. package/tui/screens/projects_app.py +6 -6
  238. package/tui/screens/projects_screen.py +19 -39
  239. package/tui/screens/sessions.py +3 -5
  240. package/tui/screens/store.py +1 -1
  241. package/tui/session_tracker.py +3 -0
  242. package/tui/settings_schema.py +2 -2
  243. package/tui/visuals/__pycache__/columns.cpython-314.pyc +0 -0
  244. package/tui/visuals/columns.py +1 -2
  245. package/tui/widgets/__pycache__/agent_response.cpython-314.pyc +0 -0
  246. package/tui/widgets/__pycache__/agent_thought.cpython-314.pyc +0 -0
  247. package/tui/widgets/__pycache__/conversation.cpython-314.pyc +0 -0
  248. package/tui/widgets/__pycache__/diff_view.cpython-314.pyc +0 -0
  249. package/tui/widgets/__pycache__/load_more.cpython-314.pyc +0 -0
  250. package/tui/widgets/__pycache__/modified_files.cpython-314.pyc +0 -0
  251. package/tui/widgets/__pycache__/plan.cpython-314.pyc +0 -0
  252. package/tui/widgets/__pycache__/project_grid_select.cpython-314.pyc +0 -0
  253. package/tui/widgets/__pycache__/session_grid_select.cpython-314.pyc +0 -0
  254. package/tui/widgets/__pycache__/shell_result.cpython-314.pyc +0 -0
  255. package/tui/widgets/__pycache__/side_bar.cpython-314.pyc +0 -0
  256. package/tui/widgets/__pycache__/subagent.cpython-314.pyc +0 -0
  257. package/tui/widgets/__pycache__/subagent_screen.cpython-314.pyc +0 -0
  258. package/tui/widgets/__pycache__/terminal.cpython-314.pyc +0 -0
  259. package/tui/widgets/__pycache__/tool_call.cpython-314.pyc +0 -0
  260. package/tui/widgets/agent_response.py +0 -1
  261. package/tui/widgets/agent_thought.py +0 -1
  262. package/tui/widgets/conversation.py +157 -14
  263. package/tui/widgets/diff_view.py +10 -0
  264. package/tui/widgets/load_more.py +24 -0
  265. package/tui/widgets/modified_files.py +173 -105
  266. package/tui/widgets/plan.py +1 -1
  267. package/tui/widgets/project_grid_select.py +9 -2
  268. package/tui/widgets/session_grid_select.py +6 -0
  269. package/tui/widgets/side_bar.py +10 -0
  270. package/tui/widgets/subagent.py +92 -58
  271. package/tui/widgets/subagent_screen.py +638 -115
  272. package/tui/widgets/terminal.py +1 -2
  273. package/tui/widgets/terminal_tool.py +11 -2
  274. package/tui/widgets/tool_call.py +35 -2
  275. package/cdh/__pycache__/__init__.cpython-313.pyc +0 -0
  276. package/cdh/__pycache__/__main__.cpython-314.pyc +0 -0
  277. package/cdh/__pycache__/scaffold.cpython-313.pyc +0 -0
  278. package/onecode/__pycache__/mock_data.cpython-314.pyc +0 -0
  279. package/onecode/agent/__pycache__/cdh_agent_acp.cpython-313.pyc +0 -0
  280. package/onecode/agent/__pycache__/cdh_agent_acp.cpython-314.pyc +0 -0
  281. package/onecode/agent/__pycache__/harness_skill.cpython-314.pyc +0 -0
  282. package/onecode/agent/tools/__pycache__/task_tools.cpython-314.pyc +0 -0
  283. package/onecode/codebase/__pycache__/config.cpython-314.pyc +0 -0
  284. package/onecode/memory/offload.py +0 -114
  285. package/onecode/memory/symbolic.py +0 -164
  286. package/onecode/models/providers/__pycache__/mock_provider.cpython-314.pyc +0 -0
  287. package/onecode/tasks/__pycache__/__init__.cpython-314.pyc +0 -0
  288. package/onecode/tasks/__pycache__/manager.cpython-314.pyc +0 -0
  289. package/onecode/tasks/__pycache__/models.cpython-314.pyc +0 -0
  290. package/tui/__pycache__/cli.cpython-314.pyc +0 -0
  291. package/tui/screens/__pycache__/settings.cpython-314.pyc +0 -0
  292. package/tui/widgets/__pycache__/conversation.cpython-313.pyc +0 -0
  293. package/tui/widgets/__pycache__/terminal.cpython-313.pyc +0 -0
  294. package/tui/widgets/__pycache__/tool_call.cpython-313.pyc +0 -0
@@ -65,9 +65,10 @@ from tui.protocol import BlockProtocol, MenuProtocol, ExpandProtocol
65
65
  from tui.menus import MenuItem
66
66
  from tui.widgets.shell_terminal import ShellTerminal
67
67
  from tui.widgets.ask_user import AskUserWidget, AskUserSubmitted
68
+ from tui.widgets.load_more import LoadMoreIndicator
68
69
 
69
70
  if TYPE_CHECKING:
70
- from collections.abc import Awaitable, Callable
71
+ from collections.abc import Callable
71
72
  from tui.acp.agent import Mode
72
73
  from tui.widgets.terminal import Terminal
73
74
  from tui.widgets.agent_response import AgentResponse
@@ -183,7 +184,12 @@ class Cursor(Static):
183
184
  self.blink_timer = self.set_interval(0.5, self._update_blink, pause=True)
184
185
 
185
186
  def _update_blink(self) -> None:
186
- if self.query_ancestor(Window).has_focus and self.screen.is_active:
187
+ try:
188
+ window = self.query_ancestor(Window)
189
+ except NoMatches:
190
+ self.blink = True
191
+ return
192
+ if window.has_focus and self.screen.is_active:
187
193
  self.blink = not self.blink
188
194
  else:
189
195
  self.blink = False
@@ -263,9 +269,19 @@ This is a view of your conversation with the agent.
263
269
  BINDING_GROUP_TITLE = "View"
264
270
  BINDINGS = [Binding("end", "screen.focus_prompt", "Prompt")]
265
271
 
272
+ auto_follow: var[bool] = var(True)
273
+
266
274
  def update_node_styles(self, animate: bool = True) -> None:
267
275
  pass
268
276
 
277
+ def watch_scroll_y(self, old_value: float, new_value: float) -> None:
278
+ self.auto_follow = new_value >= self.max_scroll_y
279
+ super().watch_scroll_y(old_value, new_value)
280
+
281
+ def scroll_end_if_following(self, animate: bool = True) -> None:
282
+ if self.auto_follow:
283
+ self.scroll_end(animate=animate)
284
+
269
285
 
270
286
  def _plan_entries_from_dicts(raw_entries):
271
287
  """Deprecated thin wrapper around the shared ``entries_from_dicts`` helper.
@@ -316,6 +332,7 @@ class Conversation(containers.Vertical):
316
332
  "cancel",
317
333
  "Cancel",
318
334
  tooltip="Cancel agent's turn",
335
+ priority=True,
319
336
  ),
320
337
  Binding(
321
338
  "ctrl+f",
@@ -330,6 +347,12 @@ class Conversation(containers.Vertical):
330
347
  "Modes",
331
348
  tooltip="Open the mode switcher",
332
349
  ),
350
+ Binding(
351
+ "ctrl+n",
352
+ "fullscreen_block",
353
+ "Full screen",
354
+ tooltip="Open full-screen view of the cursor block",
355
+ ),
333
356
  Binding(
334
357
  "ctrl+c",
335
358
  "interrupt",
@@ -424,6 +447,12 @@ class Conversation(containers.Vertical):
424
447
  self._post_lock = asyncio.Lock()
425
448
  self._auto_named_session = False
426
449
 
450
+ self._total_messages: int = 0
451
+ self._visible_count: int = 0
452
+ self._loaded_up_to: int = 0
453
+ self._load_more_widget: LoadMoreIndicator | None = None
454
+ self._flush_append_at: int | None = None
455
+
427
456
  def update_title(self) -> None:
428
457
  """Update the screen title."""
429
458
  screen = self.screen
@@ -474,6 +503,7 @@ class Conversation(containers.Vertical):
474
503
  self.prompt.prompt_text_area.insert(" ")
475
504
 
476
505
  def watch_project_path(self, path: Path) -> None:
506
+ self.working_directory = str(path)
477
507
  self.post_message(messages.SessionUpdate(path=str(path)))
478
508
 
479
509
  async def watch_shell_history_index(self, previous_index: int, index: int) -> None:
@@ -721,6 +751,13 @@ class Conversation(containers.Vertical):
721
751
  self.refresh_bindings()
722
752
  self.call_after_refresh(self.cursor.follow, cursor_block)
723
753
 
754
+ async def action_fullscreen_block(self) -> None:
755
+ if (cursor_block := self.cursor_block) is not None:
756
+ fullscreen = getattr(cursor_block, "action_fullscreen", None)
757
+ if fullscreen:
758
+ fullscreen()
759
+ self.call_after_refresh(self.cursor.follow, cursor_block)
760
+
724
761
  async def post_agent_response(self, fragment: str = "") -> AgentResponse | None:
725
762
  """Get or create an agent response widget."""
726
763
  from tui.widgets.agent_response import AgentResponse
@@ -926,6 +963,9 @@ class Conversation(containers.Vertical):
926
963
  self._auto_named_session = True
927
964
  return
928
965
 
966
+ if self.agent.session_pk is None:
967
+ return
968
+
929
969
  self._auto_named_session = True
930
970
  name = prompt.strip().split("\n")[0][:60].strip()
931
971
  if name:
@@ -957,6 +997,7 @@ class Conversation(containers.Vertical):
957
997
  )
958
998
  finally:
959
999
  self.busy_count -= 1
1000
+ await self._auto_name_session(prompt)
960
1001
  self.call_later(self.agent_turn_over, stop_reason)
961
1002
 
962
1003
  async def agent_turn_over(self, stop_reason: str | None) -> None:
@@ -971,6 +1012,14 @@ class Conversation(containers.Vertical):
971
1012
  self._agent_response = None
972
1013
  self._complete_thought()
973
1014
 
1015
+ # Safety net: mark any still-active subagents as cancelled.
1016
+ # Normal flow emits subagent_end before getting here, but
1017
+ # cancellation or errors can leave widgets stuck in "running".
1018
+ if self._active_subagents:
1019
+ for sa in self._active_subagents.values():
1020
+ sa.complete(status="cancelled", error="Turn ended")
1021
+ self._active_subagents.clear()
1022
+
974
1023
  if self._directory_changed or not self.is_watching_directory:
975
1024
  self._directory_changed = False
976
1025
  self.post_message(messages.ProjectDirectoryUpdated(project_dir=self.app.project_dir))
@@ -1094,6 +1143,10 @@ class Conversation(containers.Vertical):
1094
1143
  self._replay = message.active
1095
1144
  # When the replay just ended, flush buffer and complete any pending thought.
1096
1145
  if was_replay and not message.active:
1146
+ if message.total_messages > 0:
1147
+ self._total_messages = message.total_messages
1148
+ self._visible_count = message.visible_count
1149
+ self._loaded_up_to = message.total_messages - message.visible_count
1097
1150
  self.call_after_refresh(self._flush_replay_buffer)
1098
1151
  self._complete_thought()
1099
1152
 
@@ -1168,6 +1221,13 @@ class Conversation(containers.Vertical):
1168
1221
  entry.get("error", ""),
1169
1222
  )
1170
1223
 
1224
+ case "subagent_tool_call":
1225
+ sa = created_subagents.get(entry["id"])
1226
+ if sa is not None:
1227
+ await sa.add_or_update_tool_call(
1228
+ entry["tool_id"], entry["tool_call"]
1229
+ )
1230
+
1171
1231
  case "ask_user":
1172
1232
  current_thought = None
1173
1233
  self.new_block()
@@ -1178,11 +1238,50 @@ class Conversation(containers.Vertical):
1178
1238
  questions=entry.get("questions", []),
1179
1239
  ))
1180
1240
 
1241
+ if self._loaded_up_to > 0 and self._flush_append_at is None:
1242
+ load_more = LoadMoreIndicator(count=self._loaded_up_to)
1243
+ widgets.insert(0, load_more)
1244
+ self._load_more_widget = load_more
1245
+
1181
1246
  if widgets:
1182
- await self.contents.mount(*widgets)
1247
+ insert_before = self._flush_append_at
1248
+ if insert_before is not None:
1249
+ await self.contents.mount(*widgets, before=insert_before)
1250
+ self._loaded_up_to = max(0, self._loaded_up_to - len(widgets))
1251
+ if self._loaded_up_to > 0:
1252
+ lm = LoadMoreIndicator(count=self._loaded_up_to)
1253
+ self._load_more_widget = lm
1254
+ await self.contents.mount(lm, before=len(widgets))
1255
+ self._flush_append_at = None
1256
+ else:
1257
+ await self.contents.mount(*widgets)
1183
1258
  self._replay_buffer.clear()
1184
1259
  self._complete_thought()
1185
- self.window.scroll_end(animate=False)
1260
+ self.window.scroll_end_if_following(animate=False)
1261
+
1262
+ @on(LoadMoreIndicator.LoadMorePressed)
1263
+ async def on_load_more_pressed(self, message: LoadMoreIndicator.LoadMorePressed) -> None:
1264
+ message.stop()
1265
+ if self._loaded_up_to <= 0:
1266
+ return
1267
+ batch_size = 50
1268
+ start = max(0, self._loaded_up_to - batch_size)
1269
+ limit = self._loaded_up_to - start
1270
+ if limit <= 0:
1271
+ return
1272
+
1273
+ if self._load_more_widget is not None:
1274
+ await self._load_more_widget.remove()
1275
+ self._load_more_widget = None
1276
+
1277
+ loading = Static(" Loading earlier messages... ")
1278
+ await self.contents.mount(loading, before=0)
1279
+
1280
+ self._flush_append_at = 0
1281
+ self.post_message(acp_messages.SessionReplay(active=True))
1282
+ await self.agent.acp_load_earlier_messages(offset=start, limit=limit)
1283
+ self.post_message(acp_messages.SessionReplay(active=False))
1284
+ await loading.remove()
1186
1285
 
1187
1286
  @on(acp_messages.RequestPermission)
1188
1287
  async def on_acp_request_permission(self, message: acp_messages.RequestPermission):
@@ -1275,6 +1374,25 @@ class Conversation(containers.Vertical):
1275
1374
  return
1276
1375
  tool_id = message.tool_id
1277
1376
 
1377
+ # Tools invoked *inside* a subagent carry a ``subagentId`` (the parent
1378
+ # Spawn toolCallId). Render them as ToolCall cards INSIDE the owning
1379
+ # SubAgent widget (same style as main-conversation tools) instead of
1380
+ # the main conversation.
1381
+ subagent_id = tool_call.get("subagentId")
1382
+ if subagent_id:
1383
+ if self._replay:
1384
+ self._replay_buffer.append({
1385
+ "kind": "subagent_tool_call",
1386
+ "id": subagent_id,
1387
+ "tool_id": tool_id,
1388
+ "tool_call": tool_call,
1389
+ })
1390
+ return
1391
+ sa = self._get_subagent(subagent_id)
1392
+ if sa is not None:
1393
+ await sa.add_or_update_tool_call(tool_id, tool_call)
1394
+ return
1395
+
1278
1396
  if self._replay:
1279
1397
  if isinstance(message, acp_messages.ToolCall):
1280
1398
  self._replay_buffer.append({"kind": "tool_call", "tool_call": tool_call, "tool_id": tool_id})
@@ -1349,12 +1467,18 @@ class Conversation(containers.Vertical):
1349
1467
  from tui.widgets.subagent import SubAgent
1350
1468
 
1351
1469
  message.stop()
1470
+ logger.debug(
1471
+ "[TUI-SUBagent] on_start id=%s type=%s prompt_len=%d replay=%s",
1472
+ message.subagent_id, message.agent_type,
1473
+ len(message.prompt or ""), self._replay,
1474
+ )
1352
1475
  if self._replay:
1353
1476
  self._replay_buffer.append({"kind": "subagent_start", "agent_type": message.agent_type, "id": message.subagent_id, "prompt": message.prompt})
1354
1477
  return
1355
1478
  sa = SubAgent(message.agent_type, tool_id=message.subagent_id, prompt=message.prompt)
1356
1479
  self._active_subagents[message.subagent_id] = sa
1357
1480
  await self.post(sa, new_block=True)
1481
+ logger.debug("[TUI-SUBagent] on_start posted SubAgent id=%s", message.subagent_id)
1358
1482
 
1359
1483
  def _get_subagent(self, subagent_id: str):
1360
1484
  from tui.widgets.subagent import SubAgent
@@ -1370,10 +1494,19 @@ class Conversation(containers.Vertical):
1370
1494
  @on(acp_messages.SubAgentChunk)
1371
1495
  async def on_acp_subagent_chunk(self, message: acp_messages.SubAgentChunk):
1372
1496
  message.stop()
1497
+ sa = self._get_subagent(message.subagent_id)
1498
+ _cnt = (sa._chunk_count_log + 1) if sa else 0
1499
+ if sa:
1500
+ sa._chunk_count_log = _cnt
1501
+ if _cnt <= 5 or _cnt % 50 == 0:
1502
+ logger.debug(
1503
+ "[TUI-SUBagent] on_chunk id=%s chunk#%d bytes=%d sa=%s",
1504
+ message.subagent_id, _cnt, len(message.text or ""),
1505
+ "yes" if sa else "MISSING",
1506
+ )
1373
1507
  if self._replay:
1374
1508
  self._replay_buffer.append({"kind": "subagent_chunk", "id": message.subagent_id, "text": message.text})
1375
1509
  return
1376
- sa = self._get_subagent(message.subagent_id)
1377
1510
  if sa is not None:
1378
1511
  sa.append_chunk(message.text)
1379
1512
 
@@ -1394,6 +1527,16 @@ class Conversation(containers.Vertical):
1394
1527
  @on(acp_messages.SubAgentEnd)
1395
1528
  async def on_acp_subagent_end(self, message: acp_messages.SubAgentEnd):
1396
1529
  message.stop()
1530
+ logger.debug(
1531
+ "[TUI-SUBagent] on_end id=%s status=%s err=%r active=%s "
1532
+ "chunks=%d bytes=%d",
1533
+ message.subagent_id, message.status, (message.error or "")[:80],
1534
+ message.subagent_id in self._active_subagents,
1535
+ (self._get_subagent(message.subagent_id)._chunk_count_log
1536
+ if self._get_subagent(message.subagent_id) else 0),
1537
+ (self._get_subagent(message.subagent_id)._byte_count_log
1538
+ if self._get_subagent(message.subagent_id) else 0),
1539
+ )
1397
1540
  if self._replay:
1398
1541
  self._replay_buffer.append({
1399
1542
  "kind": "subagent_end",
@@ -2132,17 +2275,18 @@ class Conversation(containers.Vertical):
2132
2275
  cursor_block.block_cursor_down()
2133
2276
  self.refresh_block_cursor()
2134
2277
 
2135
- @work
2136
2278
  async def action_cancel(self) -> None:
2137
- if monotonic() - self._last_escape_time < 3:
2279
+ now = monotonic()
2280
+ if now - self._last_escape_time < 3:
2138
2281
  if (agent := self.agent) is not None:
2282
+ self._last_escape_time = 0.0
2139
2283
  if await agent.cancel():
2140
2284
  self.flash("Turn cancelled", style="success")
2141
2285
  else:
2142
2286
  self.flash("Agent declined to cancel. Please wait.", style="error")
2143
2287
  else:
2144
2288
  self.flash("Press [b]esc[/] again to cancel agent's turn")
2145
- self._last_escape_time = monotonic()
2289
+ self._last_escape_time = now
2146
2290
 
2147
2291
  def focus_prompt(self, reset_cursor: bool = True, scroll_end: bool = True) -> None:
2148
2292
  """Focus the prompt input.
@@ -2419,8 +2563,7 @@ class Conversation(containers.Vertical):
2419
2563
 
2420
2564
  if sub_cmd == "list":
2421
2565
  from pathlib import Path
2422
- from onecode.config import CLOUD_DEV_HARNESS_DIR
2423
- projects_dir = CLOUD_DEV_HARNESS_DIR / "projects"
2566
+ projects_dir = Path.home() / ".cdh" / "projects"
2424
2567
  if not projects_dir.exists():
2425
2568
  self.notify("No projects found", title="/project list")
2426
2569
  return True
@@ -2438,8 +2581,8 @@ class Conversation(containers.Vertical):
2438
2581
  self.notify("Project name required", title="/project load", severity="error")
2439
2582
  return True
2440
2583
  from pathlib import Path
2441
- from onecode.config import load_config, save_config, CLOUD_DEV_HARNESS_DIR
2442
- projects_dir = CLOUD_DEV_HARNESS_DIR / "projects"
2584
+ from onecode.config import load_config, save_config
2585
+ projects_dir = Path.home() / ".cdh" / "projects"
2443
2586
  project_file = None
2444
2587
  for ext in ["yaml", "yml", "json"]:
2445
2588
  pf = projects_dir / f"{name}.{ext}"
@@ -2466,11 +2609,11 @@ class Conversation(containers.Vertical):
2466
2609
  self.notify("Usage: /project new <name> [path]", title="/project new", severity="error")
2467
2610
  return True
2468
2611
  from pathlib import Path
2469
- from onecode.config import load_config, save_config, CLOUD_DEV_HARNESS_DIR
2612
+ from onecode.config import load_config, save_config
2470
2613
  from onecode.agent.cdh_loader import CdhProjectLoader
2471
2614
  from cdh.scaffold import scaffold_dlc_project
2472
2615
  import yaml
2473
- projects_dir = CLOUD_DEV_HARNESS_DIR / "projects"
2616
+ projects_dir = Path.home() / ".cdh" / "projects"
2474
2617
  projects_dir.mkdir(parents=True, exist_ok=True)
2475
2618
  project_path = path or str(Path.cwd())
2476
2619
  ws = Path(project_path).expanduser().resolve()
@@ -1,5 +1,15 @@
1
1
  from textual_diff_view import DiffView
2
2
 
3
+ # Workaround for textual_diff_view crash: DiffCode has ALLOW_SELECT = True
4
+ # but its parent chain is not a proper Widget, causing Textual's
5
+ # _forward_event to assert-fail on mouse move.
6
+ try:
7
+ from textual_diff_view._diff_view import DiffCode
8
+
9
+ DiffCode.ALLOW_SELECT = False
10
+ except ImportError:
11
+ pass
12
+
3
13
  from tui.app import A2TUIApp
4
14
 
5
15
 
@@ -0,0 +1,24 @@
1
+ from __future__ import annotations
2
+
3
+ from textual.binding import Binding
4
+ from textual.message import Message
5
+ from textual.widgets import Static
6
+
7
+
8
+ class LoadMoreIndicator(Static, can_focus=True):
9
+ """Clickable 'load earlier messages' indicator at top of conversation."""
10
+
11
+ class LoadMorePressed(Message):
12
+ pass
13
+
14
+ BINDINGS = [
15
+ Binding("enter", "press", "Load"),
16
+ ]
17
+
18
+ def __init__(self, count: int, batch: int = 50) -> None:
19
+ self.count = count
20
+ label = f" ▲ Load {min(count, batch)} earlier messages ({count} remaining) ▲ "
21
+ super().__init__(label)
22
+
23
+ def action_press(self) -> None:
24
+ self.post_message(self.LoadMorePressed())