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
|
@@ -25,6 +25,7 @@ if TYPE_CHECKING:
|
|
|
25
25
|
def create_artifacts_registry(
|
|
26
26
|
db: LocalDatabase | None = None,
|
|
27
27
|
artifact_manager: LocalArtifactManager | None = None,
|
|
28
|
+
session_manager: Any | None = None,
|
|
28
29
|
) -> InternalToolRegistry:
|
|
29
30
|
"""
|
|
30
31
|
Create an artifacts tool registry with all artifact-related tools.
|
|
@@ -32,10 +33,21 @@ def create_artifacts_registry(
|
|
|
32
33
|
Args:
|
|
33
34
|
db: LocalDatabase instance (used to create artifact_manager if not provided)
|
|
34
35
|
artifact_manager: LocalArtifactManager instance
|
|
36
|
+
session_manager: Session manager for resolving session references
|
|
35
37
|
|
|
36
38
|
Returns:
|
|
37
39
|
InternalToolRegistry with artifact tools registered
|
|
38
40
|
"""
|
|
41
|
+
from gobby.utils.project_context import get_project_context
|
|
42
|
+
|
|
43
|
+
def _resolve_session_id(ref: str) -> str:
|
|
44
|
+
"""Resolve session reference (#N, N, UUID, or prefix) to UUID."""
|
|
45
|
+
if session_manager is None:
|
|
46
|
+
return ref # No resolution available, return as-is
|
|
47
|
+
ctx = get_project_context()
|
|
48
|
+
project_id = ctx.get("id") if ctx else None
|
|
49
|
+
return str(session_manager.resolve_session_reference(ref, project_id))
|
|
50
|
+
|
|
39
51
|
# Create artifact manager if not provided
|
|
40
52
|
if artifact_manager is None:
|
|
41
53
|
if db is None:
|
|
@@ -55,7 +67,7 @@ def create_artifacts_registry(
|
|
|
55
67
|
|
|
56
68
|
@registry.tool(
|
|
57
69
|
name="search_artifacts",
|
|
58
|
-
description="Search artifacts by content using full-text search.",
|
|
70
|
+
description="Search artifacts by content using full-text search. Accepts #N, N, UUID, or prefix for session_id.",
|
|
59
71
|
)
|
|
60
72
|
def search_artifacts(
|
|
61
73
|
query: str,
|
|
@@ -68,7 +80,7 @@ def create_artifacts_registry(
|
|
|
68
80
|
|
|
69
81
|
Args:
|
|
70
82
|
query: Search query text
|
|
71
|
-
session_id: Optional session
|
|
83
|
+
session_id: Optional session reference (accepts #N, N, UUID, or prefix) to filter by
|
|
72
84
|
artifact_type: Optional artifact type to filter by (code, diff, error, etc.)
|
|
73
85
|
limit: Maximum number of results (default: 50)
|
|
74
86
|
|
|
@@ -78,10 +90,18 @@ def create_artifacts_registry(
|
|
|
78
90
|
if not query or not query.strip():
|
|
79
91
|
return {"success": True, "artifacts": [], "count": 0}
|
|
80
92
|
|
|
93
|
+
# Resolve session_id to UUID (accepts #N, N, UUID, or prefix)
|
|
94
|
+
resolved_session_id = session_id
|
|
95
|
+
if session_id:
|
|
96
|
+
try:
|
|
97
|
+
resolved_session_id = _resolve_session_id(session_id)
|
|
98
|
+
except ValueError as e:
|
|
99
|
+
return {"success": False, "error": str(e), "artifacts": []}
|
|
100
|
+
|
|
81
101
|
try:
|
|
82
102
|
artifacts = _artifact_manager.search_artifacts(
|
|
83
103
|
query_text=query,
|
|
84
|
-
session_id=
|
|
104
|
+
session_id=resolved_session_id,
|
|
85
105
|
artifact_type=artifact_type,
|
|
86
106
|
limit=limit,
|
|
87
107
|
)
|
|
@@ -95,7 +115,7 @@ def create_artifacts_registry(
|
|
|
95
115
|
|
|
96
116
|
@registry.tool(
|
|
97
117
|
name="list_artifacts",
|
|
98
|
-
description="List artifacts with optional filters.",
|
|
118
|
+
description="List artifacts with optional filters. Accepts #N, N, UUID, or prefix for session_id.",
|
|
99
119
|
)
|
|
100
120
|
def list_artifacts(
|
|
101
121
|
session_id: str | None = None,
|
|
@@ -107,7 +127,7 @@ def create_artifacts_registry(
|
|
|
107
127
|
List artifacts with optional filters.
|
|
108
128
|
|
|
109
129
|
Args:
|
|
110
|
-
session_id: Optional session
|
|
130
|
+
session_id: Optional session reference (accepts #N, N, UUID, or prefix) to filter by
|
|
111
131
|
artifact_type: Optional artifact type to filter by
|
|
112
132
|
limit: Maximum number of results (default: 100)
|
|
113
133
|
offset: Offset for pagination (default: 0)
|
|
@@ -115,9 +135,17 @@ def create_artifacts_registry(
|
|
|
115
135
|
Returns:
|
|
116
136
|
Dict with success status and list of artifacts
|
|
117
137
|
"""
|
|
138
|
+
# Resolve session_id to UUID (accepts #N, N, UUID, or prefix)
|
|
139
|
+
resolved_session_id = session_id
|
|
140
|
+
if session_id:
|
|
141
|
+
try:
|
|
142
|
+
resolved_session_id = _resolve_session_id(session_id)
|
|
143
|
+
except ValueError as e:
|
|
144
|
+
return {"success": False, "error": str(e), "artifacts": []}
|
|
145
|
+
|
|
118
146
|
try:
|
|
119
147
|
artifacts = _artifact_manager.list_artifacts(
|
|
120
|
-
session_id=
|
|
148
|
+
session_id=resolved_session_id,
|
|
121
149
|
artifact_type=artifact_type,
|
|
122
150
|
limit=limit,
|
|
123
151
|
offset=offset,
|
|
@@ -161,7 +189,7 @@ def create_artifacts_registry(
|
|
|
161
189
|
|
|
162
190
|
@registry.tool(
|
|
163
191
|
name="get_timeline",
|
|
164
|
-
description="Get artifacts for a session in chronological order.",
|
|
192
|
+
description="Get artifacts for a session in chronological order. Accepts #N, N, UUID, or prefix for session_id.",
|
|
165
193
|
)
|
|
166
194
|
def get_timeline(
|
|
167
195
|
session_id: str | None = None,
|
|
@@ -172,7 +200,7 @@ def create_artifacts_registry(
|
|
|
172
200
|
Get artifacts for a session in chronological order (oldest first).
|
|
173
201
|
|
|
174
202
|
Args:
|
|
175
|
-
session_id: Required session
|
|
203
|
+
session_id: Required session reference (accepts #N, N, UUID, or prefix) to get timeline for
|
|
176
204
|
artifact_type: Optional artifact type to filter by
|
|
177
205
|
limit: Maximum number of results (default: 100)
|
|
178
206
|
|
|
@@ -186,10 +214,16 @@ def create_artifacts_registry(
|
|
|
186
214
|
"artifacts": [],
|
|
187
215
|
}
|
|
188
216
|
|
|
217
|
+
# Resolve session_id to UUID (accepts #N, N, UUID, or prefix)
|
|
218
|
+
try:
|
|
219
|
+
resolved_session_id = _resolve_session_id(session_id)
|
|
220
|
+
except ValueError as e:
|
|
221
|
+
return {"success": False, "error": str(e), "artifacts": []}
|
|
222
|
+
|
|
189
223
|
try:
|
|
190
224
|
# Get artifacts (list_artifacts returns newest first by default)
|
|
191
225
|
artifacts = _artifact_manager.list_artifacts(
|
|
192
|
-
session_id=
|
|
226
|
+
session_id=resolved_session_id,
|
|
193
227
|
artifact_type=artifact_type,
|
|
194
228
|
limit=limit,
|
|
195
229
|
offset=0,
|
gobby/mcp_proxy/tools/clones.py
CHANGED
|
@@ -13,13 +13,11 @@ via the downstream proxy pattern (call_tool, list_tools, get_tool_schema).
|
|
|
13
13
|
from __future__ import annotations
|
|
14
14
|
|
|
15
15
|
import logging
|
|
16
|
-
from pathlib import Path
|
|
17
16
|
from typing import TYPE_CHECKING, Any, Literal
|
|
18
17
|
|
|
19
18
|
from gobby.mcp_proxy.tools.internal import InternalToolRegistry
|
|
20
19
|
|
|
21
20
|
if TYPE_CHECKING:
|
|
22
|
-
from gobby.agents.runner import AgentRunner
|
|
23
21
|
from gobby.clones.git import CloneGitManager
|
|
24
22
|
from gobby.storage.clones import LocalCloneManager
|
|
25
23
|
|
|
@@ -30,7 +28,6 @@ def create_clones_registry(
|
|
|
30
28
|
clone_storage: LocalCloneManager,
|
|
31
29
|
git_manager: CloneGitManager,
|
|
32
30
|
project_id: str,
|
|
33
|
-
agent_runner: AgentRunner | None = None,
|
|
34
31
|
) -> InternalToolRegistry:
|
|
35
32
|
"""
|
|
36
33
|
Create the gobby-clones MCP server registry.
|
|
@@ -39,7 +36,6 @@ def create_clones_registry(
|
|
|
39
36
|
clone_storage: Clone storage manager for CRUD operations
|
|
40
37
|
git_manager: Git manager for clone operations
|
|
41
38
|
project_id: Default project ID for new clones
|
|
42
|
-
agent_runner: Optional agent runner for spawning agents in clones
|
|
43
39
|
|
|
44
40
|
Returns:
|
|
45
41
|
InternalToolRegistry with clone management tools
|
|
@@ -519,385 +515,4 @@ def create_clones_registry(
|
|
|
519
515
|
func=merge_clone_to_target,
|
|
520
516
|
)
|
|
521
517
|
|
|
522
|
-
# ===== spawn_agent_in_clone =====
|
|
523
|
-
async def spawn_agent_in_clone(
|
|
524
|
-
prompt: str,
|
|
525
|
-
branch_name: str,
|
|
526
|
-
parent_session_id: str | None = None,
|
|
527
|
-
task_id: str | None = None,
|
|
528
|
-
base_branch: str = "main",
|
|
529
|
-
clone_path: str | None = None,
|
|
530
|
-
mode: str = "terminal",
|
|
531
|
-
terminal: str = "auto",
|
|
532
|
-
provider: Literal["claude", "gemini", "codex", "antigravity"] = "claude",
|
|
533
|
-
model: str | None = None,
|
|
534
|
-
workflow: str | None = None,
|
|
535
|
-
timeout: float = 120.0,
|
|
536
|
-
max_turns: int = 10,
|
|
537
|
-
) -> dict[str, Any]:
|
|
538
|
-
"""
|
|
539
|
-
Create a clone (if needed) and spawn an agent in it.
|
|
540
|
-
|
|
541
|
-
This combines clone creation with agent spawning for isolated development.
|
|
542
|
-
Unlike worktrees, clones are full repository copies that can be worked on
|
|
543
|
-
independently without affecting the main repository.
|
|
544
|
-
|
|
545
|
-
Args:
|
|
546
|
-
prompt: The task/prompt for the agent.
|
|
547
|
-
branch_name: Name for the branch in the clone.
|
|
548
|
-
parent_session_id: Parent session ID for context (required).
|
|
549
|
-
task_id: Optional task ID to link to this clone.
|
|
550
|
-
base_branch: Branch to clone from (default: main).
|
|
551
|
-
clone_path: Optional custom path for the clone.
|
|
552
|
-
mode: Execution mode (terminal, embedded, headless).
|
|
553
|
-
terminal: Terminal for terminal/embedded modes (auto, ghostty, etc.).
|
|
554
|
-
provider: LLM provider (claude, gemini, etc.).
|
|
555
|
-
model: Optional model override.
|
|
556
|
-
workflow: Workflow name to execute.
|
|
557
|
-
timeout: Execution timeout in seconds (default: 120).
|
|
558
|
-
max_turns: Maximum turns (default: 10).
|
|
559
|
-
|
|
560
|
-
Returns:
|
|
561
|
-
Dict with clone_id, run_id, and status.
|
|
562
|
-
"""
|
|
563
|
-
if agent_runner is None:
|
|
564
|
-
return {
|
|
565
|
-
"success": False,
|
|
566
|
-
"error": "Agent runner not configured. Cannot spawn agent.",
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
if parent_session_id is None:
|
|
570
|
-
return {
|
|
571
|
-
"success": False,
|
|
572
|
-
"error": "parent_session_id is required for agent spawning.",
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
# Handle mode aliases and validation
|
|
576
|
-
if mode == "interactive":
|
|
577
|
-
mode = "terminal"
|
|
578
|
-
|
|
579
|
-
valid_modes = ["terminal", "embedded", "headless"]
|
|
580
|
-
if mode not in valid_modes:
|
|
581
|
-
return {
|
|
582
|
-
"success": False,
|
|
583
|
-
"error": (
|
|
584
|
-
f"Invalid mode '{mode}'. Must be one of: {', '.join(valid_modes)}. "
|
|
585
|
-
f"Note: 'in_process' mode is not supported for spawn_agent_in_clone."
|
|
586
|
-
),
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
# Normalize terminal parameter to lowercase
|
|
590
|
-
if isinstance(terminal, str):
|
|
591
|
-
terminal = terminal.lower()
|
|
592
|
-
|
|
593
|
-
# Check spawn depth limit
|
|
594
|
-
can_spawn, reason, _depth = agent_runner.can_spawn(parent_session_id)
|
|
595
|
-
if not can_spawn:
|
|
596
|
-
return {
|
|
597
|
-
"success": False,
|
|
598
|
-
"error": reason,
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
# Check if clone already exists for this branch
|
|
602
|
-
existing = clone_storage.get_by_branch(project_id, branch_name)
|
|
603
|
-
if existing:
|
|
604
|
-
clone = existing
|
|
605
|
-
logger.info(f"Using existing clone for branch '{branch_name}'")
|
|
606
|
-
else:
|
|
607
|
-
# Get remote URL
|
|
608
|
-
remote_url = git_manager.get_remote_url() if git_manager else None
|
|
609
|
-
if not remote_url:
|
|
610
|
-
return {
|
|
611
|
-
"success": False,
|
|
612
|
-
"error": "No remote URL available. Cannot create clone.",
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
# Generate clone path if not provided
|
|
616
|
-
if clone_path is None:
|
|
617
|
-
import platform
|
|
618
|
-
import tempfile
|
|
619
|
-
|
|
620
|
-
if platform.system() == "Windows":
|
|
621
|
-
base = Path(tempfile.gettempdir()) / "gobby-clones"
|
|
622
|
-
else:
|
|
623
|
-
# nosec B108: /tmp is intentional for clones - they're temporary
|
|
624
|
-
base = Path("/tmp").resolve() / "gobby-clones" # nosec B108
|
|
625
|
-
base.mkdir(parents=True, exist_ok=True)
|
|
626
|
-
safe_branch = branch_name.replace("/", "-")
|
|
627
|
-
clone_path = str(base / f"{project_id}-{safe_branch}")
|
|
628
|
-
|
|
629
|
-
# Create the clone
|
|
630
|
-
result = git_manager.shallow_clone(
|
|
631
|
-
remote_url=remote_url,
|
|
632
|
-
clone_path=clone_path,
|
|
633
|
-
branch=base_branch,
|
|
634
|
-
depth=1,
|
|
635
|
-
)
|
|
636
|
-
|
|
637
|
-
if not result.success:
|
|
638
|
-
return {
|
|
639
|
-
"success": False,
|
|
640
|
-
"error": f"Clone failed: {result.error or result.message}",
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
# Store clone record
|
|
644
|
-
clone = clone_storage.create(
|
|
645
|
-
project_id=project_id,
|
|
646
|
-
branch_name=branch_name,
|
|
647
|
-
clone_path=clone_path,
|
|
648
|
-
base_branch=base_branch,
|
|
649
|
-
task_id=task_id,
|
|
650
|
-
remote_url=remote_url,
|
|
651
|
-
)
|
|
652
|
-
|
|
653
|
-
# Import AgentConfig and get machine_id
|
|
654
|
-
from gobby.agents.runner import AgentConfig
|
|
655
|
-
from gobby.utils.machine_id import get_machine_id
|
|
656
|
-
|
|
657
|
-
machine_id = get_machine_id()
|
|
658
|
-
|
|
659
|
-
# Create agent config
|
|
660
|
-
config = AgentConfig(
|
|
661
|
-
prompt=prompt,
|
|
662
|
-
parent_session_id=parent_session_id,
|
|
663
|
-
project_id=project_id,
|
|
664
|
-
machine_id=machine_id,
|
|
665
|
-
source=provider,
|
|
666
|
-
workflow=workflow,
|
|
667
|
-
task=task_id,
|
|
668
|
-
session_context="summary_markdown",
|
|
669
|
-
mode=mode,
|
|
670
|
-
terminal=terminal,
|
|
671
|
-
provider=provider,
|
|
672
|
-
model=model,
|
|
673
|
-
max_turns=max_turns,
|
|
674
|
-
timeout=timeout,
|
|
675
|
-
project_path=clone.clone_path,
|
|
676
|
-
)
|
|
677
|
-
|
|
678
|
-
# Prepare the run
|
|
679
|
-
from gobby.llm.executor import AgentResult
|
|
680
|
-
|
|
681
|
-
prepare_result = agent_runner.prepare_run(config)
|
|
682
|
-
if isinstance(prepare_result, AgentResult):
|
|
683
|
-
return {
|
|
684
|
-
"success": False,
|
|
685
|
-
"clone_id": clone.id,
|
|
686
|
-
"clone_path": clone.clone_path,
|
|
687
|
-
"branch_name": clone.branch_name,
|
|
688
|
-
"error": prepare_result.error,
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
context = prepare_result
|
|
692
|
-
if context.session is None or context.run is None:
|
|
693
|
-
return {
|
|
694
|
-
"success": False,
|
|
695
|
-
"clone_id": clone.id,
|
|
696
|
-
"error": "Internal error: context missing session or run",
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
child_session = context.session
|
|
700
|
-
agent_run = context.run
|
|
701
|
-
|
|
702
|
-
# Claim clone for the child session
|
|
703
|
-
clone_storage.claim(clone.id, child_session.id)
|
|
704
|
-
|
|
705
|
-
# Build enhanced prompt with clone context
|
|
706
|
-
context_lines = [
|
|
707
|
-
"## CRITICAL: Clone Context",
|
|
708
|
-
"You are working in an ISOLATED git clone, NOT the main repository.",
|
|
709
|
-
"",
|
|
710
|
-
f"**Your workspace:** {clone.clone_path}",
|
|
711
|
-
f"**Your branch:** {clone.branch_name}",
|
|
712
|
-
]
|
|
713
|
-
if task_id:
|
|
714
|
-
context_lines.append(f"**Your task:** {task_id}")
|
|
715
|
-
context_lines.extend(
|
|
716
|
-
[
|
|
717
|
-
"",
|
|
718
|
-
"**IMPORTANT RULES:**",
|
|
719
|
-
f"1. ALL file operations must be within {clone.clone_path}",
|
|
720
|
-
"2. Do NOT access the main repository",
|
|
721
|
-
"3. Run `pwd` to verify your location before any file operations",
|
|
722
|
-
f"4. Commit to YOUR branch ({clone.branch_name})",
|
|
723
|
-
"5. When your assigned task is complete, STOP - do not claim other tasks",
|
|
724
|
-
"",
|
|
725
|
-
"---",
|
|
726
|
-
"",
|
|
727
|
-
]
|
|
728
|
-
)
|
|
729
|
-
enhanced_prompt = "\n".join(context_lines) + prompt
|
|
730
|
-
|
|
731
|
-
# Spawn based on mode
|
|
732
|
-
if mode == "terminal":
|
|
733
|
-
from gobby.agents.spawn import TerminalSpawner
|
|
734
|
-
|
|
735
|
-
terminal_spawner = TerminalSpawner()
|
|
736
|
-
terminal_result = terminal_spawner.spawn_agent(
|
|
737
|
-
cli=provider,
|
|
738
|
-
cwd=clone.clone_path,
|
|
739
|
-
session_id=child_session.id,
|
|
740
|
-
parent_session_id=parent_session_id,
|
|
741
|
-
agent_run_id=agent_run.id,
|
|
742
|
-
project_id=project_id,
|
|
743
|
-
workflow_name=workflow,
|
|
744
|
-
agent_depth=child_session.agent_depth,
|
|
745
|
-
max_agent_depth=agent_runner._child_session_manager.max_agent_depth,
|
|
746
|
-
terminal=terminal,
|
|
747
|
-
prompt=enhanced_prompt,
|
|
748
|
-
)
|
|
749
|
-
|
|
750
|
-
if not terminal_result.success:
|
|
751
|
-
return {
|
|
752
|
-
"success": False,
|
|
753
|
-
"clone_id": clone.id,
|
|
754
|
-
"clone_path": clone.clone_path,
|
|
755
|
-
"branch_name": clone.branch_name,
|
|
756
|
-
"run_id": agent_run.id,
|
|
757
|
-
"child_session_id": child_session.id,
|
|
758
|
-
"error": terminal_result.error or terminal_result.message,
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
return {
|
|
762
|
-
"success": True,
|
|
763
|
-
"clone_id": clone.id,
|
|
764
|
-
"clone_path": clone.clone_path,
|
|
765
|
-
"branch_name": clone.branch_name,
|
|
766
|
-
"run_id": agent_run.id,
|
|
767
|
-
"child_session_id": child_session.id,
|
|
768
|
-
"status": "pending",
|
|
769
|
-
"message": f"Agent spawned in {terminal_result.terminal_type} (PID: {terminal_result.pid})",
|
|
770
|
-
"terminal_type": terminal_result.terminal_type,
|
|
771
|
-
"pid": terminal_result.pid,
|
|
772
|
-
}
|
|
773
|
-
|
|
774
|
-
elif mode == "embedded":
|
|
775
|
-
from gobby.agents.spawn import EmbeddedSpawner
|
|
776
|
-
|
|
777
|
-
embedded_spawner = EmbeddedSpawner()
|
|
778
|
-
embedded_result = embedded_spawner.spawn_agent(
|
|
779
|
-
cli=provider,
|
|
780
|
-
cwd=clone.clone_path,
|
|
781
|
-
session_id=child_session.id,
|
|
782
|
-
parent_session_id=parent_session_id,
|
|
783
|
-
agent_run_id=agent_run.id,
|
|
784
|
-
project_id=project_id,
|
|
785
|
-
workflow_name=workflow,
|
|
786
|
-
agent_depth=child_session.agent_depth,
|
|
787
|
-
max_agent_depth=agent_runner._child_session_manager.max_agent_depth,
|
|
788
|
-
prompt=enhanced_prompt,
|
|
789
|
-
)
|
|
790
|
-
|
|
791
|
-
return {
|
|
792
|
-
"success": embedded_result.success,
|
|
793
|
-
"clone_id": clone.id,
|
|
794
|
-
"clone_path": clone.clone_path,
|
|
795
|
-
"branch_name": clone.branch_name,
|
|
796
|
-
"run_id": agent_run.id,
|
|
797
|
-
"child_session_id": child_session.id,
|
|
798
|
-
"status": "pending" if embedded_result.success else "error",
|
|
799
|
-
"error": embedded_result.error if not embedded_result.success else None,
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
else: # headless
|
|
803
|
-
from gobby.agents.spawn import HeadlessSpawner
|
|
804
|
-
|
|
805
|
-
headless_spawner = HeadlessSpawner()
|
|
806
|
-
headless_result = headless_spawner.spawn_agent(
|
|
807
|
-
cli=provider,
|
|
808
|
-
cwd=clone.clone_path,
|
|
809
|
-
session_id=child_session.id,
|
|
810
|
-
parent_session_id=parent_session_id,
|
|
811
|
-
agent_run_id=agent_run.id,
|
|
812
|
-
project_id=project_id,
|
|
813
|
-
workflow_name=workflow,
|
|
814
|
-
agent_depth=child_session.agent_depth,
|
|
815
|
-
max_agent_depth=agent_runner._child_session_manager.max_agent_depth,
|
|
816
|
-
prompt=enhanced_prompt,
|
|
817
|
-
)
|
|
818
|
-
|
|
819
|
-
return {
|
|
820
|
-
"success": headless_result.success,
|
|
821
|
-
"clone_id": clone.id,
|
|
822
|
-
"clone_path": clone.clone_path,
|
|
823
|
-
"branch_name": clone.branch_name,
|
|
824
|
-
"run_id": agent_run.id,
|
|
825
|
-
"child_session_id": child_session.id,
|
|
826
|
-
"status": "pending" if headless_result.success else "error",
|
|
827
|
-
"pid": headless_result.pid if headless_result.success else None,
|
|
828
|
-
"error": headless_result.error if not headless_result.success else None,
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
registry.register(
|
|
832
|
-
name="spawn_agent_in_clone",
|
|
833
|
-
description="Create a clone and spawn an agent to work in it",
|
|
834
|
-
input_schema={
|
|
835
|
-
"type": "object",
|
|
836
|
-
"properties": {
|
|
837
|
-
"prompt": {
|
|
838
|
-
"type": "string",
|
|
839
|
-
"description": "The task/prompt for the agent",
|
|
840
|
-
},
|
|
841
|
-
"branch_name": {
|
|
842
|
-
"type": "string",
|
|
843
|
-
"description": "Name for the branch in the clone",
|
|
844
|
-
},
|
|
845
|
-
"parent_session_id": {
|
|
846
|
-
"type": "string",
|
|
847
|
-
"description": "Parent session ID for context (required)",
|
|
848
|
-
},
|
|
849
|
-
"task_id": {
|
|
850
|
-
"type": "string",
|
|
851
|
-
"description": "Optional task ID to link to this clone",
|
|
852
|
-
},
|
|
853
|
-
"base_branch": {
|
|
854
|
-
"type": "string",
|
|
855
|
-
"description": "Branch to clone from",
|
|
856
|
-
"default": "main",
|
|
857
|
-
},
|
|
858
|
-
"clone_path": {
|
|
859
|
-
"type": "string",
|
|
860
|
-
"description": "Optional custom path for the clone",
|
|
861
|
-
},
|
|
862
|
-
"mode": {
|
|
863
|
-
"type": "string",
|
|
864
|
-
"description": "Execution mode",
|
|
865
|
-
"enum": ["terminal", "embedded", "headless"],
|
|
866
|
-
"default": "terminal",
|
|
867
|
-
},
|
|
868
|
-
"terminal": {
|
|
869
|
-
"type": "string",
|
|
870
|
-
"description": "Terminal type for terminal/embedded modes",
|
|
871
|
-
"default": "auto",
|
|
872
|
-
},
|
|
873
|
-
"provider": {
|
|
874
|
-
"type": "string",
|
|
875
|
-
"description": "LLM provider",
|
|
876
|
-
"enum": ["claude", "gemini", "codex", "antigravity"],
|
|
877
|
-
"default": "claude",
|
|
878
|
-
},
|
|
879
|
-
"model": {
|
|
880
|
-
"type": "string",
|
|
881
|
-
"description": "Optional model override",
|
|
882
|
-
},
|
|
883
|
-
"workflow": {
|
|
884
|
-
"type": "string",
|
|
885
|
-
"description": "Workflow name to execute",
|
|
886
|
-
},
|
|
887
|
-
"timeout": {
|
|
888
|
-
"type": "number",
|
|
889
|
-
"description": "Execution timeout in seconds",
|
|
890
|
-
"default": 120.0,
|
|
891
|
-
},
|
|
892
|
-
"max_turns": {
|
|
893
|
-
"type": "integer",
|
|
894
|
-
"description": "Maximum turns",
|
|
895
|
-
"default": 10,
|
|
896
|
-
},
|
|
897
|
-
},
|
|
898
|
-
"required": ["prompt", "branch_name", "parent_session_id"],
|
|
899
|
-
},
|
|
900
|
-
func=spawn_agent_in_clone,
|
|
901
|
-
)
|
|
902
|
-
|
|
903
518
|
return registry
|
gobby/mcp_proxy/tools/memory.py
CHANGED
|
@@ -255,7 +255,7 @@ def create_memory_registry(
|
|
|
255
255
|
name="get_related_memories",
|
|
256
256
|
description="Get memories related to a specific memory via cross-references.",
|
|
257
257
|
)
|
|
258
|
-
def get_related_memories(
|
|
258
|
+
async def get_related_memories(
|
|
259
259
|
memory_id: str,
|
|
260
260
|
limit: int = 5,
|
|
261
261
|
min_similarity: float = 0.0,
|
|
@@ -272,7 +272,7 @@ def create_memory_registry(
|
|
|
272
272
|
min_similarity: Minimum similarity threshold (0.0-1.0)
|
|
273
273
|
"""
|
|
274
274
|
try:
|
|
275
|
-
memories = memory_manager.get_related(
|
|
275
|
+
memories = await memory_manager.get_related(
|
|
276
276
|
memory_id=memory_id,
|
|
277
277
|
limit=limit,
|
|
278
278
|
min_similarity=min_similarity,
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Session tools package.
|
|
2
|
+
|
|
3
|
+
This package provides MCP tools for session management. Re-exports maintain
|
|
4
|
+
backwards compatibility with the original session_messages.py module.
|
|
5
|
+
|
|
6
|
+
Public API:
|
|
7
|
+
- create_session_messages_registry: Factory function to create the session tool registry
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from gobby.mcp_proxy.tools.sessions._factory import create_session_messages_registry
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"create_session_messages_registry",
|
|
14
|
+
]
|