@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.
Files changed (147) hide show
  1. package/cdh/__pycache__/cli.cpython-314.pyc +0 -0
  2. package/cdh/__pycache__/scaffold.cpython-314.pyc +0 -0
  3. package/cdh/cli.py +473 -87
  4. package/cdh/scaffold.py +386 -121
  5. package/onecode/__pycache__/cli.cpython-314.pyc +0 -0
  6. package/onecode/__pycache__/commands.cpython-314.pyc +0 -0
  7. package/onecode/__pycache__/config.cpython-314.pyc +0 -0
  8. package/onecode/agent/__pycache__/cdh_loader.cpython-314.pyc +0 -0
  9. package/onecode/agent/__pycache__/engine.cpython-314.pyc +0 -0
  10. package/onecode/agent/__pycache__/onecode_agent_acp.cpython-314.pyc +0 -0
  11. package/onecode/agent/__pycache__/session.cpython-314.pyc +0 -0
  12. package/onecode/agent/agents/__pycache__/types.cpython-314.pyc +0 -0
  13. package/onecode/agent/agents/types.py +123 -79
  14. package/onecode/agent/cdh_loader.py +26 -13
  15. package/onecode/agent/engine.py +426 -235
  16. package/onecode/agent/onecode_agent_acp.py +202 -75
  17. package/onecode/agent/session.py +8 -13
  18. package/onecode/agent/tools/__pycache__/agent_tools.cpython-314.pyc +0 -0
  19. package/onecode/agent/tools/__pycache__/todo_tools.cpython-314.pyc +0 -0
  20. package/onecode/agent/tools/agent_tools.py +4 -4
  21. package/onecode/agent/tools/todo_tools.py +188 -0
  22. package/onecode/cli.py +3 -3
  23. package/onecode/commands.py +439 -194
  24. package/onecode/config.py +3 -3
  25. package/onecode/config_screen.py +4 -2
  26. package/onecode/models/__pycache__/errors.cpython-314.pyc +0 -0
  27. package/onecode/models/__pycache__/messages.cpython-314.pyc +0 -0
  28. package/onecode/models/__pycache__/provider.cpython-314.pyc +0 -0
  29. package/onecode/models/errors.py +11 -1
  30. package/onecode/models/messages.py +35 -16
  31. package/onecode/models/provider.py +243 -12
  32. package/onecode/models/providers/__pycache__/anthropic_provider.cpython-314.pyc +0 -0
  33. package/onecode/models/providers/__pycache__/deepseek_provider.cpython-314.pyc +0 -0
  34. package/onecode/models/providers/__pycache__/glm_provider.cpython-314.pyc +0 -0
  35. package/onecode/models/providers/__pycache__/minimax_provider.cpython-314.pyc +0 -0
  36. package/onecode/models/providers/__pycache__/minimaxi_provider.cpython-314.pyc +0 -0
  37. package/onecode/models/providers/__pycache__/ollama_provider.cpython-314.pyc +0 -0
  38. package/onecode/models/providers/__pycache__/openai_provider.cpython-314.pyc +0 -0
  39. package/onecode/models/providers/anthropic_provider.py +4 -2
  40. package/onecode/models/providers/deepseek_provider.py +2 -0
  41. package/onecode/models/providers/glm_provider.py +2 -1
  42. package/onecode/models/providers/minimax_provider.py +2 -1
  43. package/onecode/models/providers/minimaxi_provider.py +76 -25
  44. package/onecode/models/providers/ollama_provider.py +2 -1
  45. package/onecode/models/providers/openai_provider.py +4 -0
  46. package/onecode/tasks/manager.py +1 -1
  47. package/package.json +1 -1
  48. package/pyproject.toml +10 -0
  49. package/run.js +24 -7
  50. package/tui/__pycache__/agents.cpython-314.pyc +0 -0
  51. package/tui/__pycache__/app.cpython-314.pyc +0 -0
  52. package/tui/__pycache__/db.cpython-314.pyc +0 -0
  53. package/tui/__pycache__/directory_watcher.cpython-314.pyc +0 -0
  54. package/tui/__pycache__/messages.cpython-314.pyc +0 -0
  55. package/tui/__pycache__/paths.cpython-314.pyc +0 -0
  56. package/tui/__pycache__/settings_schema.cpython-314.pyc +0 -0
  57. package/tui/acp/__pycache__/agent.cpython-314.pyc +0 -0
  58. package/tui/acp/__pycache__/api.cpython-314.pyc +0 -0
  59. package/tui/acp/__pycache__/messages.cpython-314.pyc +0 -0
  60. package/tui/acp/__pycache__/prompt.cpython-314.pyc +0 -0
  61. package/tui/acp/__pycache__/protocol.cpython-314.pyc +0 -0
  62. package/tui/acp/agent.py +60 -11
  63. package/tui/acp/api.py +6 -0
  64. package/tui/acp/messages.py +11 -0
  65. package/tui/acp/prompt.py +1 -1
  66. package/tui/acp/protocol.py +31 -0
  67. package/tui/agents.py +8 -7
  68. package/tui/app.py +60 -8
  69. package/tui/data/agents/ampcode.com.toml +1 -0
  70. package/tui/data/agents/augmentcode.com.toml +1 -0
  71. package/tui/data/agents/cdh.cloud-dev-harness.toml +3 -2
  72. package/tui/data/agents/crowai.dev.toml +1 -0
  73. package/tui/data/agents/deepagents.langchain.com +1 -0
  74. package/tui/data/agents/docker.com.toml +1 -0
  75. package/tui/data/agents/goose.ai.toml +1 -0
  76. package/tui/data/agents/inference.huggingface.co.toml +1 -1
  77. package/tui/data/agents/kiro.dev.toml +1 -0
  78. package/tui/data/agents/onecode.dev.toml +22 -0
  79. package/tui/data/agents/openclaw.ai.toml +1 -1
  80. package/tui/data/agents/openhands.dev.toml +1 -0
  81. package/tui/data/agents/stakpak.dev.toml +1 -0
  82. package/tui/data/agents/vibe.mistral.ai.toml +1 -0
  83. package/tui/data/agents/vtcode.dev.toml +1 -0
  84. package/tui/db.py +23 -0
  85. package/tui/directory_watcher.py +35 -1
  86. package/tui/messages.py +4 -1
  87. package/tui/paths.py +3 -3
  88. package/tui/screens/__pycache__/main.cpython-314.pyc +0 -0
  89. package/tui/screens/component_picker.py +148 -0
  90. package/tui/screens/main.py +47 -95
  91. package/tui/screens/projects.tcss +10 -0
  92. package/tui/screens/projects_app.py +33 -5
  93. package/tui/screens/projects_screen.py +171 -9
  94. package/tui/screens/store.py +1 -1
  95. package/tui/settings_schema.py +1 -1
  96. package/tui/widgets/__pycache__/ask_user.cpython-314.pyc +0 -0
  97. package/tui/widgets/__pycache__/conversation.cpython-314.pyc +0 -0
  98. package/tui/widgets/__pycache__/modified_files.cpython-314.pyc +0 -0
  99. package/tui/widgets/__pycache__/plan.cpython-314.pyc +0 -0
  100. package/tui/widgets/__pycache__/project_directory_tree.cpython-314.pyc +0 -0
  101. package/tui/widgets/__pycache__/question.cpython-314.pyc +0 -0
  102. package/tui/widgets/__pycache__/subagent.cpython-314.pyc +0 -0
  103. package/tui/widgets/__pycache__/tool_call.cpython-314.pyc +0 -0
  104. package/tui/widgets/ask_user.py +1 -1
  105. package/tui/widgets/conversation.py +99 -42
  106. package/tui/widgets/modified_files.py +191 -65
  107. package/tui/widgets/plan.py +22 -0
  108. package/tui/widgets/project_directory_tree.py +22 -10
  109. package/tui/widgets/project_grid_select.py +23 -1
  110. package/tui/widgets/question.py +1 -1
  111. package/tui/widgets/subagent.py +202 -36
  112. package/tui/widgets/subagent_screen.py +186 -0
  113. package/tui/widgets/tool_call.py +5 -8
  114. package/cdh/__pycache__/__init__.cpython-313.pyc +0 -0
  115. package/cdh/__pycache__/scaffold.cpython-313.pyc +0 -0
  116. package/onecode/__pycache__/config_screen.cpython-314.pyc +0 -0
  117. package/onecode/__pycache__/mock_data.cpython-314.pyc +0 -0
  118. package/onecode/agent/__pycache__/cdh_agent_acp.cpython-313.pyc +0 -0
  119. package/onecode/agent/__pycache__/cdh_agent_acp.cpython-314.pyc +0 -0
  120. package/onecode/agent/__pycache__/harness_skill.cpython-314.pyc +0 -0
  121. package/onecode/agent/tools/__pycache__/task_tools.cpython-314.pyc +0 -0
  122. package/onecode/agent/tools/task_tools.py +0 -249
  123. package/onecode/codebase/__pycache__/config.cpython-314.pyc +0 -0
  124. package/onecode/models/providers/__pycache__/mock_provider.cpython-314.pyc +0 -0
  125. package/onecode/skills/__pycache__/manager.cpython-314.pyc +0 -0
  126. package/tui/__pycache__/danger.cpython-314.pyc +0 -0
  127. package/tui/__pycache__/directory_suggester.cpython-314.pyc +0 -0
  128. package/tui/screens/__pycache__/log.cpython-314.pyc +0 -0
  129. package/tui/screens/__pycache__/projects_screen.cpython-314.pyc +0 -0
  130. package/tui/screens/__pycache__/sessions.cpython-314.pyc +0 -0
  131. package/tui/screens/__pycache__/settings.cpython-314.pyc +0 -0
  132. package/tui/screens/__pycache__/store.cpython-314.pyc +0 -0
  133. package/tui/widgets/__pycache__/acp_content.cpython-314.pyc +0 -0
  134. package/tui/widgets/__pycache__/conversation.cpython-313.pyc +0 -0
  135. package/tui/widgets/__pycache__/diff_view.cpython-314.pyc +0 -0
  136. package/tui/widgets/__pycache__/directory_input.cpython-314.pyc +0 -0
  137. package/tui/widgets/__pycache__/future_text.cpython-314.pyc +0 -0
  138. package/tui/widgets/__pycache__/grid_select.cpython-314.pyc +0 -0
  139. package/tui/widgets/__pycache__/mandelbrot.cpython-314.pyc +0 -0
  140. package/tui/widgets/__pycache__/markdown_note.cpython-314.pyc +0 -0
  141. package/tui/widgets/__pycache__/project_grid_select.cpython-314.pyc +0 -0
  142. package/tui/widgets/__pycache__/project_summary.cpython-314.pyc +0 -0
  143. package/tui/widgets/__pycache__/session_grid_select.cpython-314.pyc +0 -0
  144. package/tui/widgets/__pycache__/session_summary.cpython-314.pyc +0 -0
  145. package/tui/widgets/__pycache__/shell_result.cpython-314.pyc +0 -0
  146. package/tui/widgets/__pycache__/terminal.cpython-313.pyc +0 -0
  147. package/tui/widgets/__pycache__/tool_call.cpython-313.pyc +0 -0
@@ -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, layout=True)
250
+ self.refresh(recompose=True)
251
251
 
252
252
  def compose(self) -> ComposeResult:
253
253
 
@@ -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 containers
6
- from textual.widgets import Static, Markdown
10
+ from textual.widgets import Static
7
11
 
8
- from tui.pill import pill
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 SubAgentContent(Markdown):
22
- DEFAULT_CSS = """
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
- &.-status-running SubAgentHeader {
39
- color: $text-warning;
40
- text-style: italic;
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
- &.-status-completed SubAgentHeader {
43
- color: $text-success;
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.status = "running"
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.status}")
63
- label = pill(self.agent_type, "$warning-muted", "$text-warning")
64
- yield SubAgentHeader(
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 append_chunk(self, text: str) -> None:
71
- self._chunks.append(text)
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
- content = self.query_one(SubAgentContent)
74
- content.load("".join(self._chunks))
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 complete(self) -> None:
79
- self.status = "completed"
80
- self.set_class(True, "-status-completed")
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)
@@ -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
- "Task": "🧠",
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
- "TodoComplete": "📋",
215
+ "TodoUpdate": "📋",
216
+ "TodoOutput": "📋",
217
+ "TodoStop": "📋",
221
218
  }
222
219
 
223
220
  @property