hackagent 0.3.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.
- hackagent/__init__.py +12 -0
- hackagent/agent.py +214 -0
- hackagent/api/__init__.py +1 -0
- hackagent/api/agent/__init__.py +1 -0
- hackagent/api/agent/agent_create.py +347 -0
- hackagent/api/agent/agent_destroy.py +140 -0
- hackagent/api/agent/agent_list.py +242 -0
- hackagent/api/agent/agent_partial_update.py +361 -0
- hackagent/api/agent/agent_retrieve.py +235 -0
- hackagent/api/agent/agent_update.py +361 -0
- hackagent/api/apilogs/__init__.py +1 -0
- hackagent/api/apilogs/apilogs_list.py +170 -0
- hackagent/api/apilogs/apilogs_retrieve.py +162 -0
- hackagent/api/attack/__init__.py +1 -0
- hackagent/api/attack/attack_create.py +275 -0
- hackagent/api/attack/attack_destroy.py +146 -0
- hackagent/api/attack/attack_list.py +254 -0
- hackagent/api/attack/attack_partial_update.py +289 -0
- hackagent/api/attack/attack_retrieve.py +247 -0
- hackagent/api/attack/attack_update.py +289 -0
- hackagent/api/checkout/__init__.py +1 -0
- hackagent/api/checkout/checkout_create.py +225 -0
- hackagent/api/generate/__init__.py +1 -0
- hackagent/api/generate/generate_create.py +253 -0
- hackagent/api/judge/__init__.py +1 -0
- hackagent/api/judge/judge_create.py +253 -0
- hackagent/api/key/__init__.py +1 -0
- hackagent/api/key/key_create.py +179 -0
- hackagent/api/key/key_destroy.py +103 -0
- hackagent/api/key/key_list.py +170 -0
- hackagent/api/key/key_retrieve.py +162 -0
- hackagent/api/organization/__init__.py +1 -0
- hackagent/api/organization/organization_create.py +208 -0
- hackagent/api/organization/organization_destroy.py +104 -0
- hackagent/api/organization/organization_list.py +170 -0
- hackagent/api/organization/organization_me_retrieve.py +126 -0
- hackagent/api/organization/organization_partial_update.py +222 -0
- hackagent/api/organization/organization_retrieve.py +163 -0
- hackagent/api/organization/organization_update.py +222 -0
- hackagent/api/prompt/__init__.py +1 -0
- hackagent/api/prompt/prompt_create.py +171 -0
- hackagent/api/prompt/prompt_destroy.py +104 -0
- hackagent/api/prompt/prompt_list.py +185 -0
- hackagent/api/prompt/prompt_partial_update.py +185 -0
- hackagent/api/prompt/prompt_retrieve.py +163 -0
- hackagent/api/prompt/prompt_update.py +185 -0
- hackagent/api/result/__init__.py +1 -0
- hackagent/api/result/result_create.py +175 -0
- hackagent/api/result/result_destroy.py +106 -0
- hackagent/api/result/result_list.py +249 -0
- hackagent/api/result/result_partial_update.py +193 -0
- hackagent/api/result/result_retrieve.py +167 -0
- hackagent/api/result/result_trace_create.py +177 -0
- hackagent/api/result/result_update.py +189 -0
- hackagent/api/run/__init__.py +1 -0
- hackagent/api/run/run_create.py +187 -0
- hackagent/api/run/run_destroy.py +112 -0
- hackagent/api/run/run_list.py +291 -0
- hackagent/api/run/run_partial_update.py +201 -0
- hackagent/api/run/run_result_create.py +177 -0
- hackagent/api/run/run_retrieve.py +179 -0
- hackagent/api/run/run_run_tests_create.py +187 -0
- hackagent/api/run/run_update.py +201 -0
- hackagent/api/user/__init__.py +1 -0
- hackagent/api/user/user_create.py +212 -0
- hackagent/api/user/user_destroy.py +106 -0
- hackagent/api/user/user_list.py +174 -0
- hackagent/api/user/user_me_retrieve.py +126 -0
- hackagent/api/user/user_me_update.py +196 -0
- hackagent/api/user/user_partial_update.py +226 -0
- hackagent/api/user/user_retrieve.py +167 -0
- hackagent/api/user/user_update.py +226 -0
- hackagent/attacks/AdvPrefix/__init__.py +41 -0
- hackagent/attacks/AdvPrefix/completions.py +416 -0
- hackagent/attacks/AdvPrefix/config.py +259 -0
- hackagent/attacks/AdvPrefix/evaluation.py +745 -0
- hackagent/attacks/AdvPrefix/evaluators.py +564 -0
- hackagent/attacks/AdvPrefix/generate.py +711 -0
- hackagent/attacks/AdvPrefix/utils.py +307 -0
- hackagent/attacks/__init__.py +35 -0
- hackagent/attacks/advprefix.py +507 -0
- hackagent/attacks/base.py +106 -0
- hackagent/attacks/strategies.py +906 -0
- hackagent/cli/__init__.py +19 -0
- hackagent/cli/commands/__init__.py +20 -0
- hackagent/cli/commands/agent.py +100 -0
- hackagent/cli/commands/attack.py +417 -0
- hackagent/cli/commands/config.py +301 -0
- hackagent/cli/commands/results.py +327 -0
- hackagent/cli/config.py +249 -0
- hackagent/cli/main.py +515 -0
- hackagent/cli/tui/__init__.py +31 -0
- hackagent/cli/tui/actions_logger.py +200 -0
- hackagent/cli/tui/app.py +288 -0
- hackagent/cli/tui/base.py +137 -0
- hackagent/cli/tui/logger.py +318 -0
- hackagent/cli/tui/views/__init__.py +33 -0
- hackagent/cli/tui/views/agents.py +488 -0
- hackagent/cli/tui/views/attacks.py +624 -0
- hackagent/cli/tui/views/config.py +244 -0
- hackagent/cli/tui/views/dashboard.py +307 -0
- hackagent/cli/tui/views/results.py +1210 -0
- hackagent/cli/tui/widgets/__init__.py +24 -0
- hackagent/cli/tui/widgets/actions.py +346 -0
- hackagent/cli/tui/widgets/logs.py +435 -0
- hackagent/cli/utils.py +276 -0
- hackagent/client.py +286 -0
- hackagent/errors.py +37 -0
- hackagent/logger.py +83 -0
- hackagent/models/__init__.py +109 -0
- hackagent/models/agent.py +223 -0
- hackagent/models/agent_request.py +129 -0
- hackagent/models/api_token_log.py +184 -0
- hackagent/models/attack.py +154 -0
- hackagent/models/attack_request.py +82 -0
- hackagent/models/checkout_session_request_request.py +76 -0
- hackagent/models/checkout_session_response.py +59 -0
- hackagent/models/choice.py +81 -0
- hackagent/models/choice_message.py +67 -0
- hackagent/models/evaluation_status_enum.py +14 -0
- hackagent/models/generate_error_response.py +59 -0
- hackagent/models/generate_request_request.py +212 -0
- hackagent/models/generate_success_response.py +115 -0
- hackagent/models/generic_error_response.py +70 -0
- hackagent/models/message_request.py +67 -0
- hackagent/models/organization.py +102 -0
- hackagent/models/organization_minimal.py +68 -0
- hackagent/models/organization_request.py +71 -0
- hackagent/models/paginated_agent_list.py +123 -0
- hackagent/models/paginated_api_token_log_list.py +123 -0
- hackagent/models/paginated_attack_list.py +123 -0
- hackagent/models/paginated_organization_list.py +123 -0
- hackagent/models/paginated_prompt_list.py +123 -0
- hackagent/models/paginated_result_list.py +123 -0
- hackagent/models/paginated_run_list.py +123 -0
- hackagent/models/paginated_user_api_key_list.py +123 -0
- hackagent/models/paginated_user_profile_list.py +123 -0
- hackagent/models/patched_agent_request.py +128 -0
- hackagent/models/patched_attack_request.py +92 -0
- hackagent/models/patched_organization_request.py +71 -0
- hackagent/models/patched_prompt_request.py +125 -0
- hackagent/models/patched_result_request.py +237 -0
- hackagent/models/patched_run_request.py +138 -0
- hackagent/models/patched_user_profile_request.py +99 -0
- hackagent/models/prompt.py +220 -0
- hackagent/models/prompt_request.py +126 -0
- hackagent/models/result.py +294 -0
- hackagent/models/result_list_evaluation_status.py +14 -0
- hackagent/models/result_request.py +232 -0
- hackagent/models/run.py +233 -0
- hackagent/models/run_list_status.py +12 -0
- hackagent/models/run_request.py +133 -0
- hackagent/models/status_enum.py +12 -0
- hackagent/models/step_type_enum.py +14 -0
- hackagent/models/trace.py +121 -0
- hackagent/models/trace_request.py +94 -0
- hackagent/models/usage.py +75 -0
- hackagent/models/user_api_key.py +201 -0
- hackagent/models/user_api_key_request.py +73 -0
- hackagent/models/user_profile.py +135 -0
- hackagent/models/user_profile_minimal.py +76 -0
- hackagent/models/user_profile_request.py +99 -0
- hackagent/router/__init__.py +25 -0
- hackagent/router/adapters/__init__.py +20 -0
- hackagent/router/adapters/base.py +63 -0
- hackagent/router/adapters/google_adk.py +671 -0
- hackagent/router/adapters/litellm_adapter.py +524 -0
- hackagent/router/adapters/openai_adapter.py +426 -0
- hackagent/router/router.py +969 -0
- hackagent/router/types.py +54 -0
- hackagent/tracking/__init__.py +42 -0
- hackagent/tracking/context.py +163 -0
- hackagent/tracking/decorators.py +299 -0
- hackagent/tracking/tracker.py +441 -0
- hackagent/types.py +54 -0
- hackagent/utils.py +194 -0
- hackagent/vulnerabilities/__init__.py +13 -0
- hackagent/vulnerabilities/prompts.py +81 -0
- hackagent-0.3.1.dist-info/METADATA +122 -0
- hackagent-0.3.1.dist-info/RECORD +183 -0
- hackagent-0.3.1.dist-info/WHEEL +4 -0
- hackagent-0.3.1.dist-info/entry_points.txt +2 -0
- hackagent-0.3.1.dist-info/licenses/LICENSE +202 -0
|
@@ -0,0 +1,488 @@
|
|
|
1
|
+
# Copyright 2025 - AI4I. All rights reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""
|
|
16
|
+
Agents Tab
|
|
17
|
+
|
|
18
|
+
Manage and view AI agents.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from datetime import datetime
|
|
22
|
+
from typing import Any
|
|
23
|
+
|
|
24
|
+
from textual.app import ComposeResult
|
|
25
|
+
from textual.binding import Binding
|
|
26
|
+
from textual.containers import Horizontal, VerticalScroll
|
|
27
|
+
from textual.widgets import Button, DataTable, Static
|
|
28
|
+
|
|
29
|
+
from hackagent.cli.config import CLIConfig
|
|
30
|
+
from hackagent.cli.tui.base import BaseTab
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class AgentsTab(BaseTab):
|
|
34
|
+
"""Agents tab for managing AI agents."""
|
|
35
|
+
|
|
36
|
+
DEFAULT_CSS = """
|
|
37
|
+
AgentsTab {
|
|
38
|
+
layout: vertical;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
AgentsTab .section-header {
|
|
42
|
+
background: $panel;
|
|
43
|
+
color: $accent;
|
|
44
|
+
text-style: bold;
|
|
45
|
+
padding: 0 1;
|
|
46
|
+
height: 1;
|
|
47
|
+
border-bottom: solid $primary;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
AgentsTab .toolbar {
|
|
51
|
+
height: 3;
|
|
52
|
+
padding: 1;
|
|
53
|
+
background: $panel;
|
|
54
|
+
border-bottom: solid $primary;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
AgentsTab .stats-bar {
|
|
58
|
+
height: 3;
|
|
59
|
+
background: $panel;
|
|
60
|
+
padding: 0 2;
|
|
61
|
+
border-bottom: solid $primary;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
AgentsTab #agents-table {
|
|
65
|
+
height: 2fr;
|
|
66
|
+
min-height: 10;
|
|
67
|
+
border: solid $primary;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
AgentsTab #agent-details-container {
|
|
71
|
+
height: 1fr;
|
|
72
|
+
min-height: 10;
|
|
73
|
+
max-height: 25;
|
|
74
|
+
background: $panel;
|
|
75
|
+
border: solid $accent;
|
|
76
|
+
margin: 1;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
AgentsTab .agent-details {
|
|
80
|
+
padding: 1 2;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
AgentsTab .empty-state {
|
|
84
|
+
height: 100%;
|
|
85
|
+
content-align: center middle;
|
|
86
|
+
background: $panel;
|
|
87
|
+
}
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
BINDINGS = [
|
|
91
|
+
Binding("n", "new_agent", "New Agent"),
|
|
92
|
+
Binding("d", "delete_agent", "Delete Agent"),
|
|
93
|
+
Binding("enter", "view_agent", "View Details"),
|
|
94
|
+
Binding("f5", "refresh", "Refresh"),
|
|
95
|
+
]
|
|
96
|
+
|
|
97
|
+
def __init__(self, cli_config: CLIConfig):
|
|
98
|
+
"""Initialize agents tab.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
cli_config: CLI configuration object
|
|
102
|
+
"""
|
|
103
|
+
super().__init__(cli_config)
|
|
104
|
+
self.agents_data: list[Any] = []
|
|
105
|
+
self.selected_agent: Any = None
|
|
106
|
+
|
|
107
|
+
def compose(self) -> ComposeResult:
|
|
108
|
+
"""Compose the agents layout."""
|
|
109
|
+
# Title section
|
|
110
|
+
yield Static(
|
|
111
|
+
"š¤ [bold cyan]Agent Management[/bold cyan]", classes="section-header"
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
# Statistics bar
|
|
115
|
+
yield Static(
|
|
116
|
+
"š [cyan]Total Agents:[/cyan] [yellow]0[/yellow] | "
|
|
117
|
+
"š¢ [green]Active:[/green] [yellow]0[/yellow] | "
|
|
118
|
+
"ā” [magenta]Last Updated:[/magenta] [dim]Never[/dim]",
|
|
119
|
+
id="agents-stats",
|
|
120
|
+
classes="stats-bar",
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
# Toolbar with action buttons
|
|
124
|
+
with Horizontal(classes="toolbar"):
|
|
125
|
+
yield Button("š Refresh", id="refresh-agents", variant="primary")
|
|
126
|
+
yield Button("ā New Agent", id="new-agent", variant="success")
|
|
127
|
+
yield Button("šļø Delete", id="delete-agent", variant="error")
|
|
128
|
+
|
|
129
|
+
# Agents table
|
|
130
|
+
table: DataTable = DataTable(
|
|
131
|
+
id="agents-table", zebra_stripes=True, cursor_type="row"
|
|
132
|
+
)
|
|
133
|
+
table.add_columns("ID", "Name", "Type", "Endpoint", "Status", "Created")
|
|
134
|
+
yield table
|
|
135
|
+
|
|
136
|
+
# Details panel
|
|
137
|
+
with VerticalScroll(classes="agent-details", id="agent-details-container"):
|
|
138
|
+
yield Static(
|
|
139
|
+
"[dim italic]š” Select an agent from the table above to view detailed information[/dim]",
|
|
140
|
+
id="agent-details",
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
def on_mount(self) -> None:
|
|
144
|
+
"""Called when the tab is mounted."""
|
|
145
|
+
# Show loading message immediately
|
|
146
|
+
try:
|
|
147
|
+
details_widget = self.query_one("#agent-details", Static)
|
|
148
|
+
details_widget.update("ā³ [cyan]Loading agents from API...[/cyan]")
|
|
149
|
+
|
|
150
|
+
stats_widget = self.query_one("#agents-stats", Static)
|
|
151
|
+
stats_widget.update(
|
|
152
|
+
"š [cyan]Total Agents:[/cyan] [yellow]...[/yellow] | "
|
|
153
|
+
"š¢ [green]Active:[/green] [yellow]...[/yellow] | "
|
|
154
|
+
"ā” [magenta]Status:[/magenta] [cyan]Loading...[/cyan]"
|
|
155
|
+
)
|
|
156
|
+
except Exception:
|
|
157
|
+
pass
|
|
158
|
+
|
|
159
|
+
# Call base class mount which handles initial refresh
|
|
160
|
+
super().on_mount()
|
|
161
|
+
|
|
162
|
+
# Enable auto-refresh every 10 seconds
|
|
163
|
+
self.enable_auto_refresh(interval=10.0)
|
|
164
|
+
|
|
165
|
+
def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
166
|
+
"""Handle button press events."""
|
|
167
|
+
if event.button.id == "refresh-agents":
|
|
168
|
+
self.action_refresh()
|
|
169
|
+
elif event.button.id == "new-agent":
|
|
170
|
+
self._show_info_message("ā Create new agent feature coming soon!")
|
|
171
|
+
elif event.button.id == "delete-agent":
|
|
172
|
+
if self.selected_agent:
|
|
173
|
+
self._show_info_message(
|
|
174
|
+
f"šļø Delete agent '{self.selected_agent.name}' - feature coming soon!"
|
|
175
|
+
)
|
|
176
|
+
else:
|
|
177
|
+
self._show_info_message("ā ļø Please select an agent to delete")
|
|
178
|
+
|
|
179
|
+
def action_refresh(self) -> None:
|
|
180
|
+
"""Action to manually refresh agents data."""
|
|
181
|
+
try:
|
|
182
|
+
stats_widget = self.query_one("#agents-stats", Static)
|
|
183
|
+
stats_widget.update(
|
|
184
|
+
"š [cyan]Total Agents:[/cyan] [yellow]...[/yellow] | "
|
|
185
|
+
"š¢ [green]Active:[/green] [yellow]...[/yellow] | "
|
|
186
|
+
"ā” [magenta]Status:[/magenta] [cyan]Refreshing...[/cyan]"
|
|
187
|
+
)
|
|
188
|
+
except Exception:
|
|
189
|
+
pass
|
|
190
|
+
self.refresh_data()
|
|
191
|
+
|
|
192
|
+
def on_data_table_row_selected(self, event: DataTable.RowSelected) -> None:
|
|
193
|
+
"""Handle row selection in the agents table."""
|
|
194
|
+
table = self.query_one(DataTable)
|
|
195
|
+
row_key = event.row_key
|
|
196
|
+
row_index = table.get_row_index(row_key)
|
|
197
|
+
|
|
198
|
+
if row_index < len(self.agents_data):
|
|
199
|
+
self.selected_agent = self.agents_data[row_index]
|
|
200
|
+
self._show_agent_details()
|
|
201
|
+
|
|
202
|
+
def refresh_data(self) -> None:
|
|
203
|
+
"""Refresh agents data from API."""
|
|
204
|
+
try:
|
|
205
|
+
from hackagent.api.agent import agent_list
|
|
206
|
+
|
|
207
|
+
# Validate configuration
|
|
208
|
+
if not self.cli_config.api_key:
|
|
209
|
+
self._show_empty_state(
|
|
210
|
+
"š [bold yellow]API Key Not Configured[/bold yellow]\n\n"
|
|
211
|
+
"[cyan]To get started:[/cyan]\n"
|
|
212
|
+
"1. Run: [bold]hackagent config set --api-key YOUR_KEY[/bold]\n"
|
|
213
|
+
"2. Press [bold]F5[/bold] to refresh\n\n"
|
|
214
|
+
"[dim]Need an API key? Visit the HackAgent dashboard[/dim]"
|
|
215
|
+
)
|
|
216
|
+
return
|
|
217
|
+
|
|
218
|
+
# Create API client with timeout
|
|
219
|
+
client = self.create_api_client()
|
|
220
|
+
|
|
221
|
+
# Fetch agents
|
|
222
|
+
response = agent_list.sync_detailed(client=client)
|
|
223
|
+
|
|
224
|
+
if response.status_code == 200 and response.parsed:
|
|
225
|
+
self.agents_data = (
|
|
226
|
+
response.parsed.results if response.parsed.results else []
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
# Always update the table, even if empty
|
|
230
|
+
if not self.agents_data:
|
|
231
|
+
# Clear table and show empty message
|
|
232
|
+
table = self.query_one("#agents-table", DataTable)
|
|
233
|
+
table.clear()
|
|
234
|
+
|
|
235
|
+
# Update stats
|
|
236
|
+
stats_widget = self.query_one("#agents-stats", Static)
|
|
237
|
+
stats_widget.update(
|
|
238
|
+
"š [cyan]Total Agents:[/cyan] [yellow]0[/yellow] | "
|
|
239
|
+
"š¢ [green]Active:[/green] [yellow]0[/yellow] | "
|
|
240
|
+
"ā” [magenta]Status:[/magenta] [green]Loaded[/green]"
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
details_widget = self.query_one("#agent-details", Static)
|
|
244
|
+
details_widget.update(
|
|
245
|
+
"š [bold cyan]No Agents Found[/bold cyan]\n\n"
|
|
246
|
+
"[yellow]Get started by creating your first agent:[/yellow]\n\n"
|
|
247
|
+
"⢠Click [bold]ā New Agent[/bold] button above\n"
|
|
248
|
+
"⢠Or use the CLI: [bold]hackagent agent create[/bold]\n\n"
|
|
249
|
+
"[dim]Agents are AI systems that you can test for security vulnerabilities[/dim]"
|
|
250
|
+
)
|
|
251
|
+
else:
|
|
252
|
+
self._update_table()
|
|
253
|
+
elif response.status_code == 401:
|
|
254
|
+
error_msg = self.handle_api_error(Exception("401"), "Authentication")
|
|
255
|
+
self._show_empty_state(error_msg)
|
|
256
|
+
elif response.status_code == 403:
|
|
257
|
+
self._show_empty_state(
|
|
258
|
+
"š« [bold red]Access Forbidden[/bold red]\n\n"
|
|
259
|
+
"[yellow]Your API key doesn't have permission to view agents[/yellow]\n\n"
|
|
260
|
+
"Contact your administrator or check your API key permissions"
|
|
261
|
+
)
|
|
262
|
+
else:
|
|
263
|
+
error_status = f"API error: {response.status_code}"
|
|
264
|
+
self._show_empty_state(
|
|
265
|
+
f"ā ļø [bold red]API Error[/bold red]\n\n{error_status}\n\n[dim]Press F5 to retry[/dim]"
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
except Exception as e:
|
|
269
|
+
error_msg = self.handle_api_error(e, "Loading agents")
|
|
270
|
+
self._show_empty_state(error_msg)
|
|
271
|
+
|
|
272
|
+
def _show_empty_state(self, message: str) -> None:
|
|
273
|
+
"""Show an empty state message when no data is available.
|
|
274
|
+
|
|
275
|
+
Args:
|
|
276
|
+
message: Message to display
|
|
277
|
+
"""
|
|
278
|
+
table = self.query_one("#agents-table", DataTable)
|
|
279
|
+
table.clear()
|
|
280
|
+
|
|
281
|
+
# Update stats bar
|
|
282
|
+
try:
|
|
283
|
+
stats_widget = self.query_one("#agents-stats", Static)
|
|
284
|
+
stats_widget.update(
|
|
285
|
+
"š [cyan]Total Agents:[/cyan] [red]0[/red] | "
|
|
286
|
+
"š¢ [green]Active:[/green] [red]0[/red] | "
|
|
287
|
+
"ā” [magenta]Status:[/magenta] [red]Error[/red]"
|
|
288
|
+
)
|
|
289
|
+
except Exception:
|
|
290
|
+
pass
|
|
291
|
+
|
|
292
|
+
# Show message in details area
|
|
293
|
+
details_widget = self.query_one("#agent-details", Static)
|
|
294
|
+
details_widget.update(message)
|
|
295
|
+
|
|
296
|
+
def _show_info_message(self, message: str) -> None:
|
|
297
|
+
"""Show an informational message in the details panel.
|
|
298
|
+
|
|
299
|
+
Args:
|
|
300
|
+
message: Message to display
|
|
301
|
+
"""
|
|
302
|
+
details_widget = self.query_one("#agent-details", Static)
|
|
303
|
+
details_widget.update(
|
|
304
|
+
f"\n{message}\n\n[dim]This message will be replaced when you select an agent[/dim]"
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
def _update_table(self) -> None:
|
|
308
|
+
"""Update the agents table with current data."""
|
|
309
|
+
details_widget = self.query_one("#agent-details", Static)
|
|
310
|
+
try:
|
|
311
|
+
table = self.query_one("#agents-table", DataTable)
|
|
312
|
+
table.clear()
|
|
313
|
+
|
|
314
|
+
rows_added = 0
|
|
315
|
+
active_count = 0
|
|
316
|
+
|
|
317
|
+
for agent in self.agents_data:
|
|
318
|
+
# Format creation date
|
|
319
|
+
created = "Unknown"
|
|
320
|
+
if hasattr(agent, "created_at") and agent.created_at:
|
|
321
|
+
try:
|
|
322
|
+
if isinstance(agent.created_at, datetime):
|
|
323
|
+
created = agent.created_at.strftime("%Y-%m-%d %H:%M")
|
|
324
|
+
else:
|
|
325
|
+
created = str(agent.created_at)[:16]
|
|
326
|
+
except (AttributeError, ValueError, TypeError):
|
|
327
|
+
created = str(agent.created_at)[:16]
|
|
328
|
+
|
|
329
|
+
# Get agent type
|
|
330
|
+
agent_type = "Unknown"
|
|
331
|
+
try:
|
|
332
|
+
agent_type = (
|
|
333
|
+
agent.agent_type.value
|
|
334
|
+
if hasattr(agent.agent_type, "value")
|
|
335
|
+
else str(agent.agent_type)
|
|
336
|
+
)
|
|
337
|
+
except Exception:
|
|
338
|
+
agent_type = "Unknown"
|
|
339
|
+
|
|
340
|
+
# Get endpoint
|
|
341
|
+
endpoint = "N/A"
|
|
342
|
+
try:
|
|
343
|
+
if agent.endpoint:
|
|
344
|
+
endpoint = (
|
|
345
|
+
(agent.endpoint[:35] + "...")
|
|
346
|
+
if len(agent.endpoint) > 35
|
|
347
|
+
else agent.endpoint
|
|
348
|
+
)
|
|
349
|
+
except Exception:
|
|
350
|
+
endpoint = "N/A"
|
|
351
|
+
|
|
352
|
+
# Determine status
|
|
353
|
+
status = "š¢ Active"
|
|
354
|
+
if hasattr(agent, "endpoint") and agent.endpoint:
|
|
355
|
+
active_count += 1
|
|
356
|
+
else:
|
|
357
|
+
status = "āŖ Inactive"
|
|
358
|
+
|
|
359
|
+
table.add_row(
|
|
360
|
+
str(agent.id)[:8] + "...",
|
|
361
|
+
agent.name or "Unnamed",
|
|
362
|
+
agent_type,
|
|
363
|
+
endpoint,
|
|
364
|
+
status,
|
|
365
|
+
created,
|
|
366
|
+
)
|
|
367
|
+
rows_added += 1
|
|
368
|
+
|
|
369
|
+
# Update statistics bar
|
|
370
|
+
from datetime import datetime as dt
|
|
371
|
+
|
|
372
|
+
current_time = dt.now().strftime("%H:%M:%S")
|
|
373
|
+
|
|
374
|
+
stats_widget = self.query_one("#agents-stats", Static)
|
|
375
|
+
stats_widget.update(
|
|
376
|
+
f"š [cyan]Total Agents:[/cyan] [green]{rows_added}[/green] | "
|
|
377
|
+
f"š¢ [green]Active:[/green] [green]{active_count}[/green] | "
|
|
378
|
+
f"ā” [magenta]Last Updated:[/magenta] [yellow]{current_time}[/yellow]"
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
# Show success message
|
|
382
|
+
inactive_count = rows_added - active_count
|
|
383
|
+
details_widget.update(
|
|
384
|
+
f"ā
[bold green]Successfully loaded {rows_added} agent(s)[/bold green]\n\n"
|
|
385
|
+
f"[cyan]Agent Summary:[/cyan]\n"
|
|
386
|
+
f"⢠Total: [yellow]{rows_added}[/yellow]\n"
|
|
387
|
+
f"⢠Active (with endpoint): [green]{active_count}[/green]\n"
|
|
388
|
+
f"⢠Inactive: [yellow]{inactive_count}[/yellow]\n\n"
|
|
389
|
+
f"[dim italic]š” Click on any agent in the table to view detailed information[/dim]"
|
|
390
|
+
)
|
|
391
|
+
|
|
392
|
+
except Exception as e:
|
|
393
|
+
# If table update fails, show detailed error
|
|
394
|
+
import traceback
|
|
395
|
+
from rich.markup import escape
|
|
396
|
+
|
|
397
|
+
error_details = traceback.format_exc()
|
|
398
|
+
error_msg = escape(str(e))
|
|
399
|
+
escaped_details = escape(error_details[:400])
|
|
400
|
+
|
|
401
|
+
details_widget.update(
|
|
402
|
+
f"ā [bold red]Error updating table[/bold red]\n\n"
|
|
403
|
+
f"[yellow]{type(e).__name__}:[/yellow] {error_msg}\n\n"
|
|
404
|
+
f"[dim]Debug info:\n{escaped_details}[/dim]"
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
def _show_agent_details(self) -> None:
|
|
408
|
+
"""Show details of the selected agent."""
|
|
409
|
+
if not self.selected_agent:
|
|
410
|
+
return
|
|
411
|
+
|
|
412
|
+
agent = self.selected_agent
|
|
413
|
+
details_widget = self.query_one("#agent-details", Static)
|
|
414
|
+
|
|
415
|
+
# Format creation date
|
|
416
|
+
created = "Unknown"
|
|
417
|
+
if hasattr(agent, "created_at") and agent.created_at:
|
|
418
|
+
try:
|
|
419
|
+
if isinstance(agent.created_at, datetime):
|
|
420
|
+
created = agent.created_at.strftime("%Y-%m-%d %H:%M:%S")
|
|
421
|
+
else:
|
|
422
|
+
created = str(agent.created_at)
|
|
423
|
+
except (AttributeError, ValueError, TypeError):
|
|
424
|
+
created = str(agent.created_at)
|
|
425
|
+
|
|
426
|
+
# Get agent type
|
|
427
|
+
agent_type = "Unknown"
|
|
428
|
+
try:
|
|
429
|
+
agent_type = (
|
|
430
|
+
agent.agent_type.value
|
|
431
|
+
if hasattr(agent.agent_type, "value")
|
|
432
|
+
else str(agent.agent_type)
|
|
433
|
+
)
|
|
434
|
+
except Exception:
|
|
435
|
+
agent_type = "Unknown"
|
|
436
|
+
|
|
437
|
+
# Determine status emoji
|
|
438
|
+
status_icon = "š¢" if (hasattr(agent, "endpoint") and agent.endpoint) else "āŖ"
|
|
439
|
+
status_text = "Active" if status_icon == "š¢" else "Inactive"
|
|
440
|
+
|
|
441
|
+
# Build details view with better formatting
|
|
442
|
+
details = f"""āā [bold cyan]š¤ Agent Details[/bold cyan] āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā®
|
|
443
|
+
|
|
444
|
+
{status_icon} [bold yellow]Status:[/bold yellow] {status_text}
|
|
445
|
+
|
|
446
|
+
[bold cyan]āāā Basic Information āāā[/bold cyan]
|
|
447
|
+
|
|
448
|
+
[bold]š ID:[/bold]
|
|
449
|
+
{agent.id}
|
|
450
|
+
|
|
451
|
+
[bold]š Name:[/bold]
|
|
452
|
+
{agent.name or "[dim]Unnamed[/dim]"}
|
|
453
|
+
|
|
454
|
+
[bold]š·ļø Type:[/bold]
|
|
455
|
+
{agent_type}
|
|
456
|
+
|
|
457
|
+
[bold]š
Created:[/bold]
|
|
458
|
+
{created}
|
|
459
|
+
|
|
460
|
+
[bold cyan]āāā Configuration āāā[/bold cyan]
|
|
461
|
+
|
|
462
|
+
[bold]š Endpoint:[/bold]
|
|
463
|
+
{agent.endpoint or "[dim]Not specified[/dim]"}
|
|
464
|
+
|
|
465
|
+
[bold]š Description:[/bold]
|
|
466
|
+
{agent.description or "[dim]No description provided[/dim]"}
|
|
467
|
+
"""
|
|
468
|
+
|
|
469
|
+
if hasattr(agent, "organization") and agent.organization:
|
|
470
|
+
details += f"\n [bold]š¢ Organization:[/bold]\n {agent.organization}\n"
|
|
471
|
+
|
|
472
|
+
# Add metadata section if available
|
|
473
|
+
if hasattr(agent, "metadata") and agent.metadata:
|
|
474
|
+
details += "\n[bold cyan]āāā Metadata āāā[/bold cyan]\n\n"
|
|
475
|
+
try:
|
|
476
|
+
import json
|
|
477
|
+
|
|
478
|
+
metadata_str = json.dumps(agent.metadata, indent=2)
|
|
479
|
+
details += f" {metadata_str}\n"
|
|
480
|
+
except Exception:
|
|
481
|
+
details += f" {str(agent.metadata)}\n"
|
|
482
|
+
|
|
483
|
+
details += "\nā°āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāÆ\n"
|
|
484
|
+
details += (
|
|
485
|
+
"\n[dim italic]š” Press 'd' to delete this agent or 'F5' to refresh[/dim]"
|
|
486
|
+
)
|
|
487
|
+
|
|
488
|
+
details_widget.update(details)
|