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/adapters/gemini.py
ADDED
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
"""Gemini CLI adapter for hook translation.
|
|
2
|
+
|
|
3
|
+
This adapter translates between Gemini CLI's native hook format and the unified
|
|
4
|
+
HookEvent/HookResponse models.
|
|
5
|
+
|
|
6
|
+
Gemini CLI Hook Types (11 total):
|
|
7
|
+
- SessionStart, SessionEnd: Session lifecycle
|
|
8
|
+
- BeforeAgent, AfterAgent: Agent turn lifecycle
|
|
9
|
+
- BeforeTool, AfterTool: Tool execution lifecycle
|
|
10
|
+
- BeforeToolSelection: Before tool selection (Gemini-only)
|
|
11
|
+
- BeforeModel, AfterModel: Model call lifecycle (Gemini-only)
|
|
12
|
+
- PreCompress: Context compression (maps to PRE_COMPACT)
|
|
13
|
+
- Notification: System notifications
|
|
14
|
+
|
|
15
|
+
Key differences from Claude Code:
|
|
16
|
+
- Uses PascalCase hook names (SessionStart vs session-start)
|
|
17
|
+
- Uses `hook_event_name` field instead of `hook_type`
|
|
18
|
+
- Has BeforeToolSelection, BeforeModel, AfterModel (not in Claude)
|
|
19
|
+
- Missing PermissionRequest, SubagentStart, SubagentStop (Claude-only)
|
|
20
|
+
- Different tool names (RunShellCommand vs Bash)
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
import platform
|
|
24
|
+
import uuid
|
|
25
|
+
from datetime import UTC, datetime
|
|
26
|
+
from typing import TYPE_CHECKING, Any
|
|
27
|
+
|
|
28
|
+
from gobby.adapters.base import BaseAdapter
|
|
29
|
+
from gobby.hooks.events import HookEvent, HookEventType, HookResponse, SessionSource
|
|
30
|
+
|
|
31
|
+
if TYPE_CHECKING:
|
|
32
|
+
from gobby.hooks.hook_manager import HookManager
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class GeminiAdapter(BaseAdapter):
|
|
36
|
+
"""Adapter for Gemini CLI hook translation.
|
|
37
|
+
|
|
38
|
+
This adapter:
|
|
39
|
+
1. Translates Gemini CLI's PascalCase hook payloads to unified HookEvent
|
|
40
|
+
2. Translates HookResponse back to Gemini CLI's expected format
|
|
41
|
+
3. Calls HookManager.handle() with unified HookEvent model
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
source = SessionSource.GEMINI
|
|
45
|
+
|
|
46
|
+
# Event type mapping: Gemini CLI hook names -> unified HookEventType
|
|
47
|
+
# Gemini CLI uses PascalCase hook names in the payload's "hook_event_name" field
|
|
48
|
+
EVENT_MAP: dict[str, HookEventType] = {
|
|
49
|
+
"SessionStart": HookEventType.SESSION_START,
|
|
50
|
+
"SessionEnd": HookEventType.SESSION_END,
|
|
51
|
+
"BeforeAgent": HookEventType.BEFORE_AGENT,
|
|
52
|
+
"AfterAgent": HookEventType.AFTER_AGENT,
|
|
53
|
+
"BeforeTool": HookEventType.BEFORE_TOOL,
|
|
54
|
+
"AfterTool": HookEventType.AFTER_TOOL,
|
|
55
|
+
"BeforeToolSelection": HookEventType.BEFORE_TOOL_SELECTION, # Gemini-only
|
|
56
|
+
"BeforeModel": HookEventType.BEFORE_MODEL, # Gemini-only
|
|
57
|
+
"AfterModel": HookEventType.AFTER_MODEL, # Gemini-only
|
|
58
|
+
"PreCompress": HookEventType.PRE_COMPACT, # Gemini calls it PreCompress
|
|
59
|
+
"Notification": HookEventType.NOTIFICATION,
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
# Reverse mapping for response translation
|
|
63
|
+
HOOK_EVENT_NAME_MAP: dict[str, str] = {
|
|
64
|
+
"session_start": "SessionStart",
|
|
65
|
+
"session_end": "SessionEnd",
|
|
66
|
+
"before_agent": "BeforeAgent",
|
|
67
|
+
"after_agent": "AfterAgent",
|
|
68
|
+
"before_tool": "BeforeTool",
|
|
69
|
+
"after_tool": "AfterTool",
|
|
70
|
+
"before_tool_selection": "BeforeToolSelection",
|
|
71
|
+
"before_model": "BeforeModel",
|
|
72
|
+
"after_model": "AfterModel",
|
|
73
|
+
"pre_compact": "PreCompress",
|
|
74
|
+
"notification": "Notification",
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
# Tool name mapping: Gemini tool names -> normalized names
|
|
78
|
+
# Gemini uses different tool names than Claude Code
|
|
79
|
+
TOOL_MAP: dict[str, str] = {
|
|
80
|
+
"run_shell_command": "Bash",
|
|
81
|
+
"RunShellCommand": "Bash",
|
|
82
|
+
"read_file": "Read",
|
|
83
|
+
"ReadFile": "Read",
|
|
84
|
+
"ReadFileTool": "Read",
|
|
85
|
+
"write_file": "Write",
|
|
86
|
+
"WriteFile": "Write",
|
|
87
|
+
"WriteFileTool": "Write",
|
|
88
|
+
"edit_file": "Edit",
|
|
89
|
+
"EditFile": "Edit",
|
|
90
|
+
"EditFileTool": "Edit",
|
|
91
|
+
"GlobTool": "Glob",
|
|
92
|
+
"GrepTool": "Grep",
|
|
93
|
+
"ShellTool": "Bash",
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
def __init__(self, hook_manager: "HookManager | None" = None):
|
|
97
|
+
"""Initialize the Gemini CLI adapter.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
hook_manager: Reference to HookManager for handling events.
|
|
101
|
+
If None, the adapter can only translate (not handle events).
|
|
102
|
+
"""
|
|
103
|
+
self._hook_manager = hook_manager
|
|
104
|
+
# Cache machine_id since Gemini doesn't always send it
|
|
105
|
+
self._machine_id: str | None = None
|
|
106
|
+
|
|
107
|
+
def _get_machine_id(self) -> str:
|
|
108
|
+
"""Get or generate a machine identifier.
|
|
109
|
+
|
|
110
|
+
Gemini CLI doesn't always send machine_id, so we generate one
|
|
111
|
+
based on the platform node (hostname/MAC address).
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
A stable machine identifier.
|
|
115
|
+
"""
|
|
116
|
+
if self._machine_id is None:
|
|
117
|
+
# Use platform.node() which returns hostname or MAC-based ID
|
|
118
|
+
node = platform.node()
|
|
119
|
+
if node:
|
|
120
|
+
# Create a deterministic UUID from the node name
|
|
121
|
+
self._machine_id = str(uuid.uuid5(uuid.NAMESPACE_DNS, node))
|
|
122
|
+
else:
|
|
123
|
+
# Fallback to a random UUID (less ideal but works)
|
|
124
|
+
self._machine_id = str(uuid.uuid4())
|
|
125
|
+
return self._machine_id
|
|
126
|
+
|
|
127
|
+
def normalize_tool_name(self, gemini_tool_name: str) -> str:
|
|
128
|
+
"""Normalize Gemini tool name to standard format.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
gemini_tool_name: Tool name from Gemini CLI.
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
Normalized tool name (e.g., "Bash", "Read", "Write").
|
|
135
|
+
"""
|
|
136
|
+
return self.TOOL_MAP.get(gemini_tool_name, gemini_tool_name)
|
|
137
|
+
|
|
138
|
+
def translate_to_hook_event(self, native_event: dict[str, Any]) -> HookEvent:
|
|
139
|
+
"""Convert Gemini CLI native event to unified HookEvent.
|
|
140
|
+
|
|
141
|
+
Gemini CLI payloads have the structure:
|
|
142
|
+
{
|
|
143
|
+
"hook_event_name": "SessionStart", # PascalCase hook name
|
|
144
|
+
"session_id": "abc123", # Session identifier
|
|
145
|
+
"cwd": "/path/to/project",
|
|
146
|
+
"timestamp": "2025-01-15T10:30:00Z", # ISO timestamp
|
|
147
|
+
# ... other hook-specific fields
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
Note: The hook_dispatcher.py wraps this in:
|
|
151
|
+
{
|
|
152
|
+
"source": "gemini",
|
|
153
|
+
"hook_type": "SessionStart",
|
|
154
|
+
"input_data": {...} # The actual Gemini payload
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
native_event: Raw payload from Gemini CLI's hook_dispatcher.py
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
Unified HookEvent with normalized fields.
|
|
162
|
+
"""
|
|
163
|
+
# Extract from dispatcher wrapper format (matches Claude's structure)
|
|
164
|
+
hook_type = native_event.get("hook_type", "")
|
|
165
|
+
input_data = native_event.get("input_data", {})
|
|
166
|
+
|
|
167
|
+
# If input_data is empty, the native_event might BE the input_data
|
|
168
|
+
# (for direct Gemini calls without dispatcher wrapper)
|
|
169
|
+
if not input_data and "hook_event_name" in native_event:
|
|
170
|
+
input_data = native_event
|
|
171
|
+
hook_type = native_event.get("hook_event_name", "")
|
|
172
|
+
|
|
173
|
+
# Map Gemini hook type to unified event type
|
|
174
|
+
# Fall back to NOTIFICATION for unknown types (fail-open)
|
|
175
|
+
event_type = self.EVENT_MAP.get(hook_type, HookEventType.NOTIFICATION)
|
|
176
|
+
|
|
177
|
+
# Extract session_id
|
|
178
|
+
session_id = input_data.get("session_id", "")
|
|
179
|
+
|
|
180
|
+
# Parse timestamp if present (Gemini uses ISO format)
|
|
181
|
+
timestamp_str = input_data.get("timestamp")
|
|
182
|
+
if timestamp_str:
|
|
183
|
+
try:
|
|
184
|
+
timestamp = datetime.fromisoformat(timestamp_str.replace("Z", "+00:00"))
|
|
185
|
+
except (ValueError, AttributeError):
|
|
186
|
+
timestamp = datetime.now(UTC)
|
|
187
|
+
else:
|
|
188
|
+
timestamp = datetime.now(UTC)
|
|
189
|
+
|
|
190
|
+
# Get machine_id (Gemini might not send it)
|
|
191
|
+
machine_id = input_data.get("machine_id") or self._get_machine_id()
|
|
192
|
+
|
|
193
|
+
# Normalize tool name if present (for tool-related hooks)
|
|
194
|
+
if "tool_name" in input_data:
|
|
195
|
+
original_tool = input_data.get("tool_name", "")
|
|
196
|
+
normalized_tool = self.normalize_tool_name(original_tool)
|
|
197
|
+
# Store both for logging/debugging
|
|
198
|
+
metadata = {
|
|
199
|
+
"original_tool_name": original_tool,
|
|
200
|
+
"normalized_tool_name": normalized_tool,
|
|
201
|
+
}
|
|
202
|
+
else:
|
|
203
|
+
metadata = {}
|
|
204
|
+
|
|
205
|
+
return HookEvent(
|
|
206
|
+
event_type=event_type,
|
|
207
|
+
session_id=session_id,
|
|
208
|
+
source=self.source,
|
|
209
|
+
timestamp=timestamp,
|
|
210
|
+
machine_id=machine_id,
|
|
211
|
+
cwd=input_data.get("cwd"),
|
|
212
|
+
data=input_data,
|
|
213
|
+
metadata=metadata,
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
def translate_from_hook_response(
|
|
217
|
+
self, response: HookResponse, hook_type: str | None = None
|
|
218
|
+
) -> dict[str, Any]:
|
|
219
|
+
"""Convert HookResponse to Gemini CLI's expected format.
|
|
220
|
+
|
|
221
|
+
Gemini CLI expects responses in this format:
|
|
222
|
+
{
|
|
223
|
+
"decision": "allow" | "deny", # Whether to allow the action
|
|
224
|
+
"reason": "...", # Optional reason for decision
|
|
225
|
+
"hookSpecificOutput": { # Hook-specific response data
|
|
226
|
+
"additionalContext": "...", # Context to inject
|
|
227
|
+
"llm_request": {...}, # For BeforeModel hooks
|
|
228
|
+
"toolConfig": {...} # For BeforeToolSelection hooks
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
Exit codes: 0 = allow, 2 = deny (handled by dispatcher)
|
|
233
|
+
|
|
234
|
+
Args:
|
|
235
|
+
response: Unified HookResponse from HookManager.
|
|
236
|
+
hook_type: Original Gemini CLI hook type (e.g., "SessionStart")
|
|
237
|
+
Used to format hookSpecificOutput appropriately.
|
|
238
|
+
|
|
239
|
+
Returns:
|
|
240
|
+
Dict in Gemini CLI's expected format.
|
|
241
|
+
"""
|
|
242
|
+
result: dict[str, Any] = {
|
|
243
|
+
"decision": response.decision,
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
# Add reason if present
|
|
247
|
+
if response.reason:
|
|
248
|
+
result["reason"] = response.reason
|
|
249
|
+
|
|
250
|
+
# Build hookSpecificOutput based on hook type
|
|
251
|
+
hook_specific: dict[str, Any] = {}
|
|
252
|
+
|
|
253
|
+
# Add context injection if present
|
|
254
|
+
if response.context:
|
|
255
|
+
hook_specific["additionalContext"] = response.context
|
|
256
|
+
|
|
257
|
+
# Add session/terminal context for SessionStart only
|
|
258
|
+
if hook_type == "SessionStart" and response.metadata:
|
|
259
|
+
session_id = response.metadata.get("session_id")
|
|
260
|
+
if session_id:
|
|
261
|
+
hook_event_name = self.HOOK_EVENT_NAME_MAP.get(hook_type, "Unknown")
|
|
262
|
+
context_lines = [f"session_id: {session_id}"]
|
|
263
|
+
if response.metadata.get("parent_session_id"):
|
|
264
|
+
context_lines.append(
|
|
265
|
+
f"parent_session_id: {response.metadata['parent_session_id']}"
|
|
266
|
+
)
|
|
267
|
+
if response.metadata.get("machine_id"):
|
|
268
|
+
context_lines.append(f"machine_id: {response.metadata['machine_id']}")
|
|
269
|
+
if response.metadata.get("project_id"):
|
|
270
|
+
context_lines.append(f"project_id: {response.metadata['project_id']}")
|
|
271
|
+
# Add terminal context (non-null values only)
|
|
272
|
+
if response.metadata.get("terminal_term_program"):
|
|
273
|
+
context_lines.append(f"terminal: {response.metadata['terminal_term_program']}")
|
|
274
|
+
if response.metadata.get("terminal_tty"):
|
|
275
|
+
context_lines.append(f"tty: {response.metadata['terminal_tty']}")
|
|
276
|
+
if response.metadata.get("terminal_parent_pid"):
|
|
277
|
+
context_lines.append(f"parent_pid: {response.metadata['terminal_parent_pid']}")
|
|
278
|
+
# Add terminal-specific session IDs
|
|
279
|
+
for key in [
|
|
280
|
+
"terminal_iterm_session_id",
|
|
281
|
+
"terminal_term_session_id",
|
|
282
|
+
"terminal_kitty_window_id",
|
|
283
|
+
"terminal_tmux_pane",
|
|
284
|
+
"terminal_vscode_terminal_id",
|
|
285
|
+
"terminal_alacritty_socket",
|
|
286
|
+
]:
|
|
287
|
+
if response.metadata.get(key):
|
|
288
|
+
friendly_name = key.replace("terminal_", "").replace("_", " ")
|
|
289
|
+
context_lines.append(f"{friendly_name}: {response.metadata[key]}")
|
|
290
|
+
hook_specific["hookEventName"] = hook_event_name
|
|
291
|
+
# Append to existing additionalContext if present
|
|
292
|
+
existing = hook_specific.get("additionalContext", "")
|
|
293
|
+
new_context = "\n".join(context_lines)
|
|
294
|
+
hook_specific["additionalContext"] = (
|
|
295
|
+
f"{existing}\n{new_context}" if existing else new_context
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
# Handle BeforeModel-specific output (llm_request modification)
|
|
299
|
+
if hook_type == "BeforeModel" and response.modify_args:
|
|
300
|
+
hook_specific["llm_request"] = response.modify_args
|
|
301
|
+
|
|
302
|
+
# Handle BeforeToolSelection-specific output (toolConfig modification)
|
|
303
|
+
if hook_type == "BeforeToolSelection" and response.modify_args:
|
|
304
|
+
hook_specific["toolConfig"] = response.modify_args
|
|
305
|
+
|
|
306
|
+
# Only add hookSpecificOutput if there's content
|
|
307
|
+
if hook_specific:
|
|
308
|
+
result["hookSpecificOutput"] = hook_specific
|
|
309
|
+
|
|
310
|
+
# Add system message if present (user-visible notification)
|
|
311
|
+
if response.system_message:
|
|
312
|
+
result["systemMessage"] = response.system_message
|
|
313
|
+
|
|
314
|
+
return result
|
|
315
|
+
|
|
316
|
+
def handle_native(
|
|
317
|
+
self, native_event: dict[str, Any], hook_manager: "HookManager"
|
|
318
|
+
) -> dict[str, Any]:
|
|
319
|
+
"""Main entry point for HTTP endpoint.
|
|
320
|
+
|
|
321
|
+
Translates native Gemini CLI event, processes through HookManager,
|
|
322
|
+
and returns response in Gemini's expected format.
|
|
323
|
+
|
|
324
|
+
Args:
|
|
325
|
+
native_event: Raw payload from Gemini CLI's hook_dispatcher.py
|
|
326
|
+
hook_manager: HookManager instance for processing.
|
|
327
|
+
|
|
328
|
+
Returns:
|
|
329
|
+
Response dict in Gemini CLI's expected format.
|
|
330
|
+
"""
|
|
331
|
+
# Translate to unified HookEvent
|
|
332
|
+
hook_event = self.translate_to_hook_event(native_event)
|
|
333
|
+
|
|
334
|
+
# Get original hook type for response formatting
|
|
335
|
+
hook_type = native_event.get("hook_type", "")
|
|
336
|
+
if not hook_type:
|
|
337
|
+
hook_type = native_event.get("input_data", {}).get("hook_event_name", "")
|
|
338
|
+
|
|
339
|
+
# Process through HookManager
|
|
340
|
+
hook_response = hook_manager.handle(hook_event)
|
|
341
|
+
|
|
342
|
+
# Translate response back to Gemini format
|
|
343
|
+
return self.translate_from_hook_response(hook_response, hook_type=hook_type)
|
gobby/agents/__init__.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Gobby Agents Module.
|
|
3
|
+
|
|
4
|
+
This module provides the subagent spawning system, enabling agents to spawn
|
|
5
|
+
independent subagents that can use any LLM provider and follow workflows.
|
|
6
|
+
|
|
7
|
+
Components:
|
|
8
|
+
- AgentRunner: Orchestrates agent execution with workflow integration
|
|
9
|
+
- Session management: Creates and links child sessions to parents
|
|
10
|
+
- Terminal spawning: Launches agents in separate terminal windows
|
|
11
|
+
|
|
12
|
+
Usage:
|
|
13
|
+
from gobby.agents import AgentRunner, AgentConfig
|
|
14
|
+
|
|
15
|
+
runner = AgentRunner(db, session_storage, executors)
|
|
16
|
+
result = await runner.run(AgentConfig(
|
|
17
|
+
prompt="Review the auth changes",
|
|
18
|
+
parent_session_id="sess-123",
|
|
19
|
+
project_id="proj-abc",
|
|
20
|
+
machine_id="machine-1",
|
|
21
|
+
source="claude",
|
|
22
|
+
provider="claude",
|
|
23
|
+
))
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from gobby.agents.registry import RunningAgent
|
|
27
|
+
from gobby.agents.runner import AgentConfig, AgentRunContext, AgentRunner
|
|
28
|
+
from gobby.agents.session import ChildSessionConfig, ChildSessionManager
|
|
29
|
+
|
|
30
|
+
__all__ = [
|
|
31
|
+
"AgentConfig",
|
|
32
|
+
"AgentRunContext",
|
|
33
|
+
"AgentRunner",
|
|
34
|
+
"RunningAgent",
|
|
35
|
+
"ChildSessionConfig",
|
|
36
|
+
"ChildSessionManager",
|
|
37
|
+
]
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""Codex session ID capture utility.
|
|
2
|
+
|
|
3
|
+
Captures Codex's session_id from the startup banner before launching interactive mode.
|
|
4
|
+
This is necessary because we need the session_id to:
|
|
5
|
+
1. Link to Gobby sessions (external_id)
|
|
6
|
+
2. Resume functionality with `codex resume {session_id}`
|
|
7
|
+
3. MCP tool calls that require session context
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import asyncio
|
|
11
|
+
import logging
|
|
12
|
+
import re
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
# Regex to match "session id: {uuid}" in startup banner
|
|
18
|
+
SESSION_ID_PATTERN = re.compile(r"^session id:\s*([0-9a-f-]+)$", re.IGNORECASE)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class CodexSessionInfo:
|
|
23
|
+
"""Captured Codex session information."""
|
|
24
|
+
|
|
25
|
+
session_id: str
|
|
26
|
+
model: str | None = None
|
|
27
|
+
workdir: str | None = None
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
async def capture_codex_session_id(
|
|
31
|
+
timeout: float = 30.0,
|
|
32
|
+
) -> CodexSessionInfo:
|
|
33
|
+
"""Capture Codex's session_id via preflight exec call.
|
|
34
|
+
|
|
35
|
+
Launches Codex with minimal command (`codex exec "exit"`),
|
|
36
|
+
parses the startup banner for session_id, then returns.
|
|
37
|
+
|
|
38
|
+
The Codex banner format is:
|
|
39
|
+
OpenAI Codex v0.80.0 (research preview)
|
|
40
|
+
--------
|
|
41
|
+
workdir: /path/to/dir
|
|
42
|
+
model: gpt-5.2-codex
|
|
43
|
+
...
|
|
44
|
+
session id: 019bbaea-3e0f-7d61-afc4-56a9456c2c7d
|
|
45
|
+
--------
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
timeout: Max seconds to wait for exec to complete (default 30s
|
|
49
|
+
to account for model loading time)
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
CodexSessionInfo with captured session_id and metadata
|
|
53
|
+
|
|
54
|
+
Raises:
|
|
55
|
+
asyncio.TimeoutError: If exec doesn't complete within timeout
|
|
56
|
+
ValueError: If session_id not found in output
|
|
57
|
+
FileNotFoundError: If codex CLI is not installed
|
|
58
|
+
"""
|
|
59
|
+
logger.debug("Starting Codex preflight to capture session_id")
|
|
60
|
+
|
|
61
|
+
try:
|
|
62
|
+
proc = await asyncio.create_subprocess_exec(
|
|
63
|
+
"codex",
|
|
64
|
+
"exec",
|
|
65
|
+
"exit",
|
|
66
|
+
stdout=asyncio.subprocess.PIPE,
|
|
67
|
+
stderr=asyncio.subprocess.PIPE,
|
|
68
|
+
)
|
|
69
|
+
except FileNotFoundError as e:
|
|
70
|
+
raise FileNotFoundError(
|
|
71
|
+
"Codex CLI not found. Install from: https://github.com/openai/codex"
|
|
72
|
+
) from e
|
|
73
|
+
|
|
74
|
+
try:
|
|
75
|
+
stdout, stderr = await asyncio.wait_for(proc.communicate(), timeout=timeout)
|
|
76
|
+
except TimeoutError:
|
|
77
|
+
proc.kill()
|
|
78
|
+
await proc.wait()
|
|
79
|
+
raise
|
|
80
|
+
|
|
81
|
+
# Codex outputs the startup banner (including session id) to stderr
|
|
82
|
+
output = stderr.decode()
|
|
83
|
+
session_id: str | None = None
|
|
84
|
+
model: str | None = None
|
|
85
|
+
workdir: str | None = None
|
|
86
|
+
|
|
87
|
+
# Parse the startup banner
|
|
88
|
+
for line in output.splitlines():
|
|
89
|
+
line = line.strip()
|
|
90
|
+
|
|
91
|
+
# Match session id
|
|
92
|
+
match = SESSION_ID_PATTERN.match(line)
|
|
93
|
+
if match:
|
|
94
|
+
session_id = match.group(1)
|
|
95
|
+
continue
|
|
96
|
+
|
|
97
|
+
# Extract model if present
|
|
98
|
+
if line.startswith("model:"):
|
|
99
|
+
model = line.split(":", 1)[1].strip()
|
|
100
|
+
continue
|
|
101
|
+
|
|
102
|
+
# Extract workdir if present
|
|
103
|
+
if line.startswith("workdir:"):
|
|
104
|
+
workdir = line.split(":", 1)[1].strip()
|
|
105
|
+
continue
|
|
106
|
+
|
|
107
|
+
if not session_id:
|
|
108
|
+
# Log the output for debugging
|
|
109
|
+
logger.error(f"Failed to parse Codex output:\n{output}")
|
|
110
|
+
if stderr:
|
|
111
|
+
logger.error(f"Codex stderr:\n{stderr.decode()}")
|
|
112
|
+
raise ValueError("No session id found in Codex output")
|
|
113
|
+
|
|
114
|
+
logger.debug(f"Captured Codex session_id: {session_id}, model: {model}, workdir: {workdir}")
|
|
115
|
+
|
|
116
|
+
return CodexSessionInfo(
|
|
117
|
+
session_id=session_id,
|
|
118
|
+
model=model,
|
|
119
|
+
workdir=workdir,
|
|
120
|
+
)
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Constants for agent spawning and terminal mode.
|
|
3
|
+
|
|
4
|
+
This module defines environment variables used to pass context to
|
|
5
|
+
spawned terminal processes. When an agent spawns a child in terminal
|
|
6
|
+
mode, these environment variables are set in the child process.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
# ============================================================================
|
|
10
|
+
# Terminal Mode Environment Variables
|
|
11
|
+
# ============================================================================
|
|
12
|
+
# These environment variables are set when spawning a terminal-mode agent.
|
|
13
|
+
# The child CLI process reads these to pick up its prepared state.
|
|
14
|
+
# ============================================================================
|
|
15
|
+
|
|
16
|
+
# Session identifier for the pre-created child session
|
|
17
|
+
# The spawned CLI uses this to connect to its session via hooks
|
|
18
|
+
GOBBY_SESSION_ID = "GOBBY_SESSION_ID"
|
|
19
|
+
|
|
20
|
+
# Parent session identifier for context resolution
|
|
21
|
+
# Used to look up parent session for context injection
|
|
22
|
+
GOBBY_PARENT_SESSION_ID = "GOBBY_PARENT_SESSION_ID"
|
|
23
|
+
|
|
24
|
+
# Agent run record identifier
|
|
25
|
+
# Links the terminal process back to its agent_runs record
|
|
26
|
+
GOBBY_AGENT_RUN_ID = "GOBBY_AGENT_RUN_ID"
|
|
27
|
+
|
|
28
|
+
# Workflow name to activate on session start
|
|
29
|
+
# The hook reads this and activates the workflow for the session
|
|
30
|
+
GOBBY_WORKFLOW_NAME = "GOBBY_WORKFLOW_NAME"
|
|
31
|
+
|
|
32
|
+
# Project identifier for the session
|
|
33
|
+
# Used for project-scoped operations
|
|
34
|
+
GOBBY_PROJECT_ID = "GOBBY_PROJECT_ID"
|
|
35
|
+
|
|
36
|
+
# Current agent nesting depth
|
|
37
|
+
# 0 = human-initiated, 1+ = agent-spawned
|
|
38
|
+
GOBBY_AGENT_DEPTH = "GOBBY_AGENT_DEPTH"
|
|
39
|
+
|
|
40
|
+
# Maximum allowed agent depth
|
|
41
|
+
# Prevents infinite nesting
|
|
42
|
+
GOBBY_MAX_AGENT_DEPTH = "GOBBY_MAX_AGENT_DEPTH"
|
|
43
|
+
|
|
44
|
+
# Initial prompt for the agent (short prompts only)
|
|
45
|
+
# For longer prompts, use GOBBY_PROMPT_FILE instead
|
|
46
|
+
GOBBY_PROMPT = "GOBBY_PROMPT"
|
|
47
|
+
|
|
48
|
+
# Path to file containing initial prompt (for long prompts)
|
|
49
|
+
# Takes precedence over GOBBY_PROMPT if both are set
|
|
50
|
+
GOBBY_PROMPT_FILE = "GOBBY_PROMPT_FILE"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def get_terminal_env_vars(
|
|
54
|
+
session_id: str,
|
|
55
|
+
parent_session_id: str,
|
|
56
|
+
agent_run_id: str,
|
|
57
|
+
project_id: str,
|
|
58
|
+
workflow_name: str | None = None,
|
|
59
|
+
agent_depth: int = 1,
|
|
60
|
+
max_agent_depth: int = 3,
|
|
61
|
+
prompt: str | None = None,
|
|
62
|
+
prompt_file: str | None = None,
|
|
63
|
+
) -> dict[str, str]:
|
|
64
|
+
"""
|
|
65
|
+
Build environment variables dict for spawning a terminal-mode agent.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
session_id: The pre-created child session ID.
|
|
69
|
+
parent_session_id: The parent session ID for context resolution.
|
|
70
|
+
agent_run_id: The agent run record ID.
|
|
71
|
+
project_id: The project ID.
|
|
72
|
+
workflow_name: Optional workflow to activate.
|
|
73
|
+
agent_depth: Current nesting depth (default: 1).
|
|
74
|
+
max_agent_depth: Maximum allowed depth (default: 3).
|
|
75
|
+
prompt: Optional short prompt (for inline passing).
|
|
76
|
+
prompt_file: Optional path to file containing prompt (for long prompts).
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
Dict of environment variable name to value.
|
|
80
|
+
"""
|
|
81
|
+
env = {
|
|
82
|
+
GOBBY_SESSION_ID: session_id,
|
|
83
|
+
GOBBY_PARENT_SESSION_ID: parent_session_id,
|
|
84
|
+
GOBBY_AGENT_RUN_ID: agent_run_id,
|
|
85
|
+
GOBBY_PROJECT_ID: project_id,
|
|
86
|
+
GOBBY_AGENT_DEPTH: str(agent_depth),
|
|
87
|
+
GOBBY_MAX_AGENT_DEPTH: str(max_agent_depth),
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if workflow_name:
|
|
91
|
+
env[GOBBY_WORKFLOW_NAME] = workflow_name
|
|
92
|
+
|
|
93
|
+
if prompt_file:
|
|
94
|
+
env[GOBBY_PROMPT_FILE] = prompt_file
|
|
95
|
+
elif prompt:
|
|
96
|
+
env[GOBBY_PROMPT] = prompt
|
|
97
|
+
|
|
98
|
+
return env
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
# List of all environment variable names for documentation
|
|
102
|
+
ALL_TERMINAL_ENV_VARS = [
|
|
103
|
+
GOBBY_SESSION_ID,
|
|
104
|
+
GOBBY_PARENT_SESSION_ID,
|
|
105
|
+
GOBBY_AGENT_RUN_ID,
|
|
106
|
+
GOBBY_WORKFLOW_NAME,
|
|
107
|
+
GOBBY_PROJECT_ID,
|
|
108
|
+
GOBBY_AGENT_DEPTH,
|
|
109
|
+
GOBBY_MAX_AGENT_DEPTH,
|
|
110
|
+
GOBBY_PROMPT,
|
|
111
|
+
GOBBY_PROMPT_FILE,
|
|
112
|
+
]
|