gobby 0.2.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.
- gobby/__init__.py +3 -0
- gobby/adapters/__init__.py +30 -0
- gobby/adapters/base.py +93 -0
- gobby/adapters/claude_code.py +276 -0
- gobby/adapters/codex.py +1292 -0
- gobby/adapters/gemini.py +343 -0
- gobby/agents/__init__.py +37 -0
- gobby/agents/codex_session.py +120 -0
- gobby/agents/constants.py +112 -0
- gobby/agents/context.py +362 -0
- gobby/agents/definitions.py +133 -0
- gobby/agents/gemini_session.py +111 -0
- gobby/agents/registry.py +618 -0
- gobby/agents/runner.py +968 -0
- gobby/agents/session.py +259 -0
- gobby/agents/spawn.py +916 -0
- gobby/agents/spawners/__init__.py +77 -0
- gobby/agents/spawners/base.py +142 -0
- gobby/agents/spawners/cross_platform.py +266 -0
- gobby/agents/spawners/embedded.py +225 -0
- gobby/agents/spawners/headless.py +226 -0
- gobby/agents/spawners/linux.py +125 -0
- gobby/agents/spawners/macos.py +277 -0
- gobby/agents/spawners/windows.py +308 -0
- gobby/agents/tty_config.py +319 -0
- gobby/autonomous/__init__.py +32 -0
- gobby/autonomous/progress_tracker.py +447 -0
- gobby/autonomous/stop_registry.py +269 -0
- gobby/autonomous/stuck_detector.py +383 -0
- gobby/cli/__init__.py +67 -0
- gobby/cli/__main__.py +8 -0
- gobby/cli/agents.py +529 -0
- gobby/cli/artifacts.py +266 -0
- gobby/cli/daemon.py +329 -0
- gobby/cli/extensions.py +526 -0
- gobby/cli/github.py +263 -0
- gobby/cli/init.py +53 -0
- gobby/cli/install.py +614 -0
- gobby/cli/installers/__init__.py +37 -0
- gobby/cli/installers/antigravity.py +65 -0
- gobby/cli/installers/claude.py +363 -0
- gobby/cli/installers/codex.py +192 -0
- gobby/cli/installers/gemini.py +294 -0
- gobby/cli/installers/git_hooks.py +377 -0
- gobby/cli/installers/shared.py +737 -0
- gobby/cli/linear.py +250 -0
- gobby/cli/mcp.py +30 -0
- gobby/cli/mcp_proxy.py +698 -0
- gobby/cli/memory.py +304 -0
- gobby/cli/merge.py +384 -0
- gobby/cli/projects.py +79 -0
- gobby/cli/sessions.py +622 -0
- gobby/cli/tasks/__init__.py +30 -0
- gobby/cli/tasks/_utils.py +658 -0
- gobby/cli/tasks/ai.py +1025 -0
- gobby/cli/tasks/commits.py +169 -0
- gobby/cli/tasks/crud.py +685 -0
- gobby/cli/tasks/deps.py +135 -0
- gobby/cli/tasks/labels.py +63 -0
- gobby/cli/tasks/main.py +273 -0
- gobby/cli/tasks/search.py +178 -0
- gobby/cli/tui.py +34 -0
- gobby/cli/utils.py +513 -0
- gobby/cli/workflows.py +927 -0
- gobby/cli/worktrees.py +481 -0
- gobby/config/__init__.py +129 -0
- gobby/config/app.py +551 -0
- gobby/config/extensions.py +167 -0
- gobby/config/features.py +472 -0
- gobby/config/llm_providers.py +98 -0
- gobby/config/logging.py +66 -0
- gobby/config/mcp.py +346 -0
- gobby/config/persistence.py +247 -0
- gobby/config/servers.py +141 -0
- gobby/config/sessions.py +250 -0
- gobby/config/tasks.py +784 -0
- gobby/hooks/__init__.py +104 -0
- gobby/hooks/artifact_capture.py +213 -0
- gobby/hooks/broadcaster.py +243 -0
- gobby/hooks/event_handlers.py +723 -0
- gobby/hooks/events.py +218 -0
- gobby/hooks/git.py +169 -0
- gobby/hooks/health_monitor.py +171 -0
- gobby/hooks/hook_manager.py +856 -0
- gobby/hooks/hook_types.py +575 -0
- gobby/hooks/plugins.py +813 -0
- gobby/hooks/session_coordinator.py +396 -0
- gobby/hooks/verification_runner.py +268 -0
- gobby/hooks/webhooks.py +339 -0
- gobby/install/claude/commands/gobby/bug.md +51 -0
- gobby/install/claude/commands/gobby/chore.md +51 -0
- gobby/install/claude/commands/gobby/epic.md +52 -0
- gobby/install/claude/commands/gobby/eval.md +235 -0
- gobby/install/claude/commands/gobby/feat.md +49 -0
- gobby/install/claude/commands/gobby/nit.md +52 -0
- gobby/install/claude/commands/gobby/ref.md +52 -0
- gobby/install/claude/hooks/HOOK_SCHEMAS.md +632 -0
- gobby/install/claude/hooks/hook_dispatcher.py +364 -0
- gobby/install/claude/hooks/validate_settings.py +102 -0
- gobby/install/claude/hooks-template.json +118 -0
- gobby/install/codex/hooks/hook_dispatcher.py +153 -0
- gobby/install/codex/prompts/forget.md +7 -0
- gobby/install/codex/prompts/memories.md +7 -0
- gobby/install/codex/prompts/recall.md +7 -0
- gobby/install/codex/prompts/remember.md +13 -0
- gobby/install/gemini/hooks/hook_dispatcher.py +268 -0
- gobby/install/gemini/hooks-template.json +138 -0
- gobby/install/shared/plugins/code_guardian.py +456 -0
- gobby/install/shared/plugins/example_notify.py +331 -0
- gobby/integrations/__init__.py +10 -0
- gobby/integrations/github.py +145 -0
- gobby/integrations/linear.py +145 -0
- gobby/llm/__init__.py +40 -0
- gobby/llm/base.py +120 -0
- gobby/llm/claude.py +578 -0
- gobby/llm/claude_executor.py +503 -0
- gobby/llm/codex.py +322 -0
- gobby/llm/codex_executor.py +513 -0
- gobby/llm/executor.py +316 -0
- gobby/llm/factory.py +34 -0
- gobby/llm/gemini.py +258 -0
- gobby/llm/gemini_executor.py +339 -0
- gobby/llm/litellm.py +287 -0
- gobby/llm/litellm_executor.py +303 -0
- gobby/llm/resolver.py +499 -0
- gobby/llm/service.py +236 -0
- gobby/mcp_proxy/__init__.py +29 -0
- gobby/mcp_proxy/actions.py +175 -0
- gobby/mcp_proxy/daemon_control.py +198 -0
- gobby/mcp_proxy/importer.py +436 -0
- gobby/mcp_proxy/lazy.py +325 -0
- gobby/mcp_proxy/manager.py +798 -0
- gobby/mcp_proxy/metrics.py +609 -0
- gobby/mcp_proxy/models.py +139 -0
- gobby/mcp_proxy/registries.py +215 -0
- gobby/mcp_proxy/schema_hash.py +381 -0
- gobby/mcp_proxy/semantic_search.py +706 -0
- gobby/mcp_proxy/server.py +549 -0
- gobby/mcp_proxy/services/__init__.py +0 -0
- gobby/mcp_proxy/services/fallback.py +306 -0
- gobby/mcp_proxy/services/recommendation.py +224 -0
- gobby/mcp_proxy/services/server_mgmt.py +214 -0
- gobby/mcp_proxy/services/system.py +72 -0
- gobby/mcp_proxy/services/tool_filter.py +231 -0
- gobby/mcp_proxy/services/tool_proxy.py +309 -0
- gobby/mcp_proxy/stdio.py +565 -0
- gobby/mcp_proxy/tools/__init__.py +27 -0
- gobby/mcp_proxy/tools/agents.py +1103 -0
- gobby/mcp_proxy/tools/artifacts.py +207 -0
- gobby/mcp_proxy/tools/hub.py +335 -0
- gobby/mcp_proxy/tools/internal.py +337 -0
- gobby/mcp_proxy/tools/memory.py +543 -0
- gobby/mcp_proxy/tools/merge.py +422 -0
- gobby/mcp_proxy/tools/metrics.py +283 -0
- gobby/mcp_proxy/tools/orchestration/__init__.py +23 -0
- gobby/mcp_proxy/tools/orchestration/cleanup.py +619 -0
- gobby/mcp_proxy/tools/orchestration/monitor.py +380 -0
- gobby/mcp_proxy/tools/orchestration/orchestrate.py +746 -0
- gobby/mcp_proxy/tools/orchestration/review.py +736 -0
- gobby/mcp_proxy/tools/orchestration/utils.py +16 -0
- gobby/mcp_proxy/tools/session_messages.py +1056 -0
- gobby/mcp_proxy/tools/task_dependencies.py +219 -0
- gobby/mcp_proxy/tools/task_expansion.py +591 -0
- gobby/mcp_proxy/tools/task_github.py +393 -0
- gobby/mcp_proxy/tools/task_linear.py +379 -0
- gobby/mcp_proxy/tools/task_orchestration.py +77 -0
- gobby/mcp_proxy/tools/task_readiness.py +522 -0
- gobby/mcp_proxy/tools/task_sync.py +351 -0
- gobby/mcp_proxy/tools/task_validation.py +843 -0
- gobby/mcp_proxy/tools/tasks/__init__.py +25 -0
- gobby/mcp_proxy/tools/tasks/_context.py +112 -0
- gobby/mcp_proxy/tools/tasks/_crud.py +516 -0
- gobby/mcp_proxy/tools/tasks/_factory.py +176 -0
- gobby/mcp_proxy/tools/tasks/_helpers.py +129 -0
- gobby/mcp_proxy/tools/tasks/_lifecycle.py +517 -0
- gobby/mcp_proxy/tools/tasks/_lifecycle_validation.py +301 -0
- gobby/mcp_proxy/tools/tasks/_resolution.py +55 -0
- gobby/mcp_proxy/tools/tasks/_search.py +215 -0
- gobby/mcp_proxy/tools/tasks/_session.py +125 -0
- gobby/mcp_proxy/tools/workflows.py +973 -0
- gobby/mcp_proxy/tools/worktrees.py +1264 -0
- gobby/mcp_proxy/transports/__init__.py +0 -0
- gobby/mcp_proxy/transports/base.py +95 -0
- gobby/mcp_proxy/transports/factory.py +44 -0
- gobby/mcp_proxy/transports/http.py +139 -0
- gobby/mcp_proxy/transports/stdio.py +213 -0
- gobby/mcp_proxy/transports/websocket.py +136 -0
- gobby/memory/backends/__init__.py +116 -0
- gobby/memory/backends/mem0.py +408 -0
- gobby/memory/backends/memu.py +485 -0
- gobby/memory/backends/null.py +111 -0
- gobby/memory/backends/openmemory.py +537 -0
- gobby/memory/backends/sqlite.py +304 -0
- gobby/memory/context.py +87 -0
- gobby/memory/manager.py +1001 -0
- gobby/memory/protocol.py +451 -0
- gobby/memory/search/__init__.py +66 -0
- gobby/memory/search/text.py +127 -0
- gobby/memory/viz.py +258 -0
- gobby/prompts/__init__.py +13 -0
- gobby/prompts/defaults/expansion/system.md +119 -0
- gobby/prompts/defaults/expansion/user.md +48 -0
- gobby/prompts/defaults/external_validation/agent.md +72 -0
- gobby/prompts/defaults/external_validation/external.md +63 -0
- gobby/prompts/defaults/external_validation/spawn.md +83 -0
- gobby/prompts/defaults/external_validation/system.md +6 -0
- gobby/prompts/defaults/features/import_mcp.md +22 -0
- gobby/prompts/defaults/features/import_mcp_github.md +17 -0
- gobby/prompts/defaults/features/import_mcp_search.md +16 -0
- gobby/prompts/defaults/features/recommend_tools.md +32 -0
- gobby/prompts/defaults/features/recommend_tools_hybrid.md +35 -0
- gobby/prompts/defaults/features/recommend_tools_llm.md +30 -0
- gobby/prompts/defaults/features/server_description.md +20 -0
- gobby/prompts/defaults/features/server_description_system.md +6 -0
- gobby/prompts/defaults/features/task_description.md +31 -0
- gobby/prompts/defaults/features/task_description_system.md +6 -0
- gobby/prompts/defaults/features/tool_summary.md +17 -0
- gobby/prompts/defaults/features/tool_summary_system.md +6 -0
- gobby/prompts/defaults/research/step.md +58 -0
- gobby/prompts/defaults/validation/criteria.md +47 -0
- gobby/prompts/defaults/validation/validate.md +38 -0
- gobby/prompts/loader.py +346 -0
- gobby/prompts/models.py +113 -0
- gobby/py.typed +0 -0
- gobby/runner.py +488 -0
- gobby/search/__init__.py +23 -0
- gobby/search/protocol.py +104 -0
- gobby/search/tfidf.py +232 -0
- gobby/servers/__init__.py +7 -0
- gobby/servers/http.py +636 -0
- gobby/servers/models.py +31 -0
- gobby/servers/routes/__init__.py +23 -0
- gobby/servers/routes/admin.py +416 -0
- gobby/servers/routes/dependencies.py +118 -0
- gobby/servers/routes/mcp/__init__.py +24 -0
- gobby/servers/routes/mcp/hooks.py +135 -0
- gobby/servers/routes/mcp/plugins.py +121 -0
- gobby/servers/routes/mcp/tools.py +1337 -0
- gobby/servers/routes/mcp/webhooks.py +159 -0
- gobby/servers/routes/sessions.py +582 -0
- gobby/servers/websocket.py +766 -0
- gobby/sessions/__init__.py +13 -0
- gobby/sessions/analyzer.py +322 -0
- gobby/sessions/lifecycle.py +240 -0
- gobby/sessions/manager.py +563 -0
- gobby/sessions/processor.py +225 -0
- gobby/sessions/summary.py +532 -0
- gobby/sessions/transcripts/__init__.py +41 -0
- gobby/sessions/transcripts/base.py +125 -0
- gobby/sessions/transcripts/claude.py +386 -0
- gobby/sessions/transcripts/codex.py +143 -0
- gobby/sessions/transcripts/gemini.py +195 -0
- gobby/storage/__init__.py +21 -0
- gobby/storage/agents.py +409 -0
- gobby/storage/artifact_classifier.py +341 -0
- gobby/storage/artifacts.py +285 -0
- gobby/storage/compaction.py +67 -0
- gobby/storage/database.py +357 -0
- gobby/storage/inter_session_messages.py +194 -0
- gobby/storage/mcp.py +680 -0
- gobby/storage/memories.py +562 -0
- gobby/storage/merge_resolutions.py +550 -0
- gobby/storage/migrations.py +860 -0
- gobby/storage/migrations_legacy.py +1359 -0
- gobby/storage/projects.py +166 -0
- gobby/storage/session_messages.py +251 -0
- gobby/storage/session_tasks.py +97 -0
- gobby/storage/sessions.py +817 -0
- gobby/storage/task_dependencies.py +223 -0
- gobby/storage/tasks/__init__.py +42 -0
- gobby/storage/tasks/_aggregates.py +180 -0
- gobby/storage/tasks/_crud.py +449 -0
- gobby/storage/tasks/_id.py +104 -0
- gobby/storage/tasks/_lifecycle.py +311 -0
- gobby/storage/tasks/_manager.py +889 -0
- gobby/storage/tasks/_models.py +300 -0
- gobby/storage/tasks/_ordering.py +119 -0
- gobby/storage/tasks/_path_cache.py +110 -0
- gobby/storage/tasks/_queries.py +343 -0
- gobby/storage/tasks/_search.py +143 -0
- gobby/storage/workflow_audit.py +393 -0
- gobby/storage/worktrees.py +547 -0
- gobby/sync/__init__.py +29 -0
- gobby/sync/github.py +333 -0
- gobby/sync/linear.py +304 -0
- gobby/sync/memories.py +284 -0
- gobby/sync/tasks.py +641 -0
- gobby/tasks/__init__.py +8 -0
- gobby/tasks/build_verification.py +193 -0
- gobby/tasks/commits.py +633 -0
- gobby/tasks/context.py +747 -0
- gobby/tasks/criteria.py +342 -0
- gobby/tasks/enhanced_validator.py +226 -0
- gobby/tasks/escalation.py +263 -0
- gobby/tasks/expansion.py +626 -0
- gobby/tasks/external_validator.py +764 -0
- gobby/tasks/issue_extraction.py +171 -0
- gobby/tasks/prompts/expand.py +327 -0
- gobby/tasks/research.py +421 -0
- gobby/tasks/tdd.py +352 -0
- gobby/tasks/tree_builder.py +263 -0
- gobby/tasks/validation.py +712 -0
- gobby/tasks/validation_history.py +357 -0
- gobby/tasks/validation_models.py +89 -0
- gobby/tools/__init__.py +0 -0
- gobby/tools/summarizer.py +170 -0
- gobby/tui/__init__.py +5 -0
- gobby/tui/api_client.py +281 -0
- gobby/tui/app.py +327 -0
- gobby/tui/screens/__init__.py +25 -0
- gobby/tui/screens/agents.py +333 -0
- gobby/tui/screens/chat.py +450 -0
- gobby/tui/screens/dashboard.py +377 -0
- gobby/tui/screens/memory.py +305 -0
- gobby/tui/screens/metrics.py +231 -0
- gobby/tui/screens/orchestrator.py +904 -0
- gobby/tui/screens/sessions.py +412 -0
- gobby/tui/screens/tasks.py +442 -0
- gobby/tui/screens/workflows.py +289 -0
- gobby/tui/screens/worktrees.py +174 -0
- gobby/tui/widgets/__init__.py +21 -0
- gobby/tui/widgets/chat.py +210 -0
- gobby/tui/widgets/conductor.py +104 -0
- gobby/tui/widgets/menu.py +132 -0
- gobby/tui/widgets/message_panel.py +160 -0
- gobby/tui/widgets/review_gate.py +224 -0
- gobby/tui/widgets/task_tree.py +99 -0
- gobby/tui/widgets/token_budget.py +166 -0
- gobby/tui/ws_client.py +258 -0
- gobby/utils/__init__.py +3 -0
- gobby/utils/daemon_client.py +235 -0
- gobby/utils/git.py +222 -0
- gobby/utils/id.py +38 -0
- gobby/utils/json_helpers.py +161 -0
- gobby/utils/logging.py +376 -0
- gobby/utils/machine_id.py +135 -0
- gobby/utils/metrics.py +589 -0
- gobby/utils/project_context.py +182 -0
- gobby/utils/project_init.py +263 -0
- gobby/utils/status.py +256 -0
- gobby/utils/validation.py +80 -0
- gobby/utils/version.py +23 -0
- gobby/workflows/__init__.py +4 -0
- gobby/workflows/actions.py +1310 -0
- gobby/workflows/approval_flow.py +138 -0
- gobby/workflows/artifact_actions.py +103 -0
- gobby/workflows/audit_helpers.py +110 -0
- gobby/workflows/autonomous_actions.py +286 -0
- gobby/workflows/context_actions.py +394 -0
- gobby/workflows/definitions.py +130 -0
- gobby/workflows/detection_helpers.py +208 -0
- gobby/workflows/engine.py +485 -0
- gobby/workflows/evaluator.py +669 -0
- gobby/workflows/git_utils.py +96 -0
- gobby/workflows/hooks.py +169 -0
- gobby/workflows/lifecycle_evaluator.py +613 -0
- gobby/workflows/llm_actions.py +70 -0
- gobby/workflows/loader.py +333 -0
- gobby/workflows/mcp_actions.py +60 -0
- gobby/workflows/memory_actions.py +272 -0
- gobby/workflows/premature_stop.py +164 -0
- gobby/workflows/session_actions.py +139 -0
- gobby/workflows/state_actions.py +123 -0
- gobby/workflows/state_manager.py +104 -0
- gobby/workflows/stop_signal_actions.py +163 -0
- gobby/workflows/summary_actions.py +344 -0
- gobby/workflows/task_actions.py +249 -0
- gobby/workflows/task_enforcement_actions.py +901 -0
- gobby/workflows/templates.py +52 -0
- gobby/workflows/todo_actions.py +84 -0
- gobby/workflows/webhook.py +223 -0
- gobby/workflows/webhook_executor.py +399 -0
- gobby/worktrees/__init__.py +5 -0
- gobby/worktrees/git.py +690 -0
- gobby/worktrees/merge/__init__.py +20 -0
- gobby/worktrees/merge/conflict_parser.py +177 -0
- gobby/worktrees/merge/resolver.py +485 -0
- gobby-0.2.5.dist-info/METADATA +351 -0
- gobby-0.2.5.dist-info/RECORD +383 -0
- gobby-0.2.5.dist-info/WHEEL +5 -0
- gobby-0.2.5.dist-info/entry_points.txt +2 -0
- gobby-0.2.5.dist-info/licenses/LICENSE.md +193 -0
- gobby-0.2.5.dist-info/top_level.txt +1 -0
gobby/servers/http.py
ADDED
|
@@ -0,0 +1,636 @@
|
|
|
1
|
+
"""
|
|
2
|
+
HTTP server for Gobby daemon.
|
|
3
|
+
|
|
4
|
+
Provides a FastAPI-based HTTP server for REST endpoints, MCP tool proxying,
|
|
5
|
+
and session management. Local-first version: no platform auth, no remote sync.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
import logging
|
|
10
|
+
import time
|
|
11
|
+
from collections.abc import AsyncGenerator
|
|
12
|
+
from contextlib import asynccontextmanager
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
from fastapi import FastAPI, HTTPException, Request
|
|
16
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
17
|
+
from fastapi.responses import JSONResponse
|
|
18
|
+
|
|
19
|
+
from gobby.adapters.codex import CodexAdapter
|
|
20
|
+
from gobby.hooks.broadcaster import HookEventBroadcaster
|
|
21
|
+
from gobby.hooks.hook_manager import HookManager
|
|
22
|
+
from gobby.llm import LLMService, create_llm_service
|
|
23
|
+
from gobby.mcp_proxy.registries import setup_internal_registries
|
|
24
|
+
from gobby.mcp_proxy.semantic_search import SemanticToolSearch
|
|
25
|
+
from gobby.mcp_proxy.server import GobbyDaemonTools, create_mcp_server
|
|
26
|
+
from gobby.mcp_proxy.services.tool_filter import ToolFilterService
|
|
27
|
+
from gobby.memory.manager import MemoryManager
|
|
28
|
+
|
|
29
|
+
# Re-export for backward compatibility
|
|
30
|
+
from gobby.servers.models import SessionRegisterRequest # noqa: F401
|
|
31
|
+
from gobby.storage.sessions import LocalSessionManager
|
|
32
|
+
from gobby.storage.tasks import LocalTaskManager
|
|
33
|
+
from gobby.sync.tasks import TaskSyncManager
|
|
34
|
+
from gobby.utils.metrics import get_metrics_collector
|
|
35
|
+
from gobby.utils.version import get_version
|
|
36
|
+
|
|
37
|
+
logger = logging.getLogger(__name__)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class HTTPServer:
|
|
41
|
+
"""
|
|
42
|
+
FastAPI HTTP server for Gobby daemon.
|
|
43
|
+
|
|
44
|
+
Handles MCP tool proxying, session management, and admin endpoints.
|
|
45
|
+
Local-first version: no platform authentication, uses local SQLite storage.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
def __init__(
|
|
49
|
+
self,
|
|
50
|
+
port: int = 8000,
|
|
51
|
+
test_mode: bool = False,
|
|
52
|
+
mcp_manager: Any | None = None,
|
|
53
|
+
mcp_db_manager: Any | None = None,
|
|
54
|
+
config: Any | None = None,
|
|
55
|
+
codex_client: Any | None = None,
|
|
56
|
+
session_manager: LocalSessionManager | None = None,
|
|
57
|
+
websocket_server: Any | None = None,
|
|
58
|
+
task_manager: LocalTaskManager | None = None,
|
|
59
|
+
task_sync_manager: TaskSyncManager | None = None,
|
|
60
|
+
message_processor: Any | None = None,
|
|
61
|
+
message_manager: Any | None = None, # LocalSessionMessageManager
|
|
62
|
+
memory_manager: "MemoryManager | None" = None,
|
|
63
|
+
llm_service: "LLMService | None" = None,
|
|
64
|
+
memory_sync_manager: Any | None = None,
|
|
65
|
+
task_expander: Any | None = None,
|
|
66
|
+
task_validator: Any | None = None,
|
|
67
|
+
metrics_manager: Any | None = None,
|
|
68
|
+
agent_runner: Any | None = None,
|
|
69
|
+
worktree_storage: Any | None = None,
|
|
70
|
+
) -> None:
|
|
71
|
+
"""
|
|
72
|
+
Initialize HTTP server.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
port: Server port
|
|
76
|
+
test_mode: Run in test mode (disable features that conflict with testing)
|
|
77
|
+
mcp_manager: MCPClientManager instance for multi-server support
|
|
78
|
+
mcp_db_manager: LocalMCPManager instance for SQLite-based storage of MCP
|
|
79
|
+
server configurations and tool schemas. Used by ToolsHandler for
|
|
80
|
+
progressive tool discovery. Optional; defaults to None.
|
|
81
|
+
config: DaemonConfig instance for configuration
|
|
82
|
+
codex_client: CodexAppServerClient instance for Codex integration
|
|
83
|
+
session_manager: LocalSessionManager for session storage
|
|
84
|
+
websocket_server: Optional WebSocketServer instance for event broadcasting
|
|
85
|
+
task_manager: LocalTaskManager instance
|
|
86
|
+
task_sync_manager: TaskSyncManager instance
|
|
87
|
+
message_processor: SessionMessageProcessor instance
|
|
88
|
+
message_manager: LocalSessionMessageManager instance for retrieval
|
|
89
|
+
memory_manager: MemoryManager instance
|
|
90
|
+
llm_service: LLMService instance
|
|
91
|
+
"""
|
|
92
|
+
self.port = port
|
|
93
|
+
self.test_mode = test_mode
|
|
94
|
+
self.mcp_manager = mcp_manager
|
|
95
|
+
self.config = config
|
|
96
|
+
self.codex_client = codex_client
|
|
97
|
+
self.session_manager = session_manager
|
|
98
|
+
self.task_manager = task_manager
|
|
99
|
+
self.task_sync_manager = task_sync_manager
|
|
100
|
+
self.message_processor = message_processor
|
|
101
|
+
self.message_manager = message_manager
|
|
102
|
+
self.memory_manager = memory_manager
|
|
103
|
+
self.websocket_server = websocket_server
|
|
104
|
+
self.llm_service = llm_service
|
|
105
|
+
self.memory_sync_manager = memory_sync_manager
|
|
106
|
+
self.task_expander = task_expander
|
|
107
|
+
self.task_validator = task_validator
|
|
108
|
+
self.metrics_manager = metrics_manager
|
|
109
|
+
self.agent_runner = agent_runner
|
|
110
|
+
self.worktree_storage = worktree_storage
|
|
111
|
+
|
|
112
|
+
# Initialize WebSocket broadcaster
|
|
113
|
+
# Note: websocket_server might be None if disabled
|
|
114
|
+
self.broadcaster = HookEventBroadcaster(websocket_server, config)
|
|
115
|
+
|
|
116
|
+
self._start_time: float = time.time()
|
|
117
|
+
|
|
118
|
+
# Create LLM service if not provided
|
|
119
|
+
if not self.llm_service and config:
|
|
120
|
+
try:
|
|
121
|
+
self.llm_service = create_llm_service(config)
|
|
122
|
+
logger.debug(
|
|
123
|
+
f"LLM service initialized with providers: {self.llm_service.enabled_providers}"
|
|
124
|
+
)
|
|
125
|
+
except Exception as e:
|
|
126
|
+
logger.error(f"Failed to initialize LLM service: {e}")
|
|
127
|
+
|
|
128
|
+
# Create MCP server instance
|
|
129
|
+
self._mcp_server = None
|
|
130
|
+
self._internal_manager = None
|
|
131
|
+
self._tools_handler = None
|
|
132
|
+
self._mcp_db_manager = mcp_db_manager
|
|
133
|
+
if mcp_manager:
|
|
134
|
+
# Determine WebSocket port
|
|
135
|
+
ws_port = 8766
|
|
136
|
+
if config and hasattr(config, "websocket") and config.websocket:
|
|
137
|
+
ws_port = config.websocket.port
|
|
138
|
+
|
|
139
|
+
# Create a lazy getter for tool_proxy that will be available after
|
|
140
|
+
# GobbyDaemonTools is created. This allows in-process agents to route
|
|
141
|
+
# tool calls through the MCP proxy.
|
|
142
|
+
def tool_proxy_getter() -> Any:
|
|
143
|
+
if self._tools_handler is not None:
|
|
144
|
+
return self._tools_handler.tool_proxy
|
|
145
|
+
return None
|
|
146
|
+
|
|
147
|
+
# Create merge managers if db available
|
|
148
|
+
merge_storage = None
|
|
149
|
+
merge_resolver = None
|
|
150
|
+
if mcp_db_manager:
|
|
151
|
+
from gobby.storage.merge_resolutions import MergeResolutionManager
|
|
152
|
+
from gobby.worktrees.merge.resolver import MergeResolver
|
|
153
|
+
|
|
154
|
+
merge_storage = MergeResolutionManager(mcp_db_manager.db)
|
|
155
|
+
merge_resolver = MergeResolver()
|
|
156
|
+
merge_resolver._llm_service = self.llm_service
|
|
157
|
+
logger.debug("Merge resolution subsystems initialized")
|
|
158
|
+
|
|
159
|
+
# Setup internal registries (gobby-tasks, gobby-memory, etc.)
|
|
160
|
+
self._internal_manager = setup_internal_registries(
|
|
161
|
+
_config=config,
|
|
162
|
+
_session_manager=None, # Not needed for internal registries
|
|
163
|
+
memory_manager=memory_manager,
|
|
164
|
+
task_manager=task_manager,
|
|
165
|
+
sync_manager=task_sync_manager,
|
|
166
|
+
task_expander=self.task_expander,
|
|
167
|
+
task_validator=self.task_validator,
|
|
168
|
+
message_manager=message_manager,
|
|
169
|
+
local_session_manager=session_manager,
|
|
170
|
+
metrics_manager=self.metrics_manager,
|
|
171
|
+
llm_service=self.llm_service,
|
|
172
|
+
agent_runner=self.agent_runner,
|
|
173
|
+
worktree_storage=self.worktree_storage,
|
|
174
|
+
git_manager=None, # Created per-project, not at daemon startup
|
|
175
|
+
merge_storage=merge_storage,
|
|
176
|
+
merge_resolver=merge_resolver,
|
|
177
|
+
project_id=None, # Project-specific, not global
|
|
178
|
+
tool_proxy_getter=tool_proxy_getter,
|
|
179
|
+
)
|
|
180
|
+
registry_count = len(self._internal_manager)
|
|
181
|
+
logger.debug(f"Internal registries initialized: {registry_count} registries")
|
|
182
|
+
|
|
183
|
+
# Initialize tool summarizer config
|
|
184
|
+
if config:
|
|
185
|
+
from gobby.tools.summarizer import init_summarizer_config
|
|
186
|
+
|
|
187
|
+
init_summarizer_config(config.tool_summarizer)
|
|
188
|
+
logger.debug("Tool summarizer config initialized")
|
|
189
|
+
|
|
190
|
+
# Create semantic search instance if db available
|
|
191
|
+
semantic_search = None
|
|
192
|
+
if mcp_db_manager:
|
|
193
|
+
semantic_search = SemanticToolSearch(db=mcp_db_manager.db)
|
|
194
|
+
logger.debug("Semantic tool search initialized")
|
|
195
|
+
|
|
196
|
+
# Create tool filter for workflow phase restrictions
|
|
197
|
+
tool_filter = None
|
|
198
|
+
if mcp_db_manager:
|
|
199
|
+
tool_filter = ToolFilterService(db=mcp_db_manager.db)
|
|
200
|
+
logger.debug("Tool filter service initialized")
|
|
201
|
+
|
|
202
|
+
# Create fallback resolver for alternative tool suggestions on error
|
|
203
|
+
fallback_resolver = None
|
|
204
|
+
if semantic_search and self.metrics_manager:
|
|
205
|
+
from gobby.mcp_proxy.services.fallback import ToolFallbackResolver
|
|
206
|
+
|
|
207
|
+
fallback_resolver = ToolFallbackResolver(
|
|
208
|
+
semantic_search=semantic_search,
|
|
209
|
+
metrics_manager=self.metrics_manager,
|
|
210
|
+
)
|
|
211
|
+
logger.debug("Fallback resolver initialized")
|
|
212
|
+
|
|
213
|
+
# Create tools handler
|
|
214
|
+
self._tools_handler = GobbyDaemonTools(
|
|
215
|
+
mcp_manager=mcp_manager,
|
|
216
|
+
daemon_port=port,
|
|
217
|
+
websocket_port=ws_port,
|
|
218
|
+
start_time=self._start_time,
|
|
219
|
+
internal_manager=self._internal_manager,
|
|
220
|
+
config=config,
|
|
221
|
+
llm_service=self.llm_service,
|
|
222
|
+
session_manager=session_manager,
|
|
223
|
+
memory_manager=memory_manager,
|
|
224
|
+
config_manager=mcp_db_manager,
|
|
225
|
+
semantic_search=semantic_search,
|
|
226
|
+
tool_filter=tool_filter,
|
|
227
|
+
fallback_resolver=fallback_resolver,
|
|
228
|
+
)
|
|
229
|
+
self._mcp_server = create_mcp_server(self._tools_handler)
|
|
230
|
+
logger.debug("MCP server initialized and will be mounted at /mcp")
|
|
231
|
+
|
|
232
|
+
self.app = self._create_app()
|
|
233
|
+
self._running = False
|
|
234
|
+
self._background_tasks: set[asyncio.Task[Any]] = set()
|
|
235
|
+
self._metrics = get_metrics_collector()
|
|
236
|
+
self._daemon: Any = None # Set externally by daemon
|
|
237
|
+
|
|
238
|
+
def _resolve_project_id(self, project_id: str | None, cwd: str | None) -> str:
|
|
239
|
+
"""
|
|
240
|
+
Resolve project_id from cwd if not provided.
|
|
241
|
+
|
|
242
|
+
If project_id is given, returns it directly.
|
|
243
|
+
Otherwise, looks up project from .gobby/project.json in the cwd.
|
|
244
|
+
|
|
245
|
+
Args:
|
|
246
|
+
project_id: Optional explicit project ID
|
|
247
|
+
cwd: Current working directory path
|
|
248
|
+
|
|
249
|
+
Returns:
|
|
250
|
+
Project ID from .gobby/project.json
|
|
251
|
+
|
|
252
|
+
Raises:
|
|
253
|
+
ValueError: If no project.json found (project not initialized)
|
|
254
|
+
"""
|
|
255
|
+
from pathlib import Path
|
|
256
|
+
|
|
257
|
+
if project_id:
|
|
258
|
+
return project_id
|
|
259
|
+
|
|
260
|
+
# Get cwd or use current directory
|
|
261
|
+
working_dir = Path(cwd) if cwd else Path.cwd()
|
|
262
|
+
|
|
263
|
+
# Look up project from .gobby/project.json
|
|
264
|
+
from gobby.utils.project_context import get_project_context
|
|
265
|
+
|
|
266
|
+
project_context = get_project_context(working_dir)
|
|
267
|
+
if project_context and project_context.get("id"):
|
|
268
|
+
return str(project_context["id"])
|
|
269
|
+
|
|
270
|
+
# No project.json found - require explicit initialization
|
|
271
|
+
raise ValueError(
|
|
272
|
+
f"No .gobby/project.json found in {working_dir} or parents. "
|
|
273
|
+
"Run 'gobby init' to initialize a project."
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
def _create_app(self) -> FastAPI:
|
|
277
|
+
"""
|
|
278
|
+
Create and configure FastAPI application.
|
|
279
|
+
|
|
280
|
+
Returns:
|
|
281
|
+
Configured FastAPI app instance
|
|
282
|
+
"""
|
|
283
|
+
|
|
284
|
+
# Create MCP app first if available (needed for lifespan)
|
|
285
|
+
mcp_app = None
|
|
286
|
+
if self._mcp_server:
|
|
287
|
+
mcp_app = self._mcp_server.streamable_http_app()
|
|
288
|
+
logger.debug("MCP HTTP app created")
|
|
289
|
+
|
|
290
|
+
@asynccontextmanager
|
|
291
|
+
async def lifespan(app: FastAPI) -> AsyncGenerator[None]:
|
|
292
|
+
"""Handle application startup and shutdown with combined lifespans."""
|
|
293
|
+
logger.debug("Starting Gobby HTTP server on port %d", self.port)
|
|
294
|
+
self._running = True
|
|
295
|
+
self._start_time = time.time()
|
|
296
|
+
|
|
297
|
+
# Startup operations
|
|
298
|
+
if self.test_mode:
|
|
299
|
+
logger.debug("Running in test mode - external connections disabled")
|
|
300
|
+
|
|
301
|
+
# Initialize HookManager singleton with logging config
|
|
302
|
+
hook_manager_kwargs: dict[str, Any] = {
|
|
303
|
+
"daemon_host": "localhost",
|
|
304
|
+
"daemon_port": self.port,
|
|
305
|
+
"llm_service": self.llm_service,
|
|
306
|
+
"config": self.config,
|
|
307
|
+
"broadcaster": self.broadcaster,
|
|
308
|
+
"mcp_manager": self.mcp_manager,
|
|
309
|
+
"message_processor": self.message_processor,
|
|
310
|
+
"memory_sync_manager": self.memory_sync_manager,
|
|
311
|
+
"task_sync_manager": self.task_sync_manager,
|
|
312
|
+
}
|
|
313
|
+
if self.config:
|
|
314
|
+
# Pass full log file path from config
|
|
315
|
+
hook_manager_kwargs["log_file"] = self.config.logging.hook_manager
|
|
316
|
+
hook_manager_kwargs["log_max_bytes"] = self.config.logging.max_size_mb * 1024 * 1024
|
|
317
|
+
hook_manager_kwargs["log_backup_count"] = self.config.logging.backup_count
|
|
318
|
+
|
|
319
|
+
app.state.hook_manager = HookManager(**hook_manager_kwargs)
|
|
320
|
+
logger.debug("HookManager initialized in daemon")
|
|
321
|
+
|
|
322
|
+
# Wire up stop_registry to WebSocket server for stop_request handling
|
|
323
|
+
if (
|
|
324
|
+
self.websocket_server
|
|
325
|
+
and hasattr(app.state, "hook_manager")
|
|
326
|
+
and hasattr(app.state.hook_manager, "_stop_registry")
|
|
327
|
+
):
|
|
328
|
+
self.websocket_server.stop_registry = app.state.hook_manager._stop_registry
|
|
329
|
+
logger.debug("Stop registry connected to WebSocket server")
|
|
330
|
+
|
|
331
|
+
# Store server instance for dependency injection
|
|
332
|
+
app.state.server = self
|
|
333
|
+
|
|
334
|
+
# Initialize CodexAdapter for session tracking
|
|
335
|
+
app.state.codex_adapter = None
|
|
336
|
+
if self.codex_client and CodexAdapter.is_codex_available():
|
|
337
|
+
codex_adapter = CodexAdapter(hook_manager=app.state.hook_manager)
|
|
338
|
+
codex_adapter.attach_to_client(self.codex_client)
|
|
339
|
+
app.state.codex_adapter = codex_adapter
|
|
340
|
+
logger.debug("CodexAdapter attached to CodexAppServerClient")
|
|
341
|
+
|
|
342
|
+
# Sync existing Codex sessions when client is connected
|
|
343
|
+
if self.codex_client.is_connected:
|
|
344
|
+
try:
|
|
345
|
+
synced = await codex_adapter.sync_existing_sessions()
|
|
346
|
+
logger.debug(f"Synced {synced} existing Codex sessions")
|
|
347
|
+
except Exception as e:
|
|
348
|
+
logger.warning(f"Failed to sync existing Codex sessions: {e}")
|
|
349
|
+
|
|
350
|
+
# If MCP app exists, wrap its lifespan
|
|
351
|
+
if mcp_app is not None:
|
|
352
|
+
# Use router.lifespan_context for stable FastMCP version
|
|
353
|
+
async with mcp_app.router.lifespan_context(app):
|
|
354
|
+
logger.debug("MCP server lifespan initialized")
|
|
355
|
+
yield
|
|
356
|
+
logger.debug("MCP server lifespan shutdown complete")
|
|
357
|
+
else:
|
|
358
|
+
yield
|
|
359
|
+
|
|
360
|
+
# Shutdown operations
|
|
361
|
+
logger.debug("Shutting down Gobby HTTP server")
|
|
362
|
+
|
|
363
|
+
# Cleanup CodexAdapter
|
|
364
|
+
if hasattr(app.state, "codex_adapter") and app.state.codex_adapter:
|
|
365
|
+
app.state.codex_adapter.detach_from_client()
|
|
366
|
+
logger.debug("CodexAdapter detached")
|
|
367
|
+
|
|
368
|
+
# Cleanup HookManager
|
|
369
|
+
if hasattr(app.state, "hook_manager"):
|
|
370
|
+
app.state.hook_manager.shutdown()
|
|
371
|
+
logger.debug("HookManager shutdown complete")
|
|
372
|
+
|
|
373
|
+
# Process graceful shutdown (tasks, MCP connections)
|
|
374
|
+
await self._process_shutdown()
|
|
375
|
+
|
|
376
|
+
self._running = False
|
|
377
|
+
|
|
378
|
+
app = FastAPI(
|
|
379
|
+
title="Gobby Daemon",
|
|
380
|
+
description="Local-first HTTP server for MCP and session management",
|
|
381
|
+
version=get_version(),
|
|
382
|
+
lifespan=lifespan,
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
# Add CORS middleware for cross-origin requests
|
|
386
|
+
app.add_middleware(
|
|
387
|
+
CORSMiddleware,
|
|
388
|
+
allow_origins=["*"], # Allow all origins for local development
|
|
389
|
+
allow_credentials=True,
|
|
390
|
+
allow_methods=["*"],
|
|
391
|
+
allow_headers=["*"],
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
# Register exception handlers
|
|
395
|
+
self._register_exception_handlers(app)
|
|
396
|
+
|
|
397
|
+
# Register routes
|
|
398
|
+
self._register_routes(app)
|
|
399
|
+
|
|
400
|
+
# Mount MCP server if available
|
|
401
|
+
if mcp_app is not None:
|
|
402
|
+
app.mount("/mcp", mcp_app)
|
|
403
|
+
logger.debug("MCP server mounted at /mcp")
|
|
404
|
+
|
|
405
|
+
return app
|
|
406
|
+
|
|
407
|
+
def _register_exception_handlers(self, app: FastAPI) -> None:
|
|
408
|
+
"""
|
|
409
|
+
Register global exception handlers.
|
|
410
|
+
|
|
411
|
+
All exceptions return 200 OK to prevent Claude Code hook failures.
|
|
412
|
+
|
|
413
|
+
Args:
|
|
414
|
+
app: FastAPI application instance
|
|
415
|
+
"""
|
|
416
|
+
|
|
417
|
+
@app.exception_handler(Exception)
|
|
418
|
+
async def global_exception_handler(
|
|
419
|
+
request: Request,
|
|
420
|
+
exc: Exception,
|
|
421
|
+
) -> JSONResponse:
|
|
422
|
+
"""Handle all uncaught exceptions.
|
|
423
|
+
|
|
424
|
+
HTTPException is re-raised to let FastAPI's built-in handler
|
|
425
|
+
return proper status codes (404, 422, etc.). All other exceptions
|
|
426
|
+
return 200 OK to prevent hook failures.
|
|
427
|
+
"""
|
|
428
|
+
# Let HTTPException pass through to FastAPI's built-in handler
|
|
429
|
+
# so proper status codes (404, 422, etc.) are returned
|
|
430
|
+
if isinstance(exc, HTTPException):
|
|
431
|
+
raise exc
|
|
432
|
+
|
|
433
|
+
logger.error(
|
|
434
|
+
"Unhandled exception in HTTP server: %s",
|
|
435
|
+
exc,
|
|
436
|
+
exc_info=True,
|
|
437
|
+
extra={
|
|
438
|
+
"path": request.url.path,
|
|
439
|
+
"method": request.method,
|
|
440
|
+
"client": request.client.host if request.client else None,
|
|
441
|
+
},
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
# Return 200 OK to prevent hook failure for non-HTTP exceptions
|
|
445
|
+
return JSONResponse(
|
|
446
|
+
status_code=200,
|
|
447
|
+
content={
|
|
448
|
+
"status": "error",
|
|
449
|
+
"message": "Internal error occurred but request acknowledged",
|
|
450
|
+
"error_logged": True,
|
|
451
|
+
},
|
|
452
|
+
)
|
|
453
|
+
|
|
454
|
+
def _register_routes(self, app: FastAPI) -> None:
|
|
455
|
+
"""
|
|
456
|
+
Register HTTP routes using extracted router modules.
|
|
457
|
+
|
|
458
|
+
Args:
|
|
459
|
+
app: FastAPI application instance
|
|
460
|
+
"""
|
|
461
|
+
from gobby.servers.routes import (
|
|
462
|
+
create_admin_router,
|
|
463
|
+
create_hooks_router,
|
|
464
|
+
create_mcp_router,
|
|
465
|
+
create_plugins_router,
|
|
466
|
+
create_sessions_router,
|
|
467
|
+
create_webhooks_router,
|
|
468
|
+
)
|
|
469
|
+
|
|
470
|
+
# Include all routers
|
|
471
|
+
app.include_router(create_admin_router(self))
|
|
472
|
+
app.include_router(create_sessions_router(self))
|
|
473
|
+
app.include_router(create_mcp_router())
|
|
474
|
+
app.include_router(create_hooks_router(self))
|
|
475
|
+
app.include_router(create_plugins_router())
|
|
476
|
+
app.include_router(create_webhooks_router())
|
|
477
|
+
|
|
478
|
+
async def _process_shutdown(self) -> None:
|
|
479
|
+
"""
|
|
480
|
+
Background task to perform graceful daemon shutdown.
|
|
481
|
+
"""
|
|
482
|
+
start_time = time.perf_counter()
|
|
483
|
+
|
|
484
|
+
try:
|
|
485
|
+
logger.debug("Processing graceful shutdown")
|
|
486
|
+
|
|
487
|
+
# Wait for pending background tasks to complete
|
|
488
|
+
pending_tasks_count = len(self._background_tasks)
|
|
489
|
+
if pending_tasks_count > 0:
|
|
490
|
+
logger.debug(
|
|
491
|
+
"Waiting for pending background tasks to complete",
|
|
492
|
+
extra={"pending_tasks": pending_tasks_count},
|
|
493
|
+
)
|
|
494
|
+
|
|
495
|
+
max_wait = 30.0
|
|
496
|
+
wait_start = time.perf_counter()
|
|
497
|
+
|
|
498
|
+
while (
|
|
499
|
+
len(self._background_tasks) > 0
|
|
500
|
+
and (time.perf_counter() - wait_start) < max_wait
|
|
501
|
+
):
|
|
502
|
+
await asyncio.sleep(0.5)
|
|
503
|
+
|
|
504
|
+
completed_wait = time.perf_counter() - wait_start
|
|
505
|
+
remaining_tasks = len(self._background_tasks)
|
|
506
|
+
|
|
507
|
+
if remaining_tasks > 0:
|
|
508
|
+
logger.warning(
|
|
509
|
+
"Shutdown timeout - some background tasks still pending",
|
|
510
|
+
extra={
|
|
511
|
+
"remaining_tasks": remaining_tasks,
|
|
512
|
+
"wait_seconds": completed_wait,
|
|
513
|
+
},
|
|
514
|
+
)
|
|
515
|
+
else:
|
|
516
|
+
logger.debug(
|
|
517
|
+
"All background tasks completed",
|
|
518
|
+
extra={"wait_seconds": completed_wait},
|
|
519
|
+
)
|
|
520
|
+
|
|
521
|
+
# Disconnect all MCP servers
|
|
522
|
+
if self.mcp_manager:
|
|
523
|
+
logger.debug("Disconnecting MCP servers...")
|
|
524
|
+
try:
|
|
525
|
+
await self.mcp_manager.disconnect_all()
|
|
526
|
+
logger.debug("MCP servers disconnected")
|
|
527
|
+
except Exception as e:
|
|
528
|
+
logger.warning(f"Error disconnecting MCP servers: {e}")
|
|
529
|
+
|
|
530
|
+
duration_seconds = time.perf_counter() - start_time
|
|
531
|
+
self._metrics.inc_counter("shutdown_succeeded_total")
|
|
532
|
+
|
|
533
|
+
logger.debug(
|
|
534
|
+
"Shutdown processed",
|
|
535
|
+
extra={"duration_seconds": duration_seconds},
|
|
536
|
+
)
|
|
537
|
+
|
|
538
|
+
except Exception as e:
|
|
539
|
+
duration_seconds = time.perf_counter() - start_time
|
|
540
|
+
self._metrics.inc_counter("shutdown_failed_total")
|
|
541
|
+
|
|
542
|
+
logger.error(
|
|
543
|
+
"Shutdown processing failed: %s",
|
|
544
|
+
e,
|
|
545
|
+
exc_info=True,
|
|
546
|
+
extra={"duration_seconds": duration_seconds},
|
|
547
|
+
)
|
|
548
|
+
|
|
549
|
+
|
|
550
|
+
async def create_server(
|
|
551
|
+
port: int = 8765,
|
|
552
|
+
test_mode: bool = False,
|
|
553
|
+
mcp_manager: Any | None = None,
|
|
554
|
+
config: Any | None = None,
|
|
555
|
+
session_manager: LocalSessionManager | None = None,
|
|
556
|
+
) -> HTTPServer:
|
|
557
|
+
"""
|
|
558
|
+
Create HTTP server instance.
|
|
559
|
+
|
|
560
|
+
Args:
|
|
561
|
+
port: Port to listen on
|
|
562
|
+
test_mode: Enable test mode
|
|
563
|
+
mcp_manager: MCP client manager
|
|
564
|
+
config: Daemon configuration
|
|
565
|
+
session_manager: Local session manager
|
|
566
|
+
|
|
567
|
+
Returns:
|
|
568
|
+
Configured HTTPServer instance
|
|
569
|
+
"""
|
|
570
|
+
return HTTPServer(
|
|
571
|
+
port=port,
|
|
572
|
+
test_mode=test_mode,
|
|
573
|
+
mcp_manager=mcp_manager,
|
|
574
|
+
config=config,
|
|
575
|
+
session_manager=session_manager,
|
|
576
|
+
)
|
|
577
|
+
|
|
578
|
+
|
|
579
|
+
async def run_server(
|
|
580
|
+
server: HTTPServer,
|
|
581
|
+
host: str = "0.0.0.0", # nosec B104 - local daemon needs network access
|
|
582
|
+
workers: int = 1,
|
|
583
|
+
limit_concurrency: int | None = 1000,
|
|
584
|
+
limit_max_requests: int | None = None,
|
|
585
|
+
timeout_keep_alive: int = 5,
|
|
586
|
+
timeout_graceful_shutdown: int = 30,
|
|
587
|
+
) -> None:
|
|
588
|
+
"""
|
|
589
|
+
Run HTTP server with production-ready Uvicorn configuration.
|
|
590
|
+
|
|
591
|
+
Args:
|
|
592
|
+
server: HTTPServer instance
|
|
593
|
+
host: Host to bind to (default: 0.0.0.0 for all interfaces)
|
|
594
|
+
workers: Number of worker processes (default: 1 for async)
|
|
595
|
+
limit_concurrency: Max concurrent connections (default: 1000)
|
|
596
|
+
limit_max_requests: Max requests before worker restart (None = unlimited)
|
|
597
|
+
timeout_keep_alive: Keep-alive timeout in seconds (default: 5)
|
|
598
|
+
timeout_graceful_shutdown: Graceful shutdown timeout in seconds (default: 30)
|
|
599
|
+
"""
|
|
600
|
+
import uvicorn
|
|
601
|
+
|
|
602
|
+
config = uvicorn.Config(
|
|
603
|
+
server.app,
|
|
604
|
+
host=host,
|
|
605
|
+
port=server.port,
|
|
606
|
+
log_level="info",
|
|
607
|
+
access_log=True,
|
|
608
|
+
log_config=None,
|
|
609
|
+
limit_concurrency=limit_concurrency,
|
|
610
|
+
limit_max_requests=limit_max_requests,
|
|
611
|
+
timeout_keep_alive=timeout_keep_alive,
|
|
612
|
+
timeout_graceful_shutdown=timeout_graceful_shutdown,
|
|
613
|
+
backlog=2048,
|
|
614
|
+
workers=workers,
|
|
615
|
+
loop="auto",
|
|
616
|
+
h11_max_incomplete_event_size=16384,
|
|
617
|
+
)
|
|
618
|
+
|
|
619
|
+
uvicorn_server = uvicorn.Server(config)
|
|
620
|
+
|
|
621
|
+
async def shutdown_handler() -> None:
|
|
622
|
+
"""Handle graceful shutdown of HTTP server."""
|
|
623
|
+
logger.debug("Initiating HTTP server shutdown...")
|
|
624
|
+
if hasattr(server, "_daemon") and server._daemon is not None:
|
|
625
|
+
try:
|
|
626
|
+
server._daemon.graceful_shutdown(timeout=timeout_graceful_shutdown)
|
|
627
|
+
except Exception as e:
|
|
628
|
+
logger.warning(f"Error during daemon shutdown: {e}")
|
|
629
|
+
|
|
630
|
+
try:
|
|
631
|
+
await uvicorn_server.serve()
|
|
632
|
+
except (KeyboardInterrupt, SystemExit):
|
|
633
|
+
logger.debug("Received shutdown signal")
|
|
634
|
+
await shutdown_handler()
|
|
635
|
+
finally:
|
|
636
|
+
logger.debug("HTTP server stopped")
|
gobby/servers/models.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Pydantic models for HTTP server request/response schemas.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SessionRegisterRequest(BaseModel):
|
|
9
|
+
"""Request model for session registration endpoint."""
|
|
10
|
+
|
|
11
|
+
external_id: str = Field(
|
|
12
|
+
..., description="External session identifier (e.g., from Claude Code)"
|
|
13
|
+
)
|
|
14
|
+
machine_id: str | None = Field(None, description="Unique machine identifier")
|
|
15
|
+
|
|
16
|
+
# Session metadata
|
|
17
|
+
jsonl_path: str | None = Field(None, description="Path to JSONL transcript file")
|
|
18
|
+
title: str | None = Field(None, description="Natural language session summary/title")
|
|
19
|
+
source: str | None = Field(
|
|
20
|
+
None, description="Session source (e.g., 'Claude Code', 'Agent SDK')"
|
|
21
|
+
)
|
|
22
|
+
parent_session_id: str | None = Field(
|
|
23
|
+
None, description="Parent session ID for session lineage tracking"
|
|
24
|
+
)
|
|
25
|
+
status: str | None = Field(None, description="Session status (active, paused, etc.)")
|
|
26
|
+
project_id: str | None = Field(None, description="Project ID to associate with session")
|
|
27
|
+
project_path: str | None = Field(
|
|
28
|
+
None, description="Project root directory path (for git extraction)"
|
|
29
|
+
)
|
|
30
|
+
git_branch: str | None = Field(None, description="Current git branch name")
|
|
31
|
+
cwd: str | None = Field(None, description="Current working directory")
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""
|
|
2
|
+
FastAPI route modules for Gobby HTTP server.
|
|
3
|
+
|
|
4
|
+
Each module contains an APIRouter with related endpoints.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from gobby.servers.routes.admin import create_admin_router
|
|
8
|
+
from gobby.servers.routes.mcp import (
|
|
9
|
+
create_hooks_router,
|
|
10
|
+
create_mcp_router,
|
|
11
|
+
create_plugins_router,
|
|
12
|
+
create_webhooks_router,
|
|
13
|
+
)
|
|
14
|
+
from gobby.servers.routes.sessions import create_sessions_router
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"create_admin_router",
|
|
18
|
+
"create_hooks_router",
|
|
19
|
+
"create_mcp_router",
|
|
20
|
+
"create_plugins_router",
|
|
21
|
+
"create_sessions_router",
|
|
22
|
+
"create_webhooks_router",
|
|
23
|
+
]
|