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.
Files changed (304) hide show
  1. htmlgraph/.htmlgraph/.session-warning-state.json +6 -0
  2. htmlgraph/.htmlgraph/agents.json +72 -0
  3. htmlgraph/.htmlgraph/htmlgraph.db +0 -0
  4. htmlgraph/__init__.py +51 -1
  5. htmlgraph/__init__.pyi +123 -0
  6. htmlgraph/agent_detection.py +26 -10
  7. htmlgraph/agent_registry.py +2 -1
  8. htmlgraph/analytics/__init__.py +8 -1
  9. htmlgraph/analytics/cli.py +86 -20
  10. htmlgraph/analytics/cost_analyzer.py +391 -0
  11. htmlgraph/analytics/cost_monitor.py +664 -0
  12. htmlgraph/analytics/cost_reporter.py +675 -0
  13. htmlgraph/analytics/cross_session.py +617 -0
  14. htmlgraph/analytics/dependency.py +10 -6
  15. htmlgraph/analytics/pattern_learning.py +771 -0
  16. htmlgraph/analytics/session_graph.py +707 -0
  17. htmlgraph/analytics/strategic/__init__.py +80 -0
  18. htmlgraph/analytics/strategic/cost_optimizer.py +611 -0
  19. htmlgraph/analytics/strategic/pattern_detector.py +876 -0
  20. htmlgraph/analytics/strategic/preference_manager.py +709 -0
  21. htmlgraph/analytics/strategic/suggestion_engine.py +747 -0
  22. htmlgraph/analytics/work_type.py +67 -27
  23. htmlgraph/analytics_index.py +53 -20
  24. htmlgraph/api/__init__.py +3 -0
  25. htmlgraph/api/cost_alerts_websocket.py +416 -0
  26. htmlgraph/api/main.py +2498 -0
  27. htmlgraph/api/static/htmx.min.js +1 -0
  28. htmlgraph/api/static/style-redesign.css +1344 -0
  29. htmlgraph/api/static/style.css +1079 -0
  30. htmlgraph/api/templates/dashboard-redesign.html +1366 -0
  31. htmlgraph/api/templates/dashboard.html +794 -0
  32. htmlgraph/api/templates/partials/activity-feed-hierarchical.html +326 -0
  33. htmlgraph/api/templates/partials/activity-feed.html +1100 -0
  34. htmlgraph/api/templates/partials/agents-redesign.html +317 -0
  35. htmlgraph/api/templates/partials/agents.html +317 -0
  36. htmlgraph/api/templates/partials/event-traces.html +373 -0
  37. htmlgraph/api/templates/partials/features-kanban-redesign.html +509 -0
  38. htmlgraph/api/templates/partials/features.html +578 -0
  39. htmlgraph/api/templates/partials/metrics-redesign.html +346 -0
  40. htmlgraph/api/templates/partials/metrics.html +346 -0
  41. htmlgraph/api/templates/partials/orchestration-redesign.html +443 -0
  42. htmlgraph/api/templates/partials/orchestration.html +198 -0
  43. htmlgraph/api/templates/partials/spawners.html +375 -0
  44. htmlgraph/api/templates/partials/work-items.html +613 -0
  45. htmlgraph/api/websocket.py +538 -0
  46. htmlgraph/archive/__init__.py +24 -0
  47. htmlgraph/archive/bloom.py +234 -0
  48. htmlgraph/archive/fts.py +297 -0
  49. htmlgraph/archive/manager.py +583 -0
  50. htmlgraph/archive/search.py +244 -0
  51. htmlgraph/atomic_ops.py +560 -0
  52. htmlgraph/attribute_index.py +2 -1
  53. htmlgraph/bounded_paths.py +539 -0
  54. htmlgraph/builders/base.py +57 -2
  55. htmlgraph/builders/bug.py +19 -3
  56. htmlgraph/builders/chore.py +19 -3
  57. htmlgraph/builders/epic.py +19 -3
  58. htmlgraph/builders/feature.py +27 -3
  59. htmlgraph/builders/insight.py +2 -1
  60. htmlgraph/builders/metric.py +2 -1
  61. htmlgraph/builders/pattern.py +2 -1
  62. htmlgraph/builders/phase.py +19 -3
  63. htmlgraph/builders/spike.py +29 -3
  64. htmlgraph/builders/track.py +42 -1
  65. htmlgraph/cigs/__init__.py +81 -0
  66. htmlgraph/cigs/autonomy.py +385 -0
  67. htmlgraph/cigs/cost.py +475 -0
  68. htmlgraph/cigs/messages_basic.py +472 -0
  69. htmlgraph/cigs/messaging.py +365 -0
  70. htmlgraph/cigs/models.py +771 -0
  71. htmlgraph/cigs/pattern_storage.py +427 -0
  72. htmlgraph/cigs/patterns.py +503 -0
  73. htmlgraph/cigs/posttool_analyzer.py +234 -0
  74. htmlgraph/cigs/reporter.py +818 -0
  75. htmlgraph/cigs/tracker.py +317 -0
  76. htmlgraph/cli/.htmlgraph/.session-warning-state.json +6 -0
  77. htmlgraph/cli/.htmlgraph/agents.json +72 -0
  78. htmlgraph/cli/.htmlgraph/htmlgraph.db +0 -0
  79. htmlgraph/cli/__init__.py +42 -0
  80. htmlgraph/cli/__main__.py +6 -0
  81. htmlgraph/cli/analytics.py +1424 -0
  82. htmlgraph/cli/base.py +685 -0
  83. htmlgraph/cli/constants.py +206 -0
  84. htmlgraph/cli/core.py +954 -0
  85. htmlgraph/cli/main.py +147 -0
  86. htmlgraph/cli/models.py +475 -0
  87. htmlgraph/cli/templates/__init__.py +1 -0
  88. htmlgraph/cli/templates/cost_dashboard.py +399 -0
  89. htmlgraph/cli/work/__init__.py +239 -0
  90. htmlgraph/cli/work/browse.py +115 -0
  91. htmlgraph/cli/work/features.py +568 -0
  92. htmlgraph/cli/work/orchestration.py +676 -0
  93. htmlgraph/cli/work/report.py +728 -0
  94. htmlgraph/cli/work/sessions.py +466 -0
  95. htmlgraph/cli/work/snapshot.py +559 -0
  96. htmlgraph/cli/work/tracks.py +486 -0
  97. htmlgraph/cli_commands/__init__.py +1 -0
  98. htmlgraph/cli_commands/feature.py +195 -0
  99. htmlgraph/cli_framework.py +115 -0
  100. htmlgraph/collections/__init__.py +2 -0
  101. htmlgraph/collections/base.py +197 -14
  102. htmlgraph/collections/bug.py +2 -1
  103. htmlgraph/collections/chore.py +2 -1
  104. htmlgraph/collections/epic.py +2 -1
  105. htmlgraph/collections/feature.py +2 -1
  106. htmlgraph/collections/insight.py +2 -1
  107. htmlgraph/collections/metric.py +2 -1
  108. htmlgraph/collections/pattern.py +2 -1
  109. htmlgraph/collections/phase.py +2 -1
  110. htmlgraph/collections/session.py +194 -0
  111. htmlgraph/collections/spike.py +13 -2
  112. htmlgraph/collections/task_delegation.py +241 -0
  113. htmlgraph/collections/todo.py +14 -1
  114. htmlgraph/collections/traces.py +487 -0
  115. htmlgraph/config/cost_models.json +56 -0
  116. htmlgraph/config.py +190 -0
  117. htmlgraph/context_analytics.py +2 -1
  118. htmlgraph/converter.py +116 -7
  119. htmlgraph/cost_analysis/__init__.py +5 -0
  120. htmlgraph/cost_analysis/analyzer.py +438 -0
  121. htmlgraph/dashboard.html +2246 -248
  122. htmlgraph/dashboard.html.backup +6592 -0
  123. htmlgraph/dashboard.html.bak +7181 -0
  124. htmlgraph/dashboard.html.bak2 +7231 -0
  125. htmlgraph/dashboard.html.bak3 +7232 -0
  126. htmlgraph/db/__init__.py +38 -0
  127. htmlgraph/db/queries.py +790 -0
  128. htmlgraph/db/schema.py +1788 -0
  129. htmlgraph/decorators.py +317 -0
  130. htmlgraph/dependency_models.py +2 -1
  131. htmlgraph/deploy.py +26 -27
  132. htmlgraph/docs/API_REFERENCE.md +841 -0
  133. htmlgraph/docs/HTTP_API.md +750 -0
  134. htmlgraph/docs/INTEGRATION_GUIDE.md +752 -0
  135. htmlgraph/docs/ORCHESTRATION_PATTERNS.md +717 -0
  136. htmlgraph/docs/README.md +532 -0
  137. htmlgraph/docs/__init__.py +77 -0
  138. htmlgraph/docs/docs_version.py +55 -0
  139. htmlgraph/docs/metadata.py +93 -0
  140. htmlgraph/docs/migrations.py +232 -0
  141. htmlgraph/docs/template_engine.py +143 -0
  142. htmlgraph/docs/templates/_sections/cli_reference.md.j2 +52 -0
  143. htmlgraph/docs/templates/_sections/core_concepts.md.j2 +29 -0
  144. htmlgraph/docs/templates/_sections/sdk_basics.md.j2 +69 -0
  145. htmlgraph/docs/templates/base_agents.md.j2 +78 -0
  146. htmlgraph/docs/templates/example_user_override.md.j2 +47 -0
  147. htmlgraph/docs/version_check.py +163 -0
  148. htmlgraph/edge_index.py +2 -1
  149. htmlgraph/error_handler.py +544 -0
  150. htmlgraph/event_log.py +86 -37
  151. htmlgraph/event_migration.py +2 -1
  152. htmlgraph/file_watcher.py +12 -8
  153. htmlgraph/find_api.py +2 -1
  154. htmlgraph/git_events.py +67 -9
  155. htmlgraph/hooks/.htmlgraph/.session-warning-state.json +6 -0
  156. htmlgraph/hooks/.htmlgraph/agents.json +72 -0
  157. htmlgraph/hooks/.htmlgraph/index.sqlite +0 -0
  158. htmlgraph/hooks/__init__.py +8 -0
  159. htmlgraph/hooks/bootstrap.py +169 -0
  160. htmlgraph/hooks/cigs_pretool_enforcer.py +354 -0
  161. htmlgraph/hooks/concurrent_sessions.py +208 -0
  162. htmlgraph/hooks/context.py +350 -0
  163. htmlgraph/hooks/drift_handler.py +525 -0
  164. htmlgraph/hooks/event_tracker.py +790 -99
  165. htmlgraph/hooks/git_commands.py +175 -0
  166. htmlgraph/hooks/installer.py +5 -1
  167. htmlgraph/hooks/orchestrator.py +327 -76
  168. htmlgraph/hooks/orchestrator_reflector.py +31 -4
  169. htmlgraph/hooks/post_tool_use_failure.py +32 -7
  170. htmlgraph/hooks/post_tool_use_handler.py +257 -0
  171. htmlgraph/hooks/posttooluse.py +92 -19
  172. htmlgraph/hooks/pretooluse.py +527 -7
  173. htmlgraph/hooks/prompt_analyzer.py +637 -0
  174. htmlgraph/hooks/session_handler.py +668 -0
  175. htmlgraph/hooks/session_summary.py +395 -0
  176. htmlgraph/hooks/state_manager.py +504 -0
  177. htmlgraph/hooks/subagent_detection.py +202 -0
  178. htmlgraph/hooks/subagent_stop.py +369 -0
  179. htmlgraph/hooks/task_enforcer.py +99 -4
  180. htmlgraph/hooks/validator.py +212 -91
  181. htmlgraph/ids.py +2 -1
  182. htmlgraph/learning.py +125 -100
  183. htmlgraph/mcp_server.py +2 -1
  184. htmlgraph/models.py +217 -18
  185. htmlgraph/operations/README.md +62 -0
  186. htmlgraph/operations/__init__.py +79 -0
  187. htmlgraph/operations/analytics.py +339 -0
  188. htmlgraph/operations/bootstrap.py +289 -0
  189. htmlgraph/operations/events.py +244 -0
  190. htmlgraph/operations/fastapi_server.py +231 -0
  191. htmlgraph/operations/hooks.py +350 -0
  192. htmlgraph/operations/initialization.py +597 -0
  193. htmlgraph/operations/initialization.py.backup +228 -0
  194. htmlgraph/operations/server.py +303 -0
  195. htmlgraph/orchestration/__init__.py +58 -0
  196. htmlgraph/orchestration/claude_launcher.py +179 -0
  197. htmlgraph/orchestration/command_builder.py +72 -0
  198. htmlgraph/orchestration/headless_spawner.py +281 -0
  199. htmlgraph/orchestration/live_events.py +377 -0
  200. htmlgraph/orchestration/model_selection.py +327 -0
  201. htmlgraph/orchestration/plugin_manager.py +140 -0
  202. htmlgraph/orchestration/prompts.py +137 -0
  203. htmlgraph/orchestration/spawner_event_tracker.py +383 -0
  204. htmlgraph/orchestration/spawners/__init__.py +16 -0
  205. htmlgraph/orchestration/spawners/base.py +194 -0
  206. htmlgraph/orchestration/spawners/claude.py +173 -0
  207. htmlgraph/orchestration/spawners/codex.py +435 -0
  208. htmlgraph/orchestration/spawners/copilot.py +294 -0
  209. htmlgraph/orchestration/spawners/gemini.py +471 -0
  210. htmlgraph/orchestration/subprocess_runner.py +36 -0
  211. htmlgraph/{orchestration.py → orchestration/task_coordination.py} +16 -8
  212. htmlgraph/orchestration.md +563 -0
  213. htmlgraph/orchestrator-system-prompt-optimized.txt +863 -0
  214. htmlgraph/orchestrator.py +2 -1
  215. htmlgraph/orchestrator_config.py +357 -0
  216. htmlgraph/orchestrator_mode.py +115 -4
  217. htmlgraph/parallel.py +2 -1
  218. htmlgraph/parser.py +86 -6
  219. htmlgraph/path_query.py +608 -0
  220. htmlgraph/pattern_matcher.py +636 -0
  221. htmlgraph/pydantic_models.py +476 -0
  222. htmlgraph/quality_gates.py +350 -0
  223. htmlgraph/query_builder.py +2 -1
  224. htmlgraph/query_composer.py +509 -0
  225. htmlgraph/reflection.py +443 -0
  226. htmlgraph/refs.py +344 -0
  227. htmlgraph/repo_hash.py +512 -0
  228. htmlgraph/repositories/__init__.py +292 -0
  229. htmlgraph/repositories/analytics_repository.py +455 -0
  230. htmlgraph/repositories/analytics_repository_standard.py +628 -0
  231. htmlgraph/repositories/feature_repository.py +581 -0
  232. htmlgraph/repositories/feature_repository_htmlfile.py +668 -0
  233. htmlgraph/repositories/feature_repository_memory.py +607 -0
  234. htmlgraph/repositories/feature_repository_sqlite.py +858 -0
  235. htmlgraph/repositories/filter_service.py +620 -0
  236. htmlgraph/repositories/filter_service_standard.py +445 -0
  237. htmlgraph/repositories/shared_cache.py +621 -0
  238. htmlgraph/repositories/shared_cache_memory.py +395 -0
  239. htmlgraph/repositories/track_repository.py +552 -0
  240. htmlgraph/repositories/track_repository_htmlfile.py +619 -0
  241. htmlgraph/repositories/track_repository_memory.py +508 -0
  242. htmlgraph/repositories/track_repository_sqlite.py +711 -0
  243. htmlgraph/sdk/__init__.py +398 -0
  244. htmlgraph/sdk/__init__.pyi +14 -0
  245. htmlgraph/sdk/analytics/__init__.py +19 -0
  246. htmlgraph/sdk/analytics/engine.py +155 -0
  247. htmlgraph/sdk/analytics/helpers.py +178 -0
  248. htmlgraph/sdk/analytics/registry.py +109 -0
  249. htmlgraph/sdk/base.py +484 -0
  250. htmlgraph/sdk/constants.py +216 -0
  251. htmlgraph/sdk/core.pyi +308 -0
  252. htmlgraph/sdk/discovery.py +120 -0
  253. htmlgraph/sdk/help/__init__.py +12 -0
  254. htmlgraph/sdk/help/mixin.py +699 -0
  255. htmlgraph/sdk/mixins/__init__.py +15 -0
  256. htmlgraph/sdk/mixins/attribution.py +113 -0
  257. htmlgraph/sdk/mixins/mixin.py +410 -0
  258. htmlgraph/sdk/operations/__init__.py +12 -0
  259. htmlgraph/sdk/operations/mixin.py +427 -0
  260. htmlgraph/sdk/orchestration/__init__.py +17 -0
  261. htmlgraph/sdk/orchestration/coordinator.py +203 -0
  262. htmlgraph/sdk/orchestration/spawner.py +204 -0
  263. htmlgraph/sdk/planning/__init__.py +19 -0
  264. htmlgraph/sdk/planning/bottlenecks.py +93 -0
  265. htmlgraph/sdk/planning/mixin.py +211 -0
  266. htmlgraph/sdk/planning/parallel.py +186 -0
  267. htmlgraph/sdk/planning/queue.py +210 -0
  268. htmlgraph/sdk/planning/recommendations.py +87 -0
  269. htmlgraph/sdk/planning/smart_planning.py +319 -0
  270. htmlgraph/sdk/session/__init__.py +19 -0
  271. htmlgraph/sdk/session/continuity.py +57 -0
  272. htmlgraph/sdk/session/handoff.py +110 -0
  273. htmlgraph/sdk/session/info.py +309 -0
  274. htmlgraph/sdk/session/manager.py +103 -0
  275. htmlgraph/sdk/strategic/__init__.py +26 -0
  276. htmlgraph/sdk/strategic/mixin.py +563 -0
  277. htmlgraph/server.py +295 -107
  278. htmlgraph/session_hooks.py +300 -0
  279. htmlgraph/session_manager.py +285 -3
  280. htmlgraph/session_registry.py +587 -0
  281. htmlgraph/session_state.py +436 -0
  282. htmlgraph/session_warning.py +2 -1
  283. htmlgraph/sessions/__init__.py +23 -0
  284. htmlgraph/sessions/handoff.py +756 -0
  285. htmlgraph/system_prompts.py +450 -0
  286. htmlgraph/templates/orchestration-view.html +350 -0
  287. htmlgraph/track_builder.py +33 -1
  288. htmlgraph/track_manager.py +38 -0
  289. htmlgraph/transcript.py +18 -5
  290. htmlgraph/validation.py +115 -0
  291. htmlgraph/watch.py +2 -1
  292. htmlgraph/work_type_utils.py +2 -1
  293. {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/dashboard.html +2246 -248
  294. {htmlgraph-0.20.1.dist-info → htmlgraph-0.27.5.dist-info}/METADATA +95 -64
  295. htmlgraph-0.27.5.dist-info/RECORD +337 -0
  296. {htmlgraph-0.20.1.dist-info → htmlgraph-0.27.5.dist-info}/entry_points.txt +1 -1
  297. htmlgraph/cli.py +0 -4839
  298. htmlgraph/sdk.py +0 -2359
  299. htmlgraph-0.20.1.dist-info/RECORD +0 -118
  300. {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/styles.css +0 -0
  301. {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/templates/AGENTS.md.template +0 -0
  302. {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/templates/CLAUDE.md.template +0 -0
  303. {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/templates/GEMINI.md.template +0 -0
  304. {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
+ )