gobby 0.2.7__py3-none-any.whl → 0.2.9__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 (125) hide show
  1. gobby/__init__.py +1 -1
  2. gobby/adapters/claude_code.py +99 -61
  3. gobby/adapters/gemini.py +140 -38
  4. gobby/agents/isolation.py +130 -0
  5. gobby/agents/registry.py +11 -0
  6. gobby/agents/session.py +1 -0
  7. gobby/agents/spawn_executor.py +43 -13
  8. gobby/agents/spawners/macos.py +26 -1
  9. gobby/app_context.py +59 -0
  10. gobby/cli/__init__.py +0 -2
  11. gobby/cli/memory.py +185 -0
  12. gobby/cli/utils.py +5 -17
  13. gobby/clones/git.py +177 -0
  14. gobby/config/features.py +0 -20
  15. gobby/config/skills.py +31 -0
  16. gobby/config/tasks.py +4 -0
  17. gobby/hooks/event_handlers/__init__.py +155 -0
  18. gobby/hooks/event_handlers/_agent.py +175 -0
  19. gobby/hooks/event_handlers/_base.py +87 -0
  20. gobby/hooks/event_handlers/_misc.py +66 -0
  21. gobby/hooks/event_handlers/_session.py +573 -0
  22. gobby/hooks/event_handlers/_tool.py +196 -0
  23. gobby/hooks/hook_manager.py +21 -1
  24. gobby/install/gemini/hooks/hook_dispatcher.py +74 -15
  25. gobby/llm/claude.py +377 -42
  26. gobby/mcp_proxy/importer.py +4 -41
  27. gobby/mcp_proxy/instructions.py +2 -2
  28. gobby/mcp_proxy/manager.py +13 -3
  29. gobby/mcp_proxy/registries.py +35 -4
  30. gobby/mcp_proxy/services/recommendation.py +2 -28
  31. gobby/mcp_proxy/tools/agent_messaging.py +93 -44
  32. gobby/mcp_proxy/tools/agents.py +45 -9
  33. gobby/mcp_proxy/tools/artifacts.py +46 -12
  34. gobby/mcp_proxy/tools/sessions/_commits.py +31 -24
  35. gobby/mcp_proxy/tools/sessions/_crud.py +5 -5
  36. gobby/mcp_proxy/tools/sessions/_handoff.py +45 -41
  37. gobby/mcp_proxy/tools/sessions/_messages.py +35 -7
  38. gobby/mcp_proxy/tools/spawn_agent.py +44 -6
  39. gobby/mcp_proxy/tools/task_readiness.py +27 -4
  40. gobby/mcp_proxy/tools/tasks/_context.py +18 -0
  41. gobby/mcp_proxy/tools/tasks/_crud.py +13 -6
  42. gobby/mcp_proxy/tools/tasks/_lifecycle.py +29 -14
  43. gobby/mcp_proxy/tools/tasks/_session.py +22 -7
  44. gobby/mcp_proxy/tools/workflows/__init__.py +266 -0
  45. gobby/mcp_proxy/tools/workflows/_artifacts.py +225 -0
  46. gobby/mcp_proxy/tools/workflows/_import.py +112 -0
  47. gobby/mcp_proxy/tools/workflows/_lifecycle.py +321 -0
  48. gobby/mcp_proxy/tools/workflows/_query.py +207 -0
  49. gobby/mcp_proxy/tools/workflows/_resolution.py +78 -0
  50. gobby/mcp_proxy/tools/workflows/_terminal.py +139 -0
  51. gobby/mcp_proxy/tools/worktrees.py +32 -7
  52. gobby/memory/components/__init__.py +0 -0
  53. gobby/memory/components/ingestion.py +98 -0
  54. gobby/memory/components/search.py +108 -0
  55. gobby/memory/extractor.py +15 -1
  56. gobby/memory/manager.py +16 -25
  57. gobby/paths.py +51 -0
  58. gobby/prompts/loader.py +1 -35
  59. gobby/runner.py +36 -10
  60. gobby/servers/http.py +186 -149
  61. gobby/servers/routes/admin.py +12 -0
  62. gobby/servers/routes/mcp/endpoints/execution.py +15 -7
  63. gobby/servers/routes/mcp/endpoints/registry.py +8 -8
  64. gobby/servers/routes/mcp/hooks.py +50 -3
  65. gobby/servers/websocket.py +57 -1
  66. gobby/sessions/analyzer.py +4 -4
  67. gobby/sessions/manager.py +9 -0
  68. gobby/sessions/transcripts/gemini.py +100 -34
  69. gobby/skills/parser.py +23 -0
  70. gobby/skills/sync.py +5 -4
  71. gobby/storage/artifacts.py +19 -0
  72. gobby/storage/database.py +9 -2
  73. gobby/storage/memories.py +32 -21
  74. gobby/storage/migrations.py +46 -4
  75. gobby/storage/sessions.py +4 -2
  76. gobby/storage/skills.py +87 -7
  77. gobby/tasks/external_validator.py +4 -17
  78. gobby/tasks/validation.py +13 -87
  79. gobby/tools/summarizer.py +18 -51
  80. gobby/utils/status.py +13 -0
  81. gobby/workflows/actions.py +5 -0
  82. gobby/workflows/context_actions.py +21 -24
  83. gobby/workflows/detection_helpers.py +38 -24
  84. gobby/workflows/enforcement/__init__.py +11 -1
  85. gobby/workflows/enforcement/blocking.py +109 -1
  86. gobby/workflows/enforcement/handlers.py +35 -1
  87. gobby/workflows/engine.py +96 -0
  88. gobby/workflows/evaluator.py +110 -0
  89. gobby/workflows/hooks.py +41 -0
  90. gobby/workflows/lifecycle_evaluator.py +2 -1
  91. gobby/workflows/memory_actions.py +11 -0
  92. gobby/workflows/safe_evaluator.py +8 -0
  93. gobby/workflows/summary_actions.py +123 -50
  94. {gobby-0.2.7.dist-info → gobby-0.2.9.dist-info}/METADATA +1 -1
  95. {gobby-0.2.7.dist-info → gobby-0.2.9.dist-info}/RECORD +99 -107
  96. gobby/cli/tui.py +0 -34
  97. gobby/hooks/event_handlers.py +0 -909
  98. gobby/mcp_proxy/tools/workflows.py +0 -973
  99. gobby/tui/__init__.py +0 -5
  100. gobby/tui/api_client.py +0 -278
  101. gobby/tui/app.py +0 -329
  102. gobby/tui/screens/__init__.py +0 -25
  103. gobby/tui/screens/agents.py +0 -333
  104. gobby/tui/screens/chat.py +0 -450
  105. gobby/tui/screens/dashboard.py +0 -377
  106. gobby/tui/screens/memory.py +0 -305
  107. gobby/tui/screens/metrics.py +0 -231
  108. gobby/tui/screens/orchestrator.py +0 -903
  109. gobby/tui/screens/sessions.py +0 -412
  110. gobby/tui/screens/tasks.py +0 -440
  111. gobby/tui/screens/workflows.py +0 -289
  112. gobby/tui/screens/worktrees.py +0 -174
  113. gobby/tui/widgets/__init__.py +0 -21
  114. gobby/tui/widgets/chat.py +0 -210
  115. gobby/tui/widgets/conductor.py +0 -104
  116. gobby/tui/widgets/menu.py +0 -132
  117. gobby/tui/widgets/message_panel.py +0 -160
  118. gobby/tui/widgets/review_gate.py +0 -224
  119. gobby/tui/widgets/task_tree.py +0 -99
  120. gobby/tui/widgets/token_budget.py +0 -166
  121. gobby/tui/ws_client.py +0 -258
  122. {gobby-0.2.7.dist-info → gobby-0.2.9.dist-info}/WHEEL +0 -0
  123. {gobby-0.2.7.dist-info → gobby-0.2.9.dist-info}/entry_points.txt +0 -0
  124. {gobby-0.2.7.dist-info → gobby-0.2.9.dist-info}/licenses/LICENSE.md +0 -0
  125. {gobby-0.2.7.dist-info → gobby-0.2.9.dist-info}/top_level.txt +0 -0
@@ -1,231 +0,0 @@
1
- """Metrics screen with tool usage statistics."""
2
-
3
- from __future__ import annotations
4
-
5
- import asyncio
6
- from typing import Any
7
-
8
- from textual.app import ComposeResult
9
- from textual.containers import Container, Horizontal, Vertical
10
- from textual.css.query import NoMatches
11
- from textual.reactive import reactive
12
- from textual.widget import Widget
13
- from textual.widgets import (
14
- Button,
15
- DataTable,
16
- LoadingIndicator,
17
- Static,
18
- )
19
-
20
- from gobby.tui.api_client import GobbyAPIClient
21
- from gobby.tui.ws_client import GobbyWebSocketClient
22
-
23
-
24
- class MetricsSummaryPanel(Widget):
25
- """Panel showing metrics summary."""
26
-
27
- DEFAULT_CSS = """
28
- MetricsSummaryPanel {
29
- height: auto;
30
- padding: 1;
31
- border: round #45475a;
32
- margin: 1;
33
- }
34
-
35
- MetricsSummaryPanel .summary-title {
36
- text-style: bold;
37
- color: #a78bfa;
38
- padding-bottom: 1;
39
- }
40
-
41
- MetricsSummaryPanel .summary-grid {
42
- layout: grid;
43
- grid-size: 4 1;
44
- grid-gutter: 2;
45
- }
46
-
47
- MetricsSummaryPanel .stat-box {
48
- height: 4;
49
- border: round #45475a;
50
- padding: 0 1;
51
- content-align: center middle;
52
- }
53
-
54
- MetricsSummaryPanel .stat-value {
55
- text-style: bold;
56
- color: #06b6d4;
57
- }
58
-
59
- MetricsSummaryPanel .stat-label {
60
- color: #a6adc8;
61
- }
62
- """
63
-
64
- metrics: reactive[dict[str, Any] | None] = reactive(None)
65
-
66
- def compose(self) -> ComposeResult:
67
- yield Static("📊 Summary", classes="summary-title")
68
-
69
- with Horizontal(classes="summary-grid"):
70
- # Total calls
71
- with Vertical(classes="stat-box"):
72
- total = self.metrics.get("total_calls", 0) if self.metrics else 0
73
- yield Static(str(total), classes="stat-value")
74
- yield Static("Total Calls", classes="stat-label")
75
-
76
- # Success rate
77
- with Vertical(classes="stat-box"):
78
- rate = self.metrics.get("success_rate", 0) if self.metrics else 0
79
- yield Static(f"{rate:.1%}", classes="stat-value")
80
- yield Static("Success Rate", classes="stat-label")
81
-
82
- # Avg response time
83
- with Vertical(classes="stat-box"):
84
- avg_time = self.metrics.get("avg_response_ms", 0) if self.metrics else 0
85
- yield Static(f"{avg_time:.0f}ms", classes="stat-value")
86
- yield Static("Avg Response", classes="stat-label")
87
-
88
- # Active servers
89
- with Vertical(classes="stat-box"):
90
- servers = self.metrics.get("active_servers", 0) if self.metrics else 0
91
- yield Static(str(servers), classes="stat-value")
92
- yield Static("Active Servers", classes="stat-label")
93
-
94
- def watch_metrics(self, metrics: dict[str, Any] | None) -> None:
95
- """Recompose when metrics change."""
96
- asyncio.create_task(self.recompose())
97
-
98
-
99
- class MetricsScreen(Widget):
100
- """Metrics screen showing tool usage statistics."""
101
-
102
- DEFAULT_CSS = """
103
- MetricsScreen {
104
- width: 1fr;
105
- height: 1fr;
106
- }
107
-
108
- MetricsScreen .screen-header {
109
- height: auto;
110
- padding: 1;
111
- background: #313244;
112
- }
113
-
114
- MetricsScreen .header-row {
115
- layout: horizontal;
116
- }
117
-
118
- MetricsScreen .panel-title {
119
- text-style: bold;
120
- color: #a78bfa;
121
- width: 1fr;
122
- }
123
-
124
- MetricsScreen .content-area {
125
- height: 1fr;
126
- padding: 1;
127
- }
128
-
129
- MetricsScreen #tools-table {
130
- height: 1fr;
131
- }
132
-
133
- MetricsScreen .loading-container {
134
- width: 1fr;
135
- height: 1fr;
136
- content-align: center middle;
137
- }
138
- """
139
-
140
- loading = reactive(True)
141
- metrics: reactive[dict[str, Any] | None] = reactive(None)
142
- tool_metrics: reactive[list[dict[str, Any]]] = reactive(list)
143
-
144
- def __init__(
145
- self,
146
- api_client: GobbyAPIClient,
147
- ws_client: GobbyWebSocketClient,
148
- **kwargs: Any,
149
- ) -> None:
150
- super().__init__(**kwargs)
151
- self.api_client = api_client
152
- self.ws_client = ws_client
153
-
154
- def compose(self) -> ComposeResult:
155
- with Vertical(classes="screen-header"):
156
- with Horizontal(classes="header-row"):
157
- yield Static("📈 Metrics", classes="panel-title")
158
- yield Button("Refresh", id="btn-refresh")
159
- yield Button("Export", id="btn-export")
160
-
161
- if self.loading:
162
- with Container(classes="loading-container"):
163
- yield LoadingIndicator()
164
- else:
165
- with Vertical(classes="content-area"):
166
- yield MetricsSummaryPanel(id="summary-panel")
167
- yield Static("Tool Usage", classes="panel-title")
168
- yield DataTable(id="tools-table")
169
-
170
- async def on_mount(self) -> None:
171
- """Load data when mounted."""
172
- await self.refresh_data()
173
-
174
- async def refresh_data(self) -> None:
175
- """Refresh metrics data."""
176
- try:
177
- async with GobbyAPIClient(self.api_client.base_url) as client:
178
- result = await client.call_tool(
179
- "gobby-metrics",
180
- "get_metrics",
181
- {},
182
- )
183
- self.metrics = result.get("summary", {})
184
- self.tool_metrics = result.get("tools", [])
185
-
186
- except Exception as e:
187
- self.notify(f"Failed to load metrics: {e}", severity="error")
188
- finally:
189
- self.loading = False
190
- await self.recompose()
191
- await self._update_summary()
192
- await self._setup_table()
193
-
194
- async def _update_summary(self) -> None:
195
- """Update the summary panel."""
196
- try:
197
- panel = self.query_one("#summary-panel", MetricsSummaryPanel)
198
- panel.metrics = self.metrics
199
- except NoMatches:
200
- pass # Widget may not be mounted yet
201
-
202
- async def _setup_table(self) -> None:
203
- """Set up and populate the tools table."""
204
- try:
205
- table = self.query_one("#tools-table", DataTable)
206
- table.clear(columns=True)
207
- table.add_columns("Server", "Tool", "Calls", "Success", "Avg Time")
208
- table.cursor_type = "row"
209
-
210
- for tool in self.tool_metrics:
211
- server = tool.get("server", "unknown")
212
- name = tool.get("name", "unknown")
213
- calls = str(tool.get("calls", 0))
214
- success = f"{tool.get('success_rate', 0):.1%}"
215
- avg_time = f"{tool.get('avg_ms', 0):.0f}ms"
216
-
217
- table.add_row(server, name, calls, success, avg_time)
218
-
219
- except NoMatches:
220
- pass # Table widget may not be mounted yet
221
-
222
- async def on_button_pressed(self, event: Button.Pressed) -> None:
223
- """Handle button presses."""
224
- button_id = event.button.id
225
-
226
- if button_id == "btn-refresh":
227
- self.loading = True
228
- await self.refresh_data()
229
-
230
- elif button_id == "btn-export":
231
- self.notify("Export coming soon", severity="information")