gobby 0.2.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- gobby/__init__.py +3 -0
- gobby/adapters/__init__.py +30 -0
- gobby/adapters/base.py +93 -0
- gobby/adapters/claude_code.py +276 -0
- gobby/adapters/codex.py +1292 -0
- gobby/adapters/gemini.py +343 -0
- gobby/agents/__init__.py +37 -0
- gobby/agents/codex_session.py +120 -0
- gobby/agents/constants.py +112 -0
- gobby/agents/context.py +362 -0
- gobby/agents/definitions.py +133 -0
- gobby/agents/gemini_session.py +111 -0
- gobby/agents/registry.py +618 -0
- gobby/agents/runner.py +968 -0
- gobby/agents/session.py +259 -0
- gobby/agents/spawn.py +916 -0
- gobby/agents/spawners/__init__.py +77 -0
- gobby/agents/spawners/base.py +142 -0
- gobby/agents/spawners/cross_platform.py +266 -0
- gobby/agents/spawners/embedded.py +225 -0
- gobby/agents/spawners/headless.py +226 -0
- gobby/agents/spawners/linux.py +125 -0
- gobby/agents/spawners/macos.py +277 -0
- gobby/agents/spawners/windows.py +308 -0
- gobby/agents/tty_config.py +319 -0
- gobby/autonomous/__init__.py +32 -0
- gobby/autonomous/progress_tracker.py +447 -0
- gobby/autonomous/stop_registry.py +269 -0
- gobby/autonomous/stuck_detector.py +383 -0
- gobby/cli/__init__.py +67 -0
- gobby/cli/__main__.py +8 -0
- gobby/cli/agents.py +529 -0
- gobby/cli/artifacts.py +266 -0
- gobby/cli/daemon.py +329 -0
- gobby/cli/extensions.py +526 -0
- gobby/cli/github.py +263 -0
- gobby/cli/init.py +53 -0
- gobby/cli/install.py +614 -0
- gobby/cli/installers/__init__.py +37 -0
- gobby/cli/installers/antigravity.py +65 -0
- gobby/cli/installers/claude.py +363 -0
- gobby/cli/installers/codex.py +192 -0
- gobby/cli/installers/gemini.py +294 -0
- gobby/cli/installers/git_hooks.py +377 -0
- gobby/cli/installers/shared.py +737 -0
- gobby/cli/linear.py +250 -0
- gobby/cli/mcp.py +30 -0
- gobby/cli/mcp_proxy.py +698 -0
- gobby/cli/memory.py +304 -0
- gobby/cli/merge.py +384 -0
- gobby/cli/projects.py +79 -0
- gobby/cli/sessions.py +622 -0
- gobby/cli/tasks/__init__.py +30 -0
- gobby/cli/tasks/_utils.py +658 -0
- gobby/cli/tasks/ai.py +1025 -0
- gobby/cli/tasks/commits.py +169 -0
- gobby/cli/tasks/crud.py +685 -0
- gobby/cli/tasks/deps.py +135 -0
- gobby/cli/tasks/labels.py +63 -0
- gobby/cli/tasks/main.py +273 -0
- gobby/cli/tasks/search.py +178 -0
- gobby/cli/tui.py +34 -0
- gobby/cli/utils.py +513 -0
- gobby/cli/workflows.py +927 -0
- gobby/cli/worktrees.py +481 -0
- gobby/config/__init__.py +129 -0
- gobby/config/app.py +551 -0
- gobby/config/extensions.py +167 -0
- gobby/config/features.py +472 -0
- gobby/config/llm_providers.py +98 -0
- gobby/config/logging.py +66 -0
- gobby/config/mcp.py +346 -0
- gobby/config/persistence.py +247 -0
- gobby/config/servers.py +141 -0
- gobby/config/sessions.py +250 -0
- gobby/config/tasks.py +784 -0
- gobby/hooks/__init__.py +104 -0
- gobby/hooks/artifact_capture.py +213 -0
- gobby/hooks/broadcaster.py +243 -0
- gobby/hooks/event_handlers.py +723 -0
- gobby/hooks/events.py +218 -0
- gobby/hooks/git.py +169 -0
- gobby/hooks/health_monitor.py +171 -0
- gobby/hooks/hook_manager.py +856 -0
- gobby/hooks/hook_types.py +575 -0
- gobby/hooks/plugins.py +813 -0
- gobby/hooks/session_coordinator.py +396 -0
- gobby/hooks/verification_runner.py +268 -0
- gobby/hooks/webhooks.py +339 -0
- gobby/install/claude/commands/gobby/bug.md +51 -0
- gobby/install/claude/commands/gobby/chore.md +51 -0
- gobby/install/claude/commands/gobby/epic.md +52 -0
- gobby/install/claude/commands/gobby/eval.md +235 -0
- gobby/install/claude/commands/gobby/feat.md +49 -0
- gobby/install/claude/commands/gobby/nit.md +52 -0
- gobby/install/claude/commands/gobby/ref.md +52 -0
- gobby/install/claude/hooks/HOOK_SCHEMAS.md +632 -0
- gobby/install/claude/hooks/hook_dispatcher.py +364 -0
- gobby/install/claude/hooks/validate_settings.py +102 -0
- gobby/install/claude/hooks-template.json +118 -0
- gobby/install/codex/hooks/hook_dispatcher.py +153 -0
- gobby/install/codex/prompts/forget.md +7 -0
- gobby/install/codex/prompts/memories.md +7 -0
- gobby/install/codex/prompts/recall.md +7 -0
- gobby/install/codex/prompts/remember.md +13 -0
- gobby/install/gemini/hooks/hook_dispatcher.py +268 -0
- gobby/install/gemini/hooks-template.json +138 -0
- gobby/install/shared/plugins/code_guardian.py +456 -0
- gobby/install/shared/plugins/example_notify.py +331 -0
- gobby/integrations/__init__.py +10 -0
- gobby/integrations/github.py +145 -0
- gobby/integrations/linear.py +145 -0
- gobby/llm/__init__.py +40 -0
- gobby/llm/base.py +120 -0
- gobby/llm/claude.py +578 -0
- gobby/llm/claude_executor.py +503 -0
- gobby/llm/codex.py +322 -0
- gobby/llm/codex_executor.py +513 -0
- gobby/llm/executor.py +316 -0
- gobby/llm/factory.py +34 -0
- gobby/llm/gemini.py +258 -0
- gobby/llm/gemini_executor.py +339 -0
- gobby/llm/litellm.py +287 -0
- gobby/llm/litellm_executor.py +303 -0
- gobby/llm/resolver.py +499 -0
- gobby/llm/service.py +236 -0
- gobby/mcp_proxy/__init__.py +29 -0
- gobby/mcp_proxy/actions.py +175 -0
- gobby/mcp_proxy/daemon_control.py +198 -0
- gobby/mcp_proxy/importer.py +436 -0
- gobby/mcp_proxy/lazy.py +325 -0
- gobby/mcp_proxy/manager.py +798 -0
- gobby/mcp_proxy/metrics.py +609 -0
- gobby/mcp_proxy/models.py +139 -0
- gobby/mcp_proxy/registries.py +215 -0
- gobby/mcp_proxy/schema_hash.py +381 -0
- gobby/mcp_proxy/semantic_search.py +706 -0
- gobby/mcp_proxy/server.py +549 -0
- gobby/mcp_proxy/services/__init__.py +0 -0
- gobby/mcp_proxy/services/fallback.py +306 -0
- gobby/mcp_proxy/services/recommendation.py +224 -0
- gobby/mcp_proxy/services/server_mgmt.py +214 -0
- gobby/mcp_proxy/services/system.py +72 -0
- gobby/mcp_proxy/services/tool_filter.py +231 -0
- gobby/mcp_proxy/services/tool_proxy.py +309 -0
- gobby/mcp_proxy/stdio.py +565 -0
- gobby/mcp_proxy/tools/__init__.py +27 -0
- gobby/mcp_proxy/tools/agents.py +1103 -0
- gobby/mcp_proxy/tools/artifacts.py +207 -0
- gobby/mcp_proxy/tools/hub.py +335 -0
- gobby/mcp_proxy/tools/internal.py +337 -0
- gobby/mcp_proxy/tools/memory.py +543 -0
- gobby/mcp_proxy/tools/merge.py +422 -0
- gobby/mcp_proxy/tools/metrics.py +283 -0
- gobby/mcp_proxy/tools/orchestration/__init__.py +23 -0
- gobby/mcp_proxy/tools/orchestration/cleanup.py +619 -0
- gobby/mcp_proxy/tools/orchestration/monitor.py +380 -0
- gobby/mcp_proxy/tools/orchestration/orchestrate.py +746 -0
- gobby/mcp_proxy/tools/orchestration/review.py +736 -0
- gobby/mcp_proxy/tools/orchestration/utils.py +16 -0
- gobby/mcp_proxy/tools/session_messages.py +1056 -0
- gobby/mcp_proxy/tools/task_dependencies.py +219 -0
- gobby/mcp_proxy/tools/task_expansion.py +591 -0
- gobby/mcp_proxy/tools/task_github.py +393 -0
- gobby/mcp_proxy/tools/task_linear.py +379 -0
- gobby/mcp_proxy/tools/task_orchestration.py +77 -0
- gobby/mcp_proxy/tools/task_readiness.py +522 -0
- gobby/mcp_proxy/tools/task_sync.py +351 -0
- gobby/mcp_proxy/tools/task_validation.py +843 -0
- gobby/mcp_proxy/tools/tasks/__init__.py +25 -0
- gobby/mcp_proxy/tools/tasks/_context.py +112 -0
- gobby/mcp_proxy/tools/tasks/_crud.py +516 -0
- gobby/mcp_proxy/tools/tasks/_factory.py +176 -0
- gobby/mcp_proxy/tools/tasks/_helpers.py +129 -0
- gobby/mcp_proxy/tools/tasks/_lifecycle.py +517 -0
- gobby/mcp_proxy/tools/tasks/_lifecycle_validation.py +301 -0
- gobby/mcp_proxy/tools/tasks/_resolution.py +55 -0
- gobby/mcp_proxy/tools/tasks/_search.py +215 -0
- gobby/mcp_proxy/tools/tasks/_session.py +125 -0
- gobby/mcp_proxy/tools/workflows.py +973 -0
- gobby/mcp_proxy/tools/worktrees.py +1264 -0
- gobby/mcp_proxy/transports/__init__.py +0 -0
- gobby/mcp_proxy/transports/base.py +95 -0
- gobby/mcp_proxy/transports/factory.py +44 -0
- gobby/mcp_proxy/transports/http.py +139 -0
- gobby/mcp_proxy/transports/stdio.py +213 -0
- gobby/mcp_proxy/transports/websocket.py +136 -0
- gobby/memory/backends/__init__.py +116 -0
- gobby/memory/backends/mem0.py +408 -0
- gobby/memory/backends/memu.py +485 -0
- gobby/memory/backends/null.py +111 -0
- gobby/memory/backends/openmemory.py +537 -0
- gobby/memory/backends/sqlite.py +304 -0
- gobby/memory/context.py +87 -0
- gobby/memory/manager.py +1001 -0
- gobby/memory/protocol.py +451 -0
- gobby/memory/search/__init__.py +66 -0
- gobby/memory/search/text.py +127 -0
- gobby/memory/viz.py +258 -0
- gobby/prompts/__init__.py +13 -0
- gobby/prompts/defaults/expansion/system.md +119 -0
- gobby/prompts/defaults/expansion/user.md +48 -0
- gobby/prompts/defaults/external_validation/agent.md +72 -0
- gobby/prompts/defaults/external_validation/external.md +63 -0
- gobby/prompts/defaults/external_validation/spawn.md +83 -0
- gobby/prompts/defaults/external_validation/system.md +6 -0
- gobby/prompts/defaults/features/import_mcp.md +22 -0
- gobby/prompts/defaults/features/import_mcp_github.md +17 -0
- gobby/prompts/defaults/features/import_mcp_search.md +16 -0
- gobby/prompts/defaults/features/recommend_tools.md +32 -0
- gobby/prompts/defaults/features/recommend_tools_hybrid.md +35 -0
- gobby/prompts/defaults/features/recommend_tools_llm.md +30 -0
- gobby/prompts/defaults/features/server_description.md +20 -0
- gobby/prompts/defaults/features/server_description_system.md +6 -0
- gobby/prompts/defaults/features/task_description.md +31 -0
- gobby/prompts/defaults/features/task_description_system.md +6 -0
- gobby/prompts/defaults/features/tool_summary.md +17 -0
- gobby/prompts/defaults/features/tool_summary_system.md +6 -0
- gobby/prompts/defaults/research/step.md +58 -0
- gobby/prompts/defaults/validation/criteria.md +47 -0
- gobby/prompts/defaults/validation/validate.md +38 -0
- gobby/prompts/loader.py +346 -0
- gobby/prompts/models.py +113 -0
- gobby/py.typed +0 -0
- gobby/runner.py +488 -0
- gobby/search/__init__.py +23 -0
- gobby/search/protocol.py +104 -0
- gobby/search/tfidf.py +232 -0
- gobby/servers/__init__.py +7 -0
- gobby/servers/http.py +636 -0
- gobby/servers/models.py +31 -0
- gobby/servers/routes/__init__.py +23 -0
- gobby/servers/routes/admin.py +416 -0
- gobby/servers/routes/dependencies.py +118 -0
- gobby/servers/routes/mcp/__init__.py +24 -0
- gobby/servers/routes/mcp/hooks.py +135 -0
- gobby/servers/routes/mcp/plugins.py +121 -0
- gobby/servers/routes/mcp/tools.py +1337 -0
- gobby/servers/routes/mcp/webhooks.py +159 -0
- gobby/servers/routes/sessions.py +582 -0
- gobby/servers/websocket.py +766 -0
- gobby/sessions/__init__.py +13 -0
- gobby/sessions/analyzer.py +322 -0
- gobby/sessions/lifecycle.py +240 -0
- gobby/sessions/manager.py +563 -0
- gobby/sessions/processor.py +225 -0
- gobby/sessions/summary.py +532 -0
- gobby/sessions/transcripts/__init__.py +41 -0
- gobby/sessions/transcripts/base.py +125 -0
- gobby/sessions/transcripts/claude.py +386 -0
- gobby/sessions/transcripts/codex.py +143 -0
- gobby/sessions/transcripts/gemini.py +195 -0
- gobby/storage/__init__.py +21 -0
- gobby/storage/agents.py +409 -0
- gobby/storage/artifact_classifier.py +341 -0
- gobby/storage/artifacts.py +285 -0
- gobby/storage/compaction.py +67 -0
- gobby/storage/database.py +357 -0
- gobby/storage/inter_session_messages.py +194 -0
- gobby/storage/mcp.py +680 -0
- gobby/storage/memories.py +562 -0
- gobby/storage/merge_resolutions.py +550 -0
- gobby/storage/migrations.py +860 -0
- gobby/storage/migrations_legacy.py +1359 -0
- gobby/storage/projects.py +166 -0
- gobby/storage/session_messages.py +251 -0
- gobby/storage/session_tasks.py +97 -0
- gobby/storage/sessions.py +817 -0
- gobby/storage/task_dependencies.py +223 -0
- gobby/storage/tasks/__init__.py +42 -0
- gobby/storage/tasks/_aggregates.py +180 -0
- gobby/storage/tasks/_crud.py +449 -0
- gobby/storage/tasks/_id.py +104 -0
- gobby/storage/tasks/_lifecycle.py +311 -0
- gobby/storage/tasks/_manager.py +889 -0
- gobby/storage/tasks/_models.py +300 -0
- gobby/storage/tasks/_ordering.py +119 -0
- gobby/storage/tasks/_path_cache.py +110 -0
- gobby/storage/tasks/_queries.py +343 -0
- gobby/storage/tasks/_search.py +143 -0
- gobby/storage/workflow_audit.py +393 -0
- gobby/storage/worktrees.py +547 -0
- gobby/sync/__init__.py +29 -0
- gobby/sync/github.py +333 -0
- gobby/sync/linear.py +304 -0
- gobby/sync/memories.py +284 -0
- gobby/sync/tasks.py +641 -0
- gobby/tasks/__init__.py +8 -0
- gobby/tasks/build_verification.py +193 -0
- gobby/tasks/commits.py +633 -0
- gobby/tasks/context.py +747 -0
- gobby/tasks/criteria.py +342 -0
- gobby/tasks/enhanced_validator.py +226 -0
- gobby/tasks/escalation.py +263 -0
- gobby/tasks/expansion.py +626 -0
- gobby/tasks/external_validator.py +764 -0
- gobby/tasks/issue_extraction.py +171 -0
- gobby/tasks/prompts/expand.py +327 -0
- gobby/tasks/research.py +421 -0
- gobby/tasks/tdd.py +352 -0
- gobby/tasks/tree_builder.py +263 -0
- gobby/tasks/validation.py +712 -0
- gobby/tasks/validation_history.py +357 -0
- gobby/tasks/validation_models.py +89 -0
- gobby/tools/__init__.py +0 -0
- gobby/tools/summarizer.py +170 -0
- gobby/tui/__init__.py +5 -0
- gobby/tui/api_client.py +281 -0
- gobby/tui/app.py +327 -0
- gobby/tui/screens/__init__.py +25 -0
- gobby/tui/screens/agents.py +333 -0
- gobby/tui/screens/chat.py +450 -0
- gobby/tui/screens/dashboard.py +377 -0
- gobby/tui/screens/memory.py +305 -0
- gobby/tui/screens/metrics.py +231 -0
- gobby/tui/screens/orchestrator.py +904 -0
- gobby/tui/screens/sessions.py +412 -0
- gobby/tui/screens/tasks.py +442 -0
- gobby/tui/screens/workflows.py +289 -0
- gobby/tui/screens/worktrees.py +174 -0
- gobby/tui/widgets/__init__.py +21 -0
- gobby/tui/widgets/chat.py +210 -0
- gobby/tui/widgets/conductor.py +104 -0
- gobby/tui/widgets/menu.py +132 -0
- gobby/tui/widgets/message_panel.py +160 -0
- gobby/tui/widgets/review_gate.py +224 -0
- gobby/tui/widgets/task_tree.py +99 -0
- gobby/tui/widgets/token_budget.py +166 -0
- gobby/tui/ws_client.py +258 -0
- gobby/utils/__init__.py +3 -0
- gobby/utils/daemon_client.py +235 -0
- gobby/utils/git.py +222 -0
- gobby/utils/id.py +38 -0
- gobby/utils/json_helpers.py +161 -0
- gobby/utils/logging.py +376 -0
- gobby/utils/machine_id.py +135 -0
- gobby/utils/metrics.py +589 -0
- gobby/utils/project_context.py +182 -0
- gobby/utils/project_init.py +263 -0
- gobby/utils/status.py +256 -0
- gobby/utils/validation.py +80 -0
- gobby/utils/version.py +23 -0
- gobby/workflows/__init__.py +4 -0
- gobby/workflows/actions.py +1310 -0
- gobby/workflows/approval_flow.py +138 -0
- gobby/workflows/artifact_actions.py +103 -0
- gobby/workflows/audit_helpers.py +110 -0
- gobby/workflows/autonomous_actions.py +286 -0
- gobby/workflows/context_actions.py +394 -0
- gobby/workflows/definitions.py +130 -0
- gobby/workflows/detection_helpers.py +208 -0
- gobby/workflows/engine.py +485 -0
- gobby/workflows/evaluator.py +669 -0
- gobby/workflows/git_utils.py +96 -0
- gobby/workflows/hooks.py +169 -0
- gobby/workflows/lifecycle_evaluator.py +613 -0
- gobby/workflows/llm_actions.py +70 -0
- gobby/workflows/loader.py +333 -0
- gobby/workflows/mcp_actions.py +60 -0
- gobby/workflows/memory_actions.py +272 -0
- gobby/workflows/premature_stop.py +164 -0
- gobby/workflows/session_actions.py +139 -0
- gobby/workflows/state_actions.py +123 -0
- gobby/workflows/state_manager.py +104 -0
- gobby/workflows/stop_signal_actions.py +163 -0
- gobby/workflows/summary_actions.py +344 -0
- gobby/workflows/task_actions.py +249 -0
- gobby/workflows/task_enforcement_actions.py +901 -0
- gobby/workflows/templates.py +52 -0
- gobby/workflows/todo_actions.py +84 -0
- gobby/workflows/webhook.py +223 -0
- gobby/workflows/webhook_executor.py +399 -0
- gobby/worktrees/__init__.py +5 -0
- gobby/worktrees/git.py +690 -0
- gobby/worktrees/merge/__init__.py +20 -0
- gobby/worktrees/merge/conflict_parser.py +177 -0
- gobby/worktrees/merge/resolver.py +485 -0
- gobby-0.2.5.dist-info/METADATA +351 -0
- gobby-0.2.5.dist-info/RECORD +383 -0
- gobby-0.2.5.dist-info/WHEEL +5 -0
- gobby-0.2.5.dist-info/entry_points.txt +2 -0
- gobby-0.2.5.dist-info/licenses/LICENSE.md +193 -0
- gobby-0.2.5.dist-info/top_level.txt +1 -0
gobby/agents/session.py
ADDED
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Child session management for subagent spawning.
|
|
3
|
+
|
|
4
|
+
This module provides utilities for creating and managing child sessions
|
|
5
|
+
that are spawned by agents. Child sessions are linked to their parent
|
|
6
|
+
sessions and track agent depth for safety limits.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import logging
|
|
12
|
+
import uuid
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
from typing import TYPE_CHECKING, Any
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from gobby.storage.sessions import LocalSessionManager, Session
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class ChildSessionConfig:
|
|
24
|
+
"""Configuration for creating a child session."""
|
|
25
|
+
|
|
26
|
+
parent_session_id: str
|
|
27
|
+
"""ID of the parent session spawning this child."""
|
|
28
|
+
|
|
29
|
+
project_id: str
|
|
30
|
+
"""Project ID the session belongs to."""
|
|
31
|
+
|
|
32
|
+
machine_id: str
|
|
33
|
+
"""Machine identifier."""
|
|
34
|
+
|
|
35
|
+
source: str
|
|
36
|
+
"""CLI source (e.g., 'claude', 'gemini', 'codex')."""
|
|
37
|
+
|
|
38
|
+
agent_id: str | None = None
|
|
39
|
+
"""ID of the agent that spawned this session."""
|
|
40
|
+
|
|
41
|
+
workflow_name: str | None = None
|
|
42
|
+
"""Name of the workflow being executed."""
|
|
43
|
+
|
|
44
|
+
title: str | None = None
|
|
45
|
+
"""Optional session title."""
|
|
46
|
+
|
|
47
|
+
git_branch: str | None = None
|
|
48
|
+
"""Git branch for the session."""
|
|
49
|
+
|
|
50
|
+
external_id: str | None = None
|
|
51
|
+
"""External session ID (e.g., Gemini's session_id from preflight capture)."""
|
|
52
|
+
|
|
53
|
+
lifecycle_variables: dict[str, Any] | None = None
|
|
54
|
+
"""Lifecycle variables for the session."""
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class ChildSessionManager:
|
|
58
|
+
"""
|
|
59
|
+
Manages child session creation and lifecycle.
|
|
60
|
+
|
|
61
|
+
Child sessions are spawned by agents running in parent sessions.
|
|
62
|
+
They track:
|
|
63
|
+
- Parent-child relationships via parent_session_id
|
|
64
|
+
- Agent depth (0 = human-initiated, 1+ = agent-spawned)
|
|
65
|
+
- Which agent spawned them (for tracking and cleanup)
|
|
66
|
+
|
|
67
|
+
Thread-safe: Uses the underlying LocalSessionManager's thread safety.
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
def __init__(
|
|
71
|
+
self,
|
|
72
|
+
session_storage: LocalSessionManager,
|
|
73
|
+
max_agent_depth: int = 1,
|
|
74
|
+
) -> None:
|
|
75
|
+
"""
|
|
76
|
+
Initialize ChildSessionManager.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
session_storage: LocalSessionManager for SQLite operations.
|
|
80
|
+
max_agent_depth: Maximum allowed nesting depth (default: 1).
|
|
81
|
+
Depth 0 = human-initiated session.
|
|
82
|
+
Depth 1 = agent can spawn, but child cannot spawn further.
|
|
83
|
+
"""
|
|
84
|
+
self._storage = session_storage
|
|
85
|
+
self.max_agent_depth = max_agent_depth
|
|
86
|
+
self.logger = logger
|
|
87
|
+
|
|
88
|
+
def get_session_depth(self, session_id: str) -> int:
|
|
89
|
+
"""
|
|
90
|
+
Get the agent depth of a session.
|
|
91
|
+
|
|
92
|
+
Depth is determined by counting parent links:
|
|
93
|
+
- 0: No parent (human-initiated)
|
|
94
|
+
- 1: Has parent that is depth 0
|
|
95
|
+
- N: Has parent that is depth N-1
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
session_id: The session ID to check.
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
Agent depth (0 for human sessions, 1+ for agent sessions).
|
|
102
|
+
"""
|
|
103
|
+
depth = 0
|
|
104
|
+
current_id = session_id
|
|
105
|
+
|
|
106
|
+
while current_id:
|
|
107
|
+
session = self._storage.get(current_id)
|
|
108
|
+
if not session or not session.parent_session_id:
|
|
109
|
+
break
|
|
110
|
+
depth += 1
|
|
111
|
+
current_id = session.parent_session_id
|
|
112
|
+
|
|
113
|
+
# Safety limit to prevent infinite loops
|
|
114
|
+
if depth > 10:
|
|
115
|
+
self.logger.warning(f"Session depth exceeded safety limit for {session_id}")
|
|
116
|
+
break
|
|
117
|
+
|
|
118
|
+
return depth
|
|
119
|
+
|
|
120
|
+
def can_spawn_child(self, parent_session_id: str) -> tuple[bool, str, int]:
|
|
121
|
+
"""
|
|
122
|
+
Check if a session can spawn a child agent.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
parent_session_id: The session attempting to spawn.
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
Tuple of (can_spawn, reason, parent_depth).
|
|
129
|
+
The parent_depth is returned to avoid redundant depth lookups.
|
|
130
|
+
"""
|
|
131
|
+
parent = self._storage.get(parent_session_id)
|
|
132
|
+
if not parent:
|
|
133
|
+
return False, f"Parent session {parent_session_id} not found", 0
|
|
134
|
+
|
|
135
|
+
current_depth = self.get_session_depth(parent_session_id)
|
|
136
|
+
if current_depth >= self.max_agent_depth:
|
|
137
|
+
return (
|
|
138
|
+
False,
|
|
139
|
+
(
|
|
140
|
+
f"Max agent depth ({self.max_agent_depth}) exceeded. "
|
|
141
|
+
f"Current depth: {current_depth}"
|
|
142
|
+
),
|
|
143
|
+
current_depth,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
return True, "OK", current_depth
|
|
147
|
+
|
|
148
|
+
def create_child_session(
|
|
149
|
+
self,
|
|
150
|
+
config: ChildSessionConfig,
|
|
151
|
+
) -> Session:
|
|
152
|
+
"""
|
|
153
|
+
Create a child session linked to a parent.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
config: Configuration for the child session.
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
The created child Session.
|
|
160
|
+
|
|
161
|
+
Raises:
|
|
162
|
+
ValueError: If max_agent_depth would be exceeded.
|
|
163
|
+
"""
|
|
164
|
+
# Check depth limit (also returns parent_depth to avoid redundant lookup)
|
|
165
|
+
can_spawn, reason, parent_depth = self.can_spawn_child(config.parent_session_id)
|
|
166
|
+
if not can_spawn:
|
|
167
|
+
raise ValueError(reason)
|
|
168
|
+
|
|
169
|
+
# Calculate child's agent depth (parent depth + 1)
|
|
170
|
+
child_depth = parent_depth + 1
|
|
171
|
+
|
|
172
|
+
# Use provided external_id (e.g., from Gemini preflight) or generate placeholder
|
|
173
|
+
if config.external_id:
|
|
174
|
+
external_id = config.external_id
|
|
175
|
+
use_provided_external_id = True
|
|
176
|
+
else:
|
|
177
|
+
external_id = f"agent-{uuid.uuid4().hex[:12]}"
|
|
178
|
+
use_provided_external_id = False
|
|
179
|
+
|
|
180
|
+
# Create title if not provided
|
|
181
|
+
title = config.title
|
|
182
|
+
if not title:
|
|
183
|
+
if config.workflow_name:
|
|
184
|
+
title = f"Agent: {config.workflow_name}"
|
|
185
|
+
else:
|
|
186
|
+
title = "Agent session"
|
|
187
|
+
|
|
188
|
+
# Register the child session
|
|
189
|
+
child = self._storage.register(
|
|
190
|
+
external_id=external_id,
|
|
191
|
+
machine_id=config.machine_id,
|
|
192
|
+
source=config.source,
|
|
193
|
+
project_id=config.project_id,
|
|
194
|
+
title=title,
|
|
195
|
+
git_branch=config.git_branch,
|
|
196
|
+
parent_session_id=config.parent_session_id,
|
|
197
|
+
agent_depth=child_depth,
|
|
198
|
+
spawned_by_agent_id=config.agent_id,
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
child_id = child.id
|
|
202
|
+
|
|
203
|
+
# For sessions with provided external_id (e.g., Gemini preflight), keep it.
|
|
204
|
+
# For sessions without (e.g., Claude with --session-id), update external_id
|
|
205
|
+
# to match internal id so session_start hook can find this pre-created session.
|
|
206
|
+
if not use_provided_external_id:
|
|
207
|
+
self._storage.update(session_id=child_id, external_id=child_id)
|
|
208
|
+
# Re-fetch to get updated external_id
|
|
209
|
+
updated_child = self._storage.get(child_id)
|
|
210
|
+
if updated_child is None:
|
|
211
|
+
raise RuntimeError(f"Failed to fetch child session {child_id} after creation")
|
|
212
|
+
|
|
213
|
+
self.logger.info(
|
|
214
|
+
f"Created child session {updated_child.id} "
|
|
215
|
+
f"(parent={config.parent_session_id}, agent={config.agent_id})"
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
return updated_child
|
|
219
|
+
|
|
220
|
+
def get_child_sessions(self, parent_session_id: str) -> list[Session]:
|
|
221
|
+
"""
|
|
222
|
+
Get all child sessions of a parent.
|
|
223
|
+
|
|
224
|
+
Args:
|
|
225
|
+
parent_session_id: The parent session ID.
|
|
226
|
+
|
|
227
|
+
Returns:
|
|
228
|
+
List of child Session objects.
|
|
229
|
+
"""
|
|
230
|
+
return self._storage.find_children(parent_session_id)
|
|
231
|
+
|
|
232
|
+
def get_session_lineage(self, session_id: str) -> list[Session]:
|
|
233
|
+
"""
|
|
234
|
+
Get the full lineage of a session (from root to current).
|
|
235
|
+
|
|
236
|
+
Args:
|
|
237
|
+
session_id: The session to trace.
|
|
238
|
+
|
|
239
|
+
Returns:
|
|
240
|
+
List of sessions from root (human-initiated) to current.
|
|
241
|
+
"""
|
|
242
|
+
lineage: list[Session] = []
|
|
243
|
+
current_id: str | None = session_id
|
|
244
|
+
|
|
245
|
+
while current_id:
|
|
246
|
+
session = self._storage.get(current_id)
|
|
247
|
+
if not session:
|
|
248
|
+
break
|
|
249
|
+
lineage.append(session)
|
|
250
|
+
current_id = session.parent_session_id
|
|
251
|
+
|
|
252
|
+
# Safety limit
|
|
253
|
+
if len(lineage) > 10:
|
|
254
|
+
self.logger.warning(f"Lineage exceeded safety limit for {session_id}")
|
|
255
|
+
break
|
|
256
|
+
|
|
257
|
+
# Reverse to get root-to-current order
|
|
258
|
+
lineage.reverse()
|
|
259
|
+
return lineage
|