zjcode 0.0.1__py3-none-any.whl

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 (163) hide show
  1. deepagents_code/__init__.py +42 -0
  2. deepagents_code/__main__.py +6 -0
  3. deepagents_code/_ask_user_types.py +90 -0
  4. deepagents_code/_cli_context.py +96 -0
  5. deepagents_code/_constants.py +41 -0
  6. deepagents_code/_debug.py +148 -0
  7. deepagents_code/_debug_buffer.py +204 -0
  8. deepagents_code/_env_vars.py +411 -0
  9. deepagents_code/_fake_models.py +66 -0
  10. deepagents_code/_git.py +521 -0
  11. deepagents_code/_paths.py +69 -0
  12. deepagents_code/_server_config.py +576 -0
  13. deepagents_code/_session_stats.py +235 -0
  14. deepagents_code/_startup_error.py +45 -0
  15. deepagents_code/_testing_models.py +305 -0
  16. deepagents_code/_textual_patches.py +420 -0
  17. deepagents_code/_tool_stream.py +694 -0
  18. deepagents_code/_version.py +46 -0
  19. deepagents_code/agent.py +1976 -0
  20. deepagents_code/app.py +19239 -0
  21. deepagents_code/app.tcss +438 -0
  22. deepagents_code/approval_mode.py +131 -0
  23. deepagents_code/ask_user.py +306 -0
  24. deepagents_code/auth_display.py +185 -0
  25. deepagents_code/auth_store.py +545 -0
  26. deepagents_code/built_in_skills/__init__.py +5 -0
  27. deepagents_code/built_in_skills/remember/SKILL.md +118 -0
  28. deepagents_code/built_in_skills/skill-creator/SKILL.md +383 -0
  29. deepagents_code/built_in_skills/skill-creator/scripts/init_skill.py +366 -0
  30. deepagents_code/built_in_skills/skill-creator/scripts/quick_validate.py +158 -0
  31. deepagents_code/client/__init__.py +1 -0
  32. deepagents_code/client/commands/__init__.py +1 -0
  33. deepagents_code/client/commands/auth.py +541 -0
  34. deepagents_code/client/commands/config.py +714 -0
  35. deepagents_code/client/commands/mcp.py +250 -0
  36. deepagents_code/client/commands/tools.py +416 -0
  37. deepagents_code/client/launch/__init__.py +1 -0
  38. deepagents_code/client/launch/server.py +978 -0
  39. deepagents_code/client/launch/server_manager.py +540 -0
  40. deepagents_code/client/non_interactive.py +1758 -0
  41. deepagents_code/client/remote_client.py +794 -0
  42. deepagents_code/clipboard.py +217 -0
  43. deepagents_code/command_registry.py +450 -0
  44. deepagents_code/config.py +4829 -0
  45. deepagents_code/config_manifest.py +1451 -0
  46. deepagents_code/configurable_model.py +577 -0
  47. deepagents_code/default_agent_prompt.md +12 -0
  48. deepagents_code/doctor.py +559 -0
  49. deepagents_code/editor.py +142 -0
  50. deepagents_code/event_bus.py +411 -0
  51. deepagents_code/extras_info.py +661 -0
  52. deepagents_code/file_ops.py +576 -0
  53. deepagents_code/formatting.py +135 -0
  54. deepagents_code/goal_rubric.py +497 -0
  55. deepagents_code/goal_tools.py +459 -0
  56. deepagents_code/hooks.py +348 -0
  57. deepagents_code/input.py +1041 -0
  58. deepagents_code/integrations/__init__.py +1 -0
  59. deepagents_code/integrations/openai_codex.py +551 -0
  60. deepagents_code/integrations/sandbox_config.py +198 -0
  61. deepagents_code/integrations/sandbox_factory.py +1124 -0
  62. deepagents_code/integrations/sandbox_provider.py +137 -0
  63. deepagents_code/integrations/sandbox_registry.py +350 -0
  64. deepagents_code/iterm_cursor_guide.py +176 -0
  65. deepagents_code/local_context.py +926 -0
  66. deepagents_code/main.py +3985 -0
  67. deepagents_code/managed_tools.py +642 -0
  68. deepagents_code/mcp_auth.py +1950 -0
  69. deepagents_code/mcp_config.py +176 -0
  70. deepagents_code/mcp_disabled.py +212 -0
  71. deepagents_code/mcp_login_service.py +281 -0
  72. deepagents_code/mcp_oauth_ui.py +199 -0
  73. deepagents_code/mcp_providers/__init__.py +23 -0
  74. deepagents_code/mcp_providers/_registry.py +39 -0
  75. deepagents_code/mcp_providers/base.py +133 -0
  76. deepagents_code/mcp_providers/github.py +102 -0
  77. deepagents_code/mcp_providers/slack.py +175 -0
  78. deepagents_code/mcp_tools.py +2427 -0
  79. deepagents_code/mcp_trust.py +207 -0
  80. deepagents_code/media_utils.py +826 -0
  81. deepagents_code/memory_guard.py +474 -0
  82. deepagents_code/model_config.py +4156 -0
  83. deepagents_code/notifications.py +247 -0
  84. deepagents_code/offload.py +402 -0
  85. deepagents_code/onboarding.py +223 -0
  86. deepagents_code/output.py +69 -0
  87. deepagents_code/paste_collapse.py +103 -0
  88. deepagents_code/project_utils.py +231 -0
  89. deepagents_code/py.typed +0 -0
  90. deepagents_code/reasoning_effort.py +641 -0
  91. deepagents_code/reliable_rubric.py +97 -0
  92. deepagents_code/resume_state.py +237 -0
  93. deepagents_code/server_graph.py +310 -0
  94. deepagents_code/session_end_summary.py +329 -0
  95. deepagents_code/sessions.py +1716 -0
  96. deepagents_code/skills/__init__.py +18 -0
  97. deepagents_code/skills/commands.py +1252 -0
  98. deepagents_code/skills/invocation.py +112 -0
  99. deepagents_code/skills/load.py +196 -0
  100. deepagents_code/skills/trust.py +547 -0
  101. deepagents_code/state_migration.py +136 -0
  102. deepagents_code/subagents.py +278 -0
  103. deepagents_code/system_prompt.md +204 -0
  104. deepagents_code/terminal_capabilities.py +115 -0
  105. deepagents_code/terminal_escape.py +287 -0
  106. deepagents_code/theme.py +891 -0
  107. deepagents_code/todo_list_prompt.md +12 -0
  108. deepagents_code/tool_catalog.py +509 -0
  109. deepagents_code/tool_display.py +367 -0
  110. deepagents_code/tools.py +516 -0
  111. deepagents_code/tui/__init__.py +1 -0
  112. deepagents_code/tui/textual_adapter.py +2553 -0
  113. deepagents_code/tui/widgets/__init__.py +9 -0
  114. deepagents_code/tui/widgets/_js_eval_display.py +139 -0
  115. deepagents_code/tui/widgets/_links.py +261 -0
  116. deepagents_code/tui/widgets/agent_selector.py +420 -0
  117. deepagents_code/tui/widgets/approval.py +602 -0
  118. deepagents_code/tui/widgets/ask_user.py +515 -0
  119. deepagents_code/tui/widgets/auth.py +1997 -0
  120. deepagents_code/tui/widgets/autocomplete.py +890 -0
  121. deepagents_code/tui/widgets/chat_input.py +3181 -0
  122. deepagents_code/tui/widgets/codex_auth.py +452 -0
  123. deepagents_code/tui/widgets/cwd_switch.py +242 -0
  124. deepagents_code/tui/widgets/debug_console.py +868 -0
  125. deepagents_code/tui/widgets/diff.py +252 -0
  126. deepagents_code/tui/widgets/effort_selector.py +189 -0
  127. deepagents_code/tui/widgets/goal_review.py +390 -0
  128. deepagents_code/tui/widgets/goal_status.py +50 -0
  129. deepagents_code/tui/widgets/history.py +194 -0
  130. deepagents_code/tui/widgets/install_confirm.py +248 -0
  131. deepagents_code/tui/widgets/launch_init.py +482 -0
  132. deepagents_code/tui/widgets/loading.py +227 -0
  133. deepagents_code/tui/widgets/mcp_login.py +539 -0
  134. deepagents_code/tui/widgets/mcp_reconnect.py +212 -0
  135. deepagents_code/tui/widgets/mcp_viewer.py +1634 -0
  136. deepagents_code/tui/widgets/message_store.py +999 -0
  137. deepagents_code/tui/widgets/messages.py +3846 -0
  138. deepagents_code/tui/widgets/model_selector.py +2343 -0
  139. deepagents_code/tui/widgets/notification_center.py +456 -0
  140. deepagents_code/tui/widgets/notification_detail.py +257 -0
  141. deepagents_code/tui/widgets/notification_settings.py +170 -0
  142. deepagents_code/tui/widgets/restart_prompt.py +158 -0
  143. deepagents_code/tui/widgets/skill_trust.py +131 -0
  144. deepagents_code/tui/widgets/startup_tip.py +91 -0
  145. deepagents_code/tui/widgets/status.py +781 -0
  146. deepagents_code/tui/widgets/subagent_panel.py +969 -0
  147. deepagents_code/tui/widgets/theme_selector.py +401 -0
  148. deepagents_code/tui/widgets/thread_selector.py +2564 -0
  149. deepagents_code/tui/widgets/tool_renderers.py +190 -0
  150. deepagents_code/tui/widgets/tool_widgets.py +304 -0
  151. deepagents_code/tui/widgets/update_available.py +311 -0
  152. deepagents_code/tui/widgets/update_confirm.py +184 -0
  153. deepagents_code/tui/widgets/update_progress.py +327 -0
  154. deepagents_code/tui/widgets/welcome.py +508 -0
  155. deepagents_code/turn_end_summary.py +522 -0
  156. deepagents_code/ui.py +858 -0
  157. deepagents_code/unicode_security.py +563 -0
  158. deepagents_code/update_check.py +3124 -0
  159. zjcode-0.0.1.data/data/deepagents_code/default_agent_prompt.md +12 -0
  160. zjcode-0.0.1.dist-info/METADATA +220 -0
  161. zjcode-0.0.1.dist-info/RECORD +163 -0
  162. zjcode-0.0.1.dist-info/WHEEL +4 -0
  163. zjcode-0.0.1.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,420 @@
1
+ """Interactive agent selector screen for `/agents` command."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import contextlib
7
+ import logging
8
+ from typing import TYPE_CHECKING, ClassVar
9
+
10
+ from textual.binding import Binding, BindingType
11
+ from textual.containers import Vertical
12
+ from textual.content import Content
13
+ from textual.screen import ModalScreen
14
+ from textual.widgets import OptionList, Static
15
+ from textual.widgets.option_list import Option
16
+
17
+ if TYPE_CHECKING:
18
+ from textual.app import ComposeResult
19
+
20
+ from deepagents_code import theme
21
+ from deepagents_code.config import Glyphs, get_glyphs, is_ascii_mode
22
+ from deepagents_code.model_config import clear_default_agent, save_default_agent
23
+
24
+ logger = logging.getLogger(__name__)
25
+
26
+
27
+ class AgentSelectorScreen(ModalScreen[str | None]):
28
+ """Modal dialog for switching between available agents.
29
+
30
+ Displays agents found in `~/.deepagents/` in an `OptionList`. Returns the
31
+ selected agent name on Enter, or `None` on Esc (no change).
32
+ `Ctrl+S` toggles the highlighted agent as the persisted default
33
+ (`[agents].default`), mirroring the model selector's affordance.
34
+ """
35
+
36
+ BINDINGS: ClassVar[list[BindingType]] = [
37
+ Binding("escape", "cancel", "Cancel", show=False),
38
+ Binding("tab", "cursor_down", "Next", show=False, priority=True),
39
+ Binding("shift+tab", "cursor_up", "Previous", show=False, priority=True),
40
+ Binding("ctrl+s", "set_default", "Set default", show=False, priority=True),
41
+ ]
42
+ """Key bindings for the selector.
43
+
44
+ Esc dismisses without switching agents. Arrow keys, Enter, and letter
45
+ navigation are handled natively by the embedded `OptionList`; Tab /
46
+ Shift+Tab are bound here to advance the cursor for consistency with
47
+ other selector screens. Ctrl+S toggles the highlighted agent as the
48
+ persisted default.
49
+ """
50
+
51
+ CSS = """
52
+ AgentSelectorScreen {
53
+ align: center middle;
54
+ background: transparent;
55
+ }
56
+
57
+ AgentSelectorScreen > Vertical {
58
+ width: 60;
59
+ max-width: 90%;
60
+ height: auto;
61
+ max-height: 80%;
62
+ background: $surface;
63
+ border: solid $primary;
64
+ padding: 1 2;
65
+ }
66
+
67
+ AgentSelectorScreen .agent-selector-title {
68
+ text-style: bold;
69
+ color: $primary;
70
+ text-align: center;
71
+ margin-bottom: 1;
72
+ }
73
+
74
+ AgentSelectorScreen .agent-selector-subtitle {
75
+ height: auto;
76
+ color: $text-muted;
77
+ text-align: center;
78
+ margin-bottom: 1;
79
+ }
80
+
81
+ AgentSelectorScreen OptionList {
82
+ height: auto;
83
+ max-height: 16;
84
+ background: $background;
85
+ }
86
+
87
+ AgentSelectorScreen .agent-selector-help {
88
+ height: auto;
89
+ color: $text-muted;
90
+ text-style: italic;
91
+ margin-top: 1;
92
+ text-align: center;
93
+ }
94
+ """
95
+ """Styling for the centered modal shell, title, option list, and help footer."""
96
+
97
+ def __init__(
98
+ self,
99
+ current_agent: str | None,
100
+ agent_names: list[str],
101
+ *,
102
+ default_agent: str | None = None,
103
+ ) -> None:
104
+ """Initialize the `AgentSelectorScreen`.
105
+
106
+ Args:
107
+ current_agent: The name of the currently active agent (to
108
+ highlight).
109
+
110
+ May be `None` when no agent is active.
111
+ agent_names: Sorted list of available agent names to display.
112
+ default_agent: The persisted default agent name from
113
+ `[agents].default`, or `None` if no default is set.
114
+ """
115
+ super().__init__()
116
+ self._current_agent = current_agent
117
+ self._agent_names = agent_names
118
+ self._default_agent = default_agent
119
+
120
+ def compose(self) -> ComposeResult:
121
+ """Compose the screen layout.
122
+
123
+ Yields:
124
+ Widgets for the agent selector UI.
125
+ """
126
+ glyphs = get_glyphs()
127
+
128
+ with Vertical():
129
+ yield Static("Select Agent", classes="agent-selector-title")
130
+ if self._agent_names:
131
+ yield Static(
132
+ "Switching restarts the agent and starts a new thread.",
133
+ classes="agent-selector-subtitle",
134
+ )
135
+ option_list = OptionList(
136
+ *self._build_options(),
137
+ id="agent-options",
138
+ )
139
+ option_list.highlighted = self._current_index()
140
+ yield option_list
141
+ help_text = self._help_text(glyphs)
142
+ else:
143
+ yield Static(
144
+ "No agents found in ~/.deepagents/.\n"
145
+ "Run dcode with -a <name> to create one.",
146
+ classes="agent-selector-help",
147
+ )
148
+ help_text = f"{glyphs.bullet} Esc close"
149
+ yield Static(help_text, classes="agent-selector-help", id="agent-help")
150
+
151
+ def _build_options(self) -> list[Option]:
152
+ """Build option entries with `(current)` / `(default)` suffixes.
153
+
154
+ Render labels via `Content.from_markup` so agent directory names
155
+ containing Rich markup characters (e.g. `[`) don't break rendering.
156
+
157
+ Returns:
158
+ One `Option` per agent name in `self._agent_names`.
159
+ """
160
+ return [Option(self._format_label(name), id=name) for name in self._agent_names]
161
+
162
+ def _format_label(self, name: str) -> Content:
163
+ """Render an agent's label with `(current)` / `(default)` markers.
164
+
165
+ Args:
166
+ name: The agent directory name.
167
+
168
+ Returns:
169
+ Styled `Content` label.
170
+ """
171
+ is_current = name == self._current_agent
172
+ is_default = name == self._default_agent
173
+ if is_current and is_default:
174
+ return Content.from_markup(
175
+ "$name [dim](current,[/dim] [bold]default[/bold][dim])[/dim]",
176
+ name=name,
177
+ )
178
+ if is_current:
179
+ return Content.from_markup("$name [dim](current)[/dim]", name=name)
180
+ if is_default:
181
+ return Content.from_markup(
182
+ "$name [dim]([/dim][bold]default[/bold][dim])[/dim]", name=name
183
+ )
184
+ return Content.from_markup("$name", name=name)
185
+
186
+ def _current_index(self) -> int:
187
+ """Return the index of the current agent in the option list, or 0."""
188
+ if self._current_agent is None:
189
+ return 0
190
+ try:
191
+ return self._agent_names.index(self._current_agent)
192
+ except ValueError:
193
+ return 0
194
+
195
+ @staticmethod
196
+ def _help_text(glyphs: Glyphs) -> str:
197
+ r"""Build the help-line text shown beneath the option list.
198
+
199
+ Split into two balanced rows joined by `\n` so the wrap is
200
+ predictable at the modal's fixed 60-column width — Textual's
201
+ default word-wrap might otherwise break mid-phrase (e.g.,
202
+ between "set" and "default"), which reads as a bug. The Static
203
+ host has `height: auto` and `text-align: center`, so each row
204
+ centers on its own line.
205
+
206
+ Args:
207
+ glyphs: Glyph set for the active terminal mode.
208
+
209
+ Returns:
210
+ Two-line help string describing the available key bindings.
211
+ """
212
+ return (
213
+ f"{glyphs.arrow_up}/{glyphs.arrow_down} or Tab switch"
214
+ f" {glyphs.bullet} Enter select\n"
215
+ f"Ctrl+S set default {glyphs.bullet} Esc cancel"
216
+ )
217
+
218
+ def on_mount(self) -> None:
219
+ """Apply ASCII border if needed."""
220
+ if is_ascii_mode():
221
+ container = self.query_one(Vertical)
222
+ colors = theme.get_theme_colors(self)
223
+ container.styles.border = ("ascii", colors.success)
224
+
225
+ def on_option_list_option_selected(self, event: OptionList.OptionSelected) -> None:
226
+ """Dismiss with the selected agent name.
227
+
228
+ Args:
229
+ event: The option selected event.
230
+ """
231
+ name = event.option.id
232
+ self.dismiss(name)
233
+
234
+ def action_cancel(self) -> None:
235
+ """Cancel without switching agents."""
236
+ self.dismiss(None)
237
+
238
+ def action_cursor_down(self) -> None:
239
+ """Move the option list cursor down (Tab)."""
240
+ option_list = self._option_list()
241
+ if option_list is not None:
242
+ option_list.action_cursor_down()
243
+
244
+ def action_cursor_up(self) -> None:
245
+ """Move the option list cursor up (Shift+Tab)."""
246
+ option_list = self._option_list()
247
+ if option_list is not None:
248
+ option_list.action_cursor_up()
249
+
250
+ async def action_set_default(self) -> None:
251
+ """Toggle the highlighted agent as the persisted default.
252
+
253
+ If the highlighted agent is already the default, clears it.
254
+ Otherwise sets it as the new default. Disk I/O is offloaded to a
255
+ thread so the modal stays responsive. The help line shows a
256
+ transient confirmation/error message; the option list is rebuilt
257
+ in place so the `(default)` marker tracks the new state.
258
+
259
+ Robustness notes:
260
+ * The `to_thread` call is wrapped in `try/except Exception` so
261
+ an unexpected error inside the persistence functions
262
+ (e.g., `tomli_w.dump` raising a `TypeError`) is treated as
263
+ a normal save failure rather than killing the modal.
264
+ * Post-await `query_one` is guarded against `NoMatches` so a
265
+ user dismissing the modal mid-flight does not surface a
266
+ Textual callback error.
267
+ * The option-list rebuild is staged in `_refresh_options`,
268
+ which builds the new options first and only swaps on
269
+ success. A failure mid-rebuild leaves the existing list
270
+ intact and shows an error in the help line.
271
+ """
272
+ from textual.css.query import NoMatches
273
+
274
+ option_list = self._option_list()
275
+ if option_list is None or not self._agent_names:
276
+ return
277
+
278
+ highlighted = option_list.highlighted
279
+ if highlighted is None or not (0 <= highlighted < len(self._agent_names)):
280
+ return
281
+ name = self._agent_names[highlighted]
282
+
283
+ if name == self._default_agent:
284
+ new_default: str | None = None
285
+ success_msg = "Default cleared"
286
+ failure_msg = "Failed to clear default"
287
+ try:
288
+ ok = await asyncio.to_thread(clear_default_agent)
289
+ except Exception:
290
+ logger.exception("clear_default_agent raised unexpectedly")
291
+ ok = False
292
+ else:
293
+ new_default = name
294
+ success_msg = f"Default set to {name}"
295
+ failure_msg = "Failed to save default"
296
+ try:
297
+ ok = await asyncio.to_thread(save_default_agent, name)
298
+ except Exception:
299
+ logger.exception("save_default_agent raised unexpectedly for %r", name)
300
+ ok = False
301
+
302
+ # The user may have dismissed the modal while the disk I/O was in
303
+ # flight; don't try to mutate widgets on an unmounted screen.
304
+ try:
305
+ help_widget = self.query_one("#agent-help", Static)
306
+ except NoMatches:
307
+ return
308
+
309
+ if not ok:
310
+ help_widget.update(
311
+ Content.styled(
312
+ failure_msg,
313
+ f"bold {theme.get_theme_colors(self).error}",
314
+ )
315
+ )
316
+ self.set_timer(3.0, self._restore_help_text)
317
+ return
318
+
319
+ # Apply the rebuild before mutating `_default_agent` so a failure
320
+ # leaves the picker visually consistent with on-disk state.
321
+ if not self._refresh_options(option_list, highlighted, new_default):
322
+ help_widget.update(
323
+ Content.styled(
324
+ "Failed to refresh agent list",
325
+ f"bold {theme.get_theme_colors(self).error}",
326
+ )
327
+ )
328
+ self.set_timer(3.0, self._restore_help_text)
329
+ return
330
+
331
+ self._default_agent = new_default
332
+ help_widget.update(Content.styled(success_msg, "bold"))
333
+ self.set_timer(3.0, self._restore_help_text)
334
+
335
+ def _refresh_options(
336
+ self,
337
+ option_list: OptionList,
338
+ highlighted: int,
339
+ new_default: str | None,
340
+ ) -> bool:
341
+ """Rebuild option labels in place to track the new `(default)` state.
342
+
343
+ Builds the new option list with `new_default` applied first and
344
+ only mutates the live `OptionList` once construction succeeds.
345
+ Failure mid-build leaves the existing list intact, avoiding the
346
+ catastrophic empty-picker state where `clear_options()` had
347
+ succeeded but `add_options(...)` raised.
348
+
349
+ `OptionList.clear_options()` resets the highlight to `None`, so
350
+ restore it explicitly to the previously selected row to avoid
351
+ appearing to lose the cursor after a Ctrl+S toggle.
352
+
353
+ Args:
354
+ option_list: The live `OptionList` to rebuild in place.
355
+ highlighted: Index to restore as the highlighted row.
356
+ new_default: The agent name about to become the default
357
+ (`None` for clear).
358
+
359
+ Passed in rather than read from `self._default_agent`
360
+ so the caller can decide whether to commit the new
361
+ default based on the rebuild's success.
362
+
363
+ Returns:
364
+ `True` when the rebuild applied cleanly, `False` if option
365
+ construction or mounting raised. On `False`, the live
366
+ option list has been left untouched.
367
+ """
368
+ previous_default = self._default_agent
369
+ self._default_agent = new_default
370
+ try:
371
+ new_options = self._build_options()
372
+ except Exception:
373
+ logger.exception("Failed to build new agent picker options")
374
+ self._default_agent = previous_default
375
+ return False
376
+
377
+ try:
378
+ option_list.clear_options()
379
+ option_list.add_options(new_options)
380
+ except Exception:
381
+ logger.exception("Failed to mount rebuilt agent picker options")
382
+ self._default_agent = previous_default
383
+ # Best-effort restore of the prior options so the user is
384
+ # not left staring at an empty picker.
385
+ with contextlib.suppress(Exception):
386
+ option_list.clear_options()
387
+ option_list.add_options(self._build_options())
388
+ return False
389
+
390
+ if 0 <= highlighted < len(self._agent_names):
391
+ option_list.highlighted = highlighted
392
+ # `_default_agent` is set on entry so `_build_options` reflects
393
+ # the new state; revert it here so the caller can commit only
394
+ # after the rebuild has fully succeeded.
395
+ self._default_agent = previous_default
396
+ return True
397
+
398
+ def _restore_help_text(self) -> None:
399
+ """Restore the default help text after a transient message.
400
+
401
+ Guarded against the user having dismissed the modal during the
402
+ 3-second timer; without the guard, `query_one` would raise
403
+ `NoMatches` and Textual would surface it as a callback error.
404
+ """
405
+ from textual.css.query import NoMatches
406
+
407
+ try:
408
+ help_widget = self.query_one("#agent-help", Static)
409
+ except NoMatches:
410
+ return
411
+ help_widget.update(self._help_text(get_glyphs()))
412
+
413
+ def _option_list(self) -> OptionList | None:
414
+ """Return the agent `OptionList`, or `None` when the screen is empty."""
415
+ from textual.css.query import NoMatches
416
+
417
+ try:
418
+ return self.query_one("#agent-options", OptionList)
419
+ except NoMatches:
420
+ return None