empathy-framework 5.0.1__py3-none-any.whl → 5.1.0__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.
- {empathy_framework-5.0.1.dist-info → empathy_framework-5.1.0.dist-info}/METADATA +311 -150
- {empathy_framework-5.0.1.dist-info → empathy_framework-5.1.0.dist-info}/RECORD +60 -33
- empathy_framework-5.1.0.dist-info/licenses/LICENSE +201 -0
- empathy_framework-5.1.0.dist-info/licenses/LICENSE_CHANGE_ANNOUNCEMENT.md +101 -0
- empathy_llm_toolkit/providers.py +175 -35
- empathy_llm_toolkit/utils/tokens.py +150 -30
- empathy_os/__init__.py +1 -1
- empathy_os/cli/commands/batch.py +256 -0
- empathy_os/cli/commands/cache.py +248 -0
- empathy_os/cli/commands/inspect.py +1 -2
- empathy_os/cli/commands/metrics.py +1 -1
- empathy_os/cli/commands/routing.py +285 -0
- empathy_os/cli/commands/workflow.py +2 -1
- empathy_os/cli/parsers/__init__.py +6 -0
- empathy_os/cli/parsers/batch.py +118 -0
- empathy_os/cli/parsers/cache 2.py +65 -0
- empathy_os/cli/parsers/cache.py +65 -0
- empathy_os/cli/parsers/routing.py +110 -0
- empathy_os/cli_minimal.py +3 -3
- empathy_os/cli_router 2.py +416 -0
- empathy_os/dashboard/__init__.py +1 -2
- empathy_os/dashboard/app 2.py +512 -0
- empathy_os/dashboard/app.py +1 -1
- empathy_os/dashboard/simple_server 2.py +403 -0
- empathy_os/dashboard/standalone_server 2.py +536 -0
- empathy_os/dashboard/standalone_server.py +22 -11
- empathy_os/memory/types 2.py +441 -0
- empathy_os/metrics/collector.py +31 -0
- empathy_os/models/__init__.py +19 -0
- empathy_os/models/adaptive_routing 2.py +437 -0
- empathy_os/models/auth_cli.py +444 -0
- empathy_os/models/auth_strategy.py +450 -0
- empathy_os/models/token_estimator.py +21 -13
- empathy_os/project_index/scanner_parallel 2.py +291 -0
- empathy_os/telemetry/agent_coordination 2.py +478 -0
- empathy_os/telemetry/agent_coordination.py +14 -16
- empathy_os/telemetry/agent_tracking 2.py +350 -0
- empathy_os/telemetry/agent_tracking.py +18 -20
- empathy_os/telemetry/approval_gates 2.py +563 -0
- empathy_os/telemetry/approval_gates.py +27 -39
- empathy_os/telemetry/event_streaming 2.py +405 -0
- empathy_os/telemetry/event_streaming.py +22 -22
- empathy_os/telemetry/feedback_loop 2.py +557 -0
- empathy_os/telemetry/feedback_loop.py +14 -17
- empathy_os/workflows/__init__.py +8 -0
- empathy_os/workflows/autonomous_test_gen.py +569 -0
- empathy_os/workflows/batch_processing.py +56 -10
- empathy_os/workflows/bug_predict.py +45 -0
- empathy_os/workflows/code_review.py +92 -22
- empathy_os/workflows/document_gen.py +594 -62
- empathy_os/workflows/llm_base.py +363 -0
- empathy_os/workflows/perf_audit.py +69 -0
- empathy_os/workflows/release_prep.py +54 -0
- empathy_os/workflows/security_audit.py +154 -79
- empathy_os/workflows/test_gen.py +60 -0
- empathy_os/workflows/test_gen_behavioral.py +477 -0
- empathy_os/workflows/test_gen_parallel.py +341 -0
- empathy_framework-5.0.1.dist-info/licenses/LICENSE +0 -139
- {empathy_framework-5.0.1.dist-info → empathy_framework-5.1.0.dist-info}/WHEEL +0 -0
- {empathy_framework-5.0.1.dist-info → empathy_framework-5.1.0.dist-info}/entry_points.txt +0 -0
- {empathy_framework-5.0.1.dist-info → empathy_framework-5.1.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
"""Simple Dashboard Server - Zero External Dependencies.
|
|
2
|
+
|
|
3
|
+
Uses only Python standard library (http.server, json) to serve the dashboard.
|
|
4
|
+
No FastAPI, Flask, or other web frameworks required.
|
|
5
|
+
|
|
6
|
+
Copyright 2025 Smart-AI-Memory
|
|
7
|
+
Licensed under Fair Source License 0.9
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
import logging
|
|
14
|
+
from datetime import datetime
|
|
15
|
+
from http.server import BaseHTTPRequestHandler, HTTPServer
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from urllib.parse import parse_qs, urlparse
|
|
18
|
+
|
|
19
|
+
from empathy_os.telemetry import (
|
|
20
|
+
ApprovalGate,
|
|
21
|
+
CoordinationSignals,
|
|
22
|
+
EventStreamer,
|
|
23
|
+
FeedbackLoop,
|
|
24
|
+
HeartbeatCoordinator,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
logger = logging.getLogger(__name__)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class DashboardHandler(BaseHTTPRequestHandler):
|
|
31
|
+
"""HTTP request handler for dashboard."""
|
|
32
|
+
|
|
33
|
+
def do_GET(self):
|
|
34
|
+
"""Handle GET requests."""
|
|
35
|
+
parsed = urlparse(self.path)
|
|
36
|
+
path = parsed.path
|
|
37
|
+
query = parse_qs(parsed.query)
|
|
38
|
+
|
|
39
|
+
# Route requests
|
|
40
|
+
if path == "/" or path == "/index.html":
|
|
41
|
+
self.serve_file("index.html", "text/html")
|
|
42
|
+
elif path == "/static/style.css":
|
|
43
|
+
self.serve_file("style.css", "text/css")
|
|
44
|
+
elif path == "/static/app.js":
|
|
45
|
+
self.serve_file("app.js", "application/javascript")
|
|
46
|
+
elif path == "/api/health":
|
|
47
|
+
self.api_health()
|
|
48
|
+
elif path == "/api/agents":
|
|
49
|
+
self.api_agents()
|
|
50
|
+
elif path.startswith("/api/agents/"):
|
|
51
|
+
agent_id = path.split("/")[-1]
|
|
52
|
+
self.api_agent_detail(agent_id)
|
|
53
|
+
elif path == "/api/signals":
|
|
54
|
+
limit = int(query.get("limit", [50])[0])
|
|
55
|
+
self.api_signals(limit)
|
|
56
|
+
elif path == "/api/events":
|
|
57
|
+
event_type = query.get("event_type", [None])[0]
|
|
58
|
+
limit = int(query.get("limit", [100])[0])
|
|
59
|
+
self.api_events(event_type, limit)
|
|
60
|
+
elif path == "/api/approvals":
|
|
61
|
+
self.api_approvals()
|
|
62
|
+
elif path == "/api/feedback/workflows":
|
|
63
|
+
self.api_feedback_workflows()
|
|
64
|
+
elif path == "/api/feedback/underperforming":
|
|
65
|
+
threshold = float(query.get("threshold", [0.7])[0])
|
|
66
|
+
self.api_underperforming(threshold)
|
|
67
|
+
else:
|
|
68
|
+
self.send_error(404, "Not Found")
|
|
69
|
+
|
|
70
|
+
def do_POST(self):
|
|
71
|
+
"""Handle POST requests."""
|
|
72
|
+
parsed = urlparse(self.path)
|
|
73
|
+
path = parsed.path
|
|
74
|
+
|
|
75
|
+
# Get request body
|
|
76
|
+
content_length = int(self.headers.get("Content-Length", 0))
|
|
77
|
+
body = self.rfile.read(content_length) if content_length > 0 else b"{}"
|
|
78
|
+
data = json.loads(body.decode("utf-8")) if body else {}
|
|
79
|
+
|
|
80
|
+
# Route requests
|
|
81
|
+
if "/approve" in path:
|
|
82
|
+
request_id = path.split("/")[-2]
|
|
83
|
+
self.api_approve(request_id, data.get("reason", "Approved via dashboard"))
|
|
84
|
+
elif "/reject" in path:
|
|
85
|
+
request_id = path.split("/")[-2]
|
|
86
|
+
self.api_reject(request_id, data.get("reason", "Rejected via dashboard"))
|
|
87
|
+
else:
|
|
88
|
+
self.send_error(404, "Not Found")
|
|
89
|
+
|
|
90
|
+
def serve_file(self, filename: str, content_type: str):
|
|
91
|
+
"""Serve static file."""
|
|
92
|
+
try:
|
|
93
|
+
static_dir = Path(__file__).parent / "static"
|
|
94
|
+
file_path = static_dir / filename
|
|
95
|
+
|
|
96
|
+
if not file_path.exists():
|
|
97
|
+
self.send_error(404, f"File not found: {filename}")
|
|
98
|
+
return
|
|
99
|
+
|
|
100
|
+
content = file_path.read_bytes()
|
|
101
|
+
|
|
102
|
+
self.send_response(200)
|
|
103
|
+
self.send_header("Content-Type", content_type)
|
|
104
|
+
self.send_header("Content-Length", str(len(content)))
|
|
105
|
+
self.end_headers()
|
|
106
|
+
self.wfile.write(content)
|
|
107
|
+
|
|
108
|
+
except Exception as e:
|
|
109
|
+
logger.error(f"Failed to serve file {filename}: {e}")
|
|
110
|
+
self.send_error(500, str(e))
|
|
111
|
+
|
|
112
|
+
def send_json(self, data: dict | list, status: int = 200):
|
|
113
|
+
"""Send JSON response."""
|
|
114
|
+
try:
|
|
115
|
+
content = json.dumps(data).encode("utf-8")
|
|
116
|
+
|
|
117
|
+
self.send_response(status)
|
|
118
|
+
self.send_header("Content-Type", "application/json")
|
|
119
|
+
self.send_header("Content-Length", str(len(content)))
|
|
120
|
+
self.send_header("Access-Control-Allow-Origin", "*") # CORS
|
|
121
|
+
self.end_headers()
|
|
122
|
+
self.wfile.write(content)
|
|
123
|
+
|
|
124
|
+
except Exception as e:
|
|
125
|
+
logger.error(f"Failed to send JSON: {e}")
|
|
126
|
+
self.send_error(500, str(e))
|
|
127
|
+
|
|
128
|
+
# ========================================================================
|
|
129
|
+
# API Endpoints
|
|
130
|
+
# ========================================================================
|
|
131
|
+
|
|
132
|
+
def api_health(self):
|
|
133
|
+
"""System health endpoint."""
|
|
134
|
+
try:
|
|
135
|
+
coordinator = HeartbeatCoordinator()
|
|
136
|
+
has_redis = coordinator.memory is not None
|
|
137
|
+
|
|
138
|
+
active_agents = len(coordinator.get_active_agents()) if has_redis else 0
|
|
139
|
+
|
|
140
|
+
gate = ApprovalGate()
|
|
141
|
+
pending_approvals = len(gate.get_pending_approvals()) if has_redis else 0
|
|
142
|
+
|
|
143
|
+
self.send_json(
|
|
144
|
+
{
|
|
145
|
+
"status": "healthy" if has_redis else "degraded",
|
|
146
|
+
"redis_available": has_redis,
|
|
147
|
+
"active_agents": active_agents,
|
|
148
|
+
"pending_approvals": pending_approvals,
|
|
149
|
+
"timestamp": datetime.utcnow().isoformat(),
|
|
150
|
+
}
|
|
151
|
+
)
|
|
152
|
+
except Exception as e:
|
|
153
|
+
self.send_json({"status": "error", "error": str(e)}, status=500)
|
|
154
|
+
|
|
155
|
+
def api_agents(self):
|
|
156
|
+
"""List active agents."""
|
|
157
|
+
try:
|
|
158
|
+
coordinator = HeartbeatCoordinator()
|
|
159
|
+
active_agents = coordinator.get_active_agents()
|
|
160
|
+
|
|
161
|
+
result = []
|
|
162
|
+
for agent_id in active_agents:
|
|
163
|
+
heartbeat = coordinator.get_heartbeat(agent_id)
|
|
164
|
+
if heartbeat:
|
|
165
|
+
result.append(
|
|
166
|
+
{
|
|
167
|
+
"agent_id": agent_id,
|
|
168
|
+
"status": heartbeat.status,
|
|
169
|
+
"last_seen": heartbeat.timestamp.isoformat(),
|
|
170
|
+
"progress": heartbeat.progress,
|
|
171
|
+
"current_task": heartbeat.current_task,
|
|
172
|
+
}
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
self.send_json(result)
|
|
176
|
+
except Exception as e:
|
|
177
|
+
logger.error(f"Failed to get agents: {e}")
|
|
178
|
+
self.send_json([], status=500)
|
|
179
|
+
|
|
180
|
+
def api_agent_detail(self, agent_id: str):
|
|
181
|
+
"""Get specific agent details."""
|
|
182
|
+
try:
|
|
183
|
+
coordinator = HeartbeatCoordinator()
|
|
184
|
+
heartbeat = coordinator.get_heartbeat(agent_id)
|
|
185
|
+
|
|
186
|
+
if not heartbeat:
|
|
187
|
+
self.send_json({"error": f"Agent {agent_id} not found"}, status=404)
|
|
188
|
+
return
|
|
189
|
+
|
|
190
|
+
self.send_json(
|
|
191
|
+
{
|
|
192
|
+
"agent_id": agent_id,
|
|
193
|
+
"status": heartbeat.status,
|
|
194
|
+
"last_seen": heartbeat.timestamp.isoformat(),
|
|
195
|
+
"progress": heartbeat.progress,
|
|
196
|
+
"current_task": heartbeat.current_task,
|
|
197
|
+
"metadata": heartbeat.metadata,
|
|
198
|
+
}
|
|
199
|
+
)
|
|
200
|
+
except Exception as e:
|
|
201
|
+
self.send_json({"error": str(e)}, status=500)
|
|
202
|
+
|
|
203
|
+
def api_signals(self, limit: int):
|
|
204
|
+
"""Get recent coordination signals."""
|
|
205
|
+
try:
|
|
206
|
+
signals = CoordinationSignals()
|
|
207
|
+
recent = signals.get_recent_signals(limit=limit)
|
|
208
|
+
|
|
209
|
+
result = [
|
|
210
|
+
{
|
|
211
|
+
"signal_type": sig.signal_type,
|
|
212
|
+
"source_agent": sig.source_agent,
|
|
213
|
+
"target_agent": sig.target_agent,
|
|
214
|
+
"timestamp": sig.timestamp.isoformat(),
|
|
215
|
+
"payload": sig.payload,
|
|
216
|
+
}
|
|
217
|
+
for sig in recent
|
|
218
|
+
]
|
|
219
|
+
|
|
220
|
+
self.send_json(result)
|
|
221
|
+
except Exception as e:
|
|
222
|
+
logger.error(f"Failed to get signals: {e}")
|
|
223
|
+
self.send_json([])
|
|
224
|
+
|
|
225
|
+
def api_events(self, event_type: str | None, limit: int):
|
|
226
|
+
"""Get recent events."""
|
|
227
|
+
try:
|
|
228
|
+
streamer = EventStreamer()
|
|
229
|
+
|
|
230
|
+
if event_type:
|
|
231
|
+
events = list(streamer.get_recent_events(event_type, limit=limit))
|
|
232
|
+
else:
|
|
233
|
+
# Get events from multiple streams
|
|
234
|
+
all_events = []
|
|
235
|
+
for evt_type in ["agent_heartbeat", "coordination_signal", "workflow_progress"]:
|
|
236
|
+
events = list(streamer.get_recent_events(evt_type, limit=20))
|
|
237
|
+
all_events.extend(events)
|
|
238
|
+
|
|
239
|
+
all_events.sort(key=lambda e: e.timestamp, reverse=True)
|
|
240
|
+
events = all_events[:limit]
|
|
241
|
+
|
|
242
|
+
result = [
|
|
243
|
+
{
|
|
244
|
+
"event_id": evt.event_id,
|
|
245
|
+
"event_type": evt.event_type,
|
|
246
|
+
"timestamp": evt.timestamp.isoformat(),
|
|
247
|
+
"data": evt.data,
|
|
248
|
+
"source": evt.source,
|
|
249
|
+
}
|
|
250
|
+
for evt in events
|
|
251
|
+
]
|
|
252
|
+
|
|
253
|
+
self.send_json(result)
|
|
254
|
+
except Exception as e:
|
|
255
|
+
logger.error(f"Failed to get events: {e}")
|
|
256
|
+
self.send_json([])
|
|
257
|
+
|
|
258
|
+
def api_approvals(self):
|
|
259
|
+
"""Get pending approvals."""
|
|
260
|
+
try:
|
|
261
|
+
gate = ApprovalGate()
|
|
262
|
+
pending = gate.get_pending_approvals()
|
|
263
|
+
|
|
264
|
+
result = [
|
|
265
|
+
{
|
|
266
|
+
"request_id": req.request_id,
|
|
267
|
+
"approval_type": req.approval_type,
|
|
268
|
+
"agent_id": req.agent_id,
|
|
269
|
+
"context": req.context,
|
|
270
|
+
"timestamp": req.timestamp.isoformat(),
|
|
271
|
+
"timeout_seconds": req.timeout_seconds,
|
|
272
|
+
}
|
|
273
|
+
for req in pending
|
|
274
|
+
]
|
|
275
|
+
|
|
276
|
+
self.send_json(result)
|
|
277
|
+
except Exception as e:
|
|
278
|
+
logger.error(f"Failed to get approvals: {e}")
|
|
279
|
+
self.send_json([])
|
|
280
|
+
|
|
281
|
+
def api_approve(self, request_id: str, reason: str):
|
|
282
|
+
"""Approve request."""
|
|
283
|
+
try:
|
|
284
|
+
gate = ApprovalGate()
|
|
285
|
+
success = gate.respond_to_approval(
|
|
286
|
+
request_id=request_id, approved=True, responder="dashboard", reason=reason
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
if success:
|
|
290
|
+
self.send_json({"status": "approved", "request_id": request_id})
|
|
291
|
+
else:
|
|
292
|
+
self.send_json({"error": "Failed to approve"}, status=500)
|
|
293
|
+
except Exception as e:
|
|
294
|
+
self.send_json({"error": str(e)}, status=500)
|
|
295
|
+
|
|
296
|
+
def api_reject(self, request_id: str, reason: str):
|
|
297
|
+
"""Reject request."""
|
|
298
|
+
try:
|
|
299
|
+
gate = ApprovalGate()
|
|
300
|
+
success = gate.respond_to_approval(
|
|
301
|
+
request_id=request_id, approved=False, responder="dashboard", reason=reason
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
if success:
|
|
305
|
+
self.send_json({"status": "rejected", "request_id": request_id})
|
|
306
|
+
else:
|
|
307
|
+
self.send_json({"error": "Failed to reject"}, status=500)
|
|
308
|
+
except Exception as e:
|
|
309
|
+
self.send_json({"error": str(e)}, status=500)
|
|
310
|
+
|
|
311
|
+
def api_feedback_workflows(self):
|
|
312
|
+
"""Get workflow quality metrics."""
|
|
313
|
+
try:
|
|
314
|
+
feedback = FeedbackLoop()
|
|
315
|
+
|
|
316
|
+
workflows = ["code-review", "test-generation", "refactoring"]
|
|
317
|
+
results = []
|
|
318
|
+
|
|
319
|
+
for workflow in workflows:
|
|
320
|
+
for stage in ["analysis", "generation", "validation"]:
|
|
321
|
+
for tier in ["cheap", "capable", "premium"]:
|
|
322
|
+
stats = feedback.get_quality_stats(workflow, stage, tier=tier)
|
|
323
|
+
if stats and stats.sample_count > 0:
|
|
324
|
+
results.append(
|
|
325
|
+
{
|
|
326
|
+
"workflow_name": workflow,
|
|
327
|
+
"stage_name": stage,
|
|
328
|
+
"tier": tier,
|
|
329
|
+
"avg_quality": stats.avg_quality,
|
|
330
|
+
"sample_count": stats.sample_count,
|
|
331
|
+
"trend": stats.recent_trend,
|
|
332
|
+
}
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
self.send_json(results)
|
|
336
|
+
except Exception as e:
|
|
337
|
+
logger.error(f"Failed to get quality metrics: {e}")
|
|
338
|
+
self.send_json([])
|
|
339
|
+
|
|
340
|
+
def api_underperforming(self, threshold: float):
|
|
341
|
+
"""Get underperforming stages."""
|
|
342
|
+
try:
|
|
343
|
+
feedback = FeedbackLoop()
|
|
344
|
+
|
|
345
|
+
workflows = ["code-review", "test-generation", "refactoring"]
|
|
346
|
+
all_underperforming = []
|
|
347
|
+
|
|
348
|
+
for workflow in workflows:
|
|
349
|
+
underperforming = feedback.get_underperforming_stages(workflow, quality_threshold=threshold)
|
|
350
|
+
for stage_name, stats in underperforming:
|
|
351
|
+
all_underperforming.append(
|
|
352
|
+
{
|
|
353
|
+
"workflow_name": workflow,
|
|
354
|
+
"stage_name": stage_name,
|
|
355
|
+
"avg_quality": stats.avg_quality,
|
|
356
|
+
"sample_count": stats.sample_count,
|
|
357
|
+
"min_quality": stats.min_quality,
|
|
358
|
+
"max_quality": stats.max_quality,
|
|
359
|
+
"trend": stats.recent_trend,
|
|
360
|
+
}
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
all_underperforming.sort(key=lambda x: x["avg_quality"])
|
|
364
|
+
self.send_json(all_underperforming)
|
|
365
|
+
except Exception as e:
|
|
366
|
+
logger.error(f"Failed to get underperforming: {e}")
|
|
367
|
+
self.send_json([])
|
|
368
|
+
|
|
369
|
+
def log_message(self, format, *args):
|
|
370
|
+
"""Suppress default logging."""
|
|
371
|
+
# Override to reduce noise - only log errors
|
|
372
|
+
if args[1][0] in ("4", "5"): # 4xx or 5xx errors
|
|
373
|
+
logger.warning(f"{self.address_string()} - {format % args}")
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def run_simple_dashboard(host: str = "127.0.0.1", port: int = 8000):
|
|
377
|
+
"""Run dashboard using only Python standard library.
|
|
378
|
+
|
|
379
|
+
No external dependencies required (no FastAPI, Flask, etc).
|
|
380
|
+
|
|
381
|
+
Args:
|
|
382
|
+
host: Host to bind to (default: 127.0.0.1)
|
|
383
|
+
port: Port to bind to (default: 8000)
|
|
384
|
+
|
|
385
|
+
Example:
|
|
386
|
+
>>> from empathy_os.dashboard.simple_server import run_simple_dashboard
|
|
387
|
+
>>> run_simple_dashboard(host="0.0.0.0", port=8080)
|
|
388
|
+
"""
|
|
389
|
+
server = HTTPServer((host, port), DashboardHandler)
|
|
390
|
+
|
|
391
|
+
print(f"🚀 Agent Coordination Dashboard running at http://{host}:{port}")
|
|
392
|
+
print(f"📊 Open in browser: http://{host}:{port}")
|
|
393
|
+
print("Press Ctrl+C to stop")
|
|
394
|
+
|
|
395
|
+
try:
|
|
396
|
+
server.serve_forever()
|
|
397
|
+
except KeyboardInterrupt:
|
|
398
|
+
print("\n\n🛑 Shutting down dashboard...")
|
|
399
|
+
server.shutdown()
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
if __name__ == "__main__":
|
|
403
|
+
run_simple_dashboard()
|