gobby 0.2.6__py3-none-any.whl → 0.2.8__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 (198) hide show
  1. gobby/__init__.py +1 -1
  2. gobby/adapters/__init__.py +2 -1
  3. gobby/adapters/claude_code.py +96 -35
  4. gobby/adapters/codex_impl/__init__.py +28 -0
  5. gobby/adapters/codex_impl/adapter.py +722 -0
  6. gobby/adapters/codex_impl/client.py +679 -0
  7. gobby/adapters/codex_impl/protocol.py +20 -0
  8. gobby/adapters/codex_impl/types.py +68 -0
  9. gobby/adapters/gemini.py +140 -38
  10. gobby/agents/definitions.py +11 -1
  11. gobby/agents/isolation.py +525 -0
  12. gobby/agents/registry.py +11 -0
  13. gobby/agents/sandbox.py +261 -0
  14. gobby/agents/session.py +1 -0
  15. gobby/agents/spawn.py +42 -287
  16. gobby/agents/spawn_executor.py +415 -0
  17. gobby/agents/spawners/__init__.py +24 -0
  18. gobby/agents/spawners/command_builder.py +189 -0
  19. gobby/agents/spawners/embedded.py +21 -2
  20. gobby/agents/spawners/headless.py +21 -2
  21. gobby/agents/spawners/macos.py +26 -1
  22. gobby/agents/spawners/prompt_manager.py +125 -0
  23. gobby/cli/__init__.py +0 -2
  24. gobby/cli/install.py +4 -4
  25. gobby/cli/installers/claude.py +6 -0
  26. gobby/cli/installers/gemini.py +6 -0
  27. gobby/cli/installers/shared.py +103 -4
  28. gobby/cli/memory.py +185 -0
  29. gobby/cli/sessions.py +1 -1
  30. gobby/cli/utils.py +9 -2
  31. gobby/clones/git.py +177 -0
  32. gobby/config/__init__.py +12 -97
  33. gobby/config/app.py +10 -94
  34. gobby/config/extensions.py +2 -2
  35. gobby/config/features.py +7 -130
  36. gobby/config/skills.py +31 -0
  37. gobby/config/tasks.py +4 -28
  38. gobby/hooks/__init__.py +0 -13
  39. gobby/hooks/event_handlers.py +150 -8
  40. gobby/hooks/hook_manager.py +21 -3
  41. gobby/hooks/plugins.py +1 -1
  42. gobby/hooks/webhooks.py +1 -1
  43. gobby/install/gemini/hooks/hook_dispatcher.py +74 -15
  44. gobby/llm/resolver.py +3 -2
  45. gobby/mcp_proxy/importer.py +62 -4
  46. gobby/mcp_proxy/instructions.py +4 -2
  47. gobby/mcp_proxy/registries.py +22 -8
  48. gobby/mcp_proxy/services/recommendation.py +43 -11
  49. gobby/mcp_proxy/tools/agent_messaging.py +93 -44
  50. gobby/mcp_proxy/tools/agents.py +76 -740
  51. gobby/mcp_proxy/tools/artifacts.py +43 -9
  52. gobby/mcp_proxy/tools/clones.py +0 -385
  53. gobby/mcp_proxy/tools/memory.py +2 -2
  54. gobby/mcp_proxy/tools/sessions/__init__.py +14 -0
  55. gobby/mcp_proxy/tools/sessions/_commits.py +239 -0
  56. gobby/mcp_proxy/tools/sessions/_crud.py +253 -0
  57. gobby/mcp_proxy/tools/sessions/_factory.py +63 -0
  58. gobby/mcp_proxy/tools/sessions/_handoff.py +503 -0
  59. gobby/mcp_proxy/tools/sessions/_messages.py +166 -0
  60. gobby/mcp_proxy/tools/skills/__init__.py +14 -29
  61. gobby/mcp_proxy/tools/spawn_agent.py +455 -0
  62. gobby/mcp_proxy/tools/tasks/_context.py +18 -0
  63. gobby/mcp_proxy/tools/tasks/_crud.py +13 -6
  64. gobby/mcp_proxy/tools/tasks/_lifecycle.py +79 -30
  65. gobby/mcp_proxy/tools/tasks/_lifecycle_validation.py +1 -1
  66. gobby/mcp_proxy/tools/tasks/_session.py +22 -7
  67. gobby/mcp_proxy/tools/workflows.py +84 -34
  68. gobby/mcp_proxy/tools/worktrees.py +32 -350
  69. gobby/memory/extractor.py +15 -1
  70. gobby/memory/ingestion/__init__.py +5 -0
  71. gobby/memory/ingestion/multimodal.py +221 -0
  72. gobby/memory/manager.py +62 -283
  73. gobby/memory/search/__init__.py +10 -0
  74. gobby/memory/search/coordinator.py +248 -0
  75. gobby/memory/services/__init__.py +5 -0
  76. gobby/memory/services/crossref.py +142 -0
  77. gobby/prompts/loader.py +5 -2
  78. gobby/runner.py +13 -0
  79. gobby/servers/http.py +1 -4
  80. gobby/servers/routes/admin.py +14 -0
  81. gobby/servers/routes/mcp/endpoints/__init__.py +61 -0
  82. gobby/servers/routes/mcp/endpoints/discovery.py +405 -0
  83. gobby/servers/routes/mcp/endpoints/execution.py +568 -0
  84. gobby/servers/routes/mcp/endpoints/registry.py +378 -0
  85. gobby/servers/routes/mcp/endpoints/server.py +304 -0
  86. gobby/servers/routes/mcp/hooks.py +51 -4
  87. gobby/servers/routes/mcp/tools.py +48 -1506
  88. gobby/servers/websocket.py +57 -1
  89. gobby/sessions/analyzer.py +2 -2
  90. gobby/sessions/lifecycle.py +1 -1
  91. gobby/sessions/manager.py +9 -0
  92. gobby/sessions/processor.py +10 -0
  93. gobby/sessions/transcripts/base.py +1 -0
  94. gobby/sessions/transcripts/claude.py +15 -5
  95. gobby/sessions/transcripts/gemini.py +100 -34
  96. gobby/skills/parser.py +30 -2
  97. gobby/storage/database.py +9 -2
  98. gobby/storage/memories.py +32 -21
  99. gobby/storage/migrations.py +174 -368
  100. gobby/storage/sessions.py +45 -7
  101. gobby/storage/skills.py +80 -7
  102. gobby/storage/tasks/_lifecycle.py +18 -3
  103. gobby/sync/memories.py +1 -1
  104. gobby/tasks/external_validator.py +1 -1
  105. gobby/tasks/validation.py +22 -20
  106. gobby/tools/summarizer.py +91 -10
  107. gobby/utils/project_context.py +2 -3
  108. gobby/utils/status.py +13 -0
  109. gobby/workflows/actions.py +221 -1217
  110. gobby/workflows/artifact_actions.py +31 -0
  111. gobby/workflows/autonomous_actions.py +11 -0
  112. gobby/workflows/context_actions.py +50 -1
  113. gobby/workflows/detection_helpers.py +38 -24
  114. gobby/workflows/enforcement/__init__.py +47 -0
  115. gobby/workflows/enforcement/blocking.py +281 -0
  116. gobby/workflows/enforcement/commit_policy.py +283 -0
  117. gobby/workflows/enforcement/handlers.py +269 -0
  118. gobby/workflows/enforcement/task_policy.py +542 -0
  119. gobby/workflows/engine.py +93 -0
  120. gobby/workflows/evaluator.py +110 -0
  121. gobby/workflows/git_utils.py +106 -0
  122. gobby/workflows/hooks.py +41 -0
  123. gobby/workflows/llm_actions.py +30 -0
  124. gobby/workflows/mcp_actions.py +20 -1
  125. gobby/workflows/memory_actions.py +91 -0
  126. gobby/workflows/safe_evaluator.py +191 -0
  127. gobby/workflows/session_actions.py +44 -0
  128. gobby/workflows/state_actions.py +60 -1
  129. gobby/workflows/stop_signal_actions.py +55 -0
  130. gobby/workflows/summary_actions.py +217 -51
  131. gobby/workflows/task_sync_actions.py +347 -0
  132. gobby/workflows/todo_actions.py +34 -1
  133. gobby/workflows/webhook_actions.py +185 -0
  134. {gobby-0.2.6.dist-info → gobby-0.2.8.dist-info}/METADATA +6 -1
  135. {gobby-0.2.6.dist-info → gobby-0.2.8.dist-info}/RECORD +139 -163
  136. {gobby-0.2.6.dist-info → gobby-0.2.8.dist-info}/WHEEL +1 -1
  137. gobby/adapters/codex.py +0 -1332
  138. gobby/cli/tui.py +0 -34
  139. gobby/install/claude/commands/gobby/bug.md +0 -51
  140. gobby/install/claude/commands/gobby/chore.md +0 -51
  141. gobby/install/claude/commands/gobby/epic.md +0 -52
  142. gobby/install/claude/commands/gobby/eval.md +0 -235
  143. gobby/install/claude/commands/gobby/feat.md +0 -49
  144. gobby/install/claude/commands/gobby/nit.md +0 -52
  145. gobby/install/claude/commands/gobby/ref.md +0 -52
  146. gobby/mcp_proxy/tools/session_messages.py +0 -1055
  147. gobby/prompts/defaults/expansion/system.md +0 -119
  148. gobby/prompts/defaults/expansion/user.md +0 -48
  149. gobby/prompts/defaults/external_validation/agent.md +0 -72
  150. gobby/prompts/defaults/external_validation/external.md +0 -63
  151. gobby/prompts/defaults/external_validation/spawn.md +0 -83
  152. gobby/prompts/defaults/external_validation/system.md +0 -6
  153. gobby/prompts/defaults/features/import_mcp.md +0 -22
  154. gobby/prompts/defaults/features/import_mcp_github.md +0 -17
  155. gobby/prompts/defaults/features/import_mcp_search.md +0 -16
  156. gobby/prompts/defaults/features/recommend_tools.md +0 -32
  157. gobby/prompts/defaults/features/recommend_tools_hybrid.md +0 -35
  158. gobby/prompts/defaults/features/recommend_tools_llm.md +0 -30
  159. gobby/prompts/defaults/features/server_description.md +0 -20
  160. gobby/prompts/defaults/features/server_description_system.md +0 -6
  161. gobby/prompts/defaults/features/task_description.md +0 -31
  162. gobby/prompts/defaults/features/task_description_system.md +0 -6
  163. gobby/prompts/defaults/features/tool_summary.md +0 -17
  164. gobby/prompts/defaults/features/tool_summary_system.md +0 -6
  165. gobby/prompts/defaults/handoff/compact.md +0 -63
  166. gobby/prompts/defaults/handoff/session_end.md +0 -57
  167. gobby/prompts/defaults/memory/extract.md +0 -61
  168. gobby/prompts/defaults/research/step.md +0 -58
  169. gobby/prompts/defaults/validation/criteria.md +0 -47
  170. gobby/prompts/defaults/validation/validate.md +0 -38
  171. gobby/storage/migrations_legacy.py +0 -1359
  172. gobby/tui/__init__.py +0 -5
  173. gobby/tui/api_client.py +0 -278
  174. gobby/tui/app.py +0 -329
  175. gobby/tui/screens/__init__.py +0 -25
  176. gobby/tui/screens/agents.py +0 -333
  177. gobby/tui/screens/chat.py +0 -450
  178. gobby/tui/screens/dashboard.py +0 -377
  179. gobby/tui/screens/memory.py +0 -305
  180. gobby/tui/screens/metrics.py +0 -231
  181. gobby/tui/screens/orchestrator.py +0 -903
  182. gobby/tui/screens/sessions.py +0 -412
  183. gobby/tui/screens/tasks.py +0 -440
  184. gobby/tui/screens/workflows.py +0 -289
  185. gobby/tui/screens/worktrees.py +0 -174
  186. gobby/tui/widgets/__init__.py +0 -21
  187. gobby/tui/widgets/chat.py +0 -210
  188. gobby/tui/widgets/conductor.py +0 -104
  189. gobby/tui/widgets/menu.py +0 -132
  190. gobby/tui/widgets/message_panel.py +0 -160
  191. gobby/tui/widgets/review_gate.py +0 -224
  192. gobby/tui/widgets/task_tree.py +0 -99
  193. gobby/tui/widgets/token_budget.py +0 -166
  194. gobby/tui/ws_client.py +0 -258
  195. gobby/workflows/task_enforcement_actions.py +0 -1343
  196. {gobby-0.2.6.dist-info → gobby-0.2.8.dist-info}/entry_points.txt +0 -0
  197. {gobby-0.2.6.dist-info → gobby-0.2.8.dist-info}/licenses/LICENSE.md +0 -0
  198. {gobby-0.2.6.dist-info → gobby-0.2.8.dist-info}/top_level.txt +0 -0
@@ -1,333 +0,0 @@
1
- """Agents screen with running agents and spawn controls."""
2
-
3
- from __future__ import annotations
4
-
5
- from datetime import datetime
6
- from typing import Any
7
-
8
- from textual.app import ComposeResult
9
- from textual.containers import Container, Horizontal, Vertical
10
- from textual.reactive import reactive
11
- from textual.widget import Widget
12
- from textual.widgets import (
13
- Button,
14
- DataTable,
15
- LoadingIndicator,
16
- Select,
17
- Static,
18
- TextArea,
19
- )
20
-
21
- from gobby.tui.api_client import GobbyAPIClient
22
- from gobby.tui.ws_client import GobbyWebSocketClient
23
-
24
-
25
- class SpawnAgentDialog(Widget):
26
- """Dialog for spawning a new agent."""
27
-
28
- DEFAULT_CSS = """
29
- SpawnAgentDialog {
30
- width: 100%;
31
- height: auto;
32
- padding: 1;
33
- border: round #7c3aed;
34
- background: #1e1e2e;
35
- }
36
-
37
- SpawnAgentDialog .dialog-title {
38
- text-style: bold;
39
- color: #a78bfa;
40
- padding-bottom: 1;
41
- }
42
-
43
- SpawnAgentDialog .form-row {
44
- height: auto;
45
- margin-bottom: 1;
46
- }
47
-
48
- SpawnAgentDialog .form-label {
49
- color: #a6adc8;
50
- margin-bottom: 0;
51
- }
52
-
53
- SpawnAgentDialog #prompt-input {
54
- height: 5;
55
- }
56
-
57
- SpawnAgentDialog .button-row {
58
- layout: horizontal;
59
- height: 3;
60
- margin-top: 1;
61
- }
62
-
63
- SpawnAgentDialog .button-row Button {
64
- margin-right: 1;
65
- }
66
- """
67
-
68
- def compose(self) -> ComposeResult:
69
- yield Static("🚀 Spawn New Agent", classes="dialog-title")
70
-
71
- with Vertical(classes="form-row"):
72
- yield Static("Prompt:", classes="form-label")
73
- yield TextArea(id="prompt-input")
74
-
75
- with Horizontal(classes="form-row"):
76
- with Vertical():
77
- yield Static("Mode:", classes="form-label")
78
- yield Select(
79
- [
80
- (label, value)
81
- for label, value in [
82
- ("Terminal", "terminal"),
83
- ("Embedded", "embedded"),
84
- ("Headless", "headless"),
85
- ]
86
- ],
87
- value="terminal",
88
- id="mode-select",
89
- )
90
- with Vertical():
91
- yield Static("Workflow:", classes="form-label")
92
- yield Select(
93
- [
94
- (label, value)
95
- for label, value in [
96
- ("None", ""),
97
- ("Plan-Execute", "plan-execute"),
98
- ("Test-Driven", "test-driven"),
99
- ("Auto-Task", "auto-task"),
100
- ]
101
- ],
102
- value="",
103
- id="workflow-select",
104
- )
105
-
106
- with Horizontal(classes="button-row"):
107
- yield Button("Spawn", variant="primary", id="btn-spawn")
108
- yield Button("Cancel", id="btn-cancel-spawn")
109
-
110
- def get_values(self) -> dict[str, Any]:
111
- """Get the form values."""
112
- prompt = self.query_one("#prompt-input", TextArea).text
113
- mode = str(self.query_one("#mode-select", Select).value)
114
- workflow = str(self.query_one("#workflow-select", Select).value)
115
- return {"prompt": prompt, "mode": mode, "workflow": workflow or None}
116
-
117
- def clear(self) -> None:
118
- """Clear the form."""
119
- self.query_one("#prompt-input", TextArea).clear()
120
-
121
-
122
- class AgentsScreen(Widget):
123
- """Agents screen showing running agents and spawn controls."""
124
-
125
- DEFAULT_CSS = """
126
- AgentsScreen {
127
- width: 1fr;
128
- height: 1fr;
129
- }
130
-
131
- AgentsScreen .screen-header {
132
- height: auto;
133
- padding: 1;
134
- background: #313244;
135
- }
136
-
137
- AgentsScreen .header-row {
138
- layout: horizontal;
139
- }
140
-
141
- AgentsScreen .panel-title {
142
- text-style: bold;
143
- color: #a78bfa;
144
- width: 1fr;
145
- }
146
-
147
- AgentsScreen #agents-table {
148
- height: 1fr;
149
- }
150
-
151
- AgentsScreen #spawn-dialog {
152
- display: none;
153
- margin: 1;
154
- }
155
-
156
- AgentsScreen #spawn-dialog.--visible {
157
- display: block;
158
- }
159
-
160
- AgentsScreen .loading-container {
161
- width: 1fr;
162
- height: 1fr;
163
- content-align: center middle;
164
- }
165
-
166
- AgentsScreen .empty-state {
167
- content-align: center middle;
168
- height: 1fr;
169
- color: #a6adc8;
170
- }
171
- """
172
-
173
- loading = reactive(True)
174
- agents: reactive[list[dict[str, Any]]] = reactive(list)
175
- show_spawn_dialog = reactive(False)
176
- selected_agent_id: reactive[str | None] = reactive(None)
177
-
178
- def __init__(
179
- self,
180
- api_client: GobbyAPIClient,
181
- ws_client: GobbyWebSocketClient,
182
- **kwargs: Any,
183
- ) -> None:
184
- super().__init__(**kwargs)
185
- self.api_client = api_client
186
- self.ws_client = ws_client
187
-
188
- def compose(self) -> ComposeResult:
189
- with Vertical(classes="screen-header"):
190
- with Horizontal(classes="header-row"):
191
- yield Static("🤖 Agents", classes="panel-title")
192
- yield Button("+ Spawn Agent", variant="primary", id="btn-show-spawn")
193
- yield Button("Cancel Selected", id="btn-cancel-agent")
194
- yield Button("Refresh", id="btn-refresh")
195
-
196
- yield SpawnAgentDialog(id="spawn-dialog")
197
-
198
- if self.loading:
199
- with Container(classes="loading-container"):
200
- yield LoadingIndicator()
201
- else:
202
- yield DataTable(id="agents-table")
203
-
204
- async def on_mount(self) -> None:
205
- """Load data when mounted."""
206
- await self.refresh_data()
207
-
208
- async def refresh_data(self) -> None:
209
- """Refresh agent list."""
210
- try:
211
- async with GobbyAPIClient(self.api_client.base_url) as client:
212
- agents = await client.list_agents()
213
- self.agents = agents
214
- except Exception as e:
215
- self.notify(f"Failed to load agents: {e}", severity="error")
216
- finally:
217
- self.loading = False
218
- await self._setup_table()
219
-
220
- async def _setup_table(self) -> None:
221
- """Set up and populate the agents table."""
222
- try:
223
- table = self.query_one("#agents-table", DataTable)
224
- table.clear(columns=True)
225
- table.add_columns("ID", "Status", "Mode", "Prompt", "Duration")
226
- table.cursor_type = "row"
227
-
228
- for agent in self.agents:
229
- run_id = agent.get("run_id", "")[:12]
230
- status = agent.get("status", "unknown")
231
- mode = agent.get("mode", "?")
232
- prompt_val = agent.get("prompt") or ""
233
- prompt = prompt_val[:40] + "..." if len(prompt_val) > 40 else prompt_val
234
-
235
- # Calculate duration
236
- started = agent.get("started_at", "")
237
- if started and status == "running":
238
- try:
239
- started_dt = datetime.fromisoformat(started.replace("Z", "+00:00"))
240
- duration = datetime.now(started_dt.tzinfo) - started_dt
241
- duration_str = f"{duration.seconds // 60}m"
242
- except Exception:
243
- duration_str = "?"
244
- else:
245
- duration_str = "-"
246
-
247
- table.add_row(run_id, status, mode, prompt, duration_str, key=agent.get("run_id"))
248
-
249
- except Exception:
250
- pass # nosec B110 - TUI update failure is non-critical
251
-
252
- def watch_show_spawn_dialog(self, show: bool) -> None:
253
- """Toggle spawn dialog visibility."""
254
- try:
255
- dialog = self.query_one("#spawn-dialog", SpawnAgentDialog)
256
- dialog.set_class(show, "--visible")
257
- except Exception:
258
- pass # nosec B110 - widget may not be mounted yet
259
-
260
- def on_data_table_row_selected(self, event: DataTable.RowSelected) -> None:
261
- """Handle agent selection."""
262
- self.selected_agent_id = str(event.row_key.value) if event.row_key else None
263
-
264
- async def on_button_pressed(self, event: Button.Pressed) -> None:
265
- """Handle button presses."""
266
- button_id = event.button.id
267
-
268
- if button_id == "btn-show-spawn":
269
- self.show_spawn_dialog = True
270
-
271
- elif button_id == "btn-cancel-spawn":
272
- self.show_spawn_dialog = False
273
- try:
274
- dialog = self.query_one("#spawn-dialog", SpawnAgentDialog)
275
- dialog.clear()
276
- except Exception:
277
- pass # nosec B110 - widget may not be mounted yet
278
-
279
- elif button_id == "btn-spawn":
280
- await self._spawn_agent()
281
-
282
- elif button_id == "btn-cancel-agent":
283
- await self._cancel_agent()
284
-
285
- elif button_id == "btn-refresh":
286
- self.loading = True
287
- await self.refresh_data()
288
-
289
- async def _spawn_agent(self) -> None:
290
- """Spawn a new agent."""
291
- try:
292
- dialog = self.query_one("#spawn-dialog", SpawnAgentDialog)
293
- values = dialog.get_values()
294
-
295
- if not values.get("prompt"):
296
- self.notify("Prompt is required", severity="error")
297
- return
298
-
299
- async with GobbyAPIClient(self.api_client.base_url) as client:
300
- result = await client.start_agent(
301
- prompt=values["prompt"],
302
- mode=values["mode"],
303
- workflow=values.get("workflow"),
304
- )
305
- self.notify(f"Agent spawned: {result.get('run_id', 'unknown')[:12]}")
306
-
307
- self.show_spawn_dialog = False
308
- dialog.clear()
309
- await self.refresh_data()
310
-
311
- except Exception as e:
312
- self.notify(f"Failed to spawn agent: {e}", severity="error")
313
-
314
- async def _cancel_agent(self) -> None:
315
- """Cancel the selected agent."""
316
- if not self.selected_agent_id:
317
- self.notify("No agent selected", severity="warning")
318
- return
319
-
320
- try:
321
- async with GobbyAPIClient(self.api_client.base_url) as client:
322
- await client.cancel_agent(self.selected_agent_id)
323
- self.notify(f"Agent cancelled: {self.selected_agent_id[:12]}")
324
-
325
- await self.refresh_data()
326
-
327
- except Exception as e:
328
- self.notify(f"Failed to cancel agent: {e}", severity="error")
329
-
330
- def on_ws_event(self, event_type: str, data: dict[str, Any]) -> None:
331
- """Handle WebSocket events."""
332
- if event_type == "agent_event":
333
- self.run_worker(self.refresh_data(), name="refresh_data", exclusive=True)