gobby 0.2.6__py3-none-any.whl → 0.2.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- gobby/__init__.py +1 -1
- gobby/adapters/__init__.py +2 -1
- gobby/adapters/claude_code.py +96 -35
- 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/adapters/gemini.py +140 -38
- gobby/agents/definitions.py +11 -1
- gobby/agents/isolation.py +525 -0
- gobby/agents/registry.py +11 -0
- gobby/agents/sandbox.py +261 -0
- gobby/agents/session.py +1 -0
- gobby/agents/spawn.py +42 -287
- gobby/agents/spawn_executor.py +415 -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/macos.py +26 -1
- gobby/agents/spawners/prompt_manager.py +125 -0
- gobby/cli/__init__.py +0 -2
- gobby/cli/install.py +4 -4
- gobby/cli/installers/claude.py +6 -0
- gobby/cli/installers/gemini.py +6 -0
- gobby/cli/installers/shared.py +103 -4
- gobby/cli/memory.py +185 -0
- gobby/cli/sessions.py +1 -1
- gobby/cli/utils.py +9 -2
- gobby/clones/git.py +177 -0
- gobby/config/__init__.py +12 -97
- gobby/config/app.py +10 -94
- gobby/config/extensions.py +2 -2
- gobby/config/features.py +7 -130
- gobby/config/skills.py +31 -0
- gobby/config/tasks.py +4 -28
- gobby/hooks/__init__.py +0 -13
- gobby/hooks/event_handlers.py +150 -8
- gobby/hooks/hook_manager.py +21 -3
- gobby/hooks/plugins.py +1 -1
- gobby/hooks/webhooks.py +1 -1
- gobby/install/gemini/hooks/hook_dispatcher.py +74 -15
- gobby/llm/resolver.py +3 -2
- gobby/mcp_proxy/importer.py +62 -4
- gobby/mcp_proxy/instructions.py +4 -2
- gobby/mcp_proxy/registries.py +22 -8
- gobby/mcp_proxy/services/recommendation.py +43 -11
- gobby/mcp_proxy/tools/agent_messaging.py +93 -44
- gobby/mcp_proxy/tools/agents.py +76 -740
- gobby/mcp_proxy/tools/artifacts.py +43 -9
- gobby/mcp_proxy/tools/clones.py +0 -385
- gobby/mcp_proxy/tools/memory.py +2 -2
- gobby/mcp_proxy/tools/sessions/__init__.py +14 -0
- gobby/mcp_proxy/tools/sessions/_commits.py +239 -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 +503 -0
- gobby/mcp_proxy/tools/sessions/_messages.py +166 -0
- gobby/mcp_proxy/tools/skills/__init__.py +14 -29
- gobby/mcp_proxy/tools/spawn_agent.py +455 -0
- gobby/mcp_proxy/tools/tasks/_context.py +18 -0
- gobby/mcp_proxy/tools/tasks/_crud.py +13 -6
- gobby/mcp_proxy/tools/tasks/_lifecycle.py +79 -30
- gobby/mcp_proxy/tools/tasks/_lifecycle_validation.py +1 -1
- gobby/mcp_proxy/tools/tasks/_session.py +22 -7
- gobby/mcp_proxy/tools/workflows.py +84 -34
- gobby/mcp_proxy/tools/worktrees.py +32 -350
- gobby/memory/extractor.py +15 -1
- gobby/memory/ingestion/__init__.py +5 -0
- gobby/memory/ingestion/multimodal.py +221 -0
- gobby/memory/manager.py +62 -283
- 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 +13 -0
- gobby/servers/http.py +1 -4
- gobby/servers/routes/admin.py +14 -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 +51 -4
- gobby/servers/routes/mcp/tools.py +48 -1506
- gobby/servers/websocket.py +57 -1
- gobby/sessions/analyzer.py +2 -2
- gobby/sessions/lifecycle.py +1 -1
- gobby/sessions/manager.py +9 -0
- gobby/sessions/processor.py +10 -0
- gobby/sessions/transcripts/base.py +1 -0
- gobby/sessions/transcripts/claude.py +15 -5
- gobby/sessions/transcripts/gemini.py +100 -34
- gobby/skills/parser.py +30 -2
- gobby/storage/database.py +9 -2
- gobby/storage/memories.py +32 -21
- gobby/storage/migrations.py +174 -368
- gobby/storage/sessions.py +45 -7
- gobby/storage/skills.py +80 -7
- gobby/storage/tasks/_lifecycle.py +18 -3
- gobby/sync/memories.py +1 -1
- gobby/tasks/external_validator.py +1 -1
- gobby/tasks/validation.py +22 -20
- gobby/tools/summarizer.py +91 -10
- gobby/utils/project_context.py +2 -3
- gobby/utils/status.py +13 -0
- gobby/workflows/actions.py +221 -1217
- gobby/workflows/artifact_actions.py +31 -0
- gobby/workflows/autonomous_actions.py +11 -0
- gobby/workflows/context_actions.py +50 -1
- gobby/workflows/detection_helpers.py +38 -24
- gobby/workflows/enforcement/__init__.py +47 -0
- gobby/workflows/enforcement/blocking.py +281 -0
- gobby/workflows/enforcement/commit_policy.py +283 -0
- gobby/workflows/enforcement/handlers.py +269 -0
- gobby/workflows/enforcement/task_policy.py +542 -0
- gobby/workflows/engine.py +93 -0
- gobby/workflows/evaluator.py +110 -0
- gobby/workflows/git_utils.py +106 -0
- gobby/workflows/hooks.py +41 -0
- gobby/workflows/llm_actions.py +30 -0
- gobby/workflows/mcp_actions.py +20 -1
- gobby/workflows/memory_actions.py +91 -0
- gobby/workflows/safe_evaluator.py +191 -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 +217 -51
- 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.6.dist-info → gobby-0.2.8.dist-info}/METADATA +6 -1
- {gobby-0.2.6.dist-info → gobby-0.2.8.dist-info}/RECORD +139 -163
- {gobby-0.2.6.dist-info → gobby-0.2.8.dist-info}/WHEEL +1 -1
- gobby/adapters/codex.py +0 -1332
- gobby/cli/tui.py +0 -34
- 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/mcp_proxy/tools/session_messages.py +0 -1055
- 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/handoff/compact.md +0 -63
- gobby/prompts/defaults/handoff/session_end.md +0 -57
- gobby/prompts/defaults/memory/extract.md +0 -61
- 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/tui/__init__.py +0 -5
- gobby/tui/api_client.py +0 -278
- gobby/tui/app.py +0 -329
- gobby/tui/screens/__init__.py +0 -25
- gobby/tui/screens/agents.py +0 -333
- gobby/tui/screens/chat.py +0 -450
- gobby/tui/screens/dashboard.py +0 -377
- gobby/tui/screens/memory.py +0 -305
- gobby/tui/screens/metrics.py +0 -231
- gobby/tui/screens/orchestrator.py +0 -903
- gobby/tui/screens/sessions.py +0 -412
- gobby/tui/screens/tasks.py +0 -440
- gobby/tui/screens/workflows.py +0 -289
- gobby/tui/screens/worktrees.py +0 -174
- gobby/tui/widgets/__init__.py +0 -21
- gobby/tui/widgets/chat.py +0 -210
- gobby/tui/widgets/conductor.py +0 -104
- gobby/tui/widgets/menu.py +0 -132
- gobby/tui/widgets/message_panel.py +0 -160
- gobby/tui/widgets/review_gate.py +0 -224
- gobby/tui/widgets/task_tree.py +0 -99
- gobby/tui/widgets/token_budget.py +0 -166
- gobby/tui/ws_client.py +0 -258
- gobby/workflows/task_enforcement_actions.py +0 -1343
- {gobby-0.2.6.dist-info → gobby-0.2.8.dist-info}/entry_points.txt +0 -0
- {gobby-0.2.6.dist-info → gobby-0.2.8.dist-info}/licenses/LICENSE.md +0 -0
- {gobby-0.2.6.dist-info → gobby-0.2.8.dist-info}/top_level.txt +0 -0
|
@@ -216,52 +216,111 @@ def main() -> int:
|
|
|
216
216
|
# This captures the terminal/process info for session correlation
|
|
217
217
|
if hook_type == "SessionStart":
|
|
218
218
|
input_data["terminal_context"] = get_terminal_context()
|
|
219
|
+
# Note: gobby_context (parent_session_id, workflow, etc.) is no longer
|
|
220
|
+
# injected from env vars. For spawned agents, the session is pre-created
|
|
221
|
+
# with all linkage via preflight+resume pattern, so the daemon already
|
|
222
|
+
# has the context when SessionStart fires.
|
|
219
223
|
|
|
220
224
|
# Log what Gemini CLI sends us (for debugging hook data issues)
|
|
221
|
-
|
|
225
|
+
# Extract common context fields for structured logging
|
|
226
|
+
session_id = input_data.get("session_id")
|
|
227
|
+
task_id = input_data.get("task_id")
|
|
228
|
+
project_id = input_data.get("project_id")
|
|
229
|
+
base_context = {
|
|
230
|
+
"hook_type": hook_type,
|
|
231
|
+
"session_id": session_id,
|
|
232
|
+
"task_id": task_id,
|
|
233
|
+
"project_id": project_id,
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
logger.info(
|
|
237
|
+
"[%s] Received input keys: %s",
|
|
238
|
+
hook_type,
|
|
239
|
+
list(input_data.keys()),
|
|
240
|
+
extra=base_context,
|
|
241
|
+
)
|
|
222
242
|
|
|
223
243
|
# Log hook-specific critical fields
|
|
224
244
|
if hook_type == "SessionStart":
|
|
225
|
-
logger.info(
|
|
245
|
+
logger.info(
|
|
246
|
+
"[SessionStart] session_id=%s",
|
|
247
|
+
session_id,
|
|
248
|
+
extra=base_context,
|
|
249
|
+
)
|
|
226
250
|
elif hook_type == "SessionEnd":
|
|
251
|
+
reason = input_data.get("reason")
|
|
227
252
|
logger.info(
|
|
228
|
-
|
|
229
|
-
|
|
253
|
+
"[SessionEnd] session_id=%s, reason=%s",
|
|
254
|
+
session_id,
|
|
255
|
+
reason,
|
|
256
|
+
extra={**base_context, "reason": reason},
|
|
230
257
|
)
|
|
231
258
|
elif hook_type == "BeforeAgent":
|
|
232
259
|
prompt = input_data.get("prompt", "")
|
|
233
260
|
prompt_preview = prompt[:100] + "..." if len(prompt) > 100 else prompt
|
|
234
261
|
logger.info(
|
|
235
|
-
|
|
262
|
+
"[BeforeAgent] session_id=%s, prompt=%s",
|
|
263
|
+
session_id,
|
|
264
|
+
prompt_preview,
|
|
265
|
+
extra={**base_context, "prompt_preview": prompt_preview},
|
|
236
266
|
)
|
|
237
267
|
elif hook_type == "BeforeTool":
|
|
238
268
|
tool_name = input_data.get("tool_name") or input_data.get("function_name", "unknown")
|
|
239
269
|
logger.info(
|
|
240
|
-
|
|
270
|
+
"[BeforeTool] tool_name=%s, session_id=%s",
|
|
271
|
+
tool_name,
|
|
272
|
+
session_id,
|
|
273
|
+
extra={**base_context, "tool_name": tool_name},
|
|
241
274
|
)
|
|
242
275
|
elif hook_type == "AfterTool":
|
|
243
276
|
tool_name = input_data.get("tool_name") or input_data.get("function_name", "unknown")
|
|
277
|
+
error = input_data.get("error")
|
|
244
278
|
logger.info(
|
|
245
|
-
|
|
279
|
+
"[AfterTool] tool_name=%s, session_id=%s",
|
|
280
|
+
tool_name,
|
|
281
|
+
session_id,
|
|
282
|
+
extra={**base_context, "tool_name": tool_name, "error": error},
|
|
246
283
|
)
|
|
247
284
|
elif hook_type == "BeforeToolSelection":
|
|
248
|
-
logger.info(
|
|
285
|
+
logger.info(
|
|
286
|
+
"[BeforeToolSelection] session_id=%s",
|
|
287
|
+
session_id,
|
|
288
|
+
extra=base_context,
|
|
289
|
+
)
|
|
249
290
|
elif hook_type == "BeforeModel":
|
|
291
|
+
model = input_data.get("model", "unknown")
|
|
250
292
|
logger.info(
|
|
251
|
-
|
|
252
|
-
|
|
293
|
+
"[BeforeModel] session_id=%s, model=%s",
|
|
294
|
+
session_id,
|
|
295
|
+
model,
|
|
296
|
+
extra={**base_context, "model": model},
|
|
253
297
|
)
|
|
254
298
|
elif hook_type == "AfterModel":
|
|
255
|
-
logger.info(
|
|
299
|
+
logger.info(
|
|
300
|
+
"[AfterModel] session_id=%s",
|
|
301
|
+
session_id,
|
|
302
|
+
extra=base_context,
|
|
303
|
+
)
|
|
256
304
|
elif hook_type == "PreCompress":
|
|
257
|
-
logger.info(
|
|
305
|
+
logger.info(
|
|
306
|
+
"[PreCompress] session_id=%s",
|
|
307
|
+
session_id,
|
|
308
|
+
extra=base_context,
|
|
309
|
+
)
|
|
258
310
|
elif hook_type == "Notification":
|
|
311
|
+
message = input_data.get("message")
|
|
259
312
|
logger.info(
|
|
260
|
-
|
|
261
|
-
|
|
313
|
+
"[Notification] session_id=%s, message=%s",
|
|
314
|
+
session_id,
|
|
315
|
+
message,
|
|
316
|
+
extra={**base_context, "notification_message": message},
|
|
262
317
|
)
|
|
263
318
|
elif hook_type == "AfterAgent":
|
|
264
|
-
logger.info(
|
|
319
|
+
logger.info(
|
|
320
|
+
"[AfterAgent] session_id=%s",
|
|
321
|
+
session_id,
|
|
322
|
+
extra=base_context,
|
|
323
|
+
)
|
|
265
324
|
|
|
266
325
|
if debug_mode:
|
|
267
326
|
logger.debug(f"Input data: {input_data}")
|
gobby/llm/resolver.py
CHANGED
|
@@ -18,7 +18,8 @@ from typing import TYPE_CHECKING, Literal
|
|
|
18
18
|
from gobby.llm.executor import AgentExecutor
|
|
19
19
|
|
|
20
20
|
if TYPE_CHECKING:
|
|
21
|
-
from gobby.config.app import DaemonConfig
|
|
21
|
+
from gobby.config.app import DaemonConfig
|
|
22
|
+
from gobby.config.llm_providers import LLMProvidersConfig
|
|
22
23
|
from gobby.workflows.definitions import WorkflowDefinition
|
|
23
24
|
|
|
24
25
|
logger = logging.getLogger(__name__)
|
|
@@ -479,7 +480,7 @@ def _create_codex_executor(
|
|
|
479
480
|
|
|
480
481
|
# Re-export for TYPE_CHECKING
|
|
481
482
|
if TYPE_CHECKING:
|
|
482
|
-
from gobby.config.
|
|
483
|
+
from gobby.config.llm_providers import LLMProviderConfig
|
|
483
484
|
|
|
484
485
|
|
|
485
486
|
class ExecutorRegistry:
|
gobby/mcp_proxy/importer.py
CHANGED
|
@@ -5,6 +5,8 @@ import re
|
|
|
5
5
|
from typing import TYPE_CHECKING, Any
|
|
6
6
|
|
|
7
7
|
from gobby.config.app import DaemonConfig
|
|
8
|
+
from gobby.config.features import DEFAULT_IMPORT_MCP_SERVER_PROMPT
|
|
9
|
+
from gobby.prompts import PromptLoader
|
|
8
10
|
from gobby.storage.database import DatabaseProtocol
|
|
9
11
|
from gobby.storage.mcp import LocalMCPManager
|
|
10
12
|
from gobby.storage.projects import LocalProjectManager
|
|
@@ -18,6 +20,21 @@ logger = logging.getLogger(__name__)
|
|
|
18
20
|
# Pattern to detect placeholder secrets like <YOUR_API_KEY>
|
|
19
21
|
SECRET_PLACEHOLDER_PATTERN = re.compile(r"<YOUR_[A-Z0-9_]+>")
|
|
20
22
|
|
|
23
|
+
DEFAULT_GITHUB_FETCH_PROMPT = """Fetch the README from this GitHub repository and extract MCP server configuration:
|
|
24
|
+
|
|
25
|
+
{github_url}
|
|
26
|
+
|
|
27
|
+
If the URL doesn't point directly to a README, try to find and fetch the README.md file.
|
|
28
|
+
|
|
29
|
+
After reading the documentation, extract the MCP server configuration as a JSON object."""
|
|
30
|
+
|
|
31
|
+
DEFAULT_SEARCH_FETCH_PROMPT = """Search for MCP server: {search_query}
|
|
32
|
+
|
|
33
|
+
Find the official documentation or GitHub repository for this MCP server.
|
|
34
|
+
Then fetch and read the README or installation docs.
|
|
35
|
+
|
|
36
|
+
After reading the documentation, extract the MCP server configuration as a JSON object."""
|
|
37
|
+
|
|
21
38
|
|
|
22
39
|
class MCPServerImporter:
|
|
23
40
|
"""Handles importing MCP servers from various sources."""
|
|
@@ -46,6 +63,21 @@ class MCPServerImporter:
|
|
|
46
63
|
self.mcp_client_manager = mcp_client_manager
|
|
47
64
|
self.import_config = config.get_import_mcp_server_config()
|
|
48
65
|
|
|
66
|
+
# Initialize prompt loader
|
|
67
|
+
project_path = None
|
|
68
|
+
if current_project_id:
|
|
69
|
+
if project := self.project_manager.get(current_project_id):
|
|
70
|
+
project_path = project.repo_path
|
|
71
|
+
|
|
72
|
+
from pathlib import Path
|
|
73
|
+
|
|
74
|
+
self._loader = PromptLoader(project_dir=Path(project_path) if project_path else None)
|
|
75
|
+
|
|
76
|
+
# Register fallbacks
|
|
77
|
+
self._loader.register_fallback("import/github_fetch", lambda: DEFAULT_GITHUB_FETCH_PROMPT)
|
|
78
|
+
self._loader.register_fallback("import/search_fetch", lambda: DEFAULT_SEARCH_FETCH_PROMPT)
|
|
79
|
+
self._loader.register_fallback("import/system", lambda: DEFAULT_IMPORT_MCP_SERVER_PROMPT)
|
|
80
|
+
|
|
49
81
|
async def import_from_project(
|
|
50
82
|
self,
|
|
51
83
|
source_project: str,
|
|
@@ -171,10 +203,23 @@ class MCPServerImporter:
|
|
|
171
203
|
from claude_agent_sdk import AssistantMessage, ClaudeAgentOptions, TextBlock, query
|
|
172
204
|
|
|
173
205
|
# Build prompt to fetch and extract config
|
|
174
|
-
|
|
206
|
+
prompt_path = self.import_config.github_fetch_prompt_path or "import/github_fetch"
|
|
207
|
+
try:
|
|
208
|
+
prompt = self._loader.render(prompt_path, {"github_url": github_url})
|
|
209
|
+
except Exception as e:
|
|
210
|
+
logger.warning(f"Failed to load Github fetch prompt: {e}")
|
|
211
|
+
prompt = DEFAULT_GITHUB_FETCH_PROMPT.format(github_url=github_url)
|
|
212
|
+
|
|
213
|
+
# Get system prompt
|
|
214
|
+
sys_prompt_path = self.import_config.prompt_path or "import/system"
|
|
215
|
+
try:
|
|
216
|
+
system_prompt = self._loader.render(sys_prompt_path, {})
|
|
217
|
+
except Exception as e:
|
|
218
|
+
logger.warning(f"Failed to load import system prompt: {e}")
|
|
219
|
+
system_prompt = DEFAULT_IMPORT_MCP_SERVER_PROMPT
|
|
175
220
|
|
|
176
221
|
options = ClaudeAgentOptions(
|
|
177
|
-
system_prompt=
|
|
222
|
+
system_prompt=system_prompt,
|
|
178
223
|
max_turns=3,
|
|
179
224
|
model=self.import_config.model,
|
|
180
225
|
allowed_tools=["WebFetch"],
|
|
@@ -222,10 +267,23 @@ class MCPServerImporter:
|
|
|
222
267
|
from claude_agent_sdk import AssistantMessage, ClaudeAgentOptions, TextBlock, query
|
|
223
268
|
|
|
224
269
|
# Build prompt to search and extract config
|
|
225
|
-
|
|
270
|
+
prompt_path = self.import_config.search_fetch_prompt_path or "import/search_fetch"
|
|
271
|
+
try:
|
|
272
|
+
prompt = self._loader.render(prompt_path, {"search_query": search_query})
|
|
273
|
+
except Exception as e:
|
|
274
|
+
logger.warning(f"Failed to load search fetch prompt: {e}")
|
|
275
|
+
prompt = DEFAULT_SEARCH_FETCH_PROMPT.format(search_query=search_query)
|
|
276
|
+
|
|
277
|
+
# Get system prompt
|
|
278
|
+
sys_prompt_path = self.import_config.prompt_path or "import/system"
|
|
279
|
+
try:
|
|
280
|
+
system_prompt = self._loader.render(sys_prompt_path, {})
|
|
281
|
+
except Exception as e:
|
|
282
|
+
logger.warning(f"Failed to load import system prompt: {e}")
|
|
283
|
+
system_prompt = DEFAULT_IMPORT_MCP_SERVER_PROMPT
|
|
226
284
|
|
|
227
285
|
options = ClaudeAgentOptions(
|
|
228
|
-
system_prompt=
|
|
286
|
+
system_prompt=system_prompt,
|
|
229
287
|
max_turns=5, # More turns for search + fetch
|
|
230
288
|
model=self.import_config.model,
|
|
231
289
|
allowed_tools=["WebSearch", "WebFetch"],
|
gobby/mcp_proxy/instructions.py
CHANGED
|
@@ -26,9 +26,11 @@ def build_gobby_instructions() -> str:
|
|
|
26
26
|
At the start of EVERY session:
|
|
27
27
|
1. `list_mcp_servers()` — Discover available servers
|
|
28
28
|
2. `list_skills()` — Discover available skills
|
|
29
|
-
3. Session ID: Look for `
|
|
29
|
+
3. Session ID: Look for `Gobby Session Ref:` or `Gobby Session ID:` in your context.
|
|
30
30
|
If missing, call:
|
|
31
|
-
`call_tool("gobby-sessions", "
|
|
31
|
+
`call_tool("gobby-sessions", "get_current_session", {"external_id": "<your-session-id>", "source": "<cli-name>"})`
|
|
32
|
+
|
|
33
|
+
Session and task references use `#N` format (e.g., `#1`, `#42`) which is project-scoped.
|
|
32
34
|
</startup>
|
|
33
35
|
|
|
34
36
|
<tool_discovery>
|
gobby/mcp_proxy/registries.py
CHANGED
|
@@ -114,7 +114,7 @@ def setup_internal_registries(
|
|
|
114
114
|
# Initialize sessions registry (messages + session CRUD)
|
|
115
115
|
# Register if either message_manager or local_session_manager is available
|
|
116
116
|
if message_manager is not None or local_session_manager is not None:
|
|
117
|
-
from gobby.mcp_proxy.tools.
|
|
117
|
+
from gobby.mcp_proxy.tools.sessions import create_session_messages_registry
|
|
118
118
|
|
|
119
119
|
session_messages_registry = create_session_messages_registry(
|
|
120
120
|
message_manager=message_manager,
|
|
@@ -168,22 +168,38 @@ def setup_internal_registries(
|
|
|
168
168
|
|
|
169
169
|
# Initialize agents registry if agent_runner is available
|
|
170
170
|
if agent_runner is not None:
|
|
171
|
-
from gobby.agents.
|
|
171
|
+
from gobby.agents.definitions import AgentDefinitionLoader
|
|
172
172
|
from gobby.mcp_proxy.tools.agents import create_agents_registry
|
|
173
173
|
|
|
174
|
+
# Create clone git manager if we have a git manager
|
|
175
|
+
clone_git_manager = None
|
|
176
|
+
if git_manager is not None:
|
|
177
|
+
try:
|
|
178
|
+
from gobby.clones.git import CloneGitManager
|
|
179
|
+
|
|
180
|
+
clone_git_manager = CloneGitManager(git_manager.repo_path)
|
|
181
|
+
except Exception as e:
|
|
182
|
+
logger.debug(f"CloneGitManager not available for spawn_agent: {e}")
|
|
183
|
+
|
|
174
184
|
agents_registry = create_agents_registry(
|
|
175
185
|
runner=agent_runner,
|
|
176
|
-
|
|
186
|
+
agent_loader=AgentDefinitionLoader(),
|
|
187
|
+
session_manager=local_session_manager,
|
|
188
|
+
task_manager=task_manager,
|
|
189
|
+
worktree_storage=worktree_storage,
|
|
190
|
+
git_manager=git_manager,
|
|
191
|
+
clone_storage=clone_storage,
|
|
192
|
+
clone_manager=clone_git_manager,
|
|
177
193
|
)
|
|
178
194
|
|
|
179
|
-
# Add inter-agent messaging tools if message manager
|
|
180
|
-
if inter_session_message_manager is not None:
|
|
195
|
+
# Add inter-agent messaging tools if message manager and session manager are available
|
|
196
|
+
if inter_session_message_manager is not None and local_session_manager is not None:
|
|
181
197
|
from gobby.mcp_proxy.tools.agent_messaging import add_messaging_tools
|
|
182
198
|
|
|
183
199
|
add_messaging_tools(
|
|
184
200
|
registry=agents_registry,
|
|
185
201
|
message_manager=inter_session_message_manager,
|
|
186
|
-
|
|
202
|
+
session_manager=local_session_manager,
|
|
187
203
|
)
|
|
188
204
|
logger.debug("Agent messaging tools added to agents registry")
|
|
189
205
|
|
|
@@ -198,7 +214,6 @@ def setup_internal_registries(
|
|
|
198
214
|
worktree_storage=worktree_storage,
|
|
199
215
|
git_manager=git_manager,
|
|
200
216
|
project_id=project_id,
|
|
201
|
-
agent_runner=agent_runner,
|
|
202
217
|
)
|
|
203
218
|
manager.add_registry(worktrees_registry)
|
|
204
219
|
logger.debug("Worktrees registry initialized")
|
|
@@ -222,7 +237,6 @@ def setup_internal_registries(
|
|
|
222
237
|
clone_storage=clone_storage,
|
|
223
238
|
git_manager=clone_git_manager,
|
|
224
239
|
project_id=project_id or "",
|
|
225
|
-
agent_runner=agent_runner,
|
|
226
240
|
)
|
|
227
241
|
manager.add_registry(clones_registry)
|
|
228
242
|
logger.debug("Clones registry initialized")
|
|
@@ -7,13 +7,30 @@ import logging
|
|
|
7
7
|
from typing import TYPE_CHECKING, Any, Literal
|
|
8
8
|
|
|
9
9
|
if TYPE_CHECKING:
|
|
10
|
-
from gobby.config.
|
|
10
|
+
from gobby.config.features import RecommendToolsConfig
|
|
11
|
+
from gobby.prompts import PromptLoader
|
|
11
12
|
|
|
12
13
|
logger = logging.getLogger("gobby.mcp.server")
|
|
13
14
|
|
|
14
15
|
# Search mode type
|
|
15
16
|
SearchMode = Literal["llm", "semantic", "hybrid"]
|
|
16
17
|
|
|
18
|
+
DEFAULT_HYBRID_RERANK_PROMPT = """Re-rank the following tools for the task: "{task_description}"
|
|
19
|
+
|
|
20
|
+
Candidates:
|
|
21
|
+
{candidate_list}
|
|
22
|
+
|
|
23
|
+
Select the best {top_k} tools. Return JSON:
|
|
24
|
+
{{"recommendations": [{{"server": "...", "tool": "...", "reason": "..."}}]}}"""
|
|
25
|
+
|
|
26
|
+
DEFAULT_LLM_PROMPT = """Recommend tools for the task: "{task_description}"
|
|
27
|
+
|
|
28
|
+
Available Servers:
|
|
29
|
+
{available_servers}
|
|
30
|
+
|
|
31
|
+
Return JSON:
|
|
32
|
+
{{"recommendations": [{{"server": "...", "tool": "...", "reason": "..."}}]}}"""
|
|
33
|
+
|
|
17
34
|
|
|
18
35
|
class RecommendationService:
|
|
19
36
|
"""Service for recommending tools."""
|
|
@@ -31,12 +48,17 @@ class RecommendationService:
|
|
|
31
48
|
self._semantic_search = semantic_search
|
|
32
49
|
self._project_id = project_id
|
|
33
50
|
self._config = config
|
|
51
|
+
self._loader = PromptLoader()
|
|
52
|
+
self._loader.register_fallback(
|
|
53
|
+
"features/recommend_hybrid", lambda: DEFAULT_HYBRID_RERANK_PROMPT
|
|
54
|
+
)
|
|
55
|
+
self._loader.register_fallback("features/recommend_llm", lambda: DEFAULT_LLM_PROMPT)
|
|
34
56
|
|
|
35
57
|
def _get_config(self) -> RecommendToolsConfig:
|
|
36
58
|
"""Get config with fallback to defaults."""
|
|
37
59
|
if self._config is not None:
|
|
38
60
|
return self._config
|
|
39
|
-
from gobby.config.
|
|
61
|
+
from gobby.config.features import RecommendToolsConfig
|
|
40
62
|
|
|
41
63
|
return RecommendToolsConfig()
|
|
42
64
|
|
|
@@ -153,11 +175,16 @@ class RecommendationService:
|
|
|
153
175
|
for c in candidates
|
|
154
176
|
)
|
|
155
177
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
178
|
+
prompt_path = config.hybrid_rerank_prompt_path or "features/recommend_hybrid"
|
|
179
|
+
context = {
|
|
180
|
+
"task_description": task_description,
|
|
181
|
+
"candidate_list": candidate_list,
|
|
182
|
+
"top_k": top_k,
|
|
183
|
+
}
|
|
184
|
+
try:
|
|
185
|
+
prompt = self._loader.render(prompt_path, context)
|
|
186
|
+
except Exception:
|
|
187
|
+
prompt = DEFAULT_HYBRID_RERANK_PROMPT.format(**context)
|
|
161
188
|
|
|
162
189
|
provider = self._llm_service.get_default_provider()
|
|
163
190
|
response = await provider.generate_text(prompt)
|
|
@@ -191,10 +218,15 @@ class RecommendationService:
|
|
|
191
218
|
config = self._get_config()
|
|
192
219
|
available_servers = self._mcp_manager.get_available_servers()
|
|
193
220
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
221
|
+
prompt_path = config.llm_prompt_path or "features/recommend_llm"
|
|
222
|
+
context = {
|
|
223
|
+
"task_description": task_description,
|
|
224
|
+
"available_servers": ", ".join(available_servers),
|
|
225
|
+
}
|
|
226
|
+
try:
|
|
227
|
+
prompt = self._loader.render(prompt_path, context)
|
|
228
|
+
except Exception:
|
|
229
|
+
prompt = DEFAULT_LLM_PROMPT.format(**context)
|
|
198
230
|
|
|
199
231
|
provider = self._llm_service.get_default_provider()
|
|
200
232
|
response = await provider.generate_text(prompt)
|