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/__init__.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""CLI adapters for multi-CLI session management.
|
|
2
|
+
|
|
3
|
+
This module contains adapters that translate between CLI-specific hook formats
|
|
4
|
+
and the unified HookEvent/HookResponse models.
|
|
5
|
+
|
|
6
|
+
Each adapter is responsible for:
|
|
7
|
+
1. Translating native CLI payloads to HookEvent
|
|
8
|
+
2. Translating HookResponse back to CLI-expected format
|
|
9
|
+
3. Managing CLI-specific session lifecycle
|
|
10
|
+
|
|
11
|
+
Adapters:
|
|
12
|
+
- ClaudeCodeAdapter: For Claude Code CLI hooks (HTTP-based)
|
|
13
|
+
- GeminiAdapter: For Gemini CLI hooks (HTTP-based) [Phase 3]
|
|
14
|
+
- CodexAdapter: For Codex CLI via app-server (JSON-RPC-based) [Phase 4]
|
|
15
|
+
- CodexNotifyAdapter: For Codex CLI notify events (simple HTTP-based)
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from gobby.adapters.base import BaseAdapter
|
|
19
|
+
from gobby.adapters.claude_code import ClaudeCodeAdapter
|
|
20
|
+
from gobby.adapters.codex import CodexAdapter, CodexAppServerClient, CodexNotifyAdapter
|
|
21
|
+
from gobby.adapters.gemini import GeminiAdapter
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"BaseAdapter",
|
|
25
|
+
"ClaudeCodeAdapter",
|
|
26
|
+
"CodexAdapter",
|
|
27
|
+
"CodexAppServerClient",
|
|
28
|
+
"CodexNotifyAdapter",
|
|
29
|
+
"GeminiAdapter",
|
|
30
|
+
]
|
gobby/adapters/base.py
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""Base adapter class for CLI hook translation.
|
|
2
|
+
|
|
3
|
+
This module defines the abstract base class that all CLI adapters must implement.
|
|
4
|
+
Adapters are responsible for translating between CLI-specific hook formats and
|
|
5
|
+
the unified HookEvent/HookResponse models.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from abc import ABC, abstractmethod
|
|
9
|
+
from typing import TYPE_CHECKING, Any
|
|
10
|
+
|
|
11
|
+
from gobby.hooks.events import HookEvent, HookResponse, SessionSource
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from gobby.hooks.hook_manager import HookManager
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class BaseAdapter(ABC):
|
|
18
|
+
"""Base class for CLI adapters that translate native events to HookEvents.
|
|
19
|
+
|
|
20
|
+
Each CLI (Claude Code, Gemini, Codex) has its own adapter that:
|
|
21
|
+
1. Knows how to parse the CLI's native hook payload format
|
|
22
|
+
2. Translates payloads to unified HookEvent objects
|
|
23
|
+
3. Translates HookResponse objects back to CLI-expected format
|
|
24
|
+
|
|
25
|
+
Subclasses must implement:
|
|
26
|
+
- source: The SessionSource enum value for this CLI
|
|
27
|
+
- translate_to_hook_event(): Convert native payload to HookEvent
|
|
28
|
+
- translate_from_hook_response(): Convert HookResponse to native format
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
source: SessionSource
|
|
32
|
+
|
|
33
|
+
@abstractmethod
|
|
34
|
+
def translate_to_hook_event(self, native_event: dict[str, Any]) -> HookEvent | None:
|
|
35
|
+
"""Convert native CLI event to unified HookEvent.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
native_event: The raw payload from the CLI's hook dispatcher.
|
|
39
|
+
Structure varies by CLI:
|
|
40
|
+
- Claude Code: {"hook_type": "...", "input_data": {...}}
|
|
41
|
+
- Gemini: {"hook_event_name": "...", "session_id": "...", ...}
|
|
42
|
+
- Codex: JSON-RPC params from app-server events
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
A unified HookEvent that can be processed by HookManager.
|
|
46
|
+
"""
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
@abstractmethod
|
|
50
|
+
def translate_from_hook_response(self, response: HookResponse) -> dict[str, Any]:
|
|
51
|
+
"""Convert HookResponse to native CLI response format.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
response: The unified HookResponse from HookManager.
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
A dict in the format expected by the CLI's hook dispatcher:
|
|
58
|
+
- Claude Code: {"continue": bool, "stopReason": str | None, ...}
|
|
59
|
+
- Gemini: {"decision": str, "hookSpecificOutput": {...}}
|
|
60
|
+
- Codex: JSON-RPC response format
|
|
61
|
+
"""
|
|
62
|
+
pass
|
|
63
|
+
|
|
64
|
+
def handle_native(
|
|
65
|
+
self, native_event: dict[str, Any], hook_manager: "HookManager"
|
|
66
|
+
) -> dict[str, Any]:
|
|
67
|
+
"""Main entry point for HTTP endpoints.
|
|
68
|
+
|
|
69
|
+
This method handles the full round-trip:
|
|
70
|
+
1. Translate native event to HookEvent
|
|
71
|
+
2. Process through HookManager
|
|
72
|
+
3. Translate response back to native format
|
|
73
|
+
|
|
74
|
+
Note: This method is synchronous for Phase 2A-2B compatibility.
|
|
75
|
+
In Phase 2C+, when HookManager.handle() is async, subclasses may
|
|
76
|
+
override with async versions.
|
|
77
|
+
|
|
78
|
+
Subclasses may override this to add CLI-specific behavior, such as
|
|
79
|
+
the strangler fig pattern used by ClaudeCodeAdapter.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
native_event: The raw payload from the CLI.
|
|
83
|
+
hook_manager: The HookManager instance to process events.
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
Response dict in CLI-specific format.
|
|
87
|
+
"""
|
|
88
|
+
hook_event = self.translate_to_hook_event(native_event)
|
|
89
|
+
if hook_event is None:
|
|
90
|
+
# Event ignored by adapter
|
|
91
|
+
return {}
|
|
92
|
+
hook_response = hook_manager.handle(hook_event)
|
|
93
|
+
return self.translate_from_hook_response(hook_response)
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
"""Claude Code adapter for hook translation.
|
|
2
|
+
|
|
3
|
+
This adapter translates between Claude Code's native hook format and the unified
|
|
4
|
+
HookEvent/HookResponse models. It implements the strangler fig pattern for safe
|
|
5
|
+
migration from the existing HookManager.execute() method.
|
|
6
|
+
|
|
7
|
+
Claude Code Hook Types (12 total):
|
|
8
|
+
- session-start, session-end: Session lifecycle
|
|
9
|
+
- user-prompt-submit: Before user prompt validation
|
|
10
|
+
- pre-tool-use, post-tool-use, post-tool-use-failure: Tool lifecycle
|
|
11
|
+
- pre-compact: Context compaction
|
|
12
|
+
- stop: Agent stops
|
|
13
|
+
- subagent-start, subagent-stop: Subagent lifecycle
|
|
14
|
+
- permission-request: Permission requests (future)
|
|
15
|
+
- notification: System notifications
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from datetime import UTC, datetime
|
|
19
|
+
from typing import TYPE_CHECKING, Any
|
|
20
|
+
|
|
21
|
+
from gobby.adapters.base import BaseAdapter
|
|
22
|
+
from gobby.hooks.events import HookEvent, HookEventType, HookResponse, SessionSource
|
|
23
|
+
|
|
24
|
+
if TYPE_CHECKING:
|
|
25
|
+
from gobby.hooks.hook_manager import HookManager
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ClaudeCodeAdapter(BaseAdapter):
|
|
29
|
+
"""Adapter for Claude Code CLI hook translation.
|
|
30
|
+
|
|
31
|
+
This adapter:
|
|
32
|
+
1. Translates Claude Code's kebab-case hook payloads to unified HookEvent
|
|
33
|
+
2. Translates HookResponse back to Claude Code's expected format
|
|
34
|
+
3. Calls HookManager.handle() with unified HookEvent model
|
|
35
|
+
|
|
36
|
+
Phase 2C Migration Complete:
|
|
37
|
+
- Now using HookManager.handle(HookEvent) for all hooks
|
|
38
|
+
- Legacy execute() path available via set_legacy_mode(True) for rollback
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
source = SessionSource.CLAUDE
|
|
42
|
+
|
|
43
|
+
# Event type mapping: Claude Code hook names -> unified HookEventType
|
|
44
|
+
# Claude Code uses kebab-case hook names in the payload's "hook_type" field
|
|
45
|
+
EVENT_MAP: dict[str, HookEventType] = {
|
|
46
|
+
"session-start": HookEventType.SESSION_START,
|
|
47
|
+
"session-end": HookEventType.SESSION_END,
|
|
48
|
+
"user-prompt-submit": HookEventType.BEFORE_AGENT,
|
|
49
|
+
"stop": HookEventType.STOP,
|
|
50
|
+
"pre-tool-use": HookEventType.BEFORE_TOOL,
|
|
51
|
+
"post-tool-use": HookEventType.AFTER_TOOL,
|
|
52
|
+
"post-tool-use-failure": HookEventType.AFTER_TOOL, # Same as AFTER_TOOL with error flag
|
|
53
|
+
"pre-compact": HookEventType.PRE_COMPACT,
|
|
54
|
+
"subagent-start": HookEventType.SUBAGENT_START,
|
|
55
|
+
"subagent-stop": HookEventType.SUBAGENT_STOP,
|
|
56
|
+
"permission-request": HookEventType.PERMISSION_REQUEST,
|
|
57
|
+
"notification": HookEventType.NOTIFICATION,
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
def __init__(self, hook_manager: "HookManager | None" = None):
|
|
61
|
+
"""Initialize the Claude Code adapter.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
hook_manager: Reference to HookManager for strangler fig delegation.
|
|
65
|
+
If None, the adapter can only translate (not handle events).
|
|
66
|
+
"""
|
|
67
|
+
self._hook_manager = hook_manager
|
|
68
|
+
# Phase 2C: Use new handle() path with unified HookEvent model
|
|
69
|
+
# Note: systemMessage handoff notification bug exists in both paths (see plan-multi-cli.md)
|
|
70
|
+
self._use_legacy = False
|
|
71
|
+
|
|
72
|
+
def translate_to_hook_event(self, native_event: dict[str, Any]) -> HookEvent:
|
|
73
|
+
"""Convert Claude Code native event to unified HookEvent.
|
|
74
|
+
|
|
75
|
+
Claude Code payloads have the structure:
|
|
76
|
+
{
|
|
77
|
+
"hook_type": "session-start", # kebab-case hook name
|
|
78
|
+
"input_data": {
|
|
79
|
+
"session_id": "abc123", # Claude calls this session_id but it's external_id
|
|
80
|
+
"machine_id": "...",
|
|
81
|
+
"cwd": "/path/to/project",
|
|
82
|
+
"transcript_path": "...",
|
|
83
|
+
# ... other hook-specific fields
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
native_event: Raw payload from Claude Code's hook_dispatcher.py
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
Unified HookEvent with normalized fields.
|
|
92
|
+
"""
|
|
93
|
+
hook_type = native_event.get("hook_type", "")
|
|
94
|
+
input_data = native_event.get("input_data", {})
|
|
95
|
+
|
|
96
|
+
# Map Claude hook type to unified event type
|
|
97
|
+
# Fall back to NOTIFICATION for unknown types (fail-open)
|
|
98
|
+
event_type = self.EVENT_MAP.get(hook_type, HookEventType.NOTIFICATION)
|
|
99
|
+
|
|
100
|
+
# Extract session_id (Claude calls it session_id but it's the external_id)
|
|
101
|
+
session_id = input_data.get("session_id", "")
|
|
102
|
+
|
|
103
|
+
# Check for failure flag in post-tool-use-failure
|
|
104
|
+
is_failure = hook_type == "post-tool-use-failure"
|
|
105
|
+
metadata = {"is_failure": is_failure} if is_failure else {}
|
|
106
|
+
|
|
107
|
+
return HookEvent(
|
|
108
|
+
event_type=event_type,
|
|
109
|
+
session_id=session_id,
|
|
110
|
+
source=self.source,
|
|
111
|
+
timestamp=datetime.now(UTC),
|
|
112
|
+
machine_id=input_data.get("machine_id"),
|
|
113
|
+
cwd=input_data.get("cwd"),
|
|
114
|
+
data=input_data,
|
|
115
|
+
metadata=metadata,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
# Map Claude Code hook types to hookEventName for hookSpecificOutput
|
|
119
|
+
HOOK_EVENT_NAME_MAP: dict[str, str] = {
|
|
120
|
+
"session-start": "SessionStart",
|
|
121
|
+
"session-end": "SessionEnd",
|
|
122
|
+
"user-prompt-submit": "UserPromptSubmit",
|
|
123
|
+
"stop": "Stop",
|
|
124
|
+
"pre-tool-use": "PreToolUse",
|
|
125
|
+
"post-tool-use": "PostToolUse",
|
|
126
|
+
"post-tool-use-failure": "PostToolUse",
|
|
127
|
+
"pre-compact": "PreCompact",
|
|
128
|
+
"subagent-start": "SubagentStart",
|
|
129
|
+
"subagent-stop": "SubagentStop",
|
|
130
|
+
"permission-request": "PermissionRequest",
|
|
131
|
+
"notification": "Notification",
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
def translate_from_hook_response(
|
|
135
|
+
self, response: HookResponse, hook_type: str | None = None
|
|
136
|
+
) -> dict[str, Any]:
|
|
137
|
+
"""Convert HookResponse to Claude Code's expected format.
|
|
138
|
+
|
|
139
|
+
Claude Code expects responses in this format:
|
|
140
|
+
{
|
|
141
|
+
"continue": True/False, # Whether to continue execution
|
|
142
|
+
"stopReason": "...", # Reason if stopped (optional)
|
|
143
|
+
"decision": "approve"/"block", # Tool decision
|
|
144
|
+
"hookSpecificOutput": { # Hook-specific data
|
|
145
|
+
"hookEventName": "SessionStart", # Required!
|
|
146
|
+
"additionalContext": "..." # Context to inject into Claude
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
response: Unified HookResponse from HookManager.
|
|
152
|
+
hook_type: Original Claude Code hook type (e.g., "session-start")
|
|
153
|
+
Used to set hookEventName in hookSpecificOutput.
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
Dict in Claude Code's expected format.
|
|
157
|
+
"""
|
|
158
|
+
# Map decision to continue flag
|
|
159
|
+
# Both "deny" and "block" should stop execution
|
|
160
|
+
should_continue = response.decision not in ("deny", "block")
|
|
161
|
+
|
|
162
|
+
result: dict[str, Any] = {
|
|
163
|
+
"continue": should_continue,
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
# Add stop reason if denied or blocked
|
|
167
|
+
if response.decision in ("deny", "block") and response.reason:
|
|
168
|
+
result["stopReason"] = response.reason
|
|
169
|
+
|
|
170
|
+
# Add system_message to systemMessage (for system-level messages)
|
|
171
|
+
# Note: response.context goes to additionalContext below (visible to model)
|
|
172
|
+
if response.system_message:
|
|
173
|
+
result["systemMessage"] = response.system_message
|
|
174
|
+
|
|
175
|
+
# Add tool decision for pre-tool-use hooks
|
|
176
|
+
# Claude Code schema: decision uses "approve"/"block"
|
|
177
|
+
# permissionDecision uses "allow"/"deny"/"ask"
|
|
178
|
+
if response.decision in ("deny", "block"):
|
|
179
|
+
result["decision"] = "block"
|
|
180
|
+
else:
|
|
181
|
+
result["decision"] = "approve"
|
|
182
|
+
|
|
183
|
+
# Add hookSpecificOutput with additionalContext for model context injection
|
|
184
|
+
# This includes both workflow inject_context AND session identifiers
|
|
185
|
+
hook_event_name = self.HOOK_EVENT_NAME_MAP.get(hook_type or "", "Unknown")
|
|
186
|
+
additional_context_parts: list[str] = []
|
|
187
|
+
|
|
188
|
+
# Add workflow-injected context (from inject_context action)
|
|
189
|
+
# This is the primary way to inject context visible to the model
|
|
190
|
+
if response.context:
|
|
191
|
+
additional_context_parts.append(response.context)
|
|
192
|
+
|
|
193
|
+
# Add session identifiers from metadata
|
|
194
|
+
if response.metadata:
|
|
195
|
+
session_id = response.metadata.get("session_id")
|
|
196
|
+
if session_id:
|
|
197
|
+
# Build context with all available identifiers
|
|
198
|
+
context_lines = [f"session_id: {session_id}"]
|
|
199
|
+
if response.metadata.get("parent_session_id"):
|
|
200
|
+
context_lines.append(
|
|
201
|
+
f"parent_session_id: {response.metadata['parent_session_id']}"
|
|
202
|
+
)
|
|
203
|
+
if response.metadata.get("machine_id"):
|
|
204
|
+
context_lines.append(f"machine_id: {response.metadata['machine_id']}")
|
|
205
|
+
if response.metadata.get("project_id"):
|
|
206
|
+
context_lines.append(f"project_id: {response.metadata['project_id']}")
|
|
207
|
+
# Add terminal context (non-null values only)
|
|
208
|
+
if response.metadata.get("terminal_term_program"):
|
|
209
|
+
context_lines.append(f"terminal: {response.metadata['terminal_term_program']}")
|
|
210
|
+
if response.metadata.get("terminal_tty"):
|
|
211
|
+
context_lines.append(f"tty: {response.metadata['terminal_tty']}")
|
|
212
|
+
if response.metadata.get("terminal_parent_pid"):
|
|
213
|
+
context_lines.append(f"parent_pid: {response.metadata['terminal_parent_pid']}")
|
|
214
|
+
# Add terminal-specific session IDs (only one will be present)
|
|
215
|
+
for key in [
|
|
216
|
+
"terminal_iterm_session_id",
|
|
217
|
+
"terminal_term_session_id",
|
|
218
|
+
"terminal_kitty_window_id",
|
|
219
|
+
"terminal_tmux_pane",
|
|
220
|
+
"terminal_vscode_terminal_id",
|
|
221
|
+
"terminal_alacritty_socket",
|
|
222
|
+
]:
|
|
223
|
+
if response.metadata.get(key):
|
|
224
|
+
# Use friendlier names in output
|
|
225
|
+
friendly_name = key.replace("terminal_", "").replace("_", " ")
|
|
226
|
+
context_lines.append(f"{friendly_name}: {response.metadata[key]}")
|
|
227
|
+
additional_context_parts.append("\n".join(context_lines))
|
|
228
|
+
|
|
229
|
+
# Build hookSpecificOutput if we have any context to inject
|
|
230
|
+
if additional_context_parts:
|
|
231
|
+
result["hookSpecificOutput"] = {
|
|
232
|
+
"hookEventName": hook_event_name,
|
|
233
|
+
"additionalContext": "\n\n".join(additional_context_parts),
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return result
|
|
237
|
+
|
|
238
|
+
def handle_native(
|
|
239
|
+
self, native_event: dict[str, Any], hook_manager: "HookManager"
|
|
240
|
+
) -> dict[str, Any]:
|
|
241
|
+
"""Main entry point for HTTP endpoint.
|
|
242
|
+
|
|
243
|
+
Strangler fig pattern:
|
|
244
|
+
- Phase 2A-2B: Delegates to existing execute() — validates translation only
|
|
245
|
+
- Phase 2C+: Calls new handle() with HookEvent
|
|
246
|
+
|
|
247
|
+
Note: This method is synchronous for Phase 2A-2B compatibility with
|
|
248
|
+
the existing execute() method. In Phase 2C+, it will become async
|
|
249
|
+
when handle() is implemented as async.
|
|
250
|
+
|
|
251
|
+
Args:
|
|
252
|
+
native_event: Raw payload from Claude Code's hook_dispatcher.py
|
|
253
|
+
hook_manager: HookManager instance for processing.
|
|
254
|
+
|
|
255
|
+
Returns:
|
|
256
|
+
Response dict in Claude Code's expected format.
|
|
257
|
+
"""
|
|
258
|
+
# Always translate (validates our mapping is correct)
|
|
259
|
+
hook_event = self.translate_to_hook_event(native_event)
|
|
260
|
+
|
|
261
|
+
# Phase 2C+: Use new HookEvent-based handler
|
|
262
|
+
# Legacy execute() path removed as HookManager.execute is deprecated/removed.
|
|
263
|
+
hook_type = native_event.get("hook_type", "")
|
|
264
|
+
hook_response = hook_manager.handle(hook_event)
|
|
265
|
+
return self.translate_from_hook_response(hook_response, hook_type=hook_type)
|
|
266
|
+
|
|
267
|
+
def set_legacy_mode(self, use_legacy: bool) -> None:
|
|
268
|
+
"""Toggle between legacy and new code paths.
|
|
269
|
+
|
|
270
|
+
This method is used during the strangler fig migration to switch
|
|
271
|
+
between delegating to execute() and calling handle() directly.
|
|
272
|
+
|
|
273
|
+
Args:
|
|
274
|
+
use_legacy: If True, use legacy execute() path. If False, use new handle() path.
|
|
275
|
+
"""
|
|
276
|
+
self._use_legacy = use_legacy
|