@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
|
@@ -226,13 +226,24 @@ class ProjectsApp(App):
|
|
|
226
226
|
except Exception:
|
|
227
227
|
ws = Path.cwd().resolve()
|
|
228
228
|
name = ws.name
|
|
229
|
-
from cdh.scaffold import scaffold_dlc_project
|
|
230
|
-
scaffold_dlc_project(ws, name)
|
|
231
|
-
CdhProjectLoader.init_project(ws, name)
|
|
232
229
|
projects_dir = CLOUD_DEV_HARNESS_DIR / "projects"
|
|
233
230
|
projects_dir.mkdir(parents=True, exist_ok=True)
|
|
231
|
+
proj_file = projects_dir / f"{name}.yaml"
|
|
232
|
+
if proj_file.exists():
|
|
233
|
+
self.notify(
|
|
234
|
+
f"Project '{name}' already exists in the project list",
|
|
235
|
+
severity="error",
|
|
236
|
+
)
|
|
237
|
+
return
|
|
238
|
+
from cdh.scaffold import scaffold_dlc_project
|
|
239
|
+
try:
|
|
240
|
+
scaffold_dlc_project(ws, name)
|
|
241
|
+
except (ValueError, RuntimeError) as e:
|
|
242
|
+
self.notify(str(e), severity="error")
|
|
243
|
+
return
|
|
244
|
+
CdhProjectLoader.init_project(ws, name)
|
|
234
245
|
proj_data = {"name": name, "path": str(ws), "description": ""}
|
|
235
|
-
|
|
246
|
+
proj_file.write_text(yaml.dump(proj_data))
|
|
236
247
|
cfg = load_config()
|
|
237
248
|
cfg.current_project = name
|
|
238
249
|
cfg.current_project_path = str(ws)
|
|
@@ -262,9 +273,26 @@ class ProjectsApp(App):
|
|
|
262
273
|
self.notify(f".cdh already exists at {existing}", severity="warning")
|
|
263
274
|
return
|
|
264
275
|
name = target.name
|
|
276
|
+
projects_dir = CLOUD_DEV_HARNESS_DIR / "projects"
|
|
277
|
+
projects_dir.mkdir(parents=True, exist_ok=True)
|
|
278
|
+
proj_file = projects_dir / f"{name}.yaml"
|
|
279
|
+
if proj_file.exists():
|
|
280
|
+
self.notify(
|
|
281
|
+
f"Project '{name}' already exists in the project list",
|
|
282
|
+
severity="error",
|
|
283
|
+
)
|
|
284
|
+
return
|
|
265
285
|
from cdh.scaffold import init_dlc_project
|
|
266
|
-
|
|
286
|
+
try:
|
|
287
|
+
init_dlc_project(target, name)
|
|
288
|
+
except (ValueError, RuntimeError) as e:
|
|
289
|
+
self.notify(str(e), severity="error")
|
|
290
|
+
return
|
|
267
291
|
CdhProjectLoader.init_project(target, name)
|
|
292
|
+
import yaml
|
|
293
|
+
proj_data = {"name": name, "path": str(target), "description": ""}
|
|
294
|
+
proj_file.write_text(yaml.dump(proj_data))
|
|
295
|
+
self._refresh()
|
|
268
296
|
self.notify(f"Initialized .cdh in {target}")
|
|
269
297
|
|
|
270
298
|
def _load_project(self) -> None:
|
|
@@ -2,7 +2,6 @@ from time import monotonic
|
|
|
2
2
|
|
|
3
3
|
from textual.app import ComposeResult
|
|
4
4
|
from textual.binding import Binding
|
|
5
|
-
from textual.events import ScreenResume
|
|
6
5
|
from textual.screen import ModalScreen
|
|
7
6
|
from textual import getters
|
|
8
7
|
from textual.widget import Widget
|
|
@@ -17,12 +16,26 @@ from tui.app import A2TUIApp
|
|
|
17
16
|
from tui.widgets.grid_select import GridSelect
|
|
18
17
|
from tui.widgets.project_grid_select import ProjectGridSelect
|
|
19
18
|
from tui.widgets.project_summary import ProjectSummary
|
|
19
|
+
from tui.screens.component_picker import ComponentPickerScreen
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
CONFIRM_TIMEOUT = 5.0
|
|
23
23
|
INSTRUCTIONS_NO_PROJECTS = "Your projects will be shown here."
|
|
24
24
|
|
|
25
25
|
|
|
26
|
+
def _project_db_path(name: str):
|
|
27
|
+
"""Return the path of the project entry in ``~/.cdh/projects/``
|
|
28
|
+
for *name* across all supported extensions, or ``None`` if the
|
|
29
|
+
project is not registered yet.
|
|
30
|
+
"""
|
|
31
|
+
projects_dir = CLOUD_DEV_HARNESS_DIR / "projects"
|
|
32
|
+
for ext in ("yaml", "yml", "json"):
|
|
33
|
+
pf = projects_dir / f"{name}.{ext}"
|
|
34
|
+
if pf.exists():
|
|
35
|
+
return pf
|
|
36
|
+
return None
|
|
37
|
+
|
|
38
|
+
|
|
26
39
|
class ProjectsScreen(ModalScreen[str]):
|
|
27
40
|
CSS_PATH = "projects.tcss"
|
|
28
41
|
BINDINGS = [
|
|
@@ -41,7 +54,7 @@ class ProjectsScreen(ModalScreen[str]):
|
|
|
41
54
|
yield widgets.Label("Projects")
|
|
42
55
|
yield widgets.Static(INSTRUCTIONS_NO_PROJECTS, classes="instructions")
|
|
43
56
|
yield ProjectGridSelect()
|
|
44
|
-
with containers.
|
|
57
|
+
with containers.Horizontal(id="actions"):
|
|
45
58
|
yield widgets.Button("+ New Project", id="new-project", variant="primary")
|
|
46
59
|
yield widgets.Button("Init .cdh (i)", id="init-project")
|
|
47
60
|
yield widgets.Footer()
|
|
@@ -89,12 +102,95 @@ class ProjectsScreen(ModalScreen[str]):
|
|
|
89
102
|
self.notify(f"Deleted project: {project_name}")
|
|
90
103
|
|
|
91
104
|
def action_new_project(self) -> None:
|
|
92
|
-
|
|
105
|
+
from pathlib import Path
|
|
106
|
+
from onecode.config_screen import EditFieldScreen
|
|
107
|
+
|
|
108
|
+
default_path = str(Path.cwd().resolve())
|
|
109
|
+
self.app.push_screen(
|
|
110
|
+
EditFieldScreen("Project path", default_path),
|
|
111
|
+
self._on_new_project_path,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
def _on_new_project_path(self, path_str: str | None) -> None:
|
|
115
|
+
from pathlib import Path
|
|
116
|
+
|
|
117
|
+
if not path_str:
|
|
118
|
+
return
|
|
119
|
+
try:
|
|
120
|
+
target = Path(path_str).expanduser().resolve()
|
|
121
|
+
except Exception:
|
|
122
|
+
self.notify("Invalid path", severity="error")
|
|
123
|
+
return
|
|
124
|
+
if _project_db_path(target.name) is not None:
|
|
125
|
+
self.notify(
|
|
126
|
+
f"Project '{target.name}' is already in the project list",
|
|
127
|
+
severity="warning",
|
|
128
|
+
)
|
|
129
|
+
return
|
|
130
|
+
self.app.push_screen(
|
|
131
|
+
ComponentPickerScreen(
|
|
132
|
+
title="New Project — Select Components",
|
|
133
|
+
subtitle=(
|
|
134
|
+
f"Project path: {target}\n"
|
|
135
|
+
"Cross-cutting items (contracts, shared types, etc.) are created automatically."
|
|
136
|
+
),
|
|
137
|
+
allow_empty=False,
|
|
138
|
+
),
|
|
139
|
+
lambda picked: self._on_new_project_components(target, picked),
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
def _on_new_project_components(
|
|
143
|
+
self,
|
|
144
|
+
target: "Path",
|
|
145
|
+
picked: list[str] | None,
|
|
146
|
+
) -> None:
|
|
147
|
+
if picked is None:
|
|
148
|
+
self.notify("New project cancelled", severity="warning")
|
|
149
|
+
return
|
|
150
|
+
if not picked:
|
|
151
|
+
return
|
|
152
|
+
self._do_new_project(target, picked)
|
|
153
|
+
|
|
154
|
+
def _do_new_project(
|
|
155
|
+
self,
|
|
156
|
+
target: "Path",
|
|
157
|
+
components: list[str],
|
|
158
|
+
) -> None:
|
|
159
|
+
from pathlib import Path
|
|
160
|
+
from onecode.agent.cdh_loader import CdhProjectLoader
|
|
161
|
+
|
|
162
|
+
name = target.name
|
|
163
|
+
from cdh.scaffold import scaffold_dlc_project
|
|
164
|
+
|
|
165
|
+
try:
|
|
166
|
+
scaffold_dlc_project(target, name, components=components)
|
|
167
|
+
except (ValueError, RuntimeError) as e:
|
|
168
|
+
self.notify(str(e), severity="error")
|
|
169
|
+
return
|
|
170
|
+
if _project_db_path(name) is not None:
|
|
171
|
+
self.notify(
|
|
172
|
+
f"Project '{name}' is already in the project list",
|
|
173
|
+
severity="error",
|
|
174
|
+
)
|
|
175
|
+
return
|
|
176
|
+
projects_dir = CLOUD_DEV_HARNESS_DIR / "projects"
|
|
177
|
+
projects_dir.mkdir(parents=True, exist_ok=True)
|
|
178
|
+
proj_file = projects_dir / f"{name}.yaml"
|
|
179
|
+
CdhProjectLoader.init_project(target, name)
|
|
180
|
+
import yaml
|
|
181
|
+
proj_data = {"name": name, "path": str(target), "description": ""}
|
|
182
|
+
proj_file.write_text(yaml.dump(proj_data))
|
|
183
|
+
widget = ProjectSummary(name, str(target), id=name)
|
|
184
|
+
self.project_grid_select.mount(widget)
|
|
185
|
+
self.project_grid_select.highlighted = len(self.project_grid_select.children) - 1
|
|
186
|
+
self.notify(
|
|
187
|
+
f"Created project '{name}' at {target} "
|
|
188
|
+
f"(components: {', '.join(components)})"
|
|
189
|
+
)
|
|
93
190
|
|
|
94
191
|
def action_init_project(self) -> None:
|
|
95
192
|
from pathlib import Path
|
|
96
193
|
from onecode.config_screen import EditFieldScreen
|
|
97
|
-
from onecode.agent.cdh_loader import CdhProjectLoader
|
|
98
194
|
|
|
99
195
|
default_path = str(Path.cwd().resolve())
|
|
100
196
|
self.app.push_screen(
|
|
@@ -117,14 +213,80 @@ class ProjectsScreen(ModalScreen[str]):
|
|
|
117
213
|
self.notify("Invalid path", severity="error")
|
|
118
214
|
return
|
|
119
215
|
existing = CdhProjectLoader.find_cdh_dir(target)
|
|
120
|
-
if existing is not None:
|
|
216
|
+
if existing is not None and existing.parent == target:
|
|
121
217
|
self.notify(f".cdh already exists at {existing}", severity="warning")
|
|
122
218
|
return
|
|
219
|
+
if _project_db_path(target.name) is not None:
|
|
220
|
+
self.notify(
|
|
221
|
+
f"Project '{target.name}' is already in the project list",
|
|
222
|
+
severity="warning",
|
|
223
|
+
)
|
|
224
|
+
return
|
|
225
|
+
self.app.push_screen(
|
|
226
|
+
ComponentPickerScreen(
|
|
227
|
+
title="Init .cdh — Select Components",
|
|
228
|
+
subtitle=(
|
|
229
|
+
f"Directory: {target}\n"
|
|
230
|
+
"Pick the components already present in this directory. "
|
|
231
|
+
"Empty selection is allowed (no apps/ created)."
|
|
232
|
+
),
|
|
233
|
+
allow_empty=True,
|
|
234
|
+
),
|
|
235
|
+
lambda picked: self._on_init_components(target, picked),
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
def _on_init_components(
|
|
239
|
+
self,
|
|
240
|
+
target: "Path",
|
|
241
|
+
picked: list[str] | None,
|
|
242
|
+
) -> None:
|
|
243
|
+
if picked is None:
|
|
244
|
+
self.notify("Init cancelled", severity="warning")
|
|
245
|
+
return
|
|
246
|
+
self._do_init_project(target, picked)
|
|
247
|
+
|
|
248
|
+
def _do_init_project(
|
|
249
|
+
self,
|
|
250
|
+
target: "Path",
|
|
251
|
+
components: list[str],
|
|
252
|
+
) -> None:
|
|
253
|
+
from pathlib import Path
|
|
254
|
+
from onecode.agent.cdh_loader import CdhProjectLoader
|
|
255
|
+
from cdh.scaffold import add_component, init_dlc_project
|
|
256
|
+
|
|
123
257
|
name = target.name
|
|
124
|
-
|
|
125
|
-
|
|
258
|
+
if _project_db_path(name) is not None:
|
|
259
|
+
self.notify(
|
|
260
|
+
f"Project '{name}' is already in the project list",
|
|
261
|
+
severity="error",
|
|
262
|
+
)
|
|
263
|
+
return
|
|
264
|
+
try:
|
|
265
|
+
init_dlc_project(target, name)
|
|
266
|
+
except (ValueError, RuntimeError) as e:
|
|
267
|
+
self.notify(str(e), severity="error")
|
|
268
|
+
return
|
|
269
|
+
for cid in components:
|
|
270
|
+
try:
|
|
271
|
+
add_component(target, cid)
|
|
272
|
+
except (ValueError, FileNotFoundError) as e:
|
|
273
|
+
self.notify(str(e), severity="error")
|
|
126
274
|
CdhProjectLoader.init_project(target, name)
|
|
127
|
-
|
|
275
|
+
import yaml
|
|
276
|
+
projects_dir = CLOUD_DEV_HARNESS_DIR / "projects"
|
|
277
|
+
projects_dir.mkdir(parents=True, exist_ok=True)
|
|
278
|
+
proj_file = projects_dir / f"{name}.yaml"
|
|
279
|
+
proj_data = {"name": name, "path": str(target), "description": ""}
|
|
280
|
+
proj_file.write_text(yaml.dump(proj_data))
|
|
281
|
+
widget = ProjectSummary(name, str(target), id=name)
|
|
282
|
+
self.project_grid_select.mount(widget)
|
|
283
|
+
self.project_grid_select.highlighted = len(self.project_grid_select.children) - 1
|
|
284
|
+
suffix = (
|
|
285
|
+
f" (components: {', '.join(components)})"
|
|
286
|
+
if components
|
|
287
|
+
else " (no components)"
|
|
288
|
+
)
|
|
289
|
+
self.notify(f"Initialized .cdh in {target}{suffix}")
|
|
128
290
|
|
|
129
291
|
@on(GridSelect.Selected)
|
|
130
292
|
def on_selected(self, event: GridSelect.Selected) -> None:
|
|
@@ -135,7 +297,7 @@ class ProjectsScreen(ModalScreen[str]):
|
|
|
135
297
|
|
|
136
298
|
@on(widgets.Button.Pressed, "#new-project")
|
|
137
299
|
def on_new_project(self) -> None:
|
|
138
|
-
self.
|
|
300
|
+
self.action_new_project()
|
|
139
301
|
|
|
140
302
|
@on(widgets.Button.Pressed, "#init-project")
|
|
141
303
|
def on_init_project(self) -> None:
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/tui/widgets/ask_user.py
CHANGED
|
@@ -260,5 +260,5 @@ class AskUserWidget(VerticalGroup):
|
|
|
260
260
|
def _finish(self, value: str) -> None:
|
|
261
261
|
self._answer = value
|
|
262
262
|
self._done = True
|
|
263
|
-
self.refresh(recompose=True
|
|
263
|
+
self.refresh(recompose=True)
|
|
264
264
|
self.post_message(AskUserSubmitted(value, self._tool_id))
|
|
@@ -267,6 +267,16 @@ This is a view of your conversation with the agent.
|
|
|
267
267
|
pass
|
|
268
268
|
|
|
269
269
|
|
|
270
|
+
def _plan_entries_from_dicts(raw_entries):
|
|
271
|
+
"""Deprecated thin wrapper around the shared ``entries_from_dicts`` helper.
|
|
272
|
+
|
|
273
|
+
Kept for backwards compatibility with any external callers; new code
|
|
274
|
+
should import ``entries_from_dicts`` directly from ``tui.widgets.plan``.
|
|
275
|
+
"""
|
|
276
|
+
from tui.widgets.plan import entries_from_dicts
|
|
277
|
+
return entries_from_dicts(raw_entries)
|
|
278
|
+
|
|
279
|
+
|
|
270
280
|
class Conversation(containers.Vertical):
|
|
271
281
|
"""Holds the agent conversation (input, output, and various controls / information)."""
|
|
272
282
|
|
|
@@ -378,6 +388,7 @@ class Conversation(containers.Vertical):
|
|
|
378
388
|
self._agent_thought: AgentThought | None = None
|
|
379
389
|
self._replay: bool = False
|
|
380
390
|
self._replay_buffer: list[dict] = []
|
|
391
|
+
self._active_subagents: dict = {}
|
|
381
392
|
self._last_escape_time: float = monotonic()
|
|
382
393
|
self._agent_data = agent
|
|
383
394
|
self._agent_session_id = agent_session_id
|
|
@@ -404,6 +415,7 @@ class Conversation(containers.Vertical):
|
|
|
404
415
|
|
|
405
416
|
self._directory_changed = False
|
|
406
417
|
self._directory_watcher: DirectoryWatcher | None = None
|
|
418
|
+
self._suppress_directory_changed = False
|
|
407
419
|
|
|
408
420
|
self._initial_prompt = initial_prompt
|
|
409
421
|
|
|
@@ -559,7 +571,34 @@ class Conversation(containers.Vertical):
|
|
|
559
571
|
@on(DirectoryChanged)
|
|
560
572
|
def on_directory_changed(self, event: DirectoryChanged) -> None:
|
|
561
573
|
event.stop()
|
|
562
|
-
self.
|
|
574
|
+
self._directory_changed = True
|
|
575
|
+
if self._suppress_directory_changed:
|
|
576
|
+
return
|
|
577
|
+
self._suppress_directory_changed = True
|
|
578
|
+
self.post_message(messages.ProjectDirectoryUpdated(project_dir=self.app.project_dir))
|
|
579
|
+
self.set_timer(0.5, self._release_directory_changed_suppress)
|
|
580
|
+
|
|
581
|
+
def _release_directory_changed_suppress(self) -> None:
|
|
582
|
+
self._suppress_directory_changed = False
|
|
583
|
+
|
|
584
|
+
def directory_watcher_swap(self, project_path: Path) -> None:
|
|
585
|
+
"""Stop the current DirectoryWatcher (if any) and start a new one
|
|
586
|
+
rooted at *project_path*.
|
|
587
|
+
|
|
588
|
+
Used by ``MainScreen`` when the user switches projects so we don't
|
|
589
|
+
keep receiving events for the old tree.
|
|
590
|
+
"""
|
|
591
|
+
if self._directory_watcher is not None:
|
|
592
|
+
self._directory_watcher.stop()
|
|
593
|
+
self._directory_watcher = None
|
|
594
|
+
if project_path is None:
|
|
595
|
+
return
|
|
596
|
+
try:
|
|
597
|
+
watcher = DirectoryWatcher(project_path, self)
|
|
598
|
+
except Exception:
|
|
599
|
+
return
|
|
600
|
+
watcher.start()
|
|
601
|
+
self._directory_watcher = watcher
|
|
563
602
|
|
|
564
603
|
@on(Terminal.Finalized)
|
|
565
604
|
def on_terminal_finalized(self, event: Terminal.Finalized) -> None:
|
|
@@ -572,7 +611,7 @@ class Conversation(containers.Vertical):
|
|
|
572
611
|
if self._directory_changed or not self.is_watching_directory:
|
|
573
612
|
self.prompt.project_directory_updated()
|
|
574
613
|
self._directory_changed = False
|
|
575
|
-
self.post_message(messages.ProjectDirectoryUpdated())
|
|
614
|
+
self.post_message(messages.ProjectDirectoryUpdated(project_dir=self.app.project_dir))
|
|
576
615
|
|
|
577
616
|
@on(Terminal.LongRunning)
|
|
578
617
|
def on_terminal_long_running(self, event: Terminal.LongRunning) -> None:
|
|
@@ -934,7 +973,7 @@ class Conversation(containers.Vertical):
|
|
|
934
973
|
|
|
935
974
|
if self._directory_changed or not self.is_watching_directory:
|
|
936
975
|
self._directory_changed = False
|
|
937
|
-
self.post_message(messages.ProjectDirectoryUpdated())
|
|
976
|
+
self.post_message(messages.ProjectDirectoryUpdated(project_dir=self.app.project_dir))
|
|
938
977
|
self.prompt.project_directory_updated()
|
|
939
978
|
|
|
940
979
|
self._turn_count += 1
|
|
@@ -1102,12 +1141,12 @@ class Conversation(containers.Vertical):
|
|
|
1102
1141
|
case "plan":
|
|
1103
1142
|
current_thought = None
|
|
1104
1143
|
self.new_block()
|
|
1105
|
-
widgets.append(Plan(entry["entries"]))
|
|
1144
|
+
widgets.append(Plan(_plan_entries_from_dicts(entry["entries"])))
|
|
1106
1145
|
|
|
1107
1146
|
case "subagent_start":
|
|
1108
1147
|
current_thought = None
|
|
1109
1148
|
self.new_block()
|
|
1110
|
-
sa = SubAgent(entry["agent_type"], tool_id=entry["id"])
|
|
1149
|
+
sa = SubAgent(entry["agent_type"], tool_id=entry["id"], prompt=entry.get("prompt", ""))
|
|
1111
1150
|
created_subagents[entry["id"]] = sa
|
|
1112
1151
|
widgets.append(sa)
|
|
1113
1152
|
|
|
@@ -1116,10 +1155,18 @@ class Conversation(containers.Vertical):
|
|
|
1116
1155
|
if sa is not None:
|
|
1117
1156
|
sa.append_chunk(entry["text"])
|
|
1118
1157
|
|
|
1158
|
+
case "subagent_thinking":
|
|
1159
|
+
sa = created_subagents.get(entry["id"])
|
|
1160
|
+
if sa is not None:
|
|
1161
|
+
sa.append_thinking(entry["text"])
|
|
1162
|
+
|
|
1119
1163
|
case "subagent_end":
|
|
1120
1164
|
sa = created_subagents.get(entry["id"])
|
|
1121
1165
|
if sa is not None:
|
|
1122
|
-
sa.complete(
|
|
1166
|
+
sa.complete(
|
|
1167
|
+
entry.get("status", "completed"),
|
|
1168
|
+
entry.get("error", ""),
|
|
1169
|
+
)
|
|
1123
1170
|
|
|
1124
1171
|
case "ask_user":
|
|
1125
1172
|
current_thought = None
|
|
@@ -1192,14 +1239,7 @@ class Conversation(containers.Vertical):
|
|
|
1192
1239
|
return
|
|
1193
1240
|
from tui.widgets.plan import Plan
|
|
1194
1241
|
|
|
1195
|
-
entries =
|
|
1196
|
-
Plan.Entry(
|
|
1197
|
-
Content(entry["content"]),
|
|
1198
|
-
entry.get("priority", "medium"),
|
|
1199
|
-
entry.get("status", "pending"),
|
|
1200
|
-
)
|
|
1201
|
-
for entry in message.entries
|
|
1202
|
-
]
|
|
1242
|
+
entries = _plan_entries_from_dicts(message.entries)
|
|
1203
1243
|
|
|
1204
1244
|
# Look up any existing Plan widget anywhere in the conversation
|
|
1205
1245
|
# tree — checking only ``children[-1]`` would create duplicates if
|
|
@@ -1310,46 +1350,62 @@ class Conversation(containers.Vertical):
|
|
|
1310
1350
|
|
|
1311
1351
|
message.stop()
|
|
1312
1352
|
if self._replay:
|
|
1313
|
-
self._replay_buffer.append({"kind": "subagent_start", "agent_type": message.agent_type, "id": message.subagent_id})
|
|
1353
|
+
self._replay_buffer.append({"kind": "subagent_start", "agent_type": message.agent_type, "id": message.subagent_id, "prompt": message.prompt})
|
|
1314
1354
|
return
|
|
1315
|
-
sa = SubAgent(message.agent_type, tool_id=message.subagent_id)
|
|
1355
|
+
sa = SubAgent(message.agent_type, tool_id=message.subagent_id, prompt=message.prompt)
|
|
1356
|
+
self._active_subagents[message.subagent_id] = sa
|
|
1316
1357
|
await self.post(sa, new_block=True)
|
|
1317
1358
|
|
|
1318
|
-
|
|
1319
|
-
async def on_acp_subagent_chunk(self, message: acp_messages.SubAgentChunk):
|
|
1359
|
+
def _get_subagent(self, subagent_id: str):
|
|
1320
1360
|
from tui.widgets.subagent import SubAgent
|
|
1321
1361
|
|
|
1362
|
+
sa = self._active_subagents.get(subagent_id)
|
|
1363
|
+
if sa is not None:
|
|
1364
|
+
return sa
|
|
1365
|
+
try:
|
|
1366
|
+
return self.contents.get_child_by_id(subagent_id, SubAgent)
|
|
1367
|
+
except NoMatches:
|
|
1368
|
+
return None
|
|
1369
|
+
|
|
1370
|
+
@on(acp_messages.SubAgentChunk)
|
|
1371
|
+
async def on_acp_subagent_chunk(self, message: acp_messages.SubAgentChunk):
|
|
1322
1372
|
message.stop()
|
|
1323
1373
|
if self._replay:
|
|
1324
1374
|
self._replay_buffer.append({"kind": "subagent_chunk", "id": message.subagent_id, "text": message.text})
|
|
1325
1375
|
return
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1376
|
+
sa = self._get_subagent(message.subagent_id)
|
|
1377
|
+
if sa is not None:
|
|
1378
|
+
sa.append_chunk(message.text)
|
|
1379
|
+
|
|
1380
|
+
@on(acp_messages.SubAgentThinking)
|
|
1381
|
+
async def on_acp_subagent_thinking(self, message: acp_messages.SubAgentThinking):
|
|
1382
|
+
message.stop()
|
|
1383
|
+
if self._replay:
|
|
1384
|
+
self._replay_buffer.append({
|
|
1385
|
+
"kind": "subagent_thinking",
|
|
1386
|
+
"id": message.subagent_id,
|
|
1387
|
+
"text": message.text,
|
|
1388
|
+
})
|
|
1331
1389
|
return
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1390
|
+
sa = self._get_subagent(message.subagent_id)
|
|
1391
|
+
if sa is not None:
|
|
1392
|
+
sa.append_thinking(message.text)
|
|
1335
1393
|
|
|
1336
1394
|
@on(acp_messages.SubAgentEnd)
|
|
1337
1395
|
async def on_acp_subagent_end(self, message: acp_messages.SubAgentEnd):
|
|
1338
|
-
from tui.widgets.subagent import SubAgent
|
|
1339
|
-
|
|
1340
1396
|
message.stop()
|
|
1341
1397
|
if self._replay:
|
|
1342
|
-
self._replay_buffer.append({
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
message.
|
|
1347
|
-
)
|
|
1348
|
-
except NoMatches:
|
|
1398
|
+
self._replay_buffer.append({
|
|
1399
|
+
"kind": "subagent_end",
|
|
1400
|
+
"id": message.subagent_id,
|
|
1401
|
+
"status": message.status,
|
|
1402
|
+
"error": message.error,
|
|
1403
|
+
})
|
|
1349
1404
|
return
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1405
|
+
sa = self._get_subagent(message.subagent_id)
|
|
1406
|
+
if sa is not None:
|
|
1407
|
+
sa.complete(message.status, message.error)
|
|
1408
|
+
self._active_subagents.pop(message.subagent_id, None)
|
|
1353
1409
|
|
|
1354
1410
|
@on(acp_messages.AvailableCommandsUpdate)
|
|
1355
1411
|
async def on_acp_available_commands_update(
|
|
@@ -1929,12 +1985,13 @@ class Conversation(containers.Vertical):
|
|
|
1929
1985
|
self.cursor_offset = -1
|
|
1930
1986
|
self.cursor.visible = False
|
|
1931
1987
|
self.cursor.follow(None)
|
|
1932
|
-
contents.refresh(
|
|
1988
|
+
contents.refresh()
|
|
1933
1989
|
|
|
1934
1990
|
if prune_children:
|
|
1935
1991
|
await contents.remove_children(prune_children)
|
|
1936
1992
|
|
|
1937
|
-
self.
|
|
1993
|
+
if self.window.scroll_y >= self.window.max_scroll_y:
|
|
1994
|
+
self.call_later(self.window.anchor)
|
|
1938
1995
|
|
|
1939
1996
|
async def new_terminal(self) -> Terminal:
|
|
1940
1997
|
"""Create a new interactive Terminal.
|
|
@@ -2401,7 +2458,7 @@ class Conversation(containers.Vertical):
|
|
|
2401
2458
|
save_config(cfg)
|
|
2402
2459
|
new_project_dir = Path(project_path) if project_path else Path.cwd()
|
|
2403
2460
|
self.app.project_dir = new_project_dir
|
|
2404
|
-
self.post_message(messages.ProjectDirectoryUpdated())
|
|
2461
|
+
self.post_message(messages.ProjectDirectoryUpdated(project_dir=new_project_dir))
|
|
2405
2462
|
self.flash(f"Switched to project: {name}", style="success")
|
|
2406
2463
|
return True
|
|
2407
2464
|
elif sub_cmd == "new":
|
|
@@ -2427,7 +2484,7 @@ class Conversation(containers.Vertical):
|
|
|
2427
2484
|
cfg.current_project_path = str(ws)
|
|
2428
2485
|
save_config(cfg)
|
|
2429
2486
|
self.app.project_dir = ws
|
|
2430
|
-
self.post_message(messages.ProjectDirectoryUpdated())
|
|
2487
|
+
self.post_message(messages.ProjectDirectoryUpdated(project_dir=ws))
|
|
2431
2488
|
self.flash(f"Created and switched to project: {name}", style="success")
|
|
2432
2489
|
return True
|
|
2433
2490
|
else:
|