gobby 0.2.8__py3-none-any.whl → 0.2.11__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 +6 -0
- gobby/adapters/base.py +11 -2
- gobby/adapters/claude_code.py +5 -28
- gobby/adapters/codex_impl/adapter.py +38 -43
- gobby/adapters/copilot.py +324 -0
- gobby/adapters/cursor.py +373 -0
- gobby/adapters/gemini.py +2 -26
- gobby/adapters/windsurf.py +359 -0
- gobby/agents/definitions.py +162 -2
- gobby/agents/isolation.py +33 -1
- gobby/agents/pty_reader.py +192 -0
- gobby/agents/registry.py +10 -1
- gobby/agents/runner.py +24 -8
- gobby/agents/sandbox.py +8 -3
- gobby/agents/session.py +4 -0
- gobby/agents/spawn.py +9 -2
- gobby/agents/spawn_executor.py +49 -61
- gobby/agents/spawners/command_builder.py +4 -4
- gobby/app_context.py +64 -0
- gobby/cli/__init__.py +4 -0
- gobby/cli/install.py +259 -4
- gobby/cli/installers/__init__.py +12 -0
- gobby/cli/installers/copilot.py +242 -0
- gobby/cli/installers/cursor.py +244 -0
- gobby/cli/installers/shared.py +3 -0
- gobby/cli/installers/windsurf.py +242 -0
- gobby/cli/pipelines.py +639 -0
- gobby/cli/sessions.py +3 -1
- gobby/cli/skills.py +209 -0
- gobby/cli/tasks/crud.py +6 -5
- gobby/cli/tasks/search.py +1 -1
- gobby/cli/ui.py +116 -0
- gobby/cli/utils.py +5 -17
- gobby/cli/workflows.py +38 -17
- gobby/config/app.py +5 -0
- gobby/config/features.py +0 -20
- gobby/config/skills.py +23 -2
- gobby/config/tasks.py +4 -0
- gobby/hooks/broadcaster.py +9 -0
- gobby/hooks/event_handlers/__init__.py +155 -0
- gobby/hooks/event_handlers/_agent.py +175 -0
- gobby/hooks/event_handlers/_base.py +92 -0
- gobby/hooks/event_handlers/_misc.py +66 -0
- gobby/hooks/event_handlers/_session.py +487 -0
- gobby/hooks/event_handlers/_tool.py +196 -0
- gobby/hooks/events.py +48 -0
- gobby/hooks/hook_manager.py +27 -3
- gobby/install/copilot/hooks/hook_dispatcher.py +203 -0
- gobby/install/cursor/hooks/hook_dispatcher.py +203 -0
- gobby/install/gemini/hooks/hook_dispatcher.py +8 -0
- gobby/install/windsurf/hooks/hook_dispatcher.py +205 -0
- gobby/llm/__init__.py +14 -1
- gobby/llm/claude.py +594 -43
- gobby/llm/service.py +149 -0
- gobby/mcp_proxy/importer.py +4 -41
- gobby/mcp_proxy/instructions.py +9 -27
- gobby/mcp_proxy/manager.py +13 -3
- gobby/mcp_proxy/models.py +1 -0
- gobby/mcp_proxy/registries.py +66 -5
- gobby/mcp_proxy/server.py +6 -2
- gobby/mcp_proxy/services/recommendation.py +2 -28
- gobby/mcp_proxy/services/tool_filter.py +7 -0
- gobby/mcp_proxy/services/tool_proxy.py +19 -1
- gobby/mcp_proxy/stdio.py +37 -21
- gobby/mcp_proxy/tools/agents.py +7 -0
- gobby/mcp_proxy/tools/artifacts.py +3 -3
- gobby/mcp_proxy/tools/hub.py +30 -1
- gobby/mcp_proxy/tools/orchestration/cleanup.py +5 -5
- gobby/mcp_proxy/tools/orchestration/monitor.py +1 -1
- gobby/mcp_proxy/tools/orchestration/orchestrate.py +8 -3
- gobby/mcp_proxy/tools/orchestration/review.py +17 -4
- gobby/mcp_proxy/tools/orchestration/wait.py +7 -7
- gobby/mcp_proxy/tools/pipelines/__init__.py +254 -0
- gobby/mcp_proxy/tools/pipelines/_discovery.py +67 -0
- gobby/mcp_proxy/tools/pipelines/_execution.py +281 -0
- gobby/mcp_proxy/tools/sessions/_crud.py +4 -4
- gobby/mcp_proxy/tools/sessions/_handoff.py +1 -1
- gobby/mcp_proxy/tools/skills/__init__.py +184 -30
- gobby/mcp_proxy/tools/spawn_agent.py +229 -14
- gobby/mcp_proxy/tools/task_readiness.py +27 -4
- gobby/mcp_proxy/tools/tasks/_context.py +8 -0
- gobby/mcp_proxy/tools/tasks/_crud.py +27 -1
- gobby/mcp_proxy/tools/tasks/_helpers.py +1 -1
- gobby/mcp_proxy/tools/tasks/_lifecycle.py +125 -8
- gobby/mcp_proxy/tools/tasks/_lifecycle_validation.py +2 -1
- gobby/mcp_proxy/tools/tasks/_search.py +1 -1
- gobby/mcp_proxy/tools/workflows/__init__.py +273 -0
- gobby/mcp_proxy/tools/workflows/_artifacts.py +225 -0
- gobby/mcp_proxy/tools/workflows/_import.py +112 -0
- gobby/mcp_proxy/tools/workflows/_lifecycle.py +332 -0
- gobby/mcp_proxy/tools/workflows/_query.py +226 -0
- gobby/mcp_proxy/tools/workflows/_resolution.py +78 -0
- gobby/mcp_proxy/tools/workflows/_terminal.py +175 -0
- gobby/mcp_proxy/tools/worktrees.py +54 -15
- gobby/memory/components/__init__.py +0 -0
- gobby/memory/components/ingestion.py +98 -0
- gobby/memory/components/search.py +108 -0
- gobby/memory/context.py +5 -5
- gobby/memory/manager.py +16 -25
- gobby/paths.py +51 -0
- gobby/prompts/loader.py +1 -35
- gobby/runner.py +131 -16
- gobby/servers/http.py +193 -150
- gobby/servers/routes/__init__.py +2 -0
- gobby/servers/routes/admin.py +56 -0
- gobby/servers/routes/mcp/endpoints/execution.py +33 -32
- gobby/servers/routes/mcp/endpoints/registry.py +8 -8
- gobby/servers/routes/mcp/hooks.py +10 -1
- gobby/servers/routes/pipelines.py +227 -0
- gobby/servers/websocket.py +314 -1
- gobby/sessions/analyzer.py +89 -3
- gobby/sessions/manager.py +5 -5
- gobby/sessions/transcripts/__init__.py +3 -0
- gobby/sessions/transcripts/claude.py +5 -0
- gobby/sessions/transcripts/codex.py +5 -0
- gobby/sessions/transcripts/gemini.py +5 -0
- gobby/skills/hubs/__init__.py +25 -0
- gobby/skills/hubs/base.py +234 -0
- gobby/skills/hubs/claude_plugins.py +328 -0
- gobby/skills/hubs/clawdhub.py +289 -0
- gobby/skills/hubs/github_collection.py +465 -0
- gobby/skills/hubs/manager.py +263 -0
- gobby/skills/hubs/skillhub.py +342 -0
- gobby/skills/parser.py +23 -0
- gobby/skills/sync.py +5 -4
- gobby/storage/artifacts.py +19 -0
- gobby/storage/memories.py +4 -4
- gobby/storage/migrations.py +118 -3
- gobby/storage/pipelines.py +367 -0
- gobby/storage/sessions.py +23 -4
- gobby/storage/skills.py +48 -8
- gobby/storage/tasks/_aggregates.py +2 -2
- gobby/storage/tasks/_lifecycle.py +4 -4
- gobby/storage/tasks/_models.py +7 -1
- gobby/storage/tasks/_queries.py +3 -3
- gobby/sync/memories.py +4 -3
- gobby/tasks/commits.py +48 -17
- gobby/tasks/external_validator.py +4 -17
- gobby/tasks/validation.py +13 -87
- gobby/tools/summarizer.py +18 -51
- gobby/utils/status.py +13 -0
- gobby/workflows/actions.py +80 -0
- gobby/workflows/context_actions.py +265 -27
- gobby/workflows/definitions.py +119 -1
- gobby/workflows/detection_helpers.py +23 -11
- gobby/workflows/enforcement/__init__.py +11 -1
- gobby/workflows/enforcement/blocking.py +96 -0
- gobby/workflows/enforcement/handlers.py +35 -1
- gobby/workflows/enforcement/task_policy.py +18 -0
- gobby/workflows/engine.py +26 -4
- gobby/workflows/evaluator.py +8 -5
- gobby/workflows/lifecycle_evaluator.py +59 -27
- gobby/workflows/loader.py +567 -30
- gobby/workflows/lobster_compat.py +147 -0
- gobby/workflows/pipeline_executor.py +801 -0
- gobby/workflows/pipeline_state.py +172 -0
- gobby/workflows/pipeline_webhooks.py +206 -0
- gobby/workflows/premature_stop.py +5 -0
- gobby/worktrees/git.py +135 -20
- {gobby-0.2.8.dist-info → gobby-0.2.11.dist-info}/METADATA +56 -22
- {gobby-0.2.8.dist-info → gobby-0.2.11.dist-info}/RECORD +166 -122
- gobby/hooks/event_handlers.py +0 -1008
- gobby/mcp_proxy/tools/workflows.py +0 -1023
- {gobby-0.2.8.dist-info → gobby-0.2.11.dist-info}/WHEEL +0 -0
- {gobby-0.2.8.dist-info → gobby-0.2.11.dist-info}/entry_points.txt +0 -0
- {gobby-0.2.8.dist-info → gobby-0.2.11.dist-info}/licenses/LICENSE.md +0 -0
- {gobby-0.2.8.dist-info → gobby-0.2.11.dist-info}/top_level.txt +0 -0
gobby/servers/http.py
CHANGED
|
@@ -10,7 +10,7 @@ import logging
|
|
|
10
10
|
import time
|
|
11
11
|
from collections.abc import AsyncGenerator
|
|
12
12
|
from contextlib import asynccontextmanager
|
|
13
|
-
from typing import Any
|
|
13
|
+
from typing import TYPE_CHECKING, Any
|
|
14
14
|
|
|
15
15
|
from fastapi import FastAPI, HTTPException, Request
|
|
16
16
|
from fastapi.middleware.cors import CORSMiddleware
|
|
@@ -19,18 +19,22 @@ from fastapi.responses import JSONResponse
|
|
|
19
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
|
-
from gobby.llm import
|
|
22
|
+
from gobby.llm import create_llm_service
|
|
23
23
|
from gobby.mcp_proxy.registries import setup_internal_registries
|
|
24
24
|
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
|
-
from gobby.memory.manager import MemoryManager
|
|
28
|
-
from gobby.storage.sessions import LocalSessionManager
|
|
29
|
-
from gobby.storage.tasks import LocalTaskManager
|
|
30
|
-
from gobby.sync.tasks import TaskSyncManager
|
|
31
27
|
from gobby.utils.metrics import get_metrics_collector
|
|
32
28
|
from gobby.utils.version import get_version
|
|
33
29
|
|
|
30
|
+
if TYPE_CHECKING:
|
|
31
|
+
from gobby.app_context import ServiceContainer
|
|
32
|
+
from gobby.config.app import DaemonConfig
|
|
33
|
+
from gobby.llm import LLMService
|
|
34
|
+
from gobby.mcp_proxy.manager import MCPClientManager
|
|
35
|
+
from gobby.servers.websocket import WebSocketServer
|
|
36
|
+
from gobby.utils.tool_metrics import ToolMetricsManager
|
|
37
|
+
|
|
34
38
|
logger = logging.getLogger(__name__)
|
|
35
39
|
|
|
36
40
|
|
|
@@ -44,84 +48,38 @@ class HTTPServer:
|
|
|
44
48
|
|
|
45
49
|
def __init__(
|
|
46
50
|
self,
|
|
51
|
+
services: "ServiceContainer",
|
|
47
52
|
port: int = 8000,
|
|
48
53
|
test_mode: bool = False,
|
|
49
|
-
mcp_manager: Any | None = None,
|
|
50
|
-
mcp_db_manager: Any | None = None,
|
|
51
|
-
config: Any | None = None,
|
|
52
54
|
codex_client: Any | None = None,
|
|
53
|
-
session_manager: LocalSessionManager | None = None,
|
|
54
|
-
websocket_server: Any | None = None,
|
|
55
|
-
task_manager: LocalTaskManager | None = None,
|
|
56
|
-
task_sync_manager: TaskSyncManager | None = None,
|
|
57
|
-
message_processor: Any | None = None,
|
|
58
|
-
message_manager: Any | None = None, # LocalSessionMessageManager
|
|
59
|
-
memory_manager: "MemoryManager | None" = None,
|
|
60
|
-
llm_service: "LLMService | None" = None,
|
|
61
|
-
memory_sync_manager: Any | None = None,
|
|
62
|
-
task_validator: Any | None = None,
|
|
63
|
-
metrics_manager: Any | None = None,
|
|
64
|
-
agent_runner: Any | None = None,
|
|
65
|
-
worktree_storage: Any | None = None,
|
|
66
|
-
clone_storage: Any | None = None,
|
|
67
|
-
git_manager: Any | None = None,
|
|
68
|
-
project_id: str | None = None,
|
|
69
55
|
) -> None:
|
|
70
56
|
"""
|
|
71
57
|
Initialize HTTP server.
|
|
72
58
|
|
|
73
59
|
Args:
|
|
60
|
+
services: ServiceContainer holding all dependencies
|
|
74
61
|
port: Server port
|
|
75
62
|
test_mode: Run in test mode (disable features that conflict with testing)
|
|
76
|
-
mcp_manager: MCPClientManager instance for multi-server support
|
|
77
|
-
mcp_db_manager: LocalMCPManager instance for SQLite-based storage of MCP
|
|
78
|
-
server configurations and tool schemas. Used by ToolsHandler for
|
|
79
|
-
progressive tool discovery. Optional; defaults to None.
|
|
80
|
-
config: DaemonConfig instance for configuration
|
|
81
63
|
codex_client: CodexAppServerClient instance for Codex integration
|
|
82
|
-
session_manager: LocalSessionManager for session storage
|
|
83
|
-
websocket_server: Optional WebSocketServer instance for event broadcasting
|
|
84
|
-
task_manager: LocalTaskManager instance
|
|
85
|
-
task_sync_manager: TaskSyncManager instance
|
|
86
|
-
message_processor: SessionMessageProcessor instance
|
|
87
|
-
message_manager: LocalSessionMessageManager instance for retrieval
|
|
88
|
-
memory_manager: MemoryManager instance
|
|
89
|
-
llm_service: LLMService instance
|
|
90
64
|
"""
|
|
65
|
+
self.services = services
|
|
91
66
|
self.port = port
|
|
92
67
|
self.test_mode = test_mode
|
|
93
|
-
self.mcp_manager = mcp_manager
|
|
94
|
-
self.config = config
|
|
95
68
|
self.codex_client = codex_client
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
self.
|
|
99
|
-
|
|
100
|
-
self.
|
|
101
|
-
self.memory_manager = memory_manager
|
|
102
|
-
self.websocket_server = websocket_server
|
|
103
|
-
self.llm_service = llm_service
|
|
104
|
-
self.memory_sync_manager = memory_sync_manager
|
|
105
|
-
self.task_validator = task_validator
|
|
106
|
-
self.metrics_manager = metrics_manager
|
|
107
|
-
self.agent_runner = agent_runner
|
|
108
|
-
self.worktree_storage = worktree_storage
|
|
109
|
-
self.clone_storage = clone_storage
|
|
110
|
-
self.git_manager = git_manager
|
|
111
|
-
self.project_id = project_id
|
|
112
|
-
|
|
113
|
-
# Initialize WebSocket broadcaster
|
|
114
|
-
# Note: websocket_server might be None if disabled
|
|
115
|
-
self.broadcaster = HookEventBroadcaster(websocket_server, config)
|
|
69
|
+
|
|
70
|
+
# WebSocket server reference (set by GobbyRunner after construction)
|
|
71
|
+
self.websocket_server: WebSocketServer | None = None
|
|
72
|
+
|
|
73
|
+
self.broadcaster = HookEventBroadcaster(services.websocket_server, services.config)
|
|
116
74
|
|
|
117
75
|
self._start_time: float = time.time()
|
|
118
76
|
|
|
119
|
-
# Create LLM service if not provided
|
|
120
|
-
if not
|
|
77
|
+
# Create LLM service if not provided in container (fallback)
|
|
78
|
+
if not services.llm_service and services.config:
|
|
121
79
|
try:
|
|
122
|
-
|
|
80
|
+
services.llm_service = create_llm_service(services.config)
|
|
123
81
|
logger.debug(
|
|
124
|
-
f"LLM service initialized with providers: {
|
|
82
|
+
f"LLM service initialized with providers: {services.llm_service.enabled_providers}"
|
|
125
83
|
)
|
|
126
84
|
except Exception as e:
|
|
127
85
|
logger.error(f"Failed to initialize LLM service: {e}")
|
|
@@ -130,12 +88,13 @@ class HTTPServer:
|
|
|
130
88
|
self._mcp_server = None
|
|
131
89
|
self._internal_manager = None
|
|
132
90
|
self._tools_handler = None
|
|
133
|
-
|
|
134
|
-
if mcp_manager:
|
|
91
|
+
|
|
92
|
+
if services.mcp_manager:
|
|
135
93
|
# Determine WebSocket port
|
|
136
94
|
ws_port = 60888
|
|
137
|
-
|
|
138
|
-
|
|
95
|
+
cfg = services.config
|
|
96
|
+
if cfg and hasattr(cfg, "websocket") and cfg.websocket:
|
|
97
|
+
ws_port = cfg.websocket.port
|
|
139
98
|
|
|
140
99
|
# Create a lazy getter for tool_proxy that will be available after
|
|
141
100
|
# GobbyDaemonTools is created. This allows in-process agents to route
|
|
@@ -149,84 +108,90 @@ class HTTPServer:
|
|
|
149
108
|
merge_storage = None
|
|
150
109
|
merge_resolver = None
|
|
151
110
|
inter_session_message_manager = None
|
|
152
|
-
if mcp_db_manager:
|
|
111
|
+
if self.services.mcp_db_manager:
|
|
153
112
|
from gobby.storage.inter_session_messages import InterSessionMessageManager
|
|
154
113
|
from gobby.storage.merge_resolutions import MergeResolutionManager
|
|
155
114
|
from gobby.worktrees.merge.resolver import MergeResolver
|
|
156
115
|
|
|
157
|
-
merge_storage = MergeResolutionManager(mcp_db_manager.db)
|
|
116
|
+
merge_storage = MergeResolutionManager(self.services.mcp_db_manager.db)
|
|
158
117
|
merge_resolver = MergeResolver()
|
|
159
|
-
merge_resolver._llm_service =
|
|
160
|
-
inter_session_message_manager = InterSessionMessageManager(
|
|
118
|
+
merge_resolver._llm_service = services.llm_service
|
|
119
|
+
inter_session_message_manager = InterSessionMessageManager(
|
|
120
|
+
self.services.mcp_db_manager.db
|
|
121
|
+
)
|
|
161
122
|
logger.debug("Merge resolution and inter-session messaging subsystems initialized")
|
|
162
123
|
|
|
163
|
-
# Setup internal registries (gobby-tasks, gobby-memory, etc.)
|
|
124
|
+
# Setup internal registries (gobby-tasks, gobby-memory, gobby-pipelines, etc.)
|
|
164
125
|
self._internal_manager = setup_internal_registries(
|
|
165
|
-
_config=config,
|
|
126
|
+
_config=services.config,
|
|
166
127
|
_session_manager=None, # Not needed for internal registries
|
|
167
|
-
memory_manager=memory_manager,
|
|
168
|
-
task_manager=task_manager,
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
128
|
+
memory_manager=services.memory_manager,
|
|
129
|
+
task_manager=services.task_manager,
|
|
130
|
+
db=services.mcp_db_manager.db if services.mcp_db_manager else None,
|
|
131
|
+
sync_manager=services.task_sync_manager,
|
|
132
|
+
task_validator=services.task_validator,
|
|
133
|
+
message_manager=services.message_manager,
|
|
134
|
+
local_session_manager=services.session_manager,
|
|
135
|
+
metrics_manager=services.metrics_manager,
|
|
136
|
+
llm_service=services.llm_service,
|
|
137
|
+
agent_runner=services.agent_runner,
|
|
138
|
+
worktree_storage=services.worktree_storage,
|
|
139
|
+
clone_storage=services.clone_storage,
|
|
140
|
+
git_manager=services.git_manager,
|
|
179
141
|
merge_storage=merge_storage,
|
|
180
142
|
merge_resolver=merge_resolver,
|
|
181
|
-
project_id=
|
|
143
|
+
project_id=services.project_id,
|
|
182
144
|
tool_proxy_getter=tool_proxy_getter,
|
|
183
145
|
inter_session_message_manager=inter_session_message_manager,
|
|
146
|
+
pipeline_executor=services.pipeline_executor,
|
|
147
|
+
workflow_loader=services.workflow_loader,
|
|
148
|
+
pipeline_execution_manager=services.pipeline_execution_manager,
|
|
184
149
|
)
|
|
185
150
|
registry_count = len(self._internal_manager)
|
|
186
151
|
logger.debug(f"Internal registries initialized: {registry_count} registries")
|
|
187
152
|
|
|
188
153
|
# Initialize tool summarizer config
|
|
189
|
-
if config:
|
|
154
|
+
if services.config:
|
|
190
155
|
from gobby.tools.summarizer import init_summarizer_config
|
|
191
156
|
|
|
192
|
-
init_summarizer_config(config.tool_summarizer)
|
|
157
|
+
init_summarizer_config(services.config.tool_summarizer)
|
|
193
158
|
logger.debug("Tool summarizer config initialized")
|
|
194
159
|
|
|
195
160
|
# Create semantic search instance if db available
|
|
196
161
|
semantic_search = None
|
|
197
|
-
if mcp_db_manager:
|
|
198
|
-
semantic_search = SemanticToolSearch(db=mcp_db_manager.db)
|
|
162
|
+
if services.mcp_db_manager:
|
|
163
|
+
semantic_search = SemanticToolSearch(db=services.mcp_db_manager.db)
|
|
199
164
|
logger.debug("Semantic tool search initialized")
|
|
200
165
|
|
|
201
166
|
# Create tool filter for workflow phase restrictions
|
|
202
167
|
tool_filter = None
|
|
203
|
-
if mcp_db_manager:
|
|
204
|
-
tool_filter = ToolFilterService(db=mcp_db_manager.db)
|
|
168
|
+
if services.mcp_db_manager:
|
|
169
|
+
tool_filter = ToolFilterService(db=services.mcp_db_manager.db)
|
|
205
170
|
logger.debug("Tool filter service initialized")
|
|
206
171
|
|
|
207
172
|
# Create fallback resolver for alternative tool suggestions on error
|
|
208
173
|
fallback_resolver = None
|
|
209
|
-
if semantic_search and
|
|
174
|
+
if semantic_search and services.metrics_manager:
|
|
210
175
|
from gobby.mcp_proxy.services.fallback import ToolFallbackResolver
|
|
211
176
|
|
|
212
177
|
fallback_resolver = ToolFallbackResolver(
|
|
213
178
|
semantic_search=semantic_search,
|
|
214
|
-
metrics_manager=
|
|
179
|
+
metrics_manager=services.metrics_manager,
|
|
215
180
|
)
|
|
216
181
|
logger.debug("Fallback resolver initialized")
|
|
217
182
|
|
|
218
183
|
# Create tools handler
|
|
219
184
|
self._tools_handler = GobbyDaemonTools(
|
|
220
|
-
mcp_manager=mcp_manager,
|
|
185
|
+
mcp_manager=services.mcp_manager,
|
|
221
186
|
daemon_port=port,
|
|
222
187
|
websocket_port=ws_port,
|
|
223
188
|
start_time=self._start_time,
|
|
224
189
|
internal_manager=self._internal_manager,
|
|
225
|
-
config=config,
|
|
226
|
-
llm_service=
|
|
227
|
-
session_manager=session_manager,
|
|
228
|
-
memory_manager=memory_manager,
|
|
229
|
-
config_manager=mcp_db_manager,
|
|
190
|
+
config=services.config,
|
|
191
|
+
llm_service=services.llm_service,
|
|
192
|
+
session_manager=services.session_manager,
|
|
193
|
+
memory_manager=services.memory_manager,
|
|
194
|
+
config_manager=services.mcp_db_manager,
|
|
230
195
|
semantic_search=semantic_search,
|
|
231
196
|
tool_filter=tool_filter,
|
|
232
197
|
fallback_resolver=fallback_resolver,
|
|
@@ -240,6 +205,83 @@ class HTTPServer:
|
|
|
240
205
|
self._metrics = get_metrics_collector()
|
|
241
206
|
self._daemon: Any = None # Set externally by daemon
|
|
242
207
|
|
|
208
|
+
# Property accessors for services (delegate to container)
|
|
209
|
+
@property
|
|
210
|
+
def config(self) -> "DaemonConfig | None":
|
|
211
|
+
return self.services.config
|
|
212
|
+
|
|
213
|
+
@property
|
|
214
|
+
def session_manager(self) -> Any:
|
|
215
|
+
return self.services.session_manager
|
|
216
|
+
|
|
217
|
+
@session_manager.setter
|
|
218
|
+
def session_manager(self, value: Any) -> None:
|
|
219
|
+
self.services.session_manager = value
|
|
220
|
+
|
|
221
|
+
@property
|
|
222
|
+
def task_manager(self) -> Any:
|
|
223
|
+
return self.services.task_manager
|
|
224
|
+
|
|
225
|
+
@task_manager.setter
|
|
226
|
+
def task_manager(self, value: Any) -> None:
|
|
227
|
+
self.services.task_manager = value
|
|
228
|
+
|
|
229
|
+
@property
|
|
230
|
+
def mcp_manager(self) -> "MCPClientManager | None":
|
|
231
|
+
return self.services.mcp_manager
|
|
232
|
+
|
|
233
|
+
@mcp_manager.setter
|
|
234
|
+
def mcp_manager(self, value: "MCPClientManager | None") -> None:
|
|
235
|
+
self.services.mcp_manager = value
|
|
236
|
+
|
|
237
|
+
@property
|
|
238
|
+
def llm_service(self) -> "LLMService | None":
|
|
239
|
+
return self.services.llm_service
|
|
240
|
+
|
|
241
|
+
@llm_service.setter
|
|
242
|
+
def llm_service(self, value: "LLMService | None") -> None:
|
|
243
|
+
self.services.llm_service = value
|
|
244
|
+
|
|
245
|
+
@property
|
|
246
|
+
def memory_manager(self) -> Any:
|
|
247
|
+
return self.services.memory_manager
|
|
248
|
+
|
|
249
|
+
@memory_manager.setter
|
|
250
|
+
def memory_manager(self, value: Any) -> None:
|
|
251
|
+
self.services.memory_manager = value
|
|
252
|
+
|
|
253
|
+
@property
|
|
254
|
+
def message_manager(self) -> Any:
|
|
255
|
+
return self.services.message_manager
|
|
256
|
+
|
|
257
|
+
@message_manager.setter
|
|
258
|
+
def message_manager(self, value: Any) -> None:
|
|
259
|
+
self.services.message_manager = value
|
|
260
|
+
|
|
261
|
+
@property
|
|
262
|
+
def message_processor(self) -> Any:
|
|
263
|
+
return self.services.message_processor
|
|
264
|
+
|
|
265
|
+
@message_processor.setter
|
|
266
|
+
def message_processor(self, value: Any) -> None:
|
|
267
|
+
self.services.message_processor = value
|
|
268
|
+
|
|
269
|
+
@property
|
|
270
|
+
def metrics_manager(self) -> "ToolMetricsManager | None":
|
|
271
|
+
return self.services.metrics_manager
|
|
272
|
+
|
|
273
|
+
@metrics_manager.setter
|
|
274
|
+
def metrics_manager(self, value: "ToolMetricsManager | None") -> None:
|
|
275
|
+
self.services.metrics_manager = value
|
|
276
|
+
|
|
277
|
+
@property
|
|
278
|
+
def _mcp_db_manager(self) -> Any:
|
|
279
|
+
return self.services.mcp_db_manager
|
|
280
|
+
|
|
281
|
+
@_mcp_db_manager.setter
|
|
282
|
+
def _mcp_db_manager(self, value: Any) -> None:
|
|
283
|
+
self.services.mcp_db_manager = value
|
|
284
|
+
|
|
243
285
|
@property
|
|
244
286
|
def tool_proxy(self) -> Any:
|
|
245
287
|
"""Get the ToolProxyService instance for routing tool calls with error enrichment."""
|
|
@@ -314,30 +356,32 @@ class HTTPServer:
|
|
|
314
356
|
hook_manager_kwargs: dict[str, Any] = {
|
|
315
357
|
"daemon_host": "localhost",
|
|
316
358
|
"daemon_port": self.port,
|
|
317
|
-
"llm_service": self.llm_service,
|
|
318
|
-
"config": self.config,
|
|
359
|
+
"llm_service": self.services.llm_service,
|
|
360
|
+
"config": self.services.config,
|
|
319
361
|
"broadcaster": self.broadcaster,
|
|
320
|
-
"mcp_manager": self.mcp_manager,
|
|
321
|
-
"message_processor": self.message_processor,
|
|
322
|
-
"memory_sync_manager": self.memory_sync_manager,
|
|
323
|
-
"task_sync_manager": self.task_sync_manager,
|
|
362
|
+
"mcp_manager": self.services.mcp_manager,
|
|
363
|
+
"message_processor": self.services.message_processor,
|
|
364
|
+
"memory_sync_manager": self.services.memory_sync_manager,
|
|
365
|
+
"task_sync_manager": self.services.task_sync_manager,
|
|
324
366
|
}
|
|
325
|
-
if self.config:
|
|
367
|
+
if self.services.config:
|
|
326
368
|
# Pass full log file path from config
|
|
327
|
-
hook_manager_kwargs["log_file"] = self.config.logging.hook_manager
|
|
328
|
-
hook_manager_kwargs["log_max_bytes"] =
|
|
329
|
-
|
|
369
|
+
hook_manager_kwargs["log_file"] = self.services.config.logging.hook_manager
|
|
370
|
+
hook_manager_kwargs["log_max_bytes"] = (
|
|
371
|
+
self.services.config.logging.max_size_mb * 1024 * 1024
|
|
372
|
+
)
|
|
373
|
+
hook_manager_kwargs["log_backup_count"] = self.services.config.logging.backup_count
|
|
330
374
|
|
|
331
375
|
app.state.hook_manager = HookManager(**hook_manager_kwargs)
|
|
332
376
|
logger.debug("HookManager initialized in daemon")
|
|
333
377
|
|
|
334
378
|
# Wire up stop_registry to WebSocket server for stop_request handling
|
|
335
379
|
if (
|
|
336
|
-
self.websocket_server
|
|
380
|
+
self.services.websocket_server
|
|
337
381
|
and hasattr(app.state, "hook_manager")
|
|
338
382
|
and hasattr(app.state.hook_manager, "_stop_registry")
|
|
339
383
|
):
|
|
340
|
-
self.websocket_server.stop_registry = app.state.hook_manager._stop_registry
|
|
384
|
+
self.services.websocket_server.stop_registry = app.state.hook_manager._stop_registry
|
|
341
385
|
logger.debug("Stop registry connected to WebSocket server")
|
|
342
386
|
|
|
343
387
|
# Store server instance for dependency injection
|
|
@@ -474,6 +518,7 @@ class HTTPServer:
|
|
|
474
518
|
create_admin_router,
|
|
475
519
|
create_hooks_router,
|
|
476
520
|
create_mcp_router,
|
|
521
|
+
create_pipelines_router,
|
|
477
522
|
create_plugins_router,
|
|
478
523
|
create_sessions_router,
|
|
479
524
|
create_webhooks_router,
|
|
@@ -486,6 +531,7 @@ class HTTPServer:
|
|
|
486
531
|
app.include_router(create_hooks_router(self))
|
|
487
532
|
app.include_router(create_plugins_router())
|
|
488
533
|
app.include_router(create_webhooks_router())
|
|
534
|
+
app.include_router(create_pipelines_router(self))
|
|
489
535
|
|
|
490
536
|
async def _process_shutdown(self) -> None:
|
|
491
537
|
"""
|
|
@@ -496,45 +542,48 @@ class HTTPServer:
|
|
|
496
542
|
try:
|
|
497
543
|
logger.debug("Processing graceful shutdown")
|
|
498
544
|
|
|
499
|
-
#
|
|
545
|
+
# Cancel pending background tasks immediately instead of polling
|
|
500
546
|
pending_tasks_count = len(self._background_tasks)
|
|
501
547
|
if pending_tasks_count > 0:
|
|
502
548
|
logger.debug(
|
|
503
|
-
"
|
|
549
|
+
"Cancelling pending background tasks",
|
|
504
550
|
extra={"pending_tasks": pending_tasks_count},
|
|
505
551
|
)
|
|
506
552
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
remaining_tasks = len(self._background_tasks)
|
|
518
|
-
|
|
519
|
-
if remaining_tasks > 0:
|
|
520
|
-
logger.warning(
|
|
521
|
-
"Shutdown timeout - some background tasks still pending",
|
|
522
|
-
extra={
|
|
523
|
-
"remaining_tasks": remaining_tasks,
|
|
524
|
-
"wait_seconds": completed_wait,
|
|
525
|
-
},
|
|
526
|
-
)
|
|
527
|
-
else:
|
|
528
|
-
logger.debug(
|
|
529
|
-
"All background tasks completed",
|
|
530
|
-
extra={"wait_seconds": completed_wait},
|
|
553
|
+
# Cancel all tasks
|
|
554
|
+
for task in self._background_tasks:
|
|
555
|
+
task.cancel()
|
|
556
|
+
|
|
557
|
+
# Wait for cancellation to complete with a short timeout
|
|
558
|
+
if self._background_tasks:
|
|
559
|
+
done, pending = await asyncio.wait(
|
|
560
|
+
self._background_tasks,
|
|
561
|
+
timeout=5.0,
|
|
562
|
+
return_when=asyncio.ALL_COMPLETED,
|
|
531
563
|
)
|
|
532
564
|
|
|
565
|
+
completed_count = len(done)
|
|
566
|
+
remaining_count = len(pending)
|
|
567
|
+
|
|
568
|
+
if remaining_count > 0:
|
|
569
|
+
logger.warning(
|
|
570
|
+
"Some background tasks did not cancel in time",
|
|
571
|
+
extra={
|
|
572
|
+
"completed": completed_count,
|
|
573
|
+
"remaining": remaining_count,
|
|
574
|
+
},
|
|
575
|
+
)
|
|
576
|
+
else:
|
|
577
|
+
logger.debug(
|
|
578
|
+
"All background tasks cancelled",
|
|
579
|
+
extra={"completed": completed_count},
|
|
580
|
+
)
|
|
581
|
+
|
|
533
582
|
# Disconnect all MCP servers
|
|
534
|
-
if self.mcp_manager:
|
|
583
|
+
if self.services.mcp_manager:
|
|
535
584
|
logger.debug("Disconnecting MCP servers...")
|
|
536
585
|
try:
|
|
537
|
-
await self.mcp_manager.disconnect_all()
|
|
586
|
+
await self.services.mcp_manager.disconnect_all()
|
|
538
587
|
logger.debug("MCP servers disconnected")
|
|
539
588
|
except Exception as e:
|
|
540
589
|
logger.warning(f"Error disconnecting MCP servers: {e}")
|
|
@@ -560,31 +609,25 @@ class HTTPServer:
|
|
|
560
609
|
|
|
561
610
|
|
|
562
611
|
async def create_server(
|
|
612
|
+
services: "ServiceContainer",
|
|
563
613
|
port: int = 60887,
|
|
564
614
|
test_mode: bool = False,
|
|
565
|
-
mcp_manager: Any | None = None,
|
|
566
|
-
config: Any | None = None,
|
|
567
|
-
session_manager: LocalSessionManager | None = None,
|
|
568
615
|
) -> HTTPServer:
|
|
569
616
|
"""
|
|
570
617
|
Create HTTP server instance.
|
|
571
618
|
|
|
572
619
|
Args:
|
|
620
|
+
services: ServiceContainer holding dependencies
|
|
573
621
|
port: Port to listen on
|
|
574
622
|
test_mode: Enable test mode
|
|
575
|
-
mcp_manager: MCP client manager
|
|
576
|
-
config: Daemon configuration
|
|
577
|
-
session_manager: Local session manager
|
|
578
623
|
|
|
579
624
|
Returns:
|
|
580
625
|
Configured HTTPServer instance
|
|
581
626
|
"""
|
|
582
627
|
return HTTPServer(
|
|
628
|
+
services=services,
|
|
583
629
|
port=port,
|
|
584
630
|
test_mode=test_mode,
|
|
585
|
-
mcp_manager=mcp_manager,
|
|
586
|
-
config=config,
|
|
587
|
-
session_manager=session_manager,
|
|
588
631
|
)
|
|
589
632
|
|
|
590
633
|
|
gobby/servers/routes/__init__.py
CHANGED
|
@@ -11,12 +11,14 @@ from gobby.servers.routes.mcp import (
|
|
|
11
11
|
create_plugins_router,
|
|
12
12
|
create_webhooks_router,
|
|
13
13
|
)
|
|
14
|
+
from gobby.servers.routes.pipelines import create_pipelines_router
|
|
14
15
|
from gobby.servers.routes.sessions import create_sessions_router
|
|
15
16
|
|
|
16
17
|
__all__ = [
|
|
17
18
|
"create_admin_router",
|
|
18
19
|
"create_hooks_router",
|
|
19
20
|
"create_mcp_router",
|
|
21
|
+
"create_pipelines_router",
|
|
20
22
|
"create_plugins_router",
|
|
21
23
|
"create_sessions_router",
|
|
22
24
|
"create_webhooks_router",
|
gobby/servers/routes/admin.py
CHANGED
|
@@ -175,6 +175,17 @@ def create_admin_router(server: "HTTPServer") -> APIRouter:
|
|
|
175
175
|
except Exception as e:
|
|
176
176
|
logger.warning(f"Failed to get memory stats: {e}")
|
|
177
177
|
|
|
178
|
+
# Get artifact statistics
|
|
179
|
+
artifact_stats = {"count": 0}
|
|
180
|
+
if server.session_manager is not None:
|
|
181
|
+
try:
|
|
182
|
+
from gobby.storage.artifacts import LocalArtifactManager
|
|
183
|
+
|
|
184
|
+
artifact_manager = LocalArtifactManager(server.session_manager.db)
|
|
185
|
+
artifact_stats["count"] = artifact_manager.count_artifacts()
|
|
186
|
+
except Exception as e:
|
|
187
|
+
logger.warning(f"Failed to get artifact stats: {e}")
|
|
188
|
+
|
|
178
189
|
# Get skills statistics
|
|
179
190
|
skills_stats: dict[str, Any] = {"total": 0}
|
|
180
191
|
if server._internal_manager:
|
|
@@ -231,6 +242,7 @@ def create_admin_router(server: "HTTPServer") -> APIRouter:
|
|
|
231
242
|
"sessions": session_stats,
|
|
232
243
|
"tasks": task_stats,
|
|
233
244
|
"memory": memory_stats,
|
|
245
|
+
"artifacts": artifact_stats,
|
|
234
246
|
"skills": skills_stats,
|
|
235
247
|
"plugins": plugin_stats,
|
|
236
248
|
"response_time_ms": response_time_ms,
|
|
@@ -278,6 +290,50 @@ def create_admin_router(server: "HTTPServer") -> APIRouter:
|
|
|
278
290
|
logger.error(f"Failed to export metrics: {e}", exc_info=True)
|
|
279
291
|
raise
|
|
280
292
|
|
|
293
|
+
@router.get("/models")
|
|
294
|
+
async def get_models() -> dict[str, Any]:
|
|
295
|
+
"""
|
|
296
|
+
Get available LLM providers and their models.
|
|
297
|
+
|
|
298
|
+
Returns:
|
|
299
|
+
Dictionary with providers and their available models
|
|
300
|
+
"""
|
|
301
|
+
start_time = time.perf_counter()
|
|
302
|
+
|
|
303
|
+
providers_data: dict[str, Any] = {}
|
|
304
|
+
default_provider = None
|
|
305
|
+
default_model = None
|
|
306
|
+
|
|
307
|
+
if server.llm_service is not None:
|
|
308
|
+
enabled = server.llm_service.enabled_providers
|
|
309
|
+
if enabled:
|
|
310
|
+
default_provider = "claude" if "claude" in enabled else enabled[0]
|
|
311
|
+
|
|
312
|
+
# Get models for each enabled provider from config
|
|
313
|
+
if server.services.config and server.services.config.llm_providers:
|
|
314
|
+
llm_config = server.services.config.llm_providers
|
|
315
|
+
|
|
316
|
+
for provider_name in enabled:
|
|
317
|
+
provider_config = getattr(llm_config, provider_name, None)
|
|
318
|
+
if provider_config:
|
|
319
|
+
models = provider_config.get_models_list()
|
|
320
|
+
providers_data[provider_name] = {
|
|
321
|
+
"models": models,
|
|
322
|
+
"auth_mode": provider_config.auth_mode,
|
|
323
|
+
}
|
|
324
|
+
# Set default model from first provider
|
|
325
|
+
if default_model is None and models:
|
|
326
|
+
default_model = models[0]
|
|
327
|
+
|
|
328
|
+
response_time_ms = (time.perf_counter() - start_time) * 1000
|
|
329
|
+
|
|
330
|
+
return {
|
|
331
|
+
"providers": providers_data,
|
|
332
|
+
"default_provider": default_provider,
|
|
333
|
+
"default_model": default_model,
|
|
334
|
+
"response_time_ms": response_time_ms,
|
|
335
|
+
}
|
|
336
|
+
|
|
281
337
|
@router.get("/config")
|
|
282
338
|
async def get_config() -> dict[str, Any]:
|
|
283
339
|
"""
|