supervaizer 0.10.6__py3-none-any.whl → 0.10.15__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.
supervaizer/__init__.py CHANGED
@@ -13,6 +13,8 @@ from supervaizer.agent import (
13
13
  AgentMethod,
14
14
  AgentMethodParams,
15
15
  AgentMethods,
16
+ AgentMethodField,
17
+ FieldTypeEnum,
16
18
  )
17
19
  from supervaizer.case import (
18
20
  Case,
@@ -50,6 +52,7 @@ __all__ = [
50
52
  "Agent",
51
53
  "AgentCustomMethodParams",
52
54
  "AgentMethod",
55
+ "AgentMethodField",
53
56
  "AgentMethodParams",
54
57
  "AgentMethods",
55
58
  "AgentRegisterEvent",
@@ -57,11 +60,11 @@ __all__ = [
57
60
  "ApiResult",
58
61
  "ApiSuccess",
59
62
  "Case",
60
- "CaseNodeUpdate",
61
- "CaseNodeType",
62
- "Cases",
63
63
  "CaseNode",
64
64
  "CaseNodes",
65
+ "CaseNodeType",
66
+ "CaseNodeUpdate",
67
+ "Cases",
65
68
  "CaseStartEvent",
66
69
  "CaseUpdateEvent",
67
70
  "create_error_response",
@@ -75,6 +78,7 @@ __all__ = [
75
78
  "ErrorType",
76
79
  "Event",
77
80
  "EventType",
81
+ "FieldTypeEnum",
78
82
  "Job",
79
83
  "JobContext",
80
84
  "JobFinishedEvent",
@@ -5,6 +5,6 @@
5
5
  # https://mozilla.org/MPL/2.0/.
6
6
 
7
7
 
8
- VERSION = "0.10.6"
8
+ VERSION = "0.10.15"
9
9
  API_VERSION = "v1"
10
10
  TELEMETRY_VERSION = "v1"
supervaizer/account.py CHANGED
@@ -8,7 +8,7 @@
8
8
  from typing import TYPE_CHECKING, Any, ClassVar, Dict, Optional, Union
9
9
 
10
10
  import httpx
11
- from pydantic import Field
11
+ from pydantic import Field, field_validator
12
12
 
13
13
  from supervaizer.__version__ import VERSION
14
14
  from supervaizer.common import ApiError, ApiResult, ApiSuccess, SvBaseModel
@@ -55,6 +55,14 @@ class AccountAbstract(SvBaseModel):
55
55
  description="The URL of the Supervaize SaaS API provided by Supervaize.com"
56
56
  )
57
57
 
58
+ @field_validator("workspace_id", "api_key", "api_url", mode="before")
59
+ @classmethod
60
+ def strip_whitespace(cls, v: Any) -> Any:
61
+ """Strip leading/trailing whitespace (e.g. newlines from env vars)."""
62
+ if isinstance(v, str):
63
+ return v.strip()
64
+ return v
65
+
58
66
  model_config = {
59
67
  "reference_group": "Core",
60
68
  "json_schema_extra": {
@@ -62,7 +70,7 @@ class AccountAbstract(SvBaseModel):
62
70
  {
63
71
  "workspace_id": "ws_1234567890abcdef",
64
72
  "api_key": "sk_1234567890abcdef",
65
- "api_url": "https://api.supervaize.com",
73
+ "api_url": "https://app.supervaize.com",
66
74
  }
67
75
  ]
68
76
  },
@@ -121,7 +129,7 @@ class Account(AccountAbstract):
121
129
  """URL for the Supervaize Control API.
122
130
  Tested in tests/test_account.py
123
131
  """
124
- return f"{self.api_url_w_v1}/ctrl-events/"
132
+ return f"{self.api_url_w_v1}/ctrl-events/".strip()
125
133
 
126
134
  def get_url(self, pattern_name: str, **kwargs: Any) -> str:
127
135
  """Generate a URL using the predefined patterns.
@@ -191,7 +199,13 @@ class Account(AccountAbstract):
191
199
  from supervaizer.event import ServerRegisterEvent
192
200
 
193
201
  event = ServerRegisterEvent(server=server, account=self)
194
- return self.send_event(sender=server, event=event)
202
+ result = self.send_event(sender=server, event=event)
203
+ if isinstance(result, ApiSuccess):
204
+ log.success(result.message)
205
+ # TODO: Update server with the server ID from the response. store this ID in env variable.
206
+ else:
207
+ log.error(result.message)
208
+ return result
195
209
 
196
210
  def _create_api_result(
197
211
  self,
@@ -59,14 +59,17 @@ def send_event(
59
59
 
60
60
  headers = account.api_headers
61
61
  payload = event.payload
62
+ url_event = (
63
+ account.url_event.strip()
64
+ ) # defensive: env vars often have trailing newline
62
65
 
63
66
  # Generate curl equivalent for debugging
64
67
 
65
68
  curl_headers = " ".join([f'-H "{k}: {v}"' for k, v in headers.items()])
66
- curl_cmd = f"curl -X 'GET' '{account.url_event}' {curl_headers}"
69
+ curl_cmd = f"curl -X 'GET' '{url_event}' {curl_headers}"
67
70
 
68
71
  try:
69
- response = _httpx_client.post(account.url_event, headers=headers, json=payload)
72
+ response = _httpx_client.post(url_event, headers=headers, json=payload)
70
73
 
71
74
  # Log response details before raising for status
72
75
 
@@ -76,6 +79,13 @@ def send_event(
76
79
  )
77
80
 
78
81
  log.success(result.log_message)
82
+ except (httpx.ConnectError, httpx.ConnectTimeout) as e:
83
+ log.error(
84
+ f"Supervaize controller is not available at {url_event}. "
85
+ "Connection refused or timed out. Is the controller server running?"
86
+ )
87
+ log.error(f"❌ Error sending event {event.type.name}: {e!s}")
88
+ raise e
79
89
  except httpx.HTTPError as e:
80
90
  # Enhanced error logging
81
91
  log.error("[Send event] HTTP Error occurred")
@@ -83,7 +93,7 @@ def send_event(
83
93
 
84
94
  error_result = ApiError(
85
95
  message=f"Error sending event {event.type.name}",
86
- url=account.url_event,
96
+ url=url_event,
87
97
  payload=event.payload,
88
98
  exception=e,
89
99
  )
@@ -20,8 +20,8 @@ from pathlib import Path
20
20
  from typing import Any, AsyncGenerator, Dict, List, Optional
21
21
 
22
22
  import psutil
23
- from fastapi import APIRouter, HTTPException, Query, Request, Security
24
- from fastapi.responses import HTMLResponse, Response
23
+ from fastapi import APIRouter, Depends, HTTPException, Query, Request, Security
24
+ from fastapi.responses import HTMLResponse, JSONResponse, Response
25
25
  from fastapi.security import APIKeyHeader
26
26
  from fastapi.templating import Jinja2Templates
27
27
  from pydantic import BaseModel
@@ -267,6 +267,7 @@ def create_admin_routes() -> APIRouter:
267
267
  "db_name": "TinyDB",
268
268
  "data_storage_path": str(storage.db_path.absolute()),
269
269
  "api_key": os.getenv("SUPERVAIZER_API_KEY"),
270
+ "server_id": os.getenv("SUPERVAIZER_SERVER_ID"),
270
271
  },
271
272
  )
272
273
  except Exception as e:
@@ -413,6 +414,59 @@ def create_admin_routes() -> APIRouter:
413
414
  log.error(f"Get server status API error: {e}")
414
415
  raise HTTPException(status_code=500, detail=str(e))
415
416
 
417
+ @router.post("/api/server/register")
418
+ async def register_server_with_supervisor(
419
+ request: Request,
420
+ _: bool = Depends(verify_admin_access),
421
+ ) -> JSONResponse:
422
+ """Trigger SERVER_REGISTER to the supervaizer supervisor."""
423
+ try:
424
+ from supervaizer.common import ApiSuccess
425
+ from supervaizer.routes import get_server
426
+
427
+ get_current = request.app.dependency_overrides.get(get_server)
428
+ if get_current is None:
429
+ raise HTTPException(
430
+ status_code=503,
431
+ detail="Server instance not available (admin not running with live server)",
432
+ )
433
+ try:
434
+ server = await get_current()
435
+ except (NotImplementedError, TypeError):
436
+ raise HTTPException(
437
+ status_code=503,
438
+ detail="Server instance not available (admin not running with live server)",
439
+ )
440
+ if not getattr(server, "supervisor_account", None):
441
+ raise HTTPException(
442
+ status_code=503,
443
+ detail="No supervisor account configured",
444
+ )
445
+ result = server.supervisor_account.register_server(server=server)
446
+ if isinstance(result, ApiSuccess):
447
+ return JSONResponse(
448
+ status_code=200,
449
+ content={
450
+ "success": True,
451
+ "message": result.message,
452
+ "detail": result.detail,
453
+ },
454
+ )
455
+ # ApiError
456
+ return JSONResponse(
457
+ status_code=502,
458
+ content={
459
+ "success": False,
460
+ "message": result.message,
461
+ "detail": getattr(result, "detail", None),
462
+ },
463
+ )
464
+ except HTTPException:
465
+ raise
466
+ except Exception as e:
467
+ log.error(f"Server register API error: {e}")
468
+ raise HTTPException(status_code=500, detail=str(e))
469
+
416
470
  @router.get("/api/agents")
417
471
  async def get_agents_api(
418
472
  request: Request,
@@ -872,28 +926,24 @@ def create_admin_routes() -> APIRouter:
872
926
  # Combine and sort by created_at
873
927
  activities = []
874
928
  for job in recent_jobs:
875
- activities.append(
876
- {
877
- "type": "job",
878
- "id": job.get("id"),
879
- "name": job.get("name"),
880
- "status": job.get("status"),
881
- "created_at": job.get("created_at"),
882
- "agent_name": job.get("agent_name"),
883
- }
884
- )
929
+ activities.append({
930
+ "type": "job",
931
+ "id": job.get("id"),
932
+ "name": job.get("name"),
933
+ "status": job.get("status"),
934
+ "created_at": job.get("created_at"),
935
+ "agent_name": job.get("agent_name"),
936
+ })
885
937
 
886
938
  for case in recent_cases:
887
- activities.append(
888
- {
889
- "type": "case",
890
- "id": case.get("id"),
891
- "name": case.get("name"),
892
- "status": case.get("status"),
893
- "created_at": case.get("created_at"),
894
- "job_id": case.get("job_id"),
895
- }
896
- )
939
+ activities.append({
940
+ "type": "case",
941
+ "id": case.get("id"),
942
+ "name": case.get("name"),
943
+ "status": case.get("status"),
944
+ "created_at": case.get("created_at"),
945
+ "job_id": case.get("job_id"),
946
+ })
897
947
 
898
948
  # Sort by created_at descending
899
949
  activities.sort(key=lambda x: str(x.get("created_at", "")), reverse=True)
@@ -1150,23 +1200,23 @@ def get_dashboard_stats(storage: StorageManager) -> AdminStats:
1150
1200
 
1151
1201
  # Calculate job stats
1152
1202
  job_total = len(all_jobs)
1153
- job_running = len(
1154
- [j for j in all_jobs if j.get("status") in ["in_progress", "awaiting"]]
1155
- )
1203
+ job_running = len([
1204
+ j for j in all_jobs if j.get("status") in ["in_progress", "awaiting"]
1205
+ ])
1156
1206
  job_completed = len([j for j in all_jobs if j.get("status") == "completed"])
1157
- job_failed = len(
1158
- [j for j in all_jobs if j.get("status") in ["failed", "cancelled"]]
1159
- )
1207
+ job_failed = len([
1208
+ j for j in all_jobs if j.get("status") in ["failed", "cancelled"]
1209
+ ])
1160
1210
 
1161
1211
  # Calculate case stats
1162
1212
  case_total = len(all_cases)
1163
- case_running = len(
1164
- [c for c in all_cases if c.get("status") in ["in_progress", "awaiting"]]
1165
- )
1213
+ case_running = len([
1214
+ c for c in all_cases if c.get("status") in ["in_progress", "awaiting"]
1215
+ ])
1166
1216
  case_completed = len([c for c in all_cases if c.get("status") == "completed"])
1167
- case_failed = len(
1168
- [c for c in all_cases if c.get("status") in ["failed", "cancelled"]]
1169
- )
1217
+ case_failed = len([
1218
+ c for c in all_cases if c.get("status") in ["failed", "cancelled"]
1219
+ ])
1170
1220
 
1171
1221
  # TinyDB collections count (tables)
1172
1222
  collections_count = len(storage._db.tables())
Binary file
@@ -0,0 +1,63 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Supervaizer API</title>
7
+ <!-- Tailwind CSS -->
8
+ <script src="https://cdn.tailwindcss.com"></script>
9
+ </head>
10
+ <body class="bg-gray-50 min-h-screen">
11
+ <main class="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
12
+ <div class="px-4 py-6 sm:px-0">
13
+ <h1 class="text-2xl font-bold leading-7 text-gray-900 sm:truncate sm:text-3xl sm:tracking-tight">
14
+ Supervaizer API
15
+ </h1>
16
+ <p class="mt-2 text-sm text-gray-500">
17
+ Controller version {{ version }} · API version {{ api_version }}
18
+ </p>
19
+ <p class="mt-1 text-xs text-gray-400 font-mono">Server ID: {{ server_id }}</p>
20
+
21
+ <div class="mt-8">
22
+ <h2 class="text-lg font-medium text-gray-900 mb-4">Links</h2>
23
+ <ul class="space-y-2">
24
+ <li>
25
+ <a href="{{ base }}/docs" class="text-blue-600 hover:text-blue-800 font-medium">
26
+ Swagger UI
27
+ </a>
28
+ </li>
29
+ <li>
30
+ <a href="{{ base }}/redoc" class="text-blue-600 hover:text-blue-800 font-medium">
31
+ ReDoc
32
+ </a>
33
+ </li>
34
+ <li>
35
+ <a href="{{ base }}/openapi.json" class="text-blue-600 hover:text-blue-800 font-medium">
36
+ OpenAPI
37
+ </a>
38
+ </li>
39
+ <li><strong>BASE:</strong> {{ base }}
40
+ <br>
41
+ <strong>PUBLIC_URL:</strong> {{ public_url }}
42
+ <br>
43
+ <strong>FULL_URL:</strong> {{ full_url }}
44
+ <br>
45
+ </li>
46
+
47
+ {% if show_admin %}
48
+ <li>
49
+ <a href="{{ base }}/admin" class="text-blue-600 hover:text-blue-800 font-medium">
50
+ Admin
51
+ </a>
52
+ </li>
53
+ {% endif %}
54
+ </ul>
55
+ </div>
56
+ </div>
57
+ <hr>
58
+ <div class="mt-8">
59
+ TO REPLACE THIS FILE, create an index.html at the root of the project and it will be served as the home page.
60
+ </div>
61
+ </main>
62
+ </body>
63
+ </html>
@@ -12,7 +12,15 @@
12
12
  </h2>
13
13
  <p class="mt-1 text-sm text-gray-500">Monitor server health and configuration</p>
14
14
  </div>
15
- <div class="mt-4 flex md:mt-0">
15
+ <div class="mt-4 flex md:mt-0 gap-2">
16
+ <button
17
+ id="register-supervisor-btn"
18
+ type="button"
19
+ class="inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
20
+ >
21
+ <span id="register-indicator" class="htmx-indicator -ml-1 mr-2 h-4 w-4 border-2 border-white border-t-transparent rounded-full animate-spin"></span>
22
+ Register with supervisor
23
+ </button>
16
24
  <button
17
25
  hx-get="/admin/api/server/status"
18
26
  hx-target="#server-status-container"
@@ -26,6 +34,7 @@
26
34
  Refresh
27
35
  </button>
28
36
  </div>
37
+ <div id="register-result" class="mt-2 text-sm hidden" role="alert"></div>
29
38
  </div>
30
39
 
31
40
  <!-- Server Status Cards -->
@@ -91,15 +100,52 @@
91
100
  </div>
92
101
 
93
102
  <script>
94
- // Auto-refresh server status every 30 seconds
95
103
  document.addEventListener('DOMContentLoaded', function() {
96
- // Set up auto-refresh interval
104
+ // Auto-refresh server status every 30 seconds
97
105
  setInterval(function() {
98
106
  const refreshButton = document.querySelector('[hx-get="/admin/api/server/status"]');
99
107
  if (refreshButton) {
100
108
  htmx.trigger(refreshButton, 'click');
101
109
  }
102
- }, 30000); // Refresh every 30 seconds
110
+ }, 30000);
111
+
112
+ // Register with supervisor button
113
+ const registerBtn = document.getElementById('register-supervisor-btn');
114
+ const registerIndicator = document.getElementById('register-indicator');
115
+ const registerResult = document.getElementById('register-result');
116
+ if (registerBtn) {
117
+ registerBtn.addEventListener('click', async function() {
118
+ const params = new URLSearchParams(window.location.search);
119
+ const key = params.get('key');
120
+ const url = key ? '/admin/api/server/register?key=' + encodeURIComponent(key) : '/admin/api/server/register';
121
+ registerResult.classList.add('hidden');
122
+ registerIndicator.classList.remove('htmx-indicator');
123
+ registerBtn.disabled = true;
124
+ try {
125
+ const res = await fetch(url, { method: 'POST' });
126
+ const data = await res.json().catch(function() { return {}; });
127
+ registerResult.classList.remove('hidden');
128
+ if (res.ok && data.success) {
129
+ registerResult.className = 'mt-2 text-sm text-green-700';
130
+ registerResult.textContent = data.message || 'Registered successfully.';
131
+ } else {
132
+ registerResult.className = 'mt-2 text-sm text-red-700';
133
+ if (res.status === 403) {
134
+ registerResult.textContent = 'Authentication required. Open this page with ?key=...';
135
+ } else {
136
+ registerResult.textContent = (data.detail && typeof data.detail === 'object' && data.detail.message) ? data.detail.message : (data.message || 'Registration failed.');
137
+ }
138
+ }
139
+ } catch (e) {
140
+ registerResult.classList.remove('hidden');
141
+ registerResult.className = 'mt-2 text-sm text-red-700';
142
+ registerResult.textContent = 'Request failed: ' + (e.message || String(e));
143
+ } finally {
144
+ registerIndicator.classList.add('htmx-indicator');
145
+ registerBtn.disabled = false;
146
+ }
147
+ });
148
+ }
103
149
  });
104
150
  </script>
105
151
  {% endblock %}
supervaizer/agent.py CHANGED
@@ -78,7 +78,7 @@ class AgentMethodField(BaseModel):
78
78
  type: Any = Field(
79
79
  description="Python type of the field for pydantic validation - note , ChoiceField and MultipleChoiceField are a list[str]"
80
80
  )
81
- field_type: FieldTypeEnum = Field(
81
+ field_type: FieldTypeEnum | str = Field(
82
82
  default=FieldTypeEnum.CHAR, description="Field type for persistence"
83
83
  )
84
84
  description: str | None = Field(
@@ -185,6 +185,7 @@ class AgentMethodAbstract(BaseModel):
185
185
  default=None,
186
186
  description="A list of field specifications for generating forms/UI, following the django.forms.fields definition",
187
187
  )
188
+
188
189
  description: str | None = Field(
189
190
  default=None, description="Optional description of what the method does"
190
191
  )
@@ -21,27 +21,34 @@
21
21
  <h2 class="text-lg font-medium text-gray-900 mb-4">Links</h2>
22
22
  <ul class="space-y-2">
23
23
  <li>
24
- <a href="{{ base }}/docs" class="text-blue-600 hover:text-blue-800 font-medium">
24
+ <a href="/docs" class="text-blue-600 hover:text-blue-800 font-medium">
25
25
  Swagger UI
26
26
  </a>
27
27
  </li>
28
28
  <li>
29
- <a href="{{ base }}/redoc" class="text-blue-600 hover:text-blue-800 font-medium">
29
+ <a href="/redoc" class="text-blue-600 hover:text-blue-800 font-medium">
30
30
  ReDoc
31
31
  </a>
32
32
  </li>
33
33
  <li>
34
- <a href="{{ base }}/openapi.json" class="text-blue-600 hover:text-blue-800 font-medium">
34
+ <a href="/openapi.json" class="text-blue-600 hover:text-blue-800 font-medium">
35
35
  OpenAPI
36
36
  </a>
37
37
  </li>
38
38
  {% if show_admin %}
39
39
  <li>
40
- <a href="{{ base }}/admin" class="text-blue-600 hover:text-blue-800 font-medium">
40
+ <a href="/admin" class="text-blue-600 hover:text-blue-800 font-medium">
41
41
  Admin
42
42
  </a>
43
43
  </li>
44
44
  {% endif %}
45
+ <li>
46
+ <a href="/console" class="text-blue-600 hover:text-blue-800 font-medium">
47
+ Console
48
+ </a>
49
+ </li>
50
+ <li> BASE: {{ base }}
51
+ </li>
45
52
  </ul>
46
53
  </div>
47
54
  </div>
@@ -34,28 +34,26 @@ DEV_PUBLIC_URL = "https://myagent-dev.loca.lt"
34
34
  PROD_PUBLIC_URL = "https://myagent.cloud-hosting.net:8001"
35
35
 
36
36
  # Define the parameters and secrets expected by the agent
37
- agent_parameters: ParametersSetup | None = ParametersSetup.from_list(
38
- [
39
- Parameter(
40
- name="OPEN_API_KEY",
41
- description="OpenAPI Key",
42
- is_environment=True,
43
- is_secret=True,
44
- ),
45
- Parameter(
46
- name="SERPER_API",
47
- description="Server API key updated",
48
- is_environment=True,
49
- is_secret=True,
50
- ),
51
- Parameter(
52
- name="COMPETITOR_SUMMARY_URL",
53
- description="Competitor Summary URL",
54
- is_environment=True,
55
- is_secret=False,
56
- ),
57
- ]
58
- )
37
+ agent_parameters: ParametersSetup | None = ParametersSetup.from_list([
38
+ Parameter(
39
+ name="OPEN_API_KEY",
40
+ description="OpenAPI Key",
41
+ is_environment=True,
42
+ is_secret=True,
43
+ ),
44
+ Parameter(
45
+ name="SERPER_API",
46
+ description="Server API key updated",
47
+ is_environment=True,
48
+ is_secret=True,
49
+ ),
50
+ Parameter(
51
+ name="COMPETITOR_SUMMARY_URL",
52
+ description="Competitor Summary URL",
53
+ is_environment=True,
54
+ is_secret=False,
55
+ ),
56
+ ])
59
57
 
60
58
  # Define the method used to start a job
61
59
  job_start_method: AgentMethod = AgentMethod(
@@ -180,7 +178,7 @@ agent: Agent = Agent(
180
178
  account: Account = Account(
181
179
  workspace_id=os.getenv("SUPERVAIZE_WORKSPACE_ID") or "dummy_workspace_id",
182
180
  api_key=os.getenv("SUPERVAIZE_API_KEY") or "dummy_api_key",
183
- api_url=os.getenv("SUPERVAIZE_API_URL") or "https://api.supervaize.com",
181
+ api_url=os.getenv("SUPERVAIZE_API_URL") or "https://app.supervaize.com",
184
182
  )
185
183
 
186
184
  # Define the supervaizer server capabilities
supervaizer/server.py CHANGED
@@ -55,6 +55,16 @@ T = TypeVar("T")
55
55
  # Additional imports for server persistence
56
56
 
57
57
 
58
+ def _get_or_create_server_id() -> str:
59
+ """Use SUPERVAIZER_SERVER_ID from env if set; else create uuid and set env."""
60
+ existing = os.getenv("SUPERVAIZER_SERVER_ID")
61
+ if existing:
62
+ return existing
63
+ new_id = str(uuid.uuid4())
64
+ os.environ["SUPERVAIZER_SERVER_ID"] = new_id
65
+ return new_id
66
+
67
+
58
68
  class ServerInfo(BaseModel):
59
69
  """Complete server information for storage."""
60
70
 
@@ -78,21 +88,20 @@ def save_server_info_to_storage(server_instance: "Server") -> None:
78
88
  agents = []
79
89
  if hasattr(server_instance, "agents") and server_instance.agents:
80
90
  for agent in server_instance.agents:
81
- agents.append(
82
- {
83
- "name": agent.name,
84
- "description": agent.description,
85
- "version": agent.version,
86
- "api_path": agent.path,
87
- "slug": agent.slug,
88
- "instructions_path": agent.instructions_path,
89
- }
90
- )
91
+ agents.append({
92
+ "name": agent.name,
93
+ "description": agent.description,
94
+ "version": agent.version,
95
+ "api_path": agent.path,
96
+ "slug": agent.slug,
97
+ "instructions_path": agent.instructions_path,
98
+ })
91
99
 
92
100
  # Create server info
93
101
  server_info = ServerInfo(
94
- host=getattr(server_instance, "host", "0.0.0.0"),
95
- port=getattr(server_instance, "port", 8000),
102
+ id=getattr(server_instance, "server_id", "N/A"),
103
+ host=getattr(server_instance, "host", "N/A"),
104
+ port=getattr(server_instance, "port", "N/A"),
96
105
  api_version=API_VERSION,
97
106
  environment=os.getenv("SUPERVAIZER_ENVIRONMENT", "development"),
98
107
  agents=agents,
@@ -150,6 +159,10 @@ class ServerAbstract(SvBaseModel):
150
159
  """
151
160
 
152
161
  supervaizer_VERSION: ClassVar[str] = VERSION
162
+ server_id: str = Field(
163
+ default_factory=_get_or_create_server_id,
164
+ description="Unique server id (SUPERVAIZER_SERVER_ID env or persisted uuid)",
165
+ )
153
166
  scheme: str = Field(description="URL scheme (http or https)")
154
167
  host: str = Field(
155
168
  description="Host to bind the server to (e.g., 0.0.0.0 for all interfaces)"
@@ -356,6 +369,8 @@ class Server(ServerAbstract):
356
369
  **kwargs,
357
370
  )
358
371
 
372
+ log.info(f"[Server launch] Server ID: {self.server_id}")
373
+
359
374
  # Create routes
360
375
  if self.supervisor_account:
361
376
  log.info(
@@ -379,9 +394,9 @@ class Server(ServerAbstract):
379
394
  # Save server info to storage for admin interface
380
395
  save_server_info_to_storage(self)
381
396
 
382
- # Home page (template in deploy/templates)
397
+ # Home page (template in admin/templates)
383
398
  _home_templates = Jinja2Templates(
384
- directory=str(Path(__file__).parent / "deploy" / "templates")
399
+ directory=str(Path(__file__).parent / "admin" / "templates")
385
400
  )
386
401
 
387
402
  @self.app.get("/", response_class=HTMLResponse)
@@ -395,6 +410,7 @@ class Server(ServerAbstract):
395
410
  "version": VERSION,
396
411
  "api_version": API_VERSION,
397
412
  "show_admin": bool(self.api_key and admin_interface),
413
+ "server_id": self.server_id,
398
414
  },
399
415
  )
400
416
 
@@ -465,6 +481,7 @@ class Server(ServerAbstract):
465
481
  """Get registration info for the server."""
466
482
  assert self.public_key is not None, "Public key not initialized"
467
483
  return {
484
+ "server_id": self.server_id,
468
485
  "url": self.public_url,
469
486
  "uri": self.uri,
470
487
  "api_version": API_VERSION,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: supervaizer
3
- Version: 0.10.6
3
+ Version: 0.10.15
4
4
  Summary: Controller system for Supervaize
5
5
  Project-URL: Homepage, https://supervaize.com
6
6
  Project-URL: Repository, https://github.com/supervaize/supervaizer
@@ -1,8 +1,8 @@
1
- supervaizer/__init__.py,sha256=eBbGjduMBH-FDjcGlSqeR4Kf4uo60Cf1DrRur8VGkJo,2341
2
- supervaizer/__version__.py,sha256=v8EJ2DGVFNsFsJ0Q0SzepTZR0GtKIndBvf_raRULqfs,348
3
- supervaizer/account.py,sha256=-K1pd590Lp2TncZ2jn442kBLxnm482G3WnQavVFxZ1s,11102
4
- supervaizer/account_service.py,sha256=z4lw8qp8XvCrU6Ndf4VHRnQwY_071410ts5_7E8YDAs,3046
5
- supervaizer/agent.py,sha256=Clenvdr_v-lV7_v6YCcevp8dj5JUNvpTWrBZBYM82lg,36509
1
+ supervaizer/__init__.py,sha256=UNFcgJT-2708tLRFXohWr3320LVAzh3_WZZhrU9j1Iw,2427
2
+ supervaizer/__version__.py,sha256=Oe_Vfg1WOw027ZNCTKD35vUMGykGwCa00Szf8CAsyS8,349
3
+ supervaizer/account.py,sha256=POChw9c2ZjBNvesz6JmvBzYmMD40M5oxtz5IupuMqsw,11683
4
+ supervaizer/account_service.py,sha256=ZgZ0fhsbSVA076c-35ZZoYJBrQZsAwfFS7namVeoD3s,3459
5
+ supervaizer/agent.py,sha256=EtmqBH9fsO4k8YcHnjj55_4uOmb5URZwLlIIJReWg7A,36516
6
6
  supervaizer/case.py,sha256=dOgRujyf4MFcZ-937zxJbqLIPduKg6ZspHuhnWiq13Q,14385
7
7
  supervaizer/cli.py,sha256=3KH4gZXTiJ9GtY375kGm5HaKCGV2V8WEbcWdF_sWWEc,13936
8
8
  supervaizer/common.py,sha256=6ygNS1YxliP-e6OEVX7vDuC8YAaT2rYhjqQ_8txnWlg,10066
@@ -13,11 +13,12 @@ supervaizer/job_service.py,sha256=22Qe7Z5_u3R28tcNH_21YMIYciWFtJaM7I-MXtIhBMU,46
13
13
  supervaizer/lifecycle.py,sha256=5CunJN7MsM5blyNiFMJMLFDUBmTmmAsPE24QC-gSbYA,13958
14
14
  supervaizer/parameter.py,sha256=sYDuGof_w6mlix0oxjB6odV0sO0QSBL1KwFZa3Y2cOA,8157
15
15
  supervaizer/routes.py,sha256=o3u7pGGLE0MQzWtQNdd5xk1M0G9Y_BR_JiLVmCFtdC4,34319
16
- supervaizer/server.py,sha256=l4fTVTBw7beKI5vl3TzjwmWrBmTotBOnTM8mEoqh1g0,21641
16
+ supervaizer/server.py,sha256=JLavzhd8PVTDtqfupaHGnshnY5GCi5rxTC4ThGcml9M,22270
17
17
  supervaizer/server_utils.py,sha256=FMglpADQBJynkR2v-pfwANu6obsaPvR9j0BQc5Otpac,1590
18
18
  supervaizer/storage.py,sha256=WLX8ggwt1AGF07DrfD1K6PyP2-N45c_Ep4CL0iPLVbI,15332
19
19
  supervaizer/telemetry.py,sha256=XSYw8ZwoY8W6C7mhwHU67t7trJzWd7CBkkSNdsDT_HA,2596
20
- supervaizer/admin/routes.py,sha256=r9hucWtNiTGQHgv9DsRkVpwnLbyLFaqrbse1orl-oTA,45627
20
+ supervaizer/admin/routes.py,sha256=utXv4Agn9Cz9V6IG8XCKSJnRPK4qi7Ge7Ti9ZUlSB4w,47694
21
+ supervaizer/admin/static/favicon.ico,sha256=wFyXw96AplZoEcW45dJBeC1pHTcPSH07HGWbtrc49mI,5558
21
22
  supervaizer/admin/static/js/job-start-form.js,sha256=s--AGVYzgc9mE20POYeM0BNm0Wy41aBZVv99tc0cSPU,11938
22
23
  supervaizer/admin/templates/agent_detail.html,sha256=DFOGfjuQNC39FOLYUW_jD0A01WpBY1umatGCslyJ0_c,7581
23
24
  supervaizer/admin/templates/agents.html,sha256=orB_z1iMFE2MKhFm9XejwZjmzMC1PVT69oB-F9YZPHQ,12678
@@ -28,13 +29,14 @@ supervaizer/admin/templates/cases_list.html,sha256=UV4SfULzxNiOpG8aNddablpwf6hVN
28
29
  supervaizer/admin/templates/cases_table.html,sha256=VyL5mEF003FTNHym1UYBD8JkvhA9wR328ciTKNKxdb8,6619
29
30
  supervaizer/admin/templates/console.html,sha256=tLGGf8vHjGK77Il6SYlgparoU_xz3nbvNpGVQRkVNCc,13966
30
31
  supervaizer/admin/templates/dashboard.html,sha256=3Pu5bR78QUcPNp5MyXkyibfp-wxYdJyyf77v3O_f_hU,7873
32
+ supervaizer/admin/templates/index.html,sha256=3MFAQ53gptvUgBBfzQoCLOFLAd7JYOV5Ak4NEPsU1f4,2500
31
33
  supervaizer/admin/templates/job_detail.html,sha256=LDTMWLURyVCp7SMTxQ4M8AFqNpbbUVUx253negp9JNA,10790
32
34
  supervaizer/admin/templates/job_start_test.html,sha256=eAogAit0JfuMU4N5YR7N03w0nD_Bi9mtiX8uJ7dHLPg,4270
33
35
  supervaizer/admin/templates/jobs_list.html,sha256=VJ2VYe62dHXvjQQgUAVyKcn58rO5919UuP3VKgxLVhM,9136
34
36
  supervaizer/admin/templates/jobs_table.html,sha256=BCOI_7QlxJ5XOra7WKou48a2lNwQYASCMFTtgzHNotw,5974
35
37
  supervaizer/admin/templates/navigation.html,sha256=Ci_CMLqBuIKRt3JCFtn9Vjz1AAkqC75WMT6IQUiByFI,11724
36
38
  supervaizer/admin/templates/recent_activity.html,sha256=hL06GXF1a-C_tkj3pRLrDTTDqG0KplcWMZFengFMuEc,4843
37
- supervaizer/admin/templates/server.html,sha256=m3qIQsEojogXQKTSD6ljKj4_lrwaLJTpTbMpfnWZHxU,5650
39
+ supervaizer/admin/templates/server.html,sha256=CScvNTNdc0Wi2entmzZHSnqrBjqIOZUsbQrWD58pBYM,8527
38
40
  supervaizer/admin/templates/server_status_cards.html,sha256=yJ36hkfgQpscYkiaodFDQPnmJWeb2W7gey09Z3T6JsY,7882
39
41
  supervaizer/admin/templates/supervaize_instructions.html,sha256=LTLla1xgIeLpFf7bond_lxH5qdQQ2ak52Fd7hqChi1I,10225
40
42
  supervaizer/deploy/__init__.py,sha256=DvngGQu8tS_Yz5FU4kKCvPpod11IGCtZWkUNeB5aVHI,557
@@ -61,8 +63,8 @@ supervaizer/deploy/templates/debug_env.py,sha256=WFlxfiCAlkxM1NybNvtmmG5zJnoSvjW
61
63
  supervaizer/deploy/templates/docker-compose.yml.template,sha256=ZcW8WyhmqMElaxBpokuZG12n0tFJL8BY7k7Tvdz6tdw,1100
62
64
  supervaizer/deploy/templates/dockerignore.template,sha256=bYFRn6QGsjtRDH-Y7vzWk3-u3jIp90FajV2Ed94ah5M,515
63
65
  supervaizer/deploy/templates/entrypoint.sh,sha256=z079VUotu6DJX92fJiBB3eVwCEgcP6B9D9Fvwv_FL9w,463
64
- supervaizer/deploy/templates/index.html,sha256=JRYAp5IiMQ6khBL8lI8NtJ-jN4SdOpiLIZWTEbFcy_o,1912
65
- supervaizer/examples/controller_template.py,sha256=gAmA3GnDTdYkDk3NpVe-6Adbsl4Oxs-iZKN6mrO3WEg,6419
66
+ supervaizer/deploy/templates/index.html,sha256=z2ocEOYbWbl1Rz3Toc5JZkN7OAoW_GFV1yKKhdyC3vw,2160
67
+ supervaizer/examples/controller_template.py,sha256=bQRtZdq9Z8Pg0b3bRy4oVy6YIIM_e5DzhXVPOr1gFvk,6337
66
68
  supervaizer/protocol/__init__.py,sha256=00GHbUsLNsf0a1rQrUPpVN2Uy-7rDz72Ps6TUVD91tE,389
67
69
  supervaizer/protocol/a2a/__init__.py,sha256=1ACfPGLzS6XdZPiFxn1nVamvbasqtJJ7U1BBbSmT-nI,625
68
70
  supervaizer/protocol/a2a/model.py,sha256=sWzatlA_fcva_fdtM8Yh4cioRq0KbwV5AXpNCnsOtQo,7198
@@ -70,8 +72,8 @@ supervaizer/protocol/a2a/routes.py,sha256=rkQTNBD1NTYimKCb8iOk4bVf9ldDP1LqHfOsyh
70
72
  supervaizer/utils/__init__.py,sha256=fd0NFwN_cen3QPms2SOnuz4jcetay3f_31dit2As7EA,458
71
73
  supervaizer/utils/version_check.py,sha256=-tsOURpHVh0LNTbpQsyJDJENKszC-NzXDSO_EToEQPE,1893
72
74
  supervaizer/py.typed,sha256=bHhvLx7c6MqrzXVPbdK3qAOcSxzp4wDtTx4QifMC2EY,74
73
- supervaizer-0.10.6.dist-info/METADATA,sha256=a7BdnNaBtFs3POG-bEhEAPEd04D2IZsRC8w5C8ViPfA,12647
74
- supervaizer-0.10.6.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
75
- supervaizer-0.10.6.dist-info/entry_points.txt,sha256=vL_IBR_AeEI2u2-6YL4PY9k2Mar4-gprG8-UxERWjmg,52
76
- supervaizer-0.10.6.dist-info/licenses/LICENSE.md,sha256=dmdnt1vfpxNPr8Lt0BnxKE5uzUwK3CWTthTUStgOXjY,15327
77
- supervaizer-0.10.6.dist-info/RECORD,,
75
+ supervaizer-0.10.15.dist-info/METADATA,sha256=-c3Opx2tY7d8zsKm14IRFJFE3ELjquXaPEFGYrGHhtQ,12648
76
+ supervaizer-0.10.15.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
77
+ supervaizer-0.10.15.dist-info/entry_points.txt,sha256=vL_IBR_AeEI2u2-6YL4PY9k2Mar4-gprG8-UxERWjmg,52
78
+ supervaizer-0.10.15.dist-info/licenses/LICENSE.md,sha256=dmdnt1vfpxNPr8Lt0BnxKE5uzUwK3CWTthTUStgOXjY,15327
79
+ supervaizer-0.10.15.dist-info/RECORD,,