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
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
"""Base spawner class with common functionality for all AI spawners."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import TYPE_CHECKING, Any
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from htmlgraph.orchestration.live_events import LiveEventPublisher
|
|
9
|
+
from htmlgraph.sdk import SDK
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class AIResult:
|
|
14
|
+
"""Result from AI CLI execution."""
|
|
15
|
+
|
|
16
|
+
success: bool
|
|
17
|
+
response: str
|
|
18
|
+
tokens_used: int | None
|
|
19
|
+
error: str | None
|
|
20
|
+
raw_output: dict | list | str | None
|
|
21
|
+
tracked_events: list[dict] | None = None # Events tracked in HtmlGraph
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class BaseSpawner:
|
|
25
|
+
"""
|
|
26
|
+
Base class for AI spawners with common functionality.
|
|
27
|
+
|
|
28
|
+
Provides:
|
|
29
|
+
- Live event publishing for WebSocket streaming
|
|
30
|
+
- SDK initialization with parent session support
|
|
31
|
+
- Common error handling patterns
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(self) -> None:
|
|
35
|
+
"""Initialize spawner."""
|
|
36
|
+
self._live_publisher: LiveEventPublisher | None = None
|
|
37
|
+
|
|
38
|
+
def _get_live_publisher(self) -> "LiveEventPublisher | None":
|
|
39
|
+
"""
|
|
40
|
+
Get LiveEventPublisher instance for real-time WebSocket streaming.
|
|
41
|
+
|
|
42
|
+
Returns None if publisher unavailable (optional dependency).
|
|
43
|
+
"""
|
|
44
|
+
if self._live_publisher is None:
|
|
45
|
+
try:
|
|
46
|
+
from htmlgraph.orchestration.live_events import LiveEventPublisher
|
|
47
|
+
|
|
48
|
+
self._live_publisher = LiveEventPublisher()
|
|
49
|
+
except Exception:
|
|
50
|
+
# Live events are optional
|
|
51
|
+
pass
|
|
52
|
+
return self._live_publisher
|
|
53
|
+
|
|
54
|
+
def _publish_live_event(
|
|
55
|
+
self,
|
|
56
|
+
event_type: str,
|
|
57
|
+
spawner_type: str,
|
|
58
|
+
**kwargs: str | int | float | bool | None,
|
|
59
|
+
) -> None:
|
|
60
|
+
"""
|
|
61
|
+
Publish a live event for WebSocket streaming.
|
|
62
|
+
|
|
63
|
+
Silently fails if publisher unavailable (optional feature).
|
|
64
|
+
"""
|
|
65
|
+
publisher = self._get_live_publisher()
|
|
66
|
+
if publisher is None:
|
|
67
|
+
return
|
|
68
|
+
|
|
69
|
+
parent_event_id = os.getenv("HTMLGRAPH_PARENT_EVENT")
|
|
70
|
+
|
|
71
|
+
try:
|
|
72
|
+
if event_type == "spawner_start":
|
|
73
|
+
publisher.spawner_start(
|
|
74
|
+
spawner_type=spawner_type,
|
|
75
|
+
prompt=str(kwargs.get("prompt", "")),
|
|
76
|
+
parent_event_id=parent_event_id,
|
|
77
|
+
model=str(kwargs.get("model", "")) if kwargs.get("model") else None,
|
|
78
|
+
)
|
|
79
|
+
elif event_type == "spawner_phase":
|
|
80
|
+
progress_val = kwargs.get("progress")
|
|
81
|
+
publisher.spawner_phase(
|
|
82
|
+
spawner_type=spawner_type,
|
|
83
|
+
phase=str(kwargs.get("phase", "executing")),
|
|
84
|
+
progress=int(progress_val) if progress_val is not None else None,
|
|
85
|
+
details=str(kwargs.get("details", ""))
|
|
86
|
+
if kwargs.get("details")
|
|
87
|
+
else None,
|
|
88
|
+
parent_event_id=parent_event_id,
|
|
89
|
+
)
|
|
90
|
+
elif event_type == "spawner_complete":
|
|
91
|
+
duration_val = kwargs.get("duration")
|
|
92
|
+
tokens_val = kwargs.get("tokens")
|
|
93
|
+
publisher.spawner_complete(
|
|
94
|
+
spawner_type=spawner_type,
|
|
95
|
+
success=bool(kwargs.get("success", False)),
|
|
96
|
+
duration_seconds=float(duration_val)
|
|
97
|
+
if duration_val is not None
|
|
98
|
+
else None,
|
|
99
|
+
response_preview=str(kwargs.get("response", ""))[:200]
|
|
100
|
+
if kwargs.get("response")
|
|
101
|
+
else None,
|
|
102
|
+
tokens_used=int(tokens_val) if tokens_val is not None else None,
|
|
103
|
+
error=str(kwargs.get("error", "")) if kwargs.get("error") else None,
|
|
104
|
+
parent_event_id=parent_event_id,
|
|
105
|
+
)
|
|
106
|
+
elif event_type == "spawner_tool_use":
|
|
107
|
+
publisher.spawner_tool_use(
|
|
108
|
+
spawner_type=spawner_type,
|
|
109
|
+
tool_name=str(kwargs.get("tool_name", "unknown")),
|
|
110
|
+
parent_event_id=parent_event_id,
|
|
111
|
+
)
|
|
112
|
+
elif event_type == "spawner_message":
|
|
113
|
+
publisher.spawner_message(
|
|
114
|
+
spawner_type=spawner_type,
|
|
115
|
+
message=str(kwargs.get("message", "")),
|
|
116
|
+
role=str(kwargs.get("role", "assistant")),
|
|
117
|
+
parent_event_id=parent_event_id,
|
|
118
|
+
)
|
|
119
|
+
except Exception:
|
|
120
|
+
# Live events should never break spawner execution
|
|
121
|
+
pass
|
|
122
|
+
|
|
123
|
+
def _get_sdk(self) -> "SDK | None":
|
|
124
|
+
"""
|
|
125
|
+
Get SDK instance for HtmlGraph tracking with parent session support.
|
|
126
|
+
|
|
127
|
+
Returns None if SDK unavailable.
|
|
128
|
+
"""
|
|
129
|
+
try:
|
|
130
|
+
from htmlgraph.sdk import SDK
|
|
131
|
+
|
|
132
|
+
# Read parent session context from environment
|
|
133
|
+
parent_session = os.getenv("HTMLGRAPH_PARENT_SESSION")
|
|
134
|
+
parent_agent = os.getenv("HTMLGRAPH_PARENT_AGENT")
|
|
135
|
+
|
|
136
|
+
# Create SDK with parent session context
|
|
137
|
+
sdk = SDK(
|
|
138
|
+
agent=f"spawner-{parent_agent}" if parent_agent else "spawner",
|
|
139
|
+
parent_session=parent_session, # Pass parent session
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
return sdk
|
|
143
|
+
|
|
144
|
+
except Exception:
|
|
145
|
+
# SDK unavailable or not properly initialized (optional dependency)
|
|
146
|
+
# This happens in test contexts without active sessions
|
|
147
|
+
# Don't log error to avoid noise in tests
|
|
148
|
+
return None
|
|
149
|
+
|
|
150
|
+
def _get_parent_context(self) -> tuple[str | None, int]:
|
|
151
|
+
"""
|
|
152
|
+
Get parent activity context for event tracking.
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
Tuple of (parent_activity_id, nesting_depth)
|
|
156
|
+
"""
|
|
157
|
+
parent_activity = os.getenv("HTMLGRAPH_PARENT_ACTIVITY")
|
|
158
|
+
nesting_depth_str = os.getenv("HTMLGRAPH_NESTING_DEPTH", "0")
|
|
159
|
+
nesting_depth = int(nesting_depth_str) if nesting_depth_str.isdigit() else 0
|
|
160
|
+
return parent_activity, nesting_depth
|
|
161
|
+
|
|
162
|
+
def _track_activity(
|
|
163
|
+
self,
|
|
164
|
+
sdk: "SDK",
|
|
165
|
+
tool: str,
|
|
166
|
+
summary: str,
|
|
167
|
+
payload: dict[str, Any] | None = None,
|
|
168
|
+
**kwargs: Any,
|
|
169
|
+
) -> None:
|
|
170
|
+
"""
|
|
171
|
+
Track activity in HtmlGraph with parent context.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
sdk: SDK instance
|
|
175
|
+
tool: Tool name
|
|
176
|
+
summary: Activity summary
|
|
177
|
+
payload: Activity payload (will be enriched with parent context)
|
|
178
|
+
**kwargs: Additional arguments for track_activity
|
|
179
|
+
"""
|
|
180
|
+
if payload is None:
|
|
181
|
+
payload = {}
|
|
182
|
+
|
|
183
|
+
# Enrich with parent context
|
|
184
|
+
parent_activity, nesting_depth = self._get_parent_context()
|
|
185
|
+
if parent_activity:
|
|
186
|
+
payload["parent_activity"] = parent_activity
|
|
187
|
+
if nesting_depth > 0:
|
|
188
|
+
payload["nesting_depth"] = nesting_depth
|
|
189
|
+
|
|
190
|
+
try:
|
|
191
|
+
sdk.track_activity(tool=tool, summary=summary, payload=payload, **kwargs)
|
|
192
|
+
except Exception:
|
|
193
|
+
# Tracking failure should not break execution
|
|
194
|
+
pass
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"""Claude spawner implementation."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import logging
|
|
5
|
+
import subprocess
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
from .base import AIResult, BaseSpawner
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ClaudeSpawner(BaseSpawner):
|
|
17
|
+
"""
|
|
18
|
+
Spawner for Claude Code CLI.
|
|
19
|
+
|
|
20
|
+
NOTE: Uses same Claude Code authentication as Task() tool, but provides
|
|
21
|
+
isolated execution context. Each call creates a new session without shared
|
|
22
|
+
context. Best for independent tasks or external scripts.
|
|
23
|
+
|
|
24
|
+
For orchestration workflows with shared context, prefer Task() tool which
|
|
25
|
+
leverages prompt caching (5x cheaper for related work).
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def spawn(
|
|
29
|
+
self,
|
|
30
|
+
prompt: str,
|
|
31
|
+
output_format: str = "json",
|
|
32
|
+
permission_mode: str = "bypassPermissions",
|
|
33
|
+
resume: str | None = None,
|
|
34
|
+
verbose: bool = False,
|
|
35
|
+
timeout: int = 300,
|
|
36
|
+
extra_args: list[str] | None = None,
|
|
37
|
+
) -> AIResult:
|
|
38
|
+
"""
|
|
39
|
+
Spawn Claude in headless mode.
|
|
40
|
+
|
|
41
|
+
NOTE: Uses same Claude Code authentication as Task() tool, but provides
|
|
42
|
+
isolated execution context. Each call creates a new session without shared
|
|
43
|
+
context. Best for independent tasks or external scripts.
|
|
44
|
+
|
|
45
|
+
For orchestration workflows with shared context, prefer Task() tool which
|
|
46
|
+
leverages prompt caching (5x cheaper for related work).
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
prompt: Task description for Claude
|
|
50
|
+
output_format: "text" or "json" (stream-json requires --verbose)
|
|
51
|
+
permission_mode: Permission handling mode:
|
|
52
|
+
- "bypassPermissions": Auto-approve all (default)
|
|
53
|
+
- "acceptEdits": Auto-approve edits only
|
|
54
|
+
- "dontAsk": Fail on permission prompts
|
|
55
|
+
- "default": Normal interactive prompts
|
|
56
|
+
- "plan": Plan mode (no execution)
|
|
57
|
+
- "delegate": Delegation mode
|
|
58
|
+
resume: Resume from previous session (--resume). Default: None
|
|
59
|
+
verbose: Enable verbose output (--verbose). Default: False
|
|
60
|
+
timeout: Max seconds (default: 300, Claude can be slow with initialization)
|
|
61
|
+
extra_args: Additional arguments to pass to Claude CLI
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
AIResult with response or error
|
|
65
|
+
|
|
66
|
+
Example:
|
|
67
|
+
>>> spawner = ClaudeSpawner()
|
|
68
|
+
>>> result = spawner.spawn("What is 2+2?")
|
|
69
|
+
>>> if result.success:
|
|
70
|
+
... logger.info("%s", result.response) # "4"
|
|
71
|
+
... logger.info(f"Cost: ${result.raw_output['total_cost_usd']}")
|
|
72
|
+
"""
|
|
73
|
+
cmd = ["claude", "-p"]
|
|
74
|
+
|
|
75
|
+
if output_format != "text":
|
|
76
|
+
cmd.extend(["--output-format", output_format])
|
|
77
|
+
|
|
78
|
+
if permission_mode:
|
|
79
|
+
cmd.extend(["--permission-mode", permission_mode])
|
|
80
|
+
|
|
81
|
+
# Add resume flag if specified
|
|
82
|
+
if resume:
|
|
83
|
+
cmd.extend(["--resume", resume])
|
|
84
|
+
|
|
85
|
+
# Add verbose flag
|
|
86
|
+
if verbose:
|
|
87
|
+
cmd.append("--verbose")
|
|
88
|
+
|
|
89
|
+
# Add extra args
|
|
90
|
+
if extra_args:
|
|
91
|
+
cmd.extend(extra_args)
|
|
92
|
+
|
|
93
|
+
# Use -- separator to ensure prompt isn't consumed by variadic args
|
|
94
|
+
cmd.append("--")
|
|
95
|
+
cmd.append(prompt)
|
|
96
|
+
|
|
97
|
+
try:
|
|
98
|
+
result = subprocess.run(
|
|
99
|
+
cmd,
|
|
100
|
+
capture_output=True,
|
|
101
|
+
text=True,
|
|
102
|
+
timeout=timeout,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
if output_format == "json":
|
|
106
|
+
# Parse JSON output
|
|
107
|
+
try:
|
|
108
|
+
output = json.loads(result.stdout)
|
|
109
|
+
except json.JSONDecodeError as e:
|
|
110
|
+
return AIResult(
|
|
111
|
+
success=False,
|
|
112
|
+
response="",
|
|
113
|
+
tokens_used=None,
|
|
114
|
+
error=f"Failed to parse JSON output: {e}",
|
|
115
|
+
raw_output=result.stdout,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
# Extract result and metadata
|
|
119
|
+
usage = output.get("usage", {})
|
|
120
|
+
tokens = (
|
|
121
|
+
usage.get("input_tokens", 0)
|
|
122
|
+
+ usage.get("cache_creation_input_tokens", 0)
|
|
123
|
+
+ usage.get("cache_read_input_tokens", 0)
|
|
124
|
+
+ usage.get("output_tokens", 0)
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
return AIResult(
|
|
128
|
+
success=output.get("type") == "result"
|
|
129
|
+
and not output.get("is_error"),
|
|
130
|
+
response=output.get("result", ""),
|
|
131
|
+
tokens_used=tokens,
|
|
132
|
+
error=output.get("error") if output.get("is_error") else None,
|
|
133
|
+
raw_output=output,
|
|
134
|
+
)
|
|
135
|
+
else:
|
|
136
|
+
# Plain text output
|
|
137
|
+
return AIResult(
|
|
138
|
+
success=result.returncode == 0,
|
|
139
|
+
response=result.stdout.strip(),
|
|
140
|
+
tokens_used=None,
|
|
141
|
+
error=None if result.returncode == 0 else result.stderr,
|
|
142
|
+
raw_output=result.stdout,
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
except FileNotFoundError:
|
|
146
|
+
return AIResult(
|
|
147
|
+
success=False,
|
|
148
|
+
response="",
|
|
149
|
+
tokens_used=None,
|
|
150
|
+
error="Claude CLI not found. Install Claude Code from: https://claude.com/claude-code",
|
|
151
|
+
raw_output=None,
|
|
152
|
+
)
|
|
153
|
+
except subprocess.TimeoutExpired as e:
|
|
154
|
+
return AIResult(
|
|
155
|
+
success=False,
|
|
156
|
+
response="",
|
|
157
|
+
tokens_used=None,
|
|
158
|
+
error=f"Timed out after {timeout} seconds",
|
|
159
|
+
raw_output={
|
|
160
|
+
"partial_stdout": e.stdout.decode() if e.stdout else None,
|
|
161
|
+
"partial_stderr": e.stderr.decode() if e.stderr else None,
|
|
162
|
+
}
|
|
163
|
+
if e.stdout or e.stderr
|
|
164
|
+
else None,
|
|
165
|
+
)
|
|
166
|
+
except Exception as e:
|
|
167
|
+
return AIResult(
|
|
168
|
+
success=False,
|
|
169
|
+
response="",
|
|
170
|
+
tokens_used=None,
|
|
171
|
+
error=f"Unexpected error: {type(e).__name__}: {e}",
|
|
172
|
+
raw_output=None,
|
|
173
|
+
)
|