htmlgraph 0.20.1__py3-none-any.whl ā 0.27.5__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.
- htmlgraph/.htmlgraph/.session-warning-state.json +6 -0
- htmlgraph/.htmlgraph/agents.json +72 -0
- htmlgraph/.htmlgraph/htmlgraph.db +0 -0
- htmlgraph/__init__.py +51 -1
- htmlgraph/__init__.pyi +123 -0
- htmlgraph/agent_detection.py +26 -10
- htmlgraph/agent_registry.py +2 -1
- htmlgraph/analytics/__init__.py +8 -1
- htmlgraph/analytics/cli.py +86 -20
- htmlgraph/analytics/cost_analyzer.py +391 -0
- htmlgraph/analytics/cost_monitor.py +664 -0
- htmlgraph/analytics/cost_reporter.py +675 -0
- htmlgraph/analytics/cross_session.py +617 -0
- htmlgraph/analytics/dependency.py +10 -6
- htmlgraph/analytics/pattern_learning.py +771 -0
- htmlgraph/analytics/session_graph.py +707 -0
- htmlgraph/analytics/strategic/__init__.py +80 -0
- htmlgraph/analytics/strategic/cost_optimizer.py +611 -0
- htmlgraph/analytics/strategic/pattern_detector.py +876 -0
- htmlgraph/analytics/strategic/preference_manager.py +709 -0
- htmlgraph/analytics/strategic/suggestion_engine.py +747 -0
- htmlgraph/analytics/work_type.py +67 -27
- htmlgraph/analytics_index.py +53 -20
- htmlgraph/api/__init__.py +3 -0
- htmlgraph/api/cost_alerts_websocket.py +416 -0
- htmlgraph/api/main.py +2498 -0
- htmlgraph/api/static/htmx.min.js +1 -0
- htmlgraph/api/static/style-redesign.css +1344 -0
- htmlgraph/api/static/style.css +1079 -0
- htmlgraph/api/templates/dashboard-redesign.html +1366 -0
- htmlgraph/api/templates/dashboard.html +794 -0
- htmlgraph/api/templates/partials/activity-feed-hierarchical.html +326 -0
- htmlgraph/api/templates/partials/activity-feed.html +1100 -0
- htmlgraph/api/templates/partials/agents-redesign.html +317 -0
- htmlgraph/api/templates/partials/agents.html +317 -0
- htmlgraph/api/templates/partials/event-traces.html +373 -0
- htmlgraph/api/templates/partials/features-kanban-redesign.html +509 -0
- htmlgraph/api/templates/partials/features.html +578 -0
- htmlgraph/api/templates/partials/metrics-redesign.html +346 -0
- htmlgraph/api/templates/partials/metrics.html +346 -0
- htmlgraph/api/templates/partials/orchestration-redesign.html +443 -0
- htmlgraph/api/templates/partials/orchestration.html +198 -0
- htmlgraph/api/templates/partials/spawners.html +375 -0
- htmlgraph/api/templates/partials/work-items.html +613 -0
- htmlgraph/api/websocket.py +538 -0
- htmlgraph/archive/__init__.py +24 -0
- htmlgraph/archive/bloom.py +234 -0
- htmlgraph/archive/fts.py +297 -0
- htmlgraph/archive/manager.py +583 -0
- htmlgraph/archive/search.py +244 -0
- htmlgraph/atomic_ops.py +560 -0
- htmlgraph/attribute_index.py +2 -1
- htmlgraph/bounded_paths.py +539 -0
- htmlgraph/builders/base.py +57 -2
- htmlgraph/builders/bug.py +19 -3
- htmlgraph/builders/chore.py +19 -3
- htmlgraph/builders/epic.py +19 -3
- htmlgraph/builders/feature.py +27 -3
- htmlgraph/builders/insight.py +2 -1
- htmlgraph/builders/metric.py +2 -1
- htmlgraph/builders/pattern.py +2 -1
- htmlgraph/builders/phase.py +19 -3
- htmlgraph/builders/spike.py +29 -3
- htmlgraph/builders/track.py +42 -1
- htmlgraph/cigs/__init__.py +81 -0
- htmlgraph/cigs/autonomy.py +385 -0
- htmlgraph/cigs/cost.py +475 -0
- htmlgraph/cigs/messages_basic.py +472 -0
- htmlgraph/cigs/messaging.py +365 -0
- htmlgraph/cigs/models.py +771 -0
- htmlgraph/cigs/pattern_storage.py +427 -0
- htmlgraph/cigs/patterns.py +503 -0
- htmlgraph/cigs/posttool_analyzer.py +234 -0
- htmlgraph/cigs/reporter.py +818 -0
- htmlgraph/cigs/tracker.py +317 -0
- htmlgraph/cli/.htmlgraph/.session-warning-state.json +6 -0
- htmlgraph/cli/.htmlgraph/agents.json +72 -0
- htmlgraph/cli/.htmlgraph/htmlgraph.db +0 -0
- htmlgraph/cli/__init__.py +42 -0
- htmlgraph/cli/__main__.py +6 -0
- htmlgraph/cli/analytics.py +1424 -0
- htmlgraph/cli/base.py +685 -0
- htmlgraph/cli/constants.py +206 -0
- htmlgraph/cli/core.py +954 -0
- htmlgraph/cli/main.py +147 -0
- htmlgraph/cli/models.py +475 -0
- htmlgraph/cli/templates/__init__.py +1 -0
- htmlgraph/cli/templates/cost_dashboard.py +399 -0
- htmlgraph/cli/work/__init__.py +239 -0
- htmlgraph/cli/work/browse.py +115 -0
- htmlgraph/cli/work/features.py +568 -0
- htmlgraph/cli/work/orchestration.py +676 -0
- htmlgraph/cli/work/report.py +728 -0
- htmlgraph/cli/work/sessions.py +466 -0
- htmlgraph/cli/work/snapshot.py +559 -0
- htmlgraph/cli/work/tracks.py +486 -0
- htmlgraph/cli_commands/__init__.py +1 -0
- htmlgraph/cli_commands/feature.py +195 -0
- htmlgraph/cli_framework.py +115 -0
- htmlgraph/collections/__init__.py +2 -0
- htmlgraph/collections/base.py +197 -14
- htmlgraph/collections/bug.py +2 -1
- htmlgraph/collections/chore.py +2 -1
- htmlgraph/collections/epic.py +2 -1
- htmlgraph/collections/feature.py +2 -1
- htmlgraph/collections/insight.py +2 -1
- htmlgraph/collections/metric.py +2 -1
- htmlgraph/collections/pattern.py +2 -1
- htmlgraph/collections/phase.py +2 -1
- htmlgraph/collections/session.py +194 -0
- htmlgraph/collections/spike.py +13 -2
- htmlgraph/collections/task_delegation.py +241 -0
- htmlgraph/collections/todo.py +14 -1
- htmlgraph/collections/traces.py +487 -0
- htmlgraph/config/cost_models.json +56 -0
- htmlgraph/config.py +190 -0
- htmlgraph/context_analytics.py +2 -1
- htmlgraph/converter.py +116 -7
- htmlgraph/cost_analysis/__init__.py +5 -0
- htmlgraph/cost_analysis/analyzer.py +438 -0
- htmlgraph/dashboard.html +2246 -248
- htmlgraph/dashboard.html.backup +6592 -0
- htmlgraph/dashboard.html.bak +7181 -0
- htmlgraph/dashboard.html.bak2 +7231 -0
- htmlgraph/dashboard.html.bak3 +7232 -0
- htmlgraph/db/__init__.py +38 -0
- htmlgraph/db/queries.py +790 -0
- htmlgraph/db/schema.py +1788 -0
- htmlgraph/decorators.py +317 -0
- htmlgraph/dependency_models.py +2 -1
- htmlgraph/deploy.py +26 -27
- htmlgraph/docs/API_REFERENCE.md +841 -0
- htmlgraph/docs/HTTP_API.md +750 -0
- htmlgraph/docs/INTEGRATION_GUIDE.md +752 -0
- htmlgraph/docs/ORCHESTRATION_PATTERNS.md +717 -0
- htmlgraph/docs/README.md +532 -0
- htmlgraph/docs/__init__.py +77 -0
- htmlgraph/docs/docs_version.py +55 -0
- htmlgraph/docs/metadata.py +93 -0
- htmlgraph/docs/migrations.py +232 -0
- htmlgraph/docs/template_engine.py +143 -0
- htmlgraph/docs/templates/_sections/cli_reference.md.j2 +52 -0
- htmlgraph/docs/templates/_sections/core_concepts.md.j2 +29 -0
- htmlgraph/docs/templates/_sections/sdk_basics.md.j2 +69 -0
- htmlgraph/docs/templates/base_agents.md.j2 +78 -0
- htmlgraph/docs/templates/example_user_override.md.j2 +47 -0
- htmlgraph/docs/version_check.py +163 -0
- htmlgraph/edge_index.py +2 -1
- htmlgraph/error_handler.py +544 -0
- htmlgraph/event_log.py +86 -37
- htmlgraph/event_migration.py +2 -1
- htmlgraph/file_watcher.py +12 -8
- htmlgraph/find_api.py +2 -1
- htmlgraph/git_events.py +67 -9
- htmlgraph/hooks/.htmlgraph/.session-warning-state.json +6 -0
- htmlgraph/hooks/.htmlgraph/agents.json +72 -0
- htmlgraph/hooks/.htmlgraph/index.sqlite +0 -0
- htmlgraph/hooks/__init__.py +8 -0
- htmlgraph/hooks/bootstrap.py +169 -0
- htmlgraph/hooks/cigs_pretool_enforcer.py +354 -0
- htmlgraph/hooks/concurrent_sessions.py +208 -0
- htmlgraph/hooks/context.py +350 -0
- htmlgraph/hooks/drift_handler.py +525 -0
- htmlgraph/hooks/event_tracker.py +790 -99
- htmlgraph/hooks/git_commands.py +175 -0
- htmlgraph/hooks/installer.py +5 -1
- htmlgraph/hooks/orchestrator.py +327 -76
- htmlgraph/hooks/orchestrator_reflector.py +31 -4
- htmlgraph/hooks/post_tool_use_failure.py +32 -7
- htmlgraph/hooks/post_tool_use_handler.py +257 -0
- htmlgraph/hooks/posttooluse.py +92 -19
- htmlgraph/hooks/pretooluse.py +527 -7
- htmlgraph/hooks/prompt_analyzer.py +637 -0
- htmlgraph/hooks/session_handler.py +668 -0
- htmlgraph/hooks/session_summary.py +395 -0
- htmlgraph/hooks/state_manager.py +504 -0
- htmlgraph/hooks/subagent_detection.py +202 -0
- htmlgraph/hooks/subagent_stop.py +369 -0
- htmlgraph/hooks/task_enforcer.py +99 -4
- htmlgraph/hooks/validator.py +212 -91
- htmlgraph/ids.py +2 -1
- htmlgraph/learning.py +125 -100
- htmlgraph/mcp_server.py +2 -1
- htmlgraph/models.py +217 -18
- htmlgraph/operations/README.md +62 -0
- htmlgraph/operations/__init__.py +79 -0
- htmlgraph/operations/analytics.py +339 -0
- htmlgraph/operations/bootstrap.py +289 -0
- htmlgraph/operations/events.py +244 -0
- htmlgraph/operations/fastapi_server.py +231 -0
- htmlgraph/operations/hooks.py +350 -0
- htmlgraph/operations/initialization.py +597 -0
- htmlgraph/operations/initialization.py.backup +228 -0
- htmlgraph/operations/server.py +303 -0
- htmlgraph/orchestration/__init__.py +58 -0
- htmlgraph/orchestration/claude_launcher.py +179 -0
- htmlgraph/orchestration/command_builder.py +72 -0
- htmlgraph/orchestration/headless_spawner.py +281 -0
- htmlgraph/orchestration/live_events.py +377 -0
- htmlgraph/orchestration/model_selection.py +327 -0
- htmlgraph/orchestration/plugin_manager.py +140 -0
- htmlgraph/orchestration/prompts.py +137 -0
- htmlgraph/orchestration/spawner_event_tracker.py +383 -0
- htmlgraph/orchestration/spawners/__init__.py +16 -0
- htmlgraph/orchestration/spawners/base.py +194 -0
- htmlgraph/orchestration/spawners/claude.py +173 -0
- htmlgraph/orchestration/spawners/codex.py +435 -0
- htmlgraph/orchestration/spawners/copilot.py +294 -0
- htmlgraph/orchestration/spawners/gemini.py +471 -0
- htmlgraph/orchestration/subprocess_runner.py +36 -0
- htmlgraph/{orchestration.py ā orchestration/task_coordination.py} +16 -8
- htmlgraph/orchestration.md +563 -0
- htmlgraph/orchestrator-system-prompt-optimized.txt +863 -0
- htmlgraph/orchestrator.py +2 -1
- htmlgraph/orchestrator_config.py +357 -0
- htmlgraph/orchestrator_mode.py +115 -4
- htmlgraph/parallel.py +2 -1
- htmlgraph/parser.py +86 -6
- htmlgraph/path_query.py +608 -0
- htmlgraph/pattern_matcher.py +636 -0
- htmlgraph/pydantic_models.py +476 -0
- htmlgraph/quality_gates.py +350 -0
- htmlgraph/query_builder.py +2 -1
- htmlgraph/query_composer.py +509 -0
- htmlgraph/reflection.py +443 -0
- htmlgraph/refs.py +344 -0
- htmlgraph/repo_hash.py +512 -0
- htmlgraph/repositories/__init__.py +292 -0
- htmlgraph/repositories/analytics_repository.py +455 -0
- htmlgraph/repositories/analytics_repository_standard.py +628 -0
- htmlgraph/repositories/feature_repository.py +581 -0
- htmlgraph/repositories/feature_repository_htmlfile.py +668 -0
- htmlgraph/repositories/feature_repository_memory.py +607 -0
- htmlgraph/repositories/feature_repository_sqlite.py +858 -0
- htmlgraph/repositories/filter_service.py +620 -0
- htmlgraph/repositories/filter_service_standard.py +445 -0
- htmlgraph/repositories/shared_cache.py +621 -0
- htmlgraph/repositories/shared_cache_memory.py +395 -0
- htmlgraph/repositories/track_repository.py +552 -0
- htmlgraph/repositories/track_repository_htmlfile.py +619 -0
- htmlgraph/repositories/track_repository_memory.py +508 -0
- htmlgraph/repositories/track_repository_sqlite.py +711 -0
- htmlgraph/sdk/__init__.py +398 -0
- htmlgraph/sdk/__init__.pyi +14 -0
- htmlgraph/sdk/analytics/__init__.py +19 -0
- htmlgraph/sdk/analytics/engine.py +155 -0
- htmlgraph/sdk/analytics/helpers.py +178 -0
- htmlgraph/sdk/analytics/registry.py +109 -0
- htmlgraph/sdk/base.py +484 -0
- htmlgraph/sdk/constants.py +216 -0
- htmlgraph/sdk/core.pyi +308 -0
- htmlgraph/sdk/discovery.py +120 -0
- htmlgraph/sdk/help/__init__.py +12 -0
- htmlgraph/sdk/help/mixin.py +699 -0
- htmlgraph/sdk/mixins/__init__.py +15 -0
- htmlgraph/sdk/mixins/attribution.py +113 -0
- htmlgraph/sdk/mixins/mixin.py +410 -0
- htmlgraph/sdk/operations/__init__.py +12 -0
- htmlgraph/sdk/operations/mixin.py +427 -0
- htmlgraph/sdk/orchestration/__init__.py +17 -0
- htmlgraph/sdk/orchestration/coordinator.py +203 -0
- htmlgraph/sdk/orchestration/spawner.py +204 -0
- htmlgraph/sdk/planning/__init__.py +19 -0
- htmlgraph/sdk/planning/bottlenecks.py +93 -0
- htmlgraph/sdk/planning/mixin.py +211 -0
- htmlgraph/sdk/planning/parallel.py +186 -0
- htmlgraph/sdk/planning/queue.py +210 -0
- htmlgraph/sdk/planning/recommendations.py +87 -0
- htmlgraph/sdk/planning/smart_planning.py +319 -0
- htmlgraph/sdk/session/__init__.py +19 -0
- htmlgraph/sdk/session/continuity.py +57 -0
- htmlgraph/sdk/session/handoff.py +110 -0
- htmlgraph/sdk/session/info.py +309 -0
- htmlgraph/sdk/session/manager.py +103 -0
- htmlgraph/sdk/strategic/__init__.py +26 -0
- htmlgraph/sdk/strategic/mixin.py +563 -0
- htmlgraph/server.py +295 -107
- htmlgraph/session_hooks.py +300 -0
- htmlgraph/session_manager.py +285 -3
- htmlgraph/session_registry.py +587 -0
- htmlgraph/session_state.py +436 -0
- htmlgraph/session_warning.py +2 -1
- htmlgraph/sessions/__init__.py +23 -0
- htmlgraph/sessions/handoff.py +756 -0
- htmlgraph/system_prompts.py +450 -0
- htmlgraph/templates/orchestration-view.html +350 -0
- htmlgraph/track_builder.py +33 -1
- htmlgraph/track_manager.py +38 -0
- htmlgraph/transcript.py +18 -5
- htmlgraph/validation.py +115 -0
- htmlgraph/watch.py +2 -1
- htmlgraph/work_type_utils.py +2 -1
- {htmlgraph-0.20.1.data ā htmlgraph-0.27.5.data}/data/htmlgraph/dashboard.html +2246 -248
- {htmlgraph-0.20.1.dist-info ā htmlgraph-0.27.5.dist-info}/METADATA +95 -64
- htmlgraph-0.27.5.dist-info/RECORD +337 -0
- {htmlgraph-0.20.1.dist-info ā htmlgraph-0.27.5.dist-info}/entry_points.txt +1 -1
- htmlgraph/cli.py +0 -4839
- htmlgraph/sdk.py +0 -2359
- htmlgraph-0.20.1.dist-info/RECORD +0 -118
- {htmlgraph-0.20.1.data ā htmlgraph-0.27.5.data}/data/htmlgraph/styles.css +0 -0
- {htmlgraph-0.20.1.data ā htmlgraph-0.27.5.data}/data/htmlgraph/templates/AGENTS.md.template +0 -0
- {htmlgraph-0.20.1.data ā htmlgraph-0.27.5.data}/data/htmlgraph/templates/CLAUDE.md.template +0 -0
- {htmlgraph-0.20.1.data ā htmlgraph-0.27.5.data}/data/htmlgraph/templates/GEMINI.md.template +0 -0
- {htmlgraph-0.20.1.dist-info ā htmlgraph-0.27.5.dist-info}/WHEEL +0 -0
htmlgraph/server.py
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
logger = logging.getLogger(__name__)
|
|
4
|
+
|
|
1
5
|
"""
|
|
2
6
|
HtmlGraph REST API Server.
|
|
3
7
|
|
|
@@ -17,14 +21,13 @@ import socket
|
|
|
17
21
|
import sys
|
|
18
22
|
import urllib.parse
|
|
19
23
|
from datetime import datetime, timezone
|
|
20
|
-
from http.server import
|
|
24
|
+
from http.server import SimpleHTTPRequestHandler
|
|
21
25
|
from pathlib import Path
|
|
22
26
|
from typing import Any, Literal, cast
|
|
23
27
|
|
|
24
28
|
from htmlgraph.analytics_index import AnalyticsIndex
|
|
25
29
|
from htmlgraph.converter import dict_to_node, node_to_dict
|
|
26
30
|
from htmlgraph.event_log import JsonlEventLog
|
|
27
|
-
from htmlgraph.file_watcher import GraphWatcher
|
|
28
31
|
from htmlgraph.graph import HtmlGraph
|
|
29
32
|
from htmlgraph.ids import generate_id
|
|
30
33
|
from htmlgraph.models import Node
|
|
@@ -49,6 +52,7 @@ class HtmlGraphAPIHandler(SimpleHTTPRequestHandler):
|
|
|
49
52
|
"sessions",
|
|
50
53
|
"agents",
|
|
51
54
|
"tracks",
|
|
55
|
+
"task-delegations",
|
|
52
56
|
]
|
|
53
57
|
|
|
54
58
|
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
@@ -210,7 +214,15 @@ class HtmlGraphAPIHandler(SimpleHTTPRequestHandler):
|
|
|
210
214
|
return "api", collection, node_id, query_params
|
|
211
215
|
|
|
212
216
|
def _serve_packaged_dashboard(self) -> bool:
|
|
213
|
-
"""
|
|
217
|
+
"""
|
|
218
|
+
DEPRECATED: Serve the bundled dashboard HTML if available.
|
|
219
|
+
|
|
220
|
+
NOTE: This server is LEGACY. The active server is FastAPI-based.
|
|
221
|
+
See: src/python/htmlgraph/operations/fastapi_server.py
|
|
222
|
+
|
|
223
|
+
The dashboard.html file was archived to .archived-templates/
|
|
224
|
+
Active templates are in src/python/htmlgraph/api/templates/
|
|
225
|
+
"""
|
|
214
226
|
dashboard_path = Path(__file__).parent / "dashboard.html"
|
|
215
227
|
if not dashboard_path.exists():
|
|
216
228
|
return False
|
|
@@ -235,6 +247,9 @@ class HtmlGraphAPIHandler(SimpleHTTPRequestHandler):
|
|
|
235
247
|
def do_GET(self) -> None:
|
|
236
248
|
"""Handle GET requests."""
|
|
237
249
|
api, collection, node_id, params = self._parse_path()
|
|
250
|
+
logger.debug(
|
|
251
|
+
f"do_GET: api={api}, collection={collection}, node_id={node_id}, params={params}"
|
|
252
|
+
)
|
|
238
253
|
|
|
239
254
|
# Not an API request - serve static files
|
|
240
255
|
if api != "api":
|
|
@@ -260,6 +275,15 @@ class HtmlGraphAPIHandler(SimpleHTTPRequestHandler):
|
|
|
260
275
|
if collection == "analytics":
|
|
261
276
|
return self._handle_analytics(node_id, params)
|
|
262
277
|
|
|
278
|
+
# GET /api/orchestration - Get delegation chains and agent coordination
|
|
279
|
+
if collection == "orchestration":
|
|
280
|
+
logger.info(f"DEBUG: Handling orchestration request, params={params}")
|
|
281
|
+
return self._handle_orchestration_view(params)
|
|
282
|
+
|
|
283
|
+
# GET /api/task-delegations/stats - Get aggregated delegation statistics
|
|
284
|
+
if collection == "task-delegations" and params.get("stats") == "true":
|
|
285
|
+
return self._handle_task_delegations_stats()
|
|
286
|
+
|
|
263
287
|
# GET /api/tracks/{track_id}/features - Get features for a track
|
|
264
288
|
if collection == "tracks" and node_id and params.get("features") == "true":
|
|
265
289
|
return self._handle_track_features(node_id)
|
|
@@ -1043,6 +1067,187 @@ class HtmlGraphAPIHandler(SimpleHTTPRequestHandler):
|
|
|
1043
1067
|
except Exception as e:
|
|
1044
1068
|
self._send_error_json(f"Failed to generate features: {str(e)}", 500)
|
|
1045
1069
|
|
|
1070
|
+
def _handle_orchestration_view(self, params: dict) -> None:
|
|
1071
|
+
"""
|
|
1072
|
+
Get delegation chains and agent coordination information.
|
|
1073
|
+
|
|
1074
|
+
Queries the SQLite database for delegation events and builds
|
|
1075
|
+
a view of agent coordination and handoff patterns.
|
|
1076
|
+
|
|
1077
|
+
Returns:
|
|
1078
|
+
{
|
|
1079
|
+
"delegation_count": int,
|
|
1080
|
+
"unique_agents": int,
|
|
1081
|
+
"agents": [str],
|
|
1082
|
+
"delegation_chains": {
|
|
1083
|
+
"from_agent": [
|
|
1084
|
+
{
|
|
1085
|
+
"to_agent": str,
|
|
1086
|
+
"event_type": str,
|
|
1087
|
+
"timestamp": str,
|
|
1088
|
+
"task": str,
|
|
1089
|
+
"status": str
|
|
1090
|
+
}
|
|
1091
|
+
]
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
"""
|
|
1095
|
+
try:
|
|
1096
|
+
from htmlgraph.db.schema import HtmlGraphDB
|
|
1097
|
+
|
|
1098
|
+
# Use unified index.sqlite database
|
|
1099
|
+
db_path = str(self.graph_dir / "index.sqlite")
|
|
1100
|
+
db = HtmlGraphDB(db_path=db_path)
|
|
1101
|
+
db.connect()
|
|
1102
|
+
|
|
1103
|
+
# Get all delegation events
|
|
1104
|
+
delegations = db.get_delegations(limit=1000)
|
|
1105
|
+
db.close()
|
|
1106
|
+
|
|
1107
|
+
# Build delegation chains grouped by from_agent
|
|
1108
|
+
delegation_chains: dict[str, list[dict]] = {}
|
|
1109
|
+
agents = set()
|
|
1110
|
+
delegation_count = 0
|
|
1111
|
+
|
|
1112
|
+
for delegation in delegations:
|
|
1113
|
+
from_agent = delegation.get("from_agent", "unknown")
|
|
1114
|
+
to_agent = delegation.get("to_agent", "unknown")
|
|
1115
|
+
timestamp = delegation.get("timestamp", "")
|
|
1116
|
+
reason = delegation.get("reason", "")
|
|
1117
|
+
status = delegation.get("status", "pending")
|
|
1118
|
+
|
|
1119
|
+
agents.add(from_agent)
|
|
1120
|
+
agents.add(to_agent)
|
|
1121
|
+
delegation_count += 1
|
|
1122
|
+
|
|
1123
|
+
if from_agent not in delegation_chains:
|
|
1124
|
+
delegation_chains[from_agent] = []
|
|
1125
|
+
|
|
1126
|
+
delegation_chains[from_agent].append(
|
|
1127
|
+
{
|
|
1128
|
+
"to_agent": to_agent,
|
|
1129
|
+
"event_type": "delegation",
|
|
1130
|
+
"timestamp": timestamp,
|
|
1131
|
+
"task": reason or "Unnamed task",
|
|
1132
|
+
"status": status,
|
|
1133
|
+
}
|
|
1134
|
+
)
|
|
1135
|
+
|
|
1136
|
+
self._send_json(
|
|
1137
|
+
{
|
|
1138
|
+
"delegation_count": delegation_count,
|
|
1139
|
+
"unique_agents": len(agents),
|
|
1140
|
+
"agents": sorted(list(agents)),
|
|
1141
|
+
"delegation_chains": delegation_chains,
|
|
1142
|
+
}
|
|
1143
|
+
)
|
|
1144
|
+
|
|
1145
|
+
except Exception as e:
|
|
1146
|
+
self._send_error_json(f"Failed to get orchestration view: {str(e)}", 500)
|
|
1147
|
+
|
|
1148
|
+
def _handle_task_delegations_stats(self) -> None:
|
|
1149
|
+
"""Get aggregated statistics about task delegations."""
|
|
1150
|
+
try:
|
|
1151
|
+
delegations_graph = self._get_graph("task-delegations")
|
|
1152
|
+
|
|
1153
|
+
# Get all delegations
|
|
1154
|
+
all_delegations = list(delegations_graph)
|
|
1155
|
+
|
|
1156
|
+
if not all_delegations:
|
|
1157
|
+
self._send_json(
|
|
1158
|
+
{
|
|
1159
|
+
"total_delegations": 0,
|
|
1160
|
+
"by_agent_type": {},
|
|
1161
|
+
"by_status": {},
|
|
1162
|
+
"total_tokens": 0,
|
|
1163
|
+
"total_cost": 0.0,
|
|
1164
|
+
"average_duration": 0.0,
|
|
1165
|
+
"agent_stats": [],
|
|
1166
|
+
}
|
|
1167
|
+
)
|
|
1168
|
+
return
|
|
1169
|
+
|
|
1170
|
+
# Aggregate by agent type
|
|
1171
|
+
agent_stats: dict = {}
|
|
1172
|
+
by_status: dict[str, int] = {}
|
|
1173
|
+
total_tokens = 0
|
|
1174
|
+
total_cost = 0.0
|
|
1175
|
+
durations = []
|
|
1176
|
+
|
|
1177
|
+
for delegation in all_delegations:
|
|
1178
|
+
agent_type = str(getattr(delegation, "agent_type", "unknown"))
|
|
1179
|
+
status = str(getattr(delegation, "status", "unknown"))
|
|
1180
|
+
tokens_val = getattr(delegation, "tokens_used", 0)
|
|
1181
|
+
tokens = int(tokens_val) if tokens_val else 0
|
|
1182
|
+
cost_val = getattr(delegation, "cost_usd", 0)
|
|
1183
|
+
cost = float(cost_val) if cost_val else 0.0
|
|
1184
|
+
duration_val = getattr(delegation, "duration_seconds", 0)
|
|
1185
|
+
duration = int(duration_val) if duration_val else 0
|
|
1186
|
+
|
|
1187
|
+
# Track by agent
|
|
1188
|
+
if agent_type not in agent_stats:
|
|
1189
|
+
agent_stats[agent_type] = {
|
|
1190
|
+
"agent_type": agent_type,
|
|
1191
|
+
"tasks_completed": 0,
|
|
1192
|
+
"total_duration": 0,
|
|
1193
|
+
"total_tokens": 0,
|
|
1194
|
+
"total_cost": 0.0,
|
|
1195
|
+
"success_count": 0,
|
|
1196
|
+
"failure_count": 0,
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
agent_stats[agent_type]["tasks_completed"] += 1
|
|
1200
|
+
agent_stats[agent_type]["total_duration"] += duration
|
|
1201
|
+
agent_stats[agent_type]["total_tokens"] += tokens
|
|
1202
|
+
agent_stats[agent_type]["total_cost"] += cost
|
|
1203
|
+
|
|
1204
|
+
if status == "success":
|
|
1205
|
+
agent_stats[agent_type]["success_count"] += 1
|
|
1206
|
+
else:
|
|
1207
|
+
agent_stats[agent_type]["failure_count"] += 1
|
|
1208
|
+
|
|
1209
|
+
# Track by status
|
|
1210
|
+
by_status[status] = by_status.get(status, 0) + 1
|
|
1211
|
+
|
|
1212
|
+
# Aggregate totals
|
|
1213
|
+
total_tokens += tokens
|
|
1214
|
+
total_cost += cost
|
|
1215
|
+
if duration:
|
|
1216
|
+
durations.append(duration)
|
|
1217
|
+
|
|
1218
|
+
# Calculate success rate for each agent
|
|
1219
|
+
for agent_stats_item in agent_stats.values():
|
|
1220
|
+
total = agent_stats_item["tasks_completed"]
|
|
1221
|
+
if total > 0:
|
|
1222
|
+
agent_stats_item["success_rate"] = (
|
|
1223
|
+
agent_stats_item["success_count"] / total
|
|
1224
|
+
)
|
|
1225
|
+
else:
|
|
1226
|
+
agent_stats_item["success_rate"] = 0.0
|
|
1227
|
+
|
|
1228
|
+
average_duration = sum(durations) / len(durations) if durations else 0.0
|
|
1229
|
+
|
|
1230
|
+
self._send_json(
|
|
1231
|
+
{
|
|
1232
|
+
"total_delegations": len(all_delegations),
|
|
1233
|
+
"by_agent_type": {
|
|
1234
|
+
agent: stats["tasks_completed"]
|
|
1235
|
+
for agent, stats in agent_stats.items()
|
|
1236
|
+
},
|
|
1237
|
+
"by_status": by_status,
|
|
1238
|
+
"total_tokens": total_tokens,
|
|
1239
|
+
"total_cost": round(total_cost, 4),
|
|
1240
|
+
"average_duration": round(average_duration, 2),
|
|
1241
|
+
"agent_stats": sorted(
|
|
1242
|
+
agent_stats.values(),
|
|
1243
|
+
key=lambda x: x["total_cost"],
|
|
1244
|
+
reverse=True,
|
|
1245
|
+
),
|
|
1246
|
+
}
|
|
1247
|
+
)
|
|
1248
|
+
except Exception as e:
|
|
1249
|
+
self._send_error_json(f"Failed to get delegation stats: {str(e)}", 500)
|
|
1250
|
+
|
|
1046
1251
|
def _handle_sync_track(self, track_id: str) -> None:
|
|
1047
1252
|
"""Sync task and spec completion based on features."""
|
|
1048
1253
|
from htmlgraph.track_manager import TrackManager
|
|
@@ -1074,7 +1279,7 @@ class HtmlGraphAPIHandler(SimpleHTTPRequestHandler):
|
|
|
1074
1279
|
|
|
1075
1280
|
def log_message(self, format: str, *args: str) -> None:
|
|
1076
1281
|
"""Custom log format."""
|
|
1077
|
-
|
|
1282
|
+
logger.info(f"[{datetime.now().strftime('%H:%M:%S')}] {args[0]}")
|
|
1078
1283
|
|
|
1079
1284
|
|
|
1080
1285
|
def find_available_port(start_port: int = 8080, max_attempts: int = 10) -> int:
|
|
@@ -1174,42 +1379,39 @@ def serve(
|
|
|
1174
1379
|
host: str = "localhost",
|
|
1175
1380
|
watch: bool = True,
|
|
1176
1381
|
auto_port: bool = False,
|
|
1382
|
+
show_progress: bool = False,
|
|
1383
|
+
quiet: bool = False,
|
|
1177
1384
|
) -> None:
|
|
1178
1385
|
"""
|
|
1179
|
-
Start the HtmlGraph server.
|
|
1180
|
-
|
|
1386
|
+
Start the HtmlGraph server (FastAPI-based with WebSocket support).
|
|
1387
|
+
|
|
1388
|
+
This function launches the FastAPI server which provides:
|
|
1389
|
+
- REST API for CRUD operations on graph nodes
|
|
1390
|
+
- WebSocket endpoint at /ws/events for real-time event streaming
|
|
1391
|
+
- HTMX-powered dashboard for agent observability
|
|
1181
1392
|
|
|
1182
1393
|
Args:
|
|
1183
|
-
port: Port to listen on
|
|
1394
|
+
port: Port to listen on (default: 8080)
|
|
1184
1395
|
graph_dir: Directory containing graph data (.htmlgraph/)
|
|
1185
|
-
static_dir: Directory for static files (index.html, etc.)
|
|
1186
|
-
host: Host to bind to
|
|
1187
|
-
watch: Enable file watching for auto-reload (default: True)
|
|
1396
|
+
static_dir: Directory for static files (index.html, etc.) - preserved for compatibility
|
|
1397
|
+
host: Host to bind to (default: localhost)
|
|
1398
|
+
watch: Enable file watching for auto-reload (default: True) - maps to reload in FastAPI
|
|
1188
1399
|
auto_port: Automatically find available port if specified port is in use
|
|
1400
|
+
show_progress: Show Rich progress during startup (not used with FastAPI)
|
|
1401
|
+
quiet: Suppress progress output when true
|
|
1189
1402
|
"""
|
|
1190
|
-
|
|
1191
|
-
static_dir = Path(static_dir)
|
|
1403
|
+
import asyncio
|
|
1192
1404
|
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1405
|
+
from htmlgraph.operations.fastapi_server import (
|
|
1406
|
+
FastAPIServerError,
|
|
1407
|
+
PortInUseError,
|
|
1408
|
+
run_fastapi_server,
|
|
1409
|
+
start_fastapi_server,
|
|
1410
|
+
)
|
|
1198
1411
|
|
|
1199
|
-
|
|
1200
|
-
try:
|
|
1201
|
-
if sync_dashboard_files(static_dir):
|
|
1202
|
-
print("ā ļø Dashboard files out of sync")
|
|
1203
|
-
print("ā
Synced dashboard.html ā index.html\n")
|
|
1204
|
-
except PermissionError as e:
|
|
1205
|
-
print(f"ā ļø Warning: Unable to sync dashboard files: {e}")
|
|
1206
|
-
print(
|
|
1207
|
-
f" Run: cp src/python/htmlgraph/dashboard.html {static_dir / 'index.html'}\n"
|
|
1208
|
-
)
|
|
1209
|
-
except Exception as e:
|
|
1210
|
-
print(f"ā ļø Warning: Error during dashboard sync: {e}\n")
|
|
1412
|
+
graph_dir = Path(graph_dir)
|
|
1211
1413
|
|
|
1212
|
-
#
|
|
1414
|
+
# Ensure graph directory exists
|
|
1213
1415
|
graph_dir.mkdir(parents=True, exist_ok=True)
|
|
1214
1416
|
for collection in HtmlGraphAPIHandler.COLLECTIONS:
|
|
1215
1417
|
(graph_dir / collection).mkdir(exist_ok=True)
|
|
@@ -1221,99 +1423,85 @@ def serve(
|
|
|
1221
1423
|
if styles_src.exists():
|
|
1222
1424
|
styles_dest.write_text(styles_src.read_text())
|
|
1223
1425
|
|
|
1224
|
-
#
|
|
1225
|
-
|
|
1226
|
-
HtmlGraphAPIHandler.static_dir = static_dir
|
|
1227
|
-
HtmlGraphAPIHandler.graphs = {}
|
|
1228
|
-
HtmlGraphAPIHandler.analytics_db = None
|
|
1426
|
+
# Database path - use htmlgraph.db in the graph directory
|
|
1427
|
+
db_path = str(graph_dir / "htmlgraph.db")
|
|
1229
1428
|
|
|
1230
|
-
# Create server with error handling
|
|
1231
1429
|
try:
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
print(" 1. Use a different port:")
|
|
1239
|
-
print(f" htmlgraph serve --port {port + 1}\n")
|
|
1240
|
-
print(" 2. Let htmlgraph automatically find an available port:")
|
|
1241
|
-
print(" htmlgraph serve --auto-port\n")
|
|
1242
|
-
print(f" 3. Find and kill the process using port {port}:")
|
|
1243
|
-
print(f" lsof -ti:{port} | xargs kill -9\n")
|
|
1244
|
-
|
|
1245
|
-
# Try to find and suggest an available port
|
|
1246
|
-
try:
|
|
1247
|
-
alt_port = find_available_port(port + 1)
|
|
1248
|
-
print(f"š” Found available port: {alt_port}")
|
|
1249
|
-
print(f" Run: htmlgraph serve --port {alt_port}\n")
|
|
1250
|
-
except OSError:
|
|
1251
|
-
pass
|
|
1252
|
-
|
|
1253
|
-
sys.exit(1)
|
|
1254
|
-
# Re-raise other OSErrors
|
|
1255
|
-
raise
|
|
1256
|
-
|
|
1257
|
-
# Start file watcher if enabled
|
|
1258
|
-
watcher = None
|
|
1259
|
-
if watch:
|
|
1260
|
-
|
|
1261
|
-
def get_graph(collection: str) -> HtmlGraph:
|
|
1262
|
-
"""Callback to get graph instance for a collection."""
|
|
1263
|
-
handler = HtmlGraphAPIHandler
|
|
1264
|
-
if collection not in handler.graphs:
|
|
1265
|
-
collection_dir = handler.graph_dir / collection
|
|
1266
|
-
handler.graphs[collection] = HtmlGraph(
|
|
1267
|
-
collection_dir, stylesheet_path="../styles.css", auto_load=True
|
|
1268
|
-
)
|
|
1269
|
-
return handler.graphs[collection]
|
|
1270
|
-
|
|
1271
|
-
watcher = GraphWatcher(
|
|
1272
|
-
graph_dir=graph_dir,
|
|
1273
|
-
collections=HtmlGraphAPIHandler.COLLECTIONS,
|
|
1274
|
-
get_graph_callback=get_graph,
|
|
1430
|
+
result = start_fastapi_server(
|
|
1431
|
+
port=port,
|
|
1432
|
+
host=host,
|
|
1433
|
+
db_path=db_path,
|
|
1434
|
+
auto_port=auto_port,
|
|
1435
|
+
reload=watch, # Map watch to reload for FastAPI
|
|
1275
1436
|
)
|
|
1276
|
-
watcher.start()
|
|
1277
1437
|
|
|
1278
|
-
|
|
1279
|
-
|
|
1438
|
+
# Print warnings if any
|
|
1439
|
+
for warning in result.warnings:
|
|
1440
|
+
if not quiet:
|
|
1441
|
+
logger.info(f"ā ļø {warning}")
|
|
1442
|
+
|
|
1443
|
+
# Print server info
|
|
1444
|
+
if not quiet:
|
|
1445
|
+
actual_port = result.config_used["port"]
|
|
1446
|
+
print(f"""
|
|
1280
1447
|
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
1281
|
-
ā
|
|
1448
|
+
ā HtmlGraph Server (FastAPI) ā
|
|
1282
1449
|
ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā£
|
|
1283
|
-
ā Dashboard:
|
|
1284
|
-
ā API:
|
|
1285
|
-
ā
|
|
1286
|
-
ā
|
|
1450
|
+
ā Dashboard: http://{host}:{actual_port}/
|
|
1451
|
+
ā API: http://{host}:{actual_port}/api/
|
|
1452
|
+
ā WebSocket: ws://{host}:{actual_port}/ws/events
|
|
1453
|
+
ā Graph Dir: {graph_dir}
|
|
1454
|
+
ā Database: {db_path}
|
|
1455
|
+
ā Auto-reload: {"Enabled" if watch else "Disabled"}
|
|
1287
1456
|
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
1288
1457
|
|
|
1458
|
+
Features:
|
|
1459
|
+
⢠Real-time agent activity feed (HTMX + WebSocket)
|
|
1460
|
+
⢠Orchestration chains visualization
|
|
1461
|
+
⢠Feature tracker with Kanban view
|
|
1462
|
+
⢠Session metrics & performance analytics
|
|
1463
|
+
|
|
1289
1464
|
API Endpoints:
|
|
1290
|
-
GET /api/
|
|
1291
|
-
GET /api/
|
|
1292
|
-
GET /api/
|
|
1293
|
-
GET /api/
|
|
1294
|
-
|
|
1295
|
-
GET /api/analytics/continuity?feature_id=... - Feature continuity (requires index)
|
|
1296
|
-
GET /api/analytics/transitions - Tool transitions (requires index)
|
|
1297
|
-
|
|
1298
|
-
GET /api/{{collection}} - List nodes
|
|
1299
|
-
POST /api/{{collection}} - Create node
|
|
1300
|
-
GET /api/{{collection}}/{{id}} - Get node
|
|
1301
|
-
PUT /api/{{collection}}/{{id}} - Replace node
|
|
1302
|
-
PATCH /api/{{collection}}/{{id}} - Update node
|
|
1303
|
-
DELETE /api/{{collection}}/{{id}} - Delete node
|
|
1465
|
+
GET /api/events - List events
|
|
1466
|
+
GET /api/sessions - List sessions
|
|
1467
|
+
GET /api/orchestration - Orchestration data
|
|
1468
|
+
GET /api/initial-stats - Dashboard statistics
|
|
1469
|
+
WS /ws/events - Real-time event stream
|
|
1304
1470
|
|
|
1305
1471
|
Collections: {", ".join(HtmlGraphAPIHandler.COLLECTIONS)}
|
|
1306
1472
|
|
|
1307
1473
|
Press Ctrl+C to stop.
|
|
1308
1474
|
""")
|
|
1309
1475
|
|
|
1310
|
-
|
|
1311
|
-
|
|
1476
|
+
# Run the server
|
|
1477
|
+
asyncio.run(run_fastapi_server(result.handle))
|
|
1478
|
+
|
|
1479
|
+
except PortInUseError:
|
|
1480
|
+
logger.info(f"\nā Port {port} is already in use\n")
|
|
1481
|
+
logger.info("Solutions:")
|
|
1482
|
+
logger.info(" 1. Use a different port:")
|
|
1483
|
+
logger.info(f" htmlgraph serve --port {port + 1}\n")
|
|
1484
|
+
logger.info(" 2. Let htmlgraph automatically find an available port:")
|
|
1485
|
+
logger.info(" htmlgraph serve --auto-port\n")
|
|
1486
|
+
logger.info(f" 3. Find and kill the process using port {port}:")
|
|
1487
|
+
logger.info(f" lsof -ti:{port} | xargs kill -9\n")
|
|
1488
|
+
|
|
1489
|
+
# Try to find and suggest an available port
|
|
1490
|
+
try:
|
|
1491
|
+
alt_port = find_available_port(port + 1)
|
|
1492
|
+
logger.info(f"š” Found available port: {alt_port}")
|
|
1493
|
+
logger.info(f" Run: htmlgraph serve --port {alt_port}\n")
|
|
1494
|
+
except OSError:
|
|
1495
|
+
pass
|
|
1496
|
+
|
|
1497
|
+
sys.exit(1)
|
|
1498
|
+
|
|
1499
|
+
except FastAPIServerError as e:
|
|
1500
|
+
logger.info(f"\nā Server error: {e}\n")
|
|
1501
|
+
sys.exit(1)
|
|
1502
|
+
|
|
1312
1503
|
except KeyboardInterrupt:
|
|
1313
|
-
|
|
1314
|
-
if watcher:
|
|
1315
|
-
watcher.stop()
|
|
1316
|
-
server.shutdown()
|
|
1504
|
+
logger.info("\nShutting down...")
|
|
1317
1505
|
|
|
1318
1506
|
|
|
1319
1507
|
if __name__ == "__main__":
|