empathy-framework 5.0.3__py3-none-any.whl → 5.1.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.
Files changed (59) hide show
  1. {empathy_framework-5.0.3.dist-info → empathy_framework-5.1.1.dist-info}/METADATA +259 -142
  2. {empathy_framework-5.0.3.dist-info → empathy_framework-5.1.1.dist-info}/RECORD +58 -28
  3. empathy_framework-5.1.1.dist-info/licenses/LICENSE +201 -0
  4. empathy_framework-5.1.1.dist-info/licenses/LICENSE_CHANGE_ANNOUNCEMENT.md +101 -0
  5. empathy_os/__init__.py +1 -1
  6. empathy_os/cli/commands/batch.py +5 -5
  7. empathy_os/cli/commands/routing.py +1 -1
  8. empathy_os/cli/commands/workflow.py +2 -1
  9. empathy_os/cli/parsers/cache 2.py +65 -0
  10. empathy_os/cli_minimal.py +3 -3
  11. empathy_os/cli_router 2.py +416 -0
  12. empathy_os/cli_router.py +12 -0
  13. empathy_os/dashboard/__init__.py +1 -2
  14. empathy_os/dashboard/app 2.py +512 -0
  15. empathy_os/dashboard/app.py +1 -1
  16. empathy_os/dashboard/simple_server 2.py +403 -0
  17. empathy_os/dashboard/standalone_server 2.py +536 -0
  18. empathy_os/memory/types 2.py +441 -0
  19. empathy_os/meta_workflows/intent_detector.py +71 -0
  20. empathy_os/models/__init__.py +19 -0
  21. empathy_os/models/adaptive_routing 2.py +437 -0
  22. empathy_os/models/auth_cli.py +444 -0
  23. empathy_os/models/auth_strategy.py +450 -0
  24. empathy_os/project_index/scanner_parallel 2.py +291 -0
  25. empathy_os/telemetry/agent_coordination 2.py +478 -0
  26. empathy_os/telemetry/agent_coordination.py +3 -3
  27. empathy_os/telemetry/agent_tracking 2.py +350 -0
  28. empathy_os/telemetry/agent_tracking.py +1 -2
  29. empathy_os/telemetry/approval_gates 2.py +563 -0
  30. empathy_os/telemetry/event_streaming 2.py +405 -0
  31. empathy_os/telemetry/event_streaming.py +3 -3
  32. empathy_os/telemetry/feedback_loop 2.py +557 -0
  33. empathy_os/telemetry/feedback_loop.py +1 -1
  34. empathy_os/vscode_bridge 2.py +173 -0
  35. empathy_os/workflows/__init__.py +8 -0
  36. empathy_os/workflows/autonomous_test_gen.py +569 -0
  37. empathy_os/workflows/bug_predict.py +45 -0
  38. empathy_os/workflows/code_review.py +92 -22
  39. empathy_os/workflows/document_gen.py +594 -62
  40. empathy_os/workflows/llm_base.py +363 -0
  41. empathy_os/workflows/perf_audit.py +69 -0
  42. empathy_os/workflows/progressive/README 2.md +454 -0
  43. empathy_os/workflows/progressive/__init__ 2.py +92 -0
  44. empathy_os/workflows/progressive/cli 2.py +242 -0
  45. empathy_os/workflows/progressive/core 2.py +488 -0
  46. empathy_os/workflows/progressive/orchestrator 2.py +701 -0
  47. empathy_os/workflows/progressive/reports 2.py +528 -0
  48. empathy_os/workflows/progressive/telemetry 2.py +280 -0
  49. empathy_os/workflows/progressive/test_gen 2.py +514 -0
  50. empathy_os/workflows/progressive/workflow 2.py +628 -0
  51. empathy_os/workflows/release_prep.py +54 -0
  52. empathy_os/workflows/security_audit.py +154 -79
  53. empathy_os/workflows/test_gen.py +60 -0
  54. empathy_os/workflows/test_gen_behavioral.py +477 -0
  55. empathy_os/workflows/test_gen_parallel.py +341 -0
  56. empathy_framework-5.0.3.dist-info/licenses/LICENSE +0 -139
  57. {empathy_framework-5.0.3.dist-info → empathy_framework-5.1.1.dist-info}/WHEEL +0 -0
  58. {empathy_framework-5.0.3.dist-info → empathy_framework-5.1.1.dist-info}/entry_points.txt +0 -0
  59. {empathy_framework-5.0.3.dist-info → empathy_framework-5.1.1.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()