@scemoon/cdh 1.0.0 → 1.0.4
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__/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/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 +202 -75
- package/onecode/agent/session.py +8 -13
- package/onecode/agent/tools/__pycache__/agent_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/tasks/manager.py +1 -1
- package/package.json +1 -1
- package/pyproject.toml +10 -0
- package/run.js +24 -7
- package/tui/__pycache__/agents.cpython-314.pyc +0 -0
- package/tui/__pycache__/app.cpython-314.pyc +0 -0
- package/tui/__pycache__/db.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/__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 +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/agents.py +8 -7
- package/tui/app.py +60 -8
- package/tui/data/agents/ampcode.com.toml +1 -0
- package/tui/data/agents/augmentcode.com.toml +1 -0
- package/tui/data/agents/cdh.cloud-dev-harness.toml +3 -2
- package/tui/data/agents/crowai.dev.toml +1 -0
- package/tui/data/agents/deepagents.langchain.com +1 -0
- package/tui/data/agents/docker.com.toml +1 -0
- package/tui/data/agents/goose.ai.toml +1 -0
- package/tui/data/agents/inference.huggingface.co.toml +1 -1
- package/tui/data/agents/kiro.dev.toml +1 -0
- package/tui/data/agents/onecode.dev.toml +22 -0
- package/tui/data/agents/openclaw.ai.toml +1 -1
- package/tui/data/agents/openhands.dev.toml +1 -0
- package/tui/data/agents/stakpak.dev.toml +1 -0
- package/tui/data/agents/vibe.mistral.ai.toml +1 -0
- package/tui/data/agents/vtcode.dev.toml +1 -0
- 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__/main.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/screens/store.py +1 -1
- package/tui/settings_schema.py +1 -1
- 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__/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__/question.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/subagent.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 +191 -65
- 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/cdh/__pycache__/__init__.cpython-313.pyc +0 -0
- package/cdh/__pycache__/scaffold.cpython-313.pyc +0 -0
- package/onecode/__pycache__/config_screen.cpython-314.pyc +0 -0
- package/onecode/__pycache__/mock_data.cpython-314.pyc +0 -0
- package/onecode/agent/__pycache__/cdh_agent_acp.cpython-313.pyc +0 -0
- package/onecode/agent/__pycache__/cdh_agent_acp.cpython-314.pyc +0 -0
- package/onecode/agent/__pycache__/harness_skill.cpython-314.pyc +0 -0
- package/onecode/agent/tools/__pycache__/task_tools.cpython-314.pyc +0 -0
- package/onecode/agent/tools/task_tools.py +0 -249
- package/onecode/codebase/__pycache__/config.cpython-314.pyc +0 -0
- package/onecode/models/providers/__pycache__/mock_provider.cpython-314.pyc +0 -0
- package/onecode/skills/__pycache__/manager.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/screens/__pycache__/log.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__/settings.cpython-314.pyc +0 -0
- package/tui/screens/__pycache__/store.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/acp_content.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/conversation.cpython-313.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__/mandelbrot.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/markdown_note.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__/terminal.cpython-313.pyc +0 -0
- package/tui/widgets/__pycache__/tool_call.cpython-313.pyc +0 -0
package/tui/widgets/question.py
CHANGED
|
@@ -247,7 +247,7 @@ class Question(containers.VerticalGroup, can_focus=True):
|
|
|
247
247
|
self.options = ask.options
|
|
248
248
|
self.selection = 0
|
|
249
249
|
self.selected = False
|
|
250
|
-
self.refresh(recompose=True
|
|
250
|
+
self.refresh(recompose=True)
|
|
251
251
|
|
|
252
252
|
def compose(self) -> ComposeResult:
|
|
253
253
|
|
package/tui/widgets/subagent.py
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from typing import ClassVar
|
|
4
|
+
|
|
5
|
+
from textual import containers
|
|
3
6
|
from textual.app import ComposeResult
|
|
7
|
+
from textual.binding import Binding, BindingType
|
|
8
|
+
from textual.content import Content
|
|
4
9
|
from textual.reactive import var
|
|
5
|
-
from textual import
|
|
6
|
-
from textual.widgets import Static, Markdown
|
|
10
|
+
from textual.widgets import Static
|
|
7
11
|
|
|
8
|
-
from tui.
|
|
12
|
+
from tui.protocol import ExpandProtocol
|
|
9
13
|
|
|
10
14
|
|
|
11
15
|
class SubAgentHeader(Static):
|
|
@@ -18,70 +22,232 @@ class SubAgentHeader(Static):
|
|
|
18
22
|
"""
|
|
19
23
|
|
|
20
24
|
|
|
21
|
-
class
|
|
22
|
-
|
|
23
|
-
SubAgentContent {
|
|
24
|
-
margin: 0 1 0 2;
|
|
25
|
-
}
|
|
26
|
-
"""
|
|
25
|
+
class SubAgent(containers.VerticalGroup, can_focus=True):
|
|
26
|
+
"""Sub-agent compact view — animated header + latest line with ctrl+x hint.
|
|
27
27
|
|
|
28
|
+
During streaming: header shows a spinner + latest line updates live.
|
|
29
|
+
After completion: header shows static marker.
|
|
30
|
+
Ctrl+X to open full-screen SubAgentScreen with full structured output.
|
|
31
|
+
"""
|
|
28
32
|
|
|
29
|
-
class SubAgent(containers.VerticalGroup):
|
|
30
33
|
DEFAULT_CLASSES = "block"
|
|
34
|
+
|
|
35
|
+
HELP = """
|
|
36
|
+
## Sub-agent
|
|
37
|
+
|
|
38
|
+
- **ctrl+x** Open full-screen output
|
|
39
|
+
- **up/down** Scroll (if content overflows)
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
BINDINGS: ClassVar[list[BindingType]] = [
|
|
43
|
+
Binding("ctrl+x", "toggle", "Full screen", show=False, priority=True),
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
ALLOW_MAXIMIZE = True
|
|
47
|
+
|
|
31
48
|
DEFAULT_CSS = """
|
|
32
49
|
SubAgent {
|
|
33
|
-
margin: 0 0 0 1;
|
|
34
50
|
height: auto;
|
|
51
|
+
width: 1fr;
|
|
52
|
+
min-height: 2;
|
|
53
|
+
padding: 0 1;
|
|
54
|
+
border-left: thick $secondary;
|
|
55
|
+
background: $surface-darken-1;
|
|
56
|
+
|
|
35
57
|
SubAgentHeader {
|
|
36
58
|
color: $text-secondary;
|
|
59
|
+
text-style: bold;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
&.-status-running {
|
|
63
|
+
border-left: thick $warning;
|
|
64
|
+
background: $boost;
|
|
65
|
+
SubAgentHeader {
|
|
66
|
+
color: $warning;
|
|
67
|
+
text-style: bold italic;
|
|
68
|
+
}
|
|
69
|
+
#subagent-latest {
|
|
70
|
+
color: $text;
|
|
71
|
+
}
|
|
37
72
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
73
|
+
|
|
74
|
+
&.-status-completed {
|
|
75
|
+
border-left: thick $success;
|
|
76
|
+
SubAgentHeader {
|
|
77
|
+
color: $success;
|
|
78
|
+
}
|
|
79
|
+
#subagent-latest {
|
|
80
|
+
color: $text-muted;
|
|
81
|
+
}
|
|
41
82
|
}
|
|
42
|
-
|
|
43
|
-
|
|
83
|
+
|
|
84
|
+
&.-status-failed {
|
|
85
|
+
border-left: thick $error;
|
|
86
|
+
SubAgentHeader {
|
|
87
|
+
color: $error;
|
|
88
|
+
}
|
|
89
|
+
#subagent-latest {
|
|
90
|
+
color: $error;
|
|
91
|
+
}
|
|
44
92
|
}
|
|
45
93
|
}
|
|
46
94
|
"""
|
|
47
95
|
|
|
48
|
-
agent_type: var[str] = var("")
|
|
49
96
|
status: var[str] = var("running")
|
|
97
|
+
latest_line: var[str] = var("")
|
|
98
|
+
spinner_frame: var[int] = var(0)
|
|
99
|
+
|
|
100
|
+
SPINNER_FRAMES = ("⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏")
|
|
50
101
|
|
|
51
102
|
def __init__(
|
|
52
103
|
self,
|
|
53
104
|
agent_type: str,
|
|
54
105
|
tool_id: str | None = None,
|
|
106
|
+
prompt: str = "",
|
|
55
107
|
) -> None:
|
|
56
108
|
super().__init__(id=tool_id)
|
|
57
109
|
self.agent_type = agent_type
|
|
58
|
-
self.
|
|
110
|
+
self.prompt = prompt
|
|
59
111
|
self._chunks: list[str] = []
|
|
112
|
+
self._thinking_chunks: list[str] = []
|
|
113
|
+
self._completed = False
|
|
114
|
+
self._status = "running"
|
|
115
|
+
self._error = ""
|
|
116
|
+
self._mounted = False
|
|
117
|
+
self._spin_timer = None
|
|
60
118
|
|
|
61
119
|
def compose(self) -> ComposeResult:
|
|
62
|
-
self.set_class(True, f"-status-{self.
|
|
63
|
-
|
|
64
|
-
yield
|
|
65
|
-
f"🧠 Sub‑agent: {self.agent_type} {label}",
|
|
66
|
-
markup=False,
|
|
67
|
-
)
|
|
68
|
-
yield SubAgentContent("")
|
|
120
|
+
self.set_class(True, f"-status-{self._status}")
|
|
121
|
+
yield SubAgentHeader(self._header_text(), id="subagent-header")
|
|
122
|
+
yield Static(self._latest_content, id="subagent-latest")
|
|
69
123
|
|
|
70
|
-
def
|
|
71
|
-
self.
|
|
124
|
+
def on_mount(self) -> None:
|
|
125
|
+
self._mounted = True
|
|
126
|
+
self.styles.height = "auto"
|
|
127
|
+
self.styles.min_height = 2
|
|
128
|
+
self._start_spinner()
|
|
129
|
+
self._refresh()
|
|
130
|
+
|
|
131
|
+
def _start_spinner(self) -> None:
|
|
132
|
+
if self._spin_timer is not None:
|
|
133
|
+
return
|
|
134
|
+
self._spin_timer = self.set_interval(0.1, self._tick_spinner)
|
|
135
|
+
|
|
136
|
+
def _stop_spinner(self) -> None:
|
|
137
|
+
if self._spin_timer is not None:
|
|
138
|
+
self._spin_timer.stop()
|
|
139
|
+
self._spin_timer = None
|
|
140
|
+
|
|
141
|
+
def _tick_spinner(self) -> None:
|
|
142
|
+
if self._status == "running":
|
|
143
|
+
self.spinner_frame = (self.spinner_frame + 1) % len(self.SPINNER_FRAMES)
|
|
144
|
+
self._refresh_header()
|
|
145
|
+
|
|
146
|
+
def watch_status(self, _old: str, _new: str) -> None:
|
|
147
|
+
if _new != "running":
|
|
148
|
+
self._stop_spinner()
|
|
149
|
+
|
|
150
|
+
def _refresh(self) -> None:
|
|
151
|
+
if not self._mounted:
|
|
152
|
+
return
|
|
72
153
|
try:
|
|
73
|
-
|
|
74
|
-
|
|
154
|
+
self.set_class(True, f"-status-{self._status}")
|
|
155
|
+
if self._status != "running":
|
|
156
|
+
self.set_class(False, "-status-running")
|
|
157
|
+
self.query_one("#subagent-header", SubAgentHeader).update(self._header_text())
|
|
158
|
+
self.query_one("#subagent-latest", Static).update(self._latest_content)
|
|
75
159
|
except Exception:
|
|
76
160
|
pass
|
|
77
161
|
|
|
78
|
-
def
|
|
79
|
-
self.
|
|
80
|
-
|
|
81
|
-
self.set_class(False, "-status-running")
|
|
162
|
+
def _refresh_header(self) -> None:
|
|
163
|
+
if not self._mounted:
|
|
164
|
+
return
|
|
82
165
|
try:
|
|
83
|
-
self.query_one(SubAgentHeader).update(
|
|
84
|
-
f"🧠 Sub‑agent: {self.agent_type} — completed ✅"
|
|
85
|
-
)
|
|
166
|
+
self.query_one("#subagent-header", SubAgentHeader).update(self._header_text())
|
|
86
167
|
except Exception:
|
|
87
168
|
pass
|
|
169
|
+
|
|
170
|
+
def _header_text(self) -> str:
|
|
171
|
+
label = self.agent_type
|
|
172
|
+
status_tag = self._status_label()
|
|
173
|
+
if self._status == "running":
|
|
174
|
+
spin = self.SPINNER_FRAMES[self.spinner_frame]
|
|
175
|
+
return f"{spin} @ {label} [{status_tag}]"
|
|
176
|
+
elif self._status == "completed":
|
|
177
|
+
return f"✓ @ {label} [{status_tag}]"
|
|
178
|
+
elif self._status == "failed":
|
|
179
|
+
return f"✗ @ {label} [{status_tag}]"
|
|
180
|
+
return f"@ {label} [{status_tag}]"
|
|
181
|
+
|
|
182
|
+
@property
|
|
183
|
+
def _latest_content(self) -> Content:
|
|
184
|
+
text = (self.latest_line or "").strip()
|
|
185
|
+
hint = Content.from_markup(" [$text-muted]ctrl+x 展开查看")
|
|
186
|
+
if text:
|
|
187
|
+
return Content.assemble(text, hint)
|
|
188
|
+
return Content.from_markup("[$text-muted]⏳ 等待输出… ctrl+x 展开查看")
|
|
189
|
+
|
|
190
|
+
def _status_label(self) -> str:
|
|
191
|
+
if self._status == "running":
|
|
192
|
+
return "running"
|
|
193
|
+
elif self._status == "failed":
|
|
194
|
+
return f"failed{': ' + self._error[:40] if self._error else ''}"
|
|
195
|
+
return "completed"
|
|
196
|
+
|
|
197
|
+
def _update_latest_line(self) -> str:
|
|
198
|
+
MAX_LEN = 50
|
|
199
|
+
full = "".join(self._chunks)
|
|
200
|
+
lines = full.split("\n")
|
|
201
|
+
for line in reversed(lines):
|
|
202
|
+
line = line.strip()
|
|
203
|
+
if line:
|
|
204
|
+
if len(line) > MAX_LEN:
|
|
205
|
+
return line[:MAX_LEN] + "..."
|
|
206
|
+
return line
|
|
207
|
+
return ""
|
|
208
|
+
|
|
209
|
+
def append_chunk(self, text: str) -> None:
|
|
210
|
+
self._chunks.append(text)
|
|
211
|
+
self.latest_line = self._update_latest_line()
|
|
212
|
+
self._refresh_latest()
|
|
213
|
+
|
|
214
|
+
def append_thinking(self, text: str) -> None:
|
|
215
|
+
self._thinking_chunks.append(text)
|
|
216
|
+
|
|
217
|
+
def complete(self, status: str = "completed", error: str = "") -> None:
|
|
218
|
+
if self._completed:
|
|
219
|
+
return
|
|
220
|
+
self._completed = True
|
|
221
|
+
self._status = status
|
|
222
|
+
self._error = error
|
|
223
|
+
if status == "failed":
|
|
224
|
+
self.latest_line = f"Failed: {error[:60]}" if error else "Failed"
|
|
225
|
+
else:
|
|
226
|
+
self.latest_line = self._update_latest_line()
|
|
227
|
+
self._stop_spinner()
|
|
228
|
+
self._refresh()
|
|
229
|
+
|
|
230
|
+
def _refresh_latest(self) -> None:
|
|
231
|
+
if not self._mounted:
|
|
232
|
+
return
|
|
233
|
+
try:
|
|
234
|
+
self.query_one("#subagent-latest", Static).update(self._latest_content)
|
|
235
|
+
except Exception:
|
|
236
|
+
pass
|
|
237
|
+
|
|
238
|
+
def can_expand(self) -> bool:
|
|
239
|
+
return True
|
|
240
|
+
|
|
241
|
+
def expand_block(self) -> None:
|
|
242
|
+
self.action_toggle()
|
|
243
|
+
|
|
244
|
+
def collapse_block(self) -> None:
|
|
245
|
+
pass
|
|
246
|
+
|
|
247
|
+
def is_block_expanded(self) -> bool:
|
|
248
|
+
return False
|
|
249
|
+
|
|
250
|
+
def action_toggle(self) -> None:
|
|
251
|
+
"""Ctrl+X: open full-screen view (works even while running)."""
|
|
252
|
+
from tui.widgets.subagent_screen import SubAgentScreen
|
|
253
|
+
self.app.push_screen(SubAgentScreen(self))
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from textual.app import ComposeResult
|
|
4
|
+
from textual.binding import Binding, BindingType
|
|
5
|
+
from textual.containers import Vertical
|
|
6
|
+
from textual.screen import Screen
|
|
7
|
+
from textual.widgets import Header, Markdown, Static
|
|
8
|
+
|
|
9
|
+
from tui.widgets.subagent import SubAgent
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class SubAgentScreen(Screen):
|
|
13
|
+
"""Full-screen view of a SubAgent output.
|
|
14
|
+
|
|
15
|
+
Displays the complete structured output (SUMMARY/CHANGES/EVIDENCE/RISKS/BLOCKERS)
|
|
16
|
+
as Markdown. Esc to close, Up/Down/PageUp/PageDown to scroll.
|
|
17
|
+
Streams live while the SubAgent is running via a refresh timer.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
BINDINGS: list[BindingType] = [
|
|
21
|
+
Binding("escape", "app.pop_screen", "Back", priority=True),
|
|
22
|
+
Binding("up", "scroll_up", "Up", priority=True),
|
|
23
|
+
Binding("down", "scroll_down", "Down", priority=True),
|
|
24
|
+
Binding("pageup", "scroll_page_up", "Page Up", priority=True),
|
|
25
|
+
Binding("pagedown", "scroll_page_down", "Page Down", priority=True),
|
|
26
|
+
Binding("home", "scroll_home", "Home", priority=True),
|
|
27
|
+
Binding("end", "scroll_end", "End", priority=True),
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
DEFAULT_CSS = """
|
|
31
|
+
SubAgentScreen {
|
|
32
|
+
align: center middle;
|
|
33
|
+
|
|
34
|
+
#subagent-screen-frame {
|
|
35
|
+
width: 95%;
|
|
36
|
+
max-width: 160;
|
|
37
|
+
height: 95%;
|
|
38
|
+
border: thick $primary;
|
|
39
|
+
background: $surface;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
SubAgentScreenFooter {
|
|
43
|
+
dock: bottom;
|
|
44
|
+
height: 1;
|
|
45
|
+
padding: 0 1;
|
|
46
|
+
color: $text-muted;
|
|
47
|
+
background: $boost;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
#subagent-full-content {
|
|
51
|
+
padding: 0 1;
|
|
52
|
+
height: 1fr;
|
|
53
|
+
overflow-y: auto;
|
|
54
|
+
scrollbar-gutter: stable;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
def __init__(self, subagent: SubAgent) -> None:
|
|
60
|
+
super().__init__()
|
|
61
|
+
self.subagent = subagent
|
|
62
|
+
self._auto_follow = True
|
|
63
|
+
self._refresh_timer = None
|
|
64
|
+
self._last_content_hash: str = ""
|
|
65
|
+
|
|
66
|
+
def compose(self) -> ComposeResult:
|
|
67
|
+
yield Header(show_clock=False)
|
|
68
|
+
with Vertical(id="subagent-screen-frame"):
|
|
69
|
+
yield Markdown(self._build_content(), id="subagent-full-content")
|
|
70
|
+
yield Static(
|
|
71
|
+
"[$text-muted]esc[/] 返回 "
|
|
72
|
+
"[$text-muted]↑↓[/] 滚动 "
|
|
73
|
+
"[$text-muted]pageup/pagedown[/] 翻页 "
|
|
74
|
+
"[$text-muted]end[/] 跟随最新",
|
|
75
|
+
id="subagent-screen-footer",
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
def on_mount(self) -> None:
|
|
79
|
+
self.title = "subagent"
|
|
80
|
+
self.sub_title = f"@{self.subagent.agent_type} [{self.subagent._status}]"
|
|
81
|
+
self._last_content_hash = self._content_signature()
|
|
82
|
+
self._refresh_content(scroll_to_end=True)
|
|
83
|
+
self._refresh_timer = self.set_interval(0.2, self._poll_subagent)
|
|
84
|
+
|
|
85
|
+
def on_unmount(self) -> None:
|
|
86
|
+
if self._refresh_timer is not None:
|
|
87
|
+
self._refresh_timer.stop()
|
|
88
|
+
self._refresh_timer = None
|
|
89
|
+
|
|
90
|
+
def _content_signature(self) -> str:
|
|
91
|
+
sa = self.subagent
|
|
92
|
+
return f"{sa._status}|{sa.spinner_frame}|{len(sa._chunks)}|{len(sa._thinking_chunks)}|{sa._error}"
|
|
93
|
+
|
|
94
|
+
def _poll_subagent(self) -> None:
|
|
95
|
+
sig = self._content_signature()
|
|
96
|
+
if sig != self._last_content_hash:
|
|
97
|
+
self._last_content_hash = sig
|
|
98
|
+
self._refresh_content(scroll_to_end=self._auto_follow)
|
|
99
|
+
self.sub_title = f"@{self.subagent.agent_type} [{self.subagent._status}]"
|
|
100
|
+
if self.subagent._status != "running" and self._refresh_timer is not None:
|
|
101
|
+
self._refresh_timer.stop()
|
|
102
|
+
self._refresh_timer = None
|
|
103
|
+
|
|
104
|
+
def _refresh_content(self, scroll_to_end: bool = False) -> None:
|
|
105
|
+
try:
|
|
106
|
+
md = self.query_one("#subagent-full-content", Markdown)
|
|
107
|
+
md.update(self._build_content())
|
|
108
|
+
if scroll_to_end:
|
|
109
|
+
self.call_after_refresh(md.scroll_end)
|
|
110
|
+
except Exception:
|
|
111
|
+
pass
|
|
112
|
+
|
|
113
|
+
def action_scroll_up(self) -> None:
|
|
114
|
+
self._auto_follow = False
|
|
115
|
+
try:
|
|
116
|
+
self.query_one("#subagent-full-content", Markdown).scroll_up()
|
|
117
|
+
except Exception:
|
|
118
|
+
pass
|
|
119
|
+
|
|
120
|
+
def action_scroll_down(self) -> None:
|
|
121
|
+
try:
|
|
122
|
+
md = self.query_one("#subagent-full-content", Markdown)
|
|
123
|
+
md.scroll_down()
|
|
124
|
+
except Exception:
|
|
125
|
+
pass
|
|
126
|
+
|
|
127
|
+
def action_scroll_page_up(self) -> None:
|
|
128
|
+
self._auto_follow = False
|
|
129
|
+
try:
|
|
130
|
+
self.query_one("#subagent-full-content", Markdown).scroll_page_up()
|
|
131
|
+
except Exception:
|
|
132
|
+
pass
|
|
133
|
+
|
|
134
|
+
def action_scroll_page_down(self) -> None:
|
|
135
|
+
try:
|
|
136
|
+
md = self.query_one("#subagent-full-content", Markdown)
|
|
137
|
+
md.scroll_page_down()
|
|
138
|
+
except Exception:
|
|
139
|
+
pass
|
|
140
|
+
|
|
141
|
+
def action_scroll_home(self) -> None:
|
|
142
|
+
self._auto_follow = False
|
|
143
|
+
try:
|
|
144
|
+
self.query_one("#subagent-full-content", Markdown).scroll_home()
|
|
145
|
+
except Exception:
|
|
146
|
+
pass
|
|
147
|
+
|
|
148
|
+
def action_scroll_end(self) -> None:
|
|
149
|
+
self._auto_follow = True
|
|
150
|
+
try:
|
|
151
|
+
self.query_one("#subagent-full-content", Markdown).scroll_end()
|
|
152
|
+
except Exception:
|
|
153
|
+
pass
|
|
154
|
+
|
|
155
|
+
def _build_content(self) -> str:
|
|
156
|
+
sa = self.subagent
|
|
157
|
+
parts: list[str] = []
|
|
158
|
+
parts.append(f"# @ {sa.agent_type} [{sa._status}]\n")
|
|
159
|
+
if sa.prompt:
|
|
160
|
+
prompt_preview = sa.prompt if len(sa.prompt) <= 200 else sa.prompt[:200] + "..."
|
|
161
|
+
parts.append(
|
|
162
|
+
"\n## 任务\n\n"
|
|
163
|
+
f"> {prompt_preview}\n\n"
|
|
164
|
+
"---\n"
|
|
165
|
+
)
|
|
166
|
+
if sa._thinking_chunks:
|
|
167
|
+
parts.append("## 思考\n")
|
|
168
|
+
parts.append("".join(sa._thinking_chunks))
|
|
169
|
+
parts.append("\n\n---\n\n")
|
|
170
|
+
raw = "".join(sa._chunks)
|
|
171
|
+
if sa._status == "failed":
|
|
172
|
+
parts.append(
|
|
173
|
+
f"## ❌ Failed\n\n"
|
|
174
|
+
f"**Error:** {sa._error}\n\n"
|
|
175
|
+
"---\n\n"
|
|
176
|
+
"### Partial output\n\n"
|
|
177
|
+
f"```\n{(raw or '(none)')[:4000]}\n```"
|
|
178
|
+
)
|
|
179
|
+
elif raw:
|
|
180
|
+
parts.append("## 输出\n\n")
|
|
181
|
+
parts.append(raw)
|
|
182
|
+
if sa._status == "running":
|
|
183
|
+
parts.append("\n\n*…streaming*")
|
|
184
|
+
else:
|
|
185
|
+
parts.append("## 输出\n\n_(streaming…)_")
|
|
186
|
+
return "".join(parts)
|
package/tui/widgets/tool_call.py
CHANGED
|
@@ -207,17 +207,14 @@ class ToolCall(containers.VerticalGroup):
|
|
|
207
207
|
self.expanded = status in ("completed", "failed")
|
|
208
208
|
|
|
209
209
|
_TOOL_ICONS: dict[str, str] = {
|
|
210
|
-
"
|
|
210
|
+
"Spawn": "🧠",
|
|
211
211
|
"Agent": "🧠",
|
|
212
|
-
"TaskCreate": "📋",
|
|
213
|
-
"TaskGet": "📋",
|
|
214
|
-
"TaskList": "📋",
|
|
215
|
-
"TaskUpdate": "📋",
|
|
216
|
-
"TaskOutput": "📋",
|
|
217
|
-
"TaskStop": "📋",
|
|
218
212
|
"TodoCreate": "📋",
|
|
213
|
+
"TodoGet": "📋",
|
|
219
214
|
"TodoList": "📋",
|
|
220
|
-
"
|
|
215
|
+
"TodoUpdate": "📋",
|
|
216
|
+
"TodoOutput": "📋",
|
|
217
|
+
"TodoStop": "📋",
|
|
221
218
|
}
|
|
222
219
|
|
|
223
220
|
@property
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|