@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.
- package/ai-dlc-skill/SKILL.md +4 -2
- package/ai-dlc-skill/agents/master.md +6 -0
- package/ai-dlc-skill/agents/plan-agent.md +2 -2
- package/ai-dlc-skill/agents/understand-agent.md +4 -4
- package/ai-dlc-skill/agents/verify-agent.md +1 -1
- package/ai-dlc-skill/architecture/project-structure.md +10 -10
- package/ai-dlc-skill/brownfield/README.md +2 -2
- package/ai-dlc-skill/brownfield/generate-context.sh +3 -3
- package/ai-dlc-skill/brownfield/scripts/extract-api.sh +5 -5
- package/ai-dlc-skill/components/backend.md +1 -1
- package/ai-dlc-skill/components/web.md +2 -2
- package/ai-dlc-skill/contracts/README.md +3 -3
- package/ai-dlc-skill/cross-tool/cline/export.sh +1 -1
- package/ai-dlc-skill/cross-tool/copilot/export.sh +1 -1
- package/ai-dlc-skill/cross-tool/onecode/export-skills.sh +7 -3
- package/ai-dlc-skill/cross-tool/onecode/export.sh +14 -8
- package/ai-dlc-skill/phases/deliver/lifecycle.md +6 -6
- package/ai-dlc-skill/phases/deliver/prompt.md +2 -2
- package/ai-dlc-skill/phases/deliver/rules.md +1 -1
- package/ai-dlc-skill/phases/plan/lifecycle.md +11 -11
- package/ai-dlc-skill/phases/plan/prompt.md +6 -6
- package/ai-dlc-skill/phases/plan/rules.md +1 -1
- package/ai-dlc-skill/phases/understand/entry.md +1 -1
- package/ai-dlc-skill/phases/understand/lifecycle.md +7 -7
- package/ai-dlc-skill/phases/understand/prompt.md +8 -8
- package/ai-dlc-skill/phases/understand/rules.md +5 -5
- package/ai-dlc-skill/phases/verify/lifecycle.md +24 -24
- package/ai-dlc-skill/phases/verify/prompt.md +8 -8
- package/ai-dlc-skill/phases/verify/rules.md +9 -9
- package/ai-dlc-skill/providers/README.md +2 -2
- package/ai-dlc-skill/providers/aliyun/preview.yaml +2 -2
- package/ai-dlc-skill/providers/tcb/preview.yaml +2 -2
- package/ai-dlc-skill/requirements.md +7 -7
- package/ai-dlc-skill/skill.yaml +4 -4
- package/ai-dlc-skill/templates/integration/CHANGELOG.md +2 -2
- package/ai-dlc-skill/templates/integration/contract-diff.md +7 -7
- package/ai-dlc-skill/templates/integration/contract-spec.md +7 -7
- package/ai-dlc-skill/templates/integration/runtime-contract.yaml +1 -1
- package/ai-dlc-skill/templates/project/README.md +18 -19
- package/ai-dlc-skill/templates/project/template.md +21 -21
- package/ai-dlc-skill/walkthrough/collect.sh +1 -1
- package/ai-dlc-skill/walkthrough/template.md +2 -2
- package/ai-dlc-skill/workflows/ai-dlc.yaml +12 -12
- package/ai-dlc-skill/workflows/deployment.yaml +4 -4
- package/cdh/__pycache__/cdh_mcp_injector.cpython-314.pyc +0 -0
- package/cdh/__pycache__/cdh_mcp_loader.cpython-314.pyc +0 -0
- package/cdh/__pycache__/cdh_mcp_manager.cpython-314.pyc +0 -0
- package/cdh/__pycache__/cdh_session_aggregator.cpython-314.pyc +0 -0
- package/cdh/__pycache__/cdh_skill_loader.cpython-314.pyc +0 -0
- package/cdh/__pycache__/cdh_skill_manager.cpython-314.pyc +0 -0
- package/cdh/__pycache__/cli.cpython-313.pyc +0 -0
- package/cdh/__pycache__/cli.cpython-314.pyc +0 -0
- package/cdh/__pycache__/scaffold.cpython-314.pyc +0 -0
- package/cdh/cdh_mcp_injector.py +115 -0
- package/cdh/cdh_mcp_loader.py +67 -0
- package/cdh/cdh_mcp_manager.py +73 -0
- package/cdh/cdh_session_aggregator.py +258 -0
- package/cdh/cdh_skill_manager.py +105 -0
- package/cdh/cli.py +64 -4
- package/cdh/scaffold.py +192 -41
- package/onecode/__init__.py +1 -1
- package/onecode/__pycache__/cli.cpython-314.pyc +0 -0
- package/onecode/__pycache__/commands.cpython-314.pyc +0 -0
- package/onecode/__pycache__/config.cpython-314.pyc +0 -0
- package/onecode/__pycache__/config_screen.cpython-314.pyc +0 -0
- package/onecode/__pycache__/migrate.cpython-314.pyc +0 -0
- package/onecode/agent/__pycache__/cdh_loader.cpython-314.pyc +0 -0
- package/onecode/agent/__pycache__/context.cpython-314.pyc +0 -0
- package/onecode/agent/__pycache__/engine.cpython-314.pyc +0 -0
- package/onecode/agent/__pycache__/hooks.cpython-314.pyc +0 -0
- package/onecode/agent/__pycache__/onecode_agent_acp.cpython-314.pyc +0 -0
- package/onecode/agent/__pycache__/permissions.cpython-314.pyc +0 -0
- package/onecode/agent/__pycache__/project_doc.cpython-314.pyc +0 -0
- package/onecode/agent/__pycache__/session.cpython-314.pyc +0 -0
- package/onecode/agent/agents/__pycache__/types.cpython-314.pyc +0 -0
- package/onecode/agent/agents/types.py +47 -164
- package/onecode/agent/attachments.py +0 -1
- package/onecode/agent/cdh_loader.py +17 -25
- package/onecode/agent/context.py +8 -3
- package/onecode/agent/engine.py +835 -220
- package/onecode/agent/hooks.py +1 -1
- package/onecode/agent/onecode_agent_acp.py +460 -47
- package/onecode/agent/onecode_agent_acp.py.bak +2447 -0
- package/onecode/agent/permissions.py +0 -1
- package/onecode/agent/project_doc.py +19 -0
- package/onecode/agent/session.py +17 -3
- package/onecode/agent/snapshot.py +3 -4
- package/onecode/agent/tools/__pycache__/agent_tools.cpython-314.pyc +0 -0
- package/onecode/agent/tools/__pycache__/apply_patch_tool.cpython-314.pyc +0 -0
- package/onecode/agent/tools/__pycache__/bash_tool.cpython-314.pyc +0 -0
- package/onecode/agent/tools/__pycache__/communication_tools.cpython-314.pyc +0 -0
- package/onecode/agent/tools/__pycache__/config_tool.cpython-314.pyc +0 -0
- package/onecode/agent/tools/__pycache__/cron_tools.cpython-314.pyc +0 -0
- package/onecode/agent/tools/__pycache__/file_ops.cpython-314.pyc +0 -0
- package/onecode/agent/tools/__pycache__/file_tools.cpython-314.pyc +0 -0
- package/onecode/agent/tools/__pycache__/git_tools.cpython-314.pyc +0 -0
- package/onecode/agent/tools/__pycache__/lsp_tools.cpython-314.pyc +0 -0
- package/onecode/agent/tools/__pycache__/mcp_tools.cpython-314.pyc +0 -0
- package/onecode/agent/tools/__pycache__/permission_handler.cpython-314.pyc +0 -0
- package/onecode/agent/tools/__pycache__/protocol.cpython-314.pyc +0 -0
- package/onecode/agent/tools/__pycache__/registry.cpython-314.pyc +0 -0
- package/onecode/agent/tools/__pycache__/sandbox.cpython-314.pyc +0 -0
- package/onecode/agent/tools/__pycache__/skill_tools.cpython-314.pyc +0 -0
- package/onecode/agent/tools/__pycache__/todo_tools.cpython-314.pyc +0 -0
- package/onecode/agent/tools/__pycache__/web_tools.cpython-314.pyc +0 -0
- package/onecode/agent/tools/agent_tools.py +4 -6
- package/onecode/agent/tools/apply_patch_tool.py +2 -8
- package/onecode/agent/tools/bash_tool.py +32 -12
- package/onecode/agent/tools/communication_tools.py +4 -5
- package/onecode/agent/tools/config_tool.py +3 -3
- package/onecode/agent/tools/cron_tools.py +4 -5
- package/onecode/agent/tools/file_ops.py +37 -3
- package/onecode/agent/tools/file_tools.py +10 -11
- package/onecode/agent/tools/git_tools.py +2 -2
- package/onecode/agent/tools/lsp_tools.py +2 -2
- package/onecode/agent/tools/mcp_tools.py +3 -5
- package/onecode/agent/tools/permission_handler.py +1 -1
- package/onecode/agent/tools/protocol.py +1 -1
- package/onecode/agent/tools/registry.py +69 -2
- package/onecode/agent/tools/sandbox.py +110 -16
- package/onecode/agent/tools/skill_tools.py +2 -2
- package/onecode/agent/tools/todo_tools.py +38 -19
- package/onecode/agent/tools/web_tools.py +6 -7
- package/onecode/builtin_skills/agent-browser/SKILL.md +212 -0
- package/onecode/builtin_skills/agent-browser/skill.yaml +8 -0
- package/onecode/builtin_skills/skill-creator/SKILL.md +106 -0
- package/onecode/builtin_skills/skill-creator/skill.yaml +8 -0
- package/onecode/cli.py +186 -6
- package/onecode/codebase/__pycache__/chunker.cpython-314.pyc +0 -0
- package/onecode/codebase/__pycache__/indexer.cpython-314.pyc +0 -0
- package/onecode/codebase/__pycache__/retriever.cpython-314.pyc +0 -0
- package/onecode/codebase/__pycache__/storage.cpython-314.pyc +0 -0
- package/onecode/codebase/chunker.py +0 -5
- package/onecode/codebase/indexer.py +14 -1
- package/onecode/codebase/retriever.py +13 -2
- package/onecode/codebase/storage.py +5 -4
- package/onecode/commands.py +81 -6
- package/onecode/config.py +41 -12
- package/onecode/config_screen.py +7 -3
- package/onecode/mcp/__pycache__/manager.cpython-314.pyc +0 -0
- package/onecode/mcp/manager.py +2 -3
- package/onecode/memory/__init__.py +7 -53
- package/onecode/memory/__pycache__/__init__.cpython-314.pyc +0 -0
- package/onecode/memory/__pycache__/backend.cpython-314.pyc +0 -0
- package/onecode/memory/__pycache__/offload.cpython-314.pyc +0 -0
- package/onecode/memory/__pycache__/pyramid.cpython-314.pyc +0 -0
- package/onecode/memory/__pycache__/recall.cpython-314.pyc +0 -0
- package/onecode/memory/backend.py +13 -39
- package/onecode/memory/pyramid.py +2 -71
- package/onecode/memory/recall.py +38 -3
- package/onecode/migrate.py +122 -0
- package/onecode/models/__pycache__/messages.cpython-314.pyc +0 -0
- package/onecode/models/__pycache__/provider.cpython-314.pyc +0 -0
- package/onecode/models/messages.py +58 -0
- package/onecode/models/provider.py +20 -2
- package/onecode/models/providers/__pycache__/anthropic_provider.cpython-314.pyc +0 -0
- package/onecode/models/providers/__pycache__/glm_provider.cpython-314.pyc +0 -0
- package/onecode/models/providers/__pycache__/minimax_provider.cpython-314.pyc +0 -0
- package/onecode/models/providers/__pycache__/minimaxi_provider.cpython-314.pyc +0 -0
- package/onecode/models/providers/__pycache__/ollama_provider.cpython-314.pyc +0 -0
- package/onecode/models/providers/anthropic_provider.py +59 -12
- package/onecode/models/providers/glm_provider.py +40 -0
- package/onecode/models/providers/minimax_provider.py +3 -0
- package/onecode/models/providers/minimaxi_provider.py +3 -0
- package/onecode/models/providers/ollama_provider.py +3 -0
- package/onecode/server/app.py +1 -1
- package/onecode/skills/__pycache__/argument_substitution.cpython-314.pyc +0 -0
- package/onecode/skills/__pycache__/bootstrap.cpython-314.pyc +0 -0
- package/onecode/skills/__pycache__/loader.cpython-314.pyc +0 -0
- package/onecode/skills/__pycache__/manager.cpython-314.pyc +0 -0
- package/onecode/skills/argument_substitution.py +0 -1
- package/onecode/skills/bootstrap.py +124 -0
- package/onecode/skills/loader.py +35 -53
- package/onecode/skills/manager.py +2 -2
- package/onecode/storage/activity.py +3 -4
- package/onecode/storage/session.py +3 -4
- package/onecode/tasks/manager.py +23 -10
- package/onecode/trace/tracer.py +2 -3
- package/package.json +1 -1
- package/pyproject.toml +5 -1
- package/run.js +68 -14
- package/tui/__pycache__/about.cpython-314.pyc +0 -0
- package/tui/__pycache__/app.cpython-313.pyc +0 -0
- package/tui/__pycache__/app.cpython-314.pyc +0 -0
- package/tui/__pycache__/danger.cpython-314.pyc +0 -0
- package/tui/__pycache__/directory_suggester.cpython-314.pyc +0 -0
- package/tui/__pycache__/messages.cpython-314.pyc +0 -0
- package/tui/__pycache__/path_complete.cpython-314.pyc +0 -0
- package/tui/__pycache__/paths.cpython-314.pyc +0 -0
- package/tui/__pycache__/session_tracker.cpython-314.pyc +0 -0
- package/tui/__pycache__/settings_schema.cpython-314.pyc +0 -0
- package/tui/acp/__pycache__/agent.cpython-314.pyc +0 -0
- package/tui/acp/__pycache__/api.cpython-314.pyc +0 -0
- package/tui/acp/__pycache__/messages.cpython-314.pyc +0 -0
- package/tui/acp/__pycache__/prompt.cpython-314.pyc +0 -0
- package/tui/acp/__pycache__/protocol.cpython-314.pyc +0 -0
- package/tui/acp/agent.py +125 -58
- package/tui/acp/api.py +12 -0
- package/tui/acp/messages.py +3 -1
- package/tui/acp/prompt.py +0 -57
- package/tui/acp/protocol.py +6 -0
- package/tui/app.py +11 -33
- package/tui/messages.py +2 -0
- package/tui/path_complete.py +1 -1
- package/tui/paths.py +16 -0
- package/tui/screens/__pycache__/agent_modal.cpython-314.pyc +0 -0
- package/tui/screens/__pycache__/component_picker.cpython-314.pyc +0 -0
- package/tui/screens/__pycache__/diff_screen.cpython-314.pyc +0 -0
- package/tui/screens/__pycache__/log.cpython-314.pyc +0 -0
- package/tui/screens/__pycache__/main.cpython-313.pyc +0 -0
- package/tui/screens/__pycache__/main.cpython-314.pyc +0 -0
- package/tui/screens/__pycache__/permissions.cpython-314.pyc +0 -0
- package/tui/screens/__pycache__/projects_app.cpython-314.pyc +0 -0
- package/tui/screens/__pycache__/projects_screen.cpython-314.pyc +0 -0
- package/tui/screens/__pycache__/sessions.cpython-314.pyc +0 -0
- package/tui/screens/__pycache__/store.cpython-314.pyc +0 -0
- package/tui/screens/__pycache__/tool_content_screen.cpython-314.pyc +0 -0
- package/tui/screens/component_picker.py +35 -15
- package/tui/screens/diff_screen.py +84 -0
- package/tui/screens/log.py +25 -25
- package/tui/screens/main.py +48 -26
- package/tui/screens/projects.tcss +5 -1
- package/tui/screens/projects_app.py +6 -6
- package/tui/screens/projects_screen.py +19 -39
- package/tui/screens/sessions.py +3 -5
- package/tui/session_tracker.py +3 -0
- package/tui/settings_schema.py +1 -1
- package/tui/visuals/__pycache__/columns.cpython-314.pyc +0 -0
- package/tui/visuals/columns.py +1 -2
- package/tui/widgets/__pycache__/acp_content.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/agent_response.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/agent_thought.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/conversation.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/diff_view.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/directory_input.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/future_text.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/grid_select.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/load_more.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/mandelbrot.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/markdown_note.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/modified_files.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/plan.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/project_grid_select.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/project_summary.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/session_grid_select.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/session_summary.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/shell_result.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/side_bar.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/subagent.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/subagent_screen.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/terminal.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/tool_call.cpython-314.pyc +0 -0
- package/tui/widgets/agent_response.py +0 -1
- package/tui/widgets/agent_thought.py +0 -1
- package/tui/widgets/conversation.py +157 -14
- package/tui/widgets/diff_view.py +10 -0
- package/tui/widgets/load_more.py +24 -0
- package/tui/widgets/modified_files.py +86 -12
- package/tui/widgets/plan.py +1 -1
- package/tui/widgets/project_grid_select.py +9 -2
- package/tui/widgets/session_grid_select.py +6 -0
- package/tui/widgets/side_bar.py +10 -0
- package/tui/widgets/subagent.py +92 -58
- package/tui/widgets/subagent_screen.py +638 -115
- package/tui/widgets/terminal.py +1 -2
- package/tui/widgets/terminal_tool.py +11 -2
- package/tui/widgets/tool_call.py +35 -2
- package/onecode/memory/offload.py +0 -114
- package/onecode/memory/symbolic.py +0 -164
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
|
2442
|
-
projects_dir =
|
|
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
|
|
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 =
|
|
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()
|
package/tui/widgets/diff_view.py
CHANGED
|
@@ -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())
|
|
@@ -5,11 +5,16 @@ import subprocess
|
|
|
5
5
|
|
|
6
6
|
from textual import work
|
|
7
7
|
from textual.app import ComposeResult
|
|
8
|
+
from textual.binding import Binding
|
|
9
|
+
from textual.message import Message
|
|
8
10
|
from textual.reactive import reactive
|
|
9
11
|
from textual.timer import Timer
|
|
12
|
+
from textual.widget import Widget
|
|
10
13
|
from textual.widgets import Static
|
|
11
14
|
from textual import containers
|
|
12
15
|
|
|
16
|
+
from dataclasses import dataclass
|
|
17
|
+
|
|
13
18
|
|
|
14
19
|
CONTENT_WIDTH = 35
|
|
15
20
|
|
|
@@ -55,13 +60,17 @@ def _tag_color(added: int, deleted: int) -> str:
|
|
|
55
60
|
return DIFF_COLORS["dim"]
|
|
56
61
|
|
|
57
62
|
|
|
58
|
-
class ModifiedFiles(containers.Vertical):
|
|
63
|
+
class ModifiedFiles(containers.Vertical, can_focus=True):
|
|
59
64
|
DEFAULT_CSS = """
|
|
60
65
|
ModifiedFiles {
|
|
61
66
|
height: auto;
|
|
62
67
|
overflow-y: auto;
|
|
63
68
|
padding: 0 0 0 0;
|
|
64
69
|
|
|
70
|
+
&:focus {
|
|
71
|
+
background-tint: $surface 10%;
|
|
72
|
+
}
|
|
73
|
+
|
|
65
74
|
#mf-files {
|
|
66
75
|
height: auto;
|
|
67
76
|
padding: 0 0 0 1;
|
|
@@ -69,8 +78,24 @@ class ModifiedFiles(containers.Vertical):
|
|
|
69
78
|
}
|
|
70
79
|
"""
|
|
71
80
|
|
|
81
|
+
BINDINGS = [
|
|
82
|
+
Binding("up", "cursor_up", "Up", group="Files"),
|
|
83
|
+
Binding("down", "cursor_down", "Down", group="Files"),
|
|
84
|
+
Binding("enter", "select", "Select"),
|
|
85
|
+
]
|
|
86
|
+
|
|
87
|
+
@dataclass
|
|
88
|
+
class FileSelected(Message):
|
|
89
|
+
filepath: str
|
|
90
|
+
modified_files: "ModifiedFiles"
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def control(self) -> Widget:
|
|
94
|
+
return self.modified_files
|
|
95
|
+
|
|
72
96
|
path: reactive[Path | None] = reactive(None)
|
|
73
97
|
files: reactive[list[str]] = reactive(list, recompose=False)
|
|
98
|
+
highlighted: reactive[int | None] = reactive(None)
|
|
74
99
|
|
|
75
100
|
def __init__(
|
|
76
101
|
self,
|
|
@@ -84,6 +109,8 @@ class ModifiedFiles(containers.Vertical):
|
|
|
84
109
|
self._status_kind: str = "no-changes"
|
|
85
110
|
self._status_text: str = NO_CHANGES_TEXT
|
|
86
111
|
self._initial_path: Path | None = path
|
|
112
|
+
self._filepaths: list[str] = []
|
|
113
|
+
self._lines_data: tuple[list[str], dict[str, tuple[int, int]]] | None = None
|
|
87
114
|
|
|
88
115
|
def on_mount(self) -> None:
|
|
89
116
|
initial = self._initial_path
|
|
@@ -93,17 +120,12 @@ class ModifiedFiles(containers.Vertical):
|
|
|
93
120
|
|
|
94
121
|
def watch_path(self, path: Path | None) -> None:
|
|
95
122
|
self._cancel_debounce()
|
|
96
|
-
|
|
97
|
-
self._show_status("no-changes", NO_CHANGES_TEXT)
|
|
98
|
-
return
|
|
99
|
-
self._show_status("-loading", LOADING_TEXT)
|
|
100
|
-
self._schedule_run(path)
|
|
123
|
+
self._schedule_run(path or Path.cwd())
|
|
101
124
|
|
|
102
125
|
def refresh_files(self) -> None:
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
self._run_git_status(self.path)
|
|
126
|
+
target = self.path or Path.cwd()
|
|
127
|
+
self._cancel_debounce()
|
|
128
|
+
self._run_git_status(target)
|
|
107
129
|
|
|
108
130
|
def _schedule_run(self, path: Path) -> None:
|
|
109
131
|
timer = self.set_timer(
|
|
@@ -184,13 +206,33 @@ class ModifiedFiles(containers.Vertical):
|
|
|
184
206
|
def _render_lines(self, data: tuple[list[str], dict[str, tuple[int, int]]]) -> None:
|
|
185
207
|
lines, diffmap = data
|
|
186
208
|
self.files = list(lines)
|
|
209
|
+
self._filepaths = [l[3:] for l in lines]
|
|
187
210
|
if not lines:
|
|
188
211
|
self._show_status("no-changes", NO_CHANGES_TEXT)
|
|
212
|
+
self._lines_data = None
|
|
213
|
+
self.highlighted = None
|
|
189
214
|
return
|
|
190
215
|
self._show_status("file-modified", "")
|
|
216
|
+
self._lines_data = data
|
|
217
|
+
if self.highlighted is None or self.highlighted >= len(self._filepaths):
|
|
218
|
+
self.highlighted = 0
|
|
219
|
+
self._apply_rendering()
|
|
191
220
|
|
|
221
|
+
def on_focus(self) -> None:
|
|
222
|
+
if self.highlighted is None and self._filepaths:
|
|
223
|
+
self.highlighted = 0
|
|
224
|
+
|
|
225
|
+
def watch_highlighted(
|
|
226
|
+
self, old: int | None, highlighted: int | None
|
|
227
|
+
) -> None:
|
|
228
|
+
if self._lines_data is not None:
|
|
229
|
+
self._apply_rendering()
|
|
230
|
+
|
|
231
|
+
def _apply_rendering(self) -> None:
|
|
232
|
+
assert self._lines_data is not None
|
|
233
|
+
lines, diffmap = self._lines_data
|
|
192
234
|
out: list[str] = []
|
|
193
|
-
for raw_line in lines:
|
|
235
|
+
for idx, raw_line in enumerate(lines):
|
|
194
236
|
filepath = raw_line[3:]
|
|
195
237
|
icon, sc = _status_info(raw_line[:2])
|
|
196
238
|
|
|
@@ -216,10 +258,42 @@ class ModifiedFiles(containers.Vertical):
|
|
|
216
258
|
disp = filepath[:max_fn - 1] + "…" if len(filepath) > max_fn else filepath
|
|
217
259
|
pad = CONTENT_WIDTH - 2 - len(disp) - tag_len
|
|
218
260
|
pad_s = " " * pad if pad > 0 else ""
|
|
219
|
-
|
|
261
|
+
line = f"[{sc}]{icon} {disp}[/]{pad_s}{tag_s}"
|
|
262
|
+
|
|
263
|
+
if idx == self.highlighted:
|
|
264
|
+
line = f"[reverse]{line}[/]"
|
|
265
|
+
out.append(line)
|
|
220
266
|
|
|
221
267
|
files_widget = self.query_one("#mf-files", Static)
|
|
222
268
|
files_widget.update("\n".join(out))
|
|
269
|
+
self.call_after_refresh(self._scroll_to_highlighted)
|
|
270
|
+
|
|
271
|
+
def _scroll_to_highlighted(self) -> None:
|
|
272
|
+
if self.highlighted is not None:
|
|
273
|
+
self.scroll_to(y=self.highlighted, animate=False)
|
|
274
|
+
|
|
275
|
+
def action_cursor_up(self) -> None:
|
|
276
|
+
if not self._filepaths:
|
|
277
|
+
return
|
|
278
|
+
if self.highlighted is None or self.highlighted <= 0:
|
|
279
|
+
self.highlighted = 0
|
|
280
|
+
else:
|
|
281
|
+
self.highlighted -= 1
|
|
282
|
+
|
|
283
|
+
def action_cursor_down(self) -> None:
|
|
284
|
+
if not self._filepaths:
|
|
285
|
+
return
|
|
286
|
+
if self.highlighted is None:
|
|
287
|
+
self.highlighted = 0
|
|
288
|
+
elif self.highlighted >= len(self._filepaths) - 1:
|
|
289
|
+
self.highlighted = len(self._filepaths) - 1
|
|
290
|
+
else:
|
|
291
|
+
self.highlighted += 1
|
|
292
|
+
|
|
293
|
+
def action_select(self) -> None:
|
|
294
|
+
if self.highlighted is not None and self._filepaths:
|
|
295
|
+
filepath = self._filepaths[self.highlighted]
|
|
296
|
+
self.post_message(self.FileSelected(filepath, self))
|
|
223
297
|
|
|
224
298
|
def _show_status(self, kind: str, text: str) -> None:
|
|
225
299
|
self._status_kind = kind
|
package/tui/widgets/plan.py
CHANGED
|
@@ -7,7 +7,6 @@ from textual import getters
|
|
|
7
7
|
from tui.app import A2TUIApp
|
|
8
8
|
from tui.widgets.grid_select import GridSelect
|
|
9
9
|
from tui.widgets.project_summary import ProjectSummary
|
|
10
|
-
from onecode.config import CLOUD_DEV_HARNESS_DIR
|
|
11
10
|
|
|
12
11
|
|
|
13
12
|
def _read_project_path(pf: Path) -> str:
|
|
@@ -47,11 +46,19 @@ class ProjectGridSelect(GridSelect):
|
|
|
47
46
|
return True
|
|
48
47
|
|
|
49
48
|
def compose(self) -> ComposeResult:
|
|
50
|
-
projects_dir =
|
|
49
|
+
projects_dir = Path.home() / ".cdh" / "projects"
|
|
51
50
|
projects_dir.mkdir(parents=True, exist_ok=True)
|
|
52
51
|
project_files = list(projects_dir.glob("*.yaml")) + list(projects_dir.glob("*.json"))
|
|
53
52
|
for pf in sorted(project_files):
|
|
54
53
|
yield ProjectSummary(pf.stem, _read_project_path(pf), id=pf.stem)
|
|
55
54
|
|
|
55
|
+
async def reload(self) -> None:
|
|
56
|
+
await self.remove_children()
|
|
57
|
+
projects_dir = Path.home() / ".cdh" / "projects"
|
|
58
|
+
projects_dir.mkdir(parents=True, exist_ok=True)
|
|
59
|
+
project_files = list(projects_dir.glob("*.yaml")) + list(projects_dir.glob("*.json"))
|
|
60
|
+
for pf in sorted(project_files):
|
|
61
|
+
await self.mount(ProjectSummary(pf.stem, _read_project_path(pf), id=pf.stem))
|
|
62
|
+
|
|
56
63
|
def refresh(self, **kwargs) -> None:
|
|
57
64
|
super().refresh(**kwargs)
|
|
@@ -111,6 +111,12 @@ class SessionGridSelect(GridSelect):
|
|
|
111
111
|
break
|
|
112
112
|
return
|
|
113
113
|
|
|
114
|
+
for i, existing in enumerate(self._all_sessions):
|
|
115
|
+
if existing.mode_name == mode_name:
|
|
116
|
+
self._all_sessions[i] = details
|
|
117
|
+
self._render_page()
|
|
118
|
+
return
|
|
119
|
+
|
|
114
120
|
if details.session_pk is not None:
|
|
115
121
|
for i, existing in enumerate(self._all_sessions):
|
|
116
122
|
if existing.session_pk == details.session_pk:
|
package/tui/widgets/side_bar.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
2
|
|
|
3
3
|
from textual.app import ComposeResult
|
|
4
|
+
from textual.binding import Binding
|
|
4
5
|
from textual.widget import Widget
|
|
5
6
|
from textual import containers
|
|
6
7
|
from textual import widgets
|
|
@@ -9,6 +10,9 @@ from textual.message import Message
|
|
|
9
10
|
|
|
10
11
|
class SideBarCollapsible(widgets.Collapsible):
|
|
11
12
|
BINDING_GROUP_TITLE = "Sidebar collapsible"
|
|
13
|
+
BINDINGS = [
|
|
14
|
+
Binding("down", "focus_content", "Focus content", show=False),
|
|
15
|
+
]
|
|
12
16
|
HELP = """\
|
|
13
17
|
## Sidebar
|
|
14
18
|
|
|
@@ -20,6 +24,12 @@ The Sidebar contains additonal information associated with the conversation.
|
|
|
20
24
|
- **enter** expand or collapse secions
|
|
21
25
|
"""
|
|
22
26
|
|
|
27
|
+
def action_focus_content(self) -> None:
|
|
28
|
+
"""Move focus into the content area when expanded."""
|
|
29
|
+
if self.collapsed:
|
|
30
|
+
return
|
|
31
|
+
self.screen.focus_next()
|
|
32
|
+
|
|
23
33
|
|
|
24
34
|
class SideBar(containers.Vertical):
|
|
25
35
|
BINDING_GROUP_TITLE = "Sidebar"
|