htmlgraph 0.9.3__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 (331) 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 +173 -17
  5. htmlgraph/__init__.pyi +123 -0
  6. htmlgraph/agent_detection.py +127 -0
  7. htmlgraph/agent_registry.py +45 -30
  8. htmlgraph/agents.py +160 -107
  9. htmlgraph/analytics/__init__.py +9 -2
  10. htmlgraph/analytics/cli.py +190 -51
  11. htmlgraph/analytics/cost_analyzer.py +391 -0
  12. htmlgraph/analytics/cost_monitor.py +664 -0
  13. htmlgraph/analytics/cost_reporter.py +675 -0
  14. htmlgraph/analytics/cross_session.py +617 -0
  15. htmlgraph/analytics/dependency.py +192 -100
  16. htmlgraph/analytics/pattern_learning.py +771 -0
  17. htmlgraph/analytics/session_graph.py +707 -0
  18. htmlgraph/analytics/strategic/__init__.py +80 -0
  19. htmlgraph/analytics/strategic/cost_optimizer.py +611 -0
  20. htmlgraph/analytics/strategic/pattern_detector.py +876 -0
  21. htmlgraph/analytics/strategic/preference_manager.py +709 -0
  22. htmlgraph/analytics/strategic/suggestion_engine.py +747 -0
  23. htmlgraph/analytics/work_type.py +190 -14
  24. htmlgraph/analytics_index.py +135 -51
  25. htmlgraph/api/__init__.py +3 -0
  26. htmlgraph/api/cost_alerts_websocket.py +416 -0
  27. htmlgraph/api/main.py +2498 -0
  28. htmlgraph/api/static/htmx.min.js +1 -0
  29. htmlgraph/api/static/style-redesign.css +1344 -0
  30. htmlgraph/api/static/style.css +1079 -0
  31. htmlgraph/api/templates/dashboard-redesign.html +1366 -0
  32. htmlgraph/api/templates/dashboard.html +794 -0
  33. htmlgraph/api/templates/partials/activity-feed-hierarchical.html +326 -0
  34. htmlgraph/api/templates/partials/activity-feed.html +1100 -0
  35. htmlgraph/api/templates/partials/agents-redesign.html +317 -0
  36. htmlgraph/api/templates/partials/agents.html +317 -0
  37. htmlgraph/api/templates/partials/event-traces.html +373 -0
  38. htmlgraph/api/templates/partials/features-kanban-redesign.html +509 -0
  39. htmlgraph/api/templates/partials/features.html +578 -0
  40. htmlgraph/api/templates/partials/metrics-redesign.html +346 -0
  41. htmlgraph/api/templates/partials/metrics.html +346 -0
  42. htmlgraph/api/templates/partials/orchestration-redesign.html +443 -0
  43. htmlgraph/api/templates/partials/orchestration.html +198 -0
  44. htmlgraph/api/templates/partials/spawners.html +375 -0
  45. htmlgraph/api/templates/partials/work-items.html +613 -0
  46. htmlgraph/api/websocket.py +538 -0
  47. htmlgraph/archive/__init__.py +24 -0
  48. htmlgraph/archive/bloom.py +234 -0
  49. htmlgraph/archive/fts.py +297 -0
  50. htmlgraph/archive/manager.py +583 -0
  51. htmlgraph/archive/search.py +244 -0
  52. htmlgraph/atomic_ops.py +560 -0
  53. htmlgraph/attribute_index.py +208 -0
  54. htmlgraph/bounded_paths.py +539 -0
  55. htmlgraph/builders/__init__.py +14 -0
  56. htmlgraph/builders/base.py +118 -29
  57. htmlgraph/builders/bug.py +150 -0
  58. htmlgraph/builders/chore.py +119 -0
  59. htmlgraph/builders/epic.py +150 -0
  60. htmlgraph/builders/feature.py +31 -6
  61. htmlgraph/builders/insight.py +195 -0
  62. htmlgraph/builders/metric.py +217 -0
  63. htmlgraph/builders/pattern.py +202 -0
  64. htmlgraph/builders/phase.py +162 -0
  65. htmlgraph/builders/spike.py +52 -19
  66. htmlgraph/builders/track.py +148 -72
  67. htmlgraph/cigs/__init__.py +81 -0
  68. htmlgraph/cigs/autonomy.py +385 -0
  69. htmlgraph/cigs/cost.py +475 -0
  70. htmlgraph/cigs/messages_basic.py +472 -0
  71. htmlgraph/cigs/messaging.py +365 -0
  72. htmlgraph/cigs/models.py +771 -0
  73. htmlgraph/cigs/pattern_storage.py +427 -0
  74. htmlgraph/cigs/patterns.py +503 -0
  75. htmlgraph/cigs/posttool_analyzer.py +234 -0
  76. htmlgraph/cigs/reporter.py +818 -0
  77. htmlgraph/cigs/tracker.py +317 -0
  78. htmlgraph/cli/.htmlgraph/.session-warning-state.json +6 -0
  79. htmlgraph/cli/.htmlgraph/agents.json +72 -0
  80. htmlgraph/cli/.htmlgraph/htmlgraph.db +0 -0
  81. htmlgraph/cli/__init__.py +42 -0
  82. htmlgraph/cli/__main__.py +6 -0
  83. htmlgraph/cli/analytics.py +1424 -0
  84. htmlgraph/cli/base.py +685 -0
  85. htmlgraph/cli/constants.py +206 -0
  86. htmlgraph/cli/core.py +954 -0
  87. htmlgraph/cli/main.py +147 -0
  88. htmlgraph/cli/models.py +475 -0
  89. htmlgraph/cli/templates/__init__.py +1 -0
  90. htmlgraph/cli/templates/cost_dashboard.py +399 -0
  91. htmlgraph/cli/work/__init__.py +239 -0
  92. htmlgraph/cli/work/browse.py +115 -0
  93. htmlgraph/cli/work/features.py +568 -0
  94. htmlgraph/cli/work/orchestration.py +676 -0
  95. htmlgraph/cli/work/report.py +728 -0
  96. htmlgraph/cli/work/sessions.py +466 -0
  97. htmlgraph/cli/work/snapshot.py +559 -0
  98. htmlgraph/cli/work/tracks.py +486 -0
  99. htmlgraph/cli_commands/__init__.py +1 -0
  100. htmlgraph/cli_commands/feature.py +195 -0
  101. htmlgraph/cli_framework.py +115 -0
  102. htmlgraph/collections/__init__.py +18 -0
  103. htmlgraph/collections/base.py +415 -98
  104. htmlgraph/collections/bug.py +53 -0
  105. htmlgraph/collections/chore.py +53 -0
  106. htmlgraph/collections/epic.py +53 -0
  107. htmlgraph/collections/feature.py +12 -26
  108. htmlgraph/collections/insight.py +100 -0
  109. htmlgraph/collections/metric.py +92 -0
  110. htmlgraph/collections/pattern.py +97 -0
  111. htmlgraph/collections/phase.py +53 -0
  112. htmlgraph/collections/session.py +194 -0
  113. htmlgraph/collections/spike.py +56 -16
  114. htmlgraph/collections/task_delegation.py +241 -0
  115. htmlgraph/collections/todo.py +511 -0
  116. htmlgraph/collections/traces.py +487 -0
  117. htmlgraph/config/cost_models.json +56 -0
  118. htmlgraph/config.py +190 -0
  119. htmlgraph/context_analytics.py +344 -0
  120. htmlgraph/converter.py +216 -28
  121. htmlgraph/cost_analysis/__init__.py +5 -0
  122. htmlgraph/cost_analysis/analyzer.py +438 -0
  123. htmlgraph/dashboard.html +2406 -307
  124. htmlgraph/dashboard.html.backup +6592 -0
  125. htmlgraph/dashboard.html.bak +7181 -0
  126. htmlgraph/dashboard.html.bak2 +7231 -0
  127. htmlgraph/dashboard.html.bak3 +7232 -0
  128. htmlgraph/db/__init__.py +38 -0
  129. htmlgraph/db/queries.py +790 -0
  130. htmlgraph/db/schema.py +1788 -0
  131. htmlgraph/decorators.py +317 -0
  132. htmlgraph/dependency_models.py +19 -2
  133. htmlgraph/deploy.py +142 -125
  134. htmlgraph/deployment_models.py +474 -0
  135. htmlgraph/docs/API_REFERENCE.md +841 -0
  136. htmlgraph/docs/HTTP_API.md +750 -0
  137. htmlgraph/docs/INTEGRATION_GUIDE.md +752 -0
  138. htmlgraph/docs/ORCHESTRATION_PATTERNS.md +717 -0
  139. htmlgraph/docs/README.md +532 -0
  140. htmlgraph/docs/__init__.py +77 -0
  141. htmlgraph/docs/docs_version.py +55 -0
  142. htmlgraph/docs/metadata.py +93 -0
  143. htmlgraph/docs/migrations.py +232 -0
  144. htmlgraph/docs/template_engine.py +143 -0
  145. htmlgraph/docs/templates/_sections/cli_reference.md.j2 +52 -0
  146. htmlgraph/docs/templates/_sections/core_concepts.md.j2 +29 -0
  147. htmlgraph/docs/templates/_sections/sdk_basics.md.j2 +69 -0
  148. htmlgraph/docs/templates/base_agents.md.j2 +78 -0
  149. htmlgraph/docs/templates/example_user_override.md.j2 +47 -0
  150. htmlgraph/docs/version_check.py +163 -0
  151. htmlgraph/edge_index.py +182 -27
  152. htmlgraph/error_handler.py +544 -0
  153. htmlgraph/event_log.py +100 -52
  154. htmlgraph/event_migration.py +13 -4
  155. htmlgraph/exceptions.py +49 -0
  156. htmlgraph/file_watcher.py +101 -28
  157. htmlgraph/find_api.py +75 -63
  158. htmlgraph/git_events.py +145 -63
  159. htmlgraph/graph.py +1122 -106
  160. htmlgraph/hooks/.htmlgraph/.session-warning-state.json +6 -0
  161. htmlgraph/hooks/.htmlgraph/agents.json +72 -0
  162. htmlgraph/hooks/.htmlgraph/index.sqlite +0 -0
  163. htmlgraph/hooks/__init__.py +45 -0
  164. htmlgraph/hooks/bootstrap.py +169 -0
  165. htmlgraph/hooks/cigs_pretool_enforcer.py +354 -0
  166. htmlgraph/hooks/concurrent_sessions.py +208 -0
  167. htmlgraph/hooks/context.py +350 -0
  168. htmlgraph/hooks/drift_handler.py +525 -0
  169. htmlgraph/hooks/event_tracker.py +1314 -0
  170. htmlgraph/hooks/git_commands.py +175 -0
  171. htmlgraph/hooks/hooks-config.example.json +12 -0
  172. htmlgraph/hooks/installer.py +343 -0
  173. htmlgraph/hooks/orchestrator.py +674 -0
  174. htmlgraph/hooks/orchestrator_reflector.py +223 -0
  175. htmlgraph/hooks/post-checkout.sh +28 -0
  176. htmlgraph/hooks/post-commit.sh +24 -0
  177. htmlgraph/hooks/post-merge.sh +26 -0
  178. htmlgraph/hooks/post_tool_use_failure.py +273 -0
  179. htmlgraph/hooks/post_tool_use_handler.py +257 -0
  180. htmlgraph/hooks/posttooluse.py +408 -0
  181. htmlgraph/hooks/pre-commit.sh +94 -0
  182. htmlgraph/hooks/pre-push.sh +28 -0
  183. htmlgraph/hooks/pretooluse.py +819 -0
  184. htmlgraph/hooks/prompt_analyzer.py +637 -0
  185. htmlgraph/hooks/session_handler.py +668 -0
  186. htmlgraph/hooks/session_summary.py +395 -0
  187. htmlgraph/hooks/state_manager.py +504 -0
  188. htmlgraph/hooks/subagent_detection.py +202 -0
  189. htmlgraph/hooks/subagent_stop.py +369 -0
  190. htmlgraph/hooks/task_enforcer.py +255 -0
  191. htmlgraph/hooks/task_validator.py +177 -0
  192. htmlgraph/hooks/validator.py +628 -0
  193. htmlgraph/ids.py +41 -27
  194. htmlgraph/index.d.ts +286 -0
  195. htmlgraph/learning.py +767 -0
  196. htmlgraph/mcp_server.py +69 -23
  197. htmlgraph/models.py +1586 -87
  198. htmlgraph/operations/README.md +62 -0
  199. htmlgraph/operations/__init__.py +79 -0
  200. htmlgraph/operations/analytics.py +339 -0
  201. htmlgraph/operations/bootstrap.py +289 -0
  202. htmlgraph/operations/events.py +244 -0
  203. htmlgraph/operations/fastapi_server.py +231 -0
  204. htmlgraph/operations/hooks.py +350 -0
  205. htmlgraph/operations/initialization.py +597 -0
  206. htmlgraph/operations/initialization.py.backup +228 -0
  207. htmlgraph/operations/server.py +303 -0
  208. htmlgraph/orchestration/__init__.py +58 -0
  209. htmlgraph/orchestration/claude_launcher.py +179 -0
  210. htmlgraph/orchestration/command_builder.py +72 -0
  211. htmlgraph/orchestration/headless_spawner.py +281 -0
  212. htmlgraph/orchestration/live_events.py +377 -0
  213. htmlgraph/orchestration/model_selection.py +327 -0
  214. htmlgraph/orchestration/plugin_manager.py +140 -0
  215. htmlgraph/orchestration/prompts.py +137 -0
  216. htmlgraph/orchestration/spawner_event_tracker.py +383 -0
  217. htmlgraph/orchestration/spawners/__init__.py +16 -0
  218. htmlgraph/orchestration/spawners/base.py +194 -0
  219. htmlgraph/orchestration/spawners/claude.py +173 -0
  220. htmlgraph/orchestration/spawners/codex.py +435 -0
  221. htmlgraph/orchestration/spawners/copilot.py +294 -0
  222. htmlgraph/orchestration/spawners/gemini.py +471 -0
  223. htmlgraph/orchestration/subprocess_runner.py +36 -0
  224. htmlgraph/orchestration/task_coordination.py +343 -0
  225. htmlgraph/orchestration.md +563 -0
  226. htmlgraph/orchestrator-system-prompt-optimized.txt +863 -0
  227. htmlgraph/orchestrator.py +669 -0
  228. htmlgraph/orchestrator_config.py +357 -0
  229. htmlgraph/orchestrator_mode.py +328 -0
  230. htmlgraph/orchestrator_validator.py +133 -0
  231. htmlgraph/parallel.py +646 -0
  232. htmlgraph/parser.py +160 -35
  233. htmlgraph/path_query.py +608 -0
  234. htmlgraph/pattern_matcher.py +636 -0
  235. htmlgraph/planning.py +147 -52
  236. htmlgraph/pydantic_models.py +476 -0
  237. htmlgraph/quality_gates.py +350 -0
  238. htmlgraph/query_builder.py +109 -72
  239. htmlgraph/query_composer.py +509 -0
  240. htmlgraph/reflection.py +443 -0
  241. htmlgraph/refs.py +344 -0
  242. htmlgraph/repo_hash.py +512 -0
  243. htmlgraph/repositories/__init__.py +292 -0
  244. htmlgraph/repositories/analytics_repository.py +455 -0
  245. htmlgraph/repositories/analytics_repository_standard.py +628 -0
  246. htmlgraph/repositories/feature_repository.py +581 -0
  247. htmlgraph/repositories/feature_repository_htmlfile.py +668 -0
  248. htmlgraph/repositories/feature_repository_memory.py +607 -0
  249. htmlgraph/repositories/feature_repository_sqlite.py +858 -0
  250. htmlgraph/repositories/filter_service.py +620 -0
  251. htmlgraph/repositories/filter_service_standard.py +445 -0
  252. htmlgraph/repositories/shared_cache.py +621 -0
  253. htmlgraph/repositories/shared_cache_memory.py +395 -0
  254. htmlgraph/repositories/track_repository.py +552 -0
  255. htmlgraph/repositories/track_repository_htmlfile.py +619 -0
  256. htmlgraph/repositories/track_repository_memory.py +508 -0
  257. htmlgraph/repositories/track_repository_sqlite.py +711 -0
  258. htmlgraph/routing.py +8 -19
  259. htmlgraph/scripts/deploy.py +1 -2
  260. htmlgraph/sdk/__init__.py +398 -0
  261. htmlgraph/sdk/__init__.pyi +14 -0
  262. htmlgraph/sdk/analytics/__init__.py +19 -0
  263. htmlgraph/sdk/analytics/engine.py +155 -0
  264. htmlgraph/sdk/analytics/helpers.py +178 -0
  265. htmlgraph/sdk/analytics/registry.py +109 -0
  266. htmlgraph/sdk/base.py +484 -0
  267. htmlgraph/sdk/constants.py +216 -0
  268. htmlgraph/sdk/core.pyi +308 -0
  269. htmlgraph/sdk/discovery.py +120 -0
  270. htmlgraph/sdk/help/__init__.py +12 -0
  271. htmlgraph/sdk/help/mixin.py +699 -0
  272. htmlgraph/sdk/mixins/__init__.py +15 -0
  273. htmlgraph/sdk/mixins/attribution.py +113 -0
  274. htmlgraph/sdk/mixins/mixin.py +410 -0
  275. htmlgraph/sdk/operations/__init__.py +12 -0
  276. htmlgraph/sdk/operations/mixin.py +427 -0
  277. htmlgraph/sdk/orchestration/__init__.py +17 -0
  278. htmlgraph/sdk/orchestration/coordinator.py +203 -0
  279. htmlgraph/sdk/orchestration/spawner.py +204 -0
  280. htmlgraph/sdk/planning/__init__.py +19 -0
  281. htmlgraph/sdk/planning/bottlenecks.py +93 -0
  282. htmlgraph/sdk/planning/mixin.py +211 -0
  283. htmlgraph/sdk/planning/parallel.py +186 -0
  284. htmlgraph/sdk/planning/queue.py +210 -0
  285. htmlgraph/sdk/planning/recommendations.py +87 -0
  286. htmlgraph/sdk/planning/smart_planning.py +319 -0
  287. htmlgraph/sdk/session/__init__.py +19 -0
  288. htmlgraph/sdk/session/continuity.py +57 -0
  289. htmlgraph/sdk/session/handoff.py +110 -0
  290. htmlgraph/sdk/session/info.py +309 -0
  291. htmlgraph/sdk/session/manager.py +103 -0
  292. htmlgraph/sdk/strategic/__init__.py +26 -0
  293. htmlgraph/sdk/strategic/mixin.py +563 -0
  294. htmlgraph/server.py +685 -180
  295. htmlgraph/services/__init__.py +10 -0
  296. htmlgraph/services/claiming.py +199 -0
  297. htmlgraph/session_hooks.py +300 -0
  298. htmlgraph/session_manager.py +1392 -175
  299. htmlgraph/session_registry.py +587 -0
  300. htmlgraph/session_state.py +436 -0
  301. htmlgraph/session_warning.py +201 -0
  302. htmlgraph/sessions/__init__.py +23 -0
  303. htmlgraph/sessions/handoff.py +756 -0
  304. htmlgraph/setup.py +34 -17
  305. htmlgraph/spike_index.py +143 -0
  306. htmlgraph/sync_docs.py +12 -15
  307. htmlgraph/system_prompts.py +450 -0
  308. htmlgraph/templates/AGENTS.md.template +366 -0
  309. htmlgraph/templates/CLAUDE.md.template +97 -0
  310. htmlgraph/templates/GEMINI.md.template +87 -0
  311. htmlgraph/templates/orchestration-view.html +350 -0
  312. htmlgraph/track_builder.py +146 -15
  313. htmlgraph/track_manager.py +69 -21
  314. htmlgraph/transcript.py +890 -0
  315. htmlgraph/transcript_analytics.py +699 -0
  316. htmlgraph/types.py +323 -0
  317. htmlgraph/validation.py +115 -0
  318. htmlgraph/watch.py +8 -5
  319. htmlgraph/work_type_utils.py +3 -2
  320. {htmlgraph-0.9.3.data → htmlgraph-0.27.5.data}/data/htmlgraph/dashboard.html +2406 -307
  321. htmlgraph-0.27.5.data/data/htmlgraph/templates/AGENTS.md.template +366 -0
  322. htmlgraph-0.27.5.data/data/htmlgraph/templates/CLAUDE.md.template +97 -0
  323. htmlgraph-0.27.5.data/data/htmlgraph/templates/GEMINI.md.template +87 -0
  324. {htmlgraph-0.9.3.dist-info → htmlgraph-0.27.5.dist-info}/METADATA +97 -64
  325. htmlgraph-0.27.5.dist-info/RECORD +337 -0
  326. {htmlgraph-0.9.3.dist-info → htmlgraph-0.27.5.dist-info}/entry_points.txt +1 -1
  327. htmlgraph/cli.py +0 -2688
  328. htmlgraph/sdk.py +0 -709
  329. htmlgraph-0.9.3.dist-info/RECORD +0 -61
  330. {htmlgraph-0.9.3.data → htmlgraph-0.27.5.data}/data/htmlgraph/styles.css +0 -0
  331. {htmlgraph-0.9.3.dist-info → htmlgraph-0.27.5.dist-info}/WHEEL +0 -0
@@ -0,0 +1,471 @@
1
+ """Gemini spawner implementation."""
2
+
3
+ import json
4
+ import logging
5
+ import subprocess
6
+ import time
7
+ from typing import TYPE_CHECKING, Any
8
+
9
+ from .base import AIResult, BaseSpawner
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+ if TYPE_CHECKING:
14
+ from htmlgraph.sdk import SDK
15
+
16
+
17
+ class GeminiSpawner(BaseSpawner):
18
+ """Spawner for Google Gemini CLI.
19
+
20
+ Model Selection:
21
+ The `model` parameter defaults to None, which is the RECOMMENDED approach.
22
+ When model=None, the Gemini CLI automatically selects the best available model
23
+ based on the task and current availability.
24
+
25
+ As of Gemini CLI v0.22+, the default models include:
26
+ - gemini-2.5-flash-lite: Fast, efficient model for most tasks
27
+ - gemini-3-flash-preview: Preview of Gemini 3 with enhanced capabilities
28
+
29
+ Explicitly specifying a model is DISCOURAGED because:
30
+ 1. Older models (gemini-2.0-flash, gemini-1.5-flash) may fail due to
31
+ "thinking mode" incompatibility in newer CLI versions
32
+ 2. Using None automatically benefits from Google's model updates
33
+ 3. The CLI handles model selection and fallback logic
34
+
35
+ Supported models (if you must specify):
36
+ - None (recommended): CLI chooses best available model
37
+ - "gemini-2.5-flash-lite": Fast, efficient
38
+ - "gemini-3-flash-preview": Gemini 3 preview (enhanced capabilities)
39
+ - "gemini-2.5-pro": More capable, slower
40
+
41
+ DEPRECATED models (may cause errors):
42
+ - "gemini-2.0-flash": Deprecated, use None instead
43
+ - "gemini-1.5-flash": Deprecated, use None instead
44
+ - "gemini-1.5-pro": Deprecated, use None instead
45
+ """
46
+
47
+ def _parse_and_track_events(self, jsonl_output: str, sdk: "SDK") -> list[dict]:
48
+ """
49
+ Parse Gemini stream-json events and track in HtmlGraph.
50
+
51
+ Args:
52
+ jsonl_output: JSONL output from Gemini CLI
53
+ sdk: HtmlGraph SDK instance for tracking
54
+
55
+ Returns:
56
+ Parsed events list
57
+ """
58
+ events = []
59
+
60
+ for line in jsonl_output.splitlines():
61
+ if not line.strip():
62
+ continue
63
+
64
+ try:
65
+ event = json.loads(line)
66
+ events.append(event)
67
+
68
+ # Track based on event type
69
+ event_type = event.get("type")
70
+
71
+ if event_type == "tool_use":
72
+ tool_name = event.get("tool_name", "unknown_tool")
73
+ parameters = event.get("parameters", {})
74
+ self._track_activity(
75
+ sdk,
76
+ tool="gemini_tool_call",
77
+ summary=f"Gemini called {tool_name}",
78
+ payload={
79
+ "tool_name": tool_name,
80
+ "parameters": parameters,
81
+ },
82
+ )
83
+
84
+ elif event_type == "tool_result":
85
+ status = event.get("status", "unknown")
86
+ success = status == "success"
87
+ tool_id = event.get("tool_id", "unknown")
88
+ self._track_activity(
89
+ sdk,
90
+ tool="gemini_tool_result",
91
+ summary=f"Gemini tool result: {status}",
92
+ success=success,
93
+ payload={"tool_id": tool_id, "status": status},
94
+ )
95
+
96
+ elif event_type == "message":
97
+ role = event.get("role")
98
+ if role == "assistant":
99
+ content = event.get("content", "")
100
+ # Truncate for summary
101
+ summary = (
102
+ content[:100] + "..." if len(content) > 100 else content
103
+ )
104
+ self._track_activity(
105
+ sdk,
106
+ tool="gemini_message",
107
+ summary=f"Gemini: {summary}",
108
+ payload={"role": role, "content_length": len(content)},
109
+ )
110
+
111
+ elif event_type == "result":
112
+ stats = event.get("stats", {})
113
+ self._track_activity(
114
+ sdk,
115
+ tool="gemini_completion",
116
+ summary="Gemini task completed",
117
+ payload={"stats": stats},
118
+ )
119
+
120
+ except json.JSONDecodeError:
121
+ # Skip malformed lines
122
+ continue
123
+
124
+ return events
125
+
126
+ def spawn(
127
+ self,
128
+ prompt: str,
129
+ output_format: str = "stream-json",
130
+ model: str | None = None,
131
+ include_directories: list[str] | None = None,
132
+ track_in_htmlgraph: bool = True,
133
+ timeout: int = 120,
134
+ tracker: Any = None,
135
+ parent_event_id: str | None = None,
136
+ ) -> AIResult:
137
+ """
138
+ Spawn Gemini in headless mode.
139
+
140
+ Args:
141
+ prompt: Task description for Gemini
142
+ output_format: "json" or "stream-json" (enables real-time tracking)
143
+ model: Model selection. Default: None (RECOMMENDED).
144
+
145
+ When model=None (default), the Gemini CLI automatically selects
146
+ the best available model, which includes:
147
+ - gemini-2.5-flash-lite: Fast, efficient model
148
+ - gemini-3-flash-preview: Gemini 3 with enhanced capabilities
149
+
150
+ Using None is STRONGLY RECOMMENDED because:
151
+ 1. Automatically benefits from Google's latest models
152
+ 2. Avoids deprecation issues with older model names
153
+ 3. CLI handles optimal model selection and fallback
154
+
155
+ DEPRECATED models (may cause errors with CLI v0.22+):
156
+ - gemini-2.0-flash, gemini-1.5-flash, gemini-1.5-pro
157
+
158
+ include_directories: Directories to include for context. Default: None
159
+ track_in_htmlgraph: Enable HtmlGraph activity tracking. Default: True
160
+ timeout: Max seconds to wait
161
+ tracker: Optional SpawnerEventTracker for recording subprocess invocation
162
+ parent_event_id: Optional parent event ID for event hierarchy
163
+
164
+ Returns:
165
+ AIResult with response, error, and tracked events if tracking enabled
166
+
167
+ Example:
168
+ >>> spawner = GeminiSpawner()
169
+ >>> result = spawner.spawn(
170
+ ... prompt="Analyze this codebase",
171
+ ... # model=None is the default - uses latest Gemini models
172
+ ... track_in_htmlgraph=True
173
+ ... )
174
+ """
175
+ # Initialize tracking if enabled
176
+ sdk: SDK | None = None
177
+ tracked_events: list[dict] = []
178
+ if track_in_htmlgraph:
179
+ sdk = self._get_sdk()
180
+
181
+ # Publish live event: spawner starting
182
+ self._publish_live_event(
183
+ "spawner_start",
184
+ "gemini",
185
+ prompt=prompt,
186
+ model=model,
187
+ )
188
+ start_time = time.time()
189
+
190
+ try:
191
+ # Build command based on tested pattern from spike spk-4029eef3
192
+ cmd = ["gemini", "-p", prompt, "--output-format", output_format]
193
+
194
+ # Add model option if specified
195
+ if model:
196
+ cmd.extend(["-m", model])
197
+
198
+ # Add include directories if specified
199
+ if include_directories:
200
+ for directory in include_directories:
201
+ cmd.extend(["--include-directories", directory])
202
+
203
+ # CRITICAL: Add --yolo for headless mode (auto-approve all tools)
204
+ cmd.append("--yolo")
205
+
206
+ # Track spawner start if SDK available
207
+ if sdk:
208
+ self._track_activity(
209
+ sdk,
210
+ tool="gemini_spawn_start",
211
+ summary=f"Spawning Gemini: {prompt[:80]}",
212
+ payload={"prompt_length": len(prompt), "model": model},
213
+ )
214
+
215
+ # Publish live event: executing
216
+ self._publish_live_event(
217
+ "spawner_phase",
218
+ "gemini",
219
+ phase="executing",
220
+ details="Running Gemini CLI",
221
+ )
222
+
223
+ # Record subprocess invocation if tracker is available
224
+ subprocess_event_id = None
225
+ logger.warning(
226
+ f"DEBUG: tracker={tracker is not None}, parent_event_id={parent_event_id}"
227
+ )
228
+ if tracker and parent_event_id:
229
+ logger.debug("Recording subprocess invocation for Gemini...")
230
+ try:
231
+ subprocess_event = tracker.record_tool_call(
232
+ tool_name="subprocess.gemini",
233
+ tool_input={"cmd": cmd},
234
+ phase_event_id=parent_event_id,
235
+ spawned_agent=model or "gemini-default",
236
+ )
237
+ if subprocess_event:
238
+ subprocess_event_id = subprocess_event.get("event_id")
239
+ logger.warning(
240
+ f"DEBUG: Subprocess event created for Gemini: {subprocess_event_id}"
241
+ )
242
+ else:
243
+ logger.debug("subprocess_event was None")
244
+ except Exception as e:
245
+ # Tracking failure should not break execution
246
+ logger.warning(f"DEBUG: Exception recording Gemini subprocess: {e}")
247
+ pass
248
+ else:
249
+ logger.warning(
250
+ f"DEBUG: Skipping Gemini subprocess tracking - tracker={tracker is not None}, parent_event_id={parent_event_id}"
251
+ )
252
+
253
+ # Execute with timeout and stderr redirection
254
+ # Note: Cannot use capture_output with stderr parameter
255
+ result = subprocess.run(
256
+ cmd,
257
+ stdout=subprocess.PIPE,
258
+ stderr=subprocess.DEVNULL, # Redirect stderr to avoid polluting JSON
259
+ text=True,
260
+ timeout=timeout,
261
+ )
262
+
263
+ # Complete subprocess invocation tracking
264
+ if tracker and subprocess_event_id:
265
+ try:
266
+ tracker.complete_tool_call(
267
+ event_id=subprocess_event_id,
268
+ output_summary=result.stdout[:500] if result.stdout else "",
269
+ success=result.returncode == 0,
270
+ )
271
+ except Exception:
272
+ # Tracking failure should not break execution
273
+ pass
274
+
275
+ # Publish live event: processing response
276
+ self._publish_live_event(
277
+ "spawner_phase",
278
+ "gemini",
279
+ phase="processing",
280
+ details="Parsing Gemini response",
281
+ )
282
+
283
+ # Check for command execution errors
284
+ if result.returncode != 0:
285
+ duration = time.time() - start_time
286
+ self._publish_live_event(
287
+ "spawner_complete",
288
+ "gemini",
289
+ success=False,
290
+ duration=duration,
291
+ error=f"CLI failed with exit code {result.returncode}",
292
+ )
293
+ return AIResult(
294
+ success=False,
295
+ response="",
296
+ tokens_used=None,
297
+ error=f"Gemini CLI failed with exit code {result.returncode}",
298
+ raw_output=None,
299
+ tracked_events=tracked_events,
300
+ )
301
+
302
+ # Handle stream-json format with real-time tracking
303
+ if output_format == "stream-json" and sdk:
304
+ try:
305
+ tracked_events = self._parse_and_track_events(result.stdout, sdk)
306
+ # Only use stream-json parsing if we got valid events
307
+ if tracked_events:
308
+ # For stream-json, we need to extract response differently
309
+ # Collect all assistant message content, then check result
310
+ response_text = ""
311
+ for event in tracked_events:
312
+ if event.get("type") == "message":
313
+ # Only collect assistant messages
314
+ if event.get("role") == "assistant":
315
+ content = event.get("content", "")
316
+ if content:
317
+ response_text += content
318
+ elif event.get("type") == "result":
319
+ # Result event may have response field (override if present)
320
+ if "response" in event and event["response"]:
321
+ response_text = event["response"]
322
+ # Don't break - we've already collected messages
323
+
324
+ # Token usage from stats in result event
325
+ tokens = None
326
+ for event in tracked_events:
327
+ if event.get("type") == "result":
328
+ stats = event.get("stats", {})
329
+ if stats and "models" in stats:
330
+ total_tokens = 0
331
+ for model_stats in stats["models"].values():
332
+ model_tokens = model_stats.get(
333
+ "tokens", {}
334
+ ).get("total", 0)
335
+ total_tokens += model_tokens
336
+ tokens = total_tokens if total_tokens > 0 else None
337
+ break
338
+
339
+ # Publish live event: complete
340
+ duration = time.time() - start_time
341
+ self._publish_live_event(
342
+ "spawner_complete",
343
+ "gemini",
344
+ success=True,
345
+ duration=duration,
346
+ response=response_text,
347
+ tokens=tokens,
348
+ )
349
+ return AIResult(
350
+ success=True,
351
+ response=response_text,
352
+ tokens_used=tokens,
353
+ error=None,
354
+ raw_output={"events": tracked_events},
355
+ tracked_events=tracked_events,
356
+ )
357
+
358
+ except Exception:
359
+ # Fall back to regular JSON parsing if tracking fails
360
+ pass
361
+
362
+ # Parse JSON response (for json format or fallback)
363
+ try:
364
+ output = json.loads(result.stdout)
365
+ except json.JSONDecodeError as e:
366
+ duration = time.time() - start_time
367
+ self._publish_live_event(
368
+ "spawner_complete",
369
+ "gemini",
370
+ success=False,
371
+ duration=duration,
372
+ error=f"Failed to parse JSON: {e}",
373
+ )
374
+ return AIResult(
375
+ success=False,
376
+ response="",
377
+ tokens_used=None,
378
+ error=f"Failed to parse JSON output: {e}",
379
+ raw_output={"stdout": result.stdout},
380
+ tracked_events=tracked_events,
381
+ )
382
+
383
+ # Extract response and token usage from parsed output
384
+ # Response is at top level in JSON output
385
+ response_text = output.get("response", "")
386
+
387
+ # Token usage is in stats.models (sum across all models)
388
+ tokens = None
389
+ stats = output.get("stats", {})
390
+ if stats and "models" in stats:
391
+ total_tokens = 0
392
+ for model_stats in stats["models"].values():
393
+ model_tokens = model_stats.get("tokens", {}).get("total", 0)
394
+ total_tokens += model_tokens
395
+ tokens = total_tokens if total_tokens > 0 else None
396
+
397
+ # Publish live event: complete
398
+ duration = time.time() - start_time
399
+ self._publish_live_event(
400
+ "spawner_complete",
401
+ "gemini",
402
+ success=True,
403
+ duration=duration,
404
+ response=response_text,
405
+ tokens=tokens,
406
+ )
407
+ return AIResult(
408
+ success=True,
409
+ response=response_text,
410
+ tokens_used=tokens,
411
+ error=None,
412
+ raw_output=output,
413
+ tracked_events=tracked_events,
414
+ )
415
+
416
+ except subprocess.TimeoutExpired as e:
417
+ duration = time.time() - start_time
418
+ self._publish_live_event(
419
+ "spawner_complete",
420
+ "gemini",
421
+ success=False,
422
+ duration=duration,
423
+ error=f"Timed out after {timeout} seconds",
424
+ )
425
+ return AIResult(
426
+ success=False,
427
+ response="",
428
+ tokens_used=None,
429
+ error=f"Gemini CLI timed out after {timeout} seconds",
430
+ raw_output={
431
+ "partial_stdout": e.stdout.decode() if e.stdout else None,
432
+ "partial_stderr": e.stderr.decode() if e.stderr else None,
433
+ }
434
+ if e.stdout or e.stderr
435
+ else None,
436
+ tracked_events=tracked_events,
437
+ )
438
+ except FileNotFoundError:
439
+ duration = time.time() - start_time
440
+ self._publish_live_event(
441
+ "spawner_complete",
442
+ "gemini",
443
+ success=False,
444
+ duration=duration,
445
+ error="CLI not found",
446
+ )
447
+ return AIResult(
448
+ success=False,
449
+ response="",
450
+ tokens_used=None,
451
+ error="Gemini CLI not found. Ensure 'gemini' is installed and in PATH.",
452
+ raw_output=None,
453
+ tracked_events=tracked_events,
454
+ )
455
+ except Exception as e:
456
+ duration = time.time() - start_time
457
+ self._publish_live_event(
458
+ "spawner_complete",
459
+ "gemini",
460
+ success=False,
461
+ duration=duration,
462
+ error=str(e),
463
+ )
464
+ return AIResult(
465
+ success=False,
466
+ response="",
467
+ tokens_used=None,
468
+ error=f"Unexpected error: {type(e).__name__}: {e}",
469
+ raw_output=None,
470
+ tracked_events=tracked_events,
471
+ )
@@ -0,0 +1,36 @@
1
+ from __future__ import annotations
2
+
3
+ """Subprocess execution with standardized error handling.
4
+
5
+ Provides consistent error handling for Claude Code CLI invocations.
6
+ """
7
+
8
+ import logging
9
+ import subprocess
10
+ import sys
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ class SubprocessRunner:
16
+ """Execute subprocess commands with error handling."""
17
+
18
+ @staticmethod
19
+ def run_claude_command(cmd: list[str]) -> None:
20
+ """Execute Claude Code CLI command with error handling.
21
+
22
+ Args:
23
+ cmd: Command list (e.g., ["claude", "--resume"])
24
+
25
+ Raises:
26
+ SystemExit: If 'claude' command not found or other error
27
+ """
28
+ try:
29
+ subprocess.run(cmd, check=False)
30
+ except FileNotFoundError:
31
+ logger.warning("Error: 'claude' command not found.")
32
+ print(
33
+ "Please install Claude Code CLI: https://code.claude.com",
34
+ file=sys.stderr,
35
+ )
36
+ sys.exit(1)