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,311 @@
1
+ """Dedicated modal for the update-available notification.
2
+
3
+ Shown automatically at startup when a newer version of
4
+ `deepagents-code` is available on PyPI. Surfaces the same actions the
5
+ notification center would offer for the update entry but with a
6
+ focused, single-purpose presentation instead of the generic
7
+ notification list.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from typing import TYPE_CHECKING, ClassVar
13
+
14
+ from textual.binding import Binding, BindingType
15
+ from textual.containers import Vertical
16
+ from textual.content import Content
17
+ from textual.message import Message
18
+ from textual.screen import ModalScreen
19
+ from textual.widgets import Static
20
+
21
+ if TYPE_CHECKING:
22
+ from textual.app import ComposeResult
23
+ from textual.events import Click
24
+
25
+ from deepagents_code.notifications import NotificationAction, PendingNotification
26
+
27
+ from deepagents_code import theme
28
+ from deepagents_code._version import CHANGELOG_URL
29
+ from deepagents_code.config import get_glyphs, is_ascii_mode
30
+ from deepagents_code.notifications import ActionId
31
+ from deepagents_code.tui.widgets._links import open_url_async
32
+
33
+
34
+ class ChangelogClicked(Message):
35
+ """Posted when the changelog row is clicked with the mouse."""
36
+
37
+
38
+ class _ActionOption(Static):
39
+ """Clickable single-line action row."""
40
+
41
+ def __init__(self, action: NotificationAction, widget_id: str) -> None:
42
+ """Initialize the action row widget.
43
+
44
+ Args:
45
+ action: The action this row represents.
46
+ widget_id: DOM id assigned to the widget.
47
+ """
48
+ super().__init__(id=widget_id, classes="ua-action")
49
+ self._action = action
50
+ self._is_selected = False
51
+ self.update(self._render())
52
+
53
+ @property
54
+ def action(self) -> NotificationAction:
55
+ """Underlying action."""
56
+ return self._action
57
+
58
+ def set_selected(self, selected: bool) -> None:
59
+ """Toggle selection styling.
60
+
61
+ Args:
62
+ selected: Whether this row is currently under the cursor.
63
+ """
64
+ if self._is_selected == selected:
65
+ return
66
+ self._is_selected = selected
67
+ self.set_class(selected, "-selected")
68
+ self.update(self._render())
69
+
70
+ def _render(self) -> Content:
71
+ glyphs = get_glyphs()
72
+ cursor = glyphs.cursor if self._is_selected else " "
73
+ text = f"{cursor} {self._action.label}"
74
+ if self._action.primary:
75
+ return Content.styled(text, "bold")
76
+ return Content(text)
77
+
78
+ def on_click(self, event: Click) -> None: # noqa: PLR6301 # Textual event handler
79
+ """Swallow the click without activating.
80
+
81
+ Clicks on action rows are intentionally disabled to prevent
82
+ accidentally triggering `Install now`, `Skip this version`, or
83
+ similar destructive/irreversible actions. Activation is
84
+ keyboard-only (enter). The changelog row, which only opens a
85
+ URL, still accepts clicks.
86
+ """
87
+ event.stop()
88
+
89
+
90
+ class _ChangelogOption(Static):
91
+ """Secondary row that opens the changelog URL in a browser.
92
+
93
+ Visually grouped with the action rows so Tab/Shift+Tab navigation
94
+ covers it uniformly, but activating it does not dismiss the modal
95
+ — it is a "view more info" action, not one of the three
96
+ mutually-exclusive update dispositions.
97
+ """
98
+
99
+ def __init__(self) -> None:
100
+ """Initialize the changelog row."""
101
+ super().__init__(id="ua-changelog", classes="ua-action ua-changelog")
102
+ self._is_selected = False
103
+ self.update(self._render())
104
+
105
+ def set_selected(self, selected: bool) -> None:
106
+ """Toggle selection styling.
107
+
108
+ Args:
109
+ selected: Whether this row is currently under the cursor.
110
+ """
111
+ if self._is_selected == selected:
112
+ return
113
+ self._is_selected = selected
114
+ self.set_class(selected, "-selected")
115
+ self.update(self._render())
116
+
117
+ def _render(self) -> Content:
118
+ glyphs = get_glyphs()
119
+ cursor = glyphs.cursor if self._is_selected else " "
120
+ return Content(f"{cursor} View changelog")
121
+
122
+ def on_click(self, event: Click) -> None:
123
+ """Dispatch a click as a `ChangelogClicked` message."""
124
+ event.stop()
125
+ self.post_message(ChangelogClicked())
126
+
127
+
128
+ class UpdateAvailableScreen(ModalScreen[ActionId | None]):
129
+ """Modal dedicated to the update-available notification.
130
+
131
+ Renders the entry's title and body, a "View changelog" row, and
132
+ one row per configured action. Dismisses with the selected
133
+ `ActionId`, or `None` on Esc. Activating the changelog row opens
134
+ the URL in a browser and leaves the modal open.
135
+ """
136
+
137
+ BINDINGS: ClassVar[list[BindingType]] = [
138
+ Binding("escape", "cancel", "Close", show=False),
139
+ Binding("up", "move_up", "Up", show=False, priority=True),
140
+ Binding("k", "move_up", "Up", show=False, priority=True),
141
+ Binding("down", "move_down", "Down", show=False, priority=True),
142
+ Binding("j", "move_down", "Down", show=False, priority=True),
143
+ Binding("tab", "move_down", "Next", show=False, priority=True),
144
+ Binding("shift+tab", "move_up", "Previous", show=False, priority=True),
145
+ Binding("enter", "activate", "Select", show=False, priority=True),
146
+ ]
147
+
148
+ CSS = """
149
+ UpdateAvailableScreen {
150
+ align: center middle;
151
+ }
152
+
153
+ UpdateAvailableScreen > Vertical {
154
+ width: 68;
155
+ max-width: 90%;
156
+ height: auto;
157
+ max-height: 80%;
158
+ background: $surface;
159
+ border: solid $success;
160
+ padding: 1 2;
161
+ }
162
+
163
+ UpdateAvailableScreen .ua-title {
164
+ text-style: bold;
165
+ color: $text-success;
166
+ text-align: center;
167
+ margin-bottom: 1;
168
+ }
169
+
170
+ UpdateAvailableScreen .ua-body {
171
+ color: $text-muted;
172
+ margin-bottom: 1;
173
+ }
174
+
175
+ UpdateAvailableScreen .ua-action {
176
+ height: auto;
177
+ padding: 0 1;
178
+ color: $text;
179
+ }
180
+
181
+ UpdateAvailableScreen .ua-action.-selected {
182
+ background: $surface-lighten-1;
183
+ }
184
+
185
+ UpdateAvailableScreen .ua-changelog {
186
+ color: $text-muted;
187
+ margin-bottom: 1;
188
+ }
189
+
190
+ UpdateAvailableScreen .ua-help {
191
+ height: 1;
192
+ color: $text-muted;
193
+ text-style: italic;
194
+ margin-top: 1;
195
+ text-align: center;
196
+ }
197
+ """
198
+
199
+ def __init__(self, entry: PendingNotification) -> None:
200
+ """Initialize the screen.
201
+
202
+ Args:
203
+ entry: Registered notification to render. Its actions
204
+ drive the action rows; dismissing via an action
205
+ returns the chosen `ActionId` to the caller.
206
+ """
207
+ super().__init__()
208
+ self._entry = entry
209
+ self._options: list[_ActionOption | _ChangelogOption] = []
210
+ self._selected = 0
211
+
212
+ def compose(self) -> ComposeResult:
213
+ """Compose the modal layout.
214
+
215
+ Yields:
216
+ Title, optional body, the changelog row, one
217
+ `_ActionOption` per action, and a help footer.
218
+ """
219
+ glyphs = get_glyphs()
220
+ with Vertical():
221
+ yield Static(self._entry.title, classes="ua-title", markup=False)
222
+ if self._entry.body:
223
+ yield Static(self._entry.body, classes="ua-body", markup=False)
224
+ changelog = _ChangelogOption()
225
+ self._options.append(changelog)
226
+ yield changelog
227
+ for idx, action in enumerate(self._entry.actions):
228
+ option = _ActionOption(action, f"ua-row-{idx}")
229
+ self._options.append(option)
230
+ yield option
231
+ help_text = (
232
+ f"{glyphs.arrow_up}/{glyphs.arrow_down} or Tab navigate "
233
+ f"{glyphs.bullet} Enter select "
234
+ f"{glyphs.bullet} Esc close"
235
+ )
236
+ yield Static(help_text, classes="ua-help")
237
+
238
+ def on_mount(self) -> None:
239
+ """Apply ASCII borders and highlight the primary action by default."""
240
+ if is_ascii_mode():
241
+ container = self.query_one(Vertical)
242
+ colors = theme.get_theme_colors(self)
243
+ container.styles.border = ("ascii", colors.success)
244
+ # The changelog row sits at index 0; offset the primary action
245
+ # by 1 so the initial cursor still lands on "Install now".
246
+ primary_idx = next(
247
+ (i + 1 for i, a in enumerate(self._entry.actions) if a.primary),
248
+ 1 if self._entry.actions else 0,
249
+ )
250
+ self._set_selected(primary_idx)
251
+
252
+ def _set_selected(self, new_index: int) -> None:
253
+ """Move the selection cursor to *new_index*.
254
+
255
+ Raises:
256
+ IndexError: When *new_index* is outside `0..len(self._options)`.
257
+ """
258
+ if not self._options:
259
+ return
260
+ if not 0 <= new_index < len(self._options):
261
+ msg = f"selection {new_index} out of range 0..{len(self._options)}"
262
+ raise IndexError(msg)
263
+ if new_index != self._selected:
264
+ self._options[self._selected].set_selected(selected=False)
265
+ self._selected = new_index
266
+ self._options[new_index].set_selected(selected=True)
267
+
268
+ def action_move_up(self) -> None:
269
+ """Move the cursor up one row (wraps at the top)."""
270
+ if not self._options:
271
+ return
272
+ self._set_selected((self._selected - 1) % len(self._options))
273
+
274
+ def action_move_down(self) -> None:
275
+ """Move the cursor down one row (wraps at the bottom)."""
276
+ if not self._options:
277
+ return
278
+ self._set_selected((self._selected + 1) % len(self._options))
279
+
280
+ def action_activate(self) -> None:
281
+ """Fire the highlighted option.
282
+
283
+ Activating an action row dismisses the modal with its
284
+ `ActionId`. Activating the changelog row opens the URL and
285
+ leaves the modal open so the user can still pick an action.
286
+ """
287
+ if not self._options:
288
+ self.dismiss(None)
289
+ return
290
+ option = self._options[self._selected]
291
+ if isinstance(option, _ChangelogOption):
292
+ self._open_changelog()
293
+ return
294
+ self.dismiss(option.action.action_id)
295
+
296
+ def action_cancel(self) -> None:
297
+ """Close without firing any action."""
298
+ self.dismiss(None)
299
+
300
+ def on_changelog_clicked(self, message: ChangelogClicked) -> None:
301
+ """Handle a mouse click on the changelog row."""
302
+ message.stop()
303
+ self._open_changelog()
304
+
305
+ def _open_changelog(self) -> None:
306
+ """Open `CHANGELOG_URL` in a browser without closing the modal."""
307
+ self.run_worker(
308
+ open_url_async(CHANGELOG_URL, app=self.app),
309
+ exclusive=False,
310
+ group="update-available-changelog",
311
+ )
@@ -0,0 +1,184 @@
1
+ """Confirmation modals for `/update` dependency-refresh flows in the TUI.
2
+
3
+ When `deepagents-code` itself is already on the latest release, `/update` can
4
+ still re-resolve its dependencies to the newest versions allowed by the pinned
5
+ ranges (e.g. a new `langchain-openai`). The already-current path dry-runs the
6
+ resolution first, then asks for explicit confirmation only when dependencies can
7
+ move. `/update --deps` skips that prompt, but asks before taking an available
8
+ app update ahead of the dependency refresh for the current app version.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from typing import TYPE_CHECKING, ClassVar
14
+
15
+ from textual.binding import Binding, BindingType
16
+ from textual.containers import Vertical
17
+ from textual.screen import ModalScreen
18
+ from textual.widgets import Static
19
+
20
+ if TYPE_CHECKING:
21
+ from textual.app import ComposeResult
22
+
23
+
24
+ class _DependencyConfirmScreen(ModalScreen[bool]):
25
+ """Shared base for the `/update` dependency-refresh confirmation modals.
26
+
27
+ Subclasses supply only their bindings and the title/body/help text; the base
28
+ owns the layout, styling, and the `True`/`False` dismissal contract. The
29
+ `DEFAULT_CSS` type selector matches subclasses because Textual resolves type
30
+ selectors against every class name in the MRO.
31
+ """
32
+
33
+ DEFAULT_CSS = """
34
+ _DependencyConfirmScreen {
35
+ align: center middle;
36
+ }
37
+
38
+ _DependencyConfirmScreen > Vertical {
39
+ width: 66;
40
+ max-width: 90%;
41
+ height: auto;
42
+ background: $surface;
43
+ border: solid $primary;
44
+ padding: 1 2;
45
+ }
46
+
47
+ _DependencyConfirmScreen .dependency-confirm-title {
48
+ text-style: bold;
49
+ color: $primary;
50
+ text-align: center;
51
+ margin-bottom: 1;
52
+ }
53
+
54
+ _DependencyConfirmScreen .dependency-confirm-body {
55
+ height: auto;
56
+ color: $text;
57
+ margin-bottom: 1;
58
+ }
59
+
60
+ _DependencyConfirmScreen .dependency-confirm-help {
61
+ height: 1;
62
+ color: $text-muted;
63
+ text-style: italic;
64
+ text-align: center;
65
+ }
66
+ """
67
+
68
+ def __init__(self, *, title: str, body: str, help_text: str) -> None:
69
+ """Store the dialog copy for `compose`.
70
+
71
+ Args:
72
+ title: Bold heading shown at the top of the dialog.
73
+ body: Explanatory paragraph beneath the title.
74
+ help_text: Muted key-hint row at the bottom.
75
+ """
76
+ super().__init__()
77
+ self._title = title
78
+ self._body = body
79
+ self._help = help_text
80
+
81
+ def compose(self) -> ComposeResult:
82
+ """Compose the confirmation dialog.
83
+
84
+ Yields:
85
+ Title, body, and help-row widgets parented inside a `Vertical`.
86
+ """
87
+ with Vertical():
88
+ yield Static(
89
+ self._title,
90
+ classes="dependency-confirm-title",
91
+ markup=False,
92
+ )
93
+ yield Static(
94
+ self._body,
95
+ classes="dependency-confirm-body",
96
+ markup=False,
97
+ )
98
+ yield Static(
99
+ self._help,
100
+ classes="dependency-confirm-help",
101
+ markup=False,
102
+ )
103
+
104
+ def action_confirm(self) -> None:
105
+ """Dismiss with `True`."""
106
+ self.dismiss(True)
107
+
108
+ def action_cancel(self) -> None:
109
+ """Dismiss with `False`.
110
+
111
+ The method name must stay `cancel`: the app owns a priority `escape`
112
+ binding that, for an active `ModalScreen`, dispatches to `action_cancel`
113
+ if present and otherwise falls through to `dismiss(None)`. Renaming this
114
+ would silently regress Esc to a `None` dismiss instead of an explicit
115
+ cancel.
116
+ """
117
+ self.dismiss(False)
118
+
119
+
120
+ class UpdateBeforeDependenciesConfirmScreen(_DependencyConfirmScreen):
121
+ """Confirmation overlay before `/update --deps` upgrades dcode itself.
122
+
123
+ Dismisses with `True` when the user chooses the app update first and `False`
124
+ when they prefer to refresh dependencies for the current app version.
125
+ """
126
+
127
+ BINDINGS: ClassVar[list[BindingType]] = [
128
+ Binding("enter", "confirm", "Update", show=False, priority=True),
129
+ Binding("escape", "cancel", "Refresh deps", show=False, priority=True),
130
+ ]
131
+
132
+ def __init__(self, *, current: str, latest: str) -> None:
133
+ """Create the app-update confirmation dialog.
134
+
135
+ Args:
136
+ current: Currently running `deepagents-code` version.
137
+ latest: Latest available `deepagents-code` version.
138
+ """
139
+ super().__init__(
140
+ title="Update dcode first?",
141
+ body=(
142
+ f"A newer deepagents-code version is available ({current} -> "
143
+ f"{latest}). Update dcode now, or refresh dependencies for the "
144
+ "current version you already have."
145
+ ),
146
+ help_text="Enter to update dcode, Esc to refresh current dependencies",
147
+ )
148
+
149
+
150
+ class RefreshDependenciesConfirmScreen(_DependencyConfirmScreen):
151
+ """Confirmation overlay for a dependency refresh.
152
+
153
+ Dismisses with `True` when the user confirms and `False` when the user
154
+ cancels. Esc is treated as cancel so the user is never forced into a refresh
155
+ they did not explicitly choose.
156
+ """
157
+
158
+ BINDINGS: ClassVar[list[BindingType]] = [
159
+ Binding("enter", "confirm", "Refresh", show=False, priority=True),
160
+ Binding("escape", "cancel", "Cancel", show=False, priority=True),
161
+ ]
162
+
163
+ def __init__(self, *, planned_changes: str | None = None) -> None:
164
+ """Create the dependency-refresh confirmation dialog.
165
+
166
+ Args:
167
+ planned_changes: Optional dry-run summary of dependency updates.
168
+ """
169
+ body = (
170
+ "deepagents-code is already up to date, but compatible dependency "
171
+ "updates are available. Refresh to apply these changes:\n\n"
172
+ f"{planned_changes}"
173
+ if planned_changes
174
+ else (
175
+ "deepagents-code is already up to date, but its dependencies "
176
+ "can be re-resolved to the newest compatible versions. This may "
177
+ "pull in newer minor releases of packages like langchain-openai."
178
+ )
179
+ )
180
+ super().__init__(
181
+ title="Refresh dependencies?",
182
+ body=body,
183
+ help_text="Enter to refresh, Esc to cancel",
184
+ )