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.
- deepagents_code/__init__.py +42 -0
- deepagents_code/__main__.py +6 -0
- deepagents_code/_ask_user_types.py +90 -0
- deepagents_code/_cli_context.py +96 -0
- deepagents_code/_constants.py +41 -0
- deepagents_code/_debug.py +148 -0
- deepagents_code/_debug_buffer.py +204 -0
- deepagents_code/_env_vars.py +411 -0
- deepagents_code/_fake_models.py +66 -0
- deepagents_code/_git.py +521 -0
- deepagents_code/_paths.py +69 -0
- deepagents_code/_server_config.py +576 -0
- deepagents_code/_session_stats.py +235 -0
- deepagents_code/_startup_error.py +45 -0
- deepagents_code/_testing_models.py +305 -0
- deepagents_code/_textual_patches.py +420 -0
- deepagents_code/_tool_stream.py +694 -0
- deepagents_code/_version.py +46 -0
- deepagents_code/agent.py +1976 -0
- deepagents_code/app.py +19239 -0
- deepagents_code/app.tcss +438 -0
- deepagents_code/approval_mode.py +131 -0
- deepagents_code/ask_user.py +306 -0
- deepagents_code/auth_display.py +185 -0
- deepagents_code/auth_store.py +545 -0
- deepagents_code/built_in_skills/__init__.py +5 -0
- deepagents_code/built_in_skills/remember/SKILL.md +118 -0
- deepagents_code/built_in_skills/skill-creator/SKILL.md +383 -0
- deepagents_code/built_in_skills/skill-creator/scripts/init_skill.py +366 -0
- deepagents_code/built_in_skills/skill-creator/scripts/quick_validate.py +158 -0
- deepagents_code/client/__init__.py +1 -0
- deepagents_code/client/commands/__init__.py +1 -0
- deepagents_code/client/commands/auth.py +541 -0
- deepagents_code/client/commands/config.py +714 -0
- deepagents_code/client/commands/mcp.py +250 -0
- deepagents_code/client/commands/tools.py +416 -0
- deepagents_code/client/launch/__init__.py +1 -0
- deepagents_code/client/launch/server.py +978 -0
- deepagents_code/client/launch/server_manager.py +540 -0
- deepagents_code/client/non_interactive.py +1758 -0
- deepagents_code/client/remote_client.py +794 -0
- deepagents_code/clipboard.py +217 -0
- deepagents_code/command_registry.py +450 -0
- deepagents_code/config.py +4829 -0
- deepagents_code/config_manifest.py +1451 -0
- deepagents_code/configurable_model.py +577 -0
- deepagents_code/default_agent_prompt.md +12 -0
- deepagents_code/doctor.py +559 -0
- deepagents_code/editor.py +142 -0
- deepagents_code/event_bus.py +411 -0
- deepagents_code/extras_info.py +661 -0
- deepagents_code/file_ops.py +576 -0
- deepagents_code/formatting.py +135 -0
- deepagents_code/goal_rubric.py +497 -0
- deepagents_code/goal_tools.py +459 -0
- deepagents_code/hooks.py +348 -0
- deepagents_code/input.py +1041 -0
- deepagents_code/integrations/__init__.py +1 -0
- deepagents_code/integrations/openai_codex.py +551 -0
- deepagents_code/integrations/sandbox_config.py +198 -0
- deepagents_code/integrations/sandbox_factory.py +1124 -0
- deepagents_code/integrations/sandbox_provider.py +137 -0
- deepagents_code/integrations/sandbox_registry.py +350 -0
- deepagents_code/iterm_cursor_guide.py +176 -0
- deepagents_code/local_context.py +926 -0
- deepagents_code/main.py +3985 -0
- deepagents_code/managed_tools.py +642 -0
- deepagents_code/mcp_auth.py +1950 -0
- deepagents_code/mcp_config.py +176 -0
- deepagents_code/mcp_disabled.py +212 -0
- deepagents_code/mcp_login_service.py +281 -0
- deepagents_code/mcp_oauth_ui.py +199 -0
- deepagents_code/mcp_providers/__init__.py +23 -0
- deepagents_code/mcp_providers/_registry.py +39 -0
- deepagents_code/mcp_providers/base.py +133 -0
- deepagents_code/mcp_providers/github.py +102 -0
- deepagents_code/mcp_providers/slack.py +175 -0
- deepagents_code/mcp_tools.py +2427 -0
- deepagents_code/mcp_trust.py +207 -0
- deepagents_code/media_utils.py +826 -0
- deepagents_code/memory_guard.py +474 -0
- deepagents_code/model_config.py +4156 -0
- deepagents_code/notifications.py +247 -0
- deepagents_code/offload.py +402 -0
- deepagents_code/onboarding.py +223 -0
- deepagents_code/output.py +69 -0
- deepagents_code/paste_collapse.py +103 -0
- deepagents_code/project_utils.py +231 -0
- deepagents_code/py.typed +0 -0
- deepagents_code/reasoning_effort.py +641 -0
- deepagents_code/reliable_rubric.py +97 -0
- deepagents_code/resume_state.py +237 -0
- deepagents_code/server_graph.py +310 -0
- deepagents_code/session_end_summary.py +329 -0
- deepagents_code/sessions.py +1716 -0
- deepagents_code/skills/__init__.py +18 -0
- deepagents_code/skills/commands.py +1252 -0
- deepagents_code/skills/invocation.py +112 -0
- deepagents_code/skills/load.py +196 -0
- deepagents_code/skills/trust.py +547 -0
- deepagents_code/state_migration.py +136 -0
- deepagents_code/subagents.py +278 -0
- deepagents_code/system_prompt.md +204 -0
- deepagents_code/terminal_capabilities.py +115 -0
- deepagents_code/terminal_escape.py +287 -0
- deepagents_code/theme.py +891 -0
- deepagents_code/todo_list_prompt.md +12 -0
- deepagents_code/tool_catalog.py +509 -0
- deepagents_code/tool_display.py +367 -0
- deepagents_code/tools.py +516 -0
- deepagents_code/tui/__init__.py +1 -0
- deepagents_code/tui/textual_adapter.py +2553 -0
- deepagents_code/tui/widgets/__init__.py +9 -0
- deepagents_code/tui/widgets/_js_eval_display.py +139 -0
- deepagents_code/tui/widgets/_links.py +261 -0
- deepagents_code/tui/widgets/agent_selector.py +420 -0
- deepagents_code/tui/widgets/approval.py +602 -0
- deepagents_code/tui/widgets/ask_user.py +515 -0
- deepagents_code/tui/widgets/auth.py +1997 -0
- deepagents_code/tui/widgets/autocomplete.py +890 -0
- deepagents_code/tui/widgets/chat_input.py +3181 -0
- deepagents_code/tui/widgets/codex_auth.py +452 -0
- deepagents_code/tui/widgets/cwd_switch.py +242 -0
- deepagents_code/tui/widgets/debug_console.py +868 -0
- deepagents_code/tui/widgets/diff.py +252 -0
- deepagents_code/tui/widgets/effort_selector.py +189 -0
- deepagents_code/tui/widgets/goal_review.py +390 -0
- deepagents_code/tui/widgets/goal_status.py +50 -0
- deepagents_code/tui/widgets/history.py +194 -0
- deepagents_code/tui/widgets/install_confirm.py +248 -0
- deepagents_code/tui/widgets/launch_init.py +482 -0
- deepagents_code/tui/widgets/loading.py +227 -0
- deepagents_code/tui/widgets/mcp_login.py +539 -0
- deepagents_code/tui/widgets/mcp_reconnect.py +212 -0
- deepagents_code/tui/widgets/mcp_viewer.py +1634 -0
- deepagents_code/tui/widgets/message_store.py +999 -0
- deepagents_code/tui/widgets/messages.py +3846 -0
- deepagents_code/tui/widgets/model_selector.py +2343 -0
- deepagents_code/tui/widgets/notification_center.py +456 -0
- deepagents_code/tui/widgets/notification_detail.py +257 -0
- deepagents_code/tui/widgets/notification_settings.py +170 -0
- deepagents_code/tui/widgets/restart_prompt.py +158 -0
- deepagents_code/tui/widgets/skill_trust.py +131 -0
- deepagents_code/tui/widgets/startup_tip.py +91 -0
- deepagents_code/tui/widgets/status.py +781 -0
- deepagents_code/tui/widgets/subagent_panel.py +969 -0
- deepagents_code/tui/widgets/theme_selector.py +401 -0
- deepagents_code/tui/widgets/thread_selector.py +2564 -0
- deepagents_code/tui/widgets/tool_renderers.py +190 -0
- deepagents_code/tui/widgets/tool_widgets.py +304 -0
- deepagents_code/tui/widgets/update_available.py +311 -0
- deepagents_code/tui/widgets/update_confirm.py +184 -0
- deepagents_code/tui/widgets/update_progress.py +327 -0
- deepagents_code/tui/widgets/welcome.py +508 -0
- deepagents_code/turn_end_summary.py +522 -0
- deepagents_code/ui.py +858 -0
- deepagents_code/unicode_security.py +563 -0
- deepagents_code/update_check.py +3124 -0
- zjcode-0.0.1.data/data/deepagents_code/default_agent_prompt.md +12 -0
- zjcode-0.0.1.dist-info/METADATA +220 -0
- zjcode-0.0.1.dist-info/RECORD +163 -0
- zjcode-0.0.1.dist-info/WHEEL +4 -0
- 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
|
+
)
|