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,435 @@
1
+ """Codex 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 CodexSpawner(BaseSpawner):
18
+ """Spawner for OpenAI Codex CLI."""
19
+
20
+ def _parse_and_track_events(self, jsonl_output: str, sdk: "SDK") -> list[dict]:
21
+ """
22
+ Parse Codex JSONL events and track in HtmlGraph.
23
+
24
+ Args:
25
+ jsonl_output: JSONL output from Codex CLI
26
+ sdk: HtmlGraph SDK instance for tracking
27
+
28
+ Returns:
29
+ Parsed events list
30
+ """
31
+ events = []
32
+ parse_errors = []
33
+
34
+ for line_num, line in enumerate(jsonl_output.splitlines(), start=1):
35
+ if not line.strip():
36
+ continue
37
+
38
+ try:
39
+ event = json.loads(line)
40
+ events.append(event)
41
+
42
+ event_type = event.get("type")
43
+
44
+ # Track item.started events
45
+ if event_type == "item.started":
46
+ item = event.get("item", {})
47
+ item_type = item.get("type")
48
+
49
+ if item_type == "command_execution":
50
+ command = item.get("command", "")
51
+ self._track_activity(
52
+ sdk,
53
+ tool="codex_command",
54
+ summary=f"Codex executing: {command[:80]}",
55
+ payload={"command": command},
56
+ )
57
+
58
+ # Track item.completed events
59
+ elif event_type == "item.completed":
60
+ item = event.get("item", {})
61
+ item_type = item.get("type")
62
+
63
+ if item_type == "file_change":
64
+ path = item.get("path", "unknown")
65
+ self._track_activity(
66
+ sdk,
67
+ tool="codex_file_change",
68
+ summary=f"Codex modified: {path}",
69
+ file_paths=[path],
70
+ payload={"path": path},
71
+ )
72
+
73
+ elif item_type == "agent_message":
74
+ text = item.get("text", "")
75
+ summary = text[:100] + "..." if len(text) > 100 else text
76
+ self._track_activity(
77
+ sdk,
78
+ tool="codex_message",
79
+ summary=f"Codex: {summary}",
80
+ payload={"text_length": len(text)},
81
+ )
82
+
83
+ # Track turn.completed for token usage
84
+ elif event_type == "turn.completed":
85
+ usage = event.get("usage", {})
86
+ total_tokens = sum(usage.values())
87
+ self._track_activity(
88
+ sdk,
89
+ tool="codex_completion",
90
+ summary=f"Codex turn completed ({total_tokens} tokens)",
91
+ payload={"usage": usage},
92
+ )
93
+
94
+ except json.JSONDecodeError as e:
95
+ parse_errors.append(
96
+ {
97
+ "line_number": line_num,
98
+ "error": str(e),
99
+ "content": line[:100],
100
+ }
101
+ )
102
+ continue
103
+
104
+ return events
105
+
106
+ def spawn(
107
+ self,
108
+ prompt: str,
109
+ output_json: bool = True,
110
+ model: str | None = None,
111
+ sandbox: str | None = None,
112
+ full_auto: bool = True,
113
+ images: list[str] | None = None,
114
+ output_last_message: str | None = None,
115
+ output_schema: str | None = None,
116
+ skip_git_check: bool = False,
117
+ working_directory: str | None = None,
118
+ use_oss: bool = False,
119
+ bypass_approvals: bool = False,
120
+ track_in_htmlgraph: bool = True,
121
+ timeout: int = 120,
122
+ tracker: Any = None,
123
+ parent_event_id: str | None = None,
124
+ ) -> AIResult:
125
+ """
126
+ Spawn Codex in headless mode.
127
+
128
+ Args:
129
+ prompt: Task description for Codex
130
+ output_json: JSONL output flag (enables real-time tracking)
131
+ model: Model selection (e.g., "gpt-4-turbo"). Default: None
132
+ sandbox: Sandbox mode ("read-only", "workspace-write", or full)
133
+ full_auto: Enable full auto mode. Default: True (required headless)
134
+ images: List of image paths (--image). Default: None
135
+ output_last_message: Write last message to file. Default: None
136
+ output_schema: JSON schema for validation. Default: None
137
+ skip_git_check: Skip git repo check. Default: False
138
+ working_directory: Workspace directory (--cd). Default: None
139
+ use_oss: Use local Ollama provider (--oss). Default: False
140
+ bypass_approvals: Bypass approval checks. Default: False
141
+ track_in_htmlgraph: Enable HtmlGraph activity tracking. Default: True
142
+ timeout: Max seconds to wait
143
+ tracker: Optional SpawnerEventTracker for recording subprocess invocation
144
+ parent_event_id: Optional parent event ID for event hierarchy
145
+
146
+ Returns:
147
+ AIResult with response, error, and tracked events if tracking enabled
148
+ """
149
+ # Initialize tracking if enabled
150
+ sdk: SDK | None = None
151
+ tracked_events: list[dict] = []
152
+ if track_in_htmlgraph and output_json:
153
+ sdk = self._get_sdk()
154
+
155
+ # Publish live event: spawner starting
156
+ self._publish_live_event(
157
+ "spawner_start",
158
+ "codex",
159
+ prompt=prompt,
160
+ model=model,
161
+ )
162
+ start_time = time.time()
163
+
164
+ cmd = ["codex", "exec"]
165
+
166
+ if output_json:
167
+ cmd.append("--json")
168
+
169
+ # Add model if specified
170
+ if model:
171
+ cmd.extend(["--model", model])
172
+
173
+ # Add sandbox mode if specified
174
+ if sandbox:
175
+ cmd.extend(["--sandbox", sandbox])
176
+
177
+ # Add full auto flag
178
+ if full_auto:
179
+ cmd.append("--full-auto")
180
+
181
+ # Add images
182
+ if images:
183
+ for image in images:
184
+ cmd.extend(["--image", image])
185
+
186
+ # Add output last message file if specified
187
+ if output_last_message:
188
+ cmd.extend(["--output-last-message", output_last_message])
189
+
190
+ # Add output schema if specified
191
+ if output_schema:
192
+ cmd.extend(["--output-schema", output_schema])
193
+
194
+ # Add skip git check flag
195
+ if skip_git_check:
196
+ cmd.append("--skip-git-repo-check")
197
+
198
+ # Add working directory if specified
199
+ if working_directory:
200
+ cmd.extend(["--cd", working_directory])
201
+
202
+ # Add OSS flag
203
+ if use_oss:
204
+ cmd.append("--oss")
205
+
206
+ # Add bypass approvals flag
207
+ if bypass_approvals:
208
+ cmd.append("--dangerously-bypass-approvals-and-sandbox")
209
+
210
+ # Add prompt as final argument
211
+ cmd.append(prompt)
212
+
213
+ # Track spawner start if SDK available
214
+ if sdk:
215
+ self._track_activity(
216
+ sdk,
217
+ tool="codex_spawn_start",
218
+ summary=f"Spawning Codex: {prompt[:80]}",
219
+ payload={
220
+ "prompt_length": len(prompt),
221
+ "model": model,
222
+ "sandbox": sandbox,
223
+ },
224
+ )
225
+
226
+ try:
227
+ # Publish live event: executing
228
+ self._publish_live_event(
229
+ "spawner_phase",
230
+ "codex",
231
+ phase="executing",
232
+ details="Running Codex CLI",
233
+ )
234
+
235
+ # Record subprocess invocation if tracker is available
236
+ subprocess_event_id = None
237
+ logger.warning(
238
+ f"DEBUG: tracker={tracker is not None}, parent_event_id={parent_event_id}"
239
+ )
240
+ if tracker and parent_event_id:
241
+ logger.debug("Recording subprocess invocation for Codex...")
242
+ try:
243
+ subprocess_event = tracker.record_tool_call(
244
+ tool_name="subprocess.codex",
245
+ tool_input={"cmd": cmd},
246
+ phase_event_id=parent_event_id,
247
+ spawned_agent="gpt-4",
248
+ )
249
+ if subprocess_event:
250
+ subprocess_event_id = subprocess_event.get("event_id")
251
+ logger.warning(
252
+ f"DEBUG: Subprocess event created for Codex: {subprocess_event_id}"
253
+ )
254
+ else:
255
+ logger.debug("subprocess_event was None")
256
+ except Exception as e:
257
+ # Tracking failure should not break execution
258
+ logger.warning(f"DEBUG: Exception recording Codex subprocess: {e}")
259
+ pass
260
+ else:
261
+ logger.warning(
262
+ f"DEBUG: Skipping Codex subprocess tracking - tracker={tracker is not None}, parent_event_id={parent_event_id}"
263
+ )
264
+
265
+ result = subprocess.run(
266
+ cmd,
267
+ stdout=subprocess.PIPE,
268
+ stderr=subprocess.DEVNULL,
269
+ text=True,
270
+ timeout=timeout,
271
+ )
272
+
273
+ # Complete subprocess invocation tracking
274
+ if tracker and subprocess_event_id:
275
+ try:
276
+ tracker.complete_tool_call(
277
+ event_id=subprocess_event_id,
278
+ output_summary=result.stdout[:500] if result.stdout else "",
279
+ success=result.returncode == 0,
280
+ )
281
+ except Exception:
282
+ # Tracking failure should not break execution
283
+ pass
284
+
285
+ # Publish live event: processing
286
+ self._publish_live_event(
287
+ "spawner_phase",
288
+ "codex",
289
+ phase="processing",
290
+ details="Parsing Codex response",
291
+ )
292
+
293
+ if not output_json:
294
+ # Plain text mode - return as-is
295
+ duration = time.time() - start_time
296
+ success = result.returncode == 0
297
+ self._publish_live_event(
298
+ "spawner_complete",
299
+ "codex",
300
+ success=success,
301
+ duration=duration,
302
+ response=result.stdout.strip()[:200] if success else None,
303
+ error="Command failed" if not success else None,
304
+ )
305
+ return AIResult(
306
+ success=success,
307
+ response=result.stdout.strip(),
308
+ tokens_used=None,
309
+ error=None if success else "Command failed",
310
+ raw_output=result.stdout,
311
+ tracked_events=tracked_events,
312
+ )
313
+
314
+ # Parse JSONL output
315
+ events = []
316
+ parse_errors = []
317
+
318
+ # Use tracking parser if SDK is available
319
+ if sdk:
320
+ tracked_events = self._parse_and_track_events(result.stdout, sdk)
321
+ events = tracked_events
322
+ else:
323
+ # Fallback to regular parsing without tracking
324
+ for line_num, line in enumerate(result.stdout.splitlines(), start=1):
325
+ if line.strip():
326
+ try:
327
+ events.append(json.loads(line))
328
+ except json.JSONDecodeError as e:
329
+ parse_errors.append(
330
+ {
331
+ "line_number": line_num,
332
+ "error": str(e),
333
+ "content": line[
334
+ :100
335
+ ], # First 100 chars for debugging
336
+ }
337
+ )
338
+ continue
339
+
340
+ # Extract agent message
341
+ response = None
342
+ for event in events:
343
+ if event.get("type") == "item.completed":
344
+ item = event.get("item", {})
345
+ if item.get("type") == "agent_message":
346
+ response = item.get("text")
347
+
348
+ # Extract token usage from turn.completed event
349
+ tokens = None
350
+ for event in events:
351
+ if event.get("type") == "turn.completed":
352
+ usage = event.get("usage", {})
353
+ # Sum all token types
354
+ tokens = sum(usage.values())
355
+
356
+ # Publish live event: complete
357
+ duration = time.time() - start_time
358
+ success = result.returncode == 0
359
+ self._publish_live_event(
360
+ "spawner_complete",
361
+ "codex",
362
+ success=success,
363
+ duration=duration,
364
+ response=response[:200] if response else None,
365
+ tokens=tokens,
366
+ error="Command failed" if not success else None,
367
+ )
368
+ return AIResult(
369
+ success=success,
370
+ response=response or "",
371
+ tokens_used=tokens,
372
+ error=None if success else "Command failed",
373
+ raw_output={
374
+ "events": events,
375
+ "parse_errors": parse_errors if parse_errors else None,
376
+ },
377
+ tracked_events=tracked_events,
378
+ )
379
+
380
+ except FileNotFoundError:
381
+ duration = time.time() - start_time
382
+ self._publish_live_event(
383
+ "spawner_complete",
384
+ "codex",
385
+ success=False,
386
+ duration=duration,
387
+ error="CLI not found",
388
+ )
389
+ return AIResult(
390
+ success=False,
391
+ response="",
392
+ tokens_used=None,
393
+ error="Codex CLI not found. Install from: https://github.com/openai/codex",
394
+ raw_output=None,
395
+ tracked_events=tracked_events,
396
+ )
397
+ except subprocess.TimeoutExpired as e:
398
+ duration = time.time() - start_time
399
+ self._publish_live_event(
400
+ "spawner_complete",
401
+ "codex",
402
+ success=False,
403
+ duration=duration,
404
+ error=f"Timed out after {timeout} seconds",
405
+ )
406
+ return AIResult(
407
+ success=False,
408
+ response="",
409
+ tokens_used=None,
410
+ error=f"Timed out after {timeout} seconds",
411
+ raw_output={
412
+ "partial_stdout": e.stdout.decode() if e.stdout else None,
413
+ "partial_stderr": e.stderr.decode() if e.stderr else None,
414
+ }
415
+ if e.stdout or e.stderr
416
+ else None,
417
+ tracked_events=tracked_events,
418
+ )
419
+ except Exception as e:
420
+ duration = time.time() - start_time
421
+ self._publish_live_event(
422
+ "spawner_complete",
423
+ "codex",
424
+ success=False,
425
+ duration=duration,
426
+ error=str(e),
427
+ )
428
+ return AIResult(
429
+ success=False,
430
+ response="",
431
+ tokens_used=None,
432
+ error=f"Unexpected error: {type(e).__name__}: {e}",
433
+ raw_output=None,
434
+ tracked_events=tracked_events,
435
+ )