gobby 0.2.5__py3-none-any.whl → 0.2.7__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.
- gobby/__init__.py +1 -1
- gobby/adapters/__init__.py +2 -1
- gobby/adapters/claude_code.py +13 -4
- gobby/adapters/codex_impl/__init__.py +28 -0
- gobby/adapters/codex_impl/adapter.py +722 -0
- gobby/adapters/codex_impl/client.py +679 -0
- gobby/adapters/codex_impl/protocol.py +20 -0
- gobby/adapters/codex_impl/types.py +68 -0
- gobby/agents/definitions.py +11 -1
- gobby/agents/isolation.py +395 -0
- gobby/agents/runner.py +8 -0
- gobby/agents/sandbox.py +261 -0
- gobby/agents/spawn.py +42 -287
- gobby/agents/spawn_executor.py +385 -0
- gobby/agents/spawners/__init__.py +24 -0
- gobby/agents/spawners/command_builder.py +189 -0
- gobby/agents/spawners/embedded.py +21 -2
- gobby/agents/spawners/headless.py +21 -2
- gobby/agents/spawners/prompt_manager.py +125 -0
- gobby/cli/__init__.py +6 -0
- gobby/cli/clones.py +419 -0
- gobby/cli/conductor.py +266 -0
- gobby/cli/install.py +4 -4
- gobby/cli/installers/antigravity.py +3 -9
- gobby/cli/installers/claude.py +15 -9
- gobby/cli/installers/codex.py +2 -8
- gobby/cli/installers/gemini.py +8 -8
- gobby/cli/installers/shared.py +175 -13
- gobby/cli/sessions.py +1 -1
- gobby/cli/skills.py +858 -0
- gobby/cli/tasks/ai.py +0 -440
- gobby/cli/tasks/crud.py +44 -6
- gobby/cli/tasks/main.py +0 -4
- gobby/cli/tui.py +2 -2
- gobby/cli/utils.py +12 -5
- gobby/clones/__init__.py +13 -0
- gobby/clones/git.py +547 -0
- gobby/conductor/__init__.py +16 -0
- gobby/conductor/alerts.py +135 -0
- gobby/conductor/loop.py +164 -0
- gobby/conductor/monitors/__init__.py +11 -0
- gobby/conductor/monitors/agents.py +116 -0
- gobby/conductor/monitors/tasks.py +155 -0
- gobby/conductor/pricing.py +234 -0
- gobby/conductor/token_tracker.py +160 -0
- gobby/config/__init__.py +12 -97
- gobby/config/app.py +69 -91
- gobby/config/extensions.py +2 -2
- gobby/config/features.py +7 -130
- gobby/config/search.py +110 -0
- gobby/config/servers.py +1 -1
- gobby/config/skills.py +43 -0
- gobby/config/tasks.py +9 -41
- gobby/hooks/__init__.py +0 -13
- gobby/hooks/event_handlers.py +188 -2
- gobby/hooks/hook_manager.py +50 -4
- gobby/hooks/plugins.py +1 -1
- gobby/hooks/skill_manager.py +130 -0
- gobby/hooks/webhooks.py +1 -1
- gobby/install/claude/hooks/hook_dispatcher.py +4 -4
- gobby/install/codex/hooks/hook_dispatcher.py +1 -1
- gobby/install/gemini/hooks/hook_dispatcher.py +87 -12
- gobby/llm/claude.py +22 -34
- gobby/llm/claude_executor.py +46 -256
- gobby/llm/codex_executor.py +59 -291
- gobby/llm/executor.py +21 -0
- gobby/llm/gemini.py +134 -110
- gobby/llm/litellm_executor.py +143 -6
- gobby/llm/resolver.py +98 -35
- gobby/mcp_proxy/importer.py +62 -4
- gobby/mcp_proxy/instructions.py +56 -0
- gobby/mcp_proxy/models.py +15 -0
- gobby/mcp_proxy/registries.py +68 -8
- gobby/mcp_proxy/server.py +33 -3
- gobby/mcp_proxy/services/recommendation.py +43 -11
- gobby/mcp_proxy/services/tool_proxy.py +81 -1
- gobby/mcp_proxy/stdio.py +2 -1
- gobby/mcp_proxy/tools/__init__.py +0 -2
- gobby/mcp_proxy/tools/agent_messaging.py +317 -0
- gobby/mcp_proxy/tools/agents.py +31 -731
- gobby/mcp_proxy/tools/clones.py +518 -0
- gobby/mcp_proxy/tools/memory.py +3 -26
- gobby/mcp_proxy/tools/metrics.py +65 -1
- gobby/mcp_proxy/tools/orchestration/__init__.py +3 -0
- gobby/mcp_proxy/tools/orchestration/cleanup.py +151 -0
- gobby/mcp_proxy/tools/orchestration/wait.py +467 -0
- gobby/mcp_proxy/tools/sessions/__init__.py +14 -0
- gobby/mcp_proxy/tools/sessions/_commits.py +232 -0
- gobby/mcp_proxy/tools/sessions/_crud.py +253 -0
- gobby/mcp_proxy/tools/sessions/_factory.py +63 -0
- gobby/mcp_proxy/tools/sessions/_handoff.py +499 -0
- gobby/mcp_proxy/tools/sessions/_messages.py +138 -0
- gobby/mcp_proxy/tools/skills/__init__.py +616 -0
- gobby/mcp_proxy/tools/spawn_agent.py +417 -0
- gobby/mcp_proxy/tools/task_orchestration.py +7 -0
- gobby/mcp_proxy/tools/task_readiness.py +14 -0
- gobby/mcp_proxy/tools/task_sync.py +1 -1
- gobby/mcp_proxy/tools/tasks/_context.py +0 -20
- gobby/mcp_proxy/tools/tasks/_crud.py +91 -4
- gobby/mcp_proxy/tools/tasks/_expansion.py +348 -0
- gobby/mcp_proxy/tools/tasks/_factory.py +6 -16
- gobby/mcp_proxy/tools/tasks/_lifecycle.py +110 -45
- gobby/mcp_proxy/tools/tasks/_lifecycle_validation.py +18 -29
- gobby/mcp_proxy/tools/workflows.py +1 -1
- gobby/mcp_proxy/tools/worktrees.py +0 -338
- gobby/memory/backends/__init__.py +6 -1
- gobby/memory/backends/mem0.py +6 -1
- gobby/memory/extractor.py +477 -0
- gobby/memory/ingestion/__init__.py +5 -0
- gobby/memory/ingestion/multimodal.py +221 -0
- gobby/memory/manager.py +73 -285
- gobby/memory/search/__init__.py +10 -0
- gobby/memory/search/coordinator.py +248 -0
- gobby/memory/services/__init__.py +5 -0
- gobby/memory/services/crossref.py +142 -0
- gobby/prompts/loader.py +5 -2
- gobby/runner.py +37 -16
- gobby/search/__init__.py +48 -6
- gobby/search/backends/__init__.py +159 -0
- gobby/search/backends/embedding.py +225 -0
- gobby/search/embeddings.py +238 -0
- gobby/search/models.py +148 -0
- gobby/search/unified.py +496 -0
- gobby/servers/http.py +24 -12
- gobby/servers/routes/admin.py +294 -0
- gobby/servers/routes/mcp/endpoints/__init__.py +61 -0
- gobby/servers/routes/mcp/endpoints/discovery.py +405 -0
- gobby/servers/routes/mcp/endpoints/execution.py +568 -0
- gobby/servers/routes/mcp/endpoints/registry.py +378 -0
- gobby/servers/routes/mcp/endpoints/server.py +304 -0
- gobby/servers/routes/mcp/hooks.py +1 -1
- gobby/servers/routes/mcp/tools.py +48 -1317
- gobby/servers/websocket.py +2 -2
- gobby/sessions/analyzer.py +2 -0
- gobby/sessions/lifecycle.py +1 -1
- gobby/sessions/processor.py +10 -0
- gobby/sessions/transcripts/base.py +2 -0
- gobby/sessions/transcripts/claude.py +79 -10
- gobby/skills/__init__.py +91 -0
- gobby/skills/loader.py +685 -0
- gobby/skills/manager.py +384 -0
- gobby/skills/parser.py +286 -0
- gobby/skills/search.py +463 -0
- gobby/skills/sync.py +119 -0
- gobby/skills/updater.py +385 -0
- gobby/skills/validator.py +368 -0
- gobby/storage/clones.py +378 -0
- gobby/storage/database.py +1 -1
- gobby/storage/memories.py +43 -13
- gobby/storage/migrations.py +162 -201
- gobby/storage/sessions.py +116 -7
- gobby/storage/skills.py +782 -0
- gobby/storage/tasks/_crud.py +4 -4
- gobby/storage/tasks/_lifecycle.py +57 -7
- gobby/storage/tasks/_manager.py +14 -5
- gobby/storage/tasks/_models.py +8 -3
- gobby/sync/memories.py +40 -5
- gobby/sync/tasks.py +83 -6
- gobby/tasks/__init__.py +1 -2
- gobby/tasks/external_validator.py +1 -1
- gobby/tasks/validation.py +46 -35
- gobby/tools/summarizer.py +91 -10
- gobby/tui/api_client.py +4 -7
- gobby/tui/app.py +5 -3
- gobby/tui/screens/orchestrator.py +1 -2
- gobby/tui/screens/tasks.py +2 -4
- gobby/tui/ws_client.py +1 -1
- gobby/utils/daemon_client.py +2 -2
- gobby/utils/project_context.py +2 -3
- gobby/utils/status.py +13 -0
- gobby/workflows/actions.py +221 -1135
- gobby/workflows/artifact_actions.py +31 -0
- gobby/workflows/autonomous_actions.py +11 -0
- gobby/workflows/context_actions.py +93 -1
- gobby/workflows/detection_helpers.py +115 -31
- gobby/workflows/enforcement/__init__.py +47 -0
- gobby/workflows/enforcement/blocking.py +269 -0
- gobby/workflows/enforcement/commit_policy.py +283 -0
- gobby/workflows/enforcement/handlers.py +269 -0
- gobby/workflows/{task_enforcement_actions.py → enforcement/task_policy.py} +29 -388
- gobby/workflows/engine.py +13 -2
- gobby/workflows/git_utils.py +106 -0
- gobby/workflows/lifecycle_evaluator.py +29 -1
- gobby/workflows/llm_actions.py +30 -0
- gobby/workflows/loader.py +19 -6
- gobby/workflows/mcp_actions.py +20 -1
- gobby/workflows/memory_actions.py +154 -0
- gobby/workflows/safe_evaluator.py +183 -0
- gobby/workflows/session_actions.py +44 -0
- gobby/workflows/state_actions.py +60 -1
- gobby/workflows/stop_signal_actions.py +55 -0
- gobby/workflows/summary_actions.py +111 -1
- gobby/workflows/task_sync_actions.py +347 -0
- gobby/workflows/todo_actions.py +34 -1
- gobby/workflows/webhook_actions.py +185 -0
- {gobby-0.2.5.dist-info → gobby-0.2.7.dist-info}/METADATA +87 -21
- {gobby-0.2.5.dist-info → gobby-0.2.7.dist-info}/RECORD +201 -172
- {gobby-0.2.5.dist-info → gobby-0.2.7.dist-info}/WHEEL +1 -1
- gobby/adapters/codex.py +0 -1292
- gobby/install/claude/commands/gobby/bug.md +0 -51
- gobby/install/claude/commands/gobby/chore.md +0 -51
- gobby/install/claude/commands/gobby/epic.md +0 -52
- gobby/install/claude/commands/gobby/eval.md +0 -235
- gobby/install/claude/commands/gobby/feat.md +0 -49
- gobby/install/claude/commands/gobby/nit.md +0 -52
- gobby/install/claude/commands/gobby/ref.md +0 -52
- gobby/install/codex/prompts/forget.md +0 -7
- gobby/install/codex/prompts/memories.md +0 -7
- gobby/install/codex/prompts/recall.md +0 -7
- gobby/install/codex/prompts/remember.md +0 -13
- gobby/llm/gemini_executor.py +0 -339
- gobby/mcp_proxy/tools/session_messages.py +0 -1056
- gobby/mcp_proxy/tools/task_expansion.py +0 -591
- gobby/prompts/defaults/expansion/system.md +0 -119
- gobby/prompts/defaults/expansion/user.md +0 -48
- gobby/prompts/defaults/external_validation/agent.md +0 -72
- gobby/prompts/defaults/external_validation/external.md +0 -63
- gobby/prompts/defaults/external_validation/spawn.md +0 -83
- gobby/prompts/defaults/external_validation/system.md +0 -6
- gobby/prompts/defaults/features/import_mcp.md +0 -22
- gobby/prompts/defaults/features/import_mcp_github.md +0 -17
- gobby/prompts/defaults/features/import_mcp_search.md +0 -16
- gobby/prompts/defaults/features/recommend_tools.md +0 -32
- gobby/prompts/defaults/features/recommend_tools_hybrid.md +0 -35
- gobby/prompts/defaults/features/recommend_tools_llm.md +0 -30
- gobby/prompts/defaults/features/server_description.md +0 -20
- gobby/prompts/defaults/features/server_description_system.md +0 -6
- gobby/prompts/defaults/features/task_description.md +0 -31
- gobby/prompts/defaults/features/task_description_system.md +0 -6
- gobby/prompts/defaults/features/tool_summary.md +0 -17
- gobby/prompts/defaults/features/tool_summary_system.md +0 -6
- gobby/prompts/defaults/research/step.md +0 -58
- gobby/prompts/defaults/validation/criteria.md +0 -47
- gobby/prompts/defaults/validation/validate.md +0 -38
- gobby/storage/migrations_legacy.py +0 -1359
- gobby/tasks/context.py +0 -747
- gobby/tasks/criteria.py +0 -342
- gobby/tasks/expansion.py +0 -626
- gobby/tasks/prompts/expand.py +0 -327
- gobby/tasks/research.py +0 -421
- gobby/tasks/tdd.py +0 -352
- {gobby-0.2.5.dist-info → gobby-0.2.7.dist-info}/entry_points.txt +0 -0
- {gobby-0.2.5.dist-info → gobby-0.2.7.dist-info}/licenses/LICENSE.md +0 -0
- {gobby-0.2.5.dist-info → gobby-0.2.7.dist-info}/top_level.txt +0 -0
gobby/servers/http.py
CHANGED
|
@@ -16,7 +16,7 @@ from fastapi import FastAPI, HTTPException, Request
|
|
|
16
16
|
from fastapi.middleware.cors import CORSMiddleware
|
|
17
17
|
from fastapi.responses import JSONResponse
|
|
18
18
|
|
|
19
|
-
from gobby.adapters.
|
|
19
|
+
from gobby.adapters.codex_impl.adapter import CodexAdapter
|
|
20
20
|
from gobby.hooks.broadcaster import HookEventBroadcaster
|
|
21
21
|
from gobby.hooks.hook_manager import HookManager
|
|
22
22
|
from gobby.llm import LLMService, create_llm_service
|
|
@@ -25,9 +25,6 @@ from gobby.mcp_proxy.semantic_search import SemanticToolSearch
|
|
|
25
25
|
from gobby.mcp_proxy.server import GobbyDaemonTools, create_mcp_server
|
|
26
26
|
from gobby.mcp_proxy.services.tool_filter import ToolFilterService
|
|
27
27
|
from gobby.memory.manager import MemoryManager
|
|
28
|
-
|
|
29
|
-
# Re-export for backward compatibility
|
|
30
|
-
from gobby.servers.models import SessionRegisterRequest # noqa: F401
|
|
31
28
|
from gobby.storage.sessions import LocalSessionManager
|
|
32
29
|
from gobby.storage.tasks import LocalTaskManager
|
|
33
30
|
from gobby.sync.tasks import TaskSyncManager
|
|
@@ -62,11 +59,13 @@ class HTTPServer:
|
|
|
62
59
|
memory_manager: "MemoryManager | None" = None,
|
|
63
60
|
llm_service: "LLMService | None" = None,
|
|
64
61
|
memory_sync_manager: Any | None = None,
|
|
65
|
-
task_expander: Any | None = None,
|
|
66
62
|
task_validator: Any | None = None,
|
|
67
63
|
metrics_manager: Any | None = None,
|
|
68
64
|
agent_runner: Any | None = None,
|
|
69
65
|
worktree_storage: Any | None = None,
|
|
66
|
+
clone_storage: Any | None = None,
|
|
67
|
+
git_manager: Any | None = None,
|
|
68
|
+
project_id: str | None = None,
|
|
70
69
|
) -> None:
|
|
71
70
|
"""
|
|
72
71
|
Initialize HTTP server.
|
|
@@ -103,11 +102,13 @@ class HTTPServer:
|
|
|
103
102
|
self.websocket_server = websocket_server
|
|
104
103
|
self.llm_service = llm_service
|
|
105
104
|
self.memory_sync_manager = memory_sync_manager
|
|
106
|
-
self.task_expander = task_expander
|
|
107
105
|
self.task_validator = task_validator
|
|
108
106
|
self.metrics_manager = metrics_manager
|
|
109
107
|
self.agent_runner = agent_runner
|
|
110
108
|
self.worktree_storage = worktree_storage
|
|
109
|
+
self.clone_storage = clone_storage
|
|
110
|
+
self.git_manager = git_manager
|
|
111
|
+
self.project_id = project_id
|
|
111
112
|
|
|
112
113
|
# Initialize WebSocket broadcaster
|
|
113
114
|
# Note: websocket_server might be None if disabled
|
|
@@ -132,7 +133,7 @@ class HTTPServer:
|
|
|
132
133
|
self._mcp_db_manager = mcp_db_manager
|
|
133
134
|
if mcp_manager:
|
|
134
135
|
# Determine WebSocket port
|
|
135
|
-
ws_port =
|
|
136
|
+
ws_port = 60888
|
|
136
137
|
if config and hasattr(config, "websocket") and config.websocket:
|
|
137
138
|
ws_port = config.websocket.port
|
|
138
139
|
|
|
@@ -147,14 +148,17 @@ class HTTPServer:
|
|
|
147
148
|
# Create merge managers if db available
|
|
148
149
|
merge_storage = None
|
|
149
150
|
merge_resolver = None
|
|
151
|
+
inter_session_message_manager = None
|
|
150
152
|
if mcp_db_manager:
|
|
153
|
+
from gobby.storage.inter_session_messages import InterSessionMessageManager
|
|
151
154
|
from gobby.storage.merge_resolutions import MergeResolutionManager
|
|
152
155
|
from gobby.worktrees.merge.resolver import MergeResolver
|
|
153
156
|
|
|
154
157
|
merge_storage = MergeResolutionManager(mcp_db_manager.db)
|
|
155
158
|
merge_resolver = MergeResolver()
|
|
156
159
|
merge_resolver._llm_service = self.llm_service
|
|
157
|
-
|
|
160
|
+
inter_session_message_manager = InterSessionMessageManager(mcp_db_manager.db)
|
|
161
|
+
logger.debug("Merge resolution and inter-session messaging subsystems initialized")
|
|
158
162
|
|
|
159
163
|
# Setup internal registries (gobby-tasks, gobby-memory, etc.)
|
|
160
164
|
self._internal_manager = setup_internal_registries(
|
|
@@ -163,7 +167,6 @@ class HTTPServer:
|
|
|
163
167
|
memory_manager=memory_manager,
|
|
164
168
|
task_manager=task_manager,
|
|
165
169
|
sync_manager=task_sync_manager,
|
|
166
|
-
task_expander=self.task_expander,
|
|
167
170
|
task_validator=self.task_validator,
|
|
168
171
|
message_manager=message_manager,
|
|
169
172
|
local_session_manager=session_manager,
|
|
@@ -171,11 +174,13 @@ class HTTPServer:
|
|
|
171
174
|
llm_service=self.llm_service,
|
|
172
175
|
agent_runner=self.agent_runner,
|
|
173
176
|
worktree_storage=self.worktree_storage,
|
|
174
|
-
|
|
177
|
+
clone_storage=self.clone_storage,
|
|
178
|
+
git_manager=self.git_manager,
|
|
175
179
|
merge_storage=merge_storage,
|
|
176
180
|
merge_resolver=merge_resolver,
|
|
177
|
-
project_id=
|
|
181
|
+
project_id=self.project_id,
|
|
178
182
|
tool_proxy_getter=tool_proxy_getter,
|
|
183
|
+
inter_session_message_manager=inter_session_message_manager,
|
|
179
184
|
)
|
|
180
185
|
registry_count = len(self._internal_manager)
|
|
181
186
|
logger.debug(f"Internal registries initialized: {registry_count} registries")
|
|
@@ -235,6 +240,13 @@ class HTTPServer:
|
|
|
235
240
|
self._metrics = get_metrics_collector()
|
|
236
241
|
self._daemon: Any = None # Set externally by daemon
|
|
237
242
|
|
|
243
|
+
@property
|
|
244
|
+
def tool_proxy(self) -> Any:
|
|
245
|
+
"""Get the ToolProxyService instance for routing tool calls with error enrichment."""
|
|
246
|
+
if self._tools_handler is not None:
|
|
247
|
+
return self._tools_handler.tool_proxy
|
|
248
|
+
return None
|
|
249
|
+
|
|
238
250
|
def _resolve_project_id(self, project_id: str | None, cwd: str | None) -> str:
|
|
239
251
|
"""
|
|
240
252
|
Resolve project_id from cwd if not provided.
|
|
@@ -548,7 +560,7 @@ class HTTPServer:
|
|
|
548
560
|
|
|
549
561
|
|
|
550
562
|
async def create_server(
|
|
551
|
-
port: int =
|
|
563
|
+
port: int = 60887,
|
|
552
564
|
test_mode: bool = False,
|
|
553
565
|
mcp_manager: Any | None = None,
|
|
554
566
|
config: Any | None = None,
|
gobby/servers/routes/admin.py
CHANGED
|
@@ -13,6 +13,7 @@ from typing import TYPE_CHECKING, Any
|
|
|
13
13
|
import psutil
|
|
14
14
|
from fastapi import APIRouter
|
|
15
15
|
from fastapi.responses import PlainTextResponse
|
|
16
|
+
from pydantic import BaseModel
|
|
16
17
|
|
|
17
18
|
from gobby.utils.metrics import Counter, get_metrics_collector
|
|
18
19
|
from gobby.utils.version import get_version
|
|
@@ -174,6 +175,19 @@ def create_admin_router(server: "HTTPServer") -> APIRouter:
|
|
|
174
175
|
except Exception as e:
|
|
175
176
|
logger.warning(f"Failed to get memory stats: {e}")
|
|
176
177
|
|
|
178
|
+
# Get skills statistics
|
|
179
|
+
skills_stats: dict[str, Any] = {"total": 0}
|
|
180
|
+
if server._internal_manager:
|
|
181
|
+
try:
|
|
182
|
+
for registry in server._internal_manager.get_all_registries():
|
|
183
|
+
if registry.name == "gobby-skills":
|
|
184
|
+
result = await registry.call("list_skills", {"limit": 10000})
|
|
185
|
+
if result.get("success"):
|
|
186
|
+
skills_stats["total"] = result.get("count", 0)
|
|
187
|
+
break
|
|
188
|
+
except Exception as e:
|
|
189
|
+
logger.warning(f"Failed to get skills stats: {e}")
|
|
190
|
+
|
|
177
191
|
# Get plugin status
|
|
178
192
|
plugin_stats: dict[str, Any] = {"enabled": False, "loaded": 0, "handlers": 0}
|
|
179
193
|
if hasattr(server, "_hook_manager") and server._hook_manager is not None:
|
|
@@ -217,6 +231,7 @@ def create_admin_router(server: "HTTPServer") -> APIRouter:
|
|
|
217
231
|
"sessions": session_stats,
|
|
218
232
|
"tasks": task_stats,
|
|
219
233
|
"memory": memory_stats,
|
|
234
|
+
"skills": skills_stats,
|
|
220
235
|
"plugins": plugin_stats,
|
|
221
236
|
"response_time_ms": response_time_ms,
|
|
222
237
|
}
|
|
@@ -413,4 +428,283 @@ def create_admin_router(server: "HTTPServer") -> APIRouter:
|
|
|
413
428
|
|
|
414
429
|
raise HTTPException(status_code=500, detail=str(e)) from e
|
|
415
430
|
|
|
431
|
+
# --- Test endpoints (for E2E testing) ---
|
|
432
|
+
|
|
433
|
+
class TestProjectRegisterRequest(BaseModel):
|
|
434
|
+
"""Request model for registering a test project."""
|
|
435
|
+
|
|
436
|
+
project_id: str
|
|
437
|
+
name: str
|
|
438
|
+
repo_path: str | None = None
|
|
439
|
+
|
|
440
|
+
@router.post("/test/register-project")
|
|
441
|
+
async def register_test_project(request: TestProjectRegisterRequest) -> dict[str, Any]:
|
|
442
|
+
"""
|
|
443
|
+
Register a test project in the database.
|
|
444
|
+
|
|
445
|
+
This endpoint is for E2E testing. It ensures the project exists
|
|
446
|
+
in the projects table so sessions can be created with valid project_ids.
|
|
447
|
+
|
|
448
|
+
Args:
|
|
449
|
+
request: Project registration details
|
|
450
|
+
|
|
451
|
+
Returns:
|
|
452
|
+
Registration confirmation
|
|
453
|
+
"""
|
|
454
|
+
from fastapi import HTTPException
|
|
455
|
+
|
|
456
|
+
from gobby.storage.projects import LocalProjectManager
|
|
457
|
+
|
|
458
|
+
# Guard: Only available in test mode
|
|
459
|
+
if not server.test_mode:
|
|
460
|
+
raise HTTPException(
|
|
461
|
+
status_code=403, detail="Test endpoints only available in test mode"
|
|
462
|
+
)
|
|
463
|
+
|
|
464
|
+
start_time = time.perf_counter()
|
|
465
|
+
metrics = get_metrics_collector()
|
|
466
|
+
metrics.inc_counter("http_requests_total")
|
|
467
|
+
|
|
468
|
+
try:
|
|
469
|
+
# Use server's session manager database to avoid creating separate connections
|
|
470
|
+
if server.session_manager is None:
|
|
471
|
+
raise HTTPException(status_code=503, detail="Session manager not available")
|
|
472
|
+
|
|
473
|
+
db = server.session_manager.db
|
|
474
|
+
|
|
475
|
+
project_manager = LocalProjectManager(db)
|
|
476
|
+
|
|
477
|
+
# Check if project exists
|
|
478
|
+
existing = project_manager.get(request.project_id)
|
|
479
|
+
if existing:
|
|
480
|
+
return {
|
|
481
|
+
"status": "already_exists",
|
|
482
|
+
"project_id": existing.id,
|
|
483
|
+
"name": existing.name,
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
# Create the project with the specific ID
|
|
487
|
+
from datetime import UTC, datetime
|
|
488
|
+
|
|
489
|
+
now = datetime.now(UTC).isoformat()
|
|
490
|
+
db.execute(
|
|
491
|
+
"""
|
|
492
|
+
INSERT INTO projects (id, name, repo_path, created_at, updated_at)
|
|
493
|
+
VALUES (?, ?, ?, ?, ?)
|
|
494
|
+
""",
|
|
495
|
+
(request.project_id, request.name, request.repo_path, now, now),
|
|
496
|
+
)
|
|
497
|
+
|
|
498
|
+
response_time_ms = (time.perf_counter() - start_time) * 1000
|
|
499
|
+
|
|
500
|
+
return {
|
|
501
|
+
"status": "success",
|
|
502
|
+
"message": f"Registered test project {request.project_id}",
|
|
503
|
+
"project_id": request.project_id,
|
|
504
|
+
"name": request.name,
|
|
505
|
+
"response_time_ms": response_time_ms,
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
except Exception as e:
|
|
509
|
+
metrics.inc_counter("http_requests_errors_total")
|
|
510
|
+
logger.error(f"Error registering test project: {e}", exc_info=True)
|
|
511
|
+
from fastapi import HTTPException
|
|
512
|
+
|
|
513
|
+
raise HTTPException(status_code=500, detail=str(e)) from e
|
|
514
|
+
|
|
515
|
+
class TestAgentRegisterRequest(BaseModel):
|
|
516
|
+
"""Request model for registering a test agent."""
|
|
517
|
+
|
|
518
|
+
run_id: str
|
|
519
|
+
session_id: str
|
|
520
|
+
parent_session_id: str
|
|
521
|
+
mode: str = "terminal"
|
|
522
|
+
|
|
523
|
+
@router.post("/test/register-agent")
|
|
524
|
+
async def register_test_agent(request: TestAgentRegisterRequest) -> dict[str, Any]:
|
|
525
|
+
"""
|
|
526
|
+
Register a test agent in the running agent registry.
|
|
527
|
+
|
|
528
|
+
This endpoint is for E2E testing of inter-agent messaging.
|
|
529
|
+
It allows tests to set up parent-child agent relationships
|
|
530
|
+
without actually spawning agent processes.
|
|
531
|
+
|
|
532
|
+
Args:
|
|
533
|
+
request: Agent registration details
|
|
534
|
+
|
|
535
|
+
Returns:
|
|
536
|
+
Registration confirmation
|
|
537
|
+
"""
|
|
538
|
+
from fastapi import HTTPException
|
|
539
|
+
|
|
540
|
+
from gobby.agents.registry import RunningAgent, get_running_agent_registry
|
|
541
|
+
|
|
542
|
+
# Guard: Only available in test mode
|
|
543
|
+
if not server.test_mode:
|
|
544
|
+
raise HTTPException(
|
|
545
|
+
status_code=403, detail="Test endpoints only available in test mode"
|
|
546
|
+
)
|
|
547
|
+
|
|
548
|
+
start_time = time.perf_counter()
|
|
549
|
+
metrics = get_metrics_collector()
|
|
550
|
+
metrics.inc_counter("http_requests_total")
|
|
551
|
+
|
|
552
|
+
try:
|
|
553
|
+
registry = get_running_agent_registry()
|
|
554
|
+
|
|
555
|
+
# Create and register the agent
|
|
556
|
+
running_agent = RunningAgent(
|
|
557
|
+
run_id=request.run_id,
|
|
558
|
+
session_id=request.session_id,
|
|
559
|
+
parent_session_id=request.parent_session_id,
|
|
560
|
+
mode=request.mode,
|
|
561
|
+
)
|
|
562
|
+
registry.add(running_agent)
|
|
563
|
+
|
|
564
|
+
response_time_ms = (time.perf_counter() - start_time) * 1000
|
|
565
|
+
|
|
566
|
+
return {
|
|
567
|
+
"status": "success",
|
|
568
|
+
"message": f"Registered test agent {request.run_id}",
|
|
569
|
+
"agent": running_agent.to_dict(),
|
|
570
|
+
"response_time_ms": response_time_ms,
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
except Exception as e:
|
|
574
|
+
metrics.inc_counter("http_requests_errors_total")
|
|
575
|
+
logger.error(f"Error registering test agent: {e}", exc_info=True)
|
|
576
|
+
from fastapi import HTTPException
|
|
577
|
+
|
|
578
|
+
raise HTTPException(status_code=500, detail=str(e)) from e
|
|
579
|
+
|
|
580
|
+
@router.delete("/test/unregister-agent/{run_id}")
|
|
581
|
+
async def unregister_test_agent(run_id: str) -> dict[str, Any]:
|
|
582
|
+
"""
|
|
583
|
+
Unregister a test agent from the running agent registry.
|
|
584
|
+
|
|
585
|
+
Args:
|
|
586
|
+
run_id: The agent run ID to remove
|
|
587
|
+
|
|
588
|
+
Returns:
|
|
589
|
+
Unregistration confirmation
|
|
590
|
+
"""
|
|
591
|
+
from fastapi import HTTPException
|
|
592
|
+
|
|
593
|
+
from gobby.agents.registry import get_running_agent_registry
|
|
594
|
+
|
|
595
|
+
# Guard: Only available in test mode
|
|
596
|
+
if not server.test_mode:
|
|
597
|
+
raise HTTPException(
|
|
598
|
+
status_code=403, detail="Test endpoints only available in test mode"
|
|
599
|
+
)
|
|
600
|
+
|
|
601
|
+
start_time = time.perf_counter()
|
|
602
|
+
metrics = get_metrics_collector()
|
|
603
|
+
metrics.inc_counter("http_requests_total")
|
|
604
|
+
|
|
605
|
+
try:
|
|
606
|
+
registry = get_running_agent_registry()
|
|
607
|
+
agent = registry.remove(run_id)
|
|
608
|
+
|
|
609
|
+
response_time_ms = (time.perf_counter() - start_time) * 1000
|
|
610
|
+
|
|
611
|
+
if agent:
|
|
612
|
+
return {
|
|
613
|
+
"status": "success",
|
|
614
|
+
"message": f"Unregistered test agent {run_id}",
|
|
615
|
+
"response_time_ms": response_time_ms,
|
|
616
|
+
}
|
|
617
|
+
else:
|
|
618
|
+
return {
|
|
619
|
+
"status": "not_found",
|
|
620
|
+
"message": f"Agent {run_id} not found in registry",
|
|
621
|
+
"response_time_ms": response_time_ms,
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
except Exception as e:
|
|
625
|
+
metrics.inc_counter("http_requests_errors_total")
|
|
626
|
+
logger.error(f"Error unregistering test agent: {e}", exc_info=True)
|
|
627
|
+
from fastapi import HTTPException
|
|
628
|
+
|
|
629
|
+
raise HTTPException(status_code=500, detail=str(e)) from e
|
|
630
|
+
|
|
631
|
+
class TestSessionUsageRequest(BaseModel):
|
|
632
|
+
"""Request body for setting test session usage."""
|
|
633
|
+
|
|
634
|
+
session_id: str
|
|
635
|
+
input_tokens: int = 0
|
|
636
|
+
output_tokens: int = 0
|
|
637
|
+
cache_creation_tokens: int = 0
|
|
638
|
+
cache_read_tokens: int = 0
|
|
639
|
+
total_cost_usd: float = 0.0
|
|
640
|
+
|
|
641
|
+
@router.post("/test/set-session-usage")
|
|
642
|
+
async def set_test_session_usage(request: TestSessionUsageRequest) -> dict[str, Any]:
|
|
643
|
+
"""
|
|
644
|
+
Set usage statistics for a test session.
|
|
645
|
+
|
|
646
|
+
This endpoint is for E2E testing of token budget throttling.
|
|
647
|
+
It allows tests to set session usage values to simulate
|
|
648
|
+
budget consumption.
|
|
649
|
+
|
|
650
|
+
Args:
|
|
651
|
+
request: Session usage details
|
|
652
|
+
|
|
653
|
+
Returns:
|
|
654
|
+
Update confirmation
|
|
655
|
+
"""
|
|
656
|
+
from fastapi import HTTPException
|
|
657
|
+
|
|
658
|
+
# Guard: Only available in test mode
|
|
659
|
+
if not server.test_mode:
|
|
660
|
+
raise HTTPException(
|
|
661
|
+
status_code=403, detail="Test endpoints only available in test mode"
|
|
662
|
+
)
|
|
663
|
+
|
|
664
|
+
start_time = time.perf_counter()
|
|
665
|
+
metrics = get_metrics_collector()
|
|
666
|
+
metrics.inc_counter("http_requests_total")
|
|
667
|
+
|
|
668
|
+
try:
|
|
669
|
+
if server.session_manager is None:
|
|
670
|
+
raise HTTPException(status_code=503, detail="Session manager not available")
|
|
671
|
+
|
|
672
|
+
success = server.session_manager.update_usage(
|
|
673
|
+
session_id=request.session_id,
|
|
674
|
+
input_tokens=request.input_tokens,
|
|
675
|
+
output_tokens=request.output_tokens,
|
|
676
|
+
cache_creation_tokens=request.cache_creation_tokens,
|
|
677
|
+
cache_read_tokens=request.cache_read_tokens,
|
|
678
|
+
total_cost_usd=request.total_cost_usd,
|
|
679
|
+
)
|
|
680
|
+
|
|
681
|
+
response_time_ms = (time.perf_counter() - start_time) * 1000
|
|
682
|
+
|
|
683
|
+
if success:
|
|
684
|
+
return {
|
|
685
|
+
"status": "success",
|
|
686
|
+
"session_id": request.session_id,
|
|
687
|
+
"usage_set": {
|
|
688
|
+
"input_tokens": request.input_tokens,
|
|
689
|
+
"output_tokens": request.output_tokens,
|
|
690
|
+
"cache_creation_tokens": request.cache_creation_tokens,
|
|
691
|
+
"cache_read_tokens": request.cache_read_tokens,
|
|
692
|
+
"total_cost_usd": request.total_cost_usd,
|
|
693
|
+
},
|
|
694
|
+
"response_time_ms": response_time_ms,
|
|
695
|
+
}
|
|
696
|
+
else:
|
|
697
|
+
return {
|
|
698
|
+
"status": "not_found",
|
|
699
|
+
"message": f"Session {request.session_id} not found",
|
|
700
|
+
"response_time_ms": response_time_ms,
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
except Exception as e:
|
|
704
|
+
metrics.inc_counter("http_requests_errors_total")
|
|
705
|
+
logger.error(f"Error setting test session usage: {e}", exc_info=True)
|
|
706
|
+
from fastapi import HTTPException
|
|
707
|
+
|
|
708
|
+
raise HTTPException(status_code=500, detail=str(e)) from e
|
|
709
|
+
|
|
416
710
|
return router
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MCP endpoint modules for the Gobby HTTP server.
|
|
3
|
+
|
|
4
|
+
This package contains decomposed endpoint handlers extracted from tools.py
|
|
5
|
+
using the Strangler Fig pattern. Each module handles a specific domain:
|
|
6
|
+
|
|
7
|
+
- discovery: Tool and server listing endpoints
|
|
8
|
+
- execution: Tool invocation endpoints
|
|
9
|
+
- server: Server management (add/remove/import)
|
|
10
|
+
- registry: Tool embedding, status, and refresh
|
|
11
|
+
|
|
12
|
+
External modules should import `create_mcp_router` from the parent package:
|
|
13
|
+
from gobby.servers.routes.mcp import create_mcp_router
|
|
14
|
+
|
|
15
|
+
For direct endpoint access (e.g., testing), import from submodules:
|
|
16
|
+
from gobby.servers.routes.mcp.endpoints.discovery import list_all_mcp_tools
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from gobby.servers.routes.mcp.endpoints.discovery import (
|
|
20
|
+
list_all_mcp_tools,
|
|
21
|
+
recommend_mcp_tools,
|
|
22
|
+
search_mcp_tools,
|
|
23
|
+
)
|
|
24
|
+
from gobby.servers.routes.mcp.endpoints.execution import (
|
|
25
|
+
call_mcp_tool,
|
|
26
|
+
get_tool_schema,
|
|
27
|
+
list_mcp_tools,
|
|
28
|
+
mcp_proxy,
|
|
29
|
+
)
|
|
30
|
+
from gobby.servers.routes.mcp.endpoints.registry import (
|
|
31
|
+
embed_mcp_tools,
|
|
32
|
+
get_mcp_status,
|
|
33
|
+
refresh_mcp_tools,
|
|
34
|
+
)
|
|
35
|
+
from gobby.servers.routes.mcp.endpoints.server import (
|
|
36
|
+
add_mcp_server,
|
|
37
|
+
import_mcp_server,
|
|
38
|
+
list_mcp_servers,
|
|
39
|
+
remove_mcp_server,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
__all__ = [
|
|
43
|
+
# Discovery
|
|
44
|
+
"list_all_mcp_tools",
|
|
45
|
+
"recommend_mcp_tools",
|
|
46
|
+
"search_mcp_tools",
|
|
47
|
+
# Execution
|
|
48
|
+
"call_mcp_tool",
|
|
49
|
+
"get_tool_schema",
|
|
50
|
+
"list_mcp_tools",
|
|
51
|
+
"mcp_proxy",
|
|
52
|
+
# Registry
|
|
53
|
+
"embed_mcp_tools",
|
|
54
|
+
"get_mcp_status",
|
|
55
|
+
"refresh_mcp_tools",
|
|
56
|
+
# Server
|
|
57
|
+
"add_mcp_server",
|
|
58
|
+
"import_mcp_server",
|
|
59
|
+
"list_mcp_servers",
|
|
60
|
+
"remove_mcp_server",
|
|
61
|
+
]
|