htmlgraph 0.26.25__py3-none-any.whl → 0.27.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.
- htmlgraph/__init__.py +23 -1
- htmlgraph/__init__.pyi +123 -0
- htmlgraph/agent_registry.py +2 -1
- htmlgraph/analytics/cli.py +3 -3
- htmlgraph/analytics/cost_analyzer.py +5 -1
- htmlgraph/analytics/cost_monitor.py +664 -0
- htmlgraph/analytics/cross_session.py +13 -9
- htmlgraph/analytics/dependency.py +10 -6
- 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 +15 -11
- htmlgraph/analytics_index.py +2 -1
- htmlgraph/api/cost_alerts_websocket.py +416 -0
- htmlgraph/api/main.py +167 -62
- htmlgraph/api/websocket.py +538 -0
- htmlgraph/attribute_index.py +2 -1
- htmlgraph/builders/base.py +2 -1
- htmlgraph/builders/bug.py +2 -1
- htmlgraph/builders/chore.py +2 -1
- htmlgraph/builders/epic.py +2 -1
- htmlgraph/builders/feature.py +2 -1
- htmlgraph/builders/insight.py +2 -1
- htmlgraph/builders/metric.py +2 -1
- htmlgraph/builders/pattern.py +2 -1
- htmlgraph/builders/phase.py +2 -1
- htmlgraph/builders/spike.py +2 -1
- htmlgraph/builders/track.py +2 -1
- htmlgraph/cli/analytics.py +2 -1
- htmlgraph/cli/base.py +2 -1
- htmlgraph/cli/core.py +2 -1
- htmlgraph/cli/main.py +2 -1
- htmlgraph/cli/models.py +2 -1
- htmlgraph/cli/templates/cost_dashboard.py +2 -1
- htmlgraph/cli/work/__init__.py +2 -1
- htmlgraph/cli/work/browse.py +2 -1
- htmlgraph/cli/work/features.py +2 -1
- htmlgraph/cli/work/orchestration.py +2 -1
- htmlgraph/cli/work/report.py +2 -1
- htmlgraph/cli/work/sessions.py +2 -1
- htmlgraph/cli/work/snapshot.py +2 -1
- htmlgraph/cli/work/tracks.py +2 -1
- htmlgraph/collections/base.py +10 -5
- 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 +12 -7
- htmlgraph/collections/spike.py +6 -1
- htmlgraph/collections/task_delegation.py +7 -2
- htmlgraph/collections/todo.py +2 -1
- htmlgraph/collections/traces.py +15 -10
- htmlgraph/config/cost_models.json +56 -0
- htmlgraph/context_analytics.py +2 -1
- htmlgraph/db/schema.py +67 -6
- htmlgraph/dependency_models.py +2 -1
- htmlgraph/edge_index.py +2 -1
- htmlgraph/event_log.py +83 -64
- htmlgraph/event_migration.py +2 -1
- htmlgraph/file_watcher.py +12 -8
- htmlgraph/find_api.py +2 -1
- htmlgraph/git_events.py +6 -2
- htmlgraph/hooks/cigs_pretool_enforcer.py +5 -1
- htmlgraph/hooks/drift_handler.py +3 -3
- htmlgraph/hooks/event_tracker.py +40 -61
- htmlgraph/hooks/installer.py +5 -1
- htmlgraph/hooks/orchestrator.py +4 -0
- htmlgraph/hooks/orchestrator_reflector.py +4 -0
- htmlgraph/hooks/post_tool_use_failure.py +7 -3
- htmlgraph/hooks/posttooluse.py +4 -0
- htmlgraph/hooks/prompt_analyzer.py +5 -5
- htmlgraph/hooks/session_handler.py +2 -1
- htmlgraph/hooks/session_summary.py +6 -2
- htmlgraph/hooks/validator.py +8 -4
- htmlgraph/ids.py +2 -1
- htmlgraph/learning.py +2 -1
- htmlgraph/mcp_server.py +2 -1
- htmlgraph/operations/analytics.py +2 -1
- htmlgraph/operations/bootstrap.py +2 -1
- htmlgraph/operations/events.py +2 -1
- htmlgraph/operations/fastapi_server.py +2 -1
- htmlgraph/operations/hooks.py +2 -1
- htmlgraph/operations/initialization.py +2 -1
- htmlgraph/operations/server.py +2 -1
- htmlgraph/orchestration/claude_launcher.py +23 -20
- htmlgraph/orchestration/command_builder.py +2 -1
- htmlgraph/orchestration/headless_spawner.py +6 -2
- htmlgraph/orchestration/model_selection.py +7 -3
- htmlgraph/orchestration/plugin_manager.py +24 -19
- htmlgraph/orchestration/spawners/claude.py +5 -2
- htmlgraph/orchestration/spawners/codex.py +12 -19
- htmlgraph/orchestration/spawners/copilot.py +13 -18
- htmlgraph/orchestration/spawners/gemini.py +12 -19
- htmlgraph/orchestration/subprocess_runner.py +6 -3
- htmlgraph/orchestration/task_coordination.py +16 -8
- htmlgraph/orchestrator.py +2 -1
- htmlgraph/parallel.py +2 -1
- htmlgraph/query_builder.py +2 -1
- htmlgraph/reflection.py +2 -1
- htmlgraph/refs.py +2 -1
- htmlgraph/repo_hash.py +2 -1
- 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 +21 -17
- htmlgraph/session_warning.py +2 -1
- htmlgraph/sessions/handoff.py +4 -3
- htmlgraph/system_prompts.py +2 -1
- htmlgraph/track_builder.py +2 -1
- htmlgraph/transcript.py +2 -1
- htmlgraph/watch.py +2 -1
- htmlgraph/work_type_utils.py +2 -1
- {htmlgraph-0.26.25.dist-info → htmlgraph-0.27.1.dist-info}/METADATA +1 -1
- htmlgraph-0.27.1.dist-info/RECORD +332 -0
- htmlgraph/sdk.py +0 -3500
- htmlgraph-0.26.25.dist-info/RECORD +0 -274
- {htmlgraph-0.26.25.data → htmlgraph-0.27.1.data}/data/htmlgraph/dashboard.html +0 -0
- {htmlgraph-0.26.25.data → htmlgraph-0.27.1.data}/data/htmlgraph/styles.css +0 -0
- {htmlgraph-0.26.25.data → htmlgraph-0.27.1.data}/data/htmlgraph/templates/AGENTS.md.template +0 -0
- {htmlgraph-0.26.25.data → htmlgraph-0.27.1.data}/data/htmlgraph/templates/CLAUDE.md.template +0 -0
- {htmlgraph-0.26.25.data → htmlgraph-0.27.1.data}/data/htmlgraph/templates/GEMINI.md.template +0 -0
- {htmlgraph-0.26.25.dist-info → htmlgraph-0.27.1.dist-info}/WHEEL +0 -0
- {htmlgraph-0.26.25.dist-info → htmlgraph-0.27.1.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,563 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Strategic Analytics Mixin for SDK
|
|
3
|
+
|
|
4
|
+
Provides SDK methods for accessing Phase 3 Strategic Analytics:
|
|
5
|
+
- Pattern detection (tool sequences, delegation chains, error patterns)
|
|
6
|
+
- Suggestion generation (next actions, delegations, model selection)
|
|
7
|
+
- Preference management (feedback, learning, personalization)
|
|
8
|
+
- Cost optimization (token budgets, parallelization, model selection)
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import logging
|
|
14
|
+
from dataclasses import dataclass
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import TYPE_CHECKING, Any
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from htmlgraph.sdk import SDK
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class StrategicAnalyticsInterface:
|
|
26
|
+
"""
|
|
27
|
+
Interface for strategic analytics operations.
|
|
28
|
+
|
|
29
|
+
Provides access to:
|
|
30
|
+
- PatternDetector for pattern detection
|
|
31
|
+
- SuggestionEngine for generating suggestions
|
|
32
|
+
- PreferenceManager for preference learning
|
|
33
|
+
- CostOptimizer for cost optimization
|
|
34
|
+
|
|
35
|
+
This interface is exposed as sdk.strategic on the SDK.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
_sdk: SDK
|
|
39
|
+
_db_path: Path | None = None
|
|
40
|
+
|
|
41
|
+
def __post_init__(self) -> None:
|
|
42
|
+
"""Initialize database path."""
|
|
43
|
+
from htmlgraph.config import get_database_path
|
|
44
|
+
|
|
45
|
+
self._db_path = get_database_path()
|
|
46
|
+
|
|
47
|
+
# ===== Pattern Detection =====
|
|
48
|
+
|
|
49
|
+
def detect_patterns(
|
|
50
|
+
self,
|
|
51
|
+
min_frequency: int = 3,
|
|
52
|
+
days_back: int = 30,
|
|
53
|
+
) -> list[dict[str, Any]]:
|
|
54
|
+
"""
|
|
55
|
+
Detect all patterns from event history.
|
|
56
|
+
|
|
57
|
+
Analyzes tool sequences, delegation chains, and error patterns
|
|
58
|
+
to identify successful workflows.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
min_frequency: Minimum occurrences to be considered a pattern
|
|
62
|
+
days_back: Number of days of history to analyze
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
List of pattern dictionaries sorted by confidence
|
|
66
|
+
|
|
67
|
+
Example:
|
|
68
|
+
>>> sdk = SDK(agent="claude")
|
|
69
|
+
>>> patterns = sdk.strategic.detect_patterns()
|
|
70
|
+
>>> for p in patterns[:5]:
|
|
71
|
+
... print(f"{p['pattern_type']}: {p['confidence']:.0%}")
|
|
72
|
+
"""
|
|
73
|
+
try:
|
|
74
|
+
from htmlgraph.analytics.strategic import PatternDetector
|
|
75
|
+
|
|
76
|
+
detector = PatternDetector(self._db_path)
|
|
77
|
+
patterns = detector.detect_all_patterns(
|
|
78
|
+
min_frequency=min_frequency,
|
|
79
|
+
days_back=days_back,
|
|
80
|
+
)
|
|
81
|
+
detector.close()
|
|
82
|
+
|
|
83
|
+
return [p.to_dict() for p in patterns]
|
|
84
|
+
except ImportError:
|
|
85
|
+
logger.warning("Strategic analytics not available")
|
|
86
|
+
return []
|
|
87
|
+
except Exception as e:
|
|
88
|
+
logger.error(f"Error detecting patterns: {e}")
|
|
89
|
+
return []
|
|
90
|
+
|
|
91
|
+
def detect_tool_sequences(
|
|
92
|
+
self,
|
|
93
|
+
window_size: int = 3,
|
|
94
|
+
min_frequency: int = 3,
|
|
95
|
+
) -> list[dict[str, Any]]:
|
|
96
|
+
"""
|
|
97
|
+
Detect common tool call sequence patterns.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
window_size: Number of consecutive tools in each sequence
|
|
101
|
+
min_frequency: Minimum occurrences to be considered a pattern
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
List of tool sequence patterns
|
|
105
|
+
|
|
106
|
+
Example:
|
|
107
|
+
>>> sequences = sdk.strategic.detect_tool_sequences()
|
|
108
|
+
>>> for seq in sequences[:3]:
|
|
109
|
+
... print(f"Sequence: {' -> '.join(seq['sequence'])}")
|
|
110
|
+
"""
|
|
111
|
+
try:
|
|
112
|
+
from htmlgraph.analytics.strategic import PatternDetector
|
|
113
|
+
|
|
114
|
+
detector = PatternDetector(self._db_path)
|
|
115
|
+
patterns = detector.detect_tool_sequences(
|
|
116
|
+
window_size=window_size,
|
|
117
|
+
min_frequency=min_frequency,
|
|
118
|
+
)
|
|
119
|
+
detector.close()
|
|
120
|
+
|
|
121
|
+
return [p.to_dict() for p in patterns]
|
|
122
|
+
except Exception as e:
|
|
123
|
+
logger.error(f"Error detecting tool sequences: {e}")
|
|
124
|
+
return []
|
|
125
|
+
|
|
126
|
+
def detect_delegation_chains(
|
|
127
|
+
self,
|
|
128
|
+
min_frequency: int = 2,
|
|
129
|
+
) -> list[dict[str, Any]]:
|
|
130
|
+
"""
|
|
131
|
+
Detect common delegation chain patterns.
|
|
132
|
+
|
|
133
|
+
Identifies which agent combinations work well together.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
min_frequency: Minimum occurrences to be considered a pattern
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
List of delegation chain patterns
|
|
140
|
+
|
|
141
|
+
Example:
|
|
142
|
+
>>> chains = sdk.strategic.detect_delegation_chains()
|
|
143
|
+
>>> for chain in chains:
|
|
144
|
+
... print(f"Chain: {' -> '.join(chain['agents'])}")
|
|
145
|
+
"""
|
|
146
|
+
try:
|
|
147
|
+
from htmlgraph.analytics.strategic import PatternDetector
|
|
148
|
+
|
|
149
|
+
detector = PatternDetector(self._db_path)
|
|
150
|
+
patterns = detector.detect_delegation_chains(min_frequency=min_frequency)
|
|
151
|
+
detector.close()
|
|
152
|
+
|
|
153
|
+
return [p.to_dict() for p in patterns]
|
|
154
|
+
except Exception as e:
|
|
155
|
+
logger.error(f"Error detecting delegation chains: {e}")
|
|
156
|
+
return []
|
|
157
|
+
|
|
158
|
+
# ===== Suggestion Engine =====
|
|
159
|
+
|
|
160
|
+
def get_suggestions(
|
|
161
|
+
self,
|
|
162
|
+
task_description: str = "",
|
|
163
|
+
max_suggestions: int = 5,
|
|
164
|
+
) -> list[dict[str, Any]]:
|
|
165
|
+
"""
|
|
166
|
+
Get suggestions based on current context and learned patterns.
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
task_description: Current task (optional, improves suggestions)
|
|
170
|
+
max_suggestions: Maximum number of suggestions
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
List of suggestion dictionaries sorted by score
|
|
174
|
+
|
|
175
|
+
Example:
|
|
176
|
+
>>> suggestions = sdk.strategic.get_suggestions(
|
|
177
|
+
... task_description="Implement user authentication"
|
|
178
|
+
... )
|
|
179
|
+
>>> for s in suggestions:
|
|
180
|
+
... print(f"{s['title']} ({s['confidence']:.0%})")
|
|
181
|
+
"""
|
|
182
|
+
try:
|
|
183
|
+
from htmlgraph.analytics.strategic import SuggestionEngine
|
|
184
|
+
from htmlgraph.analytics.strategic.suggestion_engine import TaskContext
|
|
185
|
+
|
|
186
|
+
engine = SuggestionEngine(self._db_path)
|
|
187
|
+
|
|
188
|
+
context = TaskContext(
|
|
189
|
+
current_task=task_description,
|
|
190
|
+
agent_type=self._sdk._agent_id or "orchestrator",
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
suggestions = engine.suggest(context, max_suggestions=max_suggestions)
|
|
194
|
+
engine.close()
|
|
195
|
+
|
|
196
|
+
return [s.to_dict() for s in suggestions]
|
|
197
|
+
except Exception as e:
|
|
198
|
+
logger.error(f"Error getting suggestions: {e}")
|
|
199
|
+
return []
|
|
200
|
+
|
|
201
|
+
def generate_task_code(self, pattern_id: str) -> str | None:
|
|
202
|
+
"""
|
|
203
|
+
Generate Task() code from a detected pattern.
|
|
204
|
+
|
|
205
|
+
Args:
|
|
206
|
+
pattern_id: ID of pattern to generate code from
|
|
207
|
+
|
|
208
|
+
Returns:
|
|
209
|
+
Python code snippet or None if pattern not found
|
|
210
|
+
|
|
211
|
+
Example:
|
|
212
|
+
>>> code = sdk.strategic.generate_task_code("tsp-abc123")
|
|
213
|
+
>>> print(code)
|
|
214
|
+
"""
|
|
215
|
+
try:
|
|
216
|
+
from htmlgraph.analytics.strategic import PatternDetector, SuggestionEngine
|
|
217
|
+
|
|
218
|
+
detector = PatternDetector(self._db_path)
|
|
219
|
+
engine = SuggestionEngine(self._db_path)
|
|
220
|
+
|
|
221
|
+
pattern = detector.get_pattern_by_id(pattern_id)
|
|
222
|
+
if pattern:
|
|
223
|
+
code = engine.generate_task_code(pattern)
|
|
224
|
+
detector.close()
|
|
225
|
+
engine.close()
|
|
226
|
+
return code
|
|
227
|
+
|
|
228
|
+
detector.close()
|
|
229
|
+
engine.close()
|
|
230
|
+
return None
|
|
231
|
+
except Exception as e:
|
|
232
|
+
logger.error(f"Error generating task code: {e}")
|
|
233
|
+
return None
|
|
234
|
+
|
|
235
|
+
# ===== Preference Management =====
|
|
236
|
+
|
|
237
|
+
def get_preferences(self, user_id: str = "default") -> dict[str, Any]:
|
|
238
|
+
"""
|
|
239
|
+
Get learned preferences for a user.
|
|
240
|
+
|
|
241
|
+
Args:
|
|
242
|
+
user_id: User identifier
|
|
243
|
+
|
|
244
|
+
Returns:
|
|
245
|
+
Dictionary of preference settings
|
|
246
|
+
|
|
247
|
+
Example:
|
|
248
|
+
>>> prefs = sdk.strategic.get_preferences()
|
|
249
|
+
>>> print(f"Model preferences: {prefs['model_preferences']}")
|
|
250
|
+
"""
|
|
251
|
+
try:
|
|
252
|
+
from htmlgraph.analytics.strategic import PreferenceManager
|
|
253
|
+
|
|
254
|
+
manager = PreferenceManager(self._db_path)
|
|
255
|
+
prefs = manager.get_preferences(user_id)
|
|
256
|
+
manager.close()
|
|
257
|
+
|
|
258
|
+
return prefs.to_dict()
|
|
259
|
+
except Exception as e:
|
|
260
|
+
logger.error(f"Error getting preferences: {e}")
|
|
261
|
+
return {}
|
|
262
|
+
|
|
263
|
+
def record_feedback(
|
|
264
|
+
self,
|
|
265
|
+
suggestion_id: str,
|
|
266
|
+
accepted: bool,
|
|
267
|
+
outcome: str = "unknown",
|
|
268
|
+
comment: str | None = None,
|
|
269
|
+
) -> str | None:
|
|
270
|
+
"""
|
|
271
|
+
Record feedback on a suggestion.
|
|
272
|
+
|
|
273
|
+
This enables the system to learn from user decisions.
|
|
274
|
+
|
|
275
|
+
Args:
|
|
276
|
+
suggestion_id: ID of the suggestion
|
|
277
|
+
accepted: Whether user accepted the suggestion
|
|
278
|
+
outcome: Result of following suggestion (successful, failed, partial)
|
|
279
|
+
comment: Optional text comment
|
|
280
|
+
|
|
281
|
+
Returns:
|
|
282
|
+
Feedback ID if successful, None otherwise
|
|
283
|
+
|
|
284
|
+
Example:
|
|
285
|
+
>>> sdk.strategic.record_feedback(
|
|
286
|
+
... suggestion_id="sug-abc123",
|
|
287
|
+
... accepted=True,
|
|
288
|
+
... outcome="successful"
|
|
289
|
+
... )
|
|
290
|
+
"""
|
|
291
|
+
try:
|
|
292
|
+
from htmlgraph.analytics.strategic import PreferenceManager
|
|
293
|
+
|
|
294
|
+
manager = PreferenceManager(self._db_path)
|
|
295
|
+
feedback_id = manager.record_feedback(
|
|
296
|
+
suggestion_id=suggestion_id,
|
|
297
|
+
accepted=accepted,
|
|
298
|
+
user_id="default",
|
|
299
|
+
session_id=self._sdk._parent_session or "cli-session",
|
|
300
|
+
outcome=outcome,
|
|
301
|
+
comment=comment,
|
|
302
|
+
)
|
|
303
|
+
manager.close()
|
|
304
|
+
|
|
305
|
+
return feedback_id
|
|
306
|
+
except Exception as e:
|
|
307
|
+
logger.error(f"Error recording feedback: {e}")
|
|
308
|
+
return None
|
|
309
|
+
|
|
310
|
+
def reset_preferences(self, user_id: str = "default") -> bool:
|
|
311
|
+
"""
|
|
312
|
+
Reset preferences to defaults.
|
|
313
|
+
|
|
314
|
+
Args:
|
|
315
|
+
user_id: User to reset
|
|
316
|
+
|
|
317
|
+
Returns:
|
|
318
|
+
True if reset successfully
|
|
319
|
+
|
|
320
|
+
Example:
|
|
321
|
+
>>> sdk.strategic.reset_preferences()
|
|
322
|
+
"""
|
|
323
|
+
try:
|
|
324
|
+
from htmlgraph.analytics.strategic import PreferenceManager
|
|
325
|
+
|
|
326
|
+
manager = PreferenceManager(self._db_path)
|
|
327
|
+
result = manager.reset_preferences(user_id)
|
|
328
|
+
manager.close()
|
|
329
|
+
|
|
330
|
+
return result
|
|
331
|
+
except Exception as e:
|
|
332
|
+
logger.error(f"Error resetting preferences: {e}")
|
|
333
|
+
return False
|
|
334
|
+
|
|
335
|
+
def get_acceptance_rate(
|
|
336
|
+
self,
|
|
337
|
+
suggestion_type: str | None = None,
|
|
338
|
+
) -> float:
|
|
339
|
+
"""
|
|
340
|
+
Get suggestion acceptance rate.
|
|
341
|
+
|
|
342
|
+
Args:
|
|
343
|
+
suggestion_type: Optional filter by type
|
|
344
|
+
|
|
345
|
+
Returns:
|
|
346
|
+
Acceptance rate as percentage (0-100)
|
|
347
|
+
|
|
348
|
+
Example:
|
|
349
|
+
>>> rate = sdk.strategic.get_acceptance_rate("delegation")
|
|
350
|
+
>>> print(f"Delegation suggestions: {rate:.0f}% accepted")
|
|
351
|
+
"""
|
|
352
|
+
try:
|
|
353
|
+
from htmlgraph.analytics.strategic import PreferenceManager
|
|
354
|
+
|
|
355
|
+
manager = PreferenceManager(self._db_path)
|
|
356
|
+
rate = manager.get_acceptance_rate("default", suggestion_type)
|
|
357
|
+
manager.close()
|
|
358
|
+
|
|
359
|
+
return rate
|
|
360
|
+
except Exception as e:
|
|
361
|
+
logger.error(f"Error getting acceptance rate: {e}")
|
|
362
|
+
return 0.0
|
|
363
|
+
|
|
364
|
+
# ===== Cost Optimization =====
|
|
365
|
+
|
|
366
|
+
def suggest_token_budget(
|
|
367
|
+
self,
|
|
368
|
+
task_description: str,
|
|
369
|
+
tool_name: str | None = None,
|
|
370
|
+
) -> dict[str, Any]:
|
|
371
|
+
"""
|
|
372
|
+
Get token budget suggestion for a task.
|
|
373
|
+
|
|
374
|
+
Args:
|
|
375
|
+
task_description: Description of the task
|
|
376
|
+
tool_name: Specific tool being used (optional)
|
|
377
|
+
|
|
378
|
+
Returns:
|
|
379
|
+
Token budget recommendation
|
|
380
|
+
|
|
381
|
+
Example:
|
|
382
|
+
>>> budget = sdk.strategic.suggest_token_budget(
|
|
383
|
+
... "Implement caching for API"
|
|
384
|
+
... )
|
|
385
|
+
>>> print(f"Recommended: {budget['recommended']} tokens")
|
|
386
|
+
"""
|
|
387
|
+
try:
|
|
388
|
+
from htmlgraph.analytics.strategic import CostOptimizer
|
|
389
|
+
|
|
390
|
+
optimizer = CostOptimizer(self._db_path)
|
|
391
|
+
budget = optimizer.suggest_token_budget(task_description, tool_name)
|
|
392
|
+
optimizer.close()
|
|
393
|
+
|
|
394
|
+
return budget.to_dict()
|
|
395
|
+
except Exception as e:
|
|
396
|
+
logger.error(f"Error suggesting token budget: {e}")
|
|
397
|
+
return {"recommended": 5000, "minimum": 2500, "maximum": 10000}
|
|
398
|
+
|
|
399
|
+
def choose_model(
|
|
400
|
+
self,
|
|
401
|
+
task_description: str,
|
|
402
|
+
budget_constraint: int | None = None,
|
|
403
|
+
) -> dict[str, Any]:
|
|
404
|
+
"""
|
|
405
|
+
Get model selection recommendation.
|
|
406
|
+
|
|
407
|
+
Args:
|
|
408
|
+
task_description: Description of the task
|
|
409
|
+
budget_constraint: Maximum token budget (optional)
|
|
410
|
+
|
|
411
|
+
Returns:
|
|
412
|
+
Model recommendation
|
|
413
|
+
|
|
414
|
+
Example:
|
|
415
|
+
>>> model = sdk.strategic.choose_model(
|
|
416
|
+
... "Refactor authentication system"
|
|
417
|
+
... )
|
|
418
|
+
>>> print(f"Recommended: {model['recommended_model']}")
|
|
419
|
+
"""
|
|
420
|
+
try:
|
|
421
|
+
from htmlgraph.analytics.strategic import CostOptimizer
|
|
422
|
+
|
|
423
|
+
optimizer = CostOptimizer(self._db_path)
|
|
424
|
+
recommendation = optimizer.choose_model(task_description, budget_constraint)
|
|
425
|
+
optimizer.close()
|
|
426
|
+
|
|
427
|
+
return recommendation.to_dict()
|
|
428
|
+
except Exception as e:
|
|
429
|
+
logger.error(f"Error choosing model: {e}")
|
|
430
|
+
return {"recommended_model": "sonnet", "confidence": 0.5}
|
|
431
|
+
|
|
432
|
+
def get_cost_summary(self, session_id: str | None = None) -> dict[str, Any]:
|
|
433
|
+
"""
|
|
434
|
+
Get cost summary for a session.
|
|
435
|
+
|
|
436
|
+
Args:
|
|
437
|
+
session_id: Session to summarize (current if not specified)
|
|
438
|
+
|
|
439
|
+
Returns:
|
|
440
|
+
Cost breakdown by tool
|
|
441
|
+
|
|
442
|
+
Example:
|
|
443
|
+
>>> summary = sdk.strategic.get_cost_summary()
|
|
444
|
+
>>> print(f"Total tokens: {summary['total_tokens']}")
|
|
445
|
+
"""
|
|
446
|
+
try:
|
|
447
|
+
from htmlgraph.analytics.strategic import CostOptimizer
|
|
448
|
+
|
|
449
|
+
optimizer = CostOptimizer(self._db_path)
|
|
450
|
+
session = session_id or self._sdk._parent_session or "cli-session"
|
|
451
|
+
summary = optimizer.get_cost_summary(session)
|
|
452
|
+
optimizer.close()
|
|
453
|
+
|
|
454
|
+
return summary
|
|
455
|
+
except Exception as e:
|
|
456
|
+
logger.error(f"Error getting cost summary: {e}")
|
|
457
|
+
return {"total_tokens": 0, "breakdown": []}
|
|
458
|
+
|
|
459
|
+
# ===== Task Decomposition =====
|
|
460
|
+
|
|
461
|
+
def suggest_task_decomposition(
|
|
462
|
+
self,
|
|
463
|
+
task_description: str,
|
|
464
|
+
max_subtasks: int = 5,
|
|
465
|
+
) -> list[dict[str, Any]]:
|
|
466
|
+
"""
|
|
467
|
+
Suggest task decomposition based on learned patterns.
|
|
468
|
+
|
|
469
|
+
Uses pattern detection and heuristics to break down complex tasks.
|
|
470
|
+
|
|
471
|
+
Args:
|
|
472
|
+
task_description: Task to decompose
|
|
473
|
+
max_subtasks: Maximum number of subtasks
|
|
474
|
+
|
|
475
|
+
Returns:
|
|
476
|
+
List of suggested subtasks with agent recommendations
|
|
477
|
+
|
|
478
|
+
Example:
|
|
479
|
+
>>> subtasks = sdk.strategic.suggest_task_decomposition(
|
|
480
|
+
... "Implement OAuth2 authentication"
|
|
481
|
+
... )
|
|
482
|
+
>>> for task in subtasks:
|
|
483
|
+
... print(f"{task['task']} -> {task['agent']}")
|
|
484
|
+
"""
|
|
485
|
+
try:
|
|
486
|
+
# Use orchestrator's decomposition method
|
|
487
|
+
orchestrator = self._sdk.orchestrator
|
|
488
|
+
result = orchestrator.suggest_task_decomposition(
|
|
489
|
+
task_description, max_subtasks
|
|
490
|
+
)
|
|
491
|
+
return list(result) if result else []
|
|
492
|
+
except Exception as e:
|
|
493
|
+
logger.error(f"Error suggesting task decomposition: {e}")
|
|
494
|
+
return []
|
|
495
|
+
|
|
496
|
+
def create_task_plan(
|
|
497
|
+
self,
|
|
498
|
+
task_description: str,
|
|
499
|
+
include_cost_estimate: bool = True,
|
|
500
|
+
) -> dict[str, Any]:
|
|
501
|
+
"""
|
|
502
|
+
Create a comprehensive task execution plan.
|
|
503
|
+
|
|
504
|
+
Combines pattern detection, cost optimization, and model selection.
|
|
505
|
+
|
|
506
|
+
Args:
|
|
507
|
+
task_description: Description of the task
|
|
508
|
+
include_cost_estimate: Whether to include cost estimates
|
|
509
|
+
|
|
510
|
+
Returns:
|
|
511
|
+
Comprehensive execution plan
|
|
512
|
+
|
|
513
|
+
Example:
|
|
514
|
+
>>> plan = sdk.strategic.create_task_plan(
|
|
515
|
+
... "Add caching to API endpoints"
|
|
516
|
+
... )
|
|
517
|
+
>>> print(f"Subtasks: {len(plan['subtasks'])}")
|
|
518
|
+
>>> print(f"Estimated tokens: {plan.get('total_estimated_tokens')}")
|
|
519
|
+
"""
|
|
520
|
+
try:
|
|
521
|
+
orchestrator = self._sdk.orchestrator
|
|
522
|
+
result = orchestrator.create_task_suggestion(
|
|
523
|
+
task_description, include_cost_estimate
|
|
524
|
+
)
|
|
525
|
+
return (
|
|
526
|
+
dict(result) if result else {"task": task_description, "subtasks": []}
|
|
527
|
+
)
|
|
528
|
+
except Exception as e:
|
|
529
|
+
logger.error(f"Error creating task plan: {e}")
|
|
530
|
+
return {"task": task_description, "subtasks": []}
|
|
531
|
+
|
|
532
|
+
|
|
533
|
+
class StrategicAnalyticsMixin:
|
|
534
|
+
"""
|
|
535
|
+
Mixin that adds strategic analytics capabilities to SDK.
|
|
536
|
+
|
|
537
|
+
Provides access to Phase 3 Strategic Analytics via sdk.strategic property.
|
|
538
|
+
"""
|
|
539
|
+
|
|
540
|
+
_strategic: StrategicAnalyticsInterface | None = None
|
|
541
|
+
|
|
542
|
+
@property
|
|
543
|
+
def strategic(self) -> StrategicAnalyticsInterface:
|
|
544
|
+
"""
|
|
545
|
+
Access strategic analytics interface.
|
|
546
|
+
|
|
547
|
+
Returns:
|
|
548
|
+
StrategicAnalyticsInterface for pattern detection, suggestions, etc.
|
|
549
|
+
|
|
550
|
+
Example:
|
|
551
|
+
>>> sdk = SDK(agent="claude")
|
|
552
|
+
>>> patterns = sdk.strategic.detect_patterns()
|
|
553
|
+
>>> suggestions = sdk.strategic.get_suggestions()
|
|
554
|
+
"""
|
|
555
|
+
if self._strategic is None:
|
|
556
|
+
# Cast self to SDK for type checking
|
|
557
|
+
from typing import cast
|
|
558
|
+
|
|
559
|
+
from htmlgraph.sdk import SDK
|
|
560
|
+
|
|
561
|
+
sdk_self = cast(SDK, self)
|
|
562
|
+
self._strategic = StrategicAnalyticsInterface(_sdk=sdk_self)
|
|
563
|
+
return self._strategic
|
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
|
|
|
@@ -243,8 +247,8 @@ class HtmlGraphAPIHandler(SimpleHTTPRequestHandler):
|
|
|
243
247
|
def do_GET(self) -> None:
|
|
244
248
|
"""Handle GET requests."""
|
|
245
249
|
api, collection, node_id, params = self._parse_path()
|
|
246
|
-
|
|
247
|
-
f"
|
|
250
|
+
logger.debug(
|
|
251
|
+
f"do_GET: api={api}, collection={collection}, node_id={node_id}, params={params}"
|
|
248
252
|
)
|
|
249
253
|
|
|
250
254
|
# Not an API request - serve static files
|
|
@@ -273,7 +277,7 @@ class HtmlGraphAPIHandler(SimpleHTTPRequestHandler):
|
|
|
273
277
|
|
|
274
278
|
# GET /api/orchestration - Get delegation chains and agent coordination
|
|
275
279
|
if collection == "orchestration":
|
|
276
|
-
|
|
280
|
+
logger.info(f"DEBUG: Handling orchestration request, params={params}")
|
|
277
281
|
return self._handle_orchestration_view(params)
|
|
278
282
|
|
|
279
283
|
# GET /api/task-delegations/stats - Get aggregated delegation statistics
|
|
@@ -1275,7 +1279,7 @@ class HtmlGraphAPIHandler(SimpleHTTPRequestHandler):
|
|
|
1275
1279
|
|
|
1276
1280
|
def log_message(self, format: str, *args: str) -> None:
|
|
1277
1281
|
"""Custom log format."""
|
|
1278
|
-
|
|
1282
|
+
logger.info(f"[{datetime.now().strftime('%H:%M:%S')}] {args[0]}")
|
|
1279
1283
|
|
|
1280
1284
|
|
|
1281
1285
|
def find_available_port(start_port: int = 8080, max_attempts: int = 10) -> int:
|
|
@@ -1434,7 +1438,7 @@ def serve(
|
|
|
1434
1438
|
# Print warnings if any
|
|
1435
1439
|
for warning in result.warnings:
|
|
1436
1440
|
if not quiet:
|
|
1437
|
-
|
|
1441
|
+
logger.info(f"⚠️ {warning}")
|
|
1438
1442
|
|
|
1439
1443
|
# Print server info
|
|
1440
1444
|
if not quiet:
|
|
@@ -1473,31 +1477,31 @@ Press Ctrl+C to stop.
|
|
|
1473
1477
|
asyncio.run(run_fastapi_server(result.handle))
|
|
1474
1478
|
|
|
1475
1479
|
except PortInUseError:
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
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")
|
|
1484
1488
|
|
|
1485
1489
|
# Try to find and suggest an available port
|
|
1486
1490
|
try:
|
|
1487
1491
|
alt_port = find_available_port(port + 1)
|
|
1488
|
-
|
|
1489
|
-
|
|
1492
|
+
logger.info(f"💡 Found available port: {alt_port}")
|
|
1493
|
+
logger.info(f" Run: htmlgraph serve --port {alt_port}\n")
|
|
1490
1494
|
except OSError:
|
|
1491
1495
|
pass
|
|
1492
1496
|
|
|
1493
1497
|
sys.exit(1)
|
|
1494
1498
|
|
|
1495
1499
|
except FastAPIServerError as e:
|
|
1496
|
-
|
|
1500
|
+
logger.info(f"\n❌ Server error: {e}\n")
|
|
1497
1501
|
sys.exit(1)
|
|
1498
1502
|
|
|
1499
1503
|
except KeyboardInterrupt:
|
|
1500
|
-
|
|
1504
|
+
logger.info("\nShutting down...")
|
|
1501
1505
|
|
|
1502
1506
|
|
|
1503
1507
|
if __name__ == "__main__":
|
htmlgraph/session_warning.py
CHANGED
htmlgraph/sessions/handoff.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
"""
|
|
2
4
|
Session Handoff and Continuity - Phase 2 Feature 3
|
|
3
5
|
|
|
@@ -19,11 +21,10 @@ Usage:
|
|
|
19
21
|
# Resume next session
|
|
20
22
|
resumed = sdk.sessions.continue_from_last()
|
|
21
23
|
if resumed:
|
|
22
|
-
|
|
23
|
-
|
|
24
|
+
logger.info("%s", resumed.summary)
|
|
25
|
+
logger.info("%s", resumed.recommended_files)
|
|
24
26
|
"""
|
|
25
27
|
|
|
26
|
-
from __future__ import annotations
|
|
27
28
|
|
|
28
29
|
import json
|
|
29
30
|
import logging
|
htmlgraph/system_prompts.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
"""System prompt management for HtmlGraph projects.
|
|
2
4
|
|
|
3
5
|
Provides a two-tier system:
|
|
@@ -10,7 +12,6 @@ Architecture:
|
|
|
10
12
|
- SDK provides methods for creation, validation, and management
|
|
11
13
|
"""
|
|
12
14
|
|
|
13
|
-
from __future__ import annotations
|
|
14
15
|
|
|
15
16
|
import logging
|
|
16
17
|
from pathlib import Path
|