@scemoon/cdh 1.0.0 → 1.0.3
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/cdh/__pycache__/__main__.cpython-314.pyc +0 -0
- package/cdh/__pycache__/cli.cpython-314.pyc +0 -0
- package/cdh/__pycache__/scaffold.cpython-314.pyc +0 -0
- package/cdh/cli.py +473 -87
- package/cdh/scaffold.py +386 -121
- 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/agent/__pycache__/cdh_loader.cpython-314.pyc +0 -0
- package/onecode/agent/__pycache__/engine.cpython-314.pyc +0 -0
- package/onecode/agent/__pycache__/onecode_agent_acp.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 +123 -79
- package/onecode/agent/cdh_loader.py +26 -13
- package/onecode/agent/engine.py +426 -235
- package/onecode/agent/onecode_agent_acp.py +200 -73
- package/onecode/agent/session.py +8 -13
- package/onecode/agent/tools/__pycache__/agent_tools.cpython-314.pyc +0 -0
- package/onecode/agent/tools/__pycache__/task_tools.cpython-314.pyc +0 -0
- package/onecode/agent/tools/__pycache__/todo_tools.cpython-314.pyc +0 -0
- package/onecode/agent/tools/agent_tools.py +4 -4
- package/onecode/agent/tools/todo_tools.py +188 -0
- package/onecode/cli.py +3 -3
- package/onecode/commands.py +439 -194
- package/onecode/config.py +3 -3
- package/onecode/config_screen.py +4 -2
- package/onecode/models/__pycache__/errors.cpython-314.pyc +0 -0
- package/onecode/models/__pycache__/messages.cpython-314.pyc +0 -0
- package/onecode/models/__pycache__/provider.cpython-314.pyc +0 -0
- package/onecode/models/errors.py +11 -1
- package/onecode/models/messages.py +35 -16
- package/onecode/models/provider.py +243 -12
- package/onecode/models/providers/__pycache__/anthropic_provider.cpython-314.pyc +0 -0
- package/onecode/models/providers/__pycache__/deepseek_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/__pycache__/openai_provider.cpython-314.pyc +0 -0
- package/onecode/models/providers/anthropic_provider.py +4 -2
- package/onecode/models/providers/deepseek_provider.py +2 -0
- package/onecode/models/providers/glm_provider.py +2 -1
- package/onecode/models/providers/minimax_provider.py +2 -1
- package/onecode/models/providers/minimaxi_provider.py +76 -25
- package/onecode/models/providers/ollama_provider.py +2 -1
- package/onecode/models/providers/openai_provider.py +4 -0
- package/onecode/skills/__pycache__/manager.cpython-314.pyc +0 -0
- package/onecode/tasks/__pycache__/__init__.cpython-314.pyc +0 -0
- package/onecode/tasks/__pycache__/manager.cpython-314.pyc +0 -0
- package/onecode/tasks/__pycache__/models.cpython-314.pyc +0 -0
- package/onecode/tasks/manager.py +1 -1
- package/package.json +1 -1
- package/pyproject.toml +10 -0
- package/run.js +24 -7
- package/tui/__pycache__/app.cpython-314.pyc +0 -0
- package/tui/__pycache__/cli.cpython-314.pyc +0 -0
- package/tui/__pycache__/db.cpython-314.pyc +0 -0
- package/tui/__pycache__/directory_suggester.cpython-314.pyc +0 -0
- package/tui/__pycache__/directory_watcher.cpython-314.pyc +0 -0
- package/tui/__pycache__/messages.cpython-314.pyc +0 -0
- package/tui/__pycache__/paths.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 +60 -11
- package/tui/acp/api.py +6 -0
- package/tui/acp/messages.py +11 -0
- package/tui/acp/prompt.py +1 -1
- package/tui/acp/protocol.py +31 -0
- package/tui/app.py +60 -8
- package/tui/db.py +23 -0
- package/tui/directory_watcher.py +35 -1
- package/tui/messages.py +4 -1
- package/tui/paths.py +3 -3
- 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__/log.cpython-314.pyc +0 -0
- package/tui/screens/__pycache__/main.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__/settings.cpython-314.pyc +0 -0
- package/tui/screens/__pycache__/store.cpython-314.pyc +0 -0
- package/tui/screens/component_picker.py +148 -0
- package/tui/screens/main.py +47 -95
- package/tui/screens/projects.tcss +10 -0
- package/tui/screens/projects_app.py +33 -5
- package/tui/screens/projects_screen.py +171 -9
- package/tui/widgets/__pycache__/acp_content.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/ask_user.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__/mandelbrot.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_directory_tree.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/project_grid_select.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/question.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__/tool_call.cpython-314.pyc +0 -0
- package/tui/widgets/ask_user.py +1 -1
- package/tui/widgets/conversation.py +99 -42
- package/tui/widgets/modified_files.py +155 -23
- package/tui/widgets/plan.py +22 -0
- package/tui/widgets/project_directory_tree.py +22 -10
- package/tui/widgets/project_grid_select.py +23 -1
- package/tui/widgets/question.py +1 -1
- package/tui/widgets/subagent.py +202 -36
- package/tui/widgets/subagent_screen.py +186 -0
- package/tui/widgets/tool_call.py +5 -8
- package/onecode/agent/tools/task_tools.py +0 -249
package/tui/app.py
CHANGED
|
@@ -363,6 +363,7 @@ class A2TUIApp(App, inherit_bindings=False):
|
|
|
363
363
|
session_id = getattr(conv.agent, "session_id", None) or conv._agent_session_id or ""
|
|
364
364
|
self._exit_metrics = {
|
|
365
365
|
"session_id": session_id,
|
|
366
|
+
"session_pk": conv._session_pk,
|
|
366
367
|
"tool_call_total": conv._tool_call_total,
|
|
367
368
|
"tool_call_success": conv._tool_call_success,
|
|
368
369
|
"tool_call_failed": conv._tool_call_failed,
|
|
@@ -876,11 +877,16 @@ class A2TUIApp(App, inherit_bindings=False):
|
|
|
876
877
|
)
|
|
877
878
|
return
|
|
878
879
|
if agent_identity:
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
880
|
+
try:
|
|
881
|
+
from tui.agents import read_agents
|
|
882
|
+
if agent_identity in await read_agents():
|
|
883
|
+
self.launch_agent(
|
|
884
|
+
agent_identity,
|
|
885
|
+
agent_session_id=agent_session_id,
|
|
886
|
+
)
|
|
887
|
+
return
|
|
888
|
+
except Exception:
|
|
889
|
+
pass
|
|
884
890
|
|
|
885
891
|
if agent_identity := self._resolve_launch_agent():
|
|
886
892
|
self.launch_agent(agent_identity, project_path=project_path)
|
|
@@ -999,9 +1005,27 @@ class A2TUIApp(App, inherit_bindings=False):
|
|
|
999
1005
|
except Exception:
|
|
1000
1006
|
pass
|
|
1001
1007
|
sid = (self._exit_metrics.get("session_id", "") or "")[:36] if self._exit_metrics else ""
|
|
1008
|
+
session_pk = self._exit_metrics.get("session_pk") if self._exit_metrics else None
|
|
1009
|
+
# Format the session ID for display: prefer the short `cdh
|
|
1010
|
+
# session list` form (`session-<pk>` + agent_session_id prefix)
|
|
1011
|
+
# so the displayed value matches what users see in
|
|
1012
|
+
# `cdh session list` and what they pass to `cdh session load`.
|
|
1013
|
+
if session_pk is not None:
|
|
1014
|
+
display_id = f"session-{session_pk} ({sid[:8]})" if sid else f"session-{session_pk}"
|
|
1015
|
+
hint = f" Reload with: cdh session load {session_pk}\n"
|
|
1016
|
+
elif sid:
|
|
1017
|
+
display_id = sid
|
|
1018
|
+
hint = ""
|
|
1019
|
+
else:
|
|
1020
|
+
display_id = "-"
|
|
1021
|
+
hint = ""
|
|
1002
1022
|
try:
|
|
1003
1023
|
with open("/dev/tty", "w") as tty:
|
|
1004
|
-
tty.write(
|
|
1024
|
+
tty.write(
|
|
1025
|
+
f"\n CDH powering down. Goodbye!\n"
|
|
1026
|
+
f" Session ID: {display_id}\n"
|
|
1027
|
+
f"{hint}"
|
|
1028
|
+
)
|
|
1005
1029
|
except Exception:
|
|
1006
1030
|
pass
|
|
1007
1031
|
import os
|
|
@@ -1079,10 +1103,23 @@ class A2TUIApp(App, inherit_bindings=False):
|
|
|
1079
1103
|
return
|
|
1080
1104
|
agent_identity = session["agent_identity"]
|
|
1081
1105
|
agent_session_id = session.get("agent_session_id")
|
|
1106
|
+
|
|
1107
|
+
project_path = None
|
|
1108
|
+
try:
|
|
1109
|
+
meta = json.loads(session.get("meta_json", "{}"))
|
|
1110
|
+
if cwd := meta.get("cwd"):
|
|
1111
|
+
project_path = Path(cwd)
|
|
1112
|
+
except (json.JSONDecodeError, TypeError):
|
|
1113
|
+
pass
|
|
1114
|
+
|
|
1115
|
+
if project_path:
|
|
1116
|
+
self.project_dir = project_path
|
|
1117
|
+
|
|
1082
1118
|
self.launch_agent(
|
|
1083
1119
|
agent_identity,
|
|
1084
1120
|
agent_session_id=agent_session_id,
|
|
1085
1121
|
session_pk=session_pk,
|
|
1122
|
+
project_path=project_path,
|
|
1086
1123
|
)
|
|
1087
1124
|
|
|
1088
1125
|
@work
|
|
@@ -1110,6 +1147,17 @@ class A2TUIApp(App, inherit_bindings=False):
|
|
|
1110
1147
|
import yaml
|
|
1111
1148
|
|
|
1112
1149
|
result = await self.push_screen_wait("projects")
|
|
1150
|
+
if result is None:
|
|
1151
|
+
cfg = load_config()
|
|
1152
|
+
if cfg.current_project:
|
|
1153
|
+
projects_dir = CLOUD_DEV_HARNESS_DIR / "projects"
|
|
1154
|
+
pf = projects_dir / f"{cfg.current_project}.yaml"
|
|
1155
|
+
if not pf.exists():
|
|
1156
|
+
cfg.current_project = None
|
|
1157
|
+
cfg.current_project_path = None
|
|
1158
|
+
save_config(cfg)
|
|
1159
|
+
self.project_dir = None
|
|
1160
|
+
self.screen.post_message(messages.ProjectDirectoryUpdated(project_dir=None))
|
|
1113
1161
|
if result is not None:
|
|
1114
1162
|
if result == "__new__":
|
|
1115
1163
|
default_path = str((self.project_dir or Path.cwd()).resolve())
|
|
@@ -1137,7 +1185,7 @@ class A2TUIApp(App, inherit_bindings=False):
|
|
|
1137
1185
|
cfg.current_project_path = str(project_path)
|
|
1138
1186
|
save_config(cfg)
|
|
1139
1187
|
self.project_dir = project_path
|
|
1140
|
-
self.screen.post_message(messages.ProjectDirectoryUpdated())
|
|
1188
|
+
self.screen.post_message(messages.ProjectDirectoryUpdated(project_dir=self.project_dir))
|
|
1141
1189
|
self.notify(f"Created project '{name}' at {project_path}")
|
|
1142
1190
|
return
|
|
1143
1191
|
|
|
@@ -1159,8 +1207,12 @@ class A2TUIApp(App, inherit_bindings=False):
|
|
|
1159
1207
|
|
|
1160
1208
|
new_project_dir = Path(project_path) if project_path else Path.cwd()
|
|
1161
1209
|
self.project_dir = new_project_dir
|
|
1162
|
-
self.screen.post_message(messages.ProjectDirectoryUpdated())
|
|
1210
|
+
self.screen.post_message(messages.ProjectDirectoryUpdated(project_dir=self.project_dir))
|
|
1163
1211
|
self.notify(f"Switched to project: {project_name}")
|
|
1212
|
+
from tui.agents import read_agents
|
|
1213
|
+
agents = await read_agents()
|
|
1214
|
+
if cfg.default_mode in agents:
|
|
1215
|
+
self.launch_agent(cfg.default_mode, project_path=new_project_dir)
|
|
1164
1216
|
|
|
1165
1217
|
@on(messages.LaunchAgent)
|
|
1166
1218
|
def on_launch_agent(self, message: messages.LaunchAgent) -> None:
|
package/tui/db.py
CHANGED
|
@@ -172,6 +172,29 @@ class DB:
|
|
|
172
172
|
except aiosqlite.Error:
|
|
173
173
|
return False
|
|
174
174
|
|
|
175
|
+
async def session_get_by_agent_session_id(self, agent_session_id: str) -> Session | None:
|
|
176
|
+
"""Get a session from its agent_session_id.
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
agent_session_id: The agent's session id (UUID).
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
A Session if one is found, or `None`.
|
|
183
|
+
"""
|
|
184
|
+
try:
|
|
185
|
+
async with self.open() as db:
|
|
186
|
+
db.row_factory = aiosqlite.Row
|
|
187
|
+
cursor = await db.execute(
|
|
188
|
+
"SELECT * from sessions WHERE agent_session_id = ?",
|
|
189
|
+
(agent_session_id,),
|
|
190
|
+
)
|
|
191
|
+
row = await cursor.fetchone()
|
|
192
|
+
except aiosqlite.Error:
|
|
193
|
+
return None
|
|
194
|
+
if row is None:
|
|
195
|
+
return None
|
|
196
|
+
return cast(Session, dict(row))
|
|
197
|
+
|
|
175
198
|
async def session_get_recent(self, max_results: int = 100) -> list[Session] | None:
|
|
176
199
|
"""Get the most recent sessions."""
|
|
177
200
|
try:
|
package/tui/directory_watcher.py
CHANGED
|
@@ -28,6 +28,32 @@ class DirectoryChanged(Message):
|
|
|
28
28
|
return isinstance(message, DirectoryChanged)
|
|
29
29
|
|
|
30
30
|
|
|
31
|
+
_NOISY_DIR_NAMES = frozenset(
|
|
32
|
+
{
|
|
33
|
+
".venv",
|
|
34
|
+
".git",
|
|
35
|
+
".pytest_cache",
|
|
36
|
+
"__pycache__",
|
|
37
|
+
".mypy_cache",
|
|
38
|
+
"node_modules",
|
|
39
|
+
".uv-cache",
|
|
40
|
+
"dist",
|
|
41
|
+
"build",
|
|
42
|
+
".idea",
|
|
43
|
+
}
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _is_noisy_path(path: str) -> bool:
|
|
48
|
+
"""Return True when *path* lives inside a directory we want to
|
|
49
|
+
ignore for change notifications (virtualenvs, caches, build output…).
|
|
50
|
+
"""
|
|
51
|
+
if not path:
|
|
52
|
+
return False
|
|
53
|
+
parts = path.replace("\\", "/").split("/")
|
|
54
|
+
return any(part in _NOISY_DIR_NAMES for part in parts)
|
|
55
|
+
|
|
56
|
+
|
|
31
57
|
class _PathEventDispatcher(FileSystemEventHandler):
|
|
32
58
|
"""Dispatches file system events to multiple DirectoryWatcher instances."""
|
|
33
59
|
|
|
@@ -53,7 +79,15 @@ class _PathEventDispatcher(FileSystemEventHandler):
|
|
|
53
79
|
return bool(self._watchers)
|
|
54
80
|
|
|
55
81
|
def on_any_event(self, event: FileSystemEvent) -> None:
|
|
56
|
-
"""Dispatch events to all registered watchers.
|
|
82
|
+
"""Dispatch events to all registered watchers.
|
|
83
|
+
|
|
84
|
+
Events for paths inside noisy directories (virtualenvs, caches,
|
|
85
|
+
build output, etc.) are dropped so they don't flood the UI.
|
|
86
|
+
"""
|
|
87
|
+
src = getattr(event, "src_path", "") or ""
|
|
88
|
+
dest = getattr(event, "dest_path", "") or ""
|
|
89
|
+
if _is_noisy_path(src) or _is_noisy_path(dest):
|
|
90
|
+
return
|
|
57
91
|
with self._lock:
|
|
58
92
|
watchers = list(self._watchers)
|
|
59
93
|
|
package/tui/messages.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
|
-
|
|
2
|
+
from pathlib import Path
|
|
3
3
|
from typing import Literal
|
|
4
4
|
|
|
5
5
|
from textual.content import Content
|
|
@@ -72,9 +72,12 @@ class Flash(Message):
|
|
|
72
72
|
duration: float | None = None
|
|
73
73
|
|
|
74
74
|
|
|
75
|
+
@dataclass
|
|
75
76
|
class ProjectDirectoryUpdated(Message):
|
|
76
77
|
"""The project directory may may changed."""
|
|
77
78
|
|
|
79
|
+
project_dir: Path | None = None
|
|
80
|
+
|
|
78
81
|
|
|
79
82
|
@dataclass
|
|
80
83
|
class SessionNavigate(Message):
|
package/tui/paths.py
CHANGED
|
@@ -2,7 +2,7 @@ from contextlib import suppress
|
|
|
2
2
|
from pathlib import Path
|
|
3
3
|
from typing import Final
|
|
4
4
|
|
|
5
|
-
from xdg_base_dirs import xdg_config_home, xdg_data_home
|
|
5
|
+
from xdg_base_dirs import xdg_config_home, xdg_data_home
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
APP_NAME: Final[str] = "tui"
|
|
@@ -39,7 +39,7 @@ def get_config() -> Path:
|
|
|
39
39
|
|
|
40
40
|
def get_state() -> Path:
|
|
41
41
|
"""Return (possibly creating) the application state directory."""
|
|
42
|
-
path =
|
|
42
|
+
path = Path.home() / ".cdh" / "state" / APP_NAME
|
|
43
43
|
with suppress(OSError):
|
|
44
44
|
path.mkdir(0o700, exist_ok=True, parents=True)
|
|
45
45
|
return path
|
|
@@ -65,7 +65,7 @@ def get_log() -> Path:
|
|
|
65
65
|
Path to log directory.
|
|
66
66
|
|
|
67
67
|
"""
|
|
68
|
-
path = Path.home() / ".
|
|
68
|
+
path = Path.home() / ".cdh" / "logs"
|
|
69
69
|
with suppress(OSError):
|
|
70
70
|
path.mkdir(0o700, exist_ok=True, parents=True)
|
|
71
71
|
return path
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from textual import on
|
|
6
|
+
from textual.app import ComposeResult
|
|
7
|
+
from textual.binding import Binding
|
|
8
|
+
from textual.containers import Horizontal, Vertical, VerticalScroll
|
|
9
|
+
from textual.screen import ModalScreen
|
|
10
|
+
from textual.widgets import Button, Checkbox, Footer, Static
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ComponentPickerScreen(ModalScreen[Optional[list[str]]]):
|
|
14
|
+
"""Modal screen for selecting application components.
|
|
15
|
+
|
|
16
|
+
Returns the list of selected component ids, or None if cancelled.
|
|
17
|
+
If ``allow_empty`` is False (default), confirming with no selection
|
|
18
|
+
shows a notification and does not dismiss the screen.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
BINDINGS = [
|
|
22
|
+
Binding("escape", "cancel", "Cancel", show=False),
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
CSS = """
|
|
26
|
+
ComponentPickerScreen {
|
|
27
|
+
align: center middle;
|
|
28
|
+
background: rgba(0, 0, 0, 0.7);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
#picker-dialog {
|
|
32
|
+
width: 78;
|
|
33
|
+
height: auto;
|
|
34
|
+
max-height: 90%;
|
|
35
|
+
background: #111;
|
|
36
|
+
border: solid #555;
|
|
37
|
+
padding: 0 1;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
#picker-title {
|
|
41
|
+
height: 1;
|
|
42
|
+
background: #333;
|
|
43
|
+
color: #fff;
|
|
44
|
+
padding: 0 1;
|
|
45
|
+
text-style: bold;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
#picker-subtitle {
|
|
49
|
+
height: 1;
|
|
50
|
+
color: #aaa;
|
|
51
|
+
padding: 0 1;
|
|
52
|
+
margin-bottom: 1;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
#picker-list {
|
|
56
|
+
height: auto;
|
|
57
|
+
max-height: 22;
|
|
58
|
+
padding: 0 1;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.component-row {
|
|
62
|
+
height: 2;
|
|
63
|
+
padding: 0 1;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.component-row Checkbox {
|
|
67
|
+
width: 1fr;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
#picker-buttons {
|
|
71
|
+
height: 3;
|
|
72
|
+
align: center middle;
|
|
73
|
+
background: #222;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
#picker-buttons Button {
|
|
77
|
+
margin: 0 1;
|
|
78
|
+
background: #444;
|
|
79
|
+
color: #fff;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
#picker-buttons Button:hover {
|
|
83
|
+
background: #666;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
#picker-buttons Button:focus {
|
|
87
|
+
background: #555;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
#picker-buttons #picker-confirm {
|
|
91
|
+
background: #1a4d8f;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
#picker-buttons #picker-confirm:hover {
|
|
95
|
+
background: #2870c2;
|
|
96
|
+
}
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
def __init__(
|
|
100
|
+
self,
|
|
101
|
+
title: str = "Select Application Components",
|
|
102
|
+
subtitle: str = "Choose the components for this project.",
|
|
103
|
+
allow_empty: bool = False,
|
|
104
|
+
) -> None:
|
|
105
|
+
super().__init__()
|
|
106
|
+
self._title_text = title
|
|
107
|
+
self._subtitle_text = subtitle
|
|
108
|
+
self._allow_empty = allow_empty
|
|
109
|
+
|
|
110
|
+
def compose(self) -> ComposeResult:
|
|
111
|
+
with Vertical(id="picker-dialog"):
|
|
112
|
+
yield Static(self._title_text, id="picker-title")
|
|
113
|
+
yield Static(self._subtitle_text, id="picker-subtitle")
|
|
114
|
+
with VerticalScroll(id="picker-list"):
|
|
115
|
+
from cdh.scaffold import COMPONENTS
|
|
116
|
+
|
|
117
|
+
for spec in COMPONENTS:
|
|
118
|
+
label = f"{spec.label} \u2014 {spec.description}"
|
|
119
|
+
yield Checkbox(label, id=f"comp-{spec.id}", classes="component-row")
|
|
120
|
+
with Horizontal(id="picker-buttons"):
|
|
121
|
+
yield Button("Cancel", id="picker-cancel")
|
|
122
|
+
yield Button("Confirm", id="picker-confirm", variant="primary")
|
|
123
|
+
yield Footer()
|
|
124
|
+
|
|
125
|
+
def action_cancel(self) -> None:
|
|
126
|
+
self.dismiss(None)
|
|
127
|
+
|
|
128
|
+
@on(Button.Pressed, "#picker-cancel")
|
|
129
|
+
def on_cancel(self) -> None:
|
|
130
|
+
self.dismiss(None)
|
|
131
|
+
|
|
132
|
+
@on(Button.Pressed, "#picker-confirm")
|
|
133
|
+
def on_confirm(self) -> None:
|
|
134
|
+
selected: list[str] = []
|
|
135
|
+
from cdh.scaffold import COMPONENTS
|
|
136
|
+
|
|
137
|
+
for spec in COMPONENTS:
|
|
138
|
+
cb = self.query_one(f"#comp-{spec.id}", Checkbox)
|
|
139
|
+
if cb.value:
|
|
140
|
+
selected.append(spec.id)
|
|
141
|
+
if not selected and not self._allow_empty:
|
|
142
|
+
self.notify(
|
|
143
|
+
"Select at least one application component",
|
|
144
|
+
title="No components selected",
|
|
145
|
+
severity="error",
|
|
146
|
+
)
|
|
147
|
+
return
|
|
148
|
+
self.dismiss(selected)
|
package/tui/screens/main.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import asyncio
|
|
1
2
|
from functools import partial
|
|
2
3
|
from pathlib import Path
|
|
3
4
|
import random
|
|
@@ -8,10 +9,10 @@ from textual import getters
|
|
|
8
9
|
from textual.binding import Binding
|
|
9
10
|
from textual.command import Hit, Hits, Provider, DiscoveryHit
|
|
10
11
|
from textual.content import Content
|
|
11
|
-
from textual.events import ScreenResume
|
|
12
|
+
from textual.events import ScreenResume
|
|
12
13
|
from textual.screen import Screen
|
|
13
14
|
from textual.reactive import var, reactive
|
|
14
|
-
from textual.widgets import Footer, OptionList, DirectoryTree, Tree
|
|
15
|
+
from textual.widgets import Footer, OptionList, DirectoryTree, Tree
|
|
15
16
|
from textual import containers
|
|
16
17
|
from textual.widget import Widget
|
|
17
18
|
|
|
@@ -29,7 +30,6 @@ from tui.widgets.conversation import Conversation
|
|
|
29
30
|
from tui.widgets.project_directory_tree import ProjectDirectoryTree
|
|
30
31
|
from tui.widgets.side_bar import SideBar
|
|
31
32
|
from tui.widgets.session_tabs import SessionsTabs, SessionLabel
|
|
32
|
-
from onecode.agent.cdh_loader import CdhProjectLoader
|
|
33
33
|
|
|
34
34
|
|
|
35
35
|
class ModeProvider(Provider):
|
|
@@ -102,7 +102,7 @@ class MainScreen(Screen, can_focus=False):
|
|
|
102
102
|
column = reactive(False)
|
|
103
103
|
column_width = reactive(80)
|
|
104
104
|
scrollbar = reactive("")
|
|
105
|
-
project_path: var[Path] = var(Path("./").expanduser().absolute())
|
|
105
|
+
project_path: var[Path | None] = var(Path("./").expanduser().absolute())
|
|
106
106
|
|
|
107
107
|
app = getters.app(A2TUIApp)
|
|
108
108
|
|
|
@@ -116,6 +116,7 @@ class MainScreen(Screen, can_focus=False):
|
|
|
116
116
|
initial_prompt: str | None = None,
|
|
117
117
|
) -> None:
|
|
118
118
|
super().__init__()
|
|
119
|
+
self._project_update_lock = asyncio.Lock()
|
|
119
120
|
self.set_reactive(MainScreen.project_path, project_path)
|
|
120
121
|
self._agent = agent
|
|
121
122
|
self._agent_session_id = agent_session_id
|
|
@@ -154,30 +155,17 @@ class MainScreen(Screen, can_focus=False):
|
|
|
154
155
|
id="context-panel",
|
|
155
156
|
),
|
|
156
157
|
]
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
),
|
|
169
|
-
)
|
|
170
|
-
else:
|
|
171
|
-
panels.append(
|
|
172
|
-
SideBar.Panel(
|
|
173
|
-
"No Project",
|
|
174
|
-
Static(
|
|
175
|
-
"[bold]No .cdh project[/]\n\nClick to initialize .cdh in this directory",
|
|
176
|
-
id="init-cdh-hint",
|
|
177
|
-
),
|
|
178
|
-
id="no-project-panel",
|
|
179
|
-
),
|
|
180
|
-
)
|
|
158
|
+
# panels.append(
|
|
159
|
+
# SideBar.Panel(
|
|
160
|
+
# "Project",
|
|
161
|
+
# ProjectDirectoryTree(
|
|
162
|
+
# self.project_path,
|
|
163
|
+
# id="project_directory_tree",
|
|
164
|
+
# ),
|
|
165
|
+
# flex=True,
|
|
166
|
+
# id="project-panel",
|
|
167
|
+
# ),
|
|
168
|
+
# )
|
|
181
169
|
panels.append(
|
|
182
170
|
SideBar.Panel(
|
|
183
171
|
"Modified Files",
|
|
@@ -217,12 +205,22 @@ class MainScreen(Screen, can_focus=False):
|
|
|
217
205
|
self.post_message(messages.SessionNavigate(self.screen.id, +1))
|
|
218
206
|
|
|
219
207
|
@on(messages.ProjectDirectoryUpdated)
|
|
220
|
-
async def on_project_directory_update(self) -> None:
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
208
|
+
async def on_project_directory_update(self, event: messages.ProjectDirectoryUpdated) -> None:
|
|
209
|
+
async with self._project_update_lock:
|
|
210
|
+
new_dir = event.project_dir
|
|
211
|
+
sidebar = self.query_one(SideBar)
|
|
212
|
+
if new_dir is None:
|
|
213
|
+
sidebar.remove_panel("project-panel")
|
|
214
|
+
self.project_path = None
|
|
215
|
+
if mf := sidebar.query_one_optional("#modified_files", ModifiedFiles):
|
|
216
|
+
mf.refresh_files()
|
|
217
|
+
self._swap_directory_watcher(None)
|
|
218
|
+
return
|
|
219
|
+
if self.project_path == new_dir and sidebar.has_panel("project-panel"):
|
|
220
|
+
if mf := sidebar.query_one_optional("#modified_files", ModifiedFiles):
|
|
221
|
+
mf.refresh_files()
|
|
222
|
+
return
|
|
223
|
+
self.project_path = new_dir
|
|
226
224
|
if not sidebar.has_panel("project-panel"):
|
|
227
225
|
collapsible = await sidebar.add_panel(
|
|
228
226
|
SideBar.Panel(
|
|
@@ -238,62 +236,23 @@ class MainScreen(Screen, can_focus=False):
|
|
|
238
236
|
tree = collapsible.query_one(ProjectDirectoryTree)
|
|
239
237
|
tree.data_bind(path=MainScreen.project_path)
|
|
240
238
|
tree.guide_depth = 3
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
else:
|
|
246
|
-
sidebar.remove_panel("project-panel")
|
|
247
|
-
if not sidebar.has_panel("no-project-panel"):
|
|
248
|
-
await sidebar.add_panel(
|
|
249
|
-
SideBar.Panel(
|
|
250
|
-
"No Project",
|
|
251
|
-
Static(
|
|
252
|
-
"[bold]No .cdh project[/]\n\nClick to initialize .cdh in this directory",
|
|
253
|
-
id="init-cdh-hint",
|
|
254
|
-
),
|
|
255
|
-
id="no-project-panel",
|
|
256
|
-
),
|
|
257
|
-
)
|
|
258
|
-
if mf := sidebar.query_one_optional("#modified_files", ModifiedFiles):
|
|
259
|
-
mf.refresh_files()
|
|
260
|
-
|
|
261
|
-
@on(Click, "#init-cdh-hint")
|
|
262
|
-
def on_init_cdh_click(self) -> None:
|
|
263
|
-
from pathlib import Path
|
|
264
|
-
from onecode.config_screen import EditFieldScreen
|
|
265
|
-
from onecode.agent.cdh_loader import CdhProjectLoader
|
|
266
|
-
|
|
267
|
-
default_path = str(self.project_path.resolve())
|
|
268
|
-
self.app.push_screen(
|
|
269
|
-
EditFieldScreen("Directory to initialize .cdh", default_path),
|
|
270
|
-
self._on_init_cdh_path,
|
|
271
|
-
)
|
|
239
|
+
|
|
240
|
+
if mf := sidebar.query_one_optional("#modified_files", ModifiedFiles):
|
|
241
|
+
mf.refresh_files()
|
|
242
|
+
self._swap_directory_watcher(new_dir)
|
|
272
243
|
|
|
273
|
-
def
|
|
274
|
-
|
|
275
|
-
|
|
244
|
+
def _swap_directory_watcher(self, project_path: Path | None) -> None:
|
|
245
|
+
try:
|
|
246
|
+
self.conversation.directory_watcher_swap(project_path)
|
|
247
|
+
except Exception:
|
|
248
|
+
pass
|
|
276
249
|
|
|
277
|
-
|
|
278
|
-
return
|
|
250
|
+
def _has_project_panel(self) -> bool:
|
|
279
251
|
try:
|
|
280
|
-
|
|
281
|
-
if not target.is_dir():
|
|
282
|
-
self.notify(f"Not a directory: {target}", severity="error")
|
|
283
|
-
return
|
|
252
|
+
sidebar = self.query_one(SideBar)
|
|
284
253
|
except Exception:
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
existing = CdhProjectLoader.find_cdh_dir(target)
|
|
288
|
-
if existing is not None:
|
|
289
|
-
self.notify(f".cdh already exists at {existing}", severity="warning")
|
|
290
|
-
return
|
|
291
|
-
name = target.name
|
|
292
|
-
from cdh.scaffold import scaffold_dlc_project
|
|
293
|
-
scaffold_dlc_project(target, name)
|
|
294
|
-
CdhProjectLoader.init_project(target, name)
|
|
295
|
-
self.notify(f"Initialized .cdh in {target}")
|
|
296
|
-
self.post_message(messages.ProjectDirectoryUpdated())
|
|
254
|
+
return False
|
|
255
|
+
return sidebar.has_panel("project-panel")
|
|
297
256
|
|
|
298
257
|
@on(DirectoryTree.FileSelected, "ProjectDirectoryTree")
|
|
299
258
|
def on_project_directory_tree_selected(self, event: Tree.NodeSelected):
|
|
@@ -303,15 +262,8 @@ class MainScreen(Screen, can_focus=False):
|
|
|
303
262
|
@on(acp_messages.Plan)
|
|
304
263
|
async def on_acp_plan(self, message: acp_messages.Plan):
|
|
305
264
|
message.stop()
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
Content(entry["content"]),
|
|
309
|
-
entry.get("priority", "medium"),
|
|
310
|
-
entry.get("status", "pending"),
|
|
311
|
-
)
|
|
312
|
-
for entry in message.entries
|
|
313
|
-
]
|
|
314
|
-
self.query_one("SideBar Plan", Plan).entries = entries
|
|
265
|
+
from tui.widgets.plan import entries_from_dicts
|
|
266
|
+
self.query_one("SideBar Plan", Plan).entries = entries_from_dicts(message.entries)
|
|
315
267
|
|
|
316
268
|
@on(messages.SessionUpdate)
|
|
317
269
|
async def on_session_update(self, event: messages.SessionUpdate) -> None:
|