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
|
@@ -22,13 +22,9 @@ from typing import TYPE_CHECKING, Any, Literal, cast
|
|
|
22
22
|
|
|
23
23
|
from gobby.mcp_proxy.tools.internal import InternalToolRegistry
|
|
24
24
|
from gobby.utils.project_context import get_project_context
|
|
25
|
-
from gobby.workflows.definitions import WorkflowState
|
|
26
|
-
from gobby.workflows.loader import WorkflowLoader
|
|
27
|
-
from gobby.workflows.state_manager import WorkflowStateManager
|
|
28
25
|
from gobby.worktrees.git import WorktreeGitManager
|
|
29
26
|
|
|
30
27
|
if TYPE_CHECKING:
|
|
31
|
-
from gobby.agents.runner import AgentRunner
|
|
32
28
|
from gobby.storage.worktrees import LocalWorktreeManager
|
|
33
29
|
from gobby.worktrees.git import WorktreeGitManager
|
|
34
30
|
|
|
@@ -291,7 +287,7 @@ def create_worktrees_registry(
|
|
|
291
287
|
worktree_storage: LocalWorktreeManager,
|
|
292
288
|
git_manager: WorktreeGitManager | None = None,
|
|
293
289
|
project_id: str | None = None,
|
|
294
|
-
|
|
290
|
+
session_manager: Any | None = None,
|
|
295
291
|
) -> InternalToolRegistry:
|
|
296
292
|
"""
|
|
297
293
|
Create a worktree tool registry with all worktree-related tools.
|
|
@@ -300,11 +296,20 @@ def create_worktrees_registry(
|
|
|
300
296
|
worktree_storage: LocalWorktreeManager for database operations.
|
|
301
297
|
git_manager: WorktreeGitManager for git operations.
|
|
302
298
|
project_id: Default project ID for operations.
|
|
303
|
-
|
|
299
|
+
session_manager: Session manager for resolving session references.
|
|
304
300
|
|
|
305
301
|
Returns:
|
|
306
302
|
InternalToolRegistry with all worktree tools registered.
|
|
307
303
|
"""
|
|
304
|
+
|
|
305
|
+
def _resolve_session_id(ref: str) -> str:
|
|
306
|
+
"""Resolve session reference (#N, N, UUID, or prefix) to UUID."""
|
|
307
|
+
if session_manager is None:
|
|
308
|
+
return ref # No resolution available, return as-is
|
|
309
|
+
ctx = get_project_context()
|
|
310
|
+
proj_id = ctx.get("id") if ctx else project_id
|
|
311
|
+
return str(session_manager.resolve_session_reference(ref, proj_id))
|
|
312
|
+
|
|
308
313
|
registry = InternalToolRegistry(
|
|
309
314
|
name="gobby-worktrees",
|
|
310
315
|
description="Git worktree management - create, manage, and cleanup isolated development directories",
|
|
@@ -441,7 +446,7 @@ def create_worktrees_registry(
|
|
|
441
446
|
|
|
442
447
|
@registry.tool(
|
|
443
448
|
name="list_worktrees",
|
|
444
|
-
description="List worktrees with optional filters.",
|
|
449
|
+
description="List worktrees with optional filters. Accepts #N, N, UUID, or prefix for agent_session_id.",
|
|
445
450
|
)
|
|
446
451
|
async def list_worktrees(
|
|
447
452
|
status: str | None = None,
|
|
@@ -453,16 +458,24 @@ def create_worktrees_registry(
|
|
|
453
458
|
|
|
454
459
|
Args:
|
|
455
460
|
status: Filter by status (active, stale, merged, abandoned).
|
|
456
|
-
agent_session_id:
|
|
461
|
+
agent_session_id: Session reference (accepts #N, N, UUID, or prefix) to filter by owning session.
|
|
457
462
|
limit: Maximum results (default: 50).
|
|
458
463
|
|
|
459
464
|
Returns:
|
|
460
465
|
Dict with list of worktrees.
|
|
461
466
|
"""
|
|
467
|
+
# Resolve session_id to UUID (accepts #N, N, UUID, or prefix)
|
|
468
|
+
resolved_session_id = agent_session_id
|
|
469
|
+
if agent_session_id:
|
|
470
|
+
try:
|
|
471
|
+
resolved_session_id = _resolve_session_id(agent_session_id)
|
|
472
|
+
except ValueError as e:
|
|
473
|
+
return {"success": False, "error": str(e)}
|
|
474
|
+
|
|
462
475
|
worktrees = worktree_storage.list_worktrees(
|
|
463
476
|
project_id=project_id,
|
|
464
477
|
status=status,
|
|
465
|
-
agent_session_id=
|
|
478
|
+
agent_session_id=resolved_session_id,
|
|
466
479
|
limit=limit,
|
|
467
480
|
)
|
|
468
481
|
|
|
@@ -485,7 +498,7 @@ def create_worktrees_registry(
|
|
|
485
498
|
|
|
486
499
|
@registry.tool(
|
|
487
500
|
name="claim_worktree",
|
|
488
|
-
description="Claim ownership of a worktree for an agent session.",
|
|
501
|
+
description="Claim ownership of a worktree for an agent session. Accepts #N, N, UUID, or prefix for session_id.",
|
|
489
502
|
)
|
|
490
503
|
async def claim_worktree(
|
|
491
504
|
worktree_id: str,
|
|
@@ -496,11 +509,17 @@ def create_worktrees_registry(
|
|
|
496
509
|
|
|
497
510
|
Args:
|
|
498
511
|
worktree_id: The worktree ID to claim.
|
|
499
|
-
session_id:
|
|
512
|
+
session_id: Session reference (accepts #N, N, UUID, or prefix) claiming ownership.
|
|
500
513
|
|
|
501
514
|
Returns:
|
|
502
515
|
Dict with success status.
|
|
503
516
|
"""
|
|
517
|
+
# Resolve session_id to UUID (accepts #N, N, UUID, or prefix)
|
|
518
|
+
try:
|
|
519
|
+
resolved_session_id = _resolve_session_id(session_id)
|
|
520
|
+
except ValueError as e:
|
|
521
|
+
return {"success": False, "error": str(e)}
|
|
522
|
+
|
|
504
523
|
worktree = worktree_storage.get(worktree_id)
|
|
505
524
|
if not worktree:
|
|
506
525
|
return {
|
|
@@ -508,13 +527,13 @@ def create_worktrees_registry(
|
|
|
508
527
|
"error": f"Worktree '{worktree_id}' not found",
|
|
509
528
|
}
|
|
510
529
|
|
|
511
|
-
if worktree.agent_session_id and worktree.agent_session_id !=
|
|
530
|
+
if worktree.agent_session_id and worktree.agent_session_id != resolved_session_id:
|
|
512
531
|
return {
|
|
513
532
|
"success": False,
|
|
514
533
|
"error": f"Worktree already claimed by session '{worktree.agent_session_id}'",
|
|
515
534
|
}
|
|
516
535
|
|
|
517
|
-
updated = worktree_storage.claim(worktree_id,
|
|
536
|
+
updated = worktree_storage.claim(worktree_id, resolved_session_id)
|
|
518
537
|
if not updated:
|
|
519
538
|
return {"error": "Failed to claim worktree"}
|
|
520
539
|
|
|
@@ -929,341 +948,4 @@ def create_worktrees_registry(
|
|
|
929
948
|
|
|
930
949
|
return {}
|
|
931
950
|
|
|
932
|
-
@registry.tool(
|
|
933
|
-
name="spawn_agent_in_worktree",
|
|
934
|
-
description="Create a worktree and spawn an agent in it.",
|
|
935
|
-
)
|
|
936
|
-
async def spawn_agent_in_worktree(
|
|
937
|
-
prompt: str,
|
|
938
|
-
branch_name: str,
|
|
939
|
-
base_branch: str = "main",
|
|
940
|
-
task_id: str | None = None,
|
|
941
|
-
parent_session_id: str | None = None,
|
|
942
|
-
mode: str = "terminal", # Note: in_process mode is not supported
|
|
943
|
-
terminal: str = "auto",
|
|
944
|
-
provider: Literal["claude", "gemini", "codex", "antigravity"] = "claude",
|
|
945
|
-
model: str | None = None,
|
|
946
|
-
workflow: str | None = None,
|
|
947
|
-
timeout: float = 120.0,
|
|
948
|
-
max_turns: int = 10,
|
|
949
|
-
project_path: str | None = None,
|
|
950
|
-
) -> dict[str, Any]:
|
|
951
|
-
"""
|
|
952
|
-
Create a worktree and spawn an agent to work in it.
|
|
953
|
-
|
|
954
|
-
This combines worktree creation with agent spawning for isolated development.
|
|
955
|
-
|
|
956
|
-
Args:
|
|
957
|
-
prompt: The task/prompt for the agent.
|
|
958
|
-
branch_name: Name for the new branch/worktree.
|
|
959
|
-
base_branch: Branch to base the worktree on (default: main).
|
|
960
|
-
task_id: Optional task ID to link to this worktree.
|
|
961
|
-
parent_session_id: Parent session ID for context.
|
|
962
|
-
mode: Execution mode (terminal, embedded, headless). Note: in_process is not supported.
|
|
963
|
-
terminal: Terminal for terminal/embedded modes (auto, ghostty, etc.).
|
|
964
|
-
provider: LLM provider (claude, gemini, etc.).
|
|
965
|
-
model: Optional model override.
|
|
966
|
-
workflow: Workflow name to execute.
|
|
967
|
-
timeout: Execution timeout in seconds (default: 120).
|
|
968
|
-
max_turns: Maximum turns (default: 10).
|
|
969
|
-
project_path: Path to project directory (pass cwd from CLI).
|
|
970
|
-
|
|
971
|
-
Returns:
|
|
972
|
-
Dict with worktree_id, run_id, and status.
|
|
973
|
-
"""
|
|
974
|
-
if agent_runner is None:
|
|
975
|
-
return {
|
|
976
|
-
"success": False,
|
|
977
|
-
"error": "Agent runner not configured. Cannot spawn agent.",
|
|
978
|
-
}
|
|
979
|
-
|
|
980
|
-
# Resolve project context
|
|
981
|
-
resolved_git_mgr, resolved_project_id, error = _resolve_project_context(
|
|
982
|
-
project_path, git_manager, project_id
|
|
983
|
-
)
|
|
984
|
-
if error:
|
|
985
|
-
return {"success": False, "error": error}
|
|
986
|
-
|
|
987
|
-
# Type narrowing: if no error, these are guaranteed non-None
|
|
988
|
-
if resolved_git_mgr is None or resolved_project_id is None:
|
|
989
|
-
raise RuntimeError("Git manager or project ID unexpectedly None")
|
|
990
|
-
|
|
991
|
-
if parent_session_id is None:
|
|
992
|
-
return {
|
|
993
|
-
"success": False,
|
|
994
|
-
"error": "parent_session_id is required for agent spawning.",
|
|
995
|
-
}
|
|
996
|
-
|
|
997
|
-
# Handle mode aliases and validation
|
|
998
|
-
# "interactive" is an alias for "terminal" mode
|
|
999
|
-
if mode == "interactive":
|
|
1000
|
-
mode = "terminal"
|
|
1001
|
-
|
|
1002
|
-
valid_modes = ["terminal", "embedded", "headless"]
|
|
1003
|
-
if mode not in valid_modes:
|
|
1004
|
-
return {
|
|
1005
|
-
"success": False,
|
|
1006
|
-
"error": (
|
|
1007
|
-
f"Invalid mode '{mode}'. Must be one of: {', '.join(valid_modes)} (or 'interactive' as alias for 'terminal'). "
|
|
1008
|
-
f"Note: 'in_process' mode is not supported for spawn_agent_in_worktree."
|
|
1009
|
-
),
|
|
1010
|
-
}
|
|
1011
|
-
|
|
1012
|
-
# Normalize terminal parameter to lowercase for enum compatibility
|
|
1013
|
-
# (TerminalType enum values are lowercase, e.g., "terminal.app" not "Terminal.app")
|
|
1014
|
-
if isinstance(terminal, str):
|
|
1015
|
-
terminal = terminal.lower()
|
|
1016
|
-
|
|
1017
|
-
# Default to 'worktree-agent' workflow if not specified
|
|
1018
|
-
# This workflow restricts tools available to spawned agents in worktrees
|
|
1019
|
-
if workflow is None:
|
|
1020
|
-
workflow = "worktree-agent"
|
|
1021
|
-
|
|
1022
|
-
# Validate workflow (reject lifecycle workflows)
|
|
1023
|
-
if workflow:
|
|
1024
|
-
workflow_loader = WorkflowLoader()
|
|
1025
|
-
is_valid, error_msg = workflow_loader.validate_workflow_for_agent(
|
|
1026
|
-
workflow, project_path=project_path
|
|
1027
|
-
)
|
|
1028
|
-
if not is_valid:
|
|
1029
|
-
return {
|
|
1030
|
-
"success": False,
|
|
1031
|
-
"error": error_msg,
|
|
1032
|
-
}
|
|
1033
|
-
|
|
1034
|
-
# Check if worktree already exists for this branch
|
|
1035
|
-
existing = worktree_storage.get_by_branch(resolved_project_id, branch_name)
|
|
1036
|
-
if existing:
|
|
1037
|
-
# Use existing worktree
|
|
1038
|
-
worktree = existing
|
|
1039
|
-
logger.info(f"Using existing worktree for branch '{branch_name}'")
|
|
1040
|
-
else:
|
|
1041
|
-
# Generate worktree path in temp directory
|
|
1042
|
-
project_name = Path(resolved_git_mgr.repo_path).name
|
|
1043
|
-
worktree_path = _generate_worktree_path(branch_name, project_name)
|
|
1044
|
-
|
|
1045
|
-
# Create git worktree
|
|
1046
|
-
result = resolved_git_mgr.create_worktree(
|
|
1047
|
-
worktree_path=worktree_path,
|
|
1048
|
-
branch_name=branch_name,
|
|
1049
|
-
base_branch=base_branch,
|
|
1050
|
-
create_branch=True,
|
|
1051
|
-
)
|
|
1052
|
-
|
|
1053
|
-
if not result.success:
|
|
1054
|
-
return {
|
|
1055
|
-
"success": False,
|
|
1056
|
-
"error": result.error or "Failed to create git worktree",
|
|
1057
|
-
}
|
|
1058
|
-
|
|
1059
|
-
# Record in database
|
|
1060
|
-
worktree = worktree_storage.create(
|
|
1061
|
-
project_id=resolved_project_id,
|
|
1062
|
-
branch_name=branch_name,
|
|
1063
|
-
worktree_path=worktree_path,
|
|
1064
|
-
base_branch=base_branch,
|
|
1065
|
-
task_id=task_id,
|
|
1066
|
-
)
|
|
1067
|
-
|
|
1068
|
-
# Copy project.json and install provider hooks
|
|
1069
|
-
_copy_project_json_to_worktree(resolved_git_mgr.repo_path, worktree.worktree_path)
|
|
1070
|
-
_install_provider_hooks(provider, worktree.worktree_path)
|
|
1071
|
-
|
|
1072
|
-
# Check spawn depth limit
|
|
1073
|
-
can_spawn, reason, _depth = agent_runner.can_spawn(parent_session_id)
|
|
1074
|
-
if not can_spawn:
|
|
1075
|
-
return {
|
|
1076
|
-
"success": False,
|
|
1077
|
-
"error": reason,
|
|
1078
|
-
"worktree_id": worktree.id,
|
|
1079
|
-
}
|
|
1080
|
-
|
|
1081
|
-
# Import AgentConfig and get machine_id
|
|
1082
|
-
from gobby.agents.runner import AgentConfig
|
|
1083
|
-
from gobby.utils.machine_id import get_machine_id
|
|
1084
|
-
|
|
1085
|
-
# Auto-detect machine_id if not provided
|
|
1086
|
-
machine_id = get_machine_id()
|
|
1087
|
-
|
|
1088
|
-
# Create agent config with worktree
|
|
1089
|
-
config = AgentConfig(
|
|
1090
|
-
prompt=prompt,
|
|
1091
|
-
parent_session_id=parent_session_id,
|
|
1092
|
-
project_id=resolved_project_id,
|
|
1093
|
-
machine_id=machine_id,
|
|
1094
|
-
source=provider,
|
|
1095
|
-
workflow=workflow,
|
|
1096
|
-
task=task_id,
|
|
1097
|
-
session_context="summary_markdown",
|
|
1098
|
-
mode=mode,
|
|
1099
|
-
terminal=terminal,
|
|
1100
|
-
worktree_id=worktree.id,
|
|
1101
|
-
provider=provider,
|
|
1102
|
-
model=model,
|
|
1103
|
-
max_turns=max_turns,
|
|
1104
|
-
timeout=timeout,
|
|
1105
|
-
project_path=worktree.worktree_path,
|
|
1106
|
-
)
|
|
1107
|
-
|
|
1108
|
-
# For terminal/embedded/headless modes, use prepare_run + spawner
|
|
1109
|
-
# (runner.run() is only for in_process mode)
|
|
1110
|
-
from gobby.llm.executor import AgentResult
|
|
1111
|
-
|
|
1112
|
-
prepare_result = agent_runner.prepare_run(config)
|
|
1113
|
-
if isinstance(prepare_result, AgentResult):
|
|
1114
|
-
# prepare_run returns AgentResult on error
|
|
1115
|
-
return {
|
|
1116
|
-
"success": False,
|
|
1117
|
-
"worktree_id": worktree.id,
|
|
1118
|
-
"worktree_path": worktree.worktree_path,
|
|
1119
|
-
"branch_name": worktree.branch_name,
|
|
1120
|
-
"error": prepare_result.error,
|
|
1121
|
-
}
|
|
1122
|
-
|
|
1123
|
-
# Successfully prepared - we have context with session and run
|
|
1124
|
-
context = prepare_result
|
|
1125
|
-
|
|
1126
|
-
if context.session is None or context.run is None:
|
|
1127
|
-
return {
|
|
1128
|
-
"success": False,
|
|
1129
|
-
"worktree_id": worktree.id,
|
|
1130
|
-
"error": "Internal error: context missing session or run after prepare_run",
|
|
1131
|
-
}
|
|
1132
|
-
|
|
1133
|
-
child_session = context.session
|
|
1134
|
-
agent_run = context.run
|
|
1135
|
-
|
|
1136
|
-
# Claim worktree for the child session
|
|
1137
|
-
worktree_storage.claim(worktree.id, child_session.id)
|
|
1138
|
-
|
|
1139
|
-
# Pre-save workflow state with session_task if task_id is provided
|
|
1140
|
-
# This ensures suggest_next_task() will scope to this task's subtasks
|
|
1141
|
-
if task_id and workflow:
|
|
1142
|
-
try:
|
|
1143
|
-
workflow_state_manager = WorkflowStateManager(worktree_storage.db)
|
|
1144
|
-
initial_state = WorkflowState(
|
|
1145
|
-
session_id=child_session.id,
|
|
1146
|
-
workflow_name=workflow,
|
|
1147
|
-
step="", # Will be set when workflow actually starts
|
|
1148
|
-
variables={"session_task": task_id},
|
|
1149
|
-
)
|
|
1150
|
-
workflow_state_manager.save_state(initial_state)
|
|
1151
|
-
logger.debug(
|
|
1152
|
-
f"Pre-saved workflow state for session {child_session.id} "
|
|
1153
|
-
f"with session_task={task_id}"
|
|
1154
|
-
)
|
|
1155
|
-
except Exception as e:
|
|
1156
|
-
logger.warning(f"Failed to pre-save workflow state: {e}")
|
|
1157
|
-
# Continue anyway - this is an optimization, not a requirement
|
|
1158
|
-
|
|
1159
|
-
# Build enhanced prompt with worktree context
|
|
1160
|
-
# This helps the agent understand it's in an isolated worktree, not the main repo
|
|
1161
|
-
enhanced_prompt = _build_worktree_context_prompt(
|
|
1162
|
-
original_prompt=prompt,
|
|
1163
|
-
worktree_path=worktree.worktree_path,
|
|
1164
|
-
branch_name=worktree.branch_name,
|
|
1165
|
-
task_id=task_id,
|
|
1166
|
-
main_repo_path=str(resolved_git_mgr.repo_path),
|
|
1167
|
-
)
|
|
1168
|
-
|
|
1169
|
-
# Spawn in terminal using TerminalSpawner
|
|
1170
|
-
if mode == "terminal":
|
|
1171
|
-
from gobby.agents.spawn import TerminalSpawner
|
|
1172
|
-
|
|
1173
|
-
terminal_spawner = TerminalSpawner()
|
|
1174
|
-
terminal_result = terminal_spawner.spawn_agent(
|
|
1175
|
-
cli=provider, # claude, gemini, codex
|
|
1176
|
-
cwd=worktree.worktree_path,
|
|
1177
|
-
session_id=child_session.id,
|
|
1178
|
-
parent_session_id=parent_session_id,
|
|
1179
|
-
agent_run_id=agent_run.id,
|
|
1180
|
-
project_id=resolved_project_id,
|
|
1181
|
-
workflow_name=workflow,
|
|
1182
|
-
agent_depth=child_session.agent_depth,
|
|
1183
|
-
max_agent_depth=agent_runner._child_session_manager.max_agent_depth,
|
|
1184
|
-
terminal=terminal,
|
|
1185
|
-
prompt=enhanced_prompt,
|
|
1186
|
-
)
|
|
1187
|
-
|
|
1188
|
-
if not terminal_result.success:
|
|
1189
|
-
return {
|
|
1190
|
-
"success": False,
|
|
1191
|
-
"worktree_id": worktree.id,
|
|
1192
|
-
"worktree_path": worktree.worktree_path,
|
|
1193
|
-
"branch_name": worktree.branch_name,
|
|
1194
|
-
"run_id": agent_run.id,
|
|
1195
|
-
"child_session_id": child_session.id,
|
|
1196
|
-
"error": terminal_result.error or terminal_result.message,
|
|
1197
|
-
}
|
|
1198
|
-
|
|
1199
|
-
return {
|
|
1200
|
-
"success": True,
|
|
1201
|
-
"worktree_id": worktree.id,
|
|
1202
|
-
"worktree_path": worktree.worktree_path,
|
|
1203
|
-
"branch_name": worktree.branch_name,
|
|
1204
|
-
"run_id": agent_run.id,
|
|
1205
|
-
"child_session_id": child_session.id,
|
|
1206
|
-
"status": "pending",
|
|
1207
|
-
"message": f"Agent spawned in {terminal_result.terminal_type} (PID: {terminal_result.pid})",
|
|
1208
|
-
"terminal_type": terminal_result.terminal_type,
|
|
1209
|
-
"pid": terminal_result.pid,
|
|
1210
|
-
}
|
|
1211
|
-
|
|
1212
|
-
elif mode == "embedded":
|
|
1213
|
-
from gobby.agents.spawn import EmbeddedSpawner
|
|
1214
|
-
|
|
1215
|
-
embedded_spawner = EmbeddedSpawner()
|
|
1216
|
-
embedded_result = embedded_spawner.spawn_agent(
|
|
1217
|
-
cli=provider,
|
|
1218
|
-
cwd=worktree.worktree_path,
|
|
1219
|
-
session_id=child_session.id,
|
|
1220
|
-
parent_session_id=parent_session_id,
|
|
1221
|
-
agent_run_id=agent_run.id,
|
|
1222
|
-
project_id=resolved_project_id,
|
|
1223
|
-
workflow_name=workflow,
|
|
1224
|
-
agent_depth=child_session.agent_depth,
|
|
1225
|
-
max_agent_depth=agent_runner._child_session_manager.max_agent_depth,
|
|
1226
|
-
prompt=enhanced_prompt,
|
|
1227
|
-
)
|
|
1228
|
-
|
|
1229
|
-
return {
|
|
1230
|
-
"success": embedded_result.success,
|
|
1231
|
-
"worktree_id": worktree.id,
|
|
1232
|
-
"worktree_path": worktree.worktree_path,
|
|
1233
|
-
"branch_name": worktree.branch_name,
|
|
1234
|
-
"run_id": agent_run.id,
|
|
1235
|
-
"child_session_id": child_session.id,
|
|
1236
|
-
"status": "pending" if embedded_result.success else "error",
|
|
1237
|
-
"error": embedded_result.error if not embedded_result.success else None,
|
|
1238
|
-
}
|
|
1239
|
-
|
|
1240
|
-
else: # headless
|
|
1241
|
-
from gobby.agents.spawn import HeadlessSpawner
|
|
1242
|
-
|
|
1243
|
-
headless_spawner = HeadlessSpawner()
|
|
1244
|
-
headless_result = headless_spawner.spawn_agent(
|
|
1245
|
-
cli=provider,
|
|
1246
|
-
cwd=worktree.worktree_path,
|
|
1247
|
-
session_id=child_session.id,
|
|
1248
|
-
parent_session_id=parent_session_id,
|
|
1249
|
-
agent_run_id=agent_run.id,
|
|
1250
|
-
project_id=resolved_project_id,
|
|
1251
|
-
workflow_name=workflow,
|
|
1252
|
-
agent_depth=child_session.agent_depth,
|
|
1253
|
-
max_agent_depth=agent_runner._child_session_manager.max_agent_depth,
|
|
1254
|
-
prompt=enhanced_prompt,
|
|
1255
|
-
)
|
|
1256
|
-
|
|
1257
|
-
return {
|
|
1258
|
-
"success": headless_result.success,
|
|
1259
|
-
"worktree_id": worktree.id,
|
|
1260
|
-
"worktree_path": worktree.worktree_path,
|
|
1261
|
-
"branch_name": worktree.branch_name,
|
|
1262
|
-
"run_id": agent_run.id,
|
|
1263
|
-
"child_session_id": child_session.id,
|
|
1264
|
-
"status": "pending" if headless_result.success else "error",
|
|
1265
|
-
"pid": headless_result.pid if headless_result.success else None,
|
|
1266
|
-
"error": headless_result.error if not headless_result.success else None,
|
|
1267
|
-
}
|
|
1268
|
-
|
|
1269
951
|
return registry
|
gobby/memory/extractor.py
CHANGED
|
@@ -153,10 +153,15 @@ class SessionMemoryExtractor:
|
|
|
153
153
|
"""
|
|
154
154
|
session = self.session_manager.get(session_id)
|
|
155
155
|
if not session:
|
|
156
|
+
logger.warning(f"Session not found for memory extraction: {session_id}")
|
|
156
157
|
return None
|
|
157
158
|
|
|
158
|
-
# Get project info
|
|
159
|
+
# Get project info - log for debugging NULL project_id issues
|
|
159
160
|
project_id = session.project_id
|
|
161
|
+
logger.debug(
|
|
162
|
+
f"Memory extraction context: session={session_id}, "
|
|
163
|
+
f"project_id={project_id!r} (type={type(project_id).__name__})"
|
|
164
|
+
)
|
|
160
165
|
project_name = "Unknown Project"
|
|
161
166
|
|
|
162
167
|
if project_id:
|
|
@@ -461,6 +466,15 @@ class SessionMemoryExtractor:
|
|
|
461
466
|
session_id: Source session ID
|
|
462
467
|
project_id: Project ID for the memories
|
|
463
468
|
"""
|
|
469
|
+
# Log project_id for debugging NULL project_id issues
|
|
470
|
+
if project_id is None:
|
|
471
|
+
logger.warning(
|
|
472
|
+
f"Storing memories with NULL project_id for session {session_id}. "
|
|
473
|
+
"This may cause duplicate detection issues."
|
|
474
|
+
)
|
|
475
|
+
else:
|
|
476
|
+
logger.debug(f"Storing {len(candidates)} memories with project_id={project_id}")
|
|
477
|
+
|
|
464
478
|
for candidate in candidates:
|
|
465
479
|
try:
|
|
466
480
|
await self.memory_manager.remember(
|