glaip-sdk 0.6.5b3__py3-none-any.whl → 0.7.17__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.
- glaip_sdk/__init__.py +42 -5
- glaip_sdk/agents/base.py +362 -39
- glaip_sdk/branding.py +113 -2
- glaip_sdk/cli/account_store.py +15 -0
- glaip_sdk/cli/auth.py +14 -8
- glaip_sdk/cli/commands/accounts.py +1 -1
- glaip_sdk/cli/commands/agents/__init__.py +116 -0
- glaip_sdk/cli/commands/agents/_common.py +562 -0
- glaip_sdk/cli/commands/agents/create.py +155 -0
- glaip_sdk/cli/commands/agents/delete.py +64 -0
- glaip_sdk/cli/commands/agents/get.py +89 -0
- glaip_sdk/cli/commands/agents/list.py +129 -0
- glaip_sdk/cli/commands/agents/run.py +264 -0
- glaip_sdk/cli/commands/agents/sync_langflow.py +72 -0
- glaip_sdk/cli/commands/agents/update.py +112 -0
- glaip_sdk/cli/commands/common_config.py +15 -12
- glaip_sdk/cli/commands/configure.py +2 -3
- glaip_sdk/cli/commands/mcps/__init__.py +94 -0
- glaip_sdk/cli/commands/mcps/_common.py +459 -0
- glaip_sdk/cli/commands/mcps/connect.py +82 -0
- glaip_sdk/cli/commands/mcps/create.py +152 -0
- glaip_sdk/cli/commands/mcps/delete.py +73 -0
- glaip_sdk/cli/commands/mcps/get.py +212 -0
- glaip_sdk/cli/commands/mcps/list.py +69 -0
- glaip_sdk/cli/commands/mcps/tools.py +235 -0
- glaip_sdk/cli/commands/mcps/update.py +190 -0
- glaip_sdk/cli/commands/models.py +2 -4
- glaip_sdk/cli/commands/shared/__init__.py +21 -0
- glaip_sdk/cli/commands/shared/formatters.py +91 -0
- glaip_sdk/cli/commands/tools/__init__.py +69 -0
- glaip_sdk/cli/commands/tools/_common.py +80 -0
- glaip_sdk/cli/commands/tools/create.py +228 -0
- glaip_sdk/cli/commands/tools/delete.py +61 -0
- glaip_sdk/cli/commands/tools/get.py +103 -0
- glaip_sdk/cli/commands/tools/list.py +69 -0
- glaip_sdk/cli/commands/tools/script.py +49 -0
- glaip_sdk/cli/commands/tools/update.py +102 -0
- glaip_sdk/cli/commands/transcripts/__init__.py +90 -0
- glaip_sdk/cli/commands/transcripts/_common.py +9 -0
- glaip_sdk/cli/commands/transcripts/clear.py +5 -0
- glaip_sdk/cli/commands/transcripts/detail.py +5 -0
- glaip_sdk/cli/commands/{transcripts.py → transcripts_original.py} +2 -1
- glaip_sdk/cli/commands/update.py +163 -17
- glaip_sdk/cli/config.py +1 -0
- glaip_sdk/cli/core/output.py +12 -7
- glaip_sdk/cli/entrypoint.py +20 -0
- glaip_sdk/cli/main.py +127 -39
- glaip_sdk/cli/pager.py +3 -3
- glaip_sdk/cli/resolution.py +2 -1
- glaip_sdk/cli/slash/accounts_controller.py +112 -32
- glaip_sdk/cli/slash/agent_session.py +5 -2
- glaip_sdk/cli/slash/prompt.py +11 -0
- glaip_sdk/cli/slash/remote_runs_controller.py +3 -1
- glaip_sdk/cli/slash/session.py +375 -25
- glaip_sdk/cli/slash/tui/__init__.py +28 -1
- glaip_sdk/cli/slash/tui/accounts.tcss +97 -6
- glaip_sdk/cli/slash/tui/accounts_app.py +1107 -126
- glaip_sdk/cli/slash/tui/clipboard.py +195 -0
- glaip_sdk/cli/slash/tui/context.py +92 -0
- glaip_sdk/cli/slash/tui/indicators.py +341 -0
- glaip_sdk/cli/slash/tui/keybind_registry.py +235 -0
- glaip_sdk/cli/slash/tui/layouts/__init__.py +14 -0
- glaip_sdk/cli/slash/tui/layouts/harlequin.py +184 -0
- glaip_sdk/cli/slash/tui/loading.py +43 -21
- glaip_sdk/cli/slash/tui/remote_runs_app.py +152 -20
- glaip_sdk/cli/slash/tui/terminal.py +407 -0
- glaip_sdk/cli/slash/tui/theme/__init__.py +15 -0
- glaip_sdk/cli/slash/tui/theme/catalog.py +79 -0
- glaip_sdk/cli/slash/tui/theme/manager.py +112 -0
- glaip_sdk/cli/slash/tui/theme/tokens.py +55 -0
- glaip_sdk/cli/slash/tui/toast.py +388 -0
- glaip_sdk/cli/transcript/history.py +1 -1
- glaip_sdk/cli/transcript/viewer.py +5 -3
- glaip_sdk/cli/tui_settings.py +125 -0
- glaip_sdk/cli/update_notifier.py +215 -7
- glaip_sdk/cli/validators.py +1 -1
- glaip_sdk/client/__init__.py +2 -1
- glaip_sdk/client/_schedule_payloads.py +89 -0
- glaip_sdk/client/agents.py +290 -16
- glaip_sdk/client/base.py +25 -0
- glaip_sdk/client/hitl.py +136 -0
- glaip_sdk/client/main.py +7 -5
- glaip_sdk/client/mcps.py +44 -13
- glaip_sdk/client/payloads/agent/__init__.py +23 -0
- glaip_sdk/client/{_agent_payloads.py → payloads/agent/requests.py} +28 -48
- glaip_sdk/client/payloads/agent/responses.py +43 -0
- glaip_sdk/client/run_rendering.py +414 -3
- glaip_sdk/client/schedules.py +439 -0
- glaip_sdk/client/tools.py +57 -26
- glaip_sdk/config/constants.py +22 -2
- glaip_sdk/guardrails/__init__.py +80 -0
- glaip_sdk/guardrails/serializer.py +89 -0
- glaip_sdk/hitl/__init__.py +48 -0
- glaip_sdk/hitl/base.py +64 -0
- glaip_sdk/hitl/callback.py +43 -0
- glaip_sdk/hitl/local.py +121 -0
- glaip_sdk/hitl/remote.py +523 -0
- glaip_sdk/models/__init__.py +47 -1
- glaip_sdk/models/_provider_mappings.py +101 -0
- glaip_sdk/models/_validation.py +97 -0
- glaip_sdk/models/agent.py +2 -1
- glaip_sdk/models/agent_runs.py +2 -1
- glaip_sdk/models/constants.py +141 -0
- glaip_sdk/models/model.py +170 -0
- glaip_sdk/models/schedule.py +224 -0
- glaip_sdk/payload_schemas/agent.py +1 -0
- glaip_sdk/payload_schemas/guardrails.py +34 -0
- glaip_sdk/registry/tool.py +273 -66
- glaip_sdk/runner/__init__.py +76 -0
- glaip_sdk/runner/base.py +84 -0
- glaip_sdk/runner/deps.py +115 -0
- glaip_sdk/runner/langgraph.py +1055 -0
- glaip_sdk/runner/logging_config.py +77 -0
- glaip_sdk/runner/mcp_adapter/__init__.py +13 -0
- glaip_sdk/runner/mcp_adapter/base_mcp_adapter.py +43 -0
- glaip_sdk/runner/mcp_adapter/langchain_mcp_adapter.py +257 -0
- glaip_sdk/runner/mcp_adapter/mcp_config_builder.py +116 -0
- glaip_sdk/runner/tool_adapter/__init__.py +18 -0
- glaip_sdk/runner/tool_adapter/base_tool_adapter.py +44 -0
- glaip_sdk/runner/tool_adapter/langchain_tool_adapter.py +242 -0
- glaip_sdk/schedules/__init__.py +22 -0
- glaip_sdk/schedules/base.py +291 -0
- glaip_sdk/tools/base.py +67 -14
- glaip_sdk/utils/__init__.py +1 -0
- glaip_sdk/utils/a2a/__init__.py +34 -0
- glaip_sdk/utils/a2a/event_processor.py +188 -0
- glaip_sdk/utils/agent_config.py +8 -2
- glaip_sdk/utils/bundler.py +138 -2
- glaip_sdk/utils/import_resolver.py +43 -11
- glaip_sdk/utils/rendering/renderer/base.py +58 -0
- glaip_sdk/utils/runtime_config.py +120 -0
- glaip_sdk/utils/sync.py +31 -11
- glaip_sdk/utils/tool_detection.py +301 -0
- glaip_sdk/utils/tool_storage_provider.py +140 -0
- {glaip_sdk-0.6.5b3.dist-info → glaip_sdk-0.7.17.dist-info}/METADATA +49 -38
- glaip_sdk-0.7.17.dist-info/RECORD +224 -0
- {glaip_sdk-0.6.5b3.dist-info → glaip_sdk-0.7.17.dist-info}/WHEEL +2 -1
- glaip_sdk-0.7.17.dist-info/entry_points.txt +2 -0
- glaip_sdk-0.7.17.dist-info/top_level.txt +1 -0
- glaip_sdk/cli/commands/agents.py +0 -1509
- glaip_sdk/cli/commands/mcps.py +0 -1356
- glaip_sdk/cli/commands/tools.py +0 -576
- glaip_sdk/cli/utils.py +0 -263
- glaip_sdk-0.6.5b3.dist-info/RECORD +0 -145
- glaip_sdk-0.6.5b3.dist-info/entry_points.txt +0 -3
|
@@ -17,14 +17,20 @@ from dataclasses import dataclass
|
|
|
17
17
|
from typing import Any
|
|
18
18
|
|
|
19
19
|
from rich.text import Text
|
|
20
|
+
|
|
20
21
|
from textual.app import App, ComposeResult
|
|
21
22
|
from textual.binding import Binding
|
|
22
|
-
from textual.containers import
|
|
23
|
+
from textual.containers import Horizontal, Vertical
|
|
24
|
+
from textual.coordinate import Coordinate
|
|
23
25
|
from textual.reactive import ReactiveError
|
|
24
26
|
from textual.screen import ModalScreen
|
|
25
|
-
from textual.widgets import DataTable, Footer, Header,
|
|
27
|
+
from textual.widgets import DataTable, Footer, Header, RichLog, Static
|
|
26
28
|
|
|
29
|
+
from glaip_sdk.cli.slash.tui.clipboard import ClipboardAdapter
|
|
30
|
+
from glaip_sdk.cli.slash.tui.context import TUIContext
|
|
31
|
+
from glaip_sdk.cli.slash.tui.indicators import PulseIndicator
|
|
27
32
|
from glaip_sdk.cli.slash.tui.loading import hide_loading_indicator, show_loading_indicator
|
|
33
|
+
from glaip_sdk.cli.slash.tui.toast import ClipboardToastMixin, Toast, ToastBus, ToastContainer, ToastHandlerMixin
|
|
28
34
|
|
|
29
35
|
logger = logging.getLogger(__name__)
|
|
30
36
|
|
|
@@ -50,6 +56,7 @@ def run_remote_runs_textual(
|
|
|
50
56
|
*,
|
|
51
57
|
agent_name: str | None = None,
|
|
52
58
|
agent_id: str | None = None,
|
|
59
|
+
ctx: TUIContext | None = None,
|
|
53
60
|
) -> tuple[int, int, int]:
|
|
54
61
|
"""Launch the Textual application and return the final pagination state.
|
|
55
62
|
|
|
@@ -59,6 +66,7 @@ def run_remote_runs_textual(
|
|
|
59
66
|
callbacks: Data provider callback bundle.
|
|
60
67
|
agent_name: Optional agent name for display purposes.
|
|
61
68
|
agent_id: Optional agent ID for display purposes.
|
|
69
|
+
ctx: Shared TUI context.
|
|
62
70
|
|
|
63
71
|
Returns:
|
|
64
72
|
Tuple of (page, limit, cursor_index) after the UI exits.
|
|
@@ -69,15 +77,27 @@ def run_remote_runs_textual(
|
|
|
69
77
|
callbacks,
|
|
70
78
|
agent_name=agent_name,
|
|
71
79
|
agent_id=agent_id,
|
|
80
|
+
ctx=ctx,
|
|
72
81
|
)
|
|
73
82
|
app.run()
|
|
74
83
|
current_page = getattr(app, "current_page", initial_page)
|
|
75
84
|
return current_page.page, current_page.limit, app.cursor_index
|
|
76
85
|
|
|
77
86
|
|
|
78
|
-
class RunDetailScreen(ModalScreen[None]):
|
|
87
|
+
class RunDetailScreen(ToastHandlerMixin, ClipboardToastMixin, ModalScreen[None]):
|
|
79
88
|
"""Modal screen displaying run metadata and output timeline."""
|
|
80
89
|
|
|
90
|
+
CSS = """
|
|
91
|
+
Screen { layout: vertical; layers: base toasts; }
|
|
92
|
+
#toast-container {
|
|
93
|
+
width: 100%;
|
|
94
|
+
height: auto;
|
|
95
|
+
dock: top;
|
|
96
|
+
align: right top;
|
|
97
|
+
layer: toasts;
|
|
98
|
+
}
|
|
99
|
+
"""
|
|
100
|
+
|
|
81
101
|
BINDINGS = [
|
|
82
102
|
Binding("escape", "dismiss", "Close", priority=True),
|
|
83
103
|
Binding("q", "dismiss_modal", "Close", priority=True),
|
|
@@ -85,14 +105,24 @@ class RunDetailScreen(ModalScreen[None]):
|
|
|
85
105
|
Binding("down", "scroll_down", "Down"),
|
|
86
106
|
Binding("pageup", "page_up", "PgUp"),
|
|
87
107
|
Binding("pagedown", "page_down", "PgDn"),
|
|
108
|
+
Binding("c", "copy_run_id", "Copy ID"),
|
|
109
|
+
Binding("C", "copy_detail_json", "Copy JSON"),
|
|
88
110
|
Binding("e", "export_detail", "Export"),
|
|
89
111
|
]
|
|
90
112
|
|
|
91
|
-
def __init__(
|
|
113
|
+
def __init__(
|
|
114
|
+
self,
|
|
115
|
+
detail: Any,
|
|
116
|
+
on_export: Callable[[Any], None] | None = None,
|
|
117
|
+
ctx: TUIContext | None = None,
|
|
118
|
+
) -> None:
|
|
92
119
|
"""Initialize the run detail screen."""
|
|
93
120
|
super().__init__()
|
|
94
121
|
self.detail = detail
|
|
95
122
|
self._on_export = on_export
|
|
123
|
+
self._ctx = ctx
|
|
124
|
+
self._clipboard: ClipboardAdapter | None = None
|
|
125
|
+
self._local_toasts: ToastBus | None = None
|
|
96
126
|
|
|
97
127
|
def compose(self) -> ComposeResult:
|
|
98
128
|
"""Render metadata and events."""
|
|
@@ -116,14 +146,17 @@ class RunDetailScreen(ModalScreen[None]):
|
|
|
116
146
|
duration = self.detail.duration_formatted() if getattr(self.detail, "duration_formatted", None) else None
|
|
117
147
|
add_meta("Duration", duration, "bold")
|
|
118
148
|
|
|
119
|
-
|
|
149
|
+
main_content = Vertical(
|
|
120
150
|
Static(meta_text, id="detail-meta"),
|
|
121
151
|
RichLog(id="detail-events", wrap=False),
|
|
122
152
|
)
|
|
153
|
+
yield main_content
|
|
154
|
+
yield ToastContainer(Toast(), id="toast-container")
|
|
123
155
|
yield Footer()
|
|
124
156
|
|
|
125
157
|
def on_mount(self) -> None:
|
|
126
158
|
"""Populate and focus the log."""
|
|
159
|
+
self._ensure_toast_bus()
|
|
127
160
|
log = self.query_one("#detail-events", RichLog)
|
|
128
161
|
log.can_focus = True
|
|
129
162
|
log.write(Text("Events", style="bold"))
|
|
@@ -149,6 +182,61 @@ class RunDetailScreen(ModalScreen[None]):
|
|
|
149
182
|
def _log(self) -> RichLog:
|
|
150
183
|
return self.query_one("#detail-events", RichLog)
|
|
151
184
|
|
|
185
|
+
def action_copy_run_id(self) -> None:
|
|
186
|
+
"""Copy the run id to the clipboard."""
|
|
187
|
+
run_id = getattr(self.detail, "id", None)
|
|
188
|
+
if not run_id:
|
|
189
|
+
self._announce_status("Run ID unavailable.")
|
|
190
|
+
return
|
|
191
|
+
self._copy_to_clipboard(str(run_id), label="Run ID")
|
|
192
|
+
|
|
193
|
+
def action_copy_detail_json(self) -> None:
|
|
194
|
+
"""Copy the run detail JSON to the clipboard."""
|
|
195
|
+
payload = self._detail_json_payload()
|
|
196
|
+
if payload is None:
|
|
197
|
+
return
|
|
198
|
+
self._copy_to_clipboard(payload, label="Run JSON")
|
|
199
|
+
|
|
200
|
+
def _detail_json_payload(self) -> str | None:
|
|
201
|
+
detail = self.detail
|
|
202
|
+
if detail is None:
|
|
203
|
+
self._announce_status("Run detail unavailable.")
|
|
204
|
+
return None
|
|
205
|
+
if isinstance(detail, str):
|
|
206
|
+
return detail
|
|
207
|
+
if isinstance(detail, dict):
|
|
208
|
+
payload = detail
|
|
209
|
+
elif hasattr(detail, "model_dump"):
|
|
210
|
+
payload = detail.model_dump(mode="json")
|
|
211
|
+
elif hasattr(detail, "dict"):
|
|
212
|
+
payload = detail.dict()
|
|
213
|
+
else:
|
|
214
|
+
payload = getattr(detail, "__dict__", {"value": detail})
|
|
215
|
+
try:
|
|
216
|
+
return json.dumps(payload, indent=2, ensure_ascii=False, default=str)
|
|
217
|
+
except Exception as exc:
|
|
218
|
+
self._announce_status(f"Failed to serialize run detail: {exc}")
|
|
219
|
+
return None
|
|
220
|
+
|
|
221
|
+
def _append_copy_fallback(self, text: str) -> None:
|
|
222
|
+
try:
|
|
223
|
+
log = self._log()
|
|
224
|
+
except Exception:
|
|
225
|
+
self._announce_status(text)
|
|
226
|
+
return
|
|
227
|
+
log.write(Text(text))
|
|
228
|
+
log.write(Text(""))
|
|
229
|
+
|
|
230
|
+
def _ensure_toast_bus(self) -> None:
|
|
231
|
+
"""Ensure toast bus is initialized and connected to message handler."""
|
|
232
|
+
if self._local_toasts is not None:
|
|
233
|
+
return # pragma: no cover - early return when already initialized
|
|
234
|
+
|
|
235
|
+
def _notify(m: ToastBus.Changed) -> None:
|
|
236
|
+
self.post_message(m)
|
|
237
|
+
|
|
238
|
+
self._local_toasts = ToastBus(on_change=_notify)
|
|
239
|
+
|
|
152
240
|
@staticmethod
|
|
153
241
|
def _status_style(status: str | None) -> str:
|
|
154
242
|
"""Return a Rich style name for the status pill."""
|
|
@@ -220,15 +308,25 @@ class RunDetailScreen(ModalScreen[None]):
|
|
|
220
308
|
update_status(message, append=True)
|
|
221
309
|
|
|
222
310
|
|
|
223
|
-
class RemoteRunsTextualApp(App[None]):
|
|
311
|
+
class RemoteRunsTextualApp(ToastHandlerMixin, App[None]):
|
|
224
312
|
"""Textual application for browsing remote runs."""
|
|
225
313
|
|
|
226
314
|
CSS = f"""
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
315
|
+
#toast-container {{
|
|
316
|
+
width: 100%;
|
|
317
|
+
height: auto;
|
|
318
|
+
dock: top;
|
|
319
|
+
align: right top;
|
|
320
|
+
layer: toasts;
|
|
321
|
+
}}
|
|
322
|
+
#{RUNS_LOADING_ID} {{
|
|
323
|
+
width: auto;
|
|
324
|
+
display: none;
|
|
325
|
+
}}
|
|
326
|
+
#status-bar {{
|
|
327
|
+
height: 3;
|
|
328
|
+
padding: 0 1;
|
|
329
|
+
}}
|
|
232
330
|
"""
|
|
233
331
|
|
|
234
332
|
BINDINGS = [
|
|
@@ -247,6 +345,7 @@ class RemoteRunsTextualApp(App[None]):
|
|
|
247
345
|
*,
|
|
248
346
|
agent_name: str | None = None,
|
|
249
347
|
agent_id: str | None = None,
|
|
348
|
+
ctx: TUIContext | None = None,
|
|
250
349
|
):
|
|
251
350
|
"""Initialize the remote runs Textual application.
|
|
252
351
|
|
|
@@ -256,6 +355,7 @@ class RemoteRunsTextualApp(App[None]):
|
|
|
256
355
|
callbacks: Callback bundle for data operations.
|
|
257
356
|
agent_name: Optional agent name for display purposes.
|
|
258
357
|
agent_id: Optional agent ID for display purposes.
|
|
358
|
+
ctx: Shared TUI context.
|
|
259
359
|
"""
|
|
260
360
|
super().__init__()
|
|
261
361
|
self.current_page = initial_page
|
|
@@ -265,6 +365,7 @@ class RemoteRunsTextualApp(App[None]):
|
|
|
265
365
|
self.current_rows = initial_page.data[:]
|
|
266
366
|
self.agent_name = (agent_name or "").strip()
|
|
267
367
|
self.agent_id = (agent_id or "").strip()
|
|
368
|
+
self._ctx = ctx
|
|
268
369
|
self._active_export_tasks: set[asyncio.Task[None]] = set()
|
|
269
370
|
self._page_loader_task: asyncio.Task[Any] | None = None
|
|
270
371
|
self._detail_loader_task: asyncio.Task[Any] | None = None
|
|
@@ -273,9 +374,10 @@ class RemoteRunsTextualApp(App[None]):
|
|
|
273
374
|
def compose(self) -> ComposeResult:
|
|
274
375
|
"""Build layout."""
|
|
275
376
|
yield Header()
|
|
276
|
-
|
|
277
|
-
table
|
|
278
|
-
table.
|
|
377
|
+
yield ToastContainer(Toast(), id="toast-container")
|
|
378
|
+
table = DataTable(id=RUNS_TABLE_ID) # pragma: no cover - mocked in tests
|
|
379
|
+
table.cursor_type = "row" # pragma: no cover - mocked in tests
|
|
380
|
+
table.add_columns( # pragma: no cover - mocked in tests
|
|
279
381
|
"Run UUID",
|
|
280
382
|
"Type",
|
|
281
383
|
"Status",
|
|
@@ -286,14 +388,24 @@ class RemoteRunsTextualApp(App[None]):
|
|
|
286
388
|
)
|
|
287
389
|
yield table # pragma: no cover - interactive UI, tested via integration
|
|
288
390
|
yield Horizontal( # pragma: no cover - interactive UI, tested via integration
|
|
289
|
-
|
|
391
|
+
PulseIndicator(id=RUNS_LOADING_ID),
|
|
290
392
|
Static(id="status"),
|
|
291
393
|
id="status-bar",
|
|
292
394
|
)
|
|
293
395
|
yield Footer() # pragma: no cover - interactive UI, tested via integration
|
|
294
396
|
|
|
397
|
+
def _ensure_toast_bus(self) -> None:
|
|
398
|
+
if self._ctx is None or self._ctx.toasts is not None:
|
|
399
|
+
return
|
|
400
|
+
|
|
401
|
+
def _notify(m: ToastBus.Changed) -> None:
|
|
402
|
+
self.post_message(m)
|
|
403
|
+
|
|
404
|
+
self._ctx.toasts = ToastBus(on_change=_notify)
|
|
405
|
+
|
|
295
406
|
def on_mount(self) -> None:
|
|
296
407
|
"""Render the initial page."""
|
|
408
|
+
self._ensure_toast_bus()
|
|
297
409
|
self._hide_loading()
|
|
298
410
|
self._render_page(self.current_page)
|
|
299
411
|
|
|
@@ -315,7 +427,7 @@ class RemoteRunsTextualApp(App[None]):
|
|
|
315
427
|
if self.current_rows:
|
|
316
428
|
self.cursor_index = max(0, min(self.cursor_index, len(self.current_rows) - 1))
|
|
317
429
|
table.focus()
|
|
318
|
-
table.cursor_coordinate = (self.cursor_index, 0)
|
|
430
|
+
table.cursor_coordinate = Coordinate(self.cursor_index, 0)
|
|
319
431
|
self.current_page = runs_page
|
|
320
432
|
total_pages = max(1, (runs_page.total + runs_page.limit - 1) // runs_page.limit)
|
|
321
433
|
agent_display = self.agent_name or "Runs"
|
|
@@ -371,6 +483,26 @@ class RemoteRunsTextualApp(App[None]):
|
|
|
371
483
|
"""Track cursor position when DataTable selection changes."""
|
|
372
484
|
self.cursor_index = getattr(event, "cursor_row", self.cursor_index)
|
|
373
485
|
|
|
486
|
+
def _handle_table_click(self, row: int | None) -> None:
|
|
487
|
+
if row is None:
|
|
488
|
+
return
|
|
489
|
+
table = self.query_one(RUNS_TABLE_SELECTOR, DataTable)
|
|
490
|
+
self.cursor_index = row
|
|
491
|
+
try:
|
|
492
|
+
table.cursor_coordinate = Coordinate(row, 0)
|
|
493
|
+
except Exception:
|
|
494
|
+
return
|
|
495
|
+
self.action_open_detail()
|
|
496
|
+
|
|
497
|
+
def on_data_table_row_selected(self, event: DataTable.RowSelected) -> None: # pragma: no cover - UI hook
|
|
498
|
+
"""Handle row selection event from DataTable."""
|
|
499
|
+
self._handle_table_click(getattr(event, "cursor_row", None))
|
|
500
|
+
|
|
501
|
+
def on_data_table_cell_selected(self, event: DataTable.CellSelected) -> None: # pragma: no cover - UI hook
|
|
502
|
+
"""Handle cell selection event from DataTable."""
|
|
503
|
+
row = getattr(event.coordinate, "row", None) if event.coordinate else None
|
|
504
|
+
self._handle_table_click(row)
|
|
505
|
+
|
|
374
506
|
def action_page_left(self) -> None:
|
|
375
507
|
"""Navigate to the previous page."""
|
|
376
508
|
if not self.current_page.has_prev:
|
|
@@ -413,7 +545,7 @@ class RemoteRunsTextualApp(App[None]):
|
|
|
413
545
|
self._update_status("Already loading run detail. Please wait…", append=True)
|
|
414
546
|
return
|
|
415
547
|
run_id = str(run.id)
|
|
416
|
-
self._show_loading("Loading run detail…", table_spinner=False)
|
|
548
|
+
self._show_loading("Loading run detail…", table_spinner=False, footer_message=False)
|
|
417
549
|
self._queue_detail_load(run_id)
|
|
418
550
|
|
|
419
551
|
async def action_export_run(self) -> None:
|
|
@@ -554,8 +686,8 @@ class RemoteRunsTextualApp(App[None]):
|
|
|
554
686
|
if detail is None:
|
|
555
687
|
self._update_status("Failed to load run detail.", append=True)
|
|
556
688
|
return
|
|
557
|
-
self.push_screen(RunDetailScreen(detail, on_export=self.queue_export_from_detail))
|
|
558
|
-
self._update_status("Detail view: ↑/↓ scroll · PgUp/PgDn · q/Esc close · e export")
|
|
689
|
+
self.push_screen(RunDetailScreen(detail, on_export=self.queue_export_from_detail, ctx=self._ctx))
|
|
690
|
+
self._update_status("Detail view: ↑/↓ scroll · PgUp/PgDn · q/Esc close · c copy ID · C copy JSON · e export")
|
|
559
691
|
|
|
560
692
|
def queue_export_from_detail(self, detail: Any) -> None:
|
|
561
693
|
"""Start an export from the detail modal."""
|
|
@@ -606,7 +738,7 @@ class RemoteRunsTextualApp(App[None]):
|
|
|
606
738
|
show_loading_indicator(
|
|
607
739
|
self,
|
|
608
740
|
RUNS_LOADING_SELECTOR,
|
|
609
|
-
message=message
|
|
741
|
+
message=message,
|
|
610
742
|
set_status=self._update_status if footer_message else None,
|
|
611
743
|
)
|
|
612
744
|
self._set_table_loading(table_spinner)
|