htmlgraph 0.26.5__py3-none-any.whl → 0.26.6__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 (69) hide show
  1. htmlgraph/.htmlgraph/.session-warning-state.json +1 -1
  2. htmlgraph/__init__.py +1 -1
  3. htmlgraph/api/main.py +50 -10
  4. htmlgraph/api/templates/dashboard-redesign.html +608 -54
  5. htmlgraph/api/templates/partials/activity-feed.html +21 -0
  6. htmlgraph/api/templates/partials/features.html +81 -12
  7. htmlgraph/api/templates/partials/orchestration.html +35 -0
  8. htmlgraph/cli/.htmlgraph/.session-warning-state.json +6 -0
  9. htmlgraph/cli/.htmlgraph/agents.json +72 -0
  10. htmlgraph/cli/__init__.py +42 -0
  11. htmlgraph/cli/__main__.py +6 -0
  12. htmlgraph/cli/analytics.py +939 -0
  13. htmlgraph/cli/base.py +660 -0
  14. htmlgraph/cli/constants.py +206 -0
  15. htmlgraph/cli/core.py +856 -0
  16. htmlgraph/cli/main.py +143 -0
  17. htmlgraph/cli/models.py +462 -0
  18. htmlgraph/cli/templates/__init__.py +1 -0
  19. htmlgraph/cli/templates/cost_dashboard.py +398 -0
  20. htmlgraph/cli/work/__init__.py +159 -0
  21. htmlgraph/cli/work/features.py +567 -0
  22. htmlgraph/cli/work/orchestration.py +675 -0
  23. htmlgraph/cli/work/sessions.py +465 -0
  24. htmlgraph/cli/work/tracks.py +485 -0
  25. htmlgraph/dashboard.html +6414 -634
  26. htmlgraph/db/schema.py +8 -3
  27. htmlgraph/docs/ORCHESTRATION_PATTERNS.md +20 -13
  28. htmlgraph/docs/README.md +2 -3
  29. htmlgraph/hooks/event_tracker.py +157 -25
  30. htmlgraph/hooks/git_commands.py +175 -0
  31. htmlgraph/hooks/orchestrator.py +137 -71
  32. htmlgraph/hooks/orchestrator_reflector.py +23 -0
  33. htmlgraph/hooks/pretooluse.py +29 -6
  34. htmlgraph/hooks/session_handler.py +28 -0
  35. htmlgraph/hooks/session_summary.py +391 -0
  36. htmlgraph/hooks/subagent_detection.py +202 -0
  37. htmlgraph/hooks/validator.py +192 -79
  38. htmlgraph/operations/__init__.py +18 -0
  39. htmlgraph/operations/initialization.py +596 -0
  40. htmlgraph/operations/initialization.py.backup +228 -0
  41. htmlgraph/orchestration/__init__.py +16 -1
  42. htmlgraph/orchestration/claude_launcher.py +185 -0
  43. htmlgraph/orchestration/command_builder.py +71 -0
  44. htmlgraph/orchestration/headless_spawner.py +72 -1332
  45. htmlgraph/orchestration/plugin_manager.py +136 -0
  46. htmlgraph/orchestration/prompts.py +137 -0
  47. htmlgraph/orchestration/spawners/__init__.py +16 -0
  48. htmlgraph/orchestration/spawners/base.py +194 -0
  49. htmlgraph/orchestration/spawners/claude.py +170 -0
  50. htmlgraph/orchestration/spawners/codex.py +442 -0
  51. htmlgraph/orchestration/spawners/copilot.py +299 -0
  52. htmlgraph/orchestration/spawners/gemini.py +478 -0
  53. htmlgraph/orchestration/subprocess_runner.py +33 -0
  54. htmlgraph/orchestration.md +563 -0
  55. htmlgraph/orchestrator-system-prompt-optimized.txt +620 -55
  56. htmlgraph/orchestrator_config.py +357 -0
  57. htmlgraph/orchestrator_mode.py +45 -12
  58. htmlgraph/transcript.py +16 -4
  59. htmlgraph-0.26.6.data/data/htmlgraph/dashboard.html +6592 -0
  60. {htmlgraph-0.26.5.dist-info → htmlgraph-0.26.6.dist-info}/METADATA +1 -1
  61. {htmlgraph-0.26.5.dist-info → htmlgraph-0.26.6.dist-info}/RECORD +67 -33
  62. {htmlgraph-0.26.5.dist-info → htmlgraph-0.26.6.dist-info}/entry_points.txt +1 -1
  63. htmlgraph/cli.py +0 -7256
  64. htmlgraph-0.26.5.data/data/htmlgraph/dashboard.html +0 -812
  65. {htmlgraph-0.26.5.data → htmlgraph-0.26.6.data}/data/htmlgraph/styles.css +0 -0
  66. {htmlgraph-0.26.5.data → htmlgraph-0.26.6.data}/data/htmlgraph/templates/AGENTS.md.template +0 -0
  67. {htmlgraph-0.26.5.data → htmlgraph-0.26.6.data}/data/htmlgraph/templates/CLAUDE.md.template +0 -0
  68. {htmlgraph-0.26.5.data → htmlgraph-0.26.6.data}/data/htmlgraph/templates/GEMINI.md.template +0 -0
  69. {htmlgraph-0.26.5.dist-info → htmlgraph-0.26.6.dist-info}/WHEEL +0 -0
@@ -0,0 +1,299 @@
1
+ """Copilot spawner implementation."""
2
+
3
+ import subprocess
4
+ import sys
5
+ import time
6
+ from typing import TYPE_CHECKING, Any
7
+
8
+ from .base import AIResult, BaseSpawner
9
+
10
+ if TYPE_CHECKING:
11
+ from htmlgraph.sdk import SDK
12
+
13
+
14
+ class CopilotSpawner(BaseSpawner):
15
+ """Spawner for GitHub Copilot CLI."""
16
+
17
+ def _parse_and_track_events(
18
+ self, prompt: str, response: str, sdk: "SDK"
19
+ ) -> list[dict]:
20
+ """
21
+ Track Copilot execution (start and result only).
22
+
23
+ Args:
24
+ prompt: Original prompt
25
+ response: Response from Copilot
26
+ sdk: HtmlGraph SDK instance for tracking
27
+
28
+ Returns:
29
+ Synthetic events list for consistency
30
+ """
31
+ events = []
32
+
33
+ # Track start
34
+ start_event = {"type": "copilot_start", "prompt": prompt[:100]}
35
+ events.append(start_event)
36
+ self._track_activity(
37
+ sdk,
38
+ tool="copilot_start",
39
+ summary=f"Copilot started with prompt: {prompt[:80]}",
40
+ payload={"prompt_length": len(prompt)},
41
+ )
42
+
43
+ # Track result
44
+ result_event = {"type": "copilot_result", "response": response[:100]}
45
+ events.append(result_event)
46
+ self._track_activity(
47
+ sdk,
48
+ tool="copilot_result",
49
+ summary=f"Copilot completed: {response[:80]}",
50
+ payload={"response_length": len(response)},
51
+ )
52
+
53
+ return events
54
+
55
+ def spawn(
56
+ self,
57
+ prompt: str,
58
+ allow_tools: list[str] | None = None,
59
+ allow_all_tools: bool = False,
60
+ deny_tools: list[str] | None = None,
61
+ track_in_htmlgraph: bool = True,
62
+ timeout: int = 120,
63
+ tracker: Any = None,
64
+ parent_event_id: str | None = None,
65
+ ) -> AIResult:
66
+ """
67
+ Spawn GitHub Copilot in headless mode.
68
+
69
+ Args:
70
+ prompt: Task description for Copilot
71
+ allow_tools: Tools to auto-approve (e.g., ["shell(git)"])
72
+ allow_all_tools: Auto-approve all tools. Default: False
73
+ deny_tools: Tools to deny (--deny-tool). Default: None
74
+ track_in_htmlgraph: Enable HtmlGraph activity tracking. Default: True
75
+ timeout: Max seconds to wait
76
+ tracker: Optional SpawnerEventTracker for recording subprocess invocation
77
+ parent_event_id: Optional parent event ID for event hierarchy
78
+
79
+ Returns:
80
+ AIResult with response, error, and tracked events if tracking enabled
81
+ """
82
+ # Initialize tracking if enabled
83
+ sdk = None
84
+ tracked_events = []
85
+ if track_in_htmlgraph:
86
+ sdk = self._get_sdk()
87
+
88
+ # Publish live event: spawner starting
89
+ self._publish_live_event(
90
+ "spawner_start",
91
+ "copilot",
92
+ prompt=prompt,
93
+ )
94
+ start_time = time.time()
95
+
96
+ cmd = ["copilot", "-p", prompt]
97
+
98
+ # Add allow all tools flag
99
+ if allow_all_tools:
100
+ cmd.append("--allow-all-tools")
101
+
102
+ # Add tool permissions
103
+ if allow_tools:
104
+ for tool in allow_tools:
105
+ cmd.extend(["--allow-tool", tool])
106
+
107
+ # Add denied tools
108
+ if deny_tools:
109
+ for tool in deny_tools:
110
+ cmd.extend(["--deny-tool", tool])
111
+
112
+ # Track spawner start if SDK available
113
+ if sdk:
114
+ self._track_activity(
115
+ sdk,
116
+ tool="copilot_spawn_start",
117
+ summary=f"Spawning Copilot: {prompt[:80]}",
118
+ payload={"prompt_length": len(prompt)},
119
+ )
120
+
121
+ try:
122
+ # Publish live event: executing
123
+ self._publish_live_event(
124
+ "spawner_phase",
125
+ "copilot",
126
+ phase="executing",
127
+ details="Running Copilot CLI",
128
+ )
129
+
130
+ # Record subprocess invocation if tracker is available
131
+ subprocess_event_id = None
132
+ print(
133
+ f"DEBUG: tracker={tracker is not None}, parent_event_id={parent_event_id}",
134
+ file=sys.stderr,
135
+ )
136
+ if tracker and parent_event_id:
137
+ print(
138
+ "DEBUG: Recording subprocess invocation for Copilot...",
139
+ file=sys.stderr,
140
+ )
141
+ try:
142
+ subprocess_event = tracker.record_tool_call(
143
+ tool_name="subprocess.copilot",
144
+ tool_input={"cmd": cmd},
145
+ phase_event_id=parent_event_id,
146
+ spawned_agent="github-copilot",
147
+ )
148
+ if subprocess_event:
149
+ subprocess_event_id = subprocess_event.get("event_id")
150
+ print(
151
+ f"DEBUG: Subprocess event created for Copilot: {subprocess_event_id}",
152
+ file=sys.stderr,
153
+ )
154
+ else:
155
+ print("DEBUG: subprocess_event was None", file=sys.stderr)
156
+ except Exception as e:
157
+ # Tracking failure should not break execution
158
+ print(
159
+ f"DEBUG: Exception recording Copilot subprocess: {e}",
160
+ file=sys.stderr,
161
+ )
162
+ pass
163
+ else:
164
+ print(
165
+ f"DEBUG: Skipping Copilot subprocess tracking - tracker={tracker is not None}, parent_event_id={parent_event_id}",
166
+ file=sys.stderr,
167
+ )
168
+
169
+ result = subprocess.run(
170
+ cmd,
171
+ capture_output=True,
172
+ text=True,
173
+ timeout=timeout,
174
+ )
175
+
176
+ # Complete subprocess invocation tracking
177
+ if tracker and subprocess_event_id:
178
+ try:
179
+ tracker.complete_tool_call(
180
+ event_id=subprocess_event_id,
181
+ output_summary=result.stdout[:500] if result.stdout else "",
182
+ success=result.returncode == 0,
183
+ )
184
+ except Exception:
185
+ # Tracking failure should not break execution
186
+ pass
187
+
188
+ # Publish live event: processing
189
+ self._publish_live_event(
190
+ "spawner_phase",
191
+ "copilot",
192
+ phase="processing",
193
+ details="Parsing Copilot response",
194
+ )
195
+
196
+ # Parse output: response is before stats block
197
+ lines = result.stdout.split("\n")
198
+
199
+ # Find where stats start (look for "Total usage est:" or "Usage by model")
200
+ stats_start = len(lines)
201
+ for i, line in enumerate(lines):
202
+ if "Total usage est" in line or "Usage by model" in line:
203
+ stats_start = i
204
+ break
205
+
206
+ # Response is everything before stats
207
+ response = "\n".join(lines[:stats_start]).strip()
208
+
209
+ # Try to extract token count from stats
210
+ tokens = None
211
+ for line in lines[stats_start:]:
212
+ # Look for token counts like "25.8k input, 5 output"
213
+ if "input" in line and "output" in line:
214
+ # Simple extraction: just note we found stats
215
+ # TODO: More sophisticated parsing if needed
216
+ tokens = 0 # Placeholder
217
+ break
218
+
219
+ # Track Copilot execution if SDK available
220
+ if sdk:
221
+ tracked_events = self._parse_and_track_events(prompt, response, sdk)
222
+
223
+ # Publish live event: complete
224
+ duration = time.time() - start_time
225
+ success = result.returncode == 0
226
+ self._publish_live_event(
227
+ "spawner_complete",
228
+ "copilot",
229
+ success=success,
230
+ duration=duration,
231
+ response=response[:200] if response else None,
232
+ tokens=tokens,
233
+ error=result.stderr if not success else None,
234
+ )
235
+ return AIResult(
236
+ success=success,
237
+ response=response,
238
+ tokens_used=tokens,
239
+ error=None if success else result.stderr,
240
+ raw_output=result.stdout,
241
+ tracked_events=tracked_events,
242
+ )
243
+
244
+ except FileNotFoundError:
245
+ duration = time.time() - start_time
246
+ self._publish_live_event(
247
+ "spawner_complete",
248
+ "copilot",
249
+ success=False,
250
+ duration=duration,
251
+ error="CLI not found",
252
+ )
253
+ return AIResult(
254
+ success=False,
255
+ response="",
256
+ tokens_used=None,
257
+ error="Copilot CLI not found. Install from: https://docs.github.com/en/copilot/using-github-copilot/using-github-copilot-in-the-command-line",
258
+ raw_output=None,
259
+ tracked_events=tracked_events,
260
+ )
261
+ except subprocess.TimeoutExpired as e:
262
+ duration = time.time() - start_time
263
+ self._publish_live_event(
264
+ "spawner_complete",
265
+ "copilot",
266
+ success=False,
267
+ duration=duration,
268
+ error=f"Timed out after {timeout} seconds",
269
+ )
270
+ return AIResult(
271
+ success=False,
272
+ response="",
273
+ tokens_used=None,
274
+ error=f"Timed out after {timeout} seconds",
275
+ raw_output={
276
+ "partial_stdout": e.stdout.decode() if e.stdout else None,
277
+ "partial_stderr": e.stderr.decode() if e.stderr else None,
278
+ }
279
+ if e.stdout or e.stderr
280
+ else None,
281
+ tracked_events=tracked_events,
282
+ )
283
+ except Exception as e:
284
+ duration = time.time() - start_time
285
+ self._publish_live_event(
286
+ "spawner_complete",
287
+ "copilot",
288
+ success=False,
289
+ duration=duration,
290
+ error=str(e),
291
+ )
292
+ return AIResult(
293
+ success=False,
294
+ response="",
295
+ tokens_used=None,
296
+ error=f"Unexpected error: {type(e).__name__}: {e}",
297
+ raw_output=None,
298
+ tracked_events=tracked_events,
299
+ )