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,257 @@
|
|
|
1
|
+
"""Generic detail modal for a single pending notification.
|
|
2
|
+
|
|
3
|
+
Used by `NotificationCenterScreen` when the user drills into an entry
|
|
4
|
+
whose payload does not have a dedicated modal (e.g. missing-dependency
|
|
5
|
+
notices). Update-available notifications continue to use
|
|
6
|
+
`UpdateAvailableScreen`, which adds a changelog row on top of the
|
|
7
|
+
action list.
|
|
8
|
+
|
|
9
|
+
Dismisses with the selected `ActionId`, or `None` on Esc.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from typing import TYPE_CHECKING, ClassVar
|
|
15
|
+
|
|
16
|
+
from textual.binding import Binding, BindingType
|
|
17
|
+
from textual.containers import Vertical
|
|
18
|
+
from textual.content import Content
|
|
19
|
+
from textual.message import Message
|
|
20
|
+
from textual.screen import ModalScreen
|
|
21
|
+
from textual.widgets import Static
|
|
22
|
+
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
from textual.app import ComposeResult
|
|
25
|
+
from textual.events import Click
|
|
26
|
+
|
|
27
|
+
from deepagents_code.notifications import (
|
|
28
|
+
ActionId,
|
|
29
|
+
NotificationAction,
|
|
30
|
+
PendingNotification,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
from deepagents_code import theme
|
|
34
|
+
from deepagents_code.config import get_glyphs, is_ascii_mode
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class DetailActionActivated(Message):
|
|
38
|
+
"""Posted when an `_ActionOption` is clicked with the mouse."""
|
|
39
|
+
|
|
40
|
+
def __init__(self, action_id: ActionId) -> None:
|
|
41
|
+
"""Initialize the message.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
action_id: The action the clicked row represents.
|
|
45
|
+
"""
|
|
46
|
+
super().__init__()
|
|
47
|
+
self.action_id = action_id
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class _ActionOption(Static):
|
|
51
|
+
"""Clickable single-line action row."""
|
|
52
|
+
|
|
53
|
+
def __init__(self, action: NotificationAction, widget_id: str) -> None:
|
|
54
|
+
"""Initialize the action row widget.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
action: The action this row represents.
|
|
58
|
+
widget_id: DOM id assigned to the widget.
|
|
59
|
+
"""
|
|
60
|
+
super().__init__(id=widget_id, classes="nd-action")
|
|
61
|
+
self._action = action
|
|
62
|
+
self._is_selected = False
|
|
63
|
+
self.update(self._render())
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def action(self) -> NotificationAction:
|
|
67
|
+
"""Underlying action."""
|
|
68
|
+
return self._action
|
|
69
|
+
|
|
70
|
+
def set_selected(self, selected: bool) -> None:
|
|
71
|
+
"""Toggle selection styling.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
selected: Whether this row is currently under the cursor.
|
|
75
|
+
"""
|
|
76
|
+
if self._is_selected == selected:
|
|
77
|
+
return
|
|
78
|
+
self._is_selected = selected
|
|
79
|
+
self.set_class(selected, "-selected")
|
|
80
|
+
self.update(self._render())
|
|
81
|
+
|
|
82
|
+
def _render(self) -> Content:
|
|
83
|
+
glyphs = get_glyphs()
|
|
84
|
+
cursor = glyphs.cursor if self._is_selected else " "
|
|
85
|
+
text = f"{cursor} {self._action.label}"
|
|
86
|
+
if self._action.primary:
|
|
87
|
+
return Content.styled(text, "bold")
|
|
88
|
+
return Content(text)
|
|
89
|
+
|
|
90
|
+
def on_click(self, event: Click) -> None:
|
|
91
|
+
"""Dispatch a click as a `DetailActionActivated` message."""
|
|
92
|
+
event.stop()
|
|
93
|
+
self.post_message(DetailActionActivated(self._action.action_id))
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class NotificationDetailScreen(ModalScreen["ActionId | None"]):
|
|
97
|
+
"""Modal displaying a single notification's title, body, and actions.
|
|
98
|
+
|
|
99
|
+
Activation returns the chosen `ActionId` via `dismiss()`; Esc
|
|
100
|
+
returns `None` so the caller can keep the underlying notification
|
|
101
|
+
center open.
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
BINDINGS: ClassVar[list[BindingType]] = [
|
|
105
|
+
Binding("escape", "cancel", "Back", show=False),
|
|
106
|
+
Binding("up", "move_up", "Up", show=False, priority=True),
|
|
107
|
+
Binding("k", "move_up", "Up", show=False, priority=True),
|
|
108
|
+
Binding("down", "move_down", "Down", show=False, priority=True),
|
|
109
|
+
Binding("j", "move_down", "Down", show=False, priority=True),
|
|
110
|
+
Binding("tab", "move_down", "Next", show=False, priority=True),
|
|
111
|
+
Binding("shift+tab", "move_up", "Previous", show=False, priority=True),
|
|
112
|
+
Binding("enter", "activate", "Select", show=False, priority=True),
|
|
113
|
+
]
|
|
114
|
+
|
|
115
|
+
CSS = """
|
|
116
|
+
NotificationDetailScreen {
|
|
117
|
+
align: center middle;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
NotificationDetailScreen > Vertical {
|
|
121
|
+
width: 68;
|
|
122
|
+
max-width: 90%;
|
|
123
|
+
height: auto;
|
|
124
|
+
max-height: 80%;
|
|
125
|
+
background: $surface;
|
|
126
|
+
border: solid $primary;
|
|
127
|
+
padding: 1 2;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
NotificationDetailScreen .nd-title {
|
|
131
|
+
text-style: bold;
|
|
132
|
+
color: $primary;
|
|
133
|
+
text-align: center;
|
|
134
|
+
margin-bottom: 1;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
NotificationDetailScreen .nd-body {
|
|
138
|
+
color: $text-muted;
|
|
139
|
+
margin-bottom: 1;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
NotificationDetailScreen .nd-action {
|
|
143
|
+
height: auto;
|
|
144
|
+
padding: 0 1;
|
|
145
|
+
color: $text;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
NotificationDetailScreen .nd-action:hover {
|
|
149
|
+
background: $surface-lighten-1;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
NotificationDetailScreen .nd-action.-selected {
|
|
153
|
+
background: $surface-lighten-1;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
NotificationDetailScreen .nd-help {
|
|
157
|
+
height: 1;
|
|
158
|
+
color: $text-muted;
|
|
159
|
+
text-style: italic;
|
|
160
|
+
margin-top: 1;
|
|
161
|
+
text-align: center;
|
|
162
|
+
}
|
|
163
|
+
"""
|
|
164
|
+
|
|
165
|
+
def __init__(self, entry: PendingNotification) -> None:
|
|
166
|
+
"""Initialize the screen.
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
entry: Notification to render. Its actions drive the row
|
|
170
|
+
list; dismissing via an action returns the chosen
|
|
171
|
+
`ActionId` to the caller.
|
|
172
|
+
"""
|
|
173
|
+
super().__init__()
|
|
174
|
+
self._entry = entry
|
|
175
|
+
self._options: list[_ActionOption] = []
|
|
176
|
+
self._selected = 0
|
|
177
|
+
|
|
178
|
+
def compose(self) -> ComposeResult:
|
|
179
|
+
"""Compose the modal layout.
|
|
180
|
+
|
|
181
|
+
Yields:
|
|
182
|
+
Title, optional body, one `_ActionOption` per action, and
|
|
183
|
+
a help footer.
|
|
184
|
+
"""
|
|
185
|
+
glyphs = get_glyphs()
|
|
186
|
+
with Vertical():
|
|
187
|
+
yield Static(self._entry.title, classes="nd-title", markup=False)
|
|
188
|
+
if self._entry.body:
|
|
189
|
+
yield Static(self._entry.body, classes="nd-body", markup=False)
|
|
190
|
+
for idx, action in enumerate(self._entry.actions):
|
|
191
|
+
option = _ActionOption(action, f"nd-row-{idx}")
|
|
192
|
+
self._options.append(option)
|
|
193
|
+
yield option
|
|
194
|
+
help_text = (
|
|
195
|
+
f"{glyphs.arrow_up}/{glyphs.arrow_down} or Tab navigate "
|
|
196
|
+
f"{glyphs.bullet} Enter select "
|
|
197
|
+
f"{glyphs.bullet} Esc back"
|
|
198
|
+
)
|
|
199
|
+
yield Static(help_text, classes="nd-help")
|
|
200
|
+
|
|
201
|
+
def on_mount(self) -> None:
|
|
202
|
+
"""Apply ASCII borders and highlight the primary action by default."""
|
|
203
|
+
if is_ascii_mode():
|
|
204
|
+
container = self.query_one(Vertical)
|
|
205
|
+
colors = theme.get_theme_colors(self)
|
|
206
|
+
container.styles.border = ("ascii", colors.primary)
|
|
207
|
+
if not self._options:
|
|
208
|
+
return
|
|
209
|
+
primary_idx = next(
|
|
210
|
+
(i for i, a in enumerate(self._entry.actions) if a.primary),
|
|
211
|
+
0,
|
|
212
|
+
)
|
|
213
|
+
self._set_selected(primary_idx)
|
|
214
|
+
|
|
215
|
+
def _set_selected(self, new_index: int) -> None:
|
|
216
|
+
"""Move the selection cursor to *new_index*.
|
|
217
|
+
|
|
218
|
+
Raises:
|
|
219
|
+
IndexError: When *new_index* is outside `0..len(self._options)`.
|
|
220
|
+
"""
|
|
221
|
+
if not self._options:
|
|
222
|
+
return
|
|
223
|
+
if not 0 <= new_index < len(self._options):
|
|
224
|
+
msg = f"selection {new_index} out of range 0..{len(self._options)}"
|
|
225
|
+
raise IndexError(msg)
|
|
226
|
+
if new_index != self._selected:
|
|
227
|
+
self._options[self._selected].set_selected(selected=False)
|
|
228
|
+
self._selected = new_index
|
|
229
|
+
self._options[new_index].set_selected(selected=True)
|
|
230
|
+
|
|
231
|
+
def action_move_up(self) -> None:
|
|
232
|
+
"""Move the cursor up one row (wraps at the top)."""
|
|
233
|
+
if not self._options:
|
|
234
|
+
return
|
|
235
|
+
self._set_selected((self._selected - 1) % len(self._options))
|
|
236
|
+
|
|
237
|
+
def action_move_down(self) -> None:
|
|
238
|
+
"""Move the cursor down one row (wraps at the bottom)."""
|
|
239
|
+
if not self._options:
|
|
240
|
+
return
|
|
241
|
+
self._set_selected((self._selected + 1) % len(self._options))
|
|
242
|
+
|
|
243
|
+
def action_activate(self) -> None:
|
|
244
|
+
"""Dismiss with the highlighted action's id."""
|
|
245
|
+
if not self._options:
|
|
246
|
+
self.dismiss(None)
|
|
247
|
+
return
|
|
248
|
+
self.dismiss(self._options[self._selected].action.action_id)
|
|
249
|
+
|
|
250
|
+
def action_cancel(self) -> None:
|
|
251
|
+
"""Close without firing any action."""
|
|
252
|
+
self.dismiss(None)
|
|
253
|
+
|
|
254
|
+
def on_detail_action_activated(self, message: DetailActionActivated) -> None:
|
|
255
|
+
"""Handle a mouse click on an action row."""
|
|
256
|
+
message.stop()
|
|
257
|
+
self.dismiss(message.action_id)
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"""Notification settings screen for `/notifications` command."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import logging
|
|
7
|
+
from typing import TYPE_CHECKING, ClassVar
|
|
8
|
+
|
|
9
|
+
from textual.binding import Binding, BindingType
|
|
10
|
+
from textual.containers import VerticalGroup
|
|
11
|
+
from textual.screen import ModalScreen
|
|
12
|
+
from textual.widgets import Checkbox, Static
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from textual.app import ComposeResult
|
|
16
|
+
|
|
17
|
+
from deepagents_code import theme
|
|
18
|
+
from deepagents_code.config import get_glyphs, is_ascii_mode
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
# Warning keys and their user-facing labels.
|
|
23
|
+
# Checked = warning is shown at startup (not suppressed). Unchecked = suppressed.
|
|
24
|
+
WARNING_TOGGLES: list[tuple[str, str]] = [
|
|
25
|
+
("ripgrep", "Warn when ripgrep is not installed"),
|
|
26
|
+
("tavily", "Warn when TAVILY_API_KEY is not set (web search)"),
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class NotificationSettingsScreen(ModalScreen[None]):
|
|
31
|
+
"""Modal dialog for managing startup warning preferences.
|
|
32
|
+
|
|
33
|
+
Each checkbox maps to a key in `[warnings].suppress` in
|
|
34
|
+
`~/.deepagents/config.toml`. Toggling a checkbox immediately
|
|
35
|
+
persists the change.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
BINDINGS: ClassVar[list[BindingType]] = [
|
|
39
|
+
Binding("escape", "cancel", "Close", show=False),
|
|
40
|
+
Binding("up", "app.focus_previous", "Previous", show=False, priority=True),
|
|
41
|
+
Binding("down", "app.focus_next", "Next", show=False, priority=True),
|
|
42
|
+
Binding("tab", "app.focus_next", "Next", show=False, priority=True),
|
|
43
|
+
Binding(
|
|
44
|
+
"shift+tab",
|
|
45
|
+
"app.focus_previous",
|
|
46
|
+
"Previous",
|
|
47
|
+
show=False,
|
|
48
|
+
priority=True,
|
|
49
|
+
),
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
CSS = """
|
|
53
|
+
NotificationSettingsScreen {
|
|
54
|
+
align: center middle;
|
|
55
|
+
background: transparent;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
NotificationSettingsScreen > VerticalGroup {
|
|
59
|
+
width: 65;
|
|
60
|
+
max-width: 90%;
|
|
61
|
+
height: auto;
|
|
62
|
+
max-height: 80%;
|
|
63
|
+
background: $surface;
|
|
64
|
+
border: solid $primary;
|
|
65
|
+
padding: 1 2;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
NotificationSettingsScreen .ns-title {
|
|
69
|
+
text-style: bold;
|
|
70
|
+
color: $primary;
|
|
71
|
+
text-align: center;
|
|
72
|
+
margin-bottom: 1;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
NotificationSettingsScreen .ns-help {
|
|
76
|
+
height: 1;
|
|
77
|
+
color: $text-muted;
|
|
78
|
+
text-style: italic;
|
|
79
|
+
margin-top: 1;
|
|
80
|
+
text-align: center;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
NotificationSettingsScreen Checkbox {
|
|
84
|
+
margin: 0;
|
|
85
|
+
border: none;
|
|
86
|
+
&:focus {
|
|
87
|
+
border: none;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
def __init__(self, suppressed: set[str]) -> None:
|
|
93
|
+
"""Initialize the notification settings screen.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
suppressed: Set of currently suppressed warning keys.
|
|
97
|
+
"""
|
|
98
|
+
super().__init__()
|
|
99
|
+
self._suppressed = suppressed
|
|
100
|
+
|
|
101
|
+
def compose(self) -> ComposeResult:
|
|
102
|
+
"""Compose the screen layout.
|
|
103
|
+
|
|
104
|
+
Yields:
|
|
105
|
+
Widgets for the notification settings UI.
|
|
106
|
+
"""
|
|
107
|
+
glyphs = get_glyphs()
|
|
108
|
+
with VerticalGroup():
|
|
109
|
+
yield Static("Notification Settings", classes="ns-title")
|
|
110
|
+
for key, label in WARNING_TOGGLES:
|
|
111
|
+
yield Checkbox(
|
|
112
|
+
label,
|
|
113
|
+
value=key not in self._suppressed,
|
|
114
|
+
id=f"ns-{key}",
|
|
115
|
+
)
|
|
116
|
+
help_text = (
|
|
117
|
+
f"{glyphs.arrow_up}/{glyphs.arrow_down} or Tab navigate"
|
|
118
|
+
f" {glyphs.bullet} Space toggle"
|
|
119
|
+
f" {glyphs.bullet} Esc close"
|
|
120
|
+
)
|
|
121
|
+
yield Static(help_text, classes="ns-help")
|
|
122
|
+
|
|
123
|
+
def on_mount(self) -> None:
|
|
124
|
+
"""Apply ASCII border if needed."""
|
|
125
|
+
if is_ascii_mode():
|
|
126
|
+
container = self.query_one(VerticalGroup)
|
|
127
|
+
colors = theme.get_theme_colors(self)
|
|
128
|
+
container.styles.border = ("ascii", colors.success)
|
|
129
|
+
|
|
130
|
+
def on_checkbox_changed(self, event: Checkbox.Changed) -> None:
|
|
131
|
+
"""Persist warning suppression toggle to config.toml on change."""
|
|
132
|
+
event.stop()
|
|
133
|
+
checkbox_id = event.checkbox.id
|
|
134
|
+
if not checkbox_id or not checkbox_id.startswith("ns-"):
|
|
135
|
+
return
|
|
136
|
+
key = checkbox_id.removeprefix("ns-")
|
|
137
|
+
enabled = event.value
|
|
138
|
+
|
|
139
|
+
async def _persist() -> None:
|
|
140
|
+
from deepagents_code.model_config import (
|
|
141
|
+
suppress_warning,
|
|
142
|
+
unsuppress_warning,
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
try:
|
|
146
|
+
if enabled:
|
|
147
|
+
ok = await asyncio.to_thread(unsuppress_warning, key)
|
|
148
|
+
else:
|
|
149
|
+
ok = await asyncio.to_thread(suppress_warning, key)
|
|
150
|
+
except Exception:
|
|
151
|
+
logger.warning(
|
|
152
|
+
"Failed to persist notification setting for %r",
|
|
153
|
+
key,
|
|
154
|
+
exc_info=True,
|
|
155
|
+
)
|
|
156
|
+
ok = False
|
|
157
|
+
if not ok:
|
|
158
|
+
self.app.notify(
|
|
159
|
+
"Could not save notification preference. "
|
|
160
|
+
"Check file permissions for ~/.deepagents/config.toml.",
|
|
161
|
+
severity="warning",
|
|
162
|
+
timeout=6,
|
|
163
|
+
markup=False,
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
self.call_later(_persist)
|
|
167
|
+
|
|
168
|
+
def action_cancel(self) -> None:
|
|
169
|
+
"""Close the screen."""
|
|
170
|
+
self.dismiss(None)
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"""Confirmation modal offered when a change needs an owned-server respawn.
|
|
2
|
+
|
|
3
|
+
Some changes take effect only when the app-owned LangGraph server subprocess
|
|
4
|
+
spawns: provider/sandbox extras and `--package` installs are imported at spawn
|
|
5
|
+
time, and a Tavily key saved via `/auth` binds the `web_search` tool only at
|
|
6
|
+
spawn time. A `/restart` respawns the subprocess without exiting the TUI, so
|
|
7
|
+
rather than make the user type `/restart` by hand, this modal offers to run
|
|
8
|
+
that restart immediately while leaving deferral one keypress away. The title
|
|
9
|
+
`verb` and `body` are caller-supplied so one modal serves each flow.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from typing import TYPE_CHECKING, ClassVar, Literal
|
|
15
|
+
|
|
16
|
+
from textual.binding import Binding, BindingType
|
|
17
|
+
from textual.containers import Vertical
|
|
18
|
+
from textual.content import Content
|
|
19
|
+
from textual.screen import ModalScreen
|
|
20
|
+
from textual.widgets import Static
|
|
21
|
+
|
|
22
|
+
from deepagents_code.config import get_glyphs
|
|
23
|
+
|
|
24
|
+
if TYPE_CHECKING:
|
|
25
|
+
from textual.app import ComposeResult
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
RestartChoice = Literal["restart", "later"]
|
|
29
|
+
"""Outcome of the prompt: restart the server now or defer."""
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class RestartPromptScreen(ModalScreen[RestartChoice]):
|
|
33
|
+
"""Modal asking whether to restart the server for a spawn-time change.
|
|
34
|
+
|
|
35
|
+
Serves both the post-install offer and the post-`/auth` web-search offer;
|
|
36
|
+
the caller supplies the title `verb` and `body` copy.
|
|
37
|
+
|
|
38
|
+
Dismisses with `"restart"` when the user accepts and `"later"` when the
|
|
39
|
+
user defers. Esc is treated as "later" so the user is never forced into a
|
|
40
|
+
restart they did not explicitly choose.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
BINDINGS: ClassVar[list[BindingType]] = [
|
|
44
|
+
Binding("enter", "restart", "Restart", show=False, priority=True),
|
|
45
|
+
Binding("escape", "later", "Later", show=False, priority=True),
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
CSS = """
|
|
49
|
+
RestartPromptScreen {
|
|
50
|
+
align: center middle;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
RestartPromptScreen > Vertical {
|
|
54
|
+
width: 64;
|
|
55
|
+
max-width: 90%;
|
|
56
|
+
height: auto;
|
|
57
|
+
background: $surface;
|
|
58
|
+
border: solid $primary;
|
|
59
|
+
padding: 1 2;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
RestartPromptScreen .restart-prompt-title {
|
|
63
|
+
text-style: bold;
|
|
64
|
+
color: $primary;
|
|
65
|
+
text-align: center;
|
|
66
|
+
margin-bottom: 1;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
RestartPromptScreen .restart-prompt-body {
|
|
70
|
+
height: auto;
|
|
71
|
+
color: $text;
|
|
72
|
+
margin-bottom: 1;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
RestartPromptScreen .restart-prompt-help {
|
|
76
|
+
height: 1;
|
|
77
|
+
color: $text-muted;
|
|
78
|
+
text-style: italic;
|
|
79
|
+
text-align: center;
|
|
80
|
+
}
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
_DEFAULT_BODY = "Restart the server to load it now, or defer with `/restart`."
|
|
84
|
+
|
|
85
|
+
def __init__(
|
|
86
|
+
self,
|
|
87
|
+
label: str,
|
|
88
|
+
*,
|
|
89
|
+
verb: str,
|
|
90
|
+
body: str | None = None,
|
|
91
|
+
) -> None:
|
|
92
|
+
"""Initialize the prompt.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
label: The subject surfaced in the title (e.g. an installed extra
|
|
96
|
+
name, or a saved credential like ``"Tavily API key"``).
|
|
97
|
+
verb: Past-tense action shown before `label` in the title — e.g.
|
|
98
|
+
`"Installed"` for the post-install flow or `"Saved"` for a
|
|
99
|
+
saved credential. Required (no default) so each flow states its
|
|
100
|
+
own intent and a future caller can't inherit install-only copy.
|
|
101
|
+
body: Optional override for the explanatory line under the title.
|
|
102
|
+
Defaults to the generic restart copy.
|
|
103
|
+
"""
|
|
104
|
+
super().__init__()
|
|
105
|
+
self._label = label
|
|
106
|
+
self._verb = verb
|
|
107
|
+
self._body = body or self._DEFAULT_BODY
|
|
108
|
+
|
|
109
|
+
def compose(self) -> ComposeResult:
|
|
110
|
+
"""Compose the confirmation dialog.
|
|
111
|
+
|
|
112
|
+
Yields:
|
|
113
|
+
Title, body, and help-row widgets parented inside a `Vertical`.
|
|
114
|
+
"""
|
|
115
|
+
glyphs = get_glyphs()
|
|
116
|
+
with Vertical():
|
|
117
|
+
yield Static(
|
|
118
|
+
Content.from_markup(
|
|
119
|
+
"$check $verb [bold]$name[/bold]",
|
|
120
|
+
check=glyphs.checkmark,
|
|
121
|
+
verb=self._verb,
|
|
122
|
+
name=self._label,
|
|
123
|
+
),
|
|
124
|
+
classes="restart-prompt-title",
|
|
125
|
+
markup=False,
|
|
126
|
+
)
|
|
127
|
+
yield Static(
|
|
128
|
+
self._body,
|
|
129
|
+
classes="restart-prompt-body",
|
|
130
|
+
markup=False,
|
|
131
|
+
)
|
|
132
|
+
yield Static(
|
|
133
|
+
"Enter to restart, Esc to defer",
|
|
134
|
+
classes="restart-prompt-help",
|
|
135
|
+
markup=False,
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
def action_restart(self) -> None:
|
|
139
|
+
"""Dismiss with `"restart"`."""
|
|
140
|
+
self.dismiss("restart")
|
|
141
|
+
|
|
142
|
+
def action_later(self) -> None:
|
|
143
|
+
"""Dismiss with `"later"`."""
|
|
144
|
+
self.dismiss("later")
|
|
145
|
+
|
|
146
|
+
def action_cancel(self) -> None:
|
|
147
|
+
"""Alias for `action_later` so Esc resolves to a deliberate defer.
|
|
148
|
+
|
|
149
|
+
The app's `action_interrupt` (`escape` binding, `priority=True`)
|
|
150
|
+
fires before this screen's own `escape` binding. When the active
|
|
151
|
+
screen is a `ModalScreen`, it dispatches to `action_cancel` if
|
|
152
|
+
present, else falls through to `dismiss(None)`. The current caller
|
|
153
|
+
no-ops on both `None` and `"later"`, but defining this alias pins Esc
|
|
154
|
+
to an explicit `"later"` — matching the sibling reconnect/cwd-switch
|
|
155
|
+
modals and keeping the outcome unambiguous for any future caller that
|
|
156
|
+
branches on a deliberate defer versus a programmatic dismiss.
|
|
157
|
+
"""
|
|
158
|
+
self.action_later()
|